memoir-cli 2.0.0 → 2.0.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/README.md CHANGED
@@ -8,6 +8,8 @@
8
8
 
9
9
  *Never lose your AI's context again. Sync and translate your AI memory across every device and tool.*
10
10
 
11
+ ![memoir demo](demo.gif)
12
+
11
13
  </div>
12
14
 
13
15
  ---
package/SHOW_HN.md ADDED
@@ -0,0 +1,23 @@
1
+ Title: Show HN: Memoir – Sync your AI CLI memory across devices and tools
2
+
3
+ URL: https://github.com/camgitt/memoir
4
+
5
+ Text:
6
+
7
+ I got tired of losing my AI setup every time I switched machines. My Claude Code rules, Gemini instructions, Cursor settings — all trapped in hidden dotfiles on one laptop.
8
+
9
+ Memoir is a CLI that backs up, restores, and translates your AI memory across devices and tools. It works with Claude Code, Gemini CLI, OpenAI Codex, Cursor, Copilot, Windsurf, and Aider.
10
+
11
+ Three commands:
12
+
13
+ memoir push # back up AI configs to GitHub or local folder
14
+ memoir restore # restore on a new machine
15
+ memoir migrate --from claude --to gemini # translate between tools
16
+
17
+ The migrate command uses Gemini to intelligently rewrite your instructions — not copy-paste, but actual translation that follows each tool's conventions.
18
+
19
+ It only syncs config and instruction files (settings.json, CLAUDE.md, GEMINI.md, etc). Never touches credentials, auth tokens, or .env files.
20
+
21
+ npm install -g memoir-cli
22
+
23
+ Built with Node.js. MIT licensed. Would love feedback on what tools or workflows to support next.
package/bin/memoir.js CHANGED
@@ -8,6 +8,7 @@ import { pushCommand } from '../src/commands/push.js';
8
8
  import { restoreCommand } from '../src/commands/restore.js';
9
9
  import { statusCommand } from '../src/commands/status.js';
10
10
  import { viewCommand } from '../src/commands/view.js';
11
+ import { diffCommand } from '../src/commands/diff.js';
11
12
  import { migrateCommand } from '../src/commands/migrate.js';
12
13
  import { snapshotCommand } from '../src/commands/snapshot.js';
13
14
  import { resumeCommand } from '../src/commands/resume.js';
@@ -78,6 +79,7 @@ program
78
79
  .alias('pull')
79
80
  .description('Restore your AI memory on this machine')
80
81
  .option('--only <tools>', 'Only restore specific tools (comma-separated: claude,gemini,codex,cursor,copilot,windsurf,aider)')
82
+ .option('-y, --yes', 'Skip confirmation prompts (restore all)')
81
83
  .action(async (options) => {
82
84
  try {
83
85
  await restoreCommand(options);
@@ -112,6 +114,19 @@ program
112
114
  }
113
115
  });
114
116
 
117
+ program
118
+ .command('diff')
119
+ .alias('changes')
120
+ .description('Show what changed since your last backup')
121
+ .action(async () => {
122
+ try {
123
+ await diffCommand();
124
+ } catch (err) {
125
+ console.error(chalk.red('\n✖ Error:'), err.message);
126
+ process.exit(1);
127
+ }
128
+ });
129
+
115
130
  program
116
131
  .command('snapshot')
117
132
  .alias('handoff')
package/demo.cast ADDED
@@ -0,0 +1,269 @@
1
+ {"version":3,"term":{"cols":44,"rows":24,"type":"xterm-256color","theme":{"fg":"#ffffff","bg":"#1e1e1e","palette":"#000000:#990000:#00a600:#999900:#0000b3:#b300b3:#00a6b3:#bfbfbf:#666666:#e60000:#00d900:#e6e600:#0000ff:#e600e6:#00e6e6:#e6e6e6"}},"timestamp":1772934225,"command":"bash demo.sh","env":{"SHELL":"/bin/zsh"}}
2
+ [0.036, "o", "\u001b[3J\u001b[H\u001b[2J"]
3
+ [0.000, "o", "\r\n"]
4
+ [0.000, "o", "\u001b[1;36m memoir — Your AI remembers everything. Sync it everywhere.\u001b[0m\r\n\u001b[0;90m https://github.com/camgitt/memoir\u001b[0m\r\n"]
5
+ [2.008, "o", "\r\n\u001b[1;33m# See what AI tools are on this machine\u001b[0m\r\n"]
6
+ [1.012, "o", "\r\n\u001b[1;32m❯\u001b[0m "]
7
+ [0.000, "o", "n"]
8
+ [0.051, "o", "o"]
9
+ [0.051, "o", "d"]
10
+ [0.051, "o", "e"]
11
+ [0.050, "o", " "]
12
+ [0.050, "o", "/"]
13
+ [0.049, "o", "U"]
14
+ [0.050, "o", "s"]
15
+ [0.048, "o", "e"]
16
+ [0.049, "o", "r"]
17
+ [0.048, "o", "s"]
18
+ [0.049, "o", "/"]
19
+ [0.049, "o", "c"]
20
+ [0.050, "o", "a"]
21
+ [0.051, "o", "m"]
22
+ [0.050, "o", "a"]
23
+ [0.049, "o", "r"]
24
+ [0.048, "o", "t"]
25
+ [0.047, "o", "h"]
26
+ [0.050, "o", "u"]
27
+ [0.049, "o", "r"]
28
+ [0.050, "o", "/"]
29
+ [0.050, "o", "m"]
30
+ [0.049, "o", "e"]
31
+ [0.050, "o", "m"]
32
+ [0.052, "o", "o"]
33
+ [0.049, "o", "i"]
34
+ [0.051, "o", "r"]
35
+ [0.050, "o", "/"]
36
+ [0.050, "o", "b"]
37
+ [0.047, "o", "i"]
38
+ [0.050, "o", "n"]
39
+ [0.043, "o", "/"]
40
+ [0.049, "o", "m"]
41
+ [0.049, "o", "e"]
42
+ [0.048, "o", "m"]
43
+ [0.049, "o", "o"]
44
+ [0.050, "o", "i"]
45
+ [0.048, "o", "r"]
46
+ [0.050, "o", "."]
47
+ [0.049, "o", "j"]
48
+ [0.049, "o", "s"]
49
+ [0.050, "o", " "]
50
+ [0.051, "o", "s"]
51
+ [0.050, "o", "t"]
52
+ [0.049, "o", "a"]
53
+ [0.049, "o", "t"]
54
+ [0.046, "o", "u"]
55
+ [0.049, "o", "s"]
56
+ [0.049, "o", "\r\n"]
57
+ [0.528, "o", "\r\n"]
58
+ [0.002, "o", "\u001b[2m\u001b[36m╭──────────────────────────────────────────╮\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[38;2;116;235;213mm\u001b[39m\u001b[38;2;116;192;235me\u001b[39m\u001b[38;2;116;127;235mm\u001b[39m\u001b[38;2;170;116;235mo\u001b[39m\u001b[38;2;235;116;235mi\u001b[39m\u001b[38;2;235;116;171mr\u001b[39m \u001b[38;2;236;126;116ms\u001b[39m\u001b[38;2;236;191;116mt\u001b[39m\u001b[38;2;215;236;116ma\u001b[39m\u001b[38;2;150;236;116mt\u001b[39m\u001b[38;2;116;236;148mu\u001b[39m\u001b[38;2;116;236;213ms\u001b[39m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[32m✔ Connected\u001b[39m\u001b[90m → \u001b[39m\u001b[36m/var/folders/hz/21xm5f\u001b[39m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[36mzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0E\u001b[39m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[36mpmHHv/memoir-backup\u001b[39m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b"]
59
+ [0.000, "o", "[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[1m\u001b[37mAI Tools\u001b[39m\u001b[22m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[32m✔ \u001b[39m\u001b[37mGemini CLI\u001b[39m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[32m✔ \u001b[39m\u001b[37mClaude CLI\u001b[39m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[32m✔ \u001b[39m\u001b[37mOpenAI Codex\u001b[39m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[90m○ Cursor\u001b[39m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[90m○ GitHub Copilot\u001b[39m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[90m○ Windsurf\u001b[39m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[32m✔ \u001b[39m\u001b[37mAider\u001b[39m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b["]
60
+ [0.000, "o", "2m\u001b[36m│\u001b[39m\u001b[22m \u001b[90m──────────────────────────────\u001b[39m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[37m4 tools ready to sync\u001b[39m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m│\u001b[39m\u001b[22m \u001b[2m\u001b[36m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[36m╰──────────────────────────────────────────╯\u001b[39m\u001b[22m\r\n\r\n"]
61
+ [1.510, "o", "\r\n\u001b[1;33m# Back up all AI memory in one command\u001b[0m\r\n"]
62
+ [1.010, "o", "\r\n\u001b[1;32m❯\u001b[0m n"]
63
+ [0.050, "o", "o"]
64
+ [0.049, "o", "d"]
65
+ [0.050, "o", "e"]
66
+ [0.049, "o", " "]
67
+ [0.047, "o", "/"]
68
+ [0.050, "o", "U"]
69
+ [0.049, "o", "s"]
70
+ [0.047, "o", "e"]
71
+ [0.049, "o", "r"]
72
+ [0.046, "o", "s"]
73
+ [0.050, "o", "/"]
74
+ [0.049, "o", "c"]
75
+ [0.048, "o", "a"]
76
+ [0.048, "o", "m"]
77
+ [0.048, "o", "a"]
78
+ [0.050, "o", "r"]
79
+ [0.046, "o", "t"]
80
+ [0.050, "o", "h"]
81
+ [0.047, "o", "u"]
82
+ [0.045, "o", "r"]
83
+ [0.046, "o", "/"]
84
+ [0.051, "o", "m"]
85
+ [0.049, "o", "e"]
86
+ [0.048, "o", "m"]
87
+ [0.048, "o", "o"]
88
+ [0.047, "o", "i"]
89
+ [0.047, "o", "r"]
90
+ [0.047, "o", "/"]
91
+ [0.048, "o", "b"]
92
+ [0.049, "o", "i"]
93
+ [0.049, "o", "n"]
94
+ [0.047, "o", "/"]
95
+ [0.050, "o", "m"]
96
+ [0.049, "o", "e"]
97
+ [0.049, "o", "m"]
98
+ [0.050, "o", "o"]
99
+ [0.046, "o", "i"]
100
+ [0.047, "o", "r"]
101
+ [0.049, "o", "."]
102
+ [0.047, "o", "j"]
103
+ [0.050, "o", "s"]
104
+ [0.047, "o", " "]
105
+ [0.049, "o", "p"]
106
+ [0.049, "o", "u"]
107
+ [0.047, "o", "s"]
108
+ [0.049, "o", "h"]
109
+ [0.050, "o", "\r\n"]
110
+ [0.441, "o", "\r\n"]
111
+ [0.007, "o", "\u001b[?25l"]
112
+ [0.000, "o", "\u001b[1G"]
113
+ [0.000, "o", "\u001b[1G"]
114
+ [0.000, "o", "\u001b[36m⠋\u001b[39m \u001b[90mScanning for AI tools...\u001b[39m"]
115
+ [0.008, "o", "\u001b[1G"]
116
+ [0.000, "o", "\u001b[0K"]
117
+ [0.000, "o", "\u001b[?25h"]
118
+ [0.000, "o", "\r\n"]
119
+ [0.000, "o", "\u001b[37m\u001b[1m Detected AI tools:\u001b[22m\u001b[39m\r\n\u001b[37m\u001b[1m\u001b[22m\u001b[39m\r\n ├─ 🔵 \u001b[36mGemini CLI\u001b[39m\u001b[90m 3 files, 237B\u001b[39m\r\n"]
120
+ [0.000, "o", " ├─ 🟣 \u001b[36mClaude CLI\u001b[39m\u001b[90m 3 files, 334B\u001b[39m\r\n ├─ 🟢 \u001b[36mOpenAI Codex\u001b[39m\u001b[90m 2 files, 90B\u001b[39m\r\n └─ 🔧 \u001b[36mAider\u001b[39m\u001b[90m 1 files, 49B\u001b[39m\r\n\r\n"]
121
+ [0.000, "o", "\u001b[?25l"]
122
+ [0.000, "o", "\u001b[1G\u001b[36m⠋\u001b[39m \u001b[90mUploading...\u001b[39m"]
123
+ [0.002, "o", "\u001b[1G\u001b[0K"]
124
+ [0.000, "o", "\u001b[?25h"]
125
+ [0.000, "o", "\u001b[32m✔\u001b[39m \u001b[32mSync complete! \u001b[39m\u001b[90m(Saved to /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/memoir-backup)\u001b[39m\r\n\u001b[1G"]
126
+ [0.000, "o", "\u001b[?25h"]
127
+ [0.002, "o", "\r\n\u001b[2m\u001b[32m╭──────────────────────────────────────────╮\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[38;2;116;235;213mB\u001b[39m\u001b[38;2;116;168;235ma\u001b[39m\u001b[38;2;153;116;235mc\u001b[39m\u001b[38;2;235;116;228mk\u001b[39m\u001b[38;2;235;116;138me\u001b[39m\u001b[38;2;236;183;116md\u001b[39m \u001b[38;2;199;236;116mu\u001b[39m\u001b[38;2;116;236;123mp\u001b[39m\u001b[38;2;116;236;213m!\u001b[39m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[36m✔ Gemini CLI\u001b[39m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[36m✔ Claude CLI\u001b[39m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[36m✔ OpenAI Codex\u001b[39m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[36m✔ Aider\u001b[39m \u001b[2m\u001b[32m│\u001b"]
128
+ [0.000, "o", "[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[37m9 files from 4 tools\u001b[39m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[90m→ /var/folders/hz/21xm5fzj4b93bbv25b\u001b[39m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[90m7jnjyr0000gn/T/tmp.GvJ0EpmHHv/memoir\u001b[39m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[90m-backup\u001b[39m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[90mRestore on another machine with:\u001b[39m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[90m\u001b[39m\u001b[36mmemoir restore\u001b[39m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m╰──────────────────────────────────────────╯\u001b[39m\u001b[2"]
129
+ [0.000, "o", "2m\r\n\r\n"]
130
+ [0.002, "o", "\u001b[?25h"]
131
+ [1.509, "o", "\r\n\u001b[1;33m# Now simulate switching to a new machine...\u001b[0m\r\n"]
132
+ [2.034, "o", "\u001b[1;31m [wiped all AI configs]\u001b[0m\r\n"]
133
+ [1.509, "o", "\r\n\u001b[1;33m# Restore everything on the new machine\u001b[0m\r\n"]
134
+ [1.010, "o", "\r\n\u001b[1;32m❯\u001b[0m "]
135
+ [0.000, "o", "n"]
136
+ [0.047, "o", "o"]
137
+ [0.047, "o", "d"]
138
+ [0.048, "o", "e"]
139
+ [0.049, "o", " "]
140
+ [0.050, "o", "/"]
141
+ [0.047, "o", "U"]
142
+ [0.050, "o", "s"]
143
+ [0.049, "o", "e"]
144
+ [0.046, "o", "r"]
145
+ [0.049, "o", "s"]
146
+ [0.050, "o", "/"]
147
+ [0.045, "o", "c"]
148
+ [0.050, "o", "a"]
149
+ [0.049, "o", "m"]
150
+ [0.048, "o", "a"]
151
+ [0.047, "o", "r"]
152
+ [0.047, "o", "t"]
153
+ [0.047, "o", "h"]
154
+ [0.047, "o", "u"]
155
+ [0.048, "o", "r"]
156
+ [0.046, "o", "/"]
157
+ [0.050, "o", "m"]
158
+ [0.049, "o", "e"]
159
+ [0.049, "o", "m"]
160
+ [0.047, "o", "o"]
161
+ [0.049, "o", "i"]
162
+ [0.045, "o", "r"]
163
+ [0.049, "o", "/"]
164
+ [0.046, "o", "b"]
165
+ [0.049, "o", "i"]
166
+ [0.050, "o", "n"]
167
+ [0.049, "o", "/"]
168
+ [0.050, "o", "m"]
169
+ [0.050, "o", "e"]
170
+ [0.049, "o", "m"]
171
+ [0.049, "o", "o"]
172
+ [0.049, "o", "i"]
173
+ [0.049, "o", "r"]
174
+ [0.050, "o", "."]
175
+ [0.048, "o", "j"]
176
+ [0.049, "o", "s"]
177
+ [0.049, "o", " "]
178
+ [0.051, "o", "r"]
179
+ [0.046, "o", "e"]
180
+ [0.050, "o", "s"]
181
+ [0.048, "o", "t"]
182
+ [0.050, "o", "o"]
183
+ [0.049, "o", "r"]
184
+ [0.050, "o", "e"]
185
+ [0.049, "o", " "]
186
+ [0.049, "o", "-"]
187
+ [0.049, "o", "-"]
188
+ [0.049, "o", "y"]
189
+ [0.048, "o", "e"]
190
+ [0.049, "o", "s"]
191
+ [0.044, "o", "\r\n"]
192
+ [0.446, "o", "\r\n"]
193
+ [0.007, "o", "\u001b[?25l"]
194
+ [0.000, "o", "\u001b[1G"]
195
+ [0.000, "o", "\u001b[1G"]
196
+ [0.001, "o", "\u001b[36m⠋\u001b[39m \u001b[90mFetching memories from local storage...\u001b[39m"]
197
+ [0.005, "o", "\u001b[1G"]
198
+ [0.000, "o", "\u001b[0K"]
199
+ [0.000, "o", "\u001b[?25h"]
200
+ [0.000, "o", "\r\n\u001b[36m🔵 Found backup for \u001b[1mGemini CLI\u001b[22m\u001b[39m\r\n"]
201
+ [0.000, "o", "\u001b[90m Will restore to: /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.gemini\u001b[39m\r\n"]
202
+ [0.000, "o", "\u001b[32m Auto-restoring Gemini CLI...\u001b[39m\r\n\u001b[?25l"]
203
+ [0.000, "o", "\u001b[1G"]
204
+ [0.000, "o", "\u001b[36m⠋\u001b[39m Fetching data from local directory: \u001b[36m/var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/memoir-backup\u001b[39m"]
205
+ [0.001, "o", "\u001b[1G\u001b[0K"]
206
+ [0.000, "o", "\u001b[1A\u001b[0K\u001b[1A\u001b[0K\u001b[?25h"]
207
+ [0.000, "o", "\u001b[32m\u001b[1m\u001b[22m\u001b[39m\r\n\u001b[32m\u001b[1m 🔵 Gemini CLI — 3 file(s) restored to \u001b[4m/var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.gemini\u001b[24m\u001b[22m\u001b[39m\r\n"]
208
+ [0.000, "o", "\u001b[32m + GEMINI.md\u001b[39m\u001b[90m (new)\u001b[39m\r\n"]
209
+ [0.000, "o", "\u001b[32m + projects.json\u001b[39m\u001b[90m (new)\u001b[39m\r\n"]
210
+ [0.000, "o", "\u001b[32m + settings.json\u001b[39m\u001b[90m (new)\u001b[39m\r\n\u001b[?25l"]
211
+ [0.000, "o", "\u001b[1G\u001b[36m⠋\u001b[39m Restoring \u001b[36mGemini CLI\u001b[39m to /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.gemini..."]
212
+ [0.000, "o", "\u001b[1G\u001b[0K\u001b[1A\u001b[0K\u001b[1A"]
213
+ [0.000, "o", "\u001b[0K\u001b[?25h"]
214
+ [0.000, "o", "\r\n\u001b[36m🟣 Found backup for \u001b[1mClaude CLI\u001b[22m\u001b[39m\r\n\u001b[90m Will restore to: /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.claude\u001b[39m\r\n"]
215
+ [0.000, "o", "\u001b[32m Auto-restoring Claude CLI...\u001b[39m\r\n"]
216
+ [0.000, "o", "\u001b[?25l"]
217
+ [0.000, "o", "\u001b[1G\u001b[36m⠋\u001b[39m Restoring \u001b[36mGemini CLI\u001b[39m to /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.gemini..."]
218
+ [0.001, "o", "\u001b[1G\u001b[0K\u001b[1A\u001b[0K\u001b[1A"]
219
+ [0.000, "o", "\u001b[0K"]
220
+ [0.000, "o", "\u001b[?25h"]
221
+ [0.000, "o", "\u001b[90m Remapping project path: -Users-dev → -var-folders-hz-21xm5fzj4b93bbv25b7jnjyr0000gn-T-tmp.GvJ0EpmHHv\u001b[39m\r\n\u001b[?25l"]
222
+ [0.000, "o", "\u001b[1G\u001b[36m⠋\u001b[39m Restoring \u001b[36mGemini CLI\u001b[39m to /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.gemini..."]
223
+ [0.001, "o", "\u001b[1G\u001b[0K\u001b[1A"]
224
+ [0.000, "o", "\u001b[0K\u001b[1A\u001b[0K"]
225
+ [0.000, "o", "\u001b[?25h"]
226
+ [0.001, "o", "\u001b[32m\u001b[1m\u001b[22m\u001b[39m\r\n\u001b[32m\u001b[1m 🟣 Claude CLI — 3 file(s) restored to \u001b[4m/var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.claude\u001b[24m\u001b[22m\u001b[39m\r\n\u001b[32m + projects/-var-folders-hz-21xm5fzj4b93bbv25b7jnjyr0000gn-T-tmp.GvJ0EpmHHv/webapp/CLAUDE.md\u001b[39m\u001b[90m (new)\u001b[39m\r\n\u001b[32m + projects/-var-folders-hz-21xm5fzj4b93bbv25b7jnjyr0000gn-T-tmp.GvJ0EpmHHv/webapp/memory/MEMORY.md\u001b[39m\u001b[90m (new)\u001b[39m\r\n\u001b[32m + settings.json\u001b[39m\u001b[90m (new)\u001b[39m\r\n\u001b[?25l\u001b[1G\u001b[36m⠋\u001b[39m Restoring \u001b[36mClaude CLI\u001b[39m to /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.claude..."]
227
+ [0.000, "o", "\u001b[1G\u001b[0K\u001b[1A\u001b[0K\u001b[1A\u001b[0K\u001b[?25h"]
228
+ [0.000, "o", "\r\n\u001b[36m🟢 Found backup for \u001b[1mOpenAI Codex\u001b[22m\u001b[39m\r\n\u001b[90m Will restore to: /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.codex\u001b[39m\r\n"]
229
+ [0.000, "o", "\u001b[32m Auto-restoring OpenAI Codex...\u001b[39m\r\n"]
230
+ [0.000, "o", "\u001b[?25l"]
231
+ [0.000, "o", "\u001b[1G\u001b[36m⠋\u001b[39m Restoring \u001b[36mClaude CLI\u001b[39m to /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.claude..."]
232
+ [0.000, "o", "\u001b[1G\u001b[0K\u001b[1A\u001b[0K\u001b[1A\u001b[0K\u001b[?25h"]
233
+ [0.000, "o", "\u001b[32m\u001b[1m\u001b[22m\u001b[39m\r\n\u001b[32m\u001b[1m 🟢 OpenAI Codex — 2 file(s) restored to \u001b[4m/var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.codex\u001b[24m\u001b[22m\u001b[39m\r\n\u001b[32m + config.json\u001b[39m\u001b[90m (new)\u001b[39m\r\n"]
234
+ [0.000, "o", "\u001b[32m + instructions.md\u001b[39m\u001b[90m (new)\u001b[39m\r\n\u001b[?25l"]
235
+ [0.001, "o", "\u001b[1G\u001b[36m⠋\u001b[39m Restoring \u001b[36mOpenAI Codex\u001b[39m to /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.codex..."]
236
+ [0.000, "o", "\u001b[1G\u001b[0K"]
237
+ [0.000, "o", "\u001b[1A\u001b[0K\u001b[1A"]
238
+ [0.000, "o", "\u001b[0K"]
239
+ [0.000, "o", "\u001b[?25h"]
240
+ [0.000, "o", "\r\n\u001b[36m🔧 Found backup for \u001b[1mAider\u001b[22m\u001b[39m\r\n\u001b[90m Will restore to: /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv\u001b[39m\r\n"]
241
+ [0.000, "o", "\u001b[32m Auto-restoring Aider...\u001b[39m\r\n"]
242
+ [0.000, "o", "\u001b[?25l"]
243
+ [0.000, "o", "\u001b[1G\u001b[36m⠋\u001b[39m Restoring \u001b[36mOpenAI Codex\u001b[39m to /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.codex..."]
244
+ [0.000, "o", "\u001b[1G\u001b[0K\u001b[1A\u001b[0K\u001b[1A"]
245
+ [0.000, "o", "\u001b[0K\u001b[?25h"]
246
+ [0.000, "o", "\u001b[32m\u001b[1m\u001b[22m\u001b[39m\r\n\u001b[32m\u001b[1m 🔧 Aider — 1 file(s) restored to \u001b[4m/var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv\u001b[24m\u001b[22m\u001b[39m\r\n"]
247
+ [0.000, "o", "\u001b[32m + .aider.conf.yml\u001b[39m\u001b[90m (new)\u001b[39m\r\n"]
248
+ [0.000, "o", "\u001b[?25l"]
249
+ [0.000, "o", "\u001b[1G"]
250
+ [0.000, "o", "\u001b[36m⠋\u001b[39m Restoring \u001b[36mOpenAI Codex\u001b[39m to /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.codex..."]
251
+ [0.000, "o", "\u001b[1G\u001b[0K\u001b[1A"]
252
+ [0.000, "o", "\u001b[0K\u001b[1A\u001b[0K"]
253
+ [0.000, "o", "\u001b[?25h"]
254
+ [0.000, "o", "\r\n\u001b[90m────────────────────────────────────────\u001b[39m\r\n"]
255
+ [0.000, "o", "\u001b[1m\u001b[37m\u001b[39m\u001b[22m\r\n\u001b[1m\u001b[37m Restore Summary:\u001b[39m\u001b[22m\r\n\u001b[1m\u001b[37m\u001b[39m\u001b[22m\r\n"]
256
+ [0.000, "o", " 🔵 \u001b[37mGemini CLI\u001b[39m\r\n\u001b[90m 3 file(s) → /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.gemini\u001b[39m\r\n"]
257
+ [0.000, "o", " 🟣 \u001b[37mClaude CLI\u001b[39m\r\n"]
258
+ [0.000, "o", "\u001b[90m 3 file(s) → /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.claude\u001b[39m\r\n"]
259
+ [0.000, "o", " 🟢 \u001b[37mOpenAI Codex\u001b[39m\r\n"]
260
+ [0.000, "o", "\u001b[90m 2 file(s) → /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv/.codex\u001b[39m\r\n 🔧 \u001b[37mAider\u001b[39m\r\n\u001b[90m 1 file(s) → /var/folders/hz/21xm5fzj4b93bbv25b7jnjyr0000gn/T/tmp.GvJ0EpmHHv\u001b[39m\r\n"]
261
+ [0.000, "o", "\r\n\u001b[1G"]
262
+ [0.000, "o", "\u001b[?25h"]
263
+ [0.001, "o", "\u001b[2m\u001b[32m╭──────────────────────────────────────────╮\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[38;2;116;235;213mD\u001b[39m\u001b[38;2;153;116;235mo\u001b[39m\u001b[38;2;235;116;138mn\u001b[39m\u001b[38;2;199;236;116me\u001b[39m\u001b[38;2;116;236;213m!\u001b[39m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[37mYour AI tools have their memories\u001b[39m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[37mback.\u001b[39m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[90mRestart your AI tools to pick up the\u001b[39m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[90mchanges.\u001b[39m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[32m│\u001b[39m\u001b[22m \u001b[2m\u001b[32m│\u001b[39m\u001b[22m\r\n\u001b[2m\u001b[3"]
264
+ [0.000, "o", "2m╰──────────────────────────────────────────╯\u001b[39m\u001b[22m\r\n\r\n"]
265
+ [0.001, "o", "\u001b[?25h"]
266
+ [1.508, "o", "\r\n\u001b[1;33m# All done. 11 files restored across 4 tools in seconds.\u001b[0m\r\n"]
267
+ [2.015, "o", "\r\n\u001b[1;32m npm install -g memoir-cli\u001b[0m\r\n"]
268
+ [0.000, "o", "\u001b[0;90m Works with Claude, Gemini, Codex, Cursor, Copilot, Windsurf, and Aider.\u001b[0m\r\n\r\n"]
269
+ [3.025, "x", "0"]
package/demo.gif ADDED
Binary file
package/demo.sh ADDED
@@ -0,0 +1,99 @@
1
+ #!/bin/bash
2
+ # Scripted demo for asciinema recording
3
+ # Usage: asciinema rec demo.cast -c "bash demo.sh"
4
+
5
+ # Simulate typing with a delay
6
+ type_cmd() {
7
+ local cmd="$1"
8
+ echo ""
9
+ echo -ne "\033[1;32m❯\033[0m "
10
+ for ((i=0; i<${#cmd}; i++)); do
11
+ echo -n "${cmd:$i:1}"
12
+ sleep 0.04
13
+ done
14
+ echo ""
15
+ sleep 0.3
16
+ eval "$cmd"
17
+ sleep 1.5
18
+ }
19
+
20
+ narrate() {
21
+ echo ""
22
+ echo -e "\033[1;33m# $1\033[0m"
23
+ sleep 1
24
+ }
25
+
26
+ export FAKE_HOME=$(mktemp -d)
27
+ export BACKUP_DIR="$FAKE_HOME/memoir-backup"
28
+ MEMOIR_BIN="$(cd "$(dirname "$0")" && pwd)/bin/memoir.js"
29
+
30
+ # Pre-setup: create mock configs and memoir config silently
31
+ mkdir -p "$FAKE_HOME/.gemini"
32
+ echo '{ "theme": "dark", "model": "gemini-2.5-pro" }' > "$FAKE_HOME/.gemini/settings.json"
33
+ cat > "$FAKE_HOME/.gemini/GEMINI.md" << 'EOF'
34
+ # My Gemini Instructions
35
+ Always use TypeScript. Prefer functional patterns. Keep responses concise.
36
+ Never use `any` type. Always handle errors explicitly.
37
+ EOF
38
+ echo '{ "recent": ["/home/dev/webapp"] }' > "$FAKE_HOME/.gemini/projects.json"
39
+
40
+ mkdir -p "$FAKE_HOME/.claude/projects/-Users-dev/webapp/memory"
41
+ echo '{ "permissions": { "allow": ["Read", "Write", "Bash"] } }' > "$FAKE_HOME/.claude/settings.json"
42
+ cat > "$FAKE_HOME/.claude/projects/-Users-dev/webapp/CLAUDE.md" << 'EOF'
43
+ # Project: webapp
44
+ Next.js 15 app with Supabase backend. Always use server components.
45
+ Database schema is in /supabase/migrations. Run tests before committing.
46
+ EOF
47
+ cat > "$FAKE_HOME/.claude/projects/-Users-dev/webapp/memory/MEMORY.md" << 'EOF'
48
+ # Memory
49
+ - User prefers Tailwind CSS v4
50
+ - Auth is handled by Supabase Auth with Google OAuth
51
+ - Deploy target: Vercel
52
+ EOF
53
+
54
+ mkdir -p "$FAKE_HOME/.codex"
55
+ echo '{ "model": "codex-1", "approval": "auto" }' > "$FAKE_HOME/.codex/config.json"
56
+ echo 'Use Python 3.12. Follow PEP 8. Prefer pathlib.' > "$FAKE_HOME/.codex/instructions.md"
57
+
58
+ cat > "$FAKE_HOME/.aider.conf.yml" << 'EOF'
59
+ model: claude-opus-4-20250514
60
+ auto-commits: true
61
+ EOF
62
+
63
+ mkdir -p "$FAKE_HOME/.config/memoir"
64
+ echo "{ \"provider\": \"local\", \"localPath\": \"$BACKUP_DIR\" }" > "$FAKE_HOME/.config/memoir/config.json"
65
+ mkdir -p "$BACKUP_DIR"
66
+
67
+ export HOME="$FAKE_HOME"
68
+
69
+ clear
70
+ echo ""
71
+ echo -e "\033[1;36m memoir — Your AI remembers everything. Sync it everywhere.\033[0m"
72
+ echo -e "\033[0;90m https://github.com/camgitt/memoir\033[0m"
73
+ sleep 2
74
+
75
+ narrate "See what AI tools are on this machine"
76
+ type_cmd "node $MEMOIR_BIN status"
77
+
78
+ narrate "Back up all AI memory in one command"
79
+ type_cmd "node $MEMOIR_BIN push"
80
+
81
+ narrate "Now simulate switching to a new machine..."
82
+ sleep 1
83
+ rm -rf "$FAKE_HOME/.gemini" "$FAKE_HOME/.claude" "$FAKE_HOME/.codex"
84
+ rm -f "$FAKE_HOME/.aider.conf.yml"
85
+ echo -e "\033[1;31m [wiped all AI configs]\033[0m"
86
+ sleep 1.5
87
+
88
+ narrate "Restore everything on the new machine"
89
+ type_cmd "node $MEMOIR_BIN restore --yes"
90
+
91
+ narrate "All done. 11 files restored across 4 tools in seconds."
92
+ sleep 1
93
+ echo ""
94
+ echo -e "\033[1;32m npm install -g memoir-cli\033[0m"
95
+ echo -e "\033[0;90m Works with Claude, Gemini, Codex, Cursor, Copilot, Windsurf, and Aider.\033[0m"
96
+ echo ""
97
+ sleep 3
98
+
99
+ rm -rf "$FAKE_HOME"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memoir-cli",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Sync AI memory across devices. Back up and restore Claude, Gemini, Codex, Cursor, Copilot, Windsurf configs. Snapshot coding sessions and resume on another machine. Migrate instructions between AI assistants.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "scripts": {
19
19
  "start": "node bin/memoir.js",
20
- "test": "echo \"Error: no test specified\" && exit 1"
20
+ "test": "bash test-local.sh"
21
21
  },
22
22
  "keywords": [
23
23
  "ai",
@@ -56,7 +56,7 @@ async function syncFiles(src, dest, changes) {
56
56
  }
57
57
  }
58
58
 
59
- export async function restoreMemories(sourceDir, spinner, onlyFilter = null) {
59
+ export async function restoreMemories(sourceDir, spinner, onlyFilter = null, autoYes = false) {
60
60
  let restoredAny = false;
61
61
  const allResults = [];
62
62
 
@@ -74,14 +74,21 @@ export async function restoreMemories(sourceDir, spinner, onlyFilter = null) {
74
74
 
75
75
  console.log('\n' + chalk.cyan(`${adapter.icon} Found backup for ${chalk.bold(adapter.name)}`));
76
76
  console.log(chalk.gray(` Will restore to: ${adapter.source}`));
77
- const { confirm } = await inquirer.prompt([
78
- {
79
- type: 'confirm',
80
- name: 'confirm',
81
- message: `Restore ${adapter.name}?`,
82
- default: true
83
- }
84
- ]);
77
+
78
+ let confirm = true;
79
+ if (!autoYes) {
80
+ const answer = await inquirer.prompt([
81
+ {
82
+ type: 'confirm',
83
+ name: 'confirm',
84
+ message: `Restore ${adapter.name}?`,
85
+ default: true
86
+ }
87
+ ]);
88
+ confirm = answer.confirm;
89
+ } else {
90
+ console.log(chalk.green(` Auto-restoring ${adapter.name}...`));
91
+ }
85
92
 
86
93
  spinner.start();
87
94
 
@@ -0,0 +1,221 @@
1
+ import chalk from 'chalk';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import ora from 'ora';
6
+ import boxen from 'boxen';
7
+ import { execSync } from 'child_process';
8
+ import { getConfig } from '../config.js';
9
+ import { adapters } from '../adapters/index.js';
10
+
11
+ async function listFiles(dir, prefix = '') {
12
+ const entries = await fs.readdir(dir, { withFileTypes: true });
13
+ const files = [];
14
+ for (const entry of entries) {
15
+ if (entry.name === '.git') continue;
16
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
17
+ if (entry.isDirectory()) {
18
+ files.push(...await listFiles(path.join(dir, entry.name), rel));
19
+ } else {
20
+ files.push({ path: rel });
21
+ }
22
+ }
23
+ return files;
24
+ }
25
+
26
+ function isBinaryFile(filePath) {
27
+ const binaryExts = ['.png', '.jpg', '.jpeg', '.gif', '.ico', '.woff', '.woff2', '.ttf', '.eot', '.zip', '.tar', '.gz', '.db', '.sqlite'];
28
+ return binaryExts.includes(path.extname(filePath).toLowerCase());
29
+ }
30
+
31
+ function simpleDiff(oldText, newText) {
32
+ const oldLines = oldText.split('\n');
33
+ const newLines = newText.split('\n');
34
+ const output = [];
35
+
36
+ const oldSet = new Set(oldLines);
37
+ const newSet = new Set(newLines);
38
+
39
+ for (const line of oldLines) {
40
+ if (!newSet.has(line) && line.trim()) {
41
+ output.push(chalk.red(` - ${line}`));
42
+ }
43
+ }
44
+ for (const line of newLines) {
45
+ if (!oldSet.has(line) && line.trim()) {
46
+ output.push(chalk.green(` + ${line}`));
47
+ }
48
+ }
49
+
50
+ return output;
51
+ }
52
+
53
+ export async function diffCommand() {
54
+ const config = await getConfig();
55
+ if (!config) {
56
+ console.log(chalk.red('\n✖ Not configured yet. Run: memoir init\n'));
57
+ return;
58
+ }
59
+
60
+ const spinner = ora('Comparing local files against last backup...').start();
61
+ const stagingDir = path.join(os.tmpdir(), `memoir-diff-${Date.now()}`);
62
+ await fs.ensureDir(stagingDir);
63
+
64
+ try {
65
+ if (config.provider === 'git') {
66
+ execSync(`git clone --depth 1 ${config.gitRepo} .`, { cwd: stagingDir, stdio: 'ignore' });
67
+ } else {
68
+ const resolvedSource = config.localPath.replace(/^~/, os.homedir());
69
+ if (!(await fs.pathExists(resolvedSource))) {
70
+ spinner.fail('No backup found. Run: memoir push');
71
+ return;
72
+ }
73
+ await fs.copy(resolvedSource, stagingDir);
74
+ }
75
+
76
+ spinner.stop();
77
+
78
+ const summary = { added: [], modified: [], deleted: [], unchanged: 0 };
79
+ const details = [];
80
+
81
+ for (const adapter of adapters) {
82
+ const backupDir = path.join(stagingDir, adapter.name.toLowerCase().replace(/ /g, '-'));
83
+ const backupExists = await fs.pathExists(backupDir);
84
+
85
+ // Get local files for this adapter
86
+ const localFiles = new Set();
87
+ if (adapter.customExtract) {
88
+ for (const file of adapter.files) {
89
+ if (await fs.pathExists(path.join(adapter.source, file))) {
90
+ localFiles.add(file);
91
+ }
92
+ }
93
+ } else if (await fs.pathExists(adapter.source)) {
94
+ // Walk local source with the adapter's filter
95
+ const walk = async (dir, prefix = '') => {
96
+ const entries = await fs.readdir(dir, { withFileTypes: true });
97
+ for (const entry of entries) {
98
+ const fullPath = path.join(dir, entry.name);
99
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
100
+ if (adapter.filter && !adapter.filter(fullPath)) continue;
101
+ if (entry.isDirectory()) {
102
+ await walk(fullPath, rel);
103
+ } else {
104
+ localFiles.add(rel);
105
+ }
106
+ }
107
+ };
108
+ await walk(adapter.source);
109
+ }
110
+
111
+ // Get backup files
112
+ const backupFiles = new Set();
113
+ if (backupExists) {
114
+ const files = await listFiles(backupDir);
115
+ files.forEach(f => backupFiles.add(f.path));
116
+ }
117
+
118
+ // New files (local but not in backup)
119
+ for (const file of localFiles) {
120
+ if (!backupFiles.has(file)) {
121
+ if (!isBinaryFile(file)) {
122
+ summary.added.push({ tool: adapter.name, icon: adapter.icon, file });
123
+ }
124
+ }
125
+ }
126
+
127
+ // Deleted files (in backup but not local)
128
+ for (const file of backupFiles) {
129
+ if (!localFiles.has(file)) {
130
+ summary.deleted.push({ tool: adapter.name, icon: adapter.icon, file });
131
+ }
132
+ }
133
+
134
+ // Modified files (in both, content differs)
135
+ for (const file of localFiles) {
136
+ if (backupFiles.has(file) && !isBinaryFile(file)) {
137
+ const localPath = adapter.customExtract
138
+ ? path.join(adapter.source, file)
139
+ : path.join(adapter.source, file);
140
+ const backupPath = path.join(backupDir, file);
141
+
142
+ try {
143
+ const localContent = await fs.readFile(localPath, 'utf8');
144
+ const backupContent = await fs.readFile(backupPath, 'utf8');
145
+
146
+ if (localContent !== backupContent) {
147
+ const diffLines = simpleDiff(backupContent, localContent);
148
+ summary.modified.push({ tool: adapter.name, icon: adapter.icon, file });
149
+ details.push({ tool: adapter.name, icon: adapter.icon, file, lines: diffLines });
150
+ } else {
151
+ summary.unchanged++;
152
+ }
153
+ } catch {
154
+ summary.unchanged++;
155
+ }
156
+ }
157
+ }
158
+ }
159
+
160
+ // Print summary
161
+ const totalChanges = summary.added.length + summary.modified.length + summary.deleted.length;
162
+
163
+ if (totalChanges === 0) {
164
+ console.log('\n' + boxen(
165
+ chalk.green('Everything is in sync.') + '\n' +
166
+ chalk.gray(`${summary.unchanged} files unchanged since last backup.`),
167
+ { padding: 1, borderStyle: 'round', borderColor: 'green', dimBorder: true }
168
+ ) + '\n');
169
+ return;
170
+ }
171
+
172
+ console.log('\n' + boxen(
173
+ chalk.cyan.bold('Changes since last backup'),
174
+ { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderStyle: 'round', borderColor: 'cyan' }
175
+ ));
176
+
177
+ if (summary.added.length > 0) {
178
+ console.log(chalk.green.bold(`\n + ${summary.added.length} new file(s)`));
179
+ for (const f of summary.added) {
180
+ console.log(chalk.green(` ${f.icon} ${f.tool}/${f.file}`));
181
+ }
182
+ }
183
+
184
+ if (summary.modified.length > 0) {
185
+ console.log(chalk.yellow.bold(`\n ~ ${summary.modified.length} modified file(s)`));
186
+ for (const f of summary.modified) {
187
+ console.log(chalk.yellow(` ${f.icon} ${f.tool}/${f.file}`));
188
+ }
189
+ }
190
+
191
+ if (summary.deleted.length > 0) {
192
+ console.log(chalk.red.bold(`\n - ${summary.deleted.length} removed file(s)`));
193
+ for (const f of summary.deleted) {
194
+ console.log(chalk.red(` ${f.icon} ${f.tool}/${f.file}`));
195
+ }
196
+ }
197
+
198
+ if (summary.unchanged > 0) {
199
+ console.log(chalk.gray(`\n ${summary.unchanged} file(s) unchanged`));
200
+ }
201
+
202
+ // Show diffs for modified files
203
+ if (details.length > 0) {
204
+ console.log(chalk.gray('\n' + '─'.repeat(40)));
205
+ console.log(chalk.bold.white('\n Changes:\n'));
206
+
207
+ for (const d of details) {
208
+ console.log(` ${d.icon} ${chalk.cyan(d.tool + '/' + d.file)}`);
209
+ for (const line of d.lines) {
210
+ console.log(line);
211
+ }
212
+ console.log('');
213
+ }
214
+ }
215
+
216
+ console.log(chalk.gray(' Run ') + chalk.cyan('memoir push') + chalk.gray(' to back up these changes.\n'));
217
+
218
+ } finally {
219
+ await fs.remove(stagingDir);
220
+ }
221
+ }
@@ -31,10 +31,12 @@ export async function restoreCommand(options = {}) {
31
31
 
32
32
  const onlyFilter = options.only ? options.only.split(',').map(t => t.trim().toLowerCase()) : null;
33
33
 
34
+ const autoYes = options.yes || false;
35
+
34
36
  if (config.provider === 'local' || config.provider.includes('local')) {
35
- restored = await fetchFromLocal(config, stagingDir, spinner, onlyFilter);
37
+ restored = await fetchFromLocal(config, stagingDir, spinner, onlyFilter, autoYes);
36
38
  } else if (config.provider === 'git' || config.provider.includes('git')) {
37
- restored = await fetchFromGit(config, stagingDir, spinner, onlyFilter);
39
+ restored = await fetchFromGit(config, stagingDir, spinner, onlyFilter, autoYes);
38
40
  } else {
39
41
  spinner.fail(chalk.red(`Unknown provider: ${config.provider}`));
40
42
  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, onlyFilter = null) {
8
+ export async function fetchFromLocal(config, stagingDir, spinner, onlyFilter = null, autoYes = false) {
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, onlyFilter = n
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, onlyFilter);
21
+ return await restoreMemories(stagingDir, spinner, onlyFilter, autoYes);
22
22
  }
23
23
 
24
- export async function fetchFromGit(config, stagingDir, spinner, onlyFilter = null) {
24
+ export async function fetchFromGit(config, stagingDir, spinner, onlyFilter = null, autoYes = false) {
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, onlyFilter = nul
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, onlyFilter);
36
+ return await restoreMemories(stagingDir, spinner, onlyFilter, autoYes);
37
37
  }
package/CLAUDE.md DELETED
@@ -1 +0,0 @@
1
- Old Claude Instructions
package/TODO.md DELETED
@@ -1,39 +0,0 @@
1
- # memoir Roadmap & TODO
2
-
3
- This list tracks planned features, identified bugs, and architectural improvements for the `memoir` CLI.
4
-
5
- ## 🔴 High Priority: Security, Reliability & Bug Fixes
6
- - [ ] **`memoir doctor` Command:** Implement a diagnostic utility to verify:
7
- - [ ] Correct installation of supported AI tools (Claude, Cursor, etc.).
8
- - [ ] File permissions for all memory directories.
9
- - [ ] Git connectivity and API key validity (Gemini).
10
- - [ ] Environment variable health.
11
- - [ ] **Secret & PII Guard:** Add a pre-push scan to detect API keys or PII in memory files. Implement a `redact` flag.
12
- - [ ] **Linux Path Support:** Add path detection for Cursor and Windsurf on Linux (currently macOS/Windows only).
13
- - [ ] **Aider Local Discovery:** Update the Aider adapter to look for `.aider/` in the current project repo, not just global config.
14
- - [ ] **Robust Claude Pathing:** Replace fragile string-replacement in `src/tools/claude.js` with more reliable hashing/matching for `~/.claude/projects`.
15
- - [ ] **Add `-y` / `--yes` Flags:** Enable non-interactive mode for `push`, `restore`, and `migrate` to support automation.
16
-
17
- ## 🟡 Medium Priority: UX & Workflow
18
- - [ ] **Local LLM Migration (Privacy):**
19
- - [ ] Add support for **Ollama** and **LM Studio** as translation engines in `migrate`.
20
- - [ ] Add a `--local` flag to `migrate` to bypass external APIs.
21
- - [ ] **`memoir watch` (Background Sync):** Create a lightweight daemon to detect changes in local memory files and trigger auto-sync.
22
- - [ ] **Project Bootstrapping (`memoir init --template`):** Seed new projects with "Golden Rule" templates (e.g., "Strict TypeScript," "React/Tailwind Best Practices").
23
- - [ ] **Interactive Merge/Diff:** Replace "Overwrite/Append" in `migrate` with a side-by-side diff view.
24
- - [ ] **Silent Mode:** Add a `--silent` flag to suppress all output except errors.
25
-
26
- ## 🟢 Low Priority: Intelligence & Advanced Features
27
- - [ ] **Unified Memory Format (UMF):** Architect an internal JSON schema to represent "Coding Context" to simplify adding new tool adapters.
28
- - [ ] **Cross-Tool Search:** Implement `memoir search <query>` to find specific instructions across all tool backups.
29
- - [ ] **Context Compression:** AI-powered `memoir optimize` command to summarize long instruction files and save tokens.
30
- - [ ] **Organization Sync:** Support for shared "Team Memories" stored in a central repository.
31
- - [ ] **Memory Analytics:** `memoir stats` to visualize the growth and "personality" of your AI instructions over time.
32
-
33
- ---
34
-
35
- ## 📝 Technical Observations & Bug Log
36
- * **Circular Dependency:** `package.json` lists `memoir-cli` as its own dependency. Needs cleanup.
37
- * **Git Performance:** `push` currently clones the entire repo every time. Optimize with `git clone --depth 1` or local cache.
38
- * **Performance:** Refactor `fs.readFileSync` inside loops (e.g., in `src/tools/claude.js`) to use `fs.promises` for better handling of large projects.
39
- * **CLI Friction:** The `migrate` command's interactive prompt for multiple files can be tedious; batching confirmations would improve UX.