@ishlabs/cli 0.12.2 → 0.14.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/dist/commands/chat-config.d.ts +23 -0
- package/dist/commands/chat-config.js +289 -0
- package/dist/commands/chat.js +26 -37
- package/dist/commands/iteration.js +219 -22
- package/dist/commands/profile.js +75 -9
- package/dist/commands/source.js +6 -4
- package/dist/commands/study-analyze.d.ts +41 -0
- package/dist/commands/study-analyze.js +187 -0
- package/dist/commands/study-run.js +359 -30
- package/dist/commands/study-screenshots.d.ts +20 -0
- package/dist/commands/study-screenshots.js +216 -0
- package/dist/commands/study.js +174 -9
- package/dist/commands/workspace.js +35 -2
- package/dist/lib/accessibility-profile.d.ts +12 -0
- package/dist/lib/accessibility-profile.js +136 -0
- package/dist/lib/alias-store.d.ts +1 -0
- package/dist/lib/alias-store.js +1 -0
- package/dist/lib/ask-questions.js +9 -0
- package/dist/lib/billing.d.ts +55 -0
- package/dist/lib/billing.js +77 -0
- package/dist/lib/command-helpers.d.ts +6 -0
- package/dist/lib/command-helpers.js +12 -0
- package/dist/lib/docs.js +1181 -38
- package/dist/lib/enums.d.ts +54 -0
- package/dist/lib/enums.js +100 -0
- package/dist/lib/local-sim/actions.d.ts +2 -1
- package/dist/lib/local-sim/actions.js +88 -13
- package/dist/lib/local-sim/loop.js +49 -19
- package/dist/lib/local-sim/tabs.d.ts +27 -0
- package/dist/lib/local-sim/tabs.js +157 -0
- package/dist/lib/local-sim/types.d.ts +15 -0
- package/dist/lib/modality.d.ts +70 -1
- package/dist/lib/modality.js +323 -17
- package/dist/lib/output.js +61 -4
- package/dist/lib/skill-content.js +397 -19
- package/dist/lib/types.d.ts +6 -1
- package/dist/lib/types.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ish chat config — Manage chatbot configurations (agent shape).
|
|
3
|
+
*
|
|
4
|
+
* A configuration captures *what the chatbot is* (model, system prompt,
|
|
5
|
+
* tools, sub-agents, custom {key, value} fields) — distinct from the
|
|
6
|
+
* *wire envelope* on a chatbot endpoint. Configurations live N-per-
|
|
7
|
+
* endpoint; chat iterations reference one and freeze a snapshot at
|
|
8
|
+
* creation time so historical comparisons stay reproducible.
|
|
9
|
+
*
|
|
10
|
+
* Four-verb surface mirroring `ish secret`: `list / set / get /
|
|
11
|
+
* delete`. `set` is the create-or-update upsert (composite write —
|
|
12
|
+
* mirrors MCP `chatbot_config_set` and the `chatbot_setup` precedent).
|
|
13
|
+
* `get --view iterations` surfaces the cross-study aggregation.
|
|
14
|
+
*/
|
|
15
|
+
import type { Command } from "commander";
|
|
16
|
+
interface CustomField {
|
|
17
|
+
key: string;
|
|
18
|
+
value: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function parseCustomField(raw: string): CustomField;
|
|
21
|
+
export declare function parseCustomFieldsFlag(values: string[] | undefined): CustomField[] | undefined;
|
|
22
|
+
export declare function attachChatConfigCommands(parent: Command): void;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ish chat config — Manage chatbot configurations (agent shape).
|
|
3
|
+
*
|
|
4
|
+
* A configuration captures *what the chatbot is* (model, system prompt,
|
|
5
|
+
* tools, sub-agents, custom {key, value} fields) — distinct from the
|
|
6
|
+
* *wire envelope* on a chatbot endpoint. Configurations live N-per-
|
|
7
|
+
* endpoint; chat iterations reference one and freeze a snapshot at
|
|
8
|
+
* creation time so historical comparisons stay reproducible.
|
|
9
|
+
*
|
|
10
|
+
* Four-verb surface mirroring `ish secret`: `list / set / get /
|
|
11
|
+
* delete`. `set` is the create-or-update upsert (composite write —
|
|
12
|
+
* mirrors MCP `chatbot_config_set` and the `chatbot_setup` precedent).
|
|
13
|
+
* `get --view iterations` surfaces the cross-study aggregation.
|
|
14
|
+
*/
|
|
15
|
+
import * as fs from "node:fs";
|
|
16
|
+
import { ApiError } from "../lib/api-client.js";
|
|
17
|
+
import { ALIAS_PREFIX, tagAlias } from "../lib/alias-store.js";
|
|
18
|
+
import { collectRepeatable, confirmDestructive, resolveChatConfig, resolveChatEndpoint, withClient, } from "../lib/command-helpers.js";
|
|
19
|
+
import { output } from "../lib/output.js";
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Helpers
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
export function parseCustomField(raw) {
|
|
24
|
+
const idx = raw.indexOf("=");
|
|
25
|
+
if (idx <= 0) {
|
|
26
|
+
throw new Error(`--custom-field must be \`key=value\` (got "${raw}").`);
|
|
27
|
+
}
|
|
28
|
+
return { key: raw.slice(0, idx).trim(), value: raw.slice(idx + 1) };
|
|
29
|
+
}
|
|
30
|
+
export function parseCustomFieldsFlag(values) {
|
|
31
|
+
if (values === undefined)
|
|
32
|
+
return undefined;
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const raw of values) {
|
|
36
|
+
const f = parseCustomField(raw);
|
|
37
|
+
if (!f.key)
|
|
38
|
+
throw new Error("--custom-field key cannot be empty.");
|
|
39
|
+
if (seen.has(f.key)) {
|
|
40
|
+
throw new Error(`--custom-field key "${f.key}" is repeated; keys must be unique.`);
|
|
41
|
+
}
|
|
42
|
+
seen.add(f.key);
|
|
43
|
+
out.push(f);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
function parseJsonListFlag(file, label) {
|
|
48
|
+
if (file === undefined)
|
|
49
|
+
return undefined;
|
|
50
|
+
const content = file === "-" ? fs.readFileSync(0, "utf-8") : fs.readFileSync(file, "utf-8");
|
|
51
|
+
let parsed;
|
|
52
|
+
try {
|
|
53
|
+
parsed = JSON.parse(content);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
57
|
+
throw new Error(`${label} is not valid JSON: ${m}`);
|
|
58
|
+
}
|
|
59
|
+
if (!Array.isArray(parsed))
|
|
60
|
+
throw new Error(`${label} must be a JSON array.`);
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
function readSystemPrompt(systemPrompt, systemPromptFile) {
|
|
64
|
+
if (systemPrompt !== undefined && systemPromptFile !== undefined) {
|
|
65
|
+
throw new Error("Pass at most one of --system-prompt or --system-prompt-file.");
|
|
66
|
+
}
|
|
67
|
+
if (systemPrompt !== undefined)
|
|
68
|
+
return systemPrompt;
|
|
69
|
+
if (systemPromptFile !== undefined) {
|
|
70
|
+
return systemPromptFile === "-"
|
|
71
|
+
? fs.readFileSync(0, "utf-8")
|
|
72
|
+
: fs.readFileSync(systemPromptFile, "utf-8");
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
function pruneUndefined(obj) {
|
|
77
|
+
const out = {};
|
|
78
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
79
|
+
if (v !== undefined)
|
|
80
|
+
out[k] = v;
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Commands
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
function attachList(parent) {
|
|
88
|
+
parent
|
|
89
|
+
.command("list")
|
|
90
|
+
.description("List configurations under a chatbot endpoint")
|
|
91
|
+
.option("--endpoint <id>", "Endpoint alias or UUID (defaults to active endpoint)")
|
|
92
|
+
.addHelpText("after", "\nExamples:\n $ ish chat config list\n $ ish chat config list --json | jq '.[] | {alias, name, isDefault, usageCount}'")
|
|
93
|
+
.action(async (opts, cmd) => {
|
|
94
|
+
await withClient(cmd, async (client, globals) => {
|
|
95
|
+
const epId = resolveChatEndpoint(undefined, opts.endpoint);
|
|
96
|
+
const rows = await client.get(`/chatbot-endpoints/${epId}/configurations`);
|
|
97
|
+
for (const row of rows) {
|
|
98
|
+
if (row.id)
|
|
99
|
+
tagAlias(ALIAS_PREFIX.chatConfig, row.id);
|
|
100
|
+
}
|
|
101
|
+
if (globals.json) {
|
|
102
|
+
output(rows, true);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (rows.length === 0) {
|
|
106
|
+
console.log("No configurations yet. Run `ish chat config set --name <name>` to author one.");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
for (const row of rows) {
|
|
110
|
+
const alias = row.id ? tagAlias(ALIAS_PREFIX.chatConfig, row.id) : "?";
|
|
111
|
+
const def = row.isDefault ? " [default]" : "";
|
|
112
|
+
const ver = row.versionLabel ? ` (${row.versionLabel})` : "";
|
|
113
|
+
const model = row.model ? ` model=${row.model}` : "";
|
|
114
|
+
const usage = ` used=${row.usageCount ?? 0}`;
|
|
115
|
+
console.log(`${alias} ${row.name}${ver}${def}${model}${usage}`);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function attachSet(parent) {
|
|
121
|
+
parent
|
|
122
|
+
.command("set")
|
|
123
|
+
.description("Create or update a chatbot configuration (composite upsert)")
|
|
124
|
+
.requiredOption("--name <name>", "Configuration name (unique per endpoint)")
|
|
125
|
+
.option("--endpoint <id>", "Endpoint alias or UUID (defaults to active endpoint; create only)")
|
|
126
|
+
.option("--config <id>", "Configuration alias or UUID — switches to update mode")
|
|
127
|
+
.option("--version-label <label>", "Optional version tag (e.g. v3, 1.2.0, git sha)")
|
|
128
|
+
.option("--description <text>", "Optional description")
|
|
129
|
+
.option("--model <id>", "Primary model identifier (e.g. claude-sonnet-4-6)")
|
|
130
|
+
.option("--system-prompt <text>", "System prompt text")
|
|
131
|
+
.option("--system-prompt-file <file>", 'Read system prompt from file (or "-" for stdin)')
|
|
132
|
+
.option("--tools-file <file>", 'JSON array of tools (or "-" for stdin)')
|
|
133
|
+
.option("--sub-agents-file <file>", 'JSON array of sub-agents (or "-" for stdin)')
|
|
134
|
+
.option("--custom-field <key=value>", "Custom field; repeat. Replaces all custom fields on update.", collectRepeatable)
|
|
135
|
+
.option("--default", "Mark as the endpoint's default configuration")
|
|
136
|
+
.addHelpText("after", `
|
|
137
|
+
Without --config, POSTs a new configuration under the endpoint. With
|
|
138
|
+
--config, PUTs an update. With --default, fires the set-default call
|
|
139
|
+
after the write so a single command leaves the endpoint with the
|
|
140
|
+
right default. Editing does NOT retroactively change historical
|
|
141
|
+
iterations — each iteration froze a snapshot at creation.
|
|
142
|
+
|
|
143
|
+
Examples:
|
|
144
|
+
$ ish chat config set --name v1-sonnet --model claude-sonnet-4-6 \\
|
|
145
|
+
--system-prompt-file ./prompt.txt --custom-field git_sha=abc --default
|
|
146
|
+
$ ish chat config set --config cc-abc --name v1.1-sonnet
|
|
147
|
+
$ cat tools.json | ish chat config set --name with-search --tools-file -`)
|
|
148
|
+
.action(async (opts, cmd) => {
|
|
149
|
+
await withClient(cmd, async (client, globals) => {
|
|
150
|
+
const cid = opts.config ? resolveChatConfig(undefined, opts.config) : null;
|
|
151
|
+
const body = pruneUndefined({
|
|
152
|
+
name: opts.name,
|
|
153
|
+
versionLabel: opts.versionLabel,
|
|
154
|
+
description: opts.description,
|
|
155
|
+
model: opts.model,
|
|
156
|
+
systemPrompt: readSystemPrompt(opts.systemPrompt, opts.systemPromptFile),
|
|
157
|
+
tools: parseJsonListFlag(opts.toolsFile, "--tools-file"),
|
|
158
|
+
subAgents: parseJsonListFlag(opts.subAgentsFile, "--sub-agents-file"),
|
|
159
|
+
customFields: parseCustomFieldsFlag(opts.customField),
|
|
160
|
+
});
|
|
161
|
+
let saved;
|
|
162
|
+
if (cid === null) {
|
|
163
|
+
const epId = resolveChatEndpoint(undefined, opts.endpoint);
|
|
164
|
+
if (opts.default)
|
|
165
|
+
body.isDefault = true;
|
|
166
|
+
saved = await client.post(`/chatbot-endpoints/${epId}/configurations`, body);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
saved = await client.put(`/chatbot-configurations/${cid}`, body);
|
|
170
|
+
if (opts.default) {
|
|
171
|
+
saved = await client.post(`/chatbot-configurations/${cid}/set-default`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (saved.id)
|
|
175
|
+
tagAlias(ALIAS_PREFIX.chatConfig, saved.id);
|
|
176
|
+
if (!globals.quiet) {
|
|
177
|
+
const action = cid === null ? "Created" : "Updated";
|
|
178
|
+
const tag = saved.isDefault ? " (default)" : "";
|
|
179
|
+
const alias = saved.id ? tagAlias(ALIAS_PREFIX.chatConfig, saved.id) : "?";
|
|
180
|
+
console.error(`${action} configuration ${alias}${tag}`);
|
|
181
|
+
}
|
|
182
|
+
output(saved, globals.json);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function attachGet(parent) {
|
|
187
|
+
parent
|
|
188
|
+
.command("get")
|
|
189
|
+
.description("Get a chatbot configuration (or its referencing iterations)")
|
|
190
|
+
.argument("[id]", "Configuration alias or UUID")
|
|
191
|
+
.option("--config <id>", "Configuration alias or UUID (alternative to positional)")
|
|
192
|
+
.option("--view <view>", "default | iterations. `iterations` returns the cross-study aggregation list.", "default")
|
|
193
|
+
.addHelpText("after", `
|
|
194
|
+
\`--view iterations\` returns every iteration referencing this
|
|
195
|
+
configuration across studies, joined to study metadata. Mirrors
|
|
196
|
+
\`study_get(view=...)\`.
|
|
197
|
+
|
|
198
|
+
Examples:
|
|
199
|
+
$ ish chat config get cc-abc
|
|
200
|
+
$ ish chat config get cc-abc --view iterations --json | jq 'group_by(.studyName) | length'`)
|
|
201
|
+
.action(async (id, opts, cmd) => {
|
|
202
|
+
await withClient(cmd, async (client, globals) => {
|
|
203
|
+
const cid = resolveChatConfig(id, opts.config);
|
|
204
|
+
if (opts.view === "iterations") {
|
|
205
|
+
const rows = await client.get(`/chatbot-configurations/${cid}/iterations`);
|
|
206
|
+
if (globals.json) {
|
|
207
|
+
output(rows, true);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (rows.length === 0) {
|
|
211
|
+
console.log("No iterations reference this configuration yet.");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
for (const r of rows) {
|
|
215
|
+
console.log(`${r.iterationLabel} ${r.studyName} — ${r.iterationName} (${r.createdAt})`);
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (opts.view !== "default") {
|
|
220
|
+
throw new Error(`--view must be 'default' or 'iterations' (got "${opts.view}").`);
|
|
221
|
+
}
|
|
222
|
+
const row = await client.get(`/chatbot-configurations/${cid}`);
|
|
223
|
+
if (row.id)
|
|
224
|
+
tagAlias(ALIAS_PREFIX.chatConfig, row.id);
|
|
225
|
+
output(row, globals.json);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
function attachDelete(parent) {
|
|
230
|
+
parent
|
|
231
|
+
.command("delete")
|
|
232
|
+
.description("Delete a chatbot configuration (rejected when iterations reference it)")
|
|
233
|
+
.argument("[id]", "Configuration alias or UUID")
|
|
234
|
+
.option("--config <id>", "Configuration alias or UUID (alternative to positional)")
|
|
235
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
236
|
+
.addHelpText("after", `
|
|
237
|
+
Returns HTTP 409 (configuration_in_use) when iterations reference the
|
|
238
|
+
configuration. Snapshots remain readable on those iterations but the
|
|
239
|
+
live cross-study aggregation link breaks. Inspect \`usageCount\`
|
|
240
|
+
via \`ish chat config get <id>\` first.
|
|
241
|
+
|
|
242
|
+
Examples:
|
|
243
|
+
$ ish chat config delete cc-abc --yes`)
|
|
244
|
+
.action(async (id, opts, cmd) => {
|
|
245
|
+
await withClient(cmd, async (client, globals) => {
|
|
246
|
+
const cid = resolveChatConfig(id, opts.config);
|
|
247
|
+
await confirmDestructive(`Delete chat configuration ${tagAlias(ALIAS_PREFIX.chatConfig, cid)}? This cannot be undone.`, { yes: opts.yes, json: globals.json });
|
|
248
|
+
try {
|
|
249
|
+
await client.del(`/chatbot-configurations/${cid}`);
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
if (err instanceof ApiError && err.status === 409) {
|
|
253
|
+
const detail = err.body;
|
|
254
|
+
const ids = detail?.detail?.iteration_ids ?? [];
|
|
255
|
+
const e = new Error(`Cannot delete: configuration is referenced by ${ids.length} iteration(s).`);
|
|
256
|
+
e.error_kind =
|
|
257
|
+
"configuration_in_use";
|
|
258
|
+
e.iteration_ids = ids;
|
|
259
|
+
throw e;
|
|
260
|
+
}
|
|
261
|
+
throw err;
|
|
262
|
+
}
|
|
263
|
+
output({
|
|
264
|
+
success: true,
|
|
265
|
+
deleted: true,
|
|
266
|
+
id: cid,
|
|
267
|
+
alias: tagAlias(ALIAS_PREFIX.chatConfig, cid),
|
|
268
|
+
}, globals.json, { writePath: true });
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
// Registration
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
export function attachChatConfigCommands(parent) {
|
|
276
|
+
const config = parent
|
|
277
|
+
.command("config")
|
|
278
|
+
.description("Manage chatbot configurations (agent shape: model, prompt, tools, sub-agents)")
|
|
279
|
+
.addHelpText("after", `
|
|
280
|
+
Configurations live under a chatbot endpoint and are referenced by chat
|
|
281
|
+
iterations. Each iteration freezes a snapshot at creation so editing a
|
|
282
|
+
configuration does NOT retroactively change historical iterations. Use
|
|
283
|
+
\`set --config <id>\` to update in place, or omit \`--config\` to create
|
|
284
|
+
a new configuration. Aliases use the \`cc-\` prefix.`);
|
|
285
|
+
attachList(config);
|
|
286
|
+
attachSet(config);
|
|
287
|
+
attachGet(config);
|
|
288
|
+
attachDelete(config);
|
|
289
|
+
}
|
package/dist/commands/chat.js
CHANGED
|
@@ -18,6 +18,7 @@ import { ApiError } from "../lib/api-client.js";
|
|
|
18
18
|
import { output } from "../lib/output.js";
|
|
19
19
|
import { formatChatEndpointList, formatChatEndpointDetail, envelopeFromRow, } from "../lib/chat-endpoint-formatters.js";
|
|
20
20
|
import { getChatEndpointTemplate, TEMPLATE_NAMES, } from "../lib/chat-endpoint-templates.js";
|
|
21
|
+
import { attachChatConfigCommands } from "./chat-config.js";
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
22
23
|
// Helpers
|
|
23
24
|
// ---------------------------------------------------------------------------
|
|
@@ -57,25 +58,6 @@ function urlLooksLocal(url) {
|
|
|
57
58
|
return false;
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
|
-
function inferredToConfig(inferred) {
|
|
61
|
-
const cfg = {
|
|
62
|
-
transport: inferred.transport,
|
|
63
|
-
outgoing: {
|
|
64
|
-
url: inferred.outgoing.url ?? undefined,
|
|
65
|
-
method: inferred.outgoing.method,
|
|
66
|
-
headers: inferred.outgoing.headers ?? {},
|
|
67
|
-
bodyTemplate: inferred.outgoing.bodyTemplate ?? {},
|
|
68
|
-
mode: inferred.outgoing.mode,
|
|
69
|
-
roleAliases: inferred.outgoing.roleAliases ?? {},
|
|
70
|
-
},
|
|
71
|
-
incoming: inferred.incoming,
|
|
72
|
-
asyncPoll: inferred.asyncPoll ?? null,
|
|
73
|
-
};
|
|
74
|
-
if (inferred.streaming) {
|
|
75
|
-
cfg.streaming = inferred.streaming;
|
|
76
|
-
}
|
|
77
|
-
return cfg;
|
|
78
|
-
}
|
|
79
61
|
async function tunnelGuard(client) {
|
|
80
62
|
try {
|
|
81
63
|
await client.get("/connect/active");
|
|
@@ -355,12 +337,12 @@ endpoint, apply the override, and PUT the merged result. Field flags win over
|
|
|
355
337
|
});
|
|
356
338
|
}
|
|
357
339
|
// ---------------------------------------------------------------------------
|
|
358
|
-
// init — auto-detect-
|
|
340
|
+
// init — test-and-map onboarding (auto-detect via /chat/test-and-map)
|
|
359
341
|
// ---------------------------------------------------------------------------
|
|
360
342
|
function attachChatEndpointInit(parent) {
|
|
361
343
|
parent
|
|
362
344
|
.command("init")
|
|
363
|
-
.description("Author an endpoint from a curl/JSON sample via
|
|
345
|
+
.description("Author an endpoint from a curl/JSON sample via test-and-map, or from a known-good template")
|
|
364
346
|
.option("--from-curl <file>", 'Path to a curl example file (or "-" for stdin)')
|
|
365
347
|
.option("--from-json <file>", 'Path to a JSON request/response sample (or "-" for stdin)')
|
|
366
348
|
.option("--template <name>", `Start from a known-good template (one of: ${TEMPLATE_NAMES.join(", ")})`)
|
|
@@ -446,22 +428,27 @@ Examples:
|
|
|
446
428
|
output(result, globals.json, { writePath: true });
|
|
447
429
|
return;
|
|
448
430
|
}
|
|
449
|
-
// Auto-detect path (curl or JSON paste).
|
|
431
|
+
// Auto-detect path (curl or JSON paste). Drives the same
|
|
432
|
+
// /test-and-map endpoint the editor uses, so the CLI and editor
|
|
433
|
+
// stay aligned on inference behaviour and error vocabulary.
|
|
450
434
|
const path = (opts.fromCurl ?? opts.fromJson);
|
|
451
435
|
const paste = await readFileOrStdin(path);
|
|
452
|
-
const
|
|
453
|
-
if (
|
|
436
|
+
const mapRes = await client.post(`/products/${ws}/chat/test-and-map`, { documentation: paste }, { timeout: 120_000 });
|
|
437
|
+
if (mapRes.kind === "failure") {
|
|
454
438
|
// Surface as a structured failure envelope on stdout AND throw so
|
|
455
439
|
// the wrapper sets a non-zero exit. The thrown Error carries the
|
|
456
|
-
//
|
|
457
|
-
const err = new Error(
|
|
458
|
-
err.error_kind =
|
|
440
|
+
// backend's error_kind for the agent to branch on.
|
|
441
|
+
const err = new Error(mapRes.errorMessage ?? "test-and-map failed.");
|
|
442
|
+
err.error_kind = mapRes.errorKind;
|
|
459
443
|
throw err;
|
|
460
444
|
}
|
|
461
|
-
const
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
445
|
+
const config = mapRes.inferredConfig;
|
|
446
|
+
const inferredUrl = (typeof config.outgoing?.url === "string" && config.outgoing.url
|
|
447
|
+
? config.outgoing.url
|
|
448
|
+
: null);
|
|
449
|
+
// Trust the backend's tunnel detection when present; fall back to
|
|
450
|
+
// the local heuristic only if the envelope didn't populate it.
|
|
451
|
+
const detectedTunnel = mapRes.tunnelBackedDetected ?? urlLooksLocal(inferredUrl);
|
|
465
452
|
let tunnelBacked;
|
|
466
453
|
if (opts.tunnelBacked === true)
|
|
467
454
|
tunnelBacked = true;
|
|
@@ -474,8 +461,9 @@ Examples:
|
|
|
474
461
|
if (!inferredUrl) {
|
|
475
462
|
warnings.push("Inferred shape has no URL; set --url before testing.");
|
|
476
463
|
}
|
|
477
|
-
|
|
478
|
-
|
|
464
|
+
const confidence = mapRes.confidence ?? null;
|
|
465
|
+
if (confidence && confidence !== "high") {
|
|
466
|
+
warnings.push(`Auto-detect confidence: ${confidence} — verify the shape before running.`);
|
|
479
467
|
}
|
|
480
468
|
// Decide whether to save. --no-save short-circuits; otherwise save when
|
|
481
469
|
// a name is available (--name wins; else fall back to the inferred
|
|
@@ -508,8 +496,8 @@ Examples:
|
|
|
508
496
|
}
|
|
509
497
|
}
|
|
510
498
|
}
|
|
511
|
-
const missingSignals = Array.isArray(
|
|
512
|
-
?
|
|
499
|
+
const missingSignals = Array.isArray(mapRes.missingSignals)
|
|
500
|
+
? mapRes.missingSignals
|
|
513
501
|
: [];
|
|
514
502
|
const result = {
|
|
515
503
|
success: true,
|
|
@@ -519,8 +507,8 @@ Examples:
|
|
|
519
507
|
config,
|
|
520
508
|
tunnel_backed: tunnelBacked,
|
|
521
509
|
tunnel_backed_detected: detectedTunnel,
|
|
522
|
-
confidence
|
|
523
|
-
explanation:
|
|
510
|
+
confidence,
|
|
511
|
+
explanation: mapRes.explanation ?? "",
|
|
524
512
|
missingSignals,
|
|
525
513
|
warnings,
|
|
526
514
|
};
|
|
@@ -714,6 +702,7 @@ mirrors that editing model.`);
|
|
|
714
702
|
attachChatEndpointInit(endpoint);
|
|
715
703
|
attachChatEndpointTest(endpoint);
|
|
716
704
|
attachChatEndpointMap(endpoint);
|
|
705
|
+
attachChatConfigCommands(chat);
|
|
717
706
|
}
|
|
718
707
|
// Re-exported for tests / external integration if needed.
|
|
719
708
|
export { envelopeFromRow };
|