@elench/testkit 0.1.86 → 0.1.88
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 +19 -12
- package/lib/cli/agents/providers/claude.mjs +1 -1
- package/lib/cli/agents/providers/codex.mjs +1 -1
- package/lib/cli/assistant/prompt-builder.mjs +78 -0
- package/lib/cli/assistant/protocol.mjs +67 -0
- package/lib/cli/assistant/session.mjs +92 -0
- package/lib/cli/assistant/slash-commands.mjs +160 -0
- package/lib/cli/assistant/state.mjs +279 -0
- package/lib/cli/assistant/tool-registry.mjs +236 -0
- package/lib/cli/assistant/tool-run-reporter.mjs +80 -0
- package/lib/cli/command-helpers.mjs +40 -24
- package/lib/cli/commands/assistant.mjs +84 -0
- package/lib/cli/entrypoint.mjs +37 -11
- package/lib/cli/presentation/tree-reporter.mjs +34 -28
- package/lib/cli/tui/assistant-app.mjs +131 -0
- package/lib/cli/tui/detail-pane.mjs +161 -0
- package/lib/cli/tui/filter-bar.mjs +12 -0
- package/lib/cli/tui/fuzzy-match.mjs +106 -0
- package/lib/cli/tui/inspect-app.mjs +306 -0
- package/lib/cli/tui/inspect-artifact-adapter.mjs +3 -0
- package/lib/cli/tui/inspect-live-adapter.mjs +15 -0
- package/lib/cli/tui/inspect-model.mjs +817 -0
- package/lib/cli/tui/inspect-state.mjs +321 -0
- package/node_modules/@elench/next-analysis/package.json +1 -1
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/ts-analysis/package.json +1 -1
- package/package.json +6 -6
- package/lib/cli/commands/artifacts.mjs +0 -45
- package/lib/cli/commands/investigate.mjs +0 -87
- package/lib/cli/commands/logs.mjs +0 -47
- package/lib/cli/commands/show.mjs +0 -47
- package/lib/cli/commands/watch.mjs +0 -23
- package/lib/cli/tui/run-app.mjs +0 -1
- package/lib/cli/tui/run-session-app.mjs +0 -432
- package/lib/cli/tui/run-session-state.mjs +0 -505
- package/lib/cli/tui/run-tree-state.mjs +0 -1
- package/lib/cli/tui/watch-app.mjs +0 -220
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyArtifactToModel,
|
|
3
|
+
buildSnapshot,
|
|
4
|
+
createEmptyInspectModel,
|
|
5
|
+
cyclePane,
|
|
6
|
+
findEntryIdForFile,
|
|
7
|
+
findEntryIdForService,
|
|
8
|
+
finishModel,
|
|
9
|
+
initModelFromPlans,
|
|
10
|
+
markFileFinished,
|
|
11
|
+
markFileRunning,
|
|
12
|
+
markPlannedSkip,
|
|
13
|
+
markRuntimeError,
|
|
14
|
+
markServiceSkipped,
|
|
15
|
+
resetInspectModel,
|
|
16
|
+
revealEntry,
|
|
17
|
+
setPane,
|
|
18
|
+
setPhase,
|
|
19
|
+
setRegressionCatalog,
|
|
20
|
+
setTotalFileCount,
|
|
21
|
+
toggleCollapsed,
|
|
22
|
+
updateFilter,
|
|
23
|
+
} from "./inspect-model.mjs";
|
|
24
|
+
|
|
25
|
+
export function createInspectState({ dataSource = "live" } = {}) {
|
|
26
|
+
const model = createEmptyInspectModel(dataSource);
|
|
27
|
+
let mode = dataSource === "artifact" ? "complete" : "running";
|
|
28
|
+
let notice = null;
|
|
29
|
+
let agentSession = null;
|
|
30
|
+
const listeners = new Set();
|
|
31
|
+
|
|
32
|
+
function notify() {
|
|
33
|
+
for (const callback of listeners) callback();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function appendTranscriptEntry(kind, text) {
|
|
37
|
+
if (!agentSession || !text) return;
|
|
38
|
+
const normalizedText = String(text);
|
|
39
|
+
const entries = agentSession.transcriptEntries || [];
|
|
40
|
+
const lastEntry = entries.at(-1);
|
|
41
|
+
if (kind === "assistant" && lastEntry?.kind === "assistant") {
|
|
42
|
+
lastEntry.text += normalizedText;
|
|
43
|
+
agentSession.updatedAt = Date.now();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
entries.push({ kind, text: normalizedText });
|
|
47
|
+
agentSession.transcriptEntries = entries;
|
|
48
|
+
agentSession.updatedAt = Date.now();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function finishAgentSession(status, extra = {}) {
|
|
52
|
+
if (!agentSession) return;
|
|
53
|
+
agentSession = {
|
|
54
|
+
...agentSession,
|
|
55
|
+
...extra,
|
|
56
|
+
status,
|
|
57
|
+
endedAt: Date.now(),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function moveCursor(delta) {
|
|
62
|
+
const snapshot = getSnapshot();
|
|
63
|
+
if (snapshot.visibleEntries.length === 0) return;
|
|
64
|
+
const currentIndex = snapshot.visibleEntries.findIndex((entry) => entry.id === snapshot.selectedEntryId);
|
|
65
|
+
const nextIndex = clampIndex((currentIndex < 0 ? 0 : currentIndex) + delta, snapshot.visibleEntries.length);
|
|
66
|
+
model.selectedEntryId = snapshot.visibleEntries[nextIndex].id;
|
|
67
|
+
notify();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getSnapshot() {
|
|
71
|
+
return {
|
|
72
|
+
...buildSnapshot(model),
|
|
73
|
+
mode,
|
|
74
|
+
notice,
|
|
75
|
+
agentSession,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
initFromPlans(servicePlans) {
|
|
81
|
+
initModelFromPlans(model, servicePlans);
|
|
82
|
+
notify();
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
hydrateFromArtifact(artifact) {
|
|
86
|
+
applyArtifactToModel(model, artifact);
|
|
87
|
+
mode = model.finished ? "complete" : "running";
|
|
88
|
+
notify();
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
resetForLive() {
|
|
92
|
+
resetInspectModel(model, "live");
|
|
93
|
+
mode = "running";
|
|
94
|
+
notice = null;
|
|
95
|
+
agentSession = null;
|
|
96
|
+
notify();
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
setRegressionCatalog(document) {
|
|
100
|
+
setRegressionCatalog(model, document);
|
|
101
|
+
notify();
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
markFileRunning(serviceName, suiteKey, filePath) {
|
|
105
|
+
markFileRunning(model, serviceName, suiteKey, filePath);
|
|
106
|
+
notify();
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
markFileFinished(task, outcome) {
|
|
110
|
+
markFileFinished(model, task, outcome);
|
|
111
|
+
notify();
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
markServiceSkipped(serviceName, reason) {
|
|
115
|
+
markServiceSkipped(model, serviceName, reason);
|
|
116
|
+
notify();
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
markPlannedSkip(entry) {
|
|
120
|
+
markPlannedSkip(model, entry);
|
|
121
|
+
notify();
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
markRuntimeError(task, message) {
|
|
125
|
+
markRuntimeError(model, task, message);
|
|
126
|
+
notify();
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
setTotalFileCount(count) {
|
|
130
|
+
setTotalFileCount(model, count);
|
|
131
|
+
notify();
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
setPhase(label) {
|
|
135
|
+
setPhase(model, label);
|
|
136
|
+
notify();
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
finish(results, durationMs, regressionReport) {
|
|
140
|
+
finishModel(model, results, durationMs, regressionReport);
|
|
141
|
+
mode = "complete";
|
|
142
|
+
notify();
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
setNotice(message) {
|
|
146
|
+
notice = message ? String(message) : null;
|
|
147
|
+
notify();
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
clearNotice() {
|
|
151
|
+
if (!notice) return;
|
|
152
|
+
notice = null;
|
|
153
|
+
notify();
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
moveCursorUp() {
|
|
157
|
+
moveCursor(-1);
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
moveCursorDown() {
|
|
161
|
+
moveCursor(1);
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
moveCursorToEntry(entryId) {
|
|
165
|
+
model.selectedEntryId = entryId || null;
|
|
166
|
+
notify();
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
toggleExpand() {
|
|
170
|
+
const snapshot = getSnapshot();
|
|
171
|
+
if (!snapshot.selectedEntry) return;
|
|
172
|
+
toggleCollapsed(model, snapshot.selectedEntry.id);
|
|
173
|
+
notify();
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
activateFilter() {
|
|
177
|
+
model.filterActive = true;
|
|
178
|
+
notify();
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
updateFilterQuery(query) {
|
|
182
|
+
updateFilter(model, query);
|
|
183
|
+
const snapshot = buildSnapshot(model);
|
|
184
|
+
if (snapshot.filter.results.length > 0) {
|
|
185
|
+
model.selectedEntryId = snapshot.filter.results[0].id;
|
|
186
|
+
}
|
|
187
|
+
notify();
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
deactivateFilter() {
|
|
191
|
+
model.filterActive = false;
|
|
192
|
+
model.filterQuery = "";
|
|
193
|
+
model.filterMatches = new Map();
|
|
194
|
+
notify();
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
revealFile(serviceName, filePath) {
|
|
198
|
+
const entryId = findEntryIdForFile(model, serviceName, filePath);
|
|
199
|
+
if (!entryId) return false;
|
|
200
|
+
revealEntry(model, entryId);
|
|
201
|
+
notify();
|
|
202
|
+
return true;
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
revealService(serviceName) {
|
|
206
|
+
const entryId = findEntryIdForService(model, serviceName);
|
|
207
|
+
if (!entryId) return false;
|
|
208
|
+
revealEntry(model, entryId);
|
|
209
|
+
notify();
|
|
210
|
+
return true;
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
cyclePaneMode() {
|
|
214
|
+
cyclePane(model);
|
|
215
|
+
notify();
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
setPaneMode(paneMode) {
|
|
219
|
+
setPane(model, paneMode);
|
|
220
|
+
notify();
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
beginInvestigation({ provider, userMessage } = {}) {
|
|
224
|
+
mode = "investigating";
|
|
225
|
+
notice = null;
|
|
226
|
+
agentSession = {
|
|
227
|
+
provider: provider || "auto",
|
|
228
|
+
userMessage: userMessage || "",
|
|
229
|
+
status: "starting",
|
|
230
|
+
startedAt: Date.now(),
|
|
231
|
+
updatedAt: Date.now(),
|
|
232
|
+
rawEvents: [],
|
|
233
|
+
transcriptEntries: [],
|
|
234
|
+
timeline: [],
|
|
235
|
+
summary: null,
|
|
236
|
+
activePhase: "planning",
|
|
237
|
+
activeStep: null,
|
|
238
|
+
viewMode: "summary",
|
|
239
|
+
};
|
|
240
|
+
notify();
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
recordInvestigationProgress(event, presentation = null) {
|
|
244
|
+
if (!agentSession || !event) return;
|
|
245
|
+
agentSession.rawEvents.push({ ...event });
|
|
246
|
+
if (event.type === "start") {
|
|
247
|
+
agentSession.status = "running";
|
|
248
|
+
} else if (event.type === "delta") {
|
|
249
|
+
appendTranscriptEntry("assistant", event.text || "");
|
|
250
|
+
} else if (event.type === "final") {
|
|
251
|
+
if (event.text && !(agentSession.transcriptEntries || []).some((entry) => entry.kind === "assistant")) {
|
|
252
|
+
appendTranscriptEntry("assistant", event.text);
|
|
253
|
+
}
|
|
254
|
+
agentSession.finalText = event.text || agentSession.finalText || "";
|
|
255
|
+
} else if (event.type === "tool") {
|
|
256
|
+
appendTranscriptEntry("tool", event.detail ? `${event.name}: ${event.detail}` : event.name);
|
|
257
|
+
} else if (event.type === "status") {
|
|
258
|
+
appendTranscriptEntry("status", event.message || "");
|
|
259
|
+
} else if (event.type === "error") {
|
|
260
|
+
appendTranscriptEntry("error", event.message || "Agent error");
|
|
261
|
+
} else if (event.type === "exit") {
|
|
262
|
+
agentSession.exitCode = event.code;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (presentation) {
|
|
266
|
+
agentSession.activePhase = presentation.phase || agentSession.activePhase || null;
|
|
267
|
+
agentSession.activeStep = presentation.activeStep || null;
|
|
268
|
+
agentSession.timeline = presentation.timeline || [];
|
|
269
|
+
agentSession.summary = presentation.summary || null;
|
|
270
|
+
}
|
|
271
|
+
notify();
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
toggleInvestigationViewMode() {
|
|
275
|
+
if (!agentSession) return;
|
|
276
|
+
agentSession.viewMode = agentSession.viewMode === "summary" ? "transcript" : "summary";
|
|
277
|
+
notify();
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
completeAgentSession(result = {}) {
|
|
281
|
+
finishAgentSession("complete", {
|
|
282
|
+
finalText: result.finalText || agentSession?.finalText || "",
|
|
283
|
+
exitCode: result.exitCode ?? agentSession?.exitCode ?? 0,
|
|
284
|
+
});
|
|
285
|
+
notify();
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
failAgentSession(error) {
|
|
289
|
+
finishAgentSession("error", {
|
|
290
|
+
error: error instanceof Error ? error.message : String(error || "Agent error"),
|
|
291
|
+
});
|
|
292
|
+
notify();
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
cancelAgentSession(message = "Cancelled investigation.") {
|
|
296
|
+
finishAgentSession("cancelled");
|
|
297
|
+
mode = "complete";
|
|
298
|
+
notice = message;
|
|
299
|
+
notify();
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
returnToSummary() {
|
|
303
|
+
mode = "complete";
|
|
304
|
+
notify();
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
subscribe(callback) {
|
|
308
|
+
listeners.add(callback);
|
|
309
|
+
return () => listeners.delete(callback);
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
getSnapshot,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function clampIndex(index, length) {
|
|
317
|
+
if (length <= 0) return 0;
|
|
318
|
+
if (index < 0) return 0;
|
|
319
|
+
if (index >= length) return length - 1;
|
|
320
|
+
return index;
|
|
321
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.88",
|
|
4
4
|
"description": "Browser bridge helpers for testkit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@elench/testkit-protocol": "0.1.
|
|
25
|
+
"@elench/testkit-protocol": "0.1.88"
|
|
26
26
|
},
|
|
27
27
|
"private": false
|
|
28
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.88",
|
|
4
4
|
"description": "CLI for discovering and running local HTTP, DAL, and Playwright test suites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"oclif": {
|
|
53
53
|
"bin": "testkit",
|
|
54
54
|
"commands": "./lib/cli/commands",
|
|
55
|
-
"default": "
|
|
55
|
+
"default": "assistant",
|
|
56
56
|
"topicSeparator": " "
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
@@ -82,10 +82,10 @@
|
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
84
|
"@babel/code-frame": "^7.29.0",
|
|
85
|
-
"@elench/next-analysis": "0.1.
|
|
86
|
-
"@elench/testkit-bridge": "0.1.
|
|
87
|
-
"@elench/testkit-protocol": "0.1.
|
|
88
|
-
"@elench/ts-analysis": "0.1.
|
|
85
|
+
"@elench/next-analysis": "0.1.88",
|
|
86
|
+
"@elench/testkit-bridge": "0.1.88",
|
|
87
|
+
"@elench/testkit-protocol": "0.1.88",
|
|
88
|
+
"@elench/ts-analysis": "0.1.88",
|
|
89
89
|
"@oclif/core": "^4.10.6",
|
|
90
90
|
"esbuild": "^0.25.11",
|
|
91
91
|
"execa": "^9.5.0",
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { Args, Command } from "@oclif/core";
|
|
2
|
-
import { sharedFlags } from "../command-helpers.mjs";
|
|
3
|
-
import { collectArtifactEntries, loadCurrentRunArtifact } from "../viewer.mjs";
|
|
4
|
-
|
|
5
|
-
export default class ArtifactsCommand extends Command {
|
|
6
|
-
static summary = "List persisted artifacts from the latest run";
|
|
7
|
-
|
|
8
|
-
static enableJsonFlag = true;
|
|
9
|
-
|
|
10
|
-
static args = {
|
|
11
|
-
file: Args.string({
|
|
12
|
-
description: "Optional file path to filter artifacts",
|
|
13
|
-
required: false,
|
|
14
|
-
}),
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
static flags = sharedFlags;
|
|
18
|
-
|
|
19
|
-
async run() {
|
|
20
|
-
const { args, flags } = await this.parse(ArtifactsCommand);
|
|
21
|
-
const productDir = flags.dir || process.cwd();
|
|
22
|
-
const runArtifact = loadCurrentRunArtifact(productDir);
|
|
23
|
-
const entries = collectArtifactEntries(productDir, runArtifact, args.file || null, flags.service || null)
|
|
24
|
-
.map((entry) => ({
|
|
25
|
-
service: entry.service.name,
|
|
26
|
-
suite: `${entry.suite.type}:${entry.suite.name}`,
|
|
27
|
-
file: entry.file.path,
|
|
28
|
-
name: entry.artifactRef.name,
|
|
29
|
-
kind: entry.artifactRef.kind,
|
|
30
|
-
summary: entry.artifactRef.summary,
|
|
31
|
-
path: entry.artifactRef.path,
|
|
32
|
-
}));
|
|
33
|
-
|
|
34
|
-
if (!this.jsonEnabled()) {
|
|
35
|
-
for (const entry of entries) {
|
|
36
|
-
this.log(`${entry.file}`);
|
|
37
|
-
this.log(` ${entry.name}${entry.kind ? ` [${entry.kind}]` : ""}`);
|
|
38
|
-
if (entry.summary) this.log(` ${entry.summary}`);
|
|
39
|
-
this.log(` ${entry.path}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return entries;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { Args, Command, Flags } from "@oclif/core";
|
|
2
|
-
import { sharedFlags } from "../command-helpers.mjs";
|
|
3
|
-
import { loadCurrentRunArtifact, resolveFileSubject } from "../viewer.mjs";
|
|
4
|
-
import { runInteractiveInvestigation, startHostedInvestigation } from "../agents/investigate.mjs";
|
|
5
|
-
|
|
6
|
-
export default class InvestigateCommand extends Command {
|
|
7
|
-
static summary = "Investigate a failed file from the latest run with Codex or Claude";
|
|
8
|
-
|
|
9
|
-
static enableJsonFlag = true;
|
|
10
|
-
|
|
11
|
-
static args = {
|
|
12
|
-
file: Args.string({
|
|
13
|
-
description: "Optional file path; defaults to the first failed file",
|
|
14
|
-
required: false,
|
|
15
|
-
}),
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
static flags = {
|
|
19
|
-
...sharedFlags,
|
|
20
|
-
provider: Flags.string({
|
|
21
|
-
description: "Agent provider to use",
|
|
22
|
-
options: ["auto", "claude", "codex"],
|
|
23
|
-
default: "auto",
|
|
24
|
-
}),
|
|
25
|
-
message: Flags.string({
|
|
26
|
-
description: "Additional user instruction for the investigation prompt",
|
|
27
|
-
}),
|
|
28
|
-
handoff: Flags.boolean({
|
|
29
|
-
description: "Launch the provider's native interactive TUI instead of hosted output",
|
|
30
|
-
default: false,
|
|
31
|
-
}),
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
async run() {
|
|
35
|
-
const { args, flags } = await this.parse(InvestigateCommand);
|
|
36
|
-
const productDir = flags.dir || process.cwd();
|
|
37
|
-
const runArtifact = loadCurrentRunArtifact(productDir);
|
|
38
|
-
const subject = resolveFileSubject(runArtifact, args.file || null, flags.service || null);
|
|
39
|
-
|
|
40
|
-
if (flags.handoff) {
|
|
41
|
-
const result = await runInteractiveInvestigation({
|
|
42
|
-
productDir,
|
|
43
|
-
serviceName: subject.service.name,
|
|
44
|
-
filePath: subject.file.path,
|
|
45
|
-
provider: flags.provider,
|
|
46
|
-
userMessage: flags.message || null,
|
|
47
|
-
});
|
|
48
|
-
if (!this.jsonEnabled()) {
|
|
49
|
-
this.log(`${result.provider} exited with code ${result.exitCode}`);
|
|
50
|
-
}
|
|
51
|
-
return result;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
let finalText = "";
|
|
55
|
-
const session = startHostedInvestigation({
|
|
56
|
-
productDir,
|
|
57
|
-
serviceName: subject.service.name,
|
|
58
|
-
filePath: subject.file.path,
|
|
59
|
-
provider: flags.provider,
|
|
60
|
-
userMessage: flags.message || null,
|
|
61
|
-
onEvent: this.jsonEnabled()
|
|
62
|
-
? null
|
|
63
|
-
: (event) => {
|
|
64
|
-
if (event.type === "status" || event.type === "tool") {
|
|
65
|
-
this.log(event.type === "tool" ? `[tool] ${event.name}${event.detail ? `: ${event.detail}` : ""}` : `[status] ${event.message}`);
|
|
66
|
-
} else if (event.type === "error") {
|
|
67
|
-
this.error(event.message);
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
const result = await session.completion;
|
|
72
|
-
finalText = result.finalText || "";
|
|
73
|
-
|
|
74
|
-
if (!this.jsonEnabled() && finalText.trim()) {
|
|
75
|
-
this.log("");
|
|
76
|
-
this.log(finalText.trim());
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
provider: result.provider,
|
|
81
|
-
exitCode: result.exitCode,
|
|
82
|
-
file: subject.file.path,
|
|
83
|
-
service: subject.service.name,
|
|
84
|
-
finalText,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { Args, Command, Flags } from "@oclif/core";
|
|
2
|
-
import { sharedFlags } from "../command-helpers.mjs";
|
|
3
|
-
import { readLogTail } from "../../runner/logs.mjs";
|
|
4
|
-
import { getServiceLogRefs, loadCurrentRunArtifact, resolveFileSubject } from "../viewer.mjs";
|
|
5
|
-
import path from "path";
|
|
6
|
-
|
|
7
|
-
export default class LogsCommand extends Command {
|
|
8
|
-
static summary = "Show backend log tails relevant to one file from the latest run";
|
|
9
|
-
|
|
10
|
-
static enableJsonFlag = true;
|
|
11
|
-
|
|
12
|
-
static args = {
|
|
13
|
-
file: Args.string({
|
|
14
|
-
description: "Optional file path; defaults to the first failed file",
|
|
15
|
-
required: false,
|
|
16
|
-
}),
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
static flags = {
|
|
20
|
-
...sharedFlags,
|
|
21
|
-
tail: Flags.integer({
|
|
22
|
-
description: "Number of lines to show from each log",
|
|
23
|
-
default: 40,
|
|
24
|
-
}),
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
async run() {
|
|
28
|
-
const { args, flags } = await this.parse(LogsCommand);
|
|
29
|
-
const productDir = flags.dir || process.cwd();
|
|
30
|
-
const runArtifact = loadCurrentRunArtifact(productDir);
|
|
31
|
-
const subject = resolveFileSubject(runArtifact, args.file || null, flags.service || null);
|
|
32
|
-
const logs = getServiceLogRefs(runArtifact, subject.service.name).map((entry) => ({
|
|
33
|
-
...entry,
|
|
34
|
-
lines: readLogTail(path.join(productDir, entry.path), flags.tail),
|
|
35
|
-
}));
|
|
36
|
-
|
|
37
|
-
if (!this.jsonEnabled()) {
|
|
38
|
-
for (const entry of logs) {
|
|
39
|
-
this.log(`${entry.runtimeLabel}`);
|
|
40
|
-
this.log(` ${entry.path}`);
|
|
41
|
-
for (const line of entry.lines) this.log(` ${line}`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return logs;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { Args, Command, Flags } from "@oclif/core";
|
|
2
|
-
import { sharedFlags } from "../command-helpers.mjs";
|
|
3
|
-
import { formatFileDetail, loadCurrentRunArtifact, resolveFileSubject } from "../viewer.mjs";
|
|
4
|
-
|
|
5
|
-
export default class ShowCommand extends Command {
|
|
6
|
-
static summary = "Show the most useful details for one file from the latest run";
|
|
7
|
-
|
|
8
|
-
static enableJsonFlag = true;
|
|
9
|
-
|
|
10
|
-
static args = {
|
|
11
|
-
file: Args.string({
|
|
12
|
-
description: "File path to inspect; defaults to the first failed file",
|
|
13
|
-
required: false,
|
|
14
|
-
}),
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
static flags = {
|
|
18
|
-
...sharedFlags,
|
|
19
|
-
"log-tail": Flags.integer({
|
|
20
|
-
description: "Number of backend log lines to include",
|
|
21
|
-
default: 12,
|
|
22
|
-
}),
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
async run() {
|
|
26
|
-
const { args, flags } = await this.parse(ShowCommand);
|
|
27
|
-
const productDir = flags.dir || process.cwd();
|
|
28
|
-
const runArtifact = loadCurrentRunArtifact(productDir);
|
|
29
|
-
const subject = resolveFileSubject(runArtifact, args.file || null, flags.service || null);
|
|
30
|
-
const result = {
|
|
31
|
-
file: subject.file,
|
|
32
|
-
suite: {
|
|
33
|
-
name: subject.suite.name,
|
|
34
|
-
type: subject.suite.type,
|
|
35
|
-
},
|
|
36
|
-
service: {
|
|
37
|
-
name: subject.service.name,
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
if (!this.jsonEnabled()) {
|
|
41
|
-
for (const line of formatFileDetail(productDir, runArtifact, subject, { logTail: flags["log-tail"] })) {
|
|
42
|
-
this.log(line);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return result;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import React, { createElement } from "react";
|
|
2
|
-
import { Command } from "@oclif/core";
|
|
3
|
-
import { render } from "ink";
|
|
4
|
-
import { sharedFlags } from "../command-helpers.mjs";
|
|
5
|
-
import { WatchApp } from "../tui/watch-app.mjs";
|
|
6
|
-
|
|
7
|
-
export default class WatchCommand extends Command {
|
|
8
|
-
static summary = "Open an interactive viewer for the latest run artifact";
|
|
9
|
-
|
|
10
|
-
static flags = sharedFlags;
|
|
11
|
-
|
|
12
|
-
async run() {
|
|
13
|
-
const { flags } = await this.parse(WatchCommand);
|
|
14
|
-
const productDir = flags.dir || process.cwd();
|
|
15
|
-
const app = render(
|
|
16
|
-
createElement(WatchApp, {
|
|
17
|
-
productDir,
|
|
18
|
-
serviceFilter: flags.service || null,
|
|
19
|
-
})
|
|
20
|
-
);
|
|
21
|
-
await app.waitUntilExit();
|
|
22
|
-
}
|
|
23
|
-
}
|
package/lib/cli/tui/run-app.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { RunSessionApp as RunApp } from "./run-session-app.mjs";
|