@butterbase/cli 0.2.1 → 0.3.2

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 (66) hide show
  1. package/README.md +118 -0
  2. package/dist/bin/butterbase.js +474 -2
  3. package/dist/bin/butterbase.js.map +1 -1
  4. package/dist/src/commands/ai.d.ts +36 -0
  5. package/dist/src/commands/ai.d.ts.map +1 -0
  6. package/dist/src/commands/ai.js +131 -0
  7. package/dist/src/commands/ai.js.map +1 -0
  8. package/dist/src/commands/app-billing.d.ts +62 -0
  9. package/dist/src/commands/app-billing.d.ts.map +1 -0
  10. package/dist/src/commands/app-billing.js +220 -0
  11. package/dist/src/commands/app-billing.js.map +1 -0
  12. package/dist/src/commands/app-config.d.ts +37 -0
  13. package/dist/src/commands/app-config.d.ts.map +1 -0
  14. package/dist/src/commands/app-config.js +152 -0
  15. package/dist/src/commands/app-config.js.map +1 -0
  16. package/dist/src/commands/audit.d.ts +15 -0
  17. package/dist/src/commands/audit.d.ts.map +1 -0
  18. package/dist/src/commands/audit.js +49 -0
  19. package/dist/src/commands/audit.js.map +1 -0
  20. package/dist/src/commands/billing.d.ts +26 -0
  21. package/dist/src/commands/billing.d.ts.map +1 -0
  22. package/dist/src/commands/billing.js +183 -0
  23. package/dist/src/commands/billing.js.map +1 -0
  24. package/dist/src/commands/do.js +1 -1
  25. package/dist/src/commands/do.js.map +1 -1
  26. package/dist/src/commands/functions.d.ts +16 -0
  27. package/dist/src/commands/functions.d.ts.map +1 -1
  28. package/dist/src/commands/functions.js +135 -2
  29. package/dist/src/commands/functions.js.map +1 -1
  30. package/dist/src/commands/integrations.d.ts +2 -0
  31. package/dist/src/commands/integrations.d.ts.map +1 -1
  32. package/dist/src/commands/integrations.js +2 -2
  33. package/dist/src/commands/integrations.js.map +1 -1
  34. package/dist/src/commands/keys.d.ts +1 -0
  35. package/dist/src/commands/keys.d.ts.map +1 -1
  36. package/dist/src/commands/keys.js +1 -1
  37. package/dist/src/commands/keys.js.map +1 -1
  38. package/dist/src/commands/move.d.ts +25 -0
  39. package/dist/src/commands/move.d.ts.map +1 -0
  40. package/dist/src/commands/move.js +157 -0
  41. package/dist/src/commands/move.js.map +1 -0
  42. package/dist/src/commands/oauth.d.ts +33 -0
  43. package/dist/src/commands/oauth.d.ts.map +1 -0
  44. package/dist/src/commands/oauth.js +122 -0
  45. package/dist/src/commands/oauth.js.map +1 -0
  46. package/dist/src/commands/rag.d.ts +44 -0
  47. package/dist/src/commands/rag.d.ts.map +1 -0
  48. package/dist/src/commands/rag.js +271 -0
  49. package/dist/src/commands/rag.js.map +1 -0
  50. package/dist/src/commands/regions.d.ts +4 -0
  51. package/dist/src/commands/regions.d.ts.map +1 -0
  52. package/dist/src/commands/regions.js +16 -0
  53. package/dist/src/commands/regions.js.map +1 -0
  54. package/dist/src/commands/rls.d.ts +26 -0
  55. package/dist/src/commands/rls.d.ts.map +1 -0
  56. package/dist/src/commands/rls.js +128 -0
  57. package/dist/src/commands/rls.js.map +1 -0
  58. package/dist/src/lib/api-client.d.ts +86 -3
  59. package/dist/src/lib/api-client.d.ts.map +1 -1
  60. package/dist/src/lib/api-client.js +229 -9
  61. package/dist/src/lib/api-client.js.map +1 -1
  62. package/dist/src/lib/errors.d.ts +8 -0
  63. package/dist/src/lib/errors.d.ts.map +1 -0
  64. package/dist/src/lib/errors.js +24 -0
  65. package/dist/src/lib/errors.js.map +1 -0
  66. package/package.json +2 -1
package/README.md CHANGED
@@ -139,6 +139,108 @@ butterbase storage upload ./image.png
139
139
  butterbase storage delete obj_abc123
140
140
  ```
141
141
 
142
+ ### AI
143
+
144
+ ```bash
145
+ butterbase ai chat "Summarize this" --model openai/gpt-4o-mini
146
+ butterbase ai chat "Explain RAG" --system "You're a concise teacher." --temperature 0.2
147
+ butterbase ai embed "hello world" "another doc"
148
+ butterbase ai models
149
+ butterbase ai config get
150
+ butterbase ai config set --default-model openai/gpt-4o-mini --max-tokens-per-request 4000
151
+ butterbase ai config set --byok-key sk-or-... # rotate BYOK key; "" to clear
152
+ butterbase ai usage --start-date 2026-05-01 --end-date 2026-05-31
153
+ ```
154
+
155
+ ### OAuth (admin)
156
+
157
+ ```bash
158
+ butterbase oauth configure google \
159
+ --client-id ... --client-secret ... \
160
+ --redirect-uri https://app.example/cb \
161
+ --scope openid --scope email
162
+ butterbase oauth list
163
+ butterbase oauth get google
164
+ butterbase oauth update google --enabled false
165
+ butterbase oauth delete google
166
+ ```
167
+
168
+ ### Audit logs
169
+
170
+ ```bash
171
+ butterbase audit query --category auth --event-type login --limit 50
172
+ butterbase audit query --from 2026-05-01 --to 2026-05-31 --action create --resource-type user
173
+ butterbase audit query --actor-id user_123 --json
174
+ ```
175
+
176
+ ### App config (server-side)
177
+
178
+ ```bash
179
+ butterbase apps config get
180
+ butterbase apps config cors --allowed-origin https://app.example --allow-credentials true
181
+ butterbase apps config jwt --access-token-ttl 15m --refresh-token-ttl-days 30
182
+ butterbase apps config storage --public-read true --max-file-size-mb 25 --allowed-content-type image/png --allowed-content-type image/jpeg
183
+ butterbase apps config access-mode authenticated
184
+ butterbase apps config secure --table posts --table comments --user-column author_id --access-mode authenticated
185
+ ```
186
+
187
+ ### Regions + multi-region moves
188
+
189
+ ```bash
190
+ butterbase regions list
191
+ butterbase apps move app_abc us-west-2 --follow
192
+ butterbase apps migrations active # current app
193
+ butterbase apps migrations status app_abc m_xyz
194
+ butterbase apps migrations abort app_abc m_xyz # before cutover
195
+ butterbase apps migrations reverse app_abc m_xyz # after cutover
196
+ butterbase apps replicas list
197
+ butterbase apps replicas teardown m_xyz
198
+ ```
199
+
200
+ ### App-level billing (Stripe Connect)
201
+
202
+ ```bash
203
+ butterbase app-billing plans list
204
+ butterbase app-billing plans create --name pro --price-cents 1999 --interval month
205
+ butterbase app-billing plans update plan_abc --price-cents 2499
206
+ butterbase app-billing products list
207
+ butterbase app-billing products create --name "Lifetime access" --price-cents 9900
208
+ butterbase app-billing subscribe plan_abc
209
+ butterbase app-billing subscription
210
+ butterbase app-billing cancel
211
+ butterbase app-billing purchase prod_xyz
212
+ butterbase app-billing orders list
213
+ butterbase app-billing orders get order_abc
214
+ ```
215
+
216
+ ### Scoped API keys + integrations
217
+
218
+ ```bash
219
+ butterbase keys generate ci-key --scope schema:read --scope functions:invoke
220
+ butterbase integrations configure github --scope repo --scope read:user
221
+ butterbase integrations connect github --redirect-url https://app.example/cb --scope repo
222
+ ```
223
+
224
+ ### Functions deploy (full options)
225
+
226
+ ```bash
227
+ butterbase functions deploy fn.ts \
228
+ --name my-fn \
229
+ --trigger cron --trigger-config '{"schedule":"*/5 * * * *"}' \
230
+ --env API_KEY=sk_... --env DEBUG=true \
231
+ --timeout-ms 9000 --memory-mb 256
232
+ ```
233
+
234
+ ### RLS
235
+
236
+ ```bash
237
+ butterbase rls create --table posts --policy-name posts_own \
238
+ --command SELECT --using "author_id = auth.uid()" \
239
+ --role user --restrictive
240
+ butterbase rls delete posts # delete all policies on table
241
+ butterbase rls delete posts --policy posts_own
242
+ ```
243
+
142
244
  ## Global Options
143
245
 
144
246
  Most commands support the `--app` flag to specify an app ID:
@@ -284,6 +386,22 @@ butterbase integrations tools gmail --app app_abc123
284
386
  butterbase integrations execute GMAIL_SEND_EMAIL --data '{"to":"x@y.com","subject":"Hi","body":"Hello"}' --app app_abc123
285
387
  ```
286
388
 
389
+ ## Error output
390
+
391
+ The CLI throws typed `ButterbaseError`s from `@butterbase/sdk`. The top-level
392
+ handler renders the class name, message, and the structured fields the backend
393
+ returned (`code`, `status`, `remediation`). Example for an unauthenticated call:
394
+
395
+ ```
396
+ AuthError: Invalid API key
397
+ code: AUTH_INVALID_API_KEY
398
+ status: 401
399
+ remediation: Rotate the key with `butterbase keys generate` and update ~/.butterbase/config.json.
400
+ ```
401
+
402
+ The error codes come from `@butterbase/shared`'s `ErrorCodes` namespace — see
403
+ the SDK README for the full list.
404
+
287
405
  ## License
288
406
 
289
407
  MIT
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
+ import { renderError } from '../src/lib/errors.js';
3
4
  import { initCommand } from '../src/commands/init.js';
4
5
  import { loginCommand, logoutCommand, configGetCommand, configSetCommand } from '../src/commands/config.js';
5
6
  import { appsListCommand, appsCreateCommand, appsUseCommand, appsDeleteCommand, appsPauseCommand, appsResumeCommand } from '../src/commands/apps.js';
6
7
  import { schemaGetCommand, schemaApplyCommand } from '../src/commands/schema.js';
7
- import { functionsListCommand, functionsDeployCommand, functionsLogsCommand, functionsDeleteCommand } from '../src/commands/functions.js';
8
+ import { functionsListCommand, functionsDeployCommand, functionsLogsCommand, functionsDeleteCommand, functionsInvokeCommand, functionsEnvSetCommand, functionsEnvListCommand } from '../src/commands/functions.js';
8
9
  import { storageListCommand, storageUploadCommand, storageDeleteCommand, storageConfigCommand } from '../src/commands/storage.js';
9
10
  import { realtimeEnableCommand, realtimeConfigCommand, realtimeDisableCommand } from '../src/commands/realtime.js';
10
11
  import { deployCommand } from '../src/commands/deploy.js';
@@ -20,7 +21,17 @@ import { pluginSetupCommand } from '../src/commands/plugin.js';
20
21
  import { integrationsListCommand, integrationsConfigCommand, integrationsConfigureCommand, integrationsDisableCommand, integrationsConnectCommand, integrationsConnectionsCommand, integrationsDisconnectCommand, integrationsToolsCommand, integrationsExecuteCommand, } from '../src/commands/integrations.js';
21
22
  import { domainsListCommand, domainsAddCommand, domainsStatusCommand, domainsVerifyCommand, domainsDeleteCommand, } from '../src/commands/domains.js';
22
23
  import { partnersListCommand, partnersCurlCommand } from '../src/commands/partners.js';
24
+ import { rlsListCommand, rlsCreateCommand, rlsEnableCommand, rlsDeleteCommand } from '../src/commands/rls.js';
25
+ import { billingStatusCommand, billingPortalCommand, billingTopupCommand, billingCapGetCommand, billingCapRaiseCommand, billingPlansCommand, billingUsageCommand, } from '../src/commands/billing.js';
26
+ import { ragCollectionsListCommand, ragCollectionsCreateCommand, ragCollectionsGetCommand, ragCollectionsDeleteCommand, ragIngestCommand, ragDocsListCommand, ragDocsDeleteCommand, ragQueryCommand, } from '../src/commands/rag.js';
23
27
  import { doDeployCommand, doListCommand, doGetCommand, doDeleteCommand, doUsageCommand, doEnvListCommand, doEnvSetCommand, doEnvUnsetCommand, } from '../src/commands/do.js';
28
+ import { aiChatCommand, aiEmbedCommand, aiModelsCommand, aiConfigGetCommand, aiConfigSetCommand, aiUsageCommand, } from '../src/commands/ai.js';
29
+ import { oauthConfigureCommand, oauthListCommand, oauthGetCommand, oauthUpdateCommand, oauthDeleteCommand, } from '../src/commands/oauth.js';
30
+ import { auditQueryCommand } from '../src/commands/audit.js';
31
+ import { appConfigGetCommand, appCorsCommand, appJwtCommand, appStorageCommand, appAccessModeCommand, appSecureCommand, } from '../src/commands/app-config.js';
32
+ import { regionsListCommand } from '../src/commands/regions.js';
33
+ import { moveCommand, migrationStatusCommand, migrationActiveCommand, migrationAbortCommand, migrationReverseCommand, replicasListCommand, replicaTeardownCommand, } from '../src/commands/move.js';
34
+ import { plansListCommand, plansCreateCommand, plansUpdateCommand, productsListCommand, productsCreateCommand, productsUpdateCommand, subscribeCommand, subscriptionCommand, cancelCommand, purchaseCommand, ordersListCommand, ordersGetCommand, } from '../src/commands/app-billing.js';
24
35
  const program = new Command();
25
36
  program
26
37
  .name('butterbase')
@@ -77,6 +88,94 @@ apps
77
88
  .command('resume [app-id]')
78
89
  .description('Resume a paused app — restore data-plane traffic')
79
90
  .action(appsResumeCommand);
91
+ const appsConfig = apps.command('config').description('Read or update the app\'s server-side config');
92
+ appsConfig
93
+ .command('get')
94
+ .description('Show the app\'s full config')
95
+ .option('--app <appId>', 'Override current app')
96
+ .option('--json', 'Output raw JSON')
97
+ .action((opts) => appConfigGetCommand(opts));
98
+ appsConfig
99
+ .command('cors')
100
+ .description('Update CORS config')
101
+ .option('--app <appId>', 'Override current app')
102
+ .option('--allowed-origin <origin>', 'Allowed origin (repeatable)', (v, prev) => prev.concat(v), [])
103
+ .option('--allowed-method <method>', 'Allowed method (repeatable)', (v, prev) => prev.concat(v), [])
104
+ .option('--allowed-header <header>', 'Allowed header (repeatable)', (v, prev) => prev.concat(v), [])
105
+ .option('--allow-credentials <bool>', '(true|false)', (v) => v === 'true')
106
+ .option('--json', 'Output raw JSON')
107
+ .action((opts) => appCorsCommand(opts));
108
+ appsConfig
109
+ .command('jwt')
110
+ .description('Update JWT TTLs')
111
+ .option('--app <appId>', 'Override current app')
112
+ .option('--access-token-ttl <duration>', 'e.g. "15m", "1h"')
113
+ .option('--refresh-token-ttl-days <n>', 'Refresh token lifetime in days', parseInt)
114
+ .option('--json', 'Output raw JSON')
115
+ .action((opts) => appJwtCommand(opts));
116
+ appsConfig
117
+ .command('storage')
118
+ .description('Update storage config')
119
+ .option('--app <appId>', 'Override current app')
120
+ .option('--public-read <bool>', 'Public-read default (true|false)', (v) => v === 'true')
121
+ .option('--max-file-size-mb <n>', 'Per-file size cap in MB', parseInt)
122
+ .option('--allowed-content-type <ct>', 'Allowed content-type (repeatable)', (v, prev) => prev.concat(v), [])
123
+ .option('--json', 'Output raw JSON')
124
+ .action((opts) => appStorageCommand(opts));
125
+ appsConfig
126
+ .command('access-mode <mode>')
127
+ .description('Set access mode: public | authenticated')
128
+ .option('--app <appId>', 'Override current app')
129
+ .option('--json', 'Output raw JSON')
130
+ .action((mode, opts) => appAccessModeCommand(mode, opts));
131
+ appsConfig
132
+ .command('secure')
133
+ .description('Enable RLS + access-mode in one shot')
134
+ .option('--app <appId>', 'Override current app')
135
+ .option('--table <name>', 'Table to secure (repeatable)', (v, prev) => prev.concat(v), [])
136
+ .option('--user-column <col>', 'User-id column name (default: user_id)')
137
+ .option('--access-mode <mode>', 'public | authenticated')
138
+ .option('--json', 'Output raw JSON')
139
+ .action((opts) => appSecureCommand(opts));
140
+ apps
141
+ .command('move <appId> <destRegion>')
142
+ .description('Migrate an app to another region')
143
+ .option('--follow', 'Poll status until terminal')
144
+ .option('--json', 'Output raw JSON')
145
+ .action((appId, destRegion, opts) => moveCommand(appId, destRegion, opts));
146
+ const appsMigrations = apps.command('migrations').description('Read or control in-flight migrations');
147
+ appsMigrations
148
+ .command('status <appId> <migrationId>')
149
+ .description('Get status of a specific migration')
150
+ .option('--json', 'Output raw JSON')
151
+ .action((appId, migrationId, opts) => migrationStatusCommand(appId, migrationId, opts));
152
+ appsMigrations
153
+ .command('active [appId]')
154
+ .description('Show the currently-active migration for an app')
155
+ .option('--app <appId>', 'Override current app')
156
+ .option('--json', 'Output raw JSON')
157
+ .action((appId, opts) => migrationActiveCommand(appId, opts));
158
+ appsMigrations
159
+ .command('abort <appId> <migrationId>')
160
+ .description('Cancel a migration that has not yet reached cutover')
161
+ .option('--json', 'Output raw JSON')
162
+ .action((appId, migrationId, opts) => migrationAbortCommand(appId, migrationId, opts));
163
+ appsMigrations
164
+ .command('reverse <appId> <migrationId>')
165
+ .description('Roll a completed migration back to source')
166
+ .option('--json', 'Output raw JSON')
167
+ .action((appId, migrationId, opts) => migrationReverseCommand(appId, migrationId, opts));
168
+ const appsReplicas = apps.command('replicas').description('Manage retained source replicas after a move');
169
+ appsReplicas
170
+ .command('list')
171
+ .description('List active retained source replicas')
172
+ .option('--json', 'Output raw JSON')
173
+ .action((opts) => replicasListCommand(opts));
174
+ appsReplicas
175
+ .command('teardown <migrationId>')
176
+ .description('Decommission a retained source replica')
177
+ .option('--json', 'Output raw JSON')
178
+ .action((migrationId, opts) => replicaTeardownCommand(migrationId, opts));
80
179
  // Schema
81
180
  const schema = program.command('schema').description('Manage database schema');
82
181
  schema
@@ -104,8 +203,12 @@ functions
104
203
  .description('Deploy a function')
105
204
  .option('--app <app-id>', 'App ID (uses current app if not specified)')
106
205
  .option('--name <name>', 'Function name (defaults to filename)')
107
- .option('--trigger <type>', 'Trigger type (http, cron)', 'http')
206
+ .option('--trigger <type>', 'Trigger type (http, cron, s3_upload, webhook, websocket)', 'http')
207
+ .option('--trigger-config <json>', 'Trigger config as JSON (e.g. \'{"schedule":"*/5 * * * *"}\')')
108
208
  .option('--description <desc>', 'Function description')
209
+ .option('--env <kv>', 'Env var as KEY=value (repeatable)', (v, prev) => prev.concat(v), [])
210
+ .option('--timeout-ms <n>', 'Per-invocation timeout (ms)', parseInt)
211
+ .option('--memory-mb <n>', 'Memory limit (MB)', parseInt)
109
212
  .action(functionsDeployCommand);
110
213
  functions
111
214
  .command('logs <function-name>')
@@ -120,6 +223,25 @@ functions
120
223
  .description('Delete a function')
121
224
  .option('--app <app-id>', 'App ID (uses current app if not specified)')
122
225
  .action(functionsDeleteCommand);
226
+ functions
227
+ .command('invoke <function-name>')
228
+ .description('Invoke a deployed function')
229
+ .option('--app <app-id>', 'App ID (uses current app if not specified)')
230
+ .option('--data <json>', 'Request body as JSON string')
231
+ .option('--json', 'Output as JSON')
232
+ .action(functionsInvokeCommand);
233
+ const functionsEnvCmd = functions.command('env').description('Manage function environment variables');
234
+ functionsEnvCmd
235
+ .command('set <function-name> <vars...>')
236
+ .description('Set env vars (KEY=VALUE pairs)')
237
+ .option('--app <app-id>', 'App ID (uses current app if not specified)')
238
+ .action(functionsEnvSetCommand);
239
+ functionsEnvCmd
240
+ .command('list <function-name>')
241
+ .description('List env var keys (values are write-only)')
242
+ .option('--app <app-id>', 'App ID (uses current app if not specified)')
243
+ .option('--json', 'Output as JSON')
244
+ .action(functionsEnvListCommand);
123
245
  // Storage
124
246
  const storage = program.command('storage').description('Manage file storage');
125
247
  storage
@@ -265,6 +387,7 @@ const keys = program.command('keys').description('Manage API keys');
265
387
  keys
266
388
  .command('generate [name]')
267
389
  .description('Generate a new API key')
390
+ .option('--scope <scope>', 'Add a scope (repeatable)', (v, prev) => prev.concat(v), [])
268
391
  .option('--json', 'Output as JSON')
269
392
  .action(keysGenerateCommand);
270
393
  keys
@@ -300,6 +423,7 @@ integrations
300
423
  .description('Enable a toolkit for the app')
301
424
  .option('--app <app-id>', 'App ID')
302
425
  .option('--display-name <name>', 'Human-readable display name')
426
+ .option('--scope <scope>', 'Add a scope (repeatable)', (v, prev) => prev.concat(v), [])
303
427
  .action(integrationsConfigureCommand);
304
428
  integrations
305
429
  .command('disable <toolkit>')
@@ -312,6 +436,7 @@ integrations
312
436
  .option('--app <app-id>', 'App ID')
313
437
  .option('--redirect-url <url>', 'URL to redirect after OAuth')
314
438
  .option('--user-id <uuid>', 'User ID (for API key auth)')
439
+ .option('--scope <scope>', 'Add a scope (repeatable)', (v, prev) => prev.concat(v), [])
315
440
  .action(integrationsConnectCommand);
316
441
  integrations
317
442
  .command('connections')
@@ -431,6 +556,353 @@ partners
431
556
  .option('-d, --data <body>', 'Request JSON body')
432
557
  .option('-x, --execute', 'Execute the curl instead of just printing it')
433
558
  .action((slug, path, opts) => partnersCurlCommand(slug, path, opts));
559
+ // RLS
560
+ const rls = program.command('rls').description('Manage Row-Level Security policies');
561
+ rls
562
+ .command('list')
563
+ .description('List RLS policies')
564
+ .option('--app <app-id>', 'App ID (uses current app if not specified)')
565
+ .option('--json', 'Output as JSON')
566
+ .action(rlsListCommand);
567
+ rls
568
+ .command('create')
569
+ .description('Create an RLS policy (user-isolation or custom)')
570
+ .option('--app <app-id>', 'App ID (uses current app if not specified)')
571
+ .option('--table <table>', 'Target table name')
572
+ .option('--user-isolation', 'Enable user-isolation mode (uses --table, --user-column, --public-read-column)')
573
+ .option('--user-column <col>', 'Column containing the user ID')
574
+ .option('--public-read-column <col>', 'Boolean column enabling public read override')
575
+ .option('--policy-name <name>', 'Policy name (custom mode)')
576
+ .option('--command <cmd>', 'SQL command (SELECT, INSERT, UPDATE, DELETE, ALL)')
577
+ .option('--using <expr>', 'USING expression')
578
+ .option('--with-check <expr>', 'WITH CHECK expression')
579
+ .option('--restrictive', 'Create as RESTRICTIVE policy')
580
+ .option('--role <role>', 'Restrict to a Postgres role (anon | user)')
581
+ .option('--json', 'Output as JSON')
582
+ .action(rlsCreateCommand);
583
+ rls
584
+ .command('enable <table>')
585
+ .description('Enable RLS on a table')
586
+ .option('--app <app-id>', 'App ID (uses current app if not specified)')
587
+ .action(rlsEnableCommand);
588
+ rls
589
+ .command('delete <table>')
590
+ .description('Delete RLS policies on a table (all by default, or named with --policy)')
591
+ .option('--app <app-id>', 'App ID (uses current app if not specified)')
592
+ .option('--policy <name>', 'Delete only this named policy')
593
+ .action(rlsDeleteCommand);
594
+ // Billing
595
+ const billing = program.command('billing').description('Manage billing, plans, and spending');
596
+ billing
597
+ .command('status')
598
+ .description('Show current plan and billing status')
599
+ .option('--json', 'Output as JSON')
600
+ .action(billingStatusCommand);
601
+ billing
602
+ .command('portal')
603
+ .description('Print the billing portal URL')
604
+ .option('--json', 'Output as JSON')
605
+ .action(billingPortalCommand);
606
+ billing
607
+ .command('topup <amount>')
608
+ .description('Add credit balance (amount in USD)')
609
+ .option('--json', 'Output as JSON')
610
+ .action(billingTopupCommand);
611
+ billing
612
+ .command('cap')
613
+ .description('Show current spending cap')
614
+ .option('--json', 'Output as JSON')
615
+ .action(billingCapGetCommand);
616
+ billing
617
+ .command('cap:raise')
618
+ .description('Raise the spending cap')
619
+ .option('--raise-by <amount>', 'Amount in USD to raise the cap by')
620
+ .option('--json', 'Output as JSON')
621
+ .action(billingCapRaiseCommand);
622
+ billing
623
+ .command('plans')
624
+ .description('List available plans')
625
+ .option('--json', 'Output as JSON')
626
+ .action(billingPlansCommand);
627
+ billing
628
+ .command('usage')
629
+ .description('Show usage metrics')
630
+ .option('--start <date>', 'Start date (ISO 8601)')
631
+ .option('--end <date>', 'End date (ISO 8601)')
632
+ .option('--meter <type>', 'Filter by meter type')
633
+ .option('--json', 'Output as JSON')
634
+ .action(billingUsageCommand);
635
+ // RAG
636
+ const rag = program.command('rag').description('Manage RAG collections and documents');
637
+ const ragCollections = rag.command('collections').description('Manage RAG collections');
638
+ ragCollections
639
+ .command('list')
640
+ .description('List RAG collections')
641
+ .option('--app <app-id>', 'App ID (uses current app if not specified)')
642
+ .option('--json', 'Output as JSON')
643
+ .action(ragCollectionsListCommand);
644
+ ragCollections
645
+ .command('create <name>')
646
+ .description('Create a RAG collection')
647
+ .option('--app <app-id>', 'App ID (uses current app if not specified)')
648
+ .option('--description <text>', 'Collection description')
649
+ .option('--access-mode <mode>', 'Access mode (public, authenticated, service_key)')
650
+ .option('--chunk-size <n>', 'Chunk size in tokens')
651
+ .option('--chunk-overlap <n>', 'Chunk overlap in tokens')
652
+ .option('--json', 'Output as JSON')
653
+ .action(ragCollectionsCreateCommand);
654
+ ragCollections
655
+ .command('get <name>')
656
+ .description('Get a RAG collection')
657
+ .option('--app <app-id>', 'App ID (uses current app if not specified)')
658
+ .option('--json', 'Output as JSON')
659
+ .action(ragCollectionsGetCommand);
660
+ ragCollections
661
+ .command('delete <name>')
662
+ .description('Delete a RAG collection')
663
+ .option('--app <app-id>', 'App ID (uses current app if not specified)')
664
+ .action(ragCollectionsDeleteCommand);
665
+ rag
666
+ .command('ingest <file-or-text>')
667
+ .description('Ingest a document or text into a RAG collection')
668
+ .requiredOption('--collection <name>', 'Target collection name')
669
+ .option('--app <app-id>', 'App ID (uses current app if not specified)')
670
+ .option('--text', 'Treat the argument as raw text instead of a file path')
671
+ .option('--filename <name>', 'Override the filename stored with the document')
672
+ .option('--metadata <json>', 'Document metadata as JSON string')
673
+ .option('--json', 'Output as JSON')
674
+ .action(ragIngestCommand);
675
+ const ragDocs = rag.command('docs').description('Manage RAG documents');
676
+ ragDocs
677
+ .command('list <collection>')
678
+ .description('List documents in a RAG collection')
679
+ .option('--app <app-id>', 'App ID (uses current app if not specified)')
680
+ .option('--json', 'Output as JSON')
681
+ .action(ragDocsListCommand);
682
+ ragDocs
683
+ .command('delete <collection> <doc-id>')
684
+ .description('Delete a document from a RAG collection')
685
+ .option('--app <app-id>', 'App ID (uses current app if not specified)')
686
+ .action(ragDocsDeleteCommand);
687
+ rag
688
+ .command('query <collection>')
689
+ .description('Query a RAG collection')
690
+ .option('--app <app-id>', 'App ID (uses current app if not specified)')
691
+ .requiredOption('-q, --query <text>', 'Query string')
692
+ .option('--top-k <n>', 'Number of results to return')
693
+ .option('--threshold <n>', 'Minimum similarity threshold')
694
+ .option('--synthesize', 'Synthesize an answer from retrieved chunks')
695
+ .option('--model <model>', 'LLM model to use for synthesis')
696
+ .option('--json', 'Output as JSON')
697
+ .action(ragQueryCommand);
698
+ // AI Gateway
699
+ const ai = program.command('ai').description('Use the app\'s AI gateway (chat, embeddings, models, BYOK, usage)');
700
+ ai
701
+ .command('chat <prompt>')
702
+ .description('Send a single-turn chat completion')
703
+ .option('--app <appId>', 'Override current app')
704
+ .option('--model <model>', 'Model id (default: app default)')
705
+ .option('--temperature <n>', 'Sampling temperature', parseFloat)
706
+ .option('--max-tokens <n>', 'Max output tokens', parseInt)
707
+ .option('--system <message>', 'Prepend a system message')
708
+ .option('--json', 'Output raw JSON')
709
+ .action((prompt, opts) => aiChatCommand(prompt, opts));
710
+ ai
711
+ .command('embed <input...>')
712
+ .description('Embed text(s) into vectors')
713
+ .option('--app <appId>', 'Override current app')
714
+ .option('--model <model>', 'Embedding model')
715
+ .option('--json', 'Output raw JSON')
716
+ .action((input, opts) => aiEmbedCommand(input, opts));
717
+ ai
718
+ .command('models')
719
+ .description('List available AI models')
720
+ .option('--app <appId>', 'Override current app')
721
+ .option('--json', 'Output raw JSON')
722
+ .action((opts) => aiModelsCommand(opts));
723
+ const aiConfig = ai.command('config').description('Read or update AI config');
724
+ aiConfig
725
+ .command('get')
726
+ .option('--app <appId>', 'Override current app')
727
+ .option('--json', 'Output raw JSON')
728
+ .action((opts) => aiConfigGetCommand(opts));
729
+ aiConfig
730
+ .command('set')
731
+ .option('--app <appId>', 'Override current app')
732
+ .option('--default-model <model>')
733
+ .option('--allowed-models <models...>')
734
+ .option('--max-tokens-per-request <n>', 'Cap on tokens per request', parseInt)
735
+ .option('--byok-key <key>', 'Set or clear BYOK key (empty string clears)')
736
+ .option('--json', 'Output raw JSON')
737
+ .action((opts) => aiConfigSetCommand(opts));
738
+ ai
739
+ .command('usage')
740
+ .description('AI token + cost usage over a window')
741
+ .option('--app <appId>', 'Override current app')
742
+ .option('--start-date <date>', 'ISO date')
743
+ .option('--end-date <date>', 'ISO date')
744
+ .option('--json', 'Output raw JSON')
745
+ .action((opts) => aiUsageCommand({
746
+ app: opts.app, startDate: opts.startDate, endDate: opts.endDate, json: opts.json,
747
+ }));
748
+ // OAuth
749
+ const oauth = program.command('oauth').description('Manage OAuth providers for end-user auth');
750
+ oauth
751
+ .command('configure <provider>')
752
+ .description('Configure an OAuth provider (e.g. google, github, apple)')
753
+ .requiredOption('--client-id <id>')
754
+ .requiredOption('--client-secret <secret>')
755
+ .option('--app <appId>', 'Override current app')
756
+ .option('--redirect-uri <uri>', 'Add a redirect URI (repeatable)', (v, prev) => prev.concat(v), [])
757
+ .option('--scope <scope>', 'Add a scope (repeatable)', (v, prev) => prev.concat(v), [])
758
+ .option('--authorization-url <url>')
759
+ .option('--token-url <url>')
760
+ .option('--userinfo-url <url>')
761
+ .option('--json', 'Output raw JSON')
762
+ .action((provider, opts) => oauthConfigureCommand(provider, opts));
763
+ oauth
764
+ .command('list')
765
+ .description('List configured OAuth providers')
766
+ .option('--app <appId>', 'Override current app')
767
+ .option('--json', 'Output raw JSON')
768
+ .action((opts) => oauthListCommand(opts));
769
+ oauth
770
+ .command('get <provider>')
771
+ .description('Show config for a provider')
772
+ .option('--app <appId>', 'Override current app')
773
+ .option('--json', 'Output raw JSON')
774
+ .action((provider, opts) => oauthGetCommand(provider, opts));
775
+ oauth
776
+ .command('update <provider>')
777
+ .description('Update provider config (any field optional)')
778
+ .option('--app <appId>', 'Override current app')
779
+ .option('--client-id <id>')
780
+ .option('--client-secret <secret>')
781
+ .option('--redirect-uri <uri>', 'Replace redirect URIs', (v, prev) => prev.concat(v), [])
782
+ .option('--scope <scope>', 'Replace scopes', (v, prev) => prev.concat(v), [])
783
+ .option('--enabled <bool>', 'Enable/disable (true|false)', (v) => v === 'true')
784
+ .option('--json', 'Output raw JSON')
785
+ .action((provider, opts) => oauthUpdateCommand(provider, opts));
786
+ oauth
787
+ .command('delete <provider>')
788
+ .description('Delete an OAuth provider configuration')
789
+ .option('--app <appId>', 'Override current app')
790
+ .option('--json', 'Output raw JSON')
791
+ .action((provider, opts) => oauthDeleteCommand(provider, opts));
792
+ // Audit
793
+ const audit = program.command('audit').description('Query the app\'s audit log');
794
+ audit
795
+ .command('query')
796
+ .description('Query audit log entries with optional filters')
797
+ .option('--app <appId>', 'Override current app')
798
+ .option('--category <c>')
799
+ .option('--event-type <e>')
800
+ .option('--action <a>')
801
+ .option('--resource-type <t>')
802
+ .option('--resource-id <id>')
803
+ .option('--actor-id <id>')
804
+ .option('--from <iso>', 'Start of window (ISO date)')
805
+ .option('--to <iso>', 'End of window (ISO date)')
806
+ .option('--limit <n>', 'Max rows', parseInt)
807
+ .option('--offset <n>', 'Pagination offset', parseInt)
808
+ .option('--json', 'Output raw JSON')
809
+ .action((opts) => auditQueryCommand(opts));
810
+ // Regions
811
+ const regions = program.command('regions').description('Multi-region operations');
812
+ regions
813
+ .command('list')
814
+ .description('List supported regions')
815
+ .option('--json', 'Output raw JSON')
816
+ .action((opts) => regionsListCommand(opts));
817
+ // App Billing (Stripe Connect — plans/products/subscriptions/orders)
818
+ const appBilling = program.command('app-billing').description('Manage app-level Stripe Connect billing (plans/products/subscriptions/orders)');
819
+ const abPlans = appBilling.command('plans').description('Subscription plans');
820
+ abPlans
821
+ .command('list')
822
+ .option('--app <appId>', 'Override current app')
823
+ .option('--json', 'Output raw JSON')
824
+ .action((opts) => plansListCommand(opts));
825
+ abPlans
826
+ .command('create')
827
+ .requiredOption('--name <name>')
828
+ .requiredOption('--price-cents <n>', 'Price in cents', parseInt)
829
+ .requiredOption('--interval <month|year>')
830
+ .option('--description <desc>')
831
+ .option('--app <appId>', 'Override current app')
832
+ .option('--json', 'Output raw JSON')
833
+ .action((opts) => plansCreateCommand(opts));
834
+ abPlans
835
+ .command('update <planId>')
836
+ .option('--name <name>')
837
+ .option('--price-cents <n>', 'Price in cents', parseInt)
838
+ .option('--description <desc>')
839
+ .option('--app <appId>', 'Override current app')
840
+ .option('--json', 'Output raw JSON')
841
+ .action((planId, opts) => plansUpdateCommand(planId, opts));
842
+ const abProducts = appBilling.command('products').description('One-time products');
843
+ abProducts
844
+ .command('list')
845
+ .option('--app <appId>', 'Override current app')
846
+ .option('--json', 'Output raw JSON')
847
+ .action((opts) => productsListCommand(opts));
848
+ abProducts
849
+ .command('create')
850
+ .requiredOption('--name <name>')
851
+ .requiredOption('--price-cents <n>', 'Price in cents', parseInt)
852
+ .option('--description <desc>')
853
+ .option('--app <appId>', 'Override current app')
854
+ .option('--json', 'Output raw JSON')
855
+ .action((opts) => productsCreateCommand(opts));
856
+ abProducts
857
+ .command('update <productId>')
858
+ .option('--name <name>')
859
+ .option('--price-cents <n>', 'Price in cents', parseInt)
860
+ .option('--description <desc>')
861
+ .option('--app <appId>', 'Override current app')
862
+ .option('--json', 'Output raw JSON')
863
+ .action((productId, opts) => productsUpdateCommand(productId, opts));
864
+ appBilling
865
+ .command('subscribe <planId>')
866
+ .option('--app <appId>', 'Override current app')
867
+ .option('--json', 'Output raw JSON')
868
+ .action((planId, opts) => subscribeCommand(planId, opts));
869
+ appBilling
870
+ .command('subscription')
871
+ .description('Show the current subscription')
872
+ .option('--app <appId>', 'Override current app')
873
+ .option('--json', 'Output raw JSON')
874
+ .action((opts) => subscriptionCommand(opts));
875
+ appBilling
876
+ .command('cancel')
877
+ .description('Cancel the current subscription')
878
+ .option('--app <appId>', 'Override current app')
879
+ .option('--json', 'Output raw JSON')
880
+ .action((opts) => cancelCommand(opts));
881
+ appBilling
882
+ .command('purchase <productId>')
883
+ .option('--app <appId>', 'Override current app')
884
+ .option('--json', 'Output raw JSON')
885
+ .action((productId, opts) => purchaseCommand(productId, opts));
886
+ const abOrders = appBilling.command('orders').description('Order history');
887
+ abOrders
888
+ .command('list')
889
+ .option('--app <appId>', 'Override current app')
890
+ .option('--json', 'Output raw JSON')
891
+ .action((opts) => ordersListCommand(opts));
892
+ abOrders
893
+ .command('get <orderId>')
894
+ .option('--app <appId>', 'Override current app')
895
+ .option('--json', 'Output raw JSON')
896
+ .action((orderId, opts) => ordersGetCommand(orderId, opts));
897
+ // Top-level error handlers for unhandled exceptions / rejections
898
+ process.on('uncaughtException', (err) => {
899
+ console.error(renderError(err));
900
+ process.exit(1);
901
+ });
902
+ process.on('unhandledRejection', (err) => {
903
+ console.error(renderError(err));
904
+ process.exit(1);
905
+ });
434
906
  // Parse arguments
435
907
  program.parse();
436
908
  //# sourceMappingURL=butterbase.js.map