@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.
- package/README.md +113 -18
- package/index.d.ts +35 -9
- package/index.js +31 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
# @rehydra/opencode
|
|
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/
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
84
|
+
import { createRehydraPluginV2 } from "@rehydra/opencode";
|
|
31
85
|
|
|
32
|
-
export default
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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 {
|
|
1
|
+
import {
|
|
2
|
+
createRehydraPlugin,
|
|
3
|
+
createRehydraPluginV2,
|
|
4
|
+
} from "rehydra/opencode-plugin";
|
|
2
5
|
|
|
3
|
-
// Named
|
|
4
|
-
|
|
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
|
|
7
|
-
//
|
|
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
|
-
|
|
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.
|
|
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.
|
|
32
|
+
"rehydra": "^0.15.0"
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
|
35
35
|
"node": ">=18.0.0"
|