From a43c8d888ff660063b7adf3534ba3b62077c1bd8 Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:48:20 +0100 Subject: [PATCH] Show the Cursor icon for terminal profiles that run cursor-agent (#5379) * Show the Cursor icon for terminal profiles that run cursor-agent The Cursor CLI binary is cursor-agent, so a profile created in settings with that command matched no known icon and fell back to the generic terminal icon. Map cursor-agent to the cursor icon while keeping command matching exact. Closes #4702 * Verify Cursor terminal profile icons in real browser --------- Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> Co-authored-by: Mohamed Boudra --- packages/app/e2e/browser/launcher-tab.spec.ts | 46 +++++++++++++++++++ .../protocol/src/terminal-profiles.test.ts | 24 +++++++++- packages/protocol/src/terminal-profiles.ts | 9 +++- 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/packages/app/e2e/browser/launcher-tab.spec.ts b/packages/app/e2e/browser/launcher-tab.spec.ts index 699f1fce40..9be6d79418 100644 --- a/packages/app/e2e/browser/launcher-tab.spec.ts +++ b/packages/app/e2e/browser/launcher-tab.spec.ts @@ -250,6 +250,52 @@ test.describe("Tab creation", () => { await expect(page).toHaveURL(/\/settings\/hosts\/[^/]+\/terminals$/); }); + test("Cursor CLI profile uses the Cursor icon in New tab and host settings", async ({ page }) => { + const guessed: TerminalProfile = { + id: "e2e-cursor-guessed", + name: "Cursor guessed", + command: "cursor-agent", + args: ["{{{prompt}}}"], + }; + const explicit: TerminalProfile = { + ...guessed, + id: "e2e-cursor-explicit", + name: "Cursor explicit", + icon: "cursor", + }; + const profileSeed = await seedTerminalProfiles([guessed, explicit]); + + try { + await gotoWorkspace(page, workspace.workspaceId); + await page.getByTestId("workspace-new-tab-button").filter({ visible: true }).click(); + + const menu = page.getByTestId("workspace-new-tab-menu").filter({ visible: true }); + const menuIconPath = (name: string) => + menu + .getByRole("menuitem", { name }) + .evaluate((element) => element.querySelector("svg path")?.getAttribute("d") ?? null); + const guessedMenuPath = await menuIconPath(guessed.name); + const explicitMenuPath = await menuIconPath(explicit.name); + + await menu.getByTestId("workspace-new-tab-menu-edit-terminal-profiles").click(); + await expect(page).toHaveURL(/\/settings\/hosts\/[^/]+\/terminals$/); + + const settingsIconPath = (id: string) => + page + .getByTestId(`terminal-profile-row-${id}`) + .evaluate((element) => element.querySelector("svg path")?.getAttribute("d") ?? null); + const guessedSettingsPath = await settingsIconPath(guessed.id); + const explicitSettingsPath = await settingsIconPath(explicit.id); + + expect(explicitMenuPath).not.toBeNull(); + expect(explicitSettingsPath).not.toBeNull(); + expect.soft(guessedMenuPath, "New tab Cursor profile icon").toBe(explicitMenuPath); + expect.soft(guessedSettingsPath, "Settings Cursor profile icon").toBe(explicitSettingsPath); + } finally { + await profileSeed.restore(); + } + }); + test("tab bar shows action buttons per pane", async ({ page }) => { await gotoWorkspace(page, workspace.workspaceId); await assertSingleNewTabButton(page); diff --git a/packages/protocol/src/terminal-profiles.test.ts b/packages/protocol/src/terminal-profiles.test.ts index e45f68b293..dece3821ed 100644 --- a/packages/protocol/src/terminal-profiles.test.ts +++ b/packages/protocol/src/terminal-profiles.test.ts @@ -196,11 +196,13 @@ describe("guessTerminalProfileIcon", () => { ["/usr/local/bin/gemini", "gemini"], ["agy", "agy"], ["/usr/local/bin/agy", "agy"], + ["cursor-agent", "cursor"], + ["/usr/local/bin/cursor-agent", "cursor"], ])("guesses %s -> %s", (command, expected) => { expect(guessTerminalProfileIcon(command)).toBe(expected); }); - it.each([["zsh"], ["bash"], ["fish"], [""], ["/usr/bin/foo"], ["cursor-agent"]])( + it.each([["zsh"], ["bash"], ["fish"], [""], ["/usr/bin/foo"], ["cursor-tool"]])( "returns undefined for unknown command %s", (command) => { expect(guessTerminalProfileIcon(command)).toBeUndefined(); @@ -219,6 +221,26 @@ describe("getTerminalProfileIcon", () => { expect(getTerminalProfileIcon(profile)).toBe("claude"); }); + it("shows the Cursor icon for a profile created in settings with the Cursor CLI", () => { + const profile = { + id: "profile_1", + name: "Cursor", + command: "cursor-agent", + args: [PROMPT_SENTINEL], + }; + expect(getTerminalProfileIcon(profile)).toBe("cursor"); + }); + + it("keeps an explicit icon over the Cursor CLI guess", () => { + const profile = { + id: "profile_1", + name: "Cursor", + command: "cursor-agent", + icon: "claude", + }; + expect(getTerminalProfileIcon(profile)).toBe("claude"); + }); + it("returns undefined when no icon is set and command is unknown", () => { const profile = { id: "1", name: "Foo", command: "zsh" }; expect(getTerminalProfileIcon(profile)).toBeUndefined(); diff --git a/packages/protocol/src/terminal-profiles.ts b/packages/protocol/src/terminal-profiles.ts index 4a811437a2..33d10fe4ed 100644 --- a/packages/protocol/src/terminal-profiles.ts +++ b/packages/protocol/src/terminal-profiles.ts @@ -105,7 +105,14 @@ export function resolveTerminalProfileLaunch( return { name: profile.name, ...substitutePrompt(profile, prompt) }; } -const WELL_KNOWN_COMMAND_ICONS = new Map(KNOWN_PROVIDER_ICON_NAMES.map((name) => [name, name])); +// CLIs whose binary is not named after their icon. Matching stays exact, so a +// command only gets an icon when its whole base name is listed. +const COMMAND_ICON_ALIASES: ReadonlyArray<[string, string]> = [["cursor-agent", "cursor"]]; + +const WELL_KNOWN_COMMAND_ICONS = new Map([ + ...KNOWN_PROVIDER_ICON_NAMES.map((name): [string, string] => [name, name]), + ...COMMAND_ICON_ALIASES, +]); function getCommandBaseName(command: string): string { const lastSlash = command.lastIndexOf("/");