@rehydra/opencode 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +113 -18
  2. package/index.d.ts +35 -9
  3. package/index.js +31 -7
  4. package/package.json +2 -2
package/README.md CHANGED
@@ -1,10 +1,15 @@
1
1
  # @rehydra/opencode
2
2
 
3
- Prevent your coding agent from leaking secrets to LLM providers.
3
+ Scrub detected secrets from OpenCode messages before the main LLM request.
4
4
 
5
- This plugin intercepts the conversation between [OpenCode](https://github.com/sst/opencode) and the LLM. Secrets from your `.env` files are replaced with placeholders before they leave your machine, and transparently restored before any tool (shell commands, file writes, etc.) executes locally.
5
+ This plugin intercepts the conversation between [OpenCode](https://github.com/anomalyco/opencode) and the LLM. Secrets from your `.env` files are replaced with placeholders before they leave your machine, and transparently restored before any tool (shell commands, file writes, etc.) executes locally.
6
6
 
7
- The LLM never sees real secret values. Your tools run with them.
7
+ Detected values are masked in requests that pass through the plugin hooks. Local tools receive the restored values.
8
+
9
+ **OpenCode compatibility:** the default entrypoint supports both OpenCode 2 (V2 plugin API) and OpenCode 1 (V2 converts the old `plugin` config key and calls `server()` for object entrypoints; OpenCode 1 releases older than 1.18.29 should import the `rehydra` function export or pin `@rehydra/opencode@0.14`). The same package ships both implementations:
10
+
11
+ - OpenCode 2 — `Plugin.define({ id: "rehydra", setup(ctx) })` via `createRehydraPluginV2()`
12
+ - OpenCode 1 — classic hooks object via `createRehydraPlugin()`
8
13
 
9
14
  ## Install
10
15
 
@@ -12,7 +17,15 @@ The LLM never sees real secret values. Your tools run with them.
12
17
  npm install @rehydra/opencode
13
18
  ```
14
19
 
15
- Add to `opencode.json`:
20
+ Add to `opencode.json` — **OpenCode 2** uses the plural `plugins` key:
21
+
22
+ ```json
23
+ {
24
+ "plugins": ["@rehydra/opencode"]
25
+ }
26
+ ```
27
+
28
+ **OpenCode 1** uses the singular `plugin` key:
16
29
 
17
30
  ```json
18
31
  {
@@ -20,16 +33,57 @@ Add to `opencode.json`:
20
33
  }
21
34
  ```
22
35
 
23
- By default, the plugin reads `.env` in your project root. Secrets with values of 4+ characters are detected and scrubbed.
36
+ By default, the plugin discovers `**/.env*` under the OpenCode project directory, including files such as `packages/api/.env` and `apps/web/.env.local`. It skips `node_modules`, `.git`, and symbolic links. Secrets with values of 4+ characters are detected and scrubbed.
37
+
38
+ `envFiles` accepts exact paths and glob patterns, resolved against the project directory even when OpenCode starts elsewhere. Use `envFiles: [".env"]` for root-only loading, or `envFiles: []` to disable file loading. Files are loaded once when the plugin initializes; restart OpenCode after changing them. Missing files are ignored. An advanced `anonymizer` configuration keeps control of its own `secrets` settings, with relative paths still rooted at the project directory unless `secrets.envBaseDirectory` is set.
24
39
 
25
40
  ## Configuration
26
41
 
27
- For custom settings, create `.opencode/plugins/rehydra.ts`:
42
+ ### OpenCode 2 — plugin options
43
+
44
+ OpenCode 2 passes options natively, with no local plugin file needed:
45
+
46
+ ```json
47
+ {
48
+ "plugins": [
49
+ {
50
+ "package": "@rehydra/opencode",
51
+ "options": {
52
+ "envFiles": [".env", ".env.local", ".env.production"],
53
+ "redactValues": ["sk-live-abc123..."],
54
+ "minValueLength": 6,
55
+ "disableTypes": ["URL", "IP_ADDRESS"],
56
+ "vcsIdentities": true
57
+ }
58
+ }
59
+ ]
60
+ }
61
+ ```
62
+
63
+ Options in the config object map 1:1 to `RehydraPluginOptions`. When both config options and factory options are given, factory options win per key.
64
+
65
+ ### OpenCode 1 — plugin options
66
+
67
+ OpenCode 1 takes options as the second element of a `[package, options]` entry:
68
+
69
+ ```json
70
+ {
71
+ "plugin": [
72
+ ["@rehydra/opencode", { "disableTypes": ["URL", "IP_ADDRESS", "DATE"] }]
73
+ ]
74
+ }
75
+ ```
76
+
77
+ `disableTypes` replaces the default list, so keep `URL` and `IP_ADDRESS` in it if they should stay disabled.
78
+
79
+ ### Local plugin file
80
+
81
+ For custom settings via a local plugin file (`.opencode/plugins/rehydra.ts`), use the factory matching your OpenCode version:
28
82
 
29
83
  ```typescript
30
- import { createRehydraPlugin } from "@rehydra/opencode";
84
+ import { createRehydraPluginV2 } from "@rehydra/opencode";
31
85
 
32
- export default createRehydraPlugin({
86
+ export default createRehydraPluginV2({
33
87
  // Scan multiple env files
34
88
  envFiles: [".env", ".env.local", ".env.production"],
35
89
 
@@ -41,9 +95,16 @@ export default createRehydraPlugin({
41
95
 
42
96
  // Disable detection of specific PII types
43
97
  disableTypes: ["URL", "IP_ADDRESS"],
98
+
99
+ // Redact identities in Git and GitHub CLI output
100
+ vcsIdentities: true,
44
101
  });
45
102
  ```
46
103
 
104
+ On OpenCode 1, import `createRehydraPlugin` instead (it returns the callable V1 plugin).
105
+
106
+ `vcsIdentities` detects logins in `gh pr` and `gh api` output, plus author and committer names in `git log`, `git show`, and `git blame` output. It redacts every occurrence of those identities in the same tool output without touching npm scopes or other `@` identifiers in unrelated commands. GitHub display names outside these structured fields still require the optional local NER model.
107
+
47
108
  ## What gets detected
48
109
 
49
110
  - Environment variable values from `.env` files
@@ -54,21 +115,53 @@ export default createRehydraPlugin({
54
115
 
55
116
  ## How it works
56
117
 
57
- The plugin uses five OpenCode hooks:
118
+ ### OpenCode 2 hooks
119
+
120
+ | Hook | What it does |
121
+ | --------------------------------------------------------------- | -------------------------------------------------------------- |
122
+ | `session.hook("context" / "compaction" / "generate" / "title")` | Scrubs secrets from all message text before each model request |
123
+ | `event.system.push(...)` | Tells the LLM to treat placeholders as real values |
124
+ | `tool.hook("execute.before")` | Restores real values in tool arguments before local execution |
125
+ | `tool.hook("execute.after")` | Restores real values in displayed tool output |
126
+
127
+ The scrubber is registered on all four session-request kinds, so title generation, checkpoint compaction, and transient generates are protected too.
128
+
129
+ ### OpenCode 1 hooks
130
+
131
+ | Hook | What it does |
132
+ | -------------------------------------- | -------------------------------------------------------------- |
133
+ | `experimental.chat.messages.transform` | Scrubs secrets from all message text before it reaches the LLM |
134
+ | `experimental.chat.system.transform` | Tells the LLM to treat placeholders as real values |
135
+ | `tool.execute.before` | Restores real values in tool arguments before local execution |
136
+ | `tool.execute.after` | Restores real values in displayed tool output |
137
+ | `experimental.text.complete` | Restores real values in LLM response text shown to you |
58
138
 
59
- | Hook | What it does |
60
- |---|---|
61
- | `messages.transform` | Scrubs secrets from all message text before it reaches the LLM |
62
- | `system.transform` | Tells the LLM to treat placeholders as real values |
63
- | `tool.execute.before` | Restores real values in tool arguments before local execution |
64
- | `tool.execute.after` | Restores real values in displayed tool output |
65
- | `text.complete` | Restores real values in LLM response text shown to you |
139
+ Detection and rehydration run locally. No data leaves your machine except the scrubbed conversation sent to the LLM provider.
66
140
 
67
- Everything runs locally. No data leaves your machine except the scrubbed conversation sent to the LLM provider.
141
+ ## Known limitations
142
+
143
+ ### Session titles — fixed on OpenCode 2
144
+
145
+ OpenCode 1 sends the first user message to a separate LLM call to generate a session title. That call bypasses `experimental.chat.messages.transform`, so the V1 plugin cannot scrub it ([OpenCode issue #46115](https://github.com/anomalyco/opencode/issues/46115)). On OpenCode 1, disable the title agent:
146
+
147
+ ```json
148
+ {
149
+ "plugin": ["@rehydra/opencode"],
150
+ "agent": {
151
+ "title": { "disable": true }
152
+ }
153
+ }
154
+ ```
155
+
156
+ On OpenCode 2 this workaround is unnecessary — the plugin registers a `title` session hook and scrubs the request.
157
+
158
+ ### Final response text on OpenCode 2
159
+
160
+ OpenCode 2 removed the `experimental.text.complete` hook with no replacement. Tool execution and outgoing conversation history stay protected; assistant responses that echo a PII placeholder into chat text will display the placeholder (`<PII type="…" id="…"/>`) instead of the real value. Tool arguments referencing the placeholder still receive real values before local execution.
68
161
 
69
162
  ## Logging
70
163
 
71
- Plugin activity is logged to OpenCode's log directory (`~/.local/share/opencode/log/`). Run with `--log-level DEBUG` for detailed output.
164
+ Plugin activity is logged to OpenCode's log directory (`~/.local/share/opencode/log/`). OpenCode 2 plugins log through the console (captured by OpenCode's log service); OpenCode 1 plugins log through the internal log API. Run with `--log-level DEBUG` for detailed output.
72
165
 
73
166
  ```
74
167
  INFO service=rehydra scrubbed={"ENV_VAR_SECRET":2} messageCount=3 scrubbed 2 secret(s) from messages
@@ -84,3 +177,5 @@ Full documentation at [docs.rehydra.ai](https://docs.rehydra.ai).
84
177
  ## License
85
178
 
86
179
  MIT
180
+
181
+ VCS identity discovery supports direct `gh pr`, `gh api`, `git log`, `git show`, and `git blame` commands, including global options such as `git -C` and `gh --repo`. Commands hidden inside shell scripts or aliases need explicit integration or NER. GitHub discovery masks participant fields and mentions of those participants; it preserves JSON keys and npm scopes. `disableTypes` still takes precedence over identity detection.
package/index.d.ts CHANGED
@@ -1,9 +1,35 @@
1
- export { createRehydraPlugin } from "rehydra/opencode-plugin";
2
- export type { RehydraPluginOptions } from "rehydra/opencode-plugin";
3
- export declare const rehydra: (input: {
4
- client: unknown;
5
- directory: string;
6
- worktree: string;
7
- [key: string]: unknown;
8
- }) => Promise<Record<string, unknown>>;
9
- export default rehydra;
1
+ import type { RehydraPluginOptions } from "rehydra/opencode-plugin";
2
+
3
+ export type {
4
+ RehydraPluginOptions,
5
+ RehydraV2Plugin,
6
+ } from "rehydra/opencode-plugin";
7
+ export {
8
+ createRehydraPlugin,
9
+ createRehydraPluginV2,
10
+ } from "rehydra/opencode-plugin";
11
+
12
+ /**
13
+ * OpenCode 1 plugin function (named export).
14
+ * OpenCode 1 calls named exports as plugin functions.
15
+ */
16
+ export declare const rehydra: (
17
+ input: {
18
+ client: unknown;
19
+ directory: string;
20
+ worktree: string;
21
+ [key: string]: unknown;
22
+ },
23
+ options?: RehydraPluginOptions,
24
+ ) => Promise<Record<string, unknown>>;
25
+
26
+ /**
27
+ * Dual V1/V2 default entrypoint.
28
+ * OpenCode 2 reads `id` + `setup()`; OpenCode 1 (>= 1.18.29) calls `server()`.
29
+ */
30
+ declare const _default: {
31
+ id: string;
32
+ setup: (ctx: unknown) => Promise<void | (() => void)>;
33
+ server: (input?: unknown, options?: RehydraPluginOptions) => Promise<Record<string, unknown>>;
34
+ };
35
+ export default _default;
package/index.js CHANGED
@@ -1,10 +1,34 @@
1
- import { createRehydraPlugin } from "rehydra/opencode-plugin";
1
+ import {
2
+ createRehydraPlugin,
3
+ createRehydraPluginV2,
4
+ } from "rehydra/opencode-plugin";
2
5
 
3
- // Named export: the factory for custom config (.opencode/plugins/rehydra.ts)
4
- export { createRehydraPlugin };
6
+ // Named factories for custom config:
7
+ // - createRehydraPlugin — OpenCode 1 (returns the callable V1 plugin)
8
+ // - createRehydraPluginV2 — OpenCode 2 (returns the { id, setup } definition)
9
+ // For OpenCode 2 with custom options prefer the config "options" form:
10
+ // { "plugins": [{ "package": "@rehydra/opencode", "options": { ... } }] }
11
+ export { createRehydraPlugin, createRehydraPluginV2 };
5
12
 
6
- // Named + default export: the plugin function for opencode.json usage.
7
- // OpenCode calls named exports as plugin functions, so this must be
8
- // the Plugin itself (takes ctx, returns hooks), not the factory.
13
+ // Named export for OpenCode 1: OpenCode calls named exports as plugin
14
+ // functions, so this must be the V1 Plugin itself (takes ctx, returns hooks).
9
15
  export const rehydra = createRehydraPlugin();
10
- export default rehydra;
16
+
17
+ // Default export: dual V1/V2 entrypoint.
18
+ // - OpenCode 2 reads `id` and `setup()`, ignoring `server()`.
19
+ // - OpenCode 1 (>= 1.18.29 object entrypoints) calls `server()` and uses the
20
+ // returned hooks. V1 releases older than 1.18.29 should import `rehydra`.
21
+ const v2 = createRehydraPluginV2();
22
+ export default {
23
+ id: v2.id,
24
+ setup: v2.setup,
25
+ async server(input, options) {
26
+ const plugin = createRehydraPlugin();
27
+ const resolved = input ?? {
28
+ client: { app: { log: () => Promise.resolve(undefined) } },
29
+ directory: process.cwd(),
30
+ worktree: process.cwd(),
31
+ };
32
+ return await plugin(resolved, options);
33
+ },
34
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rehydra/opencode",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Rehydra plugin for OpenCode — anonymizes secrets in LLM context using native plugin hooks",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -29,7 +29,7 @@
29
29
  "directory": "packages/opencode-plugin"
30
30
  },
31
31
  "dependencies": {
32
- "rehydra": "^0.13.0"
32
+ "rehydra": "^0.15.0"
33
33
  },
34
34
  "engines": {
35
35
  "node": ">=18.0.0"