@letterstory/cli 0.23.0 → 0.25.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 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.23.0";
47
+ export const VERSION = "0.25.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.
@@ -316,14 +316,20 @@ Writing kernels (kernels write the draft; they do not research — run research
316
316
  to a kernel and an in-loop shredder
317
317
  kernel endpoint delete <endpoint-id> --yes Remove one
318
318
  kernel write <endpoint-id> (--body <text>|--file <path>|--file -)
319
- export const VERSION = "0.23.0";
320
319
  WRITE a new article from a headline,
321
320
  outline, notes or a finished post.
322
321
  Follows the run unless --no-wait, and
323
322
  prints the article when it lands.
324
- kernel polish <endpoint-id> (--body <html>|--file <path>) [--format auto|html]
325
- POLISH the document you have: prose
326
- reworded, your own markup kept
323
+ kernel polish <endpoint-id> (--body <html>|--file <path>|--file -) [--no-wait]
324
+ Run the SAME pipeline against a
325
+ document you already have instead of
326
+ writing a new one: prose reworded,
327
+ your own markup kept, the same rules
328
+ and jury as any other run. Follows the
329
+ run unless --no-wait, and costs a run
330
+ exactly like 'kernel write' does —
331
+ there is no separate free, instant
332
+ polish any more.
327
333
  kernel run-status <run-id> Poll a write started with 'kernel write'
328
334
  kernel runs [--endpoint <id>] [--limit <n>] Recent endpoint writes (telemetry)
329
335
  kernel balance Kernel runs left on this org
@@ -1,12 +1,12 @@
1
1
  // `connectors` — export finished articles to external CMSs/doc tools (Webflow,
2
- // Framer, Google Docs, Contentful) and check publish status. Ported from
3
- // phantomstory-cli's connectors.ts.
2
+ // Framer, Google Docs, Contentful, GitHub) and check publish status. Ported
3
+ // from phantomstory-cli's connectors.ts.
4
4
 
5
5
  import { CliError } from "../client.mjs";
6
6
  import { printResult, ok, argsFromFlags } from "./shared.mjs";
7
7
 
8
8
  const CMS_PROVIDERS = ["webflow", "framer", "sanity", "contentful"];
9
- const PUBLISH_CONNECTORS = ["webflow", "framer", "google_docs", "contentful"];
9
+ const PUBLISH_CONNECTORS = ["webflow", "framer", "google_docs", "contentful", "github"];
10
10
 
11
11
  // How every tool this module calls gets its arguments — see `argsFromFlags` in shared.mjs.
12
12
  // `scripts/parity-check.ts` compares these keys against each tool's published input schema.
@@ -145,6 +145,9 @@ async function kernelWrite(ctx) {
145
145
  // it, so the verdict lands on the text that actually ships. Costs a shred as well as a
146
146
  // run. Reachable from every other surface and, until now, not from here.
147
147
  shredder_endpoint_id: flagStr(flags.shredder),
148
+ // How hard that in-loop shred rewrites — 'standard' (default, recommended), 'high',
149
+ // or 'max'. Ignored when no shred runs.
150
+ shred_aggression: flagStr(flags["shred-aggression"]),
148
151
  // The flag is --read-as, which is what shipped; an earlier draft of this branch
149
152
  // called it --brief-mode and never reached anyone.
150
153
  brief_mode: flagStr(flags["read-as"]),
@@ -225,7 +228,18 @@ async function kernelEndpoint(ctx) {
225
228
  throw new CliError(`Unknown kernel endpoint action: ${action ?? "(none)"}. Try: create, delete`);
226
229
  }
227
230
 
228
- /** The OTHER door: reword the document you already have, keeping its own markup. */
231
+ /**
232
+ * The OTHER door: run the same pipeline against a document you already have, instead of
233
+ * writing a new one. Skips generation only — the rules, the in-loop shred and the jury all
234
+ * still run, which is what keeps your own markup (images, embeds, links, tables, anchors,
235
+ * shortcodes) intact through a real pass rather than an unguarded shortcut.
236
+ *
237
+ * ASYNCHRONOUS, same as `write` — this used to be a separate, synchronous, unmetered "polish"
238
+ * that answered in one request. That path was deleted (2026-09-15, letterstory#1783): it
239
+ * worked by skipping the rules and the jury, and that was the actual cost of being instant
240
+ * and free. `document` now costs a run and follows the identical submit-and-poll shape
241
+ * `write` does, so this command does too — `--no-wait` behaves the same way here.
242
+ */
229
243
  async function kernelPolish(ctx) {
230
244
  const { client, positionals, flags, io } = ctx;
231
245
  const endpointId = requirePositional(positionals, 0, "endpoint-id");
@@ -238,10 +252,38 @@ async function kernelPolish(ctx) {
238
252
  document,
239
253
  format: flagStr(flags.format),
240
254
  });
241
- // Synchronous by nature: a polish is one pass over the sentences, not a multi-round write.
242
255
  const result = await client.callTool("run_kernel", args);
243
- printResult(io, flags, result);
244
- return 0;
256
+ const runId = toolField(result, "run_id");
257
+ if (!runId) {
258
+ printResult(io, flags, result);
259
+ return 0;
260
+ }
261
+ if (flags["no-wait"] === true) {
262
+ ok(ctx, `Polish started as ${runId}. Follow it with: letterstory kernel run-status ${runId}`);
263
+ printResult(io, flags, result);
264
+ return 0;
265
+ }
266
+
267
+ ok(ctx, `Polish started as ${runId}. Waiting — this keeps running on the server if you stop.`);
268
+ let lastStatus;
269
+ const { settled, value: polled } = await followJob(
270
+ io,
271
+ async () => {
272
+ const r = await client.callTool("get_kernel_endpoint_run", { run_id: runId });
273
+ lastStatus = toolField(r, "status");
274
+ return { done: !!lastStatus && lastStatus !== "running" && lastStatus !== "queued", value: r };
275
+ },
276
+ { intervalMs: POLL_INTERVAL_MS, ceilingMs: POLL_CEILING_MS }
277
+ );
278
+
279
+ if (!settled) {
280
+ ok(ctx, `Still running after 45 minutes. Follow it with: letterstory kernel run-status ${runId}`);
281
+ printResult(io, flags, polled);
282
+ return 0;
283
+ }
284
+ ok(ctx, `Polish ${lastStatus}.`);
285
+ printResult(io, flags, polled, (v) => (typeof v?.article === "string" ? v.article : JSON.stringify(v, null, 2)));
286
+ return lastStatus === "done" ? 0 : 1;
245
287
  }
246
288
 
247
289
  async function kernelRunStatus(ctx) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",