@ishlabs/cli 0.12.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/commands/chat-config.d.ts +23 -0
  2. package/dist/commands/chat-config.js +289 -0
  3. package/dist/commands/chat.js +26 -37
  4. package/dist/commands/iteration.js +219 -22
  5. package/dist/commands/profile.js +75 -9
  6. package/dist/commands/source.js +6 -4
  7. package/dist/commands/study-analyze.d.ts +41 -0
  8. package/dist/commands/study-analyze.js +187 -0
  9. package/dist/commands/study-run.js +359 -30
  10. package/dist/commands/study-screenshots.d.ts +20 -0
  11. package/dist/commands/study-screenshots.js +216 -0
  12. package/dist/commands/study.js +174 -9
  13. package/dist/commands/workspace.js +35 -2
  14. package/dist/lib/accessibility-profile.d.ts +12 -0
  15. package/dist/lib/accessibility-profile.js +136 -0
  16. package/dist/lib/alias-store.d.ts +1 -0
  17. package/dist/lib/alias-store.js +1 -0
  18. package/dist/lib/ask-questions.js +9 -0
  19. package/dist/lib/billing.d.ts +55 -0
  20. package/dist/lib/billing.js +77 -0
  21. package/dist/lib/command-helpers.d.ts +6 -0
  22. package/dist/lib/command-helpers.js +12 -0
  23. package/dist/lib/docs.js +1181 -38
  24. package/dist/lib/enums.d.ts +54 -0
  25. package/dist/lib/enums.js +100 -0
  26. package/dist/lib/local-sim/actions.d.ts +2 -1
  27. package/dist/lib/local-sim/actions.js +88 -13
  28. package/dist/lib/local-sim/loop.js +49 -19
  29. package/dist/lib/local-sim/tabs.d.ts +27 -0
  30. package/dist/lib/local-sim/tabs.js +157 -0
  31. package/dist/lib/local-sim/types.d.ts +15 -0
  32. package/dist/lib/modality.d.ts +70 -1
  33. package/dist/lib/modality.js +323 -17
  34. package/dist/lib/output.js +61 -4
  35. package/dist/lib/skill-content.js +397 -19
  36. package/dist/lib/types.d.ts +6 -1
  37. package/dist/lib/types.js +1 -1
  38. package/package.json +1 -1
@@ -0,0 +1,187 @@
1
+ /**
2
+ * ish study analyze / ish study insights — AI summary + key insights for a
3
+ * study.
4
+ *
5
+ * Wraps three backend endpoints already shipped server-side and used by the
6
+ * web overview page:
7
+ *
8
+ * POST /studies/{id}/analysis — kick off an analysis run
9
+ * GET /studies/{id}/results — list runs for a study (newest first)
10
+ * GET /study-results/{id} — fetch one run
11
+ *
12
+ * Status state machine: pending → running → (completed | failed). Polling
13
+ * mirrors the FE behaviour (5s here vs 15s in the FE — agents care more
14
+ * about latency than load).
15
+ */
16
+ import { withClient, resolveStudy, parseWaitTimeout } from "../lib/command-helpers.js";
17
+ import { resolveId } from "../lib/alias-store.js";
18
+ import { output, printTable } from "../lib/output.js";
19
+ import { WaitTimeoutError } from "./study-run.js";
20
+ const POLL_INTERVAL_MS = 5_000;
21
+ const ANALYSIS_TERMINAL_STATUSES = new Set(["completed", "failed"]);
22
+ async function pollAnalysisUntilDone(client, opts) {
23
+ const start = Date.now();
24
+ let lastReported = "";
25
+ while (true) {
26
+ const result = await client.get(`/study-results/${opts.resultId}`, undefined, { timeout: 60_000 });
27
+ if (!opts.quiet) {
28
+ const line = result.progress_message
29
+ ? `${result.status}: ${result.progress_message}`
30
+ : result.status;
31
+ if (line !== lastReported) {
32
+ process.stderr.write(` ${line}\n`);
33
+ lastReported = line;
34
+ }
35
+ }
36
+ if (ANALYSIS_TERMINAL_STATUSES.has(result.status)) {
37
+ return result;
38
+ }
39
+ if (Date.now() - start > opts.timeoutMs) {
40
+ throw new WaitTimeoutError(`Timed out after ${Math.round(opts.timeoutMs / 1000)}s waiting for analysis. ` +
41
+ `Status: ${result.status}. Run \`ish study insights ${opts.studyId}\` to check later.`, {
42
+ study_id: opts.studyId,
43
+ timeout_seconds: Math.round(opts.timeoutMs / 1000),
44
+ done: 0,
45
+ total: 1,
46
+ pending: 1,
47
+ rows: [],
48
+ });
49
+ }
50
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
51
+ }
52
+ }
53
+ function formatKeyInsightsTable(insights) {
54
+ printTable(["#", "CATEGORY", "TESTERS", "TITLE"], insights
55
+ .filter((i) => !i.is_discarded)
56
+ .map((i) => [
57
+ String(i.sequence),
58
+ i.category,
59
+ String(i.tester_count),
60
+ i.title,
61
+ ]));
62
+ }
63
+ function formatStudyAnalysis(result) {
64
+ const header = `Analysis ${result.id} — ${result.status}`;
65
+ console.log(header);
66
+ if (result.started_at)
67
+ console.log(` started_at: ${result.started_at}`);
68
+ if (result.progress_message)
69
+ console.log(` progress: ${result.progress_message}`);
70
+ if (result.error_message)
71
+ console.log(` error: ${result.error_message}`);
72
+ if (result.summary) {
73
+ console.log(`\nSummary:\n${result.summary}`);
74
+ }
75
+ if (result.key_insights && result.key_insights.length > 0) {
76
+ console.log("\nKey insights:");
77
+ formatKeyInsightsTable(result.key_insights);
78
+ }
79
+ }
80
+ export function attachStudyAnalyzeCommands(study) {
81
+ study
82
+ .command("analyze")
83
+ .description("Trigger an AI summary + key-insights analysis for a study. " +
84
+ "First analysis per study is free; subsequent runs cost 10 credits.")
85
+ .argument("[id]", "Study ID (defaults to active study)")
86
+ .option("--workspace <id>", "Workspace ID; accepted for consistency (workspace is inferred from the study)")
87
+ .option("--wait", "Poll until the run reaches completed or failed")
88
+ .option("--timeout <s>", "Wait timeout in seconds (default 300; only with --wait)")
89
+ .addHelpText("after", `
90
+ Examples:
91
+ $ ish study analyze # uses active study
92
+ $ ish study analyze <study-id>
93
+ $ ish study analyze <study-id> --wait
94
+ $ ish study analyze <study-id> --wait --timeout 600 --json
95
+
96
+ Prerequisites (enforced server-side):
97
+ - Study modality is one of: interactive, video, audio, text, image, document
98
+ - At least 5 testers with completed interactions
99
+
100
+ Read prior runs:
101
+ $ ish study insights <study-id>`)
102
+ .action(async (id, opts, cmd) => {
103
+ await withClient(cmd, async (client, globals) => {
104
+ const studyId = resolveStudy(id);
105
+ const initial = await client.post(`/studies/${studyId}/analysis`);
106
+ if (!opts.wait) {
107
+ if (globals.json) {
108
+ output(initial, true);
109
+ }
110
+ else {
111
+ formatStudyAnalysis(initial);
112
+ console.error(`\n Run \`ish study insights ${studyId}\` to read the result once it completes,\n or rerun with --wait to block.`);
113
+ }
114
+ return;
115
+ }
116
+ const timeoutMs = parseWaitTimeout(opts.timeout, 300_000);
117
+ const final = await pollAnalysisUntilDone(client, {
118
+ resultId: initial.id,
119
+ studyId,
120
+ timeoutMs,
121
+ quiet: globals.json,
122
+ });
123
+ if (globals.json) {
124
+ output(final, true);
125
+ }
126
+ else {
127
+ formatStudyAnalysis(final);
128
+ }
129
+ });
130
+ });
131
+ study
132
+ .command("insights")
133
+ .description("Read AI-generated insights from prior `ish study analyze` runs (newest first).")
134
+ .argument("[id]", "Study ID (defaults to active study)")
135
+ .option("--workspace <id>", "Workspace ID; accepted for consistency (workspace is inferred from the study)")
136
+ .option("--all", "Show every prior run as a compact table (default: latest only)")
137
+ .option("--result <id>", "Fetch a specific result by id rather than the latest")
138
+ .addHelpText("after", `
139
+ Examples:
140
+ $ ish study insights # latest run for active study
141
+ $ ish study insights <study-id>
142
+ $ ish study insights <study-id> --all
143
+ $ ish study insights <study-id> --json
144
+ $ ish study insights <study-id> --result <result-id>
145
+
146
+ Trigger a new run with \`ish study analyze --wait\`.`)
147
+ .action(async (id, opts, cmd) => {
148
+ await withClient(cmd, async (client, globals) => {
149
+ if (opts.result) {
150
+ const single = await client.get(`/study-results/${resolveId(opts.result)}`);
151
+ if (globals.json) {
152
+ output(single, true);
153
+ }
154
+ else {
155
+ formatStudyAnalysis(single);
156
+ }
157
+ return;
158
+ }
159
+ const studyId = resolveStudy(id);
160
+ const history = await client.get(`/studies/${studyId}/results`);
161
+ const latest = history[0] ?? null;
162
+ if (globals.json) {
163
+ output({ latest, history }, true);
164
+ return;
165
+ }
166
+ if (!latest) {
167
+ console.log("No analysis runs yet. Trigger one with `ish study analyze`.");
168
+ return;
169
+ }
170
+ if (opts.all) {
171
+ console.log(`Analysis history for study ${studyId}:`);
172
+ printTable(["#", "ID", "STATUS", "STARTED", "INSIGHTS"], history.map((r, idx) => [
173
+ String(history.length - idx),
174
+ r.id,
175
+ r.status,
176
+ r.started_at ?? r.created_at,
177
+ String((r.key_insights ?? []).length),
178
+ ]));
179
+ return;
180
+ }
181
+ formatStudyAnalysis(latest);
182
+ if (history.length > 1) {
183
+ console.error(`\n ${history.length - 1} prior run${history.length - 1 === 1 ? "" : "s"} — pass --all to list.`);
184
+ }
185
+ });
186
+ });
187
+ }