@regression-io/claude-config 0.37.16 → 0.37.19

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 CHANGED
@@ -96,6 +96,7 @@ claude-config workstream add <ws> <path> # Add project to workstream
96
96
  claude-config workstream remove <ws> <path> # Remove project from workstream
97
97
  claude-config workstream inject [--silent] # Output restriction + context (for hooks)
98
98
  claude-config workstream detect [path] # Detect workstream for directory
99
+ claude-config workstream check-path <path> # Check if path is within workstream (exit 0/1)
99
100
  claude-config workstream install-hook # Install pre-prompt hook for injection
100
101
  ```
101
102
 
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, workstreamDeactivate } = require('./lib/workstreams');
31
+ const { getWorkstreamsPath, loadWorkstreams, saveWorkstreams, workstreamList, workstreamCreate, workstreamUpdate, workstreamDelete, workstreamUse, workstreamActive, workstreamAddProject, workstreamRemoveProject, workstreamInject, workstreamDetect, workstreamGet, getActiveWorkstream, countWorkstreamsForProject, workstreamInstallHook, workstreamDeactivate, workstreamCheckPath } = require('./lib/workstreams');
32
32
  const { getActivityPath, getDefaultActivity, loadActivity, saveActivity, detectProjectRoot, activityLog, activitySummary, generateWorkstreamName, activitySuggestWorkstreams, activityClear } = require('./lib/activity');
33
33
  const { getSmartSyncPath, loadSmartSyncPrefs, saveSmartSyncPrefs, smartSyncRememberChoice, smartSyncDismissNudge, smartSyncUpdateSettings, smartSyncDetect, smartSyncCheckNudge, smartSyncHandleAction, smartSyncStatus } = require('./lib/smart-sync');
34
34
  const { runCli } = require('./lib/cli');
@@ -132,6 +132,7 @@ class ClaudeConfigManager {
132
132
  countWorkstreamsForProject(projectPath) { return countWorkstreamsForProject(this.installDir, projectPath); }
133
133
  workstreamInstallHook() { return workstreamInstallHook(); }
134
134
  workstreamDeactivate() { return workstreamDeactivate(); }
135
+ workstreamCheckPath(targetPath, silent) { return workstreamCheckPath(this.installDir, targetPath, silent); }
135
136
 
136
137
  // Activity
137
138
  getActivityPath() { return getActivityPath(this.installDir); }
package/lib/cli.js CHANGED
@@ -122,6 +122,15 @@ function runCli(manager) {
122
122
  manager.workstreamInstallHook();
123
123
  } else if (args[1] === 'deactivate') {
124
124
  manager.workstreamDeactivate();
125
+ } else if (args[1] === 'check-path') {
126
+ const targetPath = args[2];
127
+ if (!targetPath) {
128
+ console.error('Usage: claude-config workstream check-path <path>');
129
+ process.exit(1);
130
+ }
131
+ const silent = args.includes('--silent') || args.includes('-s');
132
+ const isValid = manager.workstreamCheckPath(targetPath, silent);
133
+ process.exit(isValid ? 0 : 1);
125
134
  } else {
126
135
  manager.workstreamList();
127
136
  }
package/lib/constants.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Constants and tool path configurations
3
3
  */
4
4
 
5
- const VERSION = '0.37.16';
5
+ const VERSION = '0.37.19';
6
6
 
7
7
  // Tool-specific path configurations
8
8
  const TOOL_PATHS = {
@@ -442,6 +442,49 @@ function workstreamDeactivate() {
442
442
  return true;
443
443
  }
444
444
 
445
+ /**
446
+ * Check if a path is within the active workstream's directories
447
+ * Used by pre-tool-call hooks for enforcement
448
+ * Returns true if path is valid, false otherwise
449
+ */
450
+ function workstreamCheckPath(installDir, targetPath, silent = false) {
451
+ const active = getActiveWorkstream(installDir);
452
+
453
+ // No active workstream = all paths allowed
454
+ if (!active) {
455
+ return true;
456
+ }
457
+
458
+ // No projects in workstream = all paths allowed
459
+ if (!active.projects || active.projects.length === 0) {
460
+ return true;
461
+ }
462
+
463
+ // Resolve the target path
464
+ const absPath = path.resolve(targetPath.replace(/^~/, process.env.HOME || ''));
465
+
466
+ // Check if path is within any of the workstream's directories
467
+ const isWithin = active.projects.some(projectPath => {
468
+ // Path is within if it starts with the project path
469
+ // Handle both exact match and subdirectories
470
+ return absPath === projectPath || absPath.startsWith(projectPath + path.sep);
471
+ });
472
+
473
+ if (!silent) {
474
+ if (isWithin) {
475
+ console.log(`✓ Path is within workstream "${active.name}"`);
476
+ } else {
477
+ console.error(`✗ Path is outside workstream "${active.name}"`);
478
+ console.error(` Allowed directories:`);
479
+ for (const p of active.projects) {
480
+ console.error(` - ${p.replace(process.env.HOME || '', '~')}`);
481
+ }
482
+ }
483
+ }
484
+
485
+ return isWithin;
486
+ }
487
+
445
488
  module.exports = {
446
489
  getWorkstreamsPath,
447
490
  loadWorkstreams,
@@ -461,4 +504,5 @@ module.exports = {
461
504
  countWorkstreamsForProject,
462
505
  workstreamInstallHook,
463
506
  workstreamDeactivate,
507
+ workstreamCheckPath,
464
508
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@regression-io/claude-config",
3
- "version": "0.37.16",
3
+ "version": "0.37.19",
4
4
  "description": "Configuration management UI for Claude Code and Antigravity - manage MCPs, rules, commands, memory, and project folders",
5
5
  "author": "regression.io",
6
6
  "main": "config-loader.js",
@@ -36,7 +36,8 @@ function getVersionFromFile(filePath) {
36
36
  }
37
37
 
38
38
  /**
39
- * Fetch npm version
39
+ * Fetch npm version and verify it's installable
40
+ * Returns version only if the tarball is accessible (CDN propagated)
40
41
  */
41
42
  function fetchNpmVersion() {
42
43
  return new Promise((resolve) => {
@@ -47,7 +48,37 @@ function fetchNpmVersion() {
47
48
  res.on('end', () => {
48
49
  try {
49
50
  const parsed = JSON.parse(data);
50
- resolve(parsed.version || null);
51
+ const version = parsed.version;
52
+ const tarballUrl = parsed.dist?.tarball;
53
+
54
+ if (!version || !tarballUrl) {
55
+ resolve(null);
56
+ return;
57
+ }
58
+
59
+ // Verify tarball is accessible (HEAD request)
60
+ const tarball = new URL(tarballUrl);
61
+ const options = {
62
+ hostname: tarball.hostname,
63
+ path: tarball.pathname,
64
+ method: 'HEAD',
65
+ timeout: 5000
66
+ };
67
+
68
+ const verifyReq = https.request(options, (verifyRes) => {
69
+ // 200 = accessible, anything else = not yet propagated
70
+ if (verifyRes.statusCode === 200) {
71
+ resolve(version);
72
+ } else {
73
+ resolve(null);
74
+ }
75
+ });
76
+ verifyReq.on('error', () => resolve(null));
77
+ verifyReq.on('timeout', () => {
78
+ verifyReq.destroy();
79
+ resolve(null);
80
+ });
81
+ verifyReq.end();
51
82
  } catch (e) {
52
83
  resolve(null);
53
84
  }
@@ -112,21 +143,41 @@ async function checkForUpdates(manager, dirname) {
112
143
  * Perform npm update
113
144
  */
114
145
  async function performNpmUpdate(targetVersion) {
115
- try {
116
- // Use npm install @latest instead of npm update for reliable updates
117
- execSync('npm install -g @regression-io/claude-config@latest', {
118
- stdio: 'pipe',
119
- timeout: 120000
120
- });
146
+ const maxRetries = 3;
147
+ const retryDelayMs = 5000;
148
+
149
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
150
+ try {
151
+ // Use npm install @latest instead of npm update for reliable updates
152
+ execSync('npm install -g @regression-io/claude-config@latest', {
153
+ stdio: 'pipe',
154
+ timeout: 120000
155
+ });
121
156
 
122
- return {
123
- success: true,
124
- updateMethod: 'npm',
125
- newVersion: targetVersion || 'latest',
126
- message: 'Updated via npm. Please restart the UI to use the new version.'
127
- };
128
- } catch (error) {
129
- return { success: false, error: error.message };
157
+ return {
158
+ success: true,
159
+ updateMethod: 'npm',
160
+ newVersion: targetVersion || 'latest',
161
+ message: 'Updated via npm. Please restart the UI to use the new version.'
162
+ };
163
+ } catch (error) {
164
+ const isEtarget = error.message.includes('ETARGET') || error.message.includes('No matching version');
165
+
166
+ // Retry on ETARGET (CDN propagation delay) if we have retries left
167
+ if (isEtarget && attempt < maxRetries) {
168
+ await new Promise(resolve => setTimeout(resolve, retryDelayMs));
169
+ continue;
170
+ }
171
+
172
+ // Final attempt failed or non-retryable error
173
+ if (isEtarget) {
174
+ return {
175
+ success: false,
176
+ error: `Version not yet available on npm CDN. Please try again in a minute. (${error.message})`
177
+ };
178
+ }
179
+ return { success: false, error: error.message };
180
+ }
130
181
  }
131
182
  }
132
183