@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.
- package/README.md +1 -0
- package/dist/index.js +159 -53
- 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);
|
|
@@ -804,8 +909,8 @@ async function mcpCommand(opts = {}) {
|
|
|
804
909
|
}
|
|
805
910
|
|
|
806
911
|
// src/commands/hook.ts
|
|
807
|
-
import { readFileSync as
|
|
808
|
-
import { join as
|
|
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 =
|
|
903
|
-
if (
|
|
1007
|
+
const marker = join6(tmpdir(), `memmo-warned-${key}`);
|
|
1008
|
+
if (existsSync6(marker)) return;
|
|
904
1009
|
try {
|
|
905
|
-
|
|
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 || !
|
|
917
|
-
const raw =
|
|
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
|
|
1004
|
-
import { join as
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
1145
|
+
const mcp = readJson(join7(cwd, ".mcp.json"));
|
|
1041
1146
|
line(mcp?.mcpServers?.memmo ? OK : NO, ".mcp.json (memmo MCP server)");
|
|
1042
|
-
const claudeMd =
|
|
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
|
|
1072
|
-
import { join as
|
|
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 =
|
|
1099
|
-
if (
|
|
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 =
|
|
1103
|
-
if (
|
|
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 =
|
|
1107
|
-
if (
|
|
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
|
|
1124
|
-
import { join as
|
|
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 (
|
|
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
|
|
1277
|
-
import { join as
|
|
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 =
|
|
1327
|
-
const existing =
|
|
1328
|
-
|
|
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
|
|
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
|
|
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 || !
|
|
1785
|
+
if (!p || !existsSync11(p)) return {};
|
|
1681
1786
|
try {
|
|
1682
|
-
const ev = JSON.parse(
|
|
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
|
-
|
|
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
|
|
1881
|
-
import { join as
|
|
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 =
|
|
1909
|
-
const file =
|
|
1910
|
-
if (
|
|
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
|
-
|
|
1915
|
-
|
|
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)));
|