diff --git a/apps/cli/src/components/typed-message.tsx b/apps/cli/src/components/typed-message.tsx
index f7871195a..2c8802220 100644
--- a/apps/cli/src/components/typed-message.tsx
+++ b/apps/cli/src/components/typed-message.tsx
@@ -2,6 +2,7 @@ import { Box, Text } from 'ink';
import React from 'react';
import type { AgentMessage } from '../services/analytics-engineer-handler';
import { ExecuteMessage } from './execute-message';
+import { WriteMessage } from './write-message';
interface AgentMessageComponentProps {
message: AgentMessage;
@@ -42,6 +43,10 @@ export function AgentMessageComponent({ message }: AgentMessageComponentProps) {
// For execute commands, use the ExecuteMessage component
return ;
+ case 'write':
+ // For write operations, use the WriteMessage component
+ return ;
+
default:
return null;
}
diff --git a/apps/cli/src/components/write-message.tsx b/apps/cli/src/components/write-message.tsx
new file mode 100644
index 000000000..bcfca311d
--- /dev/null
+++ b/apps/cli/src/components/write-message.tsx
@@ -0,0 +1,87 @@
+import { Box, Text, useInput } from 'ink';
+import React, { useState } from 'react';
+import path from 'node:path';
+import type { AgentMessage } from '../services/analytics-engineer-handler';
+
+interface WriteMessageProps {
+ message: Extract;
+}
+
+/**
+ * Component for displaying write file operations
+ * Shows WRITE badge, relative file path, and file content preview
+ * Supports expansion with Ctrl+O to show full content
+ */
+export function WriteMessage({ message }: WriteMessageProps) {
+ const [isExpanded, setIsExpanded] = useState(false);
+
+ // Handle Ctrl+O to toggle expansion
+ useInput((input, key) => {
+ if (key.ctrl && input === 'o') {
+ setIsExpanded((prev) => !prev);
+ }
+ });
+
+ const { args, result } = message;
+
+ // For each file, show its content
+ return (
+
+ {args.files.map((file, fileIdx) => {
+ // Get relative path from cwd
+ const relativePath = path.relative(process.cwd(), file.path);
+
+ // Split content into lines
+ const contentLines = file.content.split('\n');
+
+ // Show first 5 lines when not expanded, all lines when expanded
+ const displayLines = isExpanded ? contentLines : contentLines.slice(0, 5);
+
+ // Find the result for this file
+ const fileResult = result?.results[fileIdx];
+ const success = fileResult?.status === 'success';
+
+ return (
+
+ {/* WRITE badge with relative file path */}
+
+
+ WRITE
+
+ ({relativePath})
+
+
+ {/* File content lines - always show with indentation */}
+ {contentLines.length > 0 && (
+
+ {displayLines.map((line, idx) => (
+
+ {line}
+
+ ))}
+
+ )}
+
+ {/* Expansion hint if content is long */}
+ {contentLines.length > 5 && (
+
+
+ {isExpanded ? '(Press Ctrl+O to collapse)' : `... +${contentLines.length - 5} lines (Press Ctrl+O to expand)`}
+
+
+ )}
+
+ {/* Status line with indentation */}
+ {fileResult && (
+
+
+ ↳ {success ? `Wrote ${contentLines.length} lines` : `Failed: ${fileResult.errorMessage}`}
+
+
+ )}
+
+ );
+ })}
+
+ );
+}
diff --git a/apps/cli/src/services/analytics-engineer-handler.ts b/apps/cli/src/services/analytics-engineer-handler.ts
index 0f49778f5..354221138 100644
--- a/apps/cli/src/services/analytics-engineer-handler.ts
+++ b/apps/cli/src/services/analytics-engineer-handler.ts
@@ -36,6 +36,12 @@ export type AgentMessage =
event: 'start' | 'complete';
args: LsToolInput;
result?: LsToolOutput;
+ }
+ | {
+ kind: 'write';
+ event: 'start' | 'complete';
+ args: { files: { path: string; content: string }[] };
+ result?: { results: Array<{ status: 'success' | 'error'; filePath: string; errorMessage?: string }> };
};
export interface DocsAgentMessage {
@@ -122,6 +128,18 @@ export async function runDocsAgent(params: RunDocsAgentParams) {
},
});
}
+
+ // Handle write tool events - only show complete to avoid duplicates
+ if (event.tool === 'writeFileTool' && event.event === 'complete') {
+ onMessage({
+ message: {
+ kind: 'write',
+ event: 'complete',
+ args: event.args,
+ result: event.result, // Type-safe: WriteFileToolOutput
+ },
+ });
+ }
},
});
diff --git a/packages/ai/src/agents/analytics-engineer-agent/analytics-engineer-agent.ts b/packages/ai/src/agents/analytics-engineer-agent/analytics-engineer-agent.ts
index 5fa6d66ac..5cfbe045e 100644
--- a/packages/ai/src/agents/analytics-engineer-agent/analytics-engineer-agent.ts
+++ b/packages/ai/src/agents/analytics-engineer-agent/analytics-engineer-agent.ts
@@ -64,6 +64,7 @@ export function createAnalyticsEngineerAgent(analyticsEngineerAgentOptions: Anal
const writeFileTool = createWriteFileTool({
messageId: analyticsEngineerAgentOptions.messageId,
projectDirectory: analyticsEngineerAgentOptions.folder_structure,
+ onToolEvent: analyticsEngineerAgentOptions.onToolEvent,
});
const grepTool = createGrepTool({
messageId: analyticsEngineerAgentOptions.messageId,
diff --git a/packages/ai/src/tools/file-tools/write-file-tool/write-file-tool-execute.ts b/packages/ai/src/tools/file-tools/write-file-tool/write-file-tool-execute.ts
index 9c213f052..37444eb0b 100644
--- a/packages/ai/src/tools/file-tools/write-file-tool/write-file-tool-execute.ts
+++ b/packages/ai/src/tools/file-tools/write-file-tool/write-file-tool-execute.ts
@@ -90,11 +90,18 @@ async function createSingleFile(
*/
export function createWriteFileToolExecute(context: WriteFileToolContext) {
return async function execute(input: WriteFileToolInput): Promise {
- const { messageId, projectDirectory } = context;
+ const { messageId, projectDirectory, onToolEvent } = context;
const { files } = input;
console.info(`Creating ${files.length} file(s) for message ${messageId}`);
+ // Emit start event
+ onToolEvent?.({
+ tool: 'writeFileTool',
+ event: 'start',
+ args: input,
+ });
+
// Process all files in parallel
const fileResults = await Promise.all(
files.map((file) => createSingleFile(file.path, file.content, projectDirectory))
@@ -127,6 +134,16 @@ export function createWriteFileToolExecute(context: WriteFileToolContext) {
console.error('Failed files:', errors);
}
- return { results };
+ const output = { results };
+
+ // Emit complete event
+ onToolEvent?.({
+ tool: 'writeFileTool',
+ event: 'complete',
+ result: output,
+ args: input,
+ });
+
+ return output;
};
}
diff --git a/packages/ai/src/tools/file-tools/write-file-tool/write-file-tool.ts b/packages/ai/src/tools/file-tools/write-file-tool/write-file-tool.ts
index 71a90a22a..96748902b 100644
--- a/packages/ai/src/tools/file-tools/write-file-tool/write-file-tool.ts
+++ b/packages/ai/src/tools/file-tools/write-file-tool/write-file-tool.ts
@@ -30,6 +30,7 @@ const WriteFileToolOutputSchema = z.object({
const WriteFileToolContextSchema = z.object({
messageId: z.string().describe('The message ID for database updates'),
projectDirectory: z.string().describe('The root directory of the project'),
+ onToolEvent: z.any().optional().describe('Callback for tool events'),
});
export type WriteFileToolInput = z.infer;