diff --git a/packages/cli/src/ui/components/messages/ShellToolMessage.test.tsx b/packages/cli/src/ui/components/messages/ShellToolMessage.test.tsx
index b650ee4d9d..9e9255d3c4 100644
--- a/packages/cli/src/ui/components/messages/ShellToolMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/ShellToolMessage.test.tsx
@@ -132,6 +132,15 @@ describe('', () => {
{ status: CoreToolCallStatus.Success },
undefined,
],
+ [
+ 'renders with colorized command in body',
+ {
+ status: CoreToolCallStatus.Success,
+ args: { command: 'for i in {1..3}; do echo $i; done' },
+ description: 'Loop through numbers',
+ },
+ undefined,
+ ],
[
'renders in Error state',
{ status: CoreToolCallStatus.Error, resultDisplay: 'Error output' },
diff --git a/packages/cli/src/ui/components/messages/ShellToolMessage.tsx b/packages/cli/src/ui/components/messages/ShellToolMessage.tsx
index f34aa08bfb..3c5a09f402 100644
--- a/packages/cli/src/ui/components/messages/ShellToolMessage.tsx
+++ b/packages/cli/src/ui/components/messages/ShellToolMessage.tsx
@@ -5,7 +5,7 @@
*/
import React from 'react';
-import { Box, type DOMElement } from 'ink';
+import { Box, Text, type DOMElement } from 'ink';
import { ShellInputPrompt } from '../ShellInputPrompt.js';
import { StickyHeader } from '../StickyHeader.js';
import { useUIActions } from '../../contexts/UIActionsContext.js';
@@ -24,6 +24,9 @@ import type { ToolMessageProps } from './ToolMessage.js';
import { ACTIVE_SHELL_MAX_LINES } from '../../constants.js';
import { useAlternateBuffer } from '../../hooks/useAlternateBuffer.js';
import { useUIState } from '../../contexts/UIStateContext.js';
+import { colorizeCode } from '../../utils/CodeColorizer.js';
+import { useSettings } from '../../contexts/SettingsContext.js';
+import { theme } from '../../semantic-colors.js';
import {
type Config,
ShellExecutionService,
@@ -45,6 +48,8 @@ export const ShellToolMessage: React.FC = ({
description,
+ args,
+
resultDisplay,
status,
@@ -77,6 +82,7 @@ export const ShellToolMessage: React.FC = ({
constrainHeight,
} = useUIState();
const isAlternateBuffer = useAlternateBuffer();
+ const settings = useSettings();
const isThisShellFocused = checkIsShellFocused(
name,
@@ -165,6 +171,8 @@ export const ShellToolMessage: React.FC = ({
resultDisplay,
);
+ const shellCommand = args?.['command'];
+
return (
<>
= ({
paddingX={1}
flexDirection="column"
>
+ {typeof shellCommand === 'string' && (
+
+ $
+ {colorizeCode({
+ code: shellCommand,
+ language: 'bash',
+ maxWidth: terminalWidth - 4, // account for padding and borders
+ settings,
+ hideLineNumbers: true,
+ })}
+
+ )}
= ({
name,
description,
+ args: _args,
resultDisplay,
status,
kind,
diff --git a/packages/cli/src/ui/components/messages/__snapshots__/ShellToolMessage.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/ShellToolMessage.test.tsx.snap
index 1847b8ce67..d1b0608df8 100644
--- a/packages/cli/src/ui/components/messages/__snapshots__/ShellToolMessage.test.tsx.snap
+++ b/packages/cli/src/ui/components/messages/__snapshots__/ShellToolMessage.test.tsx.snap
@@ -331,3 +331,13 @@ exports[` > Snapshots > renders in Success state (history mo
│ Test result │
"
`;
+
+exports[` > Snapshots > renders with colorized command in body 1`] = `
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ✓ Shell Command Loop through numbers │
+│ │
+│ $ for i in {1..3}; do echo $i; done │
+│ │
+│ Test result │
+"
+`;
diff --git a/packages/cli/src/ui/hooks/toolMapping.ts b/packages/cli/src/ui/hooks/toolMapping.ts
index e06ebf5bb5..01fef7f95a 100644
--- a/packages/cli/src/ui/hooks/toolMapping.ts
+++ b/packages/cli/src/ui/hooks/toolMapping.ts
@@ -51,6 +51,7 @@ export function mapToDisplay(
parentCallId: call.request.parentCallId,
name: displayName,
description,
+ args: call.request.args,
renderOutputAsMarkdown,
};
diff --git a/packages/cli/src/ui/types.ts b/packages/cli/src/ui/types.ts
index 2f8e414a83..f86fedf554 100644
--- a/packages/cli/src/ui/types.ts
+++ b/packages/cli/src/ui/types.ts
@@ -102,6 +102,7 @@ export interface IndividualToolCallDisplay {
parentCallId?: string;
name: string;
description: string;
+ args?: Record;
resultDisplay: ToolResultDisplay | undefined;
status: CoreToolCallStatus;
// True when the tool was initiated directly by the user (slash/@/shell flows).
diff --git a/packages/core/src/tools/shell.test.ts b/packages/core/src/tools/shell.test.ts
index ace59cd7cf..7f4dd28029 100644
--- a/packages/core/src/tools/shell.test.ts
+++ b/packages/core/src/tools/shell.test.ts
@@ -658,6 +658,33 @@ describe('ShellTool', () => {
expect(shellTool.description).toMatchSnapshot();
});
+ it('should return the intent description if provided', () => {
+ const invocation = shellTool.build({
+ command: 'ls',
+ description: 'Listing files',
+ });
+ expect(invocation.getDescription()).toBe('Listing files');
+ });
+
+ it('should return the intent description with background suffix if provided', () => {
+ const invocation = shellTool.build({
+ command: 'ls',
+ description: 'Listing files',
+ is_background: true,
+ });
+ expect(invocation.getDescription()).toBe('Listing files [background]');
+ });
+
+ it('should return command and directory if intent description is not provided', () => {
+ const invocation = shellTool.build({
+ command: 'ls',
+ dir_path: 'subdir',
+ });
+ const description = invocation.getDescription();
+ expect(description).toContain('ls');
+ expect(description).toContain('[in subdir]');
+ });
+
it('should not include efficiency guidelines when disabled', () => {
mockPlatform.mockReturnValue('linux');
vi.mocked(mockConfig.getEnableShellOutputEfficiency).mockReturnValue(
diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts
index 8917d281bd..242bb4c425 100644
--- a/packages/core/src/tools/shell.ts
+++ b/packages/core/src/tools/shell.ts
@@ -73,6 +73,14 @@ export class ShellToolInvocation extends BaseToolInvocation<
}
getDescription(): string {
+ if (this.params.description) {
+ let description = this.params.description.replace(/\n/g, ' ');
+ if (this.params.is_background) {
+ description += ' [background]';
+ }
+ return description;
+ }
+
let description = `${this.params.command}`;
// append optional [in directory]
// note description is needed even if validation fails due to absolute path
@@ -81,10 +89,6 @@ export class ShellToolInvocation extends BaseToolInvocation<
} else {
description += ` [current working directory ${process.cwd()}]`;
}
- // append optional (description), replacing any line breaks with spaces
- if (this.params.description) {
- description += ` (${this.params.description.replace(/\n/g, ' ')})`;
- }
if (this.params.is_background) {
description += ' [background]';
}