@regression-io/claude-config 0.37.21 → 0.37.25
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/cli.js +180 -10
- package/lib/cli.js +5 -1
- package/lib/constants.js +1 -1
- package/package.json +1 -1
- package/ui/routes/updates.js +24 -6
- package/ui/server.cjs +7 -2
package/cli.js
CHANGED
|
@@ -18,14 +18,22 @@ const command = args[0] || '';
|
|
|
18
18
|
// PID file for daemon mode
|
|
19
19
|
const PID_FILE = path.join(os.homedir(), '.claude-config', 'ui.pid');
|
|
20
20
|
|
|
21
|
+
// LaunchAgent for macOS auto-start
|
|
22
|
+
const LAUNCH_AGENT_LABEL = 'io.regression.claude-config';
|
|
23
|
+
const LAUNCH_AGENT_PATH = path.join(os.homedir(), 'Library', 'LaunchAgents', `${LAUNCH_AGENT_LABEL}.plist`);
|
|
24
|
+
|
|
21
25
|
// UI command needs special handling (starts web server with better error handling)
|
|
22
26
|
if (command === 'ui' || command === 'web' || command === 'server') {
|
|
23
|
-
// Check for subcommand: ui stop, ui status
|
|
27
|
+
// Check for subcommand: ui stop, ui status, ui install, ui uninstall
|
|
24
28
|
const subcommand = args[1];
|
|
25
29
|
if (subcommand === 'stop') {
|
|
26
30
|
stopDaemon();
|
|
27
31
|
} else if (subcommand === 'status') {
|
|
28
32
|
checkDaemonStatus();
|
|
33
|
+
} else if (subcommand === 'install') {
|
|
34
|
+
installLaunchAgent();
|
|
35
|
+
} else if (subcommand === 'uninstall') {
|
|
36
|
+
uninstallLaunchAgent();
|
|
29
37
|
} else {
|
|
30
38
|
startUI();
|
|
31
39
|
}
|
|
@@ -65,21 +73,183 @@ function stopDaemon() {
|
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
function checkDaemonStatus() {
|
|
68
|
-
|
|
76
|
+
// Check for LaunchAgent first (macOS)
|
|
77
|
+
if (process.platform === 'darwin' && fs.existsSync(LAUNCH_AGENT_PATH)) {
|
|
78
|
+
try {
|
|
79
|
+
const { spawnSync } = require('child_process');
|
|
80
|
+
const result = spawnSync('launchctl', ['list', LAUNCH_AGENT_LABEL], { encoding: 'utf8' });
|
|
81
|
+
if (result.status === 0 && result.stdout) {
|
|
82
|
+
// Parse the PID from output (format: "PID" = 12345;)
|
|
83
|
+
const pidMatch = result.stdout.match(/"PID"\s*=\s*(\d+)/);
|
|
84
|
+
if (pidMatch) {
|
|
85
|
+
console.log(`Daemon: running via LaunchAgent (PID: ${pidMatch[1]})`);
|
|
86
|
+
console.log(`UI available at: http://localhost:3333`);
|
|
87
|
+
console.log(`Auto-start: enabled`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch {}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check PID file (manual daemon mode)
|
|
95
|
+
if (fs.existsSync(PID_FILE)) {
|
|
96
|
+
try {
|
|
97
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim());
|
|
98
|
+
// Check if process is running
|
|
99
|
+
process.kill(pid, 0);
|
|
100
|
+
console.log(`Daemon: running (PID: ${pid})`);
|
|
101
|
+
console.log(`UI available at: http://localhost:3333`);
|
|
102
|
+
return;
|
|
103
|
+
} catch (err) {
|
|
104
|
+
fs.unlinkSync(PID_FILE);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check if LaunchAgent exists but not running
|
|
109
|
+
if (process.platform === 'darwin' && fs.existsSync(LAUNCH_AGENT_PATH)) {
|
|
110
|
+
console.log('Daemon: not running (LaunchAgent installed but stopped)');
|
|
111
|
+
console.log('Run: launchctl load ~/Library/LaunchAgents/io.regression.claude-config.plist');
|
|
112
|
+
} else {
|
|
69
113
|
console.log('Daemon: not running');
|
|
70
|
-
|
|
114
|
+
console.log('Run: claude-config ui');
|
|
71
115
|
}
|
|
116
|
+
}
|
|
72
117
|
|
|
118
|
+
function installLaunchAgent() {
|
|
119
|
+
if (process.platform !== 'darwin') {
|
|
120
|
+
console.error('Auto-start installation is only supported on macOS.');
|
|
121
|
+
console.error('On Linux, create a systemd user service instead.');
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Find the claude-config executable
|
|
126
|
+
let execPath;
|
|
73
127
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
128
|
+
execPath = execSync('which claude-config', { encoding: 'utf8' }).trim();
|
|
129
|
+
} catch {
|
|
130
|
+
execPath = path.join(__dirname, 'cli.js');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Get the PATH that includes node
|
|
134
|
+
let nodePath;
|
|
135
|
+
try {
|
|
136
|
+
nodePath = path.dirname(execSync('which node', { encoding: 'utf8' }).trim());
|
|
137
|
+
} catch {
|
|
138
|
+
nodePath = '/opt/homebrew/bin:/usr/local/bin';
|
|
139
|
+
}
|
|
140
|
+
const envPath = `${nodePath}:/usr/bin:/bin:/usr/sbin:/sbin`;
|
|
141
|
+
|
|
142
|
+
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
143
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
144
|
+
<plist version="1.0">
|
|
145
|
+
<dict>
|
|
146
|
+
<key>Label</key>
|
|
147
|
+
<string>${LAUNCH_AGENT_LABEL}</string>
|
|
148
|
+
<key>ProgramArguments</key>
|
|
149
|
+
<array>
|
|
150
|
+
<string>${execPath}</string>
|
|
151
|
+
<string>ui</string>
|
|
152
|
+
<string>--foreground</string>
|
|
153
|
+
</array>
|
|
154
|
+
<key>EnvironmentVariables</key>
|
|
155
|
+
<dict>
|
|
156
|
+
<key>PATH</key>
|
|
157
|
+
<string>${envPath}</string>
|
|
158
|
+
</dict>
|
|
159
|
+
<key>RunAtLoad</key>
|
|
160
|
+
<true/>
|
|
161
|
+
<key>KeepAlive</key>
|
|
162
|
+
<true/>
|
|
163
|
+
<key>StandardOutPath</key>
|
|
164
|
+
<string>${path.join(os.homedir(), '.claude-config', 'ui.log')}</string>
|
|
165
|
+
<key>StandardErrorPath</key>
|
|
166
|
+
<string>${path.join(os.homedir(), '.claude-config', 'ui.log')}</string>
|
|
167
|
+
<key>WorkingDirectory</key>
|
|
168
|
+
<string>${os.homedir()}</string>
|
|
169
|
+
</dict>
|
|
170
|
+
</plist>`;
|
|
171
|
+
|
|
172
|
+
// Ensure LaunchAgents directory exists
|
|
173
|
+
const launchAgentsDir = path.dirname(LAUNCH_AGENT_PATH);
|
|
174
|
+
if (!fs.existsSync(launchAgentsDir)) {
|
|
175
|
+
fs.mkdirSync(launchAgentsDir, { recursive: true });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Ensure log directory exists
|
|
179
|
+
const logDir = path.join(os.homedir(), '.claude-config');
|
|
180
|
+
if (!fs.existsSync(logDir)) {
|
|
181
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Stop existing daemon if running
|
|
185
|
+
if (fs.existsSync(PID_FILE)) {
|
|
186
|
+
try {
|
|
187
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim());
|
|
188
|
+
process.kill(pid, 'SIGTERM');
|
|
189
|
+
fs.unlinkSync(PID_FILE);
|
|
190
|
+
} catch {}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Unload existing LaunchAgent if present (using spawn to avoid shell)
|
|
194
|
+
if (fs.existsSync(LAUNCH_AGENT_PATH)) {
|
|
195
|
+
try {
|
|
196
|
+
const { spawnSync } = require('child_process');
|
|
197
|
+
spawnSync('launchctl', ['unload', LAUNCH_AGENT_PATH], { stdio: 'ignore' });
|
|
198
|
+
} catch {}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Write plist file
|
|
202
|
+
fs.writeFileSync(LAUNCH_AGENT_PATH, plistContent);
|
|
203
|
+
|
|
204
|
+
// Load the LaunchAgent (using spawn to avoid shell injection)
|
|
205
|
+
try {
|
|
206
|
+
const { spawnSync } = require('child_process');
|
|
207
|
+
const result = spawnSync('launchctl', ['load', LAUNCH_AGENT_PATH]);
|
|
208
|
+
if (result.status !== 0) {
|
|
209
|
+
throw new Error(result.stderr?.toString() || 'launchctl failed');
|
|
210
|
+
}
|
|
79
211
|
} catch (err) {
|
|
80
|
-
console.
|
|
81
|
-
|
|
212
|
+
console.error('Failed to load LaunchAgent:', err.message);
|
|
213
|
+
process.exit(1);
|
|
82
214
|
}
|
|
215
|
+
|
|
216
|
+
console.log('✓ Installed auto-start for Claude Config UI');
|
|
217
|
+
console.log('');
|
|
218
|
+
console.log('The server will now:');
|
|
219
|
+
console.log(' • Start automatically on login');
|
|
220
|
+
console.log(' • Restart if it crashes');
|
|
221
|
+
console.log(' • Run at http://localhost:3333');
|
|
222
|
+
console.log('');
|
|
223
|
+
console.log('Your PWA can now connect anytime!');
|
|
224
|
+
console.log('');
|
|
225
|
+
console.log('Commands:');
|
|
226
|
+
console.log(' claude-config ui status - Check if running');
|
|
227
|
+
console.log(' claude-config ui uninstall - Remove auto-start');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function uninstallLaunchAgent() {
|
|
231
|
+
if (process.platform !== 'darwin') {
|
|
232
|
+
console.error('Auto-start removal is only supported on macOS.');
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!fs.existsSync(LAUNCH_AGENT_PATH)) {
|
|
237
|
+
console.log('Auto-start is not installed.');
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Unload the LaunchAgent (using spawn to avoid shell injection)
|
|
242
|
+
try {
|
|
243
|
+
const { spawnSync } = require('child_process');
|
|
244
|
+
spawnSync('launchctl', ['unload', LAUNCH_AGENT_PATH], { stdio: 'ignore' });
|
|
245
|
+
} catch {}
|
|
246
|
+
|
|
247
|
+
// Remove the plist file
|
|
248
|
+
fs.unlinkSync(LAUNCH_AGENT_PATH);
|
|
249
|
+
|
|
250
|
+
console.log('✓ Removed auto-start for Claude Config UI');
|
|
251
|
+
console.log('');
|
|
252
|
+
console.log('To start manually: claude-config ui');
|
|
83
253
|
}
|
|
84
254
|
|
|
85
255
|
function startDaemon(flags) {
|
package/lib/cli.js
CHANGED
|
@@ -232,7 +232,11 @@ Maintenance:
|
|
|
232
232
|
update Check npm for updates and install if available
|
|
233
233
|
update --check Check for updates without installing
|
|
234
234
|
update /path/to/source Update from local development source
|
|
235
|
-
ui [--port=3333]
|
|
235
|
+
ui [--port=3333] Start web UI (daemon mode by default)
|
|
236
|
+
ui install Auto-start on login (macOS LaunchAgent)
|
|
237
|
+
ui uninstall Remove auto-start
|
|
238
|
+
ui status Check if UI daemon is running
|
|
239
|
+
ui stop Stop the UI daemon
|
|
236
240
|
version Show version info
|
|
237
241
|
|
|
238
242
|
Plugins (use Claude Code CLI):
|
package/lib/constants.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@regression-io/claude-config",
|
|
3
|
-
"version": "0.37.
|
|
3
|
+
"version": "0.37.25",
|
|
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",
|
package/ui/routes/updates.js
CHANGED
|
@@ -42,7 +42,7 @@ function getVersionFromFile(filePath) {
|
|
|
42
42
|
function fetchNpmVersion() {
|
|
43
43
|
return new Promise((resolve) => {
|
|
44
44
|
const url = 'https://registry.npmjs.org/@regression-io/claude-config/latest';
|
|
45
|
-
https.get(url, (res) => {
|
|
45
|
+
const req = https.get(url, (res) => {
|
|
46
46
|
let data = '';
|
|
47
47
|
res.on('data', chunk => data += chunk);
|
|
48
48
|
res.on('end', () => {
|
|
@@ -52,6 +52,7 @@ function fetchNpmVersion() {
|
|
|
52
52
|
const tarballUrl = parsed.dist?.tarball;
|
|
53
53
|
|
|
54
54
|
if (!version || !tarballUrl) {
|
|
55
|
+
console.log('[update-check] npm registry response missing version or tarball');
|
|
55
56
|
resolve(null);
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
@@ -61,8 +62,7 @@ function fetchNpmVersion() {
|
|
|
61
62
|
const options = {
|
|
62
63
|
hostname: tarball.hostname,
|
|
63
64
|
path: tarball.pathname,
|
|
64
|
-
method: 'HEAD'
|
|
65
|
-
timeout: 5000
|
|
65
|
+
method: 'HEAD'
|
|
66
66
|
};
|
|
67
67
|
|
|
68
68
|
const verifyReq = https.request(options, (verifyRes) => {
|
|
@@ -70,20 +70,35 @@ function fetchNpmVersion() {
|
|
|
70
70
|
if (verifyRes.statusCode === 200) {
|
|
71
71
|
resolve(version);
|
|
72
72
|
} else {
|
|
73
|
+
console.log(`[update-check] tarball not accessible (status ${verifyRes.statusCode})`);
|
|
73
74
|
resolve(null);
|
|
74
75
|
}
|
|
75
76
|
});
|
|
76
|
-
verifyReq.
|
|
77
|
-
|
|
77
|
+
verifyReq.setTimeout(5000, () => {
|
|
78
|
+
console.log('[update-check] tarball verification timed out');
|
|
78
79
|
verifyReq.destroy();
|
|
79
80
|
resolve(null);
|
|
80
81
|
});
|
|
82
|
+
verifyReq.on('error', (e) => {
|
|
83
|
+
console.log('[update-check] tarball verification error:', e.message);
|
|
84
|
+
resolve(null);
|
|
85
|
+
});
|
|
81
86
|
verifyReq.end();
|
|
82
87
|
} catch (e) {
|
|
88
|
+
console.log('[update-check] failed to parse npm response:', e.message);
|
|
83
89
|
resolve(null);
|
|
84
90
|
}
|
|
85
91
|
});
|
|
86
|
-
})
|
|
92
|
+
});
|
|
93
|
+
req.setTimeout(10000, () => {
|
|
94
|
+
console.log('[update-check] npm registry request timed out');
|
|
95
|
+
req.destroy();
|
|
96
|
+
resolve(null);
|
|
97
|
+
});
|
|
98
|
+
req.on('error', (e) => {
|
|
99
|
+
console.log('[update-check] npm registry error:', e.message);
|
|
100
|
+
resolve(null);
|
|
101
|
+
});
|
|
87
102
|
});
|
|
88
103
|
}
|
|
89
104
|
|
|
@@ -118,7 +133,10 @@ async function checkForUpdates(manager, dirname) {
|
|
|
118
133
|
// Check npm for latest version
|
|
119
134
|
const npmVersion = await fetchNpmVersion();
|
|
120
135
|
|
|
136
|
+
console.log(`[update-check] installed: ${installedVersion}, npm: ${npmVersion}`);
|
|
137
|
+
|
|
121
138
|
if (npmVersion && isNewerVersion(npmVersion, installedVersion)) {
|
|
139
|
+
console.log('[update-check] update available');
|
|
122
140
|
return {
|
|
123
141
|
updateAvailable: true,
|
|
124
142
|
installedVersion,
|
package/ui/server.cjs
CHANGED
|
@@ -611,8 +611,13 @@ class ConfigUIServer {
|
|
|
611
611
|
case '/api/projects/active':
|
|
612
612
|
if (req.method === 'GET') return this.json(res, routes.projects.getActiveProject(this.manager, this.projectDir, () => this.getHierarchy(), () => routes.subprojects.getSubprojectsForDir(this.manager, this.config, this.projectDir)));
|
|
613
613
|
if (req.method === 'PUT') {
|
|
614
|
-
const result = routes.projects.setActiveProject(
|
|
615
|
-
|
|
614
|
+
const result = routes.projects.setActiveProject(
|
|
615
|
+
this.manager,
|
|
616
|
+
body.id,
|
|
617
|
+
(newDir) => { this.projectDir = newDir; },
|
|
618
|
+
() => this.getHierarchy(),
|
|
619
|
+
() => routes.subprojects.getSubprojectsForDir(this.manager, this.config, this.projectDir)
|
|
620
|
+
);
|
|
616
621
|
return this.json(res, result);
|
|
617
622
|
}
|
|
618
623
|
break;
|