@pocketenv/cli 0.2.3 → 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 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.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
- const detail = err.message ? ` (${err.message})` : "";
384
- process.stderr.write(
385
- `\r
386
- ${chalk.red(`Terminal connection lost${detail}`)}\r
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
- teardown(1);
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
@@ -4,7 +4,7 @@
4
4
  "bin": {
5
5
  "pocketenv": "dist/index.js"
6
6
  },
7
- "version": "0.2.3",
7
+ "version": "0.2.5",
8
8
  "type": "module",
9
9
  "keywords": [
10
10
  "sandbox",
@@ -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
- const detail = err.message ? ` (${err.message})` : "";
199
- process.stderr.write(
200
- `\r\n${chalk.red(`Terminal connection lost${detail}`)}\r\n`,
201
- );
202
- teardown(1);
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