oc-tweaks 0.1.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 +333 -0
- package/package.json +27 -0
- package/src/__tests__/.gitkeep +0 -0
- package/src/__tests__/background-subagent.test.ts +106 -0
- package/src/__tests__/cli-init.test.ts +70 -0
- package/src/__tests__/compaction.test.ts +113 -0
- package/src/__tests__/index.test.ts +180 -0
- package/src/__tests__/leaderboard.test.ts +244 -0
- package/src/__tests__/logger.test.ts +84 -0
- package/src/__tests__/notify.test.ts +318 -0
- package/src/__tests__/utils.test.ts +164 -0
- package/src/bun-test.d.ts +12 -0
- package/src/cli/init.ts +44 -0
- package/src/index.ts +4 -0
- package/src/plugins/.gitkeep +0 -0
- package/src/plugins/background-subagent.ts +59 -0
- package/src/plugins/compaction.ts +28 -0
- package/src/plugins/leaderboard.ts +184 -0
- package/src/plugins/notify.ts +383 -0
- package/src/types.ts +2 -0
- package/src/utils/.gitkeep +0 -0
- package/src/utils/config.ts +71 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/logger.ts +52 -0
- package/src/utils/safe-hook.ts +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# oc-tweaks
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/oc-tweaks)
|
|
4
|
+
|
|
5
|
+
A collection of runtime enhancement plugins for [OpenCode](https://opencode.ai/).
|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
`oc-tweaks` provides a set of plugins to enhance your OpenCode experience. These plugins are activated at runtime and can modify OpenCode's behavior, such as sending notifications, improving multi-language support, and enforcing best practices for sub-agent usage.
|
|
10
|
+
|
|
11
|
+
The currently available plugins are:
|
|
12
|
+
- **`notify`**: Sends desktop notifications when a task is completed or an error occurs.
|
|
13
|
+
- **`compaction`**: Injects a language preference prompt during session compaction to ensure summaries are in your preferred language.
|
|
14
|
+
- **`backgroundSubagent`**: Adds a system prompt to encourage using background sub-agents for better responsiveness.
|
|
15
|
+
- **`leaderboard`**: Reports token usage to [claudecount.com](https://claudecount.com) for community leaderboards.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Install the package from npm:
|
|
20
|
+
```bash
|
|
21
|
+
bun add oc-tweaks
|
|
22
|
+
# or
|
|
23
|
+
npm install oc-tweaks
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then, add it to your OpenCode configuration file (`~/.config/opencode/opencode.json`). Note the key is `plugin` (singular).
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"plugin": ["oc-tweaks"]
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
To generate a default configuration file, run the following command:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bunx oc-tweaks init
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This will create `~/.config/opencode/oc-tweaks.json` with all plugins configured. You can edit this file to enable or disable plugins and customize their behavior.
|
|
43
|
+
|
|
44
|
+
## Configuration
|
|
45
|
+
|
|
46
|
+
All configurations are optional. By default, most plugins are enabled after running the `init` command.
|
|
47
|
+
|
|
48
|
+
### `notify`
|
|
49
|
+
|
|
50
|
+
This plugin sends desktop notifications upon task completion (session idle) or error.
|
|
51
|
+
|
|
52
|
+
- **Windows**: Uses a custom, non-intrusive WPF window that works across virtual desktops.
|
|
53
|
+
- **macOS**: Uses `osascript`.
|
|
54
|
+
- **Linux**: Uses `notify-send`.
|
|
55
|
+
- **Fallback**: Can use the built-in TUI toast if available.
|
|
56
|
+
|
|
57
|
+
**Configuration Options:**
|
|
58
|
+
|
|
59
|
+
| Property | Type | Default | Description |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| `enabled` | boolean | `true` | Enable or disable the plugin. |
|
|
62
|
+
| `notifyOnIdle` | boolean | `true` | Notify when a session becomes idle (task completion). |
|
|
63
|
+
| `notifyOnError` | boolean | `true` | Notify on session errors. |
|
|
64
|
+
| `command` | string | `null` | A custom command to run for notifications. `$TITLE` and `$MESSAGE` are available as placeholders. |
|
|
65
|
+
| `style` | object | `{...}` | Custom styles for the Windows WPF notification window. See below. |
|
|
66
|
+
|
|
67
|
+
#### `notify.style` (Windows WPF)
|
|
68
|
+
|
|
69
|
+
Customize the appearance of the WPF notification window.
|
|
70
|
+
|
|
71
|
+
| Property | Default | Description |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `backgroundColor` | `"#101018"` | Background color of the notification window. |
|
|
74
|
+
| `backgroundOpacity` | `0.95` | Background opacity (0 to 1). |
|
|
75
|
+
| `textColor` | `"#AAAAAA"` | Main text color. |
|
|
76
|
+
| `borderRadius` | `14` | Corner radius in pixels. |
|
|
77
|
+
| `colorBarWidth` | `5` | Width of the left accent bar in pixels. |
|
|
78
|
+
| `width` | `420` | Window width in pixels. |
|
|
79
|
+
| `height` | `105` | Window height in pixels. |
|
|
80
|
+
| `titleFontSize` | `14` | Font size for the title in points. |
|
|
81
|
+
| `contentFontSize` | `11` | Font size for the content in points. |
|
|
82
|
+
| `iconFontSize` | `13` | Font size for the icon (✅/❌) in points. |
|
|
83
|
+
| `duration` | `10000` | Time in milliseconds before the notification auto-closes. |
|
|
84
|
+
| `position` | `"center"` | Window position: `"center"`, `"top-right"`, or `"bottom-right"`. |
|
|
85
|
+
| `shadow` | `true` | Enable or disable the drop shadow effect. |
|
|
86
|
+
| `idleColor` | `"#4ADE80"` | Accent color for idle (success) notifications. |
|
|
87
|
+
| `errorColor` | `"#EF4444"` | Accent color for error notifications. |
|
|
88
|
+
|
|
89
|
+
### `compaction`
|
|
90
|
+
|
|
91
|
+
This plugin ensures that when OpenCode compacts a session's context, the resulting summary is generated in your preferred language (e.g., Chinese) instead of defaulting to English.
|
|
92
|
+
|
|
93
|
+
**Configuration Options:**
|
|
94
|
+
|
|
95
|
+
| Property | Type | Default | Description |
|
|
96
|
+
|---|---|---|---|
|
|
97
|
+
| `enabled` | boolean | `true` | Enable or disable the plugin. |
|
|
98
|
+
|
|
99
|
+
### `backgroundSubagent`
|
|
100
|
+
|
|
101
|
+
This plugin injects a policy into the system prompt, reminding the AI agent to use `run_in_background=true` by default when dispatching sub-agents. It helps maintain a responsive main conversation. If a foreground task is dispatched, a friendly reminder is shown.
|
|
102
|
+
|
|
103
|
+
**Configuration Options:**
|
|
104
|
+
|
|
105
|
+
| Property | Type | Default | Description |
|
|
106
|
+
|---|---|---|---|
|
|
107
|
+
| `enabled` | boolean | `true` | Enable or disable the plugin. |
|
|
108
|
+
|
|
109
|
+
### `leaderboard`
|
|
110
|
+
|
|
111
|
+
This plugin reports token usage statistics to the community-driven [claudecount.com](https://claudecount.com) leaderboard. It is disabled by default.
|
|
112
|
+
|
|
113
|
+
**Configuration Options:**
|
|
114
|
+
|
|
115
|
+
| Property | Type | Default | Description |
|
|
116
|
+
|---|---|---|---|
|
|
117
|
+
| `enabled` | boolean | `false` | Enable or disable the plugin. |
|
|
118
|
+
| `configPath` | string | `null` | Optional path to a custom `leaderboard.json` file. If not set, it searches standard locations (`~/.claude/leaderboard.json`, `~/.config/claude/leaderboard.json`). |
|
|
119
|
+
|
|
120
|
+
### `logging`
|
|
121
|
+
|
|
122
|
+
Configure logging for `oc-tweaks`.
|
|
123
|
+
|
|
124
|
+
**Configuration Options:**
|
|
125
|
+
|
|
126
|
+
| Property | Type | Default | Description |
|
|
127
|
+
|---|---|---|---|
|
|
128
|
+
| `enabled` | boolean | `false` | Enable writing logs to a file. |
|
|
129
|
+
| `maxLines` | number | `100` | Maximum number of lines to keep in the log file. Older lines are truncated. |
|
|
130
|
+
|
|
131
|
+
Logs are written to `~/.config/opencode/plugins/oc-tweaks.log`.
|
|
132
|
+
|
|
133
|
+
## Full Configuration Example
|
|
134
|
+
|
|
135
|
+
Here is an example of a `~/.config/opencode/oc-tweaks.json` file with all options shown.
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"notify": {
|
|
140
|
+
"enabled": true,
|
|
141
|
+
"notifyOnIdle": true,
|
|
142
|
+
"notifyOnError": true,
|
|
143
|
+
// Example of a custom command to send notification to another machine via SSH
|
|
144
|
+
// "command": "ssh my-desktop 'notify-send \"$TITLE\" \"$MESSAGE\"'",
|
|
145
|
+
"style": {
|
|
146
|
+
"backgroundColor": "#101018",
|
|
147
|
+
"duration": 8000
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"compaction": {
|
|
151
|
+
"enabled": true
|
|
152
|
+
},
|
|
153
|
+
"backgroundSubagent": {
|
|
154
|
+
"enabled": true
|
|
155
|
+
},
|
|
156
|
+
"leaderboard": {
|
|
157
|
+
"enabled": false
|
|
158
|
+
// "configPath": "/path/to/my/leaderboard.json"
|
|
159
|
+
},
|
|
160
|
+
"logging": {
|
|
161
|
+
"enabled": false,
|
|
162
|
+
"maxLines": 200
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
# oc-tweaks (中文)
|
|
170
|
+
|
|
171
|
+
[](https://www.npmjs.com/package/oc-tweaks)
|
|
172
|
+
|
|
173
|
+
一套用于 [OpenCode](https://opencode.ai/) 的运行时增强插件。
|
|
174
|
+
|
|
175
|
+
## 简介
|
|
176
|
+
|
|
177
|
+
`oc-tweaks` 提供了一系列插件来增强你的 OpenCode 使用体验。这些插件在运行时激活,可以调整 OpenCode 的行为,例如发送桌面通知、改善多语言支持以及强制执行子代理使用的最佳实践。
|
|
178
|
+
|
|
179
|
+
目前可用的插件包括:
|
|
180
|
+
- **`notify`**: 在任务完成或发生错误时发送桌面通知。
|
|
181
|
+
- **`compaction`**: 在会话上下文压缩期间注入语言偏好提示,以确保摘要使用你的首选语言。
|
|
182
|
+
- **`backgroundSubagent`**: 添加系统提示,鼓励使用后台子代理以获得更好的响应性。
|
|
183
|
+
- **`leaderboard`**: 向 [claudecount.com](https://claudecount.com) 报告 token 用量,用于社区排行榜。
|
|
184
|
+
|
|
185
|
+
## 安装
|
|
186
|
+
|
|
187
|
+
通过 npm 安装该软件包:
|
|
188
|
+
```bash
|
|
189
|
+
bun add oc-tweaks
|
|
190
|
+
# 或
|
|
191
|
+
npm install oc-tweaks
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
然后,将其添加到你的 OpenCode 配置文件 (`~/.config/opencode/opencode.json`) 中。请注意,键是 `plugin` (单数)。
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"plugin": ["oc-tweaks"]
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## 快速开始
|
|
203
|
+
|
|
204
|
+
要生成默认配置文件,请运行以下命令:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
bunx oc-tweaks init
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
这将在 `~/.config/opencode/oc-tweaks.json` 创建一个包含所有插件配置的文件。你可以编辑此文件来启用或禁用插件并自定义其行为。
|
|
211
|
+
|
|
212
|
+
## 配置说明
|
|
213
|
+
|
|
214
|
+
所有配置都是可选的。默认情况下,运行 `init` 命令后,大多数插件都是启用的。
|
|
215
|
+
|
|
216
|
+
### `notify`
|
|
217
|
+
|
|
218
|
+
此插件在任务完成(会话空闲)或出错时发送桌面通知。
|
|
219
|
+
|
|
220
|
+
- **Windows**: 使用一个自定义的、无侵入性的 WPF 窗口,该窗口可跨虚拟桌面工作。
|
|
221
|
+
- **macOS**: 使用 `osascript`。
|
|
222
|
+
- **Linux**: 使用 `notify-send`。
|
|
223
|
+
- **备选方案**: 如果可用,可以使用内置的 TUI toast。
|
|
224
|
+
|
|
225
|
+
**配置选项:**
|
|
226
|
+
|
|
227
|
+
| 属性 | 类型 | 默认值 | 描述 |
|
|
228
|
+
|---|---|---|---|
|
|
229
|
+
| `enabled` | boolean | `true` | 启用或禁用此插件。 |
|
|
230
|
+
| `notifyOnIdle` | boolean | `true` | 当会话变为空闲时(任务完成)通知。 |
|
|
231
|
+
| `notifyOnError` | boolean | `true` | 在会话出错时通知。 |
|
|
232
|
+
| `command` | string | `null` | 用于发送通知的自定义命令。`$TITLE` 和 `$MESSAGE` 可作为占位符。 |
|
|
233
|
+
| `style` | object | `{...}` | 用于 Windows WPF 通知窗口的自定义样式。详见下文。 |
|
|
234
|
+
|
|
235
|
+
#### `notify.style` (Windows WPF)
|
|
236
|
+
|
|
237
|
+
自定义 WPF 通知窗口的外观。
|
|
238
|
+
|
|
239
|
+
| 属性 | 默认值 | 描述 |
|
|
240
|
+
|---|---|---|
|
|
241
|
+
| `backgroundColor` | `"#101018"` | 通知窗口的背景颜色。 |
|
|
242
|
+
| `backgroundOpacity` | `0.95` | 背景不透明度 (0 到 1)。 |
|
|
243
|
+
| `textColor` | `"#AAAAAA"` | 主要文本颜色。 |
|
|
244
|
+
| `borderRadius` | `14` | 圆角半径 (像素)。 |
|
|
245
|
+
| `colorBarWidth` | `5` | 左侧强调色条的宽度 (像素)。 |
|
|
246
|
+
| `width` | `420` | 窗口宽度 (像素)。 |
|
|
247
|
+
| `height` | `105` | 窗口高度 (像素)。 |
|
|
248
|
+
| `titleFontSize` | `14` | 标题的字体大小 (pt)。 |
|
|
249
|
+
| `contentFontSize` | `11` | 内容的字体大小 (pt)。 |
|
|
250
|
+
| `iconFontSize` | `13` | 图标 (✅/❌) 的字体大小 (pt)。 |
|
|
251
|
+
| `duration` | `10000` | 通知自动关闭前的延迟时间 (毫秒)。 |
|
|
252
|
+
| `position` | `"center"` | 窗口位置: `"center"`, `"top-right"`, 或 `"bottom-right"`。 |
|
|
253
|
+
| `shadow` | `true` | 启用或禁用下拉阴影效果。 |
|
|
254
|
+
| `idleColor` | `"#4ADE80"` | 空闲 (成功) 通知的强调色。 |
|
|
255
|
+
| `errorColor` | `"#EF4444"` | 错误通知的强调色。 |
|
|
256
|
+
|
|
257
|
+
### `compaction`
|
|
258
|
+
|
|
259
|
+
此插件确保当 OpenCode 压缩会话上下文时,生成的摘要使用你的首选语言(例如中文),而不是默认为英文。
|
|
260
|
+
|
|
261
|
+
**配置选项:**
|
|
262
|
+
|
|
263
|
+
| 属性 | 类型 | 默认值 | 描述 |
|
|
264
|
+
|---|---|---|---|
|
|
265
|
+
| `enabled` | boolean | `true` | 启用或禁用此插件。 |
|
|
266
|
+
|
|
267
|
+
### `backgroundSubagent`
|
|
268
|
+
|
|
269
|
+
此插件向系统提示中注入一项策略,提醒 AI 代理在派发子代理时默认使用 `run_in_background=true`。这有助于保持主对话的响应性。如果派发了前台任务,则会显示一个友好的提醒。
|
|
270
|
+
|
|
271
|
+
**配置选项:**
|
|
272
|
+
|
|
273
|
+
| 属性 | 类型 | 默认值 | 描述 |
|
|
274
|
+
|---|---|---|---|
|
|
275
|
+
| `enabled` | boolean | `true` | 启用或禁用此插件。 |
|
|
276
|
+
|
|
277
|
+
### `leaderboard`
|
|
278
|
+
|
|
279
|
+
此插件向社区驱动的 [claudecount.com](https://claudecount.com) 排行榜报告 token 使用情况统计。默认禁用。
|
|
280
|
+
|
|
281
|
+
**配置选项:**
|
|
282
|
+
|
|
283
|
+
| 属性 | 类型 | 默认值 | 描述 |
|
|
284
|
+
|---|---|---|---|
|
|
285
|
+
| `enabled` | boolean | `false` | 启用或禁用此插件。 |
|
|
286
|
+
| `configPath` | string | `null` | 可选的自定义 `leaderboard.json` 文件路径。如果未设置,则会搜索标准位置 (`~/.claude/leaderboard.json`, `~/.config/claude/leaderboard.json`)。 |
|
|
287
|
+
|
|
288
|
+
### `logging`
|
|
289
|
+
|
|
290
|
+
为 `oc-tweaks` 配置日志记录。
|
|
291
|
+
|
|
292
|
+
**配置选项:**
|
|
293
|
+
|
|
294
|
+
| 属性 | 类型 | 默认值 | 描述 |
|
|
295
|
+
|---|---|---|---|
|
|
296
|
+
| `enabled` | boolean | `false` | 启用将日志写入文件。 |
|
|
297
|
+
| `maxLines` | number | `100` | 日志文件中保留的最大行数。旧行将被截断。 |
|
|
298
|
+
|
|
299
|
+
日志文件路径为 `~/.config/opencode/plugins/oc-tweaks.log`。
|
|
300
|
+
|
|
301
|
+
## 完整配置示例
|
|
302
|
+
|
|
303
|
+
这是一个 `~/.config/opencode/oc-tweaks.json` 文件的完整示例,展示了所有选项。
|
|
304
|
+
|
|
305
|
+
```json
|
|
306
|
+
{
|
|
307
|
+
"notify": {
|
|
308
|
+
"enabled": true,
|
|
309
|
+
"notifyOnIdle": true,
|
|
310
|
+
"notifyOnError": true,
|
|
311
|
+
// 自定义命令示例:通过 SSH 向另一台机器发送通知
|
|
312
|
+
// "command": "ssh my-desktop 'notify-send \"$TITLE\" \"$MESSAGE\"'",
|
|
313
|
+
"style": {
|
|
314
|
+
"backgroundColor": "#101018",
|
|
315
|
+
"duration": 8000
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
"compaction": {
|
|
319
|
+
"enabled": true
|
|
320
|
+
},
|
|
321
|
+
"backgroundSubagent": {
|
|
322
|
+
"enabled": true
|
|
323
|
+
},
|
|
324
|
+
"leaderboard": {
|
|
325
|
+
"enabled": false
|
|
326
|
+
// "configPath": "/path/to/my/leaderboard.json"
|
|
327
|
+
},
|
|
328
|
+
"logging": {
|
|
329
|
+
"enabled": false,
|
|
330
|
+
"maxLines": 200
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "oc-tweaks",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./src/index.ts"
|
|
7
|
+
},
|
|
8
|
+
"peerDependencies": {
|
|
9
|
+
"@opencode-ai/plugin": ">=1.0.0"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@opencode-ai/plugin": "^1.2.15"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"oc-tweaks": "./src/cli/init.ts"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "bun test",
|
|
19
|
+
"smoke": "bun scripts/smoke-test.ts"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"src/"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
|
|
3
|
+
import { describe, test, expect, afterEach } from "bun:test"
|
|
4
|
+
|
|
5
|
+
const originalBunFile = Bun.file
|
|
6
|
+
const originalHome = Bun.env?.HOME
|
|
7
|
+
|
|
8
|
+
function mockBunFile(mockData: Record<string, any>) {
|
|
9
|
+
;(globalThis as any).Bun.file = (path: string) => ({
|
|
10
|
+
exists: async () => path in mockData,
|
|
11
|
+
json: async () => {
|
|
12
|
+
if (!(path in mockData)) throw new Error("ENOENT")
|
|
13
|
+
const data = mockData[path]
|
|
14
|
+
if (data instanceof Error) throw data
|
|
15
|
+
return data
|
|
16
|
+
},
|
|
17
|
+
text: async () => JSON.stringify(mockData[path] ?? ""),
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
;(globalThis as any).Bun.file = originalBunFile
|
|
23
|
+
if (originalHome === undefined) {
|
|
24
|
+
delete (Bun.env as any).HOME
|
|
25
|
+
} else {
|
|
26
|
+
;(Bun.env as any).HOME = originalHome
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe("backgroundSubagentPlugin", () => {
|
|
31
|
+
test("injects English system prompt", async () => {
|
|
32
|
+
const home = "/tmp/oc-background-system"
|
|
33
|
+
;(Bun.env as any).HOME = home
|
|
34
|
+
const path = `${home}/.config/opencode/oc-tweaks.json`
|
|
35
|
+
mockBunFile({
|
|
36
|
+
[path]: { backgroundSubagent: { enabled: true } },
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const { backgroundSubagentPlugin } = await import("../plugins/background-subagent")
|
|
40
|
+
const hooks = await backgroundSubagentPlugin()
|
|
41
|
+
|
|
42
|
+
const output = { system: [] as string[] }
|
|
43
|
+
await hooks["experimental.chat.system.transform"]({}, output)
|
|
44
|
+
|
|
45
|
+
expect(output.system.length).toBe(1)
|
|
46
|
+
expect(output.system[0]).toContain("Sub-Agent Dispatch Policy")
|
|
47
|
+
expect(output.system[0]).not.toMatch(/[\u4e00-\u9fff]/)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test("does not append warning for non-task tools", async () => {
|
|
51
|
+
const home = "/tmp/oc-background-non-task"
|
|
52
|
+
;(Bun.env as any).HOME = home
|
|
53
|
+
const path = `${home}/.config/opencode/oc-tweaks.json`
|
|
54
|
+
mockBunFile({
|
|
55
|
+
[path]: { backgroundSubagent: { enabled: true } },
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const { backgroundSubagentPlugin } = await import("../plugins/background-subagent")
|
|
59
|
+
const hooks = await backgroundSubagentPlugin()
|
|
60
|
+
|
|
61
|
+
const beforeOutput = { args: { run_in_background: false } }
|
|
62
|
+
await hooks["tool.execute.before"]({ callID: "c1", tool: "other" }, beforeOutput)
|
|
63
|
+
|
|
64
|
+
const afterOutput = { output: "ok" }
|
|
65
|
+
await hooks["tool.execute.after"]({ callID: "c1" }, afterOutput)
|
|
66
|
+
|
|
67
|
+
expect(afterOutput.output).toBe("ok")
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test("tracks foreground task calls and appends violation warning", async () => {
|
|
71
|
+
const home = "/tmp/oc-background-violation"
|
|
72
|
+
;(Bun.env as any).HOME = home
|
|
73
|
+
const path = `${home}/.config/opencode/oc-tweaks.json`
|
|
74
|
+
mockBunFile({
|
|
75
|
+
[path]: { backgroundSubagent: { enabled: true } },
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const { backgroundSubagentPlugin } = await import("../plugins/background-subagent")
|
|
79
|
+
const hooks = await backgroundSubagentPlugin()
|
|
80
|
+
|
|
81
|
+
const beforeOutput = { args: { run_in_background: false } }
|
|
82
|
+
await hooks["tool.execute.before"]({ callID: "c2", tool: "task" }, beforeOutput)
|
|
83
|
+
|
|
84
|
+
const afterOutput = { output: "result" }
|
|
85
|
+
await hooks["tool.execute.after"]({ callID: "c2" }, afterOutput)
|
|
86
|
+
|
|
87
|
+
expect(afterOutput.output).toContain("[Reminder]")
|
|
88
|
+
expect(afterOutput.output).not.toMatch(/[\u4e00-\u9fff]/)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test("returns empty hooks when disabled", async () => {
|
|
92
|
+
const home = "/tmp/oc-background-disabled"
|
|
93
|
+
;(Bun.env as any).HOME = home
|
|
94
|
+
const path = `${home}/.config/opencode/oc-tweaks.json`
|
|
95
|
+
mockBunFile({
|
|
96
|
+
[path]: {
|
|
97
|
+
backgroundSubagent: { enabled: false },
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const { backgroundSubagentPlugin } = await import("../plugins/background-subagent")
|
|
102
|
+
const hooks = await backgroundSubagentPlugin()
|
|
103
|
+
|
|
104
|
+
expect(hooks).toEqual({})
|
|
105
|
+
})
|
|
106
|
+
})
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
|
|
3
|
+
import { describe, test, expect, afterEach } from "bun:test"
|
|
4
|
+
|
|
5
|
+
const originalBunFile = Bun.file
|
|
6
|
+
const originalBunWrite = Bun.write
|
|
7
|
+
const originalHome = Bun.env?.HOME
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
;(globalThis as any).Bun.file = originalBunFile
|
|
11
|
+
;(globalThis as any).Bun.write = originalBunWrite
|
|
12
|
+
if (originalHome === undefined) {
|
|
13
|
+
delete (Bun.env as any).HOME
|
|
14
|
+
} else {
|
|
15
|
+
;(Bun.env as any).HOME = originalHome
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe("CLI init", () => {
|
|
20
|
+
test("creates config file when it does not exist", async () => {
|
|
21
|
+
const written: Record<string, string> = {}
|
|
22
|
+
;(globalThis as any).Bun.write = async (path: string, content: string) => {
|
|
23
|
+
written[path] = content
|
|
24
|
+
return 0
|
|
25
|
+
}
|
|
26
|
+
;(globalThis as any).Bun.file = (path: string) => ({
|
|
27
|
+
exists: async () => path in written,
|
|
28
|
+
json: async () => JSON.parse(written[path] ?? "{}"),
|
|
29
|
+
})
|
|
30
|
+
;(Bun.env as any).HOME = "/tmp/oc-init-create"
|
|
31
|
+
|
|
32
|
+
const { initConfig } = await import("../cli/init")
|
|
33
|
+
const result = await initConfig()
|
|
34
|
+
|
|
35
|
+
expect(result.created).toBe(true)
|
|
36
|
+
expect(result.path).toContain("oc-tweaks.json")
|
|
37
|
+
|
|
38
|
+
const configPath = "/tmp/oc-init-create/.config/opencode/oc-tweaks.json"
|
|
39
|
+
const content = JSON.parse(written[configPath] ?? "{}")
|
|
40
|
+
expect(content.notify?.enabled).toBe(true)
|
|
41
|
+
expect(content.leaderboard?.enabled).toBe(false)
|
|
42
|
+
expect(content.logging?.maxLines).toBe(200)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test("does not overwrite when config already exists", async () => {
|
|
46
|
+
const existingContent = JSON.stringify({ notify: { enabled: false } })
|
|
47
|
+
const written: Record<string, string> = {}
|
|
48
|
+
const configPath = "/tmp/oc-init-exists/.config/opencode/oc-tweaks.json"
|
|
49
|
+
written[configPath] = existingContent
|
|
50
|
+
|
|
51
|
+
let writeCount = 0
|
|
52
|
+
;(globalThis as any).Bun.write = async (path: string, content: string) => {
|
|
53
|
+
writeCount++
|
|
54
|
+
written[path] = content
|
|
55
|
+
return 0
|
|
56
|
+
}
|
|
57
|
+
;(globalThis as any).Bun.file = (path: string) => ({
|
|
58
|
+
exists: async () => path in written,
|
|
59
|
+
json: async () => JSON.parse(written[path] ?? "{}"),
|
|
60
|
+
})
|
|
61
|
+
;(Bun.env as any).HOME = "/tmp/oc-init-exists"
|
|
62
|
+
|
|
63
|
+
const { initConfig } = await import("../cli/init")
|
|
64
|
+
const result = await initConfig()
|
|
65
|
+
|
|
66
|
+
expect(result.created).toBe(false)
|
|
67
|
+
expect(writeCount).toBe(0)
|
|
68
|
+
expect(written[configPath]).toBe(existingContent) // unchanged
|
|
69
|
+
})
|
|
70
|
+
})
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
4
|
+
|
|
5
|
+
import { compactionPlugin } from "../plugins/compaction"
|
|
6
|
+
|
|
7
|
+
const originalBunFile = Bun.file
|
|
8
|
+
const originalHome = Bun.env?.HOME
|
|
9
|
+
|
|
10
|
+
function mockBunFile(mockData: Record<string, any>) {
|
|
11
|
+
;(globalThis as any).Bun.file = (path: string) => ({
|
|
12
|
+
exists: async () => path in mockData,
|
|
13
|
+
json: async () => {
|
|
14
|
+
if (!(path in mockData)) throw new Error("ENOENT")
|
|
15
|
+
const data = mockData[path]
|
|
16
|
+
if (data instanceof Error) throw data
|
|
17
|
+
return data
|
|
18
|
+
},
|
|
19
|
+
text: async () => JSON.stringify(mockData[path] ?? ""),
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function restoreBunFile() {
|
|
24
|
+
;(globalThis as any).Bun.file = originalBunFile
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function setHome(home: string | undefined) {
|
|
28
|
+
if (home === undefined) {
|
|
29
|
+
delete (Bun.env as any).HOME
|
|
30
|
+
} else {
|
|
31
|
+
;(Bun.env as any).HOME = home
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
setHome(originalHome)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
restoreBunFile()
|
|
41
|
+
setHome(originalHome)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe("compactionPlugin", () => {
|
|
45
|
+
test("pushes language preference prompt into context", async () => {
|
|
46
|
+
const home = "/tmp/oc-home-compaction"
|
|
47
|
+
const path = `${home}/.config/opencode/oc-tweaks.json`
|
|
48
|
+
|
|
49
|
+
setHome(home)
|
|
50
|
+
mockBunFile({
|
|
51
|
+
[path]: { compaction: { enabled: true } },
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const plugin = await compactionPlugin()
|
|
55
|
+
const hook = plugin["experimental.session.compacting"]
|
|
56
|
+
const output = { context: [] as string[] }
|
|
57
|
+
|
|
58
|
+
await hook({ sessionID: "s-1" }, output)
|
|
59
|
+
|
|
60
|
+
expect(output.context.length).toBe(1)
|
|
61
|
+
const prompt = output.context[0]
|
|
62
|
+
expect(prompt).toContain("Language Preference")
|
|
63
|
+
expect(prompt).toContain("preferred language")
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test("prompt includes preferred language phrasing", async () => {
|
|
67
|
+
const home = "/tmp/oc-home-compaction-phrase"
|
|
68
|
+
const path = `${home}/.config/opencode/oc-tweaks.json`
|
|
69
|
+
|
|
70
|
+
setHome(home)
|
|
71
|
+
mockBunFile({
|
|
72
|
+
[path]: {
|
|
73
|
+
compaction: { enabled: true },
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const plugin = await compactionPlugin()
|
|
78
|
+
const hook = plugin["experimental.session.compacting"]
|
|
79
|
+
const output = { context: [] as string[] }
|
|
80
|
+
|
|
81
|
+
await hook({ sessionID: "s-2" }, output)
|
|
82
|
+
|
|
83
|
+
const prompt = output.context[0]
|
|
84
|
+
expect(prompt.toLowerCase()).toContain("preferred language")
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test("returns empty hooks when compaction is disabled", async () => {
|
|
88
|
+
const home = "/tmp/oc-home-compaction-disabled"
|
|
89
|
+
const path = `${home}/.config/opencode/oc-tweaks.json`
|
|
90
|
+
|
|
91
|
+
setHome(home)
|
|
92
|
+
mockBunFile({
|
|
93
|
+
[path]: {
|
|
94
|
+
compaction: { enabled: false },
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const plugin = await compactionPlugin()
|
|
99
|
+
|
|
100
|
+
expect(plugin).toEqual({})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test("returns empty hooks when config file is missing", async () => {
|
|
104
|
+
const home = "/tmp/oc-home-compaction-missing"
|
|
105
|
+
|
|
106
|
+
setHome(home)
|
|
107
|
+
mockBunFile({})
|
|
108
|
+
|
|
109
|
+
const plugin = await compactionPlugin()
|
|
110
|
+
|
|
111
|
+
expect(plugin).toEqual({})
|
|
112
|
+
})
|
|
113
|
+
})
|