feat(plugin): add shell.env hook for manipulating environment in tools and shell (#12012)
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user