@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
  * Short alias system for entity IDs.
3
3
  *
4
- * Generates deterministic aliases from UUID prefixes (e.g. w-6ec, s-b2c, tp-d4e).
4
+ * Generates deterministic aliases from UUID prefixes (e.g. w-6ec, s-b2c, p-d4e, pt-a3f).
5
5
  * Same UUID always produces the same alias — stable across commands and terminals.
6
6
  * Aliases are persisted to ~/.ish/aliases.json for resolution.
7
7
  */
@@ -10,9 +10,9 @@ export declare const ALIAS_PREFIX: {
10
10
  readonly workspace: "w";
11
11
  readonly study: "s";
12
12
  readonly iteration: "i";
13
- readonly testerProfile: "tp";
14
- readonly testerProfileSource: "tps";
15
- readonly tester: "t";
13
+ readonly person: "p";
14
+ readonly personSource: "ps";
15
+ readonly participant: "pt";
16
16
  readonly config: "c";
17
17
  readonly job: "j";
18
18
  readonly ask: "a";
@@ -46,7 +46,7 @@ export declare function deterministicAlias(prefix: string, uuid: string): string
46
46
  * Resolve a short alias to a full UUID, or validate and pass through a full UUID.
47
47
  *
48
48
  * Accepted formats:
49
- * - Short alias: w-6ec, s-b2c, tp-d4e (resolved from ~/.ish/aliases.json)
49
+ * - Short alias: w-6ec, s-b2c, p-d4e (resolved from ~/.ish/aliases.json)
50
50
  * - Full UUID: 6ecf2857-1d7a-4f9c-85da-c2ac6c5c5346
51
51
  *
52
52
  * Everything else throws with guidance toward the correct usage.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Short alias system for entity IDs.
3
3
  *
4
- * Generates deterministic aliases from UUID prefixes (e.g. w-6ec, s-b2c, tp-d4e).
4
+ * Generates deterministic aliases from UUID prefixes (e.g. w-6ec, s-b2c, p-d4e, pt-a3f).
5
5
  * Same UUID always produces the same alias — stable across commands and terminals.
6
6
  * Aliases are persisted to ~/.ish/aliases.json for resolution.
7
7
  */
@@ -13,9 +13,9 @@ export const ALIAS_PREFIX = {
13
13
  workspace: "w",
14
14
  study: "s",
15
15
  iteration: "i",
16
- testerProfile: "tp",
17
- testerProfileSource: "tps",
18
- tester: "t",
16
+ person: "p",
17
+ personSource: "ps",
18
+ participant: "pt",
19
19
  config: "c",
20
20
  job: "j",
21
21
  ask: "a",
@@ -121,9 +121,9 @@ const HYDRATE_HINT = {
121
121
  w: "ish workspace list",
122
122
  s: "ish study list",
123
123
  i: "ish iteration list --study <study-id>",
124
- tp: "ish profile list",
125
- tps: "ish source list",
126
- t: "ish tester get <tester-id>",
124
+ p: "ish person list",
125
+ ps: "ish source list",
126
+ pt: "ish participant get <participant-id>",
127
127
  c: "ish config list",
128
128
  a: "ish ask list",
129
129
  r: "ish ask get <ask-id>",
@@ -142,7 +142,7 @@ function hintForPrefix(alias) {
142
142
  * Resolve a short alias to a full UUID, or validate and pass through a full UUID.
143
143
  *
144
144
  * Accepted formats:
145
- * - Short alias: w-6ec, s-b2c, tp-d4e (resolved from ~/.ish/aliases.json)
145
+ * - Short alias: w-6ec, s-b2c, p-d4e (resolved from ~/.ish/aliases.json)
146
146
  * - Full UUID: 6ecf2857-1d7a-4f9c-85da-c2ac6c5c5346
147
147
  *
148
148
  * Everything else throws with guidance toward the correct usage.
@@ -34,7 +34,7 @@ export declare class ApiClient {
34
34
  patch<T = unknown>(path: string, body?: unknown, opts?: RequestOpts): Promise<T>;
35
35
  del(path: string, opts?: RequestOpts): Promise<void>;
36
36
  localSimInit(body: {
37
- tester_id: string;
37
+ participant_id: string;
38
38
  study_id: string;
39
39
  product_id: string;
40
40
  iteration_id: string;
@@ -218,7 +218,7 @@ export class ApiClient {
218
218
  return this.handleResponse(res);
219
219
  }
220
220
  async del(path, opts) {
221
- // Deletes typically cascade (rounds → responses → testersaudience),
221
+ // Deletes typically cascade (rounds → responses → participantspeople),
222
222
  // so default to a longer timeout than the standard 15s.
223
223
  const timeout = opts?.timeout ?? 60_000;
224
224
  const url = `${this.baseUrl}${path}`;
@@ -7,9 +7,9 @@
7
7
  * this file in the same commit, otherwise the CLI will mislead agents.
8
8
  *
9
9
  * Today every modality uses the same shape: `max(1, round(steps / 10))`
10
- * per principal (per tester for media/interactive, per conversation for
11
- * chat, ×2 for tester-pair). Asks bill flat 1 credit per successful
12
- * tester response. These are intentionally per-run estimates; long-term
10
+ * per principal (per participant for media/interactive, per conversation for
11
+ * chat, ×2 for participant-pair). Asks bill flat 1 credit per successful
12
+ * participant response. These are intentionally per-run estimates; long-term
13
13
  * we'll fetch `GET /billing/rates` and parameterise modalities — see
14
14
  * `reference/credits` docs page.
15
15
  */
@@ -17,7 +17,7 @@ export interface CreditEstimate {
17
17
  /** Upper bound (no early termination). Never claims exactness. */
18
18
  upper_bound: number;
19
19
  /** Stable identifier so agents can branch on shape if the formula evolves. */
20
- formula: "media_per_tester" | "chat_solo" | "chat_pair" | "ask_per_response";
20
+ formula: "media_per_participant" | "chat_solo" | "chat_pair" | "ask_per_response";
21
21
  /** Human-readable breakdown so agents can explain the number to users. */
22
22
  breakdown: string;
23
23
  /** Always "credits" today; reserved for future-proofing (e.g. millicredits). */
@@ -28,28 +28,28 @@ export declare function mediaCreditCost(steps: number): number;
28
28
  /** Mirror of `app/chat/billing.py::chat_credit_cost`. */
29
29
  export declare function chatCreditCost(turns: number): number;
30
30
  /**
31
- * Media/interactive run: 1 credit-cost-per-tester × tester count. Modality
31
+ * Media/interactive run: 1 credit-cost-per-participant × participant count. Modality
32
32
  * doesn't currently affect the rate (interactive == text == video at the
33
33
  * billing layer) — kept as a parameter for forward compatibility.
34
34
  */
35
35
  export declare function estimateMediaRun(args: {
36
- testerCount: number;
36
+ participantCount: number;
37
37
  maxInteractions: number;
38
38
  }): CreditEstimate;
39
- /** Solo chat (single tester, external chatbot). */
39
+ /** Solo chat (single participant, external chatbot). */
40
40
  export declare function estimateChatSolo(args: {
41
- testerCount: number;
41
+ participantCount: number;
42
42
  maxTurns: number;
43
43
  }): CreditEstimate;
44
- /** Tester-pair chat: each turn bills both sides, so cost doubles. */
44
+ /** Participant-pair chat: each turn bills both sides, so cost doubles. */
45
45
  export declare function estimateChatPair(args: {
46
46
  conversationCount: number;
47
47
  maxTurns: number;
48
48
  }): CreditEstimate;
49
49
  /**
50
- * Ask round: flat 1 credit per successful tester response (charged only
50
+ * Ask round: flat 1 credit per successful participant response (charged only
51
51
  * for completed responses; the upper bound assumes everyone completes).
52
52
  */
53
53
  export declare function estimateAskRound(args: {
54
- testerCount: number;
54
+ participantCount: number;
55
55
  }): CreditEstimate;
@@ -7,9 +7,9 @@
7
7
  * this file in the same commit, otherwise the CLI will mislead agents.
8
8
  *
9
9
  * Today every modality uses the same shape: `max(1, round(steps / 10))`
10
- * per principal (per tester for media/interactive, per conversation for
11
- * chat, ×2 for tester-pair). Asks bill flat 1 credit per successful
12
- * tester response. These are intentionally per-run estimates; long-term
10
+ * per principal (per participant for media/interactive, per conversation for
11
+ * chat, ×2 for participant-pair). Asks bill flat 1 credit per successful
12
+ * participant response. These are intentionally per-run estimates; long-term
13
13
  * we'll fetch `GET /billing/rates` and parameterise modalities — see
14
14
  * `reference/credits` docs page.
15
15
  */
@@ -26,32 +26,32 @@ export function chatCreditCost(turns) {
26
26
  return Math.max(1, Math.round(turns / 10));
27
27
  }
28
28
  /**
29
- * Media/interactive run: 1 credit-cost-per-tester × tester count. Modality
29
+ * Media/interactive run: 1 credit-cost-per-participant × participant count. Modality
30
30
  * doesn't currently affect the rate (interactive == text == video at the
31
31
  * billing layer) — kept as a parameter for forward compatibility.
32
32
  */
33
33
  export function estimateMediaRun(args) {
34
- const perTester = mediaCreditCost(args.maxInteractions);
35
- const total = Math.max(0, args.testerCount) * perTester;
34
+ const perParticipant = mediaCreditCost(args.maxInteractions);
35
+ const total = Math.max(0, args.participantCount) * perParticipant;
36
36
  return {
37
37
  upper_bound: total,
38
- formula: "media_per_tester",
39
- breakdown: `${args.testerCount} tester(s) × max(1, round(${args.maxInteractions} steps / 10)) = ${args.testerCount} × ${perTester} = ${total}`,
38
+ formula: "media_per_participant",
39
+ breakdown: `${args.participantCount} participant(s) × max(1, round(${args.maxInteractions} steps / 10)) = ${args.participantCount} × ${perParticipant} = ${total}`,
40
40
  unit: "credits",
41
41
  };
42
42
  }
43
- /** Solo chat (single tester, external chatbot). */
43
+ /** Solo chat (single participant, external chatbot). */
44
44
  export function estimateChatSolo(args) {
45
- const perTester = chatCreditCost(args.maxTurns);
46
- const total = Math.max(0, args.testerCount) * perTester;
45
+ const perParticipant = chatCreditCost(args.maxTurns);
46
+ const total = Math.max(0, args.participantCount) * perParticipant;
47
47
  return {
48
48
  upper_bound: total,
49
49
  formula: "chat_solo",
50
- breakdown: `${args.testerCount} tester(s) × max(1, round(${args.maxTurns} turns / 10)) = ${args.testerCount} × ${perTester} = ${total}`,
50
+ breakdown: `${args.participantCount} participant(s) × max(1, round(${args.maxTurns} turns / 10)) = ${args.participantCount} × ${perParticipant} = ${total}`,
51
51
  unit: "credits",
52
52
  };
53
53
  }
54
- /** Tester-pair chat: each turn bills both sides, so cost doubles. */
54
+ /** Participant-pair chat: each turn bills both sides, so cost doubles. */
55
55
  export function estimateChatPair(args) {
56
56
  const perSide = chatCreditCost(args.maxTurns);
57
57
  const total = Math.max(0, args.conversationCount) * perSide * 2;
@@ -63,15 +63,15 @@ export function estimateChatPair(args) {
63
63
  };
64
64
  }
65
65
  /**
66
- * Ask round: flat 1 credit per successful tester response (charged only
66
+ * Ask round: flat 1 credit per successful participant response (charged only
67
67
  * for completed responses; the upper bound assumes everyone completes).
68
68
  */
69
69
  export function estimateAskRound(args) {
70
- const total = Math.max(0, args.testerCount);
70
+ const total = Math.max(0, args.participantCount);
71
71
  return {
72
72
  upper_bound: total,
73
73
  formula: "ask_per_response",
74
- breakdown: `${args.testerCount} tester(s) × 1 credit/response = ${total} (upper bound; only successful responses bill)`,
74
+ breakdown: `${args.participantCount} participant(s) × 1 credit/response = ${total} (upper bound; only successful responses bill)`,
75
75
  unit: "credits",
76
76
  };
77
77
  }
@@ -167,7 +167,7 @@ const BOTFRAMEWORK = {
167
167
  },
168
168
  bodyTemplate: {
169
169
  type: "message",
170
- from: { id: "{{tester.name}}" },
170
+ from: { id: "{{participant.name}}" },
171
171
  text: "{{action.text}}",
172
172
  },
173
173
  mode: "stateful",
@@ -4,8 +4,8 @@
4
4
  */
5
5
  import type { Command } from "commander";
6
6
  import { ApiClient } from "./api-client.js";
7
- export interface AudienceFilterOpts {
8
- profile?: string[];
7
+ export interface PersonFilterOpts {
8
+ person?: string[];
9
9
  sample?: string;
10
10
  all?: boolean;
11
11
  search?: string;
@@ -17,36 +17,36 @@ export interface AudienceFilterOpts {
17
17
  maxAge?: string;
18
18
  visibility?: string;
19
19
  }
20
- export interface AudienceResolveOpts {
20
+ export interface PersonResolveOpts {
21
21
  requireSimulatable?: boolean;
22
22
  allFlagName?: string;
23
- /** Profile IDs to exclude from the fetched pool (e.g. testers already on an ask). */
23
+ /** Profile IDs to exclude from the fetched pool (e.g. participants already on an ask). */
24
24
  excludeProfileIds?: Set<string>;
25
25
  }
26
- /** True when any audience-selection flag is set on the command. */
27
- export declare function hasAudienceFlags(flags: AudienceFilterOpts): boolean;
26
+ /** True when any person-selection flag is set on the command. */
27
+ export declare function hasPersonFlags(flags: PersonFilterOpts): boolean;
28
28
  /**
29
- * Resolve a tester-profile audience from CLI flags.
29
+ * Resolve person IDs from CLI flags.
30
30
  *
31
31
  * Modes (mutually exclusive):
32
- * - explicit: `--profile <id>` (repeatable) — returns those IDs verbatim.
32
+ * - explicit: `--person <id>` (repeatable) — returns those IDs verbatim.
33
33
  * - sample: `--sample N` + optional filter flags — fetches the matching
34
34
  * pool, randomly samples N.
35
35
  * - all: caller's "all" flag + optional filter flags — returns every
36
- * matching profile.
36
+ * matching person.
37
37
  * - filtered: filter flags only — treated as "all matching".
38
38
  *
39
39
  * Always sends `type=ai` to the backend (simulations are AI-driven).
40
- * If `requireSimulatable`, applies a client-side filter for profiles that
40
+ * If `requireSimulatable`, applies a client-side filter for people that
41
41
  * have a simulation config attached.
42
42
  */
43
- export declare function resolveAudienceProfileIds(client: ApiClient, workspace: string, flags: AudienceFilterOpts, opts?: AudienceResolveOpts): Promise<string[]>;
43
+ export declare function resolvePersonIds(client: ApiClient, workspace: string, flags: PersonFilterOpts, opts?: PersonResolveOpts): Promise<string[]>;
44
44
  /**
45
- * Attach the audience-selection flag set to a Commander command.
45
+ * Attach the person-selection flag set to a Commander command.
46
46
  * Pass `allFlagName: "--all-simulatable"` for ask commands to keep the
47
47
  * established flag name; defaults to `--all` for study run.
48
48
  */
49
- export declare function addAudienceFilterFlags(cmd: Command, opts?: {
49
+ export declare function addPersonFilterFlags(cmd: Command, opts?: {
50
50
  allFlagName?: string;
51
51
  allFlagDescription?: string;
52
52
  }): Command;
@@ -82,7 +82,7 @@ export interface GlobalOpts {
82
82
  * True only when the user explicitly passed `--quiet` / `-q` (or `--get`).
83
83
  * Distinct from `quiet`, which also flips on for auto-quiet (the
84
84
  * piped-stdout/auto-JSON path). Use this for actionable hints (e.g. the
85
- * `profile list` pagination hint) that should still surface when an agent
85
+ * `person list` pagination hint) that should still surface when an agent
86
86
  * pipes output but hasn't asked for silence — auto-quiet is for progress
87
87
  * chatter, not diagnostic signals.
88
88
  */
@@ -141,8 +141,8 @@ export declare function resolveChatConfig(positional?: string, flag?: string): s
141
141
  export declare function collectRepeatable(value: string, prev?: string[]): string[];
142
142
  /**
143
143
  * Commander collector for ID-list flags. Accepts a comma-separated value
144
- * (`--profile a,b,c`) and also concatenates across repeated flags
145
- * (`--profile a --profile b`). Trims whitespace and drops empty entries.
144
+ * (`--person a,b,c`) and also concatenates across repeated flags
145
+ * (`--person a --person b`). Trims whitespace and drops empty entries.
146
146
  *
147
147
  * Use this for flags whose values are IDs/aliases — never for free-form
148
148
  * strings that may legitimately contain commas (those should use
@@ -157,11 +157,11 @@ export declare function parseWaitTimeout(raw: string | undefined, defaultMs?: nu
157
157
  * calls have populated the program tree. Agents reflexively pass `--workspace`
158
158
  * on any command — without this, Commander rejects it with a generic "unknown
159
159
  * option" on read-side commands like `ask delete`, `ask get`, `study get`,
160
- * `profile get`, etc.
160
+ * `person get`, etc.
161
161
  *
162
162
  * The injected option is documentation-only on commands where the workspace is
163
163
  * inferred from an ID alias; it's accepted and then discarded by the action
164
- * body. Resolvers (`resolveWorkspace`, `resolveAudienceProfileIds`) ignore
164
+ * body. Resolvers (`resolveWorkspace`, `resolvePersonIds`) ignore
165
165
  * unused values.
166
166
  */
167
167
  /**
@@ -21,6 +21,18 @@ function shuffleInPlace(arr) {
21
21
  }
22
22
  return arr;
23
23
  }
24
+ // Wire-vocabulary aliases for `--visibility`: old → new. The backend still
25
+ // accepts the old values but logs a deprecation warning, so we map them
26
+ // client-side before they hit the wire. Canonical values pass through.
27
+ const VISIBILITY_ALIASES = {
28
+ private: "workspace",
29
+ public: "platform",
30
+ };
31
+ function normalizeVisibility(raw) {
32
+ if (raw === undefined)
33
+ return undefined;
34
+ return VISIBILITY_ALIASES[raw] ?? raw;
35
+ }
24
36
  function parseSampleSize(raw) {
25
37
  if (raw === undefined)
26
38
  return undefined;
@@ -47,11 +59,11 @@ function describeFilters(flags) {
47
59
  if (flags.maxAge)
48
60
  parts.push(`--max-age ${flags.maxAge}`);
49
61
  if (flags.visibility)
50
- parts.push(`--visibility ${flags.visibility}`);
62
+ parts.push(`--visibility ${normalizeVisibility(flags.visibility)}`);
51
63
  return parts.join(" ");
52
64
  }
53
65
  /**
54
- * Best-effort: surface the top-3 populated countries so an empty-audience
66
+ * Best-effort: surface the top-3 populated countries so an empty-result
55
67
  * error doesn't strand the agent without a pivot. Two tiers — tier 1 keeps
56
68
  * non-country filters and drops `--country` (used when `--country` was the
57
69
  * binding constraint); tier 2 fires when tier 1 returns empty (or when
@@ -91,9 +103,9 @@ async function suggestCountries(client, workspace, flags, opts) {
91
103
  if (flags.maxAge)
92
104
  broader.max_age = flags.maxAge;
93
105
  if (flags.visibility)
94
- broader.visibility = flags.visibility;
106
+ broader.visibility = normalizeVisibility(flags.visibility);
95
107
  }
96
- const data = await client.get("/tester-profiles", broader);
108
+ const data = await client.get("/people", broader);
97
109
  const items = Array.isArray(data)
98
110
  ? data
99
111
  : Array.isArray(data?.items)
@@ -135,36 +147,36 @@ function hasFilterFlag(flags) {
135
147
  || flags.maxAge
136
148
  || flags.visibility);
137
149
  }
138
- /** True when any audience-selection flag is set on the command. */
139
- export function hasAudienceFlags(flags) {
140
- return Boolean((flags.profile && flags.profile.length > 0)
150
+ /** True when any person-selection flag is set on the command. */
151
+ export function hasPersonFlags(flags) {
152
+ return Boolean((flags.person && flags.person.length > 0)
141
153
  || flags.sample !== undefined
142
154
  || flags.all
143
155
  || hasFilterFlag(flags));
144
156
  }
145
157
  /**
146
- * Resolve a tester-profile audience from CLI flags.
158
+ * Resolve person IDs from CLI flags.
147
159
  *
148
160
  * Modes (mutually exclusive):
149
- * - explicit: `--profile <id>` (repeatable) — returns those IDs verbatim.
161
+ * - explicit: `--person <id>` (repeatable) — returns those IDs verbatim.
150
162
  * - sample: `--sample N` + optional filter flags — fetches the matching
151
163
  * pool, randomly samples N.
152
164
  * - all: caller's "all" flag + optional filter flags — returns every
153
- * matching profile.
165
+ * matching person.
154
166
  * - filtered: filter flags only — treated as "all matching".
155
167
  *
156
168
  * Always sends `type=ai` to the backend (simulations are AI-driven).
157
- * If `requireSimulatable`, applies a client-side filter for profiles that
169
+ * If `requireSimulatable`, applies a client-side filter for people that
158
170
  * have a simulation config attached.
159
171
  */
160
- export async function resolveAudienceProfileIds(client, workspace, flags, opts = {}) {
172
+ export async function resolvePersonIds(client, workspace, flags, opts = {}) {
161
173
  const allFlagName = opts.allFlagName ?? "--all";
162
- const explicit = (flags.profile ?? []).map(resolveId);
174
+ const explicit = (flags.person ?? []).map(resolveId);
163
175
  const sampleN = parseSampleSize(flags.sample);
164
176
  const filtersUsed = hasFilterFlag(flags);
165
177
  if (explicit.length > 0) {
166
178
  if (sampleN !== undefined || flags.all || filtersUsed) {
167
- throw new Error(`Use either explicit --profile flags or --sample/${allFlagName}/filter flags (--bio, --country, --gender, --min-age, --max-age, --occupation, --search, --visibility), not both.`);
179
+ throw new Error(`Use either explicit --person flags or --sample/${allFlagName}/filter flags (--bio, --country, --gender, --min-age, --max-age, --occupation, --search, --visibility), not both.`);
168
180
  }
169
181
  return explicit;
170
182
  }
@@ -172,7 +184,7 @@ export async function resolveAudienceProfileIds(client, workspace, flags, opts =
172
184
  throw new Error(`Use either --sample <N> or ${allFlagName}, not both. --sample picks a random subset; ${allFlagName} returns every match.`);
173
185
  }
174
186
  if (sampleN === undefined && !flags.all && !filtersUsed) {
175
- throw new Error(`Pick an audience: pass --profile <id> (repeatable), --sample <N>, ${allFlagName}, or filter flags (--bio, --country, --gender, --min-age, --max-age, --occupation, --search, --visibility).`);
187
+ throw new Error(`Select people: pass --person <id> (repeatable), --sample <N>, ${allFlagName}, or filter flags (--bio, --country, --gender, --min-age, --max-age, --occupation, --search, --visibility).`);
176
188
  }
177
189
  const params = {
178
190
  product_id: workspace,
@@ -195,8 +207,8 @@ export async function resolveAudienceProfileIds(client, workspace, flags, opts =
195
207
  if (flags.maxAge)
196
208
  params.max_age = flags.maxAge;
197
209
  if (flags.visibility)
198
- params.visibility = flags.visibility;
199
- const data = await client.get("/tester-profiles", params);
210
+ params.visibility = normalizeVisibility(flags.visibility);
211
+ const data = await client.get("/people", params);
200
212
  const items = Array.isArray(data)
201
213
  ? data
202
214
  : Array.isArray(data?.items)
@@ -213,10 +225,10 @@ export async function resolveAudienceProfileIds(client, workspace, flags, opts =
213
225
  const filterDesc = describeFilters(flags);
214
226
  const sim = opts.requireSimulatable ? "simulatable AI " : "AI ";
215
227
  if (opts.excludeProfileIds && opts.excludeProfileIds.size > 0 && !filterDesc) {
216
- throw new Error("All matching profiles are already in this audience.");
228
+ throw new Error("All matching people are already included.");
217
229
  }
218
230
  // Country suggestion: surface the top populated countries so the agent
219
- // doesn't have to round-trip through `ish profile list` to find one that
231
+ // doesn't have to round-trip through `ish person list` to find one that
220
232
  // matches. Two tiers — tier 1 drops `--country` only and retains other
221
233
  // filters when `--country` was set (so the hint reflects "of countries
222
234
  // that match your other filters, here are the populated ones"). Tier 2
@@ -228,15 +240,15 @@ export async function resolveAudienceProfileIds(client, workspace, flags, opts =
228
240
  requireSimulatable: !!opts.requireSimulatable,
229
241
  });
230
242
  if (filterDesc) {
231
- throw new Error(`No ${sim}tester profiles in workspace ${workspace} match: ${filterDesc}.${suggestion} Broaden your filters or run \`ish profile list\` to inspect the pool.`);
243
+ throw new Error(`No ${sim}people in workspace ${workspace} match: ${filterDesc}.${suggestion} Broaden your filters or run \`ish person list\` to inspect the pool.`);
232
244
  }
233
- throw new Error(`No ${sim}tester profiles found in workspace ${workspace}.${opts.requireSimulatable ? " Create profiles with simulation configs first." : ""}`);
245
+ throw new Error(`No ${sim}people found in workspace ${workspace}.${opts.requireSimulatable ? " Create people with simulation configs first." : ""}`);
234
246
  }
235
247
  if (flags.all)
236
248
  return pool.map((p) => p.id);
237
249
  if (sampleN !== undefined) {
238
250
  if (sampleN > pool.length) {
239
- throw new Error(`--sample ${sampleN} requested but only ${pool.length} matching profile${pool.length === 1 ? "" : "s"} available.`);
251
+ throw new Error(`--sample ${sampleN} requested but only ${pool.length} matching person${pool.length === 1 ? "" : "s"} available.`);
240
252
  }
241
253
  return shuffleInPlace([...pool]).slice(0, sampleN).map((p) => p.id);
242
254
  }
@@ -244,25 +256,25 @@ export async function resolveAudienceProfileIds(client, workspace, flags, opts =
244
256
  return pool.map((p) => p.id);
245
257
  }
246
258
  /**
247
- * Attach the audience-selection flag set to a Commander command.
259
+ * Attach the person-selection flag set to a Commander command.
248
260
  * Pass `allFlagName: "--all-simulatable"` for ask commands to keep the
249
261
  * established flag name; defaults to `--all` for study run.
250
262
  */
251
- export function addAudienceFilterFlags(cmd, opts = {}) {
263
+ export function addPersonFilterFlags(cmd, opts = {}) {
252
264
  const allFlag = opts.allFlagName ?? "--all";
253
- const allDesc = opts.allFlagDescription ?? "Use every profile matching the filters";
265
+ const allDesc = opts.allFlagDescription ?? "Use every person matching the filters";
254
266
  return cmd
255
- .option("--profile <ids>", "Tester profile IDs/aliases (comma-separated or repeatable)", collectIds, [])
256
- .option("--sample <N>", "Randomly sample N profiles from the matching pool")
267
+ .option("--person <ids>", "Person IDs/aliases (comma-separated or repeatable)", collectIds, [])
268
+ .option("--sample <N>", "Randomly sample N people from the matching pool")
257
269
  .option(allFlag, allDesc)
258
- .option("--search <text>", "Substring match against profile name")
259
- .option("--bio <text>", "Substring match against profile bio")
260
- .option("--occupation <text>", "Substring match against profile occupation (repeatable)", collectRepeatable, [])
270
+ .option("--search <text>", "Substring match against person name")
271
+ .option("--bio <text>", "Substring match against person bio")
272
+ .option("--occupation <text>", "Substring match against person occupation (repeatable)", collectRepeatable, [])
261
273
  .option("--gender <gender>", "Filter by gender (repeatable)", collectRepeatable, [])
262
274
  .option("--country <code>", "Filter by 2-letter country code (repeatable)", collectRepeatable, [])
263
275
  .option("--min-age <n>", "Minimum age (inclusive)")
264
276
  .option("--max-age <n>", "Maximum age (inclusive)")
265
- .option("--visibility <v>", "Filter by visibility: workspace (your workspace), shared (community-published), platform (admin-curated). Old values `private` / `public` still accepted as aliases for `workspace` / `platform` until the next release; server logs a deprecation warning.");
277
+ .option("--visibility <v>", "Filter by visibility: workspace (your workspace), shared (community-published), platform (admin-curated). Old values `private` / `public` are accepted and mapped to `workspace` / `platform`.");
266
278
  }
267
279
  export function getGlobals(cmd) {
268
280
  let globals;
@@ -367,7 +379,7 @@ export function exitCodeFromError(err) {
367
379
  if (/no auth token found|run "ish login"|saved token is invalid|session expired/i.test(err.message))
368
380
  return 3;
369
381
  // Client-side validation failures
370
- if (err.name === "ValidationError" || /^invalid |^cannot read |is empty:|--\w[\w-]* must be|pick an audience|use either /i.test(err.message))
382
+ if (err.name === "ValidationError" || /^invalid |^cannot read |is empty:|--\w[\w-]* must be|select people|use either /i.test(err.message))
371
383
  return 2;
372
384
  // Errors that pre-declare their own retryability / error_code take
373
385
  // precedence — WaitTimeoutError sets `error_code: "wait_timeout"`
@@ -655,8 +667,8 @@ export function collectRepeatable(value, prev = []) {
655
667
  }
656
668
  /**
657
669
  * Commander collector for ID-list flags. Accepts a comma-separated value
658
- * (`--profile a,b,c`) and also concatenates across repeated flags
659
- * (`--profile a --profile b`). Trims whitespace and drops empty entries.
670
+ * (`--person a,b,c`) and also concatenates across repeated flags
671
+ * (`--person a --person b`). Trims whitespace and drops empty entries.
660
672
  *
661
673
  * Use this for flags whose values are IDs/aliases — never for free-form
662
674
  * strings that may legitimately contain commas (those should use
@@ -680,7 +692,7 @@ const WORKSPACE_SCOPED_GROUPS = new Set([
680
692
  "ask",
681
693
  "study",
682
694
  "iteration",
683
- "profile",
695
+ "person",
684
696
  "source",
685
697
  ]);
686
698
  /**
@@ -689,11 +701,11 @@ const WORKSPACE_SCOPED_GROUPS = new Set([
689
701
  * calls have populated the program tree. Agents reflexively pass `--workspace`
690
702
  * on any command — without this, Commander rejects it with a generic "unknown
691
703
  * option" on read-side commands like `ask delete`, `ask get`, `study get`,
692
- * `profile get`, etc.
704
+ * `person get`, etc.
693
705
  *
694
706
  * The injected option is documentation-only on commands where the workspace is
695
707
  * inferred from an ID alias; it's accepted and then discarded by the action
696
- * body. Resolvers (`resolveWorkspace`, `resolveAudienceProfileIds`) ignore
708
+ * body. Resolvers (`resolveWorkspace`, `resolvePersonIds`) ignore
697
709
  * unused values.
698
710
  */
699
711
  /**