cerebras-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/AGENTS.md +27 -0
  2. package/Dockerfile +10 -0
  3. package/README.md +15 -0
  4. package/bin/opencode +84 -0
  5. package/bunfig.toml +4 -0
  6. package/package.json +128 -0
  7. package/parsers-config.ts +239 -0
  8. package/script/build.ts +151 -0
  9. package/script/postinstall.mjs +122 -0
  10. package/script/publish.ts +256 -0
  11. package/script/schema.ts +47 -0
  12. package/src/acp/README.md +164 -0
  13. package/src/acp/agent.ts +812 -0
  14. package/src/acp/session.ts +70 -0
  15. package/src/acp/types.ts +22 -0
  16. package/src/agent/agent.ts +310 -0
  17. package/src/agent/generate.txt +75 -0
  18. package/src/auth/index.ts +70 -0
  19. package/src/bun/index.ts +152 -0
  20. package/src/bus/global.ts +10 -0
  21. package/src/bus/index.ts +142 -0
  22. package/src/cli/bootstrap.ts +17 -0
  23. package/src/cli/cmd/acp.ts +88 -0
  24. package/src/cli/cmd/agent.ts +165 -0
  25. package/src/cli/cmd/auth.ts +369 -0
  26. package/src/cli/cmd/cmd.ts +7 -0
  27. package/src/cli/cmd/debug/config.ts +15 -0
  28. package/src/cli/cmd/debug/file.ts +91 -0
  29. package/src/cli/cmd/debug/index.ts +41 -0
  30. package/src/cli/cmd/debug/lsp.ts +47 -0
  31. package/src/cli/cmd/debug/ripgrep.ts +83 -0
  32. package/src/cli/cmd/debug/scrap.ts +15 -0
  33. package/src/cli/cmd/debug/snapshot.ts +48 -0
  34. package/src/cli/cmd/export.ts +88 -0
  35. package/src/cli/cmd/generate.ts +38 -0
  36. package/src/cli/cmd/github.ts +1200 -0
  37. package/src/cli/cmd/import.ts +98 -0
  38. package/src/cli/cmd/mcp.ts +400 -0
  39. package/src/cli/cmd/models.ts +77 -0
  40. package/src/cli/cmd/pr.ts +112 -0
  41. package/src/cli/cmd/run.ts +342 -0
  42. package/src/cli/cmd/serve.ts +31 -0
  43. package/src/cli/cmd/session.ts +106 -0
  44. package/src/cli/cmd/stats.ts +298 -0
  45. package/src/cli/cmd/tui/app.tsx +732 -0
  46. package/src/cli/cmd/tui/attach.ts +25 -0
  47. package/src/cli/cmd/tui/component/border.tsx +21 -0
  48. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  49. package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
  50. package/src/cli/cmd/tui/component/dialog-feedback.tsx +160 -0
  51. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  52. package/src/cli/cmd/tui/component/dialog-model.tsx +223 -0
  53. package/src/cli/cmd/tui/component/dialog-notification.tsx +78 -0
  54. package/src/cli/cmd/tui/component/dialog-provider.tsx +222 -0
  55. package/src/cli/cmd/tui/component/dialog-session-list.tsx +97 -0
  56. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  57. package/src/cli/cmd/tui/component/dialog-status.tsx +114 -0
  58. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  59. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  60. package/src/cli/cmd/tui/component/logo.tsx +37 -0
  61. package/src/cli/cmd/tui/component/notification-banner.tsx +58 -0
  62. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +530 -0
  63. package/src/cli/cmd/tui/component/prompt/history.tsx +107 -0
  64. package/src/cli/cmd/tui/component/prompt/index.tsx +931 -0
  65. package/src/cli/cmd/tui/context/args.tsx +14 -0
  66. package/src/cli/cmd/tui/context/directory.ts +12 -0
  67. package/src/cli/cmd/tui/context/exit.tsx +23 -0
  68. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  69. package/src/cli/cmd/tui/context/keybind.tsx +111 -0
  70. package/src/cli/cmd/tui/context/kv.tsx +49 -0
  71. package/src/cli/cmd/tui/context/local.tsx +339 -0
  72. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  73. package/src/cli/cmd/tui/context/route.tsx +45 -0
  74. package/src/cli/cmd/tui/context/sdk.tsx +75 -0
  75. package/src/cli/cmd/tui/context/sync.tsx +374 -0
  76. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  77. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  78. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  79. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  80. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  81. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  82. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  83. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  84. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  85. package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
  86. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  87. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  88. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  89. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  90. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  91. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  92. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  93. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  94. package/src/cli/cmd/tui/context/theme/orng.json +245 -0
  95. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  96. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  97. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  98. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  99. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  100. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  101. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  102. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  103. package/src/cli/cmd/tui/context/theme.tsx +1077 -0
  104. package/src/cli/cmd/tui/event.ts +39 -0
  105. package/src/cli/cmd/tui/routes/home.tsx +104 -0
  106. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +93 -0
  107. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +37 -0
  108. package/src/cli/cmd/tui/routes/session/footer.tsx +76 -0
  109. package/src/cli/cmd/tui/routes/session/header.tsx +183 -0
  110. package/src/cli/cmd/tui/routes/session/index.tsx +1703 -0
  111. package/src/cli/cmd/tui/routes/session/sidebar.tsx +586 -0
  112. package/src/cli/cmd/tui/spawn.ts +60 -0
  113. package/src/cli/cmd/tui/thread.ts +120 -0
  114. package/src/cli/cmd/tui/ui/dialog-alert.tsx +55 -0
  115. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +81 -0
  116. package/src/cli/cmd/tui/ui/dialog-help.tsx +36 -0
  117. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +75 -0
  118. package/src/cli/cmd/tui/ui/dialog-select.tsx +317 -0
  119. package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
  120. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  121. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  122. package/src/cli/cmd/tui/util/clipboard.ts +127 -0
  123. package/src/cli/cmd/tui/util/editor.ts +32 -0
  124. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  125. package/src/cli/cmd/tui/worker.ts +63 -0
  126. package/src/cli/cmd/uninstall.ts +344 -0
  127. package/src/cli/cmd/upgrade.ts +67 -0
  128. package/src/cli/cmd/web.ts +84 -0
  129. package/src/cli/error.ts +55 -0
  130. package/src/cli/ui.ts +84 -0
  131. package/src/cli/upgrade.ts +25 -0
  132. package/src/command/index.ts +79 -0
  133. package/src/command/template/initialize.txt +10 -0
  134. package/src/command/template/review.txt +73 -0
  135. package/src/config/config.ts +886 -0
  136. package/src/config/markdown.ts +41 -0
  137. package/src/env/index.ts +26 -0
  138. package/src/file/fzf.ts +124 -0
  139. package/src/file/ignore.ts +83 -0
  140. package/src/file/index.ts +326 -0
  141. package/src/file/ripgrep.ts +391 -0
  142. package/src/file/time.ts +38 -0
  143. package/src/file/watcher.ts +89 -0
  144. package/src/flag/flag.ts +28 -0
  145. package/src/format/formatter.ts +277 -0
  146. package/src/format/index.ts +137 -0
  147. package/src/global/index.ts +52 -0
  148. package/src/id/id.ts +73 -0
  149. package/src/ide/index.ts +75 -0
  150. package/src/index.ts +158 -0
  151. package/src/installation/index.ts +194 -0
  152. package/src/lsp/client.ts +215 -0
  153. package/src/lsp/index.ts +370 -0
  154. package/src/lsp/language.ts +111 -0
  155. package/src/lsp/server.ts +1327 -0
  156. package/src/mcp/auth.ts +82 -0
  157. package/src/mcp/index.ts +576 -0
  158. package/src/mcp/oauth-callback.ts +203 -0
  159. package/src/mcp/oauth-provider.ts +132 -0
  160. package/src/notification/index.ts +101 -0
  161. package/src/patch/index.ts +622 -0
  162. package/src/permission/index.ts +198 -0
  163. package/src/plugin/index.ts +95 -0
  164. package/src/project/bootstrap.ts +31 -0
  165. package/src/project/instance.ts +68 -0
  166. package/src/project/project.ts +133 -0
  167. package/src/project/state.ts +65 -0
  168. package/src/project/vcs.ts +77 -0
  169. package/src/provider/auth.ts +143 -0
  170. package/src/provider/models-macro.ts +11 -0
  171. package/src/provider/models.ts +93 -0
  172. package/src/provider/provider.ts +996 -0
  173. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  174. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  175. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
  176. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  177. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +27 -0
  178. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  179. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  180. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  181. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
  182. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  183. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  184. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  185. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  186. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  187. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  188. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  189. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  190. package/src/provider/transform.ts +406 -0
  191. package/src/pty/index.ts +226 -0
  192. package/src/ratelimit/index.ts +185 -0
  193. package/src/server/error.ts +36 -0
  194. package/src/server/project.ts +50 -0
  195. package/src/server/server.ts +2463 -0
  196. package/src/server/tui.ts +71 -0
  197. package/src/session/compaction.ts +257 -0
  198. package/src/session/index.ts +470 -0
  199. package/src/session/message-v2.ts +641 -0
  200. package/src/session/message.ts +189 -0
  201. package/src/session/processor.ts +443 -0
  202. package/src/session/prompt/anthropic-20250930.txt +166 -0
  203. package/src/session/prompt/anthropic.txt +105 -0
  204. package/src/session/prompt/anthropic_spoof.txt +1 -0
  205. package/src/session/prompt/beast.txt +147 -0
  206. package/src/session/prompt/build-switch.txt +5 -0
  207. package/src/session/prompt/codex.txt +318 -0
  208. package/src/session/prompt/compaction.txt +12 -0
  209. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  210. package/src/session/prompt/gemini.txt +155 -0
  211. package/src/session/prompt/max-steps.txt +16 -0
  212. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  213. package/src/session/prompt/plan.txt +26 -0
  214. package/src/session/prompt/polaris.txt +107 -0
  215. package/src/session/prompt/qwen.txt +109 -0
  216. package/src/session/prompt/summarize.txt +4 -0
  217. package/src/session/prompt/title.txt +36 -0
  218. package/src/session/prompt.ts +1541 -0
  219. package/src/session/retry.ts +82 -0
  220. package/src/session/revert.ts +108 -0
  221. package/src/session/status.ts +75 -0
  222. package/src/session/summary.ts +203 -0
  223. package/src/session/system.ts +148 -0
  224. package/src/session/todo.ts +36 -0
  225. package/src/share/share-next.ts +195 -0
  226. package/src/share/share.ts +87 -0
  227. package/src/snapshot/index.ts +197 -0
  228. package/src/storage/storage.ts +226 -0
  229. package/src/telemetry/index.ts +232 -0
  230. package/src/tool/bash.ts +365 -0
  231. package/src/tool/bash.txt +128 -0
  232. package/src/tool/batch.ts +173 -0
  233. package/src/tool/batch.txt +28 -0
  234. package/src/tool/codesearch.ts +138 -0
  235. package/src/tool/codesearch.txt +12 -0
  236. package/src/tool/edit.ts +674 -0
  237. package/src/tool/edit.txt +10 -0
  238. package/src/tool/glob.ts +65 -0
  239. package/src/tool/glob.txt +6 -0
  240. package/src/tool/grep.ts +120 -0
  241. package/src/tool/grep.txt +8 -0
  242. package/src/tool/invalid.ts +17 -0
  243. package/src/tool/ls.ts +110 -0
  244. package/src/tool/ls.txt +1 -0
  245. package/src/tool/lsp-diagnostics.ts +26 -0
  246. package/src/tool/lsp-diagnostics.txt +1 -0
  247. package/src/tool/lsp-hover.ts +31 -0
  248. package/src/tool/lsp-hover.txt +1 -0
  249. package/src/tool/multiedit.ts +46 -0
  250. package/src/tool/multiedit.txt +41 -0
  251. package/src/tool/patch.ts +233 -0
  252. package/src/tool/patch.txt +1 -0
  253. package/src/tool/read.ts +217 -0
  254. package/src/tool/read.txt +12 -0
  255. package/src/tool/registry.ts +148 -0
  256. package/src/tool/task.ts +135 -0
  257. package/src/tool/task.txt +60 -0
  258. package/src/tool/todo.ts +39 -0
  259. package/src/tool/todoread.txt +14 -0
  260. package/src/tool/todowrite.txt +167 -0
  261. package/src/tool/tool.ts +66 -0
  262. package/src/tool/webfetch.ts +187 -0
  263. package/src/tool/webfetch.txt +14 -0
  264. package/src/tool/websearch.ts +150 -0
  265. package/src/tool/websearch.txt +11 -0
  266. package/src/tool/write.ts +99 -0
  267. package/src/tool/write.txt +8 -0
  268. package/src/types/shims.d.ts +3 -0
  269. package/src/util/color.ts +19 -0
  270. package/src/util/context.ts +25 -0
  271. package/src/util/defer.ts +12 -0
  272. package/src/util/eventloop.ts +20 -0
  273. package/src/util/filesystem.ts +69 -0
  274. package/src/util/fn.ts +11 -0
  275. package/src/util/iife.ts +3 -0
  276. package/src/util/keybind.ts +79 -0
  277. package/src/util/lazy.ts +11 -0
  278. package/src/util/locale.ts +81 -0
  279. package/src/util/lock.ts +98 -0
  280. package/src/util/log.ts +177 -0
  281. package/src/util/queue.ts +32 -0
  282. package/src/util/rpc.ts +42 -0
  283. package/src/util/scrap.ts +10 -0
  284. package/src/util/signal.ts +12 -0
  285. package/src/util/timeout.ts +14 -0
  286. package/src/util/token.ts +7 -0
  287. package/src/util/wildcard.ts +54 -0
  288. package/sst-env.d.ts +9 -0
  289. package/test/bun.test.ts +53 -0
  290. package/test/config/agent-color.test.ts +66 -0
  291. package/test/config/config.test.ts +503 -0
  292. package/test/config/markdown.test.ts +89 -0
  293. package/test/file/ignore.test.ts +10 -0
  294. package/test/fixture/fixture.ts +28 -0
  295. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  296. package/test/ide/ide.test.ts +82 -0
  297. package/test/keybind.test.ts +317 -0
  298. package/test/lsp/client.test.ts +95 -0
  299. package/test/patch/patch.test.ts +348 -0
  300. package/test/preload.ts +38 -0
  301. package/test/project/project.test.ts +42 -0
  302. package/test/provider/provider.test.ts +1809 -0
  303. package/test/provider/transform.test.ts +305 -0
  304. package/test/session/retry.test.ts +61 -0
  305. package/test/session/session.test.ts +71 -0
  306. package/test/snapshot/snapshot.test.ts +939 -0
  307. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  308. package/test/tool/bash.test.ts +55 -0
  309. package/test/tool/patch.test.ts +259 -0
  310. package/test/util/iife.test.ts +36 -0
  311. package/test/util/lazy.test.ts +50 -0
  312. package/test/util/timeout.test.ts +21 -0
  313. package/test/util/wildcard.test.ts +55 -0
  314. package/tsconfig.json +17 -0
@@ -0,0 +1,1327 @@
1
+ import { spawn, type ChildProcessWithoutNullStreams } from "child_process"
2
+ import path from "path"
3
+ import os from "os"
4
+ import { Global } from "../global"
5
+ import { Log } from "../util/log"
6
+ import { BunProc } from "../bun"
7
+ import { $ } from "bun"
8
+ import fs from "fs/promises"
9
+ import { Filesystem } from "../util/filesystem"
10
+ import { Instance } from "../project/instance"
11
+ import { Flag } from "../flag/flag"
12
+
13
+ export namespace LSPServer {
14
+ const log = Log.create({ service: "lsp.server" })
15
+
16
+ export interface Handle {
17
+ process: ChildProcessWithoutNullStreams
18
+ initialization?: Record<string, any>
19
+ }
20
+
21
+ type RootFunction = (file: string) => Promise<string | undefined>
22
+
23
+ const NearestRoot = (includePatterns: string[], excludePatterns?: string[]): RootFunction => {
24
+ return async (file) => {
25
+ if (excludePatterns) {
26
+ const excludedFiles = Filesystem.up({
27
+ targets: excludePatterns,
28
+ start: path.dirname(file),
29
+ stop: Instance.directory,
30
+ })
31
+ const excluded = await excludedFiles.next()
32
+ await excludedFiles.return()
33
+ if (excluded.value) return undefined
34
+ }
35
+ const files = Filesystem.up({
36
+ targets: includePatterns,
37
+ start: path.dirname(file),
38
+ stop: Instance.directory,
39
+ })
40
+ const first = await files.next()
41
+ await files.return()
42
+ if (!first.value) return Instance.directory
43
+ return path.dirname(first.value)
44
+ }
45
+ }
46
+
47
+ export interface Info {
48
+ id: string
49
+ extensions: string[]
50
+ global?: boolean
51
+ root: RootFunction
52
+ spawn(root: string): Promise<Handle | undefined>
53
+ }
54
+
55
+ export const Deno: Info = {
56
+ id: "deno",
57
+ root: async (file) => {
58
+ const files = Filesystem.up({
59
+ targets: ["deno.json", "deno.jsonc"],
60
+ start: path.dirname(file),
61
+ stop: Instance.directory,
62
+ })
63
+ const first = await files.next()
64
+ await files.return()
65
+ if (!first.value) return undefined
66
+ return path.dirname(first.value)
67
+ },
68
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs"],
69
+ async spawn(root) {
70
+ const deno = Bun.which("deno")
71
+ if (!deno) {
72
+ log.info("deno not found, please install deno first")
73
+ return
74
+ }
75
+ return {
76
+ process: spawn(deno, ["lsp"], {
77
+ cwd: root,
78
+ }),
79
+ }
80
+ },
81
+ }
82
+
83
+ export const Typescript: Info = {
84
+ id: "typescript",
85
+ root: NearestRoot(
86
+ ["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"],
87
+ ["deno.json", "deno.jsonc"],
88
+ ),
89
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
90
+ async spawn(root) {
91
+ const tsserver = await Bun.resolve("typescript/lib/tsserver.js", Instance.directory).catch(() => {})
92
+ log.info("typescript server", { tsserver })
93
+ if (!tsserver) return
94
+ const proc = spawn(BunProc.which(), ["x", "typescript-language-server", "--stdio"], {
95
+ cwd: root,
96
+ env: {
97
+ ...process.env,
98
+ BUN_BE_BUN: "1",
99
+ },
100
+ })
101
+ return {
102
+ process: proc,
103
+ initialization: {
104
+ tsserver: {
105
+ path: tsserver,
106
+ },
107
+ },
108
+ }
109
+ },
110
+ }
111
+
112
+ export const Vue: Info = {
113
+ id: "vue",
114
+ extensions: [".vue"],
115
+ root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]),
116
+ async spawn(root) {
117
+ let binary = Bun.which("vue-language-server")
118
+ const args: string[] = []
119
+ if (!binary) {
120
+ const js = path.join(
121
+ Global.Path.bin,
122
+ "node_modules",
123
+ "@vue",
124
+ "language-server",
125
+ "bin",
126
+ "vue-language-server.js",
127
+ )
128
+ if (!(await Bun.file(js).exists())) {
129
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
130
+ await Bun.spawn([BunProc.which(), "install", "@vue/language-server"], {
131
+ cwd: Global.Path.bin,
132
+ env: {
133
+ ...process.env,
134
+ BUN_BE_BUN: "1",
135
+ },
136
+ stdout: "pipe",
137
+ stderr: "pipe",
138
+ stdin: "pipe",
139
+ }).exited
140
+ }
141
+ binary = BunProc.which()
142
+ args.push("run", js)
143
+ }
144
+ args.push("--stdio")
145
+ const proc = spawn(binary, args, {
146
+ cwd: root,
147
+ env: {
148
+ ...process.env,
149
+ BUN_BE_BUN: "1",
150
+ },
151
+ })
152
+ return {
153
+ process: proc,
154
+ initialization: {
155
+ // Leave empty; the server will auto-detect workspace TypeScript.
156
+ },
157
+ }
158
+ },
159
+ }
160
+
161
+ export const ESLint: Info = {
162
+ id: "eslint",
163
+ root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]),
164
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".vue"],
165
+ async spawn(root) {
166
+ const eslint = await Bun.resolve("eslint", Instance.directory).catch(() => {})
167
+ if (!eslint) return
168
+ log.info("spawning eslint server")
169
+ const serverPath = path.join(Global.Path.bin, "vscode-eslint", "server", "out", "eslintServer.js")
170
+ if (!(await Bun.file(serverPath).exists())) {
171
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
172
+ log.info("downloading and building VS Code ESLint server")
173
+ const response = await fetch("https://github.com/microsoft/vscode-eslint/archive/refs/heads/main.zip")
174
+ if (!response.ok) return
175
+
176
+ const zipPath = path.join(Global.Path.bin, "vscode-eslint.zip")
177
+ await Bun.file(zipPath).write(response)
178
+
179
+ await $`unzip -o -q ${zipPath}`.quiet().cwd(Global.Path.bin).nothrow()
180
+ await fs.rm(zipPath, { force: true })
181
+
182
+ const extractedPath = path.join(Global.Path.bin, "vscode-eslint-main")
183
+ const finalPath = path.join(Global.Path.bin, "vscode-eslint")
184
+
185
+ const stats = await fs.stat(finalPath).catch(() => undefined)
186
+ if (stats) {
187
+ log.info("removing old eslint installation", { path: finalPath })
188
+ await fs.rm(finalPath, { force: true, recursive: true })
189
+ }
190
+ await fs.rename(extractedPath, finalPath)
191
+
192
+ await $`npm install`.cwd(finalPath).quiet()
193
+ await $`npm run compile`.cwd(finalPath).quiet()
194
+
195
+ log.info("installed VS Code ESLint server", { serverPath })
196
+ }
197
+
198
+ const proc = spawn(BunProc.which(), ["--max-old-space-size=8192", serverPath, "--stdio"], {
199
+ cwd: root,
200
+ env: {
201
+ ...process.env,
202
+ BUN_BE_BUN: "1",
203
+ },
204
+ })
205
+
206
+ return {
207
+ process: proc,
208
+ }
209
+ },
210
+ }
211
+
212
+ export const Gopls: Info = {
213
+ id: "gopls",
214
+ root: async (file) => {
215
+ const work = await NearestRoot(["go.work"])(file)
216
+ if (work) return work
217
+ return NearestRoot(["go.mod", "go.sum"])(file)
218
+ },
219
+ extensions: [".go"],
220
+ async spawn(root) {
221
+ let bin = Bun.which("gopls", {
222
+ PATH: process.env["PATH"] + ":" + Global.Path.bin,
223
+ })
224
+ if (!bin) {
225
+ if (!Bun.which("go")) return
226
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
227
+
228
+ log.info("installing gopls")
229
+ const proc = Bun.spawn({
230
+ cmd: ["go", "install", "golang.org/x/tools/gopls@latest"],
231
+ env: { ...process.env, GOBIN: Global.Path.bin },
232
+ stdout: "pipe",
233
+ stderr: "pipe",
234
+ stdin: "pipe",
235
+ })
236
+ const exit = await proc.exited
237
+ if (exit !== 0) {
238
+ log.error("Failed to install gopls")
239
+ return
240
+ }
241
+ bin = path.join(Global.Path.bin, "gopls" + (process.platform === "win32" ? ".exe" : ""))
242
+ log.info(`installed gopls`, {
243
+ bin,
244
+ })
245
+ }
246
+ return {
247
+ process: spawn(bin!, {
248
+ cwd: root,
249
+ }),
250
+ }
251
+ },
252
+ }
253
+
254
+ export const Rubocop: Info = {
255
+ id: "ruby-lsp",
256
+ root: NearestRoot(["Gemfile"]),
257
+ extensions: [".rb", ".rake", ".gemspec", ".ru"],
258
+ async spawn(root) {
259
+ let bin = Bun.which("rubocop", {
260
+ PATH: process.env["PATH"] + ":" + Global.Path.bin,
261
+ })
262
+ if (!bin) {
263
+ const ruby = Bun.which("ruby")
264
+ const gem = Bun.which("gem")
265
+ if (!ruby || !gem) {
266
+ log.info("Ruby not found, please install Ruby first")
267
+ return
268
+ }
269
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
270
+ log.info("installing rubocop")
271
+ const proc = Bun.spawn({
272
+ cmd: ["gem", "install", "rubocop", "--bindir", Global.Path.bin],
273
+ stdout: "pipe",
274
+ stderr: "pipe",
275
+ stdin: "pipe",
276
+ })
277
+ const exit = await proc.exited
278
+ if (exit !== 0) {
279
+ log.error("Failed to install rubocop")
280
+ return
281
+ }
282
+ bin = path.join(Global.Path.bin, "rubocop" + (process.platform === "win32" ? ".exe" : ""))
283
+ log.info(`installed rubocop`, {
284
+ bin,
285
+ })
286
+ }
287
+ return {
288
+ process: spawn(bin!, ["--lsp"], {
289
+ cwd: root,
290
+ }),
291
+ }
292
+ },
293
+ }
294
+
295
+ export const Pyright: Info = {
296
+ id: "pyright",
297
+ extensions: [".py", ".pyi"],
298
+ root: NearestRoot(["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json"]),
299
+ async spawn(root) {
300
+ let binary = Bun.which("pyright-langserver")
301
+ const args = []
302
+ if (!binary) {
303
+ const js = path.join(Global.Path.bin, "node_modules", "pyright", "dist", "pyright-langserver.js")
304
+ if (!(await Bun.file(js).exists())) {
305
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
306
+ await Bun.spawn([BunProc.which(), "install", "pyright"], {
307
+ cwd: Global.Path.bin,
308
+ env: {
309
+ ...process.env,
310
+ BUN_BE_BUN: "1",
311
+ },
312
+ }).exited
313
+ }
314
+ binary = BunProc.which()
315
+ args.push(...["run", js])
316
+ }
317
+ args.push("--stdio")
318
+
319
+ const initialization: Record<string, string> = {}
320
+
321
+ const potentialVenvPaths = [process.env["VIRTUAL_ENV"], path.join(root, ".venv"), path.join(root, "venv")].filter(
322
+ (p): p is string => p !== undefined,
323
+ )
324
+ for (const venvPath of potentialVenvPaths) {
325
+ const isWindows = process.platform === "win32"
326
+ const potentialPythonPath = isWindows
327
+ ? path.join(venvPath, "Scripts", "python.exe")
328
+ : path.join(venvPath, "bin", "python")
329
+ if (await Bun.file(potentialPythonPath).exists()) {
330
+ initialization["pythonPath"] = potentialPythonPath
331
+ break
332
+ }
333
+ }
334
+
335
+ const proc = spawn(binary, args, {
336
+ cwd: root,
337
+ env: {
338
+ ...process.env,
339
+ BUN_BE_BUN: "1",
340
+ },
341
+ })
342
+ return {
343
+ process: proc,
344
+ initialization,
345
+ }
346
+ },
347
+ }
348
+
349
+ export const ElixirLS: Info = {
350
+ id: "elixir-ls",
351
+ extensions: [".ex", ".exs"],
352
+ root: NearestRoot(["mix.exs", "mix.lock"]),
353
+ async spawn(root) {
354
+ let binary = Bun.which("elixir-ls")
355
+ if (!binary) {
356
+ const elixirLsPath = path.join(Global.Path.bin, "elixir-ls")
357
+ binary = path.join(
358
+ Global.Path.bin,
359
+ "elixir-ls-master",
360
+ "release",
361
+ process.platform === "win32" ? "language_server.bar" : "language_server.sh",
362
+ )
363
+
364
+ if (!(await Bun.file(binary).exists())) {
365
+ const elixir = Bun.which("elixir")
366
+ if (!elixir) {
367
+ log.error("elixir is required to run elixir-ls")
368
+ return
369
+ }
370
+
371
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
372
+ log.info("downloading elixir-ls from GitHub releases")
373
+
374
+ const response = await fetch("https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip")
375
+ if (!response.ok) return
376
+ const zipPath = path.join(Global.Path.bin, "elixir-ls.zip")
377
+ await Bun.file(zipPath).write(response)
378
+
379
+ await $`unzip -o -q ${zipPath}`.quiet().cwd(Global.Path.bin).nothrow()
380
+
381
+ await fs.rm(zipPath, {
382
+ force: true,
383
+ recursive: true,
384
+ })
385
+
386
+ await $`mix deps.get && mix compile && mix elixir_ls.release2 -o release`
387
+ .quiet()
388
+ .cwd(path.join(Global.Path.bin, "elixir-ls-master"))
389
+ .env({ MIX_ENV: "prod", ...process.env })
390
+
391
+ log.info(`installed elixir-ls`, {
392
+ path: elixirLsPath,
393
+ })
394
+ }
395
+ }
396
+
397
+ return {
398
+ process: spawn(binary, {
399
+ cwd: root,
400
+ }),
401
+ }
402
+ },
403
+ }
404
+
405
+ export const Zls: Info = {
406
+ id: "zls",
407
+ extensions: [".zig", ".zon"],
408
+ root: NearestRoot(["build.zig"]),
409
+ async spawn(root) {
410
+ let bin = Bun.which("zls", {
411
+ PATH: process.env["PATH"] + ":" + Global.Path.bin,
412
+ })
413
+
414
+ if (!bin) {
415
+ const zig = Bun.which("zig")
416
+ if (!zig) {
417
+ log.error("Zig is required to use zls. Please install Zig first.")
418
+ return
419
+ }
420
+
421
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
422
+ log.info("downloading zls from GitHub releases")
423
+
424
+ const releaseResponse = await fetch("https://api.github.com/repos/zigtools/zls/releases/latest")
425
+ if (!releaseResponse.ok) {
426
+ log.error("Failed to fetch zls release info")
427
+ return
428
+ }
429
+
430
+ const release = (await releaseResponse.json()) as any
431
+
432
+ const platform = process.platform
433
+ const arch = process.arch
434
+ let assetName = ""
435
+
436
+ let zlsArch: string = arch
437
+ if (arch === "arm64") zlsArch = "aarch64"
438
+ else if (arch === "x64") zlsArch = "x86_64"
439
+ else if (arch === "ia32") zlsArch = "x86"
440
+
441
+ let zlsPlatform: string = platform
442
+ if (platform === "darwin") zlsPlatform = "macos"
443
+ else if (platform === "win32") zlsPlatform = "windows"
444
+
445
+ const ext = platform === "win32" ? "zip" : "tar.xz"
446
+
447
+ assetName = `zls-${zlsArch}-${zlsPlatform}.${ext}`
448
+
449
+ const supportedCombos = [
450
+ "zls-x86_64-linux.tar.xz",
451
+ "zls-x86_64-macos.tar.xz",
452
+ "zls-x86_64-windows.zip",
453
+ "zls-aarch64-linux.tar.xz",
454
+ "zls-aarch64-macos.tar.xz",
455
+ "zls-aarch64-windows.zip",
456
+ "zls-x86-linux.tar.xz",
457
+ "zls-x86-windows.zip",
458
+ ]
459
+
460
+ if (!supportedCombos.includes(assetName)) {
461
+ log.error(`Platform ${platform} and architecture ${arch} is not supported by zls`)
462
+ return
463
+ }
464
+
465
+ const asset = release.assets.find((a: any) => a.name === assetName)
466
+ if (!asset) {
467
+ log.error(`Could not find asset ${assetName} in latest zls release`)
468
+ return
469
+ }
470
+
471
+ const downloadUrl = asset.browser_download_url
472
+ const downloadResponse = await fetch(downloadUrl)
473
+ if (!downloadResponse.ok) {
474
+ log.error("Failed to download zls")
475
+ return
476
+ }
477
+
478
+ const tempPath = path.join(Global.Path.bin, assetName)
479
+ await Bun.file(tempPath).write(downloadResponse)
480
+
481
+ if (ext === "zip") {
482
+ await $`unzip -o -q ${tempPath}`.quiet().cwd(Global.Path.bin).nothrow()
483
+ } else {
484
+ await $`tar -xf ${tempPath}`.cwd(Global.Path.bin).nothrow()
485
+ }
486
+
487
+ await fs.rm(tempPath, { force: true })
488
+
489
+ bin = path.join(Global.Path.bin, "zls" + (platform === "win32" ? ".exe" : ""))
490
+
491
+ if (!(await Bun.file(bin).exists())) {
492
+ log.error("Failed to extract zls binary")
493
+ return
494
+ }
495
+
496
+ if (platform !== "win32") {
497
+ await $`chmod +x ${bin}`.nothrow()
498
+ }
499
+
500
+ log.info(`installed zls`, { bin })
501
+ }
502
+
503
+ return {
504
+ process: spawn(bin, {
505
+ cwd: root,
506
+ }),
507
+ }
508
+ },
509
+ }
510
+
511
+ export const CSharp: Info = {
512
+ id: "csharp",
513
+ root: NearestRoot([".sln", ".csproj", "global.json"]),
514
+ extensions: [".cs"],
515
+ async spawn(root) {
516
+ let bin = Bun.which("csharp-ls", {
517
+ PATH: process.env["PATH"] + ":" + Global.Path.bin,
518
+ })
519
+ if (!bin) {
520
+ if (!Bun.which("dotnet")) {
521
+ log.error(".NET SDK is required to install csharp-ls")
522
+ return
523
+ }
524
+
525
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
526
+ log.info("installing csharp-ls via dotnet tool")
527
+ const proc = Bun.spawn({
528
+ cmd: ["dotnet", "tool", "install", "csharp-ls", "--tool-path", Global.Path.bin],
529
+ stdout: "pipe",
530
+ stderr: "pipe",
531
+ stdin: "pipe",
532
+ })
533
+ const exit = await proc.exited
534
+ if (exit !== 0) {
535
+ log.error("Failed to install csharp-ls")
536
+ return
537
+ }
538
+
539
+ bin = path.join(Global.Path.bin, "csharp-ls" + (process.platform === "win32" ? ".exe" : ""))
540
+ log.info(`installed csharp-ls`, { bin })
541
+ }
542
+
543
+ return {
544
+ process: spawn(bin, {
545
+ cwd: root,
546
+ }),
547
+ }
548
+ },
549
+ }
550
+
551
+ export const SourceKit: Info = {
552
+ id: "sourcekit-lsp",
553
+ extensions: [".swift", ".objc", "objcpp"],
554
+ root: NearestRoot(["Package.swift", "*.xcodeproj", "*.xcworkspace"]),
555
+ async spawn(root) {
556
+ // Check if sourcekit-lsp is available in the PATH
557
+ // This is installed with the Swift toolchain
558
+ const sourcekit = Bun.which("sourcekit-lsp")
559
+ if (sourcekit) {
560
+ return {
561
+ process: spawn(sourcekit, {
562
+ cwd: root,
563
+ }),
564
+ }
565
+ }
566
+
567
+ // If sourcekit-lsp not found, check if xcrun is available
568
+ // This is specific to macOS where sourcekit-lsp is typically installed with Xcode
569
+ if (!Bun.which("xcrun")) return
570
+
571
+ const lspLoc = await $`xcrun --find sourcekit-lsp`.quiet().nothrow()
572
+
573
+ if (lspLoc.exitCode !== 0) return
574
+
575
+ const bin = lspLoc.text().trim()
576
+
577
+ return {
578
+ process: spawn(bin, {
579
+ cwd: root,
580
+ }),
581
+ }
582
+ },
583
+ }
584
+
585
+ export const RustAnalyzer: Info = {
586
+ id: "rust",
587
+ root: async (root) => {
588
+ const crateRoot = await NearestRoot(["Cargo.toml", "Cargo.lock"])(root)
589
+ if (crateRoot === undefined) {
590
+ return undefined
591
+ }
592
+ let currentDir = crateRoot
593
+
594
+ while (currentDir !== path.dirname(currentDir)) {
595
+ // Stop at filesystem root
596
+ const cargoTomlPath = path.join(currentDir, "Cargo.toml")
597
+ try {
598
+ const cargoTomlContent = await Bun.file(cargoTomlPath).text()
599
+ if (cargoTomlContent.includes("[workspace]")) {
600
+ return currentDir
601
+ }
602
+ } catch (err) {
603
+ // File doesn't exist or can't be read, continue searching up
604
+ }
605
+
606
+ const parentDir = path.dirname(currentDir)
607
+ if (parentDir === currentDir) break // Reached filesystem root
608
+ currentDir = parentDir
609
+
610
+ // Stop if we've gone above the app root
611
+ if (!currentDir.startsWith(Instance.worktree)) break
612
+ }
613
+
614
+ return crateRoot
615
+ },
616
+ extensions: [".rs"],
617
+ async spawn(root) {
618
+ const bin = Bun.which("rust-analyzer")
619
+ if (!bin) {
620
+ log.info("rust-analyzer not found in path, please install it")
621
+ return
622
+ }
623
+ return {
624
+ process: spawn(bin, {
625
+ cwd: root,
626
+ }),
627
+ }
628
+ },
629
+ }
630
+
631
+ export const Clangd: Info = {
632
+ id: "clangd",
633
+ root: NearestRoot(["compile_commands.json", "compile_flags.txt", ".clangd", "CMakeLists.txt", "Makefile"]),
634
+ extensions: [".c", ".cpp", ".cc", ".cxx", ".c++", ".h", ".hpp", ".hh", ".hxx", ".h++"],
635
+ async spawn(root) {
636
+ const args = ["--background-index", "--clang-tidy"]
637
+ const fromPath = Bun.which("clangd")
638
+ if (fromPath) {
639
+ return {
640
+ process: spawn(fromPath, args, {
641
+ cwd: root,
642
+ }),
643
+ }
644
+ }
645
+
646
+ const ext = process.platform === "win32" ? ".exe" : ""
647
+ const direct = path.join(Global.Path.bin, "clangd" + ext)
648
+ if (await Bun.file(direct).exists()) {
649
+ return {
650
+ process: spawn(direct, args, {
651
+ cwd: root,
652
+ }),
653
+ }
654
+ }
655
+
656
+ const entries = await fs.readdir(Global.Path.bin, { withFileTypes: true }).catch(() => [])
657
+ for (const entry of entries) {
658
+ if (!entry.isDirectory()) continue
659
+ if (!entry.name.startsWith("clangd_")) continue
660
+ const candidate = path.join(Global.Path.bin, entry.name, "bin", "clangd" + ext)
661
+ if (await Bun.file(candidate).exists()) {
662
+ return {
663
+ process: spawn(candidate, args, {
664
+ cwd: root,
665
+ }),
666
+ }
667
+ }
668
+ }
669
+
670
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
671
+ log.info("downloading clangd from GitHub releases")
672
+
673
+ const releaseResponse = await fetch("https://api.github.com/repos/clangd/clangd/releases/latest")
674
+ if (!releaseResponse.ok) {
675
+ log.error("Failed to fetch clangd release info")
676
+ return
677
+ }
678
+
679
+ const release: {
680
+ tag_name?: string
681
+ assets?: { name?: string; browser_download_url?: string }[]
682
+ } = await releaseResponse.json()
683
+
684
+ const tag = release.tag_name
685
+ if (!tag) {
686
+ log.error("clangd release did not include a tag name")
687
+ return
688
+ }
689
+ const platform = process.platform
690
+ const tokens: Record<string, string> = {
691
+ darwin: "mac",
692
+ linux: "linux",
693
+ win32: "windows",
694
+ }
695
+ const token = tokens[platform]
696
+ if (!token) {
697
+ log.error(`Platform ${platform} is not supported by clangd auto-download`)
698
+ return
699
+ }
700
+
701
+ const assets = release.assets ?? []
702
+ const valid = (item: { name?: string; browser_download_url?: string }) => {
703
+ if (!item.name) return false
704
+ if (!item.browser_download_url) return false
705
+ if (!item.name.includes(token)) return false
706
+ return item.name.includes(tag)
707
+ }
708
+
709
+ const asset =
710
+ assets.find((item) => valid(item) && item.name?.endsWith(".zip")) ??
711
+ assets.find((item) => valid(item) && item.name?.endsWith(".tar.xz")) ??
712
+ assets.find((item) => valid(item))
713
+ if (!asset?.name || !asset.browser_download_url) {
714
+ log.error("clangd could not match release asset", { tag, platform })
715
+ return
716
+ }
717
+
718
+ const name = asset.name
719
+ const downloadResponse = await fetch(asset.browser_download_url)
720
+ if (!downloadResponse.ok) {
721
+ log.error("Failed to download clangd")
722
+ return
723
+ }
724
+
725
+ const archive = path.join(Global.Path.bin, name)
726
+ const buf = await downloadResponse.arrayBuffer()
727
+ if (buf.byteLength === 0) {
728
+ log.error("Failed to write clangd archive")
729
+ return
730
+ }
731
+ await Bun.write(archive, buf)
732
+
733
+ const zip = name.endsWith(".zip")
734
+ const tar = name.endsWith(".tar.xz")
735
+ if (!zip && !tar) {
736
+ log.error("clangd encountered unsupported asset", { asset: name })
737
+ return
738
+ }
739
+
740
+ if (zip) {
741
+ await $`unzip -o -q ${archive}`.quiet().cwd(Global.Path.bin).nothrow()
742
+ }
743
+ if (tar) {
744
+ await $`tar -xf ${archive}`.cwd(Global.Path.bin).nothrow()
745
+ }
746
+ await fs.rm(archive, { force: true })
747
+
748
+ const bin = path.join(Global.Path.bin, "clangd_" + tag, "bin", "clangd" + ext)
749
+ if (!(await Bun.file(bin).exists())) {
750
+ log.error("Failed to extract clangd binary")
751
+ return
752
+ }
753
+
754
+ if (platform !== "win32") {
755
+ await $`chmod +x ${bin}`.nothrow()
756
+ }
757
+
758
+ await fs.unlink(path.join(Global.Path.bin, "clangd")).catch(() => {})
759
+ await fs.symlink(bin, path.join(Global.Path.bin, "clangd")).catch(() => {})
760
+
761
+ log.info(`installed clangd`, { bin })
762
+
763
+ return {
764
+ process: spawn(bin, args, {
765
+ cwd: root,
766
+ }),
767
+ }
768
+ },
769
+ }
770
+
771
+ export const Svelte: Info = {
772
+ id: "svelte",
773
+ extensions: [".svelte"],
774
+ root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]),
775
+ async spawn(root) {
776
+ let binary = Bun.which("svelteserver")
777
+ const args: string[] = []
778
+ if (!binary) {
779
+ const js = path.join(Global.Path.bin, "node_modules", "svelte-language-server", "bin", "server.js")
780
+ if (!(await Bun.file(js).exists())) {
781
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
782
+ await Bun.spawn([BunProc.which(), "install", "svelte-language-server"], {
783
+ cwd: Global.Path.bin,
784
+ env: {
785
+ ...process.env,
786
+ BUN_BE_BUN: "1",
787
+ },
788
+ stdout: "pipe",
789
+ stderr: "pipe",
790
+ stdin: "pipe",
791
+ }).exited
792
+ }
793
+ binary = BunProc.which()
794
+ args.push("run", js)
795
+ }
796
+ args.push("--stdio")
797
+ const proc = spawn(binary, args, {
798
+ cwd: root,
799
+ env: {
800
+ ...process.env,
801
+ BUN_BE_BUN: "1",
802
+ },
803
+ })
804
+ return {
805
+ process: proc,
806
+ initialization: {},
807
+ }
808
+ },
809
+ }
810
+
811
+ export const Astro: Info = {
812
+ id: "astro",
813
+ extensions: [".astro"],
814
+ root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]),
815
+ async spawn(root) {
816
+ const tsserver = await Bun.resolve("typescript/lib/tsserver.js", Instance.directory).catch(() => {})
817
+ if (!tsserver) {
818
+ log.info("typescript not found, required for Astro language server")
819
+ return
820
+ }
821
+ const tsdk = path.dirname(tsserver)
822
+
823
+ let binary = Bun.which("astro-ls")
824
+ const args: string[] = []
825
+ if (!binary) {
826
+ const js = path.join(Global.Path.bin, "node_modules", "@astrojs", "language-server", "bin", "nodeServer.js")
827
+ if (!(await Bun.file(js).exists())) {
828
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
829
+ await Bun.spawn([BunProc.which(), "install", "@astrojs/language-server"], {
830
+ cwd: Global.Path.bin,
831
+ env: {
832
+ ...process.env,
833
+ BUN_BE_BUN: "1",
834
+ },
835
+ stdout: "pipe",
836
+ stderr: "pipe",
837
+ stdin: "pipe",
838
+ }).exited
839
+ }
840
+ binary = BunProc.which()
841
+ args.push("run", js)
842
+ }
843
+ args.push("--stdio")
844
+ const proc = spawn(binary, args, {
845
+ cwd: root,
846
+ env: {
847
+ ...process.env,
848
+ BUN_BE_BUN: "1",
849
+ },
850
+ })
851
+ return {
852
+ process: proc,
853
+ initialization: {
854
+ typescript: {
855
+ tsdk,
856
+ },
857
+ },
858
+ }
859
+ },
860
+ }
861
+
862
+ export const JDTLS: Info = {
863
+ id: "jdtls",
864
+ root: NearestRoot(["pom.xml", "build.gradle", "build.gradle.kts", ".project", ".classpath"]),
865
+ extensions: [".java"],
866
+ async spawn(root) {
867
+ const java = Bun.which("java")
868
+ if (!java) {
869
+ log.error("Java 21 or newer is required to run the JDTLS. Please install it first.")
870
+ return
871
+ }
872
+ const javaMajorVersion = await $`java -version`
873
+ .quiet()
874
+ .nothrow()
875
+ .then(({ stderr }) => {
876
+ const m = /"(\d+)\.\d+\.\d+"/.exec(stderr.toString())
877
+ return !m ? undefined : parseInt(m[1])
878
+ })
879
+ if (javaMajorVersion == null || javaMajorVersion < 21) {
880
+ log.error("JDTLS requires at least Java 21.")
881
+ return
882
+ }
883
+ const distPath = path.join(Global.Path.bin, "jdtls")
884
+ const launcherDir = path.join(distPath, "plugins")
885
+ const installed = await fs.exists(launcherDir)
886
+ if (!installed) {
887
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
888
+ log.info("Downloading JDTLS LSP server.")
889
+ await fs.mkdir(distPath, { recursive: true })
890
+ const releaseURL =
891
+ "https://www.eclipse.org/downloads/download.php?file=/jdtls/snapshots/jdt-language-server-latest.tar.gz"
892
+ const archivePath = path.join(distPath, "release.tar.gz")
893
+ await $`curl -L -o '${archivePath}' '${releaseURL}'`.quiet().nothrow()
894
+ await $`tar -xzf ${archivePath}`.cwd(distPath).quiet().nothrow()
895
+ await fs.rm(archivePath, { force: true })
896
+ }
897
+ const jarFileName = await $`ls org.eclipse.equinox.launcher_*.jar`
898
+ .cwd(launcherDir)
899
+ .quiet()
900
+ .nothrow()
901
+ .then(({ stdout }) => stdout.toString().trim())
902
+ const launcherJar = path.join(launcherDir, jarFileName)
903
+ if (!(await fs.exists(launcherJar))) {
904
+ log.error(`Failed to locate the JDTLS launcher module in the installed directory: ${distPath}.`)
905
+ return
906
+ }
907
+ const configFile = path.join(
908
+ distPath,
909
+ (() => {
910
+ switch (process.platform) {
911
+ case "darwin":
912
+ return "config_mac"
913
+ case "linux":
914
+ return "config_linux"
915
+ case "win32":
916
+ return "config_windows"
917
+ default:
918
+ return "config_linux"
919
+ }
920
+ })(),
921
+ )
922
+ const dataDir = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-jdtls-data"))
923
+ return {
924
+ process: spawn(
925
+ java,
926
+ [
927
+ "-jar",
928
+ launcherJar,
929
+ "-configuration",
930
+ configFile,
931
+ "-data",
932
+ dataDir,
933
+ "-Declipse.application=org.eclipse.jdt.ls.core.id1",
934
+ "-Dosgi.bundles.defaultStartLevel=4",
935
+ "-Declipse.product=org.eclipse.jdt.ls.core.product",
936
+ "-Dlog.level=ALL",
937
+ "--add-modules=ALL-SYSTEM",
938
+ "--add-opens java.base/java.util=ALL-UNNAMED",
939
+ "--add-opens java.base/java.lang=ALL-UNNAMED",
940
+ ],
941
+ {
942
+ cwd: root,
943
+ },
944
+ ),
945
+ }
946
+ },
947
+ }
948
+
949
+ export const YamlLS: Info = {
950
+ id: "yaml-ls",
951
+ extensions: [".yaml", ".yml"],
952
+ root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]),
953
+ async spawn(root) {
954
+ let binary = Bun.which("yaml-language-server")
955
+ const args: string[] = []
956
+ if (!binary) {
957
+ const js = path.join(
958
+ Global.Path.bin,
959
+ "node_modules",
960
+ "yaml-language-server",
961
+ "out",
962
+ "server",
963
+ "src",
964
+ "server.js",
965
+ )
966
+ const exists = await Bun.file(js).exists()
967
+ if (!exists) {
968
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
969
+ await Bun.spawn([BunProc.which(), "install", "yaml-language-server"], {
970
+ cwd: Global.Path.bin,
971
+ env: {
972
+ ...process.env,
973
+ BUN_BE_BUN: "1",
974
+ },
975
+ stdout: "pipe",
976
+ stderr: "pipe",
977
+ stdin: "pipe",
978
+ }).exited
979
+ }
980
+ binary = BunProc.which()
981
+ args.push("run", js)
982
+ }
983
+ args.push("--stdio")
984
+ const proc = spawn(binary, args, {
985
+ cwd: root,
986
+ env: {
987
+ ...process.env,
988
+ BUN_BE_BUN: "1",
989
+ },
990
+ })
991
+ return {
992
+ process: proc,
993
+ }
994
+ },
995
+ }
996
+
997
+ export const LuaLS: Info = {
998
+ id: "lua-ls",
999
+ root: NearestRoot([
1000
+ ".luarc.json",
1001
+ ".luarc.jsonc",
1002
+ ".luacheckrc",
1003
+ ".stylua.toml",
1004
+ "stylua.toml",
1005
+ "selene.toml",
1006
+ "selene.yml",
1007
+ ]),
1008
+ extensions: [".lua"],
1009
+ async spawn(root) {
1010
+ let bin = Bun.which("lua-language-server", {
1011
+ PATH: process.env["PATH"] + ":" + Global.Path.bin,
1012
+ })
1013
+
1014
+ if (!bin) {
1015
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
1016
+ log.info("downloading lua-language-server from GitHub releases")
1017
+
1018
+ const releaseResponse = await fetch("https://api.github.com/repos/LuaLS/lua-language-server/releases/latest")
1019
+ if (!releaseResponse.ok) {
1020
+ log.error("Failed to fetch lua-language-server release info")
1021
+ return
1022
+ }
1023
+
1024
+ const release = await releaseResponse.json()
1025
+
1026
+ const platform = process.platform
1027
+ const arch = process.arch
1028
+ let assetName = ""
1029
+
1030
+ let lualsArch: string = arch
1031
+ if (arch === "arm64") lualsArch = "arm64"
1032
+ else if (arch === "x64") lualsArch = "x64"
1033
+ else if (arch === "ia32") lualsArch = "ia32"
1034
+
1035
+ let lualsPlatform: string = platform
1036
+ if (platform === "darwin") lualsPlatform = "darwin"
1037
+ else if (platform === "linux") lualsPlatform = "linux"
1038
+ else if (platform === "win32") lualsPlatform = "win32"
1039
+
1040
+ const ext = platform === "win32" ? "zip" : "tar.gz"
1041
+
1042
+ assetName = `lua-language-server-${release.tag_name}-${lualsPlatform}-${lualsArch}.${ext}`
1043
+
1044
+ const supportedCombos = [
1045
+ "darwin-arm64.tar.gz",
1046
+ "darwin-x64.tar.gz",
1047
+ "linux-x64.tar.gz",
1048
+ "linux-arm64.tar.gz",
1049
+ "win32-x64.zip",
1050
+ "win32-ia32.zip",
1051
+ ]
1052
+
1053
+ const assetSuffix = `${lualsPlatform}-${lualsArch}.${ext}`
1054
+ if (!supportedCombos.includes(assetSuffix)) {
1055
+ log.error(`Platform ${platform} and architecture ${arch} is not supported by lua-language-server`)
1056
+ return
1057
+ }
1058
+
1059
+ const asset = release.assets.find((a: any) => a.name === assetName)
1060
+ if (!asset) {
1061
+ log.error(`Could not find asset ${assetName} in latest lua-language-server release`)
1062
+ return
1063
+ }
1064
+
1065
+ const downloadUrl = asset.browser_download_url
1066
+ const downloadResponse = await fetch(downloadUrl)
1067
+ if (!downloadResponse.ok) {
1068
+ log.error("Failed to download lua-language-server")
1069
+ return
1070
+ }
1071
+
1072
+ const tempPath = path.join(Global.Path.bin, assetName)
1073
+ await Bun.file(tempPath).write(downloadResponse)
1074
+
1075
+ // Unlike zls which is a single self-contained binary,
1076
+ // lua-language-server needs supporting files (meta/, locale/, etc.)
1077
+ // Extract entire archive to dedicated directory to preserve all files
1078
+ const installDir = path.join(Global.Path.bin, `lua-language-server-${lualsArch}-${lualsPlatform}`)
1079
+
1080
+ // Remove old installation if exists
1081
+ const stats = await fs.stat(installDir).catch(() => undefined)
1082
+ if (stats) {
1083
+ await fs.rm(installDir, { force: true, recursive: true })
1084
+ }
1085
+
1086
+ await fs.mkdir(installDir, { recursive: true })
1087
+
1088
+ if (ext === "zip") {
1089
+ const ok = await $`unzip -o -q ${tempPath} -d ${installDir}`.quiet().catch((error) => {
1090
+ log.error("Failed to extract lua-language-server archive", { error })
1091
+ })
1092
+ if (!ok) return
1093
+ } else {
1094
+ const ok = await $`tar -xzf ${tempPath} -C ${installDir}`.quiet().catch((error) => {
1095
+ log.error("Failed to extract lua-language-server archive", { error })
1096
+ })
1097
+ if (!ok) return
1098
+ }
1099
+
1100
+ await fs.rm(tempPath, { force: true })
1101
+
1102
+ // Binary is located in bin/ subdirectory within the extracted archive
1103
+ bin = path.join(installDir, "bin", "lua-language-server" + (platform === "win32" ? ".exe" : ""))
1104
+
1105
+ if (!(await Bun.file(bin).exists())) {
1106
+ log.error("Failed to extract lua-language-server binary")
1107
+ return
1108
+ }
1109
+
1110
+ if (platform !== "win32") {
1111
+ const ok = await $`chmod +x ${bin}`.quiet().catch((error) => {
1112
+ log.error("Failed to set executable permission for lua-language-server binary", {
1113
+ error,
1114
+ })
1115
+ })
1116
+ if (!ok) return
1117
+ }
1118
+
1119
+ log.info(`installed lua-language-server`, { bin })
1120
+ }
1121
+
1122
+ return {
1123
+ process: spawn(bin, {
1124
+ cwd: root,
1125
+ }),
1126
+ }
1127
+ },
1128
+ }
1129
+
1130
+ export const PHPIntelephense: Info = {
1131
+ id: "php intelephense",
1132
+ extensions: [".php"],
1133
+ root: NearestRoot(["composer.json", "composer.lock", ".php-version"]),
1134
+ async spawn(root) {
1135
+ let binary = Bun.which("intelephense")
1136
+ const args: string[] = []
1137
+ if (!binary) {
1138
+ const js = path.join(Global.Path.bin, "node_modules", "intelephense", "lib", "intelephense.js")
1139
+ if (!(await Bun.file(js).exists())) {
1140
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
1141
+ await Bun.spawn([BunProc.which(), "install", "intelephense"], {
1142
+ cwd: Global.Path.bin,
1143
+ env: {
1144
+ ...process.env,
1145
+ BUN_BE_BUN: "1",
1146
+ },
1147
+ stdout: "pipe",
1148
+ stderr: "pipe",
1149
+ stdin: "pipe",
1150
+ }).exited
1151
+ }
1152
+ binary = BunProc.which()
1153
+ args.push("run", js)
1154
+ }
1155
+ args.push("--stdio")
1156
+ const proc = spawn(binary, args, {
1157
+ cwd: root,
1158
+ env: {
1159
+ ...process.env,
1160
+ BUN_BE_BUN: "1",
1161
+ },
1162
+ })
1163
+ return {
1164
+ process: proc,
1165
+ initialization: {},
1166
+ }
1167
+ },
1168
+ }
1169
+
1170
+ export const Dart: Info = {
1171
+ id: "dart",
1172
+ extensions: [".dart"],
1173
+ root: NearestRoot(["pubspec.yaml", "analysis_options.yaml"]),
1174
+ async spawn(root) {
1175
+ const dart = Bun.which("dart")
1176
+ if (!dart) {
1177
+ log.info("dart not found, please install dart first")
1178
+ return
1179
+ }
1180
+ return {
1181
+ process: spawn(dart, ["language-server", "--lsp"], {
1182
+ cwd: root,
1183
+ }),
1184
+ }
1185
+ },
1186
+ }
1187
+
1188
+ export const Ocaml: Info = {
1189
+ id: "ocaml-lsp",
1190
+ extensions: [".ml", ".mli"],
1191
+ root: NearestRoot(["dune-project", "dune-workspace", ".merlin", "opam"]),
1192
+ async spawn(root) {
1193
+ const bin = Bun.which("ocamllsp")
1194
+ if (!bin) {
1195
+ log.info("ocamllsp not found, please install ocaml-lsp-server")
1196
+ return
1197
+ }
1198
+ return {
1199
+ process: spawn(bin, {
1200
+ cwd: root,
1201
+ }),
1202
+ }
1203
+ },
1204
+ }
1205
+ export const BashLS: Info = {
1206
+ id: "bash",
1207
+ extensions: [".sh", ".bash", ".zsh", ".ksh"],
1208
+ root: async () => Instance.directory,
1209
+ async spawn(root) {
1210
+ let binary = Bun.which("bash-language-server")
1211
+ const args: string[] = []
1212
+ if (!binary) {
1213
+ const js = path.join(Global.Path.bin, "node_modules", "bash-language-server", "out", "cli.js")
1214
+ if (!(await Bun.file(js).exists())) {
1215
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
1216
+ await Bun.spawn([BunProc.which(), "install", "bash-language-server"], {
1217
+ cwd: Global.Path.bin,
1218
+ env: {
1219
+ ...process.env,
1220
+ BUN_BE_BUN: "1",
1221
+ },
1222
+ stdout: "pipe",
1223
+ stderr: "pipe",
1224
+ stdin: "pipe",
1225
+ }).exited
1226
+ }
1227
+ binary = BunProc.which()
1228
+ args.push("run", js)
1229
+ }
1230
+ args.push("start")
1231
+ const proc = spawn(binary, args, {
1232
+ cwd: root,
1233
+ env: {
1234
+ ...process.env,
1235
+ BUN_BE_BUN: "1",
1236
+ },
1237
+ })
1238
+ return {
1239
+ process: proc,
1240
+ }
1241
+ },
1242
+ }
1243
+
1244
+ export const TerraformLS: Info = {
1245
+ id: "terraform",
1246
+ extensions: [".tf", ".tfvars"],
1247
+ root: NearestRoot([".terraform.lock.hcl", "terraform.tfstate", "*.tf"]),
1248
+ async spawn(root) {
1249
+ let bin = Bun.which("terraform-ls", {
1250
+ PATH: process.env["PATH"] + ":" + Global.Path.bin,
1251
+ })
1252
+
1253
+ if (!bin) {
1254
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
1255
+ log.info("downloading terraform-ls from GitHub releases")
1256
+
1257
+ const releaseResponse = await fetch("https://api.github.com/repos/hashicorp/terraform-ls/releases/latest")
1258
+ if (!releaseResponse.ok) {
1259
+ log.error("Failed to fetch terraform-ls release info")
1260
+ return
1261
+ }
1262
+
1263
+ const release = (await releaseResponse.json()) as {
1264
+ tag_name?: string
1265
+ assets?: { name?: string; browser_download_url?: string }[]
1266
+ }
1267
+ const version = release.tag_name?.replace("v", "")
1268
+ if (!version) {
1269
+ log.error("terraform-ls release did not include a version tag")
1270
+ return
1271
+ }
1272
+
1273
+ const platform = process.platform
1274
+ const arch = process.arch
1275
+
1276
+ const tfArch = arch === "arm64" ? "arm64" : "amd64"
1277
+ const tfPlatform = platform === "win32" ? "windows" : platform
1278
+
1279
+ const assetName = `terraform-ls_${version}_${tfPlatform}_${tfArch}.zip`
1280
+
1281
+ const assets = release.assets ?? []
1282
+ const asset = assets.find((a) => a.name === assetName)
1283
+ if (!asset?.browser_download_url) {
1284
+ log.error(`Could not find asset ${assetName} in terraform-ls release`)
1285
+ return
1286
+ }
1287
+
1288
+ const downloadResponse = await fetch(asset.browser_download_url)
1289
+ if (!downloadResponse.ok) {
1290
+ log.error("Failed to download terraform-ls")
1291
+ return
1292
+ }
1293
+
1294
+ const tempPath = path.join(Global.Path.bin, assetName)
1295
+ await Bun.file(tempPath).write(downloadResponse)
1296
+
1297
+ await $`unzip -o -q ${tempPath}`.cwd(Global.Path.bin).nothrow()
1298
+ await fs.rm(tempPath, { force: true })
1299
+
1300
+ bin = path.join(Global.Path.bin, "terraform-ls" + (platform === "win32" ? ".exe" : ""))
1301
+
1302
+ if (!(await Bun.file(bin).exists())) {
1303
+ log.error("Failed to extract terraform-ls binary")
1304
+ return
1305
+ }
1306
+
1307
+ if (platform !== "win32") {
1308
+ await $`chmod +x ${bin}`.nothrow()
1309
+ }
1310
+
1311
+ log.info(`installed terraform-ls`, { bin })
1312
+ }
1313
+
1314
+ return {
1315
+ process: spawn(bin, ["serve"], {
1316
+ cwd: root,
1317
+ }),
1318
+ initialization: {
1319
+ experimentalFeatures: {
1320
+ prefillRequiredFields: true,
1321
+ validateOnSave: true,
1322
+ },
1323
+ },
1324
+ }
1325
+ },
1326
+ }
1327
+ }