@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.
Files changed (64) hide show
  1. package/README.md +54 -54
  2. package/dist/commands/ask.d.ts +4 -4
  3. package/dist/commands/ask.js +66 -66
  4. package/dist/commands/chat.js +10 -10
  5. package/dist/commands/config.js +1 -1
  6. package/dist/commands/docs.js +1 -1
  7. package/dist/commands/iteration.js +57 -57
  8. package/dist/commands/mcp.d.ts +23 -0
  9. package/dist/commands/mcp.js +676 -0
  10. package/dist/commands/person.d.ts +5 -0
  11. package/dist/commands/{profile.js → person.js} +197 -162
  12. package/dist/commands/source.d.ts +6 -2
  13. package/dist/commands/source.js +35 -30
  14. package/dist/commands/study-analyze.d.ts +1 -1
  15. package/dist/commands/study-analyze.js +3 -3
  16. package/dist/commands/study-participant.d.ts +8 -0
  17. package/dist/commands/{study-tester.js → study-participant.js} +50 -50
  18. package/dist/commands/study-run.d.ts +6 -6
  19. package/dist/commands/study-run.js +341 -290
  20. package/dist/commands/study.js +106 -72
  21. package/dist/commands/workspace.js +13 -13
  22. package/dist/connect.js +5 -5
  23. package/dist/index.js +6 -4
  24. package/dist/lib/accessibility-profile.d.ts +1 -1
  25. package/dist/lib/accessibility-profile.js +1 -1
  26. package/dist/lib/alias-hydrate.js +4 -4
  27. package/dist/lib/alias-store.d.ts +5 -5
  28. package/dist/lib/alias-store.js +8 -8
  29. package/dist/lib/api-client.d.ts +1 -1
  30. package/dist/lib/api-client.js +1 -1
  31. package/dist/lib/billing.d.ts +11 -11
  32. package/dist/lib/billing.js +16 -16
  33. package/dist/lib/chat-endpoint-templates.js +1 -1
  34. package/dist/lib/command-helpers.d.ts +18 -18
  35. package/dist/lib/command-helpers.js +49 -37
  36. package/dist/lib/docs.js +570 -387
  37. package/dist/lib/enums.d.ts +2 -2
  38. package/dist/lib/enums.js +2 -2
  39. package/dist/lib/local-sim/browser.d.ts +1 -1
  40. package/dist/lib/local-sim/browser.js +1 -1
  41. package/dist/lib/local-sim/debug-report.d.ts +2 -2
  42. package/dist/lib/local-sim/debug-report.js +3 -3
  43. package/dist/lib/local-sim/loop.d.ts +5 -5
  44. package/dist/lib/local-sim/loop.js +38 -38
  45. package/dist/lib/local-sim/types.d.ts +12 -12
  46. package/dist/lib/mcp-clients.d.ts +51 -0
  47. package/dist/lib/mcp-clients.js +175 -0
  48. package/dist/lib/modality.d.ts +10 -10
  49. package/dist/lib/modality.js +46 -46
  50. package/dist/lib/output.d.ts +16 -15
  51. package/dist/lib/output.js +291 -226
  52. package/dist/lib/profile-sources.d.ts +64 -16
  53. package/dist/lib/profile-sources.js +91 -30
  54. package/dist/lib/skill-content.js +216 -168
  55. package/dist/lib/study-events.d.ts +3 -3
  56. package/dist/lib/study-events.js +1 -1
  57. package/dist/lib/study-inputs.d.ts +11 -1
  58. package/dist/lib/study-inputs.js +68 -17
  59. package/dist/lib/study-participants.d.ts +32 -0
  60. package/dist/lib/study-participants.js +12 -0
  61. package/dist/lib/types.d.ts +104 -34
  62. package/package.json +1 -1
  63. package/dist/commands/profile.d.ts +0 -5
  64. package/dist/commands/study-tester.d.ts +0 -8
@@ -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, resolveAudienceProfileIds, addAudienceFilterFlags, } from "../lib/command-helpers.js";
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 AudienceFilterOpts shape. */
16
- function audienceFlags(opts) {
15
+ /** Map raw Commander opts (with `allSimulatable`) to the shared PersonFilterOpts shape. */
16
+ function personFlags(opts) {
17
17
  return {
18
- profile: opts.profile,
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 `AudienceSubset` payload (Pattern B). Both flags must be passed
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
- * audience.
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 parseAudienceSubset(subsetRound, subsetVariant) {
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 an audience")
146
+ .description("Create and run asks — quick variant comparisons with people")
147
147
  .addHelpText("after", `
148
- An ask is a lightweight reaction artifact. Audience is 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 tester must do something.
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 testers respond to")
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 tester picks a favourite variant (compatible with --wants-ratings; can be set together).")
170
- .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.")
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
- addAudienceFilterFlags(askRun, {
173
+ addPersonFilterFlags(askRun, {
174
174
  allFlagName: "--all-simulatable",
175
- allFlagDescription: "Use every simulatable AI profile matching the filters (with --new only)",
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 testerIds = await resolveAudienceProfileIds(client, wid, audienceFlags(opts), { requireSimulatable: true, allFlagName: "--all-simulatable" });
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
- tester_profile_ids: testerIds,
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 audience/creation flags that have no effect here
259
- const appendAudienceSet = (opts.profile && opts.profile.length > 0)
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 || appendAudienceSet) {
269
- throw new Error("Audience and ask-creation flags (--name, --description, --workspace, --profile, --sample, --all-simulatable, --country, --gender, --min-age, --max-age, --search, --visibility) are only valid with --new. " +
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 = parseAudienceSubset(opts.subsetRound, opts.subsetVariant);
281
+ const subset = parseParticipantSubset(opts.subsetRound, opts.subsetVariant);
282
282
  if (subset)
283
- round.audience_subset = subset;
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 the audience")
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 testers respond to")
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 tester picks a favourite variant (compatible with --wants-ratings; can be set together).")
345
- .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.")
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
- addAudienceFilterFlags(askCreate, {
348
+ addPersonFilterFlags(askCreate, {
349
349
  allFlagName: "--all-simulatable",
350
- allFlagDescription: "Use every simulatable AI profile matching the filters",
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>`. Audience flags are still required because the testers are materialized at create time. Mutually exclusive with --wait (nothing to wait for).")
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 testers, wait for results:
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 profiles aged 35–50):
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 profiles and ratings:
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
- --profile tp-d4e,tp-a17 --wants-ratings
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 tester when
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 testerIds = await resolveAudienceProfileIds(client, wid, audienceFlags(opts), { requireSimulatable: true, allFlagName: "--all-simulatable" });
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
- tester_profile_ids: testerIds,
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,testers_count,responses_total
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 tester picks a favourite variant (compatible with --wants-ratings; can be set together).")
630
- .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.")
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 testers who picked --subset-variant on round N.")
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 audience.
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: "audience_subset_invalid".`)
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 = parseAudienceSubset(opts.subsetRound, opts.subsetVariant);
655
+ const subset = parseParticipantSubset(opts.subsetRound, opts.subsetVariant);
656
656
  if (subset)
657
- round.audience_subset = subset;
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 testers on round 1.
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-testers --------------------------------------------------------
790
- const askAddTesters = ask
791
- .command("add-testers")
792
- .description("Add testers to an existing ask (optionally backfill prior rounds)")
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 testers to (required)");
797
- addAudienceFilterFlags(askAddTesters, {
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 profile matching the filters not already in the audience",
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 testers")
801
+ .option("--backfill", "Backfill prior rounds in order for new people")
802
802
  .addHelpText("after", `
803
803
  Examples:
804
- $ ish ask add-testers a-6ec --round 1 --sample 5
805
- $ ish ask add-testers a-6ec --round 1 --country GB --sample 3 --backfill
806
- $ ish ask add-testers a-6ec --round 2 --profile tp-d4e,tp-a17 --backfill`)
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.testers ?? [])
813
- .map((t) => (t.tester_profile_id ? String(t.tester_profile_id) : ""))
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 resolveAudienceProfileIds(client, askDetail.product_id, audienceFlags(opts), {
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
- tester_profile_ids: candidateIds,
822
- round_id: round.id,
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}/testers`, body);
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
- tester_count: (data.testers ?? []).length,
838
+ participant_count: (data.participants ?? []).length,
839
839
  response_count: responses.length,
840
- new_tester_count: candidateIds.length,
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 audience")
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")
@@ -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("--tester <json>", "Tester persona JSON ({name, locale, ...}) exposed via {{tester.*}}")
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 --tester '{"name":"Pat","locale":"en-US"}'`)
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 testerBlock;
553
- if (opts.tester !== undefined) {
552
+ let participantBlock;
553
+ if (opts.participant !== undefined) {
554
554
  let parsed;
555
555
  try {
556
- parsed = JSON.parse(opts.tester);
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(`--tester must be a JSON object: ${message}`);
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("--tester must be a JSON object.");
563
+ throw new Error("--participant must be a JSON object.");
564
564
  }
565
- testerBlock = parsed;
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 (testerBlock)
579
- body.tester = testerBlock;
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 });
@@ -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 testers behave during a run (model, timing, retries, etc.).
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.
@@ -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 audience --json`)
142
+ $ ish docs search people --json`)
143
143
  .action((query, _opts, cmd) => {
144
144
  const globals = getGlobals(cmd);
145
145
  printSearch(query, globals.json);