@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,233 +1,233 @@
1
- 'use strict';
2
-
3
- // The Supabase MCP server's account and project tools, for what Motherbase
4
- // already serves. Same names and arguments where they fit, so an agent that
5
- // knows Supabase's tools can use these without learning new ones.
6
- //
7
- // Two scopes, because a session may have either or both:
8
- // - console tools (organizations, projects) go through `api.consoleRequest`
9
- // and need a console login, not a linked project;
10
- // - project tools act on the linked (or direct-mode) project through
11
- // `api.adminRequest`, like every tool in tools.js.
12
- //
13
- // Not here, because there is nothing behind them yet: branching, cost
14
- // confirmation, docs search, and Supabase's per-call `project_id` on project
15
- // tools (a session serves the one project it was started for).
16
-
17
- const api = require('../api');
18
- const { upsertFunction } = require('../commands/functions');
19
-
20
- const str = description => ({ type: 'string', description });
21
- const noArgs = { type: 'object', properties: {} };
22
- const enc = encodeURIComponent;
23
-
24
- const FUNCTION_NAME = /^[a-z0-9][a-z0-9-]*$/;
25
-
26
- /** A project from the console listing, by name or id; the listing is the only read. */
27
- async function findProject(ctx, id) {
28
- const projects = await api.consoleRequest(ctx, 'GET', '/projects');
29
- const project = (Array.isArray(projects) ? projects : []).find(p => p.name === id || p.id === id);
30
- if (!project) throw new Error(`No project "${id}" on this console. list_projects shows the ones you can see.`);
31
- return project;
32
- }
33
-
34
- /** An edge function by the slug Supabase's tools take: its route, name or id. */
35
- async function findFunction(ctx, slug) {
36
- const listing = await api.adminRequest(ctx, 'GET', '/edge-functions');
37
- const fn = ((listing && listing.functions) || []).find(f => f.route === slug || f.name === slug || f.id === slug);
38
- if (!fn) throw new Error(`No edge function "${slug}". list_edge_functions shows what is deployed.`);
39
- return fn;
40
- }
41
-
42
- /**
43
- * The single source file a function deploys as. Supabase's tool takes a file
44
- * set; Motherbase stores one module per function, so a set that needs more than
45
- * its entrypoint is refused rather than deployed half-complete.
46
- */
47
- function entrypointSource(files, entrypoint) {
48
- if (!Array.isArray(files) || !files.length) throw new Error('files must hold at least the entrypoint.');
49
- const wanted = entrypoint || 'index.ts';
50
- // A lone file is the entrypoint whatever it is called, unless one was named.
51
- const entry = files.find(f => f && f.name === wanted) || (!entrypoint && files.length === 1 ? files[0] : undefined);
52
- if (!entry) throw new Error(`No file named ${wanted} among files.`);
53
- if (files.length > 1) {
54
- throw new Error('Edge functions deploy as a single module here: inline the other files into the entrypoint.');
55
- }
56
- return String(entry.content);
57
- }
58
-
59
- /** Account-level reads. Need a console login. */
60
- function consoleReadTools(ctx) {
61
- return [
62
- {
63
- name: 'list_organizations',
64
- description: 'List the organizations the logged-in operator belongs to, with their role and project count.',
65
- inputSchema: noArgs,
66
- run: () => api.consoleRequest(ctx, 'GET', '/orgs'),
67
- },
68
- {
69
- name: 'get_organization',
70
- description: 'Details of one organization: name, members count and settings.',
71
- inputSchema: { type: 'object', properties: { id: str('Organization id.') }, required: ['id'] },
72
- run: args => api.consoleRequest(ctx, 'GET', `/orgs/${enc(args.id)}`),
73
- },
74
- {
75
- name: 'list_projects',
76
- description: 'List the projects on this console the operator can see, with their status and domain.',
77
- inputSchema: {
78
- type: 'object',
79
- properties: { organization_id: str('Only the projects of this organization.') },
80
- },
81
- run: args => api.consoleRequest(ctx, 'GET', '/projects', { query: { org: args.organization_id } }),
82
- },
83
- {
84
- name: 'get_project',
85
- description: 'Details of one project: status, domain and organization.',
86
- inputSchema: { type: 'object', properties: { id: str('Project name or id.') }, required: ['id'] },
87
- run: args => findProject(ctx, args.id),
88
- },
89
- ];
90
- }
91
-
92
- /** Account-level changes. Only with `--write`. */
93
- function consoleWriteTools(ctx) {
94
- return [
95
- {
96
- name: 'create_project',
97
- description:
98
- 'Create a project: a new, isolated stack (database, auth, storage, functions). Provisioning takes a while; poll get_project for its status.',
99
- inputSchema: {
100
- type: 'object',
101
- properties: {
102
- name: str('Project name: lowercase letters, digits and hyphens. Also its id.'),
103
- organization_id: str('Organization to create it in. Defaults to the default organization.'),
104
- domain: str('Domain it is served on. Required unless the console runs in dev mode.'),
105
- },
106
- required: ['name'],
107
- },
108
- run: args =>
109
- api.consoleRequest(ctx, 'POST', '/projects', {
110
- body: { name: args.name, domain: args.domain, orgId: args.organization_id },
111
- }),
112
- },
113
- {
114
- name: 'pause_project',
115
- description: 'Stop a project\'s containers. Its data is kept; restore_project starts it again.',
116
- inputSchema: { type: 'object', properties: { project_id: str('Project name or id.') }, required: ['project_id'] },
117
- run: args => api.consoleRequest(ctx, 'POST', `/projects/${enc(args.project_id)}/stop`),
118
- },
119
- {
120
- name: 'restore_project',
121
- description: 'Start a paused project again.',
122
- inputSchema: { type: 'object', properties: { project_id: str('Project name or id.') }, required: ['project_id'] },
123
- run: args => api.consoleRequest(ctx, 'POST', `/projects/${enc(args.project_id)}/start`),
124
- },
125
- ];
126
- }
127
-
128
- /** Reads on the served project. */
129
- function projectReadTools(ctx) {
130
- return [
131
- {
132
- name: 'get_project_url',
133
- description: 'The API URL (and realtime URL) a client app uses to reach this project.',
134
- inputSchema: noArgs,
135
- run: async () => {
136
- const info = await api.adminRequest(ctx, 'GET', '/connect-info');
137
- return { url: info.apiUrl, realtimeUrl: info.wsUrl };
138
- },
139
- },
140
- {
141
- name: 'get_publishable_keys',
142
- description:
143
- 'The publishable API keys a client app ships with. Safe to embed: row level security decides what they reach. Secret keys are never returned.',
144
- inputSchema: noArgs,
145
- run: async () => {
146
- const data = await api.adminRequest(ctx, 'GET', '/api-keys');
147
- const keys = ((data && data.keys) || [])
148
- .filter(k => k.type === 'publishable' && !k.revokedAt)
149
- .map(k => ({ name: k.name, api_key: k.token }));
150
- return { keys };
151
- },
152
- },
153
- {
154
- name: 'list_extensions',
155
- description: 'Postgres extensions available in the project database, and which are installed.',
156
- inputSchema: noArgs,
157
- run: () => api.adminRequest(ctx, 'GET', '/schema/extensions'),
158
- },
159
- {
160
- name: 'list_migrations',
161
- description: 'Schema migrations recorded for the project, with their status and when they were applied.',
162
- inputSchema: noArgs,
163
- run: () => api.adminRequest(ctx, 'GET', '/migrations'),
164
- },
165
- {
166
- name: 'list_edge_functions',
167
- description: 'Deployed edge functions: name, route, whether enabled, and limits. get_edge_function returns the source.',
168
- inputSchema: noArgs,
169
- run: async () => {
170
- const data = await api.adminRequest(ctx, 'GET', '/edge-functions');
171
- // The source of every function is noise in a listing; get_edge_function has it.
172
- const functions = ((data && data.functions) || []).map(({ code: _code, ...meta }) => meta);
173
- return { functions };
174
- },
175
- },
176
- {
177
- name: 'get_edge_function',
178
- description: 'One deployed edge function with its source code.',
179
- inputSchema: {
180
- type: 'object',
181
- properties: { function_slug: str('The function\'s route (also accepts its name or id).') },
182
- required: ['function_slug'],
183
- },
184
- run: async args => {
185
- const fn = await findFunction(ctx, args.function_slug);
186
- return api.adminRequest(ctx, 'GET', `/edge-functions/${enc(fn.id)}`);
187
- },
188
- },
189
- {
190
- name: 'list_storage_buckets',
191
- description: 'Storage buckets with their visibility, quota and usage.',
192
- inputSchema: noArgs,
193
- run: () => api.adminRequest(ctx, 'GET', '/storage/buckets'),
194
- },
195
- ];
196
- }
197
-
198
- /** Changes to the served project. Only with `--write`. */
199
- function projectWriteTools(ctx) {
200
- return [
201
- {
202
- name: 'deploy_edge_function',
203
- description:
204
- 'Deploy an edge function, creating it or replacing the one at the same route. The name is also its route. The code runs on Deno with the Web platform; export a default handler (req: Request) => Response.',
205
- inputSchema: {
206
- type: 'object',
207
- properties: {
208
- name: str('Function name: lowercase letters, digits and hyphens.'),
209
- entrypoint_path: str('Which of files is the entrypoint. Defaults to index.ts.'),
210
- files: {
211
- type: 'array',
212
- description: 'The function source: its single entrypoint module.',
213
- items: {
214
- type: 'object',
215
- properties: { name: str('File name, e.g. index.ts.'), content: str('File contents.') },
216
- required: ['name', 'content'],
217
- },
218
- },
219
- },
220
- required: ['name', 'files'],
221
- },
222
- run: args => {
223
- if (!FUNCTION_NAME.test(args.name || '')) {
224
- throw new Error('Function names are lowercase letters, digits and hyphens (the name is also the route).');
225
- }
226
- const code = entrypointSource(args.files, args.entrypoint_path);
227
- return upsertFunction(ctx, { name: args.name, route: args.name, code });
228
- },
229
- },
230
- ];
231
- }
232
-
233
- module.exports = { consoleReadTools, consoleWriteTools, projectReadTools, projectWriteTools, entrypointSource };
1
+ 'use strict';
2
+
3
+ // The Supabase MCP server's account and project tools, for what Motherbase
4
+ // already serves. Same names and arguments where they fit, so an agent that
5
+ // knows Supabase's tools can use these without learning new ones.
6
+ //
7
+ // Two scopes, because a session may have either or both:
8
+ // - console tools (organizations, projects) go through `api.consoleRequest`
9
+ // and need a console login, not a linked project;
10
+ // - project tools act on the linked (or direct-mode) project through
11
+ // `api.adminRequest`, like every tool in tools.js.
12
+ //
13
+ // Not here, because there is nothing behind them yet: branching, cost
14
+ // confirmation, docs search, and Supabase's per-call `project_id` on project
15
+ // tools (a session serves the one project it was started for).
16
+
17
+ const api = require('../api');
18
+ const { upsertFunction } = require('../commands/functions');
19
+
20
+ const str = description => ({ type: 'string', description });
21
+ const noArgs = { type: 'object', properties: {} };
22
+ const enc = encodeURIComponent;
23
+
24
+ const FUNCTION_NAME = /^[a-z0-9][a-z0-9-]*$/;
25
+
26
+ /** A project from the console listing, by name or id; the listing is the only read. */
27
+ async function findProject(ctx, id) {
28
+ const projects = await api.consoleRequest(ctx, 'GET', '/projects');
29
+ const project = (Array.isArray(projects) ? projects : []).find(p => p.name === id || p.id === id);
30
+ if (!project) throw new Error(`No project "${id}" on this console. list_projects shows the ones you can see.`);
31
+ return project;
32
+ }
33
+
34
+ /** An edge function by the slug Supabase's tools take: its route, name or id. */
35
+ async function findFunction(ctx, slug) {
36
+ const listing = await api.adminRequest(ctx, 'GET', '/edge-functions');
37
+ const fn = ((listing && listing.functions) || []).find(f => f.route === slug || f.name === slug || f.id === slug);
38
+ if (!fn) throw new Error(`No edge function "${slug}". list_edge_functions shows what is deployed.`);
39
+ return fn;
40
+ }
41
+
42
+ /**
43
+ * The single source file a function deploys as. Supabase's tool takes a file
44
+ * set; Motherbase stores one module per function, so a set that needs more than
45
+ * its entrypoint is refused rather than deployed half-complete.
46
+ */
47
+ function entrypointSource(files, entrypoint) {
48
+ if (!Array.isArray(files) || !files.length) throw new Error('files must hold at least the entrypoint.');
49
+ const wanted = entrypoint || 'index.ts';
50
+ // A lone file is the entrypoint whatever it is called, unless one was named.
51
+ const entry = files.find(f => f && f.name === wanted) || (!entrypoint && files.length === 1 ? files[0] : undefined);
52
+ if (!entry) throw new Error(`No file named ${wanted} among files.`);
53
+ if (files.length > 1) {
54
+ throw new Error('Edge functions deploy as a single module here: inline the other files into the entrypoint.');
55
+ }
56
+ return String(entry.content);
57
+ }
58
+
59
+ /** Account-level reads. Need a console login. */
60
+ function consoleReadTools(ctx) {
61
+ return [
62
+ {
63
+ name: 'list_organizations',
64
+ description: 'List the organizations the logged-in operator belongs to, with their role and project count.',
65
+ inputSchema: noArgs,
66
+ run: () => api.consoleRequest(ctx, 'GET', '/orgs'),
67
+ },
68
+ {
69
+ name: 'get_organization',
70
+ description: 'Details of one organization: name, members count and settings.',
71
+ inputSchema: { type: 'object', properties: { id: str('Organization id.') }, required: ['id'] },
72
+ run: args => api.consoleRequest(ctx, 'GET', `/orgs/${enc(args.id)}`),
73
+ },
74
+ {
75
+ name: 'list_projects',
76
+ description: 'List the projects on this console the operator can see, with their status and domain.',
77
+ inputSchema: {
78
+ type: 'object',
79
+ properties: { organization_id: str('Only the projects of this organization.') },
80
+ },
81
+ run: args => api.consoleRequest(ctx, 'GET', '/projects', { query: { org: args.organization_id } }),
82
+ },
83
+ {
84
+ name: 'get_project',
85
+ description: 'Details of one project: status, domain and organization.',
86
+ inputSchema: { type: 'object', properties: { id: str('Project name or id.') }, required: ['id'] },
87
+ run: args => findProject(ctx, args.id),
88
+ },
89
+ ];
90
+ }
91
+
92
+ /** Account-level changes. Only with `--write`. */
93
+ function consoleWriteTools(ctx) {
94
+ return [
95
+ {
96
+ name: 'create_project',
97
+ description:
98
+ 'Create a project: a new, isolated stack (database, auth, storage, functions). Provisioning takes a while; poll get_project for its status.',
99
+ inputSchema: {
100
+ type: 'object',
101
+ properties: {
102
+ name: str('Project name: lowercase letters, digits and hyphens. Also its id.'),
103
+ organization_id: str('Organization to create it in. Defaults to the default organization.'),
104
+ domain: str('Domain it is served on. Required unless the console runs in dev mode.'),
105
+ },
106
+ required: ['name'],
107
+ },
108
+ run: args =>
109
+ api.consoleRequest(ctx, 'POST', '/projects', {
110
+ body: { name: args.name, domain: args.domain, orgId: args.organization_id },
111
+ }),
112
+ },
113
+ {
114
+ name: 'pause_project',
115
+ description: 'Stop a project\'s containers. Its data is kept; restore_project starts it again.',
116
+ inputSchema: { type: 'object', properties: { project_id: str('Project name or id.') }, required: ['project_id'] },
117
+ run: args => api.consoleRequest(ctx, 'POST', `/projects/${enc(args.project_id)}/stop`),
118
+ },
119
+ {
120
+ name: 'restore_project',
121
+ description: 'Start a paused project again.',
122
+ inputSchema: { type: 'object', properties: { project_id: str('Project name or id.') }, required: ['project_id'] },
123
+ run: args => api.consoleRequest(ctx, 'POST', `/projects/${enc(args.project_id)}/start`),
124
+ },
125
+ ];
126
+ }
127
+
128
+ /** Reads on the served project. */
129
+ function projectReadTools(ctx) {
130
+ return [
131
+ {
132
+ name: 'get_project_url',
133
+ description: 'The API URL (and realtime URL) a client app uses to reach this project.',
134
+ inputSchema: noArgs,
135
+ run: async () => {
136
+ const info = await api.adminRequest(ctx, 'GET', '/connect-info');
137
+ return { url: info.apiUrl, realtimeUrl: info.wsUrl };
138
+ },
139
+ },
140
+ {
141
+ name: 'get_publishable_keys',
142
+ description:
143
+ 'The publishable API keys a client app ships with. Safe to embed: row level security decides what they reach. Secret keys are never returned.',
144
+ inputSchema: noArgs,
145
+ run: async () => {
146
+ const data = await api.adminRequest(ctx, 'GET', '/api-keys');
147
+ const keys = ((data && data.keys) || [])
148
+ .filter(k => k.type === 'publishable' && !k.revokedAt)
149
+ .map(k => ({ name: k.name, api_key: k.token }));
150
+ return { keys };
151
+ },
152
+ },
153
+ {
154
+ name: 'list_extensions',
155
+ description: 'Postgres extensions available in the project database, and which are installed.',
156
+ inputSchema: noArgs,
157
+ run: () => api.adminRequest(ctx, 'GET', '/schema/extensions'),
158
+ },
159
+ {
160
+ name: 'list_migrations',
161
+ description: 'Schema migrations recorded for the project, with their status and when they were applied.',
162
+ inputSchema: noArgs,
163
+ run: () => api.adminRequest(ctx, 'GET', '/migrations'),
164
+ },
165
+ {
166
+ name: 'list_edge_functions',
167
+ description: 'Deployed edge functions: name, route, whether enabled, and limits. get_edge_function returns the source.',
168
+ inputSchema: noArgs,
169
+ run: async () => {
170
+ const data = await api.adminRequest(ctx, 'GET', '/edge-functions');
171
+ // The source of every function is noise in a listing; get_edge_function has it.
172
+ const functions = ((data && data.functions) || []).map(({ code: _code, ...meta }) => meta);
173
+ return { functions };
174
+ },
175
+ },
176
+ {
177
+ name: 'get_edge_function',
178
+ description: 'One deployed edge function with its source code.',
179
+ inputSchema: {
180
+ type: 'object',
181
+ properties: { function_slug: str('The function\'s route (also accepts its name or id).') },
182
+ required: ['function_slug'],
183
+ },
184
+ run: async args => {
185
+ const fn = await findFunction(ctx, args.function_slug);
186
+ return api.adminRequest(ctx, 'GET', `/edge-functions/${enc(fn.id)}`);
187
+ },
188
+ },
189
+ {
190
+ name: 'list_storage_buckets',
191
+ description: 'Storage buckets with their visibility, quota and usage.',
192
+ inputSchema: noArgs,
193
+ run: () => api.adminRequest(ctx, 'GET', '/storage/buckets'),
194
+ },
195
+ ];
196
+ }
197
+
198
+ /** Changes to the served project. Only with `--write`. */
199
+ function projectWriteTools(ctx) {
200
+ return [
201
+ {
202
+ name: 'deploy_edge_function',
203
+ description:
204
+ 'Deploy an edge function, creating it or replacing the one at the same route. The name is also its route. The code runs on Deno with the Web platform; export a default handler (req: Request) => Response.',
205
+ inputSchema: {
206
+ type: 'object',
207
+ properties: {
208
+ name: str('Function name: lowercase letters, digits and hyphens.'),
209
+ entrypoint_path: str('Which of files is the entrypoint. Defaults to index.ts.'),
210
+ files: {
211
+ type: 'array',
212
+ description: 'The function source: its single entrypoint module.',
213
+ items: {
214
+ type: 'object',
215
+ properties: { name: str('File name, e.g. index.ts.'), content: str('File contents.') },
216
+ required: ['name', 'content'],
217
+ },
218
+ },
219
+ },
220
+ required: ['name', 'files'],
221
+ },
222
+ run: args => {
223
+ if (!FUNCTION_NAME.test(args.name || '')) {
224
+ throw new Error('Function names are lowercase letters, digits and hyphens (the name is also the route).');
225
+ }
226
+ const code = entrypointSource(args.files, args.entrypoint_path);
227
+ return upsertFunction(ctx, { name: args.name, route: args.name, code });
228
+ },
229
+ },
230
+ ];
231
+ }
232
+
233
+ module.exports = { consoleReadTools, consoleWriteTools, projectReadTools, projectWriteTools, entrypointSource };