opencode-debug-agent 0.1.1 → 0.1.2
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/dist/index.js +3 -4
- package/package.json +20 -17
- package/src/agent/debug.md +0 -74
- package/src/command/explain.md +0 -10
- package/src/command/format-check.md +0 -6
- package/src/command/hello.md +0 -5
- package/src/command/quick-review.md +0 -12
- package/src/index.ts +0 -174
- package/src/server.ts +0 -267
- package/src/skill/debug/SKILL.md +0 -60
- package/src/tools.ts +0 -131
package/dist/index.js
CHANGED
|
@@ -13967,7 +13967,7 @@ class DebugServer {
|
|
|
13967
13967
|
};
|
|
13968
13968
|
await this.appendLog(entry);
|
|
13969
13969
|
return c.json({ success: true });
|
|
13970
|
-
} catch
|
|
13970
|
+
} catch {
|
|
13971
13971
|
return c.json({ error: "Invalid JSON" }, 400);
|
|
13972
13972
|
}
|
|
13973
13973
|
});
|
|
@@ -14290,7 +14290,7 @@ var DEBUG_SKILL = {
|
|
|
14290
14290
|
> [Remove instrumentation from lines 42 and 67]
|
|
14291
14291
|
\`\`\``
|
|
14292
14292
|
};
|
|
14293
|
-
var DebugAgentPlugin = async () => {
|
|
14293
|
+
var DebugAgentPlugin = async (ctx) => {
|
|
14294
14294
|
return {
|
|
14295
14295
|
tool: {
|
|
14296
14296
|
debug_start: debugStart,
|
|
@@ -14314,6 +14314,5 @@ var DebugAgentPlugin = async () => {
|
|
|
14314
14314
|
};
|
|
14315
14315
|
var src_default = DebugAgentPlugin;
|
|
14316
14316
|
export {
|
|
14317
|
-
src_default as default
|
|
14318
|
-
DebugAgentPlugin
|
|
14317
|
+
src_default as default
|
|
14319
14318
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-debug-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "OpenCode plugin for runtime debugging - capture and analyze execution data",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "anis00723",
|
|
7
7
|
"email": "anis00723@gmail.com"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
10
12
|
"exports": {
|
|
11
13
|
".": {
|
|
12
14
|
"types": "./dist/index.d.ts",
|
|
@@ -15,34 +17,35 @@
|
|
|
15
17
|
},
|
|
16
18
|
"repository": {
|
|
17
19
|
"type": "git",
|
|
18
|
-
"url": "https://github.com/anis-dr/opencode-debug-agent.git"
|
|
20
|
+
"url": "git+https://github.com/anis-dr/opencode-debug-agent.git"
|
|
19
21
|
},
|
|
20
22
|
"publishConfig": {
|
|
21
23
|
"access": "public"
|
|
22
24
|
},
|
|
23
25
|
"files": [
|
|
24
|
-
"dist"
|
|
25
|
-
"src"
|
|
26
|
+
"dist"
|
|
26
27
|
],
|
|
27
28
|
"scripts": {
|
|
28
|
-
"build": "bun build ./src/index.ts --outdir dist --target bun",
|
|
29
|
+
"build": "bun build ./src/index.ts --outdir dist --target bun --format esm",
|
|
29
30
|
"prepublishOnly": "bun run build"
|
|
30
31
|
},
|
|
32
|
+
"opencode": {
|
|
33
|
+
"type": "plugin"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"opencode",
|
|
37
|
+
"opencode-plugin",
|
|
38
|
+
"debug",
|
|
39
|
+
"debugging",
|
|
40
|
+
"runtime"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
31
43
|
"dependencies": {
|
|
32
|
-
"@opencode-ai/plugin": "1.0.
|
|
44
|
+
"@opencode-ai/plugin": "^1.0.162",
|
|
33
45
|
"hono": "^4"
|
|
34
46
|
},
|
|
35
47
|
"devDependencies": {
|
|
36
|
-
"@
|
|
37
|
-
"
|
|
38
|
-
"@typescript-eslint/eslint-plugin": "8.47.0",
|
|
39
|
-
"@typescript-eslint/parser": "8.47.0",
|
|
40
|
-
"bun-types": "latest",
|
|
41
|
-
"eslint": "^9.39.1",
|
|
42
|
-
"eslint-config-prettier": "10.1.8",
|
|
43
|
-
"eslint-plugin-prettier": "^5.1.3",
|
|
44
|
-
"prettier": "^3.2.4",
|
|
45
|
-
"typescript-eslint": "^8.47.0",
|
|
46
|
-
"vitest": "^3.2.4"
|
|
48
|
+
"@types/bun": "latest",
|
|
49
|
+
"typescript": "^5"
|
|
47
50
|
}
|
|
48
51
|
}
|
package/src/agent/debug.md
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Runtime debugging - capture and analyze execution data
|
|
3
|
-
mode: primary
|
|
4
|
-
tools:
|
|
5
|
-
debug_start: true
|
|
6
|
-
debug_stop: true
|
|
7
|
-
debug_read: true
|
|
8
|
-
debug_clear: true
|
|
9
|
-
debug_status: true
|
|
10
|
-
read: true
|
|
11
|
-
edit: true
|
|
12
|
-
bash: true
|
|
13
|
-
glob: true
|
|
14
|
-
grep: true
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
You are a debugging specialist. Your purpose is to help users debug runtime issues by capturing and analyzing execution data.
|
|
18
|
-
|
|
19
|
-
## IMPORTANT: Port Handling
|
|
20
|
-
- `debug_start` returns a ready-to-use `snippet` with the correct port baked in
|
|
21
|
-
- ALWAYS use the snippet from the tool response - never hardcode ports
|
|
22
|
-
- If you need the current port later, call `debug_status`
|
|
23
|
-
- The server remembers its port across sessions, so existing instrumentations keep working
|
|
24
|
-
|
|
25
|
-
## Workflow
|
|
26
|
-
|
|
27
|
-
1. Call `debug_start` to start the debug server
|
|
28
|
-
2. Use the returned `snippet` to insert fetch() calls at strategic locations in the user's code
|
|
29
|
-
- Replace `LABEL_HERE` with a descriptive label (e.g., "before-api-call", "user-input", "loop-iteration")
|
|
30
|
-
- Replace `YOUR_DATA` with the variables you want to capture
|
|
31
|
-
3. Ask user to reproduce the issue
|
|
32
|
-
4. Call `debug_read` to analyze captured logs
|
|
33
|
-
5. Identify the problem from the runtime data
|
|
34
|
-
6. Call `debug_stop` when done
|
|
35
|
-
7. Remove all fetch() instrumentation calls from the code
|
|
36
|
-
|
|
37
|
-
## If Instrumentations Already Exist
|
|
38
|
-
- Call `debug_status` first to check for existing port
|
|
39
|
-
- Use `grep` to find existing `localhost:\d+/log` patterns in codebase
|
|
40
|
-
- Start server on the same port to avoid breaking existing instrumentations
|
|
41
|
-
|
|
42
|
-
## Example Instrumentation
|
|
43
|
-
|
|
44
|
-
After calling `debug_start`, you'll get a snippet like:
|
|
45
|
-
```javascript
|
|
46
|
-
fetch("http://localhost:54321/log", {
|
|
47
|
-
method: "POST",
|
|
48
|
-
headers: {"Content-Type": "application/json"},
|
|
49
|
-
body: JSON.stringify({label: "LABEL_HERE", data: {YOUR_DATA}})
|
|
50
|
-
})
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Insert this at strategic points:
|
|
54
|
-
```javascript
|
|
55
|
-
// Before an API call
|
|
56
|
-
fetch("http://localhost:54321/log", {
|
|
57
|
-
method: "POST",
|
|
58
|
-
headers: {"Content-Type": "application/json"},
|
|
59
|
-
body: JSON.stringify({label: "pre-api", data: {userId, requestBody}})
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
// After receiving response
|
|
63
|
-
fetch("http://localhost:54321/log", {
|
|
64
|
-
method: "POST",
|
|
65
|
-
headers: {"Content-Type": "application/json"},
|
|
66
|
-
body: JSON.stringify({label: "post-api", data: {response, status}})
|
|
67
|
-
})
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Tips
|
|
71
|
-
- Use descriptive labels to make logs easy to understand
|
|
72
|
-
- Capture relevant variables at each point
|
|
73
|
-
- Add instrumentation around suspected problem areas
|
|
74
|
-
- Compare expected vs actual values in the logs
|
package/src/command/explain.md
DELETED
package/src/command/hello.md
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Quick code review of staged changes
|
|
3
|
-
agent: plan
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
Review the staged git changes and provide:
|
|
7
|
-
|
|
8
|
-
1. A brief summary of what changed
|
|
9
|
-
2. Any potential issues or improvements
|
|
10
|
-
3. Overall assessment (good to merge / needs work)
|
|
11
|
-
|
|
12
|
-
Be concise and actionable.
|
package/src/index.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenCode Debug Agent Plugin
|
|
3
|
-
*
|
|
4
|
-
* Provides runtime debugging capabilities:
|
|
5
|
-
* - Debug HTTP server for capturing execution data
|
|
6
|
-
* - 5 tools: debug_start, debug_stop, debug_read, debug_clear, debug_status
|
|
7
|
-
* - Debug agent (primary) for dedicated debugging sessions
|
|
8
|
-
* - Debug skill for use with any agent
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { Plugin } from '@opencode-ai/plugin';
|
|
12
|
-
import { debugStart, debugStop, debugRead, debugClear, debugStatus } from './tools';
|
|
13
|
-
|
|
14
|
-
// ============================================================
|
|
15
|
-
// EMBEDDED CONTENT (to avoid file loading issues in npm packages)
|
|
16
|
-
// ============================================================
|
|
17
|
-
|
|
18
|
-
const AGENT_PROMPT = `You are a debugging specialist. Your purpose is to help users debug runtime issues by capturing and analyzing execution data.
|
|
19
|
-
|
|
20
|
-
## IMPORTANT: Port Handling
|
|
21
|
-
- \`debug_start\` returns a ready-to-use \`snippet\` with the correct port baked in
|
|
22
|
-
- ALWAYS use the snippet from the tool response - never hardcode ports
|
|
23
|
-
- If you need the current port later, call \`debug_status\`
|
|
24
|
-
- The server remembers its port across sessions, so existing instrumentations keep working
|
|
25
|
-
|
|
26
|
-
## Workflow
|
|
27
|
-
|
|
28
|
-
1. Call \`debug_start\` to start the debug server
|
|
29
|
-
2. Use the returned \`snippet\` to insert fetch() calls at strategic locations in the user's code
|
|
30
|
-
- Replace \`LABEL_HERE\` with a descriptive label (e.g., "before-api-call", "user-input", "loop-iteration")
|
|
31
|
-
- Replace \`YOUR_DATA\` with the variables you want to capture
|
|
32
|
-
3. Ask user to reproduce the issue
|
|
33
|
-
4. Call \`debug_read\` to analyze captured logs
|
|
34
|
-
5. Identify the problem from the runtime data
|
|
35
|
-
6. Call \`debug_stop\` when done
|
|
36
|
-
7. Remove all fetch() instrumentation calls from the code
|
|
37
|
-
|
|
38
|
-
## If Instrumentations Already Exist
|
|
39
|
-
- Call \`debug_status\` first to check for existing port
|
|
40
|
-
- Use \`grep\` to find existing \`localhost:\\d+/log\` patterns in codebase
|
|
41
|
-
- Start server on the same port to avoid breaking existing instrumentations
|
|
42
|
-
|
|
43
|
-
## Example Instrumentation
|
|
44
|
-
|
|
45
|
-
After calling \`debug_start\`, you'll get a snippet like:
|
|
46
|
-
\`\`\`javascript
|
|
47
|
-
fetch("http://localhost:54321/log", {
|
|
48
|
-
method: "POST",
|
|
49
|
-
headers: {"Content-Type": "application/json"},
|
|
50
|
-
body: JSON.stringify({label: "LABEL_HERE", data: {YOUR_DATA}})
|
|
51
|
-
})
|
|
52
|
-
\`\`\`
|
|
53
|
-
|
|
54
|
-
Insert this at strategic points:
|
|
55
|
-
\`\`\`javascript
|
|
56
|
-
// Before an API call
|
|
57
|
-
fetch("http://localhost:54321/log", {
|
|
58
|
-
method: "POST",
|
|
59
|
-
headers: {"Content-Type": "application/json"},
|
|
60
|
-
body: JSON.stringify({label: "pre-api", data: {userId, requestBody}})
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
// After receiving response
|
|
64
|
-
fetch("http://localhost:54321/log", {
|
|
65
|
-
method: "POST",
|
|
66
|
-
headers: {"Content-Type": "application/json"},
|
|
67
|
-
body: JSON.stringify({label: "post-api", data: {response, status}})
|
|
68
|
-
})
|
|
69
|
-
\`\`\`
|
|
70
|
-
|
|
71
|
-
## Tips
|
|
72
|
-
- Use descriptive labels to make logs easy to understand
|
|
73
|
-
- Capture relevant variables at each point
|
|
74
|
-
- Add instrumentation around suspected problem areas
|
|
75
|
-
- Compare expected vs actual values in the logs`;
|
|
76
|
-
|
|
77
|
-
const DEBUG_SKILL = {
|
|
78
|
-
name: 'debug',
|
|
79
|
-
description:
|
|
80
|
-
'Runtime debugging - start a debug server, instrument code with fetch() calls, capture and analyze execution data',
|
|
81
|
-
content: `## CRITICAL: Port Handling
|
|
82
|
-
- \`debug_start\` returns a \`snippet\` with the correct port - ALWAYS use it
|
|
83
|
-
- Never hardcode ports in fetch() calls
|
|
84
|
-
- Call \`debug_status\` to get current port if needed later
|
|
85
|
-
- Server persists port to \`.opencode/debug.port\` so existing instrumentations keep working
|
|
86
|
-
|
|
87
|
-
## Workflow
|
|
88
|
-
|
|
89
|
-
1. \`debug_start\` - Returns {port, url, snippet}
|
|
90
|
-
2. Insert the returned \`snippet\` at strategic code locations:
|
|
91
|
-
- Replace \`LABEL_HERE\` with descriptive label (e.g., "before-api", "after-parse")
|
|
92
|
-
- Replace \`YOUR_DATA\` with variables to capture (e.g., \`{userId, response}\`)
|
|
93
|
-
3. Ask user to reproduce the issue
|
|
94
|
-
4. \`debug_read\` - Analyze captured logs (returns parsed JSON array)
|
|
95
|
-
5. \`debug_stop\` - Stop server when done
|
|
96
|
-
6. Remove all fetch() instrumentation calls from the code
|
|
97
|
-
|
|
98
|
-
## Before Starting - Check for Existing Instrumentations
|
|
99
|
-
1. Call \`debug_status\` - check if server already running or port persisted
|
|
100
|
-
2. Use grep to search for \`localhost:\\d+/log\` patterns in codebase
|
|
101
|
-
3. If found, ensure server starts on same port to avoid breaking existing code
|
|
102
|
-
|
|
103
|
-
## Tools Reference
|
|
104
|
-
|
|
105
|
-
| Tool | Args | Returns |
|
|
106
|
-
|------|------|---------|
|
|
107
|
-
| \`debug_start\` | \`port?\` | \`{port, url, snippet, message}\` |
|
|
108
|
-
| \`debug_stop\` | - | confirmation message |
|
|
109
|
-
| \`debug_read\` | \`tail?\` | \`{entries: [{timestamp, label, data}, ...], count}\` |
|
|
110
|
-
| \`debug_clear\` | - | confirmation message |
|
|
111
|
-
| \`debug_status\` | - | \`{active, port?, url?, persistedPort?, hint?}\` |
|
|
112
|
-
|
|
113
|
-
## Example Session
|
|
114
|
-
|
|
115
|
-
\`\`\`
|
|
116
|
-
> debug_start
|
|
117
|
-
{port: 54321, url: "http://localhost:54321", snippet: "fetch(...)"}
|
|
118
|
-
|
|
119
|
-
> [Insert snippet at line 42 and 67 in user's code]
|
|
120
|
-
|
|
121
|
-
> [User reproduces the issue]
|
|
122
|
-
|
|
123
|
-
> debug_read
|
|
124
|
-
{entries: [
|
|
125
|
-
{timestamp: "...", label: "before-api", data: {userId: 123}},
|
|
126
|
-
{timestamp: "...", label: "after-api", data: {error: "timeout"}}
|
|
127
|
-
], count: 2}
|
|
128
|
-
|
|
129
|
-
> [Analyze: API call is timing out]
|
|
130
|
-
|
|
131
|
-
> debug_stop
|
|
132
|
-
{message: "Debug server stopped."}
|
|
133
|
-
|
|
134
|
-
> [Remove instrumentation from lines 42 and 67]
|
|
135
|
-
\`\`\``,
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
// ============================================================
|
|
139
|
-
// PLUGIN EXPORT
|
|
140
|
-
// ============================================================
|
|
141
|
-
|
|
142
|
-
export const DebugAgentPlugin: Plugin = async () => {
|
|
143
|
-
return {
|
|
144
|
-
// Register debug tools
|
|
145
|
-
tool: {
|
|
146
|
-
debug_start: debugStart,
|
|
147
|
-
debug_stop: debugStop,
|
|
148
|
-
debug_read: debugRead,
|
|
149
|
-
debug_clear: debugClear,
|
|
150
|
-
debug_status: debugStatus,
|
|
151
|
-
},
|
|
152
|
-
|
|
153
|
-
// Config hook to inject agent and skills
|
|
154
|
-
async config(config) {
|
|
155
|
-
// Inject debug agent
|
|
156
|
-
config.agent = config.agent ?? {};
|
|
157
|
-
config.agent['debug'] = {
|
|
158
|
-
description: 'Runtime debugging - capture and analyze execution data',
|
|
159
|
-
mode: 'primary',
|
|
160
|
-
prompt: AGENT_PROMPT,
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// Inject skills (using type assertion as skill may not be in Config type yet)
|
|
164
|
-
const configWithSkill = config as typeof config & {
|
|
165
|
-
skill?: Record<string, { name: string; description: string; content: string }>;
|
|
166
|
-
};
|
|
167
|
-
configWithSkill.skill = configWithSkill.skill ?? {};
|
|
168
|
-
configWithSkill.skill[DEBUG_SKILL.name] = DEBUG_SKILL;
|
|
169
|
-
},
|
|
170
|
-
};
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
// Default export for compatibility
|
|
174
|
-
export default DebugAgentPlugin;
|
package/src/server.ts
DELETED
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Debug Server - Hono-based HTTP server for capturing runtime debug data
|
|
3
|
-
*
|
|
4
|
-
* Features:
|
|
5
|
-
* - CORS enabled for browser instrumentation
|
|
6
|
-
* - Port persistence across sessions
|
|
7
|
-
* - NDJSON log format
|
|
8
|
-
* - Auto-flush with configurable interval
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { Hono } from 'hono';
|
|
12
|
-
import { cors } from 'hono/cors';
|
|
13
|
-
import { mkdir } from 'node:fs/promises';
|
|
14
|
-
import { dirname } from 'node:path';
|
|
15
|
-
|
|
16
|
-
interface LogEntry {
|
|
17
|
-
timestamp: string;
|
|
18
|
-
label: string;
|
|
19
|
-
data: unknown;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface StartResult {
|
|
23
|
-
port: number;
|
|
24
|
-
url: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface ServerInfo {
|
|
28
|
-
active: boolean;
|
|
29
|
-
port?: number;
|
|
30
|
-
url?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
class DebugServer {
|
|
34
|
-
private server: ReturnType<typeof Bun.serve> | null = null;
|
|
35
|
-
private writer: ReturnType<(typeof Bun.file)['prototype']['writer']> | null = null;
|
|
36
|
-
private flushInterval: ReturnType<typeof setInterval> | null = null;
|
|
37
|
-
|
|
38
|
-
private portFile = '.opencode/debug.port';
|
|
39
|
-
private logFile = '.opencode/debug.log';
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Start the debug server
|
|
43
|
-
* Port priority: 1) User-specified → 2) Persisted port → 3) Auto-select
|
|
44
|
-
*/
|
|
45
|
-
async start(port?: number): Promise<StartResult> {
|
|
46
|
-
// If already running, return existing info
|
|
47
|
-
if (this.server) {
|
|
48
|
-
const existingPort = this.server.port ?? 0;
|
|
49
|
-
return {
|
|
50
|
-
port: existingPort,
|
|
51
|
-
url: `http://localhost:${existingPort}`,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Determine target port
|
|
56
|
-
const targetPort = port ?? (await this.loadPersistedPort()) ?? 0;
|
|
57
|
-
|
|
58
|
-
// Create Hono app
|
|
59
|
-
const app = new Hono();
|
|
60
|
-
|
|
61
|
-
// Enable CORS for browser instrumentation
|
|
62
|
-
app.use(
|
|
63
|
-
'/*',
|
|
64
|
-
cors({
|
|
65
|
-
origin: '*',
|
|
66
|
-
allowMethods: ['POST', 'GET', 'OPTIONS'],
|
|
67
|
-
allowHeaders: ['Content-Type'],
|
|
68
|
-
})
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
// POST /log - receive debug data
|
|
72
|
-
app.post('/log', async (c) => {
|
|
73
|
-
try {
|
|
74
|
-
const body = await c.req.json();
|
|
75
|
-
const entry: LogEntry = {
|
|
76
|
-
timestamp: new Date().toISOString(),
|
|
77
|
-
label: body.label ?? 'unknown',
|
|
78
|
-
data: body.data ?? body,
|
|
79
|
-
};
|
|
80
|
-
await this.appendLog(entry);
|
|
81
|
-
return c.json({ success: true });
|
|
82
|
-
} catch (error) {
|
|
83
|
-
return c.json({ error: 'Invalid JSON' }, 400);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
// GET /health - health check
|
|
88
|
-
app.get('/health', (c) => c.text('OK'));
|
|
89
|
-
|
|
90
|
-
// Ensure .opencode directory exists
|
|
91
|
-
await mkdir(dirname(this.logFile), { recursive: true });
|
|
92
|
-
|
|
93
|
-
// Initialize log file writer
|
|
94
|
-
const file = Bun.file(this.logFile);
|
|
95
|
-
this.writer = file.writer({ highWaterMark: 1024 * 8 });
|
|
96
|
-
|
|
97
|
-
// Start server
|
|
98
|
-
this.server = Bun.serve({
|
|
99
|
-
fetch: app.fetch,
|
|
100
|
-
port: targetPort,
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// Get the actual port (Bun may have assigned one if we requested 0)
|
|
104
|
-
const actualPort = this.server.port ?? 0;
|
|
105
|
-
|
|
106
|
-
// Persist the port for future sessions
|
|
107
|
-
await this.persistPort(actualPort);
|
|
108
|
-
|
|
109
|
-
// Auto-flush every 5 seconds
|
|
110
|
-
this.flushInterval = setInterval(() => {
|
|
111
|
-
this.writer?.flush();
|
|
112
|
-
}, 5000);
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
port: actualPort,
|
|
116
|
-
url: `http://localhost:${actualPort}`,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Stop the debug server
|
|
122
|
-
*/
|
|
123
|
-
async stop(): Promise<void> {
|
|
124
|
-
if (this.flushInterval) {
|
|
125
|
-
clearInterval(this.flushInterval);
|
|
126
|
-
this.flushInterval = null;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (this.writer) {
|
|
130
|
-
await this.writer.flush();
|
|
131
|
-
await this.writer.end();
|
|
132
|
-
this.writer = null;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (this.server) {
|
|
136
|
-
this.server.stop();
|
|
137
|
-
this.server = null;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Check if server is running
|
|
143
|
-
*/
|
|
144
|
-
isRunning(): boolean {
|
|
145
|
-
return this.server !== null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Get current server info
|
|
150
|
-
*/
|
|
151
|
-
getInfo(): ServerInfo {
|
|
152
|
-
if (!this.server) {
|
|
153
|
-
return { active: false };
|
|
154
|
-
}
|
|
155
|
-
return {
|
|
156
|
-
active: true,
|
|
157
|
-
port: this.server.port,
|
|
158
|
-
url: `http://localhost:${this.server.port}`,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Get persisted port (for when server is not running)
|
|
164
|
-
*/
|
|
165
|
-
async getPersistedPort(): Promise<number | null> {
|
|
166
|
-
return this.loadPersistedPort();
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Read log entries
|
|
171
|
-
*/
|
|
172
|
-
async readLogs(tail?: number): Promise<LogEntry[]> {
|
|
173
|
-
try {
|
|
174
|
-
// Flush before reading to ensure all data is written
|
|
175
|
-
await this.writer?.flush();
|
|
176
|
-
|
|
177
|
-
const file = Bun.file(this.logFile);
|
|
178
|
-
if (!(await file.exists())) {
|
|
179
|
-
return [];
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const content = await file.text();
|
|
183
|
-
const lines = content.trim().split('\n').filter(Boolean);
|
|
184
|
-
|
|
185
|
-
const entries: LogEntry[] = [];
|
|
186
|
-
for (const line of lines) {
|
|
187
|
-
try {
|
|
188
|
-
entries.push(JSON.parse(line));
|
|
189
|
-
} catch {
|
|
190
|
-
// Skip malformed lines
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (tail && tail > 0) {
|
|
195
|
-
return entries.slice(-tail);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return entries;
|
|
199
|
-
} catch {
|
|
200
|
-
return [];
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Clear log file
|
|
206
|
-
*/
|
|
207
|
-
async clearLogs(): Promise<void> {
|
|
208
|
-
// Stop current writer if exists
|
|
209
|
-
if (this.writer) {
|
|
210
|
-
await this.writer.flush();
|
|
211
|
-
await this.writer.end();
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Truncate the file
|
|
215
|
-
await Bun.write(this.logFile, '');
|
|
216
|
-
|
|
217
|
-
// Restart writer if server is running
|
|
218
|
-
if (this.server) {
|
|
219
|
-
const file = Bun.file(this.logFile);
|
|
220
|
-
this.writer = file.writer({ highWaterMark: 1024 * 8 });
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Append a log entry
|
|
226
|
-
*/
|
|
227
|
-
private async appendLog(entry: LogEntry): Promise<void> {
|
|
228
|
-
if (!this.writer) {
|
|
229
|
-
// Server not running, append directly to file
|
|
230
|
-
await mkdir(dirname(this.logFile), { recursive: true });
|
|
231
|
-
const file = Bun.file(this.logFile);
|
|
232
|
-
const existing = (await file.exists()) ? await file.text() : '';
|
|
233
|
-
await Bun.write(this.logFile, existing + JSON.stringify(entry) + '\n');
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
this.writer.write(JSON.stringify(entry) + '\n');
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Load persisted port from file
|
|
242
|
-
*/
|
|
243
|
-
private async loadPersistedPort(): Promise<number | null> {
|
|
244
|
-
try {
|
|
245
|
-
const file = Bun.file(this.portFile);
|
|
246
|
-
if (!(await file.exists())) {
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
const content = await file.text();
|
|
250
|
-
const port = parseInt(content.trim(), 10);
|
|
251
|
-
return isNaN(port) ? null : port;
|
|
252
|
-
} catch {
|
|
253
|
-
return null;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Persist port to file
|
|
259
|
-
*/
|
|
260
|
-
private async persistPort(port: number): Promise<void> {
|
|
261
|
-
await mkdir(dirname(this.portFile), { recursive: true });
|
|
262
|
-
await Bun.write(this.portFile, String(port));
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Singleton instance
|
|
267
|
-
export const debugServer = new DebugServer();
|
package/src/skill/debug/SKILL.md
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: debug
|
|
3
|
-
description: Runtime debugging - start a debug server, instrument code with fetch() calls, capture and analyze execution data
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## CRITICAL: Port Handling
|
|
7
|
-
- `debug_start` returns a `snippet` with the correct port - ALWAYS use it
|
|
8
|
-
- Never hardcode ports in fetch() calls
|
|
9
|
-
- Call `debug_status` to get current port if needed later
|
|
10
|
-
- Server persists port to `.opencode/debug.port` so existing instrumentations keep working
|
|
11
|
-
|
|
12
|
-
## Workflow
|
|
13
|
-
|
|
14
|
-
1. `debug_start` - Returns {port, url, snippet}
|
|
15
|
-
2. Insert the returned `snippet` at strategic code locations:
|
|
16
|
-
- Replace `LABEL_HERE` with descriptive label (e.g., "before-api", "after-parse")
|
|
17
|
-
- Replace `YOUR_DATA` with variables to capture (e.g., `{userId, response}`)
|
|
18
|
-
3. Ask user to reproduce the issue
|
|
19
|
-
4. `debug_read` - Analyze captured logs (returns parsed JSON array)
|
|
20
|
-
5. `debug_stop` - Stop server when done
|
|
21
|
-
6. Remove all fetch() instrumentation calls from the code
|
|
22
|
-
|
|
23
|
-
## Before Starting - Check for Existing Instrumentations
|
|
24
|
-
1. Call `debug_status` - check if server already running or port persisted
|
|
25
|
-
2. Use grep to search for `localhost:\d+/log` patterns in codebase
|
|
26
|
-
3. If found, ensure server starts on same port to avoid breaking existing code
|
|
27
|
-
|
|
28
|
-
## Tools Reference
|
|
29
|
-
|
|
30
|
-
| Tool | Args | Returns |
|
|
31
|
-
|------|------|---------|
|
|
32
|
-
| `debug_start` | `port?` | `{port, url, snippet, message}` |
|
|
33
|
-
| `debug_stop` | - | confirmation message |
|
|
34
|
-
| `debug_read` | `tail?` | `{entries: [{timestamp, label, data}, ...], count}` |
|
|
35
|
-
| `debug_clear` | - | confirmation message |
|
|
36
|
-
| `debug_status` | - | `{active, port?, url?, persistedPort?, hint?}` |
|
|
37
|
-
|
|
38
|
-
## Example Session
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
> debug_start
|
|
42
|
-
{port: 54321, url: "http://localhost:54321", snippet: "fetch(...)"}
|
|
43
|
-
|
|
44
|
-
> [Insert snippet at line 42 and 67 in user's code]
|
|
45
|
-
|
|
46
|
-
> [User reproduces the issue]
|
|
47
|
-
|
|
48
|
-
> debug_read
|
|
49
|
-
{entries: [
|
|
50
|
-
{timestamp: "...", label: "before-api", data: {userId: 123}},
|
|
51
|
-
{timestamp: "...", label: "after-api", data: {error: "timeout"}}
|
|
52
|
-
], count: 2}
|
|
53
|
-
|
|
54
|
-
> [Analyze: API call is timing out]
|
|
55
|
-
|
|
56
|
-
> debug_stop
|
|
57
|
-
{message: "Debug server stopped."}
|
|
58
|
-
|
|
59
|
-
> [Remove instrumentation from lines 42 and 67]
|
|
60
|
-
```
|
package/src/tools.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Debug Tools - OpenCode tools for runtime debugging workflow
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { tool } from '@opencode-ai/plugin';
|
|
6
|
-
import { debugServer } from './server';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Generate the instrumentation snippet with the given URL
|
|
10
|
-
*/
|
|
11
|
-
function generateSnippet(url: string): string {
|
|
12
|
-
return `fetch("${url}/log", {
|
|
13
|
-
method: "POST",
|
|
14
|
-
headers: {"Content-Type": "application/json"},
|
|
15
|
-
body: JSON.stringify({label: "LABEL_HERE", data: {YOUR_DATA}})
|
|
16
|
-
})`;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Start debug server to capture runtime data
|
|
21
|
-
* Returns port, URL, and ready-to-use code snippet
|
|
22
|
-
*/
|
|
23
|
-
export const debugStart = tool({
|
|
24
|
-
description:
|
|
25
|
-
'Start debug server to capture runtime data. Returns port, URL, and ready-to-use code snippet for instrumentation.',
|
|
26
|
-
args: {
|
|
27
|
-
port: tool.schema
|
|
28
|
-
.number()
|
|
29
|
-
.optional()
|
|
30
|
-
.describe(
|
|
31
|
-
'Specific port to use (optional). If not provided, reuses previous port or auto-selects.'
|
|
32
|
-
),
|
|
33
|
-
},
|
|
34
|
-
async execute(args) {
|
|
35
|
-
const result = await debugServer.start(args.port);
|
|
36
|
-
return JSON.stringify({
|
|
37
|
-
port: result.port,
|
|
38
|
-
url: result.url,
|
|
39
|
-
snippet: generateSnippet(result.url),
|
|
40
|
-
message: `Debug server running on port ${result.port}. Use the snippet to instrument code.`,
|
|
41
|
-
});
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Stop the debug server and flush remaining logs
|
|
47
|
-
*/
|
|
48
|
-
export const debugStop = tool({
|
|
49
|
-
description: 'Stop the debug server and flush remaining logs to disk.',
|
|
50
|
-
args: {},
|
|
51
|
-
async execute() {
|
|
52
|
-
if (!debugServer.isRunning()) {
|
|
53
|
-
return JSON.stringify({ message: 'Debug server is not running.' });
|
|
54
|
-
}
|
|
55
|
-
await debugServer.stop();
|
|
56
|
-
return JSON.stringify({
|
|
57
|
-
message: 'Debug server stopped. Logs preserved in .opencode/debug.log',
|
|
58
|
-
});
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Read captured debug logs
|
|
64
|
-
*/
|
|
65
|
-
export const debugRead = tool({
|
|
66
|
-
description: 'Read captured debug logs. Returns parsed JSON array of log entries.',
|
|
67
|
-
args: {
|
|
68
|
-
tail: tool.schema
|
|
69
|
-
.number()
|
|
70
|
-
.optional()
|
|
71
|
-
.describe('Return only the last N entries. If not provided, returns all entries.'),
|
|
72
|
-
},
|
|
73
|
-
async execute(args) {
|
|
74
|
-
const entries = await debugServer.readLogs(args.tail);
|
|
75
|
-
if (entries.length === 0) {
|
|
76
|
-
return JSON.stringify({
|
|
77
|
-
entries: [],
|
|
78
|
-
message:
|
|
79
|
-
'No log entries found. Make sure the debug server is running and code is instrumented.',
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
return JSON.stringify({
|
|
83
|
-
entries,
|
|
84
|
-
count: entries.length,
|
|
85
|
-
});
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Clear the debug log file
|
|
91
|
-
*/
|
|
92
|
-
export const debugClear = tool({
|
|
93
|
-
description: 'Clear the debug log file.',
|
|
94
|
-
args: {},
|
|
95
|
-
async execute() {
|
|
96
|
-
await debugServer.clearLogs();
|
|
97
|
-
return JSON.stringify({ message: 'Debug log cleared.' });
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Check debug server status and get current port/URL
|
|
103
|
-
*/
|
|
104
|
-
export const debugStatus = tool({
|
|
105
|
-
description:
|
|
106
|
-
'Check if debug server is running and get current port/URL. Also shows persisted port from previous sessions.',
|
|
107
|
-
args: {},
|
|
108
|
-
async execute() {
|
|
109
|
-
const info = debugServer.getInfo();
|
|
110
|
-
|
|
111
|
-
if (info.active && info.url) {
|
|
112
|
-
return JSON.stringify({
|
|
113
|
-
active: true,
|
|
114
|
-
port: info.port,
|
|
115
|
-
url: info.url,
|
|
116
|
-
snippet: generateSnippet(info.url),
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Server not running - check for persisted port
|
|
121
|
-
const persistedPort = await debugServer.getPersistedPort();
|
|
122
|
-
|
|
123
|
-
return JSON.stringify({
|
|
124
|
-
active: false,
|
|
125
|
-
persistedPort,
|
|
126
|
-
hint: persistedPort
|
|
127
|
-
? `Previous session used port ${persistedPort}. Call debug_start to reuse it.`
|
|
128
|
-
: 'No debug server configured. Call debug_start to begin.',
|
|
129
|
-
});
|
|
130
|
-
},
|
|
131
|
-
});
|