Skip to content
Open
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
3 changes: 3 additions & 0 deletions electron/process-detector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ const CLI_PATTERNS: [RegExp, string][] = [
[/\bkimi\b/, "kimi"],
[/\bgemini\b/, "gemini"],
[/\bopencode\b/, "opencode"],
[/\baider\b/, "aider"],
[/\bamp\b/, "amp"],
[/\broo\b/, "roocode"],
[/\blazygit\b/, "lazygit"],
[/\btmux\b/, "tmux"],
];
Expand Down
4 changes: 3 additions & 1 deletion src/components/SettingsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ function ShortcutRow({
);
}

const AGENT_TYPES = ["claude", "codex", "kimi", "gemini", "opencode"] as const;
import { AI_AGENTS } from "../terminal/agentRegistry";

const AGENT_TYPES = AI_AGENTS.map((a) => a.id) as unknown as readonly TerminalType[];

type ValidateResult =
| { ok: true; resolvedPath: string; version: string | null }
Expand Down
15 changes: 5 additions & 10 deletions src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,11 @@ const STATUS_COLOR: Record<TerminalStatus, string> = {
idle: "var(--text-muted)",
};

const TYPE_LABEL: Record<TerminalType, string> = {
shell: "Shell",
claude: "Claude",
codex: "Codex",
kimi: "Kimi",
gemini: "Gemini",
opencode: "OpenCode",
lazygit: "lazygit",
tmux: "Tmux",
};
import { ALL_TERMINAL_TYPE_IDS, getAgentLabel } from "../terminal/agentRegistry";

const TYPE_LABEL: Record<TerminalType, string> = Object.fromEntries(
ALL_TERMINAL_TYPE_IDS.map((id) => [id, getAgentLabel(id)]),
) as Record<TerminalType, string>;

const iconBtnClass =
"w-5 h-5 flex items-center justify-center rounded hover:bg-[var(--sidebar-hover)] text-[var(--text-muted)] hover:text-[var(--text-primary)] transition-colors shrink-0";
Expand Down
3 changes: 3 additions & 0 deletions src/stores/terminalState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export const DEFAULT_SPAN: Record<TerminalType, { cols: number; rows: number }>
kimi: { cols: 1, rows: 1 },
gemini: { cols: 1, rows: 1 },
opencode: { cols: 1, rows: 1 },
aider: { cols: 1, rows: 1 },
amp: { cols: 1, rows: 1 },
roocode: { cols: 1, rows: 1 },
lazygit: { cols: 1, rows: 1 },
tmux: { cols: 1, rows: 1 },
};
Expand Down
15 changes: 5 additions & 10 deletions src/terminal/TerminalTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,11 @@ interface Props {
onSpanChange?: (span: { cols: number; rows: number }) => void;
}

const TYPE_CONFIG: Record<string, { color: string; label: string }> = {
shell: { color: "#888", label: "Shell" },
claude: { color: "#f5a623", label: "Claude" },
codex: { color: "#7928ca", label: "Codex" },
kimi: { color: "#0070f3", label: "Kimi" },
gemini: { color: "#4285f4", label: "Gemini" },
opencode: { color: "#50e3c2", label: "OpenCode" },
lazygit: { color: "#e84d31", label: "Lazygit" },
tmux: { color: "#1bb91f", label: "Tmux" },
};
import { ALL_TERMINAL_TYPE_IDS, getAgentColor, getAgentLabel } from "./agentRegistry";

const TYPE_CONFIG: Record<string, { color: string; label: string }> = Object.fromEntries(
ALL_TERMINAL_TYPE_IDS.map((id) => [id, { color: getAgentColor(id), label: getAgentLabel(id) }]),
);

async function pollSessionId(
ptyId: number,
Expand Down
157 changes: 157 additions & 0 deletions src/terminal/agentRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/**
* Central agent registry — the single source of truth for all supported
* AI coding agents and tool types in TermCanvas.
*
* To add a new agent:
* 1. Add an entry to AGENT_REGISTRY below.
* 2. That's it. The rest of the app reads from this registry.
*/

export interface AgentDefinition {
/** Unique identifier used as the TerminalType discriminator. */
id: string;
/** Human-readable display name. */
label: string;
/** Accent color for the terminal tile badge. */
color: string;
/** CLI command name (used for process detection and launch). */
command: string;
/** Regex pattern to match the process name in `ps` output. */
detectPattern: RegExp;
/**
* Whether this agent is an AI coding agent (shown in Settings > Agents).
* Non-agent tools like lazygit/tmux/shell are `false`.
*/
isAgent: boolean;
/** Whether this type supports session resume. */
supportsResume: boolean;
}

/**
* Ordered list of all known terminal types.
* The order matters for process detection: first match wins.
*
* NOTE: "shell" is intentionally excluded — it's the default fallback
* and has no CLI detection pattern.
*/
export const AGENT_REGISTRY: readonly AgentDefinition[] = [
{
id: "claude",
label: "Claude",
color: "#f5a623",
command: "claude",
detectPattern: /\bclaude\b/,
isAgent: true,
supportsResume: true,
},
{
id: "codex",
label: "Codex",
color: "#7928ca",
command: "codex",
detectPattern: /\bcodex\b/,
isAgent: true,
supportsResume: true,
},
{
id: "kimi",
label: "Kimi",
color: "#0070f3",
command: "kimi",
detectPattern: /\bkimi\b/,
isAgent: true,
supportsResume: true,
},
{
id: "gemini",
label: "Gemini",
color: "#4285f4",
command: "gemini",
detectPattern: /\bgemini\b/,
isAgent: true,
supportsResume: true,
},
{
id: "opencode",
label: "OpenCode",
color: "#50e3c2",
command: "opencode",
detectPattern: /\bopencode\b/,
isAgent: true,
supportsResume: true,
},
{
id: "aider",
label: "Aider",
color: "#14b8a6",
command: "aider",
detectPattern: /\baider\b/,
isAgent: true,
supportsResume: false,
},
{
id: "amp",
label: "Amp",
color: "#6366f1",
command: "amp",
detectPattern: /\bamp\b/,
isAgent: true,
supportsResume: false,
},
{
id: "roocode",
label: "Roo Code",
color: "#ec4899",
command: "roo",
detectPattern: /\broo\b/,
isAgent: true,
supportsResume: false,
},
// Non-agent tools
{
id: "lazygit",
label: "Lazygit",
color: "#e84d31",
command: "lazygit",
detectPattern: /\blazygit\b/,
isAgent: false,
supportsResume: false,
},
{
id: "tmux",
label: "Tmux",
color: "#1bb91f",
command: "tmux",
detectPattern: /\btmux\b/,
isAgent: false,
supportsResume: false,
},
] as const;

// --- Derived lookups (computed once) ---

/** Map from agent id to its definition. */
export const AGENT_BY_ID = new Map<string, AgentDefinition>(
AGENT_REGISTRY.map((a) => [a.id, a]),
);

/** Only the AI coding agents (for Settings > Agents tab). */
export const AI_AGENTS = AGENT_REGISTRY.filter((a) => a.isAgent);

/** All valid terminal type IDs including "shell". */
export const ALL_TERMINAL_TYPE_IDS = [
"shell",
...AGENT_REGISTRY.map((a) => a.id),
] as const;

/** Type-safe label lookup including "shell". */
export function getAgentLabel(id: string): string {
if (id === "shell") return "Shell";
return AGENT_BY_ID.get(id)?.label ?? id;
}

/** Type-safe color lookup including "shell". */
export function getAgentColor(id: string): string {
if (id === "shell") return "#888";
return AGENT_BY_ID.get(id)?.color ?? "#888";
}
54 changes: 54 additions & 0 deletions src/terminal/cliConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,60 @@ export const TERMINAL_CONFIG: Record<TerminalType, TerminalAdapterConfig> = {
pasteStrategy: "separate",
},
},
aider: {
type: "aider",
launch: {
shell: "aider",
resumeArgs: () => [],
newArgs: () => [],
},
composer: {
supportsComposer: true,
allowedStatuses: INTERACTIVE_STATUSES,
inputMode: "bracketed-paste",
supportsImages: false,
pasteKeySequence: () => "",
imageFallback: "error",
pasteDelayMs: 120,
pasteStrategy: "separate",
},
},
amp: {
type: "amp",
launch: {
shell: "amp",
resumeArgs: () => [],
newArgs: () => [],
},
composer: {
supportsComposer: true,
allowedStatuses: INTERACTIVE_STATUSES,
inputMode: "bracketed-paste",
supportsImages: false,
pasteKeySequence: () => "",
imageFallback: "error",
pasteDelayMs: 120,
pasteStrategy: "separate",
},
},
roocode: {
type: "roocode",
launch: {
shell: "roo",
resumeArgs: () => [],
newArgs: () => [],
},
composer: {
supportsComposer: true,
allowedStatuses: INTERACTIVE_STATUSES,
inputMode: "bracketed-paste",
supportsImages: false,
pasteKeySequence: () => "",
imageFallback: "error",
pasteDelayMs: 120,
pasteStrategy: "separate",
},
},
lazygit: {
type: "lazygit",
launch: {
Expand Down
3 changes: 3 additions & 0 deletions src/terminal/slashCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ const COMMANDS_BY_TYPE: Record<TerminalType, readonly SlashCommand[]> = {
kimi: NO_COMMANDS,
gemini: NO_COMMANDS,
opencode: NO_COMMANDS,
aider: NO_COMMANDS,
amp: NO_COMMANDS,
roocode: NO_COMMANDS,
lazygit: NO_COMMANDS,
tmux: NO_COMMANDS,
};
Expand Down
3 changes: 3 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export type TerminalType =
| "kimi"
| "gemini"
| "opencode"
| "aider"
| "amp"
| "roocode"
| "lazygit"
| "tmux";

Expand Down