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
|
@@ -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
|
+
});
|