@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,149 +1,152 @@
1
- 'use strict';
2
-
3
- // Minimal MCP server over stdio: newline-delimited JSON-RPC 2.0 on stdin/stdout.
4
- //
5
- // Why not the official SDK: a stdio tools server needs five methods, and
6
- // `@modelcontextprotocol/sdk` pulls express, hono, cors, ajv, jose and a dozen
7
- // more to provide them. This CLI is deliberately zero-dependency, and making
8
- // `npx @motherbase/cli` install a web framework so that one subcommand can read
9
- // stdin is not a trade worth making.
10
- //
11
- // ponytail: stdio transport and the tools capability only. No resources, no
12
- // prompts, no sampling, no HTTP/SSE transport. Adopt the official SDK if any of
13
- // those is ever needed rather than growing this file into a second one.
14
-
15
- // Echoed back to the client. MCP clients negotiate by sending their own version;
16
- // answering with a version we implement is the contract, and every client in the
17
- // field accepts a server that names an older one.
18
- const PROTOCOL_VERSION = '2024-11-05';
19
-
20
- /** JSON-RPC error codes used here. */
21
- const METHOD_NOT_FOUND = -32601;
22
- const INTERNAL_ERROR = -32603;
23
- const PARSE_ERROR = -32700;
24
-
25
- /**
26
- * Reads newline-delimited JSON from `input`, dispatches to `handlers`, writes
27
- * responses to `output`.
28
- *
29
- * Split from the tool definitions so the transport can be driven by a test with
30
- * two in-memory streams and no child process.
31
- */
32
- function serve({ input, output, handlers, onError }) {
33
- let buffer = '';
34
-
35
- const write = message => {
36
- output.write(JSON.stringify(message) + '\n');
37
- };
38
-
39
- const respond = (id, result) => {
40
- // A notification (no id) gets no response. Sending one is a protocol error
41
- // that some clients treat as fatal.
42
- if (id === undefined || id === null) return;
43
- write({ jsonrpc: '2.0', id, result });
44
- };
45
-
46
- const fail = (id, code, message) => {
47
- if (id === undefined || id === null) return;
48
- write({ jsonrpc: '2.0', id, error: { code, message } });
49
- };
50
-
51
- const dispatch = async message => {
52
- const { id, method, params } = message;
53
- const handler = handlers[method];
54
- if (!handler) {
55
- // Unknown notifications are ignored rather than answered: the spec sends
56
- // several the server is not required to know about.
57
- if (id === undefined || id === null) return;
58
- return fail(id, METHOD_NOT_FOUND, `Unknown method: ${method}`);
59
- }
60
- try {
61
- respond(id, await handler(params || {}));
62
- } catch (err) {
63
- if (onError) onError(err);
64
- fail(id, INTERNAL_ERROR, err && err.message ? err.message : String(err));
65
- }
66
- };
67
-
68
- return new Promise(resolve => {
69
- input.setEncoding('utf8');
70
- input.on('data', chunk => {
71
- buffer += chunk;
72
- let index;
73
- while ((index = buffer.indexOf('\n')) >= 0) {
74
- const line = buffer.slice(0, index).trim();
75
- buffer = buffer.slice(index + 1);
76
- if (!line) continue;
77
- let message;
78
- try {
79
- message = JSON.parse(line);
80
- } catch {
81
- write({ jsonrpc: '2.0', id: null, error: { code: PARSE_ERROR, message: 'Invalid JSON' } });
82
- continue;
83
- }
84
- // Deliberately not awaited: a slow tool call must not stall the reader,
85
- // and JSON-RPC responses carry their id so they may arrive out of order.
86
- void dispatch(message);
87
- }
88
- });
89
- input.on('end', resolve);
90
- input.on('close', resolve);
91
- });
92
- }
93
-
94
- /**
95
- * Build the five handlers a tools-only server needs.
96
- *
97
- * `tools` is a list of `{ name, description, inputSchema, run }`. `run` returns
98
- * anything JSON-serialisable; it is rendered as a text content block, which is
99
- * what every current client displays and what models read best.
100
- */
101
- function toolServer({ name, version, tools }) {
102
- const byName = new Map(tools.map(tool => [tool.name, tool]));
103
-
104
- return {
105
- initialize: () => ({
106
- protocolVersion: PROTOCOL_VERSION,
107
- capabilities: { tools: {} },
108
- serverInfo: { name, version },
109
- }),
110
- 'notifications/initialized': () => undefined,
111
- ping: () => ({}),
112
- 'tools/list': () => ({
113
- tools: tools.map(({ name: toolName, description, inputSchema }) => ({
114
- name: toolName,
115
- description,
116
- inputSchema,
117
- })),
118
- }),
119
- 'tools/call': async params => {
120
- const tool = byName.get(params.name);
121
- if (!tool) {
122
- // A missing tool is reported in-band, not as a JSON-RPC error: the model
123
- // should see it and correct itself rather than the client showing a
124
- // transport failure.
125
- return {
126
- isError: true,
127
- content: [{ type: 'text', text: `Unknown tool: ${params.name}` }],
128
- };
129
- }
130
- try {
131
- const result = await tool.run(params.arguments || {});
132
- return { content: [{ type: 'text', text: render(result) }] };
133
- } catch (err) {
134
- return {
135
- isError: true,
136
- content: [{ type: 'text', text: err && err.message ? err.message : String(err) }],
137
- };
138
- }
139
- },
140
- };
141
- }
142
-
143
- function render(value) {
144
- if (typeof value === 'string') return value;
145
- if (value === undefined) return 'ok';
146
- return JSON.stringify(value, null, 2);
147
- }
148
-
149
- module.exports = { serve, toolServer, PROTOCOL_VERSION };
1
+ 'use strict';
2
+
3
+ // Minimal MCP server over stdio: newline-delimited JSON-RPC 2.0 on stdin/stdout.
4
+ //
5
+ // Why not the official SDK: a stdio tools server needs five methods, and
6
+ // `@modelcontextprotocol/sdk` pulls express, hono, cors, ajv, jose and a dozen
7
+ // more to provide them. This CLI is deliberately zero-dependency, and making
8
+ // `npx @motherbase/cli` install a web framework so that one subcommand can read
9
+ // stdin is not a trade worth making.
10
+ //
11
+ // ponytail: stdio transport and the tools capability only. No resources, no
12
+ // prompts, no sampling, no HTTP/SSE transport. Adopt the official SDK if any of
13
+ // those is ever needed rather than growing this file into a second one.
14
+
15
+ // Echoed back to the client. MCP clients negotiate by sending their own version;
16
+ // answering with a version we implement is the contract, and every client in the
17
+ // field accepts a server that names an older one.
18
+ const PROTOCOL_VERSION = '2024-11-05';
19
+
20
+ /** JSON-RPC error codes used here. */
21
+ const METHOD_NOT_FOUND = -32601;
22
+ const INTERNAL_ERROR = -32603;
23
+ const PARSE_ERROR = -32700;
24
+
25
+ /**
26
+ * Reads newline-delimited JSON from `input`, dispatches to `handlers`, writes
27
+ * responses to `output`.
28
+ *
29
+ * Split from the tool definitions so the transport can be driven by a test with
30
+ * two in-memory streams and no child process.
31
+ */
32
+ function serve({ input, output, handlers, onError }) {
33
+ let buffer = '';
34
+
35
+ const write = message => {
36
+ output.write(JSON.stringify(message) + '\n');
37
+ };
38
+
39
+ const respond = (id, result) => {
40
+ // A notification (no id) gets no response. Sending one is a protocol error
41
+ // that some clients treat as fatal.
42
+ if (id === undefined || id === null) return;
43
+ write({ jsonrpc: '2.0', id, result });
44
+ };
45
+
46
+ const fail = (id, code, message) => {
47
+ if (id === undefined || id === null) return;
48
+ write({ jsonrpc: '2.0', id, error: { code, message } });
49
+ };
50
+
51
+ const dispatch = async message => {
52
+ // Valid JSON is not necessarily a request: `null` or `42` would throw on
53
+ // destructuring and, un-awaited, take the process down.
54
+ if (message === null || typeof message !== 'object') return;
55
+ const { id, method, params } = message;
56
+ const handler = Object.hasOwn(handlers, method) ? handlers[method] : undefined;
57
+ if (!handler) {
58
+ // Unknown notifications are ignored rather than answered: the spec sends
59
+ // several the server is not required to know about.
60
+ if (id === undefined || id === null) return;
61
+ return fail(id, METHOD_NOT_FOUND, `Unknown method: ${method}`);
62
+ }
63
+ try {
64
+ respond(id, await handler(params || {}));
65
+ } catch (err) {
66
+ if (onError) onError(err);
67
+ fail(id, INTERNAL_ERROR, err && err.message ? err.message : String(err));
68
+ }
69
+ };
70
+
71
+ return new Promise(resolve => {
72
+ input.setEncoding('utf8');
73
+ input.on('data', chunk => {
74
+ buffer += chunk;
75
+ let index;
76
+ while ((index = buffer.indexOf('\n')) >= 0) {
77
+ const line = buffer.slice(0, index).trim();
78
+ buffer = buffer.slice(index + 1);
79
+ if (!line) continue;
80
+ let message;
81
+ try {
82
+ message = JSON.parse(line);
83
+ } catch {
84
+ write({ jsonrpc: '2.0', id: null, error: { code: PARSE_ERROR, message: 'Invalid JSON' } });
85
+ continue;
86
+ }
87
+ // Deliberately not awaited: a slow tool call must not stall the reader,
88
+ // and JSON-RPC responses carry their id so they may arrive out of order.
89
+ void dispatch(message);
90
+ }
91
+ });
92
+ input.on('end', resolve);
93
+ input.on('close', resolve);
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Build the five handlers a tools-only server needs.
99
+ *
100
+ * `tools` is a list of `{ name, description, inputSchema, run }`. `run` returns
101
+ * anything JSON-serialisable; it is rendered as a text content block, which is
102
+ * what every current client displays and what models read best.
103
+ */
104
+ function toolServer({ name, version, tools }) {
105
+ const byName = new Map(tools.map(tool => [tool.name, tool]));
106
+
107
+ return {
108
+ initialize: () => ({
109
+ protocolVersion: PROTOCOL_VERSION,
110
+ capabilities: { tools: {} },
111
+ serverInfo: { name, version },
112
+ }),
113
+ 'notifications/initialized': () => undefined,
114
+ ping: () => ({}),
115
+ 'tools/list': () => ({
116
+ tools: tools.map(({ name: toolName, description, inputSchema }) => ({
117
+ name: toolName,
118
+ description,
119
+ inputSchema,
120
+ })),
121
+ }),
122
+ 'tools/call': async params => {
123
+ const tool = byName.get(params.name);
124
+ if (!tool) {
125
+ // A missing tool is reported in-band, not as a JSON-RPC error: the model
126
+ // should see it and correct itself rather than the client showing a
127
+ // transport failure.
128
+ return {
129
+ isError: true,
130
+ content: [{ type: 'text', text: `Unknown tool: ${params.name}` }],
131
+ };
132
+ }
133
+ try {
134
+ const result = await tool.run(params.arguments || {});
135
+ return { content: [{ type: 'text', text: render(result) }] };
136
+ } catch (err) {
137
+ return {
138
+ isError: true,
139
+ content: [{ type: 'text', text: err && err.message ? err.message : String(err) }],
140
+ };
141
+ }
142
+ },
143
+ };
144
+ }
145
+
146
+ function render(value) {
147
+ if (typeof value === 'string') return value;
148
+ if (value === undefined) return 'ok';
149
+ return JSON.stringify(value, null, 2);
150
+ }
151
+
152
+ module.exports = { serve, toolServer, PROTOCOL_VERSION };
@@ -1,93 +1,93 @@
1
- 'use strict';
2
-
3
- // Storage configuration over MCP, the counterpart of Supabase's
4
- // get_storage_config / update_storage_config. Motherbase keeps that
5
- // configuration in three settings groups plus the S3 endpoint, so these two
6
- // tools fold them back into the single view an agent expects.
7
-
8
- const api = require('../api');
9
-
10
- /** Setting group -> the tool argument that edits each of its fields. */
11
- const WRITABLE = {
12
- 'storage.limits': { maxUploadBytes: 'maxUploadBytes', allowedMimeTypes: 'allowedMimeTypes' },
13
- 'storage.imageTransform': { enabled: 'imageTransformEnabled', maxSourceBytes: 'imageTransformMaxSourceBytes' },
14
- 'storage.policies': { enabled: 'policiesEnabled' },
15
- };
16
-
17
- /** Groups the arguments touch, each with the field values to change. */
18
- function changesByGroup(args) {
19
- const changes = {};
20
- for (const [group, fields] of Object.entries(WRITABLE)) {
21
- for (const [field, argName] of Object.entries(fields)) {
22
- if (args[argName] === undefined) continue;
23
- const value = Array.isArray(args[argName]) ? args[argName].join(', ') : args[argName];
24
- (changes[group] ||= {})[field] = value;
25
- }
26
- }
27
- return changes;
28
- }
29
-
30
- const valuesOf = group => (group && group.values) || {};
31
-
32
- function readStorageTools(ctx) {
33
- return [
34
- {
35
- name: 'get_storage_config',
36
- description:
37
- 'Storage configuration: the global upload size limit and allowed MIME types, image transformations, whether PostgreSQL policies authorize storage access, and the S3 protocol endpoint. Per-bucket limits are on list_storage_buckets.',
38
- inputSchema: { type: 'object', properties: {} },
39
- run: async () => {
40
- const [limits, imageTransform, policies, s3] = await Promise.all([
41
- api.adminRequest(ctx, 'GET', '/settings/storage.limits'),
42
- api.adminRequest(ctx, 'GET', '/settings/storage.imageTransform'),
43
- api.adminRequest(ctx, 'GET', '/settings/storage.policies'),
44
- api.adminRequest(ctx, 'GET', '/storage/s3'),
45
- ]);
46
- return {
47
- limits: valuesOf(limits),
48
- imageTransform: valuesOf(imageTransform),
49
- policies: valuesOf(policies),
50
- s3,
51
- };
52
- },
53
- },
54
- ];
55
- }
56
-
57
- function writeStorageTools(ctx) {
58
- return [
59
- {
60
- name: 'update_storage_config',
61
- description:
62
- 'Change the global storage configuration. Only the fields you pass change. Per-bucket size limits and MIME types are edited on the bucket itself.',
63
- inputSchema: {
64
- type: 'object',
65
- properties: {
66
- maxUploadBytes: { type: 'integer', minimum: 0, description: 'Largest upload in bytes. 0 uses the server default.' },
67
- allowedMimeTypes: {
68
- type: 'array',
69
- items: { type: 'string' },
70
- description: 'Allowed MIME types, wildcards like image/* supported. Empty allows everything.',
71
- },
72
- imageTransformEnabled: { type: 'boolean', description: 'Serve resized images from /storage/v1/render/image.' },
73
- imageTransformMaxSourceBytes: { type: 'integer', minimum: 1, description: 'Largest image, in bytes, that will be transformed.' },
74
- policiesEnabled: { type: 'boolean', description: 'Authorize storage access with RLS policies on storage.files.' },
75
- },
76
- },
77
- run: async args => {
78
- const changes = changesByGroup(args);
79
- if (!Object.keys(changes).length) throw new Error('Pass at least one field to change.');
80
- // The settings API replaces a group wholesale, so an untouched field
81
- // would fall back to its default unless it is sent back as it is.
82
- const saved = {};
83
- for (const [group, fields] of Object.entries(changes)) {
84
- const current = valuesOf(await api.adminRequest(ctx, 'GET', `/settings/${group}`));
85
- saved[group] = await api.adminRequest(ctx, 'PUT', `/settings/${group}`, { body: { ...current, ...fields } });
86
- }
87
- return saved;
88
- },
89
- },
90
- ];
91
- }
92
-
93
- module.exports = { readStorageTools, writeStorageTools, changesByGroup };
1
+ 'use strict';
2
+
3
+ // Storage configuration over MCP, the counterpart of Supabase's
4
+ // get_storage_config / update_storage_config. Motherbase keeps that
5
+ // configuration in three settings groups plus the S3 endpoint, so these two
6
+ // tools fold them back into the single view an agent expects.
7
+
8
+ const api = require('../api');
9
+
10
+ /** Setting group -> the tool argument that edits each of its fields. */
11
+ const WRITABLE = {
12
+ 'storage.limits': { maxUploadBytes: 'maxUploadBytes', allowedMimeTypes: 'allowedMimeTypes' },
13
+ 'storage.imageTransform': { enabled: 'imageTransformEnabled', maxSourceBytes: 'imageTransformMaxSourceBytes' },
14
+ 'storage.policies': { enabled: 'policiesEnabled' },
15
+ };
16
+
17
+ /** Groups the arguments touch, each with the field values to change. */
18
+ function changesByGroup(args) {
19
+ const changes = {};
20
+ for (const [group, fields] of Object.entries(WRITABLE)) {
21
+ for (const [field, argName] of Object.entries(fields)) {
22
+ if (args[argName] === undefined) continue;
23
+ const value = Array.isArray(args[argName]) ? args[argName].join(', ') : args[argName];
24
+ (changes[group] ||= {})[field] = value;
25
+ }
26
+ }
27
+ return changes;
28
+ }
29
+
30
+ const valuesOf = group => (group && group.values) || {};
31
+
32
+ function readStorageTools(ctx) {
33
+ return [
34
+ {
35
+ name: 'get_storage_config',
36
+ description:
37
+ 'Storage configuration: the global upload size limit and allowed MIME types, image transformations, whether PostgreSQL policies authorize storage access, and the S3 protocol endpoint. Per-bucket limits are on list_storage_buckets.',
38
+ inputSchema: { type: 'object', properties: {} },
39
+ run: async () => {
40
+ const [limits, imageTransform, policies, s3] = await Promise.all([
41
+ api.adminRequest(ctx, 'GET', '/settings/storage.limits'),
42
+ api.adminRequest(ctx, 'GET', '/settings/storage.imageTransform'),
43
+ api.adminRequest(ctx, 'GET', '/settings/storage.policies'),
44
+ api.adminRequest(ctx, 'GET', '/storage/s3'),
45
+ ]);
46
+ return {
47
+ limits: valuesOf(limits),
48
+ imageTransform: valuesOf(imageTransform),
49
+ policies: valuesOf(policies),
50
+ s3,
51
+ };
52
+ },
53
+ },
54
+ ];
55
+ }
56
+
57
+ function writeStorageTools(ctx) {
58
+ return [
59
+ {
60
+ name: 'update_storage_config',
61
+ description:
62
+ 'Change the global storage configuration. Only the fields you pass change. Per-bucket size limits and MIME types are edited on the bucket itself.',
63
+ inputSchema: {
64
+ type: 'object',
65
+ properties: {
66
+ maxUploadBytes: { type: 'integer', minimum: 0, description: 'Largest upload in bytes. 0 uses the server default.' },
67
+ allowedMimeTypes: {
68
+ type: 'array',
69
+ items: { type: 'string' },
70
+ description: 'Allowed MIME types, wildcards like image/* supported. Empty allows everything.',
71
+ },
72
+ imageTransformEnabled: { type: 'boolean', description: 'Serve resized images from /storage/v1/render/image.' },
73
+ imageTransformMaxSourceBytes: { type: 'integer', minimum: 1, description: 'Largest image, in bytes, that will be transformed.' },
74
+ policiesEnabled: { type: 'boolean', description: 'Authorize storage access with RLS policies on storage.files.' },
75
+ },
76
+ },
77
+ run: async args => {
78
+ const changes = changesByGroup(args);
79
+ if (!Object.keys(changes).length) throw new Error('Pass at least one field to change.');
80
+ // The settings API replaces a group wholesale, so an untouched field
81
+ // would fall back to its default unless it is sent back as it is.
82
+ const saved = {};
83
+ for (const [group, fields] of Object.entries(changes)) {
84
+ const current = valuesOf(await api.adminRequest(ctx, 'GET', `/settings/${group}`));
85
+ saved[group] = await api.adminRequest(ctx, 'PUT', `/settings/${group}`, { body: { ...current, ...fields } });
86
+ }
87
+ return saved;
88
+ },
89
+ },
90
+ ];
91
+ }
92
+
93
+ module.exports = { readStorageTools, writeStorageTools, changesByGroup };