@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 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.0";
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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",