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
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configs Routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get all configs in hierarchy
|
|
10
|
+
*/
|
|
11
|
+
function getConfigs(manager, projectDir) {
|
|
12
|
+
const configs = manager.findAllConfigs(projectDir);
|
|
13
|
+
return configs.map(c => ({
|
|
14
|
+
dir: c.dir,
|
|
15
|
+
label: c.dir === process.env.HOME ? '~' : path.relative(projectDir, c.dir) || '.',
|
|
16
|
+
config: manager.loadJson(c.configPath) || { include: [], mcpServers: {} }
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Update a config
|
|
22
|
+
*/
|
|
23
|
+
function updateConfig(body, manager, applyConfig) {
|
|
24
|
+
const { dir, config } = body;
|
|
25
|
+
const configPath = path.join(dir, '.claude', 'mcps.json');
|
|
26
|
+
|
|
27
|
+
// Ensure directory exists
|
|
28
|
+
const claudeDir = path.join(dir, '.claude');
|
|
29
|
+
if (!fs.existsSync(claudeDir)) {
|
|
30
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
manager.saveJson(configPath, config);
|
|
34
|
+
|
|
35
|
+
// Auto-apply: Generate .mcp.json immediately after save
|
|
36
|
+
const applyResult = applyConfig(dir);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
success: true,
|
|
40
|
+
path: configPath,
|
|
41
|
+
applied: applyResult.success,
|
|
42
|
+
tools: applyResult.tools
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Apply config to generate .mcp.json
|
|
48
|
+
*/
|
|
49
|
+
function applyConfig(dir, projectDir, uiConfig, manager) {
|
|
50
|
+
const targetDir = dir || projectDir;
|
|
51
|
+
const enabledTools = uiConfig.enabledTools || ['claude'];
|
|
52
|
+
|
|
53
|
+
// Use multi-tool apply
|
|
54
|
+
const results = manager.applyForTools(targetDir, enabledTools);
|
|
55
|
+
|
|
56
|
+
// Build response with details for each tool
|
|
57
|
+
const toolResults = {};
|
|
58
|
+
let anySuccess = false;
|
|
59
|
+
|
|
60
|
+
for (const [tool, success] of Object.entries(results)) {
|
|
61
|
+
toolResults[tool] = success;
|
|
62
|
+
if (success) anySuccess = true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
success: anySuccess,
|
|
67
|
+
tools: toolResults,
|
|
68
|
+
enabledTools
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Detect template for a directory
|
|
74
|
+
*/
|
|
75
|
+
function detectTemplate(dir, manager, getTemplates) {
|
|
76
|
+
const resolvedDir = path.resolve(dir.replace(/^~/, require('os').homedir()));
|
|
77
|
+
if (!fs.existsSync(resolvedDir)) {
|
|
78
|
+
return { detected: false, error: 'Directory not found' };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Detect project markers
|
|
82
|
+
const markers = {
|
|
83
|
+
npm: fs.existsSync(path.join(resolvedDir, 'package.json')),
|
|
84
|
+
python: fs.existsSync(path.join(resolvedDir, 'pyproject.toml')) ||
|
|
85
|
+
fs.existsSync(path.join(resolvedDir, 'requirements.txt')) ||
|
|
86
|
+
fs.existsSync(path.join(resolvedDir, 'setup.py')),
|
|
87
|
+
rust: fs.existsSync(path.join(resolvedDir, 'Cargo.toml')),
|
|
88
|
+
go: fs.existsSync(path.join(resolvedDir, 'go.mod')),
|
|
89
|
+
ruby: fs.existsSync(path.join(resolvedDir, 'Gemfile')),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
let framework = null;
|
|
93
|
+
let confidence = 'low';
|
|
94
|
+
|
|
95
|
+
if (markers.npm) {
|
|
96
|
+
try {
|
|
97
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(resolvedDir, 'package.json'), 'utf8'));
|
|
98
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
99
|
+
|
|
100
|
+
if (deps.react) {
|
|
101
|
+
framework = deps.typescript ? 'react-ts' : 'react-js';
|
|
102
|
+
confidence = 'high';
|
|
103
|
+
} else if (deps.next) {
|
|
104
|
+
framework = deps.typescript ? 'react-ts' : 'react-js';
|
|
105
|
+
confidence = 'high';
|
|
106
|
+
} else if (deps.vue) {
|
|
107
|
+
framework = 'languages/javascript';
|
|
108
|
+
confidence = 'medium';
|
|
109
|
+
} else if (deps.express || deps.fastify || deps.koa) {
|
|
110
|
+
framework = deps.typescript ? 'languages/typescript' : 'languages/javascript';
|
|
111
|
+
confidence = 'medium';
|
|
112
|
+
} else if (deps.typescript) {
|
|
113
|
+
framework = 'languages/typescript';
|
|
114
|
+
confidence = 'medium';
|
|
115
|
+
} else {
|
|
116
|
+
framework = 'languages/javascript';
|
|
117
|
+
confidence = 'low';
|
|
118
|
+
}
|
|
119
|
+
} catch (e) {
|
|
120
|
+
framework = 'languages/javascript';
|
|
121
|
+
confidence = 'low';
|
|
122
|
+
}
|
|
123
|
+
} else if (markers.python) {
|
|
124
|
+
const hasRequirements = fs.existsSync(path.join(resolvedDir, 'requirements.txt'));
|
|
125
|
+
const hasPyproject = fs.existsSync(path.join(resolvedDir, 'pyproject.toml'));
|
|
126
|
+
|
|
127
|
+
if (hasRequirements) {
|
|
128
|
+
try {
|
|
129
|
+
const reqs = fs.readFileSync(path.join(resolvedDir, 'requirements.txt'), 'utf8').toLowerCase();
|
|
130
|
+
if (reqs.includes('fastapi')) {
|
|
131
|
+
framework = 'fastapi';
|
|
132
|
+
confidence = 'high';
|
|
133
|
+
} else if (reqs.includes('django')) {
|
|
134
|
+
framework = 'languages/python';
|
|
135
|
+
confidence = 'medium';
|
|
136
|
+
} else if (reqs.includes('flask')) {
|
|
137
|
+
framework = 'languages/python';
|
|
138
|
+
confidence = 'medium';
|
|
139
|
+
} else if (reqs.includes('mcp')) {
|
|
140
|
+
framework = 'mcp-python';
|
|
141
|
+
confidence = 'high';
|
|
142
|
+
}
|
|
143
|
+
} catch (e) {}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!framework && hasPyproject) {
|
|
147
|
+
try {
|
|
148
|
+
const pyproject = fs.readFileSync(path.join(resolvedDir, 'pyproject.toml'), 'utf8').toLowerCase();
|
|
149
|
+
if (pyproject.includes('fastapi')) {
|
|
150
|
+
framework = 'fastapi';
|
|
151
|
+
confidence = 'high';
|
|
152
|
+
} else if (pyproject.includes('mcp')) {
|
|
153
|
+
framework = 'mcp-python';
|
|
154
|
+
confidence = 'high';
|
|
155
|
+
}
|
|
156
|
+
} catch (e) {}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!framework) {
|
|
160
|
+
const hasMain = fs.existsSync(path.join(resolvedDir, '__main__.py')) ||
|
|
161
|
+
fs.existsSync(path.join(resolvedDir, 'cli.py')) ||
|
|
162
|
+
fs.existsSync(path.join(resolvedDir, 'main.py'));
|
|
163
|
+
if (hasMain) {
|
|
164
|
+
framework = 'python-cli';
|
|
165
|
+
confidence = 'medium';
|
|
166
|
+
} else {
|
|
167
|
+
framework = 'languages/python';
|
|
168
|
+
confidence = 'low';
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} else if (markers.rust) {
|
|
172
|
+
framework = 'languages/rust';
|
|
173
|
+
confidence = 'medium';
|
|
174
|
+
} else if (markers.go) {
|
|
175
|
+
framework = 'languages/go';
|
|
176
|
+
confidence = 'medium';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!framework) {
|
|
180
|
+
return { detected: false, reason: 'No recognizable project markers found' };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Find matching template
|
|
184
|
+
const templates = getTemplates();
|
|
185
|
+
let matchedTemplate = null;
|
|
186
|
+
|
|
187
|
+
matchedTemplate = templates.find(t =>
|
|
188
|
+
t.fullName === `frameworks/${framework}` || t.name === framework
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
if (!matchedTemplate && framework.startsWith('languages/')) {
|
|
192
|
+
matchedTemplate = templates.find(t => t.fullName === framework);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!matchedTemplate && framework.startsWith('languages/')) {
|
|
196
|
+
matchedTemplate = templates.find(t =>
|
|
197
|
+
t.includes && t.includes.includes(framework) && t.category === 'frameworks'
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!matchedTemplate) {
|
|
202
|
+
return {
|
|
203
|
+
detected: false,
|
|
204
|
+
reason: `No template found for detected framework: ${framework}`,
|
|
205
|
+
suggestedFramework: framework,
|
|
206
|
+
markers
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
detected: true,
|
|
212
|
+
template: matchedTemplate,
|
|
213
|
+
confidence,
|
|
214
|
+
reason: `Detected ${framework} project`,
|
|
215
|
+
markers
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Apply template to multiple projects (batch)
|
|
221
|
+
*/
|
|
222
|
+
function applyTemplateBatch(templateId, dirs, getTemplates, applyTemplateToDir) {
|
|
223
|
+
if (!templateId) {
|
|
224
|
+
return { error: 'templateId is required' };
|
|
225
|
+
}
|
|
226
|
+
if (!dirs || !Array.isArray(dirs) || dirs.length === 0) {
|
|
227
|
+
return { error: 'dirs array is required' };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const templates = getTemplates();
|
|
231
|
+
const template = templates.find(t => t.id === templateId);
|
|
232
|
+
if (!template) {
|
|
233
|
+
return { error: 'Template not found', templateId };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const results = [];
|
|
237
|
+
let successCount = 0;
|
|
238
|
+
|
|
239
|
+
for (const dir of dirs) {
|
|
240
|
+
const absDir = path.resolve(dir.replace(/^~/, require('os').homedir()));
|
|
241
|
+
const claudeDir = path.join(absDir, '.claude');
|
|
242
|
+
|
|
243
|
+
if (!fs.existsSync(claudeDir)) {
|
|
244
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
const result = applyTemplateToDir(template, absDir);
|
|
249
|
+
results.push({ dir, success: true, ...result });
|
|
250
|
+
successCount++;
|
|
251
|
+
} catch (error) {
|
|
252
|
+
results.push({ dir, success: false, error: error.message });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
success: true,
|
|
258
|
+
count: successCount,
|
|
259
|
+
results
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Apply a template to a single directory
|
|
265
|
+
*/
|
|
266
|
+
function applyTemplateToDir(template, absDir) {
|
|
267
|
+
const claudeDir = path.join(absDir, '.claude');
|
|
268
|
+
|
|
269
|
+
// Apply MCPs from template
|
|
270
|
+
if (template.mcps && template.mcps.length > 0) {
|
|
271
|
+
const mcpsPath = path.join(claudeDir, 'mcps.json');
|
|
272
|
+
let currentConfig = { include: [], mcpServers: {} };
|
|
273
|
+
|
|
274
|
+
if (fs.existsSync(mcpsPath)) {
|
|
275
|
+
try {
|
|
276
|
+
currentConfig = JSON.parse(fs.readFileSync(mcpsPath, 'utf-8'));
|
|
277
|
+
} catch (e) {}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const include = new Set(currentConfig.include || []);
|
|
281
|
+
for (const mcpName of template.mcps) {
|
|
282
|
+
include.add(mcpName);
|
|
283
|
+
}
|
|
284
|
+
currentConfig.include = Array.from(include);
|
|
285
|
+
|
|
286
|
+
fs.writeFileSync(mcpsPath, JSON.stringify(currentConfig, null, 2));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Apply rules from template
|
|
290
|
+
if (template.rules && template.rules.length > 0) {
|
|
291
|
+
const rulesDir = path.join(claudeDir, 'rules');
|
|
292
|
+
if (!fs.existsSync(rulesDir)) {
|
|
293
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
for (const rule of template.rules) {
|
|
297
|
+
const rulePath = path.join(rulesDir, rule.name);
|
|
298
|
+
if (!fs.existsSync(rulePath)) {
|
|
299
|
+
fs.writeFileSync(rulePath, rule.content);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Apply commands from template
|
|
305
|
+
if (template.commands && template.commands.length > 0) {
|
|
306
|
+
const commandsDir = path.join(claudeDir, 'commands');
|
|
307
|
+
if (!fs.existsSync(commandsDir)) {
|
|
308
|
+
fs.mkdirSync(commandsDir, { recursive: true });
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
for (const command of template.commands) {
|
|
312
|
+
const commandPath = path.join(commandsDir, command.name);
|
|
313
|
+
if (!fs.existsSync(commandPath)) {
|
|
314
|
+
fs.writeFileSync(commandPath, command.content);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return { applied: true };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
module.exports = {
|
|
323
|
+
getConfigs,
|
|
324
|
+
updateConfig,
|
|
325
|
+
applyConfig,
|
|
326
|
+
detectTemplate,
|
|
327
|
+
applyTemplateBatch,
|
|
328
|
+
applyTemplateToDir,
|
|
329
|
+
};
|
package/ui/routes/env.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Env Routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get env file for a directory
|
|
10
|
+
*/
|
|
11
|
+
function getEnv(dir, projectDir) {
|
|
12
|
+
const targetDir = dir || projectDir;
|
|
13
|
+
const envPath = path.join(targetDir, '.claude', '.env');
|
|
14
|
+
|
|
15
|
+
if (fs.existsSync(envPath)) {
|
|
16
|
+
return { content: fs.readFileSync(envPath, 'utf8'), path: envPath };
|
|
17
|
+
}
|
|
18
|
+
return { content: '', path: envPath };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Save env file
|
|
23
|
+
*/
|
|
24
|
+
function saveEnv(body) {
|
|
25
|
+
const { dir, content } = body;
|
|
26
|
+
const envPath = path.join(dir, '.claude', '.env');
|
|
27
|
+
const claudeDir = path.dirname(envPath);
|
|
28
|
+
|
|
29
|
+
if (!fs.existsSync(claudeDir)) {
|
|
30
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fs.writeFileSync(envPath, content);
|
|
34
|
+
return { success: true };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = {
|
|
38
|
+
getEnv,
|
|
39
|
+
saveEnv,
|
|
40
|
+
};
|