tui-devtools 0.1.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 +98 -0
- package/dist/bin/tui-devtools.js +409 -0
- package/dist/bin/tui-devtools.js.map +1 -0
- package/dist/chunk-UV6LYAWB.js +925 -0
- package/dist/chunk-UV6LYAWB.js.map +1 -0
- package/dist/daemon-27VEN2X5.js +18 -0
- package/dist/daemon-27VEN2X5.js.map +1 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# tui-devtools
|
|
2
|
+
|
|
3
|
+
DevTools for Ink TUI apps — component tree, state inspection, console capture.
|
|
4
|
+
|
|
5
|
+
Built for AI agents that need to debug terminal UI applications programmatically.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
Tools like `agent-tui` can automate TUI apps (press keys, take screenshots), but they can only see the **screen output**. When something goes wrong, there's no way to inspect component state, view console errors, or understand the component hierarchy.
|
|
10
|
+
|
|
11
|
+
`tui-devtools` bridges this gap for **Ink (React)** apps by connecting to React DevTools protocol.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Install
|
|
17
|
+
npm install -g tui-devtools
|
|
18
|
+
|
|
19
|
+
# 1. Start the DevTools server
|
|
20
|
+
tui-devtools start
|
|
21
|
+
|
|
22
|
+
# 2. Run your Ink app with DEV=true
|
|
23
|
+
DEV=true npx my-ink-app
|
|
24
|
+
|
|
25
|
+
# 3. Inspect
|
|
26
|
+
tui-devtools tree # Component hierarchy
|
|
27
|
+
tui-devtools inspect CommandMode # Props & state of a component
|
|
28
|
+
tui-devtools logs # Console output
|
|
29
|
+
tui-devtools logs --level error # Only errors
|
|
30
|
+
|
|
31
|
+
# 4. Cleanup
|
|
32
|
+
tui-devtools stop
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Commands
|
|
36
|
+
|
|
37
|
+
| Command | Description |
|
|
38
|
+
|---------|-------------|
|
|
39
|
+
| `start` | Start DevTools server daemon |
|
|
40
|
+
| `stop` | Stop daemon |
|
|
41
|
+
| `status` | Show connection status |
|
|
42
|
+
| `tree` | Print component tree |
|
|
43
|
+
| `inspect <name>` | Show props/state/hooks for a component |
|
|
44
|
+
| `find <name>` | Find components by name |
|
|
45
|
+
| `logs` | Show captured console logs |
|
|
46
|
+
|
|
47
|
+
## With agent-tui (AI Agent Workflow)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Start devtools first
|
|
51
|
+
tui-devtools -s myapp start
|
|
52
|
+
|
|
53
|
+
# Start the TUI app
|
|
54
|
+
agent-tui run -s myapp "DEV=true npx my-ink-app"
|
|
55
|
+
|
|
56
|
+
# Visual: what's on screen
|
|
57
|
+
agent-tui -s myapp screenshot
|
|
58
|
+
|
|
59
|
+
# Structural: what's in React
|
|
60
|
+
tui-devtools -s myapp tree
|
|
61
|
+
|
|
62
|
+
# Debug: what went wrong
|
|
63
|
+
tui-devtools -s myapp logs --level error
|
|
64
|
+
tui-devtools -s myapp inspect MyComponent --json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## How It Works
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
Ink App (DEV=true) tui-devtools daemon
|
|
71
|
+
───────────────── ──────────────────
|
|
72
|
+
react-devtools-core WebSocket server (:8097)
|
|
73
|
+
connectToDevTools() ──WebSocket──► Parse fiber tree operations
|
|
74
|
+
Store component tree + logs
|
|
75
|
+
│
|
|
76
|
+
Unix socket IPC
|
|
77
|
+
│
|
|
78
|
+
CLI commands ◄── AI agent
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Options
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
-s, --session <name> Session name (default: "default")
|
|
85
|
+
-p, --port <port> DevTools port (default: 8097)
|
|
86
|
+
--json JSON output for all commands
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Requirements
|
|
90
|
+
|
|
91
|
+
- Node.js >= 18
|
|
92
|
+
- Target app must use **Ink** (React-based TUI framework)
|
|
93
|
+
- `react-devtools-core` must be installed in the target app
|
|
94
|
+
- Run the app with `DEV=true` environment variable
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
MIT
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getLogPath,
|
|
4
|
+
getPidPath,
|
|
5
|
+
getSocketPath,
|
|
6
|
+
isDaemonRunning,
|
|
7
|
+
sendIpcRequest
|
|
8
|
+
} from "../chunk-UV6LYAWB.js";
|
|
9
|
+
|
|
10
|
+
// bin/tui-devtools.ts
|
|
11
|
+
import { program } from "commander";
|
|
12
|
+
import { fork } from "child_process";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
import path from "path";
|
|
15
|
+
import fs from "fs";
|
|
16
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
var __dirname = path.dirname(__filename);
|
|
18
|
+
var DEFAULT_PORT = 8097;
|
|
19
|
+
var DEFAULT_SESSION = "default";
|
|
20
|
+
program.name("tui-devtools").description("DevTools for Ink TUI apps \u2014 component tree, state inspection, console capture").version("0.1.0").option("-s, --session <name>", "Session name for daemon isolation", DEFAULT_SESSION).option("-p, --port <port>", "DevTools server port", String(DEFAULT_PORT)).option("--json", "JSON output");
|
|
21
|
+
program.command("start").description("Start the DevTools server daemon").action(async () => {
|
|
22
|
+
const opts = program.opts();
|
|
23
|
+
const session = opts.session;
|
|
24
|
+
const port = parseInt(opts.port, 10);
|
|
25
|
+
if (isDaemonRunning(session)) {
|
|
26
|
+
console.log(`Daemon already running (session: ${session})`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const child = fork(__filename, ["__daemon__", session, String(port)], {
|
|
30
|
+
detached: true,
|
|
31
|
+
stdio: "ignore"
|
|
32
|
+
});
|
|
33
|
+
child.unref();
|
|
34
|
+
const socketPath = getSocketPath(session);
|
|
35
|
+
for (let i = 0; i < 20; i++) {
|
|
36
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
37
|
+
if (fs.existsSync(socketPath)) {
|
|
38
|
+
console.log(`DevTools server started (session: ${session}, port: ${port})`);
|
|
39
|
+
console.log(` Logs: ${getLogPath(session)}`);
|
|
40
|
+
console.log(`
|
|
41
|
+
Start your Ink app with: DEV=true your-app`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
console.error("Failed to start daemon \u2014 check logs:", getLogPath(session));
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|
|
48
|
+
program.command("stop").description("Stop the daemon").action(async () => {
|
|
49
|
+
const session = program.opts().session;
|
|
50
|
+
const pidPath = getPidPath(session);
|
|
51
|
+
try {
|
|
52
|
+
const pid = parseInt(fs.readFileSync(pidPath, "utf-8").trim(), 10);
|
|
53
|
+
process.kill(pid, "SIGTERM");
|
|
54
|
+
console.log(`Daemon stopped (session: ${session})`);
|
|
55
|
+
} catch {
|
|
56
|
+
console.log(`No daemon running (session: ${session})`);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
program.command("status").description("Show connection status").action(async () => {
|
|
60
|
+
const opts = program.opts();
|
|
61
|
+
const session = opts.session;
|
|
62
|
+
const json = opts.json;
|
|
63
|
+
if (!isDaemonRunning(session)) {
|
|
64
|
+
if (json) {
|
|
65
|
+
console.log(JSON.stringify({ running: false }));
|
|
66
|
+
} else {
|
|
67
|
+
console.log(`Daemon not running (session: ${session})`);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const res = await sendIpcRequest(session, { command: "status" });
|
|
73
|
+
if (json) {
|
|
74
|
+
console.log(JSON.stringify(res.data));
|
|
75
|
+
} else {
|
|
76
|
+
const d = res.data;
|
|
77
|
+
console.log(`Session: ${session}`);
|
|
78
|
+
console.log(`Connected: ${d.connected ? "yes" : "no"}`);
|
|
79
|
+
console.log(`Components: ${d.nodeCount}`);
|
|
80
|
+
console.log(`Roots: ${d.rootCount}`);
|
|
81
|
+
console.log(`Logs: ${d.logCount}`);
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error(`Error: ${e.message}`);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
program.command("tree").description("Print component tree").option("-d, --depth <n>", "Max tree depth", "20").action(async (cmdOpts) => {
|
|
88
|
+
const opts = program.opts();
|
|
89
|
+
const session = opts.session;
|
|
90
|
+
const json = opts.json;
|
|
91
|
+
const depth = parseInt(cmdOpts.depth, 10);
|
|
92
|
+
if (!isDaemonRunning(session)) {
|
|
93
|
+
console.error("Daemon not running. Start with: tui-devtools start");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const res = await sendIpcRequest(session, {
|
|
98
|
+
command: "tree",
|
|
99
|
+
args: { depth, json }
|
|
100
|
+
});
|
|
101
|
+
if (res.ok) {
|
|
102
|
+
if (json) {
|
|
103
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
104
|
+
} else {
|
|
105
|
+
console.log(res.data || "(empty tree \u2014 is the app connected?)");
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
console.error(`Error: ${res.error}`);
|
|
109
|
+
}
|
|
110
|
+
} catch (e) {
|
|
111
|
+
console.error(`Error: ${e.message}`);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
program.command("inspect [component]").description("Inspect a component's props, state, and hooks").option("--id <id>", "Inspect by fiber ID").action(async (component, cmdOpts) => {
|
|
115
|
+
const opts = program.opts();
|
|
116
|
+
const session = opts.session;
|
|
117
|
+
const json = opts.json;
|
|
118
|
+
const id = cmdOpts.id ? parseInt(cmdOpts.id, 10) : void 0;
|
|
119
|
+
if (!component && id == null) {
|
|
120
|
+
console.error("Usage: tui-devtools inspect <component-name> or --id <id>");
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
if (!isDaemonRunning(session)) {
|
|
124
|
+
console.error("Daemon not running. Start with: tui-devtools start");
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const res = await sendIpcRequest(session, {
|
|
129
|
+
command: "inspect",
|
|
130
|
+
args: { name: component, id }
|
|
131
|
+
});
|
|
132
|
+
if (res.ok) {
|
|
133
|
+
if (json) {
|
|
134
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
135
|
+
} else {
|
|
136
|
+
const d = res.data;
|
|
137
|
+
console.log(`Component: ${d.displayName} [${d.id}]`);
|
|
138
|
+
console.log(`Type: ${d.type}`);
|
|
139
|
+
if (d.key) console.log(`Key: ${d.key}`);
|
|
140
|
+
console.log(`Parent: ${d.parentId}`);
|
|
141
|
+
console.log(`Children: ${d.childIds?.length ?? 0}`);
|
|
142
|
+
if (d.props) {
|
|
143
|
+
console.log(`
|
|
144
|
+
Props:`);
|
|
145
|
+
console.log(JSON.stringify(d.props, null, 2));
|
|
146
|
+
}
|
|
147
|
+
if (d.state) {
|
|
148
|
+
console.log(`
|
|
149
|
+
State:`);
|
|
150
|
+
console.log(JSON.stringify(d.state, null, 2));
|
|
151
|
+
}
|
|
152
|
+
if (d.inspectData) {
|
|
153
|
+
console.log(`
|
|
154
|
+
Inspect Data:`);
|
|
155
|
+
console.log(JSON.stringify(d.inspectData, null, 2));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
console.error(`Error: ${res.error}`);
|
|
160
|
+
}
|
|
161
|
+
} catch (e) {
|
|
162
|
+
console.error(`Error: ${e.message}`);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
program.command("find <name>").description("Find components by name").action(async (name) => {
|
|
166
|
+
const opts = program.opts();
|
|
167
|
+
const session = opts.session;
|
|
168
|
+
const json = opts.json;
|
|
169
|
+
if (!isDaemonRunning(session)) {
|
|
170
|
+
console.error("Daemon not running. Start with: tui-devtools start");
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const res = await sendIpcRequest(session, {
|
|
175
|
+
command: "find",
|
|
176
|
+
args: { name }
|
|
177
|
+
});
|
|
178
|
+
if (res.ok) {
|
|
179
|
+
const nodes = res.data;
|
|
180
|
+
if (json) {
|
|
181
|
+
console.log(JSON.stringify(nodes, null, 2));
|
|
182
|
+
} else if (nodes.length === 0) {
|
|
183
|
+
console.log(`No components matching "${name}"`);
|
|
184
|
+
} else {
|
|
185
|
+
for (const n of nodes) {
|
|
186
|
+
console.log(` [${n.id}] ${n.displayName} (${n.type})${n.key ? ` key="${n.key}"` : ""}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
console.error(`Error: ${res.error}`);
|
|
191
|
+
}
|
|
192
|
+
} catch (e) {
|
|
193
|
+
console.error(`Error: ${e.message}`);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
program.command("logs").description("Show captured console logs").option("-l, --level <level>", "Filter by level (log|warn|error|info|debug)").option("-t, --tail <n>", "Show last N entries", "50").action(async (cmdOpts) => {
|
|
197
|
+
const opts = program.opts();
|
|
198
|
+
const session = opts.session;
|
|
199
|
+
const json = opts.json;
|
|
200
|
+
if (!isDaemonRunning(session)) {
|
|
201
|
+
console.error("Daemon not running. Start with: tui-devtools start");
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
const res = await sendIpcRequest(session, {
|
|
206
|
+
command: "logs",
|
|
207
|
+
args: {
|
|
208
|
+
level: cmdOpts.level,
|
|
209
|
+
tail: parseInt(cmdOpts.tail, 10)
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
if (res.ok) {
|
|
213
|
+
const logs = res.data;
|
|
214
|
+
if (json) {
|
|
215
|
+
console.log(JSON.stringify(logs, null, 2));
|
|
216
|
+
} else if (logs.length === 0) {
|
|
217
|
+
console.log("(no logs captured)");
|
|
218
|
+
} else {
|
|
219
|
+
for (const entry of logs) {
|
|
220
|
+
const ts = new Date(entry.timestamp).toISOString().slice(11, 19);
|
|
221
|
+
const level = entry.level.toUpperCase().padEnd(5);
|
|
222
|
+
const msg = entry.args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
223
|
+
console.log(`${ts} [${level}] ${msg}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
console.error(`Error: ${res.error}`);
|
|
228
|
+
}
|
|
229
|
+
} catch (e) {
|
|
230
|
+
console.error(`Error: ${e.message}`);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
function ensureDaemon(session) {
|
|
234
|
+
if (!isDaemonRunning(session)) {
|
|
235
|
+
console.error("Daemon not running. Start with: tui-devtools start");
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
program.command("run <command...>").description("Run a TUI application in a virtual terminal").option("-d, --cwd <dir>", "Working directory").option("--cols <n>", "Terminal columns", "120").option("--rows <n>", "Terminal rows", "40").option("--sid <id>", "PTY session ID", "default").action(async (commandParts, cmdOpts) => {
|
|
240
|
+
const opts = program.opts();
|
|
241
|
+
const session = opts.session;
|
|
242
|
+
const json = opts.json;
|
|
243
|
+
const command = commandParts.join(" ");
|
|
244
|
+
ensureDaemon(session);
|
|
245
|
+
try {
|
|
246
|
+
const res = await sendIpcRequest(session, {
|
|
247
|
+
command: "run",
|
|
248
|
+
args: {
|
|
249
|
+
command,
|
|
250
|
+
sessionId: cmdOpts.sid,
|
|
251
|
+
cwd: cmdOpts.cwd,
|
|
252
|
+
cols: parseInt(cmdOpts.cols, 10),
|
|
253
|
+
rows: parseInt(cmdOpts.rows, 10)
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
if (res.ok) {
|
|
257
|
+
const info = res.data;
|
|
258
|
+
if (json) {
|
|
259
|
+
console.log(JSON.stringify(info));
|
|
260
|
+
} else {
|
|
261
|
+
console.log(`Session started: ${info.id}`);
|
|
262
|
+
console.log(` PID: ${info.pid}`);
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
console.error(`Error: ${res.error}`);
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
} catch (e) {
|
|
269
|
+
console.error(`Error: ${e.message}`);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
program.command("screenshot").description("Capture current terminal screen").option("--sid <id>", "PTY session ID", "default").option("--strip-ansi", "Strip ANSI color codes").action(async (cmdOpts) => {
|
|
274
|
+
const opts = program.opts();
|
|
275
|
+
const session = opts.session;
|
|
276
|
+
const json = opts.json;
|
|
277
|
+
ensureDaemon(session);
|
|
278
|
+
try {
|
|
279
|
+
const res = await sendIpcRequest(session, {
|
|
280
|
+
command: "screenshot",
|
|
281
|
+
args: { sessionId: cmdOpts.sid, stripAnsi: cmdOpts.stripAnsi }
|
|
282
|
+
});
|
|
283
|
+
if (res.ok) {
|
|
284
|
+
const data = res.data;
|
|
285
|
+
if (json) {
|
|
286
|
+
console.log(JSON.stringify(data));
|
|
287
|
+
} else {
|
|
288
|
+
console.log(`Screenshot:${data.running ? "" : " [stopped]"}`);
|
|
289
|
+
console.log(data.screenshot);
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
console.error(`Error: ${res.error}`);
|
|
293
|
+
}
|
|
294
|
+
} catch (e) {
|
|
295
|
+
console.error(`Error: ${e.message}`);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
program.command("press <keys...>").description("Send key press(es) to the terminal").option("--sid <id>", "PTY session ID", "default").action(async (keys, cmdOpts) => {
|
|
299
|
+
const opts = program.opts();
|
|
300
|
+
const session = opts.session;
|
|
301
|
+
ensureDaemon(session);
|
|
302
|
+
try {
|
|
303
|
+
const res = await sendIpcRequest(session, {
|
|
304
|
+
command: "press",
|
|
305
|
+
args: { sessionId: cmdOpts.sid, keys }
|
|
306
|
+
});
|
|
307
|
+
if (res.ok) {
|
|
308
|
+
console.log("\u2713 Key pressed");
|
|
309
|
+
} else {
|
|
310
|
+
console.error(`Error: ${res.error}`);
|
|
311
|
+
}
|
|
312
|
+
} catch (e) {
|
|
313
|
+
console.error(`Error: ${e.message}`);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
program.command("type <text>").description("Type text into the terminal").option("--sid <id>", "PTY session ID", "default").action(async (text, cmdOpts) => {
|
|
317
|
+
const opts = program.opts();
|
|
318
|
+
const session = opts.session;
|
|
319
|
+
ensureDaemon(session);
|
|
320
|
+
try {
|
|
321
|
+
const res = await sendIpcRequest(session, {
|
|
322
|
+
command: "type",
|
|
323
|
+
args: { sessionId: cmdOpts.sid, text }
|
|
324
|
+
});
|
|
325
|
+
if (res.ok) {
|
|
326
|
+
console.log("\u2713 Text typed");
|
|
327
|
+
} else {
|
|
328
|
+
console.error(`Error: ${res.error}`);
|
|
329
|
+
}
|
|
330
|
+
} catch (e) {
|
|
331
|
+
console.error(`Error: ${e.message}`);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
program.command("wait <text>").description("Wait for text to appear on screen").option("--sid <id>", "PTY session ID", "default").option("--timeout <ms>", "Timeout in milliseconds", "30000").action(async (text, cmdOpts) => {
|
|
335
|
+
const opts = program.opts();
|
|
336
|
+
const session = opts.session;
|
|
337
|
+
const json = opts.json;
|
|
338
|
+
ensureDaemon(session);
|
|
339
|
+
try {
|
|
340
|
+
const res = await sendIpcRequest(session, {
|
|
341
|
+
command: "wait",
|
|
342
|
+
args: { sessionId: cmdOpts.sid, text, timeout: parseInt(cmdOpts.timeout, 10) }
|
|
343
|
+
});
|
|
344
|
+
if (res.ok) {
|
|
345
|
+
const data = res.data;
|
|
346
|
+
if (json) {
|
|
347
|
+
console.log(JSON.stringify(data));
|
|
348
|
+
} else {
|
|
349
|
+
console.log(data.found ? `\u2713 Found: "${text}"` : `\u2717 Timeout waiting for: "${text}"`);
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
console.error(`Error: ${res.error}`);
|
|
353
|
+
}
|
|
354
|
+
} catch (e) {
|
|
355
|
+
console.error(`Error: ${e.message}`);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
program.command("kill-session").description("Kill a PTY session").option("--sid <id>", "PTY session ID", "default").action(async (cmdOpts) => {
|
|
359
|
+
const opts = program.opts();
|
|
360
|
+
const session = opts.session;
|
|
361
|
+
ensureDaemon(session);
|
|
362
|
+
try {
|
|
363
|
+
const res = await sendIpcRequest(session, {
|
|
364
|
+
command: "kill-session",
|
|
365
|
+
args: { sessionId: cmdOpts.sid }
|
|
366
|
+
});
|
|
367
|
+
if (res.ok) {
|
|
368
|
+
console.log(`Session killed: ${cmdOpts.sid}`);
|
|
369
|
+
} else {
|
|
370
|
+
console.error(`Error: ${res.error}`);
|
|
371
|
+
}
|
|
372
|
+
} catch (e) {
|
|
373
|
+
console.error(`Error: ${e.message}`);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
program.command("sessions").description("List PTY sessions").action(async () => {
|
|
377
|
+
const opts = program.opts();
|
|
378
|
+
const session = opts.session;
|
|
379
|
+
const json = opts.json;
|
|
380
|
+
ensureDaemon(session);
|
|
381
|
+
try {
|
|
382
|
+
const res = await sendIpcRequest(session, { command: "sessions" });
|
|
383
|
+
if (res.ok) {
|
|
384
|
+
const sessions = res.data;
|
|
385
|
+
if (json) {
|
|
386
|
+
console.log(JSON.stringify(sessions, null, 2));
|
|
387
|
+
} else if (sessions.length === 0) {
|
|
388
|
+
console.log("No active PTY sessions");
|
|
389
|
+
} else {
|
|
390
|
+
for (const s of sessions) {
|
|
391
|
+
console.log(` ${s.id} \u2014 ${s.command} [${s.running ? "running" : "stopped"}] ${s.cols}x${s.rows} pid:${s.pid}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
console.error(`Error: ${res.error}`);
|
|
396
|
+
}
|
|
397
|
+
} catch (e) {
|
|
398
|
+
console.error(`Error: ${e.message}`);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
if (process.argv[2] === "__daemon__") {
|
|
402
|
+
const session = process.argv[3] ?? DEFAULT_SESSION;
|
|
403
|
+
const port = parseInt(process.argv[4] ?? String(DEFAULT_PORT), 10);
|
|
404
|
+
const { startDaemon } = await import("../daemon-27VEN2X5.js");
|
|
405
|
+
await startDaemon(session, port);
|
|
406
|
+
} else {
|
|
407
|
+
program.parse();
|
|
408
|
+
}
|
|
409
|
+
//# sourceMappingURL=tui-devtools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../bin/tui-devtools.ts"],"sourcesContent":["/**\n * tui-devtools CLI — DevTools for Ink TUI apps\n *\n * Usage:\n * tui-devtools start Start DevTools server daemon\n * tui-devtools status Show connection status\n * tui-devtools tree Print component tree\n * tui-devtools inspect <name> Inspect a component's props/state\n * tui-devtools logs Show captured console logs\n * tui-devtools stop Stop daemon\n */\n\nimport { program } from 'commander';\nimport { fork } from 'node:child_process';\nimport { fileURLToPath } from 'node:url';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport {\n sendIpcRequest,\n isDaemonRunning,\n getPidPath,\n getLogPath,\n getSocketPath,\n} from '../src/daemon.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst DEFAULT_PORT = 8097;\nconst DEFAULT_SESSION = 'default';\n\nprogram\n .name('tui-devtools')\n .description('DevTools for Ink TUI apps — component tree, state inspection, console capture')\n .version('0.1.0')\n .option('-s, --session <name>', 'Session name for daemon isolation', DEFAULT_SESSION)\n .option('-p, --port <port>', 'DevTools server port', String(DEFAULT_PORT))\n .option('--json', 'JSON output');\n\n// ─── start ───\nprogram\n .command('start')\n .description('Start the DevTools server daemon')\n .action(async () => {\n const opts = program.opts();\n const session = opts.session as string;\n const port = parseInt(opts.port as string, 10);\n\n if (isDaemonRunning(session)) {\n console.log(`Daemon already running (session: ${session})`);\n return;\n }\n\n // Fork daemon process\n const child = fork(__filename, ['__daemon__', session, String(port)], {\n detached: true,\n stdio: 'ignore',\n });\n child.unref();\n\n // Wait for socket to appear\n const socketPath = getSocketPath(session);\n for (let i = 0; i < 20; i++) {\n await new Promise(r => setTimeout(r, 200));\n if (fs.existsSync(socketPath)) {\n console.log(`DevTools server started (session: ${session}, port: ${port})`);\n console.log(` Logs: ${getLogPath(session)}`);\n console.log(`\\nStart your Ink app with: DEV=true your-app`);\n return;\n }\n }\n console.error('Failed to start daemon — check logs:', getLogPath(session));\n process.exit(1);\n });\n\n// ─── stop ───\nprogram\n .command('stop')\n .description('Stop the daemon')\n .action(async () => {\n const session = program.opts().session as string;\n const pidPath = getPidPath(session);\n\n try {\n const pid = parseInt(fs.readFileSync(pidPath, 'utf-8').trim(), 10);\n process.kill(pid, 'SIGTERM');\n console.log(`Daemon stopped (session: ${session})`);\n } catch {\n console.log(`No daemon running (session: ${session})`);\n }\n });\n\n// ─── status ───\nprogram\n .command('status')\n .description('Show connection status')\n .action(async () => {\n const opts = program.opts();\n const session = opts.session as string;\n const json = opts.json as boolean;\n\n if (!isDaemonRunning(session)) {\n if (json) {\n console.log(JSON.stringify({ running: false }));\n } else {\n console.log(`Daemon not running (session: ${session})`);\n }\n return;\n }\n\n try {\n const res = await sendIpcRequest(session, { command: 'status' });\n if (json) {\n console.log(JSON.stringify(res.data));\n } else {\n const d = res.data as Record<string, unknown>;\n console.log(`Session: ${session}`);\n console.log(`Connected: ${d.connected ? 'yes' : 'no'}`);\n console.log(`Components: ${d.nodeCount}`);\n console.log(`Roots: ${d.rootCount}`);\n console.log(`Logs: ${d.logCount}`);\n }\n } catch (e) {\n console.error(`Error: ${(e as Error).message}`);\n }\n });\n\n// ─── tree ───\nprogram\n .command('tree')\n .description('Print component tree')\n .option('-d, --depth <n>', 'Max tree depth', '20')\n .action(async (cmdOpts) => {\n const opts = program.opts();\n const session = opts.session as string;\n const json = opts.json as boolean;\n const depth = parseInt(cmdOpts.depth, 10);\n\n if (!isDaemonRunning(session)) {\n console.error('Daemon not running. Start with: tui-devtools start');\n process.exit(1);\n }\n\n try {\n const res = await sendIpcRequest(session, {\n command: 'tree',\n args: { depth, json },\n });\n if (res.ok) {\n if (json) {\n console.log(JSON.stringify(res.data, null, 2));\n } else {\n console.log(res.data || '(empty tree — is the app connected?)');\n }\n } else {\n console.error(`Error: ${res.error}`);\n }\n } catch (e) {\n console.error(`Error: ${(e as Error).message}`);\n }\n });\n\n// ─── inspect ───\nprogram\n .command('inspect [component]')\n .description('Inspect a component\\'s props, state, and hooks')\n .option('--id <id>', 'Inspect by fiber ID')\n .action(async (component, cmdOpts) => {\n const opts = program.opts();\n const session = opts.session as string;\n const json = opts.json as boolean;\n const id = cmdOpts.id ? parseInt(cmdOpts.id, 10) : undefined;\n\n if (!component && id == null) {\n console.error('Usage: tui-devtools inspect <component-name> or --id <id>');\n process.exit(1);\n }\n\n if (!isDaemonRunning(session)) {\n console.error('Daemon not running. Start with: tui-devtools start');\n process.exit(1);\n }\n\n try {\n const res = await sendIpcRequest(session, {\n command: 'inspect',\n args: { name: component, id },\n });\n if (res.ok) {\n if (json) {\n console.log(JSON.stringify(res.data, null, 2));\n } else {\n const d = res.data as Record<string, unknown>;\n console.log(`Component: ${d.displayName} [${d.id}]`);\n console.log(`Type: ${d.type}`);\n if (d.key) console.log(`Key: ${d.key}`);\n console.log(`Parent: ${d.parentId}`);\n console.log(`Children: ${(d.childIds as number[])?.length ?? 0}`);\n if (d.props) {\n console.log(`\\nProps:`);\n console.log(JSON.stringify(d.props, null, 2));\n }\n if (d.state) {\n console.log(`\\nState:`);\n console.log(JSON.stringify(d.state, null, 2));\n }\n if (d.inspectData) {\n console.log(`\\nInspect Data:`);\n console.log(JSON.stringify(d.inspectData, null, 2));\n }\n }\n } else {\n console.error(`Error: ${res.error}`);\n }\n } catch (e) {\n console.error(`Error: ${(e as Error).message}`);\n }\n });\n\n// ─── find ───\nprogram\n .command('find <name>')\n .description('Find components by name')\n .action(async (name) => {\n const opts = program.opts();\n const session = opts.session as string;\n const json = opts.json as boolean;\n\n if (!isDaemonRunning(session)) {\n console.error('Daemon not running. Start with: tui-devtools start');\n process.exit(1);\n }\n\n try {\n const res = await sendIpcRequest(session, {\n command: 'find',\n args: { name },\n });\n if (res.ok) {\n const nodes = res.data as Array<{ id: number; displayName: string; type: string; key: string | null }>;\n if (json) {\n console.log(JSON.stringify(nodes, null, 2));\n } else if (nodes.length === 0) {\n console.log(`No components matching \"${name}\"`);\n } else {\n for (const n of nodes) {\n console.log(` [${n.id}] ${n.displayName} (${n.type})${n.key ? ` key=\"${n.key}\"` : ''}`);\n }\n }\n } else {\n console.error(`Error: ${res.error}`);\n }\n } catch (e) {\n console.error(`Error: ${(e as Error).message}`);\n }\n });\n\n// ─── logs ───\nprogram\n .command('logs')\n .description('Show captured console logs')\n .option('-l, --level <level>', 'Filter by level (log|warn|error|info|debug)')\n .option('-t, --tail <n>', 'Show last N entries', '50')\n .action(async (cmdOpts) => {\n const opts = program.opts();\n const session = opts.session as string;\n const json = opts.json as boolean;\n\n if (!isDaemonRunning(session)) {\n console.error('Daemon not running. Start with: tui-devtools start');\n process.exit(1);\n }\n\n try {\n const res = await sendIpcRequest(session, {\n command: 'logs',\n args: {\n level: cmdOpts.level,\n tail: parseInt(cmdOpts.tail, 10),\n },\n });\n if (res.ok) {\n const logs = res.data as Array<{ level: string; args: unknown[]; timestamp: number }>;\n if (json) {\n console.log(JSON.stringify(logs, null, 2));\n } else if (logs.length === 0) {\n console.log('(no logs captured)');\n } else {\n for (const entry of logs) {\n const ts = new Date(entry.timestamp).toISOString().slice(11, 19);\n const level = entry.level.toUpperCase().padEnd(5);\n const msg = entry.args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ');\n console.log(`${ts} [${level}] ${msg}`);\n }\n }\n } else {\n console.error(`Error: ${res.error}`);\n }\n } catch (e) {\n console.error(`Error: ${(e as Error).message}`);\n }\n });\n\n// ═══════════════════════════════════════════\n// PTY Automation Commands (replaces agent-tui)\n// ═══════════════════════════════════════════\n\nfunction ensureDaemon(session: string): void {\n if (!isDaemonRunning(session)) {\n console.error('Daemon not running. Start with: tui-devtools start');\n process.exit(1);\n }\n}\n\n// ─── run ───\nprogram\n .command('run <command...>')\n .description('Run a TUI application in a virtual terminal')\n .option('-d, --cwd <dir>', 'Working directory')\n .option('--cols <n>', 'Terminal columns', '120')\n .option('--rows <n>', 'Terminal rows', '40')\n .option('--sid <id>', 'PTY session ID', 'default')\n .action(async (commandParts: string[], cmdOpts) => {\n const opts = program.opts();\n const session = opts.session as string;\n const json = opts.json as boolean;\n const command = commandParts.join(' ');\n\n ensureDaemon(session);\n\n try {\n const res = await sendIpcRequest(session, {\n command: 'run',\n args: {\n command,\n sessionId: cmdOpts.sid,\n cwd: cmdOpts.cwd,\n cols: parseInt(cmdOpts.cols, 10),\n rows: parseInt(cmdOpts.rows, 10),\n },\n });\n if (res.ok) {\n const info = res.data as { id: string; pid: number; cols: number; rows: number };\n if (json) {\n console.log(JSON.stringify(info));\n } else {\n console.log(`Session started: ${info.id}`);\n console.log(` PID: ${info.pid}`);\n }\n } else {\n console.error(`Error: ${res.error}`);\n process.exit(1);\n }\n } catch (e) {\n console.error(`Error: ${(e as Error).message}`);\n process.exit(1);\n }\n });\n\n// ─── screenshot ───\nprogram\n .command('screenshot')\n .description('Capture current terminal screen')\n .option('--sid <id>', 'PTY session ID', 'default')\n .option('--strip-ansi', 'Strip ANSI color codes')\n .action(async (cmdOpts) => {\n const opts = program.opts();\n const session = opts.session as string;\n const json = opts.json as boolean;\n\n ensureDaemon(session);\n\n try {\n const res = await sendIpcRequest(session, {\n command: 'screenshot',\n args: { sessionId: cmdOpts.sid, stripAnsi: cmdOpts.stripAnsi },\n });\n if (res.ok) {\n const data = res.data as { screenshot: string; running: boolean };\n if (json) {\n console.log(JSON.stringify(data));\n } else {\n console.log(`Screenshot:${data.running ? '' : ' [stopped]'}`);\n console.log(data.screenshot);\n }\n } else {\n console.error(`Error: ${res.error}`);\n }\n } catch (e) {\n console.error(`Error: ${(e as Error).message}`);\n }\n });\n\n// ─── press ───\nprogram\n .command('press <keys...>')\n .description('Send key press(es) to the terminal')\n .option('--sid <id>', 'PTY session ID', 'default')\n .action(async (keys: string[], cmdOpts) => {\n const opts = program.opts();\n const session = opts.session as string;\n\n ensureDaemon(session);\n\n try {\n const res = await sendIpcRequest(session, {\n command: 'press',\n args: { sessionId: cmdOpts.sid, keys },\n });\n if (res.ok) {\n console.log('✓ Key pressed');\n } else {\n console.error(`Error: ${res.error}`);\n }\n } catch (e) {\n console.error(`Error: ${(e as Error).message}`);\n }\n });\n\n// ─── type ───\nprogram\n .command('type <text>')\n .description('Type text into the terminal')\n .option('--sid <id>', 'PTY session ID', 'default')\n .action(async (text: string, cmdOpts) => {\n const opts = program.opts();\n const session = opts.session as string;\n\n ensureDaemon(session);\n\n try {\n const res = await sendIpcRequest(session, {\n command: 'type',\n args: { sessionId: cmdOpts.sid, text },\n });\n if (res.ok) {\n console.log('✓ Text typed');\n } else {\n console.error(`Error: ${res.error}`);\n }\n } catch (e) {\n console.error(`Error: ${(e as Error).message}`);\n }\n });\n\n// ─── wait ───\nprogram\n .command('wait <text>')\n .description('Wait for text to appear on screen')\n .option('--sid <id>', 'PTY session ID', 'default')\n .option('--timeout <ms>', 'Timeout in milliseconds', '30000')\n .action(async (text: string, cmdOpts) => {\n const opts = program.opts();\n const session = opts.session as string;\n const json = opts.json as boolean;\n\n ensureDaemon(session);\n\n try {\n const res = await sendIpcRequest(session, {\n command: 'wait',\n args: { sessionId: cmdOpts.sid, text, timeout: parseInt(cmdOpts.timeout, 10) },\n });\n if (res.ok) {\n const data = res.data as { found: boolean; screenshot: string };\n if (json) {\n console.log(JSON.stringify(data));\n } else {\n console.log(data.found ? `✓ Found: \"${text}\"` : `✗ Timeout waiting for: \"${text}\"`);\n }\n } else {\n console.error(`Error: ${res.error}`);\n }\n } catch (e) {\n console.error(`Error: ${(e as Error).message}`);\n }\n });\n\n// ─── kill (PTY session) ───\nprogram\n .command('kill-session')\n .description('Kill a PTY session')\n .option('--sid <id>', 'PTY session ID', 'default')\n .action(async (cmdOpts) => {\n const opts = program.opts();\n const session = opts.session as string;\n\n ensureDaemon(session);\n\n try {\n const res = await sendIpcRequest(session, {\n command: 'kill-session',\n args: { sessionId: cmdOpts.sid },\n });\n if (res.ok) {\n console.log(`Session killed: ${cmdOpts.sid}`);\n } else {\n console.error(`Error: ${res.error}`);\n }\n } catch (e) {\n console.error(`Error: ${(e as Error).message}`);\n }\n });\n\n// ─── sessions (list PTY sessions) ───\nprogram\n .command('sessions')\n .description('List PTY sessions')\n .action(async () => {\n const opts = program.opts();\n const session = opts.session as string;\n const json = opts.json as boolean;\n\n ensureDaemon(session);\n\n try {\n const res = await sendIpcRequest(session, { command: 'sessions' });\n if (res.ok) {\n const sessions = res.data as Array<{ id: string; command: string; pid: number; running: boolean; cols: number; rows: number }>;\n if (json) {\n console.log(JSON.stringify(sessions, null, 2));\n } else if (sessions.length === 0) {\n console.log('No active PTY sessions');\n } else {\n for (const s of sessions) {\n console.log(` ${s.id} — ${s.command} [${s.running ? 'running' : 'stopped'}] ${s.cols}x${s.rows} pid:${s.pid}`);\n }\n }\n } else {\n console.error(`Error: ${res.error}`);\n }\n } catch (e) {\n console.error(`Error: ${(e as Error).message}`);\n }\n });\n\n// ─── Internal: daemon entry point ───\nif (process.argv[2] === '__daemon__') {\n const session = process.argv[3] ?? DEFAULT_SESSION;\n const port = parseInt(process.argv[4] ?? String(DEFAULT_PORT), 10);\n\n const { startDaemon } = await import('../src/daemon.js');\n await startDaemon(session, port);\n} else {\n program.parse();\n}\n"],"mappings":"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","names":[]}
|