@memrosetta/cli 0.4.8 → 0.5.0

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.
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ isValidTranscriptPath
4
+ } from "../chunk-IKIJVRHU.js";
5
+
6
+ // src/hooks/enforce-claude-code.ts
7
+ import { spawnSync } from "child_process";
8
+ import { mkdtempSync, readFileSync, writeFileSync, existsSync } from "fs";
9
+ import { tmpdir } from "os";
10
+ import { join } from "path";
11
+ function readStdinSync() {
12
+ try {
13
+ return readFileSync(0, "utf-8");
14
+ } catch {
15
+ return "";
16
+ }
17
+ }
18
+ function parseEvent(raw) {
19
+ if (!raw.trim()) return {};
20
+ try {
21
+ return JSON.parse(raw);
22
+ } catch {
23
+ return {};
24
+ }
25
+ }
26
+ function normalizeContent(content) {
27
+ if (typeof content === "string") return content;
28
+ if (Array.isArray(content)) {
29
+ return content.map((part) => {
30
+ if (typeof part === "string") return part;
31
+ if (part && typeof part === "object" && "text" in part && typeof part.text === "string") {
32
+ return part.text;
33
+ }
34
+ return "";
35
+ }).filter((s) => s.length > 0).join("\n");
36
+ }
37
+ return "";
38
+ }
39
+ function findLastAssistantMessage(transcriptPath) {
40
+ if (!isValidTranscriptPath(transcriptPath) || !existsSync(transcriptPath)) {
41
+ return { assistantMessage: "" };
42
+ }
43
+ const lines = readFileSync(transcriptPath, "utf-8").split("\n").filter((l) => l.trim().length > 0);
44
+ let lastAssistant = "";
45
+ let userPromptBeforeAssistant = "";
46
+ let pendingUserPrompt = "";
47
+ for (const line of lines) {
48
+ let parsed;
49
+ try {
50
+ parsed = JSON.parse(line);
51
+ } catch {
52
+ continue;
53
+ }
54
+ const role = parsed.role ?? parsed.message?.role;
55
+ const content = normalizeContent(parsed.message?.content);
56
+ if (!role || !content) continue;
57
+ if (role === "user") {
58
+ pendingUserPrompt = content;
59
+ } else if (role === "assistant") {
60
+ lastAssistant = content;
61
+ userPromptBeforeAssistant = pendingUserPrompt;
62
+ }
63
+ }
64
+ return {
65
+ assistantMessage: lastAssistant,
66
+ userPrompt: userPromptBeforeAssistant || void 0
67
+ };
68
+ }
69
+ function resolveMemrosettaCli() {
70
+ return process.env.MEMROSETTA_BIN ?? "memrosetta";
71
+ }
72
+ function main() {
73
+ try {
74
+ const stdin = readStdinSync();
75
+ const event = parseEvent(stdin);
76
+ if (!event.transcript_path) {
77
+ process.stdout.write("{}\n");
78
+ return;
79
+ }
80
+ const turn = findLastAssistantMessage(event.transcript_path);
81
+ if (!turn.assistantMessage) {
82
+ process.stdout.write("{}\n");
83
+ return;
84
+ }
85
+ const dir = mkdtempSync(join(tmpdir(), "mr-enforce-"));
86
+ const eventFile = join(dir, "event.json");
87
+ writeFileSync(
88
+ eventFile,
89
+ JSON.stringify({
90
+ client: "claude-code",
91
+ turnId: event.session_id,
92
+ assistantMessage: turn.assistantMessage,
93
+ userPrompt: turn.userPrompt,
94
+ cwd: event.cwd,
95
+ transcriptPath: event.transcript_path,
96
+ attempt: 1
97
+ }),
98
+ "utf-8"
99
+ );
100
+ const cli = resolveMemrosettaCli();
101
+ const res = spawnSync(
102
+ cli,
103
+ ["enforce", "stop", "--client", "claude-code", "--event-json", eventFile, "--format", "json"],
104
+ { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }
105
+ );
106
+ if (res.stdout) {
107
+ process.stdout.write(res.stdout);
108
+ }
109
+ if (res.stderr) {
110
+ process.stderr.write(res.stderr);
111
+ }
112
+ } catch (err) {
113
+ process.stderr.write(
114
+ `[memrosetta enforce wrapper] ${err instanceof Error ? err.message : String(err)}
115
+ `
116
+ );
117
+ }
118
+ }
119
+ main();
@@ -1,16 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  closeEngine,
4
- getEngineWithTimeout,
4
+ getEngineWithTimeout
5
+ } from "../chunk-TSA67QME.js";
6
+ import {
5
7
  isValidTranscriptPath,
6
8
  sanitizeSessionId
7
- } from "../chunk-MISLIVUL.js";
9
+ } from "../chunk-IKIJVRHU.js";
8
10
  import {
9
- extractMemories,
10
11
  parseTranscript,
11
- resolveUserId,
12
12
  stripSystemReminders
13
- } from "../chunk-KR63XYFW.js";
13
+ } from "../chunk-UKGD7QXV.js";
14
+ import {
15
+ extractMemories,
16
+ resolveUserId
17
+ } from "../chunk-VR3TRSC7.js";
14
18
  import {
15
19
  getConfig
16
20
  } from "../chunk-SEPYQK3J.js";
@@ -1,15 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  closeEngine,
4
- getEngine,
4
+ getEngine
5
+ } from "../chunk-TSA67QME.js";
6
+ import {
5
7
  isValidTranscriptPath,
6
8
  sanitizeSessionId
7
- } from "../chunk-MISLIVUL.js";
9
+ } from "../chunk-IKIJVRHU.js";
10
+ import {
11
+ parseTranscript
12
+ } from "../chunk-UKGD7QXV.js";
8
13
  import {
9
14
  extractMemories,
10
- parseTranscript,
11
15
  resolveUserId
12
- } from "../chunk-KR63XYFW.js";
16
+ } from "../chunk-VR3TRSC7.js";
13
17
  import {
14
18
  getConfig
15
19
  } from "../chunk-SEPYQK3J.js";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  parseGlobalArgs
4
- } from "./chunk-NU5ZJJXP.js";
4
+ } from "./chunk-SYPVELIW.js";
5
5
  import {
6
6
  closeEngine
7
7
  } from "./chunk-VAVUPQZA.js";
@@ -33,6 +33,7 @@ Commands:
33
33
  compress Run compression only
34
34
  update Update to latest version
35
35
  sync Manage multi-device sync (enable/disable/status/now/device-id)
36
+ enforce Run the enforce backend for client Stop hooks (advanced)
36
37
 
37
38
  Init Options:
38
39
  (no flag) Initialize DB + MCP server (base setup)
@@ -116,17 +117,17 @@ async function main() {
116
117
  try {
117
118
  switch (command) {
118
119
  case "store": {
119
- const mod = await import("./store-V7I5RJ6Y.js");
120
+ const mod = await import("./store-DTN3PTFQ.js");
120
121
  await mod.run(commandOptions);
121
122
  break;
122
123
  }
123
124
  case "search": {
124
- const mod = await import("./search-TVOCWXRB.js");
125
+ const mod = await import("./search-IXV43G2C.js");
125
126
  await mod.run(commandOptions);
126
127
  break;
127
128
  }
128
129
  case "ingest": {
129
- const mod = await import("./ingest-PQFTEAFQ.js");
130
+ const mod = await import("./ingest-JWLBIFEI.js");
130
131
  await mod.run(commandOptions);
131
132
  break;
132
133
  }
@@ -136,57 +137,57 @@ async function main() {
136
137
  break;
137
138
  }
138
139
  case "count": {
139
- const mod = await import("./count-KSE5XYCT.js");
140
+ const mod = await import("./count-WRCFIWWS.js");
140
141
  await mod.run(commandOptions);
141
142
  break;
142
143
  }
143
144
  case "clear": {
144
- const mod = await import("./clear-XVW4R7ZD.js");
145
+ const mod = await import("./clear-HNVVALWL.js");
145
146
  await mod.run(commandOptions);
146
147
  break;
147
148
  }
148
149
  case "relate": {
149
- const mod = await import("./relate-MIOQYWTI.js");
150
+ const mod = await import("./relate-TEZ3GIIY.js");
150
151
  await mod.run(commandOptions);
151
152
  break;
152
153
  }
153
154
  case "invalidate": {
154
- const mod = await import("./invalidate-QPOV2E5U.js");
155
+ const mod = await import("./invalidate-VUHHNZUX.js");
155
156
  await mod.run(commandOptions);
156
157
  break;
157
158
  }
158
159
  case "feedback": {
159
- const mod = await import("./feedback-JXSKOFRS.js");
160
+ const mod = await import("./feedback-O6NKG46O.js");
160
161
  await mod.run(commandOptions);
161
162
  break;
162
163
  }
163
164
  case "working-memory": {
164
- const mod = await import("./working-memory-LJ2XMUE2.js");
165
+ const mod = await import("./working-memory-CH524PJA.js");
165
166
  await mod.run(commandOptions);
166
167
  break;
167
168
  }
168
169
  case "maintain": {
169
- const mod = await import("./maintain-5QUJACCJ.js");
170
+ const mod = await import("./maintain-UUZ76QET.js");
170
171
  await mod.run(commandOptions);
171
172
  break;
172
173
  }
173
174
  case "compress": {
174
- const mod = await import("./compress-LYRY4WUY.js");
175
+ const mod = await import("./compress-VZJHSVJK.js");
175
176
  await mod.run(commandOptions);
176
177
  break;
177
178
  }
178
179
  case "status": {
179
- const mod = await import("./status-RSXRXD7C.js");
180
+ const mod = await import("./status-IHYARQXU.js");
180
181
  await mod.run(commandOptions);
181
182
  break;
182
183
  }
183
184
  case "init": {
184
- const mod = await import("./init-US5IURGE.js");
185
+ const mod = await import("./init-LIWL7Y3H.js");
185
186
  await mod.run(commandOptions);
186
187
  break;
187
188
  }
188
189
  case "reset": {
189
- const mod = await import("./reset-CYY4KYAB.js");
190
+ const mod = await import("./reset-HQFOMYVP.js");
190
191
  await mod.run(commandOptions);
191
192
  break;
192
193
  }
@@ -196,7 +197,12 @@ async function main() {
196
197
  break;
197
198
  }
198
199
  case "sync": {
199
- const mod = await import("./sync-UJBD7575.js");
200
+ const mod = await import("./sync-EHYMBZHE.js");
201
+ await mod.run(commandOptions);
202
+ break;
203
+ }
204
+ case "enforce": {
205
+ const mod = await import("./enforce-PHO4L7C2.js");
200
206
  await mod.run(commandOptions);
201
207
  break;
202
208
  }
@@ -0,0 +1,97 @@
1
+ import {
2
+ parseTranscriptContent
3
+ } from "./chunk-UKGD7QXV.js";
4
+ import {
5
+ classifyTurn
6
+ } from "./chunk-VR3TRSC7.js";
7
+ import {
8
+ optionalOption
9
+ } from "./chunk-SYPVELIW.js";
10
+ import {
11
+ getEngine
12
+ } from "./chunk-VAVUPQZA.js";
13
+ import {
14
+ output,
15
+ outputError
16
+ } from "./chunk-ET6TNQOJ.js";
17
+ import {
18
+ getDefaultUserId
19
+ } from "./chunk-SEPYQK3J.js";
20
+
21
+ // src/commands/ingest.ts
22
+ import { readFileSync } from "fs";
23
+ function turnsToMemories(turns, userId, namespace, sessionShort) {
24
+ const now = (/* @__PURE__ */ new Date()).toISOString();
25
+ const memories = [];
26
+ for (let i = 0; i < turns.length; i++) {
27
+ const turn = turns[i];
28
+ if (turn.content.length < 20) continue;
29
+ const content = turn.content.length > 500 ? turn.content.slice(0, 497) + "..." : turn.content;
30
+ memories.push({
31
+ userId,
32
+ namespace: namespace ?? `session-${sessionShort}`,
33
+ memoryType: classifyTurn(turn),
34
+ content,
35
+ documentDate: now,
36
+ sourceId: `cc-${sessionShort}-${i}`,
37
+ confidence: turn.role === "user" ? 0.9 : 0.8
38
+ });
39
+ }
40
+ return memories;
41
+ }
42
+ async function readStdin() {
43
+ const chunks = [];
44
+ for await (const chunk of process.stdin) {
45
+ chunks.push(chunk);
46
+ }
47
+ return Buffer.concat(chunks).toString("utf-8").trim();
48
+ }
49
+ async function run(options) {
50
+ const { args, format, db, noEmbeddings } = options;
51
+ const userId = optionalOption(args, "--user") ?? getDefaultUserId();
52
+ const file = optionalOption(args, "--file");
53
+ const namespace = optionalOption(args, "--namespace");
54
+ let content;
55
+ if (file) {
56
+ try {
57
+ content = readFileSync(file, "utf-8");
58
+ } catch (err) {
59
+ const msg = err instanceof Error ? err.message : String(err);
60
+ outputError(`Failed to read file: ${msg}`, format);
61
+ process.exitCode = 1;
62
+ return;
63
+ }
64
+ } else {
65
+ content = await readStdin();
66
+ }
67
+ if (!content) {
68
+ outputError("No transcript content provided", format);
69
+ process.exitCode = 1;
70
+ return;
71
+ }
72
+ const parsed = parseTranscriptContent(content);
73
+ const sessionShort = parsed.sessionId ? parsed.sessionId.slice(0, 8) : "unknown";
74
+ const memories = turnsToMemories(
75
+ parsed.turns,
76
+ userId,
77
+ namespace,
78
+ sessionShort
79
+ );
80
+ if (memories.length === 0) {
81
+ output({ stored: 0, message: "No memories extracted from transcript" }, format);
82
+ return;
83
+ }
84
+ const engine = await getEngine({ db, noEmbeddings });
85
+ const stored = await engine.storeBatch(memories);
86
+ output(
87
+ {
88
+ stored: stored.length,
89
+ sessionId: parsed.sessionId || void 0,
90
+ namespace: namespace ?? `session-${sessionShort}`
91
+ },
92
+ format
93
+ );
94
+ }
95
+ export {
96
+ run
97
+ };
@@ -0,0 +1,205 @@
1
+ import {
2
+ getAgentsMdPath,
3
+ getCodexConfigFilePath,
4
+ getCursorMcpConfigPath,
5
+ getCursorRulesPath,
6
+ getGeminiMdPath,
7
+ getGeminiSettingsFilePath,
8
+ getGenericMCPPath,
9
+ isClaudeCodeInstalled,
10
+ registerClaudeCodeHooks,
11
+ registerCodexMCP,
12
+ registerCursorMCP,
13
+ registerGeminiMCP,
14
+ registerGenericMCP,
15
+ updateClaudeMd
16
+ } from "./chunk-IS4IKWPL.js";
17
+ import {
18
+ hasFlag,
19
+ optionalOption
20
+ } from "./chunk-SYPVELIW.js";
21
+ import {
22
+ getDefaultDbPath,
23
+ getEngine
24
+ } from "./chunk-VAVUPQZA.js";
25
+ import {
26
+ output
27
+ } from "./chunk-ET6TNQOJ.js";
28
+ import {
29
+ getConfig,
30
+ writeConfig
31
+ } from "./chunk-SEPYQK3J.js";
32
+
33
+ // src/commands/init.ts
34
+ import { existsSync } from "fs";
35
+ var LANG_FLAG_TO_PRESET = {
36
+ en: "en",
37
+ multi: "multilingual",
38
+ ko: "ko"
39
+ };
40
+ async function run(options) {
41
+ const { args, format, db, noEmbeddings } = options;
42
+ const wantClaudeCode = hasFlag(args, "--claude-code");
43
+ const wantCursor = hasFlag(args, "--cursor");
44
+ const wantCodex = hasFlag(args, "--codex");
45
+ const wantGemini = hasFlag(args, "--gemini");
46
+ const langFlag = optionalOption(args, "--lang");
47
+ const embeddingPreset = langFlag ? LANG_FLAG_TO_PRESET[langFlag] : void 0;
48
+ if (langFlag && !LANG_FLAG_TO_PRESET[langFlag]) {
49
+ process.stderr.write(
50
+ `Unknown --lang value: "${langFlag}". Supported: en, multi, ko
51
+ `
52
+ );
53
+ process.exitCode = 1;
54
+ return;
55
+ }
56
+ {
57
+ const config2 = getConfig();
58
+ const updates = {};
59
+ if (db) {
60
+ updates.dbPath = db;
61
+ }
62
+ if (noEmbeddings) {
63
+ updates.enableEmbeddings = false;
64
+ }
65
+ if (embeddingPreset) {
66
+ updates.embeddingPreset = embeddingPreset;
67
+ }
68
+ if (Object.keys(updates).length > 0) {
69
+ writeConfig({ ...config2, ...updates });
70
+ }
71
+ }
72
+ const config = getConfig();
73
+ const dbPath = db ?? config.dbPath ?? getDefaultDbPath();
74
+ const existed = existsSync(dbPath);
75
+ const engine = await getEngine({ db: dbPath, noEmbeddings });
76
+ await engine.close();
77
+ const result = {
78
+ database: {
79
+ path: dbPath,
80
+ created: !existed
81
+ },
82
+ integrations: {}
83
+ };
84
+ registerGenericMCP();
85
+ result.integrations.mcp = {
86
+ registered: true,
87
+ path: getGenericMCPPath()
88
+ };
89
+ if (wantClaudeCode) {
90
+ const hooksOk = registerClaudeCodeHooks();
91
+ const claudeMdOk = updateClaudeMd();
92
+ result.integrations.claudeCode = {
93
+ hooks: hooksOk,
94
+ mcp: true,
95
+ claudeMd: claudeMdOk
96
+ };
97
+ }
98
+ if (wantCursor) {
99
+ const cursorRulesUpdated = registerCursorMCP();
100
+ result.integrations.cursor = {
101
+ mcp: true,
102
+ path: getCursorMcpConfigPath(),
103
+ cursorRules: cursorRulesUpdated,
104
+ cursorRulesPath: getCursorRulesPath()
105
+ };
106
+ }
107
+ if (wantCodex) {
108
+ const agentsMdUpdated = registerCodexMCP();
109
+ result.integrations.codex = {
110
+ mcp: true,
111
+ path: getCodexConfigFilePath(),
112
+ agentsMd: agentsMdUpdated,
113
+ agentsMdPath: getAgentsMdPath()
114
+ };
115
+ }
116
+ if (wantGemini) {
117
+ const geminiMdUpdated = registerGeminiMCP();
118
+ result.integrations.gemini = {
119
+ mcp: true,
120
+ path: getGeminiSettingsFilePath(),
121
+ geminiMd: geminiMdUpdated,
122
+ geminiMdPath: getGeminiMdPath()
123
+ };
124
+ }
125
+ if (format === "text") {
126
+ printTextOutput(result, wantClaudeCode, wantCursor, wantCodex, wantGemini);
127
+ return;
128
+ }
129
+ output(result, format);
130
+ }
131
+ function printTextOutput(result, claudeCode, cursor, codex = false, gemini = false) {
132
+ const w = (s) => process.stdout.write(s);
133
+ w("\nMemRosetta initialized successfully.\n\n");
134
+ w(" What was set up:\n");
135
+ w(" ----------------------------------------\n");
136
+ w(` Database: ${result.database.path}`);
137
+ w(result.database.created ? " (created)\n" : " (already exists)\n");
138
+ w(` MCP Server: ${result.integrations.mcp.path} (always included)
139
+ `);
140
+ const currentConfig = getConfig();
141
+ if (currentConfig.embeddingPreset && currentConfig.embeddingPreset !== "en") {
142
+ const presetLabels = {
143
+ multilingual: "multilingual (multilingual-e5-small)",
144
+ ko: "Korean (ko-sroberta-multitask)"
145
+ };
146
+ w(` Embeddings: ${presetLabels[currentConfig.embeddingPreset] ?? currentConfig.embeddingPreset}
147
+ `);
148
+ }
149
+ if (claudeCode) {
150
+ const cc = result.integrations.claudeCode;
151
+ if (cc.hooks) {
152
+ w(" Stop Hook: ~/.claude/settings.json (auto-save on session end)\n");
153
+ } else if (!isClaudeCodeInstalled()) {
154
+ w(" Stop Hook: SKIPPED (Claude Code not found at ~/.claude)\n");
155
+ w(' Install Claude Code first, then run "memrosetta init --claude-code" again.\n');
156
+ }
157
+ if (cc.claudeMd) {
158
+ w(" CLAUDE.md: ~/.claude/CLAUDE.md (memory instructions added)\n");
159
+ } else {
160
+ w(" CLAUDE.md: already configured\n");
161
+ }
162
+ }
163
+ if (cursor) {
164
+ w(` Cursor MCP: ${result.integrations.cursor.path}
165
+ `);
166
+ if (result.integrations.cursor.cursorRules) {
167
+ w(` .cursorrules: ${result.integrations.cursor.cursorRulesPath} (memory instructions added)
168
+ `);
169
+ } else {
170
+ w(" .cursorrules: already configured\n");
171
+ }
172
+ }
173
+ if (codex) {
174
+ w(` Codex MCP: ${result.integrations.codex.path}
175
+ `);
176
+ if (result.integrations.codex.agentsMd) {
177
+ w(` AGENTS.md: ${result.integrations.codex.agentsMdPath} (memory instructions added)
178
+ `);
179
+ } else {
180
+ w(" AGENTS.md: already configured\n");
181
+ }
182
+ }
183
+ if (gemini) {
184
+ w(` Gemini MCP: ${result.integrations.gemini.path}
185
+ `);
186
+ if (result.integrations.gemini.geminiMd) {
187
+ w(` GEMINI.md: ${result.integrations.gemini.geminiMdPath} (memory instructions added)
188
+ `);
189
+ } else {
190
+ w(" GEMINI.md: already configured\n");
191
+ }
192
+ }
193
+ w("\n");
194
+ if (!claudeCode && !cursor && !codex && !gemini) {
195
+ w(" MCP is ready. Add --claude-code, --cursor, --codex, or --gemini for tool-specific setup.\n");
196
+ w(" Example: memrosetta init --claude-code\n");
197
+ w("\n");
198
+ }
199
+ if (claudeCode) {
200
+ w(" Restart Claude Code to activate.\n\n");
201
+ }
202
+ }
203
+ export {
204
+ run
205
+ };