Add configurable terminal profiles

This commit is contained in:
Mohamed Boudra
2026-06-12 11:47:00 +07:00
parent 75aa626df5
commit fa800ae78e
30 changed files with 1412 additions and 211 deletions
@@ -1,4 +1,10 @@
import { describe, expect, it } from "vitest";
import {
BUILTIN_PROVIDER_ICON_NAMES,
KNOWN_PROVIDER_ICON_NAMES,
TERMINAL_PROFILE_ICON_NAMES,
} from "@getpaseo/protocol/provider-icon-names";
import { ACP_PROVIDER_CATALOG } from "@/data/acp-provider-catalog";
import { resolveProviderIconName } from "./provider-icon-name";
describe("resolveProviderIconName", () => {
@@ -10,9 +16,34 @@ describe("resolveProviderIconName", () => {
it("returns the catalog identifier for ACP catalog provider ids that ship an icon", () => {
expect(resolveProviderIconName("amp-acp")).toEqual({ kind: "catalog", id: "amp-acp" });
expect(resolveProviderIconName("gemini")).toEqual({ kind: "catalog", id: "gemini" });
});
it("falls back to the bot icon for unknown custom providers", () => {
expect(resolveProviderIconName("custom-claude-profile")).toEqual({ kind: "bot" });
});
});
describe("known provider icon names", () => {
it("includes every ACP catalog entry that ships an icon", () => {
const known = new Set(KNOWN_PROVIDER_ICON_NAMES);
for (const entry of ACP_PROVIDER_CATALOG) {
if (entry.iconSvg) {
expect(known).toContain(entry.id);
}
}
});
it("only lists ACP icon ids that have a catalog entry with an icon", () => {
const builtin = new Set(BUILTIN_PROVIDER_ICON_NAMES);
const terminalOnly = new Set(TERMINAL_PROFILE_ICON_NAMES);
const catalogIdsWithIcons = new Set(
ACP_PROVIDER_CATALOG.filter((entry) => entry.iconSvg).map((entry) => entry.id),
);
for (const name of KNOWN_PROVIDER_ICON_NAMES) {
if (!builtin.has(name) && !terminalOnly.has(name)) {
expect(catalogIdsWithIcons).toContain(name);
}
}
});
});
@@ -1,38 +1,21 @@
import { ACP_PROVIDER_CATALOG } from "@/data/acp-provider-catalog";
export type BuiltinProviderIconName =
| "claude"
| "codex"
| "copilot"
| "kiro"
| "omp"
| "opencode"
| "pi";
import {
BUILTIN_PROVIDER_ICON_NAMES,
KNOWN_PROVIDER_ICON_NAMES,
} from "@getpaseo/protocol/provider-icon-names";
export type ProviderIconName =
| { kind: "builtin"; id: BuiltinProviderIconName }
| { kind: "builtin"; id: string }
| { kind: "catalog"; id: string }
| { kind: "bot" };
const BUILTIN_PROVIDER_IDS: ReadonlySet<BuiltinProviderIconName> = new Set([
"claude",
"codex",
"copilot",
"kiro",
"omp",
"opencode",
"pi",
]);
const CATALOG_ICON_PROVIDER_IDS: ReadonlySet<string> = new Set(
ACP_PROVIDER_CATALOG.flatMap((entry) => (entry.iconSvg ? [entry.id] : [])),
);
const BUILTIN_PROVIDER_IDS = new Set(BUILTIN_PROVIDER_ICON_NAMES);
const KNOWN_PROVIDER_IDS = new Set(KNOWN_PROVIDER_ICON_NAMES);
export function resolveProviderIconName(provider: string): ProviderIconName {
if (BUILTIN_PROVIDER_IDS.has(provider as BuiltinProviderIconName)) {
return { kind: "builtin", id: provider as BuiltinProviderIconName };
if (BUILTIN_PROVIDER_IDS.has(provider)) {
return { kind: "builtin", id: provider };
}
if (CATALOG_ICON_PROVIDER_IDS.has(provider)) {
if (KNOWN_PROVIDER_IDS.has(provider)) {
return { kind: "catalog", id: provider };
}
return { kind: "bot" };
@@ -8,10 +8,7 @@ import { OpenCodeIcon } from "@/components/icons/opencode-icon";
import { OmpIcon } from "@/components/icons/omp-icon";
import { PiIcon } from "@/components/icons/pi-icon";
import { ACP_PROVIDER_CATALOG } from "@/data/acp-provider-catalog";
import {
resolveProviderIconName,
type BuiltinProviderIconName,
} from "@/components/provider-icon-name";
import { resolveProviderIconName } from "@/components/provider-icon-name";
export interface ProviderIconProps {
size: number;
@@ -20,7 +17,7 @@ export interface ProviderIconProps {
export type ProviderIconComponent = ComponentType<ProviderIconProps>;
const BUILTIN_PROVIDER_ICONS: Record<BuiltinProviderIconName, ProviderIconComponent> = {
const BUILTIN_PROVIDER_ICONS: Record<string, ProviderIconComponent> = {
claude: ClaudeIcon as unknown as ProviderIconComponent,
codex: CodexIcon as unknown as ProviderIconComponent,
copilot: CopilotIcon as unknown as ProviderIconComponent,
@@ -53,7 +50,10 @@ function getCatalogProviderIcon(provider: string): ProviderIconComponent {
if (cached) {
return cached;
}
const iconSvg = CATALOG_ICON_SVGS.get(provider) ?? "";
const iconSvg = CATALOG_ICON_SVGS.get(provider);
if (!iconSvg) {
return Bot;
}
const icon = createCatalogIcon(provider, iconSvg);
catalogIconComponents.set(provider, icon);
return icon;
@@ -56,6 +56,7 @@ import {
WorkspaceDesktopTabsRow,
type WorkspaceDesktopTabRowItem,
} from "@/screens/workspace/workspace-desktop-tabs-row";
import type { TerminalProfileInput } from "@/screens/workspace/terminals/use-workspace-terminals";
import {
WorkspaceTabPresentationResolver,
WorkspaceTabIcon,
@@ -92,7 +93,7 @@ interface SplitContainerProps {
onCloseTabsToRight: (tabId: string, paneTabs: WorkspaceTabDescriptor[]) => Promise<void> | void;
onCloseOtherTabs: (tabId: string, paneTabs: WorkspaceTabDescriptor[]) => Promise<void> | void;
onCreateDraftTab: (input: { paneId?: string }) => void;
onCreateTerminalTab: (input: { paneId?: string }) => void;
onCreateTerminalTab: (input: { paneId?: string; profile?: TerminalProfileInput }) => void;
onCreateBrowserTab: (input: { paneId?: string }) => void;
showCreateBrowserTab?: boolean;
buildPaneContentModel: (input: {
@@ -901,8 +901,6 @@ const styles = StyleSheet.create((theme) => ({
labelText: {
fontSize: theme.fontSize.xs,
color: theme.colors.foregroundMuted,
textTransform: "uppercase",
letterSpacing: 0.6,
},
separator: {
height: 1,