rird 1.0.200

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 (350) hide show
  1. package/AGENTS.md +27 -0
  2. package/Dockerfile +18 -0
  3. package/README.md +15 -0
  4. package/bin/opencode +336 -0
  5. package/bin/pty-wrapper.js +285 -0
  6. package/bunfig.toml +4 -0
  7. package/facebook_ads_library.png +0 -0
  8. package/nul`nif +0 -0
  9. package/package.json +111 -0
  10. package/parsers-config.ts +239 -0
  11. package/rird-1.0.199.tgz +0 -0
  12. package/script/build-windows.ts +54 -0
  13. package/script/build.ts +167 -0
  14. package/script/postinstall.mjs +544 -0
  15. package/script/publish-registries.ts +187 -0
  16. package/script/publish.ts +72 -0
  17. package/script/schema.ts +47 -0
  18. package/src/acp/README.md +164 -0
  19. package/src/acp/agent.ts +1063 -0
  20. package/src/acp/session.ts +101 -0
  21. package/src/acp/types.ts +22 -0
  22. package/src/agent/agent.ts +367 -0
  23. package/src/agent/generate.txt +75 -0
  24. package/src/agent/prompt/compaction.txt +12 -0
  25. package/src/agent/prompt/explore.txt +18 -0
  26. package/src/agent/prompt/summary.txt +10 -0
  27. package/src/agent/prompt/title.txt +36 -0
  28. package/src/auth/index.ts +70 -0
  29. package/src/bun/index.ts +114 -0
  30. package/src/bus/bus-event.ts +43 -0
  31. package/src/bus/global.ts +10 -0
  32. package/src/bus/index.ts +105 -0
  33. package/src/cli/bootstrap.ts +17 -0
  34. package/src/cli/cmd/acp.ts +88 -0
  35. package/src/cli/cmd/agent.ts +256 -0
  36. package/src/cli/cmd/auth.ts +391 -0
  37. package/src/cli/cmd/cmd.ts +7 -0
  38. package/src/cli/cmd/debug/config.ts +15 -0
  39. package/src/cli/cmd/debug/file.ts +91 -0
  40. package/src/cli/cmd/debug/index.ts +43 -0
  41. package/src/cli/cmd/debug/lsp.ts +48 -0
  42. package/src/cli/cmd/debug/ripgrep.ts +83 -0
  43. package/src/cli/cmd/debug/scrap.ts +15 -0
  44. package/src/cli/cmd/debug/skill.ts +15 -0
  45. package/src/cli/cmd/debug/snapshot.ts +48 -0
  46. package/src/cli/cmd/export.ts +88 -0
  47. package/src/cli/cmd/generate.ts +38 -0
  48. package/src/cli/cmd/github.ts +1400 -0
  49. package/src/cli/cmd/import.ts +98 -0
  50. package/src/cli/cmd/mcp.ts +654 -0
  51. package/src/cli/cmd/models.ts +77 -0
  52. package/src/cli/cmd/pr.ts +112 -0
  53. package/src/cli/cmd/run.ts +368 -0
  54. package/src/cli/cmd/serve.ts +31 -0
  55. package/src/cli/cmd/session.ts +106 -0
  56. package/src/cli/cmd/stats.ts +298 -0
  57. package/src/cli/cmd/tui/app.tsx +696 -0
  58. package/src/cli/cmd/tui/attach.ts +30 -0
  59. package/src/cli/cmd/tui/component/border.tsx +21 -0
  60. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  61. package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
  62. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  63. package/src/cli/cmd/tui/component/dialog-model.tsx +245 -0
  64. package/src/cli/cmd/tui/component/dialog-provider.tsx +224 -0
  65. package/src/cli/cmd/tui/component/dialog-session-list.tsx +102 -0
  66. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  67. package/src/cli/cmd/tui/component/dialog-stash.tsx +86 -0
  68. package/src/cli/cmd/tui/component/dialog-status.tsx +162 -0
  69. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  70. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  71. package/src/cli/cmd/tui/component/did-you-know.tsx +85 -0
  72. package/src/cli/cmd/tui/component/logo.tsx +35 -0
  73. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +574 -0
  74. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  75. package/src/cli/cmd/tui/component/prompt/index.tsx +1090 -0
  76. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  77. package/src/cli/cmd/tui/component/tips.ts +27 -0
  78. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  79. package/src/cli/cmd/tui/context/args.tsx +14 -0
  80. package/src/cli/cmd/tui/context/directory.ts +13 -0
  81. package/src/cli/cmd/tui/context/exit.tsx +23 -0
  82. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  83. package/src/cli/cmd/tui/context/keybind.tsx +101 -0
  84. package/src/cli/cmd/tui/context/kv.tsx +49 -0
  85. package/src/cli/cmd/tui/context/local.tsx +354 -0
  86. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  87. package/src/cli/cmd/tui/context/route.tsx +46 -0
  88. package/src/cli/cmd/tui/context/sdk.tsx +74 -0
  89. package/src/cli/cmd/tui/context/sync.tsx +372 -0
  90. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  91. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  92. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  93. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  94. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  95. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  96. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  97. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  98. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  99. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  100. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  101. package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
  102. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  103. package/src/cli/cmd/tui/context/theme/lucent-orng.json +227 -0
  104. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  105. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  106. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  107. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  108. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  109. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  110. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  111. package/src/cli/cmd/tui/context/theme/orng.json +245 -0
  112. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  113. package/src/cli/cmd/tui/context/theme/rird.json +245 -0
  114. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  115. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  116. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  117. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  118. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  119. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  120. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  121. package/src/cli/cmd/tui/context/theme.tsx +1109 -0
  122. package/src/cli/cmd/tui/event.ts +40 -0
  123. package/src/cli/cmd/tui/routes/home.tsx +138 -0
  124. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  125. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  126. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  127. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  128. package/src/cli/cmd/tui/routes/session/footer.tsx +88 -0
  129. package/src/cli/cmd/tui/routes/session/header.tsx +125 -0
  130. package/src/cli/cmd/tui/routes/session/index.tsx +1864 -0
  131. package/src/cli/cmd/tui/routes/session/sidebar.tsx +318 -0
  132. package/src/cli/cmd/tui/spawn.ts +60 -0
  133. package/src/cli/cmd/tui/thread.ts +142 -0
  134. package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
  135. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
  136. package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
  137. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
  138. package/src/cli/cmd/tui/ui/dialog-select.tsx +332 -0
  139. package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
  140. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  141. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  142. package/src/cli/cmd/tui/util/clipboard.ts +127 -0
  143. package/src/cli/cmd/tui/util/editor.ts +32 -0
  144. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  145. package/src/cli/cmd/tui/worker.ts +63 -0
  146. package/src/cli/cmd/uninstall.ts +344 -0
  147. package/src/cli/cmd/upgrade.ts +100 -0
  148. package/src/cli/cmd/web.ts +84 -0
  149. package/src/cli/error.ts +56 -0
  150. package/src/cli/ui.ts +84 -0
  151. package/src/cli/upgrade.ts +25 -0
  152. package/src/command/index.ts +80 -0
  153. package/src/command/template/initialize.txt +10 -0
  154. package/src/command/template/review.txt +97 -0
  155. package/src/config/config.ts +995 -0
  156. package/src/config/markdown.ts +41 -0
  157. package/src/env/index.ts +26 -0
  158. package/src/file/ignore.ts +83 -0
  159. package/src/file/index.ts +328 -0
  160. package/src/file/ripgrep.ts +393 -0
  161. package/src/file/time.ts +64 -0
  162. package/src/file/watcher.ts +103 -0
  163. package/src/flag/flag.ts +46 -0
  164. package/src/format/formatter.ts +315 -0
  165. package/src/format/index.ts +137 -0
  166. package/src/global/index.ts +52 -0
  167. package/src/id/id.ts +73 -0
  168. package/src/ide/index.ts +76 -0
  169. package/src/index.ts +240 -0
  170. package/src/installation/index.ts +239 -0
  171. package/src/lsp/client.ts +229 -0
  172. package/src/lsp/index.ts +485 -0
  173. package/src/lsp/language.ts +116 -0
  174. package/src/lsp/server.ts +1895 -0
  175. package/src/mcp/auth.ts +135 -0
  176. package/src/mcp/index.ts +690 -0
  177. package/src/mcp/oauth-callback.ts +200 -0
  178. package/src/mcp/oauth-provider.ts +154 -0
  179. package/src/patch/index.ts +622 -0
  180. package/src/permission/index.ts +199 -0
  181. package/src/plugin/index.ts +91 -0
  182. package/src/project/bootstrap.ts +31 -0
  183. package/src/project/instance.ts +78 -0
  184. package/src/project/project.ts +221 -0
  185. package/src/project/state.ts +65 -0
  186. package/src/project/vcs.ts +76 -0
  187. package/src/provider/auth.ts +143 -0
  188. package/src/provider/models-macro.ts +11 -0
  189. package/src/provider/models.ts +106 -0
  190. package/src/provider/provider.ts +1071 -0
  191. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  192. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  193. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
  194. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  195. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
  196. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  197. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  198. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  199. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
  200. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  201. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  202. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  203. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  204. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  205. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  206. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  207. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  208. package/src/provider/transform.ts +455 -0
  209. package/src/pty/index.ts +231 -0
  210. package/src/security/guardrails.test.ts +341 -0
  211. package/src/security/guardrails.ts +558 -0
  212. package/src/security/index.ts +19 -0
  213. package/src/server/error.ts +36 -0
  214. package/src/server/project.ts +79 -0
  215. package/src/server/server.ts +2642 -0
  216. package/src/server/tui.ts +71 -0
  217. package/src/session/compaction.ts +223 -0
  218. package/src/session/index.ts +461 -0
  219. package/src/session/llm.ts +201 -0
  220. package/src/session/message-v2.ts +690 -0
  221. package/src/session/message.ts +189 -0
  222. package/src/session/processor.ts +409 -0
  223. package/src/session/prompt/act-switch.txt +5 -0
  224. package/src/session/prompt/anthropic-20250930.txt +166 -0
  225. package/src/session/prompt/anthropic.txt +85 -0
  226. package/src/session/prompt/anthropic_spoof.txt +1 -0
  227. package/src/session/prompt/beast.txt +103 -0
  228. package/src/session/prompt/codex.txt +304 -0
  229. package/src/session/prompt/copilot-gpt-5.txt +138 -0
  230. package/src/session/prompt/gemini.txt +85 -0
  231. package/src/session/prompt/max-steps.txt +16 -0
  232. package/src/session/prompt/plan-reminder-anthropic.txt +35 -0
  233. package/src/session/prompt/plan.txt +24 -0
  234. package/src/session/prompt/polaris.txt +84 -0
  235. package/src/session/prompt/qwen.txt +106 -0
  236. package/src/session/prompt.ts +1509 -0
  237. package/src/session/retry.ts +86 -0
  238. package/src/session/revert.ts +108 -0
  239. package/src/session/sensitive-filter.test.ts +327 -0
  240. package/src/session/sensitive-filter.ts +466 -0
  241. package/src/session/status.ts +76 -0
  242. package/src/session/summary.ts +194 -0
  243. package/src/session/system.ts +120 -0
  244. package/src/session/todo.ts +37 -0
  245. package/src/share/share-next.ts +194 -0
  246. package/src/share/share.ts +87 -0
  247. package/src/shell/shell.ts +67 -0
  248. package/src/skill/index.ts +1 -0
  249. package/src/skill/skill.ts +83 -0
  250. package/src/snapshot/index.ts +197 -0
  251. package/src/storage/storage.ts +226 -0
  252. package/src/tests/agent.test.ts +308 -0
  253. package/src/tests/build-guards.test.ts +267 -0
  254. package/src/tests/config.test.ts +664 -0
  255. package/src/tests/tool-registry.test.ts +589 -0
  256. package/src/tool/bash.ts +317 -0
  257. package/src/tool/bash.txt +158 -0
  258. package/src/tool/batch.ts +175 -0
  259. package/src/tool/batch.txt +24 -0
  260. package/src/tool/codesearch.ts +168 -0
  261. package/src/tool/codesearch.txt +12 -0
  262. package/src/tool/edit.ts +675 -0
  263. package/src/tool/edit.txt +10 -0
  264. package/src/tool/glob.ts +65 -0
  265. package/src/tool/glob.txt +6 -0
  266. package/src/tool/grep.ts +121 -0
  267. package/src/tool/grep.txt +8 -0
  268. package/src/tool/invalid.ts +17 -0
  269. package/src/tool/ls.ts +110 -0
  270. package/src/tool/ls.txt +1 -0
  271. package/src/tool/lsp-diagnostics.ts +26 -0
  272. package/src/tool/lsp-diagnostics.txt +1 -0
  273. package/src/tool/lsp-hover.ts +31 -0
  274. package/src/tool/lsp-hover.txt +1 -0
  275. package/src/tool/lsp.ts +87 -0
  276. package/src/tool/lsp.txt +19 -0
  277. package/src/tool/multiedit.ts +46 -0
  278. package/src/tool/multiedit.txt +41 -0
  279. package/src/tool/patch.ts +233 -0
  280. package/src/tool/patch.txt +1 -0
  281. package/src/tool/read.ts +219 -0
  282. package/src/tool/read.txt +12 -0
  283. package/src/tool/registry.ts +162 -0
  284. package/src/tool/skill.ts +100 -0
  285. package/src/tool/task.ts +136 -0
  286. package/src/tool/task.txt +51 -0
  287. package/src/tool/todo.ts +39 -0
  288. package/src/tool/todoread.txt +14 -0
  289. package/src/tool/todowrite.txt +167 -0
  290. package/src/tool/tool.ts +71 -0
  291. package/src/tool/webfetch.ts +198 -0
  292. package/src/tool/webfetch.txt +13 -0
  293. package/src/tool/websearch.ts +180 -0
  294. package/src/tool/websearch.txt +11 -0
  295. package/src/tool/write.ts +110 -0
  296. package/src/tool/write.txt +8 -0
  297. package/src/util/archive.ts +16 -0
  298. package/src/util/color.ts +19 -0
  299. package/src/util/context.ts +25 -0
  300. package/src/util/defer.ts +12 -0
  301. package/src/util/eventloop.ts +20 -0
  302. package/src/util/filesystem.ts +83 -0
  303. package/src/util/fn.ts +11 -0
  304. package/src/util/iife.ts +3 -0
  305. package/src/util/keybind.ts +102 -0
  306. package/src/util/lazy.ts +11 -0
  307. package/src/util/license.ts +325 -0
  308. package/src/util/locale.ts +81 -0
  309. package/src/util/lock.ts +98 -0
  310. package/src/util/log.ts +180 -0
  311. package/src/util/queue.ts +32 -0
  312. package/src/util/rpc.ts +42 -0
  313. package/src/util/scrap.ts +10 -0
  314. package/src/util/signal.ts +12 -0
  315. package/src/util/timeout.ts +14 -0
  316. package/src/util/token.ts +7 -0
  317. package/src/util/wildcard.ts +54 -0
  318. package/sst-env.d.ts +9 -0
  319. package/test/agent/agent.test.ts +146 -0
  320. package/test/bun.test.ts +53 -0
  321. package/test/cli/github-remote.test.ts +80 -0
  322. package/test/config/agent-color.test.ts +66 -0
  323. package/test/config/config.test.ts +535 -0
  324. package/test/config/markdown.test.ts +89 -0
  325. package/test/file/ignore.test.ts +10 -0
  326. package/test/fixture/fixture.ts +36 -0
  327. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  328. package/test/ide/ide.test.ts +82 -0
  329. package/test/keybind.test.ts +421 -0
  330. package/test/lsp/client.test.ts +95 -0
  331. package/test/mcp/headers.test.ts +153 -0
  332. package/test/patch/patch.test.ts +348 -0
  333. package/test/preload.ts +57 -0
  334. package/test/project/project.test.ts +72 -0
  335. package/test/provider/provider.test.ts +1809 -0
  336. package/test/provider/transform.test.ts +411 -0
  337. package/test/session/retry.test.ts +111 -0
  338. package/test/session/session.test.ts +71 -0
  339. package/test/skill/skill.test.ts +131 -0
  340. package/test/snapshot/snapshot.test.ts +939 -0
  341. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  342. package/test/tool/bash.test.ts +434 -0
  343. package/test/tool/grep.test.ts +108 -0
  344. package/test/tool/patch.test.ts +259 -0
  345. package/test/tool/read.test.ts +42 -0
  346. package/test/util/iife.test.ts +36 -0
  347. package/test/util/lazy.test.ts +50 -0
  348. package/test/util/timeout.test.ts +21 -0
  349. package/test/util/wildcard.test.ts +55 -0
  350. package/tsconfig.json +16 -0
@@ -0,0 +1,9 @@
1
+ // Bun Snapshot v1, https://bun.sh/docs/test/snapshots
2
+
3
+ exports[`tool.ls basic 1`] = `
4
+ "packages/opencode/test/fixtures/example/
5
+ broken.ts
6
+ cli.ts
7
+ ink.tsx
8
+ "
9
+ `;
@@ -0,0 +1,434 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import path from "path"
3
+ import { BashTool } from "../../src/tool/bash"
4
+ import { Instance } from "../../src/project/instance"
5
+ import { Permission } from "../../src/permission"
6
+ import { tmpdir } from "../fixture/fixture"
7
+
8
+ const ctx = {
9
+ sessionID: "test",
10
+ messageID: "",
11
+ callID: "",
12
+ agent: "act",
13
+ abort: AbortSignal.any([]),
14
+ metadata: () => {},
15
+ }
16
+
17
+ const projectRoot = path.join(__dirname, "../..")
18
+
19
+ describe("tool.bash", () => {
20
+ test("basic", async () => {
21
+ await Instance.provide({
22
+ directory: projectRoot,
23
+ fn: async () => {
24
+ const bash = await BashTool.init()
25
+ const result = await bash.execute(
26
+ {
27
+ command: "echo 'test'",
28
+ description: "Echo test message",
29
+ },
30
+ ctx,
31
+ )
32
+ expect(result.metadata.exit).toBe(0)
33
+ expect(result.metadata.output).toContain("test")
34
+ },
35
+ })
36
+ })
37
+ })
38
+
39
+ describe("tool.bash permissions", () => {
40
+ test("allows command matching allow pattern", async () => {
41
+ await using tmp = await tmpdir({
42
+ init: async (dir) => {
43
+ await Bun.write(
44
+ path.join(dir, "opencode.json"),
45
+ JSON.stringify({
46
+ permission: {
47
+ bash: {
48
+ "echo *": "allow",
49
+ "*": "deny",
50
+ },
51
+ },
52
+ }),
53
+ )
54
+ },
55
+ })
56
+ await Instance.provide({
57
+ directory: tmp.path,
58
+ fn: async () => {
59
+ const bash = await BashTool.init()
60
+ const result = await bash.execute(
61
+ {
62
+ command: "echo hello",
63
+ description: "Echo hello",
64
+ },
65
+ ctx,
66
+ )
67
+ expect(result.metadata.exit).toBe(0)
68
+ expect(result.metadata.output).toContain("hello")
69
+ },
70
+ })
71
+ })
72
+
73
+ test("denies command matching deny pattern", async () => {
74
+ await using tmp = await tmpdir({
75
+ init: async (dir) => {
76
+ await Bun.write(
77
+ path.join(dir, "opencode.json"),
78
+ JSON.stringify({
79
+ permission: {
80
+ bash: {
81
+ "curl *": "deny",
82
+ "*": "allow",
83
+ },
84
+ },
85
+ }),
86
+ )
87
+ },
88
+ })
89
+ await Instance.provide({
90
+ directory: tmp.path,
91
+ fn: async () => {
92
+ const bash = await BashTool.init()
93
+ await expect(
94
+ bash.execute(
95
+ {
96
+ command: "curl https://example.com",
97
+ description: "Fetch URL",
98
+ },
99
+ ctx,
100
+ ),
101
+ ).rejects.toThrow("restricted")
102
+ },
103
+ })
104
+ })
105
+
106
+ test("denies all commands with wildcard deny", async () => {
107
+ await using tmp = await tmpdir({
108
+ init: async (dir) => {
109
+ await Bun.write(
110
+ path.join(dir, "opencode.json"),
111
+ JSON.stringify({
112
+ permission: {
113
+ bash: {
114
+ "*": "deny",
115
+ },
116
+ },
117
+ }),
118
+ )
119
+ },
120
+ })
121
+ await Instance.provide({
122
+ directory: tmp.path,
123
+ fn: async () => {
124
+ const bash = await BashTool.init()
125
+ await expect(
126
+ bash.execute(
127
+ {
128
+ command: "ls",
129
+ description: "List files",
130
+ },
131
+ ctx,
132
+ ),
133
+ ).rejects.toThrow("restricted")
134
+ },
135
+ })
136
+ })
137
+
138
+ test("more specific pattern overrides general pattern", async () => {
139
+ await using tmp = await tmpdir({
140
+ init: async (dir) => {
141
+ await Bun.write(
142
+ path.join(dir, "opencode.json"),
143
+ JSON.stringify({
144
+ permission: {
145
+ bash: {
146
+ "*": "deny",
147
+ "ls *": "allow",
148
+ "pwd*": "allow",
149
+ },
150
+ },
151
+ }),
152
+ )
153
+ },
154
+ })
155
+ await Instance.provide({
156
+ directory: tmp.path,
157
+ fn: async () => {
158
+ const bash = await BashTool.init()
159
+ // ls should be allowed
160
+ const result = await bash.execute(
161
+ {
162
+ command: "ls -la",
163
+ description: "List files",
164
+ },
165
+ ctx,
166
+ )
167
+ expect(result.metadata.exit).toBe(0)
168
+
169
+ // pwd should be allowed
170
+ const pwd = await bash.execute(
171
+ {
172
+ command: "pwd",
173
+ description: "Print working directory",
174
+ },
175
+ ctx,
176
+ )
177
+ expect(pwd.metadata.exit).toBe(0)
178
+
179
+ // cat should be denied
180
+ await expect(
181
+ bash.execute(
182
+ {
183
+ command: "cat /etc/passwd",
184
+ description: "Read file",
185
+ },
186
+ ctx,
187
+ ),
188
+ ).rejects.toThrow("restricted")
189
+ },
190
+ })
191
+ })
192
+
193
+ test("denies dangerous subcommands while allowing safe ones", async () => {
194
+ await using tmp = await tmpdir({
195
+ init: async (dir) => {
196
+ await Bun.write(
197
+ path.join(dir, "opencode.json"),
198
+ JSON.stringify({
199
+ permission: {
200
+ bash: {
201
+ "find *": "allow",
202
+ "find * -delete*": "deny",
203
+ "find * -exec*": "deny",
204
+ "*": "deny",
205
+ },
206
+ },
207
+ }),
208
+ )
209
+ },
210
+ })
211
+ await Instance.provide({
212
+ directory: tmp.path,
213
+ fn: async () => {
214
+ const bash = await BashTool.init()
215
+ // Basic find should work
216
+ const result = await bash.execute(
217
+ {
218
+ command: "find . -name '*.ts'",
219
+ description: "Find typescript files",
220
+ },
221
+ ctx,
222
+ )
223
+ expect(result.metadata.exit).toBe(0)
224
+
225
+ // find -delete should be denied
226
+ await expect(
227
+ bash.execute(
228
+ {
229
+ command: "find . -name '*.tmp' -delete",
230
+ description: "Delete temp files",
231
+ },
232
+ ctx,
233
+ ),
234
+ ).rejects.toThrow("restricted")
235
+
236
+ // find -exec should be denied
237
+ await expect(
238
+ bash.execute(
239
+ {
240
+ command: "find . -name '*.ts' -exec cat {} \\;",
241
+ description: "Find and cat files",
242
+ },
243
+ ctx,
244
+ ),
245
+ ).rejects.toThrow("restricted")
246
+ },
247
+ })
248
+ })
249
+
250
+ test("allows git read commands while denying writes", async () => {
251
+ await using tmp = await tmpdir({
252
+ git: true,
253
+ init: async (dir) => {
254
+ await Bun.write(
255
+ path.join(dir, "opencode.json"),
256
+ JSON.stringify({
257
+ permission: {
258
+ bash: {
259
+ "git status*": "allow",
260
+ "git log*": "allow",
261
+ "git diff*": "allow",
262
+ "git branch": "allow",
263
+ "git commit *": "deny",
264
+ "git push *": "deny",
265
+ "*": "deny",
266
+ },
267
+ },
268
+ }),
269
+ )
270
+ },
271
+ })
272
+ await Instance.provide({
273
+ directory: tmp.path,
274
+ fn: async () => {
275
+ const bash = await BashTool.init()
276
+ // git status should work
277
+ const status = await bash.execute(
278
+ {
279
+ command: "git status",
280
+ description: "Git status",
281
+ },
282
+ ctx,
283
+ )
284
+ expect(status.metadata.exit).toBe(0)
285
+
286
+ // git log should work
287
+ const log = await bash.execute(
288
+ {
289
+ command: "git log --oneline -5",
290
+ description: "Git log",
291
+ },
292
+ ctx,
293
+ )
294
+ expect(log.metadata.exit).toBe(0)
295
+
296
+ // git commit should be denied
297
+ await expect(
298
+ bash.execute(
299
+ {
300
+ command: "git commit -m 'test'",
301
+ description: "Git commit",
302
+ },
303
+ ctx,
304
+ ),
305
+ ).rejects.toThrow("restricted")
306
+
307
+ // git push should be denied
308
+ await expect(
309
+ bash.execute(
310
+ {
311
+ command: "git push origin main",
312
+ description: "Git push",
313
+ },
314
+ ctx,
315
+ ),
316
+ ).rejects.toThrow("restricted")
317
+ },
318
+ })
319
+ })
320
+
321
+ test("denies external directory access when permission is deny", async () => {
322
+ await using tmp = await tmpdir({
323
+ init: async (dir) => {
324
+ await Bun.write(
325
+ path.join(dir, "opencode.json"),
326
+ JSON.stringify({
327
+ permission: {
328
+ external_directory: "deny",
329
+ bash: {
330
+ "*": "allow",
331
+ },
332
+ },
333
+ }),
334
+ )
335
+ },
336
+ })
337
+ await Instance.provide({
338
+ directory: tmp.path,
339
+ fn: async () => {
340
+ const bash = await BashTool.init()
341
+ // Should deny cd to parent directory (cd is checked for external paths)
342
+ await expect(
343
+ bash.execute(
344
+ {
345
+ command: "cd ../",
346
+ description: "Change to parent directory",
347
+ },
348
+ ctx,
349
+ ),
350
+ ).rejects.toThrow()
351
+ },
352
+ })
353
+ })
354
+
355
+ test("denies workdir outside project when external_directory is deny", async () => {
356
+ await using tmp = await tmpdir({
357
+ init: async (dir) => {
358
+ await Bun.write(
359
+ path.join(dir, "opencode.json"),
360
+ JSON.stringify({
361
+ permission: {
362
+ external_directory: "deny",
363
+ bash: {
364
+ "*": "allow",
365
+ },
366
+ },
367
+ }),
368
+ )
369
+ },
370
+ })
371
+ await Instance.provide({
372
+ directory: tmp.path,
373
+ fn: async () => {
374
+ const bash = await BashTool.init()
375
+ await expect(
376
+ bash.execute(
377
+ {
378
+ command: "ls",
379
+ workdir: "/tmp",
380
+ description: "List /tmp",
381
+ },
382
+ ctx,
383
+ ),
384
+ ).rejects.toThrow()
385
+ },
386
+ })
387
+ })
388
+
389
+ test("handles multiple commands in sequence", async () => {
390
+ await using tmp = await tmpdir({
391
+ init: async (dir) => {
392
+ await Bun.write(
393
+ path.join(dir, "opencode.json"),
394
+ JSON.stringify({
395
+ permission: {
396
+ bash: {
397
+ "echo *": "allow",
398
+ "curl *": "deny",
399
+ "*": "deny",
400
+ },
401
+ },
402
+ }),
403
+ )
404
+ },
405
+ })
406
+ await Instance.provide({
407
+ directory: tmp.path,
408
+ fn: async () => {
409
+ const bash = await BashTool.init()
410
+ // echo && echo should work
411
+ const result = await bash.execute(
412
+ {
413
+ command: "echo foo && echo bar",
414
+ description: "Echo twice",
415
+ },
416
+ ctx,
417
+ )
418
+ expect(result.metadata.output).toContain("foo")
419
+ expect(result.metadata.output).toContain("bar")
420
+
421
+ // echo && curl should fail (curl is denied)
422
+ await expect(
423
+ bash.execute(
424
+ {
425
+ command: "echo hi && curl https://example.com",
426
+ description: "Echo then curl",
427
+ },
428
+ ctx,
429
+ ),
430
+ ).rejects.toThrow("restricted")
431
+ },
432
+ })
433
+ })
434
+ })
@@ -0,0 +1,108 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import path from "path"
3
+ import { GrepTool } from "../../src/tool/grep"
4
+ import { Instance } from "../../src/project/instance"
5
+ import { tmpdir } from "../fixture/fixture"
6
+
7
+ const ctx = {
8
+ sessionID: "test",
9
+ messageID: "",
10
+ callID: "",
11
+ agent: "act",
12
+ abort: AbortSignal.any([]),
13
+ metadata: () => {},
14
+ }
15
+
16
+ const projectRoot = path.join(__dirname, "../..")
17
+
18
+ describe("tool.grep", () => {
19
+ test("basic search", async () => {
20
+ await Instance.provide({
21
+ directory: projectRoot,
22
+ fn: async () => {
23
+ const grep = await GrepTool.init()
24
+ const result = await grep.execute(
25
+ {
26
+ pattern: "export",
27
+ path: path.join(projectRoot, "src/tool"),
28
+ include: "*.ts",
29
+ },
30
+ ctx,
31
+ )
32
+ expect(result.metadata.matches).toBeGreaterThan(0)
33
+ expect(result.output).toContain("Found")
34
+ },
35
+ })
36
+ })
37
+
38
+ test("no matches returns correct output", async () => {
39
+ await using tmp = await tmpdir({
40
+ init: async (dir) => {
41
+ await Bun.write(path.join(dir, "test.txt"), "hello world")
42
+ },
43
+ })
44
+ await Instance.provide({
45
+ directory: tmp.path,
46
+ fn: async () => {
47
+ const grep = await GrepTool.init()
48
+ const result = await grep.execute(
49
+ {
50
+ pattern: "xyznonexistentpatternxyz123",
51
+ path: tmp.path,
52
+ },
53
+ ctx,
54
+ )
55
+ expect(result.metadata.matches).toBe(0)
56
+ expect(result.output).toBe("No files found")
57
+ },
58
+ })
59
+ })
60
+
61
+ test("handles CRLF line endings in output", async () => {
62
+ // This test verifies the regex split handles both \n and \r\n
63
+ await using tmp = await tmpdir({
64
+ init: async (dir) => {
65
+ // Create a test file with content
66
+ await Bun.write(path.join(dir, "test.txt"), "line1\nline2\nline3")
67
+ },
68
+ })
69
+ await Instance.provide({
70
+ directory: tmp.path,
71
+ fn: async () => {
72
+ const grep = await GrepTool.init()
73
+ const result = await grep.execute(
74
+ {
75
+ pattern: "line",
76
+ path: tmp.path,
77
+ },
78
+ ctx,
79
+ )
80
+ expect(result.metadata.matches).toBeGreaterThan(0)
81
+ },
82
+ })
83
+ })
84
+ })
85
+
86
+ describe("CRLF regex handling", () => {
87
+ test("regex correctly splits Unix line endings", () => {
88
+ const unixOutput = "file1.txt|1|content1\nfile2.txt|2|content2\nfile3.txt|3|content3"
89
+ const lines = unixOutput.trim().split(/\r?\n/)
90
+ expect(lines.length).toBe(3)
91
+ expect(lines[0]).toBe("file1.txt|1|content1")
92
+ expect(lines[2]).toBe("file3.txt|3|content3")
93
+ })
94
+
95
+ test("regex correctly splits Windows CRLF line endings", () => {
96
+ const windowsOutput = "file1.txt|1|content1\r\nfile2.txt|2|content2\r\nfile3.txt|3|content3"
97
+ const lines = windowsOutput.trim().split(/\r?\n/)
98
+ expect(lines.length).toBe(3)
99
+ expect(lines[0]).toBe("file1.txt|1|content1")
100
+ expect(lines[2]).toBe("file3.txt|3|content3")
101
+ })
102
+
103
+ test("regex handles mixed line endings", () => {
104
+ const mixedOutput = "file1.txt|1|content1\nfile2.txt|2|content2\r\nfile3.txt|3|content3"
105
+ const lines = mixedOutput.trim().split(/\r?\n/)
106
+ expect(lines.length).toBe(3)
107
+ })
108
+ })