This page describes the implementation of @motherbase/cli. For installation and user workflows, see CLI Overview, CLI Getting Started, and CLI Commands.
The package is dependency-free CommonJS and requires Node.js 18 or newer. packages/cli/bin/motherbase.js passes process.argv.slice(2) to main() and maps its return value or thrown error to the process exit code.
Dispatch pipeline
packages/cli/src/main.js owns tokenization, help, command traversal, context construction, and top-level error handling.
tokenize(argv)separates positionals and flags.main()handles version/help and finds the first positional in the registry.- It descends through nested
subcommandswhile names match. - It creates context only for a runnable command.
- It calls
run(ctx, remainingArgs, flags). CliErrormessages are printed directly; unexpected failures receive anError:prefix.
Long flags accept --name=value or, unless known boolean, consume the next non-flag token. Repeated flags become arrays. -- stops flag parsing. Short aliases are -y, -h, -v, -p, -u, -e, and -f. The parser's known booleans include JSON/help/version, confirmations, codegen check/local, and RLS replace.
The registry in packages/cli/src/commands/index.js is the single source for dispatch and generated help text.
Command tree
Each leaf is a module object with summary, usage, and async run. Group nodes expose subcommands.
connection
├─ login
├─ logout
├─ whoami
├─ link
├─ unlink
└─ status
projects
├─ list
├─ create
├─ start
├─ stop
├─ restart
├─ update
├─ rebuild
├─ delete
├─ logs
├─ info
└─ limits
local self-host
├─ init
├─ start
├─ stop
├─ deploy
└─ logs
database
├─ db
│ ├─ tables
│ ├─ sql
│ ├─ dump
│ ├─ restore
│ └─ backups
├─ migrations
│ ├─ list
│ ├─ diff
│ ├─ get
│ ├─ create
│ ├─ apply
│ └─ rollback
├─ migration
│ └─ new
└─ import
resources
├─ rls
│ ├─ list
│ ├─ enable
│ ├─ disable
│ ├─ policy
│ │ ├─ create
│ │ └─ delete
│ ├─ preset
│ ├─ simulate
│ └─ lint
├─ users
│ ├─ list
│ ├─ get
│ ├─ create
│ ├─ update
│ ├─ delete
│ ├─ reset-password
│ ├─ promote
│ ├─ demote
│ └─ revoke-sessions
├─ storage
│ ├─ buckets
│ │ ├─ list
│ │ ├─ create
│ │ └─ delete
│ ├─ ls
│ ├─ cp
│ └─ rm
├─ functions
│ ├─ list
│ ├─ get
│ ├─ new
│ ├─ deploy
│ ├─ pull
│ ├─ serve
│ ├─ delete
│ ├─ invoke
│ └─ logs
├─ secrets
│ ├─ list
│ ├─ set
│ └─ unset
├─ realtime
│ ├─ config
│ └─ channels
├─ webhooks
│ ├─ list
│ ├─ create
│ ├─ delete
│ └─ test
├─ cron
│ ├─ list
│ ├─ create
│ ├─ delete
│ └─ toggle
├─ config
│ ├─ list
│ ├─ get
│ └─ set
└─ inspect
├─ logs
├─ stats
├─ performance
├─ requests
├─ advisor
└─ shield
codegen
└─ gen
└─ types
└─ typescriptdb backups config is handled as an argument inside the backups leaf rather than as a registry subcommand. Project lifecycle leaves are generated by one lifecycleCmd(action) helper in packages/cli/src/commands/projects.js.
Command module boundaries
| Area | Source | Internal responsibility |
|---|---|---|
| Connection | packages/cli/src/commands/login.js, packages/cli/src/commands/logout.js, packages/cli/src/commands/whoami.js, packages/cli/src/commands/link.js, packages/cli/src/commands/unlink.js, packages/cli/src/commands/status.js | Credentials, directory link, and health view |
| Platform projects | packages/cli/src/commands/projects.js | Console lifecycle API and lifecycle command factory |
| Local stack | packages/cli/src/commands/local.js | docker compose child processes and local initialization |
| Database | packages/cli/src/commands/db.js, packages/cli/src/commands/db-catalog.js, packages/cli/src/commands/migration.js, packages/cli/src/commands/import.js, packages/cli/src/commands/migrate.js | SQL and files; the db indexes|types|roles|publications catalog views; the server migration lifecycle plus the local new scaffold (migrations is an alias of migration); Supabase import; and migrate codemod, which ports a Supabase codebase's imports and reports what this SDK does not implement |
| Security/auth | packages/cli/src/commands/rls.js, packages/cli/src/commands/users.js | RLS policy/preset/simulation/advisor calls and user administration |
| Storage/functions | packages/cli/src/commands/storage.js, packages/cli/src/commands/functions.js | Multipart/stream I/O and repository-backed edge-function deployment |
| Operations | packages/cli/src/commands/secrets.js, packages/cli/src/commands/realtime.js, packages/cli/src/commands/webhooks.js, packages/cli/src/commands/cron.js, packages/cli/src/commands/config.js, packages/cli/src/commands/inspect.js | Thin admin-resource transports and human renderers |
| Code generation | packages/cli/src/commands/gen.js | Fetches server-rendered database types, writes/checks files, or emits stdout |
The commands deliberately delegate HTTP mechanics to packages/cli/src/api.js and shared parsing/rendering to packages/cli/src/commands/_util.js. Edge-function type generation is server-owned; gen transports the rendered file instead of duplicating the emitter.
Configuration files
packages/cli/src/config.js separates secrets from project routing.
Global credentials:
{
"consoles": {
"https://console.example.com": {
"token": "...",
"email": "operator@example.com"
}
},
"current": "https://console.example.com"
}This lives at ~/.motherbase/config.json. writeGlobal() creates it with mode 0600 and makes a best-effort chmod(0600) for an existing file.
The directory link:
{
"consoleUrl": "https://console.example.com",
"projectId": "todo",
"projectName": "todo"
}This lives at ./.motherbase/config.json and contains no token. URL normalization only removes trailing slashes. Invalid or unreadable JSON is treated as missing configuration.
Lazy context resolution
createContext(flags, env, cwd) in packages/cli/src/context.js never requires a connection immediately. This lets local commands and login run without a linked project. Values are resolved with the following precedence:
| Value | Resolution order |
|---|---|
| Console URL | --url → MOTHERBASE_CONSOLE_URL → local link.consoleUrl → global current |
| Console token | --token → MOTHERBASE_TOKEN → token stored for the resolved Console |
| Project id | --project → MOTHERBASE_PROJECT → local link.projectId |
| Project name | local link.projectName → project id |
| Direct URL | MOTHERBASE_URL |
| Direct token | MOTHERBASE_ADMIN_TOKEN |
Direct mode is selected only when both direct environment values exist and the context does not already have both a Console credential and a project link. Otherwise it stays in Console mode.
requireConsole() checks URL then token. requireProject() returns either a direct target or a Console proxy target and throws an actionable CliError only when the current command needs a missing value.
Flags override environment/configuration for Console mode. Direct project mode is intentionally environment-driven; gen types typescript --local is the exception and constructs a temporary direct context from its --url/--token values.
Login and token lifecycle
packages/cli/src/commands/login.js supports two paths:
--tokenstores a supplied token directly for non-interactive use.- Email/password prompts call
loginRequest().
loginRequest() in packages/cli/src/api.js posts JSON to /api/auth/sign-in/email, adds the target's own Origin header for Better Auth CSRF checks, and reads the session token from either body.token or the set-auth-token response header. The login command then stores it under the normalized Console URL and marks that Console current.
Password input in packages/cli/src/prompt.js uses raw TTY mode, masks printable characters, handles backspace and Ctrl-C, and ignores escape sequences. Non-TTY input falls back to a normal line prompt. Logout removes only the selected Console entry and clears current when it points to that URL.
HTTP routing
All network commands use packages/cli/src/api.js.
| Operation | URL shape |
|---|---|
| Console account/project | <consoleUrl>/api/platform<path> |
| Project admin through Console | <consoleUrl>/api/projects/<projectId>/admin<path> |
| Direct project admin | <directUrl>/api/admin<path> |
| Edge invoke through Console | <consoleUrl>/api/projects/<projectId>/edge/<route> |
| Direct edge invoke | <directUrl>/api/edge/<route> |
doFetch() adds the bearer token, adds the destination origin when no origin header exists, serializes JSON bodies, and maps network errors to CliError. parseResponse() parses JSON when possible, unwraps a top-level data property, and turns non-2xx responses into status-specific messages. A 401 message includes the login hint.
uploadFile() uses Node's native FormData/Blob and reads the source file before sending. downloadFile() bridges the web response body through Readable.fromWeb into a file stream. Both use the same target selection, auth, origin, and error mapping.
Output and prompts
packages/cli/src/ui.js provides dependency-free output helpers:
printandprintErr;- semantic
success,info, andwarn; - pretty
json; - aligned
tableandkeyValuerenderers; - a TTY spinner with a non-TTY status-line fallback.
ANSI color is enabled only when stdout is a TTY and NO_COLOR is not set. Tables print (no rows) for empty input.
output(ctx, data, humanFn) in packages/cli/src/commands/_util.js is the common output switch: --json bypasses the human renderer and prints the complete value. The same utility module centralizes required positional arguments, key=value coercion, and bucket:path splitting. Interactive confirmations and questions live in packages/cli/src/prompt.js.
Local and long-running commands
packages/cli/src/commands/local.js is the only Docker layer. It spawns docker compose with inherited stdio and a hidden Windows child window. init scaffolds a directory and copies .env.example when present.
Two command paths intentionally keep the process alive:
- project log following polls Console snapshots every three seconds;
- function serving watches function directories, coalesces editor save bursts for 150 ms, and redeploys to the real project runtime instead of implementing a second local one.
These behaviors live in packages/cli/src/commands/projects.js and packages/cli/src/commands/functions.js.