Skip to content

Commit 270fb48

Browse files
committed
feat: restructure command menu — rename category to Agent, add switch agent, thinking effort, MCP toggle, debug commands; fix case-sensitive search; display agent names in titlecase
1 parent 9cde2e1 commit 270fb48

3 files changed

Lines changed: 70 additions & 12 deletions

File tree

packages/code/src/tui/app.tsx

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { ChatArea } from "./components/chat-area.js"
77
import { CommandPalette, type CmdItem } from "./components/command-palette.js"
88
import { PromptBar } from "./prompt-bar.js"
99
import { Tips } from "./tips.js"
10-
import { genId, getMessageBlocks } from "./utils.js"
10+
import { genId, getMessageBlocks, titlecase } from "./utils.js"
1111
import type { ChatMessage, ContentBlock } from "./types.js"
1212
import { SessionStore } from "../services/session-store.js"
1313
import { SnapshotManager } from "../services/snapshot-manager.js"
@@ -19,7 +19,11 @@ import { ModelSwitcher } from "./ui/model-switcher.js"
1919
import { ManageProvidersDialog } from "./ui/manage-providers-dialog.js"
2020
import { DoctorDialog } from "./ui/doctor-dialog.js"
2121
import { AboutDialog } from "./ui/about-dialog.js"
22-
import { cycleEffort, getProviderEfforts } from "./variant-cycle.js"
22+
import { AgentSwitcher } from "./ui/agent-switcher.js"
23+
import { ThinkingEffortDialog } from "./ui/thinking-effort-dialog.js"
24+
import { McpToggleDialog } from "./ui/mcp-toggle-dialog.js"
25+
import { DebugDialog } from "./ui/debug-dialog.js"
26+
import { cycleEffort } from "./variant-cycle.js"
2327
import { MessageControls } from "./ui/message-controls.js"
2428
import { ToastContainer, showToast } from "./components/toast.js"
2529
import clipboard from "clipboardy"
@@ -91,7 +95,7 @@ export function App({ renderer }: { renderer: CliRenderer }) {
9195
const [copiedMsg, setCopiedMsg] = useState(false)
9296
const [selectedAgent, setSelectedAgent] = useState("build")
9397
const [submitKey, setSubmitKey] = useState(0)
94-
const [dialogStep, setDialogStep] = useState<{ type: "provider" } | { type: "session-list"; mode?: "delete" | "rename" } | { type: "switch-model" } | { type: "manage-providers" } | { type: "doctor"; result: any } | { type: "about" } | null>(null)
98+
const [dialogStep, setDialogStep] = useState<{ type: "provider" } | { type: "session-list"; mode?: "delete" | "rename" } | { type: "switch-model" } | { type: "manage-providers" } | { type: "doctor"; result: any } | { type: "about" } | { type: "switch-agent" } | { type: "thinking-effort" } | { type: "toggle-mcp" } | { type: "debug" } | null>(null)
9599
const [placeholderIdx, setPlaceholderIdx] = useState(0)
96100
const [promptHistory, setPromptHistory] = useState<string[]>([])
97101
const [historyIdx, setHistoryIdx] = useState(-1)
@@ -620,11 +624,12 @@ export function App({ renderer }: { renderer: CliRenderer }) {
620624
setRoute, setMessages, setStatus, setElapsedMs, setTokPerSec, setTokenUsage, setShowThinking, setShowToolCalls, setHomeKey, setNavKey, setDialogStep,
621625
onCycleVariant: handleCycleVariant,
622626
currentEffort: thinkingEffort,
623-
}), [renderer, hasModel, selectedModel, provider, mcpCount, customProviderCount, messages.length, showThinking, showToolCalls, handleCycleVariant, thinkingEffort])
627+
selectedAgent,
628+
}), [renderer, hasModel, selectedModel, provider, mcpCount, customProviderCount, messages.length, showThinking, showToolCalls, handleCycleVariant, thinkingEffort, selectedAgent])
624629

625630
const cmdFiltered = useMemo(() => {
626631
const q = cmdFilter.toLowerCase()
627-
return !q ? cmdItems : cmdItems.filter((i) => i.label.includes(q) || i.desc.includes(q) || (i.cat && i.cat.toLowerCase().includes(q)))
632+
return !q ? cmdItems : cmdItems.filter((i) => i.label.toLowerCase().includes(q) || i.desc.toLowerCase().includes(q) || (i.cat && i.cat.toLowerCase().includes(q)))
628633
}, [cmdItems, cmdFilter])
629634
const execCmd = useCallback((item: any) => { item.action(); setShowCmd(false) }, [])
630635
useEffect(() => { if (cmdSelected >= cmdFiltered.length && cmdFiltered.length > 0) setCmdSelected(cmdFiltered.length - 1) }, [cmdSelected, cmdFiltered.length])
@@ -667,8 +672,8 @@ export function App({ renderer }: { renderer: CliRenderer }) {
667672
<box height={1} />
668673
<box flexDirection="row" justifyContent="flex-end" width={Math.min(68, termWidth - 8)}>
669674
<box flexDirection="row" gap={2}>
670-
<box flexDirection="row"><text fg={c.text}>tab</text><text fg={c.dim}> agents</text></box>
671-
<box flexDirection="row"><text fg={c.text}>ctrl+t</text><text fg={c.dim}> variant</text></box>
675+
<box flexDirection="row"><text fg={c.text}>tab</text><text fg={c.dim}> agent</text></box>
676+
<box flexDirection="row"><text fg={c.text}>ctrl+t</text><text fg={c.dim}> effort</text></box>
672677
<box flexDirection="row"><text fg={c.text}>ctrl+p</text><text fg={c.dim}> commands</text></box>
673678
</box>
674679
</box>
@@ -865,6 +870,46 @@ export function App({ renderer }: { renderer: CliRenderer }) {
865870
onClose={() => setDialogStep(null)}
866871
registerHandler={(fn: any) => { dialogKeyHandler.current = fn }} />
867872
)}
873+
{dialogStep?.type === "switch-agent" && (
874+
<AgentSwitcher currentAgent={selectedAgent} termWidth={termWidth} termHeight={termHeight}
875+
onAgentSelected={(agent) => {
876+
setSelectedAgent(agent)
877+
agentRef.current = null
878+
setDialogStep(null)
879+
showToast(`Switched to ${titlecase(agent)} agent`, "info")
880+
}}
881+
onClose={() => setDialogStep(null)}
882+
registerHandler={(fn) => { dialogKeyHandler.current = fn }}
883+
/>
884+
)}
885+
{dialogStep?.type === "thinking-effort" && (
886+
<ThinkingEffortDialog provider={provider} currentEffort={thinkingEffort}
887+
termWidth={termWidth} termHeight={termHeight}
888+
onEffortSelected={(effort) => {
889+
setThinkingEffort(effort)
890+
agentRef.current = null
891+
setDialogStep(null)
892+
showToast(effort === "none" ? "Thinking: off" : `Thinking: ${effort}`, "info")
893+
}}
894+
onClose={() => setDialogStep(null)}
895+
registerHandler={(fn) => { dialogKeyHandler.current = fn }}
896+
/>
897+
)}
898+
{dialogStep?.type === "toggle-mcp" && (
899+
<McpToggleDialog termWidth={termWidth} termHeight={termHeight}
900+
onClose={() => setDialogStep(null)}
901+
registerHandler={(fn) => { dialogKeyHandler.current = fn }}
902+
/>
903+
)}
904+
{dialogStep?.type === "debug" && (
905+
<DebugDialog termWidth={termWidth} termHeight={termHeight}
906+
selectedModel={selectedModel} provider={provider}
907+
selectedAgent={selectedAgent} thinkingEffort={thinkingEffort}
908+
sessionStore={sessionStore.current} mcpCount={mcpCount}
909+
onClose={() => setDialogStep(null)}
910+
registerHandler={(fn: any) => { dialogKeyHandler.current = fn }}
911+
/>
912+
)}
868913
{msgControls && sessionId.current && (
869914
<MessageControls
870915
message={msgControls}

packages/code/src/tui/commands.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { CmdItem } from "./components/command-palette.js"
22
import type { SessionStore } from "../services/session-store.js"
33
import { getEffortLabel } from "./variant-cycle.js"
4+
import { titlecase } from "./utils.js"
45

56
export function buildCmdItems(opts: {
67
renderer: { destroy: () => void }
@@ -23,12 +24,13 @@ export function buildCmdItems(opts: {
2324
setShowToolCalls: (fn: (v: boolean) => boolean) => void
2425
setHomeKey: (fn: (k: number) => number) => void
2526
setNavKey: (fn: (k: number) => number) => void
26-
setDialogStep: (v: { type: "provider" } | { type: "session-list"; mode?: "delete" | "rename" } | { type: "switch-model" } | { type: "manage-providers" } | { type: "doctor"; result: any } | { type: "about" } | null) => void
27+
setDialogStep: (v: { type: "provider" } | { type: "session-list"; mode?: "delete" | "rename" } | { type: "switch-model" } | { type: "manage-providers" } | { type: "doctor"; result: any } | { type: "about" } | { type: "switch-agent" } | { type: "thinking-effort" } | { type: "toggle-mcp" } | { type: "debug" } | null) => void
2728
sessionIdRef: { current: string | null }
2829
onCycleVariant: () => void
2930
currentEffort?: string
31+
selectedAgent: string
3032
}): CmdItem[] {
31-
const { renderer, sessionStore: s, sessionIdRef, hasModel, selectedModel, provider, mcpCount, customProviderCount, messagesLength, showThinking, showToolCalls, setRoute, setMessages, setStatus, setElapsedMs, setTokPerSec, setTokenUsage, setShowThinking, setShowToolCalls, setHomeKey, setNavKey, setDialogStep, currentEffort } = opts
33+
const { renderer, sessionStore: s, sessionIdRef, hasModel, selectedModel, provider, mcpCount, customProviderCount, messagesLength, showThinking, showToolCalls, setRoute, setMessages, setStatus, setElapsedMs, setTokPerSec, setTokenUsage, setShowThinking, setShowToolCalls, setHomeKey, setNavKey, setDialogStep, currentEffort, selectedAgent } = opts
3234
return [
3335
// Session
3436
{ id: "new", label: "New Session", desc: "Start fresh", cat: "Session", slashName: "new", slashAliases: ["clear"], action: () => {
@@ -74,10 +76,19 @@ export function buildCmdItems(opts: {
7476
{ id: "provider", label: "Connect Provider", desc: hasModel ? "Switch API provider" : "No provider configured", cat: "Provider", slashName: "connect", slashAliases: ["provider"], action: () => { setDialogStep({ type: "provider" }) } },
7577
{ id: "manage-providers", label: "Manage Providers", desc: `${opts.customProviderCount} custom provider${opts.customProviderCount !== 1 ? "s" : ""}`, cat: "Provider", slashName: "providers", action: () => { setDialogStep({ type: "manage-providers" }) } },
7678
// Model
77-
{ id: "switch-model", label: "Switch Model", desc: selectedModel || "No model selected", cat: "Model", slashName: "model", slashAliases: ["models", "switch-model"], action: () => {
79+
{ id: "switch-model", label: "Switch Model", desc: selectedModel || "No model selected", cat: "Agent", slashName: "model", slashAliases: ["models", "switch-model"], action: () => {
7880
setDialogStep({ type: "switch-model" })
7981
} },
80-
{ id: "cycle-variant", label: "Variant Cycle", desc: `effort: ${getEffortLabel(currentEffort)}`, cat: "Model", slashName: "variant", slashAliases: ["cycle-variant"], action: () => { opts.onCycleVariant() } },
82+
{ id: "switch-agent", label: "Switch Agent", desc: titlecase(selectedAgent || "build"), cat: "Agent", slashName: "agent", slashAliases: ["agents", "switch-agent"], action: () => {
83+
setDialogStep({ type: "switch-agent" })
84+
} },
85+
{ id: "cycle-effort", label: "Thinking effort cycle", desc: `effort: ${getEffortLabel(currentEffort)}`, cat: "Agent", slashName: "effort", slashAliases: ["cycle-effort", "variant"], action: () => { opts.onCycleVariant() } },
86+
{ id: "change-effort", label: "Change Thinking effort", desc: `set to ${getEffortLabel(currentEffort)}`, cat: "Agent", slashName: "thinking-effort", slashAliases: ["change-effort"], action: () => {
87+
setDialogStep({ type: "thinking-effort" })
88+
} },
89+
{ id: "toggle-mcp", label: "Toggle MCPs", desc: `${opts.mcpCount} connected`, cat: "Agent", slashName: "mcp", slashAliases: ["toggle-mcp"], action: () => {
90+
setDialogStep({ type: "toggle-mcp" })
91+
} },
8192
// Navigation
8293
{ id: "home", label: "Go Home", desc: "Return to home", cat: "Navigation", slashName: "home", action: () => { setRoute("home") } },
8394
// System
@@ -87,6 +98,7 @@ export function buildCmdItems(opts: {
8798
setDialogStep({ type: "doctor", result } as any)
8899
}))
89100
} },
101+
{ id: "debug", label: "Debug", desc: "System information", cat: "System", slashName: "debug", action: () => { setDialogStep({ type: "debug" }) } },
90102
{ id: "about", label: "About", desc: "Version info", cat: "System", slashName: "about", action: () => { setDialogStep({ type: "about" }) } },
91103
{ id: "help", label: "Help", desc: "Keyboard shortcuts", cat: "System", slashName: "help", action: () => { setStatus("Esc quit · Tab agents · Ctrl+P palette · Ctrl+L clear"); setTimeout(() => setStatus("Ready"), 4000) } },
92104
{ id: "quit", label: "Quit", desc: "Exit", cat: "System", slashName: "exit", slashAliases: ["quit", "q"], action: () => renderer.destroy() },

packages/code/src/tui/prompt-bar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useRef, useEffect } from "react"
22
import { c, SPINNER } from "./theme.js"
3+
import { titlecase } from "./utils.js"
34

45
export interface PromptBarProps {
56
isLoading: boolean
@@ -100,7 +101,7 @@ export function PromptBar(props: PromptBarProps) {
100101
<box height={1} />
101102
<box flexDirection="row" justifyContent="space-between" alignItems="center" height={1}>
102103
<box flexDirection="row" gap={2} alignItems="center">
103-
<text fg={c.accent}>{agent}</text>
104+
<text fg={c.accent}>{titlecase(agent)}</text>
104105
<text fg={c.dim}>{model}</text>
105106
<text fg={c.subtext}>{provider}</text>
106107
{thinkingEffort && thinkingEffort !== "none" && (

0 commit comments

Comments
 (0)