commanderclaw 1.1.11 → 1.1.13

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
@@ -52,6 +52,48 @@ commanderclaw config
52
52
  | Linux | x64 |
53
53
  | Windows | x64 |
54
54
 
55
+ ## Development
56
+
57
+ ### Build
58
+
59
+ ```bash
60
+ npm install
61
+ npm run build
62
+ ```
63
+
64
+ ### Pre-Release Verification
65
+
66
+ Before publishing, verify the package works correctly:
67
+
68
+ ```bash
69
+ # 1. Create tarball
70
+ npm pack
71
+
72
+ # 2. Test in clean environment
73
+ mkdir /tmp/test-release && cd /tmp/test-release
74
+ npm init -y
75
+ npm install /path/to/commanderclaw-*.tgz
76
+
77
+ # 3. Verify gateway works
78
+ npx commanderclaw gateway start
79
+
80
+ # 4. Open http://127.0.0.1:19730 and verify:
81
+ # - WebSocket connection succeeds (check browser console)
82
+ # - API calls work (check Network tab)
83
+
84
+ # 5. If all tests pass, publish
85
+ npm publish --access public
86
+ ```
87
+
88
+ ### Release Checklist
89
+
90
+ - [ ] Update version in `package.json` and `src/cli/index.ts`
91
+ - [ ] Run `npm run build`
92
+ - [ ] Run `npm pack` and test in clean environment
93
+ - [ ] Verify WebSocket and API proxy work
94
+ - [ ] Commit and push version bump
95
+ - [ ] Run `npm publish --access public`
96
+
55
97
  ## License
56
98
 
57
99
  MIT
@@ -1,227 +1,2 @@
1
1
  #!/usr/bin/env node
2
-
3
- const path = require('path');
4
- const { spawn } = require('child_process');
5
- const os = require('os');
6
- const fs = require('fs');
7
-
8
- const command = process.argv[2];
9
-
10
- const SERVER_PORT = 19739;
11
- const WEB_PORT = 19730;
12
- const SERVER_PID_FILE = '/tmp/commanderclaw-server.pid';
13
- const WEB_PID_FILE = '/tmp/commanderclaw-web.pid';
14
- const SERVER_LOG_FILE = '/tmp/commanderclaw-server.log';
15
- const WEB_LOG_FILE = '/tmp/commanderclaw-web.log';
16
-
17
- function getPlatformId() {
18
- const platform = os.platform();
19
- const arch = os.arch();
20
-
21
- let normalizedArch = arch;
22
- if (arch === 'x64' || arch === 'amd64') {
23
- normalizedArch = 'x64';
24
- } else if (arch === 'arm64' || arch === 'aarch64') {
25
- normalizedArch = 'arm64';
26
- }
27
-
28
- return `${platform}-${normalizedArch}`;
29
- }
30
-
31
- function isProcessRunning(pidFile) {
32
- if (!fs.existsSync(pidFile)) {
33
- return false;
34
- }
35
-
36
- const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
37
- if (isNaN(pid)) {
38
- return false;
39
- }
40
-
41
- try {
42
- process.kill(pid, 0);
43
- return true;
44
- } catch {
45
- return false;
46
- }
47
- }
48
-
49
- function startServer() {
50
- const platformId = getPlatformId();
51
- const ext = os.platform() === 'win32' ? '.exe' : '';
52
- const binaryPath = path.join(
53
- __dirname,
54
- '..',
55
- 'binaries',
56
- platformId,
57
- `coordination-server${ext}`
58
- );
59
-
60
- if (!fs.existsSync(binaryPath)) {
61
- console.error(`Error: Unsupported platform: ${platformId}`);
62
- console.error('');
63
- console.error('Supported platforms:');
64
- console.error(' - darwin-x64 (macOS Intel)');
65
- console.error(' - darwin-arm64 (macOS Apple Silicon)');
66
- console.error(' - linux-x64 (Linux)');
67
- console.error(' - win32-x64 (Windows)');
68
- process.exit(1);
69
- }
70
-
71
- const configPath = path.join(__dirname, '..', 'configs', 'server.yaml');
72
- const args = ['-config', configPath, ...process.argv.slice(4)];
73
- const logFile = fs.openSync(SERVER_LOG_FILE, 'a');
74
-
75
- const child = spawn(binaryPath, args, {
76
- detached: true,
77
- stdio: ['ignore', logFile, logFile],
78
- env: { ...process.env },
79
- });
80
-
81
- child.unref();
82
- fs.writeFileSync(SERVER_PID_FILE, String(child.pid));
83
-
84
- return true;
85
- }
86
-
87
- function startWeb() {
88
- const webServerPath = path.join(__dirname, 'web-server.cjs');
89
- const webDir = path.join(__dirname, '..', 'web');
90
-
91
- if (!fs.existsSync(webDir)) {
92
- console.log('(web directory not found, skipping)');
93
- return true;
94
- }
95
-
96
- const logFile = fs.openSync(WEB_LOG_FILE, 'a');
97
-
98
- const child = spawn('node', [webServerPath], {
99
- detached: true,
100
- stdio: ['ignore', logFile, logFile],
101
- env: { ...process.env, PORT: String(WEB_PORT) },
102
- });
103
-
104
- child.unref();
105
- fs.writeFileSync(WEB_PID_FILE, String(child.pid));
106
-
107
- return true;
108
- }
109
-
110
- if (command === 'gateway' && process.argv[3] === 'start') {
111
- const serverRunning = isProcessRunning(SERVER_PID_FILE);
112
- const webRunning = isProcessRunning(WEB_PID_FILE);
113
-
114
- if (serverRunning && webRunning) {
115
- console.log('Gateway is already running');
116
- console.log(` Server: http://127.0.0.1:${SERVER_PORT}`);
117
- console.log(` Web Console: http://127.0.0.1:${WEB_PORT}`);
118
- process.exit(0);
119
- }
120
-
121
- if (!serverRunning) {
122
- process.stdout.write(`Starting server on port ${SERVER_PORT}... `);
123
- if (startServer()) {
124
- console.log('OK');
125
- } else {
126
- console.log('Failed');
127
- process.exit(1);
128
- }
129
- }
130
-
131
- if (!webRunning) {
132
- process.stdout.write(`Starting web console on port ${WEB_PORT}... `);
133
- if (startWeb()) {
134
- console.log('OK');
135
- } else {
136
- console.log('Failed');
137
- process.exit(1);
138
- }
139
- }
140
-
141
- console.log();
142
- console.log('Gateway started successfully!');
143
- console.log(` Server: http://127.0.0.1:${SERVER_PORT}`);
144
- console.log(` WebSocket: ws://127.0.0.1:${SERVER_PORT}/ws`);
145
- console.log(` Web Console: http://127.0.0.1:${WEB_PORT}`);
146
- console.log();
147
- console.log('Logs:');
148
- console.log(` Server: ${SERVER_LOG_FILE}`);
149
- console.log(` Web: ${WEB_LOG_FILE}`);
150
- process.exit(0);
151
- }
152
-
153
- else if (command === 'gateway' && process.argv[3] === 'stop') {
154
- process.stdout.write('Stopping web console... ');
155
- if (isProcessRunning(WEB_PID_FILE)) {
156
- const pid = parseInt(fs.readFileSync(WEB_PID_FILE, 'utf-8').trim(), 10);
157
- try {
158
- process.kill(pid, 'SIGTERM');
159
- } catch {}
160
- fs.unlinkSync(WEB_PID_FILE);
161
- console.log('OK');
162
- } else {
163
- console.log('(not running)');
164
- }
165
-
166
- process.stdout.write('Stopping server... ');
167
- if (isProcessRunning(SERVER_PID_FILE)) {
168
- const pid = parseInt(fs.readFileSync(SERVER_PID_FILE, 'utf-8').trim(), 10);
169
- try {
170
- process.kill(pid, 'SIGTERM');
171
- } catch {}
172
- fs.unlinkSync(SERVER_PID_FILE);
173
- console.log('OK');
174
- } else {
175
- console.log('(not running)');
176
- }
177
-
178
- console.log('Gateway stopped');
179
- process.exit(0);
180
- }
181
-
182
- else if (command === 'gateway' && process.argv[3] === 'status') {
183
- console.log('CommanderClaw Gateway Status');
184
- console.log('-'.repeat(40));
185
-
186
- const serverRunning = isProcessRunning(SERVER_PID_FILE);
187
- const webRunning = isProcessRunning(WEB_PID_FILE);
188
-
189
- let serverStatus = 'stopped';
190
- let serverPid = '';
191
- if (serverRunning) {
192
- serverStatus = 'running';
193
- serverPid = fs.readFileSync(SERVER_PID_FILE, 'utf-8').trim();
194
- }
195
-
196
- let webStatus = 'stopped';
197
- let webPid = '';
198
- if (webRunning) {
199
- webStatus = 'running';
200
- webPid = fs.readFileSync(WEB_PID_FILE, 'utf-8').trim();
201
- }
202
-
203
- console.log(`Server (port ${SERVER_PORT}): ${serverStatus}${serverPid ? ` (PID: ${serverPid})` : ''}`);
204
- console.log(`Web (port ${WEB_PORT}): ${webStatus}${webPid ? ` (PID: ${webPid})` : ''}`);
205
-
206
- if (serverRunning) {
207
- console.log();
208
- console.log(`Server URL: http://127.0.0.1:${SERVER_PORT}`);
209
- console.log(`WebSocket: ws://127.0.0.1:${SERVER_PORT}/ws`);
210
- }
211
-
212
- if (webRunning) {
213
- console.log(`Web Console: http://127.0.0.1:${WEB_PORT}`);
214
- }
215
- process.exit(0);
216
- }
217
-
218
- else {
219
- const cliPath = path.join(__dirname, '..', 'dist', 'cli', 'index.js');
220
-
221
- if (!fs.existsSync(cliPath)) {
222
- console.error('Error: CLI not built. Run: npm run build');
223
- process.exit(1);
224
- }
225
-
226
- require(cliPath);
227
- }
2
+ import('../dist/cli/index.js');
@@ -1,8 +1,10 @@
1
1
  const http = require('http');
2
+ const net = require('net');
2
3
  const fs = require('fs');
3
4
  const path = require('path');
4
5
 
5
- const PORT = process.env.PORT || 19730;
6
+ const WEB_PORT = process.env.PORT || 19730;
7
+ const SERVER_PORT = 19739;
6
8
  const WEB_DIR = path.join(__dirname, '..', 'web');
7
9
 
8
10
  const MIME_TYPES = {
@@ -16,6 +18,11 @@ const MIME_TYPES = {
16
18
  };
17
19
 
18
20
  const server = http.createServer((req, res) => {
21
+ if (req.url.startsWith('/api/')) {
22
+ proxyRequest(req, res, SERVER_PORT);
23
+ return;
24
+ }
25
+
19
26
  let filePath = path.join(WEB_DIR, req.url === '/' ? 'index.html' : req.url);
20
27
  const ext = path.extname(filePath).toLowerCase();
21
28
  const contentType = MIME_TYPES[ext] || 'application/octet-stream';
@@ -43,6 +50,87 @@ const server = http.createServer((req, res) => {
43
50
  });
44
51
  });
45
52
 
46
- server.listen(PORT, '0.0.0.0', () => {
47
- console.log(`Web console running at http://127.0.0.1:${PORT}`);
53
+ function proxyRequest(req, res, targetPort) {
54
+ const options = {
55
+ hostname: '127.0.0.1',
56
+ port: targetPort,
57
+ path: req.url,
58
+ method: req.method,
59
+ headers: {
60
+ ...req.headers,
61
+ host: `127.0.0.1:${targetPort}`,
62
+ },
63
+ };
64
+
65
+ const proxyReq = http.request(options, (proxyRes) => {
66
+ res.writeHead(proxyRes.statusCode, proxyRes.headers);
67
+ proxyRes.pipe(res);
68
+ });
69
+
70
+ proxyReq.on('error', (err) => {
71
+ console.error('Proxy error:', err.message);
72
+ res.writeHead(502);
73
+ res.end(JSON.stringify({ error: 'Bad Gateway' }));
74
+ });
75
+
76
+ req.pipe(proxyReq);
77
+ }
78
+
79
+ server.on('upgrade', (req, socket, head) => {
80
+ if (req.url === '/ws' || req.url.startsWith('/ws?')) {
81
+ const targetPort = SERVER_PORT;
82
+
83
+ const proxyReq = http.request({
84
+ hostname: '127.0.0.1',
85
+ port: targetPort,
86
+ path: req.url,
87
+ method: 'GET',
88
+ headers: {
89
+ ...req.headers,
90
+ host: `127.0.0.1:${targetPort}`,
91
+ },
92
+ });
93
+
94
+ proxyReq.on('response', (res) => {
95
+ res.pipe(socket);
96
+ });
97
+
98
+ proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => {
99
+ proxyRes.on('end', () => {
100
+ socket.end();
101
+ });
102
+
103
+ socket.on('error', (err) => {
104
+ proxySocket.end();
105
+ });
106
+
107
+ proxySocket.on('error', (err) => {
108
+ socket.end();
109
+ });
110
+
111
+ socket.write(
112
+ 'HTTP/1.1 101 Switching Protocols\r\n' +
113
+ Object.entries(proxyRes.headers)
114
+ .map(([k, v]) => `${k}: ${v}`)
115
+ .join('\r\n') +
116
+ '\r\n\r\n'
117
+ );
118
+
119
+ proxySocket.pipe(socket);
120
+ socket.pipe(proxySocket);
121
+ });
122
+
123
+ proxyReq.on('error', (err) => {
124
+ console.error('WebSocket proxy error:', err.message);
125
+ socket.end();
126
+ });
127
+
128
+ proxyReq.end();
129
+ }
130
+ });
131
+
132
+ server.listen(WEB_PORT, '0.0.0.0', () => {
133
+ console.log(`Web console running at http://127.0.0.1:${WEB_PORT}`);
134
+ console.log(`Proxying /ws -> ws://127.0.0.1:${SERVER_PORT}/ws`);
135
+ console.log(`Proxying /api -> http://127.0.0.1:${SERVER_PORT}/api`);
48
136
  });
@@ -1,8 +1,12 @@
1
1
  import * as path from 'path';
2
2
  import * as fs from 'fs';
3
3
  import * as os from 'os';
4
+ import * as http from 'http';
5
+ import { fileURLToPath } from 'url';
4
6
  import { spawn, execSync } from 'child_process';
5
7
  import { getProjectRoot } from './utils.js';
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
6
10
  const SERVER_PORT = 19739;
7
11
  const WEB_PORT = 19730;
8
12
  const SERVER_PID_FILE = '/tmp/commanderclaw-server.pid';
@@ -108,7 +112,7 @@ function stopProcess(pidFile) {
108
112
  catch { }
109
113
  fs.unlinkSync(pidFile);
110
114
  }
111
- function gatewayStart() {
115
+ async function gatewayStart() {
112
116
  const serverRunning = isProcessRunning(SERVER_PID_FILE);
113
117
  const webRunning = isProcessRunning(WEB_PID_FILE);
114
118
  if (serverRunning && webRunning) {
@@ -120,7 +124,14 @@ function gatewayStart() {
120
124
  if (!serverRunning) {
121
125
  process.stdout.write(`Starting server on port ${SERVER_PORT}... `);
122
126
  if (startServer()) {
123
- console.log('OK');
127
+ const started = await waitForServer(SERVER_PORT, 5000);
128
+ if (started) {
129
+ console.log('OK');
130
+ }
131
+ else {
132
+ console.log('Failed (timeout waiting for server)');
133
+ process.exit(1);
134
+ }
124
135
  }
125
136
  else {
126
137
  console.log('Failed');
@@ -150,26 +161,58 @@ function gatewayStart() {
150
161
  function startServer() {
151
162
  try {
152
163
  const binaryPath = getBinaryPath();
153
- const logFile = fs.openSync(SERVER_LOG_FILE, 'a');
164
+ if (!fs.existsSync(binaryPath)) {
165
+ console.error(`Server binary not found: ${binaryPath}`);
166
+ return false;
167
+ }
168
+ const logStream = fs.createWriteStream(SERVER_LOG_FILE, { flags: 'a' });
154
169
  const child = spawn(binaryPath, ['-port', String(SERVER_PORT)], {
155
170
  detached: true,
156
- stdio: ['ignore', logFile, logFile],
171
+ stdio: ['ignore', 'pipe', 'pipe'],
172
+ });
173
+ child.stdout?.pipe(logStream);
174
+ child.stderr?.pipe(logStream);
175
+ child.on('error', (err) => {
176
+ console.error(`Failed to start server: ${err.message}`);
157
177
  });
158
178
  child.unref();
159
- fs.writeFileSync(SERVER_PID_FILE, String(child.pid));
160
- // Wait a bit to verify it started
161
- setTimeout(() => {
162
- if (!isProcessRunning(SERVER_PID_FILE)) {
163
- console.log('\nServer process exited immediately. Check logs at ' + SERVER_LOG_FILE);
164
- process.exit(1);
165
- }
166
- }, 500);
179
+ const pid = child.pid;
180
+ if (!pid) {
181
+ return false;
182
+ }
183
+ fs.writeFileSync(SERVER_PID_FILE, String(pid));
167
184
  return true;
168
185
  }
169
186
  catch (e) {
187
+ console.error(`Failed to start server: ${e.message}`);
170
188
  return false;
171
189
  }
172
190
  }
191
+ function waitForServer(port, timeoutMs = 5000) {
192
+ return new Promise((resolve) => {
193
+ const startTime = Date.now();
194
+ const check = () => {
195
+ if (Date.now() - startTime > timeoutMs) {
196
+ resolve(false);
197
+ return;
198
+ }
199
+ const req = http.get(`http://127.0.0.1:${port}/api/health`, { timeout: 500 }, (res) => {
200
+ if (res.statusCode === 200) {
201
+ resolve(true);
202
+ }
203
+ else {
204
+ setTimeout(check, 200);
205
+ }
206
+ });
207
+ req.on('error', () => setTimeout(check, 200));
208
+ req.on('timeout', () => {
209
+ req.destroy();
210
+ setTimeout(check, 200);
211
+ });
212
+ };
213
+ check();
214
+ });
215
+ }
173
216
  function startWeb() {
174
217
  try {
175
218
  const packageDir = path.dirname(path.dirname(__dirname));
@@ -212,12 +255,12 @@ function gatewayStop() {
212
255
  }
213
256
  console.log('Gateway stopped');
214
257
  }
215
- function gatewayRestart() {
258
+ async function gatewayRestart() {
216
259
  console.log('Restarting gateway...');
217
260
  gatewayStop();
218
- setTimeout(() => {
261
+ setTimeout(async () => {
219
262
  console.log();
220
- gatewayStart();
263
+ await gatewayStart();
221
264
  }, 500);
222
265
  }
223
266
  function gatewayStatus() {
package/dist/cli/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { handleGateway } from './gateway.js';
2
2
  import { handlePlugin } from './plugin.js';
3
3
  import { handleConfig } from './config.js';
4
- const VERSION = '1.1.11';
4
+ const VERSION = '1.1.13';
5
5
  function printUsage() {
6
6
  console.log('CommanderClaw - Multi-device Agent Coordination Framework');
7
7
  console.log();
@@ -2,7 +2,7 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import * as os from 'os';
4
4
  import { execSync } from 'child_process';
5
- import { getPlatforms, findPlatform, rlWithDefault, maskToken, getTokenFromServer, backupFile, restoreBackup, DEFAULT_SERVER_URL, DEFAULT_DEVICE_NAME, } from './utils.js';
5
+ import { getPlatforms, findPlatform, rl, rlWithDefault, backupFile, restoreBackup, DEFAULT_SERVER_URL, DEFAULT_DEVICE_NAME, } from './utils.js';
6
6
  export function handlePlugin(args) {
7
7
  if (args.length === 0) {
8
8
  printPluginUsage();
@@ -123,20 +123,6 @@ async function handlePluginInstall(platformName) {
123
123
  console.log();
124
124
  // Collect configuration
125
125
  const config = await collectInstallConfig(platform);
126
- // Get token from server
127
- try {
128
- console.log('Fetching token from server...');
129
- const token = await getTokenFromServer(config.serverUrl);
130
- config.token = token;
131
- console.log(`✓ Token obtained: ${maskToken(token)}`);
132
- console.log();
133
- }
134
- catch (e) {
135
- console.log(`Error: Could not get token from server: ${e.message}`);
136
- console.log('Make sure the CommanderClaw server is running:');
137
- console.log(' commanderclaw gateway start');
138
- process.exit(1);
139
- }
140
126
  // Backup config
141
127
  const backupPath = platform.configPath + '.backup';
142
128
  backupFile(platform.configPath, backupPath);
@@ -226,10 +212,16 @@ async function collectInstallConfig(platform) {
226
212
  const version = await rlWithDefault('Plugin version', 'latest');
227
213
  const serverUrl = await rlWithDefault('Server URL', DEFAULT_SERVER_URL);
228
214
  const deviceName = await rlWithDefault('Device name', DEFAULT_DEVICE_NAME);
215
+ const token = await rl('Authentication token: ');
216
+ if (!token) {
217
+ console.log('Error: Token is required');
218
+ console.log('Get token from web console: http://127.0.0.1:19730 (Settings → Security)');
219
+ process.exit(1);
220
+ }
229
221
  return {
230
222
  version,
231
223
  serverUrl,
232
- token: '',
224
+ token,
233
225
  deviceName,
234
226
  autoConnect: true,
235
227
  reconnect: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commanderclaw",
3
- "version": "1.1.11",
3
+ "version": "1.1.13",
4
4
  "description": "Multi-device Agent Coordination Framework - CLI and OpenClaw/QClaw Plugin",
5
5
  "type": "module",
6
6
  "bin": {