@pocketenv/cli 0.2.5 → 0.3.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/dist/index.js CHANGED
@@ -22,7 +22,7 @@ import relativeTime from 'dayjs/plugin/relativeTime.js';
22
22
  import { password, editor, input } from '@inquirer/prompts';
23
23
  import sodium from 'libsodium-wrappers';
24
24
 
25
- var version = "0.2.5";
25
+ var version = "0.3.1";
26
26
 
27
27
  async function getAccessToken() {
28
28
  const tokenPath = path.join(os.homedir(), ".pocketenv", "token.json");
@@ -410,17 +410,23 @@ async function ssh(sandboxName) {
410
410
  }
411
411
  }
412
412
 
413
- async function start(name, { ssh: ssh$1 }) {
413
+ async function start(name, { ssh: ssh$1, repo }) {
414
414
  const token = await getAccessToken();
415
415
  try {
416
- await client.post("/xrpc/io.pocketenv.sandbox.startSandbox", void 0, {
417
- params: {
418
- id: name
416
+ await client.post(
417
+ "/xrpc/io.pocketenv.sandbox.startSandbox",
418
+ {
419
+ repo
419
420
  },
420
- headers: {
421
- Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
421
+ {
422
+ params: {
423
+ id: name
424
+ },
425
+ headers: {
426
+ Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
427
+ }
422
428
  }
423
- });
429
+ );
424
430
  if (ssh$1) {
425
431
  await ssh(name);
426
432
  return;
@@ -480,6 +486,17 @@ async function whoami() {
480
486
  );
481
487
  }
482
488
 
489
+ const c = {
490
+ primary: (s) => chalk.rgb(0, 232, 198)(s),
491
+ secondary: (s) => chalk.rgb(0, 198, 232)(s),
492
+ accent: (s) => chalk.rgb(130, 100, 255)(s),
493
+ highlight: (s) => chalk.rgb(100, 232, 130)(s),
494
+ muted: (s) => chalk.rgb(200, 210, 220)(s),
495
+ link: (s) => chalk.rgb(255, 160, 100)(s),
496
+ sky: (s) => chalk.rgb(0, 210, 255)(s),
497
+ error: (s) => chalk.rgb(255, 100, 100)(s)
498
+ };
499
+
483
500
  dayjs.extend(relativeTime);
484
501
  async function listSandboxes() {
485
502
  const token = await getAccessToken();
@@ -503,10 +520,10 @@ async function listSandboxes() {
503
520
  );
504
521
  const table = new Table({
505
522
  head: [
506
- chalk.cyan("NAME"),
507
- chalk.cyan("BASE"),
508
- chalk.cyan("STATUS"),
509
- chalk.cyan("CREATED AT")
523
+ c.primary("NAME"),
524
+ c.primary("BASE"),
525
+ c.primary("STATUS"),
526
+ c.primary("CREATED AT")
510
527
  ],
511
528
  chars: {
512
529
  top: "",
@@ -532,9 +549,9 @@ async function listSandboxes() {
532
549
  });
533
550
  for (const sandbox of response.data.sandboxes) {
534
551
  table.push([
535
- chalk.greenBright(sandbox.name),
552
+ c.secondary(sandbox.name),
536
553
  sandbox.baseSandbox,
537
- sandbox.status === "RUNNING" ? chalk.greenBright(sandbox.status) : sandbox.status,
554
+ sandbox.status === "RUNNING" ? c.highlight(sandbox.status) : sandbox.status,
538
555
  dayjs(sandbox.createdAt).fromNow()
539
556
  ]);
540
557
  }
@@ -561,12 +578,13 @@ async function stop(name) {
561
578
  async function createSandbox(name, {
562
579
  provider,
563
580
  ssh: ssh$1,
564
- base
581
+ base,
582
+ repo
565
583
  }) {
566
584
  const token = await getAccessToken();
567
585
  if (["deno", "vercel", "daytona"].includes(provider || "")) {
568
586
  consola.error(
569
- `This Sandbox Runtime is temporarily disabled. ${chalk.greenBright(provider ?? "")}`
587
+ `This Sandbox Runtime is temporarily disabled. ${c.primary(provider ?? "")}`
570
588
  );
571
589
  process.exit(1);
572
590
  }
@@ -576,7 +594,8 @@ async function createSandbox(name, {
576
594
  {
577
595
  name,
578
596
  base: base ?? "at://did:plc:aturpi2ls3yvsmhc6wybomun/io.pocketenv.sandbox/openclaw",
579
- provider: provider ?? "cloudflare"
597
+ provider: provider ?? "cloudflare",
598
+ repo
580
599
  },
581
600
  {
582
601
  headers: {
@@ -586,7 +605,7 @@ async function createSandbox(name, {
586
605
  );
587
606
  if (!ssh$1) {
588
607
  consola.success(
589
- `Sandbox created successfully: ${chalk.greenBright(sandbox.data.name)}`
608
+ `Sandbox created successfully: ${c.primary(sandbox.data.name)}`
590
609
  );
591
610
  return;
592
611
  }
@@ -662,7 +681,7 @@ async function listSecrets(sandbox) {
662
681
  }
663
682
  );
664
683
  const table = new Table({
665
- head: [chalk.cyan("ID"), chalk.cyan("NAME"), chalk.cyan("CREATED AT")],
684
+ head: [c.primary("ID"), c.primary("NAME"), c.primary("CREATED AT")],
666
685
  chars: {
667
686
  top: "",
668
687
  "top-mid": "",
@@ -687,8 +706,8 @@ async function listSecrets(sandbox) {
687
706
  });
688
707
  for (const secret of response.data.secrets) {
689
708
  table.push([
690
- chalk.greenBright(secret.id),
691
- chalk.greenBright(secret.name),
709
+ c.secondary(secret.id),
710
+ c.highlight(secret.name),
692
711
  dayjs(secret.createdAt).fromNow()
693
712
  ]);
694
713
  }
@@ -696,7 +715,13 @@ async function listSecrets(sandbox) {
696
715
  }
697
716
  async function putSecret(sandbox, key) {
698
717
  const token = await getAccessToken();
699
- const value = await password({ message: "Enter secret value" });
718
+ const isStdinPiped = !process.stdin.isTTY;
719
+ const value = isStdinPiped ? await new Promise((resolve) => {
720
+ let data2 = "";
721
+ process.stdin.setEncoding("utf8");
722
+ process.stdin.on("data", (chunk) => data2 += chunk);
723
+ process.stdin.on("end", () => resolve(data2.trimEnd()));
724
+ }) : await password({ message: "Enter secret value" });
700
725
  const { data } = await client.get("/xrpc/io.pocketenv.sandbox.getSandbox", {
701
726
  params: {
702
727
  id: sandbox
@@ -709,21 +734,26 @@ async function putSecret(sandbox, key) {
709
734
  consola.error(`Sandbox not found: ${chalk.greenBright(sandbox)}`);
710
735
  process.exit(1);
711
736
  }
712
- await client.post(
713
- "/xrpc/io.pocketenv.secret.addSecret",
714
- {
715
- secret: {
716
- sandboxId: data.sandbox.id,
717
- name: key,
718
- value: await encrypt(value)
719
- }
720
- },
721
- {
722
- headers: {
723
- Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
737
+ try {
738
+ await client.post(
739
+ "/xrpc/io.pocketenv.secret.addSecret",
740
+ {
741
+ secret: {
742
+ sandboxId: data.sandbox.id,
743
+ name: key,
744
+ value: await encrypt(value)
745
+ }
746
+ },
747
+ {
748
+ headers: {
749
+ Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
750
+ }
724
751
  }
725
- }
726
- );
752
+ );
753
+ consola.success("Secret added successfully");
754
+ } catch (error) {
755
+ consola.error("Failed to add secret:", error);
756
+ }
727
757
  }
728
758
  async function deleteSecret(id) {
729
759
  const token = await getAccessToken();
@@ -737,8 +767,8 @@ async function deleteSecret(id) {
737
767
  }
738
768
  });
739
769
  consola.success("Secret deleted successfully");
740
- } catch {
741
- consola.error("Failed to delete secret");
770
+ } catch (error) {
771
+ consola.error("Failed to delete secret:", error);
742
772
  }
743
773
  }
744
774
 
@@ -756,7 +786,7 @@ async function listEnvs(sandbox) {
756
786
  }
757
787
  );
758
788
  if (!data.sandbox) {
759
- consola.error(`Sandbox not found: ${chalk.greenBright(sandbox)}`);
789
+ consola.error(`Sandbox not found: ${c.primary(sandbox)}`);
760
790
  process.exit(1);
761
791
  }
762
792
  const response = await client.get(
@@ -774,10 +804,10 @@ async function listEnvs(sandbox) {
774
804
  );
775
805
  const table = new Table({
776
806
  head: [
777
- chalk.cyan("ID"),
778
- chalk.cyan("NAME"),
779
- chalk.cyan("VALUE"),
780
- chalk.cyan("CREATED AT")
807
+ c.primary("ID"),
808
+ c.primary("NAME"),
809
+ c.primary("VALUE"),
810
+ c.primary("CREATED AT")
781
811
  ],
782
812
  chars: {
783
813
  top: "",
@@ -803,8 +833,8 @@ async function listEnvs(sandbox) {
803
833
  });
804
834
  for (const variable of response.data.variables) {
805
835
  table.push([
806
- chalk.greenBright(variable.id),
807
- chalk.greenBright(variable.name),
836
+ c.secondary(variable.id),
837
+ c.highlight(variable.name),
808
838
  variable.value,
809
839
  dayjs(variable.createdAt).fromNow()
810
840
  ]);
@@ -1100,7 +1130,7 @@ async function putAuthKey(sandbox) {
1100
1130
  }
1101
1131
  );
1102
1132
  if (!data.sandbox) {
1103
- consola.error(`Sandbox not found: ${chalk.greenBright(sandbox)}`);
1133
+ consola.error(`Sandbox not found: ${c.primary(sandbox)}`);
1104
1134
  process.exit(1);
1105
1135
  }
1106
1136
  const redacted = authKey.length > 14 ? authKey.slice(0, 11) + "*".repeat(authKey.length - 14) + authKey.slice(-3) : authKey;
@@ -1119,7 +1149,7 @@ async function putAuthKey(sandbox) {
1119
1149
  );
1120
1150
  consola.success(redacted);
1121
1151
  consola.success(
1122
- `Tailscale auth key saved for sandbox: ${chalk.greenBright(sandbox)}`
1152
+ `Tailscale auth key saved for sandbox: ${c.primary(sandbox)}`
1123
1153
  );
1124
1154
  }
1125
1155
  async function getTailscaleAuthKey(sandbox) {
@@ -1136,7 +1166,7 @@ async function getTailscaleAuthKey(sandbox) {
1136
1166
  }
1137
1167
  );
1138
1168
  if (!data.sandbox) {
1139
- consola.error(`Sandbox not found: ${chalk.greenBright(sandbox)}`);
1169
+ consola.error(`Sandbox not found: ${c.primary(sandbox)}`);
1140
1170
  process.exit(1);
1141
1171
  }
1142
1172
  try {
@@ -1151,24 +1181,386 @@ async function getTailscaleAuthKey(sandbox) {
1151
1181
  }
1152
1182
  }
1153
1183
  );
1154
- consola.info(`Tailscale auth key: ${chalk.greenBright(tailscale.authKey)}`);
1184
+ consola.info(`Tailscale auth key: ${c.primary(tailscale.authKey)}`);
1155
1185
  } catch {
1156
1186
  consola.error(
1157
- `No Tailscale Auth Key found for sandbox: ${chalk.greenBright(sandbox)}`
1187
+ `No Tailscale Auth Key found for sandbox: ${c.primary(sandbox)}`
1158
1188
  );
1159
1189
  process.exit(1);
1160
1190
  }
1161
1191
  }
1162
1192
 
1163
- const c = {
1164
- primary: (s) => chalk.rgb(0, 232, 198)(s),
1165
- secondary: (s) => chalk.rgb(0, 198, 232)(s),
1166
- accent: (s) => chalk.rgb(130, 100, 255)(s),
1167
- highlight: (s) => chalk.rgb(100, 232, 130)(s),
1168
- muted: (s) => chalk.rgb(200, 210, 220)(s),
1169
- link: (s) => chalk.rgb(255, 160, 100)(s),
1170
- sky: (s) => chalk.rgb(0, 210, 255)(s)
1171
- };
1193
+ async function exposePort(sandbox, port, description) {
1194
+ const token = await getAccessToken();
1195
+ try {
1196
+ const response = await client.post(
1197
+ `/xrpc/io.pocketenv.sandbox.exposePort`,
1198
+ { port, description },
1199
+ {
1200
+ params: {
1201
+ id: sandbox
1202
+ },
1203
+ headers: {
1204
+ Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
1205
+ }
1206
+ }
1207
+ );
1208
+ consola.success(
1209
+ `Port ${c.primary(port)} exposed for sandbox ${c.primary(sandbox)}`
1210
+ );
1211
+ if (response.data.previewUrl) {
1212
+ consola.success(`Preview URL: ${c.secondary(response.data.previewUrl)}`);
1213
+ }
1214
+ } catch (error) {
1215
+ consola.error("Failed to expose port:", error);
1216
+ process.exit(1);
1217
+ }
1218
+ }
1219
+
1220
+ async function unexposePort(sandbox, port) {
1221
+ const token = await getAccessToken();
1222
+ try {
1223
+ await client.post(
1224
+ `/xrpc/io.pocketenv.sandbox.unexposePort`,
1225
+ { port },
1226
+ {
1227
+ params: {
1228
+ id: sandbox
1229
+ },
1230
+ headers: {
1231
+ Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
1232
+ }
1233
+ }
1234
+ );
1235
+ consola.success(
1236
+ `Port ${c.primary(port)} unexposed for sandbox ${c.primary(sandbox)}`
1237
+ );
1238
+ } catch (error) {
1239
+ consola.error(`Failed to unexpose port: ${error}`);
1240
+ process.exit(1);
1241
+ }
1242
+ }
1243
+
1244
+ dayjs.extend(relativeTime);
1245
+ async function listVolumes(sandboxId) {
1246
+ const token = await getAccessToken();
1247
+ const response = await client.get(
1248
+ "/xrpc/io.pocketenv.volume.getVolumes",
1249
+ {
1250
+ params: {
1251
+ sandboxId
1252
+ },
1253
+ headers: {
1254
+ Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
1255
+ }
1256
+ }
1257
+ );
1258
+ const table = new Table({
1259
+ head: [
1260
+ c.primary("ID"),
1261
+ c.primary("NAME"),
1262
+ c.primary("PATH"),
1263
+ c.primary("CREATED AT")
1264
+ ],
1265
+ chars: {
1266
+ top: "",
1267
+ "top-mid": "",
1268
+ "top-left": "",
1269
+ "top-right": "",
1270
+ bottom: "",
1271
+ "bottom-mid": "",
1272
+ "bottom-left": "",
1273
+ "bottom-right": "",
1274
+ left: "",
1275
+ "left-mid": "",
1276
+ mid: "",
1277
+ "mid-mid": "",
1278
+ right: "",
1279
+ "right-mid": "",
1280
+ middle: " "
1281
+ },
1282
+ style: {
1283
+ border: [],
1284
+ head: []
1285
+ }
1286
+ });
1287
+ for (const volume of response.data.volumes) {
1288
+ table.push([
1289
+ c.secondary(volume.id),
1290
+ volume.name,
1291
+ volume.path,
1292
+ dayjs(volume.createdAt).fromNow()
1293
+ ]);
1294
+ }
1295
+ consola.log(table.toString());
1296
+ }
1297
+ async function createVolume(sandbox, name, path) {
1298
+ const token = await getAccessToken();
1299
+ try {
1300
+ await client.post(
1301
+ "/xrpc/io.pocketenv.volume.addVolume",
1302
+ {
1303
+ volume: {
1304
+ sandboxId: sandbox,
1305
+ name,
1306
+ path
1307
+ }
1308
+ },
1309
+ {
1310
+ headers: {
1311
+ Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
1312
+ }
1313
+ }
1314
+ );
1315
+ consola.success(
1316
+ `Volume ${chalk.rgb(0, 232, 198)(name)} successfully mounted in sandbox ${chalk.rgb(0, 232, 198)(sandbox)} at path ${chalk.rgb(0, 232, 198)(path)}`
1317
+ );
1318
+ } catch (error) {
1319
+ consola.error("Failed to create volume:", error);
1320
+ }
1321
+ }
1322
+ async function deleteVolume(id) {
1323
+ const token = await getAccessToken();
1324
+ try {
1325
+ await client.post(`/xrpc/io.pocketenv.volume.deleteVolume`, void 0, {
1326
+ params: {
1327
+ id
1328
+ },
1329
+ headers: {
1330
+ Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
1331
+ }
1332
+ });
1333
+ } catch (error) {
1334
+ consola.error(`Failed to delete volume: ${error}`);
1335
+ return;
1336
+ }
1337
+ consola.success(
1338
+ `Volume ${chalk.rgb(0, 232, 198)(id)} successfully deleted from sandbox`
1339
+ );
1340
+ }
1341
+
1342
+ dayjs.extend(relativeTime);
1343
+ async function putFile(sandbox, remotePath, localPath) {
1344
+ const token = await getAccessToken();
1345
+ let content;
1346
+ if (!process.stdin.isTTY) {
1347
+ const chunks = [];
1348
+ for await (const chunk of process.stdin) chunks.push(chunk);
1349
+ content = Buffer.concat(chunks).toString().trim();
1350
+ } else if (localPath) {
1351
+ const resolvedPath = path.resolve(localPath);
1352
+ try {
1353
+ await fs.access(resolvedPath);
1354
+ } catch (err) {
1355
+ consola.error(`No such file: ${c.error(localPath)}`);
1356
+ process.exit(1);
1357
+ }
1358
+ content = await fs.readFile(resolvedPath, "utf-8");
1359
+ } else {
1360
+ content = (await editor({
1361
+ message: "File content (opens in $EDITOR):",
1362
+ waitForUserInput: false
1363
+ })).trim();
1364
+ }
1365
+ try {
1366
+ await client.post(
1367
+ "/xrpc/io.pocketenv.file.addFile",
1368
+ {
1369
+ file: {
1370
+ sandboxId: sandbox,
1371
+ path: remotePath,
1372
+ content: await encrypt(content)
1373
+ }
1374
+ },
1375
+ {
1376
+ headers: {
1377
+ Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
1378
+ }
1379
+ }
1380
+ );
1381
+ consola.success(
1382
+ `File ${c.primary(remotePath)} successfully created in sandbox ${c.primary(sandbox)}`
1383
+ );
1384
+ } catch (error) {
1385
+ consola.error(`Failed to create file: ${error}`);
1386
+ }
1387
+ }
1388
+ async function listFiles(sandboxId) {
1389
+ const token = await getAccessToken();
1390
+ const response = await client.get(
1391
+ "/xrpc/io.pocketenv.file.getFiles",
1392
+ {
1393
+ params: {
1394
+ sandboxId
1395
+ },
1396
+ headers: {
1397
+ Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
1398
+ }
1399
+ }
1400
+ );
1401
+ const table = new Table({
1402
+ head: [c.primary("ID"), c.primary("PATH"), c.primary("CREATED AT")],
1403
+ chars: {
1404
+ top: "",
1405
+ "top-mid": "",
1406
+ "top-left": "",
1407
+ "top-right": "",
1408
+ bottom: "",
1409
+ "bottom-mid": "",
1410
+ "bottom-left": "",
1411
+ "bottom-right": "",
1412
+ left: "",
1413
+ "left-mid": "",
1414
+ mid: "",
1415
+ "mid-mid": "",
1416
+ right: "",
1417
+ "right-mid": "",
1418
+ middle: " "
1419
+ },
1420
+ style: {
1421
+ border: [],
1422
+ head: []
1423
+ }
1424
+ });
1425
+ for (const file of response.data.files) {
1426
+ table.push([
1427
+ c.secondary(file.id),
1428
+ file.path,
1429
+ dayjs(file.createdAt).fromNow()
1430
+ ]);
1431
+ }
1432
+ consola.log(table.toString());
1433
+ }
1434
+ async function deleteFile(id) {
1435
+ const token = await getAccessToken();
1436
+ try {
1437
+ await client.post(`/xrpc/io.pocketenv.file.deleteFile`, void 0, {
1438
+ params: {
1439
+ id
1440
+ },
1441
+ headers: {
1442
+ Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
1443
+ }
1444
+ });
1445
+ consola.success(`File ${c.primary(id)} successfully deleted from sandbox`);
1446
+ } catch (error) {
1447
+ consola.error(`Failed to delete file: ${error}`);
1448
+ }
1449
+ }
1450
+
1451
+ async function listPorts(sandbox) {
1452
+ const token = await getAccessToken();
1453
+ const response = await client.get(
1454
+ "/xrpc/io.pocketenv.sandbox.getExposedPorts",
1455
+ {
1456
+ params: {
1457
+ id: sandbox
1458
+ },
1459
+ headers: {
1460
+ Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
1461
+ }
1462
+ }
1463
+ );
1464
+ const table = new Table({
1465
+ head: [
1466
+ c.primary("PORT"),
1467
+ c.primary("DESCRIPTION"),
1468
+ c.primary("PREVIEW URL")
1469
+ ],
1470
+ chars: {
1471
+ top: "",
1472
+ "top-mid": "",
1473
+ "top-left": "",
1474
+ "top-right": "",
1475
+ bottom: "",
1476
+ "bottom-mid": "",
1477
+ "bottom-left": "",
1478
+ "bottom-right": "",
1479
+ left: "",
1480
+ "left-mid": "",
1481
+ mid: "",
1482
+ "mid-mid": "",
1483
+ right: "",
1484
+ "right-mid": "",
1485
+ middle: " "
1486
+ },
1487
+ style: {
1488
+ border: [],
1489
+ head: []
1490
+ }
1491
+ });
1492
+ for (const port of response.data.ports) {
1493
+ table.push([
1494
+ c.secondary(port.port),
1495
+ port.description || "-",
1496
+ c.link(port.previewUrl || "-")
1497
+ ]);
1498
+ }
1499
+ consola.log(table.toString());
1500
+ }
1501
+
1502
+ async function exposeVscode(sandbox) {
1503
+ const token = await getAccessToken();
1504
+ try {
1505
+ const response = await client.post(
1506
+ `/xrpc/io.pocketenv.sandbox.exposeVscode`,
1507
+ void 0,
1508
+ {
1509
+ params: {
1510
+ id: sandbox
1511
+ },
1512
+ headers: {
1513
+ Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
1514
+ }
1515
+ }
1516
+ );
1517
+ consola.success(`VS Code Server exposed for sandbox ${c.primary(sandbox)}`);
1518
+ if (response.data.previewUrl) {
1519
+ consola.success(`Preview URL: ${c.secondary(response.data.previewUrl)}`);
1520
+ }
1521
+ } catch (error) {
1522
+ consola.error("Failed to expose VS Code:", error);
1523
+ process.exit(1);
1524
+ }
1525
+ }
1526
+
1527
+ async function exec(sandbox, command) {
1528
+ const token = await getAccessToken();
1529
+ try {
1530
+ const [cmd, ...args] = command;
1531
+ const response = await client.post(
1532
+ "/xrpc/io.pocketenv.sandbox.exec",
1533
+ {
1534
+ command: `${cmd} ${args.join(" ")}`
1535
+ },
1536
+ {
1537
+ params: {
1538
+ id: sandbox
1539
+ },
1540
+ headers: {
1541
+ Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
1542
+ }
1543
+ }
1544
+ );
1545
+ if (response.data.stdout) {
1546
+ process.stdout.write(
1547
+ response.data.stdout.endsWith("\n") ? response.data.stdout : response.data.stdout + "\n"
1548
+ );
1549
+ }
1550
+ if (response.data.stderr) {
1551
+ process.stderr.write(
1552
+ response.data.stderr.endsWith("\n") ? response.data.stderr : response.data.stderr + "\n"
1553
+ );
1554
+ }
1555
+ if (response.data.exitCode !== 0) {
1556
+ consola.error(`Command exited with code ${response.data.exitCode}`);
1557
+ }
1558
+ process.exit(response.data.exitCode);
1559
+ } catch (error) {
1560
+ consola.error("Failed to execute command:", error);
1561
+ }
1562
+ }
1563
+
1172
1564
  const program = new Command();
1173
1565
  program.name("pocketenv").description(
1174
1566
  `${chalk.bold.rgb(0, 232, 198)(`pocketenv v${version}`)} ${c.muted("\u2500")} ${c.muted("Open, interoperable sandbox platform for agents and humans")}`
@@ -1195,14 +1587,47 @@ program.command("login").argument("<handle>", "your AT Proto handle (e.g., <user
1195
1587
  program.command("whoami").description("get the current logged-in user").action(whoami);
1196
1588
  program.command("console").aliases(["shell", "ssh", "s"]).argument("[sandbox]", "the sandbox to connect to").description("open an interactive shell for the given sandbox").action(ssh);
1197
1589
  program.command("ls").description("list sandboxes").action(listSandboxes);
1198
- program.command("start").argument("<sandbox>", "the sandbox to start").option("--ssh, -s", "connect to the Sandbox and automatically open a shell").description("start the given sandbox").action(start);
1590
+ program.command("start").argument("<sandbox>", "the sandbox to start").option("--ssh, -s", "connect to the Sandbox and automatically open a shell").option(
1591
+ "--repo, -r <repo>",
1592
+ "the repository to clone into the sandbox (e.g., github:user/repo, tangled:user/repo, or a Git URL)"
1593
+ ).description("start the given sandbox").action(start);
1199
1594
  program.command("stop").argument("<sandbox>", "the sandbox to stop").description("stop the given sandbox").action(stop);
1200
1595
  program.command("create").aliases(["new"]).option("--provider, -p <provider>", "the provider to use for the sandbox").option(
1201
1596
  "--base, -b <base>",
1202
1597
  "the base sandbox to use for the sandbox, e.g. openclaw, claude-code, codex, copilot ..."
1203
- ).option("--ssh, -s", "connect to the Sandbox and automatically open a shell").argument("[name]", "the name of the sandbox to create").description("create a new sandbox").action(createSandbox);
1598
+ ).option("--ssh, -s", "connect to the Sandbox and automatically open a shell").option(
1599
+ "--repo, -r <repo>",
1600
+ "the repository to clone into the sandbox (e.g., github:user/repo, tangled:user/repo, or a Git URL)"
1601
+ ).argument("[name]", "the name of the sandbox to create").description("create a new sandbox").action(createSandbox);
1204
1602
  program.command("logout").description("logout (removes session token)").action(logout);
1205
1603
  program.command("rm").aliases(["delete", "remove"]).argument("<sandbox>", "the sandbox to delete").description("delete the given sandbox").action(deleteSandbox);
1604
+ program.command("vscode").aliases(["code", "code-server"]).argument("<sandbox>", "the sandbox to expose VS Code for").description("expose a visual code server to the internet").action(exposeVscode);
1605
+ program.enablePositionalOptions().command("exec").argument("<sandbox>", "the sandbox to execute the command in").argument("<command...>", "the command to execute").description("execute a command in the given sandbox").passThroughOptions().action(exec);
1606
+ program.command("expose").argument("<sandbox>", "the sandbox to expose a port for").argument("<port>", "the port to expose", (val) => {
1607
+ const port = parseInt(val, 10);
1608
+ if (isNaN(port)) {
1609
+ consola.error(`port must be a number, got: ${val}`);
1610
+ process.exit(1);
1611
+ }
1612
+ return port;
1613
+ }).argument("[description]", "an optional description for the exposed port").description("expose a port from the given sandbox to the internet").action(exposePort);
1614
+ program.command("unexpose").argument("<sandbox>", "the sandbox to unexpose a port for").argument("<port>", "the port to unexpose", (val) => {
1615
+ const port = parseInt(val, 10);
1616
+ if (isNaN(port)) {
1617
+ consola.error(`port must be a number, got: ${val}`);
1618
+ process.exit(1);
1619
+ }
1620
+ return port;
1621
+ }).description("unexpose a port from the given sandbox").action(unexposePort);
1622
+ const volume = program.command("volume").description("manage volumes");
1623
+ volume.command("put").argument("<sandbox>", "the sandbox to put the volume in").argument("<name>", "the name of the volume").argument("<path>", "the path to mount the volume at").description("put a volume in the given sandbox").action(createVolume);
1624
+ volume.command("list").aliases(["ls"]).argument("<sandbox>", "the sandbox to list volumes for").description("list volumes in the given sandbox").action(listVolumes);
1625
+ volume.command("delete").aliases(["rm", "remove"]).argument("<id>", "the ID of the volume to delete").description("delete a volume").action(deleteVolume);
1626
+ const file = program.command("file").description("manage files");
1627
+ file.command("put").argument("<sandbox>", "the sandbox to put the file in").argument("<path>", "the remote path to upload the file to").argument("[localPath]", "the local path of the file to upload").description("upload a file to the given sandbox").action(putFile);
1628
+ file.command("list").aliases(["ls"]).argument("<sandbox>", "the sandbox to list files for").description("list files in the given sandbox").action(listFiles);
1629
+ file.command("delete").aliases(["rm", "remove"]).argument("<id>", "the ID of the file to delete").description("delete a file").action(deleteFile);
1630
+ program.command("ports").argument("<sandbox>", "the sandbox to list exposed ports for").description("list exposed ports for a sandbox").action(listPorts);
1206
1631
  const secret = program.command("secret").description("manage secrets");
1207
1632
  secret.command("put").argument("<sandbox>", "the sandbox to put the secret in").argument("<key>", "the key of the secret").description("put a secret in the given sandbox").action(putSecret);
1208
1633
  secret.command("list").aliases(["ls"]).argument("<sandbox>", "the sandbox to list secrets for").description("list secrets in the given sandbox").action(listSecrets);