@memstack/cli 0.8.0 → 0.8.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @memstack/cli
2
2
 
3
- CLI for MemStack — shell-based agent memory. Use from bash scripts, opencode/Claude Code shell tools, CI pipelines, or any subprocess-capable agent.
3
+ CLI for MemStack — shell-based agent memory. Connect Claude Code and Codex to one shared memory per project, or use it from bash scripts, CI pipelines, or any subprocess-capable agent.
4
4
 
5
5
  ## Installation
6
6
 
@@ -10,9 +10,41 @@ npm install -g @memstack/cli
10
10
  npx @memstack/cli [command]
11
11
  ```
12
12
 
13
+ ## Claude Code and Codex
14
+
15
+ Give both agents one persistent memory per project. MemStack never installs
16
+ storage drivers; install the one for your store with the MCP server:
17
+
18
+ ```bash
19
+ npm install -g @memstack/cli @memstack/mcp better-sqlite3@^11.10.0
20
+ memstack init # LLM provider + store → ~/.memstack/config.json
21
+ memstack connect claude-code
22
+ memstack connect codex # then approve the MemStack hook once in Codex with /hooks
23
+ ```
24
+
25
+ | Command | What it does |
26
+ |---|---|
27
+ | `init` | Choose an LLM provider and store; verifies the key with a real request. Non-interactive: `--provider openai-compatible\|anthropic --base-url <url> --model <name> --api-key-env <VAR> --store <type> [--path <file> \| --url <url>] --yes`. |
28
+ | `connect <claude-code\|codex>` | Registers your installed `memstack-mcp`, adds a session-start hook that loads project memories, and for Codex a marked block in `~/.codex/AGENTS.md`. Checks the server first and undoes everything if a step fails. `--dry-run`, `--no-hooks`, `--no-agents-md`. |
29
+ | `disconnect <claude-code\|codex>` | Removes everything `connect` added, restoring each file exactly. Memories are kept. |
30
+ | `status` | Config, storage, the current project, and each agent's connection, hook, and guidance. |
31
+ | `doctor` | Diagnoses problems and prints the fix for each. `--live` tests the LLM key. Exits non-zero when it finds a problem. |
32
+ | `memories [query]` | Lists or searches the current project's memories. `--global`, `--limit <n>`, `--delete <id>`. |
33
+ | `project` | Shows the current project ID. `project pin <id>` writes `.memstack.json`; `project merge <old-id>` moves memories from an old ID. |
34
+
35
+ These commands print readable text, not JSON, and read
36
+ `~/.memstack/config.json` (or `$MEMSTACK_HOME/config.json`) overlaid with the
37
+ environment variables below. Projects are identified by the repository's
38
+ first commit, so clones, worktrees, and renamed remotes share memories. See
39
+ [Harness Memory](https://github.com/isiomaC/memstack#harness-memory-claude-code--codex)
40
+ for details.
41
+
13
42
  ## Configuration
14
43
 
15
- All via environment variables. Same scheme as @memstack/mcp.
44
+ Every command reads `~/.memstack/config.json` (written by `memstack init`),
45
+ overlaid with these environment variables: any LLM variable replaces the
46
+ file's LLM settings, and `MEMSTACK_STORAGE` replaces its storage settings.
47
+ `memstack status` shows which source is in use. Same scheme as @memstack/mcp.
16
48
 
17
49
  | Variable | Purpose | Default |
18
50
  |---|---|---|
@@ -29,7 +61,18 @@ All via environment variables. Same scheme as @memstack/mcp.
29
61
 
30
62
  ## Commands
31
63
 
32
- All output is JSON to stdout. Errors go to stderr.
64
+ The memory commands below print JSON to stdout. Errors go to stderr.
65
+ `memstack <command> --help` prints a command's flags; `memstack --version`
66
+ prints the installed version.
67
+
68
+ Inside a code repository, `--project` can replace `--actor` on any of them. It
69
+ uses the repository's project, the same memory Claude Code and Codex share
70
+ through `memstack connect`:
71
+
72
+ ```bash
73
+ memstack store --project --content "Deploy with fly deploy" --type instruction
74
+ memstack retrieve --project --query "deploy"
75
+ ```
33
76
 
34
77
  ### store
35
78
  ```bash
package/dist/cli.js CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { parseArgs } from 'util';
3
3
  import { readFile, writeFile } from 'fs/promises';
4
- import { MemStack, HarnessMemory, projectNamespace, defaultRecallNamespaces, AnthropicLLMAdapter, OpenAILLMAdapter, OpenAIEmbeddingAdapter, RedisStorageAdapter, SQLiteStorageAdapter, PostgresStorageAdapter, DiskStorageAdapter, MarkdownStorageAdapter, InMemoryStorageAdapter } from '@memstack/core';
5
- import { existsSync, mkdirSync, realpathSync, readFileSync, writeFileSync, statSync, chmodSync, renameSync, rmSync, copyFileSync, constants } from 'fs';
4
+ import { createRequire } from 'module';
5
+ import { projectNamespace, MemStack, HarnessMemory, defaultRecallNamespaces, AnthropicLLMAdapter, OpenAILLMAdapter, OpenAIEmbeddingAdapter, RedisStorageAdapter, SQLiteStorageAdapter, PostgresStorageAdapter, DiskStorageAdapter, MarkdownStorageAdapter, InMemoryStorageAdapter } from '@memstack/core';
6
+ import { realpathSync, existsSync, mkdirSync, readFileSync, writeFileSync, statSync, chmodSync, renameSync, rmSync, copyFileSync, constants } from 'fs';
6
7
  import { homedir } from 'os';
7
- import { join, resolve, delimiter, dirname, basename } from 'path';
8
- import { execFile, spawn, execFileSync } from 'child_process';
8
+ import { resolve, join, dirname, delimiter, basename } from 'path';
9
+ import { execFile, execFileSync, spawn } from 'child_process';
9
10
  import { createHash } from 'crypto';
10
11
  import { createInterface } from 'readline/promises';
11
12
 
@@ -262,9 +263,6 @@ async function buildStorageAdapter(env) {
262
263
  throw new Error(`Unknown MEMSTACK_STORAGE: "${storage}". Supported: memory, markdown, disk, postgres, sqlite, redis`);
263
264
  }
264
265
  }
265
- async function loadConfigFromEnv() {
266
- return buildConfig(process.env);
267
- }
268
266
  async function loadConfig(options = {}) {
269
267
  return buildConfig(mergeConfigFile(readConfigFile(options.configPath), process.env));
270
268
  }
@@ -304,7 +302,7 @@ async function buildConfig(env) {
304
302
 
305
303
  // src/config.ts
306
304
  async function loadConfig2() {
307
- const { config } = await loadConfigFromEnv();
305
+ const { config } = await loadConfig();
308
306
  return config;
309
307
  }
310
308
  var createRunner = (env = process.env) => (command, args) => new Promise((resolve2) => {
@@ -337,9 +335,9 @@ function claudeCodeAdapter(options = {}) {
337
335
  // Also on clear and compact, so the memories survive /clear and context compaction.
338
336
  sessionHook: { path: settingsPath, matcher: "startup|resume|clear|compact" },
339
337
  async inspect() {
340
- const version = await runner(binary, ["--version"]);
341
- const state = { installed: version.code === 0, configPath };
342
- if (state.installed) state.version = version.stdout.trim().split(/\s+/)[0];
338
+ const version2 = await runner(binary, ["--version"]);
339
+ const state = { installed: version2.code === 0, configPath };
340
+ if (state.installed) state.version = version2.stdout.trim().split(/\s+/)[0];
343
341
  state.entry = readEntry(configPath);
344
342
  return state;
345
343
  },
@@ -403,7 +401,7 @@ var CODEX_INSTRUCTIONS = [
403
401
  "## Project memory (MemStack)",
404
402
  "",
405
403
  "When the user asks you to remember something, save it with the memstack `memory_store` tool. Do not only acknowledge it, and do not write it into project files unless the user asks for a file change.",
406
- "Before answering questions about this project's conventions, decisions, or the user's preferences, check `memory_retrieve`."
404
+ "At the start of a task, and before answering questions about this project's conventions, decisions, or the user's preferences, check `memory_retrieve`: it holds what earlier sessions, in Codex or other agents, were told to remember."
407
405
  ].join("\n");
408
406
  function renderBlock(body, harness) {
409
407
  return `${BEGIN} (managed by \`memstack connect ${harness}\`; removed by \`memstack disconnect ${harness}\`) -->
@@ -461,10 +459,10 @@ function codexAdapter(options = {}) {
461
459
  instructions: { path: join(home, "AGENTS.md"), body: CODEX_INSTRUCTIONS },
462
460
  sessionHook: { path: join(home, "hooks.json"), matcher: "startup|resume" },
463
461
  async inspect() {
464
- const version = await runner(binary, ["--version"]);
465
- const state = { installed: version.code === 0, configPath };
462
+ const version2 = await runner(binary, ["--version"]);
463
+ const state = { installed: version2.code === 0, configPath };
466
464
  if (!state.installed) return state;
467
- state.version = version.stdout.trim().split(/\s+/).pop();
465
+ state.version = version2.stdout.trim().split(/\s+/).pop();
468
466
  const got = await runner(binary, ["mcp", "get", SERVER_NAME, "--json"]);
469
467
  if (got.code !== 0) {
470
468
  if (/No MCP server named/i.test(got.stderr + got.stdout)) return state;
@@ -978,9 +976,19 @@ async function disconnect(ids, flags) {
978
976
  var label = (name) => (name ? `${name}:` : "").padEnd(14);
979
977
  async function status() {
980
978
  const file = readConfigFile();
981
- out(`${label("Config")}${file ? configFilePath() : "none (run `memstack init`)"}`);
982
- if (file?.llm) out(`${label("LLM")}${file.llm.provider}${file.llm.model ? ` (${file.llm.model})` : ""}${file.llm.baseURL ? ` at ${file.llm.baseURL}` : ""}`);
983
- if (file?.storage) out(`${label("Storage")}${file.storage.type}${file.storage.path ? ` at ${file.storage.path}` : file.storage.url ? " (URL configured)" : ""}`);
979
+ const fromEnv = envOverrides();
980
+ const config = file ? configFilePath() : fromEnv.llm || fromEnv.storage ? "none, using environment variables" : "none (run `memstack init`)";
981
+ out(`${label("Config")}${config}`);
982
+ const env = mergeConfigFile(file, process.env);
983
+ const envNote = (overridden) => overridden ? " (from environment variables)" : "";
984
+ if (env.OPENAI_API_KEY || env.ANTHROPIC_API_KEY) {
985
+ const provider = env.ANTHROPIC_API_KEY ? "anthropic" : "openai-compatible";
986
+ const baseURL = provider === "openai-compatible" ? env.MEMSTACK_OPENAI_BASE_URL : void 0;
987
+ out(`${label("LLM")}${provider}${env.MEMSTACK_LLM_MODEL ? ` (${env.MEMSTACK_LLM_MODEL})` : ""}${baseURL ? ` at ${baseURL}` : ""}${envNote(fromEnv.llm)}`);
988
+ } else if (file || fromEnv.llm) {
989
+ out(`${label("LLM")}no API key (run \`memstack init\`)`);
990
+ }
991
+ if (env.MEMSTACK_STORAGE) out(`${label("Storage")}${describeStorage(env)}${envNote(fromEnv.storage)}`);
984
992
  const project = resolveProject();
985
993
  out(`${label("Project")}${project.id} (${describeSource(project.source)}: ${project.key})`);
986
994
  const mcp = findMemstackMcp();
@@ -1008,6 +1016,20 @@ async function status() {
1008
1016
  }
1009
1017
  return 0;
1010
1018
  }
1019
+ function envOverrides() {
1020
+ return {
1021
+ llm: ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "MEMSTACK_OPENAI_BASE_URL", "MEMSTACK_LLM_MODEL"].some((key) => process.env[key] !== void 0),
1022
+ storage: process.env.MEMSTACK_STORAGE !== void 0
1023
+ };
1024
+ }
1025
+ function describeStorage(env) {
1026
+ const type = env.MEMSTACK_STORAGE;
1027
+ if (type === "memory") return "memory (lost when the process exits)";
1028
+ const path = type === "sqlite" ? env.SQLITE_PATH : type === "disk" || type === "markdown" ? env.MEMSTACK_DIR : void 0;
1029
+ if (path) return `${type} at ${path}`;
1030
+ const url = type === "postgres" ? env.DATABASE_URL : type === "redis" ? env.REDIS_URL : void 0;
1031
+ return url ? `${type} (URL configured)` : type;
1032
+ }
1011
1033
  function hookState(adapter, mcp) {
1012
1034
  const current = readHook(adapter.sessionHook.path);
1013
1035
  if (!current) return "missing";
@@ -1026,6 +1048,7 @@ async function doctor(flags) {
1026
1048
  try {
1027
1049
  file = readConfigFile();
1028
1050
  if (file) pass(`Config file ${configFilePath()}`);
1051
+ else if (envOverrides().llm || envOverrides().storage) pass(`Using environment variables (no config file at ${configFilePath()})`);
1029
1052
  else warn(`No config file at ${configFilePath()}; run \`memstack init\`.`);
1030
1053
  } catch (error) {
1031
1054
  fail2(error.message);
@@ -1227,6 +1250,149 @@ function createPrompter() {
1227
1250
  };
1228
1251
  }
1229
1252
 
1253
+ // src/help.ts
1254
+ var ACTOR = ` --actor <id> Actor ID that groups related memories
1255
+ --project Use this repository's project instead of --actor, the same
1256
+ memories Claude Code and Codex see through \`memstack connect\``;
1257
+ var MEMORY_ENV = `
1258
+ Reads ~/.memstack/config.json (written by \`memstack init\`); environment variables
1259
+ such as OPENAI_API_KEY and MEMSTACK_STORAGE override it. Prints JSON.
1260
+ `;
1261
+ var COMMAND_HELP = {
1262
+ init: `memstack init [flags]
1263
+
1264
+ Choose an LLM provider and a store, check the key with a real request, and write
1265
+ ~/.memstack/config.json (readable only by you). Prompts for anything not given
1266
+ as a flag; with --yes or without a terminal it uses flags and existing values.
1267
+
1268
+ --provider <name> openai-compatible (default) or anthropic
1269
+ --base-url <url> OpenAI-compatible endpoint, e.g. https://api.deepseek.com
1270
+ --model <name> Model override
1271
+ --api-key-env <VAR> Read the API key from this environment variable
1272
+ --store <type> Storage type (default: sqlite)
1273
+ --path <path> File or directory for sqlite, disk, or markdown
1274
+ --url <url> Connection URL for postgres, redis, or mongodb
1275
+ --yes Don't prompt
1276
+ `,
1277
+ connect: `memstack connect <claude-code|codex> [flags]
1278
+
1279
+ Register MemStack's MCP server with a harness, add a session-start hook that
1280
+ loads the project's memories, and for Codex add a marked block to
1281
+ ~/.codex/AGENTS.md. Checks the server first and undoes every change if a step
1282
+ fails. Safe to run again.
1283
+
1284
+ --dry-run Show what would change without changing it
1285
+ --no-hooks Skip the session-start hook
1286
+ --no-agents-md Skip the Codex AGENTS.md block
1287
+ `,
1288
+ disconnect: `memstack disconnect <claude-code|codex> [flags]
1289
+
1290
+ Remove everything \`memstack connect\` added. Memories are kept.
1291
+
1292
+ --dry-run Show what would change without changing it
1293
+ `,
1294
+ status: `memstack status
1295
+
1296
+ Show where configuration comes from (config file or environment variables), the
1297
+ LLM and storage in use, this repository's project ID, and each harness's
1298
+ connection, guidance, and session-start hook.
1299
+ `,
1300
+ doctor: `memstack doctor [flags]
1301
+
1302
+ Check the configuration, storage driver, MCP server, and harness connections,
1303
+ and print a fix for each problem. Exits non-zero when it finds any.
1304
+
1305
+ --live Also send a test request to the LLM provider
1306
+ `,
1307
+ memories: `memstack memories [query] [flags]
1308
+
1309
+ List this repository's memories (project and global), or search them.
1310
+
1311
+ --global Only global memories
1312
+ --limit <n> Maximum to show (default: 20)
1313
+ --delete <id> Delete one memory
1314
+ `,
1315
+ project: `memstack project [pin <id> | merge <old-id>]
1316
+
1317
+ memstack project Show this repository's project ID and where it comes from
1318
+ memstack project pin <id> Write .memstack.json so every clone uses <id>
1319
+ memstack project merge <old> Move memories stored under an old project ID to this one
1320
+ `,
1321
+ store: `memstack store --content <text> (--actor <id> | --project) [flags]
1322
+
1323
+ ${ACTOR}
1324
+ --content <text> Memory text (required)
1325
+ --type <type> interaction (default), summary, observation, fact, reflection,
1326
+ preference, decision, or instruction
1327
+ --importance <n> 0.0 to 1.0
1328
+ --tags <a,b> Comma-separated tags
1329
+ ${MEMORY_ENV}`,
1330
+ retrieve: `memstack retrieve (--actor <id> | --project) [flags]
1331
+
1332
+ ${ACTOR}
1333
+ --query <text> Search text
1334
+ --strategy <name> recent (default), important, semantic, or hybrid
1335
+ --limit <n> Default: 10
1336
+ --tags <a,b> Only memories with these tags
1337
+ --created-after <iso> e.g. 2026-01-01T00:00:00Z
1338
+ --created-before <iso>
1339
+ ${MEMORY_ENV}`,
1340
+ context: `memstack context (--actor <id> | --project) [flags]
1341
+
1342
+ Compile memories into a token-budgeted system prompt.
1343
+
1344
+ ${ACTOR}
1345
+ --max-tokens <n> Token budget (default: 2000)
1346
+ ${MEMORY_ENV}`,
1347
+ summarize: `memstack summarize (--actor <id> | --project) [flags]
1348
+
1349
+ Compress old interactions into summaries.
1350
+
1351
+ ${ACTOR}
1352
+ --older-than <d> Only memories older than this, e.g. 7d, 24h, 30m
1353
+ ${MEMORY_ENV}`,
1354
+ prune: `memstack prune (--actor <id> | --project) [flags]
1355
+
1356
+ ${ACTOR}
1357
+ --type <name> byAge (default), byImportance, or byCount
1358
+ --max-age <d> For byAge, e.g. 30d
1359
+ --min-importance <n> For byImportance
1360
+ --max-count <n> For byCount: keep the top n
1361
+ --dry-run Show what would be removed
1362
+ ${MEMORY_ENV}`,
1363
+ purge: `memstack purge (--actor <id> | --project)
1364
+
1365
+ Delete every memory for the actor.
1366
+
1367
+ ${ACTOR}
1368
+ ${MEMORY_ENV}`,
1369
+ merge: `memstack merge --ids <id,id,...>
1370
+
1371
+ Merge several memories into one.
1372
+ ${MEMORY_ENV}`,
1373
+ stats: `memstack stats [--actor <id> | --project]
1374
+
1375
+ Memory counts and diagnostics; all actors when neither flag is given.
1376
+ ${MEMORY_ENV}`,
1377
+ delete: `memstack delete --id <id>
1378
+ ${MEMORY_ENV}`,
1379
+ health: `memstack health
1380
+
1381
+ Check storage, LLM, and embedding connectivity.
1382
+ ${MEMORY_ENV}`,
1383
+ export: `memstack export [--actor <id> | --project] [--out <file>]
1384
+
1385
+ Export memories as a JSON snapshot; all actors when neither flag is given.
1386
+ ${MEMORY_ENV}`,
1387
+ import: `memstack import (--actor <id> | --project) --file <file>
1388
+
1389
+ Import memories from a JSON snapshot.
1390
+
1391
+ ${ACTOR}
1392
+ --file <file> Snapshot path (required)
1393
+ ${MEMORY_ENV}`
1394
+ };
1395
+
1230
1396
  // src/cli.ts
1231
1397
  process.stdout.on("error", (error) => {
1232
1398
  if (error.code === "EPIPE") process.exit(0);
@@ -1268,24 +1434,43 @@ async function main() {
1268
1434
  global: { type: "boolean" },
1269
1435
  delete: { type: "string" },
1270
1436
  "no-agents-md": { type: "boolean" },
1271
- "no-hooks": { type: "boolean" }
1437
+ "no-hooks": { type: "boolean" },
1438
+ project: { type: "boolean" },
1439
+ help: { type: "boolean", short: "h" },
1440
+ version: { type: "boolean", short: "v" }
1272
1441
  }
1273
1442
  });
1443
+ if (values.version) {
1444
+ process.stdout.write(`${version()}
1445
+ `);
1446
+ return;
1447
+ }
1274
1448
  const command = positionals[0];
1449
+ if (command === "help" || values.help) {
1450
+ const topic = command === "help" ? positionals[1] : command;
1451
+ if (topic && !COMMAND_HELP[topic]) fail(`Unknown command: ${topic}. Run memstack --help for the list.`);
1452
+ process.stdout.write(topic ? COMMAND_HELP[topic] : USAGE);
1453
+ return;
1454
+ }
1275
1455
  if (!command) {
1276
- printUsage();
1456
+ process.stderr.write(USAGE);
1277
1457
  process.exit(1);
1278
1458
  }
1279
1459
  if (HARNESS_COMMANDS.includes(command)) {
1280
1460
  process.exitCode = await runHarnessCommand(command, positionals.slice(1), values);
1281
1461
  return;
1282
1462
  }
1463
+ if (!COMMAND_HELP[command]) fail(`Unknown command: ${command}. Run memstack --help for the list.`);
1464
+ if (values.project) {
1465
+ if (values.actor) fail("Use either --actor or --project, not both");
1466
+ values.actor = projectNamespace(resolveProject().id);
1467
+ }
1283
1468
  const config = await loadConfig2();
1284
1469
  const ms = new MemStack(config);
1285
1470
  let result;
1286
1471
  switch (command) {
1287
1472
  case "store": {
1288
- if (!values.actor) fail("--actor is required");
1473
+ if (!values.actor) fail("--actor (or --project) is required");
1289
1474
  if (!values.content) fail("--content is required");
1290
1475
  const importance = values.importance ? Math.max(0, Math.min(1, Number(values.importance))) : void 0;
1291
1476
  result = await ms.memory.store({
@@ -1298,7 +1483,7 @@ async function main() {
1298
1483
  break;
1299
1484
  }
1300
1485
  case "retrieve": {
1301
- if (!values.actor) fail("--actor is required");
1486
+ if (!values.actor) fail("--actor (or --project) is required");
1302
1487
  result = await ms.memory.retrieve({
1303
1488
  actorId: values.actor,
1304
1489
  query: values.query,
@@ -1311,7 +1496,7 @@ async function main() {
1311
1496
  break;
1312
1497
  }
1313
1498
  case "context": {
1314
- if (!values.actor) fail("--actor is required");
1499
+ if (!values.actor) fail("--actor (or --project) is required");
1315
1500
  result = await ms.memory.compileContext({
1316
1501
  actorId: String(values.actor),
1317
1502
  maxTokens: values["max-tokens"] ? Number(values["max-tokens"]) : void 0
@@ -1319,7 +1504,7 @@ async function main() {
1319
1504
  break;
1320
1505
  }
1321
1506
  case "summarize": {
1322
- if (!values.actor) fail("--actor is required");
1507
+ if (!values.actor) fail("--actor (or --project) is required");
1323
1508
  const durationMs = values["older-than"] ? parseDuration(String(values["older-than"])) : void 0;
1324
1509
  const olderThan = durationMs ? new Date(Date.now() - durationMs) : void 0;
1325
1510
  result = await ms.memory.summarize({
@@ -1329,7 +1514,7 @@ async function main() {
1329
1514
  break;
1330
1515
  }
1331
1516
  case "prune": {
1332
- if (!values.actor) fail("--actor is required");
1517
+ if (!values.actor) fail("--actor (or --project) is required");
1333
1518
  const VALID_TYPES = ["byAge", "byImportance", "byCount", "byType", "custom", "compose"];
1334
1519
  const pruneType = values.type ?? "byAge";
1335
1520
  if (!VALID_TYPES.includes(String(pruneType))) {
@@ -1350,7 +1535,7 @@ async function main() {
1350
1535
  break;
1351
1536
  }
1352
1537
  case "purge": {
1353
- if (!values.actor) fail("--actor is required");
1538
+ if (!values.actor) fail("--actor (or --project) is required");
1354
1539
  result = await ms.memory.purgeActor(String(values.actor));
1355
1540
  break;
1356
1541
  }
@@ -1385,7 +1570,7 @@ async function main() {
1385
1570
  break;
1386
1571
  }
1387
1572
  case "import": {
1388
- if (!values.actor) fail("--actor is required");
1573
+ if (!values.actor) fail("--actor (or --project) is required");
1389
1574
  if (!values.file) fail("--file is required");
1390
1575
  const raw = await readFile(String(values.file), "utf-8");
1391
1576
  let snapshot2 = JSON.parse(raw);
@@ -1400,8 +1585,7 @@ async function main() {
1400
1585
  break;
1401
1586
  }
1402
1587
  default:
1403
- printUsage();
1404
- process.exit(1);
1588
+ fail(`Unknown command: ${command}. Run memstack --help for the list.`);
1405
1589
  }
1406
1590
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1407
1591
  await ms.close();
@@ -1427,8 +1611,10 @@ function parseDuration(input) {
1427
1611
  return value;
1428
1612
  }
1429
1613
  }
1430
- function printUsage() {
1431
- process.stderr.write(`memstack <command> [flags]
1614
+ function version() {
1615
+ return createRequire(import.meta.url)("../package.json").version;
1616
+ }
1617
+ var USAGE = `memstack <command> [flags]
1432
1618
 
1433
1619
  Agent harnesses (Claude Code, Codex):
1434
1620
  init Choose an LLM provider and store; writes ~/.memstack/config.json
@@ -1455,9 +1641,8 @@ Commands:
1455
1641
  export Export all memories
1456
1642
  import Import memories from a JSON snapshot
1457
1643
 
1458
- Run memstack <command> --help for flags.
1459
- `);
1460
- }
1644
+ Run memstack <command> --help for flags, or memstack --version.
1645
+ `;
1461
1646
  function fail(message) {
1462
1647
  process.stderr.write(`Error: ${message}
1463
1648
  `);