opencode-rehydar 0.14.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.en.md ADDED
@@ -0,0 +1,155 @@
1
+ # opencode-rehydar
2
+
3
+ Scrub detected secrets from OpenCode messages before the main LLM request.
4
+
5
+ [中文](./README.md)
6
+
7
+ 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.
8
+
9
+ It targets OpenCode's **Plugin V2** API (`session.hook` / `tool.hook`). The earlier V1 plugin hooks and the singular `"plugin"` configuration are not supported.
10
+
11
+ Detected values are masked in requests that pass through the plugin hooks. Local tools and the assistant's primary answer receive the restored values. See the title-generation limitation below.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install opencode-rehydar
17
+ ```
18
+
19
+ Add it to `opencode.json`. V2 configures plugins as a list of `{ package, options }` entries:
20
+
21
+ ```json
22
+ {
23
+ "plugins": [
24
+ {
25
+ "package": "opencode-rehydar",
26
+ "options": {
27
+ "envFiles": [".env", ".env.local"]
28
+ }
29
+ }
30
+ ]
31
+ }
32
+ ```
33
+
34
+ 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.
35
+
36
+ `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.
37
+
38
+ ## Session title limitation
39
+
40
+ OpenCode generates session titles with a separate LLM call. The plugin scrubs that request — `session.hook("title")` anonymizes the title messages, so real values are not sent to the title model — but it does **not** rehydrate the title response. Its `http.response` hook restores values only for `kind === "primary"` responses, leaving `title`, `compaction`, and `generate` responses untouched. As a result, a session title can display placeholders such as `<PII type="..." id="..."/>` instead of the real values.
41
+
42
+ Recommended: disable OpenCode's title agent to avoid placeholder titles:
43
+
44
+ ```json
45
+ {
46
+ "plugins": [
47
+ {
48
+ "package": "opencode-rehydar",
49
+ "options": { "envFiles": [".env", ".env.local"] }
50
+ }
51
+ ],
52
+ "agent": {
53
+ "title": { "disable": true }
54
+ }
55
+ }
56
+ ```
57
+
58
+ 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.
59
+
60
+ ## Configuration
61
+
62
+ Set options in the `options` object of the `opencode.json` entry:
63
+
64
+ ```json
65
+ {
66
+ "plugins": [
67
+ {
68
+ "package": "opencode-rehydar",
69
+ "options": {
70
+ "envFiles": [".env", ".env.local", ".env.production"],
71
+ "redactValues": ["sk-live-abc123..."],
72
+ "minValueLength": 6,
73
+ "disableTypes": ["URL", "IP_ADDRESS"],
74
+ "vcsIdentities": true
75
+ }
76
+ }
77
+ ]
78
+ }
79
+ ```
80
+
81
+ For custom logic, create `.opencode/plugins/rehydra.ts` and build the plugin with the factory:
82
+
83
+ ```typescript
84
+ import { createRehydraPlugin } from "opencode-rehydar";
85
+
86
+ export default createRehydraPlugin({
87
+ // Scan multiple env files
88
+ envFiles: [".env", ".env.local", ".env.production"],
89
+
90
+ // Always redact these values, even if not in .env
91
+ redactValues: ["sk-live-abc123..."],
92
+
93
+ // Minimum value length to consider a secret (default: 4)
94
+ minValueLength: 6,
95
+
96
+ // Disable detection of specific PII types
97
+ disableTypes: ["URL", "IP_ADDRESS"],
98
+
99
+ // Redact identities in Git and GitHub CLI output
100
+ vcsIdentities: true,
101
+ });
102
+ ```
103
+
104
+ `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. 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.
105
+
106
+ ## What gets detected
107
+
108
+ - Environment variable values from `.env` files
109
+ - API keys, tokens, and credentials (pattern-based)
110
+ - AWS access keys and secret keys
111
+ - JWTs, private keys, connection strings
112
+ - Any values passed via `redactValues`
113
+
114
+ ## How it works
115
+
116
+ The plugin uses OpenCode's V2 session and tool hooks:
117
+
118
+ | Hook | What it does |
119
+ |---|---|
120
+ | `session.hook("context" / "compaction" / "generate")` | Anonymizes message text, tool arguments, and completed tool output before the request reaches the LLM, and injects the rehydra instruction once anything was anonymized |
121
+ | `session.hook("title")` | Anonymizes the session-title request messages only |
122
+ | `tool.hook("execute.before")` | Restores real values in tool arguments before local execution |
123
+ | `tool.hook("execute.after")` | Restores real values in completed tool results |
124
+ | `session.hook("http.response")` | Restores real values in the primary answer body (JSON and SSE) before OpenCode renders it |
125
+ | `session.hook("experimental.ws.receive")` | Restores real values in WebSocket frames carrying model output |
126
+
127
+ Detection and rehydration run locally. The main conversation is scrubbed through the session request hooks before forwarding to the LLM provider. Session-title requests are scrubbed as well, but their responses are not rehydrated — see the [title limitation](#session-title-limitation) above for the recommended opt-out.
128
+
129
+ ## Recovery of assistant text
130
+
131
+ The model only ever sees placeholders, so its output has to be rewritten back to the real values before OpenCode shows or reuses it. The plugin restores them on two V2 surfaces:
132
+
133
+ - `session.hook("http.response")` rewrites the primary answer body. JSON responses have every string leaf restored; SSE responses (`text/event-stream`) have each complete `data:` frame restored. The hook processes `kind === "primary"` only — title, compaction, and generate responses are left untouched, so title text can still contain placeholders (see the [title limitation](#session-title-limitation)).
134
+ - `session.hook("experimental.ws.receive")` rewrites WebSocket frames that carry model output.
135
+
136
+ Both paths are best-effort: a rehydration failure is logged and the original payload is passed through unchanged, so a malformed stream or an unavailable hook never corrupts the response or stops OpenCode.
137
+
138
+ ## Logging
139
+
140
+ Plugin activity is logged to OpenCode's log directory (`~/.local/share/opencode/log/`). Run with `--log-level DEBUG` for detailed output.
141
+
142
+ ```
143
+ INFO service=rehydra scrubbed={"ENV_VAR_SECRET":2} messageCount=3 scrubbed 2 secret(s) from messages
144
+ INFO service=rehydra tool=bash callID=call_abc123 rehydrated PII tags in tool args
145
+ ```
146
+
147
+ ## Rehydra
148
+
149
+ This plugin is part of [Rehydra](https://github.com/EightDoor/opencode-rehydra-sdk), an open-source SDK for PII anonymization and rehydration. Rehydra combines regex-based pattern matching with NER-based detection and supports any LLM provider via fetch wrappers, proxy servers, or framework plugins.
150
+
151
+ Full documentation in the [repository README](https://github.com/EightDoor/opencode-rehydra-sdk#readme).
152
+
153
+ ## License
154
+
155
+ MIT
package/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # opencode-rehydar
2
+
3
+ 在主 LLM 请求发出之前,脱敏 OpenCode 消息中检测到的密钥。
4
+
5
+ [English](./README.en.md)
6
+
7
+ 该插件拦截 [OpenCode](https://github.com/sst/opencode) 与 LLM 之间的对话。你的 `.env` 文件中的密钥在离开本机前被替换为占位符,并在任何工具(shell 命令、文件写入等)在本地执行前透明还原。
8
+
9
+ 它面向 OpenCode 的 **Plugin V2** API(`session.hook` / `tool.hook`)。不支持早期 V1 插件 hook 和单数形式的 `"plugin"` 配置。
10
+
11
+ 检测到的值会在经过插件 hook 的请求中被遮盖。本地工具和助手的主回答会收到还原后的值。参见下文的标题生成限制。
12
+
13
+ ## 安装
14
+
15
+ ```bash
16
+ npm install opencode-rehydar
17
+ ```
18
+
19
+ 在 `opencode.json` 中启用。V2 以 `{ package, options }` 条目列表的形式配置插件:
20
+
21
+ ```json
22
+ {
23
+ "plugins": [
24
+ {
25
+ "package": "opencode-rehydar",
26
+ "options": {
27
+ "envFiles": [".env", ".env.local"]
28
+ }
29
+ }
30
+ ]
31
+ }
32
+ ```
33
+
34
+ 默认情况下,插件会在 OpenCode 项目目录下发现 `**/.env*`,包括 `packages/api/.env` 和 `apps/web/.env.local` 这类文件。它会跳过 `node_modules`、`.git` 和符号链接。值长度达到 4 个字符及以上的密钥会被检测和脱敏。
35
+
36
+ `envFiles` 接受精确路径和 glob 模式,即使 OpenCode 从其他位置启动,也会相对项目目录解析。用 `envFiles: [".env"]` 只加载根目录文件,或用 `envFiles: []` 禁用文件加载。文件在插件初始化时加载一次;修改后需要重启 OpenCode。不存在的文件会被忽略。高级的 `anonymizer` 配置保留对自己 `secrets` 设置的控制权,其相对路径同样以项目目录为根,除非设置了 `secrets.envBaseDirectory`。
37
+
38
+ ## 会话标题限制
39
+
40
+ OpenCode 通过一次独立的 LLM 调用生成会话标题。插件会脱敏该请求——`session.hook("title")` 对标题消息做匿名化,因此真实值不会发送给标题模型——但它**不会**还原标题响应。它的 `http.response` hook 只对 `kind === "primary"` 的响应还原值,`title`、`compaction` 和 `generate` 响应保持不变。因此会话标题可能显示 `<PII type="..." id="..."/>` 之类的占位符,而不是真实值。
41
+
42
+ 建议禁用 OpenCode 的标题 agent 以避免占位符标题:
43
+
44
+ ```json
45
+ {
46
+ "plugins": [
47
+ {
48
+ "package": "opencode-rehydar",
49
+ "options": { "envFiles": [".env", ".env.local"] }
50
+ }
51
+ ],
52
+ "agent": {
53
+ "title": { "disable": true }
54
+ }
55
+ }
56
+ ```
57
+
58
+ 这会关闭自动会话标题。修改配置后需要重启 OpenCode。插件保护的是调用其 hook 的请求;它无法拦截这些 hook 之外的其他模型调用。
59
+
60
+ ## 配置
61
+
62
+ 在 `opencode.json` 条目的 `options` 对象中设置选项:
63
+
64
+ ```json
65
+ {
66
+ "plugins": [
67
+ {
68
+ "package": "opencode-rehydar",
69
+ "options": {
70
+ "envFiles": [".env", ".env.local", ".env.production"],
71
+ "redactValues": ["sk-live-abc123..."],
72
+ "minValueLength": 6,
73
+ "disableTypes": ["URL", "IP_ADDRESS"],
74
+ "vcsIdentities": true
75
+ }
76
+ }
77
+ ]
78
+ }
79
+ ```
80
+
81
+ 自定义逻辑可以创建 `.opencode/plugins/rehydra.ts` 并用工厂函数构建插件:
82
+
83
+ ```typescript
84
+ import { createRehydraPlugin } from "opencode-rehydar";
85
+
86
+ export default createRehydraPlugin({
87
+ // 扫描多个 env 文件
88
+ envFiles: [".env", ".env.local", ".env.production"],
89
+
90
+ // 始终脱敏这些值,即使它们不在 .env 中
91
+ redactValues: ["sk-live-abc123..."],
92
+
93
+ // 视为密钥的最小值长度(默认 4)
94
+ minValueLength: 6,
95
+
96
+ // 禁用特定 PII 类型的检测
97
+ disableTypes: ["URL", "IP_ADDRESS"],
98
+
99
+ // 脱敏 Git 和 GitHub CLI 输出中的身份信息
100
+ vcsIdentities: true,
101
+ });
102
+ ```
103
+
104
+ `vcsIdentities` 检测 `gh pr` 和 `gh api` 输出中的登录名,以及 `git log`、`git show`、`git blame` 输出中的作者和提交者姓名。它会在同一次工具输出中脱敏这些身份的所有出现,同时不触碰无关命令中的 npm scope 或其他 `@` 标识符。不在上述结构化字段中的 GitHub 显示名仍需要可选的本地 NER 模型。VCS 身份发现支持直接的 `gh pr`、`gh api`、`git log`、`git show` 和 `git blame` 命令,包括 `git -C` 和 `gh --repo` 之类的全局选项。隐藏在 shell 脚本或别名中的命令需要显式集成或 NER。GitHub 发现会遮盖参与者字段及对这些参与者的提及,同时保留 JSON 键和 npm scope。`disableTypes` 仍然优先于身份检测。
105
+
106
+ ## 检测范围
107
+
108
+ - `.env` 文件中的环境变量值
109
+ - API Key、Token 和凭证(基于模式)
110
+ - AWS access key 和 secret key
111
+ - JWT、私钥、连接串
112
+ - 通过 `redactValues` 传入的任何值
113
+
114
+ ## 工作原理
115
+
116
+ 插件使用 OpenCode 的 V2 session 与 tool hook:
117
+
118
+ | Hook | 作用 |
119
+ |---|---|
120
+ | `session.hook("context" / "compaction" / "generate")` | 在请求到达 LLM 前匿名化消息文本、工具参数和已完成的工具输出,并在发生匿名化时注入 rehydra 指令 |
121
+ | `session.hook("title")` | 仅匿名化会话标题请求的消息 |
122
+ | `tool.hook("execute.before")` | 在本地执行前还原工具参数中的真实值 |
123
+ | `tool.hook("execute.after")` | 还原已完成工具结果中的真实值 |
124
+ | `session.hook("http.response")` | 在 OpenCode 渲染前还原主回答正文(JSON 和 SSE)中的真实值 |
125
+ | `session.hook("experimental.ws.receive")` | 还原承载模型输出的 WebSocket 帧中的真实值 |
126
+
127
+ 检测和还原都在本地进行。主对话在转发给 LLM provider 之前通过 session 请求 hook 脱敏。标题请求也会脱敏,但其响应不会被还原——建议的规避方式见上文的[标题限制](#会话标题限制)。
128
+
129
+ ## 助手文本的还原
130
+
131
+ 模型始终只看到占位符,因此在 OpenCode 展示或复用其输出之前,必须改写回真实值。插件在两个 V2 接口上完成还原:
132
+
133
+ - `session.hook("http.response")` 改写主回答正文。JSON 响应还原每个字符串叶子;SSE 响应(`text/event-stream`)还原每个完整的 `data:` 帧。该 hook 只处理 `kind === "primary"`,title、compaction 和 generate 响应保持不变,因此标题文本仍可能含占位符(见[标题限制](#会话标题限制))。
134
+ - `session.hook("experimental.ws.receive")` 改写承载模型输出的 WebSocket 帧。
135
+
136
+ 两条路径都是尽力而为:还原失败会被记录日志,原始载荷原样透传,因此畸形流或不可用的 hook 不会破坏响应,也不会中断 OpenCode。
137
+
138
+ ## 日志
139
+
140
+ 插件活动记录到 OpenCode 的日志目录(`~/.local/share/opencode/log/`)。以 `--log-level DEBUG` 运行可获得详细输出。
141
+
142
+ ```
143
+ INFO service=rehydra scrubbed={"ENV_VAR_SECRET":2} messageCount=3 scrubbed 2 secret(s) from messages
144
+ INFO service=rehydra tool=bash callID=call_abc123 rehydrated PII tags in tool args
145
+ ```
146
+
147
+ ## Rehydra
148
+
149
+ 该插件属于 [Rehydra](https://github.com/EightDoor/opencode-rehydra-sdk),一个用于 PII 匿名化与还原的开源 SDK。Rehydra 将基于正则的模式匹配与基于 NER 的检测结合,并通过 fetch 包装器、代理服务或框架插件支持任意 LLM provider。
150
+
151
+ 完整文档见[仓库 README](https://github.com/EightDoor/opencode-rehydra-sdk#readme)。
152
+
153
+ ## License
154
+
155
+ MIT
package/index.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export { createRehydraPlugin } from "rehydra/opencode-plugin";
2
+ export type { RehydraPluginOptions } from "rehydra/opencode-plugin";
3
+
4
+ /**
5
+ * The pre-built plugin instance exported by `opencode-rehydar`.
6
+ *
7
+ * The type is taken from the package's own build output
8
+ * (`rehydra/opencode-plugin`) instead of being re-declared here, so it always
9
+ * matches the OpenCode V2 `Plugin` shape (`id` + `setup`).
10
+ */
11
+ export declare const rehydra: typeof import("rehydra/opencode-plugin").plugin;
12
+ export default rehydra;
package/index.js ADDED
@@ -0,0 +1,13 @@
1
+ // Load the built V2 SDK directly from the sibling dist/ directory so this
2
+ // package can be dropped into OpenCode's plugin config without requiring an
3
+ // npm install of the `rehydra` package. The relative path resolves to the
4
+ // repository's compiled output (`../../dist/opencode-plugin/index.js`).
5
+ const sdk = await import("../../dist/opencode-plugin/index.js");
6
+ const sdkDefault = sdk.default;
7
+ const sdkPlugin = sdk.plugin;
8
+ const sdkCreate = sdk.createRehydraPlugin;
9
+ export { sdkDefault as default, sdkPlugin as plugin, sdkCreate as createRehydraPlugin };
10
+ // Backwards-compatible named export for consumers that still use the V1-style
11
+ // entry: `import rehydra from "@rehydra/opencode"`. V2 reads the default
12
+ // export, but keep `rehydra` available for code that imports it explicitly.
13
+ export const rehydra = sdkDefault;
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "opencode-rehydar",
3
+ "version": "0.14.0",
4
+ "description": "Rehydra OpenCode plugin — anonymizes secrets in LLM context using native plugin hooks",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "types": "index.d.ts",
8
+ "files": [
9
+ "index.js",
10
+ "index.d.ts",
11
+ "README.md",
12
+ "README.en.md"
13
+ ],
14
+ "scripts": {
15
+ "prepublishOnly": "cd ../.. && npm run build"
16
+ },
17
+ "keywords": [
18
+ "anonymization",
19
+ "pii",
20
+ "privacy",
21
+ "secrets",
22
+ "opencode",
23
+ "opencode-plugin",
24
+ "rehydra"
25
+ ],
26
+ "author": "EightDoor",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/EightDoor/opencode-rehydra-sdk.git",
31
+ "directory": "packages/opencode-plugin"
32
+ },
33
+ "dependencies": {
34
+ "opencode-rehydra-core": "^0.14.0"
35
+ },
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ }
39
+ }