claude-ws 0.3.98 → 0.3.100
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/bin/claude-ws.js +36 -3
- package/bin/lib/cli-parser.js +109 -0
- package/bin/lib/commands/logs.js +73 -0
- package/bin/lib/commands/open.js +49 -0
- package/bin/lib/commands/start.js +73 -0
- package/bin/lib/commands/status.js +35 -0
- package/bin/lib/commands/stop.js +50 -0
- package/bin/lib/config.js +93 -0
- package/bin/lib/daemon.js +167 -0
- package/bin/lib/health.js +62 -0
- package/locales/de.json +376 -12
- package/locales/en.json +376 -12
- package/locales/es.json +400 -11
- package/locales/fr.json +400 -11
- package/locales/ja.json +400 -11
- package/locales/ko.json +400 -11
- package/locales/vi.json +376 -12
- package/locales/zh.json +400 -11
- package/package.json +1 -1
- package/server.ts +283 -6
- package/src/app/[locale]/not-found.tsx +6 -3
- package/src/app/[locale]/page.tsx +14 -4
- package/src/app/api/attempts/[id]/workflow/route.ts +76 -0
- package/src/app/api/questions/answer/route.ts +58 -0
- package/src/app/api/questions/route.ts +68 -0
- package/src/app/api/tasks/[id]/compact/route.ts +62 -0
- package/src/app/api/tasks/route.ts +27 -25
- package/src/components/access-anywhere/api-access-key-setup-modal.tsx +2 -2
- package/src/components/access-anywhere/tunnel-settings-dialog.tsx +6 -6
- package/src/components/access-anywhere/wizard-step-ctunnel.tsx +8 -8
- package/src/components/agent-factory/dependency-tree.tsx +5 -3
- package/src/components/agent-factory/discovery-dialog.tsx +26 -22
- package/src/components/agent-factory/plugin-detail-dialog.tsx +41 -38
- package/src/components/agent-factory/plugin-form-dialog.tsx +23 -20
- package/src/components/agent-factory/plugin-list.tsx +20 -17
- package/src/components/agent-factory/upload-dialog.tsx +17 -14
- package/src/components/auth/agent-provider-dialog.tsx +67 -65
- package/src/components/auth/api-key-dialog.tsx +14 -11
- package/src/components/auth/auth-error-message.tsx +6 -3
- package/src/components/editor/code-editor-with-inline-edit.tsx +4 -2
- package/src/components/editor/file-diff-resolver-modal.tsx +31 -26
- package/src/components/editor/inline-edit-dialog.tsx +9 -6
- package/src/components/editor/selection-mention-popup.tsx +3 -1
- package/src/components/header/project-selector.tsx +7 -4
- package/src/components/header.tsx +70 -4
- package/src/components/kanban/board.tsx +66 -18
- package/src/components/kanban/column.tsx +11 -0
- package/src/components/kanban/task-card.tsx +70 -4
- package/src/components/project-settings/component-selector.tsx +3 -1
- package/src/components/project-settings/plugin-upload-dialog.tsx +7 -5
- package/src/components/project-settings/project-settings-dialog.tsx +5 -3
- package/src/components/questions/questions-panel.tsx +136 -0
- package/src/components/settings/folder-browser-dialog.tsx +29 -25
- package/src/components/settings/settings-page.tsx +64 -18
- package/src/components/settings/setup-dialog.tsx +26 -23
- package/src/components/setup/unified-setup-wizard.tsx +12 -9
- package/src/components/sidebar/file-browser/file-create-buttons.tsx +7 -3
- package/src/components/sidebar/file-browser/file-tab-content.tsx +19 -15
- package/src/components/sidebar/file-browser/file-tabs-panel.tsx +7 -4
- package/src/components/sidebar/file-browser/file-tree.tsx +3 -1
- package/src/components/sidebar/git-changes/branch-checkout-modal.tsx +6 -4
- package/src/components/sidebar/git-changes/commit-details-modal.tsx +5 -3
- package/src/components/sidebar/git-changes/diff-tabs-panel.tsx +3 -1
- package/src/components/sidebar/git-changes/git-file-item.tsx +8 -6
- package/src/components/sidebar/git-changes/git-graph.tsx +8 -5
- package/src/components/sidebar/git-changes/git-panel.tsx +28 -27
- package/src/components/sidebar/git-changes/git-section.tsx +5 -3
- package/src/components/sidebar/shells/shell-panel.tsx +3 -1
- package/src/components/task/attachment-bar.tsx +4 -1
- package/src/components/task/attempt-item.tsx +7 -5
- package/src/components/task/conversation-view.tsx +21 -13
- package/src/components/task/floating-chat-window.tsx +14 -5
- package/src/components/task/interactive-command/checkpoint-list.tsx +5 -3
- package/src/components/task/interactive-command/confirm-dialog.tsx +9 -4
- package/src/components/task/interactive-command/interactive-command-overlay.tsx +23 -9
- package/src/components/task/interactive-command/question-prompt.tsx +12 -8
- package/src/components/task/pending-question-indicator.tsx +5 -3
- package/src/components/task/prompt-input.tsx +1 -1
- package/src/components/task/shell-log-view.tsx +3 -1
- package/src/components/task/status-line.tsx +84 -23
- package/src/components/task/task-detail-panel.tsx +83 -28
- package/src/components/task/task-shell-indicator.tsx +10 -6
- package/src/components/terminal/terminal-context-menu.tsx +6 -4
- package/src/components/terminal/terminal-instance.tsx +11 -3
- package/src/components/terminal/terminal-panel.tsx +6 -3
- package/src/components/terminal/terminal-shortcut-bar.tsx +3 -1
- package/src/components/terminal/terminal-tab-bar.tsx +5 -3
- package/src/components/workflow/workflow-panel.tsx +181 -0
- package/src/hooks/use-attempt-stream.ts +96 -3
- package/src/lib/agent-manager.ts +88 -2
- package/src/lib/db/index.ts +18 -0
- package/src/lib/db/schema.ts +29 -0
- package/src/lib/process-manager.ts +28 -7
- package/src/lib/session-manager.ts +60 -0
- package/src/lib/usage-tracker.ts +19 -19
- package/src/lib/workflow-tracker.ts +118 -20
- package/src/stores/panel-layout-store.ts +12 -1
- package/src/stores/questions-store.ts +76 -0
- package/src/stores/task-store.ts +34 -0
- package/src/stores/workflow-store.ts +71 -0
package/bin/claude-ws.js
CHANGED
|
@@ -14,6 +14,18 @@ const path = require('path');
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const os = require('os');
|
|
16
16
|
|
|
17
|
+
// ── Subcommand detection ──────────────────────────────────────────────
|
|
18
|
+
// If the first positional arg is a known subcommand, delegate and exit.
|
|
19
|
+
// Otherwise, fall through to the existing foreground startup logic.
|
|
20
|
+
const SUBCOMMANDS = ['start', 'stop', 'status', 'logs', 'open'];
|
|
21
|
+
const _firstArg = process.argv[2];
|
|
22
|
+
if (SUBCOMMANDS.includes(_firstArg)) {
|
|
23
|
+
require(`./lib/commands/${_firstArg}`).run(process.argv.slice(3));
|
|
24
|
+
// The command handles process.exit — nothing else to do here.
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// ── End subcommand detection ──────────────────────────────────────────
|
|
28
|
+
|
|
17
29
|
const isWindows = process.platform === 'win32';
|
|
18
30
|
|
|
19
31
|
/**
|
|
@@ -59,7 +71,23 @@ if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
|
59
71
|
Claude Workspace - Visual workspace for Claude Code
|
|
60
72
|
|
|
61
73
|
Usage:
|
|
62
|
-
claude-ws [options]
|
|
74
|
+
claude-ws [options] Start server in foreground (blocks terminal)
|
|
75
|
+
claude-ws <command> [flags] Daemon management
|
|
76
|
+
|
|
77
|
+
Commands:
|
|
78
|
+
start Start as background daemon
|
|
79
|
+
--port, -p <port> Server port (default: 8556)
|
|
80
|
+
--host <host> Bind host (default: localhost)
|
|
81
|
+
--data-dir <dir> Data directory
|
|
82
|
+
--log-dir <dir> Log directory
|
|
83
|
+
--no-open Don't open browser after start
|
|
84
|
+
stop Stop the running daemon
|
|
85
|
+
status Show daemon PID, URL, and health
|
|
86
|
+
logs Tail daemon log files
|
|
87
|
+
-f, --follow Follow log output
|
|
88
|
+
-n, --lines <N> Number of lines (default: 50)
|
|
89
|
+
-e, --error Show error log instead
|
|
90
|
+
open Open browser to running instance
|
|
63
91
|
|
|
64
92
|
Options:
|
|
65
93
|
-v, --version Show version number
|
|
@@ -68,10 +96,15 @@ Options:
|
|
|
68
96
|
Environment:
|
|
69
97
|
.env: Loaded from current working directory (./.env)
|
|
70
98
|
Database: Stored in ./data/claude-ws.db (or DATA_DIR env)
|
|
99
|
+
Config: ~/.claude-ws/config.json (port, host, dataDir, logDir)
|
|
71
100
|
|
|
72
101
|
Examples:
|
|
73
|
-
claude-ws
|
|
74
|
-
|
|
102
|
+
claude-ws Start server in foreground
|
|
103
|
+
claude-ws start Start as daemon
|
|
104
|
+
claude-ws start --port 3000 Start daemon on port 3000
|
|
105
|
+
claude-ws status Check if daemon is running
|
|
106
|
+
claude-ws logs -f Follow daemon logs
|
|
107
|
+
claude-ws stop Stop the daemon
|
|
75
108
|
|
|
76
109
|
For more info: https://github.com/Claude-Workspace/claude-ws
|
|
77
110
|
`);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight CLI argument parser.
|
|
3
|
+
* Zero dependencies - Node.js built-ins only.
|
|
4
|
+
*
|
|
5
|
+
* Supports:
|
|
6
|
+
* --port 3000, --port=3000
|
|
7
|
+
* --no-open (boolean negation)
|
|
8
|
+
* -f (short flags)
|
|
9
|
+
* -n 50 (short flags with values)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string[]} argv - process.argv.slice(N) after subcommand
|
|
14
|
+
* @param {{ flags: Record<string, { type: 'string'|'boolean', alias?: string, default?: any }> }} schema
|
|
15
|
+
* @returns {{ flags: Record<string, any>, args: string[] }}
|
|
16
|
+
*/
|
|
17
|
+
function parse(argv, schema) {
|
|
18
|
+
const flags = {};
|
|
19
|
+
const args = [];
|
|
20
|
+
const defs = schema.flags || {};
|
|
21
|
+
|
|
22
|
+
// Set defaults
|
|
23
|
+
for (const [key, def] of Object.entries(defs)) {
|
|
24
|
+
if (def.default !== undefined) {
|
|
25
|
+
flags[key] = def.default;
|
|
26
|
+
} else {
|
|
27
|
+
flags[key] = def.type === 'boolean' ? false : undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Build alias map: alias -> canonical name
|
|
32
|
+
const aliasMap = {};
|
|
33
|
+
for (const [key, def] of Object.entries(defs)) {
|
|
34
|
+
if (def.alias) {
|
|
35
|
+
aliasMap[def.alias] = key;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let i = 0;
|
|
40
|
+
while (i < argv.length) {
|
|
41
|
+
const arg = argv[i];
|
|
42
|
+
|
|
43
|
+
if (arg === '--') {
|
|
44
|
+
args.push(...argv.slice(i + 1));
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// --no-<flag> boolean negation
|
|
49
|
+
if (arg.startsWith('--no-')) {
|
|
50
|
+
const name = arg.slice(5);
|
|
51
|
+
if (defs[name] && defs[name].type === 'boolean') {
|
|
52
|
+
flags[name] = false;
|
|
53
|
+
i++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// --flag=value
|
|
59
|
+
if (arg.startsWith('--') && arg.includes('=')) {
|
|
60
|
+
const eqIdx = arg.indexOf('=');
|
|
61
|
+
const name = arg.slice(2, eqIdx);
|
|
62
|
+
const value = arg.slice(eqIdx + 1);
|
|
63
|
+
if (defs[name]) {
|
|
64
|
+
flags[name] = defs[name].type === 'boolean' ? value !== 'false' : value;
|
|
65
|
+
}
|
|
66
|
+
i++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// --flag [value]
|
|
71
|
+
if (arg.startsWith('--')) {
|
|
72
|
+
const name = arg.slice(2);
|
|
73
|
+
if (defs[name]) {
|
|
74
|
+
if (defs[name].type === 'boolean') {
|
|
75
|
+
flags[name] = true;
|
|
76
|
+
i++;
|
|
77
|
+
} else {
|
|
78
|
+
flags[name] = argv[i + 1];
|
|
79
|
+
i += 2;
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// -f or -f value (short alias)
|
|
86
|
+
if (arg.startsWith('-') && !arg.startsWith('--') && arg.length === 2) {
|
|
87
|
+
const alias = arg.slice(1);
|
|
88
|
+
const canonical = aliasMap[alias];
|
|
89
|
+
if (canonical && defs[canonical]) {
|
|
90
|
+
if (defs[canonical].type === 'boolean') {
|
|
91
|
+
flags[canonical] = true;
|
|
92
|
+
i++;
|
|
93
|
+
} else {
|
|
94
|
+
flags[canonical] = argv[i + 1];
|
|
95
|
+
i += 2;
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Positional argument
|
|
102
|
+
args.push(arg);
|
|
103
|
+
i++;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { flags, args };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = { parse };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `claude-ws logs` — Tail daemon log files.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { spawn } = require('child_process');
|
|
8
|
+
const { parse } = require('../cli-parser');
|
|
9
|
+
const config = require('../config');
|
|
10
|
+
|
|
11
|
+
const FLAG_SCHEMA = {
|
|
12
|
+
flags: {
|
|
13
|
+
follow: { type: 'boolean', alias: 'f', default: false },
|
|
14
|
+
lines: { type: 'string', alias: 'n', default: '50' },
|
|
15
|
+
error: { type: 'boolean', alias: 'e', default: false },
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function run(argv) {
|
|
20
|
+
const { flags } = parse(argv, FLAG_SCHEMA);
|
|
21
|
+
const conf = config.resolve({});
|
|
22
|
+
|
|
23
|
+
const logFile = flags.error
|
|
24
|
+
? path.join(conf.logDir, 'claude-ws-error.log')
|
|
25
|
+
: path.join(conf.logDir, 'claude-ws.log');
|
|
26
|
+
|
|
27
|
+
if (!fs.existsSync(logFile)) {
|
|
28
|
+
console.log(`[claude-ws] No log file found at ${logFile}`);
|
|
29
|
+
console.log('[claude-ws] Has the daemon been started? Try: claude-ws start');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const tailArgs = [];
|
|
34
|
+
tailArgs.push('-n', flags.lines);
|
|
35
|
+
if (flags.follow) {
|
|
36
|
+
tailArgs.push('-f');
|
|
37
|
+
}
|
|
38
|
+
tailArgs.push(logFile);
|
|
39
|
+
|
|
40
|
+
console.log(`[claude-ws] ${flags.error ? 'Error log' : 'Log'}: ${logFile}`);
|
|
41
|
+
console.log('---');
|
|
42
|
+
|
|
43
|
+
const tail = spawn('tail', tailArgs, { stdio: 'inherit' });
|
|
44
|
+
|
|
45
|
+
tail.on('error', (err) => {
|
|
46
|
+
// tail not available (Windows) — fall back to reading file
|
|
47
|
+
if (err.code === 'ENOENT') {
|
|
48
|
+
const content = fs.readFileSync(logFile, 'utf-8');
|
|
49
|
+
const lines = content.split('\n');
|
|
50
|
+
const n = parseInt(flags.lines, 10) || 50;
|
|
51
|
+
const tail = lines.slice(-n);
|
|
52
|
+
console.log(tail.join('\n'));
|
|
53
|
+
|
|
54
|
+
if (flags.follow) {
|
|
55
|
+
console.log('[claude-ws] -f (follow) is not supported on this platform.');
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
console.error(`[claude-ws] Error: ${err.message}`);
|
|
59
|
+
}
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
tail.on('close', (code) => {
|
|
64
|
+
process.exit(code || 0);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Forward Ctrl+C to tail
|
|
68
|
+
process.on('SIGINT', () => {
|
|
69
|
+
tail.kill('SIGINT');
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { run };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `claude-ws open` — Open browser to the running instance.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { exec } = require('child_process');
|
|
6
|
+
const config = require('../config');
|
|
7
|
+
const daemon = require('../daemon');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Open a URL in the default browser (cross-platform).
|
|
11
|
+
* @param {string} url
|
|
12
|
+
*/
|
|
13
|
+
function openUrl(url) {
|
|
14
|
+
const platform = process.platform;
|
|
15
|
+
let cmd;
|
|
16
|
+
if (platform === 'darwin') {
|
|
17
|
+
cmd = `open "${url}"`;
|
|
18
|
+
} else if (platform === 'win32') {
|
|
19
|
+
cmd = `start "" "${url}"`;
|
|
20
|
+
} else {
|
|
21
|
+
cmd = `xdg-open "${url}"`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
exec(cmd, (err) => {
|
|
25
|
+
if (err) {
|
|
26
|
+
console.log(`[claude-ws] Could not open browser. Visit: ${url}`);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function run(_argv) {
|
|
32
|
+
const { running, pid } = daemon.checkRunning();
|
|
33
|
+
|
|
34
|
+
if (!running) {
|
|
35
|
+
console.log('[claude-ws] No running daemon found.');
|
|
36
|
+
console.log('[claude-ws] Start one first: claude-ws start');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const conf = config.resolve({});
|
|
41
|
+
const url = `http://${conf.host}:${conf.port}`;
|
|
42
|
+
|
|
43
|
+
console.log(`[claude-ws] Opening ${url} (PID ${pid})`);
|
|
44
|
+
openUrl(url);
|
|
45
|
+
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { run, openUrl };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `claude-ws start` — Start claude-ws as a background daemon.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { parse } = require('../cli-parser');
|
|
6
|
+
const config = require('../config');
|
|
7
|
+
const daemon = require('../daemon');
|
|
8
|
+
const health = require('../health');
|
|
9
|
+
|
|
10
|
+
const FLAG_SCHEMA = {
|
|
11
|
+
flags: {
|
|
12
|
+
port: { type: 'string', alias: 'p' },
|
|
13
|
+
host: { type: 'string' },
|
|
14
|
+
'data-dir': { type: 'string' },
|
|
15
|
+
'log-dir': { type: 'string' },
|
|
16
|
+
'no-open': { type: 'boolean' },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
async function run(argv) {
|
|
21
|
+
const { flags } = parse(argv, FLAG_SCHEMA);
|
|
22
|
+
const conf = config.resolve(flags);
|
|
23
|
+
|
|
24
|
+
// Check if already running
|
|
25
|
+
const { running, pid } = daemon.checkRunning();
|
|
26
|
+
if (running) {
|
|
27
|
+
console.log(`[claude-ws] Already running (PID ${pid})`);
|
|
28
|
+
console.log(`[claude-ws] URL: http://${conf.host}:${conf.port}`);
|
|
29
|
+
console.log('[claude-ws] Use "claude-ws stop" to stop it first.');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log('[claude-ws] Starting daemon...');
|
|
34
|
+
console.log(`[claude-ws] Port: ${conf.port}`);
|
|
35
|
+
console.log(`[claude-ws] Host: ${conf.host}`);
|
|
36
|
+
console.log(`[claude-ws] Data: ${conf.dataDir}`);
|
|
37
|
+
console.log(`[claude-ws] Logs: ${conf.logDir}`);
|
|
38
|
+
|
|
39
|
+
const childPid = daemon.daemonize({
|
|
40
|
+
port: conf.port,
|
|
41
|
+
host: conf.host,
|
|
42
|
+
dataDir: conf.dataDir,
|
|
43
|
+
logDir: conf.logDir,
|
|
44
|
+
noOpen: flags['no-open'],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
console.log(`[claude-ws] Daemon started (PID ${childPid})`);
|
|
48
|
+
console.log('[claude-ws] Waiting for server to become ready...');
|
|
49
|
+
|
|
50
|
+
const ready = await health.waitUntilReady(conf.host, conf.port, 60000, 2000);
|
|
51
|
+
|
|
52
|
+
if (ready) {
|
|
53
|
+
console.log(`[claude-ws] Server is ready at http://${conf.host}:${conf.port}`);
|
|
54
|
+
|
|
55
|
+
// Open browser unless --no-open
|
|
56
|
+
if (!flags['no-open']) {
|
|
57
|
+
try {
|
|
58
|
+
const openCmd = require('../commands/open');
|
|
59
|
+
openCmd.openUrl(`http://${conf.host}:${conf.port}`);
|
|
60
|
+
} catch {
|
|
61
|
+
// Non-critical — don't fail the start
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
console.log('[claude-ws] Server did not respond within 60s.');
|
|
66
|
+
console.log('[claude-ws] Check logs: claude-ws logs');
|
|
67
|
+
console.log('[claude-ws] The daemon may still be starting up (building, installing deps, etc.).');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { run };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `claude-ws status` — Show daemon status, PID, URL, and health.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const config = require('../config');
|
|
6
|
+
const daemon = require('../daemon');
|
|
7
|
+
const health = require('../health');
|
|
8
|
+
|
|
9
|
+
async function run(_argv) {
|
|
10
|
+
const { running, pid } = daemon.checkRunning();
|
|
11
|
+
|
|
12
|
+
if (!running) {
|
|
13
|
+
console.log('[claude-ws] Status: NOT RUNNING');
|
|
14
|
+
process.exit(0);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const conf = config.resolve({});
|
|
18
|
+
const result = await health.check(conf.host, conf.port);
|
|
19
|
+
|
|
20
|
+
console.log('[claude-ws] Status: RUNNING');
|
|
21
|
+
console.log(`[claude-ws] PID: ${pid}`);
|
|
22
|
+
console.log(`[claude-ws] URL: http://${conf.host}:${conf.port}`);
|
|
23
|
+
|
|
24
|
+
if (result.ok) {
|
|
25
|
+
console.log(`[claude-ws] Health: OK (HTTP ${result.statusCode})`);
|
|
26
|
+
} else {
|
|
27
|
+
console.log(`[claude-ws] Health: UNREACHABLE (${result.error})`);
|
|
28
|
+
console.log('[claude-ws] The server process is running but not responding to HTTP.');
|
|
29
|
+
console.log('[claude-ws] It may still be starting up. Check: claude-ws logs');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { run };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `claude-ws stop` — Stop the running daemon.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const daemon = require('../daemon');
|
|
6
|
+
|
|
7
|
+
async function run(_argv) {
|
|
8
|
+
const { running, pid } = daemon.checkRunning();
|
|
9
|
+
|
|
10
|
+
if (!running) {
|
|
11
|
+
console.log('[claude-ws] No running daemon found.');
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
console.log(`[claude-ws] Stopping daemon (PID ${pid})...`);
|
|
16
|
+
|
|
17
|
+
// Send SIGTERM
|
|
18
|
+
const sent = daemon.sendSignal(pid, 'SIGTERM');
|
|
19
|
+
if (!sent) {
|
|
20
|
+
console.log('[claude-ws] Failed to send SIGTERM — process may have already exited.');
|
|
21
|
+
daemon.removePid();
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Poll for exit (up to 10s)
|
|
26
|
+
const exited = await daemon.waitForExit(pid, 10000);
|
|
27
|
+
|
|
28
|
+
if (exited) {
|
|
29
|
+
daemon.removePid();
|
|
30
|
+
console.log('[claude-ws] Daemon stopped.');
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Force kill
|
|
35
|
+
console.log('[claude-ws] Daemon did not exit gracefully, sending SIGKILL...');
|
|
36
|
+
daemon.sendSignal(pid, 'SIGKILL');
|
|
37
|
+
|
|
38
|
+
const killed = await daemon.waitForExit(pid, 5000);
|
|
39
|
+
daemon.removePid();
|
|
40
|
+
|
|
41
|
+
if (killed) {
|
|
42
|
+
console.log('[claude-ws] Daemon killed.');
|
|
43
|
+
} else {
|
|
44
|
+
console.log(`[claude-ws] Warning: Process ${pid} may still be running.`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { run };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loader for claude-ws daemon mode.
|
|
3
|
+
*
|
|
4
|
+
* Priority: CLI flags > env vars > config file > defaults.
|
|
5
|
+
* Config file: ~/.claude-ws/config.json
|
|
6
|
+
* PID file: ~/.claude-ws/claude-ws.pid
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
|
|
13
|
+
const CLAUDE_WS_DIR = path.join(os.homedir(), '.claude-ws');
|
|
14
|
+
const CONFIG_PATH = path.join(CLAUDE_WS_DIR, 'config.json');
|
|
15
|
+
const PID_PATH = path.join(CLAUDE_WS_DIR, 'claude-ws.pid');
|
|
16
|
+
|
|
17
|
+
const DEFAULTS = {
|
|
18
|
+
port: 8556,
|
|
19
|
+
host: 'localhost',
|
|
20
|
+
dataDir: path.join(CLAUDE_WS_DIR, 'data'),
|
|
21
|
+
logDir: path.join(CLAUDE_WS_DIR, 'logs'),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Ensure ~/.claude-ws/ directory exists.
|
|
26
|
+
*/
|
|
27
|
+
function ensureDir() {
|
|
28
|
+
if (!fs.existsSync(CLAUDE_WS_DIR)) {
|
|
29
|
+
fs.mkdirSync(CLAUDE_WS_DIR, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Load config file if it exists.
|
|
35
|
+
* @returns {object}
|
|
36
|
+
*/
|
|
37
|
+
function loadConfigFile() {
|
|
38
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
|
43
|
+
return JSON.parse(raw);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error(`[claude-ws] Warning: Failed to parse ${CONFIG_PATH}: ${err.message}`);
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the final configuration by merging defaults, config file, env vars,
|
|
52
|
+
* and CLI flags (passed as an object).
|
|
53
|
+
*
|
|
54
|
+
* @param {object} [cliFlags={}] - Parsed CLI flags (e.g. { port: '3000', host: '0.0.0.0' })
|
|
55
|
+
* @returns {{ port: number, host: string, dataDir: string, logDir: string }}
|
|
56
|
+
*/
|
|
57
|
+
function resolve(cliFlags = {}) {
|
|
58
|
+
ensureDir();
|
|
59
|
+
|
|
60
|
+
const file = loadConfigFile();
|
|
61
|
+
|
|
62
|
+
const port = parseInt(
|
|
63
|
+
cliFlags.port || process.env.PORT || file.port || DEFAULTS.port,
|
|
64
|
+
10,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const host =
|
|
68
|
+
cliFlags.host || process.env.HOST || file.host || DEFAULTS.host;
|
|
69
|
+
|
|
70
|
+
const dataDir =
|
|
71
|
+
cliFlags['data-dir'] || process.env.DATA_DIR || file.dataDir || DEFAULTS.dataDir;
|
|
72
|
+
|
|
73
|
+
const logDir =
|
|
74
|
+
cliFlags['log-dir'] || process.env.LOG_DIR || file.logDir || DEFAULTS.logDir;
|
|
75
|
+
|
|
76
|
+
// Ensure log and data dirs exist
|
|
77
|
+
for (const dir of [dataDir, logDir]) {
|
|
78
|
+
if (!fs.existsSync(dir)) {
|
|
79
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { port, host, dataDir, logDir };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
CLAUDE_WS_DIR,
|
|
88
|
+
CONFIG_PATH,
|
|
89
|
+
PID_PATH,
|
|
90
|
+
DEFAULTS,
|
|
91
|
+
ensureDir,
|
|
92
|
+
resolve,
|
|
93
|
+
};
|