@memmo/cli 0.7.0 → 0.7.2

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 +165 -61
  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);
@@ -789,14 +894,12 @@ async function mcpCommand(opts = {}) {
789
894
  `/repos/${config.repoId}/proposals`,
790
895
  { method: "POST", body: JSON.stringify({ memories }) }
791
896
  );
792
- return {
793
- content: [
794
- {
795
- type: "text",
796
- text: `Proposed ${result.proposedCount} memory(ies) for review in Memmo.`
797
- }
798
- ]
799
- };
897
+ const parts = [];
898
+ if (result.proposedCount) parts.push(`${result.proposedCount} proposed for review`);
899
+ if (result.autoApproved) parts.push(`${result.autoApproved} auto-approved`);
900
+ if (result.autoRejected) parts.push(`${result.autoRejected} auto-rejected (low confidence)`);
901
+ if (result.skipped) parts.push(`${result.skipped} skipped as already known`);
902
+ return { content: [{ type: "text", text: parts.length ? `Memmo: ${parts.join(", ")}.` : "Memmo: nothing new to record." }] };
800
903
  }
801
904
  );
802
905
  const transport = new StdioServerTransport();
@@ -804,8 +907,8 @@ async function mcpCommand(opts = {}) {
804
907
  }
805
908
 
806
909
  // 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";
910
+ import { readFileSync as readFileSync4, existsSync as existsSync6, writeFileSync as writeFileSync4 } from "node:fs";
911
+ import { join as join6 } from "node:path";
809
912
  import { tmpdir } from "node:os";
810
913
  import { createHash } from "node:crypto";
811
914
  init_api();
@@ -899,10 +1002,10 @@ async function preEdit(input, env) {
899
1002
  });
900
1003
  if (!warnings.length) return;
901
1004
  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;
1005
+ const marker = join6(tmpdir(), `memmo-warned-${key}`);
1006
+ if (existsSync6(marker)) return;
904
1007
  try {
905
- writeFileSync3(marker, "1");
1008
+ writeFileSync4(marker, "1");
906
1009
  } catch {
907
1010
  }
908
1011
  const body = warnings.map((w) => `\u26A0 [${w.type}] ${w.title}
@@ -913,8 +1016,8 @@ ${body}
913
1016
  process.exit(2);
914
1017
  }
915
1018
  async function sessionEnd(input, env) {
916
- if (!input.transcript_path || !existsSync5(input.transcript_path)) return;
917
- const raw = readFileSync3(input.transcript_path, "utf8");
1019
+ if (!input.transcript_path || !existsSync6(input.transcript_path)) return;
1020
+ const raw = readFileSync4(input.transcript_path, "utf8");
918
1021
  const filesChanged = [];
919
1022
  const commandsRun = [];
920
1023
  const errors = [];
@@ -1000,8 +1103,8 @@ async function hookCommand(event) {
1000
1103
  }
1001
1104
 
1002
1105
  // src/commands/status.ts
1003
- import { readFileSync as readFileSync4, existsSync as existsSync6 } from "node:fs";
1004
- import { join as join6 } from "node:path";
1106
+ import { readFileSync as readFileSync5, existsSync as existsSync7 } from "node:fs";
1107
+ import { join as join7 } from "node:path";
1005
1108
  init_api();
1006
1109
  var OK = "\x1B[32m\u2713\x1B[0m";
1007
1110
  var NO = "\x1B[31m\u2717\x1B[0m";
@@ -1011,18 +1114,18 @@ function line(mark, label, detail = "") {
1011
1114
  }
1012
1115
  function readJson(path) {
1013
1116
  try {
1014
- return JSON.parse(readFileSync4(path, "utf8"));
1117
+ return JSON.parse(readFileSync5(path, "utf8"));
1015
1118
  } catch {
1016
1119
  return null;
1017
1120
  }
1018
1121
  }
1019
1122
  function hasMemmoHooks(cwd) {
1020
1123
  const json = readJson(
1021
- join6(cwd, ".claude", "settings.json")
1124
+ join7(cwd, ".claude", "settings.json")
1022
1125
  );
1023
1126
  if (!json?.hooks) return { present: false, events: [] };
1024
1127
  const events = Object.entries(json.hooks).filter(
1025
- ([, list]) => list?.some((e) => e.hooks?.some((h) => h.command?.includes("memmo hook")))
1128
+ ([, list]) => list?.some((e) => e.hooks?.some((h) => !!h.command && isMemmoHookCommand(h.command)))
1026
1129
  ).map(([event]) => event);
1027
1130
  return { present: events.length > 0, events };
1028
1131
  }
@@ -1037,9 +1140,9 @@ async function statusCommand() {
1037
1140
  "Repo connected",
1038
1141
  config ? `${config.repoFullName ?? config.repoId}` : "run `memmo init`"
1039
1142
  );
1040
- const mcp = readJson(join6(cwd, ".mcp.json"));
1143
+ const mcp = readJson(join7(cwd, ".mcp.json"));
1041
1144
  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") : "";
1145
+ const claudeMd = existsSync7(join7(cwd, "CLAUDE.md")) ? readFileSync5(join7(cwd, "CLAUDE.md"), "utf8") : "";
1043
1146
  line(claudeMd.includes("Memmo") ? OK : WARN, "CLAUDE.md guidance");
1044
1147
  const hooks = hasMemmoHooks(cwd);
1045
1148
  line(
@@ -1068,8 +1171,8 @@ async function statusCommand() {
1068
1171
  }
1069
1172
 
1070
1173
  // src/commands/uninstall.ts
1071
- import { existsSync as existsSync7, rmSync, readdirSync } from "node:fs";
1072
- import { join as join7 } from "node:path";
1174
+ import { existsSync as existsSync8, rmSync, readdirSync } from "node:fs";
1175
+ import { join as join8 } from "node:path";
1073
1176
  import { createInterface as createInterface3 } from "node:readline/promises";
1074
1177
  async function uninstallCommand(opts = {}) {
1075
1178
  const cwd = process.cwd();
@@ -1095,16 +1198,16 @@ async function uninstallCommand(opts = {}) {
1095
1198
  push(removeClaudeSection(cwd));
1096
1199
  const remote = detectGitRemote(cwd);
1097
1200
  if (remote) forgetRepo(remote);
1098
- const configFile = join7(cwd, ".memmo", "config.json");
1099
- if (existsSync7(configFile)) {
1201
+ const configFile = join8(cwd, ".memmo", "config.json");
1202
+ if (existsSync8(configFile)) {
1100
1203
  rmSync(configFile, { force: true });
1101
1204
  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 });
1205
+ const dir = join8(cwd, ".memmo");
1206
+ if (existsSync8(dir) && readdirSync(dir).length === 0) rmSync(dir, { recursive: true, force: true });
1104
1207
  }
1105
1208
  for (const f of ["memmo-session-end.sh", "memmo-before-edit.sh"]) {
1106
- const p = join7(cwd, ".claude", "hooks", f);
1107
- if (existsSync7(p)) {
1209
+ const p = join8(cwd, ".claude", "hooks", f);
1210
+ if (existsSync8(p)) {
1108
1211
  rmSync(p, { force: true });
1109
1212
  actions.push(`removed legacy .claude/hooks/${f}`);
1110
1213
  }
@@ -1120,8 +1223,8 @@ async function uninstallCommand(opts = {}) {
1120
1223
 
1121
1224
  // src/commands/chat.ts
1122
1225
  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";
1226
+ import { existsSync as existsSync9 } from "node:fs";
1227
+ import { join as join9, delimiter as delimiter2 } from "node:path";
1125
1228
  import { createInterface as createInterface4 } from "node:readline/promises";
1126
1229
  import Anthropic from "@anthropic-ai/sdk";
1127
1230
  init_api();
@@ -1133,7 +1236,7 @@ function findOnPath2(cmd) {
1133
1236
  const exts = process.platform === "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
1134
1237
  for (const dir of (process.env.PATH ?? "").split(delimiter2)) {
1135
1238
  if (!dir) continue;
1136
- for (const ext of exts) if (existsSync8(join8(dir, cmd + ext))) return join8(dir, cmd + ext);
1239
+ for (const ext of exts) if (existsSync9(join9(dir, cmd + ext))) return join9(dir, cmd + ext);
1137
1240
  }
1138
1241
  return null;
1139
1242
  }
@@ -1273,8 +1376,8 @@ Why: ${opts.why}` : title;
1273
1376
  }
1274
1377
 
1275
1378
  // 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";
1379
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "node:fs";
1380
+ import { join as join10 } from "node:path";
1278
1381
  init_api();
1279
1382
  var START = "<!-- memmo:memory:start -->";
1280
1383
  var END = "<!-- memmo:memory:end -->";
@@ -1323,15 +1426,15 @@ async function syncCommand() {
1323
1426
  `/repos/${config.repoId}/memories?status=approved`
1324
1427
  );
1325
1428
  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));
1429
+ const path = join10(process.cwd(), "CLAUDE.md");
1430
+ const existing = existsSync10(path) ? readFileSync6(path, "utf8") : "";
1431
+ writeFileSync5(path, upsertBlock(existing, block));
1329
1432
  console.log(`Synced ${memories.length} approved memory(ies) into CLAUDE.md.`);
1330
1433
  }
1331
1434
 
1332
1435
  // src/commands/review.ts
1333
1436
  import { execFileSync } from "node:child_process";
1334
- import { writeFileSync as writeFileSync5 } from "node:fs";
1437
+ import { writeFileSync as writeFileSync6 } from "node:fs";
1335
1438
  init_api();
1336
1439
 
1337
1440
  // ../../packages/shared/src/types.ts
@@ -1674,12 +1777,12 @@ function buildReviewPayload(summary, findings, commentable, existingKeys = /* @_
1674
1777
  }
1675
1778
 
1676
1779
  // src/github.ts
1677
- import { readFileSync as readFileSync6, existsSync as existsSync10 } from "node:fs";
1780
+ import { readFileSync as readFileSync7, existsSync as existsSync11 } from "node:fs";
1678
1781
  function readEvent() {
1679
1782
  const p = process.env.GITHUB_EVENT_PATH;
1680
- if (!p || !existsSync10(p)) return {};
1783
+ if (!p || !existsSync11(p)) return {};
1681
1784
  try {
1682
- const ev = JSON.parse(readFileSync6(p, "utf8"));
1785
+ const ev = JSON.parse(readFileSync7(p, "utf8"));
1683
1786
  return {
1684
1787
  title: ev.pull_request?.title,
1685
1788
  body: ev.pull_request?.body ?? void 0,
@@ -1805,7 +1908,7 @@ async function reviewCommand(opts = {}) {
1805
1908
  }
1806
1909
  }
1807
1910
  if (opts.out) {
1808
- writeFileSync5(opts.out, comment);
1911
+ writeFileSync6(opts.out, comment);
1809
1912
  console.error(`Wrote review to ${opts.out} (${res.review?.findings.length ?? 0} findings).`);
1810
1913
  }
1811
1914
  process.stdout.write(comment + "\n");
@@ -1877,8 +1980,8 @@ async function listReviewComments(ctx) {
1877
1980
  }
1878
1981
 
1879
1982
  // 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";
1983
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync4, existsSync as existsSync12 } from "node:fs";
1984
+ import { join as join11 } from "node:path";
1882
1985
  var WORKFLOW = `name: Memmo Review
1883
1986
  on:
1884
1987
  pull_request:
@@ -1905,14 +2008,14 @@ jobs:
1905
2008
  GH_TOKEN: \${{ github.token }}
1906
2009
  `;
1907
2010
  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) {
2011
+ const dir = join11(process.cwd(), ".github", "workflows");
2012
+ const file = join11(dir, "memmo-review.yml");
2013
+ if (existsSync12(file) && !opts.force) {
1911
2014
  console.error(`${file} already exists. Re-run with --force to overwrite.`);
1912
2015
  return;
1913
2016
  }
1914
- mkdirSync3(dir, { recursive: true });
1915
- writeFileSync6(file, WORKFLOW);
2017
+ mkdirSync4(dir, { recursive: true });
2018
+ writeFileSync7(file, WORKFLOW);
1916
2019
  console.log("Wrote .github/workflows/memmo-review.yml\n");
1917
2020
  console.log("Next steps:");
1918
2021
  console.log(" 1. Commit .memmo/config.json and the workflow file.");
@@ -1928,8 +2031,9 @@ init_version();
1928
2031
  var program = new Command();
1929
2032
  program.name("memmo").description("Long-term memory for AI coding agents").version(VERSION);
1930
2033
  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)));
2034
+ 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
2035
  program.command("claude").description("Claude Code integration").command("install").description("Generate CLAUDE.md, .mcp.json, and hooks").action(() => run(() => claudeInstallCommand()));
2036
+ 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
2037
  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
2038
  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
2039
  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.2",
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",