@butterbase/cli 0.2.2 → 0.3.3

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 (72) hide show
  1. package/README.md +118 -0
  2. package/dist/bin/butterbase.js +367 -1
  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/do.js +1 -1
  21. package/dist/src/commands/do.js.map +1 -1
  22. package/dist/src/commands/functions.d.ts +4 -0
  23. package/dist/src/commands/functions.d.ts.map +1 -1
  24. package/dist/src/commands/functions.js +28 -2
  25. package/dist/src/commands/functions.js.map +1 -1
  26. package/dist/src/commands/integrations.d.ts +2 -0
  27. package/dist/src/commands/integrations.d.ts.map +1 -1
  28. package/dist/src/commands/integrations.js +2 -2
  29. package/dist/src/commands/integrations.js.map +1 -1
  30. package/dist/src/commands/keys.d.ts +1 -0
  31. package/dist/src/commands/keys.d.ts.map +1 -1
  32. package/dist/src/commands/keys.js +1 -1
  33. package/dist/src/commands/keys.js.map +1 -1
  34. package/dist/src/commands/kv.d.ts +56 -0
  35. package/dist/src/commands/kv.d.ts.map +1 -0
  36. package/dist/src/commands/kv.js +189 -0
  37. package/dist/src/commands/kv.js.map +1 -0
  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/regions.d.ts +4 -0
  47. package/dist/src/commands/regions.d.ts.map +1 -0
  48. package/dist/src/commands/regions.js +16 -0
  49. package/dist/src/commands/regions.js.map +1 -0
  50. package/dist/src/commands/rls.d.ts +1 -0
  51. package/dist/src/commands/rls.d.ts.map +1 -1
  52. package/dist/src/commands/rls.js +1 -0
  53. package/dist/src/commands/rls.js.map +1 -1
  54. package/dist/src/lib/api-client.d.ts +46 -3
  55. package/dist/src/lib/api-client.d.ts.map +1 -1
  56. package/dist/src/lib/api-client.js +167 -9
  57. package/dist/src/lib/api-client.js.map +1 -1
  58. package/dist/src/lib/errors.d.ts +8 -0
  59. package/dist/src/lib/errors.d.ts.map +1 -0
  60. package/dist/src/lib/errors.js +24 -0
  61. package/dist/src/lib/errors.js.map +1 -0
  62. package/dist/src/lib/kv-api.d.ts +15 -0
  63. package/dist/src/lib/kv-api.d.ts.map +1 -0
  64. package/dist/src/lib/kv-api.js +14 -0
  65. package/dist/src/lib/kv-api.js.map +1 -0
  66. package/dist/src/lib/load-kv-config.d.ts +14 -0
  67. package/dist/src/lib/load-kv-config.d.ts.map +1 -0
  68. package/dist/src/lib/load-kv-config.js +27 -0
  69. package/dist/src/lib/load-kv-config.js.map +1 -0
  70. package/dist/tsconfig.tsbuildinfo +1 -0
  71. package/package.json +16 -4
  72. package/templates/react-vite/.mcp.json +10 -10
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,5 +1,6 @@
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';
@@ -24,6 +25,14 @@ import { rlsListCommand, rlsCreateCommand, rlsEnableCommand, rlsDeleteCommand }
24
25
  import { billingStatusCommand, billingPortalCommand, billingTopupCommand, billingCapGetCommand, billingCapRaiseCommand, billingPlansCommand, billingUsageCommand, } from '../src/commands/billing.js';
25
26
  import { ragCollectionsListCommand, ragCollectionsCreateCommand, ragCollectionsGetCommand, ragCollectionsDeleteCommand, ragIngestCommand, ragDocsListCommand, ragDocsDeleteCommand, ragQueryCommand, } from '../src/commands/rag.js';
26
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';
35
+ import { kvGetCommand, kvSetCommand, kvDelCommand, kvLsCommand, kvStatsCommand, kvFlushCommand, kvRulesCommand, kvExposeCommand, kvUnexposeCommand, kvApplyCommand, } from '../src/commands/kv.js';
27
36
  const program = new Command();
28
37
  program
29
38
  .name('butterbase')
@@ -80,6 +89,94 @@ apps
80
89
  .command('resume [app-id]')
81
90
  .description('Resume a paused app — restore data-plane traffic')
82
91
  .action(appsResumeCommand);
92
+ const appsConfig = apps.command('config').description('Read or update the app\'s server-side config');
93
+ appsConfig
94
+ .command('get')
95
+ .description('Show the app\'s full config')
96
+ .option('--app <appId>', 'Override current app')
97
+ .option('--json', 'Output raw JSON')
98
+ .action((opts) => appConfigGetCommand(opts));
99
+ appsConfig
100
+ .command('cors')
101
+ .description('Update CORS config')
102
+ .option('--app <appId>', 'Override current app')
103
+ .option('--allowed-origin <origin>', 'Allowed origin (repeatable)', (v, prev) => prev.concat(v), [])
104
+ .option('--allowed-method <method>', 'Allowed method (repeatable)', (v, prev) => prev.concat(v), [])
105
+ .option('--allowed-header <header>', 'Allowed header (repeatable)', (v, prev) => prev.concat(v), [])
106
+ .option('--allow-credentials <bool>', '(true|false)', (v) => v === 'true')
107
+ .option('--json', 'Output raw JSON')
108
+ .action((opts) => appCorsCommand(opts));
109
+ appsConfig
110
+ .command('jwt')
111
+ .description('Update JWT TTLs')
112
+ .option('--app <appId>', 'Override current app')
113
+ .option('--access-token-ttl <duration>', 'e.g. "15m", "1h"')
114
+ .option('--refresh-token-ttl-days <n>', 'Refresh token lifetime in days', parseInt)
115
+ .option('--json', 'Output raw JSON')
116
+ .action((opts) => appJwtCommand(opts));
117
+ appsConfig
118
+ .command('storage')
119
+ .description('Update storage config')
120
+ .option('--app <appId>', 'Override current app')
121
+ .option('--public-read <bool>', 'Public-read default (true|false)', (v) => v === 'true')
122
+ .option('--max-file-size-mb <n>', 'Per-file size cap in MB', parseInt)
123
+ .option('--allowed-content-type <ct>', 'Allowed content-type (repeatable)', (v, prev) => prev.concat(v), [])
124
+ .option('--json', 'Output raw JSON')
125
+ .action((opts) => appStorageCommand(opts));
126
+ appsConfig
127
+ .command('access-mode <mode>')
128
+ .description('Set access mode: public | authenticated')
129
+ .option('--app <appId>', 'Override current app')
130
+ .option('--json', 'Output raw JSON')
131
+ .action((mode, opts) => appAccessModeCommand(mode, opts));
132
+ appsConfig
133
+ .command('secure')
134
+ .description('Enable RLS + access-mode in one shot')
135
+ .option('--app <appId>', 'Override current app')
136
+ .option('--table <name>', 'Table to secure (repeatable)', (v, prev) => prev.concat(v), [])
137
+ .option('--user-column <col>', 'User-id column name (default: user_id)')
138
+ .option('--access-mode <mode>', 'public | authenticated')
139
+ .option('--json', 'Output raw JSON')
140
+ .action((opts) => appSecureCommand(opts));
141
+ apps
142
+ .command('move <appId> <destRegion>')
143
+ .description('Migrate an app to another region')
144
+ .option('--follow', 'Poll status until terminal')
145
+ .option('--json', 'Output raw JSON')
146
+ .action((appId, destRegion, opts) => moveCommand(appId, destRegion, opts));
147
+ const appsMigrations = apps.command('migrations').description('Read or control in-flight migrations');
148
+ appsMigrations
149
+ .command('status <appId> <migrationId>')
150
+ .description('Get status of a specific migration')
151
+ .option('--json', 'Output raw JSON')
152
+ .action((appId, migrationId, opts) => migrationStatusCommand(appId, migrationId, opts));
153
+ appsMigrations
154
+ .command('active [appId]')
155
+ .description('Show the currently-active migration for an app')
156
+ .option('--app <appId>', 'Override current app')
157
+ .option('--json', 'Output raw JSON')
158
+ .action((appId, opts) => migrationActiveCommand(appId, opts));
159
+ appsMigrations
160
+ .command('abort <appId> <migrationId>')
161
+ .description('Cancel a migration that has not yet reached cutover')
162
+ .option('--json', 'Output raw JSON')
163
+ .action((appId, migrationId, opts) => migrationAbortCommand(appId, migrationId, opts));
164
+ appsMigrations
165
+ .command('reverse <appId> <migrationId>')
166
+ .description('Roll a completed migration back to source')
167
+ .option('--json', 'Output raw JSON')
168
+ .action((appId, migrationId, opts) => migrationReverseCommand(appId, migrationId, opts));
169
+ const appsReplicas = apps.command('replicas').description('Manage retained source replicas after a move');
170
+ appsReplicas
171
+ .command('list')
172
+ .description('List active retained source replicas')
173
+ .option('--json', 'Output raw JSON')
174
+ .action((opts) => replicasListCommand(opts));
175
+ appsReplicas
176
+ .command('teardown <migrationId>')
177
+ .description('Decommission a retained source replica')
178
+ .option('--json', 'Output raw JSON')
179
+ .action((migrationId, opts) => replicaTeardownCommand(migrationId, opts));
83
180
  // Schema
84
181
  const schema = program.command('schema').description('Manage database schema');
85
182
  schema
@@ -107,8 +204,12 @@ functions
107
204
  .description('Deploy a function')
108
205
  .option('--app <app-id>', 'App ID (uses current app if not specified)')
109
206
  .option('--name <name>', 'Function name (defaults to filename)')
110
- .option('--trigger <type>', 'Trigger type (http, cron)', 'http')
207
+ .option('--trigger <type>', 'Trigger type (http, cron, s3_upload, webhook, websocket)', 'http')
208
+ .option('--trigger-config <json>', 'Trigger config as JSON (e.g. \'{"schedule":"*/5 * * * *"}\')')
111
209
  .option('--description <desc>', 'Function description')
210
+ .option('--env <kv>', 'Env var as KEY=value (repeatable)', (v, prev) => prev.concat(v), [])
211
+ .option('--timeout-ms <n>', 'Per-invocation timeout (ms)', parseInt)
212
+ .option('--memory-mb <n>', 'Memory limit (MB)', parseInt)
112
213
  .action(functionsDeployCommand);
113
214
  functions
114
215
  .command('logs <function-name>')
@@ -287,6 +388,7 @@ const keys = program.command('keys').description('Manage API keys');
287
388
  keys
288
389
  .command('generate [name]')
289
390
  .description('Generate a new API key')
391
+ .option('--scope <scope>', 'Add a scope (repeatable)', (v, prev) => prev.concat(v), [])
290
392
  .option('--json', 'Output as JSON')
291
393
  .action(keysGenerateCommand);
292
394
  keys
@@ -322,6 +424,7 @@ integrations
322
424
  .description('Enable a toolkit for the app')
323
425
  .option('--app <app-id>', 'App ID')
324
426
  .option('--display-name <name>', 'Human-readable display name')
427
+ .option('--scope <scope>', 'Add a scope (repeatable)', (v, prev) => prev.concat(v), [])
325
428
  .action(integrationsConfigureCommand);
326
429
  integrations
327
430
  .command('disable <toolkit>')
@@ -334,6 +437,7 @@ integrations
334
437
  .option('--app <app-id>', 'App ID')
335
438
  .option('--redirect-url <url>', 'URL to redirect after OAuth')
336
439
  .option('--user-id <uuid>', 'User ID (for API key auth)')
440
+ .option('--scope <scope>', 'Add a scope (repeatable)', (v, prev) => prev.concat(v), [])
337
441
  .action(integrationsConnectCommand);
338
442
  integrations
339
443
  .command('connections')
@@ -474,6 +578,7 @@ rls
474
578
  .option('--using <expr>', 'USING expression')
475
579
  .option('--with-check <expr>', 'WITH CHECK expression')
476
580
  .option('--restrictive', 'Create as RESTRICTIVE policy')
581
+ .option('--role <role>', 'Restrict to a Postgres role (anon | user)')
477
582
  .option('--json', 'Output as JSON')
478
583
  .action(rlsCreateCommand);
479
584
  rls
@@ -591,6 +696,267 @@ rag
591
696
  .option('--model <model>', 'LLM model to use for synthesis')
592
697
  .option('--json', 'Output as JSON')
593
698
  .action(ragQueryCommand);
699
+ // AI Gateway
700
+ const ai = program.command('ai').description('Use the app\'s AI gateway (chat, embeddings, models, BYOK, usage)');
701
+ ai
702
+ .command('chat <prompt>')
703
+ .description('Send a single-turn chat completion')
704
+ .option('--app <appId>', 'Override current app')
705
+ .option('--model <model>', 'Model id (default: app default)')
706
+ .option('--temperature <n>', 'Sampling temperature', parseFloat)
707
+ .option('--max-tokens <n>', 'Max output tokens', parseInt)
708
+ .option('--system <message>', 'Prepend a system message')
709
+ .option('--json', 'Output raw JSON')
710
+ .action((prompt, opts) => aiChatCommand(prompt, opts));
711
+ ai
712
+ .command('embed <input...>')
713
+ .description('Embed text(s) into vectors')
714
+ .option('--app <appId>', 'Override current app')
715
+ .option('--model <model>', 'Embedding model')
716
+ .option('--json', 'Output raw JSON')
717
+ .action((input, opts) => aiEmbedCommand(input, opts));
718
+ ai
719
+ .command('models')
720
+ .description('List available AI models')
721
+ .option('--app <appId>', 'Override current app')
722
+ .option('--json', 'Output raw JSON')
723
+ .action((opts) => aiModelsCommand(opts));
724
+ const aiConfig = ai.command('config').description('Read or update AI config');
725
+ aiConfig
726
+ .command('get')
727
+ .option('--app <appId>', 'Override current app')
728
+ .option('--json', 'Output raw JSON')
729
+ .action((opts) => aiConfigGetCommand(opts));
730
+ aiConfig
731
+ .command('set')
732
+ .option('--app <appId>', 'Override current app')
733
+ .option('--default-model <model>')
734
+ .option('--allowed-models <models...>')
735
+ .option('--max-tokens-per-request <n>', 'Cap on tokens per request', parseInt)
736
+ .option('--byok-key <key>', 'Set or clear BYOK key (empty string clears)')
737
+ .option('--json', 'Output raw JSON')
738
+ .action((opts) => aiConfigSetCommand(opts));
739
+ ai
740
+ .command('usage')
741
+ .description('AI token + cost usage over a window')
742
+ .option('--app <appId>', 'Override current app')
743
+ .option('--start-date <date>', 'ISO date')
744
+ .option('--end-date <date>', 'ISO date')
745
+ .option('--json', 'Output raw JSON')
746
+ .action((opts) => aiUsageCommand({
747
+ app: opts.app, startDate: opts.startDate, endDate: opts.endDate, json: opts.json,
748
+ }));
749
+ // OAuth
750
+ const oauth = program.command('oauth').description('Manage OAuth providers for end-user auth');
751
+ oauth
752
+ .command('configure <provider>')
753
+ .description('Configure an OAuth provider (e.g. google, github, apple)')
754
+ .requiredOption('--client-id <id>')
755
+ .requiredOption('--client-secret <secret>')
756
+ .option('--app <appId>', 'Override current app')
757
+ .option('--redirect-uri <uri>', 'Add a redirect URI (repeatable)', (v, prev) => prev.concat(v), [])
758
+ .option('--scope <scope>', 'Add a scope (repeatable)', (v, prev) => prev.concat(v), [])
759
+ .option('--authorization-url <url>')
760
+ .option('--token-url <url>')
761
+ .option('--userinfo-url <url>')
762
+ .option('--json', 'Output raw JSON')
763
+ .action((provider, opts) => oauthConfigureCommand(provider, opts));
764
+ oauth
765
+ .command('list')
766
+ .description('List configured OAuth providers')
767
+ .option('--app <appId>', 'Override current app')
768
+ .option('--json', 'Output raw JSON')
769
+ .action((opts) => oauthListCommand(opts));
770
+ oauth
771
+ .command('get <provider>')
772
+ .description('Show config for a provider')
773
+ .option('--app <appId>', 'Override current app')
774
+ .option('--json', 'Output raw JSON')
775
+ .action((provider, opts) => oauthGetCommand(provider, opts));
776
+ oauth
777
+ .command('update <provider>')
778
+ .description('Update provider config (any field optional)')
779
+ .option('--app <appId>', 'Override current app')
780
+ .option('--client-id <id>')
781
+ .option('--client-secret <secret>')
782
+ .option('--redirect-uri <uri>', 'Replace redirect URIs', (v, prev) => prev.concat(v), [])
783
+ .option('--scope <scope>', 'Replace scopes', (v, prev) => prev.concat(v), [])
784
+ .option('--enabled <bool>', 'Enable/disable (true|false)', (v) => v === 'true')
785
+ .option('--json', 'Output raw JSON')
786
+ .action((provider, opts) => oauthUpdateCommand(provider, opts));
787
+ oauth
788
+ .command('delete <provider>')
789
+ .description('Delete an OAuth provider configuration')
790
+ .option('--app <appId>', 'Override current app')
791
+ .option('--json', 'Output raw JSON')
792
+ .action((provider, opts) => oauthDeleteCommand(provider, opts));
793
+ // Audit
794
+ const audit = program.command('audit').description('Query the app\'s audit log');
795
+ audit
796
+ .command('query')
797
+ .description('Query audit log entries with optional filters')
798
+ .option('--app <appId>', 'Override current app')
799
+ .option('--category <c>')
800
+ .option('--event-type <e>')
801
+ .option('--action <a>')
802
+ .option('--resource-type <t>')
803
+ .option('--resource-id <id>')
804
+ .option('--actor-id <id>')
805
+ .option('--from <iso>', 'Start of window (ISO date)')
806
+ .option('--to <iso>', 'End of window (ISO date)')
807
+ .option('--limit <n>', 'Max rows', parseInt)
808
+ .option('--offset <n>', 'Pagination offset', parseInt)
809
+ .option('--json', 'Output raw JSON')
810
+ .action((opts) => auditQueryCommand(opts));
811
+ // Regions
812
+ const regions = program.command('regions').description('Multi-region operations');
813
+ regions
814
+ .command('list')
815
+ .description('List supported regions')
816
+ .option('--json', 'Output raw JSON')
817
+ .action((opts) => regionsListCommand(opts));
818
+ // App Billing (Stripe Connect — plans/products/subscriptions/orders)
819
+ const appBilling = program.command('app-billing').description('Manage app-level Stripe Connect billing (plans/products/subscriptions/orders)');
820
+ const abPlans = appBilling.command('plans').description('Subscription plans');
821
+ abPlans
822
+ .command('list')
823
+ .option('--app <appId>', 'Override current app')
824
+ .option('--json', 'Output raw JSON')
825
+ .action((opts) => plansListCommand(opts));
826
+ abPlans
827
+ .command('create')
828
+ .requiredOption('--name <name>')
829
+ .requiredOption('--price-cents <n>', 'Price in cents', parseInt)
830
+ .requiredOption('--interval <month|year>')
831
+ .option('--description <desc>')
832
+ .option('--app <appId>', 'Override current app')
833
+ .option('--json', 'Output raw JSON')
834
+ .action((opts) => plansCreateCommand(opts));
835
+ abPlans
836
+ .command('update <planId>')
837
+ .option('--name <name>')
838
+ .option('--price-cents <n>', 'Price in cents', parseInt)
839
+ .option('--description <desc>')
840
+ .option('--app <appId>', 'Override current app')
841
+ .option('--json', 'Output raw JSON')
842
+ .action((planId, opts) => plansUpdateCommand(planId, opts));
843
+ const abProducts = appBilling.command('products').description('One-time products');
844
+ abProducts
845
+ .command('list')
846
+ .option('--app <appId>', 'Override current app')
847
+ .option('--json', 'Output raw JSON')
848
+ .action((opts) => productsListCommand(opts));
849
+ abProducts
850
+ .command('create')
851
+ .requiredOption('--name <name>')
852
+ .requiredOption('--price-cents <n>', 'Price in cents', parseInt)
853
+ .option('--description <desc>')
854
+ .option('--app <appId>', 'Override current app')
855
+ .option('--json', 'Output raw JSON')
856
+ .action((opts) => productsCreateCommand(opts));
857
+ abProducts
858
+ .command('update <productId>')
859
+ .option('--name <name>')
860
+ .option('--price-cents <n>', 'Price in cents', parseInt)
861
+ .option('--description <desc>')
862
+ .option('--app <appId>', 'Override current app')
863
+ .option('--json', 'Output raw JSON')
864
+ .action((productId, opts) => productsUpdateCommand(productId, opts));
865
+ appBilling
866
+ .command('subscribe <planId>')
867
+ .option('--app <appId>', 'Override current app')
868
+ .option('--json', 'Output raw JSON')
869
+ .action((planId, opts) => subscribeCommand(planId, opts));
870
+ appBilling
871
+ .command('subscription')
872
+ .description('Show the current subscription')
873
+ .option('--app <appId>', 'Override current app')
874
+ .option('--json', 'Output raw JSON')
875
+ .action((opts) => subscriptionCommand(opts));
876
+ appBilling
877
+ .command('cancel')
878
+ .description('Cancel the current subscription')
879
+ .option('--app <appId>', 'Override current app')
880
+ .option('--json', 'Output raw JSON')
881
+ .action((opts) => cancelCommand(opts));
882
+ appBilling
883
+ .command('purchase <productId>')
884
+ .option('--app <appId>', 'Override current app')
885
+ .option('--json', 'Output raw JSON')
886
+ .action((productId, opts) => purchaseCommand(productId, opts));
887
+ const abOrders = appBilling.command('orders').description('Order history');
888
+ abOrders
889
+ .command('list')
890
+ .option('--app <appId>', 'Override current app')
891
+ .option('--json', 'Output raw JSON')
892
+ .action((opts) => ordersListCommand(opts));
893
+ abOrders
894
+ .command('get <orderId>')
895
+ .option('--app <appId>', 'Override current app')
896
+ .option('--json', 'Output raw JSON')
897
+ .action((orderId, opts) => ordersGetCommand(orderId, opts));
898
+ // KV
899
+ const kv = program.command('kv').description('Manage app KV store');
900
+ kv.command('get <key>')
901
+ .description('Get a value by key')
902
+ .option('--app <id>', 'App ID (uses current app if not specified)')
903
+ .option('--raw', 'Return raw value without JSON decoding')
904
+ .action(kvGetCommand);
905
+ kv.command('set <key> <value>')
906
+ .description('Set a value')
907
+ .option('--app <id>', 'App ID (uses current app if not specified)')
908
+ .option('--ttl <ttl>', 'TTL: 30d, 1h, 60s, "null"/"forever" for no expiry')
909
+ .option('--ephemeral', 'Mark as ephemeral (not persisted)')
910
+ .action(kvSetCommand);
911
+ kv.command('del <key>')
912
+ .description('Delete a key')
913
+ .option('--app <id>', 'App ID (uses current app if not specified)')
914
+ .action(kvDelCommand);
915
+ kv.command('ls')
916
+ .description('List / scan keys')
917
+ .option('--app <id>', 'App ID (uses current app if not specified)')
918
+ .option('--prefix <p>', 'Key prefix filter')
919
+ .option('--limit <n>', 'Maximum keys to return (default 100)')
920
+ .action(kvLsCommand);
921
+ kv.command('stats')
922
+ .description('Show KV store statistics')
923
+ .option('--app <id>', 'App ID (uses current app if not specified)')
924
+ .action(kvStatsCommand);
925
+ kv.command('flush')
926
+ .description('Flush all keys (requires --confirm)')
927
+ .option('--app <id>', 'App ID (uses current app if not specified)')
928
+ .option('--confirm', 'Required: confirm destructive flush')
929
+ .option('--include-config', 'Also flush expose rules')
930
+ .action(kvFlushCommand);
931
+ kv.command('rules')
932
+ .description('List expose rules')
933
+ .option('--app <id>', 'App ID (uses current app if not specified)')
934
+ .action(kvRulesCommand);
935
+ kv.command('expose <pattern>')
936
+ .description('Create or update an expose rule')
937
+ .option('--app <id>', 'App ID (uses current app if not specified)')
938
+ .requiredOption('--read <role>', 'Read role (public|authed|owner|deny)')
939
+ .requiredOption('--write <role>', 'Write role (public|authed|owner|deny)')
940
+ .action(kvExposeCommand);
941
+ kv.command('unexpose <pattern>')
942
+ .description('Remove an expose rule')
943
+ .option('--app <id>', 'App ID (uses current app if not specified)')
944
+ .action(kvUnexposeCommand);
945
+ kv.command('apply <file>')
946
+ .description('Apply KV expose rules from a config file (kv.config.ts)')
947
+ .option('--app <id>', 'App ID (uses current app if not specified)')
948
+ .option('--dry-run', 'Preview changes without applying')
949
+ .option('--yes', 'Skip confirmation prompt')
950
+ .action((file, opts) => kvApplyCommand({ app: opts.app, file, dryRun: opts.dryRun, yes: opts.yes }));
951
+ // Top-level error handlers for unhandled exceptions / rejections
952
+ process.on('uncaughtException', (err) => {
953
+ console.error(renderError(err));
954
+ process.exit(1);
955
+ });
956
+ process.on('unhandledRejection', (err) => {
957
+ console.error(renderError(err));
958
+ process.exit(1);
959
+ });
594
960
  // Parse arguments
595
961
  program.parse();
596
962
  //# sourceMappingURL=butterbase.js.map