0595794385
- Default export is { id, setup, server }: OpenCode 2 calls setup(ctx), OpenCode 1 (1.18.29+) calls server()
- Tools defined once in lib/tools.ts and registered for both versions (V2 with codemode: false)
- Blocking covers path args of any tool, patch/apply_patch envelopes, shell commands naming env files, grep output, and V2 permission checks
- Secret-looking values from the project's env files are replaced in every non-envsitter tool's output
- Protected files come from envsitter's path policy; example/template files stay readable
- npm run test:e2e drives the real OpenCode 1 and 2 CLIs with a scripted model
- @opencode-ai/plugin ^1.19.3, @opencode/plugin ^2.1.0 (peer >=2.0.32), envsitter ^0.1.0, TypeScript 6
BREAKING CHANGE: the named export EnvSitterGuard is now the plugin definition object; call EnvSitterGuard.server(input) instead of EnvSitterGuard(input).
45 lines
1.7 KiB
TypeScript
45 lines
1.7 KiB
TypeScript
import type { Plugin } from "@opencode/plugin";
|
|
import type { JsonSchema } from "effect";
|
|
|
|
import { blockedAccessMessage, findBlockedAccess, permissionDenial, redactGrepResult } from "./policy.js";
|
|
import { createScrubLoader, scrubToolResult } from "./scrub.js";
|
|
import { guardTools, type ToolEnvironment } from "./tools.js";
|
|
|
|
export async function setupV2(ctx: Plugin.Context): Promise<void> {
|
|
const env: ToolEnvironment = {
|
|
directory: ctx.location.directory,
|
|
root: ctx.location.project.directory,
|
|
};
|
|
const loadScrub = createScrubLoader(env.root);
|
|
|
|
await ctx.tool.transform((editor) => {
|
|
for (const guardTool of guardTools) {
|
|
editor.add({
|
|
name: guardTool.name,
|
|
description: guardTool.description,
|
|
input: guardTool.inputSchema as JsonSchema.JsonSchema,
|
|
options: { codemode: false },
|
|
execute: async (input) => ({ content: await guardTool.run(input, env) }),
|
|
});
|
|
}
|
|
});
|
|
|
|
await ctx.tool.hook("execute.before", (event) => {
|
|
const blocked = findBlockedAccess(event.tool, event.input);
|
|
if (blocked) throw new Error(blockedAccessMessage(blocked));
|
|
});
|
|
|
|
await ctx.tool.hook("execute.after", async (event) => {
|
|
if (event.status !== "completed" || event.tool.startsWith("envsitter_")) return;
|
|
const result = event.tool === "grep" ? redactGrepResult(event.result) : event.result;
|
|
event.result = scrubToolResult(result, await loadScrub());
|
|
});
|
|
|
|
await ctx.permission.hook("evaluate", (event) => {
|
|
const message = permissionDenial(event.action, event.resources);
|
|
if (!message) return;
|
|
event.effect = "deny";
|
|
event.message = message;
|
|
});
|
|
}
|