@letterstory/cli 0.4.0 → 0.4.1
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 +19 -0
- package/lib/cli.mjs +19 -1
- package/lib/commands/covers.mjs +89 -0
- package/lib/commands.mjs +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -236,6 +236,25 @@ letterstory kernel status <kernel-job-id>
|
|
|
236
236
|
`phantom-job create --topic "…" --collection <uuid>` runs topic → draft → publish → rebuild
|
|
237
237
|
end to end, with `phantom-job status <job-id>` to follow it.
|
|
238
238
|
|
|
239
|
+
## Cover images
|
|
240
|
+
|
|
241
|
+
Which image model draws a post's cover is a CLI decision, not a UI-only one. `variants`
|
|
242
|
+
fans one headline out across up to four models and hands back a preview per model;
|
|
243
|
+
`select` commits the one you want:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
letterstory covers models # image models connected to this org
|
|
247
|
+
letterstory covers variants --article <uuid> --model bloom --model openai
|
|
248
|
+
letterstory covers select --article <uuid> --url <variant-url> --provider bloom
|
|
249
|
+
|
|
250
|
+
# …or skip the comparison and let the server pick, using the collection's cover template:
|
|
251
|
+
letterstory covers generate <article-id> [--regenerate] [--stock-only]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
`--ref-url <url>` (repeatable) conditions the look on existing images — pair it with
|
|
255
|
+
`extract_blog_reference_images` to match a site you're mirroring. Each variant costs one
|
|
256
|
+
image generation, which is why the fan-out is capped at four models.
|
|
257
|
+
|
|
239
258
|
## Insights
|
|
240
259
|
|
|
241
260
|
Search Console performance, network-wide, per post, or top posts:
|
package/lib/cli.mjs
CHANGED
|
@@ -27,12 +27,13 @@ import {
|
|
|
27
27
|
cmdOnboarding,
|
|
28
28
|
cmdInsights,
|
|
29
29
|
cmdResearch,
|
|
30
|
+
cmdCovers,
|
|
30
31
|
cmdKernel,
|
|
31
32
|
cmdPhantomJob,
|
|
32
33
|
} from "./commands.mjs";
|
|
33
34
|
|
|
34
35
|
// Keep in sync with cli/package.json.
|
|
35
|
-
export const VERSION = "0.4.
|
|
36
|
+
export const VERSION = "0.4.1";
|
|
36
37
|
|
|
37
38
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
38
39
|
// can't accidentally swallow the id as --json's value.
|
|
@@ -50,6 +51,13 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
50
51
|
"rebuild",
|
|
51
52
|
"print-key",
|
|
52
53
|
"stdin",
|
|
54
|
+
// `covers generate <id> --regenerate` and `research start --gate --article <id>`
|
|
55
|
+
// both put a value-less flag next to a value: without these entries the parser
|
|
56
|
+
// hands the following token to the flag and the real argument disappears.
|
|
57
|
+
"regenerate",
|
|
58
|
+
"stock-only",
|
|
59
|
+
"gate",
|
|
60
|
+
"skip",
|
|
53
61
|
]);
|
|
54
62
|
|
|
55
63
|
// Tiny argv parser: `--flag value`, `--flag=value`, boolean `--flag`, and positionals.
|
|
@@ -198,6 +206,15 @@ Research agent (deep research -> outline written into the post):
|
|
|
198
206
|
Answer the --gate pause
|
|
199
207
|
research cancel --article <uuid> Cancel a run in flight
|
|
200
208
|
|
|
209
|
+
Cover images (which image model draws the post's cover):
|
|
210
|
+
covers models Image models connected right now
|
|
211
|
+
covers variants --article <uuid> [--model <id> …] [--ref-url <url> …]
|
|
212
|
+
One preview per model (max 4)
|
|
213
|
+
covers select --article <uuid> --url <url> --provider <p> [--model <id>]
|
|
214
|
+
Commit a previewed variant
|
|
215
|
+
covers generate <article-id> [--regenerate] [--stock-only]
|
|
216
|
+
Single-shot mint, default provider
|
|
217
|
+
|
|
201
218
|
Writing kernels (kernels write the draft; they do not research — run research first):
|
|
202
219
|
kernel list List available writing kernels
|
|
203
220
|
kernel run --article <uuid> --kernel <uuid> [--brief <text>|--brief-file <path>]
|
|
@@ -266,6 +283,7 @@ const CLIENT_COMMANDS = {
|
|
|
266
283
|
onboarding: cmdOnboarding,
|
|
267
284
|
insights: cmdInsights,
|
|
268
285
|
research: cmdResearch,
|
|
286
|
+
covers: cmdCovers,
|
|
269
287
|
kernel: cmdKernel,
|
|
270
288
|
"phantom-job": cmdPhantomJob,
|
|
271
289
|
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// `covers` — a post's cover image, including WHICH image model draws it. `models`
|
|
2
|
+
// lists what's connected, `variants` fans a headline out across models and returns a
|
|
3
|
+
// preview per model, `select` commits the one you want, and `generate` is the
|
|
4
|
+
// single-shot headless mint (collection's template + the server's default provider).
|
|
5
|
+
// Each variant costs one image generation, so the fan-out is capped at 4 models.
|
|
6
|
+
|
|
7
|
+
import { CliError } from "../client.mjs";
|
|
8
|
+
import { flagStr, flagBool, flagList, requireFlag, requirePositional, printResult, compact, ok } from "./shared.mjs";
|
|
9
|
+
|
|
10
|
+
export async function cmdCovers(ctx) {
|
|
11
|
+
const sub = ctx.positionals[0];
|
|
12
|
+
const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
13
|
+
switch (sub) {
|
|
14
|
+
case "models":
|
|
15
|
+
return coverModels(rest);
|
|
16
|
+
case "variants":
|
|
17
|
+
case "compare":
|
|
18
|
+
return coverVariants(rest);
|
|
19
|
+
case "select":
|
|
20
|
+
case "pick":
|
|
21
|
+
return coverSelect(rest);
|
|
22
|
+
case "generate":
|
|
23
|
+
return coverGenerate(rest);
|
|
24
|
+
default:
|
|
25
|
+
throw new CliError(
|
|
26
|
+
`Unknown covers subcommand: ${sub ?? "(none)"}. Try: models, variants, select, generate`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function coverModels(ctx) {
|
|
32
|
+
const { client, flags, io } = ctx;
|
|
33
|
+
const result = await client.callTool("list_image_models", {});
|
|
34
|
+
printResult(io, flags, result);
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function coverVariants(ctx) {
|
|
39
|
+
const { client, flags, io } = ctx;
|
|
40
|
+
const article = requireFlag(flags, "article");
|
|
41
|
+
const modelIds = flagList(flags.model);
|
|
42
|
+
const refs = flagList(flags["ref-url"]);
|
|
43
|
+
const args = compact({
|
|
44
|
+
article_id: article,
|
|
45
|
+
model_ids: modelIds.length ? modelIds : undefined,
|
|
46
|
+
reference_image_urls: refs.length ? refs : undefined,
|
|
47
|
+
});
|
|
48
|
+
const result = await client.callTool("generate_cover_variants", args);
|
|
49
|
+
const first = (result?.variants ?? []).find((v) => v.url);
|
|
50
|
+
ok(
|
|
51
|
+
ctx,
|
|
52
|
+
first
|
|
53
|
+
? `Generated ${result.variants.length} variant(s). Commit one with: covers select --article ${article} --url <url> --provider <provider>`
|
|
54
|
+
: "No variant produced an image — see the per-model errors below."
|
|
55
|
+
);
|
|
56
|
+
printResult(io, flags, result);
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function coverSelect(ctx) {
|
|
61
|
+
const { client, flags, io } = ctx;
|
|
62
|
+
const article = requireFlag(flags, "article");
|
|
63
|
+
const args = compact({
|
|
64
|
+
article_id: article,
|
|
65
|
+
image_url: requireFlag(flags, "url"),
|
|
66
|
+
provider: requireFlag(flags, "provider"),
|
|
67
|
+
model: flagStr(flags.model),
|
|
68
|
+
});
|
|
69
|
+
const result = await client.callTool("select_cover_variant", args);
|
|
70
|
+
ok(ctx, "Cover set on the post.");
|
|
71
|
+
printResult(io, flags, result);
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function coverGenerate(ctx) {
|
|
76
|
+
const { client, positionals, flags, io } = ctx;
|
|
77
|
+
// `covers generate <article-id>` reads naturally, but --article keeps it
|
|
78
|
+
// consistent with every other post-scoped command; accept both.
|
|
79
|
+
const article = flagStr(flags.article) ?? requirePositional(positionals, 0, "article-id");
|
|
80
|
+
const args = compact({
|
|
81
|
+
article_id: article,
|
|
82
|
+
regenerate: flagBool(flags.regenerate) ? true : undefined,
|
|
83
|
+
stock_only: flagBool(flags["stock-only"]) ? true : undefined,
|
|
84
|
+
});
|
|
85
|
+
const result = await client.callTool("generate_article_cover", args);
|
|
86
|
+
ok(ctx, result?.generated ? "Cover generated and attached." : `No cover generated: ${result?.reason ?? "unknown"}`);
|
|
87
|
+
printResult(io, flags, result);
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
package/lib/commands.mjs
CHANGED
|
@@ -15,6 +15,7 @@ export * from "./commands/flows.mjs";
|
|
|
15
15
|
export * from "./commands/connectors.mjs";
|
|
16
16
|
export * from "./commands/strategy.mjs";
|
|
17
17
|
export * from "./commands/insights.mjs";
|
|
18
|
+
export * from "./commands/covers.mjs";
|
|
18
19
|
export * from "./commands/research.mjs";
|
|
19
20
|
export * from "./commands/kernel.mjs";
|
|
20
21
|
export * from "./commands/phantom-job.mjs";
|