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,53 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { getSocketPath } from "./paths.ts";
|
|
3
|
+
|
|
4
|
+
const POLL_INTERVAL_MS = 50;
|
|
5
|
+
const SPAWN_TIMEOUT_MS = 5000;
|
|
6
|
+
|
|
7
|
+
export async function spawnDaemon(
|
|
8
|
+
session: string,
|
|
9
|
+
options: { port?: number; timeout?: number } = {},
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
const socketPath = getSocketPath(session);
|
|
12
|
+
|
|
13
|
+
// Build the command to spawn ourselves as a daemon.
|
|
14
|
+
// process.execPath is the runtime (bun or compiled binary).
|
|
15
|
+
// process.argv[1] is the script being run (src/main.ts or undefined for compiled).
|
|
16
|
+
const spawnArgs: string[] = [];
|
|
17
|
+
const execPath = process.execPath;
|
|
18
|
+
const scriptPath = process.argv[1];
|
|
19
|
+
|
|
20
|
+
// If argv[1] exists and is a .ts file, we're running via `bun run src/main.ts`
|
|
21
|
+
// Otherwise we're running as a compiled binary
|
|
22
|
+
if (scriptPath && scriptPath.endsWith(".ts")) {
|
|
23
|
+
spawnArgs.push(execPath, "run", scriptPath);
|
|
24
|
+
} else {
|
|
25
|
+
spawnArgs.push(execPath);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
spawnArgs.push("--daemon", session);
|
|
29
|
+
if (options.timeout !== undefined) {
|
|
30
|
+
spawnArgs.push("--timeout", String(options.timeout));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const proc = Bun.spawn(spawnArgs, {
|
|
34
|
+
detached: true,
|
|
35
|
+
stdin: "ignore",
|
|
36
|
+
stdout: "ignore",
|
|
37
|
+
stderr: "ignore",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Unref so the parent process can exit
|
|
41
|
+
proc.unref();
|
|
42
|
+
|
|
43
|
+
// Wait for socket file to appear
|
|
44
|
+
const deadline = Date.now() + SPAWN_TIMEOUT_MS;
|
|
45
|
+
while (Date.now() < deadline) {
|
|
46
|
+
if (existsSync(socketPath)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
await Bun.sleep(POLL_INTERVAL_MS);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
throw new Error(`Daemon for session "${session}" failed to start within ${SPAWN_TIMEOUT_MS}ms`);
|
|
53
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function formatError(message: string, details?: string[], suggestion?: string): string {
|
|
2
|
+
const lines: string[] = [`\u2717 ${message}`];
|
|
3
|
+
|
|
4
|
+
if (details) {
|
|
5
|
+
for (const detail of details) {
|
|
6
|
+
lines.push(` ${detail}`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (suggestion) {
|
|
11
|
+
lines.push(` \u2192 Try: ${suggestion}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return lines.join("\n");
|
|
15
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export interface SourceLine {
|
|
2
|
+
lineNumber: number;
|
|
3
|
+
content: string;
|
|
4
|
+
isCurrent?: boolean;
|
|
5
|
+
currentColumn?: number; // 1-based column on current line
|
|
6
|
+
hasBreakpoint?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const MAX_LINE_WIDTH = 120;
|
|
10
|
+
|
|
11
|
+
/** Trim a long line to a window around the column, adding … on truncated sides. Returns trimmed content and adjusted column offset (0-based). */
|
|
12
|
+
function trimLine(content: string, column?: number): { text: string; caretOffset?: number } {
|
|
13
|
+
const col = column !== undefined ? column - 1 : undefined; // 0-based index
|
|
14
|
+
|
|
15
|
+
if (content.length <= MAX_LINE_WIDTH) {
|
|
16
|
+
return { text: content, caretOffset: col };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const anchor = col ?? 0;
|
|
20
|
+
const half = Math.floor(MAX_LINE_WIDTH / 2);
|
|
21
|
+
|
|
22
|
+
let start = anchor - half;
|
|
23
|
+
let end = anchor + half;
|
|
24
|
+
|
|
25
|
+
if (start < 0) {
|
|
26
|
+
end -= start;
|
|
27
|
+
start = 0;
|
|
28
|
+
}
|
|
29
|
+
if (end > content.length) {
|
|
30
|
+
start -= end - content.length;
|
|
31
|
+
end = content.length;
|
|
32
|
+
if (start < 0) start = 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const hasPrefix = start > 0;
|
|
36
|
+
const hasSuffix = end < content.length;
|
|
37
|
+
const prefix = hasPrefix ? "\u2026" : "";
|
|
38
|
+
const suffix = hasSuffix ? "\u2026" : "";
|
|
39
|
+
const adjustedCaret = col !== undefined ? col - start + (hasPrefix ? 1 : 0) : undefined;
|
|
40
|
+
|
|
41
|
+
return { text: `${prefix}${content.slice(start, end)}${suffix}`, caretOffset: adjustedCaret };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function formatSource(lines: SourceLine[]): string {
|
|
45
|
+
if (lines.length === 0) return "";
|
|
46
|
+
|
|
47
|
+
// Determine the max line number width for alignment
|
|
48
|
+
const maxLineNum = Math.max(...lines.map((l) => l.lineNumber));
|
|
49
|
+
const numWidth = String(maxLineNum).length;
|
|
50
|
+
|
|
51
|
+
const result: string[] = [];
|
|
52
|
+
for (const line of lines) {
|
|
53
|
+
const num = String(line.lineNumber).padStart(numWidth);
|
|
54
|
+
let marker = " ";
|
|
55
|
+
if (line.isCurrent && line.hasBreakpoint) {
|
|
56
|
+
marker = "\u2192\u25CF";
|
|
57
|
+
} else if (line.isCurrent) {
|
|
58
|
+
marker = " \u2192";
|
|
59
|
+
} else if (line.hasBreakpoint) {
|
|
60
|
+
marker = " \u25CF";
|
|
61
|
+
}
|
|
62
|
+
const trimmed = line.isCurrent
|
|
63
|
+
? trimLine(line.content, line.currentColumn)
|
|
64
|
+
: trimLine(line.content);
|
|
65
|
+
result.push(`${marker} ${num}\u2502${trimmed.text}`);
|
|
66
|
+
|
|
67
|
+
// Add column indicator under current line
|
|
68
|
+
if (line.isCurrent && trimmed.caretOffset !== undefined && trimmed.caretOffset >= 0) {
|
|
69
|
+
const gutterWidth = numWidth + 4; // marker(2) + space(1) + numWidth + │(1)
|
|
70
|
+
result.push(`${" ".repeat(gutterWidth)}${" ".repeat(trimmed.caretOffset)}^`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return result.join("\n");
|
|
74
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export interface StackFrame {
|
|
2
|
+
ref: string;
|
|
3
|
+
functionName: string;
|
|
4
|
+
file: string;
|
|
5
|
+
line: number;
|
|
6
|
+
column?: number;
|
|
7
|
+
isAsync?: boolean;
|
|
8
|
+
isBlackboxed?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function formatStack(frames: StackFrame[]): string {
|
|
12
|
+
const outputLines: string[] = [];
|
|
13
|
+
|
|
14
|
+
// First pass: collapse consecutive blackboxed frames and compute column widths
|
|
15
|
+
const segments: (StackFrame | { blackboxedCount: number })[] = [];
|
|
16
|
+
|
|
17
|
+
let i = 0;
|
|
18
|
+
while (i < frames.length) {
|
|
19
|
+
const frame = frames[i];
|
|
20
|
+
if (!frame) break;
|
|
21
|
+
if (frame.isBlackboxed) {
|
|
22
|
+
let count = 0;
|
|
23
|
+
while (i < frames.length && frames[i]?.isBlackboxed) {
|
|
24
|
+
count++;
|
|
25
|
+
i++;
|
|
26
|
+
}
|
|
27
|
+
segments.push({ blackboxedCount: count });
|
|
28
|
+
} else {
|
|
29
|
+
segments.push(frame);
|
|
30
|
+
i++;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Compute column widths from visible frames
|
|
35
|
+
let maxRefLen = 0;
|
|
36
|
+
let maxNameLen = 0;
|
|
37
|
+
for (const seg of segments) {
|
|
38
|
+
if ("ref" in seg) {
|
|
39
|
+
maxRefLen = Math.max(maxRefLen, seg.ref.length);
|
|
40
|
+
maxNameLen = Math.max(maxNameLen, seg.functionName.length);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const seg of segments) {
|
|
45
|
+
if ("blackboxedCount" in seg) {
|
|
46
|
+
const label =
|
|
47
|
+
seg.blackboxedCount === 1
|
|
48
|
+
? "\u250A ... 1 framework frame (blackboxed)"
|
|
49
|
+
: `\u250A ... ${seg.blackboxedCount} framework frames (blackboxed)`;
|
|
50
|
+
outputLines.push(label);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const frame = seg;
|
|
55
|
+
|
|
56
|
+
if (frame.isAsync) {
|
|
57
|
+
outputLines.push("\u250A async gap");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const ref = frame.ref.padEnd(maxRefLen);
|
|
61
|
+
const name = frame.functionName.padEnd(maxNameLen);
|
|
62
|
+
const loc =
|
|
63
|
+
frame.column !== undefined
|
|
64
|
+
? `${frame.file}:${frame.line}:${frame.column}`
|
|
65
|
+
: `${frame.file}:${frame.line}`;
|
|
66
|
+
outputLines.push(`${ref} ${name} ${loc}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return outputLines.join("\n");
|
|
70
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
export interface RemoteObject {
|
|
2
|
+
type: string;
|
|
3
|
+
subtype?: string;
|
|
4
|
+
className?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
value?: unknown;
|
|
7
|
+
objectId?: string;
|
|
8
|
+
preview?: ObjectPreview;
|
|
9
|
+
unserializableValue?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ObjectPreview {
|
|
13
|
+
type: string;
|
|
14
|
+
subtype?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
overflow: boolean;
|
|
17
|
+
properties: PropertyPreview[];
|
|
18
|
+
entries?: EntryPreview[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PropertyPreview {
|
|
22
|
+
name: string;
|
|
23
|
+
type: string;
|
|
24
|
+
value?: string;
|
|
25
|
+
subtype?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface EntryPreview {
|
|
29
|
+
key?: PropertyPreview;
|
|
30
|
+
value: PropertyPreview;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function truncate(str: string, max: number): string {
|
|
34
|
+
if (str.length <= max) return str;
|
|
35
|
+
return `${str.slice(0, max - 3)}...`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function formatPreviewValue(prop: PropertyPreview): string {
|
|
39
|
+
if (prop.subtype === "null") return "null";
|
|
40
|
+
if (prop.type === "string") return `"${prop.value ?? ""}"`;
|
|
41
|
+
if (prop.type === "undefined") return "undefined";
|
|
42
|
+
if (prop.type === "object" || prop.type === "function") {
|
|
43
|
+
return prop.value ?? prop.subtype ?? prop.type;
|
|
44
|
+
}
|
|
45
|
+
return prop.value ?? "undefined";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function formatObjectWithPreview(obj: RemoteObject, maxLen: number): string {
|
|
49
|
+
const preview = obj.preview;
|
|
50
|
+
if (!preview) {
|
|
51
|
+
const className = obj.className ?? "Object";
|
|
52
|
+
return truncate(`${className} {...}`, maxLen);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const className = preview.description ?? obj.className ?? "Object";
|
|
56
|
+
|
|
57
|
+
const props = preview.properties.map((p) => `${p.name}: ${formatPreviewValue(p)}`).join(", ");
|
|
58
|
+
|
|
59
|
+
const suffix = preview.overflow ? ", ..." : "";
|
|
60
|
+
const result = `${className} { ${props}${suffix} }`;
|
|
61
|
+
if (result.length > maxLen) {
|
|
62
|
+
// Try to fit by truncating the props portion
|
|
63
|
+
const prefix = `${className} { `;
|
|
64
|
+
const end = preview.overflow ? ", ... }" : " }";
|
|
65
|
+
const available = maxLen - prefix.length - end.length;
|
|
66
|
+
if (available > 3) {
|
|
67
|
+
return `${prefix}${truncate(props, available)}${end}`;
|
|
68
|
+
}
|
|
69
|
+
return truncate(`${className} {...}`, maxLen);
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function formatArray(obj: RemoteObject, maxLen: number): string {
|
|
75
|
+
const desc = obj.description ?? "Array";
|
|
76
|
+
const preview = obj.preview;
|
|
77
|
+
|
|
78
|
+
if (!preview) {
|
|
79
|
+
return truncate(desc, maxLen);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const items = preview.properties.map((p) => formatPreviewValue(p)).join(", ");
|
|
83
|
+
const suffix = preview.overflow ? ", ..." : "";
|
|
84
|
+
const result = `${desc} [ ${items}${suffix} ]`;
|
|
85
|
+
if (result.length > maxLen) {
|
|
86
|
+
const prefix = `${desc} [ `;
|
|
87
|
+
const end = preview.overflow ? ", ... ]" : " ]";
|
|
88
|
+
const available = maxLen - prefix.length - end.length;
|
|
89
|
+
if (available > 3) {
|
|
90
|
+
return `${prefix}${truncate(items, available)}${end}`;
|
|
91
|
+
}
|
|
92
|
+
return truncate(desc, maxLen);
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function formatFunction(obj: RemoteObject): string {
|
|
98
|
+
const desc = obj.description ?? "";
|
|
99
|
+
// Extract function name and params from description like "function processResult(job) { ... }"
|
|
100
|
+
const match = desc.match(/^(?:async\s+)?(?:function\s*\*?\s*)?(\w*)\s*\(([^)]*)\)/);
|
|
101
|
+
if (match) {
|
|
102
|
+
const name = match[1] || "anonymous";
|
|
103
|
+
const params = match[2] ?? "";
|
|
104
|
+
return `Function ${name}(${params})`;
|
|
105
|
+
}
|
|
106
|
+
// Arrow functions or other forms
|
|
107
|
+
const arrowMatch = desc.match(/^(?:async\s+)?(\w+)\s*=>\s*/);
|
|
108
|
+
if (arrowMatch) {
|
|
109
|
+
return `Function ${arrowMatch[1]}=> ...`;
|
|
110
|
+
}
|
|
111
|
+
const name = obj.className ?? "Function";
|
|
112
|
+
return `Function ${name}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function formatPromise(obj: RemoteObject, maxLen: number): string {
|
|
116
|
+
const preview = obj.preview;
|
|
117
|
+
if (!preview?.properties.length) {
|
|
118
|
+
return truncate("Promise { <pending> }", maxLen);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const status = preview.properties.find((p) => p.name === "[[PromiseState]]");
|
|
122
|
+
const value = preview.properties.find((p) => p.name === "[[PromiseResult]]");
|
|
123
|
+
|
|
124
|
+
if (!status) {
|
|
125
|
+
return truncate("Promise { <pending> }", maxLen);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const state = status.value;
|
|
129
|
+
if (state === "pending") {
|
|
130
|
+
return truncate("Promise { <pending> }", maxLen);
|
|
131
|
+
}
|
|
132
|
+
if (state === "fulfilled") {
|
|
133
|
+
const val = value ? formatPreviewValue(value) : "undefined";
|
|
134
|
+
return truncate(`Promise { <resolved: ${val}> }`, maxLen);
|
|
135
|
+
}
|
|
136
|
+
if (state === "rejected") {
|
|
137
|
+
const val = value ? formatPreviewValue(value) : "undefined";
|
|
138
|
+
return truncate(`Promise { <rejected: ${val}> }`, maxLen);
|
|
139
|
+
}
|
|
140
|
+
return truncate(`Promise { <${state}> }`, maxLen);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function formatError(obj: RemoteObject, maxLen: number): string {
|
|
144
|
+
const desc = obj.description ?? "";
|
|
145
|
+
const lines = desc.split("\n");
|
|
146
|
+
const messageLine = lines[0] ?? "Error";
|
|
147
|
+
// Extract first stack frame location
|
|
148
|
+
const frameLine = lines.find((l) => l.trim().startsWith("at "));
|
|
149
|
+
if (frameLine) {
|
|
150
|
+
const locMatch = frameLine.match(/\(([^)]+)\)/);
|
|
151
|
+
const loc = locMatch ? locMatch[1] : frameLine.replace(/^\s*at\s+/, "");
|
|
152
|
+
return truncate(`${messageLine} (at ${loc})`, maxLen);
|
|
153
|
+
}
|
|
154
|
+
return truncate(messageLine, maxLen);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function formatMap(obj: RemoteObject, maxLen: number): string {
|
|
158
|
+
const desc = obj.description ?? "Map";
|
|
159
|
+
const preview = obj.preview;
|
|
160
|
+
|
|
161
|
+
if (!preview?.entries) {
|
|
162
|
+
return truncate(`${desc} {}`, maxLen);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const items = preview.entries
|
|
166
|
+
.map((e) => {
|
|
167
|
+
const key = e.key ? formatPreviewValue(e.key) : "?";
|
|
168
|
+
const val = formatPreviewValue(e.value);
|
|
169
|
+
return `${key} => ${val}`;
|
|
170
|
+
})
|
|
171
|
+
.join(", ");
|
|
172
|
+
|
|
173
|
+
const suffix = preview.overflow ? ", ..." : "";
|
|
174
|
+
const result = `${desc} { ${items}${suffix} }`;
|
|
175
|
+
return truncate(result, maxLen);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function formatSet(obj: RemoteObject, maxLen: number): string {
|
|
179
|
+
const desc = obj.description ?? "Set";
|
|
180
|
+
const preview = obj.preview;
|
|
181
|
+
|
|
182
|
+
if (!preview?.entries) {
|
|
183
|
+
return truncate(`${desc} {}`, maxLen);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const items = preview.entries.map((e) => formatPreviewValue(e.value)).join(", ");
|
|
187
|
+
const suffix = preview.overflow ? ", ..." : "";
|
|
188
|
+
const result = `${desc} { ${items}${suffix} }`;
|
|
189
|
+
return truncate(result, maxLen);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function formatBuffer(obj: RemoteObject, maxLen: number): string {
|
|
193
|
+
const desc = obj.description ?? "Buffer";
|
|
194
|
+
const preview = obj.preview;
|
|
195
|
+
|
|
196
|
+
if (!preview) {
|
|
197
|
+
return truncate(desc, maxLen);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Preview properties contain the byte values
|
|
201
|
+
const bytes = preview.properties
|
|
202
|
+
.slice(0, 5)
|
|
203
|
+
.map((p) => {
|
|
204
|
+
const num = Number.parseInt(p.value ?? "0", 10);
|
|
205
|
+
return num.toString(16).padStart(2, "0");
|
|
206
|
+
})
|
|
207
|
+
.join(" ");
|
|
208
|
+
|
|
209
|
+
const suffix = preview.overflow ? " ..." : "";
|
|
210
|
+
return truncate(`${desc} <${bytes}${suffix}>`, maxLen);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function formatDate(obj: RemoteObject, maxLen: number): string {
|
|
214
|
+
const desc = obj.description ?? "";
|
|
215
|
+
return truncate(`Date("${desc}")`, maxLen);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function formatRegExp(obj: RemoteObject, maxLen: number): string {
|
|
219
|
+
const desc = obj.description ?? "//";
|
|
220
|
+
return truncate(desc, maxLen);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function formatValue(obj: RemoteObject, maxLen: number = 80): string {
|
|
224
|
+
// Unserializable values (NaN, Infinity, -Infinity, -0, bigint)
|
|
225
|
+
if (obj.unserializableValue !== undefined) {
|
|
226
|
+
return truncate(obj.unserializableValue, maxLen);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Primitives
|
|
230
|
+
if (obj.type === "undefined") return "undefined";
|
|
231
|
+
if (obj.type === "boolean") return String(obj.value);
|
|
232
|
+
if (obj.type === "number") return String(obj.value);
|
|
233
|
+
if (obj.type === "bigint") return truncate(`${obj.value}n`, maxLen);
|
|
234
|
+
if (obj.type === "string") return truncate(`"${obj.value}"`, maxLen);
|
|
235
|
+
if (obj.type === "symbol") return truncate(obj.description ?? "Symbol()", maxLen);
|
|
236
|
+
|
|
237
|
+
// Functions
|
|
238
|
+
if (obj.type === "function") return truncate(formatFunction(obj), maxLen);
|
|
239
|
+
|
|
240
|
+
// Null
|
|
241
|
+
if (obj.subtype === "null") return "null";
|
|
242
|
+
|
|
243
|
+
// Object subtypes
|
|
244
|
+
if (obj.subtype === "array" || obj.subtype === "typedarray") {
|
|
245
|
+
return formatArray(obj, maxLen);
|
|
246
|
+
}
|
|
247
|
+
if (obj.subtype === "regexp") return formatRegExp(obj, maxLen);
|
|
248
|
+
if (obj.subtype === "date") return formatDate(obj, maxLen);
|
|
249
|
+
if (obj.subtype === "map" || obj.subtype === "weakmap") {
|
|
250
|
+
return formatMap(obj, maxLen);
|
|
251
|
+
}
|
|
252
|
+
if (obj.subtype === "set" || obj.subtype === "weakset") {
|
|
253
|
+
return formatSet(obj, maxLen);
|
|
254
|
+
}
|
|
255
|
+
if (obj.subtype === "error") return formatError(obj, maxLen);
|
|
256
|
+
if (obj.subtype === "promise") return formatPromise(obj, maxLen);
|
|
257
|
+
if (obj.subtype === "arraybuffer" || obj.subtype === "dataview") {
|
|
258
|
+
return formatBuffer(obj, maxLen);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Buffer className
|
|
262
|
+
if (obj.className === "Buffer") return formatBuffer(obj, maxLen);
|
|
263
|
+
|
|
264
|
+
// Generic object with preview
|
|
265
|
+
if (obj.type === "object") return formatObjectWithPreview(obj, maxLen);
|
|
266
|
+
|
|
267
|
+
// Fallback
|
|
268
|
+
return truncate(obj.description ?? String(obj.value ?? obj.type), maxLen);
|
|
269
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface Variable {
|
|
2
|
+
ref: string;
|
|
3
|
+
name: string;
|
|
4
|
+
value: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function formatVariables(vars: Variable[]): string {
|
|
8
|
+
if (vars.length === 0) return "";
|
|
9
|
+
|
|
10
|
+
const maxRefLen = Math.max(...vars.map((v) => v.ref.length));
|
|
11
|
+
const maxNameLen = Math.max(...vars.map((v) => v.name.length));
|
|
12
|
+
|
|
13
|
+
return vars
|
|
14
|
+
.map((v) => {
|
|
15
|
+
const ref = v.ref.padEnd(maxRefLen);
|
|
16
|
+
const name = v.name.padEnd(maxNameLen);
|
|
17
|
+
return `${ref} ${name} ${v.value}`;
|
|
18
|
+
})
|
|
19
|
+
.join("\n");
|
|
20
|
+
}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// When spawned as a daemon subprocess, run daemon entry directly
|
|
3
|
+
if (process.argv.includes("--daemon")) {
|
|
4
|
+
await import("./daemon/entry.ts");
|
|
5
|
+
} else {
|
|
6
|
+
await import("./commands/launch.ts");
|
|
7
|
+
await import("./commands/attach.ts");
|
|
8
|
+
await import("./commands/stop.ts");
|
|
9
|
+
await import("./commands/sessions.ts");
|
|
10
|
+
await import("./commands/status.ts");
|
|
11
|
+
await import("./commands/state.ts");
|
|
12
|
+
await import("./commands/continue.ts");
|
|
13
|
+
await import("./commands/step.ts");
|
|
14
|
+
await import("./commands/pause.ts");
|
|
15
|
+
await import("./commands/run-to.ts");
|
|
16
|
+
await import("./commands/break.ts");
|
|
17
|
+
await import("./commands/break-rm.ts");
|
|
18
|
+
await import("./commands/break-ls.ts");
|
|
19
|
+
await import("./commands/logpoint.ts");
|
|
20
|
+
await import("./commands/catch.ts");
|
|
21
|
+
await import("./commands/source.ts");
|
|
22
|
+
await import("./commands/scripts.ts");
|
|
23
|
+
await import("./commands/stack.ts");
|
|
24
|
+
await import("./commands/search.ts");
|
|
25
|
+
await import("./commands/console.ts");
|
|
26
|
+
await import("./commands/exceptions.ts");
|
|
27
|
+
await import("./commands/eval.ts");
|
|
28
|
+
await import("./commands/vars.ts");
|
|
29
|
+
await import("./commands/props.ts");
|
|
30
|
+
await import("./commands/blackbox.ts");
|
|
31
|
+
await import("./commands/blackbox-ls.ts");
|
|
32
|
+
await import("./commands/blackbox-rm.ts");
|
|
33
|
+
await import("./commands/set.ts");
|
|
34
|
+
await import("./commands/set-return.ts");
|
|
35
|
+
await import("./commands/hotpatch.ts");
|
|
36
|
+
await import("./commands/break-toggle.ts");
|
|
37
|
+
await import("./commands/breakable.ts");
|
|
38
|
+
await import("./commands/restart-frame.ts");
|
|
39
|
+
await import("./commands/sourcemap.ts");
|
|
40
|
+
const { parseArgs, run } = await import("./cli/parser.ts");
|
|
41
|
+
|
|
42
|
+
const args = parseArgs(process.argv.slice(2));
|
|
43
|
+
const exitCode = await run(args);
|
|
44
|
+
process.exit(exitCode);
|
|
45
|
+
}
|