Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/billing-orgs-shortcut-commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"clerk": minor
---

Add `clerk enable` and `clerk disable` top-level commands for toggling features on the linked instance.

- `clerk enable orgs` / `clerk disable orgs` — toggle organizations, with `--force-selection`, `--auto-create`, `--max-members <n>`, and `--domains` on enable.
- `clerk enable billing [--for org,user]` / `clerk disable billing [--for org,user]` — toggle billing for organizations and/or users. `--for` defaults to both; enabling for `org` cascades to enabling organizations. Enable also offers to install the `clerk-billing` agent skill (suppress with `--no-skills`).
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Commands:
users Manage Clerk users
env Manage environment variables
config Manage instance configuration
enable Enable Clerk features on the linked instance
disable Disable Clerk features on the linked instance
api [options] [endpoint] [filter] Make authenticated requests to the Clerk API
doctor [options] Check your project's Clerk integration health
completion [shell] Generate shell autocompletion script
Expand Down
148 changes: 148 additions & 0 deletions packages/cli-core/src/cli-program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ 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 } from "./commands/billing/index.ts";

export function createProgram() {
const program = new Command()
Expand Down Expand Up @@ -470,6 +472,152 @@ Give AI agents better Clerk context: install the Clerk skills
])
.action(configPut);

// --- clerk enable / disable ---
const enable = program
.command("enable")
.description("Enable Clerk features on the linked instance")
.setExamples([
{ command: "clerk enable orgs", description: "Enable organizations" },
{
command: "clerk enable orgs --force-selection --max-members 10",
description: "Enable organizations with options",
},
{
command: "clerk enable billing --for org",
description: "Enable billing for organizations only",
},
{
command: "clerk enable billing",
description: "Enable billing for organizations and users",
},
]);

enable
.command("orgs")
.alias("organizations")
.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")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--yes is advertised on orgs enable/disable, billing enable/disable, and plans create/update/remove, but no implementation consults options.yes or calls confirm(). The commands always mutate silently. Users who see --yes will 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-102 does if (!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 --yes and the yes?: boolean fields 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.

.option("--dry-run", "Show the patch that would be sent without applying it")
.setExamples([
{ command: "clerk enable orgs", description: "Enable organizations" },
{
command: "clerk enable orgs --force-selection",
description: "Enable and force org selection",
},
{
command: "clerk enable orgs --auto-create --max-members 10",
description: "Enable with auto-creation and member limit",
},
{
command: "clerk enable orgs --dry-run",
description: "Preview the patch without applying it",
},
])
.action(orgsEnable);

enable
.command("billing")
.description("Enable billing for organizations and/or users")
.option(
"--for <targets...>",
"Billing targets (org and/or user), separated by spaces or commas (e.g. org user). Defaults to both when omitted.",
)
.option("--app <id>", "Application ID to target")
.option("--instance <id>", "Instance to target (dev, prod, or instance ID)")
.option("--yes", "Skip confirmation prompts")
.option("--dry-run", "Show the patch that would be sent without applying it")
.option("--no-skills", "Skip the optional `clerk-billing` agent skill install")
.setExamples([
{
command: "clerk enable billing",
description: "Enable billing for organizations and users",
},
{
command: "clerk enable billing --for org",
description: "Enable billing for organizations only",
},
{
command: "clerk enable billing --for user",
description: "Enable billing for users only",
},
{
command: "clerk enable billing --for org user",
description: "Enable billing for both targets",
},
{
command: "clerk enable billing --no-skills",
description: "Enable without installing the agent skill",
},
])
.action(billingEnable);

const disable = program
.command("disable")
.description("Disable Clerk features on the linked instance")
.setExamples([
{ command: "clerk disable orgs", description: "Disable organizations" },
{
command: "clerk disable billing --for org",
description: "Disable billing for organizations only (leaves organizations enabled)",
},
{
command: "clerk disable billing",
description: "Disable billing for organizations and users",
},
]);

disable
.command("orgs")
.alias("organizations")
.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")
.option("--dry-run", "Show the patch that would be sent without applying it")
.setExamples([
{ command: "clerk disable orgs", description: "Disable organizations" },
{
command: "clerk disable orgs --dry-run",
description: "Preview without applying",
},
])
.action(orgsDisable);

disable
.command("billing")
.description(
"Disable billing for organizations and/or users (does not disable organizations themselves)",
)
.option(
"--for <targets...>",
"Billing targets (org and/or user), separated by spaces or commas (e.g. org user). Defaults to both when omitted.",
)
.option("--app <id>", "Application ID to target")
.option("--instance <id>", "Instance to target (dev, prod, or instance ID)")
.option("--yes", "Skip confirmation prompts")
.option("--dry-run", "Show the patch that would be sent without applying it")
.setExamples([
{
command: "clerk disable billing",
description: "Disable billing for organizations and users",
},
{
command: "clerk disable billing --for org",
description: "Disable billing for organizations only",
},
{
command: "clerk disable billing --for user",
description: "Disable billing for users only",
},
])
.action(billingDisable);

program
.command("api")
.description("Make authenticated requests to the Clerk API")
Expand Down
66 changes: 66 additions & 0 deletions packages/cli-core/src/commands/billing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# clerk billing (enable/disable)

Toggle Clerk billing for organizations and/or users on the linked instance.
The handlers are wired to top-level `clerk enable billing` and `clerk disable
billing` commands.

For arbitrary billing config edits (plans, trials, payment-method requirements)
use `clerk config patch --json '{"billing":{...}}'` until a dedicated
`clerk billing settings` command lands.

## Usage

```
clerk enable billing [--for <targets>] [options]
clerk disable billing [--for <targets>] [options]
```

`<targets>` is `org` and/or `user`, accepted as space-separated, comma-separated,
or repeated `--for` flags (matching `clerk config pull --keys`). When omitted,
the command targets both:

```sh
clerk enable billing --for org user
clerk enable billing --for org,user
clerk enable billing --for org --for user
clerk enable billing # defaults to both
```

## Options

| Flag | Description |
| ----------------- | ------------------------------------------------------------------------------- |
| `--for <targets>` | Targets (`org` and/or `user`), separated by spaces or commas. Defaults to both. |
| `--app <id>` | Target a specific application |
| `--instance <id>` | Target a specific instance (dev, prod) |
| `--yes` | Skip the confirmation prompt |
| `--dry-run` | Preview the patch without applying it |
| `--no-skills` | Skip the post-enable `clerk-billing` agent skill install (enable only) |

## Agent skill

After a successful `enable billing`, the command offers to install the upstream `clerk-billing` agent skill from [`clerk/skills`](https://github.com/clerk/skills). `clerk init` doesn't bundle this one as a default — billing is opt-in — so this is the natural moment to surface it.

- **Human mode**: prompts `Install the` `clerk-billing` `agent skill?` defaulting to yes. Decline returns silently.
- **Agent mode (no TTY) or `--yes`**: installs non-interactively (`-y -g`).
- **`--no-skills`**: skips the install entirely.
- **`--dry-run`**: skips the install (no real side-effects in dry-run).

The install runs via the user's package runner (`bunx`, `pnpm dlx`, `yarn dlx`, or `npx`), matching the `clerk init` flow.

## Cascade behavior

- `enable billing --for org` (or `org,user`, or no `--for`) **also** sets
`organization_settings.enabled = true`. Billing for organizations requires
organizations enabled, so this saves a separate command. The cascade is
idempotent — if organizations are already on, the diff is empty for that
field.
- `disable billing` **never** touches `organization_settings`. To disable
organizations themselves, run `clerk disable orgs` separately.

## Clerk API endpoints

| Method | Endpoint | Description |
| ------ | ----------------------------------------------------------------- | ------------------------------------------------------------- |
| GET | `/v1/platform/applications/{appId}/instances/{instanceId}/config` | Fetch current config for diff before mutation |
| PATCH | `/v1/platform/applications/{appId}/instances/{instanceId}/config` | Patch `billing.*` (with `?dry_run=true` when `--dry-run` set) |
Loading
Loading