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.
- package/AGENTS.md +27 -0
- package/Dockerfile +18 -0
- package/README.md +15 -0
- package/bin/opencode +336 -0
- package/bin/pty-wrapper.js +285 -0
- package/bunfig.toml +4 -0
- package/facebook_ads_library.png +0 -0
- package/nul`nif +0 -0
- package/package.json +111 -0
- package/parsers-config.ts +239 -0
- package/rird-1.0.199.tgz +0 -0
- package/script/build-windows.ts +54 -0
- package/script/build.ts +167 -0
- package/script/postinstall.mjs +544 -0
- package/script/publish-registries.ts +187 -0
- package/script/publish.ts +72 -0
- package/script/schema.ts +47 -0
- package/src/acp/README.md +164 -0
- package/src/acp/agent.ts +1063 -0
- package/src/acp/session.ts +101 -0
- package/src/acp/types.ts +22 -0
- package/src/agent/agent.ts +367 -0
- package/src/agent/generate.txt +75 -0
- package/src/agent/prompt/compaction.txt +12 -0
- package/src/agent/prompt/explore.txt +18 -0
- package/src/agent/prompt/summary.txt +10 -0
- package/src/agent/prompt/title.txt +36 -0
- package/src/auth/index.ts +70 -0
- package/src/bun/index.ts +114 -0
- package/src/bus/bus-event.ts +43 -0
- package/src/bus/global.ts +10 -0
- package/src/bus/index.ts +105 -0
- package/src/cli/bootstrap.ts +17 -0
- package/src/cli/cmd/acp.ts +88 -0
- package/src/cli/cmd/agent.ts +256 -0
- package/src/cli/cmd/auth.ts +391 -0
- package/src/cli/cmd/cmd.ts +7 -0
- package/src/cli/cmd/debug/config.ts +15 -0
- package/src/cli/cmd/debug/file.ts +91 -0
- package/src/cli/cmd/debug/index.ts +43 -0
- package/src/cli/cmd/debug/lsp.ts +48 -0
- package/src/cli/cmd/debug/ripgrep.ts +83 -0
- package/src/cli/cmd/debug/scrap.ts +15 -0
- package/src/cli/cmd/debug/skill.ts +15 -0
- package/src/cli/cmd/debug/snapshot.ts +48 -0
- package/src/cli/cmd/export.ts +88 -0
- package/src/cli/cmd/generate.ts +38 -0
- package/src/cli/cmd/github.ts +1400 -0
- package/src/cli/cmd/import.ts +98 -0
- package/src/cli/cmd/mcp.ts +654 -0
- package/src/cli/cmd/models.ts +77 -0
- package/src/cli/cmd/pr.ts +112 -0
- package/src/cli/cmd/run.ts +368 -0
- package/src/cli/cmd/serve.ts +31 -0
- package/src/cli/cmd/session.ts +106 -0
- package/src/cli/cmd/stats.ts +298 -0
- package/src/cli/cmd/tui/app.tsx +696 -0
- package/src/cli/cmd/tui/attach.ts +30 -0
- package/src/cli/cmd/tui/component/border.tsx +21 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-model.tsx +245 -0
- package/src/cli/cmd/tui/component/dialog-provider.tsx +224 -0
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +102 -0
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-stash.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-status.tsx +162 -0
- package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
- package/src/cli/cmd/tui/component/did-you-know.tsx +85 -0
- package/src/cli/cmd/tui/component/logo.tsx +35 -0
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +574 -0
- package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
- package/src/cli/cmd/tui/component/prompt/index.tsx +1090 -0
- package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
- package/src/cli/cmd/tui/component/tips.ts +27 -0
- package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
- package/src/cli/cmd/tui/context/args.tsx +14 -0
- package/src/cli/cmd/tui/context/directory.ts +13 -0
- package/src/cli/cmd/tui/context/exit.tsx +23 -0
- package/src/cli/cmd/tui/context/helper.tsx +25 -0
- package/src/cli/cmd/tui/context/keybind.tsx +101 -0
- package/src/cli/cmd/tui/context/kv.tsx +49 -0
- package/src/cli/cmd/tui/context/local.tsx +354 -0
- package/src/cli/cmd/tui/context/prompt.tsx +18 -0
- package/src/cli/cmd/tui/context/route.tsx +46 -0
- package/src/cli/cmd/tui/context/sdk.tsx +74 -0
- package/src/cli/cmd/tui/context/sync.tsx +372 -0
- package/src/cli/cmd/tui/context/theme/aura.json +69 -0
- package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
- package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
- package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
- package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
- package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
- package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
- package/src/cli/cmd/tui/context/theme/github.json +233 -0
- package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
- package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
- package/src/cli/cmd/tui/context/theme/lucent-orng.json +227 -0
- package/src/cli/cmd/tui/context/theme/material.json +235 -0
- package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
- package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
- package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
- package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
- package/src/cli/cmd/tui/context/theme/nord.json +223 -0
- package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
- package/src/cli/cmd/tui/context/theme/orng.json +245 -0
- package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
- package/src/cli/cmd/tui/context/theme/rird.json +245 -0
- package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
- package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
- package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
- package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
- package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
- package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
- package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
- package/src/cli/cmd/tui/context/theme.tsx +1109 -0
- package/src/cli/cmd/tui/event.ts +40 -0
- package/src/cli/cmd/tui/routes/home.tsx +138 -0
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/cli/cmd/tui/routes/session/footer.tsx +88 -0
- package/src/cli/cmd/tui/routes/session/header.tsx +125 -0
- package/src/cli/cmd/tui/routes/session/index.tsx +1864 -0
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +318 -0
- package/src/cli/cmd/tui/spawn.ts +60 -0
- package/src/cli/cmd/tui/thread.ts +142 -0
- package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
- package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
- package/src/cli/cmd/tui/ui/dialog-select.tsx +332 -0
- package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
- package/src/cli/cmd/tui/ui/spinner.ts +368 -0
- package/src/cli/cmd/tui/ui/toast.tsx +100 -0
- package/src/cli/cmd/tui/util/clipboard.ts +127 -0
- package/src/cli/cmd/tui/util/editor.ts +32 -0
- package/src/cli/cmd/tui/util/terminal.ts +114 -0
- package/src/cli/cmd/tui/worker.ts +63 -0
- package/src/cli/cmd/uninstall.ts +344 -0
- package/src/cli/cmd/upgrade.ts +100 -0
- package/src/cli/cmd/web.ts +84 -0
- package/src/cli/error.ts +56 -0
- package/src/cli/ui.ts +84 -0
- package/src/cli/upgrade.ts +25 -0
- package/src/command/index.ts +80 -0
- package/src/command/template/initialize.txt +10 -0
- package/src/command/template/review.txt +97 -0
- package/src/config/config.ts +995 -0
- package/src/config/markdown.ts +41 -0
- package/src/env/index.ts +26 -0
- package/src/file/ignore.ts +83 -0
- package/src/file/index.ts +328 -0
- package/src/file/ripgrep.ts +393 -0
- package/src/file/time.ts +64 -0
- package/src/file/watcher.ts +103 -0
- package/src/flag/flag.ts +46 -0
- package/src/format/formatter.ts +315 -0
- package/src/format/index.ts +137 -0
- package/src/global/index.ts +52 -0
- package/src/id/id.ts +73 -0
- package/src/ide/index.ts +76 -0
- package/src/index.ts +240 -0
- package/src/installation/index.ts +239 -0
- package/src/lsp/client.ts +229 -0
- package/src/lsp/index.ts +485 -0
- package/src/lsp/language.ts +116 -0
- package/src/lsp/server.ts +1895 -0
- package/src/mcp/auth.ts +135 -0
- package/src/mcp/index.ts +690 -0
- package/src/mcp/oauth-callback.ts +200 -0
- package/src/mcp/oauth-provider.ts +154 -0
- package/src/patch/index.ts +622 -0
- package/src/permission/index.ts +199 -0
- package/src/plugin/index.ts +91 -0
- package/src/project/bootstrap.ts +31 -0
- package/src/project/instance.ts +78 -0
- package/src/project/project.ts +221 -0
- package/src/project/state.ts +65 -0
- package/src/project/vcs.ts +76 -0
- package/src/provider/auth.ts +143 -0
- package/src/provider/models-macro.ts +11 -0
- package/src/provider/models.ts +106 -0
- package/src/provider/provider.ts +1071 -0
- package/src/provider/sdk/openai-compatible/src/README.md +5 -0
- package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
- package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
- package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
- package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
- package/src/provider/transform.ts +455 -0
- package/src/pty/index.ts +231 -0
- package/src/security/guardrails.test.ts +341 -0
- package/src/security/guardrails.ts +558 -0
- package/src/security/index.ts +19 -0
- package/src/server/error.ts +36 -0
- package/src/server/project.ts +79 -0
- package/src/server/server.ts +2642 -0
- package/src/server/tui.ts +71 -0
- package/src/session/compaction.ts +223 -0
- package/src/session/index.ts +461 -0
- package/src/session/llm.ts +201 -0
- package/src/session/message-v2.ts +690 -0
- package/src/session/message.ts +189 -0
- package/src/session/processor.ts +409 -0
- package/src/session/prompt/act-switch.txt +5 -0
- package/src/session/prompt/anthropic-20250930.txt +166 -0
- package/src/session/prompt/anthropic.txt +85 -0
- package/src/session/prompt/anthropic_spoof.txt +1 -0
- package/src/session/prompt/beast.txt +103 -0
- package/src/session/prompt/codex.txt +304 -0
- package/src/session/prompt/copilot-gpt-5.txt +138 -0
- package/src/session/prompt/gemini.txt +85 -0
- package/src/session/prompt/max-steps.txt +16 -0
- package/src/session/prompt/plan-reminder-anthropic.txt +35 -0
- package/src/session/prompt/plan.txt +24 -0
- package/src/session/prompt/polaris.txt +84 -0
- package/src/session/prompt/qwen.txt +106 -0
- package/src/session/prompt.ts +1509 -0
- package/src/session/retry.ts +86 -0
- package/src/session/revert.ts +108 -0
- package/src/session/sensitive-filter.test.ts +327 -0
- package/src/session/sensitive-filter.ts +466 -0
- package/src/session/status.ts +76 -0
- package/src/session/summary.ts +194 -0
- package/src/session/system.ts +120 -0
- package/src/session/todo.ts +37 -0
- package/src/share/share-next.ts +194 -0
- package/src/share/share.ts +87 -0
- package/src/shell/shell.ts +67 -0
- package/src/skill/index.ts +1 -0
- package/src/skill/skill.ts +83 -0
- package/src/snapshot/index.ts +197 -0
- package/src/storage/storage.ts +226 -0
- package/src/tests/agent.test.ts +308 -0
- package/src/tests/build-guards.test.ts +267 -0
- package/src/tests/config.test.ts +664 -0
- package/src/tests/tool-registry.test.ts +589 -0
- package/src/tool/bash.ts +317 -0
- package/src/tool/bash.txt +158 -0
- package/src/tool/batch.ts +175 -0
- package/src/tool/batch.txt +24 -0
- package/src/tool/codesearch.ts +168 -0
- package/src/tool/codesearch.txt +12 -0
- package/src/tool/edit.ts +675 -0
- package/src/tool/edit.txt +10 -0
- package/src/tool/glob.ts +65 -0
- package/src/tool/glob.txt +6 -0
- package/src/tool/grep.ts +121 -0
- package/src/tool/grep.txt +8 -0
- package/src/tool/invalid.ts +17 -0
- package/src/tool/ls.ts +110 -0
- package/src/tool/ls.txt +1 -0
- package/src/tool/lsp-diagnostics.ts +26 -0
- package/src/tool/lsp-diagnostics.txt +1 -0
- package/src/tool/lsp-hover.ts +31 -0
- package/src/tool/lsp-hover.txt +1 -0
- package/src/tool/lsp.ts +87 -0
- package/src/tool/lsp.txt +19 -0
- package/src/tool/multiedit.ts +46 -0
- package/src/tool/multiedit.txt +41 -0
- package/src/tool/patch.ts +233 -0
- package/src/tool/patch.txt +1 -0
- package/src/tool/read.ts +219 -0
- package/src/tool/read.txt +12 -0
- package/src/tool/registry.ts +162 -0
- package/src/tool/skill.ts +100 -0
- package/src/tool/task.ts +136 -0
- package/src/tool/task.txt +51 -0
- package/src/tool/todo.ts +39 -0
- package/src/tool/todoread.txt +14 -0
- package/src/tool/todowrite.txt +167 -0
- package/src/tool/tool.ts +71 -0
- package/src/tool/webfetch.ts +198 -0
- package/src/tool/webfetch.txt +13 -0
- package/src/tool/websearch.ts +180 -0
- package/src/tool/websearch.txt +11 -0
- package/src/tool/write.ts +110 -0
- package/src/tool/write.txt +8 -0
- package/src/util/archive.ts +16 -0
- package/src/util/color.ts +19 -0
- package/src/util/context.ts +25 -0
- package/src/util/defer.ts +12 -0
- package/src/util/eventloop.ts +20 -0
- package/src/util/filesystem.ts +83 -0
- package/src/util/fn.ts +11 -0
- package/src/util/iife.ts +3 -0
- package/src/util/keybind.ts +102 -0
- package/src/util/lazy.ts +11 -0
- package/src/util/license.ts +325 -0
- package/src/util/locale.ts +81 -0
- package/src/util/lock.ts +98 -0
- package/src/util/log.ts +180 -0
- package/src/util/queue.ts +32 -0
- package/src/util/rpc.ts +42 -0
- package/src/util/scrap.ts +10 -0
- package/src/util/signal.ts +12 -0
- package/src/util/timeout.ts +14 -0
- package/src/util/token.ts +7 -0
- package/src/util/wildcard.ts +54 -0
- package/sst-env.d.ts +9 -0
- package/test/agent/agent.test.ts +146 -0
- package/test/bun.test.ts +53 -0
- package/test/cli/github-remote.test.ts +80 -0
- package/test/config/agent-color.test.ts +66 -0
- package/test/config/config.test.ts +535 -0
- package/test/config/markdown.test.ts +89 -0
- package/test/file/ignore.test.ts +10 -0
- package/test/fixture/fixture.ts +36 -0
- package/test/fixture/lsp/fake-lsp-server.js +77 -0
- package/test/ide/ide.test.ts +82 -0
- package/test/keybind.test.ts +421 -0
- package/test/lsp/client.test.ts +95 -0
- package/test/mcp/headers.test.ts +153 -0
- package/test/patch/patch.test.ts +348 -0
- package/test/preload.ts +57 -0
- package/test/project/project.test.ts +72 -0
- package/test/provider/provider.test.ts +1809 -0
- package/test/provider/transform.test.ts +411 -0
- package/test/session/retry.test.ts +111 -0
- package/test/session/session.test.ts +71 -0
- package/test/skill/skill.test.ts +131 -0
- package/test/snapshot/snapshot.test.ts +939 -0
- package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
- package/test/tool/bash.test.ts +434 -0
- package/test/tool/grep.test.ts +108 -0
- package/test/tool/patch.test.ts +259 -0
- package/test/tool/read.test.ts +42 -0
- package/test/util/iife.test.ts +36 -0
- package/test/util/lazy.test.ts +50 -0
- package/test/util/timeout.test.ts +21 -0
- package/test/util/wildcard.test.ts +55 -0
- package/tsconfig.json +16 -0
|
@@ -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
|
+
})
|