commanderclaw 1.1.3

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 ADDED
@@ -0,0 +1,57 @@
1
+ # CommanderClaw
2
+
3
+ Multi-device Agent Coordination Framework - CLI and OpenClaw/QClaw Plugin.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g commanderclaw
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### CLI Commands
14
+
15
+ ```bash
16
+ # Start gateway (server + web console)
17
+ commanderclaw gateway start
18
+
19
+ # Check gateway status
20
+ commanderclaw gateway status
21
+
22
+ # Stop gateway
23
+ commanderclaw gateway stop
24
+
25
+ # Install plugin to QClaw
26
+ commanderclaw plugin install qclaw
27
+
28
+ # Install plugin to OpenClaw
29
+ commanderclaw plugin install openclaw
30
+
31
+ # Check plugin status
32
+ commanderclaw plugin status
33
+
34
+ # Configure server URL and token
35
+ commanderclaw config
36
+ ```
37
+
38
+ ### Gateway Ports
39
+
40
+ | Service | Port |
41
+ |---------|------|
42
+ | Server | 19739 |
43
+ | WebSocket | ws://127.0.0.1:19739/ws |
44
+ | Web Console | 19730 |
45
+
46
+ ## Supported Platforms
47
+
48
+ | Platform | Architecture |
49
+ |----------|--------------|
50
+ | macOS | x64 (Intel) |
51
+ | macOS | arm64 (Apple Silicon) |
52
+ | Linux | x64 |
53
+ | Windows | x64 |
54
+
55
+ ## License
56
+
57
+ MIT
@@ -0,0 +1,77 @@
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
+ // Helper to get platform identifier
11
+ function getPlatformId() {
12
+ const platform = os.platform();
13
+ const arch = os.arch();
14
+
15
+ // Normalize architecture
16
+ let normalizedArch = arch;
17
+ if (arch === 'x64' || arch === 'amd64') {
18
+ normalizedArch = 'x64';
19
+ } else if (arch === 'arm64' || arch === 'aarch64') {
20
+ normalizedArch = 'arm64';
21
+ }
22
+
23
+ return `${platform}-${normalizedArch}`;
24
+ }
25
+
26
+ // Gateway start uses pre-compiled binary
27
+ // Other gateway commands use TypeScript implementation
28
+ if (command === 'gateway' && process.argv[3] === 'start') {
29
+ const platformId = getPlatformId();
30
+ const ext = os.platform() === 'win32' ? '.exe' : '';
31
+ const binaryPath = path.join(
32
+ __dirname,
33
+ '..',
34
+ 'binaries',
35
+ platformId,
36
+ `coordination-server${ext}`
37
+ );
38
+
39
+ if (!fs.existsSync(binaryPath)) {
40
+ console.error(`Error: Unsupported platform: ${platformId}`);
41
+ console.error('');
42
+ console.error('Supported platforms:');
43
+ console.error(' - darwin-x64 (macOS Intel)');
44
+ console.error(' - darwin-arm64 (macOS Apple Silicon)');
45
+ console.error(' - linux-x64 (Linux)');
46
+ console.error(' - win32-x64 (Windows)');
47
+ process.exit(1);
48
+ }
49
+
50
+ const args = process.argv.slice(4);
51
+ const child = spawn(binaryPath, args, {
52
+ stdio: 'inherit',
53
+ env: { ...process.env },
54
+ shell: os.platform() === 'win32'
55
+ });
56
+
57
+ child.on('error', (err) => {
58
+ console.error('Failed to start server:', err.message);
59
+ process.exit(1);
60
+ });
61
+
62
+ child.on('exit', (code) => {
63
+ process.exit(code || 0);
64
+ });
65
+ }
66
+
67
+ // Other commands use TypeScript implementation
68
+ else {
69
+ const cliPath = path.join(__dirname, '..', 'dist', 'cli', 'index.js');
70
+
71
+ if (!fs.existsSync(cliPath)) {
72
+ console.error('Error: CLI not built. Run: npm run build');
73
+ process.exit(1);
74
+ }
75
+
76
+ require(cliPath);
77
+ }
@@ -0,0 +1,116 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import { rl, rlWithDefault, maskToken, DEFAULT_SERVER_URL, DEFAULT_DEVICE_NAME } from './utils.js';
5
+ export function handleConfig() {
6
+ console.log('🦞 CommanderClaw Configuration');
7
+ console.log();
8
+ const homeDir = os.homedir();
9
+ let configPath;
10
+ if (fs.existsSync('/Applications/QClaw.app')) {
11
+ configPath = path.join(homeDir, '.qclaw', 'openclaw.json');
12
+ console.log('Detected QClaw installation.');
13
+ }
14
+ else {
15
+ configPath = path.join(homeDir, '.openclaw', 'openclaw.json');
16
+ }
17
+ const existingConfig = loadExistingConfig(configPath);
18
+ console.log('Current configuration:');
19
+ console.log(` Server URL: ${existingConfig.serverUrl}`);
20
+ if (existingConfig.token) {
21
+ console.log(` Token: ${maskToken(existingConfig.token)}`);
22
+ }
23
+ else {
24
+ console.log(' Token: (not set)');
25
+ }
26
+ console.log(` Device Name: ${existingConfig.deviceName}`);
27
+ console.log();
28
+ // Collect new config
29
+ collectConfigWithDefaults(existingConfig).then(config => {
30
+ updateConfigFile(configPath, config);
31
+ console.log();
32
+ console.log('✅ Configuration updated!');
33
+ console.log();
34
+ console.log('New configuration:');
35
+ console.log(` Server URL: ${config.serverUrl}`);
36
+ console.log(` Device Name: ${config.deviceName}`);
37
+ if (config.token) {
38
+ console.log(` Token: ${maskToken(config.token)}`);
39
+ }
40
+ console.log();
41
+ console.log('Restart your OpenClaw/QClaw to apply changes.');
42
+ });
43
+ }
44
+ function loadExistingConfig(configPath) {
45
+ const config = {
46
+ serverUrl: DEFAULT_SERVER_URL,
47
+ token: '',
48
+ deviceName: DEFAULT_DEVICE_NAME,
49
+ };
50
+ if (!fs.existsSync(configPath)) {
51
+ return config;
52
+ }
53
+ try {
54
+ const data = fs.readFileSync(configPath, 'utf-8');
55
+ const cfg = JSON.parse(data);
56
+ const cc = cfg.channels?.commanderclaw || cfg.plugins?.entries?.commanderclaw?.config || {};
57
+ return {
58
+ serverUrl: cc.serverUrl || config.serverUrl,
59
+ token: cc.token || config.token,
60
+ deviceName: cc.deviceName || config.deviceName,
61
+ };
62
+ }
63
+ catch {
64
+ return config;
65
+ }
66
+ }
67
+ async function collectConfigWithDefaults(defaults) {
68
+ const serverUrl = await rlWithDefault('CommanderClaw server URL', defaults.serverUrl);
69
+ const token = await rl('Authentication token (press Enter to skip): ');
70
+ const deviceName = await rlWithDefault('Device name', defaults.deviceName);
71
+ return {
72
+ serverUrl,
73
+ token: token || defaults.token,
74
+ deviceName,
75
+ };
76
+ }
77
+ function updateConfigFile(configPath, config) {
78
+ let cfg = {};
79
+ if (fs.existsSync(configPath)) {
80
+ try {
81
+ cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
82
+ }
83
+ catch { }
84
+ }
85
+ // Update channels
86
+ if (!cfg.channels)
87
+ cfg.channels = {};
88
+ cfg.channels.commanderclaw = {
89
+ enabled: true,
90
+ serverUrl: config.serverUrl,
91
+ token: config.token,
92
+ deviceName: config.deviceName,
93
+ autoConnect: true,
94
+ };
95
+ // Update plugins
96
+ if (!cfg.plugins)
97
+ cfg.plugins = {};
98
+ if (!cfg.plugins.entries)
99
+ cfg.plugins.entries = {};
100
+ cfg.plugins.entries.commanderclaw = {
101
+ enabled: true,
102
+ config: {
103
+ serverUrl: config.serverUrl,
104
+ token: config.token,
105
+ deviceName: config.deviceName,
106
+ autoConnect: true,
107
+ reconnect: {
108
+ enabled: true,
109
+ maxAttempts: 10,
110
+ delayMs: 5000,
111
+ },
112
+ },
113
+ };
114
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
115
+ fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2));
116
+ }
@@ -0,0 +1,250 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs';
3
+ import * as os from 'os';
4
+ import { spawn, execSync } from 'child_process';
5
+ import { getProjectRoot } from './utils.js';
6
+ const SERVER_PORT = 19739;
7
+ const WEB_PORT = 19730;
8
+ const SERVER_PID_FILE = '/tmp/commanderclaw-server.pid';
9
+ const WEB_PID_FILE = '/tmp/commanderclaw-web.pid';
10
+ const SERVER_LOG_FILE = '/tmp/commanderclaw-server.log';
11
+ const WEB_LOG_FILE = '/tmp/commanderclaw-web.log';
12
+ export function handleGateway(args) {
13
+ if (args.length === 0) {
14
+ printGatewayUsage();
15
+ process.exit(1);
16
+ }
17
+ const action = args[0];
18
+ switch (action) {
19
+ case 'start':
20
+ gatewayStart();
21
+ break;
22
+ case 'stop':
23
+ gatewayStop();
24
+ break;
25
+ case 'restart':
26
+ gatewayRestart();
27
+ break;
28
+ case 'status':
29
+ gatewayStatus();
30
+ break;
31
+ default:
32
+ console.log(`Unknown gateway action: ${action}`);
33
+ printGatewayUsage();
34
+ process.exit(1);
35
+ }
36
+ }
37
+ function printGatewayUsage() {
38
+ console.log('Gateway management commands:');
39
+ console.log();
40
+ console.log('Usage:');
41
+ console.log(' commanderclaw gateway <action>');
42
+ console.log();
43
+ console.log('Actions:');
44
+ console.log(' start Start server (port 19739) and web console (port 19730)');
45
+ console.log(' stop Stop server and web console');
46
+ console.log(' restart Restart server and web console');
47
+ console.log(' status Show status of server and web console');
48
+ }
49
+ function getBinaryPath() {
50
+ const platform = os.platform();
51
+ const arch = os.arch();
52
+ let platformId = `${platform}-${arch === 'x64' ? 'x64' : arch}`;
53
+ // Find binary in package binaries directory
54
+ const packageDir = path.dirname(path.dirname(__dirname));
55
+ const binaryDir = path.join(packageDir, 'binaries', platformId);
56
+ const ext = platform === 'win32' ? '.exe' : '';
57
+ const binaryPath = path.join(binaryDir, `coordination-server${ext}`);
58
+ if (fs.existsSync(binaryPath)) {
59
+ return binaryPath;
60
+ }
61
+ // Fallback to project root bin directory (development mode)
62
+ const projectRoot = getProjectRoot();
63
+ const devBinaryPath = path.join(projectRoot, 'bin', 'coordination-server');
64
+ if (fs.existsSync(devBinaryPath)) {
65
+ return devBinaryPath;
66
+ }
67
+ // Need to build
68
+ console.log('(building server first)...');
69
+ execSync('go build -o bin/coordination-server ./cmd/coordination-server', {
70
+ cwd: projectRoot,
71
+ stdio: 'inherit',
72
+ });
73
+ return devBinaryPath;
74
+ }
75
+ function isProcessRunning(pidFile) {
76
+ if (!fs.existsSync(pidFile)) {
77
+ return false;
78
+ }
79
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
80
+ if (isNaN(pid)) {
81
+ return false;
82
+ }
83
+ try {
84
+ process.kill(pid, 0);
85
+ return true;
86
+ }
87
+ catch {
88
+ return false;
89
+ }
90
+ }
91
+ function stopProcess(pidFile) {
92
+ if (!fs.existsSync(pidFile)) {
93
+ return;
94
+ }
95
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
96
+ if (isNaN(pid)) {
97
+ return;
98
+ }
99
+ try {
100
+ process.kill(pid, 'SIGTERM');
101
+ setTimeout(() => {
102
+ try {
103
+ process.kill(pid, 'SIGKILL');
104
+ }
105
+ catch { }
106
+ }, 500);
107
+ }
108
+ catch { }
109
+ fs.unlinkSync(pidFile);
110
+ }
111
+ function gatewayStart() {
112
+ const serverRunning = isProcessRunning(SERVER_PID_FILE);
113
+ const webRunning = isProcessRunning(WEB_PID_FILE);
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
+ return;
119
+ }
120
+ if (!serverRunning) {
121
+ process.stdout.write(`Starting server on port ${SERVER_PORT}... `);
122
+ if (startServer()) {
123
+ console.log('OK');
124
+ }
125
+ else {
126
+ console.log('Failed');
127
+ process.exit(1);
128
+ }
129
+ }
130
+ if (!webRunning) {
131
+ process.stdout.write(`Starting web console on port ${WEB_PORT}... `);
132
+ if (startWeb()) {
133
+ console.log('OK');
134
+ }
135
+ else {
136
+ console.log('Failed');
137
+ process.exit(1);
138
+ }
139
+ }
140
+ console.log();
141
+ console.log('Gateway started successfully!');
142
+ console.log(` Server: http://127.0.0.1:${SERVER_PORT}`);
143
+ console.log(` WebSocket: ws://127.0.0.1:${SERVER_PORT}/ws`);
144
+ console.log(` Web Console: http://127.0.0.1:${WEB_PORT}`);
145
+ console.log();
146
+ console.log('Logs:');
147
+ console.log(` Server: ${SERVER_LOG_FILE}`);
148
+ console.log(` Web: ${WEB_LOG_FILE}`);
149
+ }
150
+ function startServer() {
151
+ try {
152
+ const binaryPath = getBinaryPath();
153
+ const logFile = fs.openSync(SERVER_LOG_FILE, 'a');
154
+ const child = spawn(binaryPath, ['-port', String(SERVER_PORT)], {
155
+ detached: true,
156
+ stdio: ['ignore', logFile, logFile],
157
+ });
158
+ 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);
167
+ return true;
168
+ }
169
+ catch (e) {
170
+ return false;
171
+ }
172
+ }
173
+ function startWeb() {
174
+ try {
175
+ const projectRoot = getProjectRoot();
176
+ const webDir = path.join(projectRoot, 'web');
177
+ if (!fs.existsSync(webDir)) {
178
+ console.log('(web directory not found, skipping)');
179
+ return true;
180
+ }
181
+ const logFile = fs.openSync(WEB_LOG_FILE, 'a');
182
+ const child = spawn('npm', ['run', 'dev', '--', '--port', String(WEB_PORT)], {
183
+ cwd: webDir,
184
+ detached: true,
185
+ stdio: ['ignore', logFile, logFile],
186
+ env: { ...process.env, PORT: String(WEB_PORT) },
187
+ });
188
+ child.unref();
189
+ fs.writeFileSync(WEB_PID_FILE, String(child.pid));
190
+ return true;
191
+ }
192
+ catch (e) {
193
+ return false;
194
+ }
195
+ }
196
+ function gatewayStop() {
197
+ process.stdout.write('Stopping web console... ');
198
+ if (isProcessRunning(WEB_PID_FILE)) {
199
+ stopProcess(WEB_PID_FILE);
200
+ console.log('OK');
201
+ }
202
+ else {
203
+ console.log('(not running)');
204
+ }
205
+ process.stdout.write('Stopping server... ');
206
+ if (isProcessRunning(SERVER_PID_FILE)) {
207
+ stopProcess(SERVER_PID_FILE);
208
+ console.log('OK');
209
+ }
210
+ else {
211
+ console.log('(not running)');
212
+ }
213
+ console.log('Gateway stopped');
214
+ }
215
+ function gatewayRestart() {
216
+ console.log('Restarting gateway...');
217
+ gatewayStop();
218
+ setTimeout(() => {
219
+ console.log();
220
+ gatewayStart();
221
+ }, 500);
222
+ }
223
+ function gatewayStatus() {
224
+ console.log('CommanderClaw Gateway Status');
225
+ console.log('-'.repeat(40));
226
+ const serverRunning = isProcessRunning(SERVER_PID_FILE);
227
+ const webRunning = isProcessRunning(WEB_PID_FILE);
228
+ let serverStatus = 'stopped';
229
+ let serverPid = '';
230
+ if (serverRunning) {
231
+ serverStatus = 'running';
232
+ serverPid = fs.readFileSync(SERVER_PID_FILE, 'utf-8').trim();
233
+ }
234
+ let webStatus = 'stopped';
235
+ let webPid = '';
236
+ if (webRunning) {
237
+ webStatus = 'running';
238
+ webPid = fs.readFileSync(WEB_PID_FILE, 'utf-8').trim();
239
+ }
240
+ console.log(`Server (port ${SERVER_PORT}): ${serverStatus}${serverPid ? ` (PID: ${serverPid})` : ''}`);
241
+ console.log(`Web (port ${WEB_PORT}): ${webStatus}${webPid ? ` (PID: ${webPid})` : ''}`);
242
+ if (serverRunning) {
243
+ console.log();
244
+ console.log(`Server URL: http://127.0.0.1:${SERVER_PORT}`);
245
+ console.log(`WebSocket: ws://127.0.0.1:${SERVER_PORT}/ws`);
246
+ }
247
+ if (webRunning) {
248
+ console.log(`Web Console: http://127.0.0.1:${WEB_PORT}`);
249
+ }
250
+ }
@@ -0,0 +1,53 @@
1
+ import { handleGateway } from './gateway.js';
2
+ import { handlePlugin } from './plugin.js';
3
+ import { handleConfig } from './config.js';
4
+ const VERSION = '1.1.3';
5
+ function printUsage() {
6
+ console.log('CommanderClaw - Multi-device Agent Coordination Framework');
7
+ console.log();
8
+ console.log('Usage:');
9
+ console.log(' commanderclaw <command> [arguments]');
10
+ console.log();
11
+ console.log('Commands:');
12
+ console.log(' gateway Manage gateway services (server + web)');
13
+ console.log(' plugin Install/update plugin to OpenClaw/QClaw');
14
+ console.log(' config Configure server URL and token');
15
+ console.log(' help Show this help message');
16
+ console.log();
17
+ console.log('Run \'commanderclaw gateway\' for gateway commands.');
18
+ console.log('Run \'commanderclaw plugin\' for plugin commands.');
19
+ }
20
+ function main() {
21
+ const args = process.argv.slice(2);
22
+ if (args.length === 0) {
23
+ printUsage();
24
+ process.exit(1);
25
+ }
26
+ const command = args[0];
27
+ const subArgs = args.slice(1);
28
+ switch (command) {
29
+ case 'gateway':
30
+ handleGateway(subArgs);
31
+ break;
32
+ case 'plugin':
33
+ handlePlugin(subArgs);
34
+ break;
35
+ case 'config':
36
+ handleConfig();
37
+ break;
38
+ case 'help':
39
+ case '-h':
40
+ case '--help':
41
+ printUsage();
42
+ break;
43
+ case '-v':
44
+ case '--version':
45
+ console.log(`commanderclaw v${VERSION}`);
46
+ break;
47
+ default:
48
+ console.log(`Unknown command: ${command}`);
49
+ printUsage();
50
+ process.exit(1);
51
+ }
52
+ }
53
+ main();