@mybase-dev/cli 1.0.7 → 1.0.8

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.
Files changed (2) hide show
  1. package/dist/mybase.js +166 -17
  2. package/package.json +1 -1
package/dist/mybase.js CHANGED
@@ -116,6 +116,7 @@ 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)" },
119
120
  { id: "audit", name: "Audit log", description: "Who did what: sign-ins, key and project changes, secret reads (read only)" }
120
121
  ];
121
122
  function parseDotenv(text) {
@@ -139,7 +140,7 @@ function parseDotenv(text) {
139
140
  // package.json
140
141
  var package_default = {
141
142
  name: "@mybase-dev/cli",
142
- version: "1.0.7",
143
+ version: "1.0.8",
143
144
  description: "The mybase CLI: projects, keys, cron, queues, cache, rate limits, analytics, docs, and agent setup for a self-hosted mybase.",
144
145
  type: "module",
145
146
  repository: {
@@ -652,6 +653,9 @@ function contextMarkdown(c) {
652
653
  }
653
654
  if (c.captcha)
654
655
  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()\`.`, "");
656
+ if (c.realtime) {
657
+ 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\`).`, "");
658
+ }
655
659
  if (c.analytics)
656
660
  out.push("## Analytics", "", `Public write key \`${c.analytics.writeKey}\`, script ${c.analytics.scriptUrl}`, "");
657
661
  out.push("## Env vars", "", ...c.env.map((e) => `- \`${e.name}\`: ${e.description}`), "");
@@ -2077,6 +2081,145 @@ async function sync4(file, dryRun) {
2077
2081
  printSync(config.project, dryRun, await api(`${base6(config.project)}/rules`, { method: "PUT", body: JSON.stringify(body) }));
2078
2082
  }
2079
2083
 
2084
+ // src/realtime.ts
2085
+ var realtimeUsage = ` realtime enable <project> Turn on realtime and print the socket URL and secret
2086
+ realtime info <project> Settings, connections, and messages in the last 24 hours
2087
+ realtime key <project> [--rotate] Show (or replace) the secret that signs browser tokens and webhooks
2088
+ (rotating closes every open connection)
2089
+ realtime settings <project> [--origins a,b|none] [--public on|off] [--webhook <url>|none]
2090
+ [--events message,presence,channel] [--max-connections n]
2091
+ [--max-channels n] [--max-message-bytes n] [--max-rate n]
2092
+ realtime channels <project> [--prefix p] Channels with subscribers right now
2093
+ realtime publish <project> <channel> <event> [json] Send a message to everyone subscribed
2094
+ realtime disable <project> [--yes] Turn it off (closes every connection)`;
2095
+ var base7 = (project) => `/projects/${encodeURIComponent(project)}/realtime`;
2096
+ function printSettings3(s) {
2097
+ console.log(` socket ${s.socketUrl} (connectRealtime in @mybase-dev/sdk/realtime)`);
2098
+ console.log(` publish POST ${s.publishUrl} (realtime:write key)`);
2099
+ console.log(` secret ${s.secret} (server-side only: MYBASE_REALTIME_SECRET)`);
2100
+ console.log(` public ${s.publicChannels ? "on: public:\u2026 channels need no token" : "off: every connection needs a token"}`);
2101
+ console.log(` origins ${s.allowedOrigins.length ? s.allowedOrigins.join(", ") : "any"}`);
2102
+ console.log(` webhook ${s.webhookUrl ? `${s.webhookUrl} (${s.webhookEvents.join(", ") || "no events"})` : "none"}`);
2103
+ console.log(` limits ${s.maxConnections} connections, ${s.maxChannels} channels each, ${s.maxMessageBytes} B frames, ${s.maxRate}/s`);
2104
+ }
2105
+ var list2 = (value) => value === "none" ? [] : value.split(",").map((v) => v.trim()).filter(Boolean);
2106
+ function onOff(value, name) {
2107
+ if (value === undefined)
2108
+ return;
2109
+ if (value === "on" || value === "off")
2110
+ return value === "on";
2111
+ throw usageError(`--${name} takes on or off`);
2112
+ }
2113
+ async function realtime(sub, args) {
2114
+ const { positional, flags } = parseArgs(args);
2115
+ const [project, channel, event, data] = positional;
2116
+ switch (sub) {
2117
+ case "enable": {
2118
+ const p = need(project, "project name");
2119
+ const { linked } = await enableService(p, "realtime", flags);
2120
+ const s = await api(base7(p));
2121
+ print({ enabled: "realtime", project: p, linked, ...s }, () => {
2122
+ console.log(`Realtime enabled for "${p}"
2123
+ `);
2124
+ printSettings3(s);
2125
+ printLinked(p, linked, flags);
2126
+ });
2127
+ return true;
2128
+ }
2129
+ case "info": {
2130
+ const p = need(project, "project name");
2131
+ const [s, stats] = await Promise.all([api(base7(p)), api(`${base7(p)}/stats`)]);
2132
+ const { secret: _secret, ...settings } = s;
2133
+ print({ ...settings, stats }, () => {
2134
+ printSettings3({ ...s, secret: "(mybase realtime key)" });
2135
+ const out = stats.hours.reduce((n, h) => n + h.messagesOut, 0);
2136
+ console.log(`
2137
+ now ${stats.connections} connections (peak ${stats.peakConnections}), ${stats.channels} channels`);
2138
+ console.log(` 24 hours ${out} messages delivered`);
2139
+ if (stats.webhook.lastError)
2140
+ console.log(` webhook failing: ${stats.webhook.lastError} (${stats.webhook.queued} queued, ${stats.webhook.dropped} dropped)`);
2141
+ });
2142
+ return true;
2143
+ }
2144
+ case "key": {
2145
+ const p = need(project, "project name");
2146
+ if (flags.has("rotate"))
2147
+ confirm(args.includes("--yes"), `Every open realtime connection on "${p}" closes, and tokens signed with the old secret stop working.`, p, "the project name");
2148
+ const s = flags.has("rotate") ? await api(`${base7(p)}/secret/rotate`, { method: "POST" }) : await api(base7(p));
2149
+ print({ secret: s.secret }, () => {
2150
+ if (flags.has("rotate"))
2151
+ console.log(`Rotated. Update MYBASE_REALTIME_SECRET where your server signs tokens.
2152
+ `);
2153
+ console.log(s.secret);
2154
+ });
2155
+ return true;
2156
+ }
2157
+ case "settings": {
2158
+ const p = need(project, "project name");
2159
+ const body = {};
2160
+ const origins = flagString(flags, "origins");
2161
+ if (origins !== undefined)
2162
+ body.allowedOrigins = list2(origins);
2163
+ const pub = onOff(flagString(flags, "public"), "public");
2164
+ if (pub !== undefined)
2165
+ body.publicChannels = pub;
2166
+ const webhook = flagString(flags, "webhook");
2167
+ if (webhook !== undefined)
2168
+ body.webhookUrl = webhook === "none" ? null : webhook;
2169
+ const events = flagString(flags, "events");
2170
+ if (events !== undefined)
2171
+ body.webhookEvents = list2(events);
2172
+ const ints = { maxConnections: "max-connections", maxChannels: "max-channels", maxMessageBytes: "max-message-bytes", maxRate: "max-rate" };
2173
+ for (const [key, flag] of Object.entries(ints)) {
2174
+ const n = flagInt(flags, flag);
2175
+ if (n !== undefined)
2176
+ body[key] = n;
2177
+ }
2178
+ const s = Object.keys(body).length ? await api(base7(p), { method: "PATCH", body: JSON.stringify(body) }) : await api(base7(p));
2179
+ const { secret: _secret, ...shown } = s;
2180
+ print(shown, () => printSettings3({ ...s, secret: "(mybase realtime key)" }));
2181
+ return true;
2182
+ }
2183
+ case "channels": {
2184
+ const p = need(project, "project name");
2185
+ const prefix = flagString(flags, "prefix");
2186
+ const { channels } = await api(`${base7(p)}/channels${prefix ? `?prefix=${encodeURIComponent(prefix)}` : ""}`);
2187
+ print(channels, () => {
2188
+ if (!channels.length)
2189
+ return console.log("No channels have subscribers right now.");
2190
+ for (const c of channels)
2191
+ console.log(`${c.channel} ${c.subscribers} subscribers${c.members === undefined ? "" : `, ${c.members} members`}`);
2192
+ });
2193
+ return true;
2194
+ }
2195
+ case "publish": {
2196
+ const p = need(project, "project name");
2197
+ let payload = null;
2198
+ if (data !== undefined) {
2199
+ try {
2200
+ payload = JSON.parse(data);
2201
+ } catch {
2202
+ payload = data;
2203
+ }
2204
+ }
2205
+ const { ids } = await api(`${base7(p)}/publish`, {
2206
+ method: "POST",
2207
+ body: JSON.stringify({ channel: need(channel, "channel"), event: need(event, "event"), data: payload })
2208
+ });
2209
+ print({ id: ids[0] }, () => console.log(`Published ${event} to ${channel}`));
2210
+ return true;
2211
+ }
2212
+ case "disable": {
2213
+ const p = need(project, "project name");
2214
+ confirm(args.includes("--yes"), `Every open realtime connection on "${p}" closes, and pages listening for updates stop getting them.`, p, "the project name");
2215
+ await api(`/projects/${encodeURIComponent(p)}/services/realtime`, { method: "DELETE" });
2216
+ print({ disabled: "realtime", project: p }, () => console.log(`Realtime disabled for "${p}"`));
2217
+ return true;
2218
+ }
2219
+ }
2220
+ return false;
2221
+ }
2222
+
2080
2223
  // src/run.ts
2081
2224
  import { constants } from "os";
2082
2225
 
@@ -2113,8 +2256,8 @@ async function target(flags, needEnv = true) {
2113
2256
  }
2114
2257
  return { project, env: env ?? "" };
2115
2258
  }
2116
- var base7 = (project) => `/projects/${encodeURIComponent(project)}/secrets`;
2117
- var envPath = (project, env, rest = "") => `${base7(project)}/envs/${encodeURIComponent(env)}${rest}`;
2259
+ var base8 = (project) => `/projects/${encodeURIComponent(project)}/secrets`;
2260
+ var envPath = (project, env, rest = "") => `${base8(project)}/envs/${encodeURIComponent(env)}${rest}`;
2118
2261
  var fetchSecrets = (project, env) => api(`/secrets/${encodeURIComponent(project)}/${encodeURIComponent(env)}`);
2119
2262
  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
2263
  async function readStdin() {
@@ -2133,7 +2276,7 @@ async function secrets(sub, args) {
2133
2276
  case "enable": {
2134
2277
  const p = need(positional[0] ?? flagString(flags, "project"), "project name");
2135
2278
  await api(`/projects/${encodeURIComponent(p)}/services/secrets`, { method: "PUT" });
2136
- const overview = await api(base7(p));
2279
+ const overview = await api(base8(p));
2137
2280
  print({ enabled: "secrets", project: p, ...overview }, () => {
2138
2281
  console.log(`Secrets enabled for "${p}": ${overview.envs.map((e) => e.name).join(", ")}
2139
2282
  `);
@@ -2153,7 +2296,7 @@ async function secrets(sub, args) {
2153
2296
  }
2154
2297
  case "setup": {
2155
2298
  const { project, env } = await target(flags);
2156
- const overview = await api(base7(project));
2299
+ const overview = await api(base8(project));
2157
2300
  if (!overview.envs.some((e) => e.name === env)) {
2158
2301
  throw usageError(`"${project}" has no env "${env}" (envs: ${overview.envs.map((e) => e.name).join(", ")})`);
2159
2302
  }
@@ -2163,7 +2306,7 @@ async function secrets(sub, args) {
2163
2306
  }
2164
2307
  case "envs": {
2165
2308
  const { project } = await target(flags, false);
2166
- const overview = await api(base7(project));
2309
+ const overview = await api(base8(project));
2167
2310
  print(overview.envs, () => {
2168
2311
  for (const e of overview.envs)
2169
2312
  console.log(describeEnv(e));
@@ -2174,7 +2317,7 @@ async function secrets(sub, args) {
2174
2317
  const [action, name, next] = positional;
2175
2318
  const { project } = await target(flags, false);
2176
2319
  if (action === "create") {
2177
- const env = await api(`${base7(project)}/envs`, {
2320
+ const env = await api(`${base8(project)}/envs`, {
2178
2321
  method: "POST",
2179
2322
  body: JSON.stringify({ name: need(name, "env name"), parent: flagString(flags, "from") ?? null })
2180
2323
  });
@@ -2277,7 +2420,7 @@ async function secrets(sub, args) {
2277
2420
  const { project, env } = await target(flags);
2278
2421
  const envs = env.split(",").map((e) => e.trim()).filter(Boolean);
2279
2422
  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(`${base7(project)}/link`, { method: "POST", body: JSON.stringify({ envs }) });
2423
+ const result = await api(`${base8(project)}/link`, { method: "POST", body: JSON.stringify({ envs }) });
2281
2424
  print(result, () => {
2282
2425
  console.log(`Rotated and stored in ${result.envs.join(", ")}: ${result.keys.join(", ")}`);
2283
2426
  console.log("Restart your apps with `mybase run` to pick them up.");
@@ -2287,7 +2430,7 @@ async function secrets(sub, args) {
2287
2430
  case "unlink": {
2288
2431
  const { project, env } = await target(flags);
2289
2432
  confirm(yes, `This removes the project credentials from "${env}". They stay valid; nothing is rotated.`, env, "the env name");
2290
- await api(`${base7(project)}/link/${encodeURIComponent(env)}`, { method: "DELETE" });
2433
+ await api(`${base8(project)}/link/${encodeURIComponent(env)}`, { method: "DELETE" });
2291
2434
  print({ unlinked: env, project }, () => console.log(`Removed the credentials from "${env}"`));
2292
2435
  return true;
2293
2436
  }
@@ -3755,6 +3898,7 @@ var SERVICE_COLORS = {
3755
3898
  alerts: color.red,
3756
3899
  users: color.green,
3757
3900
  captcha: color.blue,
3901
+ realtime: color.magenta,
3758
3902
  "db-public": color.cyan,
3759
3903
  dev: color.gray
3760
3904
  };
@@ -5682,7 +5826,7 @@ var usersUsage = ` users enable <project> Turn on users: an auth schema
5682
5826
  users revoke <project> <user-id> [<session-id>] Sign a user out (one session, or everywhere)
5683
5827
  users delete <project> <user-id> [--yes] Delete a user and their sessions
5684
5828
  users disable <project> [--yes] Turn it off, deleting every user account`;
5685
- var base8 = (project) => `/projects/${encodeURIComponent(project)}/users`;
5829
+ var base9 = (project) => `/projects/${encodeURIComponent(project)}/users`;
5686
5830
  function authSnippet(project, apiUrl) {
5687
5831
  return `// auth.ts: npm i better-auth pg @mybase-dev/sdk
5688
5832
  import { betterAuth } from "better-auth";
@@ -5713,7 +5857,7 @@ async function users(sub, args) {
5713
5857
  case "enable": {
5714
5858
  const p = need(project, "project name");
5715
5859
  const { linked } = await enableService(p, "users", flags);
5716
- const o = await api(base8(p));
5860
+ const o = await api(base9(p));
5717
5861
  print({ enabled: "users", project: p, linked, ...o }, () => {
5718
5862
  console.log(`Users enabled for "${p}". Better Auth's tables go in the "${o.schema}" schema.
5719
5863
  `);
@@ -5727,7 +5871,7 @@ async function users(sub, args) {
5727
5871
  }
5728
5872
  case "init": {
5729
5873
  const p = need(project, "project name");
5730
- const o = await api(base8(p));
5874
+ const o = await api(base9(p));
5731
5875
  const env = flagString(flags, "env");
5732
5876
  let stored = null;
5733
5877
  if (env) {
@@ -5750,7 +5894,7 @@ Set BETTER_AUTH_SECRET to 32+ random bytes (openssl rand -base64 32), or rerun w
5750
5894
  case "list": {
5751
5895
  const p = need(project, "project name");
5752
5896
  const search = flagString(flags, "search");
5753
- const list = await api(`${base8(p)}/accounts?limit=100${search ? `&search=${encodeURIComponent(search)}` : ""}`);
5897
+ const list = await api(`${base9(p)}/accounts?limit=100${search ? `&search=${encodeURIComponent(search)}` : ""}`);
5754
5898
  print(list, () => {
5755
5899
  if (list.users.length === 0)
5756
5900
  console.log(search ? "No users match." : "No users yet.");
@@ -5763,7 +5907,7 @@ Set BETTER_AUTH_SECRET to 32+ random bytes (openssl rand -base64 32), or rerun w
5763
5907
  }
5764
5908
  case "show": {
5765
5909
  const p = need(project, "project name");
5766
- const u = await api(`${base8(p)}/accounts/${encodeURIComponent(need(userId, "user id"))}`);
5910
+ const u = await api(`${base9(p)}/accounts/${encodeURIComponent(need(userId, "user id"))}`);
5767
5911
  print(u, () => {
5768
5912
  console.log(`${u.email ?? u.id}${u.name ? ` (${u.name})` : ""}${u.banned ? " BANNED" : ""}`);
5769
5913
  console.log(` id ${u.id}`);
@@ -5781,10 +5925,10 @@ Active sessions (${u.sessions.length}):`);
5781
5925
  const p = need(project, "project name");
5782
5926
  const id = encodeURIComponent(need(userId, "user id"));
5783
5927
  if (sessionId) {
5784
- await api(`${base8(p)}/accounts/${id}/sessions/${encodeURIComponent(sessionId)}`, { method: "DELETE" });
5928
+ await api(`${base9(p)}/accounts/${id}/sessions/${encodeURIComponent(sessionId)}`, { method: "DELETE" });
5785
5929
  print({ revoked: 1 }, () => console.log("Signed out that session."));
5786
5930
  } else {
5787
- const r = await api(`${base8(p)}/accounts/${id}/sessions`, { method: "DELETE" });
5931
+ const r = await api(`${base9(p)}/accounts/${id}/sessions`, { method: "DELETE" });
5788
5932
  print(r, () => console.log(`Signed out ${r.revoked} session(s).`));
5789
5933
  }
5790
5934
  return true;
@@ -5793,7 +5937,7 @@ Active sessions (${u.sessions.length}):`);
5793
5937
  const p = need(project, "project name");
5794
5938
  const id = need(userId, "user id");
5795
5939
  confirm(args.includes("--yes"), `This deletes user ${id} of "${p}" and signs them out.`, id, "the user id");
5796
- await api(`${base8(p)}/accounts/${encodeURIComponent(id)}`, { method: "DELETE" });
5940
+ await api(`${base9(p)}/accounts/${encodeURIComponent(id)}`, { method: "DELETE" });
5797
5941
  print({ deleted: id }, () => console.log(`Deleted user ${id}`));
5798
5942
  return true;
5799
5943
  }
@@ -5835,6 +5979,7 @@ var USAGE = {
5835
5979
  vector: vectorUsage,
5836
5980
  users: usersUsage,
5837
5981
  captcha: captchaUsage,
5982
+ realtime: realtimeUsage,
5838
5983
  run: runUsage
5839
5984
  };
5840
5985
  var GLOBAL_FLAGS = ` --json Print results as JSON (errors too, on stderr)
@@ -5967,6 +6112,10 @@ async function main(args) {
5967
6112
  if (await captcha(sub, rest))
5968
6113
  return;
5969
6114
  }
6115
+ if (command === "realtime" && sub) {
6116
+ if (await realtime(sub, rest))
6117
+ return;
6118
+ }
5970
6119
  if (command && command !== "help")
5971
6120
  throw usageError(`unknown command: mybase ${args.filter((a) => !a.startsWith("-")).slice(0, 2).join(" ")}
5972
6121
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mybase-dev/cli",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "The mybase CLI: projects, keys, cron, queues, cache, rate limits, analytics, docs, and agent setup for a self-hosted mybase.",
5
5
  "type": "module",
6
6
  "repository": {