@letterstory/cli 0.19.0 → 0.23.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 +40 -0
- package/lib/cli.mjs +56 -13
- package/lib/commands/collections.mjs +115 -53
- package/lib/commands/connectors.mjs +69 -51
- package/lib/commands/kernel.mjs +101 -30
- package/lib/commands/onboard.mjs +329 -103
- package/lib/commands/shared.mjs +184 -0
- package/lib/commands/shredder.mjs +31 -29
- package/lib/commands/strategy.mjs +79 -54
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -287,6 +287,18 @@ single fallback plan, the client's existing publications with an overlap warning
|
|
|
287
287
|
custom-domain headroom. `mastheads` returns names that never carry the client's own name. Without
|
|
288
288
|
`--plan-file`, `phantom-onboard <domain>` still plans from the homepage alone.
|
|
289
289
|
|
|
290
|
+
To review a plan and then apply **that** plan, hand it back with `--accept-plan`. A bare
|
|
291
|
+
onboarding computes the plan twice — once for the dry run you read, once inside the job — and
|
|
292
|
+
the second masthead proposal need not match the first, so the name committed into the URL
|
|
293
|
+
would not be the one you approved. `--accept-plan` pins it, implies `--accept`, and skips the
|
|
294
|
+
second homepage read.
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
letterstory phantom-onboard acme.com --json > plan.json # dry run: read it, edit nothing
|
|
298
|
+
letterstory phantom-onboard acme.com --accept-plan plan.json
|
|
299
|
+
jq '.plan' plan.json | letterstory phantom-onboard acme.com --accept-plan -
|
|
300
|
+
```
|
|
301
|
+
|
|
290
302
|
## Content series
|
|
291
303
|
|
|
292
304
|
A series is a coverage obligation, not a keyword bet: "content that looks like this",
|
|
@@ -326,6 +338,34 @@ letterstory kernel run --article <uuid> --kernel <uuid> [--brief "…" | --brief
|
|
|
326
338
|
letterstory kernel status <kernel-job-id>
|
|
327
339
|
```
|
|
328
340
|
|
|
341
|
+
### The standalone kernel endpoint
|
|
342
|
+
|
|
343
|
+
`kernel write` and `kernel polish` hit the standalone door: prose comes back, nothing is
|
|
344
|
+
saved into an article, and it is metered in kernel-run credits rather than the AI-usage gate.
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
letterstory kernel endpoint create --name "Writer"
|
|
348
|
+
letterstory kernel endpoint delete <endpoint-id> --yes
|
|
349
|
+
|
|
350
|
+
letterstory kernel write <endpoint-id> --file outline.md \
|
|
351
|
+
--brief-mode outline --person first --author "Ada" --shredder <endpoint-id>
|
|
352
|
+
# Follows the run and prints the ARTICLE when it lands. --no-wait returns the run id instead.
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Shredding
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
letterstory shredder run <endpoint-id> --file post.md --format markdown
|
|
359
|
+
# Queues, follows the job, prints the finished DOCUMENT. Exits non-zero if the job lands
|
|
360
|
+
# as error or degraded, so a script can branch on it without parsing the payload.
|
|
361
|
+
|
|
362
|
+
letterstory shredder run <endpoint-id> --file post.md --no-wait # just queue it
|
|
363
|
+
letterstory shredder job <job-id> # poll it yourself
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Both follow loops poll every 15–20 s. `LETTERSTORY_POLL_INTERVAL_MS` overrides that — set
|
|
367
|
+
it to `0` in CI so a test does not wait on a real interval.
|
|
368
|
+
|
|
329
369
|
`phantom-job create --topic "…" --collection <uuid>` runs topic → draft → publish → rebuild
|
|
330
370
|
end to end, with `phantom-job status <job-id>` to follow it.
|
|
331
371
|
|
package/lib/cli.mjs
CHANGED
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
} from "./commands.mjs";
|
|
45
45
|
|
|
46
46
|
// Keep in sync with cli/package.json.
|
|
47
|
-
export const VERSION = "0.
|
|
47
|
+
export const VERSION = "0.23.0";
|
|
48
48
|
|
|
49
49
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
50
50
|
// can't accidentally swallow the id as --json's value.
|
|
@@ -82,9 +82,19 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
82
82
|
"no-visuals",
|
|
83
83
|
"no-enhance",
|
|
84
84
|
"no-key",
|
|
85
|
+
// `collections activate <id> --no-topic-map` / `--no-headlines`
|
|
86
|
+
"no-topic-map",
|
|
87
|
+
"no-headlines",
|
|
85
88
|
"onboard",
|
|
86
89
|
"test",
|
|
87
90
|
"real",
|
|
91
|
+
// `phantom-onboard collection --enable-planner`, `strategy sitemap --posts-only`, and the
|
|
92
|
+
// `strategy positioning set --clear-*` switches: value-less, and each sits next to a value.
|
|
93
|
+
"enable-planner",
|
|
94
|
+
"posts-only",
|
|
95
|
+
"clear-topics",
|
|
96
|
+
"clear-stances",
|
|
97
|
+
"clear-avoid",
|
|
88
98
|
]);
|
|
89
99
|
|
|
90
100
|
// Tiny argv parser: `--flag value`, `--flag=value`, boolean `--flag`, and positionals.
|
|
@@ -194,9 +204,12 @@ Reviewers:
|
|
|
194
204
|
Collections:
|
|
195
205
|
collections list List your collections
|
|
196
206
|
collections new --name <n> [--description <text>]
|
|
197
|
-
collections update <id> [--name] [--description] [--cadence-target <n>] [--cadence-period week|month]
|
|
207
|
+
collections update <id> [--name] [--description] [--cadence-target <n>] [--cadence-period week|month]
|
|
208
|
+
[--cover-canvas-type-id <uuid>|none] [--seo-market <cc>]
|
|
198
209
|
collections delete <id> --yes
|
|
199
210
|
collections assign <article-id> <collection-id>
|
|
211
|
+
collections activate <id> [--cadence <n>] [--approval manual|review_window|autopilot] [--no-topic-map] [--no-headlines]
|
|
212
|
+
The review is done: topic map from the areas, first headlines, Planner on, queue filled
|
|
200
213
|
collections autopen <id> [--template <key>|none] [--kernel-family <name>|org]
|
|
201
214
|
[--mention off|subtle|woven] [--outline-depth barebones|fleshed_out]
|
|
202
215
|
[--direction-gate true|false] [--auto-start true|false]
|
|
@@ -245,11 +258,11 @@ Connectors:
|
|
|
245
258
|
Wire a collection to a CMS + field mapping
|
|
246
259
|
|
|
247
260
|
Strategy & onboarding:
|
|
248
|
-
strategy company get | set [--name] [--domain] [--manifesto <text>|--manifesto-file <path>]
|
|
261
|
+
strategy company get | set [--name] [--domain] [--market <cc>] [--manifesto <text>|--manifesto-file <path>]
|
|
249
262
|
strategy positioning get | set [--topic <t> …] [--stance <s> …] [--avoid <t> …]
|
|
250
263
|
[--clear-topics] [--clear-stances] [--clear-avoid]
|
|
251
264
|
strategy competitors list | add <name> <domain>
|
|
252
|
-
strategy sitemap --collection <uuid> --url <sitemap_url> [--sub <url> …] [--pattern <glob>]
|
|
265
|
+
strategy sitemap --collection <uuid> --url <sitemap_url> [--sub <url> …] [--pattern <glob>] [--posts-only]
|
|
253
266
|
strategy topics list --collection <uuid> List the topic map's clusters/spokes
|
|
254
267
|
strategy topics set --collection <uuid> (--topic <topic-id> | --suggestion <suggestion-id>)
|
|
255
268
|
suggestions list <collection_id> List pending strategy suggestions + their auto-apply countdown
|
|
@@ -298,14 +311,16 @@ Writing kernels (kernels write the draft; they do not research — run research
|
|
|
298
311
|
|
|
299
312
|
Standalone — prose back, nothing saved into an article (metered in kernel-run credits):
|
|
300
313
|
kernel endpoints Your kernel endpoints
|
|
314
|
+
kernel endpoint create --name <n> [--kernel <uuid>] [--shredder <endpoint-id>]
|
|
315
|
+
New kernel endpoint, optionally bound
|
|
316
|
+
to a kernel and an in-loop shredder
|
|
317
|
+
kernel endpoint delete <endpoint-id> --yes Remove one
|
|
301
318
|
kernel write <endpoint-id> (--body <text>|--file <path>|--file -)
|
|
302
|
-
|
|
303
|
-
[--shredder <uuid>] shred every round in the loop, so the
|
|
304
|
-
kernel judges the text that ships
|
|
305
|
-
[--read-as brief|blog|outline|research|notes]
|
|
319
|
+
export const VERSION = "0.23.0";
|
|
306
320
|
WRITE a new article from a headline,
|
|
307
321
|
outline, notes or a finished post.
|
|
308
|
-
Follows the run unless --no-wait
|
|
322
|
+
Follows the run unless --no-wait, and
|
|
323
|
+
prints the article when it lands.
|
|
309
324
|
kernel polish <endpoint-id> (--body <html>|--file <path>) [--format auto|html]
|
|
310
325
|
POLISH the document you have: prose
|
|
311
326
|
reworded, your own markup kept
|
|
@@ -336,6 +351,13 @@ Phantom onboarding recipe (domain -> live phantom blog with first posts):
|
|
|
336
351
|
lane below: its direction AND pillars are
|
|
337
352
|
applied verbatim, so the site covers what
|
|
338
353
|
was approved.
|
|
354
|
+
[--accept-plan <path|->] Apply a plan you already reviewed,
|
|
355
|
+
verbatim — the "plan" from a dry run, or
|
|
356
|
+
a whole saved --json response. Implies
|
|
357
|
+
--accept and skips re-planning, so the
|
|
358
|
+
masthead you approved is the one committed
|
|
359
|
+
(without it the apply re-reads the homepage
|
|
360
|
+
and may propose a different name).
|
|
339
361
|
|
|
340
362
|
Propose lane (the Fleet wizard's Plan and Name screens; persists nothing, spends nothing):
|
|
341
363
|
${bin === "phantom" ? "onboard" : "phantom-onboard"} propose [<client-domain>] [--about "the beat"] [--name X] [--json]
|
|
@@ -353,9 +375,19 @@ Propose lane (the Fleet wizard's Plan and Name screens; persists nothing, spends
|
|
|
353
375
|
--plan-id picks the plan) or a single plan
|
|
354
376
|
(--plan '<json>' inline, or - for stdin).
|
|
355
377
|
|
|
378
|
+
What does this org still need? (the router — reads only, decides nothing):
|
|
379
|
+
${bin === "phantom" ? "onboard" : "phantom-onboard"} plan Every onboarding step (context, main blog,
|
|
380
|
+
Planner, CMS, phantom) with its status:
|
|
381
|
+
done / running / ready / blocked, and what
|
|
382
|
+
a blocked step is waiting on.
|
|
383
|
+
${bin === "phantom" ? "onboard" : "phantom-onboard"} next The first ready step Letterstory recommends,
|
|
384
|
+
as the exact command to run next. Optional
|
|
385
|
+
steps are listed, never chosen.
|
|
386
|
+
|
|
356
387
|
Collection onboarding (your OWN blog, on your org's domain — no provisioning):
|
|
357
388
|
${bin === "phantom" ? "onboard" : "phantom-onboard"} collection [--collection <uuid>] [--name "<collection name>"]
|
|
358
|
-
[--about "the direction"] [--sitemap <url>] [--cms <provider>]
|
|
389
|
+
[--about "the direction"] [--sitemap <url>] [--cms <provider>]
|
|
390
|
+
[--enable-planner [--cadence <n/week>] [--approval manual|review_window|autopilot]] [--watch]
|
|
359
391
|
Import the existing blog from its sitemap
|
|
360
392
|
(auto-discovered from the org's domain unless
|
|
361
393
|
--sitemap pins one) OR from a connected CMS with
|
|
@@ -363,7 +395,12 @@ Collection onboarding (your OWN blog, on your org's domain — no provisioning):
|
|
|
363
395
|
derive content areas + series, build the topic
|
|
364
396
|
map, bind SEO demand, and stock Headlines & Queue.
|
|
365
397
|
Omit --collection to create a fresh one (--name,
|
|
366
|
-
default Blog). The Planner stays OFF
|
|
398
|
+
default Blog). The Planner stays OFF unless you
|
|
399
|
+
pass --enable-planner, which turns it on at the
|
|
400
|
+
chosen cadence and approval and fills the queue
|
|
401
|
+
(same activation as the app's Guided review step;
|
|
402
|
+
needs a user-scoped key). --approval defaults to
|
|
403
|
+
manual: nothing publishes unreviewed.
|
|
367
404
|
${bin === "phantom" ? "onboard" : "phantom-onboard"} sync-sitemap --collection <uuid>
|
|
368
405
|
Re-check an already-onboarded blog's sitemap
|
|
369
406
|
right now instead of waiting for its cadence
|
|
@@ -449,6 +486,9 @@ Shredder (diversify content produced outside Letterstory; structure preserved):
|
|
|
449
486
|
shredder create --name <n> New endpoint (returns a callable public id)
|
|
450
487
|
shredder delete <endpoint-id> --yes
|
|
451
488
|
shredder run <endpoint-id> (--body <text>|--file <path>|--file -) [--coverage <0..1>] [--attempts <n>]
|
|
489
|
+
[--format auto|html|markdown]
|
|
490
|
+
what the text IS; 'auto' reads a document
|
|
491
|
+
containing block-level HTML as HTML
|
|
452
492
|
[--aggression standard|high|max]
|
|
453
493
|
standard (default) keeps the piece's own terminology;
|
|
454
494
|
high rewrites more and may reword recurring terms;
|
|
@@ -462,8 +502,11 @@ Shredder (diversify content produced outside Letterstory; structure preserved):
|
|
|
462
502
|
[--sync] wait inside one request (only for a few
|
|
463
503
|
sentences — anything longer is cut off)
|
|
464
504
|
Shred prose through an endpoint (logs telemetry).
|
|
465
|
-
Queues the work and follows it
|
|
466
|
-
|
|
505
|
+
Queues the work and follows it, printing the finished
|
|
506
|
+
document; a shred takes minutes and keeps running on
|
|
507
|
+
the server if you stop waiting. Exits non-zero if the
|
|
508
|
+
job lands as error or degraded. Pace the polling with
|
|
509
|
+
LETTERSTORY_POLL_INTERVAL_MS.
|
|
467
510
|
shredder job <job-id> Poll a queued shred and print it when it lands
|
|
468
511
|
shredder runs [--endpoint <id>] [--limit <n>] Recent shred calls (telemetry), newest first
|
|
469
512
|
|
|
@@ -2,7 +2,82 @@
|
|
|
2
2
|
// Ported from phantomstory-cli's collections.ts.
|
|
3
3
|
|
|
4
4
|
import { CliError } from "../client.mjs";
|
|
5
|
-
import { flagStr,
|
|
5
|
+
import { flagStr, printResult, ok, argsFromFlags } from "./shared.mjs";
|
|
6
|
+
|
|
7
|
+
// `--flag none|off|""` clears a nullable field; the server takes null for that.
|
|
8
|
+
const clearable = (flag) => ({
|
|
9
|
+
value: (flags) => {
|
|
10
|
+
const v = flagStr(flags[flag]);
|
|
11
|
+
if (v === undefined) return undefined;
|
|
12
|
+
return ["none", "off", ""].includes(v.toLowerCase()) ? null : v;
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// A `--flag false` boolean (the value is the string "false"; anything else is true).
|
|
17
|
+
const stringBool = (flag) => ({
|
|
18
|
+
value: (flags) => (flags[flag] === undefined ? undefined : flags[flag] !== "false"),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// How every tool this module calls gets its arguments — see `argsFromFlags` in shared.mjs.
|
|
22
|
+
// `scripts/parity-check.ts` compares these keys against each tool's published input schema.
|
|
23
|
+
export const ARGS = {
|
|
24
|
+
list_collections: {},
|
|
25
|
+
create_collection: {
|
|
26
|
+
name: { flag: "name", required: true },
|
|
27
|
+
description: "description",
|
|
28
|
+
},
|
|
29
|
+
update_collection: {
|
|
30
|
+
collection_id: { positional: 0, label: "collection-id", required: true },
|
|
31
|
+
name: "name",
|
|
32
|
+
description: "description",
|
|
33
|
+
cadence_target: { flag: "cadence-target", type: "num" },
|
|
34
|
+
cadence_period: { flag: "cadence-period", enum: ["week", "month"] },
|
|
35
|
+
cover_canvas_type_id: clearable("cover-canvas-type-id"),
|
|
36
|
+
seo_market: "seo-market",
|
|
37
|
+
},
|
|
38
|
+
delete_collection: {
|
|
39
|
+
collection_id: { positional: 0, label: "collection-id", required: true },
|
|
40
|
+
},
|
|
41
|
+
assign_article_to_collection: {
|
|
42
|
+
article_id: { positional: 0, label: "article-id", required: true },
|
|
43
|
+
collection_id: { positional: 1, label: "collection-id", required: true },
|
|
44
|
+
},
|
|
45
|
+
// `collections autopen <id>` — a PATCH: only the flags given are sent.
|
|
46
|
+
// `collections activate <id>` — the review is done, turn the Planner on. Omitted
|
|
47
|
+
// knobs keep the collection's saved values, so nothing here carries a default:
|
|
48
|
+
// an absent flag must stay absent rather than becoming an opinion (see #1773).
|
|
49
|
+
activate_collection: {
|
|
50
|
+
collection_id: { positional: 0, label: "collection-id", required: true },
|
|
51
|
+
cadence_per_week: { flag: "cadence", type: "num" },
|
|
52
|
+
approval_mode: { flag: "approval", enum: ["manual", "review_window", "autopilot"] },
|
|
53
|
+
// `--no-topic-map` / `--no-headlines` mean "skip that step"; absent means "do it".
|
|
54
|
+
regenerate_topic_map: { value: (flags) => (flags["no-topic-map"] === undefined ? undefined : false) },
|
|
55
|
+
stock_headlines: { value: (flags) => (flags["no-headlines"] === undefined ? undefined : false) },
|
|
56
|
+
},
|
|
57
|
+
update_auto_pen_config: {
|
|
58
|
+
collection_id: { positional: 0, label: "collection-id", required: true },
|
|
59
|
+
// `--template none` clears the binding; the server takes null for that.
|
|
60
|
+
template_key: clearable("template"),
|
|
61
|
+
outline: {
|
|
62
|
+
object: {
|
|
63
|
+
depth: "outline-depth",
|
|
64
|
+
direction_gate: stringBool("direction-gate"),
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
kernel: {
|
|
68
|
+
object: {
|
|
69
|
+
family: {
|
|
70
|
+
value: (flags) => {
|
|
71
|
+
const v = flagStr(flags["kernel-family"]);
|
|
72
|
+
return v === undefined ? undefined : v === "org" ? null : v;
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
mention: { object: { degree: "mention" } },
|
|
78
|
+
auto_start_on_curate: stringBool("auto-start"),
|
|
79
|
+
},
|
|
80
|
+
};
|
|
6
81
|
|
|
7
82
|
export async function cmdCollections(ctx) {
|
|
8
83
|
const sub = ctx.positionals[0];
|
|
@@ -23,13 +98,38 @@ export async function cmdCollections(ctx) {
|
|
|
23
98
|
return collectionsAssign(rest);
|
|
24
99
|
case "autopen":
|
|
25
100
|
return collectionsAutoPen(rest);
|
|
101
|
+
case "activate":
|
|
102
|
+
return collectionsActivate(rest);
|
|
26
103
|
default:
|
|
27
104
|
throw new CliError(
|
|
28
|
-
`Unknown collections subcommand: ${sub ?? "(none)"}. Try: list, new, update, delete, assign, autopen`
|
|
105
|
+
`Unknown collections subcommand: ${sub ?? "(none)"}. Try: list, new, update, delete, assign, autopen, activate`
|
|
29
106
|
);
|
|
30
107
|
}
|
|
31
108
|
}
|
|
32
109
|
|
|
110
|
+
/**
|
|
111
|
+
* `collections activate <id>` — the review is done: build the topic map from the
|
|
112
|
+
* collection's content areas, seed headlines, turn the Planner on with the queue
|
|
113
|
+
* filled. The same activation the app's Guided review step runs on Continue.
|
|
114
|
+
* Omitted knobs keep the collection's saved values; the response says, per knob,
|
|
115
|
+
* whether it came from you, from the saved config, or from the default.
|
|
116
|
+
*/
|
|
117
|
+
async function collectionsActivate(ctx) {
|
|
118
|
+
const { client, positionals, flags, io } = ctx;
|
|
119
|
+
const args = argsFromFlags(ARGS.activate_collection, flags, positionals);
|
|
120
|
+
const result = await client.callTool("activate_collection", args);
|
|
121
|
+
if (result && result.activated === false) {
|
|
122
|
+
// The K1 floor, as data: say why, and name the tools that clear it.
|
|
123
|
+
io.error(`Not activated: ${result.reason}`);
|
|
124
|
+
for (const r of result.remedies ?? []) io.error(` ${r.tool} — ${r.why}`);
|
|
125
|
+
printResult(io, flags, result);
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
ok(ctx, result?.note ?? `Activated collection ${args.collection_id}.`);
|
|
129
|
+
printResult(io, flags, result);
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
33
133
|
async function collectionsList(ctx) {
|
|
34
134
|
const { client, flags, io } = ctx;
|
|
35
135
|
const result = await client.callTool("list_collections", {});
|
|
@@ -39,35 +139,19 @@ async function collectionsList(ctx) {
|
|
|
39
139
|
|
|
40
140
|
async function collectionsNew(ctx) {
|
|
41
141
|
const { client, flags, io } = ctx;
|
|
42
|
-
const
|
|
43
|
-
const args = compact({ name, description: flagStr(flags.description) });
|
|
142
|
+
const args = argsFromFlags(ARGS.create_collection, flags);
|
|
44
143
|
const result = await client.callTool("create_collection", args);
|
|
45
|
-
ok(ctx, `Created collection "${name}".`);
|
|
144
|
+
ok(ctx, `Created collection "${args.name}".`);
|
|
46
145
|
printResult(io, flags, result);
|
|
47
146
|
return 0;
|
|
48
147
|
}
|
|
49
148
|
|
|
50
149
|
async function collectionsUpdate(ctx) {
|
|
51
150
|
const { client, positionals, flags, io } = ctx;
|
|
52
|
-
const
|
|
53
|
-
const coverCanvasTypeId = flagStr(flags["cover-canvas-type-id"]);
|
|
54
|
-
const patch = compact({
|
|
55
|
-
collection_id: id,
|
|
56
|
-
name: flagStr(flags.name),
|
|
57
|
-
description: flagStr(flags.description),
|
|
58
|
-
cadence_target: flagNum(flags["cadence-target"]),
|
|
59
|
-
cadence_period: flagStr(flags["cadence-period"]),
|
|
60
|
-
});
|
|
61
|
-
if (coverCanvasTypeId !== undefined) {
|
|
62
|
-
// compact() drops undefined, not null — set it after so "none"/"off" can clear
|
|
63
|
-
// the field instead of being dropped. Same convention as `webhook set --url none`.
|
|
64
|
-
patch.cover_canvas_type_id = ["none", "off", ""].includes(coverCanvasTypeId.toLowerCase())
|
|
65
|
-
? null
|
|
66
|
-
: coverCanvasTypeId;
|
|
67
|
-
}
|
|
151
|
+
const patch = argsFromFlags(ARGS.update_collection, flags, positionals);
|
|
68
152
|
if (Object.keys(patch).length <= 1) {
|
|
69
153
|
throw new CliError(
|
|
70
|
-
"Nothing to update — pass at least one of --name/--description/--cadence-target/--cadence-period/--cover-canvas-type-id."
|
|
154
|
+
"Nothing to update — pass at least one of --name/--description/--cadence-target/--cadence-period/--cover-canvas-type-id/--seo-market."
|
|
71
155
|
);
|
|
72
156
|
}
|
|
73
157
|
const result = await client.callTool("update_collection", patch);
|
|
@@ -78,12 +162,12 @@ async function collectionsUpdate(ctx) {
|
|
|
78
162
|
|
|
79
163
|
async function collectionsDelete(ctx) {
|
|
80
164
|
const { client, positionals, flags, io } = ctx;
|
|
81
|
-
const
|
|
165
|
+
const args = argsFromFlags(ARGS.delete_collection, flags, positionals);
|
|
82
166
|
if (!flags.yes) {
|
|
83
|
-
io.error(`Re-run with --yes to confirm: ${ctx.bin} collections delete ${
|
|
167
|
+
io.error(`Re-run with --yes to confirm: ${ctx.bin} collections delete ${args.collection_id} --yes`);
|
|
84
168
|
return 1;
|
|
85
169
|
}
|
|
86
|
-
const result = await client.callTool("delete_collection",
|
|
170
|
+
const result = await client.callTool("delete_collection", args);
|
|
87
171
|
ok(ctx, "Collection deleted.");
|
|
88
172
|
printResult(io, flags, result);
|
|
89
173
|
return 0;
|
|
@@ -91,12 +175,10 @@ async function collectionsDelete(ctx) {
|
|
|
91
175
|
|
|
92
176
|
async function collectionsAssign(ctx) {
|
|
93
177
|
const { client, positionals, flags, io } = ctx;
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
collection_id: collectionId,
|
|
99
|
-
});
|
|
178
|
+
const result = await client.callTool(
|
|
179
|
+
"assign_article_to_collection",
|
|
180
|
+
argsFromFlags(ARGS.assign_article_to_collection, flags, positionals)
|
|
181
|
+
);
|
|
100
182
|
ok(ctx, "Article assigned.");
|
|
101
183
|
printResult(io, flags, result);
|
|
102
184
|
return 0;
|
|
@@ -111,27 +193,7 @@ async function collectionsAssign(ctx) {
|
|
|
111
193
|
*/
|
|
112
194
|
async function collectionsAutoPen(ctx) {
|
|
113
195
|
const { client, positionals, flags, io } = ctx;
|
|
114
|
-
const
|
|
115
|
-
const patch = { collection_id: collectionId };
|
|
116
|
-
|
|
117
|
-
// `--template none` clears the binding; the server takes null for that.
|
|
118
|
-
const template = flagStr(flags.template);
|
|
119
|
-
if (template !== undefined) patch.template_key = template === "none" ? null : template;
|
|
120
|
-
|
|
121
|
-
const kernelFamily = flagStr(flags["kernel-family"]);
|
|
122
|
-
if (kernelFamily !== undefined) patch.kernel = { family: kernelFamily === "org" ? null : kernelFamily };
|
|
123
|
-
|
|
124
|
-
const mention = flagStr(flags.mention);
|
|
125
|
-
if (mention !== undefined) patch.mention = { degree: mention };
|
|
126
|
-
|
|
127
|
-
const depth = flagStr(flags["outline-depth"]);
|
|
128
|
-
if (depth !== undefined) patch.outline = { ...(patch.outline ?? {}), depth };
|
|
129
|
-
|
|
130
|
-
if (flags["direction-gate"] !== undefined) {
|
|
131
|
-
patch.outline = { ...(patch.outline ?? {}), direction_gate: flags["direction-gate"] !== "false" };
|
|
132
|
-
}
|
|
133
|
-
if (flags["auto-start"] !== undefined) patch.auto_start_on_curate = flags["auto-start"] !== "false";
|
|
134
|
-
|
|
196
|
+
const patch = argsFromFlags(ARGS.update_auto_pen_config, flags, positionals);
|
|
135
197
|
if (Object.keys(patch).length === 1) {
|
|
136
198
|
throw new CliError(
|
|
137
199
|
"Nothing to change. Pass at least one of --template, --kernel-family, --mention, --outline-depth, --direction-gate, --auto-start."
|
|
@@ -139,7 +201,7 @@ async function collectionsAutoPen(ctx) {
|
|
|
139
201
|
}
|
|
140
202
|
|
|
141
203
|
const result = await client.callTool("update_auto_pen_config", patch);
|
|
142
|
-
ok(ctx, `Auto-Pen settings updated for ${
|
|
204
|
+
ok(ctx, `Auto-Pen settings updated for ${patch.collection_id}.`);
|
|
143
205
|
printResult(io, flags, result);
|
|
144
206
|
return 0;
|
|
145
207
|
}
|
|
@@ -3,7 +3,62 @@
|
|
|
3
3
|
// phantomstory-cli's connectors.ts.
|
|
4
4
|
|
|
5
5
|
import { CliError } from "../client.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { printResult, ok, argsFromFlags } from "./shared.mjs";
|
|
7
|
+
|
|
8
|
+
const CMS_PROVIDERS = ["webflow", "framer", "sanity", "contentful"];
|
|
9
|
+
const PUBLISH_CONNECTORS = ["webflow", "framer", "google_docs", "contentful"];
|
|
10
|
+
|
|
11
|
+
// How every tool this module calls gets its arguments — see `argsFromFlags` in shared.mjs.
|
|
12
|
+
// `scripts/parity-check.ts` compares these keys against each tool's published input schema.
|
|
13
|
+
export const ARGS = {
|
|
14
|
+
list_publish_targets: {},
|
|
15
|
+
publish_to_connector: {
|
|
16
|
+
article_id: { positional: 0, label: "article-id", required: true },
|
|
17
|
+
connector: { flag: "to", required: true, enum: PUBLISH_CONNECTORS },
|
|
18
|
+
target_id: { flag: "target", required: true },
|
|
19
|
+
flow_run_id: "flow-run",
|
|
20
|
+
// A re-publish whose external item was deleted or unpublished in the client's CMS
|
|
21
|
+
// is refused, and the refusal explains itself. --confirm-missing-target is the
|
|
22
|
+
// answer to that specific question: it re-creates / re-publishes the item there.
|
|
23
|
+
// Deliberately not bundled into a general --yes, and never set by default.
|
|
24
|
+
confirm_missing_target: { flag: "confirm-missing-target", type: "bool" },
|
|
25
|
+
},
|
|
26
|
+
get_publish_status: {
|
|
27
|
+
connector: { flag: "connector", required: true, enum: PUBLISH_CONNECTORS },
|
|
28
|
+
publish_id: { flag: "publish-id", required: true },
|
|
29
|
+
},
|
|
30
|
+
import_from_cms: {
|
|
31
|
+
collection_id: "collection",
|
|
32
|
+
provider: { flag: "provider", enum: CMS_PROVIDERS },
|
|
33
|
+
},
|
|
34
|
+
connect_cms: {
|
|
35
|
+
provider: { flag: "provider", required: true, enum: CMS_PROVIDERS },
|
|
36
|
+
// api_token is required for every provider; the rest are provider-specific.
|
|
37
|
+
api_token: { flag: "token", required: true },
|
|
38
|
+
project_url: "project-url",
|
|
39
|
+
auto_deploy: { flag: "auto-deploy", type: "bool", negate: "no-auto-deploy" },
|
|
40
|
+
project_id: "project-id",
|
|
41
|
+
dataset: "dataset",
|
|
42
|
+
project_name: "project-name",
|
|
43
|
+
workspace_name: "workspace",
|
|
44
|
+
space_id: "space-id",
|
|
45
|
+
environment_id: "environment",
|
|
46
|
+
space_name: "space-name",
|
|
47
|
+
default_locale: "locale",
|
|
48
|
+
},
|
|
49
|
+
disconnect_cms: {
|
|
50
|
+
provider: { flag: "provider", required: true, enum: CMS_PROVIDERS },
|
|
51
|
+
},
|
|
52
|
+
list_cms_connections: {},
|
|
53
|
+
list_cms_mappings: {
|
|
54
|
+
provider: { flag: "provider", required: true, enum: CMS_PROVIDERS },
|
|
55
|
+
},
|
|
56
|
+
bind_collection_cms: {
|
|
57
|
+
collection_id: { flag: "collection", required: true },
|
|
58
|
+
provider: { flag: "provider", required: true, enum: CMS_PROVIDERS },
|
|
59
|
+
mapping_id: "mapping",
|
|
60
|
+
},
|
|
61
|
+
};
|
|
7
62
|
|
|
8
63
|
export async function cmdConnectors(ctx) {
|
|
9
64
|
const sub = ctx.positionals[0];
|
|
@@ -46,31 +101,16 @@ async function connectorsList(ctx) {
|
|
|
46
101
|
|
|
47
102
|
async function connectorsPublish(ctx) {
|
|
48
103
|
const { client, positionals, flags, io } = ctx;
|
|
49
|
-
const
|
|
50
|
-
const to = requireFlag(flags, "to");
|
|
51
|
-
const target = requireFlag(flags, "target");
|
|
52
|
-
// A re-publish whose external item was deleted or unpublished in the client's CMS
|
|
53
|
-
// is refused, and the refusal explains itself. --confirm-missing-target is the
|
|
54
|
-
// answer to that specific question: it re-creates / re-publishes the item there.
|
|
55
|
-
// Deliberately not bundled into a general --yes, and never set by default.
|
|
56
|
-
const args = compact({
|
|
57
|
-
article_id: articleId,
|
|
58
|
-
connector: to,
|
|
59
|
-
target_id: target,
|
|
60
|
-
flow_run_id: flagStr(flags["flow-run"]),
|
|
61
|
-
confirm_missing_target: flagBool(flags["confirm-missing-target"]) || undefined,
|
|
62
|
-
});
|
|
104
|
+
const args = argsFromFlags(ARGS.publish_to_connector, flags, positionals);
|
|
63
105
|
const result = await client.callTool("publish_to_connector", args);
|
|
64
|
-
ok(ctx, `Publishing to ${
|
|
106
|
+
ok(ctx, `Publishing to ${args.connector}.`);
|
|
65
107
|
printResult(io, flags, result);
|
|
66
108
|
return 0;
|
|
67
109
|
}
|
|
68
110
|
|
|
69
111
|
async function connectorsStatus(ctx) {
|
|
70
112
|
const { client, flags, io } = ctx;
|
|
71
|
-
const
|
|
72
|
-
const publishId = requireFlag(flags, "publish-id");
|
|
73
|
-
const result = await client.callTool("get_publish_status", { connector, publish_id: publishId });
|
|
113
|
+
const result = await client.callTool("get_publish_status", argsFromFlags(ARGS.get_publish_status, flags));
|
|
74
114
|
printResult(io, flags, result);
|
|
75
115
|
return 0;
|
|
76
116
|
}
|
|
@@ -79,16 +119,14 @@ async function connectorsPull(ctx) {
|
|
|
79
119
|
const { client, flags, io } = ctx;
|
|
80
120
|
// Dual key: --collection (a CMS-bound collection), or --provider (resolve/wire),
|
|
81
121
|
// or both (--provider wires an unbound --collection first). At least one required.
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
if (!collectionId && !provider) {
|
|
122
|
+
const args = argsFromFlags(ARGS.import_from_cms, flags);
|
|
123
|
+
if (!args.collection_id && !args.provider) {
|
|
85
124
|
throw new CliError("Pass --collection <id> and/or --provider <webflow|framer|sanity|contentful>");
|
|
86
125
|
}
|
|
87
|
-
const args = compact({ collection_id: collectionId, provider });
|
|
88
126
|
const result = await client.callTool("import_from_cms", args);
|
|
89
127
|
ok(
|
|
90
128
|
ctx,
|
|
91
|
-
`Pulled from ${result?.provider ?? provider ?? "CMS"} into collection ${result?.collection_id ??
|
|
129
|
+
`Pulled from ${result?.provider ?? args.provider ?? "CMS"} into collection ${result?.collection_id ?? args.collection_id ?? ""}.`
|
|
92
130
|
);
|
|
93
131
|
printResult(io, flags, result);
|
|
94
132
|
return 0;
|
|
@@ -96,33 +134,18 @@ async function connectorsPull(ctx) {
|
|
|
96
134
|
|
|
97
135
|
async function connectorsConnect(ctx) {
|
|
98
136
|
const { client, flags, io } = ctx;
|
|
99
|
-
const
|
|
100
|
-
// api_token is required for every provider; the rest are provider-specific.
|
|
101
|
-
const args = compact({
|
|
102
|
-
provider,
|
|
103
|
-
api_token: requireFlag(flags, "token"),
|
|
104
|
-
project_url: flagStr(flags["project-url"]),
|
|
105
|
-
auto_deploy: flags["no-auto-deploy"] ? false : flagBool(flags["auto-deploy"]) || undefined,
|
|
106
|
-
project_id: flagStr(flags["project-id"]),
|
|
107
|
-
dataset: flagStr(flags.dataset),
|
|
108
|
-
project_name: flagStr(flags["project-name"]),
|
|
109
|
-
workspace_name: flagStr(flags.workspace),
|
|
110
|
-
space_id: flagStr(flags["space-id"]),
|
|
111
|
-
environment_id: flagStr(flags.environment),
|
|
112
|
-
space_name: flagStr(flags["space-name"]),
|
|
113
|
-
default_locale: flagStr(flags.locale),
|
|
114
|
-
});
|
|
137
|
+
const args = argsFromFlags(ARGS.connect_cms, flags);
|
|
115
138
|
const result = await client.callTool("connect_cms", args);
|
|
116
|
-
ok(ctx, `Connected ${provider}${result?.label ? ` (${result.label})` : ""}.`);
|
|
139
|
+
ok(ctx, `Connected ${args.provider}${result?.label ? ` (${result.label})` : ""}.`);
|
|
117
140
|
printResult(io, flags, result);
|
|
118
141
|
return 0;
|
|
119
142
|
}
|
|
120
143
|
|
|
121
144
|
async function connectorsDisconnect(ctx) {
|
|
122
145
|
const { client, flags, io } = ctx;
|
|
123
|
-
const
|
|
124
|
-
const result = await client.callTool("disconnect_cms",
|
|
125
|
-
ok(ctx, `Disconnected ${provider}.`);
|
|
146
|
+
const args = argsFromFlags(ARGS.disconnect_cms, flags);
|
|
147
|
+
const result = await client.callTool("disconnect_cms", args);
|
|
148
|
+
ok(ctx, `Disconnected ${args.provider}.`);
|
|
126
149
|
printResult(io, flags, result);
|
|
127
150
|
return 0;
|
|
128
151
|
}
|
|
@@ -136,19 +159,14 @@ async function connectorsConnections(ctx) {
|
|
|
136
159
|
|
|
137
160
|
async function connectorsMappings(ctx) {
|
|
138
161
|
const { client, flags, io } = ctx;
|
|
139
|
-
const
|
|
140
|
-
const result = await client.callTool("list_cms_mappings", { provider });
|
|
162
|
+
const result = await client.callTool("list_cms_mappings", argsFromFlags(ARGS.list_cms_mappings, flags));
|
|
141
163
|
printResult(io, flags, result);
|
|
142
164
|
return 0;
|
|
143
165
|
}
|
|
144
166
|
|
|
145
167
|
async function connectorsBind(ctx) {
|
|
146
168
|
const { client, flags, io } = ctx;
|
|
147
|
-
const args =
|
|
148
|
-
collection_id: requireFlag(flags, "collection"),
|
|
149
|
-
provider: requireFlag(flags, "provider"),
|
|
150
|
-
mapping_id: flagStr(flags.mapping),
|
|
151
|
-
});
|
|
169
|
+
const args = argsFromFlags(ARGS.bind_collection_cms, flags);
|
|
152
170
|
const result = await client.callTool("bind_collection_cms", args);
|
|
153
171
|
ok(
|
|
154
172
|
ctx,
|