@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.
- package/LICENSE +21 -0
- package/README.md +243 -0
- package/dist/adapters/workos-org.d.ts +80 -0
- package/dist/adapters/workos-org.d.ts.map +1 -0
- package/dist/adapters/workos-org.js +176 -0
- package/dist/adapters/workos-org.js.map +1 -0
- package/dist/agent-auth/claim-store.d.ts +37 -0
- package/dist/agent-auth/claim-store.d.ts.map +1 -0
- package/dist/agent-auth/claim-store.js +64 -0
- package/dist/agent-auth/claim-store.js.map +1 -0
- package/dist/agent-auth/index.d.ts +65 -0
- package/dist/agent-auth/index.d.ts.map +1 -0
- package/dist/agent-auth/index.js +365 -0
- package/dist/agent-auth/index.js.map +1 -0
- package/dist/auth.d.ts +16 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +80 -0
- package/dist/auth.js.map +1 -0
- package/dist/billing.d.ts +59 -0
- package/dist/billing.d.ts.map +1 -0
- package/dist/billing.js +258 -0
- package/dist/billing.js.map +1 -0
- package/dist/cli/client.d.ts +10 -0
- package/dist/cli/client.d.ts.map +1 -0
- package/dist/cli/client.js +22 -0
- package/dist/cli/client.js.map +1 -0
- package/dist/cli/commands.d.ts +4 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +94 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/config.d.ts +16 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +40 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/create-billing.d.ts +78 -0
- package/dist/create-billing.d.ts.map +1 -0
- package/dist/create-billing.js +59 -0
- package/dist/create-billing.js.map +1 -0
- package/dist/dispatch.d.ts +9 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +73 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/events.d.ts +26 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +72 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/invitations.d.ts +56 -0
- package/dist/invitations.d.ts.map +1 -0
- package/dist/invitations.js +117 -0
- package/dist/invitations.js.map +1 -0
- package/dist/machine-payment/index.d.ts +57 -0
- package/dist/machine-payment/index.d.ts.map +1 -0
- package/dist/machine-payment/index.js +117 -0
- package/dist/machine-payment/index.js.map +1 -0
- package/dist/magic-auth.d.ts +4 -0
- package/dist/magic-auth.d.ts.map +1 -0
- package/dist/magic-auth.js +30 -0
- package/dist/magic-auth.js.map +1 -0
- package/dist/mirror.d.ts +35 -0
- package/dist/mirror.d.ts.map +1 -0
- package/dist/mirror.js +63 -0
- package/dist/mirror.js.map +1 -0
- package/dist/plans.d.ts +38 -0
- package/dist/plans.d.ts.map +1 -0
- package/dist/plans.js +132 -0
- package/dist/plans.js.map +1 -0
- package/dist/routes/mcp.d.ts +19 -0
- package/dist/routes/mcp.d.ts.map +1 -0
- package/dist/routes/mcp.js +39 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/rest.d.ts +20 -0
- package/dist/routes/rest.d.ts.map +1 -0
- package/dist/routes/rest.js +66 -0
- package/dist/routes/rest.js.map +1 -0
- package/dist/routes/webhook.d.ts +8 -0
- package/dist/routes/webhook.d.ts.map +1 -0
- package/dist/routes/webhook.js +39 -0
- package/dist/routes/webhook.js.map +1 -0
- package/dist/sync.d.ts +58 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +202 -0
- package/dist/sync.js.map +1 -0
- package/dist/tools/billing.d.ts +4 -0
- package/dist/tools/billing.d.ts.map +1 -0
- package/dist/tools/billing.js +125 -0
- package/dist/tools/billing.js.map +1 -0
- package/dist/tools/keys.d.ts +4 -0
- package/dist/tools/keys.d.ts.map +1 -0
- package/dist/tools/keys.js +107 -0
- package/dist/tools/keys.js.map +1 -0
- package/dist/tools/register.d.ts +20 -0
- package/dist/tools/register.d.ts.map +1 -0
- package/dist/tools/register.js +87 -0
- package/dist/tools/register.js.map +1 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +28 -0
- package/dist/types.js.map +1 -0
- package/dist/util/clearout.d.ts +5 -0
- package/dist/util/clearout.d.ts.map +1 -0
- package/dist/util/clearout.js +18 -0
- package/dist/util/clearout.js.map +1 -0
- package/dist/workos.d.ts +6 -0
- package/dist/workos.d.ts.map +1 -0
- package/dist/workos.js +24 -0
- package/dist/workos.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rest.js","sourceRoot":"","sources":["../../src/routes/rest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type Stripe from "stripe";
|
|
2
|
+
export interface WebhookOptions {
|
|
3
|
+
currency?: string;
|
|
4
|
+
/** Called for events this handler doesn't process (e.g. subscription.*). */
|
|
5
|
+
onOtherEvent?: (event: Stripe.Event) => Promise<void> | void;
|
|
6
|
+
}
|
|
7
|
+
export declare function createStripeWebhookHandler(opts?: WebhookOptions): (request: Request) => Promise<Response>;
|
|
8
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/routes/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAQjC,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC9D;AAOD,wBAAgB,0BAA0B,CAAC,IAAI,GAAE,cAAmB,IAEpD,SAAS,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAmCnD"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getStripe, creditTokens } from "../billing.js";
|
|
2
|
+
function customerIdOf(v) {
|
|
3
|
+
if (!v)
|
|
4
|
+
return null;
|
|
5
|
+
return typeof v === "string" ? v : v.id;
|
|
6
|
+
}
|
|
7
|
+
export function createStripeWebhookHandler(opts = {}) {
|
|
8
|
+
const currency = opts.currency ?? "usd";
|
|
9
|
+
return async (request) => {
|
|
10
|
+
const body = await request.text();
|
|
11
|
+
const signature = request.headers.get("stripe-signature");
|
|
12
|
+
if (!signature)
|
|
13
|
+
return Response.json({ error: "Missing signature" }, { status: 400 });
|
|
14
|
+
let event;
|
|
15
|
+
try {
|
|
16
|
+
event = getStripe().webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
20
|
+
return Response.json({ error: `Webhook signature verification failed: ${message}` }, { status: 400 });
|
|
21
|
+
}
|
|
22
|
+
if (event.type === "checkout.session.completed") {
|
|
23
|
+
const session = event.data.object;
|
|
24
|
+
if (session.mode === "payment") {
|
|
25
|
+
const customerId = customerIdOf(session.customer);
|
|
26
|
+
const tokens = parseInt(session.metadata?.tokens || "0", 10);
|
|
27
|
+
if (customerId && tokens > 0) {
|
|
28
|
+
// Idempotency key on the session id: a re-delivered webhook credits once.
|
|
29
|
+
await creditTokens(customerId, tokens, `Purchase: ${tokens} tokens via Checkout`, currency, `credit:checkout:${session.id}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (opts.onOtherEvent) {
|
|
34
|
+
await opts.onOtherEvent(event);
|
|
35
|
+
}
|
|
36
|
+
return Response.json({ received: true });
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/routes/webhook.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAaxD,SAAS,YAAY,CAAC,CAA6C;IACjE,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,OAAuB,EAAE;IAClE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC;IACxC,OAAO,KAAK,EAAE,OAAgB,EAAqB,EAAE;QACnD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAC1D,IAAI,CAAC,SAAS;YAAE,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAEtF,IAAI,KAAmB,CAAC;QACxB,IAAI,CAAC;YACH,KAAK,GAAG,SAAS,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAsB,CAAC,CAAC;QACnG,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YACrE,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0CAA0C,OAAO,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QACxG,CAAC;QAED,IAAI,KAAK,CAAC,IAAI,KAAK,4BAA4B,EAAE,CAAC;YAChD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,MAAiC,CAAC;YAC7D,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC/B,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,QAAmC,CAAC,CAAC;gBAC7E,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;gBAC7D,IAAI,UAAU,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC7B,0EAA0E;oBAC1E,MAAM,YAAY,CAChB,UAAU,EACV,MAAM,EACN,aAAa,MAAM,sBAAsB,EACzC,QAAQ,EACR,mBAAmB,OAAO,CAAC,EAAE,EAAE,CAChC,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;aAAM,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAC7B,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/sync.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type PlansConfig } from "./plans.js";
|
|
2
|
+
import type { Mirror, MirrorQuery } from "./mirror.js";
|
|
3
|
+
import type { WorkOSOrgAdapter } from "./adapters/workos-org.js";
|
|
4
|
+
/** Optional custom cursor persistence. By default the sync manages its own tiny
|
|
5
|
+
* cursor table via the `query` executor — the app declares nothing. */
|
|
6
|
+
export interface CursorStore {
|
|
7
|
+
get(source: string): Promise<string | null>;
|
|
8
|
+
set(source: string, cursor: string | null): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export interface BillingSyncOptions {
|
|
11
|
+
adapter: WorkOSOrgAdapter;
|
|
12
|
+
plans: PlansConfig;
|
|
13
|
+
/** DB executor (same one you pass to createMirror). The sync creates + uses
|
|
14
|
+
* its own `billing_sync_cursors` table through it — no app schema needed. */
|
|
15
|
+
query: MirrorQuery;
|
|
16
|
+
currency?: string;
|
|
17
|
+
/** Override cursor persistence (advanced). Defaults to the query-backed table. */
|
|
18
|
+
cursor?: CursorStore;
|
|
19
|
+
/** Mirror of the WorkOS Organization (e.g. a workspaces table). */
|
|
20
|
+
orgMirror?: Mirror;
|
|
21
|
+
/** Mirror of the WorkOS User (e.g. a users table). */
|
|
22
|
+
userMirror?: Mirror;
|
|
23
|
+
hooks?: {
|
|
24
|
+
/** Extra app-specific cleanup when a WorkOS user is deleted (the user
|
|
25
|
+
* mirror row is already removed). */
|
|
26
|
+
onUserDeleted?(workosUserId: string): Promise<void>;
|
|
27
|
+
/** A subscription invoice failed to collect (dunning). The org's status is
|
|
28
|
+
* already set to `past_due`; use this to notify the user / gate access.
|
|
29
|
+
* Stripe Smart Retries + the card-updater keep retrying automatically. */
|
|
30
|
+
onPaymentFailed?(orgId: string): Promise<void>;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export interface BillingSync {
|
|
34
|
+
/** Poll + reconcile once. Use this from a serverless cron route. */
|
|
35
|
+
runOnce(): Promise<{
|
|
36
|
+
stripe: number;
|
|
37
|
+
workos: number;
|
|
38
|
+
}>;
|
|
39
|
+
/** Start an in-process interval scheduler (for a long-lived server — e.g.
|
|
40
|
+
* Next's instrumentation register()). Runs immediately, then every
|
|
41
|
+
* intervalMs (default 60s), never overlapping. Returns a stop() fn.
|
|
42
|
+
* For multi-instance deployments run it on one replica (or use runOnce via
|
|
43
|
+
* a single external cron) to avoid duplicate polling. */
|
|
44
|
+
start(opts?: {
|
|
45
|
+
intervalMs?: number;
|
|
46
|
+
onError?: (e: unknown) => void;
|
|
47
|
+
}): () => void;
|
|
48
|
+
}
|
|
49
|
+
export declare function createBillingSync(opts: BillingSyncOptions): BillingSync;
|
|
50
|
+
/** Web-standard (Request → Response) handler that runs one sync cycle — for a
|
|
51
|
+
* serverless cron trigger. Framework-agnostic: mount in a Next route
|
|
52
|
+
* (`export const GET = createSyncRoute(sync, { secret })`), Hono, Bun, etc.
|
|
53
|
+
* If `secret` is set, requests must send it as `Authorization: Bearer <secret>`
|
|
54
|
+
* (or an `x-cron-secret` header). */
|
|
55
|
+
export declare function createSyncRoute(sync: BillingSync, opts?: {
|
|
56
|
+
secret?: string;
|
|
57
|
+
}): (request: Request) => Promise<Response>;
|
|
58
|
+
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkC,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9E,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAajE;wEACwE;AACxE,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5C,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,gBAAgB,CAAC;IAC1B,KAAK,EAAE,WAAW,CAAC;IACnB;kFAC8E;IAC9E,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE;QACN;8CACsC;QACtC,aAAa,CAAC,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QACpD;;mFAE2E;QAC3E,eAAe,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAChD,CAAC;CACH;AA6CD,MAAM,WAAW,WAAW;IAC1B,oEAAoE;IACpE,OAAO,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvD;;;;8DAI0D;IAC1D,KAAK,CAAC,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;KAAE,GAAG,MAAM,IAAI,CAAC;CACnF;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,GAAG,WAAW,CA4IvE;AAED;;;;sCAIsC;AACtC,wBAAgB,eAAe,CAC7B,IAAI,EAAE,WAAW,EACjB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAmBzC"}
|
package/dist/sync.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { pollStripeEvents, pollWorkOSEvents } from "./events.js";
|
|
2
|
+
import { creditTokens, getStripe } from "./billing.js";
|
|
3
|
+
import { includedTokens, planForPriceId } from "./plans.js";
|
|
4
|
+
const CURSOR_TABLE = "billing_sync_cursors";
|
|
5
|
+
// A CursorStore backed by a self-managed table via the app's query executor.
|
|
6
|
+
function queryCursorStore(query) {
|
|
7
|
+
let ensured = false;
|
|
8
|
+
const ensure = async () => {
|
|
9
|
+
if (ensured)
|
|
10
|
+
return;
|
|
11
|
+
await query(`CREATE TABLE IF NOT EXISTS ${CURSOR_TABLE} (source text PRIMARY KEY, cursor text, updated_at timestamptz NOT NULL DEFAULT now())`, []);
|
|
12
|
+
ensured = true;
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
async get(source) {
|
|
16
|
+
await ensure();
|
|
17
|
+
const r = await query(`SELECT cursor FROM ${CURSOR_TABLE} WHERE source = $1`, [source]);
|
|
18
|
+
return r.rows[0]?.cursor ?? null;
|
|
19
|
+
},
|
|
20
|
+
async set(source, cursor) {
|
|
21
|
+
if (cursor === null)
|
|
22
|
+
return;
|
|
23
|
+
await ensure();
|
|
24
|
+
await query(`INSERT INTO ${CURSOR_TABLE} (source, cursor) VALUES ($1, $2)
|
|
25
|
+
ON CONFLICT (source) DO UPDATE SET cursor = $2, updated_at = now()`, [source, cursor]);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const STRIPE_TYPES = [
|
|
30
|
+
"customer.subscription.created",
|
|
31
|
+
"customer.subscription.updated",
|
|
32
|
+
"customer.subscription.deleted",
|
|
33
|
+
"invoice.paid",
|
|
34
|
+
"invoice.payment_failed",
|
|
35
|
+
];
|
|
36
|
+
const WORKOS_EVENTS = [
|
|
37
|
+
"organization.updated",
|
|
38
|
+
"organization.deleted",
|
|
39
|
+
"user.updated",
|
|
40
|
+
"user.deleted",
|
|
41
|
+
];
|
|
42
|
+
export function createBillingSync(opts) {
|
|
43
|
+
const currency = opts.currency ?? "usd";
|
|
44
|
+
const cursor = opts.cursor ?? queryCursorStore(opts.query);
|
|
45
|
+
async function handleStripe(event) {
|
|
46
|
+
if (event.type.startsWith("customer.subscription.")) {
|
|
47
|
+
const sub = event.data.object;
|
|
48
|
+
const orgId = sub.metadata?.org_id;
|
|
49
|
+
if (!orgId)
|
|
50
|
+
return;
|
|
51
|
+
if (event.type === "customer.subscription.deleted") {
|
|
52
|
+
await opts.adapter.setSubscription(orgId, {
|
|
53
|
+
plan: null,
|
|
54
|
+
status: "canceled",
|
|
55
|
+
subscriptionId: null,
|
|
56
|
+
periodEnd: null,
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const priceId = sub.items?.data?.[0]?.price?.id;
|
|
61
|
+
const plan = priceId ? await planForPriceId(priceId) : null;
|
|
62
|
+
await opts.adapter.setSubscription(orgId, {
|
|
63
|
+
plan: plan ?? undefined,
|
|
64
|
+
status: sub.status,
|
|
65
|
+
subscriptionId: sub.id,
|
|
66
|
+
periodEnd: sub.current_period_end
|
|
67
|
+
? new Date(sub.current_period_end * 1000).toISOString()
|
|
68
|
+
: null,
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (event.type === "invoice.paid") {
|
|
73
|
+
const invoice = event.data.object;
|
|
74
|
+
if (invoice.billing_reason !== "subscription_create" && invoice.billing_reason !== "subscription_cycle")
|
|
75
|
+
return;
|
|
76
|
+
if (!invoice.subscription || !invoice.customer)
|
|
77
|
+
return;
|
|
78
|
+
const sub = await getStripe().subscriptions.retrieve(invoice.subscription);
|
|
79
|
+
const orgId = sub.metadata?.org_id;
|
|
80
|
+
if (!orgId)
|
|
81
|
+
return;
|
|
82
|
+
const priceId = sub.items?.data?.[0]?.price?.id;
|
|
83
|
+
const plan = priceId ? await planForPriceId(priceId) : null;
|
|
84
|
+
if (!plan)
|
|
85
|
+
return; // unknown price → no grant
|
|
86
|
+
const seats = await opts.adapter.memberCount(orgId);
|
|
87
|
+
const tokens = includedTokens(opts.plans, plan, seats);
|
|
88
|
+
if (tokens > 0) {
|
|
89
|
+
// Idempotency key on the invoice id: an overlapping/replayed poll grants
|
|
90
|
+
// the per-cycle tokens exactly once.
|
|
91
|
+
await creditTokens(invoice.customer, tokens, `Included tokens: ${plan} (${seats} seat${seats === 1 ? "" : "s"})`, currency, `credit:invoice:${invoice.id}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (event.type === "invoice.payment_failed") {
|
|
95
|
+
const invoice = event.data.object;
|
|
96
|
+
if (!invoice.subscription)
|
|
97
|
+
return; // one-off top-ups don't affect subscription state
|
|
98
|
+
const sub = (await getStripe().subscriptions.retrieve(invoice.subscription));
|
|
99
|
+
const orgId = sub.metadata?.org_id;
|
|
100
|
+
if (!orgId)
|
|
101
|
+
return;
|
|
102
|
+
await opts.adapter.setSubscription(orgId, {
|
|
103
|
+
status: "past_due",
|
|
104
|
+
subscriptionId: sub.id,
|
|
105
|
+
periodEnd: sub.current_period_end ? new Date(sub.current_period_end * 1000).toISOString() : null,
|
|
106
|
+
});
|
|
107
|
+
await opts.hooks?.onPaymentFailed?.(orgId);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function handleWorkOS(event) {
|
|
111
|
+
const data = event.data;
|
|
112
|
+
if (!data?.id)
|
|
113
|
+
return;
|
|
114
|
+
switch (event.event) {
|
|
115
|
+
case "organization.updated":
|
|
116
|
+
await opts.orgMirror?.syncResource(data.id, data);
|
|
117
|
+
break;
|
|
118
|
+
case "organization.deleted":
|
|
119
|
+
await opts.orgMirror?.remove(data.id);
|
|
120
|
+
break;
|
|
121
|
+
case "user.updated":
|
|
122
|
+
await opts.userMirror?.syncResource(data.id, data);
|
|
123
|
+
break;
|
|
124
|
+
case "user.deleted":
|
|
125
|
+
await opts.userMirror?.remove(data.id);
|
|
126
|
+
await opts.hooks?.onUserDeleted?.(data.id);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function runOnce() {
|
|
131
|
+
const s = await pollStripeEvents({
|
|
132
|
+
after: await cursor.get("stripe"),
|
|
133
|
+
types: STRIPE_TYPES,
|
|
134
|
+
onEvent: handleStripe,
|
|
135
|
+
});
|
|
136
|
+
await cursor.set("stripe", s.cursor);
|
|
137
|
+
const w = await pollWorkOSEvents({
|
|
138
|
+
after: await cursor.get("workos"),
|
|
139
|
+
events: WORKOS_EVENTS,
|
|
140
|
+
onEvent: handleWorkOS,
|
|
141
|
+
});
|
|
142
|
+
await cursor.set("workos", w.cursor);
|
|
143
|
+
return { stripe: s.count, workos: w.count };
|
|
144
|
+
}
|
|
145
|
+
function start(opts = {}) {
|
|
146
|
+
const intervalMs = opts.intervalMs ?? 60_000;
|
|
147
|
+
let running = false;
|
|
148
|
+
let stopped = false;
|
|
149
|
+
const tick = async () => {
|
|
150
|
+
if (running || stopped)
|
|
151
|
+
return; // never overlap
|
|
152
|
+
running = true;
|
|
153
|
+
try {
|
|
154
|
+
await runOnce();
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
if (opts.onError)
|
|
158
|
+
opts.onError(e);
|
|
159
|
+
else
|
|
160
|
+
console.error(`[billing-sync] ${e.message}`);
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
running = false;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
const handle = setInterval(tick, intervalMs);
|
|
167
|
+
// Don't keep the process alive just for the timer.
|
|
168
|
+
handle.unref?.();
|
|
169
|
+
void tick(); // run once immediately
|
|
170
|
+
return () => {
|
|
171
|
+
stopped = true;
|
|
172
|
+
clearInterval(handle);
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return { runOnce, start };
|
|
176
|
+
}
|
|
177
|
+
/** Web-standard (Request → Response) handler that runs one sync cycle — for a
|
|
178
|
+
* serverless cron trigger. Framework-agnostic: mount in a Next route
|
|
179
|
+
* (`export const GET = createSyncRoute(sync, { secret })`), Hono, Bun, etc.
|
|
180
|
+
* If `secret` is set, requests must send it as `Authorization: Bearer <secret>`
|
|
181
|
+
* (or an `x-cron-secret` header). */
|
|
182
|
+
export function createSyncRoute(sync, opts = {}) {
|
|
183
|
+
return async (request) => {
|
|
184
|
+
if (opts.secret) {
|
|
185
|
+
const auth = request.headers.get("authorization") ?? request.headers.get("x-cron-secret") ?? "";
|
|
186
|
+
if (auth !== opts.secret && auth !== `Bearer ${opts.secret}`) {
|
|
187
|
+
return new Response(JSON.stringify({ error: "unauthorized" }), {
|
|
188
|
+
status: 401,
|
|
189
|
+
headers: { "content-type": "application/json" },
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const result = await sync.runOnce();
|
|
195
|
+
return Response.json({ ok: true, ...result });
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
return Response.json({ ok: false, error: e.message }, { status: 500 });
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=sync.js.map
|
package/dist/sync.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { BillingAdapter, ResolvedConfig } from "../types.js";
|
|
3
|
+
export declare function registerBillingOnlyTools(server: McpServer, adapter: BillingAdapter, config: ResolvedConfig, toolCosts: Record<string, number>): void;
|
|
4
|
+
//# sourceMappingURL=billing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"billing.d.ts","sourceRoot":"","sources":["../../src/tools/billing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAmBlE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QAwJlC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { enforceAccess } from "../auth.js";
|
|
3
|
+
import { ensureStripeCustomer, getBillingCustomerId, getTokenBalance, getAutoReloadSettings, setAutoReloadSettings, createTokenCheckoutSession, createBillingPortalSession, listInvoices, stripeConfigured, } from "../billing.js";
|
|
4
|
+
const NO_STRIPE = {
|
|
5
|
+
isError: true,
|
|
6
|
+
content: [{ type: "text", text: "Billing is not configured (STRIPE_SECRET_KEY unset)." }],
|
|
7
|
+
};
|
|
8
|
+
export function registerBillingOnlyTools(server, adapter, config, toolCosts) {
|
|
9
|
+
// Resolve (or lazily create) the org's Stripe customer.
|
|
10
|
+
const customerId = async (orgId) => {
|
|
11
|
+
const existing = await getBillingCustomerId(adapter, orgId);
|
|
12
|
+
if (existing)
|
|
13
|
+
return existing;
|
|
14
|
+
return ensureStripeCustomer(adapter, orgId, undefined, config);
|
|
15
|
+
};
|
|
16
|
+
server.tool("get_token_balance", `Returns your current token balance, per-tool costs, and auto-reload settings.`, {}, async () => {
|
|
17
|
+
const auth = await enforceAccess(adapter);
|
|
18
|
+
if ("isError" in auth)
|
|
19
|
+
return auth;
|
|
20
|
+
if (!stripeConfigured())
|
|
21
|
+
return NO_STRIPE;
|
|
22
|
+
const cid = await customerId(auth.orgId);
|
|
23
|
+
const balance = await getTokenBalance(cid);
|
|
24
|
+
const autoReload = await getAutoReloadSettings(cid);
|
|
25
|
+
return {
|
|
26
|
+
content: [
|
|
27
|
+
{
|
|
28
|
+
type: "text",
|
|
29
|
+
text: JSON.stringify({ token_balance: balance, tool_costs: toolCosts, auto_reload: autoReload || { enabled: false } }, null, 2),
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
server.tool("buy_tokens", `Purchase tokens via Stripe Checkout. Returns a payment URL.
|
|
35
|
+
1 unit of currency = 100 tokens. Minimum 5, maximum 200,000. Your card is saved for auto-reload.`, {
|
|
36
|
+
amount: z.number().min(5).max(200000).describe("Amount in your currency to purchase (e.g. 10 = 1000 tokens)"),
|
|
37
|
+
}, async ({ amount }) => {
|
|
38
|
+
const auth = await enforceAccess(adapter);
|
|
39
|
+
if ("isError" in auth)
|
|
40
|
+
return auth;
|
|
41
|
+
if (!stripeConfigured())
|
|
42
|
+
return NO_STRIPE;
|
|
43
|
+
const cid = await customerId(auth.orgId);
|
|
44
|
+
const url = await createTokenCheckoutSession(cid, auth.orgId, amount, config);
|
|
45
|
+
const tokens = Math.round(amount * 100);
|
|
46
|
+
return {
|
|
47
|
+
content: [
|
|
48
|
+
{
|
|
49
|
+
type: "text",
|
|
50
|
+
text: JSON.stringify({
|
|
51
|
+
status: "checkout_created",
|
|
52
|
+
checkout_url: url,
|
|
53
|
+
amount,
|
|
54
|
+
tokens,
|
|
55
|
+
message: `Open this URL to purchase ${tokens} tokens.`,
|
|
56
|
+
}, null, 2),
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
server.tool("set_auto_reload", `Configure automatic token reload. When your balance drops to or below the threshold
|
|
62
|
+
after a tool call, your saved card is charged to bring the balance back to reload_to.
|
|
63
|
+
Requires a saved card (use buy_tokens first).`, {
|
|
64
|
+
enabled: z.boolean().describe("Enable or disable auto-reload"),
|
|
65
|
+
threshold: z.number().min(0).describe("Balance threshold that triggers reload"),
|
|
66
|
+
reload_to: z.number().min(1).describe("Target balance after reload"),
|
|
67
|
+
}, async ({ enabled, threshold, reload_to }) => {
|
|
68
|
+
const auth = await enforceAccess(adapter);
|
|
69
|
+
if ("isError" in auth)
|
|
70
|
+
return auth;
|
|
71
|
+
if (!stripeConfigured())
|
|
72
|
+
return NO_STRIPE;
|
|
73
|
+
if (reload_to <= threshold) {
|
|
74
|
+
return {
|
|
75
|
+
isError: true,
|
|
76
|
+
content: [{ type: "text", text: "reload_to must be greater than threshold." }],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const cid = await customerId(auth.orgId);
|
|
80
|
+
await setAutoReloadSettings(cid, threshold, reload_to, enabled);
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: JSON.stringify({
|
|
86
|
+
status: "ok",
|
|
87
|
+
auto_reload: { enabled, threshold, reload_to },
|
|
88
|
+
message: enabled
|
|
89
|
+
? `Auto-reload enabled: at <=${threshold} tokens, recharge to ${reload_to}.`
|
|
90
|
+
: "Auto-reload disabled.",
|
|
91
|
+
}, null, 2),
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
server.tool("get_billing_portal", `Get a Stripe billing-portal URL to manage your subscription (upgrade, downgrade, cancel),
|
|
97
|
+
update your payment method, and view invoices. Returns a short-lived link.`, { return_url: z.string().url().optional().describe("Where to send the user back to after the portal (defaults to the app)") }, async ({ return_url }) => {
|
|
98
|
+
const auth = await enforceAccess(adapter);
|
|
99
|
+
if ("isError" in auth)
|
|
100
|
+
return auth;
|
|
101
|
+
if (!stripeConfigured())
|
|
102
|
+
return NO_STRIPE;
|
|
103
|
+
const cid = await customerId(auth.orgId);
|
|
104
|
+
const url = await createBillingPortalSession(cid, return_url ?? config.baseUrl);
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: JSON.stringify({ status: "portal_created", portal_url: url, message: "Open this URL to manage billing." }, null, 2),
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
server.tool("list_invoices", `Returns your recent invoices with amounts, dates, and links to view/download PDFs.`, { limit: z.number().min(1).max(100).optional().default(10).describe("Number to return (default 10)") }, async ({ limit }) => {
|
|
115
|
+
const auth = await enforceAccess(adapter);
|
|
116
|
+
if ("isError" in auth)
|
|
117
|
+
return auth;
|
|
118
|
+
if (!stripeConfigured())
|
|
119
|
+
return NO_STRIPE;
|
|
120
|
+
const cid = await customerId(auth.orgId);
|
|
121
|
+
const invoices = await listInvoices(cid, limit);
|
|
122
|
+
return { content: [{ type: "text", text: JSON.stringify({ invoices }, null, 2) }] };
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=billing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"billing.js","sourceRoot":"","sources":["../../src/tools/billing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { BillingAdapter, ResolvedConfig } from "../types.js";
|
|
3
|
+
export declare function registerKeyTools(server: McpServer, adapter: BillingAdapter, config: ResolvedConfig): void;
|
|
4
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/tools/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAKlE,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,cAAc,QA4HvB"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { enforceAccess } from "../auth.js";
|
|
3
|
+
import { sendMagicAuth, verifyMagicAuth } from "../magic-auth.js";
|
|
4
|
+
import { ensureStripeCustomer, stripeConfigured } from "../billing.js";
|
|
5
|
+
export function registerKeyTools(server, adapter, config) {
|
|
6
|
+
server.tool("get_api_key", `Provisions or retrieves an API key for accessing protected tools.
|
|
7
|
+
Call with just an email to start authentication (sends a 6-digit code via email).
|
|
8
|
+
Then call again with the email and the code to complete authentication and receive your API key.
|
|
9
|
+
Add the key to your config as: "Authorization": "Bearer <key>"`, {
|
|
10
|
+
email: z.string().email().describe("Your email address"),
|
|
11
|
+
code: z.string().length(6).optional().describe("6-digit verification code from email"),
|
|
12
|
+
}, async ({ email, code }) => {
|
|
13
|
+
try {
|
|
14
|
+
if (!code) {
|
|
15
|
+
await sendMagicAuth(email);
|
|
16
|
+
return {
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: JSON.stringify({
|
|
21
|
+
status: "code_sent",
|
|
22
|
+
message: `A 6-digit verification code has been sent to ${email}. Call get_api_key again with your email and the code to get your API key.`,
|
|
23
|
+
}, null, 2),
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const user = await verifyMagicAuth(email, code);
|
|
29
|
+
const { orgId } = await adapter.ensureOrgForUser(user);
|
|
30
|
+
if (stripeConfigured()) {
|
|
31
|
+
await ensureStripeCustomer(adapter, orgId, email, config).catch(() => { });
|
|
32
|
+
}
|
|
33
|
+
const key = await adapter.mintApiKey(orgId, "API Key", user.id);
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: JSON.stringify({
|
|
39
|
+
status: "ok",
|
|
40
|
+
message: "API key created successfully. Add it as an Authorization header. This key is only shown once.",
|
|
41
|
+
api_key: key.value,
|
|
42
|
+
usage: { header: `Authorization: Bearer ${key.value}` },
|
|
43
|
+
}, null, 2),
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
return {
|
|
50
|
+
isError: true,
|
|
51
|
+
content: [
|
|
52
|
+
{
|
|
53
|
+
type: "text",
|
|
54
|
+
text: `Authentication error: ${e instanceof Error ? e.message : String(e)}`,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
server.tool("list_api_keys", `Lists the API keys for your workspace (obfuscated values, never the full key).
|
|
61
|
+
Useful for finding stale keys to revoke. Free.`, {}, async () => {
|
|
62
|
+
const auth = await enforceAccess(adapter);
|
|
63
|
+
if ("isError" in auth)
|
|
64
|
+
return auth;
|
|
65
|
+
try {
|
|
66
|
+
const keys = await adapter.listApiKeys(auth.orgId);
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: "text", text: JSON.stringify({ status: "ok", keys }, null, 2) }],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
return {
|
|
73
|
+
isError: true,
|
|
74
|
+
content: [
|
|
75
|
+
{ type: "text", text: `Failed to list keys: ${e instanceof Error ? e.message : String(e)}` },
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
server.tool("revoke_api_key", `Revokes (deletes) an API key by id. Use list_api_keys to find the id.
|
|
81
|
+
The revoked key stops working immediately. Free.`, { api_key_id: z.string().describe("The key id from list_api_keys") }, async ({ api_key_id }) => {
|
|
82
|
+
const auth = await enforceAccess(adapter);
|
|
83
|
+
if ("isError" in auth)
|
|
84
|
+
return auth;
|
|
85
|
+
try {
|
|
86
|
+
const revoked = await adapter.revokeApiKey(auth.orgId, api_key_id);
|
|
87
|
+
if (!revoked) {
|
|
88
|
+
return {
|
|
89
|
+
isError: true,
|
|
90
|
+
content: [{ type: "text", text: `Key ${api_key_id} not found in this workspace.` }],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
content: [{ type: "text", text: JSON.stringify({ status: "ok", revoked }, null, 2) }],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
return {
|
|
99
|
+
isError: true,
|
|
100
|
+
content: [
|
|
101
|
+
{ type: "text", text: `Failed to revoke key: ${e instanceof Error ? e.message : String(e)}` },
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/tools/keys.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { BillingAdapter, BillingConfig } from "../types.js";
|
|
3
|
+
import { type PlansConfig } from "../plans.js";
|
|
4
|
+
export declare function installInputLogging(server: McpServer): void;
|
|
5
|
+
export interface RegisterBillingToolsOptions {
|
|
6
|
+
adapter: BillingAdapter;
|
|
7
|
+
config: BillingConfig;
|
|
8
|
+
/** Per-tool token costs (for get_token_balance to echo). Usually from tools.json. */
|
|
9
|
+
toolCosts?: Record<string, number>;
|
|
10
|
+
/** Install the redacted [tool-input] logging wrapper. Default true. */
|
|
11
|
+
installLogging?: boolean;
|
|
12
|
+
/** Declarative plans. When set, a `list_plans` tool is registered and the
|
|
13
|
+
* Stripe products/prices are auto-provisioned (lazily, on first list). */
|
|
14
|
+
plans?: PlansConfig;
|
|
15
|
+
/** Default plan key (e.g. "hobby"). */
|
|
16
|
+
defaultPlan?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function registerBillingTools(server: McpServer, opts: RegisterBillingToolsOptions): void;
|
|
19
|
+
export declare const BILLING_TOOL_NAMES: readonly ["get_api_key", "list_api_keys", "revoke_api_key", "get_token_balance", "buy_tokens", "set_auto_reload", "get_billing_portal", "list_invoices"];
|
|
20
|
+
//# sourceMappingURL=register.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/tools/register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjE,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAyB5D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,QAkBpD;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;IACtB,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,uEAAuE;IACvE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;+EAC2E;IAC3E,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAID,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,2BAA2B,QAMxF;AAiCD,eAAO,MAAM,kBAAkB,0JASrB,CAAC"}
|