@ishlabs/cli 0.17.7 → 0.19.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 -54
- package/dist/commands/ask.d.ts +4 -4
- package/dist/commands/ask.js +66 -66
- package/dist/commands/chat.js +10 -10
- package/dist/commands/config.js +1 -1
- package/dist/commands/docs.js +1 -1
- package/dist/commands/iteration.js +57 -57
- package/dist/commands/mcp.d.ts +23 -0
- package/dist/commands/mcp.js +676 -0
- package/dist/commands/person.d.ts +5 -0
- package/dist/commands/{profile.js → person.js} +197 -162
- package/dist/commands/source.d.ts +6 -2
- package/dist/commands/source.js +35 -30
- package/dist/commands/study-analyze.d.ts +1 -1
- package/dist/commands/study-analyze.js +3 -3
- package/dist/commands/study-participant.d.ts +8 -0
- package/dist/commands/{study-tester.js → study-participant.js} +50 -50
- package/dist/commands/study-run.d.ts +6 -6
- package/dist/commands/study-run.js +341 -290
- package/dist/commands/study.js +106 -72
- package/dist/commands/workspace.js +13 -13
- package/dist/connect.js +5 -5
- package/dist/index.js +6 -4
- package/dist/lib/accessibility-profile.d.ts +1 -1
- package/dist/lib/accessibility-profile.js +1 -1
- package/dist/lib/alias-hydrate.js +4 -4
- package/dist/lib/alias-store.d.ts +5 -5
- package/dist/lib/alias-store.js +8 -8
- package/dist/lib/api-client.d.ts +1 -1
- package/dist/lib/api-client.js +1 -1
- package/dist/lib/billing.d.ts +11 -11
- package/dist/lib/billing.js +16 -16
- package/dist/lib/chat-endpoint-templates.js +1 -1
- package/dist/lib/command-helpers.d.ts +18 -18
- package/dist/lib/command-helpers.js +49 -37
- package/dist/lib/docs.js +570 -387
- package/dist/lib/enums.d.ts +2 -2
- package/dist/lib/enums.js +2 -2
- package/dist/lib/local-sim/browser.d.ts +1 -1
- package/dist/lib/local-sim/browser.js +1 -1
- package/dist/lib/local-sim/debug-report.d.ts +2 -2
- package/dist/lib/local-sim/debug-report.js +3 -3
- package/dist/lib/local-sim/loop.d.ts +5 -5
- package/dist/lib/local-sim/loop.js +38 -38
- package/dist/lib/local-sim/types.d.ts +12 -12
- package/dist/lib/mcp-clients.d.ts +51 -0
- package/dist/lib/mcp-clients.js +175 -0
- package/dist/lib/modality.d.ts +10 -10
- package/dist/lib/modality.js +46 -46
- package/dist/lib/output.d.ts +16 -15
- package/dist/lib/output.js +291 -226
- package/dist/lib/profile-sources.d.ts +64 -16
- package/dist/lib/profile-sources.js +91 -30
- package/dist/lib/skill-content.js +216 -168
- package/dist/lib/study-events.d.ts +3 -3
- package/dist/lib/study-events.js +1 -1
- package/dist/lib/study-inputs.d.ts +11 -1
- package/dist/lib/study-inputs.js +68 -17
- package/dist/lib/study-participants.d.ts +32 -0
- package/dist/lib/study-participants.js +12 -0
- package/dist/lib/types.d.ts +104 -34
- package/package.json +1 -1
- package/dist/commands/profile.d.ts +0 -5
- package/dist/commands/study-tester.d.ts +0 -8
package/dist/commands/ask.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ish ask — Create and run asks (multi-round surveys with variants).
|
|
3
3
|
*/
|
|
4
|
-
import { withClient, getWebUrl, terminalLink, resolveWorkspace, resolveAsk, collectRepeatable, parseWaitTimeout,
|
|
4
|
+
import { withClient, getWebUrl, terminalLink, resolveWorkspace, resolveAsk, collectRepeatable, parseWaitTimeout, resolvePersonIds, addPersonFilterFlags, } from "../lib/command-helpers.js";
|
|
5
5
|
import { resolveId, tagAlias, ALIAS_PREFIX } from "../lib/alias-store.js";
|
|
6
6
|
import { loadConfig, saveConfig } from "../config.js";
|
|
7
7
|
import { formatAskList, formatAskDetail, formatRoundDetail, formatAskResults, output, } from "../lib/output.js";
|
|
@@ -12,10 +12,10 @@ const POLL_INTERVAL_MS = 5_000;
|
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
// Helpers
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
|
-
/** Map raw Commander opts (with `allSimulatable`) to the shared
|
|
16
|
-
function
|
|
15
|
+
/** Map raw Commander opts (with `allSimulatable`) to the shared PersonFilterOpts shape. */
|
|
16
|
+
function personFlags(opts) {
|
|
17
17
|
return {
|
|
18
|
-
|
|
18
|
+
person: opts.person,
|
|
19
19
|
sample: opts.sample,
|
|
20
20
|
all: opts.allSimulatable,
|
|
21
21
|
search: opts.search,
|
|
@@ -113,15 +113,15 @@ async function buildRoundInput(client, productId, opts, quiet) {
|
|
|
113
113
|
}
|
|
114
114
|
/**
|
|
115
115
|
* Parse the `--subset-round <n> --subset-variant <variant_id>` pair into
|
|
116
|
-
* an `
|
|
116
|
+
* an `ParticipantSubset` payload (Pattern B). Both flags must be passed
|
|
117
117
|
* together or neither — half a subset is a misconfiguration the agent
|
|
118
118
|
* should fix before dispatch, not a silent fallthrough to the full
|
|
119
|
-
*
|
|
119
|
+
* participant list.
|
|
120
120
|
*
|
|
121
121
|
* Returns `undefined` when neither flag is set; throws when only one is
|
|
122
122
|
* set or when `--subset-round` isn't a positive integer.
|
|
123
123
|
*/
|
|
124
|
-
export function
|
|
124
|
+
export function parseParticipantSubset(subsetRound, subsetVariant) {
|
|
125
125
|
if (subsetRound === undefined && subsetVariant === undefined)
|
|
126
126
|
return undefined;
|
|
127
127
|
if (subsetRound === undefined || subsetVariant === undefined) {
|
|
@@ -143,10 +143,10 @@ export function parseAudienceSubset(subsetRound, subsetVariant) {
|
|
|
143
143
|
export function registerAskCommands(program) {
|
|
144
144
|
const ask = program
|
|
145
145
|
.command("ask")
|
|
146
|
-
.description("Create and run asks — quick variant comparisons with
|
|
146
|
+
.description("Create and run asks — quick variant comparisons with people")
|
|
147
147
|
.addHelpText("after", `
|
|
148
|
-
An ask is a lightweight reaction artifact.
|
|
149
|
-
Reach for an ask for tagline/image/copy comparisons; reach for a study when the
|
|
148
|
+
An ask is a lightweight reaction artifact. Participants are fixed at creation; up to 5 rounds per ask.
|
|
149
|
+
Reach for an ask for tagline/image/copy comparisons; reach for a study when the participant must do something.
|
|
150
150
|
|
|
151
151
|
Concept pages: ish docs get-page concepts/ask
|
|
152
152
|
ish docs get-page concepts/round
|
|
@@ -163,16 +163,16 @@ Concept pages: ish docs get-page concepts/ask
|
|
|
163
163
|
.option("--name <name>", "Ask name (with --new; auto-generated if omitted)")
|
|
164
164
|
.option("--description <description>", "Ask description (with --new only)")
|
|
165
165
|
.option("--workspace <id>", "Workspace ID (with --new only)")
|
|
166
|
-
.requiredOption("--prompt <prompt>", "Round prompt — what
|
|
166
|
+
.requiredOption("--prompt <prompt>", "Round prompt — what participants respond to")
|
|
167
167
|
.option("--variant <kind:value>", "Variant: text:\"...\", ./file.png, image:./file.png, ./file.png::label=B (repeatable)", collectRepeatable, [])
|
|
168
168
|
.option("--variants <file.json>", "JSON manifest of variants (alternative to --variant)")
|
|
169
|
-
.option("--wants-pick", "Each
|
|
170
|
-
.option("--wants-ratings", "Each
|
|
169
|
+
.option("--wants-pick", "Each participant picks a favourite variant (compatible with --wants-ratings; can be set together).")
|
|
170
|
+
.option("--wants-ratings", "Each participant rates every variant 1–5 (compatible with --wants-pick; can be set together). If neither is set, participants leave a free-form comment only.")
|
|
171
171
|
.option("--questions <file.json>", `Questions JSON file: [{"question":"...","type":"text"|"slider"|"likert"|"single-choice"|"multiple-choice"|"number"}]`)
|
|
172
172
|
.option("--language <code>", "2-letter language code (with --new only)", "en");
|
|
173
|
-
|
|
173
|
+
addPersonFilterFlags(askRun, {
|
|
174
174
|
allFlagName: "--all-simulatable",
|
|
175
|
-
allFlagDescription: "Use every simulatable AI
|
|
175
|
+
allFlagDescription: "Use every simulatable AI person matching the filters (with --new only)",
|
|
176
176
|
})
|
|
177
177
|
.option("--subset-round <n>", "Drill-in subset (Pattern B) — append-round only. 1-indexed prior round to filter against. Pair with --subset-variant.")
|
|
178
178
|
.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`.")
|
|
@@ -204,14 +204,14 @@ Examples:
|
|
|
204
204
|
throw new Error("--subset-round / --subset-variant are only valid when appending to an existing ask. Drop --new or drop the subset flags.");
|
|
205
205
|
}
|
|
206
206
|
const wid = resolveWorkspace(opts.workspace);
|
|
207
|
-
const
|
|
207
|
+
const participantIds = await resolvePersonIds(client, wid, personFlags(opts), { requireSimulatable: true, allFlagName: "--all-simulatable" });
|
|
208
208
|
const round = await buildRoundInput(client, wid, opts, !!globals.quiet);
|
|
209
209
|
const askName = opts.name || `CLI ${new Date().toISOString().slice(0, 16)}`;
|
|
210
210
|
const body = {
|
|
211
211
|
name: askName,
|
|
212
212
|
...(opts.description !== undefined && { description: opts.description }),
|
|
213
213
|
language: opts.language,
|
|
214
|
-
|
|
214
|
+
person_ids: participantIds,
|
|
215
215
|
first_round: round,
|
|
216
216
|
};
|
|
217
217
|
// M5 / Pattern G: `ask run --new` POSTs to a non-idempotent
|
|
@@ -255,8 +255,8 @@ Examples:
|
|
|
255
255
|
}
|
|
256
256
|
return;
|
|
257
257
|
}
|
|
258
|
-
// Append-round path — reject
|
|
259
|
-
const
|
|
258
|
+
// Append-round path — reject people/creation flags that have no effect here
|
|
259
|
+
const appendPersonSet = (opts.person && opts.person.length > 0)
|
|
260
260
|
|| opts.sample !== undefined
|
|
261
261
|
|| opts.allSimulatable
|
|
262
262
|
|| opts.search
|
|
@@ -265,8 +265,8 @@ Examples:
|
|
|
265
265
|
|| opts.minAge
|
|
266
266
|
|| opts.maxAge
|
|
267
267
|
|| opts.visibility;
|
|
268
|
-
if (opts.name || opts.description || opts.workspace ||
|
|
269
|
-
throw new Error("
|
|
268
|
+
if (opts.name || opts.description || opts.workspace || appendPersonSet) {
|
|
269
|
+
throw new Error("People and ask-creation flags (--name, --description, --workspace, --person, --sample, --all-simulatable, --country, --gender, --min-age, --max-age, --search, --visibility) are only valid with --new. " +
|
|
270
270
|
"Drop them to append a round, or add --new to create a fresh ask.");
|
|
271
271
|
}
|
|
272
272
|
let aid;
|
|
@@ -278,9 +278,9 @@ Examples:
|
|
|
278
278
|
}
|
|
279
279
|
const ask = await client.get(`/asks/${aid}`);
|
|
280
280
|
const round = await buildRoundInput(client, ask.product_id, opts, !!globals.quiet);
|
|
281
|
-
const subset =
|
|
281
|
+
const subset = parseParticipantSubset(opts.subsetRound, opts.subsetVariant);
|
|
282
282
|
if (subset)
|
|
283
|
-
round.
|
|
283
|
+
round.participant_subset = subset;
|
|
284
284
|
const created = await client.post(`/asks/${aid}/rounds`, round);
|
|
285
285
|
if (opts.wait) {
|
|
286
286
|
const timeoutMs = parseWaitTimeout(opts.timeout);
|
|
@@ -334,45 +334,45 @@ Examples:
|
|
|
334
334
|
// ---- create -------------------------------------------------------------
|
|
335
335
|
const askCreate = ask
|
|
336
336
|
.command("create")
|
|
337
|
-
.description("Create a new ask with a first round and dispatch to
|
|
337
|
+
.description("Create a new ask with a first round and dispatch to people")
|
|
338
338
|
.option("--workspace <id>", "Workspace ID")
|
|
339
339
|
.requiredOption("--name <name>", "Ask name")
|
|
340
340
|
.option("--description <description>", "Ask description")
|
|
341
|
-
.requiredOption("--prompt <prompt>", "Round prompt — what
|
|
341
|
+
.requiredOption("--prompt <prompt>", "Round prompt — what participants respond to")
|
|
342
342
|
.option("--variant <kind:value>", "Variant: text:\"...\", ./file.png, image:./file.png, ./file.png::label=B (repeatable)", collectRepeatable, [])
|
|
343
343
|
.option("--variants <file.json>", "JSON manifest of variants (alternative to --variant)")
|
|
344
|
-
.option("--wants-pick", "Each
|
|
345
|
-
.option("--wants-ratings", "Each
|
|
344
|
+
.option("--wants-pick", "Each participant picks a favourite variant (compatible with --wants-ratings; can be set together).")
|
|
345
|
+
.option("--wants-ratings", "Each participant rates every variant 1–5 (compatible with --wants-pick; can be set together). If neither is set, participants leave a free-form comment only.")
|
|
346
346
|
.option("--questions <file.json>", `Questions JSON file: [{"question":"...","type":"text"|"slider"|"likert"|"single-choice"|"multiple-choice"|"number"}]`)
|
|
347
347
|
.option("--language <code>", "2-letter language code", "en");
|
|
348
|
-
|
|
348
|
+
addPersonFilterFlags(askCreate, {
|
|
349
349
|
allFlagName: "--all-simulatable",
|
|
350
|
-
allFlagDescription: "Use every simulatable AI
|
|
350
|
+
allFlagDescription: "Use every simulatable AI person matching the filters",
|
|
351
351
|
})
|
|
352
|
-
.option("--no-dispatch", "Create the ask in DRAFT status without billing or dispatching the round. Hand the draft id back to the user, then start it with `ish ask dispatch <id>`.
|
|
352
|
+
.option("--no-dispatch", "Create the ask in DRAFT status without billing or dispatching the round. Hand the draft id back to the user, then start it with `ish ask dispatch <id>`. People flags are still required because the participants are materialized at create time. Mutually exclusive with --wait (nothing to wait for).")
|
|
353
353
|
.option("--wait", "Wait until the first round completes (or errors)")
|
|
354
354
|
.option("--timeout <s>", "Wait timeout in seconds (default 300)")
|
|
355
355
|
.addHelpText("after", `
|
|
356
356
|
Examples:
|
|
357
|
-
# Two-text comparison, sample 30
|
|
357
|
+
# Two-text comparison, sample 30 participants, wait for results:
|
|
358
358
|
$ ish ask create --workspace w-6ec --name "tagline AB" \\
|
|
359
359
|
--prompt "Which sounds better?" \\
|
|
360
360
|
--variant text:"Short and punchy." \\
|
|
361
361
|
--variant text:"A longer, descriptive line." \\
|
|
362
362
|
--sample 30 --wants-pick --wait
|
|
363
363
|
|
|
364
|
-
# Demographic-filtered sample (Swedish
|
|
364
|
+
# Demographic-filtered sample (Swedish people aged 35–50):
|
|
365
365
|
$ ish ask create --workspace w-6ec --name "SE 35-50" \\
|
|
366
366
|
--prompt "Which sounds better?" \\
|
|
367
367
|
--variant text:"A" --variant text:"B" \\
|
|
368
368
|
--country SE --min-age 35 --max-age 50 --sample 10 --wants-pick
|
|
369
369
|
|
|
370
|
-
# Image comparison from files with explicit
|
|
370
|
+
# Image comparison from files with explicit people and ratings:
|
|
371
371
|
$ ish ask create --workspace w-6ec --name "hero shots" \\
|
|
372
372
|
--prompt "Which feels premium?" \\
|
|
373
373
|
--variant image:./hero-a.png::label=A \\
|
|
374
374
|
--variant image:./hero-b.png::label=B \\
|
|
375
|
-
--
|
|
375
|
+
--person p-d4e,p-a17 --wants-ratings
|
|
376
376
|
|
|
377
377
|
# Text from a markdown file + JSON questionnaire:
|
|
378
378
|
$ ish ask create --workspace w-6ec --name "newsletter" \\
|
|
@@ -386,7 +386,7 @@ Minimal --questions JSON (server keys: "question" + "type"):
|
|
|
386
386
|
{ "question": "Rate it 1-5", "type": "slider" }
|
|
387
387
|
]
|
|
388
388
|
|
|
389
|
-
Picks come back with a \`pick_confidence\` (0..1) score per
|
|
389
|
+
Picks come back with a \`pick_confidence\` (0..1) score per participant when
|
|
390
390
|
\`--wants-pick\` is set — read it off \`pick.confidence\` in the response. See
|
|
391
391
|
\`ish docs get-page concepts/ask\` for interpretation.
|
|
392
392
|
`)
|
|
@@ -396,13 +396,13 @@ Picks come back with a \`pick_confidence\` (0..1) score per tester when
|
|
|
396
396
|
throw new Error("--no-dispatch and --wait are incompatible — a draft ask has nothing to wait for. Drop --wait, or run `ish ask dispatch <id> --wait` after the draft is created.");
|
|
397
397
|
}
|
|
398
398
|
const wid = resolveWorkspace(opts.workspace);
|
|
399
|
-
const
|
|
399
|
+
const participantIds = await resolvePersonIds(client, wid, personFlags(opts), { requireSimulatable: true, allFlagName: "--all-simulatable" });
|
|
400
400
|
const round = await buildRoundInput(client, wid, opts, !!globals.quiet);
|
|
401
401
|
const body = {
|
|
402
402
|
name: opts.name,
|
|
403
403
|
...(opts.description !== undefined && { description: opts.description }),
|
|
404
404
|
language: opts.language,
|
|
405
|
-
|
|
405
|
+
person_ids: participantIds,
|
|
406
406
|
first_round: round,
|
|
407
407
|
...(opts.dispatch === false && { dispatch: false }),
|
|
408
408
|
};
|
|
@@ -485,7 +485,7 @@ Examples:
|
|
|
485
485
|
$ ish ask get a-6ec
|
|
486
486
|
$ ish ask get a-6ec --round 2 --json
|
|
487
487
|
$ ish ask get a-6ec a-7c2 a-9d3
|
|
488
|
-
$ ish ask get a-6ec,a-7c2 --fields alias,name,
|
|
488
|
+
$ ish ask get a-6ec,a-7c2 --fields alias,name,participants_count,responses_total
|
|
489
489
|
|
|
490
490
|
With multiple IDs, returns a {items:[...], total:N} envelope and uses the
|
|
491
491
|
list table layout in human mode. --ask and --round only apply to single-ask
|
|
@@ -626,16 +626,16 @@ the model's self-reported confidence in its variant choice. See
|
|
|
626
626
|
.requiredOption("--prompt <prompt>", "Round prompt")
|
|
627
627
|
.option("--variant <kind:value>", "Variant flag (repeatable; same syntax as `ask create`)", collectRepeatable, [])
|
|
628
628
|
.option("--variants <file.json>", "JSON manifest of variants")
|
|
629
|
-
.option("--wants-pick", "Each
|
|
630
|
-
.option("--wants-ratings", "Each
|
|
629
|
+
.option("--wants-pick", "Each participant picks a favourite variant (compatible with --wants-ratings; can be set together).")
|
|
630
|
+
.option("--wants-ratings", "Each participant rates every variant 1–5 (compatible with --wants-pick; can be set together). If neither is set, participants leave a free-form comment only.")
|
|
631
631
|
.option("--questions <file.json>", `Questions JSON file: [{"question":"...","type":"text"|"slider"|"likert"|"single-choice"|"multiple-choice"|"number"}]`)
|
|
632
|
-
.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
|
|
632
|
+
.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 participants who picked --subset-variant on round N.")
|
|
633
633
|
.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.")
|
|
634
634
|
.option("--wait", "Wait until the new round completes")
|
|
635
635
|
.option("--timeout <s>", "Wait timeout in seconds (default 300)")
|
|
636
636
|
.addHelpText("after", `
|
|
637
637
|
Examples:
|
|
638
|
-
# Append round 2 to the same
|
|
638
|
+
# Append round 2 to the same participants.
|
|
639
639
|
$ ish ask add-round a-6ec --prompt "And now?" --variant text:"Hello" --variant text:"Hi" --wait
|
|
640
640
|
|
|
641
641
|
# Drill round 2 into the round-1-A-pickers (Pattern B).
|
|
@@ -646,15 +646,15 @@ Examples:
|
|
|
646
646
|
|
|
647
647
|
If --subset-round / --subset-variant fails to resolve (round missing, variant
|
|
648
648
|
not on that round, or zero pickers), the backend returns a 422 with
|
|
649
|
-
error_kind: "
|
|
649
|
+
error_kind: "participant_subset_invalid".`)
|
|
650
650
|
.action(async (id, opts, cmd) => {
|
|
651
651
|
await withClient(cmd, async (client, globals) => {
|
|
652
652
|
const aid = resolveAsk(pickAskRef(id, opts.ask));
|
|
653
653
|
const ask = await client.get(`/asks/${aid}`);
|
|
654
654
|
const round = await buildRoundInput(client, ask.product_id, opts, !!globals.quiet);
|
|
655
|
-
const subset =
|
|
655
|
+
const subset = parseParticipantSubset(opts.subsetRound, opts.subsetVariant);
|
|
656
656
|
if (subset)
|
|
657
|
-
round.
|
|
657
|
+
round.participant_subset = subset;
|
|
658
658
|
const created = await client.post(`/asks/${aid}/rounds`, round);
|
|
659
659
|
if (opts.wait) {
|
|
660
660
|
const timeoutMs = parseWaitTimeout(opts.timeout);
|
|
@@ -746,7 +746,7 @@ text, slider, likert, single-choice, multiple-choice, number.`)
|
|
|
746
746
|
.option("--timeout <s>", "Wait timeout in seconds (default 300)")
|
|
747
747
|
.addHelpText("after", `
|
|
748
748
|
Examples:
|
|
749
|
-
# Retry the errored 4 of 5
|
|
749
|
+
# Retry the errored 4 of 5 participants on round 1.
|
|
750
750
|
$ ish ask retry a-d3e --round 1
|
|
751
751
|
|
|
752
752
|
# Retry and wait for the round to settle.
|
|
@@ -786,43 +786,43 @@ Notes:
|
|
|
786
786
|
}, globals.json);
|
|
787
787
|
});
|
|
788
788
|
});
|
|
789
|
-
// ---- add-
|
|
790
|
-
const
|
|
791
|
-
.command("add-
|
|
792
|
-
.description("Add
|
|
789
|
+
// ---- add-people --------------------------------------------------------
|
|
790
|
+
const askAddPeople = ask
|
|
791
|
+
.command("add-people")
|
|
792
|
+
.description("Add people to an existing ask (they become participants when the round runs; optionally backfill prior rounds)")
|
|
793
793
|
.argument("[id]", "Ask alias or UUID (defaults to active ask)")
|
|
794
794
|
.option("--ask <id>", "Ask ID; alternative to positional argument")
|
|
795
795
|
.option("--workspace <id>", "Workspace ID; accepted for consistency (workspace is inferred from the ask)")
|
|
796
|
-
.requiredOption("--round <n|round-id>", "Round to add
|
|
797
|
-
|
|
796
|
+
.requiredOption("--round <n|round-id>", "Round to add people to (required)");
|
|
797
|
+
addPersonFilterFlags(askAddPeople, {
|
|
798
798
|
allFlagName: "--all-simulatable",
|
|
799
|
-
allFlagDescription: "Add every simulatable AI
|
|
799
|
+
allFlagDescription: "Add every simulatable AI person matching the filters not already included",
|
|
800
800
|
})
|
|
801
|
-
.option("--backfill", "Backfill prior rounds in order for new
|
|
801
|
+
.option("--backfill", "Backfill prior rounds in order for new people")
|
|
802
802
|
.addHelpText("after", `
|
|
803
803
|
Examples:
|
|
804
|
-
$ ish ask add-
|
|
805
|
-
$ ish ask add-
|
|
806
|
-
$ ish ask add-
|
|
804
|
+
$ ish ask add-people a-6ec --round 1 --sample 5
|
|
805
|
+
$ ish ask add-people a-6ec --round 1 --country GB --sample 3 --backfill
|
|
806
|
+
$ ish ask add-people a-6ec --round 2 --person p-d4e,p-a17 --backfill`)
|
|
807
807
|
.action(async (id, opts, cmd) => {
|
|
808
808
|
await withClient(cmd, async (client, globals) => {
|
|
809
809
|
const aid = resolveAsk(pickAskRef(id, opts.ask));
|
|
810
810
|
const askDetail = await client.get(`/asks/${aid}`);
|
|
811
811
|
const round = getRoundByIndexOrId(askDetail, opts.round);
|
|
812
|
-
const existingProfileIds = new Set((askDetail.
|
|
813
|
-
.map((t) => (t.
|
|
812
|
+
const existingProfileIds = new Set((askDetail.participants ?? [])
|
|
813
|
+
.map((t) => (t.person_id ? String(t.person_id) : ""))
|
|
814
814
|
.filter(Boolean));
|
|
815
|
-
const candidateIds = await
|
|
815
|
+
const candidateIds = await resolvePersonIds(client, askDetail.product_id, personFlags(opts), {
|
|
816
816
|
requireSimulatable: true,
|
|
817
817
|
allFlagName: "--all-simulatable",
|
|
818
818
|
excludeProfileIds: existingProfileIds,
|
|
819
819
|
});
|
|
820
820
|
const body = {
|
|
821
|
-
|
|
822
|
-
|
|
821
|
+
person_ids: candidateIds,
|
|
822
|
+
dispatch_into_round_id: round.id,
|
|
823
823
|
backfill_prior_rounds: !!opts.backfill,
|
|
824
824
|
};
|
|
825
|
-
const data = await client.post(`/asks/${aid}/
|
|
825
|
+
const data = await client.post(`/asks/${aid}/participants`, body);
|
|
826
826
|
if (!globals.json || globals.verbose) {
|
|
827
827
|
formatAskDetail(data, globals.json);
|
|
828
828
|
return;
|
|
@@ -835,9 +835,9 @@ Examples:
|
|
|
835
835
|
name: data.name,
|
|
836
836
|
round: {
|
|
837
837
|
round_number: (targetRound?.order_index ?? round.order_index) + 1,
|
|
838
|
-
|
|
838
|
+
participant_count: (data.participants ?? []).length,
|
|
839
839
|
response_count: responses.length,
|
|
840
|
-
|
|
840
|
+
new_participant_count: candidateIds.length,
|
|
841
841
|
},
|
|
842
842
|
}, globals.json);
|
|
843
843
|
});
|
|
@@ -924,7 +924,7 @@ Examples:
|
|
|
924
924
|
// ---- delete -------------------------------------------------------------
|
|
925
925
|
ask
|
|
926
926
|
.command("delete")
|
|
927
|
-
.description("Delete an ask and all its rounds, responses, and
|
|
927
|
+
.description("Delete an ask and all its rounds, responses, and participants")
|
|
928
928
|
.argument("[id]", "Ask alias or UUID (defaults to active ask)")
|
|
929
929
|
.option("--ask <id>", "Ask ID; alternative to positional argument")
|
|
930
930
|
.addHelpText("after", "\nExamples:\n $ ish ask delete a-6ec")
|
package/dist/commands/chat.js
CHANGED
|
@@ -527,7 +527,7 @@ function attachChatEndpointTest(parent) {
|
|
|
527
527
|
.option("--endpoint <id>", "Endpoint alias or UUID (alternative to positional)")
|
|
528
528
|
.option("-m, --message <text>", "Message to send", "Hello")
|
|
529
529
|
.option("--conversation-id <id>", "Thread state across multiple invocations (stateful endpoints)")
|
|
530
|
-
.option("--
|
|
530
|
+
.option("--participant <json>", "Participant persona JSON ({name, locale, ...}) exposed via {{participant.*}}")
|
|
531
531
|
.option("--include-request", "Include the dispatched POST body in the output")
|
|
532
532
|
.option("--workspace <id>", "Workspace ID")
|
|
533
533
|
.addHelpText("after", `
|
|
@@ -537,7 +537,7 @@ exits 5 with error_kind=TunnelInactive on miss.
|
|
|
537
537
|
Examples:
|
|
538
538
|
$ ish chat endpoint test ep-abc -m "Hello"
|
|
539
539
|
$ ish chat endpoint test ep-abc -m "Yes" --conversation-id conv-123
|
|
540
|
-
$ ish chat endpoint test ep-abc --
|
|
540
|
+
$ ish chat endpoint test ep-abc --participant '{"name":"Pat","locale":"en-US"}'`)
|
|
541
541
|
.action(async (id, opts, cmd) => {
|
|
542
542
|
await withClient(cmd, async (client, globals) => {
|
|
543
543
|
const ws = resolveWorkspace(opts.workspace);
|
|
@@ -549,20 +549,20 @@ Examples:
|
|
|
549
549
|
if (saved.isTunnelBacked) {
|
|
550
550
|
await tunnelGuard(client);
|
|
551
551
|
}
|
|
552
|
-
let
|
|
553
|
-
if (opts.
|
|
552
|
+
let participantBlock;
|
|
553
|
+
if (opts.participant !== undefined) {
|
|
554
554
|
let parsed;
|
|
555
555
|
try {
|
|
556
|
-
parsed = JSON.parse(opts.
|
|
556
|
+
parsed = JSON.parse(opts.participant);
|
|
557
557
|
}
|
|
558
558
|
catch (err) {
|
|
559
559
|
const message = err instanceof Error ? err.message : String(err);
|
|
560
|
-
throw new Error(`--
|
|
560
|
+
throw new Error(`--participant must be a JSON object: ${message}`);
|
|
561
561
|
}
|
|
562
562
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
563
|
-
throw new Error("--
|
|
563
|
+
throw new Error("--participant must be a JSON object.");
|
|
564
564
|
}
|
|
565
|
-
|
|
565
|
+
participantBlock = parsed;
|
|
566
566
|
}
|
|
567
567
|
// The backend test-endpoint route accepts the same envelope shape the
|
|
568
568
|
// editor sends — drop the saved config in unchanged, with isTunnelBacked
|
|
@@ -575,8 +575,8 @@ Examples:
|
|
|
575
575
|
endpoint: endpointPayload,
|
|
576
576
|
sample_message: opts.message,
|
|
577
577
|
};
|
|
578
|
-
if (
|
|
579
|
-
body.
|
|
578
|
+
if (participantBlock)
|
|
579
|
+
body.participant = participantBlock;
|
|
580
580
|
if (opts.conversationId)
|
|
581
581
|
body.conversation_id = opts.conversationId;
|
|
582
582
|
const res = await client.post(`/products/${ws}/chat/test-endpoint`, body, { timeout: 90_000 });
|
package/dist/commands/config.js
CHANGED
|
@@ -9,7 +9,7 @@ export function registerConfigCommands(program) {
|
|
|
9
9
|
.command("config")
|
|
10
10
|
.description("Manage simulation configs")
|
|
11
11
|
.addHelpText("after", `
|
|
12
|
-
A simulation config tunes how
|
|
12
|
+
A simulation config tunes how participants behave during a run (model, timing, retries, etc.).
|
|
13
13
|
Pass \`--config <id>\` to \`ish study run\` to override the default for one dispatch.
|
|
14
14
|
|
|
15
15
|
Configs are global — not scoped to a workspace. The --workspace flag is rejected. Use aliases (c-...) or UUIDs to identify configs.
|
package/dist/commands/docs.js
CHANGED
|
@@ -139,7 +139,7 @@ Examples:
|
|
|
139
139
|
.addHelpText("after", `\nExamples:
|
|
140
140
|
$ ish docs search assignment
|
|
141
141
|
$ ish docs search "site access"
|
|
142
|
-
$ ish docs search
|
|
142
|
+
$ ish docs search people --json`)
|
|
143
143
|
.action((query, _opts, cmd) => {
|
|
144
144
|
const globals = getGlobals(cmd);
|
|
145
145
|
printSearch(query, globals.json);
|