Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/remove-connect-command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eve": patch
---

Remove the `/connect` dev TUI command. Add connections through `eve add` or `/add`; official Connect-backed connection items now configure and patch their Vercel connector during registry setup.
14 changes: 4 additions & 10 deletions docs/guides/dev-tui.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,17 @@ The conversation streams straight into your terminal's normal scrollback, so you

Each turn renders without boxes. A colored gutter glyph marks who is speaking, tool calls collapse to a one-line summary (`✓ get_weather city="SF" → 73°F`), and a subagent's work is indented beneath its `◆` header. When input is ready, the prompt stays bare until you type. A green circle-dot pulses while the agent is waiting to answer and disappears when reasoning or answer content begins.

A persistent line beneath the prompt or status shows the model — with its authored reasoning level and a `↯` marker when the Gateway priority tier is on (`xai/grok-4.5@xhigh ↯`) — the linked Vercel project, and a yellow `/deploy pending` marker once a channel added this session still needs `/deploy`. The Vercel segment stays hidden until the directory is linked. Local sessions lead with a gray `:port` badge. Remote sessions lead with a padded `↗ project (environment)` badge, or the host when Vercel cannot resolve the deployment. The badge is gray while checking or unavailable, yellow while authentication is required or failed, and blue when connected. Status segments separate with plain whitespace. Remote status lines omit AI Gateway endpoint state.
A persistent line beneath the prompt or status shows the model — with its authored reasoning level and a `↯` marker when the Gateway priority tier is on (`xai/grok-4.5@xhigh ↯`) — and the linked Vercel project. The Vercel segment stays hidden until the directory is linked. Local sessions lead with a gray `:port` badge. Remote sessions lead with a padded `↗ project (environment)` badge, or the host when Vercel cannot resolve the deployment. The badge is gray while checking or unavailable, yellow while authentication is required or failed, and blue when connected. Status segments separate with plain whitespace. Remote status lines omit AI Gateway endpoint state.

Errors render compactly with docs links highlighted. A code bug escaping your agent's own code shows its stack trace dim beneath the error headline. Dev-server rebuilds condense into one status row that updates in place (`tui/setup-panel.ts changed · rebuilding…`, then `· rebuilt`); only the latest rebuild shows, and paths shrink to their last two components.

## Slash commands

Each command echoes as an invocation line, asks through a bordered panel that takes the input area's place (one question at a time, separate from the chat transcript), and finishes with a one-line `⎿` result. Loading states stay on the ephemeral status line instead of piling into the transcript; model, channel, connection, and the Vercel CLI commands (`/vc:install`, `/vc:login`) use the same green square pulse as the build phase, while `/deploy` keeps a spinner. A connection setup waiting for browser action changes that pulse and the word "browser" to yellow. Setup menus render the selected option with a filled arrow and an inverse label padded by one space on each side. Text prompts use a blinking block cursor over the character at the caret. The selected label is blue normally and yellow for warning rows.
Each command echoes as an invocation line, asks through a bordered panel that takes the input area's place (one question at a time, separate from the chat transcript), and finishes with a one-line `⎿` result. Loading states stay on the ephemeral status line instead of piling into the transcript; model, registry, and the Vercel CLI commands (`/vc:install`, `/vc:login`) use the same green square pulse as the build phase, while `/deploy` keeps a spinner. A setup waiting for browser action changes that pulse and the word "browser" to yellow. Setup menus render the selected option with a filled arrow and an inverse label padded by one space on each side. Text prompts use a blinking block cursor over the character at the caret. The selected label is blue normally and yellow for warning rows.

| Command | Does |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `/model` | Opens a configure menu that loops until Done (or Esc). See [Configure the model and provider](#configure-the-model-and-provider). |
| `/add` | Browses and installs integrations from the registry. |
| `/connect` | Shows the Vercel Connect MCP catalog and configures the server you pick. See [Add a connection](#add-a-connection). |
| `/add` | Searches registry integrations or adds an item address entered directly. |
| `/deploy` | Ships the agent to Vercel production, linking the directory first when it is unlinked. |
| `/vc:install` | Installs the Vercel CLI. Available locally and on a remote session. |
Expand All @@ -46,7 +44,7 @@ Each command echoes as an invocation line, asks through a bordered panel that ta
| `/exit` | Quits the TUI. |
| `/help` | Lists the commands available for the current local or remote session. |

`/model`, `/connect`, `/add`, and `/deploy` manage the local agent or its linked project. They are available only when `eve dev` runs the server locally, not when connected to a remote server with `--url`.
`/model`, `/add`, and `/deploy` manage the local agent or its linked project. They are available only when `eve dev` runs the server locally, not when connected to a remote server with `--url`.

### Configure the model and provider

Expand All @@ -60,11 +58,7 @@ The provider row demands attention (a bold yellow "Configure model access" with

`/add` first organizes integrations by what they add: a way to chat, tools and data, capabilities, or observability. Pick a category to open its searchable catalog from the official eve registry and the registry namespaces configured in `package.json`, or browse everything. Each integration shows its source beside its name, such as `Vercel` or a configured registry namespace. Select a result to review its source, packages, environment variables, and target files before adding it, or type an official item path, configured namespace address, or HTTP(S) URL directly into the search bar. After an add attempt finishes, the panel closes instead of returning to the catalog.

### Add a connection

`/connect` shows a searchable list of MCP servers available through Vercel Connect. Already-authored connections remain checked. Logged-out users are directed to `/vc:login`. When the directory is not linked, selecting a server opens the same team and project flow used by `/model`, including creating a project or linking an existing one.

For a selected server, eve first tries to attach the provider's canonical connector. If that fails, choose an existing connector from a searchable list or create one with a specific name. The fallback stays scoped to the team selected by the linked project. A connector created by the current attempt is removed if attachment or connection-file patching fails. Successful setup writes `agent/connections/<name>.ts`, records the attached connector UID, installs the new dependency so the dev server can load it, then returns to the main prompt.
Connection registry items install their definition and then configure Vercel Connect through the same `/add` flow. If the directory is unlinked, setup first offers to create or link a project. Run `eve add connection/<name>` for the equivalent CLI flow.

## Keyboard shortcuts

Expand Down
1 change: 0 additions & 1 deletion packages/eve/src/cli/dev/tui/agent-header.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { describe, expect, it } from "vitest";
import type { AgentInfoResult, AgentInfoToolEntry } from "#client/index.js";

import { AGENT_HEADER_TIPS, buildAgentHeader, pickAgentHeaderTip } from "./agent-header.js";
import { stripAnsi } from "#cli/ui/terminal-text.js";
import { createTheme } from "./theme.js";

const FRAMEWORK_TOOL: AgentInfoToolEntry = {
Expand Down
1 change: 0 additions & 1 deletion packages/eve/src/cli/dev/tui/prompt-command-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ export function createPromptCommandHandler(
command: command.name,
appRoot: target.workspaceRoot,
renderer: flow,
disabledConnectionReasons: context.disabledConnectionReasons,
withExclusiveTerminal: context.withExclusiveTerminal,
};
if (context.initialModelStep !== undefined) {
Expand Down
2 changes: 0 additions & 2 deletions packages/eve/src/cli/dev/tui/prompt-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ describe("promptCommandsFor", () => {
it("exposes project commands only for local sessions", () => {
const names = promptCommandsFor("local").map((command) => command.name);
expect(names).toContain("model");
expect(names).toContain("connect");
expect(names).toContain("add");
expect(names).toContain("deploy");
expect(names).toContain("vc:install");
Expand All @@ -107,7 +106,6 @@ describe("promptCommandsFor", () => {
expect(names).toContain("vc:login");
expect(names).not.toContain("vc:auth");
expect(names).not.toContain("model");
expect(names).not.toContain("connect");
expect(names).not.toContain("add");
expect(names).not.toContain("deploy");
});
Expand Down
16 changes: 1 addition & 15 deletions packages/eve/src/cli/dev/tui/prompt-commands.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
export type PromptCommandExtensionName =
| "model"
| "connect"
| "add"
| "deploy"
| "vc:install"
| "vc:login";
export type PromptCommandExtensionName = "model" | "add" | "deploy" | "vc:install" | "vc:login";

type PromptCommandTarget = "local" | "remote";

Expand Down Expand Up @@ -97,14 +91,6 @@ const PROMPT_COMMAND_DEFINITIONS = [
build: (argument) => ({ type: "loglevel", argument }),
targets: ["local", "remote"],
},
{
name: "connect",
aliases: [],
description: "Add an MCP server through Vercel Connect",
takesArgument: false,
build: () => ({ type: "extension", name: "connect", argument: "" }),
targets: ["local"],
},
{
name: "add",
aliases: [],
Expand Down
92 changes: 0 additions & 92 deletions packages/eve/src/cli/dev/tui/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2293,98 +2293,6 @@ describe("EveTUIRunner Vercel status line", () => {
expect(info).toHaveBeenCalledTimes(2);
});

it("forces a runtime rebuild after /connect adds a connection", async () => {
const client = stubClient();
vi.spyOn(client, "info").mockResolvedValue(AGENT_INFO);
const requests: URL[] = [];
vi.stubGlobal(
"fetch",
vi.fn(async (input: Parameters<typeof fetch>[0]) => {
const url = new URL(
typeof input === "string" ? input : input instanceof URL ? input : input.url,
);
requests.push(url);
return Response.json({ revision: url.searchParams.get("force") === "1" ? "next" : "base" });
}),
);
const prompts: Array<string | undefined> = ["/connect", undefined];
const renderer = fakeRenderer({ readPrompt: vi.fn(async () => prompts.shift()) });
const connectOutcome = {
message: "Connections added: linear.",
effect: { kind: "connection-added" },
} satisfies PromptCommandOutcome;

const runner = new EveTUIRunner({
session: stubSession(),
client,
renderer,
serverUrl: "http://localhost:3000",
name: "Weather Agent",
promptCommandHandler: { handle: async () => connectOutcome },
});
await runner.run();

expect(
requests.some(
(url) =>
url.pathname === "/eve/v1/dev/runtime-artifacts/rebuild" &&
url.searchParams.get("force") === "1",
),
).toBe(true);
});

it("forces a runtime rebuild through the /connect command path", async () => {
const client = stubClient();
vi.spyOn(client, "info").mockResolvedValue(AGENT_INFO);
const requests: URL[] = [];
vi.stubGlobal(
"fetch",
vi.fn(async (input: Parameters<typeof fetch>[0]) => {
const url = new URL(
typeof input === "string" ? input : input instanceof URL ? input : input.url,
);
requests.push(url);
return Response.json({ revision: url.searchParams.get("force") === "1" ? "next" : "base" });
}),
);
const runConnectionsFlow = vi.fn(async () => ({
kind: "done" as const,
addedConnections: ["linear"],
}));
const prompts: Array<string | undefined> = ["/connect", undefined];
const renderer = fakeRenderer({
readPrompt: vi.fn(async () => prompts.shift()),
setupFlow: idleSetupFlow(),
});

const runner = new EveTUIRunner({
session: stubSession(),
client,
renderer,
serverUrl: "http://localhost:3000",
name: "Weather Agent",
appRoot: "/tmp/weather-agent",
promptCommandHandler: createPromptCommandHandler({
target: {
kind: "local",
serverUrl: "http://localhost:3000",
workspaceRoot: "/tmp/weather-agent",
},
flows: { runConnectionsFlow },
}),
});
await runner.run();

expect(runConnectionsFlow).toHaveBeenCalledTimes(1);
expect(
requests.some(
(url) =>
url.pathname === "/eve/v1/dev/runtime-artifacts/rebuild" &&
url.searchParams.get("force") === "1",
),
).toBe(true);
});

it("never pushes Vercel status for a remote --url session", async () => {
const setVercelStatus = vi.fn();
const renderer = fakeRenderer({ setVercelStatus });
Expand Down
18 changes: 1 addition & 17 deletions packages/eve/src/cli/dev/tui/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ export interface PromptCommandOutcome {
/** Promotes an outcome to a top-level status. */
tone?: "success" | "error";
/** Post-command work after setup settles. */
effect?: VercelStatusEffect | { kind: "connection-added" } | { kind: "model-access-changed" };
effect?: VercelStatusEffect | { kind: "model-access-changed" };
}

export interface PromptCommandHandler {
Expand Down Expand Up @@ -1351,13 +1351,6 @@ export class EveTUIRunner {
}

async #applyCommandEffect(effect: PromptCommandOutcome["effect"]): Promise<void> {
if (effect?.kind === "connection-added") {
this.#vercelStatus?.applyEffect({ kind: "refresh-identity" });
this.#authHintStale = true;
await this.#refreshConnectionRuntime();
await this.#refreshModelAccess();
return;
}
if (effect?.kind === "model-access-changed") {
this.#vercelStatus?.applyEffect({ kind: "refresh-identity" });
this.#authHintStale = true;
Expand All @@ -1371,15 +1364,6 @@ export class EveTUIRunner {
void this.#refreshSetupAttention(this.#agentInfo);
}

async #refreshConnectionRuntime(): Promise<void> {
const runtimeArtifacts = this.#runtimeArtifacts;
if (runtimeArtifacts === undefined) return;

await runtimeArtifacts.refreshAfterSourceChange({
onRuntimeArtifactsChanged: () => this.#handleRuntimeArtifactsChanged(),
});
}

async #executeExtensionCommand(
command: Extract<PromptCommand, { type: "extension" }>,
title: string,
Expand Down
45 changes: 1 addition & 44 deletions packages/eve/src/cli/dev/tui/setup-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@ function fakeFlows(overrides: Partial<TuiSetupFlows> = {}): TuiSetupFlows {
kind: "done",
modelMessage: "Model changed to openai/gpt-5.5. Live on your next prompt.",
})),
runConnectionsFlow: vi.fn<TuiSetupFlows["runConnectionsFlow"]>(async () => ({
kind: "done",
addedConnections: [],
})),
runRegistryFlow: vi.fn<TuiSetupFlows["runRegistryFlow"]>(async () => ({
kind: "done",
addedItems: [],
Expand All @@ -71,7 +67,7 @@ function fakeFlows(overrides: Partial<TuiSetupFlows> = {}): TuiSetupFlows {
}

function run(input: {
command: "vc:install" | "vc:login" | "model" | "connect" | "add" | "deploy";
command: "vc:install" | "vc:login" | "model" | "add" | "deploy";
flows: TuiSetupFlows;
renderer?: TuiSetupCommandRenderer;
initialModelStep?: "provider";
Expand Down Expand Up @@ -116,7 +112,6 @@ describe("runTuiSetupCommand", () => {
"vc:install": "pulse",
"vc:login": "pulse",
model: "pulse",
connect: "pulse",
add: "pulse",
deploy: "spinner",
});
Expand Down Expand Up @@ -331,44 +326,6 @@ describe("runTuiSetupCommand", () => {
});
});

it.each([
[
"configured",
{ kind: "done", addedConnections: ["linear", "notion"] },
"Connections added: linear, notion.",
{ kind: "connection-added" },
],
[
"empty",
{ kind: "done", addedConnections: [] },
"No connections added.",
{ kind: "model-access-changed" },
],
["cancelled", { kind: "cancelled" }, "/connect dismissed.", { kind: "model-access-changed" }],
[
"partially failed",
{ kind: "failed", addedConnections: ["linear"], message: "install failed" },
"Connection files changed, but /connect failed: install failed",
{ kind: "connection-added" },
],
[
"failed before a connection file was written",
{ kind: "failed", addedConnections: [], message: "connector setup failed" },
"/connect failed: connector setup failed",
{ kind: "model-access-changed" },
],
] as const)("reports %s connection flows", async (_case, result, message, effect) => {
const runConnectionsFlow = vi.fn(async () => result);
await expect(
run({ command: "connect", flows: fakeFlows({ runConnectionsFlow }) }),
).resolves.toEqual({
message,
preserveFlowDiagnostics: true,
effect,
});
expect(runConnectionsFlow).toHaveBeenCalledWith(expect.objectContaining({ appRoot: APP_ROOT }));
});

it.each([
[
"added",
Expand Down
Loading
Loading