claude-memory-admin 1.0.1 → 1.2.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 +94 -15
- package/package.json +8 -2
- package/public/app.mjs +849 -408
- package/public/graph.mjs +32 -76
- package/public/index.html +34 -29
- package/public/markdown.mjs +74 -0
- package/public/styles.css +2 -439
- package/public/ui.mjs +309 -0
- package/server.mjs +82 -5
- package/src/instructions.mjs +123 -21
- package/src/model.mjs +4 -1
- package/src/mutate.mjs +355 -1
- package/src/parse.mjs +176 -0
- package/src/settings.mjs +120 -19
- package/src/stores.mjs +34 -4
package/src/settings.mjs
CHANGED
|
@@ -44,14 +44,38 @@ export function managedSettingsFiles() {
|
|
|
44
44
|
return files;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function
|
|
47
|
+
function readJsonDetailed(file) {
|
|
48
|
+
let raw;
|
|
48
49
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
raw = fs.readFileSync(file, 'utf8');
|
|
51
|
+
} catch (err) {
|
|
52
|
+
const status = err.code === 'ENOENT' || err.code === 'ENOTDIR' ? 'absent' : 'unreadable';
|
|
53
|
+
return { status, data: null, error: status === 'absent' ? null : err.message };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let parsed;
|
|
57
|
+
try {
|
|
58
|
+
parsed = JSON.parse(raw);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return { status: 'unparseable', data: null, error: err.message };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
64
|
+
return { status: 'not-object', data: null, error: 'the file is valid JSON but not an object' };
|
|
54
65
|
}
|
|
66
|
+
return { status: 'ok', data: parsed, error: null };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function settingsCandidates({ projectDir = null, settingsFile = null } = {}) {
|
|
70
|
+
const candidates = [];
|
|
71
|
+
for (const file of managedSettingsFiles()) candidates.push({ scope: 'managed', file });
|
|
72
|
+
if (settingsFile) candidates.push({ scope: 'settings-flag', file: settingsFile });
|
|
73
|
+
if (projectDir) {
|
|
74
|
+
candidates.push({ scope: 'local', file: path.join(projectDir, '.claude', 'settings.local.json') });
|
|
75
|
+
candidates.push({ scope: 'project', file: path.join(projectDir, '.claude', 'settings.json') });
|
|
76
|
+
}
|
|
77
|
+
candidates.push({ scope: 'user', file: USER_SETTINGS });
|
|
78
|
+
return candidates;
|
|
55
79
|
}
|
|
56
80
|
|
|
57
81
|
/**
|
|
@@ -64,20 +88,11 @@ function readJson(file) {
|
|
|
64
88
|
* cwd (for the store location, which is decided before any project is chosen) or
|
|
65
89
|
* a specific project's resolved path (for that project's auto memory state).
|
|
66
90
|
*/
|
|
67
|
-
export function settingsLayers(
|
|
68
|
-
const candidates = [];
|
|
69
|
-
for (const file of managedSettingsFiles()) candidates.push({ scope: 'managed', file });
|
|
70
|
-
if (settingsFile) candidates.push({ scope: 'settings-flag', file: settingsFile });
|
|
71
|
-
if (projectDir) {
|
|
72
|
-
candidates.push({ scope: 'local', file: path.join(projectDir, '.claude', 'settings.local.json') });
|
|
73
|
-
candidates.push({ scope: 'project', file: path.join(projectDir, '.claude', 'settings.json') });
|
|
74
|
-
}
|
|
75
|
-
candidates.push({ scope: 'user', file: USER_SETTINGS });
|
|
76
|
-
|
|
91
|
+
export function settingsLayers(options = {}) {
|
|
77
92
|
const layers = [];
|
|
78
|
-
for (const candidate of
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
93
|
+
for (const candidate of settingsCandidates(options)) {
|
|
94
|
+
const read = readJsonDetailed(candidate.file);
|
|
95
|
+
if (read.status === 'ok') layers.push({ ...candidate, data: read.data });
|
|
81
96
|
}
|
|
82
97
|
return layers;
|
|
83
98
|
}
|
|
@@ -149,3 +164,89 @@ export function cleanupPeriodDays(options = {}) {
|
|
|
149
164
|
const value = Number(found?.value);
|
|
150
165
|
return Number.isFinite(value) && value >= 1 ? value : DEFAULT_CLEANUP_PERIOD_DAYS;
|
|
151
166
|
}
|
|
167
|
+
|
|
168
|
+
export const REPORTED_KEYS = [
|
|
169
|
+
{
|
|
170
|
+
key: 'autoMemoryEnabled',
|
|
171
|
+
fallback: true,
|
|
172
|
+
detail: 'Whether Claude Code still writes new memories for this project.',
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
key: 'autoMemoryDirectory',
|
|
176
|
+
fallback: null,
|
|
177
|
+
detail: 'Where the memory store lives. Unset means ~/.claude/projects.',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
key: 'claudeMdExcludes',
|
|
181
|
+
fallback: [],
|
|
182
|
+
detail: 'Instruction files a session skips when it starts.',
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
key: 'cleanupPeriodDays',
|
|
186
|
+
fallback: DEFAULT_CLEANUP_PERIOD_DAYS,
|
|
187
|
+
detail: 'How long session transcripts are kept. Memory files are never swept.',
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
export function settingsDiagnostics(options = {}) {
|
|
192
|
+
return settingsCandidates(options).map((candidate) => {
|
|
193
|
+
const read = readJsonDetailed(candidate.file);
|
|
194
|
+
return { scope: candidate.scope, file: candidate.file, status: read.status, error: read.error };
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function settingsReport(options = {}) {
|
|
199
|
+
const reads = settingsCandidates(options).map((candidate) => ({
|
|
200
|
+
...candidate,
|
|
201
|
+
...readJsonDetailed(candidate.file),
|
|
202
|
+
}));
|
|
203
|
+
const layers = reads.map(({ scope, file, status, error }) => ({ scope, file, status, error }));
|
|
204
|
+
|
|
205
|
+
const keys = REPORTED_KEYS.map(({ key, fallback, detail }) => {
|
|
206
|
+
const values = reads
|
|
207
|
+
.filter((read) => read.status === 'ok' && Object.prototype.hasOwnProperty.call(read.data, key))
|
|
208
|
+
.map((read) => ({ scope: read.scope, file: read.file, value: read.data[key], wins: false }));
|
|
209
|
+
if (values.length) values[0].wins = true;
|
|
210
|
+
const winner = values[0] || null;
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
key,
|
|
214
|
+
detail,
|
|
215
|
+
fallback,
|
|
216
|
+
effective: winner ? { value: winner.value, scope: winner.scope, file: winner.file } : null,
|
|
217
|
+
values,
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const cleanup = keys.find((entry) => entry.key === 'cleanupPeriodDays');
|
|
222
|
+
cleanup.normalized = cleanupPeriodDays(options);
|
|
223
|
+
|
|
224
|
+
const raw = process.env.CLAUDE_CODE_DISABLE_AUTO_MEMORY;
|
|
225
|
+
const disabling = Boolean(raw && raw !== '0' && raw !== 'false');
|
|
226
|
+
|
|
227
|
+
const problems = layers
|
|
228
|
+
.filter((layer) => layer.status !== 'ok' && layer.status !== 'absent')
|
|
229
|
+
.map((layer) => ({ kind: layer.status, scope: layer.scope, file: layer.file, detail: layer.error }));
|
|
230
|
+
|
|
231
|
+
const directory = resolveMemoryDirectory(options);
|
|
232
|
+
if (directory && directory.invalid) {
|
|
233
|
+
problems.push({
|
|
234
|
+
kind: 'invalid-auto-memory-directory',
|
|
235
|
+
scope: directory.scope,
|
|
236
|
+
file: directory.file,
|
|
237
|
+
detail: `"${directory.raw}" is ${directory.invalid}`,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
projectDir: options.projectDir ?? null,
|
|
243
|
+
layers,
|
|
244
|
+
keys,
|
|
245
|
+
env: {
|
|
246
|
+
name: 'CLAUDE_CODE_DISABLE_AUTO_MEMORY',
|
|
247
|
+
value: raw ?? null,
|
|
248
|
+
overrides: disabling ? 'autoMemoryEnabled' : null,
|
|
249
|
+
},
|
|
250
|
+
problems,
|
|
251
|
+
};
|
|
252
|
+
}
|
package/src/stores.mjs
CHANGED
|
@@ -24,6 +24,7 @@ export const AGENT_PROJECT_DIR = path.join('.claude', 'agent-memory');
|
|
|
24
24
|
export const AGENT_LOCAL_DIR = path.join('.claude', 'agent-memory-local');
|
|
25
25
|
|
|
26
26
|
export const STORE_KINDS = {
|
|
27
|
+
global: { label: 'Global', scope: 'user' },
|
|
27
28
|
auto: { label: 'Project', scope: null },
|
|
28
29
|
'agent-user': { label: 'Subagent', scope: 'user' },
|
|
29
30
|
'agent-project': { label: 'Subagent', scope: 'project' },
|
|
@@ -124,17 +125,46 @@ function autoStore(project, root) {
|
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
128
|
+
* The user scope, in the shape of a store so it can be selected like one.
|
|
129
|
+
*
|
|
130
|
+
* It holds no memory: ~/.claude is where the instructions live, not a MEMORY.md
|
|
131
|
+
* index and topic files. It is listed anyway because it is the one thing every
|
|
132
|
+
* session on the machine loads, and until now it had nowhere to be seen. Leaving
|
|
133
|
+
* `hasMemoryDir` false is what keeps it out of full-text search, which skips
|
|
134
|
+
* stores without one, and out of everything else that reads memory files.
|
|
130
135
|
*/
|
|
131
|
-
|
|
136
|
+
function globalStore(home) {
|
|
137
|
+
const dir = path.join(home, '.claude');
|
|
138
|
+
return {
|
|
139
|
+
id: storeId('global', dir),
|
|
140
|
+
kind: 'global',
|
|
141
|
+
dir,
|
|
142
|
+
path: dir,
|
|
143
|
+
label: 'Global',
|
|
144
|
+
sublabel: 'user scope',
|
|
145
|
+
memoryCount: 0,
|
|
146
|
+
hasMemoryDir: false,
|
|
147
|
+
hasIndex: false,
|
|
148
|
+
pathExists: fs.existsSync(dir),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Every store, the user scope first and auto memory after it. Agent stores are
|
|
154
|
+
* found under the project paths auto memory already resolved, so a repository this
|
|
155
|
+
* tool has never seen a session for contributes nothing.
|
|
156
|
+
*
|
|
157
|
+
* `home` is a parameter because the global entry is about the real home directory
|
|
158
|
+
* rather than the memory root, which --root can point somewhere else entirely.
|
|
159
|
+
*/
|
|
160
|
+
export function listStores(root = projectsRoot(), { home = os.homedir() } = {}) {
|
|
132
161
|
const projects = listProjects(root);
|
|
133
162
|
const projectPaths = projects
|
|
134
163
|
.filter((project) => project.pathExists)
|
|
135
164
|
.flatMap((project) => [project.path, ...(project.workingDirs || [])]);
|
|
136
165
|
|
|
137
166
|
return [
|
|
167
|
+
globalStore(home),
|
|
138
168
|
...projects.map((project) => autoStore(project, root)),
|
|
139
169
|
...listAgentStores({ projectPaths }),
|
|
140
170
|
];
|