@heyamiko/amiko-cli 0.9.8 → 0.9.9-beta.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/dist/index.js +342 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22012,6 +22012,347 @@ function registerNotificationsCommand(program2) {
|
|
|
22012
22012
|
});
|
|
22013
22013
|
}
|
|
22014
22014
|
|
|
22015
|
+
// src/commands/memory.ts
|
|
22016
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
22017
|
+
import { dirname, isAbsolute, normalize, resolve as resolve2 } from "node:path";
|
|
22018
|
+
var VALID_CATEGORIES = ["fact", "preference", "pattern", "decision", "context"];
|
|
22019
|
+
var VALID_SOURCES = ["chat", "file", "manual"];
|
|
22020
|
+
var DEFAULT_WORKSPACE = "/data/.openclaw/workspace";
|
|
22021
|
+
var SYNC_BATCH_SIZE = 100;
|
|
22022
|
+
function fail(msg) {
|
|
22023
|
+
console.error(error(msg));
|
|
22024
|
+
process.exit(1);
|
|
22025
|
+
}
|
|
22026
|
+
function parseTags(raw) {
|
|
22027
|
+
if (!raw)
|
|
22028
|
+
return;
|
|
22029
|
+
const tags = raw.split(",").map((t) => t.trim()).filter(Boolean);
|
|
22030
|
+
return tags.length ? tags : undefined;
|
|
22031
|
+
}
|
|
22032
|
+
function fmtMemory(m) {
|
|
22033
|
+
console.log("");
|
|
22034
|
+
console.log(label("ID", m.id));
|
|
22035
|
+
console.log(label("Category", m.category));
|
|
22036
|
+
console.log(label("Source", m.source));
|
|
22037
|
+
if (m.tags && m.tags.length)
|
|
22038
|
+
console.log(label("Tags", m.tags.join(", ")));
|
|
22039
|
+
if (typeof m.score === "number")
|
|
22040
|
+
console.log(label("Score", m.score.toFixed(3)));
|
|
22041
|
+
if (m.created_at)
|
|
22042
|
+
console.log(label("Created", m.created_at.slice(0, 19)));
|
|
22043
|
+
console.log(m.content);
|
|
22044
|
+
}
|
|
22045
|
+
function registerMemoryCommand(program2) {
|
|
22046
|
+
const memory = program2.command("memory").description("Manage cross-agent memories — add, list, search, remove, sync local files");
|
|
22047
|
+
memory.command("add <content>").description("Add a single memory directly").option("--category <cat>", `Category (${VALID_CATEGORIES.join("|")})`, "fact").option("--tags <list>", "Comma-separated tags (max 10)").option("--source <src>", `Source (${VALID_SOURCES.join("|")})`, "manual").option("--twin <twinId>", "Override twin_id (defaults to scopedTwinId)").option("--json", "Output as JSON").action(async (content, opts) => {
|
|
22048
|
+
if (!content || !content.trim())
|
|
22049
|
+
fail("Content is required and cannot be empty.");
|
|
22050
|
+
if (opts.category && !VALID_CATEGORIES.includes(opts.category)) {
|
|
22051
|
+
fail(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`);
|
|
22052
|
+
}
|
|
22053
|
+
if (opts.source && !VALID_SOURCES.includes(opts.source)) {
|
|
22054
|
+
fail(`Invalid source. Must be one of: ${VALID_SOURCES.join(", ")}`);
|
|
22055
|
+
}
|
|
22056
|
+
const tags = parseTags(opts.tags);
|
|
22057
|
+
if (tags && tags.length > 10)
|
|
22058
|
+
fail("Max 10 tags.");
|
|
22059
|
+
const auth = resolveAuth();
|
|
22060
|
+
const spinner = opts.json ? null : ora("Saving memory...").start();
|
|
22061
|
+
try {
|
|
22062
|
+
const data = await amikoWebFetch(auth, "/api/memories", {
|
|
22063
|
+
method: "POST",
|
|
22064
|
+
body: {
|
|
22065
|
+
content: content.trim(),
|
|
22066
|
+
category: opts.category,
|
|
22067
|
+
tags,
|
|
22068
|
+
source: opts.source,
|
|
22069
|
+
twin_id: opts.twin
|
|
22070
|
+
}
|
|
22071
|
+
});
|
|
22072
|
+
spinner?.stop();
|
|
22073
|
+
renderOutput(data, (d) => {
|
|
22074
|
+
console.log(success("Memory saved"));
|
|
22075
|
+
fmtMemory(d.memory);
|
|
22076
|
+
}, { json: opts.json });
|
|
22077
|
+
} catch (e) {
|
|
22078
|
+
spinner?.stop();
|
|
22079
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
22080
|
+
}
|
|
22081
|
+
});
|
|
22082
|
+
memory.command("list").description("List the caller's memories (newest first)").option("--limit <n>", "Max results (1–100)", "25").option("--offset <n>", "Pagination offset", "0").option("--category <cat>", `Filter (${VALID_CATEGORIES.join("|")})`).option("--source <src>", `Filter (${VALID_SOURCES.join("|")})`).option("--json", "Output as JSON").action(async (opts) => {
|
|
22083
|
+
if (opts.category && !VALID_CATEGORIES.includes(opts.category)) {
|
|
22084
|
+
fail(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`);
|
|
22085
|
+
}
|
|
22086
|
+
if (opts.source && !VALID_SOURCES.includes(opts.source)) {
|
|
22087
|
+
fail(`Invalid source. Must be one of: ${VALID_SOURCES.join(", ")}`);
|
|
22088
|
+
}
|
|
22089
|
+
const auth = resolveAuth();
|
|
22090
|
+
const spinner = opts.json ? null : ora("Loading memories...").start();
|
|
22091
|
+
try {
|
|
22092
|
+
const data = await amikoWebFetch(auth, "/api/memories", {
|
|
22093
|
+
query: {
|
|
22094
|
+
limit: opts.limit,
|
|
22095
|
+
offset: opts.offset,
|
|
22096
|
+
category: opts.category,
|
|
22097
|
+
source: opts.source
|
|
22098
|
+
}
|
|
22099
|
+
});
|
|
22100
|
+
spinner?.stop();
|
|
22101
|
+
renderOutput(data, (d) => {
|
|
22102
|
+
if (!d.items.length) {
|
|
22103
|
+
console.log(dim("No memories."));
|
|
22104
|
+
return;
|
|
22105
|
+
}
|
|
22106
|
+
console.log(heading(`Memories — ${d.items.length} of ${d.total}`));
|
|
22107
|
+
for (const m of d.items)
|
|
22108
|
+
fmtMemory(m);
|
|
22109
|
+
if (d.has_more) {
|
|
22110
|
+
const next = Number(opts.offset || "0") + d.items.length;
|
|
22111
|
+
console.log("");
|
|
22112
|
+
console.log(dim(`More available — pass --offset ${next}`));
|
|
22113
|
+
}
|
|
22114
|
+
}, { json: opts.json });
|
|
22115
|
+
} catch (e) {
|
|
22116
|
+
spinner?.stop();
|
|
22117
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
22118
|
+
}
|
|
22119
|
+
});
|
|
22120
|
+
memory.command("search <query>").description("Hybrid search across the caller's memories").option("--limit <n>", "Max results (1–100)", "25").option("--category <cat>", `Filter (${VALID_CATEGORIES.join("|")})`).option("--json", "Output as JSON").action(async (query, opts) => {
|
|
22121
|
+
if (!query || !query.trim())
|
|
22122
|
+
fail("Query is required.");
|
|
22123
|
+
if (opts.category && !VALID_CATEGORIES.includes(opts.category)) {
|
|
22124
|
+
fail(`Invalid category. Must be one of: ${VALID_CATEGORIES.join(", ")}`);
|
|
22125
|
+
}
|
|
22126
|
+
const auth = resolveAuth();
|
|
22127
|
+
const spinner = opts.json ? null : ora("Searching...").start();
|
|
22128
|
+
try {
|
|
22129
|
+
const data = await amikoWebFetch(auth, "/api/memories", {
|
|
22130
|
+
query: {
|
|
22131
|
+
q: query,
|
|
22132
|
+
limit: opts.limit,
|
|
22133
|
+
category: opts.category
|
|
22134
|
+
}
|
|
22135
|
+
});
|
|
22136
|
+
spinner?.stop();
|
|
22137
|
+
renderOutput(data, (d) => {
|
|
22138
|
+
if (!d.items.length) {
|
|
22139
|
+
console.log(dim("No matches."));
|
|
22140
|
+
return;
|
|
22141
|
+
}
|
|
22142
|
+
console.log(heading(`Matches — ${d.items.length} of ${d.total}`));
|
|
22143
|
+
for (const m of d.items)
|
|
22144
|
+
fmtMemory(m);
|
|
22145
|
+
}, { json: opts.json });
|
|
22146
|
+
} catch (e) {
|
|
22147
|
+
spinner?.stop();
|
|
22148
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
22149
|
+
}
|
|
22150
|
+
});
|
|
22151
|
+
memory.command("rm <id>").description("Soft-delete a memory by id").option("--json", "Output as JSON").action(async (id, opts) => {
|
|
22152
|
+
if (!id)
|
|
22153
|
+
fail("Memory id is required.");
|
|
22154
|
+
const auth = resolveAuth();
|
|
22155
|
+
const spinner = opts.json ? null : ora("Deleting...").start();
|
|
22156
|
+
try {
|
|
22157
|
+
const data = await amikoWebFetch(auth, `/api/memories/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
22158
|
+
spinner?.stop();
|
|
22159
|
+
renderOutput(data, () => console.log(success(`Deleted memory ${id}`)), { json: opts.json });
|
|
22160
|
+
} catch (e) {
|
|
22161
|
+
spinner?.stop();
|
|
22162
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
22163
|
+
}
|
|
22164
|
+
});
|
|
22165
|
+
memory.command("status").description("Show counts of memories and synced memory files").option("--json", "Output as JSON").action(async (opts) => {
|
|
22166
|
+
const auth = resolveAuth();
|
|
22167
|
+
const spinner = opts.json ? null : ora("Loading status...").start();
|
|
22168
|
+
try {
|
|
22169
|
+
const [memories, files] = await Promise.all([
|
|
22170
|
+
amikoWebFetch(auth, "/api/memories", {
|
|
22171
|
+
query: { limit: "1" }
|
|
22172
|
+
}),
|
|
22173
|
+
amikoWebFetch(auth, "/api/memory-files", { query: { limit: "1" } })
|
|
22174
|
+
]);
|
|
22175
|
+
spinner?.stop();
|
|
22176
|
+
const fileTotal = files.total ?? files.items?.length ?? 0;
|
|
22177
|
+
renderOutput({ memories: memories.total, memory_files: fileTotal }, (d) => {
|
|
22178
|
+
console.log(heading("Memory status"));
|
|
22179
|
+
console.log(label("Memories", String(d.memories)));
|
|
22180
|
+
console.log(label("Memory files", String(d.memory_files)));
|
|
22181
|
+
}, { json: opts.json });
|
|
22182
|
+
} catch (e) {
|
|
22183
|
+
spinner?.stop();
|
|
22184
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
22185
|
+
}
|
|
22186
|
+
});
|
|
22187
|
+
memory.command("sync").description("Sync MEMORY.md and referenced files to the platform (one-way: local → server)").option("--path <dir>", `Workspace dir containing MEMORY.md (default ${DEFAULT_WORKSPACE})`).option("--dry-run", "Print what would be synced without uploading").option("--prune", "Delete server-side memory files that no longer exist locally").option("--json", "Output as JSON").action(async (opts) => {
|
|
22188
|
+
const root = resolve2(opts.path || DEFAULT_WORKSPACE);
|
|
22189
|
+
const indexPath = resolve2(root, "MEMORY.md");
|
|
22190
|
+
if (!existsSync3(indexPath)) {
|
|
22191
|
+
fail(`MEMORY.md not found at ${indexPath}`);
|
|
22192
|
+
}
|
|
22193
|
+
const collected = collectMemoryFiles(root, indexPath);
|
|
22194
|
+
if (collected.errors.length && !opts.json) {
|
|
22195
|
+
for (const e of collected.errors)
|
|
22196
|
+
console.error(error(e));
|
|
22197
|
+
}
|
|
22198
|
+
const files = collected.files;
|
|
22199
|
+
if (opts.dryRun) {
|
|
22200
|
+
renderOutput({ root, files: files.map((f) => f.path), prune: !!opts.prune }, (d) => {
|
|
22201
|
+
console.log(heading(`Dry run — ${d.files.length} file(s)`));
|
|
22202
|
+
for (const p of d.files)
|
|
22203
|
+
console.log(` ${p}`);
|
|
22204
|
+
if (d.prune)
|
|
22205
|
+
console.log(dim("--prune would remove server files not listed above."));
|
|
22206
|
+
}, { json: opts.json });
|
|
22207
|
+
return;
|
|
22208
|
+
}
|
|
22209
|
+
if (!files.length) {
|
|
22210
|
+
renderOutput({ synced: 0, changed: 0, errors: 0, extracted: 0 }, () => console.log(dim("Nothing to sync.")), { json: opts.json });
|
|
22211
|
+
return;
|
|
22212
|
+
}
|
|
22213
|
+
const auth = resolveAuth();
|
|
22214
|
+
const spinner = opts.json ? null : ora(`Syncing ${files.length} file(s)...`).start();
|
|
22215
|
+
try {
|
|
22216
|
+
const allResults = [];
|
|
22217
|
+
for (let i = 0;i < files.length; i += SYNC_BATCH_SIZE) {
|
|
22218
|
+
const chunk = files.slice(i, i + SYNC_BATCH_SIZE);
|
|
22219
|
+
const data = await amikoWebFetch(auth, "/api/memory-files/bulk", {
|
|
22220
|
+
method: "POST",
|
|
22221
|
+
body: { files: chunk }
|
|
22222
|
+
});
|
|
22223
|
+
allResults.push(...data.results);
|
|
22224
|
+
}
|
|
22225
|
+
let pruned = 0;
|
|
22226
|
+
const pruneErrors = [];
|
|
22227
|
+
if (opts.prune) {
|
|
22228
|
+
const localPaths = new Set(files.map((f) => f.path));
|
|
22229
|
+
const remote = await fetchAllMemoryFiles(auth);
|
|
22230
|
+
const stale = remote.filter((f) => !localPaths.has(f.path));
|
|
22231
|
+
for (const f of stale) {
|
|
22232
|
+
try {
|
|
22233
|
+
await amikoWebFetch(auth, `/api/memory-files/${encodeURIComponent(f.id)}`, { method: "DELETE" });
|
|
22234
|
+
pruned++;
|
|
22235
|
+
} catch (e) {
|
|
22236
|
+
pruneErrors.push(`${f.path}: ${e instanceof Error ? e.message : String(e)}`);
|
|
22237
|
+
}
|
|
22238
|
+
}
|
|
22239
|
+
}
|
|
22240
|
+
spinner?.stop();
|
|
22241
|
+
const changed = allResults.filter((r) => r.changed).length;
|
|
22242
|
+
const errors2 = allResults.filter((r) => r.error).length;
|
|
22243
|
+
const extracted = allResults.reduce((acc, r) => acc + (r.extracted ?? 0), 0);
|
|
22244
|
+
renderOutput({
|
|
22245
|
+
synced: allResults.length,
|
|
22246
|
+
changed,
|
|
22247
|
+
errors: errors2,
|
|
22248
|
+
extracted,
|
|
22249
|
+
pruned,
|
|
22250
|
+
prune_errors: pruneErrors,
|
|
22251
|
+
results: allResults
|
|
22252
|
+
}, (d) => {
|
|
22253
|
+
console.log(success(`${d.synced} file(s) synced (${d.changed} changed, ${d.errors} errors), ${d.extracted} memories extracted`));
|
|
22254
|
+
for (const r of allResults) {
|
|
22255
|
+
if (r.error) {
|
|
22256
|
+
console.log(error(`${r.path}: ${r.error}`));
|
|
22257
|
+
} else if (r.changed) {
|
|
22258
|
+
console.log(` ${r.path} ${dim(`→ ${r.extracted ?? 0} extracted`)}`);
|
|
22259
|
+
} else {
|
|
22260
|
+
console.log(` ${r.path} ${dim("(unchanged)")}`);
|
|
22261
|
+
}
|
|
22262
|
+
}
|
|
22263
|
+
if (opts.prune) {
|
|
22264
|
+
console.log("");
|
|
22265
|
+
console.log(label("Pruned", String(d.pruned)));
|
|
22266
|
+
for (const e of pruneErrors)
|
|
22267
|
+
console.log(error(e));
|
|
22268
|
+
}
|
|
22269
|
+
}, { json: opts.json });
|
|
22270
|
+
} catch (e) {
|
|
22271
|
+
spinner?.stop();
|
|
22272
|
+
fail(e instanceof Error ? e.message : String(e));
|
|
22273
|
+
}
|
|
22274
|
+
});
|
|
22275
|
+
}
|
|
22276
|
+
function collectMemoryFiles(root, indexPath) {
|
|
22277
|
+
const errors2 = [];
|
|
22278
|
+
const seen = new Set;
|
|
22279
|
+
const files = [];
|
|
22280
|
+
const indexContent = safeRead(indexPath, errors2);
|
|
22281
|
+
if (indexContent === null)
|
|
22282
|
+
return { files, errors: errors2 };
|
|
22283
|
+
pushFile(files, seen, indexPath, indexContent, root);
|
|
22284
|
+
const indexDir = dirname(indexPath);
|
|
22285
|
+
for (const ref of extractMarkdownLinks(indexContent)) {
|
|
22286
|
+
if (isExternal(ref))
|
|
22287
|
+
continue;
|
|
22288
|
+
const abs = normalize(isAbsolute(ref) ? ref : resolve2(indexDir, ref));
|
|
22289
|
+
if (!abs.startsWith(root)) {
|
|
22290
|
+
errors2.push(`Skipping ${ref}: outside workspace ${root}`);
|
|
22291
|
+
continue;
|
|
22292
|
+
}
|
|
22293
|
+
if (!existsSync3(abs)) {
|
|
22294
|
+
errors2.push(`Referenced file missing: ${ref}`);
|
|
22295
|
+
continue;
|
|
22296
|
+
}
|
|
22297
|
+
const st = statSync(abs);
|
|
22298
|
+
if (!st.isFile())
|
|
22299
|
+
continue;
|
|
22300
|
+
const content = safeRead(abs, errors2);
|
|
22301
|
+
if (content === null)
|
|
22302
|
+
continue;
|
|
22303
|
+
pushFile(files, seen, abs, content, root);
|
|
22304
|
+
}
|
|
22305
|
+
return { files, errors: errors2 };
|
|
22306
|
+
}
|
|
22307
|
+
function pushFile(files, seen, abs, content, root) {
|
|
22308
|
+
const key = relativeKey(abs, root);
|
|
22309
|
+
if (seen.has(key))
|
|
22310
|
+
return;
|
|
22311
|
+
seen.add(key);
|
|
22312
|
+
files.push({ path: key, content });
|
|
22313
|
+
}
|
|
22314
|
+
function relativeKey(abs, root) {
|
|
22315
|
+
const rel = abs.startsWith(root) ? abs.slice(root.length) : abs;
|
|
22316
|
+
return rel.replace(/^\/+/, "");
|
|
22317
|
+
}
|
|
22318
|
+
function safeRead(path2, errors2) {
|
|
22319
|
+
try {
|
|
22320
|
+
return readFileSync2(path2, "utf-8");
|
|
22321
|
+
} catch (e) {
|
|
22322
|
+
errors2.push(`Failed to read ${path2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
22323
|
+
return null;
|
|
22324
|
+
}
|
|
22325
|
+
}
|
|
22326
|
+
function extractMarkdownLinks(md) {
|
|
22327
|
+
const out = [];
|
|
22328
|
+
const re = /\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
22329
|
+
let m;
|
|
22330
|
+
while ((m = re.exec(md)) !== null) {
|
|
22331
|
+
const target = m[1].split("#")[0];
|
|
22332
|
+
if (target)
|
|
22333
|
+
out.push(target);
|
|
22334
|
+
}
|
|
22335
|
+
return out;
|
|
22336
|
+
}
|
|
22337
|
+
function isExternal(ref) {
|
|
22338
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(ref) || ref.startsWith("//");
|
|
22339
|
+
}
|
|
22340
|
+
async function fetchAllMemoryFiles(auth) {
|
|
22341
|
+
const all = [];
|
|
22342
|
+
let offset = 0;
|
|
22343
|
+
const limit = 100;
|
|
22344
|
+
for (;; ) {
|
|
22345
|
+
const data = await amikoWebFetch(auth, "/api/memory-files", {
|
|
22346
|
+
query: { limit: String(limit), offset: String(offset) }
|
|
22347
|
+
});
|
|
22348
|
+
all.push(...data.items ?? []);
|
|
22349
|
+
if (!data.has_more || !data.items?.length)
|
|
22350
|
+
break;
|
|
22351
|
+
offset += data.items.length;
|
|
22352
|
+
}
|
|
22353
|
+
return all;
|
|
22354
|
+
}
|
|
22355
|
+
|
|
22015
22356
|
// src/commands/wallets.ts
|
|
22016
22357
|
function formatBalance(b) {
|
|
22017
22358
|
const n = Number(b.amount) / 10 ** b.decimals;
|
|
@@ -22255,4 +22596,5 @@ registerReviewCommand(review);
|
|
|
22255
22596
|
registerComposioCommand(composio);
|
|
22256
22597
|
registerConversationCommand(conversation);
|
|
22257
22598
|
registerNotificationsCommand(notifications);
|
|
22599
|
+
registerMemoryCommand(program2);
|
|
22258
22600
|
program2.parse();
|