memoclaw 1.5.0 → 1.7.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 +531 -63
  2. package/package.json +2 -1
package/dist/cli.mjs CHANGED
@@ -5208,8 +5208,127 @@ function privateKeyToAccount(privateKey, options = {}) {
5208
5208
  source: "privateKey"
5209
5209
  };
5210
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
+ "force",
5222
+ "count",
5223
+ "wide"
5224
+ ]);
5225
+ var SHORT_FLAGS = {
5226
+ "-h": "help",
5227
+ "-v": "version",
5228
+ "-j": "json",
5229
+ "-q": "quiet",
5230
+ "-n": "namespace",
5231
+ "-l": "limit",
5232
+ "-t": "tags",
5233
+ "-o": "output"
5234
+ };
5235
+ function parseArgs(args) {
5236
+ const result = { _: [] };
5237
+ let i = 0;
5238
+ while (i < args.length) {
5239
+ const arg = args[i];
5240
+ if (arg[0] === "-" && arg[1] !== "-" && arg.length >= 2) {
5241
+ if (arg.length === 2 && SHORT_FLAGS[arg]) {
5242
+ const key = SHORT_FLAGS[arg];
5243
+ if (BOOLEAN_FLAGS.has(key)) {
5244
+ result[key] = true;
5245
+ i++;
5246
+ } else {
5247
+ const next = args[i + 1];
5248
+ if (next !== undefined && !next.startsWith("-")) {
5249
+ result[key] = next;
5250
+ i += 2;
5251
+ } else {
5252
+ result[key] = true;
5253
+ i++;
5254
+ }
5255
+ }
5256
+ } else if (arg.length > 2 && !SHORT_FLAGS[arg]) {
5257
+ const chars = arg.slice(1).split("");
5258
+ let allValid = true;
5259
+ for (const ch of chars) {
5260
+ const flag = `-${ch}`;
5261
+ if (!SHORT_FLAGS[flag]) {
5262
+ allValid = false;
5263
+ break;
5264
+ }
5265
+ }
5266
+ if (allValid) {
5267
+ for (let ci = 0;ci < chars.length; ci++) {
5268
+ const flag = `-${chars[ci]}`;
5269
+ const key = SHORT_FLAGS[flag];
5270
+ if (BOOLEAN_FLAGS.has(key)) {
5271
+ result[key] = true;
5272
+ } else if (ci === chars.length - 1) {
5273
+ const next = args[i + 1];
5274
+ if (next !== undefined && !next.startsWith("-")) {
5275
+ result[key] = next;
5276
+ i++;
5277
+ } else {
5278
+ result[key] = true;
5279
+ }
5280
+ } else {
5281
+ result[key] = true;
5282
+ }
5283
+ }
5284
+ i++;
5285
+ } else {
5286
+ result._.push(arg);
5287
+ i++;
5288
+ }
5289
+ } else {
5290
+ result._.push(arg);
5291
+ i++;
5292
+ }
5293
+ } else if (arg === "--") {
5294
+ result._.push(...args.slice(i + 1));
5295
+ break;
5296
+ } else if (arg.startsWith("--")) {
5297
+ const eqIdx = arg.indexOf("=");
5298
+ let key;
5299
+ let inlineValue;
5300
+ if (eqIdx !== -1) {
5301
+ key = arg.slice(2, eqIdx).replace(/-([a-z])/g, (_, ch) => ch.toUpperCase());
5302
+ inlineValue = arg.slice(eqIdx + 1);
5303
+ } else {
5304
+ key = arg.slice(2).replace(/-([a-z])/g, (_, ch) => ch.toUpperCase());
5305
+ }
5306
+ if (inlineValue !== undefined) {
5307
+ result[key] = inlineValue;
5308
+ i++;
5309
+ } else if (BOOLEAN_FLAGS.has(key)) {
5310
+ result[key] = true;
5311
+ i++;
5312
+ } else {
5313
+ const next = args[i + 1];
5314
+ if (next !== undefined && (!next.startsWith("--") || /^--?\d/.test(next))) {
5315
+ result[key] = next;
5316
+ i += 2;
5317
+ } else {
5318
+ result[key] = true;
5319
+ i++;
5320
+ }
5321
+ }
5322
+ } else {
5323
+ result._.push(arg);
5324
+ i++;
5325
+ }
5326
+ }
5327
+ return result;
5328
+ }
5329
+
5211
5330
  // src/cli.ts
5212
- var VERSION = "1.5.0";
5331
+ var VERSION = "1.7.0";
5213
5332
  var API_URL = process.env.MEMOCLAW_URL || "https://api.memoclaw.com";
5214
5333
  var PRIVATE_KEY = process.env.MEMOCLAW_PRIVATE_KEY;
5215
5334
  var NO_COLOR = !!process.env.NO_COLOR || !process.stdout.isTTY;
@@ -5260,57 +5379,6 @@ async function getWalletAuthHeader() {
5260
5379
  const signature = await account.signMessage({ message });
5261
5380
  return `${account.address}:${timestamp}:${signature}`;
5262
5381
  }
5263
- var BOOLEAN_FLAGS = new Set([
5264
- "help",
5265
- "version",
5266
- "raw",
5267
- "json",
5268
- "quiet",
5269
- "dryRun",
5270
- "verbose"
5271
- ]);
5272
- function parseArgs(args) {
5273
- const result = { _: [] };
5274
- let i = 0;
5275
- while (i < args.length) {
5276
- const arg = args[i];
5277
- if (arg === "-h" || arg === "--help") {
5278
- result.help = true;
5279
- i++;
5280
- } else if (arg === "-v" || arg === "--version") {
5281
- result.version = true;
5282
- i++;
5283
- } else if (arg === "-q" || arg === "--quiet") {
5284
- result.quiet = true;
5285
- i++;
5286
- } else if (arg === "-j" || arg === "--json") {
5287
- result.json = true;
5288
- i++;
5289
- } else if (arg === "--") {
5290
- result._.push(...args.slice(i + 1));
5291
- break;
5292
- } else if (arg.startsWith("--")) {
5293
- const key = arg.slice(2).replace(/-([a-z])/g, (_, ch) => ch.toUpperCase());
5294
- if (BOOLEAN_FLAGS.has(key)) {
5295
- result[key] = true;
5296
- i++;
5297
- } else {
5298
- const next = args[i + 1];
5299
- if (next !== undefined && !next.startsWith("--")) {
5300
- result[key] = next;
5301
- i += 2;
5302
- } else {
5303
- result[key] = true;
5304
- i++;
5305
- }
5306
- }
5307
- } else {
5308
- result._.push(arg);
5309
- i++;
5310
- }
5311
- }
5312
- return result;
5313
- }
5314
5382
  async function readStdin() {
5315
5383
  if (process.stdin.isTTY)
5316
5384
  return null;
@@ -5374,6 +5442,11 @@ function table(rows, columns) {
5374
5442
  console.log(line);
5375
5443
  }
5376
5444
  }
5445
+ function progressBar(current, total, width = 30) {
5446
+ const pct = Math.min(current / total, 1);
5447
+ const filled = Math.round(pct * width);
5448
+ return `${c.green}${"█".repeat(filled)}${c.dim}${"░".repeat(width - filled)}${c.reset} ${current}/${total}`;
5449
+ }
5377
5450
  async function request(method, path, body = null) {
5378
5451
  const url = `${API_URL}${path}`;
5379
5452
  const headers = { "Content-Type": "application/json" };
@@ -5382,7 +5455,21 @@ async function request(method, path, body = null) {
5382
5455
  options.body = JSON.stringify(body);
5383
5456
  const walletAuth = await getWalletAuthHeader();
5384
5457
  headers["x-wallet-auth"] = walletAuth;
5385
- let res = await fetch(url, { ...options, headers });
5458
+ let res;
5459
+ try {
5460
+ res = await fetch(url, { ...options, headers });
5461
+ } catch (e) {
5462
+ if (e.code === "ECONNREFUSED" || e.cause?.code === "ECONNREFUSED") {
5463
+ throw new Error(`Cannot connect to ${API_URL} — is the server running?`);
5464
+ }
5465
+ if (e.code === "ENOTFOUND" || e.cause?.code === "ENOTFOUND") {
5466
+ throw new Error(`DNS lookup failed for ${API_URL} — check your internet connection`);
5467
+ }
5468
+ if (e.name === "AbortError") {
5469
+ throw new Error(`Request timed out`);
5470
+ }
5471
+ throw new Error(`Network error: ${e.message}`);
5472
+ }
5386
5473
  const freeTierRemaining = res.headers.get("x-free-tier-remaining");
5387
5474
  if (freeTierRemaining !== null && process.env.DEBUG) {
5388
5475
  console.error(`${c.dim}Free tier remaining: ${freeTierRemaining}${c.reset}`);
@@ -5426,7 +5513,7 @@ async function request(method, path, body = null) {
5426
5513
  }
5427
5514
  async function cmdStore(content, opts) {
5428
5515
  const body = { content };
5429
- if (opts.importance)
5516
+ if (opts.importance != null && opts.importance !== true)
5430
5517
  body.importance = parseFloat(opts.importance);
5431
5518
  if (opts.tags)
5432
5519
  body.metadata = { tags: opts.tags.split(",").map((t) => t.trim()) };
@@ -5443,9 +5530,9 @@ async function cmdStore(content, opts) {
5443
5530
  }
5444
5531
  async function cmdRecall(query, opts) {
5445
5532
  const body = { query };
5446
- if (opts.limit)
5533
+ if (opts.limit != null && opts.limit !== true)
5447
5534
  body.limit = parseInt(opts.limit);
5448
- if (opts.minSimilarity)
5535
+ if (opts.minSimilarity != null && opts.minSimilarity !== true)
5449
5536
  body.min_similarity = parseFloat(opts.minSimilarity);
5450
5537
  if (opts.namespace)
5451
5538
  body.namespace = opts.namespace;
@@ -5454,6 +5541,11 @@ async function cmdRecall(query, opts) {
5454
5541
  const result = await request("POST", "/v1/recall", body);
5455
5542
  if (outputJson) {
5456
5543
  out(result);
5544
+ } else if (opts.raw) {
5545
+ const memories = result.memories || [];
5546
+ for (const mem of memories) {
5547
+ console.log(mem.content);
5548
+ }
5457
5549
  } else {
5458
5550
  const memories = result.memories || [];
5459
5551
  if (memories.length === 0) {
@@ -5476,9 +5568,9 @@ async function cmdRecall(query, opts) {
5476
5568
  }
5477
5569
  async function cmdList(opts) {
5478
5570
  const params = new URLSearchParams;
5479
- if (opts.limit)
5571
+ if (opts.limit != null && opts.limit !== true)
5480
5572
  params.set("limit", opts.limit);
5481
- if (opts.offset)
5573
+ if (opts.offset != null && opts.offset !== true)
5482
5574
  params.set("offset", opts.offset);
5483
5575
  if (opts.namespace)
5484
5576
  params.set("namespace", opts.namespace);
@@ -5510,6 +5602,30 @@ async function cmdList(opts) {
5510
5602
  }
5511
5603
  }
5512
5604
  }
5605
+ async function cmdGet(id) {
5606
+ const result = await request("GET", `/v1/memories/${id}`);
5607
+ if (outputJson) {
5608
+ out(result);
5609
+ } else {
5610
+ const mem = result.memory || result;
5611
+ console.log(`${c.bold}ID:${c.reset} ${mem.id || id}`);
5612
+ console.log(`${c.bold}Content:${c.reset} ${mem.content}`);
5613
+ if (mem.importance !== undefined)
5614
+ console.log(`${c.bold}Importance:${c.reset} ${mem.importance}`);
5615
+ if (mem.namespace)
5616
+ console.log(`${c.bold}Namespace:${c.reset} ${mem.namespace}`);
5617
+ if (mem.metadata?.tags?.length)
5618
+ console.log(`${c.bold}Tags:${c.reset} ${mem.metadata.tags.join(", ")}`);
5619
+ if (mem.memory_type)
5620
+ console.log(`${c.bold}Type:${c.reset} ${mem.memory_type}`);
5621
+ if (mem.created_at)
5622
+ console.log(`${c.bold}Created:${c.reset} ${new Date(mem.created_at).toLocaleString()}`);
5623
+ if (mem.updated_at)
5624
+ console.log(`${c.bold}Updated:${c.reset} ${new Date(mem.updated_at).toLocaleString()}`);
5625
+ if (mem.pinned)
5626
+ console.log(`${c.bold}Pinned:${c.reset} ${c.green}yes${c.reset}`);
5627
+ }
5628
+ }
5513
5629
  async function cmdDelete(id) {
5514
5630
  const result = await request("DELETE", `/v1/memories/${id}`);
5515
5631
  if (outputJson) {
@@ -5520,7 +5636,7 @@ async function cmdDelete(id) {
5520
5636
  }
5521
5637
  async function cmdSuggested(opts) {
5522
5638
  const params = new URLSearchParams;
5523
- if (opts.limit)
5639
+ if (opts.limit != null && opts.limit !== true)
5524
5640
  params.set("limit", opts.limit);
5525
5641
  if (opts.namespace)
5526
5642
  params.set("namespace", opts.namespace);
@@ -5555,7 +5671,7 @@ async function cmdUpdate(id, opts) {
5555
5671
  const body = {};
5556
5672
  if (opts.content)
5557
5673
  body.content = opts.content;
5558
- if (opts.importance)
5674
+ if (opts.importance != null && opts.importance !== true)
5559
5675
  body.importance = parseFloat(opts.importance);
5560
5676
  if (opts.memoryType)
5561
5677
  body.memory_type = opts.memoryType;
@@ -5621,7 +5737,7 @@ async function cmdConsolidate(opts) {
5621
5737
  const body = {};
5622
5738
  if (opts.namespace)
5623
5739
  body.namespace = opts.namespace;
5624
- if (opts.minSimilarity)
5740
+ if (opts.minSimilarity != null && opts.minSimilarity !== true)
5625
5741
  body.min_similarity = parseFloat(opts.minSimilarity);
5626
5742
  if (opts.mode)
5627
5743
  body.mode = opts.mode;
@@ -5779,7 +5895,7 @@ async function cmdImport(opts) {
5779
5895
  await request("POST", "/v1/store", body);
5780
5896
  imported++;
5781
5897
  if (!outputQuiet) {
5782
- process.stderr.write(`${c.dim}Imported ${imported}/${memories.length}...${c.reset}\r`);
5898
+ process.stderr.write(`\r ${progressBar(imported, memories.length)}`);
5783
5899
  }
5784
5900
  } catch (e) {
5785
5901
  failed++;
@@ -5833,11 +5949,99 @@ async function cmdStats(opts) {
5833
5949
  }
5834
5950
  }
5835
5951
  }
5952
+ async function cmdGraph(id, opts) {
5953
+ const result = await request("GET", `/v1/memories/${id}`);
5954
+ const mem = result.memory || result;
5955
+ const relResult = await request("GET", `/v1/memories/${id}/relations`);
5956
+ const relations = relResult.relations || [];
5957
+ if (outputJson) {
5958
+ out({ memory: mem, relations });
5959
+ return;
5960
+ }
5961
+ const label = (m) => {
5962
+ const text = (m.content || "").slice(0, 40);
5963
+ return text.length < (m.content || "").length ? text + "…" : text;
5964
+ };
5965
+ const shortId = (s) => s?.slice(0, 8) || "?";
5966
+ console.log();
5967
+ console.log(` ${c.bold}${c.cyan}[${shortId(mem.id)}]${c.reset} ${label(mem)}`);
5968
+ if (relations.length === 0) {
5969
+ console.log(` ${c.dim} └── (no relations)${c.reset}`);
5970
+ } else {
5971
+ for (let i = 0;i < relations.length; i++) {
5972
+ const r = relations[i];
5973
+ const isLast = i === relations.length - 1;
5974
+ const branch = isLast ? "└" : "├";
5975
+ const typeColor = {
5976
+ contradicts: c.red,
5977
+ supersedes: c.yellow,
5978
+ supports: c.green,
5979
+ derived_from: c.magenta,
5980
+ related_to: c.blue
5981
+ }[r.relation_type] || c.dim;
5982
+ console.log(` ${c.dim} ${branch}──${c.reset} ${typeColor}${r.relation_type}${c.reset} ${c.dim}→${c.reset} ${c.cyan}[${shortId(r.target_id)}]${c.reset}`);
5983
+ }
5984
+ }
5985
+ console.log();
5986
+ }
5987
+ async function cmdPurge(opts) {
5988
+ if (!opts.force) {
5989
+ if (!process.stdin.isTTY) {
5990
+ throw new Error("Use --force to confirm purge in non-interactive mode");
5991
+ }
5992
+ const readline = await import("readline");
5993
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
5994
+ const answer = await new Promise((r) => rl.question(`${c.red}⚠ Delete ALL memories${opts.namespace ? ` in namespace "${opts.namespace}"` : ""}? Type "yes" to confirm: ${c.reset}`, r));
5995
+ rl.close();
5996
+ if (answer.trim().toLowerCase() !== "yes") {
5997
+ console.log(`${c.dim}Aborted.${c.reset}`);
5998
+ return;
5999
+ }
6000
+ }
6001
+ const params = new URLSearchParams({ limit: "100" });
6002
+ if (opts.namespace)
6003
+ params.set("namespace", opts.namespace);
6004
+ let deleted = 0;
6005
+ while (true) {
6006
+ params.set("offset", "0");
6007
+ const result = await request("GET", `/v1/memories?${params}`);
6008
+ const memories = result.memories || result.data || [];
6009
+ if (memories.length === 0)
6010
+ break;
6011
+ for (const mem of memories) {
6012
+ await request("DELETE", `/v1/memories/${mem.id}`);
6013
+ deleted++;
6014
+ if (!outputQuiet)
6015
+ process.stderr.write(`\r ${progressBar(deleted, result.total || deleted)}`);
6016
+ }
6017
+ }
6018
+ if (!outputQuiet)
6019
+ process.stderr.write(`
6020
+ `);
6021
+ if (outputJson) {
6022
+ out({ deleted });
6023
+ } else {
6024
+ success(`Purged ${deleted} memories`);
6025
+ }
6026
+ }
6027
+ async function cmdCount(opts) {
6028
+ const params = new URLSearchParams({ limit: "1" });
6029
+ if (opts.namespace)
6030
+ params.set("namespace", opts.namespace);
6031
+ const result = await request("GET", `/v1/memories?${params}`);
6032
+ const total = result.total ?? "?";
6033
+ if (outputJson) {
6034
+ out({ count: total, namespace: opts.namespace || null });
6035
+ } else {
6036
+ console.log(String(total));
6037
+ }
6038
+ }
5836
6039
  async function cmdCompletions(shell) {
5837
6040
  const commands = [
5838
6041
  "store",
5839
6042
  "recall",
5840
6043
  "list",
6044
+ "get",
5841
6045
  "update",
5842
6046
  "delete",
5843
6047
  "ingest",
@@ -5849,7 +6053,12 @@ async function cmdCompletions(shell) {
5849
6053
  "export",
5850
6054
  "import",
5851
6055
  "stats",
5852
- "completions"
6056
+ "browse",
6057
+ "completions",
6058
+ "config",
6059
+ "graph",
6060
+ "purge",
6061
+ "count"
5853
6062
  ];
5854
6063
  if (shell === "bash") {
5855
6064
  console.log(`# Add to ~/.bashrc:
@@ -5878,6 +6087,186 @@ ${commands.map((cmd) => `complete -c memoclaw -n '__fish_use_subcommand' -a '${c
5878
6087
  throw new Error(`Unknown shell: ${shell}. Supported: bash, zsh, fish`);
5879
6088
  }
5880
6089
  }
6090
+ async function cmdConfig(subcmd, rest) {
6091
+ if (subcmd === "show" || !subcmd) {
6092
+ const config = {
6093
+ MEMOCLAW_URL: API_URL,
6094
+ MEMOCLAW_PRIVATE_KEY: PRIVATE_KEY ? `${PRIVATE_KEY.slice(0, 6)}…${PRIVATE_KEY.slice(-4)}` : "(not set)",
6095
+ NO_COLOR: process.env.NO_COLOR || "(not set)",
6096
+ DEBUG: process.env.DEBUG || "(not set)"
6097
+ };
6098
+ if (outputJson) {
6099
+ out(config);
6100
+ } else {
6101
+ console.log(`${c.bold}MemoClaw Configuration${c.reset}`);
6102
+ console.log(`${c.dim}${"─".repeat(50)}${c.reset}`);
6103
+ for (const [key, val] of Object.entries(config)) {
6104
+ const isSet = !val.includes("not set");
6105
+ console.log(` ${c.cyan}${key.padEnd(24)}${c.reset} ${isSet ? val : `${c.dim}${val}${c.reset}`}`);
6106
+ }
6107
+ console.log(`
6108
+ ${c.dim}Set via environment variables or .env file${c.reset}`);
6109
+ }
6110
+ } else if (subcmd === "check") {
6111
+ const issues = [];
6112
+ if (!PRIVATE_KEY)
6113
+ issues.push("MEMOCLAW_PRIVATE_KEY is not set");
6114
+ else if (!PRIVATE_KEY.startsWith("0x"))
6115
+ issues.push("MEMOCLAW_PRIVATE_KEY should start with 0x");
6116
+ else if (PRIVATE_KEY.length !== 66)
6117
+ issues.push(`MEMOCLAW_PRIVATE_KEY has wrong length (${PRIVATE_KEY.length}, expected 66)`);
6118
+ if (outputJson) {
6119
+ out({ valid: issues.length === 0, issues });
6120
+ } else {
6121
+ if (issues.length === 0) {
6122
+ success("Configuration looks good!");
6123
+ try {
6124
+ const acct = getAccount();
6125
+ info(`Wallet address: ${acct.address}`);
6126
+ } catch {}
6127
+ } else {
6128
+ for (const issue of issues) {
6129
+ console.log(`${c.red}✗${c.reset} ${issue}`);
6130
+ }
6131
+ }
6132
+ }
6133
+ } else {
6134
+ throw new Error("Usage: config [show|check]");
6135
+ }
6136
+ }
6137
+ async function cmdBrowse(opts) {
6138
+ const readline = await import("readline");
6139
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
6140
+ const prompt = (q) => new Promise((r) => rl.question(q, r));
6141
+ console.log(`${c.bold}MemoClaw Interactive Browser${c.reset} ${c.dim}(type "help" or "q" to quit)${c.reset}`);
6142
+ if (opts.namespace)
6143
+ console.log(`${c.dim}Namespace: ${opts.namespace}${c.reset}`);
6144
+ console.log();
6145
+ let offset = 0;
6146
+ const limit = 10;
6147
+ while (true) {
6148
+ const input = (await prompt(`${c.cyan}memoclaw>${c.reset} `)).trim();
6149
+ if (!input)
6150
+ continue;
6151
+ if (input === "q" || input === "quit" || input === "exit")
6152
+ break;
6153
+ const parts = input.split(/\s+/);
6154
+ const browsCmd = parts[0];
6155
+ const browseArgs = parts.slice(1).join(" ");
6156
+ try {
6157
+ switch (browsCmd) {
6158
+ case "help":
6159
+ console.log(`${c.bold}Commands:${c.reset}
6160
+ list / ls List memories (paginated)
6161
+ next / n Next page
6162
+ prev / p Previous page
6163
+ get <id> Show memory details
6164
+ recall <query> Search memories
6165
+ store <content> Store a new memory
6166
+ delete <id> Delete a memory
6167
+ stats Show stats
6168
+ q / quit Exit browser`);
6169
+ break;
6170
+ case "list":
6171
+ case "ls": {
6172
+ offset = 0;
6173
+ const params = new URLSearchParams({ limit: String(limit), offset: String(offset) });
6174
+ if (opts.namespace)
6175
+ params.set("namespace", opts.namespace);
6176
+ const result = await request("GET", `/v1/memories?${params}`);
6177
+ const memories = result.memories || result.data || [];
6178
+ if (memories.length === 0) {
6179
+ console.log(`${c.dim}No memories.${c.reset}`);
6180
+ break;
6181
+ }
6182
+ for (const m of memories) {
6183
+ const text = m.content?.length > 60 ? m.content.slice(0, 60) + "…" : m.content || "";
6184
+ console.log(` ${c.cyan}${(m.id || "?").slice(0, 8)}${c.reset} ${text}`);
6185
+ }
6186
+ console.log(`${c.dim}─ showing ${offset + 1}-${offset + memories.length}${result.total ? ` of ${result.total}` : ""}${c.reset}`);
6187
+ break;
6188
+ }
6189
+ case "next":
6190
+ case "n":
6191
+ offset += limit;
6192
+ {
6193
+ const params = new URLSearchParams({ limit: String(limit), offset: String(offset) });
6194
+ if (opts.namespace)
6195
+ params.set("namespace", opts.namespace);
6196
+ const result = await request("GET", `/v1/memories?${params}`);
6197
+ const memories = result.memories || result.data || [];
6198
+ if (memories.length === 0) {
6199
+ console.log(`${c.dim}No more memories.${c.reset}`);
6200
+ offset = Math.max(0, offset - limit);
6201
+ break;
6202
+ }
6203
+ for (const m of memories) {
6204
+ const text = m.content?.length > 60 ? m.content.slice(0, 60) + "…" : m.content || "";
6205
+ console.log(` ${c.cyan}${(m.id || "?").slice(0, 8)}${c.reset} ${text}`);
6206
+ }
6207
+ console.log(`${c.dim}─ showing ${offset + 1}-${offset + memories.length}${result.total ? ` of ${result.total}` : ""}${c.reset}`);
6208
+ }
6209
+ break;
6210
+ case "prev":
6211
+ case "p":
6212
+ offset = Math.max(0, offset - limit);
6213
+ {
6214
+ const params = new URLSearchParams({ limit: String(limit), offset: String(offset) });
6215
+ if (opts.namespace)
6216
+ params.set("namespace", opts.namespace);
6217
+ const result = await request("GET", `/v1/memories?${params}`);
6218
+ const memories = result.memories || result.data || [];
6219
+ for (const m of memories) {
6220
+ const text = m.content?.length > 60 ? m.content.slice(0, 60) + "…" : m.content || "";
6221
+ console.log(` ${c.cyan}${(m.id || "?").slice(0, 8)}${c.reset} ${text}`);
6222
+ }
6223
+ console.log(`${c.dim}─ showing ${offset + 1}-${offset + memories.length}${result.total ? ` of ${result.total}` : ""}${c.reset}`);
6224
+ }
6225
+ break;
6226
+ case "get":
6227
+ if (!browseArgs) {
6228
+ console.log(`${c.red}Usage: get <id>${c.reset}`);
6229
+ break;
6230
+ }
6231
+ await cmdGet(browseArgs);
6232
+ break;
6233
+ case "recall":
6234
+ case "search":
6235
+ if (!browseArgs) {
6236
+ console.log(`${c.red}Usage: recall <query>${c.reset}`);
6237
+ break;
6238
+ }
6239
+ await cmdRecall(browseArgs, opts);
6240
+ break;
6241
+ case "store":
6242
+ if (!browseArgs) {
6243
+ console.log(`${c.red}Usage: store <content>${c.reset}`);
6244
+ break;
6245
+ }
6246
+ await cmdStore(browseArgs, opts);
6247
+ break;
6248
+ case "delete":
6249
+ case "rm":
6250
+ if (!browseArgs) {
6251
+ console.log(`${c.red}Usage: delete <id>${c.reset}`);
6252
+ break;
6253
+ }
6254
+ await cmdDelete(browseArgs);
6255
+ break;
6256
+ case "stats":
6257
+ await cmdStats(opts);
6258
+ break;
6259
+ default:
6260
+ console.log(`${c.dim}Unknown command. Type "help" for available commands.${c.reset}`);
6261
+ }
6262
+ } catch (e) {
6263
+ console.log(`${c.red}Error:${c.reset} ${e.message}`);
6264
+ }
6265
+ console.log();
6266
+ }
6267
+ rl.close();
6268
+ console.log(`${c.dim}Bye!${c.reset}`);
6269
+ }
5881
6270
  function printHelp(command) {
5882
6271
  if (command) {
5883
6272
  const subHelp = {
@@ -5932,6 +6321,50 @@ Show memory statistics and account info.
5932
6321
 
5933
6322
  Options:
5934
6323
  --namespace <name> Filter by namespace`,
6324
+ get: `${c.bold}memoclaw get${c.reset} <id>
6325
+
6326
+ Retrieve a single memory by its ID.`,
6327
+ config: `${c.bold}memoclaw config${c.reset} [show|check]
6328
+
6329
+ Show or validate your MemoClaw configuration.
6330
+
6331
+ Subcommands:
6332
+ show Display current configuration (default)
6333
+ check Validate configuration and test connectivity`,
6334
+ browse: `${c.bold}memoclaw browse${c.reset} [options]
6335
+
6336
+ Interactive memory browser (REPL). Explore, search, and manage
6337
+ memories in a persistent session.
6338
+
6339
+ Options:
6340
+ --namespace <name> Filter by namespace
6341
+
6342
+ Commands inside browser: list, get, recall, store, delete, stats, next, prev`,
6343
+ graph: `${c.bold}memoclaw graph${c.reset} <id>
6344
+
6345
+ Show an ASCII tree of a memory and its relations.
6346
+
6347
+ Options:
6348
+ --json Output as JSON`,
6349
+ purge: `${c.bold}memoclaw purge${c.reset} [options]
6350
+
6351
+ Delete ALL memories. Requires confirmation or --force.
6352
+
6353
+ ${c.dim}memoclaw purge --force${c.reset}
6354
+ ${c.dim}memoclaw purge --namespace old-project --force${c.reset}
6355
+
6356
+ Options:
6357
+ --force Skip confirmation prompt
6358
+ --namespace <name> Only purge memories in namespace`,
6359
+ count: `${c.bold}memoclaw count${c.reset} [options]
6360
+
6361
+ Print the total number of memories (pipe-friendly).
6362
+
6363
+ ${c.dim}memoclaw count${c.reset}
6364
+ ${c.dim}memoclaw count --namespace project1${c.reset}
6365
+
6366
+ Options:
6367
+ --namespace <name> Count only in namespace`,
5935
6368
  completions: `${c.bold}memoclaw completions${c.reset} <bash|zsh|fish>
5936
6369
 
5937
6370
  Generate shell completion scripts.
@@ -5956,6 +6389,7 @@ ${c.bold}Commands:${c.reset}
5956
6389
  ${c.cyan}store${c.reset} "content" Store a memory (also accepts stdin)
5957
6390
  ${c.cyan}recall${c.reset} "query" Search memories by similarity
5958
6391
  ${c.cyan}list${c.reset} List memories in a table
6392
+ ${c.cyan}get${c.reset} <id> Get a single memory by ID
5959
6393
  ${c.cyan}update${c.reset} <id> Update a memory
5960
6394
  ${c.cyan}delete${c.reset} <id> Delete a memory
5961
6395
  ${c.cyan}ingest${c.reset} Ingest raw text into memories
@@ -5968,12 +6402,23 @@ ${c.bold}Commands:${c.reset}
5968
6402
  ${c.cyan}export${c.reset} Export all memories as JSON
5969
6403
  ${c.cyan}import${c.reset} Import memories from JSON
5970
6404
  ${c.cyan}completions${c.reset} <shell> Generate shell completions
6405
+ ${c.cyan}browse${c.reset} Interactive memory browser (REPL)
6406
+ ${c.cyan}config${c.reset} [show|check] Show or validate configuration
6407
+ ${c.cyan}graph${c.reset} <id> ASCII visualization of memory relations
6408
+ ${c.cyan}purge${c.reset} Delete ALL memories (requires --force or confirm)
6409
+ ${c.cyan}count${c.reset} Quick memory count
5971
6410
 
5972
6411
  ${c.bold}Global Options:${c.reset}
5973
6412
  -h, --help Show help (use with command for details)
5974
6413
  -v, --version Show version
5975
6414
  -j, --json Output as JSON (machine-readable)
5976
6415
  -q, --quiet Suppress non-essential output
6416
+ -n, --namespace <name> Filter/set namespace
6417
+ -l, --limit <n> Limit results
6418
+ -t, --tags <a,b> Comma-separated tags
6419
+ --raw Raw output (content only, for piping)
6420
+ --force Skip confirmation prompts
6421
+ --timeout <seconds> Request timeout (default: 30)
5977
6422
 
5978
6423
  ${c.bold}Environment:${c.reset}
5979
6424
  MEMOCLAW_PRIVATE_KEY Wallet private key for auth + payments
@@ -6009,6 +6454,7 @@ if (args.help) {
6009
6454
  printHelp(cmd);
6010
6455
  process.exit(0);
6011
6456
  }
6457
+ var TIMEOUT_MS = args.timeout ? parseInt(args.timeout) * 1000 : 30000;
6012
6458
  try {
6013
6459
  switch (cmd) {
6014
6460
  case "store": {
@@ -6031,6 +6477,11 @@ try {
6031
6477
  case "list":
6032
6478
  await cmdList(args);
6033
6479
  break;
6480
+ case "get":
6481
+ if (!rest[0])
6482
+ throw new Error("Memory ID required");
6483
+ await cmdGet(rest[0]);
6484
+ break;
6034
6485
  case "update":
6035
6486
  if (!rest[0])
6036
6487
  throw new Error("Memory ID required");
@@ -6086,6 +6537,23 @@ try {
6086
6537
  throw new Error("Shell required: bash, zsh, or fish");
6087
6538
  await cmdCompletions(rest[0]);
6088
6539
  break;
6540
+ case "browse":
6541
+ await cmdBrowse(args);
6542
+ break;
6543
+ case "config":
6544
+ await cmdConfig(rest[0], rest.slice(1));
6545
+ break;
6546
+ case "graph":
6547
+ if (!rest[0])
6548
+ throw new Error("Memory ID required");
6549
+ await cmdGraph(rest[0], args);
6550
+ break;
6551
+ case "purge":
6552
+ await cmdPurge(args);
6553
+ break;
6554
+ case "count":
6555
+ await cmdCount(args);
6556
+ break;
6089
6557
  case "help":
6090
6558
  printHelp(rest[0]);
6091
6559
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memoclaw",
3
- "version": "1.5.0",
3
+ "version": "1.7.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": [