@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,462 @@
1
+ /**
2
+ * Agent Skill content for the `ish` CLI.
3
+ *
4
+ * Shipped inline as TypeScript constants so the same content is available
5
+ * in both `tsc`-compiled npm installs and `bun build --compile` single
6
+ * binaries (which don't bundle markdown files outside of TS imports).
7
+ *
8
+ * Spec: https://agentskills.io/specification (April 2026).
9
+ *
10
+ * Distribution model: `ish init` materialises this content as a real
11
+ * directory tree at the path the user picks. Native consumers:
12
+ * - Claude Code .claude/skills/ish/
13
+ * - Codex / Cursor / .agents/skills/ish/
14
+ * Cline / Roo Code
15
+ */
16
+ import pkg from "../../package.json" with { type: "json" };
17
+ const VERSION = pkg.version;
18
+ /**
19
+ * SKILL.md description is the primary trigger mechanism — it's the only
20
+ * field always loaded into agent context (the body is loaded on match).
21
+ *
22
+ * Anthropic's authoring guidance: skills under-trigger by default; pack
23
+ * the description with verbs the user is likely to say plus the noun
24
+ * "ish". Hard cap is 1024 chars. Front-load the use case.
25
+ */
26
+ const SKILL_DESCRIPTION = "Use this skill whenever the user mentions ish, a study, a tester profile, " +
27
+ "a simulation run, an \"ask\", an audience, or wants to dispatch tests against AI testers. " +
28
+ "Wraps the `ish` CLI for managing studies, asks, iterations, tester profiles, and simulation " +
29
+ "runs against the Ish platform. Always start by running `ish docs overview` to load the " +
30
+ "domain model, then `ish docs list` and `ish docs get-page <slug>` for specifics. Prefer " +
31
+ "this skill over guessing flags from `ish --help`.";
32
+ const SKILL_BODY = `# ish
33
+
34
+ A CLI for the Ish platform — run user-research studies and quick "ask"
35
+ reactions against AI tester audiences. The CLI is the agent surface;
36
+ this skill teaches you how to use it without re-reading its docs every
37
+ time.
38
+
39
+ ## When to invoke this skill
40
+
41
+ The user mentioned any of: \`ish\`, a study, a tester profile,
42
+ a tester source, a simulation run, an iteration, an "ask", an audience,
43
+ or wants to dispatch tests against AI testers. Also invoke if the user
44
+ asks to "run a study", "generate testers", "compare variants", "test a
45
+ prototype with users", or similar.
46
+
47
+ ## First step, every time: load the mental model
48
+
49
+ Before producing any \`ish\` command, run:
50
+
51
+ \`\`\`bash
52
+ ish docs overview
53
+ \`\`\`
54
+
55
+ This prints a one-page mental model (workspace → study | ask → testers
56
+ → results) and lists every concept page available offline. The model is
57
+ non-obvious — *do not* skip this step the first time the user asks for
58
+ anything ish-related in a session.
59
+
60
+ If you need detail on a specific concept:
61
+
62
+ \`\`\`bash
63
+ ish docs list # every page available
64
+ ish docs get-page concepts/study # one page, full markdown
65
+ ish docs get-page concepts/run-verbs # study run vs ask run
66
+ ish docs search "<keyword>" # ranked hits with snippets
67
+ \`\`\`
68
+
69
+ The pages \`ish docs\` exposes are the source of truth — newer than this
70
+ skill file. **Trust \`ish docs\` over anything in this skill if they
71
+ conflict.**
72
+
73
+ ## Quick orientation (one-screen)
74
+
75
+ \`\`\`
76
+ Workspace (= product)
77
+ ├── Tester Profiles (tp-…) reusable audience personas
78
+ │ └── Sources (tps-…) transcripts/audio/images that seed generation
79
+ ├── Study (s-…) persistent research artifact
80
+ │ ├── modality interactive | text | video | audio | image | document
81
+ │ ├── assignments tasks the tester does
82
+ │ ├── questionnaire questions the tester answers
83
+ │ └── Iterations (i-…) one configured run; carries the URL or media
84
+ │ └── Testers (t-…) instance of a profile in this iteration
85
+ └── Ask (a-…) lightweight reaction artifact
86
+ └── Rounds unit of execution; audience fixed at ask creation
87
+ \`\`\`
88
+
89
+ Two run verbs:
90
+ - \`ish study run\` — dispatches simulations on the latest iteration of a study.
91
+ - \`ish ask run\` — appends a round to an ask (or \`--new\` to create one).
92
+
93
+ Use **study** when the tester must *do* something on a real surface;
94
+ use **ask** for quick reactions to text/image variants.
95
+
96
+ ## High-frequency commands
97
+
98
+ \`\`\`bash
99
+ # Auth & active selection (saved to ~/.ish/config.json)
100
+ ish login
101
+ ish workspace use w-6ec
102
+ ish study use s-b2c
103
+ ish ask use a-6ec
104
+
105
+ # Inspect
106
+ ish workspace list
107
+ ish study list
108
+ ish iteration list --study s-b2c
109
+ ish ask list
110
+
111
+ # Define / configure
112
+ ish study create --name "..." --modality interactive --assignment "..." --question "..."
113
+ ish iteration create --url https://example.com
114
+ ish profile generate --description "..." --count 5
115
+
116
+ # Run
117
+ ish study run --sample 5 --country SE --wait
118
+ ish ask run --new --name "..." --prompt "..." --variant text:"A" --variant text:"B" --sample 30 --wants-pick --wait
119
+
120
+ # Results
121
+ ish study results
122
+ ish ask results a-6ec --round 1
123
+
124
+ # Read offline docs
125
+ ish docs overview
126
+ ish docs get-page <slug>
127
+ ish docs search <query>
128
+ \`\`\`
129
+
130
+ ## Common workflows (worked examples)
131
+
132
+ See \`references/workflows.md\` in this skill for end-to-end transcripts:
133
+ - First study from zero (auth → workspace → audience → study → iteration → run → results)
134
+ - Quick A/B ask with image variants
135
+ - Generating profiles from a transcript or audio source
136
+ - Targeting a gated URL (basic auth, session cookie, login form)
137
+ - Re-running a study with a fresh audience
138
+
139
+ ## Output handling
140
+
141
+ - Every command supports \`--json\`. JSON mode is **auto-enabled when
142
+ stdout is piped**, so an agent rarely needs \`--json\` explicitly.
143
+ - \`--fields a,b,c\` strips JSON output to the listed fields (saves
144
+ tokens). \`--verbose\` adds full UUIDs and timestamps.
145
+ - Exit codes carry meaning: 0 success, 2 usage/validation,
146
+ 3 auth, 4 not-found, 5 transient. See
147
+ \`ish docs get-page reference/json-mode\`.
148
+ - Aliases (\`s-…\`, \`a-…\`, \`tp-…\`, \`i-…\`, \`t-…\`, \`tps-…\`, \`w-…\`)
149
+ are accepted anywhere a UUID is. See
150
+ \`ish docs get-page reference/aliases\`.
151
+
152
+ ## Common pitfalls (don't do these)
153
+
154
+ 1. **Don't paste flags from memory.** The CLI evolves; flags change.
155
+ 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
161
+ are mutually exclusive. Either explicit IDs or
162
+ \`--country\`/\`--gender\`/\`--min-age\`/\`--max-age\` + \`--sample\`.
163
+ 4. **Don't change audience between rounds of an ask.** It's fixed at
164
+ ask creation. Use \`ish ask add-testers\` to *extend* it; you can't
165
+ replace it.
166
+ 5. **Don't try to put credentials in the URL** for gated study URLs.
167
+ Configure them once on the workspace via
168
+ \`ish workspace site-access …\` (basic-auth, cookie, login).
169
+ See \`ish docs get-page concepts/site-access\`.
170
+ 6. **Don't commit \`~/.ish/config.json\`** — it stores tokens and active
171
+ workspace/study/ask selections. It lives in \`$HOME\`, not the repo.
172
+
173
+ ## Authentication
174
+
175
+ \`ish login\` opens a browser and saves tokens to \`~/.ish/config.json\`.
176
+ The CLI also accepts \`--token <token>\` or \`ISH_TOKEN\` env var. If a
177
+ command exits with code 3 ("auth"), tell the user to re-run \`ish login\`.
178
+
179
+ ## When ish is the wrong tool
180
+
181
+ If the user wants to *write code* against the Ish API directly, point
182
+ them at the API docs at https://ishlabs.io — this CLI is for
183
+ orchestration, not as an API client library.
184
+
185
+ ---
186
+
187
+ **Skill version:** ${VERSION}
188
+ **Skill source of truth:** \`ish docs\` (offline, ships with the binary)
189
+ `;
190
+ const WORKFLOWS_MD = `# ish workflows — worked examples
191
+
192
+ Each workflow below is a complete transcript an agent can adapt. Run
193
+ \`ish docs overview\` first if you haven't already this session.
194
+
195
+ ## 1. First study from zero
196
+
197
+ Goal: from a fresh install to a finished interactive study with 3
198
+ testers and one question.
199
+
200
+ \`\`\`bash
201
+ # 1. Authenticate (browser flow, saves tokens to ~/.ish/config.json)
202
+ ish login
203
+
204
+ # 2. Create + select a workspace
205
+ ish workspace create --name "Demo" --base-url https://example.com
206
+ ish workspace use w-…
207
+
208
+ # 3. Generate a small audience
209
+ ish profile generate \\
210
+ --description "Tech-savvy millennials in the US who use mobile banking" \\
211
+ --count 3
212
+
213
+ # 4. Define the study
214
+ ish study create --name "Onboarding UX" --modality interactive \\
215
+ --assignment "Sign up:Complete the signup flow" \\
216
+ --question "How easy was it?"
217
+ ish study use s-…
218
+
219
+ # 5. Configure an iteration with the URL under test
220
+ ish iteration create --url https://example.com
221
+
222
+ # 6. Run, blocking until done
223
+ ish study run --all --wait
224
+
225
+ # 7. Read results
226
+ ish study results --json | jq .
227
+ \`\`\`
228
+
229
+ ## 2. Quick A/B ask with image variants
230
+
231
+ Goal: ship 30 simulated reactions to two hero images, with a "which do
232
+ you prefer" question.
233
+
234
+ \`\`\`bash
235
+ ish ask run --new --name "hero shots" \\
236
+ --prompt "Which feels more premium?" \\
237
+ --variant image:./hero-a.png::label=A \\
238
+ --variant image:./hero-b.png::label=B \\
239
+ --sample 30 --wants-pick --wait
240
+
241
+ ish ask results --json | jq .
242
+ \`\`\`
243
+
244
+ Add a follow-up round with no audience change:
245
+
246
+ \`\`\`bash
247
+ ish ask run --prompt "Which one would you click on?" \\
248
+ --variant image:./hero-a.png::label=A \\
249
+ --variant image:./hero-b.png::label=B \\
250
+ --wait
251
+ \`\`\`
252
+
253
+ ## 3. Generate profiles from a real source
254
+
255
+ Goal: turn a customer interview transcript into a 4-profile audience.
256
+
257
+ \`\`\`bash
258
+ # Inline — auto-uploads the file:
259
+ ish profile generate --source ./interviews/sarah.txt --count 4
260
+
261
+ # Or upload once and reuse the source alias:
262
+ ish source upload ./call.mp3 --diarize
263
+ # → tps-3a4 (status: processed)
264
+ ish profile generate --source tps-3a4 --propose-count
265
+ # → { proposed_count: 4, rationale: "..." }
266
+ ish profile generate --source tps-3a4 --count 4
267
+ \`\`\`
268
+
269
+ ## 4. Target a gated URL (Vercel preview / staging gate / login form)
270
+
271
+ Configure credentials once on the workspace; testers reuse them.
272
+
273
+ \`\`\`bash
274
+ # Show what's configured:
275
+ ish workspace site-access status
276
+
277
+ # HTTP basic auth:
278
+ ish workspace site-access basic-auth --username alice --password hunter2
279
+
280
+ # Session cookie (Vercel preview, Lovable, etc.):
281
+ ish workspace site-access cookie --name session --value abc123
282
+
283
+ # Login form (typed by the tester into the page):
284
+ ish workspace site-access login --username demo --password demo
285
+ \`\`\`
286
+
287
+ Keep secrets out of shell history by passing \`-\` for \`--password\` /
288
+ \`--value\` and piping from stdin:
289
+
290
+ \`\`\`bash
291
+ printf %s "$STAGING_PW" | ish workspace site-access basic-auth \\
292
+ --username alice --password -
293
+ \`\`\`
294
+
295
+ ## 5. Re-run a study with a fresh audience
296
+
297
+ Goal: same study, same iteration, but compare audiences.
298
+
299
+ \`\`\`bash
300
+ # First run — Swedish 35-50:
301
+ ish study run --country SE --min-age 35 --max-age 50 --sample 5 --wait
302
+
303
+ # Second run — every female profile in the workspace, same iteration:
304
+ ish study run --gender female --all --wait
305
+ \`\`\`
306
+
307
+ If you don't pass any audience flags, \`ish study run\` reuses the
308
+ iteration's existing testers — useful for re-running after fixing the
309
+ target page.
310
+
311
+ ## 6. Localhost target (dev environment)
312
+
313
+ Expose a port via a Cloudflare tunnel; \`ish connect\` prints the public
314
+ URL the study iteration can point at. \`connect\` is foreground and
315
+ long-running — keep it open in a separate terminal (or background it).
316
+
317
+ \`\`\`bash
318
+ # Terminal A — keep open:
319
+ ish connect 3000
320
+ # stdout: Tunnel URL: https://<random>.trycloudflare.com → http://localhost:3000
321
+
322
+ # Terminal B — use the URL:
323
+ ish iteration create --url https://<random>.trycloudflare.com
324
+ ish study run --sample 3 --wait
325
+ \`\`\`
326
+
327
+ For agents/scripts, run \`connect\` in the background with \`--json\` and
328
+ read the URL from stdout (one JSON line per state change):
329
+
330
+ \`\`\`bash
331
+ ish connect 3000 --json > /tmp/ish-tunnel.log &
332
+ sleep 3
333
+ URL=$(jq -r 'select(.status=="connected") | .tunnel_url' /tmp/ish-tunnel.log | head -1)
334
+ ish iteration create --url "$URL"
335
+ \`\`\`
336
+
337
+ ## Tips for chaining commands as an agent
338
+
339
+ - Capture aliases from JSON: \`ITER=$(ish iteration create --url … --json | jq -r .alias)\`
340
+ - After \`ish study run --json\`, the testers you just dispatched are at
341
+ \`.tester_aliases[]\` (and \`.tester_ids[]\` for UUIDs). Pass these to
342
+ \`ish study poll/wait/cancel <tester_id>\`.
343
+ - Use \`--fields\` to keep JSON tight: \`ish study list --fields alias,name,status\`
344
+ - Always pass \`--wait\` (or \`ish study wait\`) before reading
345
+ \`ish study results\` — without it you may read partial data.
346
+ - For \`ask\` write-paths (update/archive/wait/add-questions/add-testers),
347
+ default JSON is compact (changed fields + alias). Pass \`--verbose\` for
348
+ the full Ask payload.
349
+ - For \`profile generate --json\`, \`simulation_config\` is trimmed by
350
+ default (~9× smaller). Pass \`--include-simulation-config\` to include it.
351
+ `;
352
+ const COMMANDS_MD = `# ish commands — quick index
353
+
354
+ This file is intentionally thin. The authoritative, always-current
355
+ reference is \`ish docs\` (built into the binary) and per-command
356
+ \`--help\`. Use this index only for orientation — when in doubt, run:
357
+
358
+ \`\`\`bash
359
+ ish docs list
360
+ ish <command> --help
361
+ \`\`\`
362
+
363
+ ## Top-level groups
364
+
365
+ | Group | Purpose | Concept page |
366
+ |-------------|-------------------------------------------------|-----------------------------|
367
+ | \`workspace\` | Top-level container (= product) | concepts/workspace |
368
+ | \`study\` | Persistent research artifact | concepts/study |
369
+ | \`iteration\` | One configured run of a study (URL or media) | concepts/iteration |
370
+ | \`ask\` | Lightweight reaction artifact | concepts/ask |
371
+ | \`profile\` | Tester profiles + audience generation | concepts/profile |
372
+ | \`source\` | Upload sources for profile generation | concepts/source |
373
+ | \`config\` | Simulation configs (model, timing, retries) | (run \`ish config --help\`) |
374
+ | \`docs\` | Offline docs for agents | (run \`ish docs --help\`) |
375
+ | \`init\` | Drop this skill into a Claude Code / Codex / | (run \`ish init --help\`) |
376
+ | | Cursor / Cline / Roo project | |
377
+ | \`login\` | Browser-based auth | — |
378
+ | \`logout\` | Clear saved credentials | — |
379
+ | \`connect\` | Cloudflare tunnel exposing localhost | — |
380
+ | \`upgrade\` | Self-update | — |
381
+
382
+ ## Discovering flags safely
383
+
384
+ Don't construct \`ish\` commands from memory for anything beyond the
385
+ high-frequency examples. Run:
386
+
387
+ \`\`\`bash
388
+ ish <group> --help # group-level summary + concept primer
389
+ ish <group> <verb> --help # exact flags for the verb
390
+ \`\`\`
391
+
392
+ Every group's \`--help\` ends with a "Concept pages" footer pointing at
393
+ the right \`ish docs get-page <slug>\` to read deep context.
394
+
395
+ ## Aliases
396
+
397
+ Short prefixed IDs (e.g. \`s-b2c\`, \`tp-795\`, \`a-6ec\`, \`i-d4e\`,
398
+ \`t-a17\`, \`tps-3a4\`, \`w-6ec\`, \`c-c3c\`) are accepted anywhere a UUID
399
+ is expected. Full UUIDs always work too. See
400
+ \`ish docs get-page reference/aliases\`.
401
+
402
+ ## Output flags (global)
403
+
404
+ | Flag | Effect |
405
+ |------------------|----------------------------------------------------------|
406
+ | \`--json\` | JSON output (auto-on when stdout is piped) |
407
+ | \`--fields a,b\` | Keep only listed fields in JSON |
408
+ | \`--verbose\` | Include UUIDs + timestamps in JSON |
409
+ | \`-q, --quiet\` | Suppress progress messages on stderr |
410
+ | \`-t, --token\` | Auth token (else ISH_TOKEN env, else \`ish login\` saved) |
411
+ | \`--api-url\` | Override backend (default https://api.ishlabs.io) |
412
+
413
+ ## Exit codes
414
+
415
+ \`0\` ok · \`1\` general · \`2\` usage/validation · \`3\` auth ·
416
+ \`4\` not-found · \`5\` transient (retryable). See
417
+ \`ish docs get-page reference/json-mode\`.
418
+ `;
419
+ /**
420
+ * Build the SKILL.md frontmatter + body. Version is stamped from the
421
+ * package.json at build time so users can see drift between the skill
422
+ * they installed and the CLI they're running.
423
+ */
424
+ function buildSkillMd() {
425
+ const frontmatter = [
426
+ "---",
427
+ "name: ish",
428
+ `description: ${JSON.stringify(SKILL_DESCRIPTION)}`,
429
+ "license: SEE LICENSE IN LICENSE",
430
+ "metadata:",
431
+ " author: ish",
432
+ ` version: ${JSON.stringify(VERSION)}`,
433
+ "allowed-tools: Bash(ish:*)",
434
+ "---",
435
+ "",
436
+ ].join("\n");
437
+ return frontmatter + SKILL_BODY;
438
+ }
439
+ /** Returns every file that makes up the bundled skill, in stable order. */
440
+ export function buildSkillFiles() {
441
+ return [
442
+ { relativePath: "SKILL.md", contents: buildSkillMd() },
443
+ { relativePath: "references/workflows.md", contents: WORKFLOWS_MD },
444
+ { relativePath: "references/commands.md", contents: COMMANDS_MD },
445
+ ];
446
+ }
447
+ /** Convenience: just the SKILL.md content (for `ish init --stdout`). */
448
+ export function getSkillMd() {
449
+ return buildSkillMd();
450
+ }
451
+ export const SKILL_TARGETS = [
452
+ {
453
+ key: "claude",
454
+ path: ".claude/skills/ish",
455
+ consumers: ["Claude Code", "Cursor (back-compat)"],
456
+ },
457
+ {
458
+ key: "agents",
459
+ path: ".agents/skills/ish",
460
+ consumers: ["Codex", "Cursor", "Cline", "Roo Code"],
461
+ },
462
+ ];
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Parsers and loaders for `ish study create/update` assignment & question
3
+ * flags. Mirrors the loose validation style of `src/lib/ask-questions.ts`.
4
+ */
5
+ import type { Assignment, InterviewQuestion } from "./types.js";
6
+ /**
7
+ * Parse `"Name:Instructions"`. Splits on the first `:`, so colons inside
8
+ * the instructions text are preserved.
9
+ */
10
+ export declare function parseAssignment(value: string): Assignment;
11
+ /**
12
+ * Read a JSON file containing an array of `{name, instructions}` entries.
13
+ */
14
+ export declare function loadAssignmentsFile(filePath: string): Assignment[];
15
+ /**
16
+ * Parse a plain question text into a default text-typed, after-timed
17
+ * `InterviewQuestion`. Anything more complex (slider, likert, choice,
18
+ * timing=before) goes through `--questionnaire <file.json>`.
19
+ */
20
+ export declare function parseQuestion(value: string): InterviewQuestion;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Parsers and loaders for `ish study create/update` assignment & question
3
+ * flags. Mirrors the loose validation style of `src/lib/ask-questions.ts`.
4
+ */
5
+ import { readFileSync } from "node:fs";
6
+ import { resolve as resolvePath } from "node:path";
7
+ /**
8
+ * Parse `"Name:Instructions"`. Splits on the first `:`, so colons inside
9
+ * the instructions text are preserved.
10
+ */
11
+ export function parseAssignment(value) {
12
+ const idx = value.indexOf(":");
13
+ if (idx < 0) {
14
+ throw new Error(`Invalid --assignment "${value}". Use "Name:Instructions" (e.g. --assignment "Sign up:Complete the signup flow").`);
15
+ }
16
+ const name = value.slice(0, idx).trim();
17
+ const instructions = value.slice(idx + 1).trim();
18
+ if (!name) {
19
+ throw new Error(`Invalid --assignment "${value}": name is empty.`);
20
+ }
21
+ if (!instructions) {
22
+ throw new Error(`Invalid --assignment "${value}": instructions are empty.`);
23
+ }
24
+ return { name, instructions };
25
+ }
26
+ /**
27
+ * Read a JSON file containing an array of `{name, instructions}` entries.
28
+ */
29
+ export function loadAssignmentsFile(filePath) {
30
+ let raw;
31
+ try {
32
+ raw = readFileSync(resolvePath(filePath), "utf-8");
33
+ }
34
+ catch {
35
+ throw new Error(`Cannot read assignments file: ${filePath}`);
36
+ }
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(raw);
40
+ }
41
+ catch {
42
+ throw new Error(`Invalid JSON in assignments file: ${filePath}`);
43
+ }
44
+ if (!Array.isArray(parsed) || parsed.length === 0) {
45
+ throw new Error(`Assignments file must be a non-empty JSON array: ${filePath}`);
46
+ }
47
+ for (let i = 0; i < parsed.length; i++) {
48
+ const a = parsed[i];
49
+ if (!a || typeof a !== "object") {
50
+ throw new Error(`assignments[${i}] must be an object with name + instructions.`);
51
+ }
52
+ if (typeof a.name !== "string" || !a.name.trim()) {
53
+ throw new Error(`assignments[${i}].name must be a non-empty string.`);
54
+ }
55
+ if (typeof a.instructions !== "string" || !a.instructions.trim()) {
56
+ throw new Error(`assignments[${i}].instructions must be a non-empty string.`);
57
+ }
58
+ }
59
+ return parsed;
60
+ }
61
+ /**
62
+ * Parse a plain question text into a default text-typed, after-timed
63
+ * `InterviewQuestion`. Anything more complex (slider, likert, choice,
64
+ * timing=before) goes through `--questionnaire <file.json>`.
65
+ */
66
+ export function parseQuestion(value) {
67
+ const text = value.trim();
68
+ if (!text) {
69
+ throw new Error(`Invalid --question "": question text is empty.`);
70
+ }
71
+ return { question: text, type: "text", timing: "after" };
72
+ }