@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.
- package/README.md +1 -0
- package/dist/index.js +165 -61
- 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
|
|
258
|
-
import { join as
|
|
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
|
|
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
|
-
|
|
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 (!
|
|
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(`
|
|
644
|
-
|
|
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
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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
|
|
808
|
-
import { join as
|
|
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 =
|
|
903
|
-
if (
|
|
1005
|
+
const marker = join6(tmpdir(), `memmo-warned-${key}`);
|
|
1006
|
+
if (existsSync6(marker)) return;
|
|
904
1007
|
try {
|
|
905
|
-
|
|
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 || !
|
|
917
|
-
const raw =
|
|
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
|
|
1004
|
-
import { join as
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
1143
|
+
const mcp = readJson(join7(cwd, ".mcp.json"));
|
|
1041
1144
|
line(mcp?.mcpServers?.memmo ? OK : NO, ".mcp.json (memmo MCP server)");
|
|
1042
|
-
const claudeMd =
|
|
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
|
|
1072
|
-
import { join as
|
|
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 =
|
|
1099
|
-
if (
|
|
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 =
|
|
1103
|
-
if (
|
|
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 =
|
|
1107
|
-
if (
|
|
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
|
|
1124
|
-
import { join as
|
|
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 (
|
|
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
|
|
1277
|
-
import { join as
|
|
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 =
|
|
1327
|
-
const existing =
|
|
1328
|
-
|
|
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
|
|
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
|
|
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 || !
|
|
1783
|
+
if (!p || !existsSync11(p)) return {};
|
|
1681
1784
|
try {
|
|
1682
|
-
const ev = JSON.parse(
|
|
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
|
-
|
|
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
|
|
1881
|
-
import { join as
|
|
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 =
|
|
1909
|
-
const file =
|
|
1910
|
-
if (
|
|
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
|
-
|
|
1915
|
-
|
|
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)));
|