@nanocollective/nanocoder
Version:
A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter
67 lines • 3.71 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { Box, Text, useInput } from 'ink';
import { useEffect, useState } from 'react';
import ToolMessage from '../components/tool-message.js';
import { TRUNCATION_OUTPUT_LIMIT } from '../constants.js';
import { useTheme } from '../hooks/useTheme.js';
import { bashExecutor } from '../services/bash-executor.js';
import { calculateTokens } from '../utils/token-calculator.js';
export default function BashProgress({ executionId, command, completedState, isLive = false, }) {
const { colors } = useTheme();
// If completedState is provided, use it directly (static mode)
const [state, setState] = useState(completedState ?? {
executionId,
command,
outputPreview: '',
fullOutput: '',
stderr: '',
isComplete: false,
exitCode: null,
error: null,
});
// Subscribe to bash executor events (only if not in static mode)
useEffect(() => {
// Skip event subscription if we have a completed state
if (completedState)
return;
const handleUpdate = (update) => {
if (update.executionId === executionId) {
setState(update);
}
};
bashExecutor.on('start', handleUpdate);
bashExecutor.on('progress', handleUpdate);
bashExecutor.on('complete', handleUpdate);
// Get initial state if execution already started
const initialState = bashExecutor.getState(executionId);
if (initialState) {
setState(initialState);
}
return () => {
bashExecutor.off('start', handleUpdate);
bashExecutor.off('progress', handleUpdate);
bashExecutor.off('complete', handleUpdate);
};
}, [executionId, completedState]);
// Handle escape key to cancel execution (only if not in static mode)
useInput((_input, key) => {
if (key.escape && !state.isComplete && !completedState) {
bashExecutor.cancel(executionId);
}
});
// Determine dot color
let dotColor = colors.secondary;
if (state.isComplete) {
dotColor =
state.exitCode === 0 && !state.error ? colors.success : colors.error;
}
// Calculate output stats for completed state (use truncated size to match what LLM receives)
const totalOutput = state.fullOutput + state.stderr;
const truncatedOutput = totalOutput.length > TRUNCATION_OUTPUT_LIMIT
? totalOutput.substring(0, TRUNCATION_OUTPUT_LIMIT)
: totalOutput;
const estimatedTokens = calculateTokens(truncatedOutput);
const messageContent = (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.tool, children: "\u2692 execute_bash" }), _jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Command: " }), _jsx(Box, { marginLeft: 1, children: _jsx(Text, { color: colors.primary, children: command }) })] }), state.isComplete && (_jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Status: " }), _jsx(Text, { color: dotColor, children: "\u25CF" })] })), !state.isComplete && state.outputPreview && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.secondary, children: "Output: " }), _jsx(Text, { color: colors.text, dimColor: true, children: state.outputPreview })] })), state.isComplete && (_jsxs(Box, { children: [_jsx(Text, { color: colors.secondary, children: "Tokens: " }), _jsxs(Text, { color: colors.text, children: ["~", estimatedTokens] })] }))] }));
return (_jsx(ToolMessage, { message: messageContent, hideBox: true, isLive: isLive }));
}
//# sourceMappingURL=bash-progress.js.map