@letterstory/cli 0.138.0 → 0.140.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 +20 -2
- package/lib/commands/repo.mjs +29 -5
- package/lib/commands/sim.mjs +285 -0
- package/lib/commands/targets.mjs +30 -1
- package/lib/commands.mjs +1 -0
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -60,10 +60,11 @@ import {
|
|
|
60
60
|
cmdCampaigns,
|
|
61
61
|
cmdTargets,
|
|
62
62
|
cmdVisits,
|
|
63
|
+
cmdSim,
|
|
63
64
|
} from "./commands.mjs";
|
|
64
65
|
|
|
65
66
|
// Keep in sync with cli/package.json.
|
|
66
|
-
export const VERSION = "0.
|
|
67
|
+
export const VERSION = "0.140.0";
|
|
67
68
|
|
|
68
69
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
69
70
|
// can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
|
|
@@ -71,6 +72,9 @@ export const VERSION = "0.138.0";
|
|
|
71
72
|
export const BOOLEAN_FLAGS = new Set([
|
|
72
73
|
"remap",
|
|
73
74
|
"json",
|
|
75
|
+
// sim seed: make the new seed active; collections it doesn't cover start empty.
|
|
76
|
+
"activate",
|
|
77
|
+
"empty",
|
|
74
78
|
// admin models runs: only the starred ones.
|
|
75
79
|
"starred",
|
|
76
80
|
// shredder file / arena file: save the version as a candidate beside live.
|
|
@@ -630,6 +634,7 @@ Ranking targets (what the org wants to be THE answer for — company-wide or on
|
|
|
630
634
|
targets subqueries [<target-id>] [--retired] The phrasings asked under each target (who retired one, and why)
|
|
631
635
|
targets subquery add <target-id> <phrasing> Another way your buyers ask a target's question
|
|
632
636
|
targets subquery update <subquery-id> [--text <t>] [--retire | --restore]
|
|
637
|
+
targets posts [<target-id>] Each target's posts (written for it first), cited and won (28 days)
|
|
633
638
|
|
|
634
639
|
Arenas (adversarial clusters of research agents -> a sourced, fact-checked outline):
|
|
635
640
|
arena run --topic <text> [--source <url> …] [--must-include <text> …] [--depth barebones|fleshed_out]
|
|
@@ -1416,7 +1421,8 @@ Repo changes (Claude Code edits your repo in a sandbox; the Letterstory GitHub A
|
|
|
1416
1421
|
repo delete-secret --repo <owner/name> --secret <KEY>
|
|
1417
1422
|
Delete one
|
|
1418
1423
|
repo status <run-id> Phase, spend, PR link and Claude's summary
|
|
1419
|
-
repo activity <run-id> [--follow]
|
|
1424
|
+
repo activity <run-id> [--follow] [--since <iso>] What the sandbox is doing, line by line (--follow: until it stops)
|
|
1425
|
+
repo say <run-id> <message…> Tell a running build something; it carries on with it
|
|
1420
1426
|
repo list [--status running|succeeded|no_changes|failed|cancelled] [--limit n] [--audit <id>]
|
|
1421
1427
|
[--source site_audit_issue|site_quick_check|letterprove_install|visitors_install]
|
|
1422
1428
|
repo publish <run-id> Make PR on a preview in review
|
|
@@ -1440,6 +1446,17 @@ Visits (Visitors: AI agents reading your site, and AI answers sending people to
|
|
|
1440
1446
|
visits pages [--since 28d] [--path '/docs/*'] [--agent <id>] [--limit <n>] [--site <id>]
|
|
1441
1447
|
People and AI agents per page
|
|
1442
1448
|
|
|
1449
|
+
Sim (Simulations: a free, simulated copy of your API for agents, and what they did with it):
|
|
1450
|
+
sim [list] Your simulations and their URLs
|
|
1451
|
+
sim create --name <n> (--spec-url <url>|--spec-file <path>) [--slug <s>] [--upstream-url <url> --upstream-credential <c>]
|
|
1452
|
+
sim show <id> Resources, their serving rules, and who set them
|
|
1453
|
+
sim rule <id> <resource> synthesize|forward|sink|refuse|inferred [--note <why>]
|
|
1454
|
+
sim pause <id> | sim resume <id>
|
|
1455
|
+
sim key <id> [--agent <a>] [--model <m>] [--goal <g>] Mint a key for your own agents or CI
|
|
1456
|
+
sim sessions <id> [--limit N] Agent sessions, newest first
|
|
1457
|
+
sim session <session-id> One session, call by call
|
|
1458
|
+
sim report <id> [--since 30d] Funnel, missing operations, argument errors, loops, by agent
|
|
1459
|
+
|
|
1443
1460
|
Evals (how the pipeline is doing on your posts, and the regression cases captured from them):
|
|
1444
1461
|
evals scoreboard [--collection <id>] [--days N] Every measure by stage: posts, failing, rate
|
|
1445
1462
|
evals cases [--status open|guarding|proposed|retired|rejected] [--stage research|brief|links|mentions|accuracy|pipeline|shred]
|
|
@@ -1537,6 +1554,7 @@ const CLIENT_COMMANDS = {
|
|
|
1537
1554
|
site: cmdSite,
|
|
1538
1555
|
evals: cmdEvals,
|
|
1539
1556
|
visits: cmdVisits,
|
|
1557
|
+
sim: cmdSim,
|
|
1540
1558
|
// Platform-scoped (staff key) — the only commands that act BEFORE an org exists.
|
|
1541
1559
|
admin: cmdAdmin,
|
|
1542
1560
|
};
|
package/lib/commands/repo.mjs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// repo list → list_repo_changes repo cancel → cancel_repo_change
|
|
6
6
|
// repo publish → publish_repo_change (Make PR on a preview)
|
|
7
7
|
// repo revise → revise_repo_change (ask for more on a preview, before the PR)
|
|
8
|
+
// repo say → send_repo_change_message (steer a build while its agent works)
|
|
8
9
|
// repo repos → list_github_app_repos repo branches → list_repo_branches
|
|
9
10
|
// repo prs → list_open_pull_requests (every open PR, whoever opened it)
|
|
10
11
|
// repo install-letterprove → letterprove_open_install_pr
|
|
@@ -182,7 +183,16 @@ const ARGS = {
|
|
|
182
183
|
auto_merge_after_hours: { value: (flags) => autoMergeHours(flags["auto-merge"]) },
|
|
183
184
|
},
|
|
184
185
|
get_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
185
|
-
get_repo_change_activity: {
|
|
186
|
+
get_repo_change_activity: {
|
|
187
|
+
run_id: { positional: 0, required: true, label: "run-id" },
|
|
188
|
+
since: "since",
|
|
189
|
+
},
|
|
190
|
+
send_repo_change_message: {
|
|
191
|
+
run_id: { positional: 0, required: true, label: "run-id" },
|
|
192
|
+
message: {
|
|
193
|
+
value: (flags, positionals) => flagStr(flags.message) ?? (positionals.slice(1).join(" ") || undefined),
|
|
194
|
+
},
|
|
195
|
+
},
|
|
186
196
|
cancel_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
187
197
|
publish_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
188
198
|
revise_repo_change: {
|
|
@@ -283,6 +293,8 @@ export async function cmdRepo(ctx) {
|
|
|
283
293
|
return repoPublish(rest);
|
|
284
294
|
case "revise":
|
|
285
295
|
return repoRevise(rest);
|
|
296
|
+
case "say":
|
|
297
|
+
return repoSay(rest);
|
|
286
298
|
case "repos":
|
|
287
299
|
return repoRepos(rest);
|
|
288
300
|
case "branches":
|
|
@@ -299,7 +311,7 @@ export async function cmdRepo(ctx) {
|
|
|
299
311
|
return repoSlackTest(rest);
|
|
300
312
|
default:
|
|
301
313
|
throw new CliError(
|
|
302
|
-
`Unknown repo subcommand: ${sub ?? "(none)"}. Try: start, fix-audit, install-letterprove, install-visitors, sites, set-site, reset-site, secrets, set-secret, delete-secret, status, list, publish, cancel, repos, branches, prs, settings, suggestions, slack-targets, slack-test`
|
|
314
|
+
`Unknown repo subcommand: ${sub ?? "(none)"}. Try: start, fix-audit, install-letterprove, install-visitors, sites, set-site, reset-site, secrets, set-secret, delete-secret, status, list, publish, revise, say, cancel, repos, branches, prs, settings, suggestions, slack-targets, slack-test`
|
|
303
315
|
);
|
|
304
316
|
}
|
|
305
317
|
}
|
|
@@ -555,12 +567,12 @@ async function repoStatus(ctx) {
|
|
|
555
567
|
return 0;
|
|
556
568
|
}
|
|
557
569
|
|
|
558
|
-
const ACTIVITY_MARK = { progress: "·", stage: "▸", say: "»", tool: "$", error: "!" };
|
|
570
|
+
const ACTIVITY_MARK = { progress: "·", stage: "▸", say: "»", think: "◇", tool: "$", user: ">", error: "!" };
|
|
559
571
|
|
|
560
|
-
/** `repo activity <run-id> [--follow]`: the build's feed; --follow prints new lines until it stops. */
|
|
572
|
+
/** `repo activity <run-id> [--follow] [--since <iso>]`: the build's feed; --follow prints new lines until it stops. */
|
|
561
573
|
async function repoActivity(ctx) {
|
|
562
574
|
const { client, flags, io } = ctx;
|
|
563
|
-
refuseUnknownFlags(ctx, {}, ["follow"]);
|
|
575
|
+
refuseUnknownFlags(ctx, {}, ["follow", "since"]);
|
|
564
576
|
const args = argsFromFlags(ARGS.get_repo_change_activity, flags, ctx.positionals);
|
|
565
577
|
const line = (l) => `${l.at.slice(11, 19)} ${ACTIVITY_MARK[l.kind] ?? " "} ${l.text}`;
|
|
566
578
|
let { activity } = await client.callTool("get_repo_change_activity", args);
|
|
@@ -623,6 +635,18 @@ async function repoPublish(ctx) {
|
|
|
623
635
|
return 0;
|
|
624
636
|
}
|
|
625
637
|
|
|
638
|
+
/** `repo say <run-id> <message…>` (or --message): steer a build while its agent works. */
|
|
639
|
+
async function repoSay(ctx) {
|
|
640
|
+
const { client, flags, io } = ctx;
|
|
641
|
+
refuseUnknownFlags(ctx, {}, ["message"]);
|
|
642
|
+
const args = argsFromFlags(ARGS.send_repo_change_message, flags, ctx.positionals);
|
|
643
|
+
if (!args.message) throw new CliError("Say what to tell the build: repo say <run-id> <message…> (or --message)");
|
|
644
|
+
const result = await client.callTool("send_repo_change_message", args);
|
|
645
|
+
ok(ctx, result.message);
|
|
646
|
+
printResult(io, flags, result.sent, (s) => `${s.delivered ? "Sent" : "Waiting for the sandbox"}: ${s.message}`);
|
|
647
|
+
return 0;
|
|
648
|
+
}
|
|
649
|
+
|
|
626
650
|
/** `repo revise <run-id> --instructions <text>`: a follow-up on a preview, built on top of it. */
|
|
627
651
|
async function repoRevise(ctx) {
|
|
628
652
|
const { client, flags, io } = ctx;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// `sim` — Simulations from a terminal: a free, simulated copy of your API that agents can
|
|
2
|
+
// use, and what they did with it. Thin wrappers over the simulation tools (create_simulation,
|
|
3
|
+
// get_simulation, update_simulation, mint_simulation_key, list_simulation_sessions,
|
|
4
|
+
// get_simulation_session, get_simulation_report), the same lib functions the app calls.
|
|
5
|
+
//
|
|
6
|
+
// letterstory sim create --name "Attio API" --spec-url https://api.attio.com/openapi/api
|
|
7
|
+
// letterstory sim show <id>
|
|
8
|
+
// letterstory sim rule <id> /v2/webhooks sink --note "never call real endpoints"
|
|
9
|
+
// letterstory sim create --name "Letterstory" --tools-file tools.json --tool-route "/api/integrations/tools/{name}"
|
|
10
|
+
// letterstory sim key <id> --agent our-ci --goal "nightly integration test"
|
|
11
|
+
// letterstory sim seed <id> --prompt "a 3-person content team with two blogs" --activate
|
|
12
|
+
// letterstory sim seed <id> --use <seed-id|none>
|
|
13
|
+
// letterstory sim report <id> --since 7d
|
|
14
|
+
// letterstory sim sessions <id>
|
|
15
|
+
// letterstory sim session <session-id>
|
|
16
|
+
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import { CliError } from "../client.mjs";
|
|
19
|
+
import { flagStr, printResult, argsFromFlags, refuseUnknownFlags } from "./shared.mjs";
|
|
20
|
+
import { sinceDays } from "./visits.mjs";
|
|
21
|
+
|
|
22
|
+
const RULES = ["synthesize", "forward", "sink", "refuse", "inferred"];
|
|
23
|
+
const simulationId = { positional: 0, label: "simulation-id", required: true };
|
|
24
|
+
|
|
25
|
+
export const ARGS = {
|
|
26
|
+
create_simulation: {
|
|
27
|
+
name: { flag: "name", required: true },
|
|
28
|
+
slug: "slug",
|
|
29
|
+
spec_url: "spec-url",
|
|
30
|
+
spec: {
|
|
31
|
+
value: (flags) => {
|
|
32
|
+
const path = flagStr(flags["spec-file"]);
|
|
33
|
+
if (!path) return undefined;
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
36
|
+
} catch {
|
|
37
|
+
throw new CliError(`--spec-file must be a JSON OpenAPI document (could not parse ${path})`);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
offering_id: "offering",
|
|
42
|
+
upstream_base_url: "upstream-url",
|
|
43
|
+
upstream_credential: "upstream-credential",
|
|
44
|
+
mcp_tools: {
|
|
45
|
+
value: (flags) => {
|
|
46
|
+
const path = flagStr(flags["tools-file"]);
|
|
47
|
+
if (!path) return undefined;
|
|
48
|
+
let parsed;
|
|
49
|
+
try {
|
|
50
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
51
|
+
} catch {
|
|
52
|
+
throw new CliError(
|
|
53
|
+
`--tools-file must be JSON: a tools/list result or its tools array (could not parse ${path})`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return Array.isArray(parsed) ? parsed : (parsed?.result?.tools ?? parsed?.tools);
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
tool_route: "tool-route",
|
|
60
|
+
auth_header: "auth-header",
|
|
61
|
+
},
|
|
62
|
+
list_simulations: {},
|
|
63
|
+
get_simulation: { simulation_id: simulationId },
|
|
64
|
+
update_simulation: {
|
|
65
|
+
simulation_id: simulationId,
|
|
66
|
+
status: { flag: "status", enum: ["live", "paused"] },
|
|
67
|
+
active_seed_id: {
|
|
68
|
+
value: (flags) => {
|
|
69
|
+
const v = flagStr(flags.use);
|
|
70
|
+
return v === undefined ? undefined : v === "none" ? null : v;
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
resource_rules: {
|
|
74
|
+
value: (flags, positionals) => {
|
|
75
|
+
if (positionals.length < 3) return undefined;
|
|
76
|
+
const rule = positionals[2];
|
|
77
|
+
if (!RULES.includes(rule)) throw new CliError(`rule must be one of: ${RULES.join(", ")}`);
|
|
78
|
+
return [
|
|
79
|
+
{ resource: positionals[1], rule: rule === "inferred" ? null : rule, note: flagStr(flags.note) },
|
|
80
|
+
];
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
generate_simulation_seed: {
|
|
85
|
+
simulation_id: simulationId,
|
|
86
|
+
prompt: { flag: "prompt", required: true },
|
|
87
|
+
resources: { flag: "resources", type: "list" },
|
|
88
|
+
items_per_collection: { flag: "items", type: "num" },
|
|
89
|
+
empty_workspace: { flag: "empty", type: "bool" },
|
|
90
|
+
activate: { flag: "activate", type: "bool" },
|
|
91
|
+
},
|
|
92
|
+
mint_simulation_key: { simulation_id: simulationId, agent: "agent", model: "model", goal: "goal" },
|
|
93
|
+
list_simulation_sessions: { simulation_id: simulationId, limit: { flag: "limit", type: "num" } },
|
|
94
|
+
get_simulation_session: { session_id: { positional: 0, label: "session-id", required: true } },
|
|
95
|
+
get_simulation_report: { simulation_id: simulationId, days: { value: (flags) => sinceDays(flags.since) } },
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function call(ctx, tool) {
|
|
99
|
+
return ctx.client.callTool(tool, argsFromFlags(ARGS[tool], ctx.flags, ctx.positionals));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function cmdSim(ctx) {
|
|
103
|
+
const sub = ctx.positionals[0];
|
|
104
|
+
const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
105
|
+
switch (sub) {
|
|
106
|
+
case undefined:
|
|
107
|
+
case "list":
|
|
108
|
+
case "ls":
|
|
109
|
+
return simList(rest);
|
|
110
|
+
case "create":
|
|
111
|
+
refuseUnknownFlags(rest, ARGS.create_simulation, ["spec-file", "tools-file"]);
|
|
112
|
+
return simShow(rest, await call(rest, "create_simulation"));
|
|
113
|
+
case "show":
|
|
114
|
+
return simShow(rest, await call(rest, "get_simulation"));
|
|
115
|
+
case "rule":
|
|
116
|
+
case "pause":
|
|
117
|
+
case "resume": {
|
|
118
|
+
refuseUnknownFlags(rest, ARGS.update_simulation, ["note"]);
|
|
119
|
+
if (sub !== "rule") rest.flags = { ...rest.flags, status: sub === "pause" ? "paused" : "live" };
|
|
120
|
+
return simShow(rest, await call(rest, "update_simulation"));
|
|
121
|
+
}
|
|
122
|
+
case "seed":
|
|
123
|
+
return simSeed(rest);
|
|
124
|
+
case "key":
|
|
125
|
+
return simKey(rest);
|
|
126
|
+
case "sessions":
|
|
127
|
+
return simSessions(rest);
|
|
128
|
+
case "session":
|
|
129
|
+
return simSession(rest);
|
|
130
|
+
case "report":
|
|
131
|
+
return simReport(rest);
|
|
132
|
+
default:
|
|
133
|
+
throw new CliError("Usage: sim [list|create|show|rule|pause|resume|seed|key|sessions|session|report]");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function simList(ctx) {
|
|
138
|
+
const result = await call(ctx, "list_simulations");
|
|
139
|
+
printResult(ctx.io, ctx.flags, result, (r) => {
|
|
140
|
+
if (!r.simulations?.length)
|
|
141
|
+
return "No simulations yet. Create one: sim create --name <name> --spec-url <openapi url>";
|
|
142
|
+
return r.simulations
|
|
143
|
+
.map(
|
|
144
|
+
(s) =>
|
|
145
|
+
`${s.id} ${s.name.padEnd(24)} ${s.status.padEnd(7)} ${String(s.sessions_7d).padStart(4)} sessions/7d ${s.urls.base}`
|
|
146
|
+
)
|
|
147
|
+
.join("\n");
|
|
148
|
+
});
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function simShow(ctx, result) {
|
|
153
|
+
printResult(ctx.io, ctx.flags, result, (r) => {
|
|
154
|
+
const s = r.simulation;
|
|
155
|
+
const lines = [
|
|
156
|
+
`${s.name} (${s.title} v${s.version}) — ${s.status}, ${s.operations} operations`,
|
|
157
|
+
` REST ${s.urls.base}`,
|
|
158
|
+
` MCP ${s.urls.mcp}`,
|
|
159
|
+
` Docs ${s.urls.llms}`,
|
|
160
|
+
"",
|
|
161
|
+
"Resources (rule · side effect · who set it):",
|
|
162
|
+
];
|
|
163
|
+
for (const res of r.resources ?? []) {
|
|
164
|
+
lines.push(` ${res.rule.padEnd(10)} ${res.side_effect.padEnd(9)} ${res.set_by.padEnd(8)} ${res.resource}`);
|
|
165
|
+
}
|
|
166
|
+
return lines.join("\n");
|
|
167
|
+
});
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** `sim seed <id> --prompt …` writes a seed; `sim seed <id> --use <seed-id|none>` switches the active one. */
|
|
172
|
+
async function simSeed(ctx) {
|
|
173
|
+
if (ctx.flags.use !== undefined) {
|
|
174
|
+
refuseUnknownFlags(ctx, ARGS.update_simulation, ["use"]);
|
|
175
|
+
return simShow(ctx, await call(ctx, "update_simulation"));
|
|
176
|
+
}
|
|
177
|
+
const result = await call(ctx, "generate_simulation_seed");
|
|
178
|
+
printResult(ctx.io, ctx.flags, result, (r) => {
|
|
179
|
+
const s = r.seed;
|
|
180
|
+
const rep = s.report;
|
|
181
|
+
return [
|
|
182
|
+
`Seed v${s.version} (${s.id})${s.active ? " — active" : ""}, by ${s.model ?? "upload"}`,
|
|
183
|
+
...Object.entries(s.items).map(([k, n]) => ` ${k}: ${n} items`),
|
|
184
|
+
`Kept ${rep.kept}, completed ${rep.filled}, dropped ${rep.dropped}, dangling ids ${rep.dangling.length}`,
|
|
185
|
+
s.active ? "" : `Make it active: sim seed <simulation-id> --use ${s.id}`,
|
|
186
|
+
]
|
|
187
|
+
.filter(Boolean)
|
|
188
|
+
.join("\n");
|
|
189
|
+
});
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function simKey(ctx) {
|
|
194
|
+
const result = await call(ctx, "mint_simulation_key");
|
|
195
|
+
printResult(ctx.io, ctx.flags, result, (r) =>
|
|
196
|
+
[
|
|
197
|
+
`Key (shown once): ${r.key}`,
|
|
198
|
+
`Expires: ${r.expires_at}`,
|
|
199
|
+
`Base URL: ${r.urls.base}`,
|
|
200
|
+
`Session: ${r.session_id}`,
|
|
201
|
+
].join("\n")
|
|
202
|
+
);
|
|
203
|
+
return 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function simSessions(ctx) {
|
|
207
|
+
const result = await call(ctx, "list_simulation_sessions");
|
|
208
|
+
printResult(ctx.io, ctx.flags, result, (r) => {
|
|
209
|
+
if (!r.sessions?.length) return "No sessions yet.";
|
|
210
|
+
return r.sessions
|
|
211
|
+
.map(
|
|
212
|
+
(s) =>
|
|
213
|
+
`${s.id} ${String(s.calls).padStart(4)} calls ${(s.agent ?? s.client ?? s.requester_agent ?? "unknown").padEnd(22)} ${s.goal ?? ""}`
|
|
214
|
+
)
|
|
215
|
+
.join("\n");
|
|
216
|
+
});
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function simSession(ctx) {
|
|
221
|
+
const result = await call(ctx, "get_simulation_session");
|
|
222
|
+
printResult(ctx.io, ctx.flags, result, (r) => {
|
|
223
|
+
const lines = [
|
|
224
|
+
`Session ${r.session.id} — ${r.session.agent ?? r.session.client ?? "unknown agent"}, ${r.calls.length} events`,
|
|
225
|
+
];
|
|
226
|
+
for (const c of r.calls) {
|
|
227
|
+
if (c.kind === "feedback") {
|
|
228
|
+
lines.push(` ${String(c.seq).padStart(3)} feedback: ${c.note}`);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const errs =
|
|
232
|
+
Array.isArray(c.field_errors) && c.field_errors.length
|
|
233
|
+
? ` ${c.field_errors.map((e) => `${e.path || "body"}:${e.code}`).join(", ")}`
|
|
234
|
+
: "";
|
|
235
|
+
lines.push(
|
|
236
|
+
` ${String(c.seq).padStart(3)} ${String(c.status).padEnd(4)}${(c.served ?? "").padEnd(12)} ${c.method} ${c.path}${errs}`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
return lines.join("\n");
|
|
240
|
+
});
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function simReport(ctx) {
|
|
245
|
+
const result = await call(ctx, "get_simulation_report");
|
|
246
|
+
printResult(ctx.io, ctx.flags, result, ({ report: r }) => {
|
|
247
|
+
const f = r.funnel;
|
|
248
|
+
const lines = [
|
|
249
|
+
`${r.sessions} sessions, ${r.calls} calls in ${r.window_days} days`,
|
|
250
|
+
`Funnel: ${f.minted} keys → ${f.called} called → ${f.succeeded} succeeded → ${f.wrote} wrote`,
|
|
251
|
+
`First success: median ${r.first_success.median_calls ?? "–"} calls, ${r.first_success.median_seconds ?? "–"}s`,
|
|
252
|
+
];
|
|
253
|
+
if (r.hallucinated.length) {
|
|
254
|
+
lines.push("", "Expected but missing:");
|
|
255
|
+
for (const h of r.hallucinated.slice(0, 10))
|
|
256
|
+
lines.push(
|
|
257
|
+
` ${String(h.sessions).padStart(3)} sessions ${h.method} ${h.path}${h.nearest_operation ? ` (nearest: ${h.nearest_operation})` : ""}`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
if (r.field_errors.length) {
|
|
261
|
+
lines.push("", "Argument errors:");
|
|
262
|
+
for (const e of r.field_errors.slice(0, 10))
|
|
263
|
+
lines.push(` ${String(e.count).padStart(3)}× ${e.operation_id} ${e.field} (${e.code})`);
|
|
264
|
+
}
|
|
265
|
+
if (r.error_loops.length) {
|
|
266
|
+
lines.push("", "Error loops:");
|
|
267
|
+
for (const l of r.error_loops)
|
|
268
|
+
lines.push(` ${l.operation_id}: ${l.sessions} sessions, up to ${l.longest} in a row`);
|
|
269
|
+
}
|
|
270
|
+
if (r.by_agent.length) {
|
|
271
|
+
lines.push("", "By agent:");
|
|
272
|
+
for (const a of r.by_agent)
|
|
273
|
+
lines.push(
|
|
274
|
+
` ${a.agent.padEnd(22)} ${a.sessions} sessions, ${a.calls} calls, ${Math.round(a.success_rate * 100)}% ok`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
if (r.feedback.length) {
|
|
278
|
+
lines.push("", "Feedback:");
|
|
279
|
+
for (const fb of r.feedback.slice(-5)) lines.push(` “${fb.note.slice(0, 200)}”`);
|
|
280
|
+
}
|
|
281
|
+
lines.push("", `Unused: ${r.unused.count} of ${r.unused.total} operations`);
|
|
282
|
+
return lines.join("\n");
|
|
283
|
+
});
|
|
284
|
+
return 0;
|
|
285
|
+
}
|
package/lib/commands/targets.mjs
CHANGED
|
@@ -44,6 +44,7 @@ const priority = { value: readPriority };
|
|
|
44
44
|
// `scripts/parity-check.ts` compares these keys against each tool's published input schema.
|
|
45
45
|
export const ARGS = {
|
|
46
46
|
list_ranking_targets: { offering_id: offering },
|
|
47
|
+
list_ranking_target_posts: { target_id: { positional: 0, label: "target-id" } },
|
|
47
48
|
add_ranking_target: {
|
|
48
49
|
// The phrase is every positional, so `targets add best payroll software` needs no quotes.
|
|
49
50
|
phrase: {
|
|
@@ -138,7 +139,8 @@ const USAGE = `Usage: targets <list|add|update|activate|deactivate|cap|suggest|s
|
|
|
138
139
|
targets suggest [--offering <offering-id|company>]
|
|
139
140
|
targets subqueries [<target-id>] [--retired] the phrasings asked under each target
|
|
140
141
|
targets subquery add <target-id> <phrasing>
|
|
141
|
-
targets subquery update <subquery-id> [--text <t>] [--retire | --restore]
|
|
142
|
+
targets subquery update <subquery-id> [--text <t>] [--retire | --restore]
|
|
143
|
+
targets posts [<target-id>]`;
|
|
142
144
|
|
|
143
145
|
export async function cmdTargets(ctx) {
|
|
144
146
|
const sub = ctx.positionals[0];
|
|
@@ -163,6 +165,8 @@ export async function cmdTargets(ctx) {
|
|
|
163
165
|
return subqueriesList(rest);
|
|
164
166
|
case "subquery":
|
|
165
167
|
return subqueryWrite(rest);
|
|
168
|
+
case "posts":
|
|
169
|
+
return targetsPosts(rest);
|
|
166
170
|
default:
|
|
167
171
|
throw new CliError(USAGE);
|
|
168
172
|
}
|
|
@@ -277,6 +281,31 @@ async function targetsCap(ctx) {
|
|
|
277
281
|
return 0;
|
|
278
282
|
}
|
|
279
283
|
|
|
284
|
+
function formatTargetPosts(r) {
|
|
285
|
+
if (!r.targets?.length) return "No active ranking targets.";
|
|
286
|
+
const lines = [];
|
|
287
|
+
for (const t of r.targets) {
|
|
288
|
+
lines.push(`${t.phrase}${t.queued ? ` (${t.queued} queued)` : ""}`);
|
|
289
|
+
if (!t.posts.length) lines.push(" No published posts yet.");
|
|
290
|
+
for (const p of t.posts) {
|
|
291
|
+
const why = p.writtenForTarget ? (p.subquery ? `for "${p.subquery}"` : "written for it") : "on its pillar";
|
|
292
|
+
lines.push(` ${p.title} [${why}] cited for ${p.citedQueries} · won ${p.breakthroughQueries}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
lines.push(`Cited and won: distinct queries, the last ${r.windowDays} days.`);
|
|
296
|
+
return lines.join("\n");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function targetsPosts(ctx) {
|
|
300
|
+
const { client, flags, io } = ctx;
|
|
301
|
+
const result = await client.callTool(
|
|
302
|
+
"list_ranking_target_posts",
|
|
303
|
+
argsFromFlags(ARGS.list_ranking_target_posts, flags, ctx.positionals)
|
|
304
|
+
);
|
|
305
|
+
printResult(io, flags, result, formatTargetPosts);
|
|
306
|
+
return 0;
|
|
307
|
+
}
|
|
308
|
+
|
|
280
309
|
async function targetsSuggest(ctx) {
|
|
281
310
|
const { client, flags, io } = ctx;
|
|
282
311
|
const result = await client.callTool("suggest_ranking_targets", argsFromFlags(ARGS.suggest_ranking_targets, flags));
|
package/lib/commands.mjs
CHANGED