@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.
- package/README.md +95 -30
- package/index.d.ts +35 -9
- package/index.js +31 -7
- 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/
|
|
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.
|
|
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
|
-
##
|
|
40
|
+
## Configuration
|
|
28
41
|
|
|
29
|
-
OpenCode
|
|
42
|
+
### OpenCode 2 — plugin options
|
|
30
43
|
|
|
31
|
-
|
|
44
|
+
OpenCode 2 passes options natively, with no local plugin file needed:
|
|
32
45
|
|
|
33
46
|
```json
|
|
34
47
|
{
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
84
|
+
import { createRehydraPluginV2 } from "@rehydra/opencode";
|
|
50
85
|
|
|
51
|
-
export default
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|