coder-config 0.45.13 → 0.46.0-beta.7

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
@@ -28,7 +28,7 @@ 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');
@@ -208,6 +208,11 @@ class ClaudeConfigManager {
208
208
  workstreamCdHookStatus() { return workstreamCdHookStatus(); }
209
209
  generateRulesFromRepos(projects) { return generateRulesFromRepos(projects); }
210
210
  generateRulesWithClaude(projects) { return generateRulesWithClaude(projects); }
211
+ generateRulesWithAI(projects, toolId, options) { return generateRulesWithAI(projects, toolId, options); }
212
+ getAvailableAITools() { return getAvailableAITools(); }
213
+ findAIBinary(toolId) { return findAIBinary(toolId); }
214
+ get AI_TOOLS() { return AI_TOOLS; }
215
+ discoverSubProjects(rootPath, maxDepth) { return discoverSubProjects(rootPath, maxDepth); }
211
216
 
212
217
  // Loops (Ralph Loop)
213
218
  getLoopsPath() { return getLoopsPath(this.installDir); }
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.7';
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: {
@@ -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.7",
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",
@@ -1,45 +1,78 @@
1
1
  #!/bin/bash
2
- # Auto-release script: bumps patch version, commits, tags, and pushes
3
- # Usage: ./scripts/release.sh "commit message"
4
- # ./scripts/release.sh "commit message" --minor
5
- # ./scripts/release.sh "commit message" --major
2
+ # Stable release script
3
+ # Usage: npm run release # Interactive, prompts for version
4
+ # npm run release -- 0.46.0 # Specific version
5
+ # npm run release -- --minor # Bump minor version
6
+ # npm run release -- --major # Bump major version
6
7
 
7
8
  set -e
8
9
 
9
- if [ -z "$1" ]; then
10
- echo "Usage: $0 \"commit message\" [--minor|--major]"
11
- exit 1
12
- fi
13
-
14
- MESSAGE="$1"
15
- BUMP_TYPE="${2:---patch}"
16
-
17
10
  # Get current version
18
- CURRENT_VERSION=$(node -p "require('./package.json').version")
11
+ CURRENT_VERSION=$(node -p "require('./package.json').version.split('-')[0]")
19
12
 
20
- # Parse version
13
+ # Parse current version
21
14
  IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
22
15
 
23
- # Bump based on type
24
- case "$BUMP_TYPE" in
25
- --major)
26
- MAJOR=$((MAJOR + 1))
27
- MINOR=0
28
- PATCH=0
29
- ;;
30
- --minor)
31
- MINOR=$((MINOR + 1))
32
- PATCH=0
33
- ;;
34
- --patch|*)
16
+ # Determine new version
17
+ if [ -n "$1" ]; then
18
+ case "$1" in
19
+ --major)
20
+ MAJOR=$((MAJOR + 1))
21
+ MINOR=0
22
+ PATCH=0
23
+ NEW_VERSION="$MAJOR.$MINOR.$PATCH"
24
+ ;;
25
+ --minor)
26
+ MINOR=$((MINOR + 1))
27
+ PATCH=0
28
+ NEW_VERSION="$MAJOR.$MINOR.$PATCH"
29
+ ;;
30
+ --patch)
31
+ PATCH=$((PATCH + 1))
32
+ NEW_VERSION="$MAJOR.$MINOR.$PATCH"
33
+ ;;
34
+ *)
35
+ # Assume it's a version number
36
+ NEW_VERSION="$1"
37
+ ;;
38
+ esac
39
+ else
40
+ # Interactive mode
41
+ echo ""
42
+ echo "Current version: $CURRENT_VERSION"
43
+ echo ""
44
+ echo "Enter new version (or press Enter for patch bump):"
45
+ read -r INPUT_VERSION
46
+
47
+ if [ -z "$INPUT_VERSION" ]; then
35
48
  PATCH=$((PATCH + 1))
36
- ;;
37
- esac
49
+ NEW_VERSION="$MAJOR.$MINOR.$PATCH"
50
+ else
51
+ NEW_VERSION="$INPUT_VERSION"
52
+ fi
53
+ fi
54
+
55
+ # Validate version format
56
+ if ! [[ "$NEW_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
57
+ echo "Invalid version format: $NEW_VERSION"
58
+ echo "Expected format: X.Y.Z (e.g., 0.46.0)"
59
+ exit 1
60
+ fi
38
61
 
39
- NEW_VERSION="$MAJOR.$MINOR.$PATCH"
40
62
  TAG="v$NEW_VERSION"
41
63
 
42
- echo "Releasing $CURRENT_VERSION -> $NEW_VERSION"
64
+ echo ""
65
+ echo "📦 Releasing: $CURRENT_VERSION -> $NEW_VERSION"
66
+ echo ""
67
+
68
+ # Check for uncommitted changes
69
+ if [ -n "$(git status --porcelain)" ]; then
70
+ echo "⚠️ You have uncommitted changes:"
71
+ git status --short
72
+ echo ""
73
+ echo "Commit them first, or press Enter to continue anyway:"
74
+ read -r
75
+ fi
43
76
 
44
77
  # Update version in package.json
45
78
  node -e "
@@ -52,20 +85,22 @@ fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
52
85
  # Sync version to other files
53
86
  npm run version:sync
54
87
 
55
- # Stage all changes
56
- git add -A
88
+ # Stage version files
89
+ git add package.json lib/constants.js ui/package.json
57
90
 
58
- # Commit
59
- git commit -m "$MESSAGE
91
+ # Commit (skip hooks)
92
+ SKIP_AUTO_PUSH=1 git commit -m "release: v$NEW_VERSION"
60
93
 
61
- Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
62
-
63
- # Tag
94
+ # Create tag
64
95
  git tag "$TAG"
65
96
 
66
- # Push with tags
97
+ # Push commit and tag
98
+ echo ""
99
+ echo "🚀 Pushing $TAG..."
67
100
  git push && git push --tags
68
101
 
69
102
  echo ""
70
103
  echo "✅ Released $TAG"
71
- echo " CI will publish to npm automatically"
104
+ echo ""
105
+ echo "CI will publish to npm with 'latest' tag."
106
+ echo "Users will get this version with: npm install -g coder-config"