@letterstory/cli 0.49.2 → 0.52.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 +21 -0
- package/lib/cli.mjs +1 -1
- package/lib/commands/admin.mjs +141 -1
- package/lib/commands/arena.mjs +90 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -352,6 +352,13 @@ long-running agent can watch the fleet on a schedule without a browser.
|
|
|
352
352
|
export LETTERSTORY_STAFF_API_KEY=<your staff key with tuning:experiment>
|
|
353
353
|
|
|
354
354
|
letterstory admin tuning-context --q "colons" # what the agent knows (JSON)
|
|
355
|
+
letterstory admin tuning kernels # every catalog kernel: live version, writer, candidates
|
|
356
|
+
letterstory admin tuning kernel <service-kernel-id> --version live # one version's kernel file (JSON)
|
|
357
|
+
letterstory admin tuning candidate <service-kernel-id> --file hazel.json --note "Echo as the writer"
|
|
358
|
+
letterstory admin tuning compare --title "Echo vs live" --candidates Hazel:21,Aspen:20
|
|
359
|
+
letterstory admin tuning comparison <id> # per-measure verdicts (better / worse / within noise / refused)
|
|
360
|
+
letterstory admin tuning picks <id> # the blind queue; then: admin tuning pick <id> --cell <key> --pick left|right|tie
|
|
361
|
+
letterstory admin tuning promote <experiment-id> --versions <service-kernel-id>:21 # from a concluded "ship" decision
|
|
355
362
|
letterstory admin experiments list --q colons # what we've tried
|
|
356
363
|
letterstory admin experiments new "We use too many colons" --as "claude-code"
|
|
357
364
|
letterstory admin experiments update <id> --plan-file plan.json --budget 120
|
|
@@ -491,11 +498,25 @@ letterstory arena run-status <run-id> # status: awaiting_direct
|
|
|
491
498
|
letterstory arena direction <run-id> --choice 1
|
|
492
499
|
letterstory arena direction <run-id> --custom "Lead with the hidden costs"
|
|
493
500
|
letterstory arena direction <run-id> --skip # let the agents decide
|
|
501
|
+
letterstory arena run --topic "payroll solutions" --direction "Lead with the hidden costs" # no gate, no vote
|
|
502
|
+
|
|
503
|
+
# Watching runs (what an agent monitoring the account polls):
|
|
504
|
+
letterstory arena runs --status awaiting_direction # the gates waiting on you
|
|
505
|
+
letterstory arena runs --since 2026-10-02T00:00:00Z --limit 50
|
|
506
|
+
letterstory arena runs --before <next_cursor> # the next (older) page
|
|
507
|
+
letterstory arena latest --article <uuid> # the post's latest run, whoever started it
|
|
508
|
+
|
|
509
|
+
# What research decided each site is — and your call on one (research never overrides it):
|
|
510
|
+
letterstory arena roles --rival # competitors research found that you never listed
|
|
511
|
+
letterstory arena role set reviewsite.com --role context
|
|
512
|
+
letterstory arena role set rival.com --role company --company "Rival" --rival
|
|
513
|
+
letterstory arena role reset rival.com # hand it back to research
|
|
494
514
|
|
|
495
515
|
# Arenas are callable endpoints with one-file defaults (the org's default arena is "Letterstory"):
|
|
496
516
|
letterstory arena list
|
|
497
517
|
letterstory arena endpoint create --name "Docs research" --defaults '{"depth":"barebones"}'
|
|
498
518
|
letterstory arena endpoint config <arena-id> [--file cfg.json]
|
|
519
|
+
letterstory arena endpoint update <arena-id> --name "Docs research v2" # creator or org admin only
|
|
499
520
|
letterstory arena endpoint delete <arena-id> --yes
|
|
500
521
|
|
|
501
522
|
# Ledes: a prospect's context in, ready-to-send opening lines out (chosen from a
|
package/lib/cli.mjs
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
} from "./commands.mjs";
|
|
55
55
|
|
|
56
56
|
// Keep in sync with cli/package.json.
|
|
57
|
-
export const VERSION = "0.
|
|
57
|
+
export const VERSION = "0.52.0";
|
|
58
58
|
|
|
59
59
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
60
60
|
// can't accidentally swallow the id as --json's value.
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// admin experiments list|show|new|update|note|started|spend|conclude|abandon … (tuning:experiment)
|
|
8
8
|
// admin tuning-context [--q "<question>"] (tuning:experiment)
|
|
9
9
|
// admin tuning-import <brief-results.json> [--experiment <id>] (tuning:experiment)
|
|
10
|
+
// admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|promote … (tuning:experiment)
|
|
10
11
|
// admin evals scoreboard|cases|runs|run|triage|guard|retire … (fleet:read / tuning:experiment)
|
|
11
12
|
// admin crm import|capture|signals|record|signal|accounts|account|score|slack-channels|link-slack|white-glove|set-white-glove|churn|record-churn|undo-churn|onboard|onboarding|retry-onboarding|onboard-plan|payment-link … (crm:read / crm:write)
|
|
12
13
|
// admin create-org --domain acme.com [--name "Acme"] [--test] [--no-key] [--key-name N]
|
|
@@ -412,6 +413,144 @@ async function cmdAdminTuningContext(ctx) {
|
|
|
412
413
|
);
|
|
413
414
|
}
|
|
414
415
|
|
|
416
|
+
/* ------------------------------------------------------------ the tuning console's Write area */
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* `admin tuning …` — kernel versions as files, comparisons of a candidate against live, blind picks
|
|
420
|
+
* and promotion from the ledger (docs/architecture/tuning-foundation.md): the headless half of the
|
|
421
|
+
* tuning console's Write › Kernels and Compare tabs. Needs `tuning:experiment` on the key.
|
|
422
|
+
*
|
|
423
|
+
* admin tuning kernels every catalog kernel, live version, writer, candidates
|
|
424
|
+
* admin tuning kernel <service-kernel-id> [--version N|live] one version's file (JSON)
|
|
425
|
+
* admin tuning candidate <service-kernel-id> --file f.json [--note "…"] save a file as a candidate version
|
|
426
|
+
* admin tuning compare --title "…" --candidates Hazel:21,Aspen:20 [--experiment <id>] start a comparison
|
|
427
|
+
* admin tuning comparisons | comparison <id> list / read one (per-measure verdicts)
|
|
428
|
+
* admin tuning picks <comparison-id> the blind queue (next pairs)
|
|
429
|
+
* admin tuning pick <comparison-id> --cell <key> --pick left|right|tie record a blind pick
|
|
430
|
+
* admin tuning promote <experiment-id> --versions <service-kernel-id>:<version>,… promote from a "ship" decision
|
|
431
|
+
*/
|
|
432
|
+
async function cmdAdminTuning(ctx) {
|
|
433
|
+
const [sub, id] = ctx.positionals;
|
|
434
|
+
const { flags, io } = ctx;
|
|
435
|
+
const json = flagBool(flags.json);
|
|
436
|
+
const T = "/api/superadmin/tuning";
|
|
437
|
+
switch (sub) {
|
|
438
|
+
case "kernels": {
|
|
439
|
+
const r = await staffRequest(ctx, "GET", `${T}/kernel-files`);
|
|
440
|
+
if (json) return printResult(io, flags, r);
|
|
441
|
+
return io.log(
|
|
442
|
+
r.kernels
|
|
443
|
+
.map((k) => {
|
|
444
|
+
const c = k.versions
|
|
445
|
+
.filter((v) => v.status === "candidate")
|
|
446
|
+
.map((v) => `v${v.version} ${v.writer}`);
|
|
447
|
+
return `${k.kernel.padEnd(12)} live v${k.live} ${k.writer} ${k.serviceKernelId}${c.length ? `\n candidates: ${c.join(", ")}` : ""}`;
|
|
448
|
+
})
|
|
449
|
+
.join("\n") || "(no kernels)"
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
case "kernel": {
|
|
453
|
+
if (!id) throw new CliError("Usage: admin tuning kernel <service-kernel-id> [--version N|live]");
|
|
454
|
+
const version = flagStr(flags.version) ?? "live";
|
|
455
|
+
return printResult(
|
|
456
|
+
io,
|
|
457
|
+
{ ...flags, json: true },
|
|
458
|
+
await staffRequest(ctx, "GET", `${T}/kernel-files/${id}/versions/${version}`)
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
case "candidate": {
|
|
462
|
+
const path = flagStr(flags.file);
|
|
463
|
+
let file;
|
|
464
|
+
if (path) {
|
|
465
|
+
try {
|
|
466
|
+
file = JSON.parse(readFileSync(path, "utf8"));
|
|
467
|
+
} catch (err) {
|
|
468
|
+
throw new CliError(`--file ${path}: ${err.message}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (!id || !file)
|
|
472
|
+
throw new CliError('Usage: admin tuning candidate <service-kernel-id> --file kernel.json [--note "…"]');
|
|
473
|
+
const r = await staffRequest(
|
|
474
|
+
ctx,
|
|
475
|
+
"POST",
|
|
476
|
+
`${T}/kernel-files/${id}/versions`,
|
|
477
|
+
compact({ file, note: flagStr(flags.note) })
|
|
478
|
+
);
|
|
479
|
+
return json ? printResult(io, flags, r) : io.log(`saved candidate v${r.version}`);
|
|
480
|
+
}
|
|
481
|
+
case "compare": {
|
|
482
|
+
const title = flagStr(flags.title);
|
|
483
|
+
const list = flagList(flags.candidates);
|
|
484
|
+
if (!title || !list?.length)
|
|
485
|
+
throw new CliError(
|
|
486
|
+
'Usage: admin tuning compare --title "…" --candidates Hazel:21,Aspen:20 [--experiment <id>]'
|
|
487
|
+
);
|
|
488
|
+
const candidates = list.map((item) => {
|
|
489
|
+
const at = item.lastIndexOf(":");
|
|
490
|
+
const version = Number(item.slice(at + 1));
|
|
491
|
+
if (at < 1 || !Number.isInteger(version))
|
|
492
|
+
throw new CliError(`--candidates items are kernel:version, got "${item}"`);
|
|
493
|
+
return { kernel: item.slice(0, at), candidate_version: version };
|
|
494
|
+
});
|
|
495
|
+
const r = await staffRequest(
|
|
496
|
+
ctx,
|
|
497
|
+
"POST",
|
|
498
|
+
`${T}/comparisons`,
|
|
499
|
+
compact({ title, candidates, experiment_id: flagStr(flags.experiment) })
|
|
500
|
+
);
|
|
501
|
+
return json ? printResult(io, flags, r) : io.log(`comparison ${r.comparison?.id ?? r.id ?? ""} started`);
|
|
502
|
+
}
|
|
503
|
+
case "comparisons":
|
|
504
|
+
return printResult(io, { ...flags, json: true }, await staffRequest(ctx, "GET", `${T}/comparisons`));
|
|
505
|
+
case "comparison":
|
|
506
|
+
if (!id) throw new CliError("Usage: admin tuning comparison <id>");
|
|
507
|
+
return printResult(io, { ...flags, json: true }, await staffRequest(ctx, "GET", `${T}/comparisons/${id}`));
|
|
508
|
+
case "picks":
|
|
509
|
+
if (!id) throw new CliError("Usage: admin tuning picks <comparison-id>");
|
|
510
|
+
return printResult(
|
|
511
|
+
io,
|
|
512
|
+
{ ...flags, json: true },
|
|
513
|
+
await staffRequest(ctx, "GET", `${T}/comparisons/${id}/picks`)
|
|
514
|
+
);
|
|
515
|
+
case "pick": {
|
|
516
|
+
const cell = flagStr(flags.cell);
|
|
517
|
+
const pick = flagStr(flags.pick);
|
|
518
|
+
if (!id || !cell || !["left", "right", "tie"].includes(pick ?? ""))
|
|
519
|
+
throw new CliError("Usage: admin tuning pick <comparison-id> --cell <key> --pick left|right|tie");
|
|
520
|
+
return printResult(
|
|
521
|
+
io,
|
|
522
|
+
flags,
|
|
523
|
+
await staffRequest(ctx, "POST", `${T}/comparisons/${id}/picks`, { cell_key: cell, outcome: pick })
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
case "promote": {
|
|
527
|
+
const list = flagList(flags.versions);
|
|
528
|
+
if (!id || !list?.length)
|
|
529
|
+
throw new CliError(
|
|
530
|
+
"Usage: admin tuning promote <experiment-id> --versions <service-kernel-id>:<version>,…"
|
|
531
|
+
);
|
|
532
|
+
const versions = list.map((item) => {
|
|
533
|
+
const at = item.lastIndexOf(":");
|
|
534
|
+
return { service_kernel_id: item.slice(0, at), version: Number(item.slice(at + 1)) };
|
|
535
|
+
});
|
|
536
|
+
return printResult(
|
|
537
|
+
io,
|
|
538
|
+
{ ...flags, json: true },
|
|
539
|
+
await staffRequest(
|
|
540
|
+
ctx,
|
|
541
|
+
"POST",
|
|
542
|
+
`${EXPERIMENTS}/${id}/promote`,
|
|
543
|
+
compact({ versions, actor: actorFrom(flags) })
|
|
544
|
+
)
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
default:
|
|
548
|
+
throw new CliError(
|
|
549
|
+
"Usage: admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|promote (see --help)"
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
415
554
|
/* ------------------------------------------------------------ the staff CRM */
|
|
416
555
|
|
|
417
556
|
const CRM = "/api/superadmin/crm";
|
|
@@ -1078,8 +1217,9 @@ export async function cmdAdmin(ctx) {
|
|
|
1078
1217
|
if (sub === "experiments") return cmdAdminExperiments({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
1079
1218
|
if (sub === "tuning-context") return cmdAdminTuningContext({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
1080
1219
|
if (sub === "tuning-import") return cmdAdminTuningImport({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
1220
|
+
if (sub === "tuning") return cmdAdminTuning({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
1081
1221
|
if (sub === "evals") return cmdAdminEvals({ ...ctx, positionals: ctx.positionals.slice(1) });
|
|
1082
1222
|
throw new CliError(
|
|
1083
|
-
'Usage: admin create-org --domain <bare-domain> [--onboard] … | admin ask "<question>" | admin experiments … | admin crm … | admin evals … | admin tuning-context (see --help)'
|
|
1223
|
+
'Usage: admin create-org --domain <bare-domain> [--onboard] … | admin ask "<question>" | admin experiments … | admin crm … | admin evals … | admin tuning … | admin tuning-context (see --help)'
|
|
1084
1224
|
);
|
|
1085
1225
|
}
|
package/lib/commands/arena.mjs
CHANGED
|
@@ -68,6 +68,8 @@ const RUN_OPTIONS = {
|
|
|
68
68
|
// --no-verify turns the fact-check off; nothing sent otherwise (the default is on).
|
|
69
69
|
verify: { value: (flags) => (flags["no-verify"] === true ? false : undefined) },
|
|
70
70
|
mention_degree: { flag: "mention", enum: ["off", "subtle", "woven"] },
|
|
71
|
+
// What the fact-check does with a claim it can neither fix nor stand behind: flag or remove it.
|
|
72
|
+
unresolved_claims: { flag: "unresolved", enum: ["comment", "drop"] },
|
|
71
73
|
// Positioning as data — a file, not flags: `--context-file ctx.json` of
|
|
72
74
|
// {manifesto, stances[], priority_topics[], avoid_topics[], identity{is[],is_not[],one_liner}}.
|
|
73
75
|
context: { value: (flags) => readJsonObjectFlag(flags, "context") },
|
|
@@ -116,6 +118,8 @@ export const ARGS = {
|
|
|
116
118
|
},
|
|
117
119
|
},
|
|
118
120
|
existing_content: { value: (flags) => flagStr(flags["existing-content"]) },
|
|
121
|
+
// Your own thesis: adopted as the run's direction, so a gate never pauses for it.
|
|
122
|
+
direction: { flag: "direction" },
|
|
119
123
|
article_id: { flag: "article" },
|
|
120
124
|
...RUN_OPTIONS,
|
|
121
125
|
},
|
|
@@ -124,8 +128,32 @@ export const ARGS = {
|
|
|
124
128
|
},
|
|
125
129
|
list_arena_runs: {
|
|
126
130
|
arena_id: { flag: "arena" },
|
|
131
|
+
article_id: { flag: "article" },
|
|
132
|
+
status: { flag: "status", type: "list" },
|
|
133
|
+
since: { flag: "since" },
|
|
134
|
+
before: { flag: "before" },
|
|
127
135
|
limit: { flag: "limit", type: "num" },
|
|
128
136
|
},
|
|
137
|
+
get_latest_arena_run: {
|
|
138
|
+
article_id: { flag: "article", required: true },
|
|
139
|
+
},
|
|
140
|
+
// What research decided each site is, and your call on one (never overridden by the model).
|
|
141
|
+
list_source_roles: {
|
|
142
|
+
role: { flag: "role", enum: ["authority", "company", "context"] },
|
|
143
|
+
rival: { flag: "rival", type: "bool" },
|
|
144
|
+
decided_by: { flag: "decided-by", enum: ["model", "customer", "staff"] },
|
|
145
|
+
limit: { flag: "limit", type: "num" },
|
|
146
|
+
},
|
|
147
|
+
set_source_role: {
|
|
148
|
+
site: { positional: 1, label: "site", required: true },
|
|
149
|
+
role: { flag: "role", enum: ["authority", "company", "context"], required: true },
|
|
150
|
+
company: { flag: "company" },
|
|
151
|
+
rival: { flag: "rival", type: "bool" },
|
|
152
|
+
note: { flag: "note" },
|
|
153
|
+
},
|
|
154
|
+
reset_source_role: {
|
|
155
|
+
site: { positional: 1, label: "site", required: true },
|
|
156
|
+
},
|
|
129
157
|
submit_arena_direction: {
|
|
130
158
|
run_id: { positional: 0, label: "run-id", required: true },
|
|
131
159
|
choice: { flag: "choice", type: "num" },
|
|
@@ -144,6 +172,11 @@ export const ARGS = {
|
|
|
144
172
|
delete_arena: {
|
|
145
173
|
arena_id: { positional: 1, label: "arena-id", required: true },
|
|
146
174
|
},
|
|
175
|
+
update_arena: {
|
|
176
|
+
arena_id: { positional: 1, label: "arena-id", required: true },
|
|
177
|
+
name: { flag: "name" },
|
|
178
|
+
defaults: { value: (flags) => readJsonObjectFlag(flags, "defaults") },
|
|
179
|
+
},
|
|
147
180
|
// The arena's configuration as ONE file: `arena endpoint config <id>` prints it,
|
|
148
181
|
// `arena endpoint config <id> --file cfg.json` replaces it with the file's document.
|
|
149
182
|
get_arena_config: {
|
|
@@ -167,6 +200,12 @@ export async function cmdArena(ctx) {
|
|
|
167
200
|
return arenaRunStatus(rest);
|
|
168
201
|
case "runs":
|
|
169
202
|
return arenaRuns(rest);
|
|
203
|
+
case "latest":
|
|
204
|
+
return arenaLatest(rest);
|
|
205
|
+
case "roles":
|
|
206
|
+
return arenaRoles(rest);
|
|
207
|
+
case "role":
|
|
208
|
+
return arenaRole(rest);
|
|
170
209
|
case "direction":
|
|
171
210
|
return arenaDirection(rest);
|
|
172
211
|
case "cancel":
|
|
@@ -181,7 +220,7 @@ export async function cmdArena(ctx) {
|
|
|
181
220
|
return arenaEndpoint(rest);
|
|
182
221
|
default:
|
|
183
222
|
throw new CliError(
|
|
184
|
-
`Unknown arena subcommand: ${sub ?? "(none)"}. Try: run, run-status, runs, direction, cancel, balance, list, endpoint`
|
|
223
|
+
`Unknown arena subcommand: ${sub ?? "(none)"}. Try: run, run-status, runs, latest, direction, cancel, balance, list, endpoint, roles, role`
|
|
185
224
|
);
|
|
186
225
|
}
|
|
187
226
|
}
|
|
@@ -262,6 +301,47 @@ async function arenaRuns(ctx) {
|
|
|
262
301
|
return 0;
|
|
263
302
|
}
|
|
264
303
|
|
|
304
|
+
async function arenaLatest(ctx) {
|
|
305
|
+
const { client, flags, io } = ctx;
|
|
306
|
+
const args = argsFromFlags(ARGS.get_latest_arena_run, flags, ctx.positionals);
|
|
307
|
+
const result = await client.callTool("get_latest_arena_run", args);
|
|
308
|
+
printResult(io, flags, result);
|
|
309
|
+
return 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function arenaRoles(ctx) {
|
|
313
|
+
const { client, flags, io } = ctx;
|
|
314
|
+
const result = await client.callTool(
|
|
315
|
+
"list_source_roles",
|
|
316
|
+
argsFromFlags(ARGS.list_source_roles, flags, ctx.positionals)
|
|
317
|
+
);
|
|
318
|
+
printResult(io, flags, result);
|
|
319
|
+
return 0;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** `arena role set <site> --role …` / `arena role reset <site>` — your call on one site. */
|
|
323
|
+
async function arenaRole(ctx) {
|
|
324
|
+
const { client, positionals, flags, io } = ctx;
|
|
325
|
+
const action = positionals[0];
|
|
326
|
+
if (action === "set") {
|
|
327
|
+
const args = argsFromFlags(ARGS.set_source_role, flags, positionals);
|
|
328
|
+
const result = await client.callTool("set_source_role", args);
|
|
329
|
+
ok(ctx, `${result?.role?.site ?? args.site} is now ${args.role}${args.rival ? " (a rival)" : ""}.`);
|
|
330
|
+
printResult(io, flags, result);
|
|
331
|
+
return 0;
|
|
332
|
+
}
|
|
333
|
+
if (action === "reset") {
|
|
334
|
+
const result = await client.callTool(
|
|
335
|
+
"reset_source_role",
|
|
336
|
+
argsFromFlags(ARGS.reset_source_role, flags, positionals)
|
|
337
|
+
);
|
|
338
|
+
ok(ctx, result?.reset ? "Handed back to research." : "Research had not recorded that site.");
|
|
339
|
+
printResult(io, flags, result);
|
|
340
|
+
return 0;
|
|
341
|
+
}
|
|
342
|
+
throw new CliError(`Unknown arena role action: ${action ?? "(none)"}. Try: set, reset`);
|
|
343
|
+
}
|
|
344
|
+
|
|
265
345
|
async function arenaDirection(ctx) {
|
|
266
346
|
const { client, flags, io } = ctx;
|
|
267
347
|
const runId = requirePositional(ctx.positionals, 0, "run-id");
|
|
@@ -327,6 +407,14 @@ async function arenaEndpoint(ctx) {
|
|
|
327
407
|
printResult(io, flags, result);
|
|
328
408
|
return 0;
|
|
329
409
|
}
|
|
410
|
+
if (action === "update" || action === "edit") {
|
|
411
|
+
const id = requirePositional(positionals, 1, "arena-id");
|
|
412
|
+
const args = argsFromFlags(ARGS.update_arena, flags, positionals);
|
|
413
|
+
const result = await client.callTool("update_arena", args);
|
|
414
|
+
ok(ctx, `Updated arena ${id}.`);
|
|
415
|
+
printResult(io, flags, result);
|
|
416
|
+
return 0;
|
|
417
|
+
}
|
|
330
418
|
if (action === "config") {
|
|
331
419
|
const id = requirePositional(positionals, 1, "arena-id");
|
|
332
420
|
if (flagStr(flags.file) === undefined) {
|
|
@@ -344,5 +432,5 @@ async function arenaEndpoint(ctx) {
|
|
|
344
432
|
printResult(io, flags, result);
|
|
345
433
|
return 0;
|
|
346
434
|
}
|
|
347
|
-
throw new CliError(`Unknown arena endpoint action: ${action ?? "(none)"}. Try: create, delete, config`);
|
|
435
|
+
throw new CliError(`Unknown arena endpoint action: ${action ?? "(none)"}. Try: create, update, delete, config`);
|
|
348
436
|
}
|