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 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 (error45) {
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
- function parseSkillFrontmatter(content) {
14180
- const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
14181
- const match2 = content.match(frontmatterRegex);
14182
- if (!match2) {
14183
- return { frontmatter: { name: "", description: "" }, body: content.trim() };
14184
- }
14185
- const [, yamlContent, body] = match2;
14186
- const frontmatter = { name: "", description: "" };
14187
- for (const line of yamlContent.split(`
14188
- `)) {
14189
- const colonIndex = line.indexOf(":");
14190
- if (colonIndex === -1)
14191
- continue;
14192
- const key = line.slice(0, colonIndex).trim();
14193
- const value = line.slice(colonIndex + 1).trim();
14194
- if (key === "name")
14195
- frontmatter.name = value;
14196
- if (key === "description")
14197
- frontmatter.description = value;
14198
- }
14199
- return { frontmatter, body: body.trim() };
14200
- }
14201
- async function loadSkills() {
14202
- const skills = [];
14203
- const skillDir = path.join(import.meta.dir, "skill");
14204
- const glob = new Bun.Glob("**/SKILL.md");
14205
- try {
14206
- for await (const file2 of glob.scan({ cwd: skillDir, absolute: true })) {
14207
- const content = await Bun.file(file2).text();
14208
- const { frontmatter, body } = parseSkillFrontmatter(content);
14209
- if (frontmatter.name) {
14210
- skills.push({
14211
- name: frontmatter.name,
14212
- description: frontmatter.description,
14213
- content: body
14214
- });
14215
- }
14216
- }
14217
- } catch {}
14218
- return skills;
14219
- }
14220
- async function loadAgentPrompt() {
14221
- try {
14222
- const agentFile = path.join(import.meta.dir, "agent", "debug.md");
14223
- const content = await Bun.file(agentFile).text();
14224
- const match2 = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
14225
- return match2 ? match2[1].trim() : content;
14226
- } catch {
14227
- return "You are a debugging specialist.";
14228
- }
14229
- }
14230
- var DebugAgentPlugin = async () => {
14231
- const skills = await loadSkills();
14232
- const agentPrompt = await loadAgentPrompt();
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: agentPrompt
14307
+ prompt: AGENT_PROMPT
14247
14308
  };
14248
14309
  const configWithSkill = config2;
14249
14310
  configWithSkill.skill = configWithSkill.skill ?? {};
14250
- for (const skill of skills) {
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.0",
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.85",
44
+ "@opencode-ai/plugin": "^1.0.162",
33
45
  "hono": "^4"
34
46
  },
35
47
  "devDependencies": {
36
- "@eslint/js": "^9.39.1",
37
- "@types/node": "^20.11.5",
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
  }
@@ -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
@@ -1,10 +0,0 @@
1
- ---
2
- description: Explain the current file or selection
3
- model: anthropic/claude-sonnet-4-20250514
4
- ---
5
-
6
- Explain the code in the current context:
7
-
8
- - What does it do?
9
- - How does it work?
10
- - Any notable patterns or techniques used?
@@ -1,6 +0,0 @@
1
- ---
2
- description: Check code formatting without blocking
3
- subtask: true
4
- ---
5
-
6
- Run the formatter check and report any files that need formatting.
@@ -1,5 +0,0 @@
1
- ---
2
- description: A friendly greeting command
3
- ---
4
-
5
- Say hello to the user in a friendly and creative way.
@@ -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();
@@ -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
- });