@cloudcli-ai/cloudcli 1.28.1 → 1.29.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/README.de.md +11 -0
- package/README.ja.md +11 -0
- package/README.ko.md +13 -1
- package/README.md +31 -21
- package/README.ru.md +12 -1
- package/README.zh-CN.md +13 -1
- package/dist/assets/{index-_9VG3czA.js → index-BOWaxSnV.js} +234 -234
- package/dist/index.html +1 -1
- package/package.json +3 -2
- package/server/cli.js +360 -3
- package/server/index.js +12 -5
package/dist/index.html
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
<!-- Prevent zoom on iOS -->
|
|
27
27
|
<meta name="format-detection" content="telephone=no" />
|
|
28
|
-
<script type="module" crossorigin src="/assets/index-
|
|
28
|
+
<script type="module" crossorigin src="/assets/index-BOWaxSnV.js"></script>
|
|
29
29
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-D7WwDXvu.js">
|
|
30
30
|
<link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-NA4v81it.js">
|
|
31
31
|
<link rel="modulepreload" crossorigin href="/assets/vendor-xterm-CJZjLICi.js">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudcli-ai/cloudcli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.1",
|
|
4
4
|
"description": "A web-based UI for Claude Code CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server/index.js",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"release": "./release.sh",
|
|
36
36
|
"prepublishOnly": "npm run build",
|
|
37
37
|
"postinstall": "node scripts/fix-node-pty.js",
|
|
38
|
-
"prepare": "husky"
|
|
38
|
+
"prepare": "husky",
|
|
39
|
+
"update:platform": "./update-platform.sh"
|
|
39
40
|
},
|
|
40
41
|
"keywords": [
|
|
41
42
|
"claude code",
|
package/server/cli.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Commands:
|
|
8
8
|
* (no args) - Start the server (default)
|
|
9
9
|
* start - Start the server
|
|
10
|
+
* sandbox - Manage Docker sandbox environments
|
|
10
11
|
* status - Show configuration and data locations
|
|
11
12
|
* help - Show help information
|
|
12
13
|
* version - Show version information
|
|
@@ -150,6 +151,7 @@ Usage:
|
|
|
150
151
|
|
|
151
152
|
Commands:
|
|
152
153
|
start Start the CloudCLI server (default)
|
|
154
|
+
sandbox Manage Docker sandbox environments
|
|
153
155
|
status Show configuration and data locations
|
|
154
156
|
update Update to the latest version
|
|
155
157
|
help Show this help information
|
|
@@ -164,8 +166,7 @@ Options:
|
|
|
164
166
|
Examples:
|
|
165
167
|
$ cloudcli # Start with defaults
|
|
166
168
|
$ cloudcli --port 8080 # Start on port 8080
|
|
167
|
-
$ cloudcli -
|
|
168
|
-
$ cloudcli start --port 4000 # Explicit start command
|
|
169
|
+
$ cloudcli sandbox ~/my-project # Run in a Docker sandbox
|
|
169
170
|
$ cloudcli status # Show configuration
|
|
170
171
|
|
|
171
172
|
Environment Variables:
|
|
@@ -244,6 +245,355 @@ async function updatePackage() {
|
|
|
244
245
|
}
|
|
245
246
|
}
|
|
246
247
|
|
|
248
|
+
// ── Sandbox command ─────────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
const SANDBOX_TEMPLATES = {
|
|
251
|
+
claude: 'docker.io/cloudcliai/sandbox:claude-code',
|
|
252
|
+
codex: 'docker.io/cloudcliai/sandbox:codex',
|
|
253
|
+
gemini: 'docker.io/cloudcliai/sandbox:gemini',
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const SANDBOX_SECRETS = {
|
|
257
|
+
claude: 'anthropic',
|
|
258
|
+
codex: 'openai',
|
|
259
|
+
gemini: 'google',
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
function parseSandboxArgs(args) {
|
|
263
|
+
const result = {
|
|
264
|
+
subcommand: null,
|
|
265
|
+
workspace: null,
|
|
266
|
+
agent: 'claude',
|
|
267
|
+
name: null,
|
|
268
|
+
port: 3001,
|
|
269
|
+
template: null,
|
|
270
|
+
env: [],
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const subcommands = ['ls', 'stop', 'start', 'rm', 'logs', 'help'];
|
|
274
|
+
|
|
275
|
+
for (let i = 0; i < args.length; i++) {
|
|
276
|
+
const arg = args[i];
|
|
277
|
+
|
|
278
|
+
if (i === 0 && subcommands.includes(arg)) {
|
|
279
|
+
result.subcommand = arg;
|
|
280
|
+
} else if (arg === '--agent' || arg === '-a') {
|
|
281
|
+
result.agent = args[++i];
|
|
282
|
+
} else if (arg === '--name' || arg === '-n') {
|
|
283
|
+
result.name = args[++i];
|
|
284
|
+
} else if (arg === '--port') {
|
|
285
|
+
result.port = parseInt(args[++i], 10);
|
|
286
|
+
} else if (arg === '--template' || arg === '-t') {
|
|
287
|
+
result.template = args[++i];
|
|
288
|
+
} else if (arg === '--env' || arg === '-e') {
|
|
289
|
+
result.env.push(args[++i]);
|
|
290
|
+
} else if (!arg.startsWith('-')) {
|
|
291
|
+
if (!result.subcommand) {
|
|
292
|
+
result.workspace = arg;
|
|
293
|
+
} else {
|
|
294
|
+
result.name = arg; // for stop/start/rm/logs <name>
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Default subcommand based on what we got
|
|
300
|
+
if (!result.subcommand) {
|
|
301
|
+
result.subcommand = 'create';
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Derive name from workspace path if not set
|
|
305
|
+
if (!result.name && result.workspace) {
|
|
306
|
+
result.name = path.basename(path.resolve(result.workspace.replace(/^~/, os.homedir())));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Default template from agent
|
|
310
|
+
if (!result.template) {
|
|
311
|
+
result.template = SANDBOX_TEMPLATES[result.agent] || SANDBOX_TEMPLATES.claude;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function showSandboxHelp() {
|
|
318
|
+
console.log(`
|
|
319
|
+
${c.bright('CloudCLI Sandbox')} — Run CloudCLI inside Docker Sandboxes
|
|
320
|
+
|
|
321
|
+
Usage:
|
|
322
|
+
cloudcli sandbox <workspace> Create and start a sandbox
|
|
323
|
+
cloudcli sandbox <subcommand> [name] Manage sandboxes
|
|
324
|
+
|
|
325
|
+
Subcommands:
|
|
326
|
+
${c.bright('(default)')} Create a sandbox and start the web UI
|
|
327
|
+
${c.bright('ls')} List all sandboxes
|
|
328
|
+
${c.bright('start')} Restart a stopped sandbox and re-launch the web UI
|
|
329
|
+
${c.bright('stop')} Stop a sandbox (preserves state)
|
|
330
|
+
${c.bright('rm')} Remove a sandbox
|
|
331
|
+
${c.bright('logs')} Show CloudCLI server logs
|
|
332
|
+
${c.bright('help')} Show this help
|
|
333
|
+
|
|
334
|
+
Options:
|
|
335
|
+
-a, --agent <agent> Agent to use: claude, codex, gemini (default: claude)
|
|
336
|
+
-n, --name <name> Sandbox name (default: derived from workspace folder)
|
|
337
|
+
-t, --template <image> Custom template image
|
|
338
|
+
-e, --env <KEY=VALUE> Set environment variable (repeatable)
|
|
339
|
+
--port <port> Host port for the web UI (default: 3001)
|
|
340
|
+
|
|
341
|
+
Examples:
|
|
342
|
+
$ cloudcli sandbox ~/my-project
|
|
343
|
+
$ cloudcli sandbox ~/my-project --agent codex --port 8080
|
|
344
|
+
$ cloudcli sandbox ~/my-project --env SERVER_PORT=8080 --env HOST=0.0.0.0
|
|
345
|
+
$ cloudcli sandbox ls
|
|
346
|
+
$ cloudcli sandbox stop my-project
|
|
347
|
+
$ cloudcli sandbox start my-project
|
|
348
|
+
$ cloudcli sandbox rm my-project
|
|
349
|
+
|
|
350
|
+
Prerequisites:
|
|
351
|
+
1. Install sbx CLI: https://docs.docker.com/ai/sandboxes/get-started/
|
|
352
|
+
2. Authenticate and store your API key:
|
|
353
|
+
sbx login
|
|
354
|
+
sbx secret set -g anthropic # for Claude
|
|
355
|
+
sbx secret set -g openai # for Codex
|
|
356
|
+
sbx secret set -g google # for Gemini
|
|
357
|
+
|
|
358
|
+
Advanced usage:
|
|
359
|
+
For branch mode, multiple workspaces, memory limits, network policies,
|
|
360
|
+
or passing prompts to the agent, use sbx directly with the template:
|
|
361
|
+
|
|
362
|
+
sbx run --template docker.io/cloudcliai/sandbox:claude-code claude ~/my-project --branch my-feature
|
|
363
|
+
sbx run --template docker.io/cloudcliai/sandbox:claude-code claude ~/project ~/libs:ro --memory 8g
|
|
364
|
+
|
|
365
|
+
Full Docker Sandboxes docs: https://docs.docker.com/ai/sandboxes/usage/
|
|
366
|
+
`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function sandboxCommand(args) {
|
|
370
|
+
const { execFileSync } = await import('child_process');
|
|
371
|
+
|
|
372
|
+
// Safe execution — uses execFileSync (no shell) to prevent injection
|
|
373
|
+
const sbx = (subcmd, opts = {}) => {
|
|
374
|
+
const result = execFileSync('sbx', subcmd, {
|
|
375
|
+
encoding: 'utf8',
|
|
376
|
+
stdio: opts.inherit ? 'inherit' : 'pipe',
|
|
377
|
+
});
|
|
378
|
+
return result || '';
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const opts = parseSandboxArgs(args);
|
|
382
|
+
|
|
383
|
+
if (opts.subcommand === 'help') {
|
|
384
|
+
showSandboxHelp();
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Validate name (alphanumeric, hyphens, underscores only)
|
|
389
|
+
if (opts.name && !/^[\w-]+$/.test(opts.name)) {
|
|
390
|
+
console.error(`\n${c.error('❌')} Invalid sandbox name: ${opts.name}`);
|
|
391
|
+
console.log(` Names may only contain letters, numbers, hyphens, and underscores.\n`);
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Check sbx is installed
|
|
396
|
+
try {
|
|
397
|
+
sbx(['version']);
|
|
398
|
+
} catch {
|
|
399
|
+
console.error(`\n${c.error('❌')} ${c.bright('sbx')} CLI not found.\n`);
|
|
400
|
+
console.log(` Install it from: ${c.info('https://docs.docker.com/ai/sandboxes/get-started/')}`);
|
|
401
|
+
console.log(` Then run: ${c.bright('sbx login')}`);
|
|
402
|
+
console.log(` And store your API key: ${c.bright('sbx secret set -g anthropic')}\n`);
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
switch (opts.subcommand) {
|
|
407
|
+
|
|
408
|
+
case 'ls':
|
|
409
|
+
sbx(['ls'], { inherit: true });
|
|
410
|
+
break;
|
|
411
|
+
|
|
412
|
+
case 'stop':
|
|
413
|
+
if (!opts.name) {
|
|
414
|
+
console.error(`\n${c.error('❌')} Sandbox name required: cloudcli sandbox stop <name>\n`);
|
|
415
|
+
process.exit(1);
|
|
416
|
+
}
|
|
417
|
+
sbx(['stop', opts.name], { inherit: true });
|
|
418
|
+
break;
|
|
419
|
+
|
|
420
|
+
case 'rm':
|
|
421
|
+
if (!opts.name) {
|
|
422
|
+
console.error(`\n${c.error('❌')} Sandbox name required: cloudcli sandbox rm <name>\n`);
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
sbx(['rm', opts.name], { inherit: true });
|
|
426
|
+
break;
|
|
427
|
+
|
|
428
|
+
case 'logs':
|
|
429
|
+
if (!opts.name) {
|
|
430
|
+
console.error(`\n${c.error('❌')} Sandbox name required: cloudcli sandbox logs <name>\n`);
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
sbx(['exec', opts.name, 'bash', '-c', 'cat /tmp/cloudcli-ui.log'], { inherit: true });
|
|
435
|
+
} catch (e) {
|
|
436
|
+
console.error(`\n${c.error('❌')} Could not read logs: ${e.message || 'Is the sandbox running?'}\n`);
|
|
437
|
+
}
|
|
438
|
+
break;
|
|
439
|
+
|
|
440
|
+
case 'start': {
|
|
441
|
+
if (!opts.name) {
|
|
442
|
+
console.error(`\n${c.error('❌')} Sandbox name required: cloudcli sandbox start <name>\n`);
|
|
443
|
+
process.exit(1);
|
|
444
|
+
}
|
|
445
|
+
console.log(`\n${c.info('▶')} Starting sandbox ${c.bright(opts.name)}...`);
|
|
446
|
+
try {
|
|
447
|
+
sbx(['start', opts.name], { inherit: true });
|
|
448
|
+
} catch { /* might already be running */ }
|
|
449
|
+
|
|
450
|
+
console.log(`${c.info('▶')} Launching CloudCLI web server...`);
|
|
451
|
+
sbx(['exec', '-d', opts.name, 'cloudcli', 'start', '--port', '3001']);
|
|
452
|
+
|
|
453
|
+
console.log(`${c.info('▶')} Forwarding port ${opts.port} → 3001...`);
|
|
454
|
+
try {
|
|
455
|
+
sbx(['ports', opts.name, '--publish', `${opts.port}:3001`]);
|
|
456
|
+
} catch (e) {
|
|
457
|
+
const msg = e.stdout || e.stderr || e.message || '';
|
|
458
|
+
if (msg.includes('address already in use')) {
|
|
459
|
+
const altPort = opts.port + 1;
|
|
460
|
+
console.log(`${c.warn('⚠')} Port ${opts.port} in use, trying ${altPort}...`);
|
|
461
|
+
try {
|
|
462
|
+
sbx(['ports', opts.name, '--publish', `${altPort}:3001`]);
|
|
463
|
+
opts.port = altPort;
|
|
464
|
+
} catch {
|
|
465
|
+
console.error(`${c.error('❌')} Ports ${opts.port} and ${altPort} both in use. Use --port to specify a free port.`);
|
|
466
|
+
process.exit(1);
|
|
467
|
+
}
|
|
468
|
+
} else {
|
|
469
|
+
throw e;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
console.log(`\n${c.ok('✔')} ${c.bright('CloudCLI is ready!')}`);
|
|
474
|
+
console.log(` ${c.info('→')} ${c.bright(`http://localhost:${opts.port}`)}\n`);
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
case 'create': {
|
|
479
|
+
if (!opts.workspace) {
|
|
480
|
+
console.error(`\n${c.error('❌')} Workspace path required: cloudcli sandbox <path>\n`);
|
|
481
|
+
console.log(` Example: ${c.bright('cloudcli sandbox ~/my-project')}\n`);
|
|
482
|
+
process.exit(1);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const workspace = opts.workspace.startsWith('~')
|
|
486
|
+
? opts.workspace.replace(/^~/, os.homedir())
|
|
487
|
+
: path.resolve(opts.workspace);
|
|
488
|
+
|
|
489
|
+
if (!fs.existsSync(workspace)) {
|
|
490
|
+
console.error(`\n${c.error('❌')} Workspace path not found: ${c.dim(workspace)}\n`);
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const secret = SANDBOX_SECRETS[opts.agent] || 'anthropic';
|
|
495
|
+
|
|
496
|
+
// Check if the required secret is stored
|
|
497
|
+
try {
|
|
498
|
+
const secretList = sbx(['secret', 'ls']);
|
|
499
|
+
if (!secretList.includes(secret)) {
|
|
500
|
+
console.error(`\n${c.error('❌')} No ${c.bright(secret)} API key found.\n`);
|
|
501
|
+
console.log(` Run: ${c.bright(`sbx secret set -g ${secret}`)}\n`);
|
|
502
|
+
process.exit(1);
|
|
503
|
+
}
|
|
504
|
+
} catch { /* sbx secret ls not available, skip check */ }
|
|
505
|
+
|
|
506
|
+
console.log(`\n${c.bright('CloudCLI Sandbox')}`);
|
|
507
|
+
console.log(c.dim('─'.repeat(50)));
|
|
508
|
+
console.log(` Agent: ${c.info(opts.agent)} ${c.dim(`(${secret} credentials)`)}`);
|
|
509
|
+
console.log(` Workspace: ${c.dim(workspace)}`);
|
|
510
|
+
console.log(` Name: ${c.dim(opts.name)}`);
|
|
511
|
+
console.log(` Template: ${c.dim(opts.template)}`);
|
|
512
|
+
console.log(` Port: ${c.dim(String(opts.port))}`);
|
|
513
|
+
if (opts.env.length > 0) {
|
|
514
|
+
console.log(` Env: ${c.dim(opts.env.join(', '))}`);
|
|
515
|
+
}
|
|
516
|
+
console.log(c.dim('─'.repeat(50)));
|
|
517
|
+
|
|
518
|
+
// Step 1: Create sandbox
|
|
519
|
+
console.log(`\n${c.info('▶')} Creating sandbox ${c.bright(opts.name)}...`);
|
|
520
|
+
try {
|
|
521
|
+
sbx(
|
|
522
|
+
['create', '--template', opts.template, '--name', opts.name, opts.agent, workspace],
|
|
523
|
+
{ inherit: true }
|
|
524
|
+
);
|
|
525
|
+
} catch (e) {
|
|
526
|
+
const msg = e.stdout || e.stderr || e.message || '';
|
|
527
|
+
if (msg.includes('already exists')) {
|
|
528
|
+
console.log(`${c.warn('⚠')} Sandbox ${c.bright(opts.name)} already exists. Starting it instead...\n`);
|
|
529
|
+
try { sbx(['start', opts.name]); } catch { /* may already be running */ }
|
|
530
|
+
} else {
|
|
531
|
+
throw e;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Step 2: Inject environment variables
|
|
536
|
+
if (opts.env.length > 0) {
|
|
537
|
+
console.log(`${c.info('▶')} Setting environment variables...`);
|
|
538
|
+
const exports = opts.env
|
|
539
|
+
.filter(e => /^\w+=.+$/.test(e))
|
|
540
|
+
.map(e => `export ${e}`)
|
|
541
|
+
.join('\n');
|
|
542
|
+
if (exports) {
|
|
543
|
+
sbx(['exec', opts.name, 'bash', '-c', `echo '${exports}' >> /etc/sandbox-persistent.sh`]);
|
|
544
|
+
}
|
|
545
|
+
const invalid = opts.env.filter(e => !/^\w+=.+$/.test(e));
|
|
546
|
+
if (invalid.length > 0) {
|
|
547
|
+
console.log(`${c.warn('⚠')} Skipped invalid env vars: ${invalid.join(', ')} (expected KEY=VALUE)`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Step 3: Start CloudCLI as a long-running detached exec session.
|
|
552
|
+
// Using -d with a long-running command (cloudcli start never exits)
|
|
553
|
+
// keeps the exec session alive, which keeps the sandbox running.
|
|
554
|
+
console.log(`${c.info('▶')} Launching CloudCLI web server...`);
|
|
555
|
+
sbx(['exec', '-d', opts.name, 'cloudcli', 'start', '--port', '3001']);
|
|
556
|
+
|
|
557
|
+
// Step 4: Forward port
|
|
558
|
+
console.log(`${c.info('▶')} Forwarding port ${opts.port} → 3001...`);
|
|
559
|
+
try {
|
|
560
|
+
sbx(['ports', opts.name, '--publish', `${opts.port}:3001`]);
|
|
561
|
+
} catch (e) {
|
|
562
|
+
const msg = e.stdout || e.stderr || e.message || '';
|
|
563
|
+
if (msg.includes('address already in use')) {
|
|
564
|
+
const altPort = opts.port + 1;
|
|
565
|
+
console.log(`${c.warn('⚠')} Port ${opts.port} in use, trying ${altPort}...`);
|
|
566
|
+
try {
|
|
567
|
+
sbx(['ports', opts.name, '--publish', `${altPort}:3001`]);
|
|
568
|
+
opts.port = altPort;
|
|
569
|
+
} catch {
|
|
570
|
+
console.error(`${c.error('❌')} Ports ${opts.port} and ${altPort} both in use. Use --port to specify a free port.`);
|
|
571
|
+
process.exit(1);
|
|
572
|
+
}
|
|
573
|
+
} else {
|
|
574
|
+
throw e;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Done
|
|
579
|
+
console.log(`\n${c.ok('✔')} ${c.bright('CloudCLI is ready!')}`);
|
|
580
|
+
console.log(` ${c.info('→')} Open ${c.bright(`http://localhost:${opts.port}`)}`);
|
|
581
|
+
console.log(`\n${c.dim(' Manage with:')}`);
|
|
582
|
+
console.log(` ${c.dim('$')} sbx ls`);
|
|
583
|
+
console.log(` ${c.dim('$')} sbx stop ${opts.name}`);
|
|
584
|
+
console.log(` ${c.dim('$')} sbx start ${opts.name}`);
|
|
585
|
+
console.log(` ${c.dim('$')} sbx rm ${opts.name}`);
|
|
586
|
+
console.log(`\n${c.dim(' Or install globally:')} npm install -g @cloudcli-ai/cloudcli\n`);
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
default:
|
|
591
|
+
showSandboxHelp();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// ── Server ──────────────────────────────────────────────────
|
|
596
|
+
|
|
247
597
|
// Start the server
|
|
248
598
|
async function startServer() {
|
|
249
599
|
// Check for updates silently on startup
|
|
@@ -274,6 +624,10 @@ function parseArgs(args) {
|
|
|
274
624
|
parsed.command = 'version';
|
|
275
625
|
} else if (!arg.startsWith('-')) {
|
|
276
626
|
parsed.command = arg;
|
|
627
|
+
if (arg === 'sandbox') {
|
|
628
|
+
parsed.remainingArgs = args.slice(i + 1);
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
277
631
|
}
|
|
278
632
|
}
|
|
279
633
|
|
|
@@ -283,7 +637,7 @@ function parseArgs(args) {
|
|
|
283
637
|
// Main CLI handler
|
|
284
638
|
async function main() {
|
|
285
639
|
const args = process.argv.slice(2);
|
|
286
|
-
const { command, options } = parseArgs(args);
|
|
640
|
+
const { command, options, remainingArgs } = parseArgs(args);
|
|
287
641
|
|
|
288
642
|
// Apply CLI options to environment variables
|
|
289
643
|
if (options.serverPort) {
|
|
@@ -299,6 +653,9 @@ async function main() {
|
|
|
299
653
|
case 'start':
|
|
300
654
|
await startServer();
|
|
301
655
|
break;
|
|
656
|
+
case 'sandbox':
|
|
657
|
+
await sandboxCommand(remainingArgs || []);
|
|
658
|
+
break;
|
|
302
659
|
case 'status':
|
|
303
660
|
case 'info':
|
|
304
661
|
showStatus();
|
package/server/index.js
CHANGED
|
@@ -435,13 +435,20 @@ app.post('/api/system/update', authenticateToken, async (req, res) => {
|
|
|
435
435
|
|
|
436
436
|
console.log('Starting system update from directory:', projectRoot);
|
|
437
437
|
|
|
438
|
-
//
|
|
439
|
-
const updateCommand =
|
|
440
|
-
|
|
441
|
-
|
|
438
|
+
// Platform deployments use their own update workflow from the project root.
|
|
439
|
+
const updateCommand = IS_PLATFORM
|
|
440
|
+
// In platform, husky and dev dependencies are not needed
|
|
441
|
+
? 'npm run update:platform'
|
|
442
|
+
: installMode === 'git'
|
|
443
|
+
? 'git checkout main && git pull && npm install'
|
|
444
|
+
: 'npm install -g @cloudcli-ai/cloudcli@latest';
|
|
445
|
+
|
|
446
|
+
const updateCwd = IS_PLATFORM || installMode === 'git'
|
|
447
|
+
? projectRoot
|
|
448
|
+
: os.homedir();
|
|
442
449
|
|
|
443
450
|
const child = spawn('sh', ['-c', updateCommand], {
|
|
444
|
-
cwd:
|
|
451
|
+
cwd: updateCwd,
|
|
445
452
|
env: process.env
|
|
446
453
|
});
|
|
447
454
|
|