@letterstory/cli 0.13.0 → 0.14.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/README.md +27 -0
- package/lib/cli.mjs +42 -1
- package/lib/commands/admin.mjs +146 -0
- package/lib/commands/onboard.mjs +151 -0
- package/lib/commands.mjs +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -229,6 +229,33 @@ letterstory onboarding status
|
|
|
229
229
|
letterstory onboarding step --complete connect_domain
|
|
230
230
|
```
|
|
231
231
|
|
|
232
|
+
### One-shot domain onboarding
|
|
233
|
+
|
|
234
|
+
A bare company domain in, a fully-configured organization out — as ONE durable job that
|
|
235
|
+
composes the existing engines: the main blog (imported from its sitemap or a connected
|
|
236
|
+
CMS, content areas, topic map, SEO, series, company GEO probing, headlines), org visuals
|
|
237
|
+
inferred from the site, the main blog's Enhance flow configured (new posts are enhanced;
|
|
238
|
+
imported posts are left as published), a phantom provisioned + configured + seeded with
|
|
239
|
+
posts that are actually written and published, and Seers in suggest mode when they fit.
|
|
240
|
+
The job finishes from a readback: it waits (default 2h) for the seed posts to publish and
|
|
241
|
+
the first GEO probe to land, then reports what landed.
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
# On the org behind your key (its registered domain is the source):
|
|
245
|
+
letterstory phantom-onboard domain --about "payroll for platform teams" --posts 3 --watch
|
|
246
|
+
letterstory phantom-onboard domain --sitemap https://acme.com/sitemap.xml --seers skip --no-visuals
|
|
247
|
+
letterstory phantom-onboard domain --attach <job-id> --watch # watch a job started elsewhere
|
|
248
|
+
letterstory phantom-onboard domain --status # the org's latest domain onboarding
|
|
249
|
+
|
|
250
|
+
# Staff: create the org itself first (the key exchange), with a STAFF key.
|
|
251
|
+
LETTERSTORY_STAFF_API_KEY=<staff key with org:create> \
|
|
252
|
+
letterstory admin create-org --domain acme.com --onboard --about "payroll for platform teams"
|
|
253
|
+
# → prints the org id, a fresh ORG-scoped API key (once), and the job id to watch.
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Everything the parent job does is also its own tool for existing orgs:
|
|
257
|
+
`ensure_collection_enhance_flow`, `provision_org_visuals`, `propose_seers` (`--args '{"apply":true}'`).
|
|
258
|
+
|
|
232
259
|
## Content series
|
|
233
260
|
|
|
234
261
|
A series is a coverage obligation, not a keyword bet: "content that looks like this",
|
package/lib/cli.mjs
CHANGED
|
@@ -37,10 +37,11 @@ import {
|
|
|
37
37
|
cmdTemplates,
|
|
38
38
|
cmdShredder,
|
|
39
39
|
cmdLandings,
|
|
40
|
+
cmdAdmin,
|
|
40
41
|
} from "./commands.mjs";
|
|
41
42
|
|
|
42
43
|
// Keep in sync with cli/package.json.
|
|
43
|
-
export const VERSION = "0.
|
|
44
|
+
export const VERSION = "0.14.0";
|
|
44
45
|
|
|
45
46
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
46
47
|
// can't accidentally swallow the id as --json's value.
|
|
@@ -71,6 +72,16 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
71
72
|
"stock-only",
|
|
72
73
|
"gate",
|
|
73
74
|
"skip",
|
|
75
|
+
// `phantom-onboard domain` / `admin create-org --onboard` switches.
|
|
76
|
+
"watch",
|
|
77
|
+
"status",
|
|
78
|
+
"no-phantom",
|
|
79
|
+
"no-visuals",
|
|
80
|
+
"no-enhance",
|
|
81
|
+
"no-key",
|
|
82
|
+
"onboard",
|
|
83
|
+
"test",
|
|
84
|
+
"real",
|
|
74
85
|
]);
|
|
75
86
|
|
|
76
87
|
// Tiny argv parser: `--flag value`, `--flag=value`, boolean `--flag`, and positionals.
|
|
@@ -309,6 +320,34 @@ Collection onboarding (your OWN blog, on your org's domain — no provisioning):
|
|
|
309
320
|
--watch polls to completion, printing each
|
|
310
321
|
review note as it lands.
|
|
311
322
|
|
|
323
|
+
Domain onboarding (ONE SHOT: a bare domain in, a fully-configured organization out):
|
|
324
|
+
${bin === "phantom" ? "onboard" : "phantom-onboard"} domain [--about "the direction"] [--sitemap <url>] [--cms <provider>]
|
|
325
|
+
[--name "<main collection name>"] [--posts N] [--no-phantom] [--phantom-name X]
|
|
326
|
+
[--site-domain host] [--domain-mode subdomain|buy_now|connect_later]
|
|
327
|
+
[--seers suggest|skip] [--no-visuals] [--no-enhance] [--test]
|
|
328
|
+
[--readiness-minutes N] [--watch]
|
|
329
|
+
On the org behind the key (its registered
|
|
330
|
+
domain): onboard the main blog (the
|
|
331
|
+
collection recipe), infer org visuals from
|
|
332
|
+
the site, configure the main blog's Enhance
|
|
333
|
+
flow, provision + configure + seed a phantom
|
|
334
|
+
(default 3 posts, written and published),
|
|
335
|
+
and create Seers in suggest mode when they
|
|
336
|
+
fit — then wait (default 2h) for the seeds
|
|
337
|
+
to publish and report what landed.
|
|
338
|
+
${bin === "phantom" ? "onboard" : "phantom-onboard"} domain --attach <job-id> [--watch]
|
|
339
|
+
Watch / read a job started elsewhere.
|
|
340
|
+
${bin === "phantom" ? "onboard" : "phantom-onboard"} domain --status Latest domain onboarding on this org.
|
|
341
|
+
|
|
342
|
+
Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org exists):
|
|
343
|
+
admin create-org --domain <bare-domain> [--name "Acme"] [--test] [--no-key]
|
|
344
|
+
[--key-name N] [--capabilities a,b] [--onboard] [+ the onboard domain flags]
|
|
345
|
+
The key exchange: create the client org from
|
|
346
|
+
its domain (one org per domain), mint an
|
|
347
|
+
ORG-scoped API key and print it ONCE. With
|
|
348
|
+
--onboard the domain onboarding starts in the
|
|
349
|
+
same call; watch it with the printed key.
|
|
350
|
+
|
|
312
351
|
Seers (event-driven signals -> drafts):
|
|
313
352
|
seers list List seers with recent activity
|
|
314
353
|
seers get <seer-id> One seer's full config + poll state
|
|
@@ -428,6 +467,8 @@ const CLIENT_COMMANDS = {
|
|
|
428
467
|
templates: cmdTemplates,
|
|
429
468
|
shredder: cmdShredder,
|
|
430
469
|
landings: cmdLandings,
|
|
470
|
+
// Platform-scoped (staff key) — the only commands that act BEFORE an org exists.
|
|
471
|
+
admin: cmdAdmin,
|
|
431
472
|
};
|
|
432
473
|
|
|
433
474
|
// LETTERSTORY_POLL_INTERVAL_MS / LETTERSTORY_MAX_POLLS let an operator (or an
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// `admin` — the PLATFORM-scoped commands, for staff. Everything else in this CLI is
|
|
2
|
+
// org-scoped and rides the MCP endpoint with an org API key; these ride a STAFF API key
|
|
3
|
+
// (`x-staff-api-key`, minted at Settings → Staff keys) against the superadmin REST
|
|
4
|
+
// routes, because they exist precisely to act BEFORE an org exists.
|
|
5
|
+
//
|
|
6
|
+
// admin create-org --domain acme.com [--name "Acme"] [--test] [--no-key] [--key-name N]
|
|
7
|
+
// [--capabilities a,b] [--onboard] [--about "…"] [--sitemap <url>]
|
|
8
|
+
// [--cms <provider>] [--posts N] [--seers suggest|skip] [--no-visuals]
|
|
9
|
+
// [--no-enhance] [--readiness-minutes N]
|
|
10
|
+
//
|
|
11
|
+
// The KEY EXCHANGE: create the client org from its domain (one org per domain; an existing
|
|
12
|
+
// owner is adopted), mint an ORG-scoped API key for it and print the plaintext ONCE. With
|
|
13
|
+
// --onboard, the one-shot domain onboarding (main blog + org visuals + enhance + phantom +
|
|
14
|
+
// seers) starts in the same call; watch it afterwards with
|
|
15
|
+
// LETTERSTORY_API_KEY=<printed key> letterstory phantom-onboard domain --watch --attach <job-id>
|
|
16
|
+
//
|
|
17
|
+
// The staff key comes from --staff-key or LETTERSTORY_STAFF_API_KEY; the base URL from the
|
|
18
|
+
// usual --url / LETTERSTORY_API_URL / config resolution.
|
|
19
|
+
|
|
20
|
+
import { CliError } from "../client.mjs";
|
|
21
|
+
import { flagStr, flagNum, flagBool, flagList, printResult, compact, ok } from "./shared.mjs";
|
|
22
|
+
|
|
23
|
+
const SEER_MODES = ["suggest", "skip"];
|
|
24
|
+
const CMS_PROVIDERS = ["webflow", "framer", "sanity", "contentful"];
|
|
25
|
+
|
|
26
|
+
function staffKeyFrom(flags) {
|
|
27
|
+
const key = flagStr(flags["staff-key"]) || process.env.LETTERSTORY_STAFF_API_KEY || null;
|
|
28
|
+
if (!key) {
|
|
29
|
+
throw new CliError(
|
|
30
|
+
"admin commands need a STAFF API key: pass --staff-key <key> or set LETTERSTORY_STAFF_API_KEY " +
|
|
31
|
+
"(mint one at Settings → Staff keys with the org:create capability)."
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return key;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** POST a superadmin route with the staff key; surfaces the server's error text on non-2xx. */
|
|
38
|
+
async function staffPost(ctx, path, body) {
|
|
39
|
+
const { client, flags } = ctx;
|
|
40
|
+
const url = `${client.url}${path}`;
|
|
41
|
+
const res = await client.fetch(url, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "content-type": "application/json", "x-staff-api-key": staffKeyFrom(flags) },
|
|
44
|
+
body: JSON.stringify(body),
|
|
45
|
+
});
|
|
46
|
+
let payload = null;
|
|
47
|
+
try {
|
|
48
|
+
payload = await res.json();
|
|
49
|
+
} catch {
|
|
50
|
+
payload = null;
|
|
51
|
+
}
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
const message = payload?.error ?? `${res.status} ${res.statusText}`;
|
|
54
|
+
throw new CliError(`${path} failed: ${message}`);
|
|
55
|
+
}
|
|
56
|
+
return payload ?? {};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function onboardOptionsFromFlags(flags) {
|
|
60
|
+
const seers = flagStr(flags.seers);
|
|
61
|
+
if (seers !== undefined && !SEER_MODES.includes(seers)) {
|
|
62
|
+
throw new CliError(`--seers must be one of: ${SEER_MODES.join(", ")}`);
|
|
63
|
+
}
|
|
64
|
+
const cms = flagStr(flags.cms);
|
|
65
|
+
if (cms !== undefined && !CMS_PROVIDERS.includes(cms)) {
|
|
66
|
+
throw new CliError(`--cms must be one of: ${CMS_PROVIDERS.join(", ")}`);
|
|
67
|
+
}
|
|
68
|
+
const posts = flagNum(flags.posts);
|
|
69
|
+
const options = compact({
|
|
70
|
+
phantom:
|
|
71
|
+
posts !== undefined || flagBool(flags["no-phantom"])
|
|
72
|
+
? compact({ enabled: flagBool(flags["no-phantom"]) ? false : undefined, posts_to_seed: posts })
|
|
73
|
+
: undefined,
|
|
74
|
+
seers,
|
|
75
|
+
visuals: flagBool(flags["no-visuals"]) ? false : undefined,
|
|
76
|
+
enhance: flagBool(flags["no-enhance"]) ? false : undefined,
|
|
77
|
+
readiness_minutes: flagNum(flags["readiness-minutes"]),
|
|
78
|
+
});
|
|
79
|
+
return compact({
|
|
80
|
+
direction: flagStr(flags.about) ?? flagStr(flags.direction),
|
|
81
|
+
sitemap_url: flagStr(flags.sitemap),
|
|
82
|
+
cms_provider: cms,
|
|
83
|
+
options: Object.keys(options).length ? options : undefined,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function formatCreateOrg(r) {
|
|
88
|
+
const lines = [
|
|
89
|
+
`${r.org_created ? "Created" : "Reused"} organization "${r.org_name}" (${r.domain})`,
|
|
90
|
+
` org_id: ${r.org_id}`,
|
|
91
|
+
];
|
|
92
|
+
if (r.api_key) {
|
|
93
|
+
lines.push(
|
|
94
|
+
` api key "${r.api_key.name}" (${(r.api_key.capabilities ?? []).length} capabilities):`,
|
|
95
|
+
` ${r.api_key.plain_key}`,
|
|
96
|
+
` ${r.api_key.warning ?? "Save this key now — it is not shown again."}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (r.job_id) {
|
|
100
|
+
lines.push(
|
|
101
|
+
` domain onboarding started: job ${r.job_id}`,
|
|
102
|
+
` watch it: LETTERSTORY_API_KEY=<the key above> letterstory phantom-onboard domain --attach ${r.job_id} --watch`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (r.error) lines.push(` error: ${r.error}`);
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function cmdAdminCreateOrg(ctx) {
|
|
110
|
+
const { flags, io } = ctx;
|
|
111
|
+
const domain = flagStr(flags.domain);
|
|
112
|
+
if (!domain) throw new CliError("admin create-org requires --domain <bare-domain>.");
|
|
113
|
+
|
|
114
|
+
// --capabilities a,b or repeated --capabilities a --capabilities b.
|
|
115
|
+
const capabilities = flagList(flags.capabilities)
|
|
116
|
+
.flatMap((v) => v.split(","))
|
|
117
|
+
.map((v) => v.trim())
|
|
118
|
+
.filter(Boolean);
|
|
119
|
+
const key = flagBool(flags["no-key"])
|
|
120
|
+
? false
|
|
121
|
+
: capabilities?.length || flagStr(flags["key-name"])
|
|
122
|
+
? compact({
|
|
123
|
+
name: flagStr(flags["key-name"]),
|
|
124
|
+
capabilities: capabilities?.length ? capabilities : undefined,
|
|
125
|
+
})
|
|
126
|
+
: true;
|
|
127
|
+
|
|
128
|
+
const body = compact({
|
|
129
|
+
domain,
|
|
130
|
+
name: flagStr(flags.name),
|
|
131
|
+
is_test: flagBool(flags.test) ? true : undefined,
|
|
132
|
+
key,
|
|
133
|
+
onboard: flagBool(flags.onboard) ? onboardOptionsFromFlags(flags) : undefined,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
ok(ctx, `Provisioning ${domain}…`);
|
|
137
|
+
const result = await staffPost(ctx, "/api/superadmin/orgs", body);
|
|
138
|
+
printResult(io, flags, result, formatCreateOrg);
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function cmdAdmin(ctx) {
|
|
143
|
+
const sub = ctx.positionals[0];
|
|
144
|
+
if (sub === "create-org") return cmdAdminCreateOrg({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
145
|
+
throw new CliError("Usage: admin create-org --domain <bare-domain> [--onboard] … (see --help)");
|
|
146
|
+
}
|
package/lib/commands/onboard.mjs
CHANGED
|
@@ -18,6 +18,14 @@
|
|
|
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 domain` is the ONE-SHOT parent: a bare company domain in (the org's registered
|
|
22
|
+
// domain, from the API key), a fully-configured organization out — the main blog onboarded
|
|
23
|
+
// (the `collection` recipe), org visuals inferred from the site, the main blog's Enhance
|
|
24
|
+
// flow configured, a phantom provisioned + configured + seeded, Seers in suggest mode —
|
|
25
|
+
// driven by `onboard_domain` + `get_domain_onboard_status`. `--watch` polls to completion;
|
|
26
|
+
// `--attach <job-id>` watches a job started elsewhere (e.g. `admin create-org --onboard`).
|
|
27
|
+
// To create the ORG itself headlessly, use `admin create-org` with a staff key first.
|
|
28
|
+
|
|
21
29
|
// `onboard collection` is the CLIENT-blog sibling: the same recipe minus provisioning,
|
|
22
30
|
// driven by `onboard_collection` + `get_collection_onboard_status`. The org (and its
|
|
23
31
|
// domain) comes from the API key — the job reads the org's registered domain, imports
|
|
@@ -216,12 +224,155 @@ async function cmdOnboardSyncSitemap(ctx) {
|
|
|
216
224
|
return 0;
|
|
217
225
|
}
|
|
218
226
|
|
|
227
|
+
const SEER_MODES = ["suggest", "skip"];
|
|
228
|
+
const CMS_PROVIDERS = ["webflow", "framer", "sanity", "contentful"];
|
|
229
|
+
|
|
230
|
+
// Final readback of a domain-onboarding job — the counts the engine PERSISTED.
|
|
231
|
+
function formatDomainStatus(s) {
|
|
232
|
+
const r = s.result ?? {};
|
|
233
|
+
const lines = [
|
|
234
|
+
`Domain onboarding ${s.status} at stage "${s.stage}" (${s.domain})`,
|
|
235
|
+
` org: ${s.org_id}`,
|
|
236
|
+
` main blog: ${s.collection_id ?? "(pending)"}${s.collection_job_id ? ` (job ${s.collection_job_id})` : ""}`,
|
|
237
|
+
` phantom job: ${s.phantom_job_id ?? "(none)"}`,
|
|
238
|
+
];
|
|
239
|
+
if (s.error) lines.push(` error: ${s.error}`);
|
|
240
|
+
if (r.collection) {
|
|
241
|
+
lines.push(
|
|
242
|
+
` posts imported: ${r.collection.posts_imported ?? 0} · content areas ${r.collection.coverage_areas ?? 0} · pillars ${r.collection.pillars ?? 0} · headlines ${r.collection.headlines_suggested ?? 0}`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
if (r.visuals) {
|
|
246
|
+
lines.push(
|
|
247
|
+
` org visuals: ${r.visuals.skipped_reason ? `skipped (${r.visuals.skipped_reason})` : `${r.visuals.created} created, ${r.visuals.reused} reused; bound ${(r.visuals.bound ?? []).join("/") || "nothing"}`}`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
if (r.enhance)
|
|
251
|
+
lines.push(` enhance flow: ${r.enhance.action}${r.enhance.flow_id ? ` (${r.enhance.flow_id})` : ""}`);
|
|
252
|
+
if (r.phantom) {
|
|
253
|
+
lines.push(
|
|
254
|
+
` phantom: ${r.phantom.site_url ?? r.phantom.deployment_id ?? "(no site)"} — ${r.phantom.status}, seeds ${r.phantom.seeds_published}/${r.phantom.seeds_total} published`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
if (r.seers) {
|
|
258
|
+
lines.push(
|
|
259
|
+
` seers: ${r.seers.skipped_reason ? `skipped (${r.seers.skipped_reason})` : `${r.seers.created} created (suggest mode), ${r.seers.reused} reused`}`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
if (r.probes) lines.push(` GEO probes: ${r.probes.runs} run(s), ${r.probes.responses} answers`);
|
|
263
|
+
for (const w of r.warnings ?? []) lines.push(` warning: ${w}`);
|
|
264
|
+
return lines.join("\n");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function watchDomainJob(ctx, jobId) {
|
|
268
|
+
const { client, flags } = ctx;
|
|
269
|
+
let printed = 0;
|
|
270
|
+
let lastStage = null;
|
|
271
|
+
const final = await pollUntil(
|
|
272
|
+
ctx.io,
|
|
273
|
+
async () => {
|
|
274
|
+
const s = await client.callTool("get_domain_onboard_status", { job_id: jobId });
|
|
275
|
+
if (s.stage !== lastStage) {
|
|
276
|
+
ok(ctx, ` … ${s.stage}`);
|
|
277
|
+
lastStage = s.stage;
|
|
278
|
+
}
|
|
279
|
+
const review = Array.isArray(s.review) ? s.review : [];
|
|
280
|
+
for (; printed < review.length; printed++) ok(ctx, ` ${review[printed]}`);
|
|
281
|
+
if (s.status === "failed") {
|
|
282
|
+
throw new CliError(
|
|
283
|
+
`Domain onboarding failed at stage "${s.stage}": ${s.error ?? "unknown error"} (the main blog job, if it ran, keeps what it imported).`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
return { done: s.status !== "running", value: s };
|
|
287
|
+
},
|
|
288
|
+
// The readiness stage waits for seed posts (default up to 2h); the watch window
|
|
289
|
+
// covers the default and a little slack.
|
|
290
|
+
{ label: "domain onboarding to finish", timeoutMs: 150 * 60 * 1000, intervalMs: 10000 }
|
|
291
|
+
);
|
|
292
|
+
printResult(ctx.io, flags, final, formatDomainStatus);
|
|
293
|
+
return 0;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// `onboard domain` — the one-shot parent. The org and its domain come from the API key.
|
|
297
|
+
async function cmdOnboardDomain(ctx) {
|
|
298
|
+
const { client, flags } = ctx;
|
|
299
|
+
|
|
300
|
+
// Attach to a job started elsewhere (admin create-org --onboard prints one).
|
|
301
|
+
const attach = flagStr(flags.attach);
|
|
302
|
+
if (attach) {
|
|
303
|
+
if (!flagBool(flags.watch)) {
|
|
304
|
+
const s = await client.callTool("get_domain_onboard_status", { job_id: attach });
|
|
305
|
+
printResult(ctx.io, flags, s, formatDomainStatus);
|
|
306
|
+
return 0;
|
|
307
|
+
}
|
|
308
|
+
return watchDomainJob(ctx, attach);
|
|
309
|
+
}
|
|
310
|
+
if (flagBool(flags.status)) {
|
|
311
|
+
const s = await client.callTool("get_domain_onboard_status", {});
|
|
312
|
+
printResult(ctx.io, flags, s, formatDomainStatus);
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const seers = flagStr(flags.seers);
|
|
317
|
+
if (seers !== undefined && !SEER_MODES.includes(seers)) {
|
|
318
|
+
throw new CliError(`--seers must be one of: ${SEER_MODES.join(", ")}`);
|
|
319
|
+
}
|
|
320
|
+
const cms = flagStr(flags.cms);
|
|
321
|
+
if (cms !== undefined && !CMS_PROVIDERS.includes(cms)) {
|
|
322
|
+
throw new CliError(`--cms must be one of: ${CMS_PROVIDERS.join(", ")}`);
|
|
323
|
+
}
|
|
324
|
+
const posts = flagNum(flags.posts);
|
|
325
|
+
const phantom = compact({
|
|
326
|
+
enabled: flagBool(flags["no-phantom"]) ? false : undefined,
|
|
327
|
+
posts_to_seed: posts,
|
|
328
|
+
name: flagStr(flags["phantom-name"]),
|
|
329
|
+
site_domain: flagStr(flags["site-domain"]),
|
|
330
|
+
domain_mode: flagStr(flags["domain-mode"]),
|
|
331
|
+
});
|
|
332
|
+
const args = compact({
|
|
333
|
+
direction: flagStr(flags.about) ?? flagStr(flags.direction),
|
|
334
|
+
sitemap_url: flagStr(flags.sitemap),
|
|
335
|
+
cms_provider: cms,
|
|
336
|
+
collection_name: flagStr(flags.name),
|
|
337
|
+
phantom: Object.keys(phantom).length ? phantom : undefined,
|
|
338
|
+
seers,
|
|
339
|
+
visuals: flagBool(flags["no-visuals"]) ? false : undefined,
|
|
340
|
+
enhance: flagBool(flags["no-enhance"]) ? false : undefined,
|
|
341
|
+
is_test: flagBool(flags.test) ? true : undefined,
|
|
342
|
+
readiness_minutes: flagNum(flags["readiness-minutes"]),
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
ok(ctx, "Starting domain onboarding…");
|
|
346
|
+
const started = await client.callTool("onboard_domain", args);
|
|
347
|
+
if (!started.job_id) throw new CliError("onboard_domain did not return a job id.");
|
|
348
|
+
ok(ctx, `Job ${started.job_id} running for ${started.domain}.`);
|
|
349
|
+
|
|
350
|
+
if (!flagBool(flags.watch)) {
|
|
351
|
+
printResult(ctx.io, flags, started, (s) =>
|
|
352
|
+
[
|
|
353
|
+
`Domain onboarding started (stage "${s.stage}").`,
|
|
354
|
+
` job: ${s.job_id}`,
|
|
355
|
+
` org: ${s.org_id}`,
|
|
356
|
+
` domain: ${s.domain}`,
|
|
357
|
+
`Watch it with: ${ctx.bin} ${ctx.positionals.length ? "phantom-onboard" : "onboard"} domain --attach ${s.job_id} --watch, or poll:`,
|
|
358
|
+
` ${ctx.bin} call get_domain_onboard_status --args '{"job_id":"${s.job_id}"}'`,
|
|
359
|
+
].join("\n")
|
|
360
|
+
);
|
|
361
|
+
return 0;
|
|
362
|
+
}
|
|
363
|
+
return watchDomainJob(ctx, started.job_id);
|
|
364
|
+
}
|
|
365
|
+
|
|
219
366
|
export async function cmdOnboard(ctx) {
|
|
220
367
|
const { client, flags, io } = ctx;
|
|
221
368
|
// `onboard collection …` — the client-blog variant (no <domain>; org from the key).
|
|
222
369
|
if (ctx.positionals[0] === "collection") {
|
|
223
370
|
return cmdOnboardCollection({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
224
371
|
}
|
|
372
|
+
// `onboard domain …` — the one-shot parent (org + domain from the key).
|
|
373
|
+
if (ctx.positionals[0] === "domain") {
|
|
374
|
+
return cmdOnboardDomain({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
375
|
+
}
|
|
225
376
|
// `onboard sync-sitemap --collection <uuid>` — refresh an already-onboarded blog.
|
|
226
377
|
if (ctx.positionals[0] === "sync-sitemap") {
|
|
227
378
|
return cmdOnboardSyncSitemap({ ...ctx, positionals: ctx.positionals.slice(1) });
|
package/lib/commands.mjs
CHANGED