clawrtc 1.2.1 → 1.5.0

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/clawrtc.js DELETED
@@ -1,538 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * ClawRTC — Mine RTC tokens with your AI agent on real hardware.
4
- *
5
- * Modern machines get 1x multiplier. Vintage hardware gets bonus.
6
- * VMs are detected and penalized — real iron only.
7
- *
8
- * All miner scripts are bundled with this package — no external downloads.
9
- * Network endpoint uses CA-signed TLS certificate.
10
- *
11
- * Security:
12
- * clawrtc install --dry-run Preview without installing
13
- * clawrtc install --verify Show SHA256 hashes of bundled files
14
- * clawrtc start --service Opt-in background service
15
- */
16
-
17
- const { execSync, spawn } = require('child_process');
18
- const crypto = require('crypto');
19
- const fs = require('fs');
20
- const https = require('https');
21
- const os = require('os');
22
- const path = require('path');
23
- const readline = require('readline');
24
-
25
- const VERSION = '1.2.0';
26
- const INSTALL_DIR = path.join(os.homedir(), '.clawrtc');
27
- const VENV_DIR = path.join(INSTALL_DIR, 'venv');
28
- const NODE_URL = 'https://bulbous-bouffant.metalseed.net';
29
- const DATA_DIR = path.join(__dirname, '..', 'data');
30
-
31
- // ANSI colors
32
- const C = '\x1b[36m', G = '\x1b[32m', R = '\x1b[31m', Y = '\x1b[33m';
33
- const B = '\x1b[1m', D = '\x1b[2m', NC = '\x1b[0m';
34
-
35
- const log = (m) => console.log(`${C}[clawrtc]${NC} ${m}`);
36
- const ok = (m) => console.log(`${G}[OK]${NC} ${m}`);
37
- const warn = (m) => console.log(`${Y}[WARN]${NC} ${m}`);
38
-
39
- // Bundled files shipped with the package
40
- const BUNDLED_FILES = [
41
- ['miner.py', 'miner.py'],
42
- ['fingerprint_checks.py', 'fingerprint_checks.py'],
43
- ];
44
-
45
- function sha256File(filepath) {
46
- const data = fs.readFileSync(filepath);
47
- return crypto.createHash('sha256').update(data).digest('hex');
48
- }
49
-
50
- function ask(prompt) {
51
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
52
- return new Promise((resolve) => {
53
- rl.question(prompt, (ans) => { rl.close(); resolve(ans.trim()); });
54
- });
55
- }
56
-
57
- function detectVM() {
58
- const hints = [];
59
- if (os.platform() === 'linux') {
60
- const dmiPaths = ['/sys/class/dmi/id/sys_vendor', '/sys/class/dmi/id/product_name'];
61
- const vmVendors = ['qemu', 'vmware', 'virtualbox', 'xen', 'kvm', 'microsoft', 'parallels'];
62
- for (const p of dmiPaths) {
63
- try {
64
- const val = fs.readFileSync(p, 'utf8').trim().toLowerCase();
65
- if (vmVendors.some(v => val.includes(v))) hints.push(`${p}: ${val}`);
66
- } catch (e) {}
67
- }
68
- try {
69
- const cpu = fs.readFileSync('/proc/cpuinfo', 'utf8').toLowerCase();
70
- if (cpu.includes('hypervisor')) hints.push('cpuinfo: hypervisor flag');
71
- } catch (e) {}
72
- }
73
- return hints;
74
- }
75
-
76
- function showConsentDisclosure() {
77
- console.log(`
78
- ${B}What ClawRTC will do:${NC}
79
-
80
- ${C}1. Extract${NC} Two Python scripts bundled with this package:
81
- - fingerprint_checks.py (hardware detection)
82
- - miner.py (attestation client)
83
- ${D}No external downloads — all code ships with the package.${NC}
84
-
85
- ${C}2. Install${NC} A Python virtual environment in ~/.clawrtc/
86
- with one dependency: 'requests' (HTTP library)
87
-
88
- ${C}3. Attest${NC} When started, the miner contacts the RustChain network
89
- every few minutes to prove your hardware is real.
90
- Endpoint: ${NODE_URL} (CA-signed TLS certificate)
91
-
92
- ${C}4. Collect${NC} Hardware fingerprint data sent during attestation:
93
- - CPU model, architecture, vendor
94
- - Clock timing variance (proves real oscillator)
95
- - Cache latency profile (proves real cache hierarchy)
96
- - VM detection flags (hypervisor, DMI vendor)
97
- ${D}No personal data, files, browsing history, or credentials are collected.
98
- No data is sent to any third party — only to the RustChain node.${NC}
99
-
100
- ${C}5. Earn${NC} RTC tokens accumulate in your wallet each epoch (~10 min)
101
-
102
- ${D}Verify yourself:${NC}
103
- clawrtc install --dry-run Preview without installing
104
- clawrtc install --verify Show SHA256 hashes of bundled files
105
- Source code: https://github.com/Scottcjn/Rustchain
106
- Block explorer: ${NODE_URL}/explorer
107
- `);
108
- }
109
-
110
- async function cmdInstall(flags) {
111
- console.log(`
112
- ${C}${B}
113
- ██████╗██╗ █████╗ ██╗ ██╗██████╗ ████████╗ ██████╗
114
- ██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗╚══██╔══╝██╔════╝
115
- ██║ ██║ ███████║██║ █╗ ██║██████╔╝ ██║ ██║
116
- ██║ ██║ ██╔══██║██║███╗██║██╔══██╗ ██║ ██║
117
- ╚██████╗███████╗██║ ██║╚███╔███╔╝██║ ██║ ██║ ╚██████╗
118
- ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝
119
- ${NC}
120
- ${D} Mine RTC tokens with your AI agent on real hardware${NC}
121
- ${D} Modern x86/ARM = 1x | Vintage PowerPC = up to 2.5x | VM = ~0x${NC}
122
- ${D} Version ${VERSION}${NC}
123
- `);
124
-
125
- const plat = os.platform();
126
- const arch = os.arch();
127
- log(`Platform: ${plat} | Arch: ${arch}`);
128
-
129
- if (plat !== 'linux' && plat !== 'darwin') {
130
- console.error(`${R}[ERROR]${NC} Unsupported platform: ${plat}. Use Linux or macOS.`);
131
- process.exit(1);
132
- }
133
-
134
- // --verify: show bundled file hashes and exit
135
- if (flags.verify) {
136
- log('Bundled file hashes (SHA256):');
137
- for (const [srcName, destName] of BUNDLED_FILES) {
138
- const src = path.join(DATA_DIR, srcName);
139
- if (fs.existsSync(src)) {
140
- console.log(` ${destName}: ${sha256File(src)}`);
141
- } else {
142
- console.log(` ${destName}: NOT FOUND in package`);
143
- }
144
- }
145
- return;
146
- }
147
-
148
- // --dry-run: show what would happen
149
- if (flags.dryRun) {
150
- showConsentDisclosure();
151
- log('DRY RUN — no files extracted, no services created.');
152
- return;
153
- }
154
-
155
- // Show disclosure and get consent (unless --yes)
156
- if (!flags.yes) {
157
- showConsentDisclosure();
158
- const answer = await ask(`${C}[clawrtc]${NC} Proceed with installation? [y/N] `);
159
- if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
160
- log('Installation cancelled.');
161
- return;
162
- }
163
- }
164
-
165
- // VM check
166
- const vmHints = detectVM();
167
- if (vmHints.length > 0) {
168
- console.log(`
169
- ${R}${B} ╔══════════════════════════════════════════════════════════╗
170
- ║ VM DETECTED — READ THIS ║
171
- ╠══════════════════════════════════════════════════════════╣
172
- ║ This machine appears to be a virtual machine. ║
173
- ║ RustChain will detect VMs and assign near-zero weight. ║
174
- ║ Your miner will attest but earn effectively nothing. ║
175
- ║ To earn RTC, run on bare-metal hardware. ║
176
- ╚══════════════════════════════════════════════════════════╝${NC}`);
177
- for (const h of vmHints.slice(0, 4)) console.log(` ${R} * ${h}${NC}`);
178
- console.log();
179
- }
180
-
181
- // Wallet
182
- let wallet = flags.wallet;
183
- if (!wallet) {
184
- wallet = await ask(`${C}[clawrtc]${NC} Enter agent wallet name (e.g. my-claw-agent): `);
185
- }
186
- if (!wallet) {
187
- const host = os.hostname().split('.')[0] || 'agent';
188
- wallet = `claw-${host}-${Date.now() % 100000}`;
189
- warn(`No wallet provided. Auto-generated: ${wallet}`);
190
- }
191
-
192
- // Create install dir
193
- log(`Installing to ${INSTALL_DIR}`);
194
- fs.mkdirSync(INSTALL_DIR, { recursive: true });
195
- fs.writeFileSync(path.join(INSTALL_DIR, '.wallet'), wallet);
196
-
197
- // Check for python3
198
- let pythonBin = 'python3';
199
- try { execSync('python3 --version', { stdio: 'pipe' }); }
200
- catch (e) {
201
- try { execSync('python --version', { stdio: 'pipe' }); pythonBin = 'python'; }
202
- catch (e2) {
203
- console.error(`${R}[ERROR]${NC} Python 3 not found. Install Python 3.8+ first.`);
204
- process.exit(1);
205
- }
206
- }
207
-
208
- // Create venv
209
- if (!fs.existsSync(VENV_DIR)) {
210
- log('Creating Python environment...');
211
- execSync(`${pythonBin} -m venv "${VENV_DIR}"`, { stdio: 'inherit' });
212
- }
213
-
214
- // Install deps
215
- log('Installing dependencies...');
216
- const pip = path.join(VENV_DIR, 'bin', 'pip');
217
- execSync(`"${pip}" install --upgrade pip -q`, { stdio: 'pipe' });
218
- execSync(`"${pip}" install requests -q`, { stdio: 'pipe' });
219
- ok('Dependencies ready');
220
-
221
- // Extract bundled miner files (no download!)
222
- log('Extracting bundled miner scripts...');
223
- for (const [srcName, destName] of BUNDLED_FILES) {
224
- const src = path.join(DATA_DIR, srcName);
225
- const dest = path.join(INSTALL_DIR, destName);
226
- if (!fs.existsSync(src)) {
227
- console.error(`${R}[ERROR]${NC} Bundled file missing: ${srcName}. Package may be corrupted.`);
228
- process.exit(1);
229
- }
230
- fs.copyFileSync(src, dest);
231
- const hash = sha256File(dest);
232
- const size = fs.statSync(dest).size;
233
- log(` ${destName} (${(size / 1024).toFixed(1)} KB) SHA256: ${hash.slice(0, 16)}...`);
234
- }
235
- ok('Miner files extracted from package (no external downloads)');
236
-
237
- // Setup service ONLY if --service flag is passed
238
- if (flags.service) {
239
- log('Setting up background service (--service flag)...');
240
- if (plat === 'linux') {
241
- setupSystemd(wallet);
242
- } else if (plat === 'darwin') {
243
- setupLaunchd(wallet);
244
- }
245
- } else {
246
- log('No background service created. To enable auto-start, re-run with --service');
247
- log('Or start manually: clawrtc start');
248
- }
249
-
250
- // Network check (CA-signed, no rejectUnauthorized needed)
251
- log('Checking RustChain network...');
252
- try {
253
- const data = await new Promise((resolve, reject) => {
254
- https.get(`${NODE_URL}/api/miners`, (res) => {
255
- let d = '';
256
- res.on('data', c => d += c);
257
- res.on('end', () => resolve(d));
258
- }).on('error', reject);
259
- });
260
- const miners = JSON.parse(data);
261
- log(`Active miners on network: ${miners.length}`);
262
- } catch (e) {
263
- warn('Could not reach network (node may be temporarily unavailable)');
264
- }
265
-
266
- // Anonymous install telemetry — fire-and-forget, no PII
267
- try {
268
- const payload = JSON.stringify({
269
- package: 'clawrtc', version: VERSION,
270
- platform: os.platform(), arch: os.arch(), source: 'npm'
271
- });
272
- const req = https.request('https://bottube.ai/api/telemetry/install', {
273
- method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
274
- timeout: 5000
275
- });
276
- req.on('error', () => {});
277
- req.end(payload);
278
- } catch (e) {}
279
-
280
- console.log(`
281
- ${G}${B}═══════════════════════════════════════════════════════════
282
- ClawRTC installed! Your agent is ready to mine RTC.
283
-
284
- Wallet: ${wallet}
285
- Location: ${INSTALL_DIR}
286
- Reward: 1x multiplier (modern hardware)
287
- Node: ${NODE_URL} (CA-signed TLS)
288
-
289
- Next steps:
290
- clawrtc start Start mining (foreground)
291
- clawrtc start --service Start + enable auto-restart
292
- clawrtc stop Stop mining
293
- clawrtc status Check miner + network status
294
- clawrtc logs View miner output
295
-
296
- How it works:
297
- * Your agent proves real hardware via 6 fingerprint checks
298
- * Attestation happens automatically every few minutes
299
- * RTC tokens accumulate in your wallet each epoch (~10 min)
300
- * Check balance: clawrtc status
301
-
302
- Verify & audit:
303
- * Source: https://github.com/Scottcjn/Rustchain
304
- * Explorer: ${NODE_URL}/explorer
305
- * clawrtc uninstall Remove everything cleanly
306
- ═══════════════════════════════════════════════════════════${NC}
307
- `);
308
- }
309
-
310
- function setupSystemd(wallet) {
311
- const svcDir = path.join(os.homedir(), '.config', 'systemd', 'user');
312
- fs.mkdirSync(svcDir, { recursive: true });
313
- const svcFile = path.join(svcDir, 'clawrtc-miner.service');
314
- const pythonBin = path.join(VENV_DIR, 'bin', 'python');
315
- const minerPy = path.join(INSTALL_DIR, 'miner.py');
316
-
317
- fs.writeFileSync(svcFile, `[Unit]
318
- Description=ClawRTC RTC Miner — AI Agent Mining
319
- After=network-online.target
320
- Wants=network-online.target
321
-
322
- [Service]
323
- ExecStart=${pythonBin} ${minerPy} --wallet ${wallet}
324
- Restart=always
325
- RestartSec=30
326
- WorkingDirectory=${INSTALL_DIR}
327
- Environment=PYTHONUNBUFFERED=1
328
-
329
- [Install]
330
- WantedBy=default.target
331
- `);
332
-
333
- try {
334
- execSync('systemctl --user daemon-reload', { stdio: 'pipe' });
335
- execSync('systemctl --user enable clawrtc-miner', { stdio: 'pipe' });
336
- execSync('systemctl --user start clawrtc-miner', { stdio: 'pipe' });
337
- ok('Service installed and started (auto-restarts on reboot)');
338
- } catch (e) {
339
- warn('Systemd user services not available. Use: clawrtc start');
340
- }
341
- }
342
-
343
- function setupLaunchd(wallet) {
344
- const plistDir = path.join(os.homedir(), 'Library', 'LaunchAgents');
345
- fs.mkdirSync(plistDir, { recursive: true });
346
- const plistFile = path.join(plistDir, 'com.clawrtc.miner.plist');
347
- const pythonBin = path.join(VENV_DIR, 'bin', 'python');
348
- const minerPy = path.join(INSTALL_DIR, 'miner.py');
349
-
350
- fs.writeFileSync(plistFile, `<?xml version="1.0" encoding="UTF-8"?>
351
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
352
- "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
353
- <plist version="1.0">
354
- <dict>
355
- <key>Label</key>
356
- <string>com.clawrtc.miner</string>
357
- <key>ProgramArguments</key>
358
- <array>
359
- <string>${pythonBin}</string>
360
- <string>${minerPy}</string>
361
- <string>--wallet</string>
362
- <string>${wallet}</string>
363
- </array>
364
- <key>WorkingDirectory</key>
365
- <string>${INSTALL_DIR}</string>
366
- <key>RunAtLoad</key>
367
- <true/>
368
- <key>KeepAlive</key>
369
- <true/>
370
- <key>StandardOutPath</key>
371
- <string>${path.join(INSTALL_DIR, 'miner.log')}</string>
372
- <key>StandardErrorPath</key>
373
- <string>${path.join(INSTALL_DIR, 'miner.err')}</string>
374
- </dict>
375
- </plist>`);
376
-
377
- try {
378
- execSync(`launchctl unload "${plistFile}" 2>/dev/null`, { stdio: 'pipe' });
379
- } catch (e) {}
380
- try {
381
- execSync(`launchctl load "${plistFile}"`, { stdio: 'pipe' });
382
- ok('LaunchAgent installed and loaded (auto-restarts on login)');
383
- } catch (e) {
384
- warn('Could not load LaunchAgent. Use: clawrtc start');
385
- }
386
- }
387
-
388
- function cmdStart(flags) {
389
- const plat = os.platform();
390
-
391
- if (flags.service) {
392
- const wf = path.join(INSTALL_DIR, '.wallet');
393
- const wallet = fs.existsSync(wf) ? fs.readFileSync(wf, 'utf8').trim() : 'agent';
394
- if (plat === 'linux') setupSystemd(wallet);
395
- else if (plat === 'darwin') setupLaunchd(wallet);
396
- return;
397
- }
398
-
399
- if (plat === 'linux') {
400
- const sf = path.join(os.homedir(), '.config', 'systemd', 'user', 'clawrtc-miner.service');
401
- if (fs.existsSync(sf)) {
402
- try { execSync('systemctl --user start clawrtc-miner', { stdio: 'inherit' }); ok('Miner started (systemd)'); return; } catch (e) {}
403
- }
404
- } else if (plat === 'darwin') {
405
- const pf = path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.clawrtc.miner.plist');
406
- if (fs.existsSync(pf)) {
407
- try { execSync(`launchctl load "${pf}"`, { stdio: 'inherit' }); ok('Miner started (launchd)'); return; } catch (e) {}
408
- }
409
- }
410
-
411
- const minerPy = path.join(INSTALL_DIR, 'miner.py');
412
- const pythonBin = path.join(VENV_DIR, 'bin', 'python');
413
- const wf = path.join(INSTALL_DIR, '.wallet');
414
-
415
- if (!fs.existsSync(minerPy)) { console.error(`${R}[ERROR]${NC} Miner not installed. Run: clawrtc install`); process.exit(1); }
416
-
417
- const wallet = fs.existsSync(wf) ? fs.readFileSync(wf, 'utf8').trim() : '';
418
- const walletArgs = wallet ? ['--wallet', wallet] : [];
419
- log('Starting miner in foreground (Ctrl+C to stop)...');
420
- log('Tip: Use "clawrtc start --service" for background auto-restart');
421
- const child = spawn(pythonBin, [minerPy, ...walletArgs], { stdio: 'inherit' });
422
- child.on('exit', (code) => process.exit(code || 0));
423
- }
424
-
425
- function cmdStop() {
426
- if (os.platform() === 'linux') {
427
- try { execSync('systemctl --user stop clawrtc-miner', { stdio: 'pipe' }); } catch (e) {}
428
- } else if (os.platform() === 'darwin') {
429
- const pf = path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.clawrtc.miner.plist');
430
- try { execSync(`launchctl unload "${pf}"`, { stdio: 'pipe' }); } catch (e) {}
431
- }
432
- ok('Miner stopped');
433
- }
434
-
435
- function cmdStatus() {
436
- const plat = os.platform();
437
- if (plat === 'linux') {
438
- const sf = path.join(os.homedir(), '.config', 'systemd', 'user', 'clawrtc-miner.service');
439
- if (fs.existsSync(sf)) { try { execSync('systemctl --user status clawrtc-miner', { stdio: 'inherit' }); } catch (e) {} }
440
- else log('No background service configured. Use: clawrtc start --service');
441
- }
442
-
443
- const wf = path.join(INSTALL_DIR, '.wallet');
444
- if (fs.existsSync(wf)) log(`Wallet: ${fs.readFileSync(wf, 'utf8').trim()}`);
445
-
446
- for (const filename of ['miner.py', 'fingerprint_checks.py']) {
447
- const fp = path.join(INSTALL_DIR, filename);
448
- if (fs.existsSync(fp)) log(`${filename} SHA256: ${sha256File(fp).slice(0, 16)}...`);
449
- }
450
-
451
- https.get(`${NODE_URL}/health`, (res) => {
452
- let d = '';
453
- res.on('data', c => d += c);
454
- res.on('end', () => {
455
- try { const h = JSON.parse(d); log(`Network: ${h.ok ? 'online' : 'offline'} (v${h.version || '?'})`); }
456
- catch (e) { warn('Could not parse network status'); }
457
- });
458
- }).on('error', () => warn('Could not reach network'));
459
- }
460
-
461
- function cmdLogs() {
462
- if (os.platform() === 'linux') {
463
- const sf = path.join(os.homedir(), '.config', 'systemd', 'user', 'clawrtc-miner.service');
464
- if (fs.existsSync(sf)) { spawn('journalctl', ['--user', '-u', 'clawrtc-miner', '-f', '--no-pager', '-n', '50'], { stdio: 'inherit' }); }
465
- else { const lf = path.join(INSTALL_DIR, 'miner.log'); if (fs.existsSync(lf)) spawn('tail', ['-f', lf], { stdio: 'inherit' }); else warn('No logs found.'); }
466
- } else {
467
- const lf = path.join(INSTALL_DIR, 'miner.log');
468
- if (fs.existsSync(lf)) spawn('tail', ['-f', lf], { stdio: 'inherit' }); else warn('No log file found');
469
- }
470
- }
471
-
472
- function cmdUninstall() {
473
- log('Stopping miner...');
474
- cmdStop();
475
- if (os.platform() === 'linux') {
476
- try { execSync('systemctl --user disable clawrtc-miner', { stdio: 'pipe' }); } catch (e) {}
477
- const sf = path.join(os.homedir(), '.config', 'systemd', 'user', 'clawrtc-miner.service');
478
- try { fs.unlinkSync(sf); } catch (e) {}
479
- try { execSync('systemctl --user daemon-reload', { stdio: 'pipe' }); } catch (e) {}
480
- } else if (os.platform() === 'darwin') {
481
- const pf = path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.clawrtc.miner.plist');
482
- try { fs.unlinkSync(pf); } catch (e) {}
483
- }
484
- try { fs.rmSync(INSTALL_DIR, { recursive: true, force: true }); } catch (e) {}
485
- ok('ClawRTC miner fully uninstalled — no files remain');
486
- }
487
-
488
- function showHelp() {
489
- console.log(`
490
- ClawRTC v${VERSION} — Mine RTC tokens with your AI agent on real hardware
491
-
492
- Commands:
493
- clawrtc install [--wallet NAME] Install miner and configure wallet
494
- clawrtc start Start mining (foreground)
495
- clawrtc start --service Start + create background service
496
- clawrtc stop Stop mining
497
- clawrtc status Check miner + network status + file hashes
498
- clawrtc logs View miner output
499
- clawrtc uninstall Remove everything cleanly
500
-
501
- Security & Verification:
502
- clawrtc install --dry-run Preview without installing
503
- clawrtc install --verify Show SHA256 hashes of bundled files
504
- clawrtc install -y Skip consent prompt (for CI/automation)
505
-
506
- All miner code is bundled in the package. No external downloads.
507
- Network endpoint: ${NODE_URL} (CA-signed TLS certificate)
508
-
509
- Source: https://github.com/Scottcjn/Rustchain
510
- `);
511
- }
512
-
513
- // Parse flags
514
- const args = process.argv.slice(2);
515
- const cmd = args[0];
516
- const flags = {
517
- wallet: null,
518
- dryRun: args.includes('--dry-run'),
519
- verify: args.includes('--verify'),
520
- service: args.includes('--service'),
521
- yes: args.includes('-y') || args.includes('--yes'),
522
- };
523
- const walletIdx = args.indexOf('--wallet');
524
- if (walletIdx >= 0) flags.wallet = args[walletIdx + 1];
525
-
526
- switch (cmd) {
527
- case 'install': cmdInstall(flags); break;
528
- case 'start': cmdStart(flags); break;
529
- case 'stop': cmdStop(); break;
530
- case 'status': cmdStatus(); break;
531
- case 'logs': cmdLogs(); break;
532
- case 'uninstall': cmdUninstall(); break;
533
- case '--help': case '-h': showHelp(); break;
534
- case undefined: showHelp(); break;
535
- default:
536
- console.error(`Unknown command: ${cmd}. Use --help for usage.`);
537
- process.exit(1);
538
- }
File without changes