@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/lib/commands/kernel.mjs
CHANGED
|
@@ -16,7 +16,42 @@
|
|
|
16
16
|
// unless you tell it not to.
|
|
17
17
|
|
|
18
18
|
import { CliError } from "../client.mjs";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
flagStr,
|
|
21
|
+
flagBool,
|
|
22
|
+
requireFlag,
|
|
23
|
+
requirePositional,
|
|
24
|
+
printResult,
|
|
25
|
+
compact,
|
|
26
|
+
ok,
|
|
27
|
+
readBodyInput,
|
|
28
|
+
toolField,
|
|
29
|
+
followJob,
|
|
30
|
+
argsFromFlags,
|
|
31
|
+
} from "./shared.mjs";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* How the commands ADDED to this file source their arguments — see `argsFromFlags`
|
|
35
|
+
* in shared.mjs, and `scripts/parity-check.ts`, which compares these keys against
|
|
36
|
+
* each tool's published input schema.
|
|
37
|
+
*
|
|
38
|
+
* The rest of this module predates the field-level ratchet and is covered by the
|
|
39
|
+
* frozen UNMAPPED_TOOLS list. New commands declare; that list only shrinks.
|
|
40
|
+
*/
|
|
41
|
+
export const ARGS = {
|
|
42
|
+
create_kernel_endpoint: {
|
|
43
|
+
name: { flag: "name", required: true },
|
|
44
|
+
// Both optional bindings the tool has always accepted at create: which kernel
|
|
45
|
+
// writes, and which shredder (if any) runs inside the loop. Exposed rather than
|
|
46
|
+
// left as a known gap — an endpoint you cannot bind at create is one you have to
|
|
47
|
+
// go and fix in the app.
|
|
48
|
+
kernel_id: "kernel",
|
|
49
|
+
shredder_endpoint_id: "shredder",
|
|
50
|
+
},
|
|
51
|
+
delete_kernel_endpoint: {
|
|
52
|
+
endpoint_id: { positional: 1, label: "endpoint-id", required: true },
|
|
53
|
+
},
|
|
54
|
+
};
|
|
20
55
|
|
|
21
56
|
export async function cmdKernel(ctx) {
|
|
22
57
|
const sub = ctx.positionals[0];
|
|
@@ -31,6 +66,8 @@ export async function cmdKernel(ctx) {
|
|
|
31
66
|
return kernelStatus(rest);
|
|
32
67
|
case "endpoints":
|
|
33
68
|
return kernelEndpoints(rest);
|
|
69
|
+
case "endpoint":
|
|
70
|
+
return kernelEndpoint(rest);
|
|
34
71
|
case "write":
|
|
35
72
|
return kernelWrite(rest);
|
|
36
73
|
case "polish":
|
|
@@ -43,7 +80,7 @@ export async function cmdKernel(ctx) {
|
|
|
43
80
|
return kernelBalance(rest);
|
|
44
81
|
default:
|
|
45
82
|
throw new CliError(
|
|
46
|
-
`Unknown kernel subcommand: ${sub ?? "(none)"}. Try: list, endpoints, write, polish, run-status, runs, balance, run, status`
|
|
83
|
+
`Unknown kernel subcommand: ${sub ?? "(none)"}. Try: list, endpoints, endpoint, write, polish, run-status, runs, balance, run, status`
|
|
47
84
|
);
|
|
48
85
|
}
|
|
49
86
|
}
|
|
@@ -84,19 +121,6 @@ const POLL_INTERVAL_MS = 20_000;
|
|
|
84
121
|
/** Stop following after this; the run keeps going and `run-status` picks it up. */
|
|
85
122
|
const POLL_CEILING_MS = 45 * 60 * 1000;
|
|
86
123
|
|
|
87
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
88
|
-
|
|
89
|
-
/** The tool result is a text payload holding JSON; pull one field out, leniently. */
|
|
90
|
-
function readToolField(result, field) {
|
|
91
|
-
const raw = result?.content?.[0]?.text;
|
|
92
|
-
if (typeof raw !== "string") return undefined;
|
|
93
|
-
try {
|
|
94
|
-
return JSON.parse(raw)?.[field];
|
|
95
|
-
} catch {
|
|
96
|
-
return undefined;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
124
|
async function kernelEndpoints(ctx) {
|
|
101
125
|
const { client, flags, io } = ctx;
|
|
102
126
|
const result = await client.callTool("list_kernel_endpoints", {});
|
|
@@ -121,10 +145,17 @@ async function kernelWrite(ctx) {
|
|
|
121
145
|
// it, so the verdict lands on the text that actually ships. Costs a shred as well as a
|
|
122
146
|
// run. Reachable from every other surface and, until now, not from here.
|
|
123
147
|
shredder_endpoint_id: flagStr(flags.shredder),
|
|
148
|
+
// The flag is --read-as, which is what shipped; an earlier draft of this branch
|
|
149
|
+
// called it --brief-mode and never reached anyone.
|
|
124
150
|
brief_mode: flagStr(flags["read-as"]),
|
|
151
|
+
// The other three `run_kernel` has always taken and the CLI still did not expose,
|
|
152
|
+
// so the only way to reach them was hand-written JSON through `mcp call`.
|
|
153
|
+
person: flagStr(flags.person),
|
|
154
|
+
author_name: flagStr(flags.author),
|
|
155
|
+
allow_experience_claims: flagBool(flags["experience-claims"]),
|
|
125
156
|
});
|
|
126
157
|
const result = await client.callTool("run_kernel", args);
|
|
127
|
-
const runId =
|
|
158
|
+
const runId = toolField(result, "run_id");
|
|
128
159
|
if (!runId) {
|
|
129
160
|
printResult(io, flags, result);
|
|
130
161
|
return 0;
|
|
@@ -136,22 +167,62 @@ async function kernelWrite(ctx) {
|
|
|
136
167
|
}
|
|
137
168
|
|
|
138
169
|
ok(ctx, `Write started as ${runId}. Waiting — this keeps running on the server if you stop.`);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
170
|
+
let lastStatus;
|
|
171
|
+
const { settled, value: polled } = await followJob(
|
|
172
|
+
io,
|
|
173
|
+
async () => {
|
|
174
|
+
const r = await client.callTool("get_kernel_endpoint_run", { run_id: runId });
|
|
175
|
+
lastStatus = toolField(r, "status");
|
|
176
|
+
return { done: !!lastStatus && lastStatus !== "running" && lastStatus !== "queued", value: r };
|
|
177
|
+
},
|
|
178
|
+
{ intervalMs: POLL_INTERVAL_MS, ceilingMs: POLL_CEILING_MS }
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
if (!settled) {
|
|
182
|
+
ok(ctx, `Still running after 45 minutes. Follow it with: letterstory kernel run-status ${runId}`);
|
|
183
|
+
printResult(io, flags, polled);
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
ok(ctx, `Write ${lastStatus}.`);
|
|
187
|
+
// The operator waited for the ARTICLE; print it, not the envelope. --json still
|
|
188
|
+
// gets the whole payload.
|
|
189
|
+
printResult(io, flags, polled, (v) => (typeof v?.article === "string" ? v.article : JSON.stringify(v, null, 2)));
|
|
190
|
+
return lastStatus === "done" ? 0 : 1;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* `kernel endpoint create|delete` — the standalone endpoint's own CRUD.
|
|
195
|
+
*
|
|
196
|
+
* `create_kernel_endpoint` and `delete_kernel_endpoint` existed as tools with no
|
|
197
|
+
* command at all, so provisioning the thing `kernel write` runs against could
|
|
198
|
+
* only be done through the app or hand-written JSON.
|
|
199
|
+
*/
|
|
200
|
+
async function kernelEndpoint(ctx) {
|
|
201
|
+
const { client, positionals, flags, io } = ctx;
|
|
202
|
+
const action = positionals[0];
|
|
203
|
+
if (action === "create" || action === "new") {
|
|
204
|
+
const args = argsFromFlags(ARGS.create_kernel_endpoint, flags, positionals);
|
|
205
|
+
const name = args.name;
|
|
206
|
+
const result = await client.callTool("create_kernel_endpoint", args);
|
|
207
|
+
ok(ctx, `Created kernel endpoint "${name}".`);
|
|
208
|
+
printResult(io, flags, result);
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
if (action === "delete" || action === "rm") {
|
|
212
|
+
const id = requirePositional(positionals, 1, "endpoint-id");
|
|
213
|
+
if (!flags.yes) {
|
|
214
|
+
io.error(`Re-run with --yes to confirm: ${ctx.bin} kernel endpoint delete ${id} --yes`);
|
|
215
|
+
return 1;
|
|
153
216
|
}
|
|
217
|
+
const result = await client.callTool(
|
|
218
|
+
"delete_kernel_endpoint",
|
|
219
|
+
argsFromFlags(ARGS.delete_kernel_endpoint, flags, positionals)
|
|
220
|
+
);
|
|
221
|
+
ok(ctx, "Kernel endpoint deleted.");
|
|
222
|
+
printResult(io, flags, result);
|
|
223
|
+
return 0;
|
|
154
224
|
}
|
|
225
|
+
throw new CliError(`Unknown kernel endpoint action: ${action ?? "(none)"}. Try: create, delete`);
|
|
155
226
|
}
|
|
156
227
|
|
|
157
228
|
/** The OTHER door: reword the document you already have, keeping its own markup. */
|