@pithy-sh/cloudflare 0.1.2 → 0.1.4

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 (139) hide show
  1. package/dist/ai/aiManager.d.ts +101 -0
  2. package/dist/ai/aiManager.d.ts.map +1 -0
  3. package/dist/ai/aiManager.js +150 -0
  4. package/dist/ai/vectorizeManager.d.ts +69 -0
  5. package/dist/ai/vectorizeManager.d.ts.map +1 -0
  6. package/dist/ai/vectorizeManager.js +103 -0
  7. package/dist/ai/vectorizeProvisioner.d.ts +125 -0
  8. package/dist/ai/vectorizeProvisioner.d.ts.map +1 -0
  9. package/dist/ai/vectorizeProvisioner.js +197 -0
  10. package/dist/client/accounts.d.ts +50 -0
  11. package/dist/client/accounts.d.ts.map +1 -0
  12. package/dist/client/accounts.js +57 -0
  13. package/dist/client/clients.d.ts +124 -0
  14. package/dist/client/clients.d.ts.map +1 -0
  15. package/dist/client/clients.js +218 -0
  16. package/dist/client/errors.d.ts +75 -0
  17. package/dist/client/errors.d.ts.map +1 -0
  18. package/dist/client/errors.js +100 -0
  19. package/dist/client/manager.d.ts +47 -0
  20. package/dist/client/manager.d.ts.map +1 -0
  21. package/dist/client/manager.js +53 -0
  22. package/dist/d1/d1Manager.d.ts +76 -0
  23. package/dist/d1/d1Manager.d.ts.map +1 -0
  24. package/dist/d1/d1Manager.js +130 -0
  25. package/dist/d1/d1PreparedStatement.d.ts +26 -0
  26. package/dist/d1/d1PreparedStatement.d.ts.map +1 -0
  27. package/dist/d1/d1PreparedStatement.js +93 -0
  28. package/dist/d1/d1Provisioner.d.ts +30 -0
  29. package/dist/d1/d1Provisioner.d.ts.map +1 -0
  30. package/dist/d1/d1Provisioner.js +69 -0
  31. package/dist/email/emailRoutingManager.d.ts +38 -0
  32. package/dist/email/emailRoutingManager.d.ts.map +1 -0
  33. package/dist/email/emailRoutingManager.js +126 -0
  34. package/dist/email/emailSendManager.d.ts +47 -0
  35. package/dist/email/emailSendManager.d.ts.map +1 -0
  36. package/dist/email/emailSendManager.js +54 -0
  37. package/dist/env/devVars.d.ts +67 -0
  38. package/dist/env/devVars.d.ts.map +1 -0
  39. package/dist/env/devVars.js +86 -0
  40. package/dist/hostnames/customHostnamesManager.d.ts +53 -0
  41. package/dist/hostnames/customHostnamesManager.d.ts.map +1 -0
  42. package/dist/hostnames/customHostnamesManager.js +86 -0
  43. package/dist/kv/kvManager.d.ts +78 -0
  44. package/dist/kv/kvManager.d.ts.map +1 -0
  45. package/dist/kv/kvManager.js +137 -0
  46. package/dist/kv/kvProvisioner.d.ts +30 -0
  47. package/dist/kv/kvProvisioner.d.ts.map +1 -0
  48. package/dist/kv/kvProvisioner.js +73 -0
  49. package/dist/media/assetSeeder.d.ts +33 -0
  50. package/dist/media/assetSeeder.d.ts.map +1 -0
  51. package/dist/media/assetSeeder.js +72 -0
  52. package/dist/media/imageManager.d.ts +36 -0
  53. package/dist/media/imageManager.d.ts.map +1 -0
  54. package/dist/media/imageManager.js +105 -0
  55. package/dist/media/ownership.d.ts +42 -0
  56. package/dist/media/ownership.d.ts.map +1 -0
  57. package/dist/media/ownership.js +48 -0
  58. package/dist/media/streamManager.d.ts +85 -0
  59. package/dist/media/streamManager.d.ts.map +1 -0
  60. package/dist/media/streamManager.js +147 -0
  61. package/dist/queue/queueManager.d.ts +86 -0
  62. package/dist/queue/queueManager.d.ts.map +1 -0
  63. package/dist/queue/queueManager.js +116 -0
  64. package/dist/r2/r2Credentials.d.ts +14 -0
  65. package/dist/r2/r2Credentials.d.ts.map +1 -0
  66. package/dist/r2/r2Credentials.js +15 -0
  67. package/dist/r2/r2Manager.d.ts +228 -0
  68. package/dist/r2/r2Manager.d.ts.map +1 -0
  69. package/dist/r2/r2Manager.js +407 -0
  70. package/dist/r2/r2Provisioner.d.ts +41 -0
  71. package/dist/r2/r2Provisioner.d.ts.map +1 -0
  72. package/dist/r2/r2Provisioner.js +88 -0
  73. package/dist/secrets/secretsStoreManager.d.ts +84 -0
  74. package/dist/secrets/secretsStoreManager.d.ts.map +1 -0
  75. package/dist/secrets/secretsStoreManager.js +149 -0
  76. package/dist/secrets/secretsStores.d.ts +49 -0
  77. package/dist/secrets/secretsStores.d.ts.map +1 -0
  78. package/dist/secrets/secretsStores.js +67 -0
  79. package/dist/test-utils/emailRoutingRules.d.ts +41 -0
  80. package/dist/test-utils/emailRoutingRules.d.ts.map +1 -0
  81. package/dist/test-utils/emailRoutingRules.js +91 -0
  82. package/dist/test-utils/fixtureReportSetup.d.ts +27 -0
  83. package/dist/test-utils/fixtureReportSetup.d.ts.map +1 -0
  84. package/dist/test-utils/fixtureReportSetup.js +32 -0
  85. package/dist/test-utils/fixtures.d.ts +226 -0
  86. package/dist/test-utils/fixtures.d.ts.map +1 -0
  87. package/dist/test-utils/fixtures.js +279 -0
  88. package/dist/test-utils/harness.d.ts +194 -0
  89. package/dist/test-utils/harness.d.ts.map +1 -0
  90. package/dist/test-utils/harness.js +353 -0
  91. package/dist/test-utils/inboundRecorder.d.ts +93 -0
  92. package/dist/test-utils/inboundRecorder.d.ts.map +1 -0
  93. package/dist/test-utils/inboundRecorder.js +143 -0
  94. package/dist/test-utils/integrationSetup.d.ts +24 -0
  95. package/dist/test-utils/integrationSetup.d.ts.map +1 -0
  96. package/dist/test-utils/integrationSetup.js +39 -0
  97. package/dist/test-utils/reap.d.ts +114 -0
  98. package/dist/test-utils/reap.d.ts.map +1 -0
  99. package/dist/test-utils/reap.js +187 -0
  100. package/dist/tokens/accountTokensManager.d.ts +159 -0
  101. package/dist/tokens/accountTokensManager.d.ts.map +1 -0
  102. package/dist/tokens/accountTokensManager.js +282 -0
  103. package/dist/tokens/permissions.d.ts +38 -0
  104. package/dist/tokens/permissions.d.ts.map +1 -0
  105. package/dist/tokens/permissions.js +52 -0
  106. package/dist/tokens/profiles.d.ts +104 -0
  107. package/dist/tokens/profiles.d.ts.map +1 -0
  108. package/dist/tokens/profiles.js +139 -0
  109. package/dist/turnstile/turnstileManager.d.ts +77 -0
  110. package/dist/turnstile/turnstileManager.d.ts.map +1 -0
  111. package/dist/turnstile/turnstileManager.js +140 -0
  112. package/dist/user/userManager.d.ts +38 -0
  113. package/dist/user/userManager.d.ts.map +1 -0
  114. package/dist/user/userManager.js +60 -0
  115. package/dist/workers/buildsManager.d.ts +118 -0
  116. package/dist/workers/buildsManager.d.ts.map +1 -0
  117. package/dist/workers/buildsManager.js +239 -0
  118. package/dist/workers/buildsTypes.d.ts +104 -0
  119. package/dist/workers/buildsTypes.d.ts.map +1 -0
  120. package/dist/workers/buildsTypes.js +89 -0
  121. package/dist/workers/workersBuildEvent.d.ts +66 -0
  122. package/dist/workers/workersBuildEvent.d.ts.map +1 -0
  123. package/dist/workers/workersBuildEvent.js +49 -0
  124. package/dist/workers/workersManager.d.ts +138 -0
  125. package/dist/workers/workersManager.d.ts.map +1 -0
  126. package/dist/workers/workersManager.js +314 -0
  127. package/dist/workers/workersProvisioner.d.ts +82 -0
  128. package/dist/workers/workersProvisioner.d.ts.map +1 -0
  129. package/dist/workers/workersProvisioner.js +108 -0
  130. package/dist/workflows/stepFailure.d.ts +182 -0
  131. package/dist/workflows/stepFailure.d.ts.map +1 -0
  132. package/dist/workflows/stepFailure.js +229 -0
  133. package/dist/workflows/workflowsClient.d.ts +89 -0
  134. package/dist/workflows/workflowsClient.d.ts.map +1 -0
  135. package/dist/workflows/workflowsClient.js +158 -0
  136. package/dist/zones/zonesManager.d.ts +52 -0
  137. package/dist/zones/zonesManager.d.ts.map +1 -0
  138. package/dist/zones/zonesManager.js +76 -0
  139. package/package.json +18 -9
@@ -0,0 +1,159 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { z } from "zod";
4
+ import { CloudflareManager } from "../client/manager";
5
+ import { CfTokenVerification } from "../user/userManager";
6
+ /**
7
+ * One access policy to attach to a minted token: a set of permission groups (named, resolved to ids
8
+ * at mint time) and the resources they apply to. This is the reusable unit — every use case that
9
+ * needs a scoped CF token (the secrets manager is the first) describes itself as a list of these,
10
+ * and the manager turns names into the CF policy shape. `effect` defaults to `allow`.
11
+ */
12
+ export interface TokenPermission {
13
+ /** Permission-group names to grant, resolved to ids against the live account list (e.g. "Secrets Store Read"). */
14
+ permissionGroupNames: string[];
15
+ /** The resource scope the groups apply to (e.g. `{ "com.cloudflare.api.account.<id>": "*" }`). */
16
+ resources: Record<string, string>;
17
+ /** Allow or deny the groups against the resources. Defaults to `allow`. */
18
+ effect?: "allow" | "deny";
19
+ }
20
+ /**
21
+ * The whole-account resource scope for a token policy: `{ "com.cloudflare.api.account.<id>": "*" }`.
22
+ * Account-level permission groups (Secrets Store, D1, Workers, …) scope to this. One helper so the
23
+ * exact resource key format lives in a single place instead of being hand-spelled per use case.
24
+ */
25
+ export declare function accountResource(accountId: string): Record<string, string>;
26
+ /**
27
+ * A permission group available to account-owned tokens, as returned by the permission-groups list.
28
+ * Only the id and name matter for resolution — the manager maps a requested name to its id.
29
+ */
30
+ export declare const CfPermissionGroup: z.ZodObject<{
31
+ id: z.ZodString;
32
+ name: z.ZodString;
33
+ }, z.core.$strip>;
34
+ export type CfPermissionGroup = z.output<typeof CfPermissionGroup>;
35
+ /**
36
+ * A freshly minted account-owned token. The `value` is the secret bearer credential — present **only**
37
+ * in the create response, never re-readable — so it is captured here and must be stored at once
38
+ * (e.g. into the Secrets Store). Never log it.
39
+ */
40
+ export declare const MintedAccountToken: z.ZodObject<{
41
+ id: z.ZodString;
42
+ value: z.ZodString;
43
+ name: z.ZodOptional<z.ZodString>;
44
+ status: z.ZodOptional<z.ZodEnum<{
45
+ active: "active";
46
+ disabled: "disabled";
47
+ expired: "expired";
48
+ }>>;
49
+ }, z.core.$strip>;
50
+ export type MintedAccountToken = z.output<typeof MintedAccountToken>;
51
+ /**
52
+ * An existing account-owned token's metadata, as returned by list/get. No `value` — Cloudflare never
53
+ * returns a token's secret after creation — so this is only enough to find a token by name and
54
+ * address it for deletion.
55
+ */
56
+ export declare const AccountTokenSummary: z.ZodObject<{
57
+ id: z.ZodString;
58
+ name: z.ZodString;
59
+ status: z.ZodOptional<z.ZodEnum<{
60
+ active: "active";
61
+ disabled: "disabled";
62
+ expired: "expired";
63
+ }>>;
64
+ }, z.core.$strip>;
65
+ export type AccountTokenSummary = z.output<typeof AccountTokenSummary>;
66
+ /**
67
+ * Out-of-Worker control plane for **account-owned** Cloudflare API tokens: mint scoped, least-privilege
68
+ * tokens, find them by name, and delete them. Account-owned (not user-bound) by design — CLAUDE.md
69
+ * prefers org-level tokens that outlive any one person.
70
+ *
71
+ * This is the reusable seam behind every "pithy mints its own scoped token" case. A caller hands it a
72
+ * token name and a set of {@link TokenPermission}s (permission-group names + resource scope); the
73
+ * manager resolves the names to ids against the live account list and creates the token. The secrets
74
+ * manager's runtime credential is the first concrete use; more follow without touching this class.
75
+ */
76
+ export declare class CloudflareAccountTokensManager extends CloudflareManager {
77
+ getServiceType(): string;
78
+ /** Prove access by listing permission groups — a read, never a token create/delete. Never throws. */
79
+ validateServiceAccess(): Promise<boolean>;
80
+ /**
81
+ * Verify the **calling** token against this account — `GET /accounts/{id}/tokens/verify`.
82
+ *
83
+ * Distinct from {@link CloudflareUserManager.verifyToken}, which hits the *user*-scoped
84
+ * `/user/tokens/verify` and returns `Invalid API Token` for an account-owned (`cfat_*`) credential.
85
+ * Since `CLAUDE.md` prefers account-owned tokens over user-bound ones, the user endpoint is the wrong
86
+ * one for the common case, and using it reports a working token as invalid.
87
+ */
88
+ verifyToken(): Promise<CfTokenVerification>;
89
+ /**
90
+ * The name of an account-owned token by id, or `null` — `GET /accounts/{id}/tokens/{token_id}`.
91
+ *
92
+ * Distinct from {@link CloudflareUserManager.getTokenName}, which hits `GET /user/tokens/:id` and
93
+ * answers `9109 — Valid user-level authentication not found` for a `cfat_*` caller. Both halves of
94
+ * the account-token identity read — verify and name — are account-scoped, or neither is.
95
+ *
96
+ * **Best effort, by contract.** Reading a token record needs `API Tokens Read`, which the
97
+ * least-privilege tokens {@link mintToken} produces deliberately do not carry: a 403 is the normal
98
+ * answer for a CI credential asking its own name, not a fault. So a denied read returns `null` and
99
+ * the caller falls back to the token id from {@link verifyToken} — which any account token can call
100
+ * with no permission at all. Every other failure still throws.
101
+ */
102
+ getTokenName(tokenId: string): Promise<string | null>;
103
+ /** Every permission group available to account-owned tokens in this account (SDK auto-paginates). */
104
+ listPermissionGroups(): Promise<CfPermissionGroup[]>;
105
+ /**
106
+ * Resolve permission-group names to their `{ id }` references against the account's live group list.
107
+ * Throws a clear, actionable error naming any name that does not exist, or any name that is
108
+ * **ambiguous** — Cloudflare reuses display names across resource scopes (e.g. account vs zone), so a
109
+ * name that maps to more than one id can't be resolved without picking the wrong scope. Either way a
110
+ * typo'd or ambiguous group fails loudly at mint time, never silently widening or narrowing scope.
111
+ */
112
+ resolvePermissionGroups(names: string[]): Promise<Array<{
113
+ id: string;
114
+ }>>;
115
+ /**
116
+ * Mint a new account-owned token named `name` carrying `permissions`. Resolves every permission
117
+ * group name to its id (one group-list fetch for all policies), builds the CF policy set, and
118
+ * creates the token. Returns the token's one-time secret `value` — store it immediately. A 403 (the
119
+ * calling token cannot create tokens) is reraised as an actionable "grant 'Account API Tokens
120
+ * Write'" error so the cause is obvious; this doubles as the fail-fast preflight when provisioning
121
+ * runs the mint first.
122
+ */
123
+ mintToken(name: string, permissions: TokenPermission[]): Promise<MintedAccountToken>;
124
+ /**
125
+ * Resolve names against a prefetched name→ids index, throwing on any unknown or ambiguous name.
126
+ * The shared core of {@link resolvePermissionGroups} and {@link mintToken}: take the group list once,
127
+ * resolve every name against it. Ambiguity (a name with more than one id) is a hard error, not a
128
+ * silent pick — see {@link resolvePermissionGroups}.
129
+ */
130
+ private resolveAgainstIndex;
131
+ /** Every account-owned token's metadata (SDK auto-paginates). No secret values — list is metadata only. */
132
+ listTokens(): Promise<AccountTokenSummary[]>;
133
+ /** Find an account token by exact name, or `null` — for idempotent re-mint (SDK auto-paginates). */
134
+ findTokenByName(name: string): Promise<AccountTokenSummary | null>;
135
+ /** Delete an account token by id. */
136
+ deleteToken(tokenId: string): Promise<void>;
137
+ /**
138
+ * Delete every account token with the given name; returns how many were removed. Names are not
139
+ * unique in Cloudflare, so any duplicates (e.g. a prior interrupted run) are swept here. A no-op
140
+ * when none match. Used by teardown.
141
+ */
142
+ deleteTokensByName(name: string): Promise<number>;
143
+ /**
144
+ * Roll an existing token's secret: regenerate its value **in place**, keeping the same token id,
145
+ * name, and policies (the dashboard's "Roll" action). Returns the new secret value — the only time
146
+ * Cloudflare hands it back — so it must be stored at once. The seam the deferred value-rotation
147
+ * builds on: a credential self-rolls without ever changing identity.
148
+ */
149
+ rollTokenValue(tokenId: string): Promise<string>;
150
+ /**
151
+ * Ensure a named token exists and return a **fresh** secret for it. If a token of this name already
152
+ * exists, roll its value in place (same id and policies, new secret); otherwise mint a new one.
153
+ * Either way the caller gets a usable secret to store — the idempotent path for a credential that
154
+ * lives in the Secrets Store, since Cloudflare never returns a token's existing secret. Mirrors the
155
+ * dashboard's roll-or-create.
156
+ */
157
+ rollToken(name: string, permissions: TokenPermission[]): Promise<MintedAccountToken>;
158
+ }
159
+ //# sourceMappingURL=accountTokensManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accountTokensManager.d.ts","sourceRoot":"","sources":["../../src/tokens/accountTokensManager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,kHAAkH;IAClH,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,kGAAkG;IAClG,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,2EAA2E;IAC3E,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEzE;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB;;;iBAK8E,CAAC;AAC7G,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEnE;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;iBAO6D,CAAC;AAC7F,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAErE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;iBAMuD,CAAC;AACxF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEvE;;;;;;;;;GASG;AACH,qBAAa,8BAA+B,SAAQ,iBAAiB;IACnE,cAAc,IAAI,MAAM,CAEvB;IAED,qGAAqG;IAC/F,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO9C;IAED;;;;;;;OAOG;IACG,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAKhD;IAED;;;;;;;;;;;;OAYG;IACG,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAW1D;IAED,qGAAqG;IAC/F,oBAAoB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAWzD;IAED;;;;;;OAMG;IACG,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAE7E;IAED;;;;;;;OAOG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA2BzF;IAED;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAqB3B,2GAA2G;IACrG,UAAU,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC,CASjD;IAED,oGAAoG;IAC9F,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAQvE;IAED,qCAAqC;IAC/B,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhD;IAED;;;;OAIG;IACG,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYtD;IAED;;;;;OAKG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKrD;IAED;;;;;;OAMG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAKzF;CACF"}
@@ -0,0 +1,282 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { CloudflareNotConfiguredError, CloudflareRequestError, cloudflareRequest, decodeResponse, isAuthorizationError, messageOf } from "../client/errors.js";
4
+ import { CloudflareManager } from "../client/manager.js";
5
+ import { CfTokenVerification } from "../user/userManager.js";
6
+ import { z } from "zod";
7
+ //#region src/tokens/accountTokensManager.ts
8
+ /**
9
+ * The whole-account resource scope for a token policy: `{ "com.cloudflare.api.account.<id>": "*" }`.
10
+ * Account-level permission groups (Secrets Store, D1, Workers, …) scope to this. One helper so the
11
+ * exact resource key format lives in a single place instead of being hand-spelled per use case.
12
+ */
13
+ function accountResource(accountId) {
14
+ return { [`com.cloudflare.api.account.${accountId}`]: "*" };
15
+ }
16
+ /**
17
+ * A permission group available to account-owned tokens, as returned by the permission-groups list.
18
+ * Only the id and name matter for resolution — the manager maps a requested name to its id.
19
+ */
20
+ const CfPermissionGroup = z.object({
21
+ id: z.string().describe("The CF-assigned permission-group id, referenced in a token policy."),
22
+ name: z.string().describe("The human-readable permission-group name (e.g. 'Secrets Store Read').")
23
+ }).describe("A Cloudflare account-token permission group: the id a policy references and its display name.");
24
+ /**
25
+ * A freshly minted account-owned token. The `value` is the secret bearer credential — present **only**
26
+ * in the create response, never re-readable — so it is captured here and must be stored at once
27
+ * (e.g. into the Secrets Store). Never log it.
28
+ */
29
+ const MintedAccountToken = z.object({
30
+ id: z.string().describe("The CF-assigned token id, used to address the token for get/delete."),
31
+ value: z.string().describe("The secret token value — returned once on create, never again. Store immediately."),
32
+ name: z.string().optional().describe("The token name as registered with Cloudflare."),
33
+ status: z.enum([
34
+ "active",
35
+ "disabled",
36
+ "expired"
37
+ ]).optional().describe("The token's lifecycle status.")
38
+ }).describe("A newly created account-owned API token, including its one-time secret value.");
39
+ /**
40
+ * An existing account-owned token's metadata, as returned by list/get. No `value` — Cloudflare never
41
+ * returns a token's secret after creation — so this is only enough to find a token by name and
42
+ * address it for deletion.
43
+ */
44
+ const AccountTokenSummary = z.object({
45
+ id: z.string().describe("The CF-assigned token id, used to address the token for deletion."),
46
+ name: z.string().describe("The token name, the key callers match on for idempotent re-mint."),
47
+ status: z.enum([
48
+ "active",
49
+ "disabled",
50
+ "expired"
51
+ ]).optional().describe("The token's lifecycle status.")
52
+ }).describe("An existing account-owned API token's metadata (never its secret value).");
53
+ /**
54
+ * Out-of-Worker control plane for **account-owned** Cloudflare API tokens: mint scoped, least-privilege
55
+ * tokens, find them by name, and delete them. Account-owned (not user-bound) by design — CLAUDE.md
56
+ * prefers org-level tokens that outlive any one person.
57
+ *
58
+ * This is the reusable seam behind every "pithy mints its own scoped token" case. A caller hands it a
59
+ * token name and a set of {@link TokenPermission}s (permission-group names + resource scope); the
60
+ * manager resolves the names to ids against the live account list and creates the token. The secrets
61
+ * manager's runtime credential is the first concrete use; more follow without touching this class.
62
+ */
63
+ var CloudflareAccountTokensManager = class extends CloudflareManager {
64
+ getServiceType() {
65
+ return "Cloudflare Account API Tokens";
66
+ }
67
+ /** Prove access by listing permission groups — a read, never a token create/delete. Never throws. */
68
+ async validateServiceAccess() {
69
+ try {
70
+ await this.listPermissionGroups();
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+ /**
77
+ * Verify the **calling** token against this account — `GET /accounts/{id}/tokens/verify`.
78
+ *
79
+ * Distinct from {@link CloudflareUserManager.verifyToken}, which hits the *user*-scoped
80
+ * `/user/tokens/verify` and returns `Invalid API Token` for an account-owned (`cfat_*`) credential.
81
+ * Since `CLAUDE.md` prefers account-owned tokens over user-bound ones, the user endpoint is the wrong
82
+ * one for the common case, and using it reports a working token as invalid.
83
+ */
84
+ async verifyToken() {
85
+ const raw = await cloudflareRequest("verify account token", () => this.getClient().accounts.tokens.verify({ account_id: this.accountId }));
86
+ return decodeResponse(CfTokenVerification, raw, "account token verify");
87
+ }
88
+ /**
89
+ * The name of an account-owned token by id, or `null` — `GET /accounts/{id}/tokens/{token_id}`.
90
+ *
91
+ * Distinct from {@link CloudflareUserManager.getTokenName}, which hits `GET /user/tokens/:id` and
92
+ * answers `9109 — Valid user-level authentication not found` for a `cfat_*` caller. Both halves of
93
+ * the account-token identity read — verify and name — are account-scoped, or neither is.
94
+ *
95
+ * **Best effort, by contract.** Reading a token record needs `API Tokens Read`, which the
96
+ * least-privilege tokens {@link mintToken} produces deliberately do not carry: a 403 is the normal
97
+ * answer for a CI credential asking its own name, not a fault. So a denied read returns `null` and
98
+ * the caller falls back to the token id from {@link verifyToken} — which any account token can call
99
+ * with no permission at all. Every other failure still throws.
100
+ */
101
+ async getTokenName(tokenId) {
102
+ const raw = await cloudflareRequest(`get account token ${tokenId}`, async () => {
103
+ try {
104
+ return await this.getClient().accounts.tokens.get(tokenId, { account_id: this.accountId });
105
+ } catch (error) {
106
+ if (isAuthorizationError(error)) return null;
107
+ throw error;
108
+ }
109
+ });
110
+ const parsed = TokenName.safeParse(raw);
111
+ return parsed.success ? parsed.data.name : null;
112
+ }
113
+ /** Every permission group available to account-owned tokens in this account (SDK auto-paginates). */
114
+ async listPermissionGroups() {
115
+ return cloudflareRequest("list account token permission groups", async () => {
116
+ const out = [];
117
+ for await (const group of this.getClient().accounts.tokens.permissionGroups.list({ account_id: this.accountId })) {
118
+ const parsed = CfPermissionGroup.safeParse(group);
119
+ if (parsed.success) out.push(parsed.data);
120
+ }
121
+ return out;
122
+ });
123
+ }
124
+ /**
125
+ * Resolve permission-group names to their `{ id }` references against the account's live group list.
126
+ * Throws a clear, actionable error naming any name that does not exist, or any name that is
127
+ * **ambiguous** — Cloudflare reuses display names across resource scopes (e.g. account vs zone), so a
128
+ * name that maps to more than one id can't be resolved without picking the wrong scope. Either way a
129
+ * typo'd or ambiguous group fails loudly at mint time, never silently widening or narrowing scope.
130
+ */
131
+ async resolvePermissionGroups(names) {
132
+ return this.resolveAgainstIndex(indexByName(await this.listPermissionGroups()), names);
133
+ }
134
+ /**
135
+ * Mint a new account-owned token named `name` carrying `permissions`. Resolves every permission
136
+ * group name to its id (one group-list fetch for all policies), builds the CF policy set, and
137
+ * creates the token. Returns the token's one-time secret `value` — store it immediately. A 403 (the
138
+ * calling token cannot create tokens) is reraised as an actionable "grant 'Account API Tokens
139
+ * Write'" error so the cause is obvious; this doubles as the fail-fast preflight when provisioning
140
+ * runs the mint first.
141
+ */
142
+ async mintToken(name, permissions) {
143
+ const index = indexByName(await this.listPermissionGroups());
144
+ const policies = permissions.map((permission) => ({
145
+ effect: permission.effect ?? "allow",
146
+ permission_groups: this.resolveAgainstIndex(index, permission.permissionGroupNames),
147
+ resources: permission.resources
148
+ }));
149
+ let raw;
150
+ try {
151
+ raw = await this.getClient().accounts.tokens.create({
152
+ account_id: this.accountId,
153
+ name,
154
+ policies
155
+ });
156
+ } catch (error) {
157
+ if (isAuthorizationError(error)) throw new CloudflareNotConfiguredError({
158
+ message: "The Cloudflare API token is not allowed to create account tokens.",
159
+ action: "Grant it 'Account API Tokens Write' (Account → API Tokens → Edit), then re-run.",
160
+ detail: `mint account token '${name}': ${messageOf(error)}`
161
+ }, { cause: error });
162
+ throw new CloudflareRequestError({
163
+ message: `Failed to mint account token '${name}'.`,
164
+ detail: messageOf(error)
165
+ }, { cause: error });
166
+ }
167
+ return decodeResponse(MintedAccountToken, raw, "account token create");
168
+ }
169
+ /**
170
+ * Resolve names against a prefetched name→ids index, throwing on any unknown or ambiguous name.
171
+ * The shared core of {@link resolvePermissionGroups} and {@link mintToken}: take the group list once,
172
+ * resolve every name against it. Ambiguity (a name with more than one id) is a hard error, not a
173
+ * silent pick — see {@link resolvePermissionGroups}.
174
+ */
175
+ resolveAgainstIndex(index, names) {
176
+ const unknown = names.filter((name) => !index.has(name));
177
+ if (unknown.length > 0) throw new CloudflareNotConfiguredError({
178
+ message: `Unknown Cloudflare permission group(s): ${unknown.join(", ")}.`,
179
+ action: "Check the exact permission-group names against the account's available groups.",
180
+ detail: `resolve permission groups: not found in account ${this.accountId} — ${unknown.join(", ")}`
181
+ });
182
+ const ambiguous = names.filter((name) => (index.get(name)?.length ?? 0) > 1);
183
+ if (ambiguous.length > 0) throw new CloudflareNotConfiguredError({
184
+ message: `Ambiguous Cloudflare permission group(s): ${ambiguous.join(", ")}.`,
185
+ action: "These names map to more than one permission group; scope the token by a unique group name.",
186
+ detail: `resolve permission groups: name maps to multiple ids in account ${this.accountId} — ${ambiguous.join(", ")}`
187
+ });
188
+ return names.map((name) => ({ id: index.get(name)[0] }));
189
+ }
190
+ /** Every account-owned token's metadata (SDK auto-paginates). No secret values — list is metadata only. */
191
+ async listTokens() {
192
+ return cloudflareRequest("list account tokens", async () => {
193
+ const out = [];
194
+ for await (const token of this.getClient().accounts.tokens.list({ account_id: this.accountId })) {
195
+ const parsed = AccountTokenSummary.safeParse(token);
196
+ if (parsed.success) out.push(parsed.data);
197
+ }
198
+ return out;
199
+ });
200
+ }
201
+ /** Find an account token by exact name, or `null` — for idempotent re-mint (SDK auto-paginates). */
202
+ async findTokenByName(name) {
203
+ return cloudflareRequest(`find account token ${name}`, async () => {
204
+ for await (const token of this.getClient().accounts.tokens.list({ account_id: this.accountId })) {
205
+ const parsed = AccountTokenSummary.safeParse(token);
206
+ if (parsed.success && parsed.data.name === name) return parsed.data;
207
+ }
208
+ return null;
209
+ });
210
+ }
211
+ /** Delete an account token by id. */
212
+ async deleteToken(tokenId) {
213
+ await cloudflareRequest(`delete account token ${tokenId}`, () => this.getClient().accounts.tokens.delete(tokenId, { account_id: this.accountId }));
214
+ }
215
+ /**
216
+ * Delete every account token with the given name; returns how many were removed. Names are not
217
+ * unique in Cloudflare, so any duplicates (e.g. a prior interrupted run) are swept here. A no-op
218
+ * when none match. Used by teardown.
219
+ */
220
+ async deleteTokensByName(name) {
221
+ return cloudflareRequest(`delete account tokens named ${name}`, async () => {
222
+ const ids = [];
223
+ for await (const token of this.getClient().accounts.tokens.list({ account_id: this.accountId })) {
224
+ const parsed = AccountTokenSummary.safeParse(token);
225
+ if (parsed.success && parsed.data.name === name) ids.push(parsed.data.id);
226
+ }
227
+ for (const id of ids) await this.getClient().accounts.tokens.delete(id, { account_id: this.accountId });
228
+ return ids.length;
229
+ });
230
+ }
231
+ /**
232
+ * Roll an existing token's secret: regenerate its value **in place**, keeping the same token id,
233
+ * name, and policies (the dashboard's "Roll" action). Returns the new secret value — the only time
234
+ * Cloudflare hands it back — so it must be stored at once. The seam the deferred value-rotation
235
+ * builds on: a credential self-rolls without ever changing identity.
236
+ */
237
+ async rollTokenValue(tokenId) {
238
+ const raw = await cloudflareRequest(`roll account token value ${tokenId}`, () => this.getClient().accounts.tokens.value.update(tokenId, {
239
+ account_id: this.accountId,
240
+ body: {}
241
+ }));
242
+ return decodeResponse(RolledTokenValue, raw, "account token value roll");
243
+ }
244
+ /**
245
+ * Ensure a named token exists and return a **fresh** secret for it. If a token of this name already
246
+ * exists, roll its value in place (same id and policies, new secret); otherwise mint a new one.
247
+ * Either way the caller gets a usable secret to store — the idempotent path for a credential that
248
+ * lives in the Secrets Store, since Cloudflare never returns a token's existing secret. Mirrors the
249
+ * dashboard's roll-or-create.
250
+ */
251
+ async rollToken(name, permissions) {
252
+ const existing = await this.findTokenByName(name);
253
+ if (!existing) return this.mintToken(name, permissions);
254
+ const value = await this.rollTokenValue(existing.id);
255
+ return {
256
+ id: existing.id,
257
+ value,
258
+ name: existing.name,
259
+ status: existing.status
260
+ };
261
+ }
262
+ };
263
+ /** The rolled secret value Cloudflare returns from a value-roll — a non-empty bearer string. */
264
+ const RolledTokenValue = z.string().min(1);
265
+ /** Just the token's name, the piece the account-token actor path needs. */
266
+ const TokenName = z.object({ name: z.string().describe("The token's registered name — the audit actor id for an account token.") }).describe("The one field a token-record read is narrowed to: its name.");
267
+ /**
268
+ * Index permission groups by display name, collecting **every** id that bears a name. A list rather
269
+ * than one id per name on purpose: Cloudflare reuses display names across scopes, and a name with
270
+ * more than one id is ambiguous — the resolver rejects it rather than silently pick one.
271
+ */
272
+ function indexByName(groups) {
273
+ const index = /* @__PURE__ */ new Map();
274
+ for (const group of groups) {
275
+ const ids = index.get(group.name) ?? [];
276
+ ids.push(group.id);
277
+ index.set(group.name, ids);
278
+ }
279
+ return index;
280
+ }
281
+ //#endregion
282
+ export { AccountTokenSummary, CfPermissionGroup, CloudflareAccountTokensManager, MintedAccountToken, accountResource };
@@ -0,0 +1,38 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * The permission catalog: short, stable permission **keys** (`d1:read`) mapped to the Cloudflare
5
+ * account **permission-group display names** they grant (`"D1 Read"`). Token profiles are declared in
6
+ * these keys, so the profiles read cleanly and the CF-specific names live in one place. The names are
7
+ * resolved to permission-group **ids** against the live account at mint time
8
+ * (`CloudflareAccountTokensManager.resolvePermissionGroups`), which fails loudly on an unknown or
9
+ * ambiguous name — so a name that drifts from Cloudflare's catalog is caught at mint, never silently
10
+ * mis-scoped. Verify the exact names against the account when adding a key; an adopter can override a
11
+ * profile's keys if their account differs.
12
+ */
13
+ export declare const PERMISSION_GROUPS: {
14
+ readonly "d1:read": readonly ["D1 Read"];
15
+ readonly "d1:write": readonly ["D1 Write"];
16
+ readonly "workers:write": readonly ["Workers Scripts Write"];
17
+ readonly "secrets:read": readonly ["Secrets Store Read"];
18
+ readonly "secrets:write": readonly ["Secrets Store Write"];
19
+ readonly "email:routing": readonly ["Email Routing Rules Write"];
20
+ readonly "kv:write": readonly ["Workers KV Storage Write"];
21
+ readonly "r2:read": readonly ["Workers R2 Storage Read"];
22
+ readonly "r2:write": readonly ["Workers R2 Storage Write"];
23
+ readonly "vectorize:read": readonly ["Vectorize Read"];
24
+ readonly "vectorize:write": readonly ["Vectorize Write"];
25
+ readonly "ai:read": readonly ["Workers AI Read"];
26
+ readonly "zone:read": readonly ["Zone Read"];
27
+ };
28
+ /** A known permission key — one of {@link PERMISSION_GROUPS}'s keys. */
29
+ export type PermissionKey = keyof typeof PERMISSION_GROUPS;
30
+ /** Narrow an arbitrary string to a {@link PermissionKey}. */
31
+ export declare function isPermissionKey(key: string): key is PermissionKey;
32
+ /**
33
+ * Resolve permission keys to the CF permission-group display names they grant, de-duped in first-seen
34
+ * order. An unknown key fails with an actionable error naming the valid keys — caught at the CLI/config
35
+ * boundary before any CF call.
36
+ */
37
+ export declare function resolvePermissionKeys(keys: string[]): string[];
38
+ //# sourceMappingURL=permissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/tokens/permissions.ts"],"names":[],"mappings":"AAKA;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB;aAC5B,SAAS,YAAG,SAAS;aAIrB,UAAU,YAAG,UAAU;aACvB,eAAe,YAAG,uBAAuB;aACzC,cAAc,YAAG,oBAAoB;aACrC,eAAe,YAAG,qBAAqB;aACvC,eAAe,YAAG,2BAA2B;aAC7C,UAAU,YAAG,0BAA0B;aACvC,SAAS,YAAG,yBAAyB;aACrC,UAAU,YAAG,0BAA0B;aACvC,gBAAgB,YAAG,gBAAgB;aACnC,iBAAiB,YAAG,iBAAiB;aACrC,SAAS,YAAG,iBAAiB;aAK7B,WAAW,YAAG,WAAW;CACjB,CAAC;AAEX,wEAAwE;AACxE,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,iBAAiB,CAAC;AAE3D,6DAA6D;AAC7D,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,aAAa,CAEjE;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAe9D"}
@@ -0,0 +1,52 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import { CloudflareNotConfiguredError } from "../client/errors.js";
4
+ //#region src/tokens/permissions.ts
5
+ /**
6
+ * The permission catalog: short, stable permission **keys** (`d1:read`) mapped to the Cloudflare
7
+ * account **permission-group display names** they grant (`"D1 Read"`). Token profiles are declared in
8
+ * these keys, so the profiles read cleanly and the CF-specific names live in one place. The names are
9
+ * resolved to permission-group **ids** against the live account at mint time
10
+ * (`CloudflareAccountTokensManager.resolvePermissionGroups`), which fails loudly on an unknown or
11
+ * ambiguous name — so a name that drifts from Cloudflare's catalog is caught at mint, never silently
12
+ * mis-scoped. Verify the exact names against the account when adding a key; an adopter can override a
13
+ * profile's keys if their account differs.
14
+ */
15
+ const PERMISSION_GROUPS = {
16
+ "d1:read": ["D1 Read"],
17
+ "d1:write": ["D1 Write"],
18
+ "workers:write": ["Workers Scripts Write"],
19
+ "secrets:read": ["Secrets Store Read"],
20
+ "secrets:write": ["Secrets Store Write"],
21
+ "email:routing": ["Email Routing Rules Write"],
22
+ "kv:write": ["Workers KV Storage Write"],
23
+ "r2:read": ["Workers R2 Storage Read"],
24
+ "r2:write": ["Workers R2 Storage Write"],
25
+ "vectorize:read": ["Vectorize Read"],
26
+ "vectorize:write": ["Vectorize Write"],
27
+ "ai:read": ["Workers AI Read"],
28
+ "zone:read": ["Zone Read"]
29
+ };
30
+ /** Narrow an arbitrary string to a {@link PermissionKey}. */
31
+ function isPermissionKey(key) {
32
+ return key in PERMISSION_GROUPS;
33
+ }
34
+ /**
35
+ * Resolve permission keys to the CF permission-group display names they grant, de-duped in first-seen
36
+ * order. An unknown key fails with an actionable error naming the valid keys — caught at the CLI/config
37
+ * boundary before any CF call.
38
+ */
39
+ function resolvePermissionKeys(keys) {
40
+ const names = [];
41
+ for (const key of keys) {
42
+ if (!isPermissionKey(key)) throw new CloudflareNotConfiguredError({
43
+ message: `Unknown token permission key: ${key}.`,
44
+ action: `Use one of: ${Object.keys(PERMISSION_GROUPS).join(", ")}.`,
45
+ detail: `resolve permission keys: ${key} is not in the permission catalog`
46
+ });
47
+ for (const name of PERMISSION_GROUPS[key]) if (!names.includes(name)) names.push(name);
48
+ }
49
+ return names;
50
+ }
51
+ //#endregion
52
+ export { PERMISSION_GROUPS, isPermissionKey, resolvePermissionKeys };
@@ -0,0 +1,104 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { Capability, TokenProfileSeam } from "@pithy-sh/core/src/capability/capability";
4
+ import { type TokenPermission } from "./accountTokensManager";
5
+ import { type PermissionKey } from "./permissions";
6
+ /**
7
+ * Where a minted token's value is written.
8
+ *
9
+ * - `secrets-store` — the CF Secrets Store; a Worker reads it via its binding. The destination for a
10
+ * worker-consumer token; resolved from the token's declared secret (its registry backend) unless a
11
+ * profile or `--store` names it directly.
12
+ * - `dev-vars` — `<config>/<project>/tokens.json`, keyed by environment and outside every checkout.
13
+ * **Read by an operator, never by a command** — nothing resolves a credential from it. The store for
14
+ * the `ci-system` token: mint it here, read the value
15
+ * out, and set it as CI's `CLOUDFLARE_API_TOKEN`. It wrote `.dev.vars.<env>` *inside* the project until
16
+ * #182; the name stays because it is a public `--store` flag value.
17
+ * - `ephemeral` — nothing is written; the value is used in-process and discarded. The one-step CI
18
+ * path — CI can't read the CF Secrets Store, so it mints and uses the token in the same job.
19
+ */
20
+ export type TokenStore = "secrets-store" | "dev-vars" | "ephemeral";
21
+ /** The valid stores, for validating a `--store` flag or a profile's `defaultStore`. */
22
+ export declare const TOKEN_STORES: readonly TokenStore[];
23
+ /** The name of the one CI system token — the single least-privilege credential a CI pipeline runs under. */
24
+ export declare const CI_SYSTEM_PROFILE = "ci-system";
25
+ /**
26
+ * A named token profile: the CF permissions a job needs (short {@link PermissionKey}s), the resource
27
+ * scope, the **secret** its minted value is stored under (whose registry backend is the destination),
28
+ * and an optional built-in store override. The consuming code is the source of truth for its access;
29
+ * adopters override any field per profile.
30
+ */
31
+ export interface TokenProfile {
32
+ /** The profile's stable name — the key it is minted, listed, and revoked under. */
33
+ name: string;
34
+ /** The CF permissions the token grants, as short catalog keys (resolved to group names at mint). */
35
+ permissions: PermissionKey[];
36
+ /** The resource scope: `"account"` (the default) or an explicit CF resource map. */
37
+ resources?: "account" | Record<string, string>;
38
+ /** The secret-registry name the minted value is stored under; its declared backend is the destination. */
39
+ secret: string;
40
+ /**
41
+ * Whether the value behind {@link secret} differs per environment (the default) or is one value every
42
+ * environment shares. It decides the environment segment of the **CF Secrets Store entry name** the
43
+ * minted value is written to, and nothing else — never the variable key, which is a variable name
44
+ * and stays verbatim. A `global` profile writes one entry with the literal `global` in that slot,
45
+ * matching what provisioning wrote; an `environment` profile writes one entry per environment.
46
+ */
47
+ secretScope?: TokenSecretScope;
48
+ /** A built-in destination override (`dev-vars`/`ephemeral`/`secrets-store`); else the secret's backend. */
49
+ defaultStore?: TokenStore;
50
+ /** Why this profile exists / what job consumes it. */
51
+ description: string;
52
+ }
53
+ /** Whether a profile's secret is one value per environment or one value shared by all of them. */
54
+ export type TokenSecretScope = "environment" | "global";
55
+ /** An adopter's override of a profile's defaults (from `pithy.config.ts` or CLI flags). Every field optional. */
56
+ export interface ProfileOverride {
57
+ /** Replace the profile's permission keys. */
58
+ permissions?: PermissionKey[];
59
+ /** Replace the profile's resource scope with an explicit CF resource map. */
60
+ resources?: Record<string, string>;
61
+ /** Replace where the value is written (`--store`). */
62
+ store?: TokenStore;
63
+ }
64
+ /** The env-var / secret name a profile's token is stored under: `CF_TOKEN_<PROFILE>`. */
65
+ export declare function tokenSecretName(profile: string): string;
66
+ /**
67
+ * Author a token profile: validates its permission keys against the catalog at author time, so a typo
68
+ * fails where the profile is declared, not deep in a mint call. Returns the profile unchanged, typed.
69
+ */
70
+ export declare function defineTokenProfile(profile: TokenProfile): TokenProfile;
71
+ /**
72
+ * The seam fields this package reads. Core's {@link TokenProfileSeam} is the structural contract every
73
+ * capability declares against; `secretScope` is additive on top of it, so it is named here rather than
74
+ * in core — a seam value that omits it is still assignable, and one that carries it is read as declared.
75
+ *
76
+ * Exported so a capability can type its `tokenProfiles` entry against it. Declaring `secretScope` as an
77
+ * inline object literal directly in `tokenProfiles` still trips TypeScript's excess-property check
78
+ * against core's narrower `TokenProfileSeam`; declare the entry as its own `const` (with `as const` or
79
+ * `satisfies TokenProfileSeamInput`) and it flows through.
80
+ */
81
+ export interface TokenProfileSeamInput extends TokenProfileSeam {
82
+ readonly secretScope?: string;
83
+ }
84
+ /**
85
+ * The project's token-profile registry: the aggregating `ci-system` profile plus every composed
86
+ * capability's worker-consumer {@link Capability.tokenProfiles} slice. A capability profile that
87
+ * clashes with `ci-system` (or another capability's name) fails loudly at assembly. This is the one
88
+ * registry `pithy token` reads.
89
+ */
90
+ export declare function resolveTokenProfiles(capabilities: readonly Capability[]): Record<string, TokenProfile>;
91
+ /**
92
+ * Resolve a profile by name from the aggregated registry, applying an adopter override. An unknown
93
+ * name fails with an actionable error listing the known profiles.
94
+ */
95
+ export declare function resolveProfile(profiles: Record<string, TokenProfile>, name: string, override?: ProfileOverride): TokenProfile;
96
+ /** Build the account-scoped {@link TokenPermission}s for a set of permission keys — the mint input. */
97
+ export declare function permissionsForKeys(keys: PermissionKey[], accountId: string): TokenPermission[];
98
+ /**
99
+ * Turn a resolved profile into the {@link TokenPermission}s the account-tokens manager mints from: the
100
+ * profile's permission keys resolved to CF group names, scoped to the account (or the profile's
101
+ * explicit resources). One policy per profile — a token carries exactly the access its profile declares.
102
+ */
103
+ export declare function profilePermissions(profile: TokenProfile, accountId: string): TokenPermission[];
104
+ //# sourceMappingURL=profiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../src/tokens/profiles.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAE7F,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EAAmB,KAAK,aAAa,EAAyB,MAAM,eAAe,CAAC;AAE3F;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG,UAAU,GAAG,WAAW,CAAC;AAEpE,uFAAuF;AACvF,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAA+C,CAAC;AAE9F,4GAA4G;AAC5G,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAe7C;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;IACb,oGAAoG;IACpG,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,oFAAoF;IACpF,SAAS,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,0GAA0G;IAC1G,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,2GAA2G;IAC3G,YAAY,CAAC,EAAE,UAAU,CAAC;IAC1B,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,kGAAkG;AAClG,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAAG,QAAQ,CAAC;AAKxD,iHAAiH;AACjH,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,WAAW,CAAC,EAAE,aAAa,EAAE,CAAC;IAC9B,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,sDAAsD;IACtD,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED,yFAAyF;AACzF,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,YAAY,CAGtE;AAkCD;;;;;;;;;GASG;AACH,MAAM,WAAW,qBAAsB,SAAQ,gBAAgB;IAC7D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AA6BD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CActG;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EACtC,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,eAAe,GACzB,YAAY,CAgBd;AAED,uGAAuG;AACvG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE,CAE9F;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE,CAI9F"}