local-agentic-ai-mem 0.1.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.
- package/README.md +74 -0
- package/dist/commands/import.js +232 -0
- package/dist/commands/install.js +112 -0
- package/dist/commands/status.js +29 -0
- package/dist/commands/uninstall-legacy.js +159 -0
- package/dist/db.js +359 -0
- package/dist/index.js +58 -0
- package/dist/lib/compliance.js +169 -0
- package/dist/lib/compose.js +130 -0
- package/dist/lib/conventions.js +78 -0
- package/dist/lib/embed.js +47 -0
- package/dist/lib/extract.js +297 -0
- package/dist/lib/maintain.js +81 -0
- package/dist/lib/recall.js +152 -0
- package/dist/lib/redact.js +64 -0
- package/dist/lib/tiers.js +405 -0
- package/dist/mcp/server.js +238 -0
- package/package.json +42 -0
- package/templates/hooks/post-tool-use.mjs +29 -0
- package/templates/hooks/session-start.mjs +72 -0
- package/templates/hooks/stop.mjs +113 -0
- package/templates/hooks/user-prompt-submit.mjs +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# agentic-memory
|
|
2
|
+
|
|
3
|
+
Local-first memory for Claude Code. Your code and your memory never leave your machine.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx local-agentic-ai-mem install
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
That is the whole setup. No account, no API key, no server.
|
|
10
|
+
|
|
11
|
+
## What it does
|
|
12
|
+
|
|
13
|
+
Claude Code forgets everything when the terminal closes. This remembers the
|
|
14
|
+
part that matters — what you decided, what you tried that did not work, and
|
|
15
|
+
what someone told you that is not written down anywhere in the code.
|
|
16
|
+
|
|
17
|
+
It runs as hooks, not as a tool you have to remember to call:
|
|
18
|
+
|
|
19
|
+
- **SessionStart** injects what is known to be true about the project
|
|
20
|
+
- **UserPromptSubmit** surfaces relevant past work as you type
|
|
21
|
+
- **Stop** records what the session concluded, or records nothing if it
|
|
22
|
+
concluded nothing
|
|
23
|
+
|
|
24
|
+
An MCP server is registered too, but only as an escape hatch: `recall_memory`
|
|
25
|
+
for an explicit lookup, `save_memory` for something you want kept. The project
|
|
26
|
+
facts are also exposed as an MCP resource, so a client can attach them without
|
|
27
|
+
the model deciding to ask.
|
|
28
|
+
|
|
29
|
+
## What it stores
|
|
30
|
+
|
|
31
|
+
Not everything. A turn that changed no files and concluded nothing is
|
|
32
|
+
discarded rather than saved — on a real corpus that was about 16% of all
|
|
33
|
+
turns, and keeping them measurably made recall worse.
|
|
34
|
+
|
|
35
|
+
What survives is graded:
|
|
36
|
+
|
|
37
|
+
| | |
|
|
38
|
+
|---|---|
|
|
39
|
+
| **Tier 1** | 20–40 facts per project, injected whole every session |
|
|
40
|
+
| **Tier 2** | decisions anchored to files or a commit, searchable |
|
|
41
|
+
| **Tier 3** | raw turns, which decay and get archived |
|
|
42
|
+
|
|
43
|
+
Memories that reach a remote are ranked higher than ones that only ever
|
|
44
|
+
existed locally. A memory restated later, or explicitly replaced, is marked
|
|
45
|
+
superseded rather than deleted — a file changing does not mean the decision
|
|
46
|
+
behind it was wrong.
|
|
47
|
+
|
|
48
|
+
## Where it lives
|
|
49
|
+
|
|
50
|
+
By default `~/.agentic-memory/memory.db`, a single SQLite file.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx local-agentic-ai-mem install --project
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
stores it in `./.agentic-memory/` instead, so committing it means your memory
|
|
57
|
+
travels through git — to your other machines, and to anyone else on the repo.
|
|
58
|
+
Secrets are redacted before anything is written, which is what makes that safe.
|
|
59
|
+
|
|
60
|
+
## Commands
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
install [--project] install hooks + MCP
|
|
64
|
+
mcp run the MCP server on stdio
|
|
65
|
+
import <dump.sql> import an existing MemoryOS database
|
|
66
|
+
status what is stored
|
|
67
|
+
purge-legacy remove an old MemoryOS install
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Privacy
|
|
71
|
+
|
|
72
|
+
There is no server to send anything to. Embeddings are computed locally with
|
|
73
|
+
bge-small-en-v1.5 on CPU. The only network access this package ever makes is
|
|
74
|
+
downloading that model, once.
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.importDump = importDump;
|
|
4
|
+
/**
|
|
5
|
+
* Import a MemoryOS pg_dump into the local database.
|
|
6
|
+
*
|
|
7
|
+
* Five months of captured work is worth keeping, but not verbatim: everything
|
|
8
|
+
* is re-composed, re-tiered and re-embedded on the way in, so what lands is
|
|
9
|
+
* the curated corpus rather than the raw one.
|
|
10
|
+
*/
|
|
11
|
+
const fs_1 = require("fs");
|
|
12
|
+
const db_1 = require("../db");
|
|
13
|
+
const compose_1 = require("../lib/compose");
|
|
14
|
+
const tiers_1 = require("../lib/tiers");
|
|
15
|
+
const redact_1 = require("../lib/redact");
|
|
16
|
+
const embed_1 = require("../lib/embed");
|
|
17
|
+
const maintain_1 = require("../lib/maintain");
|
|
18
|
+
const COLS = [
|
|
19
|
+
"id", "user_id", "project_id", "prompt", "started_at", "duration_ms",
|
|
20
|
+
"tool_calls", "files_touched", "git_commit", "embedding_bge", "embed_model",
|
|
21
|
+
"client_id", "tokens_input", "tokens_output", "tokens_cache_create",
|
|
22
|
+
"tokens_cache_read", "relevance_score", "last_recalled", "recall_count",
|
|
23
|
+
"consolidated", "archived_at", "decisions",
|
|
24
|
+
];
|
|
25
|
+
function splitTuple(t) {
|
|
26
|
+
const s = t.trim().replace(/^\(/, "").replace(/\)$/, "");
|
|
27
|
+
const out = [];
|
|
28
|
+
let i = 0;
|
|
29
|
+
while (i < s.length) {
|
|
30
|
+
while (i < s.length && (s[i] === " " || s[i] === ","))
|
|
31
|
+
i++;
|
|
32
|
+
if (i >= s.length)
|
|
33
|
+
break;
|
|
34
|
+
if (s[i] === "'") {
|
|
35
|
+
let j = i + 1, buf = "";
|
|
36
|
+
while (j < s.length) {
|
|
37
|
+
if (s[j] === "'") {
|
|
38
|
+
if (s[j + 1] === "'") {
|
|
39
|
+
buf += "'";
|
|
40
|
+
j += 2;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
buf += s[j];
|
|
46
|
+
j++;
|
|
47
|
+
}
|
|
48
|
+
out.push(buf);
|
|
49
|
+
i = j + 1;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
let j = i, depth = 0;
|
|
53
|
+
while (j < s.length) {
|
|
54
|
+
if ("[{(".includes(s[j]))
|
|
55
|
+
depth++;
|
|
56
|
+
else if ("]})".includes(s[j]))
|
|
57
|
+
depth--;
|
|
58
|
+
else if (s[j] === "," && depth === 0)
|
|
59
|
+
break;
|
|
60
|
+
j++;
|
|
61
|
+
}
|
|
62
|
+
out.push(s.slice(i, j).trim());
|
|
63
|
+
i = j;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
function pgArray(v) {
|
|
69
|
+
if (!v || v === "NULL" || v === "{}")
|
|
70
|
+
return [];
|
|
71
|
+
const inner = v.replace(/^\{/, "").replace(/\}$/, "");
|
|
72
|
+
const out = [];
|
|
73
|
+
let cur = "", inQ = false;
|
|
74
|
+
for (let i = 0; i < inner.length; i++) {
|
|
75
|
+
const c = inner[i];
|
|
76
|
+
if (inQ) {
|
|
77
|
+
if (c === "\\") {
|
|
78
|
+
cur += inner[++i];
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (c === '"') {
|
|
82
|
+
inQ = false;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
cur += c;
|
|
86
|
+
}
|
|
87
|
+
else if (c === '"')
|
|
88
|
+
inQ = true;
|
|
89
|
+
else if (c === ",") {
|
|
90
|
+
out.push(cur);
|
|
91
|
+
cur = "";
|
|
92
|
+
}
|
|
93
|
+
else
|
|
94
|
+
cur += c;
|
|
95
|
+
}
|
|
96
|
+
if (cur)
|
|
97
|
+
out.push(cur);
|
|
98
|
+
return out.map((x) => x.trim()).filter(Boolean);
|
|
99
|
+
}
|
|
100
|
+
function parse(path) {
|
|
101
|
+
const text = (0, fs_1.readFileSync)(path, "utf8");
|
|
102
|
+
const atoms = [];
|
|
103
|
+
const slugs = new Map();
|
|
104
|
+
let table = null;
|
|
105
|
+
let buf = null;
|
|
106
|
+
for (const line of text.split("\n")) {
|
|
107
|
+
if (buf === null && line.startsWith("INSERT INTO")) {
|
|
108
|
+
const m = line.match(/INSERT INTO "public"\."(\w+)"/);
|
|
109
|
+
table = m ? m[1] : null;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (table === "projects" && buf === null && line.startsWith("(")) {
|
|
113
|
+
const f = splitTuple(line.trim().replace(/[,;]$/, ""));
|
|
114
|
+
if (f.length > 4)
|
|
115
|
+
slugs.set(f[0], f[4] || f[3]);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (table !== "atoms" && buf === null)
|
|
119
|
+
continue;
|
|
120
|
+
if (buf === null) {
|
|
121
|
+
if (!line.startsWith("("))
|
|
122
|
+
continue;
|
|
123
|
+
buf = "";
|
|
124
|
+
}
|
|
125
|
+
buf += line + "\n";
|
|
126
|
+
let inQ = false;
|
|
127
|
+
for (let i = 0; i < buf.length; i++) {
|
|
128
|
+
if (buf[i] === "'") {
|
|
129
|
+
if (inQ && buf[i + 1] === "'") {
|
|
130
|
+
i++;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
inQ = !inQ;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (inQ)
|
|
137
|
+
continue;
|
|
138
|
+
const f = splitTuple(buf.trim().replace(/[,;]$/, ""));
|
|
139
|
+
buf = null;
|
|
140
|
+
if (f.length !== COLS.length)
|
|
141
|
+
continue;
|
|
142
|
+
if (f[20] !== "NULL")
|
|
143
|
+
continue; // already archived upstream
|
|
144
|
+
atoms.push({
|
|
145
|
+
projectId: f[2],
|
|
146
|
+
prompt: f[3] ?? "",
|
|
147
|
+
decisions: pgArray(f[21]),
|
|
148
|
+
files: pgArray(f[7]),
|
|
149
|
+
toolCalls: f[6],
|
|
150
|
+
gitCommit: f[8] === "NULL" ? null : f[8],
|
|
151
|
+
startedAt: f[4],
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return { atoms, slugs };
|
|
155
|
+
}
|
|
156
|
+
async function importDump(path) {
|
|
157
|
+
if (!path) {
|
|
158
|
+
console.error(" usage: agentic-memory import <memoryos-dump.sql>");
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
console.log(`\n Reading ${path}…`);
|
|
162
|
+
const { atoms, slugs } = parse(path);
|
|
163
|
+
console.log(` ${atoms.length} atoms found`);
|
|
164
|
+
// Compose first, so nothing that fails the bar is embedded at all.
|
|
165
|
+
const keep = [];
|
|
166
|
+
let dropped = 0;
|
|
167
|
+
for (const a of atoms) {
|
|
168
|
+
const composed = (0, compose_1.composeAtomContent)({
|
|
169
|
+
prompt: a.prompt,
|
|
170
|
+
decisions: a.decisions,
|
|
171
|
+
files_touched: a.files,
|
|
172
|
+
tool_calls: a.toolCalls,
|
|
173
|
+
git_commit: a.gitCommit,
|
|
174
|
+
});
|
|
175
|
+
if (!composed) {
|
|
176
|
+
dropped++;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
keep.push({ src: a, content: (0, redact_1.redact)(composed.content), decisions: composed.decisions.map(redact_1.redact) });
|
|
180
|
+
}
|
|
181
|
+
console.log(` ${keep.length} worth keeping, ${dropped} discarded as non-durable\n`);
|
|
182
|
+
(0, db_1.db)(process.cwd());
|
|
183
|
+
const projectIds = new Map();
|
|
184
|
+
for (const k of keep) {
|
|
185
|
+
if (projectIds.has(k.src.projectId))
|
|
186
|
+
continue;
|
|
187
|
+
const slug = slugs.get(k.src.projectId) ?? k.src.projectId.slice(0, 8);
|
|
188
|
+
projectIds.set(k.src.projectId, (0, db_1.upsertProject)(slug));
|
|
189
|
+
}
|
|
190
|
+
const t0 = Date.now();
|
|
191
|
+
const report = (n, total) => {
|
|
192
|
+
const rate = n / Math.max((Date.now() - t0) / 1000, 0.001);
|
|
193
|
+
process.stderr.write(`\r embedding ${n}/${total} (${rate.toFixed(1)}/s) `);
|
|
194
|
+
};
|
|
195
|
+
const contentVecs = await (0, embed_1.embedMany)(keep.map((k) => (0, compose_1.indexText)((0, redact_1.redact)(k.src.prompt), k.content)), (n) => report(n, keep.length * 2));
|
|
196
|
+
const promptVecs = await (0, embed_1.embedMany)(keep.map((k) => (0, redact_1.redact)(k.src.prompt)), (n) => report(keep.length + n, keep.length * 2));
|
|
197
|
+
process.stderr.write("\n");
|
|
198
|
+
keep.forEach((k, i) => {
|
|
199
|
+
const atom = {
|
|
200
|
+
id: String(i),
|
|
201
|
+
project_id: k.src.projectId,
|
|
202
|
+
prompt: k.src.prompt,
|
|
203
|
+
content: k.content,
|
|
204
|
+
decisions: k.decisions,
|
|
205
|
+
files_touched: k.src.files,
|
|
206
|
+
tool_calls: k.src.toolCalls,
|
|
207
|
+
git_commit: k.src.gitCommit,
|
|
208
|
+
started_at: k.src.startedAt,
|
|
209
|
+
};
|
|
210
|
+
(0, db_1.insertMemory)({
|
|
211
|
+
projectId: projectIds.get(k.src.projectId),
|
|
212
|
+
prompt: (0, redact_1.redact)(k.src.prompt),
|
|
213
|
+
content: k.content,
|
|
214
|
+
decisions: k.decisions,
|
|
215
|
+
files: k.src.files,
|
|
216
|
+
toolCalls: [],
|
|
217
|
+
gitCommit: k.src.gitCommit,
|
|
218
|
+
anchorSha: k.src.gitCommit,
|
|
219
|
+
confidence: (0, tiers_1.gradeConfidence)({ git_commit: k.src.gitCommit }),
|
|
220
|
+
tier: (0, tiers_1.assignTier)(atom),
|
|
221
|
+
startedAt: k.src.startedAt,
|
|
222
|
+
sessionId: null,
|
|
223
|
+
vec: contentVecs[i],
|
|
224
|
+
promptVec: promptVecs[i],
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
console.log(` imported ${keep.length}`);
|
|
228
|
+
const m = (0, maintain_1.maintain)(true);
|
|
229
|
+
if (m)
|
|
230
|
+
console.log(` superseded ${m.superseded}, tier 2 ${m.tier2}, project facts ${m.facts}`);
|
|
231
|
+
console.log("");
|
|
232
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installHooks = installHooks;
|
|
4
|
+
exports.registerSettings = registerSettings;
|
|
5
|
+
exports.install = install;
|
|
6
|
+
/**
|
|
7
|
+
* Install: write hooks, register the MCP server, create the database.
|
|
8
|
+
*
|
|
9
|
+
* One command, no account, no API key, nothing to sign up for. The hooks are
|
|
10
|
+
* the primary surface — they push context in without the model choosing to
|
|
11
|
+
* ask — so registering them correctly matters more than the MCP entry.
|
|
12
|
+
*/
|
|
13
|
+
const fs_1 = require("fs");
|
|
14
|
+
const os_1 = require("os");
|
|
15
|
+
const path_1 = require("path");
|
|
16
|
+
const db_1 = require("../db");
|
|
17
|
+
const uninstall_legacy_1 = require("./uninstall-legacy");
|
|
18
|
+
const HOOK_EVENTS = {
|
|
19
|
+
SessionStart: "session-start.mjs",
|
|
20
|
+
UserPromptSubmit: "user-prompt-submit.mjs",
|
|
21
|
+
PostToolUse: "post-tool-use.mjs",
|
|
22
|
+
Stop: "stop.mjs",
|
|
23
|
+
};
|
|
24
|
+
function templatesDir() {
|
|
25
|
+
for (const c of [(0, path_1.resolve)(__dirname, "..", "templates"), (0, path_1.resolve)(__dirname, "..", "..", "templates")]) {
|
|
26
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(c, "hooks", "stop.mjs")))
|
|
27
|
+
return c;
|
|
28
|
+
}
|
|
29
|
+
throw new Error("agentic-memory templates not found");
|
|
30
|
+
}
|
|
31
|
+
function installHooks(home = (0, os_1.homedir)()) {
|
|
32
|
+
const hooksDir = (0, path_1.join)(home, ".claude", "hooks", "agentic-memory");
|
|
33
|
+
(0, fs_1.mkdirSync)(hooksDir, { recursive: true });
|
|
34
|
+
const tpl = (0, path_1.join)(templatesDir(), "hooks");
|
|
35
|
+
// The hooks run from ~/.claude/hooks/, outside any node_modules tree, so a
|
|
36
|
+
// bare specifier would not resolve. Stamp in the absolute package root.
|
|
37
|
+
const root = (0, path_1.resolve)(__dirname, "..", "..");
|
|
38
|
+
const written = [];
|
|
39
|
+
for (const f of (0, fs_1.readdirSync)(tpl)) {
|
|
40
|
+
const dest = (0, path_1.join)(hooksDir, f);
|
|
41
|
+
const src = (0, fs_1.readFileSync)((0, path_1.join)(tpl, f), "utf8").replace(/__AGENTIC_MEMORY_ROOT__/g, root);
|
|
42
|
+
(0, fs_1.writeFileSync)(dest, src);
|
|
43
|
+
(0, fs_1.chmodSync)(dest, 0o755);
|
|
44
|
+
written.push(dest);
|
|
45
|
+
}
|
|
46
|
+
return written;
|
|
47
|
+
}
|
|
48
|
+
/** Merge our entries into settings.json without disturbing anything else. */
|
|
49
|
+
function registerSettings(home = (0, os_1.homedir)()) {
|
|
50
|
+
const path = (0, path_1.join)(home, ".claude", "settings.json");
|
|
51
|
+
(0, fs_1.mkdirSync)((0, path_1.join)(home, ".claude"), { recursive: true });
|
|
52
|
+
let settings = {};
|
|
53
|
+
if ((0, fs_1.existsSync)(path)) {
|
|
54
|
+
try {
|
|
55
|
+
settings = JSON.parse((0, fs_1.readFileSync)(path, "utf8"));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
settings = {};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const hooksDir = (0, path_1.join)(home, ".claude", "hooks", "agentic-memory");
|
|
62
|
+
settings.hooks ??= {};
|
|
63
|
+
for (const [event, file] of Object.entries(HOOK_EVENTS)) {
|
|
64
|
+
const command = `node ${(0, path_1.join)(hooksDir, file)}`;
|
|
65
|
+
settings.hooks[event] ??= [];
|
|
66
|
+
const groups = settings.hooks[event];
|
|
67
|
+
const already = groups.some((g) => (g?.hooks ?? []).some((h) => h?.command === command));
|
|
68
|
+
if (!already)
|
|
69
|
+
groups.push({ hooks: [{ type: "command", command }] });
|
|
70
|
+
}
|
|
71
|
+
settings.mcpServers ??= {};
|
|
72
|
+
settings.mcpServers["agentic-memory"] = {
|
|
73
|
+
command: "npx",
|
|
74
|
+
args: ["-y", "local-agentic-ai-mem", "mcp"],
|
|
75
|
+
};
|
|
76
|
+
(0, fs_1.writeFileSync)(path, JSON.stringify(settings, null, 2) + "\n");
|
|
77
|
+
}
|
|
78
|
+
function install(opts = {}) {
|
|
79
|
+
console.log("\n agentic-memory — local-first memory for Claude Code\n");
|
|
80
|
+
if (!opts.keepLegacy) {
|
|
81
|
+
const legacy = (0, uninstall_legacy_1.removeLegacyInstall)();
|
|
82
|
+
const touched = legacy.hooksRemoved.length + (legacy.mcpRemoved ? 1 : 0) + legacy.settingsCleaned;
|
|
83
|
+
if (touched > 0) {
|
|
84
|
+
console.log(` Removed previous MemoryOS install:`);
|
|
85
|
+
if (legacy.hooksRemoved.length)
|
|
86
|
+
console.log(` hooks ${legacy.hooksRemoved.length}`);
|
|
87
|
+
if (legacy.mcpRemoved)
|
|
88
|
+
console.log(` MCP shim removed`);
|
|
89
|
+
if (legacy.credentialRemoved)
|
|
90
|
+
console.log(` credential file removed`);
|
|
91
|
+
if (legacy.settingsCleaned)
|
|
92
|
+
console.log(` settings entries ${legacy.settingsCleaned}`);
|
|
93
|
+
if (legacy.modelsMoved)
|
|
94
|
+
console.log(` model cache kept (moved, not re-downloaded)`);
|
|
95
|
+
console.log("");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (opts.projectLocal) {
|
|
99
|
+
// Memory lives with the code, so it travels through git to other machines
|
|
100
|
+
// and to teammates — and there is still no server.
|
|
101
|
+
(0, fs_1.mkdirSync)((0, path_1.join)(process.cwd(), ".agentic-memory"), { recursive: true });
|
|
102
|
+
console.log(` Storing memory in ./.agentic-memory/ (commit it to share and sync)`);
|
|
103
|
+
}
|
|
104
|
+
(0, db_1.db)(process.cwd());
|
|
105
|
+
(0, db_1.upsertProject)(require("path").basename(process.cwd()), process.cwd());
|
|
106
|
+
const hooks = installHooks();
|
|
107
|
+
registerSettings();
|
|
108
|
+
console.log(` Hooks installed ${hooks.length}`);
|
|
109
|
+
console.log(` MCP registered agentic-memory`);
|
|
110
|
+
console.log(` Database ${require("../db").resolveDbPath(process.cwd())}`);
|
|
111
|
+
console.log("\n Nothing leaves this machine. Restart Claude Code to pick it up.\n");
|
|
112
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.status = status;
|
|
4
|
+
const db_1 = require("../db");
|
|
5
|
+
const uninstall_legacy_1 = require("./uninstall-legacy");
|
|
6
|
+
const db_2 = require("../db");
|
|
7
|
+
const compliance_1 = require("../lib/compliance");
|
|
8
|
+
function status() {
|
|
9
|
+
const path = (0, db_1.resolveDbPath)(process.cwd());
|
|
10
|
+
const conn = (0, db_1.db)(process.cwd());
|
|
11
|
+
const one = (sql) => conn.prepare(sql).get()?.n ?? 0;
|
|
12
|
+
console.log(`\n database ${path}`);
|
|
13
|
+
console.log(` projects ${one("SELECT count(*) n FROM projects")}`);
|
|
14
|
+
console.log(` memories ${one("SELECT count(*) n FROM memories WHERE archived_at IS NULL AND superseded_at IS NULL")}`);
|
|
15
|
+
console.log(` tier 2 ${one("SELECT count(*) n FROM memories WHERE tier = 2 AND archived_at IS NULL AND superseded_at IS NULL")}`);
|
|
16
|
+
console.log(` archived ${one("SELECT count(*) n FROM memories WHERE archived_at IS NOT NULL")}`);
|
|
17
|
+
console.log(` superseded ${one("SELECT count(*) n FROM memories WHERE superseded_at IS NOT NULL")}`);
|
|
18
|
+
console.log(` facts ${one("SELECT count(*) n FROM facts WHERE superseded_at IS NULL")}`);
|
|
19
|
+
// Retrieval numbers say whether the right memory was found. This says
|
|
20
|
+
// whether it was used, which is the only one that decides if it helps.
|
|
21
|
+
console.log((0, compliance_1.formatReport)((0, db_2.complianceReport)()).replace(/^/gm, " ").trimEnd());
|
|
22
|
+
const left = (0, uninstall_legacy_1.findLegacyLeftovers)();
|
|
23
|
+
if (left.length) {
|
|
24
|
+
console.log(`\n Old MemoryOS files still present — run 'agentic-memory purge-legacy':`);
|
|
25
|
+
for (const f of left)
|
|
26
|
+
console.log(` ${f}`);
|
|
27
|
+
}
|
|
28
|
+
console.log("");
|
|
29
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.removeLegacyInstall = removeLegacyInstall;
|
|
4
|
+
exports.cleanSettings = cleanSettings;
|
|
5
|
+
exports.findLegacyLeftovers = findLegacyLeftovers;
|
|
6
|
+
/**
|
|
7
|
+
* Remove a previous MemoryOS install.
|
|
8
|
+
*
|
|
9
|
+
* The old CLI wrote hooks into ~/.claude/hooks/, an MCP shim into
|
|
10
|
+
* ~/.claude/memory/, a credential file and a shared lib into ~/.memoryos/, and
|
|
11
|
+
* entries into ~/.claude/settings.json. All of it points at a hosted API this
|
|
12
|
+
* package does not use, so leaving it in place means two systems firing on
|
|
13
|
+
* every prompt and a live credential sitting on disk.
|
|
14
|
+
*
|
|
15
|
+
* The downloaded embedding model is the one thing worth keeping — it is
|
|
16
|
+
* hundreds of megabytes and identical to what this package needs — so it is
|
|
17
|
+
* moved rather than deleted.
|
|
18
|
+
*/
|
|
19
|
+
const fs_1 = require("fs");
|
|
20
|
+
const os_1 = require("os");
|
|
21
|
+
const path_1 = require("path");
|
|
22
|
+
const LEGACY_HOOKS = [
|
|
23
|
+
"atom-session-start.mjs",
|
|
24
|
+
"atom-user-prompt-submit.mjs",
|
|
25
|
+
"atom-pre-tool-use.mjs",
|
|
26
|
+
"atom-post-tool-use.mjs",
|
|
27
|
+
"atom-stop.mjs",
|
|
28
|
+
"atom-rules-reinject.mjs",
|
|
29
|
+
"session-start-load.mjs",
|
|
30
|
+
"session-end-save.mjs",
|
|
31
|
+
"session-final-save.mjs",
|
|
32
|
+
"pre-compact-save.mjs",
|
|
33
|
+
];
|
|
34
|
+
function removeLegacyInstall(home = (0, os_1.homedir)(), apply = true) {
|
|
35
|
+
const report = {
|
|
36
|
+
hooksRemoved: [],
|
|
37
|
+
mcpRemoved: false,
|
|
38
|
+
libRemoved: false,
|
|
39
|
+
credentialRemoved: false,
|
|
40
|
+
modelsMoved: false,
|
|
41
|
+
settingsCleaned: 0,
|
|
42
|
+
};
|
|
43
|
+
const claudeDir = (0, path_1.join)(home, ".claude");
|
|
44
|
+
const hooksDir = (0, path_1.join)(claudeDir, "hooks");
|
|
45
|
+
for (const name of LEGACY_HOOKS) {
|
|
46
|
+
const p = (0, path_1.join)(hooksDir, name);
|
|
47
|
+
if (!(0, fs_1.existsSync)(p))
|
|
48
|
+
continue;
|
|
49
|
+
report.hooksRemoved.push(name);
|
|
50
|
+
if (apply)
|
|
51
|
+
(0, fs_1.rmSync)(p, { force: true });
|
|
52
|
+
}
|
|
53
|
+
const mcpShim = (0, path_1.join)(claudeDir, "memory", "mcp-server.mjs");
|
|
54
|
+
if ((0, fs_1.existsSync)(mcpShim)) {
|
|
55
|
+
report.mcpRemoved = true;
|
|
56
|
+
if (apply)
|
|
57
|
+
(0, fs_1.rmSync)((0, path_1.join)(claudeDir, "memory"), { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
const oldRoot = (0, path_1.join)(home, ".memoryos");
|
|
60
|
+
if ((0, fs_1.existsSync)(oldRoot)) {
|
|
61
|
+
// Salvage the model cache before removing the directory around it.
|
|
62
|
+
const oldModels = (0, path_1.join)(oldRoot, "models");
|
|
63
|
+
const newModels = (0, path_1.join)(home, ".agentic-memory", "models");
|
|
64
|
+
if ((0, fs_1.existsSync)(oldModels) && !(0, fs_1.existsSync)(newModels)) {
|
|
65
|
+
report.modelsMoved = true;
|
|
66
|
+
if (apply) {
|
|
67
|
+
(0, fs_1.mkdirSync)((0, path_1.join)(home, ".agentic-memory"), { recursive: true });
|
|
68
|
+
try {
|
|
69
|
+
(0, fs_1.renameSync)(oldModels, newModels);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Cross-device rename fails; leaving the model behind only costs a
|
|
73
|
+
// re-download, so it is not worth failing the uninstall over.
|
|
74
|
+
report.modelsMoved = false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(oldRoot, "credentials.json")))
|
|
79
|
+
report.credentialRemoved = true;
|
|
80
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(oldRoot, "lib")))
|
|
81
|
+
report.libRemoved = true;
|
|
82
|
+
if (apply)
|
|
83
|
+
(0, fs_1.rmSync)(oldRoot, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
report.settingsCleaned = cleanSettings((0, path_1.join)(claudeDir, "settings.json"), apply);
|
|
86
|
+
return report;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Strip hook entries and the MCP server registration that point at the old
|
|
90
|
+
* install, leaving everything else in the file untouched.
|
|
91
|
+
*/
|
|
92
|
+
function cleanSettings(path, apply = true) {
|
|
93
|
+
if (!(0, fs_1.existsSync)(path))
|
|
94
|
+
return 0;
|
|
95
|
+
let parsed;
|
|
96
|
+
const raw = (0, fs_1.readFileSync)(path, "utf8");
|
|
97
|
+
try {
|
|
98
|
+
parsed = JSON.parse(raw);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
let removed = 0;
|
|
104
|
+
const isLegacy = (s) => typeof s === "string" && (/atom-[a-z-]+\.mjs/.test(s) || /\.claude\/memory\/mcp-server\.mjs/.test(s) || /memoryos/i.test(s));
|
|
105
|
+
if (parsed.hooks && typeof parsed.hooks === "object") {
|
|
106
|
+
for (const [event, entries] of Object.entries(parsed.hooks)) {
|
|
107
|
+
if (!Array.isArray(entries))
|
|
108
|
+
continue;
|
|
109
|
+
const kept = entries
|
|
110
|
+
.map((group) => {
|
|
111
|
+
if (!group || !Array.isArray(group.hooks))
|
|
112
|
+
return group;
|
|
113
|
+
const before = group.hooks.length;
|
|
114
|
+
group.hooks = group.hooks.filter((h) => !isLegacy(h?.command));
|
|
115
|
+
removed += before - group.hooks.length;
|
|
116
|
+
return group;
|
|
117
|
+
})
|
|
118
|
+
.filter((group) => !Array.isArray(group?.hooks) || group.hooks.length > 0);
|
|
119
|
+
if (kept.length)
|
|
120
|
+
parsed.hooks[event] = kept;
|
|
121
|
+
else
|
|
122
|
+
delete parsed.hooks[event];
|
|
123
|
+
}
|
|
124
|
+
if (Object.keys(parsed.hooks).length === 0)
|
|
125
|
+
delete parsed.hooks;
|
|
126
|
+
}
|
|
127
|
+
if (parsed.mcpServers && typeof parsed.mcpServers === "object") {
|
|
128
|
+
for (const key of Object.keys(parsed.mcpServers)) {
|
|
129
|
+
const entry = parsed.mcpServers[key];
|
|
130
|
+
const args = Array.isArray(entry?.args) ? entry.args : [];
|
|
131
|
+
if (/memoryos|claude-memory/i.test(key) || args.some(isLegacy)) {
|
|
132
|
+
delete parsed.mcpServers[key];
|
|
133
|
+
removed++;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (Object.keys(parsed.mcpServers).length === 0)
|
|
137
|
+
delete parsed.mcpServers;
|
|
138
|
+
}
|
|
139
|
+
if (apply && removed > 0)
|
|
140
|
+
(0, fs_1.writeFileSync)(path, JSON.stringify(parsed, null, 2) + "\n");
|
|
141
|
+
return removed;
|
|
142
|
+
}
|
|
143
|
+
/** Anything left behind that mentions the old install, for reporting. */
|
|
144
|
+
function findLegacyLeftovers(home = (0, os_1.homedir)()) {
|
|
145
|
+
const out = [];
|
|
146
|
+
const hooksDir = (0, path_1.join)(home, ".claude", "hooks");
|
|
147
|
+
if ((0, fs_1.existsSync)(hooksDir)) {
|
|
148
|
+
for (const f of (0, fs_1.readdirSync)(hooksDir)) {
|
|
149
|
+
if (/^atom-|memoryos/i.test(f))
|
|
150
|
+
out.push((0, path_1.join)(hooksDir, f));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(home, ".memoryos")))
|
|
154
|
+
out.push((0, path_1.join)(home, ".memoryos"));
|
|
155
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(home, ".claude", "memory", "mcp-server.mjs"))) {
|
|
156
|
+
out.push((0, path_1.join)(home, ".claude", "memory", "mcp-server.mjs"));
|
|
157
|
+
}
|
|
158
|
+
return out;
|
|
159
|
+
}
|