@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
@@ -1,176 +1,176 @@
1
- 'use strict';
2
-
3
- const api = require('../api');
4
- const { output, requireArg } = require('./_util');
5
- const { CliError } = require('../context');
6
-
7
- const policy = {
8
- summary: 'Manage RLS policies',
9
- usage: 'motherbase rls policy create --table <t> --name <n> --command <SELECT|INSERT|UPDATE|DELETE|ALL> [--using <expr>] [--check <expr>] [--roles a,b] | motherbase rls policy delete <table> <name>',
10
- subcommands: {
11
- create: {
12
- summary: 'Create an RLS policy',
13
- usage: 'motherbase rls policy create --table <t> --name <n> --command <cmd> [--using <expr>] [--check <expr>] [--roles a,b]',
14
- async run(ctx, args, flags) {
15
- const body = {
16
- table: flags.table,
17
- name: flags.name,
18
- command: (flags.command || 'ALL').toUpperCase(),
19
- };
20
- if (!body.table || !body.name) throw new CliError('--table and --name are required');
21
- if (flags.using) body.using = flags.using;
22
- if (flags.check) body.check = flags.check;
23
- if (flags.roles) body.roles = String(flags.roles).split(',').map(s => s.trim()).filter(Boolean);
24
- const data = await api.adminRequest(ctx, 'POST', '/rls/policies', { body });
25
- output(ctx, data, (d, ui) => ui.success(`Created policy ${body.name} on ${body.table}`));
26
- },
27
- },
28
- delete: {
29
- summary: 'Delete an RLS policy',
30
- usage: 'motherbase rls policy delete <table> <name>',
31
- async run(ctx, args) {
32
- const table = requireArg(args[0], 'table');
33
- const name = requireArg(args[1], 'name');
34
- const data = await api.adminRequest(ctx, 'DELETE', '/rls/policies', { body: { table, name } });
35
- output(ctx, data, (d, ui) => ui.success(`Dropped policy ${name} on ${table}`));
36
- },
37
- },
38
- },
39
- };
40
-
41
- const subcommands = {
42
- list: {
43
- summary: 'List tables with RLS status and policies',
44
- usage: 'motherbase rls list',
45
- async run(ctx) {
46
- const data = await api.adminRequest(ctx, 'GET', '/rls/tables');
47
- output(ctx, data, (rows, ui) =>
48
- ui.table((Array.isArray(rows) ? rows : []).map(r => ({
49
- table: r.table,
50
- rls: r.rlsEnabled ? 'on' : 'off',
51
- policies: Array.isArray(r.policies) ? r.policies.length : 0,
52
- })), [
53
- { key: 'table', label: 'TABLE' },
54
- { key: 'rls', label: 'RLS' },
55
- { key: 'policies', label: 'POLICIES' },
56
- ]),
57
- );
58
- },
59
- },
60
- enable: {
61
- summary: 'Enable RLS on a table',
62
- usage: 'motherbase rls enable <table>',
63
- async run(ctx, args) {
64
- const table = requireArg(args[0], 'table');
65
- const data = await api.adminRequest(ctx, 'POST', `/rls/tables/${table}/rls`, { body: { enabled: true } });
66
- output(ctx, data, (d, ui) => ui.success(`RLS enabled on ${table}`));
67
- },
68
- },
69
- disable: {
70
- summary: 'Disable RLS on a table',
71
- usage: 'motherbase rls disable <table>',
72
- async run(ctx, args) {
73
- const table = requireArg(args[0], 'table');
74
- const data = await api.adminRequest(ctx, 'POST', `/rls/tables/${table}/rls`, { body: { enabled: false } });
75
- output(ctx, data, (d, ui) => ui.success(`RLS disabled on ${table}`));
76
- },
77
- },
78
- policy,
79
- preset: {
80
- summary: 'Apply a policy preset to a table',
81
- usage:
82
- 'motherbase rls preset <table> <starter|public_crud|authenticated_crud|admin_only|blocked|owner_only|org_scoped|public_read_owner_write|soft_delete> [--owner-column c] [--org-column c] [--org-claim c] [--deleted-column c] [--replace]',
83
- async run(ctx, args, flags) {
84
- const table = requireArg(args[0], 'table');
85
- const preset = requireArg(args[1], 'preset');
86
- const body = { preset };
87
- if (flags['owner-column']) body.ownerColumn = flags['owner-column'];
88
- if (flags['org-column']) body.orgColumn = flags['org-column'];
89
- if (flags['org-claim']) body.orgClaim = flags['org-claim'];
90
- if (flags['deleted-column']) body.deletedColumn = flags['deleted-column'];
91
- if (flags.replace) body.replace = true;
92
- const data = await api.adminRequest(ctx, 'POST', `/rls/tables/${table}/preset`, { body });
93
- output(ctx, data, (d, ui) =>
94
- ui.success(`Applied ${preset} to ${table}: ${(d && d.policies ? d.policies : []).join(', ')}`)
95
- );
96
- },
97
- },
98
- simulate: {
99
- summary: 'Run a query as a given caller and show what RLS lets through',
100
- usage:
101
- 'motherbase rls simulate --sql "select * from t" [--as anon|authenticated|<userId>] [--claims \'{"org_id":"x"}\'] [--limit n]',
102
- async run(ctx, args, flags) {
103
- const sql = flags.sql || args.join(' ');
104
- if (!sql) throw new CliError('--sql is required');
105
-
106
- // `--as` takes a role or a user id. A value that is not one of the two
107
- // roles is treated as a user id, which is what makes "as this person"
108
- // spellable without looking their claims up by hand.
109
- const as = flags.as || 'anon';
110
- const body = { sql };
111
- if (as === 'anon' || as === 'authenticated') body.role = as;
112
- else body.userId = as;
113
- if (flags.claims) {
114
- try {
115
- body.claims = JSON.parse(flags.claims);
116
- } catch {
117
- throw new CliError('--claims must be a JSON object');
118
- }
119
- }
120
- if (flags.limit) body.limit = Number(flags.limit);
121
-
122
- const data = await api.adminRequest(ctx, 'POST', '/rls/simulate', { body });
123
- output(ctx, data, (d, ui) => {
124
- if (d.denied) {
125
- ui.warn(`Refused by Postgres: ${d.denied.message}`);
126
- }
127
- if (d.rows && d.rows.length) {
128
- ui.table(d.rows, (d.columns || []).map(key => ({ key, label: key.toUpperCase() })));
129
- } else {
130
- ui.info('0 rows');
131
- }
132
- if (d.truncated) ui.info(`Truncated at ${d.rowCount} rows.`);
133
- for (const t of d.tables || []) {
134
- const reason =
135
- t.deniedBy === 'grant'
136
- ? 'no privilege for this role'
137
- : t.deniedBy === 'policy'
138
- ? 'RLS on, no policy grants this role read access'
139
- : null;
140
- ui.info(`${t.schema}.${t.table}: RLS ${t.rlsEnabled ? 'on' : 'off'}${reason ? ` (${reason})` : ''}`);
141
- for (const p of t.policies || []) {
142
- ui.info(` ${p.applies ? '*' : ' '} ${p.name} ${p.command} to ${p.roles.join(',')}: ${p.using || 'true'}`);
143
- }
144
- }
145
- });
146
- },
147
- },
148
- lint: {
149
- summary: 'Run the security and performance advisor',
150
- usage: 'motherbase rls lint [--level critical|warning|info]',
151
- async run(ctx, args, flags) {
152
- const data = await api.adminRequest(ctx, 'GET', '/advisor');
153
- const findings = (data && data.findings ? data.findings : []).filter(
154
- f => !flags.level || f.level === flags.level
155
- );
156
- output(ctx, { ...data, findings }, (d, ui) => {
157
- if (!findings.length) return ui.success('No findings.');
158
- ui.table(
159
- findings.map(f => ({ level: f.level, id: f.id, target: f.target, title: f.title })),
160
- [
161
- { key: 'level', label: 'LEVEL' },
162
- { key: 'id', label: 'RULE' },
163
- { key: 'target', label: 'TARGET' },
164
- { key: 'title', label: 'FINDING' },
165
- ]
166
- );
167
- });
168
- },
169
- },
170
- };
171
-
172
- module.exports = {
173
- summary: 'Row-level security (tables, policies, presets, simulator, lint)',
174
- usage: 'motherbase rls <list|enable|disable|policy|preset|simulate|lint> ...',
175
- subcommands,
176
- };
1
+ 'use strict';
2
+
3
+ const api = require('../api');
4
+ const { output, requireArg } = require('./_util');
5
+ const { CliError } = require('../context');
6
+
7
+ const policy = {
8
+ summary: 'Manage RLS policies',
9
+ usage: 'motherbase rls policy create --table <t> --name <n> --command <SELECT|INSERT|UPDATE|DELETE|ALL> [--using <expr>] [--check <expr>] [--roles a,b] | motherbase rls policy delete <table> <name>',
10
+ subcommands: {
11
+ create: {
12
+ summary: 'Create an RLS policy',
13
+ usage: 'motherbase rls policy create --table <t> --name <n> --command <cmd> [--using <expr>] [--check <expr>] [--roles a,b]',
14
+ async run(ctx, args, flags) {
15
+ const body = {
16
+ table: flags.table,
17
+ name: flags.name,
18
+ command: (flags.command || 'ALL').toUpperCase(),
19
+ };
20
+ if (!body.table || !body.name) throw new CliError('--table and --name are required');
21
+ if (flags.using) body.using = flags.using;
22
+ if (flags.check) body.check = flags.check;
23
+ if (flags.roles) body.roles = String(flags.roles).split(',').map(s => s.trim()).filter(Boolean);
24
+ const data = await api.adminRequest(ctx, 'POST', '/rls/policies', { body });
25
+ output(ctx, data, (d, ui) => ui.success(`Created policy ${body.name} on ${body.table}`));
26
+ },
27
+ },
28
+ delete: {
29
+ summary: 'Delete an RLS policy',
30
+ usage: 'motherbase rls policy delete <table> <name>',
31
+ async run(ctx, args) {
32
+ const table = requireArg(args[0], 'table');
33
+ const name = requireArg(args[1], 'name');
34
+ const data = await api.adminRequest(ctx, 'DELETE', '/rls/policies', { body: { table, name } });
35
+ output(ctx, data, (d, ui) => ui.success(`Dropped policy ${name} on ${table}`));
36
+ },
37
+ },
38
+ },
39
+ };
40
+
41
+ const subcommands = {
42
+ list: {
43
+ summary: 'List tables with RLS status and policies',
44
+ usage: 'motherbase rls list',
45
+ async run(ctx) {
46
+ const data = await api.adminRequest(ctx, 'GET', '/rls/tables');
47
+ output(ctx, data, (rows, ui) =>
48
+ ui.table((Array.isArray(rows) ? rows : []).map(r => ({
49
+ table: r.table,
50
+ rls: r.rlsEnabled ? 'on' : 'off',
51
+ policies: Array.isArray(r.policies) ? r.policies.length : 0,
52
+ })), [
53
+ { key: 'table', label: 'TABLE' },
54
+ { key: 'rls', label: 'RLS' },
55
+ { key: 'policies', label: 'POLICIES' },
56
+ ]),
57
+ );
58
+ },
59
+ },
60
+ enable: {
61
+ summary: 'Enable RLS on a table',
62
+ usage: 'motherbase rls enable <table>',
63
+ async run(ctx, args) {
64
+ const table = requireArg(args[0], 'table');
65
+ const data = await api.adminRequest(ctx, 'POST', `/rls/tables/${table}/rls`, { body: { enabled: true } });
66
+ output(ctx, data, (d, ui) => ui.success(`RLS enabled on ${table}`));
67
+ },
68
+ },
69
+ disable: {
70
+ summary: 'Disable RLS on a table',
71
+ usage: 'motherbase rls disable <table>',
72
+ async run(ctx, args) {
73
+ const table = requireArg(args[0], 'table');
74
+ const data = await api.adminRequest(ctx, 'POST', `/rls/tables/${table}/rls`, { body: { enabled: false } });
75
+ output(ctx, data, (d, ui) => ui.success(`RLS disabled on ${table}`));
76
+ },
77
+ },
78
+ policy,
79
+ preset: {
80
+ summary: 'Apply a policy preset to a table',
81
+ usage:
82
+ 'motherbase rls preset <table> <starter|public_crud|authenticated_crud|admin_only|blocked|owner_only|org_scoped|public_read_owner_write|soft_delete> [--owner-column c] [--org-column c] [--org-claim c] [--deleted-column c] [--replace]',
83
+ async run(ctx, args, flags) {
84
+ const table = requireArg(args[0], 'table');
85
+ const preset = requireArg(args[1], 'preset');
86
+ const body = { preset };
87
+ if (flags['owner-column']) body.ownerColumn = flags['owner-column'];
88
+ if (flags['org-column']) body.orgColumn = flags['org-column'];
89
+ if (flags['org-claim']) body.orgClaim = flags['org-claim'];
90
+ if (flags['deleted-column']) body.deletedColumn = flags['deleted-column'];
91
+ if (flags.replace) body.replace = true;
92
+ const data = await api.adminRequest(ctx, 'POST', `/rls/tables/${table}/preset`, { body });
93
+ output(ctx, data, (d, ui) =>
94
+ ui.success(`Applied ${preset} to ${table}: ${(d && d.policies ? d.policies : []).join(', ')}`)
95
+ );
96
+ },
97
+ },
98
+ simulate: {
99
+ summary: 'Run a query as a given caller and show what RLS lets through',
100
+ usage:
101
+ 'motherbase rls simulate --sql "select * from t" [--as anon|authenticated|<userId>] [--claims \'{"org_id":"x"}\'] [--limit n]',
102
+ async run(ctx, args, flags) {
103
+ const sql = flags.sql || args.join(' ');
104
+ if (!sql) throw new CliError('--sql is required');
105
+
106
+ // `--as` takes a role or a user id. A value that is not one of the two
107
+ // roles is treated as a user id, which is what makes "as this person"
108
+ // spellable without looking their claims up by hand.
109
+ const as = flags.as || 'anon';
110
+ const body = { sql };
111
+ if (as === 'anon' || as === 'authenticated') body.role = as;
112
+ else body.userId = as;
113
+ if (flags.claims) {
114
+ try {
115
+ body.claims = JSON.parse(flags.claims);
116
+ } catch {
117
+ throw new CliError('--claims must be a JSON object');
118
+ }
119
+ }
120
+ if (flags.limit) body.limit = Number(flags.limit);
121
+
122
+ const data = await api.adminRequest(ctx, 'POST', '/rls/simulate', { body });
123
+ output(ctx, data, (d, ui) => {
124
+ if (d.denied) {
125
+ ui.warn(`Refused by Postgres: ${d.denied.message}`);
126
+ }
127
+ if (d.rows && d.rows.length) {
128
+ ui.table(d.rows, (d.columns || []).map(key => ({ key, label: key.toUpperCase() })));
129
+ } else {
130
+ ui.info('0 rows');
131
+ }
132
+ if (d.truncated) ui.info(`Truncated at ${d.rowCount} rows.`);
133
+ for (const t of d.tables || []) {
134
+ const reason =
135
+ t.deniedBy === 'grant'
136
+ ? 'no privilege for this role'
137
+ : t.deniedBy === 'policy'
138
+ ? 'RLS on, no policy grants this role read access'
139
+ : null;
140
+ ui.info(`${t.schema}.${t.table}: RLS ${t.rlsEnabled ? 'on' : 'off'}${reason ? ` (${reason})` : ''}`);
141
+ for (const p of t.policies || []) {
142
+ ui.info(` ${p.applies ? '*' : ' '} ${p.name} ${p.command} to ${p.roles.join(',')}: ${p.using || 'true'}`);
143
+ }
144
+ }
145
+ });
146
+ },
147
+ },
148
+ lint: {
149
+ summary: 'Run the security and performance advisor',
150
+ usage: 'motherbase rls lint [--level critical|warning|info]',
151
+ async run(ctx, args, flags) {
152
+ const data = await api.adminRequest(ctx, 'GET', '/advisor');
153
+ const findings = (data && data.findings ? data.findings : []).filter(
154
+ f => !flags.level || f.level === flags.level
155
+ );
156
+ output(ctx, { ...data, findings }, (d, ui) => {
157
+ if (!findings.length) return ui.success('No findings.');
158
+ ui.table(
159
+ findings.map(f => ({ level: f.level, id: f.id, target: f.target, title: f.title })),
160
+ [
161
+ { key: 'level', label: 'LEVEL' },
162
+ { key: 'id', label: 'RULE' },
163
+ { key: 'target', label: 'TARGET' },
164
+ { key: 'title', label: 'FINDING' },
165
+ ]
166
+ );
167
+ });
168
+ },
169
+ },
170
+ };
171
+
172
+ module.exports = {
173
+ summary: 'Row-level security (tables, policies, presets, simulator, lint)',
174
+ usage: 'motherbase rls <list|enable|disable|policy|preset|simulate|lint> ...',
175
+ subcommands,
176
+ };
@@ -1,66 +1,66 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const api = require('../api');
6
- const { output, requireArg, parseKeyValues, parseDotenv } = require('./_util');
7
- const { CliError } = require('../context');
8
-
9
- // `NAME=VALUE ...` plus `--env-file`, as the Supabase CLI takes them. The older
10
- // `set NAME value` form keeps working. Positional pairs win over the file.
11
- // Node itself pre-scans argv for `--env-file` and exits (code 9) when the path
12
- // does not exist relative to the shell's cwd, before this code runs; with
13
- // `--workdir`, pass an absolute path.
14
- function collectSecrets(cwd, args, flags) {
15
- const fromFile = flags['env-file']
16
- ? parseDotenv(fs.readFileSync(path.resolve(cwd, String(flags['env-file'])), 'utf8'))
17
- : {};
18
- const legacy = args.length === 2 && !args[0].includes('=');
19
- const fromArgs = legacy ? { [args[0]]: args[1] } : parseKeyValues(args, { coerce: false });
20
- return { ...fromFile, ...fromArgs };
21
- }
22
-
23
- const subcommands = {
24
- list: {
25
- summary: 'List edge secrets (names only)',
26
- usage: 'motherbase secrets list',
27
- async run(ctx) {
28
- const data = await api.adminRequest(ctx, 'GET', '/edge-secrets');
29
- const secrets = (data && data.secrets) || data || [];
30
- output(ctx, data, (_d, ui) =>
31
- ui.table((Array.isArray(secrets) ? secrets : []).map(s => (typeof s === 'string' ? { name: s } : s)), [
32
- { key: 'name', label: 'NAME' },
33
- ]),
34
- );
35
- },
36
- },
37
- set: {
38
- summary: 'Set one or more secrets',
39
- usage: 'motherbase secrets set NAME=VALUE [NAME=VALUE ...] [--env-file .env]',
40
- async run(ctx, args, flags) {
41
- const secrets = collectSecrets(ctx.cwd, args, flags);
42
- const names = Object.keys(secrets);
43
- if (!names.length) throw new CliError('Nothing to set. Pass NAME=VALUE pairs or --env-file <path>.');
44
- // The server sets one secret per request.
45
- for (const name of names) {
46
- await api.adminRequest(ctx, 'PUT', '/edge-secrets', { body: { name, value: secrets[name] } });
47
- }
48
- output(ctx, { set: names }, (d, ui) => ui.success(`Set ${names.length === 1 ? 'secret' : 'secrets'} ${names.join(', ')}`));
49
- },
50
- },
51
- unset: {
52
- summary: 'Remove a secret',
53
- usage: 'motherbase secrets unset <NAME>',
54
- async run(ctx, args) {
55
- const name = requireArg(args[0], 'NAME');
56
- const data = await api.adminRequest(ctx, 'DELETE', `/edge-secrets/${encodeURIComponent(name)}`);
57
- output(ctx, data, (d, ui) => ui.success(`Removed secret ${name}`));
58
- },
59
- },
60
- };
61
-
62
- module.exports = {
63
- summary: 'Edge function secrets',
64
- usage: 'motherbase secrets <list|set|unset> ...',
65
- subcommands,
66
- };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const api = require('../api');
6
+ const { output, requireArg, parseKeyValues, parseDotenv } = require('./_util');
7
+ const { CliError } = require('../context');
8
+
9
+ // `NAME=VALUE ...` plus `--env-file`, as the Supabase CLI takes them. The older
10
+ // `set NAME value` form keeps working. Positional pairs win over the file.
11
+ // Node itself pre-scans argv for `--env-file` and exits (code 9) when the path
12
+ // does not exist relative to the shell's cwd, before this code runs; with
13
+ // `--workdir`, pass an absolute path.
14
+ function collectSecrets(cwd, args, flags) {
15
+ const fromFile = flags['env-file']
16
+ ? parseDotenv(fs.readFileSync(path.resolve(cwd, String(flags['env-file'])), 'utf8'))
17
+ : {};
18
+ const legacy = args.length === 2 && !args[0].includes('=');
19
+ const fromArgs = legacy ? { [args[0]]: args[1] } : parseKeyValues(args, { coerce: false });
20
+ return { ...fromFile, ...fromArgs };
21
+ }
22
+
23
+ const subcommands = {
24
+ list: {
25
+ summary: 'List edge secrets (names only)',
26
+ usage: 'motherbase secrets list',
27
+ async run(ctx) {
28
+ const data = await api.adminRequest(ctx, 'GET', '/edge-secrets');
29
+ const secrets = (data && data.secrets) || data || [];
30
+ output(ctx, data, (_d, ui) =>
31
+ ui.table((Array.isArray(secrets) ? secrets : []).map(s => (typeof s === 'string' ? { name: s } : s)), [
32
+ { key: 'name', label: 'NAME' },
33
+ ]),
34
+ );
35
+ },
36
+ },
37
+ set: {
38
+ summary: 'Set one or more secrets',
39
+ usage: 'motherbase secrets set NAME=VALUE [NAME=VALUE ...] [--env-file .env]',
40
+ async run(ctx, args, flags) {
41
+ const secrets = collectSecrets(ctx.cwd, args, flags);
42
+ const names = Object.keys(secrets);
43
+ if (!names.length) throw new CliError('Nothing to set. Pass NAME=VALUE pairs or --env-file <path>.');
44
+ // The server sets one secret per request.
45
+ for (const name of names) {
46
+ await api.adminRequest(ctx, 'PUT', '/edge-secrets', { body: { name, value: secrets[name] } });
47
+ }
48
+ output(ctx, { set: names }, (d, ui) => ui.success(`Set ${names.length === 1 ? 'secret' : 'secrets'} ${names.join(', ')}`));
49
+ },
50
+ },
51
+ unset: {
52
+ summary: 'Remove a secret',
53
+ usage: 'motherbase secrets unset <NAME>',
54
+ async run(ctx, args) {
55
+ const name = requireArg(args[0], 'NAME');
56
+ const data = await api.adminRequest(ctx, 'DELETE', `/edge-secrets/${encodeURIComponent(name)}`);
57
+ output(ctx, data, (d, ui) => ui.success(`Removed secret ${name}`));
58
+ },
59
+ },
60
+ };
61
+
62
+ module.exports = {
63
+ summary: 'Edge function secrets',
64
+ usage: 'motherbase secrets <list|set|unset> ...',
65
+ subcommands,
66
+ };