eve 0.13.5 → 0.13.7

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 (131) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/src/channel/send.d.ts +3 -1
  3. package/dist/src/channel/send.js +1 -1
  4. package/dist/src/channel/types.d.ts +6 -0
  5. package/dist/src/chunks/{use-eve-agent-_dbX0ASK.js → use-eve-agent-8X2UMr8q.js} +26 -2
  6. package/dist/src/chunks/{use-eve-agent-BjAM8_a3.js → use-eve-agent-9ZNiSFMb.js} +26 -2
  7. package/dist/src/cli/banner.d.ts +2 -4
  8. package/dist/src/cli/banner.js +1 -1
  9. package/dist/src/cli/commands/agent-prompt/build-and-verify.md +7 -4
  10. package/dist/src/cli/dev/tui/agent-header.js +1 -1
  11. package/dist/src/cli/dev/tui/blocks.d.ts +1 -1
  12. package/dist/src/cli/dev/tui/blocks.js +1 -1
  13. package/dist/src/cli/dev/tui/prompt-command-handler.d.ts +5 -1
  14. package/dist/src/cli/dev/tui/prompt-command-handler.js +1 -1
  15. package/dist/src/cli/dev/tui/prompt-commands.d.ts +7 -7
  16. package/dist/src/cli/dev/tui/prompt-commands.js +2 -2
  17. package/dist/src/cli/dev/tui/remote-auth-command.d.ts +11 -0
  18. package/dist/src/cli/dev/tui/remote-auth-command.js +3 -0
  19. package/dist/src/cli/dev/tui/remote-auth-result.d.ts +5 -0
  20. package/dist/src/cli/dev/tui/remote-auth-result.js +2 -1
  21. package/dist/src/cli/dev/tui/remote-auth.d.ts +30 -0
  22. package/dist/src/cli/dev/tui/remote-auth.js +1 -0
  23. package/dist/src/cli/dev/tui/remote-connection-probe.d.ts +3 -1
  24. package/dist/src/cli/dev/tui/remote-connection-probe.js +1 -1
  25. package/dist/src/cli/dev/tui/remote-connection-types.d.ts +2 -1
  26. package/dist/src/cli/dev/tui/remote-connection.js +1 -1
  27. package/dist/src/cli/dev/tui/runner.d.ts +18 -1
  28. package/dist/src/cli/dev/tui/runner.js +1 -1
  29. package/dist/src/cli/dev/tui/setup-commands.d.ts +4 -4
  30. package/dist/src/cli/dev/tui/setup-commands.js +2 -2
  31. package/dist/src/cli/dev/tui/setup-issues.d.ts +1 -1
  32. package/dist/src/cli/dev/tui/setup-issues.js +1 -1
  33. package/dist/src/cli/dev/tui/status-line.d.ts +6 -14
  34. package/dist/src/cli/dev/tui/status-line.js +1 -1
  35. package/dist/src/cli/dev/tui/terminal-renderer.d.ts +8 -1
  36. package/dist/src/cli/dev/tui/terminal-renderer.js +9 -9
  37. package/dist/src/cli/dev/tui/test/index.d.ts +1 -0
  38. package/dist/src/cli/dev/tui/test/index.js +1 -1
  39. package/dist/src/cli/dev/tui/tui.d.ts +1 -1
  40. package/dist/src/cli/dev/tui/tui.js +1 -1
  41. package/dist/src/cli/dev/tui/vercel-trusted-sources.js +1 -1
  42. package/dist/src/cli/run.js +1 -1
  43. package/dist/src/client/agent-info-error.d.ts +17 -0
  44. package/dist/src/client/agent-info-error.js +1 -0
  45. package/dist/src/client/client.d.ts +4 -0
  46. package/dist/src/client/client.js +1 -1
  47. package/dist/src/client/index.d.ts +1 -0
  48. package/dist/src/client/index.js +1 -1
  49. package/dist/src/client/ndjson.js +3 -3
  50. package/dist/src/context/build-dynamic-tools.js +1 -1
  51. package/dist/src/context/dynamic-tool-lifecycle.js +1 -1
  52. package/dist/src/context/keys.d.ts +2 -0
  53. package/dist/src/context/keys.js +1 -1
  54. package/dist/src/evals/cli/eval-client.js +1 -1
  55. package/dist/src/execution/delegated-parent-notification.js +1 -1
  56. package/dist/src/execution/durable-session-store.js +1 -1
  57. package/dist/src/execution/eve-workflow-attributes.d.ts +8 -0
  58. package/dist/src/execution/eve-workflow-attributes.js +1 -1
  59. package/dist/src/execution/ndjson-stream.d.ts +19 -0
  60. package/dist/src/execution/ndjson-stream.js +3 -0
  61. package/dist/src/execution/runtime-context.js +1 -1
  62. package/dist/src/execution/subagent-adapter.js +1 -1
  63. package/dist/src/execution/turn-workflow.js +1 -1
  64. package/dist/src/execution/workflow-entry.js +1 -1
  65. package/dist/src/execution/workflow-runtime.d.ts +1 -1
  66. package/dist/src/execution/workflow-runtime.js +1 -3
  67. package/dist/src/execution/workflow-steps.js +1 -1
  68. package/dist/src/internal/application/package.js +1 -1
  69. package/dist/src/internal/authored-definition/connection.js +1 -1
  70. package/dist/src/internal/nitro/host/configure-nitro-routes.js +1 -1
  71. package/dist/src/internal/nitro/routes/channel-dispatch.js +1 -1
  72. package/dist/src/internal/workflow/configure-world.js +1 -1
  73. package/dist/src/internal/workflow/queue-namespace.d.ts +0 -1
  74. package/dist/src/internal/workflow/queue-namespace.js +1 -1
  75. package/dist/src/internal/workflow/runtime.d.ts +4 -0
  76. package/dist/src/internal/workflow/runtime.js +1 -0
  77. package/dist/src/internal/workflow-bundle/builder-support.js +6 -6
  78. package/dist/src/internal/workflow-bundle/builder.js +2 -2
  79. package/dist/src/internal/workflow-bundle/vercel-workflow-output.js +1 -1
  80. package/dist/src/public/channels/github/dispatch.d.ts +22 -1
  81. package/dist/src/public/channels/github/dispatch.js +1 -1
  82. package/dist/src/public/channels/github/githubChannel.d.ts +21 -3
  83. package/dist/src/public/channels/github/githubChannel.js +1 -1
  84. package/dist/src/public/channels/github/inbound.d.ts +46 -1
  85. package/dist/src/public/channels/github/inbound.js +2 -2
  86. package/dist/src/public/channels/github/index.d.ts +1 -1
  87. package/dist/src/public/channels/github/state.d.ts +3 -1
  88. package/dist/src/public/channels/github/state.js +1 -1
  89. package/dist/src/public/channels/slack/interactions.js +1 -1
  90. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  91. package/dist/src/runtime/connections/callback-route.js +1 -1
  92. package/dist/src/runtime/connections/principal.js +1 -1
  93. package/dist/src/runtime/session-callback-route.js +1 -1
  94. package/dist/src/services/dev-client/request-headers.d.ts +2 -0
  95. package/dist/src/services/dev-client/request-headers.js +1 -1
  96. package/dist/src/services/dev-client/vercel-auth-error.d.ts +12 -14
  97. package/dist/src/services/dev-client/vercel-auth-error.js +9 -2
  98. package/dist/src/setup/flows/install-vercel-cli.d.ts +1 -1
  99. package/dist/src/setup/flows/login.d.ts +3 -1
  100. package/dist/src/setup/flows/login.js +1 -1
  101. package/dist/src/setup/primitives/run-vercel.d.ts +8 -0
  102. package/dist/src/setup/primitives/run-vercel.js +1 -1
  103. package/dist/src/setup/scaffold/create/project.js +6 -2
  104. package/dist/src/setup/scaffold/create/web-template.d.ts +1 -1
  105. package/dist/src/setup/scaffold/create/web-template.js +0 -17
  106. package/dist/src/setup/vercel-deployment.d.ts +12 -5
  107. package/dist/src/setup/vercel-deployment.js +1 -1
  108. package/dist/src/setup/vercel-project-api.d.ts +13 -2
  109. package/dist/src/setup/vercel-project-api.js +1 -1
  110. package/dist/src/setup/vercel-project.d.ts +5 -1
  111. package/dist/src/setup/vercel-project.js +0 -0
  112. package/dist/src/setup/verified-remote-client.js +1 -1
  113. package/dist/src/svelte/index.js +1 -1
  114. package/dist/src/svelte/use-eve-agent.js +1 -1
  115. package/dist/src/vue/index.js +1 -1
  116. package/dist/src/vue/use-eve-agent.js +1 -1
  117. package/docs/README.md +2 -2
  118. package/docs/channels/github.mdx +13 -1
  119. package/docs/channels/linear.mdx +1 -1
  120. package/docs/channels/slack.mdx +1 -1
  121. package/docs/connections/mcp.mdx +61 -0
  122. package/docs/connections/meta.json +4 -0
  123. package/docs/connections/openapi.mdx +68 -0
  124. package/docs/{connections.mdx → connections/overview.mdx} +28 -63
  125. package/docs/getting-started.mdx +0 -6
  126. package/docs/guides/dev-tui.md +22 -12
  127. package/docs/introduction.mdx +2 -8
  128. package/docs/reference/typescript-api.md +20 -19
  129. package/docs/tutorial/connect-a-warehouse.mdx +3 -3
  130. package/docs/tutorial/ship-it.mdx +1 -1
  131. package/package.json +1 -1
@@ -14,31 +14,29 @@
14
14
  * actionable message instead.
15
15
  */
16
16
  import type { DevelopmentOidcTokenFailure } from "#services/dev-client/request-headers.js";
17
+ /** Returns the stable Trusted Sources code embedded in a Vercel error message. */
18
+ export declare function vercelTrustedSourcesErrorCode(message: string): string | undefined;
17
19
  /**
18
- * Returns `true` when the thrown error is the Vercel Deployment
19
- * Protection SSO challenge.
20
+ * Returns `true` for an HTTP 401 carrying Vercel's protection challenge.
20
21
  *
21
22
  * Accepts both real {@link ClientError} instances and structurally
22
23
  * compatible duck-typed errors (`{ status: number, body: string }`)
23
24
  * so callers can detect the challenge regardless of whether the
24
25
  * error survived a network/IPC boundary.
25
- *
26
- * Exported so the dev REPL and other dev-client consumers can render
27
- * a focused authentication directive instead of dumping the SSO
28
- * challenge HTML to the terminal.
29
26
  */
30
27
  export declare function isVercelAuthChallenge(error: unknown): boolean;
31
28
  /**
32
- * Builds the human-readable message rendered when
33
- * {@link isVercelAuthChallenge} returns `true`.
34
- *
35
- * The message states the cause, the target URL, and the supported escape
36
- * hatches (link the project so an OIDC token can be minted, set an
37
- * automation bypass token, or disable protection). It is intentionally
38
- * short — multi-line CLI output is harder to scan than a focused
39
- * directive.
29
+ * Builds the human-readable repair message for the existing dev-client
30
+ * challenge surface, including a structured local OIDC failure when known.
40
31
  */
41
32
  export declare function formatVercelAuthChallengeMessage(input: {
42
33
  readonly serverUrl: string;
43
34
  readonly oidcTokenFailure?: DevelopmentOidcTokenFailure;
44
35
  }): string;
36
+ export declare function formatDevelopmentOidcTokenFailure(failure: DevelopmentOidcTokenFailure): string;
37
+ /**
38
+ * Keeps the actionable Trusted Sources reason and stable error code while
39
+ * dropping Vercel's per-request id. The id is useful in platform logs but is
40
+ * noise in a command result and changes on every retry.
41
+ */
42
+ export declare function formatVercelTrustedSourcesFailure(message: string): string;
@@ -1,2 +1,9 @@
1
- import{isObject}from"#shared/guards.js";import{ClientError}from"#client/client-error.js";const VERCEL_AUTH_CHALLENGE_MARKERS=[`vercel.com/sso-api`,`<title>Authentication Required</title>`,`Vercel Authentication`];function bodyLooksLikeVercelAuthChallenge(e){return e.length>0&&VERCEL_AUTH_CHALLENGE_MARKERS.every(t=>e.includes(t))}function isVercelAuthChallenge(n){return n instanceof ClientError?n.status===401&&bodyLooksLikeVercelAuthChallenge(n.body):isObject(n)&&n.status===401&&typeof n.body==`string`&&bodyLooksLikeVercelAuthChallenge(n.body)}function formatVercelAuthChallengeMessage(e){let t=[`Vercel Deployment Protection blocked the request to ${e.serverUrl}.`];return e.oidcTokenFailure!==void 0&&t.push(``,formatOidcTokenFailure(e.oidcTokenFailure)),t.push(``,"To access the deployment from `eve dev`, do one of:"," • Run `vercel link` in this project so the CLI can mint an OIDC",` token for the deployment automatically.`,` • Set VERCEL_AUTOMATION_BYPASS_SECRET to a Protection Bypass for`,` Automation token (Project Settings → Deployment Protection).`,` • Disable Deployment Protection on the target deployment.`,``,`Docs: https://vercel.com/docs/deployment-protection`),t.join(`
2
- `)}function formatOidcTokenFailure(e){switch(e.kind){case`resolution-failed`:return`The local Vercel OIDC token could not be resolved: ${e.message}`;case`malformed-token`:return e.reason===`missing-payload`?`Vercel returned a local OIDC token without a JWT payload.`:`Vercel returned a local OIDC token whose payload is not valid JSON.`;case`invalid-claims`:return`The local Vercel OIDC token has invalid claims: ${e.invalidClaims.join(`, `)}.`;case`target-mismatch`:return`The local Vercel OIDC token does not match the resolved deployment: ${e.mismatchedClaims.join(`, `)}.`;default:return e}}export{formatVercelAuthChallengeMessage,isVercelAuthChallenge};
1
+ import{isObject}from"#shared/guards.js";import{ClientError}from"#client/client-error.js";const VERCEL_AUTH_CHALLENGE_MARKERS=[`vercel.com/sso-api`,`<title>Authentication Required</title>`],TRUSTED_SOURCES_ERROR_CODE=/^TRUSTED_SOURCES_[A-Z0-9_]+$/u;function vercelTrustedSourcesErrorCode(e){for(let t of e.replaceAll(`\r
2
+ `,`
3
+ `).trim().split(`
4
+ `)){let e=t.trim();if(TRUSTED_SOURCES_ERROR_CODE.test(e))return e}}function bodyLooksLikeVercelAuthChallenge(e){return e.length>0&&VERCEL_AUTH_CHALLENGE_MARKERS.every(t=>e.includes(t))}function isVercelAuthChallenge(n){return n instanceof ClientError?n.status===401&&bodyLooksLikeVercelAuthChallenge(n.body):isObject(n)&&n.status===401&&typeof n.body==`string`&&bodyLooksLikeVercelAuthChallenge(n.body)}function formatVercelAuthChallengeMessage(e){let t=[`Vercel Deployment Protection blocked the request to ${e.serverUrl}.`];return e.oidcTokenFailure!==void 0&&t.push(``,formatDevelopmentOidcTokenFailure(e.oidcTokenFailure)),t.push(``,"To access the deployment from `eve dev`, do one of:"," • Run `vercel link` in this project so the CLI can mint an OIDC",` token for the deployment automatically.`,` • Set VERCEL_AUTOMATION_BYPASS_SECRET to a Protection Bypass for`,` Automation token (Project Settings → Deployment Protection).`,` • Disable Deployment Protection on the target deployment.`,``,`Docs: https://vercel.com/docs/deployment-protection`),t.join(`
5
+ `)}function formatDevelopmentOidcTokenFailure(e){switch(e.kind){case`resolution-failed`:return`The local Vercel OIDC token could not be resolved: ${e.message}`;case`malformed-token`:return e.reason===`missing-payload`?`Vercel returned a local OIDC token without a JWT payload.`:`Vercel returned a local OIDC token whose payload is not valid JSON.`;case`invalid-claims`:return`The local Vercel OIDC token has invalid claims: ${e.invalidClaims.join(`, `)}.`;case`target-mismatch`:return`The local Vercel OIDC token does not match the resolved deployment: ${e.mismatchedClaims.join(`, `)}.`;default:return e}}function formatVercelTrustedSourcesFailure(e){let t=e.replaceAll(`\r
6
+ `,`
7
+ `).trim().split(`
8
+ `),n=t.findIndex(e=>TRUSTED_SOURCES_ERROR_CODE.test(e.trim()));if(n<0)return e;let r=t[n].trim(),i=t.slice(0,n).join(`
9
+ `).trim();return i.length===0?e:`${i}\n\n${r}`}export{formatDevelopmentOidcTokenFailure,formatVercelAuthChallengeMessage,formatVercelTrustedSourcesFailure,isVercelAuthChallenge,vercelTrustedSourcesErrorCode};
@@ -26,7 +26,7 @@ export interface InstallVercelCliDeps {
26
26
  spawnPackageManager: typeof spawnPackageManager;
27
27
  }
28
28
  /**
29
- * THE INSTALL FLOW for the dev TUI's `/vc`: the fix command for the
29
+ * THE INSTALL FLOW for the dev TUI's `/vc:install`: the fix command for the
30
30
  * "Vercel CLI not found" diagnostic, so every diagnostic has a matching
31
31
  * command. Short-circuits when the CLI already resolves; otherwise runs a
32
32
  * global install with the project's package manager, streaming output to the
@@ -36,7 +36,7 @@ export interface LoginFlowDeps {
36
36
  }) => Promise<boolean>;
37
37
  }
38
38
  /**
39
- * THE LOGIN FLOW for the dev TUI's `/login`. Short-circuits when already
39
+ * THE LOGIN FLOW for the dev TUI's `/vc:login`. Short-circuits when already
40
40
  * authenticated; otherwise runs `vercel login` as a browser flow the TUI waits
41
41
  * on (see {@link runVercelLoginWithControls}) and re-probes after, so a
42
42
  * half-finished or abandoned login reports `failed`, never a false success.
@@ -44,6 +44,8 @@ export interface LoginFlowDeps {
44
44
  export declare function runLoginFlow(input: {
45
45
  appRoot: string;
46
46
  prompter: Prompter;
47
+ /** Run the browser login even when the account-level Vercel session is valid. */
48
+ force?: boolean;
47
49
  signal?: AbortSignal;
48
50
  deps?: Partial<LoginFlowDeps>;
49
51
  }): Promise<LoginFlowResult>;
@@ -1 +1 @@
1
- import{withSpinner}from"../with-spinner.js";import{getVercelAuthStatus}from"#setup/vercel-project.js";import{createPromptCommandOutput}from"#setup/cli/index.js";import{runVercel}from"#setup/primitives/run-vercel.js";const defaultDeps={getVercelAuthStatus,runVercelLogin:({cwd:e,onOutput:t,signal:n})=>runVercel([`login`],{cwd:e,nonInteractive:!0,onOutput:t,timeoutMs:3e5,signal:n})};async function runVercelLoginWithControls(t,n,r,i,a){if(i.awaitChoice===void 0)return withSpinner(i,`Opening Vercel login in your browser…`,()=>t.runVercelLogin({cwd:n,onOutput:r,signal:a}));let o=new AbortController,s=a?AbortSignal.any([a,o.signal]):o.signal,c=i.awaitChoice({status:`Logging in to Vercel…`,context:`Complete the login in your browser`,actions:[{value:`cancel`,label:`Cancel`}]}),l=t.runVercelLogin({cwd:n,onOutput:r,signal:s});try{let e=await Promise.race([l.then(e=>({via:`work`,ok:e})),c.choice.then(()=>({via:`choice`}))]);return e.via===`work`?e.ok:(o.abort(),await l.catch(()=>{}),`cancelled`)}finally{c.close()}}async function runLoginFlow(t){let{appRoot:r,prompter:a,signal:o}=t,s={...defaultDeps,...t.deps},c=createPromptCommandOutput(a.log),probeAuth=()=>s.getVercelAuthStatus(r,{signal:o}),l=await withSpinner(a,`Checking your Vercel login…`,probeAuth);switch(o?.throwIfAborted(),l){case`authenticated`:return{kind:`already`};case`cli-missing`:return{kind:`cli-missing`};case`unavailable`:return{kind:`unavailable`};case`logged-out`:break;default:return l}let u=await runVercelLoginWithControls(s,r,c,a,o);if(u===`cancelled`)return{kind:`cancelled`};if(!u)return{kind:`failed`};let d=await withSpinner(a,`Confirming your Vercel login…`,probeAuth);switch(o?.throwIfAborted(),d){case`authenticated`:return{kind:`logged-in`};case`logged-out`:return{kind:`failed`};case`cli-missing`:return{kind:`cli-missing`};case`unavailable`:return{kind:`unavailable`};default:return d}}export{runLoginFlow};
1
+ import{withSpinner}from"../with-spinner.js";import{getVercelAuthStatus}from"#setup/vercel-project.js";import{createPromptCommandOutput}from"#setup/cli/index.js";import{runVercel}from"#setup/primitives/run-vercel.js";const defaultDeps={getVercelAuthStatus,runVercelLogin:({cwd:e,onOutput:t,signal:n})=>runVercel([`login`],{cwd:e,nonInteractive:!0,onOutput:t,timeoutMs:3e5,signal:n})};async function runVercelLoginWithControls(t,n,r,i,a){if(i.awaitChoice===void 0)return withSpinner(i,`Opening Vercel login in your browser…`,()=>t.runVercelLogin({cwd:n,onOutput:r,signal:a}));let o=new AbortController,s=a?AbortSignal.any([a,o.signal]):o.signal,c=i.awaitChoice({status:`Logging in to Vercel…`,context:`Complete the login in your browser`,actions:[{value:`cancel`,label:`Cancel`}]}),l=t.runVercelLogin({cwd:n,onOutput:r,signal:s});try{let e=await Promise.race([l.then(e=>({via:`work`,ok:e})),c.choice.then(()=>({via:`choice`}))]);return e.via===`work`?e.ok:(o.abort(),await l.catch(()=>{}),`cancelled`)}finally{c.close()}}async function runLoginFlow(t){let{appRoot:r,prompter:a,signal:o}=t,s={...defaultDeps,...t.deps},c=createPromptCommandOutput(a.log),probeAuth=()=>s.getVercelAuthStatus(r,{signal:o}),l=await withSpinner(a,`Checking your Vercel login…`,probeAuth);switch(o?.throwIfAborted(),l){case`authenticated`:if(t.force!==!0)return{kind:`already`};break;case`cli-missing`:return{kind:`cli-missing`};case`unavailable`:return{kind:`unavailable`};case`logged-out`:break;default:return l}let u=await runVercelLoginWithControls(s,r,c,a,o);if(u===`cancelled`)return{kind:`cancelled`};if(!u)return{kind:`failed`};let d=await withSpinner(a,`Confirming your Vercel login…`,probeAuth);switch(o?.throwIfAborted(),d){case`authenticated`:return{kind:`logged-in`};case`logged-out`:return{kind:`failed`};case`cli-missing`:return{kind:`cli-missing`};case`unavailable`:return{kind:`unavailable`};default:return d}}export{runLoginFlow};
@@ -17,6 +17,13 @@ export interface RunVercelOptions {
17
17
  */
18
18
  timeoutMs?: number;
19
19
  }
20
+ interface VercelCliInvocation {
21
+ command: string;
22
+ commandArgs: string[];
23
+ shell?: boolean;
24
+ }
25
+ type Platform = NodeJS.Platform;
26
+ export declare function resolveVercelInvocation(cwd: string, args?: string[], platform?: Platform): VercelCliInvocation;
20
27
  /**
21
28
  * Runs a Vercel CLI command with the Connect feature flag enabled.
22
29
  *
@@ -73,3 +80,4 @@ export type VercelCaptureResult = {
73
80
  * streamed to it and the failure summary is appended after a non-zero exit.
74
81
  */
75
82
  export declare function captureVercel(args: string[], options: RunVercelOptions): Promise<VercelCaptureResult>;
83
+ export {};
@@ -1 +1 @@
1
- import{withoutCodingAgentMarkers}from"./coding-agent-env.js";import{createProcessOutputBuffer}from"./process-output.js";import{armProcessAbort}from"./process-abort.js";import{accessSync,constants,existsSync,statSync}from"node:fs";import{dirname,join,resolve}from"node:path";import{spawn}from"node:child_process";const CONNECT_FEATURE_FLAG_ENV={FF_CONNECT_ENABLED:`1`},VERCEL_NOT_FOUND_MESSAGE=`Vercel CLI not found. Install with: npm i -g vercel@latest`;function buildSpawnEnv(t){return{...withoutCodingAgentMarkers(process.env),...CONNECT_FEATURE_FLAG_ENV,...t}}function commandArgs(e,t){return!t||e.includes(`--non-interactive`)?e:[...e,`--non-interactive`]}function existingDir(e){let t=resolve(e);for(;!existsSync(t);){let e=dirname(t);if(e===t)break;t=e}return t}function armDeadline(e,t,n){if(t===void 0)return()=>{};let r=setTimeout(()=>{n(),e.kill(`SIGTERM`);let t=setTimeout(()=>e.kill(`SIGKILL`),5e3);t.unref(),e.once(`close`,()=>clearTimeout(t))},t);return r.unref(),()=>clearTimeout(r)}function timeoutMessage(e,t){return`vercel ${e.join(` `)} timed out after ${Math.round(t/1e3)}s and was aborted.`}function abortMessage(e){return`vercel ${e.join(` `)} was aborted.`}function isAbortError(e,t){return t?.aborted===!0||e.name===`AbortError`||e.code===`ABORT_ERR`}function ancestorDirectories(e){let t=[],n=resolve(e);for(;;){t.push(n);let e=dirname(n);if(e===n)return t;n=e}}function findExecutable(e){try{if(accessSync(e,constants.F_OK|constants.X_OK),statSync(e).isFile())return e}catch{return}}function findLocalVercel(e){for(let t of ancestorDirectories(e)){let e=findExecutable(join(t,`node_modules`,`.bin`,`vercel`));if(e!==void 0)return e}}function resolveVercelInvocation(e){let t=findLocalVercel(e);return t===void 0?{command:`vercel`,commandArgs:[]}:{command:t,commandArgs:[]}}function stdioForRun(e){return e.onOutput?[e.nonInteractive?`ignore`:`inherit`,`pipe`,`pipe`]:e.nonInteractive?[`ignore`,`pipe`,`pipe`]:`inherit`}async function runVercel(e,r){return r.signal?.aborted===!0?!1:new Promise(i=>{let a=existingDir(r.cwd),o=resolveVercelInvocation(a),s=r.onOutput&&createProcessOutputBuffer(r.onOutput),c=spawn(o.command,[...o.commandArgs,...commandArgs(e,r.nonInteractive)],{cwd:a,stdio:stdioForRun(r),env:buildSpawnEnv(r.extraEnv??{}),signal:r.signal}),l=armProcessAbort(c,r.signal);c.stdout?.on(`data`,e=>s?.write(`stdout`,e)),c.stderr?.on(`data`,e=>s?.write(`stderr`,e));let u=!1;function settle(e){u||(u=!0,s?.flush(),i(e))}function reportFailure(e){r.onOutput?r.onOutput({stream:`stderr`,text:e}):process.stderr.write(`\n${e}\n`)}let d=armDeadline(c,r.timeoutMs,()=>{reportFailure(timeoutMessage(e,r.timeoutMs??0)),settle(!1)});c.on(`error`,t=>{isAbortError(t,r.signal)||(t.code===`ENOENT`?(l(),d(),reportFailure(VERCEL_NOT_FOUND_MESSAGE),settle(!1)):(l(),d(),reportFailure(`vercel ${e.join(` `)} failed: ${t.message}`),settle(!1)))}),c.on(`close`,t=>{if(l(),d(),r.signal?.aborted===!0){settle(!1);return}!u&&t!==0&&t!==null&&(s?.flush(),reportFailure(`vercel ${e.join(` `)} exited with code ${t}.`)),settle(t===0)})})}async function runVercelCaptureStdout(e,r){return r.signal?.aborted===!0?{ok:!1,stdout:``}:new Promise(i=>{let a=existingDir(r.cwd),o=resolveVercelInvocation(a),s=r.onOutput&&createProcessOutputBuffer(r.onOutput),c=spawn(o.command,[...o.commandArgs,...commandArgs(e,r.nonInteractive)],{cwd:a,stdio:[r.nonInteractive?`ignore`:`inherit`,`pipe`,r.onOutput?`pipe`:`inherit`],env:buildSpawnEnv(r.extraEnv??{}),signal:r.signal}),l=armProcessAbort(c,r.signal),u=[];c.stdout?.on(`data`,e=>u.push(e.toString(`utf8`))),c.stderr?.on(`data`,e=>s?.write(`stderr`,e));let d=!1;function settle(e){d||(d=!0,s?.flush(),i({ok:e,stdout:u.join(``)}))}function reportFailure(e){r.onOutput?r.onOutput({stream:`stderr`,text:e}):process.stderr.write(`\n${e}\n`)}let f=armDeadline(c,r.timeoutMs,()=>{reportFailure(timeoutMessage(e,r.timeoutMs??0)),settle(!1)});c.on(`error`,t=>{isAbortError(t,r.signal)||(l(),f(),reportFailure(t.code===`ENOENT`?VERCEL_NOT_FOUND_MESSAGE:`vercel ${e.join(` `)} failed: ${t.message}`),settle(!1))}),c.on(`close`,t=>{if(l(),f(),r.signal?.aborted===!0){settle(!1);return}!d&&t!==0&&t!==null&&reportFailure(`vercel ${e.join(` `)} exited with code ${t}.`),settle(t===0)})})}async function captureVercel(e,r){return r.signal?.aborted===!0?{ok:!1,failure:{errno:`ABORT_ERR`,stdout:``,stderr:``,message:abortMessage(e)}}:new Promise(i=>{let a=existingDir(r.cwd),o=resolveVercelInvocation(a),s=r.onOutput&&createProcessOutputBuffer(r.onOutput),c=spawn(o.command,[...o.commandArgs,...commandArgs(e,r.nonInteractive)],{cwd:a,stdio:[`ignore`,`pipe`,`pipe`],env:buildSpawnEnv(r.extraEnv??{}),signal:r.signal}),l=armProcessAbort(c,r.signal),u=[],d=[];c.stdout?.on(`data`,e=>u.push(e.toString(`utf8`))),c.stderr?.on(`data`,e=>{d.push(e.toString(`utf8`)),s?.write(`stderr`,e)});let f=!1;function fail(e,t=!0){f||(f=!0,s?.flush(),t&&r.onOutput?.({stream:`stderr`,text:e.message}),i({ok:!1,failure:e}))}function succeed(){f||(f=!0,s?.flush(),i({ok:!0,stdout:u.join(``)}))}let p=armDeadline(c,r.timeoutMs,()=>{fail({code:null,stdout:u.join(``),stderr:d.join(``),message:timeoutMessage(e,r.timeoutMs??0)})});c.on(`error`,t=>{isAbortError(t,r.signal)||(l(),p(),fail({errno:t.code,stdout:u.join(``),stderr:d.join(``),message:t.code===`ENOENT`?VERCEL_NOT_FOUND_MESSAGE:`vercel ${e.join(` `)} failed: ${t.message}`}))}),c.on(`close`,t=>{if(l(),p(),r.signal?.aborted===!0){fail({errno:`ABORT_ERR`,stdout:u.join(``),stderr:d.join(``),message:abortMessage(e)},!1);return}if(t!==0&&t!==null){fail({code:t,stdout:u.join(``),stderr:d.join(``),message:`vercel ${e.join(` `)} exited with code ${t}.`});return}succeed()})})}export{captureVercel,runVercel,runVercelCaptureStdout};
1
+ import{withoutCodingAgentMarkers}from"./coding-agent-env.js";import{createProcessOutputBuffer}from"./process-output.js";import{armProcessAbort}from"./process-abort.js";import{accessSync,constants,existsSync,statSync}from"node:fs";import{dirname,join,resolve}from"node:path";import{spawn}from"node:child_process";const CONNECT_FEATURE_FLAG_ENV={FF_CONNECT_ENABLED:`1`},VERCEL_NOT_FOUND_MESSAGE=`Vercel CLI not found. Install with: npm i -g vercel@latest`;function buildSpawnEnv(t){return{...withoutCodingAgentMarkers(process.env),...CONNECT_FEATURE_FLAG_ENV,...t}}function commandArgs(e,t){return!t||e.includes(`--non-interactive`)?e:[...e,`--non-interactive`]}function existingDir(e){let t=resolve(e);for(;!existsSync(t);){let e=dirname(t);if(e===t)break;t=e}return t}function armDeadline(e,t,n){if(t===void 0)return()=>{};let r=setTimeout(()=>{n(),e.kill(`SIGTERM`);let t=setTimeout(()=>e.kill(`SIGKILL`),5e3);t.unref(),e.once(`close`,()=>clearTimeout(t))},t);return r.unref(),()=>clearTimeout(r)}function timeoutMessage(e,t){return`vercel ${e.join(` `)} timed out after ${Math.round(t/1e3)}s and was aborted.`}function abortMessage(e){return`vercel ${e.join(` `)} was aborted.`}function isAbortError(e,t){return t?.aborted===!0||e.name===`AbortError`||e.code===`ABORT_ERR`}function ancestorDirectories(e){let t=[],n=resolve(e);for(;;){t.push(n);let e=dirname(n);if(e===n)return t;n=e}}function findExecutable(e){try{if(accessSync(e,constants.F_OK|constants.X_OK),statSync(e).isFile())return e}catch{return}}function vercelExecutableNames(e){return e===`win32`?[`vercel.cmd`,`vercel.exe`]:[`vercel`]}function findLocalVercel(e,t){for(let n of ancestorDirectories(e))for(let e of vercelExecutableNames(t)){let t=findExecutable(join(n,`node_modules`,`.bin`,e));if(t!==void 0)return t}}function resolveVercelInvocation(e,t=[],n=process.platform){let r=findLocalVercel(e,n);return n===`win32`?{command:r??`vercel`,commandArgs:t,shell:!0}:r===void 0?{command:`vercel`,commandArgs:t}:{command:r,commandArgs:t}}function stdioForRun(e){return e.onOutput?[e.nonInteractive?`ignore`:`inherit`,`pipe`,`pipe`]:e.nonInteractive?[`ignore`,`pipe`,`pipe`]:`inherit`}async function runVercel(e,r){return r.signal?.aborted===!0?!1:new Promise(i=>{let a=existingDir(r.cwd),o=resolveVercelInvocation(a,commandArgs(e,r.nonInteractive)),s=r.onOutput&&createProcessOutputBuffer(r.onOutput),c=spawn(o.command,o.commandArgs,{cwd:a,stdio:stdioForRun(r),env:buildSpawnEnv(r.extraEnv??{}),shell:o.shell,signal:r.signal}),l=armProcessAbort(c,r.signal);c.stdout?.on(`data`,e=>s?.write(`stdout`,e)),c.stderr?.on(`data`,e=>s?.write(`stderr`,e));let u=!1;function settle(e){u||(u=!0,s?.flush(),i(e))}function reportFailure(e){r.onOutput?r.onOutput({stream:`stderr`,text:e}):process.stderr.write(`\n${e}\n`)}let d=armDeadline(c,r.timeoutMs,()=>{reportFailure(timeoutMessage(e,r.timeoutMs??0)),settle(!1)});c.on(`error`,t=>{isAbortError(t,r.signal)||(t.code===`ENOENT`?(l(),d(),reportFailure(VERCEL_NOT_FOUND_MESSAGE),settle(!1)):(l(),d(),reportFailure(`vercel ${e.join(` `)} failed: ${t.message}`),settle(!1)))}),c.on(`close`,t=>{if(l(),d(),r.signal?.aborted===!0){settle(!1);return}!u&&t!==0&&t!==null&&(s?.flush(),reportFailure(`vercel ${e.join(` `)} exited with code ${t}.`)),settle(t===0)})})}async function runVercelCaptureStdout(e,r){return r.signal?.aborted===!0?{ok:!1,stdout:``}:new Promise(i=>{let a=existingDir(r.cwd),o=resolveVercelInvocation(a,commandArgs(e,r.nonInteractive)),s=r.onOutput&&createProcessOutputBuffer(r.onOutput),c=spawn(o.command,o.commandArgs,{cwd:a,stdio:[r.nonInteractive?`ignore`:`inherit`,`pipe`,r.onOutput?`pipe`:`inherit`],env:buildSpawnEnv(r.extraEnv??{}),shell:o.shell,signal:r.signal}),l=armProcessAbort(c,r.signal),u=[];c.stdout?.on(`data`,e=>u.push(e.toString(`utf8`))),c.stderr?.on(`data`,e=>s?.write(`stderr`,e));let d=!1;function settle(e){d||(d=!0,s?.flush(),i({ok:e,stdout:u.join(``)}))}function reportFailure(e){r.onOutput?r.onOutput({stream:`stderr`,text:e}):process.stderr.write(`\n${e}\n`)}let f=armDeadline(c,r.timeoutMs,()=>{reportFailure(timeoutMessage(e,r.timeoutMs??0)),settle(!1)});c.on(`error`,t=>{isAbortError(t,r.signal)||(l(),f(),reportFailure(t.code===`ENOENT`?VERCEL_NOT_FOUND_MESSAGE:`vercel ${e.join(` `)} failed: ${t.message}`),settle(!1))}),c.on(`close`,t=>{if(l(),f(),r.signal?.aborted===!0){settle(!1);return}!d&&t!==0&&t!==null&&reportFailure(`vercel ${e.join(` `)} exited with code ${t}.`),settle(t===0)})})}async function captureVercel(e,r){return r.signal?.aborted===!0?{ok:!1,failure:{errno:`ABORT_ERR`,stdout:``,stderr:``,message:abortMessage(e)}}:new Promise(i=>{let a=existingDir(r.cwd),o=resolveVercelInvocation(a,commandArgs(e,r.nonInteractive)),s=r.onOutput&&createProcessOutputBuffer(r.onOutput),c=spawn(o.command,o.commandArgs,{cwd:a,stdio:[`ignore`,`pipe`,`pipe`],env:buildSpawnEnv(r.extraEnv??{}),shell:o.shell,signal:r.signal}),l=armProcessAbort(c,r.signal),u=[],d=[];c.stdout?.on(`data`,e=>u.push(e.toString(`utf8`))),c.stderr?.on(`data`,e=>{d.push(e.toString(`utf8`)),s?.write(`stderr`,e)});let f=!1;function fail(e,t=!0){f||(f=!0,s?.flush(),t&&r.onOutput?.({stream:`stderr`,text:e.message}),i({ok:!1,failure:e}))}function succeed(){f||(f=!0,s?.flush(),i({ok:!0,stdout:u.join(``)}))}let p=armDeadline(c,r.timeoutMs,()=>{fail({code:null,stdout:u.join(``),stderr:d.join(``),message:timeoutMessage(e,r.timeoutMs??0)})});c.on(`error`,t=>{isAbortError(t,r.signal)||(l(),p(),fail({errno:t.code,stdout:u.join(``),stderr:d.join(``),message:t.code===`ENOENT`?VERCEL_NOT_FOUND_MESSAGE:`vercel ${e.join(` `)} failed: ${t.message}`}))}),c.on(`close`,t=>{if(l(),p(),r.signal?.aborted===!0){fail({errno:`ABORT_ERR`,stdout:u.join(``),stderr:d.join(``),message:abortMessage(e)},!1);return}if(t!==0&&t!==null){fail({code:t,stdout:u.join(``),stderr:d.join(``),message:`vercel ${e.join(` `)} exited with code ${t}.`});return}succeed()})})}export{captureVercel,resolveVercelInvocation,runVercel,runVercelCaptureStdout};
@@ -1,4 +1,4 @@
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-beta.178`,DEFAULT_CONNECT_PACKAGE_VERSION=`0.2.2`,DEFAULT_ZOD_PACKAGE_VERSION=`4.4.3`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.13.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-beta.178`,DEFAULT_CONNECT_PACKAGE_VERSION=`0.2.2`,DEFAULT_ZOD_PACKAGE_VERSION=`4.4.3`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.13.7`,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__",
@@ -75,7 +75,11 @@ dist
75
75
  dist
76
76
  `,"AGENTS.md":`# eve Agent App
77
77
 
78
- This project uses the eve framework. Before writing code, always read the relevant guide in \`node_modules/eve/docs/\`.
78
+ This project uses the eve framework. Before writing code, read the relevant guide
79
+ from the installed eve package docs. In most installs, those docs are at
80
+ \`node_modules/eve/docs/\`. In workspaces or local package installs, resolve the
81
+ installed \`eve\` package location first and read its \`docs/\` directory. If
82
+ package docs are unavailable, use https://eve.dev/docs as a fallback.
79
83
  `,"CLAUDE.md":`@AGENTS.md
80
84
  `};function templateFiles(e,t){return{"agent/agent.ts":e?`import { defineAgent } from "eve";
81
85
 
@@ -1,6 +1,6 @@
1
1
  export declare const WEB_APP_TEMPLATE_FILES: {
2
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';
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__";\nconst BETA_TERMS_HREF = "https://vercel.com/docs/release-phases/public-beta-agreement";\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 <a\n className="rounded-full border border-amber-500/30 px-2 py-0.5 font-medium text-amber-700 text-xs transition-colors hover:bg-amber-500/10 dark:text-amber-300"\n href={BETA_TERMS_HREF}\n rel="noreferrer"\n target="_blank"\n >\n Public preview\n </a>\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 <a\n className="rounded-full border border-amber-500/30 px-2 py-0.5 font-medium text-amber-700 text-xs transition-colors hover:bg-amber-500/10 dark:text-amber-300"\n href={BETA_TERMS_HREF}\n rel="noreferrer"\n target="_blank"\n >\n Public preview\n </a>\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';
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 { EveDynamicToolPart, EveMessage, EveMessagePart } from "eve/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";\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 "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 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 "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';
6
6
  readonly "app/layout.tsx": 'import type { Metadata } from "next";\nimport { Geist, Geist_Mono } from "next/font/google";\nimport type { ReactNode } from "react";\nimport { TooltipProvider } from "@/components/ui/tooltip";\nimport { cn } from "@/lib/utils";\nimport "./globals.css";\n\nconst sans = Geist({\n variable: "--font-sans",\n subsets: ["latin"],\n weight: "variable",\n display: "swap",\n});\n\nconst mono = Geist_Mono({\n variable: "--font-mono",\n subsets: ["latin"],\n weight: "variable",\n display: "swap",\n});\n\nexport const metadata: Metadata = {\n title: "__EVE_INIT_APP_NAME__",\n description: "A Next.js starter for eve agents with AI Elements.",\n};\n\nexport default function RootLayout({ children }: { readonly children: ReactNode }) {\n return (\n <html className={cn(sans.variable, mono.variable)} lang="en">\n <body>\n <TooltipProvider>{children}</TooltipProvider>\n </body>\n </html>\n );\n}\n';
@@ -32,7 +32,6 @@ import { cn } from "@/lib/utils";
32
32
  import { AgentMessage } from "./agent-message";
33
33
 
34
34
  const AGENT_NAME = "__EVE_INIT_APP_NAME__";
35
- const BETA_TERMS_HREF = "https://vercel.com/docs/release-phases/public-beta-agreement";
36
35
 
37
36
  type AgentStatus = ReturnType<typeof useEveAgent>["status"];
38
37
 
@@ -63,14 +62,6 @@ export function AgentChat() {
63
62
  <span className="truncate text-muted-foreground text-sm">{AGENT_NAME}</span>
64
63
  <StatusDot status={agent.status} />
65
64
  </span>
66
- <a
67
- className="rounded-full border border-amber-500/30 px-2 py-0.5 font-medium text-amber-700 text-xs transition-colors hover:bg-amber-500/10 dark:text-amber-300"
68
- href={BETA_TERMS_HREF}
69
- rel="noreferrer"
70
- target="_blank"
71
- >
72
- Public preview
73
- </a>
74
65
  </header>
75
66
  )}
76
67
 
@@ -116,14 +107,6 @@ export function AgentChat() {
116
107
  {isEmpty ? (
117
108
  <div className="flex flex-col items-center gap-3 text-center">
118
109
  <h1 className="font-medium text-5xl tracking-tighter">{AGENT_NAME}</h1>
119
- <a
120
- className="rounded-full border border-amber-500/30 px-2 py-0.5 font-medium text-amber-700 text-xs transition-colors hover:bg-amber-500/10 dark:text-amber-300"
121
- href={BETA_TERMS_HREF}
122
- rel="noreferrer"
123
- target="_blank"
124
- >
125
- Public preview
126
- </a>
127
110
  </div>
128
111
  ) : null}
129
112
  <div className="w-full">{composer}</div>
@@ -1,5 +1,5 @@
1
1
  import { captureVercel, type VercelCaptureFailure } from "#setup/primitives/index.js";
2
- import { readProjectLink, type VercelProjectReference } from "#setup/project-resolution.js";
2
+ import type { VercelProjectReference } from "#setup/project-resolution.js";
3
3
  declare const verifiedVercelTargetBrand: unique symbol;
4
4
  export interface ResolvedVercelDeployment {
5
5
  readonly provider: "vercel";
@@ -27,7 +27,7 @@ export type VercelDeploymentResolution = {
27
27
  } | {
28
28
  readonly kind: "not-found";
29
29
  } | {
30
- readonly kind: "unscoped";
30
+ readonly kind: "forbidden";
31
31
  } | {
32
32
  readonly kind: "project-mismatch";
33
33
  readonly expectedProjectId: string;
@@ -40,14 +40,21 @@ export type VercelDeploymentResolution = {
40
40
  };
41
41
  export interface VercelDeploymentResolutionDeps {
42
42
  readonly captureVercel: typeof captureVercel;
43
- readonly readProjectLink: typeof readProjectLink;
44
43
  }
44
+ type VercelDeploymentLookupScope = {
45
+ /** An explicit Vercel scope selected for this lookup. */
46
+ readonly scope?: string;
47
+ readonly source?: never;
48
+ } | {
49
+ /** A known project source, which also cross-checks the returned project id. */
50
+ readonly scope?: never;
51
+ readonly source: Pick<VercelProjectReference, "orgId" | "projectId">;
52
+ };
45
53
  /** Resolves a Vercel deployment URL to its project and target environment. */
46
54
  export declare function resolveVercelDeployment(input: {
47
55
  readonly workspaceRoot: string;
48
56
  readonly host: string;
49
- readonly source?: Pick<VercelProjectReference, "orgId" | "projectId">;
50
57
  readonly signal?: AbortSignal;
51
58
  readonly deps?: Partial<VercelDeploymentResolutionDeps>;
52
- }): Promise<VercelDeploymentResolution>;
59
+ } & VercelDeploymentLookupScope): Promise<VercelDeploymentResolution>;
53
60
  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{isNotFoundApiFailure,normalizeVercelApiResult}from"./vercel-api-failure.js";import{projectReferenceFromEnvironment,readProjectLink}from"#setup/project-resolution.js";import{captureVercel}from"#setup/primitives/index.js";const VercelDeploymentSchema=object({projectId:string().min(1),name:string().min(1),target:string().nullable().optional(),customEnvironment:object({slug:string().min(1)}).nullable().optional()}),verifiedVercelTargetBrand=Symbol(`VerifiedVercelTarget`),defaultDeps={captureVercel,readProjectLink};function environmentForDeployment(e){return e.customEnvironment!==null&&e.customEnvironment!==void 0?e.customEnvironment.slug:e.target===`production`?`production`:`preview`}async function resolveVercelDeployment(e){let t={...defaultDeps,...e.deps},n=e.source??projectReferenceFromEnvironment(process.env)??await t.readProjectLink(e.workspaceRoot);if(n===void 0)return{kind:`unscoped`};let r=normalizeVercelApiResult(await t.captureVercel([`api`,`/v13/deployments/${encodeURIComponent(e.host)}`,`--scope`,n.orgId,`--raw`],{cwd:e.workspaceRoot,nonInteractive:!0,signal:e.signal,timeoutMs:1e4}));if(!r.ok)return e.signal?.aborted===!0||r.failure.errno===`ABORT_ERR`?{kind:`cancelled`}:isNotFoundApiFailure(r.failure)?{kind:`not-found`}:{kind:`failed`,failure:{cause:`vercel`,failure:r.failure}};let i;try{i=JSON.parse(r.stdout)}catch{return{kind:`failed`,failure:{cause:`invalid-json`,message:`Vercel returned invalid deployment JSON.`}}}let a=VercelDeploymentSchema.safeParse(i);if(!a.success)return{kind:`failed`,failure:{cause:`invalid-shape`,message:`Vercel returned an invalid deployment response.`}};if(a.data.projectId!==n.projectId)return{kind:`project-mismatch`,expectedProjectId:n.projectId,actualProjectId:a.data.projectId};let o=`https://${new URL(`https://${e.host}`).host}`;return{kind:`resolved`,target:{[verifiedVercelTargetBrand]:!0,origin:o,deployment:{provider:`vercel`,ownerId:n.orgId,projectId:a.data.projectId,projectName:a.data.name,environment:environmentForDeployment(a.data)}}}}export{resolveVercelDeployment};
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{isForbiddenApiFailure,isNotFoundApiFailure,normalizeVercelApiResult}from"./vercel-api-failure.js";import{captureVercel}from"#setup/primitives/index.js";const VercelDeploymentSchema=object({ownerId:string().min(1),projectId:string().min(1),name:string().min(1),target:string().nullable().optional(),customEnvironment:object({slug:string().min(1)}).nullable().optional()}),verifiedVercelTargetBrand=Symbol(`VerifiedVercelTarget`),defaultDeps={captureVercel};function environmentForDeployment(e){return e.customEnvironment!==null&&e.customEnvironment!==void 0?e.customEnvironment.slug:e.target===`production`?`production`:`preview`}async function resolveVercelDeployment(e){let t={...defaultDeps,...e.deps},n=e.source,r=e.scope??n?.orgId,i=normalizeVercelApiResult(await t.captureVercel([`api`,`/v13/deployments/${encodeURIComponent(e.host)}`,...r===void 0?[]:[`--scope`,r],`--raw`],{cwd:e.workspaceRoot,nonInteractive:!0,signal:e.signal,timeoutMs:1e4}));if(!i.ok)return e.signal?.aborted===!0||i.failure.errno===`ABORT_ERR`?{kind:`cancelled`}:isNotFoundApiFailure(i.failure)?{kind:`not-found`}:isForbiddenApiFailure(i.failure)?{kind:`forbidden`}:{kind:`failed`,failure:{cause:`vercel`,failure:i.failure}};let a;try{a=JSON.parse(i.stdout)}catch{return{kind:`failed`,failure:{cause:`invalid-json`,message:`Vercel returned invalid deployment JSON.`}}}let o=VercelDeploymentSchema.safeParse(a);if(!o.success)return{kind:`failed`,failure:{cause:`invalid-shape`,message:`Vercel returned an invalid deployment response.`}};if(n!==void 0&&o.data.projectId!==n.projectId)return{kind:`project-mismatch`,expectedProjectId:n.projectId,actualProjectId:o.data.projectId};let s=`https://${new URL(`https://${e.host}`).host}`;return{kind:`resolved`,target:{[verifiedVercelTargetBrand]:!0,origin:s,deployment:{provider:`vercel`,ownerId:o.data.ownerId,projectId:o.data.projectId,projectName:o.data.name,environment:environmentForDeployment(o.data)}}}}export{resolveVercelDeployment};
@@ -2,6 +2,8 @@ import { type VercelCaptureFailure } from "#setup/primitives/index.js";
2
2
  import { z } from "zod";
3
3
  import type { VercelProjectOperationOptions } from "./project-resolution.js";
4
4
  export type { VercelProjectOperationOptions };
5
+ /** Shared deadline for Vercel project list and exact-lookup requests. */
6
+ export declare const VERCEL_PROJECT_REQUEST_TIMEOUT_MS = 15000;
5
7
  declare const VercelTeamListEntrySchema: z.ZodObject<{
6
8
  name: z.ZodString;
7
9
  slug: z.ZodString;
@@ -13,6 +15,11 @@ declare const VercelProjectListEntrySchema: z.ZodObject<{
13
15
  id: z.ZodString;
14
16
  }, z.core.$strip>;
15
17
  export type VercelProjectListEntry = z.infer<typeof VercelProjectListEntrySchema>;
18
+ /** One ranked Vercel project search page and its optional continuation cursor. */
19
+ export interface VercelProjectSearchPage {
20
+ readonly projects: VercelProjectListEntry[];
21
+ readonly next?: number;
22
+ }
16
23
  /** Parses one JSON response captured from the Vercel CLI. */
17
24
  export declare function parseVercelJson(stdout: string, description: string): unknown;
18
25
  /** Converts a scoped API denial into the Vercel re-authentication action. */
@@ -21,5 +28,9 @@ export declare function requireVercelTeamAccess(failure: VercelCaptureFailure):
21
28
  export declare function listTeams(projectRoot: string, options?: VercelProjectOperationOptions): Promise<VercelTeamListEntry[]>;
22
29
  /** Lists the 20 most recent Vercel projects in one account scope. */
23
30
  export declare function listRecentProjects(projectRoot: string, team: string, options?: VercelProjectOperationOptions): Promise<VercelProjectListEntry[]>;
24
- /** Searches every matching Vercel project page in one account scope. */
25
- export declare function searchProjects(projectRoot: string, team: string, query: string, options?: VercelProjectOperationOptions): Promise<VercelProjectListEntry[]>;
31
+ /** Ranks exact and prefix project-name matches ahead of substring matches. */
32
+ export declare function rankProjectSearchResults(projects: readonly VercelProjectListEntry[], query: string): VercelProjectListEntry[];
33
+ /** Searches one ranked Vercel project page and retains its continuation cursor. */
34
+ export declare function searchProjects(projectRoot: string, team: string, query: string, options?: VercelProjectOperationOptions & {
35
+ readonly next?: number;
36
+ }): Promise<VercelProjectSearchPage>;
@@ -1 +1 @@
1
- import{array,boolean,number,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{isForbiddenApiFailure}from"./vercel-api-failure.js";import{captureVercel}from"#setup/primitives/index.js";import{HumanActionRequiredError}from"#setup/human-action.js";const VercelTeamListEntrySchema=object({name:string(),slug:string(),current:boolean()}),VercelProjectListEntrySchema=object({name:string(),id:string()}),VercelPaginationSchema=object({next:number().int().nonnegative().nullable().optional()}),VercelTeamPageSchema=object({teams:array(VercelTeamListEntrySchema),pagination:VercelPaginationSchema.optional()}).transform(e=>({items:e.teams,next:e.pagination?.next??void 0})),VercelProjectPageSchema=object({projects:array(VercelProjectListEntrySchema),pagination:VercelPaginationSchema.optional()}).transform(e=>({items:e.projects,next:e.pagination?.next??void 0}));function parseVercelJson(e,t){try{return JSON.parse(e)}catch{throw Error(`Could not parse ${t} JSON from Vercel CLI output.`)}}async function drainPages(e,t,n){let r=new Map,i=new Set,a;for(;;){let o=await n(a);for(let e of o.items)r.set(t(e),e);if(o.next===void 0)return[...r.values()];if(i.has(o.next))throw Error(`Vercel returned a repeated pagination cursor for ${e}.`);i.add(o.next),a=o.next}}function requireVercelTeamAccess(e){let t=e.stderr.trim();throw new HumanActionRequiredError({kind:`vercel-forbidden`,command:`vercel login`,reason:`Vercel denied access to this scope.${t?` ${t}`:``} Re-authenticate (for example to complete a team's SSO) or switch to a team you can access.`})}async function fetchTeamPage(e,t,n){let r=[`teams`,`ls`,`--format`,`json`];n!==void 0&&r.push(`--next`,String(n));let i=await captureVercel(r,{cwd:e,signal:t.signal});if(t.signal?.throwIfAborted(),!i.ok)throw isForbiddenApiFailure(i.failure)&&requireVercelTeamAccess(i.failure),Error(`Could not list Vercel teams. ${i.failure.message}`);let s=VercelTeamPageSchema.safeParse(parseVercelJson(i.stdout,`teams`));if(!s.success)throw Error(`Could not read teams from Vercel CLI JSON output.`);return s.data}async function listTeams(e,t={}){return drainPages(`Vercel teams`,e=>e.slug,n=>fetchTeamPage(e,t,n))}async function fetchProjectPage(e,t,n){let r=[`project`,`ls`,`--format`,`json`,`--scope`,t];n.search!==void 0&&r.push(`--filter`,n.search),n.next!==void 0&&r.push(`--next`,String(n.next));let i=await captureVercel(r,{cwd:e,signal:n.signal,timeoutMs:15e3});if(n.signal?.throwIfAborted(),!i.ok)throw isForbiddenApiFailure(i.failure)&&requireVercelTeamAccess(i.failure),Error(`Could not list Vercel projects in ${t}. ${i.failure.message}`);let s=VercelProjectPageSchema.safeParse(parseVercelJson(i.stdout,`projects`));if(!s.success)throw Error(`Could not read projects from Vercel CLI JSON output.`);return s.data}async function listRecentProjects(e,t,n={}){return(await fetchProjectPage(e,t,n)).items}async function searchProjects(e,t,n,r={}){let i=n.trim();if(i.length===0)throw Error(`Project search query cannot be empty.`);return drainPages(`project search in ${t}`,e=>e.id,n=>fetchProjectPage(e,t,{...r,search:i,next:n}))}export{listRecentProjects,listTeams,parseVercelJson,requireVercelTeamAccess,searchProjects};
1
+ import{array,boolean,number,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{isForbiddenApiFailure}from"./vercel-api-failure.js";import{captureVercel}from"#setup/primitives/index.js";import{HumanActionRequiredError}from"#setup/human-action.js";const VERCEL_PROJECT_REQUEST_TIMEOUT_MS=15e3,VercelTeamListEntrySchema=object({name:string(),slug:string(),current:boolean()}),VercelProjectListEntrySchema=object({name:string(),id:string()}),VercelPaginationSchema=object({next:number().int().nonnegative().nullable().optional()}),VercelTeamPageSchema=object({teams:array(VercelTeamListEntrySchema),pagination:VercelPaginationSchema.optional()}).transform(e=>({items:e.teams,next:e.pagination?.next??void 0})),VercelProjectPageSchema=object({projects:array(VercelProjectListEntrySchema),pagination:VercelPaginationSchema.optional()}).transform(e=>({items:e.projects,next:e.pagination?.next??void 0}));function parseVercelJson(e,t){try{return JSON.parse(e)}catch{throw Error(`Could not parse ${t} JSON from Vercel CLI output.`)}}async function drainPages(e,t,n){let r=new Map,i=new Set,a;for(;;){let o=await n(a);for(let e of o.items)r.set(t(e),e);if(o.next===void 0)return[...r.values()];if(i.has(o.next))throw Error(`Vercel returned a repeated pagination cursor for ${e}.`);i.add(o.next),a=o.next}}function requireVercelTeamAccess(e){let t=e.stderr.trim();throw new HumanActionRequiredError({kind:`vercel-forbidden`,command:`vercel login`,reason:`Vercel denied access to this scope.${t?` ${t}`:``} Re-authenticate (for example to complete a team's SSO) or switch to a team you can access.`})}async function fetchTeamPage(e,t,n){let r=[`teams`,`ls`,`--format`,`json`];n!==void 0&&r.push(`--next`,String(n));let i=await captureVercel(r,{cwd:e,signal:t.signal});if(t.signal?.throwIfAborted(),!i.ok)throw isForbiddenApiFailure(i.failure)&&requireVercelTeamAccess(i.failure),Error(`Could not list Vercel teams. ${i.failure.message}`);let s=VercelTeamPageSchema.safeParse(parseVercelJson(i.stdout,`teams`));if(!s.success)throw Error(`Could not read teams from Vercel CLI JSON output.`);return s.data}async function listTeams(e,t={}){return drainPages(`Vercel teams`,e=>e.slug,n=>fetchTeamPage(e,t,n))}async function fetchProjectPage(e,t,n){let r=[`project`,`ls`,`--format`,`json`,`--scope`,t];n.search!==void 0&&r.push(`--filter`,n.search),n.next!==void 0&&r.push(`--next`,String(n.next));let i=await captureVercel(r,{cwd:e,signal:n.signal,timeoutMs:VERCEL_PROJECT_REQUEST_TIMEOUT_MS});if(n.signal?.throwIfAborted(),!i.ok)throw isForbiddenApiFailure(i.failure)&&requireVercelTeamAccess(i.failure),Error(`Could not list Vercel projects in ${t}. ${i.failure.message}`);let s=VercelProjectPageSchema.safeParse(parseVercelJson(i.stdout,`projects`));if(!s.success)throw Error(`Could not read projects from Vercel CLI JSON output.`);return s.data}async function listRecentProjects(e,t,n={}){return(await fetchProjectPage(e,t,n)).items}function projectSearchRank(e,t){let n=e.name.toLowerCase(),r=t.toLowerCase();return n===r?0:n.startsWith(r)?1:2}function rankProjectSearchResults(e,t){let n=t.trim();return[...e].sort((e,t)=>projectSearchRank(e,n)-projectSearchRank(t,n))}async function searchProjects(e,t,n,r={}){let i=n.trim();if(i.length===0)throw Error(`Project search query cannot be empty.`);let a=await fetchProjectPage(e,t,{...r,search:i}),o=rankProjectSearchResults(a.items,i);return a.next===void 0?{projects:o}:{projects:o,next:a.next}}export{VERCEL_PROJECT_REQUEST_TIMEOUT_MS,listRecentProjects,listTeams,parseVercelJson,rankProjectSearchResults,requireVercelTeamAccess,searchProjects};
@@ -8,6 +8,10 @@ export interface PickProjectOptions extends VercelProjectOperationOptions {
8
8
  /** Whether an empty project list may fall back to entering a name to create. */
9
9
  allowCreateWhenEmpty?: boolean;
10
10
  }
11
+ export interface PickTeamOptions extends VercelProjectOperationOptions {
12
+ /** Builds the team selector heading from the current team's display name. */
13
+ selectMessage?: (currentTeam: string) => string;
14
+ }
11
15
  export declare function unresolvedProject(): ProjectResolution;
12
16
  /** Resolves the linked project id from a resolution, if any. */
13
17
  export declare function projectIdFromResolution(project: ProjectResolution): string | undefined;
@@ -74,7 +78,7 @@ export declare function validateTeam(prompter: Prompter, projectRoot: string, te
74
78
  * zero or one team the current scope is used without prompting; otherwise the
75
79
  * user filters and chooses from the list with a single-selection picker.
76
80
  */
77
- export declare function pickTeam(prompter: Prompter, projectRoot: string, presetTeam: string | undefined, options?: VercelProjectOperationOptions): Promise<string>;
81
+ export declare function pickTeam(prompter: Prompter, projectRoot: string, presetTeam: string | undefined, options?: PickTeamOptions): Promise<string>;
78
82
  /** Picks an existing project under a team, or a name to create when none exist. */
79
83
  export declare function pickProject(prompter: Prompter, projectRoot: string, team: string, options?: PickProjectOptions): Promise<ResolvedVercelProjectSpec>;
80
84
  /** Returns a project name for a new Vercel project, prompting when the default exists. */
Binary file
@@ -1 +1 @@
1
- import{resolveVercelDeployment}from"./vercel-deployment.js";import{resolveRemoteDevelopmentClientOptions}from"#services/dev-client/client-options.js";import{createDevelopmentCredentialGate}from"#services/dev-client/credential-gate.js";import{resolveDevelopmentOidcToken}from"#services/dev-client/request-headers.js";const defaultDeps={resolveVercelDeployment,resolveDevelopmentOidcToken};async function resolveVerifiedRemoteDevelopmentClient(e){let n={...defaultDeps,...e.deps},r=createDevelopmentCredentialGate(e.serverUrl),i=await n.resolveVercelDeployment({workspaceRoot:e.workspaceRoot,host:new URL(e.serverUrl).host});if(i.kind===`resolved`){let{ownerId:e,projectId:t}=i.target.deployment;r.authorize({target:i.target,resolveToken:()=>n.resolveDevelopmentOidcToken({ownerId:e,projectId:t})})}return{options:resolveRemoteDevelopmentClientOptions({serverUrl:e.serverUrl,credentials:r}),lastOidcTokenFailure:r.lastTokenFailure}}export{resolveVerifiedRemoteDevelopmentClient};
1
+ import{resolveVercelDeployment}from"./vercel-deployment.js";import{resolveDevelopmentOidcToken}from"#services/dev-client/request-headers.js";import{resolveRemoteDevelopmentClientOptions}from"#services/dev-client/client-options.js";import{createDevelopmentCredentialGate}from"#services/dev-client/credential-gate.js";const defaultDeps={resolveVercelDeployment,resolveDevelopmentOidcToken};async function resolveVerifiedRemoteDevelopmentClient(e){let t={...defaultDeps,...e.deps},r=createDevelopmentCredentialGate(e.serverUrl),i=await t.resolveVercelDeployment({workspaceRoot:e.workspaceRoot,host:new URL(e.serverUrl).host});if(i.kind===`resolved`){let{ownerId:e,projectId:n}=i.target.deployment;r.authorize({target:i.target,resolveToken:()=>t.resolveDevelopmentOidcToken({ownerId:e,projectId:n})})}return{options:resolveRemoteDevelopmentClientOptions({serverUrl:e.serverUrl,credentials:r}),lastOidcTokenFailure:r.lastTokenFailure}}export{resolveVerifiedRemoteDevelopmentClient};
@@ -1,3 +1,3 @@
1
- import { n as defaultMessageReducer, t as useEveAgent } from "../chunks/use-eve-agent-_dbX0ASK.js";
1
+ import { n as defaultMessageReducer, t as useEveAgent } from "../chunks/use-eve-agent-8X2UMr8q.js";
2
2
 
3
3
  export { defaultMessageReducer, useEveAgent };
@@ -1,3 +1,3 @@
1
- import { t as useEveAgent } from "../chunks/use-eve-agent-_dbX0ASK.js";
1
+ import { t as useEveAgent } from "../chunks/use-eve-agent-8X2UMr8q.js";
2
2
 
3
3
  export { useEveAgent };
@@ -1,3 +1,3 @@
1
- import { n as defaultMessageReducer, t as useEveAgent } from "../chunks/use-eve-agent-BjAM8_a3.js";
1
+ import { n as defaultMessageReducer, t as useEveAgent } from "../chunks/use-eve-agent-9ZNiSFMb.js";
2
2
 
3
3
  export { defaultMessageReducer, useEveAgent };
@@ -1,3 +1,3 @@
1
- import { t as useEveAgent } from "../chunks/use-eve-agent-BjAM8_a3.js";
1
+ import { t as useEveAgent } from "../chunks/use-eve-agent-9ZNiSFMb.js";
2
2
 
3
3
  export { useEveAgent };
package/docs/README.md CHANGED
@@ -12,7 +12,7 @@ Important naming note:
12
12
 
13
13
  ## Legal and safeguards
14
14
 
15
- eve is currently a preview and subject to the Vercel beta terms; the framework, APIs, documentation, and behavior may change before general availability.
15
+ eve is in preview; the framework, APIs, documentation, and behavior may change before general availability.
16
16
 
17
17
  As the deployer, it is your responsibility to ensure your agent complies with applicable laws.
18
18
 
@@ -41,7 +41,7 @@ Read in this order:
41
41
  6. [Context Control](./concepts/context-control.md)
42
42
  7. [Skills](./skills.mdx)
43
43
  8. [Tools](./tools/overview.mdx)
44
- 9. [Connections](./connections.mdx)
44
+ 9. [Connections](./connections/overview.mdx)
45
45
  10. [Sandboxes](./sandbox.mdx)
46
46
  11. [Channels](./channels/overview.mdx)
47
47
  12. [Session Context](./reference/typescript-api.md)
@@ -32,7 +32,7 @@ GITHUB_APP_SLUG=... # supplies botName when it is not set in config
32
32
 
33
33
  `appId`/`privateKey`/`webhookSecret` also take a lazy resolver function if you'd rather fetch them on demand.
34
34
 
35
- Point the GitHub App webhook URL at `https://<deployment>/eve/v1/github`. For mention-driven turns, subscribe to `issue_comment` and `pull_request_review_comment`; add `issues` / `pull_request` if you wire up their opt-in hooks. A comment that `@mention`s `botName` starts a turn.
35
+ Point the GitHub App webhook URL at `https://<deployment>/eve/v1/github`. For mention-driven turns, subscribe to `issue_comment` and `pull_request_review_comment`; add `issues`, `pull_request`, `check_suite`, `check_run`, or `workflow_run` if you wire up their opt-in hooks. A comment that `@mention`s `botName` starts a turn.
36
36
 
37
37
  ## How the channel handles messages
38
38
 
@@ -50,9 +50,21 @@ export default githubChannel({
50
50
  // Opt in; no default dispatch on these events.
51
51
  onIssue: (ctx, issue) => (issue.action === "opened" ? { auth: defaultGitHubAuth(ctx) } : null),
52
52
  onPullRequest: (ctx, pr) => (pr.action === "opened" ? { auth: defaultGitHubAuth(ctx) } : null),
53
+ onCheckSuite: (ctx, suite) =>
54
+ suite.action === "completed" &&
55
+ suite.conclusion === "failure" &&
56
+ suite.app.slug === "github-actions" &&
57
+ suite.pullRequests.length > 0
58
+ ? {
59
+ auth: defaultGitHubAuth(ctx),
60
+ context: [`Triage failed check suite ${suite.checkSuiteId} at ${suite.headSha}.`],
61
+ }
62
+ : null,
53
63
  });
54
64
  ```
55
65
 
66
+ The CI hooks expose normalized `action`, `status`, `conclusion`, `app.slug`, `headSha`, and `pullRequests` fields, plus `checkSuiteId`, `checkRunId`, or `workflowRunId`. `workflow_run` is a GitHub Actions-only event, so its normalized `app.slug` is `"github-actions"`. A dispatched CI turn is anchored to the first number in `pullRequests`; the hook still runs when the array is empty, but it must return `null` because there is no issue or PR thread for the session.
67
+
56
68
  ### Delivery
57
69
 
58
70
  When a turn starts, the channel adds an `eyes` reaction to the triggering comment (turn this off with `progress: { reactions: false }`). The reply comes back as a comment, on the timeline or in the review thread, and splits across multiple comments when it runs long. If the turn fails, you get a short error comment carrying an error id.
@@ -158,4 +158,4 @@ For issue or comment targets, the channel calls Linear's proactive Agent Session
158
158
  ## What to read next
159
159
 
160
160
  - [Channels overview](./overview): the channel contract and every built-in channel
161
- - [Connections](../connections): use the Linear MCP connection when the agent needs to inspect or edit Linear data from another channel
161
+ - [MCP connections](../connections/mcp): use the Linear MCP connection when the agent needs to inspect or edit Linear data from another channel
@@ -80,7 +80,7 @@ export default slackChannel({
80
80
 
81
81
  The default handlers reply in-thread and show progress. Typing indicators post automatically: `Thinking…` on inbound, `Working…` on `turn.started`, a truncated reasoning snippet on `reasoning.appended`, and tool status on `actions.requested`. Reasoning snippets build progressively: extensions of at least four characters appear immediately, while smaller streamed deltas use the five-second refresh interval to avoid one Slack request per token. Override `events["reasoning.appended"]` if you prefer generic wording. Override `onAppMention` or the `events` handlers to customize.
82
82
 
83
- When a session starts without a `threadTs` (say, from a schedule or `receive(slack, ...)`), it anchors on the first agent post, and later posts and mentions resume that same session. Pass `initialMessage` with a `Card` to land a structured anchor first instead. `threadTs` and `initialMessage` are mutually exclusive.
83
+ When a session starts without a `threadTs` (say, from a schedule or `receive(slack, ...)`), eve gives it a unique temporary continuation token. The first agent post anchors the session to the Slack message timestamp, and later posts and mentions resume that same session. Pass `initialMessage` with a `Card` to land a structured anchor first instead. `threadTs` and `initialMessage` are mutually exclusive.
84
84
 
85
85
  The example below overrides `onAppMention` to gate on an authored message and posts the completed reply to the thread. Event handlers receive `(eventData, channel, ctx)`, with Slack platform handles on `channel.thread` and `channel.slack`:
86
86
 
@@ -0,0 +1,61 @@
1
+ ---
2
+ title: "MCP Connections"
3
+ description: "Connect an eve agent to a remote MCP server and control which tools the model can discover."
4
+ ---
5
+
6
+ MCP connections point eve at an MCP server you do not author. The server publishes its tools and schemas, and eve exposes matched tools to the model through `connection_search`.
7
+
8
+ Use MCP when the service already has an MCP server, when the server needs to own tool schemas dynamically, or when one connection should expose a family of related remote tools.
9
+
10
+ ## Define an MCP connection
11
+
12
+ `defineMcpClientConnection` needs a `url` and a `description`:
13
+
14
+ ```ts title="agent/connections/linear.ts"
15
+ import { defineMcpClientConnection } from "eve/connections";
16
+
17
+ export default defineMcpClientConnection({
18
+ url: "https://mcp.linear.app/sse",
19
+ description: "Linear workspace: issues, projects, cycles, and comments.",
20
+ auth: {
21
+ getToken: async () => ({ token: process.env.LINEAR_API_TOKEN! }),
22
+ },
23
+ });
24
+ ```
25
+
26
+ The `url` must speak Streamable HTTP or SSE. Write the `description` for the model, not for yourself. It shows up in `connection_search`, and the model uses it to decide which connection to query.
27
+
28
+ The file path provides the connection name. `agent/connections/linear.ts` registers as `linear`, and remote tools appear as `linear__<tool>` after discovery.
29
+
30
+ ## Tool filters
31
+
32
+ To narrow which remote tools the model sees, set exactly one of `tools.allow` or `tools.block`. Filtered-out tools do not appear in `connection_search`:
33
+
34
+ ```ts title="agent/connections/linear.ts"
35
+ import { defineMcpClientConnection } from "eve/connections";
36
+
37
+ export default defineMcpClientConnection({
38
+ url: "https://mcp.linear.app/sse",
39
+ description: "Linear: read-only.",
40
+ auth: { getToken: async () => ({ token: process.env.LINEAR_API_TOKEN! }) },
41
+ tools: { allow: ["search_issues", "get_issue"] },
42
+ });
43
+ ```
44
+
45
+ Use `allow` for the smallest safe surface, especially on MCP servers that expose write tools alongside read tools. Use `block` only when the server has a broad stable surface and you need to hide a few tools.
46
+
47
+ ## Auth, headers, and approval
48
+
49
+ MCP connections support the shared connection options:
50
+
51
+ - `auth` for static tokens, Vercel Connect, or self-hosted interactive OAuth.
52
+ - `headers` for API-key schemes or extra server configuration.
53
+ - `approval` for human-in-the-loop gates before connection tools run.
54
+
55
+ See [Connections](/docs/connections) for the shared auth, headers, and approval shapes.
56
+
57
+ ## What to read next
58
+
59
+ - [OpenAPI connections](./openapi): generate tools from OpenAPI operations.
60
+ - [Auth & route protection](../guides/auth-and-route-protection): the full interactive-OAuth flow with Vercel Connect.
61
+ - [Security model](../concepts/security-model): how connection credentials stay out of the model's reach.
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "Connections",
3
+ "pages": ["overview", "mcp", "openapi"]
4
+ }