@letterstory/cli 0.25.0 → 0.25.2
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 +1 -1
- package/lib/commands/autopen.mjs +11 -4
- package/lib/commands/flows.mjs +10 -4
- package/lib/commands/kernel.mjs +139 -46
- package/lib/commands/posts.mjs +9 -2
- package/lib/commands/research.mjs +15 -17
- package/lib/commands/seers.mjs +14 -3
- package/lib/commands/shredder.mjs +28 -20
- package/package.json +1 -1
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.25.
|
|
47
|
+
export const VERSION = "0.25.2";
|
|
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.
|
package/lib/commands/autopen.mjs
CHANGED
|
@@ -11,7 +11,14 @@
|
|
|
11
11
|
// finishes whether or not anyone polls.
|
|
12
12
|
|
|
13
13
|
import { CliError } from "../client.mjs";
|
|
14
|
-
import { flagBool, requireFlag, printResult, ok } from "./shared.mjs";
|
|
14
|
+
import { flagBool, requireFlag, printResult, ok, argsFromFlags } from "./shared.mjs";
|
|
15
|
+
|
|
16
|
+
/** See `argsFromFlags` in shared.mjs; `scripts/parity-check.ts` diffs these keys against the tool. */
|
|
17
|
+
export const ARGS = {
|
|
18
|
+
run_auto_pen: {
|
|
19
|
+
article_id: { flag: "article", required: true },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
15
22
|
|
|
16
23
|
export async function cmdAutoPen(ctx) {
|
|
17
24
|
const sub = ctx.positionals[0];
|
|
@@ -31,9 +38,9 @@ export async function cmdAutoPen(ctx) {
|
|
|
31
38
|
|
|
32
39
|
async function autoPenStart(ctx) {
|
|
33
40
|
const { client, flags, io } = ctx;
|
|
34
|
-
const
|
|
35
|
-
const result = await client.callTool("run_auto_pen",
|
|
36
|
-
ok(ctx, `Auto-Pen started. Check progress with: autopen status --article ${
|
|
41
|
+
const args = argsFromFlags(ARGS.run_auto_pen, flags, ctx.positionals);
|
|
42
|
+
const result = await client.callTool("run_auto_pen", args);
|
|
43
|
+
ok(ctx, `Auto-Pen started. Check progress with: autopen status --article ${args.article_id}`);
|
|
37
44
|
printResult(io, flags, result);
|
|
38
45
|
return 0;
|
|
39
46
|
}
|
package/lib/commands/flows.mjs
CHANGED
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
// flow_run.completed completion webhook. Ported from phantomstory-cli's flows.ts.
|
|
3
3
|
|
|
4
4
|
import { CliError } from "../client.mjs";
|
|
5
|
-
import { flagStr, requirePositional, printResult, compact, ok } from "./shared.mjs";
|
|
5
|
+
import { flagStr, requirePositional, printResult, compact, ok, argsFromFlags } from "./shared.mjs";
|
|
6
|
+
|
|
7
|
+
/** See `argsFromFlags` in shared.mjs; `scripts/parity-check.ts` diffs these keys against the tool. */
|
|
8
|
+
export const ARGS = {
|
|
9
|
+
run_flow: {
|
|
10
|
+
flow_id: { positional: 0, label: "flow-id", required: true },
|
|
11
|
+
article_id: { positional: 1, label: "article-id", required: true },
|
|
12
|
+
},
|
|
13
|
+
};
|
|
6
14
|
|
|
7
15
|
export async function cmdFlows(ctx) {
|
|
8
16
|
const sub = ctx.positionals[0];
|
|
@@ -31,9 +39,7 @@ async function flowsList(ctx) {
|
|
|
31
39
|
|
|
32
40
|
async function flowsRun(ctx) {
|
|
33
41
|
const { client, positionals, flags, io } = ctx;
|
|
34
|
-
const
|
|
35
|
-
const articleId = requirePositional(positionals, 1, "article-id");
|
|
36
|
-
const result = await client.callTool("run_flow", { flow_id: flowId, article_id: articleId });
|
|
42
|
+
const result = await client.callTool("run_flow", argsFromFlags(ARGS.run_flow, flags, positionals));
|
|
37
43
|
ok(ctx, "Flow run started.");
|
|
38
44
|
printResult(io, flags, result);
|
|
39
45
|
return 0;
|
package/lib/commands/kernel.mjs
CHANGED
|
@@ -15,11 +15,10 @@
|
|
|
15
15
|
// Neither door blocks: a write runs for minutes, so `write` submits and then follows the run
|
|
16
16
|
// unless you tell it not to.
|
|
17
17
|
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
18
19
|
import { CliError } from "../client.mjs";
|
|
19
20
|
import {
|
|
20
21
|
flagStr,
|
|
21
|
-
flagBool,
|
|
22
|
-
requireFlag,
|
|
23
22
|
requirePositional,
|
|
24
23
|
printResult,
|
|
25
24
|
compact,
|
|
@@ -30,6 +29,38 @@ import {
|
|
|
30
29
|
argsFromFlags,
|
|
31
30
|
} from "./shared.mjs";
|
|
32
31
|
|
|
32
|
+
/** `--<flag> <path>` → the file's parsed JSON, or undefined when the flag is not set. */
|
|
33
|
+
function readJsonFileFlag(flags, flag) {
|
|
34
|
+
const path = flagStr(flags[flag]);
|
|
35
|
+
if (path === undefined) return undefined;
|
|
36
|
+
let text;
|
|
37
|
+
try {
|
|
38
|
+
text = readFileSync(path, "utf8");
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new CliError(
|
|
41
|
+
`Could not read --${flag} ${path}: ${error instanceof Error ? error.message : String(error)}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(text);
|
|
46
|
+
} catch {
|
|
47
|
+
throw new CliError(`--${flag} ${path} is not valid JSON.`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** `--<flag> <path>` → the file's text, or undefined when the flag is not set. */
|
|
52
|
+
function readTextFileFlag(flags, flag) {
|
|
53
|
+
const path = flagStr(flags[flag]);
|
|
54
|
+
if (path === undefined) return undefined;
|
|
55
|
+
try {
|
|
56
|
+
return readFileSync(path, "utf8");
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw new CliError(
|
|
59
|
+
`Could not read --${flag} ${path}: ${error instanceof Error ? error.message : String(error)}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
33
64
|
/**
|
|
34
65
|
* How the commands ADDED to this file source their arguments — see `argsFromFlags`
|
|
35
66
|
* in shared.mjs, and `scripts/parity-check.ts`, which compares these keys against
|
|
@@ -39,6 +70,72 @@ import {
|
|
|
39
70
|
* frozen UNMAPPED_TOOLS list. New commands declare; that list only shrinks.
|
|
40
71
|
*/
|
|
41
72
|
export const ARGS = {
|
|
73
|
+
// The article-bound door: submit a kernel run INTO an article you already have.
|
|
74
|
+
submit_kernel_run: {
|
|
75
|
+
article_id: { flag: "article", required: true },
|
|
76
|
+
kernel_id: { flag: "kernel", required: true },
|
|
77
|
+
// A brief overrides the article's own content as the instruction; --brief-file reads
|
|
78
|
+
// it from a path. Omit both to write from the article as it stands.
|
|
79
|
+
brief: { value: (flags) => flagStr(flags.brief) ?? readBodyInput({ file: flagStr(flags["brief-file"]) }) },
|
|
80
|
+
},
|
|
81
|
+
// The standalone door. Two subcommands share this one tool: `write` and `polish` both
|
|
82
|
+
// send `input`, `polish` forcing content_kind to "document" — the map is one, so a field
|
|
83
|
+
// the tool gains is a field BOTH subcommands must learn (or knowingly gap) — that is the
|
|
84
|
+
// ratchet.
|
|
85
|
+
run_kernel: {
|
|
86
|
+
endpoint_id: { positional: 0, label: "endpoint-id", required: true },
|
|
87
|
+
input: { value: (flags) => readBodyInput(flags) },
|
|
88
|
+
kernel_id: "kernel",
|
|
89
|
+
target_word_count: { flag: "words", type: "num" },
|
|
90
|
+
label: "label",
|
|
91
|
+
// The flag is --read-as, which is what shipped; an earlier draft called it --brief-mode.
|
|
92
|
+
content_kind: {
|
|
93
|
+
flag: "read-as",
|
|
94
|
+
enum: ["auto", "instruction", "document", "article", "outline", "research", "notes"],
|
|
95
|
+
},
|
|
96
|
+
// The two products, in one run: every round is shredded before the kernel's jury sees
|
|
97
|
+
// it, so the verdict lands on the text that actually ships. Costs a shred as well as a
|
|
98
|
+
// run.
|
|
99
|
+
shredder_endpoint_id: "shredder",
|
|
100
|
+
shred_aggression: { flag: "shred-aggression", enum: ["standard", "high", "max"] },
|
|
101
|
+
voice: {
|
|
102
|
+
object: {
|
|
103
|
+
person: { flag: "person", enum: ["third", "first_plural", "first_singular"] },
|
|
104
|
+
allow_experience_claims: { flag: "experience-claims", type: "bool" },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
// The mechanism's full contract, declared on the door (2026-09-17, Kernels wave step 1).
|
|
108
|
+
// A shape and a typed brief are files, not flags — `--skeleton-file shape.json` is the
|
|
109
|
+
// field a template resolves to, sent by a caller who has their own.
|
|
110
|
+
skeleton: { value: (flags) => readJsonFileFlag(flags, "skeleton-file") },
|
|
111
|
+
brief_json: { value: (flags) => readJsonFileFlag(flags, "brief-json-file") },
|
|
112
|
+
max_iterations: { flag: "max-iterations", type: "num" },
|
|
113
|
+
kernel_version: { flag: "kernel-version", type: "num" },
|
|
114
|
+
// In-loop shred tuning. Only sent when a flag is set; ignored when no shred runs.
|
|
115
|
+
shred: {
|
|
116
|
+
object: {
|
|
117
|
+
tier: { flag: "shred-tier", enum: ["cheap", "mid", "strong", "frontier"] },
|
|
118
|
+
families: { flag: "shred-family", type: "list" },
|
|
119
|
+
revise_shredded: { flag: "shred-revise", type: "bool" },
|
|
120
|
+
revision_model: "shred-revision-model",
|
|
121
|
+
skip_voice_bearing: { flag: "shred-skip-voice-bearing", type: "bool" },
|
|
122
|
+
judge: { flag: "shred-judge", type: "bool" },
|
|
123
|
+
attempts_per_unit: { flag: "shred-attempts", type: "num" },
|
|
124
|
+
max_changeable_retention: { flag: "shred-max-retention", type: "num" },
|
|
125
|
+
turnover_retries: { flag: "shred-turnover-retries", type: "num" },
|
|
126
|
+
turnover_avoid_list: { flag: "shred-turnover-avoid-list", type: "bool" },
|
|
127
|
+
evidence: { value: (flags) => readTextFileFlag(flags, "shred-evidence-file") },
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
// Named-entity treatment (2026-09-17, renamed 2026-09-18): a list, not a scalar flag,
|
|
131
|
+
// so it's a file like skeleton/brief_json — `--entities-file entities.json` of
|
|
132
|
+
// `[{"name":"...","treatment":"never"|"mention_once"|"mention_often"|"lead_company",
|
|
133
|
+
// "linkable":false}, ...]` (linkable optional, default true).
|
|
134
|
+
entities: { value: (flags) => readJsonFileFlag(flags, "entities-file") },
|
|
135
|
+
// content_kind "document" only: hold the document's own shape through the edit. Default
|
|
136
|
+
// true; pass --no-preserve-shape to let the kernel restructure freely.
|
|
137
|
+
preserve_shape: { flag: "preserve-shape", type: "bool" },
|
|
138
|
+
},
|
|
42
139
|
create_kernel_endpoint: {
|
|
43
140
|
name: { flag: "name", required: true },
|
|
44
141
|
// Both optional bindings the tool has always accepted at create: which kernel
|
|
@@ -51,6 +148,15 @@ export const ARGS = {
|
|
|
51
148
|
delete_kernel_endpoint: {
|
|
52
149
|
endpoint_id: { positional: 1, label: "endpoint-id", required: true },
|
|
53
150
|
},
|
|
151
|
+
// The endpoint's configuration as ONE file: `kernel endpoint config <id>` prints it,
|
|
152
|
+
// `kernel endpoint config <id> --file cfg.json` replaces it with the file's document.
|
|
153
|
+
get_kernel_endpoint_config: {
|
|
154
|
+
endpoint_id: { positional: 1, label: "endpoint-id", required: true },
|
|
155
|
+
},
|
|
156
|
+
set_kernel_endpoint_config: {
|
|
157
|
+
endpoint_id: { positional: 1, label: "endpoint-id", required: true },
|
|
158
|
+
config: { value: (flags) => readJsonFileFlag(flags, "file") },
|
|
159
|
+
},
|
|
54
160
|
};
|
|
55
161
|
|
|
56
162
|
export async function cmdKernel(ctx) {
|
|
@@ -94,10 +200,7 @@ async function kernelList(ctx) {
|
|
|
94
200
|
|
|
95
201
|
async function kernelRun(ctx) {
|
|
96
202
|
const { client, flags, io } = ctx;
|
|
97
|
-
const
|
|
98
|
-
const kernelId = requireFlag(flags, "kernel");
|
|
99
|
-
const brief = flagStr(flags.brief) ?? readBodyInput({ file: flagStr(flags["brief-file"]) });
|
|
100
|
-
const args = compact({ article_id: article, kernel_id: kernelId, brief });
|
|
203
|
+
const args = argsFromFlags(ARGS.submit_kernel_run, flags, ctx.positionals);
|
|
101
204
|
const result = await client.callTool("submit_kernel_run", args);
|
|
102
205
|
ok(ctx, `Kernel run started. Check progress with: kernel status ${result?.kernel_job_id ?? "<kernel-job-id>"}`);
|
|
103
206
|
printResult(io, flags, result);
|
|
@@ -130,33 +233,10 @@ async function kernelEndpoints(ctx) {
|
|
|
130
233
|
|
|
131
234
|
async function kernelWrite(ctx) {
|
|
132
235
|
const { client, positionals, flags, io } = ctx;
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
if (brief === undefined) {
|
|
236
|
+
const args = argsFromFlags(ARGS.run_kernel, flags, positionals);
|
|
237
|
+
if (args.input === undefined) {
|
|
136
238
|
throw new CliError("Provide the brief with --body <text>, --file <path>, or --file - (stdin).");
|
|
137
239
|
}
|
|
138
|
-
const args = compact({
|
|
139
|
-
endpoint_id: endpointId,
|
|
140
|
-
brief,
|
|
141
|
-
kernel_id: flagStr(flags.kernel),
|
|
142
|
-
target_word_count: flags.words === undefined ? undefined : Number(flags.words),
|
|
143
|
-
content_type: flagStr(flags["content-type"]),
|
|
144
|
-
// The two products, in one run: every round is shredded before the kernel's jury sees
|
|
145
|
-
// it, so the verdict lands on the text that actually ships. Costs a shred as well as a
|
|
146
|
-
// run. Reachable from every other surface and, until now, not from here.
|
|
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"]),
|
|
151
|
-
// The flag is --read-as, which is what shipped; an earlier draft of this branch
|
|
152
|
-
// called it --brief-mode and never reached anyone.
|
|
153
|
-
brief_mode: flagStr(flags["read-as"]),
|
|
154
|
-
// The other three `run_kernel` has always taken and the CLI still did not expose,
|
|
155
|
-
// so the only way to reach them was hand-written JSON through `mcp call`.
|
|
156
|
-
person: flagStr(flags.person),
|
|
157
|
-
author_name: flagStr(flags.author),
|
|
158
|
-
allow_experience_claims: flagBool(flags["experience-claims"]),
|
|
159
|
-
});
|
|
160
240
|
const result = await client.callTool("run_kernel", args);
|
|
161
241
|
const runId = toolField(result, "run_id");
|
|
162
242
|
if (!runId) {
|
|
@@ -225,33 +305,46 @@ async function kernelEndpoint(ctx) {
|
|
|
225
305
|
printResult(io, flags, result);
|
|
226
306
|
return 0;
|
|
227
307
|
}
|
|
228
|
-
|
|
308
|
+
if (action === "config") {
|
|
309
|
+
const id = requirePositional(positionals, 1, "endpoint-id");
|
|
310
|
+
if (flagStr(flags.file) === undefined) {
|
|
311
|
+
const result = await client.callTool(
|
|
312
|
+
"get_kernel_endpoint_config",
|
|
313
|
+
argsFromFlags(ARGS.get_kernel_endpoint_config, flags, positionals)
|
|
314
|
+
);
|
|
315
|
+
// The document itself, so `> cfg.json` round-trips into `--file cfg.json`.
|
|
316
|
+
printResult(io, flags, result, (r) => JSON.stringify(r?.config ?? r, null, 2));
|
|
317
|
+
return 0;
|
|
318
|
+
}
|
|
319
|
+
const args = argsFromFlags(ARGS.set_kernel_endpoint_config, flags, positionals);
|
|
320
|
+
const result = await client.callTool("set_kernel_endpoint_config", args);
|
|
321
|
+
ok(ctx, `Replaced the configuration of kernel endpoint ${id}.`);
|
|
322
|
+
printResult(io, flags, result);
|
|
323
|
+
return 0;
|
|
324
|
+
}
|
|
325
|
+
throw new CliError(`Unknown kernel endpoint action: ${action ?? "(none)"}. Try: create, delete, config`);
|
|
229
326
|
}
|
|
230
327
|
|
|
231
328
|
/**
|
|
232
329
|
* The OTHER door: run the same pipeline against a document you already have, instead of
|
|
233
|
-
* writing a new one.
|
|
234
|
-
*
|
|
235
|
-
*
|
|
330
|
+
* writing a new one. Decomposed into a brief and GENERATED FRESH (content_kind: "document",
|
|
331
|
+
* forced below) — not edited in place — with your own headings and embedded media (images,
|
|
332
|
+
* tables) extracted first and spliced back into the generated draft afterward, so they
|
|
333
|
+
* survive a pass that would otherwise be text-only and lose them.
|
|
236
334
|
*
|
|
237
335
|
* ASYNCHRONOUS, same as `write` — this used to be a separate, synchronous, unmetered "polish"
|
|
238
|
-
* that answered in one request
|
|
239
|
-
*
|
|
240
|
-
*
|
|
336
|
+
* that answered in one request, and before that an EARLIER `document` mode that skipped
|
|
337
|
+
* generation to hold shape "in place." Neither exists any more (2026-09-15, letterstory#1783;
|
|
338
|
+
* 2026-09-17). `document` now costs a run and follows the identical submit-and-poll shape
|
|
241
339
|
* `write` does, so this command does too — `--no-wait` behaves the same way here.
|
|
242
340
|
*/
|
|
243
341
|
async function kernelPolish(ctx) {
|
|
244
342
|
const { client, positionals, flags, io } = ctx;
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
if (document === undefined) {
|
|
343
|
+
const args = argsFromFlags(ARGS.run_kernel, flags, positionals);
|
|
344
|
+
if (args.input === undefined) {
|
|
248
345
|
throw new CliError("Provide the document with --body <text>, --file <path>, or --file - (stdin).");
|
|
249
346
|
}
|
|
250
|
-
|
|
251
|
-
endpoint_id: endpointId,
|
|
252
|
-
document,
|
|
253
|
-
format: flagStr(flags.format),
|
|
254
|
-
});
|
|
347
|
+
args.content_kind = args.content_kind ?? "document";
|
|
255
348
|
const result = await client.callTool("run_kernel", args);
|
|
256
349
|
const runId = toolField(result, "run_id");
|
|
257
350
|
if (!runId) {
|
package/lib/commands/posts.mjs
CHANGED
|
@@ -12,8 +12,16 @@ import {
|
|
|
12
12
|
compact,
|
|
13
13
|
ok,
|
|
14
14
|
readBodyInput,
|
|
15
|
+
argsFromFlags,
|
|
15
16
|
} from "./shared.mjs";
|
|
16
17
|
|
|
18
|
+
/** See `argsFromFlags` in shared.mjs; `scripts/parity-check.ts` diffs these keys against the tool. */
|
|
19
|
+
export const ARGS = {
|
|
20
|
+
publish_article: {
|
|
21
|
+
article_id: { positional: 0, label: "article-id", required: true },
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
17
25
|
export async function cmdPosts(ctx) {
|
|
18
26
|
const sub = ctx.positionals[0];
|
|
19
27
|
const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
@@ -66,8 +74,7 @@ async function postsNew(ctx) {
|
|
|
66
74
|
|
|
67
75
|
async function postsPublish(ctx) {
|
|
68
76
|
const { client, positionals, flags, io } = ctx;
|
|
69
|
-
const
|
|
70
|
-
const result = await client.callTool("publish_article", { article_id: id });
|
|
77
|
+
const result = await client.callTool("publish_article", argsFromFlags(ARGS.publish_article, flags, positionals));
|
|
71
78
|
ok(ctx, "Published.");
|
|
72
79
|
printResult(io, flags, result);
|
|
73
80
|
return 0;
|
|
@@ -4,7 +4,19 @@
|
|
|
4
4
|
// kernels do NOT research — `research start` then `kernel run` is the full pipeline.
|
|
5
5
|
|
|
6
6
|
import { CliError } from "../client.mjs";
|
|
7
|
-
import { flagStr, flagNum, flagBool,
|
|
7
|
+
import { flagStr, flagNum, flagBool, requireFlag, printResult, compact, ok, argsFromFlags } from "./shared.mjs";
|
|
8
|
+
|
|
9
|
+
/** See `argsFromFlags` in shared.mjs; `scripts/parity-check.ts` diffs these keys against the tool. */
|
|
10
|
+
export const ARGS = {
|
|
11
|
+
start_research: {
|
|
12
|
+
article_id: { flag: "article", required: true },
|
|
13
|
+
topic: "topic",
|
|
14
|
+
source_urls: { flag: "url", type: "list" },
|
|
15
|
+
must_include: { flag: "must-include", type: "list" },
|
|
16
|
+
depth: { flag: "depth", enum: ["barebones", "fleshed_out"] },
|
|
17
|
+
direction_gate: { flag: "gate", type: "bool" },
|
|
18
|
+
},
|
|
19
|
+
};
|
|
8
20
|
|
|
9
21
|
export async function cmdResearch(ctx) {
|
|
10
22
|
const sub = ctx.positionals[0];
|
|
@@ -28,23 +40,9 @@ export async function cmdResearch(ctx) {
|
|
|
28
40
|
|
|
29
41
|
async function researchStart(ctx) {
|
|
30
42
|
const { client, flags, io } = ctx;
|
|
31
|
-
const
|
|
32
|
-
const depth = flagStr(flags.depth);
|
|
33
|
-
if (depth !== undefined && depth !== "barebones" && depth !== "fleshed_out") {
|
|
34
|
-
throw new CliError(`--depth must be barebones or fleshed_out, got "${depth}"`);
|
|
35
|
-
}
|
|
36
|
-
const sourceUrls = flagList(flags.url);
|
|
37
|
-
const mustInclude = flagList(flags["must-include"]);
|
|
38
|
-
const args = compact({
|
|
39
|
-
article_id: article,
|
|
40
|
-
topic: flagStr(flags.topic),
|
|
41
|
-
source_urls: sourceUrls.length ? sourceUrls : undefined,
|
|
42
|
-
must_include: mustInclude.length ? mustInclude : undefined,
|
|
43
|
-
depth,
|
|
44
|
-
direction_gate: flagBool(flags.gate) ? true : undefined,
|
|
45
|
-
});
|
|
43
|
+
const args = argsFromFlags(ARGS.start_research, flags, ctx.positionals);
|
|
46
44
|
const result = await client.callTool("start_research", args);
|
|
47
|
-
ok(ctx, `Research started. Check progress with: research status --article ${
|
|
45
|
+
ok(ctx, `Research started. Check progress with: research status --article ${args.article_id}`);
|
|
48
46
|
printResult(io, flags, result);
|
|
49
47
|
return 0;
|
|
50
48
|
}
|
package/lib/commands/seers.mjs
CHANGED
|
@@ -12,7 +12,18 @@
|
|
|
12
12
|
|
|
13
13
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
14
14
|
import { CliError } from "../client.mjs";
|
|
15
|
-
import { flagStr, flagNum, flagBool, requirePositional, printResult, compact, ok } from "./shared.mjs";
|
|
15
|
+
import { flagStr, flagNum, flagBool, requirePositional, printResult, compact, ok, argsFromFlags } from "./shared.mjs";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* How the ratcheted commands in this file source their tool inputs — see `argsFromFlags` in
|
|
19
|
+
* shared.mjs and `scripts/parity-check.ts`. The rest of the module predates the field-level
|
|
20
|
+
* ratchet and stays in UNMAPPED_TOOLS; that list only shrinks.
|
|
21
|
+
*/
|
|
22
|
+
export const ARGS = {
|
|
23
|
+
run_seer: {
|
|
24
|
+
seer_id: { positional: 0, label: "seer-id", required: true },
|
|
25
|
+
},
|
|
26
|
+
};
|
|
16
27
|
|
|
17
28
|
export async function cmdSeers(ctx) {
|
|
18
29
|
const sub = ctx.positionals[0];
|
|
@@ -177,9 +188,9 @@ async function seersDelete(ctx) {
|
|
|
177
188
|
|
|
178
189
|
async function seersRun(ctx) {
|
|
179
190
|
const { client, positionals, flags, io } = ctx;
|
|
180
|
-
const
|
|
191
|
+
const args = argsFromFlags(ARGS.run_seer, flags, positionals);
|
|
181
192
|
ok(ctx, "Polling now — web-scan providers can take a few minutes…");
|
|
182
|
-
const result = await client.callTool("run_seer",
|
|
193
|
+
const result = await client.callTool("run_seer", args);
|
|
183
194
|
printResult(
|
|
184
195
|
io,
|
|
185
196
|
flags,
|
|
@@ -15,8 +15,32 @@ import {
|
|
|
15
15
|
ok,
|
|
16
16
|
toolField,
|
|
17
17
|
followJob,
|
|
18
|
+
argsFromFlags,
|
|
18
19
|
} from "./shared.mjs";
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* How `shredder run` sources `run_shred`'s inputs — see `argsFromFlags` in shared.mjs and
|
|
23
|
+
* `scripts/parity-check.ts`, which diffs these keys against the tool's published schema.
|
|
24
|
+
* The rest of this module predates the ratchet and stays in UNMAPPED_TOOLS.
|
|
25
|
+
*/
|
|
26
|
+
export const ARGS = {
|
|
27
|
+
run_shred: {
|
|
28
|
+
endpoint_id: { positional: 0, label: "endpoint-id", required: true },
|
|
29
|
+
text: { value: (flags) => readBodyInput(flags) },
|
|
30
|
+
coverage: { flag: "coverage", type: "num" },
|
|
31
|
+
attempts_per_unit: { flag: "attempts", type: "num" },
|
|
32
|
+
// What the text IS. Without this an HTML file could only go through the `auto`
|
|
33
|
+
// heuristic, which misreads a markdown post carrying an embed.
|
|
34
|
+
format: { flag: "format", enum: ["auto", "html", "markdown"] },
|
|
35
|
+
// How hard to rewrite. Validated service-side; sent only when the caller asked.
|
|
36
|
+
aggression: { flag: "aggression", enum: ["standard", "high", "max"] },
|
|
37
|
+
// Hold every rewrite to the live writing rules. A trade, not a free win.
|
|
38
|
+
apply_rules: { flag: "apply-rules", type: "bool" },
|
|
39
|
+
// ASYNCHRONOUS BY DEFAULT — `--sync` is the opt-out (see shredderRun for why).
|
|
40
|
+
async: { value: (flags) => (flags.sync === true ? undefined : true) },
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
20
44
|
export async function cmdShredder(ctx) {
|
|
21
45
|
const sub = ctx.positionals[0];
|
|
22
46
|
const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
@@ -87,11 +111,6 @@ const POLL_CEILING_MS = 30 * 60 * 1000;
|
|
|
87
111
|
|
|
88
112
|
async function shredderRun(ctx) {
|
|
89
113
|
const { client, positionals, flags, io } = ctx;
|
|
90
|
-
const id = requirePositional(positionals, 0, "endpoint-id");
|
|
91
|
-
const text = readBodyInput(flags);
|
|
92
|
-
if (text === undefined) {
|
|
93
|
-
throw new CliError("Provide the prose to shred with --body <text>, --file <path>, or --file - (stdin).");
|
|
94
|
-
}
|
|
95
114
|
// ASYNCHRONOUS BY DEFAULT, and `--sync` is the opt-out rather than the other way round.
|
|
96
115
|
//
|
|
97
116
|
// A shred has no workable synchronous ceiling — the identical article took 203s, 541s and
|
|
@@ -100,21 +119,10 @@ async function shredderRun(ctx) {
|
|
|
100
119
|
// answer, so the caller gets no text, no error, and no record. Defaulting the other way
|
|
101
120
|
// made the common case the broken one.
|
|
102
121
|
const sync = flags.sync === true;
|
|
103
|
-
const args =
|
|
104
|
-
|
|
105
|
-
text
|
|
106
|
-
|
|
107
|
-
attempts_per_unit: flagNum(flags.attempts),
|
|
108
|
-
// What the text IS. Without this an HTML file could only go through the
|
|
109
|
-
// `auto` heuristic, which misreads a markdown post carrying an embed.
|
|
110
|
-
...(flags.format ? { format: String(flags.format) } : {}),
|
|
111
|
-
// How hard to rewrite. Validated service-side; sent only when the caller asked.
|
|
112
|
-
...(flags.aggression ? { aggression: String(flags.aggression) } : {}),
|
|
113
|
-
// Hold every rewrite to the live writing rules. A trade, not a free win: refused rewrites
|
|
114
|
-
// cost provider attempts and a sentence with no clean rewrite keeps its original.
|
|
115
|
-
...(flags["apply-rules"] === true ? { apply_rules: true } : {}),
|
|
116
|
-
...(sync ? {} : { async: true }),
|
|
117
|
-
});
|
|
122
|
+
const args = argsFromFlags(ARGS.run_shred, flags, positionals);
|
|
123
|
+
if (args.text === undefined) {
|
|
124
|
+
throw new CliError("Provide the prose to shred with --body <text>, --file <path>, or --file - (stdin).");
|
|
125
|
+
}
|
|
118
126
|
const result = await client.callTool("run_shred", args);
|
|
119
127
|
if (sync) {
|
|
120
128
|
ok(ctx, "Shred complete.");
|