@mybase-dev/cli 1.0.7 → 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 +288 -18
- package/package.json +1 -1
package/dist/mybase.js
CHANGED
|
@@ -116,6 +116,8 @@ var KEY_RESOURCES = [
|
|
|
116
116
|
{ id: "vector", name: "Vector search", description: "Indexes, documents, and queries (read), and changing them (write)" },
|
|
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
|
+
{ 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" },
|
|
119
121
|
{ id: "audit", name: "Audit log", description: "Who did what: sign-ins, key and project changes, secret reads (read only)" }
|
|
120
122
|
];
|
|
121
123
|
function parseDotenv(text) {
|
|
@@ -139,7 +141,7 @@ function parseDotenv(text) {
|
|
|
139
141
|
// package.json
|
|
140
142
|
var package_default = {
|
|
141
143
|
name: "@mybase-dev/cli",
|
|
142
|
-
version: "1.0.
|
|
144
|
+
version: "1.0.9",
|
|
143
145
|
description: "The mybase CLI: projects, keys, cron, queues, cache, rate limits, analytics, docs, and agent setup for a self-hosted mybase.",
|
|
144
146
|
type: "module",
|
|
145
147
|
repository: {
|
|
@@ -261,15 +263,18 @@ function confirm(yes, warning, expected, what = expected) {
|
|
|
261
263
|
var config = await loadConfig();
|
|
262
264
|
var API_URL = (process.env.MYBASE_API_URL || config.url || DEFAULT_API_URL).replace(/\/$/, "");
|
|
263
265
|
var TOKEN = process.env.MYBASE_TOKEN || config.token;
|
|
266
|
+
var apiToken = () => TOKEN;
|
|
264
267
|
var CLI_VERSION = package_default.version;
|
|
265
268
|
async function api(path, init) {
|
|
266
269
|
const token = init?.token === undefined ? TOKEN : init.token;
|
|
267
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));
|
|
268
273
|
let res;
|
|
269
274
|
try {
|
|
270
275
|
res = await fetch(`${base}${path}`, {
|
|
271
276
|
...init,
|
|
272
|
-
headers
|
|
277
|
+
headers
|
|
273
278
|
});
|
|
274
279
|
} catch {
|
|
275
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.`);
|
|
@@ -277,6 +282,8 @@ async function api(path, init) {
|
|
|
277
282
|
init?.onStatus?.(res.status);
|
|
278
283
|
if (res.status === 204)
|
|
279
284
|
return;
|
|
285
|
+
if (res.ok && init?.raw)
|
|
286
|
+
return res;
|
|
280
287
|
const body = await res.json().catch(() => ({}));
|
|
281
288
|
if (res.status === 401 && !path.startsWith("/auth/login")) {
|
|
282
289
|
const message = token ? "the API rejected your token. Run `mybase login` again." : "not signed in. Run `mybase login` (or set MYBASE_TOKEN).";
|
|
@@ -652,6 +659,9 @@ function contextMarkdown(c) {
|
|
|
652
659
|
}
|
|
653
660
|
if (c.captcha)
|
|
654
661
|
out.push("## Captcha", "", `Script: ${c.captcha.scriptUrl} (data-project="${c.project}"). Check tokens with \`createCaptchaClient\` (\`captcha:write\` key) or Better Auth's captcha plugin via \`mybaseCaptcha()\`.`, "");
|
|
662
|
+
if (c.realtime) {
|
|
663
|
+
out.push("## Realtime", "", `Browsers: \`connectRealtime({ apiUrl, project: "${c.project}", token })\` (socket ${c.realtime.socketUrl}) with tokens your server signs via \`createRealtimeToken(MYBASE_REALTIME_SECRET, \u2026)\`. Publish from the server: POST ${c.realtime.publishUrl} (\`realtime:write\` key, \`createRealtimeClient\`).`, "");
|
|
664
|
+
}
|
|
655
665
|
if (c.analytics)
|
|
656
666
|
out.push("## Analytics", "", `Public write key \`${c.analytics.writeKey}\`, script ${c.analytics.scriptUrl}`, "");
|
|
657
667
|
out.push("## Env vars", "", ...c.env.map((e) => `- \`${e.name}\`: ${e.description}`), "");
|
|
@@ -2077,6 +2087,145 @@ async function sync4(file, dryRun) {
|
|
|
2077
2087
|
printSync(config.project, dryRun, await api(`${base6(config.project)}/rules`, { method: "PUT", body: JSON.stringify(body) }));
|
|
2078
2088
|
}
|
|
2079
2089
|
|
|
2090
|
+
// src/realtime.ts
|
|
2091
|
+
var realtimeUsage = ` realtime enable <project> Turn on realtime and print the socket URL and secret
|
|
2092
|
+
realtime info <project> Settings, connections, and messages in the last 24 hours
|
|
2093
|
+
realtime key <project> [--rotate] Show (or replace) the secret that signs browser tokens and webhooks
|
|
2094
|
+
(rotating closes every open connection)
|
|
2095
|
+
realtime settings <project> [--origins a,b|none] [--public on|off] [--webhook <url>|none]
|
|
2096
|
+
[--events message,presence,channel] [--max-connections n]
|
|
2097
|
+
[--max-channels n] [--max-message-bytes n] [--max-rate n]
|
|
2098
|
+
realtime channels <project> [--prefix p] Channels with subscribers right now
|
|
2099
|
+
realtime publish <project> <channel> <event> [json] Send a message to everyone subscribed
|
|
2100
|
+
realtime disable <project> [--yes] Turn it off (closes every connection)`;
|
|
2101
|
+
var base7 = (project) => `/projects/${encodeURIComponent(project)}/realtime`;
|
|
2102
|
+
function printSettings3(s) {
|
|
2103
|
+
console.log(` socket ${s.socketUrl} (connectRealtime in @mybase-dev/sdk/realtime)`);
|
|
2104
|
+
console.log(` publish POST ${s.publishUrl} (realtime:write key)`);
|
|
2105
|
+
console.log(` secret ${s.secret} (server-side only: MYBASE_REALTIME_SECRET)`);
|
|
2106
|
+
console.log(` public ${s.publicChannels ? "on: public:\u2026 channels need no token" : "off: every connection needs a token"}`);
|
|
2107
|
+
console.log(` origins ${s.allowedOrigins.length ? s.allowedOrigins.join(", ") : "any"}`);
|
|
2108
|
+
console.log(` webhook ${s.webhookUrl ? `${s.webhookUrl} (${s.webhookEvents.join(", ") || "no events"})` : "none"}`);
|
|
2109
|
+
console.log(` limits ${s.maxConnections} connections, ${s.maxChannels} channels each, ${s.maxMessageBytes} B frames, ${s.maxRate}/s`);
|
|
2110
|
+
}
|
|
2111
|
+
var list2 = (value) => value === "none" ? [] : value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
2112
|
+
function onOff(value, name) {
|
|
2113
|
+
if (value === undefined)
|
|
2114
|
+
return;
|
|
2115
|
+
if (value === "on" || value === "off")
|
|
2116
|
+
return value === "on";
|
|
2117
|
+
throw usageError(`--${name} takes on or off`);
|
|
2118
|
+
}
|
|
2119
|
+
async function realtime(sub, args) {
|
|
2120
|
+
const { positional, flags } = parseArgs(args);
|
|
2121
|
+
const [project, channel, event, data] = positional;
|
|
2122
|
+
switch (sub) {
|
|
2123
|
+
case "enable": {
|
|
2124
|
+
const p = need(project, "project name");
|
|
2125
|
+
const { linked } = await enableService(p, "realtime", flags);
|
|
2126
|
+
const s = await api(base7(p));
|
|
2127
|
+
print({ enabled: "realtime", project: p, linked, ...s }, () => {
|
|
2128
|
+
console.log(`Realtime enabled for "${p}"
|
|
2129
|
+
`);
|
|
2130
|
+
printSettings3(s);
|
|
2131
|
+
printLinked(p, linked, flags);
|
|
2132
|
+
});
|
|
2133
|
+
return true;
|
|
2134
|
+
}
|
|
2135
|
+
case "info": {
|
|
2136
|
+
const p = need(project, "project name");
|
|
2137
|
+
const [s, stats] = await Promise.all([api(base7(p)), api(`${base7(p)}/stats`)]);
|
|
2138
|
+
const { secret: _secret, ...settings } = s;
|
|
2139
|
+
print({ ...settings, stats }, () => {
|
|
2140
|
+
printSettings3({ ...s, secret: "(mybase realtime key)" });
|
|
2141
|
+
const out = stats.hours.reduce((n, h) => n + h.messagesOut, 0);
|
|
2142
|
+
console.log(`
|
|
2143
|
+
now ${stats.connections} connections (peak ${stats.peakConnections}), ${stats.channels} channels`);
|
|
2144
|
+
console.log(` 24 hours ${out} messages delivered`);
|
|
2145
|
+
if (stats.webhook.lastError)
|
|
2146
|
+
console.log(` webhook failing: ${stats.webhook.lastError} (${stats.webhook.queued} queued, ${stats.webhook.dropped} dropped)`);
|
|
2147
|
+
});
|
|
2148
|
+
return true;
|
|
2149
|
+
}
|
|
2150
|
+
case "key": {
|
|
2151
|
+
const p = need(project, "project name");
|
|
2152
|
+
if (flags.has("rotate"))
|
|
2153
|
+
confirm(args.includes("--yes"), `Every open realtime connection on "${p}" closes, and tokens signed with the old secret stop working.`, p, "the project name");
|
|
2154
|
+
const s = flags.has("rotate") ? await api(`${base7(p)}/secret/rotate`, { method: "POST" }) : await api(base7(p));
|
|
2155
|
+
print({ secret: s.secret }, () => {
|
|
2156
|
+
if (flags.has("rotate"))
|
|
2157
|
+
console.log(`Rotated. Update MYBASE_REALTIME_SECRET where your server signs tokens.
|
|
2158
|
+
`);
|
|
2159
|
+
console.log(s.secret);
|
|
2160
|
+
});
|
|
2161
|
+
return true;
|
|
2162
|
+
}
|
|
2163
|
+
case "settings": {
|
|
2164
|
+
const p = need(project, "project name");
|
|
2165
|
+
const body = {};
|
|
2166
|
+
const origins = flagString(flags, "origins");
|
|
2167
|
+
if (origins !== undefined)
|
|
2168
|
+
body.allowedOrigins = list2(origins);
|
|
2169
|
+
const pub = onOff(flagString(flags, "public"), "public");
|
|
2170
|
+
if (pub !== undefined)
|
|
2171
|
+
body.publicChannels = pub;
|
|
2172
|
+
const webhook = flagString(flags, "webhook");
|
|
2173
|
+
if (webhook !== undefined)
|
|
2174
|
+
body.webhookUrl = webhook === "none" ? null : webhook;
|
|
2175
|
+
const events = flagString(flags, "events");
|
|
2176
|
+
if (events !== undefined)
|
|
2177
|
+
body.webhookEvents = list2(events);
|
|
2178
|
+
const ints = { maxConnections: "max-connections", maxChannels: "max-channels", maxMessageBytes: "max-message-bytes", maxRate: "max-rate" };
|
|
2179
|
+
for (const [key, flag] of Object.entries(ints)) {
|
|
2180
|
+
const n = flagInt(flags, flag);
|
|
2181
|
+
if (n !== undefined)
|
|
2182
|
+
body[key] = n;
|
|
2183
|
+
}
|
|
2184
|
+
const s = Object.keys(body).length ? await api(base7(p), { method: "PATCH", body: JSON.stringify(body) }) : await api(base7(p));
|
|
2185
|
+
const { secret: _secret, ...shown } = s;
|
|
2186
|
+
print(shown, () => printSettings3({ ...s, secret: "(mybase realtime key)" }));
|
|
2187
|
+
return true;
|
|
2188
|
+
}
|
|
2189
|
+
case "channels": {
|
|
2190
|
+
const p = need(project, "project name");
|
|
2191
|
+
const prefix = flagString(flags, "prefix");
|
|
2192
|
+
const { channels } = await api(`${base7(p)}/channels${prefix ? `?prefix=${encodeURIComponent(prefix)}` : ""}`);
|
|
2193
|
+
print(channels, () => {
|
|
2194
|
+
if (!channels.length)
|
|
2195
|
+
return console.log("No channels have subscribers right now.");
|
|
2196
|
+
for (const c of channels)
|
|
2197
|
+
console.log(`${c.channel} ${c.subscribers} subscribers${c.members === undefined ? "" : `, ${c.members} members`}`);
|
|
2198
|
+
});
|
|
2199
|
+
return true;
|
|
2200
|
+
}
|
|
2201
|
+
case "publish": {
|
|
2202
|
+
const p = need(project, "project name");
|
|
2203
|
+
let payload = null;
|
|
2204
|
+
if (data !== undefined) {
|
|
2205
|
+
try {
|
|
2206
|
+
payload = JSON.parse(data);
|
|
2207
|
+
} catch {
|
|
2208
|
+
payload = data;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
const { ids } = await api(`${base7(p)}/publish`, {
|
|
2212
|
+
method: "POST",
|
|
2213
|
+
body: JSON.stringify({ channel: need(channel, "channel"), event: need(event, "event"), data: payload })
|
|
2214
|
+
});
|
|
2215
|
+
print({ id: ids[0] }, () => console.log(`Published ${event} to ${channel}`));
|
|
2216
|
+
return true;
|
|
2217
|
+
}
|
|
2218
|
+
case "disable": {
|
|
2219
|
+
const p = need(project, "project name");
|
|
2220
|
+
confirm(args.includes("--yes"), `Every open realtime connection on "${p}" closes, and pages listening for updates stop getting them.`, p, "the project name");
|
|
2221
|
+
await api(`/projects/${encodeURIComponent(p)}/services/realtime`, { method: "DELETE" });
|
|
2222
|
+
print({ disabled: "realtime", project: p }, () => console.log(`Realtime disabled for "${p}"`));
|
|
2223
|
+
return true;
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
return false;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2080
2229
|
// src/run.ts
|
|
2081
2230
|
import { constants } from "os";
|
|
2082
2231
|
|
|
@@ -2113,8 +2262,8 @@ async function target(flags, needEnv = true) {
|
|
|
2113
2262
|
}
|
|
2114
2263
|
return { project, env: env ?? "" };
|
|
2115
2264
|
}
|
|
2116
|
-
var
|
|
2117
|
-
var envPath = (project, env, rest = "") => `${
|
|
2265
|
+
var base8 = (project) => `/projects/${encodeURIComponent(project)}/secrets`;
|
|
2266
|
+
var envPath = (project, env, rest = "") => `${base8(project)}/envs/${encodeURIComponent(env)}${rest}`;
|
|
2118
2267
|
var fetchSecrets = (project, env) => api(`/secrets/${encodeURIComponent(project)}/${encodeURIComponent(env)}`);
|
|
2119
2268
|
var describeEnv = (e) => [e.name, e.parent ? `branch of ${e.parent}` : "root", `${e.keys} keys`, `rev ${e.revision}`, e.linked ? "linked" : ""].filter(Boolean).join("\t");
|
|
2120
2269
|
async function readStdin() {
|
|
@@ -2133,7 +2282,7 @@ async function secrets(sub, args) {
|
|
|
2133
2282
|
case "enable": {
|
|
2134
2283
|
const p = need(positional[0] ?? flagString(flags, "project"), "project name");
|
|
2135
2284
|
await api(`/projects/${encodeURIComponent(p)}/services/secrets`, { method: "PUT" });
|
|
2136
|
-
const overview = await api(
|
|
2285
|
+
const overview = await api(base8(p));
|
|
2137
2286
|
print({ enabled: "secrets", project: p, ...overview }, () => {
|
|
2138
2287
|
console.log(`Secrets enabled for "${p}": ${overview.envs.map((e) => e.name).join(", ")}
|
|
2139
2288
|
`);
|
|
@@ -2153,7 +2302,7 @@ async function secrets(sub, args) {
|
|
|
2153
2302
|
}
|
|
2154
2303
|
case "setup": {
|
|
2155
2304
|
const { project, env } = await target(flags);
|
|
2156
|
-
const overview = await api(
|
|
2305
|
+
const overview = await api(base8(project));
|
|
2157
2306
|
if (!overview.envs.some((e) => e.name === env)) {
|
|
2158
2307
|
throw usageError(`"${project}" has no env "${env}" (envs: ${overview.envs.map((e) => e.name).join(", ")})`);
|
|
2159
2308
|
}
|
|
@@ -2163,7 +2312,7 @@ async function secrets(sub, args) {
|
|
|
2163
2312
|
}
|
|
2164
2313
|
case "envs": {
|
|
2165
2314
|
const { project } = await target(flags, false);
|
|
2166
|
-
const overview = await api(
|
|
2315
|
+
const overview = await api(base8(project));
|
|
2167
2316
|
print(overview.envs, () => {
|
|
2168
2317
|
for (const e of overview.envs)
|
|
2169
2318
|
console.log(describeEnv(e));
|
|
@@ -2174,7 +2323,7 @@ async function secrets(sub, args) {
|
|
|
2174
2323
|
const [action, name, next] = positional;
|
|
2175
2324
|
const { project } = await target(flags, false);
|
|
2176
2325
|
if (action === "create") {
|
|
2177
|
-
const env = await api(`${
|
|
2326
|
+
const env = await api(`${base8(project)}/envs`, {
|
|
2178
2327
|
method: "POST",
|
|
2179
2328
|
body: JSON.stringify({ name: need(name, "env name"), parent: flagString(flags, "from") ?? null })
|
|
2180
2329
|
});
|
|
@@ -2277,7 +2426,7 @@ async function secrets(sub, args) {
|
|
|
2277
2426
|
const { project, env } = await target(flags);
|
|
2278
2427
|
const envs = env.split(",").map((e) => e.trim()).filter(Boolean);
|
|
2279
2428
|
confirm(yes, `This rotates the credentials of "${project}" (database password, storage key, cache password if the cache is on, and the API key link made before) and stores the new ones, with the other services' keys, in ${envs.join(", ")} plus any env linked before. Apps still using the old credentials will stop working.`, project, "the project name");
|
|
2280
|
-
const result = await api(`${
|
|
2429
|
+
const result = await api(`${base8(project)}/link`, { method: "POST", body: JSON.stringify({ envs }) });
|
|
2281
2430
|
print(result, () => {
|
|
2282
2431
|
console.log(`Rotated and stored in ${result.envs.join(", ")}: ${result.keys.join(", ")}`);
|
|
2283
2432
|
console.log("Restart your apps with `mybase run` to pick them up.");
|
|
@@ -2287,7 +2436,7 @@ async function secrets(sub, args) {
|
|
|
2287
2436
|
case "unlink": {
|
|
2288
2437
|
const { project, env } = await target(flags);
|
|
2289
2438
|
confirm(yes, `This removes the project credentials from "${env}". They stay valid; nothing is rotated.`, env, "the env name");
|
|
2290
|
-
await api(`${
|
|
2439
|
+
await api(`${base8(project)}/link/${encodeURIComponent(env)}`, { method: "DELETE" });
|
|
2291
2440
|
print({ unlinked: env, project }, () => console.log(`Removed the credentials from "${env}"`));
|
|
2292
2441
|
return true;
|
|
2293
2442
|
}
|
|
@@ -2365,6 +2514,93 @@ async function search(sub, args) {
|
|
|
2365
2514
|
}
|
|
2366
2515
|
}
|
|
2367
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
|
+
|
|
2368
2604
|
// ../../node_modules/.bun/@clack+core@1.5.1/node_modules/@clack/core/dist/index.mjs
|
|
2369
2605
|
import { styleText } from "util";
|
|
2370
2606
|
import { stdout, stdin } from "process";
|
|
@@ -3755,6 +3991,9 @@ var SERVICE_COLORS = {
|
|
|
3755
3991
|
alerts: color.red,
|
|
3756
3992
|
users: color.green,
|
|
3757
3993
|
captcha: color.blue,
|
|
3994
|
+
realtime: color.magenta,
|
|
3995
|
+
sandbox: color.yellow,
|
|
3996
|
+
"sandbox-worker": color.yellow,
|
|
3758
3997
|
"db-public": color.cyan,
|
|
3759
3998
|
dev: color.gray
|
|
3760
3999
|
};
|
|
@@ -3940,6 +4179,8 @@ services:
|
|
|
3940
4179
|
# 1 for one restart turns the admin's 2FA off (lost phone and recovery codes).
|
|
3941
4180
|
MYBASE_ADMIN_RESET_MFA: \${MYBASE_ADMIN_RESET_MFA:-}
|
|
3942
4181
|
LOG_LEVEL: \${LOG_LEVEL:-info}
|
|
4182
|
+
MYBASE_SANDBOX_WORKER_URL: \${MYBASE_SANDBOX_WORKER_URL:-}
|
|
4183
|
+
MYBASE_SANDBOX_WORKER_KEY: \${MYBASE_SANDBOX_WORKER_KEY:-}
|
|
3943
4184
|
ports:
|
|
3944
4185
|
- "\${MYBASE_BIND:-0.0.0.0}:\${MYBASE_PORT:-4000}:4000"
|
|
3945
4186
|
|
|
@@ -4017,6 +4258,27 @@ services:
|
|
|
4017
4258
|
- source: searxng
|
|
4018
4259
|
target: /etc/searxng/settings.yml
|
|
4019
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
|
+
|
|
4020
4282
|
# Cloudflare Tunnel: public HTTPS without opening router ports. Off unless COMPOSE_PROFILES=tunnel.
|
|
4021
4283
|
# Hostnames are set up in the Cloudflare dashboard (see the top of this file).
|
|
4022
4284
|
cloudflared:
|
|
@@ -5682,7 +5944,7 @@ var usersUsage = ` users enable <project> Turn on users: an auth schema
|
|
|
5682
5944
|
users revoke <project> <user-id> [<session-id>] Sign a user out (one session, or everywhere)
|
|
5683
5945
|
users delete <project> <user-id> [--yes] Delete a user and their sessions
|
|
5684
5946
|
users disable <project> [--yes] Turn it off, deleting every user account`;
|
|
5685
|
-
var
|
|
5947
|
+
var base9 = (project) => `/projects/${encodeURIComponent(project)}/users`;
|
|
5686
5948
|
function authSnippet(project, apiUrl) {
|
|
5687
5949
|
return `// auth.ts: npm i better-auth pg @mybase-dev/sdk
|
|
5688
5950
|
import { betterAuth } from "better-auth";
|
|
@@ -5713,7 +5975,7 @@ async function users(sub, args) {
|
|
|
5713
5975
|
case "enable": {
|
|
5714
5976
|
const p = need(project, "project name");
|
|
5715
5977
|
const { linked } = await enableService(p, "users", flags);
|
|
5716
|
-
const o = await api(
|
|
5978
|
+
const o = await api(base9(p));
|
|
5717
5979
|
print({ enabled: "users", project: p, linked, ...o }, () => {
|
|
5718
5980
|
console.log(`Users enabled for "${p}". Better Auth's tables go in the "${o.schema}" schema.
|
|
5719
5981
|
`);
|
|
@@ -5727,7 +5989,7 @@ async function users(sub, args) {
|
|
|
5727
5989
|
}
|
|
5728
5990
|
case "init": {
|
|
5729
5991
|
const p = need(project, "project name");
|
|
5730
|
-
const o = await api(
|
|
5992
|
+
const o = await api(base9(p));
|
|
5731
5993
|
const env = flagString(flags, "env");
|
|
5732
5994
|
let stored = null;
|
|
5733
5995
|
if (env) {
|
|
@@ -5750,7 +6012,7 @@ Set BETTER_AUTH_SECRET to 32+ random bytes (openssl rand -base64 32), or rerun w
|
|
|
5750
6012
|
case "list": {
|
|
5751
6013
|
const p = need(project, "project name");
|
|
5752
6014
|
const search = flagString(flags, "search");
|
|
5753
|
-
const list = await api(`${
|
|
6015
|
+
const list = await api(`${base9(p)}/accounts?limit=100${search ? `&search=${encodeURIComponent(search)}` : ""}`);
|
|
5754
6016
|
print(list, () => {
|
|
5755
6017
|
if (list.users.length === 0)
|
|
5756
6018
|
console.log(search ? "No users match." : "No users yet.");
|
|
@@ -5763,7 +6025,7 @@ Set BETTER_AUTH_SECRET to 32+ random bytes (openssl rand -base64 32), or rerun w
|
|
|
5763
6025
|
}
|
|
5764
6026
|
case "show": {
|
|
5765
6027
|
const p = need(project, "project name");
|
|
5766
|
-
const u = await api(`${
|
|
6028
|
+
const u = await api(`${base9(p)}/accounts/${encodeURIComponent(need(userId, "user id"))}`);
|
|
5767
6029
|
print(u, () => {
|
|
5768
6030
|
console.log(`${u.email ?? u.id}${u.name ? ` (${u.name})` : ""}${u.banned ? " BANNED" : ""}`);
|
|
5769
6031
|
console.log(` id ${u.id}`);
|
|
@@ -5781,10 +6043,10 @@ Active sessions (${u.sessions.length}):`);
|
|
|
5781
6043
|
const p = need(project, "project name");
|
|
5782
6044
|
const id = encodeURIComponent(need(userId, "user id"));
|
|
5783
6045
|
if (sessionId) {
|
|
5784
|
-
await api(`${
|
|
6046
|
+
await api(`${base9(p)}/accounts/${id}/sessions/${encodeURIComponent(sessionId)}`, { method: "DELETE" });
|
|
5785
6047
|
print({ revoked: 1 }, () => console.log("Signed out that session."));
|
|
5786
6048
|
} else {
|
|
5787
|
-
const r = await api(`${
|
|
6049
|
+
const r = await api(`${base9(p)}/accounts/${id}/sessions`, { method: "DELETE" });
|
|
5788
6050
|
print(r, () => console.log(`Signed out ${r.revoked} session(s).`));
|
|
5789
6051
|
}
|
|
5790
6052
|
return true;
|
|
@@ -5793,7 +6055,7 @@ Active sessions (${u.sessions.length}):`);
|
|
|
5793
6055
|
const p = need(project, "project name");
|
|
5794
6056
|
const id = need(userId, "user id");
|
|
5795
6057
|
confirm(args.includes("--yes"), `This deletes user ${id} of "${p}" and signs them out.`, id, "the user id");
|
|
5796
|
-
await api(`${
|
|
6058
|
+
await api(`${base9(p)}/accounts/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
5797
6059
|
print({ deleted: id }, () => console.log(`Deleted user ${id}`));
|
|
5798
6060
|
return true;
|
|
5799
6061
|
}
|
|
@@ -5831,10 +6093,12 @@ var USAGE = {
|
|
|
5831
6093
|
ratelimit: ratelimitUsage,
|
|
5832
6094
|
queue: queueUsage,
|
|
5833
6095
|
search: searchUsage,
|
|
6096
|
+
sandbox: sandboxUsage,
|
|
5834
6097
|
secrets: secretsUsage,
|
|
5835
6098
|
vector: vectorUsage,
|
|
5836
6099
|
users: usersUsage,
|
|
5837
6100
|
captcha: captchaUsage,
|
|
6101
|
+
realtime: realtimeUsage,
|
|
5838
6102
|
run: runUsage
|
|
5839
6103
|
};
|
|
5840
6104
|
var GLOBAL_FLAGS = ` --json Print results as JSON (errors too, on stderr)
|
|
@@ -5877,6 +6141,8 @@ async function main(args) {
|
|
|
5877
6141
|
return audit(args.slice(1));
|
|
5878
6142
|
if (command === "alerts")
|
|
5879
6143
|
return alerts(args.slice(1));
|
|
6144
|
+
if (command === "sandbox" && sub && await sandbox(sub, rest))
|
|
6145
|
+
return;
|
|
5880
6146
|
if (command === "project" && sub === "create") {
|
|
5881
6147
|
const name = need(rest[0], "project name");
|
|
5882
6148
|
const project = await api("/projects", {
|
|
@@ -5967,6 +6233,10 @@ async function main(args) {
|
|
|
5967
6233
|
if (await captcha(sub, rest))
|
|
5968
6234
|
return;
|
|
5969
6235
|
}
|
|
6236
|
+
if (command === "realtime" && sub) {
|
|
6237
|
+
if (await realtime(sub, rest))
|
|
6238
|
+
return;
|
|
6239
|
+
}
|
|
5970
6240
|
if (command && command !== "help")
|
|
5971
6241
|
throw usageError(`unknown command: mybase ${args.filter((a) => !a.startsWith("-")).slice(0, 2).join(" ")}
|
|
5972
6242
|
|
package/package.json
CHANGED