memoir-cli 1.5.0 → 1.5.2

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/bin/memoir.js CHANGED
@@ -24,6 +24,7 @@ if (process.argv.length <= 2) {
24
24
  chalk.cyan(' memoir push ') + chalk.gray('— back up your AI memory') + '\n' +
25
25
  chalk.cyan(' memoir restore ') + chalk.gray('— restore on a new machine') + '\n' +
26
26
  chalk.cyan(' memoir status ') + chalk.gray('— see detected AI tools') + '\n\n' +
27
+ chalk.gray(' Tip: use --only claude,gemini to sync specific tools') + '\n\n' +
27
28
  chalk.gray(`v${VERSION}`),
28
29
  { padding: 1, borderStyle: 'round', borderColor: 'cyan', dimBorder: true }
29
30
  ) + '\n');
@@ -58,9 +59,10 @@ program
58
59
  .command('push')
59
60
  .alias('remember')
60
61
  .description('Back up your AI memory to the cloud')
61
- .action(async () => {
62
+ .option('--only <tools>', 'Only sync specific tools (comma-separated: claude,gemini,codex,cursor,copilot,windsurf,aider)')
63
+ .action(async (options) => {
62
64
  try {
63
- await pushCommand();
65
+ await pushCommand(options);
64
66
  } catch (err) {
65
67
  console.error(chalk.red('\n✖ Error during sync:'), err.message);
66
68
  process.exit(1);
@@ -71,9 +73,10 @@ program
71
73
  .command('restore')
72
74
  .alias('pull')
73
75
  .description('Restore your AI memory on this machine')
74
- .action(async () => {
76
+ .option('--only <tools>', 'Only restore specific tools (comma-separated: claude,gemini,codex,cursor,copilot,windsurf,aider)')
77
+ .action(async (options) => {
75
78
  try {
76
- await restoreCommand();
79
+ await restoreCommand(options);
77
80
  } catch (err) {
78
81
  console.error(chalk.red('\n✖ Error during restore:'), err.message);
79
82
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memoir-cli",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "Sync and translate AI memory across devices and tools. Back up Claude, Gemini, Codex, Cursor, Copilot, Windsurf, and Aider configs. Migrate instructions between AI coding assistants with one command.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -55,7 +55,6 @@
55
55
  "fs-extra": "^11.2.0",
56
56
  "gradient-string": "^3.0.0",
57
57
  "inquirer": "^9.2.15",
58
- "memoir-cli": "^1.4.4",
59
58
  "open": "^11.0.0",
60
59
  "ora": "^7.0.1"
61
60
  }
@@ -15,9 +15,13 @@ export const adapters = [
15
15
  icon: '🔵',
16
16
  source: path.join(home, '.gemini'),
17
17
  filter: (src) => {
18
+ const geminiDir = path.join(home, '.gemini');
19
+ const rel = path.relative(geminiDir, src);
20
+ if (src === geminiDir) return true;
21
+ // Only sync config/settings files — skip caches, history, auth, sandbox, etc.
22
+ const allowed = ['settings.json', 'projects.json', 'state.json', 'installation_id', 'trustedFolders.json', '.gitignore', 'GEMINI.md'];
18
23
  const basename = path.basename(src);
19
- const ignored = ['.git', 'oauth_creds.json', 'google_accounts.json', 'tmp', 'history'];
20
- return !ignored.includes(basename);
24
+ return allowed.includes(basename) && !rel.includes(path.sep);
21
25
  }
22
26
  },
23
27
  {
@@ -54,9 +58,13 @@ export const adapters = [
54
58
  icon: '🟢',
55
59
  source: path.join(home, '.codex'),
56
60
  filter: (src) => {
61
+ const codexDir = path.join(home, '.codex');
62
+ const rel = path.relative(codexDir, src);
63
+ if (src === codexDir) return true;
57
64
  const basename = path.basename(src);
58
- const ignored = ['.git', 'sessions', 'cache'];
59
- return !ignored.includes(basename) && !basename.endsWith('.key') && basename !== '.env';
65
+ // Only sync config files
66
+ const allowed = ['config.json', 'settings.json', 'instructions.md'];
67
+ return allowed.includes(basename) && !rel.includes(path.sep);
60
68
  }
61
69
  },
62
70
  {
@@ -66,9 +74,19 @@ export const adapters = [
66
74
  ? path.join(appData, 'Cursor', 'User')
67
75
  : path.join(home, 'Library', 'Application Support', 'Cursor', 'User'),
68
76
  filter: (src) => {
77
+ const cursorDir = isWin
78
+ ? path.join(appData, 'Cursor', 'User')
79
+ : path.join(home, 'Library', 'Application Support', 'Cursor', 'User');
80
+ const rel = path.relative(cursorDir, src);
81
+ if (src === cursorDir) return true;
69
82
  const basename = path.basename(src);
70
- const ignored = ['globalStorage', 'workspaceStorage', 'CachedData', 'Cache', 'GPUCache', 'logs', 'History', 'Backups', 'snippets'];
71
- return !ignored.includes(basename);
83
+ // Only sync settings and keybindings not extensions, cache, storage
84
+ const allowed = ['settings.json', 'keybindings.json', 'rules'];
85
+ const topDir = rel.split(path.sep)[0];
86
+ if (allowed.includes(basename) && !rel.includes(path.sep)) return true;
87
+ // Allow rules directory (cursor rules)
88
+ if (topDir === 'rules') return true;
89
+ return false;
72
90
  }
73
91
  },
74
92
  {
@@ -78,9 +96,14 @@ export const adapters = [
78
96
  ? path.join(appData, 'GitHub Copilot')
79
97
  : path.join(home, '.config', 'github-copilot'),
80
98
  filter: (src) => {
99
+ const copilotDir = isWin
100
+ ? path.join(appData, 'GitHub Copilot')
101
+ : path.join(home, '.config', 'github-copilot');
102
+ if (src === copilotDir) return true;
81
103
  const basename = path.basename(src);
82
- const ignored = ['hosts.json', 'apps.json', 'versions.json'];
83
- return !ignored.includes(basename);
104
+ // Only sync config skip auth tokens and version files
105
+ const allowed = ['settings.json', 'config.json'];
106
+ return allowed.includes(basename);
84
107
  }
85
108
  },
86
109
  {
@@ -90,9 +113,18 @@ export const adapters = [
90
113
  ? path.join(appData, 'Windsurf', 'User')
91
114
  : path.join(home, 'Library', 'Application Support', 'Windsurf', 'User'),
92
115
  filter: (src) => {
116
+ const windsurfDir = isWin
117
+ ? path.join(appData, 'Windsurf', 'User')
118
+ : path.join(home, 'Library', 'Application Support', 'Windsurf', 'User');
119
+ const rel = path.relative(windsurfDir, src);
120
+ if (src === windsurfDir) return true;
93
121
  const basename = path.basename(src);
94
- const ignored = ['workspaceStorage', 'CachedData', 'Cache', 'GPUCache', 'logs', 'History', 'Backups', 'memories', 'snippets'];
95
- return !ignored.includes(basename);
122
+ // Only sync settings and keybindings
123
+ const allowed = ['settings.json', 'keybindings.json', 'rules'];
124
+ const topDir = rel.split(path.sep)[0];
125
+ if (allowed.includes(basename) && !rel.includes(path.sep)) return true;
126
+ if (topDir === 'rules') return true;
127
+ return false;
96
128
  }
97
129
  },
98
130
  {
@@ -139,11 +171,17 @@ function formatSize(bytes) {
139
171
  return `${(bytes / (1024 * 1024)).toFixed(1)}mb`;
140
172
  }
141
173
 
142
- export async function extractMemories(stagingDir, spinner) {
174
+ export async function extractMemories(stagingDir, spinner, onlyFilter = null) {
143
175
  let foundAny = false;
144
176
  const results = [];
145
177
 
146
178
  for (const adapter of adapters) {
179
+ // Skip if --only filter is set and this adapter doesn't match
180
+ if (onlyFilter) {
181
+ const adapterKey = adapter.name.toLowerCase().replace(/ /g, '-').replace('cli', '').replace('openai-', '').trim().replace(/-$/, '');
182
+ const matches = onlyFilter.some(f => adapter.name.toLowerCase().includes(f) || adapterKey.includes(f));
183
+ if (!matches) continue;
184
+ }
147
185
  if (adapter.customExtract) {
148
186
  const dest = path.join(stagingDir, adapter.name.toLowerCase().replace(/ /g, '-'));
149
187
  let foundFile = false;
@@ -56,11 +56,17 @@ async function syncFiles(src, dest, changes) {
56
56
  }
57
57
  }
58
58
 
59
- export async function restoreMemories(sourceDir, spinner) {
59
+ export async function restoreMemories(sourceDir, spinner, onlyFilter = null) {
60
60
  let restoredAny = false;
61
61
  const allResults = [];
62
62
 
63
63
  for (const adapter of adapters) {
64
+ // Skip if --only filter is set and this adapter doesn't match
65
+ if (onlyFilter) {
66
+ const matches = onlyFilter.some(f => adapter.name.toLowerCase().includes(f));
67
+ if (!matches) continue;
68
+ }
69
+
64
70
  const backupDir = path.join(sourceDir, adapter.name.toLowerCase().replace(/ /g, '-'));
65
71
 
66
72
  if (await fs.pathExists(backupDir)) {
@@ -9,7 +9,7 @@ import { getConfig } from '../config.js';
9
9
  import { extractMemories, adapters } from '../adapters/index.js';
10
10
  import { syncToLocal, syncToGit } from '../providers/index.js';
11
11
 
12
- export async function pushCommand() {
12
+ export async function pushCommand(options = {}) {
13
13
  const config = await getConfig();
14
14
 
15
15
  if (!config) {
@@ -28,7 +28,8 @@ export async function pushCommand() {
28
28
  await fs.ensureDir(stagingDir);
29
29
 
30
30
  try {
31
- const foundAny = await extractMemories(stagingDir, spinner);
31
+ const onlyFilter = options.only ? options.only.split(',').map(t => t.trim().toLowerCase()) : null;
32
+ const foundAny = await extractMemories(stagingDir, spinner, onlyFilter);
32
33
 
33
34
  if (!foundAny) {
34
35
  spinner.stop();
@@ -8,7 +8,7 @@ import gradient from 'gradient-string';
8
8
  import { getConfig } from '../config.js';
9
9
  import { fetchFromLocal, fetchFromGit } from '../providers/restore.js';
10
10
 
11
- export async function restoreCommand() {
11
+ export async function restoreCommand(options = {}) {
12
12
  const config = await getConfig();
13
13
 
14
14
  if (!config) {
@@ -29,10 +29,12 @@ export async function restoreCommand() {
29
29
  try {
30
30
  let restored = false;
31
31
 
32
+ const onlyFilter = options.only ? options.only.split(',').map(t => t.trim().toLowerCase()) : null;
33
+
32
34
  if (config.provider === 'local' || config.provider.includes('local')) {
33
- restored = await fetchFromLocal(config, stagingDir, spinner);
35
+ restored = await fetchFromLocal(config, stagingDir, spinner, onlyFilter);
34
36
  } else if (config.provider === 'git' || config.provider.includes('git')) {
35
- restored = await fetchFromGit(config, stagingDir, spinner);
37
+ restored = await fetchFromGit(config, stagingDir, spinner, onlyFilter);
36
38
  } else {
37
39
  spinner.fail(chalk.red(`Unknown provider: ${config.provider}`));
38
40
  return;
@@ -5,7 +5,7 @@ import os from 'os';
5
5
  import { execFileSync } from 'child_process';
6
6
  import { restoreMemories } from '../adapters/restore.js';
7
7
 
8
- export async function fetchFromLocal(config, stagingDir, spinner) {
8
+ export async function fetchFromLocal(config, stagingDir, spinner, onlyFilter = null) {
9
9
  const sourceDir = config.localPath;
10
10
  if (!sourceDir) throw new Error('Local path is not configured.');
11
11
 
@@ -18,10 +18,10 @@ export async function fetchFromLocal(config, stagingDir, spinner) {
18
18
  spinner.text = `Fetching data from local directory: ${chalk.cyan(resolvedSource)}`;
19
19
  await fs.copy(resolvedSource, stagingDir);
20
20
 
21
- return await restoreMemories(stagingDir, spinner);
21
+ return await restoreMemories(stagingDir, spinner, onlyFilter);
22
22
  }
23
23
 
24
- export async function fetchFromGit(config, stagingDir, spinner) {
24
+ export async function fetchFromGit(config, stagingDir, spinner, onlyFilter = null) {
25
25
  const repoUrl = config.gitRepo;
26
26
  if (!repoUrl) throw new Error('Git repository is not configured.');
27
27
 
@@ -33,5 +33,5 @@ export async function fetchFromGit(config, stagingDir, spinner) {
33
33
  throw new Error('Failed to pull from git repository. Ensure your SSH keys are configured and the repository is accessible.');
34
34
  }
35
35
 
36
- return await restoreMemories(stagingDir, spinner);
36
+ return await restoreMemories(stagingDir, spinner, onlyFilter);
37
37
  }