coder-config 0.45.13 → 0.46.0-beta.10

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/config-loader.js CHANGED
@@ -22,13 +22,13 @@ const { VERSION, TOOL_PATHS } = require('./lib/constants');
22
22
  const { loadJson, saveJson, loadEnvFile, interpolate, resolveEnvVars, copyDirRecursive } = require('./lib/utils');
23
23
  const { findProjectRoot, findAllConfigs, mergeConfigs, getConfigPath, collectFilesFromHierarchy, findAllConfigsForTool } = require('./lib/config');
24
24
  const { apply, applyForAntigravity, applyForGemini, detectInstalledTools, applyForTools } = require('./lib/apply');
25
- const { list, add, remove } = require('./lib/mcps');
25
+ const { list, add, remove, listGlobal, addGlobal, removeGlobal } = require('./lib/mcps');
26
26
  const { registryList, registryAdd, registryRemove } = require('./lib/registry');
27
27
  const { init, show } = require('./lib/init');
28
28
  const { memoryList, memoryInit, memoryAdd, memorySearch } = require('./lib/memory');
29
29
  const { envList, envSet, envUnset } = require('./lib/env');
30
30
  const { getProjectsRegistryPath, loadProjectsRegistry, saveProjectsRegistry, projectList, projectAdd, projectRemove } = require('./lib/projects');
31
- const { getWorkstreamsPath, loadWorkstreams, saveWorkstreams, workstreamList, workstreamCreate, workstreamUpdate, workstreamDelete, workstreamUse, workstreamActive, workstreamAddProject, workstreamRemoveProject, workstreamInject, workstreamDetect, workstreamGet, getActiveWorkstream, countWorkstreamsForProject, workstreamInstallHook, workstreamInstallHookGemini, workstreamInstallHookCodex, workstreamDeactivate, workstreamCheckPath, getSettingsPath, loadSettings, saveSettings, workstreamAddTrigger, workstreamRemoveTrigger, workstreamSetAutoActivate, setGlobalAutoActivate, shouldAutoActivate, workstreamCheckFolder, workstreamInstallCdHook, workstreamUninstallCdHook, workstreamCdHookStatus, generateRulesFromRepos, generateRulesWithClaude } = require('./lib/workstreams');
31
+ const { getWorkstreamsPath, loadWorkstreams, saveWorkstreams, workstreamList, workstreamCreate, workstreamUpdate, workstreamDelete, workstreamUse, workstreamActive, workstreamAddProject, workstreamRemoveProject, workstreamInject, workstreamDetect, workstreamGet, getActiveWorkstream, countWorkstreamsForProject, workstreamInstallHook, workstreamInstallHookGemini, workstreamInstallHookCodex, workstreamDeactivate, workstreamCheckPath, getSettingsPath, loadSettings, saveSettings, workstreamAddTrigger, workstreamRemoveTrigger, workstreamSetAutoActivate, setGlobalAutoActivate, shouldAutoActivate, workstreamCheckFolder, workstreamInstallCdHook, workstreamUninstallCdHook, workstreamCdHookStatus, discoverSubProjects, generateRulesFromRepos, generateRulesWithClaude, generateRulesWithAI, getAvailableAITools, findAIBinary, AI_TOOLS } = require('./lib/workstreams');
32
32
  const { getActivityPath, getDefaultActivity, loadActivity, saveActivity, detectProjectRoot, activityLog, activitySummary, generateWorkstreamName, activitySuggestWorkstreams, activityClear } = require('./lib/activity');
33
33
  const { getLoopsPath, loadLoops, saveLoops, loadLoopState, saveLoopState, loadHistory, saveHistory, loopList, loopCreate, loopGet, loopUpdate, loopDelete, loopStart, loopPause, loopResume, loopCancel, loopApprove, loopComplete, loopFail, loopStatus, loopHistory, loopConfig, getActiveLoop, recordIteration, saveClarifications, savePlan, loadClarifications, loadPlan, loopInject, archiveLoop } = require('./lib/loops');
34
34
  const { getSessionStatus, showSessionStatus, flushContext, clearContext, installHooks: sessionInstallHooks, getFlushedContext, installFlushCommand, installAll: sessionInstallAll, SESSION_DIR, FLUSHED_CONTEXT_FILE } = require('./lib/sessions');
@@ -138,11 +138,16 @@ class ClaudeConfigManager {
138
138
  getToolPaths() { return TOOL_PATHS; }
139
139
  applyForTools(projectDir, tools) { return applyForTools(this.registryPath, projectDir, tools); }
140
140
 
141
- // MCPs
141
+ // MCPs (project)
142
142
  list() { return list(this.registryPath); }
143
143
  add(mcpNames) { return add(this.registryPath, this.installDir, mcpNames); }
144
144
  remove(mcpNames) { return remove(this.installDir, mcpNames); }
145
145
 
146
+ // MCPs (global - ~/.claude.json)
147
+ globalList() { return listGlobal(); }
148
+ globalAdd(mcpNames) { return addGlobal(this.registryPath, mcpNames); }
149
+ globalRemove(mcpNames) { return removeGlobal(mcpNames); }
150
+
146
151
  // Registry
147
152
  registryList() { return registryList(this.registryPath); }
148
153
  registryAdd(name, configJson) { return registryAdd(this.registryPath, name, configJson); }
@@ -208,6 +213,11 @@ class ClaudeConfigManager {
208
213
  workstreamCdHookStatus() { return workstreamCdHookStatus(); }
209
214
  generateRulesFromRepos(projects) { return generateRulesFromRepos(projects); }
210
215
  generateRulesWithClaude(projects) { return generateRulesWithClaude(projects); }
216
+ generateRulesWithAI(projects, toolId, options) { return generateRulesWithAI(projects, toolId, options); }
217
+ getAvailableAITools() { return getAvailableAITools(); }
218
+ findAIBinary(toolId) { return findAIBinary(toolId); }
219
+ get AI_TOOLS() { return AI_TOOLS; }
220
+ discoverSubProjects(rootPath, maxDepth) { return discoverSubProjects(rootPath, maxDepth); }
211
221
 
212
222
  // Loops (Ralph Loop)
213
223
  getLoopsPath() { return getLoopsPath(this.installDir); }
package/lib/apply.js CHANGED
@@ -29,16 +29,37 @@ function apply(registryPath, projectDir = null) {
29
29
  return false;
30
30
  }
31
31
 
32
- const loadedConfigs = configLocations.map(loc => ({
33
- ...loc,
34
- config: loadJson(loc.configPath)
35
- }));
32
+ const loadedConfigs = configLocations.map(loc => {
33
+ const rawConfig = loadJson(loc.configPath);
34
+
35
+ // Handle ~/.claude.json format (global MCPs under mcpServers key)
36
+ if (loc.isGlobalClaudeJson && rawConfig) {
37
+ return {
38
+ ...loc,
39
+ config: {
40
+ include: [],
41
+ mcpServers: rawConfig.mcpServers || {}
42
+ }
43
+ };
44
+ }
36
45
 
37
- if (loadedConfigs.length > 1) {
46
+ return {
47
+ ...loc,
48
+ config: rawConfig
49
+ };
50
+ });
51
+
52
+ // Filter out empty configs and show hierarchy
53
+ const activeConfigs = loadedConfigs.filter(c => c.config);
54
+ if (activeConfigs.length > 1) {
38
55
  console.log('📚 Config hierarchy (merged):');
39
- for (const { dir: d, configPath } of loadedConfigs) {
56
+ for (const { dir: d, configPath, isGlobalClaudeJson } of activeConfigs) {
40
57
  const relPath = d === process.env.HOME ? '~' : path.relative(process.cwd(), d) || '.';
41
- console.log(` • ${relPath}/.claude/mcps.json`);
58
+ if (isGlobalClaudeJson) {
59
+ console.log(` • ~/.claude.json (global MCPs)`);
60
+ } else {
61
+ console.log(` • ${relPath}/.claude/mcps.json`);
62
+ }
42
63
  }
43
64
  console.log('');
44
65
  }
package/lib/cli.js CHANGED
@@ -63,6 +63,17 @@ function runCli(manager) {
63
63
  manager.remove(args.slice(1));
64
64
  break;
65
65
 
66
+ // Global MCPs (~/.claude.json)
67
+ case 'global':
68
+ if (args[1] === 'add') {
69
+ manager.globalAdd(args.slice(2));
70
+ } else if (args[1] === 'remove' || args[1] === 'rm') {
71
+ manager.globalRemove(args.slice(2));
72
+ } else {
73
+ manager.globalList();
74
+ }
75
+ break;
76
+
66
77
  // Registry management
67
78
  case 'registry':
68
79
  if (args[1] === 'add') {
@@ -371,6 +382,14 @@ ${chalk.dim('Configuration manager for AI coding tools (Claude Code, Gemini CLI,
371
382
  ]));
372
383
  console.log();
373
384
 
385
+ // Global MCPs (~/.claude.json)
386
+ console.log(box('Global MCPs', [
387
+ cmd('global', 'List global MCPs (~/.claude.json)'),
388
+ cmd('global add <mcp> [mcp...]', 'Add MCP(s) to global config'),
389
+ cmd('global remove <mcp>', 'Remove MCP from global config'),
390
+ ]));
391
+ console.log();
392
+
374
393
  // Memory Commands
375
394
  console.log(box('Memory', [
376
395
  cmd('memory', 'Show memory status'),
package/lib/config.js CHANGED
@@ -23,8 +23,11 @@ function findProjectRoot(startDir = process.cwd()) {
23
23
  }
24
24
 
25
25
  /**
26
- * Find ALL .claude/mcps.json configs from cwd up to root (and ~/.claude)
26
+ * Find ALL .claude/mcps.json configs from cwd up to root (and ~/.claude.json for global)
27
27
  * Returns array from root to leaf (so child overrides parent when merged)
28
+ *
29
+ * Global MCPs: Read from ~/.claude.json under mcpServers key (Claude Code's native format)
30
+ * Project MCPs: Read from .claude/mcps.json files in hierarchy
28
31
  */
29
32
  function findAllConfigs(startDir = process.cwd()) {
30
33
  const configs = [];
@@ -32,6 +35,7 @@ function findAllConfigs(startDir = process.cwd()) {
32
35
  const root = path.parse(dir).root;
33
36
  const homeDir = process.env.HOME || '';
34
37
 
38
+ // Find project configs in hierarchy
35
39
  while (dir !== root) {
36
40
  const configPath = path.join(dir, '.claude', 'mcps.json');
37
41
  if (fs.existsSync(configPath)) {
@@ -40,10 +44,21 @@ function findAllConfigs(startDir = process.cwd()) {
40
44
  dir = path.dirname(dir);
41
45
  }
42
46
 
43
- const homeConfig = path.join(homeDir, '.claude', 'mcps.json');
44
- if (fs.existsSync(homeConfig)) {
45
- if (!configs.some(c => c.configPath === homeConfig)) {
46
- configs.unshift({ dir: homeDir, configPath: homeConfig });
47
+ // Read global MCPs from ~/.claude.json (Claude Code's native config)
48
+ const claudeJsonPath = path.join(homeDir, '.claude.json');
49
+ if (fs.existsSync(claudeJsonPath)) {
50
+ configs.unshift({
51
+ dir: homeDir,
52
+ configPath: claudeJsonPath,
53
+ isGlobalClaudeJson: true // Flag to handle different format
54
+ });
55
+ }
56
+
57
+ // Legacy: Also check ~/.claude/mcps.json for backwards compatibility
58
+ const legacyHomeConfig = path.join(homeDir, '.claude', 'mcps.json');
59
+ if (fs.existsSync(legacyHomeConfig)) {
60
+ if (!configs.some(c => c.configPath === legacyHomeConfig)) {
61
+ configs.unshift({ dir: homeDir, configPath: legacyHomeConfig, isLegacy: true });
47
62
  }
48
63
  }
49
64
 
package/lib/constants.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Constants and tool path configurations
3
3
  */
4
4
 
5
- const VERSION = '0.45.13';
5
+ const VERSION = '0.46.0-beta.10';
6
6
 
7
7
  // Tool-specific path configurations
8
8
  const TOOL_PATHS = {
@@ -10,14 +10,16 @@ const TOOL_PATHS = {
10
10
  name: 'Claude Code',
11
11
  icon: 'sparkles',
12
12
  color: 'orange',
13
- globalConfig: '~/.claude/mcps.json',
13
+ // Global MCPs are embedded in ~/.claude.json under mcpServers key (no separate file)
14
+ globalConfig: '~/.claude.json',
14
15
  globalSettings: '~/.claude/settings.json',
16
+ globalMcpKey: 'mcpServers', // MCPs are under this key in globalConfig
15
17
  projectFolder: '.claude',
16
18
  projectRules: '.claude/rules',
17
19
  projectCommands: '.claude/commands',
18
20
  projectWorkflows: '.claude/workflows',
19
21
  projectInstructions: 'CLAUDE.md',
20
- outputFile: '.mcp.json',
22
+ outputFile: '.mcp.json', // Per-project MCPs go here (shared via git)
21
23
  supportsEnvInterpolation: true,
22
24
  },
23
25
  gemini: {
package/lib/mcps.js CHANGED
@@ -2,10 +2,19 @@
2
2
  * MCP add/remove commands
3
3
  */
4
4
 
5
+ const fs = require('fs');
5
6
  const path = require('path');
6
7
  const { loadJson, saveJson } = require('./utils');
7
8
  const { findProjectRoot } = require('./config');
8
9
 
10
+ /**
11
+ * Get path to Claude's global config (~/.claude.json)
12
+ */
13
+ function getGlobalConfigPath() {
14
+ const homeDir = process.env.HOME || '';
15
+ return path.join(homeDir, '.claude.json');
16
+ }
17
+
9
18
  /**
10
19
  * List available MCPs
11
20
  */
@@ -132,8 +141,122 @@ function remove(installDir, mcpNames) {
132
141
  return removed.length > 0;
133
142
  }
134
143
 
144
+ /**
145
+ * List global MCPs from ~/.claude.json
146
+ */
147
+ function listGlobal() {
148
+ const configPath = getGlobalConfigPath();
149
+ const config = loadJson(configPath);
150
+
151
+ if (!config || !config.mcpServers) {
152
+ console.log('\n📚 Global MCPs (~/.claude.json): None configured\n');
153
+ return;
154
+ }
155
+
156
+ const mcps = Object.keys(config.mcpServers);
157
+ console.log('\n📚 Global MCPs (~/.claude.json):\n');
158
+ for (const name of mcps) {
159
+ const server = config.mcpServers[name];
160
+ const type = server.type || 'stdio';
161
+ console.log(` • ${name} (${type})`);
162
+ }
163
+ console.log(`\n Total: ${mcps.length} global MCP(s)\n`);
164
+ }
165
+
166
+ /**
167
+ * Add MCP(s) to global config (~/.claude.json)
168
+ */
169
+ function addGlobal(registryPath, mcpNames) {
170
+ if (!mcpNames || mcpNames.length === 0) {
171
+ console.error('Usage: coder-config global add <mcp-name> [mcp-name...]');
172
+ return false;
173
+ }
174
+
175
+ const configPath = getGlobalConfigPath();
176
+ let config = loadJson(configPath) || {};
177
+
178
+ if (!config.mcpServers) {
179
+ config.mcpServers = {};
180
+ }
181
+
182
+ const registry = loadJson(registryPath);
183
+ const added = [];
184
+ const notFound = [];
185
+ const alreadyExists = [];
186
+
187
+ for (const name of mcpNames) {
188
+ if (config.mcpServers[name]) {
189
+ alreadyExists.push(name);
190
+ } else if (registry?.mcpServers?.[name]) {
191
+ config.mcpServers[name] = registry.mcpServers[name];
192
+ added.push(name);
193
+ } else {
194
+ notFound.push(name);
195
+ }
196
+ }
197
+
198
+ if (added.length) {
199
+ saveJson(configPath, config);
200
+ console.log(`✓ Added to ~/.claude.json: ${added.join(', ')}`);
201
+ console.log(' (Global MCPs apply to all projects)');
202
+ }
203
+ if (alreadyExists.length) {
204
+ console.log(`Already in global config: ${alreadyExists.join(', ')}`);
205
+ }
206
+ if (notFound.length) {
207
+ console.log(`Not in registry: ${notFound.join(', ')}`);
208
+ console.log(' (Use "coder-config list" to see available MCPs)');
209
+ }
210
+
211
+ return added.length > 0;
212
+ }
213
+
214
+ /**
215
+ * Remove MCP(s) from global config (~/.claude.json)
216
+ */
217
+ function removeGlobal(mcpNames) {
218
+ if (!mcpNames || mcpNames.length === 0) {
219
+ console.error('Usage: coder-config global remove <mcp-name> [mcp-name...]');
220
+ return false;
221
+ }
222
+
223
+ const configPath = getGlobalConfigPath();
224
+ let config = loadJson(configPath);
225
+
226
+ if (!config || !config.mcpServers) {
227
+ console.error('No global MCPs configured in ~/.claude.json');
228
+ return false;
229
+ }
230
+
231
+ const removed = [];
232
+ const notFound = [];
233
+
234
+ for (const name of mcpNames) {
235
+ if (config.mcpServers[name]) {
236
+ delete config.mcpServers[name];
237
+ removed.push(name);
238
+ } else {
239
+ notFound.push(name);
240
+ }
241
+ }
242
+
243
+ if (removed.length) {
244
+ saveJson(configPath, config);
245
+ console.log(`✓ Removed from ~/.claude.json: ${removed.join(', ')}`);
246
+ }
247
+ if (notFound.length) {
248
+ console.log(`Not in global config: ${notFound.join(', ')}`);
249
+ }
250
+
251
+ return removed.length > 0;
252
+ }
253
+
135
254
  module.exports = {
136
255
  list,
137
256
  add,
138
257
  remove,
258
+ listGlobal,
259
+ addGlobal,
260
+ removeGlobal,
261
+ getGlobalConfigPath,
139
262
  };
@@ -688,34 +688,130 @@ function workstreamCheckPath(installDir, targetPath, silent = false) {
688
688
  }
689
689
 
690
690
  /**
691
- * Generate rules/context from project repositories using Claude Code
692
- * Runs `claude -p` to analyze repos and generate smart context
691
+ * Supported AI tools for context generation
693
692
  */
694
- async function generateRulesWithClaude(projects) {
693
+ const AI_TOOLS = {
694
+ claude: {
695
+ name: 'Claude',
696
+ binary: 'claude',
697
+ buildArgs: (prompt) => ['-p', prompt],
698
+ candidates: (os) => [
699
+ path.join(os.homedir(), '.local', 'bin', 'claude'),
700
+ '/usr/local/bin/claude',
701
+ '/opt/homebrew/bin/claude',
702
+ path.join(os.homedir(), '.npm-global', 'bin', 'claude'),
703
+ ],
704
+ },
705
+ gemini: {
706
+ name: 'Gemini',
707
+ binary: 'gemini',
708
+ buildArgs: (prompt) => ['-p', prompt],
709
+ candidates: (os) => [
710
+ path.join(os.homedir(), '.local', 'bin', 'gemini'),
711
+ '/usr/local/bin/gemini',
712
+ '/opt/homebrew/bin/gemini',
713
+ path.join(os.homedir(), '.npm-global', 'bin', 'gemini'),
714
+ ],
715
+ },
716
+ codex: {
717
+ name: 'Codex',
718
+ binary: 'codex',
719
+ buildArgs: (prompt) => ['exec', prompt],
720
+ candidates: (os) => [
721
+ path.join(os.homedir(), '.local', 'bin', 'codex'),
722
+ '/usr/local/bin/codex',
723
+ '/opt/homebrew/bin/codex',
724
+ path.join(os.homedir(), '.npm-global', 'bin', 'codex'),
725
+ ],
726
+ },
727
+ ollama: {
728
+ name: 'Ollama',
729
+ binary: 'ollama',
730
+ // Model must be specified in options
731
+ buildArgs: (prompt, options) => ['run', options.model || 'llama3.2', prompt],
732
+ candidates: (os) => [
733
+ path.join(os.homedir(), '.local', 'bin', 'ollama'),
734
+ '/usr/local/bin/ollama',
735
+ '/opt/homebrew/bin/ollama',
736
+ ],
737
+ },
738
+ aider: {
739
+ name: 'Aider',
740
+ binary: 'aider',
741
+ buildArgs: (prompt) => ['--message', prompt, '--yes', '--no-git'],
742
+ candidates: (os) => [
743
+ path.join(os.homedir(), '.local', 'bin', 'aider'),
744
+ '/usr/local/bin/aider',
745
+ '/opt/homebrew/bin/aider',
746
+ path.join(os.homedir(), '.local', 'pipx', 'venvs', 'aider-chat', 'bin', 'aider'),
747
+ ],
748
+ },
749
+ };
750
+
751
+ /**
752
+ * Find the binary path for an AI tool
753
+ */
754
+ function findAIBinary(toolId) {
755
+ const { execFileSync } = require('child_process');
756
+ const os = require('os');
757
+
758
+ const tool = AI_TOOLS[toolId];
759
+ if (!tool) {
760
+ throw new Error(`Unknown AI tool: ${toolId}`);
761
+ }
762
+
763
+ // Check candidate paths
764
+ for (const p of tool.candidates(os)) {
765
+ if (fs.existsSync(p)) return p;
766
+ }
767
+
768
+ // Try which command
769
+ try {
770
+ const resolved = execFileSync('which', [tool.binary], { encoding: 'utf8' }).trim();
771
+ if (resolved && fs.existsSync(resolved)) return resolved;
772
+ } catch (e) {}
773
+
774
+ // Fall back to bare binary name (let shell resolve it)
775
+ return tool.binary;
776
+ }
777
+
778
+ /**
779
+ * Get list of available AI tools (ones that are installed)
780
+ */
781
+ function getAvailableAITools() {
782
+ const available = [];
783
+ for (const [id, tool] of Object.entries(AI_TOOLS)) {
784
+ try {
785
+ const binaryPath = findAIBinary(id);
786
+ if (fs.existsSync(binaryPath)) {
787
+ available.push({ id, name: tool.name, path: binaryPath });
788
+ }
789
+ } catch (e) {
790
+ // Tool not available
791
+ }
792
+ }
793
+ return available;
794
+ }
795
+
796
+ /**
797
+ * Generate rules/context from project repositories using an AI tool
798
+ * Supports: claude, gemini, codex, ollama, aider
799
+ * @param {string[]} projects - Array of project paths
800
+ * @param {string} toolId - AI tool to use (default: 'claude')
801
+ * @param {object} options - Tool-specific options (e.g., { model: 'llama3.2' } for ollama)
802
+ */
803
+ async function generateRulesWithAI(projects, toolId = 'claude', options = {}) {
695
804
  if (!projects || projects.length === 0) {
696
805
  return '';
697
806
  }
698
807
 
699
808
  const { execFileSync } = require('child_process');
700
- const os = require('os');
701
809
 
702
- // Find claude binary (daemon processes may not have full PATH)
703
- const getClaudePath = () => {
704
- const candidates = [
705
- path.join(os.homedir(), '.local', 'bin', 'claude'),
706
- '/usr/local/bin/claude',
707
- '/opt/homebrew/bin/claude',
708
- path.join(os.homedir(), '.npm-global', 'bin', 'claude'),
709
- ];
710
- for (const p of candidates) {
711
- if (fs.existsSync(p)) return p;
712
- }
713
- try {
714
- const resolved = execFileSync('which', ['claude'], { encoding: 'utf8' }).trim();
715
- if (resolved && fs.existsSync(resolved)) return resolved;
716
- } catch (e) {}
717
- return 'claude';
718
- };
810
+ const tool = AI_TOOLS[toolId];
811
+ if (!tool) {
812
+ console.error(`Unknown AI tool: ${toolId}. Available: ${Object.keys(AI_TOOLS).join(', ')}`);
813
+ return generateRulesFromRepos(projects);
814
+ }
719
815
 
720
816
  // Expand projects to include discovered sub-projects
721
817
  const allProjects = [];
@@ -728,7 +824,7 @@ async function generateRulesWithClaude(projects) {
728
824
  allProjects.push(absPath);
729
825
  }
730
826
 
731
- // Discover sub-projects (discoverSubProjects defined below)
827
+ // Discover sub-projects
732
828
  const subProjects = discoverSubProjects(absPath);
733
829
  for (const subPath of subProjects) {
734
830
  if (!seen.has(subPath)) {
@@ -752,23 +848,34 @@ ${projectList}
752
848
  Output markdown suitable for injecting into an AI assistant's context. Keep it concise (under 500 words). Do not include code blocks or examples - just descriptions and guidelines.`;
753
849
 
754
850
  try {
755
- // Run claude -p with the prompt using execFileSync (safer than exec)
756
- const claudePath = getClaudePath();
757
- const result = execFileSync(claudePath, ['-p', prompt], {
851
+ const binaryPath = findAIBinary(toolId);
852
+ const args = tool.buildArgs(prompt, options);
853
+
854
+ console.log(`Generating context with ${tool.name}...`);
855
+
856
+ const result = execFileSync(binaryPath, args, {
758
857
  cwd: allProjects[0],
759
858
  encoding: 'utf8',
760
- timeout: 60000, // 60 second timeout
859
+ timeout: 120000, // 2 minute timeout (some models are slower)
761
860
  maxBuffer: 1024 * 1024, // 1MB buffer
762
861
  });
763
862
 
764
863
  return result.trim();
765
864
  } catch (error) {
766
- console.error('Claude generation failed:', error.message);
865
+ console.error(`${tool.name} generation failed:`, error.message);
767
866
  // Fall back to simple generation
768
867
  return generateRulesFromRepos(projects);
769
868
  }
770
869
  }
771
870
 
871
+ /**
872
+ * Generate rules/context from project repositories using Claude Code
873
+ * @deprecated Use generateRulesWithAI(projects, 'claude') instead
874
+ */
875
+ async function generateRulesWithClaude(projects) {
876
+ return generateRulesWithAI(projects, 'claude');
877
+ }
878
+
772
879
  /**
773
880
  * Discover sub-projects within a directory
774
881
  * Looks for directories containing project markers (package.json, pyproject.toml, etc.)
@@ -1460,6 +1567,10 @@ module.exports = {
1460
1567
  discoverSubProjects,
1461
1568
  generateRulesFromRepos,
1462
1569
  generateRulesWithClaude,
1570
+ generateRulesWithAI,
1571
+ getAvailableAITools,
1572
+ findAIBinary,
1573
+ AI_TOOLS,
1463
1574
  // New folder auto-activation functions
1464
1575
  getSettingsPath,
1465
1576
  loadSettings,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coder-config",
3
- "version": "0.45.13",
3
+ "version": "0.46.0-beta.10",
4
4
  "description": "Configuration manager for AI coding tools - Claude Code, Gemini CLI, Codex CLI, Antigravity. Manage MCPs, rules, permissions, memory, and workstreams.",
5
5
  "author": "regression.io",
6
6
  "main": "config-loader.js",