@letterstory/cli 0.10.0 → 0.12.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/lib/cli.mjs CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  } from "./commands.mjs";
41
41
 
42
42
  // Keep in sync with cli/package.json.
43
- export const VERSION = "0.10.0";
43
+ export const VERSION = "0.12.0";
44
44
 
45
45
  // Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
46
46
  // can't accidentally swallow the id as --json's value.
@@ -203,6 +203,11 @@ Connectors:
203
203
  [--confirm-missing-target] re-publish even though
204
204
  the item was deleted/unpublished on the provider
205
205
  connectors status --connector <connector> --publish-id <id>
206
+ connectors pull [--collection <uuid>] [--provider <webflow|framer|sanity|contentful>]
207
+ Pull existing posts FROM a connected CMS into a
208
+ collection, reconciling (adopt/update/create/orphan).
209
+ --collection for a bound collection, --provider to
210
+ auto-resolve, or both to wire an unbound collection.
206
211
 
207
212
  Strategy & onboarding:
208
213
  strategy company get | set [--name] [--domain] [--manifesto <text>|--manifesto-file <path>]
@@ -270,14 +275,23 @@ Phantom onboarding recipe (domain -> live phantom blog with first posts):
270
275
 
271
276
  Collection onboarding (your OWN blog, on your org's domain — no provisioning):
272
277
  ${bin === "phantom" ? "onboard" : "phantom-onboard"} collection [--collection <uuid>] [--name "<collection name>"]
273
- [--about "the direction"] [--sitemap <url>] [--watch]
278
+ [--about "the direction"] [--sitemap <url>] [--cms <provider>] [--watch]
274
279
  Import the existing blog from its sitemap
275
280
  (auto-discovered from the org's domain unless
276
- --sitemap pins one), derive content areas +
277
- series, build the topic map, bind SEO demand,
278
- and stock Headlines & Queue. Omit --collection
279
- to create a fresh one (--name, default Blog).
280
- The Planner stays OFF (manual approval).
281
+ --sitemap pins one) OR from a connected CMS with
282
+ --cms <webflow|framer|sanity|contentful>, then
283
+ derive content areas + series, build the topic
284
+ map, bind SEO demand, and stock Headlines & Queue.
285
+ Omit --collection to create a fresh one (--name,
286
+ default Blog). The Planner stays OFF (manual approval).
287
+ ${bin === "phantom" ? "onboard" : "phantom-onboard"} sync-sitemap --collection <uuid>
288
+ Re-check an already-onboarded blog's sitemap
289
+ right now instead of waiting for its cadence
290
+ (default every 14 days). Skips pages whose
291
+ <lastmod> hasn't moved, imports new posts,
292
+ refreshes changed ones. Only for collections
293
+ with no CMS connected — once one is, use the
294
+ CMS pull/reconcile path instead.
281
295
  --watch polls to completion, printing each
282
296
  review note as it lands.
283
297
 
@@ -17,8 +17,11 @@ export async function cmdConnectors(ctx) {
17
17
  return connectorsPublish(rest);
18
18
  case "status":
19
19
  return connectorsStatus(rest);
20
+ case "pull":
21
+ case "import":
22
+ return connectorsPull(rest);
20
23
  default:
21
- throw new CliError(`Unknown connectors subcommand: ${sub ?? "(none)"}. Try: list, publish, status`);
24
+ throw new CliError(`Unknown connectors subcommand: ${sub ?? "(none)"}. Try: list, publish, status, pull`);
22
25
  }
23
26
  }
24
27
 
@@ -59,3 +62,22 @@ async function connectorsStatus(ctx) {
59
62
  printResult(io, flags, result);
60
63
  return 0;
61
64
  }
65
+
66
+ async function connectorsPull(ctx) {
67
+ const { client, flags, io } = ctx;
68
+ // Dual key: --collection (a CMS-bound collection), or --provider (resolve/wire),
69
+ // or both (--provider wires an unbound --collection first). At least one required.
70
+ const collectionId = flagStr(flags.collection);
71
+ const provider = flagStr(flags.provider);
72
+ if (!collectionId && !provider) {
73
+ throw new CliError("Pass --collection <id> and/or --provider <webflow|framer|sanity|contentful>");
74
+ }
75
+ const args = compact({ collection_id: collectionId, provider });
76
+ const result = await client.callTool("import_from_cms", args);
77
+ ok(
78
+ ctx,
79
+ `Pulled from ${result?.provider ?? provider ?? "CMS"} into collection ${result?.collection_id ?? collectionId ?? ""}.`
80
+ );
81
+ printResult(io, flags, result);
82
+ return 0;
83
+ }
@@ -140,6 +140,8 @@ async function cmdOnboardCollection(ctx) {
140
140
  collection_name: flagStr(flags.name),
141
141
  direction: flagStr(flags.about) ?? flagStr(flags.direction),
142
142
  sitemap_url: flagStr(flags.sitemap),
143
+ // Source the existing blog from a connected CMS instead of a sitemap.
144
+ cms_provider: flagStr(flags.cms),
143
145
  });
144
146
 
145
147
  ok(ctx, "Starting collection onboarding…");
@@ -182,12 +184,48 @@ async function cmdOnboardCollection(ctx) {
182
184
  return 0;
183
185
  }
184
186
 
187
+ // `onboard sync-sitemap --collection <uuid>` — the periodic-refresh sibling: re-check a
188
+ // collection's sitemap right now instead of waiting for its cadence
189
+ // (collections.sitemap_sync_cadence_days). One-shot, no polling — the underlying work is
190
+ // bounded per run, so it returns synchronously.
191
+ async function cmdOnboardSyncSitemap(ctx) {
192
+ const { client, flags } = ctx;
193
+ const collectionId = flagStr(flags.collection);
194
+ if (!collectionId) {
195
+ throw new CliError("onboard sync-sitemap requires --collection <uuid>.");
196
+ }
197
+
198
+ ok(ctx, "Re-checking the sitemap…");
199
+ const result = await client.callTool("sync_collection_sitemap", { collection_id: collectionId });
200
+
201
+ printResult(ctx.io, flags, result, (r) =>
202
+ [
203
+ `Sitemap sync complete — ${r.checked} post${r.checked === 1 ? "" : "s"} checked.`,
204
+ ` created: ${r.created}`,
205
+ ` updated: ${r.updated}`,
206
+ ` unchanged: ${r.unchanged} (skipped — <lastmod> hadn't moved)`,
207
+ r.skipped ? ` skipped: ${r.skipped} (nothing readable)` : null,
208
+ r.failed
209
+ ? ` failed: ${r.failed}${r.failure_reasons?.length ? ` (${r.failure_reasons.join("; ")})` : ""}`
210
+ : null,
211
+ r.truncated ? ` capped this run — rerun (or wait for the cadence) to catch up the rest.` : null,
212
+ ]
213
+ .filter(Boolean)
214
+ .join("\n")
215
+ );
216
+ return 0;
217
+ }
218
+
185
219
  export async function cmdOnboard(ctx) {
186
220
  const { client, flags, io } = ctx;
187
221
  // `onboard collection …` — the client-blog variant (no <domain>; org from the key).
188
222
  if (ctx.positionals[0] === "collection") {
189
223
  return cmdOnboardCollection({ ...ctx, positionals: ctx.positionals.slice(1) });
190
224
  }
225
+ // `onboard sync-sitemap --collection <uuid>` — refresh an already-onboarded blog.
226
+ if (ctx.positionals[0] === "sync-sitemap") {
227
+ return cmdOnboardSyncSitemap({ ...ctx, positionals: ctx.positionals.slice(1) });
228
+ }
191
229
  const domain = requirePositional(ctx.positionals, 0, "domain");
192
230
  const overrides = overridesFromFlags(flags);
193
231
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",