@tokentop/ttop 0.2.2 → 0.3.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/README.md +2 -5
- package/package.json +8 -5
- package/src/agents/aggregator.ts +39 -19
- package/src/agents/costing.ts +11 -9
- package/src/agents/index.ts +7 -9
- package/src/agents/types.ts +2 -2
- package/src/cli.ts +28 -23
- package/src/config/schema.test.ts +42 -40
- package/src/config/schema.ts +26 -26
- package/src/demo/simulator.ts +161 -122
- package/src/plugins/agents/index.ts +2 -2
- package/src/plugins/auth-sources.ts +42 -38
- package/src/plugins/lifecycle.ts +32 -32
- package/src/plugins/loader.test.ts +49 -49
- package/src/plugins/loader.ts +67 -63
- package/src/plugins/notification-bus.ts +21 -16
- package/src/plugins/notifications/index.ts +2 -2
- package/src/plugins/notifications/terminal-bell.ts +29 -23
- package/src/plugins/notifications/visual-flash.ts +21 -19
- package/src/plugins/npm-installer.ts +23 -25
- package/src/plugins/plugin-context-factory.ts +10 -10
- package/src/plugins/plugin-host.ts +12 -5
- package/src/plugins/providers/anthropic.ts +64 -57
- package/src/plugins/providers/antigravity.ts +81 -69
- package/src/plugins/providers/codex.ts +42 -42
- package/src/plugins/providers/gemini.ts +106 -87
- package/src/plugins/providers/github-copilot.ts +49 -45
- package/src/plugins/providers/index.ts +10 -12
- package/src/plugins/providers/minimax.ts +50 -44
- package/src/plugins/providers/openai-api.ts +82 -62
- package/src/plugins/providers/opencode-zen.ts +46 -39
- package/src/plugins/providers/perplexity.ts +55 -46
- package/src/plugins/providers/zai.ts +66 -57
- package/src/plugins/registry.ts +47 -43
- package/src/plugins/sandbox-guard.test.ts +45 -48
- package/src/plugins/sandbox-guard.ts +20 -25
- package/src/plugins/sandbox.ts +26 -48
- package/src/plugins/themes/catppuccin-latte.ts +29 -29
- package/src/plugins/themes/catppuccin-mocha.ts +29 -29
- package/src/plugins/themes/claude-code.ts +29 -29
- package/src/plugins/themes/dracula.ts +29 -29
- package/src/plugins/themes/github-light.ts +29 -29
- package/src/plugins/themes/gruvbox-dark.ts +29 -29
- package/src/plugins/themes/gruvbox-light.ts +29 -29
- package/src/plugins/themes/index.ts +15 -15
- package/src/plugins/themes/kanagawa.ts +29 -29
- package/src/plugins/themes/nord.ts +29 -29
- package/src/plugins/themes/one-dark.ts +29 -29
- package/src/plugins/themes/opencode.ts +31 -31
- package/src/plugins/themes/rose-pine-dawn.ts +29 -29
- package/src/plugins/themes/rose-pine.ts +29 -29
- package/src/plugins/themes/solarized-light.ts +29 -29
- package/src/plugins/themes/tokyo-night.ts +29 -29
- package/src/plugins/types/agent.ts +5 -5
- package/src/plugins/types/base.ts +35 -24
- package/src/plugins/types/index.ts +9 -9
- package/src/plugins/types/notification.ts +12 -12
- package/src/plugins/types/provider.ts +12 -8
- package/src/plugins/types/theme.ts +31 -23
- package/src/plugins/update-checker.ts +6 -6
- package/src/pricing/estimator.test.ts +39 -44
- package/src/pricing/estimator.ts +7 -13
- package/src/pricing/fallback.test.ts +30 -34
- package/src/pricing/fallback.ts +54 -27
- package/src/pricing/index.ts +18 -12
- package/src/pricing/models-dev.ts +6 -6
- package/src/storage/database.ts +56 -46
- package/src/storage/db.ts +12 -12
- package/src/storage/index.ts +44 -45
- package/src/storage/migrations/index.ts +2 -2
- package/src/storage/paths.ts +13 -13
- package/src/storage/repos/agentSessions.ts +33 -23
- package/src/storage/repos/providerSnapshots.ts +5 -5
- package/src/storage/repos/usageEvents.ts +40 -28
- package/src/storage/types.ts +5 -5
- package/src/tui/App.tsx +180 -137
- package/src/tui/components/CommandPalette.tsx +46 -34
- package/src/tui/components/DebugConsole.tsx +39 -33
- package/src/tui/components/DebugPanel.tsx +295 -177
- package/src/tui/components/FooterHints.tsx +2 -2
- package/src/tui/components/GhostProviderCard.tsx +37 -35
- package/src/tui/components/Header.tsx +68 -35
- package/src/tui/components/HelpOverlay.tsx +99 -29
- package/src/tui/components/InlineGauge.tsx +10 -7
- package/src/tui/components/InlineSparkline.tsx +17 -11
- package/src/tui/components/KpiStrip.tsx +128 -98
- package/src/tui/components/LimitGauge.tsx +96 -59
- package/src/tui/components/ModalBackdrop.tsx +4 -4
- package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
- package/src/tui/components/ProviderCard.tsx +232 -226
- package/src/tui/components/ProviderDetailPanel.tsx +133 -58
- package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
- package/src/tui/components/ProvidersList.tsx +215 -126
- package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
- package/src/tui/components/SessionsTable.tsx +313 -166
- package/src/tui/components/SettingsModal.tsx +231 -214
- package/src/tui/components/SidebarBreakdown.tsx +30 -16
- package/src/tui/components/Skeleton.tsx +24 -17
- package/src/tui/components/SmartSidebar.tsx +360 -258
- package/src/tui/components/Sparkline.tsx +64 -52
- package/src/tui/components/Spinner.tsx +3 -3
- package/src/tui/components/StatusBar.tsx +27 -27
- package/src/tui/components/ThemePicker.tsx +67 -66
- package/src/tui/components/Toast.tsx +9 -6
- package/src/tui/components/UsageGauge.tsx +10 -13
- package/src/tui/components/index.ts +5 -5
- package/src/tui/contexts/AgentSessionContext.tsx +193 -144
- package/src/tui/contexts/ConfigContext.tsx +15 -13
- package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
- package/src/tui/contexts/DemoModeContext.tsx +22 -15
- package/src/tui/contexts/DrawerContext.tsx +11 -9
- package/src/tui/contexts/InputContext.tsx +2 -2
- package/src/tui/contexts/LogContext.tsx +62 -47
- package/src/tui/contexts/PluginContext.tsx +232 -183
- package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
- package/src/tui/contexts/StorageContext.tsx +151 -117
- package/src/tui/contexts/ThemeContext.tsx +21 -16
- package/src/tui/contexts/TimeWindowContext.tsx +72 -61
- package/src/tui/contexts/ToastContext.tsx +10 -7
- package/src/tui/contexts/index.ts +9 -5
- package/src/tui/createApp.tsx +8 -12
- package/src/tui/debug/captureFrame.ts +25 -28
- package/src/tui/debug/snapshot.tsx +179 -162
- package/src/tui/driver/assertions.ts +30 -22
- package/src/tui/driver/cli.ts +231 -221
- package/src/tui/driver/coverage.ts +27 -44
- package/src/tui/driver/demo-snapshot.ts +33 -33
- package/src/tui/driver/diff.ts +26 -32
- package/src/tui/driver/driver.ts +43 -48
- package/src/tui/driver/index.ts +36 -40
- package/src/tui/driver/recorder.ts +37 -48
- package/src/tui/driver/test-workflow.ts +51 -51
- package/src/tui/driver/test.ts +25 -25
- package/src/tui/hooks/index.ts +24 -20
- package/src/tui/hooks/useAnimatedValue.ts +13 -18
- package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
- package/src/tui/hooks/useDashboardState.ts +26 -24
- package/src/tui/hooks/useEmaActivity.ts +82 -74
- package/src/tui/hooks/useEntranceAnimation.ts +11 -11
- package/src/tui/hooks/useExitAnimation.ts +3 -3
- package/src/tui/hooks/usePulse.ts +4 -4
- package/src/tui/hooks/useSafeRenderer.ts +5 -5
- package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
- package/src/tui/hooks/useValueFlash.ts +10 -14
- package/src/tui/index.tsx +29 -6
- package/src/tui/shutdown.ts +19 -0
- package/src/tui/utils/providerColor.ts +1 -1
- package/src/tui/views/Dashboard.tsx +127 -102
- package/src/tui/views/HistoricalTrendsView.tsx +579 -250
- package/src/tui/views/ProjectsView.tsx +466 -237
- package/src/tui/views/RealTimeDashboard.tsx +150 -111
- package/src/tui/views/SettingsView.tsx +222 -191
- package/src/tui/views/index.ts +2 -2
- package/src/types/opentui-test-utils.d.ts +7 -7
- package/src/utils/clipboard.ts +29 -29
- package/src/utils/currency.test.ts +53 -53
- package/src/utils/currency.ts +35 -32
- package/src/version.ts +1 -1
package/src/tui/driver/cli.ts
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
4
|
-
import * as
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
2
|
+
import * as fs from "fs/promises";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as readline from "readline";
|
|
5
|
+
import { assertSnapshot, deleteGoldenFile, getGoldenFile, listGoldenFiles } from "./assertions.ts";
|
|
6
|
+
import {
|
|
7
|
+
type CoverageTracker,
|
|
8
|
+
createCoverageTracker,
|
|
9
|
+
detectViewFromFrame,
|
|
10
|
+
formatCoverageReport,
|
|
11
|
+
} from "./coverage.ts";
|
|
12
|
+
import { diffFrames, highlightDiff } from "./diff.ts";
|
|
13
|
+
import { createDriver, type Driver, type DriverOptions } from "./driver.ts";
|
|
8
14
|
import {
|
|
9
15
|
createRecorder,
|
|
10
|
-
replayRecording,
|
|
11
|
-
saveRecording,
|
|
12
|
-
loadRecording,
|
|
13
|
-
listRecordings,
|
|
14
16
|
deleteRecording,
|
|
17
|
+
listRecordings,
|
|
18
|
+
loadRecording,
|
|
15
19
|
type Recorder,
|
|
16
20
|
type Recording,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
detectViewFromFrame,
|
|
21
|
-
formatCoverageReport,
|
|
22
|
-
type CoverageTracker,
|
|
23
|
-
} from './coverage.ts';
|
|
21
|
+
replayRecording,
|
|
22
|
+
saveRecording,
|
|
23
|
+
} from "./recorder.ts";
|
|
24
24
|
|
|
25
|
-
const DEFAULT_SNAPSHOTS_DIR =
|
|
25
|
+
const DEFAULT_SNAPSHOTS_DIR = "./snapshots";
|
|
26
26
|
|
|
27
27
|
interface Command {
|
|
28
28
|
action: string;
|
|
@@ -49,28 +49,29 @@ async function ensureDir(dir: string): Promise<void> {
|
|
|
49
49
|
async function saveFrameToFile(frame: string, filePath: string): Promise<string> {
|
|
50
50
|
const dir = path.dirname(filePath);
|
|
51
51
|
await ensureDir(dir);
|
|
52
|
-
await fs.writeFile(filePath, frame,
|
|
52
|
+
await fs.writeFile(filePath, frame, "utf-8");
|
|
53
53
|
return path.resolve(filePath);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
async function handleCommand(cmd: Command): Promise<Response> {
|
|
57
57
|
try {
|
|
58
58
|
switch (cmd.action) {
|
|
59
|
-
case
|
|
59
|
+
case "launch": {
|
|
60
60
|
if (driver?.isRunning()) {
|
|
61
|
-
return { ok: false, error:
|
|
61
|
+
return { ok: false, error: "Driver already running" };
|
|
62
62
|
}
|
|
63
|
-
currentWidth = typeof cmd.width ===
|
|
64
|
-
currentHeight = typeof cmd.height ===
|
|
63
|
+
currentWidth = typeof cmd.width === "number" ? cmd.width : 100;
|
|
64
|
+
currentHeight = typeof cmd.height === "number" ? cmd.height : 30;
|
|
65
65
|
const options: DriverOptions = {
|
|
66
66
|
width: currentWidth,
|
|
67
67
|
height: currentHeight,
|
|
68
68
|
appOptions: {
|
|
69
69
|
debug: cmd.debug === true,
|
|
70
70
|
demoMode: cmd.demo === true,
|
|
71
|
-
...(typeof cmd.demoSeed ===
|
|
72
|
-
...(typeof cmd.demoPreset ===
|
|
73
|
-
|
|
71
|
+
...(typeof cmd.demoSeed === "number" ? { demoSeed: cmd.demoSeed, demoMode: true } : {}),
|
|
72
|
+
...(typeof cmd.demoPreset === "string" &&
|
|
73
|
+
["light", "normal", "heavy"].includes(cmd.demoPreset)
|
|
74
|
+
? { demoPreset: cmd.demoPreset as "light" | "normal" | "heavy", demoMode: true }
|
|
74
75
|
: {}),
|
|
75
76
|
},
|
|
76
77
|
};
|
|
@@ -79,203 +80,207 @@ async function handleCommand(cmd: Command): Promise<Response> {
|
|
|
79
80
|
return { ok: true };
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
case
|
|
83
|
+
case "close": {
|
|
83
84
|
if (!driver) {
|
|
84
|
-
return { ok: false, error:
|
|
85
|
+
return { ok: false, error: "Driver not running" };
|
|
85
86
|
}
|
|
86
87
|
await driver.close();
|
|
87
88
|
driver = null;
|
|
88
89
|
return { ok: true };
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
case
|
|
92
|
+
case "sendKeys": {
|
|
92
93
|
if (!driver?.isRunning()) {
|
|
93
|
-
return { ok: false, error:
|
|
94
|
+
return { ok: false, error: "Driver not running" };
|
|
94
95
|
}
|
|
95
|
-
const keys = typeof cmd.keys ===
|
|
96
|
+
const keys = typeof cmd.keys === "string" ? cmd.keys : "";
|
|
96
97
|
await driver.sendKeys(keys);
|
|
97
|
-
recorder?.addCommand(
|
|
98
|
+
recorder?.addCommand("sendKeys", { keys });
|
|
98
99
|
if (recorder?.isRecording()) {
|
|
99
100
|
recorder.captureFrame(await driver.capture());
|
|
100
101
|
}
|
|
101
102
|
return { ok: true };
|
|
102
103
|
}
|
|
103
104
|
|
|
104
|
-
case
|
|
105
|
+
case "pressKey": {
|
|
105
106
|
if (!driver?.isRunning()) {
|
|
106
|
-
return { ok: false, error:
|
|
107
|
+
return { ok: false, error: "Driver not running" };
|
|
107
108
|
}
|
|
108
|
-
const key = typeof cmd.key ===
|
|
109
|
-
const modifiers =
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
const key = typeof cmd.key === "string" ? cmd.key : "";
|
|
110
|
+
const modifiers =
|
|
111
|
+
typeof cmd.modifiers === "object" && cmd.modifiers !== null
|
|
112
|
+
? (cmd.modifiers as Record<string, boolean>)
|
|
113
|
+
: {};
|
|
112
114
|
await driver.pressKey(key, modifiers);
|
|
113
|
-
recorder?.addCommand(
|
|
115
|
+
recorder?.addCommand("pressKey", {
|
|
116
|
+
key,
|
|
117
|
+
...(Object.keys(modifiers).length > 0 ? { modifiers } : {}),
|
|
118
|
+
});
|
|
114
119
|
if (recorder?.isRecording()) {
|
|
115
120
|
recorder.captureFrame(await driver.capture());
|
|
116
121
|
}
|
|
117
122
|
return { ok: true };
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
case
|
|
125
|
+
case "typeText": {
|
|
121
126
|
if (!driver?.isRunning()) {
|
|
122
|
-
return { ok: false, error:
|
|
127
|
+
return { ok: false, error: "Driver not running" };
|
|
123
128
|
}
|
|
124
|
-
const text = typeof cmd.text ===
|
|
125
|
-
const delay = typeof cmd.delay ===
|
|
129
|
+
const text = typeof cmd.text === "string" ? cmd.text : "";
|
|
130
|
+
const delay = typeof cmd.delay === "number" ? cmd.delay : 0;
|
|
126
131
|
await driver.typeText(text, delay);
|
|
127
|
-
recorder?.addCommand(
|
|
132
|
+
recorder?.addCommand("typeText", { text, ...(delay > 0 ? { delay } : {}) });
|
|
128
133
|
if (recorder?.isRecording()) {
|
|
129
134
|
recorder.captureFrame(await driver.capture());
|
|
130
135
|
}
|
|
131
136
|
return { ok: true };
|
|
132
137
|
}
|
|
133
138
|
|
|
134
|
-
case
|
|
139
|
+
case "pressTab": {
|
|
135
140
|
if (!driver?.isRunning()) {
|
|
136
|
-
return { ok: false, error:
|
|
141
|
+
return { ok: false, error: "Driver not running" };
|
|
137
142
|
}
|
|
138
143
|
await driver.pressTab();
|
|
139
|
-
recorder?.addCommand(
|
|
144
|
+
recorder?.addCommand("pressTab");
|
|
140
145
|
if (recorder?.isRecording()) {
|
|
141
146
|
recorder.captureFrame(await driver.capture());
|
|
142
147
|
}
|
|
143
148
|
return { ok: true };
|
|
144
149
|
}
|
|
145
150
|
|
|
146
|
-
case
|
|
151
|
+
case "pressEnter": {
|
|
147
152
|
if (!driver?.isRunning()) {
|
|
148
|
-
return { ok: false, error:
|
|
153
|
+
return { ok: false, error: "Driver not running" };
|
|
149
154
|
}
|
|
150
155
|
await driver.pressEnter();
|
|
151
|
-
recorder?.addCommand(
|
|
156
|
+
recorder?.addCommand("pressEnter");
|
|
152
157
|
if (recorder?.isRecording()) {
|
|
153
158
|
recorder.captureFrame(await driver.capture());
|
|
154
159
|
}
|
|
155
160
|
return { ok: true };
|
|
156
161
|
}
|
|
157
162
|
|
|
158
|
-
case
|
|
163
|
+
case "pressEscape": {
|
|
159
164
|
if (!driver?.isRunning()) {
|
|
160
|
-
return { ok: false, error:
|
|
165
|
+
return { ok: false, error: "Driver not running" };
|
|
161
166
|
}
|
|
162
167
|
await driver.pressEscape();
|
|
163
|
-
recorder?.addCommand(
|
|
168
|
+
recorder?.addCommand("pressEscape");
|
|
164
169
|
if (recorder?.isRecording()) {
|
|
165
170
|
recorder.captureFrame(await driver.capture());
|
|
166
171
|
}
|
|
167
172
|
return { ok: true };
|
|
168
173
|
}
|
|
169
174
|
|
|
170
|
-
case
|
|
175
|
+
case "pressArrow": {
|
|
171
176
|
if (!driver?.isRunning()) {
|
|
172
|
-
return { ok: false, error:
|
|
177
|
+
return { ok: false, error: "Driver not running" };
|
|
173
178
|
}
|
|
174
|
-
const direction = cmd.direction as
|
|
175
|
-
if (![
|
|
176
|
-
return { ok: false, error:
|
|
179
|
+
const direction = cmd.direction as "up" | "down" | "left" | "right";
|
|
180
|
+
if (!["up", "down", "left", "right"].includes(direction)) {
|
|
181
|
+
return { ok: false, error: "Invalid direction. Use: up, down, left, right" };
|
|
177
182
|
}
|
|
178
183
|
await driver.pressArrow(direction);
|
|
179
|
-
recorder?.addCommand(
|
|
184
|
+
recorder?.addCommand("pressArrow", { direction });
|
|
180
185
|
if (recorder?.isRecording()) {
|
|
181
186
|
recorder.captureFrame(await driver.capture());
|
|
182
187
|
}
|
|
183
188
|
return { ok: true };
|
|
184
189
|
}
|
|
185
190
|
|
|
186
|
-
case
|
|
191
|
+
case "capture": {
|
|
187
192
|
if (!driver?.isRunning()) {
|
|
188
|
-
return { ok: false, error:
|
|
193
|
+
return { ok: false, error: "Driver not running" };
|
|
189
194
|
}
|
|
190
|
-
const result =
|
|
191
|
-
? await driver.captureWithMeta()
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (typeof cmd.save === 'string') {
|
|
195
|
+
const result =
|
|
196
|
+
cmd.meta === true ? await driver.captureWithMeta() : { frame: await driver.capture() };
|
|
197
|
+
|
|
198
|
+
if (typeof cmd.save === "string") {
|
|
195
199
|
const savedPath = await saveFrameToFile(result.frame, cmd.save);
|
|
196
200
|
return { ok: true, ...result, savedTo: savedPath };
|
|
197
201
|
}
|
|
198
|
-
|
|
202
|
+
|
|
199
203
|
return { ok: true, ...result };
|
|
200
204
|
}
|
|
201
205
|
|
|
202
|
-
case
|
|
206
|
+
case "snapshot": {
|
|
203
207
|
if (!driver?.isRunning()) {
|
|
204
|
-
return { ok: false, error:
|
|
208
|
+
return { ok: false, error: "Driver not running" };
|
|
205
209
|
}
|
|
206
210
|
const result = await driver.captureWithMeta();
|
|
207
|
-
const dir = typeof cmd.dir ===
|
|
208
|
-
const name =
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
211
|
+
const dir = typeof cmd.dir === "string" ? cmd.dir : DEFAULT_SNAPSHOTS_DIR;
|
|
212
|
+
const name =
|
|
213
|
+
typeof cmd.name === "string"
|
|
214
|
+
? cmd.name
|
|
215
|
+
: `frame-${String(++frameCounter).padStart(4, "0")}`;
|
|
216
|
+
|
|
212
217
|
const framePath = path.join(dir, `${name}.txt`);
|
|
213
218
|
const metadataPath = path.join(dir, `${name}.json`);
|
|
214
|
-
|
|
219
|
+
|
|
215
220
|
const metadata = {
|
|
216
221
|
timestamp: new Date(result.timestamp).toISOString(),
|
|
217
222
|
width: result.width,
|
|
218
223
|
height: result.height,
|
|
219
224
|
name,
|
|
220
225
|
};
|
|
221
|
-
|
|
226
|
+
|
|
222
227
|
await ensureDir(dir);
|
|
223
228
|
await Promise.all([
|
|
224
|
-
fs.writeFile(framePath, result.frame,
|
|
225
|
-
fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2),
|
|
229
|
+
fs.writeFile(framePath, result.frame, "utf-8"),
|
|
230
|
+
fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf-8"),
|
|
226
231
|
]);
|
|
227
|
-
|
|
228
|
-
return {
|
|
229
|
-
ok: true,
|
|
230
|
-
savedTo: path.resolve(framePath),
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
ok: true,
|
|
235
|
+
savedTo: path.resolve(framePath),
|
|
231
236
|
metadataPath: path.resolve(metadataPath),
|
|
232
237
|
name,
|
|
233
238
|
metadata,
|
|
234
239
|
};
|
|
235
240
|
}
|
|
236
241
|
|
|
237
|
-
case
|
|
242
|
+
case "waitForText": {
|
|
238
243
|
if (!driver?.isRunning()) {
|
|
239
|
-
return { ok: false, error:
|
|
244
|
+
return { ok: false, error: "Driver not running" };
|
|
240
245
|
}
|
|
241
|
-
const text = typeof cmd.text ===
|
|
242
|
-
const timeout = typeof cmd.timeout ===
|
|
246
|
+
const text = typeof cmd.text === "string" ? cmd.text : "";
|
|
247
|
+
const timeout = typeof cmd.timeout === "number" ? cmd.timeout : 5000;
|
|
243
248
|
const found = await driver.waitForText(text, { timeout });
|
|
244
249
|
return { ok: true, found };
|
|
245
250
|
}
|
|
246
251
|
|
|
247
|
-
case
|
|
252
|
+
case "waitForStable": {
|
|
248
253
|
if (!driver?.isRunning()) {
|
|
249
|
-
return { ok: false, error:
|
|
254
|
+
return { ok: false, error: "Driver not running" };
|
|
250
255
|
}
|
|
251
|
-
const maxIterations = typeof cmd.maxIterations ===
|
|
252
|
-
const intervalMs = typeof cmd.intervalMs ===
|
|
256
|
+
const maxIterations = typeof cmd.maxIterations === "number" ? cmd.maxIterations : 10;
|
|
257
|
+
const intervalMs = typeof cmd.intervalMs === "number" ? cmd.intervalMs : 50;
|
|
253
258
|
await driver.waitForStable({ maxIterations, intervalMs });
|
|
254
|
-
recorder?.addCommand(
|
|
259
|
+
recorder?.addCommand("waitForStable", { maxIterations, intervalMs });
|
|
255
260
|
return { ok: true };
|
|
256
261
|
}
|
|
257
262
|
|
|
258
|
-
case
|
|
259
|
-
const ms = typeof cmd.ms ===
|
|
260
|
-
await new Promise(resolve => setTimeout(resolve, ms));
|
|
261
|
-
recorder?.addCommand(
|
|
263
|
+
case "sleep": {
|
|
264
|
+
const ms = typeof cmd.ms === "number" ? cmd.ms : 1000;
|
|
265
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
266
|
+
recorder?.addCommand("sleep", { ms });
|
|
262
267
|
return { ok: true, sleptMs: ms };
|
|
263
268
|
}
|
|
264
269
|
|
|
265
|
-
case
|
|
270
|
+
case "resize": {
|
|
266
271
|
if (!driver?.isRunning()) {
|
|
267
|
-
return { ok: false, error:
|
|
272
|
+
return { ok: false, error: "Driver not running" };
|
|
268
273
|
}
|
|
269
|
-
const cols = typeof cmd.cols ===
|
|
270
|
-
const rows = typeof cmd.rows ===
|
|
274
|
+
const cols = typeof cmd.cols === "number" ? cmd.cols : 100;
|
|
275
|
+
const rows = typeof cmd.rows === "number" ? cmd.rows : 30;
|
|
271
276
|
await driver.resize(cols, rows);
|
|
272
277
|
currentWidth = cols;
|
|
273
278
|
currentHeight = rows;
|
|
274
|
-
recorder?.addCommand(
|
|
279
|
+
recorder?.addCommand("resize", { cols, rows });
|
|
275
280
|
return { ok: true };
|
|
276
281
|
}
|
|
277
282
|
|
|
278
|
-
case
|
|
283
|
+
case "status": {
|
|
279
284
|
return {
|
|
280
285
|
ok: true,
|
|
281
286
|
running: driver?.isRunning() ?? false,
|
|
@@ -283,29 +288,30 @@ async function handleCommand(cmd: Command): Promise<Response> {
|
|
|
283
288
|
};
|
|
284
289
|
}
|
|
285
290
|
|
|
286
|
-
case
|
|
287
|
-
const file1 = typeof cmd.file1 ===
|
|
288
|
-
const file2 = typeof cmd.file2 ===
|
|
289
|
-
const frame1 = typeof cmd.frame1 ===
|
|
290
|
-
const frame2 = typeof cmd.frame2 ===
|
|
291
|
+
case "diff": {
|
|
292
|
+
const file1 = typeof cmd.file1 === "string" ? cmd.file1 : null;
|
|
293
|
+
const file2 = typeof cmd.file2 === "string" ? cmd.file2 : null;
|
|
294
|
+
const frame1 = typeof cmd.frame1 === "string" ? cmd.frame1 : null;
|
|
295
|
+
const frame2 = typeof cmd.frame2 === "string" ? cmd.frame2 : null;
|
|
291
296
|
const ignoreWhitespace = cmd.ignoreWhitespace === true;
|
|
292
|
-
|
|
297
|
+
|
|
293
298
|
let expected: string;
|
|
294
299
|
let actual: string;
|
|
295
|
-
|
|
300
|
+
|
|
296
301
|
if (file1 && file2) {
|
|
297
|
-
expected = await fs.readFile(file1,
|
|
298
|
-
actual = await fs.readFile(file2,
|
|
302
|
+
expected = await fs.readFile(file1, "utf-8");
|
|
303
|
+
actual = await fs.readFile(file2, "utf-8");
|
|
299
304
|
} else if (frame1 && frame2) {
|
|
300
305
|
expected = frame1;
|
|
301
306
|
actual = frame2;
|
|
302
307
|
} else {
|
|
303
|
-
return { ok: false, error:
|
|
308
|
+
return { ok: false, error: "Provide file1/file2 or frame1/frame2" };
|
|
304
309
|
}
|
|
305
|
-
|
|
310
|
+
|
|
306
311
|
const result = diffFrames(expected, actual, { ignoreWhitespace });
|
|
307
|
-
const visual =
|
|
308
|
-
|
|
312
|
+
const visual =
|
|
313
|
+
cmd.visual === true ? highlightDiff(expected, actual, { ignoreWhitespace }) : undefined;
|
|
314
|
+
|
|
309
315
|
return {
|
|
310
316
|
ok: true,
|
|
311
317
|
identical: result.identical,
|
|
@@ -319,21 +325,21 @@ async function handleCommand(cmd: Command): Promise<Response> {
|
|
|
319
325
|
};
|
|
320
326
|
}
|
|
321
327
|
|
|
322
|
-
case
|
|
328
|
+
case "assert": {
|
|
323
329
|
if (!driver?.isRunning()) {
|
|
324
|
-
return { ok: false, error:
|
|
330
|
+
return { ok: false, error: "Driver not running" };
|
|
325
331
|
}
|
|
326
|
-
const name = typeof cmd.name ===
|
|
332
|
+
const name = typeof cmd.name === "string" ? cmd.name : null;
|
|
327
333
|
if (!name) {
|
|
328
|
-
return { ok: false, error:
|
|
334
|
+
return { ok: false, error: "Name is required for assertion" };
|
|
329
335
|
}
|
|
330
|
-
|
|
336
|
+
|
|
331
337
|
const frame = await driver.capture();
|
|
332
338
|
const { width, height } = driver.getSize();
|
|
333
|
-
const goldenDir = typeof cmd.goldenDir ===
|
|
339
|
+
const goldenDir = typeof cmd.goldenDir === "string" ? cmd.goldenDir : "./golden";
|
|
334
340
|
const updateGolden = cmd.update === true;
|
|
335
341
|
const ignoreWhitespace = cmd.ignoreWhitespace === true;
|
|
336
|
-
|
|
342
|
+
|
|
337
343
|
const result = await assertSnapshot(name, frame, {
|
|
338
344
|
goldenDir,
|
|
339
345
|
updateGolden,
|
|
@@ -341,7 +347,7 @@ async function handleCommand(cmd: Command): Promise<Response> {
|
|
|
341
347
|
width,
|
|
342
348
|
height,
|
|
343
349
|
});
|
|
344
|
-
|
|
350
|
+
|
|
345
351
|
return {
|
|
346
352
|
ok: true,
|
|
347
353
|
passed: result.passed,
|
|
@@ -351,27 +357,29 @@ async function handleCommand(cmd: Command): Promise<Response> {
|
|
|
351
357
|
width,
|
|
352
358
|
height,
|
|
353
359
|
...(result.dimensionMismatch ? { dimensionMismatch: result.dimensionMismatch } : {}),
|
|
354
|
-
...(result.diff
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
360
|
+
...(result.diff
|
|
361
|
+
? {
|
|
362
|
+
changedLines: result.diff.changedLines,
|
|
363
|
+
totalLines: result.diff.totalLines,
|
|
364
|
+
changePercentage: result.diff.changePercentage,
|
|
365
|
+
}
|
|
366
|
+
: {}),
|
|
359
367
|
...(result.visual && cmd.visual === true ? { visual: result.visual } : {}),
|
|
360
368
|
};
|
|
361
369
|
}
|
|
362
370
|
|
|
363
|
-
case
|
|
364
|
-
const goldenDir = typeof cmd.goldenDir ===
|
|
371
|
+
case "listGolden": {
|
|
372
|
+
const goldenDir = typeof cmd.goldenDir === "string" ? cmd.goldenDir : "./golden";
|
|
365
373
|
const files = await listGoldenFiles(goldenDir);
|
|
366
374
|
return { ok: true, files, count: files.length };
|
|
367
375
|
}
|
|
368
376
|
|
|
369
|
-
case
|
|
370
|
-
const name = typeof cmd.name ===
|
|
377
|
+
case "getGolden": {
|
|
378
|
+
const name = typeof cmd.name === "string" ? cmd.name : null;
|
|
371
379
|
if (!name) {
|
|
372
|
-
return { ok: false, error:
|
|
380
|
+
return { ok: false, error: "Name is required" };
|
|
373
381
|
}
|
|
374
|
-
const goldenDir = typeof cmd.goldenDir ===
|
|
382
|
+
const goldenDir = typeof cmd.goldenDir === "string" ? cmd.goldenDir : "./golden";
|
|
375
383
|
const info = await getGoldenFile(name, goldenDir);
|
|
376
384
|
if (info === null) {
|
|
377
385
|
return { ok: false, error: `Golden file not found: ${name}` };
|
|
@@ -388,36 +396,36 @@ async function handleCommand(cmd: Command): Promise<Response> {
|
|
|
388
396
|
};
|
|
389
397
|
}
|
|
390
398
|
|
|
391
|
-
case
|
|
392
|
-
const name = typeof cmd.name ===
|
|
399
|
+
case "deleteGolden": {
|
|
400
|
+
const name = typeof cmd.name === "string" ? cmd.name : null;
|
|
393
401
|
if (!name) {
|
|
394
|
-
return { ok: false, error:
|
|
402
|
+
return { ok: false, error: "Name is required" };
|
|
395
403
|
}
|
|
396
|
-
const goldenDir = typeof cmd.goldenDir ===
|
|
404
|
+
const goldenDir = typeof cmd.goldenDir === "string" ? cmd.goldenDir : "./golden";
|
|
397
405
|
const deleted = await deleteGoldenFile(name, goldenDir);
|
|
398
406
|
return { ok: true, deleted, name };
|
|
399
407
|
}
|
|
400
408
|
|
|
401
|
-
case
|
|
409
|
+
case "startRecording": {
|
|
402
410
|
if (!driver?.isRunning()) {
|
|
403
|
-
return { ok: false, error:
|
|
411
|
+
return { ok: false, error: "Driver not running" };
|
|
404
412
|
}
|
|
405
413
|
if (recorder?.isRecording()) {
|
|
406
|
-
return { ok: false, error:
|
|
414
|
+
return { ok: false, error: "Already recording" };
|
|
407
415
|
}
|
|
408
|
-
const name = typeof cmd.name ===
|
|
416
|
+
const name = typeof cmd.name === "string" ? cmd.name : `recording-${Date.now()}`;
|
|
409
417
|
const captureFrames = cmd.captureFrames === true;
|
|
410
418
|
recorder = createRecorder(currentWidth, currentHeight, { name, captureFrames });
|
|
411
419
|
recorder.start();
|
|
412
420
|
return { ok: true, name, captureFrames };
|
|
413
421
|
}
|
|
414
422
|
|
|
415
|
-
case
|
|
423
|
+
case "stopRecording": {
|
|
416
424
|
if (!recorder?.isRecording()) {
|
|
417
|
-
return { ok: false, error:
|
|
425
|
+
return { ok: false, error: "Not recording" };
|
|
418
426
|
}
|
|
419
427
|
const recording = recorder.stop();
|
|
420
|
-
const recordingsDir = typeof cmd.dir ===
|
|
428
|
+
const recordingsDir = typeof cmd.dir === "string" ? cmd.dir : "./recordings";
|
|
421
429
|
const savedPath = await saveRecording(recording, recordingsDir);
|
|
422
430
|
recorder = null;
|
|
423
431
|
return {
|
|
@@ -428,15 +436,15 @@ async function handleCommand(cmd: Command): Promise<Response> {
|
|
|
428
436
|
};
|
|
429
437
|
}
|
|
430
438
|
|
|
431
|
-
case
|
|
439
|
+
case "cancelRecording": {
|
|
432
440
|
if (!recorder?.isRecording()) {
|
|
433
|
-
return { ok: false, error:
|
|
441
|
+
return { ok: false, error: "Not recording" };
|
|
434
442
|
}
|
|
435
443
|
recorder = null;
|
|
436
444
|
return { ok: true };
|
|
437
445
|
}
|
|
438
446
|
|
|
439
|
-
case
|
|
447
|
+
case "recordingStatus": {
|
|
440
448
|
return {
|
|
441
449
|
ok: true,
|
|
442
450
|
recording: recorder?.isRecording() ?? false,
|
|
@@ -444,26 +452,26 @@ async function handleCommand(cmd: Command): Promise<Response> {
|
|
|
444
452
|
};
|
|
445
453
|
}
|
|
446
454
|
|
|
447
|
-
case
|
|
455
|
+
case "replay": {
|
|
448
456
|
if (!driver?.isRunning()) {
|
|
449
|
-
return { ok: false, error:
|
|
457
|
+
return { ok: false, error: "Driver not running" };
|
|
450
458
|
}
|
|
451
|
-
const name = typeof cmd.name ===
|
|
459
|
+
const name = typeof cmd.name === "string" ? cmd.name : null;
|
|
452
460
|
let recording: Recording | null = null;
|
|
453
461
|
|
|
454
462
|
if (name) {
|
|
455
|
-
const recordingsDir = typeof cmd.dir ===
|
|
463
|
+
const recordingsDir = typeof cmd.dir === "string" ? cmd.dir : "./recordings";
|
|
456
464
|
recording = await loadRecording(name, recordingsDir);
|
|
457
465
|
if (!recording) {
|
|
458
466
|
return { ok: false, error: `Recording not found: ${name}` };
|
|
459
467
|
}
|
|
460
|
-
} else if (typeof cmd.recording ===
|
|
468
|
+
} else if (typeof cmd.recording === "object" && cmd.recording !== null) {
|
|
461
469
|
recording = cmd.recording as Recording;
|
|
462
470
|
} else {
|
|
463
|
-
return { ok: false, error:
|
|
471
|
+
return { ok: false, error: "Provide name or recording object" };
|
|
464
472
|
}
|
|
465
473
|
|
|
466
|
-
const speed = typeof cmd.speed ===
|
|
474
|
+
const speed = typeof cmd.speed === "number" ? cmd.speed : Infinity;
|
|
467
475
|
const result = await replayRecording(driver, recording, { speed });
|
|
468
476
|
|
|
469
477
|
return {
|
|
@@ -475,18 +483,18 @@ async function handleCommand(cmd: Command): Promise<Response> {
|
|
|
475
483
|
};
|
|
476
484
|
}
|
|
477
485
|
|
|
478
|
-
case
|
|
479
|
-
const recordingsDir = typeof cmd.dir ===
|
|
486
|
+
case "listRecordings": {
|
|
487
|
+
const recordingsDir = typeof cmd.dir === "string" ? cmd.dir : "./recordings";
|
|
480
488
|
const recordings = await listRecordings(recordingsDir);
|
|
481
489
|
return { ok: true, recordings, count: recordings.length };
|
|
482
490
|
}
|
|
483
491
|
|
|
484
|
-
case
|
|
485
|
-
const name = typeof cmd.name ===
|
|
492
|
+
case "getRecording": {
|
|
493
|
+
const name = typeof cmd.name === "string" ? cmd.name : null;
|
|
486
494
|
if (!name) {
|
|
487
|
-
return { ok: false, error:
|
|
495
|
+
return { ok: false, error: "Name is required" };
|
|
488
496
|
}
|
|
489
|
-
const recordingsDir = typeof cmd.dir ===
|
|
497
|
+
const recordingsDir = typeof cmd.dir === "string" ? cmd.dir : "./recordings";
|
|
490
498
|
const recording = await loadRecording(name, recordingsDir);
|
|
491
499
|
if (!recording) {
|
|
492
500
|
return { ok: false, error: `Recording not found: ${name}` };
|
|
@@ -494,26 +502,26 @@ async function handleCommand(cmd: Command): Promise<Response> {
|
|
|
494
502
|
return { ok: true, recording };
|
|
495
503
|
}
|
|
496
504
|
|
|
497
|
-
case
|
|
498
|
-
const name = typeof cmd.name ===
|
|
505
|
+
case "deleteRecording": {
|
|
506
|
+
const name = typeof cmd.name === "string" ? cmd.name : null;
|
|
499
507
|
if (!name) {
|
|
500
|
-
return { ok: false, error:
|
|
508
|
+
return { ok: false, error: "Name is required" };
|
|
501
509
|
}
|
|
502
|
-
const recordingsDir = typeof cmd.dir ===
|
|
510
|
+
const recordingsDir = typeof cmd.dir === "string" ? cmd.dir : "./recordings";
|
|
503
511
|
const deleted = await deleteRecording(name, recordingsDir);
|
|
504
512
|
return { ok: true, deleted, name };
|
|
505
513
|
}
|
|
506
514
|
|
|
507
|
-
case
|
|
515
|
+
case "startCoverage": {
|
|
508
516
|
if (!driver?.isRunning()) {
|
|
509
|
-
return { ok: false, error:
|
|
517
|
+
return { ok: false, error: "Driver not running" };
|
|
510
518
|
}
|
|
511
519
|
if (coverageTracker?.isTracking()) {
|
|
512
|
-
return { ok: false, error:
|
|
520
|
+
return { ok: false, error: "Already tracking coverage" };
|
|
513
521
|
}
|
|
514
522
|
const knownViews =
|
|
515
523
|
Array.isArray(cmd.knownViews) &&
|
|
516
|
-
cmd.knownViews.every((v): v is string => typeof v ===
|
|
524
|
+
cmd.knownViews.every((v): v is string => typeof v === "string")
|
|
517
525
|
? cmd.knownViews
|
|
518
526
|
: undefined;
|
|
519
527
|
coverageTracker = createCoverageTracker(knownViews);
|
|
@@ -521,9 +529,9 @@ async function handleCommand(cmd: Command): Promise<Response> {
|
|
|
521
529
|
return { ok: true };
|
|
522
530
|
}
|
|
523
531
|
|
|
524
|
-
case
|
|
532
|
+
case "stopCoverage": {
|
|
525
533
|
if (!coverageTracker?.isTracking()) {
|
|
526
|
-
return { ok: false, error:
|
|
534
|
+
return { ok: false, error: "Not tracking coverage" };
|
|
527
535
|
}
|
|
528
536
|
const report = coverageTracker.stop();
|
|
529
537
|
const visual = cmd.visual === true ? formatCoverageReport(report) : undefined;
|
|
@@ -535,12 +543,12 @@ async function handleCommand(cmd: Command): Promise<Response> {
|
|
|
535
543
|
};
|
|
536
544
|
}
|
|
537
545
|
|
|
538
|
-
case
|
|
546
|
+
case "recordViewFromFrame": {
|
|
539
547
|
if (!driver?.isRunning()) {
|
|
540
|
-
return { ok: false, error:
|
|
548
|
+
return { ok: false, error: "Driver not running" };
|
|
541
549
|
}
|
|
542
550
|
if (!coverageTracker?.isTracking()) {
|
|
543
|
-
return { ok: false, error:
|
|
551
|
+
return { ok: false, error: "Not tracking coverage" };
|
|
544
552
|
}
|
|
545
553
|
const frame = await driver.capture();
|
|
546
554
|
const view = detectViewFromFrame(frame);
|
|
@@ -550,9 +558,9 @@ async function handleCommand(cmd: Command): Promise<Response> {
|
|
|
550
558
|
return { ok: true, detectedView: view };
|
|
551
559
|
}
|
|
552
560
|
|
|
553
|
-
case
|
|
561
|
+
case "getCoverage": {
|
|
554
562
|
if (!coverageTracker) {
|
|
555
|
-
return { ok: false, error:
|
|
563
|
+
return { ok: false, error: "No coverage tracker" };
|
|
556
564
|
}
|
|
557
565
|
const report = coverageTracker.getReport();
|
|
558
566
|
const visual = cmd.visual === true ? formatCoverageReport(report) : undefined;
|
|
@@ -563,43 +571,43 @@ async function handleCommand(cmd: Command): Promise<Response> {
|
|
|
563
571
|
};
|
|
564
572
|
}
|
|
565
573
|
|
|
566
|
-
case
|
|
574
|
+
case "help": {
|
|
567
575
|
return {
|
|
568
576
|
ok: true,
|
|
569
577
|
commands: [
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
578
|
+
"launch - Start the app (options: width, height, debug; demo mode: demo, demoSeed, demoPreset)",
|
|
579
|
+
"close - Stop the app",
|
|
580
|
+
"sendKeys - Send key sequence (options: keys)",
|
|
581
|
+
"pressKey - Press single key (options: key, modifiers)",
|
|
582
|
+
"pressTab - Press Tab key",
|
|
583
|
+
"pressEnter - Press Enter key",
|
|
584
|
+
"pressEscape - Press Escape key",
|
|
585
|
+
"pressArrow - Press arrow key (options: direction)",
|
|
586
|
+
"typeText - Type text (options: text, delay)",
|
|
587
|
+
"capture - Get current frame (options: meta, save)",
|
|
588
|
+
"snapshot - Save frame to file (options: dir, name)",
|
|
589
|
+
"waitForText - Wait for text (options: text, timeout)",
|
|
590
|
+
"waitForStable - Wait for stable frame (options: maxIterations, intervalMs)",
|
|
591
|
+
"sleep - Pause for duration (options: ms)",
|
|
592
|
+
"resize - Resize terminal (options: cols, rows)",
|
|
593
|
+
"status - Get driver status",
|
|
594
|
+
"diff - Compare two frames (options: file1, file2 OR frame1, frame2, ignoreWhitespace, visual)",
|
|
595
|
+
"assert - Assert snapshot matches golden file (options: name, goldenDir, update, ignoreWhitespace, visual)",
|
|
596
|
+
"listGolden - List all golden files (options: goldenDir)",
|
|
597
|
+
"getGolden - Get content of golden file (options: name, goldenDir)",
|
|
598
|
+
"deleteGolden - Delete a golden file (options: name, goldenDir)",
|
|
599
|
+
"startRecording - Start recording commands (options: name, captureFrames)",
|
|
600
|
+
"stopRecording - Stop recording and save (options: dir)",
|
|
601
|
+
"cancelRecording - Cancel recording without saving",
|
|
602
|
+
"recordingStatus - Get current recording status",
|
|
603
|
+
"replay - Replay a recording (options: name, dir, speed)",
|
|
604
|
+
"listRecordings - List all recordings (options: dir)",
|
|
605
|
+
"getRecording - Get a recording (options: name, dir)",
|
|
606
|
+
"deleteRecording - Delete a recording (options: name, dir)",
|
|
607
|
+
"startCoverage - Start tracking view coverage (options: knownViews)",
|
|
608
|
+
"stopCoverage - Stop tracking and get report (options: visual)",
|
|
609
|
+
"recordViewFromFrame - Detect and record current view",
|
|
610
|
+
"getCoverage - Get current coverage report (options: visual)",
|
|
603
611
|
],
|
|
604
612
|
};
|
|
605
613
|
}
|
|
@@ -617,8 +625,8 @@ async function handleCommand(cmd: Command): Promise<Response> {
|
|
|
617
625
|
|
|
618
626
|
async function main() {
|
|
619
627
|
const args = process.argv.slice(2);
|
|
620
|
-
|
|
621
|
-
if (args.includes(
|
|
628
|
+
|
|
629
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
622
630
|
console.log(`
|
|
623
631
|
Tokentop Driver CLI
|
|
624
632
|
|
|
@@ -667,7 +675,7 @@ Example:
|
|
|
667
675
|
async function processQueue(): Promise<void> {
|
|
668
676
|
if (processing) return;
|
|
669
677
|
processing = true;
|
|
670
|
-
|
|
678
|
+
|
|
671
679
|
while (commandQueue.length > 0) {
|
|
672
680
|
const line = commandQueue.shift()!;
|
|
673
681
|
try {
|
|
@@ -675,15 +683,17 @@ Example:
|
|
|
675
683
|
const response = await handleCommand(cmd);
|
|
676
684
|
console.log(JSON.stringify(response));
|
|
677
685
|
} catch (err) {
|
|
678
|
-
console.log(
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
686
|
+
console.log(
|
|
687
|
+
JSON.stringify({
|
|
688
|
+
ok: false,
|
|
689
|
+
error: `Invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
690
|
+
}),
|
|
691
|
+
);
|
|
682
692
|
}
|
|
683
693
|
}
|
|
684
|
-
|
|
694
|
+
|
|
685
695
|
processing = false;
|
|
686
|
-
|
|
696
|
+
|
|
687
697
|
if (closed && commandQueue.length === 0) {
|
|
688
698
|
if (driver?.isRunning()) {
|
|
689
699
|
await driver.close();
|
|
@@ -692,14 +702,14 @@ Example:
|
|
|
692
702
|
}
|
|
693
703
|
}
|
|
694
704
|
|
|
695
|
-
rl.on(
|
|
705
|
+
rl.on("line", (line) => {
|
|
696
706
|
const trimmed = line.trim();
|
|
697
707
|
if (!trimmed) return;
|
|
698
708
|
commandQueue.push(trimmed);
|
|
699
709
|
processQueue();
|
|
700
710
|
});
|
|
701
711
|
|
|
702
|
-
rl.on(
|
|
712
|
+
rl.on("close", () => {
|
|
703
713
|
closed = true;
|
|
704
714
|
if (!processing && commandQueue.length === 0) {
|
|
705
715
|
if (driver?.isRunning()) {
|
|
@@ -712,6 +722,6 @@ Example:
|
|
|
712
722
|
}
|
|
713
723
|
|
|
714
724
|
main().catch((err) => {
|
|
715
|
-
console.error(
|
|
725
|
+
console.error("Driver CLI failed:", err);
|
|
716
726
|
process.exit(1);
|
|
717
727
|
});
|