@rehydra/opencode 0.14.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 +95 -30
  2. package/index.d.ts +35 -9
  3. package/index.js +31 -7
  4. package/package.json +2 -2
package/README.md CHANGED
@@ -2,9 +2,14 @@
2
2
 
3
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
- Detected values are masked in requests that pass through the plugin hooks. Local tools receive the restored values. See the title-generation limitation below.
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 @@ Detected values are masked in requests that pass through the plugin hooks. Local
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
  {
@@ -24,31 +37,53 @@ By default, the plugin discovers `**/.env*` under the OpenCode project directory
24
37
 
25
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.
26
39
 
27
- ## Session title limitation
40
+ ## Configuration
28
41
 
29
- OpenCode also sends the first user message to a separate LLM call to generate a session title. That call bypasses `experimental.chat.messages.transform`, so the plugin cannot scrub it. The first message can reach the title model with secrets intact even when the main conversation is anonymized. This limitation is tracked in [OpenCode issue #46115](https://github.com/anomalyco/opencode/issues/46115).
42
+ ### OpenCode 2 — plugin options
30
43
 
31
- To prevent that request, disable OpenCode's title agent in `opencode.json`:
44
+ OpenCode 2 passes options natively, with no local plugin file needed:
32
45
 
33
46
  ```json
34
47
  {
35
- "plugin": ["@rehydra/opencode"],
36
- "agent": {
37
- "title": { "disable": true }
38
- }
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
+ ]
39
60
  }
40
61
  ```
41
62
 
42
- This turns off automatic session titles. Restart OpenCode after changing the configuration. The plugin protects requests that invoke its hooks; it cannot intercept other model calls made outside those hooks.
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.
43
64
 
44
- ## Configuration
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.
45
78
 
46
- For custom settings, create `.opencode/plugins/rehydra.ts`:
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:
47
82
 
48
83
  ```typescript
49
- import { createRehydraPlugin } from "@rehydra/opencode";
84
+ import { createRehydraPluginV2 } from "@rehydra/opencode";
50
85
 
51
- export default createRehydraPlugin({
86
+ export default createRehydraPluginV2({
52
87
  // Scan multiple env files
53
88
  envFiles: [".env", ".env.local", ".env.production"],
54
89
 
@@ -66,11 +101,9 @@ export default createRehydraPlugin({
66
101
  });
67
102
  ```
68
103
 
69
- `vcsIdentities` detects logins in `gh pr` and `gh api` output, plus author and
70
- committer names in `git log`, `git show`, and `git blame` output. It redacts
71
- every occurrence of those identities in the same tool output without touching
72
- npm scopes or other `@` identifiers in unrelated commands. GitHub display names
73
- outside these structured fields still require the optional local NER model.
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.
74
107
 
75
108
  ## What gets detected
76
109
 
@@ -82,21 +115,53 @@ outside these structured fields still require the optional local NER model.
82
115
 
83
116
  ## How it works
84
117
 
85
- 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 |
138
+
139
+ Detection and rehydration run locally. No data leaves your machine except the scrubbed conversation sent to the LLM provider.
140
+
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.
86
157
 
87
- | Hook | What it does |
88
- |---|---|
89
- | `messages.transform` | Scrubs secrets from all message text before it reaches the LLM |
90
- | `system.transform` | Tells the LLM to treat placeholders as real values |
91
- | `tool.execute.before` | Restores real values in tool arguments before local execution |
92
- | `tool.execute.after` | Restores real values in displayed tool output |
93
- | `text.complete` | Restores real values in LLM response text shown to you |
158
+ ### Final response text on OpenCode 2
94
159
 
95
- Detection and rehydration run locally. The main conversation is scrubbed through the message hook before forwarding to the LLM provider. The separate title request described above needs its own opt-out.
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.
96
161
 
97
162
  ## Logging
98
163
 
99
- 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.
100
165
 
101
166
  ```
102
167
  INFO service=rehydra scrubbed={"ENV_VAR_SECRET":2} messageCount=3 scrubbed 2 secret(s) from messages
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.14.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.14.0"
32
+ "rehydra": "^0.15.0"
33
33
  },
34
34
  "engines": {
35
35
  "node": ">=18.0.0"