@lanonasis/recall-forge 1.1.1
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/.claw/skills/SKILL.md +347 -0
- package/CHANGELOG.md +162 -0
- package/LICENSE +21 -0
- package/README.md +302 -0
- package/SETUP.md +190 -0
- package/dist/cli-common.d.ts +25 -0
- package/dist/cli-common.js +338 -0
- package/dist/cli-memory.d.ts +6 -0
- package/dist/cli-memory.js +146 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +135 -0
- package/dist/client.d.ts +116 -0
- package/dist/client.js +643 -0
- package/dist/config.d.ts +41 -0
- package/dist/config.js +125 -0
- package/dist/enrichment/capture-filter.d.ts +4 -0
- package/dist/enrichment/capture-filter.js +44 -0
- package/dist/enrichment/prompt-safety.d.ts +13 -0
- package/dist/enrichment/prompt-safety.js +83 -0
- package/dist/enrichment/tag-extractor.d.ts +1 -0
- package/dist/enrichment/tag-extractor.js +47 -0
- package/dist/enrichment/type-detector.d.ts +2 -0
- package/dist/enrichment/type-detector.js +95 -0
- package/dist/extraction/cli-extract.d.ts +8 -0
- package/dist/extraction/cli-extract.js +66 -0
- package/dist/extraction/format-adapters.d.ts +8 -0
- package/dist/extraction/format-adapters.js +268 -0
- package/dist/extraction/index.d.ts +7 -0
- package/dist/extraction/index.js +7 -0
- package/dist/extraction/jsonl-extractor.d.ts +32 -0
- package/dist/extraction/jsonl-extractor.js +207 -0
- package/dist/extraction/markdown-extractor.d.ts +23 -0
- package/dist/extraction/markdown-extractor.js +228 -0
- package/dist/extraction/secret-redactor.d.ts +7 -0
- package/dist/extraction/secret-redactor.js +112 -0
- package/dist/extraction/sqlite-extractor.d.ts +15 -0
- package/dist/extraction/sqlite-extractor.js +245 -0
- package/dist/extraction/types.d.ts +50 -0
- package/dist/extraction/types.js +1 -0
- package/dist/hooks/capture.d.ts +23 -0
- package/dist/hooks/capture.js +162 -0
- package/dist/hooks/context-engine.d.ts +4 -0
- package/dist/hooks/context-engine.js +54 -0
- package/dist/hooks/local-fallback.d.ts +5 -0
- package/dist/hooks/local-fallback.js +31 -0
- package/dist/hooks/recall.d.ts +21 -0
- package/dist/hooks/recall.js +123 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +103 -0
- package/dist/plugin-sdk-stub.d.ts +53 -0
- package/dist/plugin-sdk-stub.js +3 -0
- package/dist/privacy/privacy-guard.d.ts +33 -0
- package/dist/privacy/privacy-guard.js +130 -0
- package/dist/privacy/privacy-log.d.ts +6 -0
- package/dist/privacy/privacy-log.js +44 -0
- package/dist/tools/memory-forget.d.ts +3 -0
- package/dist/tools/memory-forget.js +109 -0
- package/dist/tools/memory-get.d.ts +3 -0
- package/dist/tools/memory-get.js +46 -0
- package/dist/tools/memory-search.d.ts +4 -0
- package/dist/tools/memory-search.js +95 -0
- package/dist/tools/memory-store.d.ts +5 -0
- package/dist/tools/memory-store.js +199 -0
- package/openclaw.plugin.json +315 -0
- package/package.json +90 -0
- package/setup/agents-memory.md +63 -0
- package/setup/heartbeat-memory.md +53 -0
- package/setup/install.sh +179 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
export const MEMORY_TYPES = [
|
|
3
|
+
"context",
|
|
4
|
+
"project",
|
|
5
|
+
"knowledge",
|
|
6
|
+
"reference",
|
|
7
|
+
"personal",
|
|
8
|
+
"workflow",
|
|
9
|
+
];
|
|
10
|
+
const SORT_FIELD_MAP = {
|
|
11
|
+
created_at: "created_at",
|
|
12
|
+
updated_at: "updated_at",
|
|
13
|
+
title: "title",
|
|
14
|
+
type: "memory_type",
|
|
15
|
+
};
|
|
16
|
+
function errorMessage(error) {
|
|
17
|
+
if (error instanceof Error && error.message.trim()) {
|
|
18
|
+
return error.message;
|
|
19
|
+
}
|
|
20
|
+
return typeof error === "string" && error.trim() ? error : "unknown error";
|
|
21
|
+
}
|
|
22
|
+
export function exitWithError(error) {
|
|
23
|
+
console.error(`Error: ${errorMessage(error)}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
export function isNotFoundError(error) {
|
|
27
|
+
const message = errorMessage(error);
|
|
28
|
+
return message.includes("(404)") || /\b404\b/.test(message);
|
|
29
|
+
}
|
|
30
|
+
export function normalizeTextInput(label, value) {
|
|
31
|
+
if (value === undefined)
|
|
32
|
+
return undefined;
|
|
33
|
+
const normalized = value.trim();
|
|
34
|
+
if (!normalized) {
|
|
35
|
+
throw new Error(`${label} cannot be empty or whitespace-only.`);
|
|
36
|
+
}
|
|
37
|
+
return normalized;
|
|
38
|
+
}
|
|
39
|
+
export async function resolveContentInput(options) {
|
|
40
|
+
const { content, contentFile, required = false } = options;
|
|
41
|
+
if (content !== undefined && contentFile !== undefined) {
|
|
42
|
+
throw new Error("Provide either --content or --content-file, not both.");
|
|
43
|
+
}
|
|
44
|
+
if (contentFile !== undefined) {
|
|
45
|
+
try {
|
|
46
|
+
const fileContent = await readFile(contentFile, "utf8");
|
|
47
|
+
return normalizeTextInput("Content", fileContent);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw new Error(`Unable to read content file "${contentFile}": ${errorMessage(error)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const normalized = normalizeTextInput("Content", content);
|
|
54
|
+
if (required && normalized === undefined) {
|
|
55
|
+
throw new Error("Provide one of --content or --content-file.");
|
|
56
|
+
}
|
|
57
|
+
return normalized;
|
|
58
|
+
}
|
|
59
|
+
export function parseTags(input) {
|
|
60
|
+
if (input === undefined)
|
|
61
|
+
return undefined;
|
|
62
|
+
const tags = [...new Set(input.split(",").map((tag) => tag.trim()).filter(Boolean))];
|
|
63
|
+
if (tags.length === 0) {
|
|
64
|
+
throw new Error("`--tags` must include at least one non-empty tag.");
|
|
65
|
+
}
|
|
66
|
+
return tags;
|
|
67
|
+
}
|
|
68
|
+
export function parsePositiveInt(flag, raw, defaultValue) {
|
|
69
|
+
if (raw === undefined)
|
|
70
|
+
return defaultValue;
|
|
71
|
+
const value = Number(raw);
|
|
72
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
73
|
+
throw new Error(`${flag} must be a positive integer.`);
|
|
74
|
+
}
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
export function parseThreshold(raw) {
|
|
78
|
+
if (raw === undefined)
|
|
79
|
+
return undefined;
|
|
80
|
+
const value = Number(raw);
|
|
81
|
+
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
82
|
+
throw new Error("--threshold must be a number between 0 and 1.");
|
|
83
|
+
}
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
export function parseMemoryType(raw, flag = "--type") {
|
|
87
|
+
if (raw === undefined)
|
|
88
|
+
return undefined;
|
|
89
|
+
const normalized = raw.trim().toLowerCase();
|
|
90
|
+
if (!normalized) {
|
|
91
|
+
throw new Error(`${flag} cannot be empty or whitespace-only.`);
|
|
92
|
+
}
|
|
93
|
+
if (!MEMORY_TYPES.includes(normalized)) {
|
|
94
|
+
throw new Error(`${flag} must be one of: ${MEMORY_TYPES.join(", ")}.`);
|
|
95
|
+
}
|
|
96
|
+
return normalized;
|
|
97
|
+
}
|
|
98
|
+
export function parseSortField(raw) {
|
|
99
|
+
if (raw === undefined)
|
|
100
|
+
return undefined;
|
|
101
|
+
const normalized = raw.trim().toLowerCase();
|
|
102
|
+
if (!normalized) {
|
|
103
|
+
throw new Error("--sort cannot be empty or whitespace-only.");
|
|
104
|
+
}
|
|
105
|
+
const sortField = SORT_FIELD_MAP[normalized];
|
|
106
|
+
if (!sortField) {
|
|
107
|
+
throw new Error("--sort must be one of: created_at, updated_at, title, type.");
|
|
108
|
+
}
|
|
109
|
+
return sortField;
|
|
110
|
+
}
|
|
111
|
+
export function parseSortOrder(raw) {
|
|
112
|
+
if (raw === undefined)
|
|
113
|
+
return undefined;
|
|
114
|
+
const normalized = raw.trim().toLowerCase();
|
|
115
|
+
if (normalized !== "asc" && normalized !== "desc") {
|
|
116
|
+
throw new Error("--order must be either asc or desc.");
|
|
117
|
+
}
|
|
118
|
+
return normalized;
|
|
119
|
+
}
|
|
120
|
+
export function requireMemoryId(id) {
|
|
121
|
+
const normalized = normalizeTextInput("Memory ID", id);
|
|
122
|
+
if (!normalized) {
|
|
123
|
+
throw new Error("Memory ID is required.");
|
|
124
|
+
}
|
|
125
|
+
return normalized;
|
|
126
|
+
}
|
|
127
|
+
export function requireUpdateFields(updates, message = "Provide at least one field to update: --title, --content, --content-file, --type, or --tags.") {
|
|
128
|
+
if (Object.keys(updates).length === 0) {
|
|
129
|
+
throw new Error(message);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export function formatPreview(content, maxLength = 140) {
|
|
133
|
+
const normalized = content.replace(/\s+/g, " ").trim();
|
|
134
|
+
if (!normalized)
|
|
135
|
+
return "";
|
|
136
|
+
if (normalized.length <= maxLength)
|
|
137
|
+
return normalized;
|
|
138
|
+
return `${normalized.slice(0, maxLength - 3).trimEnd()}...`;
|
|
139
|
+
}
|
|
140
|
+
export function memoryTypeOf(memory) {
|
|
141
|
+
return memory.memory_type ?? memory.type ?? "unknown";
|
|
142
|
+
}
|
|
143
|
+
function formatTags(tags) {
|
|
144
|
+
return tags && tags.length > 0 ? tags.join(", ") : "(none)";
|
|
145
|
+
}
|
|
146
|
+
export function similarityOf(memory) {
|
|
147
|
+
return memory.similarity ?? memory.similarity_score;
|
|
148
|
+
}
|
|
149
|
+
export function printMemorySummary(memory, heading) {
|
|
150
|
+
const lines = [
|
|
151
|
+
`${heading}:`,
|
|
152
|
+
` ID: ${memory.id}`,
|
|
153
|
+
` Title: ${memory.title}`,
|
|
154
|
+
` Type: ${memoryTypeOf(memory)}`,
|
|
155
|
+
` Tags: ${formatTags(memory.tags)}`,
|
|
156
|
+
];
|
|
157
|
+
if (memory.created_at)
|
|
158
|
+
lines.push(` Created: ${memory.created_at}`);
|
|
159
|
+
if (memory.updated_at)
|
|
160
|
+
lines.push(` Updated: ${memory.updated_at}`);
|
|
161
|
+
const preview = formatPreview(memory.content);
|
|
162
|
+
if (preview)
|
|
163
|
+
lines.push(` Preview: ${preview}`);
|
|
164
|
+
console.log(lines.join("\n"));
|
|
165
|
+
}
|
|
166
|
+
export function printMemoryDetail(memory) {
|
|
167
|
+
const lines = [
|
|
168
|
+
`ID: ${memory.id}`,
|
|
169
|
+
`Title: ${memory.title}`,
|
|
170
|
+
`Type: ${memoryTypeOf(memory)}`,
|
|
171
|
+
`Tags: ${formatTags(memory.tags)}`,
|
|
172
|
+
`Created: ${memory.created_at ?? "(unknown)"}`,
|
|
173
|
+
`Updated: ${memory.updated_at ?? "(unknown)"}`,
|
|
174
|
+
"",
|
|
175
|
+
"Content:",
|
|
176
|
+
memory.content,
|
|
177
|
+
"",
|
|
178
|
+
"Metadata:",
|
|
179
|
+
JSON.stringify(memory.metadata ?? {}, null, 2),
|
|
180
|
+
];
|
|
181
|
+
console.log(lines.join("\n"));
|
|
182
|
+
}
|
|
183
|
+
export function assertMemoryStatsShape(stats) {
|
|
184
|
+
if (!stats || typeof stats !== "object") {
|
|
185
|
+
throw new Error("Stats endpoint returned an invalid response. Expected an object.");
|
|
186
|
+
}
|
|
187
|
+
const typedStats = stats;
|
|
188
|
+
if (typeof typedStats.total_memories !== "number") {
|
|
189
|
+
throw new Error("Stats endpoint returned an invalid response. Missing numeric total_memories.");
|
|
190
|
+
}
|
|
191
|
+
const byType = typedStats.memories_by_type ?? typedStats.by_type;
|
|
192
|
+
if (!byType || typeof byType !== "object" || Array.isArray(byType)) {
|
|
193
|
+
throw new Error("Stats endpoint returned an invalid response. Missing object memories_by_type or by_type.");
|
|
194
|
+
}
|
|
195
|
+
for (const [key, value] of Object.entries(byType)) {
|
|
196
|
+
if (typeof value !== "number") {
|
|
197
|
+
throw new Error(`Stats endpoint returned an invalid response. by_type.${key} must be numeric.`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (typedStats.with_embeddings !== undefined &&
|
|
201
|
+
typeof typedStats.with_embeddings !== "number") {
|
|
202
|
+
throw new Error("Stats endpoint returned an invalid response. with_embeddings must be numeric.");
|
|
203
|
+
}
|
|
204
|
+
if (typedStats.without_embeddings !== undefined &&
|
|
205
|
+
typeof typedStats.without_embeddings !== "number") {
|
|
206
|
+
throw new Error("Stats endpoint returned an invalid response. without_embeddings must be numeric.");
|
|
207
|
+
}
|
|
208
|
+
if (typedStats.recent_activity !== undefined) {
|
|
209
|
+
if (!typedStats.recent_activity ||
|
|
210
|
+
typeof typedStats.recent_activity !== "object" ||
|
|
211
|
+
Array.isArray(typedStats.recent_activity)) {
|
|
212
|
+
throw new Error("Stats endpoint returned an invalid response. recent_activity must be an object.");
|
|
213
|
+
}
|
|
214
|
+
for (const [key, value] of Object.entries(typedStats.recent_activity)) {
|
|
215
|
+
if (typeof value !== "number") {
|
|
216
|
+
throw new Error(`Stats endpoint returned an invalid response. recent_activity.${key} must be numeric.`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (typedStats.top_tags !== undefined) {
|
|
221
|
+
if (!Array.isArray(typedStats.top_tags) ||
|
|
222
|
+
typedStats.top_tags.some((entry) => {
|
|
223
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
const typedEntry = entry;
|
|
227
|
+
return (typeof typedEntry.tag !== "string" ||
|
|
228
|
+
typeof typedEntry.count !== "number");
|
|
229
|
+
})) {
|
|
230
|
+
throw new Error("Stats endpoint returned an invalid response. top_tags must be an array of { tag, count } objects.");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (typedStats.storage !== undefined &&
|
|
234
|
+
(!typedStats.storage ||
|
|
235
|
+
typeof typedStats.storage !== "object" ||
|
|
236
|
+
Array.isArray(typedStats.storage))) {
|
|
237
|
+
throw new Error("Stats endpoint returned an invalid response. storage must be an object.");
|
|
238
|
+
}
|
|
239
|
+
if (typedStats.organization_id !== undefined &&
|
|
240
|
+
typeof typedStats.organization_id !== "string") {
|
|
241
|
+
throw new Error("Stats endpoint returned an invalid response. organization_id must be a string.");
|
|
242
|
+
}
|
|
243
|
+
if (typedStats.generated_at !== undefined &&
|
|
244
|
+
typeof typedStats.generated_at !== "string") {
|
|
245
|
+
throw new Error("Stats endpoint returned an invalid response. generated_at must be a string.");
|
|
246
|
+
}
|
|
247
|
+
if (typedStats.total_topics !== undefined &&
|
|
248
|
+
typeof typedStats.total_topics !== "number") {
|
|
249
|
+
throw new Error("Stats endpoint returned an invalid response. total_topics must be numeric.");
|
|
250
|
+
}
|
|
251
|
+
if (typedStats.most_accessed_memory !== undefined &&
|
|
252
|
+
typeof typedStats.most_accessed_memory !== "string") {
|
|
253
|
+
throw new Error("Stats endpoint returned an invalid response. most_accessed_memory must be a string.");
|
|
254
|
+
}
|
|
255
|
+
if (typedStats.recent_memories !== undefined &&
|
|
256
|
+
(!Array.isArray(typedStats.recent_memories) ||
|
|
257
|
+
typedStats.recent_memories.some((entry) => typeof entry !== "string"))) {
|
|
258
|
+
throw new Error("Stats endpoint returned an invalid response. recent_memories must be an array of strings.");
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
total_memories: typedStats.total_memories,
|
|
262
|
+
memories_by_type: byType,
|
|
263
|
+
with_embeddings: typedStats.with_embeddings,
|
|
264
|
+
without_embeddings: typedStats.without_embeddings,
|
|
265
|
+
recent_activity: typedStats.recent_activity,
|
|
266
|
+
top_tags: typedStats.top_tags,
|
|
267
|
+
storage: typedStats.storage,
|
|
268
|
+
organization_id: typedStats.organization_id,
|
|
269
|
+
generated_at: typedStats.generated_at,
|
|
270
|
+
total_topics: typedStats.total_topics,
|
|
271
|
+
most_accessed_memory: typedStats.most_accessed_memory,
|
|
272
|
+
recent_memories: typedStats.recent_memories,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
export function printMemoryStats(stats) {
|
|
276
|
+
console.log(`Total memories: ${stats.total_memories}`);
|
|
277
|
+
if (stats.organization_id) {
|
|
278
|
+
console.log(`Organization: ${stats.organization_id}`);
|
|
279
|
+
}
|
|
280
|
+
if (stats.generated_at) {
|
|
281
|
+
console.log(`Generated: ${stats.generated_at}`);
|
|
282
|
+
}
|
|
283
|
+
if (typeof stats.total_topics === "number") {
|
|
284
|
+
console.log(`Total topics: ${stats.total_topics}`);
|
|
285
|
+
}
|
|
286
|
+
if (typeof stats.with_embeddings === "number" ||
|
|
287
|
+
typeof stats.without_embeddings === "number") {
|
|
288
|
+
console.log(`Embeddings: with=${stats.with_embeddings ?? 0}, without=${stats.without_embeddings ?? 0}`);
|
|
289
|
+
}
|
|
290
|
+
console.log("");
|
|
291
|
+
console.log("By type:");
|
|
292
|
+
const byTypeEntries = Object.entries(stats.memories_by_type).sort((a, b) => b[1] - a[1]);
|
|
293
|
+
if (byTypeEntries.length === 0) {
|
|
294
|
+
console.log(" (none)");
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
byTypeEntries.forEach(([type, count]) => {
|
|
298
|
+
console.log(` ${type.padEnd(12)}: ${count}`);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
if (stats.recent_activity) {
|
|
302
|
+
console.log("");
|
|
303
|
+
console.log("Recent activity (24h):");
|
|
304
|
+
console.log(` created : ${stats.recent_activity.created_last_24h ?? 0}`);
|
|
305
|
+
console.log(` updated : ${stats.recent_activity.updated_last_24h ?? 0}`);
|
|
306
|
+
console.log(` accessed: ${stats.recent_activity.accessed_last_24h ?? 0}`);
|
|
307
|
+
}
|
|
308
|
+
if (stats.most_accessed_memory) {
|
|
309
|
+
console.log("");
|
|
310
|
+
console.log(`Most accessed: ${stats.most_accessed_memory}`);
|
|
311
|
+
}
|
|
312
|
+
if (stats.recent_memories && stats.recent_memories.length > 0) {
|
|
313
|
+
console.log("");
|
|
314
|
+
console.log("Recent memories:");
|
|
315
|
+
stats.recent_memories.forEach((entry) => {
|
|
316
|
+
console.log(` - ${entry}`);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
if (stats.top_tags && stats.top_tags.length > 0) {
|
|
320
|
+
console.log("");
|
|
321
|
+
console.log("Top tags:");
|
|
322
|
+
stats.top_tags.forEach((entry) => {
|
|
323
|
+
console.log(` ${entry.tag}: ${entry.count}`);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
if (stats.storage && Object.keys(stats.storage).length > 0) {
|
|
327
|
+
console.log("");
|
|
328
|
+
console.log("Storage:");
|
|
329
|
+
Object.entries(stats.storage).forEach(([key, value]) => {
|
|
330
|
+
const rendered = typeof value === "string" ||
|
|
331
|
+
typeof value === "number" ||
|
|
332
|
+
typeof value === "boolean"
|
|
333
|
+
? String(value)
|
|
334
|
+
: JSON.stringify(value);
|
|
335
|
+
console.log(` ${key}: ${rendered}`);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { exitWithError, isNotFoundError, parseMemoryType, parseTags, printMemoryDetail, printMemorySummary, requireMemoryId, requireUpdateFields, resolveContentInput, normalizeTextInput, } from "./cli-common.js";
|
|
2
|
+
import { detectMemoryType } from "./enrichment/type-detector.js";
|
|
3
|
+
import { extractTags } from "./enrichment/tag-extractor.js";
|
|
4
|
+
function buildCliMetadata(cfg) {
|
|
5
|
+
return {
|
|
6
|
+
agent_id: cfg.agentId,
|
|
7
|
+
source: "openclaw-cli",
|
|
8
|
+
captured_at: new Date().toISOString(),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
async function runDelete(client, rawId, options, deprecated = false) {
|
|
12
|
+
try {
|
|
13
|
+
const id = requireMemoryId(rawId);
|
|
14
|
+
if (deprecated) {
|
|
15
|
+
console.error("Warning: `forget` is deprecated and will be removed after two tagged releases. Use `delete` or `rm`.");
|
|
16
|
+
}
|
|
17
|
+
if (!options.force) {
|
|
18
|
+
throw new Error("Deletion requires --force. Re-run as `openclaw recall delete <id> --force`.");
|
|
19
|
+
}
|
|
20
|
+
await client.deleteMemory(id);
|
|
21
|
+
console.log(`Deleted memory: ${id}`);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (isNotFoundError(error)) {
|
|
25
|
+
exitWithError(`Memory not found: ${rawId}`);
|
|
26
|
+
}
|
|
27
|
+
exitWithError(error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function registerMemoryCli(cmd, getRuntime) {
|
|
31
|
+
cmd
|
|
32
|
+
.command("create")
|
|
33
|
+
.alias("add")
|
|
34
|
+
.description("Create a memory")
|
|
35
|
+
.requiredOption("--title <title>", "Memory title")
|
|
36
|
+
.option("--content <content>", "Memory content")
|
|
37
|
+
.option("--content-file <path>", "Read memory content from a file")
|
|
38
|
+
.option("--type <type>", "Memory type")
|
|
39
|
+
.option("--tags <tags>", "Comma-separated tags")
|
|
40
|
+
.action(async (options) => {
|
|
41
|
+
try {
|
|
42
|
+
const { client, cfg } = getRuntime();
|
|
43
|
+
const title = normalizeTextInput("Title", options.title);
|
|
44
|
+
if (!title) {
|
|
45
|
+
throw new Error("`--title` is required.");
|
|
46
|
+
}
|
|
47
|
+
const content = await resolveContentInput({
|
|
48
|
+
content: options.content,
|
|
49
|
+
contentFile: options.contentFile,
|
|
50
|
+
required: true,
|
|
51
|
+
});
|
|
52
|
+
if (!content) {
|
|
53
|
+
throw new Error("Provide one of --content or --content-file.");
|
|
54
|
+
}
|
|
55
|
+
const type = parseMemoryType(options.type) ?? detectMemoryType(content);
|
|
56
|
+
const tags = parseTags(options.tags) ?? extractTags(content);
|
|
57
|
+
const memory = await client.createMemory({
|
|
58
|
+
title,
|
|
59
|
+
content,
|
|
60
|
+
type,
|
|
61
|
+
tags,
|
|
62
|
+
metadata: buildCliMetadata(cfg),
|
|
63
|
+
});
|
|
64
|
+
printMemorySummary(memory, "Created memory");
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
exitWithError(error);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
cmd
|
|
71
|
+
.command("get <id>")
|
|
72
|
+
.alias("show")
|
|
73
|
+
.description("Get a memory by ID")
|
|
74
|
+
.action(async (rawId) => {
|
|
75
|
+
try {
|
|
76
|
+
const { client } = getRuntime();
|
|
77
|
+
const id = requireMemoryId(rawId);
|
|
78
|
+
const memory = await client.getMemory(id);
|
|
79
|
+
printMemoryDetail(memory);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (isNotFoundError(error)) {
|
|
83
|
+
exitWithError(`Memory not found: ${rawId}`);
|
|
84
|
+
}
|
|
85
|
+
exitWithError(error);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
cmd
|
|
89
|
+
.command("update <id>")
|
|
90
|
+
.description("Update an existing memory")
|
|
91
|
+
.option("--title <title>", "New memory title")
|
|
92
|
+
.option("--content <content>", "New memory content")
|
|
93
|
+
.option("--content-file <path>", "Read new memory content from a file")
|
|
94
|
+
.option("--type <type>", "New memory type")
|
|
95
|
+
.option("--tags <tags>", "Comma-separated tags")
|
|
96
|
+
.action(async (rawId, options) => {
|
|
97
|
+
try {
|
|
98
|
+
const { client } = getRuntime();
|
|
99
|
+
const id = requireMemoryId(rawId);
|
|
100
|
+
const updates = {};
|
|
101
|
+
if (options.title !== undefined) {
|
|
102
|
+
updates.title = normalizeTextInput("Title", options.title);
|
|
103
|
+
}
|
|
104
|
+
if (options.content !== undefined ||
|
|
105
|
+
options.contentFile !== undefined) {
|
|
106
|
+
updates.content = await resolveContentInput({
|
|
107
|
+
content: options.content,
|
|
108
|
+
contentFile: options.contentFile,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (options.type !== undefined) {
|
|
112
|
+
updates.type = parseMemoryType(options.type);
|
|
113
|
+
}
|
|
114
|
+
if (options.tags !== undefined) {
|
|
115
|
+
updates.tags = parseTags(options.tags);
|
|
116
|
+
}
|
|
117
|
+
requireUpdateFields(updates);
|
|
118
|
+
const memory = await client.updateMemory(id, updates);
|
|
119
|
+
printMemorySummary(memory, "Updated memory");
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (isNotFoundError(error)) {
|
|
123
|
+
exitWithError(`Memory not found: ${rawId}`);
|
|
124
|
+
}
|
|
125
|
+
exitWithError(error);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
cmd
|
|
129
|
+
.command("delete <id>")
|
|
130
|
+
.alias("rm")
|
|
131
|
+
.description("Delete a memory by ID")
|
|
132
|
+
.option("--force", "Confirm deletion")
|
|
133
|
+
.action((rawId, options) => {
|
|
134
|
+
const { client } = getRuntime();
|
|
135
|
+
return runDelete(client, rawId, options);
|
|
136
|
+
});
|
|
137
|
+
const forgetCommand = cmd
|
|
138
|
+
.command("forget <id>")
|
|
139
|
+
.description("Deprecated alias for delete")
|
|
140
|
+
.option("--force", "Confirm deletion")
|
|
141
|
+
.action((rawId, options) => {
|
|
142
|
+
const { client } = getRuntime();
|
|
143
|
+
return runDelete(client, rawId, options, true);
|
|
144
|
+
});
|
|
145
|
+
forgetCommand.hideHelp?.();
|
|
146
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "./plugin-sdk-stub.js";
|
|
2
|
+
import type { LanonasisClient } from "./client.js";
|
|
3
|
+
import type { LanonasisConfig } from "./config.js";
|
|
4
|
+
export declare function registerCli(api: OpenClawPluginApi, getRuntime: () => {
|
|
5
|
+
client: LanonasisClient;
|
|
6
|
+
cfg: LanonasisConfig;
|
|
7
|
+
}): void;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { assertMemoryStatsShape, exitWithError, isNotFoundError, memoryTypeOf, parseMemoryType, parsePositiveInt, parseSortField, parseSortOrder, parseTags, parseThreshold, printMemoryStats, similarityOf, normalizeTextInput, } from "./cli-common.js";
|
|
2
|
+
import { registerMemoryCli } from "./cli-memory.js";
|
|
3
|
+
import { registerExtractCli } from "./extraction/cli-extract.js";
|
|
4
|
+
function padCell(value, width) {
|
|
5
|
+
return (value ?? "").slice(0, width - 2).padEnd(width);
|
|
6
|
+
}
|
|
7
|
+
export function registerCli(api, getRuntime) {
|
|
8
|
+
api.registerCli(({ program }) => {
|
|
9
|
+
const cmd = program
|
|
10
|
+
.command("recall")
|
|
11
|
+
.alias("lrf")
|
|
12
|
+
.description("RecallForge memory commands (alias: lrf)");
|
|
13
|
+
// status
|
|
14
|
+
cmd
|
|
15
|
+
.command("status")
|
|
16
|
+
.description("Check LanOnasis connection status")
|
|
17
|
+
.action(async () => {
|
|
18
|
+
try {
|
|
19
|
+
const { client, cfg } = getRuntime();
|
|
20
|
+
const h = await client.getHealth();
|
|
21
|
+
console.log(`Status: ${h.status} | v${h.version} | project: ${cfg.projectId}`);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
console.error(`Error: ${err instanceof Error ? err.message : "unknown"}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
// search
|
|
29
|
+
cmd
|
|
30
|
+
.command("search <query>")
|
|
31
|
+
.description("Semantic search memories")
|
|
32
|
+
.option("--agent <id>", "Filter by agent_id")
|
|
33
|
+
.option("--limit <n>", "Max results", "5")
|
|
34
|
+
.option("--threshold <n>", "Minimum semantic similarity (0-1)")
|
|
35
|
+
.option("--type <type>", "Filter by memory type")
|
|
36
|
+
.option("--tags <tags>", "Comma-separated tags")
|
|
37
|
+
.action(async (query, options) => {
|
|
38
|
+
try {
|
|
39
|
+
const { client } = getRuntime();
|
|
40
|
+
const normalizedQuery = normalizeTextInput("Query", query);
|
|
41
|
+
if (!normalizedQuery) {
|
|
42
|
+
throw new Error("Query cannot be empty or whitespace-only.");
|
|
43
|
+
}
|
|
44
|
+
const metadata = {};
|
|
45
|
+
if (options.agent)
|
|
46
|
+
metadata.agent_id = options.agent;
|
|
47
|
+
const memories = await client.searchMemories({
|
|
48
|
+
query: normalizedQuery,
|
|
49
|
+
threshold: parseThreshold(options.threshold),
|
|
50
|
+
limit: parsePositiveInt("--limit", options.limit, 5),
|
|
51
|
+
type: parseMemoryType(options.type),
|
|
52
|
+
tags: parseTags(options.tags),
|
|
53
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
54
|
+
});
|
|
55
|
+
if (!memories || memories.length === 0) {
|
|
56
|
+
console.log("No memories found.");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Table format: id(8), title(40), type, score
|
|
60
|
+
console.log(`│ ${"ID".padEnd(10)} │ ${"Title".padEnd(42)} │ ${"Type".padEnd(12)} │ Score │`);
|
|
61
|
+
console.log(`├────────────┼──────────────────────────────────────────┼──────────────┼───────┤`);
|
|
62
|
+
memories.forEach((m) => {
|
|
63
|
+
const id = padCell(m.id?.slice(0, 8), 10);
|
|
64
|
+
const title = padCell(m.title, 42);
|
|
65
|
+
const type = padCell(memoryTypeOf(m), 12);
|
|
66
|
+
const similarity = similarityOf(m);
|
|
67
|
+
const score = similarity !== undefined ? similarity.toFixed(2) : "N/A";
|
|
68
|
+
console.log(`│ ${id} │ ${title} │ ${type} │ ${score.padStart(5)} │`);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
exitWithError(err);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
// list
|
|
76
|
+
cmd
|
|
77
|
+
.command("list")
|
|
78
|
+
.description("List memories")
|
|
79
|
+
.option("--type <type>", "Filter by type")
|
|
80
|
+
.option("--limit <n>", "Max results", "20")
|
|
81
|
+
.option("--page <n>", "Page number", "1")
|
|
82
|
+
.option("--sort <field>", "Sort by created_at, updated_at, title, or type")
|
|
83
|
+
.option("--order <order>", "Sort order: asc or desc")
|
|
84
|
+
.option("--tags <tags>", "Comma-separated tags")
|
|
85
|
+
.action(async (options) => {
|
|
86
|
+
try {
|
|
87
|
+
const { client } = getRuntime();
|
|
88
|
+
const result = await client.listMemories({
|
|
89
|
+
limit: parsePositiveInt("--limit", options.limit, 20),
|
|
90
|
+
page: parsePositiveInt("--page", options.page, 1),
|
|
91
|
+
type: parseMemoryType(options.type),
|
|
92
|
+
tags: parseTags(options.tags),
|
|
93
|
+
sort: parseSortField(options.sort),
|
|
94
|
+
order: parseSortOrder(options.order),
|
|
95
|
+
});
|
|
96
|
+
if (!result.memories || result.memories.length === 0) {
|
|
97
|
+
console.log("No memories found.");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
console.log(`│ ${"ID".padEnd(10)} │ ${"Title".padEnd(42)} │ ${"Type".padEnd(12)} │`);
|
|
101
|
+
console.log(`├────────────┼──────────────────────────────────────────┼──────────────┤`);
|
|
102
|
+
result.memories.forEach((m) => {
|
|
103
|
+
const id = padCell(m.id?.slice(0, 8), 10);
|
|
104
|
+
const title = padCell(m.title, 42);
|
|
105
|
+
const type = padCell(memoryTypeOf(m), 12);
|
|
106
|
+
console.log(`│ ${id} │ ${title} │ ${type} │`);
|
|
107
|
+
});
|
|
108
|
+
console.log(`\nTotal: ${result.total}`);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
exitWithError(err);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
// stats
|
|
115
|
+
cmd
|
|
116
|
+
.command("stats")
|
|
117
|
+
.description("Show memory statistics")
|
|
118
|
+
.action(async () => {
|
|
119
|
+
try {
|
|
120
|
+
const { client } = getRuntime();
|
|
121
|
+
const stats = assertMemoryStatsShape(await client.getStats());
|
|
122
|
+
printMemoryStats(stats);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
if (isNotFoundError(err)) {
|
|
126
|
+
exitWithError("Stats endpoint unavailable. Verify the LanOnasis deployment exposes /api/v1/memory/stats.");
|
|
127
|
+
}
|
|
128
|
+
exitWithError(err);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
registerMemoryCli(cmd, getRuntime);
|
|
132
|
+
// extract — JSONL extraction with secret redaction
|
|
133
|
+
registerExtractCli(cmd, getRuntime);
|
|
134
|
+
}, { commands: ["recall", "lrf"] });
|
|
135
|
+
}
|