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.
Files changed (99) hide show
  1. package/.bin/ndbg +0 -0
  2. package/.claude/settings.local.json +21 -0
  3. package/.claude/skills/ndbg-debugger/ndbg-debugger/SKILL.md +116 -0
  4. package/.claude/skills/ndbg-debugger/ndbg-debugger/references/commands.md +173 -0
  5. package/CLAUDE.md +43 -0
  6. package/PROGRESS.md +261 -0
  7. package/README.md +67 -0
  8. package/biome.json +41 -0
  9. package/ndbg-spec.md +958 -0
  10. package/package.json +30 -0
  11. package/src/cdp/client.ts +198 -0
  12. package/src/cdp/types.ts +16 -0
  13. package/src/cli/parser.ts +287 -0
  14. package/src/cli/registry.ts +7 -0
  15. package/src/cli/types.ts +24 -0
  16. package/src/commands/attach.ts +47 -0
  17. package/src/commands/blackbox-ls.ts +38 -0
  18. package/src/commands/blackbox-rm.ts +57 -0
  19. package/src/commands/blackbox.ts +48 -0
  20. package/src/commands/break-ls.ts +57 -0
  21. package/src/commands/break-rm.ts +40 -0
  22. package/src/commands/break-toggle.ts +42 -0
  23. package/src/commands/break.ts +145 -0
  24. package/src/commands/breakable.ts +69 -0
  25. package/src/commands/catch.ts +38 -0
  26. package/src/commands/console.ts +61 -0
  27. package/src/commands/continue.ts +46 -0
  28. package/src/commands/eval.ts +70 -0
  29. package/src/commands/exceptions.ts +61 -0
  30. package/src/commands/hotpatch.ts +67 -0
  31. package/src/commands/launch.ts +69 -0
  32. package/src/commands/logpoint.ts +78 -0
  33. package/src/commands/pause.ts +46 -0
  34. package/src/commands/props.ts +77 -0
  35. package/src/commands/restart-frame.ts +36 -0
  36. package/src/commands/run-to.ts +70 -0
  37. package/src/commands/scripts.ts +57 -0
  38. package/src/commands/search.ts +73 -0
  39. package/src/commands/sessions.ts +71 -0
  40. package/src/commands/set-return.ts +49 -0
  41. package/src/commands/set.ts +61 -0
  42. package/src/commands/source.ts +59 -0
  43. package/src/commands/sourcemap.ts +66 -0
  44. package/src/commands/stack.ts +64 -0
  45. package/src/commands/state.ts +124 -0
  46. package/src/commands/status.ts +57 -0
  47. package/src/commands/step.ts +50 -0
  48. package/src/commands/stop.ts +27 -0
  49. package/src/commands/vars.ts +71 -0
  50. package/src/daemon/client.ts +147 -0
  51. package/src/daemon/entry.ts +242 -0
  52. package/src/daemon/paths.ts +26 -0
  53. package/src/daemon/server.ts +185 -0
  54. package/src/daemon/session-blackbox.ts +41 -0
  55. package/src/daemon/session-breakpoints.ts +492 -0
  56. package/src/daemon/session-execution.ts +121 -0
  57. package/src/daemon/session-inspection.ts +701 -0
  58. package/src/daemon/session-mutation.ts +197 -0
  59. package/src/daemon/session-state.ts +258 -0
  60. package/src/daemon/session.ts +938 -0
  61. package/src/daemon/spawn.ts +53 -0
  62. package/src/formatter/errors.ts +15 -0
  63. package/src/formatter/source.ts +74 -0
  64. package/src/formatter/stack.ts +70 -0
  65. package/src/formatter/values.ts +269 -0
  66. package/src/formatter/variables.ts +20 -0
  67. package/src/main.ts +45 -0
  68. package/src/protocol/messages.ts +316 -0
  69. package/src/refs/ref-table.ts +120 -0
  70. package/src/refs/resolver.ts +24 -0
  71. package/src/sourcemap/resolver.ts +318 -0
  72. package/tests/fixtures/async-app.js +34 -0
  73. package/tests/fixtures/console-app.js +12 -0
  74. package/tests/fixtures/error-app.js +28 -0
  75. package/tests/fixtures/exception-app.js +6 -0
  76. package/tests/fixtures/inspect-app.js +10 -0
  77. package/tests/fixtures/mutation-app.js +9 -0
  78. package/tests/fixtures/simple-app.js +50 -0
  79. package/tests/fixtures/step-app.js +13 -0
  80. package/tests/fixtures/ts-app/src/app.ts +21 -0
  81. package/tests/fixtures/ts-app/tsconfig.json +14 -0
  82. package/tests/integration/blackbox.test.ts +135 -0
  83. package/tests/integration/break-extras.test.ts +241 -0
  84. package/tests/integration/breakpoint.test.ts +217 -0
  85. package/tests/integration/console.test.ts +275 -0
  86. package/tests/integration/execution.test.ts +247 -0
  87. package/tests/integration/inspection.test.ts +311 -0
  88. package/tests/integration/mutation.test.ts +178 -0
  89. package/tests/integration/session.test.ts +223 -0
  90. package/tests/integration/source.test.ts +209 -0
  91. package/tests/integration/sourcemap.test.ts +214 -0
  92. package/tests/integration/state.test.ts +208 -0
  93. package/tests/unit/cdp-client.test.ts +422 -0
  94. package/tests/unit/daemon.test.ts +286 -0
  95. package/tests/unit/formatter.test.ts +716 -0
  96. package/tests/unit/parser.test.ts +105 -0
  97. package/tests/unit/refs.test.ts +383 -0
  98. package/tests/unit/sourcemap.test.ts +236 -0
  99. 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
+ }
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ import type { CommandHandler } from "./types.ts";
2
+
3
+ export const registry = new Map<string, CommandHandler>();
4
+
5
+ export function registerCommand(name: string, handler: CommandHandler): void {
6
+ registry.set(name, handler);
7
+ }
@@ -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
+ });