@arnaudjnn/billing-tools 0.14.0

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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +243 -0
  3. package/dist/adapters/workos-org.d.ts +80 -0
  4. package/dist/adapters/workos-org.d.ts.map +1 -0
  5. package/dist/adapters/workos-org.js +176 -0
  6. package/dist/adapters/workos-org.js.map +1 -0
  7. package/dist/agent-auth/claim-store.d.ts +37 -0
  8. package/dist/agent-auth/claim-store.d.ts.map +1 -0
  9. package/dist/agent-auth/claim-store.js +64 -0
  10. package/dist/agent-auth/claim-store.js.map +1 -0
  11. package/dist/agent-auth/index.d.ts +65 -0
  12. package/dist/agent-auth/index.d.ts.map +1 -0
  13. package/dist/agent-auth/index.js +365 -0
  14. package/dist/agent-auth/index.js.map +1 -0
  15. package/dist/auth.d.ts +16 -0
  16. package/dist/auth.d.ts.map +1 -0
  17. package/dist/auth.js +80 -0
  18. package/dist/auth.js.map +1 -0
  19. package/dist/billing.d.ts +59 -0
  20. package/dist/billing.d.ts.map +1 -0
  21. package/dist/billing.js +258 -0
  22. package/dist/billing.js.map +1 -0
  23. package/dist/cli/client.d.ts +10 -0
  24. package/dist/cli/client.d.ts.map +1 -0
  25. package/dist/cli/client.js +22 -0
  26. package/dist/cli/client.js.map +1 -0
  27. package/dist/cli/commands.d.ts +4 -0
  28. package/dist/cli/commands.d.ts.map +1 -0
  29. package/dist/cli/commands.js +94 -0
  30. package/dist/cli/commands.js.map +1 -0
  31. package/dist/cli/config.d.ts +16 -0
  32. package/dist/cli/config.d.ts.map +1 -0
  33. package/dist/cli/config.js +40 -0
  34. package/dist/cli/config.js.map +1 -0
  35. package/dist/create-billing.d.ts +78 -0
  36. package/dist/create-billing.d.ts.map +1 -0
  37. package/dist/create-billing.js +59 -0
  38. package/dist/create-billing.js.map +1 -0
  39. package/dist/dispatch.d.ts +9 -0
  40. package/dist/dispatch.d.ts.map +1 -0
  41. package/dist/dispatch.js +73 -0
  42. package/dist/dispatch.js.map +1 -0
  43. package/dist/events.d.ts +26 -0
  44. package/dist/events.d.ts.map +1 -0
  45. package/dist/events.js +72 -0
  46. package/dist/events.js.map +1 -0
  47. package/dist/index.d.ts +24 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +40 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/invitations.d.ts +56 -0
  52. package/dist/invitations.d.ts.map +1 -0
  53. package/dist/invitations.js +117 -0
  54. package/dist/invitations.js.map +1 -0
  55. package/dist/machine-payment/index.d.ts +57 -0
  56. package/dist/machine-payment/index.d.ts.map +1 -0
  57. package/dist/machine-payment/index.js +117 -0
  58. package/dist/machine-payment/index.js.map +1 -0
  59. package/dist/magic-auth.d.ts +4 -0
  60. package/dist/magic-auth.d.ts.map +1 -0
  61. package/dist/magic-auth.js +30 -0
  62. package/dist/magic-auth.js.map +1 -0
  63. package/dist/mirror.d.ts +35 -0
  64. package/dist/mirror.d.ts.map +1 -0
  65. package/dist/mirror.js +63 -0
  66. package/dist/mirror.js.map +1 -0
  67. package/dist/plans.d.ts +38 -0
  68. package/dist/plans.d.ts.map +1 -0
  69. package/dist/plans.js +132 -0
  70. package/dist/plans.js.map +1 -0
  71. package/dist/routes/mcp.d.ts +19 -0
  72. package/dist/routes/mcp.d.ts.map +1 -0
  73. package/dist/routes/mcp.js +39 -0
  74. package/dist/routes/mcp.js.map +1 -0
  75. package/dist/routes/rest.d.ts +20 -0
  76. package/dist/routes/rest.d.ts.map +1 -0
  77. package/dist/routes/rest.js +66 -0
  78. package/dist/routes/rest.js.map +1 -0
  79. package/dist/routes/webhook.d.ts +8 -0
  80. package/dist/routes/webhook.d.ts.map +1 -0
  81. package/dist/routes/webhook.js +39 -0
  82. package/dist/routes/webhook.js.map +1 -0
  83. package/dist/sync.d.ts +58 -0
  84. package/dist/sync.d.ts.map +1 -0
  85. package/dist/sync.js +202 -0
  86. package/dist/sync.js.map +1 -0
  87. package/dist/tools/billing.d.ts +4 -0
  88. package/dist/tools/billing.d.ts.map +1 -0
  89. package/dist/tools/billing.js +125 -0
  90. package/dist/tools/billing.js.map +1 -0
  91. package/dist/tools/keys.d.ts +4 -0
  92. package/dist/tools/keys.d.ts.map +1 -0
  93. package/dist/tools/keys.js +107 -0
  94. package/dist/tools/keys.js.map +1 -0
  95. package/dist/tools/register.d.ts +20 -0
  96. package/dist/tools/register.d.ts.map +1 -0
  97. package/dist/tools/register.js +87 -0
  98. package/dist/tools/register.js.map +1 -0
  99. package/dist/types.d.ts +90 -0
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +28 -0
  102. package/dist/types.js.map +1 -0
  103. package/dist/util/clearout.d.ts +5 -0
  104. package/dist/util/clearout.d.ts.map +1 -0
  105. package/dist/util/clearout.js +18 -0
  106. package/dist/util/clearout.js.map +1 -0
  107. package/dist/workos.d.ts +6 -0
  108. package/dist/workos.d.ts.map +1 -0
  109. package/dist/workos.js +24 -0
  110. package/dist/workos.js.map +1 -0
  111. package/package.json +81 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arnaud Jeannin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,243 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/arnaudjnn/billing-tools/main/assets/hero.svg" alt="Billing Tools, the get-paid engine for Stripe + WorkOS apps, for humans and AI agents" width="100%">
3
+ </p>
4
+
5
+ <p align="center">
6
+ <b>Drop-in auth + billing for any Stripe&nbsp;+&nbsp;WorkOS app, usable by humans <i>and</i> AI agents,</b><br/>
7
+ exposed as <b>MCP tools</b>, a <b>REST API</b>, and a <b>CLI</b>, over one storage-agnostic engine.
8
+ </p>
9
+
10
+ <p align="center">
11
+ <img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-635BFF.svg">
12
+ <img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-strict-3178C6.svg">
13
+ <img alt="Stripe" src="https://img.shields.io/badge/Stripe-635BFF.svg">
14
+ <img alt="WorkOS" src="https://img.shields.io/badge/WorkOS-000000.svg">
15
+ <img alt="MCP compatible" src="https://img.shields.io/badge/MCP-compatible-16a34a.svg">
16
+ <img alt="Framework agnostic" src="https://img.shields.io/badge/framework-agnostic-8A8A9A.svg">
17
+ <img alt="PRs welcome" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg">
18
+ </p>
19
+
20
+ ---
21
+
22
+ **Billing Tools** packages the "get money in" plumbing you'd otherwise rebuild in every SaaS: API-key auth on top of **WorkOS**, token/credit + subscription billing on top of **Stripe**, and (because the next wave of customers is autonomous) first-class **agent** rails: [`auth.md`](https://workos.com/auth-md) self-registration and [`MPP`](https://mpp.dev/) machine payments. Wire it once; serve humans through a browser and agents through headless HTTP with the same engine. Storage is pluggable behind one small adapter (use the built-in WorkOS store, or mirror into your own Postgres).
23
+
24
+ ## Table of contents
25
+
26
+ - [Ship tools, not billing plumbing](#ship-tools-not-billing-plumbing)
27
+ - [Key Features](#key-features)
28
+ - [Getting Started](#getting-started)
29
+ - [How it works](#how-it-works)
30
+ - [Agent auth (auth.md)](#agent-auth-authmd)
31
+ - [Machine payments (MPP)](#machine-payments-mpp)
32
+ - [CLI](#cli)
33
+ - [Configuration](#configuration)
34
+ - [Roadmap](#roadmap)
35
+ - [Contributing](#contributing)
36
+ - [License](#license)
37
+
38
+ ## Ship tools, not billing plumbing
39
+
40
+ The next generation of software is **agents**, and an agent is only as useful as the **tools** it can call. Tools *are* the product now.
41
+
42
+ But you don't go to war naked. Ship a tool into the wild with no auth and no way to get paid, and you're just donating GPU time to strangers. Someone still has to meter every call, hold a balance, bill the workspace, enforce seats, and cut off the freeloaders: the boring, identical, get-it-wrong-once-and-bleed-money layer that *every* AI product rebuilds from scratch.
43
+
44
+ **Billing Tools is the armor.** Bring a Stripe + WorkOS stack and you inherit, out of the box, the exact monetization model the frontier labs run on. The same shape as **Anthropic, OpenAI, and xAI (Grok)**:
45
+
46
+ - 💰 **A price per token:** every tool call costs tokens, metered and deducted automatically.
47
+ - 🏢 **Workspaces:** the billable account, with one Stripe customer + balance each.
48
+ - 👥 **Users per workspace:** seats, invitations, and roles.
49
+ - 🪙 **Tokens per user:** per-seat grants, seat limits, and auto-reload.
50
+
51
+ So you do the one thing only you can do (**build great tools**) and monetize them the same afternoon. The get-paid layer is already handled, battle-tested, and agent-ready. 🛡️
52
+
53
+ ## Key Features
54
+
55
+ ### 🔐 Auth & identity
56
+ - 🔑 **API-key auth:** WorkOS organization API keys (`Authorization: Bearer sk_…`), validated on every request.
57
+ - ✉️ **Passwordless magic-auth:** email a 6-digit code, get back a workspace/org + API key. No passwords.
58
+ - 🤖 **auth.md agent self-registration:** the [WorkOS auth.md](https://workos.com/auth-md) protocol (RFC 9728 Protected-Resource-Metadata + RFC 8414 Authorization-Server-Metadata + `/agent/identity` anonymous or verified-email + the claim ceremony), so agents onboard with **no human signup**.
59
+ - ♻️ **RFC 7009 revocation:** revoke a key by value; always-200, no token-existence leak.
60
+ - 🎫 **Optional OAuth-JWT hook:** bring your own MCP OAuth proxy via a single adapter method.
61
+
62
+ ### 💳 Billing & payments
63
+ - 🪙 **Usage-metered token wallet:** held in the native Stripe customer credit balance (1 token = 1¢), with idempotent credit/debit.
64
+ - 🛒 **Checkout top-ups:** Stripe Checkout that auto-offers cards **+ Apple&nbsp;Pay / Google&nbsp;Pay / Link**.
65
+ - 📦 **Declarative subscription plans:** describe plans in code; products/prices are **auto-provisioned in Stripe** via `lookup_key` (immutable-price safe, orphan-cleaning). Zero dashboard clicks.
66
+ - 🎟️ **Per-seat / per-cycle token grants + seat limits:** included tokens scale with active members.
67
+ - 🔁 **Auto-reload:** off-session saved-card recharge when the balance drops below a threshold.
68
+ - 🧾 **Invoices + 🏛️ Customer Portal:** list invoices, and a one-call Stripe Billing Portal URL for self-serve upgrade/downgrade/cancel + card updates.
69
+ - 🔒 **Idempotency on every money move:** welcome bonus, checkout credit, and per-cycle grants each carry a stable key, so retries/replays never double-charge.
70
+
71
+ ### 🤖 Built for AI agents
72
+ - 📄 **auth.md:** the [agent onboarding standard](https://workos.com/auth-md), served for you (`/auth.md` narrative + discovery metadata).
73
+ - 🏧 **MPP machine payments:** Stripe's [Machine Payments Protocol](https://mpp.dev/): a HTTP **402 + `WWW-Authenticate: Payment`** challenge for pay-per-request (SPT card or crypto/USDC), the payment sibling of auth.md's 401.
74
+ - 🧰 **MCP server tools:** `get_api_key`, `get_token_balance`, `buy_tokens`, `list_plans`, `get_billing_portal`, and more, drop straight into Claude, Cursor, or any MCP client.
75
+ - 🧭 **Discovery hints:** every 401/402 advertises `resource_metadata`, so an agent can bootstrap unattended.
76
+
77
+ ### 🧩 Three surfaces, one engine
78
+ - 🛠️ **MCP:** `createMcpTransport({ register, adapter })`.
79
+ - 🌐 **REST:** `createToolListHandler` + `createToolDispatchHandler` (429/401/400/404 mapping built in).
80
+ - ⌨️ **CLI:** `registerBillingCommands(program, …)` gives you `auth`, `keys`, `balance`, `buy`, `invoices`.
81
+ - 🪝 **Stripe webhook:** `createStripeWebhookHandler()` for instant checkout crediting.
82
+
83
+ ### 🔄 Zero-webhook sync
84
+ - 📡 **Event polling:** reconcile Stripe **and** WorkOS via their Events APIs. No webhook endpoints, no signing secrets.
85
+ - ⏱️ **Any scheduler:** run it in-process with `sync.start()` (persistent hosts) or as a serverless cron via `createSyncRoute()`.
86
+ - 🪞 **Generic DB mirror:** shadow WorkOS orgs/users into your own tables (`createMirror`), driver-agnostic.
87
+ - 📉 **Dunning hook:** `invoice.payment_failed` flips the org to `past_due` and fires an `onPaymentFailed` hook (Stripe Smart Retries do the retries).
88
+
89
+ ### 🌍 Framework and platform agnostic
90
+ - ⚡ **Web-standard handlers:** everything returns `Request` to `Response`; mount in Next.js, Hono, Bun, Deno, or Cloudflare Workers.
91
+ - 🧱 **No framework imports:** runtime deps are just `stripe` + `@workos-inc/node`.
92
+ - 🗄️ **Bring your own DB:** no `pg` dependency; you pass a `query` executor (any Postgres-compatible driver).
93
+ - 🔌 **Pluggable storage adapter:** WorkOS-only, or WorkOS + your DB mirror, behind one interface.
94
+ - ☁️ **No lock-in:** runs on any Node host (Railway / Render / Fly / Vercel / self-host).
95
+
96
+ ### 🏛️ Design doctrine
97
+ - 📐 **SDK-first:** thin-wraps the Stripe & WorkOS SDKs (SDK types, pagination, typed errors, idempotency) so the lib evolves *with* the platforms instead of drifting.
98
+ - 🎯 **One memoized client per SDK:** lazy, never constructed at import.
99
+ - 🔒 **Secure by default:** AES-256-GCM session encryption at rest, SHA-256-hashed claim tokens, verified-domain-only internal-org checks.
100
+ - 🧪 **Offline-testable:** the auth.md + MPP protocol surfaces are pure `Request` to `Response`, unit-testable without a live account.
101
+
102
+ ## Getting Started
103
+
104
+ ### Prerequisites
105
+ - **Node 18+**
106
+ - A **Stripe** secret key (`STRIPE_SECRET_KEY`)
107
+ - A **WorkOS** API key + client id (`WORKOS_API_KEY`, `WORKOS_CLIENT_ID`)
108
+
109
+ ### Install
110
+
111
+ ```bash
112
+ npm install @arnaudjnn/billing-tools
113
+ # peers (provide the ones you use): @modelcontextprotocol/sdk mcp-handler zod
114
+ ```
115
+
116
+ Ships compiled `dist/`, so there's no build step or `transpilePackages` needed in the consumer.
117
+
118
+ ### Environment
119
+
120
+ ```bash
121
+ STRIPE_SECRET_KEY=sk_test_…
122
+ STRIPE_WEBHOOK_SECRET=whsec_… # only if you mount the webhook
123
+ WORKOS_API_KEY=sk_…
124
+ WORKOS_CLIENT_ID=client_…
125
+ INTERNAL_ORG_DOMAINS=acme.com # optional: orgs with these verified domains are unmetered
126
+ ```
127
+
128
+ ### Wire it up (one call)
129
+
130
+ `createBilling()` composes every surface from a single config (one module instance = one shared auth context). You supply the adapter + config; it returns the mounted handlers.
131
+
132
+ ```ts
133
+ // billing.ts
134
+ import { createBilling } from "@arnaudjnn/billing-tools";
135
+ import { WorkOSOrgAdapter } from "@arnaudjnn/billing-tools/adapters/workos-org";
136
+
137
+ export const { mcp, restList, restDispatch, webhook, agentAuth } = createBilling({
138
+ adapter: new WorkOSOrgAdapter(), // WorkOS is the source of truth
139
+ config: { currency: "usd", baseUrl: process.env.APP_URL! },
140
+ agentAuth: { branding: { productName: "Acme" } }, // enables auth.md (optional)
141
+ });
142
+ ```
143
+
144
+ ```ts
145
+ // app/[transport]/route.ts → export const { GET, POST } = mcp
146
+ // app/api/v0/route.ts → export const GET = restList
147
+ // app/api/v0/[tool]/route.ts → export const POST = restDispatch
148
+ // app/api/stripe/webhook/route.ts→ export const POST = webhook
149
+ ```
150
+
151
+ Prefer fine-grained control? Every factory is exported individually (`registerBillingTools`, `createMcpTransport`, `createToolListHandler`, `createToolDispatchHandler`, `createStripeWebhookHandler`, `createAgentAuth`, …) so you can build your own composition root.
152
+
153
+ ### First call
154
+
155
+ ```bash
156
+ # List available tools + costs
157
+ curl https://your-app.com/api/v0
158
+
159
+ # Use a key (Bearer sk_…)
160
+ curl -X POST https://your-app.com/api/v0/get_token_balance \
161
+ -H "Authorization: Bearer sk_…"
162
+ ```
163
+
164
+ Add agent onboarding with [`createAgentAuth`](#agent-auth-authmd) and pay-per-call with [`createMachinePaymentHandler`](#machine-payments-mpp). See below.
165
+
166
+ ## How it works
167
+
168
+ **WorkOS is always the source of truth.** Your app's `orgId` is opaque: implement the adapter and everything (auth, metering, Stripe math, all surfaces) works unchanged. Two shipped patterns:
169
+
170
+ | Pattern | What `orgId` maps to | Storage | Use when |
171
+ |---|---|---|---|
172
+ | **A: WorkOS-only** | the WorkOS org id | none beyond WorkOS | you don't have (or need) your own DB rows |
173
+ | **B: WorkOS + DB mirror** | your own id (e.g. `ws_…`) | a `workos_org_id` column + `org.externalId`, 1:1 | you keep local rows and mirror WorkOS into them |
174
+
175
+ In both, API keys are WorkOS org keys and the Stripe pointer lives on the org. The `WorkOSOrgAdapter` (`@arnaudjnn/billing-tools/adapters/workos-org`) implements both; pass a `map` for Pattern B.
176
+
177
+ ## Agent auth (auth.md)
178
+
179
+ Implements the [WorkOS auth.md](https://workos.com/auth-md) spec.
180
+
181
+ ```ts
182
+ import { createAgentAuth } from "@arnaudjnn/billing-tools";
183
+
184
+ export const agentAuth = createAgentAuth({
185
+ adapter, config,
186
+ branding: { productName: "Acme", logoUri: "https://acme.com/logo.svg" },
187
+ identityTypes: ["anonymous", "verified_email"],
188
+ });
189
+ // mount: /auth.md, /.well-known/oauth-{protected-resource,authorization-server},
190
+ // /agent/identity, /agent/identity/claim, /oauth/token, /oauth/revoke
191
+ ```
192
+
193
+ An agent hits a 401, follows the `resource_metadata` hint to your metadata, reads `/auth.md`, then registers (either **anonymously** for an instant key, or via a **verified-email** claim ceremony where the user reads back a code) and starts calling tools. All handlers are `Request` to `Response`.
194
+
195
+ ## Machine payments (MPP)
196
+
197
+ Implements Stripe's [Machine Payments Protocol](https://mpp.dev/).
198
+
199
+ ```ts
200
+ import { createMachinePaymentHandler } from "@arnaudjnn/billing-tools";
201
+
202
+ const pay = createMachinePaymentHandler({ methods: ["stripe"], amount: 50, currency: "usd" });
203
+ const gate = await pay.requirePayment(request);
204
+ if (gate instanceof Response) return gate; // 402 + WWW-Authenticate: Payment challenge
205
+ // else settlement succeeded, serve the paid resource
206
+ ```
207
+
208
+ The 402 challenge + credential parsing ship ready and are offline-testable. Actual settlement (card via shared payment tokens, or crypto/USDC) is injected via a `settle` function once your Stripe account is enabled for machine payments; until then the gate returns a clean "settlement not enabled" 402 (never a crash). `createPaymentMd()` serves an agent-facing `/payment.md`.
209
+
210
+ ## CLI
211
+
212
+ ```ts
213
+ import { registerBillingCommands } from "@arnaudjnn/billing-tools";
214
+ import { Command } from "commander";
215
+
216
+ const program = new Command();
217
+ registerBillingCommands(program, { configDir: "~/.acme", envPrefix: "ACME", defaultUrl: "https://acme.com" });
218
+ // acme auth | keys list|revoke | balance | buy | invoices
219
+ ```
220
+
221
+ ## Configuration
222
+
223
+ `BillingConfig` (pass to `resolveConfig`):
224
+
225
+ | Field | Default | Purpose |
226
+ |---|---|---|
227
+ | `baseUrl` | required | Checkout success/cancel + portal return URLs |
228
+ | `currency` | `"usd"` | Stripe currency |
229
+ | `freeTokens` | `100` | Welcome credit on first customer creation |
230
+ | `internalDomains` | `[]` | Orgs with these **verified** WorkOS domains are unmetered (see `internalDomainsFromEnv`) |
231
+
232
+ ## Roadmap
233
+
234
+ - 🧾 Stripe Tax (`automatic_tax`) opt-in
235
+ - 🪙 x402 machine-payment protocol alongside [MPP](https://mpp.dev/)
236
+
237
+ ## Contributing
238
+
239
+ Issues and PRs welcome. The engine is plain TypeScript compiled with `tsc`; `dist/` is committed (consumers install via Git). See `AGENTS.md` for the architecture, the adapter interface, the SDK-first doctrine, and the release flow.
240
+
241
+ ## License
242
+
243
+ [MIT](LICENSE) © Arnaud Jeannin
@@ -0,0 +1,80 @@
1
+ import type { BillingAdapter, BillingUser, ApiKeyInfo } from "../types.js";
2
+ /** Bidirectional id map between the app's `orgId` and the WorkOS org id. */
3
+ export interface WorkOSOrgMap {
4
+ /** app orgId → WorkOS org id (create/reconcile-on-read as needed). */
5
+ toWorkosOrgId(orgId: string): Promise<string>;
6
+ /** WorkOS org id → app orgId, or null if it maps to nothing. */
7
+ toOrgId(workosOrgId: string): Promise<string | null>;
8
+ }
9
+ export interface WorkOSOrgAdapterOptions {
10
+ apiKey?: string;
11
+ clientId?: string;
12
+ /** Key name for new keys. Default "API Key". */
13
+ keyName?: string;
14
+ /** DB-mirror id map. Omit for a WorkOS-only app (identity mapping). */
15
+ map?: WorkOSOrgMap;
16
+ /** Override org creation (e.g. also insert the mirror workspace row +
17
+ * membership). Omit to use the default (company-domain org + verified
18
+ * domain + membership). */
19
+ ensureOrg?: (user: BillingUser) => Promise<{
20
+ orgId: string;
21
+ }>;
22
+ }
23
+ export declare class WorkOSOrgAdapter implements BillingAdapter {
24
+ private apiKey?;
25
+ private clientId?;
26
+ private map?;
27
+ private ensureOrg?;
28
+ constructor(opts?: WorkOSOrgAdapterOptions);
29
+ private get workos();
30
+ /** app orgId → WorkOS org id (identity when no map is configured). */
31
+ private wid;
32
+ validateApiKey(token: string): Promise<{
33
+ orgId: string;
34
+ } | null>;
35
+ getOrgDomains(orgId: string): Promise<string[]>;
36
+ getBillingCustomerId(orgId: string): Promise<string | null>;
37
+ setBillingCustomerId(orgId: string, customerId: string): Promise<void>;
38
+ ensureOrgForUser(user: BillingUser): Promise<{
39
+ orgId: string;
40
+ }>;
41
+ mintApiKey(orgId: string, name: string, _createdBy?: string): Promise<{
42
+ id: string;
43
+ value: string;
44
+ }>;
45
+ listApiKeys(orgId: string): Promise<ApiKeyInfo[]>;
46
+ /** Revoke by raw token value (RFC 7009): validate → key id → hard delete. */
47
+ revokeApiKeyByToken(token: string): Promise<boolean>;
48
+ /** Create an org with no user (auth.md anonymous). No verified domain, so it
49
+ * never satisfies the internal-org unmetered check. Returns the WorkOS org id
50
+ * as `orgId` (Pattern A). With a `map` configured there's no mirror row, so
51
+ * prefer disabling anonymous for mirror apps (identityTypes without it). */
52
+ createAnonymousOrg(opts: {
53
+ name: string;
54
+ metadata?: Record<string, string>;
55
+ }): Promise<{
56
+ orgId: string;
57
+ }>;
58
+ revokeApiKey(orgId: string, id: string): Promise<{
59
+ id: string;
60
+ name: string;
61
+ } | null>;
62
+ /** Active-member count for the org (per-seat token grants + seat limits).
63
+ * Auto-paginates so orgs with >100 members aren't undercounted. */
64
+ memberCount(orgId: string): Promise<number>;
65
+ getSubscription(orgId: string): Promise<{
66
+ plan: string | null;
67
+ status: string | null;
68
+ subscriptionId: string | null;
69
+ periodEnd: string | null;
70
+ }>;
71
+ /** Write subscription state onto the org metadata. `plan: undefined` leaves
72
+ * the plan as-is; `null` clears it (back to the default plan). */
73
+ setSubscription(orgId: string, sub: {
74
+ plan?: string | null;
75
+ status: string | null;
76
+ subscriptionId: string | null;
77
+ periodEnd: string | null;
78
+ }): Promise<void>;
79
+ }
80
+ //# sourceMappingURL=workos-org.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workos-org.d.ts","sourceRoot":"","sources":["../../src/adapters/workos-org.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,176 @@
1
+ import { DomainDataState, OrganizationDomainState } from "@workos-inc/node";
2
+ import { getWorkOS } from "../workos.js";
3
+ import { lookupCompany } from "../util/clearout.js";
4
+ export class WorkOSOrgAdapter {
5
+ apiKey;
6
+ clientId;
7
+ map;
8
+ ensureOrg;
9
+ constructor(opts = {}) {
10
+ this.apiKey = opts.apiKey;
11
+ this.clientId = opts.clientId;
12
+ this.map = opts.map;
13
+ this.ensureOrg = opts.ensureOrg;
14
+ }
15
+ // The shared, lazily-memoized client (see workos.ts). With no explicit creds
16
+ // this is the process-wide singleton; explicit creds get their own client.
17
+ get workos() {
18
+ return getWorkOS({ apiKey: this.apiKey, clientId: this.clientId });
19
+ }
20
+ /** app orgId → WorkOS org id (identity when no map is configured). */
21
+ wid(orgId) {
22
+ return this.map ? this.map.toWorkosOrgId(orgId) : Promise.resolve(orgId);
23
+ }
24
+ async validateApiKey(token) {
25
+ try {
26
+ const { apiKey } = await this.workos.apiKeys.createValidation({ value: token });
27
+ if (!apiKey)
28
+ return null;
29
+ const workosOrgId = apiKey.owner.id;
30
+ const orgId = this.map ? await this.map.toOrgId(workosOrgId) : workosOrgId;
31
+ return orgId ? { orgId } : null;
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ async getOrgDomains(orgId) {
38
+ const org = await this.workos.organizations.getOrganization(await this.wid(orgId));
39
+ // Only VERIFIED domains count — a pending/failed domain must not unlock the
40
+ // internal-org unmetered path (see auth.ts isInternalOrg).
41
+ return org.domains
42
+ .filter((d) => d.state === OrganizationDomainState.Verified)
43
+ .map((d) => d.domain);
44
+ }
45
+ async getBillingCustomerId(orgId) {
46
+ const org = await this.workos.organizations.getOrganization(await this.wid(orgId));
47
+ // Native field first (v10); fall back to the legacy metadata pointer so
48
+ // apps that stored it in metadata under v8 don't orphan their customer.
49
+ return (org.stripeCustomerId ||
50
+ org.metadata?.stripeCustomerId ||
51
+ null);
52
+ }
53
+ async setBillingCustomerId(orgId, customerId) {
54
+ await this.workos.organizations.updateOrganization({
55
+ organization: await this.wid(orgId),
56
+ stripeCustomerId: customerId,
57
+ });
58
+ }
59
+ async ensureOrgForUser(user) {
60
+ if (this.ensureOrg)
61
+ return this.ensureOrg(user);
62
+ const memberships = await this.workos.userManagement.listOrganizationMemberships({ userId: user.id });
63
+ if (memberships.data.length > 0)
64
+ return { orgId: memberships.data[0].organizationId };
65
+ const domain = user.email.split("@")[1];
66
+ const company = await lookupCompany(domain);
67
+ const org = await this.workos.organizations.createOrganization({
68
+ name: company?.name || domain,
69
+ domainData: [{ domain, state: DomainDataState.Verified }],
70
+ metadata: company?.logoUrl ? { logoUrl: company.logoUrl } : undefined,
71
+ });
72
+ await this.workos.userManagement.createOrganizationMembership({
73
+ organizationId: org.id,
74
+ userId: user.id,
75
+ });
76
+ return { orgId: org.id };
77
+ }
78
+ async mintApiKey(orgId, name, _createdBy) {
79
+ const key = await this.workos.apiKeys.createOrganizationApiKey({
80
+ organizationId: await this.wid(orgId),
81
+ name,
82
+ });
83
+ return { id: key.id, value: key.value };
84
+ }
85
+ async listApiKeys(orgId) {
86
+ const paginatable = await this.workos.apiKeys.listOrganizationApiKeys({
87
+ organizationId: await this.wid(orgId),
88
+ });
89
+ const keys = await paginatable.autoPagination();
90
+ return keys.map((k) => ({
91
+ id: k.id,
92
+ name: k.name,
93
+ obfuscatedValue: k.obfuscatedValue,
94
+ }));
95
+ }
96
+ /** Revoke by raw token value (RFC 7009): validate → key id → hard delete. */
97
+ async revokeApiKeyByToken(token) {
98
+ try {
99
+ const { apiKey } = await this.workos.apiKeys.createValidation({ value: token });
100
+ if (!apiKey)
101
+ return false;
102
+ await this.workos.apiKeys.deleteApiKey(apiKey.id);
103
+ return true;
104
+ }
105
+ catch {
106
+ return false;
107
+ }
108
+ }
109
+ /** Create an org with no user (auth.md anonymous). No verified domain, so it
110
+ * never satisfies the internal-org unmetered check. Returns the WorkOS org id
111
+ * as `orgId` (Pattern A). With a `map` configured there's no mirror row, so
112
+ * prefer disabling anonymous for mirror apps (identityTypes without it). */
113
+ async createAnonymousOrg(opts) {
114
+ const org = await this.workos.organizations.createOrganization({
115
+ name: opts.name,
116
+ metadata: opts.metadata,
117
+ });
118
+ const orgId = this.map ? (await this.map.toOrgId(org.id)) ?? org.id : org.id;
119
+ return { orgId };
120
+ }
121
+ async revokeApiKey(orgId, id) {
122
+ // Full-list scope check (all pages) so a key on a later page isn't mistaken
123
+ // for "not found"; the SDK delete needs only the id.
124
+ const paginatable = await this.workos.apiKeys.listOrganizationApiKeys({
125
+ organizationId: await this.wid(orgId),
126
+ });
127
+ const keys = await paginatable.autoPagination();
128
+ const target = keys.find((k) => k.id === id);
129
+ if (!target)
130
+ return null;
131
+ await this.workos.apiKeys.deleteApiKey(id);
132
+ return { id: target.id, name: target.name };
133
+ }
134
+ // ── Subscription state + seats (org metadata; used by the billing-sync) ────
135
+ /** Active-member count for the org (per-seat token grants + seat limits).
136
+ * Auto-paginates so orgs with >100 members aren't undercounted. */
137
+ async memberCount(orgId) {
138
+ const paginatable = await this.workos.userManagement.listOrganizationMemberships({
139
+ organizationId: await this.wid(orgId),
140
+ statuses: ["active"],
141
+ });
142
+ const members = await paginatable.autoPagination();
143
+ return members.length;
144
+ }
145
+ async getSubscription(orgId) {
146
+ const org = await this.workos.organizations.getOrganization(await this.wid(orgId));
147
+ const m = (org.metadata ?? {});
148
+ return {
149
+ plan: m.plan ?? null,
150
+ status: m.subscriptionStatus ?? null,
151
+ subscriptionId: m.stripeSubscriptionId ?? null,
152
+ periodEnd: m.subscriptionPeriodEnd ?? null,
153
+ };
154
+ }
155
+ /** Write subscription state onto the org metadata. `plan: undefined` leaves
156
+ * the plan as-is; `null` clears it (back to the default plan). */
157
+ async setSubscription(orgId, sub) {
158
+ const wid = await this.wid(orgId);
159
+ const org = await this.workos.organizations.getOrganization(wid);
160
+ const metadata = { ...(org.metadata ?? {}) };
161
+ const set = (k, v) => {
162
+ if (v === undefined)
163
+ return;
164
+ if (v === null || v === "")
165
+ delete metadata[k];
166
+ else
167
+ metadata[k] = v;
168
+ };
169
+ set("subscriptionStatus", sub.status);
170
+ set("stripeSubscriptionId", sub.subscriptionId);
171
+ set("subscriptionPeriodEnd", sub.periodEnd);
172
+ set("plan", sub.plan);
173
+ await this.workos.organizations.updateOrganization({ organization: wid, metadata });
174
+ }
175
+ }
176
+ //# sourceMappingURL=workos-org.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workos-org.js","sourceRoot":"","sources":["../../src/adapters/workos-org.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ export type ClaimStatus = "pending" | "ready" | "error";
2
+ export type ClaimReadResult = {
3
+ status: "not_found";
4
+ } | {
5
+ status: "pending";
6
+ email: string;
7
+ } | {
8
+ status: "ready";
9
+ email: string;
10
+ apiKey: string;
11
+ organizationId: string;
12
+ } | {
13
+ status: "error";
14
+ email: string;
15
+ error: string;
16
+ };
17
+ /** Persistence for the claim ceremony. claim_token is hashed at rest by the
18
+ * default store (SHA-256) per the auth.md spec; a custom store should do the
19
+ * same and never persist the plaintext token. */
20
+ export interface ClaimStore {
21
+ create(email: string): Promise<{
22
+ claimToken: string;
23
+ expiresIn: number;
24
+ }>;
25
+ get(claimToken: string): Promise<ClaimReadResult>;
26
+ markReady(claimToken: string, data: {
27
+ apiKey: string;
28
+ organizationId: string;
29
+ }): Promise<boolean>;
30
+ markError(claimToken: string, error: string): Promise<boolean>;
31
+ /** One-shot: return the entry and delete it once ready/error. */
32
+ consume(claimToken: string): Promise<ClaimReadResult>;
33
+ }
34
+ /** Default single-process claim store. sha256(claim_token) is the map key;
35
+ * 10-minute TTL; pruned every minute + lazily on read. */
36
+ export declare function inMemoryClaimStore(ttlMs?: number): ClaimStore;
37
+ //# sourceMappingURL=claim-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim-store.d.ts","sourceRoot":"","sources":["../../src/agent-auth/claim-store.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAExD,MAAM,MAAM,eAAe,GACvB;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GACvB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GAC1E;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD;;kDAEkD;AAClD,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1E,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAClD,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClG,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,iEAAiE;IACjE,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CACvD;AAWD;2DAC2D;AAC3D,wBAAgB,kBAAkB,CAAC,KAAK,SAAiB,GAAG,UAAU,CAqDrE"}
@@ -0,0 +1,64 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ /** Default single-process claim store. sha256(claim_token) is the map key;
3
+ * 10-minute TTL; pruned every minute + lazily on read. */
4
+ export function inMemoryClaimStore(ttlMs = 10 * 60 * 1000) {
5
+ const claims = new Map();
6
+ const hash = (t) => createHash("sha256").update(t).digest("hex");
7
+ const prune = () => {
8
+ const now = Date.now();
9
+ for (const [k, v] of claims)
10
+ if (now > v.expiresAt)
11
+ claims.delete(k);
12
+ };
13
+ const g = globalThis;
14
+ if (typeof globalThis !== "undefined" && !g.__billing_claim_cleanup) {
15
+ g.__billing_claim_cleanup = setInterval(prune, 60 * 1000);
16
+ g.__billing_claim_cleanup.unref?.();
17
+ }
18
+ const read = (claimToken) => {
19
+ prune();
20
+ const e = claims.get(hash(claimToken));
21
+ if (!e)
22
+ return { status: "not_found" };
23
+ if (e.status === "pending")
24
+ return { status: "pending", email: e.email };
25
+ if (e.status === "error")
26
+ return { status: "error", email: e.email, error: e.error ?? "unknown" };
27
+ return { status: "ready", email: e.email, apiKey: e.apiKey, organizationId: e.organizationId };
28
+ };
29
+ return {
30
+ async create(email) {
31
+ prune();
32
+ const claimToken = `clm_${randomBytes(32).toString("base64url")}`;
33
+ claims.set(hash(claimToken), { email, status: "pending", expiresAt: Date.now() + ttlMs });
34
+ return { claimToken, expiresIn: Math.floor(ttlMs / 1000) };
35
+ },
36
+ async get(claimToken) {
37
+ return read(claimToken);
38
+ },
39
+ async markReady(claimToken, data) {
40
+ const e = claims.get(hash(claimToken));
41
+ if (!e)
42
+ return false;
43
+ e.status = "ready";
44
+ e.apiKey = data.apiKey;
45
+ e.organizationId = data.organizationId;
46
+ return true;
47
+ },
48
+ async markError(claimToken, error) {
49
+ const e = claims.get(hash(claimToken));
50
+ if (!e)
51
+ return false;
52
+ e.status = "error";
53
+ e.error = error;
54
+ return true;
55
+ },
56
+ async consume(claimToken) {
57
+ const result = read(claimToken);
58
+ if (result.status === "ready" || result.status === "error")
59
+ claims.delete(hash(claimToken));
60
+ return result;
61
+ },
62
+ };
63
+ }
64
+ //# sourceMappingURL=claim-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim-store.js","sourceRoot":"","sources":["../../src/agent-auth/claim-store.ts"],"names":[],"mappings":"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"}