memoir-cli 3.6.0 → 3.6.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/package.json +3 -2
- package/src/adapters/restore.js +76 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memoir-cli",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.1",
|
|
4
4
|
"mcpName": "io.github.camgitt/memoir",
|
|
5
5
|
"description": "MCP server that gives Claude, Cursor, and Gemini long-term memory across sessions. Your AI remembers your codebase, decisions, and preferences — across tools and machines.",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"start": "node bin/memoir.js",
|
|
22
|
-
"test": "bash test-
|
|
22
|
+
"test": "node test-cross-machine.mjs && bash test-cross-machine-e2e.sh",
|
|
23
|
+
"test:legacy": "bash test-local.sh",
|
|
23
24
|
"postinstall": "node -e \"try{const c='\\x1b[36m',r='\\x1b[0m',g='\\x1b[90m';console.log('\\n '+c+'memoir'+r+' installed.\\n Run '+c+'memoir activate'+r+' in any project to give your AI long-term memory.\\n '+g+'https://memoir.sh'+r+'\\n')}catch{}\""
|
|
24
25
|
},
|
|
25
26
|
"keywords": [
|
package/src/adapters/restore.js
CHANGED
|
@@ -14,9 +14,19 @@ export function detectLocalHomeKey(adapterSource) {
|
|
|
14
14
|
if (!fs.existsSync(localProjectsDir)) return null;
|
|
15
15
|
|
|
16
16
|
const entries = fs.readdirSync(localProjectsDir)
|
|
17
|
+
.filter(e => !e.startsWith('.'))
|
|
17
18
|
.filter(e => fs.statSync(path.join(localProjectsDir, e)).isDirectory());
|
|
18
19
|
if (entries.length === 0) return null;
|
|
19
20
|
|
|
21
|
+
// Prefer the key that matches this machine's homedir encoding.
|
|
22
|
+
// Stale foreign dirs (from older memoir versions) can have newer mtimes,
|
|
23
|
+
// so mtime alone is unreliable — the encoded homedir is the ground truth.
|
|
24
|
+
const home = os.homedir();
|
|
25
|
+
const expectedKey = process.platform === 'win32'
|
|
26
|
+
? home.replace(/\\/g, '-').replace(/:/g, '-')
|
|
27
|
+
: '-' + home.replace(/^\//, '').replace(/\//g, '-');
|
|
28
|
+
if (entries.includes(expectedKey)) return expectedKey;
|
|
29
|
+
|
|
20
30
|
// Find dirs with a memory/ subfolder that aren't sub-projects of another dir
|
|
21
31
|
const candidates = entries.filter(entry => {
|
|
22
32
|
const hasMemory = fs.existsSync(path.join(localProjectsDir, entry, 'memory'));
|
|
@@ -143,6 +153,57 @@ function remapProjectPaths(backupDir, adapterSource) {
|
|
|
143
153
|
return remaps;
|
|
144
154
|
}
|
|
145
155
|
|
|
156
|
+
// Scan local ~/.claude/projects/ for foreign home-key dirs left behind by older
|
|
157
|
+
// memoir versions (when remap was unreliable). Merge their memory/ into the local
|
|
158
|
+
// home key and move the stale dir into .memoir-archived-{ts}/ so nothing is lost.
|
|
159
|
+
export async function cleanupLocalForeignKeys(adapterSource) {
|
|
160
|
+
const projectsDir = path.join(adapterSource, 'projects');
|
|
161
|
+
if (!await fs.pathExists(projectsDir)) return { archived: [], merged: 0 };
|
|
162
|
+
|
|
163
|
+
const localHomeKey = detectLocalHomeKey(adapterSource);
|
|
164
|
+
if (!localHomeKey) return { archived: [], merged: 0 };
|
|
165
|
+
|
|
166
|
+
const home = os.homedir();
|
|
167
|
+
const localUsername = path.basename(home).toLowerCase();
|
|
168
|
+
|
|
169
|
+
const entries = (await fs.readdir(projectsDir, { withFileTypes: true }))
|
|
170
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('.'))
|
|
171
|
+
.map(e => e.name);
|
|
172
|
+
|
|
173
|
+
const foreignKeys = [];
|
|
174
|
+
for (const entry of entries) {
|
|
175
|
+
if (entry === localHomeKey) continue;
|
|
176
|
+
if (entry.startsWith(localHomeKey + '-')) continue;
|
|
177
|
+
// Leave alt encodings of THIS machine alone (contain local username)
|
|
178
|
+
if (entry.toLowerCase().includes(localUsername)) continue;
|
|
179
|
+
// Must have memory/ — dirs without it are project dirs that Claude won't read anyway
|
|
180
|
+
if (!await fs.pathExists(path.join(projectsDir, entry, 'memory'))) continue;
|
|
181
|
+
foreignKeys.push(entry);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (foreignKeys.length === 0) return { archived: [], merged: 0 };
|
|
185
|
+
|
|
186
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
187
|
+
const archiveDir = path.join(projectsDir, `.memoir-archived-${ts}`);
|
|
188
|
+
await fs.ensureDir(archiveDir);
|
|
189
|
+
|
|
190
|
+
const localMemDir = path.join(projectsDir, localHomeKey, 'memory');
|
|
191
|
+
await fs.ensureDir(localMemDir);
|
|
192
|
+
|
|
193
|
+
let merged = 0;
|
|
194
|
+
for (const key of foreignKeys) {
|
|
195
|
+
const foreignMemDir = path.join(projectsDir, key, 'memory');
|
|
196
|
+
if (await fs.pathExists(foreignMemDir)) {
|
|
197
|
+
const before = (await fs.readdir(localMemDir)).length;
|
|
198
|
+
await mergeMemoryDirs(foreignMemDir, localMemDir);
|
|
199
|
+
merged += (await fs.readdir(localMemDir)).length - before;
|
|
200
|
+
}
|
|
201
|
+
await fs.move(path.join(projectsDir, key), path.join(archiveDir, key));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return { archived: foreignKeys, merged };
|
|
205
|
+
}
|
|
206
|
+
|
|
146
207
|
// Merge memory dirs from a foreign machine — copies files that don't exist locally,
|
|
147
208
|
// and for files that exist on both, keeps the newer version.
|
|
148
209
|
async function mergeMemoryDirs(src, dest) {
|
|
@@ -291,6 +352,21 @@ export async function restoreMemories(sourceDir, spinner, onlyFilter = null, aut
|
|
|
291
352
|
if (confirm) {
|
|
292
353
|
const changes = { added: [], updated: [], skipped: [] };
|
|
293
354
|
|
|
355
|
+
// Clean up stale foreign home-key dirs left on this machine by older
|
|
356
|
+
// memoir versions. Must run before remap so detectLocalHomeKey sees a
|
|
357
|
+
// clean local state.
|
|
358
|
+
if (adapter.name === 'Claude CLI') {
|
|
359
|
+
try {
|
|
360
|
+
const { archived, merged } = await cleanupLocalForeignKeys(adapter.source);
|
|
361
|
+
if (archived.length > 0) {
|
|
362
|
+
spinner.stop();
|
|
363
|
+
console.log(chalk.gray(` Cleaned up ${archived.length} stale foreign dir(s) on this machine${merged > 0 ? ` (merged ${merged} new file(s))` : ''}`));
|
|
364
|
+
for (const k of archived) console.log(chalk.gray(` archived: ${k}`));
|
|
365
|
+
spinner.start();
|
|
366
|
+
}
|
|
367
|
+
} catch {}
|
|
368
|
+
}
|
|
369
|
+
|
|
294
370
|
// Remap Claude project paths from source machine to this machine
|
|
295
371
|
if (adapter.name === 'Claude CLI') {
|
|
296
372
|
const remaps = remapProjectPaths(backupDir, adapter.source);
|