summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-07-31 16:38:31 -0400
committerDax Raad <[email protected]>2025-07-31 16:38:37 -0400
commita2191ce6fb594526f66a551ee1654c0d871a34ca (patch)
tree1195d012b0bb580726dcdd7d477c1ea79d29ef09
parent168350c981279b2127b952dcefcb2e63191900ad (diff)
downloadopencode-a2191ce6fb594526f66a551ee1654c0d871a34ca.tar.gz
opencode-a2191ce6fb594526f66a551ee1654c0d871a34ca.zip
wip: permissions
-rwxr-xr-xpackages/opencode/script/publish.ts2
-rw-r--r--packages/opencode/src/cli/cmd/tui.ts18
-rw-r--r--packages/opencode/src/session/index.ts2
-rw-r--r--packages/opencode/src/tool/bash.ts36
-rw-r--r--packages/opencode/src/tool/edit.ts4
-rw-r--r--packages/opencode/src/tool/test.ts53
-rw-r--r--packages/opencode/src/tool/tool.ts2
-rw-r--r--packages/opencode/src/tool/write.ts2
-rw-r--r--packages/sdk/js/package.json2
-rw-r--r--packages/sdk/js/src/gen/sdk.gen.ts89
-rw-r--r--packages/sdk/js/src/gen/types.gen.ts155
11 files changed, 324 insertions, 41 deletions
diff --git a/packages/opencode/script/publish.ts b/packages/opencode/script/publish.ts
index 29a4c9084..3c4dd6b3a 100755
--- a/packages/opencode/script/publish.ts
+++ b/packages/opencode/script/publish.ts
@@ -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(
diff --git a/packages/opencode/src/cli/cmd/tui.ts b/packages/opencode/src/cli/cmd/tui.ts
index bb5d94b05..54cb14971 100644
--- a/packages/opencode/src/cli/cmd/tui.ts
+++ b/packages/opencode/src/cli/cmd/tui.ts
@@ -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()
diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts
index d4bcdda73..bb74e5e0c 100644
--- a/packages/opencode/src/session/index.ts
+++ b/packages/opencode/src/session/index.ts
@@ -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") {
diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts
index ea845e4d2..9076fada0 100644
--- a/packages/opencode/src/tool/bash.ts
+++ b/packages/opencode/src/tool/bash.ts
@@ -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],
diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts
index 588522cba..5bed09157 100644
--- a/packages/opencode/src/tool/edit.ts
+++ b/packages/opencode/src/tool/edit.ts
@@ -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,
diff --git a/packages/opencode/src/tool/test.ts b/packages/opencode/src/tool/test.ts
new file mode 100644
index 000000000..4ac819826
--- /dev/null
+++ b/packages/opencode/src/tool/test.ts
@@ -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(", ")}]`);
+});
diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts
index eae144144..1c71b9a77 100644
--- a/packages/opencode/src/tool/tool.ts
+++ b/packages/opencode/src/tool/tool.ts
@@ -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
}
diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts
index 279b7fa6f..5b0028f84 100644
--- a/packages/opencode/src/tool/write.ts
+++ b/packages/opencode/src/tool/write.ts
@@ -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,
diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json
index 502f2b7bb..968451785 100644
--- a/packages/sdk/js/package.json
+++ b/packages/sdk/js/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
- "version": "0.0.0",
+ "version": "0.0.0-202507312003",
"type": "module",
"exports": {
".": "./dist/index.js"
diff --git a/packages/sdk/js/src/gen/sdk.gen.ts b/packages/sdk/js/src/gen/sdk.gen.ts
index 312084856..23bc1f916 100644
--- a/packages/sdk/js/src/gen/sdk.gen.ts
+++ b/packages/sdk/js/src/gen/sdk.gen.ts
@@ -1,7 +1,7 @@
// This file is auto-generated by @hey-api/openapi-ts
import type { Options as ClientOptions, TDataShape, Client } from './client';
-import type { EventSubscribeData, EventSubscribeResponses, AppGetData, AppGetResponses, AppInitData, AppInitResponses, ConfigGetData, ConfigGetResponses, SessionListData, SessionListResponses, SessionCreateData, SessionCreateResponses, SessionCreateErrors, SessionDeleteData, SessionDeleteResponses, SessionInitData, SessionInitResponses, SessionAbortData, SessionAbortResponses, SessionUnshareData, SessionUnshareResponses, SessionShareData, SessionShareResponses, SessionSummarizeData, SessionSummarizeResponses, SessionMessagesData, SessionMessagesResponses, SessionChatData, SessionChatResponses, SessionRevertData, SessionRevertResponses, SessionUnrevertData, SessionUnrevertResponses, ConfigProvidersData, ConfigProvidersResponses, FindTextData, FindTextResponses, FindFilesData, FindFilesResponses, FindSymbolsData, FindSymbolsResponses, FileReadData, FileReadResponses, FileStatusData, FileStatusResponses, AppLogData, AppLogResponses, AppModesData, AppModesResponses, TuiAppendPromptData, TuiAppendPromptResponses, TuiOpenHelpData, TuiOpenHelpResponses } from './types.gen';
+import type { EventSubscribeData, EventSubscribeResponses, AppGetData, AppGetResponses, AppInitData, AppInitResponses, ConfigGetData, ConfigGetResponses, SessionListData, SessionListResponses, SessionCreateData, SessionCreateResponses, SessionCreateErrors, SessionDeleteData, SessionDeleteResponses, SessionInitData, SessionInitResponses, SessionAbortData, SessionAbortResponses, SessionUnshareData, SessionUnshareResponses, SessionShareData, SessionShareResponses, SessionSummarizeData, SessionSummarizeResponses, SessionMessagesData, SessionMessagesResponses, SessionChatData, SessionChatResponses, SessionMessageData, SessionMessageResponses, SessionRevertData, SessionRevertResponses, SessionUnrevertData, SessionUnrevertResponses, PostSessionByIdPermissionsByPermissionIdData, PostSessionByIdPermissionsByPermissionIdResponses, ConfigProvidersData, ConfigProvidersResponses, FindTextData, FindTextResponses, FindFilesData, FindFilesResponses, FindSymbolsData, FindSymbolsResponses, FileReadData, FileReadResponses, FileStatusData, FileStatusResponses, AppLogData, AppLogResponses, AppModesData, AppModesResponses, TuiAppendPromptData, TuiAppendPromptResponses, TuiOpenHelpData, TuiOpenHelpResponses, TuiOpenSessionsData, TuiOpenSessionsResponses, TuiOpenThemesData, TuiOpenThemesResponses, TuiOpenModelsData, TuiOpenModelsResponses, TuiSubmitPromptData, TuiSubmitPromptResponses, TuiClearPromptData, TuiClearPromptResponses, TuiExecuteCommandData, TuiExecuteCommandResponses } from './types.gen';
import { client as _heyApiClient } from './client.gen';
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
@@ -224,6 +224,16 @@ class Session extends _HeyApiClient {
}
/**
+ * Get a message from a session
+ */
+ public message<ThrowOnError extends boolean = false>(options: Options<SessionMessageData, ThrowOnError>) {
+ return (options.client ?? this._client).get<SessionMessageResponses, unknown, ThrowOnError>({
+ url: '/session/{id}/message/{messageID}',
+ ...options
+ });
+ }
+
+ /**
* Revert a message
*/
public revert<ThrowOnError extends boolean = false>(options: Options<SessionRevertData, ThrowOnError>) {
@@ -326,9 +336,86 @@ class Tui extends _HeyApiClient {
...options
});
}
+
+ /**
+ * Open the session dialog
+ */
+ public openSessions<ThrowOnError extends boolean = false>(options?: Options<TuiOpenSessionsData, ThrowOnError>) {
+ return (options?.client ?? this._client).post<TuiOpenSessionsResponses, unknown, ThrowOnError>({
+ url: '/tui/open-sessions',
+ ...options
+ });
+ }
+
+ /**
+ * Open the theme dialog
+ */
+ public openThemes<ThrowOnError extends boolean = false>(options?: Options<TuiOpenThemesData, ThrowOnError>) {
+ return (options?.client ?? this._client).post<TuiOpenThemesResponses, unknown, ThrowOnError>({
+ url: '/tui/open-themes',
+ ...options
+ });
+ }
+
+ /**
+ * Open the model dialog
+ */
+ public openModels<ThrowOnError extends boolean = false>(options?: Options<TuiOpenModelsData, ThrowOnError>) {
+ return (options?.client ?? this._client).post<TuiOpenModelsResponses, unknown, ThrowOnError>({
+ url: '/tui/open-models',
+ ...options
+ });
+ }
+
+ /**
+ * Submit the prompt
+ */
+ public submitPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiSubmitPromptData, ThrowOnError>) {
+ return (options?.client ?? this._client).post<TuiSubmitPromptResponses, unknown, ThrowOnError>({
+ url: '/tui/submit-prompt',
+ ...options
+ });
+ }
+
+ /**
+ * Clear the prompt
+ */
+ public clearPrompt<ThrowOnError extends boolean = false>(options?: Options<TuiClearPromptData, ThrowOnError>) {
+ return (options?.client ?? this._client).post<TuiClearPromptResponses, unknown, ThrowOnError>({
+ url: '/tui/clear-prompt',
+ ...options
+ });
+ }
+
+ /**
+ * Execute a TUI command (e.g. switch_mode)
+ */
+ public executeCommand<ThrowOnError extends boolean = false>(options?: Options<TuiExecuteCommandData, ThrowOnError>) {
+ return (options?.client ?? this._client).post<TuiExecuteCommandResponses, unknown, ThrowOnError>({
+ url: '/tui/execute-command',
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options?.headers
+ }
+ });
+ }
}
export class OpencodeClient extends _HeyApiClient {
+ /**
+ * Respond to a permission request
+ */
+ public postSessionByIdPermissionsByPermissionId<ThrowOnError extends boolean = false>(options: Options<PostSessionByIdPermissionsByPermissionIdData, ThrowOnError>) {
+ return (options.client ?? this._client).post<PostSessionByIdPermissionsByPermissionIdResponses, unknown, ThrowOnError>({
+ url: '/session/{id}/permissions/{permissionID}',
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options.headers
+ }
+ });
+ }
event = new Event({ client: this._client });
app = new App({ client: this._client });
config = new Config({ client: this._client });
diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts
index 6fcd1c415..9e4475ee2 100644
--- a/packages/sdk/js/src/gen/types.gen.ts
+++ b/packages/sdk/js/src/gen/types.gen.ts
@@ -355,12 +355,16 @@ export type EventStorageWrite = {
export type EventPermissionUpdated = {
type: string;
- properties: PermissionInfo;
+ properties: Permission;
};
-export type PermissionInfo = {
+export type Permission = {
id: string;
+ type: string;
+ pattern?: string;
sessionID: string;
+ messageID: string;
+ callID?: string;
title: string;
metadata: {
[key: string]: unknown;
@@ -1193,6 +1197,34 @@ export type SessionChatResponses = {
export type SessionChatResponse = SessionChatResponses[keyof SessionChatResponses];
+export type SessionMessageData = {
+ body?: never;
+ path: {
+ /**
+ * Session ID
+ */
+ id: string;
+ /**
+ * Message ID
+ */
+ messageID: string;
+ };
+ query?: never;
+ url: '/session/{id}/message/{messageID}';
+};
+
+export type SessionMessageResponses = {
+ /**
+ * Message
+ */
+ 200: {
+ info: Message;
+ parts: Array<Part>;
+ };
+};
+
+export type SessionMessageResponse = SessionMessageResponses[keyof SessionMessageResponses];
+
export type SessionRevertData = {
body?: {
messageID: string;
@@ -1232,6 +1264,27 @@ export type SessionUnrevertResponses = {
export type SessionUnrevertResponse = SessionUnrevertResponses[keyof SessionUnrevertResponses];
+export type PostSessionByIdPermissionsByPermissionIdData = {
+ body?: {
+ response: 'once' | 'always' | 'reject';
+ };
+ path: {
+ id: string;
+ permissionID: string;
+ };
+ query?: never;
+ url: '/session/{id}/permissions/{permissionID}';
+};
+
+export type PostSessionByIdPermissionsByPermissionIdResponses = {
+ /**
+ * Permission processed successfully
+ */
+ 200: boolean;
+};
+
+export type PostSessionByIdPermissionsByPermissionIdResponse = PostSessionByIdPermissionsByPermissionIdResponses[keyof PostSessionByIdPermissionsByPermissionIdResponses];
+
export type ConfigProvidersData = {
body?: never;
path?: never;
@@ -1445,6 +1498,104 @@ export type TuiOpenHelpResponses = {
export type TuiOpenHelpResponse = TuiOpenHelpResponses[keyof TuiOpenHelpResponses];
+export type TuiOpenSessionsData = {
+ body?: never;
+ path?: never;
+ query?: never;
+ url: '/tui/open-sessions';
+};
+
+export type TuiOpenSessionsResponses = {
+ /**
+ * Session dialog opened successfully
+ */
+ 200: boolean;
+};
+
+export type TuiOpenSessionsResponse = TuiOpenSessionsResponses[keyof TuiOpenSessionsResponses];
+
+export type TuiOpenThemesData = {
+ body?: never;
+ path?: never;
+ query?: never;
+ url: '/tui/open-themes';
+};
+
+export type TuiOpenThemesResponses = {
+ /**
+ * Theme dialog opened successfully
+ */
+ 200: boolean;
+};
+
+export type TuiOpenThemesResponse = TuiOpenThemesResponses[keyof TuiOpenThemesResponses];
+
+export type TuiOpenModelsData = {
+ body?: never;
+ path?: never;
+ query?: never;
+ url: '/tui/open-models';
+};
+
+export type TuiOpenModelsResponses = {
+ /**
+ * Model dialog opened successfully
+ */
+ 200: boolean;
+};
+
+export type TuiOpenModelsResponse = TuiOpenModelsResponses[keyof TuiOpenModelsResponses];
+
+export type TuiSubmitPromptData = {
+ body?: never;
+ path?: never;
+ query?: never;
+ url: '/tui/submit-prompt';
+};
+
+export type TuiSubmitPromptResponses = {
+ /**
+ * Prompt submitted successfully
+ */
+ 200: boolean;
+};
+
+export type TuiSubmitPromptResponse = TuiSubmitPromptResponses[keyof TuiSubmitPromptResponses];
+
+export type TuiClearPromptData = {
+ body?: never;
+ path?: never;
+ query?: never;
+ url: '/tui/clear-prompt';
+};
+
+export type TuiClearPromptResponses = {
+ /**
+ * Prompt cleared successfully
+ */
+ 200: boolean;
+};
+
+export type TuiClearPromptResponse = TuiClearPromptResponses[keyof TuiClearPromptResponses];
+
+export type TuiExecuteCommandData = {
+ body?: {
+ command: string;
+ };
+ path?: never;
+ query?: never;
+ url: '/tui/execute-command';
+};
+
+export type TuiExecuteCommandResponses = {
+ /**
+ * Command executed successfully
+ */
+ 200: boolean;
+};
+
+export type TuiExecuteCommandResponse = TuiExecuteCommandResponses[keyof TuiExecuteCommandResponses];
+
export type ClientOptions = {
baseUrl: `${string}://${string}` | (string & {});
}; \ No newline at end of file