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,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
+ }