@motherbase/cli 0.2.6 → 0.2.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.
Files changed (62) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +8 -1
  3. package/bin/.motherbase/config.json +5 -0
  4. package/bin/motherbase.js +10 -10
  5. package/package.json +4 -4
  6. package/src/api.js +39 -16
  7. package/src/commands/_util.js +106 -106
  8. package/src/commands/backups.js +16 -16
  9. package/src/commands/branches.js +113 -113
  10. package/src/commands/completion.js +90 -90
  11. package/src/commands/config.js +47 -47
  12. package/src/commands/cron.js +65 -65
  13. package/src/commands/db-catalog.js +39 -1
  14. package/src/commands/db.js +103 -103
  15. package/src/commands/domains.js +124 -124
  16. package/src/commands/gen.js +178 -172
  17. package/src/commands/import.js +55 -55
  18. package/src/commands/index.js +86 -85
  19. package/src/commands/inspect.js +178 -178
  20. package/src/commands/keys.js +72 -72
  21. package/src/commands/link.js +35 -35
  22. package/src/commands/local.js +101 -24
  23. package/src/commands/login.js +9 -3
  24. package/src/commands/logout.js +16 -16
  25. package/src/commands/mcp.js +61 -61
  26. package/src/commands/migrate.js +0 -5
  27. package/src/commands/migration.js +1 -1
  28. package/src/commands/network.js +83 -83
  29. package/src/commands/orgs.js +40 -40
  30. package/src/commands/postgres-config.js +113 -113
  31. package/src/commands/projects.js +131 -130
  32. package/src/commands/queues.js +133 -133
  33. package/src/commands/realtime.js +35 -35
  34. package/src/commands/rls.js +176 -176
  35. package/src/commands/secrets.js +66 -66
  36. package/src/commands/signing.js +130 -130
  37. package/src/commands/snippets.js +44 -44
  38. package/src/commands/status.js +42 -42
  39. package/src/commands/storage.js +261 -261
  40. package/src/commands/test.js +9 -9
  41. package/src/commands/unlink.js +15 -15
  42. package/src/commands/users.js +29 -1
  43. package/src/commands/vault.js +82 -82
  44. package/src/commands/webhooks.js +62 -62
  45. package/src/commands/whoami.js +20 -20
  46. package/src/commands/wrappers.js +150 -150
  47. package/src/config.js +98 -98
  48. package/src/context.js +106 -106
  49. package/src/edge-templates.js +194 -194
  50. package/src/format.js +92 -92
  51. package/src/inspect/csv.js +22 -22
  52. package/src/inspect/queries.js +562 -562
  53. package/src/main.js +145 -66
  54. package/src/mcp/platform-tools.js +233 -233
  55. package/src/mcp/protocol.js +152 -152
  56. package/src/mcp/storage-tools.js +93 -93
  57. package/src/mcp/tools.js +478 -478
  58. package/src/migration/commands.js +2 -2
  59. package/src/migration/files.js +13 -0
  60. package/src/prompt.js +91 -91
  61. package/src/storage/remote.js +62 -62
  62. package/src/ui.js +111 -111
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Motherbase contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Motherbase contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -146,10 +146,17 @@ emits, so `createClient<Database>(url, key)` types every row, rejects unknown
146
146
  columns and types `.rpc()` calls:
147
147
 
148
148
  ```bash
149
- motherbase gen types typescript > database.types.ts
149
+ motherbase gen types typescript # in a terminal: writes ./database.types.ts
150
+ motherbase gen types typescript > database.types.ts # piped: prints the file
150
151
  motherbase gen types typescript --schema shop --output src/shop.types.ts
151
152
  ```
152
153
 
154
+ The same `Database` also names the project's edge functions and buckets, under
155
+ `__InternalMotherbase`, so `client.functions.invoke('…')` and
156
+ `client.storage.from('…')` autocomplete with no extra generic. A name created
157
+ after the file was generated still compiles. Since they are part of the file,
158
+ adding a function or a bucket is drift for `--check` below.
159
+
153
160
  With no `--output` the file goes to stdout and nothing else does, so the
154
161
  redirect above produces a compiling file. `--local` skips the Console proxy and
155
162
  talks to a project API on this machine (`MOTHERBASE_URL`, default
@@ -0,0 +1,5 @@
1
+ {
2
+ "consoleUrl": "https://console.test.fidelix.pro",
3
+ "projectId": "gvask8",
4
+ "projectName": "gvask8"
5
+ }
package/bin/motherbase.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const { main } = require('../src/main');
5
-
6
- main(process.argv.slice(2))
7
- .then(code => { process.exitCode = code || 0; })
8
- .catch(err => {
9
- process.stderr.write((err && err.message ? err.message : String(err)) + '\n');
10
- process.exitCode = 1;
11
- });
2
+ 'use strict';
3
+
4
+ const { main } = require('../src/main');
5
+
6
+ main(process.argv.slice(2))
7
+ .then(code => { process.exitCode = code || 0; })
8
+ .catch(err => {
9
+ process.stderr.write((err && err.message ? err.message : String(err)) + '\n');
10
+ process.exitCode = 1;
11
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motherbase/cli",
3
- "version": "0.2.6",
3
+ "version": "0.2.9",
4
4
  "description": "Motherbase operator CLI: drives the Platform Console and linked projects.",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -25,11 +25,11 @@
25
25
  "license": "MIT",
26
26
  "repository": {
27
27
  "type": "git",
28
- "url": "git+https://github.com/JulienGuinot/Boilerplate-backend.git",
28
+ "url": "git+https://github.com/JulienGuinot/motherbase.git",
29
29
  "directory": "packages/cli"
30
30
  },
31
- "homepage": "https://github.com/JulienGuinot/Boilerplate-backend#readme",
31
+ "homepage": "https://github.com/JulienGuinot/motherbase#readme",
32
32
  "bugs": {
33
- "url": "https://github.com/JulienGuinot/Boilerplate-backend/issues"
33
+ "url": "https://github.com/JulienGuinot/motherbase/issues"
34
34
  }
35
35
  }
package/src/api.js CHANGED
@@ -107,35 +107,57 @@ function consoleRequest(ctx, method, apiPath, { body, query } = {}) {
107
107
  return doFetch(url, { method, token, body });
108
108
  }
109
109
 
110
- // Login is special: no token yet. Uses Better Auth's native sign-in endpoint;
111
- // the session token comes back in the body and/or the `set-auth-token` header
112
- // (bearer plugin). Returns `{ token, user }` so the caller can persist it.
113
- async function loginRequest(consoleUrl, email, password) {
114
- const url = `${consoleUrl}/api/auth/sign-in/email`;
110
+ // Login is special: no token yet, so it talks to Better Auth's native endpoints
111
+ // directly. Returns the raw response next to the parsed body because the token
112
+ // lives in the `set-auth-token` header (bearer plugin) and the 2FA step needs
113
+ // the response cookies.
114
+ async function authPost(url, body, headers = {}) {
115
+ const finalHeaders = { 'content-type': 'application/json', ...headers };
116
+ const origin = originFor(url);
117
+ if (origin) finalHeaders['origin'] = origin;
115
118
  let res;
116
119
  try {
117
- const headers = { 'content-type': 'application/json' };
118
- const origin = originFor(url);
119
- if (origin) headers['origin'] = origin;
120
- res = await fetch(url, {
121
- method: 'POST',
122
- headers,
123
- body: JSON.stringify({ email, password }),
124
- });
120
+ res = await fetch(url, { method: 'POST', headers: finalHeaders, body: JSON.stringify(body) });
125
121
  } catch (err) {
126
122
  throw new CliError(`Cannot reach ${url}: ${err.message}`);
127
123
  }
128
124
  trace('POST', url, res);
129
125
  const text = await res.text();
130
- let body;
131
- if (text) { try { body = JSON.parse(text); } catch { body = undefined; } }
126
+ let parsed;
127
+ if (text) { try { parsed = JSON.parse(text); } catch { parsed = undefined; } }
132
128
  if (!res.ok) {
133
- throw new CliError(errorMessage(res.status, body, text));
129
+ throw new CliError(errorMessage(res.status, parsed, text));
134
130
  }
131
+ return { res, body: parsed };
132
+ }
133
+
134
+ // The `two_factor` cookie (prefixed `__Secure-` on https) scopes the
135
+ // verify-totp call to the pending sign-in.
136
+ function twoFactorCookie(res) {
137
+ const setCookies = typeof res.headers.getSetCookie === 'function' ? res.headers.getSetCookie() : [];
138
+ const pair = setCookies.map(c => c.split(';')[0]).find(c => /two_factor=./.test(c));
139
+ if (!pair) throw new CliError('two-factor sign-in started but the server sent no two_factor cookie');
140
+ return pair;
141
+ }
142
+
143
+ // Returns `{ token, user }`, or `{ twoFactorCookie }` when the account has 2FA
144
+ // enrolled. In that case Better Auth answers `{ twoFactorRedirect: true }` and
145
+ // the `set-auth-token` header still carries the pre-2FA session it has just
146
+ // deleted, so the header must not be trusted (see the console login route).
147
+ async function loginRequest(consoleUrl, email, password) {
148
+ const { res, body } = await authPost(`${consoleUrl}/api/auth/sign-in/email`, { email, password });
149
+ if (body && body.twoFactorRedirect) return { twoFactorCookie: twoFactorCookie(res) };
135
150
  const token = (body && body.token) || res.headers.get('set-auth-token') || undefined;
136
151
  return { token, user: body && body.user };
137
152
  }
138
153
 
154
+ // Second step of a 2FA sign-in: trade the TOTP code for the real session token.
155
+ async function verifyTotpRequest(consoleUrl, cookie, code) {
156
+ const { res, body } = await authPost(`${consoleUrl}/api/auth/two-factor/verify-totp`, { code }, { cookie });
157
+ const token = res.headers.get('set-auth-token') || (body && body.token) || undefined;
158
+ return { token, user: body && body.user };
159
+ }
160
+
139
161
  // ── Admin / resource (via proxy or direct) ───────────────────────────────────
140
162
 
141
163
  function adminBase(target) {
@@ -224,6 +246,7 @@ async function downloadFile(ctx, apiPath, destPath, { query } = {}) {
224
246
  module.exports = {
225
247
  consoleRequest,
226
248
  loginRequest,
249
+ verifyTotpRequest,
227
250
  adminRequest,
228
251
  projectUrl,
229
252
  uploadFile,
@@ -1,106 +1,106 @@
1
- 'use strict';
2
-
3
- // Small shared helpers for command modules.
4
-
5
- const path = require('path');
6
- const { CliError } = require('../context');
7
- const { outputFormat, toYaml, toEnv } = require('../format');
8
-
9
- // Parse `key=value` tokens into an object. Values that look like numbers/bools are
10
- // coerced unless `raw` is set. Supports dotted keys via `--json`-style callers.
11
- function parseKeyValues(tokens, { coerce = true } = {}) {
12
- const out = {};
13
- for (const tok of tokens) {
14
- const idx = tok.indexOf('=');
15
- if (idx <= 0) throw new CliError(`expected key=value, got "${tok}"`);
16
- const key = tok.slice(0, idx).trim();
17
- let value = tok.slice(idx + 1);
18
- if (coerce) {
19
- if (value === 'true') value = true;
20
- else if (value === 'false') value = false;
21
- else if (value !== '' && !Number.isNaN(Number(value)) && /^-?\d+(\.\d+)?$/.test(value)) value = Number(value);
22
- }
23
- out[key] = value;
24
- }
25
- return out;
26
- }
27
-
28
- // Render a command's result in the selected `-o/--output` format; `pretty` is
29
- // the command's own human renderer.
30
- function output(ctx, data, humanFn) {
31
- const ui = require('../ui');
32
- switch (outputFormat(ctx.flags)) {
33
- case 'json': ui.json(data); return;
34
- case 'yaml': process.stdout.write(toYaml(data)); return;
35
- case 'env': process.stdout.write(toEnv(data)); return;
36
- default: humanFn(data, ui);
37
- }
38
- }
39
-
40
- // The same command under a second (Supabase) name: one implementation, with
41
- // help that shows the name it was reached by.
42
- function alias(entry, usage, summary = entry.summary) {
43
- return { ...entry, usage, summary };
44
- }
45
-
46
- // Parse a dotenv file: `KEY=value`, `export KEY=value`, `#` comments, and
47
- // single- or double-quoted values (double quotes expand \n).
48
- function parseDotenv(text) {
49
- const out = {};
50
- for (const raw of String(text).split(/\r?\n/)) {
51
- const line = raw.trim().replace(/^export\s+/, '');
52
- if (!line || line.startsWith('#')) continue;
53
- const idx = line.indexOf('=');
54
- if (idx <= 0) throw new CliError(`invalid env line: "${raw}"`);
55
- const key = line.slice(0, idx).trim();
56
- let value = line.slice(idx + 1).trim();
57
- const quote = value[0];
58
- if ((quote === '"' || quote === "'") && value.endsWith(quote) && value.length > 1) {
59
- value = value.slice(1, -1);
60
- if (quote === '"') value = value.replace(/\\n/g, '\n');
61
- } else {
62
- value = value.replace(/\s+#.*$/, '');
63
- }
64
- out[key] = value;
65
- }
66
- return out;
67
- }
68
-
69
- function requireArg(value, name) {
70
- if (value === undefined || value === null || value === '') {
71
- throw new CliError(`missing required argument: <${name}>`);
72
- }
73
- return value;
74
- }
75
-
76
- // Split "bucket:path" into { bucket, rest }.
77
- function splitRef(ref) {
78
- const idx = ref.indexOf(':');
79
- if (idx < 0) return { bucket: undefined, rest: ref };
80
- return { bucket: ref.slice(0, idx), rest: ref.slice(idx + 1) };
81
- }
82
-
83
- // `/projects/<linked project>` on the Console, for the per-project platform
84
- // surfaces (branches, domains) that live on the Console rather than the
85
- // project API. They need a Console, so direct mode cannot reach them.
86
- function consoleProjectPath(ctx, suffix = '') {
87
- ctx.requireConsole();
88
- if (!ctx.projectId) {
89
- throw new CliError('No project linked. Run `motherbase link` or pass --project-ref <id>.');
90
- }
91
- return `/projects/${encodeURIComponent(ctx.projectId)}${suffix}`;
92
- }
93
-
94
- // Join `parts` under `root`, refusing a result that escapes it. Names that come
95
- // from the server (object keys, function routes) must not write outside the
96
- // directory the user picked: `../../.bashrc` is a valid object key.
97
- function resolveInside(root, ...parts) {
98
- const target = path.resolve(root, ...parts);
99
- const rel = path.relative(root, target);
100
- if (rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
101
- throw new CliError(`Refusing to write outside ${root}: ${parts.join('/')}`);
102
- }
103
- return target;
104
- }
105
-
106
- module.exports = { parseKeyValues, output, alias, parseDotenv, requireArg, splitRef, consoleProjectPath, resolveInside };
1
+ 'use strict';
2
+
3
+ // Small shared helpers for command modules.
4
+
5
+ const path = require('path');
6
+ const { CliError } = require('../context');
7
+ const { outputFormat, toYaml, toEnv } = require('../format');
8
+
9
+ // Parse `key=value` tokens into an object. Values that look like numbers/bools are
10
+ // coerced unless `raw` is set. Supports dotted keys via `--json`-style callers.
11
+ function parseKeyValues(tokens, { coerce = true } = {}) {
12
+ const out = {};
13
+ for (const tok of tokens) {
14
+ const idx = tok.indexOf('=');
15
+ if (idx <= 0) throw new CliError(`expected key=value, got "${tok}"`);
16
+ const key = tok.slice(0, idx).trim();
17
+ let value = tok.slice(idx + 1);
18
+ if (coerce) {
19
+ if (value === 'true') value = true;
20
+ else if (value === 'false') value = false;
21
+ else if (value !== '' && !Number.isNaN(Number(value)) && /^-?\d+(\.\d+)?$/.test(value)) value = Number(value);
22
+ }
23
+ out[key] = value;
24
+ }
25
+ return out;
26
+ }
27
+
28
+ // Render a command's result in the selected `-o/--output` format; `pretty` is
29
+ // the command's own human renderer.
30
+ function output(ctx, data, humanFn) {
31
+ const ui = require('../ui');
32
+ switch (outputFormat(ctx.flags)) {
33
+ case 'json': ui.json(data); return;
34
+ case 'yaml': process.stdout.write(toYaml(data)); return;
35
+ case 'env': process.stdout.write(toEnv(data)); return;
36
+ default: humanFn(data, ui);
37
+ }
38
+ }
39
+
40
+ // The same command under a second (Supabase) name: one implementation, with
41
+ // help that shows the name it was reached by.
42
+ function alias(entry, usage, summary = entry.summary) {
43
+ return { ...entry, usage, summary };
44
+ }
45
+
46
+ // Parse a dotenv file: `KEY=value`, `export KEY=value`, `#` comments, and
47
+ // single- or double-quoted values (double quotes expand \n).
48
+ function parseDotenv(text) {
49
+ const out = {};
50
+ for (const raw of String(text).split(/\r?\n/)) {
51
+ const line = raw.trim().replace(/^export\s+/, '');
52
+ if (!line || line.startsWith('#')) continue;
53
+ const idx = line.indexOf('=');
54
+ if (idx <= 0) throw new CliError(`invalid env line: "${raw}"`);
55
+ const key = line.slice(0, idx).trim();
56
+ let value = line.slice(idx + 1).trim();
57
+ const quote = value[0];
58
+ if ((quote === '"' || quote === "'") && value.endsWith(quote) && value.length > 1) {
59
+ value = value.slice(1, -1);
60
+ if (quote === '"') value = value.replace(/\\n/g, '\n');
61
+ } else {
62
+ value = value.replace(/\s+#.*$/, '');
63
+ }
64
+ out[key] = value;
65
+ }
66
+ return out;
67
+ }
68
+
69
+ function requireArg(value, name) {
70
+ if (value === undefined || value === null || value === '') {
71
+ throw new CliError(`missing required argument: <${name}>`);
72
+ }
73
+ return value;
74
+ }
75
+
76
+ // Split "bucket:path" into { bucket, rest }.
77
+ function splitRef(ref) {
78
+ const idx = ref.indexOf(':');
79
+ if (idx < 0) return { bucket: undefined, rest: ref };
80
+ return { bucket: ref.slice(0, idx), rest: ref.slice(idx + 1) };
81
+ }
82
+
83
+ // `/projects/<linked project>` on the Console, for the per-project platform
84
+ // surfaces (branches, domains) that live on the Console rather than the
85
+ // project API. They need a Console, so direct mode cannot reach them.
86
+ function consoleProjectPath(ctx, suffix = '') {
87
+ ctx.requireConsole();
88
+ if (!ctx.projectId) {
89
+ throw new CliError('No project linked. Run `motherbase link` or pass --project-ref <id>.');
90
+ }
91
+ return `/projects/${encodeURIComponent(ctx.projectId)}${suffix}`;
92
+ }
93
+
94
+ // Join `parts` under `root`, refusing a result that escapes it. Names that come
95
+ // from the server (object keys, function routes) must not write outside the
96
+ // directory the user picked: `../../.bashrc` is a valid object key.
97
+ function resolveInside(root, ...parts) {
98
+ const target = path.resolve(root, ...parts);
99
+ const rel = path.relative(root, target);
100
+ if (rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
101
+ throw new CliError(`Refusing to write outside ${root}: ${parts.join('/')}`);
102
+ }
103
+ return target;
104
+ }
105
+
106
+ module.exports = { parseKeyValues, output, alias, parseDotenv, requireArg, splitRef, consoleProjectPath, resolveInside };
@@ -1,16 +1,16 @@
1
- 'use strict';
2
-
3
- // Supabase's top-level `backups`. The implementations live under `db`, which
4
- // keeps its own names for them.
5
-
6
- const db = require('./db');
7
- const { alias } = require('./_util');
8
-
9
- module.exports = {
10
- summary: 'List and restore database backups',
11
- usage: 'motherbase backups <list|restore> ...',
12
- subcommands: {
13
- list: alias(db.subcommands.backups, 'motherbase backups list', 'List database backups'),
14
- restore: alias(db.subcommands.restore, 'motherbase backups restore <file> | --key <remoteKey> --confirm'),
15
- },
16
- };
1
+ 'use strict';
2
+
3
+ // Supabase's top-level `backups`. The implementations live under `db`, which
4
+ // keeps its own names for them.
5
+
6
+ const db = require('./db');
7
+ const { alias } = require('./_util');
8
+
9
+ module.exports = {
10
+ summary: 'List and restore database backups',
11
+ usage: 'motherbase backups <list|restore> ...',
12
+ subcommands: {
13
+ list: alias(db.subcommands.backups, 'motherbase backups list', 'List database backups'),
14
+ restore: alias(db.subcommands.restore, 'motherbase backups restore <file> | --key <remoteKey> --confirm'),
15
+ },
16
+ };