agent-dbg 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/.bin/ndbg +0 -0
- package/.claude/settings.local.json +21 -0
- package/.claude/skills/ndbg-debugger/ndbg-debugger/SKILL.md +116 -0
- package/.claude/skills/ndbg-debugger/ndbg-debugger/references/commands.md +173 -0
- package/CLAUDE.md +43 -0
- package/PROGRESS.md +261 -0
- package/README.md +67 -0
- package/biome.json +41 -0
- package/ndbg-spec.md +958 -0
- package/package.json +30 -0
- package/src/cdp/client.ts +198 -0
- package/src/cdp/types.ts +16 -0
- package/src/cli/parser.ts +287 -0
- package/src/cli/registry.ts +7 -0
- package/src/cli/types.ts +24 -0
- package/src/commands/attach.ts +47 -0
- package/src/commands/blackbox-ls.ts +38 -0
- package/src/commands/blackbox-rm.ts +57 -0
- package/src/commands/blackbox.ts +48 -0
- package/src/commands/break-ls.ts +57 -0
- package/src/commands/break-rm.ts +40 -0
- package/src/commands/break-toggle.ts +42 -0
- package/src/commands/break.ts +145 -0
- package/src/commands/breakable.ts +69 -0
- package/src/commands/catch.ts +38 -0
- package/src/commands/console.ts +61 -0
- package/src/commands/continue.ts +46 -0
- package/src/commands/eval.ts +70 -0
- package/src/commands/exceptions.ts +61 -0
- package/src/commands/hotpatch.ts +67 -0
- package/src/commands/launch.ts +69 -0
- package/src/commands/logpoint.ts +78 -0
- package/src/commands/pause.ts +46 -0
- package/src/commands/props.ts +77 -0
- package/src/commands/restart-frame.ts +36 -0
- package/src/commands/run-to.ts +70 -0
- package/src/commands/scripts.ts +57 -0
- package/src/commands/search.ts +73 -0
- package/src/commands/sessions.ts +71 -0
- package/src/commands/set-return.ts +49 -0
- package/src/commands/set.ts +61 -0
- package/src/commands/source.ts +59 -0
- package/src/commands/sourcemap.ts +66 -0
- package/src/commands/stack.ts +64 -0
- package/src/commands/state.ts +124 -0
- package/src/commands/status.ts +57 -0
- package/src/commands/step.ts +50 -0
- package/src/commands/stop.ts +27 -0
- package/src/commands/vars.ts +71 -0
- package/src/daemon/client.ts +147 -0
- package/src/daemon/entry.ts +242 -0
- package/src/daemon/paths.ts +26 -0
- package/src/daemon/server.ts +185 -0
- package/src/daemon/session-blackbox.ts +41 -0
- package/src/daemon/session-breakpoints.ts +492 -0
- package/src/daemon/session-execution.ts +121 -0
- package/src/daemon/session-inspection.ts +701 -0
- package/src/daemon/session-mutation.ts +197 -0
- package/src/daemon/session-state.ts +258 -0
- package/src/daemon/session.ts +938 -0
- package/src/daemon/spawn.ts +53 -0
- package/src/formatter/errors.ts +15 -0
- package/src/formatter/source.ts +74 -0
- package/src/formatter/stack.ts +70 -0
- package/src/formatter/values.ts +269 -0
- package/src/formatter/variables.ts +20 -0
- package/src/main.ts +45 -0
- package/src/protocol/messages.ts +316 -0
- package/src/refs/ref-table.ts +120 -0
- package/src/refs/resolver.ts +24 -0
- package/src/sourcemap/resolver.ts +318 -0
- package/tests/fixtures/async-app.js +34 -0
- package/tests/fixtures/console-app.js +12 -0
- package/tests/fixtures/error-app.js +28 -0
- package/tests/fixtures/exception-app.js +6 -0
- package/tests/fixtures/inspect-app.js +10 -0
- package/tests/fixtures/mutation-app.js +9 -0
- package/tests/fixtures/simple-app.js +50 -0
- package/tests/fixtures/step-app.js +13 -0
- package/tests/fixtures/ts-app/src/app.ts +21 -0
- package/tests/fixtures/ts-app/tsconfig.json +14 -0
- package/tests/integration/blackbox.test.ts +135 -0
- package/tests/integration/break-extras.test.ts +241 -0
- package/tests/integration/breakpoint.test.ts +217 -0
- package/tests/integration/console.test.ts +275 -0
- package/tests/integration/execution.test.ts +247 -0
- package/tests/integration/inspection.test.ts +311 -0
- package/tests/integration/mutation.test.ts +178 -0
- package/tests/integration/session.test.ts +223 -0
- package/tests/integration/source.test.ts +209 -0
- package/tests/integration/sourcemap.test.ts +214 -0
- package/tests/integration/state.test.ts +208 -0
- package/tests/unit/cdp-client.test.ts +422 -0
- package/tests/unit/daemon.test.ts +286 -0
- package/tests/unit/formatter.test.ts +716 -0
- package/tests/unit/parser.test.ts +105 -0
- package/tests/unit/refs.test.ts +383 -0
- package/tests/unit/sourcemap.test.ts +236 -0
- package/tsconfig.json +32 -0
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-dbg",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Node.js Debugger CLI for AI Agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ndbg": "./src/main.ts"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "bun run src/main.ts",
|
|
11
|
+
"build": "bun build src/main.ts --compile --outfile dist/ndbg",
|
|
12
|
+
"test": "bun test",
|
|
13
|
+
"lint": "biome check .",
|
|
14
|
+
"format": "biome check --write .",
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@biomejs/biome": "^2.3.14",
|
|
19
|
+
"@types/bun": "latest",
|
|
20
|
+
"devtools-protocol": "^0.0.1581282"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"typescript": "^5"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@jridgewell/trace-mapping": "^0.3.31",
|
|
27
|
+
"zod": "^4.0.0"
|
|
28
|
+
},
|
|
29
|
+
"module": "index.ts"
|
|
30
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import type { ProtocolMapping } from "devtools-protocol/types/protocol-mapping.js";
|
|
2
|
+
import type { CdpEvent, CdpRequest, CdpResponse } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
5
|
+
|
|
6
|
+
type CdpCommand = keyof ProtocolMapping.Commands;
|
|
7
|
+
type CdpEventName = keyof ProtocolMapping.Events;
|
|
8
|
+
|
|
9
|
+
// biome-ignore lint/suspicious/noExplicitAny: Required for handler map that stores both typed and untyped handlers
|
|
10
|
+
type AnyHandler = (...args: any[]) => void;
|
|
11
|
+
|
|
12
|
+
interface PendingRequest {
|
|
13
|
+
resolve: (result: unknown) => void;
|
|
14
|
+
reject: (error: Error) => void;
|
|
15
|
+
timer: ReturnType<typeof setTimeout>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class CdpClient {
|
|
19
|
+
private ws: WebSocket;
|
|
20
|
+
private nextId = 1;
|
|
21
|
+
private pending = new Map<number, PendingRequest>();
|
|
22
|
+
private listeners = new Map<string, Set<AnyHandler>>();
|
|
23
|
+
private isConnected = false;
|
|
24
|
+
|
|
25
|
+
private constructor(ws: WebSocket) {
|
|
26
|
+
this.ws = ws;
|
|
27
|
+
this.isConnected = true;
|
|
28
|
+
this.setupHandlers();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static async connect(wsUrl: string): Promise<CdpClient> {
|
|
32
|
+
return new Promise<CdpClient>((resolve, reject) => {
|
|
33
|
+
const ws = new WebSocket(wsUrl);
|
|
34
|
+
|
|
35
|
+
const onOpen = () => {
|
|
36
|
+
ws.removeEventListener("error", onError);
|
|
37
|
+
const client = new CdpClient(ws);
|
|
38
|
+
resolve(client);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const onError = (event: Event) => {
|
|
42
|
+
ws.removeEventListener("open", onOpen);
|
|
43
|
+
const message = event instanceof ErrorEvent ? event.message : "WebSocket connection failed";
|
|
44
|
+
reject(new Error(message));
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
ws.addEventListener("open", onOpen, { once: true });
|
|
48
|
+
ws.addEventListener("error", onError, { once: true });
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async send<T extends CdpCommand>(
|
|
53
|
+
method: T,
|
|
54
|
+
...params: ProtocolMapping.Commands[T]["paramsType"]
|
|
55
|
+
): Promise<ProtocolMapping.Commands[T]["returnType"]>;
|
|
56
|
+
async send(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
57
|
+
async send(method: string, ...args: unknown[]): Promise<unknown> {
|
|
58
|
+
if (!this.isConnected) {
|
|
59
|
+
throw new Error("CDP client is not connected");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const params = args[0] as Record<string, unknown> | undefined;
|
|
63
|
+
const id = this.nextId++;
|
|
64
|
+
const request: CdpRequest = { id, method };
|
|
65
|
+
if (params !== undefined) {
|
|
66
|
+
request.params = params;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return new Promise<unknown>((resolve, reject) => {
|
|
70
|
+
const timer = setTimeout(() => {
|
|
71
|
+
this.pending.delete(id);
|
|
72
|
+
reject(new Error(`CDP request timed out: ${method} (id=${id})`));
|
|
73
|
+
}, DEFAULT_TIMEOUT_MS);
|
|
74
|
+
|
|
75
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
76
|
+
this.ws.send(JSON.stringify(request));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
on<T extends CdpEventName>(event: T, handler: (...args: ProtocolMapping.Events[T]) => void): void;
|
|
81
|
+
on(event: string, handler: (params: unknown) => void): void;
|
|
82
|
+
on(event: string, handler: AnyHandler): void {
|
|
83
|
+
let handlers = this.listeners.get(event);
|
|
84
|
+
if (!handlers) {
|
|
85
|
+
handlers = new Set();
|
|
86
|
+
this.listeners.set(event, handlers);
|
|
87
|
+
}
|
|
88
|
+
handlers.add(handler);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
off<T extends CdpEventName>(
|
|
92
|
+
event: T,
|
|
93
|
+
handler: (...args: ProtocolMapping.Events[T]) => void,
|
|
94
|
+
): void;
|
|
95
|
+
off(event: string, handler: (params: unknown) => void): void;
|
|
96
|
+
off(event: string, handler: AnyHandler): void {
|
|
97
|
+
const handlers = this.listeners.get(event);
|
|
98
|
+
if (handlers) {
|
|
99
|
+
handlers.delete(handler);
|
|
100
|
+
if (handlers.size === 0) {
|
|
101
|
+
this.listeners.delete(event);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async enableDomains(): Promise<void> {
|
|
107
|
+
await Promise.all([
|
|
108
|
+
this.send("Debugger.enable"),
|
|
109
|
+
this.send("Runtime.enable"),
|
|
110
|
+
this.send("Profiler.enable"),
|
|
111
|
+
this.send("HeapProfiler.enable"),
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async runIfWaitingForDebugger(): Promise<void> {
|
|
116
|
+
await this.send("Runtime.runIfWaitingForDebugger");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
disconnect(): void {
|
|
120
|
+
if (!this.isConnected) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this.isConnected = false;
|
|
124
|
+
|
|
125
|
+
const error = new Error("CDP client disconnected");
|
|
126
|
+
for (const [id, pending] of this.pending) {
|
|
127
|
+
clearTimeout(pending.timer);
|
|
128
|
+
pending.reject(error);
|
|
129
|
+
this.pending.delete(id);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.listeners.clear();
|
|
133
|
+
this.ws.close();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
get connected(): boolean {
|
|
137
|
+
return this.isConnected;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Exposed for testing: directly handle a raw message string. */
|
|
141
|
+
handleMessage(data: string): void {
|
|
142
|
+
this.onMessage(data);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private setupHandlers(): void {
|
|
146
|
+
this.ws.addEventListener("message", (event: MessageEvent) => {
|
|
147
|
+
const data = typeof event.data === "string" ? event.data : String(event.data);
|
|
148
|
+
this.onMessage(data);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
this.ws.addEventListener("close", () => {
|
|
152
|
+
this.isConnected = false;
|
|
153
|
+
const error = new Error("WebSocket connection closed");
|
|
154
|
+
for (const [id, pending] of this.pending) {
|
|
155
|
+
clearTimeout(pending.timer);
|
|
156
|
+
pending.reject(error);
|
|
157
|
+
this.pending.delete(id);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
this.ws.addEventListener("error", () => {
|
|
162
|
+
// Error events are followed by close events, so cleanup happens there.
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private onMessage(data: string): void {
|
|
167
|
+
let parsed: CdpResponse | CdpEvent;
|
|
168
|
+
try {
|
|
169
|
+
parsed = JSON.parse(data) as CdpResponse | CdpEvent;
|
|
170
|
+
} catch {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if ("id" in parsed && typeof parsed.id === "number") {
|
|
175
|
+
const response = parsed as CdpResponse;
|
|
176
|
+
const pending = this.pending.get(response.id);
|
|
177
|
+
if (!pending) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.pending.delete(response.id);
|
|
181
|
+
clearTimeout(pending.timer);
|
|
182
|
+
|
|
183
|
+
if (response.error) {
|
|
184
|
+
pending.reject(new Error(`CDP error (${response.error.code}): ${response.error.message}`));
|
|
185
|
+
} else {
|
|
186
|
+
pending.resolve(response.result);
|
|
187
|
+
}
|
|
188
|
+
} else if ("method" in parsed) {
|
|
189
|
+
const event = parsed as CdpEvent;
|
|
190
|
+
const handlers = this.listeners.get(event.method);
|
|
191
|
+
if (handlers) {
|
|
192
|
+
for (const handler of handlers) {
|
|
193
|
+
handler(event.params);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
package/src/cdp/types.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface CdpRequest {
|
|
2
|
+
id: number;
|
|
3
|
+
method: string;
|
|
4
|
+
params?: Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface CdpResponse {
|
|
8
|
+
id: number;
|
|
9
|
+
result?: unknown;
|
|
10
|
+
error?: { code: number; message: string };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CdpEvent {
|
|
14
|
+
method: string;
|
|
15
|
+
params?: unknown;
|
|
16
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { registry } from "./registry.ts";
|
|
2
|
+
import type { GlobalFlags, ParsedArgs } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
const GLOBAL_FLAGS = new Set(["session", "json", "color", "help-agent", "help"]);
|
|
5
|
+
const BOOLEAN_FLAGS = new Set([
|
|
6
|
+
"json",
|
|
7
|
+
"color",
|
|
8
|
+
"help-agent",
|
|
9
|
+
"help",
|
|
10
|
+
"brk",
|
|
11
|
+
"compact",
|
|
12
|
+
"all-scopes",
|
|
13
|
+
"vars",
|
|
14
|
+
"stack",
|
|
15
|
+
"breakpoints",
|
|
16
|
+
"code",
|
|
17
|
+
"own",
|
|
18
|
+
"private",
|
|
19
|
+
"internal",
|
|
20
|
+
"regex",
|
|
21
|
+
"case-sensitive",
|
|
22
|
+
"detailed",
|
|
23
|
+
"follow",
|
|
24
|
+
"clear",
|
|
25
|
+
"uncovered",
|
|
26
|
+
"include-gc",
|
|
27
|
+
"silent",
|
|
28
|
+
"side-effect-free",
|
|
29
|
+
"sourcemap",
|
|
30
|
+
"dry-run",
|
|
31
|
+
"continue",
|
|
32
|
+
"all",
|
|
33
|
+
"cleanup",
|
|
34
|
+
"disable",
|
|
35
|
+
"generated",
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
export function parseArgs(argv: string[]): ParsedArgs {
|
|
39
|
+
const flags: Record<string, string | boolean> = {};
|
|
40
|
+
const positionals: string[] = [];
|
|
41
|
+
let command = "";
|
|
42
|
+
let subcommand: string | null = null;
|
|
43
|
+
|
|
44
|
+
let i = 0;
|
|
45
|
+
|
|
46
|
+
// Extract command (first non-flag argument)
|
|
47
|
+
while (i < argv.length) {
|
|
48
|
+
const arg = argv[i];
|
|
49
|
+
if (arg === undefined) break;
|
|
50
|
+
if (arg.startsWith("-")) break;
|
|
51
|
+
if (!command) {
|
|
52
|
+
command = arg;
|
|
53
|
+
} else if (!subcommand) {
|
|
54
|
+
subcommand = arg;
|
|
55
|
+
} else {
|
|
56
|
+
positionals.push(arg);
|
|
57
|
+
}
|
|
58
|
+
i++;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Parse remaining arguments
|
|
62
|
+
while (i < argv.length) {
|
|
63
|
+
const arg = argv[i];
|
|
64
|
+
if (arg === undefined) {
|
|
65
|
+
i++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (arg === "--") {
|
|
70
|
+
// Everything after -- is positional
|
|
71
|
+
i++;
|
|
72
|
+
while (i < argv.length) {
|
|
73
|
+
const rest = argv[i];
|
|
74
|
+
if (rest !== undefined) positionals.push(rest);
|
|
75
|
+
i++;
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (arg.startsWith("--")) {
|
|
81
|
+
const key = arg.slice(2);
|
|
82
|
+
if (BOOLEAN_FLAGS.has(key)) {
|
|
83
|
+
flags[key] = true;
|
|
84
|
+
} else {
|
|
85
|
+
const next = argv[i + 1];
|
|
86
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
87
|
+
flags[key] = next;
|
|
88
|
+
i++;
|
|
89
|
+
} else {
|
|
90
|
+
flags[key] = true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} else if (arg.startsWith("-") && arg.length === 2) {
|
|
94
|
+
// Short flags
|
|
95
|
+
const key = arg.slice(1);
|
|
96
|
+
const shortMap: Record<string, string> = {
|
|
97
|
+
v: "vars",
|
|
98
|
+
s: "stack",
|
|
99
|
+
b: "breakpoints",
|
|
100
|
+
c: "code",
|
|
101
|
+
};
|
|
102
|
+
const mapped = shortMap[key];
|
|
103
|
+
if (mapped) {
|
|
104
|
+
flags[mapped] = true;
|
|
105
|
+
} else {
|
|
106
|
+
flags[key] = true;
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
positionals.push(arg);
|
|
110
|
+
}
|
|
111
|
+
i++;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const global: GlobalFlags = {
|
|
115
|
+
session: typeof flags.session === "string" ? flags.session : "default",
|
|
116
|
+
json: flags.json === true,
|
|
117
|
+
color: flags.color === true,
|
|
118
|
+
helpAgent: flags["help-agent"] === true,
|
|
119
|
+
help: flags.help === true,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Remove global flags from flags map
|
|
123
|
+
for (const key of GLOBAL_FLAGS) {
|
|
124
|
+
delete flags[key];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { command, subcommand, positionals, flags, global };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function run(args: ParsedArgs): Promise<number> {
|
|
131
|
+
if (args.global.helpAgent) {
|
|
132
|
+
printHelpAgent();
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!args.command || args.global.help) {
|
|
137
|
+
printHelp();
|
|
138
|
+
return args.command ? 0 : 1;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const handler = registry.get(args.command);
|
|
142
|
+
if (!handler) {
|
|
143
|
+
console.error(`✗ Unknown command: ${args.command}`);
|
|
144
|
+
console.error(" → Try: ndbg --help");
|
|
145
|
+
return 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
return await handler(args);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
152
|
+
console.error(`✗ ${message}`);
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function printHelp(): void {
|
|
158
|
+
console.log(`ndbg — Node.js debugger CLI for AI agents
|
|
159
|
+
|
|
160
|
+
Usage: ndbg <command> [options]
|
|
161
|
+
|
|
162
|
+
Session:
|
|
163
|
+
launch [--brk] <command...> Start + attach debugger
|
|
164
|
+
attach <pid|ws-url|port> Attach to running process
|
|
165
|
+
stop Kill process + daemon
|
|
166
|
+
sessions [--cleanup] List active sessions
|
|
167
|
+
status Session info
|
|
168
|
+
|
|
169
|
+
Execution (returns state automatically):
|
|
170
|
+
continue Resume execution
|
|
171
|
+
step [over|into|out] Step one statement
|
|
172
|
+
run-to <file>:<line> Continue to location
|
|
173
|
+
pause Interrupt running process
|
|
174
|
+
restart-frame [@fN] Re-execute frame from beginning
|
|
175
|
+
|
|
176
|
+
Inspection:
|
|
177
|
+
state [-v|-s|-b|-c] Debug state snapshot
|
|
178
|
+
[--depth N] [--lines N] [--frame @fN] [--all-scopes] [--compact] [--generated]
|
|
179
|
+
vars [name...] Show local variables
|
|
180
|
+
[--frame @fN] [--all-scopes]
|
|
181
|
+
stack [--async-depth N] Show call stack
|
|
182
|
+
[--generated]
|
|
183
|
+
eval <expression> Evaluate expression
|
|
184
|
+
[--frame @fN] [--silent] [--timeout MS] [--side-effect-free]
|
|
185
|
+
props <@ref> Expand object properties
|
|
186
|
+
[--own] [--depth N] [--private] [--internal]
|
|
187
|
+
source [--lines N] Show source code
|
|
188
|
+
[--file <path>] [--all] [--generated]
|
|
189
|
+
search <query> Search loaded scripts
|
|
190
|
+
[--regex] [--case-sensitive] [--file <id>]
|
|
191
|
+
scripts [--filter <pattern>] List loaded scripts
|
|
192
|
+
console [--since N] [--level] Console output
|
|
193
|
+
[--clear]
|
|
194
|
+
exceptions [--since N] Captured exceptions
|
|
195
|
+
|
|
196
|
+
Breakpoints:
|
|
197
|
+
break <file>:<line> Set breakpoint
|
|
198
|
+
[--condition <expr>] [--hit-count <n>] [--continue] [--pattern <regex>:<line>]
|
|
199
|
+
break-rm <BP#|all> Remove breakpoint
|
|
200
|
+
break-ls List breakpoints
|
|
201
|
+
break-toggle <BP#|all> Enable/disable breakpoints
|
|
202
|
+
breakable <file>:<start>-<end> List valid breakpoint locations
|
|
203
|
+
logpoint <file>:<line> <tpl> Set logpoint
|
|
204
|
+
[--condition <expr>]
|
|
205
|
+
catch [all|uncaught|caught|none] Pause on exceptions
|
|
206
|
+
|
|
207
|
+
Mutation:
|
|
208
|
+
set <@ref|name> <value> Change variable value
|
|
209
|
+
set-return <value> Change return value (at return point)
|
|
210
|
+
hotpatch <file> [--dry-run] Live-edit script source
|
|
211
|
+
|
|
212
|
+
Blackboxing:
|
|
213
|
+
blackbox <pattern...> Skip stepping into matching scripts
|
|
214
|
+
blackbox-ls List current patterns
|
|
215
|
+
blackbox-rm <pattern|all> Remove patterns
|
|
216
|
+
|
|
217
|
+
Source Maps:
|
|
218
|
+
sourcemap [file] Show source map info
|
|
219
|
+
sourcemap --disable Disable resolution globally
|
|
220
|
+
|
|
221
|
+
Global flags:
|
|
222
|
+
--session NAME Target session (default: "default")
|
|
223
|
+
--json JSON output
|
|
224
|
+
--color ANSI colors
|
|
225
|
+
--help-agent LLM reference card
|
|
226
|
+
--help Show this help`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function printHelpAgent(): void {
|
|
230
|
+
console.log(`ndbg — Node.js debugger CLI for AI agents
|
|
231
|
+
|
|
232
|
+
CORE LOOP:
|
|
233
|
+
1. ndbg launch --brk "node app.js" → pauses at first line, returns state
|
|
234
|
+
2. ndbg break src/file.ts:42 → set breakpoint
|
|
235
|
+
3. ndbg continue → run to breakpoint, returns state
|
|
236
|
+
4. Inspect: ndbg vars, ndbg eval, ndbg props @v1
|
|
237
|
+
5. Mutate/fix: ndbg set @v1 value, ndbg hotpatch src/file.ts
|
|
238
|
+
6. Repeat from 3
|
|
239
|
+
|
|
240
|
+
REFS: Every output assigns @refs. Use them everywhere:
|
|
241
|
+
@v1..@vN variables | ndbg props @v1, ndbg set @v2 true
|
|
242
|
+
@f0..@fN stack frames | ndbg eval --frame @f1
|
|
243
|
+
BP#1..N breakpoints | ndbg break-rm BP#1, ndbg break-toggle BP#1
|
|
244
|
+
|
|
245
|
+
EXECUTION (all return state automatically):
|
|
246
|
+
ndbg continue Resume to next breakpoint
|
|
247
|
+
ndbg step [over|into|out] Step one statement
|
|
248
|
+
ndbg run-to file:line Continue to location
|
|
249
|
+
ndbg pause Interrupt running process
|
|
250
|
+
ndbg restart-frame [@fN] Re-run frame from beginning
|
|
251
|
+
|
|
252
|
+
BREAKPOINTS:
|
|
253
|
+
ndbg break file:line [--condition expr] [--hit-count N] [--continue]
|
|
254
|
+
ndbg break --pattern "regex":line
|
|
255
|
+
ndbg break-rm <BP#|all> Remove breakpoints
|
|
256
|
+
ndbg break-ls List breakpoints
|
|
257
|
+
ndbg break-toggle <BP#|all> Enable/disable breakpoints
|
|
258
|
+
ndbg breakable file:start-end Valid breakpoint locations
|
|
259
|
+
ndbg logpoint file:line "template \${var}" [--condition expr]
|
|
260
|
+
ndbg catch [all|uncaught|caught|none]
|
|
261
|
+
|
|
262
|
+
INSPECTION:
|
|
263
|
+
ndbg state [-v|-s|-b|-c] [--depth N] [--lines N] [--frame @fN] [--all-scopes] [--compact] [--generated]
|
|
264
|
+
ndbg vars [name...] [--frame @fN] [--all-scopes]
|
|
265
|
+
ndbg stack [--async-depth N] [--generated]
|
|
266
|
+
ndbg eval <expr> [--frame @fN] [--silent] [--timeout MS] [--side-effect-free]
|
|
267
|
+
ndbg props @ref [--own] [--depth N] [--private] [--internal]
|
|
268
|
+
ndbg source [--lines N] [--file path] [--all] [--generated]
|
|
269
|
+
ndbg search "query" [--regex] [--case-sensitive] [--file id]
|
|
270
|
+
ndbg scripts [--filter pattern]
|
|
271
|
+
ndbg console [--since N] [--level type] [--clear]
|
|
272
|
+
ndbg exceptions [--since N]
|
|
273
|
+
|
|
274
|
+
MUTATION:
|
|
275
|
+
ndbg set <@ref|name> <value> Change variable
|
|
276
|
+
ndbg set-return <value> Change return value (at return point)
|
|
277
|
+
ndbg hotpatch <file> [--dry-run] Live-edit code (no restart!)
|
|
278
|
+
|
|
279
|
+
BLACKBOXING:
|
|
280
|
+
ndbg blackbox <pattern...> Skip stepping into matching scripts
|
|
281
|
+
ndbg blackbox-ls List current patterns
|
|
282
|
+
ndbg blackbox-rm <pattern|all> Remove patterns
|
|
283
|
+
|
|
284
|
+
SOURCE MAPS:
|
|
285
|
+
ndbg sourcemap [file] Show source map info
|
|
286
|
+
ndbg sourcemap --disable Disable resolution globally`);
|
|
287
|
+
}
|
package/src/cli/types.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface GlobalFlags {
|
|
2
|
+
session: string;
|
|
3
|
+
json: boolean;
|
|
4
|
+
color: boolean;
|
|
5
|
+
helpAgent: boolean;
|
|
6
|
+
help: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ParsedArgs {
|
|
10
|
+
command: string;
|
|
11
|
+
subcommand: string | null;
|
|
12
|
+
positionals: string[];
|
|
13
|
+
flags: Record<string, string | boolean>;
|
|
14
|
+
global: GlobalFlags;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type CommandHandler = (args: ParsedArgs) => Promise<number>;
|
|
18
|
+
|
|
19
|
+
export interface CommandDef {
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
usage: string;
|
|
23
|
+
handler: CommandHandler;
|
|
24
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { registerCommand } from "../cli/registry.ts";
|
|
2
|
+
import { DaemonClient } from "../daemon/client.ts";
|
|
3
|
+
import { spawnDaemon } from "../daemon/spawn.ts";
|
|
4
|
+
|
|
5
|
+
registerCommand("attach", async (args) => {
|
|
6
|
+
const session = args.global.session;
|
|
7
|
+
const target = args.subcommand ?? args.positionals[0];
|
|
8
|
+
|
|
9
|
+
if (!target) {
|
|
10
|
+
console.error("No target specified");
|
|
11
|
+
console.error(" -> Try: ndbg attach <ws-url | port>");
|
|
12
|
+
return 1;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Check if daemon already running
|
|
16
|
+
if (DaemonClient.isRunning(session)) {
|
|
17
|
+
console.error(`Session "${session}" is already active`);
|
|
18
|
+
console.error(` -> Try: ndbg stop --session ${session}`);
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Spawn daemon
|
|
23
|
+
const timeout =
|
|
24
|
+
typeof args.flags.timeout === "string" ? parseInt(args.flags.timeout, 10) : undefined;
|
|
25
|
+
await spawnDaemon(session, { timeout });
|
|
26
|
+
|
|
27
|
+
// Send attach command
|
|
28
|
+
const client = new DaemonClient(session);
|
|
29
|
+
const response = await client.request("attach", { target });
|
|
30
|
+
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
console.error(`${response.error}`);
|
|
33
|
+
if (response.suggestion) console.error(` ${response.suggestion}`);
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const data = response.data as { wsUrl: string };
|
|
38
|
+
|
|
39
|
+
if (args.global.json) {
|
|
40
|
+
console.log(JSON.stringify(data, null, 2));
|
|
41
|
+
} else {
|
|
42
|
+
console.log(`Session "${session}" attached`);
|
|
43
|
+
console.log(`Connected to ${data.wsUrl}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return 0;
|
|
47
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { registerCommand } from "../cli/registry.ts";
|
|
2
|
+
import { DaemonClient } from "../daemon/client.ts";
|
|
3
|
+
|
|
4
|
+
registerCommand("blackbox-ls", async (args) => {
|
|
5
|
+
const session = args.global.session;
|
|
6
|
+
|
|
7
|
+
if (!DaemonClient.isRunning(session)) {
|
|
8
|
+
console.error(`No active session "${session}"`);
|
|
9
|
+
console.error(" -> Try: ndbg launch --brk node app.js");
|
|
10
|
+
return 1;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const client = new DaemonClient(session);
|
|
14
|
+
const response = await client.request("blackbox-ls");
|
|
15
|
+
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
console.error(`${response.error}`);
|
|
18
|
+
if (response.suggestion) console.error(` ${response.suggestion}`);
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const data = response.data as string[];
|
|
23
|
+
|
|
24
|
+
if (args.global.json) {
|
|
25
|
+
console.log(JSON.stringify(data, null, 2));
|
|
26
|
+
} else {
|
|
27
|
+
if (data.length === 0) {
|
|
28
|
+
console.log("No blackbox patterns set");
|
|
29
|
+
} else {
|
|
30
|
+
console.log("Blackbox patterns:");
|
|
31
|
+
for (const p of data) {
|
|
32
|
+
console.log(` ${p}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return 0;
|
|
38
|
+
});
|