coder-config 0.42.13 → 0.42.15
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/README.md +40 -0
- package/config-loader.js +14 -1
- package/lib/cli.js +41 -0
- package/lib/constants.js +1 -1
- package/lib/workstreams.js +378 -0
- package/package.json +1 -1
- package/ui/dist/assets/index-C7td6w_P.css +32 -0
- package/ui/dist/assets/{index-DQdk5fuy.js → index-FlFQc2hz.js} +175 -175
- package/ui/dist/index.html +2 -2
- package/ui/routes/workstreams.js +119 -0
- package/ui/server.cjs +26 -0
- package/ui/dist/assets/index-X0dVhMey.css +0 -32
package/README.md
CHANGED
|
@@ -143,6 +143,15 @@ coder-config workstream install-hook # Install hook for Claude Code
|
|
|
143
143
|
coder-config workstream install-hook --gemini # Install hook for Gemini CLI
|
|
144
144
|
coder-config workstream install-hook --codex # Install hook for Codex CLI
|
|
145
145
|
coder-config workstream install-hook --all # Install hooks for all supported tools
|
|
146
|
+
|
|
147
|
+
# Folder auto-activation
|
|
148
|
+
coder-config workstream add-trigger <ws> <folder> # Add trigger folder
|
|
149
|
+
coder-config workstream remove-trigger <ws> <folder> # Remove trigger folder
|
|
150
|
+
coder-config workstream auto-activate <ws> [on|off|default] # Set auto-activate
|
|
151
|
+
coder-config workstream check-folder [path] [--json] # Check folder for matches
|
|
152
|
+
coder-config workstream install-cd-hook # Install cd hook for shell
|
|
153
|
+
coder-config workstream uninstall-cd-hook # Remove cd hook
|
|
154
|
+
coder-config workstream cd-hook-status # Check cd hook status
|
|
146
155
|
```
|
|
147
156
|
|
|
148
157
|
**Per-terminal isolation**: With [shell integration](#shell-integration), each terminal can have its own active workstream:
|
|
@@ -171,6 +180,37 @@ coder-config workstream install-hook --codex
|
|
|
171
180
|
coder-config workstream install-hook --all
|
|
172
181
|
```
|
|
173
182
|
|
|
183
|
+
**Folder auto-activation**: Automatically activate workstreams when you cd into matching directories:
|
|
184
|
+
```bash
|
|
185
|
+
# Install the cd hook (adds function to ~/.zshrc or ~/.bashrc)
|
|
186
|
+
coder-config workstream install-cd-hook
|
|
187
|
+
|
|
188
|
+
# Now when you cd into a project folder:
|
|
189
|
+
cd ~/projects/my-app # Auto-activates matching workstream
|
|
190
|
+
# Output: 📂 Workstream: My App
|
|
191
|
+
|
|
192
|
+
# If multiple workstreams match, you'll be prompted:
|
|
193
|
+
cd ~/projects
|
|
194
|
+
# Output: Multiple workstreams match this folder:
|
|
195
|
+
# 1) Frontend
|
|
196
|
+
# 2) Backend
|
|
197
|
+
# 0) Skip
|
|
198
|
+
# Choose [0-2]:
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Trigger folders**: Besides project paths, you can add extra trigger folders:
|
|
202
|
+
```bash
|
|
203
|
+
coder-config workstream add-trigger "My Work" ~/projects
|
|
204
|
+
coder-config workstream remove-trigger "My Work" ~/projects
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Auto-activate setting**: Control per-workstream or globally:
|
|
208
|
+
```bash
|
|
209
|
+
coder-config workstream auto-activate "My Work" on # Always auto-activate
|
|
210
|
+
coder-config workstream auto-activate "My Work" off # Never auto-activate
|
|
211
|
+
coder-config workstream auto-activate "My Work" default # Use global setting
|
|
212
|
+
```
|
|
213
|
+
|
|
174
214
|
### Loop Commands (Ralph Loop)
|
|
175
215
|
|
|
176
216
|
Ralph Loops enable autonomous development - Claude Code runs continuously until a task is completed.
|
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 } = 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 } = 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, loopStatus, loopHistory, loopConfig, getActiveLoop, recordIteration, saveClarifications, savePlan, loadClarifications, loadPlan, loopInject, archiveLoop } = require('./lib/loops');
|
|
34
34
|
const { runCli } = require('./lib/cli');
|
|
@@ -148,6 +148,19 @@ class ClaudeConfigManager {
|
|
|
148
148
|
workstreamInstallHookCodex() { return workstreamInstallHookCodex(); }
|
|
149
149
|
workstreamDeactivate() { return workstreamDeactivate(); }
|
|
150
150
|
workstreamCheckPath(targetPath, silent) { return workstreamCheckPath(this.installDir, targetPath, silent); }
|
|
151
|
+
// Workstream folder auto-activation
|
|
152
|
+
getSettingsPath() { return getSettingsPath(this.installDir); }
|
|
153
|
+
loadSettings() { return loadSettings(this.installDir); }
|
|
154
|
+
saveSettings(settings) { return saveSettings(this.installDir, settings); }
|
|
155
|
+
workstreamAddTrigger(idOrName, folderPath) { return workstreamAddTrigger(this.installDir, idOrName, folderPath); }
|
|
156
|
+
workstreamRemoveTrigger(idOrName, folderPath) { return workstreamRemoveTrigger(this.installDir, idOrName, folderPath); }
|
|
157
|
+
workstreamSetAutoActivate(idOrName, value) { return workstreamSetAutoActivate(this.installDir, idOrName, value); }
|
|
158
|
+
setGlobalAutoActivate(value) { return setGlobalAutoActivate(this.installDir, value); }
|
|
159
|
+
shouldAutoActivate(workstream) { return shouldAutoActivate(this.installDir, workstream); }
|
|
160
|
+
workstreamCheckFolder(folderPath, jsonOutput) { return workstreamCheckFolder(this.installDir, folderPath, jsonOutput); }
|
|
161
|
+
workstreamInstallCdHook() { return workstreamInstallCdHook(); }
|
|
162
|
+
workstreamUninstallCdHook() { return workstreamUninstallCdHook(); }
|
|
163
|
+
workstreamCdHookStatus() { return workstreamCdHookStatus(); }
|
|
151
164
|
|
|
152
165
|
// Loops (Ralph Loop)
|
|
153
166
|
getLoopsPath() { return getLoopsPath(this.installDir); }
|
package/lib/cli.js
CHANGED
|
@@ -144,6 +144,38 @@ function runCli(manager) {
|
|
|
144
144
|
const silent = args.includes('--silent') || args.includes('-s');
|
|
145
145
|
const isValid = manager.workstreamCheckPath(targetPath, silent);
|
|
146
146
|
process.exit(isValid ? 0 : 1);
|
|
147
|
+
} else if (args[1] === 'add-trigger') {
|
|
148
|
+
if (!args[2] || !args[3]) {
|
|
149
|
+
console.error('Usage: coder-config workstream add-trigger <workstream> <folder>');
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
manager.workstreamAddTrigger(args[2], args[3]);
|
|
153
|
+
} else if (args[1] === 'remove-trigger') {
|
|
154
|
+
if (!args[2] || !args[3]) {
|
|
155
|
+
console.error('Usage: coder-config workstream remove-trigger <workstream> <folder>');
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
manager.workstreamRemoveTrigger(args[2], args[3]);
|
|
159
|
+
} else if (args[1] === 'auto-activate') {
|
|
160
|
+
if (!args[2]) {
|
|
161
|
+
console.error('Usage: coder-config workstream auto-activate <workstream> [on|off|default]');
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
const value = args[3] || 'on';
|
|
165
|
+
manager.workstreamSetAutoActivate(args[2], value);
|
|
166
|
+
} else if (args[1] === 'check-folder') {
|
|
167
|
+
const folderPath = args[2] || process.cwd();
|
|
168
|
+
const jsonOutput = args.includes('--json') || args.includes('-j');
|
|
169
|
+
manager.workstreamCheckFolder(folderPath, jsonOutput);
|
|
170
|
+
} else if (args[1] === 'install-cd-hook') {
|
|
171
|
+
manager.workstreamInstallCdHook();
|
|
172
|
+
} else if (args[1] === 'uninstall-cd-hook') {
|
|
173
|
+
manager.workstreamUninstallCdHook();
|
|
174
|
+
} else if (args[1] === 'cd-hook-status') {
|
|
175
|
+
const status = manager.workstreamCdHookStatus();
|
|
176
|
+
console.log(`CD hook: ${status.installed ? 'installed' : 'not installed'}`);
|
|
177
|
+
console.log(`Shell: ${status.shell}`);
|
|
178
|
+
console.log(`RC file: ${status.rcFile}`);
|
|
147
179
|
} else {
|
|
148
180
|
manager.workstreamList();
|
|
149
181
|
}
|
|
@@ -286,6 +318,15 @@ Workstream Commands:
|
|
|
286
318
|
workstream install-hook --codex Install for Codex CLI
|
|
287
319
|
workstream install-hook --all Install for all supported tools
|
|
288
320
|
|
|
321
|
+
Folder Auto-Activation:
|
|
322
|
+
workstream add-trigger <ws> <folder> Add trigger folder
|
|
323
|
+
workstream remove-trigger <ws> <folder> Remove trigger folder
|
|
324
|
+
workstream auto-activate <ws> [on|off|default] Set auto-activate
|
|
325
|
+
workstream check-folder [path] [--json] Check folder for matches
|
|
326
|
+
workstream install-cd-hook Install cd hook for auto-activation
|
|
327
|
+
workstream uninstall-cd-hook Remove cd hook
|
|
328
|
+
workstream cd-hook-status Check if cd hook is installed
|
|
329
|
+
|
|
289
330
|
Per-session activation (enables parallel work):
|
|
290
331
|
export CODER_WORKSTREAM=<name-or-id>
|
|
291
332
|
|
package/lib/constants.js
CHANGED
package/lib/workstreams.js
CHANGED
|
@@ -875,6 +875,371 @@ function extractFirstParagraph(content) {
|
|
|
875
875
|
return result.join(' ').replace(/\s+/g, ' ').trim();
|
|
876
876
|
}
|
|
877
877
|
|
|
878
|
+
/**
|
|
879
|
+
* Get global settings path
|
|
880
|
+
*/
|
|
881
|
+
function getSettingsPath(installDir) {
|
|
882
|
+
return path.join(installDir, 'settings.json');
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Load global settings
|
|
887
|
+
*/
|
|
888
|
+
function loadSettings(installDir) {
|
|
889
|
+
const settingsPath = getSettingsPath(installDir);
|
|
890
|
+
if (fs.existsSync(settingsPath)) {
|
|
891
|
+
try {
|
|
892
|
+
return JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
893
|
+
} catch (e) {
|
|
894
|
+
return { workstreamAutoActivate: true };
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return { workstreamAutoActivate: true };
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Save global settings
|
|
902
|
+
*/
|
|
903
|
+
function saveSettings(installDir, settings) {
|
|
904
|
+
const settingsPath = getSettingsPath(installDir);
|
|
905
|
+
const dir = path.dirname(settingsPath);
|
|
906
|
+
if (!fs.existsSync(dir)) {
|
|
907
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
908
|
+
}
|
|
909
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Add trigger folder to workstream
|
|
914
|
+
*/
|
|
915
|
+
function workstreamAddTrigger(installDir, idOrName, folderPath) {
|
|
916
|
+
const data = loadWorkstreams(installDir);
|
|
917
|
+
const ws = data.workstreams.find(
|
|
918
|
+
w => w.id === idOrName || w.name.toLowerCase() === idOrName.toLowerCase()
|
|
919
|
+
);
|
|
920
|
+
|
|
921
|
+
if (!ws) {
|
|
922
|
+
console.error(`Workstream not found: ${idOrName}`);
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const absPath = path.resolve(folderPath.replace(/^~/, process.env.HOME || ''));
|
|
927
|
+
|
|
928
|
+
if (!ws.triggerFolders) {
|
|
929
|
+
ws.triggerFolders = [];
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
if (!ws.triggerFolders.includes(absPath)) {
|
|
933
|
+
ws.triggerFolders.push(absPath);
|
|
934
|
+
ws.updatedAt = new Date().toISOString();
|
|
935
|
+
saveWorkstreams(installDir, data);
|
|
936
|
+
console.log(`✓ Added trigger folder ${path.basename(absPath)} to ${ws.name}`);
|
|
937
|
+
} else {
|
|
938
|
+
console.log(`Trigger folder already in workstream: ${path.basename(absPath)}`);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
return ws;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* Remove trigger folder from workstream
|
|
946
|
+
*/
|
|
947
|
+
function workstreamRemoveTrigger(installDir, idOrName, folderPath) {
|
|
948
|
+
const data = loadWorkstreams(installDir);
|
|
949
|
+
const ws = data.workstreams.find(
|
|
950
|
+
w => w.id === idOrName || w.name.toLowerCase() === idOrName.toLowerCase()
|
|
951
|
+
);
|
|
952
|
+
|
|
953
|
+
if (!ws) {
|
|
954
|
+
console.error(`Workstream not found: ${idOrName}`);
|
|
955
|
+
return null;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
const absPath = path.resolve(folderPath.replace(/^~/, process.env.HOME || ''));
|
|
959
|
+
|
|
960
|
+
if (!ws.triggerFolders) {
|
|
961
|
+
ws.triggerFolders = [];
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
const idx = ws.triggerFolders.indexOf(absPath);
|
|
965
|
+
|
|
966
|
+
if (idx !== -1) {
|
|
967
|
+
ws.triggerFolders.splice(idx, 1);
|
|
968
|
+
ws.updatedAt = new Date().toISOString();
|
|
969
|
+
saveWorkstreams(installDir, data);
|
|
970
|
+
console.log(`✓ Removed trigger folder ${path.basename(absPath)} from ${ws.name}`);
|
|
971
|
+
} else {
|
|
972
|
+
console.log(`Trigger folder not in workstream: ${path.basename(absPath)}`);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
return ws;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Set auto-activate for a workstream
|
|
980
|
+
* value: true, false, or null (use global default)
|
|
981
|
+
*/
|
|
982
|
+
function workstreamSetAutoActivate(installDir, idOrName, value) {
|
|
983
|
+
const data = loadWorkstreams(installDir);
|
|
984
|
+
const ws = data.workstreams.find(
|
|
985
|
+
w => w.id === idOrName || w.name.toLowerCase() === idOrName.toLowerCase()
|
|
986
|
+
);
|
|
987
|
+
|
|
988
|
+
if (!ws) {
|
|
989
|
+
console.error(`Workstream not found: ${idOrName}`);
|
|
990
|
+
return null;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
if (value === 'on' || value === true) {
|
|
994
|
+
ws.autoActivate = true;
|
|
995
|
+
console.log(`✓ Auto-activate enabled for ${ws.name}`);
|
|
996
|
+
} else if (value === 'off' || value === false) {
|
|
997
|
+
ws.autoActivate = false;
|
|
998
|
+
console.log(`✓ Auto-activate disabled for ${ws.name}`);
|
|
999
|
+
} else if (value === 'default' || value === null) {
|
|
1000
|
+
delete ws.autoActivate;
|
|
1001
|
+
console.log(`✓ Auto-activate set to global default for ${ws.name}`);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
ws.updatedAt = new Date().toISOString();
|
|
1005
|
+
saveWorkstreams(installDir, data);
|
|
1006
|
+
return ws;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* Set global auto-activate setting
|
|
1011
|
+
*/
|
|
1012
|
+
function setGlobalAutoActivate(installDir, value) {
|
|
1013
|
+
const settings = loadSettings(installDir);
|
|
1014
|
+
settings.workstreamAutoActivate = value === true || value === 'true' || value === 'on';
|
|
1015
|
+
saveSettings(installDir, settings);
|
|
1016
|
+
console.log(`✓ Global workstream auto-activate: ${settings.workstreamAutoActivate ? 'on' : 'off'}`);
|
|
1017
|
+
return settings;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Check if a workstream should auto-activate based on its setting and global default
|
|
1022
|
+
*/
|
|
1023
|
+
function shouldAutoActivate(installDir, workstream) {
|
|
1024
|
+
if (workstream.autoActivate === true) return true;
|
|
1025
|
+
if (workstream.autoActivate === false) return false;
|
|
1026
|
+
// Use global default
|
|
1027
|
+
const settings = loadSettings(installDir);
|
|
1028
|
+
return settings.workstreamAutoActivate !== false;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* Check folder for matching workstreams
|
|
1033
|
+
* Returns { count, current, matches: [{id, name, autoActivate}] }
|
|
1034
|
+
*/
|
|
1035
|
+
function workstreamCheckFolder(installDir, folderPath = process.cwd(), jsonOutput = false) {
|
|
1036
|
+
const data = loadWorkstreams(installDir);
|
|
1037
|
+
const absPath = path.resolve(folderPath.replace(/^~/, process.env.HOME || ''));
|
|
1038
|
+
|
|
1039
|
+
// Find all workstreams that match this folder
|
|
1040
|
+
const matches = data.workstreams.filter(ws => {
|
|
1041
|
+
// Check if folder is within any project
|
|
1042
|
+
const inProject = (ws.projects || []).some(p =>
|
|
1043
|
+
absPath === p || absPath.startsWith(p + path.sep) || p.startsWith(absPath + path.sep)
|
|
1044
|
+
);
|
|
1045
|
+
// Check if folder is within any trigger folder
|
|
1046
|
+
const inTrigger = (ws.triggerFolders || []).some(t =>
|
|
1047
|
+
absPath === t || absPath.startsWith(t + path.sep) || t.startsWith(absPath + path.sep)
|
|
1048
|
+
);
|
|
1049
|
+
return inProject || inTrigger;
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
// Filter to only those that should auto-activate
|
|
1053
|
+
const autoActivateMatches = matches.filter(ws => shouldAutoActivate(installDir, ws));
|
|
1054
|
+
|
|
1055
|
+
// Check if current workstream is already one of the matches
|
|
1056
|
+
const currentWs = getActiveWorkstream(installDir);
|
|
1057
|
+
const currentIsMatch = currentWs && autoActivateMatches.some(m => m.id === currentWs.id);
|
|
1058
|
+
|
|
1059
|
+
const result = {
|
|
1060
|
+
count: autoActivateMatches.length,
|
|
1061
|
+
current: currentIsMatch,
|
|
1062
|
+
matches: autoActivateMatches.map(ws => ({
|
|
1063
|
+
id: ws.id,
|
|
1064
|
+
name: ws.name,
|
|
1065
|
+
autoActivate: ws.autoActivate
|
|
1066
|
+
}))
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
if (jsonOutput) {
|
|
1070
|
+
console.log(JSON.stringify(result));
|
|
1071
|
+
} else {
|
|
1072
|
+
if (result.count === 0) {
|
|
1073
|
+
console.log('No matching workstreams for this folder');
|
|
1074
|
+
} else if (result.current) {
|
|
1075
|
+
console.log(`Current workstream "${currentWs.name}" matches this folder`);
|
|
1076
|
+
} else if (result.count === 1) {
|
|
1077
|
+
console.log(`Matching workstream: ${result.matches[0].name}`);
|
|
1078
|
+
} else {
|
|
1079
|
+
console.log(`${result.count} matching workstreams:`);
|
|
1080
|
+
result.matches.forEach((m, i) => {
|
|
1081
|
+
console.log(` ${i + 1}) ${m.name}`);
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
return result;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/**
|
|
1090
|
+
* Install the cd hook for automatic workstream activation
|
|
1091
|
+
*/
|
|
1092
|
+
function workstreamInstallCdHook() {
|
|
1093
|
+
const shell = process.env.SHELL || '/bin/zsh';
|
|
1094
|
+
const isZsh = shell.includes('zsh');
|
|
1095
|
+
const rcFile = isZsh
|
|
1096
|
+
? path.join(process.env.HOME || '', '.zshrc')
|
|
1097
|
+
: path.join(process.env.HOME || '', '.bashrc');
|
|
1098
|
+
|
|
1099
|
+
const hookMarker = '# coder-config workstream cd hook';
|
|
1100
|
+
const hookCode = `
|
|
1101
|
+
${hookMarker}
|
|
1102
|
+
coder_workstream_cd() {
|
|
1103
|
+
builtin cd "$@" || return $?
|
|
1104
|
+
|
|
1105
|
+
# Check for matching workstreams
|
|
1106
|
+
local result
|
|
1107
|
+
result=$(coder-config workstream check-folder "$PWD" --json 2>/dev/null)
|
|
1108
|
+
|
|
1109
|
+
[ -z "$result" ] && return 0
|
|
1110
|
+
|
|
1111
|
+
local count current
|
|
1112
|
+
count=$(echo "$result" | grep -o '"count":[0-9]*' | cut -d: -f2)
|
|
1113
|
+
current=$(echo "$result" | grep -o '"current":true' || echo "")
|
|
1114
|
+
|
|
1115
|
+
# Already on matching workstream
|
|
1116
|
+
[ -n "$current" ] && return 0
|
|
1117
|
+
|
|
1118
|
+
[ "$count" = "0" ] && return 0
|
|
1119
|
+
|
|
1120
|
+
if [ "$count" = "1" ]; then
|
|
1121
|
+
# Single match - auto-activate
|
|
1122
|
+
local name id
|
|
1123
|
+
name=$(echo "$result" | grep -o '"name":"[^"]*"' | head -1 | cut -d'"' -f4)
|
|
1124
|
+
id=$(echo "$result" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
|
1125
|
+
export CODER_WORKSTREAM="$id"
|
|
1126
|
+
echo "📂 Workstream: $name"
|
|
1127
|
+
elif [ "$count" -gt 1 ]; then
|
|
1128
|
+
# Multiple matches - prompt
|
|
1129
|
+
echo "Multiple workstreams match this folder:"
|
|
1130
|
+
local i=1
|
|
1131
|
+
echo "$result" | grep -o '"name":"[^"]*"' | cut -d'"' -f4 | while read -r name; do
|
|
1132
|
+
echo " $i) $name"
|
|
1133
|
+
i=$((i + 1))
|
|
1134
|
+
done
|
|
1135
|
+
echo " 0) Skip"
|
|
1136
|
+
${isZsh ? 'read "choice?Choose [0-$count]: "' : 'read -p "Choose [0-$count]: " choice'}
|
|
1137
|
+
if [ "$choice" -gt 0 ] 2>/dev/null; then
|
|
1138
|
+
local id name
|
|
1139
|
+
id=$(echo "$result" | grep -o '"id":"[^"]*"' | sed -n "${choice}p" | cut -d'"' -f4)
|
|
1140
|
+
name=$(echo "$result" | grep -o '"name":"[^"]*"' | sed -n "${choice}p" | cut -d'"' -f4)
|
|
1141
|
+
if [ -n "$id" ]; then
|
|
1142
|
+
export CODER_WORKSTREAM="$id"
|
|
1143
|
+
echo "📂 Workstream: $name"
|
|
1144
|
+
fi
|
|
1145
|
+
fi
|
|
1146
|
+
fi
|
|
1147
|
+
}
|
|
1148
|
+
alias cd='coder_workstream_cd'
|
|
1149
|
+
# end coder-config workstream cd hook
|
|
1150
|
+
`;
|
|
1151
|
+
|
|
1152
|
+
// Check if hook already installed
|
|
1153
|
+
if (fs.existsSync(rcFile)) {
|
|
1154
|
+
const content = fs.readFileSync(rcFile, 'utf8');
|
|
1155
|
+
if (content.includes(hookMarker)) {
|
|
1156
|
+
console.log('✓ Workstream cd hook already installed');
|
|
1157
|
+
console.log(` Location: ${rcFile}`);
|
|
1158
|
+
return true;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Append hook to rc file
|
|
1163
|
+
fs.appendFileSync(rcFile, hookCode);
|
|
1164
|
+
console.log(`✓ Installed workstream cd hook`);
|
|
1165
|
+
console.log(` Location: ${rcFile}`);
|
|
1166
|
+
console.log('');
|
|
1167
|
+
console.log('Restart your shell or run:');
|
|
1168
|
+
console.log(` source ${rcFile}`);
|
|
1169
|
+
|
|
1170
|
+
return true;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Uninstall the cd hook
|
|
1175
|
+
*/
|
|
1176
|
+
function workstreamUninstallCdHook() {
|
|
1177
|
+
const shell = process.env.SHELL || '/bin/zsh';
|
|
1178
|
+
const isZsh = shell.includes('zsh');
|
|
1179
|
+
const rcFile = isZsh
|
|
1180
|
+
? path.join(process.env.HOME || '', '.zshrc')
|
|
1181
|
+
: path.join(process.env.HOME || '', '.bashrc');
|
|
1182
|
+
|
|
1183
|
+
if (!fs.existsSync(rcFile)) {
|
|
1184
|
+
console.log('Shell config file not found');
|
|
1185
|
+
return false;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
const content = fs.readFileSync(rcFile, 'utf8');
|
|
1189
|
+
const hookMarker = '# coder-config workstream cd hook';
|
|
1190
|
+
const endMarker = '# end coder-config workstream cd hook';
|
|
1191
|
+
|
|
1192
|
+
if (!content.includes(hookMarker)) {
|
|
1193
|
+
console.log('Workstream cd hook not installed');
|
|
1194
|
+
return false;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// Remove the hook block
|
|
1198
|
+
const startIdx = content.indexOf(hookMarker);
|
|
1199
|
+
const endIdx = content.indexOf(endMarker);
|
|
1200
|
+
|
|
1201
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
1202
|
+
console.error('Could not find hook boundaries');
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// Find the newline before the marker (to remove leading blank line)
|
|
1207
|
+
let removeStart = startIdx;
|
|
1208
|
+
if (startIdx > 0 && content[startIdx - 1] === '\n') {
|
|
1209
|
+
removeStart = startIdx - 1;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
const newContent = content.slice(0, removeStart) + content.slice(endIdx + endMarker.length);
|
|
1213
|
+
fs.writeFileSync(rcFile, newContent);
|
|
1214
|
+
|
|
1215
|
+
console.log(`✓ Uninstalled workstream cd hook from ${rcFile}`);
|
|
1216
|
+
console.log('');
|
|
1217
|
+
console.log('Restart your shell or run:');
|
|
1218
|
+
console.log(` source ${rcFile}`);
|
|
1219
|
+
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* Check if cd hook is installed
|
|
1225
|
+
*/
|
|
1226
|
+
function workstreamCdHookStatus() {
|
|
1227
|
+
const shell = process.env.SHELL || '/bin/zsh';
|
|
1228
|
+
const isZsh = shell.includes('zsh');
|
|
1229
|
+
const rcFile = isZsh
|
|
1230
|
+
? path.join(process.env.HOME || '', '.zshrc')
|
|
1231
|
+
: path.join(process.env.HOME || '', '.bashrc');
|
|
1232
|
+
|
|
1233
|
+
if (!fs.existsSync(rcFile)) {
|
|
1234
|
+
return { installed: false, shell: isZsh ? 'zsh' : 'bash', rcFile };
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const content = fs.readFileSync(rcFile, 'utf8');
|
|
1238
|
+
const installed = content.includes('# coder-config workstream cd hook');
|
|
1239
|
+
|
|
1240
|
+
return { installed, shell: isZsh ? 'zsh' : 'bash', rcFile };
|
|
1241
|
+
}
|
|
1242
|
+
|
|
878
1243
|
module.exports = {
|
|
879
1244
|
getWorkstreamsPath,
|
|
880
1245
|
loadWorkstreams,
|
|
@@ -899,4 +1264,17 @@ module.exports = {
|
|
|
899
1264
|
workstreamCheckPath,
|
|
900
1265
|
generateRulesFromRepos,
|
|
901
1266
|
generateRulesWithClaude,
|
|
1267
|
+
// New folder auto-activation functions
|
|
1268
|
+
getSettingsPath,
|
|
1269
|
+
loadSettings,
|
|
1270
|
+
saveSettings,
|
|
1271
|
+
workstreamAddTrigger,
|
|
1272
|
+
workstreamRemoveTrigger,
|
|
1273
|
+
workstreamSetAutoActivate,
|
|
1274
|
+
setGlobalAutoActivate,
|
|
1275
|
+
shouldAutoActivate,
|
|
1276
|
+
workstreamCheckFolder,
|
|
1277
|
+
workstreamInstallCdHook,
|
|
1278
|
+
workstreamUninstallCdHook,
|
|
1279
|
+
workstreamCdHookStatus,
|
|
902
1280
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coder-config",
|
|
3
|
-
"version": "0.42.
|
|
3
|
+
"version": "0.42.15",
|
|
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",
|