memoir-cli 3.3.0 → 3.4.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 +45 -195
- package/bin/memoir.js +76 -15
- package/package.json +4 -3
- package/src/cloud/auth.js +58 -1
- package/src/cloud/constants.js +1 -1
- package/src/commands/activate.js +232 -0
- package/src/commands/init.js +79 -50
- package/src/commands/login.js +41 -1
- package/src/commands/push.js +8 -0
- package/src/commands/upgrade.js +1 -1
package/README.md
CHANGED
|
@@ -2,37 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
# memoir
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Portable memory for every AI coding tool.**
|
|
6
6
|
|
|
7
7
|
[](https://npmjs.org/package/memoir-cli)
|
|
8
8
|
[](https://npmjs.org/package/memoir-cli)
|
|
9
9
|
[](https://github.com/camgitt/memoir/stargazers)
|
|
10
10
|
[](LICENSE)
|
|
11
|
-
[](https://nodejs.org)
|
|
12
|
-
|
|
13
|
-
Your AI forgets everything between sessions. memoir gives it long-term memory via MCP.
|
|
14
|
-
|
|
15
|
-
**11 tools supported** • **E2E encrypted** • **Cross-platform** • **Open source**
|
|
16
|
-
|
|
17
|
-
Works with Claude Code, Cursor, Windsurf, Gemini, Copilot, Codex, ChatGPT, Aider, Zed, Cline, and Continue.dev.
|
|
18
|
-
|
|
19
|
-
[Website](https://memoir.sh) • [npm](https://npmjs.org/package/memoir-cli) • [Blog](https://memoir.sh/blog) • [Contributing](CONTRIBUTING.md)
|
|
20
|
-
|
|
21
|
-
<br />
|
|
22
|
-
|
|
23
|
-
<img src="demo.svg" alt="memoir demo — push, restore, and sync AI memory" width="700" />
|
|
24
11
|
|
|
25
12
|
</div>
|
|
26
13
|
|
|
27
|
-
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g memoir-cli
|
|
16
|
+
memoir activate
|
|
17
|
+
```
|
|
28
18
|
|
|
29
|
-
|
|
19
|
+
Your AI now remembers across sessions, tools, and machines. Works with Claude Code, Cursor, Windsurf, Gemini, Copilot, and 6 more tools.
|
|
30
20
|
|
|
31
|
-
|
|
21
|
+
---
|
|
32
22
|
|
|
33
|
-
##
|
|
23
|
+
## What it does
|
|
34
24
|
|
|
35
|
-
memoir is
|
|
25
|
+
memoir is an [MCP server](https://modelcontextprotocol.io) that gives your AI tools persistent memory. Your AI can search, save, and recall context automatically.
|
|
36
26
|
|
|
37
27
|
```
|
|
38
28
|
you: how does auth work in this project?
|
|
@@ -47,38 +37,15 @@ claude: Based on your previous sessions: this project uses JWT auth
|
|
|
47
37
|
|
|
48
38
|
No re-explaining. memoir remembered.
|
|
49
39
|
|
|
50
|
-
##
|
|
51
|
-
|
|
52
|
-
```mermaid
|
|
53
|
-
graph LR
|
|
54
|
-
A[Claude Code] --> M[memoir MCP]
|
|
55
|
-
B[Cursor] --> M
|
|
56
|
-
C[Gemini CLI] --> M
|
|
57
|
-
D[Windsurf] --> M
|
|
58
|
-
E[+ 7 more] --> M
|
|
59
|
-
|
|
60
|
-
M --> R[recall / remember / list / read]
|
|
61
|
-
R --> S[(Local Memory Store)]
|
|
62
|
-
|
|
63
|
-
S --> P[memoir push]
|
|
64
|
-
P --> G[GitHub / Cloud]
|
|
65
|
-
G --> Q[memoir restore]
|
|
66
|
-
Q --> S2[(New Machine)]
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
memoir runs as a local MCP server inside your AI tools. Your AI can search, save, and recall memories automatically. Push to sync across machines.
|
|
40
|
+
## Setup
|
|
70
41
|
|
|
71
|
-
|
|
42
|
+
### 1. Install
|
|
72
43
|
|
|
73
44
|
```bash
|
|
74
|
-
# Install
|
|
75
45
|
npm install -g memoir-cli
|
|
76
|
-
|
|
77
|
-
# Setup
|
|
78
|
-
memoir init
|
|
79
46
|
```
|
|
80
47
|
|
|
81
|
-
### Add MCP to your AI
|
|
48
|
+
### 2. Add MCP to your AI tool
|
|
82
49
|
|
|
83
50
|
**Claude Code** — add to `~/.mcp.json`:
|
|
84
51
|
```json
|
|
@@ -98,7 +65,13 @@ memoir init
|
|
|
98
65
|
}
|
|
99
66
|
```
|
|
100
67
|
|
|
101
|
-
|
|
68
|
+
### 3. Activate in your project
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
memoir activate
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
That's it. Your AI now has 6 memory tools:
|
|
102
75
|
|
|
103
76
|
| MCP Tool | What it does |
|
|
104
77
|
|----------|-------------|
|
|
@@ -109,49 +82,28 @@ That's it. Your AI tools now have 6 memory abilities:
|
|
|
109
82
|
| `memoir_status` | See which AI tools are detected |
|
|
110
83
|
| `memoir_profiles` | Switch between work/personal |
|
|
111
84
|
|
|
112
|
-
##
|
|
113
|
-
|
|
114
|
-
memoir syncs three layers that no other tool connects:
|
|
85
|
+
## Why memoir
|
|
115
86
|
|
|
116
|
-
|
|
117
|
-
Configs, instructions, and project knowledge across 11 tools — searchable and writable from inside any AI conversation.
|
|
87
|
+
Your AI forgets everything between sessions. You re-explain your codebase, your conventions, your decisions — every time.
|
|
118
88
|
|
|
119
|
-
|
|
120
|
-
|------|-----------------|
|
|
121
|
-
| **Claude Code** | ~/.claude/ settings, memory, CLAUDE.md files |
|
|
122
|
-
| **Gemini CLI** | ~/.gemini/ config, GEMINI.md files |
|
|
123
|
-
| **ChatGPT** | CHATGPT.md custom instructions |
|
|
124
|
-
| **OpenAI Codex** | ~/.codex/ config, AGENTS.md |
|
|
125
|
-
| **Cursor** | Settings, keybindings, .cursorrules |
|
|
126
|
-
| **GitHub Copilot** | Config, copilot-instructions.md |
|
|
127
|
-
| **Windsurf** | Settings, keybindings, .windsurfrules |
|
|
128
|
-
| **Zed** | Settings, keymap, tasks |
|
|
129
|
-
| **Cline** | Settings, .clinerules |
|
|
130
|
-
| **Continue.dev** | Config, .continuerules |
|
|
131
|
-
| **Aider** | .aider.conf.yml, system prompt |
|
|
89
|
+
memoir fixes this by giving your AI a shared memory layer that works across **every tool you use**. Tell Claude something once. Cursor knows it too.
|
|
132
90
|
|
|
133
|
-
|
|
134
|
-
What you were **doing** — not just what your AI knows, but the active context.
|
|
91
|
+
**11 tools supported:** Claude Code, Cursor, Windsurf, Gemini CLI, GitHub Copilot, OpenAI Codex, ChatGPT, Aider, Zed, Cline, Continue.dev
|
|
135
92
|
|
|
136
|
-
|
|
137
|
-
- What files you changed, what errors you hit, what decisions you made
|
|
138
|
-
- Injected into your AI on restore so it picks up mid-conversation
|
|
139
|
-
- Secrets auto-redacted (API keys, tokens, passwords stripped before sync)
|
|
93
|
+
## Sync across machines
|
|
140
94
|
|
|
141
|
-
|
|
142
|
-
|
|
95
|
+
```bash
|
|
96
|
+
# Back up
|
|
97
|
+
memoir push
|
|
143
98
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
- **Zero git commands needed** — memoir handles it all
|
|
99
|
+
# Restore on any machine
|
|
100
|
+
memoir restore -y
|
|
101
|
+
```
|
|
148
102
|
|
|
149
|
-
|
|
103
|
+
Push syncs AI memory, session context, workspace (git repos + uncommitted work), and project configs. E2E encrypted with AES-256-GCM.
|
|
150
104
|
|
|
151
|
-
|
|
152
|
-
Tell Claude something once. Cursor knows it too. memoir is the shared memory layer between all your AI tools.
|
|
105
|
+
## Translate between AI tools
|
|
153
106
|
|
|
154
|
-
### Translate between AI tools
|
|
155
107
|
```bash
|
|
156
108
|
memoir migrate --from chatgpt --to claude
|
|
157
109
|
# AI-powered — rewrites conventions, not copy-paste
|
|
@@ -160,56 +112,25 @@ memoir migrate --from chatgpt --to all
|
|
|
160
112
|
# Translate to every tool at once
|
|
161
113
|
```
|
|
162
114
|
|
|
163
|
-
|
|
164
|
-
```bash
|
|
165
|
-
# On your main machine
|
|
166
|
-
memoir push
|
|
167
|
-
|
|
168
|
-
# On any other machine
|
|
169
|
-
memoir restore -y
|
|
170
|
-
# ✔ AI memory restored (Claude, Gemini, Cursor, 11 tools)
|
|
171
|
-
# ✔ 44 projects cloned & unpacked
|
|
172
|
-
# ✔ Uncommitted changes applied
|
|
173
|
-
# ✔ Session context injected — AI picks up mid-conversation
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### E2E Encryption
|
|
177
|
-
```bash
|
|
178
|
-
memoir encrypt # toggle encryption on/off
|
|
179
|
-
memoir push # prompted for passphrase, AES-256-GCM encrypted
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
Your backup is encrypted before it leaves your machine. Secret scanning auto-redacts API keys, tokens, and passwords.
|
|
183
|
-
|
|
184
|
-
### Profiles (personal / work)
|
|
185
|
-
```bash
|
|
186
|
-
memoir profile create work
|
|
187
|
-
memoir push --profile work
|
|
188
|
-
memoir profile switch personal
|
|
189
|
-
```
|
|
115
|
+
## Cloud sync
|
|
190
116
|
|
|
191
|
-
### Cloud sync
|
|
192
117
|
```bash
|
|
193
118
|
memoir login
|
|
194
119
|
memoir cloud push # encrypted cloud backup
|
|
195
120
|
memoir cloud restore # restore from any version
|
|
121
|
+
memoir history # view backup versions
|
|
122
|
+
memoir share # create encrypted shareable link
|
|
196
123
|
```
|
|
197
124
|
|
|
198
|
-
### Cross-platform (Mac / Windows / Linux)
|
|
199
|
-
Paths remap automatically between platforms. Push from Mac, restore on Windows. It just works.
|
|
200
|
-
|
|
201
125
|
## All Commands
|
|
202
126
|
|
|
203
127
|
| Command | What it does |
|
|
204
128
|
|---------|-------------|
|
|
205
|
-
| `memoir
|
|
129
|
+
| `memoir activate` | Enable auto-recall in this project |
|
|
130
|
+
| `memoir deactivate` | Remove memoir from this project |
|
|
206
131
|
| `memoir push` | Back up AI memory + workspace + session |
|
|
207
132
|
| `memoir restore` | Restore everything on a new machine |
|
|
208
|
-
| `memoir mcp` | Start MCP server for editor integration |
|
|
209
133
|
| `memoir status` | Show detected AI tools |
|
|
210
|
-
| `memoir doctor` | Diagnose issues, scan for secrets |
|
|
211
|
-
| `memoir view` | Preview what's in your backup |
|
|
212
|
-
| `memoir diff` | Show changes since last backup |
|
|
213
134
|
| `memoir migrate` | Translate memory between tools via AI |
|
|
214
135
|
| `memoir snapshot` | Capture current coding session |
|
|
215
136
|
| `memoir resume` | Pick up where you left off |
|
|
@@ -217,95 +138,24 @@ Paths remap automatically between platforms. Push from Mac, restore on Windows.
|
|
|
217
138
|
| `memoir profile` | Manage profiles (personal/work) |
|
|
218
139
|
| `memoir cloud push` | Back up to memoir cloud |
|
|
219
140
|
| `memoir cloud restore` | Restore from memoir cloud |
|
|
220
|
-
| `memoir history` | View cloud backup versions |
|
|
221
|
-
| `memoir login` | Sign in to memoir cloud |
|
|
222
141
|
| `memoir share` | Create encrypted shareable link |
|
|
223
|
-
| `memoir
|
|
142
|
+
| `memoir doctor` | Diagnose issues |
|
|
143
|
+
| `memoir diff` | Show changes since last backup |
|
|
144
|
+
| `memoir view` | Preview what's in your backup |
|
|
224
145
|
| `memoir update` | Self-update to latest version |
|
|
225
146
|
|
|
226
|
-
## How memoir compares
|
|
227
|
-
|
|
228
|
-
| Feature | memoir | dotfiles managers | ai-rulez | memories.sh |
|
|
229
|
-
|---------|--------|-------------------|----------|-------------|
|
|
230
|
-
| MCP memory layer | **6 tools** | No | No | No |
|
|
231
|
-
| AI memory sync | **11 tools** | No | 18 tools | 3 tools |
|
|
232
|
-
| Cross-tool recall | **Yes** | No | No | No |
|
|
233
|
-
| Workspace sync | **Yes** | No | No | No |
|
|
234
|
-
| Session handoff | **Yes** | No | No | No |
|
|
235
|
-
| AI-powered migration | **Yes** | No | No | No |
|
|
236
|
-
| E2E encryption | **Yes** | No | No | No |
|
|
237
|
-
| Secret scanning | **Yes** | Some | No | No |
|
|
238
|
-
| Cross-platform remap | **Yes** | Some | No | No |
|
|
239
|
-
| Cloud backup | **Yes** | No | No | Yes ($15/mo) |
|
|
240
|
-
| Profiles | **Yes** | No | No | No |
|
|
241
|
-
| Free & open source | **Yes** | Yes | Yes | No |
|
|
242
|
-
|
|
243
|
-
## Common Workflows
|
|
244
|
-
|
|
245
|
-
### Your AI remembers across sessions
|
|
246
|
-
```
|
|
247
|
-
# Monday — you explain your auth setup to Claude
|
|
248
|
-
# ...Claude calls memoir_remember to save the decision
|
|
249
|
-
|
|
250
|
-
# Thursday — new conversation
|
|
251
|
-
you: "add a protected route"
|
|
252
|
-
# Claude calls memoir_recall, finds your auth architecture
|
|
253
|
-
# No re-explaining needed
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### New machine setup
|
|
257
|
-
```bash
|
|
258
|
-
npm install -g memoir-cli && memoir init && memoir restore -y
|
|
259
|
-
# Done. Everything's back.
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
### Switching AI tools
|
|
263
|
-
```bash
|
|
264
|
-
memoir migrate --from chatgpt --to claude
|
|
265
|
-
# Your custom instructions become a proper CLAUDE.md
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
### Team onboarding
|
|
269
|
-
```bash
|
|
270
|
-
# Senior dev pushes team config
|
|
271
|
-
memoir push --profile team
|
|
272
|
-
|
|
273
|
-
# New hire runs one command
|
|
274
|
-
memoir restore --profile team
|
|
275
|
-
# Every project cloned. Every AI tool configured. Day one productive.
|
|
276
|
-
```
|
|
277
|
-
|
|
278
147
|
## Security
|
|
279
148
|
|
|
280
149
|
- **E2E encryption** — AES-256-GCM with scrypt key derivation
|
|
281
|
-
- **Secret scanning** —
|
|
282
|
-
- **
|
|
283
|
-
- **
|
|
284
|
-
- **Passphrase verified** — wrong passphrase caught before decrypt attempt
|
|
285
|
-
- **Local MCP server** — runs on your machine, no data sent to external services
|
|
286
|
-
|
|
287
|
-
## Requirements
|
|
288
|
-
|
|
289
|
-
- Node.js >= 18
|
|
290
|
-
- Git (for workspace sync)
|
|
291
|
-
- Works on macOS, Windows, Linux
|
|
292
|
-
|
|
293
|
-
## Contributing
|
|
294
|
-
|
|
295
|
-
Contributions welcome — especially new tool adapters and MCP improvements.
|
|
296
|
-
|
|
297
|
-
1. Fork the repo
|
|
298
|
-
2. Create your branch (`git checkout -b feature/my-feature`)
|
|
299
|
-
3. Commit and push
|
|
300
|
-
4. Open a PR
|
|
150
|
+
- **Secret scanning** — API keys, tokens, passwords auto-redacted before sync
|
|
151
|
+
- **Local MCP server** — runs on your machine, no data sent externally
|
|
152
|
+
- **Zero-knowledge cloud** — encrypted before upload
|
|
301
153
|
|
|
302
154
|
## Links
|
|
303
155
|
|
|
304
156
|
- **Website:** [memoir.sh](https://memoir.sh)
|
|
305
157
|
- **npm:** [memoir-cli](https://npmjs.org/package/memoir-cli)
|
|
306
|
-
- **Blog:** [memoir.sh/blog](https://memoir.sh/blog)
|
|
307
158
|
- **Issues:** [GitHub Issues](https://github.com/camgitt/memoir/issues)
|
|
159
|
+
- **Contributing:** [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
308
160
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
MIT
|
|
161
|
+
MIT Licensed
|
package/bin/memoir.js
CHANGED
|
@@ -14,12 +14,13 @@ import { migrateCommand } from '../src/commands/migrate.js';
|
|
|
14
14
|
import { snapshotCommand } from '../src/commands/snapshot.js';
|
|
15
15
|
import { resumeCommand } from '../src/commands/resume.js';
|
|
16
16
|
import { profileListCommand, profileCreateCommand, profileSwitchCommand, profileDeleteCommand } from '../src/commands/profile.js';
|
|
17
|
-
import { loginCommand, logoutCommand, forgotPasswordCommand } from '../src/commands/login.js';
|
|
17
|
+
import { loginCommand, logoutCommand, forgotPasswordCommand, deleteAccountCommand } from '../src/commands/login.js';
|
|
18
18
|
import { cloudPushCommand, cloudRestoreCommand } from '../src/commands/cloud.js';
|
|
19
19
|
import { shareCommand } from '../src/commands/share.js';
|
|
20
20
|
import { historyCommand } from '../src/commands/history.js';
|
|
21
21
|
import { projectsListCommand, projectsTodoCommand } from '../src/commands/projects.js';
|
|
22
22
|
import { upgradeCommand } from '../src/commands/upgrade.js';
|
|
23
|
+
import { activateCommand, deactivateCommand } from '../src/commands/activate.js';
|
|
23
24
|
import { createRequire } from 'module';
|
|
24
25
|
|
|
25
26
|
const require = createRequire(import.meta.url);
|
|
@@ -66,6 +67,7 @@ if (process.argv.length <= 2) {
|
|
|
66
67
|
chalk.cyan(' memoir status ') + chalk.gray('— see detected AI tools') + '\n' +
|
|
67
68
|
chalk.cyan(' memoir profile ') + chalk.gray('— manage profiles (personal/work)') + '\n' +
|
|
68
69
|
chalk.cyan(' memoir projects ') + chalk.gray('— see all your projects at a glance') + '\n' +
|
|
70
|
+
chalk.cyan(' memoir activate ') + chalk.gray('— enable auto-recall in this project') + '\n' +
|
|
69
71
|
chalk.cyan(' memoir encrypt ') + chalk.gray('— toggle E2E encryption') + '\n' +
|
|
70
72
|
chalk.cyan(' memoir update ') + chalk.gray('— update to latest version') + '\n' +
|
|
71
73
|
chalk.cyan(' memoir upgrade ') + chalk.gray('— view plans & upgrade') + '\n\n' +
|
|
@@ -97,9 +99,16 @@ program
|
|
|
97
99
|
program
|
|
98
100
|
.command('init')
|
|
99
101
|
.description('Set up memoir with your storage provider')
|
|
100
|
-
.
|
|
102
|
+
.option('--direction <direction>', 'Upload or download (upload, download)')
|
|
103
|
+
.option('--provider <provider>', 'Storage provider (git, local)')
|
|
104
|
+
.option('--local-path <path>', 'Local folder path (for local provider)')
|
|
105
|
+
.option('--username <name>', 'GitHub username (for git provider)')
|
|
106
|
+
.option('--repo <name>', 'GitHub repo name (for git provider)')
|
|
107
|
+
.option('--encrypt', 'Enable E2E encryption')
|
|
108
|
+
.option('--no-encrypt', 'Disable E2E encryption')
|
|
109
|
+
.action(async (options) => {
|
|
101
110
|
try {
|
|
102
|
-
await initCommand();
|
|
111
|
+
await initCommand(options);
|
|
103
112
|
} catch (err) {
|
|
104
113
|
console.error(chalk.red('\n✖ Error during initialization:'), err.message);
|
|
105
114
|
process.exit(1);
|
|
@@ -291,10 +300,36 @@ program
|
|
|
291
300
|
}
|
|
292
301
|
});
|
|
293
302
|
|
|
303
|
+
program
|
|
304
|
+
.command('activate')
|
|
305
|
+
.description('Add memoir instructions to this project so your AI uses it automatically')
|
|
306
|
+
.action(async () => {
|
|
307
|
+
try {
|
|
308
|
+
await activateCommand();
|
|
309
|
+
} catch (err) {
|
|
310
|
+
console.error(chalk.red('\n✖ Error:'), err.message);
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
program
|
|
316
|
+
.command('deactivate')
|
|
317
|
+
.description('Remove memoir instructions from this project')
|
|
318
|
+
.action(async () => {
|
|
319
|
+
try {
|
|
320
|
+
await deactivateCommand();
|
|
321
|
+
} catch (err) {
|
|
322
|
+
console.error(chalk.red('\n✖ Error:'), err.message);
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
|
|
294
327
|
program
|
|
295
328
|
.command('encrypt')
|
|
296
329
|
.description('Toggle E2E encryption for your backups')
|
|
297
|
-
.
|
|
330
|
+
.option('--on', 'Enable encryption without prompting')
|
|
331
|
+
.option('--off', 'Disable encryption without prompting')
|
|
332
|
+
.action(async (options) => {
|
|
298
333
|
try {
|
|
299
334
|
const { getConfig, getRawConfig, saveConfig, migrateConfigToV2 } = await import('../src/config.js');
|
|
300
335
|
const config = await getConfig();
|
|
@@ -304,24 +339,34 @@ program
|
|
|
304
339
|
}
|
|
305
340
|
const current = config.encrypt || false;
|
|
306
341
|
console.log(chalk.white(`\n Encryption is currently: ${current ? chalk.green('ON') : chalk.red('OFF')}`));
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
|
|
342
|
+
|
|
343
|
+
let newValue;
|
|
344
|
+
if (options.on) {
|
|
345
|
+
newValue = true;
|
|
346
|
+
} else if (options.off) {
|
|
347
|
+
newValue = false;
|
|
348
|
+
} else {
|
|
349
|
+
const inquirer = (await import('inquirer')).default;
|
|
350
|
+
const { toggle } = await inquirer.prompt([{
|
|
351
|
+
type: 'confirm',
|
|
352
|
+
name: 'toggle',
|
|
353
|
+
message: current ? 'Disable encryption?' : 'Enable encryption?',
|
|
354
|
+
default: !current
|
|
355
|
+
}]);
|
|
356
|
+
newValue = toggle ? !current : current;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (newValue !== current) {
|
|
315
360
|
let raw = await getRawConfig();
|
|
316
361
|
if (!raw.version || raw.version < 2) raw = migrateConfigToV2(raw);
|
|
317
362
|
const profileName = raw.activeProfile || 'default';
|
|
318
363
|
if (raw.profiles?.[profileName]) {
|
|
319
|
-
raw.profiles[profileName].encrypt =
|
|
364
|
+
raw.profiles[profileName].encrypt = newValue;
|
|
320
365
|
} else {
|
|
321
|
-
raw.encrypt =
|
|
366
|
+
raw.encrypt = newValue;
|
|
322
367
|
}
|
|
323
368
|
await saveConfig(raw);
|
|
324
|
-
console.log(chalk.green(`\n ✔ Encryption ${
|
|
369
|
+
console.log(chalk.green(`\n ✔ Encryption ${newValue ? 'enabled' : 'disabled'}. Next push will ${newValue ? 'encrypt' : 'skip encryption'}.\n`));
|
|
325
370
|
}
|
|
326
371
|
} catch (err) {
|
|
327
372
|
console.error(chalk.red('\n✖ Error:'), err.message);
|
|
@@ -386,6 +431,22 @@ program
|
|
|
386
431
|
}
|
|
387
432
|
});
|
|
388
433
|
|
|
434
|
+
// Account management
|
|
435
|
+
const account = program.command('account').description('Manage your memoir account');
|
|
436
|
+
|
|
437
|
+
account
|
|
438
|
+
.command('delete')
|
|
439
|
+
.description('Permanently delete your account and all cloud data')
|
|
440
|
+
.option('--confirm', 'Skip interactive prompt (Node 25 workaround)')
|
|
441
|
+
.action(async (options) => {
|
|
442
|
+
try {
|
|
443
|
+
await deleteAccountCommand(options);
|
|
444
|
+
} catch (err) {
|
|
445
|
+
console.error(chalk.red('\n✖ Error:'), err.message);
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
389
450
|
// Cloud sync
|
|
390
451
|
const cloud = program.command('cloud').description('Cloud backup and restore (Pro)');
|
|
391
452
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memoir-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"mcpName": "io.github.camgitt/memoir",
|
|
5
|
-
"description": "
|
|
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",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"bin": {
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"start": "node bin/memoir.js",
|
|
22
|
-
"test": "bash test-local.sh"
|
|
22
|
+
"test": "bash test-local.sh",
|
|
23
|
+
"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{}\""
|
|
23
24
|
},
|
|
24
25
|
"keywords": [
|
|
25
26
|
"mcp",
|
package/src/cloud/auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
|
-
import { SUPABASE_URL, SUPABASE_ANON_KEY } from './constants.js';
|
|
4
|
+
import { SUPABASE_URL, SUPABASE_ANON_KEY, STORAGE_BUCKET } from './constants.js';
|
|
5
5
|
|
|
6
6
|
const isWin = process.platform === 'win32';
|
|
7
7
|
const configDir = isWin
|
|
@@ -131,4 +131,61 @@ export async function resetPassword(email) {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
export async function deleteAccount(session) {
|
|
135
|
+
// 1. Delete all backups (storage files + metadata rows)
|
|
136
|
+
const backupsRes = await fetch(
|
|
137
|
+
`${SUPABASE_URL}/rest/v1/backups?select=*&user_id=eq.${session.user.id}`,
|
|
138
|
+
{
|
|
139
|
+
headers: {
|
|
140
|
+
'Authorization': `Bearer ${session.access_token}`,
|
|
141
|
+
'apikey': SUPABASE_ANON_KEY,
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
if (backupsRes.ok) {
|
|
147
|
+
const backups = await backupsRes.json();
|
|
148
|
+
for (const backup of backups) {
|
|
149
|
+
// Delete storage object
|
|
150
|
+
await fetch(`${SUPABASE_URL}/storage/v1/object/${STORAGE_BUCKET}/${backup.storage_path}`, {
|
|
151
|
+
method: 'DELETE',
|
|
152
|
+
headers: {
|
|
153
|
+
'Authorization': `Bearer ${session.access_token}`,
|
|
154
|
+
'apikey': SUPABASE_ANON_KEY,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Delete metadata row
|
|
159
|
+
await fetch(`${SUPABASE_URL}/rest/v1/backups?id=eq.${backup.id}`, {
|
|
160
|
+
method: 'DELETE',
|
|
161
|
+
headers: {
|
|
162
|
+
'Authorization': `Bearer ${session.access_token}`,
|
|
163
|
+
'apikey': SUPABASE_ANON_KEY,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 2. Delete shared links
|
|
170
|
+
await fetch(`${SUPABASE_URL}/rest/v1/shared_links?user_id=eq.${session.user.id}`, {
|
|
171
|
+
method: 'DELETE',
|
|
172
|
+
headers: {
|
|
173
|
+
'Authorization': `Bearer ${session.access_token}`,
|
|
174
|
+
'apikey': SUPABASE_ANON_KEY,
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// 3. Delete subscription
|
|
179
|
+
await fetch(`${SUPABASE_URL}/rest/v1/subscriptions?user_id=eq.${session.user.id}`, {
|
|
180
|
+
method: 'DELETE',
|
|
181
|
+
headers: {
|
|
182
|
+
'Authorization': `Bearer ${session.access_token}`,
|
|
183
|
+
'apikey': SUPABASE_ANON_KEY,
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// 4. Sign out locally
|
|
188
|
+
await logout();
|
|
189
|
+
}
|
|
190
|
+
|
|
134
191
|
export { AUTH_FILE, supaFetch };
|
package/src/cloud/constants.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const SUPABASE_URL = process.env.MEMOIR_SUPABASE_URL || 'https://oqrkxytbahfwjhcbyzrx.supabase.co';
|
|
2
2
|
export const SUPABASE_ANON_KEY = process.env.MEMOIR_SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9xcmt4eXRiYWhmd2poY2J5enJ4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzMyMTQ4MzMsImV4cCI6MjA4ODc5MDgzM30.jOKOi73OJgIgi1zj0VOIQkGp0xqS3ee4gfCjpdqCnvM';
|
|
3
3
|
export const STORAGE_BUCKET = 'memoir-backups';
|
|
4
|
-
export const MAX_BACKUPS_FREE =
|
|
4
|
+
export const MAX_BACKUPS_FREE = 100;
|
|
5
5
|
export const MAX_BACKUPS_PRO = 50;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
|
|
7
|
+
// The instruction files each AI tool reads, in priority order
|
|
8
|
+
const INSTRUCTION_FILES = [
|
|
9
|
+
{ file: 'CLAUDE.md', tool: 'Claude' },
|
|
10
|
+
{ file: '.cursorrules', tool: 'Cursor' },
|
|
11
|
+
{ file: '.windsurfrules', tool: 'Windsurf' },
|
|
12
|
+
{ file: '.github/copilot-instructions.md', tool: 'Copilot' },
|
|
13
|
+
{ file: '.clinerules', tool: 'Cline' },
|
|
14
|
+
{ file: 'GEMINI.md', tool: 'Gemini' },
|
|
15
|
+
{ file: '.aider.system-prompt.md', tool: 'Aider' },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const BLOCK_START = '<!-- memoir:start -->';
|
|
19
|
+
const BLOCK_END = '<!-- memoir:end -->';
|
|
20
|
+
|
|
21
|
+
const MEMOIR_BLOCK = `${BLOCK_START}
|
|
22
|
+
# Memoir — Persistent Memory
|
|
23
|
+
<!-- Cross-session memory for AI tools — https://memoir.sh -->
|
|
24
|
+
<!-- Install: npm i -g memoir-cli -->
|
|
25
|
+
Use memoir_recall to search past context before answering project questions.
|
|
26
|
+
Use memoir_remember to save important decisions, architecture choices, or context worth keeping.
|
|
27
|
+
${BLOCK_END}`;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Detect which instruction files exist in the current project.
|
|
31
|
+
* Returns array of { file, tool, fullPath, exists }
|
|
32
|
+
*/
|
|
33
|
+
function detectInstructionFiles(projectDir) {
|
|
34
|
+
return INSTRUCTION_FILES.map(({ file, tool }) => {
|
|
35
|
+
const fullPath = path.join(projectDir, file);
|
|
36
|
+
return { file, tool, fullPath, exists: fs.existsSync(fullPath) };
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if a file already has the memoir block
|
|
42
|
+
*/
|
|
43
|
+
function hasMemoir(content) {
|
|
44
|
+
return content.includes(BLOCK_START);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Inject memoir block into a file (append, or create)
|
|
49
|
+
*/
|
|
50
|
+
async function injectBlock(filePath) {
|
|
51
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
52
|
+
|
|
53
|
+
if (await fs.pathExists(filePath)) {
|
|
54
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
55
|
+
if (hasMemoir(content)) {
|
|
56
|
+
return 'already';
|
|
57
|
+
}
|
|
58
|
+
// Append with spacing
|
|
59
|
+
const separator = content.endsWith('\n') ? '\n' : '\n\n';
|
|
60
|
+
await fs.writeFile(filePath, content + separator + MEMOIR_BLOCK + '\n');
|
|
61
|
+
return 'appended';
|
|
62
|
+
} else {
|
|
63
|
+
await fs.writeFile(filePath, MEMOIR_BLOCK + '\n');
|
|
64
|
+
return 'created';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Remove memoir block from a file
|
|
70
|
+
*/
|
|
71
|
+
async function removeBlock(filePath) {
|
|
72
|
+
if (!await fs.pathExists(filePath)) return 'not_found';
|
|
73
|
+
|
|
74
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
75
|
+
if (!hasMemoir(content)) return 'not_present';
|
|
76
|
+
|
|
77
|
+
const startIdx = content.indexOf(BLOCK_START);
|
|
78
|
+
const endIdx = content.indexOf(BLOCK_END);
|
|
79
|
+
if (startIdx === -1 || endIdx === -1) return 'not_present';
|
|
80
|
+
|
|
81
|
+
const before = content.slice(0, startIdx).replace(/\n+$/, '');
|
|
82
|
+
const after = content.slice(endIdx + BLOCK_END.length).replace(/^\n+/, '');
|
|
83
|
+
const cleaned = (before + (before && after ? '\n\n' : '') + after).trim();
|
|
84
|
+
|
|
85
|
+
if (!cleaned) {
|
|
86
|
+
// File would be empty — delete it
|
|
87
|
+
await fs.remove(filePath);
|
|
88
|
+
return 'deleted';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await fs.writeFile(filePath, cleaned + '\n');
|
|
92
|
+
return 'removed';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Track which projects have been activated
|
|
97
|
+
*/
|
|
98
|
+
function getActivatedPath() {
|
|
99
|
+
return path.join(os.homedir(), '.config', 'memoir', 'activated-projects.json');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function loadActivated() {
|
|
103
|
+
const p = getActivatedPath();
|
|
104
|
+
if (await fs.pathExists(p)) {
|
|
105
|
+
const data = await fs.readJson(p);
|
|
106
|
+
return Array.isArray(data) ? data : [];
|
|
107
|
+
}
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function markActivated(projectDir) {
|
|
112
|
+
const activated = await loadActivated();
|
|
113
|
+
if (!activated.includes(projectDir)) {
|
|
114
|
+
activated.push(projectDir);
|
|
115
|
+
await fs.ensureDir(path.dirname(getActivatedPath()));
|
|
116
|
+
await fs.writeJson(getActivatedPath(), activated);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function markDeactivated(projectDir) {
|
|
121
|
+
let activated = await loadActivated();
|
|
122
|
+
activated = activated.filter(p => p !== projectDir);
|
|
123
|
+
await fs.ensureDir(path.dirname(getActivatedPath()));
|
|
124
|
+
await fs.writeJson(getActivatedPath(), activated);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function isActivated(projectDir) {
|
|
128
|
+
const activated = await loadActivated();
|
|
129
|
+
return activated.includes(projectDir);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* memoir activate — inject memoir instructions into project AI config files
|
|
134
|
+
*/
|
|
135
|
+
export async function activateCommand(options = {}) {
|
|
136
|
+
const projectDir = process.cwd();
|
|
137
|
+
const detected = detectInstructionFiles(projectDir);
|
|
138
|
+
const existing = detected.filter(d => d.exists);
|
|
139
|
+
|
|
140
|
+
if (existing.length === 0) {
|
|
141
|
+
// No instruction files exist — create CLAUDE.md by default
|
|
142
|
+
const result = await injectBlock(path.join(projectDir, 'CLAUDE.md'));
|
|
143
|
+
console.log(chalk.green('\n ✔ Created CLAUDE.md with memoir instructions'));
|
|
144
|
+
console.log(chalk.gray(' Your AI will use memoir_recall and memoir_remember automatically.\n'));
|
|
145
|
+
await markActivated(projectDir);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Inject into all existing instruction files
|
|
150
|
+
let injected = 0;
|
|
151
|
+
for (const { file, tool, fullPath } of existing) {
|
|
152
|
+
const result = await injectBlock(fullPath);
|
|
153
|
+
if (result === 'appended') {
|
|
154
|
+
console.log(chalk.green(` ✔ Added memoir to ${file}`) + chalk.gray(` (${tool})`));
|
|
155
|
+
injected++;
|
|
156
|
+
} else if (result === 'created') {
|
|
157
|
+
console.log(chalk.green(` ✔ Created ${file} with memoir instructions`) + chalk.gray(` (${tool})`));
|
|
158
|
+
injected++;
|
|
159
|
+
} else if (result === 'already') {
|
|
160
|
+
console.log(chalk.gray(` · ${file} already has memoir`) + chalk.gray(` (${tool})`));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (injected > 0) {
|
|
165
|
+
console.log(chalk.gray('\n Your AI tools will now use memoir automatically.\n'));
|
|
166
|
+
} else {
|
|
167
|
+
console.log(chalk.gray('\n memoir is already active in this project.\n'));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await markActivated(projectDir);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* memoir deactivate — remove memoir instructions from project AI config files
|
|
175
|
+
*/
|
|
176
|
+
export async function deactivateCommand(options = {}) {
|
|
177
|
+
const projectDir = process.cwd();
|
|
178
|
+
const detected = detectInstructionFiles(projectDir);
|
|
179
|
+
|
|
180
|
+
let removed = 0;
|
|
181
|
+
for (const { file, tool, fullPath } of detected) {
|
|
182
|
+
const result = await removeBlock(fullPath);
|
|
183
|
+
if (result === 'removed') {
|
|
184
|
+
console.log(chalk.yellow(` ✔ Removed memoir from ${file}`) + chalk.gray(` (${tool})`));
|
|
185
|
+
removed++;
|
|
186
|
+
} else if (result === 'deleted') {
|
|
187
|
+
console.log(chalk.yellow(` ✔ Deleted ${file}`) + chalk.gray(' (was only memoir block)'));
|
|
188
|
+
removed++;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (removed === 0) {
|
|
193
|
+
console.log(chalk.gray(' · memoir is not active in this project.\n'));
|
|
194
|
+
} else {
|
|
195
|
+
console.log(chalk.gray('\n memoir instructions removed.\n'));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
await markDeactivated(projectDir);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Prompt to activate — called from push on first push per project
|
|
203
|
+
*/
|
|
204
|
+
export async function promptActivate() {
|
|
205
|
+
const projectDir = process.cwd();
|
|
206
|
+
|
|
207
|
+
// Don't prompt if already activated or if not in a project directory
|
|
208
|
+
if (await isActivated(projectDir)) return;
|
|
209
|
+
|
|
210
|
+
// Check if we're in a project (has git, or has instruction files, or has package.json etc.)
|
|
211
|
+
const projectSignals = ['.git', 'package.json', 'Cargo.toml', 'go.mod', 'pyproject.toml', 'Makefile'];
|
|
212
|
+
const isProject = projectSignals.some(f => fs.existsSync(path.join(projectDir, f)));
|
|
213
|
+
if (!isProject) {
|
|
214
|
+
await markActivated(projectDir); // Don't ask again for non-projects
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
console.log('');
|
|
219
|
+
const { activate } = await inquirer.prompt([{
|
|
220
|
+
type: 'confirm',
|
|
221
|
+
name: 'activate',
|
|
222
|
+
message: 'Add memoir instructions to this project so your AI uses it automatically?',
|
|
223
|
+
default: true,
|
|
224
|
+
}]);
|
|
225
|
+
|
|
226
|
+
if (activate) {
|
|
227
|
+
await activateCommand();
|
|
228
|
+
} else {
|
|
229
|
+
await markActivated(projectDir); // Don't ask again
|
|
230
|
+
console.log(chalk.gray(' · Skipped. Run ') + chalk.cyan('memoir activate') + chalk.gray(' anytime to enable.\n'));
|
|
231
|
+
}
|
|
232
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -23,7 +23,7 @@ function getGitHubUsername() {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export async function initCommand() {
|
|
26
|
+
export async function initCommand(options = {}) {
|
|
27
27
|
console.log('');
|
|
28
28
|
console.log(boxen(
|
|
29
29
|
gradient.pastel('memoir') + '\n' +
|
|
@@ -34,57 +34,80 @@ export async function initCommand() {
|
|
|
34
34
|
|
|
35
35
|
const detectedUser = getGitHubUsername();
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
{
|
|
39
|
-
type: 'list',
|
|
40
|
-
name: 'direction',
|
|
41
|
-
message: 'Upload or download?',
|
|
42
|
-
choices: [
|
|
43
|
-
{ name: 'Upload — back up this machine', value: 'upload' },
|
|
44
|
-
{ name: 'Download — restore from backup', value: 'download' }
|
|
45
|
-
]
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
type: 'list',
|
|
49
|
-
name: 'provider',
|
|
50
|
-
message: (answers) => answers.direction === 'upload' ? 'Back up to?' : 'Restore from?',
|
|
51
|
-
choices: [
|
|
52
|
-
{ name: 'GitHub', value: 'git' },
|
|
53
|
-
{ name: 'Local folder', value: 'local' }
|
|
54
|
-
]
|
|
55
|
-
}
|
|
56
|
-
]);
|
|
57
|
-
|
|
58
|
-
let config = { provider };
|
|
37
|
+
let direction, provider;
|
|
59
38
|
|
|
60
|
-
if (provider
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
name: 'localPath',
|
|
65
|
-
message: msg,
|
|
66
|
-
validate: (input) => input.trim() ? true : 'Required'
|
|
67
|
-
}]);
|
|
68
|
-
config.localPath = localPath;
|
|
39
|
+
if (options.provider) {
|
|
40
|
+
// Non-interactive: use flags directly
|
|
41
|
+
provider = options.provider;
|
|
42
|
+
direction = options.direction || 'upload';
|
|
69
43
|
} else {
|
|
44
|
+
// Interactive: prompt the user
|
|
70
45
|
const answers = await inquirer.prompt([
|
|
71
46
|
{
|
|
72
|
-
type: '
|
|
73
|
-
name: '
|
|
74
|
-
message: '
|
|
75
|
-
|
|
76
|
-
|
|
47
|
+
type: 'list',
|
|
48
|
+
name: 'direction',
|
|
49
|
+
message: 'Upload or download?',
|
|
50
|
+
choices: [
|
|
51
|
+
{ name: 'Upload — back up this machine', value: 'upload' },
|
|
52
|
+
{ name: 'Download — restore from backup', value: 'download' }
|
|
53
|
+
]
|
|
77
54
|
},
|
|
78
55
|
{
|
|
79
|
-
type: '
|
|
80
|
-
name: '
|
|
81
|
-
message: '
|
|
82
|
-
|
|
83
|
-
|
|
56
|
+
type: 'list',
|
|
57
|
+
name: 'provider',
|
|
58
|
+
message: (a) => a.direction === 'upload' ? 'Back up to?' : 'Restore from?',
|
|
59
|
+
choices: [
|
|
60
|
+
{ name: 'GitHub', value: 'git' },
|
|
61
|
+
{ name: 'Local folder', value: 'local' }
|
|
62
|
+
]
|
|
84
63
|
}
|
|
85
64
|
]);
|
|
86
|
-
|
|
87
|
-
|
|
65
|
+
direction = answers.direction;
|
|
66
|
+
provider = answers.provider;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let config = { provider };
|
|
70
|
+
|
|
71
|
+
if (provider === 'local') {
|
|
72
|
+
let localPath;
|
|
73
|
+
if (options.localPath) {
|
|
74
|
+
localPath = options.localPath;
|
|
75
|
+
} else {
|
|
76
|
+
const msg = direction === 'upload' ? 'Save to:' : 'Backup folder:';
|
|
77
|
+
const answers = await inquirer.prompt([{
|
|
78
|
+
type: 'input',
|
|
79
|
+
name: 'localPath',
|
|
80
|
+
message: msg,
|
|
81
|
+
validate: (input) => input.trim() ? true : 'Required'
|
|
82
|
+
}]);
|
|
83
|
+
localPath = answers.localPath;
|
|
84
|
+
}
|
|
85
|
+
config.localPath = localPath;
|
|
86
|
+
} else {
|
|
87
|
+
let username, repo;
|
|
88
|
+
if (options.username) {
|
|
89
|
+
username = options.username.trim();
|
|
90
|
+
repo = (options.repo || 'ai-memory').trim();
|
|
91
|
+
} else {
|
|
92
|
+
const answers = await inquirer.prompt([
|
|
93
|
+
{
|
|
94
|
+
type: 'input',
|
|
95
|
+
name: 'username',
|
|
96
|
+
message: 'GitHub username:',
|
|
97
|
+
default: detectedUser || undefined,
|
|
98
|
+
validate: (input) => input.trim() ? true : 'Required'
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
type: 'input',
|
|
102
|
+
name: 'repo',
|
|
103
|
+
message: 'Repo name:',
|
|
104
|
+
default: 'ai-memory',
|
|
105
|
+
validate: (input) => input.trim() ? true : 'Required'
|
|
106
|
+
}
|
|
107
|
+
]);
|
|
108
|
+
username = answers.username.trim();
|
|
109
|
+
repo = answers.repo.trim();
|
|
110
|
+
}
|
|
88
111
|
|
|
89
112
|
config.gitRepo = `https://github.com/${username}/${repo}.git`;
|
|
90
113
|
console.log(chalk.gray(` → ${config.gitRepo}`));
|
|
@@ -109,12 +132,18 @@ export async function initCommand() {
|
|
|
109
132
|
}
|
|
110
133
|
|
|
111
134
|
// Ask about encryption
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
135
|
+
let encrypt;
|
|
136
|
+
if (options.encrypt !== undefined) {
|
|
137
|
+
encrypt = options.encrypt;
|
|
138
|
+
} else {
|
|
139
|
+
const answers = await inquirer.prompt([{
|
|
140
|
+
type: 'confirm',
|
|
141
|
+
name: 'encrypt',
|
|
142
|
+
message: 'Enable E2E encryption? (protects your data even if backup is compromised)',
|
|
143
|
+
default: true
|
|
144
|
+
}]);
|
|
145
|
+
encrypt = answers.encrypt;
|
|
146
|
+
}
|
|
118
147
|
config.encrypt = encrypt;
|
|
119
148
|
|
|
120
149
|
if (encrypt) {
|
package/src/commands/login.js
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import boxen from 'boxen';
|
|
3
3
|
import gradient from 'gradient-string';
|
|
4
4
|
import inquirer from 'inquirer';
|
|
5
|
-
import { signIn, signUp, saveSession, getSession, logout, getSubscription, resetPassword } from '../cloud/auth.js';
|
|
5
|
+
import { signIn, signUp, saveSession, getSession, logout, getSubscription, resetPassword, deleteAccount } from '../cloud/auth.js';
|
|
6
6
|
|
|
7
7
|
export async function loginCommand(options = {}) {
|
|
8
8
|
// Check if already logged in
|
|
@@ -131,3 +131,43 @@ export async function logoutCommand() {
|
|
|
131
131
|
{ padding: 1, borderStyle: 'round', borderColor: 'green', dimBorder: true }
|
|
132
132
|
) + '\n');
|
|
133
133
|
}
|
|
134
|
+
|
|
135
|
+
export async function deleteAccountCommand(options = {}) {
|
|
136
|
+
const session = await getSession();
|
|
137
|
+
if (!session) {
|
|
138
|
+
console.log('\n' + boxen(
|
|
139
|
+
chalk.red('✖ Not logged in. Run ') + chalk.cyan('memoir login') + chalk.red(' first.'),
|
|
140
|
+
{ padding: 1, borderStyle: 'round', borderColor: 'red' }
|
|
141
|
+
) + '\n');
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!options.confirm) {
|
|
146
|
+
const answer = await inquirer.prompt([{
|
|
147
|
+
type: 'input',
|
|
148
|
+
name: 'confirmation',
|
|
149
|
+
message: 'Type DELETE to confirm account deletion:',
|
|
150
|
+
}]);
|
|
151
|
+
|
|
152
|
+
if (answer.confirmation !== 'DELETE') {
|
|
153
|
+
console.log('\n' + chalk.gray(' Account deletion cancelled.') + '\n');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
await deleteAccount(session);
|
|
160
|
+
|
|
161
|
+
console.log('\n' + boxen(
|
|
162
|
+
gradient.pastel(' memoir cloud ') + '\n\n' +
|
|
163
|
+
chalk.green('✔ Account deleted.') + '\n' +
|
|
164
|
+
chalk.gray('All backups, shared links, and data have been removed.'),
|
|
165
|
+
{ padding: 1, borderStyle: 'round', borderColor: 'green', dimBorder: true }
|
|
166
|
+
) + '\n');
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.log('\n' + boxen(
|
|
169
|
+
chalk.red('✖ ' + error.message),
|
|
170
|
+
{ padding: 1, borderStyle: 'round', borderColor: 'red' }
|
|
171
|
+
) + '\n');
|
|
172
|
+
}
|
|
173
|
+
}
|
package/src/commands/push.js
CHANGED
|
@@ -14,6 +14,7 @@ import { scanForSecrets, printSecurityReport } from '../security/scanner.js';
|
|
|
14
14
|
import { encryptDirectory, createVerifyToken } from '../security/encryption.js';
|
|
15
15
|
import { getRawConfig, saveConfig, migrateConfigToV2 } from '../config.js';
|
|
16
16
|
import { scanWorkspace } from '../workspace/tracker.js';
|
|
17
|
+
import { promptActivate } from './activate.js';
|
|
17
18
|
|
|
18
19
|
export async function pushCommand(options = {}) {
|
|
19
20
|
const config = await getConfig(options.profile);
|
|
@@ -262,6 +263,13 @@ export async function pushCommand(options = {}) {
|
|
|
262
263
|
chalk.gray('Restore on another machine with: ') + chalk.cyan('memoir restore'),
|
|
263
264
|
{ padding: 1, borderStyle: 'round', borderColor: 'green', dimBorder: true }
|
|
264
265
|
) + '\n');
|
|
266
|
+
|
|
267
|
+
// Prompt to activate memoir in this project (first push only)
|
|
268
|
+
try {
|
|
269
|
+
await promptActivate();
|
|
270
|
+
} catch {
|
|
271
|
+
// Activation prompt is best-effort
|
|
272
|
+
}
|
|
265
273
|
} catch (error) {
|
|
266
274
|
spinner.fail(chalk.red('Sync failed: ') + error.message);
|
|
267
275
|
} finally {
|
package/src/commands/upgrade.js
CHANGED
|
@@ -63,7 +63,7 @@ export async function upgradeCommand() {
|
|
|
63
63
|
const sep = chalk.gray('─'.repeat(col1 + col2 + 18));
|
|
64
64
|
|
|
65
65
|
const rows = [
|
|
66
|
-
[chalk.gray('
|
|
66
|
+
[chalk.gray('100 cloud backups'), chalk.white('Unlimited backups'), chalk.white('Unlimited backups')],
|
|
67
67
|
[chalk.gray('Local only'), chalk.white('Unlimited machines'), chalk.white('Shared team context')],
|
|
68
68
|
[chalk.gray('Manual snapshots'), chalk.white('Auto snapshots'), chalk.white('Team dashboard')],
|
|
69
69
|
[chalk.gray('Community support'), chalk.white('Priority support'), chalk.white('Audit log')],
|