claude-memory-admin 1.0.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/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ The MIT License (MIT)
2
+ =====================
3
+
4
+ Copyright © 2026 Steven Giesel
5
+
6
+ Permission is hereby granted, free of charge, to any person
7
+ obtaining a copy of this software and associated documentation
8
+ files (the “Software”), to deal in the Software without
9
+ restriction, including without limitation the rights to use,
10
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the
12
+ Software is furnished to do so, subject to the following
13
+ conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25
+ OTHER DEALINGS IN THE SOFTWARE.
26
+
package/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # claude-memory-admin
2
+
3
+ [![CI](https://github.com/linkdotnet/claude-memory-admin/actions/workflows/ci.yml/badge.svg)](https://github.com/linkdotnet/claude-memory-admin/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/claude-memory-admin)](https://www.npmjs.com/package/claude-memory-admin)
5
+
6
+ Browse, audit and prune the **auto memory** Claude Code writes for each project.
7
+
8
+ ```bash
9
+ npm install -g claude-memory-admin
10
+ claude-memory-admin
11
+ ```
12
+
13
+ It opens `http://localhost:4173` and reads `~/.claude/projects/*/memory/`.
14
+ Nothing leaves your machine; the server binds `127.0.0.1` only.
15
+
16
+ <sub>Brought to you by [BitSpire](https://bitspire.ch/).</sub>
17
+
18
+ ---
19
+
20
+ ### Prune: see what MEMORY.md actually costs you
21
+
22
+ ![The Prune tab, showing how much of the MEMORY.md load limit a project uses and a sortable list of memories by age, size and inbound links](assets/prune.webp)
23
+
24
+ ### Graph: see how memories link to each other
25
+
26
+ ![The Graph tab, showing memories as nodes coloured by type with wikilinks as edges](assets/graph.webp)
27
+
28
+ ---
29
+
30
+ ## What this is for
31
+
32
+ Claude Code keeps two kinds of memory. This tool is about the second one:
33
+
34
+ | | CLAUDE.md | Auto memory |
35
+ | --- | --- | --- |
36
+ | Written by | you | Claude |
37
+ | Lives in | `./CLAUDE.md`, `~/.claude/CLAUDE.md` | `~/.claude/projects/<project>/memory/` |
38
+ | Contains | rules and instructions | learnings Claude picked up |
39
+
40
+ The auto memory directory holds `MEMORY.md` (a concise index) plus one topic
41
+ file per memory, cross-linked with `[[wikilinks]]`.
42
+
43
+ **`MEMORY.md` is loaded at the start of every session, and only the first 200
44
+ lines or 25KB, whichever comes first.** Everything past that cutoff is silently
45
+ dropped. That single fact drives most of this tool: it shows you how close each
46
+ project is to the cliff, and makes it quick to get back under it.
47
+
48
+ ## What it does
49
+
50
+ - **Projects by real path.** The directories on disk are slugified cwds
51
+ (`-Users-me-repos-Blog`) and the slugification is lossy. The true path is
52
+ recovered from the `cwd` field in the session transcripts stored next to each
53
+ memory directory; anything that cannot be confirmed is shown as the raw slug
54
+ rather than a plausible guess. `autoMemoryDirectory` in `~/.claude/settings.json`
55
+ is honoured if you have moved the store.
56
+ - **Search across every project**: names, descriptions, bodies and index hooks,
57
+ with snippets and match highlighting. Press `/` to jump to it.
58
+ - **Read each memory** with its frontmatter as structured metadata and
59
+ `[[wikilinks]]` as clickable links. Dead links are struck through in red.
60
+ - **Prune** (see below).
61
+ - **Graph** the wikilinks between memories. Hovering dims everything that is not
62
+ a neighbour, which is the only practical way to read a dense cluster.
63
+ - **Health**: orphans, dangling pointers, broken wikilinks, files linked only
64
+ mid-sentence, `name` fields that disagree with the filename.
65
+ - **Delete with cascade**, always reversible.
66
+
67
+ ## Keeping MEMORY.md small
68
+
69
+ The **Prune** tab exists because a bloated index costs tokens on every single
70
+ session and, past the limit, silently stops loading.
71
+
72
+ - **Load meter**. How much of the 200-line / 25KB budget the index uses, and
73
+ which of the two is binding. Frontmatter and HTML comments are excluded,
74
+ because Claude Code strips those before loading.
75
+ - **Long hooks**. The hook is the text after the dash in `MEMORY.md`. A
76
+ 400-character hook can cost more than the memory it points at, and shortening
77
+ it is the cheapest win available.
78
+ - **Possible overlap**. Pairs of memories ranked by shared *rare* vocabulary, to
79
+ surface the same lesson saved three times from three sessions. It is a hint,
80
+ not a verdict.
81
+ - **Bulk prune**. Sort by age, size, or inbound links, tick several, delete them
82
+ as one restore point.
83
+
84
+ Anthropic's own guidance for the index: one line per entry, detail in the topic
85
+ files, merge or drop stale entries.
86
+
87
+ ## Deleting is reversible
88
+
89
+ Delete shows a preview first: the exact lines that will go, the prose mentions it
90
+ will deliberately leave alone, and which other memories link here and will break.
91
+ Those linking memories each get a checkbox, tick any you want removed in the same
92
+ operation, and they are trashed and restored as a single step.
93
+
94
+ **Delete all memory** clears one project: `MEMORY.md` and every memory file, as
95
+ one restore point. Session transcripts (`*.jsonl`) and the project folder are
96
+ never touched, only the contents of `memory/`.
97
+
98
+ **Remove link** in the Health tab clears a `[[wikilink]]` whose target no longer
99
+ exists. The markup goes and the words stay, so `see [[gone]] for details` becomes
100
+ `see gone for details`.
101
+
102
+ Everything lands in `memory/.trash/` with a restore record and comes back from the
103
+ Trash tab. The app never creates memories and never rewrites their prose beyond
104
+ clearing a dead link's brackets.
105
+
106
+ ## Usage
107
+
108
+ ```
109
+ claude-memory-admin [options]
110
+
111
+ -p, --port <n> port to listen on (default 4173)
112
+ -r, --root <dir> memory store to read (default ~/.claude/projects)
113
+ --no-open do not launch a browser
114
+ -h, --help show help
115
+ -v, --version print the version
116
+ ```
117
+
118
+ Point it at a copy if you want to experiment safely:
119
+
120
+ ```bash
121
+ cp -R ~/.claude/projects /tmp/memory-snapshot
122
+ claude-memory-admin --root /tmp/memory-snapshot
123
+ ```
124
+
125
+ ## Safety
126
+
127
+ - Binds `127.0.0.1`; no telemetry, no network calls.
128
+ - Every write target must resolve to a plain `.md` file inside that project's own
129
+ `memory/` directory. `..`, absolute paths and subdirectories are refused.
130
+ - `MEMORY.md` is replaced atomically (temp file, `fsync`, `rename`) with a backup
131
+ restored if anything throws.
132
+ - A test asserts that parsing and rewriting every real `MEMORY.md` with no
133
+ deletions reproduces it byte for byte. Real indexes are hand-written prose with
134
+ headings and nested bullets, and mangling one would be silent.
135
+
136
+ ## Development
137
+
138
+ ```bash
139
+ git clone https://github.com/linkdotnet/claude-memory-admin
140
+ cd claude-memory-admin
141
+ npm install
142
+ npm start # or: node server.mjs
143
+ npm test # runs on a throwaway copy of your real store
144
+ ```
145
+
146
+ No bundler and no build step: the backend is `node:http` plus `node:fs`, and the
147
+ frontend is plain ES modules the browser loads directly. Two runtime dependencies,
148
+ `marked` and `dompurify`, both only for rendering memory bodies safely.
149
+
150
+ | Path | Purpose |
151
+ | --- | --- |
152
+ | `bin/claude-memory-admin.mjs` | CLI entry point and argument parsing |
153
+ | `server.mjs` | HTTP server: static files + JSON API |
154
+ | `src/projects.mjs` | Project discovery, slug → real path resolution |
155
+ | `src/parse.mjs` | `MEMORY.md` and frontmatter parsers, wikilinks |
156
+ | `src/model.mjs` | Joins index, files, graph and health into one model |
157
+ | `src/stats.mjs` | Load-limit accounting and overlap detection |
158
+ | `src/search.mjs` | Full-text search across projects |
159
+ | `src/mutate.mjs` | Delete / restore / unlink, the only code that writes |
160
+ | `public/` | Frontend |
161
+
162
+ Tests run against a committed fixture store under `test/fixtures/store/`, which
163
+ encodes the awkward shapes real memory directories contain. Your own
164
+ `~/.claude/projects` is additionally checked when it exists, always on a
165
+ throwaway copy.
166
+
167
+ ### Releasing
168
+
169
+ Publishing runs in CI. You do not bump anything by hand.
170
+
171
+ Go to **Actions, Release, Run workflow**, pick `patch`, `minor` or `major`, and
172
+ run it. The workflow tests, bumps `package.json` and `package-lock.json`, commits
173
+ and tags the bump, pushes both, then publishes to npm.
174
+
175
+ If you would rather cut the version locally, that still works:
176
+
177
+ ```bash
178
+ npm version patch
179
+ git push --follow-tags
180
+ ```
181
+
182
+ Pushing a `v*` tag publishes whatever is in `package.json`, after checking the
183
+ two agree. Either route refuses to publish a version that is already on npm,
184
+ because npm never allows one to be replaced.
185
+
186
+ It needs one repository secret:
187
+
188
+ | Secret | Value |
189
+ | --- | --- |
190
+ | `NPM_TOKEN` | an npm **Automation** access token with publish rights |
191
+
192
+ Add it under *Settings, Secrets and variables, Actions, New repository secret*.
193
+ An Automation token is the right kind because it bypasses 2FA, which an
194
+ unattended workflow cannot satisfy.
195
+
196
+ Releases are published with [npm provenance](https://docs.npmjs.com/generating-provenance-statements),
197
+ so every version carries a signed, verifiable record of the workflow run and
198
+ commit that built it. That needs the `id-token: write` permission the workflow
199
+ already requests, a public repository, and the `repository` field in
200
+ `package.json` pointing at this repo.
201
+
202
+ Two things that will bite if they apply to you: the workflow pushes the bump
203
+ commit to the default branch, so a branch protection rule that blocks pushes
204
+ will stop it; and the tag it pushes uses `GITHUB_TOKEN`, which by design does
205
+ not trigger other workflows, so there is no double publish.
206
+
207
+ ### Screenshots and demo data
208
+
209
+ The screenshots come from an invented store, never a real one:
210
+
211
+ ```bash
212
+ node scripts/demo-store.mjs /tmp/demo-store
213
+ npm start -- --root /tmp/demo-store
214
+ ```
215
+
216
+ ## License
217
+
218
+ MIT
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ // CLI entry point for the globally installed tool.
3
+
4
+ import { startServer } from '../server.mjs';
5
+ import { readFileSync } from 'node:fs';
6
+ import { fileURLToPath } from 'node:url';
7
+ import path from 'node:path';
8
+
9
+ const here = path.dirname(fileURLToPath(import.meta.url));
10
+ const pkg = JSON.parse(readFileSync(path.join(here, '..', 'package.json'), 'utf8'));
11
+
12
+ const HELP = `
13
+ claude-memory-admin - browse and prune Claude Code's auto memory
14
+
15
+ Usage
16
+ claude-memory-admin [options]
17
+
18
+ Options
19
+ -p, --port <n> port to listen on (default 4173)
20
+ -r, --root <dir> memory store to read (default ~/.claude/projects,
21
+ or autoMemoryDirectory from ~/.claude/settings.json)
22
+ --no-open do not launch a browser
23
+ -h, --help show this help
24
+ -v, --version print the version
25
+
26
+ The store is read from disk on every request. The only writes are deletes,
27
+ restores, and clearing a broken link - each one reversible from the Trash tab.
28
+ `;
29
+
30
+ function parseArgs(argv) {
31
+ const options = { port: undefined, root: undefined, open: true };
32
+ for (let i = 0; i < argv.length; i++) {
33
+ const arg = argv[i];
34
+ const next = () => {
35
+ const value = argv[++i];
36
+ if (value === undefined) {
37
+ console.error(`Missing value for ${arg}`);
38
+ process.exit(1);
39
+ }
40
+ return value;
41
+ };
42
+ switch (arg) {
43
+ case '-h': case '--help': console.log(HELP); process.exit(0); break;
44
+ case '-v': case '--version': console.log(pkg.version); process.exit(0); break;
45
+ case '-p': case '--port': options.port = Number(next()); break;
46
+ case '-r': case '--root': options.root = next(); break;
47
+ case '--no-open': options.open = false; break;
48
+ default:
49
+ if (arg.startsWith('-')) {
50
+ console.error(`Unknown option: ${arg}\n${HELP}`);
51
+ process.exit(1);
52
+ }
53
+ }
54
+ }
55
+ if (options.port !== undefined && (!Number.isInteger(options.port) || options.port < 1 || options.port > 65535)) {
56
+ console.error(`Invalid port: ${options.port}`);
57
+ process.exit(1);
58
+ }
59
+ return options;
60
+ }
61
+
62
+ startServer(parseArgs(process.argv.slice(2)));
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "claude-memory-admin",
3
+ "version": "1.0.0",
4
+ "description": "Browse, audit and prune the auto memory Claude Code keeps under ~/.claude/projects",
5
+ "keywords": [
6
+ "claude",
7
+ "claude-code",
8
+ "anthropic",
9
+ "claude-memory",
10
+ "auto-memory",
11
+ "memory",
12
+ "memory-md",
13
+ "claude-md",
14
+ "cleanup",
15
+ "prune",
16
+ "declutter",
17
+ "audit",
18
+ "context-window",
19
+ "token-budget",
20
+ "knowledge-base",
21
+ "wikilinks",
22
+ "markdown",
23
+ "cli",
24
+ "local-first",
25
+ "developer-tools"
26
+ ],
27
+ "license": "MIT",
28
+ "author": "Steven Giesel",
29
+ "homepage": "https://github.com/linkdotnet/claude-memory-admin#readme",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/linkdotnet/claude-memory-admin.git"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/linkdotnet/claude-memory-admin/issues"
36
+ },
37
+ "type": "module",
38
+ "bin": {
39
+ "claude-memory-admin": "bin/claude-memory-admin.mjs"
40
+ },
41
+ "exports": {
42
+ ".": "./server.mjs"
43
+ },
44
+ "files": [
45
+ "bin/",
46
+ "src/",
47
+ "public/",
48
+ "server.mjs",
49
+ "README.md",
50
+ "LICENSE"
51
+ ],
52
+ "engines": {
53
+ "node": ">=20.6.0"
54
+ },
55
+ "scripts": {
56
+ "start": "node bin/claude-memory-admin.mjs",
57
+ "test": "node --test test/*.test.mjs"
58
+ },
59
+ "dependencies": {
60
+ "dompurify": "^3.2.7",
61
+ "marked": "^16.4.0"
62
+ }
63
+ }