oc-tweaks 0.1.3 → 0.2.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 CHANGED
@@ -11,6 +11,7 @@ A collection of runtime enhancement plugins for [OpenCode](https://opencode.ai/)
11
11
  The currently available plugins are:
12
12
  - **`notify`**: Sends desktop notifications when a task is completed or an error occurs.
13
13
  - **`compaction`**: Injects a language preference prompt during session compaction to ensure summaries are in your preferred language.
14
+ - **`autoMemory`**: Smart memory assistant that injects memory context, detects memory trigger phrases, and supports active memory writes via `remember` tool and `/remember` command.
14
15
  - **`backgroundSubagent`**: Adds a system prompt to encourage using background sub-agents for better responsiveness.
15
16
  - **`leaderboard`**: Reports token usage to [claudecount.com](https://claudecount.com) for community leaderboards.
16
17
 
@@ -96,6 +97,20 @@ This plugin ensures that when OpenCode compacts a session's context, the resulti
96
97
  |---|---|---|---|
97
98
  | `enabled` | boolean | `true` | Enable or disable the plugin. |
98
99
 
100
+ ### `autoMemory`
101
+
102
+ This plugin provides an intelligent memory workflow:
103
+ - Injects global/project memory context into system prompt.
104
+ - Adds trigger phrases (Chinese + English) so the assistant remembers when to persist key information.
105
+ - Registers a custom `remember` tool for active memory writes.
106
+ - Creates `~/.config/opencode/commands/remember.md` automatically (if missing) so users can run `/remember` explicitly.
107
+
108
+ **Configuration Options:**
109
+
110
+ | Property | Type | Default | Description |
111
+ |---|---|---|---|
112
+ | `enabled` | boolean | `true` | Enable or disable the plugin. |
113
+
99
114
  ### `backgroundSubagent`
100
115
 
101
116
  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.
@@ -150,6 +165,9 @@ Here is an example of a `~/.config/opencode/oc-tweaks.json` file with all option
150
165
  "compaction": {
151
166
  "enabled": true
152
167
  },
168
+ "autoMemory": {
169
+ "enabled": true
170
+ },
153
171
  "backgroundSubagent": {
154
172
  "enabled": true
155
173
  },
@@ -179,6 +197,7 @@ Here is an example of a `~/.config/opencode/oc-tweaks.json` file with all option
179
197
  目前可用的插件包括:
180
198
  - **`notify`**: 在任务完成或发生错误时发送桌面通知。
181
199
  - **`compaction`**: 在会话上下文压缩期间注入语言偏好提示,以确保摘要使用你的首选语言。
200
+ - **`autoMemory`**: 智能记忆助手——自动注入 memory 上下文、识别触发词,并支持 `remember` tool 与 `/remember` 命令主动写入。
182
201
  - **`backgroundSubagent`**: 添加系统提示,鼓励使用后台子代理以获得更好的响应性。
183
202
  - **`leaderboard`**: 向 [claudecount.com](https://claudecount.com) 报告 token 用量,用于社区排行榜。
184
203
 
@@ -264,6 +283,20 @@ bunx oc-tweaks init
264
283
  |---|---|---|---|
265
284
  | `enabled` | boolean | `true` | 启用或禁用此插件。 |
266
285
 
286
+ ### `autoMemory`
287
+
288
+ 该插件提供智能记忆工作流:
289
+ - 自动把全局/项目 memory 上下文注入 system prompt。
290
+ - 内置中英文触发词,命中后优先执行记忆写入。
291
+ - 注册自定义 `remember` tool,支持 AI 主动写入 memory 文件。
292
+ - 自动创建 `~/.config/opencode/commands/remember.md`(若不存在),支持用户显式输入 `/remember`。
293
+
294
+ **配置选项:**
295
+
296
+ | 属性 | 类型 | 默认值 | 描述 |
297
+ |---|---|---|---|
298
+ | `enabled` | boolean | `true` | 启用或禁用此插件。 |
299
+
267
300
  ### `backgroundSubagent`
268
301
 
269
302
  此插件向系统提示中注入一项策略,提醒 AI 代理在派发子代理时默认使用 `run_in_background=true`。这有助于保持主对话的响应性。如果派发了前台任务,则会显示一个友好的提醒。
@@ -318,6 +351,9 @@ bunx oc-tweaks init
318
351
  "compaction": {
319
352
  "enabled": true
320
353
  },
354
+ "autoMemory": {
355
+ "enabled": true
356
+ },
321
357
  "backgroundSubagent": {
322
358
  "enabled": true
323
359
  },
package/dist/cli/init.js CHANGED
@@ -7,6 +7,7 @@ import { dirname } from "path";
7
7
  var DEFAULT_CONFIG = {
8
8
  notify: { enabled: true },
9
9
  compaction: { enabled: true },
10
+ autoMemory: { enabled: true },
10
11
  backgroundSubagent: { enabled: true },
11
12
  leaderboard: { enabled: false },
12
13
  logging: { enabled: false, maxLines: 200 }
package/dist/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ // src/plugins/auto-memory.ts
2
+ import { tool } from "@opencode-ai/plugin";
3
+ import { mkdir as mkdir2, readdir } from "node:fs/promises";
4
+
1
5
  // src/utils/logger.ts
2
6
  import { mkdir } from "node:fs/promises";
3
7
  import { dirname } from "node:path";
@@ -59,6 +63,7 @@ async function loadJsonConfig(path, defaults) {
59
63
  }
60
64
  var DEFAULT_CONFIG = {
61
65
  compaction: {},
66
+ autoMemory: {},
62
67
  backgroundSubagent: {},
63
68
  leaderboard: {},
64
69
  notify: {}
@@ -76,6 +81,203 @@ async function loadOcTweaksConfig() {
76
81
  return null;
77
82
  }
78
83
  }
84
+ // src/plugins/auto-memory.ts
85
+ var TRIGGER_WORDS_CN = ["记住", "保存偏好", "记录一下", "记到memory", "别忘了"];
86
+ var TRIGGER_WORDS_EN = ["remember", "save to memory", "note this down", "don't forget", "record"];
87
+ var REMEMBER_COMMAND_CONTENT = `---
88
+ description: 记忆助手 - 从当前会话提取关键信息并写入 memory 文件
89
+ ---
90
+
91
+ 当用户希望你记住偏好、决策或长期有价值的信息时:
92
+ 1. 提取要保存的信息(保持原意,不扩写)
93
+ 2. 推断 category(如 preferences / decisions / setup / notes)
94
+ 3. 推断 scope(global 或 project)
95
+ 4. 调用 remember tool 执行写入
96
+
97
+ 参数:
98
+ - content: 要保存的内容
99
+ - category: 目标文件分类(不带 .md)
100
+ - scope: global | project
101
+
102
+ 如有参数,则优先围绕参数提取重点:$ARGUMENTS
103
+ `;
104
+ function getHome2() {
105
+ return Bun.env?.HOME ?? process.env.HOME ?? "";
106
+ }
107
+ function sanitizeCategory(raw) {
108
+ if (!raw || !raw.trim())
109
+ return "notes";
110
+ const normalized = raw.trim().toLowerCase().replace(/\.md$/i, "").replace(/[^a-z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
111
+ return normalized || "notes";
112
+ }
113
+ function resolveScope(raw) {
114
+ if (!raw)
115
+ return "global";
116
+ return raw.trim().toLowerCase() === "project" ? "project" : "global";
117
+ }
118
+ async function listMarkdownFiles(path) {
119
+ try {
120
+ const entries = await readdir(path);
121
+ return entries.filter((item) => item.endsWith(".md")).sort();
122
+ } catch {
123
+ return [];
124
+ }
125
+ }
126
+ async function readPreferences(path) {
127
+ try {
128
+ const file = Bun.file(path);
129
+ if (!await file.exists())
130
+ return "(尚无 preferences.md)";
131
+ const content = await file.text();
132
+ return content.trim() || "(preferences.md 为空)";
133
+ } catch {
134
+ return "(读取 preferences.md 失败)";
135
+ }
136
+ }
137
+ async function ensureRememberCommand(home) {
138
+ const commandDir = `${home}/.config/opencode/commands`;
139
+ const commandPath = `${commandDir}/remember.md`;
140
+ const commandFile = Bun.file(commandPath);
141
+ if (await commandFile.exists())
142
+ return;
143
+ await mkdir2(commandDir, { recursive: true });
144
+ await Bun.write(commandPath, REMEMBER_COMMAND_CONTENT);
145
+ }
146
+ async function ensureAutoMemoryInfra(home, projectMemoryDir) {
147
+ await mkdir2(`${home}/.config/opencode/memory`, { recursive: true });
148
+ await mkdir2(projectMemoryDir, { recursive: true });
149
+ await ensureRememberCommand(home);
150
+ }
151
+ async function appendMemoryRecord(filePath, content) {
152
+ const file = Bun.file(filePath);
153
+ let previous = "";
154
+ try {
155
+ if (await file.exists()) {
156
+ previous = await file.text();
157
+ }
158
+ } catch {}
159
+ const prefix = previous.length > 0 && !previous.endsWith(`
160
+ `) ? `
161
+ ` : "";
162
+ const record = `[${new Date().toISOString()}]
163
+ ${content.trim()}
164
+
165
+ `;
166
+ await Bun.write(filePath, `${previous}${prefix}${record}`);
167
+ }
168
+ function buildMemoryGuide(params) {
169
+ const globalList = params.globalFiles.length > 0 ? params.globalFiles.map((name) => `- \`${name}\``).join(`
170
+ `) : "- (暂无全局 memory 文件)";
171
+ const projectList = params.projectFiles.length > 0 ? params.projectFiles.map((name) => `- \`${name}\``).join(`
172
+ `) : "- (暂无项目级 memory 文件)";
173
+ return `## \uD83E\uDDE0 Memory 系统指引
174
+
175
+ 可用记忆层:
176
+ 1. 全局 memory:\`${params.globalMemoryDir}\`
177
+ 2. 项目 memory:\`${params.projectMemoryDir}\`
178
+
179
+ ### 当前文件
180
+ **全局**
181
+ ${globalList}
182
+
183
+ **项目级**
184
+ ${projectList}
185
+
186
+ ### 触发词(优先调用 remember tool)
187
+ - 中文:${TRIGGER_WORDS_CN.join("、")}
188
+ - English: ${TRIGGER_WORDS_EN.join(", ")}
189
+
190
+ 命中触发词后:
191
+ 1. 提取要保存的信息
192
+ 2. 判断 scope(global / project)
193
+ 3. 判断 category(例如 preferences / decisions / setup / notes)
194
+ 4. 调用 \`remember\` tool 写入
195
+
196
+ ### 用户核心 Preferences
197
+ \`\`\`markdown
198
+ ${params.preferencesContent}
199
+ \`\`\`
200
+ `;
201
+ }
202
+ var autoMemoryPlugin = async ({ directory }) => {
203
+ const home = getHome2();
204
+ const globalMemoryDir = `${home}/.config/opencode/memory`;
205
+ const projectMemoryDir = `${directory}/.opencode/memory`;
206
+ try {
207
+ const config = await loadOcTweaksConfig();
208
+ if (config?.autoMemory?.enabled === true) {
209
+ await ensureAutoMemoryInfra(home, projectMemoryDir);
210
+ }
211
+ } catch {}
212
+ return {
213
+ "experimental.chat.system.transform": safeHook("auto-memory:system.transform", async (_input, output) => {
214
+ const config = await loadOcTweaksConfig();
215
+ if (!config || config.autoMemory?.enabled !== true)
216
+ return;
217
+ await ensureAutoMemoryInfra(home, projectMemoryDir);
218
+ const [globalFiles, projectFiles, preferencesContent] = await Promise.all([
219
+ listMarkdownFiles(globalMemoryDir),
220
+ listMarkdownFiles(projectMemoryDir),
221
+ readPreferences(`${globalMemoryDir}/preferences.md`)
222
+ ]);
223
+ output.system.push(buildMemoryGuide({
224
+ globalMemoryDir,
225
+ projectMemoryDir,
226
+ globalFiles,
227
+ projectFiles,
228
+ preferencesContent
229
+ }));
230
+ }),
231
+ "experimental.session.compacting": safeHook("auto-memory:compacting", async (_input, output) => {
232
+ const config = await loadOcTweaksConfig();
233
+ if (!config || config.autoMemory?.enabled !== true)
234
+ return;
235
+ await ensureAutoMemoryInfra(home, projectMemoryDir);
236
+ output.context.push(`## \uD83D\uDCBE Memory 保存提示 (Compaction Phase)
237
+
238
+ 如果本轮对话有值得长期保存的信息,请在摘要中标记:
239
+
240
+ \`\`\`
241
+ [MEMORY: 文件名.md]
242
+ 这里写要保存的内容
243
+ \`\`\`
244
+
245
+ 后续对话中应根据该标记调用 write/edit 或 remember tool 写入对应 memory 文件。`);
246
+ }),
247
+ tool: {
248
+ remember: tool({
249
+ description: "Save important session facts into global/project memory markdown files",
250
+ args: {
251
+ content: tool.schema.string(),
252
+ category: tool.schema.string().optional(),
253
+ scope: tool.schema.string().optional()
254
+ },
255
+ async execute(args, context) {
256
+ try {
257
+ const config = await loadOcTweaksConfig();
258
+ if (!config || config.autoMemory?.enabled !== true) {
259
+ return "autoMemory is disabled in oc-tweaks config";
260
+ }
261
+ const scope = resolveScope(args.scope);
262
+ const category = sanitizeCategory(args.category);
263
+ const runtimeHome = getHome2();
264
+ await ensureAutoMemoryInfra(runtimeHome, `${context.directory}/.opencode/memory`);
265
+ const targetDir = scope === "project" ? `${context.directory}/.opencode/memory` : `${runtimeHome}/.config/opencode/memory`;
266
+ await mkdir2(targetDir, { recursive: true });
267
+ const targetPath = `${targetDir}/${category}.md`;
268
+ await appendMemoryRecord(targetPath, args.content);
269
+ return `Saved to ${targetPath}
270
+
271
+ Content preview: ${args.content.slice(0, 150)}${args.content.length > 150 ? "..." : ""}`;
272
+ } catch (error) {
273
+ const message = error instanceof Error ? error.message : String(error);
274
+ return `Failed to save memory: ${message}`;
275
+ }
276
+ }
277
+ })
278
+ }
279
+ };
280
+ };
79
281
  // src/plugins/background-subagent.ts
80
282
  var SUB_AGENT_DISPATCH_PROMPT = `
81
283
  ## Sub-Agent Dispatch Policy
@@ -141,7 +343,7 @@ var compactionPlugin = async () => {
141
343
  };
142
344
  };
143
345
  // src/plugins/leaderboard.ts
144
- function getHome2() {
346
+ function getHome3() {
145
347
  return Bun.env?.HOME ?? (globalThis?.process?.env?.HOME ?? "") ?? "";
146
348
  }
147
349
  var API_ENDPOINT = "https://api.claudecount.com/api/usage/hook";
@@ -185,7 +387,7 @@ async function loadLeaderboardConfig(configPath) {
185
387
  if (typeof configPath === "string") {
186
388
  return readLeaderboardConfig(configPath);
187
389
  }
188
- const paths = [`${getHome2()}/.claude/leaderboard.json`, `${getHome2()}/.config/claude/leaderboard.json`];
390
+ const paths = [`${getHome3()}/.claude/leaderboard.json`, `${getHome3()}/.config/claude/leaderboard.json`];
189
391
  for (const path of paths) {
190
392
  const config = await readLeaderboardConfig(path);
191
393
  if (config)
@@ -550,5 +752,6 @@ export {
550
752
  notifyPlugin,
551
753
  leaderboardPlugin,
552
754
  compactionPlugin,
553
- backgroundSubagentPlugin
755
+ backgroundSubagentPlugin,
756
+ autoMemoryPlugin
554
757
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-tweaks",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dist/index.js"