eve 0.6.0-beta.11 → 0.6.0-beta.12
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.
- package/CHANGELOG.md +15 -0
- package/dist/docs/public/getting-started.mdx +2 -2
- package/dist/src/cli/commands/setup.d.ts +6 -3
- package/dist/src/cli/commands/setup.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/setup/ask.d.ts +7 -0
- package/dist/src/setup/ask.js +1 -1
- package/dist/src/setup/boxes/add-channels.d.ts +11 -2
- package/dist/src/setup/boxes/add-channels.js +1 -1
- package/dist/src/setup/boxes/add-connections.d.ts +8 -58
- package/dist/src/setup/boxes/add-connections.js +1 -1
- package/dist/src/setup/boxes/deploy-project.js +1 -1
- package/dist/src/setup/boxes/one-shot-next-steps.d.ts +14 -0
- package/dist/src/setup/boxes/one-shot-next-steps.js +2 -0
- package/dist/src/setup/boxes/resolve-provisioning.js +1 -1
- package/dist/src/setup/boxes/select-connections.d.ts +32 -0
- package/dist/src/setup/boxes/select-connections.js +1 -0
- package/dist/src/setup/boxes/select-model.d.ts +4 -4
- package/dist/src/setup/boxes/select-model.js +1 -1
- package/dist/src/setup/boxes/select-setup-mode.d.ts +32 -0
- package/dist/src/setup/boxes/select-setup-mode.js +1 -0
- package/dist/src/setup/cli/index.d.ts +1 -0
- package/dist/src/setup/cli/index.js +1 -1
- package/dist/src/setup/cli/prompt-ui.d.ts +39 -15
- package/dist/src/setup/cli/prompt-ui.js +1 -1
- package/dist/src/setup/cli/select-component.d.ts +16 -1
- package/dist/src/setup/cli/select-component.js +1 -1
- package/dist/src/setup/cli/select-state.d.ts +13 -1
- package/dist/src/setup/cli/select-state.js +1 -1
- package/dist/src/setup/cli/whimsy.d.ts +16 -0
- package/dist/src/setup/cli/whimsy.js +1 -0
- package/dist/src/setup/connection-connector.js +1 -1
- package/dist/src/setup/index.d.ts +2 -2
- package/dist/src/setup/onboarding.d.ts +5 -3
- package/dist/src/setup/onboarding.js +1 -1
- package/dist/src/setup/primitives/run-vercel.d.ts +7 -0
- package/dist/src/setup/primitives/run-vercel.js +1 -1
- package/dist/src/setup/prompter.d.ts +20 -3
- package/dist/src/setup/prompter.js +1 -1
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/setup/scaffold/update/channels.js +1 -1
- package/dist/src/setup/slackbot.js +1 -1
- package/dist/src/setup/state.d.ts +35 -1
- package/dist/src/setup/state.js +1 -1
- package/dist/src/setup/vercel-project.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# eve
|
|
2
2
|
|
|
3
|
+
## 0.6.0-beta.12
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4c1dd92: The connections picker moved into the onboarding interview: it now asks right after channel selection, before any files are written. The selected connections are still scaffolded and provisioned after the project link, exactly as before.
|
|
8
|
+
- 432deaa: The setup model picker now opens on a curated shortlist (Claude Sonnet 4.6 as the default, Claude Opus 4.8, GPT-5.5, and Gemini 3.5) and surfaces the rest of the AI Gateway catalog through scrolling or search. The search filter now accepts spaces. Vercel Connect steps in Slack channel setup run under hard deadlines, so an abandoned browser OAuth can no longer stall `connect create slack` forever.
|
|
9
|
+
- 4c1dd92: Onboarding deploys to Vercel only when the Slack channel was added — its connector needs a public production URL. A web-only onboarding run links the project but skips the deploy; Web Chat runs locally through `eve dev`. Adding channels to an existing project (`eve setup` in-project, `eve channels add`) still deploys for any channel.
|
|
10
|
+
- 4c1dd92: The onboarding flow now asks how much to set up after the agent name: "Complete setup" (the existing full flow, recommended) or "One-shot", which scaffolds the base template with the default model and skips provisioning, channels, deploy, and chat, ending with a next-steps note. `eve setup` in a directory that is not an Eve project now asks for the agent name and where the agent should live instead of force-scaffolding in place over the existing repo.
|
|
11
|
+
- 432deaa: A failed Slackbot provision no longer aborts onboarding or `eve setup`. The run warns, skips Slack, and keeps scaffolding and deploying, and you can add Slack later with `eve channels add slack`. The `eve channels add slack` command itself still fails hard, since Slack is its whole purpose.
|
|
12
|
+
- 432deaa: Setup multi-select prompts now render checkboxes and end with a bold Submit row. Enter toggles the highlighted option, and only enter on Submit confirms, so a stray enter can no longer skip the checklist. Single-select prompts mark the highlighted row with a `›` arrow instead of a check.
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 432deaa: Refreshed onboarding copy: the deploy question now asks where to deploy (Vercel vs. elsewhere), the team and project pickers use shorter titles, and the intro banners spell the product as 𝐞𝐯𝐞.
|
|
17
|
+
|
|
3
18
|
## 0.6.0-beta.11
|
|
4
19
|
|
|
5
20
|
### Minor Changes
|
|
@@ -20,7 +20,7 @@ The fastest path is the `create` CLI. It scaffolds the project, prompts for a mo
|
|
|
20
20
|
pnpm create eve@beta
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
The wizard asks for a model and which channel you want (Web Chat or Slack). You can skip both: every app ships the built-in HTTP channel (`agent/channels/eve.ts`) regardless. For a local chat it installs dependencies and starts the dev server for you.
|
|
23
|
+
The wizard first asks how much to set up: a complete setup, or a one-shot scaffold (also available as `--one-shot`) that just writes the project files with the default model. The complete path asks for a model and which channel you want (Web Chat or Slack). You can skip both: every app ships the built-in HTTP channel (`agent/channels/eve.ts`) regardless. For a local chat it installs dependencies and starts the dev server for you.
|
|
24
24
|
|
|
25
25
|
To add Eve to an existing app instead:
|
|
26
26
|
|
|
@@ -164,7 +164,7 @@ Once `eve` is a dependency, the full docs are bundled in the package, so the age
|
|
|
164
164
|
|
|
165
165
|
- Docs: `node_modules/eve/dist/docs/public/`
|
|
166
166
|
|
|
167
|
-
To scaffold a project end to end, `pnpm create eve@beta` collects the decisions (name, model, channels), runs setup, adds Slack interactively with `eve channels add slack`, and verifies the result with `eve info --json`.
|
|
167
|
+
To scaffold a project end to end, `pnpm create eve@beta` collects the decisions (name, setup mode, model, channels), runs setup, adds Slack interactively with `eve channels add slack`, and verifies the result with `eve info --json`.
|
|
168
168
|
|
|
169
169
|
## What to read next
|
|
170
170
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type DeploymentInfo } from "#setup/project-resolution.js";
|
|
2
2
|
import { type Prompter } from "#setup/prompter.js";
|
|
3
|
-
import { type ArgsHeadlessAiGateway, type ArgsHeadlessProject, type ChannelKind, type ChatPreference } from "#setup/state.js";
|
|
3
|
+
import { type ArgsHeadlessAiGateway, type ArgsHeadlessProject, type ChannelKind, type ChatPreference, type SetupMode } from "#setup/state.js";
|
|
4
4
|
export interface SetupCliLogger {
|
|
5
5
|
error(message: string): void;
|
|
6
6
|
log(message: string): void;
|
|
@@ -12,6 +12,7 @@ export interface SetupCliLogger {
|
|
|
12
12
|
*/
|
|
13
13
|
export interface SetupCommandPresets {
|
|
14
14
|
presetName?: string;
|
|
15
|
+
presetMode?: SetupMode;
|
|
15
16
|
presetModel?: string;
|
|
16
17
|
presetChannels?: ChannelKind[];
|
|
17
18
|
presetCreateSlackbot?: boolean;
|
|
@@ -46,8 +47,10 @@ export interface SetupCommandDependencies {
|
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
48
49
|
* The unified `eve setup` entry. In a directory that is not yet an Eve project
|
|
49
|
-
* it runs the SAME onboarding composition as `pnpm create eve
|
|
50
|
-
*
|
|
50
|
+
* it runs the SAME onboarding composition as `pnpm create eve`: an interactive
|
|
51
|
+
* run asks the name and where the agent should live (a `./<name>` child or the
|
|
52
|
+
* current directory), while a headless run scaffolds in place. Inside an
|
|
53
|
+
* existing project it runs in-project setup: the channel
|
|
51
54
|
* interview, then channel scaffolding, then deploy, against the detected
|
|
52
55
|
* on-disk project link. Both compose the shared boxes through the shared
|
|
53
56
|
* runner.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{basename}from"node:path";import{isEveProject}from"#setup/scaffold/index.js";import{headlessAsker,interactiveAsker}from"#setup/ask.js";import{addChannels}from"#setup/boxes/add-channels.js";import{deployProject}from"#setup/boxes/deploy-project.js";import{selectChannels}from"#setup/boxes/select-channels.js";import{detectDeployment,projectResolutionFromDeployment}from"#setup/project-resolution.js";import{createPrompter}from"#setup/prompter.js";import{runHeadless,runInteractive}from"#setup/runner.js";import{createDefaultSetupState,snapshotSetupState}from"#setup/state.js";import{composeOnboardingBoxes}from"#setup/onboarding.js";const defaultDependencies={createPrompter,detectDeployment,isEveProject,hasInteractiveTerminal:()=>!!(process.stdin.isTTY&&process.stdout.isTTY)};async function runSetupCommand(t,n,r,i=defaultDependencies){let a=!await i.isEveProject(n),o=r.evePackageVersion??`beta`,s=r.presets??{},c=r.headless!==!0;if(c&&!i.hasInteractiveTerminal()){t.error("eve setup is interactive and needs a terminal. For automation, use `create-eve --headless` to scaffold a new agent or `eve channels add` inside an existing project."),process.exitCode=1;return}let l=i.createPrompter(),u={write:e=>l.log.message(e)};l.intro(a?`Create
|
|
1
|
+
import{basename}from"node:path";import{isEveProject}from"#setup/scaffold/index.js";import{headlessAsker,interactiveAsker}from"#setup/ask.js";import{addChannels}from"#setup/boxes/add-channels.js";import{deployProject}from"#setup/boxes/deploy-project.js";import{selectChannels}from"#setup/boxes/select-channels.js";import{detectDeployment,projectResolutionFromDeployment}from"#setup/project-resolution.js";import{createPrompter}from"#setup/prompter.js";import{runHeadless,runInteractive}from"#setup/runner.js";import{createDefaultSetupState,snapshotSetupState}from"#setup/state.js";import{whimsyFor}from"#setup/cli/index.js";import{composeOnboardingBoxes}from"#setup/onboarding.js";const defaultDependencies={createPrompter,detectDeployment,isEveProject,hasInteractiveTerminal:()=>!!(process.stdin.isTTY&&process.stdout.isTTY)};async function runSetupCommand(t,n,r,i=defaultDependencies){let a=!await i.isEveProject(n),o=r.evePackageVersion??`beta`,s=r.presets??{},c=r.headless!==!0;if(c&&!i.hasInteractiveTerminal()){t.error("eve setup is interactive and needs a terminal. For automation, use `create-eve --headless` to scaffold a new agent or `eve channels add` inside an existing project."),process.exitCode=1;return}let l=i.createPrompter(),u={write:e=>l.log.message(e)};l.intro(a?`Create an 𝐞𝐯𝐞 agent`:`Set-up your 𝐞𝐯𝐞 agent`);let d=a?`Agent created.`:`Setup complete.`,f,p;a?(f=createDefaultSetupState(),p=composeOnboardingBoxes({prompter:l,presetName:s.presetName,presetMode:s.presetMode,presetModel:s.presetModel,presetChannels:s.presetChannels,presetConnections:s.presetConnections,presetCreateSlackbot:s.presetCreateSlackbot,provisioning:r.provisioning,presetChatPreference:s.presetChatPreference??`skip`,targetDirectory:n,inPlace:!c,overwriteExisting:s.overwriteExisting,presetNoDeploy:s.presetNoDeploy,headless:!c,evePackageVersion:o})):(l.log.message(whimsyFor(`project-detect`)),f={...createDefaultSetupState(),project:projectResolutionFromDeployment(await i.detectDeployment(n)),agentName:basename(n),projectPath:{kind:`resolved`,inPlace:!0,path:n}},p=[selectChannels({asker:c?interactiveAsker(l):headlessAsker(),presetChannels:s.presetChannels}),addChannels({asker:c?interactiveAsker(l):headlessAsker(),prompter:l,evePackageVersion:o,presetCreateSlackbot:s.presetCreateSlackbot,headless:!c,slackbotFailure:`warn-and-continue`}),deployProject({prompter:l,skip:s.presetNoDeploy,headless:!c})]);try{if(c){let e=await runInteractive(p,f,u,{snapshot:snapshotSetupState});l.outro(e.kind===`cancelled`?`Cancelled.`:d);return}await runHeadless(p,f,u,{snapshot:snapshotSetupState}),l.outro(d)}catch(e){t.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}export{runSetupCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{EVE_PACKAGE_NAME}from"#internal/package-name.js";import{fileURLToPath}from"node:url";let cachedPackageInfo;const WORKFLOW_MODULE_ALIASES={"workflow/api":`src/compiled/@workflow/core/runtime.js`,"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`,"workflow/runtime":`src/compiled/@workflow/core/runtime.js`};function resolveFallbackPackageVersion(){return`0.6.0-beta.
|
|
1
|
+
import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{EVE_PACKAGE_NAME}from"#internal/package-name.js";import{fileURLToPath}from"node:url";let cachedPackageInfo;const WORKFLOW_MODULE_ALIASES={"workflow/api":`src/compiled/@workflow/core/runtime.js`,"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`,"workflow/runtime":`src/compiled/@workflow/core/runtime.js`};function resolveFallbackPackageVersion(){return`0.6.0-beta.12`}const FALLBACK_PACKAGE_INFO={name:EVE_PACKAGE_NAME,version:resolveFallbackPackageVersion()};function resolveCurrentModulePath(){return typeof __filename==`string`?__filename:resolveCurrentModulePathFromStack()}function resolveCurrentModulePathFromStack(){let e=Error.prepareStackTrace;try{Error.prepareStackTrace=(e,t)=>t;let e=Error().stack?.[0]?.getFileName();if(typeof e!=`string`||e.length===0)throw Error(`Failed to resolve the current module path from the stack trace.`);return e.startsWith(`file:`)?fileURLToPath(e):e}finally{Error.prepareStackTrace=e}}const require=createRequire(resolveCurrentModulePath());function isBuildOutputPackageRoot(e){return basename(e)===`dist`&&existsSync(join(dirname(e),`package.json`))}function resolvePackageBuildRoot(){let e=dirname(realpathSync(resolveCurrentModulePath()));for(;;){if(isBuildOutputPackageRoot(e))return e;let t=dirname(e);if(t===e)return null;e=t}}function findNearestPackageRoot(e){let t=e;for(;;){if(existsSync(join(t,`package.json`))&&!isBuildOutputPackageRoot(t))return t;let r=dirname(t);if(r===t)throw Error(`Failed to resolve package root from "${e}".`);t=r}}function resolvePackageRoot(){return findNearestPackageRoot(dirname(realpathSync(resolveCurrentModulePath())))}function tryResolvePackageRoot(){try{return resolvePackageRoot()}catch{return}}function rewriteSourceFilePathForBuild(e){return e.replace(/\.[cm]?tsx?$/,`.js`)}function resolvePackageSourceFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),e):join(t,rewriteSourceFilePathForBuild(e))}function resolvePackageSourceDirectoryPath(e){let t=resolvePackageBuildRoot();return join(t===null?resolvePackageRoot():t,e)}function resolvePackageDependencyPath(e){return require.resolve(e)}function resolvePackageCompiledFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),`.generated`,`compiled`,e.replace(/^src\/compiled\//,``)):join(t,e)}function normalizeInstalledPackageInfo(e){let t=e;if(!(typeof t.name!=`string`||typeof t.version!=`string`))return{name:t.name,version:t.version}}function tryReadInstalledPackageInfo(e,t){let n=normalizeInstalledPackageInfo(JSON.parse(readFileSync(e,`utf8`)));if(n?.name===t)return n}function resolveInstalledPackageInfo(){if(cachedPackageInfo)return cachedPackageInfo;let e=tryResolvePackageRoot(),t=e===void 0?void 0:tryReadInstalledPackageInfo(join(e,`package.json`),EVE_PACKAGE_NAME);if(t)return cachedPackageInfo=t,cachedPackageInfo;try{let e=tryReadInstalledPackageInfo(require.resolve(`${EVE_PACKAGE_NAME}/package.json`),EVE_PACKAGE_NAME);if(e)return cachedPackageInfo=e,cachedPackageInfo}catch{}return cachedPackageInfo={...FALLBACK_PACKAGE_INFO},cachedPackageInfo}function resolveWorkflowModulePath(e){if(e===`workflow`)return resolvePackageSourceFilePath(`src/internal/workflow/index.ts`);if(e===`workflow/internal/builtins`)return resolvePackageSourceFilePath(`src/internal/workflow/builtins.ts`);let t=WORKFLOW_MODULE_ALIASES[e];return t===void 0?require.resolve(e):resolvePackageCompiledFilePath(t)}export{resolveInstalledPackageInfo,resolvePackageDependencyPath,resolvePackageRoot,resolvePackageSourceDirectoryPath,resolvePackageSourceFilePath,resolveWorkflowModulePath};
|
package/dist/src/setup/ask.d.ts
CHANGED
|
@@ -6,6 +6,13 @@ export interface SelectOption<T> {
|
|
|
6
6
|
label: string;
|
|
7
7
|
value: T;
|
|
8
8
|
hint?: string;
|
|
9
|
+
/**
|
|
10
|
+
* A leading run of featured options forms a searchable picker's default
|
|
11
|
+
* viewport: with no filter typed, only they are in view, and scrolling or
|
|
12
|
+
* filtering reaches the rest of the list. Featured options must be sorted
|
|
13
|
+
* to the front. Meaningless without `search`.
|
|
14
|
+
*/
|
|
15
|
+
featured?: boolean;
|
|
9
16
|
}
|
|
10
17
|
/**
|
|
11
18
|
* One choice in a multi-select question. Row availability is part of the
|
package/dist/src/setup/ask.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const select=e=>({...e,kind:`select`}),confirm=e=>({...e,kind:`confirm`}),text=e=>({...e,kind:`text`});var SkippedSignal=class extends Error{key;constructor(e){super(`Skipped: ${e}`),this.name=`SkippedSignal`,this.key=e}},InteractionRequired=class extends Error{question;constructor(e){super(`Interaction required for "${e.key}": ${e.message}`),this.name=`InteractionRequired`,this.question=e}},InvalidAnswerError=class extends Error{key;constructor(e,t){super(t),this.name=`InvalidAnswerError`,this.key=e}};function announce(e,t,n,r){return e?.onResolved?.({key:t,value:n,source:r}),n}function coerceAnswer(e,t){if(e.kind===`select`){let n=String(t),r=e.options.find(e=>e.id===n);if(!r){let t=e.options.map(e=>e.id).join(`, `);throw new InvalidAnswerError(e.key,`Invalid answer for "${e.key}": ${n}. Expected one of: ${t}.`)}return r.value}if(e.kind===`confirm`){let n=typeof t==`boolean`?t:t===`true`?!0:t===`false`?!1:null;if(n===null)throw new InvalidAnswerError(e.key,`Invalid answer for "${e.key}": expected a boolean.`);return n}let n=String(t),r=e.validate?.(n);if(r)throw new InvalidAnswerError(e.key,r);return n}function coerceManyAnswer(e,t){if(!Array.isArray(t))throw new InvalidAnswerError(e.key,`Invalid answer for "${e.key}": expected an array of option ids.`);let n=[];for(let r of t){let t=String(r),i=e.options.find(e=>e.id===t);if(!i){let n=e.options.map(e=>e.id).join(`, `);throw new InvalidAnswerError(e.key,`Invalid answer for "${e.key}": ${t}. Expected one of: ${n}.`)}if(i.disabled){let n=i.disabledReason===void 0?``:` (${i.disabledReason})`;throw new InvalidAnswerError(e.key,`Invalid answer for "${e.key}": ${t} is unavailable${n}.`)}n.includes(i.value)||n.push(i.value)}for(let t of e.options)t.locked===!0&&!n.includes(t.value)&&n.push(t.value);return n}async function renderQuestion(e,t){if(t.kind===`select`){let n=t.detected??t.recommended;return coerceAnswer(t,await e.select({message:t.message,options:t.options.map(e=>({value:e.id,label:e.label,hint:e.hint})),initialValue:n===void 0?void 0:t.options.find(e=>e.value===n)?.id,search:t.search,placeholder:t.placeholder}))}if(t.kind===`confirm`){let n=t.detected??t.recommended;return coerceAnswer(t,await e.select({message:t.message,options:[{value:`yes`,label:`Yes`},{value:`no`,label:`No`}],initialValue:n===void 0?void 0:n?`yes`:`no`})===`yes`)}let n=t.validate,r=n===void 0?void 0:e=>n(e)??void 0,i=t.detected??t.recommended;return coerceAnswer(t,t.sensitive?await e.password({message:t.message,validate:r}):await e.text({message:t.message,placeholder:t.placeholder,defaultValue:i===void 0?void 0:String(i),validate:r}))}async function renderManyQuestion(e,t){let n=t.detected??t.recommended;return(await e.select({multiple:!0,message:t.message,options:t.options.map(e=>({value:e.id,label:e.label,hint:e.hint,disabled:e.disabled,disabledReason:e.disabledReason,locked:e.locked,lockedReason:e.lockedReason})),initialValues:n===void 0?void 0:t.options.filter(e=>n.includes(e.value)).map(e=>e.id),required:t.requireSelection,search:t.search,placeholder:t.placeholder})).map(e=>{let n=t.options.find(t=>t.id===e);if(!n)throw new InvalidAnswerError(t.key,`Invalid answer for "${t.key}": ${e} is not an option id.`);return n.value})}function interactiveAsker(e,t){return{async ask(n){let r=await renderQuestion(e,n);return n.internal?r:announce(t,n.key,r,`asked`)},async askMany(n){let r=await renderManyQuestion(e,n);return n.internal?r:announce(t,n.key,r,`asked`)}}}function headlessAsker(e){function refuse(t){throw t.required?new InteractionRequired(t):(announce(e,t.key,void 0,`skipped`),new SkippedSignal(t.key))}return{async ask(e){return refuse(e)},async askMany(e){return refuse(e)}}}function withAnswers(e,t){return n=>({async ask(r){if(r.key in e){let n=coerceAnswer(r,e[r.key]);return announce(t,r.key,n,`answer`)}return n.ask(r)},async askMany(r){if(r.key in e){let n=coerceManyAnswer(r,e[r.key]);return announce(t,r.key,n,`answer`)}return n.askMany(r)}})}function withRequired(e){return t=>({async ask(n){return!n.required&&e.includes(n.key)?t.ask({...n,required:!0}):t.ask(n)},async askMany(n){return!n.required&&e.includes(n.key)?t.askMany({...n,required:!0}):t.askMany(n)}})}function withPolicy(e,t){return n=>({async ask(r){if(e===`assume`){if(r.detected!==void 0)return announce(t,r.key,r.detected,`detected`);if(r.recommended!==void 0)return announce(t,r.key,r.recommended,`assumed`);if(!r.required)throw announce(t,r.key,void 0,`skipped`),new SkippedSignal(r.key);return n.ask(r)}return r.detected!==void 0&&await n.ask(confirm({key:r.key,message:`Use the detected value for "${r.key}"?`,internal:!0}))?announce(t,r.key,r.detected,`detected`):n.ask(r)},async askMany(r){if(e===`assume`){if(r.detected!==void 0)return announce(t,r.key,[...r.detected],`detected`);if(r.recommended!==void 0)return announce(t,r.key,[...r.recommended],`assumed`);if(!r.required)throw announce(t,r.key,void 0,`skipped`),new SkippedSignal(r.key);return n.askMany(r)}return r.detected!==void 0&&await n.ask(confirm({key:r.key,message:`Use the detected value for "${r.key}"?`,internal:!0}))?announce(t,r.key,[...r.detected],`detected`):n.askMany(r)}})}export{InteractionRequired,InvalidAnswerError,SkippedSignal,confirm,headlessAsker,interactiveAsker,select,text,withAnswers,withPolicy,withRequired};
|
|
1
|
+
const select=e=>({...e,kind:`select`}),confirm=e=>({...e,kind:`confirm`}),text=e=>({...e,kind:`text`});var SkippedSignal=class extends Error{key;constructor(e){super(`Skipped: ${e}`),this.name=`SkippedSignal`,this.key=e}},InteractionRequired=class extends Error{question;constructor(e){super(`Interaction required for "${e.key}": ${e.message}`),this.name=`InteractionRequired`,this.question=e}},InvalidAnswerError=class extends Error{key;constructor(e,t){super(t),this.name=`InvalidAnswerError`,this.key=e}};function announce(e,t,n,r){return e?.onResolved?.({key:t,value:n,source:r}),n}function coerceAnswer(e,t){if(e.kind===`select`){let n=String(t),r=e.options.find(e=>e.id===n);if(!r){let t=e.options.map(e=>e.id).join(`, `);throw new InvalidAnswerError(e.key,`Invalid answer for "${e.key}": ${n}. Expected one of: ${t}.`)}return r.value}if(e.kind===`confirm`){let n=typeof t==`boolean`?t:t===`true`?!0:t===`false`?!1:null;if(n===null)throw new InvalidAnswerError(e.key,`Invalid answer for "${e.key}": expected a boolean.`);return n}let n=String(t),r=e.validate?.(n);if(r)throw new InvalidAnswerError(e.key,r);return n}function coerceManyAnswer(e,t){if(!Array.isArray(t))throw new InvalidAnswerError(e.key,`Invalid answer for "${e.key}": expected an array of option ids.`);let n=[];for(let r of t){let t=String(r),i=e.options.find(e=>e.id===t);if(!i){let n=e.options.map(e=>e.id).join(`, `);throw new InvalidAnswerError(e.key,`Invalid answer for "${e.key}": ${t}. Expected one of: ${n}.`)}if(i.disabled){let n=i.disabledReason===void 0?``:` (${i.disabledReason})`;throw new InvalidAnswerError(e.key,`Invalid answer for "${e.key}": ${t} is unavailable${n}.`)}n.includes(i.value)||n.push(i.value)}for(let t of e.options)t.locked===!0&&!n.includes(t.value)&&n.push(t.value);return n}async function renderQuestion(e,t){if(t.kind===`select`){let n=t.detected??t.recommended;return coerceAnswer(t,await e.select({message:t.message,options:t.options.map(e=>({value:e.id,label:e.label,hint:e.hint,featured:e.featured})),initialValue:n===void 0?void 0:t.options.find(e=>e.value===n)?.id,search:t.search,placeholder:t.placeholder}))}if(t.kind===`confirm`){let n=t.detected??t.recommended;return coerceAnswer(t,await e.select({message:t.message,options:[{value:`yes`,label:`Yes`},{value:`no`,label:`No`}],initialValue:n===void 0?void 0:n?`yes`:`no`})===`yes`)}let n=t.validate,r=n===void 0?void 0:e=>n(e)??void 0,i=t.detected??t.recommended;return coerceAnswer(t,t.sensitive?await e.password({message:t.message,validate:r}):await e.text({message:t.message,placeholder:t.placeholder,defaultValue:i===void 0?void 0:String(i),validate:r}))}async function renderManyQuestion(e,t){let n=t.detected??t.recommended;return(await e.select({multiple:!0,message:t.message,options:t.options.map(e=>({value:e.id,label:e.label,hint:e.hint,featured:e.featured,disabled:e.disabled,disabledReason:e.disabledReason,locked:e.locked,lockedReason:e.lockedReason})),initialValues:n===void 0?void 0:t.options.filter(e=>n.includes(e.value)).map(e=>e.id),required:t.requireSelection,search:t.search,placeholder:t.placeholder})).map(e=>{let n=t.options.find(t=>t.id===e);if(!n)throw new InvalidAnswerError(t.key,`Invalid answer for "${t.key}": ${e} is not an option id.`);return n.value})}function interactiveAsker(e,t){return{async ask(n){let r=await renderQuestion(e,n);return n.internal?r:announce(t,n.key,r,`asked`)},async askMany(n){let r=await renderManyQuestion(e,n);return n.internal?r:announce(t,n.key,r,`asked`)}}}function headlessAsker(e){function refuse(t){throw t.required?new InteractionRequired(t):(announce(e,t.key,void 0,`skipped`),new SkippedSignal(t.key))}return{async ask(e){return refuse(e)},async askMany(e){return refuse(e)}}}function withAnswers(e,t){return n=>({async ask(r){if(r.key in e){let n=coerceAnswer(r,e[r.key]);return announce(t,r.key,n,`answer`)}return n.ask(r)},async askMany(r){if(r.key in e){let n=coerceManyAnswer(r,e[r.key]);return announce(t,r.key,n,`answer`)}return n.askMany(r)}})}function withRequired(e){return t=>({async ask(n){return!n.required&&e.includes(n.key)?t.ask({...n,required:!0}):t.ask(n)},async askMany(n){return!n.required&&e.includes(n.key)?t.askMany({...n,required:!0}):t.askMany(n)}})}function withPolicy(e,t){return n=>({async ask(r){if(e===`assume`){if(r.detected!==void 0)return announce(t,r.key,r.detected,`detected`);if(r.recommended!==void 0)return announce(t,r.key,r.recommended,`assumed`);if(!r.required)throw announce(t,r.key,void 0,`skipped`),new SkippedSignal(r.key);return n.ask(r)}return r.detected!==void 0&&await n.ask(confirm({key:r.key,message:`Use the detected value for "${r.key}"?`,internal:!0}))?announce(t,r.key,r.detected,`detected`):n.ask(r)},async askMany(r){if(e===`assume`){if(r.detected!==void 0)return announce(t,r.key,[...r.detected],`detected`);if(r.recommended!==void 0)return announce(t,r.key,[...r.recommended],`assumed`);if(!r.required)throw announce(t,r.key,void 0,`skipped`),new SkippedSignal(r.key);return n.askMany(r)}return r.detected!==void 0&&await n.ask(confirm({key:r.key,message:`Use the detected value for "${r.key}"?`,internal:!0}))?announce(t,r.key,[...r.detected],`detected`):n.askMany(r)}})}export{InteractionRequired,InvalidAnswerError,SkippedSignal,confirm,headlessAsker,interactiveAsker,select,text,withAnswers,withPolicy,withRequired};
|
|
@@ -54,6 +54,14 @@ export interface AddChannelsOptions {
|
|
|
54
54
|
* resolves the project up front via the link box and keeps the hard gate.
|
|
55
55
|
*/
|
|
56
56
|
ensureLinkedProject?: "interactive-vercel-link";
|
|
57
|
+
/**
|
|
58
|
+
* What a failed slackbot provision (create or attach) does to the run. The
|
|
59
|
+
* default, "abort", fails the whole box — right for `eve channels add slack`,
|
|
60
|
+
* where Slack is the point. Onboarding passes "warn-and-continue": the agent
|
|
61
|
+
* still scaffolds, deploys, and chats without Slack (recorded as nothing, so
|
|
62
|
+
* a later `eve channels add slack` starts clean).
|
|
63
|
+
*/
|
|
64
|
+
slackbotFailure?: "abort" | "warn-and-continue";
|
|
57
65
|
deps?: AddChannelsDeps;
|
|
58
66
|
}
|
|
59
67
|
/**
|
|
@@ -74,8 +82,9 @@ export interface AddChannelsSlackbotFacts {
|
|
|
74
82
|
* What `perform` actually did. `channelsAdded` lists the channels recorded this
|
|
75
83
|
* run (web before slack); a skipped Web scaffold (Next.js detected) records
|
|
76
84
|
* nothing, deliberately. `slackbot` is present only after a fresh, fully
|
|
77
|
-
* attached provision; every failure mode throws
|
|
78
|
-
*
|
|
85
|
+
* attached provision; every failure mode either throws or (under
|
|
86
|
+
* `slackbotFailure: "warn-and-continue"`) skips Slack entirely, so a failed
|
|
87
|
+
* Slack setup records nothing (atomicity).
|
|
79
88
|
*/
|
|
80
89
|
export interface AddChannelsPayload {
|
|
81
90
|
channelsAdded: ChannelKind[];
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{SkippedSignal,confirm}from"../ask.js";import{detectDeployment,isProjectResolved,mergeProjectResolution,projectResolutionFromDeployment}from"../project-resolution.js";import{provisionSlackbot,reconcileSlackUid}from"../slackbot.js";import{hasVercelProject,requireProjectPath}from"../state.js";import{deriveSlackConnectorSlug,ensureChannel}from"#setup/scaffold/index.js";import{HumanActionRequiredError}from"#setup/human-action.js";import{runVercel}from"#setup/primitives/run-vercel.js";const SLACKBOT_NOT_ATTACHED_ERROR=`Slackbot provisioning did not attach this project. Slack channel was not added.`;function warnOverwrittenFiles(e,t){for(let n of t??[])e.warning(`Overwrote ${n}`)}function warnCompetingNextConfigFiles(e,t){for(let n of t??[])e.warning(`Found competing Next.js config at ${n}; merge any needed settings into next.config.ts and remove it before starting the preview, or Next.js may ignore the generated Eve rewrite.`)}function addChannels(l){let u=l.deps??{ensureChannel,deriveSlackConnectorSlug,provisionSlackbot,reconcileSlackUid,runVercel,detectDeployment};async function scaffoldSlackChannel(e,t,n,r,i){if(!t.slackScaffolded){e.message(`Scaffolding Slack channel files...`);let t=await u.ensureChannel({projectRoot:n,kind:`slack`,slackConnectorSlug:r,force:l.force});warnOverwrittenFiles(e,t.filesOverwritten),t.action===`created`||t.action===`overwritten`?e.success(`Scaffolded channel: slack`):e.info(`Channel "slack" already exists. Skipping file creation.`),i.slackScaffolded=!0}i.channelsAdded.push(`slack`)}async function linkProjectForSlackbot(e,t,n,o){if(o)throw new HumanActionRequiredError({kind:`vercel-link`,command:`vercel link`,reason:`Slackbot creation needs this directory linked to a Vercel project.`});if(e.message(`Linking this directory to a Vercel project...`),!await u.runVercel([`link`],{cwd:t}))throw Error(`Vercel project linking failed. Slackbot creation did not start.`);let s=mergeProjectResolution(n,projectResolutionFromDeployment(await u.detectDeployment(t)));if(!isProjectResolved(s))throw Error(`Vercel project linking failed. Slackbot creation did not start.`);return s}async function performAddChannels(e,t){let n=l.prompter.log,i=requireProjectPath(e),a=e.channelSelection,o={channelsAdded:[],webScaffolded:e.webScaffolded,slackScaffolded:e.slackScaffolded,project:e.project};if(a.includes(`web`))if(e.webScaffolded)o.channelsAdded.push(`web`);else{n.message(`Scaffolding Web Chat channel files...`);let t={projectRoot:i,kind:`web`,force:l.force,configureVercelServices:l.configureVercelServices??hasVercelProject(e)};l.evePackageVersion!==void 0&&(t.webPackageVersions={evePackageVersion:l.evePackageVersion});let r=await u.ensureChannel(t);warnOverwrittenFiles(n,r.filesOverwritten),warnCompetingNextConfigFiles(n,`competingNextConfigFiles`in r?r.competingNextConfigFiles:void 0),r.action===`created`||r.action===`overwritten`?(n.success(`Scaffolded channel: web`),o.webScaffolded=!0,o.channelsAdded.push(`web`)):n.info(`Next.js project detected. Skipping Web Chat scaffolding.`)}if(a.includes(`slack`)){if(l.ensureLinkedProject===void 0){if(!hasVercelProject(e))throw Error(`Slack requires a Vercel project. Re-run and choose to deploy to Vercel to add Slack.`);if(!isProjectResolved(e.project))throw Error(`Expected a linked Vercel project for Slack, but none was resolved.`)}let a=await u.deriveSlackConnectorSlug(i,e.agentName),s=`slack/${a}`;if(e.slackbotCreated){if(!e.slackbotAttached)throw Error(SLACKBOT_NOT_ATTACHED_ERROR);if(e.deploymentPending){await scaffoldSlackChannel(n,e,i,a,o);let t=e.slackConnectorUid;if(t===void 0)throw Error(`Slack connector UID was not resolved. Slack deployment did not start.`);if(!await u.reconcileSlackUid(n,i,{kind:`attached`,created:!0,attached:!0,connectorUid:t},s))throw Error(`Slack connector UID update is required before deployment.`)}}else if(t.createSlackbot!==!0)n.info(`Slack channel was not added because Slackbot setup was skipped.`);else{isProjectResolved(o.project)||(o.project=await linkProjectForSlackbot(n,i,o.project,t.headless));let c=await u.provisionSlackbot(n,i,a);if(
|
|
1
|
+
import{SkippedSignal,confirm}from"../ask.js";import{detectDeployment,isProjectResolved,mergeProjectResolution,projectResolutionFromDeployment}from"../project-resolution.js";import{provisionSlackbot,reconcileSlackUid}from"../slackbot.js";import{hasVercelProject,requireProjectPath}from"../state.js";import{deriveSlackConnectorSlug,ensureChannel}from"#setup/scaffold/index.js";import{HumanActionRequiredError}from"#setup/human-action.js";import{runVercel}from"#setup/primitives/run-vercel.js";const SLACKBOT_NOT_ATTACHED_ERROR=`Slackbot provisioning did not attach this project. Slack channel was not added.`;function warnOverwrittenFiles(e,t){for(let n of t??[])e.warning(`Overwrote ${n}`)}function warnCompetingNextConfigFiles(e,t){for(let n of t??[])e.warning(`Found competing Next.js config at ${n}; merge any needed settings into next.config.ts and remove it before starting the preview, or Next.js may ignore the generated Eve rewrite.`)}function addChannels(l){let u=l.deps??{ensureChannel,deriveSlackConnectorSlug,provisionSlackbot,reconcileSlackUid,runVercel,detectDeployment};async function scaffoldSlackChannel(e,t,n,r,i){if(!t.slackScaffolded){e.message(`Scaffolding Slack channel files...`);let t=await u.ensureChannel({projectRoot:n,kind:`slack`,slackConnectorSlug:r,force:l.force});warnOverwrittenFiles(e,t.filesOverwritten),t.action===`created`||t.action===`overwritten`?e.success(`Scaffolded channel: slack`):e.info(`Channel "slack" already exists. Skipping file creation.`),i.slackScaffolded=!0}i.channelsAdded.push(`slack`)}async function linkProjectForSlackbot(e,t,n,o){if(o)throw new HumanActionRequiredError({kind:`vercel-link`,command:`vercel link`,reason:`Slackbot creation needs this directory linked to a Vercel project.`});if(e.message(`Linking this directory to a Vercel project...`),!await u.runVercel([`link`],{cwd:t}))throw Error(`Vercel project linking failed. Slackbot creation did not start.`);let s=mergeProjectResolution(n,projectResolutionFromDeployment(await u.detectDeployment(t)));if(!isProjectResolved(s))throw Error(`Vercel project linking failed. Slackbot creation did not start.`);return s}async function performAddChannels(e,t){let n=l.prompter.log,i=requireProjectPath(e),a=e.channelSelection,o={channelsAdded:[],webScaffolded:e.webScaffolded,slackScaffolded:e.slackScaffolded,project:e.project};if(a.includes(`web`))if(e.webScaffolded)o.channelsAdded.push(`web`);else{n.message(`Scaffolding Web Chat channel files...`);let t={projectRoot:i,kind:`web`,force:l.force,configureVercelServices:l.configureVercelServices??hasVercelProject(e)};l.evePackageVersion!==void 0&&(t.webPackageVersions={evePackageVersion:l.evePackageVersion});let r=await u.ensureChannel(t);warnOverwrittenFiles(n,r.filesOverwritten),warnCompetingNextConfigFiles(n,`competingNextConfigFiles`in r?r.competingNextConfigFiles:void 0),r.action===`created`||r.action===`overwritten`?(n.success(`Scaffolded channel: web`),o.webScaffolded=!0,o.channelsAdded.push(`web`)):n.info(`Next.js project detected. Skipping Web Chat scaffolding.`)}if(a.includes(`slack`)){if(l.ensureLinkedProject===void 0){if(!hasVercelProject(e))throw Error(`Slack requires a Vercel project. Re-run and choose to deploy to Vercel to add Slack.`);if(!isProjectResolved(e.project))throw Error(`Expected a linked Vercel project for Slack, but none was resolved.`)}let a=await u.deriveSlackConnectorSlug(i,e.agentName),s=`slack/${a}`;if(e.slackbotCreated){if(!e.slackbotAttached)throw Error(SLACKBOT_NOT_ATTACHED_ERROR);if(e.deploymentPending){await scaffoldSlackChannel(n,e,i,a,o);let t=e.slackConnectorUid;if(t===void 0)throw Error(`Slack connector UID was not resolved. Slack deployment did not start.`);if(!await u.reconcileSlackUid(n,i,{kind:`attached`,created:!0,attached:!0,connectorUid:t},s))throw Error(`Slack connector UID update is required before deployment.`)}}else if(t.createSlackbot!==!0)n.info(`Slack channel was not added because Slackbot setup was skipped.`);else{isProjectResolved(o.project)||(o.project=await linkProjectForSlackbot(n,i,o.project,t.headless));let c=await u.provisionSlackbot(n,i,a);if(c.kind!==`attached`){let e=c.created?SLACKBOT_NOT_ATTACHED_ERROR:`Slackbot creation failed.`;if(l.slackbotFailure!==`warn-and-continue`)throw Error(e);let t=c.created?"Continuing without Slack — finish event delivery with the `vercel connect attach` command above.":"Continuing without Slack — add it later with `eve channels add slack`.";n.warning(`${e} ${t}`)}else if(o.slackbot={connectorUid:c.connectorUid,workspaceUrl:c.workspaceUrl,workspaceName:c.workspaceName},await scaffoldSlackChannel(n,e,i,a,o),!await u.reconcileSlackUid(n,i,c,s))throw Error(`Slack connector UID update is required before deployment.`)}}return o}return{id:`add-channels`,async gather({state:n}){let r=l.headless??!1;if(r&&n.channelSelection.includes(`slack`))throw Error(`Slack setup is interactive. Run the guided create flow with -i, or add Slack from the onboarding steps after headless scaffolding.`);if(!n.channelSelection.includes(`slack`)||l.presetCreateSlackbot!==void 0||n.slackbotCreated)return{headless:r,createSlackbot:l.presetCreateSlackbot};try{return{headless:r,createSlackbot:await l.asker.ask(confirm({key:`create-slackbot`,message:`Do you want to create your slackbot?`}))}}catch(t){if(t instanceof SkippedSignal)return{headless:r,createSlackbot:!1};throw t}},async perform({state:e,input:t}){try{return await performAddChannels(e,t)}catch(e){let t=e instanceof Error?e.message:String(e),n=t.split(`
|
|
2
2
|
`)[0]?.trim()??t;throw l.prompter.log.error(n),e}},apply(e,t){let n=[...e.channels];for(let e of t.channelsAdded)n.includes(e)||n.push(e);let r={...e,channels:n,webScaffolded:t.webScaffolded,slackScaffolded:t.slackScaffolded,deploymentPending:e.deploymentPending||t.channelsAdded.length>0,project:mergeProjectResolution(e.project,t.project)};return t.slackbot===void 0?r:{...r,slackbotCreated:!0,slackbotAttached:!0,slackConnectorUid:t.slackbot.connectorUid,slackWorkspaceUrl:t.slackbot.workspaceUrl,slackWorkspaceName:t.slackbot.workspaceName}}}}export{addChannels};
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { ensureConnection
|
|
2
|
-
import type { ConnectionSelectOption } from "#setup/cli/index.js";
|
|
3
|
-
import { type Asker } from "../ask.js";
|
|
1
|
+
import { ensureConnection } from "#setup/scaffold/index.js";
|
|
4
2
|
import { setupConnectionConnector } from "../connection-connector.js";
|
|
5
3
|
import { type ProjectResolution } from "../project-resolution.js";
|
|
6
4
|
import type { Prompter } from "../prompter.js";
|
|
@@ -12,63 +10,15 @@ export interface AddConnectionsDeps {
|
|
|
12
10
|
setupConnectionConnector: typeof setupConnectionConnector;
|
|
13
11
|
}
|
|
14
12
|
export interface AddConnectionsOptions {
|
|
15
|
-
/**
|
|
16
|
-
asker: Asker;
|
|
17
|
-
/**
|
|
18
|
-
* Carries the skipped-step log line and `perform`'s progress output. The
|
|
19
|
-
* picker and custom sub-questions themselves now travel the asker.
|
|
20
|
-
*/
|
|
13
|
+
/** Carries the follow-up log lines and `perform`'s progress output. The box never prompts. */
|
|
21
14
|
prompter: Prompter;
|
|
22
|
-
/**
|
|
23
|
-
* Headless mode: skips the picker entirely unless `presetConnections` were
|
|
24
|
-
* requested. Fixed at composition time (the same place the asker base is
|
|
25
|
-
* chosen), since `gather` cannot read the mode off the asker.
|
|
26
|
-
*/
|
|
27
|
-
headless?: boolean;
|
|
28
|
-
/** Skip the picker and scaffold exactly these catalog slugs. */
|
|
29
|
-
presetConnections?: string[];
|
|
30
15
|
deps?: AddConnectionsDeps;
|
|
31
16
|
}
|
|
32
17
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
18
|
+
* THE CONNECTIONS BOX: executes the {@link ConnectionPlan}s the
|
|
19
|
+
* select-connections box recorded during the interview. Prompts for nothing —
|
|
20
|
+
* every decision (slug, protocol, entry, provisioning mode) was resolved at
|
|
21
|
+
* selection time — and only runs effects: the file scaffold, the follow-up log
|
|
22
|
+
* lines, and the Connect connector provisioning against the linked project.
|
|
36
23
|
*/
|
|
37
|
-
export
|
|
38
|
-
slug: string;
|
|
39
|
-
protocol: ConnectionProtocol;
|
|
40
|
-
entry: ConnectionInput;
|
|
41
|
-
provision: {
|
|
42
|
-
kind: "connect";
|
|
43
|
-
service: string;
|
|
44
|
-
} | {
|
|
45
|
-
kind: "connect-manual";
|
|
46
|
-
} | {
|
|
47
|
-
kind: "command-hint";
|
|
48
|
-
service: string;
|
|
49
|
-
} | {
|
|
50
|
-
kind: "none";
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Which gather face produced the input. `skip` records the face's decision that
|
|
55
|
-
* there is nothing to do (headless with no presets, or an interactive run where
|
|
56
|
-
* no catalog entry is usable without a project), since `shouldRun` is sync and
|
|
57
|
-
* cannot tell the faces apart.
|
|
58
|
-
*/
|
|
59
|
-
export interface AddConnectionsInput {
|
|
60
|
-
headless: boolean;
|
|
61
|
-
skip: boolean;
|
|
62
|
-
plans: ConnectionPlan[];
|
|
63
|
-
}
|
|
64
|
-
/** Exported for tests: the picker rows derived from the curated catalog. */
|
|
65
|
-
export declare function buildCatalogOptions(disabledReasons: Readonly<Record<string, string>>): ConnectionSelectOption[];
|
|
66
|
-
/**
|
|
67
|
-
* THE CONNECTIONS BOX. Offers the connection catalog (plus a custom MCP /
|
|
68
|
-
* OpenAPI escape hatch through preset slugs) and scaffolds the selected
|
|
69
|
-
* `agent/connections/*.ts`. Gather resolves every prompt into fully-specified
|
|
70
|
-
* {@link ConnectionPlan}s; `perform` only runs effects: the file scaffold, the
|
|
71
|
-
* follow-up log lines, and the Connect connector provisioning. Skipped in
|
|
72
|
-
* headless mode unless explicit `presetConnections` were requested.
|
|
73
|
-
*/
|
|
74
|
-
export declare function addConnections(options: AddConnectionsOptions): SetupBox<SetupState, AddConnectionsInput, ProjectResolution>;
|
|
24
|
+
export declare function addConnections(options: AddConnectionsOptions): SetupBox<SetupState, null, ProjectResolution>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{isProjectResolved,mergeProjectResolution}from"../project-resolution.js";import{hasVercelProject,requireProjectPath}from"../state.js";import{setupConnectionConnector}from"../connection-connector.js";import{projectIdFromResolution}from"../vercel-project.js";import{CONNECT_REQUIRES_VERCEL}from"./select-connections.js";import{ensureConnection}from"#setup/scaffold/index.js";function logFollowUp(e,t){if(t.action===`skipped`){e.warning(`Skipped ${t.slug} (already exists; pass --force to overwrite).`);return}e.success(`Added agent/connections/${t.slug}.ts`),t.envKeysAdded.length>0?e.info(`Set ${t.envKeysAdded.join(`, `)} in .env.local`):t.envKeysRequired.length>0&&e.info(`Set ${t.envKeysRequired.join(`, `)} in your environment`)}function addConnections(a){let o=a.deps??{ensureConnection,setupConnectionConnector};return{id:`add-connections`,shouldRun(e){return e.connectionSelection.length>0},async gather(){return null},async perform({state:t}){let r=a.prompter.log,i=requireProjectPath(t),s=!hasVercelProject(t),c=t.project;for(let n of t.connectionSelection){let t=await o.ensureConnection({projectRoot:i,slug:n.slug,protocol:n.protocol,entry:n.entry});if(logFollowUp(r,t),t.action!==`skipped`)switch(n.provision.kind){case`connect`:await o.setupConnectionConnector({log:r,projectRoot:i,slug:t.slug,service:n.provision.service,connectionFilePath:t.filePath,linkProject:async()=>{if(s)throw Error(CONNECT_REQUIRES_VERCEL);if(!isProjectResolved(c))throw Error(`Expected a linked Vercel project for Connect, but none was resolved.`);return projectIdFromResolution(c)}});break;case`command-hint`:r.info(`Run \`vercel connect create ${n.provision.service} --name ${t.slug}\`, then set the connector UID in agent/connections/${t.slug}.ts.`);break;case`connect-manual`:r.warning(`Could not determine a Connect service for ${t.slug}. Create the connector manually and set its UID in agent/connections/${t.slug}.ts.`);break;case`none`:break}}return c},apply(e,n){return{...e,project:mergeProjectResolution(e.project,n)}}}}export{addConnections};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{detectDeployment,isProjectResolved,mergeProjectResolution,projectResolutionFromDeployResult,projectResolutionFromDeployment}from"../project-resolution.js";import{hasVercelProject,requireProjectPath}from"../state.js";import{
|
|
1
|
+
import{detectDeployment,isProjectResolved,mergeProjectResolution,projectResolutionFromDeployResult,projectResolutionFromDeployment}from"../project-resolution.js";import{hasVercelProject,requireProjectPath}from"../state.js";import{createPromptCommandOutput}from"#setup/cli/index.js";import{HumanActionRequiredError}from"#setup/human-action.js";import{runVercel}from"#setup/primitives/run-vercel.js";import{runPnpmInstall}from"#setup/primitives/run-pnpm.js";const VERCEL_DEPLOY_ENV={VERCEL_USE_EXPERIMENTAL_FRAMEWORKS:`1`};function deployProject(a){let o=a.deps??{runVercel,runPnpmInstall,detectDeployment};return{id:`deploy-project`,shouldRun(e){return a.skip||!e.deploymentPending?!1:hasVercelProject(e)||a.ensureLinkedProject===`interactive-vercel-link`},async gather(){return{headless:a.headless??!1}},async perform({state:e,input:n}){let r=requireProjectPath(e),{log:i}=a.prompter,s=createPromptCommandOutput(i),c=e.project;if(!isProjectResolved(c)){if(n.headless)throw new HumanActionRequiredError({kind:`vercel-link`,command:`vercel link`,reason:`Deployment needs this directory linked to a Vercel project.`});if(i.message(`Linking this directory to a Vercel project before deployment...`),!await o.runVercel([`link`],{cwd:r})||(c=mergeProjectResolution(c,projectResolutionFromDeployment(await o.detectDeployment(r))),!isProjectResolved(c)))throw Error(`Vercel project linking failed. Deployment did not start.`)}if(!e.deploymentDependenciesInstalled&&(i.message(`Installing project dependencies before deployment (pnpm install)...`),!await o.runPnpmInstall(r,{onOutput:s})))throw Error(`Dependency installation failed. Deployment did not start.`);i.message(`Deploying the agent to Vercel production...`);let l=n.headless?[`deploy`,`--prod`,`--yes`,`--non-interactive`]:[`deploy`,`--prod`,`--yes`];if(!await o.runVercel(l,{cwd:r,extraEnv:VERCEL_DEPLOY_ENV,nonInteractive:n.headless,onOutput:s}))throw i.error("`vercel deploy --prod` failed. The deploy output above shows the cause; fix it, then run `vercel deploy --prod` to retry."),Error(`Deployment failed after channel setup.`);i.message(`Pulling Vercel environment variables into .env.local...`),await o.runVercel([`env`,`pull`,`--yes`],{cwd:r,nonInteractive:n.headless,onOutput:s})||i.warning(`Deployment succeeded, but pulling Vercel environment variables did not complete.`);let u=await o.detectDeployment(r);u.state===`deployed`&&u.productionUrl!==void 0&&i.info(`Production URL: ${u.productionUrl}`);let d=u.state===`deployed`,f=projectResolutionFromDeployResult(c,{deployed:d,productionUrl:u.productionUrl});if(!d)throw Error(`Deployment failed after channel setup.`);return{project:f,deploymentPending:!1,deploymentDependenciesInstalled:!0}},apply(e,t){return{...e,project:t.project,deploymentPending:t.deploymentPending,deploymentDependenciesInstalled:t.deploymentDependenciesInstalled}}}}export{deployProject};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Prompter } from "../prompter.js";
|
|
2
|
+
import { type SetupState } from "../state.js";
|
|
3
|
+
import type { SetupBox } from "../step.js";
|
|
4
|
+
export interface OneShotNextStepsOptions {
|
|
5
|
+
/** Reports the next-steps note. The box never prompts. */
|
|
6
|
+
prompter: Prompter;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* THE ONE-SHOT EPILOGUE BOX: a one-shot run skips every post-scaffold box, so
|
|
10
|
+
* it ends here with the manual steps a complete run would have performed:
|
|
11
|
+
* install and local dev, plus a credential hint since no gateway credential
|
|
12
|
+
* was wired.
|
|
13
|
+
*/
|
|
14
|
+
export declare function oneShotNextSteps(options: OneShotNextStepsOptions): SetupBox<SetupState, null, null>;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{requireProjectPath}from"../state.js";import{relative}from"node:path";function cdTarget(e){let n=relative(process.cwd(),e);return n.length===0||n.startsWith(`..`)?e:n}function oneShotNextSteps(t){return{id:`one-shot-next-steps`,shouldRun(e){return e.setupMode===`one-shot`},async gather(){return null},async perform({state:n}){let r=requireProjectPath(n),i=[`Your agent is scaffolded but not deployed or connected yet.`,...n.projectPath.inPlace?[]:[` cd ${cdTarget(r)}`],` pnpm install`,` eve dev # run your agent locally`,"Set AI_GATEWAY_API_KEY in .env.local (or run `vercel link`) before chatting."];return t.prompter.note(i.join(`
|
|
2
|
+
`),`Next steps`,{tone:`success`}),null},apply(e){return e}}}export{oneShotNextSteps};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{select,text}from"../ask.js";import{assertNewProjectNameAvailable,pickNewProjectName,pickProject,pickTeam,requireAuth,resolveTeam,validateTeam}from"../vercel-project.js";import{resolve}from"node:path";function resolveProvisioning(o){let s=o.deps??{requireAuth,validateTeam,resolveTeam,pickTeam,pickProject,pickNewProjectName,assertNewProjectNameAvailable},parent=()=>resolve(o.targetDirectory??process.cwd());async function plansFromFlags(e,t,n){if(t.skipVercel)return n.apiKey===void 0?{vercelProject:{kind:`none`},aiGateway:{kind:`byop`},modelWiring:`self`}:{vercelProject:{kind:`none`},aiGateway:{kind:`byok`,apiGatewayKey:n.apiKey},modelWiring:`gateway`};if(t.team===void 0||t.team.length===0)throw Error(`Headless Vercel provisioning requires --team <slug> or --scope <slug> so the current CLI scope is not applied silently.`);await s.requireAuth(parent()),await s.validateTeam(o.prompter,parent(),t.team);let r=await s.resolveTeam(parent(),t.team),i=n.apiKey===void 0?{kind:`inherit`}:{kind:`byok`,apiGatewayKey:n.apiKey},a=t.project===void 0?{kind:`new`,project:e,team:r}:{kind:`existing`,project:t.project,team:r};return a.kind===`new`&&await s.assertNewProjectNameAvailable(parent(),r,a.project),{vercelProject:a,aiGateway:i,modelWiring:`gateway`}}async function promptPlans(n){let r=o.prompter;if(await o.asker.ask(select({key:`deploy`,message:`
|
|
1
|
+
import{select,text}from"../ask.js";import{assertNewProjectNameAvailable,pickNewProjectName,pickProject,pickTeam,requireAuth,resolveTeam,validateTeam}from"../vercel-project.js";import{resolve}from"node:path";function resolveProvisioning(o){let s=o.deps??{requireAuth,validateTeam,resolveTeam,pickTeam,pickProject,pickNewProjectName,assertNewProjectNameAvailable},parent=()=>resolve(o.targetDirectory??process.cwd());async function plansFromFlags(e,t,n){if(t.skipVercel)return n.apiKey===void 0?{vercelProject:{kind:`none`},aiGateway:{kind:`byop`},modelWiring:`self`}:{vercelProject:{kind:`none`},aiGateway:{kind:`byok`,apiGatewayKey:n.apiKey},modelWiring:`gateway`};if(t.team===void 0||t.team.length===0)throw Error(`Headless Vercel provisioning requires --team <slug> or --scope <slug> so the current CLI scope is not applied silently.`);await s.requireAuth(parent()),await s.validateTeam(o.prompter,parent(),t.team);let r=await s.resolveTeam(parent(),t.team),i=n.apiKey===void 0?{kind:`inherit`}:{kind:`byok`,apiGatewayKey:n.apiKey},a=t.project===void 0?{kind:`new`,project:e,team:r}:{kind:`existing`,project:t.project,team:r};return a.kind===`new`&&await s.assertNewProjectNameAvailable(parent(),r,a.project),{vercelProject:a,aiGateway:i,modelWiring:`gateway`}}async function promptPlans(n){let r=o.prompter;if(await o.asker.ask(select({key:`deploy`,message:`Where do you want to deploy your agent?`,options:[{id:`yes`,value:!0,label:`Vercel`,hint:`AI Gateway, Durable Workflow, Sandbox, and more`},{id:`no`,value:!1,label:`Elsewhere`,hint:`Local for now`}],recommended:!0,required:!0}))){await s.requireAuth(parent(),r);let t=await s.pickTeam(r,parent(),void 0);if(await o.asker.ask(select({key:`vercel-project`,message:`Vercel project`,options:[{id:`new`,value:`new`,label:`Create a new project`,hint:`Named '${n}'`},{id:`link`,value:`link`,label:`Link an existing project`}],recommended:`new`,required:!0}))===`new`)return{vercelProject:{kind:`new`,project:await s.pickNewProjectName(r,parent(),t,n),team:t},aiGateway:{kind:`inherit`},modelWiring:`gateway`};let i=await s.pickProject(r,parent(),t);return{vercelProject:{kind:i.exists?`existing`:`new`,project:i.project,team:t},aiGateway:{kind:`inherit`},modelWiring:`gateway`}}return await o.asker.ask(select({key:`credential`,message:`How do you want to wire the model provider credentials?`,options:[{id:`api-key`,value:`api-key`,label:`Use my own AI Gateway API key`,hint:`AI_GATEWAY_API_KEY`},{id:`local`,value:`local`,label:`Use my own provider API key`,hint:`e.g., OPENAI_API_KEY`}],recommended:`api-key`,required:!0}))===`api-key`?{vercelProject:{kind:`none`},aiGateway:{kind:`byok`,apiGatewayKey:await o.asker.ask(text({key:`gateway-api-key`,message:`Enter your AI_GATEWAY_API_KEY`,sensitive:!0,validate:e=>e.trim().length===0?`API key cannot be empty.`:null,required:!0}))},modelWiring:`gateway`}:{vercelProject:{kind:`none`},aiGateway:{kind:`byop`},modelWiring:`self`}}return{id:`resolve-provisioning`,async gather({state:e}){return o.mode.headless?plansFromFlags(e.agentName,o.mode.project,o.mode.aiGateway):promptPlans(e.agentName)},async perform({input:e}){return e},apply(e,t){return{...e,vercelProject:t.vercelProject,aiGateway:t.aiGateway,modelWiring:t.modelWiring}}}}export{resolveProvisioning};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ConnectionSelectOption } from "#setup/cli/index.js";
|
|
2
|
+
import { type Asker } from "../ask.js";
|
|
3
|
+
import type { Prompter } from "../prompter.js";
|
|
4
|
+
import { type ConnectionPlan, type SetupState } from "../state.js";
|
|
5
|
+
import type { SetupBox } from "../step.js";
|
|
6
|
+
export declare const CONNECT_REQUIRES_VERCEL = "Authenticates through Vercel Connect, which needs a Vercel project. Re-run and choose to deploy to Vercel.";
|
|
7
|
+
export interface SelectConnectionsOptions {
|
|
8
|
+
/** Resolves the picker and custom sub-questions; the composed stack decides how. */
|
|
9
|
+
asker: Asker;
|
|
10
|
+
/** Carries the skipped-step log line; the questions themselves travel the asker. */
|
|
11
|
+
prompter: Prompter;
|
|
12
|
+
/**
|
|
13
|
+
* Headless mode: skips the picker entirely unless `presetConnections` were
|
|
14
|
+
* requested. Fixed at composition time (the same place the asker base is
|
|
15
|
+
* chosen), since `gather` cannot read the mode off the asker.
|
|
16
|
+
*/
|
|
17
|
+
headless?: boolean;
|
|
18
|
+
/** Skip the picker and plan exactly these catalog slugs. */
|
|
19
|
+
presetConnections?: string[];
|
|
20
|
+
}
|
|
21
|
+
/** Exported for tests: the picker rows derived from the curated catalog. */
|
|
22
|
+
export declare function buildCatalogOptions(disabledReasons: Readonly<Record<string, string>>): ConnectionSelectOption[];
|
|
23
|
+
/**
|
|
24
|
+
* THE CONNECTION SELECTION BOX: part of the interview phase, before any
|
|
25
|
+
* filesystem write. Offers the connection catalog (plus a custom MCP / OpenAPI
|
|
26
|
+
* escape hatch through preset slugs) and resolves every prompt into
|
|
27
|
+
* fully-specified {@link ConnectionPlan}s recorded on the state; the
|
|
28
|
+
* add-connections box executes them after the scaffold and link. The Vercel
|
|
29
|
+
* gating reads the provisioning plan decided earlier in the interview, so
|
|
30
|
+
* Connect-backed rows disable correctly before any project exists on disk.
|
|
31
|
+
*/
|
|
32
|
+
export declare function selectConnections(options: SelectConnectionsOptions): SetupBox<SetupState, ConnectionPlan[], ConnectionPlan[]>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{select,text}from"../ask.js";import{hasVercelProject}from"../state.js";import{CONNECTION_CATALOG,CUSTOM_CONNECTION_SLUG,SUPPORTED_PROTOCOLS,catalogSlugs,effectiveProtocols,getCatalogEntry,isValidConnectionSlug}from"#setup/scaffold/index.js";import{connectorServiceForEntry}from"#setup/scaffold/connections/catalog.js";const CONNECT_BACKED_SLUGS=CONNECTION_CATALOG.filter(e=>e.auth.kind===`connect`).map(e=>e.slug),CONNECT_REQUIRES_VERCEL=`Authenticates through Vercel Connect, which needs a Vercel project. Re-run and choose to deploy to Vercel.`,NO_AVAILABLE_CONNECTIONS_TITLE=`All connections require a Vercel project. Skipping.`,PROTOCOL_LABELS={mcp:`MCP`,openapi:`OpenAPI`};function buildCatalogOptions(e){return CONNECTION_CATALOG.map(t=>{let n=e[t.slug];return n===void 0?{value:t.slug,label:t.label,hint:t.hint}:{value:t.slug,label:t.label,hint:t.hint,disabled:!0,disabledReason:n}})}function hasAvailableCatalogConnection(e){return CONNECTION_CATALOG.some(t=>!(e&&t.auth.kind===`connect`))}function logSkippedConnectionsStep(e){if(e.log.section!==void 0){e.log.section(NO_AVAILABLE_CONNECTIONS_TITLE,[]);return}e.log.info(NO_AVAILABLE_CONNECTIONS_TITLE)}function assertConnectPresetsAllowed(e,t){if(!e)return;let n=t.filter(e=>CONNECT_BACKED_SLUGS.includes(e));if(n.length>0)throw Error(`${n.join(`, `)} ${CONNECT_REQUIRES_VERCEL}`)}function unknownSlugError(e){return Error(`Unknown connection "${e}". Known: ${catalogSlugs().join(`, `)}, or pass a custom name with a definition.`)}function assertSupportedProtocols(e,t){if(e.length===0)throw Error(`No supported protocol for "${t}". Supported: ${SUPPORTED_PROTOCOLS.join(`, `)}.`)}async function resolveProtocolInteractive(t,n,r){let i=effectiveProtocols(n);return assertSupportedProtocols(i,r),i.length===1?i[0]:t.ask(select({key:`protocol:${r}`,message:`Protocol for ${r}`,options:i.map(e=>({id:e,value:e,label:PROTOCOL_LABELS[e]}))}))}function resolveProtocolHeadless(e,t){let n=effectiveProtocols(e);if(assertSupportedProtocols(n,t),n.length>1)throw Error(`Connection "${t}" supports multiple protocols (${n.join(`, `)}). Pass --protocol to choose one.`);return n[0]}function deriveProvision(e,t){if(e.auth?.kind!==`connect`)return{kind:`none`};let n=connectorServiceForEntry({mcp:e.mcp,auth:e.auth});return n===void 0?{kind:`connect-manual`}:t?{kind:`command-hint`,service:n}:{kind:`connect`,service:n}}async function promptCustomSlug(e){return e.ask(text({key:`connection-name`,message:`Connection name`,placeholder:`mycorp`,validate:e=>{let t=e.trim();return t.length===0?`A name is required.`:isValidConnectionSlug(t)?null:`Start with a lowercase letter; use only lowercase letters, digits, and hyphens (max 64 characters).`}}))}async function planCustomInteractive(e,n){let r=await resolveProtocolInteractive(e,void 0,n),i=await e.ask(text({key:`description:${n}`,message:`Description for ${n}`,placeholder:`What this connection exposes`}));if(r===`mcp`){let a={slug:n,description:i,protocols:[`mcp`],mcp:{url:(await e.ask(text({key:`mcp-url:${n}`,message:`MCP server URL for ${n}`,placeholder:`https://mcp.example.com/sse`,validate:e=>e.trim().length===0?`A URL is required.`:null}))).trim()},auth:{kind:`connect`,connector:n}};return{slug:n,protocol:r,entry:a,provision:deriveProvision(a,!1)}}let a=await e.ask(text({key:`openapi-spec:${n}`,message:`OpenAPI spec URL for ${n}`,placeholder:`https://api.example.com/openapi.json`,validate:e=>e.trim().length===0?`A spec URL is required.`:null})),o=await e.ask(text({key:`openapi-base-url:${n}`,message:`Base URL for ${n} (optional)`,placeholder:`https://api.example.com`})),s={slug:n,description:i,protocols:[`openapi`],openapi:o.trim().length>0?{spec:a.trim(),baseUrl:o.trim()}:{spec:a.trim()},auth:{kind:`connect`,connector:n}};return{slug:n,protocol:r,entry:s,provision:deriveProvision(s,!1)}}async function planSelectionInteractive(e,t){if(t===CUSTOM_CONNECTION_SLUG)return planCustomInteractive(e,(await promptCustomSlug(e)).trim());let n=getCatalogEntry(t);if(n!==void 0)return{slug:t,protocol:await resolveProtocolInteractive(e,n.protocols,t),entry:n,provision:deriveProvision(n,!1)};if(!isValidConnectionSlug(t))throw unknownSlugError(t);return planCustomInteractive(e,t)}function planPresetHeadless(e){if(e===CUSTOM_CONNECTION_SLUG)throw Error(`Custom connection requires interactive input or a preset definition.`);let t=getCatalogEntry(e);if(t!==void 0)return{slug:e,protocol:resolveProtocolHeadless(t.protocols,e),entry:t,provision:deriveProvision(t,!0)};throw isValidConnectionSlug(e)?Error(`Custom connection "${e}" requires interactive input or a preset definition.`):unknownSlugError(e)}function selectConnections(e){return{id:`select-connections`,async gather({state:t}){let r=e.headless??!1,i=e.presetConnections??[],a=!hasVercelProject(t);if(r)return i.length===0?[]:(assertConnectPresetsAllowed(a,i),i.map(e=>planPresetHeadless(e)));let o;if(i.length>0)assertConnectPresetsAllowed(a,i),o=[...i];else{if(a&&!hasAvailableCatalogConnection(a))return logSkippedConnectionsStep(e.prompter),[];let t=buildCatalogOptions(a?Object.fromEntries(CONNECT_BACKED_SLUGS.map(e=>[e,CONNECT_REQUIRES_VERCEL])):{}).map(e=>({id:String(e.value),value:String(e.value),label:e.label,hint:e.hint,disabled:e.disabled,disabledReason:e.disabledReason}));o=await e.asker.askMany({key:`connection`,message:`Select connections`,options:t})}let s=[];for(let t of o)s.push(await planSelectionInteractive(e.asker,t));return s},async perform({input:e}){return e},apply(e,t){return{...e,connectionSelection:t}}}}export{CONNECT_REQUIRES_VERCEL,buildCatalogOptions,selectConnections};
|
|
@@ -2,11 +2,11 @@ import { type Asker } from "../ask.js";
|
|
|
2
2
|
import type { SetupState } from "../state.js";
|
|
3
3
|
import type { SetupBox } from "../step.js";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
* "wire your own provider" path). The
|
|
7
|
-
* provider, so this is an Anthropic model.
|
|
5
|
+
* The default model: pre-selected in the picker and baked into the scaffold
|
|
6
|
+
* when the model prompt is skipped (the "wire your own provider" path). The
|
|
7
|
+
* `byok` template wires the Anthropic provider, so this is an Anthropic model.
|
|
8
8
|
*/
|
|
9
|
-
export declare const DEFAULT_MODEL_ID = "anthropic/claude-
|
|
9
|
+
export declare const DEFAULT_MODEL_ID = "anthropic/claude-sonnet-4.6";
|
|
10
10
|
/** One model entry from the AI Gateway catalog response. */
|
|
11
11
|
export interface GatewayCatalogModel {
|
|
12
12
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{select}from"../ask.js";const POPULAR_PROVIDERS=[`anthropic`,`openai`,`google`],DEFAULT_MODEL_ID=`anthropic/claude-
|
|
1
|
+
import{select}from"../ask.js";const POPULAR_PROVIDERS=[`anthropic`,`openai`,`google`],DEFAULT_MODEL_ID=`anthropic/claude-sonnet-4.6`;function modelOption(e,t,n){return{id:e,label:t,value:e,hint:n,featured:!0}}const FEATURED_MODEL_IDS=[DEFAULT_MODEL_ID,`anthropic/claude-opus-4.8`,`openai/gpt-5.5`,`google/gemini-3.5`],FALLBACK_MODELS=[modelOption(DEFAULT_MODEL_ID,`Claude Sonnet 4.6`,`Anthropic`),modelOption(`anthropic/claude-opus-4.8`,`Claude Opus 4.8`,`Anthropic`),modelOption(`openai/gpt-5.5`,`GPT-5.5`,`OpenAI`),modelOption(`google/gemini-3.5`,`Gemini 3.5`,`Google`)];function providerLabel(e){return e.length===0?``:e.charAt(0).toUpperCase()+e.slice(1)}function providerPriority(e){let n=POPULAR_PROVIDERS.indexOf(e);return n===-1?POPULAR_PROVIDERS.length:n}async function fetchGatewayCatalog(){let e=new AbortController,t=setTimeout(()=>e.abort(),5e3);try{return(await(await fetch(`https://ai-gateway.vercel.sh/v1/models`,{signal:e.signal})).json()).data}finally{clearTimeout(t)}}function featuredPriority(e){let t=FEATURED_MODEL_IDS.indexOf(e);return t===-1?FEATURED_MODEL_IDS.length:t}async function buildModelOptions(e){try{let t=(await e()).filter(e=>e.type===`language`&&(e.tags??[]).includes(`web-search`)).map(e=>{let t=e.id.split(`/`)[0]??``;return{value:e.id,label:e.name,hint:providerLabel(t),provider:t}}).sort((e,t)=>{let n=featuredPriority(e.value)-featuredPriority(t.value);if(n!==0)return n;let r=providerPriority(e.provider)-providerPriority(t.provider);if(r!==0)return r;let i=e.provider.localeCompare(t.provider);return i===0?e.label.localeCompare(t.label):i});return t.length===0?FALLBACK_MODELS:t.map(({value:e,label:t,hint:n})=>({id:e,label:t,value:e,hint:n,featured:FEATURED_MODEL_IDS.includes(e)||void 0}))}catch{return FALLBACK_MODELS}}function selectModel(t){let r=t.deps??{fetchModels:fetchGatewayCatalog};function presetFor(e){if(e.modelWiring===`self`)return DEFAULT_MODEL_ID;if(t.presetModel!==void 0&&t.presetModel.length>0)return t.presetModel}return{id:`select-model`,async gather({state:i}){let a=presetFor(i);if(a!==void 0)return a;let o=await buildModelOptions(r.fetchModels),s=t.defaultModel!==void 0&&o.some(e=>e.value===t.defaultModel)?t.defaultModel:o.some(e=>e.value===`anthropic/claude-sonnet-4.6`)?DEFAULT_MODEL_ID:o[0]?.value;return t.asker.ask(select({key:`model`,message:`Which model should your agent use?`,options:o,recommended:s,required:!0,search:!0,placeholder:`type to filter (e.g. 'claude', 'gpt', 'gemini')`}))},async perform({input:e}){return e},apply(e,t){return{...e,modelId:t}}}}export{DEFAULT_MODEL_ID,selectModel};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Asker } from "../ask.js";
|
|
2
|
+
import type { SetupMode, SetupState } from "../state.js";
|
|
3
|
+
import type { SetupBox } from "../step.js";
|
|
4
|
+
export interface SelectSetupModeOptions {
|
|
5
|
+
/** Resolves the mode question; the composed stack decides how. */
|
|
6
|
+
asker: Asker;
|
|
7
|
+
/**
|
|
8
|
+
* Skip the mode question and use this value. Stays a factory option (not a
|
|
9
|
+
* `withAnswers` rung) so it short-circuits before any ask, which is what
|
|
10
|
+
* lets a headless `--one-shot` run resolve without a terminal.
|
|
11
|
+
*/
|
|
12
|
+
presetMode?: SetupMode;
|
|
13
|
+
/**
|
|
14
|
+
* Model baked into a one-shot scaffold instead of {@link DEFAULT_MODEL_ID}.
|
|
15
|
+
* Threaded from the same `--model` preset the model box consumes, so the
|
|
16
|
+
* flag keeps working when the model box is skipped.
|
|
17
|
+
*/
|
|
18
|
+
presetModel?: string;
|
|
19
|
+
}
|
|
20
|
+
/** The mode plus the model a one-shot run pins, since the model box is skipped. */
|
|
21
|
+
export interface SelectSetupModePayload {
|
|
22
|
+
mode: SetupMode;
|
|
23
|
+
modelId?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* THE SETUP-MODE BOX: decide whether the run is the complete onboarding flow
|
|
27
|
+
* or a one-shot scaffold. One-shot pins the default model here because every
|
|
28
|
+
* later interview box (including the model picker) is gated off. The question
|
|
29
|
+
* is skippable, so a headless stack without a preset resolves to "complete"
|
|
30
|
+
* and current headless behavior is unchanged.
|
|
31
|
+
*/
|
|
32
|
+
export declare function selectSetupMode(options: SelectSetupModeOptions): SetupBox<SetupState, SetupMode, SelectSetupModePayload>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{SkippedSignal,select}from"../ask.js";import"./select-model.js";function selectSetupMode(n){return{id:`select-setup-mode`,async gather(){if(n.presetMode!==void 0)return n.presetMode;try{return await n.asker.ask(select({key:`setup-mode`,message:`How much should we set up now?`,options:[{id:`complete`,label:`Complete setup`,value:`complete`,hint:`Vercel project, model, channels, deploy`},{id:`one-shot`,label:`One-shot`,value:`one-shot`,hint:`just write the project files`}],recommended:`complete`}))}catch(t){if(t instanceof SkippedSignal)return`complete`;throw t}},async perform({input:e}){return e===`one-shot`?{mode:e,modelId:n.presetModel??`anthropic/claude-sonnet-4.6`}:{mode:e}},apply(e,t){return{...e,setupMode:t.mode,modelId:t.modelId??e.modelId}}}}export{selectSetupMode};
|
|
@@ -4,3 +4,4 @@ export { createPromptCommandOutput, type PromptCommandLog } from "./command-outp
|
|
|
4
4
|
export { runSelectComponent, SelectComponent, type SelectGuard } from "./select-component.js";
|
|
5
5
|
export { CORNER, RAIL, bulletFor, cornerFor, formatPromptCancellation, formatPromptHeader, formatPromptOpener, formatPromptOutro, formatPromptSubmission, formatRailLine, railFor, renderMultiselectPrompt, renderOptionRow, renderSearchableSelect, renderSelectPrompt, type PromptColors, type PromptOption, type PromptState, type PromptValue, } from "./prompt-ui.js";
|
|
6
6
|
export { createRailLog, type RailLog, type RailLogOptions, type RailSpinner } from "./rail-log.js";
|
|
7
|
+
export { whimsyFor, WHIMSY_POOLS, type WhimsyTask } from "./whimsy.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createPromptCommandOutput}from"./command-output.js";import{CORNER,RAIL,bulletFor,cornerFor,formatPromptCancellation,formatPromptHeader,formatPromptOpener,formatPromptOutro,formatPromptSubmission,formatRailLine,railFor,renderMultiselectPrompt,renderOptionRow,renderSearchableSelect,renderSelectPrompt}from"./prompt-ui.js";import{SelectComponent,runSelectComponent}from"./select-component.js";import{createRailLog}from"./rail-log.js";export{CORNER,RAIL,SelectComponent,bulletFor,cornerFor,createPromptCommandOutput,createRailLog,formatPromptCancellation,formatPromptHeader,formatPromptOpener,formatPromptOutro,formatPromptSubmission,formatRailLine,railFor,renderMultiselectPrompt,renderOptionRow,renderSearchableSelect,renderSelectPrompt,runSelectComponent};
|
|
1
|
+
import{createPromptCommandOutput}from"./command-output.js";import{CORNER,RAIL,bulletFor,cornerFor,formatPromptCancellation,formatPromptHeader,formatPromptOpener,formatPromptOutro,formatPromptSubmission,formatRailLine,railFor,renderMultiselectPrompt,renderOptionRow,renderSearchableSelect,renderSelectPrompt}from"./prompt-ui.js";import{SelectComponent,runSelectComponent}from"./select-component.js";import{createRailLog}from"./rail-log.js";import{WHIMSY_POOLS,whimsyFor}from"./whimsy.js";export{CORNER,RAIL,SelectComponent,WHIMSY_POOLS,bulletFor,cornerFor,createPromptCommandOutput,createRailLog,formatPromptCancellation,formatPromptHeader,formatPromptOpener,formatPromptOutro,formatPromptSubmission,formatRailLine,railFor,renderMultiselectPrompt,renderOptionRow,renderSearchableSelect,renderSelectPrompt,runSelectComponent,whimsyFor};
|