@ishlabs/cli 0.8.1 → 0.8.3

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 (70) hide show
  1. package/README.md +323 -21
  2. package/dist/auth.d.ts +17 -1
  3. package/dist/auth.js +62 -9
  4. package/dist/commands/ask.d.ts +5 -0
  5. package/dist/commands/ask.js +722 -0
  6. package/dist/commands/config.js +25 -1
  7. package/dist/commands/docs.d.ts +17 -0
  8. package/dist/commands/docs.js +147 -0
  9. package/dist/commands/init.d.ts +16 -0
  10. package/dist/commands/init.js +182 -0
  11. package/dist/commands/iteration.d.ts +5 -1
  12. package/dist/commands/iteration.js +243 -31
  13. package/dist/commands/profile.d.ts +5 -0
  14. package/dist/commands/profile.js +313 -0
  15. package/dist/commands/source.d.ts +10 -0
  16. package/dist/commands/source.js +78 -0
  17. package/dist/commands/study-run.d.ts +11 -0
  18. package/dist/commands/study-run.js +552 -0
  19. package/dist/commands/study-tester.d.ts +8 -0
  20. package/dist/commands/study-tester.js +149 -0
  21. package/dist/commands/study.js +145 -70
  22. package/dist/commands/workspace.js +193 -7
  23. package/dist/config.d.ts +3 -1
  24. package/dist/config.js +10 -10
  25. package/dist/connect.d.ts +4 -1
  26. package/dist/connect.js +127 -94
  27. package/dist/index.js +82 -34
  28. package/dist/lib/alias-store.d.ts +3 -0
  29. package/dist/lib/alias-store.js +9 -7
  30. package/dist/lib/api-client.d.ts +9 -6
  31. package/dist/lib/api-client.js +87 -26
  32. package/dist/lib/ask-questions.d.ts +9 -0
  33. package/dist/lib/ask-questions.js +35 -0
  34. package/dist/lib/ask-variants.d.ts +48 -0
  35. package/dist/lib/ask-variants.js +236 -0
  36. package/dist/lib/auth.d.ts +1 -1
  37. package/dist/lib/auth.js +24 -8
  38. package/dist/lib/colors.d.ts +30 -0
  39. package/dist/lib/colors.js +48 -0
  40. package/dist/lib/command-helpers.d.ts +74 -0
  41. package/dist/lib/command-helpers.js +232 -6
  42. package/dist/lib/docs.d.ts +32 -0
  43. package/dist/lib/docs.js +930 -0
  44. package/dist/lib/local-sim/browser.d.ts +0 -1
  45. package/dist/lib/local-sim/browser.js +0 -2
  46. package/dist/lib/local-sim/install.d.ts +2 -12
  47. package/dist/lib/local-sim/install.js +22 -30
  48. package/dist/lib/output.d.ts +25 -3
  49. package/dist/lib/output.js +465 -20
  50. package/dist/lib/paths.d.ts +14 -0
  51. package/dist/lib/paths.js +36 -0
  52. package/dist/lib/profile-sources.d.ts +55 -0
  53. package/dist/lib/profile-sources.js +157 -0
  54. package/dist/lib/site-access.d.ts +80 -0
  55. package/dist/lib/site-access.js +188 -0
  56. package/dist/lib/skill-content.d.ts +31 -0
  57. package/dist/lib/skill-content.js +462 -0
  58. package/dist/lib/study-inputs.d.ts +20 -0
  59. package/dist/lib/study-inputs.js +72 -0
  60. package/dist/lib/types.d.ts +207 -9
  61. package/dist/lib/types.js +7 -0
  62. package/dist/lib/upload.js +2 -2
  63. package/dist/upgrade.js +11 -1
  64. package/package.json +3 -2
  65. package/dist/commands/simulation.d.ts +0 -10
  66. package/dist/commands/simulation.js +0 -647
  67. package/dist/commands/tester-profile.d.ts +0 -5
  68. package/dist/commands/tester-profile.js +0 -109
  69. package/dist/commands/tester.d.ts +0 -5
  70. package/dist/commands/tester.js +0 -73
@@ -0,0 +1,930 @@
1
+ /**
2
+ * ish docs — In-binary documentation aimed at AI coding agents.
3
+ *
4
+ * Pages ship as TypeScript string constants so they survive both the `tsc`
5
+ * build and the `bun build --compile` single-binary build. Slugs use
6
+ * forward-slashes so the surface mirrors a doc tree (`concepts/study`).
7
+ */
8
+ const OVERVIEW = `# ish — overview for agents
9
+
10
+ ish is a CLI for running studies and asks against AI tester audiences.
11
+ The agent (you) is the primary user. Every command supports \`--json\`,
12
+ exits non-zero on failure, and resolves IDs from short aliases.
13
+
14
+ ## Mental model
15
+
16
+ \`\`\`
17
+ Workspace (= product)
18
+ ├── Tester Profiles ────── reusable audience personas (alias: tp-…)
19
+ │ └── Sources ──────── transcripts/audio/images that seed generation
20
+ ├── Study ──────────────── persistent research artifact (alias: s-…)
21
+ │ ├── modality ──────── interactive | text | video | audio | image | document
22
+ │ ├── assignments ───── tasks the tester does
23
+ │ ├── questionnaire ─── questions the tester answers
24
+ │ └── Iterations ────── one configured run (URL or content) (alias: i-…)
25
+ │ └── Testers ─── instance of a Profile in this iteration (alias: t-…)
26
+ └── Ask ────────────────── lightweight reaction artifact (alias: a-…)
27
+ └── Rounds ────────── unit of execution; audience fixed at ask creation
28
+ \`\`\`
29
+
30
+ Two top-level run verbs:
31
+ - \`ish study run\` — dispatches simulations on the latest iteration of a study.
32
+ - \`ish ask run\` — appends a round to an ask (or creates one with \`--new\`).
33
+
34
+ ## How agents should use this CLI
35
+
36
+ 1. Run \`ish docs list\` to see every concept and reference page available offline.
37
+ 2. Run \`ish docs get-page <slug>\` to read a specific page (e.g. \`concepts/study\`).
38
+ 3. Run \`ish docs search <query>\` for keyword lookup across all pages.
39
+ 4. Every command prints structured JSON when stdout is piped or \`--json\` is set.
40
+ 5. Aliases like \`s-b2c\`, \`a-6ec\`, \`tp-795\`, \`i-d4e\`, \`t-a17\` are accepted
41
+ anywhere an ID is expected. See \`ish docs get-page reference/aliases\`.
42
+
43
+ ## Where to look next
44
+
45
+ - New here? \`ish docs get-page concepts/workspace\`, then \`concepts/study\`.
46
+ - Running your first study? \`ish docs get-page guides/first-study\`.
47
+ - Comparing study vs ask? \`ish docs get-page concepts/run-verbs\`.
48
+ - Need machine-readable output? \`ish docs get-page reference/json-mode\`.
49
+ - Auth gated URL? \`ish docs get-page concepts/site-access\`.
50
+
51
+ ## Install the skill into this project
52
+
53
+ \`ish init\` writes a SKILL.md (https://agentskills.io spec) into your
54
+ project so your agent loads ish context automatically:
55
+ - Claude Code: \`ish init\` → \`.claude/skills/ish/\`
56
+ - Codex / Cursor / Cline / Roo: \`ish init --target agents\` → \`.agents/skills/ish/\`
57
+ - Both: \`ish init --target both\`
58
+ `;
59
+ const CONCEPT_WORKSPACE = `# concept: workspace
60
+
61
+ A **workspace** is the top-level container — one per product. Everything
62
+ else (studies, asks, profiles, sources, configs) belongs to exactly one
63
+ workspace.
64
+
65
+ - Alias prefix: \`w-\`
66
+ - Created via: \`ish workspace create --name "My product"\`
67
+ - Selected via: \`ish workspace use <id>\` — saved to \`~/.ish/config.json\`,
68
+ used as the default for every subsequent command.
69
+
70
+ A workspace carries:
71
+ - \`base_url\` — default origin used by site-access rules and study URLs.
72
+ - Site-access credentials (encrypted at rest) — see \`concepts/site-access\`.
73
+ - Tester profiles + sources visible to every study/ask in the workspace.
74
+
75
+ ## Common commands
76
+
77
+ \`\`\`
78
+ ish workspace list
79
+ ish workspace create --name "My product" --base-url https://example.com
80
+ ish workspace use w-6ec # set as active
81
+ ish workspace get # show the active workspace
82
+ ish workspace site-access status
83
+ \`\`\`
84
+ `;
85
+ const CONCEPT_STUDY = `# concept: study
86
+
87
+ A **study** is the persistent research artifact. It defines:
88
+ - \`modality\`: \`interactive\` (the tester drives a real browser) or one of
89
+ \`text | video | audio | image | document\` (media reaction studies).
90
+ - \`content_type\` (media studies only): \`email | social_post | ad | …\` —
91
+ controls the framing the tester is given.
92
+ - \`assignments\`: the tasks the tester performs. See \`concepts/assignment\`.
93
+ - \`questionnaire\`: the questions the tester answers. See \`concepts/questionnaire\`.
94
+
95
+ A study does **not** carry the URL or media being tested — that lives on
96
+ its iterations. Think: a study is the recipe; an iteration is one batch.
97
+
98
+ - Alias prefix: \`s-\`
99
+ - Created via: \`ish study create …\`
100
+ - Selected via: \`ish study use <id>\` — becomes the default for run/iteration commands.
101
+
102
+ ## Lifecycle
103
+
104
+ 1. \`ish study create --name "Onboarding UX" --modality interactive --assignment "Sign up:Complete the signup flow" --question "How easy was it?"\`
105
+ 2. \`ish iteration create --url https://example.com\` (creates the first iteration)
106
+ 3. \`ish study run --sample 5 --country SE\` (dispatches simulations)
107
+ 4. \`ish study results\` or \`ish study wait\` to gather outputs.
108
+
109
+ ## Related
110
+
111
+ - \`concepts/iteration\` — the unit of execution within a study.
112
+ - \`concepts/assignment\` — task definition syntax.
113
+ - \`concepts/questionnaire\` — question types and timing.
114
+ - \`concepts/run-verbs\` — when to use \`study run\` vs \`ask run\`.
115
+ `;
116
+ const CONCEPT_ITERATION = `# concept: iteration
117
+
118
+ An **iteration** is one configured run of a study. It carries the
119
+ volatile bits — the URL (interactive) or the media (video/text/etc.) —
120
+ while the study carries the persistent shape (assignments, questionnaire,
121
+ modality).
122
+
123
+ - Alias prefix: \`i-\`
124
+ - A study has 1..N iterations. \`ish study run\` defaults to the latest.
125
+ - Local files passed to \`--content-url\`, \`--image-urls\`, etc. are
126
+ uploaded automatically. \`@filepath\` reads text from a file.
127
+
128
+ ## Why two layers?
129
+
130
+ Because you want to A/B different URLs or content variants while keeping
131
+ the same task definitions and questionnaire. Each iteration also owns
132
+ its own roster of testers, so you can compare audiences as well.
133
+
134
+ ## Common commands
135
+
136
+ \`\`\`
137
+ # Interactive — URL:
138
+ ish iteration create --study s-b2c --url https://example.com
139
+
140
+ # Interactive on mobile screen format:
141
+ ish iteration create --url https://example.com --screen-format mobile_portrait
142
+
143
+ # Text/email content from a file:
144
+ ish iteration create --content-text @./email.html --title "Newsletter"
145
+
146
+ # Video (URL or local file):
147
+ ish iteration create --content-url ./video.mp4
148
+
149
+ # Image set:
150
+ ish iteration create --image-urls "./a.png,./b.png"
151
+
152
+ # Document (PDF):
153
+ ish iteration create --content-url ./report.pdf
154
+
155
+ # Inspect:
156
+ ish iteration list --study s-b2c
157
+ ish iteration get i-d4e
158
+ \`\`\`
159
+
160
+ ## Related
161
+
162
+ - \`concepts/study\` — the parent artifact.
163
+ - \`concepts/run-verbs\` — how \`ish study run\` selects the iteration.
164
+ - \`concepts/audience\` — how testers are picked for a run.
165
+ `;
166
+ const CONCEPT_ASSIGNMENT = `# concept: assignment
167
+
168
+ An **assignment** is a single task a tester performs during an
169
+ interactive study (or considers, for media studies). A study has 1..N
170
+ assignments and they run in order.
171
+
172
+ Each assignment has:
173
+ - \`name\` — short label shown to the tester ("Sign up").
174
+ - \`instructions\` — what the tester is asked to do ("Complete the signup
175
+ flow using a personal email").
176
+
177
+ ## CLI input formats
178
+
179
+ Three flags, mutually exclusive (pass exactly one or none):
180
+
181
+ \`\`\`
182
+ # Repeatable inline form — "<name>:<instructions>":
183
+ ish study create --name "Onboarding" --modality interactive \\
184
+ --assignment "Sign up:Complete the signup flow" \\
185
+ --assignment "Verify:Confirm the email and log in"
186
+
187
+ # JSON file:
188
+ ish study create --name "Checkout" --modality interactive \\
189
+ --assignments-file ./assignments.json
190
+
191
+ # Inline JSON string:
192
+ ish study create … --assignments '[{"name":"Browse","instructions":"…"}]'
193
+ \`\`\`
194
+
195
+ \`assignments.json\` shape:
196
+ \`\`\`json
197
+ [
198
+ { "name": "Browse", "instructions": "Find a product you like" },
199
+ { "name": "Buy", "instructions": "Add to cart and checkout" }
200
+ ]
201
+ \`\`\`
202
+
203
+ ## Update or replace
204
+
205
+ \`ish study update <id> --assignment …\` (or \`--assignments-file\`)
206
+ replaces the full assignment list — additive editing is not supported.
207
+
208
+ ## Related
209
+
210
+ - \`concepts/study\` — assignments are immutable to the run; questionnaire is too.
211
+ - \`concepts/questionnaire\` — the other half of the study definition.
212
+ `;
213
+ const CONCEPT_QUESTIONNAIRE = `# concept: questionnaire
214
+
215
+ The **questionnaire** is the list of \`interview_questions\` a tester
216
+ answers before, during, or after their assignments. A study has 0..N
217
+ questions, each with a type and a timing.
218
+
219
+ ## Question shape
220
+
221
+ \`\`\`json
222
+ {
223
+ "question": "How easy was checkout?",
224
+ "type": "slider", // text | slider | likert | choice_single |
225
+ // choice_multiple | number | …
226
+ "timing": "after", // before | during | after
227
+ "min": 1, "max": 7, "step": 1,
228
+ "labels": ["Hard", "Easy"],
229
+ "options": ["A", "B", "C"] // only for choice_*
230
+ }
231
+ \`\`\`
232
+
233
+ ## CLI input formats
234
+
235
+ Two flags, mutually exclusive:
236
+
237
+ \`\`\`
238
+ # --question is repeatable. Defaults to type=text, timing=after.
239
+ ish study create … --question "How easy was it?" --question "Anything confusing?"
240
+
241
+ # Richer types from a JSON manifest:
242
+ ish study create … --questionnaire ./questionnaire.json
243
+ \`\`\`
244
+
245
+ \`questionnaire.json\` is an array of question objects in the shape above.
246
+ The same shape is accepted by \`ish ask add-questions … --questions …\`.
247
+
248
+ ## Related
249
+
250
+ - \`concepts/ask\` — asks have per-round questions, similar shape.
251
+ - \`concepts/study\` — questionnaire is part of the persistent study definition.
252
+ `;
253
+ const CONCEPT_ASK = `# concept: ask
254
+
255
+ An **ask** is a lightweight reaction artifact — much less ceremony than
256
+ a study. It has an audience (fixed at creation) and a sequence of
257
+ **rounds**. Each round shows the audience a prompt plus 1..N variants
258
+ (text or image) and collects their reactions.
259
+
260
+ - Alias prefix: \`a-\`
261
+ - Audience is fixed at ask creation — you cannot swap audiences between
262
+ rounds. (You can extend it via \`ish ask add-testers\`.)
263
+ - Up to 5 rounds per ask.
264
+
265
+ ## When to use ask vs study
266
+
267
+ - Reach for **ask** for: tagline A/B, hero-image picks, copy comparisons,
268
+ quick reactions to creative variants.
269
+ - Reach for **study** for: anything that needs a tester to *do* something
270
+ (interactive flow, multi-step task, time-on-page).
271
+
272
+ See \`concepts/run-verbs\` for the side-by-side.
273
+
274
+ ## Lifecycle
275
+
276
+ \`\`\`
277
+ # One-shot: create ask + run round 1
278
+ ish ask run --new --name "tagline AB" \\
279
+ --prompt "Which sounds better?" \\
280
+ --variant text:"Short and punchy." \\
281
+ --variant text:"A longer, descriptive line." \\
282
+ --sample 30 --wants-pick --wait
283
+
284
+ # Append round 2 to the active ask:
285
+ ish ask run --prompt "And now which?" \\
286
+ --variant text:"X" --variant text:"Y" --wait
287
+
288
+ # Inspect:
289
+ ish ask list
290
+ ish ask get a-6ec --round 2
291
+ ish ask results a-6ec
292
+ \`\`\`
293
+
294
+ ## Variant syntax
295
+
296
+ \`--variant <type>:<value>[::label=<label>]\`
297
+
298
+ - \`text:"A"\` — inline text
299
+ - \`text:@./body.md\` — text from a file
300
+ - \`image:./hero-a.png\` — local image (auto-uploaded)
301
+ - \`image:./a.png::label=A\` — with explicit label
302
+
303
+ ## Related
304
+
305
+ - \`concepts/round\` — what a round is and how it executes.
306
+ - \`concepts/audience\` — how testers are chosen at ask creation.
307
+ - \`concepts/run-verbs\` — \`ish ask run\` vs \`ish study run\`.
308
+ `;
309
+ const CONCEPT_ROUND = `# concept: round
310
+
311
+ A **round** is the unit of execution within an ask. Each round shows the
312
+ ask's audience a prompt + variants and collects reactions.
313
+
314
+ - Up to 5 rounds per ask.
315
+ - Rounds are 1-indexed in user-facing flags (\`--round 2\`) but stored
316
+ with \`order_index\` starting at 0.
317
+ - Each round can carry its own per-round questions in addition to
318
+ \`--wants-pick\` / \`--wants-ratings\`.
319
+
320
+ ## Common commands
321
+
322
+ \`\`\`
323
+ ish ask run --prompt … --variant … # append a round to active ask
324
+ ish ask add-round a-6ec --prompt … --variant … # explicit form
325
+ ish ask add-questions a-6ec --round 1 --questions ./qs.json
326
+ ish ask wait a-6ec --round 2 --timeout 600
327
+ ish ask results a-6ec --round 1
328
+ \`\`\`
329
+
330
+ ## Related
331
+
332
+ - \`concepts/ask\` — the parent artifact.
333
+ - \`concepts/audience\` — fixed at ask creation; rounds reuse it.
334
+ `;
335
+ const CONCEPT_PROFILE = `# concept: tester profile
336
+
337
+ A **tester profile** is a reusable audience persona — the simulated
338
+ human whose behaviour drives a tester instance during a study or ask.
339
+
340
+ - Alias prefix: \`tp-\`
341
+ - Lives at the workspace level, reusable across studies and asks.
342
+ - Distinct from a "tester" (\`t-\`) — a tester is one *instance* of a
343
+ profile inside one iteration.
344
+
345
+ ## Generation vs. manual creation
346
+
347
+ \`ish profile generate\` runs the same audience-generation flow used in
348
+ the web UI: an LLM reads your description and any uploaded sources
349
+ (transcripts, customer records, audio, images) and returns either a
350
+ single profile or a full audience.
351
+
352
+ \`\`\`
353
+ # 5 profiles from a written brief:
354
+ ish profile generate \\
355
+ --description "Tech-savvy millennials in the US who use mobile banking" \\
356
+ --count 5
357
+
358
+ # One profile from a transcript (auto-uploaded):
359
+ ish profile generate --source ./interviews/sarah.txt --count 1
360
+
361
+ # Audio with diarization:
362
+ ish profile generate --description "Voices behind support tickets" \\
363
+ --source ./call.mp3 --diarize --count 3
364
+ \`\`\`
365
+
366
+ For explicit control over uploads (reusing one source across runs):
367
+
368
+ \`\`\`
369
+ ish source upload ./call.mp3 --diarize
370
+ # → tps-3a4 (status: processed)
371
+ ish profile generate --source tps-3a4 --count 4
372
+ \`\`\`
373
+
374
+ ## Manual create
375
+
376
+ \`\`\`
377
+ ish profile create --file profile.json
378
+ \`\`\`
379
+
380
+ Expected JSON: \`{ "name": "...", "type": "ai", "gender": "female",
381
+ "country": "US", "occupation": "...", "bio": "..." }\`
382
+
383
+ ## Related
384
+
385
+ - \`concepts/source\` — the inputs to \`profile generate\`.
386
+ - \`concepts/audience\` — how profiles get selected into a run.
387
+ `;
388
+ const CONCEPT_SOURCE = `# concept: source
389
+
390
+ A **source** is an input to \`ish profile generate\`: a transcript,
391
+ audio file, image, or PDF that an LLM reads to ground generated profiles
392
+ in real customer evidence.
393
+
394
+ - Alias prefix: \`tps-\`
395
+ - Source kinds: \`text_file | audio | image\` (auto-detected from extension).
396
+ - Audio supports speaker diarization via \`--diarize\`.
397
+
398
+ ## Two ways to use a source
399
+
400
+ 1. **Inline** — pass a local file directly to \`profile generate\`. The
401
+ file is uploaded and processed in-line:
402
+ \`\`\`
403
+ ish profile generate --source ./call.mp3 --diarize --count 3
404
+ \`\`\`
405
+
406
+ 2. **Upload-then-reuse** — upload once, reference the alias from many
407
+ \`generate\` runs:
408
+ \`\`\`
409
+ ish source upload ./call.mp3 --diarize
410
+ # → tps-3a4 (status: processed)
411
+ ish profile generate --source tps-3a4 --count 4
412
+ \`\`\`
413
+
414
+ ## Inspect
415
+
416
+ \`\`\`
417
+ ish source get tps-3a4
418
+ \`\`\`
419
+
420
+ ## Related
421
+
422
+ - \`concepts/profile\` — sources feed profile generation.
423
+ `;
424
+ const CONCEPT_AUDIENCE = `# concept: audience selection
425
+
426
+ Both \`ish study run\` and \`ish ask run --new\` accept the same audience
427
+ flags. Two ways to select:
428
+
429
+ 1. **Explicit profile IDs** — \`--profile tp-795,tp-af2\` (or repeated).
430
+ 2. **Demographic-filtered sample from the workspace pool** — combine any
431
+ of the filters with \`--sample <N>\` or \`--all\` / \`--all-simulatable\`:
432
+ - \`--country SE,NO\` (repeatable)
433
+ - \`--gender female\` (repeatable)
434
+ - \`--min-age 25\`
435
+ - \`--max-age 50\`
436
+ - \`--search "early adopter"\`
437
+ - \`--visibility shared|private\`
438
+
439
+ The two modes are **mutually exclusive** — pass either \`--profile\` or
440
+ the filter set, not both.
441
+
442
+ ## Defaults
443
+
444
+ - \`ish study run\` with no audience flags → reuses the iteration's
445
+ existing testers. Ideal for re-running the same audience.
446
+ - \`ish ask run\` (without \`--new\`) → cannot change audience; the ask
447
+ fixes it at creation. Audience flags only apply with \`--new\`.
448
+
449
+ ## Examples
450
+
451
+ \`\`\`
452
+ # Explicit:
453
+ ish study run --profile tp-795,tp-af2
454
+
455
+ # Sample 3 Swedish profiles aged 35-50:
456
+ ish study run --country SE --min-age 35 --max-age 50 --sample 3
457
+
458
+ # Every female profile in the workspace:
459
+ ish study run --gender female --all
460
+
461
+ # Ask + audience in one shot:
462
+ ish ask run --new --name "SE 35-50" --prompt "Which sounds better?" \\
463
+ --variant text:"A" --variant text:"B" \\
464
+ --country SE --min-age 35 --max-age 50 --sample 10
465
+ \`\`\`
466
+
467
+ ## Related
468
+
469
+ - \`concepts/profile\` — generate profiles to fill the pool.
470
+ - \`concepts/run-verbs\` — when audience flags apply.
471
+ `;
472
+ const CONCEPT_SITE_ACCESS = `# concept: site access
473
+
474
+ For interactive studies that target a gated URL — HTTP basic auth,
475
+ session-cookie walls (Vercel preview, Lovable, etc.), or login forms —
476
+ configure credentials on the workspace once. Testers reuse them when a
477
+ study points at a matching origin.
478
+
479
+ Credentials are encrypted at rest. The CLI never reads them back; it
480
+ only checks whether each method is configured.
481
+
482
+ ## Methods
483
+
484
+ \`\`\`
485
+ # Show what's configured:
486
+ ish workspace site-access status
487
+
488
+ # HTTP basic auth (e.g. staging gate):
489
+ ish workspace site-access basic-auth --username alice --password hunter2
490
+
491
+ # Session cookie:
492
+ ish workspace site-access cookie --name session --value abc123
493
+
494
+ # Login form (typed by the tester):
495
+ ish workspace site-access login --username demo --password demo
496
+
497
+ # Mark as public (silence the "credentials needed?" prompt):
498
+ ish workspace site-access affirm-public
499
+
500
+ # Clear:
501
+ ish workspace site-access clear cookie
502
+ ish workspace site-access clear all
503
+ \`\`\`
504
+
505
+ \`--origin\` defaults to the workspace \`base_url\`
506
+ (\`ish workspace update --base-url …\`).
507
+
508
+ ## Keep secrets out of shell history
509
+
510
+ Pass \`-\` for \`--password\` or \`--value\` to read from stdin:
511
+
512
+ \`\`\`
513
+ printf %s "$STAGING_PW" | ish workspace site-access basic-auth \\
514
+ --username alice --password -
515
+ \`\`\`
516
+ `;
517
+ const CONCEPT_RUN_VERBS = `# concept: run verbs — \`study run\` vs \`ask run\`
518
+
519
+ Both verbs dispatch simulations against an audience, but the lifecycle
520
+ and what they target differ.
521
+
522
+ ## Side by side
523
+
524
+ | | \`ish study run\` | \`ish ask run\` |
525
+ |----------------|--------------------------------------------------|------------------------------------------------|
526
+ | Default | latest iteration of the active study | append a round to the active ask |
527
+ | Fresh setup | \`ish iteration create …\` first, then run | \`--new\` (creates ask + round 1 in one shot) |
528
+ | Specific target| \`--iteration <id>\` | positional ask id (\`a-6ec\`) |
529
+ | Audience | \`--profile\` OR filters with \`--sample\`/\`--all\` — else reuse iteration's testers | only at \`--new\`; fixed for the ask afterwards |
530
+ | Output unit | per-tester interactions + questionnaire answers | per-tester reactions per round |
531
+
532
+ ## Decision rule
533
+
534
+ - The tester needs to **do** something on a real surface
535
+ (URL/app/document)? → study.
536
+ - The tester needs to **react** to one or more variants
537
+ (text/image) of creative? → ask.
538
+
539
+ ## Common commands
540
+
541
+ \`\`\`
542
+ # Study — reuse iteration testers, block until done:
543
+ ish study run --wait
544
+
545
+ # Study — fresh audience by demographic:
546
+ ish study run --country SE --min-age 35 --sample 3
547
+
548
+ # Ask — append a round:
549
+ ish ask run --prompt "And now?" --variant text:"X" --variant text:"Y" --wait
550
+
551
+ # Ask — fresh ask + round 1 in one shot:
552
+ ish ask run --new --name "tagline AB" \\
553
+ --prompt "Which sounds better?" \\
554
+ --variant text:"A" --variant text:"B" --sample 30 --wants-pick --wait
555
+ \`\`\`
556
+
557
+ ## Tracking individual testers after \`study run\`
558
+
559
+ \`ish study run --json\` returns a top-level \`tester_aliases[]\` and
560
+ \`tester_ids[]\` for the testers it just dispatched. Pass either to the
561
+ low-level lifecycle verbs:
562
+
563
+ \`\`\`
564
+ ish study run --study s-b2c -y --json | jq -r '.tester_aliases[]' # → t-072, t-1ed, ...
565
+
566
+ ish study poll <tester_id> # one-shot status for one tester
567
+ ish study wait <tester_id> --timeout 600 # block until that tester finishes
568
+ ish study cancel <tester_id> # cancel a running simulation
569
+ \`\`\`
570
+
571
+ \`<tester_id>\` accepts a tester alias (\`t-…\`) or a full UUID. The
572
+ study-level \`poll\`/\`wait\` forms also exist (\`--study <id>\` /
573
+ \`--iteration <id>\`) for whole-batch progress.
574
+ `;
575
+ const REFERENCE_ALIASES = `# reference: aliases
576
+
577
+ ish accepts short, prefixed aliases anywhere a UUID is expected. Aliases
578
+ are derived from a deterministic hash of the entity ID and persist
579
+ across sessions. They are written to \`~/.ish/config.json\` the first
580
+ time the CLI sees an entity.
581
+
582
+ ## Prefixes
583
+
584
+ - \`w-\` workspace
585
+ - \`s-\` study
586
+ - \`i-\` iteration
587
+ - \`t-\` tester (instance of a profile in an iteration)
588
+ - \`tp-\` tester profile
589
+ - \`tps-\` tester-profile source
590
+ - \`a-\` ask
591
+ - \`c-\` config (simulation config)
592
+
593
+ ## Examples
594
+
595
+ \`\`\`
596
+ ish iteration create --study s-b2c --url https://example.com
597
+ ish iteration get i-d4e
598
+ ish study tester t-a17
599
+ ish profile generate --source tps-3a4 --count 4
600
+ \`\`\`
601
+
602
+ The full UUID is also always accepted. Add \`--verbose\` to JSON output
603
+ to see UUIDs alongside aliases.
604
+ `;
605
+ const REFERENCE_JSON_MODE = `# reference: JSON output for agents
606
+
607
+ Every command that produces output supports machine-readable JSON. JSON
608
+ mode is **auto-enabled when stdout is piped**, so an agent rarely needs
609
+ \`--json\` explicitly.
610
+
611
+ ## Flags
612
+
613
+ - \`--json\` — force JSON output even on a TTY.
614
+ - \`--fields a,b,c\` — keep only these fields in JSON output (e.g.
615
+ \`alias,name,status\`). Filters per item only;
616
+ paginated wrappers (\`{items, total, limit,
617
+ offset}\`) keep their shape.
618
+ - \`--verbose\` — include full UUIDs, timestamps, and (on
619
+ write paths) the full server payload instead
620
+ of the compact response.
621
+ - \`-q, --quiet\` — suppress progress messages on stderr (errors
622
+ still go to stderr).
623
+
624
+ ## Stable shape rules
625
+
626
+ The CLI guarantees these contracts so agents can chain safely:
627
+
628
+ - **Lists keep their wrapper.** \`--fields\` strips per-item, never the
629
+ envelope. A paginated list with \`{items, total, limit, offset}\` will
630
+ always have those four keys.
631
+ - **Write paths always include \`id\` AND \`alias\`.** Even with
632
+ \`--fields\` set, you can identify the affected resource. Default
633
+ write-path JSON is compact (\`{id, alias, name, updated_at,
634
+ ...changed_fields}\`); pass \`--verbose\` for the full server payload.
635
+ - **\`profile generate\` trims \`simulation_config\` by default** (~9×
636
+ smaller than the raw response). Pass \`--include-simulation-config\`
637
+ if you need it.
638
+ - **\`study run --json\` exposes tester handles.** The top-level
639
+ \`tester_ids[]\` and \`tester_aliases[]\` arrays are the canonical
640
+ inputs to \`ish study poll/wait/cancel\`.
641
+
642
+ ## Exit codes
643
+
644
+ | Code | Meaning |
645
+ |------|----------------------|
646
+ | 0 | Success |
647
+ | 1 | General error |
648
+ | 2 | Usage / validation |
649
+ | 3 | Auth (re-run \`ish login\`) |
650
+ | 4 | Not found |
651
+ | 5 | Transient — retryable (timeout, 5xx, network) |
652
+
653
+ Use these to branch in scripts; do not parse the human stderr message.
654
+
655
+ ## Error envelope
656
+
657
+ When a command fails with \`--json\` (or piped stdout), the CLI prints
658
+ a structured error object on **stdout** and a human message on
659
+ **stderr**:
660
+
661
+ \`\`\`json
662
+ {
663
+ "error": "Validation error",
664
+ "error_code": "validation",
665
+ "status": 422,
666
+ "retryable": false,
667
+ "errors": [
668
+ {
669
+ "loc": ["body", "testers", 0, "tester_profile_id"],
670
+ "msg": "Input should be a valid UUID",
671
+ "type": "uuid_parsing",
672
+ "input": "tp-bogus",
673
+ "allowed_values": ["..."]
674
+ }
675
+ ],
676
+ "suggestions": ["Pass a tester profile alias (tp-...) or UUID."]
677
+ }
678
+ \`\`\`
679
+
680
+ \`error_code\` mirrors the exit-code family
681
+ (\`validation\`, \`auth\`, \`not_found\`, \`timeout\`, \`server\`,
682
+ \`network\`, …). \`retryable: true\` matches exit code 5.
683
+
684
+ ## Conventions
685
+
686
+ - Successful commands exit 0 and print one JSON object/array on stdout.
687
+ - Lists print as JSON arrays (or paginated wrappers). Single resources
688
+ as JSON objects.
689
+ - Field names match the underlying API resource (snake_case).
690
+ - Aliases (\`s-…\`, \`a-…\`, \`tp-…\`, …) appear alongside UUIDs in
691
+ \`--verbose\` mode and replace UUIDs in default lean mode.
692
+
693
+ ## Examples
694
+
695
+ \`\`\`
696
+ ish workspace list --json | jq '.[].alias'
697
+ ish study get s-b2c --fields alias,name,status,iterations
698
+ ish ask results a-6ec --round 1 --json
699
+ ish profile generate --description "..." --count 3 --json | jq '.[].alias'
700
+ \`\`\`
701
+
702
+ ## Composing commands
703
+
704
+ JSON mode + alias resolution makes pipelines safe:
705
+
706
+ \`\`\`
707
+ ITER=$(ish iteration create --url https://example.com --json | jq -r .alias)
708
+ TESTERS=$(ish study run --iteration "$ITER" --sample 5 --country SE \\
709
+ --json | jq -r '.tester_aliases[]')
710
+ for t in $TESTERS; do
711
+ ish study wait "$t" --timeout 600
712
+ done
713
+ ish study results --json | jq .
714
+ \`\`\`
715
+ `;
716
+ const GUIDE_FIRST_STUDY = `# guide: your first study, end to end
717
+
718
+ Goal: from zero to a finished interactive study with 3 testers and one
719
+ question, produced in a single workspace.
720
+
721
+ ## 1. Authenticate
722
+
723
+ \`\`\`
724
+ ish login
725
+ \`\`\`
726
+
727
+ ## 2. Create + select a workspace
728
+
729
+ \`\`\`
730
+ ish workspace create --name "Demo" --base-url https://example.com
731
+ ish workspace use w-… # use the alias printed above
732
+ \`\`\`
733
+
734
+ ## 3. Generate a small audience
735
+
736
+ \`\`\`
737
+ ish profile generate \\
738
+ --description "Tech-savvy millennials in the US who use mobile banking" \\
739
+ --count 3
740
+ \`\`\`
741
+
742
+ ## 4. Define the study
743
+
744
+ \`\`\`
745
+ ish study create --name "Onboarding UX" --modality interactive \\
746
+ --assignment "Sign up:Complete the signup flow" \\
747
+ --question "How easy was it?"
748
+ ish study use s-…
749
+ \`\`\`
750
+
751
+ ## 5. Configure an iteration with the URL under test
752
+
753
+ \`\`\`
754
+ ish iteration create --url https://example.com
755
+ \`\`\`
756
+
757
+ ## 6. Run
758
+
759
+ \`\`\`
760
+ ish study run --all --wait
761
+ \`\`\`
762
+
763
+ ## 7. Read results
764
+
765
+ \`\`\`
766
+ ish study results --json | jq .
767
+ \`\`\`
768
+
769
+ ## Variations
770
+
771
+ - Gated URL? \`ish docs get-page concepts/site-access\`.
772
+ - Localhost? \`ish connect 3000\` to expose a Cloudflare tunnel, then use
773
+ the printed URL as \`--url\`.
774
+ - Want a quick reaction test instead of an interactive study? Skip to
775
+ \`ish docs get-page concepts/ask\`.
776
+ `;
777
+ const PAGES = [
778
+ {
779
+ slug: "overview",
780
+ title: "ish — overview for agents",
781
+ description: "Mental model, agent traversal order, where to look next.",
782
+ body: OVERVIEW,
783
+ },
784
+ {
785
+ slug: "concepts/workspace",
786
+ title: "concept: workspace",
787
+ description: "Top-level container; one per product.",
788
+ body: CONCEPT_WORKSPACE,
789
+ },
790
+ {
791
+ slug: "concepts/study",
792
+ title: "concept: study",
793
+ description: "Persistent research artifact: modality, assignments, questionnaire.",
794
+ body: CONCEPT_STUDY,
795
+ },
796
+ {
797
+ slug: "concepts/iteration",
798
+ title: "concept: iteration",
799
+ description: "One configured run of a study (URL or media).",
800
+ body: CONCEPT_ITERATION,
801
+ },
802
+ {
803
+ slug: "concepts/assignment",
804
+ title: "concept: assignment",
805
+ description: "A single task a tester performs; CLI input formats.",
806
+ body: CONCEPT_ASSIGNMENT,
807
+ },
808
+ {
809
+ slug: "concepts/questionnaire",
810
+ title: "concept: questionnaire",
811
+ description: "Question types, timing, JSON manifest shape.",
812
+ body: CONCEPT_QUESTIONNAIRE,
813
+ },
814
+ {
815
+ slug: "concepts/ask",
816
+ title: "concept: ask",
817
+ description: "Lightweight reaction artifact; rounds + variants.",
818
+ body: CONCEPT_ASK,
819
+ },
820
+ {
821
+ slug: "concepts/round",
822
+ title: "concept: round",
823
+ description: "Unit of execution within an ask.",
824
+ body: CONCEPT_ROUND,
825
+ },
826
+ {
827
+ slug: "concepts/profile",
828
+ title: "concept: tester profile",
829
+ description: "Reusable audience persona; generate vs manual create.",
830
+ body: CONCEPT_PROFILE,
831
+ },
832
+ {
833
+ slug: "concepts/source",
834
+ title: "concept: source",
835
+ description: "Inputs to profile generation: transcripts, audio, images, PDFs.",
836
+ body: CONCEPT_SOURCE,
837
+ },
838
+ {
839
+ slug: "concepts/audience",
840
+ title: "concept: audience selection",
841
+ description: "Audience flags shared by study run and ask run --new.",
842
+ body: CONCEPT_AUDIENCE,
843
+ },
844
+ {
845
+ slug: "concepts/site-access",
846
+ title: "concept: site access",
847
+ description: "Credentials for gated URLs (basic auth, cookies, login forms).",
848
+ body: CONCEPT_SITE_ACCESS,
849
+ },
850
+ {
851
+ slug: "concepts/run-verbs",
852
+ title: "concept: run verbs — study run vs ask run",
853
+ description: "Side-by-side; decision rule for choosing one over the other.",
854
+ body: CONCEPT_RUN_VERBS,
855
+ },
856
+ {
857
+ slug: "reference/aliases",
858
+ title: "reference: aliases",
859
+ description: "Short prefixed IDs accepted anywhere a UUID is expected.",
860
+ body: REFERENCE_ALIASES,
861
+ },
862
+ {
863
+ slug: "reference/json-mode",
864
+ title: "reference: JSON output for agents",
865
+ description: "JSON, --fields, --verbose, exit codes, pipe behaviour.",
866
+ body: REFERENCE_JSON_MODE,
867
+ },
868
+ {
869
+ slug: "guides/first-study",
870
+ title: "guide: your first study, end to end",
871
+ description: "Login → workspace → audience → study → iteration → run → results.",
872
+ body: GUIDE_FIRST_STUDY,
873
+ },
874
+ ];
875
+ const PAGES_BY_SLUG = new Map(PAGES.map((p) => [p.slug, p]));
876
+ export function listPages() {
877
+ return [...PAGES];
878
+ }
879
+ export function getPage(slug) {
880
+ return PAGES_BY_SLUG.get(slug);
881
+ }
882
+ /** Concise pointer printed at the bottom of `--help` outputs. */
883
+ export const AGENT_HELP_FOOTER = "\nFor agents: run `ish docs overview` for the mental model, " +
884
+ "`ish docs list` for every concept page, " +
885
+ "or `ish docs search <query>` for keyword lookup. Every command supports `--json`.\n\n" +
886
+ "Global flags (--json, --fields, --verbose, --quiet, --token, --api-url) apply to all subcommands. " +
887
+ "Run `ish --help` to see them.";
888
+ /**
889
+ * Substring search over title + description + body. Title hits score
890
+ * highest, then description, then body. Returns at most `limit` hits
891
+ * sorted by score desc.
892
+ */
893
+ export function searchPages(query, limit = 10) {
894
+ const q = query.trim().toLowerCase();
895
+ if (!q)
896
+ return [];
897
+ const hits = [];
898
+ for (const page of PAGES) {
899
+ const titleHit = page.title.toLowerCase().includes(q);
900
+ const descHit = page.description.toLowerCase().includes(q);
901
+ const bodyLower = page.body.toLowerCase();
902
+ const bodyIdx = bodyLower.indexOf(q);
903
+ if (!titleHit && !descHit && bodyIdx === -1)
904
+ continue;
905
+ let score = 0;
906
+ if (titleHit)
907
+ score += 100;
908
+ if (descHit)
909
+ score += 50;
910
+ if (bodyIdx !== -1)
911
+ score += 10;
912
+ let snippet = page.description;
913
+ if (bodyIdx !== -1) {
914
+ const start = Math.max(0, bodyIdx - 60);
915
+ const end = Math.min(page.body.length, bodyIdx + q.length + 140);
916
+ snippet = (start > 0 ? "…" : "") +
917
+ page.body.slice(start, end).replace(/\s+/g, " ").trim() +
918
+ (end < page.body.length ? "…" : "");
919
+ }
920
+ hits.push({
921
+ slug: page.slug,
922
+ title: page.title,
923
+ description: page.description,
924
+ snippet,
925
+ score,
926
+ });
927
+ }
928
+ hits.sort((a, b) => b.score - a.score);
929
+ return hits.slice(0, limit);
930
+ }