@ishlabs/cli 0.9.0 → 0.10.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 +54 -5
- package/dist/commands/ask.d.ts +12 -0
- package/dist/commands/ask.js +127 -2
- package/dist/commands/chat.d.ts +17 -0
- package/dist/commands/chat.js +589 -0
- package/dist/commands/iteration.js +134 -14
- package/dist/commands/secret.d.ts +20 -0
- package/dist/commands/secret.js +246 -0
- package/dist/commands/study-run.d.ts +38 -0
- package/dist/commands/study-run.js +199 -80
- package/dist/commands/study-tester.js +17 -2
- package/dist/commands/study.js +309 -37
- package/dist/commands/workspace.js +81 -0
- package/dist/config.d.ts +3 -0
- package/dist/connect.d.ts +3 -0
- package/dist/connect.js +346 -22
- package/dist/index.js +64 -6
- package/dist/lib/alias-hydrate.d.ts +42 -0
- package/dist/lib/alias-hydrate.js +175 -0
- package/dist/lib/alias-store.d.ts +1 -0
- package/dist/lib/alias-store.js +28 -1
- package/dist/lib/auth.js +4 -2
- package/dist/lib/chat-endpoint-formatters.d.ts +39 -0
- package/dist/lib/chat-endpoint-formatters.js +104 -0
- package/dist/lib/command-helpers.d.ts +18 -0
- package/dist/lib/command-helpers.js +105 -3
- package/dist/lib/docs.js +542 -17
- package/dist/lib/modality.d.ts +42 -0
- package/dist/lib/modality.js +192 -0
- package/dist/lib/output.d.ts +41 -0
- package/dist/lib/output.js +453 -19
- package/dist/lib/paths.d.ts +1 -0
- package/dist/lib/paths.js +3 -0
- package/dist/lib/skill-content.js +182 -12
- package/dist/lib/types.d.ts +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,7 +88,7 @@ Workspace (= product, top-level container)
|
|
|
88
88
|
```bash
|
|
89
89
|
ish login # browser auth
|
|
90
90
|
ish logout
|
|
91
|
-
ish connect <port> # Cloudflare tunnel exposing localhost
|
|
91
|
+
ish connect <port> # Cloudflare tunnel exposing localhost (--detach, ish disconnect, ish connect status)
|
|
92
92
|
ish upgrade # self-update (single-binary installs only)
|
|
93
93
|
ish upgrade --release 0.8.1 # pin a specific release
|
|
94
94
|
```
|
|
@@ -98,15 +98,21 @@ ish upgrade --release 0.8.1 # pin a specific release
|
|
|
98
98
|
### Workspaces, studies, iterations, profiles, configs (CRUD groups)
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
|
-
ish workspace list | create | get | update | delete | use
|
|
101
|
+
ish workspace list | create | get | update | delete | use | info
|
|
102
102
|
ish workspace site-access status | basic-auth | cookie | login | affirm-public | clear
|
|
103
103
|
ish study list | create | generate | get | results | update | delete | use
|
|
104
104
|
ish iteration list | create | get | update | delete
|
|
105
105
|
ish profile list | create | generate | get | update | delete
|
|
106
106
|
ish source upload | get | delete
|
|
107
107
|
ish config list | create | get | schema | update | delete
|
|
108
|
+
ish chat endpoint list | create | get | update | delete | use | init | test
|
|
109
|
+
ish secret list | set | delete
|
|
108
110
|
```
|
|
109
111
|
|
|
112
|
+
`ish workspace info` reports `studies_used / studies_max / testers_used / testers_max / tier` so an agent can branch on plan caps before a destructive call returns `error_code: usage_limit_reached`.
|
|
113
|
+
|
|
114
|
+
`ish chat endpoint` configures HTTP-bot endpoints for chat-modality studies (auto-detect from a curl example, smoke-test, edit). `ish secret` is the per-workspace KV store referenced from chatbot endpoint headers via `{{secret:KEY}}` placeholders. Run `ish docs get-page guides/chat` for the end-to-end recipe.
|
|
115
|
+
|
|
110
116
|
Testers live as a nested group on a study (low-level — usually created via `study run`):
|
|
111
117
|
|
|
112
118
|
```bash
|
|
@@ -347,16 +353,59 @@ ish profile generate --source tps-3a4 --propose-count
|
|
|
347
353
|
ish profile generate --source tps-3a4 --count 4
|
|
348
354
|
```
|
|
349
355
|
|
|
356
|
+
### Chat-modality studies — `ish chat`
|
|
357
|
+
|
|
358
|
+
Configure a customer chatbot endpoint and run chat-modality studies against it.
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
# Author from a curl example (or hand-write the config)
|
|
362
|
+
ish chat endpoint init --from-curl ./bot.curl --name my-bot
|
|
363
|
+
ish chat endpoint create --endpoint-config ./bot-config.json --name "my-bot"
|
|
364
|
+
|
|
365
|
+
# CRUD on saved endpoints (every dialog edit reduces to one of these)
|
|
366
|
+
ish chat endpoint list
|
|
367
|
+
ish chat endpoint get ep-abc --verbose # round-trippable {id, name, isTunnelBacked, config}
|
|
368
|
+
ish chat endpoint update ep-abc --name "Production support bot"
|
|
369
|
+
ish chat endpoint update ep-abc --url https://api.example.com/v2/chat --mode stateless
|
|
370
|
+
ish chat endpoint get ep-abc --verbose | jq '.config.outgoing.headers["X-API-Key"] = "{{secret:KEY}}"' \
|
|
371
|
+
| ish chat endpoint update ep-abc --endpoint-config -
|
|
372
|
+
ish chat endpoint delete ep-abc
|
|
373
|
+
ish chat endpoint use ep-abc # set as the active chat endpoint
|
|
374
|
+
|
|
375
|
+
# Smoke test the connection (single turn; tunnel pre-flight when applicable)
|
|
376
|
+
ish chat endpoint test ep-abc -m "Hello"
|
|
377
|
+
ish chat endpoint test ep-abc -m "Tell me more" --conversation-id "$CID" # stateful threading
|
|
378
|
+
|
|
379
|
+
# Run a chat-modality study using the saved endpoint (existing study verbs).
|
|
380
|
+
# Audience size lives on study run via --sample / --all / --profile.
|
|
381
|
+
ish study create --modality chat --endpoint ep-abc --name "Sign-up Q1" --assignment "Sign up:Try to sign up"
|
|
382
|
+
ish study run --study stu-xyz --sample 5 --wait
|
|
383
|
+
ish study results stu-xyz --json | jq '.testers'
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Local bots (`localhost` / `127.0.0.1` / `0.0.0.0`) auto-flag `is_tunnel_backed=true` on `init`; pair with `ish connect <port>` in another shell. Override with `--tunnel-backed` / `--no-tunnel-backed`.
|
|
387
|
+
|
|
388
|
+
`init` returns `confidence` (`high` / `medium` / `low`) and a `missingSignals: [...]` array naming any inputs the inference couldn't observe (e.g. `["response_shape", "message_path"]` when no response sample is provided). When confidence is `low`, verify with `chat endpoint test` before running a study.
|
|
389
|
+
|
|
390
|
+
Failures from `chat endpoint test` carry a structured `error_kind`: `TunnelInactive` (run `ish connect <port>` first), `BotUnreachable` (URL/port wrong or bot down), `BotResponseError` (non-2xx with a status code), `BotEnvelopeError` (200 OK with the bot's own error in the body — see `raw_excerpt`), `BotInvalidResponseError` (response doesn't match the parsing schema), `BotAuthError`, `BotTimeoutError`, `BotRetryExhaustedError`.
|
|
391
|
+
|
|
392
|
+
Full guide: `ish docs get-page guides/chat`.
|
|
393
|
+
|
|
350
394
|
### Expose localhost
|
|
351
395
|
|
|
352
|
-
For interactive studies that need to reach a service running on your machine:
|
|
396
|
+
For interactive studies (and chat endpoints with `is_tunnel_backed=true`) that need to reach a service running on your machine:
|
|
353
397
|
|
|
354
398
|
```bash
|
|
355
|
-
ish connect 3000
|
|
399
|
+
ish connect 3000 # foreground Cloudflare tunnel to :3000
|
|
400
|
+
ish connect 3000 --detach --json # fork after first heartbeat; prints {pid, tunnel_url, registered}
|
|
401
|
+
ish connect status --json # {active, pid, tunnel_url, registered_at} or {active:false}
|
|
402
|
+
ish disconnect --json # graceful shutdown of an active tunnel
|
|
356
403
|
ISH_TOKEN=YOUR_TOKEN ish connect 8080
|
|
357
404
|
```
|
|
358
405
|
|
|
359
|
-
`connect` is
|
|
406
|
+
Foreground `connect` is long-running — keep it open while testers run. The tunnel URL prints prominently after "Connected"; pass `--json` for one-line machine-readable output (`{"status":"connected","tunnel_url":"...","local_port":3000,"registered":true}`). The `--detach` form forks after the first successful heartbeat and returns immediately, tracking PID + URL in `~/.ish/connect.lock` so `connect status` and `disconnect` find it later.
|
|
407
|
+
|
|
408
|
+
Destructive verbs in `--json` mode (e.g. `chat endpoint delete`, `study delete`) require an explicit `--yes`; the rejection envelope carries `error_kind: "ConfirmationRequired"` and an `example` field with the same command + `--yes` appended, so an agent can recover without re-reading the help text.
|
|
360
409
|
|
|
361
410
|
## Global flags
|
|
362
411
|
|
package/dist/commands/ask.d.ts
CHANGED
|
@@ -2,4 +2,16 @@
|
|
|
2
2
|
* ish ask — Create and run asks (multi-round surveys with variants).
|
|
3
3
|
*/
|
|
4
4
|
import type { Command } from "commander";
|
|
5
|
+
import type { AudienceSubset } from "../lib/types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Parse the `--subset-round <n> --subset-variant <variant_id>` pair into
|
|
8
|
+
* an `AudienceSubset` payload (Pattern B). Both flags must be passed
|
|
9
|
+
* together or neither — half a subset is a misconfiguration the agent
|
|
10
|
+
* should fix before dispatch, not a silent fallthrough to the full
|
|
11
|
+
* audience.
|
|
12
|
+
*
|
|
13
|
+
* Returns `undefined` when neither flag is set; throws when only one is
|
|
14
|
+
* set or when `--subset-round` isn't a positive integer.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseAudienceSubset(subsetRound: string | undefined, subsetVariant: string | undefined): AudienceSubset | undefined;
|
|
5
17
|
export declare function registerAskCommands(program: Command): void;
|
package/dist/commands/ask.js
CHANGED
|
@@ -7,6 +7,7 @@ import { loadConfig, saveConfig } from "../config.js";
|
|
|
7
7
|
import { formatAskList, formatAskDetail, formatRoundDetail, formatAskResults, output, } from "../lib/output.js";
|
|
8
8
|
import { parseVariantInputs, uploadAndBuildVariants, } from "../lib/ask-variants.js";
|
|
9
9
|
import { loadQuestionsManifest } from "../lib/ask-questions.js";
|
|
10
|
+
import { ApiError } from "../lib/api-client.js";
|
|
10
11
|
const POLL_INTERVAL_MS = 5_000;
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
12
13
|
// Helpers
|
|
@@ -108,6 +109,32 @@ async function buildRoundInput(client, productId, opts, quiet) {
|
|
|
108
109
|
round.questions = questions;
|
|
109
110
|
return round;
|
|
110
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Parse the `--subset-round <n> --subset-variant <variant_id>` pair into
|
|
114
|
+
* an `AudienceSubset` payload (Pattern B). Both flags must be passed
|
|
115
|
+
* together or neither — half a subset is a misconfiguration the agent
|
|
116
|
+
* should fix before dispatch, not a silent fallthrough to the full
|
|
117
|
+
* audience.
|
|
118
|
+
*
|
|
119
|
+
* Returns `undefined` when neither flag is set; throws when only one is
|
|
120
|
+
* set or when `--subset-round` isn't a positive integer.
|
|
121
|
+
*/
|
|
122
|
+
export function parseAudienceSubset(subsetRound, subsetVariant) {
|
|
123
|
+
if (subsetRound === undefined && subsetVariant === undefined)
|
|
124
|
+
return undefined;
|
|
125
|
+
if (subsetRound === undefined || subsetVariant === undefined) {
|
|
126
|
+
throw new Error("--subset-round and --subset-variant must be passed together (or both omitted).");
|
|
127
|
+
}
|
|
128
|
+
const round = Number.parseInt(subsetRound, 10);
|
|
129
|
+
if (!Number.isFinite(round) || round < 1 || !/^\d+$/.test(subsetRound)) {
|
|
130
|
+
throw new Error(`--subset-round must be a positive integer (got "${subsetRound}").`);
|
|
131
|
+
}
|
|
132
|
+
const trimmedVariant = subsetVariant.trim();
|
|
133
|
+
if (trimmedVariant.length === 0) {
|
|
134
|
+
throw new Error("--subset-variant must be a variant UUID (got empty string).");
|
|
135
|
+
}
|
|
136
|
+
return { round, picked_variant_id: trimmedVariant };
|
|
137
|
+
}
|
|
111
138
|
// ---------------------------------------------------------------------------
|
|
112
139
|
// Command registration
|
|
113
140
|
// ---------------------------------------------------------------------------
|
|
@@ -145,6 +172,8 @@ Concept pages: ish docs get-page concepts/ask
|
|
|
145
172
|
allFlagName: "--all-simulatable",
|
|
146
173
|
allFlagDescription: "Use every simulatable AI profile matching the filters (with --new only)",
|
|
147
174
|
})
|
|
175
|
+
.option("--subset-round <n>", "Drill-in subset (Pattern B) — append-round only. 1-indexed prior round to filter against. Pair with --subset-variant.")
|
|
176
|
+
.option("--subset-variant <variant_id>", "Drill-in subset (Pattern B) — append-round only. Variant id (UUID) on the prior round whose pickers should inherit. Read from `aggregates.pick_buckets` or `variants[*].id` on the prior round's `ask results --json`.")
|
|
148
177
|
.option("--wait", "Wait until the round completes (or errors)")
|
|
149
178
|
.option("--timeout <s>", "Wait timeout in seconds (default 300)")
|
|
150
179
|
.addHelpText("after", `
|
|
@@ -169,6 +198,9 @@ Examples:
|
|
|
169
198
|
if (pickedId) {
|
|
170
199
|
throw new Error("Cannot pass an ask id together with --new. Drop the id, or drop --new to append a round.");
|
|
171
200
|
}
|
|
201
|
+
if (opts.subsetRound !== undefined || opts.subsetVariant !== undefined) {
|
|
202
|
+
throw new Error("--subset-round / --subset-variant are only valid when appending to an existing ask. Drop --new or drop the subset flags.");
|
|
203
|
+
}
|
|
172
204
|
const wid = resolveWorkspace(opts.workspace);
|
|
173
205
|
const testerIds = await resolveAudienceProfileIds(client, wid, audienceFlags(opts), { requireSimulatable: true, allFlagName: "--all-simulatable" });
|
|
174
206
|
const round = await buildRoundInput(client, wid, opts, !!globals.quiet);
|
|
@@ -180,7 +212,28 @@ Examples:
|
|
|
180
212
|
tester_profile_ids: testerIds,
|
|
181
213
|
first_round: round,
|
|
182
214
|
};
|
|
183
|
-
|
|
215
|
+
// M5 / Pattern G: `ask run --new` POSTs to a non-idempotent
|
|
216
|
+
// create endpoint. If the backend errors after the row is
|
|
217
|
+
// committed (a 500 mid-pipeline, a network timeout after the
|
|
218
|
+
// POST landed), an automatic retry would create a duplicate
|
|
219
|
+
// ask. Override `retryable` to false on any failure here so
|
|
220
|
+
// agents don't auto-retry. The error envelope also reminds
|
|
221
|
+
// the agent to inspect `ish ask list --workspace <id>` before
|
|
222
|
+
// re-running, since the resource may already exist.
|
|
223
|
+
let data;
|
|
224
|
+
try {
|
|
225
|
+
data = await client.post(`/products/${wid}/asks`, body, { timeout: 120_000 });
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
if (err instanceof ApiError) {
|
|
229
|
+
err.retryable = false;
|
|
230
|
+
const tagged = err;
|
|
231
|
+
tagged.suggestions = [
|
|
232
|
+
`\`ish ask list --workspace ${wid}\` to check whether the ask was created server-side before retrying — \`ask run --new\` is non-idempotent and will duplicate on retry.`,
|
|
233
|
+
];
|
|
234
|
+
}
|
|
235
|
+
throw err;
|
|
236
|
+
}
|
|
184
237
|
if (data.id) {
|
|
185
238
|
const config = loadConfig();
|
|
186
239
|
config.ask = data.id;
|
|
@@ -223,6 +276,9 @@ Examples:
|
|
|
223
276
|
}
|
|
224
277
|
const ask = await client.get(`/asks/${aid}`);
|
|
225
278
|
const round = await buildRoundInput(client, ask.product_id, opts, !!globals.quiet);
|
|
279
|
+
const subset = parseAudienceSubset(opts.subsetRound, opts.subsetVariant);
|
|
280
|
+
if (subset)
|
|
281
|
+
round.audience_subset = subset;
|
|
226
282
|
const created = await client.post(`/asks/${aid}/rounds`, round);
|
|
227
283
|
if (opts.wait) {
|
|
228
284
|
const timeoutMs = parseWaitTimeout(opts.timeout);
|
|
@@ -519,14 +575,32 @@ the model's self-reported confidence in its variant choice. See
|
|
|
519
575
|
.option("--wants-pick", "Each tester picks a favourite variant (compatible with --wants-ratings; can be set together).")
|
|
520
576
|
.option("--wants-ratings", "Each tester rates every variant 1–5 (compatible with --wants-pick; can be set together). If neither is set, testers leave a free-form comment only.")
|
|
521
577
|
.option("--questions <file.json>", `Questions JSON file: [{"question":"...","type":"text"|"slider"|"likert"|"single-choice"|"multiple-choice"|"number"}]`)
|
|
578
|
+
.option("--subset-round <n>", "Drill-in subset (Pattern B) — 1-indexed prior round to filter against. Pair with --subset-variant. The new round dispatches only to testers who picked --subset-variant on round N.")
|
|
579
|
+
.option("--subset-variant <variant_id>", "Drill-in subset (Pattern B) — variant id (UUID) on the prior round whose pickers should inherit. Pair with --subset-round. Read from `aggregates.pick_buckets` or `variants[*].id` on the prior round.")
|
|
522
580
|
.option("--wait", "Wait until the new round completes")
|
|
523
581
|
.option("--timeout <s>", "Wait timeout in seconds (default 300)")
|
|
524
|
-
.addHelpText("after",
|
|
582
|
+
.addHelpText("after", `
|
|
583
|
+
Examples:
|
|
584
|
+
# Append round 2 to the same audience.
|
|
585
|
+
$ ish ask add-round a-6ec --prompt "And now?" --variant text:"Hello" --variant text:"Hi" --wait
|
|
586
|
+
|
|
587
|
+
# Drill round 2 into the round-1-A-pickers (Pattern B).
|
|
588
|
+
$ ish ask add-round a-6ec \\
|
|
589
|
+
--prompt "What would make you actually click?" \\
|
|
590
|
+
--subset-round 1 --subset-variant 5f3a... \\
|
|
591
|
+
--wait
|
|
592
|
+
|
|
593
|
+
If --subset-round / --subset-variant fails to resolve (round missing, variant
|
|
594
|
+
not on that round, or zero pickers), the backend returns a 422 with
|
|
595
|
+
error_kind: "audience_subset_invalid".`)
|
|
525
596
|
.action(async (id, opts, cmd) => {
|
|
526
597
|
await withClient(cmd, async (client, globals) => {
|
|
527
598
|
const aid = resolveAsk(pickAskRef(id, opts.ask));
|
|
528
599
|
const ask = await client.get(`/asks/${aid}`);
|
|
529
600
|
const round = await buildRoundInput(client, ask.product_id, opts, !!globals.quiet);
|
|
601
|
+
const subset = parseAudienceSubset(opts.subsetRound, opts.subsetVariant);
|
|
602
|
+
if (subset)
|
|
603
|
+
round.audience_subset = subset;
|
|
530
604
|
const created = await client.post(`/asks/${aid}/rounds`, round);
|
|
531
605
|
if (opts.wait) {
|
|
532
606
|
const timeoutMs = parseWaitTimeout(opts.timeout);
|
|
@@ -607,6 +681,57 @@ text, slider, likert, single-choice, multiple-choice, number.`)
|
|
|
607
681
|
}, globals.json);
|
|
608
682
|
});
|
|
609
683
|
});
|
|
684
|
+
// ---- retry --------------------------------------------------------------
|
|
685
|
+
ask
|
|
686
|
+
.command("retry")
|
|
687
|
+
.description("Re-dispatch only the errored responses on a round (idempotent: zero-errored is a no-op).")
|
|
688
|
+
.argument("[id]", "Ask alias or UUID (defaults to active ask)")
|
|
689
|
+
.option("--ask <id>", "Ask ID; alternative to positional argument")
|
|
690
|
+
.requiredOption("--round <n|round-id>", "Round number (1-indexed) or round id/alias")
|
|
691
|
+
.option("--wait", "Wait until the retried round completes (or errors)")
|
|
692
|
+
.option("--timeout <s>", "Wait timeout in seconds (default 300)")
|
|
693
|
+
.addHelpText("after", `
|
|
694
|
+
Examples:
|
|
695
|
+
# Retry the errored 4 of 5 testers on round 1.
|
|
696
|
+
$ ish ask retry a-d3e --round 1
|
|
697
|
+
|
|
698
|
+
# Retry and wait for the round to settle.
|
|
699
|
+
$ ish ask retry a-d3e --round 1 --wait
|
|
700
|
+
|
|
701
|
+
Notes:
|
|
702
|
+
- COMPLETED responses are left untouched. Only ERRORED rows are reset to PENDING and re-run from scratch.
|
|
703
|
+
- The round flips back to RUNNING for the duration of the retry; the prior round summary is dropped and rebuilt once the retry settles.
|
|
704
|
+
- On a round with no errored responses, the verb is a no-op and returns the round unchanged.`)
|
|
705
|
+
.action(async (id, opts, cmd) => {
|
|
706
|
+
await withClient(cmd, async (client, globals) => {
|
|
707
|
+
const aid = resolveAsk(pickAskRef(id, opts.ask));
|
|
708
|
+
const ask = await client.get(`/asks/${aid}`);
|
|
709
|
+
const round = getRoundByIndexOrId(ask, opts.round);
|
|
710
|
+
const updated = await client.post(`/asks/${aid}/rounds/${round.id}/retry`, {});
|
|
711
|
+
if (opts.wait) {
|
|
712
|
+
const timeoutMs = parseWaitTimeout(opts.timeout);
|
|
713
|
+
await pollUntilRoundDone(client, aid, updated.order_index, timeoutMs, !!globals.quiet);
|
|
714
|
+
const refreshed = await client.get(`/asks/${aid}`);
|
|
715
|
+
const target = refreshed.rounds.find((r) => r.id === updated.id);
|
|
716
|
+
if (target) {
|
|
717
|
+
formatRoundDetail(target, globals.json);
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (!globals.json || globals.verbose) {
|
|
722
|
+
formatRoundDetail(updated, globals.json);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
output({
|
|
726
|
+
id: aid,
|
|
727
|
+
alias: tagAlias(ALIAS_PREFIX.ask, aid),
|
|
728
|
+
round: {
|
|
729
|
+
round_number: updated.order_index + 1,
|
|
730
|
+
status: updated.status,
|
|
731
|
+
},
|
|
732
|
+
}, globals.json);
|
|
733
|
+
});
|
|
734
|
+
});
|
|
610
735
|
// ---- add-testers --------------------------------------------------------
|
|
611
736
|
const askAddTesters = ask
|
|
612
737
|
.command("add-testers")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ish chat — Configure chatbot endpoints and run chat-modality studies.
|
|
3
|
+
*
|
|
4
|
+
* The CLI's primary user is autonomous AI agents. Every verb here is
|
|
5
|
+
* scriptable: deterministic JSON outputs, no interactive prompts, no
|
|
6
|
+
* REPLs. Endpoint editing matches the editor dialog's semantics
|
|
7
|
+
* (full-replace via PUT) plus client-side field-shorthand flags for
|
|
8
|
+
* common one-line edits.
|
|
9
|
+
*
|
|
10
|
+
* Chat-modality studies are reached via the existing `ish study create
|
|
11
|
+
* --modality chat --endpoint <id>` extension; this file does NOT
|
|
12
|
+
* fork a parallel `chat run` verb tree.
|
|
13
|
+
*/
|
|
14
|
+
import type { Command } from "commander";
|
|
15
|
+
import { envelopeFromRow } from "../lib/chat-endpoint-formatters.js";
|
|
16
|
+
export declare function registerChatCommand(program: Command): void;
|
|
17
|
+
export { envelopeFromRow };
|