@motherbase/cli 0.2.6 → 0.2.8

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 (59) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +248 -247
  3. package/bin/.motherbase/config.json +5 -0
  4. package/bin/motherbase.js +10 -10
  5. package/package.json +1 -1
  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.js +103 -103
  14. package/src/commands/domains.js +124 -124
  15. package/src/commands/gen.js +178 -172
  16. package/src/commands/import.js +55 -55
  17. package/src/commands/index.js +85 -85
  18. package/src/commands/inspect.js +178 -178
  19. package/src/commands/keys.js +72 -72
  20. package/src/commands/link.js +35 -35
  21. package/src/commands/local.js +100 -23
  22. package/src/commands/login.js +9 -3
  23. package/src/commands/logout.js +16 -16
  24. package/src/commands/mcp.js +61 -61
  25. package/src/commands/migrate.js +0 -5
  26. package/src/commands/migration.js +1 -1
  27. package/src/commands/network.js +83 -83
  28. package/src/commands/orgs.js +40 -40
  29. package/src/commands/postgres-config.js +113 -113
  30. package/src/commands/projects.js +131 -130
  31. package/src/commands/queues.js +133 -133
  32. package/src/commands/realtime.js +35 -35
  33. package/src/commands/rls.js +176 -176
  34. package/src/commands/secrets.js +66 -66
  35. package/src/commands/signing.js +130 -130
  36. package/src/commands/snippets.js +44 -44
  37. package/src/commands/status.js +42 -42
  38. package/src/commands/storage.js +261 -261
  39. package/src/commands/test.js +9 -9
  40. package/src/commands/unlink.js +15 -15
  41. package/src/commands/vault.js +82 -82
  42. package/src/commands/webhooks.js +62 -62
  43. package/src/commands/whoami.js +20 -20
  44. package/src/commands/wrappers.js +150 -150
  45. package/src/config.js +98 -98
  46. package/src/context.js +106 -106
  47. package/src/edge-templates.js +194 -194
  48. package/src/format.js +92 -92
  49. package/src/inspect/csv.js +22 -22
  50. package/src/inspect/queries.js +562 -562
  51. package/src/mcp/platform-tools.js +233 -233
  52. package/src/mcp/protocol.js +152 -152
  53. package/src/mcp/storage-tools.js +93 -93
  54. package/src/mcp/tools.js +478 -478
  55. package/src/migration/commands.js +2 -2
  56. package/src/migration/files.js +13 -0
  57. package/src/prompt.js +91 -91
  58. package/src/storage/remote.js +62 -62
  59. package/src/ui.js +111 -111
@@ -1,152 +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
- // 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
+ '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 };