@letterstory/cli 0.9.0 → 0.11.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 +22 -1
- package/lib/commands/onboard.mjs +131 -0
- package/package.json +1 -1
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.
|
|
43
|
+
export const VERSION = "0.11.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.
|
|
@@ -268,6 +268,27 @@ Phantom onboarding recipe (domain -> live phantom blog with first posts):
|
|
|
268
268
|
applying. --domain-mode buy_now SPENDS
|
|
269
269
|
money (2/org, $30 cap).
|
|
270
270
|
|
|
271
|
+
Collection onboarding (your OWN blog, on your org's domain — no provisioning):
|
|
272
|
+
${bin === "phantom" ? "onboard" : "phantom-onboard"} collection [--collection <uuid>] [--name "<collection name>"]
|
|
273
|
+
[--about "the direction"] [--sitemap <url>] [--watch]
|
|
274
|
+
Import the existing blog from its sitemap
|
|
275
|
+
(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
|
+
${bin === "phantom" ? "onboard" : "phantom-onboard"} sync-sitemap --collection <uuid>
|
|
282
|
+
Re-check an already-onboarded blog's sitemap
|
|
283
|
+
right now instead of waiting for its cadence
|
|
284
|
+
(default every 14 days). Skips pages whose
|
|
285
|
+
<lastmod> hasn't moved, imports new posts,
|
|
286
|
+
refreshes changed ones. Only for collections
|
|
287
|
+
with no CMS connected — once one is, use the
|
|
288
|
+
CMS pull/reconcile path instead.
|
|
289
|
+
--watch polls to completion, printing each
|
|
290
|
+
review note as it lands.
|
|
291
|
+
|
|
271
292
|
Seers (event-driven signals -> drafts):
|
|
272
293
|
seers list List seers with recent activity
|
|
273
294
|
seers get <seer-id> One seer's full config + poll state
|
package/lib/commands/onboard.mjs
CHANGED
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
// posts are written by background jobs (20-40min each); they're reported as job ids and
|
|
19
19
|
// this command does not block on them — poll with `phantom-job status <job-id>`.
|
|
20
20
|
|
|
21
|
+
// `onboard collection` is the CLIENT-blog sibling: the same recipe minus provisioning,
|
|
22
|
+
// driven by `onboard_collection` + `get_collection_onboard_status`. The org (and its
|
|
23
|
+
// domain) comes from the API key — the job reads the org's registered domain, imports
|
|
24
|
+
// the existing blog from its sitemap, derives content areas + series, builds the topic
|
|
25
|
+
// map, binds SEO demand, and stocks the Headlines tab. `--watch` polls to completion,
|
|
26
|
+
// printing each review note as the job writes it.
|
|
27
|
+
|
|
21
28
|
import { CliError } from "../client.mjs";
|
|
22
29
|
import { flagStr, flagNum, flagBool, requirePositional, printResult, compact, ok, pollUntil } from "./shared.mjs";
|
|
23
30
|
|
|
@@ -91,8 +98,132 @@ function overridesFromFlags(flags) {
|
|
|
91
98
|
});
|
|
92
99
|
}
|
|
93
100
|
|
|
101
|
+
// Final readback of a collection-onboarding job — the counts the engine PERSISTED.
|
|
102
|
+
function formatCollectionStatus(s) {
|
|
103
|
+
const r = s.result ?? {};
|
|
104
|
+
const lines = [
|
|
105
|
+
`Onboarding ${s.status} at stage "${s.stage}"`,
|
|
106
|
+
` collection: ${s.collection_id}`,
|
|
107
|
+
` blog found: ${s.has_blog === null ? "(not yet determined)" : s.has_blog ? (s.sitemap_url ?? "yes") : "no — onboarded from positioning"}`,
|
|
108
|
+
];
|
|
109
|
+
if (s.error) lines.push(` error: ${s.error}`);
|
|
110
|
+
if (s.status === "done") {
|
|
111
|
+
lines.push(
|
|
112
|
+
` posts imported: ${r.posts_imported ?? 0}`,
|
|
113
|
+
` content areas (org): ${r.coverage_areas ?? 0}`,
|
|
114
|
+
` pillars: ${r.pillars ?? 0}`,
|
|
115
|
+
` series: ${r.series ?? 0}`,
|
|
116
|
+
` open topics: ${r.spokes_open ?? 0} (${r.spokes_seo_bound ?? 0} SEO-bound)`,
|
|
117
|
+
` headlines suggested: ${r.headlines_suggested ?? 0}`,
|
|
118
|
+
` Review them in Headlines & Queue — the Planner is configured but OFF (manual approval).`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return lines.join("\n");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// `onboard collection` — onboard the caller's own (client-brand) blog. The org and its
|
|
125
|
+
// domain come from the API key, so there's no <domain> positional; pin the sitemap with
|
|
126
|
+
// --sitemap when auto-discovery from the org's domain won't find it.
|
|
127
|
+
async function cmdOnboardCollection(ctx) {
|
|
128
|
+
const { client, flags } = ctx;
|
|
129
|
+
// The phantom variant's --domain means "custom site domain to buy/connect"; here the
|
|
130
|
+
// site is the org's own, read from the API key. Refuse rather than silently ignore.
|
|
131
|
+
if (flagStr(flags.domain) !== undefined) {
|
|
132
|
+
throw new CliError(
|
|
133
|
+
"onboard collection uses your organization's registered domain (from the API key). " +
|
|
134
|
+
"To pin where posts import from, pass --sitemap <https://…/sitemap.xml> instead."
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const args = compact({
|
|
139
|
+
collection_id: flagStr(flags.collection),
|
|
140
|
+
collection_name: flagStr(flags.name),
|
|
141
|
+
direction: flagStr(flags.about) ?? flagStr(flags.direction),
|
|
142
|
+
sitemap_url: flagStr(flags.sitemap),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
ok(ctx, "Starting collection onboarding…");
|
|
146
|
+
const started = await client.callTool("onboard_collection", args);
|
|
147
|
+
if (!started.job_id) throw new CliError("onboard_collection did not return a job id.");
|
|
148
|
+
ok(ctx, `Job ${started.job_id} running for collection ${started.collection_id}.`);
|
|
149
|
+
|
|
150
|
+
if (!flagBool(flags.watch)) {
|
|
151
|
+
printResult(ctx.io, flags, started, (s) =>
|
|
152
|
+
[
|
|
153
|
+
`Onboarding started (stage "${s.stage}").`,
|
|
154
|
+
` job: ${s.job_id}`,
|
|
155
|
+
` collection: ${s.collection_id}`,
|
|
156
|
+
`Watch it with: --watch (re-running is safe), or poll:`,
|
|
157
|
+
` ${ctx.bin} call get_collection_onboard_status --args '{"job_id":"${s.job_id}"}'`,
|
|
158
|
+
].join("\n")
|
|
159
|
+
);
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Watch: poll to a terminal state, printing each review note as the job writes it.
|
|
164
|
+
let printed = 0;
|
|
165
|
+
const final = await pollUntil(
|
|
166
|
+
ctx.io,
|
|
167
|
+
async () => {
|
|
168
|
+
const s = await client.callTool("get_collection_onboard_status", { job_id: started.job_id });
|
|
169
|
+
const review = Array.isArray(s.review) ? s.review : [];
|
|
170
|
+
for (; printed < review.length; printed++) ok(ctx, ` … ${review[printed]}`);
|
|
171
|
+
if (s.status === "failed") {
|
|
172
|
+
throw new CliError(
|
|
173
|
+
`Onboarding failed at stage "${s.stage}": ${s.error ?? "unknown error"} (re-running the command retries — the job is idempotent).`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return { done: s.status !== "running", value: s };
|
|
177
|
+
},
|
|
178
|
+
{ label: "collection onboarding to finish", timeoutMs: 30 * 60 * 1000, intervalMs: 5000 }
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
printResult(ctx.io, flags, final, formatCollectionStatus);
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// `onboard sync-sitemap --collection <uuid>` — the periodic-refresh sibling: re-check a
|
|
186
|
+
// collection's sitemap right now instead of waiting for its cadence
|
|
187
|
+
// (collections.sitemap_sync_cadence_days). One-shot, no polling — the underlying work is
|
|
188
|
+
// bounded per run, so it returns synchronously.
|
|
189
|
+
async function cmdOnboardSyncSitemap(ctx) {
|
|
190
|
+
const { client, flags } = ctx;
|
|
191
|
+
const collectionId = flagStr(flags.collection);
|
|
192
|
+
if (!collectionId) {
|
|
193
|
+
throw new CliError("onboard sync-sitemap requires --collection <uuid>.");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
ok(ctx, "Re-checking the sitemap…");
|
|
197
|
+
const result = await client.callTool("sync_collection_sitemap", { collection_id: collectionId });
|
|
198
|
+
|
|
199
|
+
printResult(ctx.io, flags, result, (r) =>
|
|
200
|
+
[
|
|
201
|
+
`Sitemap sync complete — ${r.checked} post${r.checked === 1 ? "" : "s"} checked.`,
|
|
202
|
+
` created: ${r.created}`,
|
|
203
|
+
` updated: ${r.updated}`,
|
|
204
|
+
` unchanged: ${r.unchanged} (skipped — <lastmod> hadn't moved)`,
|
|
205
|
+
r.skipped ? ` skipped: ${r.skipped} (nothing readable)` : null,
|
|
206
|
+
r.failed
|
|
207
|
+
? ` failed: ${r.failed}${r.failure_reasons?.length ? ` (${r.failure_reasons.join("; ")})` : ""}`
|
|
208
|
+
: null,
|
|
209
|
+
r.truncated ? ` capped this run — rerun (or wait for the cadence) to catch up the rest.` : null,
|
|
210
|
+
]
|
|
211
|
+
.filter(Boolean)
|
|
212
|
+
.join("\n")
|
|
213
|
+
);
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
94
217
|
export async function cmdOnboard(ctx) {
|
|
95
218
|
const { client, flags, io } = ctx;
|
|
219
|
+
// `onboard collection …` — the client-blog variant (no <domain>; org from the key).
|
|
220
|
+
if (ctx.positionals[0] === "collection") {
|
|
221
|
+
return cmdOnboardCollection({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
222
|
+
}
|
|
223
|
+
// `onboard sync-sitemap --collection <uuid>` — refresh an already-onboarded blog.
|
|
224
|
+
if (ctx.positionals[0] === "sync-sitemap") {
|
|
225
|
+
return cmdOnboardSyncSitemap({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
226
|
+
}
|
|
96
227
|
const domain = requirePositional(ctx.positionals, 0, "domain");
|
|
97
228
|
const overrides = overridesFromFlags(flags);
|
|
98
229
|
|