sequoia-cli 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +250 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -67309,6 +67309,11 @@ function parseFrontmatter(content, mapping) {
|
|
|
67309
67309
|
frontmatter.ogImage = raw[coverField] || raw.ogImage;
|
|
67310
67310
|
const tagsField = mapping?.tags || "tags";
|
|
67311
67311
|
frontmatter.tags = raw[tagsField] || raw.tags;
|
|
67312
|
+
const draftField = mapping?.draft || "draft";
|
|
67313
|
+
const draftValue = raw[draftField] ?? raw.draft;
|
|
67314
|
+
if (draftValue !== undefined) {
|
|
67315
|
+
frontmatter.draft = draftValue === true || draftValue === "true";
|
|
67316
|
+
}
|
|
67312
67317
|
frontmatter.atUri = raw.atUri;
|
|
67313
67318
|
return { frontmatter, body };
|
|
67314
67319
|
}
|
|
@@ -67552,6 +67557,16 @@ async function updateDocument(agent, post, atUri, config, coverImage) {
|
|
|
67552
67557
|
record
|
|
67553
67558
|
});
|
|
67554
67559
|
}
|
|
67560
|
+
function parseAtUri(atUri) {
|
|
67561
|
+
const match2 = atUri.match(/^at:\/\/([^/]+)\/([^/]+)\/(.+)$/);
|
|
67562
|
+
if (!match2)
|
|
67563
|
+
return null;
|
|
67564
|
+
return {
|
|
67565
|
+
did: match2[1],
|
|
67566
|
+
collection: match2[2],
|
|
67567
|
+
rkey: match2[3]
|
|
67568
|
+
};
|
|
67569
|
+
}
|
|
67555
67570
|
async function listDocuments(agent, publicationUri) {
|
|
67556
67571
|
const documents = [];
|
|
67557
67572
|
let cursor;
|
|
@@ -67606,6 +67621,129 @@ async function createPublication(agent, options) {
|
|
|
67606
67621
|
});
|
|
67607
67622
|
return response.data.uri;
|
|
67608
67623
|
}
|
|
67624
|
+
function countGraphemes(str) {
|
|
67625
|
+
if (typeof Intl !== "undefined" && Intl.Segmenter) {
|
|
67626
|
+
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
|
|
67627
|
+
return [...segmenter.segment(str)].length;
|
|
67628
|
+
}
|
|
67629
|
+
return [...str].length;
|
|
67630
|
+
}
|
|
67631
|
+
function truncateToGraphemes(str, maxGraphemes) {
|
|
67632
|
+
if (typeof Intl !== "undefined" && Intl.Segmenter) {
|
|
67633
|
+
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
|
|
67634
|
+
const segments = [...segmenter.segment(str)];
|
|
67635
|
+
if (segments.length <= maxGraphemes)
|
|
67636
|
+
return str;
|
|
67637
|
+
return segments.slice(0, maxGraphemes - 3).map((s) => s.segment).join("") + "...";
|
|
67638
|
+
}
|
|
67639
|
+
const chars = [...str];
|
|
67640
|
+
if (chars.length <= maxGraphemes)
|
|
67641
|
+
return str;
|
|
67642
|
+
return chars.slice(0, maxGraphemes - 3).join("") + "...";
|
|
67643
|
+
}
|
|
67644
|
+
async function createBlueskyPost(agent, options) {
|
|
67645
|
+
const { title, description, canonicalUrl, coverImage, publishedAt } = options;
|
|
67646
|
+
const MAX_GRAPHEMES = 300;
|
|
67647
|
+
let postText;
|
|
67648
|
+
const urlPart = `
|
|
67649
|
+
|
|
67650
|
+
${canonicalUrl}`;
|
|
67651
|
+
const urlGraphemes = countGraphemes(urlPart);
|
|
67652
|
+
if (description) {
|
|
67653
|
+
const fullText = `${title}
|
|
67654
|
+
|
|
67655
|
+
${description}${urlPart}`;
|
|
67656
|
+
if (countGraphemes(fullText) <= MAX_GRAPHEMES) {
|
|
67657
|
+
postText = fullText;
|
|
67658
|
+
} else {
|
|
67659
|
+
const availableForDesc = MAX_GRAPHEMES - countGraphemes(title) - countGraphemes(`
|
|
67660
|
+
|
|
67661
|
+
`) - urlGraphemes - countGraphemes(`
|
|
67662
|
+
|
|
67663
|
+
`);
|
|
67664
|
+
if (availableForDesc > 10) {
|
|
67665
|
+
const truncatedDesc = truncateToGraphemes(description, availableForDesc);
|
|
67666
|
+
postText = `${title}
|
|
67667
|
+
|
|
67668
|
+
${truncatedDesc}${urlPart}`;
|
|
67669
|
+
} else {
|
|
67670
|
+
postText = `${title}${urlPart}`;
|
|
67671
|
+
}
|
|
67672
|
+
}
|
|
67673
|
+
} else {
|
|
67674
|
+
postText = `${title}${urlPart}`;
|
|
67675
|
+
}
|
|
67676
|
+
if (countGraphemes(postText) > MAX_GRAPHEMES) {
|
|
67677
|
+
postText = truncateToGraphemes(postText, MAX_GRAPHEMES);
|
|
67678
|
+
}
|
|
67679
|
+
const encoder = new TextEncoder;
|
|
67680
|
+
const urlStartInText = postText.lastIndexOf(canonicalUrl);
|
|
67681
|
+
const beforeUrl = postText.substring(0, urlStartInText);
|
|
67682
|
+
const byteStart = encoder.encode(beforeUrl).length;
|
|
67683
|
+
const byteEnd = byteStart + encoder.encode(canonicalUrl).length;
|
|
67684
|
+
const facets = [
|
|
67685
|
+
{
|
|
67686
|
+
index: {
|
|
67687
|
+
byteStart,
|
|
67688
|
+
byteEnd
|
|
67689
|
+
},
|
|
67690
|
+
features: [
|
|
67691
|
+
{
|
|
67692
|
+
$type: "app.bsky.richtext.facet#link",
|
|
67693
|
+
uri: canonicalUrl
|
|
67694
|
+
}
|
|
67695
|
+
]
|
|
67696
|
+
}
|
|
67697
|
+
];
|
|
67698
|
+
const embed = {
|
|
67699
|
+
$type: "app.bsky.embed.external",
|
|
67700
|
+
external: {
|
|
67701
|
+
uri: canonicalUrl,
|
|
67702
|
+
title: title.substring(0, 500),
|
|
67703
|
+
description: (description || "").substring(0, 1000)
|
|
67704
|
+
}
|
|
67705
|
+
};
|
|
67706
|
+
if (coverImage) {
|
|
67707
|
+
embed.external.thumb = coverImage;
|
|
67708
|
+
}
|
|
67709
|
+
const record = {
|
|
67710
|
+
$type: "app.bsky.feed.post",
|
|
67711
|
+
text: postText,
|
|
67712
|
+
facets,
|
|
67713
|
+
embed,
|
|
67714
|
+
createdAt: new Date(publishedAt).toISOString()
|
|
67715
|
+
};
|
|
67716
|
+
const response = await agent.com.atproto.repo.createRecord({
|
|
67717
|
+
repo: agent.session.did,
|
|
67718
|
+
collection: "app.bsky.feed.post",
|
|
67719
|
+
record
|
|
67720
|
+
});
|
|
67721
|
+
return {
|
|
67722
|
+
uri: response.data.uri,
|
|
67723
|
+
cid: response.data.cid
|
|
67724
|
+
};
|
|
67725
|
+
}
|
|
67726
|
+
async function addBskyPostRefToDocument(agent, documentAtUri, bskyPostRef) {
|
|
67727
|
+
const parsed = parseAtUri(documentAtUri);
|
|
67728
|
+
if (!parsed) {
|
|
67729
|
+
throw new Error(`Invalid document URI: ${documentAtUri}`);
|
|
67730
|
+
}
|
|
67731
|
+
const existingRecord = await agent.com.atproto.repo.getRecord({
|
|
67732
|
+
repo: parsed.did,
|
|
67733
|
+
collection: parsed.collection,
|
|
67734
|
+
rkey: parsed.rkey
|
|
67735
|
+
});
|
|
67736
|
+
const updatedRecord = {
|
|
67737
|
+
...existingRecord.data.value,
|
|
67738
|
+
bskyPostRef
|
|
67739
|
+
};
|
|
67740
|
+
await agent.com.atproto.repo.putRecord({
|
|
67741
|
+
repo: parsed.did,
|
|
67742
|
+
collection: parsed.collection,
|
|
67743
|
+
rkey: parsed.rkey,
|
|
67744
|
+
record: updatedRecord
|
|
67745
|
+
});
|
|
67746
|
+
}
|
|
67609
67747
|
|
|
67610
67748
|
// src/lib/prompts.ts
|
|
67611
67749
|
function exitOnCancel(value) {
|
|
@@ -67818,6 +67956,9 @@ function generateConfigTemplate(options) {
|
|
|
67818
67956
|
if (options.ignore && options.ignore.length > 0) {
|
|
67819
67957
|
config.ignore = options.ignore;
|
|
67820
67958
|
}
|
|
67959
|
+
if (options.bluesky) {
|
|
67960
|
+
config.bluesky = options.bluesky;
|
|
67961
|
+
}
|
|
67821
67962
|
return JSON.stringify(config, null, 2);
|
|
67822
67963
|
}
|
|
67823
67964
|
async function loadState(configDir) {
|
|
@@ -67932,6 +68073,11 @@ var initCommand = import_cmd_ts2.command({
|
|
|
67932
68073
|
message: "Field name for tags:",
|
|
67933
68074
|
defaultValue: "tags",
|
|
67934
68075
|
placeholder: "tags, categories, keywords, etc."
|
|
68076
|
+
}),
|
|
68077
|
+
draftField: () => Qt({
|
|
68078
|
+
message: "Field name for draft status:",
|
|
68079
|
+
defaultValue: "draft",
|
|
68080
|
+
placeholder: "draft, private, hidden, etc."
|
|
67935
68081
|
})
|
|
67936
68082
|
}, { onCancel });
|
|
67937
68083
|
const fieldMappings = [
|
|
@@ -67939,7 +68085,8 @@ var initCommand = import_cmd_ts2.command({
|
|
|
67939
68085
|
["description", frontmatterConfig.descField, "description"],
|
|
67940
68086
|
["publishDate", frontmatterConfig.dateField, "publishDate"],
|
|
67941
68087
|
["coverImage", frontmatterConfig.coverField, "ogImage"],
|
|
67942
|
-
["tags", frontmatterConfig.tagsField, "tags"]
|
|
68088
|
+
["tags", frontmatterConfig.tagsField, "tags"],
|
|
68089
|
+
["draft", frontmatterConfig.draftField, "draft"]
|
|
67943
68090
|
];
|
|
67944
68091
|
const builtMapping = fieldMappings.reduce((acc, [key, value, defaultValue]) => {
|
|
67945
68092
|
if (value !== defaultValue) {
|
|
@@ -68027,6 +68174,35 @@ var initCommand = import_cmd_ts2.command({
|
|
|
68027
68174
|
}
|
|
68028
68175
|
publicationUri = uri;
|
|
68029
68176
|
}
|
|
68177
|
+
const enableBluesky = await Mt2({
|
|
68178
|
+
message: "Enable automatic Bluesky posting when publishing?",
|
|
68179
|
+
initialValue: false
|
|
68180
|
+
});
|
|
68181
|
+
if (enableBluesky === Symbol.for("cancel")) {
|
|
68182
|
+
onCancel();
|
|
68183
|
+
}
|
|
68184
|
+
let blueskyConfig;
|
|
68185
|
+
if (enableBluesky) {
|
|
68186
|
+
const maxAgeDaysInput = await Qt({
|
|
68187
|
+
message: "Maximum age (in days) for posts to be shared on Bluesky:",
|
|
68188
|
+
defaultValue: "7",
|
|
68189
|
+
placeholder: "7",
|
|
68190
|
+
validate: (value) => {
|
|
68191
|
+
const num = parseInt(value, 10);
|
|
68192
|
+
if (isNaN(num) || num < 1) {
|
|
68193
|
+
return "Please enter a positive number";
|
|
68194
|
+
}
|
|
68195
|
+
}
|
|
68196
|
+
});
|
|
68197
|
+
if (maxAgeDaysInput === Symbol.for("cancel")) {
|
|
68198
|
+
onCancel();
|
|
68199
|
+
}
|
|
68200
|
+
const maxAgeDays = parseInt(maxAgeDaysInput, 10);
|
|
68201
|
+
blueskyConfig = {
|
|
68202
|
+
enabled: true,
|
|
68203
|
+
...maxAgeDays !== 7 && { maxAgeDays }
|
|
68204
|
+
};
|
|
68205
|
+
}
|
|
68030
68206
|
const pdsUrl = credentials?.pdsUrl;
|
|
68031
68207
|
const configContent = generateConfigTemplate({
|
|
68032
68208
|
siteUrl: siteConfig.siteUrl,
|
|
@@ -68037,7 +68213,8 @@ var initCommand = import_cmd_ts2.command({
|
|
|
68037
68213
|
pathPrefix: siteConfig.pathPrefix || "/posts",
|
|
68038
68214
|
publicationUri,
|
|
68039
68215
|
pdsUrl,
|
|
68040
|
-
frontmatter: frontmatterMapping
|
|
68216
|
+
frontmatter: frontmatterMapping,
|
|
68217
|
+
bluesky: blueskyConfig
|
|
68041
68218
|
});
|
|
68042
68219
|
const configPath = path6.join(process.cwd(), "sequoia.json");
|
|
68043
68220
|
await fs5.writeFile(configPath, configContent);
|
|
@@ -68269,7 +68446,12 @@ var publishCommand = import_cmd_ts4.command({
|
|
|
68269
68446
|
const posts = await scanContentDirectory(contentDir, config.frontmatter, config.ignore);
|
|
68270
68447
|
s.stop(`Found ${posts.length} posts`);
|
|
68271
68448
|
const postsToPublish = [];
|
|
68449
|
+
const draftPosts = [];
|
|
68272
68450
|
for (const post of posts) {
|
|
68451
|
+
if (post.frontmatter.draft) {
|
|
68452
|
+
draftPosts.push(post);
|
|
68453
|
+
continue;
|
|
68454
|
+
}
|
|
68273
68455
|
const contentHash = await getContentHash(post.rawContent);
|
|
68274
68456
|
const relativeFilePath = path8.relative(configDir, post.filePath);
|
|
68275
68457
|
const postState = state.posts[relativeFilePath];
|
|
@@ -68293,6 +68475,9 @@ var publishCommand = import_cmd_ts4.command({
|
|
|
68293
68475
|
});
|
|
68294
68476
|
}
|
|
68295
68477
|
}
|
|
68478
|
+
if (draftPosts.length > 0) {
|
|
68479
|
+
R2.info(`Skipping ${draftPosts.length} draft post${draftPosts.length === 1 ? "" : "s"}`);
|
|
68480
|
+
}
|
|
68296
68481
|
if (postsToPublish.length === 0) {
|
|
68297
68482
|
R2.success("All posts are up to date. Nothing to publish.");
|
|
68298
68483
|
return;
|
|
@@ -68300,11 +68485,34 @@ var publishCommand = import_cmd_ts4.command({
|
|
|
68300
68485
|
R2.info(`
|
|
68301
68486
|
${postsToPublish.length} posts to publish:
|
|
68302
68487
|
`);
|
|
68488
|
+
const blueskyEnabled = config.bluesky?.enabled ?? false;
|
|
68489
|
+
const maxAgeDays = config.bluesky?.maxAgeDays ?? 7;
|
|
68490
|
+
const cutoffDate = new Date;
|
|
68491
|
+
cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
|
|
68303
68492
|
for (const { post, action, reason } of postsToPublish) {
|
|
68304
68493
|
const icon = action === "create" ? "+" : "~";
|
|
68305
|
-
|
|
68494
|
+
const relativeFilePath = path8.relative(configDir, post.filePath);
|
|
68495
|
+
const existingBskyPostRef = state.posts[relativeFilePath]?.bskyPostRef;
|
|
68496
|
+
let bskyNote = "";
|
|
68497
|
+
if (blueskyEnabled) {
|
|
68498
|
+
if (existingBskyPostRef) {
|
|
68499
|
+
bskyNote = " [bsky: exists]";
|
|
68500
|
+
} else {
|
|
68501
|
+
const publishDate = new Date(post.frontmatter.publishDate);
|
|
68502
|
+
if (publishDate < cutoffDate) {
|
|
68503
|
+
bskyNote = ` [bsky: skipped, older than ${maxAgeDays} days]`;
|
|
68504
|
+
} else {
|
|
68505
|
+
bskyNote = " [bsky: will post]";
|
|
68506
|
+
}
|
|
68507
|
+
}
|
|
68508
|
+
}
|
|
68509
|
+
R2.message(` ${icon} ${post.frontmatter.title} (${reason})${bskyNote}`);
|
|
68306
68510
|
}
|
|
68307
68511
|
if (dryRun) {
|
|
68512
|
+
if (blueskyEnabled) {
|
|
68513
|
+
R2.info(`
|
|
68514
|
+
Bluesky posting: enabled (max age: ${maxAgeDays} days)`);
|
|
68515
|
+
}
|
|
68308
68516
|
R2.info(`
|
|
68309
68517
|
Dry run complete. No changes made.`);
|
|
68310
68518
|
return;
|
|
@@ -68322,6 +68530,7 @@ Dry run complete. No changes made.`);
|
|
|
68322
68530
|
let publishedCount = 0;
|
|
68323
68531
|
let updatedCount = 0;
|
|
68324
68532
|
let errorCount = 0;
|
|
68533
|
+
let bskyPostCount = 0;
|
|
68325
68534
|
for (const { post, action } of postsToPublish) {
|
|
68326
68535
|
s.start(`Publishing: ${post.frontmatter.title}`);
|
|
68327
68536
|
try {
|
|
@@ -68340,6 +68549,9 @@ Dry run complete. No changes made.`);
|
|
|
68340
68549
|
}
|
|
68341
68550
|
let atUri;
|
|
68342
68551
|
let contentForHash;
|
|
68552
|
+
let bskyPostRef;
|
|
68553
|
+
const relativeFilePath = path8.relative(configDir, post.filePath);
|
|
68554
|
+
const existingBskyPostRef = state.posts[relativeFilePath]?.bskyPostRef;
|
|
68343
68555
|
if (action === "create") {
|
|
68344
68556
|
atUri = await createDocument(agent, post, config, coverImage);
|
|
68345
68557
|
s.stop(`Created: ${atUri}`);
|
|
@@ -68355,12 +68567,41 @@ Dry run complete. No changes made.`);
|
|
|
68355
68567
|
contentForHash = post.rawContent;
|
|
68356
68568
|
updatedCount++;
|
|
68357
68569
|
}
|
|
68570
|
+
if (blueskyEnabled) {
|
|
68571
|
+
if (existingBskyPostRef) {
|
|
68572
|
+
R2.info(` Bluesky post already exists, skipping`);
|
|
68573
|
+
bskyPostRef = existingBskyPostRef;
|
|
68574
|
+
} else {
|
|
68575
|
+
const publishDate = new Date(post.frontmatter.publishDate);
|
|
68576
|
+
if (publishDate < cutoffDate) {
|
|
68577
|
+
R2.info(` Post is older than ${maxAgeDays} days, skipping Bluesky post`);
|
|
68578
|
+
} else {
|
|
68579
|
+
try {
|
|
68580
|
+
const pathPrefix = config.pathPrefix || "/posts";
|
|
68581
|
+
const canonicalUrl = `${config.siteUrl}${pathPrefix}/${post.slug}`;
|
|
68582
|
+
bskyPostRef = await createBlueskyPost(agent, {
|
|
68583
|
+
title: post.frontmatter.title,
|
|
68584
|
+
description: post.frontmatter.description,
|
|
68585
|
+
canonicalUrl,
|
|
68586
|
+
coverImage,
|
|
68587
|
+
publishedAt: post.frontmatter.publishDate
|
|
68588
|
+
});
|
|
68589
|
+
await addBskyPostRefToDocument(agent, atUri, bskyPostRef);
|
|
68590
|
+
R2.info(` Created Bluesky post: ${bskyPostRef.uri}`);
|
|
68591
|
+
bskyPostCount++;
|
|
68592
|
+
} catch (bskyError) {
|
|
68593
|
+
const errorMsg = bskyError instanceof Error ? bskyError.message : String(bskyError);
|
|
68594
|
+
R2.warn(` Failed to create Bluesky post: ${errorMsg}`);
|
|
68595
|
+
}
|
|
68596
|
+
}
|
|
68597
|
+
}
|
|
68598
|
+
}
|
|
68358
68599
|
const contentHash = await getContentHash(contentForHash);
|
|
68359
|
-
const relativeFilePath = path8.relative(configDir, post.filePath);
|
|
68360
68600
|
state.posts[relativeFilePath] = {
|
|
68361
68601
|
contentHash,
|
|
68362
68602
|
atUri,
|
|
68363
|
-
lastPublished: new Date().toISOString()
|
|
68603
|
+
lastPublished: new Date().toISOString(),
|
|
68604
|
+
bskyPostRef
|
|
68364
68605
|
};
|
|
68365
68606
|
} catch (error) {
|
|
68366
68607
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -68374,6 +68615,9 @@ Dry run complete. No changes made.`);
|
|
|
68374
68615
|
---`);
|
|
68375
68616
|
R2.info(`Published: ${publishedCount}`);
|
|
68376
68617
|
R2.info(`Updated: ${updatedCount}`);
|
|
68618
|
+
if (bskyPostCount > 0) {
|
|
68619
|
+
R2.info(`Bluesky posts: ${bskyPostCount}`);
|
|
68620
|
+
}
|
|
68377
68621
|
if (errorCount > 0) {
|
|
68378
68622
|
R2.warn(`Errors: ${errorCount}`);
|
|
68379
68623
|
}
|
|
@@ -68549,7 +68793,7 @@ Publish evergreen content to the ATmosphere
|
|
|
68549
68793
|
|
|
68550
68794
|
> https://tangled.org/stevedylan.dev/sequoia
|
|
68551
68795
|
`,
|
|
68552
|
-
version: "0.
|
|
68796
|
+
version: "0.2.0",
|
|
68553
68797
|
cmds: {
|
|
68554
68798
|
auth: authCommand,
|
|
68555
68799
|
init: initCommand,
|