@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
|
@@ -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,
|
|
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
|
|
14
|
-
readonly
|
|
15
|
-
readonly
|
|
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,
|
|
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.
|
package/dist/lib/alias-store.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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,
|
|
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.
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -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
|
-
|
|
37
|
+
participant_id: string;
|
|
38
38
|
study_id: string;
|
|
39
39
|
product_id: string;
|
|
40
40
|
iteration_id: string;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -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 →
|
|
221
|
+
// Deletes typically cascade (rounds → responses → participants → people),
|
|
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}`;
|
package/dist/lib/billing.d.ts
CHANGED
|
@@ -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
|
|
11
|
-
* chat, ×2 for
|
|
12
|
-
*
|
|
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: "
|
|
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-
|
|
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
|
-
|
|
36
|
+
participantCount: number;
|
|
37
37
|
maxInteractions: number;
|
|
38
38
|
}): CreditEstimate;
|
|
39
|
-
/** Solo chat (single
|
|
39
|
+
/** Solo chat (single participant, external chatbot). */
|
|
40
40
|
export declare function estimateChatSolo(args: {
|
|
41
|
-
|
|
41
|
+
participantCount: number;
|
|
42
42
|
maxTurns: number;
|
|
43
43
|
}): CreditEstimate;
|
|
44
|
-
/**
|
|
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
|
|
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
|
-
|
|
54
|
+
participantCount: number;
|
|
55
55
|
}): CreditEstimate;
|
package/dist/lib/billing.js
CHANGED
|
@@ -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
|
|
11
|
-
* chat, ×2 for
|
|
12
|
-
*
|
|
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-
|
|
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
|
|
35
|
-
const total = Math.max(0, args.
|
|
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: "
|
|
39
|
-
breakdown: `${args.
|
|
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
|
|
43
|
+
/** Solo chat (single participant, external chatbot). */
|
|
44
44
|
export function estimateChatSolo(args) {
|
|
45
|
-
const
|
|
46
|
-
const total = Math.max(0, args.
|
|
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.
|
|
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
|
-
/**
|
|
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
|
|
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.
|
|
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.
|
|
74
|
+
breakdown: `${args.participantCount} participant(s) × 1 credit/response = ${total} (upper bound; only successful responses bill)`,
|
|
75
75
|
unit: "credits",
|
|
76
76
|
};
|
|
77
77
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { Command } from "commander";
|
|
6
6
|
import { ApiClient } from "./api-client.js";
|
|
7
|
-
export interface
|
|
8
|
-
|
|
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
|
|
20
|
+
export interface PersonResolveOpts {
|
|
21
21
|
requireSimulatable?: boolean;
|
|
22
22
|
allFlagName?: string;
|
|
23
|
-
/** Profile IDs to exclude from the fetched pool (e.g.
|
|
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
|
|
27
|
-
export declare function
|
|
26
|
+
/** True when any person-selection flag is set on the command. */
|
|
27
|
+
export declare function hasPersonFlags(flags: PersonFilterOpts): boolean;
|
|
28
28
|
/**
|
|
29
|
-
* Resolve
|
|
29
|
+
* Resolve person IDs from CLI flags.
|
|
30
30
|
*
|
|
31
31
|
* Modes (mutually exclusive):
|
|
32
|
-
* - explicit: `--
|
|
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
|
|
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
|
|
40
|
+
* If `requireSimulatable`, applies a client-side filter for people that
|
|
41
41
|
* have a simulation config attached.
|
|
42
42
|
*/
|
|
43
|
-
export declare function
|
|
43
|
+
export declare function resolvePersonIds(client: ApiClient, workspace: string, flags: PersonFilterOpts, opts?: PersonResolveOpts): Promise<string[]>;
|
|
44
44
|
/**
|
|
45
|
-
* Attach the
|
|
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
|
|
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
|
-
* `
|
|
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
|
-
* (`--
|
|
145
|
-
* (`--
|
|
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
|
-
* `
|
|
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`, `
|
|
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-
|
|
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("/
|
|
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
|
|
139
|
-
export function
|
|
140
|
-
return Boolean((flags.
|
|
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
|
|
158
|
+
* Resolve person IDs from CLI flags.
|
|
147
159
|
*
|
|
148
160
|
* Modes (mutually exclusive):
|
|
149
|
-
* - explicit: `--
|
|
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
|
|
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
|
|
169
|
+
* If `requireSimulatable`, applies a client-side filter for people that
|
|
158
170
|
* have a simulation config attached.
|
|
159
171
|
*/
|
|
160
|
-
export async function
|
|
172
|
+
export async function resolvePersonIds(client, workspace, flags, opts = {}) {
|
|
161
173
|
const allFlagName = opts.allFlagName ?? "--all";
|
|
162
|
-
const explicit = (flags.
|
|
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 --
|
|
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(`
|
|
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("/
|
|
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
|
|
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
|
|
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}
|
|
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}
|
|
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
|
|
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
|
|
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
|
|
263
|
+
export function addPersonFilterFlags(cmd, opts = {}) {
|
|
252
264
|
const allFlag = opts.allFlagName ?? "--all";
|
|
253
|
-
const allDesc = opts.allFlagDescription ?? "Use every
|
|
265
|
+
const allDesc = opts.allFlagDescription ?? "Use every person matching the filters";
|
|
254
266
|
return cmd
|
|
255
|
-
.option("--
|
|
256
|
-
.option("--sample <N>", "Randomly sample N
|
|
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
|
|
259
|
-
.option("--bio <text>", "Substring match against
|
|
260
|
-
.option("--occupation <text>", "Substring match against
|
|
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`
|
|
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|
|
|
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
|
-
* (`--
|
|
659
|
-
* (`--
|
|
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
|
-
"
|
|
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
|
-
* `
|
|
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`, `
|
|
708
|
+
* body. Resolvers (`resolveWorkspace`, `resolvePersonIds`) ignore
|
|
697
709
|
* unused values.
|
|
698
710
|
*/
|
|
699
711
|
/**
|