eve 0.15.5 → 0.16.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 (107) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/src/cli/commands/init.js +1 -1
  3. package/dist/src/cli/dev/tui/agent-header.js +1 -1
  4. package/dist/src/cli/dev/tui/blocks.js +8 -8
  5. package/dist/src/cli/dev/tui/prompt-command-handler.js +1 -1
  6. package/dist/src/cli/dev/tui/prompt-commands.d.ts +1 -1
  7. package/dist/src/cli/dev/tui/prompt-commands.js +1 -1
  8. package/dist/src/cli/dev/tui/runner.d.ts +10 -3
  9. package/dist/src/cli/dev/tui/runner.js +1 -1
  10. package/dist/src/cli/dev/tui/setup-commands.d.ts +9 -1
  11. package/dist/src/cli/dev/tui/setup-commands.js +2 -2
  12. package/dist/src/cli/dev/tui/setup-flow.d.ts +8 -1
  13. package/dist/src/cli/dev/tui/setup-issues.d.ts +2 -14
  14. package/dist/src/cli/dev/tui/setup-issues.js +1 -1
  15. package/dist/src/cli/dev/tui/setup-panel.d.ts +14 -8
  16. package/dist/src/cli/dev/tui/setup-panel.js +3 -3
  17. package/dist/src/cli/dev/tui/terminal-renderer.js +6 -6
  18. package/dist/src/cli/dev/tui/tui-prompter.js +1 -1
  19. package/dist/src/cli/dev/tui/tui.d.ts +2 -3
  20. package/dist/src/cli/dev/tui/tui.js +1 -1
  21. package/dist/src/cli/dev/ui-options.d.ts +21 -0
  22. package/dist/src/cli/dev/ui-options.js +1 -0
  23. package/dist/src/cli/run.d.ts +6 -42
  24. package/dist/src/cli/run.js +2 -2
  25. package/dist/src/compiled/.vendor-stamp.json +1 -1
  26. package/dist/src/compiled/@ai-sdk/mcp/index.d.ts +71 -1
  27. package/dist/src/compiled/@ai-sdk/mcp/index.js +1 -1
  28. package/dist/src/harness/emission.d.ts +3 -3
  29. package/dist/src/harness/emission.js +1 -1
  30. package/dist/src/harness/stream-actions.d.ts +17 -0
  31. package/dist/src/harness/stream-actions.js +1 -0
  32. package/dist/src/internal/application/package.js +1 -1
  33. package/dist/src/internal/nitro/host/configure-nitro-routes.js +2 -2
  34. package/dist/src/internal/nitro/host/dev-authored-source-watcher.d.ts +1 -0
  35. package/dist/src/internal/nitro/host/dev-authored-source-watcher.js +1 -1
  36. package/dist/src/internal/nitro/host/start-development-server.d.ts +8 -0
  37. package/dist/src/internal/nitro/host/start-development-server.js +2 -2
  38. package/dist/src/internal/nitro/host.d.ts +1 -1
  39. package/dist/src/internal/nitro/host.js +1 -1
  40. package/dist/src/internal/nitro/routes/channel-dispatch.js +1 -1
  41. package/dist/src/internal/nitro/routes/dev-runtime-artifacts.d.ts +0 -3
  42. package/dist/src/internal/nitro/routes/dev-runtime-artifacts.js +1 -1
  43. package/dist/src/internal/nitro/routes/info.js +1 -1
  44. package/dist/src/internal/vercel/project-link.d.ts +10 -0
  45. package/dist/src/internal/vercel/project-link.js +1 -0
  46. package/dist/src/public/channels/auth.d.ts +26 -13
  47. package/dist/src/public/channels/auth.js +1 -1
  48. package/dist/src/runtime/agent/mock-model-adapter.d.ts +3 -2
  49. package/dist/src/runtime/agent/mock-model-adapter.js +1 -1
  50. package/dist/src/runtime/connections/principal.d.ts +2 -0
  51. package/dist/src/runtime/connections/principal.js +1 -1
  52. package/dist/src/runtime/connections/types.d.ts +4 -1
  53. package/dist/src/runtime/framework-channels/index.d.ts +1 -6
  54. package/dist/src/runtime/framework-channels/index.js +1 -1
  55. package/dist/src/runtime/governance/auth/oidc.js +1 -1
  56. package/dist/src/runtime/governance/auth/types.d.ts +14 -10
  57. package/dist/src/runtime/governance/auth/vercel-oidc-project.d.ts +12 -0
  58. package/dist/src/runtime/governance/auth/vercel-oidc-project.js +1 -0
  59. package/dist/src/services/dev-client/client-options.d.ts +5 -0
  60. package/dist/src/services/dev-client/client-options.js +1 -1
  61. package/dist/src/services/dev-client/request-headers.d.ts +8 -0
  62. package/dist/src/services/dev-client/request-headers.js +1 -1
  63. package/dist/src/services/dev-client/runtime-artifacts.d.ts +1 -0
  64. package/dist/src/services/dev-client/runtime-artifacts.js +1 -1
  65. package/dist/src/services/dev-client.d.ts +8 -0
  66. package/dist/src/services/dev-client.js +1 -1
  67. package/dist/src/setup/boxes/add-connections.d.ts +10 -8
  68. package/dist/src/setup/boxes/add-connections.js +1 -1
  69. package/dist/src/setup/boxes/resolve-provisioning.d.ts +1 -0
  70. package/dist/src/setup/boxes/resolve-provisioning.js +1 -1
  71. package/dist/src/setup/cli/channel-setup-prompter.d.ts +9 -2
  72. package/dist/src/setup/cli/channel-setup-prompter.js +1 -1
  73. package/dist/src/setup/cli/index.d.ts +1 -1
  74. package/dist/src/setup/cli/prompt-ui.d.ts +2 -0
  75. package/dist/src/setup/cli/select-state.js +1 -1
  76. package/dist/src/setup/connection-connector.d.ts +26 -52
  77. package/dist/src/setup/connection-connector.js +2 -1
  78. package/dist/src/setup/flows/channels.js +1 -1
  79. package/dist/src/setup/flows/connections.d.ts +36 -0
  80. package/dist/src/setup/flows/connections.js +3 -0
  81. package/dist/src/setup/flows/link.d.ts +1 -0
  82. package/dist/src/setup/flows/link.js +1 -1
  83. package/dist/src/setup/project-resolution.d.ts +2 -8
  84. package/dist/src/setup/project-resolution.js +1 -1
  85. package/dist/src/setup/prompter.d.ts +2 -0
  86. package/dist/src/setup/scaffold/connections/catalog.d.ts +10 -5
  87. package/dist/src/setup/scaffold/connections/catalog.js +1 -1
  88. package/dist/src/setup/scaffold/create/project.js +11 -11
  89. package/dist/src/setup/scaffold/create/web-template.d.ts +1 -1
  90. package/dist/src/setup/scaffold/create/web-template.js +2 -2
  91. package/dist/src/setup/scaffold/index.d.ts +1 -1
  92. package/dist/src/setup/scaffold/index.js +1 -1
  93. package/dist/src/setup/scaffold/update/connections.d.ts +6 -0
  94. package/dist/src/setup/scaffold/update/connections.js +3 -3
  95. package/docs/channels/eve.mdx +3 -3
  96. package/docs/concepts/sessions-runs-and-streaming.md +1 -1
  97. package/docs/guides/auth-and-route-protection.md +5 -5
  98. package/docs/guides/dev-tui.md +11 -4
  99. package/docs/guides/frontend/nextjs.mdx +2 -2
  100. package/docs/guides/frontend/nuxt.mdx +2 -2
  101. package/docs/guides/frontend/sveltekit.mdx +2 -2
  102. package/docs/reference/cli.md +3 -1
  103. package/docs/tutorial/ship-it.mdx +1 -1
  104. package/docs/tutorial/team-playbooks.mdx +1 -1
  105. package/package.json +2 -2
  106. package/dist/src/internal/nitro/host/dev-rebuild-registry.d.ts +0 -5
  107. package/dist/src/internal/nitro/host/dev-rebuild-registry.js +0 -1
@@ -1,66 +1,40 @@
1
1
  import { type ChannelSetupLog } from "#setup/cli/index.js";
2
- /** Controls connector provisioning while adding a Connect-backed connection. */
2
+ import type { Prompter } from "#setup/prompter.js";
3
+ import { type VercelProjectReference } from "#setup/project-resolution.js";
4
+ /** Controls connector selection while adding a Connect-backed connection. */
3
5
  export interface SetupConnectionConnectorOptions {
4
- /** Status and command output stream through this log (rail styling preserved). */
5
6
  log: ChannelSetupLog;
7
+ prompter: Prompter;
6
8
  projectRoot: string;
7
- /** Connection slug; also the connector `--name`. */
8
9
  slug: string;
9
- /** `vercel connect create <service>` identifier (e.g. `mcp.linear.app`). */
10
10
  service: string;
11
- /** Generated `agent/connections/<slug>.ts` whose `connect("…")` is patched. */
12
- connectionFilePath: string;
13
- /**
14
- * Links a Vercel project before Connect provisioning when the caller owns a
15
- * richer linking flow (e.g. shared team selection). Returns the linked
16
- * project id, or `undefined` when linking did not complete. When omitted,
17
- * falls back to a bare `vercel link`.
18
- */
19
- linkProject?: () => Promise<string | undefined>;
11
+ canonicalConnectorName: string;
12
+ project: VercelProjectReference;
13
+ signal?: AbortSignal;
14
+ }
15
+ /** Connector identity returned by the Vercel CLI. */
16
+ export interface ConnectConnectorRef {
17
+ uid: string;
18
+ id: string;
19
+ name?: string;
20
20
  }
21
- /** Outcome of the Connect create-and-patch sequence for a connection. */
22
21
  export type SetupConnectionConnectorResult = {
23
- kind: "create-failed";
24
- created: false;
25
- } | {
26
- kind: "connector-unresolved";
27
- created: true;
28
- } | {
29
- kind: "patch-failed";
30
- created: true;
22
+ kind: "existing";
31
23
  connectorUid: string;
32
24
  } | {
33
- kind: "patched";
34
- created: true;
25
+ kind: "created";
35
26
  connectorUid: string;
27
+ connectorId: string;
36
28
  };
37
- /** Identifiers returned by Vercel Connect for an OAuth connector. */
38
- export interface ConnectConnectorRef {
39
- uid: string;
40
- id: string;
41
- }
42
- /**
43
- * Reads the connector identifiers from `vercel connect create … -F json`
44
- * stdout. This is the authoritative source for the just-created connector's
45
- * UID — it avoids a follow-up `connect list`, which can momentarily 404/rate
46
- * limit right after creation and cannot disambiguate when a service already
47
- * has multiple connectors. Returns `undefined` when stdout is empty or not the
48
- * expected JSON (e.g. an older CLI without `-F json` support on `create`).
49
- */
29
+ /** Parses a created connector that can issue user credentials. */
50
30
  export declare function parseCreatedConnector(stdout: string): ConnectConnectorRef | undefined;
51
- /**
52
- * Finds the connector to wire into the generated connection. The list is
53
- * expected to be scoped to the requested service server-side (via
54
- * `--service`), since `vercel connect list -F json` does not include a
55
- * `service` field per connector. Prefers, in order: the connector already
56
- * attached to this project, then the newest connector. When Connect does
57
- * report a `service` field, mismatches are still skipped defensively.
58
- */
59
- export declare function pickConnectConnector(listJson: unknown, service: string, projectId: string | undefined): ConnectConnectorRef | undefined;
60
- /**
61
- * Creates a Vercel Connect OAuth connector for a connection and rewrites the
62
- * generated `connect("…")` call to the connector UID Connect assigns. The
63
- * `vercel connect create` step is interactive (it opens a browser to complete
64
- * the OAuth grant); callers should only invoke this from an interactive flow.
65
- */
31
+ /** Removes a connector created by this setup attempt. */
32
+ export declare function cleanupCreatedConnectionConnector(input: {
33
+ log: ChannelSetupLog;
34
+ projectRoot: string;
35
+ connectorId: string;
36
+ /** The linked Vercel owner; inferred from the project link when omitted. */
37
+ orgId?: string;
38
+ }): Promise<void>;
39
+ /** Attaches the canonical connector by name first, then offers explicit Find/Create fallbacks. */
66
40
  export declare function setupConnectionConnector(options: SetupConnectionConnectorOptions): Promise<SetupConnectionConnectorResult>;
@@ -1 +1,2 @@
1
- import{join}from"node:path";import{readFile}from"node:fs/promises";import{createPromptCommandOutput}from"#setup/cli/index.js";import{captureVercel,runVercel,runVercelCaptureStdout}from"#setup/primitives/run-vercel.js";import{updateConnectionConnectorUid}from"#setup/scaffold/update/update-connection-connector.js";function parseCreatedConnector(e){let t=e.trim();if(!t)return;let n;try{n=JSON.parse(t)}catch{return}if(typeof n!=`object`||!n)return;let{uid:r,id:i}=n;if(!(typeof r!=`string`||typeof i!=`string`))return{uid:r,id:i}}function attachedToProject(e,t){return t===void 0||!Array.isArray(e.projects)?!1:e.projects.some(e=>typeof e==`object`&&!!e&&e.id===t)}function pickConnectConnector(e,t,n){if(typeof e!=`object`||!e)return;let r=e,i=r.connectors??r.clients;if(!Array.isArray(i))return;let a,o;for(let e of i){if(typeof e.service==`string`&&e.service!==t||typeof e.uid!=`string`||typeof e.id!=`string`)continue;let r={uid:e.uid,id:e.id},i=typeof e.createdAt==`number`?e.createdAt:0;(!o||i>o.createdAt)&&(o={ref:r,createdAt:i}),attachedToProject(e,n)&&(!a||i>a.createdAt)&&(a={ref:r,createdAt:i})}return(a??o)?.ref}async function readProjectId(n){try{let r=await readFile(join(n,`.vercel`,`project.json`),`utf8`),i=JSON.parse(r);return typeof i.projectId==`string`?i.projectId:void 0}catch{return}}async function ensureLinkedProject(e,t,n){return await readProjectId(t)||(e.message(`Linking a Vercel project for Connect...`),await runVercel([`link`],{cwd:t,onOutput:n}),readProjectId(t))}async function findConnector(e,t,n,i){let a=await captureVercel([`connect`,`list`,`-F`,`json`,`--all-projects`,`--service`,t],{cwd:e,onOutput:i});if(a.ok)try{return pickConnectConnector(JSON.parse(a.stdout),t,n)}catch{return}}async function setupConnectionConnector(e){let{log:t,projectRoot:r,slug:s,service:c,connectionFilePath:l}=e,u=createPromptCommandOutput(t),d=e.linkProject?await e.linkProject():await ensureLinkedProject(t,r,u);t.message(`Connecting ${s} via Vercel Connect...`);let f=await runVercelCaptureStdout([`connect`,`create`,c,`--name`,s,`-F`,`json`],{cwd:r,onOutput:u});if(!f.ok)return t.warning(`Could not create the connector. Run \`vercel connect create ${c} --name ${s}\`, then set the UID in agent/connections/${s}.ts.`),{kind:`create-failed`,created:!1};let p=parseCreatedConnector(f.stdout);if(p||=await findConnector(r,c,d,u),!p)return t.warning(`Could not locate the connector. Run \`vercel connect list --all-projects\` to find its UID, then set it in agent/connections/${s}.ts.`),{kind:`connector-unresolved`,created:!0};d?await runVercel([`connect`,`attach`,p.uid,`--yes`],{cwd:r,onOutput:u})||t.warning(`Created connector ${p.uid} but could not attach it to this project. Run \`vercel connect attach ${p.uid} --yes\`.`):t.warning(`Created connector ${p.uid} but no Vercel project is linked, so it isn't attached. Run \`vercel link\`, then \`vercel connect attach ${p.uid} --yes\`.`);let{patched:m}=await updateConnectionConnectorUid(l,p.uid);return m?(t.success(`Linked ${s} to ${p.uid}`),{kind:`patched`,created:!0,connectorUid:p.uid}):(t.warning(`Created connector ${p.uid}. Update \`connect("…")\` in agent/connections/${s}.ts to "${p.uid}".`),{kind:`patch-failed`,created:!0,connectorUid:p.uid})}export{parseCreatedConnector,pickConnectConnector,setupConnectionConnector};
1
+ import{readProjectLink}from"#setup/project-resolution.js";import{createPromptCommandOutput,withPhase}from"#setup/cli/index.js";import{captureVercel,runVercel,runVercelCaptureStdout}from"#setup/primitives/run-vercel.js";const CREATED_CONNECTOR=/\bConnector created:\s*(scl_[A-Za-z0-9_-]+)\b/u;function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function parseJson(e){try{return JSON.parse(e)}catch{return}}function parseConnectorRef(e){if(!isRecord(e)||typeof e.uid!=`string`||typeof e.id!=`string`)return;let t={uid:e.uid,id:e.id};return typeof e.name==`string`&&(t.name=e.name),t}function parseCreatedConnector(e){let t=parseJson(e),n=parseConnectorRef(t);if(!isRecord(t)||n===void 0)return;let r=t.supportedSubjectTypes;return Array.isArray(r)&&r.includes(`user`)?n:void 0}function parseConnectorList(e,t){let n=[];for(let r of e){if(!isRecord(r)||typeof r.service==`string`&&r.service!==t)continue;let e=parseConnectorRef(r);e!==void 0&&n.push(e)}return n}function parseConnectorListPage(e,t){if(!isRecord(e)||!Array.isArray(e.connectors))return;let n=typeof e.cursor==`string`?e.cursor:void 0;return n===void 0?{connectors:parseConnectorList(e.connectors,t)}:{connectors:parseConnectorList(e.connectors,t),cursor:n}}async function listConnectors(e,t,n){let i=[],a=new Set,o;do{let s=[`connect`,`list`,`-F`,`json`,`--all-projects`,`--service`,e.service,`--scope`,t.orgId];o!==void 0&&s.push(`--next`,o);let c=await captureVercel(s,{cwd:e.projectRoot,onOutput:n,signal:e.signal});if(!c.ok)throw Error(c.failure.message);let l=parseConnectorListPage(parseJson(c.stdout),e.service);if(l===void 0)throw Error(`Vercel returned an invalid connector list for ${e.service}.`);i.push(...l.connectors);let u=l.cursor;if(u!==void 0&&a.has(u))throw Error(`The connector list repeated cursor ${u}.`);u!==void 0&&a.add(u),o=u}while(o!==void 0);return i}async function supportsUserAuthorization(e,t,n,i){let a=await captureVercel([`api`,`/v1/connect/connectors/${encodeURIComponent(n.id)}`,`--scope`,t.orgId,`--raw`],{cwd:e.projectRoot,onOutput:i,signal:e.signal});if(!a.ok)throw Error(`Could not verify connector ${n.uid}.`);let o=parseJson(a.stdout);if(!isRecord(o)||o.id!==n.id||o.uid!==n.uid||typeof o.service==`string`&&o.service!==e.service)throw Error(`Vercel returned invalid details for connector ${n.uid}.`);let s=o.supportedSubjectTypes;return Array.isArray(s)&&s.includes(`user`)}function connectorNames(e){let t=new Set;for(let n of e){n.name!==void 0&&t.add(n.name.toLowerCase());let e=n.uid.slice(n.uid.lastIndexOf(`/`)+1).trim();e.length>0&&t.add(e.toLowerCase())}return t}function connectorMatchesCanonicalName(e,t){let n=t.toLowerCase();return e.name?.toLowerCase()===n?!0:e.uid.slice(e.uid.lastIndexOf(`/`)+1).trim().toLowerCase()===n}function nextConnectorName(e,t){if(!t.has(e.toLowerCase()))return e;let n=2;for(;t.has(`${e}-${n}`.toLowerCase());)n+=1;return`${e}-${n}`}async function cleanupCreatedConnectionConnector(n){let r=n.orgId??(await readProjectLink(n.projectRoot))?.orgId,a=[`connect`,`remove`,n.connectorId,`--disconnect-all`,`--yes`];if(r!==void 0&&a.push(`--scope`,r),!await runVercel(a,{cwd:n.projectRoot,onOutput:createPromptCommandOutput(n.log)}))throw Error(`Could not remove connector ${n.connectorId}; run \`vercel connect remove ${n.connectorId} --disconnect-all --yes\`.`)}async function attach(e,t,n,r){return runVercel([`connect`,`attach`,n,`--yes`,`--scope`,t.orgId],{cwd:e.projectRoot,onOutput:r,signal:e.signal})}async function resolveFallbackConnector(e,t,r,i,s){let c=s;for(;;){if(await e.prompter.select({message:`Which connector should ${e.slug} use?`,hintLayout:`inline`,notices:[{tone:`warning`,text:c}],options:[{value:`find`,label:`Find a new one`,hint:`Browse existing connectors`},{value:`create`,label:`Create a new one`,hint:`Register another connector`}]})===`find`){let n=[];for(let a of i)await supportsUserAuthorization(e,t,a,r)&&n.push(a);if(n.length===0){c=`No existing ${e.service} connectors support user authorization.`;continue}let a=new Map(n.map(e=>[e.uid,e])),o=await e.prompter.select({message:`Select a connector for ${e.slug}`,hintLayout:`inline`,search:!0,placeholder:`type to search connectors`,options:n.map(e=>({value:e.uid,label:e.uid,hint:e.name??e.id}))}),s=a.get(o);if(s===void 0)throw Error(`Connector ${o} is no longer available.`);return{kind:`existing`,connector:s}}let s=connectorNames(i),l=(await e.prompter.text({message:`New connector name`,defaultValue:nextConnectorName(e.slug,s),validate:e=>{let t=e.trim().toLowerCase();return t.length===0?`A name is required.`:s.has(t)?`A connector with this name already exists.`:void 0}})).trim(),u=[],createOutput=e=>{u.push(e.text),r(e)},d=await withPhase(e.log,`Waiting for you to complete setup in the browser…`,()=>runVercelCaptureStdout([`connect`,`create`,e.service,`--name`,l,`-F`,`json`,`--scope`,t.orgId],{cwd:e.projectRoot,onOutput:createOutput,signal:e.signal}),{kind:`external-action`,emphasis:`browser`}),f=parseConnectorRef(parseJson(d.stdout))?.id??CREATED_CONNECTOR.exec(u.join(`
2
+ `))?.[1],p=d.ok?parseCreatedConnector(d.stdout):void 0;if(p!==void 0)return{kind:`created`,connector:p};let m=d.ok?`The ${e.service} connector does not support user authorization.`:`Could not create the ${e.service} connector.`;if(f!==void 0){try{await cleanupCreatedConnectionConnector({log:e.log,projectRoot:e.projectRoot,connectorId:f,orgId:t.orgId})}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`${m} ${t}`)}e.signal?.throwIfAborted()}throw Error(m)}}async function setupConnectionConnector(e){let n=createPromptCommandOutput(e.log),r=e.project,i=await listConnectors(e,r,n),a=i.find(t=>connectorMatchesCanonicalName(t,e.canonicalConnectorName)),o=`Could not find a connector named ${e.canonicalConnectorName}.`;if(a!==void 0)if(await supportsUserAuthorization(e,r,a,n)){if(await attach(e,r,a.uid,n))return e.log.success(`Attached ${a.uid} connector`),{kind:`existing`,connectorUid:a.uid};e.signal?.throwIfAborted(),o=`Could not attach ${a.uid}.`}else o=`${a.uid} does not support user authorization.`;let s=await resolveFallbackConnector(e,r,n,i,o);if(!await attach(e,r,s.connector.uid,n)){if(s.kind===`created`){let t=`Could not attach ${s.connector.uid} to the linked project.`;try{await cleanupCreatedConnectionConnector({log:e.log,projectRoot:e.projectRoot,connectorId:s.connector.id,orgId:r.orgId})}catch(e){let n=e instanceof Error?e.message:String(e);throw Error(`${t} ${n}`)}throw e.signal?.throwIfAborted(),Error(t)}throw Error(`Could not attach ${s.connector.uid} to the linked project.`)}return e.log.success(`Attached ${s.connector.uid} connector`),s.kind===`created`?{kind:`created`,connectorUid:s.connector.uid,connectorId:s.connector.id}:{kind:`existing`,connectorUid:s.connector.uid}}export{cleanupCreatedConnectionConnector,parseCreatedConnector,setupConnectionConnector};
@@ -1 +1 @@
1
- import{interactiveAsker}from"../ask.js";import{detectDeployment,isProjectResolved,projectResolutionFromDeployment}from"../project-resolution.js";import{createDefaultSetupState,snapshotSetupState}from"../state.js";import{WizardCancelledError}from"../step.js";import{addChannels}from"../boxes/add-channels.js";import{withSpinner}from"../with-spinner.js";import{getVercelAuthStatus,vercelAuthBlockerReason}from"../vercel-project.js";import{CHANNELS_PROMPT_MESSAGE,selectChannels}from"../boxes/select-channels.js";import{assertCanAddSelectedChannels,inspectExistingChannelRegistrations}from"../channel-add-conflicts.js";import{runInteractive}from"../runner.js";import{prompterSink}from"./in-project.js";import{SCAFFOLDABLE_CHANNELS}from"#setup/scaffold/index.js";import{toErrorMessage}from"#shared/errors.js";const SEE_IT_LIVE_MESSAGE=`See it live`;async function promptSeeItLive(e){try{return await e.select({message:SEE_IT_LIVE_MESSAGE,options:[{value:`deploy`,label:`Deploy and chat`},{value:`later`,label:`Later`}]})}catch(e){if(e instanceof WizardCancelledError)return`later`;throw e}}function channelAlreadyAdded(e,t){return t===`web`?e.webAppPresent:e.slackOwners.length>0}function appendChannel(e,t){return e.includes(t)?[...e]:[...e,t]}async function pickChannel(e,t,n,r){let i=channelListRows(t,n,r),a=!i.some(e=>e.value!==`done`&&e.completed!==!0&&e.disabled!==!0),s={message:CHANNELS_PROMPT_MESSAGE,options:i,hintLayout:`inline`};a&&(s.initialValue=`done`);try{return{kind:`picked`,value:await e.select(s)}}catch(e){if(e instanceof WizardCancelledError)return{kind:`cancelled`};throw e}}function channelLandedDuringSubflow(e,t,n){return!channelAlreadyAdded(e,n)&&channelAlreadyAdded(t,n)}function deployAndChatDetails(e){let t={};return e.slackChatUrl!==void 0&&(t.chatUrl=e.slackChatUrl),e.slackWorkspaceName!==void 0&&(t.workspaceName=e.slackWorkspaceName),t}function vercelChannelBlocker(e,t){let n=vercelAuthBlockerReason(e);if(n!==void 0)return n;if(!t)return`Requires Vercel account, see /model`}function channelListRows(e,t,n){let r=[{value:`repl`,label:`Terminal UI`,completed:!0,focusHint:`Already installed`}];for(let i of SCAFFOLDABLE_CHANNELS){if(channelAlreadyAdded(e,i.kind)){r.push({value:i.kind,label:i.label,completed:!0,focusHint:`Already installed`});continue}let a=e.disabledChannelReasons[i.kind];if(a!==void 0){r.push({value:i.kind,label:i.label,disabled:!0,disabledReason:a});continue}if(i.requiresVercelProject===!0){let e=vercelChannelBlocker(n,t);if(e!==void 0){r.push({value:i.kind,label:i.label,disabled:!0,disabledReason:e,disabledReasonTone:`warning`});continue}}let o={value:i.kind,label:i.label};i.hint!==void 0&&(o.hint=i.hint),r.push(o)}return r.push({value:`done`,label:`Done`}),r}async function runChannelsFlow(o){let{appRoot:c,prompter:l,signal:u}=o,d={detectDeployment,inspectExistingChannelRegistrations,getVercelAuthStatus,...o.deps},[f,p,m]=await withSpinner(l,`Checking the project…`,()=>Promise.all([d.detectDeployment(c,{signal:u}),d.inspectExistingChannelRegistrations(c),d.getVercelAuthStatus(c,{signal:u})]));u?.throwIfAborted();let h=p,g={...createDefaultSetupState(),project:projectResolutionFromDeployment(f),projectPath:{kind:`resolved`,inPlace:!0,path:c}},_;for(;;){let t=await pickChannel(l,h,isProjectResolved(g.project),m);if(t.kind===`cancelled`){if(g.channels.length===0)return{kind:`cancelled`};break}let r=t.value;if(r===`done`)break;if(r===`repl`||channelAlreadyAdded(h,r))continue;let i=[selectChannels({asker:interactiveAsker(l),variant:`channels-add`,presetChannels:[r],validateSelection:e=>assertCanAddSelectedChannels(e,h)}),addChannels({asker:interactiveAsker(l),prompter:l,configureVercelServices:!0,deps:d.addChannels})],o;try{o=await runInteractive(i,g,prompterSink(l),{snapshot:snapshotSetupState,signal:u})}catch(e){let t=await withSpinner(l,`Checking the project…`,()=>d.inspectExistingChannelRegistrations(c));if(channelLandedDuringSubflow(h,t,r)){if(g={...g,channels:appendChannel(g.channels,r)},h=t,_=toErrorMessage(e),u?.aborted===!0)break;continue}throw e}if(o.kind===`done`){if(g=o.state,h=await withSpinner(l,`Checking the project…`,()=>d.inspectExistingChannelRegistrations(c)),u?.throwIfAborted(),r===`slack`&&g.slackbotAttached&&await promptSeeItLive(l)===`deploy`)return{kind:`deploy-and-chat`,addedChannels:g.channels,chat:deployAndChatDetails(g)}}else{let e=await withSpinner(l,`Checking the project…`,()=>d.inspectExistingChannelRegistrations(c));if(channelLandedDuringSubflow(h,e,r))return{kind:`done`,addedChannels:appendChannel(g.channels,r)};h=e}}return _===void 0?{kind:`done`,addedChannels:g.channels}:{kind:`failed`,addedChannels:g.channels,message:_}}export{SEE_IT_LIVE_MESSAGE,runChannelsFlow};
1
+ import{interactiveAsker}from"../ask.js";import{detectDeployment,isProjectResolved,projectResolutionFromDeployment}from"../project-resolution.js";import{createDefaultSetupState,snapshotSetupState}from"../state.js";import{WizardCancelledError}from"../step.js";import{addChannels}from"../boxes/add-channels.js";import{withSpinner}from"../with-spinner.js";import{getVercelAuthStatus,vercelAuthBlockerReason}from"../vercel-project.js";import{CHANNELS_PROMPT_MESSAGE,selectChannels}from"../boxes/select-channels.js";import{assertCanAddSelectedChannels,inspectExistingChannelRegistrations}from"../channel-add-conflicts.js";import{runInteractive}from"../runner.js";import{prompterSink}from"./in-project.js";import{SCAFFOLDABLE_CHANNELS}from"#setup/scaffold/index.js";import{toErrorMessage}from"#shared/errors.js";const SEE_IT_LIVE_MESSAGE=`See it live`;async function promptSeeItLive(e){try{return await e.select({message:SEE_IT_LIVE_MESSAGE,options:[{value:`deploy`,label:`Deploy and chat`},{value:`later`,label:`Later`}]})}catch(e){if(e instanceof WizardCancelledError)return`later`;throw e}}function channelAlreadyAdded(e,t){return t===`web`?e.webAppPresent:e.slackOwners.length>0}function appendChannel(e,t){return e.includes(t)?[...e]:[...e,t]}async function pickChannel(e,t,n,r){let i=channelListRows(t,n,r),a=!i.some(e=>e.value!==`done`&&e.completed!==!0&&e.disabled!==!0),s={message:CHANNELS_PROMPT_MESSAGE,options:i,hintLayout:`inline`};a&&(s.initialValue=`done`);try{return{kind:`picked`,value:await e.select(s)}}catch(e){if(e instanceof WizardCancelledError)return{kind:`cancelled`};throw e}}function channelLandedDuringSubflow(e,t,n){return!channelAlreadyAdded(e,n)&&channelAlreadyAdded(t,n)}function deployAndChatDetails(e){let t={};return e.slackChatUrl!==void 0&&(t.chatUrl=e.slackChatUrl),e.slackWorkspaceName!==void 0&&(t.workspaceName=e.slackWorkspaceName),t}function vercelChannelBlocker(e,t){let n=vercelAuthBlockerReason(e);if(n!==void 0)return n;if(!t)return`Requires Vercel account, see /model`}function channelListRows(e,t,n){let r=[{value:`repl`,label:`Terminal UI`,completed:!0,focusHint:`Already installed`}];for(let i of SCAFFOLDABLE_CHANNELS){if(channelAlreadyAdded(e,i.kind)){r.push({value:i.kind,label:i.label,completed:!0,focusHint:`Already installed`});continue}let a=e.disabledChannelReasons[i.kind];if(a!==void 0){r.push({value:i.kind,label:i.label,disabled:!0,disabledReason:a});continue}if(i.requiresVercelProject===!0){let e=vercelChannelBlocker(n,t);if(e!==void 0){r.push({value:i.kind,label:i.label,disabled:!0,disabledReason:e,disabledReasonTone:`warning`});continue}}let o={value:i.kind,label:i.label};i.hint!==void 0&&(o.hint=i.hint),r.push(o)}return r.push({value:`done`,label:`Done`,trailingAction:!0}),r}async function runChannelsFlow(o){let{appRoot:c,prompter:l,signal:u}=o,d={detectDeployment,inspectExistingChannelRegistrations,getVercelAuthStatus,...o.deps},[f,p,m]=await withSpinner(l,`Checking the project…`,()=>Promise.all([d.detectDeployment(c,{signal:u}),d.inspectExistingChannelRegistrations(c),d.getVercelAuthStatus(c,{signal:u})]));u?.throwIfAborted();let h=p,g={...createDefaultSetupState(),project:projectResolutionFromDeployment(f),projectPath:{kind:`resolved`,inPlace:!0,path:c}},_;for(;;){let t=await pickChannel(l,h,isProjectResolved(g.project),m);if(t.kind===`cancelled`){if(g.channels.length===0)return{kind:`cancelled`};break}let r=t.value;if(r===`done`)break;if(r===`repl`||channelAlreadyAdded(h,r))continue;let i=[selectChannels({asker:interactiveAsker(l),variant:`channels-add`,presetChannels:[r],validateSelection:e=>assertCanAddSelectedChannels(e,h)}),addChannels({asker:interactiveAsker(l),prompter:l,configureVercelServices:!0,deps:d.addChannels})],o;try{o=await runInteractive(i,g,prompterSink(l),{snapshot:snapshotSetupState,signal:u})}catch(e){let t=await withSpinner(l,`Checking the project…`,()=>d.inspectExistingChannelRegistrations(c));if(channelLandedDuringSubflow(h,t,r)){if(g={...g,channels:appendChannel(g.channels,r)},h=t,_=toErrorMessage(e),u?.aborted===!0)break;continue}throw e}if(o.kind===`done`){if(g=o.state,h=await withSpinner(l,`Checking the project…`,()=>d.inspectExistingChannelRegistrations(c)),u?.throwIfAborted(),r===`slack`&&g.slackbotAttached&&await promptSeeItLive(l)===`deploy`)return{kind:`deploy-and-chat`,addedChannels:g.channels,chat:deployAndChatDetails(g)}}else{let e=await withSpinner(l,`Checking the project…`,()=>d.inspectExistingChannelRegistrations(c));if(channelLandedDuringSubflow(h,e,r))return{kind:`done`,addedChannels:appendChannel(g.channels,r)};h=e}}return _===void 0?{kind:`done`,addedChannels:g.channels}:{kind:`failed`,addedChannels:g.channels,message:_}}export{SEE_IT_LIVE_MESSAGE,runChannelsFlow};
@@ -0,0 +1,36 @@
1
+ import { ensureConnectionDependencies, listAuthoredConnections } from "#setup/scaffold/index.js";
2
+ import { detectPackageManager } from "#setup/package-manager.js";
3
+ import { runPackageManagerInstall } from "#setup/primitives/pm/run.js";
4
+ import { type AddConnectionsDeps } from "../boxes/add-connections.js";
5
+ import { detectDeployment } from "../project-resolution.js";
6
+ import type { Prompter } from "../prompter.js";
7
+ import { getVercelAuthStatus } from "../vercel-project.js";
8
+ import { runLinkFlow } from "./link.js";
9
+ export declare const CONNECTIONS_PROMPT_MESSAGE = "Select an MCP server to add to your agent through Vercel Connect";
10
+ export interface ConnectionsFlowDeps {
11
+ detectDeployment: typeof detectDeployment;
12
+ detectPackageManager: typeof detectPackageManager;
13
+ getVercelAuthStatus: typeof getVercelAuthStatus;
14
+ runLinkFlow: typeof runLinkFlow;
15
+ ensureConnectionDependencies: typeof ensureConnectionDependencies;
16
+ listAuthoredConnections: typeof listAuthoredConnections;
17
+ runPackageManagerInstall: typeof runPackageManagerInstall;
18
+ addConnections?: AddConnectionsDeps;
19
+ }
20
+ export type ConnectionsFlowResult = {
21
+ kind: "done";
22
+ addedConnections: readonly string[];
23
+ } | {
24
+ kind: "cancelled";
25
+ } | {
26
+ kind: "failed";
27
+ addedConnections: readonly string[];
28
+ message: string;
29
+ };
30
+ /** Runs `/connect`, linking a project on first selection when needed. */
31
+ export declare function runConnectionsFlow(input: {
32
+ appRoot: string;
33
+ prompter: Prompter;
34
+ signal?: AbortSignal;
35
+ deps?: Partial<ConnectionsFlowDeps>;
36
+ }): Promise<ConnectionsFlowResult>;
@@ -0,0 +1,3 @@
1
+ import{interactiveAsker}from"../ask.js";import{detectDeployment,isProjectResolved,projectResolutionFromDeployment}from"../project-resolution.js";import{snapshotSetupState}from"../state.js";import{WizardCancelledError}from"../step.js";import{withSpinner}from"../with-spinner.js";import{getVercelAuthStatus,vercelAuthBlockerReason}from"../vercel-project.js";import{selectConnections}from"../boxes/select-connections.js";import{addConnections}from"../boxes/add-connections.js";import{runInteractive}from"../runner.js";import{inProjectSetupState,prompterSink}from"./in-project.js";import{runLinkFlow}from"./link.js";import{CONNECTION_CATALOG,ensureConnectionDependencies,listAuthoredConnections}from"#setup/scaffold/index.js";import{detectPackageManager}from"#setup/package-manager.js";import{toErrorMessage}from"#shared/errors.js";import{createPromptCommandOutput,withPhase}from"#setup/cli/index.js";import{runPackageManagerInstall}from"#setup/primitives/pm/run.js";const CONNECTIONS_PROMPT_MESSAGE=`Select an MCP server to add to your agent through Vercel Connect`,CONNECT_CONNECTIONS=CONNECTION_CATALOG.filter(e=>e.auth.kind===`connect`);function connectionRows(e,t){let n=vercelAuthBlockerReason(t),r=CONNECT_CONNECTIONS.map(t=>{let r={value:t.slug,label:t.label};return e.has(t.slug)?{...r,completed:!0,focusHint:`Already added`}:n===void 0?{...r,hint:t.hint}:{...r,disabled:!0,disabledReason:n,disabledReasonTone:`warning`}});return r.push({value:`done`,label:`Done`,trailingAction:!0}),r}async function pickConnection(e,t,n){let r=connectionRows(t,n),i={message:CONNECTIONS_PROMPT_MESSAGE,options:r,hintLayout:`inline`,search:!0,placeholder:`type to search MCP servers`};r.some(e=>e.value!==`done`&&e.disabled!==!0&&e.completed!==!0)||(i.initialValue=`done`);try{return await e.select(i)}catch(e){if(e instanceof WizardCancelledError)return;throw e}}async function runConnectionsFlow(a){let{appRoot:o,prompter:s,signal:c}=a,l={detectDeployment,detectPackageManager,ensureConnectionDependencies,getVercelAuthStatus,listAuthoredConnections,runLinkFlow,runPackageManagerInstall,...a.deps},[u,d,f]=await withSpinner(s,`Checking the project…`,()=>Promise.all([l.detectDeployment(o,{signal:c}),l.listAuthoredConnections(o),l.getVercelAuthStatus(o,{signal:c})]));c?.throwIfAborted();let p=inProjectSetupState(o,projectResolutionFromDeployment(u)),m=new Set(d),h=await pickConnection(s,m,f);if(h===void 0)return{kind:`cancelled`};if(h===`done`||m.has(h))return{kind:`done`,addedConnections:[]};try{if(!isProjectResolved(p.project)){if((await l.runLinkFlow({appRoot:o,prompter:s,signal:c,projectSelection:`create-or-link`,teamSelectMessage:()=>`You need to link to a project to use Vercel Connect.
2
+
3
+ Select your team`})).kind===`cancelled`)return{kind:`cancelled`};let e=projectResolutionFromDeployment(await withSpinner(s,`Checking the project…`,()=>l.detectDeployment(o,{signal:c})));if(!isProjectResolved(e))throw Error(`Project link was not found after linking.`);p={...p,project:e}}let t=await l.detectPackageManager(o);if(await l.ensureConnectionDependencies({projectRoot:o}),!await withPhase(s.log,`Installing connection dependencies (${t.kind} install)...`,()=>l.runPackageManagerInstall(t.kind,o,{onOutput:createPromptCommandOutput(s.log),signal:c})))throw Error(`Dependency installation failed. Run \`${t.kind} install\`.`);if((await runInteractive([selectConnections({asker:interactiveAsker(s),presetConnections:[h]}),addConnections({prompter:s,signal:c,deps:l.addConnections})],p,prompterSink(s),{snapshot:snapshotSetupState,signal:c})).kind!==`done`)return{kind:`cancelled`};if(!new Set(await l.listAuthoredConnections(o)).has(h))throw Error(`Connection "${h}" was not added.`);return{kind:`done`,addedConnections:[h]}}catch(e){return e instanceof WizardCancelledError?{kind:`cancelled`}:{kind:`failed`,addedConnections:new Set(await l.listAuthoredConnections(o)).has(h)?[h]:[],message:toErrorMessage(e)}}}export{CONNECTIONS_PROMPT_MESSAGE,runConnectionsFlow};
@@ -47,5 +47,6 @@ export declare function runLinkFlow(input: {
47
47
  * branch, where a fresh agent has no project yet).
48
48
  */
49
49
  projectSelection?: "create-or-link" | "existing-only";
50
+ teamSelectMessage?: (currentTeam: string) => string;
50
51
  deps?: Partial<LinkFlowDeps>;
51
52
  }): Promise<LinkFlowResult>;
@@ -1 +1 @@
1
- import{__toESM}from"../../_virtual/_rolldown/runtime.js";import{require_picocolors}from"../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js";import{AI_GATEWAY_API_KEY_ENV_VAR}from"../ai-gateway-api-key.js";import{interactiveAsker,withAnswers}from"../ask.js";import{detectProjectIdentity}from"../project-resolution.js";import{snapshotSetupState}from"../state.js";import{WizardCancelledError}from"../step.js";import{withSpinner}from"../with-spinner.js";import{detectAiGateway,findEnvFileWithKey}from"../boxes/detect-ai-gateway.js";import{applyAiGatewayCredential}from"../boxes/apply-ai-gateway-credential.js";import{linkVercelProject}from"../boxes/link-project.js";import{resolveProvisioning}from"../boxes/resolve-provisioning.js";import{runInteractive}from"../runner.js";import{inProjectSetupState,prompterSink}from"./in-project.js";var import_picocolors=__toESM(require_picocolors(),1);async function runLinkFlow(e){let{appRoot:t,prompter:i,signal:a,projectSelection:o=`existing-only`}=e,s={detectProjectIdentity,findEnvFileWithKey,...e.deps},c=await withSpinner(i,`Checking the current Vercel link...`,async()=>{let e=await s.detectProjectIdentity(t,{signal:a});return a?.throwIfAborted(),e});if(c===void 0){let[e,n]=await Promise.all([s.findEnvFileWithKey(t,AI_GATEWAY_API_KEY_ENV_VAR),s.findEnvFileWithKey(t,`VERCEL_OIDC_TOKEN`)]),r=e??n;r!==void 0&&i.log.message(`This directory is not linked to a Vercel project yet — the model currently runs on credentials from ${r}.`)}else{let e=c.teamName===void 0?import_picocolors.default.bold(c.projectName):`${import_picocolors.default.bold(c.projectName)} in ${import_picocolors.default.bold(c.teamName)}`;try{if(await i.select({message:`This directory is already linked to\n${e}`,options:[{value:`relink`,label:`Link to another project`},{value:`dismiss`,label:`Dismiss`}]})===`dismiss`)return{kind:`cancelled`}}catch(e){if(e instanceof WizardCancelledError)return{kind:`cancelled`};throw e}}let l=inProjectSetupState(t,{kind:`unresolved`});if((await runInteractive([resolveProvisioning({asker:withAnswers({deploy:`vercel`})(interactiveAsker(i)),prompter:i,targetDirectory:t,mode:{headless:!1},adoptExistingLink:!1,projectSelection:o,deps:s.resolveProvisioning}),linkVercelProject({prompter:i,deps:s.linkProject}),detectAiGateway(),applyAiGatewayCredential({prompter:i,deps:s.applyAiGatewayCredential})],l,prompterSink(i),{snapshot:snapshotSetupState,signal:a})).kind===`cancelled`)return{kind:`cancelled`};let[u,d]=await Promise.all([s.findEnvFileWithKey(t,`VERCEL_OIDC_TOKEN`),s.findEnvFileWithKey(t,AI_GATEWAY_API_KEY_ENV_VAR)]);a?.throwIfAborted(),u===void 0&&d===void 0&&i.log.warning("Linked, but no model credential landed in an env file (VERCEL_OIDC_TOKEN or AI_GATEWAY_API_KEY). Run `vercel env pull` once the project has AI Gateway access.");let f={kind:`done`};return u===void 0?d!==void 0&&(f.credential=AI_GATEWAY_API_KEY_ENV_VAR):f.credential=`VERCEL_OIDC_TOKEN`,f}export{runLinkFlow};
1
+ import{__toESM}from"../../_virtual/_rolldown/runtime.js";import{require_picocolors}from"../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js";import{AI_GATEWAY_API_KEY_ENV_VAR}from"../ai-gateway-api-key.js";import{interactiveAsker,withAnswers}from"../ask.js";import{detectProjectIdentity}from"../project-resolution.js";import{snapshotSetupState}from"../state.js";import{WizardCancelledError}from"../step.js";import{withSpinner}from"../with-spinner.js";import{detectAiGateway,findEnvFileWithKey}from"../boxes/detect-ai-gateway.js";import{applyAiGatewayCredential}from"../boxes/apply-ai-gateway-credential.js";import{linkVercelProject}from"../boxes/link-project.js";import{resolveProvisioning}from"../boxes/resolve-provisioning.js";import{runInteractive}from"../runner.js";import{inProjectSetupState,prompterSink}from"./in-project.js";var import_picocolors=__toESM(require_picocolors(),1);async function runLinkFlow(e){let{appRoot:t,prompter:a,signal:o,projectSelection:s=`existing-only`}=e,c={detectProjectIdentity,findEnvFileWithKey,...e.deps},l=await withSpinner(a,`Checking the current Vercel link...`,async()=>{let e=await c.detectProjectIdentity(t,{signal:o});return o?.throwIfAborted(),e});if(l===void 0){let[n,r]=await Promise.all([c.findEnvFileWithKey(t,AI_GATEWAY_API_KEY_ENV_VAR),c.findEnvFileWithKey(t,`VERCEL_OIDC_TOKEN`)]),i=n??r;i!==void 0&&e.teamSelectMessage===void 0&&a.log.message(`This directory is not linked to a Vercel project yet — the model currently runs on credentials from ${i}.`)}else{let e=l.teamName===void 0?import_picocolors.default.bold(l.projectName):`${import_picocolors.default.bold(l.projectName)} in ${import_picocolors.default.bold(l.teamName)}`;try{if(await a.select({message:`This directory is already linked to\n${e}`,options:[{value:`relink`,label:`Link to another project`},{value:`dismiss`,label:`Dismiss`}]})===`dismiss`)return{kind:`cancelled`}}catch(e){if(e instanceof WizardCancelledError)return{kind:`cancelled`};throw e}}let u=inProjectSetupState(t,{kind:`unresolved`});if((await runInteractive([resolveProvisioning({asker:withAnswers({deploy:`vercel`})(interactiveAsker(a)),prompter:a,targetDirectory:t,mode:{headless:!1},adoptExistingLink:!1,projectSelection:s,teamSelectMessage:e.teamSelectMessage,deps:c.resolveProvisioning}),linkVercelProject({prompter:a,deps:c.linkProject}),detectAiGateway(),applyAiGatewayCredential({prompter:a,deps:c.applyAiGatewayCredential})],u,prompterSink(a),{snapshot:snapshotSetupState,signal:o})).kind===`cancelled`)return{kind:`cancelled`};let[d,f]=await Promise.all([c.findEnvFileWithKey(t,`VERCEL_OIDC_TOKEN`),c.findEnvFileWithKey(t,AI_GATEWAY_API_KEY_ENV_VAR)]);o?.throwIfAborted(),d===void 0&&f===void 0&&a.log.warning("Linked, but no model credential landed in an env file (VERCEL_OIDC_TOKEN or AI_GATEWAY_API_KEY). Run `vercel env pull` once the project has AI Gateway access.");let p={kind:`done`};return d===void 0?f!==void 0&&(p.credential=AI_GATEWAY_API_KEY_ENV_VAR):p.credential=`VERCEL_OIDC_TOKEN`,p}export{runLinkFlow};
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import { type VercelProjectLink } from "#internal/vercel/project-link.js";
2
2
  /** Link and production-deployment status for a Vercel project directory. */
3
3
  export type DeploymentState = "unlinked" | "linked" | "deployed";
4
4
  /** Vercel project data resolved from local link metadata and the API. */
@@ -8,13 +8,8 @@ export interface DeploymentInfo {
8
8
  orgId?: string;
9
9
  productionUrl?: string;
10
10
  }
11
- declare const VercelProjectReferenceSchema: z.ZodObject<{
12
- projectId: z.ZodString;
13
- orgId: z.ZodString;
14
- projectName: z.ZodOptional<z.ZodString>;
15
- }, z.core.$strip>;
16
11
  /** Validated Vercel owner and project identifiers. */
17
- export type VercelProjectReference = z.infer<typeof VercelProjectReferenceSchema>;
12
+ export type VercelProjectReference = VercelProjectLink;
18
13
  /** Parses the complete Vercel owner and project environment pair. */
19
14
  export declare function projectReferenceFromEnvironment(environment: Readonly<Record<string, string | undefined>>): VercelProjectReference | undefined;
20
15
  /** Rejects Vercel's unsupported legacy link directory before link mutation. */
@@ -70,4 +65,3 @@ export declare function projectResolutionFromDeployResult(project: ProjectResolu
70
65
  }): ProjectResolution;
71
66
  export declare function isProjectResolved(project: ProjectResolution): boolean;
72
67
  export declare function projectProductionUrlFromResolution(project: ProjectResolution): string | undefined;
73
- export {};
@@ -1 +1 @@
1
- import{object,string}from"../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/schemas.js";import"../node_modules/.pnpm/zod@4.4.3/node_modules/zod/index.js";import{captureVercel}from"./primitives/run-vercel.js";import{join}from"node:path";import{readFile,stat}from"node:fs/promises";const VercelProjectReferenceSchema=object({projectId:string().min(1),orgId:string().min(1),projectName:string().min(1).optional()}),VercelProjectEnvironmentSchema=object({VERCEL_ORG_ID:VercelProjectReferenceSchema.shape.orgId,VERCEL_PROJECT_ID:VercelProjectReferenceSchema.shape.projectId});function projectReferenceFromEnvironment(e){let t=VercelProjectEnvironmentSchema.safeParse(e);if(t.success)return{orgId:t.data.VERCEL_ORG_ID,projectId:t.data.VERCEL_PROJECT_ID}}async function assertNoLegacyProjectLinkDirectory(e){try{if(!(await stat(join(e,`.now`))).isDirectory())return}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return;throw e}throw Error("Legacy Vercel link directory `.now` is not supported. Remove `.now` before linking this project.")}async function readProjectLink(e){await assertNoLegacyProjectLinkDirectory(e);try{let t=await readFile(join(e,`.vercel`,`project.json`),`utf8`),n=VercelProjectReferenceSchema.safeParse(JSON.parse(t));return n.success?n.data:void 0}catch{return}}function pickShortestAlias(e){if(!Array.isArray(e))return;let t;for(let n of e)typeof n!=`string`||n.length===0||(t===void 0||n.length<t.length)&&(t=n);return t}async function fetchProductionAlias(e,t,r,i){let a=await captureVercel([`api`,`/v9/projects/${e}?teamId=${t}`,`--scope`,t],{cwd:r,signal:i.signal});if(a.ok)try{let e=pickShortestAlias(JSON.parse(a.stdout).targets?.production?.alias);return e?`https://${e}`:void 0}catch{return}}async function detectDeployment(e,t={}){t.signal?.throwIfAborted();let n=await readProjectLink(e);if(n===void 0)return{state:`unlinked`};let r=await fetchProductionAlias(n.projectId,n.orgId,e,t);return t.signal?.throwIfAborted(),{state:r?`deployed`:`linked`,projectId:n.projectId,orgId:n.orgId,productionUrl:r}}async function fetchVercelName(e,t,r,i){let a=await captureVercel([`api`,e,`--scope`,t],{cwd:r,signal:i.signal});if(a.ok)try{let e=JSON.parse(a.stdout);return typeof e.name==`string`&&e.name.length>0?e.name:typeof e.slug==`string`&&e.slug.length>0?e.slug:void 0}catch{return}}async function detectProjectIdentity(e,t={}){t.signal?.throwIfAborted();let n=await readProjectLink(e);if(n===void 0)return;let[r,i]=await Promise.all([fetchVercelName(`/v9/projects/${n.projectId}?teamId=${n.orgId}`,n.orgId,e,t).then(e=>e??n.projectId),n.orgId.startsWith(`team_`)?fetchVercelName(`/v2/teams/${n.orgId}`,n.orgId,e,t):Promise.resolve(void 0)]);return t.signal?.throwIfAborted(),{projectName:r,teamName:i}}function projectResolutionFromDeployment(e){return e.state===`unlinked`||e.projectId===void 0?{kind:`unresolved`}:e.state===`deployed`&&e.productionUrl!==void 0?{kind:`deployed`,projectId:e.projectId,productionUrl:e.productionUrl}:{kind:`linked`,projectId:e.projectId}}async function detectProjectResolution(e,t={}){return projectResolutionFromDeployment(await detectDeployment(e,t))}function mergeProjectResolution(e,t){return t.kind===`unresolved`||e.kind===`deployed`&&e.projectId===t.projectId?e:t}function projectResolutionFromDeployResult(e,t){return e.kind===`unresolved`||!t.deployed||t.productionUrl===void 0?e:{kind:`deployed`,projectId:e.projectId,productionUrl:t.productionUrl}}function isProjectResolved(e){return e.kind!==`unresolved`}function projectProductionUrlFromResolution(e){return e.kind===`deployed`?e.productionUrl:void 0}export{assertNoLegacyProjectLinkDirectory,detectDeployment,detectProjectIdentity,detectProjectResolution,isProjectResolved,mergeProjectResolution,projectProductionUrlFromResolution,projectReferenceFromEnvironment,projectResolutionFromDeployResult,projectResolutionFromDeployment,readProjectLink};
1
+ import{object}from"../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/schemas.js";import"../node_modules/.pnpm/zod@4.4.3/node_modules/zod/index.js";import{captureVercel}from"./primitives/run-vercel.js";import{join}from"node:path";import{stat}from"node:fs/promises";import{VercelProjectLinkSchema,readVercelProjectLink}from"#internal/vercel/project-link.js";const VercelProjectEnvironmentSchema=object({VERCEL_ORG_ID:VercelProjectLinkSchema.shape.orgId,VERCEL_PROJECT_ID:VercelProjectLinkSchema.shape.projectId});function projectReferenceFromEnvironment(e){let t=VercelProjectEnvironmentSchema.safeParse(e);if(t.success)return{orgId:t.data.VERCEL_ORG_ID,projectId:t.data.VERCEL_PROJECT_ID}}async function assertNoLegacyProjectLinkDirectory(e){try{if(!(await stat(join(e,`.now`))).isDirectory())return}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return;throw e}throw Error("Legacy Vercel link directory `.now` is not supported. Remove `.now` before linking this project.")}async function readProjectLink(e){return await assertNoLegacyProjectLinkDirectory(e),await readVercelProjectLink(e)}function pickShortestAlias(e){if(!Array.isArray(e))return;let t;for(let n of e)typeof n!=`string`||n.length===0||(t===void 0||n.length<t.length)&&(t=n);return t}async function fetchProductionAlias(e,n,r,i){let a=await captureVercel([`api`,`/v9/projects/${e}?teamId=${n}`,`--scope`,n],{cwd:r,signal:i.signal});if(a.ok)try{let e=pickShortestAlias(JSON.parse(a.stdout).targets?.production?.alias);return e?`https://${e}`:void 0}catch{return}}async function detectDeployment(e,t={}){t.signal?.throwIfAborted();let n=await readProjectLink(e);if(n===void 0)return{state:`unlinked`};let r=await fetchProductionAlias(n.projectId,n.orgId,e,t);return t.signal?.throwIfAborted(),{state:r?`deployed`:`linked`,projectId:n.projectId,orgId:n.orgId,productionUrl:r}}async function fetchVercelName(e,n,r,i){let a=await captureVercel([`api`,e,`--scope`,n],{cwd:r,signal:i.signal});if(a.ok)try{let e=JSON.parse(a.stdout);return typeof e.name==`string`&&e.name.length>0?e.name:typeof e.slug==`string`&&e.slug.length>0?e.slug:void 0}catch{return}}async function detectProjectIdentity(e,t={}){t.signal?.throwIfAborted();let n=await readProjectLink(e);if(n===void 0)return;let[r,i]=await Promise.all([fetchVercelName(`/v9/projects/${n.projectId}?teamId=${n.orgId}`,n.orgId,e,t).then(e=>e??n.projectId),n.orgId.startsWith(`team_`)?fetchVercelName(`/v2/teams/${n.orgId}`,n.orgId,e,t):Promise.resolve(void 0)]);return t.signal?.throwIfAborted(),{projectName:r,teamName:i}}function projectResolutionFromDeployment(e){return e.state===`unlinked`||e.projectId===void 0?{kind:`unresolved`}:e.state===`deployed`&&e.productionUrl!==void 0?{kind:`deployed`,projectId:e.projectId,productionUrl:e.productionUrl}:{kind:`linked`,projectId:e.projectId}}async function detectProjectResolution(e,t={}){return projectResolutionFromDeployment(await detectDeployment(e,t))}function mergeProjectResolution(e,t){return t.kind===`unresolved`||e.kind===`deployed`&&e.projectId===t.projectId?e:t}function projectResolutionFromDeployResult(e,t){return e.kind===`unresolved`||!t.deployed||t.productionUrl===void 0?e:{kind:`deployed`,projectId:e.projectId,productionUrl:t.productionUrl}}function isProjectResolved(e){return e.kind!==`unresolved`}function projectProductionUrlFromResolution(e){return e.kind===`deployed`?e.productionUrl:void 0}export{assertNoLegacyProjectLinkDirectory,detectDeployment,detectProjectIdentity,detectProjectResolution,isProjectResolved,mergeProjectResolution,projectProductionUrlFromResolution,projectReferenceFromEnvironment,projectResolutionFromDeployResult,projectResolutionFromDeployment,readProjectLink};
@@ -9,6 +9,8 @@ export type PrompterValue = string | number | boolean;
9
9
  export interface SelectOption<T extends PrompterValue> {
10
10
  value: T;
11
11
  label: string;
12
+ /** Completion action kept after searchable results instead of being filtered. */
13
+ trailingAction?: boolean;
12
14
  hint?: string;
13
15
  /** Short inline annotation shown dimmed only while the cursor is on this row. */
14
16
  focusHint?: string;
@@ -32,8 +32,9 @@ export type ConnectionAuthSpec =
32
32
  * It starts as a placeholder and is rewritten to the real connector UID once
33
33
  * the connector is provisioned (see {@link service}). `service` is the
34
34
  * managed-connector identifier passed to `vercel connect create <service>`
35
- * (e.g. the MCP host `mcp.linear.app`); when omitted, the connector must be
36
- * created out of band and its UID set by hand.
35
+ * (e.g. `mcp.linear.app`); when omitted, eve falls back to the MCP endpoint
36
+ * host. It is distinct from the connector UID, which Vercel assigns when the
37
+ * connector is created or listed.
37
38
  */
38
39
  {
39
40
  kind: "connect";
@@ -107,11 +108,15 @@ export declare function effectiveProtocols(declared: readonly ConnectionProtocol
107
108
  export declare function isValidConnectionSlug(slug: string): boolean;
108
109
  /**
109
110
  * The `vercel connect create <service>` identifier for a Connect-backed
110
- * connection: the explicit `auth.service` when set, otherwise the host of the
111
- * MCP endpoint (e.g. `mcp.linear.app`). Returns `undefined` when neither is
112
- * available, in which case the connector must be provisioned out of band.
111
+ * connection: the explicit `auth.service` when set, otherwise the MCP host.
112
+ * Returns `undefined` when neither is available, in which case the connector
113
+ * must be provisioned out of band.
113
114
  */
114
115
  export declare function connectorServiceForEntry(entry: Pick<ConnectionCatalogEntry, "mcp" | "auth">): string | undefined;
116
+ /** Canonical connector name attempted before offering discovery or creation. */
117
+ export declare function canonicalConnectorNameForEntry(entry: {
118
+ auth?: ConnectionAuthSpec;
119
+ }): string | undefined;
115
120
  /** Extracts the bare host from an MCP URL, or `undefined` when unparseable. */
116
121
  export declare function mcpServiceHost(url: string | undefined): string | undefined;
117
122
  /** Returns the endpoint block required for a protocol, or `null` when missing. */
@@ -1 +1 @@
1
- import{connectionEntries,connectionProtocols}from"../../../packages/eve-catalog/src/index.js";const SUPPORTED_PROTOCOLS=[`mcp`],CUSTOM_CONNECTION_SLUG=`custom`,CONNECTION_AUTH={linear:{kind:`connect`,connector:`linear`,service:`mcp.linear.app`},notion:{kind:`connect`,connector:`notion`,service:`mcp.notion.com`},datadog:{kind:`connect`,connector:`datadog`,service:`mcp.datadoghq.com`},honeycomb:{kind:`connect`,connector:`honeycomb`,service:`mcp.honeycomb.io`}};function buildCatalogEntry(e,n,r){let i=CONNECTION_AUTH[e];if(i===void 0)throw Error(`Connection "${e}" is in the catalog but has no scaffolder auth overlay.`);let a={slug:e,label:n,protocols:connectionProtocols(r),description:r.description,auth:i};return r.mcp&&(a.mcp={url:r.mcp.url}),r.openapi&&(a.openapi={spec:r.openapi.spec,baseUrl:r.openapi.baseUrl}),a}const CONNECTION_CATALOG=connectionEntries().map(e=>{if(e.connection===void 0)throw Error(`Catalog connection "${e.slug}" is missing its connection identity.`);return buildCatalogEntry(e.slug,e.name,e.connection)}),CATALOG_BY_SLUG=new Map(CONNECTION_CATALOG.map(e=>[e.slug,e]));function getCatalogEntry(e){return CATALOG_BY_SLUG.get(e)}function catalogSlugs(){return CONNECTION_CATALOG.map(e=>e.slug)}function effectiveProtocols(e){let t=e===void 0||e.length===0?SUPPORTED_PROTOCOLS:e;return SUPPORTED_PROTOCOLS.filter(e=>t.includes(e))}const CONNECTION_SLUG_PATTERN=/^[a-z][a-z0-9-]{0,63}$/;function isValidConnectionSlug(e){return CONNECTION_SLUG_PATTERN.test(e)}function connectorServiceForEntry(e){if(e.auth.kind===`connect`)return e.auth.service?e.auth.service:mcpServiceHost(e.mcp?.url)}function mcpServiceHost(e){if(e)try{return new URL(e).host||void 0}catch{return}}function endpointForProtocol(e,t){return t===`mcp`?e.mcp??null:e.openapi??null}export{CONNECTION_CATALOG,CUSTOM_CONNECTION_SLUG,SUPPORTED_PROTOCOLS,catalogSlugs,connectorServiceForEntry,effectiveProtocols,endpointForProtocol,getCatalogEntry,isValidConnectionSlug,mcpServiceHost};
1
+ import{connectionEntries,connectionProtocols}from"../../../packages/eve-catalog/src/index.js";const SUPPORTED_PROTOCOLS=[`mcp`],CUSTOM_CONNECTION_SLUG=`custom`,CONNECTION_AUTH={linear:{kind:`connect`,connector:`linear`,service:`mcp.linear.app`},notion:{kind:`connect`,connector:`notion`,service:`mcp.notion.com`},datadog:{kind:`connect`,connector:`datadog`,service:`mcp.datadoghq.com`},honeycomb:{kind:`connect`,connector:`honeycomb`,service:`mcp.honeycomb.io`}};function buildCatalogEntry(e,n,r){let i=CONNECTION_AUTH[e];if(i===void 0)throw Error(`Connection "${e}" is in the catalog but has no scaffolder auth overlay.`);let a={slug:e,label:n,protocols:connectionProtocols(r),description:r.description,auth:i};return r.mcp&&(a.mcp={url:r.mcp.url}),r.openapi&&(a.openapi={spec:r.openapi.spec,baseUrl:r.openapi.baseUrl}),a}const CONNECTION_CATALOG=connectionEntries().map(e=>{if(e.connection===void 0)throw Error(`Catalog connection "${e.slug}" is missing its connection identity.`);return buildCatalogEntry(e.slug,e.name,e.connection)}),CATALOG_BY_SLUG=new Map(CONNECTION_CATALOG.map(e=>[e.slug,e]));function getCatalogEntry(e){return CATALOG_BY_SLUG.get(e)}function catalogSlugs(){return CONNECTION_CATALOG.map(e=>e.slug)}function effectiveProtocols(e){let t=e===void 0||e.length===0?SUPPORTED_PROTOCOLS:e;return SUPPORTED_PROTOCOLS.filter(e=>t.includes(e))}const CONNECTION_SLUG_PATTERN=/^[a-z][a-z0-9-]{0,63}$/;function isValidConnectionSlug(e){return CONNECTION_SLUG_PATTERN.test(e)}function connectorServiceForEntry(e){if(e.auth.kind===`connect`)return e.auth.service?e.auth.service:mcpServiceHost(e.mcp?.url)}function canonicalConnectorNameForEntry(e){if(e.auth?.kind!==`connect`)return;let t=e.auth.connector.trim();return t.length>0?t:void 0}function mcpServiceHost(e){if(e)try{return new URL(e).host||void 0}catch{return}}function endpointForProtocol(e,t){return t===`mcp`?e.mcp??null:e.openapi??null}export{CONNECTION_CATALOG,CUSTOM_CONNECTION_SLUG,SUPPORTED_PROTOCOLS,canonicalConnectorNameForEntry,catalogSlugs,connectorServiceForEntry,effectiveProtocols,endpointForProtocol,getCatalogEntry,isValidConnectionSlug,mcpServiceHost};
@@ -1,9 +1,15 @@
1
- import{pinnedNodeEngineMajor}from"../../node-engine.js";import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{applyPackageManagerWorkspaceConfiguration,isPackageManagerWorkspaceMember,patchWorkspaceRootPackageJson}from"../workspace-root.js";import{SUPPORTED_AUTHORED_MODULE_FILE_EXTENSIONS}from"../update/module-files.js";import{WEB_APP_TEMPLATE_FILES}from"./web-template.js";import{basename,join,resolve}from"node:path";import{mkdir,readdir,stat}from"node:fs/promises";const CURRENT_DIRECTORY_PROJECT_NAME=`.`,ALLOWED_CREATE_IN_PLACE_ENTRIES=new Set([`.DS_Store`,`.git`,`.gitkeep`,`.hg`]),DEFAULT_AI_PACKAGE_VERSION=`^7.0.0`,DEFAULT_CONNECT_PACKAGE_VERSION=`0.2.2`,DEFAULT_ZOD_PACKAGE_VERSION=`4.4.3`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.15.5`,nodeEngine:`>=24`};function resolveEvePackageContract(e=DEFAULT_EVE_PACKAGE_CONTRACT){return{version:resolveVersionToken(`evePackage.version`,e.version),nodeEngine:resolveVersionToken(`evePackage.nodeEngine`,e.nodeEngine)}}function modelProviderSlug(e){let t=(e.split(`/`)[0]??``).replaceAll(/[^A-Za-z0-9._-]/gu,``);return t.length>0?t:`anthropic`}function byokProviderEnvVar(e){let t=modelProviderSlug(e).toUpperCase().replaceAll(/[^A-Z0-9]/gu,`_`);return`${/^[0-9]/.test(t)?`_`:``}${t}_API_KEY`}function agentTemplateFiles(e){return{"agent/agent.ts":BASE_AGENT_TEMPLATE.replaceAll(`__EVE_INIT_MODEL__`,e),"agent/channels/eve.ts":WEB_APP_TEMPLATE_FILES[`agent/channels/eve.ts`],"agent/instructions.md":AGENT_INSTRUCTIONS_TEMPLATE}}function renderTemplate(e,t){return e.replaceAll(`__EVE_INIT_APP_NAME__`,t.appName).replaceAll(`__EVE_INIT_MODEL__`,t.model).replaceAll(`__EVE_INIT_BYOK_PROVIDER__`,modelProviderSlug(t.model)).replaceAll(`__EVE_INIT_BYOK_ENV_VAR__`,byokProviderEnvVar(t.model)).replaceAll(`__EVE_INIT_PACKAGE_VERSION__`,formatEveDependencySpecifier(t.eveVersion)).replaceAll(`__EVE_INIT_AI_SDK_VERSION__`,t.aiPackageVersion).replaceAll(`__EVE_INIT_CONNECT_VERSION__`,t.connectPackageVersion).replaceAll(`__EVE_INIT_ZOD_VERSION__`,t.zodPackageVersion).replaceAll(`__EVE_INIT_TYPESCRIPT_VERSION__`,t.typescriptPackageVersion).replaceAll(`__EVE_INIT_TYPES_NODE_VERSION__`,t.nodeTypesVersion).replaceAll(`__EVE_INIT_NODE_ENGINE__`,t.nodeEngine)}function formatEveDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}const BASE_AGENT_TEMPLATE=`import { defineAgent } from "eve";
1
+ import{pinnedNodeEngineMajor}from"../../node-engine.js";import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{applyPackageManagerWorkspaceConfiguration,isPackageManagerWorkspaceMember,patchWorkspaceRootPackageJson}from"../workspace-root.js";import{SUPPORTED_AUTHORED_MODULE_FILE_EXTENSIONS}from"../update/module-files.js";import{WEB_APP_TEMPLATE_FILES}from"./web-template.js";import{basename,join,resolve}from"node:path";import{mkdir,readdir,stat}from"node:fs/promises";const CURRENT_DIRECTORY_PROJECT_NAME=`.`,ALLOWED_CREATE_IN_PLACE_ENTRIES=new Set([`.DS_Store`,`.git`,`.gitkeep`,`.hg`]),DEFAULT_AI_PACKAGE_VERSION=`^7.0.0`,DEFAULT_CONNECT_PACKAGE_VERSION=`0.2.2`,DEFAULT_ZOD_PACKAGE_VERSION=`4.4.3`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.16.0`,nodeEngine:`>=24`};function resolveEvePackageContract(e=DEFAULT_EVE_PACKAGE_CONTRACT){return{version:resolveVersionToken(`evePackage.version`,e.version),nodeEngine:resolveVersionToken(`evePackage.nodeEngine`,e.nodeEngine)}}function modelProviderSlug(e){let t=(e.split(`/`)[0]??``).replaceAll(/[^A-Za-z0-9._-]/gu,``);return t.length>0?t:`anthropic`}function byokProviderEnvVar(e){let t=modelProviderSlug(e).toUpperCase().replaceAll(/[^A-Z0-9]/gu,`_`);return`${/^[0-9]/.test(t)?`_`:``}${t}_API_KEY`}function agentTemplateFiles(e){return{"agent/agent.ts":BASE_AGENT_TEMPLATE.replaceAll(`__EVE_INIT_MODEL__`,e),"agent/channels/eve.ts":WEB_APP_TEMPLATE_FILES[`agent/channels/eve.ts`],"agent/instructions.md":AGENT_INSTRUCTIONS_TEMPLATE}}function renderTemplate(e,t){return e.replaceAll(`__EVE_INIT_APP_NAME__`,t.appName).replaceAll(`__EVE_INIT_MODEL__`,t.model).replaceAll(`__EVE_INIT_BYOK_PROVIDER__`,modelProviderSlug(t.model)).replaceAll(`__EVE_INIT_BYOK_ENV_VAR__`,byokProviderEnvVar(t.model)).replaceAll(`__EVE_INIT_PACKAGE_VERSION__`,formatEveDependencySpecifier(t.eveVersion)).replaceAll(`__EVE_INIT_AI_SDK_VERSION__`,t.aiPackageVersion).replaceAll(`__EVE_INIT_CONNECT_VERSION__`,t.connectPackageVersion).replaceAll(`__EVE_INIT_ZOD_VERSION__`,t.zodPackageVersion).replaceAll(`__EVE_INIT_TYPESCRIPT_VERSION__`,t.typescriptPackageVersion).replaceAll(`__EVE_INIT_TYPES_NODE_VERSION__`,t.nodeTypesVersion).replaceAll(`__EVE_INIT_NODE_ENGINE__`,t.nodeEngine)}function formatEveDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}const BASE_AGENT_TEMPLATE=`import { defineAgent } from "eve";
2
2
 
3
3
  export default defineAgent({
4
4
  model: "__EVE_INIT_MODEL__",
5
5
  });
6
- `;function packageJsonTemplate(e){return`{
6
+ `;function packageManagerAiPinTemplateSuffix(e){switch(e){case`bun`:case`npm`:return`,
7
+ "overrides": {
8
+ "ai": "__EVE_INIT_AI_SDK_VERSION__"
9
+ }`;case`yarn`:return`,
10
+ "resolutions": {
11
+ "ai": "__EVE_INIT_AI_SDK_VERSION__"
12
+ }`;case`pnpm`:return``;default:return e}}function packageJsonTemplate(e){return`{
7
13
  "name": "__EVE_INIT_APP_NAME__",
8
14
  "version": "0.0.0",
9
15
  "type": "module",
@@ -26,15 +32,9 @@ export default defineAgent({
26
32
  "devDependencies": {
27
33
  "@types/node": "__EVE_INIT_TYPES_NODE_VERSION__",
28
34
  "typescript": "__EVE_INIT_TYPESCRIPT_VERSION__"
29
- }${e?ROOT_ONLY_PACKAGE_JSON_TEMPLATE_SUFFIX:``}
35
+ }${e.includeRootOnlyFields?`${packageManagerAiPinTemplateSuffix(e.packageManager)}${ROOT_ONLY_PACKAGE_JSON_TEMPLATE_SUFFIX}`:``}
30
36
  }
31
37
  `}const ROOT_ONLY_PACKAGE_JSON_TEMPLATE_SUFFIX=`,
32
- "overrides": {
33
- "ai": "__EVE_INIT_AI_SDK_VERSION__"
34
- },
35
- "resolutions": {
36
- "ai": "__EVE_INIT_AI_SDK_VERSION__"
37
- },
38
38
  "engines": {
39
39
  "node": "__EVE_INIT_NODE_ENGINE__"
40
40
  }
@@ -81,7 +81,7 @@ from the installed eve package docs. In most installs, those docs are at
81
81
  installed \`eve\` package location first and read its \`docs/\` directory. If
82
82
  package docs are unavailable, use https://eve.dev/docs as a fallback.
83
83
  `,"CLAUDE.md":`@AGENTS.md
84
- `};function templateFiles(e,t){return{"agent/agent.ts":e?`import { defineAgent } from "eve";
84
+ `};function templateFiles(e){return{"agent/agent.ts":e.byokProvider?`import { defineAgent } from "eve";
85
85
 
86
86
  export default defineAgent({
87
87
  model: "__EVE_INIT_MODEL__",
@@ -95,4 +95,4 @@ export default defineAgent({
95
95
  },
96
96
  },
97
97
  });
98
- `:BASE_AGENT_TEMPLATE,...SHARED_TEMPLATE_FILES,"package.json":packageJsonTemplate(t)}}async function assertCanCreateInPlace(e,n){if(!await pathExists(e))return;let r=(await readdir(e)).filter(e=>!ALLOWED_CREATE_IN_PLACE_ENTRIES.has(e));if(r.length>0&&!n){let e=r.slice(0,5).join(`, `),t=r.length>5?`, and ${r.length-5} more`:``;throw Error(`Cannot create project in current directory because it is not empty. Found: ${e}${t}. Use an empty directory.`)}}async function scaffoldBaseProject(o){let s=resolve(o.targetDirectory??process.cwd(),o.projectName),c=o.projectName===`.`,l=o.overwriteExisting??!1,u=o.byokProvider??!1,d=o.packageManager??`pnpm`,f=resolveEvePackageContract(o.evePackage),p=pinnedNodeEngineMajor(f.nodeEngine),m=resolve(o.workspaceProbeDirectory??s),h=isPackageManagerWorkspaceMember(d,m);if(c)await assertCanCreateInPlace(s,l);else if(await pathExists(s))throw Error(`Cannot create project because "${s}" already exists.`);let g={appName:basename(s),model:o.model,eveVersion:f.version,aiPackageVersion:resolveVersionToken(`aiPackageVersion`,o.aiPackageVersion??`^7.0.0`),connectPackageVersion:resolveVersionToken(`connectPackageVersion`,o.connectPackageVersion??`0.2.2`),zodPackageVersion:resolveVersionToken(`zodPackageVersion`,o.zodPackageVersion??`4.4.3`),typescriptPackageVersion:resolveVersionToken(`typescriptPackageVersion`,o.typescriptPackageVersion??`7.0.1-rc`),nodeTypesVersion:p,nodeEngine:p};await mkdir(s,{recursive:!0});for(let[e,r]of Object.entries(templateFiles(u,!h))){let i=`${s}/${e}`,a=await pathExists(i);await writeTextFile(i,renderTemplate(r,g),{force:c&&l}),a&&await o.onOverwriteFile?.(i)}return await applyPackageManagerWorkspaceConfiguration({packageManager:d,projectRoot:s,workspaceProbeRoot:m,onWorkspaceRootMutation:o.onWorkspaceRootMutation}),await patchWorkspaceRootPackageJson(d,m,{aiPackageVersion:g.aiPackageVersion,nodeEngineRequirement:f.nodeEngine,onWorkspaceRootMutation:o.onWorkspaceRootMutation}),s}async function isEveProject(e){for(let t of SUPPORTED_AUTHORED_MODULE_FILE_EXTENSIONS)try{return await stat(join(e,`agent`,`agent${t}`)),!0}catch{}return!1}export{CURRENT_DIRECTORY_PROJECT_NAME,DEFAULT_AI_PACKAGE_VERSION,DEFAULT_CONNECT_PACKAGE_VERSION,DEFAULT_EVE_PACKAGE_CONTRACT,DEFAULT_ZOD_PACKAGE_VERSION,agentTemplateFiles,byokProviderEnvVar,formatEveDependencySpecifier,isEveProject,modelProviderSlug,resolveEvePackageContract,scaffoldBaseProject};
98
+ `:BASE_AGENT_TEMPLATE,...SHARED_TEMPLATE_FILES,"package.json":packageJsonTemplate({includeRootOnlyFields:e.includeRootOnlyPackageJsonFields,packageManager:e.packageManager})}}async function assertCanCreateInPlace(e,n){if(!await pathExists(e))return;let r=(await readdir(e)).filter(e=>!ALLOWED_CREATE_IN_PLACE_ENTRIES.has(e));if(r.length>0&&!n){let e=r.slice(0,5).join(`, `),t=r.length>5?`, and ${r.length-5} more`:``;throw Error(`Cannot create project in current directory because it is not empty. Found: ${e}${t}. Use an empty directory.`)}}async function scaffoldBaseProject(o){let s=resolve(o.targetDirectory??process.cwd(),o.projectName),c=o.projectName===`.`,l=o.overwriteExisting??!1,u=o.byokProvider??!1,d=o.packageManager??`pnpm`,f=resolveEvePackageContract(o.evePackage),p=pinnedNodeEngineMajor(f.nodeEngine),m=resolve(o.workspaceProbeDirectory??s),h=isPackageManagerWorkspaceMember(d,m);if(c)await assertCanCreateInPlace(s,l);else if(await pathExists(s))throw Error(`Cannot create project because "${s}" already exists.`);let g={appName:basename(s),model:o.model,eveVersion:f.version,aiPackageVersion:resolveVersionToken(`aiPackageVersion`,o.aiPackageVersion??`^7.0.0`),connectPackageVersion:resolveVersionToken(`connectPackageVersion`,o.connectPackageVersion??`0.2.2`),zodPackageVersion:resolveVersionToken(`zodPackageVersion`,o.zodPackageVersion??`4.4.3`),typescriptPackageVersion:resolveVersionToken(`typescriptPackageVersion`,o.typescriptPackageVersion??`7.0.1-rc`),nodeTypesVersion:p,nodeEngine:p};await mkdir(s,{recursive:!0});for(let[e,r]of Object.entries(templateFiles({byokProvider:u,includeRootOnlyPackageJsonFields:!h,packageManager:d}))){let i=`${s}/${e}`,a=await pathExists(i);await writeTextFile(i,renderTemplate(r,g),{force:c&&l}),a&&await o.onOverwriteFile?.(i)}return await applyPackageManagerWorkspaceConfiguration({packageManager:d,projectRoot:s,workspaceProbeRoot:m,onWorkspaceRootMutation:o.onWorkspaceRootMutation}),await patchWorkspaceRootPackageJson(d,m,{aiPackageVersion:g.aiPackageVersion,nodeEngineRequirement:f.nodeEngine,onWorkspaceRootMutation:o.onWorkspaceRootMutation}),s}async function isEveProject(e){for(let t of SUPPORTED_AUTHORED_MODULE_FILE_EXTENSIONS)try{return await stat(join(e,`agent`,`agent${t}`)),!0}catch{}return!1}export{CURRENT_DIRECTORY_PROJECT_NAME,DEFAULT_AI_PACKAGE_VERSION,DEFAULT_CONNECT_PACKAGE_VERSION,DEFAULT_EVE_PACKAGE_CONTRACT,DEFAULT_ZOD_PACKAGE_VERSION,agentTemplateFiles,byokProviderEnvVar,formatEveDependencySpecifier,isEveProject,modelProviderSlug,resolveEvePackageContract,scaffoldBaseProject};
@@ -1,5 +1,5 @@
1
1
  export declare const WEB_APP_TEMPLATE_FILES: {
2
- readonly "agent/channels/eve.ts": 'import { eveChannel } from "eve/channels/eve";\nimport { localDev, placeholderAuth, vercelOidc } from "eve/channels/auth";\n\nexport default eveChannel({\n auth: [\n // Open on localhost for `eve dev` and the REPL; ignored in production.\n localDev(),\n // Lets the eve TUI and your Vercel deployments reach the deployed agent.\n vercelOidc(),\n // This placeholder will not allow browser requests in production.\n // Replace it with your app\'s auth provider, like Auth.js or Clerk,\n // or use none() for a public demo.\n placeholderAuth(),\n ],\n});\n';
2
+ readonly "agent/channels/eve.ts": 'import { eveChannel } from "eve/channels/eve";\nimport { localDev, placeholderAuth, vercelOidc } from "eve/channels/auth";\n\nexport default eveChannel({\n auth: [\n // Lets the eve TUI and your Vercel deployments reach the deployed agent.\n vercelOidc(),\n // Open on localhost for `eve dev` and the REPL; ignored in production.\n localDev(),\n // This placeholder will not allow browser requests in production.\n // Replace it with your app\'s auth provider, like Auth.js or Clerk,\n // or use none() for a public demo.\n placeholderAuth(),\n ],\n});\n';
3
3
  readonly "app/_components/agent-chat.tsx": '"use client";\n\nimport { useEveAgent } from "eve/react";\nimport { AlertCircleIcon } from "lucide-react";\nimport {\n Conversation,\n ConversationContent,\n ConversationScrollButton,\n} from "@/components/ai-elements/conversation";\nimport {\n PromptInput,\n type PromptInputMessage,\n PromptInputSubmit,\n PromptInputTextarea,\n} from "@/components/ai-elements/prompt-input";\nimport { cn } from "@/lib/utils";\nimport { AgentMessage } from "./agent-message";\n\nconst AGENT_NAME = "__EVE_INIT_APP_NAME__";\n\ntype AgentStatus = ReturnType<typeof useEveAgent>["status"];\n\nexport function AgentChat() {\n const agent = useEveAgent();\n const isBusy = agent.status === "submitted" || agent.status === "streaming";\n const isEmpty = agent.data.messages.length === 0;\n\n const handleSubmit = async (message: PromptInputMessage) => {\n const text = message.text.trim();\n if (!text || isBusy) return;\n\n await agent.send({ message: text });\n };\n\n const composer = (\n <PromptInput onSubmit={handleSubmit}>\n <PromptInputTextarea placeholder="Send a message…" />\n <PromptInputSubmit onStop={agent.stop} status={agent.status} />\n </PromptInput>\n );\n\n return (\n <main className="flex h-dvh flex-col overflow-hidden bg-background text-foreground">\n {isEmpty ? null : (\n <header className="flex h-14 shrink-0 items-center justify-center gap-3 pl-4 pr-2">\n <span className="flex min-w-0 items-center gap-2">\n <span className="truncate text-muted-foreground text-sm">{AGENT_NAME}</span>\n <StatusDot status={agent.status} />\n </span>\n </header>\n )}\n\n {agent.error ? (\n <div className="mx-auto w-full max-w-3xl shrink-0 px-4 pt-2 sm:px-6">\n <div className="flex items-start gap-3 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2.5 text-sm">\n <AlertCircleIcon className="mt-0.5 size-4 shrink-0 text-destructive" />\n <div>\n <p className="font-medium">Request failed</p>\n <p className="mt-0.5 text-muted-foreground">{agent.error.message}</p>\n </div>\n </div>\n </div>\n ) : null}\n\n {isEmpty ? null : (\n <Conversation className="min-h-0 flex-1">\n <ConversationContent className="mx-auto w-full max-w-3xl gap-6 px-4 py-6 sm:px-6">\n {agent.data.messages.map((message, index) => (\n <AgentMessage\n canRespond={!isBusy}\n isStreaming={\n agent.status === "streaming" && index === agent.data.messages.length - 1\n }\n key={message.id}\n message={message}\n onInputResponses={(inputResponses) => agent.send({ inputResponses })}\n />\n ))}\n </ConversationContent>\n <ConversationScrollButton />\n </Conversation>\n )}\n\n <div\n className={cn(\n "mx-auto w-full px-4 sm:px-6",\n isEmpty\n ? "flex max-w-xl flex-1 flex-col items-center justify-center gap-8 pb-[10vh]"\n : "max-w-3xl shrink-0 pb-6",\n )}\n >\n {isEmpty ? (\n <div className="flex flex-col items-center gap-3 text-center">\n <h1 className="font-medium text-5xl tracking-tighter">{AGENT_NAME}</h1>\n </div>\n ) : null}\n <div className="w-full">{composer}</div>\n </div>\n </main>\n );\n}\n\nfunction StatusDot({ status }: { readonly status: AgentStatus }) {\n const isLive = status === "submitted" || status === "streaming";\n const tone =\n status === "error"\n ? "bg-destructive"\n : isLive\n ? "bg-emerald-500"\n : status === "ready"\n ? "bg-muted-foreground"\n : "bg-muted-foreground/50";\n\n return (\n <span className="relative flex size-1">\n {isLive ? (\n <span\n className={cn(\n "absolute inline-flex size-full animate-ping rounded-full opacity-75",\n tone,\n )}\n />\n ) : null}\n <span className={cn("relative inline-flex size-1 rounded-full transition-colors", tone)} />\n </span>\n );\n}\n';
4
4
  readonly "app/_components/agent-message.tsx": '"use client";\n\nimport type {\n EveAuthorizationPart,\n EveDynamicToolPart,\n EveMessage,\n EveMessagePart,\n} from "eve/react";\nimport { CheckCircleIcon, ExternalLinkIcon, KeyRoundIcon, XCircleIcon } from "lucide-react";\nimport { Message, MessageContent, MessageResponse } from "@/components/ai-elements/message";\nimport { Reasoning, ReasoningContent, ReasoningTrigger } from "@/components/ai-elements/reasoning";\nimport {\n Tool,\n ToolContent,\n ToolHeader,\n ToolInput,\n ToolOutput,\n} from "@/components/ai-elements/tool";\nimport { Button } from "@/components/ui/button";\nimport { cn } from "@/lib/utils";\n\nexport type AgentInputResponse = {\n readonly optionId?: string;\n readonly requestId: string;\n readonly text?: string;\n};\n\nexport function AgentMessage({\n canRespond,\n isStreaming,\n message,\n onInputResponses,\n}: {\n readonly canRespond: boolean;\n readonly isStreaming: boolean;\n readonly message: EveMessage;\n readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;\n}) {\n const lastTextIndex = message.parts.reduce(\n (last, part, index) => (part.type === "text" ? index : last),\n -1,\n );\n\n return (\n <Message\n data-optimistic={message.metadata?.optimistic ? "true" : undefined}\n from={message.role}\n >\n <MessageContent>\n {message.parts.map((part, index) => (\n <AgentMessagePart\n canRespond={canRespond}\n key={partKey(part, index)}\n onInputResponses={onInputResponses}\n part={part}\n showCaret={isStreaming && message.role === "assistant" && index === lastTextIndex}\n />\n ))}\n </MessageContent>\n </Message>\n );\n}\n\nfunction AgentMessagePart({\n canRespond,\n onInputResponses,\n part,\n showCaret,\n}: {\n readonly canRespond: boolean;\n readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;\n readonly part: EveMessagePart;\n readonly showCaret: boolean;\n}) {\n switch (part.type) {\n case "step-start":\n return null;\n case "text":\n return (\n <MessageResponse caret="block" isAnimating={showCaret}>\n {part.text}\n </MessageResponse>\n );\n case "reasoning":\n return (\n <Reasoning defaultOpen isStreaming={part.state === "streaming"}>\n <ReasoningTrigger />\n <ReasoningContent>{part.text}</ReasoningContent>\n </Reasoning>\n );\n case "authorization":\n return <AuthorizationPrompt part={part} />;\n case "dynamic-tool":\n return (\n <Tool\n defaultOpen={part.state === "approval-requested" || part.state === "approval-responded"}\n >\n <ToolHeader\n state={part.state}\n title={part.toolName}\n toolName={part.toolName}\n type="dynamic-tool"\n />\n <ToolContent>\n <ToolInput input={part.input} />\n <InputRequestActions\n canRespond={canRespond}\n part={part}\n onInputResponses={onInputResponses}\n />\n <ToolOutput errorText={part.errorText} output={part.output} />\n </ToolContent>\n </Tool>\n );\n }\n}\n\nfunction AuthorizationPrompt({ part }: { readonly part: EveAuthorizationPart }) {\n const isAuthorized = part.state === "completed" && part.outcome === "authorized";\n const isCompleted = part.state === "completed";\n const Icon = isAuthorized ? CheckCircleIcon : isCompleted ? XCircleIcon : KeyRoundIcon;\n const instructions = part.authorization?.instructions;\n const shouldShowInstructions = instructions !== undefined && instructions !== part.description;\n\n return (\n <div\n className={cn(\n "space-y-3 rounded-md border p-3",\n isAuthorized\n ? "border-emerald-500/30 bg-emerald-500/5"\n : isCompleted\n ? "border-destructive/30 bg-destructive/5"\n : "border-blue-500/30 bg-blue-500/5",\n )}\n >\n <div className="flex items-start gap-3">\n <span\n className={cn(\n "mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-full",\n isAuthorized\n ? "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300"\n : isCompleted\n ? "bg-destructive/10 text-destructive"\n : "bg-blue-500/10 text-blue-700 dark:text-blue-300",\n )}\n >\n <Icon className="size-4" />\n </span>\n <div className="min-w-0 flex-1 space-y-2">\n <p className="font-medium text-sm">{authorizationTitle(part)}</p>\n <p className="text-muted-foreground text-sm">{authorizationDescription(part)}</p>\n {shouldShowInstructions ? (\n <p className="text-muted-foreground text-sm">{instructions}</p>\n ) : null}\n {part.state === "required" && part.authorization?.userCode ? (\n <div className="flex flex-wrap items-center gap-2 text-sm">\n <span className="text-muted-foreground">Code</span>\n <code className="rounded-md bg-background px-2 py-1 font-mono">\n {part.authorization.userCode}\n </code>\n </div>\n ) : null}\n {part.state === "required" && part.authorization?.url ? (\n <Button asChild size="sm">\n <a href={part.authorization.url} rel="noreferrer" target="_blank">\n <ExternalLinkIcon className="size-4" />\n Sign in with {part.displayName}\n </a>\n </Button>\n ) : null}\n </div>\n </div>\n </div>\n );\n}\n\nfunction authorizationTitle(part: EveAuthorizationPart): string {\n if (part.state === "required") {\n return `Connect ${part.displayName}`;\n }\n if (part.outcome === "authorized") {\n return `${part.displayName} connected`;\n }\n return `${part.displayName} authorization ${formatAuthorizationOutcome(part.outcome)}`;\n}\n\nfunction authorizationDescription(part: EveAuthorizationPart): string {\n if (part.state === "required") {\n return part.description;\n }\n if (part.outcome === "authorized") {\n return `${part.displayName} connected.`;\n }\n const tail = part.reason !== undefined ? ` (${part.reason})` : "";\n return `${part.displayName} authorization ${formatAuthorizationOutcome(part.outcome)}${tail}.`;\n}\n\nfunction formatAuthorizationOutcome(outcome: NonNullable<EveAuthorizationPart["outcome"]>): string {\n switch (outcome) {\n case "authorized":\n return "authorized";\n case "declined":\n return "declined";\n case "failed":\n return "failed";\n case "timed-out":\n return "timed out";\n }\n}\n\nfunction InputRequestActions({\n canRespond,\n onInputResponses,\n part,\n}: {\n readonly canRespond: boolean;\n readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;\n readonly part: EveDynamicToolPart;\n}) {\n const inputRequest = part.toolMetadata?.eve?.inputRequest;\n if (!inputRequest) {\n return null;\n }\n\n const inputResponse = part.toolMetadata?.eve?.inputResponse;\n const selectedOption = inputRequest.options?.find(\n (option) => option.id === inputResponse?.optionId,\n );\n\n return (\n <div className="space-y-3 rounded-md border border-yellow-500/30 bg-yellow-500/5 p-3">\n <p className="text-muted-foreground text-sm">{inputRequest.prompt}</p>\n {inputResponse ? (\n <p className="font-medium text-sm">\n Responded: {selectedOption?.label ?? inputResponse.text ?? inputResponse.optionId}\n </p>\n ) : (\n <div className="flex flex-wrap gap-2">\n {inputRequest.options?.map((option) => (\n <Button\n disabled={!canRespond}\n key={option.id}\n onClick={() => {\n void onInputResponses([\n {\n optionId: option.id,\n requestId: inputRequest.requestId,\n },\n ]);\n }}\n size="sm"\n type="button"\n variant={option.style === "danger" ? "destructive" : "default"}\n >\n {option.label}\n </Button>\n ))}\n </div>\n )}\n </div>\n );\n}\n\nfunction partKey(part: EveMessagePart, index: number): string {\n switch (part.type) {\n case "authorization":\n return `authorization:${part.turnId}:${part.stepIndex}:${part.name}`;\n case "dynamic-tool":\n return part.toolCallId;\n default:\n return `${part.type}:${index}`;\n }\n}\n';
5
5
  readonly "app/globals.css": '@import "tailwindcss";\n@source "../node_modules/streamdown/dist/*.js";\n\n@theme inline {\n --color-background: var(--background);\n --color-foreground: var(--foreground);\n --color-card: var(--card);\n --color-card-foreground: var(--card-foreground);\n --color-popover: var(--popover);\n --color-popover-foreground: var(--popover-foreground);\n --color-primary: var(--primary);\n --color-primary-foreground: var(--primary-foreground);\n --color-secondary: var(--secondary);\n --color-secondary-foreground: var(--secondary-foreground);\n --color-muted: var(--muted);\n --color-muted-foreground: var(--muted-foreground);\n --color-accent: var(--accent);\n --color-accent-foreground: var(--accent-foreground);\n --color-destructive: var(--destructive);\n --color-border: var(--border);\n --color-input: var(--input);\n --color-ring: var(--ring);\n --radius-sm: calc(var(--radius) - 4px);\n --radius-md: calc(var(--radius) - 2px);\n --radius-lg: var(--radius);\n --radius-xl: calc(var(--radius) + 4px);\n --font-sans: "Geist", "Geist Fallback", ui-sans-serif, system-ui, sans-serif;\n --font-mono: "Geist Mono", "Geist Mono Fallback", ui-monospace, monospace;\n}\n\n:root {\n color-scheme: light;\n /* Soft neutral page with white elevated surfaces so cards/composer pop. */\n --background: oklch(0.971 0 0);\n --foreground: oklch(0.16 0 0);\n --card: oklch(1 0 0);\n --card-foreground: oklch(0.16 0 0);\n --popover: oklch(1 0 0);\n --popover-foreground: oklch(0.16 0 0);\n --primary: oklch(0.19 0 0);\n --primary-foreground: oklch(0.985 0 0);\n --secondary: oklch(0.94 0 0);\n --secondary-foreground: oklch(0.19 0 0);\n --muted: oklch(0.94 0 0);\n --muted-foreground: oklch(0.6 0 0);\n --accent: oklch(0.94 0 0);\n --accent-foreground: oklch(0.19 0 0);\n --destructive: oklch(0.577 0.245 27.325);\n --border: oklch(0.916 0 0);\n --input: oklch(0.916 0 0);\n --ring: oklch(0.708 0 0);\n --radius: 0.625rem;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n color-scheme: dark;\n --background: oklch(0.145 0 0);\n --foreground: oklch(0.985 0 0);\n --card: oklch(0.205 0 0);\n --card-foreground: oklch(0.985 0 0);\n --popover: oklch(0.205 0 0);\n --popover-foreground: oklch(0.985 0 0);\n --primary: oklch(0.922 0 0);\n --primary-foreground: oklch(0.205 0 0);\n --secondary: oklch(0.269 0 0);\n --secondary-foreground: oklch(0.985 0 0);\n --muted: oklch(0.269 0 0);\n --muted-foreground: oklch(0.708 0 0);\n --accent: oklch(0.269 0 0);\n --accent-foreground: oklch(0.985 0 0);\n --destructive: oklch(0.704 0.191 22.216);\n --border: oklch(1 0 0 / 10%);\n --input: oklch(1 0 0 / 15%);\n --ring: oklch(0.556 0 0);\n }\n}\n\n* {\n border-color: var(--border);\n}\n\nhtml {\n height: 100%;\n}\n\nbody {\n min-height: 100%;\n margin: 0;\n background: var(--background);\n font-family: var(--font-sans);\n}\n\nbutton,\ninput,\ntextarea {\n font: inherit;\n}\n';
@@ -3,10 +3,10 @@ import { localDev, placeholderAuth, vercelOidc } from "eve/channels/auth";
3
3
 
4
4
  export default eveChannel({
5
5
  auth: [
6
- // Open on localhost for \`eve dev\` and the REPL; ignored in production.
7
- localDev(),
8
6
  // Lets the eve TUI and your Vercel deployments reach the deployed agent.
9
7
  vercelOidc(),
8
+ // Open on localhost for \`eve dev\` and the REPL; ignored in production.
9
+ localDev(),
10
10
  // This placeholder will not allow browser requests in production.
11
11
  // Replace it with your app's auth provider, like Auth.js or Clerk,
12
12
  // or use none() for a public demo.
@@ -1,7 +1,7 @@
1
1
  export { byokProviderEnvVar, CURRENT_DIRECTORY_PROJECT_NAME, isEveProject, scaffoldBaseProject, type EvePackageContract, type ScaffoldBaseProjectOptions, } from "./create/project.js";
2
2
  export { DEFAULT_SLACK_CONNECTOR_SLUG, SLACK_CHANNEL_DEFAULT_ROUTE, deriveSlackConnectorSlug, ensureChannel, hasVercelHostFramework, isNextJsProject, listAuthoredChannels, normalizeSlackConnectorSlug, type ChannelKind, type ChannelMutationResult, type EnsureChannelOptions, type SlackConnectorSlug, type WebPackageVersions, } from "./update/channels.js";
3
3
  export { SCAFFOLDABLE_CHANNELS, type ScaffoldableChannel } from "./channels-catalog.js";
4
- export { ensureConnection, listAuthoredConnections, type ConnectionInput, type ConnectionMutationAction, type ConnectionMutationResult, type EnsureConnectionOptions, } from "./update/connections.js";
4
+ export { ensureConnection, ensureConnectionDependencies, listAuthoredConnections, type ConnectionInput, type ConnectionMutationAction, type ConnectionMutationResult, type EnsureConnectionOptions, type EnsureConnectionDependenciesOptions, } from "./update/connections.js";
5
5
  export { catalogSlugs, CONNECTION_CATALOG, CUSTOM_CONNECTION_SLUG, effectiveProtocols, endpointForProtocol, getCatalogEntry, isValidConnectionSlug, SUPPORTED_PROTOCOLS, type ConnectionAuthSpec, type ConnectionCatalogEntry, type ConnectionProtocol, type CustomConnectionInput, type EnvHeader, type McpEndpoint, type OpenApiEndpoint, } from "./connections/catalog.js";
6
6
  export { WriteFileExistsError } from "./files.js";
7
7
  export { HumanActionRequiredError, type HumanAction } from "../human-action.js";
@@ -1 +1 @@
1
- import{HumanActionRequiredError}from"../human-action.js";import{SCAFFOLDABLE_CHANNELS}from"./channels-catalog.js";import{CONNECTION_CATALOG,CUSTOM_CONNECTION_SLUG,SUPPORTED_PROTOCOLS,catalogSlugs,effectiveProtocols,endpointForProtocol,getCatalogEntry,isValidConnectionSlug}from"./connections/catalog.js";import{WriteFileExistsError}from"./files.js";import{CURRENT_DIRECTORY_PROJECT_NAME,byokProviderEnvVar,isEveProject,scaffoldBaseProject}from"./create/project.js";import{DEFAULT_SLACK_CONNECTOR_SLUG,SLACK_CHANNEL_DEFAULT_ROUTE,deriveSlackConnectorSlug,ensureChannel,hasVercelHostFramework,isNextJsProject,listAuthoredChannels,normalizeSlackConnectorSlug}from"./update/channels.js";import{ensureConnection,listAuthoredConnections}from"./update/connections.js";export{CONNECTION_CATALOG,CURRENT_DIRECTORY_PROJECT_NAME,CUSTOM_CONNECTION_SLUG,DEFAULT_SLACK_CONNECTOR_SLUG,HumanActionRequiredError,SCAFFOLDABLE_CHANNELS,SLACK_CHANNEL_DEFAULT_ROUTE,SUPPORTED_PROTOCOLS,WriteFileExistsError,byokProviderEnvVar,catalogSlugs,deriveSlackConnectorSlug,effectiveProtocols,endpointForProtocol,ensureChannel,ensureConnection,getCatalogEntry,hasVercelHostFramework,isEveProject,isNextJsProject,isValidConnectionSlug,listAuthoredChannels,listAuthoredConnections,normalizeSlackConnectorSlug,scaffoldBaseProject};
1
+ import{CONNECTION_CATALOG,CUSTOM_CONNECTION_SLUG,SUPPORTED_PROTOCOLS,catalogSlugs,effectiveProtocols,endpointForProtocol,getCatalogEntry,isValidConnectionSlug}from"./connections/catalog.js";import{HumanActionRequiredError}from"../human-action.js";import{SCAFFOLDABLE_CHANNELS}from"./channels-catalog.js";import{WriteFileExistsError}from"./files.js";import{CURRENT_DIRECTORY_PROJECT_NAME,byokProviderEnvVar,isEveProject,scaffoldBaseProject}from"./create/project.js";import{DEFAULT_SLACK_CONNECTOR_SLUG,SLACK_CHANNEL_DEFAULT_ROUTE,deriveSlackConnectorSlug,ensureChannel,hasVercelHostFramework,isNextJsProject,listAuthoredChannels,normalizeSlackConnectorSlug}from"./update/channels.js";import{ensureConnection,ensureConnectionDependencies,listAuthoredConnections}from"./update/connections.js";export{CONNECTION_CATALOG,CURRENT_DIRECTORY_PROJECT_NAME,CUSTOM_CONNECTION_SLUG,DEFAULT_SLACK_CONNECTOR_SLUG,HumanActionRequiredError,SCAFFOLDABLE_CHANNELS,SLACK_CHANNEL_DEFAULT_ROUTE,SUPPORTED_PROTOCOLS,WriteFileExistsError,byokProviderEnvVar,catalogSlugs,deriveSlackConnectorSlug,effectiveProtocols,endpointForProtocol,ensureChannel,ensureConnection,ensureConnectionDependencies,getCatalogEntry,hasVercelHostFramework,isEveProject,isNextJsProject,isValidConnectionSlug,listAuthoredChannels,listAuthoredConnections,normalizeSlackConnectorSlug,scaffoldBaseProject};
@@ -28,6 +28,12 @@ export interface EnsureConnectionOptions {
28
28
  force?: boolean;
29
29
  connectPackageVersion?: string;
30
30
  }
31
+ export interface EnsureConnectionDependenciesOptions {
32
+ projectRoot: string;
33
+ connectPackageVersion?: string;
34
+ }
35
+ /** Adds the package dependency required by a Connect-auth connection. */
36
+ export declare function ensureConnectionDependencies(options: EnsureConnectionDependenciesOptions): Promise<PackageJsonMutation[]>;
31
37
  /**
32
38
  * Scaffolds `agent/connections/<slug>.ts` from a catalog entry or custom
33
39
  * input, patching `package.json` for Connect-auth connections and seeding
@@ -1,4 +1,4 @@
1
- import{endpointForProtocol}from"../connections/catalog.js";import{pathExists,writeTextFile}from"../files.js";import{patchPackageJson}from"./package-json.js";import{resolveVersionToken}from"../version-tokens.js";import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{join}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const CONNECT_PACKAGE_NAME=`@vercel/connect`,USER_AUTHORED_CONNECTION_DIR=`agent/connections`;function resolveAuth(e){return e.auth??{kind:`none`}}function envKeysForAuth(e){switch(e.kind){case`bearer-env`:return[e.envVar];case`header`:return e.headers.map(e=>e.envVar);case`connect`:case`none`:return[]}}function authBlock(e){switch(e.kind){case`connect`:return` auth: connect("${e.connector}"),\n`;case`bearer-env`:return` auth: { getToken: async () => ({ token: process.env.${e.envVar}! }) },\n`;case`header`:return` headers: () => ({\n${e.headers.map(e=>` "${e.header}": process.env.${e.envVar}!,`).join(`
1
+ import{endpointForProtocol}from"../connections/catalog.js";import{pathExists,writeTextFile}from"../files.js";import{patchPackageJson}from"./package-json.js";import{resolveVersionToken}from"../version-tokens.js";import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{join}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const DEFAULT_CONNECT_PACKAGE_VERSION=`0.2.2`,CONNECT_PACKAGE_NAME=`@vercel/connect`,USER_AUTHORED_CONNECTION_DIR=`agent/connections`;function resolveAuth(e){return e.auth??{kind:`none`}}function envKeysForAuth(e){switch(e.kind){case`bearer-env`:return[e.envVar];case`header`:return e.headers.map(e=>e.envVar);case`connect`:case`none`:return[]}}function authBlock(e){switch(e.kind){case`connect`:return` auth: connect("${e.connector}"),\n`;case`bearer-env`:return` auth: { getToken: async () => ({ token: process.env.${e.envVar}! }) },\n`;case`header`:return` headers: () => ({\n${e.headers.map(e=>` "${e.header}": process.env.${e.envVar}!,`).join(`
2
2
  `)}\n }),\n`;case`none`:return``}}function renderMcpTemplate(e,t,n){return`${n.kind===`connect`?`import { connect } from "@vercel/connect/eve";
3
3
  import { defineMcpClientConnection } from "eve/connections";
4
4
  `:`import { defineMcpClientConnection } from "eve/connections";
@@ -15,7 +15,7 @@ export default defineOpenAPIConnection({
15
15
  spec: "${e.spec}",
16
16
  ${i} description: "${t}",
17
17
  ${authBlock(n)}});
18
- `}function renderTemplate(e,t,n,r){return e===`mcp`?renderMcpTemplate(t,n,r):renderOpenApiTemplate(t,n,r)}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function ensureConnectDependency(e,n){if(!await pathExists(e))return[];let i=JSON.parse(await readFile(e,`utf8`));return(isJsonObject(i)&&isJsonObject(i.dependencies)?i.dependencies[CONNECT_PACKAGE_NAME]:void 0)===n?[]:(await patchPackageJson(e,{dependencies:{[CONNECT_PACKAGE_NAME]:n}}),[{path:e,dependencies:[CONNECT_PACKAGE_NAME],devDependencies:[],scripts:[]}])}function envKeyPresent(e,t){return RegExp(`^\\s*(?:export\\s+)?${t}\\s*=`,`m`).test(e)}async function seedEnvPlaceholders(e,n){if(n.length===0)return[];let r=``;await pathExists(e)&&(r=await readFile(e,`utf8`));let i=n.filter(e=>!envKeyPresent(r,e));if(i.length===0)return[];let a=`${r.length>0&&!r.endsWith(`
18
+ `}function renderTemplate(e,t,n,r){return e===`mcp`?renderMcpTemplate(t,n,r):renderOpenApiTemplate(t,n,r)}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function ensureConnectDependency(e,n){if(!await pathExists(e))return[];let i=JSON.parse(await readFile(e,`utf8`));return(isJsonObject(i)&&isJsonObject(i.dependencies)?i.dependencies[CONNECT_PACKAGE_NAME]:void 0)===n?[]:(await patchPackageJson(e,{dependencies:{[CONNECT_PACKAGE_NAME]:n}}),[{path:e,dependencies:[CONNECT_PACKAGE_NAME],devDependencies:[],scripts:[]}])}async function ensureConnectionDependencies(e){let t=resolveVersionToken(`connectPackageVersion`,e.connectPackageVersion??DEFAULT_CONNECT_PACKAGE_VERSION);return ensureConnectDependency(join(e.projectRoot,`package.json`),t)}function envKeyPresent(e,t){return RegExp(`^\\s*(?:export\\s+)?${t}\\s*=`,`m`).test(e)}async function seedEnvPlaceholders(e,n){if(n.length===0)return[];let r=``;await pathExists(e)&&(r=await readFile(e,`utf8`));let i=n.filter(e=>!envKeyPresent(r,e));if(i.length===0)return[];let a=`${r.length>0&&!r.endsWith(`
19
19
  `)?`
20
20
  `:``}${i.map(e=>`${e}=`).join(`
21
- `)}\n`;return await writeFile(e,r+a,`utf8`),i}async function ensureConnection(r){let a=r.slug??r.entry.slug,o=resolveAuth(r.entry),s=endpointForProtocol(r.entry,r.protocol);if(s===null)throw Error(`Connection "${a}" is missing a ${r.protocol===`mcp`?`mcp.url`:`openapi.spec`} endpoint for protocol "${r.protocol}".`);let c=join(r.projectRoot,USER_AUTHORED_CONNECTION_DIR,`${a}.ts`),l=envKeysForAuth(o),u=await pathExists(c);if(!r.force&&u)return{slug:a,protocol:r.protocol,action:`skipped`,filePath:c,filesWritten:[],filesSkipped:[c],packageJsonUpdated:[],envKeysAdded:[],envKeysRequired:l};let d=[];if(o.kind===`connect`){let e=resolveVersionToken(`connectPackageVersion`,r.connectPackageVersion??`0.2.2`);d.push(...await ensureConnectDependency(join(r.projectRoot,`package.json`),e))}await writeTextFile(c,renderTemplate(r.protocol,s,r.entry.description,o),{force:!0});let f=await seedEnvPlaceholders(join(r.projectRoot,`.env.local`),l),p={slug:a,protocol:r.protocol,action:u?`overwritten`:`created`,filePath:c,filesWritten:[c],filesSkipped:[],packageJsonUpdated:d,envKeysAdded:f,envKeysRequired:l};return u&&(p.filesOverwritten=[c]),p}async function listAuthoredConnections(e){let t=join(e,USER_AUTHORED_CONNECTION_DIR),n;try{n=await readdir(t,{withFileTypes:!0})}catch(e){if(e.code===`ENOENT`)return[];throw e}let r=[];for(let e of n){if(e.isFile()){let t=getSupportedModuleBaseName(e.name);t!==null&&r.push(t);continue}if(e.isDirectory())try{(await readdir(join(t,e.name))).some(e=>matchesSupportedModuleBaseName(e,`connection`))&&r.push(e.name)}catch{}}return r.sort()}export{ensureConnection,listAuthoredConnections};
21
+ `)}\n`;return await writeFile(e,r+a,`utf8`),i}async function ensureConnection(r){let a=r.slug??r.entry.slug,o=resolveAuth(r.entry),s=endpointForProtocol(r.entry,r.protocol);if(s===null)throw Error(`Connection "${a}" is missing a ${r.protocol===`mcp`?`mcp.url`:`openapi.spec`} endpoint for protocol "${r.protocol}".`);let c=join(r.projectRoot,USER_AUTHORED_CONNECTION_DIR,`${a}.ts`),l=envKeysForAuth(o),u=await pathExists(c);if(!r.force&&u)return{slug:a,protocol:r.protocol,action:`skipped`,filePath:c,filesWritten:[],filesSkipped:[c],packageJsonUpdated:[],envKeysAdded:[],envKeysRequired:l};let d=[];if(o.kind===`connect`){let e=resolveVersionToken(`connectPackageVersion`,r.connectPackageVersion??DEFAULT_CONNECT_PACKAGE_VERSION);d.push(...await ensureConnectDependency(join(r.projectRoot,`package.json`),e))}await writeTextFile(c,renderTemplate(r.protocol,s,r.entry.description,o),{force:!0});let f=await seedEnvPlaceholders(join(r.projectRoot,`.env.local`),l),p={slug:a,protocol:r.protocol,action:u?`overwritten`:`created`,filePath:c,filesWritten:[c],filesSkipped:[],packageJsonUpdated:d,envKeysAdded:f,envKeysRequired:l};return u&&(p.filesOverwritten=[c]),p}async function listAuthoredConnections(e){let t=join(e,USER_AUTHORED_CONNECTION_DIR),n;try{n=await readdir(t,{withFileTypes:!0})}catch(e){if(e.code===`ENOENT`)return[];throw e}let r=[];for(let e of n){if(e.isFile()){let t=getSupportedModuleBaseName(e.name);t!==null&&r.push(t);continue}if(e.isDirectory())try{(await readdir(join(t,e.name))).some(e=>matchesSupportedModuleBaseName(e,`connection`))&&r.push(e.name)}catch{}}return r.sort()}export{ensureConnection,ensureConnectionDependencies,listAuthoredConnections};
@@ -12,7 +12,7 @@ import { eveChannel } from "eve/channels/eve";
12
12
  import { localDev, vercelOidc } from "eve/channels/auth";
13
13
 
14
14
  export default eveChannel({
15
- auth: [localDev(), vercelOidc()],
15
+ auth: [vercelOidc(), localDev()],
16
16
  });
17
17
  ```
18
18
 
@@ -54,7 +54,7 @@ The `auth` option decides who can call these routes. The built-in helpers cover
54
54
 
55
55
  Neither admits browser users or external clients in production. For a public app, wire the channel to your own auth (Clerk, Auth.js, your own OIDC/JWT verification, an API-key verifier, or any custom `AuthFn`). Vercel OIDC is optional; use it only when Vercel-issued deployment tokens are part of your trust model.
56
56
 
57
- `eve init` scaffolds an `agent/channels/eve.ts` with a production placeholder so you replace it before going live. The generated channel allows Vercel OIDC and localhost, and includes `placeholderAuth()`, which returns a setup-focused 401 in production until you swap it for real auth. Delete the file and eve falls back to `[localDev(), vercelOidc()]`, which still does not admit browser users in production.
57
+ `eve init` scaffolds an `agent/channels/eve.ts` with a production placeholder so you replace it before going live. The generated channel checks Vercel OIDC before falling back to localhost access, and includes `placeholderAuth()`, which returns a setup-focused 401 in production until you swap it for real auth. Delete the file and eve falls back to `[vercelOidc(), localDev()]`, which still does not admit browser users in production.
58
58
 
59
59
  For the full auth model and helper list, see [Auth & route protection](../guides/auth-and-route-protection).
60
60
 
@@ -67,7 +67,7 @@ import { eveChannel, defaultEveAuth } from "eve/channels/eve";
67
67
  import { localDev, vercelOidc } from "eve/channels/auth";
68
68
 
69
69
  export default eveChannel({
70
- auth: [localDev(), vercelOidc()],
70
+ auth: [vercelOidc(), localDev()],
71
71
  onMessage(ctx, message) {
72
72
  const callerId = ctx.eve.caller?.principalId ?? "anonymous";
73
73
  return {
@@ -108,7 +108,7 @@ Start with the [TypeScript SDK](../guides/client/overview) guide. It covers basi
108
108
  curl http://127.0.0.1:3000/eve/v1/info
109
109
  ```
110
110
 
111
- The route uses the same default auth chain as the eve channel (`[localDev(), vercelOidc()]`). Locally it answers anonymously; a deployed Vercel target requires a valid OIDC bearer, with a same-project bypass for in-deployment callers. See [auth & route protection](../guides/auth-and-route-protection).
111
+ The route uses the same default auth chain as the eve channel (`[vercelOidc(), localDev()]`). A local Vercel OIDC bearer takes precedence; other local requests fall back to development access. A deployed Vercel target requires a valid OIDC bearer, with a same-project bypass for in-deployment callers. See [auth & route protection](../guides/auth-and-route-protection).
112
112
 
113
113
  ## Dispatch order
114
114