coder-config 0.40.1

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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +553 -0
  3. package/cli.js +431 -0
  4. package/config-loader.js +294 -0
  5. package/hooks/activity-track.sh +56 -0
  6. package/hooks/codex-workstream.sh +44 -0
  7. package/hooks/gemini-workstream.sh +44 -0
  8. package/hooks/workstream-inject.sh +20 -0
  9. package/lib/activity.js +283 -0
  10. package/lib/apply.js +344 -0
  11. package/lib/cli.js +267 -0
  12. package/lib/config.js +171 -0
  13. package/lib/constants.js +55 -0
  14. package/lib/env.js +114 -0
  15. package/lib/index.js +47 -0
  16. package/lib/init.js +122 -0
  17. package/lib/mcps.js +139 -0
  18. package/lib/memory.js +201 -0
  19. package/lib/projects.js +138 -0
  20. package/lib/registry.js +83 -0
  21. package/lib/utils.js +129 -0
  22. package/lib/workstreams.js +652 -0
  23. package/package.json +80 -0
  24. package/scripts/capture-screenshots.js +142 -0
  25. package/scripts/postinstall.js +122 -0
  26. package/scripts/release.sh +71 -0
  27. package/scripts/sync-version.js +77 -0
  28. package/scripts/tauri-prepare.js +328 -0
  29. package/shared/mcp-registry.json +76 -0
  30. package/ui/dist/assets/index-DbZ3_HBD.js +3204 -0
  31. package/ui/dist/assets/index-DjLdm3Mr.css +32 -0
  32. package/ui/dist/icons/icon-192.svg +16 -0
  33. package/ui/dist/icons/icon-512.svg +16 -0
  34. package/ui/dist/index.html +39 -0
  35. package/ui/dist/manifest.json +25 -0
  36. package/ui/dist/sw.js +24 -0
  37. package/ui/dist/tutorial/claude-settings.png +0 -0
  38. package/ui/dist/tutorial/header.png +0 -0
  39. package/ui/dist/tutorial/mcp-registry.png +0 -0
  40. package/ui/dist/tutorial/memory-view.png +0 -0
  41. package/ui/dist/tutorial/permissions.png +0 -0
  42. package/ui/dist/tutorial/plugins-view.png +0 -0
  43. package/ui/dist/tutorial/project-explorer.png +0 -0
  44. package/ui/dist/tutorial/projects-view.png +0 -0
  45. package/ui/dist/tutorial/sidebar.png +0 -0
  46. package/ui/dist/tutorial/tutorial-view.png +0 -0
  47. package/ui/dist/tutorial/workstreams-view.png +0 -0
  48. package/ui/routes/activity.js +58 -0
  49. package/ui/routes/commands.js +74 -0
  50. package/ui/routes/configs.js +329 -0
  51. package/ui/routes/env.js +40 -0
  52. package/ui/routes/file-explorer.js +668 -0
  53. package/ui/routes/index.js +41 -0
  54. package/ui/routes/mcp-discovery.js +235 -0
  55. package/ui/routes/memory.js +385 -0
  56. package/ui/routes/package.json +3 -0
  57. package/ui/routes/plugins.js +466 -0
  58. package/ui/routes/projects.js +198 -0
  59. package/ui/routes/registry.js +30 -0
  60. package/ui/routes/rules.js +74 -0
  61. package/ui/routes/search.js +125 -0
  62. package/ui/routes/settings.js +381 -0
  63. package/ui/routes/subprojects.js +208 -0
  64. package/ui/routes/tool-sync.js +127 -0
  65. package/ui/routes/updates.js +339 -0
  66. package/ui/routes/workstreams.js +224 -0
  67. package/ui/server.cjs +773 -0
  68. package/ui/terminal-server.cjs +160 -0
@@ -0,0 +1,294 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Claude Code Configuration Loader
5
+ *
6
+ * Uses standard JSON format throughout - no custom YAML.
7
+ * Copy/paste MCP configs from anywhere.
8
+ *
9
+ * Files:
10
+ * ~/.claude-config/mcp-registry.json - All available MCPs (copy/paste friendly)
11
+ * project/.claude/mcps.json - Which MCPs this project uses
12
+ * project/.claude/rules/*.md - Project rules
13
+ * project/.claude/commands/*.md - Project commands
14
+ * project/.mcp.json - Generated output for Claude Code
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+
20
+ // Import from modular lib
21
+ const { VERSION, TOOL_PATHS } = require('./lib/constants');
22
+ const { loadJson, saveJson, loadEnvFile, interpolate, resolveEnvVars, copyDirRecursive } = require('./lib/utils');
23
+ const { findProjectRoot, findAllConfigs, mergeConfigs, getConfigPath, collectFilesFromHierarchy, findAllConfigsForTool } = require('./lib/config');
24
+ const { apply, applyForAntigravity, applyForGemini, detectInstalledTools, applyForTools } = require('./lib/apply');
25
+ const { list, add, remove } = require('./lib/mcps');
26
+ const { registryList, registryAdd, registryRemove } = require('./lib/registry');
27
+ const { init, show } = require('./lib/init');
28
+ const { memoryList, memoryInit, memoryAdd, memorySearch } = require('./lib/memory');
29
+ const { envList, envSet, envUnset } = require('./lib/env');
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');
32
+ const { getActivityPath, getDefaultActivity, loadActivity, saveActivity, detectProjectRoot, activityLog, activitySummary, generateWorkstreamName, activitySuggestWorkstreams, activityClear } = require('./lib/activity');
33
+ const { runCli } = require('./lib/cli');
34
+
35
+ class ClaudeConfigManager {
36
+ constructor() {
37
+ this.installDir = process.env.CLAUDE_CONFIG_HOME || path.join(process.env.HOME || '', '.claude-config');
38
+
39
+ // Look for registry in multiple places
40
+ const possiblePaths = [
41
+ path.join(__dirname, 'shared', 'mcp-registry.json'),
42
+ path.join(__dirname, 'mcp-registry.json'),
43
+ path.join(this.installDir, 'shared', 'mcp-registry.json')
44
+ ];
45
+ this.registryPath = possiblePaths.find(p => fs.existsSync(p)) || possiblePaths[0];
46
+ }
47
+
48
+ // Utils
49
+ loadJson(filePath) { return loadJson(filePath); }
50
+ saveJson(filePath, data) { return saveJson(filePath, data); }
51
+ loadEnvFile(envPath) { return loadEnvFile(envPath); }
52
+ interpolate(obj, env) { return interpolate(obj, env); }
53
+ resolveEnvVars(obj, env) { return resolveEnvVars(obj, env); }
54
+ copyDirRecursive(src, dest) { return copyDirRecursive(src, dest); }
55
+
56
+ // Config
57
+ findProjectRoot(startDir) { return findProjectRoot(startDir); }
58
+ findAllConfigs(startDir) { return findAllConfigs(startDir); }
59
+ mergeConfigs(configs) { return mergeConfigs(configs); }
60
+ getConfigPath(projectDir) { return getConfigPath(this.installDir, projectDir); }
61
+ collectFilesFromHierarchy(configLocations, subdir) { return collectFilesFromHierarchy(configLocations, subdir); }
62
+ findAllConfigsForTool(toolId, startDir) { return findAllConfigsForTool(toolId, startDir); }
63
+
64
+ getAllRules(startDir = process.cwd()) {
65
+ const configLocations = findAllConfigs(startDir);
66
+ return collectFilesFromHierarchy(configLocations, 'rules');
67
+ }
68
+
69
+ getAllCommands(startDir = process.cwd()) {
70
+ const configLocations = findAllConfigs(startDir);
71
+ return collectFilesFromHierarchy(configLocations, 'commands');
72
+ }
73
+
74
+ // Apply
75
+ apply(projectDir) { return apply(this.registryPath, projectDir); }
76
+ applyForAntigravity(projectDir) { return applyForAntigravity(this.registryPath, projectDir); }
77
+ applyForGemini(projectDir) { return applyForGemini(this.registryPath, projectDir); }
78
+ detectInstalledTools() { return detectInstalledTools(); }
79
+ getToolPaths() { return TOOL_PATHS; }
80
+ applyForTools(projectDir, tools) { return applyForTools(this.registryPath, projectDir, tools); }
81
+
82
+ // MCPs
83
+ list() { return list(this.registryPath); }
84
+ add(mcpNames) { return add(this.registryPath, this.installDir, mcpNames); }
85
+ remove(mcpNames) { return remove(this.installDir, mcpNames); }
86
+
87
+ // Registry
88
+ registryList() { return registryList(this.registryPath); }
89
+ registryAdd(name, configJson) { return registryAdd(this.registryPath, name, configJson); }
90
+ registryRemove(name) { return registryRemove(this.registryPath, name); }
91
+
92
+ // Init
93
+ init(projectDir) { return init(this.registryPath, projectDir); }
94
+ show(projectDir) { return show(projectDir); }
95
+
96
+ // Memory
97
+ memoryList(projectDir) { return memoryList(projectDir); }
98
+ memoryInit(projectDir) { return memoryInit(projectDir); }
99
+ memoryAdd(type, content, projectDir) { return memoryAdd(type, content, projectDir); }
100
+ memorySearch(query, projectDir) { return memorySearch(query, projectDir); }
101
+
102
+ // Env
103
+ envList(projectDir) { return envList(projectDir); }
104
+ envSet(key, value, projectDir) { return envSet(key, value, projectDir); }
105
+ envUnset(key, projectDir) { return envUnset(key, projectDir); }
106
+
107
+ // Projects
108
+ getProjectsRegistryPath() { return getProjectsRegistryPath(this.installDir); }
109
+ loadProjectsRegistry() { return loadProjectsRegistry(this.installDir); }
110
+ saveProjectsRegistry(registry) { return saveProjectsRegistry(this.installDir, registry); }
111
+ projectList() { return projectList(this.installDir); }
112
+ projectAdd(projectPath, name) { return projectAdd(this.installDir, projectPath, name); }
113
+ projectRemove(nameOrPath) { return projectRemove(this.installDir, nameOrPath); }
114
+
115
+ // Workstreams
116
+ getWorkstreamsPath() { return getWorkstreamsPath(this.installDir); }
117
+ loadWorkstreams() { return loadWorkstreams(this.installDir); }
118
+ saveWorkstreams(data) { return saveWorkstreams(this.installDir, data); }
119
+ workstreamList() { return workstreamList(this.installDir); }
120
+ workstreamCreate(name, projects, rules) { return workstreamCreate(this.installDir, name, projects, rules); }
121
+ workstreamUpdate(idOrName, updates) { return workstreamUpdate(this.installDir, idOrName, updates); }
122
+ workstreamDelete(idOrName) { return workstreamDelete(this.installDir, idOrName); }
123
+ workstreamUse(idOrName) { return workstreamUse(this.installDir, idOrName); }
124
+ workstreamActive() { return workstreamActive(this.installDir); }
125
+ workstreamAddProject(idOrName, projectPath) { return workstreamAddProject(this.installDir, idOrName, projectPath); }
126
+ workstreamRemoveProject(idOrName, projectPath) { return workstreamRemoveProject(this.installDir, idOrName, projectPath); }
127
+ workstreamInject(silent) { return workstreamInject(this.installDir, silent); }
128
+ workstreamDetect(dir) { return workstreamDetect(this.installDir, dir); }
129
+ workstreamGet(id) { return workstreamGet(this.installDir, id); }
130
+ getActiveWorkstream() { return getActiveWorkstream(this.installDir); }
131
+ countWorkstreamsForProject(projectPath) { return countWorkstreamsForProject(this.installDir, projectPath); }
132
+ workstreamInstallHook() { return workstreamInstallHook(); }
133
+ workstreamInstallHookGemini() { return workstreamInstallHookGemini(); }
134
+ workstreamInstallHookCodex() { return workstreamInstallHookCodex(); }
135
+ workstreamDeactivate() { return workstreamDeactivate(); }
136
+ workstreamCheckPath(targetPath, silent) { return workstreamCheckPath(this.installDir, targetPath, silent); }
137
+
138
+ // Activity
139
+ getActivityPath() { return getActivityPath(this.installDir); }
140
+ loadActivity() { return loadActivity(this.installDir); }
141
+ getDefaultActivity() { return getDefaultActivity(); }
142
+ saveActivity(data) { return saveActivity(this.installDir, data); }
143
+ detectProjectRoot(filePath) { return detectProjectRoot(filePath); }
144
+ activityLog(files, sessionId) { return activityLog(this.installDir, files, sessionId); }
145
+ activitySummary() { return activitySummary(this.installDir); }
146
+ generateWorkstreamName(projects) { return generateWorkstreamName(projects); }
147
+ activitySuggestWorkstreams() { return activitySuggestWorkstreams(this.installDir); }
148
+ activityClear(olderThanDays) { return activityClear(this.installDir, olderThanDays); }
149
+
150
+ // Update - check npm for updates or update from local source
151
+ async update(args = []) {
152
+ const https = require('https');
153
+ const { execSync } = require('child_process');
154
+
155
+ // Parse args
156
+ const checkOnly = args.includes('--check') || args.includes('-c');
157
+ const sourcePath = args.find(a => !a.startsWith('-'));
158
+
159
+ // If source path provided, use local update
160
+ if (sourcePath) {
161
+ return this._updateFromLocal(sourcePath);
162
+ }
163
+
164
+ // Check npm for updates
165
+ console.log('Checking for updates...');
166
+
167
+ const npmVersion = await this._fetchNpmVersion(https);
168
+ if (!npmVersion) {
169
+ console.error('Failed to check npm registry');
170
+ return false;
171
+ }
172
+
173
+ const isNewer = this._isNewerVersion(npmVersion, VERSION);
174
+
175
+ if (!isNewer) {
176
+ console.log(`✓ You're up to date (v${VERSION})`);
177
+ return true;
178
+ }
179
+
180
+ console.log(`\nUpdate available: v${VERSION} → v${npmVersion}`);
181
+
182
+ if (checkOnly) {
183
+ console.log('\nRun "claude-config update" to install the update.');
184
+ return true;
185
+ }
186
+
187
+ // Perform npm update
188
+ console.log('\nUpdating via npm...');
189
+ try {
190
+ execSync('npm install -g @regression-io/claude-config@latest', {
191
+ stdio: 'inherit',
192
+ timeout: 120000
193
+ });
194
+ console.log(`\n✅ Updated to v${npmVersion}`);
195
+ console.log('Run "claude-config ui" to restart the UI with the new version.');
196
+ return true;
197
+ } catch (error) {
198
+ console.error('Update failed:', error.message);
199
+ console.log('\nTry manually: npm install -g @regression-io/claude-config@latest');
200
+ return false;
201
+ }
202
+ }
203
+
204
+ // Fetch latest version from npm registry
205
+ _fetchNpmVersion(https) {
206
+ return new Promise((resolve) => {
207
+ const url = 'https://registry.npmjs.org/@regression-io/claude-config/latest';
208
+ https.get(url, (res) => {
209
+ let data = '';
210
+ res.on('data', chunk => data += chunk);
211
+ res.on('end', () => {
212
+ try {
213
+ const parsed = JSON.parse(data);
214
+ resolve(parsed.version || null);
215
+ } catch (e) {
216
+ resolve(null);
217
+ }
218
+ });
219
+ }).on('error', () => resolve(null));
220
+ });
221
+ }
222
+
223
+ // Compare semver versions
224
+ _isNewerVersion(source, installed) {
225
+ if (!source || !installed) return false;
226
+ const parseVersion = (v) => v.split('.').map(n => parseInt(n, 10) || 0);
227
+ const s = parseVersion(source);
228
+ const i = parseVersion(installed);
229
+ for (let j = 0; j < Math.max(s.length, i.length); j++) {
230
+ const sv = s[j] || 0;
231
+ const iv = i[j] || 0;
232
+ if (sv > iv) return true;
233
+ if (sv < iv) return false;
234
+ }
235
+ return false;
236
+ }
237
+
238
+ // Update from local source directory
239
+ _updateFromLocal(sourcePath) {
240
+ if (!fs.existsSync(sourcePath)) {
241
+ console.error(`Source not found: ${sourcePath}`);
242
+ return false;
243
+ }
244
+
245
+ const files = [
246
+ 'config-loader.js',
247
+ 'shared/mcp-registry.json',
248
+ 'shell/claude-config.zsh'
249
+ ];
250
+
251
+ let updated = 0;
252
+ for (const file of files) {
253
+ const src = path.join(sourcePath, file);
254
+ const dest = path.join(this.installDir, file);
255
+
256
+ if (fs.existsSync(src)) {
257
+ const destDir = path.dirname(dest);
258
+ if (!fs.existsSync(destDir)) {
259
+ fs.mkdirSync(destDir, { recursive: true });
260
+ }
261
+ fs.copyFileSync(src, dest);
262
+ console.log(`✓ Updated ${file}`);
263
+ updated++;
264
+ }
265
+ }
266
+
267
+ if (updated > 0) {
268
+ console.log(`\n✅ Updated ${updated} item(s)`);
269
+ console.log('Restart your shell or run: source ~/.zshrc');
270
+ } else {
271
+ console.log('No files found to update');
272
+ }
273
+
274
+ return updated > 0;
275
+ }
276
+
277
+ // Version
278
+ version() {
279
+ console.log(`claude-config v${VERSION}`);
280
+ console.log(`Install: ${this.installDir}`);
281
+ console.log(`Registry: ${this.registryPath}`);
282
+ }
283
+ }
284
+
285
+ // =============================================================================
286
+ // CLI
287
+ // =============================================================================
288
+
289
+ if (require.main === module) {
290
+ const manager = new ClaudeConfigManager();
291
+ runCli(manager);
292
+ }
293
+
294
+ module.exports = ClaudeConfigManager;
@@ -0,0 +1,56 @@
1
+ #!/bin/bash
2
+ # Activity Tracking Hook for Claude Code
3
+ # Install: Add to ~/.claude/hooks/post-response.sh
4
+ #
5
+ # This hook tracks file activity to help suggest workstreams based on usage patterns.
6
+ # It extracts file paths from Claude's response and logs them to the activity tracker.
7
+
8
+ # Exit silently if claude-config isn't available
9
+ if ! command -v claude-config &> /dev/null; then
10
+ exit 0
11
+ fi
12
+
13
+ # Generate session ID from current terminal session
14
+ SESSION_ID="${CLAUDE_SESSION_ID:-$(date +%Y%m%d)-$$}"
15
+
16
+ # The hook receives the response as stdin
17
+ # We extract file paths mentioned in tool calls (Read, Edit, Write, Grep, Glob results)
18
+ response=$(cat)
19
+
20
+ # Extract file paths from the response
21
+ # Look for patterns like:
22
+ # - "file_path": "/path/to/file"
23
+ # - Read file: /path/to/file
24
+ # - Edit file: /path/to/file
25
+ # - paths from glob/grep results
26
+
27
+ files=$(echo "$response" | grep -oE '(/[a-zA-Z0-9_/.~-]+\.[a-zA-Z0-9]+)' | sort -u | head -50)
28
+
29
+ if [ -n "$files" ]; then
30
+ # Convert to JSON array
31
+ json_files="["
32
+ first=true
33
+ while IFS= read -r file; do
34
+ # Skip common non-file patterns
35
+ [[ "$file" == *"/api/"* ]] && continue
36
+ [[ "$file" == *"/ws/"* ]] && continue
37
+ [[ "$file" == *".com"* ]] && continue
38
+ [[ "$file" == *".org"* ]] && continue
39
+
40
+ if [ "$first" = true ]; then
41
+ first=false
42
+ else
43
+ json_files+=","
44
+ fi
45
+ json_files+="\"$file\""
46
+ done <<< "$files"
47
+ json_files+="]"
48
+
49
+ # Log to activity tracker via API (run in background to not block)
50
+ curl -s -X POST "http://localhost:3333/api/activity/log" \
51
+ -H "Content-Type: application/json" \
52
+ -d "{\"files\": $json_files, \"sessionId\": \"$SESSION_ID\"}" > /dev/null 2>&1 &
53
+ fi
54
+
55
+ # Pass through the response unchanged
56
+ echo "$response"
@@ -0,0 +1,44 @@
1
+ #!/bin/bash
2
+ # Codex CLI Workstream Injection Hook
3
+ # Install: claude-config workstream install-hook --codex
4
+ #
5
+ # This hook injects workstream context at session start, restricting
6
+ # the agent to work within specified directories.
7
+
8
+ # Exit silently if claude-config isn't available
9
+ if ! command -v claude-config &> /dev/null; then
10
+ echo '{"decision": "allow"}'
11
+ exit 0
12
+ fi
13
+
14
+ # Read input from stdin (Codex passes JSON)
15
+ input=$(cat)
16
+
17
+ # Check for active workstream via env var
18
+ if [ -z "$CLAUDE_WORKSTREAM" ]; then
19
+ # No workstream active, allow without context
20
+ echo '{"decision": "allow"}'
21
+ exit 0
22
+ fi
23
+
24
+ # Get workstream injection content
25
+ context=$(claude-config workstream inject --silent 2>/dev/null)
26
+
27
+ if [ -n "$context" ]; then
28
+ # Escape the context for JSON
29
+ escaped_context=$(echo "$context" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')
30
+
31
+ # Return with additional context to inject
32
+ cat <<EOF
33
+ {
34
+ "decision": "allow",
35
+ "hookSpecificOutput": {
36
+ "additionalContext": $escaped_context
37
+ }
38
+ }
39
+ EOF
40
+ else
41
+ echo '{"decision": "allow"}'
42
+ fi
43
+
44
+ exit 0
@@ -0,0 +1,44 @@
1
+ #!/bin/bash
2
+ # Gemini CLI Workstream Injection Hook
3
+ # Install: Add to ~/.gemini/settings.json hooks configuration
4
+ #
5
+ # This hook injects workstream context at session start, restricting
6
+ # the agent to work within specified directories.
7
+
8
+ # Exit silently if claude-config isn't available
9
+ if ! command -v claude-config &> /dev/null; then
10
+ echo '{"decision": "allow"}'
11
+ exit 0
12
+ fi
13
+
14
+ # Read input from stdin (Gemini passes JSON)
15
+ input=$(cat)
16
+
17
+ # Check for active workstream via env var
18
+ if [ -z "$CLAUDE_WORKSTREAM" ]; then
19
+ # No workstream active, allow without context
20
+ echo '{"decision": "allow"}'
21
+ exit 0
22
+ fi
23
+
24
+ # Get workstream injection content
25
+ context=$(claude-config workstream inject --silent 2>/dev/null)
26
+
27
+ if [ -n "$context" ]; then
28
+ # Escape the context for JSON
29
+ escaped_context=$(echo "$context" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')
30
+
31
+ # Return with additional context to inject
32
+ cat <<EOF
33
+ {
34
+ "decision": "allow",
35
+ "hookSpecificOutput": {
36
+ "additionalContext": $escaped_context
37
+ }
38
+ }
39
+ EOF
40
+ else
41
+ echo '{"decision": "allow"}'
42
+ fi
43
+
44
+ exit 0
@@ -0,0 +1,20 @@
1
+ #!/bin/bash
2
+ # Workstream Rule Injection Hook for Claude Code
3
+ #
4
+ # This hook injects the active workstream's rules into every Claude session.
5
+ #
6
+ # Installation:
7
+ # 1. Copy this file to ~/.claude/hooks/pre-prompt.sh (or add to existing)
8
+ # 2. Make it executable: chmod +x ~/.claude/hooks/pre-prompt.sh
9
+ # 3. Set active workstream: claude-config workstream use "My Workstream"
10
+ #
11
+ # The rules will be prepended to Claude's context automatically.
12
+
13
+ # Check if claude-config is available
14
+ if ! command -v claude-config &> /dev/null; then
15
+ exit 0
16
+ fi
17
+
18
+ # Inject workstream rules silently
19
+ # The --silent flag suppresses "No active workstream" messages
20
+ claude-config workstream inject --silent