plan
This commit is contained in:
@@ -0,0 +1,302 @@
|
|||||||
|
# Spiceflow - AI Session Orchestration PWA
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
A Progressive Web App for monitoring and interacting with running Claude Code and OpenCode sessions from mobile devices. "The spice must flow."
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐ ┌─────────────────────────┐ ┌─────────────────┐
|
||||||
|
│ Claude Code │◀───▶│ Spiceflow Server │◀───▶│ PWA Client │
|
||||||
|
│ (CLI) │ │ (Clojure) │ │ (Svelte) │
|
||||||
|
└─────────────────┘ │ │ └─────────────────┘
|
||||||
|
│ ┌─────────────────┐ │
|
||||||
|
┌─────────────────┐ │ │ SQLite + DB │ │
|
||||||
|
│ OpenCode │◀───▶│ │ Abstraction │ │
|
||||||
|
│ (CLI) │ │ │ WebSocket/SSE │ │
|
||||||
|
└─────────────────┘ │ └─────────────────┘ │
|
||||||
|
└─────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## CLI Analysis
|
||||||
|
|
||||||
|
### Claude Code
|
||||||
|
- **Session storage**: `~/.claude/projects/{encoded-path}/{session-id}.jsonl`
|
||||||
|
- **Resume session**: `claude --resume <session-id>` or `claude --session-id <uuid>`
|
||||||
|
- **Programmatic I/O**: `--output-format stream-json` + `--input-format stream-json` + `--print`
|
||||||
|
- **JSONL format**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "user",
|
||||||
|
"sessionId": "uuid",
|
||||||
|
"parentUuid": "uuid|null",
|
||||||
|
"message": {"role": "user", "content": "..."},
|
||||||
|
"uuid": "message-uuid",
|
||||||
|
"timestamp": "ISO-8601"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Key flags**: `--permission-mode`, `--model`, `--continue`
|
||||||
|
|
||||||
|
### OpenCode
|
||||||
|
- **List sessions**: `opencode session list`
|
||||||
|
- **Export session**: `opencode export <session-id>` (JSON)
|
||||||
|
- **Continue session**: `opencode --session <session-id>` or `--continue`
|
||||||
|
- **Headless mode**: `opencode serve` (starts server), `opencode run <message>`
|
||||||
|
- **JSON format**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"info": {"id": "ses_xxx", "title": "...", "time": {...}},
|
||||||
|
"messages": [{"info": {...}, "parts": [{type, text, ...}]}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
- **Frontend**: SvelteKit + TypeScript + Vite
|
||||||
|
- **PWA**: vite-plugin-pwa (Workbox under the hood)
|
||||||
|
- **Styling**: Tailwind CSS (mobile-first)
|
||||||
|
- **Backend**: Clojure + Ring/Jetty + next.jdbc
|
||||||
|
- **Database**: SQLite (via generic interface for future swapping)
|
||||||
|
- **Real-time**: WebSocket (via Ring adapter) or SSE
|
||||||
|
- **Build**: deps.edn (Clojure), pnpm (frontend)
|
||||||
|
|
||||||
|
## Supported Agentic Runtimes
|
||||||
|
|
||||||
|
The UI includes a **runtime selector dropdown** for choosing which agentic CLI to use:
|
||||||
|
|
||||||
|
| Runtime | Status | Notes |
|
||||||
|
|---------|--------|-------|
|
||||||
|
| **Claude Code** | Default | Anthropic's official CLI |
|
||||||
|
| OpenCode | Supported | Open-source alternative |
|
||||||
|
| *(Future)* | Planned | Aider, Cursor CLI, Continue, etc. |
|
||||||
|
|
||||||
|
The adapter system is designed to be extensible—adding a new runtime requires implementing the `AgentAdapter` protocol.
|
||||||
|
|
||||||
|
## Mobile UI Design
|
||||||
|
|
||||||
|
The PWA is **mobile-first**. Key design decisions:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────┐
|
||||||
|
│ ☰ Spiceflow [Claude ▾] │ ← Header + runtime dropdown
|
||||||
|
├─────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ▶ User: Fix the login... │ ← Collapsed (tap to expand)
|
||||||
|
│ ▼ Assistant: I'll fix... │ ← Expanded message
|
||||||
|
│ [full content here] │
|
||||||
|
│ ... │
|
||||||
|
│ ▶ User: Now add tests │ ← Collapsed
|
||||||
|
│ ▶ Assistant: Added 3... │ ← Collapsed
|
||||||
|
│ │
|
||||||
|
├─────────────────────────────┤
|
||||||
|
│ [Type message...] [▶] │ ← Small fixed input bar
|
||||||
|
└─────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Collapsible messages**: Each message shows a preview (first line or summary). Tap to expand full content. Keeps history skimmable.
|
||||||
|
- **Fixed input bar**: Small, anchored to bottom. Expands only when focused.
|
||||||
|
- **Touch-friendly**: Large tap targets, swipe gestures for navigation.
|
||||||
|
- **Session list**: Cards showing title, runtime badge, last activity. Pull-to-refresh.
|
||||||
|
|
||||||
|
## Database Abstraction
|
||||||
|
|
||||||
|
Generic interface to allow mocking in tests or swapping providers:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; src/spiceflow/db/protocol.clj
|
||||||
|
(defprotocol DataStore
|
||||||
|
(get-sessions [this])
|
||||||
|
(get-session [this id])
|
||||||
|
(save-session [this session])
|
||||||
|
(update-session [this id data])
|
||||||
|
(delete-session [this id])
|
||||||
|
(get-messages [this session-id])
|
||||||
|
(save-message [this message]))
|
||||||
|
|
||||||
|
;; Implementations:
|
||||||
|
;; - SQLiteStore (production)
|
||||||
|
;; - AtomStore (testing/mocking)
|
||||||
|
;; - Future: PostgresStore, etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
SQLite schema:
|
||||||
|
```sql
|
||||||
|
CREATE TABLE sessions (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
provider TEXT NOT NULL, -- 'claude' | 'opencode'
|
||||||
|
external_id TEXT, -- original session ID from CLI
|
||||||
|
title TEXT,
|
||||||
|
working_dir TEXT,
|
||||||
|
status TEXT DEFAULT 'idle', -- 'idle' | 'running' | 'completed'
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE messages (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
session_id TEXT REFERENCES sessions(id),
|
||||||
|
role TEXT NOT NULL, -- 'user' | 'assistant' | 'system'
|
||||||
|
content TEXT,
|
||||||
|
metadata TEXT, -- JSON blob for tool calls, etc.
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
spiceflow/
|
||||||
|
├── server/ # Clojure backend
|
||||||
|
│ ├── deps.edn
|
||||||
|
│ ├── src/spiceflow/
|
||||||
|
│ │ ├── core.clj # Entry point
|
||||||
|
│ │ ├── config.clj # Configuration
|
||||||
|
│ │ ├── db/
|
||||||
|
│ │ │ ├── protocol.clj # DataStore protocol
|
||||||
|
│ │ │ ├── sqlite.clj # SQLite implementation
|
||||||
|
│ │ │ └── memory.clj # In-memory impl for tests
|
||||||
|
│ │ ├── adapters/
|
||||||
|
│ │ │ ├── protocol.clj # AgentAdapter protocol
|
||||||
|
│ │ │ ├── claude.clj # Claude Code adapter
|
||||||
|
│ │ │ └── opencode.clj # OpenCode adapter
|
||||||
|
│ │ ├── api/
|
||||||
|
│ │ │ ├── routes.clj # REST endpoints
|
||||||
|
│ │ │ └── websocket.clj # WebSocket handlers
|
||||||
|
│ │ └── session/
|
||||||
|
│ │ └── manager.clj # Session lifecycle
|
||||||
|
│ ├── test/spiceflow/
|
||||||
|
│ │ ├── db_test.clj
|
||||||
|
│ │ └── adapters_test.clj
|
||||||
|
│ └── resources/
|
||||||
|
│ └── migrations/ # SQL migrations
|
||||||
|
│
|
||||||
|
├── client/ # SvelteKit PWA
|
||||||
|
│ ├── package.json
|
||||||
|
│ ├── svelte.config.js
|
||||||
|
│ ├── vite.config.ts
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── routes/
|
||||||
|
│ │ │ ├── +layout.svelte
|
||||||
|
│ │ │ ├── +page.svelte # Session list
|
||||||
|
│ │ │ └── session/
|
||||||
|
│ │ │ └── [id]/
|
||||||
|
│ │ │ └── +page.svelte
|
||||||
|
│ │ ├── lib/
|
||||||
|
│ │ │ ├── stores/
|
||||||
|
│ │ │ │ ├── runtime.ts # Selected runtime (persisted)
|
||||||
|
│ │ │ │ └── sessions.ts
|
||||||
|
│ │ │ ├── components/
|
||||||
|
│ │ │ │ ├── RuntimeSelector.svelte # Dropdown for agentic runtime
|
||||||
|
│ │ │ │ ├── SessionCard.svelte # Session list card
|
||||||
|
│ │ │ │ ├── MessageList.svelte # Scrollable message container
|
||||||
|
│ │ │ │ ├── CollapsibleMessage.svelte # Expandable message item
|
||||||
|
│ │ │ │ └── InputBar.svelte # Fixed bottom input
|
||||||
|
│ │ │ └── api.ts # Server API client
|
||||||
|
│ │ └── app.html
|
||||||
|
│ ├── static/
|
||||||
|
│ │ └── manifest.json
|
||||||
|
│ └── tests/
|
||||||
|
│
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Flow: Continuing a Session
|
||||||
|
|
||||||
|
1. **User opens PWA** → sees list of tracked sessions from SQLite
|
||||||
|
2. **User selects session** → sees message history, status
|
||||||
|
3. **User types message** → POST to `/api/sessions/:id/send`
|
||||||
|
4. **Server receives message**:
|
||||||
|
- Saves message to SQLite
|
||||||
|
- Spawns CLI process: `claude --resume <external_id> --print --output-format stream-json --input-format stream-json`
|
||||||
|
- Pipes user message to stdin
|
||||||
|
- Streams stdout back via WebSocket
|
||||||
|
5. **Client receives streamed response** → renders in real-time
|
||||||
|
6. **Process completes** → server saves assistant message to SQLite
|
||||||
|
|
||||||
|
## Implementation Phases
|
||||||
|
|
||||||
|
### Phase 1: Project Scaffolding
|
||||||
|
1. Initialize Clojure project with deps.edn
|
||||||
|
2. Initialize SvelteKit project with TypeScript
|
||||||
|
3. Set up Tailwind CSS
|
||||||
|
4. Create basic directory structure
|
||||||
|
|
||||||
|
### Phase 2: Database Layer
|
||||||
|
1. Define DataStore protocol
|
||||||
|
2. Implement SQLite store with next.jdbc
|
||||||
|
3. Implement in-memory store for tests
|
||||||
|
4. Write migrations
|
||||||
|
5. Write tests for both implementations
|
||||||
|
|
||||||
|
### Phase 3: Server Core
|
||||||
|
1. Ring server setup with Jetty
|
||||||
|
2. REST API: GET/POST /sessions, GET /sessions/:id
|
||||||
|
3. WebSocket setup for real-time streaming
|
||||||
|
4. Configuration management
|
||||||
|
|
||||||
|
### Phase 4: Agent Adapters
|
||||||
|
1. Define AgentAdapter protocol (discover, spawn, send, stream)
|
||||||
|
2. Claude Code adapter:
|
||||||
|
- Discover sessions from `~/.claude/projects/`
|
||||||
|
- Spawn with `--resume --print --output-format stream-json --input-format stream-json`
|
||||||
|
- Parse JSONL output
|
||||||
|
3. OpenCode adapter:
|
||||||
|
- Discover via `opencode session list`
|
||||||
|
- Spawn with `opencode run --session <id>`
|
||||||
|
- Parse JSON output
|
||||||
|
|
||||||
|
### Phase 5: Client Core
|
||||||
|
1. SvelteKit routing setup
|
||||||
|
2. Runtime selector dropdown (Claude Code default, OpenCode, extensible for future runtimes)
|
||||||
|
3. Session list page filtered by selected runtime
|
||||||
|
4. Session detail page with message history
|
||||||
|
5. Real-time message streaming via WebSocket
|
||||||
|
6. Message input component
|
||||||
|
|
||||||
|
### Phase 6: PWA Setup
|
||||||
|
1. Configure vite-plugin-pwa
|
||||||
|
2. Web app manifest (icons, theme color, display)
|
||||||
|
3. Service worker for offline caching
|
||||||
|
4. Install prompt handling
|
||||||
|
|
||||||
|
### Phase 7: Interaction Features
|
||||||
|
1. Send message to session
|
||||||
|
2. Display tool calls and outputs
|
||||||
|
3. Session status indicators
|
||||||
|
4. Error handling and reconnection
|
||||||
|
|
||||||
|
## Key Files to Create
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `server/deps.edn` | Clojure dependencies |
|
||||||
|
| `server/src/spiceflow/db/protocol.clj` | DataStore protocol |
|
||||||
|
| `server/src/spiceflow/db/sqlite.clj` | SQLite implementation |
|
||||||
|
| `server/src/spiceflow/adapters/claude.clj` | Claude CLI adapter |
|
||||||
|
| `server/src/spiceflow/api/routes.clj` | REST API |
|
||||||
|
| `client/src/routes/+page.svelte` | Session list |
|
||||||
|
| `client/src/lib/components/RuntimeSelector.svelte` | Runtime dropdown (Claude default) |
|
||||||
|
| `client/src/lib/stores/sessions.ts` | Session state |
|
||||||
|
| `client/static/manifest.json` | PWA manifest |
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
1. **Database test**: Run `clj -M:test` → all DataStore tests pass
|
||||||
|
2. **Server test**: `clj -M:run`, hit `GET /api/sessions` → returns `[]`
|
||||||
|
3. **Adapter test**: Claude adapter discovers existing sessions from ~/.claude
|
||||||
|
4. **Client test**: `pnpm dev`, open in browser → see session list
|
||||||
|
5. **PWA test**: Lighthouse audit → PWA criteria met
|
||||||
|
6. **E2E test**: Send message via PWA → see response streamed back
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/runtimes List available runtimes (claude=default, opencode, etc.)
|
||||||
|
GET /api/sessions List all tracked sessions (?runtime=claude filter)
|
||||||
|
POST /api/sessions Import/track a new session
|
||||||
|
GET /api/sessions/:id Get session details + messages
|
||||||
|
POST /api/sessions/:id/send Send message to session
|
||||||
|
WS /api/ws WebSocket for real-time updates
|
||||||
|
|
||||||
|
GET /api/discover/claude Discover Claude sessions
|
||||||
|
GET /api/discover/opencode Discover OpenCode sessions
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user