@ishlabs/cli 0.8.3 → 0.8.5

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.
@@ -96,6 +96,10 @@ use **ask** for quick reactions to text/image variants.
96
96
  ## High-frequency commands
97
97
 
98
98
  \`\`\`bash
99
+ # First command on a cold start — confirms login + active context:
100
+ ish status # or: ish whoami
101
+ # → user, active workspace/study/ask, token validity, API url
102
+
99
103
  # Auth & active selection (saved to ~/.ish/config.json)
100
104
  ish login
101
105
  ish workspace use w-6ec
@@ -136,15 +140,129 @@ See \`references/workflows.md\` in this skill for end-to-end transcripts:
136
140
  - Targeting a gated URL (basic auth, session cookie, login form)
137
141
  - Re-running a study with a fresh audience
138
142
 
143
+ ## Display vs. capture: the right output mode
144
+
145
+ Three output modes — pick the one matching your intent, **don't reach
146
+ for \`jq\` / \`python\` reflexively**:
147
+
148
+ | Intent | Use |
149
+ |-------------------------------------------------|----------------------|
150
+ | Show the user a list/table | bare command (TTY) or \`--human\` |
151
+ | Capture one value to feed into the next command | \`--get <field>\` |
152
+ | Parse multiple fields / nested shape | \`--json\` |
153
+
154
+ \`--get\` extracts a single field from the JSON response and prints its
155
+ bare value. It supports dotted paths and auto-descends into list
156
+ \`items\` so \`--get alias\` on a paginated list yields one alias per
157
+ line. \`--human\` forces human output even when stdout is piped — use
158
+ it when you want to \`tee\` a table to a file but still show it. The
159
+ two flags are mutually exclusive (capture and display are different
160
+ intents).
161
+
162
+ ### Worked example — capture in a script, display to the user
163
+
164
+ \`\`\`bash
165
+ # DON'T: shim around the CLI with jq just to grab one value.
166
+ # ASK=$(ish ask create … --json | jq -r .alias)
167
+
168
+ # DO: capture mode — bare value, exit 0.
169
+ ASK=$(ish ask create --new --name demo \\
170
+ --prompt "Which?" --variant text:A --variant text:B \\
171
+ --sample 30 --get alias)
172
+
173
+ # DON'T: pipe --json through jq when you want to show the user a table.
174
+ # ish ask results "$ASK" --json | jq … | tee /tmp/x.txt
175
+
176
+ # DO: --human keeps the table layout even through tee.
177
+ ish ask results "$ASK" --human | tee /tmp/transcript.txt
178
+ \`\`\`
179
+
180
+ Missing field on \`--get\` → exit 2 with a usage error. \`--get\` also
181
+ implies \`--quiet\` so the bare value is the only thing on stdout.
182
+
139
183
  ## Output handling
140
184
 
141
185
  - Every command supports \`--json\`. JSON mode is **auto-enabled when
142
186
  stdout is piped**, so an agent rarely needs \`--json\` explicitly.
187
+ - **\`--get <field>\` is the right way to capture a single value.**
188
+ Dotted paths supported (\`tester_profile.name\`); on a paginated
189
+ \`{items: [...]}\` response, a leading non-\`items\` segment
190
+ auto-descends into items. Replaces the
191
+ \`--json | jq -r .field\` shim. Implies \`--json\` and \`--quiet\`.
192
+ - **\`--human\` forces human output even when stdout is piped.** Use it
193
+ to \`tee\` a table without losing the layout. Mutually exclusive
194
+ with \`--get\`.
143
195
  - \`--fields a,b,c\` strips JSON output to the listed fields (saves
144
196
  tokens). \`--verbose\` adds full UUIDs and timestamps.
197
+ - **Stdout is data only.** All progress, status, and "Open in browser"
198
+ hints go to stderr; \`--json | jq -e .\` parses cleanly without
199
+ defensive piping.
200
+ - **List responses are a six-key envelope:** \`{items, total, returned,
201
+ limit, offset, has_more}\`. Use \`has_more\` to detect truncation;
202
+ don't count items yourself.
203
+ - **Use \`runtime_status\`, not \`status\`, on study responses.** Values:
204
+ \`draft | running | completed | completed_with_errors | cancelled\`.
205
+ Derived from iteration testers' actual state — never reports
206
+ \`failed\` while completed runs exist. The CLI also surfaces
207
+ \`status_inferred\` + a stderr warning when raw \`status\` and the
208
+ testers disagree.
209
+ - **\`study generate --json\` returns \`modality_rationale\`** (one
210
+ short sentence). Inspect it before adding iterations; if the LLM
211
+ picked the wrong modality, override via
212
+ \`ish study update <id> --modality text\`.
213
+ - **Failed testers expose \`error_message\`.** \`study tester --json\`
214
+ and \`study results --json\` (in \`testers[]\`) include
215
+ \`error_message: "<reason>"\` for any tester with \`status: failed\`.
216
+ Don't drill into logs — read the field. \`study results\` also
217
+ includes a top-level \`failed_count\` alongside \`completed_count\`.
218
+ - **\`ask add-questions\` is additive by default.** Appending a
219
+ follow-up question to a completed round preserves prior comments,
220
+ picks, and ratings; only the new question is dispatched. Pass
221
+ \`--redispatch-all\` for the legacy reset-and-rerun behavior.
222
+ - **\`ask results --json\` adds \`cross_round_summary\` for 2+ rounds.**
223
+ Top-level field with per-round picks/winner snapshots and
224
+ \`picks_delta\` (R1 → last). Don't diff two \`ask results\` calls by
225
+ hand.
226
+ - **\`--workspace\` works at the program root AND every subcommand.**
227
+ \`ish --workspace w-6ec study list\` and \`ish study list --workspace
228
+ w-6ec\` are equivalent; if both are passed, the subcommand-level
229
+ flag wins. Without either, the CLI falls back to \`ISH_WORKSPACE\`
230
+ env then the active workspace in \`~/.ish/config.json\`.
231
+ - **\`profile generate\` emits stderr progress.** \`generating N
232
+ profiles…\` then \`generated N profiles\` around the ~10–20s LLM
233
+ call. Suppress with \`--quiet\`. Generated bios reference the
234
+ brief's domain context naturally (occupation, daily work,
235
+ frustrations) — they no longer parrot vocabulary from the brief
236
+ verbatim. DOBs spread across the year instead of all-on-\`06-15\`.
237
+ - **Empty-pool errors include a country-suggestion line.** When
238
+ \`study run\` / \`ask run --new\` rejects because \`--country XX\`
239
+ matched zero profiles, the error includes the top-3 populated
240
+ countries that satisfy your *other* filters. Pivot directly without
241
+ a second \`profile list\` round-trip.
242
+ - **\`<entity> list\` emits a stderr pagination hint** when
243
+ \`has_more=true\` and \`--quiet\` is unset. Goes to stderr in **every
244
+ mode** (including \`--json\` and piped stdout) — it never pollutes
245
+ machine-readable stdout but is visible to any agent reading stderr.
246
+ Format: "showing N–M of TOTAL; pass --offset M --limit N for more."
247
+ - **\`study delete\` requires explicit confirmation.** Interactive:
248
+ prompts on stderr. Non-interactive (\`--json\`, piped, non-TTY
249
+ stdin): pass \`-y\` / \`--yes\` to confirm. Without it, the CLI
250
+ exits with usage code 2.
251
+ - **\`ask add-questions\` supports \`--wait\` / \`--timeout\`.** Match
252
+ the parity of \`ask create\` and \`ask run\`. Without \`--wait\` the
253
+ command returns after dispatch (round still running).
254
+ - **\`pick_confidence\` (0..1) is on every \`--wants-pick\` response.**
255
+ The model's self-reported confidence in its variant choice. Use it
256
+ to break ties when nominal pick counts are close. See
257
+ \`ish docs get-page concepts/ask\`.
145
258
  - Exit codes carry meaning: 0 success, 2 usage/validation,
146
259
  3 auth, 4 not-found, 5 transient. See
147
260
  \`ish docs get-page reference/json-mode\`.
261
+ - **Tier limits surface as \`error_code: "usage_limit_reached"\`**
262
+ (HTTP 403, exit 1, non-retryable). The error body includes
263
+ \`tier\`, \`limit\`, \`current\`, \`max\`, \`upgrade_url\`. Do not
264
+ retry — branch on the code and surface the upgrade link. See
265
+ \`ish docs get-page reference/billing-limits\`.
148
266
  - Aliases (\`s-…\`, \`a-…\`, \`tp-…\`, \`i-…\`, \`t-…\`, \`tps-…\`, \`w-…\`)
149
267
  are accepted anywhere a UUID is. See
150
268
  \`ish docs get-page reference/aliases\`.
@@ -153,22 +271,42 @@ See \`references/workflows.md\` in this skill for end-to-end transcripts:
153
271
 
154
272
  1. **Don't paste flags from memory.** The CLI evolves; flags change.
155
273
  Run \`ish <command> --help\` to confirm before constructing a command.
156
- 2. **Don't run \`ish study run\` before an iteration exists** — create
157
- one first via \`ish iteration create --url …\` (or \`--content-url …\`
158
- for media studies). The error message tells you this, but the round
159
- trip wastes time.
160
- 3. **Don't pass \`--profile\` together with demographic filters** — they
274
+ 2. **Don't pipe \`--json\` through \`python\`/\`jq\` to reshape output** —
275
+ the CLI already has the affordances:
276
+ - Inspect a few specific entities? \`ish profile get tp-1b9 tp-fc1
277
+ tp-2fc\` (also works for \`study get\`, \`iteration get\`, \`ask
278
+ get\`). Returns a \`{items:[...], total:N}\` envelope.
279
+ - Want only certain fields? \`--fields alias,name,country,occupation\`.
280
+ - Need counts of a nested array? \`ask get\` / \`ask create --wait\`
281
+ already include \`testers_count\`, \`responses_total\`,
282
+ \`responses_complete\` (per-round and aggregate). Don't recount.
283
+ - Want machine-readable A/B verdicts? \`ask results --json\` already
284
+ ships \`aggregates: { picks, ratings, winner }\` per round.
285
+ 3. **Don't run \`ish study run\` against an empty study.** \`ish study
286
+ create\` and \`ish study generate\` no longer auto-create iteration
287
+ A — the first explicit \`ish iteration create\` becomes A. Running
288
+ \`study run\` on a study with zero iterations exits 2; create one
289
+ first via \`ish iteration create --url …\` / \`--content-url …\` /
290
+ \`--content-text …\`. Or pass \`--content-text\` / \`--url\` directly
291
+ on \`study create\` for a one-shot study + iteration A.
292
+ 4. **Don't pass \`--profile\` together with demographic filters** — they
161
293
  are mutually exclusive. Either explicit IDs or
162
294
  \`--country\`/\`--gender\`/\`--min-age\`/\`--max-age\` + \`--sample\`.
163
- 4. **Don't change audience between rounds of an ask.** It's fixed at
295
+ 5. **Don't change audience between rounds of an ask.** It's fixed at
164
296
  ask creation. Use \`ish ask add-testers\` to *extend* it; you can't
165
297
  replace it.
166
- 5. **Don't try to put credentials in the URL** for gated study URLs.
298
+ 6. **Don't try to put credentials in the URL** for gated study URLs.
167
299
  Configure them once on the workspace via
168
300
  \`ish workspace site-access …\` (basic-auth, cookie, login).
169
301
  See \`ish docs get-page concepts/site-access\`.
170
- 6. **Don't commit \`~/.ish/config.json\`** — it stores tokens and active
302
+ 7. **Don't commit \`~/.ish/config.json\`** — it stores tokens and active
171
303
  workspace/study/ask selections. It lives in \`$HOME\`, not the repo.
304
+ 8. **Don't retry \`usage_limit_reached\` errors.** Tier caps
305
+ (\`maxProducts\`, \`maxStudiesPerProduct\`, \`maxIterationsPerStudy\`,
306
+ \`maxCustomTesterProfiles\`) are enforced server-side. The error body
307
+ carries \`tier\`, \`limit\`, \`current\`, \`max\`, \`upgrade_url\` — show
308
+ the upgrade link or delete an existing resource to free headroom.
309
+ See \`ish docs get-page reference/billing-limits\` for the table.
172
310
 
173
311
  ## Authentication
174
312
 
@@ -238,9 +376,17 @@ ish ask run --new --name "hero shots" \\
238
376
  --variant image:./hero-b.png::label=B \\
239
377
  --sample 30 --wants-pick --wait
240
378
 
241
- ish ask results --json | jq .
379
+ # Read the verdict directly no comment-parsing required:
380
+ ish ask results --json | jq '.rounds[0].aggregates'
381
+ # → { "picks": { "A": 22, "B": 8 },
382
+ # "winner": { "letter": "A", "count": 22, "tied": false } }
242
383
  \`\`\`
243
384
 
385
+ For \`--wants-pick\` / \`--wants-ratings\` rounds, \`ask results --json\`
386
+ adds an \`aggregates\` field per round with \`picks\`, \`ratings\` (mean
387
+ + n per variant), and a \`winner\`. See \`ish docs get-page
388
+ reference/json-mode\` for the full shape.
389
+
244
390
  Add a follow-up round with no audience change:
245
391
 
246
392
  \`\`\`bash
@@ -334,6 +480,35 @@ URL=$(jq -r 'select(.status=="connected") | .tunnel_url' /tmp/ish-tunnel.log | h
334
480
  ish iteration create --url "$URL"
335
481
  \`\`\`
336
482
 
483
+ ## 7. Display-vs-capture: a script that does both
484
+
485
+ Goal: drive an A/B in a script, capture aliases without \`jq\`, and
486
+ still show the human a readable result table at the end.
487
+
488
+ \`\`\`bash
489
+ # Capture mode — bare values, suitable for shell variables.
490
+ ASK=$(ish ask create --new --name "tagline AB" \\
491
+ --prompt "Which sounds better?" \\
492
+ --variant text:"Short and punchy." \\
493
+ --variant text:"A longer, descriptive line." \\
494
+ --sample 30 --wants-pick --get alias)
495
+
496
+ # Wait silently — exit code is what matters here.
497
+ ish ask wait "$ASK" --timeout 600 --quiet
498
+
499
+ # Capture the winner letter for downstream branching:
500
+ WINNER=$(ish ask results "$ASK" --get rounds.aggregates.winner.letter)
501
+ echo "Winning variant: $WINNER"
502
+
503
+ # Display mode — show the user the full results table even though
504
+ # we're inside a script (stdout is piped to tee).
505
+ ish ask results "$ASK" --human | tee "/tmp/ask-\${ASK}.txt"
506
+ \`\`\`
507
+
508
+ The mental rule: **\`--get\` is for capture, bare commands / \`--human\`
509
+ are for display, \`--json\` is for chaining (multiple fields at once).**
510
+ If you find yourself reaching for \`jq -r .x\`, you wanted \`--get x\`.
511
+
337
512
  ## Tips for chaining commands as an agent
338
513
 
339
514
  - Capture aliases from JSON: \`ITER=$(ish iteration create --url … --json | jq -r .alias)\`
@@ -348,6 +523,31 @@ ish iteration create --url "$URL"
348
523
  the full Ask payload.
349
524
  - For \`profile generate --json\`, \`simulation_config\` is trimmed by
350
525
  default (~9× smaller). Pass \`--include-simulation-config\` to include it.
526
+ - On \`error_code: "usage_limit_reached"\` (HTTP 403), don't retry —
527
+ read \`tier\`, \`limit\`, \`current\`, \`max\`, and \`upgrade_url\` from
528
+ the JSON body to construct a recovery message. \`profile generate\` /
529
+ \`study generate\` refuse the entire batch when the post-generation
530
+ count would exceed the cap; re-issue with a smaller \`--count\`.
531
+
532
+ ## Common reshaping → use the CLI, not jq/python
533
+
534
+ | You want to… | Don't | Do |
535
+ |-------------------------------------------|----------------------------------------|--------------------------------------------------------------------|
536
+ | Capture a single value (alias, id, …) | \`--json \\| jq -r .alias\` | \`--get alias\` |
537
+ | Capture a nested value | \`--json \\| jq -r .tester_profile.name\` | \`--get tester_profile.name\` |
538
+ | Capture every alias from a list | \`--json \\| jq -r '.items[].alias'\` | \`--get alias\` (auto-descends into \`items\`, one per line) |
539
+ | Force human output through tee/redirect | none, output silently became JSON | \`--human\` |
540
+ | Look up 2-3 specific profiles | \`profile list --json \\| jq '.items[] \\| select(...)'\` | \`ish profile get tp-1b9 tp-fc1 tp-2fc\` |
541
+ | Show only some fields | \`--json \\| jq '{alias, name, country}'\` | \`--fields alias,name,country\` |
542
+ | Count testers on an ask | \`--json \\| jq '.testers \\| length'\` | \`ish ask get a-… --fields alias,testers_count\` |
543
+ | Count responses on a round | \`--json \\| jq '.rounds[0].responses \\| length'\` | \`ish ask get a-… --fields alias,rounds,responses_complete,responses_total\` |
544
+ | Pick the A/B winner | \`--json \\| jq '.rounds[0].responses…'\` | \`ish ask results a-… --json\` then read \`.rounds[].aggregates.winner\` |
545
+ | List of testers from \`study run\` | \`--json \\| jq '.testers[].id'\` | \`--get tester_aliases\` (or \`tester_ids\` for UUIDs) |
546
+
547
+ The bias here is intentional: \`ish\` ships shapes designed for agent
548
+ consumption. If you find yourself reaching for \`jq\` or \`python\` to
549
+ reshape ish output, run \`ish docs search <keyword>\` first — there is
550
+ almost always a flag that does the work for you.
351
551
  `;
352
552
  const COMMANDS_MD = `# ish commands — quick index
353
553
 
@@ -376,6 +576,8 @@ ish <command> --help
376
576
  | | Cursor / Cline / Roo project | |
377
577
  | \`login\` | Browser-based auth | — |
378
578
  | \`logout\` | Clear saved credentials | — |
579
+ | \`status\` | Show active session (user, workspace, | concepts/active-context |
580
+ | | study, ask, token validity) — alias \`whoami\` | |
379
581
  | \`connect\` | Cloudflare tunnel exposing localhost | — |
380
582
  | \`upgrade\` | Self-update | — |
381
583
 
@@ -403,6 +605,8 @@ is expected. Full UUIDs always work too. See
403
605
 
404
606
  | Flag | Effect |
405
607
  |------------------|----------------------------------------------------------|
608
+ | \`--get <field>\` | **Capture mode.** Print the bare value at the dotted path; auto-descends into list \`items\`. Implies \`--json\` + \`--quiet\`. Replaces \`--json \\| jq -r .field\`. |
609
+ | \`--human\` | **Force display mode** even when stdout is piped (overrides JSON-when-piped). Mutually exclusive with \`--get\`. |
406
610
  | \`--json\` | JSON output (auto-on when stdout is piped) |
407
611
  | \`--fields a,b\` | Keep only listed fields in JSON |
408
612
  | \`--verbose\` | Include UUIDs + timestamps in JSON |
@@ -410,6 +614,9 @@ is expected. Full UUIDs always work too. See
410
614
  | \`-t, --token\` | Auth token (else ISH_TOKEN env, else \`ish login\` saved) |
411
615
  | \`--api-url\` | Override backend (default https://api.ishlabs.io) |
412
616
 
617
+ See \`ish docs get-page reference/json-mode\` for the full display-vs-
618
+ capture-vs-chain decision rule.
619
+
413
620
  ## Exit codes
414
621
 
415
622
  \`0\` ok · \`1\` general · \`2\` usage/validation · \`3\` auth ·
@@ -97,6 +97,7 @@ export interface StudyCreateInput {
97
97
  assignments?: Assignment[];
98
98
  interview_questions?: InterviewQuestion[];
99
99
  start_frame_id?: string;
100
+ iteration?: IterationCreateInput;
100
101
  }
101
102
  export interface StudyUpdateInput {
102
103
  name?: string;
@@ -124,7 +125,7 @@ export interface Iteration {
124
125
  updated_at: string;
125
126
  }
126
127
  export interface IterationCreateInput {
127
- name: string;
128
+ name?: string;
128
129
  description?: string;
129
130
  details?: Record<string, unknown>;
130
131
  }
@@ -290,6 +291,7 @@ export interface AddTestersInput {
290
291
  }
291
292
  export interface AddRoundQuestionsInput {
292
293
  questions: InterviewQuestion[];
294
+ redispatch_all?: boolean;
293
295
  }
294
296
  export interface AskAudienceTester {
295
297
  id: string;
package/dist/upgrade.js CHANGED
@@ -40,10 +40,10 @@ export async function upgrade(currentVersion, targetVersion) {
40
40
  }
41
41
  const latest = targetVersion || (await getLatestVersion());
42
42
  if (latest === currentVersion) {
43
- console.log(`Already up to date (v${currentVersion}).`);
43
+ console.error(`Already up to date (v${currentVersion}).`);
44
44
  return;
45
45
  }
46
- console.log(`Updating ish v${currentVersion} → v${latest}...`);
46
+ console.error(`Updating ish v${currentVersion} → v${latest}...`);
47
47
  const target = getPlatformTarget();
48
48
  const ext = process.platform === "win32" ? ".exe" : "";
49
49
  const assetName = `ish-${target}${ext}`;
@@ -100,5 +100,5 @@ export async function upgrade(currentVersion, targetVersion) {
100
100
  chmodSync(tmpPath, 0o755);
101
101
  renameSync(tmpPath, execPath);
102
102
  }
103
- console.log(`Updated to v${latest}.`);
103
+ console.error(`Updated to v${latest}.`);
104
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ishlabs/cli",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "The command-line interface for Ish",
5
5
  "type": "module",
6
6
  "bin": {