@hippo-memo/cli 1.0.0 → 1.0.2

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.
@@ -15,8 +15,14 @@ Hippo Memory 的预设内存模板集合。
15
15
  ```
16
16
  template/
17
17
  ├── mcp/ # MCP 服务器相关模板
18
- │ └── system/ # 系统级内存模板
18
+ │ └── AGENTS.md # MCP 协议配置
19
+ ├── skill/ # Claude Code Skill 模板
20
+ │ ├── hippo-memory/ # 记忆管理技能
21
+ │ └── hippo-init/ # 初始化引导技能
19
22
  └── system/ # 通用系统模板
23
+ ├── agent.md # AI 能力边界
24
+ ├── soul.md # AI 身份和行为模式
25
+ └── user.md # 用户信息和偏好
20
26
  ```
21
27
 
22
28
  ## 系统模板
@@ -50,4 +56,38 @@ AI 能力边界定义
50
56
 
51
57
  ## 使用方式
52
58
 
53
- 模板通常在初始化新的 Hippo Memory 实例时自动应用,通过 `@hippo-memo/core` 包的初始化功能加载。
59
+ ### CLI 初始化
60
+
61
+ 运行 `hippo init` 时会自动:
62
+
63
+ 1. 创建 `.hippo` 记忆存储目录
64
+ 2. 初始化系统记忆(system://boot, system://soul, system://user)
65
+ 3. 提示选择 Claude Code 集成方式:
66
+ - **MCP 模式**:将协议注入到 `AGENTS.md` 或 `CLAUDE.md`
67
+ - **Skill 模式**:复制技能模板到 `.claude/skills/`
68
+ - **混合模式**:同时配置 MCP 和 Skill
69
+
70
+ ### MCP vs Skill
71
+
72
+ | 特性 | MCP | Skill |
73
+ |------|-----|-------|
74
+ | **作用域** | 全局/用户级 | 项目级 |
75
+ | **存储位置** | Claude 配置目录 | `.claude/skills/` |
76
+ | **版本控制** | 不推荐 | 推荐 |
77
+ | **适用场景** | 跨项目共享 | 项目特定流程 |
78
+
79
+ ### 技能说明
80
+
81
+ #### hippo-memory
82
+ 提供完整的 Hippo Memory 操作指南,包括:
83
+ - 创建、读取、更新、删除记忆
84
+ - 搜索记忆内容
85
+ - 管理记忆别名
86
+ - URI 设计规范和最佳实践
87
+
88
+ #### hippo-init
89
+ 项目初始化引导技能,包括:
90
+ - 记忆目录创建
91
+ - MCP vs Skill 选择指导
92
+ - 配置文件说明
93
+ - 故障排查
@@ -0,0 +1,257 @@
1
+ ---
2
+ name: hippo-memory
3
+ description: 【必须主动判定触发】每当用户透露偏好、约定、决策时立即存储。主动触发场景:①用户说"记住"、"记录"、"我习惯" ②发现项目决策/架构约定 ③用户表达偏好"我喜欢/我不用/我不喜欢" ④重复出现的信息应缓存。
4
+ ---
5
+
6
+ # Hippo Memory Skill
7
+
8
+ ## 概述
9
+
10
+ 此技能提供 Hippo Memory 存储系统的管理功能,通过 `bunx @hippo-memo/cli@latest` 命令进行记忆的增删改查、搜索和别名操作。
11
+
12
+ ## ⚡ 会话启动协议
13
+
14
+ **每次新会话必须且只能执行:**
15
+ ```bash
16
+ bunx @hippo-memo/cli@latest memory read "system://boot"
17
+ ```
18
+
19
+ ## 🎯 "三思而后答"强制检查
20
+
21
+ **回复前必须完成以下思维链:**
22
+
23
+ | 检查项 | 判定 | 动作 |
24
+ |--------|------|------|
25
+ | **A. 领域** | 用户询问私有记忆/历史/偏好? | → 立即检索 |
26
+ | **B. 完备性** | 我有确凿证据支撑回答? | → 不确定则先验证 |
27
+ | **C. 决策** | A=B=是 | → **调用 CLI 后再输出** |
28
+
29
+ **违规 = 任务失败**
30
+
31
+ ## 🏗️ 系统节点
32
+
33
+ 推荐创建以下系统级记忆节点:
34
+
35
+ | URI | 作用 |
36
+ |-----|------|
37
+ | `system://boot` | 会话入口,合成核心摘要 |
38
+ | `system://soul` | 身份/行为模式 |
39
+ | `system://user` | 用户信息/偏好 |
40
+ | `system://project` | 项目核心信息 |
41
+ | `system://recent` | 近期事件 |
42
+
43
+ ## 📖 读取协议:检索优先
44
+
45
+ ### 必须检索的触发条件(任一)
46
+ 1. 用户提到:项目名、人名、过往约定
47
+ 2. 问题包含:"我记得"、"之前说过"、"我的配置"
48
+ 3. 涉及时间线:"上周"、"上次"
49
+ 4. disclosure 被触发
50
+ 5. 回答依赖"可能"、"大概"
51
+
52
+ ### 检索策略
53
+ ```bash
54
+ # 确切 URI
55
+ bunx @hippo-memo/cli@latest memory read "system://user"
56
+
57
+ # 大致位置
58
+ bunx @hippo-memo/cli@latest memory search "数据库"
59
+
60
+ # 不确定 - 多个关键词尝试
61
+ bunx @hippo-memo/cli@latest memory search "上周 项目 数据库"
62
+ ```
63
+
64
+ ### ❌ 禁止
65
+ - 基于通用知识回答私有信息
66
+ - 使用"可能"、"大概"而不验证
67
+ - 未检索时说"我记得..."
68
+
69
+ ### ✅ 正确
70
+ 调用 CLI → "根据您的记录..."
71
+
72
+ ## ✍️ 写入协议
73
+
74
+ ### create_memory 触发条件
75
+ - 新的重要认知/感悟
76
+ - 用户透露重要信息
77
+ - 发生重大事件
78
+ - 跨会话复用的结论
79
+
80
+ ### update_memory 触发条件
81
+ - **必须先 `memory read`**,再修正
82
+ - 发现过去认知错误
83
+ - 用户纠正你
84
+ - 信息过时
85
+
86
+ **铁律:改记忆前,先读记忆。**
87
+
88
+ ## URI 设计规范
89
+
90
+ ### 域名分类
91
+ - `system://` - 系统级记忆(身份、配置)
92
+ - `project://` - 项目级记忆(架构、决策)
93
+ - `user://` - 用户级记忆(偏好、习惯)
94
+ - `knowledge://` - 知识库(技术文档、最佳实践)
95
+ - `temp://` - 临时记忆(会话缓存)
96
+
97
+ ### 路径设计
98
+ ```
99
+ system://boot
100
+ system://soul
101
+ system://user/preferences
102
+ system://project/{project-name}
103
+ project://myapp/architecture/database
104
+ user://preferences/pkg-manager
105
+ knowledge://tech/auth-jwt
106
+ temp://session/cache
107
+ ```
108
+
109
+ ## CLI 命令
110
+
111
+ ### 1. 创建记忆
112
+ ```bash
113
+ bunx @hippo-memo/cli@latest memory create "user://note/test" "# 测试笔记\n这是内容"
114
+ ```
115
+ 选项:
116
+ - `-p, --priority <1-10>` - 优先级(默认: 5)
117
+ - `-d, --disclosure <string>` - 触发条件
118
+ - `--dir <directory>` - 记忆目录(默认: .memory)
119
+
120
+ ### 2. 读取记忆
121
+ ```bash
122
+ bunx @hippo-memo/cli@latest memory read "user://note/test"
123
+ ```
124
+
125
+ ### 3. 更新记忆
126
+ ```bash
127
+ bunx @hippo-memo/cli@latest memory update "user://note/test" "更新内容"
128
+ ```
129
+ 选项:
130
+ - `-p, --priority <1-10>` - 更新优先级
131
+ - `-d, --disclosure <string>` - 更新触发条件
132
+
133
+ ### 4. 删除记忆
134
+ ```bash
135
+ bunx @hippo-memo/cli@latest memory delete "user://note/test"
136
+ ```
137
+ 选项:
138
+ - `-r, --recursive` - 递归删除子记忆
139
+ - `--permanent` - 永久删除(不进入回收站)
140
+
141
+ ### 5. 搜索记忆
142
+ ```bash
143
+ bunx @hippo-memo/cli@latest memory search "测试"
144
+ ```
145
+ 选项:
146
+ - `--domain <string>` - 限定域名
147
+ - `-l, --limit <number>` - 结果数量(默认: 20)
148
+
149
+ ### 6. 列出所有记忆
150
+ ```bash
151
+ bunx @hippo-memo/cli@latest memory list
152
+ ```
153
+ 选项:
154
+ - `--domain <string>` - 限定域名
155
+
156
+ ### 7. 添加别名
157
+ ```bash
158
+ bunx @hippo-memo/cli@latest memory alias "alias://test" "user://note/test"
159
+ ```
160
+ 选项:
161
+ - `-p, --priority <1-10>` - 别名优先级(继承目标默认)
162
+ - `-d, --disclosure <string>` - 别名触发条件
163
+
164
+ ## 🔄 结构操作
165
+
166
+ ```bash
167
+ # 移动/重命名:先建后删
168
+ bunx @hippo-memo/cli@latest memory alias "new" "old"
169
+ bunx @hippo-memo/cli@latest memory delete "old"
170
+
171
+ # 删除前:先读确认
172
+ bunx @hippo-memo/cli@latest memory read "to_delete"
173
+ bunx @hippo-memo/cli@latest memory delete "to_delete"
174
+
175
+ # 多重入口
176
+ bunx @hippo-memo/cli@latest memory alias "path/a" "path/b"
177
+ bunx @hippo-memo/cli@latest memory alias "path/c" "path/b"
178
+ ```
179
+
180
+ ## 最佳实践
181
+
182
+ ### 优先级指南
183
+ | 级别 | 含义 | 上限 | 示例 |
184
+ |------|------|------|------|
185
+ | **10** | 核心身份 | 5条 | `system://boot`, `system://soul` |
186
+ | **8-9** | 关键事实/高频模式 | 15条 | 用户偏好、项目架构决策 |
187
+ | **5-7** | 一般记忆 | 无限制 | 技术文档、会议记录 |
188
+ | **1-4** | 冷门信息 | 无限制 | 临时缓存、调试信息 |
189
+
190
+ ### Disclosure 规范
191
+ Disclosure 定义了记忆的自动触发条件,帮助 LLM 主动检索:
192
+
193
+ ✅ **好的 disclosure**:
194
+ ```
195
+ "当用户提到项目 hippo 时"
196
+ "当讨论包管理器选择时"
197
+ "当周五或周末安排任务时"
198
+ "当实现认证功能时"
199
+ ```
200
+
201
+ ❌ **不好的 disclosure**:
202
+ ```
203
+ "重要"
204
+ "记住"
205
+ "用户信息"
206
+ "配置"
207
+ ```
208
+
209
+ ### 主动触发场景
210
+
211
+ | 场景 | 示例输入 | 存储动作 |
212
+ |------|----------|----------|
213
+ | **显性请求** | "记住我周末不工作" | `memory create "user://availability/weekend" ...` |
214
+ | **偏好表达** | "我喜欢用 pnpm" | `memory create "user://pref/pkg-manager" "pnpm" --disclosure "当选择包管理器时"` |
215
+ | **架构决策** | "这个项目用 PostgreSQL" | `memory create "project://myapp/database" "PostgreSQL 15+" --disclosure "当讨论数据库时"` |
216
+ | **技术约定** | "组件都用 shadcn/ui" | `memory create "project://myapp/ui-lib" "shadcn/ui" --disclosure "当创建 UI 组件时"` |
217
+
218
+ ## 🎭 Few-Shot 示例
219
+
220
+ ### 检索优先
221
+ ```
222
+ 用户:"我上周项目用的什么数据库?"
223
+
224
+ ❌ "可能是 PostgreSQL..."
225
+ ✅ bunx @hippo-memo/cli@latest memory search "上周 项目 数据库"
226
+ → bunx @hippo-memo/cli@latest memory read "..."
227
+ → "根据记录,用的是 MongoDB"
228
+ ```
229
+
230
+ ### Disclosure 触发
231
+ ```
232
+ 用户:"我要实现登录功能"
233
+
234
+ ✅ 检测到"登录"
235
+ → bunx @hippo-memo/cli@latest memory read "knowledge/auth"
236
+ → "您之前确定用 JWT 方案,对吗?"
237
+ ```
238
+
239
+ ### 写入新记忆
240
+ ```
241
+ 用户:"我周末不工作"
242
+
243
+ ✅ bunx @hippo-memo/cli@latest memory create \
244
+ "user://availability/weekend" \
245
+ "用户周末不工作,周一处理" \
246
+ --priority 8 \
247
+ --disclosure "当周五/周末安排任务时"
248
+ ```
249
+
250
+ ## 🔚 回复前自检
251
+
252
+ - [ ] 涉及私有信息?→ 调用 `memory read`/`memory search`
253
+ - [ ] 不确定?→ 先验证而非猜测
254
+ - [ ] 用户新信息?→ 立即 `memory create`
255
+ - [ ] 引用记忆?→ 标注来源 URI
256
+
257
+ **全部通过 → 输出 | 任一未通过 → 先执行 CLI**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hippo-memo/cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "hippo": "./dist/index.js"
@@ -14,15 +14,9 @@
14
14
  "dev": "concurrently -n 'web,cli' -c 'blue,green' 'bun run dev:web' 'bun run dev:cli'",
15
15
  "start": "bun dist/index.js"
16
16
  },
17
- "dependencies": {
18
- "@hono/node-server": "^1.14.1",
19
- "@hippo-memo/core": "1.0.0",
20
- "@hippo-memo/shared": "1.0.0",
21
- "commander": "^12.1.0",
22
- "hono": "^4.7.11"
23
- },
24
17
  "devDependencies": {
25
18
  "@types/node": "^20",
19
+ "@types/prompts": "^2.4.9",
26
20
  "concurrently": "^9.2.1",
27
21
  "typescript": "^5"
28
22
  }
@@ -1,2 +1,3 @@
1
1
  export * from "./init";
2
+ export * from "./memory";
2
3
  export * from "./web";
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  access,
3
3
  constants,
4
+ cp,
4
5
  mkdir,
5
6
  readFile,
6
7
  writeFile
@@ -9,10 +10,12 @@ import { EOL } from "node:os";
9
10
  import { join } from "node:path";
10
11
  import { createConfig, createStore } from "@hippo-memo/core";
11
12
  import { MEMORY_DIR_NAME } from "@hippo-memo/shared";
13
+ import prompts from "prompts";
12
14
 
13
15
  const __dirname = import.meta.dirname;
14
16
  const TEMPLATE_DIR = join(__dirname, "../../template/system");
15
17
  const AGENTS_TEMPLATE_PATH = join(__dirname, "../../template/mcp/AGENTS.md");
18
+ const SKILL_TEMPLATE_DIR = join(__dirname, "../../template/skill");
16
19
 
17
20
  const TEMPLATE_URIS = {
18
21
  agent: "system://agent",
@@ -20,6 +23,8 @@ const TEMPLATE_URIS = {
20
23
  user: "system://user"
21
24
  };
22
25
 
26
+ type InitMode = "mcp" | "skill";
27
+
23
28
  async function readTemplateContent(filename: string): Promise<string> {
24
29
  return readFile(join(TEMPLATE_DIR, filename), "utf-8");
25
30
  }
@@ -38,15 +43,12 @@ function getInjectionMarker(content: string): string {
38
43
  */
39
44
  function isAlreadyInjected(content: string, marker: string): boolean {
40
45
  const lines = content.split("\n");
41
- // 跳过开头的空行
42
46
  const firstNonEmptyLine = lines.find((line) => line.trim() !== "");
43
47
  return firstNonEmptyLine?.trim() === marker;
44
48
  }
45
49
 
46
50
  /**
47
51
  * 安全地将内容注入到文件顶部
48
- * @param filePath 目标文件路径
49
- * @param contentToInject 要注入的内容
50
52
  */
51
53
  async function injectToTopOfFile(
52
54
  filePath: string,
@@ -61,10 +63,8 @@ async function injectToTopOfFile(
61
63
  originalContent = "";
62
64
  }
63
65
 
64
- // 从注入内容中提取第一行作为标识符
65
66
  const injectionMarker = getInjectionMarker(contentToInject);
66
67
 
67
- // 幂等性检查:通过第一行判断是否已注入
68
68
  if (
69
69
  originalContent &&
70
70
  isAlreadyInjected(originalContent, injectionMarker)
@@ -73,7 +73,6 @@ async function injectToTopOfFile(
73
73
  return;
74
74
  }
75
75
 
76
- // 拼接内容,注意处理换行符
77
76
  const separator = originalContent ? `${EOL}${EOL}` : "";
78
77
  const updatedContent = `${contentToInject}${separator}${originalContent}`;
79
78
 
@@ -109,24 +108,138 @@ async function handleAgentsAndClaudeFiles(directory: string): Promise<void> {
109
108
  // CLAUDE.md 不存在
110
109
  }
111
110
 
112
- // 读取 AGENTS.md 模板内容
113
111
  const agentsContent = await readFile(AGENTS_TEMPLATE_PATH, "utf-8");
114
112
 
115
113
  if (hasClaude) {
116
- // CLAUDE.md 存在:优先注入到 CLAUDE.md 顶部
117
114
  console.log("› Found: CLAUDE.md");
118
- await injectToTopOfFile(claudePath, agentsContent);
115
+
116
+ // 检查是否已注入
117
+ const existingContent = await readFile(claudePath, "utf-8");
118
+ const injectionMarker = getInjectionMarker(agentsContent);
119
+
120
+ if (!isAlreadyInjected(existingContent, injectionMarker)) {
121
+ const response = await prompts({
122
+ type: "confirm",
123
+ name: "inject",
124
+ message: "Inject MCP configuration to CLAUDE.md?",
125
+ initial: true
126
+ });
127
+
128
+ if (response.inject) {
129
+ await injectToTopOfFile(claudePath, agentsContent);
130
+ } else {
131
+ console.log("› Skipped: CLAUDE.md");
132
+ }
133
+ } else {
134
+ console.log("› Skipped: content already exists");
135
+ }
119
136
  } else if (hasAgents) {
120
- // 只有 AGENTS.md 存在:注入到 AGENTS.md 顶部
121
137
  console.log("› Found: AGENTS.md");
122
- await injectToTopOfFile(agentsPath, agentsContent);
138
+
139
+ const existingContent = await readFile(agentsPath, "utf-8");
140
+ const injectionMarker = getInjectionMarker(agentsContent);
141
+
142
+ if (!isAlreadyInjected(existingContent, injectionMarker)) {
143
+ const response = await prompts({
144
+ type: "confirm",
145
+ name: "inject",
146
+ message: "Inject MCP configuration to AGENTS.md?",
147
+ initial: true
148
+ });
149
+
150
+ if (response.inject) {
151
+ await injectToTopOfFile(agentsPath, agentsContent);
152
+ } else {
153
+ console.log("› Skipped: AGENTS.md");
154
+ }
155
+ } else {
156
+ console.log("› Skipped: content already exists");
157
+ }
123
158
  } else {
124
- // 两者都不存在:创建 AGENTS.md
125
159
  console.log("✔ Created: AGENTS.md");
126
160
  await writeFile(agentsPath, agentsContent, "utf-8");
127
161
  }
128
162
  }
129
163
 
164
+ /**
165
+ * 复制 Skill 模板到项目目录
166
+ */
167
+ async function copySkillTemplates(directory: string): Promise<void> {
168
+ const targetSkillDir = join(directory, ".claude", "skills");
169
+
170
+ try {
171
+ await mkdir(targetSkillDir, { recursive: true });
172
+ } catch {
173
+ // 目录可能已存在
174
+ }
175
+
176
+ const skills = ["hippo-memory"];
177
+
178
+ for (const skill of skills) {
179
+ const sourceDir = join(SKILL_TEMPLATE_DIR, skill);
180
+ const targetDir = join(targetSkillDir, skill);
181
+ const targetFile = join(targetDir, "SKILL.md");
182
+
183
+ try {
184
+ await mkdir(targetDir, { recursive: true });
185
+
186
+ // 检查文件是否已存在
187
+ let fileExists = false;
188
+ try {
189
+ await access(targetFile, constants.F_OK);
190
+ fileExists = true;
191
+ } catch {
192
+ // 文件不存在
193
+ }
194
+
195
+ if (fileExists) {
196
+ const response = await prompts({
197
+ type: "confirm",
198
+ name: "overwrite",
199
+ message: `Skill ${skill} already exists. Overwrite?`,
200
+ initial: false
201
+ });
202
+
203
+ if (!response.overwrite) {
204
+ console.log(`› Skipped: ${skill}`);
205
+ continue;
206
+ }
207
+ }
208
+
209
+ await cp(join(sourceDir, "SKILL.md"), targetFile);
210
+ console.log(`✔ Installed skill: ${skill}`);
211
+ } catch (error: unknown) {
212
+ console.error(`✖ Failed to install skill: ${skill}`, error);
213
+ }
214
+ }
215
+ }
216
+
217
+ /**
218
+ * 询问用户初始化模式
219
+ */
220
+ async function askInitMode(): Promise<InitMode> {
221
+ const response = await prompts({
222
+ type: "select",
223
+ name: "mode",
224
+ message: "选择 Claude Code 集成方式:",
225
+ choices: [
226
+ {
227
+ title: "🔌 MCP 模式 (推荐全局配置)",
228
+ description: "适用于跨项目共享记忆,需要单独配置 MCP 服务器",
229
+ value: "mcp"
230
+ },
231
+ {
232
+ title: "📜 Skill 模式 (推荐项目配置)",
233
+ description: "项目级技能,可提交到 Git 与团队共享",
234
+ value: "skill"
235
+ }
236
+ ],
237
+ initial: 1
238
+ });
239
+
240
+ return (response.mode as InitMode) || "skill";
241
+ }
242
+
130
243
  export async function init(directory: string): Promise<void> {
131
244
  const memoryRoot = join(directory, MEMORY_DIR_NAME);
132
245
 
@@ -134,7 +247,6 @@ export async function init(directory: string): Promise<void> {
134
247
  await access(memoryRoot, constants.F_OK);
135
248
  console.log(`Memory directory already exists at ${memoryRoot}`);
136
249
  } catch {
137
- // Directory doesn't exist, proceed with creation
138
250
  await mkdir(memoryRoot, { recursive: true });
139
251
 
140
252
  const store = createStore({
@@ -149,10 +261,32 @@ export async function init(directory: string): Promise<void> {
149
261
  })
150
262
  );
151
263
 
152
- console.log(`Initialized hippo memory at ${memoryRoot}`);
264
+ console.log(`✔ Initialized hippo memory at ${memoryRoot}`);
153
265
  }
154
266
 
155
- // 处理 AGENTS.md 和 CLAUDE.md 文件
156
- console.log("◇ Checking AGENTS.md and CLAUDE.md...");
157
- await handleAgentsAndClaudeFiles(directory);
267
+ // 询问初始化模式
268
+ const mode = await askInitMode();
269
+
270
+ // 处理 MCP 配置 (注入到 AGENTS.md/CLAUDE.md)
271
+ if (mode === "mcp") {
272
+ console.log(`${EOL}◇ Setting up MCP integration...`);
273
+ await handleAgentsAndClaudeFiles(directory);
274
+ console.log(`${EOL}✨ Setup complete!${EOL}`);
275
+ console.log("📌 Next steps:");
276
+ console.log(" 1. Configure MCP server in Claude Code settings");
277
+ console.log(" 2. Run: hippo web -p 3000");
278
+ console.log("");
279
+ }
280
+
281
+ // 处理 Skill 配置
282
+ if (mode === "skill") {
283
+ console.log(`${EOL}◇ Installing Claude Code Skills...`);
284
+ await copySkillTemplates(directory);
285
+ console.log(`${EOL}✨ Setup complete!${EOL}`);
286
+ console.log("📌 Next steps:");
287
+ console.log(" 1. Skills installed in .claude/skills/");
288
+ console.log(" 2. LLM will automatically trigger when needed");
289
+ console.log(" 3. Commit to Git to share with your team");
290
+ console.log("");
291
+ }
158
292
  }