- Add PRD.md with detailed product requirements documentation - Unify tmux screen size presets to consistent 24-row height - Add Ctrl+Down keyboard shortcut to scroll terminal to bottom Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1425 lines
45 KiB
Markdown
1425 lines
45 KiB
Markdown
# Spiceflow Product Requirements Document
|
|
|
|
## Executive Summary
|
|
|
|
Spiceflow is an AI Session Orchestration Progressive Web App (PWA) for monitoring and interacting with Claude Code and OpenCode CLI sessions from mobile devices or web browsers. It provides a unified interface to manage multiple AI coding assistant sessions, handle permission requests, and interact with tmux terminal sessions.
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Product Overview](#1-product-overview)
|
|
2. [System Architecture](#2-system-architecture)
|
|
3. [Data Models](#3-data-models)
|
|
4. [Session Management](#4-session-management)
|
|
5. [Messaging System](#5-messaging-system)
|
|
6. [Permission System](#6-permission-system)
|
|
7. [Terminal Integration](#7-terminal-integration)
|
|
8. [Push Notifications](#8-push-notifications)
|
|
9. [API Specification](#9-api-specification)
|
|
10. [WebSocket Protocol](#10-websocket-protocol)
|
|
11. [User Interface](#11-user-interface)
|
|
12. [User Flows](#12-user-flows)
|
|
13. [Security Considerations](#13-security-considerations)
|
|
14. [Performance Requirements](#14-performance-requirements)
|
|
|
|
---
|
|
|
|
## 1. Product Overview
|
|
|
|
### 1.1 Purpose
|
|
|
|
Spiceflow bridges the gap between AI coding assistants (Claude Code, OpenCode) and mobile/web interfaces, enabling developers to:
|
|
|
|
- Monitor AI coding sessions from any device
|
|
- Respond to permission requests remotely
|
|
- Manage multiple concurrent sessions
|
|
- Interact with shell terminals via tmux
|
|
|
|
### 1.2 Supported Providers
|
|
|
|
| Provider | Type | Interaction Model | Permission Handling |
|
|
|----------|------|-------------------|---------------------|
|
|
| Claude Code | AI Coding Assistant | Chat-based with streaming | Manual or Auto-accept |
|
|
| OpenCode | AI Coding Assistant | Chat-based with streaming | Auto-approved by CLI |
|
|
| tmux | Terminal Multiplexer | Direct shell access | N/A |
|
|
|
|
### 1.3 Key Features
|
|
|
|
- **Real-time Streaming**: Live display of AI responses via WebSocket
|
|
- **Permission Interception**: Approve, deny, or redirect file/command operations
|
|
- **Auto-Accept Edits**: Automatic approval of Write/Edit operations for Claude
|
|
- **Terminal Emulation**: Full tmux session control with keyboard input
|
|
- **Push Notifications**: Web push alerts for pending permissions
|
|
- **PWA Support**: Installable app with offline capability
|
|
|
|
---
|
|
|
|
## 2. System Architecture
|
|
|
|
### 2.1 High-Level Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ PWA Client (SvelteKit) │
|
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
|
|
│ │ Sessions │ │ Messages │ │ Terminal │ │ Push Notif Store │ │
|
|
│ │ Store │ │ Store │ │ View │ │ │ │
|
|
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────────┬─────────┘ │
|
|
│ │ │ │ │ │
|
|
│ └─────────────┴─────────────┴──────────────────┘ │
|
|
│ │ │
|
|
│ ┌─────────┴─────────┐ │
|
|
│ │ API Client │ │
|
|
│ │ WebSocket │ │
|
|
│ └─────────┬─────────┘ │
|
|
└──────────────────────────────┼───────────────────────────────────┘
|
|
│ HTTP / WebSocket
|
|
┌──────────────────────────────┼───────────────────────────────────┐
|
|
│ Spiceflow Server (Clojure) │
|
|
│ ┌─────────────────┬─────────┴─────────┬─────────────────────┐ │
|
|
│ │ HTTP Routes │ WebSocket │ Push Sender │ │
|
|
│ │ (Reitit) │ Manager │ (RFC 8291) │ │
|
|
│ └────────┬────────┴─────────┬─────────┴──────────┬──────────┘ │
|
|
│ │ │ │ │
|
|
│ ┌────────┴──────────────────┴────────────────────┴──────────┐ │
|
|
│ │ Session Manager │ │
|
|
│ │ ┌──────────────────────────────────────────────────────┐ │ │
|
|
│ │ │ Active Processes (ConcurrentHashMap) │ │ │
|
|
│ │ │ session-id → {process, stdin, stdout, stderr} │ │ │
|
|
│ │ └──────────────────────────────────────────────────────┘ │ │
|
|
│ └────────┬──────────────────┬────────────────────┬──────────┘ │
|
|
│ │ │ │ │
|
|
│ ┌────────┴────────┐ ┌───────┴───────┐ ┌─────────┴─────────┐ │
|
|
│ │ Claude Adapter │ │OpenCode Adapt.│ │ Tmux Adapter │ │
|
|
│ │ (JSONL stream) │ │(JSON stream) │ │ (raw terminal) │ │
|
|
│ └────────┬────────┘ └───────┬───────┘ └─────────┬─────────┘ │
|
|
│ │ │ │ │
|
|
│ ┌────────┴──────────────────┴───────────────────┴──────────┐ │
|
|
│ │ DataStore (SQLite) │ │
|
|
│ │ sessions | messages | push_subscriptions | vapid_keys │ │
|
|
│ └───────────────────────────────────────────────────────────┘ │
|
|
└──────────────────────────────────────────────────────────────────┘
|
|
│
|
|
┌──────────────────────┼──────────────────────┐
|
|
│ │ │
|
|
┌───────┴───────┐ ┌───────┴───────┐ ┌───────┴───────┐
|
|
│ Claude Code │ │ OpenCode │ │ tmux │
|
|
│ CLI │ │ CLI │ │ sessions │
|
|
└───────────────┘ └───────────────┘ └───────────────┘
|
|
```
|
|
|
|
### 2.2 Technology Stack
|
|
|
|
| Layer | Technology | Purpose |
|
|
|-------|------------|---------|
|
|
| Frontend | SvelteKit 2.5, Svelte 4 | PWA framework |
|
|
| Styling | Tailwind CSS | Utility-first CSS |
|
|
| Build | Vite, vite-plugin-pwa | Asset bundling, service worker |
|
|
| Backend | Clojure 1.11, Ring/Jetty | HTTP server |
|
|
| Routing | Reitit | REST API routing |
|
|
| Database | SQLite via next.jdbc | Persistent storage |
|
|
| State | Mount | Component lifecycle |
|
|
| Testing | Kaocha (unit), Playwright (E2E) | Test frameworks |
|
|
|
|
### 2.3 Data Flow
|
|
|
|
```
|
|
User Input → HTTP POST → Session Manager → Adapter → CLI stdin
|
|
│
|
|
CLI stdout → Adapter.read-stream → Session Manager ─────┤
|
|
│ │
|
|
┌──────────┴──────────┐ │
|
|
│ │ │
|
|
Save to DB WebSocket Broadcast
|
|
│ │
|
|
└──────────┬──────────┘
|
|
│
|
|
PWA Client
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Data Models
|
|
|
|
### 3.1 Session
|
|
|
|
```typescript
|
|
interface Session {
|
|
id: string; // UUID (DB sessions) or tmux name
|
|
provider: 'claude' | 'opencode' | 'tmux';
|
|
external_id?: string; // CLI-specific session identifier
|
|
title?: string; // User-friendly name
|
|
working_dir?: string; // Current working directory
|
|
spawn_dir?: string; // Original spawn directory
|
|
status: 'idle' | 'processing' | 'awaiting-permission';
|
|
pending_permission?: PermissionRequest; // JSON blob
|
|
auto_accept_edits?: boolean; // Auto-approve Write/Edit (Claude only)
|
|
created_at: string; // ISO timestamp
|
|
updated_at: string; // ISO timestamp
|
|
}
|
|
```
|
|
|
|
**Status State Machine:**
|
|
```
|
|
┌─────────────────────────────────┐
|
|
│ │
|
|
▼ │
|
|
┌──────┐ send_message ┌────────┴───┐
|
|
│ idle │ ─────────────────▶ │ processing │
|
|
└──────┘ └─────┬──────┘
|
|
▲ │
|
|
│ │
|
|
│ stream_ends │ permission_denied
|
|
│ (no pending) │
|
|
│ ▼
|
|
│ ┌─────────────────────┐
|
|
│ │ awaiting-permission │
|
|
│ └──────────┬──────────┘
|
|
│ │
|
|
│ respond_to_permission │
|
|
└───────────────────────────────┘
|
|
```
|
|
|
|
### 3.2 Message
|
|
|
|
```typescript
|
|
interface Message {
|
|
id: string; // UUID
|
|
session_id: string; // FK to sessions
|
|
role: 'user' | 'assistant' | 'system';
|
|
content: string; // Message text
|
|
metadata?: {
|
|
type?: 'permission-request'; // Permission message
|
|
status?: 'accept' | 'deny' | 'steer'; // Permission response
|
|
auto_accepted?: boolean; // Was auto-approved
|
|
denials?: PermissionDenial[]; // Tool requests
|
|
message_id?: string; // For permission tracking
|
|
};
|
|
created_at: string; // ISO timestamp
|
|
}
|
|
```
|
|
|
|
### 3.3 Permission Request
|
|
|
|
```typescript
|
|
interface PermissionRequest {
|
|
tools: string[]; // ['Write', 'Edit', 'Bash', etc.]
|
|
denials: PermissionDenial[]; // Detailed tool requests
|
|
}
|
|
|
|
interface PermissionDenial {
|
|
tool: string; // Tool name
|
|
input: ToolInput; // Tool-specific input
|
|
description: string; // Human-readable description
|
|
}
|
|
|
|
// Tool Input Types
|
|
interface WriteToolInput {
|
|
file_path: string;
|
|
content: string;
|
|
}
|
|
|
|
interface EditToolInput {
|
|
file_path: string;
|
|
old_string: string;
|
|
new_string: string;
|
|
}
|
|
|
|
interface BashToolInput {
|
|
command: string;
|
|
}
|
|
```
|
|
|
|
### 3.4 Push Subscription
|
|
|
|
```typescript
|
|
interface PushSubscription {
|
|
id: string; // UUID
|
|
endpoint: string; // Push service URL (unique)
|
|
p256dh: string; // Client ECDH public key (base64url)
|
|
auth: string; // Client auth secret (base64url)
|
|
user_agent?: string; // Browser info
|
|
created_at: string; // ISO timestamp
|
|
}
|
|
```
|
|
|
|
### 3.5 VAPID Keys
|
|
|
|
```typescript
|
|
interface VapidKeys {
|
|
public_key: string; // EC P-256 uncompressed point (base64url)
|
|
private_key: string; // Raw scalar (base64url)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Session Management
|
|
|
|
### 4.1 Session Lifecycle
|
|
|
|
#### 4.1.1 Creation
|
|
|
|
**Claude/OpenCode:**
|
|
1. Client POSTs to `/api/sessions` with `{provider: 'claude'|'opencode'}`
|
|
2. Server generates UUID, saves to database
|
|
3. Returns session with `status: 'idle'`
|
|
|
|
**Tmux:**
|
|
1. Client POSTs to `/api/sessions` with `{provider: 'tmux'}`
|
|
2. Server generates readable name: `spiceflow-{adjective}-{noun}-{4digits}`
|
|
3. Creates tmux session via `tmux new-session -d -s {name}`
|
|
4. Sets up output capture via `pipe-pane`
|
|
5. Returns session (ID = tmux session name)
|
|
|
|
#### 4.1.2 Message Sending
|
|
|
|
```
|
|
send_message_to_session(session_id, message)
|
|
│
|
|
├─▶ Save user message to database
|
|
│
|
|
├─▶ Session not running?
|
|
│ └─▶ start_session(session_id)
|
|
│ ├─▶ Get adapter for provider
|
|
│ ├─▶ adapter.spawn_session(session_id, opts)
|
|
│ └─▶ Store handle in active_processes map
|
|
│
|
|
├─▶ adapter.send_message(handle, message)
|
|
│
|
|
└─▶ Return handle for streaming
|
|
```
|
|
|
|
#### 4.1.3 Response Streaming
|
|
|
|
```
|
|
stream_session_response(session_id, callback)
|
|
│
|
|
├─▶ Get session and active handle
|
|
│
|
|
├─▶ adapter.read_stream(handle, event_callback)
|
|
│ │
|
|
│ ├─▶ On 'init': Extract external_id, spawn_dir
|
|
│ │
|
|
│ ├─▶ On 'content-delta':
|
|
│ │ ├─▶ Accumulate text
|
|
│ │ └─▶ Broadcast via WebSocket
|
|
│ │
|
|
│ ├─▶ On 'result':
|
|
│ │ ├─▶ Save assistant message
|
|
│ │ ├─▶ Check for permission_denials
|
|
│ │ │ ├─▶ If auto-accept eligible:
|
|
│ │ │ │ ├─▶ Mark message status='accept'
|
|
│ │ │ │ ├─▶ Broadcast with auto_accepted=true
|
|
│ │ │ │ └─▶ Recurse after responding
|
|
│ │ │ └─▶ Else:
|
|
│ │ │ ├─▶ Set pending_permission
|
|
│ │ │ ├─▶ Broadcast permission-request
|
|
│ │ │ └─▶ Schedule push notification (15s)
|
|
│ │ └─▶ Extract working_dir from tool results
|
|
│ │
|
|
│ └─▶ On 'message-stop': Finalize
|
|
│
|
|
└─▶ On stream end:
|
|
├─▶ If auto-accepted: respond_to_permission(:accept)
|
|
├─▶ Update session status
|
|
└─▶ Remove from active_processes
|
|
```
|
|
|
|
#### 4.1.4 Session Discovery
|
|
|
|
**Claude Code:**
|
|
- Scans `~/.claude/projects/*/sessions/*.json`
|
|
- Decodes URL-encoded paths from filenames
|
|
- Returns list of discovered sessions with external IDs
|
|
|
|
**Tmux:**
|
|
- Runs `tmux list-sessions -F "#{session_name}:#{pane_current_path}"`
|
|
- Filters for `spiceflow-*` prefix
|
|
- Returns managed sessions only
|
|
|
|
### 4.2 Process Handle Structure
|
|
|
|
```clojure
|
|
{:process java.lang.Process ; JVM process reference
|
|
:stdin java.io.BufferedWriter ; Write messages here
|
|
:stdout java.io.BufferedReader ; Read JSONL/output here
|
|
:stderr java.io.BufferedReader ; Error stream
|
|
;; Tmux-specific:
|
|
:session-name string ; Tmux session name
|
|
:output-file string ; Pipe-pane log path
|
|
:end-marker string ; Command completion marker
|
|
:marker-id string ; UUID for marker
|
|
:original-cmd string ; User's command (for filtering)
|
|
}
|
|
```
|
|
|
|
### 4.3 Adapter Protocol
|
|
|
|
```clojure
|
|
(defprotocol AgentAdapter
|
|
(provider-name [this])
|
|
;; Returns :claude, :opencode, or :tmux
|
|
|
|
(discover-sessions [this])
|
|
;; Returns [{:external-id :working-dir :title}]
|
|
|
|
(spawn-session [this session-id opts])
|
|
;; Creates CLI process, returns handle
|
|
|
|
(send-message [this handle message])
|
|
;; Writes to stdin, returns handle (possibly updated)
|
|
|
|
(read-stream [this handle callback])
|
|
;; Reads output, calls callback(event) for each
|
|
|
|
(kill-process [this handle])
|
|
;; Terminates process
|
|
|
|
(parse-output [this line]))
|
|
;; Parses output line to event map
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Messaging System
|
|
|
|
### 5.1 Message Flow
|
|
|
|
```
|
|
┌─────────┐ POST /send ┌─────────┐ stdin ┌─────────┐
|
|
│ Client │ ──────────────────▶ │ Server │ ──────────────▶ │ CLI │
|
|
└─────────┘ └─────────┘ └────┬────┘
|
|
▲ │ │
|
|
│ │ │ stdout
|
|
│ Save user │ (JSONL)
|
|
│ message │
|
|
│ │ ▼
|
|
│ │ ┌─────────┐
|
|
│ WebSocket ┌┴┐ │ Adapter │
|
|
│ content-delta ◀───┤ │◀───────────────────│ Parse │
|
|
│ message-stop └┬┘ └─────────┘
|
|
│ permission-request │
|
|
│ │
|
|
│ Save assistant
|
|
│ message
|
|
└───────────────────────────────┘
|
|
```
|
|
|
|
### 5.2 Stream Event Types
|
|
|
|
| Event | Source | Payload | Purpose |
|
|
|-------|--------|---------|---------|
|
|
| `init` | CLI startup | `{session-id, cwd}` | Session initialized |
|
|
| `content-delta` | Assistant response | `{text}` | Incremental text |
|
|
| `message-stop` | Response complete | `{}` | Stream finalized |
|
|
| `permission-request` | Permission denied | `{permission-request, message-id, message}` | Tool blocked |
|
|
| `working-dir-update` | Directory change | `{working-dir}` | CWD updated |
|
|
| `terminal-update` | Tmux output | `{content, diff}` | Terminal changed |
|
|
| `error` | Any failure | `{message}` | Error occurred |
|
|
|
|
### 5.3 JSONL Parsing (Claude)
|
|
|
|
```json
|
|
{"type":"system","subtype":"init","session_id":"abc","cwd":"/home/user"}
|
|
{"type":"assistant","message":{"content":[{"type":"text","text":"Hello"}]}}
|
|
{"type":"result","result":"success","permission_denials":[...]}
|
|
```
|
|
|
|
**Event Mapping:**
|
|
- `system.init` → `:init`
|
|
- `assistant` → `:content-delta` (aggregated)
|
|
- `result` → `:result` with permission extraction
|
|
|
|
### 5.4 JSON Parsing (OpenCode)
|
|
|
|
```json
|
|
{"type":"step_start","sessionID":"abc","cwd":"/home/user"}
|
|
{"type":"text","content":"Hello"}
|
|
{"type":"step_finish","reason":"complete"}
|
|
```
|
|
|
|
**Event Mapping:**
|
|
- `step_start` → `:init`
|
|
- `text` → `:content-delta`
|
|
- `step_finish` → `:result`
|
|
|
|
---
|
|
|
|
## 6. Permission System
|
|
|
|
### 6.1 Permission Flow
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Permission Request Flow │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
CLI requests tool ──▶ Server detects denial ──▶ Check auto-accept
|
|
│
|
|
┌────────────────────────────┴────────┐
|
|
│ │
|
|
Auto-accept? Manual
|
|
│ │
|
|
┌─────┴─────┐ │
|
|
│ │ │
|
|
Only Write/ Other │
|
|
Edit tools tools │
|
|
│ │ │
|
|
▼ │ │
|
|
Auto-accept └───────────────────────────────┤
|
|
immediately │
|
|
│ │
|
|
│ ▼
|
|
│ ┌───────────────────┐
|
|
│ │ Set pending_perm │
|
|
│ │ Broadcast to WS │
|
|
│ │ Schedule push │
|
|
│ └─────────┬─────────┘
|
|
│ │
|
|
│ ▼
|
|
│ ┌───────────────────┐
|
|
│ │ Client displays │
|
|
│ │ permission UI │
|
|
│ └─────────┬─────────┘
|
|
│ │
|
|
│ User responds: Accept/Deny/Steer
|
|
│ │
|
|
│ ▼
|
|
▼ ┌───────────────────┐
|
|
┌───────────────────┐ │ POST /permission │
|
|
│ respond_to_perm │◀────────────────│ {response, msg} │
|
|
│ :accept, nil │ └───────────────────┘
|
|
└─────────┬─────────┘
|
|
│
|
|
▼
|
|
┌───────────────────┐
|
|
│ Spawn new process │
|
|
│ --resume │
|
|
│ --allowedTools │
|
|
└─────────┬─────────┘
|
|
│
|
|
▼
|
|
┌───────────────────┐
|
|
│ Send message: │
|
|
│ "continue" (accept)│
|
|
│ "denied" (deny) │
|
|
│ user text (steer) │
|
|
└─────────┬─────────┘
|
|
│
|
|
▼
|
|
┌───────────────────┐
|
|
│ Stream new response│
|
|
└───────────────────┘
|
|
```
|
|
|
|
### 6.2 Auto-Accept Logic
|
|
|
|
```clojure
|
|
(defn should-auto-accept? [session perm-req]
|
|
(and (:auto-accept-edits session)
|
|
(every? #{"Write" "Edit"} (:tools perm-req))))
|
|
```
|
|
|
|
**Eligible Tools:**
|
|
- `Write` - File creation
|
|
- `Edit` - File modification
|
|
|
|
**Ineligible Tools:**
|
|
- `Bash` - Shell commands
|
|
- `WebFetch` - HTTP requests
|
|
- `WebSearch` - Web searches
|
|
- `NotebookEdit` - Jupyter notebooks
|
|
- `Task` - Sub-agent spawning
|
|
- `Skill` - Skill invocation
|
|
|
|
### 6.3 Permission Response Types
|
|
|
|
| Response | Message Sent | Behavior |
|
|
|----------|--------------|----------|
|
|
| `accept` | "continue" | Grants all requested tools |
|
|
| `deny` | "Permission denied. Find another approach without using that tool." | Rejects request |
|
|
| `steer` | User-provided text | Redirects with instructions |
|
|
|
|
### 6.4 Permission Message Recording
|
|
|
|
Permissions are recorded as assistant messages with metadata:
|
|
|
|
```typescript
|
|
{
|
|
role: 'assistant',
|
|
content: 'I need permission to...',
|
|
metadata: {
|
|
type: 'permission-request',
|
|
status: 'accept' | 'deny' | 'steer',
|
|
auto_accepted?: true,
|
|
denials: [...],
|
|
message_id: 'uuid'
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Terminal Integration
|
|
|
|
### 7.1 Tmux Session Management
|
|
|
|
#### 7.1.1 Session Naming
|
|
|
|
- **Format:** `spiceflow-{adjective}-{noun}-{4digits}`
|
|
- **Examples:** `spiceflow-brave-fox-0042`, `spiceflow-calm-owl-1337`
|
|
- **Purpose:** Human-readable, unique identifiers
|
|
|
|
#### 7.1.2 Output Capture
|
|
|
|
```bash
|
|
# Create session
|
|
tmux new-session -d -s {session-name} -c {working-dir}
|
|
|
|
# Capture output to file
|
|
tmux pipe-pane -t {session-name} "cat >> /tmp/spiceflow-tmux-{name}.log"
|
|
|
|
# Capture visible pane
|
|
tmux capture-pane -t {session-name} -p -e -S -1000
|
|
```
|
|
|
|
### 7.2 Terminal Diff System
|
|
|
|
#### 7.2.1 Diff Types
|
|
|
|
| Type | When Used | Payload |
|
|
|------|-----------|---------|
|
|
| `full` | Initial load, >50% changed | `{lines: [...], hash, frame-id}` |
|
|
| `diff` | Partial update | `{changes: {line: content}, total-lines, hash, frame-id}` |
|
|
| `unchanged` | No changes | `{hash, frame-id}` |
|
|
|
|
#### 7.2.2 Frame Ordering
|
|
|
|
- **Frame ID:** Auto-incrementing counter (wraps at 2^53-1)
|
|
- **Purpose:** Prevent out-of-order updates from network delays
|
|
- **Client Logic:** Only apply frames with ID > last frame ID
|
|
|
|
#### 7.2.3 Change Detection
|
|
|
|
```clojure
|
|
(defn compute-line-diff [old-lines new-lines]
|
|
;; Compare line-by-line
|
|
;; Return map of {line-num -> new-content}
|
|
;; If >50% changed, return nil (signal full refresh))
|
|
```
|
|
|
|
### 7.3 Input Handling
|
|
|
|
#### 7.3.1 Key Mapping
|
|
|
|
| Input | tmux send-keys |
|
|
|-------|----------------|
|
|
| Regular text | `-l "{text}"` (literal) |
|
|
| Enter | `Enter` (key name) |
|
|
| Newline | `-l "\n"` |
|
|
| Ctrl+C | `\x03` (raw) |
|
|
| Ctrl+D | `\x04` (raw) |
|
|
| Tab | `\t` |
|
|
| Escape | `\x1b` |
|
|
| Arrow keys | `\x1b[A/B/C/D` |
|
|
|
|
#### 7.3.2 Input Batching
|
|
|
|
- **Client-side:** 30ms batching for rapid keystrokes
|
|
- **Server-side:** Immediate send to tmux
|
|
- **Feedback:** 100ms delayed terminal capture after input
|
|
|
|
### 7.4 Screen Size Presets
|
|
|
|
| Mode | Dimensions | Use Case |
|
|
|------|------------|----------|
|
|
| `portrait` | 40x24 | Phone portrait |
|
|
| `landscape` | 65x24 | Phone landscape |
|
|
| `desktop` | 100x24 | Split screen |
|
|
| `fullscreen` | 180x24 | Full terminal |
|
|
|
|
---
|
|
|
|
## 8. Push Notifications
|
|
|
|
### 8.1 VAPID Authentication (RFC 8292)
|
|
|
|
#### 8.1.1 Key Generation
|
|
|
|
```clojure
|
|
;; Generate EC P-256 key pair
|
|
(defn generate-keypair []
|
|
{:public-key ;; Uncompressed point (0x04 || x || y), base64url
|
|
:private-key ;; Raw scalar (32 bytes), base64url
|
|
})
|
|
```
|
|
|
|
#### 8.1.2 JWT Structure
|
|
|
|
```json
|
|
{
|
|
"aud": "https://push.service.com",
|
|
"exp": 1234567890,
|
|
"sub": "mailto:admin@example.com"
|
|
}
|
|
```
|
|
|
|
**Signature:** ES256 (ECDSA P-256 + SHA-256)
|
|
|
|
### 8.2 Web Push Encryption (RFC 8291)
|
|
|
|
#### 8.2.1 Encryption Flow
|
|
|
|
```
|
|
1. Decode client keys (p256dh, auth) from base64url
|
|
2. Generate ephemeral server EC key pair
|
|
3. Generate random 16-byte salt
|
|
4. ECDH with client public key → shared secret
|
|
5. HKDF-extract with auth secret
|
|
6. HKDF-expand for CEK (16 bytes) and nonce (12 bytes)
|
|
7. Pad plaintext (2-byte length prefix)
|
|
8. AES-128-GCM encrypt
|
|
```
|
|
|
|
#### 8.2.2 Message Format
|
|
|
|
```
|
|
salt (16) || record_size (4) || keyid_len (1) || keyid (65) || ciphertext
|
|
```
|
|
|
|
### 8.3 Notification Payload
|
|
|
|
```json
|
|
{
|
|
"title": "Permission Required",
|
|
"body": "Claude needs permission for: Write file.txt",
|
|
"sessionId": "uuid",
|
|
"sessionTitle": "My Session",
|
|
"tools": ["Write"]
|
|
}
|
|
```
|
|
|
|
### 8.4 Notification Timing
|
|
|
|
- **Delay:** 15 seconds after permission request
|
|
- **Condition:** Permission still pending (same message-id)
|
|
- **Auto-cleanup:** Subscriptions removed on 404/410 response
|
|
|
|
---
|
|
|
|
## 9. API Specification
|
|
|
|
### 9.1 Session Endpoints
|
|
|
|
#### GET /api/sessions
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{
|
|
"id": "uuid",
|
|
"provider": "claude",
|
|
"title": "My Session",
|
|
"status": "idle",
|
|
"working-dir": "/home/user/project",
|
|
"auto-accept-edits": false,
|
|
"created-at": "2024-01-01T00:00:00Z",
|
|
"updated-at": "2024-01-01T00:00:00Z"
|
|
}
|
|
]
|
|
```
|
|
|
|
#### POST /api/sessions
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"provider": "claude" | "opencode" | "tmux",
|
|
"working-dir": "/optional/path"
|
|
}
|
|
```
|
|
|
|
**Response:** Created session object
|
|
|
|
#### GET /api/sessions/:id
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"provider": "claude",
|
|
"messages": [
|
|
{
|
|
"id": "uuid",
|
|
"role": "user",
|
|
"content": "Hello",
|
|
"created-at": "2024-01-01T00:00:00Z"
|
|
}
|
|
],
|
|
"pending-permission": null
|
|
}
|
|
```
|
|
|
|
#### PATCH /api/sessions/:id
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"title": "New Title",
|
|
"auto-accept-edits": true
|
|
}
|
|
```
|
|
|
|
**Response:** Updated session object
|
|
|
|
#### DELETE /api/sessions/:id
|
|
|
|
**Response:** 204 No Content
|
|
|
|
### 9.2 Message Endpoints
|
|
|
|
#### POST /api/sessions/:id/send
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"message": "User message text"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"status": "sent"
|
|
}
|
|
```
|
|
|
|
#### POST /api/sessions/:id/permission
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"response": "accept" | "deny" | "steer",
|
|
"message": "Optional steer instructions"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"status": "permission-response-sent"
|
|
}
|
|
```
|
|
|
|
### 9.3 Terminal Endpoints
|
|
|
|
#### GET /api/sessions/:id/terminal
|
|
|
|
**Query Parameters:**
|
|
- `fresh=true` - Invalidate cache
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"content": "terminal output...",
|
|
"alive": true,
|
|
"session-name": "spiceflow-brave-fox-0042",
|
|
"layout": "landscape",
|
|
"diff": {
|
|
"type": "diff",
|
|
"changes": {"5": "new line content"},
|
|
"total-lines": 24,
|
|
"hash": 12345,
|
|
"frame-id": 100
|
|
}
|
|
}
|
|
```
|
|
|
|
#### POST /api/sessions/:id/terminal/input
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"input": "ls -la\r"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"status": "sent"
|
|
}
|
|
```
|
|
|
|
#### POST /api/sessions/:id/terminal/resize
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"mode": "landscape" | "portrait" | "desktop" | "fullscreen"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"status": "resized",
|
|
"mode": "landscape"
|
|
}
|
|
```
|
|
|
|
### 9.4 Push Endpoints
|
|
|
|
#### GET /api/push/vapid-key
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"publicKey": "base64url-encoded-public-key"
|
|
}
|
|
```
|
|
|
|
#### POST /api/push/subscribe
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"endpoint": "https://push.service.com/...",
|
|
"keys": {
|
|
"p256dh": "base64url-client-key",
|
|
"auth": "base64url-auth-secret"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": "subscription-uuid"
|
|
}
|
|
```
|
|
|
|
#### POST /api/push/unsubscribe
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"endpoint": "https://push.service.com/..."
|
|
}
|
|
```
|
|
|
|
**Response:** 204 No Content
|
|
|
|
### 9.5 Utility Endpoints
|
|
|
|
#### GET /api/health
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"service": "spiceflow"
|
|
}
|
|
```
|
|
|
|
#### GET /api/tmux/external
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{
|
|
"name": "my-session",
|
|
"working-dir": "/home/user"
|
|
}
|
|
]
|
|
```
|
|
|
|
#### POST /api/tmux/import
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"name": "my-session"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": "spiceflow-my-session",
|
|
"name": "spiceflow-my-session",
|
|
"working-dir": "/home/user"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 10. WebSocket Protocol
|
|
|
|
### 10.1 Connection
|
|
|
|
**Endpoint:** `/api/ws`
|
|
|
|
**Upgrade:** Standard WebSocket handshake
|
|
|
|
### 10.2 Client Messages
|
|
|
|
#### Subscribe to Session
|
|
```json
|
|
{
|
|
"type": "subscribe",
|
|
"session-id": "uuid"
|
|
}
|
|
```
|
|
|
|
#### Unsubscribe from Session
|
|
```json
|
|
{
|
|
"type": "unsubscribe",
|
|
"session-id": "uuid"
|
|
}
|
|
```
|
|
|
|
#### Heartbeat
|
|
```json
|
|
{
|
|
"type": "ping"
|
|
}
|
|
```
|
|
|
|
### 10.3 Server Messages
|
|
|
|
#### Connection Established
|
|
```json
|
|
{
|
|
"type": "connected"
|
|
}
|
|
```
|
|
|
|
#### Subscription Confirmed
|
|
```json
|
|
{
|
|
"type": "subscribed",
|
|
"session-id": "uuid"
|
|
}
|
|
```
|
|
|
|
#### Content Delta
|
|
```json
|
|
{
|
|
"event": "content-delta",
|
|
"session-id": "uuid",
|
|
"text": "streamed text chunk"
|
|
}
|
|
```
|
|
|
|
#### Permission Request
|
|
```json
|
|
{
|
|
"event": "permission-request",
|
|
"session-id": "uuid",
|
|
"permission-request": {
|
|
"tools": ["Write"],
|
|
"denials": [...]
|
|
},
|
|
"message-id": "uuid",
|
|
"message": {
|
|
"id": "uuid",
|
|
"content": "...",
|
|
"metadata": {...}
|
|
},
|
|
"auto-accepted": false
|
|
}
|
|
```
|
|
|
|
#### Message Stop
|
|
```json
|
|
{
|
|
"event": "message-stop",
|
|
"session-id": "uuid"
|
|
}
|
|
```
|
|
|
|
#### Terminal Update
|
|
```json
|
|
{
|
|
"event": "terminal-update",
|
|
"session-id": "uuid",
|
|
"content": "full terminal content",
|
|
"diff": {...}
|
|
}
|
|
```
|
|
|
|
#### Working Directory Update
|
|
```json
|
|
{
|
|
"event": "working-dir-update",
|
|
"session-id": "uuid",
|
|
"working-dir": "/new/path"
|
|
}
|
|
```
|
|
|
|
#### Error
|
|
```json
|
|
{
|
|
"event": "error",
|
|
"session-id": "uuid",
|
|
"message": "Error description"
|
|
}
|
|
```
|
|
|
|
#### Pong
|
|
```json
|
|
{
|
|
"type": "pong"
|
|
}
|
|
```
|
|
|
|
### 10.4 Reconnection Strategy
|
|
|
|
- **Max Attempts:** 5
|
|
- **Initial Delay:** 1 second
|
|
- **Backoff:** Exponential (1s, 2s, 4s, 8s, 16s)
|
|
- **Heartbeat:** Ping every 25 seconds
|
|
- **Pong Timeout:** 10 seconds
|
|
|
|
---
|
|
|
|
## 11. User Interface
|
|
|
|
### 11.1 Page Structure
|
|
|
|
#### Home Page (`/`)
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ ☰ Spiceflow 🔔 + ↻ │
|
|
├─────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌─────────────────────────────────┐ │
|
|
│ │ ● My Claude Session claude │ │
|
|
│ │ /home/user/project 2h ago │ │
|
|
│ └─────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────┐ │
|
|
│ │ ○ Terminal Session tmux │ │
|
|
│ │ /home/user 1d ago │ │
|
|
│ └─────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────┘
|
|
```
|
|
|
|
**Elements:**
|
|
- Header with branding and action buttons
|
|
- Session cards with status indicators
|
|
- Provider badges (color-coded)
|
|
- Relative timestamps
|
|
|
|
#### Session Page (`/session/:id`)
|
|
|
|
**Chat Mode (Claude/OpenCode):**
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ ← Session Title ⚙️ CLAUDE │
|
|
├─────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌─────────────────────────────────┐ │
|
|
│ │ User: Hello │ │
|
|
│ └─────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────┐ │
|
|
│ │ Assistant: Hi! How can I help? │ │
|
|
│ │ ● ● ● (thinking) │ │
|
|
│ └─────────────────────────────────┘ │
|
|
│ │
|
|
├─────────────────────────────────────────┤
|
|
│ [Type a message... ] [➤] │
|
|
└─────────────────────────────────────────┘
|
|
```
|
|
|
|
**Terminal Mode (tmux):**
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ ← brave-fox-0042 ⚙️ TERMINAL │
|
|
├─────────────────────────────────────────┤
|
|
│ $ pwd │
|
|
│ /home/user │
|
|
│ $ ls -la │
|
|
│ total 32 │
|
|
│ drwxr-xr-x 5 user user 4096 Jan 1 00:0│
|
|
│ -rw-r--r-- 1 user user 123 Jan 1 00:0│
|
|
│ $ │
|
|
├─────────────────────────────────────────┤
|
|
│ [^] [^C] [^D] | [y] [n] | [1-4] | [⇥] │
|
|
│ [-] 100% [+] | [📱] [📺] [🖥️] [⬜] [↓] │
|
|
└─────────────────────────────────────────┘
|
|
```
|
|
|
|
### 11.2 Permission UI
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ ⚠️ Claude needs permission │
|
|
├─────────────────────────────────────────┤
|
|
│ │
|
|
│ Write: /home/user/foo.md │
|
|
│ ┌─────────────────────────────────┐ │
|
|
│ │ + 1 # My Haiku │ │
|
|
│ │ + 2 │ │
|
|
│ │ + 3 Code flows like water │ │
|
|
│ │ + 4 Tests catch bugs silently │ │
|
|
│ │ + 5 Green lights bring peace │ │
|
|
│ └─────────────────────────────────┘ │
|
|
│ │
|
|
│ [Accept] [Deny] [No, and...] │
|
|
└─────────────────────────────────────────┘
|
|
```
|
|
|
|
### 11.3 Component Inventory
|
|
|
|
| Component | Purpose | Props |
|
|
|-----------|---------|-------|
|
|
| `SessionCard` | Session list item | `session` |
|
|
| `MessageList` | Chat history display | `messages, streamingContent, isThinking` |
|
|
| `InputBar` | Message input | `disabled, placeholder, autoFocus` |
|
|
| `PermissionRequest` | Permission approval UI | `permission, assistantName` |
|
|
| `FileDiff` | File change visualization | `tool, input, filePath` |
|
|
| `TerminalView` | Tmux display/input | `sessionId, autoScroll` |
|
|
| `SessionSettings` | Settings dropdown | `autoAcceptEdits, autoScroll, provider` |
|
|
| `PushToggle` | Notification toggle | (uses store) |
|
|
|
|
### 11.4 Responsive Behavior
|
|
|
|
| Breakpoint | Header | Layout |
|
|
|------------|--------|--------|
|
|
| Portrait | Full header | Vertical stack |
|
|
| Landscape mobile | Hamburger menu | Compact header |
|
|
| Desktop | Full header | All controls visible |
|
|
|
|
---
|
|
|
|
## 12. User Flows
|
|
|
|
### 12.1 Create and Chat with Claude
|
|
|
|
1. User opens app → sees session list
|
|
2. User taps **+** button → provider menu appears
|
|
3. User selects **Claude Code** → navigates to new session
|
|
4. User sees "No messages yet" state
|
|
5. User types message in input bar
|
|
6. User taps send → message appears immediately (optimistic)
|
|
7. Thinking indicator (●●●) appears
|
|
8. Streaming text appears incrementally
|
|
9. Response completes → thinking indicator disappears
|
|
10. User can continue conversation
|
|
|
|
### 12.2 Handle Permission Request
|
|
|
|
1. User sends message requesting file operation
|
|
2. Claude attempts Write/Edit/Bash
|
|
3. **Permission UI appears** with tool details
|
|
4. User reviews file diff (for Write/Edit)
|
|
5. User chooses:
|
|
- **Accept** → grants permission, streaming continues
|
|
- **Deny** → rejects, Claude finds alternative
|
|
- **No, and...** → enters steer mode
|
|
6. If steer: user types redirect instructions
|
|
7. Permission UI disappears
|
|
8. New response streams
|
|
|
|
### 12.3 Enable Auto-Accept
|
|
|
|
1. User opens session with Claude
|
|
2. User taps **⚙️** (settings gear)
|
|
3. User enables **Auto-accept edits** checkbox
|
|
4. Server saves preference (`auto-accept-edits: true`)
|
|
5. Future Write/Edit operations auto-approved
|
|
6. Permission messages appear with green ✓ status
|
|
7. No interruption for file operations
|
|
|
|
### 12.4 Use Terminal Session
|
|
|
|
1. User creates tmux session
|
|
2. User sees terminal with prompt
|
|
3. User taps terminal to focus
|
|
4. User types command (keyboard or quick buttons)
|
|
5. Input sent to tmux session
|
|
6. Output appears in terminal view
|
|
7. User can resize terminal (portrait/landscape/desktop/fullscreen)
|
|
8. User can use quick buttons (Ctrl+C, y/n, etc.)
|
|
|
|
### 12.5 Receive Push Notification
|
|
|
|
1. User enables push notifications (bell icon)
|
|
2. Browser prompts for permission
|
|
3. User grants permission
|
|
4. Subscription saved to server
|
|
5. Later: permission request pending for 15+ seconds
|
|
6. Server sends push notification
|
|
7. User's device shows notification
|
|
8. User taps notification → app opens to session
|
|
9. User sees permission UI, responds
|
|
|
|
---
|
|
|
|
## 13. Security Considerations
|
|
|
|
### 13.1 Permission Model
|
|
|
|
- **No auto-execute:** All tool operations require explicit approval (or opt-in auto-accept)
|
|
- **Auto-accept scope:** Limited to Write/Edit only, never Bash/network
|
|
- **Steer capability:** User can redirect AI without accepting dangerous operations
|
|
|
|
### 13.2 Push Security
|
|
|
|
- **VAPID authentication:** Server identity verified to push services
|
|
- **End-to-end encryption:** RFC 8291 AES-128-GCM encryption
|
|
- **Subscription cleanup:** Invalid subscriptions auto-removed
|
|
|
|
### 13.3 Data Protection
|
|
|
|
- **Local SQLite:** Data stored locally on server
|
|
- **No cloud sync:** Sessions not transmitted externally
|
|
- **Process isolation:** CLI processes run with server user permissions
|
|
|
|
### 13.4 API Security
|
|
|
|
- **CORS:** Configured for allowed origins
|
|
- **Input validation:** Session IDs, permission responses validated
|
|
- **No secrets in URLs:** Sensitive data in request bodies
|
|
|
|
---
|
|
|
|
## 14. Performance Requirements
|
|
|
|
### 14.1 Response Times
|
|
|
|
| Operation | Target | Maximum |
|
|
|-----------|--------|---------|
|
|
| Page load | <1s | 3s |
|
|
| Session list | <500ms | 2s |
|
|
| Message send | <100ms (optimistic) | 1s |
|
|
| WebSocket connect | <1s | 5s |
|
|
| Terminal input | <50ms | 200ms |
|
|
|
|
### 14.2 Streaming Performance
|
|
|
|
- **Content delta frequency:** Real-time as received
|
|
- **Terminal diff threshold:** >50% change triggers full refresh
|
|
- **Frame ordering:** Prevents out-of-order updates
|
|
|
|
### 14.3 Resource Usage
|
|
|
|
- **Active processes:** ConcurrentHashMap for thread-safe access
|
|
- **WebSocket connections:** Efficient broadcast to subscribers only
|
|
- **Terminal cache:** Per-session diff state
|
|
|
|
### 14.4 Scalability
|
|
|
|
- **Concurrent sessions:** Limited by server resources
|
|
- **WebSocket subscribers:** Multiple clients per session supported
|
|
- **Database:** SQLite (single-writer, multiple-reader)
|
|
|
|
---
|
|
|
|
## Appendix A: Configuration Reference
|
|
|
|
| Variable | Default | Description |
|
|
|----------|---------|-------------|
|
|
| `SPICEFLOW_PORT` | 3000 | Server port |
|
|
| `SPICEFLOW_HOST` | 0.0.0.0 | Server host |
|
|
| `SPICEFLOW_DB` | spiceflow.db | SQLite database path |
|
|
| `CLAUDE_SESSIONS_DIR` | ~/.claude/projects | Claude sessions directory |
|
|
| `OPENCODE_CMD` | opencode | OpenCode binary name |
|
|
|
|
## Appendix B: Database Schema
|
|
|
|
```sql
|
|
CREATE TABLE session_statuses (
|
|
id INTEGER PRIMARY KEY,
|
|
name TEXT NOT NULL UNIQUE
|
|
);
|
|
|
|
INSERT INTO session_statuses VALUES
|
|
(1, 'idle'), (2, 'processing'), (3, 'awaiting-permission');
|
|
|
|
CREATE TABLE sessions (
|
|
id TEXT PRIMARY KEY,
|
|
provider TEXT NOT NULL,
|
|
external_id TEXT,
|
|
title TEXT,
|
|
working_dir TEXT,
|
|
spawn_dir TEXT,
|
|
status_id INTEGER DEFAULT 1 REFERENCES session_statuses(id),
|
|
pending_permission TEXT,
|
|
auto_accept_edits INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE messages (
|
|
id TEXT PRIMARY KEY,
|
|
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
role TEXT NOT NULL,
|
|
content TEXT NOT NULL,
|
|
metadata TEXT,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE push_subscriptions (
|
|
id TEXT PRIMARY KEY,
|
|
endpoint TEXT NOT NULL UNIQUE,
|
|
p256dh TEXT NOT NULL,
|
|
auth TEXT NOT NULL,
|
|
user_agent TEXT,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE vapid_keys (
|
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
public_key TEXT NOT NULL,
|
|
private_key TEXT NOT NULL,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
```
|
|
|
|
## Appendix C: CLI Command Reference
|
|
|
|
### Claude Code
|
|
|
|
```bash
|
|
claude --output-format stream-json \
|
|
--input-format stream-json \
|
|
--verbose --print \
|
|
--resume <session-id> \
|
|
--allowedTools Write Edit \
|
|
-- <working-dir>
|
|
```
|
|
|
|
### OpenCode
|
|
|
|
```bash
|
|
script -qc "opencode run --format json --session <id> <message>" /dev/null
|
|
```
|
|
|
|
### tmux
|
|
|
|
```bash
|
|
# Create session
|
|
tmux new-session -d -s <name> -c <dir>
|
|
|
|
# Send keys
|
|
tmux send-keys -t <name> [-l] "<input>" [Enter]
|
|
|
|
# Capture pane
|
|
tmux capture-pane -t <name> -p -e -S -1000
|
|
|
|
# Resize window
|
|
tmux resize-window -t <name> -x <width> -y <height>
|
|
|
|
# Pipe output
|
|
tmux pipe-pane -t <name> "cat >> <file>"
|
|
```
|