forked from 777genius/claude-code-source-code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathToolBash.tsx
More file actions
157 lines (145 loc) · 4.58 KB
/
Copy pathToolBash.tsx
File metadata and controls
157 lines (145 loc) · 4.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
"use client";
import { useState } from "react";
import { Copy, Check, Clock } from "lucide-react";
import { cn } from "@/lib/utils";
import { AnsiRenderer } from "./AnsiRenderer";
import { ToolUseBlock } from "./ToolUseBlock";
interface ToolBashProps {
input: {
command: string;
timeout?: number;
description?: string;
};
result?: string;
isError?: boolean;
isRunning?: boolean;
startedAt?: number;
completedAt?: number;
}
function CopyButton({ text }: { text: string }) {
const [copied, setCopied] = useState(false);
return (
<button
onClick={() => {
navigator.clipboard.writeText(text).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
});
}}
className="p-1 rounded text-surface-400 hover:text-surface-200 hover:bg-surface-700 transition-colors"
title="Copy command"
>
{copied ? (
<Check className="w-3.5 h-3.5 text-green-400" />
) : (
<Copy className="w-3.5 h-3.5" />
)}
</button>
);
}
// Parse exit code from result — bash tool often appends it
function parseExitCode(result: string): number | null {
const match = result.match(/\nExit code: (\d+)\s*$/);
if (match) return parseInt(match[1], 10);
return null;
}
function stripExitCode(result: string): string {
return result.replace(/\nExit code: \d+\s*$/, "");
}
const MAX_OUTPUT_LINES = 200;
export function ToolBash({
input,
result,
isError = false,
isRunning = false,
startedAt,
completedAt,
}: ToolBashProps) {
const [showAll, setShowAll] = useState(false);
const exitCode = result ? parseExitCode(result) : null;
const outputText = result ? stripExitCode(result) : "";
const outputLines = outputText.split("\n");
const isTruncated = !showAll && outputLines.length > MAX_OUTPUT_LINES;
const displayOutput = isTruncated
? outputLines.slice(0, MAX_OUTPUT_LINES).join("\n")
: outputText;
// Determine if it's an error (non-zero exit code or isError prop)
const hasError = isError || (exitCode !== null && exitCode !== 0);
return (
<ToolUseBlock
toolName="bash"
toolInput={input}
toolResult={result}
isError={hasError}
isRunning={isRunning}
startedAt={startedAt}
completedAt={completedAt}
>
{/* Command display */}
<div className="flex items-center gap-2 px-3 py-2 bg-surface-850 border-b border-surface-700/50">
<span className="text-brand-400 font-mono text-xs select-none">$</span>
<code className="font-mono text-xs text-surface-100 flex-1 break-all">
{input.command}
</code>
<div className="flex items-center gap-1 flex-shrink-0">
{input.timeout && (
<span
className="flex items-center gap-1 text-xs text-surface-500"
title={`Timeout: ${input.timeout}ms`}
>
<Clock className="w-3 h-3" />
{(input.timeout / 1000).toFixed(0)}s
</span>
)}
<CopyButton text={input.command} />
</div>
</div>
{/* Output */}
{isRunning ? (
<div className="px-3 py-3 font-mono text-xs text-brand-400 animate-pulse-soft">
Running…
</div>
) : outputText ? (
<div>
<div
className={cn(
"overflow-auto max-h-[400px] bg-[#0d0d0d] px-3 py-3",
"font-mono text-xs leading-5 whitespace-pre"
)}
>
<AnsiRenderer text={displayOutput} />
{isTruncated && (
<button
onClick={() => setShowAll(true)}
className="mt-2 block text-brand-400 hover:text-brand-300 text-xs"
>
↓ Show {outputLines.length - MAX_OUTPUT_LINES} more lines
</button>
)}
</div>
{/* Footer: exit code */}
{exitCode !== null && (
<div
className={cn(
"flex items-center gap-2 px-3 py-1.5 border-t border-surface-700/50",
exitCode === 0 ? "bg-surface-850" : "bg-red-950/20"
)}
>
<span className="text-xs text-surface-500">Exit code</span>
<span
className={cn(
"font-mono text-xs px-1.5 py-0.5 rounded",
exitCode === 0
? "bg-green-900/40 text-green-400"
: "bg-red-900/40 text-red-400"
)}
>
{exitCode}
</span>
</div>
)}
</div>
) : null}
</ToolUseBlock>
);
}