@motherbase/cli 0.2.5 → 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 +247 -247
  3. package/bin/motherbase.js +10 -10
  4. package/package.json +35 -26
  5. package/src/api.js +233 -241
  6. package/src/commands/_util.js +106 -93
  7. package/src/commands/backups.js +16 -16
  8. package/src/commands/branches.js +113 -113
  9. package/src/commands/completion.js +90 -90
  10. package/src/commands/config.js +47 -47
  11. package/src/commands/cron.js +65 -65
  12. package/src/commands/db-catalog.js +326 -326
  13. package/src/commands/db.js +103 -103
  14. package/src/commands/domains.js +124 -124
  15. package/src/commands/functions.js +304 -297
  16. package/src/commands/gen.js +172 -172
  17. package/src/commands/import.js +55 -55
  18. package/src/commands/index.js +85 -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 +294 -273
  23. package/src/commands/login.js +39 -39
  24. package/src/commands/logout.js +16 -16
  25. package/src/commands/mcp.js +61 -61
  26. package/src/commands/migrate.js +207 -179
  27. package/src/commands/migration.js +108 -108
  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 +130 -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 +142 -142
  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 -102
  49. package/src/docs.js +68 -0
  50. package/src/edge-templates.js +194 -194
  51. package/src/format.js +92 -92
  52. package/src/inspect/csv.js +22 -22
  53. package/src/inspect/queries.js +562 -562
  54. package/src/main.js +224 -194
  55. package/src/mcp/platform-tools.js +233 -233
  56. package/src/mcp/protocol.js +152 -149
  57. package/src/mcp/storage-tools.js +93 -93
  58. package/src/mcp/tools.js +478 -478
  59. package/src/migration/commands.js +375 -375
  60. package/src/migration/files.js +245 -245
  61. package/src/prompt.js +91 -91
  62. package/src/storage/remote.js +62 -62
  63. package/src/ui.js +111 -111
package/src/prompt.js CHANGED
@@ -1,91 +1,91 @@
1
- 'use strict';
2
-
3
- // readline-based prompting, including hidden password entry. Built-ins only.
4
-
5
- const readline = require('readline');
6
-
7
- function question(query) {
8
- return new Promise(resolve => {
9
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
10
- rl.question(query, answer => {
11
- rl.close();
12
- resolve(answer.trim());
13
- });
14
- });
15
- }
16
-
17
- // Hidden input. Reads the TTY in raw mode so nothing echoes, masking each typed
18
- // character with '*'. Handles backspace, Enter, Ctrl+C, Ctrl+D and swallows
19
- // escape sequences (arrow keys etc.) so they never land in the value. When
20
- // stdin is not a TTY (pipes, CI) we fall back to a plain, unmasked line read.
21
- function password(query = 'Password: ') {
22
- const stdin = process.stdin;
23
- const stdout = process.stdout;
24
- if (!stdin.isTTY) return question(query);
25
-
26
- return new Promise(resolve => {
27
- let value = '';
28
- let inEscape = false;
29
- stdout.write(query);
30
- stdin.setRawMode(true);
31
- stdin.resume();
32
- stdin.setEncoding('utf8');
33
-
34
- const done = result => {
35
- stdin.removeListener('data', onData);
36
- stdin.setRawMode(false);
37
- stdin.pause();
38
- stdout.write('\n');
39
- resolve(result);
40
- };
41
-
42
- const onData = chunk => {
43
- for (const ch of chunk) {
44
- if (inEscape) {
45
- // Terminators of a CSI/escape sequence (e.g. arrow keys): stop swallowing.
46
- if (/[a-zA-Z~]/.test(ch)) inEscape = false;
47
- continue;
48
- }
49
- switch (ch) {
50
- case '\r':
51
- case '\n':
52
- case '\u0004': // Ctrl-D
53
- return done(value);
54
- case '\u0003': // Ctrl-C
55
- stdin.setRawMode(false);
56
- stdin.pause();
57
- stdout.write('\n');
58
- process.exit(130);
59
- return;
60
- case '\u007f': // Backspace / Delete
61
- case '\b':
62
- if (value.length > 0) {
63
- value = value.slice(0, -1);
64
- stdout.write('\b \b');
65
- }
66
- break;
67
- case '\u001b': // ESC: start of an escape sequence
68
- inEscape = true;
69
- break;
70
- default:
71
- // Ignore remaining control characters; mask printable ones.
72
- if (ch >= ' ') {
73
- value += ch;
74
- stdout.write('*');
75
- }
76
- }
77
- }
78
- };
79
-
80
- stdin.on('data', onData);
81
- });
82
- }
83
-
84
- async function confirm(query, defaultYes = false) {
85
- const suffix = defaultYes ? ' [Y/n] ' : ' [y/N] ';
86
- const answer = (await question(query + suffix)).toLowerCase();
87
- if (!answer) return defaultYes;
88
- return answer === 'y' || answer === 'yes';
89
- }
90
-
91
- module.exports = { question, password, confirm };
1
+ 'use strict';
2
+
3
+ // readline-based prompting, including hidden password entry. Built-ins only.
4
+
5
+ const readline = require('readline');
6
+
7
+ function question(query) {
8
+ return new Promise(resolve => {
9
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
10
+ rl.question(query, answer => {
11
+ rl.close();
12
+ resolve(answer.trim());
13
+ });
14
+ });
15
+ }
16
+
17
+ // Hidden input. Reads the TTY in raw mode so nothing echoes, masking each typed
18
+ // character with '*'. Handles backspace, Enter, Ctrl+C, Ctrl+D and swallows
19
+ // escape sequences (arrow keys etc.) so they never land in the value. When
20
+ // stdin is not a TTY (pipes, CI) we fall back to a plain, unmasked line read.
21
+ function password(query = 'Password: ') {
22
+ const stdin = process.stdin;
23
+ const stdout = process.stdout;
24
+ if (!stdin.isTTY) return question(query);
25
+
26
+ return new Promise(resolve => {
27
+ let value = '';
28
+ let inEscape = false;
29
+ stdout.write(query);
30
+ stdin.setRawMode(true);
31
+ stdin.resume();
32
+ stdin.setEncoding('utf8');
33
+
34
+ const done = result => {
35
+ stdin.removeListener('data', onData);
36
+ stdin.setRawMode(false);
37
+ stdin.pause();
38
+ stdout.write('\n');
39
+ resolve(result);
40
+ };
41
+
42
+ const onData = chunk => {
43
+ for (const ch of chunk) {
44
+ if (inEscape) {
45
+ // Terminators of a CSI/escape sequence (e.g. arrow keys): stop swallowing.
46
+ if (/[a-zA-Z~]/.test(ch)) inEscape = false;
47
+ continue;
48
+ }
49
+ switch (ch) {
50
+ case '\r':
51
+ case '\n':
52
+ case '\u0004': // Ctrl-D
53
+ return done(value);
54
+ case '\u0003': // Ctrl-C
55
+ stdin.setRawMode(false);
56
+ stdin.pause();
57
+ stdout.write('\n');
58
+ process.exit(130);
59
+ return;
60
+ case '\u007f': // Backspace / Delete
61
+ case '\b':
62
+ if (value.length > 0) {
63
+ value = value.slice(0, -1);
64
+ stdout.write('\b \b');
65
+ }
66
+ break;
67
+ case '\u001b': // ESC: start of an escape sequence
68
+ inEscape = true;
69
+ break;
70
+ default:
71
+ // Ignore remaining control characters; mask printable ones.
72
+ if (ch >= ' ') {
73
+ value += ch;
74
+ stdout.write('*');
75
+ }
76
+ }
77
+ }
78
+ };
79
+
80
+ stdin.on('data', onData);
81
+ });
82
+ }
83
+
84
+ async function confirm(query, defaultYes = false) {
85
+ const suffix = defaultYes ? ' [Y/n] ' : ' [y/N] ';
86
+ const answer = (await question(query + suffix)).toLowerCase();
87
+ if (!answer) return defaultYes;
88
+ return answer === 'y' || answer === 'yes';
89
+ }
90
+
91
+ module.exports = { question, password, confirm };
@@ -1,62 +1,62 @@
1
- 'use strict';
2
-
3
- // Storage paths in the Supabase CLI's form: `ss:///<bucket>/<key>`. Anything
4
- // else is a local path. Kept free of I/O so the path rules can be tested alone.
5
-
6
- const path = require('path');
7
- const { CliError } = require('../context');
8
-
9
- const SCHEME = 'ss:///';
10
-
11
- const isRemote = value => typeof value === 'string' && value.startsWith('ss://');
12
-
13
- /** `ss:///bucket/a/b` → `{ bucket: 'bucket', key: 'a/b' }`; `ss:///` → `{ bucket: '', key: '' }`. */
14
- function parseRemote(value) {
15
- if (!value.startsWith(SCHEME)) {
16
- throw new CliError(`Storage paths look like ${SCHEME}<bucket>/<key>, got "${value}".`);
17
- }
18
- const [bucket = '', ...rest] = value.slice(SCHEME.length).split('/');
19
- return { bucket, key: rest.join('/') };
20
- }
21
-
22
- function requireBucket(remote, value) {
23
- if (!remote.bucket) throw new CliError(`"${value}" names no bucket.`);
24
- return remote;
25
- }
26
-
27
- /** A key as a URL path: every segment encoded, the slashes between them kept. */
28
- const encodeKey = key => key.split('/').map(encodeURIComponent).join('/');
29
-
30
- /** `a/b/` and `a/b` both mean the prefix `a/b/`; the empty prefix is the bucket root. */
31
- const asPrefix = key => (key ? key.replace(/\/*$/, '/') : '');
32
-
33
- /**
34
- * One directory level of a listing, as `ls` shows it: the objects directly
35
- * under `prefix`, and each deeper key collapsed into its first folder (with a
36
- * trailing slash), deduplicated, in order.
37
- */
38
- function childrenOf(keys, prefix) {
39
- const seen = new Set();
40
- for (const key of keys) {
41
- if (!key.startsWith(prefix)) continue;
42
- const rest = key.slice(prefix.length);
43
- const slash = rest.indexOf('/');
44
- seen.add(slash < 0 ? rest : rest.slice(0, slash + 1));
45
- }
46
- return [...seen].filter(Boolean);
47
- }
48
-
49
- /**
50
- * Where a single file lands. A destination ending in `/` (or the bucket root)
51
- * is a folder, so the source's basename is appended, as `cp` does locally.
52
- */
53
- function destinationKey(dstKey, sourceName) {
54
- return !dstKey || dstKey.endsWith('/') ? `${dstKey}${path.basename(sourceName)}` : dstKey;
55
- }
56
-
57
- /** The key under `toPrefix` that `key` maps to when moving/copying `fromPrefix`. */
58
- function rebase(key, fromPrefix, toPrefix) {
59
- return `${toPrefix}${key.slice(fromPrefix.length)}`;
60
- }
61
-
62
- module.exports = { SCHEME, isRemote, parseRemote, requireBucket, encodeKey, asPrefix, childrenOf, destinationKey, rebase };
1
+ 'use strict';
2
+
3
+ // Storage paths in the Supabase CLI's form: `ss:///<bucket>/<key>`. Anything
4
+ // else is a local path. Kept free of I/O so the path rules can be tested alone.
5
+
6
+ const path = require('path');
7
+ const { CliError } = require('../context');
8
+
9
+ const SCHEME = 'ss:///';
10
+
11
+ const isRemote = value => typeof value === 'string' && value.startsWith('ss://');
12
+
13
+ /** `ss:///bucket/a/b` → `{ bucket: 'bucket', key: 'a/b' }`; `ss:///` → `{ bucket: '', key: '' }`. */
14
+ function parseRemote(value) {
15
+ if (!value.startsWith(SCHEME)) {
16
+ throw new CliError(`Storage paths look like ${SCHEME}<bucket>/<key>, got "${value}".`);
17
+ }
18
+ const [bucket = '', ...rest] = value.slice(SCHEME.length).split('/');
19
+ return { bucket, key: rest.join('/') };
20
+ }
21
+
22
+ function requireBucket(remote, value) {
23
+ if (!remote.bucket) throw new CliError(`"${value}" names no bucket.`);
24
+ return remote;
25
+ }
26
+
27
+ /** A key as a URL path: every segment encoded, the slashes between them kept. */
28
+ const encodeKey = key => key.split('/').map(encodeURIComponent).join('/');
29
+
30
+ /** `a/b/` and `a/b` both mean the prefix `a/b/`; the empty prefix is the bucket root. */
31
+ const asPrefix = key => (key ? key.replace(/\/*$/, '/') : '');
32
+
33
+ /**
34
+ * One directory level of a listing, as `ls` shows it: the objects directly
35
+ * under `prefix`, and each deeper key collapsed into its first folder (with a
36
+ * trailing slash), deduplicated, in order.
37
+ */
38
+ function childrenOf(keys, prefix) {
39
+ const seen = new Set();
40
+ for (const key of keys) {
41
+ if (!key.startsWith(prefix)) continue;
42
+ const rest = key.slice(prefix.length);
43
+ const slash = rest.indexOf('/');
44
+ seen.add(slash < 0 ? rest : rest.slice(0, slash + 1));
45
+ }
46
+ return [...seen].filter(Boolean);
47
+ }
48
+
49
+ /**
50
+ * Where a single file lands. A destination ending in `/` (or the bucket root)
51
+ * is a folder, so the source's basename is appended, as `cp` does locally.
52
+ */
53
+ function destinationKey(dstKey, sourceName) {
54
+ return !dstKey || dstKey.endsWith('/') ? `${dstKey}${path.basename(sourceName)}` : dstKey;
55
+ }
56
+
57
+ /** The key under `toPrefix` that `key` maps to when moving/copying `fromPrefix`. */
58
+ function rebase(key, fromPrefix, toPrefix) {
59
+ return `${toPrefix}${key.slice(fromPrefix.length)}`;
60
+ }
61
+
62
+ module.exports = { SCHEME, isRemote, parseRemote, requireBucket, encodeKey, asPrefix, childrenOf, destinationKey, rebase };
package/src/ui.js CHANGED
@@ -1,111 +1,111 @@
1
- 'use strict';
2
-
3
- // Minimal, dependency-free presentation helpers: ANSI colour (auto-off when not a
4
- // TTY or NO_COLOR is set), a table printer, a key/value printer, a spinner, and a
5
- // --json passthrough.
6
-
7
- const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
8
-
9
- function wrap(code) {
10
- return s => (useColor ? `[${code}m${s}` : String(s));
11
- }
12
-
13
- const color = {
14
- bold: wrap('1'),
15
- dim: wrap('2'),
16
- red: wrap('31'),
17
- green: wrap('32'),
18
- yellow: wrap('33'),
19
- cyan: wrap('36'),
20
- gray: wrap('90'),
21
- };
22
-
23
- function print(msg = '') {
24
- process.stdout.write(msg + '\n');
25
- }
26
-
27
- function printErr(msg = '') {
28
- process.stderr.write(msg + '\n');
29
- }
30
-
31
- function success(msg) {
32
- print(color.green('✓ ') + msg);
33
- }
34
-
35
- function info(msg) {
36
- print(color.cyan('• ') + msg);
37
- }
38
-
39
- function warn(msg) {
40
- printErr(color.yellow('! ') + msg);
41
- }
42
-
43
- function json(value) {
44
- print(JSON.stringify(value, null, 2));
45
- }
46
-
47
- // Render an array of flat objects as an aligned table using the given columns
48
- // (array of keys or { key, label } specs). Falls back to a friendly note if empty.
49
- function table(rows, columns) {
50
- if (!Array.isArray(rows) || rows.length === 0) {
51
- print(color.dim('(no rows)'));
52
- return;
53
- }
54
- const cols = (columns || Object.keys(rows[0])).map(c =>
55
- typeof c === 'string' ? { key: c, label: c } : c,
56
- );
57
- const widths = cols.map(c => c.label.length);
58
- const cells = rows.map(row =>
59
- cols.map((c, i) => {
60
- const v = row[c.key];
61
- const s = v === undefined || v === null ? '' : String(v);
62
- if (s.length > widths[i]) widths[i] = s.length;
63
- return s;
64
- }),
65
- );
66
- const header = cols.map((c, i) => color.bold(c.label.padEnd(widths[i]))).join(' ');
67
- print(header);
68
- print(color.dim(cols.map((_, i) => '-'.repeat(widths[i])).join(' ')));
69
- for (const rowCells of cells) {
70
- print(rowCells.map((s, i) => s.padEnd(widths[i])).join(' '));
71
- }
72
- }
73
-
74
- // Print a { key: value } object as aligned "key : value" lines.
75
- function keyValue(obj) {
76
- if (!obj || typeof obj !== 'object') {
77
- print(String(obj));
78
- return;
79
- }
80
- const keys = Object.keys(obj);
81
- const w = keys.reduce((m, k) => Math.max(m, k.length), 0);
82
- for (const k of keys) {
83
- let v = obj[k];
84
- if (v && typeof v === 'object') v = JSON.stringify(v);
85
- print(`${color.gray(k.padEnd(w))} ${v === undefined || v === null ? '' : v}`);
86
- }
87
- }
88
-
89
- // A tiny spinner. No-op (single status line) when stderr is not a TTY.
90
- function spinner(text) {
91
- const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
92
- if (!process.stderr.isTTY) {
93
- printErr(color.dim(text + ' ...'));
94
- return { stop() {}, succeed() {}, fail() {} };
95
- }
96
- let i = 0;
97
- const timer = setInterval(() => {
98
- process.stderr.write(`\r${color.cyan(frames[i++ % frames.length])} ${text}`);
99
- }, 80);
100
- const clear = () => {
101
- clearInterval(timer);
102
- process.stderr.write('\r');
103
- };
104
- return {
105
- stop: clear,
106
- succeed(msg) { clear(); if (msg) printErr(color.green('✓ ') + msg); },
107
- fail(msg) { clear(); if (msg) printErr(color.red('✗ ') + msg); },
108
- };
109
- }
110
-
111
- module.exports = { color, print, printErr, success, info, warn, json, table, keyValue, spinner };
1
+ 'use strict';
2
+
3
+ // Minimal, dependency-free presentation helpers: ANSI colour (auto-off when not a
4
+ // TTY or NO_COLOR is set), a table printer, a key/value printer, a spinner, and a
5
+ // --json passthrough.
6
+
7
+ const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
8
+
9
+ function wrap(code) {
10
+ return s => (useColor ? `[${code}m${s}` : String(s));
11
+ }
12
+
13
+ const color = {
14
+ bold: wrap('1'),
15
+ dim: wrap('2'),
16
+ red: wrap('31'),
17
+ green: wrap('32'),
18
+ yellow: wrap('33'),
19
+ cyan: wrap('36'),
20
+ gray: wrap('90'),
21
+ };
22
+
23
+ function print(msg = '') {
24
+ process.stdout.write(msg + '\n');
25
+ }
26
+
27
+ function printErr(msg = '') {
28
+ process.stderr.write(msg + '\n');
29
+ }
30
+
31
+ function success(msg) {
32
+ print(color.green('✓ ') + msg);
33
+ }
34
+
35
+ function info(msg) {
36
+ print(color.cyan('• ') + msg);
37
+ }
38
+
39
+ function warn(msg) {
40
+ printErr(color.yellow('! ') + msg);
41
+ }
42
+
43
+ function json(value) {
44
+ print(JSON.stringify(value, null, 2));
45
+ }
46
+
47
+ // Render an array of flat objects as an aligned table using the given columns
48
+ // (array of keys or { key, label } specs). Falls back to a friendly note if empty.
49
+ function table(rows, columns) {
50
+ if (!Array.isArray(rows) || rows.length === 0) {
51
+ print(color.dim('(no rows)'));
52
+ return;
53
+ }
54
+ const cols = (columns || Object.keys(rows[0])).map(c =>
55
+ typeof c === 'string' ? { key: c, label: c } : c,
56
+ );
57
+ const widths = cols.map(c => c.label.length);
58
+ const cells = rows.map(row =>
59
+ cols.map((c, i) => {
60
+ const v = row[c.key];
61
+ const s = v === undefined || v === null ? '' : String(v);
62
+ if (s.length > widths[i]) widths[i] = s.length;
63
+ return s;
64
+ }),
65
+ );
66
+ const header = cols.map((c, i) => color.bold(c.label.padEnd(widths[i]))).join(' ');
67
+ print(header);
68
+ print(color.dim(cols.map((_, i) => '-'.repeat(widths[i])).join(' ')));
69
+ for (const rowCells of cells) {
70
+ print(rowCells.map((s, i) => s.padEnd(widths[i])).join(' '));
71
+ }
72
+ }
73
+
74
+ // Print a { key: value } object as aligned "key : value" lines.
75
+ function keyValue(obj) {
76
+ if (!obj || typeof obj !== 'object') {
77
+ print(String(obj));
78
+ return;
79
+ }
80
+ const keys = Object.keys(obj);
81
+ const w = keys.reduce((m, k) => Math.max(m, k.length), 0);
82
+ for (const k of keys) {
83
+ let v = obj[k];
84
+ if (v && typeof v === 'object') v = JSON.stringify(v);
85
+ print(`${color.gray(k.padEnd(w))} ${v === undefined || v === null ? '' : v}`);
86
+ }
87
+ }
88
+
89
+ // A tiny spinner. No-op (single status line) when stderr is not a TTY.
90
+ function spinner(text) {
91
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
92
+ if (!process.stderr.isTTY) {
93
+ printErr(color.dim(text + ' ...'));
94
+ return { stop() {}, succeed() {}, fail() {} };
95
+ }
96
+ let i = 0;
97
+ const timer = setInterval(() => {
98
+ process.stderr.write(`\r${color.cyan(frames[i++ % frames.length])} ${text}`);
99
+ }, 80);
100
+ const clear = () => {
101
+ clearInterval(timer);
102
+ process.stderr.write('\r');
103
+ };
104
+ return {
105
+ stop: clear,
106
+ succeed(msg) { clear(); if (msg) printErr(color.green('✓ ') + msg); },
107
+ fail(msg) { clear(); if (msg) printErr(color.red('✗ ') + msg); },
108
+ };
109
+ }
110
+
111
+ module.exports = { color, print, printErr, success, info, warn, json, table, keyValue, spinner };