opencodekit 0.6.4 → 0.6.5

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
@@ -750,7 +750,7 @@ var cac = (name = "") => new CAC(name);
750
750
  // package.json
751
751
  var package_default = {
752
752
  name: "opencodekit",
753
- version: "0.6.4",
753
+ version: "0.6.5",
754
754
  description: "CLI tool for bootstrapping and managing OpenCodeKit projects",
755
755
  type: "module",
756
756
  repository: {
@@ -136,11 +136,11 @@ Branch: [branch]
136
136
 
137
137
  Saved: .beads/artifacts/<bead-id>/handoffs/<timestamp>.md
138
138
 
139
- Resume: /resume <bead-id>
139
+ ━━━━━━━━━━━━━━━━━━━━━━━━
140
+ 🔄 Press Ctrl+K to compact session with handoff context
141
+ ━━━━━━━━━━━━━━━━━━━━━━━━
142
+
143
+ The handoff plugin will automatically inject this context into the new session.
140
144
 
141
- Next session will auto-load:
142
- - This handoff
143
- - Previous session context (read_session)
144
- - Git state
145
- - Bead artifacts
145
+ Alternatively, resume manually: /resume <bead-id>
146
146
  ```
@@ -1,8 +1,71 @@
1
- import { existsSync } from "node:fs";
2
- import { extname } from "node:path";
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { extname, join } from "node:path";
3
4
  import { BUILTIN_SERVERS, EXT_TO_LANG } from "./constants";
4
5
  import type { LSPServerConfig } from "./types";
5
6
 
7
+ /**
8
+ * Known paths where OpenCode installs bundled LSP servers
9
+ * These are checked when the command isn't found in PATH
10
+ */
11
+ function getOpenCodeDataDir(): string {
12
+ const home = homedir();
13
+ switch (process.platform) {
14
+ case "darwin":
15
+ return join(home, ".local/share/opencode");
16
+ case "win32":
17
+ return join(
18
+ process.env.APPDATA ?? join(home, "AppData/Roaming"),
19
+ "opencode",
20
+ );
21
+ default: // linux
22
+ return join(
23
+ process.env.XDG_DATA_HOME ?? join(home, ".local/share"),
24
+ "opencode",
25
+ );
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Find OpenCode's bundled jdtls launcher jar
31
+ */
32
+ function findOpenCodeJdtls(): string[] | null {
33
+ const jdtlsDir = join(getOpenCodeDataDir(), "bin/jdtls");
34
+ const pluginsDir = join(jdtlsDir, "plugins");
35
+
36
+ if (!existsSync(pluginsDir)) return null;
37
+
38
+ try {
39
+ const files = readdirSync(pluginsDir);
40
+ const launcherJar = files.find(
41
+ (f) =>
42
+ f.startsWith("org.eclipse.equinox.launcher_") && f.endsWith(".jar"),
43
+ );
44
+ if (!launcherJar) return null;
45
+
46
+ const configDir = join(
47
+ jdtlsDir,
48
+ process.platform === "darwin"
49
+ ? "config_mac"
50
+ : process.platform === "win32"
51
+ ? "config_win"
52
+ : "config_linux",
53
+ );
54
+
55
+ if (!existsSync(configDir)) return null;
56
+
57
+ return [
58
+ "java",
59
+ "-jar",
60
+ join(pluginsDir, launcherJar),
61
+ "-configuration",
62
+ configDir,
63
+ ];
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
68
+
6
69
  export interface ResolvedServer extends LSPServerConfig {
7
70
  id: string;
8
71
  }
@@ -58,6 +121,18 @@ export function resolveServer(filePath: string): ResolvedServer | null {
58
121
  }
59
122
  }
60
123
 
124
+ // Special case: Try OpenCode's bundled jdtls for Java files
125
+ if (ext === ".java") {
126
+ const jdtlsCommand = findOpenCodeJdtls();
127
+ if (jdtlsCommand && commandExists("java")) {
128
+ return {
129
+ id: "jdtls",
130
+ command: jdtlsCommand,
131
+ extensions: [".java"],
132
+ };
133
+ }
134
+ }
135
+
61
136
  return null;
62
137
  }
63
138
 
@@ -87,10 +162,17 @@ export function listAvailableServers(): Array<{
87
162
  for (const [id, config] of Object.entries(BUILTIN_SERVERS)) {
88
163
  if (configuredIds.has(id)) continue;
89
164
  const [cmd] = config.command;
165
+
166
+ // Special case: jdtls can use OpenCode's bundled version
167
+ let available = !!cmd && commandExists(cmd);
168
+ if (!available && id === "jdtls") {
169
+ available = !!(findOpenCodeJdtls() && commandExists("java"));
170
+ }
171
+
90
172
  results.push({
91
173
  id,
92
174
  extensions: config.extensions,
93
- available: !!cmd && commandExists(cmd),
175
+ available,
94
176
  });
95
177
  }
96
178
 
@@ -40,20 +40,45 @@ export const DEFAULT_MAX_REFERENCES = 200;
40
40
  export const DEFAULT_MAX_SYMBOLS = 200;
41
41
  export const DEFAULT_MAX_DIAGNOSTICS = 200;
42
42
 
43
- // Synced with OpenCode's server.ts
43
+ /**
44
+ * Built-in LSP servers synced with OpenCode's LSP configuration
45
+ * @see https://opencode.ai/docs/lsp/
46
+ *
47
+ * Servers are matched in order - first match wins.
48
+ * Some servers (jdtls, pyright, etc.) may use OpenCode's bundled installation.
49
+ */
44
50
  export const BUILTIN_SERVERS: Record<string, Omit<LSPServerConfig, "id">> = {
51
+ // TypeScript/JavaScript
45
52
  typescript: {
46
53
  command: ["typescript-language-server", "--stdio"],
47
54
  extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
48
55
  },
56
+ deno: {
57
+ command: ["deno", "lsp"],
58
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs"],
59
+ },
60
+
61
+ // Frontend frameworks
49
62
  vue: {
50
63
  command: ["vue-language-server", "--stdio"],
51
64
  extensions: [".vue"],
52
65
  },
66
+ svelte: {
67
+ command: ["svelteserver", "--stdio"],
68
+ extensions: [".svelte"],
69
+ },
70
+ astro: {
71
+ command: ["astro-ls", "--stdio"],
72
+ extensions: [".astro"],
73
+ },
74
+
75
+ // Go
53
76
  gopls: {
54
77
  command: ["gopls"],
55
78
  extensions: [".go"],
56
79
  },
80
+
81
+ // Python
57
82
  basedpyright: {
58
83
  command: ["basedpyright-langserver", "--stdio"],
59
84
  extensions: [".py", ".pyi"],
@@ -62,10 +87,14 @@ export const BUILTIN_SERVERS: Record<string, Omit<LSPServerConfig, "id">> = {
62
87
  command: ["pyright-langserver", "--stdio"],
63
88
  extensions: [".py", ".pyi"],
64
89
  },
90
+
91
+ // Rust
65
92
  rust: {
66
93
  command: ["rust-analyzer"],
67
94
  extensions: [".rs"],
68
95
  },
96
+
97
+ // C/C++
69
98
  clangd: {
70
99
  command: ["clangd", "--background-index", "--clang-tidy"],
71
100
  extensions: [
@@ -81,26 +110,117 @@ export const BUILTIN_SERVERS: Record<string, Omit<LSPServerConfig, "id">> = {
81
110
  ".h++",
82
111
  ],
83
112
  },
84
- svelte: {
85
- command: ["svelteserver", "--stdio"],
86
- extensions: [".svelte"],
87
- },
88
- astro: {
89
- command: ["astro-ls", "--stdio"],
90
- extensions: [".astro"],
91
- },
113
+
114
+ // Java - OpenCode auto-installs jdtls, requires Java 21+
92
115
  jdtls: {
93
116
  command: ["jdtls"],
94
117
  extensions: [".java"],
95
118
  },
96
- lua: {
119
+
120
+ // C#/F#
121
+ csharp: {
122
+ command: ["OmniSharp", "-lsp"],
123
+ extensions: [".cs"],
124
+ },
125
+ fsharp: {
126
+ command: ["fsautocomplete", "--adaptive-lsp-server-enabled"],
127
+ extensions: [".fs", ".fsi", ".fsx", ".fsscript"],
128
+ },
129
+
130
+ // Ruby
131
+ "ruby-lsp": {
132
+ command: ["ruby-lsp"],
133
+ extensions: [".rb", ".rake", ".gemspec", ".ru"],
134
+ },
135
+
136
+ // PHP
137
+ intelephense: {
138
+ command: ["intelephense", "--stdio"],
139
+ extensions: [".php"],
140
+ },
141
+
142
+ // Lua
143
+ "lua-ls": {
97
144
  command: ["lua-language-server"],
98
145
  extensions: [".lua"],
99
146
  },
147
+
148
+ // Shell
149
+ bash: {
150
+ command: ["bash-language-server", "start"],
151
+ extensions: [".sh", ".bash", ".zsh", ".ksh"],
152
+ },
153
+
154
+ // Swift/Objective-C
155
+ "sourcekit-lsp": {
156
+ command: ["sourcekit-lsp"],
157
+ extensions: [".swift", ".objc", ".objcpp"],
158
+ },
159
+
160
+ // Dart
161
+ dart: {
162
+ command: ["dart", "language-server", "--protocol=lsp"],
163
+ extensions: [".dart"],
164
+ },
165
+
166
+ // Elixir
167
+ "elixir-ls": {
168
+ command: ["elixir-ls"],
169
+ extensions: [".ex", ".exs"],
170
+ },
171
+
172
+ // Gleam
173
+ gleam: {
174
+ command: ["gleam", "lsp"],
175
+ extensions: [".gleam"],
176
+ },
177
+
178
+ // Clojure
179
+ "clojure-lsp": {
180
+ command: ["clojure-lsp"],
181
+ extensions: [".clj", ".cljs", ".cljc", ".edn"],
182
+ },
183
+
184
+ // OCaml
185
+ "ocaml-lsp": {
186
+ command: ["ocamllsp"],
187
+ extensions: [".ml", ".mli"],
188
+ },
189
+
190
+ // Zig
191
+ zls: {
192
+ command: ["zls"],
193
+ extensions: [".zig", ".zon"],
194
+ },
195
+
196
+ // Nix
197
+ nixd: {
198
+ command: ["nixd"],
199
+ extensions: [".nix"],
200
+ },
201
+
202
+ // Terraform
203
+ terraform: {
204
+ command: ["terraform-ls", "serve"],
205
+ extensions: [".tf", ".tfvars"],
206
+ },
207
+
208
+ // Typst
209
+ tinymist: {
210
+ command: ["tinymist", "lsp"],
211
+ extensions: [".typ", ".typc"],
212
+ },
213
+
214
+ // YAML
215
+ "yaml-ls": {
216
+ command: ["yaml-language-server", "--stdio"],
217
+ extensions: [".yaml", ".yml"],
218
+ },
100
219
  };
101
220
 
102
- // Extension to language ID mapping
221
+ // Extension to language ID mapping (used for LSP textDocument/didOpen)
103
222
  export const EXT_TO_LANG: Record<string, string> = {
223
+ // TypeScript/JavaScript
104
224
  ".ts": "typescript",
105
225
  ".tsx": "typescriptreact",
106
226
  ".mts": "typescript",
@@ -111,28 +231,109 @@ export const EXT_TO_LANG: Record<string, string> = {
111
231
  ".cjs": "javascript",
112
232
  ".json": "json",
113
233
  ".jsonc": "jsonc",
234
+
235
+ // Python
114
236
  ".py": "python",
115
237
  ".pyi": "python",
238
+
239
+ // Go
116
240
  ".go": "go",
241
+
242
+ // Rust
117
243
  ".rs": "rust",
244
+
245
+ // C/C++
118
246
  ".c": "c",
119
247
  ".cpp": "cpp",
120
248
  ".cc": "cpp",
121
249
  ".cxx": "cpp",
250
+ ".c++": "cpp",
122
251
  ".h": "c",
123
252
  ".hpp": "cpp",
253
+ ".hh": "cpp",
254
+ ".hxx": "cpp",
255
+ ".h++": "cpp",
256
+
257
+ // Java
124
258
  ".java": "java",
259
+
260
+ // C#/F#
261
+ ".cs": "csharp",
262
+ ".fs": "fsharp",
263
+ ".fsi": "fsharp",
264
+ ".fsx": "fsharp",
265
+ ".fsscript": "fsharp",
266
+
267
+ // Ruby
268
+ ".rb": "ruby",
269
+ ".rake": "ruby",
270
+ ".gemspec": "ruby",
271
+ ".ru": "ruby",
272
+
273
+ // PHP
274
+ ".php": "php",
275
+
276
+ // Lua
125
277
  ".lua": "lua",
278
+
279
+ // Shell
280
+ ".sh": "shellscript",
281
+ ".bash": "shellscript",
282
+ ".zsh": "shellscript",
283
+ ".ksh": "shellscript",
284
+
285
+ // Swift/Objective-C
286
+ ".swift": "swift",
287
+ ".objc": "objective-c",
288
+ ".objcpp": "objective-cpp",
289
+
290
+ // Dart
291
+ ".dart": "dart",
292
+
293
+ // Elixir
294
+ ".ex": "elixir",
295
+ ".exs": "elixir",
296
+
297
+ // Gleam
298
+ ".gleam": "gleam",
299
+
300
+ // Clojure
301
+ ".clj": "clojure",
302
+ ".cljs": "clojurescript",
303
+ ".cljc": "clojure",
304
+ ".edn": "edn",
305
+
306
+ // OCaml
307
+ ".ml": "ocaml",
308
+ ".mli": "ocaml",
309
+
310
+ // Zig
311
+ ".zig": "zig",
312
+ ".zon": "zig",
313
+
314
+ // Nix
315
+ ".nix": "nix",
316
+
317
+ // Terraform
318
+ ".tf": "terraform",
319
+ ".tfvars": "terraform",
320
+
321
+ // Typst
322
+ ".typ": "typst",
323
+ ".typc": "typst",
324
+
325
+ // Frontend frameworks
126
326
  ".vue": "vue",
127
327
  ".svelte": "svelte",
128
328
  ".astro": "astro",
329
+
330
+ // Web
129
331
  ".html": "html",
130
332
  ".css": "css",
131
333
  ".scss": "scss",
132
- ".md": "markdown",
334
+
335
+ // Config/Data
133
336
  ".yaml": "yaml",
134
337
  ".yml": "yaml",
135
- ".sh": "shellscript",
136
- ".bash": "shellscript",
137
- ".zsh": "shellscript",
338
+ ".md": "markdown",
138
339
  };
@@ -0,0 +1,76 @@
1
+ # Handoff: test-handoff-plugin
2
+
3
+ **Created:** 2025-12-27T10:30:00Z
4
+ **Agent:** build
5
+
6
+ ## Provenance
7
+
8
+ | Key | Value |
9
+ | ------ | --------------------------------------------------- |
10
+ | Repo | git@github.com:opencodekit/opencodekit-template.git |
11
+ | Branch | main |
12
+ | Commit | ae38a95f899300419e9b17030268cedc60ebb44f |
13
+ | Clean | no |
14
+
15
+ ## Session Context
16
+
17
+ **Current Session:** testing handoff plugin
18
+ **Token Usage:** ~50k estimated
19
+
20
+ Next session can load context with:
21
+
22
+ ```
23
+ read_session("last", project="current")
24
+ ```
25
+
26
+ ## Progress
27
+
28
+ ### Completed
29
+
30
+ - [x] Released v0.6.4 (version bump, changelog, tag, GitHub release)
31
+ - [x] Reviewed Agent-Skills-for-Context-Engineering repo (verdict: mostly theory, not worth stealing)
32
+ - [x] Reviewed Karpathy's hn-time-capsule (verdict: useful patterns)
33
+ - [x] Created handoff plugin (`plugin/handoff.ts`)
34
+ - [x] Updated `/handoff` command to instruct Ctrl+K
35
+
36
+ ### In Progress
37
+
38
+ - [ ] Testing handoff plugin - verifying it injects context on compaction
39
+
40
+ ### Remaining
41
+
42
+ - [ ] Commit new handoff plugin and updated command
43
+ - [ ] Test full flow: handoff → Ctrl+K → new session with context
44
+
45
+ ## Context
46
+
47
+ ### Key Files
48
+
49
+ - `.opencode/plugin/handoff.ts` - New plugin that injects handoff on compaction
50
+ - `.opencode/command/handoff.md` - Updated to tell user to press Ctrl+K
51
+ - `.opencode/plugin/compactor.ts` - Reference for plugin structure
52
+
53
+ ### Decisions Made
54
+
55
+ - Use `experimental.session.compacting` hook to inject handoff context
56
+ - Only inject handoffs < 1 hour old to prevent stale context
57
+ - Search both `.opencode/memory/handoffs/` and `.beads/artifacts/` for handoffs
58
+
59
+ ### Blockers/Issues
60
+
61
+ - OpenCode lacks `session.start` hook (Issue #5409) - can't auto-navigate to new session
62
+ - Workaround: User must manually press Ctrl+K after /handoff
63
+
64
+ ## Resume Instructions
65
+
66
+ 1. Commit the new handoff plugin and updated command
67
+ 2. Test the full flow: run /handoff, press Ctrl+K, verify context injected
68
+ 3. If working, consider adding to v0.6.5 release
69
+
70
+ **Recommended:** Start next session with `/resume test-handoff-plugin` to auto-load this handoff + previous session context.
71
+
72
+ ## Artifacts
73
+
74
+ - spec.md: missing
75
+ - research.md: missing
76
+ - plan.md: missing
@@ -109,7 +109,8 @@
109
109
  "command_list": ";",
110
110
  "leader": "`",
111
111
  "session_child_cycle": "ctrl+alt+right",
112
- "session_child_cycle_reverse": "ctrl+alt+left"
112
+ "session_child_cycle_reverse": "ctrl+alt+left",
113
+ "session_compact": "ctrl+k"
113
114
  },
114
115
  "mcp": {
115
116
  "Framelink MCP for Figma": {
@@ -169,7 +170,8 @@
169
170
  "opencode-gemini-auth",
170
171
  "@tarquinen/opencode-dcp@latest",
171
172
  "@franlol/opencode-md-table-formatter@0.0.3",
172
- "./plugin/skill.ts"
173
+ "./plugin/skill.ts",
174
+ "./plugin/handoff.ts"
173
175
  ],
174
176
  "provider": {
175
177
  "github-copilot": {
@@ -252,65 +254,62 @@
252
254
  "models": {
253
255
  "gemini-2.5-computer-use-preview-10-2025": {
254
256
  "limit": {
255
- "context": 1048576,
257
+ "context": 880964,
256
258
  "output": 65536
257
259
  },
258
260
  "name": "Gemini 2 5 Computer Use Preview 10 2025"
259
261
  },
260
262
  "gemini-2.5-flash": {
261
263
  "limit": {
262
- "context": 1048576,
264
+ "context": 880964,
263
265
  "output": 65536
264
266
  },
265
267
  "name": "Gemini 2 5 Flash"
266
268
  },
267
269
  "gemini-2.5-flash-lite": {
268
270
  "limit": {
269
- "context": 1048576,
271
+ "context": 880964,
270
272
  "output": 65536
271
273
  },
272
274
  "name": "Gemini 2 5 Flash Lite"
273
275
  },
274
276
  "gemini-2.5-pro": {
275
277
  "limit": {
276
- "context": 1048576,
278
+ "context": 880964,
277
279
  "output": 65536
278
280
  },
279
281
  "name": "Gemini 2 5 Pro"
280
282
  },
281
283
  "gemini-3-flash-preview": {
282
284
  "limit": {
283
- "context": 1048576,
285
+ "context": 880964,
284
286
  "output": 65536
285
287
  },
286
288
  "name": "Gemini 3 Flash Preview"
287
289
  },
288
290
  "gemini-3-pro-image-preview": {
289
291
  "limit": {
290
- "context": 1048576,
292
+ "context": 880964,
291
293
  "output": 65536
292
294
  },
293
295
  "name": "Gemini 3 Pro Image Preview"
294
296
  },
295
297
  "gemini-3-pro-preview": {
296
298
  "limit": {
297
- "context": 1048576,
299
+ "context": 880964,
298
300
  "output": 65536
299
301
  },
300
302
  "name": "Gemini 3 Pro Preview"
301
303
  },
302
304
  "gemini-claude-opus-4-5-thinking": {
303
- "interleaved": {
304
- "field": "reasoning_content"
305
- },
306
305
  "limit": {
307
- "context": 200000,
306
+ "context": 168000,
308
307
  "output": 64000
309
308
  },
310
309
  "name": "Gemini Claude Opus 4 5 Thinking",
311
310
  "options": {
312
311
  "thinking": {
313
- "budget_tokens": 32768,
312
+ "budgetTokens": 32768,
314
313
  "type": "enabled"
315
314
  }
316
315
  },
@@ -318,23 +317,20 @@
318
317
  },
319
318
  "gemini-claude-sonnet-4-5": {
320
319
  "limit": {
321
- "context": 200000,
320
+ "context": 168000,
322
321
  "output": 64000
323
322
  },
324
323
  "name": "Gemini Claude Sonnet 4 5"
325
324
  },
326
325
  "gemini-claude-sonnet-4-5-thinking": {
327
- "interleaved": {
328
- "field": "reasoning_content"
329
- },
330
326
  "limit": {
331
- "context": 200000,
327
+ "context": 168000,
332
328
  "output": 64000
333
329
  },
334
330
  "name": "Gemini Claude Sonnet 4 5 Thinking",
335
331
  "options": {
336
332
  "thinking": {
337
- "budget_tokens": 32768,
333
+ "budgetTokens": 32768,
338
334
  "type": "enabled"
339
335
  }
340
336
  },
@@ -342,162 +338,146 @@
342
338
  },
343
339
  "glm-4.6": {
344
340
  "limit": {
345
- "context": 128000,
341
+ "context": 107520,
346
342
  "output": 16384
347
343
  },
348
344
  "name": "Glm 4 6"
349
345
  },
350
346
  "glm-4.7": {
351
347
  "limit": {
352
- "context": 128000,
348
+ "context": 107520,
353
349
  "output": 16384
354
350
  },
355
351
  "name": "Glm 4 7"
356
352
  },
357
353
  "gpt-5": {
358
354
  "limit": {
359
- "context": 400000,
355
+ "context": 336000,
360
356
  "output": 32768
361
357
  },
362
358
  "name": "Gpt 5",
363
359
  "options": {
364
- "reasoning": {
365
- "effort": "medium"
366
- }
360
+ "reasoningEffort": "medium"
367
361
  },
368
362
  "reasoning": true
369
363
  },
370
364
  "gpt-5-codex": {
371
365
  "limit": {
372
- "context": 400000,
366
+ "context": 336000,
373
367
  "output": 32768
374
368
  },
375
369
  "name": "Gpt 5 Codex",
376
370
  "options": {
377
- "reasoning": {
378
- "effort": "medium"
379
- }
371
+ "reasoningEffort": "medium"
380
372
  },
381
373
  "reasoning": true
382
374
  },
383
375
  "gpt-5-codex-mini": {
384
376
  "limit": {
385
- "context": 400000,
377
+ "context": 336000,
386
378
  "output": 32768
387
379
  },
388
380
  "name": "Gpt 5 Codex Mini",
389
381
  "options": {
390
- "reasoning": {
391
- "effort": "medium"
392
- }
382
+ "reasoningEffort": "medium"
393
383
  },
394
384
  "reasoning": true
395
385
  },
396
386
  "gpt-5.1": {
397
387
  "limit": {
398
- "context": 400000,
388
+ "context": 336000,
399
389
  "output": 32768
400
390
  },
401
391
  "name": "Gpt 5 1",
402
392
  "options": {
403
- "reasoning": {
404
- "effort": "medium"
405
- }
393
+ "reasoningEffort": "medium"
406
394
  },
407
395
  "reasoning": true
408
396
  },
409
397
  "gpt-5.1-codex": {
410
398
  "limit": {
411
- "context": 400000,
399
+ "context": 336000,
412
400
  "output": 32768
413
401
  },
414
402
  "name": "Gpt 5 1 Codex",
415
403
  "options": {
416
- "reasoning": {
417
- "effort": "medium"
418
- }
404
+ "reasoningEffort": "medium"
419
405
  },
420
406
  "reasoning": true
421
407
  },
422
408
  "gpt-5.1-codex-max": {
423
409
  "limit": {
424
- "context": 400000,
410
+ "context": 336000,
425
411
  "output": 32768
426
412
  },
427
413
  "name": "Gpt 5 1 Codex Max",
428
414
  "options": {
429
- "reasoning": {
430
- "effort": "medium"
431
- }
415
+ "reasoningEffort": "medium"
432
416
  },
433
417
  "reasoning": true
434
418
  },
435
419
  "gpt-5.1-codex-mini": {
436
420
  "limit": {
437
- "context": 400000,
421
+ "context": 336000,
438
422
  "output": 32768
439
423
  },
440
424
  "name": "Gpt 5 1 Codex Mini",
441
425
  "options": {
442
- "reasoning": {
443
- "effort": "medium"
444
- }
426
+ "reasoningEffort": "medium"
445
427
  },
446
428
  "reasoning": true
447
429
  },
448
430
  "gpt-5.2": {
449
431
  "limit": {
450
- "context": 400000,
432
+ "context": 336000,
451
433
  "output": 32768
452
434
  },
453
435
  "name": "Gpt 5 2",
454
436
  "options": {
455
- "reasoning": {
456
- "effort": "medium"
457
- }
437
+ "reasoningEffort": "medium"
458
438
  },
459
439
  "reasoning": true
460
440
  },
461
441
  "gpt-5.2-codex": {
462
442
  "limit": {
463
- "context": 400000,
443
+ "context": 336000,
464
444
  "output": 32768
465
445
  },
466
446
  "name": "Gpt 5 2 Codex",
467
447
  "options": {
468
- "reasoning": {
469
- "effort": "medium"
470
- }
448
+ "reasoningEffort": "medium"
471
449
  },
472
450
  "reasoning": true
473
451
  },
474
452
  "gpt-oss-120b-medium": {
475
453
  "limit": {
476
- "context": 128000,
454
+ "context": 107520,
477
455
  "output": 16384
478
456
  },
479
457
  "name": "Gpt Oss 120b Medium"
480
458
  }
481
459
  },
482
460
  "name": "ProxyPal",
483
- "npm": "@ai-sdk/openai-compatible",
461
+ "npm": "@ai-sdk/anthropic",
484
462
  "options": {
485
463
  "apiKey": "proxypal-local",
486
- "baseURL": "http://127.0.0.1:8317/v1"
464
+ "baseURL": "http://127.0.0.1:8317/v1",
465
+ "includeUsage": true
487
466
  }
488
467
  },
489
468
  "zai-coding-plan": {
490
469
  "models": {
491
470
  "glm-4.6": {
492
471
  "attachment": true,
472
+ "limit": {
473
+ "context": 128000,
474
+ "output": 16384
475
+ },
493
476
  "options": {
494
- "reasoningEffort": "high",
495
477
  "temperature": 1,
496
478
  "thinking": {
497
479
  "type": "enabled"
498
- },
499
- "top_k": 40,
500
- "top_p": 0.95
480
+ }
501
481
  },
502
482
  "reasoning": true,
503
483
  "temperature": true,
@@ -506,20 +486,25 @@
506
486
  "glm-4.7": {
507
487
  "id": "glm-4.7",
508
488
  "interleaved": true,
489
+ "limit": {
490
+ "context": 200000,
491
+ "output": 128000
492
+ },
509
493
  "name": "GLM-4.7",
510
494
  "options": {
511
- "maxOutputTokens": 131072,
512
- "reasoningEffort": "high",
513
- "reasoningSummary": "true",
514
495
  "temperature": 1,
515
496
  "thinking": {
516
497
  "type": "enabled"
517
- },
518
- "top_k": 40,
519
- "top_p": 0.95
498
+ }
520
499
  },
521
500
  "reasoning": true
522
501
  }
502
+ },
503
+ "name": "Z.AI Coding Plan",
504
+ "npm": "@ai-sdk/openai-compatible",
505
+ "options": {
506
+ "apiKey": "{env:ZAI_API_KEY}",
507
+ "baseURL": "https://api.z.ai/api/paas/v4"
523
508
  }
524
509
  }
525
510
  },
@@ -11,7 +11,7 @@
11
11
  "author": "",
12
12
  "license": "ISC",
13
13
  "dependencies": {
14
- "@opencode-ai/plugin": "1.0.202"
14
+ "@opencode-ai/plugin": "1.0.207"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@types/node": "^25.0.3",
@@ -0,0 +1,182 @@
1
+ /**
2
+ * OpenCode Handoff Plugin
3
+ * Injects handoff context into session compaction for seamless session transitions
4
+ *
5
+ * Workflow:
6
+ * 1. User runs /handoff command → creates handoff markdown file
7
+ * 2. User presses Ctrl+K to compact session
8
+ * 3. This plugin injects the handoff context into the compaction prompt
9
+ * 4. New session starts with full context from previous session
10
+ */
11
+
12
+ import { existsSync, readFileSync, readdirSync } from "fs";
13
+ import { join } from "path";
14
+ import type { Plugin } from "@opencode-ai/plugin";
15
+
16
+ const HANDOFF_DIRS = [
17
+ ".opencode/memory/handoffs",
18
+ ".beads/artifacts", // Check for bead-specific handoffs
19
+ ] as const;
20
+
21
+ interface HandoffFile {
22
+ path: string;
23
+ mtime: number;
24
+ content: string;
25
+ }
26
+
27
+ function findLatestHandoff(baseDir: string): HandoffFile | null {
28
+ const handoffs: HandoffFile[] = [];
29
+
30
+ for (const dir of HANDOFF_DIRS) {
31
+ const fullPath = join(baseDir, dir);
32
+ if (!existsSync(fullPath)) continue;
33
+
34
+ try {
35
+ const files = findMarkdownFiles(fullPath);
36
+ for (const file of files) {
37
+ try {
38
+ const stat = require("fs").statSync(file);
39
+ const content = readFileSync(file, "utf-8");
40
+
41
+ // Only include files that look like handoffs
42
+ if (
43
+ content.includes("## Resume Instructions") ||
44
+ content.includes("## Progress") ||
45
+ content.includes("# Handoff:")
46
+ ) {
47
+ handoffs.push({
48
+ path: file,
49
+ mtime: stat.mtimeMs,
50
+ content,
51
+ });
52
+ }
53
+ } catch {
54
+ // Skip unreadable files
55
+ }
56
+ }
57
+ } catch {
58
+ // Skip inaccessible directories
59
+ }
60
+ }
61
+
62
+ if (handoffs.length === 0) return null;
63
+
64
+ // Return most recent
65
+ return handoffs.sort((a, b) => b.mtime - a.mtime)[0];
66
+ }
67
+
68
+ function findMarkdownFiles(dir: string, depth = 3): string[] {
69
+ if (depth <= 0) return [];
70
+
71
+ const results: string[] = [];
72
+
73
+ try {
74
+ const entries = readdirSync(dir, { withFileTypes: true });
75
+
76
+ for (const entry of entries) {
77
+ const fullPath = join(dir, entry.name);
78
+
79
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
80
+ results.push(...findMarkdownFiles(fullPath, depth - 1));
81
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
82
+ results.push(fullPath);
83
+ }
84
+ }
85
+ } catch {
86
+ // Skip inaccessible directories
87
+ }
88
+
89
+ return results;
90
+ }
91
+
92
+ function formatHandoffForCompaction(handoff: HandoffFile): string {
93
+ // Extract key sections from handoff
94
+ const content = handoff.content;
95
+
96
+ return `## Previous Session Handoff
97
+
98
+ <handoff-context>
99
+ ${content}
100
+ </handoff-context>
101
+
102
+ **IMPORTANT**: Resume work from where the previous session left off. Check the "Resume Instructions" section above for specific next steps.`;
103
+ }
104
+
105
+ export const HandoffPlugin: Plugin = async ({ client, directory }) => {
106
+ client.app
107
+ .log({
108
+ body: {
109
+ service: "handoff-plugin",
110
+ level: "info",
111
+ message: "🔄 Handoff Plugin loaded - session continuity enabled",
112
+ },
113
+ })
114
+ .catch(() => {});
115
+
116
+ return {
117
+ // Inject handoff context when session is being compacted
118
+ "experimental.session.compacting": async (_input, output) => {
119
+ const handoff = findLatestHandoff(directory);
120
+
121
+ if (!handoff) {
122
+ client.app
123
+ .log({
124
+ body: {
125
+ service: "handoff-plugin",
126
+ level: "debug",
127
+ message: "No handoff found - compacting without handoff context",
128
+ },
129
+ })
130
+ .catch(() => {});
131
+ return;
132
+ }
133
+
134
+ // Check if handoff is recent (within last hour)
135
+ const oneHourAgo = Date.now() - 60 * 60 * 1000;
136
+ const isRecent = handoff.mtime > oneHourAgo;
137
+
138
+ if (!isRecent) {
139
+ client.app
140
+ .log({
141
+ body: {
142
+ service: "handoff-plugin",
143
+ level: "debug",
144
+ message: `Handoff found but stale (${new Date(handoff.mtime).toISOString()}) - skipping injection`,
145
+ },
146
+ })
147
+ .catch(() => {});
148
+ return;
149
+ }
150
+
151
+ // Inject handoff context
152
+ const formattedHandoff = formatHandoffForCompaction(handoff);
153
+ output.context.push(formattedHandoff);
154
+
155
+ client.app
156
+ .log({
157
+ body: {
158
+ service: "handoff-plugin",
159
+ level: "info",
160
+ message: `📋 Injected handoff context from: ${handoff.path}`,
161
+ },
162
+ })
163
+ .catch(() => {});
164
+ },
165
+
166
+ // Log when compaction completes
167
+ event: async ({ event }) => {
168
+ if (event.type === "session.compacted") {
169
+ client.app
170
+ .log({
171
+ body: {
172
+ service: "handoff-plugin",
173
+ level: "info",
174
+ message:
175
+ "✅ Session compacted with handoff context - ready to continue",
176
+ },
177
+ })
178
+ .catch(() => {});
179
+ }
180
+ },
181
+ };
182
+ };
@@ -3,7 +3,9 @@
3
3
  * Semantic code refactoring using Language Server Protocol
4
4
  *
5
5
  * Provides: lsp_rename, lsp_code_actions, lsp_code_action_apply, lsp_organize_imports
6
- * Requires: Language servers installed (e.g., typescript-language-server, pyright, gopls)
6
+ *
7
+ * Uses OpenCode's bundled LSP servers when available, falls back to system-installed servers.
8
+ * @see https://opencode.ai/docs/lsp/
7
9
  */
8
10
 
9
11
  import { resolve } from "node:path";
@@ -55,11 +57,16 @@ IMPORTANT: This tool directly modifies files. Review the changes shown in output
55
57
  if (!server) {
56
58
  return `Error: No LSP server available for file type: ${absPath}
57
59
 
58
- Install a language server:
59
- - TypeScript: npm i -g typescript-language-server typescript
60
- - Python: npm i -g pyright
61
- - Go: go install golang.org/x/tools/gopls@latest
62
- - Rust: rustup component add rust-analyzer`;
60
+ OpenCode supports 30+ languages with built-in LSP servers.
61
+ See https://opencode.ai/docs/lsp/ for the full list.
62
+
63
+ Common servers:
64
+ - TypeScript: Requires 'typescript' in project dependencies
65
+ - Python: Requires 'pyright' dependency installed
66
+ - Go: Requires 'go' command available
67
+ - Rust: Requires 'rust-analyzer' command available
68
+ - Java: Requires Java SDK 21+ installed (OpenCode auto-installs jdtls)
69
+ - C/C++: Requires 'clangd' available (OpenCode auto-installs for C/C++ projects)`;
63
70
  }
64
71
 
65
72
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencodekit",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "CLI tool for bootstrapping and managing OpenCodeKit projects",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,11 +0,0 @@
1
- {
2
- // Ultrathink config for pickle-thinker
3
- // mode: "lite" keeps the original behavior (prefix user prompts only).
4
- // mode: "tool" adds an extra user turn after each tool result to force deeper analysis.
5
- // Note: tool mode increases turns/tokens and may impact subscription limits.
6
- "enabled": true,
7
- // "lite" | "tool"
8
- "mode": "tool",
9
- // Change the thinking keyword if you like
10
- "prefix": "Ultrathink: "
11
- }