memoir-cli 2.2.0 → 2.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 +91 -113
- package/bin/memoir.js +75 -12
- package/package.json +7 -3
- package/src/adapters/index.js +1 -1
- package/src/commands/diff.js +2 -2
- package/src/commands/doctor.js +2 -2
- package/src/commands/profile.js +199 -0
- package/src/commands/push.js +4 -2
- package/src/commands/restore.js +1 -1
- package/src/commands/resume.js +1 -1
- package/src/commands/snapshot.js +1 -1
- package/src/commands/status.js +2 -2
- package/src/commands/view.js +2 -2
- package/src/config.js +94 -7
- package/src/tools/chatgpt.js +24 -0
- package/src/tools/index.js +2 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# memoir
|
|
4
4
|
**Your AI Remembers Everything. Sync It Everywhere.**
|
|
5
5
|
|
|
6
6
|
[](https://npmjs.org/package/memoir-cli)
|
|
@@ -14,186 +14,164 @@
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## The Problem
|
|
18
18
|
|
|
19
|
-
You spend weeks teaching your
|
|
19
|
+
You spend weeks teaching your AI tools how you code — your preferred patterns, project context, coding standards.
|
|
20
20
|
|
|
21
|
-
Then
|
|
21
|
+
Then you switch laptops. Or try a new AI tool. Or want your team on the same page.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
All that context is trapped in hidden dotfiles on one machine.
|
|
24
24
|
|
|
25
|
-
##
|
|
25
|
+
## The Solution
|
|
26
26
|
|
|
27
|
-
`memoir`
|
|
28
|
-
|
|
29
|
-
No locked-in SaaS, no lost context, no complex shell scripts. Switch from Claude to Gemini in one command.
|
|
30
|
-
|
|
31
|
-
### Supported Integrations
|
|
32
|
-
- [x] **Gemini CLI**
|
|
33
|
-
- [x] **Claude Code**
|
|
34
|
-
- [x] **OpenAI Codex CLI**
|
|
35
|
-
- [x] **Cursor**
|
|
36
|
-
- [x] **GitHub Copilot**
|
|
37
|
-
- [x] **Windsurf**
|
|
38
|
-
- [x] **Aider**
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## 🛠️ Installation
|
|
43
|
-
|
|
44
|
-
Install globally via npm so you can use it anywhere on your machine:
|
|
27
|
+
`memoir` extracts, backs up, restores, and **translates** your AI memory across any computer and any tool. One command to save. One command to restore. One command to translate between tools.
|
|
45
28
|
|
|
46
29
|
```bash
|
|
47
30
|
npm install -g memoir-cli
|
|
48
31
|
```
|
|
49
32
|
|
|
50
|
-
|
|
33
|
+
### Supported Tools (11)
|
|
34
|
+
| Tool | Config synced |
|
|
35
|
+
|------|--------------|
|
|
36
|
+
| **ChatGPT** | `CHATGPT.md` — custom instructions, preferences |
|
|
37
|
+
| **Claude Code** | `~/.claude/` — settings, projects, memory files |
|
|
38
|
+
| **Gemini CLI** | `~/.gemini/` — settings, GEMINI.md |
|
|
39
|
+
| **OpenAI Codex** | `~/.codex/` — config, instructions |
|
|
40
|
+
| **Cursor** | Settings, keybindings, rules |
|
|
41
|
+
| **GitHub Copilot** | Config, settings |
|
|
42
|
+
| **Windsurf** | Settings, keybindings, rules |
|
|
43
|
+
| **Zed** | Settings, keymap, tasks |
|
|
44
|
+
| **Cline** | Settings, rules |
|
|
45
|
+
| **Continue.dev** | Config, rules |
|
|
46
|
+
| **Aider** | `.aider.conf.yml`, system prompt |
|
|
47
|
+
|
|
48
|
+
Plus **per-project configs**: automatically finds `CLAUDE.md`, `GEMINI.md`, `CHATGPT.md`, `.cursorrules`, `AGENTS.md` across all your projects.
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
Run the setup wizard. We'll help you securely link a private GitHub repository or a local sync folder.
|
|
50
|
+
---
|
|
54
51
|
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
### 1. Initialize
|
|
55
55
|
```bash
|
|
56
56
|
memoir init
|
|
57
|
+
# Walks you through setup — GitHub repo or local folder
|
|
58
|
+
# Auto-creates a private repo if you have gh CLI
|
|
57
59
|
```
|
|
58
60
|
|
|
59
|
-
### 2.
|
|
60
|
-
Just had a great session? Save your AI's learned context to the cloud:
|
|
61
|
-
|
|
61
|
+
### 2. Back up your memory
|
|
62
62
|
```bash
|
|
63
63
|
memoir push
|
|
64
|
-
# or simply use the alias:
|
|
65
|
-
memoir remember
|
|
66
64
|
```
|
|
67
65
|
|
|
68
|
-
### 3. Restore
|
|
69
|
-
Got a new machine? Pull your brain down instantly:
|
|
70
|
-
|
|
66
|
+
### 3. Restore on a new machine
|
|
71
67
|
```bash
|
|
72
68
|
memoir restore
|
|
73
|
-
# or:
|
|
74
|
-
memoir pull
|
|
75
69
|
```
|
|
76
70
|
|
|
77
|
-
### 4. Translate
|
|
78
|
-
Switch AI tools without losing context. Memoir uses Gemini AI to intelligently rewrite your memory files for any supported tool:
|
|
79
|
-
|
|
71
|
+
### 4. Translate between tools
|
|
80
72
|
```bash
|
|
81
73
|
memoir migrate --from claude --to gemini
|
|
82
|
-
#
|
|
83
|
-
memoir migrate
|
|
74
|
+
# AI-powered translation — not copy-paste, real rewriting
|
|
84
75
|
```
|
|
85
76
|
|
|
86
|
-
Your Claude instructions become a proper `GEMINI.md` — not a copy-paste, but a real translation that follows each tool's conventions.
|
|
87
|
-
|
|
88
77
|
---
|
|
89
78
|
|
|
90
|
-
##
|
|
79
|
+
## All Commands
|
|
91
80
|
|
|
92
81
|
| Command | What it does |
|
|
93
82
|
|---------|-------------|
|
|
94
|
-
| `memoir init` | Setup wizard —
|
|
95
|
-
| `memoir push` |
|
|
96
|
-
| `memoir restore` |
|
|
97
|
-
| `memoir status` | Show
|
|
98
|
-
| `memoir
|
|
99
|
-
| `memoir
|
|
83
|
+
| `memoir init` | Setup wizard — GitHub or local, upload or download |
|
|
84
|
+
| `memoir push` | Back up all AI configs |
|
|
85
|
+
| `memoir restore` | Restore on a new machine (non-destructive) |
|
|
86
|
+
| `memoir status` | Show detected AI tools |
|
|
87
|
+
| `memoir doctor` | Diagnose issues, scan for secrets |
|
|
88
|
+
| `memoir view` | Preview what's in your backup |
|
|
89
|
+
| `memoir diff` | Show changes since last backup |
|
|
90
|
+
| `memoir migrate` | Translate memory between tools via AI |
|
|
91
|
+
| `memoir snapshot` | Capture current coding session |
|
|
92
|
+
| `memoir resume` | Pick up where you left off |
|
|
93
|
+
| `memoir profile` | Manage profiles (personal/work) |
|
|
94
|
+
| `memoir update` | Self-update to latest version |
|
|
100
95
|
|
|
101
96
|
---
|
|
102
97
|
|
|
103
|
-
##
|
|
98
|
+
## Profiles
|
|
99
|
+
|
|
100
|
+
Separate personal and work configs — different repos, different tools.
|
|
104
101
|
|
|
105
|
-
### New laptop setup
|
|
106
102
|
```bash
|
|
107
|
-
#
|
|
108
|
-
memoir
|
|
103
|
+
memoir profile create work # set up work profile with its own repo
|
|
104
|
+
memoir profile create personal # personal side projects
|
|
109
105
|
|
|
110
|
-
#
|
|
111
|
-
memoir
|
|
112
|
-
# All your .claude/, .gemini/, .cursorrules configs restored in 30 seconds
|
|
113
|
-
```
|
|
106
|
+
memoir push --profile work # sync only work configs
|
|
107
|
+
memoir restore --profile personal
|
|
114
108
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
memoir migrate --from claude --to gemini
|
|
109
|
+
memoir profile list # see all profiles
|
|
110
|
+
memoir profile switch work # change default
|
|
118
111
|
```
|
|
119
|
-
Your CLAUDE.md + Claude memory files get intelligently rewritten as a proper GEMINI.md — not a copy-paste, but a real translation that follows Gemini's conventions.
|
|
120
112
|
|
|
121
|
-
|
|
122
|
-
```bash
|
|
123
|
-
# Team lead writes CLAUDE.md, then generates for everyone else:
|
|
124
|
-
memoir migrate --from claude --to cursor
|
|
125
|
-
memoir migrate --from claude --to copilot
|
|
126
|
-
memoir migrate --from claude --to codex
|
|
127
|
-
```
|
|
113
|
+
Each profile can filter which tools to sync, so your personal side project memory never mixes with work.
|
|
128
114
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
memoir migrate --from claude --to codex
|
|
133
|
-
memoir migrate --from claude --to cursor
|
|
134
|
-
memoir migrate --from claude --to windsurf
|
|
135
|
-
memoir migrate --from claude --to aider
|
|
136
|
-
```
|
|
137
|
-
Use one tool as the source of truth, propagate to all others.
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Common Workflows
|
|
138
118
|
|
|
139
|
-
###
|
|
119
|
+
### New laptop setup
|
|
140
120
|
```bash
|
|
141
|
-
|
|
142
|
-
|
|
121
|
+
# Old machine
|
|
122
|
+
memoir push
|
|
123
|
+
|
|
124
|
+
# New machine
|
|
125
|
+
memoir init # → Download → same GitHub repo
|
|
126
|
+
memoir restore # All configs restored in seconds
|
|
143
127
|
```
|
|
144
128
|
|
|
145
|
-
###
|
|
129
|
+
### Translate between tools
|
|
146
130
|
```bash
|
|
147
|
-
memoir migrate --from
|
|
148
|
-
#
|
|
149
|
-
# → Overwrite / Append / Skip
|
|
150
|
-
# Append adds a dated separator so you keep your existing instructions
|
|
131
|
+
memoir migrate --from chatgpt --to claude
|
|
132
|
+
# Your ChatGPT custom instructions become a proper CLAUDE.md
|
|
151
133
|
```
|
|
152
134
|
|
|
153
|
-
###
|
|
135
|
+
### Fan out to every tool
|
|
154
136
|
```bash
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
# Next morning, different machine
|
|
159
|
-
memoir pull
|
|
137
|
+
memoir migrate --from chatgpt --to all
|
|
138
|
+
# One source of truth, every tool gets its own format
|
|
160
139
|
```
|
|
161
140
|
|
|
162
|
-
###
|
|
141
|
+
### Daily sync
|
|
163
142
|
```bash
|
|
164
|
-
memoir
|
|
165
|
-
#
|
|
143
|
+
memoir push # end of day
|
|
144
|
+
memoir restore # next morning, different machine
|
|
166
145
|
```
|
|
167
146
|
|
|
168
147
|
---
|
|
169
148
|
|
|
170
|
-
##
|
|
149
|
+
## Security
|
|
171
150
|
|
|
172
|
-
|
|
151
|
+
Memoir **only** syncs config files, instructions, and memory markdown. It never touches credentials, API keys, `.env` files, or auth tokens.
|
|
173
152
|
|
|
174
|
-
|
|
153
|
+
Run `memoir doctor` to see exactly what would be synced and scan for accidental secrets before pushing.
|
|
175
154
|
|
|
176
155
|
---
|
|
177
156
|
|
|
178
|
-
##
|
|
157
|
+
## Roadmap
|
|
179
158
|
|
|
180
|
-
**
|
|
181
|
-
-
|
|
182
|
-
-
|
|
183
|
-
-
|
|
159
|
+
- **Universal format** — write one `MEMOIR.md`, generate all tool-specific configs
|
|
160
|
+
- **Cloud sync** — no GitHub needed, encrypted backups
|
|
161
|
+
- **Teams** — shared coding standards across your whole team
|
|
162
|
+
- **Templates** — community-shared AI tool configs
|
|
184
163
|
|
|
185
164
|
---
|
|
186
165
|
|
|
187
|
-
##
|
|
166
|
+
## Contributing
|
|
188
167
|
|
|
189
|
-
|
|
168
|
+
Contributions welcome — especially new tool adapters and migration improvements.
|
|
190
169
|
|
|
191
|
-
1. Fork the
|
|
192
|
-
2. Create your
|
|
193
|
-
3. Commit
|
|
194
|
-
4.
|
|
195
|
-
5. Open a Pull Request
|
|
170
|
+
1. Fork the repo
|
|
171
|
+
2. Create your branch (`git checkout -b feature/my-feature`)
|
|
172
|
+
3. Commit and push
|
|
173
|
+
4. Open a PR
|
|
196
174
|
|
|
197
|
-
##
|
|
175
|
+
## License
|
|
198
176
|
|
|
199
|
-
|
|
177
|
+
MIT
|
package/bin/memoir.js
CHANGED
|
@@ -13,6 +13,7 @@ import { diffCommand } from '../src/commands/diff.js';
|
|
|
13
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
|
+
import { profileListCommand, profileCreateCommand, profileSwitchCommand, profileDeleteCommand } from '../src/commands/profile.js';
|
|
16
17
|
import { createRequire } from 'module';
|
|
17
18
|
|
|
18
19
|
const require = createRequire(import.meta.url);
|
|
@@ -57,8 +58,9 @@ if (process.argv.length <= 2) {
|
|
|
57
58
|
chalk.cyan(' memoir snapshot ') + chalk.gray('— capture your current session') + '\n' +
|
|
58
59
|
chalk.cyan(' memoir resume ') + chalk.gray('— pick up where you left off') + '\n' +
|
|
59
60
|
chalk.cyan(' memoir status ') + chalk.gray('— see detected AI tools') + '\n' +
|
|
61
|
+
chalk.cyan(' memoir profile ') + chalk.gray('— manage profiles (personal/work)') + '\n' +
|
|
60
62
|
chalk.cyan(' memoir update ') + chalk.gray('— update to latest version') + '\n\n' +
|
|
61
|
-
chalk.gray(' Tip: use --
|
|
63
|
+
chalk.gray(' Tip: use --profile work to sync a specific profile') + '\n\n' +
|
|
62
64
|
chalk.gray(`v${VERSION}`),
|
|
63
65
|
{ padding: 1, borderStyle: 'round', borderColor: 'cyan', dimBorder: true }
|
|
64
66
|
) + '\n');
|
|
@@ -93,7 +95,8 @@ program
|
|
|
93
95
|
.command('push')
|
|
94
96
|
.alias('remember')
|
|
95
97
|
.description('Back up your AI memory to the cloud')
|
|
96
|
-
.option('--only <tools>', 'Only sync specific tools (comma-separated
|
|
98
|
+
.option('--only <tools>', 'Only sync specific tools (comma-separated)')
|
|
99
|
+
.option('-p, --profile <name>', 'Use a specific profile')
|
|
97
100
|
.action(async (options) => {
|
|
98
101
|
try {
|
|
99
102
|
await pushCommand(options);
|
|
@@ -107,8 +110,9 @@ program
|
|
|
107
110
|
.command('restore')
|
|
108
111
|
.alias('pull')
|
|
109
112
|
.description('Restore your AI memory on this machine')
|
|
110
|
-
.option('--only <tools>', 'Only restore specific tools (comma-separated
|
|
113
|
+
.option('--only <tools>', 'Only restore specific tools (comma-separated)')
|
|
111
114
|
.option('-y, --yes', 'Skip confirmation prompts (restore all)')
|
|
115
|
+
.option('-p, --profile <name>', 'Use a specific profile')
|
|
112
116
|
.action(async (options) => {
|
|
113
117
|
try {
|
|
114
118
|
await restoreCommand(options);
|
|
@@ -121,9 +125,10 @@ program
|
|
|
121
125
|
program
|
|
122
126
|
.command('status')
|
|
123
127
|
.description('See what AI tools are on this machine')
|
|
124
|
-
.
|
|
128
|
+
.option('-p, --profile <name>', 'Use a specific profile')
|
|
129
|
+
.action(async (options) => {
|
|
125
130
|
try {
|
|
126
|
-
await statusCommand();
|
|
131
|
+
await statusCommand(options);
|
|
127
132
|
} catch (err) {
|
|
128
133
|
console.error(chalk.red('\n✖ Error:'), err.message);
|
|
129
134
|
process.exit(1);
|
|
@@ -134,9 +139,10 @@ program
|
|
|
134
139
|
.command('doctor')
|
|
135
140
|
.alias('diagnose')
|
|
136
141
|
.description('Diagnose common issues with your memoir setup')
|
|
137
|
-
.
|
|
142
|
+
.option('-p, --profile <name>', 'Use a specific profile')
|
|
143
|
+
.action(async (options) => {
|
|
138
144
|
try {
|
|
139
|
-
await doctorCommand();
|
|
145
|
+
await doctorCommand(options);
|
|
140
146
|
} catch (err) {
|
|
141
147
|
console.error(chalk.red('\n✖ Error:'), err.message);
|
|
142
148
|
process.exit(1);
|
|
@@ -147,9 +153,10 @@ program
|
|
|
147
153
|
.command('view')
|
|
148
154
|
.alias('ls')
|
|
149
155
|
.description('Preview what files are in your backup')
|
|
150
|
-
.
|
|
156
|
+
.option('-p, --profile <name>', 'Use a specific profile')
|
|
157
|
+
.action(async (options) => {
|
|
151
158
|
try {
|
|
152
|
-
await viewCommand();
|
|
159
|
+
await viewCommand(options);
|
|
153
160
|
} catch (err) {
|
|
154
161
|
console.error(chalk.red('\n✖ Error:'), err.message);
|
|
155
162
|
process.exit(1);
|
|
@@ -160,9 +167,10 @@ program
|
|
|
160
167
|
.command('diff')
|
|
161
168
|
.alias('changes')
|
|
162
169
|
.description('Show what changed since your last backup')
|
|
163
|
-
.
|
|
170
|
+
.option('-p, --profile <name>', 'Use a specific profile')
|
|
171
|
+
.action(async (options) => {
|
|
164
172
|
try {
|
|
165
|
-
await diffCommand();
|
|
173
|
+
await diffCommand(options);
|
|
166
174
|
} catch (err) {
|
|
167
175
|
console.error(chalk.red('\n✖ Error:'), err.message);
|
|
168
176
|
process.exit(1);
|
|
@@ -175,6 +183,7 @@ program
|
|
|
175
183
|
.description('Capture your current coding session for handoff')
|
|
176
184
|
.option('--smart', 'Use AI to generate a better summary (requires Gemini API key)')
|
|
177
185
|
.option('--goal <goal>', 'What you want to do next (goal-directed handoff)')
|
|
186
|
+
.option('-p, --profile <name>', 'Use a specific profile')
|
|
178
187
|
.action(async (options) => {
|
|
179
188
|
try {
|
|
180
189
|
await snapshotCommand(options);
|
|
@@ -189,6 +198,7 @@ program
|
|
|
189
198
|
.description('Pick up where you left off on another machine')
|
|
190
199
|
.option('--inject', 'Write the handoff where your AI tool will read it')
|
|
191
200
|
.option('--to <tool>', 'Target tool for injection (claude, gemini, cursor, codex)')
|
|
201
|
+
.option('-p, --profile <name>', 'Use a specific profile')
|
|
192
202
|
.action(async (options) => {
|
|
193
203
|
try {
|
|
194
204
|
await resumeCommand(options);
|
|
@@ -220,7 +230,6 @@ program
|
|
|
220
230
|
console.log('\n' + chalk.cyan(`Updating memoir ${VERSION} → ${chalk.green.bold(latest)}...`) + '\n');
|
|
221
231
|
|
|
222
232
|
const { execSync } = await import('child_process');
|
|
223
|
-
// Detect package manager — prefer the one that installed memoir
|
|
224
233
|
const execPath = process.argv[1] || '';
|
|
225
234
|
const useBun = execPath.includes('.bun') || process.env.BUN_INSTALL;
|
|
226
235
|
const cmd = useBun ? 'bun install -g memoir-cli' : 'npm install -g memoir-cli';
|
|
@@ -254,6 +263,60 @@ program
|
|
|
254
263
|
}
|
|
255
264
|
});
|
|
256
265
|
|
|
266
|
+
// Profile management
|
|
267
|
+
const profile = program.command('profile').description('Manage profiles (personal, work, etc.)');
|
|
268
|
+
|
|
269
|
+
profile
|
|
270
|
+
.command('list')
|
|
271
|
+
.alias('ls')
|
|
272
|
+
.description('List all profiles')
|
|
273
|
+
.action(async () => {
|
|
274
|
+
try {
|
|
275
|
+
await profileListCommand();
|
|
276
|
+
} catch (err) {
|
|
277
|
+
console.error(chalk.red('\n✖ Error:'), err.message);
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
profile
|
|
283
|
+
.command('create <name>')
|
|
284
|
+
.description('Create a new profile')
|
|
285
|
+
.action(async (name) => {
|
|
286
|
+
try {
|
|
287
|
+
await profileCreateCommand(name);
|
|
288
|
+
} catch (err) {
|
|
289
|
+
console.error(chalk.red('\n✖ Error:'), err.message);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
profile
|
|
295
|
+
.command('switch <name>')
|
|
296
|
+
.alias('use')
|
|
297
|
+
.description('Switch to a profile')
|
|
298
|
+
.action(async (name) => {
|
|
299
|
+
try {
|
|
300
|
+
await profileSwitchCommand(name);
|
|
301
|
+
} catch (err) {
|
|
302
|
+
console.error(chalk.red('\n✖ Error:'), err.message);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
profile
|
|
308
|
+
.command('delete <name>')
|
|
309
|
+
.alias('rm')
|
|
310
|
+
.description('Delete a profile')
|
|
311
|
+
.action(async (name) => {
|
|
312
|
+
try {
|
|
313
|
+
await profileDeleteCommand(name);
|
|
314
|
+
} catch (err) {
|
|
315
|
+
console.error(chalk.red('\n✖ Error:'), err.message);
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
257
320
|
program.hook('postAction', async () => {
|
|
258
321
|
await checkForUpdate();
|
|
259
322
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memoir-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
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",
|
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
"copilot",
|
|
36
36
|
"windsurf",
|
|
37
37
|
"aider",
|
|
38
|
+
"zed",
|
|
39
|
+
"cline",
|
|
40
|
+
"continue-dev",
|
|
41
|
+
"profiles",
|
|
38
42
|
"ai-memory",
|
|
39
43
|
"ai-tools",
|
|
40
44
|
"dotfiles",
|
|
@@ -42,6 +46,7 @@
|
|
|
42
46
|
"claude-code",
|
|
43
47
|
"gemini-cli",
|
|
44
48
|
"openai",
|
|
49
|
+
"chatgpt",
|
|
45
50
|
"ai-assistant",
|
|
46
51
|
"coding-assistant",
|
|
47
52
|
"context-sync",
|
|
@@ -58,7 +63,6 @@
|
|
|
58
63
|
"fs-extra": "^11.2.0",
|
|
59
64
|
"gradient-string": "^3.0.0",
|
|
60
65
|
"inquirer": "^9.2.15",
|
|
61
|
-
|
|
62
|
-
"ora": "^7.0.1"
|
|
66
|
+
"ora": "^7.0.1"
|
|
63
67
|
}
|
|
64
68
|
}
|
package/src/adapters/index.js
CHANGED
|
@@ -298,7 +298,7 @@ export async function extractMemories(stagingDir, spinner, onlyFilter = null) {
|
|
|
298
298
|
spinner.text = `📁 Scanning for project-level AI configs...`;
|
|
299
299
|
|
|
300
300
|
const projectFiles = [
|
|
301
|
-
'CLAUDE.md', 'GEMINI.md', 'AGENTS.md', '.cursorrules',
|
|
301
|
+
'CLAUDE.md', 'GEMINI.md', 'CHATGPT.md', 'AGENTS.md', '.cursorrules',
|
|
302
302
|
'.github/copilot-instructions.md', '.windsurfrules',
|
|
303
303
|
'.aider.conf.yml', '.clinerules'
|
|
304
304
|
];
|
package/src/commands/diff.js
CHANGED
|
@@ -50,8 +50,8 @@ function simpleDiff(oldText, newText) {
|
|
|
50
50
|
return output;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export async function diffCommand() {
|
|
54
|
-
const config = await getConfig();
|
|
53
|
+
export async function diffCommand(options = {}) {
|
|
54
|
+
const config = await getConfig(options.profile);
|
|
55
55
|
if (!config) {
|
|
56
56
|
console.log(chalk.red('\n✖ Not configured yet. Run: memoir init\n'));
|
|
57
57
|
return;
|
package/src/commands/doctor.js
CHANGED
|
@@ -74,7 +74,7 @@ async function scanForSecrets(files) {
|
|
|
74
74
|
return warnings;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
export async function doctorCommand() {
|
|
77
|
+
export async function doctorCommand(options = {}) {
|
|
78
78
|
const spinner = ora({ text: 'Running diagnostics...', color: 'cyan' }).start();
|
|
79
79
|
const lines = [];
|
|
80
80
|
let passCount = 0;
|
|
@@ -87,7 +87,7 @@ export async function doctorCommand() {
|
|
|
87
87
|
|
|
88
88
|
// 1. Config check
|
|
89
89
|
spinner.text = 'Checking configuration...';
|
|
90
|
-
const config = await getConfig();
|
|
90
|
+
const config = await getConfig(options.profile);
|
|
91
91
|
if (config) {
|
|
92
92
|
const providerLabel = config.provider === 'git' ? 'git' : 'local';
|
|
93
93
|
const dest = config.provider === 'git' ? config.gitRepo : config.localPath;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import boxen from 'boxen';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import { execFileSync } from 'child_process';
|
|
5
|
+
import {
|
|
6
|
+
getRawConfig, listProfiles, getActiveProfileName,
|
|
7
|
+
createProfile, switchProfile, deleteProfile
|
|
8
|
+
} from '../config.js';
|
|
9
|
+
|
|
10
|
+
function getGitHubUsername() {
|
|
11
|
+
try {
|
|
12
|
+
return execFileSync('gh', ['api', 'user', '--jq', '.login'], { encoding: 'utf8' }).trim();
|
|
13
|
+
} catch {
|
|
14
|
+
try {
|
|
15
|
+
return execFileSync('git', ['config', '--global', 'user.name'], { encoding: 'utf8' }).trim();
|
|
16
|
+
} catch { return ''; }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function profileListCommand() {
|
|
21
|
+
const profiles = await listProfiles();
|
|
22
|
+
const active = await getActiveProfileName();
|
|
23
|
+
const raw = await getRawConfig();
|
|
24
|
+
|
|
25
|
+
if (profiles.length === 0) {
|
|
26
|
+
console.log('\n' + chalk.yellow('No profiles configured. Run ') + chalk.cyan('memoir init') + chalk.yellow(' first.\n'));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log();
|
|
31
|
+
console.log(chalk.bold.white(' Profiles:\n'));
|
|
32
|
+
|
|
33
|
+
for (const name of profiles) {
|
|
34
|
+
const isActive = name === active;
|
|
35
|
+
const marker = isActive ? chalk.green(' ✔ ') : chalk.gray(' ');
|
|
36
|
+
const label = isActive ? chalk.white.bold(name) : chalk.white(name);
|
|
37
|
+
|
|
38
|
+
// Get profile details
|
|
39
|
+
let detail = '';
|
|
40
|
+
if (raw.version >= 2 && raw.profiles?.[name]) {
|
|
41
|
+
const p = raw.profiles[name];
|
|
42
|
+
const dest = p.provider === 'git' ? p.gitRepo : p.localPath;
|
|
43
|
+
detail = chalk.gray(` → ${dest}`);
|
|
44
|
+
if (p.only) detail += chalk.gray(` (${p.only.join(', ')})`);
|
|
45
|
+
} else if (!raw.version) {
|
|
46
|
+
const dest = raw.provider === 'git' ? raw.gitRepo : raw.localPath;
|
|
47
|
+
detail = chalk.gray(` → ${dest}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(`${marker}${label}${detail}`);
|
|
51
|
+
}
|
|
52
|
+
console.log();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function profileCreateCommand(name) {
|
|
56
|
+
const profiles = await listProfiles();
|
|
57
|
+
if (profiles.includes(name)) {
|
|
58
|
+
console.log(chalk.red(`\n✖ Profile "${name}" already exists.\n`));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log('\n' + chalk.cyan(`Creating profile: ${chalk.bold(name)}\n`));
|
|
63
|
+
|
|
64
|
+
const detectedUser = getGitHubUsername();
|
|
65
|
+
|
|
66
|
+
const { provider } = await inquirer.prompt([{
|
|
67
|
+
type: 'list',
|
|
68
|
+
name: 'provider',
|
|
69
|
+
message: 'Storage for this profile?',
|
|
70
|
+
choices: [
|
|
71
|
+
{ name: 'GitHub', value: 'git' },
|
|
72
|
+
{ name: 'Local folder', value: 'local' }
|
|
73
|
+
]
|
|
74
|
+
}]);
|
|
75
|
+
|
|
76
|
+
const profileConfig = { provider };
|
|
77
|
+
|
|
78
|
+
if (provider === 'local') {
|
|
79
|
+
const { localPath } = await inquirer.prompt([{
|
|
80
|
+
type: 'input',
|
|
81
|
+
name: 'localPath',
|
|
82
|
+
message: 'Save to:',
|
|
83
|
+
validate: (input) => input.trim() ? true : 'Required'
|
|
84
|
+
}]);
|
|
85
|
+
profileConfig.localPath = localPath;
|
|
86
|
+
} else {
|
|
87
|
+
const answers = await inquirer.prompt([
|
|
88
|
+
{
|
|
89
|
+
type: 'input',
|
|
90
|
+
name: 'username',
|
|
91
|
+
message: 'GitHub username:',
|
|
92
|
+
default: detectedUser || undefined,
|
|
93
|
+
validate: (input) => input.trim() ? true : 'Required'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
type: 'input',
|
|
97
|
+
name: 'repo',
|
|
98
|
+
message: 'Repo name:',
|
|
99
|
+
default: `ai-memory-${name}`,
|
|
100
|
+
validate: (input) => input.trim() ? true : 'Required'
|
|
101
|
+
}
|
|
102
|
+
]);
|
|
103
|
+
const username = answers.username.trim();
|
|
104
|
+
const repo = answers.repo.trim();
|
|
105
|
+
profileConfig.gitRepo = `https://github.com/${username}/${repo}.git`;
|
|
106
|
+
|
|
107
|
+
// Auto-create repo if possible
|
|
108
|
+
try {
|
|
109
|
+
execFileSync('gh', ['repo', 'view', `${username}/${repo}`], { stdio: 'ignore' });
|
|
110
|
+
console.log(chalk.gray(` ✔ Repo exists`));
|
|
111
|
+
} catch {
|
|
112
|
+
try {
|
|
113
|
+
execFileSync('gh', ['repo', 'create', `${username}/${repo}`, '--private', '--description', `AI memory backup - ${name} (memoir-cli)`], { stdio: 'ignore' });
|
|
114
|
+
console.log(chalk.green(` ✔ Created private repo`));
|
|
115
|
+
} catch {
|
|
116
|
+
console.log(chalk.yellow(` ⚠ Could not auto-create repo. Create it manually on GitHub.`));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Ask which tools to sync (optional filter)
|
|
122
|
+
const { filterTools } = await inquirer.prompt([{
|
|
123
|
+
type: 'confirm',
|
|
124
|
+
name: 'filterTools',
|
|
125
|
+
message: 'Limit this profile to specific tools?',
|
|
126
|
+
default: false
|
|
127
|
+
}]);
|
|
128
|
+
|
|
129
|
+
if (filterTools) {
|
|
130
|
+
const { tools } = await inquirer.prompt([{
|
|
131
|
+
type: 'checkbox',
|
|
132
|
+
name: 'tools',
|
|
133
|
+
message: 'Which tools should this profile sync?',
|
|
134
|
+
choices: [
|
|
135
|
+
{ name: 'Claude Code', value: 'claude' },
|
|
136
|
+
{ name: 'Gemini CLI', value: 'gemini' },
|
|
137
|
+
{ name: 'OpenAI Codex', value: 'codex' },
|
|
138
|
+
{ name: 'Cursor', value: 'cursor' },
|
|
139
|
+
{ name: 'GitHub Copilot', value: 'copilot' },
|
|
140
|
+
{ name: 'Windsurf', value: 'windsurf' },
|
|
141
|
+
{ name: 'Zed', value: 'zed' },
|
|
142
|
+
{ name: 'Cline', value: 'cline' },
|
|
143
|
+
{ name: 'Continue.dev', value: 'continue' },
|
|
144
|
+
{ name: 'Aider', value: 'aider' }
|
|
145
|
+
]
|
|
146
|
+
}]);
|
|
147
|
+
if (tools.length > 0) {
|
|
148
|
+
profileConfig.only = tools;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
await createProfile(name, profileConfig);
|
|
153
|
+
|
|
154
|
+
// Ask if they want to switch to it
|
|
155
|
+
const { switchNow } = await inquirer.prompt([{
|
|
156
|
+
type: 'confirm',
|
|
157
|
+
name: 'switchNow',
|
|
158
|
+
message: `Switch to "${name}" now?`,
|
|
159
|
+
default: true
|
|
160
|
+
}]);
|
|
161
|
+
|
|
162
|
+
if (switchNow) {
|
|
163
|
+
await switchProfile(name);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log('\n' + boxen(
|
|
167
|
+
chalk.green(`✔ Profile "${name}" created`) +
|
|
168
|
+
(switchNow ? chalk.gray(` (now active)`) : ''),
|
|
169
|
+
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderStyle: 'round', borderColor: 'green', dimBorder: true }
|
|
170
|
+
) + '\n');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function profileSwitchCommand(name) {
|
|
174
|
+
try {
|
|
175
|
+
await switchProfile(name);
|
|
176
|
+
console.log('\n' + chalk.green(`✔ Switched to profile "${name}"\n`));
|
|
177
|
+
} catch (err) {
|
|
178
|
+
console.log('\n' + chalk.red(`✖ ${err.message}\n`));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function profileDeleteCommand(name) {
|
|
183
|
+
try {
|
|
184
|
+
const { confirm } = await inquirer.prompt([{
|
|
185
|
+
type: 'confirm',
|
|
186
|
+
name: 'confirm',
|
|
187
|
+
message: `Delete profile "${name}"? This cannot be undone.`,
|
|
188
|
+
default: false
|
|
189
|
+
}]);
|
|
190
|
+
if (!confirm) {
|
|
191
|
+
console.log(chalk.gray('\nCancelled.\n'));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
await deleteProfile(name);
|
|
195
|
+
console.log('\n' + chalk.green(`✔ Profile "${name}" deleted\n`));
|
|
196
|
+
} catch (err) {
|
|
197
|
+
console.log('\n' + chalk.red(`✖ ${err.message}\n`));
|
|
198
|
+
}
|
|
199
|
+
}
|
package/src/commands/push.js
CHANGED
|
@@ -10,7 +10,7 @@ import { extractMemories, adapters } from '../adapters/index.js';
|
|
|
10
10
|
import { syncToLocal, syncToGit } from '../providers/index.js';
|
|
11
11
|
|
|
12
12
|
export async function pushCommand(options = {}) {
|
|
13
|
-
const config = await getConfig();
|
|
13
|
+
const config = await getConfig(options.profile);
|
|
14
14
|
|
|
15
15
|
if (!config) {
|
|
16
16
|
console.log('\n' + boxen(
|
|
@@ -28,7 +28,9 @@ export async function pushCommand(options = {}) {
|
|
|
28
28
|
await fs.ensureDir(stagingDir);
|
|
29
29
|
|
|
30
30
|
try {
|
|
31
|
-
|
|
31
|
+
// Profile-level tool filter (config.only) merged with CLI --only flag
|
|
32
|
+
const onlyRaw = options.only || (config.only ? config.only.join(',') : null);
|
|
33
|
+
const onlyFilter = onlyRaw ? onlyRaw.split(',').map(t => t.trim().toLowerCase()) : null;
|
|
32
34
|
const foundAny = await extractMemories(stagingDir, spinner, onlyFilter);
|
|
33
35
|
|
|
34
36
|
if (!foundAny) {
|
package/src/commands/restore.js
CHANGED
|
@@ -9,7 +9,7 @@ import { getConfig } from '../config.js';
|
|
|
9
9
|
import { fetchFromLocal, fetchFromGit } from '../providers/restore.js';
|
|
10
10
|
|
|
11
11
|
export async function restoreCommand(options = {}) {
|
|
12
|
-
const config = await getConfig();
|
|
12
|
+
const config = await getConfig(options.profile);
|
|
13
13
|
|
|
14
14
|
if (!config) {
|
|
15
15
|
console.log('\n' + boxen(
|
package/src/commands/resume.js
CHANGED
package/src/commands/snapshot.js
CHANGED
|
@@ -264,7 +264,7 @@ Keep it under 300 words total. Be specific about file names and features.`;
|
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
export async function snapshotCommand(options = {}) {
|
|
267
|
-
const config = await getConfig();
|
|
267
|
+
const config = await getConfig(options.profile);
|
|
268
268
|
|
|
269
269
|
console.log();
|
|
270
270
|
const spinner = ora({ text: chalk.gray('Finding latest session...'), spinner: 'dots' }).start();
|
package/src/commands/status.js
CHANGED
|
@@ -6,8 +6,8 @@ import gradient from 'gradient-string';
|
|
|
6
6
|
import { getConfig } from '../config.js';
|
|
7
7
|
import { adapters } from '../adapters/index.js';
|
|
8
8
|
|
|
9
|
-
export async function statusCommand() {
|
|
10
|
-
const config = await getConfig();
|
|
9
|
+
export async function statusCommand(options = {}) {
|
|
10
|
+
const config = await getConfig(options.profile);
|
|
11
11
|
|
|
12
12
|
console.log();
|
|
13
13
|
|
package/src/commands/view.js
CHANGED
|
@@ -36,8 +36,8 @@ function isBinaryFile(filePath) {
|
|
|
36
36
|
return binaryExts.includes(path.extname(filePath).toLowerCase());
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export async function viewCommand() {
|
|
40
|
-
const config = await getConfig();
|
|
39
|
+
export async function viewCommand(options = {}) {
|
|
40
|
+
const config = await getConfig(options.profile);
|
|
41
41
|
if (!config) {
|
|
42
42
|
console.log(chalk.red('\n✖ Not configured yet. Run: memoir init\n'));
|
|
43
43
|
return;
|
package/src/config.js
CHANGED
|
@@ -7,7 +7,8 @@ const CONFIG_DIR = process.platform === 'win32'
|
|
|
7
7
|
: path.join(os.homedir(), '.config', 'memoir');
|
|
8
8
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// Read the raw config file as-is
|
|
11
|
+
export async function getRawConfig() {
|
|
11
12
|
if (await fs.pathExists(CONFIG_FILE)) {
|
|
12
13
|
try {
|
|
13
14
|
return await fs.readJson(CONFIG_FILE);
|
|
@@ -18,22 +19,108 @@ export async function getConfig() {
|
|
|
18
19
|
return null;
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
// Get resolved config for a specific profile (or active profile)
|
|
23
|
+
// Backwards compatible: v1 flat configs return as-is
|
|
24
|
+
export async function getConfig(profileName = null) {
|
|
25
|
+
const raw = await getRawConfig();
|
|
26
|
+
if (!raw) return null;
|
|
27
|
+
|
|
28
|
+
// v1 flat config — no profiles, return as-is
|
|
29
|
+
if (!raw.version || raw.version < 2) return raw;
|
|
30
|
+
|
|
31
|
+
// v2 — resolve profile
|
|
32
|
+
const name = profileName || raw.activeProfile || 'default';
|
|
33
|
+
const profile = raw.profiles?.[name];
|
|
34
|
+
if (!profile) return null;
|
|
35
|
+
|
|
36
|
+
// Merge top-level shared keys into profile
|
|
37
|
+
return { ...profile, geminiApiKey: raw.geminiApiKey };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Save entire raw config
|
|
21
41
|
export async function saveConfig(config) {
|
|
22
42
|
await fs.ensureDir(CONFIG_DIR);
|
|
23
43
|
await fs.writeJson(CONFIG_FILE, config, { spaces: 2 });
|
|
24
|
-
// Restrict permissions — config may contain API keys
|
|
25
44
|
if (process.platform !== 'win32') {
|
|
26
45
|
await fs.chmod(CONFIG_FILE, 0o600);
|
|
27
46
|
}
|
|
28
47
|
}
|
|
29
48
|
|
|
49
|
+
// Save config for a specific profile (creates v2 format if needed)
|
|
50
|
+
export async function saveProfileConfig(profileName, profileData) {
|
|
51
|
+
let raw = await getRawConfig() || {};
|
|
52
|
+
if (!raw.version || raw.version < 2) {
|
|
53
|
+
raw = migrateConfigToV2(raw);
|
|
54
|
+
}
|
|
55
|
+
raw.profiles[profileName] = profileData;
|
|
56
|
+
await saveConfig(raw);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Migrate v1 flat config to v2 profiles format
|
|
60
|
+
export function migrateConfigToV2(flat) {
|
|
61
|
+
const { provider, gitRepo, localPath, geminiApiKey, ...rest } = flat;
|
|
62
|
+
return {
|
|
63
|
+
version: 2,
|
|
64
|
+
activeProfile: 'default',
|
|
65
|
+
geminiApiKey: geminiApiKey || undefined,
|
|
66
|
+
profiles: {
|
|
67
|
+
default: { provider, gitRepo, localPath, ...rest }
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function getActiveProfileName() {
|
|
73
|
+
const raw = await getRawConfig();
|
|
74
|
+
if (!raw || !raw.version || raw.version < 2) return 'default';
|
|
75
|
+
return raw.activeProfile || 'default';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function listProfiles() {
|
|
79
|
+
const raw = await getRawConfig();
|
|
80
|
+
if (!raw) return [];
|
|
81
|
+
if (!raw.version || raw.version < 2) return ['default'];
|
|
82
|
+
return Object.keys(raw.profiles || {});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function createProfile(name, profileConfig) {
|
|
86
|
+
let raw = await getRawConfig() || {};
|
|
87
|
+
if (!raw.version || raw.version < 2) {
|
|
88
|
+
raw = migrateConfigToV2(raw);
|
|
89
|
+
}
|
|
90
|
+
raw.profiles[name] = profileConfig;
|
|
91
|
+
await saveConfig(raw);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function switchProfile(name) {
|
|
95
|
+
let raw = await getRawConfig();
|
|
96
|
+
if (!raw) throw new Error('Not configured. Run memoir init first.');
|
|
97
|
+
if (!raw.version || raw.version < 2) {
|
|
98
|
+
raw = migrateConfigToV2(raw);
|
|
99
|
+
}
|
|
100
|
+
if (!raw.profiles[name]) throw new Error(`Profile "${name}" does not exist.`);
|
|
101
|
+
raw.activeProfile = name;
|
|
102
|
+
await saveConfig(raw);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function deleteProfile(name) {
|
|
106
|
+
const raw = await getRawConfig();
|
|
107
|
+
if (!raw || !raw.version || raw.version < 2) {
|
|
108
|
+
throw new Error('No profiles configured.');
|
|
109
|
+
}
|
|
110
|
+
if (!raw.profiles[name]) throw new Error(`Profile "${name}" does not exist.`);
|
|
111
|
+
if (raw.activeProfile === name) throw new Error(`Cannot delete the active profile. Switch first with: memoir profile switch <name>`);
|
|
112
|
+
if (Object.keys(raw.profiles).length <= 1) throw new Error('Cannot delete the last profile.');
|
|
113
|
+
delete raw.profiles[name];
|
|
114
|
+
await saveConfig(raw);
|
|
115
|
+
}
|
|
116
|
+
|
|
30
117
|
export async function getGeminiApiKey() {
|
|
31
|
-
const
|
|
32
|
-
return
|
|
118
|
+
const raw = await getRawConfig();
|
|
119
|
+
return raw?.geminiApiKey || process.env.GEMINI_API_KEY || null;
|
|
33
120
|
}
|
|
34
121
|
|
|
35
122
|
export async function saveGeminiApiKey(apiKey) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
await saveConfig(
|
|
123
|
+
let raw = await getRawConfig() || {};
|
|
124
|
+
raw.geminiApiKey = apiKey;
|
|
125
|
+
await saveConfig(raw);
|
|
39
126
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
const cwd = process.cwd();
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
key: 'chatgpt',
|
|
8
|
+
name: 'ChatGPT',
|
|
9
|
+
icon: '💬',
|
|
10
|
+
format: 'Markdown custom instructions in CHATGPT.md. Written as instructions for ChatGPT — your preferences, coding style, response format, and project context. Paste into ChatGPT\'s Custom Instructions or Memory settings.',
|
|
11
|
+
|
|
12
|
+
discover() {
|
|
13
|
+
const files = [];
|
|
14
|
+
const projectFile = path.join(cwd, 'CHATGPT.md');
|
|
15
|
+
if (fs.existsSync(projectFile)) {
|
|
16
|
+
files.push({ filePath: projectFile, content: fs.readFileSync(projectFile, 'utf-8'), scope: 'project' });
|
|
17
|
+
}
|
|
18
|
+
return files;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
targetPath() {
|
|
22
|
+
return path.join(cwd, 'CHATGPT.md');
|
|
23
|
+
}
|
|
24
|
+
};
|
package/src/tools/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import claude from './claude.js';
|
|
2
2
|
import gemini from './gemini.js';
|
|
3
|
+
import chatgpt from './chatgpt.js';
|
|
3
4
|
import codex from './codex.js';
|
|
4
5
|
import cursor from './cursor.js';
|
|
5
6
|
import copilot from './copilot.js';
|
|
@@ -10,7 +11,7 @@ import continuedev from './continuedev.js';
|
|
|
10
11
|
import aider from './aider.js';
|
|
11
12
|
|
|
12
13
|
const registry = {};
|
|
13
|
-
for (const tool of [claude, gemini, codex, cursor, copilot, windsurf, zed, cline, continuedev, aider]) {
|
|
14
|
+
for (const tool of [claude, gemini, chatgpt, codex, cursor, copilot, windsurf, zed, cline, continuedev, aider]) {
|
|
14
15
|
registry[tool.key] = tool;
|
|
15
16
|
}
|
|
16
17
|
|