Adds option for custom bskyPost text rather than just title, desc, and url. Also removes url from post text to save graphemes. 501c7bd6
Anuj Ahooja · 2026-02-24 13:24 3 file(s) · +18 −39
packages/cli/src/commands/publish.ts +1 −0
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,
packages/cli/src/lib/atproto.ts +16 −39
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
	};
packages/cli/src/lib/types.ts +1 −0
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;