memoclaw 1.4.0 → 1.6.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.
Files changed (2) hide show
  1. package/dist/cli.mjs +956 -196
  2. package/package.json +2 -1
package/dist/cli.mjs CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
2
3
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
4
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
5
 
4
6
  // node_modules/viem/_esm/utils/data/isHex.js
5
7
  function isHex(value, { strict = true } = {}) {
@@ -5206,12 +5208,143 @@ function privateKeyToAccount(privateKey, options = {}) {
5206
5208
  source: "privateKey"
5207
5209
  };
5208
5210
  }
5211
+ // src/args.ts
5212
+ var BOOLEAN_FLAGS = new Set([
5213
+ "help",
5214
+ "version",
5215
+ "raw",
5216
+ "json",
5217
+ "quiet",
5218
+ "dryRun",
5219
+ "verbose",
5220
+ "noColor"
5221
+ ]);
5222
+ var SHORT_FLAGS = {
5223
+ "-h": "help",
5224
+ "-v": "version",
5225
+ "-j": "json",
5226
+ "-q": "quiet",
5227
+ "-n": "namespace",
5228
+ "-l": "limit",
5229
+ "-t": "tags",
5230
+ "-o": "output"
5231
+ };
5232
+ function parseArgs(args) {
5233
+ const result = { _: [] };
5234
+ let i = 0;
5235
+ while (i < args.length) {
5236
+ const arg = args[i];
5237
+ if (arg[0] === "-" && arg[1] !== "-" && arg.length >= 2) {
5238
+ if (arg.length === 2 && SHORT_FLAGS[arg]) {
5239
+ const key = SHORT_FLAGS[arg];
5240
+ if (BOOLEAN_FLAGS.has(key)) {
5241
+ result[key] = true;
5242
+ i++;
5243
+ } else {
5244
+ const next = args[i + 1];
5245
+ if (next !== undefined && !next.startsWith("-")) {
5246
+ result[key] = next;
5247
+ i += 2;
5248
+ } else {
5249
+ result[key] = true;
5250
+ i++;
5251
+ }
5252
+ }
5253
+ } else if (arg.length > 2 && !SHORT_FLAGS[arg]) {
5254
+ const chars = arg.slice(1).split("");
5255
+ let allValid = true;
5256
+ for (const ch of chars) {
5257
+ const flag = `-${ch}`;
5258
+ if (!SHORT_FLAGS[flag]) {
5259
+ allValid = false;
5260
+ break;
5261
+ }
5262
+ }
5263
+ if (allValid) {
5264
+ for (let ci = 0;ci < chars.length; ci++) {
5265
+ const flag = `-${chars[ci]}`;
5266
+ const key = SHORT_FLAGS[flag];
5267
+ if (BOOLEAN_FLAGS.has(key)) {
5268
+ result[key] = true;
5269
+ } else if (ci === chars.length - 1) {
5270
+ const next = args[i + 1];
5271
+ if (next !== undefined && !next.startsWith("-")) {
5272
+ result[key] = next;
5273
+ i++;
5274
+ } else {
5275
+ result[key] = true;
5276
+ }
5277
+ } else {
5278
+ result[key] = true;
5279
+ }
5280
+ }
5281
+ i++;
5282
+ } else {
5283
+ result._.push(arg);
5284
+ i++;
5285
+ }
5286
+ } else {
5287
+ result._.push(arg);
5288
+ i++;
5289
+ }
5290
+ } else if (arg === "--") {
5291
+ result._.push(...args.slice(i + 1));
5292
+ break;
5293
+ } else if (arg.startsWith("--")) {
5294
+ const eqIdx = arg.indexOf("=");
5295
+ let key;
5296
+ let inlineValue;
5297
+ if (eqIdx !== -1) {
5298
+ key = arg.slice(2, eqIdx).replace(/-([a-z])/g, (_, ch) => ch.toUpperCase());
5299
+ inlineValue = arg.slice(eqIdx + 1);
5300
+ } else {
5301
+ key = arg.slice(2).replace(/-([a-z])/g, (_, ch) => ch.toUpperCase());
5302
+ }
5303
+ if (inlineValue !== undefined) {
5304
+ result[key] = inlineValue;
5305
+ i++;
5306
+ } else if (BOOLEAN_FLAGS.has(key)) {
5307
+ result[key] = true;
5308
+ i++;
5309
+ } else {
5310
+ const next = args[i + 1];
5311
+ if (next !== undefined && (!next.startsWith("--") || /^--?\d/.test(next))) {
5312
+ result[key] = next;
5313
+ i += 2;
5314
+ } else {
5315
+ result[key] = true;
5316
+ i++;
5317
+ }
5318
+ }
5319
+ } else {
5320
+ result._.push(arg);
5321
+ i++;
5322
+ }
5323
+ }
5324
+ return result;
5325
+ }
5326
+
5209
5327
  // src/cli.ts
5328
+ var VERSION = "1.6.0";
5210
5329
  var API_URL = process.env.MEMOCLAW_URL || "https://api.memoclaw.com";
5211
5330
  var PRIVATE_KEY = process.env.MEMOCLAW_PRIVATE_KEY;
5331
+ var NO_COLOR = !!process.env.NO_COLOR || !process.stdout.isTTY;
5332
+ var c = {
5333
+ reset: NO_COLOR ? "" : "\x1B[0m",
5334
+ bold: NO_COLOR ? "" : "\x1B[1m",
5335
+ dim: NO_COLOR ? "" : "\x1B[2m",
5336
+ red: NO_COLOR ? "" : "\x1B[31m",
5337
+ green: NO_COLOR ? "" : "\x1B[32m",
5338
+ yellow: NO_COLOR ? "" : "\x1B[33m",
5339
+ blue: NO_COLOR ? "" : "\x1B[34m",
5340
+ magenta: NO_COLOR ? "" : "\x1B[35m",
5341
+ cyan: NO_COLOR ? "" : "\x1B[36m",
5342
+ gray: NO_COLOR ? "" : "\x1B[90m"
5343
+ };
5212
5344
  function ensureAuth() {
5213
5345
  if (!PRIVATE_KEY) {
5214
- console.error("Error: MEMOCLAW_PRIVATE_KEY environment variable required");
5346
+ console.error(`${c.red}Error:${c.reset} MEMOCLAW_PRIVATE_KEY environment variable required`);
5347
+ console.error(`${c.dim}Set it with: export MEMOCLAW_PRIVATE_KEY=0x...${c.reset}`);
5215
5348
  process.exit(1);
5216
5349
  }
5217
5350
  }
@@ -5243,33 +5376,73 @@ async function getWalletAuthHeader() {
5243
5376
  const signature = await account.signMessage({ message });
5244
5377
  return `${account.address}:${timestamp}:${signature}`;
5245
5378
  }
5246
- function parseArgs(args) {
5247
- const result = { _: [] };
5248
- let i = 0;
5249
- while (i < args.length) {
5250
- const arg = args[i];
5251
- if (arg === "-h" || arg === "--help") {
5252
- result.help = true;
5253
- i++;
5254
- } else if (arg === "-v" || arg === "--version") {
5255
- result.version = true;
5256
- i++;
5257
- } else if (arg.startsWith("--")) {
5258
- const key = arg.slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
5259
- const next = args[i + 1];
5260
- if (next && !next.startsWith("--")) {
5261
- result[key] = next;
5262
- i += 2;
5263
- } else {
5264
- result[key] = true;
5265
- i++;
5266
- }
5267
- } else {
5268
- result._.push(arg);
5269
- i++;
5270
- }
5271
- }
5272
- return result;
5379
+ async function readStdin() {
5380
+ if (process.stdin.isTTY)
5381
+ return null;
5382
+ const chunks = [];
5383
+ for await (const chunk of process.stdin)
5384
+ chunks.push(chunk.toString());
5385
+ const text = chunks.join("").trim();
5386
+ return text || null;
5387
+ }
5388
+ var outputJson = false;
5389
+ var outputQuiet = false;
5390
+ function out(data) {
5391
+ if (outputQuiet)
5392
+ return;
5393
+ if (outputJson) {
5394
+ console.log(JSON.stringify(data, null, 2));
5395
+ } else if (typeof data === "string") {
5396
+ console.log(data);
5397
+ } else {
5398
+ console.log(JSON.stringify(data, null, 2));
5399
+ }
5400
+ }
5401
+ function success(msg) {
5402
+ if (outputQuiet)
5403
+ return;
5404
+ if (outputJson)
5405
+ return;
5406
+ console.log(`${c.green}✓${c.reset} ${msg}`);
5407
+ }
5408
+ function info(msg) {
5409
+ if (outputQuiet)
5410
+ return;
5411
+ if (outputJson)
5412
+ return;
5413
+ console.error(`${c.blue}ℹ${c.reset} ${msg}`);
5414
+ }
5415
+ function table(rows, columns) {
5416
+ if (rows.length === 0)
5417
+ return;
5418
+ if (outputJson) {
5419
+ console.log(JSON.stringify(rows, null, 2));
5420
+ return;
5421
+ }
5422
+ if (!columns) {
5423
+ columns = Object.keys(rows[0]).map((k) => ({ key: k, label: k.toUpperCase() }));
5424
+ }
5425
+ for (const col of columns) {
5426
+ if (!col.width) {
5427
+ col.width = Math.max(col.label.length, ...rows.map((r) => String(r[col.key] ?? "").length));
5428
+ col.width = Math.min(col.width, 60);
5429
+ }
5430
+ }
5431
+ const header = columns.map((col) => col.label.padEnd(col.width)).join(" ");
5432
+ console.log(`${c.bold}${header}${c.reset}`);
5433
+ console.log(`${c.dim}${columns.map((col) => "─".repeat(col.width)).join("──")}${c.reset}`);
5434
+ for (const row of rows) {
5435
+ const line = columns.map((col) => {
5436
+ const val = String(row[col.key] ?? "");
5437
+ return val.length > col.width ? val.slice(0, col.width - 1) + "…" : val.padEnd(col.width);
5438
+ }).join(" ");
5439
+ console.log(line);
5440
+ }
5441
+ }
5442
+ function progressBar(current, total, width = 30) {
5443
+ const pct = Math.min(current / total, 1);
5444
+ const filled = Math.round(pct * width);
5445
+ return `${c.green}${"█".repeat(filled)}${c.dim}${"░".repeat(width - filled)}${c.reset} ${current}/${total}`;
5273
5446
  }
5274
5447
  async function request(method, path, body = null) {
5275
5448
  const url = `${API_URL}${path}`;
@@ -5281,10 +5454,8 @@ async function request(method, path, body = null) {
5281
5454
  headers["x-wallet-auth"] = walletAuth;
5282
5455
  let res = await fetch(url, { ...options, headers });
5283
5456
  const freeTierRemaining = res.headers.get("x-free-tier-remaining");
5284
- if (freeTierRemaining !== null) {
5285
- if (process.env.DEBUG) {
5286
- console.error(`Free tier remaining: ${freeTierRemaining}`);
5287
- }
5457
+ if (freeTierRemaining !== null && process.env.DEBUG) {
5458
+ console.error(`${c.dim}Free tier remaining: ${freeTierRemaining}${c.reset}`);
5288
5459
  }
5289
5460
  if (res.status === 402) {
5290
5461
  const errorBody = await res.json();
@@ -5323,93 +5494,167 @@ async function request(method, path, body = null) {
5323
5494
  }
5324
5495
  return data;
5325
5496
  }
5326
- async function store(content, opts) {
5497
+ async function cmdStore(content, opts) {
5327
5498
  const body = { content };
5328
- if (opts.importance)
5499
+ if (opts.importance != null && opts.importance !== true)
5329
5500
  body.importance = parseFloat(opts.importance);
5330
5501
  if (opts.tags)
5331
5502
  body.metadata = { tags: opts.tags.split(",").map((t) => t.trim()) };
5332
5503
  if (opts.namespace)
5333
5504
  body.namespace = opts.namespace;
5334
5505
  const result = await request("POST", "/v1/store", body);
5335
- console.log(JSON.stringify(result, null, 2));
5506
+ if (outputJson) {
5507
+ out(result);
5508
+ } else {
5509
+ success(`Memory stored${result.id ? ` (${c.cyan}${result.id}${c.reset})` : ""}`);
5510
+ if (result.importance !== undefined)
5511
+ info(`Importance: ${result.importance}`);
5512
+ }
5336
5513
  }
5337
- async function recall(query, opts) {
5514
+ async function cmdRecall(query, opts) {
5338
5515
  const body = { query };
5339
- if (opts.limit)
5516
+ if (opts.limit != null && opts.limit !== true)
5340
5517
  body.limit = parseInt(opts.limit);
5341
- if (opts.minSimilarity)
5518
+ if (opts.minSimilarity != null && opts.minSimilarity !== true)
5342
5519
  body.min_similarity = parseFloat(opts.minSimilarity);
5343
5520
  if (opts.namespace)
5344
5521
  body.namespace = opts.namespace;
5345
5522
  if (opts.tags)
5346
5523
  body.filters = { tags: opts.tags.split(",").map((t) => t.trim()) };
5347
5524
  const result = await request("POST", "/v1/recall", body);
5348
- if (opts.raw) {
5349
- console.log(JSON.stringify(result, null, 2));
5525
+ if (outputJson) {
5526
+ out(result);
5527
+ } else if (opts.raw) {
5528
+ const memories = result.memories || [];
5529
+ for (const mem of memories) {
5530
+ console.log(mem.content);
5531
+ }
5350
5532
  } else {
5351
5533
  const memories = result.memories || [];
5352
5534
  if (memories.length === 0) {
5353
- console.log("No memories found.");
5535
+ console.log(`${c.dim}No memories found.${c.reset}`);
5354
5536
  } else {
5355
5537
  for (const mem of memories) {
5356
- console.log(`[${mem.similarity?.toFixed(3) || "???"}] ${mem.content}`);
5357
- if (mem.metadata?.tags?.length)
5358
- console.log(` tags: ${mem.metadata.tags.join(", ")}`);
5538
+ const sim = mem.similarity?.toFixed(3) || "???";
5539
+ const simColor = (mem.similarity || 0) > 0.8 ? c.green : (mem.similarity || 0) > 0.5 ? c.yellow : c.red;
5540
+ console.log(`${simColor}[${sim}]${c.reset} ${mem.content}`);
5541
+ if (mem.metadata?.tags?.length) {
5542
+ console.log(` ${c.dim}tags: ${mem.metadata.tags.join(", ")}${c.reset}`);
5543
+ }
5544
+ if (mem.id) {
5545
+ console.log(` ${c.dim}id: ${mem.id}${c.reset}`);
5546
+ }
5359
5547
  }
5548
+ console.log(`${c.dim}─ ${memories.length} result${memories.length !== 1 ? "s" : ""}${c.reset}`);
5360
5549
  }
5361
5550
  }
5362
5551
  }
5363
- async function list(opts) {
5552
+ async function cmdList(opts) {
5364
5553
  const params = new URLSearchParams;
5365
- if (opts.limit)
5554
+ if (opts.limit != null && opts.limit !== true)
5366
5555
  params.set("limit", opts.limit);
5367
- if (opts.offset)
5556
+ if (opts.offset != null && opts.offset !== true)
5368
5557
  params.set("offset", opts.offset);
5369
5558
  if (opts.namespace)
5370
5559
  params.set("namespace", opts.namespace);
5371
5560
  const result = await request("GET", `/v1/memories?${params}`);
5372
- console.log(JSON.stringify(result, null, 2));
5561
+ if (outputJson) {
5562
+ out(result);
5563
+ } else {
5564
+ const memories = result.memories || result.data || [];
5565
+ if (memories.length === 0) {
5566
+ console.log(`${c.dim}No memories found.${c.reset}`);
5567
+ } else {
5568
+ const rows = memories.map((m) => ({
5569
+ id: m.id?.slice(0, 8) || "?",
5570
+ content: m.content?.length > 50 ? m.content.slice(0, 50) + "…" : m.content || "",
5571
+ importance: m.importance?.toFixed(2) || "-",
5572
+ tags: m.metadata?.tags?.join(", ") || "",
5573
+ created: m.created_at ? new Date(m.created_at).toLocaleDateString() : ""
5574
+ }));
5575
+ table(rows, [
5576
+ { key: "id", label: "ID", width: 10 },
5577
+ { key: "content", label: "CONTENT", width: 52 },
5578
+ { key: "importance", label: "IMP", width: 5 },
5579
+ { key: "tags", label: "TAGS", width: 20 },
5580
+ { key: "created", label: "CREATED", width: 12 }
5581
+ ]);
5582
+ if (result.total !== undefined) {
5583
+ console.log(`${c.dim}─ ${memories.length} of ${result.total} memories${c.reset}`);
5584
+ }
5585
+ }
5586
+ }
5373
5587
  }
5374
- async function deleteMemory(id) {
5588
+ async function cmdGet(id) {
5589
+ const result = await request("GET", `/v1/memories/${id}`);
5590
+ if (outputJson) {
5591
+ out(result);
5592
+ } else {
5593
+ const mem = result.memory || result;
5594
+ console.log(`${c.bold}ID:${c.reset} ${mem.id || id}`);
5595
+ console.log(`${c.bold}Content:${c.reset} ${mem.content}`);
5596
+ if (mem.importance !== undefined)
5597
+ console.log(`${c.bold}Importance:${c.reset} ${mem.importance}`);
5598
+ if (mem.namespace)
5599
+ console.log(`${c.bold}Namespace:${c.reset} ${mem.namespace}`);
5600
+ if (mem.metadata?.tags?.length)
5601
+ console.log(`${c.bold}Tags:${c.reset} ${mem.metadata.tags.join(", ")}`);
5602
+ if (mem.memory_type)
5603
+ console.log(`${c.bold}Type:${c.reset} ${mem.memory_type}`);
5604
+ if (mem.created_at)
5605
+ console.log(`${c.bold}Created:${c.reset} ${new Date(mem.created_at).toLocaleString()}`);
5606
+ if (mem.updated_at)
5607
+ console.log(`${c.bold}Updated:${c.reset} ${new Date(mem.updated_at).toLocaleString()}`);
5608
+ if (mem.pinned)
5609
+ console.log(`${c.bold}Pinned:${c.reset} ${c.green}yes${c.reset}`);
5610
+ }
5611
+ }
5612
+ async function cmdDelete(id) {
5375
5613
  const result = await request("DELETE", `/v1/memories/${id}`);
5376
- console.log(JSON.stringify(result, null, 2));
5614
+ if (outputJson) {
5615
+ out(result);
5616
+ } else {
5617
+ success(`Memory ${c.cyan}${id.slice(0, 8)}…${c.reset} deleted`);
5618
+ }
5377
5619
  }
5378
- async function suggested(opts) {
5620
+ async function cmdSuggested(opts) {
5379
5621
  const params = new URLSearchParams;
5380
- if (opts.limit)
5622
+ if (opts.limit != null && opts.limit !== true)
5381
5623
  params.set("limit", opts.limit);
5382
5624
  if (opts.namespace)
5383
5625
  params.set("namespace", opts.namespace);
5384
5626
  if (opts.category)
5385
5627
  params.set("category", opts.category);
5386
5628
  const result = await request("GET", `/v1/suggested?${params}`);
5387
- if (opts.raw) {
5388
- console.log(JSON.stringify(result, null, 2));
5629
+ if (outputJson) {
5630
+ out(result);
5389
5631
  } else {
5390
5632
  if (result.categories) {
5391
- console.log("Categories:", Object.entries(result.categories).map(([k, v]) => `${k}=${v}`).join(", "));
5392
- console.log("---");
5633
+ const cats = Object.entries(result.categories).map(([k, v]) => `${c.bold}${k}${c.reset}=${v}`).join(" ");
5634
+ console.log(`Categories: ${cats}`);
5635
+ console.log(`${c.dim}${"─".repeat(60)}${c.reset}`);
5393
5636
  }
5394
5637
  const suggestions = result.suggested || [];
5395
5638
  if (suggestions.length === 0) {
5396
- console.log("No suggested memories.");
5639
+ console.log(`${c.dim}No suggested memories.${c.reset}`);
5397
5640
  } else {
5398
5641
  for (const mem of suggestions) {
5399
5642
  const cat = mem.category?.toUpperCase() || "???";
5400
- const text = mem.content.length > 100 ? mem.content.slice(0, 100) + "..." : mem.content;
5401
- console.log(`[${cat}] (${mem.review_score?.toFixed(2) || "?"}) ${text}`);
5402
- if (mem.metadata?.tags?.length)
5403
- console.log(` tags: ${mem.metadata.tags.join(", ")}`);
5643
+ const catColor = { STALE: c.red, FRESH: c.green, HOT: c.yellow, DECAYING: c.magenta }[cat] || c.gray;
5644
+ const text = mem.content.length > 100 ? mem.content.slice(0, 100) + "" : mem.content;
5645
+ console.log(`${catColor}[${cat}]${c.reset} ${c.dim}(${mem.review_score?.toFixed(2) || "?"})${c.reset} ${text}`);
5646
+ if (mem.metadata?.tags?.length) {
5647
+ console.log(` ${c.dim}tags: ${mem.metadata.tags.join(", ")}${c.reset}`);
5648
+ }
5404
5649
  }
5405
5650
  }
5406
5651
  }
5407
5652
  }
5408
- async function update(id, opts) {
5653
+ async function cmdUpdate(id, opts) {
5409
5654
  const body = {};
5410
5655
  if (opts.content)
5411
5656
  body.content = opts.content;
5412
- if (opts.importance)
5657
+ if (opts.importance != null && opts.importance !== true)
5413
5658
  body.importance = parseFloat(opts.importance);
5414
5659
  if (opts.memoryType)
5415
5660
  body.memory_type = opts.memoryType;
@@ -5421,10 +5666,17 @@ async function update(id, opts) {
5421
5666
  body.expires_at = opts.expiresAt;
5422
5667
  if (opts.pinned !== undefined)
5423
5668
  body.pinned = opts.pinned === "true" || opts.pinned === true;
5669
+ if (Object.keys(body).length === 0) {
5670
+ throw new Error("No fields to update. Use --content, --importance, --tags, etc.");
5671
+ }
5424
5672
  const result = await request("PATCH", `/v1/memories/${id}`, body);
5425
- console.log(JSON.stringify(result, null, 2));
5673
+ if (outputJson) {
5674
+ out(result);
5675
+ } else {
5676
+ success(`Memory ${c.cyan}${id.slice(0, 8)}…${c.reset} updated`);
5677
+ }
5426
5678
  }
5427
- async function ingest(opts) {
5679
+ async function cmdIngest(opts) {
5428
5680
  const body = {};
5429
5681
  if (opts.text)
5430
5682
  body.text = opts.text;
@@ -5438,18 +5690,22 @@ async function ingest(opts) {
5438
5690
  body.auto_relate = opts.autoRelate !== "false";
5439
5691
  else
5440
5692
  body.auto_relate = true;
5441
- if (!body.text && !process.stdin.isTTY) {
5442
- const chunks = [];
5443
- for await (const chunk of process.stdin)
5444
- chunks.push(chunk.toString());
5445
- body.text = chunks.join("");
5693
+ if (!body.text) {
5694
+ const stdin = await readStdin();
5695
+ if (stdin)
5696
+ body.text = stdin;
5446
5697
  }
5447
5698
  if (!body.text)
5448
5699
  throw new Error("Text required (use --text or pipe via stdin)");
5449
5700
  const result = await request("POST", "/v1/ingest", body);
5450
- console.log(JSON.stringify(result, null, 2));
5701
+ if (outputJson) {
5702
+ out(result);
5703
+ } else {
5704
+ const count = result.memories_created ?? result.count ?? "?";
5705
+ success(`Ingested text → ${count} memories created`);
5706
+ }
5451
5707
  }
5452
- async function extract(text, opts) {
5708
+ async function cmdExtract(text, opts) {
5453
5709
  const body = { text };
5454
5710
  if (opts.namespace)
5455
5711
  body.namespace = opts.namespace;
@@ -5458,206 +5714,710 @@ async function extract(text, opts) {
5458
5714
  if (opts.agentId)
5459
5715
  body.agent_id = opts.agentId;
5460
5716
  const result = await request("POST", "/v1/memories/extract", body);
5461
- console.log(JSON.stringify(result, null, 2));
5717
+ out(result);
5462
5718
  }
5463
- async function consolidate(opts) {
5719
+ async function cmdConsolidate(opts) {
5464
5720
  const body = {};
5465
5721
  if (opts.namespace)
5466
5722
  body.namespace = opts.namespace;
5467
- if (opts.minSimilarity)
5723
+ if (opts.minSimilarity != null && opts.minSimilarity !== true)
5468
5724
  body.min_similarity = parseFloat(opts.minSimilarity);
5469
5725
  if (opts.mode)
5470
5726
  body.mode = opts.mode;
5471
5727
  if (opts.dryRun !== undefined)
5472
5728
  body.dry_run = true;
5473
5729
  const result = await request("POST", "/v1/memories/consolidate", body);
5474
- console.log(JSON.stringify(result, null, 2));
5475
- }
5476
- async function createRelation(memoryId, targetId, relationType, opts) {
5477
- const body = { target_id: targetId, relation_type: relationType };
5478
- const result = await request("POST", `/v1/memories/${memoryId}/relations`, body);
5479
- console.log(JSON.stringify(result, null, 2));
5480
- }
5481
- async function listRelations(memoryId) {
5482
- const result = await request("GET", `/v1/memories/${memoryId}/relations`);
5483
- console.log(JSON.stringify(result, null, 2));
5730
+ if (outputJson) {
5731
+ out(result);
5732
+ } else {
5733
+ if (opts.dryRun) {
5734
+ info("Dry run no changes applied");
5735
+ }
5736
+ const merged = result.merged_count ?? result.merged ?? "?";
5737
+ success(`Consolidated: ${merged} memories merged`);
5738
+ if (result.clusters) {
5739
+ info(`Clusters found: ${result.clusters.length}`);
5740
+ }
5741
+ }
5484
5742
  }
5485
- async function deleteRelation(memoryId, relationId) {
5486
- const result = await request("DELETE", `/v1/memories/${memoryId}/relations/${relationId}`);
5487
- console.log(JSON.stringify(result, null, 2));
5743
+ async function cmdRelations(subcmd, rest, opts) {
5744
+ if (subcmd === "list") {
5745
+ if (!rest[0])
5746
+ throw new Error("Memory ID required");
5747
+ const result = await request("GET", `/v1/memories/${rest[0]}/relations`);
5748
+ if (outputJson) {
5749
+ out(result);
5750
+ } else {
5751
+ const relations = result.relations || [];
5752
+ if (relations.length === 0) {
5753
+ console.log(`${c.dim}No relations found.${c.reset}`);
5754
+ } else {
5755
+ const rows = relations.map((r) => ({
5756
+ id: r.id?.slice(0, 8) || "?",
5757
+ type: r.relation_type || "?",
5758
+ target: r.target_id?.slice(0, 8) || "?"
5759
+ }));
5760
+ table(rows, [
5761
+ { key: "id", label: "ID", width: 10 },
5762
+ { key: "type", label: "TYPE", width: 16 },
5763
+ { key: "target", label: "TARGET", width: 10 }
5764
+ ]);
5765
+ }
5766
+ }
5767
+ } else if (subcmd === "create") {
5768
+ if (!rest[0] || !rest[1] || !rest[2])
5769
+ throw new Error("Usage: relations create <memory-id> <target-id> <type>");
5770
+ const validTypes = ["related_to", "derived_from", "contradicts", "supersedes", "supports"];
5771
+ if (!validTypes.includes(rest[2])) {
5772
+ throw new Error(`Invalid relation type "${rest[2]}". Valid: ${validTypes.join(", ")}`);
5773
+ }
5774
+ const body = { target_id: rest[1], relation_type: rest[2] };
5775
+ const result = await request("POST", `/v1/memories/${rest[0]}/relations`, body);
5776
+ if (outputJson) {
5777
+ out(result);
5778
+ } else {
5779
+ success(`Relation created: ${rest[0].slice(0, 8)}… ${c.cyan}${rest[2]}${c.reset} → ${rest[1].slice(0, 8)}…`);
5780
+ }
5781
+ } else if (subcmd === "delete") {
5782
+ if (!rest[0] || !rest[1])
5783
+ throw new Error("Usage: relations delete <memory-id> <relation-id>");
5784
+ const result = await request("DELETE", `/v1/memories/${rest[0]}/relations/${rest[1]}`);
5785
+ if (outputJson) {
5786
+ out(result);
5787
+ } else {
5788
+ success("Relation deleted");
5789
+ }
5790
+ } else {
5791
+ throw new Error("Usage: relations [list|create|delete]");
5792
+ }
5488
5793
  }
5489
- async function status() {
5794
+ async function cmdStatus() {
5490
5795
  const walletAuth = await getWalletAuthHeader();
5491
5796
  const res = await fetch(`${API_URL}/v1/free-tier/status`, {
5492
5797
  headers: { "x-wallet-auth": walletAuth }
5493
5798
  });
5494
5799
  if (res.ok) {
5495
5800
  const data = await res.json();
5496
- console.log(`Wallet: ${data.wallet}`);
5497
- console.log(`Free tier: ${data.free_tier_remaining}/${data.free_tier_total} calls remaining`);
5498
- if (data.free_tier_remaining === 0) {
5499
- console.log("→ Next calls will use x402 payment ($0.001/call)");
5801
+ if (outputJson) {
5802
+ out(data);
5803
+ } else {
5804
+ console.log(`${c.bold}Wallet:${c.reset} ${data.wallet}`);
5805
+ const remaining = data.free_tier_remaining ?? 0;
5806
+ const total = data.free_tier_total ?? 1000;
5807
+ const pct = Math.round(remaining / total * 100);
5808
+ const barLen = 20;
5809
+ const filled = Math.round(remaining / total * barLen);
5810
+ const bar = `${c.green}${"█".repeat(filled)}${c.dim}${"░".repeat(barLen - filled)}${c.reset}`;
5811
+ console.log(`${c.bold}Free tier:${c.reset} ${remaining}/${total} calls remaining`);
5812
+ console.log(` ${bar} ${pct}%`);
5813
+ if (remaining === 0) {
5814
+ console.log(`${c.yellow}→ Next calls will use x402 payment ($0.001/call)${c.reset}`);
5815
+ }
5500
5816
  }
5501
5817
  } else {
5502
5818
  const err = await res.json();
5503
5819
  throw new Error(err.error?.message || "Failed to get status");
5504
5820
  }
5505
5821
  }
5506
- function printHelp() {
5507
- console.log(`MemoClaw CLI - Memory-as-a-Service for AI agents
5508
-
5509
- Usage:
5510
- memoclaw store "content" [options]
5511
- --importance <0-1> Importance score (default: 0.5)
5512
- --tags <tag1,tag2> Comma-separated tags
5513
- --namespace <name> Memory namespace
5514
-
5515
- memoclaw recall "query" [options]
5516
- --limit <n> Max results (default: 10)
5517
- --min-similarity <0-1> Similarity threshold (default: 0.5)
5518
- --namespace <name> Filter by namespace
5519
- --tags <tag1,tag2> Filter by tags
5520
- --raw Output raw JSON
5521
-
5522
- memoclaw list [options]
5523
- --limit <n> Max results (default: 20)
5524
- --offset <n> Pagination offset
5525
- --namespace <name> Filter by namespace
5526
-
5527
- memoclaw update <id> [options]
5528
- --content <text> New content
5529
- --importance <0-1> New importance score
5530
- --memory-type <type> New memory type
5531
- --namespace <name> New namespace
5532
- --tags <tag1,tag2> New tags
5533
- --expires-at <date> Expiry date (ISO 8601) or "null"
5534
- --pinned <true|false> Pin/unpin memory
5535
-
5536
- memoclaw delete <id>
5537
-
5538
- memoclaw ingest [options]
5539
- --text <text> Raw text to ingest (or pipe via stdin)
5540
- --namespace <name> Namespace for memories
5541
- --session-id <id> Session identifier
5542
- --agent-id <id> Agent identifier
5543
- --auto-relate <bool> Auto-create relations (default: true)
5544
-
5545
- memoclaw extract "text" [options]
5546
- --namespace <name> Namespace for memories
5547
- --session-id <id> Session identifier
5548
- --agent-id <id> Agent identifier
5549
-
5550
- memoclaw consolidate [options]
5551
- --namespace <name> Namespace to consolidate
5552
- --min-similarity <0-1> Similarity threshold for clustering
5553
- --mode <mode> Consolidation mode
5554
- --dry-run Preview without merging
5555
-
5556
- memoclaw relations list <memory-id>
5557
- memoclaw relations create <memory-id> <target-id> <type>
5558
- Types: related_to, derived_from, contradicts, supersedes, supports
5559
- memoclaw relations delete <memory-id> <relation-id>
5560
-
5561
- memoclaw suggested [options]
5562
- --limit <n> Max results (default: 10)
5563
- --namespace <name> Filter by namespace
5564
- --category <cat> Filter: stale|fresh|hot|decaying
5565
- --raw Output raw JSON
5566
-
5567
- memoclaw status
5568
- Check free tier remaining and wallet info
5822
+ async function cmdExport(opts) {
5823
+ const params = new URLSearchParams;
5824
+ if (opts.namespace)
5825
+ params.set("namespace", opts.namespace);
5826
+ params.set("limit", opts.limit || "1000");
5827
+ let offset = 0;
5828
+ const allMemories = [];
5829
+ const limit = parseInt(opts.limit || "1000");
5830
+ while (true) {
5831
+ params.set("offset", String(offset));
5832
+ const result = await request("GET", `/v1/memories?${params}`);
5833
+ const memories = result.memories || result.data || [];
5834
+ allMemories.push(...memories);
5835
+ if (memories.length < limit)
5836
+ break;
5837
+ offset += limit;
5838
+ if (!outputQuiet)
5839
+ process.stderr.write(`${c.dim}Fetched ${allMemories.length} memories...${c.reset}\r`);
5840
+ }
5841
+ const exportData = {
5842
+ version: 1,
5843
+ exported_at: new Date().toISOString(),
5844
+ count: allMemories.length,
5845
+ memories: allMemories
5846
+ };
5847
+ console.log(JSON.stringify(exportData, null, 2));
5848
+ if (!outputQuiet) {
5849
+ console.error(`${c.green}✓${c.reset} Exported ${allMemories.length} memories`);
5850
+ }
5851
+ }
5852
+ async function cmdImport(opts) {
5853
+ let jsonText;
5854
+ if (opts.file) {
5855
+ const fs = await import("fs");
5856
+ jsonText = fs.readFileSync(opts.file, "utf-8");
5857
+ } else {
5858
+ const stdin = await readStdin();
5859
+ if (!stdin)
5860
+ throw new Error("Provide --file <path> or pipe JSON via stdin");
5861
+ jsonText = stdin;
5862
+ }
5863
+ const data = JSON.parse(jsonText);
5864
+ const memories = data.memories || data;
5865
+ if (!Array.isArray(memories))
5866
+ throw new Error("Invalid format: expected { memories: [...] } or [...]");
5867
+ let imported = 0;
5868
+ let failed = 0;
5869
+ for (const mem of memories) {
5870
+ try {
5871
+ const body = { content: mem.content };
5872
+ if (mem.importance !== undefined)
5873
+ body.importance = mem.importance;
5874
+ if (mem.metadata)
5875
+ body.metadata = mem.metadata;
5876
+ if (mem.namespace || opts.namespace)
5877
+ body.namespace = mem.namespace || opts.namespace;
5878
+ await request("POST", "/v1/store", body);
5879
+ imported++;
5880
+ if (!outputQuiet) {
5881
+ process.stderr.write(`\r ${progressBar(imported, memories.length)}`);
5882
+ }
5883
+ } catch (e) {
5884
+ failed++;
5885
+ if (process.env.DEBUG)
5886
+ console.error(`Failed to import: ${e.message}`);
5887
+ }
5888
+ }
5889
+ if (!outputQuiet)
5890
+ process.stderr.write(`
5891
+ `);
5892
+ if (outputJson) {
5893
+ out({ imported, failed, total: memories.length });
5894
+ } else {
5895
+ success(`Imported ${imported}/${memories.length} memories${failed ? ` (${c.red}${failed} failed${c.reset})` : ""}`);
5896
+ }
5897
+ }
5898
+ async function cmdStats(opts) {
5899
+ const params = new URLSearchParams;
5900
+ if (opts.namespace)
5901
+ params.set("namespace", opts.namespace);
5902
+ params.set("limit", "1");
5903
+ const result = await request("GET", `/v1/memories?${params}`);
5904
+ const total = result.total ?? "?";
5905
+ const walletAuth = await getWalletAuthHeader();
5906
+ const statusRes = await fetch(`${API_URL}/v1/free-tier/status`, {
5907
+ headers: { "x-wallet-auth": walletAuth }
5908
+ });
5909
+ let tierData = {};
5910
+ if (statusRes.ok) {
5911
+ tierData = await statusRes.json();
5912
+ }
5913
+ if (outputJson) {
5914
+ out({
5915
+ total_memories: total,
5916
+ api_url: API_URL,
5917
+ wallet: tierData.wallet || getAccount().address,
5918
+ free_tier_remaining: tierData.free_tier_remaining,
5919
+ free_tier_total: tierData.free_tier_total
5920
+ });
5921
+ } else {
5922
+ console.log(`${c.bold}MemoClaw Stats${c.reset}`);
5923
+ console.log(`${c.dim}${"─".repeat(40)}${c.reset}`);
5924
+ console.log(`Memories: ${c.cyan}${total}${c.reset}`);
5925
+ console.log(`API: ${c.dim}${API_URL}${c.reset}`);
5926
+ console.log(`Wallet: ${c.dim}${tierData.wallet || getAccount().address}${c.reset}`);
5927
+ if (tierData.free_tier_remaining !== undefined) {
5928
+ console.log(`Free calls left: ${c.cyan}${tierData.free_tier_remaining}${c.reset}/${tierData.free_tier_total}`);
5929
+ }
5930
+ if (opts.namespace) {
5931
+ console.log(`Namespace: ${c.cyan}${opts.namespace}${c.reset}`);
5932
+ }
5933
+ }
5934
+ }
5935
+ async function cmdCompletions(shell) {
5936
+ const commands = [
5937
+ "store",
5938
+ "recall",
5939
+ "list",
5940
+ "get",
5941
+ "update",
5942
+ "delete",
5943
+ "ingest",
5944
+ "extract",
5945
+ "consolidate",
5946
+ "relations",
5947
+ "suggested",
5948
+ "status",
5949
+ "export",
5950
+ "import",
5951
+ "stats",
5952
+ "browse",
5953
+ "completions",
5954
+ "config"
5955
+ ];
5956
+ if (shell === "bash") {
5957
+ console.log(`# Add to ~/.bashrc:
5958
+ # eval "$(memoclaw completions bash)"
5959
+ _memoclaw() {
5960
+ local cur="\${COMP_WORDS[COMP_CWORD]}"
5961
+ local cmds="${commands.join(" ")}"
5962
+ if [ "$COMP_CWORD" -eq 1 ]; then
5963
+ COMPREPLY=( $(compgen -W "$cmds" -- "$cur") )
5964
+ fi
5965
+ }
5966
+ complete -F _memoclaw memoclaw`);
5967
+ } else if (shell === "zsh") {
5968
+ console.log(`# Add to ~/.zshrc:
5969
+ # eval "$(memoclaw completions zsh)"
5970
+ _memoclaw() {
5971
+ local -a commands=(${commands.map((c2) => `'${c2}'`).join(" ")})
5972
+ _describe 'command' commands
5973
+ }
5974
+ compdef _memoclaw memoclaw`);
5975
+ } else if (shell === "fish") {
5976
+ console.log(`# Add to ~/.config/fish/completions/memoclaw.fish:
5977
+ ${commands.map((cmd) => `complete -c memoclaw -n '__fish_use_subcommand' -a '${cmd}'`).join(`
5978
+ `)}`);
5979
+ } else {
5980
+ throw new Error(`Unknown shell: ${shell}. Supported: bash, zsh, fish`);
5981
+ }
5982
+ }
5983
+ async function cmdConfig(subcmd, rest) {
5984
+ if (subcmd === "show" || !subcmd) {
5985
+ const config = {
5986
+ MEMOCLAW_URL: API_URL,
5987
+ MEMOCLAW_PRIVATE_KEY: PRIVATE_KEY ? `${PRIVATE_KEY.slice(0, 6)}…${PRIVATE_KEY.slice(-4)}` : "(not set)",
5988
+ NO_COLOR: process.env.NO_COLOR || "(not set)",
5989
+ DEBUG: process.env.DEBUG || "(not set)"
5990
+ };
5991
+ if (outputJson) {
5992
+ out(config);
5993
+ } else {
5994
+ console.log(`${c.bold}MemoClaw Configuration${c.reset}`);
5995
+ console.log(`${c.dim}${"─".repeat(50)}${c.reset}`);
5996
+ for (const [key, val] of Object.entries(config)) {
5997
+ const isSet = !val.includes("not set");
5998
+ console.log(` ${c.cyan}${key.padEnd(24)}${c.reset} ${isSet ? val : `${c.dim}${val}${c.reset}`}`);
5999
+ }
6000
+ console.log(`
6001
+ ${c.dim}Set via environment variables or .env file${c.reset}`);
6002
+ }
6003
+ } else if (subcmd === "check") {
6004
+ const issues = [];
6005
+ if (!PRIVATE_KEY)
6006
+ issues.push("MEMOCLAW_PRIVATE_KEY is not set");
6007
+ else if (!PRIVATE_KEY.startsWith("0x"))
6008
+ issues.push("MEMOCLAW_PRIVATE_KEY should start with 0x");
6009
+ else if (PRIVATE_KEY.length !== 66)
6010
+ issues.push(`MEMOCLAW_PRIVATE_KEY has wrong length (${PRIVATE_KEY.length}, expected 66)`);
6011
+ if (outputJson) {
6012
+ out({ valid: issues.length === 0, issues });
6013
+ } else {
6014
+ if (issues.length === 0) {
6015
+ success("Configuration looks good!");
6016
+ try {
6017
+ const acct = getAccount();
6018
+ info(`Wallet address: ${acct.address}`);
6019
+ } catch {}
6020
+ } else {
6021
+ for (const issue of issues) {
6022
+ console.log(`${c.red}✗${c.reset} ${issue}`);
6023
+ }
6024
+ }
6025
+ }
6026
+ } else {
6027
+ throw new Error("Usage: config [show|check]");
6028
+ }
6029
+ }
6030
+ async function cmdBrowse(opts) {
6031
+ const readline = await import("readline");
6032
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
6033
+ const prompt = (q) => new Promise((r) => rl.question(q, r));
6034
+ console.log(`${c.bold}MemoClaw Interactive Browser${c.reset} ${c.dim}(type "help" or "q" to quit)${c.reset}`);
6035
+ if (opts.namespace)
6036
+ console.log(`${c.dim}Namespace: ${opts.namespace}${c.reset}`);
6037
+ console.log();
6038
+ let offset = 0;
6039
+ const limit = 10;
6040
+ while (true) {
6041
+ const input = (await prompt(`${c.cyan}memoclaw>${c.reset} `)).trim();
6042
+ if (!input)
6043
+ continue;
6044
+ if (input === "q" || input === "quit" || input === "exit")
6045
+ break;
6046
+ const parts = input.split(/\s+/);
6047
+ const browsCmd = parts[0];
6048
+ const browseArgs = parts.slice(1).join(" ");
6049
+ try {
6050
+ switch (browsCmd) {
6051
+ case "help":
6052
+ console.log(`${c.bold}Commands:${c.reset}
6053
+ list / ls List memories (paginated)
6054
+ next / n Next page
6055
+ prev / p Previous page
6056
+ get <id> Show memory details
6057
+ recall <query> Search memories
6058
+ store <content> Store a new memory
6059
+ delete <id> Delete a memory
6060
+ stats Show stats
6061
+ q / quit Exit browser`);
6062
+ break;
6063
+ case "list":
6064
+ case "ls": {
6065
+ offset = 0;
6066
+ const params = new URLSearchParams({ limit: String(limit), offset: String(offset) });
6067
+ if (opts.namespace)
6068
+ params.set("namespace", opts.namespace);
6069
+ const result = await request("GET", `/v1/memories?${params}`);
6070
+ const memories = result.memories || result.data || [];
6071
+ if (memories.length === 0) {
6072
+ console.log(`${c.dim}No memories.${c.reset}`);
6073
+ break;
6074
+ }
6075
+ for (const m of memories) {
6076
+ const text = m.content?.length > 60 ? m.content.slice(0, 60) + "…" : m.content || "";
6077
+ console.log(` ${c.cyan}${(m.id || "?").slice(0, 8)}${c.reset} ${text}`);
6078
+ }
6079
+ console.log(`${c.dim}─ showing ${offset + 1}-${offset + memories.length}${result.total ? ` of ${result.total}` : ""}${c.reset}`);
6080
+ break;
6081
+ }
6082
+ case "next":
6083
+ case "n":
6084
+ offset += limit;
6085
+ {
6086
+ const params = new URLSearchParams({ limit: String(limit), offset: String(offset) });
6087
+ if (opts.namespace)
6088
+ params.set("namespace", opts.namespace);
6089
+ const result = await request("GET", `/v1/memories?${params}`);
6090
+ const memories = result.memories || result.data || [];
6091
+ if (memories.length === 0) {
6092
+ console.log(`${c.dim}No more memories.${c.reset}`);
6093
+ offset = Math.max(0, offset - limit);
6094
+ break;
6095
+ }
6096
+ for (const m of memories) {
6097
+ const text = m.content?.length > 60 ? m.content.slice(0, 60) + "…" : m.content || "";
6098
+ console.log(` ${c.cyan}${(m.id || "?").slice(0, 8)}${c.reset} ${text}`);
6099
+ }
6100
+ console.log(`${c.dim}─ showing ${offset + 1}-${offset + memories.length}${result.total ? ` of ${result.total}` : ""}${c.reset}`);
6101
+ }
6102
+ break;
6103
+ case "prev":
6104
+ case "p":
6105
+ offset = Math.max(0, offset - limit);
6106
+ {
6107
+ const params = new URLSearchParams({ limit: String(limit), offset: String(offset) });
6108
+ if (opts.namespace)
6109
+ params.set("namespace", opts.namespace);
6110
+ const result = await request("GET", `/v1/memories?${params}`);
6111
+ const memories = result.memories || result.data || [];
6112
+ for (const m of memories) {
6113
+ const text = m.content?.length > 60 ? m.content.slice(0, 60) + "…" : m.content || "";
6114
+ console.log(` ${c.cyan}${(m.id || "?").slice(0, 8)}${c.reset} ${text}`);
6115
+ }
6116
+ console.log(`${c.dim}─ showing ${offset + 1}-${offset + memories.length}${result.total ? ` of ${result.total}` : ""}${c.reset}`);
6117
+ }
6118
+ break;
6119
+ case "get":
6120
+ if (!browseArgs) {
6121
+ console.log(`${c.red}Usage: get <id>${c.reset}`);
6122
+ break;
6123
+ }
6124
+ await cmdGet(browseArgs);
6125
+ break;
6126
+ case "recall":
6127
+ case "search":
6128
+ if (!browseArgs) {
6129
+ console.log(`${c.red}Usage: recall <query>${c.reset}`);
6130
+ break;
6131
+ }
6132
+ await cmdRecall(browseArgs, opts);
6133
+ break;
6134
+ case "store":
6135
+ if (!browseArgs) {
6136
+ console.log(`${c.red}Usage: store <content>${c.reset}`);
6137
+ break;
6138
+ }
6139
+ await cmdStore(browseArgs, opts);
6140
+ break;
6141
+ case "delete":
6142
+ case "rm":
6143
+ if (!browseArgs) {
6144
+ console.log(`${c.red}Usage: delete <id>${c.reset}`);
6145
+ break;
6146
+ }
6147
+ await cmdDelete(browseArgs);
6148
+ break;
6149
+ case "stats":
6150
+ await cmdStats(opts);
6151
+ break;
6152
+ default:
6153
+ console.log(`${c.dim}Unknown command. Type "help" for available commands.${c.reset}`);
6154
+ }
6155
+ } catch (e) {
6156
+ console.log(`${c.red}Error:${c.reset} ${e.message}`);
6157
+ }
6158
+ console.log();
6159
+ }
6160
+ rl.close();
6161
+ console.log(`${c.dim}Bye!${c.reset}`);
6162
+ }
6163
+ function printHelp(command) {
6164
+ if (command) {
6165
+ const subHelp = {
6166
+ store: `${c.bold}memoclaw store${c.reset} "content" [options]
6167
+
6168
+ Store a memory. Supports piping: ${c.dim}echo "content" | memoclaw store${c.reset}
6169
+
6170
+ Options:
6171
+ --importance <0-1> Importance score (default: 0.5)
6172
+ --tags <tag1,tag2> Comma-separated tags
6173
+ --namespace <name> Memory namespace`,
6174
+ recall: `${c.bold}memoclaw recall${c.reset} "query" [options]
6175
+
6176
+ Search memories by semantic similarity.
5569
6177
 
5570
6178
  Options:
5571
- --help, -h Show this help
5572
- --version, -v Show version
6179
+ --limit <n> Max results (default: 10)
6180
+ --min-similarity <0-1> Similarity threshold (default: 0.5)
6181
+ --namespace <name> Filter by namespace
6182
+ --tags <tag1,tag2> Filter by tags`,
6183
+ list: `${c.bold}memoclaw list${c.reset} [options]
6184
+
6185
+ List all memories in a table format.
5573
6186
 
5574
- Environment:
6187
+ Options:
6188
+ --limit <n> Max results (default: 20)
6189
+ --offset <n> Pagination offset
6190
+ --namespace <name> Filter by namespace`,
6191
+ export: `${c.bold}memoclaw export${c.reset} [options]
6192
+
6193
+ Export all memories as JSON. Useful for backups.
6194
+
6195
+ ${c.dim}memoclaw export > backup.json${c.reset}
6196
+ ${c.dim}memoclaw export --namespace project1 > project1.json${c.reset}
6197
+
6198
+ Options:
6199
+ --namespace <name> Filter by namespace
6200
+ --limit <n> Max per page (default: 1000)`,
6201
+ import: `${c.bold}memoclaw import${c.reset} [options]
6202
+
6203
+ Import memories from JSON file or stdin.
6204
+
6205
+ ${c.dim}memoclaw import --file backup.json${c.reset}
6206
+ ${c.dim}cat backup.json | memoclaw import${c.reset}
6207
+
6208
+ Options:
6209
+ --file <path> JSON file to import
6210
+ --namespace <name> Override namespace for all memories`,
6211
+ stats: `${c.bold}memoclaw stats${c.reset} [options]
6212
+
6213
+ Show memory statistics and account info.
6214
+
6215
+ Options:
6216
+ --namespace <name> Filter by namespace`,
6217
+ get: `${c.bold}memoclaw get${c.reset} <id>
6218
+
6219
+ Retrieve a single memory by its ID.`,
6220
+ config: `${c.bold}memoclaw config${c.reset} [show|check]
6221
+
6222
+ Show or validate your MemoClaw configuration.
6223
+
6224
+ Subcommands:
6225
+ show Display current configuration (default)
6226
+ check Validate configuration and test connectivity`,
6227
+ browse: `${c.bold}memoclaw browse${c.reset} [options]
6228
+
6229
+ Interactive memory browser (REPL). Explore, search, and manage
6230
+ memories in a persistent session.
6231
+
6232
+ Options:
6233
+ --namespace <name> Filter by namespace
6234
+
6235
+ Commands inside browser: list, get, recall, store, delete, stats, next, prev`,
6236
+ completions: `${c.bold}memoclaw completions${c.reset} <bash|zsh|fish>
6237
+
6238
+ Generate shell completion scripts.
6239
+
6240
+ ${c.dim}eval "$(memoclaw completions bash)"${c.reset}
6241
+ ${c.dim}eval "$(memoclaw completions zsh)"${c.reset}
6242
+ ${c.dim}memoclaw completions fish > ~/.config/fish/completions/memoclaw.fish${c.reset}`
6243
+ };
6244
+ if (subHelp[command]) {
6245
+ console.log(subHelp[command]);
6246
+ } else {
6247
+ console.log(`No detailed help for "${command}". Run ${c.dim}memoclaw --help${c.reset} for overview.`);
6248
+ }
6249
+ return;
6250
+ }
6251
+ console.log(`${c.bold}MemoClaw CLI${c.reset} ${c.dim}v${VERSION}${c.reset} — Memory-as-a-Service for AI agents
6252
+
6253
+ ${c.bold}Usage:${c.reset}
6254
+ memoclaw <command> [options]
6255
+
6256
+ ${c.bold}Commands:${c.reset}
6257
+ ${c.cyan}store${c.reset} "content" Store a memory (also accepts stdin)
6258
+ ${c.cyan}recall${c.reset} "query" Search memories by similarity
6259
+ ${c.cyan}list${c.reset} List memories in a table
6260
+ ${c.cyan}get${c.reset} <id> Get a single memory by ID
6261
+ ${c.cyan}update${c.reset} <id> Update a memory
6262
+ ${c.cyan}delete${c.reset} <id> Delete a memory
6263
+ ${c.cyan}ingest${c.reset} Ingest raw text into memories
6264
+ ${c.cyan}extract${c.reset} "text" Extract memories from text
6265
+ ${c.cyan}consolidate${c.reset} Merge similar memories
6266
+ ${c.cyan}relations${c.reset} <sub> Manage memory relations
6267
+ ${c.cyan}suggested${c.reset} Get suggested memories for review
6268
+ ${c.cyan}status${c.reset} Check account & free tier info
6269
+ ${c.cyan}stats${c.reset} Memory statistics
6270
+ ${c.cyan}export${c.reset} Export all memories as JSON
6271
+ ${c.cyan}import${c.reset} Import memories from JSON
6272
+ ${c.cyan}completions${c.reset} <shell> Generate shell completions
6273
+ ${c.cyan}browse${c.reset} Interactive memory browser (REPL)
6274
+ ${c.cyan}config${c.reset} [show|check] Show or validate configuration
6275
+
6276
+ ${c.bold}Global Options:${c.reset}
6277
+ -h, --help Show help (use with command for details)
6278
+ -v, --version Show version
6279
+ -j, --json Output as JSON (machine-readable)
6280
+ -q, --quiet Suppress non-essential output
6281
+ -n, --namespace <name> Filter/set namespace
6282
+ -l, --limit <n> Limit results
6283
+ -t, --tags <a,b> Comma-separated tags
6284
+ --raw Raw output (content only, for piping)
6285
+
6286
+ ${c.bold}Environment:${c.reset}
5575
6287
  MEMOCLAW_PRIVATE_KEY Wallet private key for auth + payments
5576
6288
  MEMOCLAW_URL API endpoint (default: https://api.memoclaw.com)
6289
+ NO_COLOR Disable colored output
6290
+ DEBUG Enable debug logging
5577
6291
 
5578
- Free Tier:
6292
+ ${c.bold}Piping:${c.reset}
6293
+ echo "meeting notes" | memoclaw store
6294
+ echo "long text" | memoclaw ingest
6295
+ memoclaw export | jq '.memories | length'
6296
+ cat backup.json | memoclaw import
6297
+
6298
+ ${c.bold}Free Tier:${c.reset}
5579
6299
  Every wallet gets 1000 free API calls. After that, x402
5580
6300
  micropayments kick in automatically ($0.001/call USDC on Base).
5581
6301
 
5582
- API: https://api.memoclaw.com`);
6302
+ ${c.dim}API: https://api.memoclaw.com${c.reset}`);
5583
6303
  }
5584
6304
  var args = parseArgs(process.argv.slice(2));
5585
6305
  var [cmd, ...rest] = args._;
6306
+ outputJson = !!args.json;
6307
+ outputQuiet = !!args.quiet;
5586
6308
  if (args.version) {
5587
- console.log("memoclaw 1.3.0");
6309
+ console.log(`memoclaw ${VERSION}`);
5588
6310
  process.exit(0);
5589
6311
  }
5590
- if (args.help || !cmd && args._.length === 0) {
6312
+ if (!cmd && args._.length === 0) {
5591
6313
  printHelp();
5592
6314
  process.exit(0);
5593
6315
  }
6316
+ if (args.help) {
6317
+ printHelp(cmd);
6318
+ process.exit(0);
6319
+ }
5594
6320
  try {
5595
6321
  switch (cmd) {
5596
- case "store":
5597
- if (!rest[0])
5598
- throw new Error("Content required");
5599
- await store(rest[0], args);
6322
+ case "store": {
6323
+ let content = rest[0];
6324
+ if (!content) {
6325
+ const stdin = await readStdin();
6326
+ if (stdin)
6327
+ content = stdin;
6328
+ }
6329
+ if (!content)
6330
+ throw new Error("Content required. Provide as argument or pipe via stdin.");
6331
+ await cmdStore(content, args);
5600
6332
  break;
6333
+ }
5601
6334
  case "recall":
5602
6335
  if (!rest[0])
5603
6336
  throw new Error("Query required");
5604
- await recall(rest[0], args);
6337
+ await cmdRecall(rest[0], args);
5605
6338
  break;
5606
6339
  case "list":
5607
- await list(args);
6340
+ await cmdList(args);
6341
+ break;
6342
+ case "get":
6343
+ if (!rest[0])
6344
+ throw new Error("Memory ID required");
6345
+ await cmdGet(rest[0]);
5608
6346
  break;
5609
6347
  case "update":
5610
6348
  if (!rest[0])
5611
6349
  throw new Error("Memory ID required");
5612
- await update(rest[0], args);
6350
+ await cmdUpdate(rest[0], args);
5613
6351
  break;
5614
6352
  case "delete":
5615
6353
  if (!rest[0])
5616
6354
  throw new Error("Memory ID required");
5617
- await deleteMemory(rest[0]);
6355
+ await cmdDelete(rest[0]);
5618
6356
  break;
5619
6357
  case "ingest":
5620
- await ingest(args);
6358
+ await cmdIngest(args);
5621
6359
  break;
5622
- case "extract":
5623
- if (!rest[0])
5624
- throw new Error("Text required");
5625
- await extract(rest[0], args);
6360
+ case "extract": {
6361
+ let text = rest[0];
6362
+ if (!text) {
6363
+ const stdin = await readStdin();
6364
+ if (stdin)
6365
+ text = stdin;
6366
+ }
6367
+ if (!text)
6368
+ throw new Error("Text required. Provide as argument or pipe via stdin.");
6369
+ await cmdExtract(text, args);
5626
6370
  break;
6371
+ }
5627
6372
  case "consolidate":
5628
- await consolidate(args);
6373
+ await cmdConsolidate(args);
5629
6374
  break;
5630
6375
  case "relations": {
5631
6376
  const subcmd = rest[0];
5632
- if (subcmd === "list") {
5633
- if (!rest[1])
5634
- throw new Error("Memory ID required");
5635
- await listRelations(rest[1]);
5636
- } else if (subcmd === "create") {
5637
- if (!rest[1] || !rest[2] || !rest[3])
5638
- throw new Error("Usage: relations create <memory-id> <target-id> <type>");
5639
- await createRelation(rest[1], rest[2], rest[3], args);
5640
- } else if (subcmd === "delete") {
5641
- if (!rest[1] || !rest[2])
5642
- throw new Error("Usage: relations delete <memory-id> <relation-id>");
5643
- await deleteRelation(rest[1], rest[2]);
5644
- } else {
6377
+ if (!subcmd)
5645
6378
  throw new Error("Usage: relations [list|create|delete]");
5646
- }
6379
+ await cmdRelations(subcmd, rest.slice(1), args);
5647
6380
  break;
5648
6381
  }
5649
6382
  case "suggested":
5650
- await suggested(args);
6383
+ await cmdSuggested(args);
5651
6384
  break;
5652
6385
  case "status":
5653
- await status();
6386
+ await cmdStatus();
6387
+ break;
6388
+ case "export":
6389
+ await cmdExport(args);
6390
+ break;
6391
+ case "import":
6392
+ await cmdImport(args);
6393
+ break;
6394
+ case "stats":
6395
+ await cmdStats(args);
6396
+ break;
6397
+ case "completions":
6398
+ if (!rest[0])
6399
+ throw new Error("Shell required: bash, zsh, or fish");
6400
+ await cmdCompletions(rest[0]);
6401
+ break;
6402
+ case "browse":
6403
+ await cmdBrowse(args);
6404
+ break;
6405
+ case "config":
6406
+ await cmdConfig(rest[0], rest.slice(1));
6407
+ break;
6408
+ case "help":
6409
+ printHelp(rest[0]);
5654
6410
  break;
5655
6411
  default:
5656
- console.error(`Unknown command: ${cmd}`);
5657
- console.error('Run "memoclaw --help" for usage.');
6412
+ console.error(`${c.red}Unknown command: ${cmd}${c.reset}`);
6413
+ console.error(`Run ${c.dim}memoclaw --help${c.reset} for usage.`);
5658
6414
  process.exit(1);
5659
6415
  }
5660
6416
  } catch (err) {
5661
- console.error("Error:", err.message);
6417
+ if (outputJson) {
6418
+ console.error(JSON.stringify({ error: err.message }));
6419
+ } else {
6420
+ console.error(`${c.red}Error:${c.reset} ${err.message}`);
6421
+ }
5662
6422
  process.exit(1);
5663
6423
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memoclaw",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "MemoClaw CLI - Memory-as-a-Service for AI agents. 1000 free calls, then x402 micropayments.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,6 +11,7 @@
11
11
  ],
12
12
  "scripts": {
13
13
  "build": "bun build src/cli.ts --outfile=dist/cli.mjs --target=node",
14
+ "test": "bun test",
14
15
  "prepublishOnly": "bun run build"
15
16
  },
16
17
  "keywords": [