coder-config 0.46.16 → 0.47.1-beta
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 +3 -2
- package/lib/apply.js +19 -1
- package/lib/cli.js +9 -1
- package/lib/constants.js +1 -1
- package/lib/workstreams.js +100 -1
- package/package.json +1 -1
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, discoverSubProjects, generateRulesFromRepos, generateRulesWithClaude, generateRulesWithAI, getAvailableAITools, findAIBinary, AI_TOOLS } = 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, workstreamSetSandbox } = 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');
|
|
@@ -132,7 +132,7 @@ class ClaudeConfigManager {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
// Apply
|
|
135
|
-
apply(projectDir) { return apply(this.registryPath, projectDir); }
|
|
135
|
+
apply(projectDir) { return apply(this.registryPath, projectDir, this.installDir); }
|
|
136
136
|
applyForAntigravity(projectDir) { return applyForAntigravity(this.registryPath, projectDir); }
|
|
137
137
|
applyForGemini(projectDir) { return applyForGemini(this.registryPath, projectDir); }
|
|
138
138
|
detectInstalledTools() { return detectInstalledTools(); }
|
|
@@ -206,6 +206,7 @@ class ClaudeConfigManager {
|
|
|
206
206
|
workstreamAddTrigger(idOrName, folderPath) { return workstreamAddTrigger(this.installDir, idOrName, folderPath); }
|
|
207
207
|
workstreamRemoveTrigger(idOrName, folderPath) { return workstreamRemoveTrigger(this.installDir, idOrName, folderPath); }
|
|
208
208
|
workstreamSetAutoActivate(idOrName, value) { return workstreamSetAutoActivate(this.installDir, idOrName, value); }
|
|
209
|
+
workstreamSetSandbox(idOrName, value) { return workstreamSetSandbox(this.installDir, idOrName, value); }
|
|
209
210
|
setGlobalAutoActivate(value) { return setGlobalAutoActivate(this.installDir, value); }
|
|
210
211
|
shouldAutoActivate(workstream) { return shouldAutoActivate(this.installDir, workstream); }
|
|
211
212
|
workstreamCheckFolder(folderPath, jsonOutput) { return workstreamCheckFolder(this.installDir, folderPath, jsonOutput); }
|
package/lib/apply.js
CHANGED
|
@@ -8,11 +8,12 @@ const { execSync } = require('child_process');
|
|
|
8
8
|
const { TOOL_PATHS } = require('./constants');
|
|
9
9
|
const { loadJson, saveJson, loadEnvFile, interpolate, resolveEnvVars } = require('./utils');
|
|
10
10
|
const { findProjectRoot, findAllConfigs, mergeConfigs, findAllConfigsForTool } = require('./config');
|
|
11
|
+
const { getActiveWorkstream, writeWorkstreamSandboxSettings } = require('./workstreams');
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Generate .mcp.json for a project (with hierarchical config merging)
|
|
14
15
|
*/
|
|
15
|
-
function apply(registryPath, projectDir = null) {
|
|
16
|
+
function apply(registryPath, projectDir = null, installDir = null) {
|
|
16
17
|
const dir = projectDir || findProjectRoot() || process.cwd();
|
|
17
18
|
|
|
18
19
|
const registry = loadJson(registryPath);
|
|
@@ -100,6 +101,23 @@ function apply(registryPath, projectDir = null) {
|
|
|
100
101
|
console.log(`✓ Generated ${outputPath}`);
|
|
101
102
|
console.log(` └─ ${count} MCP(s): ${Object.keys(output.mcpServers).join(', ')}`);
|
|
102
103
|
|
|
104
|
+
// Generate settings.local.json with additionalDirectories for workstream sandbox scope
|
|
105
|
+
if (installDir) {
|
|
106
|
+
const active = getActiveWorkstream(installDir);
|
|
107
|
+
if (active && active.projects && active.projects.length > 0 && active.sandbox === true) {
|
|
108
|
+
const otherProjects = active.projects.filter(p => {
|
|
109
|
+
const resolved = path.resolve(p);
|
|
110
|
+
return resolved !== path.resolve(dir) && !dir.startsWith(resolved + path.sep);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (otherProjects.length > 0) {
|
|
114
|
+
writeWorkstreamSandboxSettings(dir, otherProjects);
|
|
115
|
+
console.log(`✓ Generated .claude/settings.local.json (sandbox scope)`);
|
|
116
|
+
console.log(` └─ ${otherProjects.length} additional director${otherProjects.length === 1 ? 'y' : 'ies'}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
103
121
|
// Generate settings.json with enabledPlugins if any are configured
|
|
104
122
|
if (mergedConfig.enabledPlugins && Object.keys(mergedConfig.enabledPlugins).length > 0) {
|
|
105
123
|
const settingsPath = path.join(dir, '.claude', 'settings.json');
|
package/lib/cli.js
CHANGED
|
@@ -194,6 +194,13 @@ function runCli(manager) {
|
|
|
194
194
|
process.exit(1);
|
|
195
195
|
}
|
|
196
196
|
manager.workstreamRemoveTrigger(args[2], args[3]);
|
|
197
|
+
} else if (args[1] === 'sandbox') {
|
|
198
|
+
if (!args[2]) {
|
|
199
|
+
console.error('Usage: coder-config workstream sandbox <workstream> [on|off]');
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
const value = args[3] || 'on';
|
|
203
|
+
manager.workstreamSetSandbox(args[2], value);
|
|
197
204
|
} else if (args[1] === 'auto-activate') {
|
|
198
205
|
if (!args[2]) {
|
|
199
206
|
console.error('Usage: coder-config workstream auto-activate <workstream> [on|off|default]');
|
|
@@ -385,7 +392,7 @@ ${chalk.dim('Configuration manager for AI coding tools (Claude Code, Gemini CLI,
|
|
|
385
392
|
// Project Commands
|
|
386
393
|
console.log(box('Project', [
|
|
387
394
|
cmd('init', 'Initialize project with .claude/mcps.json'),
|
|
388
|
-
cmd('apply', 'Generate .mcp.json
|
|
395
|
+
cmd('apply', 'Generate .mcp.json and sandbox scope'),
|
|
389
396
|
cmd('show', 'Show current project config'),
|
|
390
397
|
cmd('list', 'List available MCPs (✓ = active)'),
|
|
391
398
|
cmd('add <mcp> [mcp...]', 'Add MCP(s) to project'),
|
|
@@ -434,6 +441,7 @@ ${chalk.dim('Configuration manager for AI coding tools (Claude Code, Gemini CLI,
|
|
|
434
441
|
cmd('workstream use <name>', 'Set active workstream'),
|
|
435
442
|
cmd('workstream add <ws> <path>', 'Add project to workstream'),
|
|
436
443
|
cmd('workstream remove <ws> <path>', 'Remove from workstream'),
|
|
444
|
+
cmd('workstream sandbox <ws> [on|off]', 'Toggle sandbox enforcement'),
|
|
437
445
|
cmd('workstream install-hook', 'Install pre-prompt hook'),
|
|
438
446
|
]));
|
|
439
447
|
console.log();
|
package/lib/constants.js
CHANGED
package/lib/workstreams.js
CHANGED
|
@@ -86,7 +86,11 @@ function workstreamList(installDir) {
|
|
|
86
86
|
const detected = workstreamDetect(installDir, process.cwd());
|
|
87
87
|
for (const ws of data.workstreams) {
|
|
88
88
|
const active = detected && ws.id === detected.id ? '● ' : '○ ';
|
|
89
|
-
|
|
89
|
+
const badges = [];
|
|
90
|
+
if (ws.sandbox === true) badges.push('[sandbox]');
|
|
91
|
+
if (ws.autoActivate === true) badges.push('[auto]');
|
|
92
|
+
const badgeStr = badges.length > 0 ? ' ' + badges.join(' ') : '';
|
|
93
|
+
console.log(`${active}${ws.name}${badgeStr}`);
|
|
90
94
|
if (ws.projects && ws.projects.length > 0) {
|
|
91
95
|
console.log(` Projects: ${ws.projects.map(p => path.basename(p)).join(', ')}`);
|
|
92
96
|
}
|
|
@@ -391,6 +395,20 @@ function workstreamInject(installDir, silent = false) {
|
|
|
391
395
|
// Always output the context (for hooks), silent only suppresses "no active" message
|
|
392
396
|
console.log(output);
|
|
393
397
|
|
|
398
|
+
// Generate settings.local.json with additionalDirectories for sandbox scope
|
|
399
|
+
// Only when sandbox mode is explicitly enabled (default: off)
|
|
400
|
+
if (active.sandbox === true && active.projects && active.projects.length > 0) {
|
|
401
|
+
const cwd = process.cwd();
|
|
402
|
+
const otherProjects = active.projects.filter(p => {
|
|
403
|
+
const resolved = path.resolve(p);
|
|
404
|
+
return resolved !== path.resolve(cwd) && !cwd.startsWith(resolved + path.sep);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
if (otherProjects.length > 0) {
|
|
408
|
+
writeWorkstreamSandboxSettings(cwd, otherProjects);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
394
412
|
return output;
|
|
395
413
|
}
|
|
396
414
|
|
|
@@ -633,10 +651,59 @@ fi
|
|
|
633
651
|
return true;
|
|
634
652
|
}
|
|
635
653
|
|
|
654
|
+
/**
|
|
655
|
+
* Write additionalDirectories to .claude/settings.local.json for sandbox scope
|
|
656
|
+
*/
|
|
657
|
+
function writeWorkstreamSandboxSettings(dir, additionalDirectories) {
|
|
658
|
+
const claudeDir = path.join(dir, '.claude');
|
|
659
|
+
const settingsLocalPath = path.join(claudeDir, 'settings.local.json');
|
|
660
|
+
|
|
661
|
+
let existing = {};
|
|
662
|
+
if (fs.existsSync(settingsLocalPath)) {
|
|
663
|
+
try { existing = JSON.parse(fs.readFileSync(settingsLocalPath, 'utf8')); } catch {}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const permissions = existing.permissions || {};
|
|
667
|
+
permissions.additionalDirectories = additionalDirectories;
|
|
668
|
+
|
|
669
|
+
if (!fs.existsSync(claudeDir)) {
|
|
670
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
671
|
+
}
|
|
672
|
+
fs.writeFileSync(settingsLocalPath, JSON.stringify({ ...existing, permissions }, null, 2) + '\n');
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Remove additionalDirectories from .claude/settings.local.json
|
|
677
|
+
*/
|
|
678
|
+
function removeWorkstreamSandboxSettings(dir) {
|
|
679
|
+
const settingsLocalPath = path.join(dir, '.claude', 'settings.local.json');
|
|
680
|
+
if (!fs.existsSync(settingsLocalPath)) return;
|
|
681
|
+
|
|
682
|
+
let existing = {};
|
|
683
|
+
try { existing = JSON.parse(fs.readFileSync(settingsLocalPath, 'utf8')); } catch { return; }
|
|
684
|
+
|
|
685
|
+
if (existing.permissions && existing.permissions.additionalDirectories) {
|
|
686
|
+
delete existing.permissions.additionalDirectories;
|
|
687
|
+
// Clean up empty permissions object
|
|
688
|
+
if (Object.keys(existing.permissions).length === 0) {
|
|
689
|
+
delete existing.permissions;
|
|
690
|
+
}
|
|
691
|
+
// If nothing left, remove the file
|
|
692
|
+
if (Object.keys(existing).length === 0) {
|
|
693
|
+
fs.unlinkSync(settingsLocalPath);
|
|
694
|
+
} else {
|
|
695
|
+
fs.writeFileSync(settingsLocalPath, JSON.stringify(existing, null, 2) + '\n');
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
636
700
|
/**
|
|
637
701
|
* Deactivate workstream (output shell command to unset env var)
|
|
638
702
|
*/
|
|
639
703
|
function workstreamDeactivate() {
|
|
704
|
+
// Clean up sandbox settings from CWD
|
|
705
|
+
removeWorkstreamSandboxSettings(process.cwd());
|
|
706
|
+
|
|
640
707
|
console.log('To deactivate the workstream for this session, run:');
|
|
641
708
|
console.log(' unset CODER_WORKSTREAM');
|
|
642
709
|
console.log('\nOr to clear the global active workstream:');
|
|
@@ -1308,6 +1375,35 @@ function workstreamRemoveTrigger(installDir, idOrName, folderPath) {
|
|
|
1308
1375
|
return ws;
|
|
1309
1376
|
}
|
|
1310
1377
|
|
|
1378
|
+
/**
|
|
1379
|
+
* Set sandbox mode for a workstream
|
|
1380
|
+
* When true (default): generates additionalDirectories in settings.local.json for OS-level enforcement
|
|
1381
|
+
* When false: only injects advisory LLM rules (softer, LLM can override if important)
|
|
1382
|
+
*/
|
|
1383
|
+
function workstreamSetSandbox(installDir, idOrName, value) {
|
|
1384
|
+
const data = loadWorkstreams(installDir);
|
|
1385
|
+
const ws = data.workstreams.find(
|
|
1386
|
+
w => w.id === idOrName || w.name.toLowerCase() === idOrName.toLowerCase()
|
|
1387
|
+
);
|
|
1388
|
+
|
|
1389
|
+
if (!ws) {
|
|
1390
|
+
console.error(`Workstream not found: ${idOrName}`);
|
|
1391
|
+
return null;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
if (value === 'on' || value === true || value === 'true') {
|
|
1395
|
+
ws.sandbox = true;
|
|
1396
|
+
console.log(`✓ Sandbox enabled for ${ws.name} (OS-level directory enforcement)`);
|
|
1397
|
+
} else if (value === 'off' || value === false || value === 'false') {
|
|
1398
|
+
ws.sandbox = false;
|
|
1399
|
+
console.log(`✓ Sandbox disabled for ${ws.name} (advisory LLM rules only)`);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
ws.updatedAt = new Date().toISOString();
|
|
1403
|
+
saveWorkstreams(installDir, data);
|
|
1404
|
+
return ws;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1311
1407
|
/**
|
|
1312
1408
|
* Set auto-activate for a workstream
|
|
1313
1409
|
* value: true, false, or null (use global default)
|
|
@@ -1641,4 +1737,7 @@ module.exports = {
|
|
|
1641
1737
|
workstreamInstallCdHook,
|
|
1642
1738
|
workstreamUninstallCdHook,
|
|
1643
1739
|
workstreamCdHookStatus,
|
|
1740
|
+
writeWorkstreamSandboxSettings,
|
|
1741
|
+
removeWorkstreamSandboxSettings,
|
|
1742
|
+
workstreamSetSandbox,
|
|
1644
1743
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coder-config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.1-beta",
|
|
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",
|