@motherbase/cli 0.2.4 → 0.2.6

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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +131 -18
  3. package/bin/motherbase.js +10 -10
  4. package/package.json +35 -26
  5. package/src/api.js +233 -229
  6. package/src/commands/_util.js +106 -50
  7. package/src/commands/backups.js +16 -0
  8. package/src/commands/branches.js +113 -0
  9. package/src/commands/completion.js +90 -0
  10. package/src/commands/config.js +47 -47
  11. package/src/commands/cron.js +65 -65
  12. package/src/commands/db-catalog.js +326 -0
  13. package/src/commands/db.js +103 -92
  14. package/src/commands/domains.js +124 -0
  15. package/src/commands/functions.js +63 -33
  16. package/src/commands/gen.js +63 -17
  17. package/src/commands/import.js +55 -35
  18. package/src/commands/index.js +85 -64
  19. package/src/commands/inspect.js +178 -41
  20. package/src/commands/link.js +35 -35
  21. package/src/commands/local.js +23 -2
  22. package/src/commands/login.js +39 -39
  23. package/src/commands/logout.js +16 -16
  24. package/src/commands/mcp.js +27 -9
  25. package/src/commands/migrate.js +207 -0
  26. package/src/commands/migration.js +108 -33
  27. package/src/commands/network.js +83 -0
  28. package/src/commands/orgs.js +40 -0
  29. package/src/commands/postgres-config.js +113 -0
  30. package/src/commands/projects.js +130 -125
  31. package/src/commands/queues.js +133 -0
  32. package/src/commands/realtime.js +35 -35
  33. package/src/commands/rls.js +176 -176
  34. package/src/commands/secrets.js +66 -45
  35. package/src/commands/signing.js +130 -0
  36. package/src/commands/snippets.js +44 -0
  37. package/src/commands/status.js +42 -42
  38. package/src/commands/storage.js +261 -113
  39. package/src/commands/test.js +9 -0
  40. package/src/commands/unlink.js +15 -15
  41. package/src/commands/users.js +142 -113
  42. package/src/commands/vault.js +82 -0
  43. package/src/commands/webhooks.js +62 -62
  44. package/src/commands/whoami.js +20 -20
  45. package/src/commands/wrappers.js +150 -0
  46. package/src/config.js +98 -98
  47. package/src/context.js +106 -102
  48. package/src/docs.js +68 -0
  49. package/src/edge-templates.js +194 -0
  50. package/src/format.js +92 -0
  51. package/src/inspect/csv.js +22 -0
  52. package/src/inspect/queries.js +562 -0
  53. package/src/main.js +224 -160
  54. package/src/mcp/platform-tools.js +233 -0
  55. package/src/mcp/protocol.js +4 -1
  56. package/src/mcp/storage-tools.js +93 -0
  57. package/src/mcp/tools.js +318 -6
  58. package/src/migration/commands.js +375 -0
  59. package/src/migration/files.js +245 -0
  60. package/src/prompt.js +91 -91
  61. package/src/storage/remote.js +62 -0
  62. package/src/ui.js +111 -111
  63. package/src/commands/migrations.js +0 -85
@@ -4,8 +4,9 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const api = require('../api');
6
6
  const ui = require('../ui');
7
- const { output, requireArg } = require('./_util');
7
+ const { output, alias, requireArg, resolveInside } = require('./_util');
8
8
  const { CliError } = require('../context');
9
+ const { templates, DEFAULT_TEMPLATE, findTemplate, renderTemplate } = require('../edge-templates');
9
10
 
10
11
  // Functions live as repo files, one directory per function, the layout Supabase
11
12
  // projects already use:
@@ -20,20 +21,30 @@ const DEFAULT_DIR = 'functions';
20
21
  const LEGACY_DIR = path.join('motherbase', 'functions');
21
22
  const ENTRY_NAMES = ['index.ts', 'index.js', 'index.mjs'];
22
23
 
23
- const TEMPLATE = `// Edge function: %NAME%
24
- //
25
- // Runs in a V8 isolate with the Fetch API available: Request, Response,
26
- // Headers, URL, TextEncoder/TextDecoder and crypto (including subtle.sign, for
27
- // webhook signature checks). npm imports are resolved at deploy time:
28
- //
29
- // import { z } from 'zod';
30
- //
31
- // \`env\` holds this project's edge secrets.
24
+ // Written next to the functions so an editor knows the runtime globals the
25
+ // Deno edge runtime injects. Without it every `env.X` / `sendMail(...)` is a red squiggle.
26
+ const TYPES_FILE = 'motherbase.d.ts';
27
+ const TYPES = `// Generated by \`motherbase functions new\`. Runtime globals injected by the
28
+ // Motherbase edge runtime. Safe to re-generate: delete it and scaffold again.
32
29
 
33
- export default async (req: Request) => {
34
- const body = req.method === 'GET' ? {} : await req.json().catch(() => ({}));
35
- return Response.json({ hello: '%NAME%', received: body, at: new Date().toISOString() });
36
- };
30
+ /** This project's edge secrets (Studio > Edge functions > Secrets). */
31
+ declare const env: Record<string, string>;
32
+
33
+ /** The invoke payload, for the legacy expression-shaped function form. */
34
+ declare const context: Record<string, unknown>;
35
+
36
+ /**
37
+ * Sends an email through this project's SMTP settings, the same transport the
38
+ * auth emails use: the project API sends it, so there is no mail API to sign up for.
39
+ * Rejects when SMTP is unconfigured, when \`to\` is not an address, or when
40
+ * neither \`text\` nor \`html\` is given.
41
+ */
42
+ declare function sendMail(options: {
43
+ to: string;
44
+ subject: string;
45
+ text?: string;
46
+ html?: string;
47
+ }): Promise<{ ok: true }>;
37
48
  `;
38
49
 
39
50
  function functionsDir(ctx) {
@@ -75,16 +86,19 @@ function resolveLocal(ctx, name) {
75
86
  }
76
87
 
77
88
  /** Upsert by route, so deploying twice updates instead of conflicting. */
89
+ async function upsertFunction(ctx, body) {
90
+ const listing = await api.adminRequest(ctx, 'GET', '/edge-functions');
91
+ const existing = ((listing && listing.functions) || []).find(f => f.route === body.route);
92
+ if (existing) return api.adminRequest(ctx, 'PATCH', `/edge-functions/${existing.id}`, { body });
93
+ return api.adminRequest(ctx, 'POST', '/edge-functions', { body: { ...body, enabled: true } });
94
+ }
95
+
78
96
  async function deployOne(ctx, fn, flags) {
79
97
  const code = fs.readFileSync(fn.entry, 'utf8');
80
98
  const body = { name: fn.name, route: fn.name, code };
81
99
  if (flags && flags.memory) body.memoryLimitMb = Number(flags.memory);
82
100
  if (flags && flags.timeout) body.timeoutMs = Number(flags.timeout);
83
-
84
- const listing = await api.adminRequest(ctx, 'GET', '/edge-functions');
85
- const existing = ((listing && listing.functions) || []).find(f => f.route === fn.name);
86
- if (existing) return api.adminRequest(ctx, 'PATCH', `/edge-functions/${existing.id}`, { body });
87
- return api.adminRequest(ctx, 'POST', '/edge-functions', { body: { ...body, enabled: true } });
101
+ return upsertFunction(ctx, body);
88
102
  }
89
103
 
90
104
  const subcommands = {
@@ -115,9 +129,13 @@ const subcommands = {
115
129
  },
116
130
  new: {
117
131
  summary: 'Scaffold functions/<name>/index.ts',
118
- usage: 'motherbase functions new <name>',
119
- async run(ctx, args) {
132
+ usage: `motherbase functions new <name> [--template ${templates.map(t => t.id).join('|')}]`,
133
+ async run(ctx, args, flags) {
120
134
  const name = requireArg(args[0], 'name');
135
+ const template = findTemplate(flags.template || DEFAULT_TEMPLATE);
136
+ if (!template) {
137
+ throw new CliError(`Unknown template "${flags.template}". Use one of: ${templates.map(t => t.id).join(', ')}.`);
138
+ }
121
139
  if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) {
122
140
  throw new CliError('Function names are lowercase letters, digits and hyphens (the name is also the route).');
123
141
  }
@@ -125,8 +143,13 @@ const subcommands = {
125
143
  if (entryFile(dir)) throw new CliError(`${path.relative(ctx.cwd, dir)} already has an entry file.`);
126
144
  fs.mkdirSync(dir, { recursive: true });
127
145
  const file = path.join(dir, 'index.ts');
128
- fs.writeFileSync(file, TEMPLATE.replace(/%NAME%/g, name));
146
+ fs.writeFileSync(file, renderTemplate(template, name));
129
147
  ui.success(`Created ${path.relative(ctx.cwd, file)}`);
148
+ const types = path.join(functionsDir(ctx), TYPES_FILE);
149
+ if (!fs.existsSync(types)) {
150
+ fs.writeFileSync(types, TYPES);
151
+ ui.success(`Created ${path.relative(ctx.cwd, types)}`);
152
+ }
130
153
  ui.info(`Deploy it with: motherbase functions deploy ${name}`);
131
154
  },
132
155
  },
@@ -141,11 +164,7 @@ const subcommands = {
141
164
  if (!route) throw new CliError('--route is required with --file');
142
165
  const code = fs.readFileSync(path.resolve(ctx.cwd, flags.file), 'utf8');
143
166
  const name = flags.name || route;
144
- const listing = await api.adminRequest(ctx, 'GET', '/edge-functions');
145
- const existing = ((listing && listing.functions) || []).find(f => f.route === route);
146
- const data = existing
147
- ? await api.adminRequest(ctx, 'PATCH', `/edge-functions/${existing.id}`, { body: { name, route, code } })
148
- : await api.adminRequest(ctx, 'POST', '/edge-functions', { body: { name, route, code, enabled: true } });
167
+ const data = await upsertFunction(ctx, { name, route, code });
149
168
  return output(ctx, data, (_d, u) => u.success(`Deployed function ${name} (${route})`));
150
169
  }
151
170
 
@@ -177,7 +196,7 @@ const subcommands = {
177
196
  // The list endpoint may omit code, so read the function itself.
178
197
  const detail = await api.adminRequest(ctx, 'GET', `/edge-functions/${fn.id}`);
179
198
  const code = (detail && detail.function ? detail.function.code : detail && detail.code) || '';
180
- const dir = path.join(functionsDir(ctx), fn.route || fn.name);
199
+ const dir = resolveInside(functionsDir(ctx), fn.route || fn.name);
181
200
  fs.mkdirSync(dir, { recursive: true });
182
201
  const file = path.join(dir, 'index.ts');
183
202
  fs.writeFileSync(file, code);
@@ -190,8 +209,8 @@ const subcommands = {
190
209
  summary: 'Watch local functions and redeploy on every save',
191
210
  usage: 'motherbase functions serve [name]',
192
211
  async run(ctx, args) {
193
- // Deliberately not a second local runtime. The isolate, the secrets and
194
- // the bundler all live in the project API, and a local re-implementation
212
+ // Deliberately not a second local runtime. The runtime, the secrets and
213
+ // the mail bridge all live with the project API, and a local re-implementation
195
214
  // would differ from production in exactly the ways that matter. This
196
215
  // watches and redeploys instead, so the edit-invoke loop is one save long.
197
216
  const targets = resolveLocal(ctx, args[0]);
@@ -240,7 +259,7 @@ const subcommands = {
240
259
  },
241
260
  },
242
261
  invoke: {
243
- summary: 'Invoke an edge function by route',
262
+ summary: 'Invoke a deployed edge function by route, as the operator',
244
263
  usage: 'motherbase functions invoke <route> [--body <json>]',
245
264
  async run(ctx, args, flags) {
246
265
  const route = requireArg(args[0], 'route');
@@ -248,7 +267,14 @@ const subcommands = {
248
267
  if (flags.body) {
249
268
  try { body = JSON.parse(flags.body); } catch { throw new CliError('--body must be valid JSON'); }
250
269
  }
251
- const data = await api.edgeInvokeRequest(ctx, route, body);
270
+ // Through the admin surface, as Studio's "Run" does. The public /edge
271
+ // route authenticates the function's own callers: the console proxy
272
+ // strips the operator's session before forwarding there (operator code
273
+ // must never see it), so the CLI arrived anonymous and got a 401.
274
+ const listing = await api.adminRequest(ctx, 'GET', '/edge-functions');
275
+ const fn = ((listing && listing.functions) || []).find(f => f.route === route || f.name === route);
276
+ if (!fn) throw new CliError(`No deployed function "${route}". See: motherbase functions list`);
277
+ const data = await api.adminRequest(ctx, 'POST', `/edge-functions/${fn.id}/invoke`, { body });
252
278
  output(ctx, data, (d, u) => u.json(d));
253
279
  },
254
280
  },
@@ -267,8 +293,12 @@ const subcommands = {
267
293
  },
268
294
  };
269
295
 
296
+ // Supabase's name for `pull`.
297
+ subcommands.download = alias(subcommands.pull, 'motherbase functions download [name]');
298
+
270
299
  module.exports = {
271
300
  summary: 'Edge functions (repo files, deploy, invoke, logs)',
272
- usage: 'motherbase functions <list|get|new|deploy|pull|serve|delete|invoke|logs> ...',
301
+ usage: 'motherbase functions <list|get|new|deploy|pull|download|serve|delete|invoke|logs> ...',
273
302
  subcommands,
303
+ upsertFunction,
274
304
  };
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- // Code generation from the live schema. Today: TypeScript database types.
3
+ // Code generation from the live schema: TypeScript types and zod schemas.
4
4
  //
5
- // The server owns the emitter (apps/server/services/admin/typegen.service.ts).
5
+ // The server owns both emitters (apps/server/services/admin/typegen/).
6
6
  // This command is a transport: it asks `GET /admin/schema/types` for the
7
7
  // rendered file and writes it. Re-implementing the emitter here would guarantee
8
8
  // drift between the CLI, the Studio download and the MCP tool within a release.
@@ -12,9 +12,11 @@ const path = require('path');
12
12
  const api = require('../api');
13
13
  const config = require('../config');
14
14
  const { output } = require('./_util');
15
+ const { outputFormat } = require('../format');
15
16
  const { CliError } = require('../context');
16
17
 
17
18
  const DEFAULT_OUTPUT = 'database.types.ts';
19
+ const DEFAULT_ZOD_OUTPUT = 'database.schemas.ts';
18
20
  const DEFAULT_LOCAL_URL = 'http://localhost:5001';
19
21
 
20
22
  /**
@@ -51,31 +53,36 @@ function firstDifference(a, b) {
51
53
  return null;
52
54
  }
53
55
 
54
- const typescript = {
55
- summary: 'Generate TypeScript types for the database schema',
56
- usage:
57
- 'motherbase gen types typescript [--schema public] [--local] [--output database.types.ts] [--check]',
58
- async run(ctx, args, flags) {
56
+ /**
57
+ * Both targets are the same transport: ask the server for the rendered file,
58
+ * then write it, diff it or print it. `field` names which rendering to take
59
+ * from the one response, so a new target is a few lines rather than a copy of
60
+ * this whole flow.
61
+ */
62
+ function emitter({ field, defaultOutput, label, command }) {
63
+ return async function run(ctx, args, flags) {
59
64
  const schema = flags.schema || 'public';
60
65
  const data = await api.adminRequest(resolveTarget(ctx, flags), 'GET', '/schema/types', {
61
66
  query: { schema },
62
67
  });
63
68
 
64
- const types = data && typeof data.types === 'string' ? data.types : null;
69
+ const types = data && typeof data[field] === 'string' ? data[field] : null;
65
70
  if (!types) {
66
71
  throw new CliError(
67
- 'The server returned no generated types. Check that the project runs a build with GET /admin/schema/types.',
72
+ `The server returned no generated ${label}. Check that the project serves ` +
73
+ `GET /admin/schema/types with a build new enough for \`${command}\`.`,
68
74
  );
69
75
  }
70
76
 
71
77
  const outputPath = flags.output && flags.output !== true ? String(flags.output) : null;
78
+ const DEFAULT_OUTPUT = defaultOutput;
72
79
 
73
80
  // --check is the CI drift gate: compare the committed file with a fresh
74
81
  // generation and fail loudly, without ever writing.
75
82
  if (flags.check) {
76
83
  const target = path.resolve(ctx.cwd, outputPath || DEFAULT_OUTPUT);
77
84
  if (!fs.existsSync(target)) {
78
- throw new CliError(`${target} does not exist. Run \`motherbase gen types typescript --output ${outputPath || DEFAULT_OUTPUT}\`.`);
85
+ throw new CliError(`${target} does not exist. Run \`${command} --output ${outputPath || DEFAULT_OUTPUT}\`.`);
79
86
  }
80
87
  const onDisk = fs.readFileSync(target, 'utf8');
81
88
  const diff = firstDifference(onDisk, types);
@@ -96,7 +103,7 @@ const typescript = {
96
103
  // No --output: write the file to stdout so `... > database.types.ts` works.
97
104
  // Nothing else may go to stdout on this path or it lands in the file.
98
105
  if (!outputPath) {
99
- if (ctx.flags.json) {
106
+ if (outputFormat(ctx.flags) !== 'pretty') {
100
107
  output(ctx, { schema, types }, () => {});
101
108
  return;
102
109
  }
@@ -110,17 +117,56 @@ const typescript = {
110
117
  output(ctx, { schema, output: target, bytes: Buffer.byteLength(types) }, (d, ui) =>
111
118
  ui.success(`Wrote ${target} (${schema} schema)`),
112
119
  );
113
- },
120
+ };
121
+ }
122
+
123
+ const typescript = {
124
+ summary: 'Generate TypeScript types for the database schema',
125
+ usage:
126
+ 'motherbase gen types typescript [--schema public] [--local] [--output database.types.ts] [--check]',
127
+ // Here `--output` names the file to write, not the `-o` output format.
128
+ outputIsPath: true,
129
+ run: emitter({
130
+ field: 'types',
131
+ defaultOutput: DEFAULT_OUTPUT,
132
+ label: 'types',
133
+ command: 'motherbase gen types typescript',
134
+ }),
135
+ };
136
+
137
+ const zod = {
138
+ summary: 'Generate zod schemas for the database schema',
139
+ usage:
140
+ 'motherbase gen types zod [--schema public] [--local] [--output database.schemas.ts] [--check]',
141
+ outputIsPath: true,
142
+ run: emitter({
143
+ field: 'zod',
144
+ defaultOutput: DEFAULT_ZOD_OUTPUT,
145
+ label: 'schemas',
146
+ command: 'motherbase gen types zod',
147
+ }),
114
148
  };
115
149
 
150
+ const LANGS = { typescript, zod };
151
+
152
+ // `gen types --lang typescript` is the Supabase spelling; `gen types typescript`
153
+ // reaches the same emitter through the subcommand.
116
154
  const types = {
117
155
  summary: 'Generate types from the database schema',
118
- usage: 'motherbase gen types typescript [flags]',
119
- subcommands: { typescript },
156
+ usage: 'motherbase gen types [--lang typescript|zod] [flags] | motherbase gen types <typescript|zod> [flags]',
157
+ subcommands: LANGS,
158
+ outputIsPath: true,
159
+ async run(ctx, args, flags) {
160
+ const lang = flags.lang || 'typescript';
161
+ if (!LANGS[lang]) throw new CliError(`--lang must be one of: ${Object.keys(LANGS).join(', ')}`);
162
+ return LANGS[lang].run(ctx, args, flags);
163
+ },
120
164
  };
121
165
 
166
+ const signing = require('./signing');
167
+
122
168
  module.exports = {
123
- summary: 'Generate code from the live schema',
124
- usage: 'motherbase gen types typescript [--schema public] [--local] [--output <file>] [--check]',
125
- subcommands: { types },
169
+ summary: 'Generate code from the live schema, and signing keys, tokens and secrets',
170
+ usage: 'motherbase gen <types|signing-key|bearer-jwt|keys> ...',
171
+ subcommands: { types, 'signing-key': signing.signingKey, 'bearer-jwt': signing.bearerJwt, keys: signing.keys },
126
172
  };
@@ -1,35 +1,55 @@
1
- 'use strict';
2
-
3
- const api = require('../api');
4
- const prompt = require('../prompt');
5
- const { output } = require('./_util');
6
- const { CliError } = require('../context');
7
-
8
- module.exports = {
9
- summary: 'Import a Supabase project (schema + data + auth) into the linked project',
10
- usage: 'motherbase import --from-supabase <connectionString> [--preview] [--no-data] [--no-auth]',
11
- async run(ctx, args, flags) {
12
- const connectionString = flags['from-supabase'] || flags.fromSupabase;
13
- if (!connectionString) throw new CliError('--from-supabase <connectionString> is required');
14
-
15
- const include = { schema: true, data: !flags['no-data'], auth: !flags['no-auth'] };
16
- const preview = Boolean(flags.preview);
17
-
18
- const body = { connectionString, include };
19
- if (preview) {
20
- body.preview = true;
21
- } else {
22
- if (!flags.yes) {
23
- const ok = await prompt.confirm('This will import into the linked project. Continue?');
24
- if (!ok) throw new CliError('Aborted.');
25
- }
26
- body.confirm = 'IMPORT';
27
- }
28
-
29
- const data = await api.adminRequest(ctx, 'POST', '/import/supabase', { body });
30
- output(ctx, data, (d, ui) => {
31
- if (preview) { ui.info('Import preview (dry run):'); ui.json(d); }
32
- else { ui.success('Import complete'); ui.json(d); }
33
- });
34
- },
35
- };
1
+ 'use strict';
2
+
3
+ const api = require('../api');
4
+ const prompt = require('../prompt');
5
+ const { output } = require('./_util');
6
+ const { CliError } = require('../context');
7
+
8
+ // Sections beyond the database need their own credential, because that is where
9
+ // Supabase keeps them: object bytes behind the Storage API (service_role key),
10
+ // edge functions and project secrets behind the Management API (a personal
11
+ // access token). Both are optional; without one, that section reports what it
12
+ // could not read instead of failing the import.
13
+ module.exports = {
14
+ summary: 'Import a Supabase project (schema, data, auth, storage, edge functions, secrets)',
15
+ usage:
16
+ 'motherbase import --from-supabase <connectionString> [--service-role-key <key>] [--management-token <sbp_…>] ' +
17
+ '[--preview] [--no-data] [--no-auth] [--no-storage] [--no-edge-functions] [--no-secrets]',
18
+ async run(ctx, args, flags) {
19
+ const connectionString = flags['from-supabase'] || flags.fromSupabase;
20
+ if (!connectionString) throw new CliError('--from-supabase <connectionString> is required');
21
+
22
+ const include = {
23
+ schema: true,
24
+ data: !flags['no-data'],
25
+ auth: !flags['no-auth'],
26
+ storage: !flags['no-storage'],
27
+ edgeFunctions: !flags['no-edge-functions'],
28
+ secrets: !flags['no-secrets'],
29
+ };
30
+ const preview = Boolean(flags.preview);
31
+
32
+ const body = { connectionString, include };
33
+ const serviceRoleKey = flags['service-role-key'] || flags.serviceRoleKey;
34
+ const managementToken = flags['management-token'] || flags.managementToken;
35
+ if (serviceRoleKey) body.serviceRoleKey = serviceRoleKey;
36
+ if (managementToken) body.managementToken = managementToken;
37
+ if (flags['project-ref'] || flags.projectRef) body.projectRef = flags['project-ref'] || flags.projectRef;
38
+
39
+ if (preview) {
40
+ body.preview = true;
41
+ } else {
42
+ if (!flags.yes) {
43
+ const ok = await prompt.confirm('This will import into the linked project. Continue?');
44
+ if (!ok) throw new CliError('Aborted.');
45
+ }
46
+ body.confirm = 'IMPORT';
47
+ }
48
+
49
+ const data = await api.adminRequest(ctx, 'POST', '/import/supabase', { body });
50
+ output(ctx, data, (d, ui) => {
51
+ if (preview) { ui.info('Import preview (dry run):'); ui.json(d); }
52
+ else { ui.success('Import complete'); ui.json(d); }
53
+ });
54
+ },
55
+ };
@@ -1,64 +1,85 @@
1
- 'use strict';
2
-
3
- // Command registry: name -> { summary, usage, subcommands?, run? }.
4
- // `main.js` builds all help/dispatch from this single map.
5
-
6
- const local = require('./local');
7
-
8
- const registry = {
9
- // ── Connection ─────────────────────────────────────────────────────────────
10
- login: require('./login'),
11
- logout: require('./logout'),
12
- whoami: require('./whoami'),
13
- link: require('./link'),
14
- unlink: require('./unlink'),
15
- status: require('./status'),
16
-
17
- // ── Projects (Platform Console) ────────────────────────────────────────────
18
- projects: require('./projects'),
19
-
20
- // ── Local self-host (docker compose) ───────────────────────────────────────
21
- init: local.init,
22
- start: local.start,
23
- stop: local.stop,
24
- deploy: local.deploy,
25
- logs: local.logs,
26
-
27
- // ── Database / migrations / import ─────────────────────────────────────────
28
- db: require('./db'),
29
- migrations: require('./migrations'),
30
- migration: require('./migration'),
31
- import: require('./import'),
32
-
33
- // ── Resource domains ───────────────────────────────────────────────────────
34
- rls: require('./rls'),
35
- users: require('./users'),
36
- storage: require('./storage'),
37
- functions: require('./functions'),
38
- secrets: require('./secrets'),
39
- keys: require('./keys'),
40
- realtime: require('./realtime'),
41
- webhooks: require('./webhooks'),
42
- cron: require('./cron'),
43
- config: require('./config'),
44
- inspect: require('./inspect'),
45
-
46
- // ── Codegen ────────────────────────────────────────────────────────────────
47
- gen: require('./gen'),
48
-
49
- // ── AI agents ──────────────────────────────────────────────────────────────
50
- mcp: require('./mcp'),
51
- };
52
-
53
- // Grouping used only to render a tidy top-level help listing.
54
- const groups = [
55
- { title: 'Connection', names: ['login', 'logout', 'whoami', 'link', 'unlink', 'status'] },
56
- { title: 'Projects (Console)', names: ['projects'] },
57
- { title: 'Local self-host', names: ['init', 'start', 'stop', 'deploy', 'logs'] },
58
- { title: 'Database', names: ['db', 'migrations', 'migration', 'import'] },
59
- { title: 'Resources', names: ['rls', 'users', 'storage', 'functions', 'secrets', 'keys', 'realtime', 'webhooks', 'cron', 'config', 'inspect'] },
60
- { title: 'Codegen', names: ['gen'] },
61
- { title: 'AI agents', names: ['mcp'] },
62
- ];
63
-
64
- module.exports = { registry, groups };
1
+ 'use strict';
2
+
3
+ // Command registry: name -> { summary, usage, subcommands?, run? }.
4
+ // `main.js` builds all help/dispatch from this single map.
5
+
6
+ const local = require('./local');
7
+ const { alias } = require('./_util');
8
+ const migration = require('./migration');
9
+
10
+ const registry = {
11
+ // ── Connection ─────────────────────────────────────────────────────────────
12
+ login: require('./login'),
13
+ logout: require('./logout'),
14
+ whoami: require('./whoami'),
15
+ link: require('./link'),
16
+ unlink: require('./unlink'),
17
+ status: require('./status'),
18
+
19
+ // ── Orgs & projects (Platform Console) ─────────────────────────────────────
20
+ orgs: require('./orgs'),
21
+ projects: require('./projects'),
22
+ branches: require('./branches'),
23
+ domains: require('./domains').domains,
24
+ 'vanity-subdomains': require('./domains').vanitySubdomains,
25
+
26
+ // ── Local self-host (docker compose) ───────────────────────────────────────
27
+ init: local.init,
28
+ start: local.start,
29
+ stop: local.stop,
30
+ deploy: local.deploy,
31
+ logs: local.logs,
32
+
33
+ // ── Database / migrations / import ─────────────────────────────────────────
34
+ db: require('./db'),
35
+ backups: require('./backups'),
36
+ migration,
37
+ // The name Motherbase used before it matched Supabase's `migration`.
38
+ migrations: alias(migration, 'motherbase migrations <new|list|up|down|repair|squash|fetch|diff|get|create|apply|rollback> ... (alias of `migration`)'),
39
+ import: require('./import'),
40
+ migrate: require('./migrate'),
41
+ test: require('./test'),
42
+ 'postgres-config': require('./postgres-config'),
43
+
44
+ // ── Resource domains ───────────────────────────────────────────────────────
45
+ rls: require('./rls'),
46
+ users: require('./users'),
47
+ storage: require('./storage'),
48
+ functions: require('./functions'),
49
+ secrets: require('./secrets'),
50
+ keys: require('./keys'),
51
+ realtime: require('./realtime'),
52
+ webhooks: require('./webhooks'),
53
+ cron: require('./cron'),
54
+ queues: require('./queues'),
55
+ vault: require('./vault'),
56
+ wrappers: require('./wrappers'),
57
+ config: require('./config'),
58
+ inspect: require('./inspect'),
59
+ 'network-restrictions': require('./network').networkRestrictions,
60
+ 'ssl-enforcement': require('./network').sslEnforcement,
61
+ snippets: require('./snippets'),
62
+
63
+ // ── Codegen ────────────────────────────────────────────────────────────────
64
+ gen: require('./gen'),
65
+
66
+ // ── AI agents ──────────────────────────────────────────────────────────────
67
+ mcp: require('./mcp'),
68
+
69
+ // ── Shell ──────────────────────────────────────────────────────────────────
70
+ completion: require('./completion'),
71
+ };
72
+
73
+ // Grouping used only to render a tidy top-level help listing.
74
+ const groups = [
75
+ { title: 'Connection', names: ['login', 'logout', 'whoami', 'link', 'unlink', 'status'] },
76
+ { title: 'Orgs & projects (Console)', names: ['orgs', 'projects', 'branches', 'domains', 'vanity-subdomains'] },
77
+ { title: 'Local self-host', names: ['init', 'start', 'stop', 'deploy', 'logs'] },
78
+ { title: 'Database', names: ['db', 'backups', 'migrations', 'migration', 'import', 'test', 'postgres-config'] },
79
+ { title: 'Resources', names: ['rls', 'users', 'storage', 'functions', 'secrets', 'keys', 'realtime', 'webhooks', 'cron', 'queues', 'vault', 'wrappers', 'config', 'inspect', 'snippets', 'network-restrictions', 'ssl-enforcement'] },
80
+ { title: 'Codegen', names: ['gen'] },
81
+ { title: 'AI agents', names: ['mcp'] },
82
+ { title: 'Shell', names: ['completion'] },
83
+ ];
84
+
85
+ module.exports = { registry, groups };