@volter/twin-calcom 0.1.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 (40) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +90 -0
  3. package/client/calcom-mirror.css +29 -0
  4. package/client/calcom-mirror.tsx +125 -0
  5. package/dist/client/calcom-mirror.bundle.js +321 -0
  6. package/dist/client/calcom-mirror.css +29 -0
  7. package/dist/client/calcom-mirror.d.ts +13 -0
  8. package/dist/client/calcom-mirror.js +61 -0
  9. package/dist/client/calcom-mirror.tsx +125 -0
  10. package/dist/src/calcom-budget.d.ts +52 -0
  11. package/dist/src/calcom-budget.js +145 -0
  12. package/dist/src/calcom-capabilities.d.ts +3 -0
  13. package/dist/src/calcom-capabilities.js +521 -0
  14. package/dist/src/calcom-conformance.d.ts +15 -0
  15. package/dist/src/calcom-conformance.js +86 -0
  16. package/dist/src/calcom-connector.d.ts +90 -0
  17. package/dist/src/calcom-connector.js +229 -0
  18. package/dist/src/calcom-mirror-ui.d.ts +23 -0
  19. package/dist/src/calcom-mirror-ui.js +100 -0
  20. package/dist/src/calcom-perform-harness.d.ts +5 -0
  21. package/dist/src/calcom-perform-harness.js +29 -0
  22. package/dist/src/calcom-server.d.ts +14 -0
  23. package/dist/src/calcom-server.js +31 -0
  24. package/dist/src/calcom-twin.d.ts +16 -0
  25. package/dist/src/calcom-twin.js +718 -0
  26. package/dist/src/cli.d.ts +2 -0
  27. package/dist/src/cli.js +31 -0
  28. package/dist/src/index.d.ts +11 -0
  29. package/dist/src/index.js +69 -0
  30. package/package.json +72 -0
  31. package/src/calcom-budget.ts +171 -0
  32. package/src/calcom-capabilities.ts +529 -0
  33. package/src/calcom-conformance.ts +92 -0
  34. package/src/calcom-connector.ts +258 -0
  35. package/src/calcom-mirror-ui.ts +110 -0
  36. package/src/calcom-perform-harness.ts +24 -0
  37. package/src/calcom-server.ts +39 -0
  38. package/src/calcom-twin.ts +675 -0
  39. package/src/cli.ts +29 -0
  40. package/src/index.ts +101 -0
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import { keepProcessAlive } from '@volter/world-core/lifecycle';
3
+ // world-calcom CLI: serve the Cal.com v2 API twin, the mirror UI, or run conformance.
4
+ import { hasFlag, optionValue } from '@volter/world-core/args';
5
+ import { createCalcomTwinServer } from "./calcom-server.js";
6
+ import { createCalcomMirrorServer } from "./calcom-mirror-ui.js";
7
+ const [cmd, ...rest] = process.argv.slice(2);
8
+ const port = Number(optionValue(rest, '--port', '0')) || undefined;
9
+ const root = optionValue(rest, '--root') || undefined;
10
+ const readOnly = hasFlag(rest, '--read-only'); // a twin accepts writes unless started read-only
11
+ if (cmd === 'serve') {
12
+ const s = await createCalcomTwinServer({ readOnly, ...(root ? { root } : {}), ...(port ? { port } : {}) });
13
+ process.stdout.write(`calcom twin (v2 API)${readOnly ? ' [read-only]' : ''} at http://127.0.0.1:${s.port}\n`);
14
+ await keepProcessAlive();
15
+ }
16
+ else if (cmd === 'mirror') {
17
+ const s = await createCalcomMirrorServer({ ...(root ? { root } : {}), ...(port ? { port } : {}) });
18
+ process.stdout.write(`calcom mirror UI (dashboard) at http://127.0.0.1:${s.port}\n`);
19
+ await keepProcessAlive();
20
+ }
21
+ else if (cmd === 'conformance') {
22
+ // dev-only; lazy so the bin runs without @volter/world-tooling in the runtime graph
23
+ const { checkCalcomConformance } = await import("./calcom-conformance.js");
24
+ const report = checkCalcomConformance({ ...(root ? { root } : {}) });
25
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
26
+ if (!report.ok)
27
+ process.exitCode = 1;
28
+ }
29
+ else {
30
+ process.stdout.write('Usage: world-calcom serve|mirror|conformance [--port N] [--root DIR] [--read-only]\n');
31
+ }
@@ -0,0 +1,11 @@
1
+ export { handleCalcomTwinRequest, TWIN_API_VERSION, RESOURCE_TYPES } from './calcom-twin.js';
2
+ export type { CalcomRequest, CalcomResponse } from './calcom-twin.js';
3
+ export { createCalcomTwinFetch, createCalcomTwinServer, type CalcomTwinFetchOptions } from './calcom-server.js';
4
+ export { calcomRequestForAction, liveCalcomExecute, mapBooking, mapEventType, pullCalcomAll, pullCalcomBookings, pullCalcomEventTypes, syncCalcomFromReal, } from './calcom-connector.js';
5
+ export type { CalcomExecute, LiveCalcomOptions } from './calcom-connector.js';
6
+ export { CALCOM_BUDGET_CEILING, CALCOM_BUDGET_MAX_RETRY_AFTER_S, CALCOM_BUDGET_WINDOW_MS, CALCOM_CALL_WEIGHTS, CALCOM_RATE_BUDGET, CalcomBudget, CalcomBudgetError, calcomBudgetPath, calcomCallWeight, } from './calcom-budget.js';
7
+ export type { CalcomBudgetErrorKind, CalcomBudgetOptions, CalcomBudgetReservation, CalcomBudgetSnapshot } from './calcom-budget.js';
8
+ export { attendeeLabel, bookingTone, buildCalcomMirrorClient, calcomMirrorHtml, createCalcomMirrorServer, formatWindow, } from './calcom-mirror-ui.js';
9
+ export type { CalcomRow } from './calcom-mirror-ui.js';
10
+ import { type TwinPack } from '@volter/world-core';
11
+ export declare const pack: TwinPack;
@@ -0,0 +1,69 @@
1
+ // @volter/twin-calcom — the Cal.com twin (one vendor, one package), built on the shared
2
+ // @volter/world-core kernel. REST (v2) transport, spec-faithful `{ status, data }` envelope,
3
+ // stateful bookings/event-types/schedules/teams/webhooks, and a React dashboard mirror.
4
+ // Auth is a faked-locally Bearer API key — there is NO JWT / JWKS / webhook signing
5
+ // (Cal.com's API auth is a plain bearer `cal_…` key). Conformance tooling lives in
6
+ // @volter/world-tooling (a dev dependency) — not shipped in the runtime API.
7
+ export { handleCalcomTwinRequest, TWIN_API_VERSION, RESOURCE_TYPES } from "./calcom-twin.js";
8
+ export { createCalcomTwinFetch, createCalcomTwinServer } from "./calcom-server.js";
9
+ export { calcomRequestForAction, liveCalcomExecute, mapBooking, mapEventType, pullCalcomAll, pullCalcomBookings, pullCalcomEventTypes, syncCalcomFromReal, } from "./calcom-connector.js";
10
+ // The client-side rate budget — the fail-closed backstop `liveCalcomExecute` routes every live
11
+ // request through. The MECHANISM is the kernel's shared, vendor-agnostic `RateBudget`; what lives
12
+ // here is Cal.com's DECLARATION (window/ceiling/per-endpoint weights) plus the vendor-bound
13
+ // bindings. Exported so an operator can inspect spend (`snapshot`) and so a caller can catch
14
+ // `CalcomBudgetError` by type; there is deliberately no export that disables the guard.
15
+ export { CALCOM_BUDGET_CEILING, CALCOM_BUDGET_MAX_RETRY_AFTER_S, CALCOM_BUDGET_WINDOW_MS, CALCOM_CALL_WEIGHTS, CALCOM_RATE_BUDGET, CalcomBudget, CalcomBudgetError, calcomBudgetPath, calcomCallWeight, } from "./calcom-budget.js";
16
+ export { attendeeLabel, bookingTone, buildCalcomMirrorClient, calcomMirrorHtml, createCalcomMirrorServer, formatWindow, } from "./calcom-mirror-ui.js";
17
+ // Registry descriptor: the pack self-describes so tooling can discover it.
18
+ import { registerPack } from '@volter/world-core';
19
+ import { CALCOM_RATE_BUDGET as RATE_BUDGET } from "./calcom-budget.js";
20
+ import { performCalcomAction, syncCalcomFromRemote } from "./calcom-connector.js";
21
+ export const pack = {
22
+ vendor: 'calcom',
23
+ // PROTOCOL 2 (docs/contributing/architecture.md#protocol-2-the-pack-is-a-plugin): the pack is a plugin — its wire, its tree, and its half of the real
24
+ // state system: perform one entry against Cal.com v2, refresh the root from it. Moved 2026-09-08.
25
+ protocol: '2',
26
+ refresh: { every: '5m', webhook: true, onDemand: { atMost: '30s' } },
27
+ stateSystem: { perform: performCalcomAction, refresh: syncCalcomFromRemote },
28
+ // the round trip: an event type, then a booking of it — the two things a scheduling account is made of
29
+ // the round trip is ONE write — an event type. A booking would have to name an event type, and the only
30
+ // one it could name statically is the twin's implicit default (7001), which is not the subject the kernel
31
+ // adopts; the reference trip below is where a booking gets made, against the event type this created.
32
+ roundTrip: { method: 'POST', path: '/v2/event-types', body: { title: 'Round trip', slug: 'round-trip', lengthInMinutes: 30 }, headers: { authorization: 'Bearer round-trip', 'cal-api-version': '2024-06-14' } },
33
+ // rule 5: a booking names the event type it is a booking OF; when the head adopts Cal.com's id for the
34
+ // event type, the kernel resolves the booking's field first
35
+ referenceTrip: { method: 'POST', path: '/v2/bookings', body: { start: '2026-09-09T11:00:00.000Z', eventTypeId: '{{evtId}}', attendee: { name: 'Round Trip', email: 'round@trip.test', timeZone: 'UTC' } }, headers: { authorization: 'Bearer round-trip', 'cal-api-version': '2024-08-13' } },
36
+ references: [{
37
+ type: 'booking', to: 'event_type',
38
+ key: (f) => (f.eventTypeId === undefined || f.eventTypeId === null ? undefined : `evt_${String(f.eventTypeId)}`),
39
+ adopt: (_f, vendorId) => ({ eventTypeId: vendorId.replace(/^evt_/, '') }),
40
+ }],
41
+ parityOrigin: 'http://twin',
42
+ shapeParity: 'held',
43
+ // The SAME object calcom-budget.ts declares at module load — one source of truth, so registering
44
+ // the pack and importing the connector can never arm two different ceilings.
45
+ rateBudget: RATE_BUDGET,
46
+ transport: 'rest',
47
+ archetype: 'crud',
48
+ bin: 'world-calcom',
49
+ resources: ['booking', 'event_type', 'schedule', 'team', 'membership', 'webhook', 'slot'],
50
+ specSource: 'cal.com/docs/api-reference/v2 (hand-authored from the published v2 reference)',
51
+ description: 'Cal.com v2 REST twin — { status, data } envelope, stateful bookings/event-types/schedules/teams/webhooks, dashboard mirror.',
52
+ // Adoption + interception, moved off the central maps unchanged (descriptor-first back-migration, adding-a-twin.md §3,
53
+ // 2026-08-31). Both credential-env spellings the vendor uses — CALCOM_* and the
54
+ // shorter CAL_* — stem here; there is no npm client to claim (Cal.com integrations call the v2
55
+ // REST surface directly), so `sdks` is deliberately absent rather than invented.
56
+ adoption: {
57
+ // Cal.com ships no Python SDK and no community client exists. The `Calcom` distribution on
58
+ // PyPI is an unrelated mathematics package - claiming it would map a maths dependency onto a
59
+ // scheduling twin.
60
+ pypi: [],
61
+ envStems: ['CALCOM', 'CAL'],
62
+ },
63
+ hosts: [{ host: 'api.cal.com' }],
64
+ // A scheduling client calls api.cal.com/v2/… — the dev proxy forwards '/v2/' to the twin
65
+ // and strips the absolute host so calls come back same-origin.
66
+ browserRouting: { apiPathPrefix: '/v2/', loaderHost: 'https://api.cal.com' },
67
+ };
68
+ // registered at import: the kernel learns the pack's state system (protocol 2)
69
+ registerPack(pack);
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@volter/twin-calcom",
3
+ "version": "0.1.0",
4
+ "description": "Local Cal.com twin — a faithful, stateful local Cal.com v2 API your scheduling integration talks to unmodified. Mirror, simulate, and fork. Built on @volter/world-core.",
5
+ "keywords": [
6
+ "twin",
7
+ "local",
8
+ "mock",
9
+ "mirror",
10
+ "simulator",
11
+ "fixtures",
12
+ "testing",
13
+ "api",
14
+ "scheduling",
15
+ "calcom",
16
+ "cal.com",
17
+ "bookings"
18
+ ],
19
+ "author": "Volter (https://github.com/volter-ai)",
20
+ "license": "Apache-2.0",
21
+ "files": [
22
+ "src",
23
+ "client",
24
+ "README.md",
25
+ "LICENSE",
26
+ "!**/*.test.ts",
27
+ "!**/*.test.tsx",
28
+ "dist"
29
+ ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/volter-ai/twin.git",
33
+ "directory": "packages/twin/calcom"
34
+ },
35
+ "homepage": "https://github.com/volter-ai/twin/tree/main/packages/twin/calcom#readme",
36
+ "type": "module",
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/src/index.d.ts",
40
+ "default": "./dist/src/index.js"
41
+ }
42
+ },
43
+ "bin": {
44
+ "world-calcom": "dist/src/cli.js"
45
+ },
46
+ "scripts": {
47
+ "test": "bun test src/*.test.ts",
48
+ "typecheck": "tsc --noEmit",
49
+ "build": "node ../../../scripts/publish/build.mjs",
50
+ "prepack": "node ../../../scripts/publish/prepare-publish.mjs prepack",
51
+ "postpack": "node ../../../scripts/publish/prepare-publish.mjs postpack"
52
+ },
53
+ "dependencies": {
54
+ "react": "^19.2.7",
55
+ "react-dom": "^19.2.7"
56
+ },
57
+ "peerDependencies": {
58
+ "@volter/world-core": "2.0.0"
59
+ },
60
+ "devDependencies": {
61
+ "@volter/world-core": "2.0.0",
62
+ "@volter/world-tooling": "0.1.0",
63
+ "@types/bun": "^1.2.20",
64
+ "@types/node": "^24.0.0",
65
+ "@types/react": "^19.2.17",
66
+ "@types/react-dom": "^19.2.3",
67
+ "typescript": "^5.9.0"
68
+ },
69
+ "engines": {
70
+ "node": ">=22.3"
71
+ }
72
+ }
@@ -0,0 +1,171 @@
1
+ // Cal.com's CLIENT-SIDE RATE BUDGET — the pack's DECLARATION (the numbers) plus the thin typed
2
+ // bindings `liveCalcomExecute` uses. The MECHANISM — the durable token-keyed ledger, the rolling
3
+ // window, reserve-under-lock, the `Retry-After`/429 cooldown, fail-CLOSED on a corrupt ledger —
4
+ // lives ONCE in the vendor-agnostic kernel (`@volter/world-core` → `rateBudget.ts`). Read that module's
5
+ // header for the full rationale AND for the honest list of what the guard does not guarantee (an
6
+ // injected clock or ledger path still defeats it — it guards carelessness, not malice).
7
+ //
8
+ // ── WHY THIS EXISTS ─────────────────────────────────────────────────────────────────────────
9
+ // A real ~4.5-DAY vendor lockout (Figma, 2026-07-25) happened because raw API calls were made
10
+ // outside the pack's connector — no cache, no batching, no ceiling. Discipline only binds the code
11
+ // that follows it; a BUDGET binds the code that does not.
12
+ //
13
+ // ── HOW THE CEILING WAS CHOSEN ──────────────────────────────────────────────────────────────
14
+ // Cal.com DOES publish a scalar limit, so this models the real thing rather than guessing. From
15
+ // https://cal.com/docs/api-reference/v2/introduction (read 2026-07-26): API-key authentication is
16
+ // limited to 120 requests per minute (raisable on request); the same 120/minute is the default for
17
+ // unauthenticated traffic.
18
+ //
19
+ // The `X-RateLimit-Limit` / `-Remaining` / `-Reset` response headers this guard reads are NOT on that
20
+ // page — they are the conventional spelling, matched structurally by the kernel, and are read IF
21
+ // PRESENT and never assumed. (§9 corrected an earlier line here that attributed them to the cited
22
+ // page, 2026-07-26. Do not turn an observation into a citation.)
23
+ //
24
+ // The ceiling is 60 weighted units per 60s — 60 calls a minute at the default weight, HALF the
25
+ // documented 120/minute, so the 60-second AVERAGE stays under the vendor's own even before any
26
+ // weighting. It is more permissive than the kernel's undeclared fallback (30 calls/min) precisely
27
+ // because 120/min is documented; without that number it would not be.
28
+ //
29
+ // It bounds the average; it does NOT pace (the kernel refuses, it never sleeps — see its header).
30
+ // Cal.com's window is also a minute, so the shapes line up well here, but an intra-second burst can
31
+ // still reach the vendor first; the backstop for that is the cooldown armed from the 429 /
32
+ // `X-RateLimit-Remaining: 0`.
33
+ //
34
+ // ── HOW THE WEIGHTS WERE CHOSEN (and what is a judgement call) ───────────────────────────────
35
+ // Cal.com counts REQUESTS, so weight 1 is the faithful price and that is what reads get. Two
36
+ // deliberate exceptions, both judgement calls rather than published costs:
37
+ // • `POST /bookings` costs 3. A booking is not a database row — it sends invitation e-mail,
38
+ // writes to the organiser's and attendees' real connected calendars, and fires webhooks. A
39
+ // runaway create loop is the failure mode with a HUMAN blast radius, not just a throttle.
40
+ // • `GET /slots` costs 2. Availability is computed across every connected calendar for a date
41
+ // range, so it is the expensive read, and it is the one a naive UI polls in a loop.
42
+ import {
43
+ declareRateBudget,
44
+ rateBudgetPath,
45
+ rateBudgetWeight,
46
+ RateBudget,
47
+ type RateBudgetDeclaration,
48
+ type RateBudgetOptions,
49
+ type RateBudgetReservation,
50
+ type RateBudgetSnapshot,
51
+ } from '@volter/world-core';
52
+
53
+ const VENDOR = 'calcom';
54
+
55
+ /** Rolling window, in ms. Spend older than this is pruned. */
56
+ export const CALCOM_BUDGET_WINDOW_MS = 60_000;
57
+
58
+ /**
59
+ * Weighted units allowed inside one window. 60/60s = 60 calls a minute at the default weight —
60
+ * half Cal.com's documented 120 requests/minute per API key.
61
+ */
62
+ export const CALCOM_BUDGET_CEILING = 60;
63
+
64
+ /** Seconds. A `Retry-After` above this means the key is throttled hard — fail loudly, don't sleep. */
65
+ export const CALCOM_BUDGET_MAX_RETRY_AFTER_S = 300;
66
+
67
+ /** Per-call cost, keyed by `"<METHOD> <path>"`. See the header for what is documented vs. judged. */
68
+ export const CALCOM_CALL_WEIGHTS = {
69
+ /** `POST /bookings` — sends e-mail, writes real calendars, fires webhooks. Human blast radius. */
70
+ book: 3,
71
+ /** `GET /slots` — availability computed across every connected calendar. The expensive read. */
72
+ slots: 2,
73
+ /** Everything else: booking/event-type reads, updates, cancellations, schedules, me. */
74
+ other: 1,
75
+ } as const;
76
+
77
+ /** THE PACK'S DECLARATION — pure data, the only Cal.com-specific thing in the whole budget. */
78
+ export const CALCOM_RATE_BUDGET: RateBudgetDeclaration = {
79
+ windowMs: CALCOM_BUDGET_WINDOW_MS,
80
+ ceiling: CALCOM_BUDGET_CEILING,
81
+ defaultWeight: CALCOM_CALL_WEIGHTS.other,
82
+ maxRetryAfterSeconds: CALCOM_BUDGET_MAX_RETRY_AFTER_S,
83
+ rules: [
84
+ { match: '^POST /bookings$', weight: CALCOM_CALL_WEIGHTS.book },
85
+ { match: '^GET /slots', weight: CALCOM_CALL_WEIGHTS.slots },
86
+ ],
87
+ reason:
88
+ 'Cal.com documents 120 requests/minute per API key (cal.com/docs/api-reference/v2/introduction, ' +
89
+ 'read 2026-07-26; the same 120/min is the unauthenticated default, and it is raisable on ' +
90
+ 'request). That page documents no response headers; the X-RateLimit-* names the guard reads are ' +
91
+ 'the conventional spelling, read if present and never assumed. 60 weighted units / 60s is 60 ' +
92
+ 'calls a minute — HALF the documented 120, so the 60s average stays ' +
93
+ "under the vendor's own. It is deliberately more permissive than the kernel's undeclared " +
94
+ 'fallback (30 calls/min) BECAUSE 120/min is documented; without that number it would not be. ' +
95
+ 'POST /bookings costs 3 (it sends e-mail, writes real connected calendars and fires webhooks — ' +
96
+ 'a human blast radius, not just a throttle) and GET /slots costs 2 (availability computed across ' +
97
+ 'every connected calendar, and the endpoint a naive UI polls in a loop) — judgement calls, not ' +
98
+ 'published costs. The window bounds the 60s AVERAGE and does not pace; the 429 / ' +
99
+ '`X-RateLimit-Remaining: 0` cooldown is the backstop for a sub-second burst.',
100
+ };
101
+
102
+ // Declared at module load, so merely importing this module (which `calcom-connector.ts` does) is
103
+ // enough to arm the real ceiling. `RateBudget` reads its policy live precisely so this declaration
104
+ // takes effect the moment it lands, and constructing through the subclass below (which imports this
105
+ // module) is what makes the ordering a non-issue in practice.
106
+ declareRateBudget(VENDOR, CALCOM_RATE_BUDGET);
107
+
108
+ /**
109
+ * Price one call. The key is `"<METHOD> <path>"` (the v2 path as the connector states it, without
110
+ * the `/v2` prefix the live executor adds) with the query string split off, so a rule can price by
111
+ * method without the kernel knowing anything about Cal.com. An unclassified endpoint still costs
112
+ * `defaultWeight` — nothing is ever free.
113
+ */
114
+ export function calcomCallWeight(method: string, path: string): number {
115
+ const { bare, query } = splitQuery(path);
116
+ // UPPER-CASE the method: `fetch` normalizes a known lowercase method before sending, so
117
+ // `execute('post', …)` really does issue a POST and must be priced as one.
118
+ return rateBudgetWeight(VENDOR, `${String(method).toUpperCase()} ${bare}`, query);
119
+ }
120
+
121
+ /**
122
+ * `/v1/x?a=1` -> `{ bare: '/v1/x', query: { a: '1' } }`. Rules match the path; `whenQuery*` the query.
123
+ *
124
+ * NORMALIZED, because the anchored rules are otherwise trivially evaded (§9 finding, 2026-07-26):
125
+ * `fetch` upper-cases a known method before sending, so `execute('post', …)` issues a real WRITE
126
+ * that a `^POST ` rule would price as a read; and a trailing slash makes a path miss a `$` anchor
127
+ * while most routers treat it as the same endpoint. Both are input variations, not attacks, and
128
+ * either one silently voids the "expensive endpoints are priced up" claim the ceiling rests on.
129
+ */
130
+ function splitQuery(path: string): { bare: string; query: Record<string, string> } {
131
+ const at = path.indexOf('?');
132
+ const query: Record<string, string> = {};
133
+ if (at !== -1) for (const [k, v] of new URLSearchParams(path.slice(at + 1))) query[k] = v;
134
+ const raw = at === -1 ? path : path.slice(0, at);
135
+ // Collapse a trailing slash, but never turn the root path into the empty string.
136
+ const bare = raw.length > 1 && raw.endsWith('/') ? raw.replace(/\/+$/, '') : raw;
137
+ return { bare, query };
138
+ }
139
+
140
+ /** Where Cal.com's ledger lives. Token-keyed and cwd-independent by default (the limit is per API
141
+ * key, so a cwd-scoped ledger would hand the same key a fresh allowance in every checkout,
142
+ * worktree and CI matrix leg); pass `root` to opt into world-scoped accounting. */
143
+ export function calcomBudgetPath(opts: { root?: string; token?: string } | string = {}): string {
144
+ const o = typeof opts === 'string' ? { root: opts } : opts;
145
+ // VENDOR spread LAST: a loosely-typed `{ vendor: 'other', … }` slipping through (TypeScript's
146
+ // excess-property check only catches object literals) must not redirect this pack's ledger to
147
+ // another vendor's file.
148
+ return rateBudgetPath({ ...o, vendor: VENDOR });
149
+ }
150
+
151
+ /** Construction options for Cal.com's budget. The vendor is fixed; everything else may only TIGHTEN. */
152
+ export type CalcomBudgetOptions = Omit<RateBudgetOptions, 'vendor'>;
153
+
154
+ /**
155
+ * Cal.com's budget — the shared kernel guard bound to this vendor's declaration. A real subclass,
156
+ * not an alias, so `budget instanceof CalcomBudget` in `liveCalcomExecute` means "a budget that
157
+ * accounts against CAL.COM's ledger under CAL.COM's ceiling": another vendor's `RateBudget` (with
158
+ * its own, possibly larger, ceiling) is NOT assignable there.
159
+ */
160
+ export class CalcomBudget extends RateBudget {
161
+ constructor(opts: CalcomBudgetOptions = {}) {
162
+ super({ ...opts, vendor: VENDOR });
163
+ }
164
+ }
165
+
166
+ /** The typed refusal. One error class shared with every other vendor's budget; `err.vendor` says
167
+ * which one refused, and `err.kind` says why. */
168
+ export { RateBudgetError as CalcomBudgetError } from '@volter/world-core';
169
+ export type { RateBudgetErrorKind as CalcomBudgetErrorKind } from '@volter/world-core';
170
+ export type CalcomBudgetReservation = RateBudgetReservation;
171
+ export type CalcomBudgetSnapshot = RateBudgetSnapshot;