coder-config 0.40.1
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/LICENSE +21 -0
- package/README.md +553 -0
- package/cli.js +431 -0
- package/config-loader.js +294 -0
- package/hooks/activity-track.sh +56 -0
- package/hooks/codex-workstream.sh +44 -0
- package/hooks/gemini-workstream.sh +44 -0
- package/hooks/workstream-inject.sh +20 -0
- package/lib/activity.js +283 -0
- package/lib/apply.js +344 -0
- package/lib/cli.js +267 -0
- package/lib/config.js +171 -0
- package/lib/constants.js +55 -0
- package/lib/env.js +114 -0
- package/lib/index.js +47 -0
- package/lib/init.js +122 -0
- package/lib/mcps.js +139 -0
- package/lib/memory.js +201 -0
- package/lib/projects.js +138 -0
- package/lib/registry.js +83 -0
- package/lib/utils.js +129 -0
- package/lib/workstreams.js +652 -0
- package/package.json +80 -0
- package/scripts/capture-screenshots.js +142 -0
- package/scripts/postinstall.js +122 -0
- package/scripts/release.sh +71 -0
- package/scripts/sync-version.js +77 -0
- package/scripts/tauri-prepare.js +328 -0
- package/shared/mcp-registry.json +76 -0
- package/ui/dist/assets/index-DbZ3_HBD.js +3204 -0
- package/ui/dist/assets/index-DjLdm3Mr.css +32 -0
- package/ui/dist/icons/icon-192.svg +16 -0
- package/ui/dist/icons/icon-512.svg +16 -0
- package/ui/dist/index.html +39 -0
- package/ui/dist/manifest.json +25 -0
- package/ui/dist/sw.js +24 -0
- package/ui/dist/tutorial/claude-settings.png +0 -0
- package/ui/dist/tutorial/header.png +0 -0
- package/ui/dist/tutorial/mcp-registry.png +0 -0
- package/ui/dist/tutorial/memory-view.png +0 -0
- package/ui/dist/tutorial/permissions.png +0 -0
- package/ui/dist/tutorial/plugins-view.png +0 -0
- package/ui/dist/tutorial/project-explorer.png +0 -0
- package/ui/dist/tutorial/projects-view.png +0 -0
- package/ui/dist/tutorial/sidebar.png +0 -0
- package/ui/dist/tutorial/tutorial-view.png +0 -0
- package/ui/dist/tutorial/workstreams-view.png +0 -0
- package/ui/routes/activity.js +58 -0
- package/ui/routes/commands.js +74 -0
- package/ui/routes/configs.js +329 -0
- package/ui/routes/env.js +40 -0
- package/ui/routes/file-explorer.js +668 -0
- package/ui/routes/index.js +41 -0
- package/ui/routes/mcp-discovery.js +235 -0
- package/ui/routes/memory.js +385 -0
- package/ui/routes/package.json +3 -0
- package/ui/routes/plugins.js +466 -0
- package/ui/routes/projects.js +198 -0
- package/ui/routes/registry.js +30 -0
- package/ui/routes/rules.js +74 -0
- package/ui/routes/search.js +125 -0
- package/ui/routes/settings.js +381 -0
- package/ui/routes/subprojects.js +208 -0
- package/ui/routes/tool-sync.js +127 -0
- package/ui/routes/updates.js +339 -0
- package/ui/routes/workstreams.js +224 -0
- package/ui/server.cjs +773 -0
- package/ui/terminal-server.cjs +160 -0
package/lib/init.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project initialization commands
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { loadJson, saveJson } = require('./utils');
|
|
8
|
+
const { findProjectRoot, findAllConfigs, mergeConfigs, collectFilesFromHierarchy } = require('./config');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Initialize project
|
|
12
|
+
*/
|
|
13
|
+
function init(registryPath, projectDir = null) {
|
|
14
|
+
const dir = projectDir || process.cwd();
|
|
15
|
+
const claudeDir = path.join(dir, '.claude');
|
|
16
|
+
const configPath = path.join(claudeDir, 'mcps.json');
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(claudeDir)) {
|
|
19
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(configPath)) {
|
|
23
|
+
const config = {
|
|
24
|
+
"include": ['github', 'filesystem'],
|
|
25
|
+
"mcpServers": {}
|
|
26
|
+
};
|
|
27
|
+
saveJson(configPath, config);
|
|
28
|
+
console.log(`ā Created ${configPath}`);
|
|
29
|
+
} else {
|
|
30
|
+
console.log(`ā ${configPath} already exists`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const envPath = path.join(claudeDir, '.env');
|
|
34
|
+
if (!fs.existsSync(envPath)) {
|
|
35
|
+
fs.writeFileSync(envPath, `# Project secrets (gitignored)
|
|
36
|
+
# GITHUB_TOKEN=ghp_xxx
|
|
37
|
+
# DATABASE_URL=postgres://...
|
|
38
|
+
`);
|
|
39
|
+
console.log(`ā Created ${envPath}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const gitignorePath = path.join(dir, '.gitignore');
|
|
43
|
+
if (fs.existsSync(gitignorePath)) {
|
|
44
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
45
|
+
if (!content.includes('.claude/.env')) {
|
|
46
|
+
fs.appendFileSync(gitignorePath, '\n.claude/.env\n');
|
|
47
|
+
console.log('ā Updated .gitignore');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log('\nā
Project initialized!');
|
|
52
|
+
console.log('Next steps:');
|
|
53
|
+
console.log(' 1. Edit .claude/mcps.json to customize MCPs');
|
|
54
|
+
console.log(' 2. Run: claude-config ui');
|
|
55
|
+
console.log(' (Plugins marketplace auto-installs on first use)');
|
|
56
|
+
console.log(' 3. Run: claude-config apply\n');
|
|
57
|
+
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Show current project config (including hierarchy)
|
|
63
|
+
*/
|
|
64
|
+
function show(projectDir = null) {
|
|
65
|
+
const dir = projectDir || findProjectRoot() || process.cwd();
|
|
66
|
+
|
|
67
|
+
const configLocations = findAllConfigs(dir);
|
|
68
|
+
|
|
69
|
+
if (configLocations.length === 0) {
|
|
70
|
+
console.log('No .claude/mcps.json found in current directory or parents');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(`\nš Project: ${dir}`);
|
|
75
|
+
|
|
76
|
+
if (configLocations.length > 1) {
|
|
77
|
+
console.log('\nš Config Hierarchy (root ā leaf):');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const { dir: d, configPath } of configLocations) {
|
|
81
|
+
const config = loadJson(configPath);
|
|
82
|
+
const relPath = d === process.env.HOME ? '~' : path.relative(process.cwd(), d) || '.';
|
|
83
|
+
|
|
84
|
+
console.log(`\nš ${relPath}/.claude/mcps.json:`);
|
|
85
|
+
console.log(JSON.stringify(config, null, 2));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (configLocations.length > 1) {
|
|
89
|
+
const loadedConfigs = configLocations.map(loc => ({
|
|
90
|
+
...loc,
|
|
91
|
+
config: loadJson(loc.configPath)
|
|
92
|
+
}));
|
|
93
|
+
const merged = mergeConfigs(loadedConfigs);
|
|
94
|
+
console.log('\nš Merged Config (effective):');
|
|
95
|
+
console.log(JSON.stringify(merged, null, 2));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const allRules = collectFilesFromHierarchy(configLocations, 'rules');
|
|
99
|
+
const allCommands = collectFilesFromHierarchy(configLocations, 'commands');
|
|
100
|
+
|
|
101
|
+
if (allRules.length) {
|
|
102
|
+
console.log(`\nš Rules (${allRules.length} total):`);
|
|
103
|
+
for (const { file, source } of allRules) {
|
|
104
|
+
const sourceLabel = source === process.env.HOME ? '~' : path.relative(process.cwd(), source) || '.';
|
|
105
|
+
console.log(` ⢠${file} (${sourceLabel})`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (allCommands.length) {
|
|
110
|
+
console.log(`\nā” Commands (${allCommands.length} total):`);
|
|
111
|
+
for (const { file, source } of allCommands) {
|
|
112
|
+
const sourceLabel = source === process.env.HOME ? '~' : path.relative(process.cwd(), source) || '.';
|
|
113
|
+
console.log(` ⢠${file} (${sourceLabel})`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
console.log('');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = {
|
|
120
|
+
init,
|
|
121
|
+
show,
|
|
122
|
+
};
|
package/lib/mcps.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP add/remove commands
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { loadJson, saveJson } = require('./utils');
|
|
7
|
+
const { findProjectRoot } = require('./config');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* List available MCPs
|
|
11
|
+
*/
|
|
12
|
+
function list(registryPath) {
|
|
13
|
+
const registry = loadJson(registryPath);
|
|
14
|
+
if (!registry || !registry.mcpServers) {
|
|
15
|
+
console.error('Error: Could not load MCP registry');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const dir = findProjectRoot();
|
|
20
|
+
const projectConfig = dir ? loadJson(path.join(dir, '.claude', 'mcps.json')) : null;
|
|
21
|
+
const included = projectConfig?.include || [];
|
|
22
|
+
|
|
23
|
+
console.log('\nš Available MCPs:\n');
|
|
24
|
+
for (const name of Object.keys(registry.mcpServers)) {
|
|
25
|
+
const active = included.includes(name) ? ' ā' : '';
|
|
26
|
+
console.log(` ⢠${name}${active}`);
|
|
27
|
+
}
|
|
28
|
+
console.log(`\n Total: ${Object.keys(registry.mcpServers).length} in registry`);
|
|
29
|
+
if (included.length) {
|
|
30
|
+
console.log(` Active: ${included.join(', ')}`);
|
|
31
|
+
}
|
|
32
|
+
console.log('');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Add MCP(s) to current project
|
|
37
|
+
*/
|
|
38
|
+
function add(registryPath, installDir, mcpNames) {
|
|
39
|
+
if (!mcpNames || mcpNames.length === 0) {
|
|
40
|
+
console.error('Usage: claude-config add <mcp-name> [mcp-name...]');
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const dir = findProjectRoot() || process.cwd();
|
|
45
|
+
const configPath = path.join(dir, '.claude', 'mcps.json');
|
|
46
|
+
let config = loadJson(configPath);
|
|
47
|
+
|
|
48
|
+
if (!config) {
|
|
49
|
+
console.error('No .claude/mcps.json found. Run: claude-config init');
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const registry = loadJson(registryPath);
|
|
54
|
+
if (!config.include) config.include = [];
|
|
55
|
+
|
|
56
|
+
const added = [];
|
|
57
|
+
const notFound = [];
|
|
58
|
+
const alreadyExists = [];
|
|
59
|
+
|
|
60
|
+
for (const name of mcpNames) {
|
|
61
|
+
if (config.include.includes(name)) {
|
|
62
|
+
alreadyExists.push(name);
|
|
63
|
+
} else if (registry?.mcpServers?.[name]) {
|
|
64
|
+
config.include.push(name);
|
|
65
|
+
added.push(name);
|
|
66
|
+
} else {
|
|
67
|
+
notFound.push(name);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (added.length) {
|
|
72
|
+
saveJson(configPath, config);
|
|
73
|
+
console.log(`ā Added: ${added.join(', ')}`);
|
|
74
|
+
}
|
|
75
|
+
if (alreadyExists.length) {
|
|
76
|
+
console.log(`Already included: ${alreadyExists.join(', ')}`);
|
|
77
|
+
}
|
|
78
|
+
if (notFound.length) {
|
|
79
|
+
console.log(`Not in registry: ${notFound.join(', ')}`);
|
|
80
|
+
console.log(' (Use "claude-config list" to see available MCPs)');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (added.length) {
|
|
84
|
+
console.log('\nRun "claude-config apply" to regenerate .mcp.json');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return added.length > 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Remove MCP(s) from current project
|
|
92
|
+
*/
|
|
93
|
+
function remove(installDir, mcpNames) {
|
|
94
|
+
if (!mcpNames || mcpNames.length === 0) {
|
|
95
|
+
console.error('Usage: claude-config remove <mcp-name> [mcp-name...]');
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const dir = findProjectRoot() || process.cwd();
|
|
100
|
+
const configPath = path.join(dir, '.claude', 'mcps.json');
|
|
101
|
+
let config = loadJson(configPath);
|
|
102
|
+
|
|
103
|
+
if (!config) {
|
|
104
|
+
console.error('No .claude/mcps.json found');
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!config.include) config.include = [];
|
|
109
|
+
|
|
110
|
+
const removed = [];
|
|
111
|
+
const notFound = [];
|
|
112
|
+
|
|
113
|
+
for (const name of mcpNames) {
|
|
114
|
+
const idx = config.include.indexOf(name);
|
|
115
|
+
if (idx !== -1) {
|
|
116
|
+
config.include.splice(idx, 1);
|
|
117
|
+
removed.push(name);
|
|
118
|
+
} else {
|
|
119
|
+
notFound.push(name);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (removed.length) {
|
|
124
|
+
saveJson(configPath, config);
|
|
125
|
+
console.log(`ā Removed: ${removed.join(', ')}`);
|
|
126
|
+
console.log('\nRun "claude-config apply" to regenerate .mcp.json');
|
|
127
|
+
}
|
|
128
|
+
if (notFound.length) {
|
|
129
|
+
console.log(`Not in project: ${notFound.join(', ')}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return removed.length > 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = {
|
|
136
|
+
list,
|
|
137
|
+
add,
|
|
138
|
+
remove,
|
|
139
|
+
};
|
package/lib/memory.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory system commands
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Show memory status and contents
|
|
10
|
+
*/
|
|
11
|
+
function memoryList(projectDir = process.cwd()) {
|
|
12
|
+
const homeDir = process.env.HOME || '';
|
|
13
|
+
const globalMemoryDir = path.join(homeDir, '.claude', 'memory');
|
|
14
|
+
const projectMemoryDir = path.join(projectDir, '.claude', 'memory');
|
|
15
|
+
|
|
16
|
+
console.log('\nš Memory System\n');
|
|
17
|
+
|
|
18
|
+
console.log('Global (~/.claude/memory/):');
|
|
19
|
+
if (fs.existsSync(globalMemoryDir)) {
|
|
20
|
+
const files = ['preferences.md', 'corrections.md', 'facts.md'];
|
|
21
|
+
for (const file of files) {
|
|
22
|
+
const filePath = path.join(globalMemoryDir, file);
|
|
23
|
+
if (fs.existsSync(filePath)) {
|
|
24
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
25
|
+
const lines = content.split('\n').filter(l => l.trim() && !l.startsWith('#')).length;
|
|
26
|
+
console.log(` ā ${file} (${lines} entries)`);
|
|
27
|
+
} else {
|
|
28
|
+
console.log(` ā ${file} (not created)`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
console.log(' Not initialized');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(`\nProject (${projectDir}/.claude/memory/):`);
|
|
36
|
+
if (fs.existsSync(projectMemoryDir)) {
|
|
37
|
+
const files = ['context.md', 'patterns.md', 'decisions.md', 'issues.md', 'history.md'];
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
const filePath = path.join(projectMemoryDir, file);
|
|
40
|
+
if (fs.existsSync(filePath)) {
|
|
41
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
42
|
+
const lines = content.split('\n').filter(l => l.trim() && !l.startsWith('#')).length;
|
|
43
|
+
console.log(` ā ${file} (${lines} entries)`);
|
|
44
|
+
} else {
|
|
45
|
+
console.log(` ā ${file} (not created)`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
console.log(' Not initialized. Run: claude-config memory init');
|
|
50
|
+
}
|
|
51
|
+
console.log();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Initialize project memory
|
|
56
|
+
*/
|
|
57
|
+
function memoryInit(projectDir = process.cwd()) {
|
|
58
|
+
const memoryDir = path.join(projectDir, '.claude', 'memory');
|
|
59
|
+
|
|
60
|
+
if (fs.existsSync(memoryDir)) {
|
|
61
|
+
console.log('Project memory already initialized at', memoryDir);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fs.mkdirSync(memoryDir, { recursive: true });
|
|
66
|
+
|
|
67
|
+
const files = {
|
|
68
|
+
'context.md': '# Project Context\n\n<!-- Project overview and key information -->\n',
|
|
69
|
+
'patterns.md': '# Code Patterns\n\n<!-- Established patterns in this codebase -->\n',
|
|
70
|
+
'decisions.md': '# Architecture Decisions\n\n<!-- Key decisions and their rationale -->\n',
|
|
71
|
+
'issues.md': '# Known Issues\n\n<!-- Current issues and workarounds -->\n',
|
|
72
|
+
'history.md': '# Session History\n\n<!-- Notable changes and milestones -->\n'
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
for (const [file, content] of Object.entries(files)) {
|
|
76
|
+
fs.writeFileSync(path.join(memoryDir, file), content);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log(`ā Initialized project memory at ${memoryDir}`);
|
|
80
|
+
console.log('\nCreated:');
|
|
81
|
+
for (const file of Object.keys(files)) {
|
|
82
|
+
console.log(` ${file}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Add entry to memory
|
|
88
|
+
*/
|
|
89
|
+
function memoryAdd(type, content, projectDir = process.cwd()) {
|
|
90
|
+
if (!type || !content) {
|
|
91
|
+
console.error('Usage: claude-config memory add <type> "<content>"');
|
|
92
|
+
console.log('\nTypes:');
|
|
93
|
+
console.log(' Global: preference, correction, fact');
|
|
94
|
+
console.log(' Project: context, pattern, decision, issue, history');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const homeDir = process.env.HOME || '';
|
|
99
|
+
const timestamp = new Date().toISOString().split('T')[0];
|
|
100
|
+
|
|
101
|
+
const typeMap = {
|
|
102
|
+
preference: { dir: path.join(homeDir, '.claude', 'memory'), file: 'preferences.md' },
|
|
103
|
+
correction: { dir: path.join(homeDir, '.claude', 'memory'), file: 'corrections.md' },
|
|
104
|
+
fact: { dir: path.join(homeDir, '.claude', 'memory'), file: 'facts.md' },
|
|
105
|
+
context: { dir: path.join(projectDir, '.claude', 'memory'), file: 'context.md' },
|
|
106
|
+
pattern: { dir: path.join(projectDir, '.claude', 'memory'), file: 'patterns.md' },
|
|
107
|
+
decision: { dir: path.join(projectDir, '.claude', 'memory'), file: 'decisions.md' },
|
|
108
|
+
issue: { dir: path.join(projectDir, '.claude', 'memory'), file: 'issues.md' },
|
|
109
|
+
history: { dir: path.join(projectDir, '.claude', 'memory'), file: 'history.md' }
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const target = typeMap[type];
|
|
113
|
+
if (!target) {
|
|
114
|
+
console.error(`Unknown type: ${type}`);
|
|
115
|
+
console.log('Valid types: preference, correction, fact, context, pattern, decision, issue, history');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!fs.existsSync(target.dir)) {
|
|
120
|
+
fs.mkdirSync(target.dir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const filePath = path.join(target.dir, target.file);
|
|
124
|
+
|
|
125
|
+
if (!fs.existsSync(filePath)) {
|
|
126
|
+
const headers = {
|
|
127
|
+
'preferences.md': '# Preferences\n',
|
|
128
|
+
'corrections.md': '# Corrections\n',
|
|
129
|
+
'facts.md': '# Facts\n',
|
|
130
|
+
'context.md': '# Project Context\n',
|
|
131
|
+
'patterns.md': '# Code Patterns\n',
|
|
132
|
+
'decisions.md': '# Architecture Decisions\n',
|
|
133
|
+
'issues.md': '# Known Issues\n',
|
|
134
|
+
'history.md': '# Session History\n'
|
|
135
|
+
};
|
|
136
|
+
fs.writeFileSync(filePath, headers[target.file] || '');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const entry = `\n- [${timestamp}] ${content}\n`;
|
|
140
|
+
fs.appendFileSync(filePath, entry);
|
|
141
|
+
|
|
142
|
+
console.log(`ā Added ${type} to ${target.file}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Search memory files
|
|
147
|
+
*/
|
|
148
|
+
function memorySearch(query, projectDir = process.cwd()) {
|
|
149
|
+
if (!query) {
|
|
150
|
+
console.error('Usage: claude-config memory search <query>');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const homeDir = process.env.HOME || '';
|
|
155
|
+
const searchDirs = [
|
|
156
|
+
{ label: 'Global', dir: path.join(homeDir, '.claude', 'memory') },
|
|
157
|
+
{ label: 'Project', dir: path.join(projectDir, '.claude', 'memory') }
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
const results = [];
|
|
161
|
+
const queryLower = query.toLowerCase();
|
|
162
|
+
|
|
163
|
+
for (const { label, dir } of searchDirs) {
|
|
164
|
+
if (!fs.existsSync(dir)) continue;
|
|
165
|
+
|
|
166
|
+
for (const file of fs.readdirSync(dir)) {
|
|
167
|
+
if (!file.endsWith('.md')) continue;
|
|
168
|
+
const filePath = path.join(dir, file);
|
|
169
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
170
|
+
const lines = content.split('\n');
|
|
171
|
+
|
|
172
|
+
for (let i = 0; i < lines.length; i++) {
|
|
173
|
+
if (lines[i].toLowerCase().includes(queryLower)) {
|
|
174
|
+
results.push({
|
|
175
|
+
location: `${label}/${file}`,
|
|
176
|
+
line: i + 1,
|
|
177
|
+
content: lines[i].trim()
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (results.length === 0) {
|
|
185
|
+
console.log(`No matches found for "${query}"`);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log(`\nš Found ${results.length} match(es) for "${query}":\n`);
|
|
190
|
+
for (const r of results) {
|
|
191
|
+
console.log(` ${r.location}:${r.line}`);
|
|
192
|
+
console.log(` ${r.content}\n`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = {
|
|
197
|
+
memoryList,
|
|
198
|
+
memoryInit,
|
|
199
|
+
memoryAdd,
|
|
200
|
+
memorySearch,
|
|
201
|
+
};
|
package/lib/projects.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project registry for UI project switching
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { loadJson, saveJson } = require('./utils');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get projects registry path
|
|
11
|
+
*/
|
|
12
|
+
function getProjectsRegistryPath(installDir) {
|
|
13
|
+
return path.join(installDir, 'projects.json');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Load projects registry
|
|
18
|
+
*/
|
|
19
|
+
function loadProjectsRegistry(installDir) {
|
|
20
|
+
const registryPath = getProjectsRegistryPath(installDir);
|
|
21
|
+
if (fs.existsSync(registryPath)) {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
24
|
+
} catch (e) {
|
|
25
|
+
return { projects: [], activeProjectId: null };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { projects: [], activeProjectId: null };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Save projects registry
|
|
33
|
+
*/
|
|
34
|
+
function saveProjectsRegistry(installDir, registry) {
|
|
35
|
+
const registryPath = getProjectsRegistryPath(installDir);
|
|
36
|
+
fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + '\n');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* List registered projects
|
|
41
|
+
*/
|
|
42
|
+
function projectList(installDir) {
|
|
43
|
+
const registry = loadProjectsRegistry(installDir);
|
|
44
|
+
|
|
45
|
+
if (registry.projects.length === 0) {
|
|
46
|
+
console.log('\nNo projects registered.');
|
|
47
|
+
console.log('Add one with: claude-config project add [path]\n');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log('\nš Registered Projects:\n');
|
|
52
|
+
for (const p of registry.projects) {
|
|
53
|
+
const active = p.id === registry.activeProjectId ? 'ā ' : ' ';
|
|
54
|
+
const exists = fs.existsSync(p.path) ? '' : ' (not found)';
|
|
55
|
+
console.log(`${active}${p.name}${exists}`);
|
|
56
|
+
console.log(` ${p.path}`);
|
|
57
|
+
}
|
|
58
|
+
console.log('');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Add project to registry
|
|
63
|
+
*/
|
|
64
|
+
function projectAdd(installDir, projectPath = process.cwd(), name = null) {
|
|
65
|
+
const absPath = path.resolve(projectPath.replace(/^~/, process.env.HOME || ''));
|
|
66
|
+
|
|
67
|
+
if (!fs.existsSync(absPath)) {
|
|
68
|
+
console.error(`Path not found: ${absPath}`);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const registry = loadProjectsRegistry(installDir);
|
|
73
|
+
|
|
74
|
+
if (registry.projects.some(p => p.path === absPath)) {
|
|
75
|
+
console.log(`Already registered: ${absPath}`);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const project = {
|
|
80
|
+
id: Date.now().toString(36) + Math.random().toString(36).substr(2, 5),
|
|
81
|
+
name: name || path.basename(absPath),
|
|
82
|
+
path: absPath,
|
|
83
|
+
addedAt: new Date().toISOString(),
|
|
84
|
+
lastOpened: null
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
registry.projects.push(project);
|
|
88
|
+
|
|
89
|
+
if (!registry.activeProjectId) {
|
|
90
|
+
registry.activeProjectId = project.id;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
saveProjectsRegistry(installDir, registry);
|
|
94
|
+
console.log(`ā Added project: ${project.name}`);
|
|
95
|
+
console.log(` ${absPath}`);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Remove project from registry
|
|
101
|
+
*/
|
|
102
|
+
function projectRemove(installDir, nameOrPath) {
|
|
103
|
+
if (!nameOrPath) {
|
|
104
|
+
console.error('Usage: claude-config project remove <name|path>');
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const registry = loadProjectsRegistry(installDir);
|
|
109
|
+
const absPath = path.resolve(nameOrPath.replace(/^~/, process.env.HOME || ''));
|
|
110
|
+
|
|
111
|
+
const idx = registry.projects.findIndex(
|
|
112
|
+
p => p.name === nameOrPath || p.path === absPath
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (idx === -1) {
|
|
116
|
+
console.error(`Project not found: ${nameOrPath}`);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const removed = registry.projects.splice(idx, 1)[0];
|
|
121
|
+
|
|
122
|
+
if (registry.activeProjectId === removed.id) {
|
|
123
|
+
registry.activeProjectId = registry.projects[0]?.id || null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
saveProjectsRegistry(installDir, registry);
|
|
127
|
+
console.log(`ā Removed project: ${removed.name}`);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = {
|
|
132
|
+
getProjectsRegistryPath,
|
|
133
|
+
loadProjectsRegistry,
|
|
134
|
+
saveProjectsRegistry,
|
|
135
|
+
projectList,
|
|
136
|
+
projectAdd,
|
|
137
|
+
projectRemove,
|
|
138
|
+
};
|
package/lib/registry.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Registry management commands
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { loadJson, saveJson } = require('./utils');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* List MCPs in global registry
|
|
9
|
+
*/
|
|
10
|
+
function registryList(registryPath) {
|
|
11
|
+
const registry = loadJson(registryPath);
|
|
12
|
+
const mcps = registry?.mcpServers || {};
|
|
13
|
+
const names = Object.keys(mcps);
|
|
14
|
+
|
|
15
|
+
if (names.length === 0) {
|
|
16
|
+
console.log('\nNo MCPs in global registry.');
|
|
17
|
+
console.log('Add one with: claude-config registry add <name> \'{"command":"..."}\'');
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log('\nš¦ Global MCP Registry:\n');
|
|
22
|
+
for (const name of names.sort()) {
|
|
23
|
+
const mcp = mcps[name];
|
|
24
|
+
const cmd = mcp.command || 'unknown';
|
|
25
|
+
console.log(` ${name}`);
|
|
26
|
+
console.log(` command: ${cmd}`);
|
|
27
|
+
}
|
|
28
|
+
console.log('');
|
|
29
|
+
return names;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Add MCP to global registry
|
|
34
|
+
*/
|
|
35
|
+
function registryAdd(registryPath, name, configJson) {
|
|
36
|
+
if (!name || !configJson) {
|
|
37
|
+
console.error('Usage: claude-config registry add <name> \'{"command":"...","args":[...]}\'');
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let mcpConfig;
|
|
42
|
+
try {
|
|
43
|
+
mcpConfig = JSON.parse(configJson);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error('Invalid JSON:', e.message);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const registry = loadJson(registryPath) || { mcpServers: {} };
|
|
50
|
+
registry.mcpServers[name] = mcpConfig;
|
|
51
|
+
saveJson(registryPath, registry);
|
|
52
|
+
|
|
53
|
+
console.log(`ā Added "${name}" to registry`);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Remove MCP from global registry
|
|
59
|
+
*/
|
|
60
|
+
function registryRemove(registryPath, name) {
|
|
61
|
+
if (!name) {
|
|
62
|
+
console.error('Usage: claude-config registry remove <name>');
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const registry = loadJson(registryPath);
|
|
67
|
+
if (!registry?.mcpServers?.[name]) {
|
|
68
|
+
console.error(`"${name}" not found in registry`);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
delete registry.mcpServers[name];
|
|
73
|
+
saveJson(registryPath, registry);
|
|
74
|
+
|
|
75
|
+
console.log(`ā Removed "${name}" from registry`);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
registryList,
|
|
81
|
+
registryAdd,
|
|
82
|
+
registryRemove,
|
|
83
|
+
};
|