@vendoai/vendo 0.4.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 (212) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +28 -0
  3. package/bin/vendo.mjs +4 -0
  4. package/dist/ai-sdk.d.ts +35 -0
  5. package/dist/ai-sdk.js +62 -0
  6. package/dist/auth-presets/auth-js.d.ts +14 -0
  7. package/dist/auth-presets/auth-js.js +91 -0
  8. package/dist/auth-presets/auth0.d.ts +23 -0
  9. package/dist/auth-presets/auth0.js +98 -0
  10. package/dist/auth-presets/clerk.d.ts +23 -0
  11. package/dist/auth-presets/clerk.js +65 -0
  12. package/dist/auth-presets/conformance.d.ts +42 -0
  13. package/dist/auth-presets/conformance.js +139 -0
  14. package/dist/auth-presets/identity.d.ts +56 -0
  15. package/dist/auth-presets/identity.js +152 -0
  16. package/dist/auth-presets/index.d.ts +14 -0
  17. package/dist/auth-presets/index.js +12 -0
  18. package/dist/auth-presets/jwt.d.ts +16 -0
  19. package/dist/auth-presets/jwt.js +54 -0
  20. package/dist/auth-presets/shared.d.ts +35 -0
  21. package/dist/auth-presets/shared.js +1 -0
  22. package/dist/auth-presets/supabase.d.ts +42 -0
  23. package/dist/auth-presets/supabase.js +229 -0
  24. package/dist/byo-approvals.d.ts +37 -0
  25. package/dist/byo-approvals.js +189 -0
  26. package/dist/capability-misses.d.ts +38 -0
  27. package/dist/capability-misses.js +172 -0
  28. package/dist/catalog.d.ts +19 -0
  29. package/dist/catalog.js +152 -0
  30. package/dist/cli/cloud/args.d.ts +3 -0
  31. package/dist/cli/cloud/args.js +32 -0
  32. package/dist/cli/cloud/auth.d.ts +17 -0
  33. package/dist/cli/cloud/auth.js +123 -0
  34. package/dist/cli/cloud/client.d.ts +28 -0
  35. package/dist/cli/cloud/client.js +133 -0
  36. package/dist/cli/cloud/command.d.ts +22 -0
  37. package/dist/cli/cloud/command.js +73 -0
  38. package/dist/cli/cloud/deploy.d.ts +26 -0
  39. package/dist/cli/cloud/deploy.js +187 -0
  40. package/dist/cli/cloud/device-login.d.ts +32 -0
  41. package/dist/cli/cloud/device-login.js +259 -0
  42. package/dist/cli/cloud/index.d.ts +5 -0
  43. package/dist/cli/cloud/index.js +59 -0
  44. package/dist/cli/cloud/keys.d.ts +2 -0
  45. package/dist/cli/cloud/keys.js +31 -0
  46. package/dist/cli/cloud/members.d.ts +3 -0
  47. package/dist/cli/cloud/members.js +25 -0
  48. package/dist/cli/cloud/output.d.ts +4 -0
  49. package/dist/cli/cloud/output.js +12 -0
  50. package/dist/cli/cloud/pending-claim.d.ts +29 -0
  51. package/dist/cli/cloud/pending-claim.js +38 -0
  52. package/dist/cli/cloud/read.d.ts +3 -0
  53. package/dist/cli/cloud/read.js +12 -0
  54. package/dist/cli/cloud/services.d.ts +2 -0
  55. package/dist/cli/cloud/services.js +9 -0
  56. package/dist/cli/cloud/session.d.ts +13 -0
  57. package/dist/cli/cloud/session.js +41 -0
  58. package/dist/cli/cloud-init.d.ts +60 -0
  59. package/dist/cli/cloud-init.js +149 -0
  60. package/dist/cli/dep-versions.d.ts +26 -0
  61. package/dist/cli/dep-versions.js +77 -0
  62. package/dist/cli/doctor-codes.d.ts +58 -0
  63. package/dist/cli/doctor-codes.js +60 -0
  64. package/dist/cli/doctor-live.d.ts +67 -0
  65. package/dist/cli/doctor-live.js +169 -0
  66. package/dist/cli/doctor.d.ts +52 -0
  67. package/dist/cli/doctor.js +467 -0
  68. package/dist/cli/eject.d.ts +29 -0
  69. package/dist/cli/eject.js +141 -0
  70. package/dist/cli/extract/apply.d.ts +26 -0
  71. package/dist/cli/extract/apply.js +123 -0
  72. package/dist/cli/extract/claude-cli-harness.d.ts +18 -0
  73. package/dist/cli/extract/claude-cli-harness.js +132 -0
  74. package/dist/cli/extract/claude-harness.d.ts +14 -0
  75. package/dist/cli/extract/claude-harness.js +127 -0
  76. package/dist/cli/extract/codex-cli-harness.d.ts +31 -0
  77. package/dist/cli/extract/codex-cli-harness.js +154 -0
  78. package/dist/cli/extract/delegate.d.ts +21 -0
  79. package/dist/cli/extract/delegate.js +77 -0
  80. package/dist/cli/extract/extraction.d.ts +65 -0
  81. package/dist/cli/extract/extraction.js +300 -0
  82. package/dist/cli/extract/gateway-fuel.d.ts +69 -0
  83. package/dist/cli/extract/gateway-fuel.js +69 -0
  84. package/dist/cli/extract/harness.d.ts +114 -0
  85. package/dist/cli/extract/harness.js +84 -0
  86. package/dist/cli/extract/index.d.ts +9 -0
  87. package/dist/cli/extract/index.js +9 -0
  88. package/dist/cli/extract/npx-engine-harness.d.ts +73 -0
  89. package/dist/cli/extract/npx-engine-harness.js +225 -0
  90. package/dist/cli/extract/stages.d.ts +149 -0
  91. package/dist/cli/extract/stages.js +398 -0
  92. package/dist/cli/framework.d.ts +8 -0
  93. package/dist/cli/framework.js +36 -0
  94. package/dist/cli/init-auth.d.ts +67 -0
  95. package/dist/cli/init-auth.js +142 -0
  96. package/dist/cli/init-scaffolds.d.ts +28 -0
  97. package/dist/cli/init-scaffolds.js +235 -0
  98. package/dist/cli/init.d.ts +115 -0
  99. package/dist/cli/init.js +1008 -0
  100. package/dist/cli/mcp/index.d.ts +7 -0
  101. package/dist/cli/mcp/index.js +59 -0
  102. package/dist/cli/mcp/registry.d.ts +9 -0
  103. package/dist/cli/mcp/registry.js +117 -0
  104. package/dist/cli/mcp/server-json.d.ts +12 -0
  105. package/dist/cli/mcp/server-json.js +65 -0
  106. package/dist/cli/mcp/server.schema.json +574 -0
  107. package/dist/cli/mcp/verify-domain.d.ts +11 -0
  108. package/dist/cli/mcp/verify-domain.js +48 -0
  109. package/dist/cli/playground/app/embed-entry.d.ts +20 -0
  110. package/dist/cli/playground/app/embed-entry.js +46 -0
  111. package/dist/cli/playground/app/fake-client.d.ts +3 -0
  112. package/dist/cli/playground/app/fake-client.js +215 -0
  113. package/dist/cli/playground/app/fixtures.d.ts +47 -0
  114. package/dist/cli/playground/app/fixtures.js +472 -0
  115. package/dist/cli/playground/app/main.d.ts +1 -0
  116. package/dist/cli/playground/app/main.js +108 -0
  117. package/dist/cli/playground/app/scenario-mount.d.ts +13 -0
  118. package/dist/cli/playground/app/scenario-mount.js +48 -0
  119. package/dist/cli/playground/app/scenarios.d.ts +25 -0
  120. package/dist/cli/playground/app/scenarios.js +152 -0
  121. package/dist/cli/playground/app/theme-editor.d.ts +13 -0
  122. package/dist/cli/playground/app/theme-editor.js +149 -0
  123. package/dist/cli/playground/app/theme-state.d.ts +29 -0
  124. package/dist/cli/playground/app/theme-state.js +151 -0
  125. package/dist/cli/playground/bundle.gen.d.ts +2 -0
  126. package/dist/cli/playground/bundle.gen.js +2 -0
  127. package/dist/cli/playground/embed-bundle.gen.d.ts +2 -0
  128. package/dist/cli/playground/embed-bundle.gen.js +2 -0
  129. package/dist/cli/playground.d.ts +33 -0
  130. package/dist/cli/playground.js +120 -0
  131. package/dist/cli/pretty.d.ts +48 -0
  132. package/dist/cli/pretty.js +330 -0
  133. package/dist/cli/refine.d.ts +43 -0
  134. package/dist/cli/refine.js +181 -0
  135. package/dist/cli/semantics.d.ts +31 -0
  136. package/dist/cli/semantics.js +122 -0
  137. package/dist/cli/shared.d.ts +68 -0
  138. package/dist/cli/shared.js +184 -0
  139. package/dist/cli/sync.d.ts +36 -0
  140. package/dist/cli/sync.js +202 -0
  141. package/dist/cli/theme/color.d.ts +15 -0
  142. package/dist/cli/theme/color.js +188 -0
  143. package/dist/cli/theme/css-vars.d.ts +13 -0
  144. package/dist/cli/theme/css-vars.js +40 -0
  145. package/dist/cli/theme/entry-candidates.d.ts +5 -0
  146. package/dist/cli/theme/entry-candidates.js +22 -0
  147. package/dist/cli/theme/extract-theme.d.ts +184 -0
  148. package/dist/cli/theme/extract-theme.js +358 -0
  149. package/dist/cli/theme/walk.d.ts +2 -0
  150. package/dist/cli/theme/walk.js +24 -0
  151. package/dist/cli.d.ts +2 -0
  152. package/dist/cli.js +333 -0
  153. package/dist/cloud-apps.d.ts +17 -0
  154. package/dist/cloud-apps.js +46 -0
  155. package/dist/cloud-console.d.ts +32 -0
  156. package/dist/cloud-console.js +72 -0
  157. package/dist/cloud-tools.d.ts +23 -0
  158. package/dist/cloud-tools.js +196 -0
  159. package/dist/connections.d.ts +71 -0
  160. package/dist/connections.js +192 -0
  161. package/dist/deployment-identity.d.ts +12 -0
  162. package/dist/deployment-identity.js +69 -0
  163. package/dist/dev-creds/model.d.ts +65 -0
  164. package/dist/dev-creds/model.js +159 -0
  165. package/dist/dev-creds/resolve.d.ts +36 -0
  166. package/dist/dev-creds/resolve.js +57 -0
  167. package/dist/hosted-store.d.ts +52 -0
  168. package/dist/hosted-store.js +322 -0
  169. package/dist/index.d.ts +13 -0
  170. package/dist/index.js +1 -0
  171. package/dist/mastra.d.ts +46 -0
  172. package/dist/mastra.js +88 -0
  173. package/dist/react.d.ts +10 -0
  174. package/dist/react.js +32 -0
  175. package/dist/refine.d.ts +244 -0
  176. package/dist/refine.js +599 -0
  177. package/dist/remixable.d.ts +18 -0
  178. package/dist/remixable.js +42 -0
  179. package/dist/runtime-capture.d.ts +18 -0
  180. package/dist/runtime-capture.js +111 -0
  181. package/dist/sandbox-wire.d.ts +101 -0
  182. package/dist/sandbox-wire.js +101 -0
  183. package/dist/sandbox.d.ts +38 -0
  184. package/dist/sandbox.js +389 -0
  185. package/dist/server.d.ts +216 -0
  186. package/dist/server.js +1320 -0
  187. package/dist/sync-impact.d.ts +14 -0
  188. package/dist/sync-impact.js +75 -0
  189. package/dist/turn-liveness.d.ts +33 -0
  190. package/dist/turn-liveness.js +105 -0
  191. package/dist/wire/approvals.d.ts +10 -0
  192. package/dist/wire/approvals.js +59 -0
  193. package/dist/wire/apps.d.ts +5 -0
  194. package/dist/wire/apps.js +155 -0
  195. package/dist/wire/automations.d.ts +5 -0
  196. package/dist/wire/automations.js +57 -0
  197. package/dist/wire/box.d.ts +3 -0
  198. package/dist/wire/box.js +268 -0
  199. package/dist/wire/connections.d.ts +5 -0
  200. package/dist/wire/connections.js +47 -0
  201. package/dist/wire/context.d.ts +22 -0
  202. package/dist/wire/context.js +190 -0
  203. package/dist/wire/doctor.d.ts +19 -0
  204. package/dist/wire/doctor.js +116 -0
  205. package/dist/wire/misc.d.ts +18 -0
  206. package/dist/wire/misc.js +227 -0
  207. package/dist/wire/shared.d.ts +158 -0
  208. package/dist/wire/shared.js +134 -0
  209. package/dist/wire/threads.d.ts +3 -0
  210. package/dist/wire/threads.js +68 -0
  211. package/package.json +151 -0
  212. package/skills/vendo-setup/SKILL.md +128 -0
@@ -0,0 +1,42 @@
1
+ import { type AuthMaterial, type PermissionGrant } from "@vendoai/core";
2
+ import type { ConformanceSuite } from "@vendoai/core/conformance";
3
+ import type { HostAuthPreset } from "./shared.js";
4
+ /**
5
+ * 09-vendo §2.1 — executable three-seam checks for a HostAuthPreset: the preset
6
+ * must behave exactly like the hand-written per-seam trio it replaces. Built on
7
+ * the core conformance kit's framework-agnostic shape (`@vendoai/core/conformance`),
8
+ * so every named preset (authJs today; clerk/supabase/auth0/jwt as they land)
9
+ * runs the SAME suite: mount with `for (const c of suite.cases) it(c.name, c.run)`
10
+ * or execute via `runConformance`.
11
+ *
12
+ * The suite asserts host-integrated behavior — the preset under test must be
13
+ * configured with a subject→user resolver (or provider-side equivalent) that
14
+ * knows `knownSubject` and has never issued `unknownSubject`.
15
+ */
16
+ export interface HostAuthPresetConformanceOptions {
17
+ preset: HostAuthPreset;
18
+ /** Build a wire Request carrying a VALID host session for the subject. */
19
+ sessionRequest(subject: string): Request | Promise<Request>;
20
+ /** A request with no host session. Default: a bare GET with no credentials. */
21
+ anonymousRequest?(): Request;
22
+ /** A subject the host knows: sessions resolve, actAs mints, the door resolves. */
23
+ knownSubject: string;
24
+ /** A subject the host never issued: actAs declines, the door's lookup returns null. */
25
+ unknownSubject: string;
26
+ /** When set, principals resolved for `knownSubject` must carry exactly this display. */
27
+ expectedDisplay?: string;
28
+ /** The grant actAs minting is exercised with. Default: a standing tool grant
29
+ for the subject under test. */
30
+ grant?(subject: string): PermissionGrant;
31
+ /** Out-of-band verification for the actAs round-trip case. Cookie-minting
32
+ presets (authJs, supabase, jwt) mint AuthMaterial their own `principal`
33
+ resolver accepts, so the default round-trips through it — but
34
+ producer/verify-split systems (clerk/auth0, 04 §2.1) mint away-tokens whose
35
+ verifier is host-mounted middleware, not the preset. Supply the split
36
+ system's verify half here; return the verified subject, or null when
37
+ verification rejects the material. */
38
+ verifyActAs?(material: AuthMaterial): Promise<string | null> | string | null;
39
+ }
40
+ /** Executable HostAuthPreset checks from 09-vendo §2.1 (plus 01-core §13 for the
41
+ actAs half and 10-mcp §3 for the oauth half). */
42
+ export declare function hostAuthPresetConformance(opts: HostAuthPresetConformanceOptions): ConformanceSuite;
@@ -0,0 +1,139 @@
1
+ import { authMaterialSchema, principalSchema } from "@vendoai/core";
2
+ const assert = (condition, message) => {
3
+ if (!condition)
4
+ throw new Error(message);
5
+ };
6
+ const defaultAnonymousRequest = () => new Request("https://host.conformance.test/api/vendo/threads");
7
+ /** The deployment's public origin, computed exactly the way the presets do:
8
+ the operator-set VENDO_BASE_URL when configured (empty string = unset, the
9
+ umbrella's `environment()` semantics), else the request's own origin. Kept
10
+ env-aware so the redirect case passes on CORRECT preset behavior whether or
11
+ not the suite's runner has VENDO_BASE_URL in its environment. */
12
+ const publicOrigin = (request) => {
13
+ const base = typeof process === "undefined" ? undefined : process.env["VENDO_BASE_URL"];
14
+ if (base !== undefined && base.length > 0) {
15
+ try {
16
+ return new URL(base).origin;
17
+ }
18
+ catch {
19
+ // An unparseable base is an env misconfiguration; fall back to the
20
+ // request origin rather than failing the case on the harness's env.
21
+ }
22
+ }
23
+ return new URL(request.url).origin;
24
+ };
25
+ const defaultGrant = (subject) => ({
26
+ id: "grt_host_auth_preset_conformance",
27
+ subject,
28
+ tool: "host_conformance",
29
+ descriptorHash: "sha256:host-auth-preset-conformance",
30
+ scope: { kind: "tool" },
31
+ duration: "standing",
32
+ source: "automation",
33
+ grantedAt: "2026-07-18T00:00:00.000Z",
34
+ });
35
+ const assertKnownPrincipal = (resolved, opts, seam) => {
36
+ assert(resolved !== null, `${seam} returned null for the known subject`);
37
+ const parsed = principalSchema.safeParse(resolved);
38
+ assert(parsed.success, `${seam} returned an invalid Principal`);
39
+ assert(parsed.data.subject === opts.knownSubject, `${seam} resolved the wrong subject: ${parsed.data.subject}`);
40
+ if (opts.expectedDisplay !== undefined) {
41
+ assert(parsed.data.display === opts.expectedDisplay, `${seam} display was ${String(parsed.data.display)}, expected ${opts.expectedDisplay}`);
42
+ }
43
+ };
44
+ /** Executable HostAuthPreset checks from 09-vendo §2.1 (plus 01-core §13 for the
45
+ actAs half and 10-mcp §3 for the oauth half). */
46
+ export function hostAuthPresetConformance(opts) {
47
+ const anonymousRequest = opts.anonymousRequest ?? defaultAnonymousRequest;
48
+ const grantFor = opts.grant ?? defaultGrant;
49
+ const requireActAs = () => {
50
+ assert(opts.preset.actAs !== undefined, "preset has no actAs half");
51
+ return opts.preset.actAs;
52
+ };
53
+ const requireOauth = () => {
54
+ assert(opts.preset.oauth !== undefined, "preset has no oauth half");
55
+ return opts.preset.oauth;
56
+ };
57
+ return {
58
+ seam: "HostAuthPreset",
59
+ cases: [
60
+ {
61
+ name: "09 §2.1 — principal resolves a live session request to the known subject",
62
+ async run() {
63
+ const resolved = await opts.preset.principal(await opts.sessionRequest(opts.knownSubject));
64
+ assertKnownPrincipal(resolved, opts, "principal");
65
+ },
66
+ },
67
+ {
68
+ name: "09 §2.1 — principal resolves a sessionless request to null (ephemeral anonymous)",
69
+ async run() {
70
+ assert(await opts.preset.principal(anonymousRequest()) === null, "principal did not resolve a sessionless request to null");
71
+ },
72
+ },
73
+ {
74
+ name: "01-core §13 — actAs mints AuthMaterial the preset's verification accepts (round-trip)",
75
+ async run() {
76
+ const material = await requireActAs()({ kind: "user", subject: opts.knownSubject }, grantFor(opts.knownSubject));
77
+ assert(material !== null, "actAs declined the known subject");
78
+ const parsed = authMaterialSchema.safeParse(material);
79
+ assert(parsed.success, "actAs returned invalid AuthMaterial");
80
+ if (opts.verifyActAs !== undefined) {
81
+ // Producer/verify-split systems (clerk/auth0): the mint is real iff
82
+ // the host-mounted verify half accepts it and yields the subject.
83
+ const subject = await opts.verifyActAs(parsed.data);
84
+ assert(subject !== null, "actAs round-trip verification rejected the minted material");
85
+ assert(subject === opts.knownSubject, `actAs round-trip verified the wrong subject: ${subject}`);
86
+ return;
87
+ }
88
+ // The mint is real iff the preset's own session lookup accepts it —
89
+ // the same round-trip the doctor actAs probe drives over the wire.
90
+ const authed = new Request("https://host.conformance.test/api/vendo/doctor/act-as/echo", {
91
+ headers: parsed.data.headers,
92
+ });
93
+ assertKnownPrincipal(await opts.preset.principal(authed), opts, "actAs round-trip principal");
94
+ },
95
+ },
96
+ {
97
+ name: "01-core §13 — actAs declines a subject the host never issued",
98
+ async run() {
99
+ const material = await requireActAs()({ kind: "user", subject: opts.unknownSubject }, grantFor(opts.unknownSubject));
100
+ assert(material === null, "actAs minted material for an unknown subject");
101
+ },
102
+ },
103
+ {
104
+ name: "10-mcp §3 — oauth.session returns the subject for a live session",
105
+ async run() {
106
+ const returnTo = "https://host.conformance.test/api/vendo/mcp/authorize?state=xyz";
107
+ const result = await requireOauth().session?.(await opts.sessionRequest(opts.knownSubject), { returnTo });
108
+ assert(result !== undefined, "preset oauth half has no session lookup");
109
+ assert(!(result instanceof Response), "oauth.session redirected a live session");
110
+ assert(result.subject === opts.knownSubject, `oauth.session resolved the wrong subject: ${result.subject}`);
111
+ },
112
+ },
113
+ {
114
+ name: "10-mcp §3 — oauth.session redirects a sessionless request to login carrying returnTo",
115
+ async run() {
116
+ const request = anonymousRequest();
117
+ const returnTo = "https://host.conformance.test/api/vendo/mcp/authorize?state=xyz";
118
+ const result = await requireOauth().session?.(request, { returnTo });
119
+ assert(result !== undefined, "preset oauth half has no session lookup");
120
+ assert(result instanceof Response, "oauth.session did not redirect a sessionless request");
121
+ assert(result.status >= 300 && result.status < 400, `oauth.session returned status ${result.status}, not a redirect`);
122
+ const location = result.headers.get("location");
123
+ assert(location !== null, "oauth.session redirect carries no location");
124
+ const target = new URL(location);
125
+ assert(target.origin === publicOrigin(request), `oauth.session redirected off the deployment's public origin: ${target.origin}`);
126
+ assert(target.searchParams.get("returnTo") === returnTo, "oauth.session redirect does not carry the returnTo that resumes authorization");
127
+ },
128
+ },
129
+ {
130
+ name: "10-mcp §3 — oauth.principal resolves the known subject and returns null for an unknown one",
131
+ async run() {
132
+ const oauth = requireOauth();
133
+ assertKnownPrincipal(await oauth.principal(opts.knownSubject), opts, "oauth.principal");
134
+ assert(await oauth.principal(opts.unknownSubject) === null, "oauth.principal resolved a subject the host never issued");
135
+ },
136
+ },
137
+ ],
138
+ };
139
+ }
@@ -0,0 +1,56 @@
1
+ import type { SecretSource } from "@vendoai/actions/presets";
2
+ import type { ActAs, PermissionGrant, Principal } from "@vendoai/core";
3
+ import type { HostAuthPreset, HostAuthPresetUser, HostAuthPresetUserResolver } from "./shared.js";
4
+ /** Internal machinery shared by the named host-identity presets (09 §2.1).
5
+ authJs (the template preset) predates this file and keeps its own copy of
6
+ the same moves; the public surface stays the preset functions themselves. */
7
+ export type JwtClaims = Record<string, unknown>;
8
+ export declare function claimString(claims: JwtClaims, key: string): string | undefined;
9
+ /** The presets' default claims→user mapping: display from the `name` claim,
10
+ falling back to `email`; email from `email` (feeds actAs claims only). */
11
+ export declare function userFromNameEmailClaims(claims: JwtClaims): HostAuthPresetUser;
12
+ /** One identity lookup for all three seams: the host's subject→user resolver
13
+ when configured (null = subject unknown → decline), else the system's
14
+ claims-derived defaults. `claims` is {} where no token exists (actAs
15
+ minting, the door's subject lookup). */
16
+ export declare function makeUserResolver(user: HostAuthPresetUserResolver | undefined, defaults: (claims: JwtClaims) => HostAuthPresetUser): (subject: string, claims: JwtClaims) => Promise<HostAuthPresetUser | null>;
17
+ /** Bridge the subject→user resolver into an actions-preset claims resolver:
18
+ null still declines the mint; display/email become session claims. */
19
+ export declare function actAsClaimsFromUser(user: HostAuthPresetUserResolver, toClaims?: (resolved: HostAuthPresetUser) => JwtClaims): (principal: Principal, grant: PermissionGrant) => Promise<JwtClaims | null>;
20
+ /** Per-call secret resolution (authJs's resolveAuthSecret pattern): default to
21
+ the system's own env variable, resolved lazily so composition order never
22
+ races env loading; absence fails loud with the fix in hand. */
23
+ export declare function resolvePresetSecret(source: SecretSource | undefined, environmentName: string | undefined, missingMessage: string): Promise<string>;
24
+ /** The operator-set public origin (VENDO_BASE_URL) or, failing that, the
25
+ request's own origin — mirrors authJs and how the door derives its URLs. */
26
+ export declare function publicOrigin(request: Request): URL;
27
+ export declare function requestCookies(request: Request): [name: string, value: string][];
28
+ export declare function cookieValue(request: Request, name: string): string | undefined;
29
+ export declare function bearerToken(request: Request): string | undefined;
30
+ /** Optional-SDK loader mirroring authJs's loadGetToken: cached on success,
31
+ reset on failure so a later call retries after an install, and an import
32
+ failure surfaces the actionable install instruction, never a bare
33
+ module-not-found. */
34
+ export declare function lazyModule<T>(load: () => Promise<T>, missingMessage: string): () => Promise<T>;
35
+ /** ActAs built lazily on FIRST MINT and cached (authJs's pattern, its
36
+ TokenCache surviving across calls): env-dependent secrets and posture
37
+ resolve at use time, never racing env loading at composition. */
38
+ export declare function lazyActAs(build: () => ActAs): ActAs;
39
+ /** /login?returnTo= on the deployment's public origin (authJs parity). Systems
40
+ whose own convention demands it pass a different path (Clerk's /sign-in,
41
+ Auth0's /auth/login) or extra params — never a new preset option. */
42
+ export declare function loginRedirect(request: Request, returnTo: string, path?: string, extraParams?: Record<string, string>): Response;
43
+ export interface ComposeHostAuthPresetOptions {
44
+ /** Decode + verify the request's host session; null = no/invalid session. */
45
+ sessionClaims(request: Request): Promise<JwtClaims | null>;
46
+ /** One identity lookup for all three seams ({} claims where none exist). */
47
+ resolveUser(subject: string, claims: JwtClaims): Promise<HostAuthPresetUser | null>;
48
+ actAs: ActAs;
49
+ /** The sessionless-door redirect (10-mcp §3). */
50
+ login(request: Request, returnTo: string): Response;
51
+ }
52
+ /** The three-seam shape every named preset shares (authJs is the template):
53
+ only session decoding, identity defaults, the mint, and the login target
54
+ differ per system. The door's oauth half owns consent/CSRF/replay; this
55
+ only supplies session lookup + subject resolution (10-mcp §3). */
56
+ export declare function composeHostAuthPreset(opts: ComposeHostAuthPresetOptions): HostAuthPreset;
@@ -0,0 +1,152 @@
1
+ import { environment } from "../wire/shared.js";
2
+ export function claimString(claims, key) {
3
+ const value = claims[key];
4
+ return typeof value === "string" && value.length > 0 ? value : undefined;
5
+ }
6
+ /** The presets' default claims→user mapping: display from the `name` claim,
7
+ falling back to `email`; email from `email` (feeds actAs claims only). */
8
+ export function userFromNameEmailClaims(claims) {
9
+ const email = claimString(claims, "email");
10
+ const display = claimString(claims, "name") ?? email;
11
+ return {
12
+ ...(display === undefined ? {} : { display }),
13
+ ...(email === undefined ? {} : { email }),
14
+ };
15
+ }
16
+ /** One identity lookup for all three seams: the host's subject→user resolver
17
+ when configured (null = subject unknown → decline), else the system's
18
+ claims-derived defaults. `claims` is {} where no token exists (actAs
19
+ minting, the door's subject lookup). */
20
+ export function makeUserResolver(user, defaults) {
21
+ return async (subject, claims) => (user !== undefined ? user(subject, claims) : defaults(claims));
22
+ }
23
+ /** Bridge the subject→user resolver into an actions-preset claims resolver:
24
+ null still declines the mint; display/email become session claims. */
25
+ export function actAsClaimsFromUser(user, toClaims = (resolved) => ({
26
+ ...(resolved.display === undefined ? {} : { name: resolved.display }),
27
+ ...(resolved.email === undefined ? {} : { email: resolved.email }),
28
+ })) {
29
+ return async (principal) => {
30
+ const resolved = await user(principal.subject, {});
31
+ return resolved === null ? null : toClaims(resolved);
32
+ };
33
+ }
34
+ /** Per-call secret resolution (authJs's resolveAuthSecret pattern): default to
35
+ the system's own env variable, resolved lazily so composition order never
36
+ races env loading; absence fails loud with the fix in hand. */
37
+ export async function resolvePresetSecret(source, environmentName, missingMessage) {
38
+ const value = source === undefined
39
+ ? (environmentName === undefined ? undefined : environment(environmentName))
40
+ : typeof source === "function"
41
+ ? await source()
42
+ : source;
43
+ if (value === undefined || value.length === 0) {
44
+ throw new Error(missingMessage);
45
+ }
46
+ return value;
47
+ }
48
+ /** The operator-set public origin (VENDO_BASE_URL) or, failing that, the
49
+ request's own origin — mirrors authJs and how the door derives its URLs. */
50
+ export function publicOrigin(request) {
51
+ return new URL(environment("VENDO_BASE_URL") ?? request.url);
52
+ }
53
+ export function requestCookies(request) {
54
+ const header = request.headers.get("cookie");
55
+ if (header === null)
56
+ return [];
57
+ const entries = [];
58
+ for (const part of header.split(";")) {
59
+ const separator = part.indexOf("=");
60
+ if (separator === -1)
61
+ continue;
62
+ const name = part.slice(0, separator).trim();
63
+ if (name.length === 0)
64
+ continue;
65
+ let value = part.slice(separator + 1).trim();
66
+ try {
67
+ value = decodeURIComponent(value);
68
+ }
69
+ catch {
70
+ // Not URI-encoded — keep the raw value.
71
+ }
72
+ entries.push([name, value]);
73
+ }
74
+ return entries;
75
+ }
76
+ export function cookieValue(request, name) {
77
+ return requestCookies(request).find(([cookieName]) => cookieName === name)?.[1];
78
+ }
79
+ export function bearerToken(request) {
80
+ const header = request.headers.get("authorization");
81
+ const match = header === null ? null : /^Bearer\s+(\S+)$/i.exec(header.trim());
82
+ return match?.[1];
83
+ }
84
+ /** Optional-SDK loader mirroring authJs's loadGetToken: cached on success,
85
+ reset on failure so a later call retries after an install, and an import
86
+ failure surfaces the actionable install instruction, never a bare
87
+ module-not-found. */
88
+ export function lazyModule(load, missingMessage) {
89
+ let cached;
90
+ return () => {
91
+ cached ??= load().then((loaded) => loaded, (cause) => {
92
+ cached = undefined; // let a later call retry after an install
93
+ throw new Error(missingMessage, { cause: cause });
94
+ });
95
+ return cached;
96
+ };
97
+ }
98
+ /** ActAs built lazily on FIRST MINT and cached (authJs's pattern, its
99
+ TokenCache surviving across calls): env-dependent secrets and posture
100
+ resolve at use time, never racing env loading at composition. */
101
+ export function lazyActAs(build) {
102
+ let mint;
103
+ return async (principal, grant) => (mint ??= build())(principal, grant);
104
+ }
105
+ /** /login?returnTo= on the deployment's public origin (authJs parity). Systems
106
+ whose own convention demands it pass a different path (Clerk's /sign-in,
107
+ Auth0's /auth/login) or extra params — never a new preset option. */
108
+ export function loginRedirect(request, returnTo, path = "/login", extraParams = {}) {
109
+ const login = new URL(path, publicOrigin(request));
110
+ login.searchParams.set("returnTo", returnTo);
111
+ for (const [name, value] of Object.entries(extraParams)) {
112
+ login.searchParams.set(name, value);
113
+ }
114
+ return Response.redirect(login);
115
+ }
116
+ /** The three-seam shape every named preset shares (authJs is the template):
117
+ only session decoding, identity defaults, the mint, and the login target
118
+ differ per system. The door's oauth half owns consent/CSRF/replay; this
119
+ only supplies session lookup + subject resolution (10-mcp §3). */
120
+ export function composeHostAuthPreset(opts) {
121
+ const principalFor = async (subject, claims) => {
122
+ const resolved = await opts.resolveUser(subject, claims);
123
+ if (resolved === null)
124
+ return null;
125
+ return {
126
+ kind: "user",
127
+ subject,
128
+ ...(resolved.display === undefined ? {} : { display: resolved.display }),
129
+ };
130
+ };
131
+ const principal = async (request) => {
132
+ const claims = await opts.sessionClaims(request);
133
+ if (claims === null)
134
+ return null;
135
+ const subject = claimString(claims, "sub");
136
+ return subject === undefined ? null : principalFor(subject, claims);
137
+ };
138
+ const oauth = {
139
+ async session(request, { returnTo }) {
140
+ const claims = await opts.sessionClaims(request);
141
+ const subject = claims === null ? undefined : claimString(claims, "sub");
142
+ if (subject !== undefined && claims !== null && await opts.resolveUser(subject, claims) !== null) {
143
+ return { subject };
144
+ }
145
+ return opts.login(request, returnTo);
146
+ },
147
+ async principal(subject) {
148
+ return principalFor(subject, {});
149
+ },
150
+ };
151
+ return { principal, actAs: opts.actAs, oauth };
152
+ }
@@ -0,0 +1,14 @@
1
+ /** 09-vendo §2.1 — host-identity presets: one config key (`createVendo({ auth })`)
2
+ filling the principal, actAs, and oauth seams from a single identity story.
3
+ The five zero-arg preset FUNCTIONS do NOT ship from this barrel — each has
4
+ its own subpath (@vendoai/vendo/auth/<name>) instead. A bundler resolves
5
+ every `export ... from` target in a barrel file regardless of which named
6
+ export a consumer actually uses, so combining all five here (as before
7
+ corpus-triage Task 9) meant any host importing even one preset — or none —
8
+ forced every preset's optional peer dep (jose/@auth/core/@clerk/backend) to
9
+ resolve, breaking builds for hosts lacking one. Only the conformance kit and
10
+ the shared types (erased before any bundler sees them) stay on this barrel;
11
+ server.ts re-exports both from here unchanged. */
12
+ export { hostAuthPresetConformance, type HostAuthPresetConformanceOptions, } from "./conformance.js";
13
+ export type { HostAuthPreset, HostAuthPresetOptions, HostAuthPresetUser, HostAuthPresetUserResolver, } from "./shared.js";
14
+ export type { SupabaseHostAuthPresetOptions } from "./supabase.js";
@@ -0,0 +1,12 @@
1
+ /** 09-vendo §2.1 — host-identity presets: one config key (`createVendo({ auth })`)
2
+ filling the principal, actAs, and oauth seams from a single identity story.
3
+ The five zero-arg preset FUNCTIONS do NOT ship from this barrel — each has
4
+ its own subpath (@vendoai/vendo/auth/<name>) instead. A bundler resolves
5
+ every `export ... from` target in a barrel file regardless of which named
6
+ export a consumer actually uses, so combining all five here (as before
7
+ corpus-triage Task 9) meant any host importing even one preset — or none —
8
+ forced every preset's optional peer dep (jose/@auth/core/@clerk/backend) to
9
+ resolve, breaking builds for hosts lacking one. Only the conformance kit and
10
+ the shared types (erased before any bundler sees them) stay on this barrel;
11
+ server.ts re-exports both from here unchanged. */
12
+ export { hostAuthPresetConformance, } from "./conformance.js";
@@ -0,0 +1,16 @@
1
+ import type { HostAuthPreset, HostAuthPresetOptions } from "./shared.js";
2
+ /**
3
+ * 09-vendo §2.1 — the host-generic JWT host-identity preset, pairing the
4
+ * docs' generic recipe (docs/act-as-presets.md) with a matching session
5
+ * resolver: the host API's own HS256 bearer JWT, verified with the same
6
+ * shared secret the actAs half (`genericJwtPreset`, 04 §2.1) signs with.
7
+ *
8
+ * Unlike the vendor presets this one is NOT zero-argument by nature: there is
9
+ * no vendor-owned env variable a generic scheme could read (AUTH_SECRET
10
+ * belongs to Auth.js, SUPABASE_JWT_SECRET to Supabase — a host's own secret
11
+ * name is unknowable), so `secret` is required and construction fails loud
12
+ * without it. Sessions arrive as `Authorization: Bearer <jwt>`; display
13
+ * derives from name/email claims; the optional subject→user resolver has the
14
+ * same semantics as authJs (null = subject unknown → decline/null).
15
+ */
16
+ export declare function jwt(options?: HostAuthPresetOptions): HostAuthPreset;
@@ -0,0 +1,54 @@
1
+ import { genericJwtPreset, verifyHs256 } from "@vendoai/actions/presets";
2
+ import { actAsClaimsFromUser, bearerToken, composeHostAuthPreset, lazyActAs, loginRedirect, makeUserResolver, userFromNameEmailClaims, } from "./identity.js";
3
+ const MISSING_SECRET_OPTION_MESSAGE = "jwt() is not zero-argument: a host-generic JWT scheme has no vendor-owned env variable to read. Pass jwt({ secret }) — e.g. jwt({ secret: () => process.env.HOST_API_JWT_SECRET }).";
4
+ const EMPTY_SECRET_MESSAGE = "jwt() has no session secret: the { secret } source resolved empty.";
5
+ /**
6
+ * 09-vendo §2.1 — the host-generic JWT host-identity preset, pairing the
7
+ * docs' generic recipe (docs/act-as-presets.md) with a matching session
8
+ * resolver: the host API's own HS256 bearer JWT, verified with the same
9
+ * shared secret the actAs half (`genericJwtPreset`, 04 §2.1) signs with.
10
+ *
11
+ * Unlike the vendor presets this one is NOT zero-argument by nature: there is
12
+ * no vendor-owned env variable a generic scheme could read (AUTH_SECRET
13
+ * belongs to Auth.js, SUPABASE_JWT_SECRET to Supabase — a host's own secret
14
+ * name is unknowable), so `secret` is required and construction fails loud
15
+ * without it. Sessions arrive as `Authorization: Bearer <jwt>`; display
16
+ * derives from name/email claims; the optional subject→user resolver has the
17
+ * same semantics as authJs (null = subject unknown → decline/null).
18
+ */
19
+ export function jwt(options = {}) {
20
+ const { secret, user } = options;
21
+ if (secret === undefined) {
22
+ throw new Error(MISSING_SECRET_OPTION_MESSAGE);
23
+ }
24
+ const resolveSecret = async () => {
25
+ const value = typeof secret === "function" ? await secret() : secret;
26
+ if (value === undefined || value.length === 0) {
27
+ throw new Error(EMPTY_SECRET_MESSAGE);
28
+ }
29
+ return value;
30
+ };
31
+ const sessionClaims = async (request) => {
32
+ const token = bearerToken(request);
33
+ if (token === undefined)
34
+ return null;
35
+ const resolved = await resolveSecret();
36
+ try {
37
+ return (await verifyHs256(token, resolved)).payload;
38
+ }
39
+ catch {
40
+ return null; // unverifiable/expired token = no session, mirroring authJs
41
+ }
42
+ };
43
+ return composeHostAuthPreset({
44
+ sessionClaims,
45
+ resolveUser: makeUserResolver(user, userFromNameEmailClaims),
46
+ // Away + MCP execution: the shipped generic HS256 minting preset (04
47
+ // §2.1), fed the same secret this preset verifies sessions with.
48
+ actAs: lazyActAs(() => genericJwtPreset({
49
+ secret,
50
+ ...(user === undefined ? {} : { claims: actAsClaimsFromUser(user) }),
51
+ })),
52
+ login: (request, returnTo) => loginRedirect(request, returnTo),
53
+ });
54
+ }
@@ -0,0 +1,35 @@
1
+ import type { SecretSource } from "@vendoai/actions/presets";
2
+ import type { ActAs, Principal } from "@vendoai/core";
3
+ import type { HostOAuthAdapter } from "@vendoai/mcp";
4
+ /** 09-vendo §2.1 — one host-identity story, three seams. A HostAuthPreset fills
5
+ the request→Principal resolver, the away/MCP actAs seam, and the door's
6
+ HostOAuthAdapter from one config key. Passed as `createVendo({ auth })`;
7
+ mutually exclusive with the per-seam `principal`/`actAs`/`oauth` trio. */
8
+ export interface HostAuthPreset {
9
+ principal: (req: Request) => Promise<Principal | null>;
10
+ /** Absent → away/MCP execution cleanly unavailable, as ever (01-core §13). */
11
+ actAs?: ActAs;
12
+ /** Absent → the MCP door cannot open (`mcp: true` still requires an adapter, 09 §2). */
13
+ oauth?: HostOAuthAdapter;
14
+ }
15
+ /** What a host's subject→user resolver returns. `display` names the resolved
16
+ Principal; `email` only feeds actAs session claims (Principal has no email). */
17
+ export interface HostAuthPresetUser {
18
+ display?: string;
19
+ email?: string;
20
+ }
21
+ /** Optional subject→user resolver for custom logic (09 §2.1). `claims` carries
22
+ the decoded session-token claims where a token exists ({} where none does —
23
+ actAs minting and the door's subject lookup). Returning null means "subject
24
+ unknown to host": the principal resolver treats the session as absent, actAs
25
+ declines the mint, and the door's principal lookup returns null. */
26
+ export type HostAuthPresetUserResolver = (subject: string, claims: Record<string, unknown>) => HostAuthPresetUser | null | Promise<HostAuthPresetUser | null>;
27
+ export interface HostAuthPresetOptions {
28
+ /** The preset's shared session secret (or system-equivalent). Default: the
29
+ provider's own env variable — AUTH_SECRET for Auth.js, SUPABASE_JWT_SECRET
30
+ for Supabase, VENDO_AWAY_TOKEN_SECRET (the away-token secret) for
31
+ Clerk/Auth0 — resolved lazily per call so composition order never races
32
+ env loading. jwt() has no vendor env to read: its secret is required. */
33
+ secret?: SecretSource;
34
+ user?: HostAuthPresetUserResolver;
35
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ import type { HostAuthPreset, HostAuthPresetOptions } from "./shared.js";
2
+ export interface SupabaseHostAuthPresetOptions extends HostAuthPresetOptions {
3
+ /** GoTrue's JWKS URL for ES256 session verification. Default: derived from
4
+ SUPABASE_URL as `<url>/auth/v1/.well-known/jwks.json` (GoTrue's own
5
+ well-known path); pass this to point somewhere else. The thunk form
6
+ resolves lazily per call (the SecretSource pattern), so composition
7
+ order never races env loading. */
8
+ jwks?: string | URL | (() => string | URL | undefined);
9
+ }
10
+ /**
11
+ * 09-vendo §2.1 — the Supabase Auth host-identity preset. Zero-argument in
12
+ * the standard case: it reads Supabase's own env, the session resolves off a
13
+ * plain Request per Supabase's own formats (Authorization: Bearer, or the
14
+ * `sb-*-auth-token` cookie — @supabase/ssr's `base64-`/chunked shape, the
15
+ * legacy JSON shapes, and the raw access token), and display derives from
16
+ * user_metadata.name/full_name/email. The optional subject→user resolver has
17
+ * the same semantics as authJs (null = subject unknown → decline/null).
18
+ *
19
+ * Session verification is Supabase's documented HYBRID (the same one a
20
+ * project with JWT signing keys needs), routed by the token's own `alg`:
21
+ *
22
+ * 1. HS256 first — the project's legacy JWT secret (SUPABASE_JWT_SECRET or
23
+ * `secret`), verified OFFLINE through the SAME shared `verifyHs256` the
24
+ * minting half targets: no network, no optional SDK, and every actAs-minted
25
+ * away token stays verifiable with no Supabase stack running.
26
+ * 2. ES256 fallback — what `supabase start` ≥ v2.71 and hosted projects on
27
+ * the new key system sign interactive logins with, verified against
28
+ * GoTrue's JWKS (SUPABASE_URL → /auth/v1/.well-known/jwks.json, or the
29
+ * `jwks` option) through a lazily-imported jose (optional peer, cached
30
+ * remote key set per URL).
31
+ *
32
+ * Both paths enforce Supabase's `authenticated` audience, which is also what
33
+ * keeps the project's API keys (anon/service_role — HS256 JWTs under the same
34
+ * secret, but without that audience) from ever counting as a signed-in user.
35
+ * Tokens neither path can verify resolve to null (no session); construction
36
+ * with NEITHER a secret NOR a JWKS source fails loud, naming both.
37
+ *
38
+ * The actAs half IS the shipped `@vendoai/actions/presets` supabasePreset —
39
+ * one minting story (04 §2.1), configured from these same options. Tokens
40
+ * mint (and verify) under Supabase's `authenticated` audience convention.
41
+ */
42
+ export declare function supabase(options?: SupabaseHostAuthPresetOptions): HostAuthPreset;