@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.
- package/dist/commands/chat-config.d.ts +23 -0
- package/dist/commands/chat-config.js +289 -0
- package/dist/commands/chat.js +26 -37
- package/dist/commands/iteration.js +219 -22
- package/dist/commands/profile.js +75 -9
- package/dist/commands/source.js +6 -4
- package/dist/commands/study-analyze.d.ts +41 -0
- package/dist/commands/study-analyze.js +187 -0
- package/dist/commands/study-run.js +359 -30
- package/dist/commands/study-screenshots.d.ts +20 -0
- package/dist/commands/study-screenshots.js +216 -0
- package/dist/commands/study.js +174 -9
- package/dist/commands/workspace.js +35 -2
- package/dist/lib/accessibility-profile.d.ts +12 -0
- package/dist/lib/accessibility-profile.js +136 -0
- package/dist/lib/alias-store.d.ts +1 -0
- package/dist/lib/alias-store.js +1 -0
- package/dist/lib/ask-questions.js +9 -0
- package/dist/lib/billing.d.ts +55 -0
- package/dist/lib/billing.js +77 -0
- package/dist/lib/command-helpers.d.ts +6 -0
- package/dist/lib/command-helpers.js +12 -0
- package/dist/lib/docs.js +1181 -38
- package/dist/lib/enums.d.ts +54 -0
- package/dist/lib/enums.js +100 -0
- package/dist/lib/local-sim/actions.d.ts +2 -1
- package/dist/lib/local-sim/actions.js +88 -13
- package/dist/lib/local-sim/loop.js +49 -19
- package/dist/lib/local-sim/tabs.d.ts +27 -0
- package/dist/lib/local-sim/tabs.js +157 -0
- package/dist/lib/local-sim/types.d.ts +15 -0
- package/dist/lib/modality.d.ts +70 -1
- package/dist/lib/modality.js +323 -17
- package/dist/lib/output.js +61 -4
- package/dist/lib/skill-content.js +397 -19
- package/dist/lib/types.d.ts +6 -1
- package/dist/lib/types.js +1 -1
- 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
|
+
}
|