feat(eve): refresh TUI startup card (#2562)

Signed-off-by: Colton Padden <colton.padden@vercel.com>
This commit is contained in:
Colton Padden
2026-08-26 19:06:11 -04:00
committed by GitHub
parent 47b3e4809e
commit 9c0a1380d7
8 changed files with 352 additions and 92 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"eve": patch
---
Replace the eve TUI's separate banner and text header with a startup card showing the installed version, active model, and the instructions, tools, skills, subagents, and schedules loaded by the agent.
+103 -19
View File
@@ -1,6 +1,13 @@
import { describe, expect, it } from "vitest";
import type { AgentInfoResult, AgentInfoToolEntry } from "#client/index.js";
import type {
AgentInfoInstructionsEntry,
AgentInfoRemoteAgentEntry,
AgentInfoResult,
AgentInfoScheduleEntry,
AgentInfoSkillEntry,
AgentInfoToolEntry,
} from "#client/index.js";
import { stripAnsi } from "#cli/ui/terminal-text.js";
import { createTestAgentInfoResult } from "#internal/testing/agent-info-fixture.js";
@@ -53,43 +60,120 @@ const INFO: AgentInfoResult = {
describe("buildAgentHeader", () => {
const theme = createTheme({ color: false, unicode: false });
it("renders the brand line with the agent name", () => {
const lines = buildAgentHeader({ name: "agent-subagents", info: INFO, theme, width: 120 });
it("renders the startup card", () => {
const colorTheme = createTheme({ color: true, unicode: true });
const info: AgentInfoResult = {
...INFO,
agent: {
...INFO.agent,
model: {
id: "zai/glm-5.2",
routing: { kind: "gateway", target: "zai" },
endpoint: { kind: "gateway", connected: true, credential: "api-key" },
},
},
};
const lines = buildAgentHeader({ info, theme: colorTheme, width: 120 });
const plain = lines.map(stripAnsi);
expect(lines).toEqual([" eve agent-subagents"]);
const card = plain.join("\n");
const titleIndex = plain.findIndex((line) => line.includes("Weather Agent"));
const logoIndex = plain.findIndex((line) => line.includes("⣿⣿⣿⣿⣿⣿⣿⣿⣿"));
const modelIndex = plain.findIndex((line) => line.includes("model"));
expect(plain[0]).toBe(`${"─".repeat(66)}`);
expect(plain[titleIndex]).toMatch(/^ eve \(v\d+\.\d+\.\d+\) +Weather Agent $/u);
expect(titleIndex).toBeLessThan(logoIndex);
expect(logoIndex).toBeLessThan(modelIndex);
expect(card).toContain("model zai/glm-5.2 via ai-gateway(api-key)");
expect(card).toContain("instructions none");
expect(card).toContain("agent get_weather");
expect(card).toContain("eve bash");
expect(card).toContain("skills none");
expect(card).toContain("subagents none");
expect(card).toContain("schedules none");
expect(plain.at(-2)).toContain("schedules none");
expect(lines[0]).toBe(colorTheme.colors.dim(plain[0]!));
expect(lines[logoIndex]).toContain(colorTheme.colors.cyan("⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⠏⣿⣿⣿⣿⣿⣿⣿⣿⣿"));
expect(lines[modelIndex]).toContain(colorTheme.colors.dim("via "));
});
it("renders just the brand line when info is unavailable", () => {
expect(buildAgentHeader({ name: "weather-agent", theme, width: 120 })).toEqual([
" eve weather-agent",
]);
});
it("bounds every collection for large agents", () => {
const source = (name: string) => ({
logicalPath: `${name}.ts`,
owner: { kind: "application" as const },
sourceId: `${name}.ts`,
sourceKind: "module" as const,
});
const instructions: AgentInfoInstructionsEntry[] = Array.from({ length: 12 }, (_, index) => ({
...source(`instructions-${index}`),
content: "Instructions.",
name: `instructions-${index}`,
role: "system",
}));
const skills: AgentInfoSkillEntry[] = Array.from({ length: 12 }, (_, index) => ({
...source(`skill-${index}`),
description: "Skill.",
markdown: "# Skill",
name: `skill-${index}`,
}));
const schedules: AgentInfoScheduleEntry[] = Array.from({ length: 12 }, (_, index) => ({
...source(`schedule-${index}`),
cron: "0 0 * * *",
hasRun: true,
name: `schedule-${index}`,
}));
const remoteAgents: AgentInfoRemoteAgentEntry[] = Array.from({ length: 12 }, (_, index) => ({
...source(`subagent-${index}`),
description: "Subagent.",
name: `subagent-${index}`,
nodeId: `remote-${index}`,
parentNodeId: "__root__",
}));
const extensionTools = Array.from({ length: 6 }, (_, index): AgentInfoToolEntry => ({
...AUTHORED_TOOL,
logicalPath: `extension-${index}/tool.ts`,
name: `extension_tool_${index}`,
owner: {
kind: "extension",
namespace: `extension-${index}`,
packageName: `@example/extension-${index}`,
},
sourceId: `extension-${index}/tool.ts`,
}));
const info: AgentInfoResult = {
...INFO,
instructions: { dynamic: [], static: instructions },
remoteAgents: { entries: remoteAgents, total: remoteAgents.length },
schedules,
skills: { dynamic: [], static: skills },
tools: { dynamic: [], static: [AUTHORED_TOOL, ...extensionTools, FRAMEWORK_TOOL] },
};
it("renders the tip line for local sessions only", () => {
const tip = AGENT_HEADER_TIPS[0]!;
const local = buildAgentHeader({ name: "weather-agent", info: INFO, theme, width: 120, tip });
expect(local).toEqual([" eve weather-agent", ` ${tip}`]);
const card = buildAgentHeader({ info, theme, width: 120 }).join("\n");
const remote = buildAgentHeader({ name: "weather-agent", info: INFO, theme, width: 120 });
expect(remote.join("\n")).not.toContain("/channels");
for (const label of ["instructions", "skills", "subagents", "schedules"]) {
expect(card.match(new RegExp(`${label}.*\\+\\d+ more`, "u"))).not.toBeNull();
}
expect(card).toContain(" +4 groups");
expect(card).not.toContain(" eve bash");
});
it("renders the /add tip with a blue command", () => {
const colorTheme = createTheme({ color: true, unicode: false });
const tip = AGENT_HEADER_TIPS.find((candidate) => candidate.includes("/add"));
expect(tip).toBe("Use /add to install integrations from the registry.");
expect(tip).toBe("Use the /add command to install an integration.");
if (tip === undefined) return;
const line = buildAgentHeader({
name: "weather-agent",
info: INFO,
theme: colorTheme,
width: 120,
tip,
}).at(-1);
expect(stripAnsi(line ?? "")).toBe(` ${tip}`);
expect(stripAnsi(line ?? "")).toBe(` Tip: ${tip}`);
expect(line).toContain(colorTheme.colors.blue("/add"));
});
@@ -98,7 +182,7 @@ describe("buildAgentHeader", () => {
...INFO,
diagnostics: { discoveryErrors: 1, discoveryWarnings: 2 },
};
const lines = buildAgentHeader({ name: "weather-agent", info, theme, width: 120 });
const lines = buildAgentHeader({ info, theme, width: 120 });
expect(lines.some((line) => line.includes("1 error"))).toBe(true);
expect(lines.some((line) => line.includes("2 warnings"))).toBe(true);
+203 -30
View File
@@ -1,25 +1,20 @@
/**
* Builds the startup header the dev TUI commits to scrollback before the
* first prompt: one `eve <agent name>` brand line, a discovery-diagnostics
* line when the compiler reported problems, and a rotating tip for local
* sessions. The resolved model is not repeated here — it lives on the
* persistent status line at the bottom.
*/
/** Builds the startup card the dev TUI commits before the first prompt. */
import type { AgentInfoResult } from "#client/index.js";
import { resolveInstalledPackageInfo } from "#internal/application/package.js";
import { clipVisible, visibleLength } from "#cli/ui/terminal-text.js";
import { isPromptControlCommand } from "./prompt-commands.js";
import type { Theme } from "./theme.js";
import { truncate } from "./tool-format.js";
export interface AgentHeaderInput {
/** Resolved display name (e.g. "weather-agent"). */
name: string;
/** Resolved display name used when agent inspection is unavailable. */
name?: string;
/** Agent inspection payload, or `undefined` when it could not be fetched. */
info?: AgentInfoResult;
theme: Theme;
/** Available terminal width. */
width: number;
/** Message-of-the-day line rendered under the brand line, when present. */
/** Message-of-the-day line rendered below the startup card, when present. */
tip?: string;
}
@@ -28,11 +23,21 @@ export interface AgentHeaderInput {
* slash commands, so callers only attach a tip to local `eve dev` sessions.
*/
export const AGENT_HEADER_TIPS: readonly string[] = [
"Use /add to install integrations from the registry.",
"Use /deploy to see your agent go live.",
"Type /help to see every command.",
"Use the /add command to install an integration.",
"Use the /deploy command to deploy your agent.",
"Use the /help command to see every command.",
];
const MAX_TOOL_GROUPS = 4;
const EVE_LOGO = [
"⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⠏⣿⣿⣿⣿⣿⣿⣿⣿⣿",
" ⠇⣿⠏",
"⣿⣿⣿⣿⣿⣿⠇ ⠇⣿⠏ ⠇⣿⣿⣿⣿⣿",
" ⠃⣿⠏",
"⣿⣿⣿⣿⣿⣿⠏ ⠃⣿⠏ ⠇⣿⣿⣿⣿⣿⣿⣿",
] as const;
/** Picks one tip; `random` is a test seam over Math.random. */
export function pickAgentHeaderTip(random: () => number = Math.random): string {
const index = Math.min(
@@ -42,17 +47,81 @@ export function pickAgentHeaderTip(random: () => number = Math.random): string {
return AGENT_HEADER_TIPS[index]!;
}
/**
* Returns the styled rows of the startup header (no trailing blank line is
* added by callers other than the one separating it from the transcript).
*/
/** Returns the styled rows of the startup card and optional tip. */
export function buildAgentHeader(input: AgentHeaderInput): string[] {
const { theme, info, name, width } = input;
const { theme, info, width } = input;
const c = theme.colors;
const version = resolveInstalledPackageInfo().version;
// Leave the terminal's final column untouched so terminals that wrap on a
// write there do not add an untracked row beneath the live region.
const cardWidth = Math.min(68, Math.max(0, width - 1));
const lines: string[] = [];
const brand = c.bold("eve");
lines.push(` ${brand} ${c.dim(truncate(name, Math.max(8, width - 8)))}`);
const brand = `${c.dim("☰")}${c.bold("eve")} ${c.dim(`(v${version})`)}`;
if (cardWidth < 4) return [clipVisible(brand, width)];
const horizontal = theme.unicode ? "─" : "-";
const vertical = theme.unicode ? "│" : "|";
const topLeft = theme.unicode ? "╭" : "+";
const topRight = theme.unicode ? "╮" : "+";
const bottomLeft = theme.unicode ? "╰" : "+";
const bottomRight = theme.unicode ? "╯" : "+";
const innerWidth = cardWidth - 2;
const border = horizontal.repeat(innerWidth);
const row = (text = "", ambiguousWidth = 0): string => {
const available = Math.max(0, innerWidth - 2);
const body = clipVisible(text, available);
const padding = Math.max(0, available - visibleLength(body) - ambiguousWidth);
return `${c.dim(vertical)} ${body}${" ".repeat(padding)} ${c.dim(vertical)}`;
};
const agentName = info?.agent.name ?? input.name;
const title =
agentName === undefined ? brand : spreadRow(brand, c.bold(agentName), innerWidth - 2, 1);
const lines = [c.dim(`${topLeft}${border}${topRight}`)];
// U+2630 is East Asian Ambiguous and renders as two cells in some
// terminals, so reserve its second cell explicitly inside the card.
lines.push(row(title, 1), row());
const logoWidth = Math.max(...EVE_LOGO.map((line) => visibleLength(line)));
if (theme.unicode && innerWidth - 2 >= logoWidth) {
lines.push(
...EVE_LOGO.map((line) => row(centerLogoLine(line, logoWidth, innerWidth - 2, theme))),
row(),
);
}
const detail = (label: string, value: string): string =>
row(`${c.dim(label.padEnd(14))}${value}`);
const model = formatHeaderModel(info?.agent.model, theme);
if (model !== undefined) lines.push(detail("model", model));
if (info !== undefined) {
lines.push(detail("instructions", formatInstructions(info, innerWidth - 18)));
lines.push(row());
const toolGroups = groupTools(info);
if (toolGroups.length === 0) {
lines.push(detail("tools", "none"));
} else {
lines.push(detail("tools", ""));
for (const group of toolGroups.slice(0, MAX_TOOL_GROUPS)) {
lines.push(detail(` ${group.label}`, fitNames(group.names, innerWidth - 18)));
}
const omittedGroups = toolGroups.length - MAX_TOOL_GROUPS;
if (omittedGroups > 0) {
lines.push(detail(` +${omittedGroups} ${pluralize(omittedGroups, "group")}`, ""));
}
}
lines.push(detail("skills", fitNames(skillNames(info), innerWidth - 18)));
lines.push(detail("subagents", fitNames(subagentNames(info), innerWidth - 18)));
lines.push(
detail(
"schedules",
fitNames(
info.schedules.map((schedule) => schedule.name),
innerWidth - 18,
),
),
);
}
lines.push(c.dim(`${bottomLeft}${border}${bottomRight}`));
if (info && (info.diagnostics.discoveryErrors > 0 || info.diagnostics.discoveryWarnings > 0)) {
const parts: string[] = [];
@@ -72,25 +141,129 @@ export function buildAgentHeader(input: AgentHeaderInput): string[] {
),
);
}
lines.push(` ${c.dim(theme.glyph.warning)} ${parts.join(c.dim(" · "))}`);
lines.push("", ` ${c.dim(theme.glyph.warning)} ${parts.join(c.dim(" · "))}`);
}
if (input.tip !== undefined) {
lines.push(` ${renderTip(input.tip, Math.max(8, width - 2), theme)}`);
lines.push("", ` ${c.bold("Tip:")} ${renderTip(input.tip, Math.max(8, width - 7), theme)}`);
}
return lines;
}
function centerLogoLine(text: string, logoWidth: number, width: number, theme: Theme): string {
const padding = Math.max(0, Math.floor((width - logoWidth) / 2));
return `${" ".repeat(padding)}${theme.colors.cyan(text)}`;
}
function spreadRow(left: string, right: string, width: number, ambiguousWidth: number): string {
const available = Math.max(1, width - ambiguousWidth);
const clippedLeft = clipVisible(left, Math.max(1, available - visibleLength(right) - 1));
const clippedRight = clipVisible(right, Math.max(1, available - visibleLength(clippedLeft) - 1));
const gap = Math.max(1, available - visibleLength(clippedLeft) - visibleLength(clippedRight));
return `${clippedLeft}${" ".repeat(gap)}${clippedRight}`;
}
function groupTools(info: AgentInfoResult): Array<{ label: string; names: string[] }> {
const groups = new Map<string, string[]>();
for (const tool of info.tools.static) {
const label =
tool.owner.kind === "application"
? "agent"
: tool.owner.kind === "framework"
? "eve"
: tool.owner.namespace;
const names = groups.get(label) ?? [];
names.push(tool.name);
groups.set(label, names);
}
if (info.tools.dynamic.length > 0) {
groups.set(
"dynamic",
info.tools.dynamic.map((resolver) => resolver.slug),
);
}
const priority = (label: string): number => {
if (label === "agent") return 0;
if (label === "dynamic") return 2;
if (label === "eve") return 3;
return 1;
};
return [...groups]
.map(([label, names]) => ({ label, names: names.sort() }))
.sort((a, b) => priority(a.label) - priority(b.label) || a.label.localeCompare(b.label));
}
function skillNames(info: AgentInfoResult): string[] {
return [
...info.skills.static.map((skill) => skill.name),
...info.skills.dynamic.map((resolver) => `${resolver.slug} (dynamic)`),
].sort();
}
function subagentNames(info: AgentInfoResult): string[] {
return [...info.subagents.local, ...info.remoteAgents.entries]
.map((subagent) => subagent.name)
.sort();
}
function formatInstructions(info: AgentInfoResult, width: number): string {
const names = [
...info.instructions.static.map(
(instructions) => `${instructions.logicalPath} (${instructions.role})`,
),
...info.instructions.dynamic.map((resolver) => `${resolver.slug} (dynamic)`),
];
return fitNames(names, width);
}
function fitNames(names: readonly string[], width: number): string {
if (names.length === 0) return "none";
for (let count = names.length; count > 0; count -= 1) {
const hidden = names.length - count;
const value = `${names.slice(0, count).join(", ")}${hidden > 0 ? `, +${hidden} more` : ""}`;
if (visibleLength(value) <= width) return value;
}
return clipVisible(names[0]!, width);
}
function formatHeaderModel(
model: AgentInfoResult["agent"]["model"] | undefined,
theme: Theme,
): string | undefined {
if (model?.id === undefined) return undefined;
const endpoint = model.endpoint;
if (endpoint === undefined) return model.id;
const via = theme.colors.dim("via ");
switch (endpoint.kind) {
case "external":
return `${model.id} ${via}${endpoint.provider}`;
case "chatgpt":
return `${model.id} ${via}chatgpt-sub`;
case "gateway":
return endpoint.connected
? `${model.id} ${via}ai-gateway(${endpoint.credential})`
: `${model.id} ${via}ai-gateway(not connected)`;
}
}
function renderTip(tip: string, width: number, theme: Theme): string {
return truncate(tip, width)
.split(/(\/[a-z:-]+)/u)
.map((part) =>
isPromptControlCommand(part) ? theme.colors.blue(part) : theme.colors.dim(part),
)
.join("");
return clipVisible(
tip
.split(/(\/[a-z:-]+)/u)
.map((part) =>
isPromptControlCommand(part) ? theme.colors.blue(part) : theme.colors.dim(part),
)
.join(""),
width,
);
}
function plural(count: number): string {
return count === 1 ? "" : "s";
}
function pluralize(count: number, noun: string): string {
return `${noun}${plural(count)}`;
}
+4 -5
View File
@@ -226,16 +226,15 @@ export type AgentTUIAgentHeader = {
name: string;
serverUrl: string;
info?: AgentInfoResult;
/** Message-of-the-day line shown under the brand line (local sessions only). */
/** Message-of-the-day line shown below the startup card (local sessions only). */
tip?: string;
};
export type AgentTUIRenderer = {
/**
* Commits a startup header describing the connected agent (brand mark,
* model, instructions, tools, skills, subagents) to the transcript before
* the first prompt, and refreshes it after local dev artifact changes.
* Optional — renderers without a header simply skip it.
* Commits the startup card to the transcript before the first prompt and
* refreshes it after local dev artifact changes. Optional — renderers
* without a header simply skip it.
*/
renderAgentHeader?(header: AgentTUIAgentHeader): void;
/**
@@ -171,24 +171,24 @@ describe("TerminalRenderer (inline scrollback)", () => {
expect(process.listeners("exit")).toEqual(before);
});
it("renders the brand line with the agent name and a tip", () => {
it("commits the startup card before the prompt", () => {
const { screen, renderer } = makeRenderer();
renderer.renderAgentHeader({
name: "Weather Agent",
serverUrl: "http://localhost:3000",
info: agentInfoWithModel("gpt-5"),
tip: "Use eve add to install integrations from the registry.",
info: agentInfoWithModel("gpt-5", {
kind: "gateway",
connected: true,
credential: "api-key",
}),
tip: "Use the /deploy command to deploy your agent.",
});
renderer.shutdown();
const snapshot = screen.snapshot();
expect(snapshot).toContain("eve Weather Agent");
expect(snapshot).toContain("Use eve add to install integrations from the registry.");
// The model lives on the status line, not the header; the old config
// rows and key hints are gone.
expect(snapshot).not.toContain("gpt-5");
expect(snapshot).toMatch(/eve \(v\d+\.\d+\.\d+\).*Weather Agent/u);
expect(snapshot).toContain("Tip: Use the /deploy command to deploy your agent.");
expect(snapshot).not.toContain("http://localhost:3000");
expect(snapshot).not.toContain("Type to chat");
});
it("refreshes the committed agent header with the latest model", async () => {
@@ -215,7 +215,9 @@ describe("TerminalRenderer (inline scrollback)", () => {
const snapshot = screen.snapshot();
expect(snapshot).toContain("new-model");
expect(snapshot).not.toContain("old-model");
// Header refreshes append to scrollback, preserving the prior startup card.
expect(snapshot).toContain("old-model");
expect(snapshot.lastIndexOf("new-model")).toBeGreaterThan(snapshot.lastIndexOf("old-model"));
expect(snapshot).toContain("hello");
expect(snapshot).toContain("still here");
renderer.shutdown();
@@ -3586,25 +3588,6 @@ describe("TerminalRenderer (inline scrollback)", () => {
expect(screen.snapshot()).toContain("Session ended — started a new session.");
});
it("refreshing the agent header preserves committed transcript and scrollback", () => {
const { screen, renderer } = makeRenderer();
renderer.renderAgentHeader({ name: "Weather Agent", serverUrl: "http://localhost:3000" });
renderer.renderNotice("previous transcript");
// Dev HMR refresh: a fresh header is committed beneath the transcript —
// nothing is cleared or replayed.
renderer.renderAgentHeader({ name: "Weather Agent v2", serverUrl: "http://localhost:3000" });
renderer.shutdown();
const snapshot = screen.snapshot();
expect(snapshot).toContain("previous transcript");
expect(snapshot).toContain("Weather Agent v2");
// The refreshed header lands after the prior transcript, not on a wiped screen.
expect(snapshot.indexOf("Weather Agent v2")).toBeGreaterThan(
snapshot.indexOf("previous transcript"),
);
});
it("does not repeat the banner when a source reload re-sends an unchanged header", () => {
const { screen, renderer } = makeRenderer();
renderer.renderAgentHeader({ name: "Weather Agent", serverUrl: "http://localhost:3000" });
@@ -3615,7 +3598,7 @@ describe("TerminalRenderer (inline scrollback)", () => {
renderer.renderAgentHeader({ name: "Weather Agent", serverUrl: "http://localhost:3000" });
renderer.shutdown();
expect(countOccurrences(screen.snapshot(), "Weather Agent")).toBe(1);
expect(countOccurrences(screen.snapshot(), "☰eve (v")).toBe(1);
});
it("reset clears committed transcript rows", () => {
@@ -295,7 +295,7 @@ export type AgentHeaderOptions = {
name: string;
serverUrl: string;
info?: AgentInfoResult;
/** Message-of-the-day line under the brand line (local sessions only). */
/** Message-of-the-day line below the startup card (local sessions only). */
tip?: string;
};
@@ -629,8 +629,8 @@ export class TerminalRenderer implements AgentTUIRenderer {
/**
* Commits the startup agent header (brand mark + resolved configuration) to
* scrollback before the first prompt. Later calls (dev HMR refreshing fields
* such as the agent name) commit a fresh header beneath the existing
* transcript only when the rendered header actually changed every source
* such as the model) commit a fresh header beneath the existing transcript
* only when the rendered header actually changed every source
* reload re-sends it, and an identical banner repeated per reload is noise.
* Committed scrollback is never cleared or replayed.
*/
+19
View File
@@ -828,6 +828,25 @@ describe("eve acp", () => {
});
describe("eve dev boot progress", () => {
it("leaves the interactive startup banner to the TUI", async () => {
const logger = { error: vi.fn(), log: vi.fn() };
const startHost = vi.fn(() => ({
start: async () => ({
kind: "started" as const,
appRoot: "/canonical/app",
url: "http://127.0.0.1:2000",
}),
close: async () => {},
}));
await withInteractiveTerminal(() =>
runCli(["dev"], logger, { runDevelopmentTui: vi.fn(async () => {}), startHost }),
);
expect(logger.log).not.toHaveBeenCalledWith(expect.stringContaining("☰eve"));
expect(logger.log).not.toHaveBeenCalledWith("");
});
it("passes one reporter through local startup and clears the row on failure", async () => {
const writes: string[] = [];
const close = vi.fn(async () => {});
+2 -5
View File
@@ -167,9 +167,7 @@ function createCliProgram(
.exitOverride()
.hook("preAction", (_program, actionCommand) => {
const { json } = actionCommand.opts<{ json?: boolean }>();
if (["info", "dev", "init"].includes(actionCommand.name()) && !json) {
logger.log(eveCliBanner());
}
if (["info", "init"].includes(actionCommand.name()) && !json) logger.log(eveCliBanner());
})
.configureOutput({
writeErr: (message) => {
@@ -387,6 +385,7 @@ function createCliProgram(
const remoteServerUrl = remoteTarget?.serverUrl;
const interactive = hasInteractiveTerminal();
const mode = resolveDevUiMode({ options, interactive });
if (mode === "headless") logger.log(eveCliBanner());
if (options.input !== undefined && mode === "headless") {
throw new InvalidArgumentError("--input requires the interactive UI.");
}
@@ -465,7 +464,6 @@ function createCliProgram(
logger.log(
`${existingLocalDevelopmentServer ? "local" : "remote"} mode targeting ${theme.info(new URL(remoteServerUrl).host)}`,
);
if (mode === "headless") {
logger.log(
renderCliTaggedLine(theme, {
@@ -487,7 +485,6 @@ function createCliProgram(
return;
}
if (mode === "tui") logger.log("");
const buildProgress = mode === "tui" ? startCliLiveRow(logger) : undefined;
const onBootProgress = createDevBootProgressReporter(buildProgress);
buildProgress?.update("Building your agent");