coder-config 0.45.14 → 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,7 +22,7 @@ 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');
@@ -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); }
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.14';
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coder-config",
3
- "version": "0.45.14",
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",
@@ -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"