@mybase-dev/cli 1.0.8 → 1.0.9
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/mybase.js +123 -2
- package/package.json +1 -1
package/dist/mybase.js
CHANGED
|
@@ -117,6 +117,7 @@ var KEY_RESOURCES = [
|
|
|
117
117
|
{ id: "users", name: "Users", description: "Your app's users and sessions (read), revoking and deleting them, and sending sign-in events (write)" },
|
|
118
118
|
{ id: "captcha", name: "Captcha", description: "Captcha settings (read), and verifying tokens (write)" },
|
|
119
119
|
{ id: "realtime", name: "Realtime", description: "Channels, members, and settings (read), and publishing messages (write)" },
|
|
120
|
+
{ id: "sandbox", name: "Agent sandboxes", description: "Sandboxes, executions, files, artifacts, and browser connections" },
|
|
120
121
|
{ id: "audit", name: "Audit log", description: "Who did what: sign-ins, key and project changes, secret reads (read only)" }
|
|
121
122
|
];
|
|
122
123
|
function parseDotenv(text) {
|
|
@@ -140,7 +141,7 @@ function parseDotenv(text) {
|
|
|
140
141
|
// package.json
|
|
141
142
|
var package_default = {
|
|
142
143
|
name: "@mybase-dev/cli",
|
|
143
|
-
version: "1.0.
|
|
144
|
+
version: "1.0.9",
|
|
144
145
|
description: "The mybase CLI: projects, keys, cron, queues, cache, rate limits, analytics, docs, and agent setup for a self-hosted mybase.",
|
|
145
146
|
type: "module",
|
|
146
147
|
repository: {
|
|
@@ -262,15 +263,18 @@ function confirm(yes, warning, expected, what = expected) {
|
|
|
262
263
|
var config = await loadConfig();
|
|
263
264
|
var API_URL = (process.env.MYBASE_API_URL || config.url || DEFAULT_API_URL).replace(/\/$/, "");
|
|
264
265
|
var TOKEN = process.env.MYBASE_TOKEN || config.token;
|
|
266
|
+
var apiToken = () => TOKEN;
|
|
265
267
|
var CLI_VERSION = package_default.version;
|
|
266
268
|
async function api(path, init) {
|
|
267
269
|
const token = init?.token === undefined ? TOKEN : init.token;
|
|
268
270
|
const base = init?.url ?? API_URL;
|
|
271
|
+
const headers = new Headers({ "Content-Type": "application/json", "User-Agent": `mybase-cli/${CLI_VERSION}`, ...token ? { Authorization: `Bearer ${token}` } : {} });
|
|
272
|
+
new Headers(init?.headers).forEach((value, key) => headers.set(key, value));
|
|
269
273
|
let res;
|
|
270
274
|
try {
|
|
271
275
|
res = await fetch(`${base}${path}`, {
|
|
272
276
|
...init,
|
|
273
|
-
headers
|
|
277
|
+
headers
|
|
274
278
|
});
|
|
275
279
|
} catch {
|
|
276
280
|
throw new CliError(`can't reach the API at ${base}. Check that it's running, or point MYBASE_API_URL / \`mybase login --url\` at it.`);
|
|
@@ -278,6 +282,8 @@ async function api(path, init) {
|
|
|
278
282
|
init?.onStatus?.(res.status);
|
|
279
283
|
if (res.status === 204)
|
|
280
284
|
return;
|
|
285
|
+
if (res.ok && init?.raw)
|
|
286
|
+
return res;
|
|
281
287
|
const body = await res.json().catch(() => ({}));
|
|
282
288
|
if (res.status === 401 && !path.startsWith("/auth/login")) {
|
|
283
289
|
const message = token ? "the API rejected your token. Run `mybase login` again." : "not signed in. Run `mybase login` (or set MYBASE_TOKEN).";
|
|
@@ -2508,6 +2514,93 @@ async function search(sub, args) {
|
|
|
2508
2514
|
}
|
|
2509
2515
|
}
|
|
2510
2516
|
|
|
2517
|
+
// src/sandbox.ts
|
|
2518
|
+
var sandboxUsage = ` sandbox enable <project> Enable isolated agent compute (requires sandbox worker)
|
|
2519
|
+
sandbox disable <project> [--yes] Stop sandboxes and delete their artifacts
|
|
2520
|
+
sandbox create <project> --key <key> [--ttl 300] [--idle 120]
|
|
2521
|
+
sandbox list|get|usage <project> [<id>]
|
|
2522
|
+
sandbox terminate <project> <id> [--yes]
|
|
2523
|
+
sandbox exec <project> <id> --key <key> [--code file.js | --command '["node","script.js"]'] [--timeout 30000]
|
|
2524
|
+
sandbox runs|artifacts|browser <project> <id>
|
|
2525
|
+
sandbox run <project> <id> <execution>
|
|
2526
|
+
sandbox cancel <project> <id> <execution> [--yes]
|
|
2527
|
+
sandbox upload|download <project> <id> <workspace-path> <local-file>
|
|
2528
|
+
sandbox export <project> <id> <workspace-path> [--type image/png]
|
|
2529
|
+
sandbox artifact <project> <id> <artifact-id> <local-file>
|
|
2530
|
+
Creation/execution return promptly. Poll get/run. Cancelling execution terminates sandbox.
|
|
2531
|
+
Browser credentials expire after 30s; authenticated CDP connection lasts at most 60s.`;
|
|
2532
|
+
async function sandbox(sub, args) {
|
|
2533
|
+
const known = ["enable", "disable", "create", "list", "get", "usage", "terminate", "exec", "runs", "run", "cancel", "upload", "download", "export", "artifacts", "artifact", "browser"];
|
|
2534
|
+
if (!known.includes(sub))
|
|
2535
|
+
return false;
|
|
2536
|
+
const { positional, flags } = parseArgs(args);
|
|
2537
|
+
const [project, id, item, local] = positional;
|
|
2538
|
+
const p = need(project, "project name");
|
|
2539
|
+
const base = `/sandbox/${encodeURIComponent(p)}`;
|
|
2540
|
+
const root = () => `${base}/${encodeURIComponent(need(id, "sandbox id"))}`;
|
|
2541
|
+
const value = (key) => {
|
|
2542
|
+
const found = flags.get(key);
|
|
2543
|
+
return typeof found === "string" ? found : undefined;
|
|
2544
|
+
};
|
|
2545
|
+
const number = (key) => value(key) === undefined ? undefined : Number(value(key));
|
|
2546
|
+
let result;
|
|
2547
|
+
if (sub === "enable") {
|
|
2548
|
+
result = await enableService(p, "sandbox", flags);
|
|
2549
|
+
} else if (sub === "disable") {
|
|
2550
|
+
confirm(flags.has("yes"), `This stops every sandbox and permanently deletes sandbox artifacts for "${p}".`, p, "the project name");
|
|
2551
|
+
await api(`/projects/${encodeURIComponent(p)}/services/sandbox`, { method: "DELETE" });
|
|
2552
|
+
result = { disabled: "sandbox", project: p };
|
|
2553
|
+
} else if (sub === "create")
|
|
2554
|
+
result = await api(base, { method: "POST", body: JSON.stringify({ key: need(value("key"), "--key"), ttlSeconds: number("ttl"), idleSeconds: number("idle"), profile: "browser" }) });
|
|
2555
|
+
else if (sub === "list" || sub === "usage")
|
|
2556
|
+
result = await api(sub === "list" ? base : `${base}/usage`);
|
|
2557
|
+
else if (sub === "get")
|
|
2558
|
+
result = await api(root());
|
|
2559
|
+
else if (sub === "terminate") {
|
|
2560
|
+
confirm(flags.has("yes"), `This stops sandbox "${need(id, "sandbox id")}" and deletes its temporary workspace.`, id, "the sandbox id");
|
|
2561
|
+
result = await api(root(), { method: "DELETE" });
|
|
2562
|
+
} else if (sub === "exec") {
|
|
2563
|
+
const codePath = value("code");
|
|
2564
|
+
const command = value("command");
|
|
2565
|
+
if (!!codePath === !!command)
|
|
2566
|
+
usageError(`provide exactly one of --code file.js or --command '["node","script.js"]'`);
|
|
2567
|
+
let argv;
|
|
2568
|
+
if (command) {
|
|
2569
|
+
try {
|
|
2570
|
+
argv = JSON.parse(command);
|
|
2571
|
+
} catch {
|
|
2572
|
+
usageError("--command must be a JSON argument array");
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
result = await api(`${root()}/executions`, { method: "POST", body: JSON.stringify({ key: need(value("key"), "--key"), code: codePath ? await Bun.file(codePath).text() : undefined, command: argv, timeoutMs: number("timeout") }) });
|
|
2576
|
+
} else if (sub === "runs")
|
|
2577
|
+
result = await api(`${root()}/executions`);
|
|
2578
|
+
else if (sub === "run" || sub === "cancel") {
|
|
2579
|
+
if (sub === "cancel")
|
|
2580
|
+
confirm(flags.has("yes"), `Cancelling stops the entire sandbox "${need(id, "sandbox id")}" and deletes its temporary workspace.`, id, "the sandbox id");
|
|
2581
|
+
result = await api(`${root()}/executions/${encodeURIComponent(need(item, "execution id"))}`, { method: sub === "run" ? "GET" : "DELETE" });
|
|
2582
|
+
} else if (sub === "upload") {
|
|
2583
|
+
const bytes = await Bun.file(need(local, "local file")).arrayBuffer();
|
|
2584
|
+
await api(`${root()}/files?path=${encodeURIComponent(need(item, "workspace path"))}`, { method: "PUT", body: bytes, headers: { "Content-Type": "application/octet-stream" } });
|
|
2585
|
+
result = { uploaded: item, bytes: bytes.byteLength };
|
|
2586
|
+
} else if (sub === "download" || sub === "artifact") {
|
|
2587
|
+
const path = sub === "download" ? `${root()}/files?path=${encodeURIComponent(need(item, "workspace path"))}` : `${root()}/artifacts/${encodeURIComponent(need(item, "artifact id"))}`;
|
|
2588
|
+
const response = await api(path, { raw: true });
|
|
2589
|
+
const size = await Bun.write(need(local, "local file"), response);
|
|
2590
|
+
result = { file: local, bytes: size };
|
|
2591
|
+
} else if (sub === "export")
|
|
2592
|
+
result = await api(`${root()}/artifacts`, { method: "POST", body: JSON.stringify({ path: need(item, "workspace path"), type: value("type") }) });
|
|
2593
|
+
else if (sub === "artifacts")
|
|
2594
|
+
result = await api(`${root()}/artifacts`);
|
|
2595
|
+
else if (sub === "browser") {
|
|
2596
|
+
const connection = await api(`${root()}/browser`, { method: "POST", body: "{}" });
|
|
2597
|
+
connection.headers.Authorization = `Bearer ${apiToken()}`;
|
|
2598
|
+
result = connection;
|
|
2599
|
+
}
|
|
2600
|
+
print(result, () => console.log(JSON.stringify(result, null, 2)));
|
|
2601
|
+
return true;
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2511
2604
|
// ../../node_modules/.bun/@clack+core@1.5.1/node_modules/@clack/core/dist/index.mjs
|
|
2512
2605
|
import { styleText } from "util";
|
|
2513
2606
|
import { stdout, stdin } from "process";
|
|
@@ -3899,6 +3992,8 @@ var SERVICE_COLORS = {
|
|
|
3899
3992
|
users: color.green,
|
|
3900
3993
|
captcha: color.blue,
|
|
3901
3994
|
realtime: color.magenta,
|
|
3995
|
+
sandbox: color.yellow,
|
|
3996
|
+
"sandbox-worker": color.yellow,
|
|
3902
3997
|
"db-public": color.cyan,
|
|
3903
3998
|
dev: color.gray
|
|
3904
3999
|
};
|
|
@@ -4084,6 +4179,8 @@ services:
|
|
|
4084
4179
|
# 1 for one restart turns the admin's 2FA off (lost phone and recovery codes).
|
|
4085
4180
|
MYBASE_ADMIN_RESET_MFA: \${MYBASE_ADMIN_RESET_MFA:-}
|
|
4086
4181
|
LOG_LEVEL: \${LOG_LEVEL:-info}
|
|
4182
|
+
MYBASE_SANDBOX_WORKER_URL: \${MYBASE_SANDBOX_WORKER_URL:-}
|
|
4183
|
+
MYBASE_SANDBOX_WORKER_KEY: \${MYBASE_SANDBOX_WORKER_KEY:-}
|
|
4087
4184
|
ports:
|
|
4088
4185
|
- "\${MYBASE_BIND:-0.0.0.0}:\${MYBASE_PORT:-4000}:4000"
|
|
4089
4186
|
|
|
@@ -4161,6 +4258,27 @@ services:
|
|
|
4161
4258
|
- source: searxng
|
|
4162
4259
|
target: /etc/searxng/settings.yml
|
|
4163
4260
|
|
|
4261
|
+
# Opt-in trusted agent host. Requires runsc/cgroup v2 on the Docker host and a pre-pulled
|
|
4262
|
+
# sandbox image. See docs/sandboxes.md. No guest ever receives this socket or worker key.
|
|
4263
|
+
sandbox-worker:
|
|
4264
|
+
image: \${MYBASE_SANDBOX_WORKER_IMAGE:-ghcr.io/landonharter/mybase-sandbox-worker:latest}
|
|
4265
|
+
profiles: [sandbox]
|
|
4266
|
+
restart: \${MYBASE_RESTART:-unless-stopped}
|
|
4267
|
+
stop_grace_period: 45s
|
|
4268
|
+
environment:
|
|
4269
|
+
MYBASE_SANDBOX_WORKER_KEY: \${MYBASE_SANDBOX_WORKER_KEY:-}
|
|
4270
|
+
MYBASE_SANDBOX_DENY_HOSTS: \${MYBASE_SANDBOX_DENY_HOSTS:-}
|
|
4271
|
+
MYBASE_SANDBOX_IMAGE: \${MYBASE_SANDBOX_IMAGE:-ghcr.io/landonharter/mybase-sandbox:latest}
|
|
4272
|
+
LOG_LEVEL: \${LOG_LEVEL:-info}
|
|
4273
|
+
volumes:
|
|
4274
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
4275
|
+
healthcheck:
|
|
4276
|
+
test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:4010/health',{headers:{Authorization:'Bearer '+process.env.MYBASE_SANDBOX_WORKER_KEY}}).then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
|
|
4277
|
+
interval: 30s
|
|
4278
|
+
timeout: 5s
|
|
4279
|
+
start_period: 30s
|
|
4280
|
+
# Private Compose network only. No published worker or browser-debugging ports.
|
|
4281
|
+
|
|
4164
4282
|
# Cloudflare Tunnel: public HTTPS without opening router ports. Off unless COMPOSE_PROFILES=tunnel.
|
|
4165
4283
|
# Hostnames are set up in the Cloudflare dashboard (see the top of this file).
|
|
4166
4284
|
cloudflared:
|
|
@@ -5975,6 +6093,7 @@ var USAGE = {
|
|
|
5975
6093
|
ratelimit: ratelimitUsage,
|
|
5976
6094
|
queue: queueUsage,
|
|
5977
6095
|
search: searchUsage,
|
|
6096
|
+
sandbox: sandboxUsage,
|
|
5978
6097
|
secrets: secretsUsage,
|
|
5979
6098
|
vector: vectorUsage,
|
|
5980
6099
|
users: usersUsage,
|
|
@@ -6022,6 +6141,8 @@ async function main(args) {
|
|
|
6022
6141
|
return audit(args.slice(1));
|
|
6023
6142
|
if (command === "alerts")
|
|
6024
6143
|
return alerts(args.slice(1));
|
|
6144
|
+
if (command === "sandbox" && sub && await sandbox(sub, rest))
|
|
6145
|
+
return;
|
|
6025
6146
|
if (command === "project" && sub === "create") {
|
|
6026
6147
|
const name = need(rest[0], "project name");
|
|
6027
6148
|
const project = await api("/projects", {
|
package/package.json
CHANGED