@cdoing/opentuicli 0.1.21 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,207 +0,0 @@
1
- /**
2
- * DialogCommand — command palette (Ctrl+P)
3
- *
4
- * Full command palette with categories, fuzzy search, shortcuts,
5
- * and keyboard navigation. Inspired by OpenCode's command palette.
6
- */
7
-
8
- import { TextAttributes } from "@opentui/core";
9
- import type { SelectOption } from "@opentui/core";
10
- import { useState, useMemo } from "react";
11
- import { useKeyboard, useTerminalDimensions } from "@opentui/react";
12
- import { useTheme } from "../context/theme";
13
-
14
- // ── Command Definition ───────────────────────────────────
15
-
16
- export interface Command {
17
- id: string;
18
- label: string;
19
- shortcut?: string;
20
- category: string;
21
- icon?: string;
22
- }
23
-
24
- const COMMANDS: Command[] = [
25
- // Session
26
- { id: "session:new", label: "New Session", shortcut: "Ctrl+N", category: "Session", icon: "+" },
27
- { id: "session:browse", label: "Browse Sessions", shortcut: "Ctrl+S", category: "Session", icon: "◦" },
28
- { id: "session:clear", label: "Clear History", shortcut: "", category: "Session", icon: "✕" },
29
-
30
- // Model & Provider
31
- { id: "model:switch", label: "Switch Model", shortcut: "Ctrl+O", category: "Model", icon: "◆" },
32
- { id: "model:provider", label: "Switch Provider", shortcut: "", category: "Model", icon: "◆" },
33
-
34
- // Theme & Appearance
35
- { id: "theme:picker", label: "Browse Themes", shortcut: "Ctrl+T", category: "Appearance", icon: "◈" },
36
- { id: "theme:dark", label: "Dark Mode", shortcut: "", category: "Appearance", icon: "●" },
37
- { id: "theme:light", label: "Light Mode", shortcut: "", category: "Appearance", icon: "○" },
38
- { id: "display:sidebar", label: "Toggle Sidebar", shortcut: "Ctrl+B", category: "Appearance", icon: "▐" },
39
-
40
- // Tools
41
- { id: "tool:shell", label: "Run Shell Command", shortcut: "", category: "Tools", icon: "$" },
42
- { id: "tool:search", label: "Search Codebase", shortcut: "", category: "Tools", icon: "?" },
43
- { id: "tool:tree", label: "File Tree", shortcut: "", category: "Tools", icon: "├" },
44
-
45
- // System
46
- { id: "system:status", label: "System Status", shortcut: "", category: "System", icon: "i" },
47
- { id: "system:help", label: "Help", shortcut: "F1", category: "System", icon: "?" },
48
- { id: "system:doctor", label: "Doctor (Diagnostics)", shortcut: "", category: "System", icon: "+" },
49
- { id: "system:setup", label: "Setup Wizard (Connect Provider)", shortcut: "", category: "System", icon: "⚙" },
50
- { id: "system:exit", label: "Exit", shortcut: "Ctrl+C", category: "System", icon: "⏻" },
51
- ];
52
-
53
- // ── Fuzzy Match ──────────────────────────────────────────
54
-
55
- function fuzzyMatch(query: string, text: string): boolean {
56
- if (!query) return true;
57
- const lower = text.toLowerCase();
58
- const q = query.toLowerCase();
59
- let qi = 0;
60
- for (let i = 0; i < lower.length && qi < q.length; i++) {
61
- if (lower[i] === q[qi]) qi++;
62
- }
63
- return qi === q.length;
64
- }
65
-
66
- // ── Component ────────────────────────────────────────────
67
-
68
- export function DialogCommand(props: {
69
- onSelect: (commandId: string) => void;
70
- onClose: () => void;
71
- }) {
72
- const { theme, customBg } = useTheme();
73
- const t = theme;
74
- const dims = useTerminalDimensions();
75
- const [query, setQuery] = useState("");
76
-
77
- const dialogWidth = Math.min(64, (dims.width || 80) - 4);
78
- const maxVisible = Math.max(8, Math.floor((dims.height || 24) * 0.6));
79
-
80
- // Filter commands by fuzzy search across label, category, and id
81
- const filtered = useMemo(() => {
82
- return COMMANDS.filter(
83
- (cmd) =>
84
- fuzzyMatch(query, cmd.label) ||
85
- fuzzyMatch(query, cmd.category) ||
86
- fuzzyMatch(query, cmd.id)
87
- );
88
- }, [query]);
89
-
90
- // Group by category for display (with separators)
91
- const selectOptions: SelectOption[] = useMemo(() => {
92
- const opts: SelectOption[] = [];
93
- let lastCategory = "";
94
- for (const cmd of filtered) {
95
- if (cmd.category !== lastCategory) {
96
- lastCategory = cmd.category;
97
- }
98
- const shortcutStr = cmd.shortcut || "";
99
- opts.push({
100
- name: `${cmd.icon || " "} ${cmd.label}`,
101
- description: [cmd.category, shortcutStr].filter(Boolean).join(" "),
102
- value: cmd.id,
103
- });
104
- }
105
- return opts;
106
- }, [filtered]);
107
-
108
- useKeyboard((key: any) => {
109
- if (key.name === "escape" || (key.ctrl && key.name === "p")) {
110
- props.onClose();
111
- return;
112
- }
113
-
114
- // Let <select> handle up/down/return
115
- if (key.name === "up" || key.name === "down" || key.name === "return") return;
116
-
117
- // Backspace — search
118
- if (key.name === "backspace") {
119
- setQuery((q) => q.slice(0, -1));
120
- return;
121
- }
122
-
123
- // Ctrl+U — clear search
124
- if (key.ctrl && key.name === "u") {
125
- setQuery("");
126
- return;
127
- }
128
-
129
- // Printable character — append to query
130
- if (key.sequence && key.sequence.length === 1 && !key.ctrl && !key.meta) {
131
- setQuery((q) => q + key.sequence);
132
- }
133
- });
134
-
135
- return (
136
- <box
137
- borderStyle="double"
138
- borderColor={t.primary}
139
- backgroundColor={customBg || t.bg}
140
- paddingX={1}
141
- paddingY={0}
142
- flexDirection="column"
143
- position="absolute"
144
- top={Math.max(1, Math.floor((dims.height || 24) * 0.1))}
145
- left={Math.max(1, Math.floor(((dims.width || 80) - dialogWidth) / 2))}
146
- width={dialogWidth}
147
- >
148
- {/* Title bar */}
149
- <box flexDirection="row" flexShrink={0} height={1}>
150
- <text fg={t.primary} attributes={TextAttributes.BOLD} flexGrow={1}>
151
- {" Commands"}
152
- </text>
153
- <text fg={t.textDim}>{"Ctrl+P "}</text>
154
- </box>
155
-
156
- {/* Search input */}
157
- <box height={1} flexShrink={0}>
158
- <text fg={t.border}>{"─".repeat(dialogWidth - 4)}</text>
159
- </box>
160
- <box flexDirection="row" flexShrink={0} height={1}>
161
- <text fg={t.textMuted}>{" > "}</text>
162
- <text fg={t.text}>{query || ""}</text>
163
- <text fg={t.primary} attributes={TextAttributes.BOLD}>{"█"}</text>
164
- </box>
165
- <box height={1} flexShrink={0}>
166
- <text fg={t.border}>{"─".repeat(dialogWidth - 4)}</text>
167
- </box>
168
-
169
- {/* Command list */}
170
- {selectOptions.length > 0 ? (
171
- <select
172
- options={selectOptions}
173
- focused={true}
174
- height={Math.min(maxVisible, selectOptions.length)}
175
- showDescription={true}
176
- backgroundColor={customBg || undefined}
177
- focusedBackgroundColor={customBg || undefined}
178
- textColor={t.text}
179
- focusedTextColor={t.text}
180
- selectedBackgroundColor={t.primary}
181
- selectedTextColor={t.bg}
182
- descriptionColor={t.textDim}
183
- selectedDescriptionColor={t.bg}
184
- showScrollIndicator={selectOptions.length > maxVisible}
185
- onSelect={(_index: number, option: SelectOption | null) => {
186
- if (option?.value) props.onSelect(option.value);
187
- }}
188
- />
189
- ) : (
190
- <box height={1}>
191
- <text fg={t.textDim}>{" No matching commands"}</text>
192
- </box>
193
- )}
194
-
195
- {/* Footer */}
196
- <box height={1} flexShrink={0}>
197
- <text fg={t.border}>{"─".repeat(dialogWidth - 4)}</text>
198
- </box>
199
- <box flexDirection="row" height={1} flexShrink={0}>
200
- <text fg={t.textDim}>{" ↑↓ navigate "}</text>
201
- <text fg={t.textDim}>{"enter select "}</text>
202
- <text fg={t.textDim}>{"type to filter "}</text>
203
- <text fg={t.textDim}>{"esc close"}</text>
204
- </box>
205
- </box>
206
- );
207
- }
@@ -1,151 +0,0 @@
1
- /**
2
- * DialogHelp — help dialog showing keyboard shortcuts, slash commands, and @mentions
3
- *
4
- * Uses native <scrollbox> for smooth scrolling. Close with Esc or q.
5
- */
6
-
7
- import { TextAttributes } from "@opentui/core";
8
- import { useKeyboard, useTerminalDimensions } from "@opentui/react";
9
- import { useTheme } from "../context/theme";
10
-
11
- // ── Content Sections ────────────────────────────────────
12
-
13
- interface HelpEntry {
14
- key: string;
15
- description: string;
16
- }
17
-
18
- const KEYBOARD_SHORTCUTS: HelpEntry[] = [
19
- { key: "Ctrl+P", description: "Command palette" },
20
- { key: "Ctrl+O", description: "Switch model" },
21
- { key: "Ctrl+N", description: "New session" },
22
- { key: "Ctrl+S", description: "Browse sessions" },
23
- { key: "F1", description: "Show this help" },
24
- { key: "Ctrl+V", description: "Paste text or image" },
25
- { key: "Ctrl+U", description: "Clear input line" },
26
- { key: "Ctrl+W", description: "Delete last word" },
27
- { key: "Tab", description: "Switch mode (Build / Plan)" },
28
- { key: "→ (Right)", description: "Accept autocomplete" },
29
- { key: "Up / Down", description: "Navigate suggestions" },
30
- { key: "Escape", description: "Close dialog / dropdown" },
31
- { key: "Ctrl+C", description: "Quit" },
32
- ];
33
-
34
- const SLASH_COMMANDS: HelpEntry[] = [
35
- { key: "/help", description: "Show help" },
36
- { key: "/clear", description: "Clear chat history" },
37
- { key: "/new", description: "Start new conversation" },
38
- { key: "/compact", description: "Compress context window" },
39
- { key: "/btw <question>", description: "Ask without adding to history" },
40
- { key: "/model [name]", description: "Show/change model" },
41
- { key: "/provider [name]", description: "Show/change provider" },
42
- { key: "/mode", description: "Show permission mode" },
43
- { key: "/dir [path]", description: "Show/change working directory" },
44
- { key: "/config", description: "Show configuration" },
45
- { key: "/config set k v", description: "Set a config value" },
46
- { key: "/theme <mode>", description: "Switch theme (dark/light/auto)" },
47
- { key: "/effort <level>", description: "Set effort level" },
48
- { key: "/plan <on|off>", description: "Toggle plan mode" },
49
- { key: "/history", description: "List saved conversations" },
50
- { key: "/resume <id>", description: "Resume conversation" },
51
- { key: "/view <id>", description: "View conversation messages" },
52
- { key: "/fork [id]", description: "Fork conversation" },
53
- { key: "/delete <id>", description: "Delete conversation" },
54
- { key: "/bg <prompt>", description: "Run prompt in background" },
55
- { key: "/jobs [id]", description: "List/inspect background jobs" },
56
- { key: "/permissions", description: "Show permission rules" },
57
- { key: "/hooks", description: "Show configured hooks" },
58
- { key: "/rules", description: "Show project rules" },
59
- { key: "/context", description: "Show context providers" },
60
- { key: "/mcp", description: "MCP server management" },
61
- { key: "/doctor", description: "System health check" },
62
- { key: "/usage", description: "Show token usage" },
63
- { key: "/auth-status", description: "Show authentication status" },
64
- { key: "/setup", description: "Run setup wizard" },
65
- { key: "/login", description: "Open setup wizard" },
66
- { key: "/logout", description: "Clear OAuth tokens" },
67
- { key: "/init", description: "Initialize project config" },
68
- { key: "/exit", description: "Quit" },
69
- ];
70
-
71
- const AT_MENTIONS: HelpEntry[] = [
72
- { key: "@terminal", description: "Recent terminal output" },
73
- { key: "@url", description: "Fetch URL content" },
74
- { key: "@tree", description: "Directory tree" },
75
- { key: "@codebase", description: "Search codebase" },
76
- { key: "@clip", description: "Clipboard contents" },
77
- { key: "@file", description: "Include file" },
78
- ];
79
-
80
- const keyColWidth = 20;
81
-
82
- function Section(props: { title: string; entries: HelpEntry[]; theme: any }) {
83
- const t = props.theme;
84
- return (
85
- <box flexDirection="column" flexShrink={0}>
86
- <text fg={t.primary} attributes={TextAttributes.BOLD}>
87
- {` ${props.title}`}
88
- </text>
89
- <text>{""}</text>
90
- {props.entries.map((entry) => (
91
- <box key={entry.key} flexDirection="row">
92
- <text fg={t.secondary}>{` ${entry.key.padEnd(keyColWidth)}`}</text>
93
- <text fg={t.textMuted}>{entry.description}</text>
94
- </box>
95
- ))}
96
- <text>{""}</text>
97
- </box>
98
- );
99
- }
100
-
101
- // ── Component ───────────────────────────────────────────
102
-
103
- export function DialogHelp(props: {
104
- onClose: () => void;
105
- }) {
106
- const { theme, customBg } = useTheme();
107
- const t = theme;
108
- const dims = useTerminalDimensions();
109
- const dialogWidth = Math.min(70, (dims.width || 80) - 4);
110
- const dialogHeight = Math.max(10, (dims.height || 24) - 6);
111
-
112
- useKeyboard((key: any) => {
113
- if (key.name === "escape" || key.name === "q") {
114
- props.onClose();
115
- }
116
- });
117
-
118
- return (
119
- <box
120
- borderStyle="double"
121
- borderColor={t.primary}
122
- backgroundColor={customBg || t.bg}
123
- paddingX={2}
124
- paddingY={1}
125
- flexDirection="column"
126
- position="absolute"
127
- top={Math.max(1, Math.floor((dims.height || 24) * 0.05))}
128
- left={Math.max(1, Math.floor(((dims.width || 80) - dialogWidth) / 2))}
129
- width={dialogWidth}
130
- height={dialogHeight}
131
- >
132
- {/* Title bar */}
133
- <box flexDirection="row" flexShrink={0}>
134
- <text fg={t.primary} attributes={TextAttributes.BOLD} flexGrow={1}>
135
- {" Help"}
136
- </text>
137
- <text fg={t.textDim}>{"esc"}</text>
138
- </box>
139
- <text flexShrink={0}>{""}</text>
140
-
141
- {/* Scrollable content */}
142
- <scrollbox flexGrow={1}>
143
- <box flexShrink={0}>
144
- <Section title="Keyboard Shortcuts" entries={KEYBOARD_SHORTCUTS} theme={t} />
145
- <Section title="Slash Commands" entries={SLASH_COMMANDS} theme={t} />
146
- <Section title="@Mentions" entries={AT_MENTIONS} theme={t} />
147
- </box>
148
- </scrollbox>
149
- </box>
150
- );
151
- }
@@ -1,142 +0,0 @@
1
- /**
2
- * DialogModel — model picker dialog (Ctrl+O)
3
- *
4
- * Uses OpenTUI <select> for the model list with proper
5
- * highlight styling and keyboard navigation.
6
- */
7
-
8
- import { TextAttributes } from "@opentui/core";
9
- import type { SelectOption } from "@opentui/core";
10
- import { useState, useMemo } from "react";
11
- import { useKeyboard, useTerminalDimensions } from "@opentui/react";
12
- import { useTheme } from "../context/theme";
13
- import { getProviders } from "@cdoing/ai";
14
-
15
- export interface ModelOption {
16
- id: string;
17
- name: string;
18
- hint?: string;
19
- }
20
-
21
- // Build model map from centralized catalog
22
- const MODELS: Record<string, ModelOption[]> = {};
23
- for (const p of getProviders() as Array<{ id: string; models: Array<{ id: string; label: string; hint?: string }> }>) {
24
- MODELS[p.id] = p.models.map((m) => ({ id: m.id, name: m.label, hint: m.hint }));
25
- }
26
-
27
- export function DialogModel(props: {
28
- provider: string;
29
- currentModel: string;
30
- onSelect: (model: string) => void;
31
- onClose: () => void;
32
- }) {
33
- const { theme, customBg } = useTheme();
34
- const t = theme;
35
- const dims = useTerminalDimensions();
36
- const models = MODELS[props.provider] || [];
37
- const [isCustom, setIsCustom] = useState(false);
38
- const [customInput, setCustomInput] = useState("");
39
-
40
- // Build SelectOption list: models + "Custom model..." at the end
41
- const selectOptions: SelectOption[] = useMemo(() => {
42
- const opts: SelectOption[] = models.map((m) => ({
43
- name: m.name,
44
- description: [
45
- m.hint || "",
46
- m.id === props.currentModel ? "● current" : "",
47
- ].filter(Boolean).join(" "),
48
- value: m.id,
49
- }));
50
- opts.push({
51
- name: "Custom model...",
52
- description: "type any model name",
53
- value: "__custom__",
54
- });
55
- return opts;
56
- }, [models, props.currentModel]);
57
-
58
- const initialIndex = Math.max(0, models.findIndex((m) => m.id === props.currentModel));
59
-
60
- useKeyboard((key: any) => {
61
- if (key.name === "escape" || (key.ctrl && key.name === "c")) {
62
- if (isCustom) { setIsCustom(false); return; }
63
- props.onClose();
64
- return;
65
- }
66
- if (!isCustom) return; // Let <select> handle navigation
67
- // Custom model text input mode
68
- if (key.name === "return") {
69
- const m = customInput.trim();
70
- if (m) props.onSelect(m);
71
- } else if (key.name === "backspace") {
72
- setCustomInput((s) => s.slice(0, -1));
73
- } else if (key.ctrl && key.name === "u") {
74
- setCustomInput("");
75
- } else if (key.sequence && key.sequence.length === 1 && !key.ctrl && !key.meta) {
76
- setCustomInput((s) => s + key.sequence);
77
- }
78
- });
79
-
80
- return (
81
- <box
82
- borderStyle="double"
83
- borderColor={t.primary}
84
- backgroundColor={customBg || t.bg}
85
- paddingX={1}
86
- paddingY={1}
87
- flexDirection="column"
88
- position="absolute"
89
- top={Math.max(2, Math.floor((dims.height || 24) * 0.25))}
90
- left={Math.max(1, Math.floor(((dims.width || 80) - Math.min(60, (dims.width || 80) - 4)) / 2))}
91
- width={Math.min(60, (dims.width || 80) - 4)}
92
- >
93
- <text fg={t.primary} attributes={TextAttributes.BOLD}>
94
- {" Select Model"}
95
- </text>
96
- <text fg={t.textDim}>{` Provider: ${props.provider}`}</text>
97
- <text>{""}</text>
98
- {isCustom ? (
99
- <>
100
- <text fg={t.text}>{" Enter custom model ID:"}</text>
101
- <box flexDirection="row">
102
- <text fg={t.primary}>{" > "}</text>
103
- <text fg={t.text}>{customInput}</text>
104
- <text fg={t.primary} attributes={TextAttributes.BOLD}>{"_"}</text>
105
- </box>
106
- <text>{""}</text>
107
- <text fg={t.textDim}>{" Enter Confirm Ctrl+U Clear Esc Back"}</text>
108
- </>
109
- ) : (
110
- <>
111
- <select
112
- options={selectOptions}
113
- focused={!isCustom}
114
- selectedIndex={initialIndex}
115
- height={Math.min(selectOptions.length, 10)}
116
- showDescription={true}
117
- backgroundColor={customBg || undefined}
118
- focusedBackgroundColor={customBg || undefined}
119
- textColor={t.text}
120
- focusedTextColor={t.text}
121
- selectedBackgroundColor={t.primary}
122
- selectedTextColor={t.bg}
123
- descriptionColor={t.textDim}
124
- selectedDescriptionColor={t.bg}
125
- showScrollIndicator={selectOptions.length > 10}
126
- onSelect={(_index: number, option: SelectOption | null) => {
127
- if (!option) return;
128
- if (option.value === "__custom__") {
129
- setIsCustom(true);
130
- setCustomInput("");
131
- } else {
132
- props.onSelect(option.value);
133
- }
134
- }}
135
- />
136
- <text>{""}</text>
137
- <text fg={t.textDim}>{" ↑↓ Navigate Enter Select Esc Close"}</text>
138
- </>
139
- )}
140
- </box>
141
- );
142
- }
@@ -1,84 +0,0 @@
1
- /**
2
- * DialogStatus — system status dialog showing provider, tools, config info.
3
- * Uses native <scrollbox> for smooth scrolling through sections.
4
- */
5
-
6
- import { TextAttributes } from "@opentui/core";
7
- import { useKeyboard, useTerminalDimensions } from "@opentui/react";
8
- import { useTheme } from "../context/theme";
9
- import { useSDK } from "../context/sdk";
10
-
11
- export function DialogStatus(props: { onClose: () => void }) {
12
- const { theme, customBg } = useTheme();
13
- const t = theme;
14
- const sdk = useSDK();
15
- const dims = useTerminalDimensions();
16
-
17
- const dialogWidth = Math.min(60, (dims.width || 80) - 4);
18
- const dialogHeight = Math.max(10, (dims.height || 24) - 6);
19
-
20
- // Gather status info
21
- const allTools = sdk.registry.getAll ? sdk.registry.getAll() : [];
22
- const toolNames = Array.isArray(allTools)
23
- ? allTools.map((tool: any) =>
24
- tool.definition?.name || tool.name || "unknown"
25
- )
26
- : [];
27
-
28
- useKeyboard((key: any) => {
29
- if (key.name === "escape" || key.name === "q") props.onClose();
30
- });
31
-
32
- return (
33
- <box
34
- borderStyle="double"
35
- borderColor={t.primary}
36
- backgroundColor={customBg || t.bg}
37
- paddingX={1}
38
- paddingY={1}
39
- flexDirection="column"
40
- position="absolute"
41
- top={Math.max(1, Math.floor((dims.height || 24) * 0.1))}
42
- left={Math.max(1, Math.floor(((dims.width || 80) - dialogWidth) / 2))}
43
- width={dialogWidth}
44
- height={dialogHeight}
45
- >
46
- {/* Title bar */}
47
- <box flexDirection="row" flexShrink={0}>
48
- <text fg={t.primary} attributes={TextAttributes.BOLD} flexGrow={1}>
49
- {" System Status"}
50
- </text>
51
- <text fg={t.textDim}>{"esc"}</text>
52
- </box>
53
- <text flexShrink={0}>{""}</text>
54
-
55
- <scrollbox flexGrow={1}>
56
- <box flexShrink={0}>
57
- {/* Provider */}
58
- <text fg={t.secondary} attributes={TextAttributes.BOLD}>{" Provider"}</text>
59
- <box flexDirection="row"><text fg={t.textMuted}>{" Provider "}</text><text fg={t.text}>{sdk.provider}</text></box>
60
- <box flexDirection="row"><text fg={t.textMuted}>{" Model "}</text><text fg={t.text}>{sdk.model}</text></box>
61
- <box flexDirection="row"><text fg={t.textMuted}>{" Directory "}</text><text fg={t.text}>{sdk.workingDir}</text></box>
62
- <text>{""}</text>
63
-
64
- {/* System */}
65
- <text fg={t.secondary} attributes={TextAttributes.BOLD}>{" System"}</text>
66
- <box flexDirection="row"><text fg={t.textMuted}>{" Node "}</text><text fg={t.text}>{process.version}</text></box>
67
- <box flexDirection="row"><text fg={t.textMuted}>{" Platform "}</text><text fg={t.text}>{`${process.platform} ${process.arch}`}</text></box>
68
- <box flexDirection="row"><text fg={t.textMuted}>{" Terminal "}</text><text fg={t.text}>{process.env.TERM_PROGRAM || process.env.TERM || "unknown"}</text></box>
69
- <box flexDirection="row"><text fg={t.textMuted}>{" Shell "}</text><text fg={t.text}>{process.env.SHELL || "unknown"}</text></box>
70
- <text>{""}</text>
71
-
72
- {/* Tools */}
73
- <text fg={t.secondary} attributes={TextAttributes.BOLD}>{` Tools (${toolNames.length})`}</text>
74
- {toolNames.map((name: string) => (
75
- <box key={name} flexDirection="row">
76
- <text fg={t.textMuted}>{" • "}</text>
77
- <text fg={t.text}>{name}</text>
78
- </box>
79
- ))}
80
- </box>
81
- </scrollbox>
82
- </box>
83
- );
84
- }