A CLI for publishing standard.site documents to ATProto sequoia.pub
standard site lexicon cli publishing

Adds option for custom bskyPost text rather than just title, desc, and url. Also removes url from post text to save graphemes.

authored by quillmatiq.com and committed by tangled.org 501c7bd6 f6d1e094

+18 -39
+1
packages/cli/src/commands/publish.ts
··· 359 359 bskyPostRef = await createBlueskyPost(agent, { 360 360 title: post.frontmatter.title, 361 361 description: post.frontmatter.description, 362 + bskyPost: post.frontmatter.bskyPost, 362 363 canonicalUrl, 363 364 coverImage, 364 365 publishedAt: post.frontmatter.publishDate,
+16 -39
packages/cli/src/lib/atproto.ts
··· 569 569 export interface CreateBlueskyPostOptions { 570 570 title: string; 571 571 description?: string; 572 + bskyPost?: string; 572 573 canonicalUrl: string; 573 574 coverImage?: BlobObject; 574 575 publishedAt: string; // Used as createdAt for the post ··· 612 613 agent: Agent, 613 614 options: CreateBlueskyPostOptions, 614 615 ): Promise<StrongRef> { 615 - const { title, description, canonicalUrl, coverImage, publishedAt } = options; 616 + const { title, description, bskyPost, canonicalUrl, coverImage, publishedAt } = options; 616 617 617 - // Build post text: title + description + URL 618 + // Build post text: title + description 618 619 // Max 300 graphemes for Bluesky posts 619 620 const MAX_GRAPHEMES = 300; 620 621 621 622 let postText: string; 622 - const urlPart = `\n\n${canonicalUrl}`; 623 - const urlGraphemes = countGraphemes(urlPart); 624 623 625 - if (description) { 626 - // Try: title + description + URL 627 - const fullText = `${title}\n\n${description}${urlPart}`; 624 + if (bskyPost) { 625 + // Custom bsky post overrides any default behavior 626 + postText = bskyPost; 627 + } 628 + else if (description) { 629 + // Try: title + description 630 + const fullText = `${title}\n\n${description}`; 628 631 if (countGraphemes(fullText) <= MAX_GRAPHEMES) { 629 632 postText = fullText; 630 633 } else { ··· 632 635 const availableForDesc = 633 636 MAX_GRAPHEMES - 634 637 countGraphemes(title) - 635 - countGraphemes("\n\n") - 636 - urlGraphemes - 637 638 countGraphemes("\n\n"); 638 639 if (availableForDesc > 10) { 639 640 const truncatedDesc = truncateToGraphemes( 640 641 description, 641 642 availableForDesc, 642 643 ); 643 - postText = `${title}\n\n${truncatedDesc}${urlPart}`; 644 + postText = `${title}\n\n${truncatedDesc}`; 644 645 } else { 645 - // Just title + URL 646 - postText = `${title}${urlPart}`; 646 + // Just title 647 + postText = `${title}`; 647 648 } 648 649 } 649 650 } else { 650 - // Just title + URL 651 - postText = `${title}${urlPart}`; 651 + // Just title 652 + postText = `${title}`; 652 653 } 653 654 654 - // Final truncation if still too long (shouldn't happen but safety check) 655 + // Final truncation in case title or bskyPost are longer than expected 655 656 if (countGraphemes(postText) > MAX_GRAPHEMES) { 656 657 postText = truncateToGraphemes(postText, MAX_GRAPHEMES); 657 658 } 658 659 659 - // Calculate byte indices for the URL facet 660 - const encoder = new TextEncoder(); 661 - const urlStartInText = postText.lastIndexOf(canonicalUrl); 662 - const beforeUrl = postText.substring(0, urlStartInText); 663 - const byteStart = encoder.encode(beforeUrl).length; 664 - const byteEnd = byteStart + encoder.encode(canonicalUrl).length; 665 - 666 - // Build facets for the URL link 667 - const facets = [ 668 - { 669 - index: { 670 - byteStart, 671 - byteEnd, 672 - }, 673 - features: [ 674 - { 675 - $type: "app.bsky.richtext.facet#link", 676 - uri: canonicalUrl, 677 - }, 678 - ], 679 - }, 680 - ]; 681 - 682 660 // Build external embed 683 661 const embed: Record<string, unknown> = { 684 662 $type: "app.bsky.embed.external", ··· 698 676 const record: Record<string, unknown> = { 699 677 $type: "app.bsky.feed.post", 700 678 text: postText, 701 - facets, 702 679 embed, 703 680 createdAt: new Date(publishedAt).toISOString(), 704 681 };
+1
packages/cli/src/lib/types.ts
··· 87 87 export interface PostFrontmatter { 88 88 title: string; 89 89 description?: string; 90 + bskyPost?: string; 90 91 publishDate: string; 91 92 tags?: string[]; 92 93 ogImage?: string;