@memmo/cli 0.7.0 → 0.7.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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +159 -53
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -16,6 +16,7 @@ npm install -g @memmo/cli
16
16
  memmo login # once per machine; stores a token in ~/.memmo
17
17
  memmo init # in a repo: registers it from the git remote, wires Claude Code, offers a scan
18
18
  memmo status # check the setup
19
+ memmo desktop # wire Claude Desktop to this repo (init offers this when Desktop is installed)
19
20
  memmo scan # propose starter memories (drives your local Claude Code)
20
21
  memmo decision "…" # record a decision as memory
21
22
  memmo sync # write approved memories into a managed CLAUDE.md block
package/dist/index.js CHANGED
@@ -254,8 +254,8 @@ async function loginCommand(opts) {
254
254
 
255
255
  // src/commands/init.ts
256
256
  import { createInterface as createInterface2 } from "node:readline/promises";
257
- import { existsSync as existsSync4 } from "node:fs";
258
- import { join as join4 } from "node:path";
257
+ import { existsSync as existsSync5 } from "node:fs";
258
+ import { join as join5 } from "node:path";
259
259
  init_api();
260
260
 
261
261
  // src/commands/claude-install.ts
@@ -291,7 +291,10 @@ var HOOK_EVENTS = {
291
291
  },
292
292
  SessionEnd: { hooks: [{ type: "command", command: hook("session-end") }] }
293
293
  };
294
- var isMemmoHook = (e) => e.hooks?.some((h) => typeof h.command === "string" && h.command.includes("memmo hook"));
294
+ var isMemmoHook = (e) => e.hooks?.some((h) => typeof h.command === "string" && isMemmoHookCommand(h.command));
295
+ function isMemmoHookCommand(cmd) {
296
+ return /(^|[\s/@])memmo(\/cli)?\s+hook\s/.test(cmd);
297
+ }
295
298
  function mergeMcpJson(path) {
296
299
  if (!existsSync2(path)) {
297
300
  writeFileSync2(path, JSON.stringify({ mcpServers: { memmo: MCP_SERVER } }, null, 2) + "\n");
@@ -332,6 +335,20 @@ function mergeSettingsHooks(claudeDir) {
332
335
  }
333
336
  }
334
337
  json.hooks = json.hooks ?? {};
338
+ let removed = 0;
339
+ for (const event of Object.keys(json.hooks)) {
340
+ const list = json.hooks[event] ?? [];
341
+ let seen = false;
342
+ json.hooks[event] = list.filter((e) => {
343
+ if (!isMemmoHook(e)) return true;
344
+ if (seen) {
345
+ removed++;
346
+ return false;
347
+ }
348
+ seen = true;
349
+ return true;
350
+ });
351
+ }
335
352
  let added = 0;
336
353
  for (const [event, entry] of Object.entries(HOOK_EVENTS)) {
337
354
  const list = json.hooks[event] = json.hooks[event] ?? [];
@@ -339,8 +356,9 @@ function mergeSettingsHooks(claudeDir) {
339
356
  list.push(entry);
340
357
  added++;
341
358
  }
342
- if (added === 0) return ".claude/settings.json already has the memmo hooks \u2014 left as is";
359
+ if (added === 0 && removed === 0) return ".claude/settings.json already has the memmo hooks \u2014 left as is";
343
360
  writeFileSync2(path, JSON.stringify(json, null, 2) + "\n");
361
+ if (added === 0) return `removed ${removed} duplicate Memmo hook(s) from .claude/settings.json`;
344
362
  return existsSync2(path) ? `registered ${added} Memmo hook(s) in .claude/settings.json` : "wrote .claude/settings.json";
345
363
  }
346
364
  function writeClaudeAssets(cwd = process.cwd()) {
@@ -502,9 +520,7 @@ async function agentScan(claudeBin, client, config, cwd) {
502
520
  });
503
521
  if (code !== 0) {
504
522
  throw new Error(
505
- `Claude Code exited with code ${code}.
506
- If you saw a 401/auth error, make sure Claude Code is logged in: run \`claude\` once and sign in (or \`claude login\`).
507
- Alternatively, use \`memmo scan --server\` to scan with the project API key.`
523
+ "Claude Code couldn't run the scan (it's usually not signed in \u2014 run `claude` once). No problem: memory also builds up from your normal sessions, and you can retry with `memmo scan`."
508
524
  );
509
525
  }
510
526
  const after = await countProposed(client, config.repoId);
@@ -556,6 +572,83 @@ async function scanCommand(opts = {}) {
556
572
  return serverScan(client, config);
557
573
  }
558
574
 
575
+ // src/commands/desktop.ts
576
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
577
+ import { homedir as homedir2, platform } from "node:os";
578
+ import { dirname, join as join4 } from "node:path";
579
+ init_api();
580
+ function desktopConfigPath() {
581
+ const home = homedir2();
582
+ if (platform() === "darwin") return join4(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
583
+ if (platform() === "win32") return join4(process.env.APPDATA ?? join4(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
584
+ return join4(process.env.XDG_CONFIG_HOME ?? join4(home, ".config"), "Claude", "claude_desktop_config.json");
585
+ }
586
+ function desktopInstalled() {
587
+ if (existsSync4(dirname(desktopConfigPath()))) return true;
588
+ if (platform() === "darwin") return existsSync4("/Applications/Claude.app");
589
+ if (platform() === "win32") return existsSync4(join4(process.env.LOCALAPPDATA ?? "", "AnthropicClaude"));
590
+ return false;
591
+ }
592
+ function readConfig(path) {
593
+ if (!existsSync4(path)) return {};
594
+ try {
595
+ return JSON.parse(readFileSync3(path, "utf8"));
596
+ } catch {
597
+ throw new Error(`${path} isn't valid JSON \u2014 fix or remove it, then run \`memmo desktop\` again.`);
598
+ }
599
+ }
600
+ async function desktopCommand(opts = {}) {
601
+ const path = desktopConfigPath();
602
+ const config = readConfig(path);
603
+ if (opts.remove) {
604
+ if (!config.mcpServers?.memmo) {
605
+ console.log("Claude Desktop isn't wired to Memmo \u2014 nothing to remove.");
606
+ return;
607
+ }
608
+ delete config.mcpServers.memmo;
609
+ writeFileSync3(path, JSON.stringify(config, null, 2) + "\n");
610
+ console.log(`Removed the memmo server from ${path}. Restart Claude Desktop.`);
611
+ return;
612
+ }
613
+ const creds = loadCredentials();
614
+ if (!creds) throw new Error("Not logged in. Run `memmo login` first.");
615
+ const client = { baseUrl: process.env.MEMMO_API_URL ?? creds.apiBaseUrl, token: creds.token };
616
+ let repoId = opts.repo;
617
+ let label = repoId;
618
+ if (!repoId) {
619
+ const here = await resolveProjectConfig(process.cwd());
620
+ if (here) {
621
+ repoId = here.repoId;
622
+ label = here.repoFullName ?? here.repoId;
623
+ }
624
+ }
625
+ if (!repoId) {
626
+ const repos = await apiFetch(client, "/repos");
627
+ if (repos.length === 0) throw new Error("No repo connected yet. Run `memmo init` inside a repo first.");
628
+ if (repos.length > 1) {
629
+ console.log("Claude Desktop serves one repo at a time. Pass --repo <id>; your repos:");
630
+ for (const r of repos) console.log(` ${r.id} ${r.fullName}`);
631
+ throw new Error("Choose a repo with --repo.");
632
+ }
633
+ repoId = repos[0].id;
634
+ label = repos[0].fullName;
635
+ }
636
+ config.mcpServers = config.mcpServers ?? {};
637
+ const args = ["-y", "@memmo/cli", "mcp", "--repo", repoId];
638
+ const existing = config.mcpServers.memmo;
639
+ const same = existing && existing.command === "npx" && JSON.stringify(existing.args) === JSON.stringify(args);
640
+ if (same) {
641
+ console.log(`Claude Desktop already serves ${label ?? repoId} from Memmo (${path}).`);
642
+ return;
643
+ }
644
+ config.mcpServers.memmo = { command: "npx", args };
645
+ mkdirSync3(dirname(path), { recursive: true });
646
+ writeFileSync3(path, JSON.stringify(config, null, 2) + "\n");
647
+ console.log(`${existing ? "Updated" : "Added"} the memmo server in ${path}`);
648
+ console.log(` repo: ${label ?? repoId}`);
649
+ console.log("Restart Claude Desktop, then look for the memmo tools (get_repo_context, search_memory, \u2026) in a chat.");
650
+ }
651
+
559
652
  // src/commands/init.ts
560
653
  async function ask(q) {
561
654
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
@@ -584,7 +677,7 @@ async function initCommand(opts) {
584
677
  if (!creds) throw new Error("Not logged in. Run `memmo login` first.");
585
678
  const client = { baseUrl: process.env.MEMMO_API_URL ?? creds.apiBaseUrl, token: creds.token };
586
679
  const cwd = process.cwd();
587
- if (!existsSync4(join4(cwd, ".git"))) {
680
+ if (!existsSync5(join5(cwd, ".git"))) {
588
681
  console.log("note: no .git here \u2014 run `memmo init` at your repo root.");
589
682
  }
590
683
  const remote = detectGitRemote(cwd);
@@ -630,6 +723,18 @@ Add ${name} to project "${project.name}"?`);
630
723
  }
631
724
  const actions = writeClaudeAssets(cwd);
632
725
  for (const a of actions) console.log(" - " + a);
726
+ let doDesktop = opts.desktop === true;
727
+ if (!doDesktop && !opts.yes && desktopInstalled()) {
728
+ const ans = (await ask("\nAlso give Claude Desktop this repo's memory? (Y/n) ")).toLowerCase();
729
+ doDesktop = ans === "" || ans === "y" || ans === "yes";
730
+ }
731
+ if (doDesktop) {
732
+ try {
733
+ await desktopCommand({ repo: repo.id });
734
+ } catch (e) {
735
+ console.log(`Claude Desktop not wired: ${e instanceof Error ? e.message : String(e)} (retry with \`memmo desktop\`)`);
736
+ }
737
+ }
633
738
  let doScan = opts.scan === true;
634
739
  if (!doScan && !opts.yes) {
635
740
  const ans = (await ask("\nScan this repo now to propose starter memories? (Y/n) ")).toLowerCase();
@@ -640,8 +745,8 @@ Add ${name} to project "${project.name}"?`);
640
745
  console.log("");
641
746
  await scanCommand();
642
747
  } catch (e) {
643
- console.log(`Scan skipped: ${e instanceof Error ? e.message : String(e)}`);
644
- console.log("Run it later with `memmo scan`.");
748
+ console.log(`
749
+ Scan skipped. ${e instanceof Error ? e.message : String(e)}`);
645
750
  }
646
751
  }
647
752
  const appUrl = await appUrlFor(client);
@@ -804,8 +909,8 @@ async function mcpCommand(opts = {}) {
804
909
  }
805
910
 
806
911
  // src/commands/hook.ts
807
- import { readFileSync as readFileSync3, existsSync as existsSync5, writeFileSync as writeFileSync3 } from "node:fs";
808
- import { join as join5 } from "node:path";
912
+ import { readFileSync as readFileSync4, existsSync as existsSync6, writeFileSync as writeFileSync4 } from "node:fs";
913
+ import { join as join6 } from "node:path";
809
914
  import { tmpdir } from "node:os";
810
915
  import { createHash } from "node:crypto";
811
916
  init_api();
@@ -899,10 +1004,10 @@ async function preEdit(input, env) {
899
1004
  });
900
1005
  if (!warnings.length) return;
901
1006
  const key = createHash("sha1").update(`${input.session_id ?? "x"}:${file}`).digest("hex").slice(0, 16);
902
- const marker = join5(tmpdir(), `memmo-warned-${key}`);
903
- if (existsSync5(marker)) return;
1007
+ const marker = join6(tmpdir(), `memmo-warned-${key}`);
1008
+ if (existsSync6(marker)) return;
904
1009
  try {
905
- writeFileSync3(marker, "1");
1010
+ writeFileSync4(marker, "1");
906
1011
  } catch {
907
1012
  }
908
1013
  const body = warnings.map((w) => `\u26A0 [${w.type}] ${w.title}
@@ -913,8 +1018,8 @@ ${body}
913
1018
  process.exit(2);
914
1019
  }
915
1020
  async function sessionEnd(input, env) {
916
- if (!input.transcript_path || !existsSync5(input.transcript_path)) return;
917
- const raw = readFileSync3(input.transcript_path, "utf8");
1021
+ if (!input.transcript_path || !existsSync6(input.transcript_path)) return;
1022
+ const raw = readFileSync4(input.transcript_path, "utf8");
918
1023
  const filesChanged = [];
919
1024
  const commandsRun = [];
920
1025
  const errors = [];
@@ -1000,8 +1105,8 @@ async function hookCommand(event) {
1000
1105
  }
1001
1106
 
1002
1107
  // src/commands/status.ts
1003
- import { readFileSync as readFileSync4, existsSync as existsSync6 } from "node:fs";
1004
- import { join as join6 } from "node:path";
1108
+ import { readFileSync as readFileSync5, existsSync as existsSync7 } from "node:fs";
1109
+ import { join as join7 } from "node:path";
1005
1110
  init_api();
1006
1111
  var OK = "\x1B[32m\u2713\x1B[0m";
1007
1112
  var NO = "\x1B[31m\u2717\x1B[0m";
@@ -1011,18 +1116,18 @@ function line(mark, label, detail = "") {
1011
1116
  }
1012
1117
  function readJson(path) {
1013
1118
  try {
1014
- return JSON.parse(readFileSync4(path, "utf8"));
1119
+ return JSON.parse(readFileSync5(path, "utf8"));
1015
1120
  } catch {
1016
1121
  return null;
1017
1122
  }
1018
1123
  }
1019
1124
  function hasMemmoHooks(cwd) {
1020
1125
  const json = readJson(
1021
- join6(cwd, ".claude", "settings.json")
1126
+ join7(cwd, ".claude", "settings.json")
1022
1127
  );
1023
1128
  if (!json?.hooks) return { present: false, events: [] };
1024
1129
  const events = Object.entries(json.hooks).filter(
1025
- ([, list]) => list?.some((e) => e.hooks?.some((h) => h.command?.includes("memmo hook")))
1130
+ ([, list]) => list?.some((e) => e.hooks?.some((h) => !!h.command && isMemmoHookCommand(h.command)))
1026
1131
  ).map(([event]) => event);
1027
1132
  return { present: events.length > 0, events };
1028
1133
  }
@@ -1037,9 +1142,9 @@ async function statusCommand() {
1037
1142
  "Repo connected",
1038
1143
  config ? `${config.repoFullName ?? config.repoId}` : "run `memmo init`"
1039
1144
  );
1040
- const mcp = readJson(join6(cwd, ".mcp.json"));
1145
+ const mcp = readJson(join7(cwd, ".mcp.json"));
1041
1146
  line(mcp?.mcpServers?.memmo ? OK : NO, ".mcp.json (memmo MCP server)");
1042
- const claudeMd = existsSync6(join6(cwd, "CLAUDE.md")) ? readFileSync4(join6(cwd, "CLAUDE.md"), "utf8") : "";
1147
+ const claudeMd = existsSync7(join7(cwd, "CLAUDE.md")) ? readFileSync5(join7(cwd, "CLAUDE.md"), "utf8") : "";
1043
1148
  line(claudeMd.includes("Memmo") ? OK : WARN, "CLAUDE.md guidance");
1044
1149
  const hooks = hasMemmoHooks(cwd);
1045
1150
  line(
@@ -1068,8 +1173,8 @@ async function statusCommand() {
1068
1173
  }
1069
1174
 
1070
1175
  // src/commands/uninstall.ts
1071
- import { existsSync as existsSync7, rmSync, readdirSync } from "node:fs";
1072
- import { join as join7 } from "node:path";
1176
+ import { existsSync as existsSync8, rmSync, readdirSync } from "node:fs";
1177
+ import { join as join8 } from "node:path";
1073
1178
  import { createInterface as createInterface3 } from "node:readline/promises";
1074
1179
  async function uninstallCommand(opts = {}) {
1075
1180
  const cwd = process.cwd();
@@ -1095,16 +1200,16 @@ async function uninstallCommand(opts = {}) {
1095
1200
  push(removeClaudeSection(cwd));
1096
1201
  const remote = detectGitRemote(cwd);
1097
1202
  if (remote) forgetRepo(remote);
1098
- const configFile = join7(cwd, ".memmo", "config.json");
1099
- if (existsSync7(configFile)) {
1203
+ const configFile = join8(cwd, ".memmo", "config.json");
1204
+ if (existsSync8(configFile)) {
1100
1205
  rmSync(configFile, { force: true });
1101
1206
  actions.push("removed .memmo/config.json");
1102
- const dir = join7(cwd, ".memmo");
1103
- if (existsSync7(dir) && readdirSync(dir).length === 0) rmSync(dir, { recursive: true, force: true });
1207
+ const dir = join8(cwd, ".memmo");
1208
+ if (existsSync8(dir) && readdirSync(dir).length === 0) rmSync(dir, { recursive: true, force: true });
1104
1209
  }
1105
1210
  for (const f of ["memmo-session-end.sh", "memmo-before-edit.sh"]) {
1106
- const p = join7(cwd, ".claude", "hooks", f);
1107
- if (existsSync7(p)) {
1211
+ const p = join8(cwd, ".claude", "hooks", f);
1212
+ if (existsSync8(p)) {
1108
1213
  rmSync(p, { force: true });
1109
1214
  actions.push(`removed legacy .claude/hooks/${f}`);
1110
1215
  }
@@ -1120,8 +1225,8 @@ async function uninstallCommand(opts = {}) {
1120
1225
 
1121
1226
  // src/commands/chat.ts
1122
1227
  import { spawn as spawn2 } from "node:child_process";
1123
- import { existsSync as existsSync8 } from "node:fs";
1124
- import { join as join8, delimiter as delimiter2 } from "node:path";
1228
+ import { existsSync as existsSync9 } from "node:fs";
1229
+ import { join as join9, delimiter as delimiter2 } from "node:path";
1125
1230
  import { createInterface as createInterface4 } from "node:readline/promises";
1126
1231
  import Anthropic from "@anthropic-ai/sdk";
1127
1232
  init_api();
@@ -1133,7 +1238,7 @@ function findOnPath2(cmd) {
1133
1238
  const exts = process.platform === "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
1134
1239
  for (const dir of (process.env.PATH ?? "").split(delimiter2)) {
1135
1240
  if (!dir) continue;
1136
- for (const ext of exts) if (existsSync8(join8(dir, cmd + ext))) return join8(dir, cmd + ext);
1241
+ for (const ext of exts) if (existsSync9(join9(dir, cmd + ext))) return join9(dir, cmd + ext);
1137
1242
  }
1138
1243
  return null;
1139
1244
  }
@@ -1273,8 +1378,8 @@ Why: ${opts.why}` : title;
1273
1378
  }
1274
1379
 
1275
1380
  // src/commands/sync.ts
1276
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync9 } from "node:fs";
1277
- import { join as join9 } from "node:path";
1381
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "node:fs";
1382
+ import { join as join10 } from "node:path";
1278
1383
  init_api();
1279
1384
  var START = "<!-- memmo:memory:start -->";
1280
1385
  var END = "<!-- memmo:memory:end -->";
@@ -1323,15 +1428,15 @@ async function syncCommand() {
1323
1428
  `/repos/${config.repoId}/memories?status=approved`
1324
1429
  );
1325
1430
  const block = renderBlock(memories);
1326
- const path = join9(process.cwd(), "CLAUDE.md");
1327
- const existing = existsSync9(path) ? readFileSync5(path, "utf8") : "";
1328
- writeFileSync4(path, upsertBlock(existing, block));
1431
+ const path = join10(process.cwd(), "CLAUDE.md");
1432
+ const existing = existsSync10(path) ? readFileSync6(path, "utf8") : "";
1433
+ writeFileSync5(path, upsertBlock(existing, block));
1329
1434
  console.log(`Synced ${memories.length} approved memory(ies) into CLAUDE.md.`);
1330
1435
  }
1331
1436
 
1332
1437
  // src/commands/review.ts
1333
1438
  import { execFileSync } from "node:child_process";
1334
- import { writeFileSync as writeFileSync5 } from "node:fs";
1439
+ import { writeFileSync as writeFileSync6 } from "node:fs";
1335
1440
  init_api();
1336
1441
 
1337
1442
  // ../../packages/shared/src/types.ts
@@ -1674,12 +1779,12 @@ function buildReviewPayload(summary, findings, commentable, existingKeys = /* @_
1674
1779
  }
1675
1780
 
1676
1781
  // src/github.ts
1677
- import { readFileSync as readFileSync6, existsSync as existsSync10 } from "node:fs";
1782
+ import { readFileSync as readFileSync7, existsSync as existsSync11 } from "node:fs";
1678
1783
  function readEvent() {
1679
1784
  const p = process.env.GITHUB_EVENT_PATH;
1680
- if (!p || !existsSync10(p)) return {};
1785
+ if (!p || !existsSync11(p)) return {};
1681
1786
  try {
1682
- const ev = JSON.parse(readFileSync6(p, "utf8"));
1787
+ const ev = JSON.parse(readFileSync7(p, "utf8"));
1683
1788
  return {
1684
1789
  title: ev.pull_request?.title,
1685
1790
  body: ev.pull_request?.body ?? void 0,
@@ -1805,7 +1910,7 @@ async function reviewCommand(opts = {}) {
1805
1910
  }
1806
1911
  }
1807
1912
  if (opts.out) {
1808
- writeFileSync5(opts.out, comment);
1913
+ writeFileSync6(opts.out, comment);
1809
1914
  console.error(`Wrote review to ${opts.out} (${res.review?.findings.length ?? 0} findings).`);
1810
1915
  }
1811
1916
  process.stdout.write(comment + "\n");
@@ -1877,8 +1982,8 @@ async function listReviewComments(ctx) {
1877
1982
  }
1878
1983
 
1879
1984
  // src/commands/ci.ts
1880
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync3, existsSync as existsSync11 } from "node:fs";
1881
- import { join as join10 } from "node:path";
1985
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync4, existsSync as existsSync12 } from "node:fs";
1986
+ import { join as join11 } from "node:path";
1882
1987
  var WORKFLOW = `name: Memmo Review
1883
1988
  on:
1884
1989
  pull_request:
@@ -1905,14 +2010,14 @@ jobs:
1905
2010
  GH_TOKEN: \${{ github.token }}
1906
2011
  `;
1907
2012
  async function ciCommand(opts = {}) {
1908
- const dir = join10(process.cwd(), ".github", "workflows");
1909
- const file = join10(dir, "memmo-review.yml");
1910
- if (existsSync11(file) && !opts.force) {
2013
+ const dir = join11(process.cwd(), ".github", "workflows");
2014
+ const file = join11(dir, "memmo-review.yml");
2015
+ if (existsSync12(file) && !opts.force) {
1911
2016
  console.error(`${file} already exists. Re-run with --force to overwrite.`);
1912
2017
  return;
1913
2018
  }
1914
- mkdirSync3(dir, { recursive: true });
1915
- writeFileSync6(file, WORKFLOW);
2019
+ mkdirSync4(dir, { recursive: true });
2020
+ writeFileSync7(file, WORKFLOW);
1916
2021
  console.log("Wrote .github/workflows/memmo-review.yml\n");
1917
2022
  console.log("Next steps:");
1918
2023
  console.log(" 1. Commit .memmo/config.json and the workflow file.");
@@ -1928,8 +2033,9 @@ init_version();
1928
2033
  var program = new Command();
1929
2034
  program.name("memmo").description("Long-term memory for AI coding agents").version(VERSION);
1930
2035
  program.command("login").description("Sign in (opens your browser) and store an API token in ~/.memmo").option("-t, --token <token>", "Use a token from Account \u2192 API tokens instead of the browser (CI/headless)").option("-e, --email <email>", "Dev-only email login (local API)").option("--no-browser", "Print the sign-in URL instead of opening a browser").option("--api <url>", "API base URL").action((opts) => run(() => loginCommand(opts)));
1931
- program.command("init").description("Connect this repo to Memmo (registers it from the git remote) and wire up Claude Code").option("-r, --repo <repoId>", "Repo ID to connect (default: detected from the git remote)").option("-p, --project <idOrName>", "Project to add the repo to (default: your only project, or a prompt)").option("-y, --yes", "Skip prompts").option("--scan", "Run an initial codebase scan after connecting").action((opts) => run(() => initCommand(opts)));
2036
+ program.command("init").description("Connect this repo to Memmo (registers it from the git remote) and wire up Claude Code").option("-r, --repo <repoId>", "Repo ID to connect (default: detected from the git remote)").option("-p, --project <idOrName>", "Project to add the repo to (default: your only project, or a prompt)").option("-y, --yes", "Skip prompts").option("--scan", "Run an initial codebase scan after connecting").option("--desktop", "Also wire Claude Desktop to this repo (asked interactively when Desktop is installed)").action((opts) => run(() => initCommand(opts)));
1932
2037
  program.command("claude").description("Claude Code integration").command("install").description("Generate CLAUDE.md, .mcp.json, and hooks").action(() => run(() => claudeInstallCommand()));
2038
+ program.command("desktop").description("Wire Memmo into Claude Desktop (writes its MCP config; Desktop serves one repo)").option("-r, --repo <repoId>", "Repo to serve (default: the current directory's repo)").option("--remove", "Remove the memmo server from Claude Desktop").action((opts) => run(() => desktopCommand(opts)));
1933
2039
  program.command("mcp").description("Run the Memmo MCP stdio server (used by Claude Code / Claude Desktop)").option("-r, --repo <repoId>", "Repo to serve (for Claude Desktop; defaults to .memmo/config.json)").option("--api <url>", "API base URL").action((opts) => run(() => mcpCommand(opts)));
1934
2040
  program.command("scan").description("Scan the connected repo and propose starter memories").option("--agent", "Deep scan with local Claude Code (no API key, uses your subscription)").option("--server", "Scan on the server from GitHub (uses the project Anthropic key)").action((opts) => run(() => scanCommand(opts)));
1935
2041
  program.command("chat [question...]").description("Chat with this repo's memory using your own Anthropic (key or Claude subscription)").action((question) => run(() => chatCommand(question)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memmo/cli",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Long-term memory for AI coding agents — CLI + MCP server for Claude Code.",
5
5
  "license": "MIT",
6
6
  "author": "Benjamin Beguin",