@lizard-build/cli 4.0.17 → 4.0.19
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/README.md +4 -0
- package/dist/commands/agents.d.ts +3 -0
- package/dist/commands/agents.js +295 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/sandbox-ssh.d.ts +13 -0
- package/dist/commands/sandbox-ssh.js +103 -0
- package/dist/commands/sandbox-ssh.js.map +1 -0
- package/dist/commands/sandbox.js +19 -0
- package/dist/commands/sandbox.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/skills-data.generated.js +1 -1
- package/dist/lib/skills-data.generated.js.map +1 -1
- package/dist/lib/updater.d.ts +1 -1
- package/dist/lib/updater.js +1 -1
- package/package.json +4 -2
- package/skill-data/core/SKILL.md +24 -0
- package/src/commands/agents.ts +278 -0
- package/src/commands/sandbox-ssh.ts +97 -0
- package/src/commands/sandbox.ts +24 -0
- package/src/index.ts +2 -0
- package/src/lib/updater.ts +1 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import WebSocket from "ws";
|
|
3
|
+
import { getBaseURL, getRequestToken } from "../lib/api.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `lizard sandbox ssh <id>` — an interactive shell in a running sandbox.
|
|
7
|
+
*
|
|
8
|
+
* Not the SSH protocol: it opens the same terminal the dashboard uses
|
|
9
|
+
* (wss://…/api/sandboxes/terminal), which the platform bridges onto an exec into the
|
|
10
|
+
* sandbox's pod with a TTY. So there are no keys to set up and nothing to open — the
|
|
11
|
+
* CLI's own API key is the credential, and a workspace- or project-scoped key only
|
|
12
|
+
* reaches sandboxes in its scope.
|
|
13
|
+
*
|
|
14
|
+
* Wire protocol (server/src/routes/ws.ts): binary frames are raw terminal bytes both
|
|
15
|
+
* ways; a text frame {"type":"resize","cols":N,"rows":M} resizes the remote TTY.
|
|
16
|
+
*/
|
|
17
|
+
export async function sandboxShell(id: string): Promise<number> {
|
|
18
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
19
|
+
throw new Error("`lizard sandbox ssh` needs an interactive terminal. To run a command, use `lizard sandbox exec <id> -- <cmd>`.");
|
|
20
|
+
}
|
|
21
|
+
const token = getRequestToken();
|
|
22
|
+
if (!token) throw new Error("Not logged in. Run `lizard login` first.");
|
|
23
|
+
|
|
24
|
+
const base = new URL(getBaseURL());
|
|
25
|
+
base.protocol = base.protocol === "http:" ? "ws:" : "wss:";
|
|
26
|
+
const url = new URL(`/api/sandboxes/terminal?id=${encodeURIComponent(id)}`, base);
|
|
27
|
+
|
|
28
|
+
const ws = new WebSocket(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
29
|
+
const stdin = process.stdin;
|
|
30
|
+
const stdout = process.stdout;
|
|
31
|
+
let rawMode = false;
|
|
32
|
+
|
|
33
|
+
const restore = () => {
|
|
34
|
+
if (rawMode) { try { stdin.setRawMode(false); } catch { /* stdin closed */ } rawMode = false; }
|
|
35
|
+
stdin.pause();
|
|
36
|
+
stdin.removeAllListeners("data");
|
|
37
|
+
stdout.removeAllListeners("resize");
|
|
38
|
+
};
|
|
39
|
+
const sendSize = () => {
|
|
40
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
41
|
+
ws.send(JSON.stringify({ type: "resize", cols: stdout.columns || 80, rows: stdout.rows || 24 }));
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return await new Promise<number>((settle) => {
|
|
46
|
+
let opened = false;
|
|
47
|
+
// ws emits `error` and then `close` for one failure: report and settle once.
|
|
48
|
+
let finished = false;
|
|
49
|
+
const resolve = (code: number) => { if (!finished) { finished = true; settle(code); } };
|
|
50
|
+
|
|
51
|
+
ws.on("open", () => {
|
|
52
|
+
stdin.setRawMode(true);
|
|
53
|
+
rawMode = true;
|
|
54
|
+
stdin.resume();
|
|
55
|
+
// Raw mode: Ctrl-C, Ctrl-Z, arrows and tab completion all go to the remote shell.
|
|
56
|
+
stdin.on("data", (chunk: Buffer) => { if (ws.readyState === WebSocket.OPEN) ws.send(chunk, { binary: true }); });
|
|
57
|
+
stdout.on("resize", sendSize);
|
|
58
|
+
sendSize();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
ws.on("message", (data: WebSocket.RawData) => {
|
|
62
|
+
// The socket opens before the server has checked the sandbox, so "connected" is
|
|
63
|
+
// only true once the shell itself speaks.
|
|
64
|
+
if (!opened) {
|
|
65
|
+
opened = true;
|
|
66
|
+
stdout.write(chalk.dim(`Connected to ${id}. Type \`exit\` or press Ctrl-D to leave.\r\n`));
|
|
67
|
+
}
|
|
68
|
+
stdout.write(Array.isArray(data) ? Buffer.concat(data) : Buffer.from(data as ArrayBuffer));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
ws.on("close", (code: number, reasonBuf: Buffer) => {
|
|
72
|
+
if (finished) return;
|
|
73
|
+
restore();
|
|
74
|
+
const reason = reasonBuf.toString();
|
|
75
|
+
// 1000 with "Shell exited" is the normal end of a session.
|
|
76
|
+
if (code === 1000 || code === 1005) {
|
|
77
|
+
if (opened) stdout.write(chalk.dim(`\r\nConnection to ${id} closed.\r\n`));
|
|
78
|
+
resolve(0);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const why: Record<number, string> = {
|
|
82
|
+
4001: "Not authorized — your login or API key was rejected. Run `lizard login`.",
|
|
83
|
+
4003: reason || "Forbidden — this sandbox is not in a workspace your key can reach.",
|
|
84
|
+
4004: `Sandbox ${id} not found.`,
|
|
85
|
+
};
|
|
86
|
+
stdout.write(chalk.red(`\r\n${why[code] ?? (reason || `Connection closed (${code}).`)}\r\n`));
|
|
87
|
+
resolve(1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
ws.on("error", (err: Error) => {
|
|
91
|
+
if (finished) return;
|
|
92
|
+
restore();
|
|
93
|
+
stdout.write(chalk.red(`\r\nCould not connect to ${id}: ${err.message}\r\n`));
|
|
94
|
+
resolve(1);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
package/src/commands/sandbox.ts
CHANGED
|
@@ -11,6 +11,8 @@ import { getToken } from "../lib/auth.js";
|
|
|
11
11
|
import { resolveProjectScope } from "../lib/resolve.js";
|
|
12
12
|
import { resolveProjectId } from "../lib/config.js";
|
|
13
13
|
import { resolveVolume } from "../lib/volume.js";
|
|
14
|
+
import { sandboxShell } from "./sandbox-ssh.js";
|
|
15
|
+
import { registerSandboxAgentCommands } from "./agents.js";
|
|
14
16
|
import { success, info, error, isJSONMode, printJSON, table, statusColor, timeAgo, isTTY } from "../lib/format.js";
|
|
15
17
|
|
|
16
18
|
// Templates are per-region rows in sandbox_templates, not a constant. Hardcoding them
|
|
@@ -269,6 +271,28 @@ Examples:
|
|
|
269
271
|
process.exit(exitCode);
|
|
270
272
|
});
|
|
271
273
|
|
|
274
|
+
registerSandboxAgentCommands(sb);
|
|
275
|
+
|
|
276
|
+
sb.command("ssh")
|
|
277
|
+
.alias("shell")
|
|
278
|
+
.argument("<id>", "Sandbox ID")
|
|
279
|
+
.description("Open an interactive shell in a running sandbox")
|
|
280
|
+
.addHelpText(
|
|
281
|
+
"after",
|
|
282
|
+
`
|
|
283
|
+
Works for every running sandbox with no keys to set up: it uses your Lizard login,
|
|
284
|
+
and a scoped API key only reaches sandboxes in its scope. Ctrl-C, arrows and tab
|
|
285
|
+
completion go to the sandbox; type \`exit\` or press Ctrl-D to leave.
|
|
286
|
+
|
|
287
|
+
To run one command non-interactively, use \`lizard sandbox exec <id> -- <cmd>\`.
|
|
288
|
+
|
|
289
|
+
Example:
|
|
290
|
+
lizard sandbox ssh sb_abc123`,
|
|
291
|
+
)
|
|
292
|
+
.action(async (id: string) => {
|
|
293
|
+
process.exit(await sandboxShell(id));
|
|
294
|
+
});
|
|
295
|
+
|
|
272
296
|
sb.command("logs")
|
|
273
297
|
.argument("<id>", "Sandbox ID")
|
|
274
298
|
.description("Stream sandbox vm-agent logs")
|
package/src/index.ts
CHANGED
|
@@ -83,6 +83,7 @@ import { registerRestart } from "./commands/restart.js";
|
|
|
83
83
|
import { registerRun } from "./commands/run.js";
|
|
84
84
|
import { registerS3 } from "./commands/s3.js";
|
|
85
85
|
import { registerSandbox } from "./commands/sandbox.js";
|
|
86
|
+
import { registerAgents } from "./commands/agents.js";
|
|
86
87
|
import { registerScale } from "./commands/scale.js";
|
|
87
88
|
import { registerSecrets } from "./commands/secrets.js";
|
|
88
89
|
import { registerService } from "./commands/service.js";
|
|
@@ -178,6 +179,7 @@ registerRestart(program);
|
|
|
178
179
|
registerRun(program);
|
|
179
180
|
registerS3(program);
|
|
180
181
|
registerSandbox(program);
|
|
182
|
+
registerAgents(program);
|
|
181
183
|
registerScale(program);
|
|
182
184
|
registerSecrets(program);
|
|
183
185
|
registerService(program);
|
package/src/lib/updater.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { join, dirname } from "node:path";
|
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
|
|
8
|
-
export const CURRENT_VERSION = "4.0.
|
|
8
|
+
export const CURRENT_VERSION = "4.0.19";
|
|
9
9
|
const RELEASES_API = "https://api.github.com/repos/lizard-build/lizard-cli/releases/latest";
|
|
10
10
|
const RELEASE_BASE = "https://github.com/lizard-build/lizard-cli/releases/latest/download";
|
|
11
11
|
|