Przeglądaj źródła

feat(plugin): add shell.env hook for manipulating environment in tools and shell (#12012)

Tyler Gannon 2 tygodni temu
rodzic
commit
a30696f9bf

+ 3 - 0
packages/opencode/src/pty/index.ts

@@ -8,6 +8,7 @@ import type { WSContext } from "hono/ws"
 import { Instance } from "../project/instance"
 import { Instance } from "../project/instance"
 import { lazy } from "@opencode-ai/util/lazy"
 import { lazy } from "@opencode-ai/util/lazy"
 import { Shell } from "@/shell/shell"
 import { Shell } from "@/shell/shell"
+import { Plugin } from "@/plugin"
 
 
 export namespace Pty {
 export namespace Pty {
   const log = Log.create({ service: "pty" })
   const log = Log.create({ service: "pty" })
@@ -102,9 +103,11 @@ export namespace Pty {
     }
     }
 
 
     const cwd = input.cwd || Instance.directory
     const cwd = input.cwd || Instance.directory
+    const shellEnv = await Plugin.trigger("shell.env", { cwd }, { env: {} })
     const env = {
     const env = {
       ...process.env,
       ...process.env,
       ...input.env,
       ...input.env,
+      ...shellEnv.env,
       TERM: "xterm-256color",
       TERM: "xterm-256color",
       OPENCODE_TERMINAL: "1",
       OPENCODE_TERMINAL: "1",
     } as Record<string, string>
     } as Record<string, string>

+ 4 - 1
packages/opencode/src/session/prompt.ts

@@ -1500,12 +1500,15 @@ NOTE: At any point in time through this workflow you should feel free to ask the
     const matchingInvocation = invocations[shellName] ?? invocations[""]
     const matchingInvocation = invocations[shellName] ?? invocations[""]
     const args = matchingInvocation?.args
     const args = matchingInvocation?.args
 
 
+    const cwd = Instance.directory
+    const shellEnv = await Plugin.trigger("shell.env", { cwd }, { env: {} })
     const proc = spawn(shell, args, {
     const proc = spawn(shell, args, {
-      cwd: Instance.directory,
+      cwd,
       detached: process.platform !== "win32",
       detached: process.platform !== "win32",
       stdio: ["ignore", "pipe", "pipe"],
       stdio: ["ignore", "pipe", "pipe"],
       env: {
       env: {
         ...process.env,
         ...process.env,
+        ...shellEnv.env,
         TERM: "dumb",
         TERM: "dumb",
       },
       },
     })
     })

+ 3 - 0
packages/opencode/src/tool/bash.ts

@@ -16,6 +16,7 @@ import { Shell } from "@/shell/shell"
 
 
 import { BashArity } from "@/permission/arity"
 import { BashArity } from "@/permission/arity"
 import { Truncate } from "./truncation"
 import { Truncate } from "./truncation"
+import { Plugin } from "@/plugin"
 
 
 const MAX_METADATA_LENGTH = 30_000
 const MAX_METADATA_LENGTH = 30_000
 const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS || 2 * 60 * 1000
 const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS || 2 * 60 * 1000
@@ -162,11 +163,13 @@ export const BashTool = Tool.define("bash", async () => {
         })
         })
       }
       }
 
 
+      const shellEnv = await Plugin.trigger("shell.env", { cwd }, { env: {} })
       const proc = spawn(params.command, {
       const proc = spawn(params.command, {
         shell,
         shell,
         cwd,
         cwd,
         env: {
         env: {
           ...process.env,
           ...process.env,
+          ...shellEnv.env,
         },
         },
         stdio: ["ignore", "pipe", "pipe"],
         stdio: ["ignore", "pipe", "pipe"],
         detached: process.platform !== "win32",
         detached: process.platform !== "win32",

+ 1 - 0
packages/plugin/src/index.ts

@@ -185,6 +185,7 @@ export interface Hooks {
     input: { tool: string; sessionID: string; callID: string },
     input: { tool: string; sessionID: string; callID: string },
     output: { args: any },
     output: { args: any },
   ) => Promise<void>
   ) => Promise<void>
+  "shell.env"?: (input: { cwd: string }, output: { env: Record<string, string> }) => Promise<void>
   "tool.execute.after"?: (
   "tool.execute.after"?: (
     input: { tool: string; sessionID: string; callID: string },
     input: { tool: string; sessionID: string; callID: string },
     output: {
     output: {

+ 21 - 0
packages/web/src/content/docs/plugins.mdx

@@ -192,6 +192,10 @@ Plugins can subscribe to events as seen below in the Examples section. Here is a
 
 
 - `todo.updated`
 - `todo.updated`
 
 
+#### Shell Events
+
+- `shell.env`
+
 #### Tool Events
 #### Tool Events
 
 
 - `tool.execute.after`
 - `tool.execute.after`
@@ -254,6 +258,23 @@ export const EnvProtection = async ({ project, client, $, directory, worktree })
 
 
 ---
 ---
 
 
+### Inject environment variables
+
+Inject environment variables into all shell execution (AI tools and user terminals):
+
+```javascript title=".opencode/plugins/inject-env.js"
+export const InjectEnvPlugin = async () => {
+  return {
+    "shell.env": async (input, output) => {
+      output.env.MY_API_KEY = "secret"
+      output.env.PROJECT_ROOT = input.cwd
+    },
+  }
+}
+```
+
+---
+
 ### Custom tools
 ### Custom tools
 
 
 Plugins can also add custom tools to opencode:
 Plugins can also add custom tools to opencode: