memoir-cli 2.1.1 → 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 +137 -12
- package/package.json +7 -3
- package/src/adapters/index.js +75 -1
- package/src/commands/diff.js +2 -2
- package/src/commands/doctor.js +223 -0
- package/src/commands/init.js +19 -1
- 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 +19 -11
- package/src/commands/view.js +2 -2
- package/src/config.js +94 -7
- package/src/tools/chatgpt.js +24 -0
- package/src/tools/cline.js +72 -0
- package/src/tools/continuedev.js +55 -0
- package/src/tools/index.js +5 -1
- package/src/tools/zed.js +50 -0
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
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
const home = os.homedir();
|
|
6
|
+
const cwd = process.cwd();
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
key: 'cline',
|
|
10
|
+
name: 'Cline',
|
|
11
|
+
icon: '🤖',
|
|
12
|
+
format: 'Configuration and rules for Cline AI coding assistant. Includes settings for AI behavior and custom rules files for project-specific instructions.',
|
|
13
|
+
|
|
14
|
+
discover() {
|
|
15
|
+
const files = [];
|
|
16
|
+
const clineDir = process.platform === 'win32'
|
|
17
|
+
? path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev')
|
|
18
|
+
: path.join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev');
|
|
19
|
+
|
|
20
|
+
// Check for .clinerules in project
|
|
21
|
+
const projectFile = path.join(cwd, '.clinerules');
|
|
22
|
+
if (fs.existsSync(projectFile)) {
|
|
23
|
+
files.push({ filePath: projectFile, content: fs.readFileSync(projectFile, 'utf-8'), scope: 'project' });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Discover settings and rules from Cline extension storage
|
|
27
|
+
if (fs.existsSync(clineDir)) {
|
|
28
|
+
const scanDir = (dir, scope) => {
|
|
29
|
+
try {
|
|
30
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
const filePath = path.join(dir, entry.name);
|
|
33
|
+
if (entry.isFile()) {
|
|
34
|
+
files.push({ filePath, content: fs.readFileSync(filePath, 'utf-8'), scope });
|
|
35
|
+
} else if (entry.isDirectory()) {
|
|
36
|
+
scanDir(filePath, scope);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch {}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const settingsDir = path.join(clineDir, 'settings');
|
|
43
|
+
if (fs.existsSync(settingsDir)) {
|
|
44
|
+
scanDir(settingsDir, 'user');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const rulesDir = path.join(clineDir, 'rules');
|
|
48
|
+
if (fs.existsSync(rulesDir)) {
|
|
49
|
+
scanDir(rulesDir, 'user');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Discover .md files in root
|
|
53
|
+
try {
|
|
54
|
+
const entries = fs.readdirSync(clineDir);
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
if (entry.endsWith('.md')) {
|
|
57
|
+
const filePath = path.join(clineDir, entry);
|
|
58
|
+
if (fs.statSync(filePath).isFile()) {
|
|
59
|
+
files.push({ filePath, content: fs.readFileSync(filePath, 'utf-8'), scope: 'user' });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch {}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return files;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
targetPath() {
|
|
70
|
+
return path.join(cwd, '.clinerules');
|
|
71
|
+
}
|
|
72
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
const home = os.homedir();
|
|
6
|
+
const cwd = process.cwd();
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
key: 'continuedev',
|
|
10
|
+
name: 'Continue.dev',
|
|
11
|
+
icon: '🔄',
|
|
12
|
+
format: 'JSON, TypeScript, or YAML configuration for Continue.dev AI assistant. Includes config files for model selection, context providers, and slash commands. Supports .continuerules for project-specific instructions.',
|
|
13
|
+
|
|
14
|
+
discover() {
|
|
15
|
+
const files = [];
|
|
16
|
+
const continueDir = process.platform === 'win32'
|
|
17
|
+
? path.join(process.env.USERPROFILE || home, '.continue')
|
|
18
|
+
: path.join(home, '.continue');
|
|
19
|
+
|
|
20
|
+
// Check for .continuerules in project
|
|
21
|
+
const projectFile = path.join(cwd, '.continuerules');
|
|
22
|
+
if (fs.existsSync(projectFile)) {
|
|
23
|
+
files.push({ filePath: projectFile, content: fs.readFileSync(projectFile, 'utf-8'), scope: 'project' });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (fs.existsSync(continueDir)) {
|
|
27
|
+
const configFiles = ['config.json', 'config.ts', 'config.yaml', '.continuerules'];
|
|
28
|
+
for (const file of configFiles) {
|
|
29
|
+
const filePath = path.join(continueDir, file);
|
|
30
|
+
if (fs.existsSync(filePath)) {
|
|
31
|
+
files.push({ filePath, content: fs.readFileSync(filePath, 'utf-8'), scope: 'user' });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Discover .md files in root
|
|
36
|
+
try {
|
|
37
|
+
const entries = fs.readdirSync(continueDir);
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
if (entry.endsWith('.md')) {
|
|
40
|
+
const filePath = path.join(continueDir, entry);
|
|
41
|
+
if (fs.statSync(filePath).isFile()) {
|
|
42
|
+
files.push({ filePath, content: fs.readFileSync(filePath, 'utf-8'), scope: 'user' });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return files;
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
targetPath() {
|
|
53
|
+
return path.join(cwd, '.continuerules');
|
|
54
|
+
}
|
|
55
|
+
};
|
package/src/tools/index.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
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';
|
|
6
7
|
import windsurf from './windsurf.js';
|
|
8
|
+
import zed from './zed.js';
|
|
9
|
+
import cline from './cline.js';
|
|
10
|
+
import continuedev from './continuedev.js';
|
|
7
11
|
import aider from './aider.js';
|
|
8
12
|
|
|
9
13
|
const registry = {};
|
|
10
|
-
for (const tool of [claude, gemini, codex, cursor, copilot, windsurf, aider]) {
|
|
14
|
+
for (const tool of [claude, gemini, chatgpt, codex, cursor, copilot, windsurf, zed, cline, continuedev, aider]) {
|
|
11
15
|
registry[tool.key] = tool;
|
|
12
16
|
}
|
|
13
17
|
|
package/src/tools/zed.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
const home = os.homedir();
|
|
6
|
+
const cwd = process.cwd();
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
key: 'zed',
|
|
10
|
+
name: 'Zed',
|
|
11
|
+
icon: '🔶',
|
|
12
|
+
format: 'JSON or markdown configuration files for Zed editor. Includes settings.json for editor preferences, keymap.json for keybindings, and tasks.json for task runner configs.',
|
|
13
|
+
|
|
14
|
+
discover() {
|
|
15
|
+
const files = [];
|
|
16
|
+
const zedDir = process.platform === 'win32'
|
|
17
|
+
? path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'Zed')
|
|
18
|
+
: path.join(home, '.config', 'zed');
|
|
19
|
+
|
|
20
|
+
if (fs.existsSync(zedDir)) {
|
|
21
|
+
const configFiles = ['settings.json', 'keymap.json', 'tasks.json'];
|
|
22
|
+
for (const file of configFiles) {
|
|
23
|
+
const filePath = path.join(zedDir, file);
|
|
24
|
+
if (fs.existsSync(filePath)) {
|
|
25
|
+
files.push({ filePath, content: fs.readFileSync(filePath, 'utf-8'), scope: 'user' });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Also discover .md files in root
|
|
29
|
+
try {
|
|
30
|
+
const entries = fs.readdirSync(zedDir);
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
if (entry.endsWith('.md')) {
|
|
33
|
+
const filePath = path.join(zedDir, entry);
|
|
34
|
+
if (fs.statSync(filePath).isFile()) {
|
|
35
|
+
files.push({ filePath, content: fs.readFileSync(filePath, 'utf-8'), scope: 'user' });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch {}
|
|
40
|
+
}
|
|
41
|
+
return files;
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
targetPath() {
|
|
45
|
+
const zedDir = process.platform === 'win32'
|
|
46
|
+
? path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'Zed')
|
|
47
|
+
: path.join(home, '.config', 'zed');
|
|
48
|
+
return path.join(zedDir, 'settings.json');
|
|
49
|
+
}
|
|
50
|
+
};
|