diff --git a/bun.lock b/bun.lock index e0f417e6f6..9797b47ab7 100644 --- a/bun.lock +++ b/bun.lock @@ -12,7 +12,6 @@ }, "devDependencies": { "@tsconfig/bun": "catalog:", - "@types/better-sqlite3": "7.6.13", "@types/pg": "8.18.0", "@typescript/native-preview": "catalog:", "husky": "9.1.7", @@ -42,7 +41,6 @@ "optionalDependencies": { "@databricks/sql": "^1.0.0", "@google-cloud/bigquery": "^8.0.0", - "better-sqlite3": "^11.0.0", "duckdb": "^1.0.0", "mssql": "^11.0.0", "mysql2": "^3.0.0", @@ -1286,7 +1284,7 @@ "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], @@ -3126,10 +3124,10 @@ "table-layout/typical": ["typical@7.3.0", "", {}, "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw=="], - "tar/chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], - "tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + "tar-stream/bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], "tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], diff --git a/packages/opencode/test/tool/bash.test.ts b/packages/opencode/test/tool/bash.test.ts index f947398b37..c340c4d38e 100644 --- a/packages/opencode/test/tool/bash.test.ts +++ b/packages/opencode/test/tool/bash.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "bun:test" +import fs from "fs/promises" import os from "os" import path from "path" import { BashTool } from "../../src/tool/bash" @@ -401,3 +402,86 @@ describe("tool.bash truncation", () => { }) }) }) + +// altimate_change start — tests for .opencode/tools/ PATH injection (skill CLI feature) +describe("tool.bash PATH injection", () => { + test(".opencode/tools/ is prepended to PATH so user tools are executable", async () => { + await using tmp = await tmpdir({ git: true }) + const toolsDir = path.join(tmp.path, ".opencode", "tools") + await fs.mkdir(toolsDir, { recursive: true }) + await fs.writeFile( + path.join(toolsDir, "my-custom-tool"), + '#!/usr/bin/env bash\necho "custom-tool-output"', + { mode: 0o755 }, + ) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const bash = await BashTool.init() + const result = await bash.execute( + { + command: "my-custom-tool", + description: "Run custom tool from .opencode/tools/", + }, + ctx, + ) + expect(result.metadata.exit).toBe(0) + expect(result.metadata.output).toContain("custom-tool-output") + }, + }) + }) + + test("PATH does not contain duplicate .opencode/tools/ entries", async () => { + await using tmp = await tmpdir({ git: true }) + const toolsDir = path.join(tmp.path, ".opencode", "tools") + await fs.mkdir(toolsDir, { recursive: true }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const bash = await BashTool.init() + const result = await bash.execute( + { + command: 'echo "$PATH"', + description: "Print PATH", + }, + ctx, + ) + const pathValue = result.metadata.output.trim() + const sep = process.platform === "win32" ? ";" : ":" + const entries = pathValue.split(sep) + const count = entries.filter((e: string) => e === toolsDir).length + expect(count).toBeLessThanOrEqual(1) + }, + }) + }) + + test("ALTIMATE_BIN_DIR is on PATH with higher priority than system dirs", async () => { + await using tmp = await tmpdir({ git: true }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const bash = await BashTool.init() + const result = await bash.execute( + { + command: 'echo "$PATH"', + description: "Print PATH to check ALTIMATE_BIN_DIR", + }, + ctx, + ) + const pathValue = result.metadata.output.trim() + const binDir = process.env.ALTIMATE_BIN_DIR + if (binDir) { + const sep = process.platform === "win32" ? ";" : ":" + const entries = pathValue.split(sep) + const binIdx = entries.indexOf(binDir) + // ALTIMATE_BIN_DIR should appear early in PATH (prepended, not appended) + expect(binIdx).toBeGreaterThanOrEqual(0) + expect(binIdx).toBeLessThan(5) + } + }, + }) + }) +}) +// altimate_change end