BashTool
Render a command tool card. Provide input.command and optional output.stdout; use input.approval for the footer.
Getting Started
$pnpm add @sinups/ai-kit @mantine/core @mantine/hooks @tabler/icons-reactExamples
Terminal card
Running state
Source
The component source as it ships in the package. Useful as a reference, or as a starting point if you want to fork a piece.
tools/BashTool.tsx
import React, { memo, useMemo } from 'react';
import { Box } from '@mantine/core';
import { useToolComplete } from '../hooks/use-tool-complete';
import { IconThinSpinner } from '../icons';
import { TextShimmer } from '../TextShimmer/TextShimmer';
import type { ToolPart } from '../types';
import type { StepState, ToolCallStep } from '../types/timeline';
import { cx } from '../utils/cx';
import { getPartInput } from '../utils/format-tool';
import { getBashRunInfo, type BashRunInfo } from './bash-output';
import { ShellOutput } from './ShellOutput';
import { ToolApprovalFooter, type ToolApproval } from './ToolApprovalFooter';
import { noopComplete, useToolStep } from './use-tool-step';
import classes from './BashTool.module.css';
function extractCommandSummary(cmd: string): string {
return cmd
.split('|')
.map((s) => s.trim().split(/\s+/)[0] ?? '')
.filter(Boolean)
.slice(0, 4)
.join(', ');
}
function toSingleLine(command: string): string {
return command.replace(/\s+/g, ' ').trim();
}
export interface BashToolTerminalCardProps {
/** Timeline step describing the tool call */
step: ToolCallStep;
/** Animation state of the step, the card shows shimmer while `animating` */
state: StepState;
/** Called once `step.duration` elapses while animating */
onComplete: () => void;
/** When set, renders `ToolApprovalFooter` with approve/reject buttons under the card */
approval?: ToolApproval;
/** Output and run metadata used by `withOutputMeta` and `formatOutput`, which also stream `run.output` while the command runs */
run?: BashRunInfo;
/** Output lines shown from the end with `formatOutput`, `8` by default */
maxOutputLines?: number;
/** Shows exit code, duration, timeout, size and a copy button above the output, `false` by default */
withOutputMeta?: boolean;
/** Renders ANSI colors, clickable links, pretty JSON and a Show all toggle in the output */
formatOutput?: boolean;
/** Header text: `short` lists the programs of a pipeline (`ls, grep`), `full` shows the whole command on one line, `short` by default */
commandSummary?: 'short' | 'full';
/** Class name added to the root element */
className?: string;
/** Inline styles added to the root element */
style?: React.CSSProperties;
}
/** Terminal-style card showing the command and (once finished) its output */
export function BashToolTerminalCard({
step,
state,
onComplete,
approval,
run,
maxOutputLines = 8,
withOutputMeta = false,
formatOutput = false,
commandSummary = 'short',
className,
style,
}: BashToolTerminalCardProps) {
useToolComplete(state === 'animating', step.duration, onComplete);
const isPending = state === 'animating';
const command = step.bashCommand ?? step.toolDetail;
const summary =
commandSummary === 'full' ? toSingleLine(command) : extractCommandSummary(command);
const rich = withOutputMeta || formatOutput;
const output = rich
? (run?.output ?? (isPending ? undefined : step.bashOutput))
: isPending
? undefined
: (step.bashOutput ?? run?.output);
return (
<Box className={cx(classes.card, className)} style={style}>
<div className={classes.header}>
<div
className={classes.headerContent}
title={commandSummary === 'full' ? summary : undefined}
>
{isPending ? (
<TextShimmer as="span" duration={1.2} className={classes.shimmer}>
Running command: {summary}
</TextShimmer>
) : (
<span className={classes.title}>Ran command: {summary}</span>
)}
</div>
{isPending && <IconThinSpinner size={12} className={classes.spinner} />}
</div>
<div className={classes.body}>
<div className={classes.commandLine}>
<span className={classes.prompt}>$ </span>
<span className={classes.command}>{command}</span>
</div>
{rich
? (Boolean(output) || (withOutputMeta && !isPending && run?.exitCode !== undefined)) && (
<ShellOutput
variant="compact"
className={classes.rich}
output={output ?? ''}
live={isPending}
maxLines={formatOutput ? maxOutputLines : 0}
formatJson={formatOutput}
withMeta={withOutputMeta}
withCopy={withOutputMeta}
exitCode={isPending ? undefined : run?.exitCode}
durationMs={run?.durationMs}
startedAt={run?.startedAt}
timeoutMs={run?.timeoutMs}
sizeBytes={run?.sizeBytes}
/>
)
: output && <div className={classes.output}>{output}</div>}
</div>
{approval && (
<ToolApprovalFooter
isPending={isPending}
isComplete={Boolean(approval.hideWhenComplete) && state === 'complete'}
{...approval}
/>
)}
</Box>
);
}
export interface BashToolProps {
/** Tool part in AI SDK v5 shape: `{ type, toolCallId, state, input, output }` */
part: ToolPart;
/** Shows exit code, duration, timeout, size and a copy button above the output, `false` by default */
withOutputMeta?: boolean;
/** Renders ANSI colors, clickable links, pretty JSON and a Show all toggle in the output */
formatOutput?: boolean;
/** Header text: `short` lists the programs of a pipeline (`ls, grep`), `full` shows the whole command on one line, `short` by default */
commandSummary?: 'short' | 'full';
/** Class name added to the root element */
className?: string;
/** Inline styles added to the root element */
style?: React.CSSProperties;
}
/** Renders a `tool-Bash` part as a terminal card */
export const BashTool = memo(function BashTool({
part,
withOutputMeta,
formatOutput,
commandSummary,
className,
style,
}: BashToolProps) {
const input = getPartInput(part);
const approval = input.approval as ToolApproval | undefined;
const { step, stepState } = useToolStep(part, 'Bash', 'bash');
const run = useMemo(() => getBashRunInfo(part), [part]);
return (
<BashToolTerminalCard
step={step}
state={stepState}
onComplete={noopComplete}
approval={approval}
run={run}
withOutputMeta={withOutputMeta}
formatOutput={formatOutput}
commandSummary={commandSummary}
className={className}
style={style}
/>
);
});
BashTool.displayName = 'BashTool';
Depends on:
API reference
Prop
Type
Required
part
ToolPart
Yes
Tool part in AI SDK v5 shape: `{ type, toolCallId, state, input, output }`
withOutputMeta
boolean
No
Shows exit code, duration, timeout, size and a copy button above the output, `false` by default
formatOutput
boolean
No
Renders ANSI colors, clickable links, pretty JSON and a Show all toggle in the output
commandSummary
'short' | 'full'
No
Header text: `short` lists the programs of a pipeline (`ls, grep`), `full` shows the whole command on one line, `short` by default
className
string
No
Class name added to the root element
style
React.CSSProperties
No
Inline styles added to the root element