@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
@@ -1,178 +1,178 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const api = require('../api');
6
- const { output } = require('./_util');
7
- const { CliError } = require('../context');
8
- const { QUERIES, DEPRECATED, REPORT, resolveQuery } = require('../inspect/queries');
9
- const { toCsv } = require('../inspect/csv');
10
-
11
- function simple(apiPath, summary, render) {
12
- return {
13
- summary,
14
- // usage is filled in from the subcommand key below: deriving it from the
15
- // summary printed `inspect security` for `shield` and `inspect request` for
16
- // `requests`, neither of which is a command.
17
- usage: '',
18
- async run(ctx) {
19
- const data = await api.adminRequest(ctx, 'GET', apiPath);
20
- output(ctx, data, render || ((d, ui) => ui.json(d)));
21
- },
22
- };
23
- }
24
-
25
- // Postgres' two ways of saying pg_stat_statements is not usable here: the
26
- // extension was never created in this database, or the library is not preloaded.
27
- const STAT_STATEMENTS_MISSING = /pg_stat_statements.*(does not exist|must be loaded)/i;
28
-
29
- class StatStatementsMissing extends CliError {
30
- constructor() {
31
- super(
32
- 'pg_stat_statements is not enabled in this database. Enable it from Studio (Database > Extensions) '
33
- + 'or run: motherbase db sql -e "create extension pg_stat_statements"',
34
- );
35
- }
36
- }
37
-
38
- /** Rows of one inspect query, run on the read-only SQL endpoint. */
39
- async function runQuery(ctx, query) {
40
- try {
41
- const data = await api.adminRequest(ctx, 'POST', '/sql/readonly', { body: { sql: query.sql } });
42
- return (data && data.rows) || [];
43
- } catch (err) {
44
- if (query.needsStatStatements && STAT_STATEMENTS_MISSING.test(err.message)) {
45
- throw new StatStatementsMissing();
46
- }
47
- throw err;
48
- }
49
- }
50
-
51
- // Statements span lines and `to_char` pads numbers; a table cell wants neither.
52
- // A statement is also cut short, or one query pushes every other column off
53
- // screen; -o json keeps it whole.
54
- const CELL_WIDTH = 60;
55
- function cell(value) {
56
- if (typeof value !== 'string') return value;
57
- const text = value.replace(/\s+/g, ' ').trim();
58
- return text.length > CELL_WIDTH ? `${text.slice(0, CELL_WIDTH - 1)}…` : text;
59
- }
60
-
61
- function dbCommand(name) {
62
- const { name: canonical, query } = resolveQuery(name);
63
- const replaced = canonical !== name;
64
- return {
65
- summary: replaced ? `Deprecated: use "${canonical}" instead` : query.summary,
66
- usage: `motherbase inspect db ${name}`,
67
- async run(ctx) {
68
- const rows = await runQuery(ctx, query);
69
- output(ctx, { query: canonical, rows }, (_d, ui) => {
70
- if (replaced) ui.warn(`"inspect db ${name}" is deprecated: showing "${canonical}".`);
71
- const display = rows.map(row => Object.fromEntries(Object.entries(row).map(([k, v]) => [k, cell(v)])));
72
- ui.table(display, query.columns);
73
- });
74
- },
75
- };
76
- }
77
-
78
- const db = {
79
- summary: 'Inspect the database: bloat, locks, slow queries, index and table stats',
80
- usage: `motherbase inspect db <${Object.keys(QUERIES).join('|')}>`,
81
- subcommands: Object.fromEntries(
82
- [...Object.keys(QUERIES), ...Object.keys(DEPRECATED)].map(name => [name, dbCommand(name)]),
83
- ),
84
- };
85
-
86
- // Local-time YYYY-MM-DD: the report's folder, one per day as upstream does.
87
- function dateFolder(now = new Date()) {
88
- const pad = n => String(n).padStart(2, '0');
89
- return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
90
- }
91
-
92
- const report = {
93
- summary: 'Write every inspect query to a CSV in a dated folder',
94
- usage: 'motherbase inspect report [--output-dir <dir>]',
95
- async run(ctx, _args, flags) {
96
- const dir = path.resolve(ctx.cwd, String(flags['output-dir'] || '.'), dateFolder());
97
- fs.mkdirSync(dir, { recursive: true });
98
-
99
- const files = [];
100
- const skipped = [];
101
- // One at a time: fourteen catalog scans at once is a load spike on the
102
- // database being diagnosed.
103
- for (const { file, name, query } of REPORT) {
104
- let rows;
105
- try {
106
- rows = await runQuery(ctx, query);
107
- } catch (err) {
108
- // A missing pg_stat_statements costs two files, not the whole report.
109
- if (!(err instanceof StatStatementsMissing)) throw err;
110
- skipped.push(name);
111
- continue;
112
- }
113
- const target = path.join(dir, `${file}.csv`);
114
- fs.writeFileSync(target, toCsv(query.columns, rows));
115
- files.push(target);
116
- }
117
-
118
- output(ctx, { outputDir: dir, files, skipped }, (d, ui) => {
119
- if (d.skipped.length) ui.warn(`Skipped ${d.skipped.join(', ')}: ${new StatStatementsMissing().message}`);
120
- ui.success(`Saved ${d.files.length} CSV files to ${path.relative(ctx.cwd, d.outputDir) || '.'}`);
121
- });
122
- },
123
- };
124
-
125
- const subcommands = {
126
- db,
127
- report,
128
- logs: {
129
- summary: 'Recent server logs',
130
- usage: 'motherbase inspect logs [--limit n] [--source api|rest|postgres|pooler|auth|storage|realtime|edge-function|cron|platform]',
131
- async run(ctx, args, flags) {
132
- // A Logs Explorer "source" is the server's `service` filter.
133
- const query = flags.limit || flags.source ? { limit: flags.limit, service: flags.source } : undefined;
134
- const data = await api.adminRequest(ctx, 'GET', '/logs', { query });
135
- const logs = (data && data.logs) || [];
136
- output(ctx, data, (_d, ui) =>
137
- logs.forEach(l => ui.print(typeof l === 'string' ? l : `${l.level || ''} ${l.source || ''} ${l.message || JSON.stringify(l)}`)),
138
- );
139
- },
140
- },
141
- 'index-advisor': {
142
- summary: 'Suggest indexes for a statement (hypopg + index_advisor)',
143
- usage: 'motherbase inspect index-advisor --sql "<select ...>" | motherbase inspect index-advisor --enable',
144
- async run(ctx, args, flags) {
145
- if (flags.enable) {
146
- const status = await api.adminRequest(ctx, 'POST', '/performance/index-advisor/enable');
147
- output(ctx, status, (_d, ui) => ui.success('Index advisor enabled'));
148
- return;
149
- }
150
- const query = flags.sql || flags.e;
151
- if (typeof query !== 'string') throw new CliError('Provide the statement with --sql "..." (or --enable to install the advisor).');
152
- const data = await api.adminRequest(ctx, 'POST', '/performance/index-advisor', { body: { query } });
153
- output(ctx, data, (d, ui) => {
154
- const statements = (d && d.indexStatements) || [];
155
- if (!statements.length) ui.print('No index would help this statement.');
156
- statements.forEach(s => ui.print(s));
157
- if (d && d.improvementPct != null) ui.print(`Estimated cost drop: ${d.improvementPct}%`);
158
- });
159
- },
160
- },
161
- stats: simple('/stats', 'Stats overview'),
162
- performance: simple('/performance', 'Performance metrics'),
163
- requests: simple('/metrics/requests', 'Request metrics'),
164
- advisor: simple('/advisor', 'Advisor findings'),
165
- shield: simple('/shield', 'Security shield report'),
166
- };
167
-
168
- for (const [name, def] of Object.entries(subcommands)) {
169
- if (!def.usage) def.usage = `motherbase inspect ${name}`;
170
- }
171
-
172
- module.exports = {
173
- summary: 'Diagnostics: database inspection, report, logs, stats, index-advisor, advisor, shield',
174
- usage: 'motherbase inspect <db|report|logs|stats|performance|index-advisor|requests|advisor|shield>',
175
- subcommands,
176
- dateFolder,
177
- cell,
178
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const api = require('../api');
6
+ const { output } = require('./_util');
7
+ const { CliError } = require('../context');
8
+ const { QUERIES, DEPRECATED, REPORT, resolveQuery } = require('../inspect/queries');
9
+ const { toCsv } = require('../inspect/csv');
10
+
11
+ function simple(apiPath, summary, render) {
12
+ return {
13
+ summary,
14
+ // usage is filled in from the subcommand key below: deriving it from the
15
+ // summary printed `inspect security` for `shield` and `inspect request` for
16
+ // `requests`, neither of which is a command.
17
+ usage: '',
18
+ async run(ctx) {
19
+ const data = await api.adminRequest(ctx, 'GET', apiPath);
20
+ output(ctx, data, render || ((d, ui) => ui.json(d)));
21
+ },
22
+ };
23
+ }
24
+
25
+ // Postgres' two ways of saying pg_stat_statements is not usable here: the
26
+ // extension was never created in this database, or the library is not preloaded.
27
+ const STAT_STATEMENTS_MISSING = /pg_stat_statements.*(does not exist|must be loaded)/i;
28
+
29
+ class StatStatementsMissing extends CliError {
30
+ constructor() {
31
+ super(
32
+ 'pg_stat_statements is not enabled in this database. Enable it from Studio (Database > Extensions) '
33
+ + 'or run: motherbase db sql -e "create extension pg_stat_statements"',
34
+ );
35
+ }
36
+ }
37
+
38
+ /** Rows of one inspect query, run on the read-only SQL endpoint. */
39
+ async function runQuery(ctx, query) {
40
+ try {
41
+ const data = await api.adminRequest(ctx, 'POST', '/sql/readonly', { body: { sql: query.sql } });
42
+ return (data && data.rows) || [];
43
+ } catch (err) {
44
+ if (query.needsStatStatements && STAT_STATEMENTS_MISSING.test(err.message)) {
45
+ throw new StatStatementsMissing();
46
+ }
47
+ throw err;
48
+ }
49
+ }
50
+
51
+ // Statements span lines and `to_char` pads numbers; a table cell wants neither.
52
+ // A statement is also cut short, or one query pushes every other column off
53
+ // screen; -o json keeps it whole.
54
+ const CELL_WIDTH = 60;
55
+ function cell(value) {
56
+ if (typeof value !== 'string') return value;
57
+ const text = value.replace(/\s+/g, ' ').trim();
58
+ return text.length > CELL_WIDTH ? `${text.slice(0, CELL_WIDTH - 1)}…` : text;
59
+ }
60
+
61
+ function dbCommand(name) {
62
+ const { name: canonical, query } = resolveQuery(name);
63
+ const replaced = canonical !== name;
64
+ return {
65
+ summary: replaced ? `Deprecated: use "${canonical}" instead` : query.summary,
66
+ usage: `motherbase inspect db ${name}`,
67
+ async run(ctx) {
68
+ const rows = await runQuery(ctx, query);
69
+ output(ctx, { query: canonical, rows }, (_d, ui) => {
70
+ if (replaced) ui.warn(`"inspect db ${name}" is deprecated: showing "${canonical}".`);
71
+ const display = rows.map(row => Object.fromEntries(Object.entries(row).map(([k, v]) => [k, cell(v)])));
72
+ ui.table(display, query.columns);
73
+ });
74
+ },
75
+ };
76
+ }
77
+
78
+ const db = {
79
+ summary: 'Inspect the database: bloat, locks, slow queries, index and table stats',
80
+ usage: `motherbase inspect db <${Object.keys(QUERIES).join('|')}>`,
81
+ subcommands: Object.fromEntries(
82
+ [...Object.keys(QUERIES), ...Object.keys(DEPRECATED)].map(name => [name, dbCommand(name)]),
83
+ ),
84
+ };
85
+
86
+ // Local-time YYYY-MM-DD: the report's folder, one per day as upstream does.
87
+ function dateFolder(now = new Date()) {
88
+ const pad = n => String(n).padStart(2, '0');
89
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
90
+ }
91
+
92
+ const report = {
93
+ summary: 'Write every inspect query to a CSV in a dated folder',
94
+ usage: 'motherbase inspect report [--output-dir <dir>]',
95
+ async run(ctx, _args, flags) {
96
+ const dir = path.resolve(ctx.cwd, String(flags['output-dir'] || '.'), dateFolder());
97
+ fs.mkdirSync(dir, { recursive: true });
98
+
99
+ const files = [];
100
+ const skipped = [];
101
+ // One at a time: fourteen catalog scans at once is a load spike on the
102
+ // database being diagnosed.
103
+ for (const { file, name, query } of REPORT) {
104
+ let rows;
105
+ try {
106
+ rows = await runQuery(ctx, query);
107
+ } catch (err) {
108
+ // A missing pg_stat_statements costs two files, not the whole report.
109
+ if (!(err instanceof StatStatementsMissing)) throw err;
110
+ skipped.push(name);
111
+ continue;
112
+ }
113
+ const target = path.join(dir, `${file}.csv`);
114
+ fs.writeFileSync(target, toCsv(query.columns, rows));
115
+ files.push(target);
116
+ }
117
+
118
+ output(ctx, { outputDir: dir, files, skipped }, (d, ui) => {
119
+ if (d.skipped.length) ui.warn(`Skipped ${d.skipped.join(', ')}: ${new StatStatementsMissing().message}`);
120
+ ui.success(`Saved ${d.files.length} CSV files to ${path.relative(ctx.cwd, d.outputDir) || '.'}`);
121
+ });
122
+ },
123
+ };
124
+
125
+ const subcommands = {
126
+ db,
127
+ report,
128
+ logs: {
129
+ summary: 'Recent server logs',
130
+ usage: 'motherbase inspect logs [--limit n] [--source api|rest|postgres|pooler|auth|storage|realtime|edge-function|cron|platform]',
131
+ async run(ctx, args, flags) {
132
+ // A Logs Explorer "source" is the server's `service` filter.
133
+ const query = flags.limit || flags.source ? { limit: flags.limit, service: flags.source } : undefined;
134
+ const data = await api.adminRequest(ctx, 'GET', '/logs', { query });
135
+ const logs = (data && data.logs) || [];
136
+ output(ctx, data, (_d, ui) =>
137
+ logs.forEach(l => ui.print(typeof l === 'string' ? l : `${l.level || ''} ${l.source || ''} ${l.message || JSON.stringify(l)}`)),
138
+ );
139
+ },
140
+ },
141
+ 'index-advisor': {
142
+ summary: 'Suggest indexes for a statement (hypopg + index_advisor)',
143
+ usage: 'motherbase inspect index-advisor --sql "<select ...>" | motherbase inspect index-advisor --enable',
144
+ async run(ctx, args, flags) {
145
+ if (flags.enable) {
146
+ const status = await api.adminRequest(ctx, 'POST', '/performance/index-advisor/enable');
147
+ output(ctx, status, (_d, ui) => ui.success('Index advisor enabled'));
148
+ return;
149
+ }
150
+ const query = flags.sql || flags.e;
151
+ if (typeof query !== 'string') throw new CliError('Provide the statement with --sql "..." (or --enable to install the advisor).');
152
+ const data = await api.adminRequest(ctx, 'POST', '/performance/index-advisor', { body: { query } });
153
+ output(ctx, data, (d, ui) => {
154
+ const statements = (d && d.indexStatements) || [];
155
+ if (!statements.length) ui.print('No index would help this statement.');
156
+ statements.forEach(s => ui.print(s));
157
+ if (d && d.improvementPct != null) ui.print(`Estimated cost drop: ${d.improvementPct}%`);
158
+ });
159
+ },
160
+ },
161
+ stats: simple('/stats', 'Stats overview'),
162
+ performance: simple('/performance', 'Performance metrics'),
163
+ requests: simple('/metrics/requests', 'Request metrics'),
164
+ advisor: simple('/advisor', 'Advisor findings'),
165
+ shield: simple('/shield', 'Security shield report'),
166
+ };
167
+
168
+ for (const [name, def] of Object.entries(subcommands)) {
169
+ if (!def.usage) def.usage = `motherbase inspect ${name}`;
170
+ }
171
+
172
+ module.exports = {
173
+ summary: 'Diagnostics: database inspection, report, logs, stats, index-advisor, advisor, shield',
174
+ usage: 'motherbase inspect <db|report|logs|stats|performance|index-advisor|requests|advisor|shield>',
175
+ subcommands,
176
+ dateFolder,
177
+ cell,
178
+ };
@@ -1,72 +1,72 @@
1
- 'use strict';
2
-
3
- // Project API keys. `secrets` next door is a different thing: those are values
4
- // an edge function reads, these are what a client presents to reach the project.
5
-
6
- const api = require('../api');
7
- const { output, requireArg } = require('./_util');
8
-
9
- const TYPES = ['publishable', 'secret'];
10
-
11
- const subcommands = {
12
- list: {
13
- summary: 'List project API keys',
14
- usage: 'motherbase keys list',
15
- async run(ctx) {
16
- const data = await api.adminRequest(ctx, 'GET', '/api-keys');
17
- const keys = (data && data.keys) || [];
18
- output(ctx, data, (_d, ui) =>
19
- ui.table(
20
- keys.map(k => ({
21
- name: k.name,
22
- type: k.type,
23
- // A secret key has no stored clear value; the prefix is all there is.
24
- key: k.token || `${k.prefix}…`,
25
- status: k.revokedAt ? 'revoked' : 'active',
26
- })),
27
- [
28
- { key: 'name', label: 'NAME' },
29
- { key: 'type', label: 'TYPE' },
30
- { key: 'key', label: 'KEY' },
31
- { key: 'status', label: 'STATUS' },
32
- ],
33
- ),
34
- );
35
- },
36
- },
37
- create: {
38
- summary: 'Create a key. A secret key is printed once and never again',
39
- usage: 'motherbase keys create <NAME> [--type publishable|secret]',
40
- async run(ctx, args, flags) {
41
- const name = requireArg(args[0], 'NAME');
42
- const type = flags.type || 'secret';
43
- if (!TYPES.includes(type)) {
44
- throw new Error(`--type must be one of: ${TYPES.join(', ')}`);
45
- }
46
- const data = await api.adminRequest(ctx, 'POST', '/api-keys', { body: { name, type } });
47
- output(ctx, data, (d, ui) => {
48
- ui.success(`Created ${type} key "${name}"`);
49
- ui.print(d.clearToken);
50
- if (type === 'secret') {
51
- ui.warn('Copy it now. It is stored hashed and cannot be shown again.');
52
- ui.warn('It bypasses row level security: keep it server side.');
53
- }
54
- });
55
- },
56
- },
57
- revoke: {
58
- summary: 'Revoke a key by id',
59
- usage: 'motherbase keys revoke <ID>',
60
- async run(ctx, args) {
61
- const id = requireArg(args[0], 'ID');
62
- const data = await api.adminRequest(ctx, 'DELETE', `/api-keys/${encodeURIComponent(id)}`);
63
- output(ctx, data, (d, ui) => ui.success(`Revoked key ${d.name || id}`));
64
- },
65
- },
66
- };
67
-
68
- module.exports = {
69
- summary: 'Project API keys (publishable and secret)',
70
- usage: 'motherbase keys <list|create|revoke> ...',
71
- subcommands,
72
- };
1
+ 'use strict';
2
+
3
+ // Project API keys. `secrets` next door is a different thing: those are values
4
+ // an edge function reads, these are what a client presents to reach the project.
5
+
6
+ const api = require('../api');
7
+ const { output, requireArg } = require('./_util');
8
+
9
+ const TYPES = ['publishable', 'secret'];
10
+
11
+ const subcommands = {
12
+ list: {
13
+ summary: 'List project API keys',
14
+ usage: 'motherbase keys list',
15
+ async run(ctx) {
16
+ const data = await api.adminRequest(ctx, 'GET', '/api-keys');
17
+ const keys = (data && data.keys) || [];
18
+ output(ctx, data, (_d, ui) =>
19
+ ui.table(
20
+ keys.map(k => ({
21
+ name: k.name,
22
+ type: k.type,
23
+ // A secret key has no stored clear value; the prefix is all there is.
24
+ key: k.token || `${k.prefix}…`,
25
+ status: k.revokedAt ? 'revoked' : 'active',
26
+ })),
27
+ [
28
+ { key: 'name', label: 'NAME' },
29
+ { key: 'type', label: 'TYPE' },
30
+ { key: 'key', label: 'KEY' },
31
+ { key: 'status', label: 'STATUS' },
32
+ ],
33
+ ),
34
+ );
35
+ },
36
+ },
37
+ create: {
38
+ summary: 'Create a key. A secret key is printed once and never again',
39
+ usage: 'motherbase keys create <NAME> [--type publishable|secret]',
40
+ async run(ctx, args, flags) {
41
+ const name = requireArg(args[0], 'NAME');
42
+ const type = flags.type || 'secret';
43
+ if (!TYPES.includes(type)) {
44
+ throw new Error(`--type must be one of: ${TYPES.join(', ')}`);
45
+ }
46
+ const data = await api.adminRequest(ctx, 'POST', '/api-keys', { body: { name, type } });
47
+ output(ctx, data, (d, ui) => {
48
+ ui.success(`Created ${type} key "${name}"`);
49
+ ui.print(d.clearToken);
50
+ if (type === 'secret') {
51
+ ui.warn('Copy it now. It is stored hashed and cannot be shown again.');
52
+ ui.warn('It bypasses row level security: keep it server side.');
53
+ }
54
+ });
55
+ },
56
+ },
57
+ revoke: {
58
+ summary: 'Revoke a key by id',
59
+ usage: 'motherbase keys revoke <ID>',
60
+ async run(ctx, args) {
61
+ const id = requireArg(args[0], 'ID');
62
+ const data = await api.adminRequest(ctx, 'DELETE', `/api-keys/${encodeURIComponent(id)}`);
63
+ output(ctx, data, (d, ui) => ui.success(`Revoked key ${d.name || id}`));
64
+ },
65
+ },
66
+ };
67
+
68
+ module.exports = {
69
+ summary: 'Project API keys (publishable and secret)',
70
+ usage: 'motherbase keys <list|create|revoke> ...',
71
+ subcommands,
72
+ };
@@ -1,35 +1,35 @@
1
- 'use strict';
2
-
3
- const api = require('../api');
4
- const config = require('../config');
5
- const prompt = require('../prompt');
6
- const ui = require('../ui');
7
- const { CliError } = require('../context');
8
-
9
- module.exports = {
10
- summary: 'Link the current directory to a project (writes ./.motherbase/config.json)',
11
- usage: 'motherbase link [projectNameOrId]',
12
- async run(ctx, args, flags) {
13
- const { consoleUrl } = ctx.requireConsole();
14
- const projects = await api.consoleRequest(ctx, 'GET', '/projects');
15
- const list = Array.isArray(projects) ? projects : [];
16
- if (list.length === 0) throw new CliError('No projects available on this console.');
17
-
18
- let picked = args[0];
19
- if (!picked) {
20
- if (flags.yes) throw new CliError('Specify a project name/id when using --yes.');
21
- ui.print(ui.color.bold('Available projects:'));
22
- list.forEach((p, i) => ui.print(` ${i + 1}. ${p.name || p.id}`));
23
- const answer = await prompt.question('Pick a project (number or name): ');
24
- const asNum = Number(answer);
25
- picked = Number.isInteger(asNum) && list[asNum - 1] ? (list[asNum - 1].name || list[asNum - 1].id) : answer;
26
- }
27
-
28
- const match = list.find(p => p.name === picked || p.id === picked);
29
- if (!match) throw new CliError(`Project "${picked}" not found on ${consoleUrl}.`);
30
-
31
- const projectId = match.name || match.id;
32
- config.writeLink(ctx.cwd, { consoleUrl, projectId, projectName: match.name || projectId });
33
- ui.success(`Linked ${ctx.cwd} → ${projectId} (${consoleUrl})`);
34
- },
35
- };
1
+ 'use strict';
2
+
3
+ const api = require('../api');
4
+ const config = require('../config');
5
+ const prompt = require('../prompt');
6
+ const ui = require('../ui');
7
+ const { CliError } = require('../context');
8
+
9
+ module.exports = {
10
+ summary: 'Link the current directory to a project (writes ./.motherbase/config.json)',
11
+ usage: 'motherbase link [projectNameOrId]',
12
+ async run(ctx, args, flags) {
13
+ const { consoleUrl } = ctx.requireConsole();
14
+ const projects = await api.consoleRequest(ctx, 'GET', '/projects');
15
+ const list = Array.isArray(projects) ? projects : [];
16
+ if (list.length === 0) throw new CliError('No projects available on this console.');
17
+
18
+ let picked = args[0];
19
+ if (!picked) {
20
+ if (flags.yes) throw new CliError('Specify a project name/id when using --yes.');
21
+ ui.print(ui.color.bold('Available projects:'));
22
+ list.forEach((p, i) => ui.print(` ${i + 1}. ${p.name || p.id}`));
23
+ const answer = await prompt.question('Pick a project (number or name): ');
24
+ const asNum = Number(answer);
25
+ picked = Number.isInteger(asNum) && list[asNum - 1] ? (list[asNum - 1].name || list[asNum - 1].id) : answer;
26
+ }
27
+
28
+ const match = list.find(p => p.name === picked || p.id === picked);
29
+ if (!match) throw new CliError(`Project "${picked}" not found on ${consoleUrl}.`);
30
+
31
+ const projectId = match.name || match.id;
32
+ config.writeLink(ctx.cwd, { consoleUrl, projectId, projectName: match.name || projectId });
33
+ ui.success(`Linked ${ctx.cwd} → ${projectId} (${consoleUrl})`);
34
+ },
35
+ };