From 7cd97ce8d35446e77a84768e2b35078ae76069ec Mon Sep 17 00:00:00 2001 From: uroma Date: Thu, 22 Jan 2026 11:58:39 +0000 Subject: [PATCH] Add CodeNomad-inspired tool rendering system Phase 1 of enhancement plan: - Created tool-renderers.js with 13+ specialized tool renderers - Created tool-rendering.css for context-specific tool styling - Integrated into index.html with cache bust (v1769083100000) Supported tools: - bash, edit, write, read (file operations) - websearch, webfetch (web operations) - task, todowrite (agent operations) - grep, glob, list, patch (utilities) Each tool gets specialized rendering showing the most relevant information in a user-friendly format. Co-Authored-By: Claude --- public/claude-ide/index.html | 4 +- public/claude-ide/tool-renderers.js | 568 +++++++++++++++++++++++++ public/claude-ide/tool-rendering.css | 602 +++++++++++++++++++++++++++ 3 files changed, 1173 insertions(+), 1 deletion(-) create mode 100644 public/claude-ide/tool-renderers.js create mode 100644 public/claude-ide/tool-rendering.css diff --git a/public/claude-ide/index.html b/public/claude-ide/index.html index 3f244f01..674ef7d8 100644 --- a/public/claude-ide/index.html +++ b/public/claude-ide/index.html @@ -233,7 +233,7 @@ + diff --git a/public/claude-ide/tool-renderers.js b/public/claude-ide/tool-renderers.js new file mode 100644 index 00000000..8f59b914 --- /dev/null +++ b/public/claude-ide/tool-renderers.js @@ -0,0 +1,568 @@ +/** + * Tool Call Renderers - Specialized rendering per tool type + * Inspired by CodeNomad's tool rendering system + * https://github.com/NeuralNomadsAI/CodeNomad + * + * Each tool type gets specialized rendering to show the most relevant information + * in a user-friendly, context-specific way. + */ + +'use strict'; + +// ============================================================ +// Tool Renderer Registry +// ============================================================ + +class ToolRendererRegistry { + constructor() { + this.renderers = new Map(); + this.registerDefaultRenderers(); + } + + /** + * Register a tool renderer + * @param {string} toolName - Name of the tool + * @param {Object} renderer - Renderer object with getAction, getTitle, renderBody + */ + register(toolName, renderer) { + this.renderers.set(toolName, renderer); + } + + /** + * Get renderer for a tool + * @param {string} toolName - Name of the tool + * @returns {Object} - Renderer object + */ + get(toolName) { + return this.renderers.get(toolName) || this.renderers.get('default'); + } + + /** + * Register all default tool renderers + */ + registerDefaultRenderers() { + // Bash/Shell Commands + this.register('bash', { + icon: 'โšก', + getAction: (input) => 'Writing command...', + getTitle: (input, output, metadata) => { + const desc = input.description || input.command || ''; + const timeout = input.timeout ? `ยท ${input.timeout}ms` : ''; + return desc ? `Shell ${desc} ${timeout}`.trim() : 'Shell'; + }, + renderBody: (input, output, metadata) => { + const command = input.command ? `$ ${escapeHtml(input.command)}` : ''; + const out = metadata.output || output || ''; + const exitCode = metadata.exitCode !== undefined ? `Exit: ${metadata.exitCode}` : ''; + + return ` +
+ ${command ? `
${command}
` : ''} + ${out ? `
${formatAnsiOutput(escapeHtml(out))}
` : ''} + ${exitCode ? `
${exitCode}
` : ''} +
+ `; + } + }); + + // File Edit + this.register('edit', { + icon: 'โœ๏ธ', + getAction: () => 'Preparing edit...', + getTitle: (input) => { + const file = input.filePath?.split('/').pop() || 'file'; + return `Edit ${file}`; + }, + renderBody: (input, output, metadata) => { + const diff = metadata.diff || output; + const filePath = input.filePath || 'unknown'; + + if (diff) { + return ` +
+
Modified: ${escapeHtml(filePath)}
+
${formatDiff(diff)}
+
+ `; + } + + const stats = metadata.stats || {}; + const statsText = stats.added || stats.removed ? + `${stats.added || 0} additions, ${stats.removed || 0} deletions` : ''; + + return ` +
+
Modified: ${escapeHtml(filePath)}
+ ${statsText ? `
${statsText}
` : ''} +
+ `; + } + }); + + // File Write + this.register('write', { + icon: '๐Ÿ“', + getAction: () => 'Writing file...', + getTitle: (input) => { + const file = input.filePath?.split('/').pop() || 'file'; + return `Write ${file}`; + }, + renderBody: (input, output) => { + const filePath = input.filePath || 'unknown'; + const content = output || ''; + const lines = content.split('\n').length; + const size = new Blob([content]).size; + + return ` +
+
Created: ${escapeHtml(filePath)}
+
${lines} lines (${formatBytes(size)})
+ ${content ? `
${escapeHtml(content.split('\n').slice(0, 6).join('\n'))}${lines > 6 ? '\n...' : ''}
` : ''} +
+ `; + } + }); + + // File Read + this.register('read', { + icon: '๐Ÿ“–', + getAction: () => 'Reading file...', + getTitle: (input) => { + const file = input.filePath?.split('/').pop() || 'file'; + return `Read ${file}`; + }, + renderBody: (input, output, metadata) => { + const preview = metadata.preview || output || ''; + const filePath = input.filePath || 'unknown'; + + if (preview) { + const lines = preview.split('\n').slice(0, 6).join('\n'); + const totalLines = preview.split('\n').length; + + return ` +
+
Read: ${escapeHtml(filePath)}
+
${escapeHtml(lines)}${totalLines > 6 ? '\n...' : ''}
+
${totalLines} lines total
+
+ `; + } + + return ` +
+
Read: ${escapeHtml(filePath)}
+
+ `; + } + }); + + // Web Search + this.register('websearch', { + icon: '๐Ÿ”', + getAction: () => 'Searching...', + getTitle: (input) => { + const query = input.query?.substring(0, 50) || ''; + return query ? `Search: ${query}...` : 'Search'; + }, + renderBody: (input, output) => { + if (Array.isArray(output)) { + return ` +
+ ${output.map(result => ` +
+ ${escapeHtml(result.title || '')} +
${escapeHtml(result.snippet || result.description || '')}
+
+ `).join('')} +
+ `; + } + + // Fallback for string output + if (typeof output === 'string') { + return `
${escapeHtml(output.substring(0, 500))}
`; + } + + return ''; + } + }); + + // WebFetch + this.register('webfetch', { + icon: '๐ŸŒ', + getAction: () => 'Fetching...', + getTitle: (input) => { + const url = input.url?.substring(0, 50) || ''; + return url ? `Fetch ${url}...` : 'Fetch'; + }, + renderBody: (input, output, metadata) => { + const url = input.url || ''; + const content = output || ''; + const preview = content.substring(0, 300); + + return ` +
+ +
${escapeHtml(preview)}${content.length > 300 ? '...' : ''}
+ ${content.length > 300 ? `
${content.length} characters
` : ''} +
+ `; + } + }); + + // Task/Agent Delegation + this.register('task', { + icon: '๐Ÿค–', + getAction: () => 'Delegating...', + getTitle: (input) => { + const type = input.subagent_type || 'agent'; + const desc = input.description?.substring(0, 40) || ''; + return desc ? `Task[${type}] ${desc}...` : `Task[${type}]`; + }, + renderBody: (input, output, metadata) => { + const summary = metadata.summary || []; + const result = output; + + if (summary.length > 0) { + return ` +
+ ${summary.map(item => ` +
+ ${getToolIcon(item.tool)} + ${escapeHtml(item.description || item.tool)} +
+ `).join('')} +
+ `; + } + + if (result && typeof result === 'string') { + return `
${escapeHtml(result.substring(0, 500))}
`; + } + + return ''; + } + }); + + // Todo/Plan Management + this.register('todowrite', { + icon: '๐Ÿ“‹', + getAction: (input) => { + const todos = input.todos || []; + const allPending = todos.every(t => t.status === 'pending'); + const allCompleted = todos.every(t => t.status === 'completed'); + if (allPending) return 'Creating plan...'; + if (allCompleted) return 'Completing plan...'; + return 'Updating plan...'; + }, + getTitle: (input) => { + const todos = input.todos || []; + const completed = todos.filter(t => t.status === 'completed').length; + const total = todos.length; + return `Plan (${completed}/${total})`; + }, + renderBody: (input) => { + const todos = input.todos || []; + return ` +
+ ${todos.map(todo => ` +
+ + ${todo.status === 'completed' ? 'โ˜‘' : + todo.status === 'in_progress' ? 'โ˜' : 'โ˜'} + + ${escapeHtml(todo.content)} +
+ `).join('')} +
+ `; + } + }); + + // Grep - Content Search + this.register('grep', { + icon: '๐Ÿ”Ž', + getAction: () => 'Searching...', + getTitle: (input) => { + const pattern = input.pattern?.substring(0, 40) || ''; + return pattern ? `Grep "${pattern}"` : 'Grep'; + }, + renderBody: (input, output) => { + if (Array.isArray(output)) { + return ` +
+ ${output.slice(0, 20).map(match => ` +
+
${escapeHtml(match.path || match.file || '')}
+
Line ${match.line || '?'}: ${escapeHtml(match.text || match.content || '').substring(0, 100)}
+
+ `).join('')} + ${output.length > 20 ? `
+${output.length - 20} more matches
` : ''} +
+ `; + } + return ''; + } + }); + + // Glob - File Pattern Matching + this.register('glob', { + icon: '๐Ÿ“', + getAction: () => 'Searching files...', + getTitle: (input) => { + const pattern = input.pattern?.substring(0, 50) || ''; + return pattern ? `Glob ${pattern}` : 'Glob'; + }, + renderBody: (input, output) => { + const files = output || []; + const count = files.length; + + if (count > 0) { + return ` +
+
${count} file${count !== 1 ? 's' : ''} found
+
+ ${files.slice(0, 15).map(f => `
${escapeHtml(f)}
`).join('')} +
+ ${count > 15 ? `
+${count - 15} more files
` : ''} +
+ `; + } + + return '
No files found
'; + } + }); + + // List - Directory Listing + this.register('list', { + icon: '๐Ÿ“‚', + getAction: () => 'Listing directory...', + getTitle: (input) => { + const path = input.path || ''; + return `List ${path.split('/').pop() || 'directory'}`; + }, + renderBody: (input, output) => { + const entries = output?.entries || []; + return ` +
+ ${entries.slice(0, 20).map(entry => ` +
+ ${entry.type === 'directory' ? '๐Ÿ“' : '๐Ÿ“„'} + ${escapeHtml(entry.name)} +
+ `).join('')} + ${entries.length > 20 ? `
+${entries.length - 20} more entries
` : ''} +
+ `; + } + }); + + // Patch - Apply Patch + this.register('patch', { + icon: '๐Ÿ”ง', + getAction: () => 'Applying patch...', + getTitle: () => 'Patch', + renderBody: (input, output) => { + return ` +
+
Patch applied successfully
+
+ `; + } + }); + + // Default fallback for unknown tools + this.register('default', { + icon: '๐Ÿ”ง', + getAction: () => 'Processing...', + getTitle: (input, toolName) => { + return capitalize(toolName || 'tool'); + }, + renderBody: (input, output) => { + if (typeof output === 'string' && output) { + const preview = output.split('\n').slice(0, 10).join('\n'); + return `
${escapeHtml(preview)}
`; + } + if (output && typeof output === 'object') { + return `
${escapeHtml(JSON.stringify(output, null, 2).substring(0, 500))}
`; + } + return ''; + } + }); + } +} + +// ============================================================ +// Helper Functions +// ============================================================ + +/** + * Escape HTML to prevent XSS + */ +function escapeHtml(text) { + if (text === null || text === undefined) return ''; + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +} + +/** + * Capitalize first letter + */ +function capitalize(str) { + if (!str) return ''; + return str.charAt(0).toUpperCase() + str.slice(1); +} + +/** + * Format bytes to human readable size + */ +function formatBytes(bytes) { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; +} + +/** + * Format ANSI escape sequences for terminal output + */ +function formatAnsiOutput(text) { + // Simple ANSI to HTML conversion + // Remove ANSI codes for now (can be enhanced with proper library) + return text + .replace(/\x1b\[[0-9;]*m/g, '') // Remove ANSI color codes + .replace(/\x1b\[[0-9;]*K/g, ''); // Remove ANSI erase codes +} + +/** + * Format unified diff for display + */ +function formatDiff(diff) { + if (!diff) return ''; + + const lines = diff.split('\n'); + let html = ''; + + for (const line of lines) { + let className = 'diff-line'; + if (line.startsWith('+') && !line.startsWith('+++')) { + className += ' diff-add'; + } else if (line.startsWith('-') && !line.startsWith('---')) { + className += ' diff-remove'; + } else if (line.startsWith('@@')) { + className += ' diff-hunk'; + } + + html += `
${escapeHtml(line)}
`; + } + + return html; +} + +/** + * Get icon for tool type + */ +function getToolIcon(toolName) { + const icons = { + 'bash': 'โšก', + 'edit': 'โœ๏ธ', + 'write': '๐Ÿ“', + 'read': '๐Ÿ“–', + 'websearch': '๐Ÿ”', + 'webfetch': '๐ŸŒ', + 'task': '๐Ÿค–', + 'todowrite': '๐Ÿ“‹', + 'grep': '๐Ÿ”Ž', + 'glob': '๐Ÿ“', + 'list': '๐Ÿ“‚' + }; + return icons[toolName] || '๐Ÿ”ง'; +} + +/** + * Get status icon for tool state + */ +function getStatusIcon(status) { + const icons = { + 'pending': 'โณ', + 'running': 'โšก', + 'completed': 'โœ“', + 'error': 'โœ—' + }; + return icons[status] || 'โณ'; +} + +// ============================================================ +// Public API +// ============================================================ + +/** + * Render a tool call with specialized renderer + * @param {Object} toolCall - Tool call object + * @param {string} status - Tool status (pending, running, completed, error) + * @returns {string} - HTML string + */ +function renderToolCall(toolCall, status = 'pending') { + const registry = window.toolRendererRegistry; + if (!registry) return ''; + + const renderer = registry.get(toolCall.name); + const input = toolCall.input || {}; + const output = toolCall.output; + const metadata = toolCall.metadata || {}; + const toolId = toolCall.id || `tool-${Date.now()}`; + + const icon = renderer.icon; + const action = status === 'pending' + ? renderer.getAction(input) + : renderer.getTitle(input, output, metadata); + + const body = status !== 'pending' + ? renderer.renderBody(input, output, metadata) + : ''; + + return ` +
+ +
+ ${body} +
+
+ `; +} + +/** + * Toggle tool call expand/collapse + */ +function toggleToolCall(toolId) { + const body = document.getElementById(`tool-body-${toolId}`); + const header = body?.previousElementSibling; + const expand = header?.querySelector('.tool-expand'); + + if (body && header && expand) { + const isCollapsed = body.style.display === 'none'; + body.style.display = isCollapsed ? 'block' : 'none'; + expand.textContent = isCollapsed ? 'โ–ผ' : 'โ–ถ'; + } +} + +// ============================================================ +// Initialize +// ============================================================ + +// Create global registry +window.toolRendererRegistry = new ToolRendererRegistry(); + +// Export functions +window.renderToolCall = renderToolCall; +window.toggleToolCall = toggleToolCall; + +console.log('[ToolRenderers] Tool renderer registry initialized with ' + + window.toolRendererRegistry.renderers.size + ' renderers'); diff --git a/public/claude-ide/tool-rendering.css b/public/claude-ide/tool-rendering.css new file mode 100644 index 00000000..4d712d96 --- /dev/null +++ b/public/claude-ide/tool-rendering.css @@ -0,0 +1,602 @@ +/** + * Tool Call Rendering Styles + * Inspired by CodeNomad's tool rendering + * https://github.com/NeuralNomadsAI/CodeNomad + */ + +/* ============================================================ + Tool Call Container + ============================================================ */ + +.tool-call { + border: 1px solid #333; + border-radius: 8px; + margin: 8px 0; + overflow: hidden; + background: #1a1a1a; + transition: border-color 0.2s ease; +} + +/* Status-specific border colors */ +.tool-call.tool-status-pending { + border-left: 3px solid #ffa500; +} + +.tool-call.tool-status-running { + border-left: 3px solid #4a9eff; +} + +.tool-call.tool-status-completed { + border-left: 3px solid #51cf66; +} + +.tool-call.tool-status-error { + border-left: 3px solid #ff6b6b; +} + +/* ============================================================ + Tool Header (Toggle Button) + ============================================================ */ + +.tool-header { + width: 100%; + display: flex; + align-items: center; + gap: 8px; + padding: 10px 12px; + background: #222; + border: none; + cursor: pointer; + color: #e0e0e0; + transition: background 0.2s ease; +} + +.tool-header:hover { + background: #2a2a2a; +} + +.tool-expand { + font-size: 10px; + transition: transform 0.2s ease; + min-width: 12px; + text-align: center; +} + +.tool-icon { + font-size: 16px; + line-height: 1; +} + +.tool-action { + flex: 1; + text-align: left; + font-size: 13px; + font-weight: 500; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.tool-status { + font-size: 14px; + line-height: 1; +} + +/* ============================================================ + Tool Body Content + ============================================================ */ + +.tool-body { + padding: 12px; + border-top: 1px solid #333; + background: #1a1a1a; + font-size: 13px; + line-height: 1.5; + max-height: 400px; + overflow-y: auto; +} + +/* Scrollbar styling */ +.tool-body::-webkit-scrollbar { + width: 8px; +} + +.tool-body::-webkit-scrollbar-track { + background: #1a1a1a; +} + +.tool-body::-webkit-scrollbar-thumb { + background: #444; + border-radius: 4px; +} + +.tool-body::-webkit-scrollbar-thumb:hover { + background: #555; +} + +/* ============================================================ + Bash Tool + ============================================================ */ + +.tool-bash { + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; +} + +.bash-command { + color: #51cf66; + margin-bottom: 8px; + font-weight: 500; +} + +.bash-output { + color: #e0e0e0; + white-space: pre-wrap; + word-break: break-all; + margin-bottom: 8px; +} + +.bash-exit { + color: #888; + font-size: 12px; + padding-top: 4px; + border-top: 1px solid #333; +} + +/* ============================================================ + Edit Tool + ============================================================ */ + +.tool-edit { + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; +} + +.edit-file { + color: #4a9eff; + margin-bottom: 8px; + font-weight: 500; +} + +.edit-stats { + color: #888; + font-size: 12px; + margin-bottom: 8px; +} + +.edit-diff { + font-size: 12px; + line-height: 1.4; +} + +/* Diff rendering */ +.diff-line { + padding: 2px 4px; + white-space: pre-wrap; + word-break: break-all; +} + +.diff-add { + background: rgba(81, 207, 102, 0.15); + color: #51cf66; +} + +.diff-remove { + background: rgba(255, 107, 107, 0.15); + color: #ff6b6b; +} + +.diff-hunk { + color: #888; + background: rgba(136, 136, 136, 0.1); +} + +/* ============================================================ + Write Tool + ============================================================ */ + +.tool-write { + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; +} + +.write-file { + color: #a78bfa; + margin-bottom: 8px; + font-weight: 500; +} + +.write-stats { + color: #888; + font-size: 12px; + margin-bottom: 8px; +} + +.write-preview { + margin-top: 8px; + padding: 8px; + background: #222; + border-radius: 4px; + font-size: 12px; +} + +.write-preview pre { + margin: 0; + white-space: pre-wrap; + word-break: break-all; +} + +/* ============================================================ + Read Tool + ============================================================ */ + +.tool-read { + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; +} + +.read-file { + color: #4a9eff; + margin-bottom: 8px; + font-weight: 500; +} + +.read-preview { + margin-top: 8px; + padding: 8px; + background: #222; + border-radius: 4px; + font-size: 12px; +} + +.read-preview pre { + margin: 0; + white-space: pre-wrap; + word-break: break-all; +} + +.read-stats { + color: #888; + font-size: 12px; + margin-top: 4px; +} + +/* ============================================================ + Search Results (WebSearch, Grep) + ============================================================ */ + +.tool-search-results, +.tool-grep-results { + display: flex; + flex-direction: column; + gap: 8px; +} + +.search-result, +.grep-match { + padding: 8px; + background: #222; + border-radius: 4px; +} + +.search-result a { + color: #4a9eff; + text-decoration: none; + font-weight: 500; +} + +.search-result a:hover { + text-decoration: underline; +} + +.result-snippet { + color: #888; + font-size: 12px; + margin-top: 4px; +} + +.grep-file { + color: #4a9eff; + font-size: 12px; + font-weight: 500; +} + +.grep-line { + color: #e0e0e0; + font-size: 12px; + margin-top: 2px; + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; +} + +.grep-stats { + color: #888; + font-size: 12px; + text-align: center; + padding: 4px; +} + +/* ============================================================ + WebFetch Tool + ============================================================ */ + +.tool-fetch { + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; +} + +.fetch-url { + margin-bottom: 8px; +} + +.fetch-url a { + color: #4a9eff; + text-decoration: none; + word-break: break-all; +} + +.fetch-url a:hover { + text-decoration: underline; +} + +.fetch-preview { + margin-top: 8px; + padding: 8px; + background: #222; + border-radius: 4px; + font-size: 12px; +} + +.fetch-preview pre { + margin: 0; + white-space: pre-wrap; + word-break: break-all; +} + +.fetch-stats { + color: #888; + font-size: 12px; + margin-top: 4px; +} + +/* ============================================================ + Task/Agent Delegation + ============================================================ */ + +.tool-task-summary { + display: flex; + flex-direction: column; + gap: 6px; +} + +.task-item { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 8px; + background: #222; + border-radius: 4px; + font-size: 12px; +} + +.task-icon { + font-size: 14px; +} + +.task-desc { + color: #e0e0e0; +} + +.tool-task-result { + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 12px; +} + +.tool-task-result pre { + white-space: pre-wrap; + word-break: break-all; +} + +/* ============================================================ + Todo/Plan Tool + ============================================================ */ + +.tool-todos { + display: flex; + flex-direction: column; + gap: 6px; +} + +.todo-item { + display: flex; + align-items: flex-start; + gap: 8px; + padding: 6px 8px; + background: #222; + border-radius: 4px; + font-size: 13px; +} + +.todo-checkbox { + font-size: 14px; + line-height: 1.4; +} + +.todo-text { + flex: 1; + word-break: break-word; +} + +/* Todo states */ +.todo-completed .todo-text { + text-decoration: line-through; + color: #888; +} + +.todo-in_progress .todo-text { + color: #4a9eff; + font-weight: 500; +} + +/* ============================================================ + Glob Tool + ============================================================ */ + +.tool-glob-results { + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; +} + +.glob-stats { + color: #888; + font-size: 12px; + margin-bottom: 8px; +} + +.glob-files { + display: flex; + flex-direction: column; + gap: 4px; +} + +.glob-file { + color: #e0e0e0; + font-size: 12px; + padding: 4px 8px; + background: #222; + border-radius: 4px; +} + +.glob-more { + color: #888; + font-size: 12px; + text-align: center; + padding: 8px; +} + +/* ============================================================ + List Tool + ============================================================ */ + +.tool-list-results { + display: flex; + flex-direction: column; + gap: 4px; +} + +.list-entry { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 8px; + background: #222; + border-radius: 4px; + font-size: 13px; +} + +.entry-icon { + font-size: 14px; +} + +.entry-name { + color: #e0e0e0; +} + +.list-more { + color: #888; + font-size: 12px; + text-align: center; + padding: 8px; +} + +/* ============================================================ + Patch Tool + ============================================================ */ + +.tool-patch { + text-align: center; + padding: 12px; +} + +.patch-applied { + color: #51cf66; + font-weight: 500; +} + +/* ============================================================ + Default Tool + ============================================================ */ + +.tool-default { + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; +} + +.tool-default pre { + margin: 0; + white-space: pre-wrap; + word-break: break-all; + font-size: 12px; +} + +/* ============================================================ + Empty State + ============================================================ */ + +.tool-empty { + color: #888; + font-style: italic; + text-align: center; + padding: 12px; +} + +/* ============================================================ + Responsive Design + ============================================================ */ + +@media (max-width: 768px) { + .tool-header { + padding: 8px 10px; + } + + .tool-action { + font-size: 12px; + } + + .tool-body { + padding: 10px; + font-size: 12px; + } + + .bash-command, + .edit-file, + .write-file, + .read-file { + font-size: 12px; + } +} + +/* ============================================================ + Animations + ============================================================ */ + +@keyframes toolExpand { + from { + opacity: 0; + transform: translateY(-4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.tool-body { + animation: toolExpand 0.15s ease-out; +} + +/* Status pulse for running tools */ +@keyframes statusPulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.6; + } +} + +.tool-status-running .tool-status { + animation: statusPulse 1.5s ease-in-out infinite; +}