@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 +486 -61
- package/package.json +1 -1
- package/src/cmd/create.ts +9 -6
- package/src/cmd/env.ts +8 -8
- package/src/cmd/exec.ts +53 -0
- package/src/cmd/expose.ts +38 -0
- package/src/cmd/file.ts +139 -0
- package/src/cmd/list.ts +7 -7
- package/src/cmd/ports.ts +63 -0
- package/src/cmd/secret.ts +34 -19
- package/src/cmd/start.ts +16 -7
- package/src/cmd/tailscale.ts +6 -6
- package/src/cmd/unexpose.ts +31 -0
- package/src/cmd/volume.ts +123 -0
- package/src/cmd/vscode.ts +32 -0
- package/src/index.ts +116 -10
- package/src/theme.ts +12 -0
- package/src/types/port.ts +5 -0
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.
|
|
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(
|
|
417
|
-
|
|
418
|
-
|
|
416
|
+
await client.post(
|
|
417
|
+
"/xrpc/io.pocketenv.sandbox.startSandbox",
|
|
418
|
+
{
|
|
419
|
+
repo
|
|
419
420
|
},
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
552
|
+
c.secondary(sandbox.name),
|
|
536
553
|
sandbox.baseSandbox,
|
|
537
|
-
sandbox.status === "RUNNING" ?
|
|
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. ${
|
|
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: ${
|
|
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: [
|
|
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
|
-
|
|
691
|
-
|
|
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
|
|
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
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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: ${
|
|
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
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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
|
-
|
|
807
|
-
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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").
|
|
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").
|
|
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);
|