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.
- package/LICENSE +21 -0
- package/README.md +553 -0
- package/cli.js +431 -0
- package/config-loader.js +294 -0
- package/hooks/activity-track.sh +56 -0
- package/hooks/codex-workstream.sh +44 -0
- package/hooks/gemini-workstream.sh +44 -0
- package/hooks/workstream-inject.sh +20 -0
- package/lib/activity.js +283 -0
- package/lib/apply.js +344 -0
- package/lib/cli.js +267 -0
- package/lib/config.js +171 -0
- package/lib/constants.js +55 -0
- package/lib/env.js +114 -0
- package/lib/index.js +47 -0
- package/lib/init.js +122 -0
- package/lib/mcps.js +139 -0
- package/lib/memory.js +201 -0
- package/lib/projects.js +138 -0
- package/lib/registry.js +83 -0
- package/lib/utils.js +129 -0
- package/lib/workstreams.js +652 -0
- package/package.json +80 -0
- package/scripts/capture-screenshots.js +142 -0
- package/scripts/postinstall.js +122 -0
- package/scripts/release.sh +71 -0
- package/scripts/sync-version.js +77 -0
- package/scripts/tauri-prepare.js +328 -0
- package/shared/mcp-registry.json +76 -0
- package/ui/dist/assets/index-DbZ3_HBD.js +3204 -0
- package/ui/dist/assets/index-DjLdm3Mr.css +32 -0
- package/ui/dist/icons/icon-192.svg +16 -0
- package/ui/dist/icons/icon-512.svg +16 -0
- package/ui/dist/index.html +39 -0
- package/ui/dist/manifest.json +25 -0
- package/ui/dist/sw.js +24 -0
- package/ui/dist/tutorial/claude-settings.png +0 -0
- package/ui/dist/tutorial/header.png +0 -0
- package/ui/dist/tutorial/mcp-registry.png +0 -0
- package/ui/dist/tutorial/memory-view.png +0 -0
- package/ui/dist/tutorial/permissions.png +0 -0
- package/ui/dist/tutorial/plugins-view.png +0 -0
- package/ui/dist/tutorial/project-explorer.png +0 -0
- package/ui/dist/tutorial/projects-view.png +0 -0
- package/ui/dist/tutorial/sidebar.png +0 -0
- package/ui/dist/tutorial/tutorial-view.png +0 -0
- package/ui/dist/tutorial/workstreams-view.png +0 -0
- package/ui/routes/activity.js +58 -0
- package/ui/routes/commands.js +74 -0
- package/ui/routes/configs.js +329 -0
- package/ui/routes/env.js +40 -0
- package/ui/routes/file-explorer.js +668 -0
- package/ui/routes/index.js +41 -0
- package/ui/routes/mcp-discovery.js +235 -0
- package/ui/routes/memory.js +385 -0
- package/ui/routes/package.json +3 -0
- package/ui/routes/plugins.js +466 -0
- package/ui/routes/projects.js +198 -0
- package/ui/routes/registry.js +30 -0
- package/ui/routes/rules.js +74 -0
- package/ui/routes/search.js +125 -0
- package/ui/routes/settings.js +381 -0
- package/ui/routes/subprojects.js +208 -0
- package/ui/routes/tool-sync.js +127 -0
- package/ui/routes/updates.js +339 -0
- package/ui/routes/workstreams.js +224 -0
- package/ui/server.cjs +773 -0
- package/ui/terminal-server.cjs +160 -0
package/cli.js
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Claude Config CLI
|
|
5
|
+
*
|
|
6
|
+
* Configuration management for Claude Code
|
|
7
|
+
* CLI-first with optional Web UI
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const { spawn, execSync } = require('child_process');
|
|
14
|
+
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
const command = args[0] || '';
|
|
17
|
+
|
|
18
|
+
// PID file for daemon mode
|
|
19
|
+
const PID_FILE = path.join(os.homedir(), '.claude-config', 'ui.pid');
|
|
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
|
+
|
|
25
|
+
// UI command needs special handling (starts web server with better error handling)
|
|
26
|
+
if (command === 'ui' || command === 'web' || command === 'server') {
|
|
27
|
+
// Check for subcommand: ui stop, ui status, ui install, ui uninstall
|
|
28
|
+
const subcommand = args[1];
|
|
29
|
+
if (subcommand === 'stop') {
|
|
30
|
+
stopDaemon();
|
|
31
|
+
} else if (subcommand === 'status') {
|
|
32
|
+
checkDaemonStatus();
|
|
33
|
+
} else if (subcommand === 'install') {
|
|
34
|
+
installLaunchAgent();
|
|
35
|
+
} else if (subcommand === 'uninstall') {
|
|
36
|
+
uninstallLaunchAgent();
|
|
37
|
+
} else {
|
|
38
|
+
startUI();
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
// Pass everything to config-loader.js
|
|
42
|
+
const loaderPath = path.join(__dirname, 'config-loader.js');
|
|
43
|
+
const child = spawn('node', [loaderPath, ...args], {
|
|
44
|
+
stdio: 'inherit',
|
|
45
|
+
cwd: process.cwd()
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
child.on('close', (code) => {
|
|
49
|
+
process.exit(code || 0);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function stopDaemon() {
|
|
54
|
+
if (!fs.existsSync(PID_FILE)) {
|
|
55
|
+
console.log('No daemon running (PID file not found)');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim());
|
|
61
|
+
process.kill(pid, 'SIGTERM');
|
|
62
|
+
fs.unlinkSync(PID_FILE);
|
|
63
|
+
console.log(`Stopped daemon (PID: ${pid})`);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
if (err.code === 'ESRCH') {
|
|
66
|
+
// Process doesn't exist, clean up PID file
|
|
67
|
+
fs.unlinkSync(PID_FILE);
|
|
68
|
+
console.log('Daemon was not running, cleaned up stale PID file');
|
|
69
|
+
} else {
|
|
70
|
+
console.error('Failed to stop daemon:', err.message);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function checkDaemonStatus() {
|
|
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 {
|
|
113
|
+
console.log('Daemon: not running');
|
|
114
|
+
console.log('Run: claude-config ui');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
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;
|
|
127
|
+
try {
|
|
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
|
+
}
|
|
211
|
+
} catch (err) {
|
|
212
|
+
console.error('Failed to load LaunchAgent:', err.message);
|
|
213
|
+
process.exit(1);
|
|
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');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function startDaemon(flags) {
|
|
256
|
+
// Check if already running
|
|
257
|
+
if (fs.existsSync(PID_FILE)) {
|
|
258
|
+
try {
|
|
259
|
+
const existingPid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim());
|
|
260
|
+
process.kill(existingPid, 0);
|
|
261
|
+
console.log(`Daemon already running (PID: ${existingPid})`);
|
|
262
|
+
console.log(`UI available at: http://localhost:${flags.port}`);
|
|
263
|
+
console.log('Use "claude-config ui stop" to stop the daemon');
|
|
264
|
+
return;
|
|
265
|
+
} catch (err) {
|
|
266
|
+
// Process not running, clean up stale PID file
|
|
267
|
+
fs.unlinkSync(PID_FILE);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Ensure PID directory exists
|
|
272
|
+
const pidDir = path.dirname(PID_FILE);
|
|
273
|
+
if (!fs.existsSync(pidDir)) {
|
|
274
|
+
fs.mkdirSync(pidDir, { recursive: true });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Log file for daemon output
|
|
278
|
+
const logFile = path.join(pidDir, 'ui.log');
|
|
279
|
+
|
|
280
|
+
// Build args for spawned process - must use --foreground since daemon is now default
|
|
281
|
+
const spawnArgs = ['ui', '--foreground'];
|
|
282
|
+
if (flags.port !== 3333) {
|
|
283
|
+
spawnArgs.push('--port', String(flags.port));
|
|
284
|
+
}
|
|
285
|
+
if (flags.dir) {
|
|
286
|
+
spawnArgs.push('--dir', flags.dir);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Spawn detached process
|
|
290
|
+
const out = fs.openSync(logFile, 'a');
|
|
291
|
+
const err = fs.openSync(logFile, 'a');
|
|
292
|
+
|
|
293
|
+
const child = spawn(process.execPath, [__filename, ...spawnArgs], {
|
|
294
|
+
detached: true,
|
|
295
|
+
stdio: ['ignore', out, err],
|
|
296
|
+
cwd: os.homedir()
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Write PID file
|
|
300
|
+
fs.writeFileSync(PID_FILE, String(child.pid));
|
|
301
|
+
|
|
302
|
+
// Unref to allow parent to exit
|
|
303
|
+
child.unref();
|
|
304
|
+
|
|
305
|
+
console.log(`Started daemon (PID: ${child.pid})`);
|
|
306
|
+
console.log(`UI available at: http://localhost:${flags.port}`);
|
|
307
|
+
console.log(`Logs: ${logFile}`);
|
|
308
|
+
console.log('\nCommands:');
|
|
309
|
+
console.log(' claude-config ui status - Check daemon status');
|
|
310
|
+
console.log(' claude-config ui stop - Stop the daemon');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function startUI() {
|
|
314
|
+
// Parse UI-specific flags
|
|
315
|
+
const flags = {
|
|
316
|
+
port: 3333,
|
|
317
|
+
dir: null, // Will default to active project or home
|
|
318
|
+
foreground: false // Default to daemon mode
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
for (let i = 1; i < args.length; i++) {
|
|
322
|
+
const arg = args[i];
|
|
323
|
+
if (arg === '--port' || arg === '-p') {
|
|
324
|
+
const portArg = args[++i];
|
|
325
|
+
if (!portArg || isNaN(parseInt(portArg))) {
|
|
326
|
+
console.error('Error: --port requires a valid port number');
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
flags.port = parseInt(portArg);
|
|
330
|
+
} else if (arg.startsWith('--port=')) {
|
|
331
|
+
const portVal = parseInt(arg.split('=')[1]);
|
|
332
|
+
if (isNaN(portVal)) {
|
|
333
|
+
console.error('Error: --port requires a valid port number');
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
flags.port = portVal;
|
|
337
|
+
} else if (arg === '--dir' || arg === '-d') {
|
|
338
|
+
flags.dir = args[++i] || null;
|
|
339
|
+
} else if (arg.startsWith('--dir=')) {
|
|
340
|
+
flags.dir = arg.split('=')[1] || null;
|
|
341
|
+
} else if (arg === '--foreground' || arg === '-f' || arg === '--daemon' || arg === '-D') {
|
|
342
|
+
// --foreground runs in foreground, --daemon kept for backwards compat (now default)
|
|
343
|
+
flags.foreground = (arg === '--foreground' || arg === '-f');
|
|
344
|
+
} else if (!arg.startsWith('-') && fs.existsSync(arg) && fs.statSync(arg).isDirectory()) {
|
|
345
|
+
flags.dir = arg;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Default: daemon mode (spawn detached and exit)
|
|
350
|
+
if (!flags.foreground) {
|
|
351
|
+
return startDaemon(flags);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Validate port range
|
|
355
|
+
if (flags.port < 1 || flags.port > 65535) {
|
|
356
|
+
console.error('Error: Port must be between 1 and 65535');
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Validate directory exists (if specified)
|
|
361
|
+
if (flags.dir) {
|
|
362
|
+
if (!fs.existsSync(flags.dir)) {
|
|
363
|
+
console.error(`Error: Directory not found: ${flags.dir}`);
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (!fs.statSync(flags.dir).isDirectory()) {
|
|
368
|
+
console.error(`Error: Not a directory: ${flags.dir}`);
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
flags.dir = path.resolve(flags.dir);
|
|
373
|
+
}
|
|
374
|
+
// If no dir specified, server will load from projects registry or use cwd
|
|
375
|
+
|
|
376
|
+
// Load dependencies
|
|
377
|
+
const serverPath = path.join(__dirname, 'ui', 'server.cjs');
|
|
378
|
+
|
|
379
|
+
if (!fs.existsSync(serverPath)) {
|
|
380
|
+
console.error('Error: UI server not found.');
|
|
381
|
+
console.error('The package may not be installed correctly.');
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const distPath = path.join(__dirname, 'ui', 'dist');
|
|
386
|
+
if (!fs.existsSync(distPath)) {
|
|
387
|
+
console.error('Error: UI build not found.');
|
|
388
|
+
console.error('Run "npm run build" to build the UI first.');
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
let ConfigUIServer, ClaudeConfigManager;
|
|
393
|
+
try {
|
|
394
|
+
ConfigUIServer = require(serverPath);
|
|
395
|
+
ClaudeConfigManager = require('./config-loader.js');
|
|
396
|
+
} catch (err) {
|
|
397
|
+
console.error('Error: Failed to load dependencies');
|
|
398
|
+
console.error(err.message);
|
|
399
|
+
if (err.message.includes('node-pty')) {
|
|
400
|
+
console.error('\nThe terminal feature requires node-pty which failed to load.');
|
|
401
|
+
console.error('Try reinstalling: npm rebuild node-pty');
|
|
402
|
+
}
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Handle server errors
|
|
407
|
+
process.on('uncaughtException', (err) => {
|
|
408
|
+
if (err.code === 'EADDRINUSE') {
|
|
409
|
+
console.error(`\nError: Port ${flags.port} is already in use.`);
|
|
410
|
+
console.error(`Try a different port: claude-config ui --port ${flags.port + 1}`);
|
|
411
|
+
process.exit(1);
|
|
412
|
+
} else if (err.code === 'EACCES') {
|
|
413
|
+
console.error(`\nError: Permission denied for port ${flags.port}.`);
|
|
414
|
+
console.error('Ports below 1024 require elevated privileges.');
|
|
415
|
+
process.exit(1);
|
|
416
|
+
} else {
|
|
417
|
+
console.error('\nUnexpected error:', err.message);
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
const manager = new ClaudeConfigManager();
|
|
424
|
+
const server = new ConfigUIServer(flags.port, flags.dir, manager);
|
|
425
|
+
server.start();
|
|
426
|
+
} catch (err) {
|
|
427
|
+
console.error('Error: Failed to start server');
|
|
428
|
+
console.error(err.message);
|
|
429
|
+
process.exit(1);
|
|
430
|
+
}
|
|
431
|
+
}
|