@pocketenv/cli 0.2.4 → 0.3.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/dist/index.js +511 -125
- package/package.json +1 -1
- package/src/cmd/create.ts +3 -3
- package/src/cmd/env.ts +8 -8
- 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/ssh/tty.ts +21 -5
- package/src/cmd/start.ts +7 -1
- 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 +100 -10
- package/src/theme.ts +12 -0
- package/src/types/port.ts +5 -0
package/dist/index.js
CHANGED
|
@@ -8,21 +8,21 @@ import os from 'os';
|
|
|
8
8
|
import { env as env$2 } from 'process';
|
|
9
9
|
import axios from 'axios';
|
|
10
10
|
import { cleanEnv, str } from 'envalid';
|
|
11
|
+
import WebSocket from 'ws';
|
|
12
|
+
import { EventSource } from 'eventsource';
|
|
11
13
|
import open from 'open';
|
|
12
14
|
import express from 'express';
|
|
13
15
|
import cors from 'cors';
|
|
14
16
|
import fs$1 from 'node:fs/promises';
|
|
15
17
|
import os$1 from 'node:os';
|
|
16
18
|
import path$1 from 'node:path';
|
|
17
|
-
import WebSocket from 'ws';
|
|
18
|
-
import { EventSource } from 'eventsource';
|
|
19
19
|
import Table from 'cli-table3';
|
|
20
20
|
import dayjs from 'dayjs';
|
|
21
21
|
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.0";
|
|
26
26
|
|
|
27
27
|
async function getAccessToken() {
|
|
28
28
|
const tokenPath = path.join(os.homedir(), ".pocketenv", "token.json");
|
|
@@ -65,72 +65,6 @@ const client = axios.create({
|
|
|
65
65
|
baseURL: env$1.POCKETENV_API_URL
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
async function start(name) {
|
|
69
|
-
const token = await getAccessToken();
|
|
70
|
-
try {
|
|
71
|
-
await client.post("/xrpc/io.pocketenv.sandbox.startSandbox", void 0, {
|
|
72
|
-
params: {
|
|
73
|
-
id: name
|
|
74
|
-
},
|
|
75
|
-
headers: {
|
|
76
|
-
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
consola.success(`Sandbox ${chalk.greenBright(name)} started`);
|
|
80
|
-
consola.log(
|
|
81
|
-
`Run ${chalk.greenBright(`pocketenv console ${name}`)} to access the sandbox`
|
|
82
|
-
);
|
|
83
|
-
} catch {
|
|
84
|
-
consola.error("Failed to start sandbox");
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async function login(handle) {
|
|
89
|
-
const app = express();
|
|
90
|
-
app.use(cors());
|
|
91
|
-
app.use(express.json());
|
|
92
|
-
const server = app.listen(6997);
|
|
93
|
-
app.post("/token", async (req, res) => {
|
|
94
|
-
console.log(chalk.bold(chalk.greenBright("Login successful!\n")));
|
|
95
|
-
const tokenPath = path$1.join(os$1.homedir(), ".pocketenv", "token.json");
|
|
96
|
-
await fs$1.mkdir(path$1.dirname(tokenPath), { recursive: true });
|
|
97
|
-
await fs$1.writeFile(
|
|
98
|
-
tokenPath,
|
|
99
|
-
JSON.stringify({ token: req.body.token }, null, 2)
|
|
100
|
-
);
|
|
101
|
-
res.json({
|
|
102
|
-
ok: 1
|
|
103
|
-
});
|
|
104
|
-
server.close();
|
|
105
|
-
});
|
|
106
|
-
const response = await client.post(`/login`, { handle, cli: true });
|
|
107
|
-
const redirectUrl = response.data;
|
|
108
|
-
if (!redirectUrl.includes("authorize")) {
|
|
109
|
-
console.error("Failed to login, please check your handle and try again.");
|
|
110
|
-
server.close();
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
console.log("Please visit this URL to authorize the app:");
|
|
114
|
-
console.log(chalk.cyan(redirectUrl));
|
|
115
|
-
await open(response.data);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function whoami() {
|
|
119
|
-
const token = await getAccessToken();
|
|
120
|
-
const profile = await client.get(
|
|
121
|
-
"/xrpc/io.pocketenv.actor.getProfile",
|
|
122
|
-
{
|
|
123
|
-
headers: {
|
|
124
|
-
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
);
|
|
128
|
-
const handle = `@${profile.data.handle}`;
|
|
129
|
-
consola.log(
|
|
130
|
-
`You are logged in as ${chalk.cyan(handle)} (${profile.data.displayName}).`
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
68
|
function sendInput$1(ws, data) {
|
|
135
69
|
if (ws.readyState === WebSocket.OPEN) {
|
|
136
70
|
ws.send(data);
|
|
@@ -334,6 +268,10 @@ async function ssh$1(sandbox) {
|
|
|
334
268
|
}
|
|
335
269
|
process.stdin.resume();
|
|
336
270
|
process.stdin.on("data", (chunk) => {
|
|
271
|
+
if (chunk.includes(11)) {
|
|
272
|
+
teardown(0);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
337
275
|
sendInput(ttyUrl, sandbox.id, chunk, authToken);
|
|
338
276
|
});
|
|
339
277
|
process.stdout.on("resize", () => {
|
|
@@ -380,13 +318,16 @@ ${chalk.dim("Process exited.")}\r
|
|
|
380
318
|
});
|
|
381
319
|
es.onerror = (err) => {
|
|
382
320
|
if (es && es.readyState === EventSource.CLOSED) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
321
|
+
if (!err.message) {
|
|
322
|
+
teardown(0);
|
|
323
|
+
} else {
|
|
324
|
+
process.stderr.write(
|
|
325
|
+
`\r
|
|
326
|
+
${chalk.red(`Terminal connection lost (${err.message})`)}\r
|
|
387
327
|
`
|
|
388
|
-
|
|
389
|
-
|
|
328
|
+
);
|
|
329
|
+
teardown(1);
|
|
330
|
+
}
|
|
390
331
|
}
|
|
391
332
|
};
|
|
392
333
|
process.on("SIGINT", () => teardown(0));
|
|
@@ -469,6 +410,87 @@ async function ssh(sandboxName) {
|
|
|
469
410
|
}
|
|
470
411
|
}
|
|
471
412
|
|
|
413
|
+
async function start(name, { ssh: ssh$1 }) {
|
|
414
|
+
const token = await getAccessToken();
|
|
415
|
+
try {
|
|
416
|
+
await client.post("/xrpc/io.pocketenv.sandbox.startSandbox", void 0, {
|
|
417
|
+
params: {
|
|
418
|
+
id: name
|
|
419
|
+
},
|
|
420
|
+
headers: {
|
|
421
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
if (ssh$1) {
|
|
425
|
+
await ssh(name);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
consola.success(`Sandbox ${chalk.greenBright(name)} started`);
|
|
429
|
+
consola.log(
|
|
430
|
+
`Run ${chalk.greenBright(`pocketenv console ${name}`)} to access the sandbox`
|
|
431
|
+
);
|
|
432
|
+
} catch {
|
|
433
|
+
consola.error("Failed to start sandbox");
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function login(handle) {
|
|
438
|
+
const app = express();
|
|
439
|
+
app.use(cors());
|
|
440
|
+
app.use(express.json());
|
|
441
|
+
const server = app.listen(6997);
|
|
442
|
+
app.post("/token", async (req, res) => {
|
|
443
|
+
console.log(chalk.bold(chalk.greenBright("Login successful!\n")));
|
|
444
|
+
const tokenPath = path$1.join(os$1.homedir(), ".pocketenv", "token.json");
|
|
445
|
+
await fs$1.mkdir(path$1.dirname(tokenPath), { recursive: true });
|
|
446
|
+
await fs$1.writeFile(
|
|
447
|
+
tokenPath,
|
|
448
|
+
JSON.stringify({ token: req.body.token }, null, 2)
|
|
449
|
+
);
|
|
450
|
+
res.json({
|
|
451
|
+
ok: 1
|
|
452
|
+
});
|
|
453
|
+
server.close();
|
|
454
|
+
});
|
|
455
|
+
const response = await client.post(`/login`, { handle, cli: true });
|
|
456
|
+
const redirectUrl = response.data;
|
|
457
|
+
if (!redirectUrl.includes("authorize")) {
|
|
458
|
+
console.error("Failed to login, please check your handle and try again.");
|
|
459
|
+
server.close();
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
console.log("Please visit this URL to authorize the app:");
|
|
463
|
+
console.log(chalk.cyan(redirectUrl));
|
|
464
|
+
await open(response.data);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
async function whoami() {
|
|
468
|
+
const token = await getAccessToken();
|
|
469
|
+
const profile = await client.get(
|
|
470
|
+
"/xrpc/io.pocketenv.actor.getProfile",
|
|
471
|
+
{
|
|
472
|
+
headers: {
|
|
473
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
);
|
|
477
|
+
const handle = `@${profile.data.handle}`;
|
|
478
|
+
consola.log(
|
|
479
|
+
`You are logged in as ${chalk.cyan(handle)} (${profile.data.displayName}).`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const c = {
|
|
484
|
+
primary: (s) => chalk.rgb(0, 232, 198)(s),
|
|
485
|
+
secondary: (s) => chalk.rgb(0, 198, 232)(s),
|
|
486
|
+
accent: (s) => chalk.rgb(130, 100, 255)(s),
|
|
487
|
+
highlight: (s) => chalk.rgb(100, 232, 130)(s),
|
|
488
|
+
muted: (s) => chalk.rgb(200, 210, 220)(s),
|
|
489
|
+
link: (s) => chalk.rgb(255, 160, 100)(s),
|
|
490
|
+
sky: (s) => chalk.rgb(0, 210, 255)(s),
|
|
491
|
+
error: (s) => chalk.rgb(255, 100, 100)(s)
|
|
492
|
+
};
|
|
493
|
+
|
|
472
494
|
dayjs.extend(relativeTime);
|
|
473
495
|
async function listSandboxes() {
|
|
474
496
|
const token = await getAccessToken();
|
|
@@ -492,10 +514,10 @@ async function listSandboxes() {
|
|
|
492
514
|
);
|
|
493
515
|
const table = new Table({
|
|
494
516
|
head: [
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
517
|
+
c.primary("NAME"),
|
|
518
|
+
c.primary("BASE"),
|
|
519
|
+
c.primary("STATUS"),
|
|
520
|
+
c.primary("CREATED AT")
|
|
499
521
|
],
|
|
500
522
|
chars: {
|
|
501
523
|
top: "",
|
|
@@ -521,9 +543,9 @@ async function listSandboxes() {
|
|
|
521
543
|
});
|
|
522
544
|
for (const sandbox of response.data.sandboxes) {
|
|
523
545
|
table.push([
|
|
524
|
-
|
|
546
|
+
c.secondary(sandbox.name),
|
|
525
547
|
sandbox.baseSandbox,
|
|
526
|
-
sandbox.status === "RUNNING" ?
|
|
548
|
+
sandbox.status === "RUNNING" ? c.highlight(sandbox.status) : sandbox.status,
|
|
527
549
|
dayjs(sandbox.createdAt).fromNow()
|
|
528
550
|
]);
|
|
529
551
|
}
|
|
@@ -555,7 +577,7 @@ async function createSandbox(name, {
|
|
|
555
577
|
const token = await getAccessToken();
|
|
556
578
|
if (["deno", "vercel", "daytona"].includes(provider || "")) {
|
|
557
579
|
consola.error(
|
|
558
|
-
`This Sandbox Runtime is temporarily disabled. ${
|
|
580
|
+
`This Sandbox Runtime is temporarily disabled. ${c.primary(provider ?? "")}`
|
|
559
581
|
);
|
|
560
582
|
process.exit(1);
|
|
561
583
|
}
|
|
@@ -575,7 +597,7 @@ async function createSandbox(name, {
|
|
|
575
597
|
);
|
|
576
598
|
if (!ssh$1) {
|
|
577
599
|
consola.success(
|
|
578
|
-
`Sandbox created successfully: ${
|
|
600
|
+
`Sandbox created successfully: ${c.primary(sandbox.data.name)}`
|
|
579
601
|
);
|
|
580
602
|
return;
|
|
581
603
|
}
|
|
@@ -651,7 +673,7 @@ async function listSecrets(sandbox) {
|
|
|
651
673
|
}
|
|
652
674
|
);
|
|
653
675
|
const table = new Table({
|
|
654
|
-
head: [
|
|
676
|
+
head: [c.primary("ID"), c.primary("NAME"), c.primary("CREATED AT")],
|
|
655
677
|
chars: {
|
|
656
678
|
top: "",
|
|
657
679
|
"top-mid": "",
|
|
@@ -676,8 +698,8 @@ async function listSecrets(sandbox) {
|
|
|
676
698
|
});
|
|
677
699
|
for (const secret of response.data.secrets) {
|
|
678
700
|
table.push([
|
|
679
|
-
|
|
680
|
-
|
|
701
|
+
c.secondary(secret.id),
|
|
702
|
+
c.highlight(secret.name),
|
|
681
703
|
dayjs(secret.createdAt).fromNow()
|
|
682
704
|
]);
|
|
683
705
|
}
|
|
@@ -685,7 +707,13 @@ async function listSecrets(sandbox) {
|
|
|
685
707
|
}
|
|
686
708
|
async function putSecret(sandbox, key) {
|
|
687
709
|
const token = await getAccessToken();
|
|
688
|
-
const
|
|
710
|
+
const isStdinPiped = !process.stdin.isTTY;
|
|
711
|
+
const value = isStdinPiped ? await new Promise((resolve) => {
|
|
712
|
+
let data2 = "";
|
|
713
|
+
process.stdin.setEncoding("utf8");
|
|
714
|
+
process.stdin.on("data", (chunk) => data2 += chunk);
|
|
715
|
+
process.stdin.on("end", () => resolve(data2.trimEnd()));
|
|
716
|
+
}) : await password({ message: "Enter secret value" });
|
|
689
717
|
const { data } = await client.get("/xrpc/io.pocketenv.sandbox.getSandbox", {
|
|
690
718
|
params: {
|
|
691
719
|
id: sandbox
|
|
@@ -698,21 +726,26 @@ async function putSecret(sandbox, key) {
|
|
|
698
726
|
consola.error(`Sandbox not found: ${chalk.greenBright(sandbox)}`);
|
|
699
727
|
process.exit(1);
|
|
700
728
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
729
|
+
try {
|
|
730
|
+
await client.post(
|
|
731
|
+
"/xrpc/io.pocketenv.secret.addSecret",
|
|
732
|
+
{
|
|
733
|
+
secret: {
|
|
734
|
+
sandboxId: data.sandbox.id,
|
|
735
|
+
name: key,
|
|
736
|
+
value: await encrypt(value)
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
headers: {
|
|
741
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
742
|
+
}
|
|
713
743
|
}
|
|
714
|
-
|
|
715
|
-
|
|
744
|
+
);
|
|
745
|
+
consola.success("Secret added successfully");
|
|
746
|
+
} catch (error) {
|
|
747
|
+
consola.error("Failed to add secret:", error);
|
|
748
|
+
}
|
|
716
749
|
}
|
|
717
750
|
async function deleteSecret(id) {
|
|
718
751
|
const token = await getAccessToken();
|
|
@@ -726,8 +759,8 @@ async function deleteSecret(id) {
|
|
|
726
759
|
}
|
|
727
760
|
});
|
|
728
761
|
consola.success("Secret deleted successfully");
|
|
729
|
-
} catch {
|
|
730
|
-
consola.error("Failed to delete secret");
|
|
762
|
+
} catch (error) {
|
|
763
|
+
consola.error("Failed to delete secret:", error);
|
|
731
764
|
}
|
|
732
765
|
}
|
|
733
766
|
|
|
@@ -745,7 +778,7 @@ async function listEnvs(sandbox) {
|
|
|
745
778
|
}
|
|
746
779
|
);
|
|
747
780
|
if (!data.sandbox) {
|
|
748
|
-
consola.error(`Sandbox not found: ${
|
|
781
|
+
consola.error(`Sandbox not found: ${c.primary(sandbox)}`);
|
|
749
782
|
process.exit(1);
|
|
750
783
|
}
|
|
751
784
|
const response = await client.get(
|
|
@@ -763,10 +796,10 @@ async function listEnvs(sandbox) {
|
|
|
763
796
|
);
|
|
764
797
|
const table = new Table({
|
|
765
798
|
head: [
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
799
|
+
c.primary("ID"),
|
|
800
|
+
c.primary("NAME"),
|
|
801
|
+
c.primary("VALUE"),
|
|
802
|
+
c.primary("CREATED AT")
|
|
770
803
|
],
|
|
771
804
|
chars: {
|
|
772
805
|
top: "",
|
|
@@ -792,8 +825,8 @@ async function listEnvs(sandbox) {
|
|
|
792
825
|
});
|
|
793
826
|
for (const variable of response.data.variables) {
|
|
794
827
|
table.push([
|
|
795
|
-
|
|
796
|
-
|
|
828
|
+
c.secondary(variable.id),
|
|
829
|
+
c.highlight(variable.name),
|
|
797
830
|
variable.value,
|
|
798
831
|
dayjs(variable.createdAt).fromNow()
|
|
799
832
|
]);
|
|
@@ -1089,7 +1122,7 @@ async function putAuthKey(sandbox) {
|
|
|
1089
1122
|
}
|
|
1090
1123
|
);
|
|
1091
1124
|
if (!data.sandbox) {
|
|
1092
|
-
consola.error(`Sandbox not found: ${
|
|
1125
|
+
consola.error(`Sandbox not found: ${c.primary(sandbox)}`);
|
|
1093
1126
|
process.exit(1);
|
|
1094
1127
|
}
|
|
1095
1128
|
const redacted = authKey.length > 14 ? authKey.slice(0, 11) + "*".repeat(authKey.length - 14) + authKey.slice(-3) : authKey;
|
|
@@ -1108,7 +1141,7 @@ async function putAuthKey(sandbox) {
|
|
|
1108
1141
|
);
|
|
1109
1142
|
consola.success(redacted);
|
|
1110
1143
|
consola.success(
|
|
1111
|
-
`Tailscale auth key saved for sandbox: ${
|
|
1144
|
+
`Tailscale auth key saved for sandbox: ${c.primary(sandbox)}`
|
|
1112
1145
|
);
|
|
1113
1146
|
}
|
|
1114
1147
|
async function getTailscaleAuthKey(sandbox) {
|
|
@@ -1125,7 +1158,7 @@ async function getTailscaleAuthKey(sandbox) {
|
|
|
1125
1158
|
}
|
|
1126
1159
|
);
|
|
1127
1160
|
if (!data.sandbox) {
|
|
1128
|
-
consola.error(`Sandbox not found: ${
|
|
1161
|
+
consola.error(`Sandbox not found: ${c.primary(sandbox)}`);
|
|
1129
1162
|
process.exit(1);
|
|
1130
1163
|
}
|
|
1131
1164
|
try {
|
|
@@ -1140,24 +1173,349 @@ async function getTailscaleAuthKey(sandbox) {
|
|
|
1140
1173
|
}
|
|
1141
1174
|
}
|
|
1142
1175
|
);
|
|
1143
|
-
consola.info(`Tailscale auth key: ${
|
|
1176
|
+
consola.info(`Tailscale auth key: ${c.primary(tailscale.authKey)}`);
|
|
1144
1177
|
} catch {
|
|
1145
1178
|
consola.error(
|
|
1146
|
-
`No Tailscale Auth Key found for sandbox: ${
|
|
1179
|
+
`No Tailscale Auth Key found for sandbox: ${c.primary(sandbox)}`
|
|
1147
1180
|
);
|
|
1148
1181
|
process.exit(1);
|
|
1149
1182
|
}
|
|
1150
1183
|
}
|
|
1151
1184
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1185
|
+
async function exposePort(sandbox, port, description) {
|
|
1186
|
+
const token = await getAccessToken();
|
|
1187
|
+
try {
|
|
1188
|
+
const response = await client.post(
|
|
1189
|
+
`/xrpc/io.pocketenv.sandbox.exposePort`,
|
|
1190
|
+
{ port, description },
|
|
1191
|
+
{
|
|
1192
|
+
params: {
|
|
1193
|
+
id: sandbox
|
|
1194
|
+
},
|
|
1195
|
+
headers: {
|
|
1196
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
);
|
|
1200
|
+
consola.success(
|
|
1201
|
+
`Port ${c.primary(port)} exposed for sandbox ${c.primary(sandbox)}`
|
|
1202
|
+
);
|
|
1203
|
+
if (response.data.previewUrl) {
|
|
1204
|
+
consola.success(`Preview URL: ${c.secondary(response.data.previewUrl)}`);
|
|
1205
|
+
}
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
consola.error("Failed to expose port:", error);
|
|
1208
|
+
process.exit(1);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
async function unexposePort(sandbox, port) {
|
|
1213
|
+
const token = await getAccessToken();
|
|
1214
|
+
try {
|
|
1215
|
+
await client.post(
|
|
1216
|
+
`/xrpc/io.pocketenv.sandbox.unexposePort`,
|
|
1217
|
+
{ port },
|
|
1218
|
+
{
|
|
1219
|
+
params: {
|
|
1220
|
+
id: sandbox
|
|
1221
|
+
},
|
|
1222
|
+
headers: {
|
|
1223
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
);
|
|
1227
|
+
consola.success(
|
|
1228
|
+
`Port ${c.primary(port)} unexposed for sandbox ${c.primary(sandbox)}`
|
|
1229
|
+
);
|
|
1230
|
+
} catch (error) {
|
|
1231
|
+
consola.error(`Failed to unexpose port: ${error}`);
|
|
1232
|
+
process.exit(1);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
dayjs.extend(relativeTime);
|
|
1237
|
+
async function listVolumes(sandboxId) {
|
|
1238
|
+
const token = await getAccessToken();
|
|
1239
|
+
const response = await client.get(
|
|
1240
|
+
"/xrpc/io.pocketenv.volume.getVolumes",
|
|
1241
|
+
{
|
|
1242
|
+
params: {
|
|
1243
|
+
sandboxId
|
|
1244
|
+
},
|
|
1245
|
+
headers: {
|
|
1246
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
);
|
|
1250
|
+
const table = new Table({
|
|
1251
|
+
head: [
|
|
1252
|
+
c.primary("ID"),
|
|
1253
|
+
c.primary("NAME"),
|
|
1254
|
+
c.primary("PATH"),
|
|
1255
|
+
c.primary("CREATED AT")
|
|
1256
|
+
],
|
|
1257
|
+
chars: {
|
|
1258
|
+
top: "",
|
|
1259
|
+
"top-mid": "",
|
|
1260
|
+
"top-left": "",
|
|
1261
|
+
"top-right": "",
|
|
1262
|
+
bottom: "",
|
|
1263
|
+
"bottom-mid": "",
|
|
1264
|
+
"bottom-left": "",
|
|
1265
|
+
"bottom-right": "",
|
|
1266
|
+
left: "",
|
|
1267
|
+
"left-mid": "",
|
|
1268
|
+
mid: "",
|
|
1269
|
+
"mid-mid": "",
|
|
1270
|
+
right: "",
|
|
1271
|
+
"right-mid": "",
|
|
1272
|
+
middle: " "
|
|
1273
|
+
},
|
|
1274
|
+
style: {
|
|
1275
|
+
border: [],
|
|
1276
|
+
head: []
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1279
|
+
for (const volume of response.data.volumes) {
|
|
1280
|
+
table.push([
|
|
1281
|
+
c.secondary(volume.id),
|
|
1282
|
+
volume.name,
|
|
1283
|
+
volume.path,
|
|
1284
|
+
dayjs(volume.createdAt).fromNow()
|
|
1285
|
+
]);
|
|
1286
|
+
}
|
|
1287
|
+
consola.log(table.toString());
|
|
1288
|
+
}
|
|
1289
|
+
async function createVolume(sandbox, name, path) {
|
|
1290
|
+
const token = await getAccessToken();
|
|
1291
|
+
try {
|
|
1292
|
+
await client.post(
|
|
1293
|
+
"/xrpc/io.pocketenv.volume.addVolume",
|
|
1294
|
+
{
|
|
1295
|
+
volume: {
|
|
1296
|
+
sandboxId: sandbox,
|
|
1297
|
+
name,
|
|
1298
|
+
path
|
|
1299
|
+
}
|
|
1300
|
+
},
|
|
1301
|
+
{
|
|
1302
|
+
headers: {
|
|
1303
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
);
|
|
1307
|
+
consola.success(
|
|
1308
|
+
`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)}`
|
|
1309
|
+
);
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
consola.error("Failed to create volume:", error);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
async function deleteVolume(id) {
|
|
1315
|
+
const token = await getAccessToken();
|
|
1316
|
+
try {
|
|
1317
|
+
await client.post(`/xrpc/io.pocketenv.volume.deleteVolume`, void 0, {
|
|
1318
|
+
params: {
|
|
1319
|
+
id
|
|
1320
|
+
},
|
|
1321
|
+
headers: {
|
|
1322
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
consola.error(`Failed to delete volume: ${error}`);
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
consola.success(
|
|
1330
|
+
`Volume ${chalk.rgb(0, 232, 198)(id)} successfully deleted from sandbox`
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
dayjs.extend(relativeTime);
|
|
1335
|
+
async function putFile(sandbox, remotePath, localPath) {
|
|
1336
|
+
const token = await getAccessToken();
|
|
1337
|
+
let content;
|
|
1338
|
+
if (!process.stdin.isTTY) {
|
|
1339
|
+
const chunks = [];
|
|
1340
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
1341
|
+
content = Buffer.concat(chunks).toString().trim();
|
|
1342
|
+
} else if (localPath) {
|
|
1343
|
+
const resolvedPath = path.resolve(localPath);
|
|
1344
|
+
try {
|
|
1345
|
+
await fs.access(resolvedPath);
|
|
1346
|
+
} catch (err) {
|
|
1347
|
+
consola.error(`No such file: ${c.error(localPath)}`);
|
|
1348
|
+
process.exit(1);
|
|
1349
|
+
}
|
|
1350
|
+
content = await fs.readFile(resolvedPath, "utf-8");
|
|
1351
|
+
} else {
|
|
1352
|
+
content = (await editor({
|
|
1353
|
+
message: "File content (opens in $EDITOR):",
|
|
1354
|
+
waitForUserInput: false
|
|
1355
|
+
})).trim();
|
|
1356
|
+
}
|
|
1357
|
+
try {
|
|
1358
|
+
await client.post(
|
|
1359
|
+
"/xrpc/io.pocketenv.file.addFile",
|
|
1360
|
+
{
|
|
1361
|
+
file: {
|
|
1362
|
+
sandboxId: sandbox,
|
|
1363
|
+
path: remotePath,
|
|
1364
|
+
content: await encrypt(content)
|
|
1365
|
+
}
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
headers: {
|
|
1369
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
);
|
|
1373
|
+
consola.success(
|
|
1374
|
+
`File ${c.primary(remotePath)} successfully created in sandbox ${c.primary(sandbox)}`
|
|
1375
|
+
);
|
|
1376
|
+
} catch (error) {
|
|
1377
|
+
consola.error(`Failed to create file: ${error}`);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
async function listFiles(sandboxId) {
|
|
1381
|
+
const token = await getAccessToken();
|
|
1382
|
+
const response = await client.get(
|
|
1383
|
+
"/xrpc/io.pocketenv.file.getFiles",
|
|
1384
|
+
{
|
|
1385
|
+
params: {
|
|
1386
|
+
sandboxId
|
|
1387
|
+
},
|
|
1388
|
+
headers: {
|
|
1389
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
);
|
|
1393
|
+
const table = new Table({
|
|
1394
|
+
head: [c.primary("ID"), c.primary("PATH"), c.primary("CREATED AT")],
|
|
1395
|
+
chars: {
|
|
1396
|
+
top: "",
|
|
1397
|
+
"top-mid": "",
|
|
1398
|
+
"top-left": "",
|
|
1399
|
+
"top-right": "",
|
|
1400
|
+
bottom: "",
|
|
1401
|
+
"bottom-mid": "",
|
|
1402
|
+
"bottom-left": "",
|
|
1403
|
+
"bottom-right": "",
|
|
1404
|
+
left: "",
|
|
1405
|
+
"left-mid": "",
|
|
1406
|
+
mid: "",
|
|
1407
|
+
"mid-mid": "",
|
|
1408
|
+
right: "",
|
|
1409
|
+
"right-mid": "",
|
|
1410
|
+
middle: " "
|
|
1411
|
+
},
|
|
1412
|
+
style: {
|
|
1413
|
+
border: [],
|
|
1414
|
+
head: []
|
|
1415
|
+
}
|
|
1416
|
+
});
|
|
1417
|
+
for (const file of response.data.files) {
|
|
1418
|
+
table.push([
|
|
1419
|
+
c.secondary(file.id),
|
|
1420
|
+
file.path,
|
|
1421
|
+
dayjs(file.createdAt).fromNow()
|
|
1422
|
+
]);
|
|
1423
|
+
}
|
|
1424
|
+
consola.log(table.toString());
|
|
1425
|
+
}
|
|
1426
|
+
async function deleteFile(id) {
|
|
1427
|
+
const token = await getAccessToken();
|
|
1428
|
+
try {
|
|
1429
|
+
await client.post(`/xrpc/io.pocketenv.file.deleteFile`, void 0, {
|
|
1430
|
+
params: {
|
|
1431
|
+
id
|
|
1432
|
+
},
|
|
1433
|
+
headers: {
|
|
1434
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
consola.success(`File ${c.primary(id)} successfully deleted from sandbox`);
|
|
1438
|
+
} catch (error) {
|
|
1439
|
+
consola.error(`Failed to delete file: ${error}`);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
async function listPorts(sandbox) {
|
|
1444
|
+
const token = await getAccessToken();
|
|
1445
|
+
const response = await client.get(
|
|
1446
|
+
"/xrpc/io.pocketenv.sandbox.getExposedPorts",
|
|
1447
|
+
{
|
|
1448
|
+
params: {
|
|
1449
|
+
id: sandbox
|
|
1450
|
+
},
|
|
1451
|
+
headers: {
|
|
1452
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
);
|
|
1456
|
+
const table = new Table({
|
|
1457
|
+
head: [
|
|
1458
|
+
c.primary("PORT"),
|
|
1459
|
+
c.primary("DESCRIPTION"),
|
|
1460
|
+
c.primary("PREVIEW URL")
|
|
1461
|
+
],
|
|
1462
|
+
chars: {
|
|
1463
|
+
top: "",
|
|
1464
|
+
"top-mid": "",
|
|
1465
|
+
"top-left": "",
|
|
1466
|
+
"top-right": "",
|
|
1467
|
+
bottom: "",
|
|
1468
|
+
"bottom-mid": "",
|
|
1469
|
+
"bottom-left": "",
|
|
1470
|
+
"bottom-right": "",
|
|
1471
|
+
left: "",
|
|
1472
|
+
"left-mid": "",
|
|
1473
|
+
mid: "",
|
|
1474
|
+
"mid-mid": "",
|
|
1475
|
+
right: "",
|
|
1476
|
+
"right-mid": "",
|
|
1477
|
+
middle: " "
|
|
1478
|
+
},
|
|
1479
|
+
style: {
|
|
1480
|
+
border: [],
|
|
1481
|
+
head: []
|
|
1482
|
+
}
|
|
1483
|
+
});
|
|
1484
|
+
for (const port of response.data.ports) {
|
|
1485
|
+
table.push([
|
|
1486
|
+
c.secondary(port.port),
|
|
1487
|
+
port.description || "-",
|
|
1488
|
+
c.link(port.previewUrl || "-")
|
|
1489
|
+
]);
|
|
1490
|
+
}
|
|
1491
|
+
consola.log(table.toString());
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
async function exposeVscode(sandbox) {
|
|
1495
|
+
const token = await getAccessToken();
|
|
1496
|
+
try {
|
|
1497
|
+
const response = await client.post(
|
|
1498
|
+
`/xrpc/io.pocketenv.sandbox.exposeVscode`,
|
|
1499
|
+
void 0,
|
|
1500
|
+
{
|
|
1501
|
+
params: {
|
|
1502
|
+
id: sandbox
|
|
1503
|
+
},
|
|
1504
|
+
headers: {
|
|
1505
|
+
Authorization: `Bearer ${env$1.POCKETENV_TOKEN || token}`
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
);
|
|
1509
|
+
consola.success(`VS Code Server exposed for sandbox ${c.primary(sandbox)}`);
|
|
1510
|
+
if (response.data.previewUrl) {
|
|
1511
|
+
consola.success(`Preview URL: ${c.secondary(response.data.previewUrl)}`);
|
|
1512
|
+
}
|
|
1513
|
+
} catch (error) {
|
|
1514
|
+
consola.error("Failed to expose VS Code:", error);
|
|
1515
|
+
process.exit(1);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1161
1519
|
const program = new Command();
|
|
1162
1520
|
program.name("pocketenv").description(
|
|
1163
1521
|
`${chalk.bold.rgb(0, 232, 198)(`pocketenv v${version}`)} ${c.muted("\u2500")} ${c.muted("Open, interoperable sandbox platform for agents and humans")}`
|
|
@@ -1184,7 +1542,7 @@ program.command("login").argument("<handle>", "your AT Proto handle (e.g., <user
|
|
|
1184
1542
|
program.command("whoami").description("get the current logged-in user").action(whoami);
|
|
1185
1543
|
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);
|
|
1186
1544
|
program.command("ls").description("list sandboxes").action(listSandboxes);
|
|
1187
|
-
program.command("start").argument("<sandbox>", "the sandbox to start").description("start the given sandbox").action(start);
|
|
1545
|
+
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);
|
|
1188
1546
|
program.command("stop").argument("<sandbox>", "the sandbox to stop").description("stop the given sandbox").action(stop);
|
|
1189
1547
|
program.command("create").aliases(["new"]).option("--provider, -p <provider>", "the provider to use for the sandbox").option(
|
|
1190
1548
|
"--base, -b <base>",
|
|
@@ -1192,6 +1550,34 @@ program.command("create").aliases(["new"]).option("--provider, -p <provider>", "
|
|
|
1192
1550
|
).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);
|
|
1193
1551
|
program.command("logout").description("logout (removes session token)").action(logout);
|
|
1194
1552
|
program.command("rm").aliases(["delete", "remove"]).argument("<sandbox>", "the sandbox to delete").description("delete the given sandbox").action(deleteSandbox);
|
|
1553
|
+
program.command("vscode").argument("<sandbox>", "the sandbox to expose VS Code for").description(
|
|
1554
|
+
"expose a VS Code Server instance running in the given sandbox to the internet"
|
|
1555
|
+
).action(exposeVscode);
|
|
1556
|
+
program.command("expose").argument("<sandbox>", "the sandbox to expose a port for").argument("<port>", "the port to expose", (val) => {
|
|
1557
|
+
const port = parseInt(val, 10);
|
|
1558
|
+
if (isNaN(port)) {
|
|
1559
|
+
consola.error(`port must be a number, got: ${val}`);
|
|
1560
|
+
process.exit(1);
|
|
1561
|
+
}
|
|
1562
|
+
return port;
|
|
1563
|
+
}).argument("[description]", "an optional description for the exposed port").description("expose a port from the given sandbox to the internet").action(exposePort);
|
|
1564
|
+
program.command("unexpose").argument("<sandbox>", "the sandbox to unexpose a port for").argument("<port>", "the port to unexpose", (val) => {
|
|
1565
|
+
const port = parseInt(val, 10);
|
|
1566
|
+
if (isNaN(port)) {
|
|
1567
|
+
consola.error(`port must be a number, got: ${val}`);
|
|
1568
|
+
process.exit(1);
|
|
1569
|
+
}
|
|
1570
|
+
return port;
|
|
1571
|
+
}).description("unexpose a port from the given sandbox").action(unexposePort);
|
|
1572
|
+
const volume = program.command("volume").description("manage volumes");
|
|
1573
|
+
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);
|
|
1574
|
+
volume.command("list").aliases(["ls"]).argument("<sandbox>", "the sandbox to list volumes for").description("list volumes in the given sandbox").action(listVolumes);
|
|
1575
|
+
volume.command("delete").aliases(["rm", "remove"]).argument("<id>", "the ID of the volume to delete").description("delete a volume").action(deleteVolume);
|
|
1576
|
+
const file = program.command("file").description("manage files");
|
|
1577
|
+
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);
|
|
1578
|
+
file.command("list").aliases(["ls"]).argument("<sandbox>", "the sandbox to list files for").description("list files in the given sandbox").action(listFiles);
|
|
1579
|
+
file.command("delete").aliases(["rm", "remove"]).argument("<id>", "the ID of the file to delete").description("delete a file").action(deleteFile);
|
|
1580
|
+
program.command("ports").argument("<sandbox>", "the sandbox to list exposed ports for").description("list exposed ports for a sandbox").action(listPorts);
|
|
1195
1581
|
const secret = program.command("secret").description("manage secrets");
|
|
1196
1582
|
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);
|
|
1197
1583
|
secret.command("list").aliases(["ls"]).argument("<sandbox>", "the sandbox to list secrets for").description("list secrets in the given sandbox").action(listSecrets);
|