wip: permissions

This commit is contained in:
Dax Raad
2025-07-31 16:38:31 -04:00
parent 168350c981
commit a2191ce6fb
11 changed files with 324 additions and 41 deletions

View File

@@ -38,7 +38,7 @@ for (const [os, arch] of targets) {
await $`CGO_ENABLED=0 GOOS=${os} GOARCH=${GOARCH[arch]} go build -ldflags="-s -w -X main.Version=${version}" -o ../opencode/dist/${name}/bin/tui ../tui/cmd/opencode/main.go`.cwd(
"../tui",
)
await $`bun build --define OPENCODE_VERSION="'${version}'" --compile --minify --target=bun-${os}-${arch} --outfile=dist/${name}/bin/opencode ./src/index.ts ./dist/${name}/bin/tui`
await $`bun build --define OPENCODE_TUI_PATH="'../../../dist/${name}/bin/tui'" --define OPENCODE_VERSION="'${version}'" --compile --target=bun-${os}-${arch} --outfile=dist/${name}/bin/opencode ./src/index.ts`
await $`rm -rf ./dist/${name}/bin/tui`
await Bun.file(`dist/${name}/package.json`).write(
JSON.stringify(

View File

@@ -14,6 +14,16 @@ import { FileWatcher } from "../../file/watch"
import { Mode } from "../../session/mode"
import { Ide } from "../../ide"
declare global {
const OPENCODE_TUI_PATH: string
}
if (typeof OPENCODE_TUI_PATH !== "undefined") {
await import(OPENCODE_TUI_PATH as string, {
with: { type: "file" },
})
}
export const TuiCommand = cmd({
command: "$0 [project]",
describe: "start opencode tui",
@@ -71,16 +81,16 @@ export const TuiCommand = cmd({
let cmd = ["go", "run", "./main.go"]
let cwd = Bun.fileURLToPath(new URL("../../../../tui/cmd/opencode", import.meta.url))
if (Bun.embeddedFiles.length > 0) {
const blob = Bun.embeddedFiles[0] as File
let binaryName = blob.name
const tui = Bun.embeddedFiles.find((item) => (item as File).name.includes("tui")) as File
if (tui) {
let binaryName = tui.name
if (process.platform === "win32" && !binaryName.endsWith(".exe")) {
binaryName += ".exe"
}
const binary = path.join(Global.Path.cache, "tui", binaryName)
const file = Bun.file(binary)
if (!(await file.exists())) {
await Bun.write(file, blob, { mode: 0o755 })
await Bun.write(file, tui, { mode: 0o755 })
await fs.chmod(binary, 0o755)
}
cwd = process.cwd()

View File

@@ -721,7 +721,7 @@ export namespace Session {
sessionID: input.sessionID,
abort: abort.signal,
messageID: assistantMsg.id,
toolCallID: options.toolCallId,
callID: options.toolCallId,
metadata: async (val) => {
const match = processor.partFromToolCall(options.toolCallId)
if (match && match.state.status === "running") {

View File

@@ -3,18 +3,18 @@ import { Tool } from "./tool"
import DESCRIPTION from "./bash.txt"
import { App } from "../app/app"
import { Permission } from "../permission"
import Parser from "tree-sitter"
import Bash from "tree-sitter-bash"
import { Config } from "../config/config"
// import Parser from "tree-sitter"
// import Bash from "tree-sitter-bash"
// import { Config } from "../config/config"
import { Filesystem } from "../util/filesystem"
import path from "path"
const MAX_OUTPUT_LENGTH = 30000
const DEFAULT_TIMEOUT = 1 * 60 * 1000
const MAX_TIMEOUT = 10 * 60 * 1000
// const parser = new Parser()
// parser.setLanguage(Bash.language as any)
const parser = new Parser()
parser.setLanguage(Bash.language as any)
export const BashTool = Tool.define("bash", {
description: DESCRIPTION,
@@ -30,8 +30,7 @@ export const BashTool = Tool.define("bash", {
async execute(params, ctx) {
const timeout = Math.min(params.timeout ?? DEFAULT_TIMEOUT, MAX_TIMEOUT)
const app = App.info()
/*
const _cfg = await Config.get()
const cfg = await Config.get()
const tree = parser.parse(params.command)
const permissions = (() => {
const value = cfg.permission?.bash
@@ -93,33 +92,16 @@ export const BashTool = Tool.define("bash", {
if (needsAsk) {
await Permission.ask({
id: "bash",
type: "bash",
sessionID: ctx.sessionID,
messageID: ctx.messageID,
toolCallID: ctx.toolCallID,
callID: ctx.callID,
title: params.command,
metadata: {
command: params.command,
},
})
}
*/
const cfg = await Config.get()
if (cfg.permission?.bash === "ask")
await Permission.ask({
type: "bash",
pattern: params.command.split(" ").slice(0, 2).join(" ").trim(),
sessionID: ctx.sessionID,
messageID: ctx.messageID,
callID: ctx.toolCallID,
title: "Run this command: " + params.command,
metadata: {
command: params.command,
description: params.description,
timeout: params.timeout,
},
})
const process = Bun.spawn({
cmd: ["bash", "-c", params.command],

View File

@@ -53,7 +53,7 @@ export const EditTool = Tool.define("edit", {
type: "edit",
sessionID: ctx.sessionID,
messageID: ctx.messageID,
callID: ctx.toolCallID,
callID: ctx.callID,
title: "Edit this file: " + filePath,
metadata: {
filePath,
@@ -82,7 +82,7 @@ export const EditTool = Tool.define("edit", {
type: "edit",
sessionID: ctx.sessionID,
messageID: ctx.messageID,
callID: ctx.toolCallID,
callID: ctx.callID,
title: "Edit this file: " + filePath,
metadata: {
filePath,

View File

@@ -0,0 +1,53 @@
import Parser from "tree-sitter";
import Bash from "tree-sitter-bash";
const parser = new Parser();
parser.setLanguage(Bash.language as any);
const sourceCode = `cd --foo foo/bar && echo "hello" && cd ../baz`;
const tree = parser.parse(sourceCode);
// Function to extract commands and arguments
function extractCommands(
node: any,
): Array<{ command: string; args: string[] }> {
const commands: Array<{ command: string; args: string[] }> = [];
function traverse(node: any) {
if (node.type === "command") {
const commandNode = node.child(0);
if (commandNode) {
const command = commandNode.text;
const args: string[] = [];
// Extract arguments
for (let i = 1; i < node.childCount; i++) {
const child = node.child(i);
if (child && child.type === "word") {
args.push(child.text);
}
}
commands.push({ command, args });
}
}
// Traverse children
for (let i = 0; i < node.childCount; i++) {
traverse(node.child(i));
}
}
traverse(node);
return commands;
}
// Extract and display commands
console.log("Source code: " + sourceCode);
const commands = extractCommands(tree.rootNode);
console.log("Extracted commands:");
commands.forEach((cmd, index) => {
console.log(`${index + 1}. Command: ${cmd.command}`);
console.log(` Args: [${cmd.args.join(", ")}]`);
});

View File

@@ -7,7 +7,7 @@ export namespace Tool {
export type Context<M extends Metadata = Metadata> = {
sessionID: string
messageID: string
toolCallID?: string
callID?: string
abort: AbortSignal
metadata(input: { title?: string; metadata?: M }): void
}

View File

@@ -34,7 +34,7 @@ export const WriteTool = Tool.define("write", {
type: "write",
sessionID: ctx.sessionID,
messageID: ctx.messageID,
callID: ctx.toolCallID,
callID: ctx.callID,
title: exists ? "Overwrite this file: " + filepath : "Create new file: " + filepath,
metadata: {
filePath: filepath,