@easybits.cloud/cli 0.6.0 → 0.7.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/README.md +26 -8
- package/dist/index.js +176 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,9 @@ Requires Node 22+.
|
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
easybits login # opens your browser (OAuth2 + PKCE); session renews itself
|
|
20
|
+
easybits login --no-browser # only print the sign-in URL
|
|
20
21
|
easybits login - < key.txt # or save an API key, read from stdin (never in argv)
|
|
22
|
+
printenv EB_KEY | easybits login --with-token # same as -
|
|
21
23
|
easybits usage
|
|
22
24
|
easybits logout
|
|
23
25
|
```
|
|
@@ -25,7 +27,8 @@ easybits logout
|
|
|
25
27
|
Running a command without a session in a terminal starts the login automatically;
|
|
26
28
|
without a terminal (agents, CI) it exits `3`. Precedence: `EASYBITS_API_KEY` env var >
|
|
27
29
|
`--token <key>` > browser session > saved API key. Keys: https://www.easybits.cloud/dash/developer.
|
|
28
|
-
In CI and agents, `EASYBITS_API_KEY` needs no login at all.
|
|
30
|
+
In CI and agents, `EASYBITS_API_KEY` needs no login at all. `EASYBITS_URL` points the CLI at
|
|
31
|
+
another server (default `https://www.easybits.cloud`; `baseUrl` in `~/.easybitsrc` works too).
|
|
29
32
|
|
|
30
33
|
## Commands
|
|
31
34
|
|
|
@@ -33,9 +36,10 @@ In CI and agents, `EASYBITS_API_KEY` needs no login at all.
|
|
|
33
36
|
# Sandboxes (alias: sb)
|
|
34
37
|
easybits sandboxes ls
|
|
35
38
|
easybits sandboxes create --template node --name scratch --dotenv .env
|
|
39
|
+
easybits sandboxes create --size m --timeout 1800 --no-wait # size s|m|l|xl (plan-gated)
|
|
36
40
|
easybits sandboxes get sb_abc123
|
|
37
41
|
easybits sandboxes exec sb_abc123 -- npm test
|
|
38
|
-
easybits sandboxes logs sb_abc123 --unit myapp --lines 100
|
|
42
|
+
easybits sandboxes logs sb_abc123 --unit myapp --lines 100 --since "10 min ago" --grep ERROR
|
|
39
43
|
easybits sandboxes files ls sb_abc123 /data/work
|
|
40
44
|
easybits sandboxes files read sb_abc123 /etc/os-release
|
|
41
45
|
easybits sandboxes files write sb_abc123 /data/work/app.js ./app.js
|
|
@@ -47,8 +51,8 @@ easybits sandboxes destroy sb_abc123 --yes
|
|
|
47
51
|
# Hosting: permanent machines (alias: deploy)
|
|
48
52
|
easybits machines ls
|
|
49
53
|
easybits machines deploy sb_abc123 -m "v1.2"
|
|
50
|
-
easybits machines releases sb_abc123
|
|
51
|
-
easybits machines logs sb_abc123 --grep ERROR
|
|
54
|
+
easybits machines releases sb_abc123 --limit 5
|
|
55
|
+
easybits machines logs sb_abc123 --lines 100 --grep ERROR
|
|
52
56
|
easybits machines rollback sb_abc123 rel_789
|
|
53
57
|
easybits machines secrets ls sb_abc123
|
|
54
58
|
easybits machines secrets set sb_abc123 --dotenv .env.production # - reads stdin
|
|
@@ -71,14 +75,17 @@ easybits db rm leads --yes
|
|
|
71
75
|
# Agents
|
|
72
76
|
easybits agents ls
|
|
73
77
|
easybits agents create --template goose --name helper --dotenv .env
|
|
74
|
-
easybits agents
|
|
75
|
-
easybits agents
|
|
78
|
+
easybits agents get helper # by name or id
|
|
79
|
+
easybits agents message helper "hello"
|
|
80
|
+
easybits agents message helper "and now?" --session ses_456 # continue a conversation
|
|
81
|
+
easybits agents destroy helper --yes
|
|
76
82
|
|
|
77
83
|
# Files, websites, account
|
|
78
84
|
easybits files ls
|
|
79
85
|
easybits files upload ./report.pdf
|
|
80
86
|
easybits files delete FILE_ID --yes
|
|
81
87
|
easybits websites ls
|
|
88
|
+
easybits providers # storage provider (Tigris by default)
|
|
82
89
|
easybits usage
|
|
83
90
|
|
|
84
91
|
# MCP, SSH, docs
|
|
@@ -86,15 +93,20 @@ easybits config # MCP config (streamable HTTP)
|
|
|
86
93
|
easybits mcp # MCP config (stdio)
|
|
87
94
|
easybits ssh-key # public key for sandbox SSH
|
|
88
95
|
easybits docs cli # a docs section as markdown (--en for English)
|
|
96
|
+
easybits docs --open # open the docs in your browser
|
|
89
97
|
```
|
|
90
98
|
|
|
99
|
+
**Names or ids.** Wherever a command takes an agent, a sandbox/machine or a database, you can
|
|
100
|
+
pass its name instead of the id (`easybits sb exec scratch -- ls`, `easybits domains ls shop`).
|
|
101
|
+
The match is exact and case-insensitive; if two share the name the CLI exits 2 and lists the ids.
|
|
102
|
+
|
|
91
103
|
Every command has help with examples: `easybits <command> <subcommand> --help`
|
|
92
104
|
(or `easybits help <command> <subcommand>`). A typo gets a "Did you mean" suggestion.
|
|
93
105
|
|
|
94
106
|
**Destructive commands** (`db rm`, `agents destroy`, `sandboxes destroy`, `domains rm`,
|
|
95
107
|
`files delete`) ask before acting in a terminal: `[y/N]`, or typing the name/id for
|
|
96
108
|
irreversible ones (a database, an agent, a permanent sandbox). Without a terminal they
|
|
97
|
-
never prompt
|
|
109
|
+
never prompt — and neither do they with `--json`: pass `--yes` / `-y`, or they exit `2`.
|
|
98
110
|
|
|
99
111
|
**Secrets** go in a dotenv file or on stdin, never in argv (visible in `ps` and shell
|
|
100
112
|
history): `--dotenv <path>` (`KEY=VALUE` lines, `-` = stdin) on `sandboxes create|exec|resume`,
|
|
@@ -116,12 +128,18 @@ Host *.ghosty
|
|
|
116
128
|
- `--json`: stdout is JSON only, errors included: `{"error":"…","code":3,"hint":"…"}` (`code` = exit code).
|
|
117
129
|
- Exit codes: `0` ok · `1` API error · `2` usage error · `3` no session, expired or rejected.
|
|
118
130
|
- Login for a person: `easybits login --json` prints `{"event":"login_url","url":…}` first (show it to them), then `{"event":"logged_in","email":…}`.
|
|
119
|
-
- Destructive commands need `--yes` (no TTY means no prompt: exit `2` with the exact command to rerun).
|
|
131
|
+
- Destructive commands need `--yes`, also with `--json` (no TTY or `--json` means no prompt: exit `2` with the exact command to rerun).
|
|
120
132
|
- `sandboxes exec` without `--json` exits with the remote command's code; with `--json` it exits 0 and reports `exitCode`.
|
|
121
133
|
- Skill: `npx skills add https://easybits.cloud` (includes `easybits-cli`).
|
|
122
134
|
|
|
123
135
|
Full reference: https://www.easybits.cloud/docs#cli · https://www.easybits.cloud/en/docs/cli.md
|
|
124
136
|
|
|
137
|
+
## Changes in 0.6.1
|
|
138
|
+
|
|
139
|
+
- Help: `--yes` is no longer listed as a global flag; it lives on each destructive command
|
|
140
|
+
(and the global help says it is required without a terminal or with `--json`).
|
|
141
|
+
- `easybits init` suggests `machines secrets set <id> --dotenv …` instead of `KEY=VALUE` in argv.
|
|
142
|
+
|
|
125
143
|
## Changes in 0.6.0
|
|
126
144
|
|
|
127
145
|
- **Breaking for scripts:** `db rm`, `agents destroy`, `sandboxes destroy`, `domains rm` and
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,7 @@ var KIND_HINTS = {
|
|
|
59
59
|
SandboxHostTimeout: "The operation may still be running. Check `easybits sb get <id>` and retry in a few minutes.",
|
|
60
60
|
SandboxHostError: "The sandbox host failed. Retry; if it persists, contact support.",
|
|
61
61
|
SQL_ERROR: "Fix the SQL statement.",
|
|
62
|
-
DATABASE_STORAGE_MISSING: "This database has no storage; its data is unavailable. Delete it (easybits db rm) and create a new one.",
|
|
62
|
+
DATABASE_STORAGE_MISSING: "This database has no storage; its data is unavailable. Delete it (easybits db rm <db> --yes) and create a new one.",
|
|
63
63
|
DATABASE_BACKEND_ERROR: "The database backend failed. Retry in a moment."
|
|
64
64
|
};
|
|
65
65
|
function toCliError(err) {
|
|
@@ -573,7 +573,7 @@ import { basename, extname } from "path";
|
|
|
573
573
|
// src/prompt.ts
|
|
574
574
|
import { createInterface } from "readline/promises";
|
|
575
575
|
var YES_OPTION = {
|
|
576
|
-
yes: { type: "boolean", short: "y", description: "Skip the confirmation (required without a terminal)" }
|
|
576
|
+
yes: { type: "boolean", short: "y", description: "Skip the confirmation (required without a terminal or with --json)" }
|
|
577
577
|
};
|
|
578
578
|
function sameCommand() {
|
|
579
579
|
const argv2 = process.argv.slice(2).map((a) => /^[\w@%+=:,./-]+$/.test(a) ? a : `'${a.replace(/'/g, `'\\''`)}'`);
|
|
@@ -877,7 +877,7 @@ One-time setup left:
|
|
|
877
877
|
|
|
878
878
|
3. If your app uses secret env vars:
|
|
879
879
|
|
|
880
|
-
easybits machines secrets set SANDBOX_ID
|
|
880
|
+
easybits machines secrets set SANDBOX_ID --dotenv .env.production
|
|
881
881
|
|
|
882
882
|
From then on, every push to main deploys.`));
|
|
883
883
|
}
|
|
@@ -992,11 +992,11 @@ function printSandbox(s) {
|
|
|
992
992
|
if (s.tier) console.log(`Tier: ${s.tier}${s.monthlyMxn != null ? ` ($${s.monthlyMxn} MXN/month)` : ""}`);
|
|
993
993
|
}
|
|
994
994
|
async function sandbox(ctx, usage2) {
|
|
995
|
-
const id = need(ctx, 0, "sandbox
|
|
995
|
+
const id = need(ctx, 0, "sandbox", usage2);
|
|
996
996
|
const eb = await getClient(ctx);
|
|
997
997
|
return eb.sandboxes.get(id);
|
|
998
998
|
}
|
|
999
|
-
var FILES_USAGE = "easybits sandboxes files <ls|read|write> <sandbox
|
|
999
|
+
var FILES_USAGE = "easybits sandboxes files <ls|read|write> <sandbox> <path> [local-file]";
|
|
1000
1000
|
var sandboxes = {
|
|
1001
1001
|
name: "sandboxes",
|
|
1002
1002
|
aliases: ["sandbox", "sb"],
|
|
@@ -1061,7 +1061,7 @@ var sandboxes = {
|
|
|
1061
1061
|
get: {
|
|
1062
1062
|
aliases: ["show", "status"],
|
|
1063
1063
|
summary: "Show one sandbox",
|
|
1064
|
-
usage: "easybits sandboxes get <sandbox
|
|
1064
|
+
usage: "easybits sandboxes get <sandbox>",
|
|
1065
1065
|
examples: ["easybits sandboxes get sb_abc123"],
|
|
1066
1066
|
async run(ctx) {
|
|
1067
1067
|
const rec = sandboxRecord(await sandbox(ctx, this.usage));
|
|
@@ -1070,7 +1070,7 @@ var sandboxes = {
|
|
|
1070
1070
|
},
|
|
1071
1071
|
exec: {
|
|
1072
1072
|
summary: "Run a shell command inside a sandbox",
|
|
1073
|
-
usage: "easybits sandboxes exec <sandbox
|
|
1073
|
+
usage: "easybits sandboxes exec <sandbox> [--cwd <dir>] [--timeout <s>] [--dotenv <path>] -- <command...>",
|
|
1074
1074
|
options: {
|
|
1075
1075
|
cwd: { type: "string", value: "dir", description: "Working directory" },
|
|
1076
1076
|
timeout: { type: "string", value: "seconds", description: "Kill the command after this long" },
|
|
@@ -1084,7 +1084,7 @@ var sandboxes = {
|
|
|
1084
1084
|
"easybits sb exec sb_abc123 --json -- 'ls -la /' | jq .exitCode"
|
|
1085
1085
|
],
|
|
1086
1086
|
async run(ctx) {
|
|
1087
|
-
need(ctx, 0, "sandbox
|
|
1087
|
+
need(ctx, 0, "sandbox", this.usage);
|
|
1088
1088
|
const command = ctx.args.slice(1).join(" ");
|
|
1089
1089
|
if (!command) throw usageError("Missing <command>.", this.usage);
|
|
1090
1090
|
const env = await envFrom(ctx, this.usage);
|
|
@@ -1107,7 +1107,7 @@ var sandboxes = {
|
|
|
1107
1107
|
},
|
|
1108
1108
|
logs: {
|
|
1109
1109
|
summary: "Read the sandbox journal (optionally one systemd unit)",
|
|
1110
|
-
usage: "easybits sandboxes logs <sandbox
|
|
1110
|
+
usage: "easybits sandboxes logs <sandbox> [--unit <unit>] [--lines <n>] [--since <spec>] [--grep <text>]",
|
|
1111
1111
|
options: {
|
|
1112
1112
|
unit: { type: "string", value: "unit", description: "systemd unit to filter" },
|
|
1113
1113
|
lines: { type: "string", value: "n", description: "Last N lines (default 200)" },
|
|
@@ -1142,7 +1142,7 @@ var sandboxes = {
|
|
|
1142
1142
|
],
|
|
1143
1143
|
async run(ctx) {
|
|
1144
1144
|
const action = need(ctx, 0, "ls|read|write", FILES_USAGE);
|
|
1145
|
-
const id = need(ctx, 1, "sandbox
|
|
1145
|
+
const id = need(ctx, 1, "sandbox", FILES_USAGE);
|
|
1146
1146
|
const path = need(ctx, 2, "path", FILES_USAGE);
|
|
1147
1147
|
const eb = await getClient(ctx);
|
|
1148
1148
|
const sb = await eb.sandboxes.get(id);
|
|
@@ -1186,7 +1186,7 @@ var sandboxes = {
|
|
|
1186
1186
|
},
|
|
1187
1187
|
suspend: {
|
|
1188
1188
|
summary: "Suspend to disk (frees CPU; TTL paused)",
|
|
1189
|
-
usage: "easybits sandboxes suspend <sandbox
|
|
1189
|
+
usage: "easybits sandboxes suspend <sandbox>",
|
|
1190
1190
|
examples: ["easybits sandboxes suspend sb_abc123"],
|
|
1191
1191
|
async run(ctx) {
|
|
1192
1192
|
const r = await (await sandbox(ctx, this.usage)).suspend();
|
|
@@ -1195,7 +1195,7 @@ var sandboxes = {
|
|
|
1195
1195
|
},
|
|
1196
1196
|
resume: {
|
|
1197
1197
|
summary: "Wake a suspended sandbox",
|
|
1198
|
-
usage: "easybits sandboxes resume <sandbox
|
|
1198
|
+
usage: "easybits sandboxes resume <sandbox> [--dotenv <path>] [--env K=V]...",
|
|
1199
1199
|
options: {
|
|
1200
1200
|
env: { type: "string", multiple: true, value: "K=V", description: "Rewrite env on wake, non-secret (repeatable)" },
|
|
1201
1201
|
...ENV_FILE_OPTION
|
|
@@ -1210,11 +1210,11 @@ var sandboxes = {
|
|
|
1210
1210
|
destroy: {
|
|
1211
1211
|
aliases: ["rm", "delete"],
|
|
1212
1212
|
summary: "Destroy a sandbox (irreversible)",
|
|
1213
|
-
usage: "easybits sandboxes destroy <sandbox
|
|
1213
|
+
usage: "easybits sandboxes destroy <sandbox> [--yes]",
|
|
1214
1214
|
options: { ...YES_OPTION },
|
|
1215
1215
|
examples: ["easybits sandboxes destroy sb_abc123", "easybits sb rm sb_abc123 --yes"],
|
|
1216
1216
|
async run(ctx) {
|
|
1217
|
-
need(ctx, 0, "sandbox
|
|
1217
|
+
need(ctx, 0, "sandbox", this.usage);
|
|
1218
1218
|
requireYesIfHeadless(ctx);
|
|
1219
1219
|
const sb = await sandbox(ctx, this.usage);
|
|
1220
1220
|
await confirm(
|
|
@@ -1228,7 +1228,7 @@ var sandboxes = {
|
|
|
1228
1228
|
},
|
|
1229
1229
|
snapshot: {
|
|
1230
1230
|
summary: "Capture a copy-on-write snapshot without stopping the box",
|
|
1231
|
-
usage: "easybits sandboxes snapshot <sandbox
|
|
1231
|
+
usage: "easybits sandboxes snapshot <sandbox> [--name <name>]",
|
|
1232
1232
|
options: { name: { type: "string", value: "name", description: "Snapshot label" } },
|
|
1233
1233
|
examples: ["easybits sandboxes snapshot sb_abc123 --name before-upgrade"],
|
|
1234
1234
|
async run(ctx) {
|
|
@@ -1240,7 +1240,7 @@ var sandboxes = {
|
|
|
1240
1240
|
};
|
|
1241
1241
|
|
|
1242
1242
|
// src/commands/machines.ts
|
|
1243
|
-
var SECRETS_USAGE = "easybits machines secrets <ls|set|unset> <machine
|
|
1243
|
+
var SECRETS_USAGE = "easybits machines secrets <ls|set|unset> <machine> [--dotenv <path> | NAME]";
|
|
1244
1244
|
var machines = {
|
|
1245
1245
|
name: "machines",
|
|
1246
1246
|
aliases: ["deploy", "machine"],
|
|
@@ -1270,11 +1270,11 @@ var machines = {
|
|
|
1270
1270
|
deploy: {
|
|
1271
1271
|
aliases: ["release"],
|
|
1272
1272
|
summary: "Publish the machine's current app code as a new release",
|
|
1273
|
-
usage: "easybits machines deploy <machine
|
|
1273
|
+
usage: "easybits machines deploy <machine> [--message <text>]",
|
|
1274
1274
|
options: { message: { type: "string", short: "m", value: "text", description: "Release note" } },
|
|
1275
1275
|
examples: ['easybits machines deploy sb_abc123 -m "v1.2: fix checkout"'],
|
|
1276
1276
|
async run(ctx) {
|
|
1277
|
-
const id = need(ctx, 0, "machine
|
|
1277
|
+
const id = need(ctx, 0, "machine", this.usage);
|
|
1278
1278
|
const eb = await getClient(ctx);
|
|
1279
1279
|
const r = await eb.machines.deploy(id, { message: str(ctx, "message") });
|
|
1280
1280
|
emit(ctx, r, () => console.log(`Release v${r.version} ${r.releaseId} (${r.status}, ${fmtBytes(r.sizeBytes)})`));
|
|
@@ -1282,11 +1282,11 @@ var machines = {
|
|
|
1282
1282
|
},
|
|
1283
1283
|
releases: {
|
|
1284
1284
|
summary: "List releases, newest first",
|
|
1285
|
-
usage: "easybits machines releases <machine
|
|
1285
|
+
usage: "easybits machines releases <machine> [--limit <n>]",
|
|
1286
1286
|
options: { limit: { type: "string", value: "n", description: "How many" } },
|
|
1287
1287
|
examples: ["easybits machines releases sb_abc123 --limit 5"],
|
|
1288
1288
|
async run(ctx) {
|
|
1289
|
-
const id = need(ctx, 0, "machine
|
|
1289
|
+
const id = need(ctx, 0, "machine", this.usage);
|
|
1290
1290
|
const eb = await getClient(ctx);
|
|
1291
1291
|
const r = await eb.machines.releases(id, { limit: int(ctx, "limit", this.usage) });
|
|
1292
1292
|
emit(
|
|
@@ -1295,21 +1295,21 @@ var machines = {
|
|
|
1295
1295
|
() => table(
|
|
1296
1296
|
r.items.map((x) => ({ ...x, size: fmtBytes(x.sizeBytes), createdAt: fmtDate(x.createdAt) })),
|
|
1297
1297
|
[["version", "VERSION"], ["releaseId", "ID"], ["status", "STATUS"], ["size", "SIZE"], ["createdAt", "CREATED"], ["message", "MESSAGE"]],
|
|
1298
|
-
"No releases yet. Publish one: easybits machines deploy <machine
|
|
1298
|
+
"No releases yet. Publish one: easybits machines deploy <machine>"
|
|
1299
1299
|
)
|
|
1300
1300
|
);
|
|
1301
1301
|
}
|
|
1302
1302
|
},
|
|
1303
1303
|
logs: {
|
|
1304
1304
|
summary: "Tail the app's own log",
|
|
1305
|
-
usage: "easybits machines logs <machine
|
|
1305
|
+
usage: "easybits machines logs <machine> [--lines <n>] [--grep <text>]",
|
|
1306
1306
|
options: {
|
|
1307
1307
|
lines: { type: "string", value: "n", description: "Last N lines" },
|
|
1308
1308
|
grep: { type: "string", value: "text", description: "Only lines matching" }
|
|
1309
1309
|
},
|
|
1310
1310
|
examples: ["easybits machines logs sb_abc123 --lines 100", "easybits machines logs sb_abc123 --grep ERROR"],
|
|
1311
1311
|
async run(ctx) {
|
|
1312
|
-
const id = need(ctx, 0, "machine
|
|
1312
|
+
const id = need(ctx, 0, "machine", this.usage);
|
|
1313
1313
|
const eb = await getClient(ctx);
|
|
1314
1314
|
const r = await eb.machines.logs(id, { lines: int(ctx, "lines", this.usage), grep: str(ctx, "grep") });
|
|
1315
1315
|
emit(ctx, r, () => process.stdout.write(r.output.endsWith("\n") || !r.output ? r.output : r.output + "\n"));
|
|
@@ -1317,10 +1317,10 @@ var machines = {
|
|
|
1317
1317
|
},
|
|
1318
1318
|
rollback: {
|
|
1319
1319
|
summary: "Put a previous release back on the same machine (data untouched)",
|
|
1320
|
-
usage: "easybits machines rollback <machine
|
|
1320
|
+
usage: "easybits machines rollback <machine> <release-id>",
|
|
1321
1321
|
examples: ["easybits machines rollback sb_abc123 rel_789"],
|
|
1322
1322
|
async run(ctx) {
|
|
1323
|
-
const id = need(ctx, 0, "machine
|
|
1323
|
+
const id = need(ctx, 0, "machine", this.usage);
|
|
1324
1324
|
const rel = need(ctx, 1, "release-id", this.usage);
|
|
1325
1325
|
const eb = await getClient(ctx);
|
|
1326
1326
|
const r = await eb.machines.rollback(id, rel);
|
|
@@ -1340,7 +1340,7 @@ var machines = {
|
|
|
1340
1340
|
],
|
|
1341
1341
|
async run(ctx) {
|
|
1342
1342
|
const action = need(ctx, 0, "ls|set|unset", SECRETS_USAGE);
|
|
1343
|
-
const id = need(ctx, 1, "machine
|
|
1343
|
+
const id = need(ctx, 1, "machine", SECRETS_USAGE);
|
|
1344
1344
|
const eb = await getClient(ctx);
|
|
1345
1345
|
if (action === "ls" || action === "list") {
|
|
1346
1346
|
const r = await eb.machines.secrets(id);
|
|
@@ -1368,7 +1368,7 @@ var machines = {
|
|
|
1368
1368
|
|
|
1369
1369
|
// src/commands/domains.ts
|
|
1370
1370
|
async function sandbox2(ctx, usage2) {
|
|
1371
|
-
const id = need(ctx, 0, "sandbox
|
|
1371
|
+
const id = need(ctx, 0, "sandbox", usage2);
|
|
1372
1372
|
return (await getClient(ctx)).sandboxes.get(id);
|
|
1373
1373
|
}
|
|
1374
1374
|
var domains = {
|
|
@@ -1381,7 +1381,7 @@ var domains = {
|
|
|
1381
1381
|
ls: {
|
|
1382
1382
|
aliases: ["list"],
|
|
1383
1383
|
summary: "List domains attached to a sandbox",
|
|
1384
|
-
usage: "easybits domains ls <sandbox
|
|
1384
|
+
usage: "easybits domains ls <sandbox>",
|
|
1385
1385
|
examples: ["easybits domains ls sb_abc123"],
|
|
1386
1386
|
async run(ctx) {
|
|
1387
1387
|
const items = await (await sandbox2(ctx, this.usage)).listDomains();
|
|
@@ -1390,7 +1390,7 @@ var domains = {
|
|
|
1390
1390
|
},
|
|
1391
1391
|
add: {
|
|
1392
1392
|
summary: "Attach a domain to a port; prints the DNS record to create",
|
|
1393
|
-
usage: "easybits domains add <sandbox
|
|
1393
|
+
usage: "easybits domains add <sandbox> <domain> --port <port>",
|
|
1394
1394
|
options: { port: { type: "string", value: "port", description: "Port inside the sandbox (required)" } },
|
|
1395
1395
|
examples: ["easybits domains add sb_abc123 shop.example.com --port 3000"],
|
|
1396
1396
|
async run(ctx) {
|
|
@@ -1407,7 +1407,7 @@ var domains = {
|
|
|
1407
1407
|
},
|
|
1408
1408
|
verify: {
|
|
1409
1409
|
summary: "Check DNS + HTTPS for a domain",
|
|
1410
|
-
usage: "easybits domains verify <sandbox
|
|
1410
|
+
usage: "easybits domains verify <sandbox> <domain>",
|
|
1411
1411
|
examples: ["easybits domains verify sb_abc123 shop.example.com"],
|
|
1412
1412
|
async run(ctx) {
|
|
1413
1413
|
const domain = need(ctx, 1, "domain", this.usage);
|
|
@@ -1423,11 +1423,11 @@ var domains = {
|
|
|
1423
1423
|
rm: {
|
|
1424
1424
|
aliases: ["remove", "delete"],
|
|
1425
1425
|
summary: "Detach a domain",
|
|
1426
|
-
usage: "easybits domains rm <sandbox
|
|
1426
|
+
usage: "easybits domains rm <sandbox> <domain> [--yes]",
|
|
1427
1427
|
options: { ...YES_OPTION },
|
|
1428
1428
|
examples: ["easybits domains rm sb_abc123 shop.example.com", "easybits domains rm sb_abc123 shop.example.com --yes"],
|
|
1429
1429
|
async run(ctx) {
|
|
1430
|
-
need(ctx, 0, "sandbox
|
|
1430
|
+
need(ctx, 0, "sandbox", this.usage);
|
|
1431
1431
|
const domain = need(ctx, 1, "domain", this.usage);
|
|
1432
1432
|
requireYesIfHeadless(ctx);
|
|
1433
1433
|
await confirm(ctx, `Detach ${domain} from ${ctx.args[0]}? It stops serving right away.`);
|
|
@@ -1438,12 +1438,100 @@ var domains = {
|
|
|
1438
1438
|
}
|
|
1439
1439
|
};
|
|
1440
1440
|
|
|
1441
|
+
// src/refs.ts
|
|
1442
|
+
var ID_FORMAT = {
|
|
1443
|
+
agent: /^[0-9a-f]{24}$/i,
|
|
1444
|
+
db: /^[0-9a-f]{24}$/i,
|
|
1445
|
+
sandbox: /^sb_[0-9a-z-]+$/i
|
|
1446
|
+
};
|
|
1447
|
+
var looksLikeId = (kind, ref) => ID_FORMAT[kind].test(ref);
|
|
1448
|
+
var REF_ARGS = {
|
|
1449
|
+
"agents get": { index: 0, kind: "agent" },
|
|
1450
|
+
"agents message": { index: 0, kind: "agent" },
|
|
1451
|
+
"agents destroy": { index: 0, kind: "agent" },
|
|
1452
|
+
"sandboxes get": { index: 0, kind: "sandbox" },
|
|
1453
|
+
"sandboxes exec": { index: 0, kind: "sandbox" },
|
|
1454
|
+
"sandboxes logs": { index: 0, kind: "sandbox" },
|
|
1455
|
+
"sandboxes files": { index: 1, kind: "sandbox" },
|
|
1456
|
+
"sandboxes suspend": { index: 0, kind: "sandbox" },
|
|
1457
|
+
"sandboxes resume": { index: 0, kind: "sandbox" },
|
|
1458
|
+
"sandboxes destroy": { index: 0, kind: "sandbox" },
|
|
1459
|
+
"sandboxes snapshot": { index: 0, kind: "sandbox" },
|
|
1460
|
+
"machines deploy": { index: 0, kind: "sandbox" },
|
|
1461
|
+
"machines releases": { index: 0, kind: "sandbox" },
|
|
1462
|
+
"machines logs": { index: 0, kind: "sandbox" },
|
|
1463
|
+
"machines rollback": { index: 0, kind: "sandbox" },
|
|
1464
|
+
"machines secrets": { index: 1, kind: "sandbox" },
|
|
1465
|
+
"domains ls": { index: 0, kind: "sandbox" },
|
|
1466
|
+
"domains add": { index: 0, kind: "sandbox" },
|
|
1467
|
+
"domains verify": { index: 0, kind: "sandbox" },
|
|
1468
|
+
"domains rm": { index: 0, kind: "sandbox" },
|
|
1469
|
+
"db rm": { index: 0, kind: "db" },
|
|
1470
|
+
"db tables": { index: 0, kind: "db" },
|
|
1471
|
+
"db query": { index: 0, kind: "db" }
|
|
1472
|
+
};
|
|
1473
|
+
function matchRef(kind, ref, items) {
|
|
1474
|
+
if (looksLikeId(kind, ref)) return { ok: true, id: ref };
|
|
1475
|
+
const list2 = items ?? [];
|
|
1476
|
+
if (list2.some((x) => x.id === ref)) return { ok: true, id: ref };
|
|
1477
|
+
const want = ref.trim().toLowerCase();
|
|
1478
|
+
const matches = list2.filter((x) => (x.name ?? "").trim().toLowerCase() === want);
|
|
1479
|
+
if (matches.length === 1) return { ok: true, id: matches[0].id };
|
|
1480
|
+
if (matches.length === 0) return { ok: false, reason: "not_found" };
|
|
1481
|
+
return { ok: false, reason: "ambiguous", matches };
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
// src/resolve.ts
|
|
1485
|
+
var LS_HINT = {
|
|
1486
|
+
agent: "easybits agents ls",
|
|
1487
|
+
sandbox: "easybits sandboxes ls",
|
|
1488
|
+
db: "easybits db ls"
|
|
1489
|
+
};
|
|
1490
|
+
var NOUN = { agent: "agent", sandbox: "sandbox or machine", db: "database" };
|
|
1491
|
+
var cache = /* @__PURE__ */ new Map();
|
|
1492
|
+
function listRefs(ctx, kind) {
|
|
1493
|
+
let p = cache.get(kind);
|
|
1494
|
+
if (!p) {
|
|
1495
|
+
p = (async () => {
|
|
1496
|
+
const eb = await getClient(ctx);
|
|
1497
|
+
if (kind === "agent") return (await eb.listAgents()).map((a) => ({ id: a.agentId, name: a.name, status: a.status }));
|
|
1498
|
+
if (kind === "db") return (await eb.listDatabases()).items.map((d) => ({ id: d.id, name: d.name }));
|
|
1499
|
+
return (await eb.sandboxes.list()).map((s) => ({ id: s.sandboxId, name: s.name, status: s.status }));
|
|
1500
|
+
})();
|
|
1501
|
+
p.catch(() => cache.delete(kind));
|
|
1502
|
+
cache.set(kind, p);
|
|
1503
|
+
}
|
|
1504
|
+
return p;
|
|
1505
|
+
}
|
|
1506
|
+
async function resolveRef(ctx, kind, ref) {
|
|
1507
|
+
if (looksLikeId(kind, ref)) return ref;
|
|
1508
|
+
const r = matchRef(kind, ref, await listRefs(ctx, kind));
|
|
1509
|
+
if (r.ok) return r.id;
|
|
1510
|
+
if (r.reason === "not_found") {
|
|
1511
|
+
throw new CliError(`No ${NOUN[kind]} with id or name "${ref}".`, EXIT.API, `List them with: ${LS_HINT[kind]}`, "not_found", 404);
|
|
1512
|
+
}
|
|
1513
|
+
const shown = r.matches.slice(0, 10).map((m) => ` ${m.id}${m.status ? ` (${m.status})` : ""}`);
|
|
1514
|
+
if (r.matches.length > 10) shown.push(` \u2026and ${r.matches.length - 10} more`);
|
|
1515
|
+
throw new CliError(
|
|
1516
|
+
`"${ref}" matches ${r.matches.length} ${kind === "db" ? "databases" : `${kind}s`}. Use the id:`,
|
|
1517
|
+
EXIT.USAGE,
|
|
1518
|
+
`${shown.join("\n")}
|
|
1519
|
+
List them with: ${LS_HINT[kind]}`,
|
|
1520
|
+
"ambiguous"
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
async function resolveRefs(ctx, key) {
|
|
1524
|
+
const spec = REF_ARGS[key];
|
|
1525
|
+
const ref = spec ? ctx.args[spec.index] : void 0;
|
|
1526
|
+
if (!spec || !ref) return;
|
|
1527
|
+
ctx.args[spec.index] = await resolveRef(ctx, spec.kind, ref);
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1441
1530
|
// src/commands/db.ts
|
|
1442
|
-
async function findDb(
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
return hit;
|
|
1531
|
+
async function findDb(ctx, id) {
|
|
1532
|
+
const hit = (await listRefs(ctx, "db")).find((d) => d.id === id);
|
|
1533
|
+
if (!hit) throw new CliError(`No database "${id}".`, 1, "List them with: easybits db ls", "not_found", 404);
|
|
1534
|
+
return { id, name: hit.name ?? id };
|
|
1447
1535
|
}
|
|
1448
1536
|
var db = {
|
|
1449
1537
|
name: "db",
|
|
@@ -1493,7 +1581,7 @@ var db = {
|
|
|
1493
1581
|
const ref = need(ctx, 0, "db-id|name", this.usage);
|
|
1494
1582
|
requireYesIfHeadless(ctx);
|
|
1495
1583
|
const eb = await getClient(ctx);
|
|
1496
|
-
const hit = await findDb(
|
|
1584
|
+
const hit = await findDb(ctx, ref);
|
|
1497
1585
|
await confirm(ctx, `Delete database ${hit.name} (${hit.id}) and all its data?`, { typeName: hit.name });
|
|
1498
1586
|
const r = await eb.deleteDatabase(hit.id);
|
|
1499
1587
|
emit(ctx, { ...r, id: hit.id, name: hit.name }, () => console.log(`Deleted ${hit.name} (${hit.id})`));
|
|
@@ -1508,7 +1596,7 @@ var db = {
|
|
|
1508
1596
|
async run(ctx) {
|
|
1509
1597
|
const ref = need(ctx, 0, "db-id|name", this.usage);
|
|
1510
1598
|
const eb = await getClient(ctx);
|
|
1511
|
-
const hit = await findDb(
|
|
1599
|
+
const hit = await findDb(ctx, ref);
|
|
1512
1600
|
const q = (sql, args) => eb.queryDatabase(hit.id, sql, args);
|
|
1513
1601
|
const names = (await q("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_litestream_%' ORDER BY name")).rows.map((r) => String(r[0]));
|
|
1514
1602
|
const ident = (n) => `"${n.replace(/"/g, '""')}"`;
|
|
@@ -1549,7 +1637,7 @@ var db = {
|
|
|
1549
1637
|
const ref = need(ctx, 0, "db-id|name", this.usage);
|
|
1550
1638
|
const sql = need(ctx, 1, "sql", this.usage);
|
|
1551
1639
|
const eb = await getClient(ctx);
|
|
1552
|
-
const hit = await findDb(
|
|
1640
|
+
const hit = await findDb(ctx, ref);
|
|
1553
1641
|
const args = list(ctx, "arg");
|
|
1554
1642
|
const r = await eb.queryDatabase(hit.id, sql, args.length ? args : void 0);
|
|
1555
1643
|
emit(ctx, r, () => {
|
|
@@ -1595,6 +1683,27 @@ var agents = {
|
|
|
1595
1683
|
);
|
|
1596
1684
|
}
|
|
1597
1685
|
},
|
|
1686
|
+
get: {
|
|
1687
|
+
aliases: ["show"],
|
|
1688
|
+
summary: "Show one agent (by id or name)",
|
|
1689
|
+
usage: "easybits agents get <agent>",
|
|
1690
|
+
examples: ["easybits agents get helper", "easybits agents get 6650f0c2a1b2c3d4e5f60718 --json"],
|
|
1691
|
+
async run(ctx) {
|
|
1692
|
+
const id = need(ctx, 0, "agent", this.usage);
|
|
1693
|
+
const eb = await getClient(ctx);
|
|
1694
|
+
const a = await eb.getAgent(id);
|
|
1695
|
+
emit(ctx, a, () => {
|
|
1696
|
+
console.log(`ID: ${a.agentId}`);
|
|
1697
|
+
if (a.name) console.log(`Name: ${a.name}`);
|
|
1698
|
+
console.log(`Template: ${a.template}`);
|
|
1699
|
+
console.log(`Status: ${a.status}`);
|
|
1700
|
+
console.log(`Sandbox: ${a.sandboxId}`);
|
|
1701
|
+
console.log(`URL: ${a.agentUrl}`);
|
|
1702
|
+
console.log(`Created: ${fmtDate(a.createdAt)}`);
|
|
1703
|
+
console.log(`Expires: ${a.expiresAt ? fmtDate(a.expiresAt) : "never"}`);
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
},
|
|
1598
1707
|
create: {
|
|
1599
1708
|
summary: "Create an agent from a template",
|
|
1600
1709
|
usage: "easybits agents create --template <template> [--name <name>] [--dotenv <path>] [--env K=V]... [--timeout <s>]",
|
|
@@ -1631,12 +1740,12 @@ var agents = {
|
|
|
1631
1740
|
},
|
|
1632
1741
|
message: {
|
|
1633
1742
|
aliases: ["msg", "send"],
|
|
1634
|
-
summary: "Send a message; streams the reply",
|
|
1635
|
-
usage: "easybits agents message <agent
|
|
1743
|
+
summary: "Send a message (agent by id or name); streams the reply",
|
|
1744
|
+
usage: "easybits agents message <agent> <text...> [--session <id>]",
|
|
1636
1745
|
options: { session: { type: "string", value: "id", description: "Continue a conversation" } },
|
|
1637
|
-
examples: ['easybits agents message
|
|
1746
|
+
examples: ['easybits agents message helper "summarize /data/work/README.md"', "easybits agents message 6650f0c2a1b2c3d4e5f60718 hi --json"],
|
|
1638
1747
|
async run(ctx) {
|
|
1639
|
-
const id = need(ctx, 0, "agent
|
|
1748
|
+
const id = need(ctx, 0, "agent", this.usage);
|
|
1640
1749
|
const content = ctx.args.slice(1).join(" ");
|
|
1641
1750
|
if (!content) throw usageError("Missing <text>.", this.usage);
|
|
1642
1751
|
const eb = await getClient(ctx);
|
|
@@ -1653,11 +1762,11 @@ var agents = {
|
|
|
1653
1762
|
destroy: {
|
|
1654
1763
|
aliases: ["rm", "delete"],
|
|
1655
1764
|
summary: "Destroy an agent and its sandbox",
|
|
1656
|
-
usage: "easybits agents destroy <agent
|
|
1765
|
+
usage: "easybits agents destroy <agent> [--yes]",
|
|
1657
1766
|
options: { ...YES_OPTION },
|
|
1658
|
-
examples: ["easybits agents destroy
|
|
1767
|
+
examples: ["easybits agents destroy helper # asks you to type the id", "easybits agents destroy helper --yes"],
|
|
1659
1768
|
async run(ctx) {
|
|
1660
|
-
const id = need(ctx, 0, "agent
|
|
1769
|
+
const id = need(ctx, 0, "agent", this.usage);
|
|
1661
1770
|
requireYesIfHeadless(ctx);
|
|
1662
1771
|
await confirm(ctx, `Destroy agent ${id} and its sandbox (including /data)?`, { typeName: id });
|
|
1663
1772
|
const eb = await getClient(ctx);
|
|
@@ -1767,10 +1876,17 @@ ${hits.map((h) => ` ${prefix}${h}`).join("\n")}`;
|
|
|
1767
1876
|
var GLOBAL_FLAGS = `Global flags:
|
|
1768
1877
|
--json Machine output: JSON on stdout, errors included
|
|
1769
1878
|
--token <key> API key for this call (prefer EASYBITS_API_KEY: argv leaks to ps)
|
|
1770
|
-
-y, --yes On rm/destroy: skip the confirmation (required without a TTY)
|
|
1771
1879
|
-h, --help Help (also: easybits <command> --help)
|
|
1772
1880
|
-v, --version Print the CLI version
|
|
1773
1881
|
|
|
1882
|
+
Names or ids: agents, sandboxes/machines and databases take either
|
|
1883
|
+
(exact, case-insensitive; two with the same name \u2192 exit 2 with the ids).
|
|
1884
|
+
|
|
1885
|
+
Deletes (sandboxes destroy, agents destroy, db rm, domains rm, files delete) ask first;
|
|
1886
|
+
without a terminal or with --json pass -y/--yes, or they exit 2.
|
|
1887
|
+
|
|
1888
|
+
Env: EASYBITS_API_KEY (API key), EASYBITS_URL (API server, default https://www.easybits.cloud)
|
|
1889
|
+
|
|
1774
1890
|
Exit codes: 0 ok, 1 API error, 2 usage error, 3 not logged in or bad key`;
|
|
1775
1891
|
var COL = 17;
|
|
1776
1892
|
function globalHelp(version) {
|
|
@@ -1826,7 +1942,7 @@ var BANNER = " \x1B[38;2;244;245;251m\u2860\u2812\u2812\u28E4\x1B[38;
|
|
|
1826
1942
|
|
|
1827
1943
|
// src/index.ts
|
|
1828
1944
|
import { EasybitsError as EasybitsError2 } from "@easybits.cloud/sdk";
|
|
1829
|
-
var VERSION = true ? "0.
|
|
1945
|
+
var VERSION = true ? "0.7.0" : "dev";
|
|
1830
1946
|
var GLOBAL_OPTIONS = {
|
|
1831
1947
|
json: { type: "boolean" },
|
|
1832
1948
|
token: { type: "string" },
|
|
@@ -1918,17 +2034,20 @@ async function main(argv2) {
|
|
|
1918
2034
|
const cmd = findCommand(cmdName);
|
|
1919
2035
|
if (!cmd) throw unknownCommand(cmdName);
|
|
1920
2036
|
let leaf = cmd.leaf;
|
|
2037
|
+
let subKey;
|
|
1921
2038
|
const skip = [pos[0]];
|
|
1922
2039
|
if (cmd.subs) {
|
|
1923
2040
|
const subName = pos[1] != null ? argv2[pos[1]] : void 0;
|
|
1924
2041
|
const entry = subName ? findSub(cmd, subName) : void 0;
|
|
1925
2042
|
if (entry) {
|
|
1926
2043
|
leaf = entry[1];
|
|
2044
|
+
subKey = entry[0];
|
|
1927
2045
|
skip.push(pos[1]);
|
|
1928
2046
|
} else if (subName) {
|
|
1929
2047
|
throw unknownSub(cmd, subName);
|
|
1930
2048
|
} else if (cmd.defaultSub && !argv2.includes("--help") && !argv2.includes("-h")) {
|
|
1931
2049
|
leaf = cmd.subs[cmd.defaultSub];
|
|
2050
|
+
subKey = cmd.defaultSub;
|
|
1932
2051
|
} else if (!argv2.includes("--help") && !argv2.includes("-h")) {
|
|
1933
2052
|
throw usageError(`Missing subcommand for "${cmd.name}".`, `easybits ${cmd.name} <${Object.keys(cmd.subs).join("|")}>`);
|
|
1934
2053
|
}
|
|
@@ -1948,11 +2067,17 @@ async function main(argv2) {
|
|
|
1948
2067
|
args: positionals,
|
|
1949
2068
|
opts: values
|
|
1950
2069
|
};
|
|
2070
|
+
const refKey = `${cmd.name} ${subKey ?? ""}`.trim();
|
|
2071
|
+
const target = leaf;
|
|
2072
|
+
const run = async () => {
|
|
2073
|
+
await resolveRefs(ctx, refKey);
|
|
2074
|
+
await target.run.call(target, ctx);
|
|
2075
|
+
};
|
|
1951
2076
|
try {
|
|
1952
|
-
await
|
|
2077
|
+
await run();
|
|
1953
2078
|
} catch (e) {
|
|
1954
2079
|
if (e instanceof EasybitsError2 && e.status === 401 && usedSession() && await forceRefresh()) {
|
|
1955
|
-
await
|
|
2080
|
+
await run();
|
|
1956
2081
|
return;
|
|
1957
2082
|
}
|
|
1958
2083
|
throw e;
|
package/package.json
CHANGED