coder-config 0.46.16 → 0.47.2-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 +5 -4
- package/lib/apply.js +17 -3
- package/lib/cli.js +9 -1
- package/lib/constants.js +1 -1
- package/lib/workstreams.js +126 -2
- 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,12 +132,12 @@ 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(); }
|
|
139
139
|
getToolPaths() { return TOOL_PATHS; }
|
|
140
|
-
applyForTools(projectDir, tools) { return applyForTools(this.registryPath, projectDir, tools); }
|
|
140
|
+
applyForTools(projectDir, tools) { return applyForTools(this.registryPath, projectDir, tools, this.installDir); }
|
|
141
141
|
|
|
142
142
|
// MCPs (project)
|
|
143
143
|
list() { return list(this.registryPath); }
|
|
@@ -197,7 +197,7 @@ class ClaudeConfigManager {
|
|
|
197
197
|
workstreamInstallHook() { return workstreamInstallHook(); }
|
|
198
198
|
workstreamInstallHookGemini() { return workstreamInstallHookGemini(); }
|
|
199
199
|
workstreamInstallHookCodex() { return workstreamInstallHookCodex(); }
|
|
200
|
-
workstreamDeactivate() { return workstreamDeactivate(); }
|
|
200
|
+
workstreamDeactivate() { return workstreamDeactivate(this.installDir); }
|
|
201
201
|
workstreamCheckPath(targetPath, silent) { return workstreamCheckPath(this.installDir, targetPath, silent); }
|
|
202
202
|
// Workstream folder auto-activation
|
|
203
203
|
getSettingsPath() { return getSettingsPath(this.installDir); }
|
|
@@ -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, applySandboxIfEnabled } = 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,19 @@ 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 (applySandboxIfEnabled(active, dir)) {
|
|
108
|
+
const resolvedDir = path.resolve(dir);
|
|
109
|
+
const count = active.projects.filter(p =>
|
|
110
|
+
path.resolve(p) !== resolvedDir && !resolvedDir.startsWith(path.resolve(p) + path.sep)
|
|
111
|
+
).length;
|
|
112
|
+
console.log(`✓ Generated .claude/settings.local.json (sandbox scope)`);
|
|
113
|
+
console.log(` └─ ${count} additional director${count === 1 ? 'y' : 'ies'}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
103
117
|
// Generate settings.json with enabledPlugins if any are configured
|
|
104
118
|
if (mergedConfig.enabledPlugins && Object.keys(mergedConfig.enabledPlugins).length > 0) {
|
|
105
119
|
const settingsPath = path.join(dir, '.claude', 'settings.json');
|
|
@@ -424,12 +438,12 @@ function detectInstalledTools() {
|
|
|
424
438
|
/**
|
|
425
439
|
* Apply config for multiple tools based on preferences
|
|
426
440
|
*/
|
|
427
|
-
function applyForTools(registryPath, projectDir = null, tools = ['claude']) {
|
|
441
|
+
function applyForTools(registryPath, projectDir = null, tools = ['claude'], installDir = null) {
|
|
428
442
|
const results = {};
|
|
429
443
|
|
|
430
444
|
for (const tool of tools) {
|
|
431
445
|
if (tool === 'claude') {
|
|
432
|
-
results.claude = apply(registryPath, projectDir);
|
|
446
|
+
results.claude = apply(registryPath, projectDir, installDir);
|
|
433
447
|
} else if (tool === 'gemini') {
|
|
434
448
|
results.gemini = applyForGemini(registryPath, projectDir);
|
|
435
449
|
} else if (tool === 'antigravity') {
|
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,9 @@ 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
|
+
applySandboxIfEnabled(active, process.cwd());
|
|
400
|
+
|
|
394
401
|
return output;
|
|
395
402
|
}
|
|
396
403
|
|
|
@@ -633,10 +640,93 @@ fi
|
|
|
633
640
|
return true;
|
|
634
641
|
}
|
|
635
642
|
|
|
643
|
+
/**
|
|
644
|
+
* Apply sandbox settings if the workstream has sandbox enabled.
|
|
645
|
+
* Shared logic used by both workstreamInject() and apply().
|
|
646
|
+
* @param {object} active - Active workstream object
|
|
647
|
+
* @param {string} dir - Target project directory (absolute)
|
|
648
|
+
* @returns {boolean} Whether sandbox settings were written
|
|
649
|
+
*/
|
|
650
|
+
function applySandboxIfEnabled(active, dir) {
|
|
651
|
+
if (!active || active.sandbox !== true || !active.projects || active.projects.length === 0) {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const resolvedDir = path.resolve(dir);
|
|
656
|
+
const otherProjects = active.projects.filter(p => {
|
|
657
|
+
const resolved = path.resolve(p);
|
|
658
|
+
return resolved !== resolvedDir && !resolvedDir.startsWith(resolved + path.sep);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
if (otherProjects.length > 0) {
|
|
662
|
+
writeWorkstreamSandboxSettings(resolvedDir, otherProjects);
|
|
663
|
+
return true;
|
|
664
|
+
}
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Write additionalDirectories to .claude/settings.local.json for sandbox scope
|
|
670
|
+
*/
|
|
671
|
+
function writeWorkstreamSandboxSettings(dir, additionalDirectories) {
|
|
672
|
+
const claudeDir = path.join(dir, '.claude');
|
|
673
|
+
const settingsLocalPath = path.join(claudeDir, 'settings.local.json');
|
|
674
|
+
|
|
675
|
+
let existing = {};
|
|
676
|
+
if (fs.existsSync(settingsLocalPath)) {
|
|
677
|
+
try { existing = JSON.parse(fs.readFileSync(settingsLocalPath, 'utf8')); } catch {}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const permissions = existing.permissions || {};
|
|
681
|
+
permissions.additionalDirectories = additionalDirectories;
|
|
682
|
+
|
|
683
|
+
if (!fs.existsSync(claudeDir)) {
|
|
684
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
685
|
+
}
|
|
686
|
+
fs.writeFileSync(settingsLocalPath, JSON.stringify({ ...existing, permissions }, null, 2) + '\n');
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Remove additionalDirectories from .claude/settings.local.json
|
|
691
|
+
*/
|
|
692
|
+
function removeWorkstreamSandboxSettings(dir) {
|
|
693
|
+
const settingsLocalPath = path.join(dir, '.claude', 'settings.local.json');
|
|
694
|
+
if (!fs.existsSync(settingsLocalPath)) return;
|
|
695
|
+
|
|
696
|
+
let existing = {};
|
|
697
|
+
try { existing = JSON.parse(fs.readFileSync(settingsLocalPath, 'utf8')); } catch { return; }
|
|
698
|
+
|
|
699
|
+
if (existing.permissions && existing.permissions.additionalDirectories) {
|
|
700
|
+
delete existing.permissions.additionalDirectories;
|
|
701
|
+
// Clean up empty permissions object
|
|
702
|
+
if (Object.keys(existing.permissions).length === 0) {
|
|
703
|
+
delete existing.permissions;
|
|
704
|
+
}
|
|
705
|
+
// If nothing left, remove the file
|
|
706
|
+
if (Object.keys(existing).length === 0) {
|
|
707
|
+
fs.unlinkSync(settingsLocalPath);
|
|
708
|
+
} else {
|
|
709
|
+
fs.writeFileSync(settingsLocalPath, JSON.stringify(existing, null, 2) + '\n');
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
636
714
|
/**
|
|
637
715
|
* Deactivate workstream (output shell command to unset env var)
|
|
638
716
|
*/
|
|
639
|
-
function workstreamDeactivate() {
|
|
717
|
+
function workstreamDeactivate(installDir) {
|
|
718
|
+
// Clean up sandbox settings from all workstream project directories
|
|
719
|
+
if (installDir) {
|
|
720
|
+
const active = getActiveWorkstream(installDir);
|
|
721
|
+
if (active && active.projects) {
|
|
722
|
+
for (const p of active.projects) {
|
|
723
|
+
removeWorkstreamSandboxSettings(path.resolve(p));
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
// Also clean CWD in case it's not a listed project
|
|
728
|
+
removeWorkstreamSandboxSettings(process.cwd());
|
|
729
|
+
|
|
640
730
|
console.log('To deactivate the workstream for this session, run:');
|
|
641
731
|
console.log(' unset CODER_WORKSTREAM');
|
|
642
732
|
console.log('\nOr to clear the global active workstream:');
|
|
@@ -1308,6 +1398,38 @@ function workstreamRemoveTrigger(installDir, idOrName, folderPath) {
|
|
|
1308
1398
|
return ws;
|
|
1309
1399
|
}
|
|
1310
1400
|
|
|
1401
|
+
/**
|
|
1402
|
+
* Set sandbox mode for a workstream
|
|
1403
|
+
* When true: generates additionalDirectories in settings.local.json for OS-level enforcement
|
|
1404
|
+
* When false (default): only injects advisory LLM rules (softer, LLM can override if important)
|
|
1405
|
+
*/
|
|
1406
|
+
function workstreamSetSandbox(installDir, idOrName, value) {
|
|
1407
|
+
const data = loadWorkstreams(installDir);
|
|
1408
|
+
const ws = data.workstreams.find(
|
|
1409
|
+
w => w.id === idOrName || w.name.toLowerCase() === idOrName.toLowerCase()
|
|
1410
|
+
);
|
|
1411
|
+
|
|
1412
|
+
if (!ws) {
|
|
1413
|
+
console.error(`Workstream not found: ${idOrName}`);
|
|
1414
|
+
return null;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
if (value === 'on' || value === true || value === 'true') {
|
|
1418
|
+
ws.sandbox = true;
|
|
1419
|
+
console.log(`✓ Sandbox enabled for ${ws.name} (OS-level directory enforcement)`);
|
|
1420
|
+
} else if (value === 'off' || value === false || value === 'false') {
|
|
1421
|
+
ws.sandbox = false;
|
|
1422
|
+
console.log(`✓ Sandbox disabled for ${ws.name} (advisory LLM rules only)`);
|
|
1423
|
+
} else {
|
|
1424
|
+
console.error(`Invalid value: ${value}. Use "on" or "off".`);
|
|
1425
|
+
return null;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
ws.updatedAt = new Date().toISOString();
|
|
1429
|
+
saveWorkstreams(installDir, data);
|
|
1430
|
+
return ws;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1311
1433
|
/**
|
|
1312
1434
|
* Set auto-activate for a workstream
|
|
1313
1435
|
* value: true, false, or null (use global default)
|
|
@@ -1641,4 +1763,6 @@ module.exports = {
|
|
|
1641
1763
|
workstreamInstallCdHook,
|
|
1642
1764
|
workstreamUninstallCdHook,
|
|
1643
1765
|
workstreamCdHookStatus,
|
|
1766
|
+
applySandboxIfEnabled,
|
|
1767
|
+
workstreamSetSandbox,
|
|
1644
1768
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coder-config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.2-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",
|