@hippo-memo/cli 1.0.1 → 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.
- package/dist/index.js +174 -66
- package/dist/template/README.md +42 -2
- package/dist/template/skill/hippo-memory/SKILL.md +257 -0
- package/package.json +2 -1
- package/src/commands/index.ts +1 -0
- package/src/commands/init.ts +151 -17
- package/src/commands/memory/actions.ts +268 -0
- package/src/commands/memory/index.ts +64 -0
- package/src/index.ts +3 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { createConfig, createStore } from "@hippo-memo/core";
|
|
3
|
+
|
|
4
|
+
interface BaseOptions {
|
|
5
|
+
dir?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface CreateOptions extends BaseOptions {
|
|
9
|
+
priority?: string;
|
|
10
|
+
disclosure?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface SearchOptions extends BaseOptions {
|
|
14
|
+
domain?: string;
|
|
15
|
+
limit?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface DeleteOptions extends BaseOptions {
|
|
19
|
+
recursive?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeNewlines(content: string): string {
|
|
23
|
+
return content.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getStore(directory: string | undefined) {
|
|
27
|
+
const rootPath = directory || process.cwd();
|
|
28
|
+
if (!existsSync(rootPath)) {
|
|
29
|
+
throw new Error(`目录不存在: ${rootPath}`);
|
|
30
|
+
}
|
|
31
|
+
return createStore({
|
|
32
|
+
type: "filesystem",
|
|
33
|
+
config: createConfig(rootPath)
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function create(
|
|
38
|
+
uri: string,
|
|
39
|
+
content: string,
|
|
40
|
+
options: CreateOptions
|
|
41
|
+
) {
|
|
42
|
+
try {
|
|
43
|
+
const store = getStore(options.dir);
|
|
44
|
+
const priority = options.priority
|
|
45
|
+
? Number.parseInt(options.priority, 10)
|
|
46
|
+
: 5;
|
|
47
|
+
await store.createMemory({
|
|
48
|
+
uri,
|
|
49
|
+
content: normalizeNewlines(content),
|
|
50
|
+
priority,
|
|
51
|
+
disclosure: options.disclosure
|
|
52
|
+
});
|
|
53
|
+
console.log("OK");
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function read(uri: string, options: BaseOptions) {
|
|
61
|
+
try {
|
|
62
|
+
const store = getStore(options.dir);
|
|
63
|
+
const memory = await store.getMemory(uri);
|
|
64
|
+
if (!memory) {
|
|
65
|
+
console.error("Memory not found");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
if (!memory.content || memory.content.trim() === "") {
|
|
69
|
+
console.log("No Content");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
console.log(memory.content);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function update(
|
|
80
|
+
uri: string,
|
|
81
|
+
content: string,
|
|
82
|
+
options: CreateOptions
|
|
83
|
+
) {
|
|
84
|
+
try {
|
|
85
|
+
const store = getStore(options.dir);
|
|
86
|
+
const updateData: {
|
|
87
|
+
uri: string;
|
|
88
|
+
content: string;
|
|
89
|
+
priority?: number;
|
|
90
|
+
disclosure?: string;
|
|
91
|
+
} = {
|
|
92
|
+
uri,
|
|
93
|
+
content: normalizeNewlines(content)
|
|
94
|
+
};
|
|
95
|
+
if (options.priority) {
|
|
96
|
+
updateData.priority = Number.parseInt(options.priority, 10);
|
|
97
|
+
}
|
|
98
|
+
if (options.disclosure) {
|
|
99
|
+
updateData.disclosure = options.disclosure;
|
|
100
|
+
}
|
|
101
|
+
await store.updateMemory(updateData);
|
|
102
|
+
console.log("OK");
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function remove(uri: string, options: DeleteOptions) {
|
|
110
|
+
try {
|
|
111
|
+
const store = getStore(options.dir);
|
|
112
|
+
await store.deleteMemory(uri, {
|
|
113
|
+
recursive: options.recursive
|
|
114
|
+
});
|
|
115
|
+
console.log("OK");
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function search(query: string, options: SearchOptions) {
|
|
123
|
+
try {
|
|
124
|
+
const store = getStore(options.dir);
|
|
125
|
+
const limit = options.limit ? Number.parseInt(options.limit, 10) : 10;
|
|
126
|
+
const results = await store.searchMemory(query, {
|
|
127
|
+
domain: options.domain,
|
|
128
|
+
limit
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
if (results.length === 0) {
|
|
132
|
+
console.log(`No memories found matching "${query}"`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const truncateContent = (
|
|
137
|
+
content: string,
|
|
138
|
+
keyword: string,
|
|
139
|
+
maxLen = 50
|
|
140
|
+
): string => {
|
|
141
|
+
if (content.length <= maxLen) return content;
|
|
142
|
+
|
|
143
|
+
const lowerContent = content.toLowerCase();
|
|
144
|
+
const lowerKeyword = keyword.toLowerCase();
|
|
145
|
+
const index = lowerContent.indexOf(lowerKeyword);
|
|
146
|
+
if (index === -1) {
|
|
147
|
+
return `${content.slice(0, maxLen - 3)}...`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const keywordLen = keyword.length;
|
|
151
|
+
let start = index - Math.floor((maxLen - keywordLen) / 2);
|
|
152
|
+
let end = index + keywordLen + Math.ceil((maxLen - keywordLen) / 2);
|
|
153
|
+
|
|
154
|
+
if (start < 0) {
|
|
155
|
+
end += -start;
|
|
156
|
+
start = 0;
|
|
157
|
+
}
|
|
158
|
+
if (end > content.length) {
|
|
159
|
+
start -= end - content.length;
|
|
160
|
+
end = content.length;
|
|
161
|
+
}
|
|
162
|
+
start = Math.max(0, start);
|
|
163
|
+
|
|
164
|
+
let result = content.slice(start, end);
|
|
165
|
+
if (start > 0) result = `...${result}`;
|
|
166
|
+
if (end < content.length) result = `${result}...`;
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const summary = results
|
|
172
|
+
.map((m) => {
|
|
173
|
+
const lines = [
|
|
174
|
+
`- ${m.uri} [★${m.priority}]`,
|
|
175
|
+
` content: ${truncateContent(m.content, query)}`
|
|
176
|
+
];
|
|
177
|
+
if (m.disclosure) {
|
|
178
|
+
lines.splice(1, 0, ` disclosure: ${m.disclosure}`);
|
|
179
|
+
}
|
|
180
|
+
return lines.join("\n");
|
|
181
|
+
})
|
|
182
|
+
.join("\n\n");
|
|
183
|
+
|
|
184
|
+
console.log(`Found ${results.length} memories:\n\n${summary}`);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export async function list(options: SearchOptions) {
|
|
192
|
+
try {
|
|
193
|
+
const store = getStore(options.dir);
|
|
194
|
+
const limit = options.limit
|
|
195
|
+
? Number.parseInt(options.limit, 10)
|
|
196
|
+
: undefined;
|
|
197
|
+
const memories = await store.getAllMemories({
|
|
198
|
+
domain: options.domain
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const limitedMemories = limit ? memories.slice(0, limit) : memories;
|
|
202
|
+
|
|
203
|
+
if (limitedMemories.length === 0) {
|
|
204
|
+
console.log("No memories found.");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const truncateContent = (content: string, maxLen = 50): string => {
|
|
209
|
+
if (content.length <= maxLen) return content;
|
|
210
|
+
return `${content.slice(0, maxLen - 3)}...`;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const summary = limitedMemories
|
|
214
|
+
.map((m) => {
|
|
215
|
+
const lines = [
|
|
216
|
+
`- ${m.uri} [★${m.priority}]`,
|
|
217
|
+
` content: ${truncateContent(m.content)}`
|
|
218
|
+
];
|
|
219
|
+
if (m.disclosure) {
|
|
220
|
+
lines.splice(1, 0, ` disclosure: ${m.disclosure}`);
|
|
221
|
+
}
|
|
222
|
+
return lines.join("\n");
|
|
223
|
+
})
|
|
224
|
+
.join("\n\n");
|
|
225
|
+
|
|
226
|
+
const totalCount = memories.length;
|
|
227
|
+
const displayCount = limitedMemories.length;
|
|
228
|
+
const countText =
|
|
229
|
+
limit && totalCount > limit
|
|
230
|
+
? `Showing ${displayCount} of ${totalCount} memories`
|
|
231
|
+
: `Total: ${totalCount} memories`;
|
|
232
|
+
|
|
233
|
+
console.log(`${countText}\n\n${summary}`);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export async function alias(
|
|
241
|
+
newUri: string,
|
|
242
|
+
targetUri: string,
|
|
243
|
+
options: CreateOptions
|
|
244
|
+
) {
|
|
245
|
+
try {
|
|
246
|
+
const store = getStore(options.dir);
|
|
247
|
+
const aliasData: {
|
|
248
|
+
newUri: string;
|
|
249
|
+
targetUri: string;
|
|
250
|
+
priority?: number;
|
|
251
|
+
disclosure?: string;
|
|
252
|
+
} = {
|
|
253
|
+
newUri,
|
|
254
|
+
targetUri
|
|
255
|
+
};
|
|
256
|
+
if (options.priority) {
|
|
257
|
+
aliasData.priority = Number.parseInt(options.priority, 10);
|
|
258
|
+
}
|
|
259
|
+
if (options.disclosure) {
|
|
260
|
+
aliasData.disclosure = options.disclosure;
|
|
261
|
+
}
|
|
262
|
+
await store.addAlias(aliasData);
|
|
263
|
+
console.log("OK");
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { alias, create, list, read, remove, search, update } from "./actions";
|
|
3
|
+
|
|
4
|
+
export const memoryCmd = new Command("memory").description(
|
|
5
|
+
"Memory 管理操作 (无需 init,直接使用当前目录)"
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
memoryCmd
|
|
9
|
+
.command("create <uri> <content>")
|
|
10
|
+
.description("创建新记忆")
|
|
11
|
+
.option("-p, --priority <number>", "优先级 (1-10)", "5")
|
|
12
|
+
.option("-d, --disclosure <string>", "触发条件")
|
|
13
|
+
.option("--dir <directory>", "项目根目录 (默认: .)", ".")
|
|
14
|
+
.action(create);
|
|
15
|
+
|
|
16
|
+
memoryCmd
|
|
17
|
+
.command("read <uri>")
|
|
18
|
+
.description("读取指定记忆")
|
|
19
|
+
.option("--dir <directory>", "项目根目录 (默认: .)", ".")
|
|
20
|
+
.action(read);
|
|
21
|
+
|
|
22
|
+
memoryCmd
|
|
23
|
+
.command("update <uri> <content>")
|
|
24
|
+
.description("更新记忆内容")
|
|
25
|
+
.option("-p, --priority <number>", "优先级 (1-10)")
|
|
26
|
+
.option("-d, --disclosure <string>", "触发条件")
|
|
27
|
+
.option("--dir <directory>", "项目根目录 (默认: .)", ".")
|
|
28
|
+
.action(update);
|
|
29
|
+
|
|
30
|
+
memoryCmd
|
|
31
|
+
.command("delete <uri>")
|
|
32
|
+
.description("删除记忆")
|
|
33
|
+
.option("-r, --recursive", "递归删除子记忆")
|
|
34
|
+
.option("--permanent", "永久删除(不进入回收站)")
|
|
35
|
+
.option("--dir <directory>", "项目根目录 (默认: .)", ".")
|
|
36
|
+
.action(remove);
|
|
37
|
+
|
|
38
|
+
memoryCmd
|
|
39
|
+
.command("search <query>")
|
|
40
|
+
.description("搜索记忆")
|
|
41
|
+
.option("--domain <string>", "限定域名")
|
|
42
|
+
.option("-l, --limit <number>", "结果数量", "10")
|
|
43
|
+
.option("--dir <directory>", "项目根目录 (默认: .)", ".")
|
|
44
|
+
.action(search);
|
|
45
|
+
|
|
46
|
+
memoryCmd
|
|
47
|
+
.command("alias <newUri> <targetUri>")
|
|
48
|
+
.description("为已有记忆添加别名")
|
|
49
|
+
.option("-p, --priority <number>", "别名优先级 (继承目标默认)")
|
|
50
|
+
.option("-d, --disclosure <string>", "别名触发条件")
|
|
51
|
+
.option("--dir <directory>", "项目根目录 (默认: .)", ".")
|
|
52
|
+
.action(alias);
|
|
53
|
+
|
|
54
|
+
memoryCmd
|
|
55
|
+
.command("list")
|
|
56
|
+
.description("列出所有记忆")
|
|
57
|
+
.option("--domain <string>", "限定域名")
|
|
58
|
+
.option("-l, --limit <number>", "结果数量")
|
|
59
|
+
.option("--dir <directory>", "项目根目录 (默认: .)", ".")
|
|
60
|
+
.action(async (options) => {
|
|
61
|
+
await list(options);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export default memoryCmd;
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { init, web } from "./commands";
|
|
4
|
+
import memoryCmd from "./commands/memory";
|
|
4
5
|
|
|
5
6
|
const program = new Command();
|
|
6
7
|
|
|
@@ -30,4 +31,6 @@ program
|
|
|
30
31
|
await web(directory, port, storeType);
|
|
31
32
|
});
|
|
32
33
|
|
|
34
|
+
program.addCommand(memoryCmd);
|
|
35
|
+
|
|
33
36
|
program.parse();
|