opencode-debug-agent 0.1.0 → 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 +122 -68
- 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 -145
- 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
|
@@ -10,9 +10,6 @@ var __export = (target, all) => {
|
|
|
10
10
|
});
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
// src/index.ts
|
|
14
|
-
import path from "path";
|
|
15
|
-
|
|
16
13
|
// node_modules/zod/v4/classic/external.js
|
|
17
14
|
var exports_external = {};
|
|
18
15
|
__export(exports_external, {
|
|
@@ -13970,7 +13967,7 @@ class DebugServer {
|
|
|
13970
13967
|
};
|
|
13971
13968
|
await this.appendLog(entry);
|
|
13972
13969
|
return c.json({ success: true });
|
|
13973
|
-
} catch
|
|
13970
|
+
} catch {
|
|
13974
13971
|
return c.json({ error: "Invalid JSON" }, 400);
|
|
13975
13972
|
}
|
|
13976
13973
|
});
|
|
@@ -14176,60 +14173,124 @@ var debugStatus = tool({
|
|
|
14176
14173
|
});
|
|
14177
14174
|
|
|
14178
14175
|
// src/index.ts
|
|
14179
|
-
|
|
14180
|
-
|
|
14181
|
-
|
|
14182
|
-
|
|
14183
|
-
|
|
14184
|
-
|
|
14185
|
-
|
|
14186
|
-
|
|
14187
|
-
|
|
14188
|
-
|
|
14189
|
-
|
|
14190
|
-
|
|
14191
|
-
|
|
14192
|
-
|
|
14193
|
-
|
|
14194
|
-
|
|
14195
|
-
|
|
14196
|
-
|
|
14197
|
-
|
|
14198
|
-
|
|
14199
|
-
|
|
14200
|
-
|
|
14201
|
-
|
|
14202
|
-
|
|
14203
|
-
|
|
14204
|
-
|
|
14205
|
-
|
|
14206
|
-
|
|
14207
|
-
|
|
14208
|
-
|
|
14209
|
-
|
|
14210
|
-
|
|
14211
|
-
|
|
14212
|
-
|
|
14213
|
-
|
|
14214
|
-
|
|
14215
|
-
|
|
14216
|
-
|
|
14217
|
-
|
|
14218
|
-
|
|
14219
|
-
|
|
14220
|
-
|
|
14221
|
-
|
|
14222
|
-
|
|
14223
|
-
|
|
14224
|
-
|
|
14225
|
-
|
|
14226
|
-
|
|
14227
|
-
|
|
14228
|
-
}
|
|
14229
|
-
}
|
|
14230
|
-
|
|
14231
|
-
|
|
14232
|
-
|
|
14176
|
+
var AGENT_PROMPT = `You are a debugging specialist. Your purpose is to help users debug runtime issues by capturing and analyzing execution data.
|
|
14177
|
+
|
|
14178
|
+
## IMPORTANT: Port Handling
|
|
14179
|
+
- \`debug_start\` returns a ready-to-use \`snippet\` with the correct port baked in
|
|
14180
|
+
- ALWAYS use the snippet from the tool response - never hardcode ports
|
|
14181
|
+
- If you need the current port later, call \`debug_status\`
|
|
14182
|
+
- The server remembers its port across sessions, so existing instrumentations keep working
|
|
14183
|
+
|
|
14184
|
+
## Workflow
|
|
14185
|
+
|
|
14186
|
+
1. Call \`debug_start\` to start the debug server
|
|
14187
|
+
2. Use the returned \`snippet\` to insert fetch() calls at strategic locations in the user's code
|
|
14188
|
+
- Replace \`LABEL_HERE\` with a descriptive label (e.g., "before-api-call", "user-input", "loop-iteration")
|
|
14189
|
+
- Replace \`YOUR_DATA\` with the variables you want to capture
|
|
14190
|
+
3. Ask user to reproduce the issue
|
|
14191
|
+
4. Call \`debug_read\` to analyze captured logs
|
|
14192
|
+
5. Identify the problem from the runtime data
|
|
14193
|
+
6. Call \`debug_stop\` when done
|
|
14194
|
+
7. Remove all fetch() instrumentation calls from the code
|
|
14195
|
+
|
|
14196
|
+
## If Instrumentations Already Exist
|
|
14197
|
+
- Call \`debug_status\` first to check for existing port
|
|
14198
|
+
- Use \`grep\` to find existing \`localhost:\\d+/log\` patterns in codebase
|
|
14199
|
+
- Start server on the same port to avoid breaking existing instrumentations
|
|
14200
|
+
|
|
14201
|
+
## Example Instrumentation
|
|
14202
|
+
|
|
14203
|
+
After calling \`debug_start\`, you'll get a snippet like:
|
|
14204
|
+
\`\`\`javascript
|
|
14205
|
+
fetch("http://localhost:54321/log", {
|
|
14206
|
+
method: "POST",
|
|
14207
|
+
headers: {"Content-Type": "application/json"},
|
|
14208
|
+
body: JSON.stringify({label: "LABEL_HERE", data: {YOUR_DATA}})
|
|
14209
|
+
})
|
|
14210
|
+
\`\`\`
|
|
14211
|
+
|
|
14212
|
+
Insert this at strategic points:
|
|
14213
|
+
\`\`\`javascript
|
|
14214
|
+
// Before an API call
|
|
14215
|
+
fetch("http://localhost:54321/log", {
|
|
14216
|
+
method: "POST",
|
|
14217
|
+
headers: {"Content-Type": "application/json"},
|
|
14218
|
+
body: JSON.stringify({label: "pre-api", data: {userId, requestBody}})
|
|
14219
|
+
})
|
|
14220
|
+
|
|
14221
|
+
// After receiving response
|
|
14222
|
+
fetch("http://localhost:54321/log", {
|
|
14223
|
+
method: "POST",
|
|
14224
|
+
headers: {"Content-Type": "application/json"},
|
|
14225
|
+
body: JSON.stringify({label: "post-api", data: {response, status}})
|
|
14226
|
+
})
|
|
14227
|
+
\`\`\`
|
|
14228
|
+
|
|
14229
|
+
## Tips
|
|
14230
|
+
- Use descriptive labels to make logs easy to understand
|
|
14231
|
+
- Capture relevant variables at each point
|
|
14232
|
+
- Add instrumentation around suspected problem areas
|
|
14233
|
+
- Compare expected vs actual values in the logs`;
|
|
14234
|
+
var DEBUG_SKILL = {
|
|
14235
|
+
name: "debug",
|
|
14236
|
+
description: "Runtime debugging - start a debug server, instrument code with fetch() calls, capture and analyze execution data",
|
|
14237
|
+
content: `## CRITICAL: Port Handling
|
|
14238
|
+
- \`debug_start\` returns a \`snippet\` with the correct port - ALWAYS use it
|
|
14239
|
+
- Never hardcode ports in fetch() calls
|
|
14240
|
+
- Call \`debug_status\` to get current port if needed later
|
|
14241
|
+
- Server persists port to \`.opencode/debug.port\` so existing instrumentations keep working
|
|
14242
|
+
|
|
14243
|
+
## Workflow
|
|
14244
|
+
|
|
14245
|
+
1. \`debug_start\` - Returns {port, url, snippet}
|
|
14246
|
+
2. Insert the returned \`snippet\` at strategic code locations:
|
|
14247
|
+
- Replace \`LABEL_HERE\` with descriptive label (e.g., "before-api", "after-parse")
|
|
14248
|
+
- Replace \`YOUR_DATA\` with variables to capture (e.g., \`{userId, response}\`)
|
|
14249
|
+
3. Ask user to reproduce the issue
|
|
14250
|
+
4. \`debug_read\` - Analyze captured logs (returns parsed JSON array)
|
|
14251
|
+
5. \`debug_stop\` - Stop server when done
|
|
14252
|
+
6. Remove all fetch() instrumentation calls from the code
|
|
14253
|
+
|
|
14254
|
+
## Before Starting - Check for Existing Instrumentations
|
|
14255
|
+
1. Call \`debug_status\` - check if server already running or port persisted
|
|
14256
|
+
2. Use grep to search for \`localhost:\\d+/log\` patterns in codebase
|
|
14257
|
+
3. If found, ensure server starts on same port to avoid breaking existing code
|
|
14258
|
+
|
|
14259
|
+
## Tools Reference
|
|
14260
|
+
|
|
14261
|
+
| Tool | Args | Returns |
|
|
14262
|
+
|------|------|---------|
|
|
14263
|
+
| \`debug_start\` | \`port?\` | \`{port, url, snippet, message}\` |
|
|
14264
|
+
| \`debug_stop\` | - | confirmation message |
|
|
14265
|
+
| \`debug_read\` | \`tail?\` | \`{entries: [{timestamp, label, data}, ...], count}\` |
|
|
14266
|
+
| \`debug_clear\` | - | confirmation message |
|
|
14267
|
+
| \`debug_status\` | - | \`{active, port?, url?, persistedPort?, hint?}\` |
|
|
14268
|
+
|
|
14269
|
+
## Example Session
|
|
14270
|
+
|
|
14271
|
+
\`\`\`
|
|
14272
|
+
> debug_start
|
|
14273
|
+
{port: 54321, url: "http://localhost:54321", snippet: "fetch(...)"}
|
|
14274
|
+
|
|
14275
|
+
> [Insert snippet at line 42 and 67 in user's code]
|
|
14276
|
+
|
|
14277
|
+
> [User reproduces the issue]
|
|
14278
|
+
|
|
14279
|
+
> debug_read
|
|
14280
|
+
{entries: [
|
|
14281
|
+
{timestamp: "...", label: "before-api", data: {userId: 123}},
|
|
14282
|
+
{timestamp: "...", label: "after-api", data: {error: "timeout"}}
|
|
14283
|
+
], count: 2}
|
|
14284
|
+
|
|
14285
|
+
> [Analyze: API call is timing out]
|
|
14286
|
+
|
|
14287
|
+
> debug_stop
|
|
14288
|
+
{message: "Debug server stopped."}
|
|
14289
|
+
|
|
14290
|
+
> [Remove instrumentation from lines 42 and 67]
|
|
14291
|
+
\`\`\``
|
|
14292
|
+
};
|
|
14293
|
+
var DebugAgentPlugin = async (ctx) => {
|
|
14233
14294
|
return {
|
|
14234
14295
|
tool: {
|
|
14235
14296
|
debug_start: debugStart,
|
|
@@ -14243,22 +14304,15 @@ var DebugAgentPlugin = async () => {
|
|
|
14243
14304
|
config2.agent["debug"] = {
|
|
14244
14305
|
description: "Runtime debugging - capture and analyze execution data",
|
|
14245
14306
|
mode: "primary",
|
|
14246
|
-
prompt:
|
|
14307
|
+
prompt: AGENT_PROMPT
|
|
14247
14308
|
};
|
|
14248
14309
|
const configWithSkill = config2;
|
|
14249
14310
|
configWithSkill.skill = configWithSkill.skill ?? {};
|
|
14250
|
-
|
|
14251
|
-
configWithSkill.skill[skill.name] = {
|
|
14252
|
-
name: skill.name,
|
|
14253
|
-
description: skill.description,
|
|
14254
|
-
content: skill.content
|
|
14255
|
-
};
|
|
14256
|
-
}
|
|
14311
|
+
configWithSkill.skill[DEBUG_SKILL.name] = DEBUG_SKILL;
|
|
14257
14312
|
}
|
|
14258
14313
|
};
|
|
14259
14314
|
};
|
|
14260
14315
|
var src_default = DebugAgentPlugin;
|
|
14261
14316
|
export {
|
|
14262
|
-
src_default as default
|
|
14263
|
-
DebugAgentPlugin
|
|
14317
|
+
src_default as default
|
|
14264
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,145 +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 path from 'path';
|
|
13
|
-
import { debugStart, debugStop, debugRead, debugClear, debugStatus } from './tools';
|
|
14
|
-
|
|
15
|
-
// ============================================================
|
|
16
|
-
// SKILL LOADER
|
|
17
|
-
// Loads SKILL.md files from src/skill/ directory
|
|
18
|
-
// ============================================================
|
|
19
|
-
|
|
20
|
-
interface SkillFrontmatter {
|
|
21
|
-
name: string;
|
|
22
|
-
description: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface ParsedSkill {
|
|
26
|
-
name: string;
|
|
27
|
-
description: string;
|
|
28
|
-
content: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Parse YAML frontmatter from a skill file
|
|
33
|
-
*/
|
|
34
|
-
function parseSkillFrontmatter(content: string): { frontmatter: SkillFrontmatter; body: string } {
|
|
35
|
-
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
|
|
36
|
-
const match = content.match(frontmatterRegex);
|
|
37
|
-
|
|
38
|
-
if (!match) {
|
|
39
|
-
return { frontmatter: { name: '', description: '' }, body: content.trim() };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const [, yamlContent, body] = match;
|
|
43
|
-
const frontmatter: SkillFrontmatter = { name: '', description: '' };
|
|
44
|
-
|
|
45
|
-
for (const line of yamlContent.split('\n')) {
|
|
46
|
-
const colonIndex = line.indexOf(':');
|
|
47
|
-
if (colonIndex === -1) continue;
|
|
48
|
-
|
|
49
|
-
const key = line.slice(0, colonIndex).trim();
|
|
50
|
-
const value = line.slice(colonIndex + 1).trim();
|
|
51
|
-
|
|
52
|
-
if (key === 'name') frontmatter.name = value;
|
|
53
|
-
if (key === 'description') frontmatter.description = value;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return { frontmatter, body: body.trim() };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Load all skill files from the skill directory
|
|
61
|
-
*/
|
|
62
|
-
async function loadSkills(): Promise<ParsedSkill[]> {
|
|
63
|
-
const skills: ParsedSkill[] = [];
|
|
64
|
-
const skillDir = path.join(import.meta.dir, 'skill');
|
|
65
|
-
const glob = new Bun.Glob('**/SKILL.md');
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
for await (const file of glob.scan({ cwd: skillDir, absolute: true })) {
|
|
69
|
-
const content = await Bun.file(file).text();
|
|
70
|
-
const { frontmatter, body } = parseSkillFrontmatter(content);
|
|
71
|
-
|
|
72
|
-
if (frontmatter.name) {
|
|
73
|
-
skills.push({
|
|
74
|
-
name: frontmatter.name,
|
|
75
|
-
description: frontmatter.description,
|
|
76
|
-
content: body,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
} catch {
|
|
81
|
-
// Skill directory may not exist yet
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return skills;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Load agent definition from markdown file
|
|
89
|
-
*/
|
|
90
|
-
async function loadAgentPrompt(): Promise<string> {
|
|
91
|
-
try {
|
|
92
|
-
const agentFile = path.join(import.meta.dir, 'agent', 'debug.md');
|
|
93
|
-
const content = await Bun.file(agentFile).text();
|
|
94
|
-
|
|
95
|
-
// Extract body after frontmatter
|
|
96
|
-
const match = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
|
|
97
|
-
return match ? match[1].trim() : content;
|
|
98
|
-
} catch {
|
|
99
|
-
return 'You are a debugging specialist.';
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export const DebugAgentPlugin: Plugin = async () => {
|
|
104
|
-
// Load skills at initialization
|
|
105
|
-
const skills = await loadSkills();
|
|
106
|
-
const agentPrompt = await loadAgentPrompt();
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
// Register debug tools
|
|
110
|
-
tool: {
|
|
111
|
-
debug_start: debugStart,
|
|
112
|
-
debug_stop: debugStop,
|
|
113
|
-
debug_read: debugRead,
|
|
114
|
-
debug_clear: debugClear,
|
|
115
|
-
debug_status: debugStatus,
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
// Config hook to inject agent and skills
|
|
119
|
-
async config(config) {
|
|
120
|
-
// Inject debug agent
|
|
121
|
-
config.agent = config.agent ?? {};
|
|
122
|
-
config.agent['debug'] = {
|
|
123
|
-
description: 'Runtime debugging - capture and analyze execution data',
|
|
124
|
-
mode: 'primary',
|
|
125
|
-
prompt: agentPrompt,
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
// Inject skills (using type assertion as skill may not be in Config type yet)
|
|
129
|
-
const configWithSkill = config as typeof config & {
|
|
130
|
-
skill?: Record<string, { name: string; description: string; content: string }>;
|
|
131
|
-
};
|
|
132
|
-
configWithSkill.skill = configWithSkill.skill ?? {};
|
|
133
|
-
for (const skill of skills) {
|
|
134
|
-
configWithSkill.skill[skill.name] = {
|
|
135
|
-
name: skill.name,
|
|
136
|
-
description: skill.description,
|
|
137
|
-
content: skill.content,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
// Default export for compatibility
|
|
145
|
-
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
|
-
});
|