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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +553 -0
  3. package/cli.js +431 -0
  4. package/config-loader.js +294 -0
  5. package/hooks/activity-track.sh +56 -0
  6. package/hooks/codex-workstream.sh +44 -0
  7. package/hooks/gemini-workstream.sh +44 -0
  8. package/hooks/workstream-inject.sh +20 -0
  9. package/lib/activity.js +283 -0
  10. package/lib/apply.js +344 -0
  11. package/lib/cli.js +267 -0
  12. package/lib/config.js +171 -0
  13. package/lib/constants.js +55 -0
  14. package/lib/env.js +114 -0
  15. package/lib/index.js +47 -0
  16. package/lib/init.js +122 -0
  17. package/lib/mcps.js +139 -0
  18. package/lib/memory.js +201 -0
  19. package/lib/projects.js +138 -0
  20. package/lib/registry.js +83 -0
  21. package/lib/utils.js +129 -0
  22. package/lib/workstreams.js +652 -0
  23. package/package.json +80 -0
  24. package/scripts/capture-screenshots.js +142 -0
  25. package/scripts/postinstall.js +122 -0
  26. package/scripts/release.sh +71 -0
  27. package/scripts/sync-version.js +77 -0
  28. package/scripts/tauri-prepare.js +328 -0
  29. package/shared/mcp-registry.json +76 -0
  30. package/ui/dist/assets/index-DbZ3_HBD.js +3204 -0
  31. package/ui/dist/assets/index-DjLdm3Mr.css +32 -0
  32. package/ui/dist/icons/icon-192.svg +16 -0
  33. package/ui/dist/icons/icon-512.svg +16 -0
  34. package/ui/dist/index.html +39 -0
  35. package/ui/dist/manifest.json +25 -0
  36. package/ui/dist/sw.js +24 -0
  37. package/ui/dist/tutorial/claude-settings.png +0 -0
  38. package/ui/dist/tutorial/header.png +0 -0
  39. package/ui/dist/tutorial/mcp-registry.png +0 -0
  40. package/ui/dist/tutorial/memory-view.png +0 -0
  41. package/ui/dist/tutorial/permissions.png +0 -0
  42. package/ui/dist/tutorial/plugins-view.png +0 -0
  43. package/ui/dist/tutorial/project-explorer.png +0 -0
  44. package/ui/dist/tutorial/projects-view.png +0 -0
  45. package/ui/dist/tutorial/sidebar.png +0 -0
  46. package/ui/dist/tutorial/tutorial-view.png +0 -0
  47. package/ui/dist/tutorial/workstreams-view.png +0 -0
  48. package/ui/routes/activity.js +58 -0
  49. package/ui/routes/commands.js +74 -0
  50. package/ui/routes/configs.js +329 -0
  51. package/ui/routes/env.js +40 -0
  52. package/ui/routes/file-explorer.js +668 -0
  53. package/ui/routes/index.js +41 -0
  54. package/ui/routes/mcp-discovery.js +235 -0
  55. package/ui/routes/memory.js +385 -0
  56. package/ui/routes/package.json +3 -0
  57. package/ui/routes/plugins.js +466 -0
  58. package/ui/routes/projects.js +198 -0
  59. package/ui/routes/registry.js +30 -0
  60. package/ui/routes/rules.js +74 -0
  61. package/ui/routes/search.js +125 -0
  62. package/ui/routes/settings.js +381 -0
  63. package/ui/routes/subprojects.js +208 -0
  64. package/ui/routes/tool-sync.js +127 -0
  65. package/ui/routes/updates.js +339 -0
  66. package/ui/routes/workstreams.js +224 -0
  67. package/ui/server.cjs +773 -0
  68. 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
+ };
@@ -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
+ };