{"_id":"@bryanguffey/astro-standard-site","_rev":"3-bd0321576f039be5c1f2c279d6c70e77","name":"@bryanguffey/astro-standard-site","dist-tags":{"latest":"1.0.3"},"versions":{"1.0.0":{"name":"@bryanguffey/astro-standard-site","version":"1.0.0","keywords":["astro","astro-integration","atproto","bluesky","standard.site","leaflet","whitewind","blog","ssg","comments","federation"],"author":{"url":"https://bryanguffey.com","name":"Bryan Guffey","email":"bryan@makeworkbetter.io"},"license":"MIT","_id":"@bryanguffey/astro-standard-site@1.0.0","maintainers":[{"name":"bryanguffey","email":"bryan@makeworkbetter.io"}],"homepage":"https://github.com/musicjunkieg/astro-standard-site#readme","bugs":{"url":"https://github.com/musicjunkieg/astro-standard-site/issues"},"dist":{"shasum":"11ffe1b67a2a18471a4ded50835a6531bf6cf5c0","tarball":"https://registry.npmjs.org/@bryanguffey/astro-standard-site/-/astro-standard-site-1.0.0.tgz","fileCount":31,"integrity":"sha512-jQDB8s/Rop7mxBH+UOwuMFW0GIeq5cUyOKZithm+chPJjMd//krNs4RUpDDpqEorA2PbwgKnCsNfmOUsA37Ahw==","signatures":[{"sig":"MEUCIQDvAaX8LYY19UdEw8GXeq0RfR72UJHT4K06Ox64RM81ZgIgPc+ma2fkPTfVVbeZMycs/5yg2D8dimQBx+KukLhWy5Q=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":165752},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./loader":{"types":"./dist/loader.d.ts","import":"./dist/loader.js"},"./content":{"types":"./dist/content.d.ts","import":"./dist/content.js"},"./comments":{"types":"./dist/comments.d.ts","import":"./dist/comments.js"},"./publisher":{"types":"./dist/publisher.d.ts","import":"./dist/publisher.js"},"./components/*":"./components/*"},"gitHead":"f49b5a6259ab83746063b3b7024245334c4e1cdf","scripts":{"test":"vitest run","build":"tsc","test:watch":"vitest","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"bryanguffey","email":"bryan@makeworkbetter.io"},"repository":{"url":"git+https://github.com/musicjunkieg/astro-standard-site.git","type":"git"},"_npmVersion":"11.7.0","description":"Astro integration for standard.site - unified ATProto longform publishing","directories":{},"_nodeVersion":"24.12.0","dependencies":{"zod":"^3.24.0","@atproto/api":"^0.15.0"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.16","typescript":"^5.7.0","@types/node":"^22.0.0"},"peerDependencies":{"astro":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/astro-standard-site_1.0.0_1767564096488_0.40429417421643477","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@bryanguffey/astro-standard-site","version":"1.0.2","keywords":["astro","astro-integration","atproto","bluesky","standard.site","leaflet","whitewind","blog","ssg","comments","federation"],"author":{"url":"https://bryanguffey.com","name":"Bryan Guffey","email":"bryan@makeworkbetter.io"},"license":"MIT","_id":"@bryanguffey/astro-standard-site@1.0.2","maintainers":[{"name":"bryanguffey","email":"bryan@makeworkbetter.io"}],"homepage":"https://github.com/musicjunkieg/astro-standard-site#readme","bugs":{"url":"https://github.com/musicjunkieg/astro-standard-site/issues"},"dist":{"shasum":"01612862b0c03688f03447f9917449c9d88867a0","tarball":"https://registry.npmjs.org/@bryanguffey/astro-standard-site/-/astro-standard-site-1.0.2.tgz","fileCount":31,"integrity":"sha512-jU06mYdKBOjkiNMtK+IEDJWiYBYB8pDtO2jTY8aMpME7jcPU4heBdnlmF7L+jyoWocbB6rxbTbESl2cnT4Ef+g==","signatures":[{"sig":"MEUCIBZO3ehgjidVukDU1VTxceUme+5ICgqhTC/wEUZMllVrAiEAkoEMkUxi8cHZs5v1gseDVM2i9BBuyMsQnstsBDAtqIE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bryanguffey%2fastro-standard-site@1.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":173492},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./loader":{"types":"./dist/loader.d.ts","import":"./dist/loader.js"},"./content":{"types":"./dist/content.d.ts","import":"./dist/content.js"},"./comments":{"types":"./dist/comments.d.ts","import":"./dist/comments.js"},"./publisher":{"types":"./dist/publisher.d.ts","import":"./dist/publisher.js"},"./components/*":"./components/*"},"gitHead":"4a29861c0de9f8cb20d59ef460bdcdfb951602be","scripts":{"test":"vitest run","build":"tsc","test:watch":"vitest","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2cb773c5-9922-4b46-b1f6-4b7bbfe6e75e"}},"repository":{"url":"git+https://github.com/musicjunkieg/astro-standard-site.git","type":"git"},"_npmVersion":"11.7.0","description":"Astro integration for standard.site - unified ATProto longform publishing","directories":{},"_nodeVersion":"20.19.6","dependencies":{"zod":"^3.24.0","@atproto/api":"^0.15.0"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.16","typescript":"^5.7.0","@types/node":"^22.0.0"},"peerDependencies":{"astro":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/astro-standard-site_1.0.2_1767572892581_0.6003643915735934","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@bryanguffey/astro-standard-site","version":"1.0.3","description":"Astro integration for standard.site - unified ATProto longform publishing","keywords":["astro","astro-integration","atproto","bluesky","standard.site","leaflet","whitewind","blog","ssg","comments","federation"],"homepage":"https://github.com/musicjunkieg/astro-standard-site#readme","bugs":{"url":"https://github.com/musicjunkieg/astro-standard-site/issues"},"repository":{"type":"git","url":"git+https://github.com/musicjunkieg/astro-standard-site.git"},"license":"MIT","author":{"name":"Bryan Guffey","email":"bryan@makeworkbetter.io","url":"https://bryanguffey.com"},"type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./loader":{"types":"./dist/loader.d.ts","import":"./dist/loader.js"},"./publisher":{"types":"./dist/publisher.d.ts","import":"./dist/publisher.js"},"./content":{"types":"./dist/content.d.ts","import":"./dist/content.js"},"./comments":{"types":"./dist/comments.d.ts","import":"./dist/comments.js"},"./components/*":"./components/*"},"main":"./dist/index.js","types":"./dist/index.d.ts","scripts":{"build":"tsc","test":"vitest run","test:watch":"vitest","prepublishOnly":"npm run build && npm test"},"dependencies":{"@atproto/api":"^0.15.0","zod":"^3.24.0"},"devDependencies":{"@types/node":"^22.0.0","typescript":"^5.7.0","vitest":"^4.0.16"},"peerDependencies":{"astro":"^5.0.0"},"gitHead":"b03a0b64faf1b84a67763baa0334fce78091a459","_id":"@bryanguffey/astro-standard-site@1.0.3","_nodeVersion":"20.19.6","_npmVersion":"11.7.0","dist":{"integrity":"sha512-+4//yk8rRD3ZfC9uOKY7OHu7Shm5qMZSgFTIcDs079hcoV42yF8mK8O0AI6QMFb7iMCPqN9rO+leP+PMEEmR3A==","shasum":"6fbc941702fafdc84253856654afc81a8eea66c1","tarball":"https://registry.npmjs.org/@bryanguffey/astro-standard-site/-/astro-standard-site-1.0.3.tgz","fileCount":31,"unpackedSize":173500,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bryanguffey%2fastro-standard-site@1.0.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIF7sQt5BPsFzJNi/8hRwxr40NtlV+xD7BNSQpUDWi/70AiEAsoxwyUfCZe+pqjvbR+ljcy9v/frVHG3R7gt34dUiWW4="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2cb773c5-9922-4b46-b1f6-4b7bbfe6e75e"}},"directories":{},"maintainers":[{"name":"bryanguffey","email":"bryan@makeworkbetter.io"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/astro-standard-site_1.0.3_1767574703701_0.16434341087577886"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-04T22:01:36.326Z","modified":"2026-01-05T00:58:24.187Z","1.0.0":"2026-01-04T22:01:36.631Z","1.0.2":"2026-01-05T00:28:12.730Z","1.0.3":"2026-01-05T00:58:23.862Z"},"bugs":{"url":"https://github.com/musicjunkieg/astro-standard-site/issues"},"author":{"name":"Bryan Guffey","email":"bryan@makeworkbetter.io","url":"https://bryanguffey.com"},"license":"MIT","homepage":"https://github.com/musicjunkieg/astro-standard-site#readme","keywords":["astro","astro-integration","atproto","bluesky","standard.site","leaflet","whitewind","blog","ssg","comments","federation"],"repository":{"type":"git","url":"git+https://github.com/musicjunkieg/astro-standard-site.git"},"description":"Astro integration for standard.site - unified ATProto longform publishing","maintainers":[{"name":"bryanguffey","email":"bryan@makeworkbetter.io"}],"readme":"# astro-standard-site\n\nPublish your Astro blog to the federated web. This package connects your blog to [ATProto](https://atproto.com/) (the protocol behind Bluesky) using the [standard.site](https://standard.site/) schema, enabling:\n\n- **Cross-platform publishing** — Your posts appear on Leaflet, WhiteWind, and other ATProto readers\n- **Federated comments** — Display Bluesky replies as comments on your blog\n- **Verified ownership** — Prove you own your content with cryptographic verification\n\nCreated with love by *Bryan Guffey*\n## Installation\n\n```bash\nnpm install @bryanguffey/astro-standard-site\n```\n\n## Use Cases\n\nThis package supports multiple workflows:\n\n| You want to... | Use |\n|----------------|-----|\n| Show Bluesky replies as comments | `<Comments />` component |\n| Publish Astro posts to ATProto | `StandardSitePublisher` |\n| Pull ATProto posts into Astro | `standardSiteLoader` |\n| Verify you own your content | Verification helpers |\n\nYou can mix and match — use comments without publishing, or publish without loading, etc.\n\n## Quick Start\n\n### 1. Display Bluesky Comments on Your Blog\n\nThe fastest way to get started — add federated comments to your existing posts.\n\n**Add to your blog post layout:**\n\n```astro\n---\n// src/layouts/BlogPost.astro\nimport Comments from '@bryanguffey/astro-standard-site/components/Comments.astro';\n\nconst { bskyPostUri } = Astro.props.frontmatter;\n---\n\n<article>\n  <slot />\n</article>\n\n{bskyPostUri && (\n  <Comments \n    bskyPostUri={bskyPostUri}\n    canonicalUrl={Astro.url.href}\n  />\n)}\n```\n\n**Add the field to your content schema:**\n\n```ts\n// src/content/config.ts\nimport { defineCollection, z } from 'astro:content';\n\nconst blog = defineCollection({\n  schema: z.object({\n    title: z.string(),\n    date: z.date(),\n    // ... your existing fields\n    bskyPostUri: z.string().optional(),\n  }),\n});\n\nexport const collections = { blog };\n```\n\n**Link a post to Bluesky:**\n\n1. Publish your blog post\n2. Share it on Bluesky\n3. Copy the post's AT-URI (click ··· → \"Copy post link\", then convert to AT-URI format)\n4. Add it to your post's frontmatter:\n\n```yaml\n---\ntitle: \"My First Federated Post\"\ndate: 2026-01-15\nbskyPostUri: \"at://did:plc:your-did/app.bsky.feed.post/abc123def\"\n---\n```\n\n5. Rebuild your site — comments now appear!\n\n> **Tip:** To get the AT-URI from a Bluesky URL like `https://bsky.app/profile/you.bsky.social/post/abc123def`, the format is `at://did:plc:YOUR_DID/app.bsky.feed.post/abc123def`. You can find your DID at [bsky.app/settings](https://bsky.app/settings).\n\n---\n\n## Full Setup: Publish to ATProto\n\nTo publish your posts *to* ATProto (not just display comments), you'll need:\n\n1. A Bluesky account (or any ATProto PDS)\n2. An [app password](https://bsky.app/settings/app-passwords)\n\n### Create a Publication\n\nFirst, create a publication record that represents your blog:\n\n```ts\n// scripts/create-publication.ts\nimport { StandardSitePublisher } from '@bryanguffey/astro-standard-site';\n\nconst publisher = new StandardSitePublisher({\n  handle: 'you.bsky.social',\n  appPassword: process.env.ATPROTO_APP_PASSWORD!,\n});\n\nawait publisher.login();\n\nconst result = await publisher.publishPublication({\n  name: 'My Awesome Blog',\n  url: 'https://yourblog.com',\n  description: 'Thoughts on code, life, and everything',\n  // Optional: customize your theme colors (RGB 0-255)\n  basicTheme: {\n    background: { r: 13, g: 17, b: 23 },\n    foreground: { r: 230, g: 237, b: 243 },\n    accent: { r: 74, g: 124, b: 155 },\n    accentForeground: { r: 255, g: 255, b: 255 },\n  },\n});\n\nconsole.log('Publication created!');\nconsole.log('AT-URI:', result.uri);\nconsole.log('Save this rkey for verification:', result.uri.split('/').pop());\n```\n\nRun it once:\n\n```bash\nATPROTO_APP_PASSWORD=\"xxxx-xxxx-xxxx-xxxx\" npx tsx scripts/create-publication.ts\n```\n\n### Publish Posts\n\nCreate a sync script to publish your Astro posts:\n\n```ts\n// scripts/sync-to-atproto.ts\nimport { StandardSitePublisher, transformContent } from '@bryanguffey/astro-standard-site';\nimport { getCollection } from 'astro:content';\n\nconst publisher = new StandardSitePublisher({\n  handle: 'you.bsky.social',\n  appPassword: process.env.ATPROTO_APP_PASSWORD!,\n});\n\nawait publisher.login();\n\nconst posts = await getCollection('blog');\n\nfor (const post of posts) {\n  // Transform content for ATProto compatibility\n  const transformed = transformContent(post.body, {\n    baseUrl: 'https://yourblog.com',\n  });\n\n  const result = await publisher.publishDocument({\n    site: 'https://yourblog.com',\n    path: `/blog/${post.slug}`,\n    title: post.data.title,\n    description: post.data.description,\n    content: {\n      $type: 'site.standard.content.markdown',\n      text: transformed.markdown,\n      version: '1.0',\n    },\n    textContent: transformed.textContent,\n    publishedAt: post.data.date.toISOString(),\n    tags: post.data.tags,\n  });\n\n  console.log(`Published: ${post.data.title}`);\n  console.log(`  → ${result.uri}`);\n}\n```\n\n### Set Up Verification\n\nVerification lets platforms confirm you own the content. Create a well-known endpoint:\n\n```ts\n// src/pages/.well-known/site.standard.publication.ts\nimport type { APIRoute } from 'astro';\nimport { generatePublicationWellKnown } from '@bryanguffey/astro-standard-site';\n\nexport const GET: APIRoute = () => {\n  return new Response(\n    generatePublicationWellKnown({\n      did: 'did:plc:your-did-here',           // Your DID\n      publicationRkey: '3abc123xyz789',        // From create-publication output\n    }),\n    { headers: { 'Content-Type': 'text/plain' } }\n  );\n};\n```\n\nAfter deploying, verify it works:\n\n```bash\ncurl https://yourblog.com/.well-known/site.standard.publication\n# Should output: at://did:plc:xxx/site.standard.publication/3abc123xyz789\n```\n\n---\n\n## Components\n\n### `<Comments />`\n\nDisplays Bluesky replies as a comment section.\n\n```astro\n<Comments \n  bskyPostUri=\"at://did:plc:xxx/app.bsky.feed.post/abc123\"\n  canonicalUrl=\"https://yourblog.com/post/my-post\"\n  maxDepth={3}\n  title=\"Discussion\"\n  showReplyLink={true}\n  class=\"my-custom-class\"\n/>\n```\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `bskyPostUri` | `string` | — | AT-URI of the Bluesky announcement post |\n| `canonicalUrl` | `string` | — | URL of your blog post (for mention search) |\n| `maxDepth` | `number` | `3` | Maximum nesting depth for replies |\n| `title` | `string` | `\"Comments\"` | Section heading |\n| `showReplyLink` | `boolean` | `true` | Show \"Reply on Bluesky\" link |\n| `class` | `string` | — | Custom CSS class |\n\n**Styling:** The component uses CSS custom properties that inherit from your site's theme:\n\n```css\n--color-border-soft\n--color-text-primary\n--color-text-secondary\n--color-text-muted\n--color-text-link\n--color-bg-elevated\n--space-xs, --space-sm, --space-md, --space-lg, --space-xl, --space-2xl\n```\n\n---\n\n## API Reference\n\n### `StandardSitePublisher`\n\nHandles authentication and publishing to ATProto.\n\n```ts\nimport { StandardSitePublisher } from '@bryanguffey/astro-standard-site';\n\nconst publisher = new StandardSitePublisher({\n  handle: 'you.bsky.social',      // Your handle\n  appPassword: 'xxxx-xxxx-xxxx',  // App password (not your main password!)\n  // Optional: specify PDS directly (auto-resolved from DID by default)\n  pdsUrl: 'https://bsky.social',\n});\n\nawait publisher.login();\n```\n\n#### `publishDocument(input)`\n\nPublish a blog post.\n\n```ts\nconst result = await publisher.publishDocument({\n  // Required\n  site: 'https://yourblog.com',\n  title: 'My Post Title',\n  publishedAt: '2026-01-15T12:00:00Z',\n  \n  // Recommended\n  path: '/blog/my-post',\n  description: 'A short excerpt...',\n  content: {\n    $type: 'site.standard.content.markdown',\n    text: '# Full markdown content...',\n    version: '1.0',\n  },\n  textContent: 'Plain text version for search indexing',\n  \n  // Optional\n  updatedAt: '2026-01-16T12:00:00Z',\n  tags: ['astro', 'atproto'],\n});\n\nconsole.log(result.uri);  // at://did:plc:xxx/site.standard.document/3abc...\nconsole.log(result.cid);  // Content hash\n```\n\n#### `publishPublication(input)`\n\nCreate or update your publication metadata.\n\n```ts\nconst result = await publisher.publishPublication({\n  name: 'My Blog',\n  url: 'https://yourblog.com',\n  description: 'What this blog is about',\n  basicTheme: {\n    background: { r: 255, g: 255, b: 255 },\n    foreground: { r: 0, g: 0, b: 0 },\n    accent: { r: 0, g: 102, b: 204 },\n    accentForeground: { r: 255, g: 255, b: 255 },\n  },\n  preferences: {\n    showInDiscover: true,\n  },\n});\n```\n\n### `transformContent(markdown, options)`\n\nTransform markdown for ATProto compatibility.\n\n```ts\nimport { transformContent } from '@bryanguffey/astro-standard-site';\n\nconst result = transformContent(markdownString, {\n  baseUrl: 'https://yourblog.com',  // For resolving relative links\n});\n\nresult.markdown;      // Cleaned markdown (sidenotes converted, links resolved)\nresult.textContent;   // Plain text for search indexing\nresult.wordCount;     // Number of words\nresult.readingTime;   // Estimated minutes to read\n```\n\n**What it does:**\n\n- Converts HTML sidenotes to markdown blockquotes\n- Resolves relative links (`/about` → `https://yourblog.com/about`)\n- Strips markdown to plain text for the `textContent` field\n- Calculates word count and reading time\n\n### `standardSiteLoader(config)`\n\nAstro Content Layer loader — pull YOUR content written on other platforms (Leaflet, WhiteWind) into your Astro blog.\n\n**Primary use case:** You write posts on Leaflet, and want them to appear on your Astro site — but NOT the posts you published *from* your Astro site to ATProto.\n\n```ts\n// src/content/config.ts\nimport { defineCollection } from 'astro:content';\nimport { standardSiteLoader } from '@bryanguffey/astro-standard-site';\n\nconst federated = defineCollection({\n  loader: standardSiteLoader({\n    repo: 'me.bsky.social',              // Your ATProto handle or DID\n    excludeSite: 'https://myblog.com',   // Skip posts published FROM your Astro blog\n  }),\n});\n\nexport const collections = { federated };\n```\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `repo` | `string` | **Required.** ATProto handle or DID to load from |\n| `excludeSite` | `string` | Skip documents with this site URL (your blog) |\n| `publication` | `string` | Only load documents from this specific site |\n| `limit` | `number` | Max documents to fetch (default: 100) |\n| `service` | `string` | PDS endpoint (default: public API) |\n\n**Using loaded documents:**\n\n```astro\n---\n// src/pages/federated/[...slug].astro\nimport { getCollection } from 'astro:content';\n\nconst posts = await getCollection('federated');\n---\n\n{posts.map(post => (\n  <article>\n    <h2><a href={post.data.url}>{post.data.title}</a></h2>\n    <time>{post.data.publishedAt.toLocaleDateString()}</time>\n    <p>{post.data.description}</p>\n    \n    {/* For plain text display */}\n    {post.data.textContent && (\n      <div>{post.data.textContent}</div>\n    )}\n    \n    {/* Or handle markdown content specifically */}\n    {post.data.content?.$type === 'site.standard.content.markdown' && (\n      <div set:html={marked(post.data.content.text)} />\n    )}\n  </article>\n))}\n```\n\n**Loaded document fields:**\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `id` | `string` | Record key (TID) |\n| `uri` | `string` | Full AT-URI |\n| `title` | `string` | Document title |\n| `site` | `string` | Source publication URL |\n| `publishedAt` | `Date` | Publication date |\n| `path` | `string?` | Path segment |\n| `url` | `string?` | Full URL (site + path) |\n| `description` | `string?` | Excerpt |\n| `tags` | `string[]` | Categories |\n| `textContent` | `string?` | Plain text for display/search |\n| `content` | `unknown` | Platform-specific content (see below) |\n| `_raw` | `Document` | Full raw record |\n\n**About the `content` field:**\n\nThe `content` field is an open union — different platforms use different types. Use `textContent` for simple display, or check `content.$type` for rich rendering:\n\n```ts\n// Markdown content (Leaflet, this package)\nif (post.data.content?.$type === 'site.standard.content.markdown') {\n  const markdown = post.data.content.text;\n}\n\n// Or just use textContent for plain text\nconst plainText = post.data.textContent;\n```\n\n### `publicationLoader(config)`\n\nLoad publication metadata (blog info, not posts):\n\n```ts\nconst publications = defineCollection({\n  loader: publicationLoader({ repo: 'someone.bsky.social' }),\n});\n```\n\n### `fetchComments(options)`\n\nFetch comments programmatically (used internally by the Comments component).\n\n```ts\nimport { fetchComments } from '@bryanguffey/astro-standard-site';\n\nconst comments = await fetchComments({\n  bskyPostUri: 'at://did:plc:xxx/app.bsky.feed.post/abc123',\n  canonicalUrl: 'https://yourblog.com/post/my-post',\n  maxDepth: 3,\n});\n\n// Returns array of Comment objects with nested replies\n```\n\n### Verification Helpers\n\n```ts\nimport { \n  generatePublicationWellKnown,\n  generateDocumentLinkTag,\n  getDocumentAtUri,\n  getPublicationAtUri,\n  parseAtUri,\n} from '@bryanguffey/astro-standard-site';\n\n// For /.well-known/site.standard.publication endpoint\ngeneratePublicationWellKnown({ did: '...', publicationRkey: '...' });\n// → \"at://did:plc:xxx/site.standard.publication/abc123\"\n\n// For <head> tag to verify individual documents\ngenerateDocumentLinkTag({ did: '...', documentRkey: '...' });\n// → '<link rel=\"site.standard.document\" href=\"at://...\">'\n\n// Build AT-URIs\ngetDocumentAtUri('did:plc:xxx', '3abc123');\n// → \"at://did:plc:xxx/site.standard.document/3abc123\"\n\n// Parse AT-URIs\nparseAtUri('at://did:plc:xxx/site.standard.document/3abc123');\n// → { did: 'did:plc:xxx', collection: 'site.standard.document', rkey: '3abc123' }\n```\n\n---\n\n## Workflow Tips\n\n### Getting Your DID\n\nYour DID is your permanent identifier on ATProto. Find it at:\n- [bsky.app/settings](https://bsky.app/settings) → scroll to \"DID\"\n- Or: `https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=you.bsky.social`\n\n### Getting AT-URIs from Bluesky URLs\n\nBluesky web URLs look like:\n```\nhttps://bsky.app/profile/you.bsky.social/post/3abc123xyz\n```\n\nThe AT-URI format is:\n```\nat://did:plc:YOUR_DID/app.bsky.feed.post/3abc123xyz\n```\n\n### Viewing Your Published Content\n\nAfter publishing, view your records at:\n- `https://pdsls.dev/at://YOUR_DID/site.standard.publication`\n- `https://pdsls.dev/at://YOUR_DID/site.standard.document`\n\n### Comments Appear at Build Time\n\nComments are fetched when you build your site (static). To show new comments, rebuild and redeploy. For high-traffic sites, consider scheduled rebuilds or on-demand ISR.\n\n---\n\n## Troubleshooting\n\n### \"Failed to resolve handle\"\n\n- Check your handle is correct\n- Verify your PDS is reachable\n- Make sure you're using an app password, not your main password\n\n### \"Schema validation failed\" / \"invalid TID\"\n\nRecord keys must be TIDs (timestamp identifiers). The package generates these automatically — if you see this error, you may be using an older version or passing a custom rkey.\n\n### Comments not appearing\n\n1. Verify the `bskyPostUri` is correct (AT-URI format, not web URL)\n2. Check the Bluesky post exists and has public replies\n3. Rebuild your site after adding the URI\n\n### Verification endpoint returning 404\n\n- Ensure the file is at `src/pages/.well-known/site.standard.publication.ts`\n- The `.well-known` folder needs to be inside `pages/`\n- Check your hosting platform allows `.well-known` paths\n\n---\n\n## How It Works\n\nThis package implements the [standard.site](https://standard.site/) specification, which defines a common schema for longform content on ATProto. This means:\n\n1. **Your content is portable** — It lives in your ATProto repository, not locked to any platform\n2. **Multiple readers** — Leaflet, WhiteWind, and future apps can all display your posts\n3. **Federated engagement** — Comments and likes from any ATProto app appear on your blog\n4. **Verified ownership** — The `.well-known` endpoint proves you control the content\n\n---\n\n## Links\n\n- [standard.site specification](https://standard.site/)\n- [ATProto documentation](https://atproto.com/)\n- [Bluesky](https://bsky.app/)\n- [Leaflet](https://leaflet.pub/) — ATProto blog reader\n- [WhiteWind](https://whitewind.pages.dev/) — Another ATProto blog platform\n- [pdsls.dev](https://pdsls.dev/) — Browse ATProto records\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md"}