@pocketenv/cli 0.2.4 → 0.2.5
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 +87 -76
- package/package.json +1 -1
- package/src/cmd/ssh/tty.ts +21 -5
- package/src/cmd/start.ts +7 -1
- package/src/index.ts +1 -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.2.
|
|
25
|
+
var version = "0.2.5";
|
|
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,76 @@ 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
|
+
|
|
472
483
|
dayjs.extend(relativeTime);
|
|
473
484
|
async function listSandboxes() {
|
|
474
485
|
const token = await getAccessToken();
|
|
@@ -1184,7 +1195,7 @@ program.command("login").argument("<handle>", "your AT Proto handle (e.g., <user
|
|
|
1184
1195
|
program.command("whoami").description("get the current logged-in user").action(whoami);
|
|
1185
1196
|
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
1197
|
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);
|
|
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);
|
|
1188
1199
|
program.command("stop").argument("<sandbox>", "the sandbox to stop").description("stop the given sandbox").action(stop);
|
|
1189
1200
|
program.command("create").aliases(["new"]).option("--provider, -p <provider>", "the provider to use for the sandbox").option(
|
|
1190
1201
|
"--base, -b <base>",
|
package/package.json
CHANGED
package/src/cmd/ssh/tty.ts
CHANGED
|
@@ -129,7 +129,16 @@ async function ssh(sandbox: Sandbox): Promise<void> {
|
|
|
129
129
|
process.stdin.resume();
|
|
130
130
|
|
|
131
131
|
// stdin → POST /tty/:id/input
|
|
132
|
+
// In raw mode the OS never raises SIGINT — Ctrl+C arrives as a raw byte
|
|
133
|
+
// in the data stream and is forwarded to the remote shell as-is.
|
|
134
|
+
// We use Ctrl+K (\x0b) as a local-only escape hatch to avoid conflicting
|
|
135
|
+
// with Ctrl+C semantics inside the remote shell.
|
|
132
136
|
process.stdin.on("data", (chunk: Buffer) => {
|
|
137
|
+
if (chunk.includes(0x0b)) {
|
|
138
|
+
// Ctrl+K pressed — tear down immediately without waiting for the server.
|
|
139
|
+
teardown(0);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
133
142
|
sendInput(ttyUrl, sandbox.id, chunk, authToken);
|
|
134
143
|
});
|
|
135
144
|
|
|
@@ -195,11 +204,18 @@ async function ssh(sandbox: Sandbox): Promise<void> {
|
|
|
195
204
|
es.onerror = (err: ErrorEvent) => {
|
|
196
205
|
// The eventsource package exposes readyState on the EventSource instance.
|
|
197
206
|
if (es && es.readyState === EventSource.CLOSED) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
207
|
+
// If the shell exited cleanly the server will close the SSE stream with
|
|
208
|
+
// no error message. Treat a message-less close as a graceful exit (code
|
|
209
|
+
// 0) rather than a connection error, so the user isn't shown a red
|
|
210
|
+
// "connection lost" banner after a normal `exit`.
|
|
211
|
+
if (!err.message) {
|
|
212
|
+
teardown(0);
|
|
213
|
+
} else {
|
|
214
|
+
process.stderr.write(
|
|
215
|
+
`\r\n${chalk.red(`Terminal connection lost (${err.message})`)}\r\n`,
|
|
216
|
+
);
|
|
217
|
+
teardown(1);
|
|
218
|
+
}
|
|
203
219
|
}
|
|
204
220
|
};
|
|
205
221
|
|
package/src/cmd/start.ts
CHANGED
|
@@ -3,8 +3,9 @@ import chalk from "chalk";
|
|
|
3
3
|
import getAccessToken from "../lib/getAccessToken";
|
|
4
4
|
import { client } from "../client";
|
|
5
5
|
import { env } from "../lib/env";
|
|
6
|
+
import connectToSandbox from "./ssh";
|
|
6
7
|
|
|
7
|
-
async function start(name: string) {
|
|
8
|
+
async function start(name: string, { ssh }: { ssh?: boolean }) {
|
|
8
9
|
const token = await getAccessToken();
|
|
9
10
|
|
|
10
11
|
try {
|
|
@@ -17,6 +18,11 @@ async function start(name: string) {
|
|
|
17
18
|
},
|
|
18
19
|
});
|
|
19
20
|
|
|
21
|
+
if (ssh) {
|
|
22
|
+
await connectToSandbox(name);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
consola.success(`Sandbox ${chalk.greenBright(name)} started`);
|
|
21
27
|
consola.log(
|
|
22
28
|
`Run ${chalk.greenBright(`pocketenv console ${name}`)} to access the sandbox`,
|
package/src/index.ts
CHANGED
|
@@ -77,6 +77,7 @@ program.command("ls").description("list sandboxes").action(listSandboxes);
|
|
|
77
77
|
program
|
|
78
78
|
.command("start")
|
|
79
79
|
.argument("<sandbox>", "the sandbox to start")
|
|
80
|
+
.option("--ssh, -s", "connect to the Sandbox and automatically open a shell")
|
|
80
81
|
.description("start the given sandbox")
|
|
81
82
|
.action(start);
|
|
82
83
|
|