mastracode 0.0.1

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 (336) hide show
  1. package/AGENTS.md +27 -0
  2. package/Dockerfile +18 -0
  3. package/README.md +15 -0
  4. package/bin/opencode +84 -0
  5. package/bunfig.toml +4 -0
  6. package/package.json +113 -0
  7. package/parsers-config.ts +239 -0
  8. package/script/build.ts +167 -0
  9. package/script/postinstall.mjs +122 -0
  10. package/script/publish-registries.ts +187 -0
  11. package/script/publish.ts +70 -0
  12. package/script/schema.ts +47 -0
  13. package/src/acp/README.md +164 -0
  14. package/src/acp/agent.ts +1051 -0
  15. package/src/acp/session.ts +101 -0
  16. package/src/acp/types.ts +22 -0
  17. package/src/agent/agent.ts +398 -0
  18. package/src/agent/generate.txt +75 -0
  19. package/src/agent/prompt/compaction.txt +12 -0
  20. package/src/agent/prompt/explore.txt +18 -0
  21. package/src/agent/prompt/summary.txt +10 -0
  22. package/src/agent/prompt/title.txt +36 -0
  23. package/src/auth/index.ts +70 -0
  24. package/src/bun/index.ts +114 -0
  25. package/src/bus/bus-event.ts +43 -0
  26. package/src/bus/global.ts +10 -0
  27. package/src/bus/index.ts +105 -0
  28. package/src/cli/bootstrap.ts +17 -0
  29. package/src/cli/cmd/acp.ts +88 -0
  30. package/src/cli/cmd/agent.ts +256 -0
  31. package/src/cli/cmd/auth.ts +391 -0
  32. package/src/cli/cmd/cmd.ts +7 -0
  33. package/src/cli/cmd/debug/config.ts +15 -0
  34. package/src/cli/cmd/debug/file.ts +91 -0
  35. package/src/cli/cmd/debug/index.ts +43 -0
  36. package/src/cli/cmd/debug/lsp.ts +48 -0
  37. package/src/cli/cmd/debug/ripgrep.ts +83 -0
  38. package/src/cli/cmd/debug/scrap.ts +15 -0
  39. package/src/cli/cmd/debug/skill.ts +15 -0
  40. package/src/cli/cmd/debug/snapshot.ts +48 -0
  41. package/src/cli/cmd/export.ts +88 -0
  42. package/src/cli/cmd/generate.ts +38 -0
  43. package/src/cli/cmd/github.ts +1408 -0
  44. package/src/cli/cmd/import.ts +98 -0
  45. package/src/cli/cmd/mcp.ts +654 -0
  46. package/src/cli/cmd/models.ts +77 -0
  47. package/src/cli/cmd/pr.ts +112 -0
  48. package/src/cli/cmd/run.ts +368 -0
  49. package/src/cli/cmd/serve.ts +31 -0
  50. package/src/cli/cmd/session.ts +106 -0
  51. package/src/cli/cmd/stats.ts +298 -0
  52. package/src/cli/cmd/tui/app.tsx +686 -0
  53. package/src/cli/cmd/tui/attach.ts +30 -0
  54. package/src/cli/cmd/tui/component/border.tsx +21 -0
  55. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  56. package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
  57. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  58. package/src/cli/cmd/tui/component/dialog-model.tsx +230 -0
  59. package/src/cli/cmd/tui/component/dialog-provider.tsx +224 -0
  60. package/src/cli/cmd/tui/component/dialog-session-list.tsx +102 -0
  61. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  62. package/src/cli/cmd/tui/component/dialog-stash.tsx +86 -0
  63. package/src/cli/cmd/tui/component/dialog-status.tsx +162 -0
  64. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  65. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  66. package/src/cli/cmd/tui/component/did-you-know.tsx +85 -0
  67. package/src/cli/cmd/tui/component/logo.tsx +27 -0
  68. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +574 -0
  69. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  70. package/src/cli/cmd/tui/component/prompt/index.tsx +1117 -0
  71. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  72. package/src/cli/cmd/tui/component/tips.ts +103 -0
  73. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  74. package/src/cli/cmd/tui/context/args.tsx +14 -0
  75. package/src/cli/cmd/tui/context/directory.ts +13 -0
  76. package/src/cli/cmd/tui/context/exit.tsx +23 -0
  77. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  78. package/src/cli/cmd/tui/context/keybind.tsx +101 -0
  79. package/src/cli/cmd/tui/context/kv.tsx +49 -0
  80. package/src/cli/cmd/tui/context/local.tsx +339 -0
  81. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  82. package/src/cli/cmd/tui/context/route.tsx +46 -0
  83. package/src/cli/cmd/tui/context/sdk.tsx +74 -0
  84. package/src/cli/cmd/tui/context/sync.tsx +372 -0
  85. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  86. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  87. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  88. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  89. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  90. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  91. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  92. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  93. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  94. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  95. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  96. package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
  97. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  98. package/src/cli/cmd/tui/context/theme/lucent-orng.json +227 -0
  99. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  100. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  101. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  102. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  103. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  104. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  105. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  106. package/src/cli/cmd/tui/context/theme/orng.json +245 -0
  107. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  108. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  109. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  110. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  111. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  112. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  113. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  114. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  115. package/src/cli/cmd/tui/context/theme.tsx +1109 -0
  116. package/src/cli/cmd/tui/event.ts +40 -0
  117. package/src/cli/cmd/tui/routes/home.tsx +140 -0
  118. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  119. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  120. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  121. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  122. package/src/cli/cmd/tui/routes/session/footer.tsx +88 -0
  123. package/src/cli/cmd/tui/routes/session/header.tsx +141 -0
  124. package/src/cli/cmd/tui/routes/session/index.tsx +1885 -0
  125. package/src/cli/cmd/tui/routes/session/sidebar.tsx +322 -0
  126. package/src/cli/cmd/tui/spawn.ts +60 -0
  127. package/src/cli/cmd/tui/thread.ts +120 -0
  128. package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
  129. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
  130. package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
  131. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
  132. package/src/cli/cmd/tui/ui/dialog-select.tsx +332 -0
  133. package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
  134. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  135. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  136. package/src/cli/cmd/tui/util/clipboard.ts +127 -0
  137. package/src/cli/cmd/tui/util/editor.ts +32 -0
  138. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  139. package/src/cli/cmd/tui/worker.ts +63 -0
  140. package/src/cli/cmd/uninstall.ts +344 -0
  141. package/src/cli/cmd/upgrade.ts +67 -0
  142. package/src/cli/cmd/web.ts +84 -0
  143. package/src/cli/error.ts +56 -0
  144. package/src/cli/ui.ts +84 -0
  145. package/src/cli/upgrade.ts +25 -0
  146. package/src/command/index.ts +80 -0
  147. package/src/command/template/initialize.txt +10 -0
  148. package/src/command/template/review.txt +97 -0
  149. package/src/config/config.ts +997 -0
  150. package/src/config/markdown.ts +41 -0
  151. package/src/env/index.ts +26 -0
  152. package/src/file/ignore.ts +83 -0
  153. package/src/file/index.ts +328 -0
  154. package/src/file/ripgrep.ts +393 -0
  155. package/src/file/time.ts +64 -0
  156. package/src/file/watcher.ts +103 -0
  157. package/src/flag/flag.ts +46 -0
  158. package/src/format/formatter.ts +315 -0
  159. package/src/format/index.ts +137 -0
  160. package/src/global/index.ts +52 -0
  161. package/src/id/id.ts +73 -0
  162. package/src/ide/index.ts +76 -0
  163. package/src/index.ts +158 -0
  164. package/src/installation/index.ts +196 -0
  165. package/src/lsp/client.ts +229 -0
  166. package/src/lsp/index.ts +485 -0
  167. package/src/lsp/language.ts +116 -0
  168. package/src/lsp/server.ts +1895 -0
  169. package/src/mcp/auth.ts +135 -0
  170. package/src/mcp/index.ts +654 -0
  171. package/src/mcp/oauth-callback.ts +200 -0
  172. package/src/mcp/oauth-provider.ts +154 -0
  173. package/src/patch/index.ts +622 -0
  174. package/src/permission/index.ts +199 -0
  175. package/src/plugin/index.ts +91 -0
  176. package/src/project/bootstrap.ts +31 -0
  177. package/src/project/instance.ts +78 -0
  178. package/src/project/project.ts +221 -0
  179. package/src/project/state.ts +65 -0
  180. package/src/project/vcs.ts +76 -0
  181. package/src/provider/auth.ts +143 -0
  182. package/src/provider/models-macro.ts +11 -0
  183. package/src/provider/models.ts +106 -0
  184. package/src/provider/provider.ts +1056 -0
  185. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  186. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  187. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
  188. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  189. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
  190. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  191. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  192. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  193. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
  194. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  195. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  196. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  197. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  198. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  199. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  200. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  201. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  202. package/src/provider/transform.ts +455 -0
  203. package/src/pty/index.ts +231 -0
  204. package/src/server/error.ts +36 -0
  205. package/src/server/project.ts +79 -0
  206. package/src/server/server.ts +2642 -0
  207. package/src/server/tui.ts +71 -0
  208. package/src/session/compaction.ts +223 -0
  209. package/src/session/index.ts +458 -0
  210. package/src/session/llm-mastra.ts +412 -0
  211. package/src/session/llm-shared.ts +172 -0
  212. package/src/session/llm.ts +439 -0
  213. package/src/session/message-v2.ts +675 -0
  214. package/src/session/message.ts +189 -0
  215. package/src/session/processor.ts +171 -0
  216. package/src/session/prompt/anthropic-20250930.txt +166 -0
  217. package/src/session/prompt/anthropic.txt +105 -0
  218. package/src/session/prompt/anthropic_spoof.txt +1 -0
  219. package/src/session/prompt/beast.txt +147 -0
  220. package/src/session/prompt/build-switch.txt +5 -0
  221. package/src/session/prompt/codex.txt +318 -0
  222. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  223. package/src/session/prompt/gemini.txt +155 -0
  224. package/src/session/prompt/max-steps.txt +16 -0
  225. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  226. package/src/session/prompt/plan.txt +26 -0
  227. package/src/session/prompt/polaris.txt +107 -0
  228. package/src/session/prompt/qwen.txt +109 -0
  229. package/src/session/prompt.ts +1454 -0
  230. package/src/session/retry.ts +86 -0
  231. package/src/session/revert.ts +108 -0
  232. package/src/session/status.ts +76 -0
  233. package/src/session/summary.ts +194 -0
  234. package/src/session/system.ts +120 -0
  235. package/src/session/todo.ts +37 -0
  236. package/src/share/share-next.ts +194 -0
  237. package/src/share/share.ts +87 -0
  238. package/src/shell/shell.ts +67 -0
  239. package/src/skill/index.ts +1 -0
  240. package/src/skill/skill.ts +83 -0
  241. package/src/snapshot/index.ts +197 -0
  242. package/src/storage/storage.ts +226 -0
  243. package/src/tool/bash.ts +306 -0
  244. package/src/tool/bash.txt +158 -0
  245. package/src/tool/batch.ts +175 -0
  246. package/src/tool/batch.txt +24 -0
  247. package/src/tool/codesearch.ts +138 -0
  248. package/src/tool/codesearch.txt +12 -0
  249. package/src/tool/edit.ts +675 -0
  250. package/src/tool/edit.txt +10 -0
  251. package/src/tool/glob.ts +65 -0
  252. package/src/tool/glob.txt +6 -0
  253. package/src/tool/grep.ts +121 -0
  254. package/src/tool/grep.txt +8 -0
  255. package/src/tool/invalid.ts +17 -0
  256. package/src/tool/ls.ts +110 -0
  257. package/src/tool/ls.txt +1 -0
  258. package/src/tool/lsp-diagnostics.ts +26 -0
  259. package/src/tool/lsp-diagnostics.txt +1 -0
  260. package/src/tool/lsp-hover.ts +31 -0
  261. package/src/tool/lsp-hover.txt +1 -0
  262. package/src/tool/lsp.ts +87 -0
  263. package/src/tool/lsp.txt +19 -0
  264. package/src/tool/multiedit.ts +46 -0
  265. package/src/tool/multiedit.txt +41 -0
  266. package/src/tool/patch.ts +233 -0
  267. package/src/tool/patch.txt +1 -0
  268. package/src/tool/read.ts +219 -0
  269. package/src/tool/read.txt +12 -0
  270. package/src/tool/registry.ts +162 -0
  271. package/src/tool/skill.ts +100 -0
  272. package/src/tool/task.ts +136 -0
  273. package/src/tool/task.txt +60 -0
  274. package/src/tool/todo.ts +39 -0
  275. package/src/tool/todoread.txt +14 -0
  276. package/src/tool/todowrite.txt +167 -0
  277. package/src/tool/tool.ts +71 -0
  278. package/src/tool/webfetch.ts +187 -0
  279. package/src/tool/webfetch.txt +13 -0
  280. package/src/tool/websearch.ts +150 -0
  281. package/src/tool/websearch.txt +11 -0
  282. package/src/tool/write.ts +110 -0
  283. package/src/tool/write.txt +8 -0
  284. package/src/util/archive.ts +16 -0
  285. package/src/util/color.ts +19 -0
  286. package/src/util/context.ts +25 -0
  287. package/src/util/defer.ts +12 -0
  288. package/src/util/eventloop.ts +20 -0
  289. package/src/util/filesystem.ts +83 -0
  290. package/src/util/fn.ts +11 -0
  291. package/src/util/iife.ts +3 -0
  292. package/src/util/keybind.ts +102 -0
  293. package/src/util/lazy.ts +11 -0
  294. package/src/util/locale.ts +81 -0
  295. package/src/util/lock.ts +98 -0
  296. package/src/util/log.ts +180 -0
  297. package/src/util/queue.ts +32 -0
  298. package/src/util/rpc.ts +42 -0
  299. package/src/util/scrap.ts +10 -0
  300. package/src/util/signal.ts +12 -0
  301. package/src/util/timeout.ts +14 -0
  302. package/src/util/token.ts +7 -0
  303. package/src/util/wildcard.ts +54 -0
  304. package/sst-env.d.ts +9 -0
  305. package/test/agent/agent.test.ts +146 -0
  306. package/test/bun.test.ts +53 -0
  307. package/test/cli/github-remote.test.ts +80 -0
  308. package/test/config/agent-color.test.ts +66 -0
  309. package/test/config/config.test.ts +535 -0
  310. package/test/config/markdown.test.ts +89 -0
  311. package/test/file/ignore.test.ts +10 -0
  312. package/test/fixture/fixture.ts +34 -0
  313. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  314. package/test/ide/ide.test.ts +82 -0
  315. package/test/keybind.test.ts +421 -0
  316. package/test/lsp/client.test.ts +95 -0
  317. package/test/mcp/headers.test.ts +153 -0
  318. package/test/patch/patch.test.ts +348 -0
  319. package/test/preload.ts +57 -0
  320. package/test/project/project.test.ts +72 -0
  321. package/test/provider/provider.test.ts +1809 -0
  322. package/test/provider/transform.test.ts +411 -0
  323. package/test/session/retry.test.ts +61 -0
  324. package/test/session/session.test.ts +71 -0
  325. package/test/skill/skill.test.ts +131 -0
  326. package/test/snapshot/snapshot.test.ts +939 -0
  327. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  328. package/test/tool/bash.test.ts +434 -0
  329. package/test/tool/grep.test.ts +108 -0
  330. package/test/tool/patch.test.ts +259 -0
  331. package/test/tool/read.test.ts +42 -0
  332. package/test/util/iife.test.ts +36 -0
  333. package/test/util/lazy.test.ts +50 -0
  334. package/test/util/timeout.test.ts +21 -0
  335. package/test/util/wildcard.test.ts +55 -0
  336. package/tsconfig.json +16 -0
@@ -0,0 +1,146 @@
1
+ import { test, expect } from "bun:test"
2
+ import path from "path"
3
+ import fs from "fs/promises"
4
+ import { tmpdir } from "../fixture/fixture"
5
+ import { Instance } from "../../src/project/instance"
6
+ import { Agent } from "../../src/agent/agent"
7
+
8
+ test("loads built-in agents when no custom agents configured", async () => {
9
+ await using tmp = await tmpdir()
10
+ await Instance.provide({
11
+ directory: tmp.path,
12
+ fn: async () => {
13
+ const agents = await Agent.list()
14
+ const names = agents.map((a) => a.name)
15
+ expect(names).toContain("build")
16
+ expect(names).toContain("plan")
17
+ },
18
+ })
19
+ })
20
+
21
+ test("custom subagent works alongside built-in primary agents", async () => {
22
+ await using tmp = await tmpdir({
23
+ init: async (dir) => {
24
+ const opencodeDir = path.join(dir, ".opencode")
25
+ await fs.mkdir(opencodeDir, { recursive: true })
26
+ const agentDir = path.join(opencodeDir, "agent")
27
+ await fs.mkdir(agentDir, { recursive: true })
28
+
29
+ await Bun.write(
30
+ path.join(agentDir, "helper.md"),
31
+ `---
32
+ model: test/model
33
+ mode: subagent
34
+ ---
35
+ Helper subagent prompt`,
36
+ )
37
+ },
38
+ })
39
+ await Instance.provide({
40
+ directory: tmp.path,
41
+ fn: async () => {
42
+ const agents = await Agent.list()
43
+ const helper = agents.find((a) => a.name === "helper")
44
+ expect(helper).toBeDefined()
45
+ expect(helper?.mode).toBe("subagent")
46
+
47
+ // Built-in primary agents should still exist
48
+ const build = agents.find((a) => a.name === "build")
49
+ expect(build).toBeDefined()
50
+ expect(build?.mode).toBe("primary")
51
+ },
52
+ })
53
+ })
54
+
55
+ test("throws error when all primary agents are disabled", async () => {
56
+ await using tmp = await tmpdir({
57
+ init: async (dir) => {
58
+ await Bun.write(
59
+ path.join(dir, "opencode.json"),
60
+ JSON.stringify({
61
+ $schema: "https://opencode.ai/config.json",
62
+ agent: {
63
+ build: { disable: true },
64
+ plan: { disable: true },
65
+ },
66
+ }),
67
+ )
68
+ },
69
+ })
70
+ await Instance.provide({
71
+ directory: tmp.path,
72
+ fn: async () => {
73
+ try {
74
+ await Agent.list()
75
+ expect(true).toBe(false) // should not reach here
76
+ } catch (e: any) {
77
+ expect(e.data?.message).toContain("No primary agents are available")
78
+ }
79
+ },
80
+ })
81
+ })
82
+
83
+ test("does not throw when at least one primary agent remains", async () => {
84
+ await using tmp = await tmpdir({
85
+ init: async (dir) => {
86
+ await Bun.write(
87
+ path.join(dir, "opencode.json"),
88
+ JSON.stringify({
89
+ $schema: "https://opencode.ai/config.json",
90
+ agent: {
91
+ build: { disable: true },
92
+ },
93
+ }),
94
+ )
95
+ },
96
+ })
97
+ await Instance.provide({
98
+ directory: tmp.path,
99
+ fn: async () => {
100
+ const agents = await Agent.list()
101
+ const plan = agents.find((a) => a.name === "plan")
102
+ expect(plan).toBeDefined()
103
+ expect(plan?.mode).toBe("primary")
104
+ },
105
+ })
106
+ })
107
+
108
+ test("custom primary agent satisfies requirement when built-ins disabled", async () => {
109
+ await using tmp = await tmpdir({
110
+ init: async (dir) => {
111
+ const opencodeDir = path.join(dir, ".opencode")
112
+ await fs.mkdir(opencodeDir, { recursive: true })
113
+ const agentDir = path.join(opencodeDir, "agent")
114
+ await fs.mkdir(agentDir, { recursive: true })
115
+
116
+ await Bun.write(
117
+ path.join(agentDir, "custom.md"),
118
+ `---
119
+ model: test/model
120
+ mode: primary
121
+ ---
122
+ Custom primary agent`,
123
+ )
124
+
125
+ await Bun.write(
126
+ path.join(dir, "opencode.json"),
127
+ JSON.stringify({
128
+ $schema: "https://opencode.ai/config.json",
129
+ agent: {
130
+ build: { disable: true },
131
+ plan: { disable: true },
132
+ },
133
+ }),
134
+ )
135
+ },
136
+ })
137
+ await Instance.provide({
138
+ directory: tmp.path,
139
+ fn: async () => {
140
+ const agents = await Agent.list()
141
+ const custom = agents.find((a) => a.name === "custom")
142
+ expect(custom).toBeDefined()
143
+ expect(custom?.mode).toBe("primary")
144
+ },
145
+ })
146
+ })
@@ -0,0 +1,53 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import fs from "fs/promises"
3
+ import path from "path"
4
+
5
+ describe("BunProc registry configuration", () => {
6
+ test("should not contain hardcoded registry parameters", async () => {
7
+ // Read the bun/index.ts file
8
+ const bunIndexPath = path.join(__dirname, "../src/bun/index.ts")
9
+ const content = await fs.readFile(bunIndexPath, "utf-8")
10
+
11
+ // Verify that no hardcoded registry is present
12
+ expect(content).not.toContain("--registry=")
13
+ expect(content).not.toContain("hasNpmRcConfig")
14
+ expect(content).not.toContain("NpmRc")
15
+ })
16
+
17
+ test("should use Bun's default registry resolution", async () => {
18
+ // Read the bun/index.ts file
19
+ const bunIndexPath = path.join(__dirname, "../src/bun/index.ts")
20
+ const content = await fs.readFile(bunIndexPath, "utf-8")
21
+
22
+ // Verify that it uses Bun's default resolution
23
+ expect(content).toContain("Bun's default registry resolution")
24
+ expect(content).toContain("Bun will use them automatically")
25
+ expect(content).toContain("No need to pass --registry flag")
26
+ })
27
+
28
+ test("should have correct command structure without registry", async () => {
29
+ // Read the bun/index.ts file
30
+ const bunIndexPath = path.join(__dirname, "../src/bun/index.ts")
31
+ const content = await fs.readFile(bunIndexPath, "utf-8")
32
+
33
+ // Extract the install function
34
+ const installFunctionMatch = content.match(/export async function install[\s\S]*?^ }/m)
35
+ expect(installFunctionMatch).toBeTruthy()
36
+
37
+ if (installFunctionMatch) {
38
+ const installFunction = installFunctionMatch[0]
39
+
40
+ // Verify expected arguments are present
41
+ expect(installFunction).toContain('"add"')
42
+ expect(installFunction).toContain('"--force"')
43
+ expect(installFunction).toContain('"--exact"')
44
+ expect(installFunction).toContain('"--cwd"')
45
+ expect(installFunction).toContain("Global.Path.cache")
46
+ expect(installFunction).toContain('pkg + "@" + version')
47
+
48
+ // Verify no registry argument is added
49
+ expect(installFunction).not.toContain('"--registry"')
50
+ expect(installFunction).not.toContain('args.push("--registry')
51
+ }
52
+ })
53
+ })
@@ -0,0 +1,80 @@
1
+ import { test, expect } from "bun:test"
2
+ import { parseGitHubRemote } from "../../src/cli/cmd/github"
3
+
4
+ test("parses https URL with .git suffix", () => {
5
+ expect(parseGitHubRemote("https://github.com/sst/opencode.git")).toEqual({ owner: "sst", repo: "opencode" })
6
+ })
7
+
8
+ test("parses https URL without .git suffix", () => {
9
+ expect(parseGitHubRemote("https://github.com/sst/opencode")).toEqual({ owner: "sst", repo: "opencode" })
10
+ })
11
+
12
+ test("parses git@ URL with .git suffix", () => {
13
+ expect(parseGitHubRemote("git@github.com:sst/opencode.git")).toEqual({ owner: "sst", repo: "opencode" })
14
+ })
15
+
16
+ test("parses git@ URL without .git suffix", () => {
17
+ expect(parseGitHubRemote("git@github.com:sst/opencode")).toEqual({ owner: "sst", repo: "opencode" })
18
+ })
19
+
20
+ test("parses ssh:// URL with .git suffix", () => {
21
+ expect(parseGitHubRemote("ssh://git@github.com/sst/opencode.git")).toEqual({ owner: "sst", repo: "opencode" })
22
+ })
23
+
24
+ test("parses ssh:// URL without .git suffix", () => {
25
+ expect(parseGitHubRemote("ssh://git@github.com/sst/opencode")).toEqual({ owner: "sst", repo: "opencode" })
26
+ })
27
+
28
+ test("parses http URL", () => {
29
+ expect(parseGitHubRemote("http://github.com/owner/repo")).toEqual({ owner: "owner", repo: "repo" })
30
+ })
31
+
32
+ test("parses URL with hyphenated owner and repo names", () => {
33
+ expect(parseGitHubRemote("https://github.com/my-org/my-repo.git")).toEqual({ owner: "my-org", repo: "my-repo" })
34
+ })
35
+
36
+ test("parses URL with underscores in names", () => {
37
+ expect(parseGitHubRemote("git@github.com:my_org/my_repo.git")).toEqual({ owner: "my_org", repo: "my_repo" })
38
+ })
39
+
40
+ test("parses URL with numbers in names", () => {
41
+ expect(parseGitHubRemote("https://github.com/org123/repo456")).toEqual({ owner: "org123", repo: "repo456" })
42
+ })
43
+
44
+ test("parses repos with dots in the name", () => {
45
+ expect(parseGitHubRemote("https://github.com/socketio/socket.io.git")).toEqual({
46
+ owner: "socketio",
47
+ repo: "socket.io",
48
+ })
49
+ expect(parseGitHubRemote("https://github.com/vuejs/vue.js")).toEqual({
50
+ owner: "vuejs",
51
+ repo: "vue.js",
52
+ })
53
+ expect(parseGitHubRemote("git@github.com:mrdoob/three.js.git")).toEqual({
54
+ owner: "mrdoob",
55
+ repo: "three.js",
56
+ })
57
+ expect(parseGitHubRemote("https://github.com/jashkenas/backbone.git")).toEqual({
58
+ owner: "jashkenas",
59
+ repo: "backbone",
60
+ })
61
+ })
62
+
63
+ test("returns null for non-github URLs", () => {
64
+ expect(parseGitHubRemote("https://gitlab.com/owner/repo.git")).toBeNull()
65
+ expect(parseGitHubRemote("git@gitlab.com:owner/repo.git")).toBeNull()
66
+ expect(parseGitHubRemote("https://bitbucket.org/owner/repo")).toBeNull()
67
+ })
68
+
69
+ test("returns null for invalid URLs", () => {
70
+ expect(parseGitHubRemote("not-a-url")).toBeNull()
71
+ expect(parseGitHubRemote("")).toBeNull()
72
+ expect(parseGitHubRemote("github.com")).toBeNull()
73
+ expect(parseGitHubRemote("https://github.com/")).toBeNull()
74
+ expect(parseGitHubRemote("https://github.com/owner")).toBeNull()
75
+ })
76
+
77
+ test("returns null for URLs with extra path segments", () => {
78
+ expect(parseGitHubRemote("https://github.com/owner/repo/tree/main")).toBeNull()
79
+ expect(parseGitHubRemote("https://github.com/owner/repo/blob/main/file.ts")).toBeNull()
80
+ })
@@ -0,0 +1,66 @@
1
+ import { test, expect } from "bun:test"
2
+ import path from "path"
3
+ import { tmpdir } from "../fixture/fixture"
4
+ import { Instance } from "../../src/project/instance"
5
+ import { Config } from "../../src/config/config"
6
+ import { Agent as AgentSvc } from "../../src/agent/agent"
7
+ import { Color } from "../../src/util/color"
8
+
9
+ test("agent color parsed from project config", async () => {
10
+ await using tmp = await tmpdir({
11
+ init: async (dir) => {
12
+ await Bun.write(
13
+ path.join(dir, "opencode.json"),
14
+ JSON.stringify({
15
+ $schema: "https://opencode.ai/config.json",
16
+ agent: {
17
+ build: { color: "#FFA500" },
18
+ },
19
+ }),
20
+ )
21
+ },
22
+ })
23
+ await Instance.provide({
24
+ directory: tmp.path,
25
+ fn: async () => {
26
+ const cfg = await Config.get()
27
+ expect(cfg.agent?.["build"]?.color).toBe("#FFA500")
28
+ },
29
+ })
30
+ })
31
+
32
+ test("Agent.get includes color from config", async () => {
33
+ await using tmp = await tmpdir({
34
+ init: async (dir) => {
35
+ await Bun.write(
36
+ path.join(dir, "opencode.json"),
37
+ JSON.stringify({
38
+ $schema: "https://opencode.ai/config.json",
39
+ agent: {
40
+ plan: { color: "#A855F7" },
41
+ },
42
+ }),
43
+ )
44
+ },
45
+ })
46
+ await Instance.provide({
47
+ directory: tmp.path,
48
+ fn: async () => {
49
+ const plan = await AgentSvc.get("plan")
50
+ expect(plan?.color).toBe("#A855F7")
51
+ },
52
+ })
53
+ })
54
+
55
+ test("Color.hexToAnsiBold converts valid hex to ANSI", () => {
56
+ const result = Color.hexToAnsiBold("#FFA500")
57
+ expect(result).toBe("\x1b[38;2;255;165;0m\x1b[1m")
58
+ })
59
+
60
+ test("Color.hexToAnsiBold returns undefined for invalid hex", () => {
61
+ expect(Color.hexToAnsiBold(undefined)).toBeUndefined()
62
+ expect(Color.hexToAnsiBold("")).toBeUndefined()
63
+ expect(Color.hexToAnsiBold("#FFF")).toBeUndefined()
64
+ expect(Color.hexToAnsiBold("FFA500")).toBeUndefined()
65
+ expect(Color.hexToAnsiBold("#GGGGGG")).toBeUndefined()
66
+ })