@ishlabs/cli 0.8.1 → 0.8.2
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 +323 -21
- package/dist/auth.d.ts +17 -1
- package/dist/auth.js +62 -9
- package/dist/commands/ask.d.ts +5 -0
- package/dist/commands/ask.js +722 -0
- package/dist/commands/config.js +25 -1
- package/dist/commands/docs.d.ts +17 -0
- package/dist/commands/docs.js +147 -0
- package/dist/commands/init.d.ts +16 -0
- package/dist/commands/init.js +182 -0
- package/dist/commands/iteration.d.ts +5 -1
- package/dist/commands/iteration.js +243 -31
- package/dist/commands/profile.d.ts +5 -0
- package/dist/commands/profile.js +313 -0
- package/dist/commands/source.d.ts +10 -0
- package/dist/commands/source.js +78 -0
- package/dist/commands/study-run.d.ts +11 -0
- package/dist/commands/study-run.js +552 -0
- package/dist/commands/study-tester.d.ts +8 -0
- package/dist/commands/study-tester.js +149 -0
- package/dist/commands/study.js +145 -70
- package/dist/commands/workspace.js +193 -7
- package/dist/config.d.ts +3 -1
- package/dist/config.js +10 -10
- package/dist/connect.d.ts +4 -1
- package/dist/connect.js +127 -94
- package/dist/index.js +82 -34
- package/dist/lib/alias-store.d.ts +3 -0
- package/dist/lib/alias-store.js +9 -7
- package/dist/lib/api-client.d.ts +9 -6
- package/dist/lib/api-client.js +87 -26
- package/dist/lib/ask-questions.d.ts +9 -0
- package/dist/lib/ask-questions.js +35 -0
- package/dist/lib/ask-variants.d.ts +48 -0
- package/dist/lib/ask-variants.js +236 -0
- package/dist/lib/auth.d.ts +1 -1
- package/dist/lib/auth.js +24 -8
- package/dist/lib/colors.d.ts +30 -0
- package/dist/lib/colors.js +48 -0
- package/dist/lib/command-helpers.d.ts +74 -0
- package/dist/lib/command-helpers.js +232 -6
- package/dist/lib/docs.d.ts +32 -0
- package/dist/lib/docs.js +930 -0
- package/dist/lib/local-sim/browser.d.ts +0 -1
- package/dist/lib/local-sim/browser.js +0 -2
- package/dist/lib/local-sim/install.d.ts +4 -7
- package/dist/lib/local-sim/install.js +6 -21
- package/dist/lib/output.d.ts +25 -3
- package/dist/lib/output.js +465 -20
- package/dist/lib/paths.d.ts +14 -0
- package/dist/lib/paths.js +36 -0
- package/dist/lib/profile-sources.d.ts +55 -0
- package/dist/lib/profile-sources.js +157 -0
- package/dist/lib/site-access.d.ts +80 -0
- package/dist/lib/site-access.js +188 -0
- package/dist/lib/skill-content.d.ts +31 -0
- package/dist/lib/skill-content.js +462 -0
- package/dist/lib/study-inputs.d.ts +20 -0
- package/dist/lib/study-inputs.js +72 -0
- package/dist/lib/types.d.ts +207 -9
- package/dist/lib/types.js +7 -0
- package/dist/lib/upload.js +2 -2
- package/dist/upgrade.js +11 -1
- package/package.json +1 -1
- package/dist/commands/simulation.d.ts +0 -10
- package/dist/commands/simulation.js +0 -647
- package/dist/commands/tester-profile.d.ts +0 -5
- package/dist/commands/tester-profile.js +0 -109
- package/dist/commands/tester.d.ts +0 -5
- package/dist/commands/tester.js +0 -73
package/dist/lib/docs.js
ADDED
|
@@ -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
|
+
}
|