From 75766dbfdc274e9768377b311f6185daae58c57c Mon Sep 17 00:00:00 2001 From: Vaayne Date: Wed, 30 Jul 2025 16:19:00 +0800 Subject: [PATCH] feat: Add POC command page structure and routing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created CommandPocPage.tsx with basic layout structure - Added POC-specific TypeScript interfaces and types - Implemented basic UI components: PocHeader, PocMessageList, PocMessageBubble, PocCommandInput, PocStatusBar - Added /command-poc route to Router.tsx - Set up component folder structure following PRD specifications 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- PRD.md | 665 ++++++++++++++ cagents/python-cli-web-poc/README.md | 90 ++ cagents/python-cli-web-poc/package-lock.json | 857 ++++++++++++++++++ cagents/python-cli-web-poc/package.json | 17 + cagents/python-cli-web-poc/public/index.html | 39 + cagents/python-cli-web-poc/public/script.js | 221 +++++ cagents/python-cli-web-poc/public/style.css | 207 +++++ cagents/python-cli-web-poc/server.js | 179 ++++ cagents/python-cli-web-poc/simple-calc.py | 29 + cagents/python-cli-web-poc/test-script.py | 39 + src/renderer/src/Router.tsx | 2 + .../src/pages/command-poc/CommandPocPage.tsx | 37 + .../components/PocCommandInput.tsx | 115 +++ .../command-poc/components/PocHeader.tsx | 48 + .../components/PocMessageBubble.tsx | 90 ++ .../command-poc/components/PocMessageList.tsx | 55 ++ .../command-poc/components/PocStatusBar.tsx | 84 ++ src/renderer/src/pages/command-poc/types.ts | 40 + 18 files changed, 2814 insertions(+) create mode 100644 PRD.md create mode 100644 cagents/python-cli-web-poc/README.md create mode 100644 cagents/python-cli-web-poc/package-lock.json create mode 100644 cagents/python-cli-web-poc/package.json create mode 100644 cagents/python-cli-web-poc/public/index.html create mode 100644 cagents/python-cli-web-poc/public/script.js create mode 100644 cagents/python-cli-web-poc/public/style.css create mode 100644 cagents/python-cli-web-poc/server.js create mode 100644 cagents/python-cli-web-poc/simple-calc.py create mode 100644 cagents/python-cli-web-poc/test-script.py create mode 100644 src/renderer/src/pages/command-poc/CommandPocPage.tsx create mode 100644 src/renderer/src/pages/command-poc/components/PocCommandInput.tsx create mode 100644 src/renderer/src/pages/command-poc/components/PocHeader.tsx create mode 100644 src/renderer/src/pages/command-poc/components/PocMessageBubble.tsx create mode 100644 src/renderer/src/pages/command-poc/components/PocMessageList.tsx create mode 100644 src/renderer/src/pages/command-poc/components/PocStatusBar.tsx create mode 100644 src/renderer/src/pages/command-poc/types.ts diff --git a/PRD.md b/PRD.md new file mode 100644 index 000000000..34ca05938 --- /dev/null +++ b/PRD.md @@ -0,0 +1,665 @@ +# Product Requirements Document (PRD) +## Cherry Studio AI Agent Command Interface + +### 1. Overview + +**Product Name**: Cherry Studio AI Agent Command Interface +**Version**: 1.0 +**Date**: July 30, 2025 + +**Vision**: Create a conversational AI Agent interface in Cherry Studio that enables users to execute shell commands through natural language interaction, with seamless communication between the renderer and main processes, providing an intelligent command execution experience. + +### 2. Scope & Objectives + +This PRD focuses on two core areas: + +#### 2.1 Core Implementation Scope +- **Renderer ↔ Main Process Communication**: Robust IPC communication for command execution +- **Shell Command Execution**: Safe and efficient shell command processing in the main process +- **Real-time Output Streaming**: Live command output display integrated into chat interface +- **AI Agent Integration**: Natural language command interpretation and execution workflow + +#### 2.2 UI/UX Design Scope +- **Conversational Interface Design**: Chat-like UI that fits Cherry Studio's design language +- **Command Agent Experience**: AI-powered command interpretation and execution feedback +- **Interactive Output Display**: Rich formatting of command results within chat messages +- **Responsive Design**: Consistent chat experience across different window sizes and layouts + +### 3. Technical Requirements + +#### 3.1 Core Implementation Requirements + +##### 3.1.1 IPC Communication Architecture +**Requirement**: Establish bidirectional communication between renderer and main processes for AI Agent command execution + +**Technical Specifications**: +- **Agent Command Request Flow**: Renderer → Main Process + ```typescript + interface AgentCommandRequest { + id: string + messageId: string // Chat message ID for correlation + command: string + workingDirectory?: string + timeout?: number + environment?: Record + context?: string // Additional context from chat conversation + } + ``` + +- **Agent Output Streaming Flow**: Main Process → Renderer + ```typescript + interface AgentCommandOutput { + id: string + messageId: string // Chat message ID for correlation + type: 'stdout' | 'stderr' | 'exit' | 'error' | 'progress' + data: string + exitCode?: number + timestamp: number + } + ``` + +- **IPC Channel Names**: + - `agent-command-execute` (Renderer → Main) + - `agent-command-output` (Main → Renderer) + - `agent-command-interrupt` (Renderer → Main) + +##### 3.1.2 Main Process Agent Command Service +**Requirement**: Create a new `AgentCommandService` in the main process + +**Technical Specifications**: +- **Service Location**: `src/main/services/AgentCommandService.ts` +- **Core Methods**: + ```typescript + class AgentCommandService { + executeCommand(request: AgentCommandRequest): Promise + interruptCommand(commandId: string): Promise + getRunningCommands(): string[] + setWorkingDirectory(path: string): void + formatCommandOutput(output: string, type: string): string + } + ``` + +- **Process Management**: + - Use Node.js `child_process.spawn()` for command execution + - Support real-time stdout/stderr streaming to chat interface + - Handle process interruption via chat commands + - Maintain working directory state per agent session + - Format output for better chat display (tables, JSON, etc.) + +- **Error Handling**: + - Command not found errors with helpful suggestions + - Permission denied errors with explanations + - Timeout handling with progress updates + - Process termination with cleanup notifications + +##### 3.1.3 Renderer Process Integration +**Requirement**: Implement AI Agent command functionality in the renderer process + +**Technical Specifications**: +- **Service Location**: `src/renderer/src/services/AgentCommandService.ts` +- **Component Integration**: Agent chat page and command execution components +- **State Management**: Chat session state, command history, output formatting +- **Message Correlation**: Link command outputs to specific chat messages + +#### 3.2 Performance Requirements +- **Command Response Time**: < 100ms for command initiation +- **Output Streaming Latency**: < 50ms for real-time output display +- **Memory Management**: Efficient handling of large command outputs (>10MB) +- **Concurrent Commands**: Support up to 5 simultaneous command executions + +#### 3.3 Security Requirements +- **Command Validation**: Basic validation for dangerous commands +- **Working Directory Restrictions**: Respect file system permissions +- **Environment Variable Handling**: Secure handling of environment variables +- **Process Isolation**: Commands run with application user privileges + +### 4. UI/UX Design Requirements + +#### 4.1 Design Principles +**Target Audience**: Senior Frontend and UI Designers +**Design Goals**: Create an intuitive, conversational AI Agent interface that enhances developer productivity through natural language command execution + +##### 4.1.1 Visual Design Requirements +- **Design System Integration**: Follow Cherry Studio's existing chat design patterns +- **Theme Support**: Light/dark theme compatibility +- **Typography**: Mix of regular chat font and monospace for command outputs +- **Color Scheme**: Distinct styling for user messages, agent responses, and command outputs +- **Message Bubbles**: Clear visual distinction between conversation and command execution + +##### 4.1.2 Layout Requirements +**Primary Layout Structure** (Chat Interface): +``` +┌─────────────────────────────────────┐ +│ Agent Header (name + status + controls) │ +├─────────────────────────────────────┤ +│ │ +│ Chat Messages Area │ +│ (user messages + agent replies │ +│ + command outputs) │ +│ │ +├─────────────────────────────────────┤ +│ Message Input (natural language) │ +└─────────────────────────────────────┘ +``` + +**Responsive Considerations**: +- Minimum width: 320px (mobile) +- Optimal width: 600-800px (desktop) +- Message bubbles adapt to content width +- Command outputs can expand full width + +##### 4.1.3 Component Specifications + +**Agent Header Component**: +- Agent name and avatar +- Working directory indicator +- Active command status (running/idle) +- Session controls (clear chat, export logs) + +**Chat Messages Component**: +- **User Messages**: Standard chat bubbles for natural language input +- **Agent Responses**: AI responses explaining commands or asking for clarification +- **Command Execution Messages**: Special formatting for: + - Command being executed (with syntax highlighting) + - Real-time output streaming (scrollable, copyable) + - Execution status (success/error/interrupted) + - Formatted results (tables, JSON, file listings) + +**Message Input Component**: +- Natural language input field +- Send button with loading state during command execution +- Suggestion chips for common requests +- Support for follow-up questions and command modifications + +#### 4.2 User Experience Requirements + +##### 4.2.1 Interaction Patterns +**Conversational Flow**: +- User types natural language requests ("list files in src directory") +- Agent interprets and confirms command before execution +- Real-time command output appears in chat +- User can ask follow-up questions or modify commands + +**Keyboard Shortcuts**: +- `Enter`: Send message/command +- `Ctrl+Enter`: Force command execution without confirmation +- `Ctrl+K`: Interrupt running command +- `Ctrl+L`: Clear chat history +- `↑/↓`: Navigate message input history + +**Mouse Interactions**: +- Click on command outputs to copy +- Click on file paths to open in Cherry Studio +- Hover over commands for quick actions (copy, re-run, modify) + +##### 4.2.2 Feedback & Status Indicators +**Visual Feedback Requirements**: +- **Agent Thinking**: Typing indicator while processing user request +- **Command Execution**: Progress indicator and real-time output streaming +- **Execution Status**: Success/error/warning indicators in message bubbles +- **Working Directory**: Persistent display in agent header +- **Command History**: Visual indication of previous commands in chat + +##### 4.2.3 Accessibility Requirements +- **Keyboard Navigation**: Full chat functionality accessible via keyboard +- **Screen Reader Support**: Proper ARIA labels for chat messages and command outputs +- **High Contrast**: Support for high contrast themes in all message types +- **Focus Management**: Logical tab order through chat interface + +#### 4.3 Advanced UX Features (Future Considerations) +- **Command Suggestions**: AI-powered suggestions based on current context +- **Smart Output Formatting**: Automatic formatting for JSON, tables, logs, etc. +- **File Integration**: Deep integration with Cherry Studio's file management +- **Session Memory**: Agent remembers context across chat sessions +- **Multi-step Workflows**: Support for complex, multi-command operations + +### 5. Implementation Approach + +#### 5.1 Development Phases +**Phase 1: Core Infrastructure** (2-3 weeks) +- Implement AgentCommandService in main process +- Establish IPC communication for chat-command flow +- Basic command execution and output streaming to chat interface + +**Phase 2: AI Agent Chat Interface** (3-4 weeks) +- Design and implement conversational chat components +- Create command execution message types and formatting +- Integrate natural language command interpretation +- Implement real-time output streaming in chat bubbles + +**Phase 3: Enhanced Agent Features** (2-3 weeks) +- Add command confirmation and clarification flows +- Implement smart output formatting (tables, JSON, etc.) +- Add working directory management in chat context +- Integrate with Cherry Studio's existing AI infrastructure + +#### 5.2 Integration Points +- **Router Integration**: Add `/agent` or `/command-agent` route to `src/renderer/src/Router.tsx` +- **Navigation**: Add agent icon to Cherry Studio's main navigation +- **AI Core Integration**: Leverage existing AI infrastructure for command interpretation +- **Settings Integration**: Agent preferences in application settings +- **Chat System**: Reuse existing chat components and patterns from Cherry Studio + +### 6. Success Metrics + +#### 6.1 Technical Metrics +- Command execution success rate: >99% +- Average command response time: <100ms +- Output streaming latency: <50ms +- Zero memory leaks during extended usage + +#### 6.2 User Experience Metrics +- User adoption rate within first month +- Average chat session duration +- Natural language command interpretation accuracy +- Command execution success rate through conversational interface +- User feedback scores on AI Agent usability and helpfulness + +### 7. Dependencies & Constraints + +#### 7.1 Technical Dependencies +- Node.js `child_process` module +- Electron IPC capabilities +- Cherry Studio's existing service architecture +- React/TypeScript frontend stack +- Cherry Studio's AI Core infrastructure +- Existing chat components and design system + +#### 7.2 Platform Constraints +- Cross-platform compatibility (Windows, macOS, Linux) +- Shell availability on target platforms +- File system permission handling + +--- + +## 8. Proof of Concept (POC) Implementation + +### 8.1 POC Objectives + +**Primary Goal**: Validate the core concept of chat-based command execution with minimal implementation complexity. + +**Key Validation Points**: +- User experience of command execution through chat interface +- Technical feasibility of IPC communication for real-time output streaming +- Performance characteristics of command output display in chat bubbles +- Cross-platform compatibility of basic shell command execution + +### 8.2 POC Scope & Limitations + +#### 8.2.1 Included Features +✅ **Direct Command Execution**: Users type shell commands directly (no AI interpretation) +✅ **Real-time Output Streaming**: Command output appears live in chat bubbles +✅ **Basic Chat Interface**: Simple message list with input field +✅ **Command History**: Navigate previous commands with arrow keys +✅ **Cross-platform Support**: Works on Windows, macOS, and Linux +✅ **Process Management**: Start/stop command execution + +#### 8.2.2 Excluded Features (Future Work) +❌ AI natural language interpretation of commands +❌ Command confirmation or clarification flows +❌ Advanced output formatting (tables, JSON highlighting) +❌ Security validation and command filtering +❌ Session persistence between app restarts +❌ Multiple concurrent command execution +❌ Working directory management UI +❌ Integration with Cherry Studio's AI core + +### 8.3 Technical Architecture + +#### 8.3.1 Component Structure +``` +src/renderer/src/pages/command-poc/ +├── CommandPocPage.tsx # Main container component +├── components/ +│ ├── PocHeader.tsx # Header with working directory +│ ├── PocMessageList.tsx # Scrollable message container +│ ├── PocMessageBubble.tsx # Individual message display +│ ├── PocCommandInput.tsx # Command input with history +│ └── PocStatusBar.tsx # Command execution status +├── hooks/ +│ ├── usePocMessages.ts # Message state management +│ ├── usePocCommand.ts # Command execution logic +│ └── useCommandHistory.ts # Input history navigation +└── types.ts # POC-specific TypeScript interfaces +``` + +#### 8.3.2 Data Structures +```typescript +interface PocMessage { + id: string + type: 'user-command' | 'output' | 'error' | 'system' + content: string + timestamp: number + commandId?: string // Links output to originating command + isComplete: boolean // For streaming messages +} + +interface PocCommandExecution { + id: string + command: string + startTime: number + endTime?: number + exitCode?: number + isRunning: boolean +} +``` + +#### 8.3.3 IPC Communication +```typescript +// Renderer → Main Process +interface PocExecuteCommandRequest { + id: string + command: string + workingDirectory: string +} + +// Main Process → Renderer +interface PocCommandOutput { + commandId: string + type: 'stdout' | 'stderr' | 'exit' | 'error' + data: string + exitCode?: number +} + +// IPC Channels +const IPC_CHANNELS = { + EXECUTE_COMMAND: 'poc-execute-command', + COMMAND_OUTPUT: 'poc-command-output', + INTERRUPT_COMMAND: 'poc-interrupt-command' +} +``` + +### 8.4 Implementation Details + +#### 8.4.1 Main Process Implementation +**File**: `src/main/poc/commandExecutor.ts` +```typescript +class PocCommandExecutor { + private activeProcesses = new Map() + + executeCommand(request: PocExecuteCommandRequest) { + const { spawn } = require('child_process') + const shell = process.platform === 'win32' ? 'cmd' : 'bash' + const args = process.platform === 'win32' ? ['/c'] : ['-c'] + + const child = spawn(shell, [...args, request.command], { + cwd: request.workingDirectory + }) + + this.activeProcesses.set(request.id, child) + + // Stream output handling + child.stdout.on('data', (data) => { + this.sendOutput(request.id, 'stdout', data.toString()) + }) + + child.stderr.on('data', (data) => { + this.sendOutput(request.id, 'stderr', data.toString()) + }) + + child.on('close', (code) => { + this.sendOutput(request.id, 'exit', '', code) + this.activeProcesses.delete(request.id) + }) + } +} +``` + +#### 8.4.2 Renderer Process Implementation +**State Management Strategy**: +```typescript +const usePocMessages = () => { + const [messages, setMessages] = useState([]) + const [activeCommand, setActiveCommand] = useState(null) + + const addUserCommand = (command: string) => { + const commandMessage: PocMessage = { + id: uuid(), + type: 'user-command', + content: command, + timestamp: Date.now(), + isComplete: true + } + + const outputMessage: PocMessage = { + id: uuid(), + type: 'output', + content: '', + timestamp: Date.now(), + commandId: commandMessage.id, + isComplete: false + } + + setMessages(prev => [...prev, commandMessage, outputMessage]) + return outputMessage.id + } + + const appendOutput = (messageId: string, data: string) => { + setMessages(prev => prev.map(msg => + msg.id === messageId + ? { ...msg, content: msg.content + data } + : msg + )) + } +} +``` + +**Output Streaming with Buffering**: +```typescript +const useOutputBuffer = () => { + const bufferRef = useRef('') + const timeoutRef = useRef() + + const bufferOutput = (data: string, messageId: string) => { + bufferRef.current += data + + clearTimeout(timeoutRef.current) + timeoutRef.current = setTimeout(() => { + appendOutput(messageId, bufferRef.current) + bufferRef.current = '' + }, 100) // 100ms debounce + } +} +``` + +#### 8.4.3 UI Components +**Message Bubble Component**: +```typescript +const PocMessageBubble: React.FC<{ message: PocMessage }> = ({ message }) => { + const isUserCommand = message.type === 'user-command' + + return ( + + {isUserCommand ? ( + + $ + {message.content} + + ) : ( + +
{message.content}
+ {!message.isComplete && } +
+ )} +
+ ) +} +``` + +**Command Input with History**: +```typescript +const PocCommandInput: React.FC = ({ onSendCommand }) => { + const [input, setInput] = useState('') + const { history, addToHistory, navigateHistory } = useCommandHistory() + + const handleKeyDown = (e: React.KeyboardEvent) => { + switch (e.key) { + case 'Enter': + if (input.trim()) { + onSendCommand(input.trim()) + addToHistory(input.trim()) + setInput('') + } + break + case 'ArrowUp': + e.preventDefault() + setInput(navigateHistory('up')) + break + case 'ArrowDown': + e.preventDefault() + setInput(navigateHistory('down')) + break + } + } +} +``` + +### 8.5 Cross-Platform Considerations + +#### 8.5.1 Shell Detection +```typescript +const getShellConfig = () => { + switch (process.platform) { + case 'win32': + return { shell: 'cmd', args: ['/c'] } + case 'darwin': + case 'linux': + return { shell: 'bash', args: ['-c'] } + default: + return { shell: 'sh', args: ['-c'] } + } +} +``` + +#### 8.5.2 Path Handling +```typescript +const normalizeWorkingDirectory = (path: string) => { + return process.platform === 'win32' + ? path.replace(/\//g, '\\') + : path.replace(/\\/g, '/') +} +``` + +### 8.6 Performance Optimizations + +#### 8.6.1 Virtual Scrolling +```typescript +const PocMessageList: React.FC = ({ messages }) => { + const [visibleRange, setVisibleRange] = useState({ start: 0, end: 50 }) + + // Only render visible messages for large message lists + const visibleMessages = messages.slice( + visibleRange.start, + visibleRange.end + ) + + return ( + + {visibleMessages.map(message => ( + + ))} + + ) +} +``` + +#### 8.6.2 Output Truncation +```typescript +const MAX_OUTPUT_LENGTH = 1024 * 1024 // 1MB per message +const MAX_TOTAL_MESSAGES = 1000 + +const truncateIfNeeded = (content: string) => { + if (content.length > MAX_OUTPUT_LENGTH) { + return content.slice(0, MAX_OUTPUT_LENGTH) + '\n\n[Output truncated...]' + } + return content +} +``` + +### 8.7 Testing Strategy + +#### 8.7.1 Manual Test Cases +1. **Basic Commands**: + - `ls -la` / `dir` (directory listing) + - `pwd` / `cd` (working directory) + - `echo "Hello World"` (simple output) + +2. **Streaming Output**: + - `ping google.com -c 5` (timed output) + - `find . -name "*.ts"` (large output) + - `npm install` (mixed stdout/stderr) + +3. **Error Scenarios**: + - `nonexistentcommand` (command not found) + - `cat /root/protected` (permission denied) + - Long-running command interruption + +4. **Cross-Platform**: + - Test on Windows, macOS, and Linux + - Verify shell detection works correctly + - Check path handling differences + +#### 8.7.2 Performance Tests +- **Large Output**: Commands generating >100MB output +- **Rapid Output**: Commands with high-frequency output +- **Memory Usage**: Monitor memory consumption during long sessions +- **UI Responsiveness**: Ensure UI remains responsive during command execution + +### 8.8 Success Criteria + +#### 8.8.1 Functional Requirements +✅ Users can execute shell commands through chat interface +✅ Command output streams in real-time to chat bubbles +✅ Command history navigation works with arrow keys +✅ Cross-platform compatibility (Windows/macOS/Linux) +✅ Process interruption works reliably + +#### 8.8.2 Performance Requirements +✅ Command execution starts within 100ms of user sending +✅ Output streaming latency < 200ms +✅ UI remains responsive with outputs up to 10MB +✅ Memory usage remains stable during extended use + +#### 8.8.3 User Experience Requirements +✅ Chat interface feels natural and intuitive +✅ Clear visual distinction between commands and output +✅ Loading indicators provide appropriate feedback +✅ Auto-scroll behavior works as expected + +### 8.9 Implementation Timeline + +**Phase 1: Core Infrastructure** (Day 1) +- Set up POC page structure and routing +- Implement basic IPC communication +- Create simple command execution in main process + +**Phase 2: Basic UI** (Day 2) +- Build message display components +- Implement command input with history +- Add basic styling and layout + +**Phase 3: Streaming & Polish** (Day 3) +- Implement real-time output streaming +- Add loading states and status indicators +- Test cross-platform compatibility + +**Phase 4: Testing & Refinement** (Day 4) +- Comprehensive manual testing +- Performance optimization +- Bug fixes and UX improvements + +**Total Estimated Time: 4 days** + +### 8.10 Migration Path to Production + +The POC provides a foundation for the full production implementation: + +1. **Component Reusability**: POC components can be enhanced rather than rewritten +2. **Architecture Validation**: IPC patterns proven in POC extend to production +3. **User Feedback**: POC enables early user testing and feedback collection +4. **Performance Baseline**: POC establishes performance expectations +5. **Cross-platform Foundation**: Platform compatibility issues resolved early + +--- + +This PRD provides a focused scope for implementing a robust AI Agent command interface that enhances Cherry Studio's development capabilities through natural language interaction, while maintaining high standards for both technical implementation and user experience design. \ No newline at end of file diff --git a/cagents/python-cli-web-poc/README.md b/cagents/python-cli-web-poc/README.md new file mode 100644 index 000000000..16006545c --- /dev/null +++ b/cagents/python-cli-web-poc/README.md @@ -0,0 +1,90 @@ +# Python CLI Web Interface POC + +A proof-of-concept web interface for running Python CLI applications with real-time input/output interaction. + +## Features + +- **Web-based Terminal**: Chat-like interface for running Python commands +- **Real-time I/O**: Bidirectional communication with Python processes +- **Interactive Input**: Support for Python scripts that require user input +- **Process Management**: Start, monitor, and kill Python processes +- **WebSocket Communication**: Low-latency real-time updates + +## Quick Start + +1. **Install dependencies**: + ```bash + npm install + ``` + +2. **Start the server**: + ```bash + npm start + ``` + +3. **Open your browser**: + - Navigate to `http://localhost:3000` + - The interface will load automatically + +## Usage Examples + +### Basic Python Commands +```bash +python --version +python -c "print('Hello World')" +``` + +### Interactive Scripts +```bash +python test-script.py +python simple-calc.py +``` + +### Test Scripts Included + +- **`test-script.py`**: Demonstrates user input, output streaming, and interactive features +- **`simple-calc.py`**: Simple calculator that takes mathematical expressions + +## How It Works + +1. **Frontend**: HTML/CSS/JS interface that looks like a terminal/chat +2. **WebSocket**: Real-time bidirectional communication +3. **Node.js Backend**: Spawns Python processes and manages I/O +4. **Process Management**: Each command runs in its own Python process + +## Architecture + +``` +┌─────────────────┐ WebSocket ┌──────────────────┐ spawn() ┌─────────────┐ +│ Web Browser │ ◄─────────────► │ Node.js Server │ ◄──────────► │ Python │ +│ (Frontend) │ │ (Backend) │ │ Process │ +└─────────────────┘ └──────────────────┘ └─────────────┘ +``` + +## Key Files + +- `server.js` - Node.js server with WebSocket support +- `public/index.html` - Web interface +- `public/script.js` - Frontend JavaScript logic +- `public/style.css` - Interface styling +- `test-script.py` - Sample interactive Python script + +## Development + +The POC supports: +- ✅ Running Python commands +- ✅ Real-time output streaming +- ✅ Interactive input handling +- ✅ Process termination +- ✅ Error handling +- ✅ Connection status monitoring +- ✅ Auto-reconnection + +## Limitations + +This is a POC with basic security. For production use, consider: +- Input sanitization +- Process sandboxing +- Authentication/authorization +- Resource limits +- Proper error handling \ No newline at end of file diff --git a/cagents/python-cli-web-poc/package-lock.json b/cagents/python-cli-web-poc/package-lock.json new file mode 100644 index 000000000..223b988a9 --- /dev/null +++ b/cagents/python-cli-web-poc/package-lock.json @@ -0,0 +1,857 @@ +{ + "name": "python-cli-web-poc", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "python-cli-web-poc", + "version": "1.0.0", + "dependencies": { + "express": "^4.18.2", + "ws": "^8.14.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/cagents/python-cli-web-poc/package.json b/cagents/python-cli-web-poc/package.json new file mode 100644 index 000000000..0cb5d2880 --- /dev/null +++ b/cagents/python-cli-web-poc/package.json @@ -0,0 +1,17 @@ +{ + "name": "python-cli-web-poc", + "version": "1.0.0", + "description": "POC for running Python CLI apps through web interface", + "main": "server.js", + "scripts": { + "start": "node server.js", + "dev": "node server.js" + }, + "dependencies": { + "express": "^4.18.2", + "ws": "^8.14.2" + }, + "engines": { + "node": ">=14.0.0" + } +} \ No newline at end of file diff --git a/cagents/python-cli-web-poc/public/index.html b/cagents/python-cli-web-poc/public/index.html new file mode 100644 index 000000000..177e7c8e9 --- /dev/null +++ b/cagents/python-cli-web-poc/public/index.html @@ -0,0 +1,39 @@ + + + + + + Python CLI Web Interface + + + +
+
+

Python CLI Web Interface

+
+ + Disconnected +
+
+ +
+
+ +
+
+ + + +
+ + +
+
+
+ + + + \ No newline at end of file diff --git a/cagents/python-cli-web-poc/public/script.js b/cagents/python-cli-web-poc/public/script.js new file mode 100644 index 000000000..6b4cd132e --- /dev/null +++ b/cagents/python-cli-web-poc/public/script.js @@ -0,0 +1,221 @@ +class PythonWebInterface { + constructor() { + this.ws = null + this.currentSessionId = null + this.isConnected = false + this.isRunning = false + + this.initializeElements() + this.setupEventListeners() + this.connect() + } + + initializeElements() { + this.statusIndicator = document.getElementById('status-indicator') + this.statusText = document.getElementById('status-text') + this.output = document.getElementById('output') + this.commandInput = document.getElementById('command-input') + this.userInput = document.getElementById('user-input') + this.userInputContainer = document.getElementById('user-input-container') + this.runBtn = document.getElementById('run-btn') + this.killBtn = document.getElementById('kill-btn') + this.sendBtn = document.getElementById('send-btn') + } + + setupEventListeners() { + this.runBtn.addEventListener('click', () => this.runCommand()) + this.killBtn.addEventListener('click', () => this.killSession()) + this.sendBtn.addEventListener('click', () => this.sendInput()) + + this.commandInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter' && !this.isRunning) { + this.runCommand() + } + }) + + this.userInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.sendInput() + } + }) + } + + connect() { + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' + const wsUrl = `${protocol}//${window.location.host}` + + this.ws = new WebSocket(wsUrl) + + this.ws.onopen = () => { + this.isConnected = true + this.updateStatus('Connected', true) + this.addSystemMessage('Connected to server') + } + + this.ws.onclose = () => { + this.isConnected = false + this.updateStatus('Disconnected', false) + this.addSystemMessage('Disconnected from server. Attempting to reconnect...') + + // Attempt to reconnect after 3 seconds + setTimeout(() => { + if (!this.isConnected) { + this.connect() + } + }, 3000) + } + + this.ws.onerror = (error) => { + this.addErrorMessage('WebSocket error occurred') + } + + this.ws.onmessage = (event) => { + this.handleMessage(JSON.parse(event.data)) + } + } + + handleMessage(data) { + switch (data.type) { + case 'command_started': + this.isRunning = true + this.currentSessionId = data.sessionId + this.updateUIState() + this.showUserInput() + this.addSystemMessage(`Started: ${data.command}`) + break + + case 'output': + this.addOutput(data.data, data.stream === 'stderr') + break + + case 'command_finished': + this.isRunning = false + this.currentSessionId = null + this.updateUIState() + this.hideUserInput() + this.addSystemMessage(`Process finished with exit code: ${data.exitCode}`) + break + + case 'error': + this.addErrorMessage(data.message) + this.isRunning = false + this.currentSessionId = null + this.updateUIState() + this.hideUserInput() + break + } + } + + runCommand() { + const command = this.commandInput.value.trim() + if (!command || !this.isConnected || this.isRunning) return + + this.addCommandMessage(command) + + const sessionId = 'session_' + Date.now() + this.ws.send( + JSON.stringify({ + type: 'run_command', + command: command, + sessionId: sessionId + }) + ) + + this.commandInput.value = '' + } + + sendInput() { + const input = this.userInput.value + if (!input || !this.currentSessionId) return + + this.addInputMessage(input) + + this.ws.send( + JSON.stringify({ + type: 'send_input', + input: input, + sessionId: this.currentSessionId + }) + ) + + this.userInput.value = '' + } + + killSession() { + if (this.currentSessionId) { + this.ws.send( + JSON.stringify({ + type: 'kill_session', + sessionId: this.currentSessionId + }) + ) + } + } + + updateStatus(text, connected) { + this.statusText.textContent = text + this.statusIndicator.className = connected ? 'status-connected' : 'status-disconnected' + } + + updateUIState() { + this.runBtn.disabled = !this.isConnected || this.isRunning + this.killBtn.disabled = !this.isRunning + this.commandInput.disabled = !this.isConnected || this.isRunning + } + + showUserInput() { + this.userInputContainer.style.display = 'flex' + this.userInput.focus() + } + + hideUserInput() { + this.userInputContainer.style.display = 'none' + } + + addMessage(content, className) { + const messageDiv = document.createElement('div') + messageDiv.className = `message ${className}` + + const timestamp = document.createElement('div') + timestamp.className = 'timestamp' + timestamp.textContent = new Date().toLocaleTimeString() + + const contentDiv = document.createElement('pre') + contentDiv.textContent = content + + messageDiv.appendChild(timestamp) + messageDiv.appendChild(contentDiv) + + this.output.appendChild(messageDiv) + this.scrollToBottom() + } + + addCommandMessage(command) { + this.addMessage(`$ ${command}`, 'command') + } + + addInputMessage(input) { + this.addMessage(`> ${input}`, 'command') + } + + addOutput(data, isError = false) { + this.addMessage(data, isError ? 'error' : 'output') + } + + addSystemMessage(message) { + this.addMessage(message, 'system') + } + + addErrorMessage(message) { + this.addMessage(`Error: ${message}`, 'error') + } + + scrollToBottom() { + this.output.scrollTop = this.output.scrollHeight + } +} + +// Initialize the interface when the page loads +document.addEventListener('DOMContentLoaded', () => { + new PythonWebInterface() +}) diff --git a/cagents/python-cli-web-poc/public/style.css b/cagents/python-cli-web-poc/public/style.css new file mode 100644 index 000000000..2df22a8f9 --- /dev/null +++ b/cagents/python-cli-web-poc/public/style.css @@ -0,0 +1,207 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: #1e1e1e; + color: #ffffff; + height: 100vh; + overflow: hidden; +} + +.container { + display: flex; + flex-direction: column; + height: 100vh; +} + +header { + background-color: #2d2d2d; + padding: 1rem 2rem; + border-bottom: 1px solid #404040; + display: flex; + justify-content: space-between; + align-items: center; +} + +header h1 { + font-size: 1.5rem; + font-weight: 600; +} + +.status { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.status-connected { + color: #4caf50; +} + +.status-disconnected { + color: #f44336; +} + +.chat-container { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.output-area { + flex: 1; + padding: 1rem 2rem; + overflow-y: auto; + background-color: #1e1e1e; + font-family: 'Courier New', monospace; + font-size: 14px; + line-height: 1.4; +} + +.message { + margin-bottom: 1rem; + padding: 0.75rem 1rem; + border-radius: 8px; + max-width: 100%; + word-wrap: break-word; +} + +.message.command { + background-color: #0d7377; + color: white; + margin-left: auto; + margin-right: 0; + text-align: right; +} + +.message.output { + background-color: #2d2d2d; + border-left: 4px solid #4caf50; +} + +.message.error { + background-color: #2d2d2d; + border-left: 4px solid #f44336; +} + +.message.system { + background-color: #2d2d2d; + border-left: 4px solid #ff9800; + font-style: italic; +} + +.message pre { + margin: 0; + white-space: pre-wrap; + word-break: break-all; +} + +.input-area { + background-color: #2d2d2d; + padding: 1rem 2rem; + border-top: 1px solid #404040; +} + +.command-input-container, +.user-input-container { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.user-input-container { + margin-top: 0.5rem; +} + +input[type="text"] { + flex: 1; + padding: 0.75rem 1rem; + border: 1px solid #404040; + border-radius: 4px; + background-color: #1e1e1e; + color: #ffffff; + font-size: 14px; + font-family: 'Courier New', monospace; +} + +input[type="text"]:focus { + outline: none; + border-color: #0d7377; + box-shadow: 0 0 0 2px rgba(13, 115, 119, 0.2); +} + +button { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 4px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s; +} + +#run-btn { + background-color: #4caf50; + color: white; +} + +#run-btn:hover:not(:disabled) { + background-color: #45a049; +} + +#run-btn:disabled { + background-color: #666; + cursor: not-allowed; +} + +#kill-btn { + background-color: #f44336; + color: white; +} + +#kill-btn:hover:not(:disabled) { + background-color: #da190b; +} + +#kill-btn:disabled { + background-color: #666; + cursor: not-allowed; +} + +#send-btn { + background-color: #2196f3; + color: white; +} + +#send-btn:hover:not(:disabled) { + background-color: #1976d2; +} + +.timestamp { + font-size: 12px; + color: #888; + margin-bottom: 0.25rem; +} + +/* Scrollbar styling for webkit browsers */ +.output-area::-webkit-scrollbar { + width: 8px; +} + +.output-area::-webkit-scrollbar-track { + background: #1e1e1e; +} + +.output-area::-webkit-scrollbar-thumb { + background: #404040; + border-radius: 4px; +} + +.output-area::-webkit-scrollbar-thumb:hover { + background: #555; +} \ No newline at end of file diff --git a/cagents/python-cli-web-poc/server.js b/cagents/python-cli-web-poc/server.js new file mode 100644 index 000000000..abc22ae63 --- /dev/null +++ b/cagents/python-cli-web-poc/server.js @@ -0,0 +1,179 @@ +const express = require('express') +const WebSocket = require('ws') +const { spawn } = require('child_process') +const path = require('path') +const http = require('http') + +const app = express() +const server = http.createServer(app) +const wss = new WebSocket.Server({ server }) + +const PORT = process.env.PORT || 3000 + +// Serve static files from public directory +app.use(express.static('public')) + +// Route for main page +app.get('/', (req, res) => { + res.redirect('/index.html') +}) + +// Store active Python processes +const activeSessions = new Map() + +wss.on('connection', (ws) => { + console.log('Client connected') + + ws.on('message', (message) => { + try { + const data = JSON.parse(message) + + switch (data.type) { + case 'run_command': + handlePythonCommand(ws, data.command, data.sessionId) + break + case 'send_input': + handleUserInput(ws, data.input, data.sessionId) + break + case 'kill_session': + killSession(data.sessionId) + break + } + } catch (error) { + ws.send( + JSON.stringify({ + type: 'error', + message: 'Invalid message format' + }) + ) + } + }) + + ws.on('close', () => { + console.log('Client disconnected') + // Clean up any active sessions for this client + for (let [sessionId, session] of activeSessions) { + if (session.ws === ws) { + killSession(sessionId) + } + } + }) +}) + +function handlePythonCommand(ws, command, sessionId) { + // Kill existing session if any + if (activeSessions.has(sessionId)) { + killSession(sessionId) + } + + // Parse command (simple split for POC) + const args = command.trim().split(' ') + const pythonCommand = args[0] === 'python' || args[0] === 'python3' ? args[0] : 'python3' + const scriptArgs = args[0] === 'python' || args[0] === 'python3' ? args.slice(1) : args + + try { + // Spawn Python process + const pythonProcess = spawn(pythonCommand, scriptArgs, { + stdio: ['pipe', 'pipe', 'pipe'] + }) + + // Store session + activeSessions.set(sessionId, { + process: pythonProcess, + ws: ws + }) + + // Send confirmation + ws.send( + JSON.stringify({ + type: 'command_started', + sessionId: sessionId, + command: command + }) + ) + + // Handle stdout + pythonProcess.stdout.on('data', (data) => { + ws.send( + JSON.stringify({ + type: 'output', + sessionId: sessionId, + data: data.toString(), + stream: 'stdout' + }) + ) + }) + + // Handle stderr + pythonProcess.stderr.on('data', (data) => { + ws.send( + JSON.stringify({ + type: 'output', + sessionId: sessionId, + data: data.toString(), + stream: 'stderr' + }) + ) + }) + + // Handle process exit + pythonProcess.on('close', (code) => { + ws.send( + JSON.stringify({ + type: 'command_finished', + sessionId: sessionId, + exitCode: code + }) + ) + activeSessions.delete(sessionId) + }) + + // Handle process error + pythonProcess.on('error', (error) => { + ws.send( + JSON.stringify({ + type: 'error', + sessionId: sessionId, + message: `Failed to start process: ${error.message}` + }) + ) + activeSessions.delete(sessionId) + }) + } catch (error) { + ws.send( + JSON.stringify({ + type: 'error', + sessionId: sessionId, + message: `Error running command: ${error.message}` + }) + ) + } +} + +function handleUserInput(ws, input, sessionId) { + const session = activeSessions.get(sessionId) + if (session && session.process) { + session.process.stdin.write(input + '\n') + } else { + ws.send( + JSON.stringify({ + type: 'error', + sessionId: sessionId, + message: 'No active session to send input to' + }) + ) + } +} + +function killSession(sessionId) { + const session = activeSessions.get(sessionId) + if (session && session.process) { + session.process.kill() + activeSessions.delete(sessionId) + } +} + +server.listen(PORT, () => { + console.log(`Server running on http://localhost:${PORT}`) + console.log('Open your browser and navigate to the URL above') +}) diff --git a/cagents/python-cli-web-poc/simple-calc.py b/cagents/python-cli-web-poc/simple-calc.py new file mode 100644 index 000000000..80194c2da --- /dev/null +++ b/cagents/python-cli-web-poc/simple-calc.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +""" +Simple calculator for testing input/output +""" + +def main(): + print("🧮 Simple Calculator") + print("-" * 20) + print("Enter expressions like: 2 + 3, 10 * 5, etc.") + print("Type 'quit' to exit") + + while True: + try: + expression = input("Calculate: ").strip() + + if expression.lower() == 'quit': + print("👋 Goodbye!") + break + + # Simple evaluation (unsafe in production, but fine for POC) + result = eval(expression) + print(f"Result: {result}") + + except Exception as e: + print(f"❌ Error: {e}") + print("Please enter a valid mathematical expression") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/cagents/python-cli-web-poc/test-script.py b/cagents/python-cli-web-poc/test-script.py new file mode 100644 index 000000000..16f3ce796 --- /dev/null +++ b/cagents/python-cli-web-poc/test-script.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +""" +Test Python script for the web CLI POC +Demonstrates various interactive features +""" + +import time +import sys + +def main(): + print("🐍 Python CLI Web Interface Test Script") + print("=" * 40) + + # Test basic output + print("Hello from Python!") + print("This is a test script to demonstrate the web interface.") + + # Test user input + name = input("What's your name? ") + print(f"Nice to meet you, {name}!") + + # Test multiple inputs + while True: + color = input("What's your favorite color? (or 'quit' to exit): ") + if color.lower() == 'quit': + break + print(f"🎨 {color} is a great choice!") + + # Test countdown with streaming output + print("\nStarting countdown...") + for i in range(5, 0, -1): + print(f"⏰ {i}...") + sys.stdout.flush() # Force output to be sent immediately + time.sleep(1) + + print("🎉 Done! Thanks for testing the Python CLI Web Interface!") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/renderer/src/Router.tsx b/src/renderer/src/Router.tsx index 624c6ccc4..8eeace0f7 100644 --- a/src/renderer/src/Router.tsx +++ b/src/renderer/src/Router.tsx @@ -8,6 +8,7 @@ import TabsContainer from './components/Tab/TabContainer' import NavigationHandler from './handler/NavigationHandler' import { useNavbarPosition } from './hooks/useSettings' import AgentsPage from './pages/agents/AgentsPage' +import CommandPocPage from './pages/command-poc/CommandPocPage' import FilesPage from './pages/files/FilesPage' import HomePage from './pages/home/HomePage' import KnowledgePage from './pages/knowledge/KnowledgePage' @@ -25,6 +26,7 @@ const Router: FC = () => { } /> } /> + } /> } /> } /> } /> diff --git a/src/renderer/src/pages/command-poc/CommandPocPage.tsx b/src/renderer/src/pages/command-poc/CommandPocPage.tsx new file mode 100644 index 000000000..124eeb81e --- /dev/null +++ b/src/renderer/src/pages/command-poc/CommandPocPage.tsx @@ -0,0 +1,37 @@ +import React from 'react' +import styled from 'styled-components' + +import PocHeader from './components/PocHeader' +import PocMessageList from './components/PocMessageList' +import PocCommandInput from './components/PocCommandInput' +import PocStatusBar from './components/PocStatusBar' + +const PageContainer = styled.div` + display: flex; + flex-direction: column; + height: 100vh; + width: 100%; + background: var(--color-background); +` + +const ContentArea = styled.div` + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +` + +const CommandPocPage: React.FC = () => { + return ( + + + + + + + + + ) +} + +export default CommandPocPage \ No newline at end of file diff --git a/src/renderer/src/pages/command-poc/components/PocCommandInput.tsx b/src/renderer/src/pages/command-poc/components/PocCommandInput.tsx new file mode 100644 index 000000000..1553d5c16 --- /dev/null +++ b/src/renderer/src/pages/command-poc/components/PocCommandInput.tsx @@ -0,0 +1,115 @@ +import React, { useState, useCallback, KeyboardEvent } from 'react' +import styled from 'styled-components' + +const InputContainer = styled.div` + display: flex; + padding: 16px; + border-top: 1px solid var(--color-border); + background: var(--color-background); + gap: 12px; + align-items: flex-end; +` + +const InputWrapper = styled.div` + flex: 1; + position: relative; +` + +const Input = styled.input` + width: 100%; + padding: 12px 16px; + border: 1px solid var(--color-border); + border-radius: 8px; + font-size: 14px; + font-family: var(--font-mono); + background: var(--color-background-soft); + color: var(--color-text); + outline: none; + + &:focus { + border-color: var(--color-primary); + box-shadow: 0 0 0 2px var(--color-primary-alpha); + } + + &::placeholder { + color: var(--color-text-secondary); + font-family: var(--font-text); + } +` + +const SendButton = styled.button` + padding: 12px 20px; + background: var(--color-primary); + color: var(--color-primary-text); + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: background-color 0.2s; + + &:hover:not(:disabled) { + background: var(--color-primary-hover); + } + + &:disabled { + opacity: 0.6; + cursor: not-allowed; + } +` + +const Hint = styled.div` + position: absolute; + top: -20px; + left: 0; + font-size: 11px; + color: var(--color-text-secondary); +` + +interface PocCommandInputProps { + onSendCommand?: (command: string) => void + disabled?: boolean +} + +const PocCommandInput: React.FC = ({ + onSendCommand = () => {}, + disabled = false +}) => { + const [input, setInput] = useState('') + + const handleSend = useCallback(() => { + const trimmedInput = input.trim() + if (trimmedInput && !disabled) { + onSendCommand(trimmedInput) + setInput('') + } + }, [input, disabled, onSendCommand]) + + const handleKeyDown = useCallback((e: KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault() + handleSend() + } + // TODO: Add arrow key history navigation + }, [handleSend]) + + return ( + + + Enter shell commands (e.g., ls, pwd, echo "hello") + setInput(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Type a shell command..." + disabled={disabled} + /> + + + Send + + + ) +} + +export default PocCommandInput \ No newline at end of file diff --git a/src/renderer/src/pages/command-poc/components/PocHeader.tsx b/src/renderer/src/pages/command-poc/components/PocHeader.tsx new file mode 100644 index 000000000..308667f17 --- /dev/null +++ b/src/renderer/src/pages/command-poc/components/PocHeader.tsx @@ -0,0 +1,48 @@ +import React from 'react' +import styled from 'styled-components' + +const HeaderContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + border-bottom: 1px solid var(--color-border); + background: var(--color-background); + min-height: 60px; +` + +const Title = styled.h1` + font-size: 18px; + font-weight: 600; + margin: 0; + color: var(--color-text); +` + +const WorkingDirectory = styled.div` + font-size: 12px; + color: var(--color-text-secondary); + font-family: var(--font-mono); +` + +const Controls = styled.div` + display: flex; + gap: 8px; +` + +const PocHeader: React.FC = () => { + const workingDir = process.cwd() // This will be replaced with actual working directory + + return ( + +
+ Command POC + 📁 {workingDir} +
+ + {/* Future: Add session controls */} + +
+ ) +} + +export default PocHeader \ No newline at end of file diff --git a/src/renderer/src/pages/command-poc/components/PocMessageBubble.tsx b/src/renderer/src/pages/command-poc/components/PocMessageBubble.tsx new file mode 100644 index 000000000..1cbd54986 --- /dev/null +++ b/src/renderer/src/pages/command-poc/components/PocMessageBubble.tsx @@ -0,0 +1,90 @@ +import React from 'react' +import styled from 'styled-components' + +import { PocMessage } from '../types' + +const MessageContainer = styled.div<{ $isUser: boolean }>` + display: flex; + justify-content: ${props => props.$isUser ? 'flex-end' : 'flex-start'}; + margin-bottom: 8px; +` + +const BubbleBase = styled.div` + max-width: 80%; + padding: 12px 16px; + border-radius: 12px; + font-size: 14px; + line-height: 1.4; +` + +const CommandBubble = styled(BubbleBase)` + background: var(--color-primary); + color: var(--color-primary-text); + font-family: var(--font-mono); +` + +const OutputBubble = styled(BubbleBase)` + background: var(--color-background-soft); + color: var(--color-text); + border: 1px solid var(--color-border); +` + +const CommandPrefix = styled.span` + color: var(--color-primary-text); + opacity: 0.8; + margin-right: 8px; +` + +const CommandText = styled.span` + font-family: var(--font-mono); +` + +const OutputPre = styled.pre` + margin: 0; + font-family: var(--font-mono); + font-size: 13px; + white-space: pre-wrap; + word-break: break-word; +` + +const LoadingDots = styled.div` + display: inline-block; + margin-left: 8px; + + &::after { + content: '...'; + animation: loading 1.5s infinite; + } + + @keyframes loading { + 0%, 20% { content: '.'; } + 40% { content: '..'; } + 60%, 100% { content: '...'; } + } +` + +interface PocMessageBubbleProps { + message: PocMessage +} + +const PocMessageBubble: React.FC = ({ message }) => { + const isUserCommand = message.type === 'user-command' + + return ( + + {isUserCommand ? ( + + $ + {message.content} + + ) : ( + + {message.content} + {!message.isComplete && } + + )} + + ) +} + +export default PocMessageBubble \ No newline at end of file diff --git a/src/renderer/src/pages/command-poc/components/PocMessageList.tsx b/src/renderer/src/pages/command-poc/components/PocMessageList.tsx new file mode 100644 index 000000000..4a5f52344 --- /dev/null +++ b/src/renderer/src/pages/command-poc/components/PocMessageList.tsx @@ -0,0 +1,55 @@ +import React from 'react' +import styled from 'styled-components' + +import PocMessageBubble from './PocMessageBubble' +import { PocMessage } from '../types' + +const MessageContainer = styled.div` + flex: 1; + overflow-y: auto; + padding: 16px; + display: flex; + flex-direction: column; + gap: 12px; +` + +const EmptyState = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + color: var(--color-text-secondary); + text-align: center; +` + +interface PocMessageListProps { + messages?: PocMessage[] +} + +const PocMessageList: React.FC = ({ messages = [] }) => { + if (messages.length === 0) { + return ( + + +
💻
+

Command POC Interface

+

Enter shell commands below to see them execute in real-time.

+

+ Try commands like: ls, pwd, or echo "Hello World" +

+
+
+ ) + } + + return ( + + {messages.map(message => ( + + ))} + + ) +} + +export default PocMessageList \ No newline at end of file diff --git a/src/renderer/src/pages/command-poc/components/PocStatusBar.tsx b/src/renderer/src/pages/command-poc/components/PocStatusBar.tsx new file mode 100644 index 000000000..8c37183b6 --- /dev/null +++ b/src/renderer/src/pages/command-poc/components/PocStatusBar.tsx @@ -0,0 +1,84 @@ +import React from 'react' +import styled from 'styled-components' + +const StatusContainer = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 16px; + background: var(--color-background-soft); + border-top: 1px solid var(--color-border); + font-size: 12px; + color: var(--color-text-secondary); + min-height: 32px; +` + +const StatusLeft = styled.div` + display: flex; + align-items: center; + gap: 12px; +` + +const StatusRight = styled.div` + display: flex; + align-items: center; + gap: 12px; +` + +const StatusIndicator = styled.div<{ $status: 'idle' | 'running' | 'error' }>` + display: flex; + align-items: center; + gap: 6px; + + &::before { + content: ''; + width: 8px; + height: 8px; + border-radius: 50%; + background: ${props => { + switch (props.$status) { + case 'running': return '#22c55e' + case 'error': return '#ef4444' + default: return '#6b7280' + } + }}; + } +` + +interface PocStatusBarProps { + status?: 'idle' | 'running' | 'error' + activeCommand?: string + commandCount?: number +} + +const PocStatusBar: React.FC = ({ + status = 'idle', + activeCommand, + commandCount = 0 +}) => { + const getStatusText = () => { + switch (status) { + case 'running': + return activeCommand ? `Running: ${activeCommand}` : 'Running command...' + case 'error': + return 'Command failed' + default: + return 'Ready' + } + } + + return ( + + + + {getStatusText()} + + + +
Commands executed: {commandCount}
+
+
+ ) +} + +export default PocStatusBar \ No newline at end of file diff --git a/src/renderer/src/pages/command-poc/types.ts b/src/renderer/src/pages/command-poc/types.ts new file mode 100644 index 000000000..9eaa14ea9 --- /dev/null +++ b/src/renderer/src/pages/command-poc/types.ts @@ -0,0 +1,40 @@ +// POC-specific TypeScript interfaces for command execution + +export interface PocMessage { + id: string + type: 'user-command' | 'output' | 'error' | 'system' + content: string + timestamp: number + commandId?: string // Links output to originating command + isComplete: boolean // For streaming messages +} + +export interface PocCommandExecution { + id: string + command: string + startTime: number + endTime?: number + exitCode?: number + isRunning: boolean +} + +// IPC Communication interfaces +export interface PocExecuteCommandRequest { + id: string + command: string + workingDirectory: string +} + +export interface PocCommandOutput { + commandId: string + type: 'stdout' | 'stderr' | 'exit' | 'error' + data: string + exitCode?: number +} + +// IPC Channel constants +export const IPC_CHANNELS = { + EXECUTE_COMMAND: 'poc-execute-command', + COMMAND_OUTPUT: 'poc-command-output', + INTERRUPT_COMMAND: 'poc-interrupt-command' +} as const \ No newline at end of file