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
@@ -0,0 +1,71 @@
1
+ import { registerCommand } from "../cli/registry.ts";
2
+ import { DaemonClient } from "../daemon/client.ts";
3
+
4
+ registerCommand("sessions", async (args) => {
5
+ const cleanup = args.flags.cleanup === true;
6
+ const sessions = DaemonClient.listSessions();
7
+
8
+ if (cleanup) {
9
+ let cleaned = 0;
10
+ for (const s of sessions) {
11
+ const alive = await DaemonClient.isAlive(s);
12
+ if (!alive) {
13
+ try {
14
+ const client = new DaemonClient(s);
15
+ await client.request("stop");
16
+ } catch {
17
+ // Socket exists but daemon is dead — stale detection handles cleanup
18
+ }
19
+ cleaned++;
20
+ }
21
+ }
22
+ console.log(`Cleaned up ${cleaned} orphaned session(s)`);
23
+ return 0;
24
+ }
25
+
26
+ if (sessions.length === 0) {
27
+ if (args.global.json) {
28
+ console.log("[]");
29
+ } else {
30
+ console.log("No active sessions");
31
+ }
32
+ return 0;
33
+ }
34
+
35
+ if (args.global.json) {
36
+ const results: unknown[] = [];
37
+ for (const s of sessions) {
38
+ try {
39
+ const client = new DaemonClient(s);
40
+ const resp = await client.request("status");
41
+ results.push(resp.ok ? resp.data : { session: s, state: "unknown" });
42
+ } catch {
43
+ results.push({ session: s, state: "unreachable" });
44
+ }
45
+ }
46
+ console.log(JSON.stringify(results, null, 2));
47
+ } else {
48
+ for (const s of sessions) {
49
+ try {
50
+ const client = new DaemonClient(s);
51
+ const resp = await client.request("status");
52
+ if (resp.ok) {
53
+ const d = resp.data as {
54
+ state: string;
55
+ pid?: number;
56
+ uptime: number;
57
+ };
58
+ const pid = d.pid ? ` (pid ${d.pid})` : "";
59
+ const uptime = `${Math.round(d.uptime)}s`;
60
+ console.log(` ${s}${pid} ${d.state} uptime=${uptime}`);
61
+ } else {
62
+ console.log(` ${s} unknown`);
63
+ }
64
+ } catch {
65
+ console.log(` ${s} unreachable`);
66
+ }
67
+ }
68
+ }
69
+
70
+ return 0;
71
+ });
@@ -0,0 +1,49 @@
1
+ import { registerCommand } from "../cli/registry.ts";
2
+ import { DaemonClient } from "../daemon/client.ts";
3
+
4
+ registerCommand("set-return", 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
+ // Build value from subcommand + positionals
14
+ const parts: string[] = [];
15
+ if (args.subcommand) {
16
+ parts.push(args.subcommand);
17
+ }
18
+ parts.push(...args.positionals);
19
+ const value = parts.join(" ");
20
+
21
+ if (!value) {
22
+ console.error("No value specified");
23
+ console.error(" -> Try: ndbg set-return 42");
24
+ return 1;
25
+ }
26
+
27
+ const client = new DaemonClient(session);
28
+ const response = await client.request("set-return", { value });
29
+
30
+ if (!response.ok) {
31
+ console.error(`${response.error}`);
32
+ if (response.suggestion) console.error(` ${response.suggestion}`);
33
+ return 1;
34
+ }
35
+
36
+ const data = response.data as {
37
+ value: string;
38
+ type: string;
39
+ };
40
+
41
+ if (args.global.json) {
42
+ console.log(JSON.stringify(data, null, 2));
43
+ return 0;
44
+ }
45
+
46
+ console.log(`return value set to: ${data.value}`);
47
+
48
+ return 0;
49
+ });
@@ -0,0 +1,61 @@
1
+ import { registerCommand } from "../cli/registry.ts";
2
+ import { DaemonClient } from "../daemon/client.ts";
3
+
4
+ registerCommand("set", 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 varName = args.subcommand;
14
+ if (!varName) {
15
+ console.error("No variable name specified");
16
+ console.error(" -> Try: ndbg set counter 42");
17
+ return 1;
18
+ }
19
+
20
+ const valueParts = args.positionals;
21
+ if (valueParts.length === 0) {
22
+ console.error("No value specified");
23
+ console.error(" -> Try: ndbg set counter 42");
24
+ return 1;
25
+ }
26
+ const value = valueParts.join(" ");
27
+
28
+ const setArgs: Record<string, unknown> = {
29
+ name: varName,
30
+ value,
31
+ };
32
+
33
+ if (typeof args.flags.frame === "string") {
34
+ setArgs.frame = args.flags.frame;
35
+ }
36
+
37
+ const client = new DaemonClient(session);
38
+ const response = await client.request("set", setArgs);
39
+
40
+ if (!response.ok) {
41
+ console.error(`${response.error}`);
42
+ if (response.suggestion) console.error(` ${response.suggestion}`);
43
+ return 1;
44
+ }
45
+
46
+ const data = response.data as {
47
+ name: string;
48
+ oldValue?: string;
49
+ newValue: string;
50
+ type: string;
51
+ };
52
+
53
+ if (args.global.json) {
54
+ console.log(JSON.stringify(data, null, 2));
55
+ return 0;
56
+ }
57
+
58
+ console.log(`${data.name} = ${data.newValue}`);
59
+
60
+ return 0;
61
+ });
@@ -0,0 +1,59 @@
1
+ import { registerCommand } from "../cli/registry.ts";
2
+ import { DaemonClient } from "../daemon/client.ts";
3
+ import type { SourceLine } from "../formatter/source.ts";
4
+ import { formatSource } from "../formatter/source.ts";
5
+
6
+ registerCommand("source", async (args) => {
7
+ const session = args.global.session;
8
+
9
+ if (!DaemonClient.isRunning(session)) {
10
+ console.error(`No active session "${session}"`);
11
+ console.error(" -> Try: ndbg launch --brk node app.js");
12
+ return 1;
13
+ }
14
+
15
+ const client = new DaemonClient(session);
16
+
17
+ const sourceArgs: Record<string, unknown> = {};
18
+
19
+ if (typeof args.flags.lines === "string") {
20
+ sourceArgs.lines = parseInt(args.flags.lines, 10);
21
+ }
22
+ if (typeof args.flags.file === "string") {
23
+ sourceArgs.file = args.flags.file;
24
+ }
25
+ if (args.flags.all === true) {
26
+ sourceArgs.all = true;
27
+ }
28
+ if (args.flags.generated === true) {
29
+ sourceArgs.generated = true;
30
+ }
31
+
32
+ const response = await client.request("source", sourceArgs);
33
+
34
+ if (!response.ok) {
35
+ console.error(`${response.error}`);
36
+ if (response.suggestion) console.error(` ${response.suggestion}`);
37
+ return 1;
38
+ }
39
+
40
+ const data = response.data as {
41
+ url: string;
42
+ lines: Array<{ line: number; text: string; current?: boolean }>;
43
+ };
44
+
45
+ if (args.global.json) {
46
+ console.log(JSON.stringify(data, null, 2));
47
+ return 0;
48
+ }
49
+
50
+ console.log(`Source: ${data.url}`);
51
+ const sourceLines: SourceLine[] = data.lines.map((l) => ({
52
+ lineNumber: l.line,
53
+ content: l.text,
54
+ isCurrent: l.current,
55
+ }));
56
+ console.log(formatSource(sourceLines));
57
+
58
+ return 0;
59
+ });
@@ -0,0 +1,66 @@
1
+ import { registerCommand } from "../cli/registry.ts";
2
+ import { DaemonClient } from "../daemon/client.ts";
3
+
4
+ registerCommand("sourcemap", 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
+
15
+ // Handle --disable flag
16
+ if (args.flags.disable === true) {
17
+ const response = await client.request("sourcemap-disable", {});
18
+ if (!response.ok) {
19
+ console.error(`${response.error}`);
20
+ return 1;
21
+ }
22
+ console.log("Source map resolution disabled");
23
+ return 0;
24
+ }
25
+
26
+ // Query source map info
27
+ const smArgs: Record<string, unknown> = {};
28
+ if (args.subcommand) {
29
+ smArgs.file = args.subcommand;
30
+ }
31
+
32
+ const response = await client.request("sourcemap", smArgs);
33
+
34
+ if (!response.ok) {
35
+ console.error(`${response.error}`);
36
+ if (response.suggestion) console.error(` ${response.suggestion}`);
37
+ return 1;
38
+ }
39
+
40
+ const data = response.data as Array<{
41
+ scriptId: string;
42
+ generatedUrl: string;
43
+ mapUrl: string;
44
+ sources: string[];
45
+ hasSourcesContent: boolean;
46
+ }>;
47
+
48
+ if (args.global.json) {
49
+ console.log(JSON.stringify(data, null, 2));
50
+ return 0;
51
+ }
52
+
53
+ if (data.length === 0) {
54
+ console.log("No source maps loaded");
55
+ return 0;
56
+ }
57
+
58
+ for (const info of data) {
59
+ console.log(`Script: ${info.generatedUrl}`);
60
+ console.log(` Map: ${info.mapUrl}`);
61
+ console.log(` Sources: ${info.sources.join(", ")}`);
62
+ console.log(` Has sourcesContent: ${info.hasSourcesContent}`);
63
+ }
64
+
65
+ return 0;
66
+ });
@@ -0,0 +1,64 @@
1
+ import { registerCommand } from "../cli/registry.ts";
2
+ import { DaemonClient } from "../daemon/client.ts";
3
+ import type { StackFrame } from "../formatter/stack.ts";
4
+ import { formatStack } from "../formatter/stack.ts";
5
+
6
+ registerCommand("stack", async (args) => {
7
+ const session = args.global.session;
8
+
9
+ if (!DaemonClient.isRunning(session)) {
10
+ console.error(`No active session "${session}"`);
11
+ console.error(" -> Try: ndbg launch --brk node app.js");
12
+ return 1;
13
+ }
14
+
15
+ const client = new DaemonClient(session);
16
+
17
+ const stackArgs: Record<string, unknown> = {};
18
+
19
+ if (typeof args.flags["async-depth"] === "string") {
20
+ stackArgs.asyncDepth = parseInt(args.flags["async-depth"], 10);
21
+ }
22
+ if (args.flags.generated === true) {
23
+ stackArgs.generated = true;
24
+ }
25
+
26
+ const response = await client.request("stack", stackArgs);
27
+
28
+ if (!response.ok) {
29
+ console.error(`${response.error}`);
30
+ if (response.suggestion) console.error(` ${response.suggestion}`);
31
+ return 1;
32
+ }
33
+
34
+ const data = response.data as Array<{
35
+ ref: string;
36
+ functionName: string;
37
+ file: string;
38
+ line: number;
39
+ column?: number;
40
+ isAsync?: boolean;
41
+ }>;
42
+
43
+ if (args.global.json) {
44
+ console.log(JSON.stringify(data, null, 2));
45
+ return 0;
46
+ }
47
+
48
+ if (data.length === 0) {
49
+ console.log("No stack frames");
50
+ return 0;
51
+ }
52
+
53
+ const frames: StackFrame[] = data.map((f) => ({
54
+ ref: f.ref,
55
+ functionName: f.functionName,
56
+ file: f.file,
57
+ line: f.line,
58
+ column: f.column,
59
+ isAsync: f.isAsync,
60
+ }));
61
+ console.log(formatStack(frames));
62
+
63
+ return 0;
64
+ });
@@ -0,0 +1,124 @@
1
+ import { registerCommand } from "../cli/registry.ts";
2
+ import { DaemonClient } from "../daemon/client.ts";
3
+ import type { StateSnapshot } from "../daemon/session.ts";
4
+ import type { SourceLine } from "../formatter/source.ts";
5
+ import { formatSource } from "../formatter/source.ts";
6
+ import type { StackFrame } from "../formatter/stack.ts";
7
+ import { formatStack } from "../formatter/stack.ts";
8
+ import type { Variable } from "../formatter/variables.ts";
9
+ import { formatVariables } from "../formatter/variables.ts";
10
+
11
+ registerCommand("state", async (args) => {
12
+ const session = args.global.session;
13
+
14
+ if (!DaemonClient.isRunning(session)) {
15
+ console.error(`No active session "${session}"`);
16
+ console.error(" -> Try: ndbg launch --brk node app.js");
17
+ return 1;
18
+ }
19
+
20
+ const client = new DaemonClient(session);
21
+
22
+ const stateArgs: Record<string, unknown> = {};
23
+
24
+ if (args.flags.vars === true) stateArgs.vars = true;
25
+ if (args.flags.stack === true) stateArgs.stack = true;
26
+ if (args.flags.breakpoints === true) stateArgs.breakpoints = true;
27
+ if (args.flags.code === true) stateArgs.code = true;
28
+ if (args.flags.compact === true) stateArgs.compact = true;
29
+ if (args.flags["all-scopes"] === true) stateArgs.allScopes = true;
30
+ if (typeof args.flags.depth === "string") {
31
+ stateArgs.depth = parseInt(args.flags.depth, 10);
32
+ }
33
+ if (typeof args.flags.lines === "string") {
34
+ stateArgs.lines = parseInt(args.flags.lines, 10);
35
+ }
36
+ if (typeof args.flags.frame === "string") {
37
+ stateArgs.frame = args.flags.frame;
38
+ }
39
+ if (args.flags.generated === true) stateArgs.generated = true;
40
+
41
+ const response = await client.request("state", stateArgs);
42
+
43
+ if (!response.ok) {
44
+ console.error(`${response.error}`);
45
+ if (response.suggestion) console.error(` ${response.suggestion}`);
46
+ return 1;
47
+ }
48
+
49
+ const data = response.data as StateSnapshot;
50
+
51
+ if (args.global.json) {
52
+ console.log(JSON.stringify(data, null, 2));
53
+ return 0;
54
+ }
55
+
56
+ // Non-paused states
57
+ if (data.status !== "paused") {
58
+ const icon = data.status === "running" ? "\u25B6" : "\u25CB";
59
+ console.log(`${icon} ${data.status === "running" ? "Running" : "Idle"}`);
60
+ return 0;
61
+ }
62
+
63
+ // Paused state — header
64
+ const loc = data.location
65
+ ? `${data.location.url}:${data.location.line}${data.location.column !== undefined ? `:${data.location.column}` : ""}`
66
+ : "unknown";
67
+ const reason = data.reason ?? "unknown";
68
+ console.log(`\u23F8 Paused at ${loc} (${reason})`);
69
+
70
+ const showAll = !stateArgs.vars && !stateArgs.stack && !stateArgs.breakpoints && !stateArgs.code;
71
+
72
+ // Source section
73
+ if ((showAll || stateArgs.code) && data.source?.lines) {
74
+ console.log("");
75
+ console.log("Source:");
76
+ const sourceLines: SourceLine[] = data.source.lines.map((l) => ({
77
+ lineNumber: l.line,
78
+ content: l.text,
79
+ isCurrent: l.current,
80
+ currentColumn: l.current ? data.location?.column : undefined,
81
+ }));
82
+ console.log(formatSource(sourceLines));
83
+ }
84
+
85
+ // Locals section
86
+ if ((showAll || stateArgs.vars) && data.locals) {
87
+ console.log("");
88
+ console.log("Locals:");
89
+ const vars: Variable[] = data.locals.map((v) => ({
90
+ ref: v.ref,
91
+ name: v.name,
92
+ value: v.value,
93
+ }));
94
+ const formatted = formatVariables(vars);
95
+ if (formatted) {
96
+ console.log(formatted);
97
+ } else {
98
+ console.log(" (no locals)");
99
+ }
100
+ }
101
+
102
+ // Stack section
103
+ if ((showAll || stateArgs.stack) && data.stack) {
104
+ console.log("");
105
+ console.log("Stack:");
106
+ const frames: StackFrame[] = data.stack.map((f) => ({
107
+ ref: f.ref,
108
+ functionName: f.functionName,
109
+ file: f.file,
110
+ line: f.line,
111
+ column: f.column,
112
+ isAsync: f.isAsync,
113
+ }));
114
+ console.log(formatStack(frames));
115
+ }
116
+
117
+ // Breakpoints section
118
+ if ((showAll || stateArgs.breakpoints) && data.breakpointCount !== undefined) {
119
+ console.log("");
120
+ console.log(`Breakpoints: ${data.breakpointCount} active`);
121
+ }
122
+
123
+ return 0;
124
+ });
@@ -0,0 +1,57 @@
1
+ import { registerCommand } from "../cli/registry.ts";
2
+ import { DaemonClient } from "../daemon/client.ts";
3
+
4
+ registerCommand("status", 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("status");
15
+
16
+ if (!response.ok) {
17
+ console.error(`${response.error}`);
18
+ return 1;
19
+ }
20
+
21
+ const data = response.data as {
22
+ session: string;
23
+ state: string;
24
+ pid?: number;
25
+ wsUrl?: string;
26
+ pauseInfo?: {
27
+ reason: string;
28
+ url?: string;
29
+ line?: number;
30
+ column?: number;
31
+ };
32
+ uptime: number;
33
+ scriptCount: number;
34
+ };
35
+
36
+ if (args.global.json) {
37
+ console.log(JSON.stringify(data, null, 2));
38
+ } else {
39
+ const stateIcon =
40
+ data.state === "paused" ? "Paused" : data.state === "running" ? "Running" : "Idle";
41
+ console.log(`${stateIcon} — Session "${data.session}" — ${data.state}`);
42
+
43
+ if (data.pid) console.log(` PID: ${data.pid}`);
44
+ if (data.wsUrl) console.log(` Inspector: ${data.wsUrl}`);
45
+ console.log(` Uptime: ${Math.round(data.uptime)}s`);
46
+ console.log(` Scripts loaded: ${data.scriptCount}`);
47
+
48
+ if (data.pauseInfo) {
49
+ const loc = data.pauseInfo.url
50
+ ? `${data.pauseInfo.url}:${data.pauseInfo.line}${data.pauseInfo.column !== undefined ? `:${data.pauseInfo.column}` : ""}`
51
+ : "unknown";
52
+ console.log(` Paused: ${data.pauseInfo.reason} at ${loc}`);
53
+ }
54
+ }
55
+
56
+ return 0;
57
+ });
@@ -0,0 +1,50 @@
1
+ import { registerCommand } from "../cli/registry.ts";
2
+ import { DaemonClient } from "../daemon/client.ts";
3
+ import type { SessionStatus } from "../daemon/session.ts";
4
+
5
+ registerCommand("step", async (args) => {
6
+ const session = args.global.session;
7
+
8
+ if (!DaemonClient.isRunning(session)) {
9
+ console.error(`No active session "${session}"`);
10
+ console.error(" -> Try: ndbg launch --brk node app.js");
11
+ return 1;
12
+ }
13
+
14
+ // The subcommand is the step mode: over, into, or out (default: over)
15
+ const validModes = new Set(["over", "into", "out"]);
16
+ const mode = args.subcommand && validModes.has(args.subcommand) ? args.subcommand : "over";
17
+
18
+ const client = new DaemonClient(session);
19
+ const response = await client.request("step", { mode });
20
+
21
+ if (!response.ok) {
22
+ console.error(`${response.error}`);
23
+ if (response.suggestion) console.error(` ${response.suggestion}`);
24
+ return 1;
25
+ }
26
+
27
+ const data = response.data as SessionStatus;
28
+
29
+ if (args.global.json) {
30
+ console.log(JSON.stringify(data, null, 2));
31
+ } else {
32
+ printStatus(data);
33
+ }
34
+
35
+ return 0;
36
+ });
37
+
38
+ function printStatus(data: SessionStatus): void {
39
+ if (data.state === "paused" && data.pauseInfo) {
40
+ const col = data.pauseInfo.column !== undefined ? `:${data.pauseInfo.column + 1}` : "";
41
+ const loc = data.pauseInfo.url
42
+ ? `${data.pauseInfo.url}:${(data.pauseInfo.line ?? 0) + 1}${col}`
43
+ : "unknown";
44
+ console.log(`Paused at ${loc} (${data.pauseInfo.reason})`);
45
+ } else if (data.state === "running") {
46
+ console.log("Running");
47
+ } else {
48
+ console.log(`${data.state}`);
49
+ }
50
+ }
@@ -0,0 +1,27 @@
1
+ import { registerCommand } from "../cli/registry.ts";
2
+ import { DaemonClient } from "../daemon/client.ts";
3
+
4
+ registerCommand("stop", async (args) => {
5
+ const session = args.global.session;
6
+
7
+ if (!DaemonClient.isRunning(session)) {
8
+ console.error(`No active session "${session}"`);
9
+ return 1;
10
+ }
11
+
12
+ const client = new DaemonClient(session);
13
+ const response = await client.request("stop");
14
+
15
+ if (!response.ok) {
16
+ console.error(`${response.error}`);
17
+ return 1;
18
+ }
19
+
20
+ if (args.global.json) {
21
+ console.log(JSON.stringify({ ok: true, session }));
22
+ } else {
23
+ console.log(`Session "${session}" stopped`);
24
+ }
25
+
26
+ return 0;
27
+ });
@@ -0,0 +1,71 @@
1
+ import { registerCommand } from "../cli/registry.ts";
2
+ import { DaemonClient } from "../daemon/client.ts";
3
+ import type { Variable } from "../formatter/variables.ts";
4
+ import { formatVariables } from "../formatter/variables.ts";
5
+
6
+ registerCommand("vars", async (args) => {
7
+ const session = args.global.session;
8
+
9
+ if (!DaemonClient.isRunning(session)) {
10
+ console.error(`No active session "${session}"`);
11
+ console.error(" -> Try: ndbg launch --brk node app.js");
12
+ return 1;
13
+ }
14
+
15
+ // Optional name filter from subcommand + positionals
16
+ const names: string[] = [];
17
+ if (args.subcommand) {
18
+ names.push(args.subcommand);
19
+ }
20
+ names.push(...args.positionals);
21
+
22
+ const varsArgs: Record<string, unknown> = {};
23
+
24
+ if (names.length > 0) {
25
+ varsArgs.names = names;
26
+ }
27
+ if (typeof args.flags.frame === "string") {
28
+ varsArgs.frame = args.flags.frame;
29
+ }
30
+ if (args.flags["all-scopes"] === true) {
31
+ varsArgs.allScopes = true;
32
+ }
33
+
34
+ const client = new DaemonClient(session);
35
+ const response = await client.request("vars", varsArgs);
36
+
37
+ if (!response.ok) {
38
+ console.error(`${response.error}`);
39
+ if (response.suggestion) console.error(` ${response.suggestion}`);
40
+ return 1;
41
+ }
42
+
43
+ const data = response.data as Array<{
44
+ ref: string;
45
+ name: string;
46
+ type: string;
47
+ value: string;
48
+ }>;
49
+
50
+ if (args.global.json) {
51
+ console.log(JSON.stringify(data, null, 2));
52
+ return 0;
53
+ }
54
+
55
+ if (data.length === 0) {
56
+ console.log("(no variables)");
57
+ return 0;
58
+ }
59
+
60
+ const vars: Variable[] = data.map((v) => ({
61
+ ref: v.ref,
62
+ name: v.name,
63
+ value: v.value,
64
+ }));
65
+ const formatted = formatVariables(vars);
66
+ if (formatted) {
67
+ console.log(formatted);
68
+ }
69
+
70
+ return 0;
71
+ });