@motherbase/cli 0.2.4 → 0.2.5

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 (47) hide show
  1. package/README.md +247 -134
  2. package/package.json +2 -2
  3. package/src/api.js +14 -2
  4. package/src/commands/_util.js +49 -6
  5. package/src/commands/backups.js +16 -0
  6. package/src/commands/branches.js +113 -0
  7. package/src/commands/completion.js +90 -0
  8. package/src/commands/db-catalog.js +326 -0
  9. package/src/commands/db.js +16 -5
  10. package/src/commands/domains.js +124 -0
  11. package/src/commands/functions.js +297 -274
  12. package/src/commands/gen.js +172 -126
  13. package/src/commands/import.js +23 -3
  14. package/src/commands/index.js +27 -6
  15. package/src/commands/inspect.js +142 -5
  16. package/src/commands/keys.js +72 -72
  17. package/src/commands/local.js +273 -273
  18. package/src/commands/mcp.js +61 -43
  19. package/src/commands/migrate.js +179 -0
  20. package/src/commands/migration.js +81 -6
  21. package/src/commands/network.js +83 -0
  22. package/src/commands/orgs.js +40 -0
  23. package/src/commands/postgres-config.js +113 -0
  24. package/src/commands/projects.js +8 -3
  25. package/src/commands/queues.js +133 -0
  26. package/src/commands/secrets.js +29 -8
  27. package/src/commands/signing.js +130 -0
  28. package/src/commands/snippets.js +44 -0
  29. package/src/commands/storage.js +192 -44
  30. package/src/commands/test.js +9 -0
  31. package/src/commands/users.js +30 -1
  32. package/src/commands/vault.js +82 -0
  33. package/src/commands/wrappers.js +150 -0
  34. package/src/edge-templates.js +194 -0
  35. package/src/format.js +92 -0
  36. package/src/inspect/csv.js +22 -0
  37. package/src/inspect/queries.js +562 -0
  38. package/src/main.js +41 -7
  39. package/src/mcp/platform-tools.js +233 -0
  40. package/src/mcp/protocol.js +149 -149
  41. package/src/mcp/storage-tools.js +93 -0
  42. package/src/mcp/tools.js +478 -166
  43. package/src/migration/commands.js +375 -0
  44. package/src/migration/files.js +245 -0
  45. package/src/prompt.js +1 -1
  46. package/src/storage/remote.js +62 -0
  47. package/src/commands/migrations.js +0 -85
package/README.md CHANGED
@@ -1,134 +1,247 @@
1
- # Motherbase CLI
2
-
3
- A Supabase-CLI-like operator CLI for **Motherbase**. It drives both the
4
- **Platform Console** (account + multi-project lifecycle) and a **linked project's
5
- admin API** through the Console proxy — or a single project API directly.
6
-
7
- Zero runtime dependencies. Node ≥ 18 (uses built-in `fetch`, `readline`, `fs`,
8
- `child_process` only).
9
-
10
- ```bash
11
- node bin/motherbase.js --help # auto-generated help
12
- npm link # or: npm i -g @motherbase/cli
13
- motherbase login --url https://console.example.com
14
- motherbase link # pick a project → writes ./.motherbase/config.json
15
- motherbase db tables
16
- motherbase functions deploy --name hello --route hello --file ./hello.js
17
- ```
18
-
19
- ## Connection model
20
-
21
- 1. **Console / account** — `motherbase login [--url <consoleUrl>]` performs a real
22
- Console login (`POST /api/platform/login`) and stores the session token.
23
- 2. **Linked project** — `motherbase link [project]` records which project this
24
- directory targets. All resource commands then route through the Console proxy
25
- (`/api/projects/:id/admin/*`).
26
- 3. **Direct mode (CI / single stack)** — set `MOTHERBASE_URL` +
27
- `MOTHERBASE_ADMIN_TOKEN` to talk straight to one project's `/api/admin/*`.
28
-
29
- ### Configuration files
30
-
31
- **Global credentials** — `~/.motherbase/config.json` (contains tokens, written `0o600`):
32
-
33
- ```json
34
- {
35
- "consoles": {
36
- "https://console.example.com": { "token": "…", "email": "you@example.com" }
37
- },
38
- "current": "https://console.example.com"
39
- }
40
- ```
41
-
42
- **Project link** — `./.motherbase/config.json` (versionable, non-secret):
43
-
44
- ```json
45
- { "consoleUrl": "https://console.example.com", "projectId": "my-app", "projectName": "my-app" }
46
- ```
47
-
48
- ### Resolution precedence
49
-
50
- `flags > env > config`, per value:
51
-
52
- | Value | Flag | Env | Config |
53
- | ----------- | ----------------- | ------------------------- | ------ |
54
- | console URL | `--url/-u` | `MOTHERBASE_CONSOLE_URL` | link / global `current` |
55
- | token | `--token` | `MOTHERBASE_TOKEN` | global console entry |
56
- | project | `--project/-p` | `MOTHERBASE_PROJECT` | link |
57
- | direct API | — | `MOTHERBASE_URL` + `MOTHERBASE_ADMIN_TOKEN` | — |
58
-
59
- ### Auth headers
60
-
61
- The console session token is sent as `Authorization: Bearer <token>` on every
62
- request. Console requests hit `/api/platform/*`; admin requests hit the proxy at
63
- `/api/projects/:id/admin/*` (which validates the session, then injects its own
64
- `X-Platform-Token` to the project). In direct mode the admin token is the Bearer.
65
-
66
- ## Commands
67
-
68
- Global flags: `--json`, `--yes/-y`, `--project/-p <id>`, `--url/-u <consoleUrl>`,
69
- `--token <t>`, `-h/--help`, `-v/--version`.
70
-
71
- - **Connection**: `login`, `logout`, `whoami`, `link`, `unlink`, `status`
72
- - **Projects (Console)**: `projects list|create|start|stop|restart|update|rebuild|delete|logs|info|limits`
73
- - **Local self-host** (docker compose): `init`, `start`, `stop`, `deploy`, `logs`
74
- - **Database**: `db tables|sql|dump|restore|backups`
75
- - **Migrations**: `migrations list|diff|get|create|apply|rollback`, `migration new`
76
- - **Import**: `import --from-supabase <conn> [--preview] [--no-data] [--no-auth]`
77
- - **RLS**: `rls list|enable|disable`, `rls policy create|delete`
78
- - **Users**: `users list|get|create|update|delete|reset-password|promote|demote|revoke-sessions`
79
- - **Storage**: `storage buckets list|create|delete`, `storage ls|cp|rm`
80
- - **Functions**: `functions list|get|new|deploy|delete|invoke|logs`
81
- - **Secrets**: `secrets list|set|unset`
82
- - **Realtime**: `realtime config|channels`
83
- - **Webhooks**: `webhooks list|create|delete|test`
84
- - **Cron**: `cron list|create|delete|toggle`
85
- - **Config**: `config list|get|set`
86
- - **Inspect**: `inspect logs|stats|performance|requests|advisor|shield`
87
- - **Codegen**: `gen types typescript [--schema public] [--local] [--output <file>] [--check]`
88
-
89
- Run `motherbase <group> -h` for per-group usage.
90
-
91
- ## Type generation
92
-
93
- Generate TypeScript definitions for the live schema, in the shape supabase-js
94
- emits, so `createClient<Database>(url, key)` types every row, rejects unknown
95
- columns and types `.rpc()` calls:
96
-
97
- ```bash
98
- motherbase gen types typescript > database.types.ts
99
- motherbase gen types typescript --schema shop --output src/shop.types.ts
100
- ```
101
-
102
- With no `--output` the file goes to stdout and nothing else does, so the
103
- redirect above produces a compiling file. `--local` skips the Console proxy and
104
- talks to a project API on this machine (`MOTHERBASE_URL`, default
105
- `http://localhost:5001`, with `MOTHERBASE_ADMIN_TOKEN`).
106
-
107
- Gate CI on schema drift with `--check`, which never writes and exits non-zero
108
- when the committed file differs from a fresh generation:
109
-
110
- ```bash
111
- motherbase gen types typescript --check --output database.types.ts
112
- ```
113
-
114
- ## Supabase migration
115
-
116
- Preview a Supabase import without writing to Motherbase:
117
-
118
- ```bash
119
- motherbase import --from-supabase "$SUPABASE_DB_URL" --preview --json
120
- ```
121
-
122
- Run the import:
123
-
124
- ```bash
125
- motherbase import --from-supabase "$SUPABASE_DB_URL" --yes
126
- ```
127
-
128
- The importer moves the portable parts of a Supabase project: `public` schema, table data, RLS policies, and email/password users from `auth.users`. Storage objects are not copied. Use `--no-data` or `--no-auth` for partial imports.
129
-
130
- ## Tests
131
-
132
- ```bash
133
- npm test # node --test, mocked fetch, no network/DB
134
- ```
1
+ # Motherbase CLI
2
+
3
+ A Supabase-CLI-like operator CLI for **Motherbase**. It drives both the
4
+ **Platform Console** (account + multi-project lifecycle) and a **linked project's
5
+ admin API** through the Console proxy, or a single project API directly.
6
+
7
+ Zero runtime dependencies. Node ≥ 18 (uses built-in `fetch`, `readline`, `fs`,
8
+ `child_process` only).
9
+
10
+ ```bash
11
+ node bin/motherbase.js --help # auto-generated help
12
+ npm link # or: npm i -g @motherbase/cli
13
+ motherbase login --url https://console.example.com
14
+ motherbase link # pick a project → writes ./.motherbase/config.json
15
+ motherbase db tables
16
+ motherbase functions deploy --name hello --route hello --file ./hello.js
17
+ ```
18
+
19
+ ## Connection model
20
+
21
+ 1. **Console / account**, `motherbase login [--url <consoleUrl>]` performs a real
22
+ Console login (`POST /api/platform/login`) and stores the session token.
23
+ 2. **Linked project**, `motherbase link [project]` records which project this
24
+ directory targets. All resource commands then route through the Console proxy
25
+ (`/api/projects/:id/admin/*`).
26
+ 3. **Direct mode (CI / single stack)**. Set `MOTHERBASE_URL` +
27
+ `MOTHERBASE_ADMIN_TOKEN` to talk straight to one project's `/api/admin/*`.
28
+
29
+ ### Configuration files
30
+
31
+ **Global credentials**: `~/.motherbase/config.json` (contains tokens, written `0o600`):
32
+
33
+ ```json
34
+ {
35
+ "consoles": {
36
+ "https://console.example.com": { "token": "…", "email": "you@example.com" }
37
+ },
38
+ "current": "https://console.example.com"
39
+ }
40
+ ```
41
+
42
+ **Project link**: `./.motherbase/config.json` (versionable, non-secret):
43
+
44
+ ```json
45
+ { "consoleUrl": "https://console.example.com", "projectId": "my-app", "projectName": "my-app" }
46
+ ```
47
+
48
+ ### Resolution precedence
49
+
50
+ `flags > env > config`, per value:
51
+
52
+ | Value | Flag | Env | Config |
53
+ | ----------- | ----------------- | ------------------------- | ------ |
54
+ | console URL | `--url/-u` | `MOTHERBASE_CONSOLE_URL` | link / global `current` |
55
+ | token | `--token` | `MOTHERBASE_TOKEN` | global console entry |
56
+ | project | `--project/-p` | `MOTHERBASE_PROJECT` | link |
57
+ | direct API | (| `MOTHERBASE_URL` + `MOTHERBASE_ADMIN_TOKEN` |) |
58
+
59
+ ### Auth headers
60
+
61
+ The console session token is sent as `Authorization: Bearer <token>` on every
62
+ request. Console requests hit `/api/platform/*`; admin requests hit the proxy at
63
+ `/api/projects/:id/admin/*` (which validates the session, then injects its own
64
+ `X-Platform-Token` to the project). In direct mode the admin token is the Bearer.
65
+
66
+ ## Commands
67
+
68
+ Global flags: `-o/--output pretty|json|yaml|env` (`--json` is `-o json`),
69
+ `--yes/-y`, `--project/-p/--project-ref <id>`, `--url/-u <consoleUrl>`,
70
+ `--token <t>`, `--workdir <dir>`, `--debug` (one stderr line per HTTP request),
71
+ `-h/--help`, `-v/--version`.
72
+
73
+ Commands follow the Supabase CLI's names. Where Motherbase already had its own
74
+ name, both work: `projects api-keys` / `keys list`, `backups list|restore` /
75
+ `db backups|restore`, `functions download` / `functions pull`, `db lint` /
76
+ `rls lint`, `gen types --lang <l>` / `gen types <l>`.
77
+
78
+ - **Connection**: `login`, `logout`, `whoami`, `link`, `unlink`, `status`
79
+ - **Orgs & projects (Console)**: `orgs list|create`, `projects list|create|start|stop|restart|update|rebuild|delete|logs|info|limits|api-keys`
80
+ - **Branches (Console)**: `branches create <name> [--with-data] [--git-branch <ref>]`, `branches list|get|update|delete|pause|unpause|reset|merge|rebase|switch <name>`
81
+ - **Domains (Console)**: `domains create --custom-hostname <host>`, `domains get|reverify|activate|delete`, `vanity-subdomains get|check-availability|activate --desired-subdomain <name>|delete`
82
+ - **Database access**: `network-restrictions get|update --db-allow-cidr <cidr> ...`, `ssl-enforcement get|update --enable-db-ssl-enforcement|--disable-db-ssl-enforcement`
83
+ - **Local self-host** (docker compose): `init`, `start`, `stop`, `deploy`, `logs`
84
+ - **Database**: `db push|pull|diff|reset|dump|seed`, `db tables|sql|backup|restore|backups|lint`, `backups list|restore`, `test db`
85
+ - **Database objects**: `db indexes list|create|drop`, `db types list|create|add-value|rename-value|drop`, `db roles list|create|alter|drop`, `db column-privileges list|grant|revoke`, `db publications list|create|alter|drop`
86
+ - **Postgres config**: `postgres-config get [--all]`, `postgres-config update --config k=v [--scope database|role|system] [--replace-existing-overrides]`, `postgres-config delete --config k1,k2`
87
+ - **Migrations**: `migration new|list|up|down|repair|squash|fetch|diff|get|create|apply|rollback` (`migrations` is an alias)
88
+ - **Import**: `import --from-supabase <conn> [--preview] [--no-data] [--no-auth]`
89
+ - **RLS**: `rls list|enable|disable`, `rls policy create|delete`
90
+ - **Users**: `users list|get|create|update|delete|reset-password|promote|demote|revoke-sessions|audit`
91
+ - **Storage**: `storage buckets list|create|delete`, `storage ls|cp|mv|rm [-r]` on `ss:///bucket/key` paths
92
+ - **Functions**: `functions list|get|new|deploy|pull|download|serve|delete|invoke|logs`
93
+ - **Secrets**: `secrets list|unset`, `secrets set NAME=VALUE ... [--env-file .env]`
94
+ - **Realtime**: `realtime config|channels`
95
+ - **Webhooks**: `webhooks list|create|delete|test`
96
+ - **Cron**: `cron list|create|delete|toggle`
97
+ - **Queues**: `queues list|enable|expose|create|drop|send|read|archive|purge`
98
+ - **Vault**: `vault list|enable|create|update|reveal|delete`
99
+ - **Wrappers**: `wrappers list|enable|create-server|drop-server|credentials|import|create-table|drop-table`
100
+ - **Config**: `config list|get|set`
101
+ - **Inspect**: `inspect db <query>`, `inspect report`, `inspect logs|stats|performance|index-advisor|requests|advisor|shield`
102
+ - **Codegen**: `gen types [--lang typescript|zod] [--schema public] [--local] [--output <file>] [--check]`
103
+ - **AI agents**: `mcp [--write]`
104
+ - **Keys**: `gen signing-key [--algorithm ES256|RS256|EdDSA] [--append]`, `gen bearer-jwt --sub <user> [--role] [--valid-for 1h]`, `gen keys [-o env]`
105
+ - **Shell**: `completion bash|zsh|fish` (e.g. `source <(motherbase completion bash)`)
106
+
107
+ Run `motherbase <group> -h` for per-group usage.
108
+
109
+ ## Migrations and seeds
110
+
111
+ Local files, laid out like a Supabase project's `supabase/` directory:
112
+
113
+ ```
114
+ motherbase/
115
+ migrations/<version>_<name>.sql `migration new <name>` stamps the version (UTC)
116
+ seed.sql or db.seed.sql_paths in motherbase/config.json
117
+ tests/*.sql pgTAP, for `motherbase test db`
118
+ ```
119
+
120
+ A migration file may end with a `-- @down` line followed by the SQL that undoes
121
+ it; `migration down` needs one. The linked project keeps the history, keyed on
122
+ `version`, and the Studio Migrations page shows the same rows (origin `cli`,
123
+ `studio` or `git`).
124
+
125
+ ```bash
126
+ motherbase migration list # LOCAL | REMOTE side by side
127
+ motherbase db push --dry-run # then without, add --include-seed for the seeds
128
+ motherbase db pull # remote changes -> a new file, recorded as applied
129
+ motherbase db diff -f add_index # the same diff, written as a new migration
130
+ motherbase migration repair 20260101120000 --status reverted
131
+ motherbase db reset --confirm # backup, empty app schemas, replay migrations + seed
132
+ motherbase db dump -f schema.sql --schema-only
133
+ ```
134
+
135
+ Each pushed file runs in its own transaction; a push stops at the first file
136
+ that fails, and nothing of that file is recorded. `db diff` replays the local
137
+ migrations into a throwaway database on the same server and compares tables,
138
+ columns and indexes: review what it writes. With no local migration yet, `db
139
+ pull` writes the remote schema itself, from `pg_dump`. `db dump` used to take a
140
+ server-side backup; that is `db backup` now.
141
+
142
+ ## Type generation
143
+
144
+ Generate TypeScript definitions for the live schema, in the shape supabase-js
145
+ emits, so `createClient<Database>(url, key)` types every row, rejects unknown
146
+ columns and types `.rpc()` calls:
147
+
148
+ ```bash
149
+ motherbase gen types typescript > database.types.ts
150
+ motherbase gen types typescript --schema shop --output src/shop.types.ts
151
+ ```
152
+
153
+ With no `--output` the file goes to stdout and nothing else does, so the
154
+ redirect above produces a compiling file. `--local` skips the Console proxy and
155
+ talks to a project API on this machine (`MOTHERBASE_URL`, default
156
+ `http://localhost:5001`, with `MOTHERBASE_ADMIN_TOKEN`).
157
+
158
+ Gate CI on schema drift with `--check`, which never writes and exits non-zero
159
+ when the committed file differs from a fresh generation:
160
+
161
+ ```bash
162
+ motherbase gen types typescript --check --output database.types.ts
163
+ ```
164
+
165
+ ## Storage paths
166
+
167
+ Objects are addressed as the Supabase CLI does, `ss:///<bucket>/<key>`, and
168
+ `-r` applies a command to everything under a prefix:
169
+
170
+ ```bash
171
+ motherbase storage ls # buckets
172
+ motherbase storage ls ss:///media/img # one level; -r for every key
173
+ motherbase storage cp ./logo.png ss:///media/img/ # upload (overwrites)
174
+ motherbase storage cp -r ./site ss:///web/v2 # upload a directory tree
175
+ motherbase storage cp -r ss:///web/v2 ./site-backup # download a prefix
176
+ motherbase storage mv -r ss:///web/v2 ss:///web/live # move within a bucket
177
+ motherbase storage rm -r ss:///web/old
178
+ ```
179
+
180
+ Bulk commands send one request per object and stop at the first failure,
181
+ saying how many had already gone through. `mv` and in-bucket `cp` work within
182
+ one bucket. The older forms still work: `cp ./a.png bucket:` uploads with a
183
+ generated key, `cp <fileId> ./a.png` downloads and `rm <fileId>` deletes by id.
184
+
185
+ ## Database inspection
186
+
187
+ `inspect db` runs the Supabase CLI's diagnostic queries against the linked
188
+ project, on its read-only SQL endpoint:
189
+
190
+ ```bash
191
+ motherbase inspect db outliers # slowest queries by total time
192
+ motherbase inspect db table-stats -o json
193
+ motherbase inspect report --output-dir reports # one CSV per query in reports/<YYYY-MM-DD>/
194
+ ```
195
+
196
+ The queries are `bloat`, `blocking`, `calls`, `db-stats`, `index-stats`,
197
+ `locks`, `long-running-queries`, `outliers`, `replication-slots`, `role-stats`,
198
+ `table-stats`, `traffic-profile` and `vacuum-stats`. The names Supabase has
199
+ deprecated (`cache-hit`, `index-usage`, `table-sizes`, `unused-indexes`, …)
200
+ still work and run the query that replaced them. `calls` and `outliers` read
201
+ `pg_stat_statements`: it is preloaded by the Motherbase Postgres image but has
202
+ to be created once per database (Studio > Database > Extensions, or
203
+ `motherbase db sql -e "create extension pg_stat_statements"`); `inspect report`
204
+ skips those two files until it is.
205
+
206
+ ## MCP server
207
+
208
+ `motherbase mcp` serves this project to an AI agent over stdio:
209
+
210
+ ```json
211
+ { "mcpServers": { "motherbase": { "command": "npx", "args": ["@motherbase/cli", "mcp"] } } }
212
+ ```
213
+
214
+ It exposes what the session can reach. With a linked project (or direct mode):
215
+ `list_tables`, `describe_table`, `list_policies`, `simulate_rls`,
216
+ `get_advisors`, `get_logs`, `generate_types`, `execute_sql` (read-only),
217
+ `get_project_url`, `get_publishable_keys`, `list_extensions`,
218
+ `list_migrations`, `list_edge_functions`, `get_edge_function`,
219
+ `list_storage_buckets` and `get_storage_config`. With a console login:
220
+ `list_organizations`, `get_organization`, `list_projects` and `get_project`.
221
+
222
+ Everything that changes something needs `--write`: `apply_migration`,
223
+ `execute_sql_write`, `deploy_edge_function`, `create_project`,
224
+ `pause_project` and `restore_project`. The names and arguments follow
225
+ Supabase's MCP server.
226
+
227
+ ## Supabase migration
228
+
229
+ Preview a Supabase import without writing to Motherbase:
230
+
231
+ ```bash
232
+ motherbase import --from-supabase "$SUPABASE_DB_URL" --preview --json
233
+ ```
234
+
235
+ Run the import:
236
+
237
+ ```bash
238
+ motherbase import --from-supabase "$SUPABASE_DB_URL" --yes
239
+ ```
240
+
241
+ The importer moves the portable parts of a Supabase project: `public` schema, table data, RLS policies, and email/password users from `auth.users`. Storage objects are not copied. Use `--no-data` or `--no-auth` for partial imports.
242
+
243
+ ## Tests
244
+
245
+ ```bash
246
+ npm test # node --test, mocked fetch, no network/DB
247
+ ```
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@motherbase/cli",
3
- "version": "0.2.4",
4
- "description": "Motherbase operator CLI — drives the Platform Console and linked projects.",
3
+ "version": "0.2.5",
4
+ "description": "Motherbase operator CLI: drives the Platform Console and linked projects.",
5
5
  "type": "commonjs",
6
6
  "bin": {
7
7
  "motherbase": "./bin/motherbase.js"
package/src/api.js CHANGED
@@ -19,6 +19,7 @@ const fs = require('fs');
19
19
  const path = require('path');
20
20
  const { Readable } = require('stream');
21
21
  const { CliError } = require('./context');
22
+ const ui = require('./ui');
22
23
 
23
24
  function buildQuery(query) {
24
25
  if (!query) return '';
@@ -38,8 +39,8 @@ function unwrap(body) {
38
39
 
39
40
  // Non-browser clients send no Origin header. Better Auth (and reverse proxies
40
41
  // that inject cookies) then treat the request as CSRF-suspect and reject it with
41
- // "Missing or null Origin". Sending the target's own origin — which the server
42
- // keeps in its trustedOrigins (baseURL) — satisfies that check for every route.
42
+ // "Missing or null Origin". Sending the target's own origin (which the server
43
+ // keeps in its trustedOrigins (baseURL)) satisfies that check for every route.
43
44
  function originFor(url) {
44
45
  try { return new URL(url).origin; } catch { return undefined; }
45
46
  }
@@ -58,6 +59,13 @@ function errorMessage(status, body, text) {
58
59
  return `Request failed (${status})${snippet ? `: ${snippet}` : ''}`;
59
60
  }
60
61
 
62
+ // `--debug` (or MOTHERBASE_DEBUG=1): one stderr line per request. Never the
63
+ // headers or body, which carry the token and passwords.
64
+ function trace(method, url, res) {
65
+ if (!process.env.MOTHERBASE_DEBUG) return;
66
+ ui.printErr(ui.color.dim(`[debug] ${method} ${url} → ${res.status}`));
67
+ }
68
+
61
69
  async function parseResponse(res) {
62
70
  const text = await res.text();
63
71
  let body;
@@ -88,6 +96,7 @@ async function doFetch(url, { method = 'GET', token, body, headers = {} } = {})
88
96
  } catch (err) {
89
97
  throw new CliError(`Cannot reach ${url}: ${err.message}`);
90
98
  }
99
+ trace(method, url, res);
91
100
  return parseResponse(res);
92
101
  }
93
102
 
@@ -117,6 +126,7 @@ async function loginRequest(consoleUrl, email, password) {
117
126
  } catch (err) {
118
127
  throw new CliError(`Cannot reach ${url}: ${err.message}`);
119
128
  }
129
+ trace('POST', url, res);
120
130
  const text = await res.text();
121
131
  let body;
122
132
  if (text) { try { body = JSON.parse(text); } catch { body = undefined; } }
@@ -184,6 +194,7 @@ async function uploadFile(ctx, apiPath, filePath, fields = {}) {
184
194
  } catch (err) {
185
195
  throw new CliError(`Cannot reach ${url}: ${err.message}`);
186
196
  }
197
+ trace('POST', url, res);
187
198
  return parseResponse(res);
188
199
  }
189
200
 
@@ -202,6 +213,7 @@ async function downloadFile(ctx, apiPath, destPath, { query } = {}) {
202
213
  } catch (err) {
203
214
  throw new CliError(`Cannot reach ${url}: ${err.message}`);
204
215
  }
216
+ trace('GET', url, res);
205
217
  if (!res.ok) {
206
218
  const text = await res.text().catch(() => '');
207
219
  let body; try { body = JSON.parse(text); } catch { /* noop */ }
@@ -3,6 +3,7 @@
3
3
  // Small shared helpers for command modules.
4
4
 
5
5
  const { CliError } = require('../context');
6
+ const { outputFormat, toYaml, toEnv } = require('../format');
6
7
 
7
8
  // Parse `key=value` tokens into an object. Values that look like numbers/bools are
8
9
  // coerced unless `raw` is set. Supports dotted keys via `--json`-style callers.
@@ -23,14 +24,45 @@ function parseKeyValues(tokens, { coerce = true } = {}) {
23
24
  return out;
24
25
  }
25
26
 
26
- // Emit either raw JSON (when --json) or run the human renderer.
27
+ // Render a command's result in the selected `-o/--output` format; `pretty` is
28
+ // the command's own human renderer.
27
29
  function output(ctx, data, humanFn) {
28
30
  const ui = require('../ui');
29
- if (ctx.flags.json) {
30
- ui.json(data);
31
- return;
31
+ switch (outputFormat(ctx.flags)) {
32
+ case 'json': ui.json(data); return;
33
+ case 'yaml': process.stdout.write(toYaml(data)); return;
34
+ case 'env': process.stdout.write(toEnv(data)); return;
35
+ default: humanFn(data, ui);
32
36
  }
33
- humanFn(data, ui);
37
+ }
38
+
39
+ // The same command under a second (Supabase) name: one implementation, with
40
+ // help that shows the name it was reached by.
41
+ function alias(entry, usage, summary = entry.summary) {
42
+ return { ...entry, usage, summary };
43
+ }
44
+
45
+ // Parse a dotenv file: `KEY=value`, `export KEY=value`, `#` comments, and
46
+ // single- or double-quoted values (double quotes expand \n).
47
+ function parseDotenv(text) {
48
+ const out = {};
49
+ for (const raw of String(text).split(/\r?\n/)) {
50
+ const line = raw.trim().replace(/^export\s+/, '');
51
+ if (!line || line.startsWith('#')) continue;
52
+ const idx = line.indexOf('=');
53
+ if (idx <= 0) throw new CliError(`invalid env line: "${raw}"`);
54
+ const key = line.slice(0, idx).trim();
55
+ let value = line.slice(idx + 1).trim();
56
+ const quote = value[0];
57
+ if ((quote === '"' || quote === "'") && value.endsWith(quote) && value.length > 1) {
58
+ value = value.slice(1, -1);
59
+ if (quote === '"') value = value.replace(/\\n/g, '\n');
60
+ } else {
61
+ value = value.replace(/\s+#.*$/, '');
62
+ }
63
+ out[key] = value;
64
+ }
65
+ return out;
34
66
  }
35
67
 
36
68
  function requireArg(value, name) {
@@ -47,4 +79,15 @@ function splitRef(ref) {
47
79
  return { bucket: ref.slice(0, idx), rest: ref.slice(idx + 1) };
48
80
  }
49
81
 
50
- module.exports = { parseKeyValues, output, requireArg, splitRef };
82
+ // `/projects/<linked project>` on the Console, for the per-project platform
83
+ // surfaces (branches, domains) that live on the Console rather than the
84
+ // project API. They need a Console, so direct mode cannot reach them.
85
+ function consoleProjectPath(ctx, suffix = '') {
86
+ ctx.requireConsole();
87
+ if (!ctx.projectId) {
88
+ throw new CliError('No project linked. Run `motherbase link` or pass --project-ref <id>.');
89
+ }
90
+ return `/projects/${encodeURIComponent(ctx.projectId)}${suffix}`;
91
+ }
92
+
93
+ module.exports = { parseKeyValues, output, alias, parseDotenv, requireArg, splitRef, consoleProjectPath };
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ // Supabase's top-level `backups`. The implementations live under `db`, which
4
+ // keeps its own names for them.
5
+
6
+ const db = require('./db');
7
+ const { alias } = require('./_util');
8
+
9
+ module.exports = {
10
+ summary: 'List and restore database backups',
11
+ usage: 'motherbase backups <list|restore> ...',
12
+ subcommands: {
13
+ list: alias(db.subcommands.backups, 'motherbase backups list', 'List database backups'),
14
+ restore: alias(db.subcommands.restore, 'motherbase backups restore <file> | --key <remoteKey> --confirm'),
15
+ },
16
+ };