import type { AuthWire } from "./init-auth.js"; import { type ScaffoldModel } from "./init-scaffolds.js"; /** Which authorization server fronts the door. Init no longer ASKS this — a Cloud key answers both environments at once, and the runtime resolves which one it is per environment (compose-mcp.ts) — so it arrives only as the `--posture` flag, for a host that wants a Cloud-fronted-only door. Init still never discovers a posture and never reaches a broker to find out. */ export type McpPosture = "local" | "broker"; export interface McpPlanInput { root: string; /** The host's app directory (`app` or `src/app`), already resolved. */ appDir: string; /** The composition module the wire route already imports (`lib/vendo.ts`), absolute — this path CHANGES what it holds rather than gaining a second composition next to the route, and the discovery route imports the same one. Resolved by the caller: this module stays fs-free. */ composition: string; /** How the discovery route reaches it (`compositionSpecifier`). */ compositionSpecifier: string; framework: "next" | "express" | "custom"; /** * What the fresh composition wired, or null. `mcp: true` is written ONLY * when this is non-null: the door mints its own principals through a * `HostOAuthAdapter` and composition THROWS without one (compose-mcp.ts:77-82). * Every preset carries the oauth half — `jwt()` composes through the same * `composeHostAuthPreset` the vendor ones do (auth-presets/identity.ts:228-246) * — and so does the hand-written seam, whose whole point is that `oauth`. * Only "none yet" surfaces here as null, and only it is refused. */ authWired: AuthWire | null; /** * Does the composition ALREADY on disk wire one of those presets? A re-run * over an existing composition asks no auth question — init never rewrites a * file it did not author — so `authWired` is null even for a host whose * `lib/vendo.ts` says `auth: authJs()`, and refusing on that alone told a * correctly wired host to "wire an auth preset" and wrote no door at all. * Resolved by the caller (`composedAuthPreset`); this module stays fs-free. * * Never true at the same time as a non-null `authWired`: init only decides * auth for a composition it is CREATING, and then there is nothing on disk. */ authAlreadyWired?: boolean; /** Does the host have a live `"use server"` surface? The composition imports the generated registration map when it does. The map itself stays the caller's to plan: an EXISTING one is compared by the keys it registers, which a pure planner cannot read. */ serverActions: boolean; posture: McpPosture; /** Wire the dev sign-in key. Not a question any more: a local door gets one by default, because `.env.local` is dev-only and the deployment — which never sees the variable — takes the Cloud broker instead (compose-mcp.ts). */ serviceKey: boolean; /** A well-formed `VENDO_SERVICE_KEY` already in the host's env files, to REUSE. Minting one unconditionally rotated the secret on every re-run and the caller then overwrote .env.local with it, so every backend already exchanging the old key started failing — the same reuse-don't-remint rule `VENDO_API_KEY` follows. Read by the caller; this module stays fs-free. */ existingServiceKey?: string; /** The provider key init found in the environment. This path's composition module is the ONLY place it may land: the thin route composes nothing, so writing it there too would be a second, dead selection. */ models?: ScaffoldModel | null; } /** A file the MCP path creates. Always new — `before` is null for every one of them — so the caller renders the diff it already knows how to render. */ export interface McpChange { absolute: string; /** Root-relative, posix-style: the path the summary prints. */ path: string; after: string; } export interface McpPlan { /** The one file the MCP path ADDS: the origin-root discovery route. */ changes: McpChange[]; /** The composition module's body, with the door opened. Separate from `changes` because the composition is a file the caller may already have on disk, and a pure planner cannot know that — the caller pushes it with the `before` it already read. Null when the plan is `blocked`. */ compositionSource: string | null; /** * The service key the composition wires, for the caller to write to * `.env.local` — `existingServiceKey` when the host already has a well-formed * one, else freshly minted. * Present on local posture with a yes, and NOWHERE else. `serviceAuth` is * local-door mechanics: the RFC 8693 exchange lives at the door's own * `/token`, which a broker-fronted door does not serve — and an explicit * local `serviceAuth` is host config that beats the env default, so * generating one under broker posture would quietly hold the door LOCAL * against the posture the user just chose (compose-mcp.ts:98-113). */ serviceKeyValue?: string; /** The provider and file of the `models` line this plan wrote — the composition module, never the route it replaced. The caller's closing summary names this file, so it can never point a reader at a route that holds nothing. Null when no provider key resolved or the plan is `blocked`. */ modelWritten: { provider: ScaffoldModel["provider"]; path: string; } | null; /** Why nothing was written. Set means the other fields are empty. */ blocked?: string; /** …and whether the RUN may continue. An auth-less door is the use case itself failing — the user asked for MCP, there is no door, and exiting 0 is how init used to say "Wired" over an install that answered nothing they came for. A non-Next host is a different shape: its whole install still lands and only the door is hand-work, so that one stays advisory. */ blockedFatal?: true; } /** The recipe the auth refusal prints: the seam `--auth custom` would have written, so a reader who does not want a vendor preset can paste it into the composition they already have instead of re-running anything. Rendered from the SAME function init scaffolds with — one copy, or the printed recipe drifts from the written one. */ export declare const OWN_SEAM_RECIPE: string; /** Why a service key cannot ride the broker posture, in the ONE voice both refusals speak: `cli.ts` catches the flag pair it can read off argv, and init catches a posture chosen at the select, which never reaches argv. The lead-in differs because the user did two different things; the explanation and the way out must not. */ export declare const SERVICE_KEY_ON_BROKER: string; /** Why a Cloud-fronted door cannot stand on an http origin. Cloud registers `VENDO_BASE_URL` as the tenant's forwarding address and refuses one that is not https, so the two answers used to pass init, print `Wired`, and leave a door that died on its first request. Same shape as the refusal above: the lead-in names the origin, this says why and how out. */ export declare const BROKER_NEEDS_HTTPS: string; /** A fresh service key: 32 random bytes, hex. `planMcp` mints one itself when the answers call for it AND the host has none to reuse; this is separately callable so the shape can be asserted without a plan. */ export declare function generateServiceKey(): string; /** Is the value already in the host's env a key this door can exchange? The shape `generateServiceKey` mints, and nothing else: anything other than 32 hex bytes is not reusable, so it is replaced rather than trusted. */ export declare function wellFormedServiceKey(value: string | null | undefined): boolean; /** * The origin-root discovery route (`app/.well-known/[...vendo]/route.ts`). * * A two-line body over the SAME instance the wire route serves: * `wellKnownVendoHandler` resolves its path set by instance identity * (server.ts:447-452), so a second `createVendo` call in this file would answer * 404 on every well-known path — which is precisely the bug this route exists * to prevent. */ export declare function wellKnownRouteSource(specifier: string): string; export declare function planMcp(input: McpPlanInput): McpPlan;