-
Notifications
You must be signed in to change notification settings - Fork 0
feat(cli-core): add clerk orgs and clerk billing shortcut commands #219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e81ba33
8583f13
8198e40
a87ed32
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "clerk": minor | ||
| --- | ||
|
|
||
| Add `clerk orgs enable/disable` and `clerk billing enable/disable/plans` shortcut commands for managing organizations, billing, and subscription plans from the CLI. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,15 @@ import { log } from "./lib/log.ts"; | |
| import { maybeNotifyUpdate, getCurrentVersion } from "./lib/update-check.ts"; | ||
| import { update } from "./commands/update/index.ts"; | ||
| import { isClerkSkillInstalled } from "./lib/skill-detection.ts"; | ||
| import { orgsEnable, orgsDisable } from "./commands/orgs/index.ts"; | ||
| import { | ||
| billingEnable, | ||
| billingDisable, | ||
| plansCreate, | ||
| plansList, | ||
| plansUpdate, | ||
| plansRemove, | ||
| } from "./commands/billing/index.ts"; | ||
|
|
||
| export function createProgram() { | ||
| const program = new Command() | ||
|
|
@@ -414,6 +423,175 @@ Give AI agents better Clerk context: install the Clerk skills | |
| ]) | ||
| .action(configPut); | ||
|
|
||
| // --- clerk orgs --- | ||
| const orgs = program | ||
| .command("orgs") | ||
| .alias("organizations") | ||
| .description("Manage Clerk Organizations") | ||
| .setExamples([ | ||
| { command: "clerk orgs enable", description: "Enable organizations" }, | ||
| { | ||
| command: "clerk orgs enable --force-selection --max-members 10", | ||
| description: "Enable with options", | ||
| }, | ||
| { command: "clerk orgs disable", description: "Disable organizations" }, | ||
| ]); | ||
|
|
||
| orgs | ||
| .command("enable") | ||
| .description("Enable organizations on the linked instance") | ||
| .option("--app <id>", "Application ID to target") | ||
| .option("--instance <id>", "Instance to target (dev, prod, or instance ID)") | ||
| .option("--force-selection", "Force organization selection on login") | ||
| .option("--auto-create", "Auto-create an organization for new users") | ||
| .option("--max-members <n>", "Maximum members per organization") | ||
| .option("--domains", "Enable verified domains") | ||
| .option("--yes", "Skip confirmation prompts") | ||
| .setExamples([ | ||
| { command: "clerk orgs enable", description: "Enable organizations" }, | ||
| { | ||
| command: "clerk orgs enable --force-selection", | ||
| description: "Enable and force org selection", | ||
| }, | ||
| { | ||
| command: "clerk orgs enable --auto-create --max-members 10", | ||
| description: "Enable with auto-creation and member limit", | ||
| }, | ||
| ]) | ||
| .action(orgsEnable); | ||
|
|
||
| orgs | ||
| .command("disable") | ||
| .description("Disable organizations on the linked instance") | ||
| .option("--app <id>", "Application ID to target") | ||
| .option("--instance <id>", "Instance to target (dev, prod, or instance ID)") | ||
| .option("--yes", "Skip confirmation prompts") | ||
| .setExamples([{ command: "clerk orgs disable", description: "Disable organizations" }]) | ||
| .action(orgsDisable); | ||
|
|
||
| // --- clerk billing --- | ||
| const billing = program | ||
| .command("billing") | ||
| .description("Manage billing and subscription plans") | ||
| .setExamples([ | ||
| { command: "clerk billing enable --for org", description: "Enable org billing" }, | ||
| { | ||
| command: "clerk billing plans create pro --amount 1999 --payer org", | ||
| description: "Create a Pro plan", | ||
| }, | ||
| { command: "clerk billing plans list", description: "List all plans" }, | ||
| ]); | ||
|
|
||
| billing | ||
| .command("enable") | ||
| .description("Enable billing on the linked instance") | ||
| .addOption(createOption("--for <type>", "Billing target type").choices(["org", "user"])) | ||
| .option("--app <id>", "Application ID to target") | ||
| .option("--instance <id>", "Instance to target (dev, prod, or instance ID)") | ||
| .option("--require-payment-method", "Require payment method for free trials") | ||
| .option("--yes", "Skip confirmation prompts") | ||
| .setExamples([ | ||
| { command: "clerk billing enable --for org", description: "Enable org billing" }, | ||
| { command: "clerk billing enable --for user", description: "Enable user billing" }, | ||
| ]) | ||
| .action(billingEnable); | ||
|
|
||
| billing | ||
| .command("disable") | ||
| .description("Disable billing on the linked instance") | ||
| .addOption(createOption("--for <type>", "Billing target type").choices(["org", "user"])) | ||
| .option("--app <id>", "Application ID to target") | ||
| .option("--instance <id>", "Instance to target (dev, prod, or instance ID)") | ||
| .option("--yes", "Skip confirmation prompts") | ||
| .setExamples([ | ||
| { command: "clerk billing disable --for org", description: "Disable org billing" }, | ||
| ]) | ||
| .action(billingDisable); | ||
|
|
||
| const plans = billing | ||
| .command("plans") | ||
| .description("Manage subscription plans") | ||
| .setExamples([ | ||
| { command: "clerk billing plans list", description: "List all plans" }, | ||
| { | ||
| command: "clerk billing plans create pro --amount 1999 --payer org", | ||
| description: "Create a Pro plan at $19.99/mo", | ||
| }, | ||
| ]); | ||
|
|
||
| plans | ||
| .command("create") | ||
| .description("Create a subscription plan") | ||
| .argument("<slug>", "Plan slug (display name auto-derived via title case)") | ||
| .option("--name <name>", "Override display name") | ||
| .requiredOption("--amount <cents>", "Monthly price in cents") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
A maintainer who wants to preview Fix: add |
||
| .addOption(createOption("--payer <type>", "Who pays").choices(["org", "user"])) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line 527 uses When omitted, Fix: .addOption(
createOption("--payer <type>", "Who pays")
.choices(["org", "user"])
.makeOptionMandatory(),
)Add a test covering the missing- |
||
| .option("--currency <code>", "Currency code (default: usd)") | ||
| .option("--description <text>", "Plan description") | ||
| .option("--trial-days <n>", "Free trial length in days") | ||
| .option("--annual-amount <cents>", "Monthly equivalent when billed annually, in cents") | ||
| .option("--hidden", "Hide plan from end users") | ||
| .option("--app <id>", "Application ID to target") | ||
| .option("--instance <id>", "Instance to target (dev, prod, or instance ID)") | ||
| .option("--yes", "Skip confirmation prompts") | ||
| .setExamples([ | ||
| { | ||
| command: "clerk billing plans create pro --amount 1999 --payer org", | ||
| description: "Create a Pro plan at $19.99/mo for orgs", | ||
| }, | ||
| { | ||
| command: | ||
| 'clerk billing plans create enterprise --name "Enterprise Plus" --amount 9999 --payer org --trial-days 14', | ||
| description: "Create an Enterprise plan with a 14-day trial", | ||
| }, | ||
| ]) | ||
| .action(plansCreate); | ||
|
|
||
| plans | ||
| .command("list") | ||
| .description("List subscription plans") | ||
| .option("--json", "Output as JSON") | ||
| .option("--app <id>", "Application ID to target") | ||
| .option("--instance <id>", "Instance to target (dev, prod, or instance ID)") | ||
| .setExamples([ | ||
| { command: "clerk billing plans list", description: "List all plans" }, | ||
| { command: "clerk billing plans list --json", description: "Output as JSON" }, | ||
| ]) | ||
| .action(plansList); | ||
|
|
||
| plans | ||
| .command("update") | ||
| .description("Update a subscription plan") | ||
| .argument("<slug>", "Plan slug to update") | ||
| .option("--name <name>", "Update display name") | ||
| .option("--amount <cents>", "Update monthly price in cents") | ||
| .option("--currency <code>", "Update currency") | ||
| .option("--description <text>", "Update description") | ||
| .option("--trial-days <n>", "Update free trial days") | ||
| .option("--annual-amount <cents>", "Update annual amount") | ||
| .option("--hidden", "Hide plan from end users") | ||
| .option("--visible", "Show plan to end users") | ||
| .option("--app <id>", "Application ID to target") | ||
| .option("--instance <id>", "Instance to target (dev, prod, or instance ID)") | ||
| .option("--yes", "Skip confirmation prompts") | ||
| .setExamples([ | ||
| { command: "clerk billing plans update pro --amount 2999", description: "Update price" }, | ||
| { command: "clerk billing plans update pro --hidden", description: "Hide plan" }, | ||
| ]) | ||
| .action(plansUpdate); | ||
|
|
||
| plans | ||
| .command("remove") | ||
| .description("Remove a subscription plan") | ||
| .argument("<slug>", "Plan slug to remove") | ||
| .option("--app <id>", "Application ID to target") | ||
| .option("--instance <id>", "Instance to target (dev, prod, or instance ID)") | ||
| .option("--yes", "Skip confirmation prompts") | ||
| .setExamples([ | ||
| { command: "clerk billing plans remove pro", description: "Remove the Pro plan" }, | ||
| ]) | ||
| .action(plansRemove); | ||
|
|
||
| program | ||
| .command("api") | ||
| .description("Make authenticated requests to the Clerk API") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| # clerk billing | ||
|
|
||
| Enable/disable billing and manage subscription plans on the linked instance. | ||
|
|
||
| ## Usage | ||
|
|
||
| ``` | ||
| clerk billing enable --for <org|user> [options] | ||
| clerk billing disable --for <org|user> [options] | ||
| clerk billing plans create <slug> [options] | ||
| clerk billing plans list [options] | ||
| clerk billing plans update <slug> [options] | ||
| clerk billing plans remove <slug> [options] | ||
| ``` | ||
|
|
||
| ## Options | ||
|
|
||
| ### `enable` / `disable` | ||
|
|
||
| | Flag | Description | | ||
| | -------------------------- | ---------------------------------------------------- | | ||
| | `--for <org\|user>` | **(required)** Target billing type | | ||
| | `--require-payment-method` | Require payment method for free trials (enable only) | | ||
| | `--app <id>` | Target a specific application | | ||
| | `--instance <id>` | Target a specific instance (dev, prod) | | ||
|
|
||
| ### `plans create` | ||
|
|
||
| | Flag | Description | | ||
| | ------------------------- | ------------------------------------------------------------------- | | ||
| | `<slug>` | Plan slug (positional). Display name is auto-derived via title case | | ||
| | `--name <name>` | Override display name (default: title-cased slug) | | ||
| | `--amount <cents>` | **(required)** Monthly price in cents | | ||
| | `--payer <org\|user>` | **(required)** Who pays | | ||
| | `--currency <code>` | Currency code (default: usd) | | ||
| | `--description <text>` | Plan description | | ||
| | `--trial-days <n>` | Free trial length in days | | ||
| | `--annual-amount <cents>` | Monthly equivalent when billed annually, in cents | | ||
| | `--hidden` | Hide plan from end users | | ||
| | `--app <id>` | Target a specific application | | ||
| | `--instance <id>` | Target a specific instance (dev, prod) | | ||
|
|
||
| ### `plans list` | ||
|
|
||
| | Flag | Description | | ||
| | ----------------- | -------------------------------------- | | ||
| | `--json` | Output as JSON | | ||
| | `--app <id>` | Target a specific application | | ||
| | `--instance <id>` | Target a specific instance (dev, prod) | | ||
|
|
||
| ### `plans update` | ||
|
|
||
| | Flag | Description | | ||
| | ------------------------- | -------------------------------------- | | ||
| | `<slug>` | Plan slug to update (positional) | | ||
| | `--name <name>` | Update display name | | ||
| | `--amount <cents>` | Update monthly price | | ||
| | `--currency <code>` | Update currency | | ||
| | `--description <text>` | Update description | | ||
| | `--trial-days <n>` | Update free trial days | | ||
| | `--annual-amount <cents>` | Update annual amount | | ||
| | `--hidden` | Hide plan | | ||
| | `--visible` | Show plan | | ||
| | `--app <id>` | Target a specific application | | ||
| | `--instance <id>` | Target a specific instance (dev, prod) | | ||
|
|
||
| ### `plans remove` | ||
|
|
||
| | Flag | Description | | ||
| | ----------------- | -------------------------------------- | | ||
| | `<slug>` | Plan slug to remove (positional) | | ||
| | `--app <id>` | Target a specific application | | ||
| | `--instance <id>` | Target a specific instance (dev, prod) | | ||
|
|
||
| ## Clerk API endpoints | ||
|
|
||
| | Method | Endpoint | Description | | ||
| | ------ | ----------------------------------------------------------------- | -------------------------------------------- | | ||
| | GET | `/v1/platform/applications/{appId}/instances/{instanceId}/config` | Fetch current config (for plans list/remove) | | ||
| | PATCH | `/v1/platform/applications/{appId}/instances/{instanceId}/config` | Patch billing and plans config | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--yesis advertised onorgs enable/disable,billing enable/disable, andplans create/update/remove, but no implementation consultsoptions.yesor callsconfirm(). The commands always mutate silently. Users who see--yeswill reasonably assume there is a prompt to skip.Compare with
config patch, which these shortcuts wrap:packages/cli-core/src/commands/config/push.ts:94-102doesif (!options.dryRun && isHuman() && !options.yes) { await confirm(...) }. Without an equivalent branch here, the shortcut is strictly more dangerous than the command it's supposed to replace.Fix: either remove
--yesand theyes?: booleanfields until a prompt exists, or add the real confirmation branch for every mutating command. Option two is the right call, these are production config edits.