vibe-reader 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vibe_reader-0.1.0/.github/workflows/publish.yml +30 -0
- vibe_reader-0.1.0/.gitignore +29 -0
- vibe_reader-0.1.0/.python-version +1 -0
- vibe_reader-0.1.0/AGENTS.md +35 -0
- vibe_reader-0.1.0/CLAUDE.md +99 -0
- vibe_reader-0.1.0/PKG-INFO +138 -0
- vibe_reader-0.1.0/README.md +122 -0
- vibe_reader-0.1.0/backend/__init__.py +1 -0
- vibe_reader-0.1.0/backend/files.py +100 -0
- vibe_reader-0.1.0/backend/main.py +26 -0
- vibe_reader-0.1.0/backend/rendering.py +146 -0
- vibe_reader-0.1.0/backend/tmux.py +88 -0
- vibe_reader-0.1.0/frontend/app.js +617 -0
- vibe_reader-0.1.0/frontend/favicon.png +0 -0
- vibe_reader-0.1.0/frontend/favicon.svg +6 -0
- vibe_reader-0.1.0/frontend/index.html +120 -0
- vibe_reader-0.1.0/frontend/styles.css +520 -0
- vibe_reader-0.1.0/pyproject.toml +35 -0
- vibe_reader-0.1.0/tests/test_files.py +86 -0
- vibe_reader-0.1.0/uv.lock +313 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: '3.13'
|
|
22
|
+
|
|
23
|
+
- name: Install uv
|
|
24
|
+
uses: astral-sh/setup-uv@v5
|
|
25
|
+
|
|
26
|
+
- name: Build package
|
|
27
|
+
run: uv build
|
|
28
|
+
|
|
29
|
+
- name: Publish to PyPI
|
|
30
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
*.egg-info/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
|
|
11
|
+
# Virtual environments
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
ENV/
|
|
15
|
+
env/
|
|
16
|
+
|
|
17
|
+
# IDE
|
|
18
|
+
.vscode/
|
|
19
|
+
.idea/
|
|
20
|
+
*.swp
|
|
21
|
+
*.swo
|
|
22
|
+
*~
|
|
23
|
+
|
|
24
|
+
# OS
|
|
25
|
+
.DS_Store
|
|
26
|
+
Thumbs.db
|
|
27
|
+
|
|
28
|
+
# Project specific
|
|
29
|
+
*.log
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Repository Guidelines
|
|
2
|
+
|
|
3
|
+
## Project Structure & Module Organization
|
|
4
|
+
- `backend/`: FastAPI routers (`main.py`, `tmux.py`, `files.py`) and any new backend utilities.
|
|
5
|
+
- `frontend/`: eink UI assets (`index.html`, `app.js`, `styles.css`) with vanilla JS and utility-first CSS.
|
|
6
|
+
- `src/vibe_reader/`: packaging glue and shared Python modules; keep type markers here.
|
|
7
|
+
- `tests/`: mirror backend modules with `test_*` files (e.g., `tests/test_tmux.py`, `tests/test_files.py`).
|
|
8
|
+
|
|
9
|
+
## Build, Test, and Development Commands
|
|
10
|
+
- `uv sync`: install project dependencies (add `HTTP(S)_PROXY` env vars when required).
|
|
11
|
+
- `uv run uvicorn backend.main:app --host 0.0.0.0 --port 28000 --reload`: local dev server with hot reload.
|
|
12
|
+
- `uv run uvicorn backend.main:app --host 0.0.0.0 --port 28000`: production preview.
|
|
13
|
+
- `uv run pytest`: execute the test suite once pytest is listed under dev dependencies.
|
|
14
|
+
|
|
15
|
+
## Coding Style & Naming Conventions
|
|
16
|
+
- Python: 3.10+, four-space indents, type hints, and FastAPI error handling via `HTTPException`.
|
|
17
|
+
- JavaScript: keep DOM refs camelCase at the top of `frontend/app.js`; stay framework-free.
|
|
18
|
+
- CSS: high-contrast, animation-free styles optimized for eink; prefer reusable utility classes.
|
|
19
|
+
- Share helpers across routers via dedicated modules in `backend/`.
|
|
20
|
+
|
|
21
|
+
## Testing Guidelines
|
|
22
|
+
- Use `pytest` with `pytest-asyncio` for coroutine coverage; mock `libtmux.Server`.
|
|
23
|
+
- Target ≥80% coverage on new paths; assert edge cases (empty tmux sessions, missing panes, binary file rejection).
|
|
24
|
+
- Name tests `test_*` and keep fixtures local when possible.
|
|
25
|
+
|
|
26
|
+
## Commit & Pull Request Guidelines
|
|
27
|
+
- Commit subjects: imperative mood (“Add scrollback scrollbar”); include brief bodies for behavior changes.
|
|
28
|
+
- Reference issues with `Fixes #123` when applicable.
|
|
29
|
+
- PRs should summarize intent, list validation steps (`uv run pytest`, tmux capture checks, file browser smoke tests), and include eink UI screenshots for frontend tweaks.
|
|
30
|
+
- Request cross-review when touching both backend and frontend layers.
|
|
31
|
+
|
|
32
|
+
## Security & Configuration Tips
|
|
33
|
+
- Expose the backend only behind VPN/SSH; never ship tmux without access controls.
|
|
34
|
+
- Keep proxy env vars out of committed scripts.
|
|
35
|
+
- Document new flags in `README.md` and surface user-configurable options in the Config view.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Vibe Reader is a web application for reading terminal output and code files on eink devices during vibe coding sessions. It provides eink-optimized UI with scrollback history, visual scrollbars, and configuration options.
|
|
8
|
+
|
|
9
|
+
## Development Commands
|
|
10
|
+
|
|
11
|
+
### Running the Server
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Development with auto-reload
|
|
15
|
+
uv run uvicorn backend.main:app --host 0.0.0.0 --port 28000 --reload
|
|
16
|
+
|
|
17
|
+
# Production
|
|
18
|
+
uv run uvicorn backend.main:app --host 0.0.0.0 --port 28000
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Dependency Management
|
|
22
|
+
|
|
23
|
+
If you encounter network issues, use the proxy:
|
|
24
|
+
```bash
|
|
25
|
+
HTTP_PROXY=http://127.0.0.1:9082 HTTPS_PROXY=http://127.0.0.1:9082 uv sync
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Architecture
|
|
29
|
+
|
|
30
|
+
### Backend (FastAPI + libtmux)
|
|
31
|
+
|
|
32
|
+
- **Modular routers**:
|
|
33
|
+
- `backend/main.py` - Main app with router includes
|
|
34
|
+
- `backend/tmux.py` - Tmux endpoints (APIRouter)
|
|
35
|
+
- `backend/files.py` - File browser endpoints (APIRouter)
|
|
36
|
+
- **Static file serving**: FastAPI serves frontend from `/frontend` directory at `/static`
|
|
37
|
+
- **Tmux integration**: Uses libtmux library to access tmux server with 1000-line scrollback
|
|
38
|
+
|
|
39
|
+
### Frontend (Vanilla JS)
|
|
40
|
+
|
|
41
|
+
- **Single-page application**: All UI in `frontend/index.html`
|
|
42
|
+
- **Three views**: Tmux, Files, and Config (toggle via navbar)
|
|
43
|
+
- **Layout**: Collapsible sidebar + main content area + visual scrollbars
|
|
44
|
+
- **Config persistence**: localStorage for scrollback lines and font size settings
|
|
45
|
+
- **Eink optimizations**: No animations, high contrast, instant updates (`scroll-behavior: auto`, `transition: none`)
|
|
46
|
+
|
|
47
|
+
### Critical Implementation Details
|
|
48
|
+
|
|
49
|
+
**Tmux Window/Pane Indexing**:
|
|
50
|
+
- Tmux uses actual indices (can be non-contiguous: 2, 3, 4, 5, 6)
|
|
51
|
+
- DO NOT use array positions to access windows/panes
|
|
52
|
+
- Always iterate to find matching `window.index` or `pane.index`
|
|
53
|
+
- See `backend/tmux.py:47-94` for correct implementation
|
|
54
|
+
|
|
55
|
+
**Tmux Scrollback History**:
|
|
56
|
+
- Backend captures last N lines (default 1000, configurable)
|
|
57
|
+
- Uses `pane.capture_pane(start='-1000')` to get scrollback
|
|
58
|
+
- Returns only `content` string
|
|
59
|
+
|
|
60
|
+
**Scrollbar Implementation**:
|
|
61
|
+
- Traditional scrollbar design with thumb showing viewport size
|
|
62
|
+
- Tmux: Bar represents total scrollback, thumb shows viewport position
|
|
63
|
+
- Files: Bar represents file length, thumb shows viewport position
|
|
64
|
+
- Position calculation uses scroll ratio: `currentScrollRatio = scrollTop / maxScrollTop`
|
|
65
|
+
- Thumb position: `thumbTop = (scrollbarHeight - thumbHeight) * currentScrollRatio`
|
|
66
|
+
|
|
67
|
+
**Single-pane Window Handling**:
|
|
68
|
+
- Windows with one pane show window name as clickable (no pane list)
|
|
69
|
+
- Detect via `window.panes.length === 1` in frontend
|
|
70
|
+
- Apply `.single-pane` class for special styling
|
|
71
|
+
|
|
72
|
+
**Auto-scroll Behavior**:
|
|
73
|
+
- Tmux: Auto-scroll to end of content when enabled, preserve scroll position when disabled
|
|
74
|
+
- File content: Always scrolls to top when loaded
|
|
75
|
+
- Manual scrolling: Page up/down buttons scroll 90% of viewport height
|
|
76
|
+
|
|
77
|
+
**Configuration**:
|
|
78
|
+
- Settings stored in localStorage as JSON
|
|
79
|
+
- `scrollbackLines`: 100-10000 (default 1000)
|
|
80
|
+
- `fontSize`: 12-20px (default 14px)
|
|
81
|
+
- Applied dynamically via `element.style.fontSize`
|
|
82
|
+
|
|
83
|
+
## API Endpoints
|
|
84
|
+
|
|
85
|
+
### Tmux (backend/tmux.py)
|
|
86
|
+
- `GET /api/tmux/tree` - Returns full session/window/pane hierarchy
|
|
87
|
+
- `GET /api/tmux/pane/{session}/{window}/{pane}` - Captures pane content with scrollback
|
|
88
|
+
- Returns: `content` (string)
|
|
89
|
+
|
|
90
|
+
### Files (backend/files.py)
|
|
91
|
+
- `GET /api/files?path=<path>` - Lists directory contents
|
|
92
|
+
- `GET /api/files/content?path=<path>` - Returns file content
|
|
93
|
+
|
|
94
|
+
## Design Constraints
|
|
95
|
+
|
|
96
|
+
1. **Eink-first**: No animations, transitions, or smooth scrolling
|
|
97
|
+
2. **Single-page viewport**: All content must fit without page scrolling
|
|
98
|
+
3. **High contrast**: Black/white color scheme only
|
|
99
|
+
4. **Space-efficient**: Collapsible sidebar, compact controls
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vibe-reader
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author-email: PsychArch <PsychArch@github.com>
|
|
6
|
+
Requires-Python: >=3.13
|
|
7
|
+
Requires-Dist: fastapi
|
|
8
|
+
Requires-Dist: libtmux
|
|
9
|
+
Requires-Dist: markdown
|
|
10
|
+
Requires-Dist: pygments
|
|
11
|
+
Requires-Dist: uvicorn
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: httpx; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Vibe Reader
|
|
18
|
+
|
|
19
|
+
Read terminal output and code files on your eink device. Designed for comfortable reading during vibe coding sessions.
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
### Tmux Integration
|
|
24
|
+
- Browse and view tmux session/window/pane output
|
|
25
|
+
- **1000-line scrollback history** capture (configurable)
|
|
26
|
+
- **Smart auto-scroll**: Follow content end or freeze position
|
|
27
|
+
- **Visual scrollbar**: Shows position in scrollback and viewport size
|
|
28
|
+
|
|
29
|
+
### File Browser
|
|
30
|
+
- Navigate directories and view file contents
|
|
31
|
+
- Visual scrollbar showing file length and current position
|
|
32
|
+
- Server-side syntax highlighting for code and Markdown (tables supported)
|
|
33
|
+
|
|
34
|
+
### Configuration
|
|
35
|
+
- Adjustable scrollback lines (100-10000)
|
|
36
|
+
- Font size selection (12-20px)
|
|
37
|
+
- Settings persist via localStorage
|
|
38
|
+
|
|
39
|
+
### Eink-Optimized UI
|
|
40
|
+
- High contrast, large fonts, no animations
|
|
41
|
+
- Collapsible sidebar, compact window display
|
|
42
|
+
- Page up/down buttons for manual scrolling
|
|
43
|
+
- Single-page design that fits viewport
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Install dependencies (use proxy if needed)
|
|
49
|
+
HTTP_PROXY=http://127.0.0.1:9082 HTTPS_PROXY=http://127.0.0.1:9082 uv sync
|
|
50
|
+
|
|
51
|
+
# Start server (restricts file browsing to current directory by default)
|
|
52
|
+
uv run uvicorn backend.main:app --host 0.0.0.0 --port 28000
|
|
53
|
+
|
|
54
|
+
# Optional: Set custom project root for file browser
|
|
55
|
+
VIBE_READER_ROOT=/path/to/project uv run uvicorn backend.main:app --host 0.0.0.0 --port 28000
|
|
56
|
+
|
|
57
|
+
# Open on your eink device
|
|
58
|
+
# Navigate to: http://YOUR_SERVER_IP:28000
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
### Tmux View
|
|
64
|
+
- Click **☰** to toggle sidebar
|
|
65
|
+
- Select session → window (→ pane if multiple)
|
|
66
|
+
- Click **Refresh** to update content
|
|
67
|
+
- Use **Page ↑/↓** for manual scrolling
|
|
68
|
+
- Toggle **Auto↓** to enable/disable auto-scroll to end
|
|
69
|
+
- **Scrollbar** shows your position in scrollback history
|
|
70
|
+
|
|
71
|
+
### Files View
|
|
72
|
+
- Enter path or browse directories
|
|
73
|
+
- Click folders to navigate, files to view
|
|
74
|
+
- **Page ↑/↓** for scrolling
|
|
75
|
+
- **Scrollbar** shows file length and current position
|
|
76
|
+
- **Security**: File browsing is restricted to the project root (configurable via `VIBE_READER_ROOT`)
|
|
77
|
+
- **File size limit**: Maximum 10MB per file
|
|
78
|
+
|
|
79
|
+
### Config View
|
|
80
|
+
- Adjust scrollback lines (100-10000, default 1000)
|
|
81
|
+
- Change font size (12-20px, default 14px)
|
|
82
|
+
- Set auto-refresh interval (0-60 seconds, default 5s)
|
|
83
|
+
- Click **Save Settings** to persist changes
|
|
84
|
+
|
|
85
|
+
## Technical Details
|
|
86
|
+
|
|
87
|
+
**Backend**: FastAPI + libtmux
|
|
88
|
+
**Frontend**: Vanilla JS, eink-optimized CSS
|
|
89
|
+
**Package Manager**: uv
|
|
90
|
+
|
|
91
|
+
### Environment Variables
|
|
92
|
+
|
|
93
|
+
- `VIBE_READER_ROOT`: Sets the root directory for file browsing (default: current working directory)
|
|
94
|
+
- Files outside this directory cannot be accessed
|
|
95
|
+
- Use this to restrict file browsing to a specific project
|
|
96
|
+
|
|
97
|
+
### API Endpoints
|
|
98
|
+
|
|
99
|
+
**Tmux:**
|
|
100
|
+
- `GET /api/tmux/tree` - List all sessions/windows/panes
|
|
101
|
+
- `GET /api/tmux/pane/{session}/{window}/{pane}?scrollback=1000` - Get pane content with configurable scrollback
|
|
102
|
+
|
|
103
|
+
**Files:**
|
|
104
|
+
- `GET /api/files?path=.` - List directory contents (relative to project root)
|
|
105
|
+
- `GET /api/files/content?path=file.txt` - Render file content (max 10MB) with fields `path`, `render_mode`, `html`, and `metadata`
|
|
106
|
+
|
|
107
|
+
## Design Principles
|
|
108
|
+
|
|
109
|
+
1. **Eink-first**: No animations, high contrast, instant updates
|
|
110
|
+
2. **Minimal overhead**: Direct tmux capture, simple file operations
|
|
111
|
+
3. **Space-efficient**: Every pixel counts on small eink screens
|
|
112
|
+
4. **Single-page**: All content fits viewport, reduces refresh artifacts
|
|
113
|
+
|
|
114
|
+
## Project Structure
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
vibe-reader/
|
|
118
|
+
backend/
|
|
119
|
+
main.py # FastAPI app with router includes
|
|
120
|
+
tmux.py # Tmux endpoints
|
|
121
|
+
files.py # File browser endpoints
|
|
122
|
+
frontend/
|
|
123
|
+
index.html # Single-page UI (Tmux/Files/Config views)
|
|
124
|
+
styles.css # Eink-optimized styles
|
|
125
|
+
app.js # Client logic with scrollbar management
|
|
126
|
+
pyproject.toml # uv configuration
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
## Requirements
|
|
131
|
+
|
|
132
|
+
- Python 3.13+
|
|
133
|
+
- tmux (for tmux viewing feature)
|
|
134
|
+
- Modern browser on eink device
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Vibe Reader
|
|
2
|
+
|
|
3
|
+
Read terminal output and code files on your eink device. Designed for comfortable reading during vibe coding sessions.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### Tmux Integration
|
|
8
|
+
- Browse and view tmux session/window/pane output
|
|
9
|
+
- **1000-line scrollback history** capture (configurable)
|
|
10
|
+
- **Smart auto-scroll**: Follow content end or freeze position
|
|
11
|
+
- **Visual scrollbar**: Shows position in scrollback and viewport size
|
|
12
|
+
|
|
13
|
+
### File Browser
|
|
14
|
+
- Navigate directories and view file contents
|
|
15
|
+
- Visual scrollbar showing file length and current position
|
|
16
|
+
- Server-side syntax highlighting for code and Markdown (tables supported)
|
|
17
|
+
|
|
18
|
+
### Configuration
|
|
19
|
+
- Adjustable scrollback lines (100-10000)
|
|
20
|
+
- Font size selection (12-20px)
|
|
21
|
+
- Settings persist via localStorage
|
|
22
|
+
|
|
23
|
+
### Eink-Optimized UI
|
|
24
|
+
- High contrast, large fonts, no animations
|
|
25
|
+
- Collapsible sidebar, compact window display
|
|
26
|
+
- Page up/down buttons for manual scrolling
|
|
27
|
+
- Single-page design that fits viewport
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Install dependencies (use proxy if needed)
|
|
33
|
+
HTTP_PROXY=http://127.0.0.1:9082 HTTPS_PROXY=http://127.0.0.1:9082 uv sync
|
|
34
|
+
|
|
35
|
+
# Start server (restricts file browsing to current directory by default)
|
|
36
|
+
uv run uvicorn backend.main:app --host 0.0.0.0 --port 28000
|
|
37
|
+
|
|
38
|
+
# Optional: Set custom project root for file browser
|
|
39
|
+
VIBE_READER_ROOT=/path/to/project uv run uvicorn backend.main:app --host 0.0.0.0 --port 28000
|
|
40
|
+
|
|
41
|
+
# Open on your eink device
|
|
42
|
+
# Navigate to: http://YOUR_SERVER_IP:28000
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
### Tmux View
|
|
48
|
+
- Click **☰** to toggle sidebar
|
|
49
|
+
- Select session → window (→ pane if multiple)
|
|
50
|
+
- Click **Refresh** to update content
|
|
51
|
+
- Use **Page ↑/↓** for manual scrolling
|
|
52
|
+
- Toggle **Auto↓** to enable/disable auto-scroll to end
|
|
53
|
+
- **Scrollbar** shows your position in scrollback history
|
|
54
|
+
|
|
55
|
+
### Files View
|
|
56
|
+
- Enter path or browse directories
|
|
57
|
+
- Click folders to navigate, files to view
|
|
58
|
+
- **Page ↑/↓** for scrolling
|
|
59
|
+
- **Scrollbar** shows file length and current position
|
|
60
|
+
- **Security**: File browsing is restricted to the project root (configurable via `VIBE_READER_ROOT`)
|
|
61
|
+
- **File size limit**: Maximum 10MB per file
|
|
62
|
+
|
|
63
|
+
### Config View
|
|
64
|
+
- Adjust scrollback lines (100-10000, default 1000)
|
|
65
|
+
- Change font size (12-20px, default 14px)
|
|
66
|
+
- Set auto-refresh interval (0-60 seconds, default 5s)
|
|
67
|
+
- Click **Save Settings** to persist changes
|
|
68
|
+
|
|
69
|
+
## Technical Details
|
|
70
|
+
|
|
71
|
+
**Backend**: FastAPI + libtmux
|
|
72
|
+
**Frontend**: Vanilla JS, eink-optimized CSS
|
|
73
|
+
**Package Manager**: uv
|
|
74
|
+
|
|
75
|
+
### Environment Variables
|
|
76
|
+
|
|
77
|
+
- `VIBE_READER_ROOT`: Sets the root directory for file browsing (default: current working directory)
|
|
78
|
+
- Files outside this directory cannot be accessed
|
|
79
|
+
- Use this to restrict file browsing to a specific project
|
|
80
|
+
|
|
81
|
+
### API Endpoints
|
|
82
|
+
|
|
83
|
+
**Tmux:**
|
|
84
|
+
- `GET /api/tmux/tree` - List all sessions/windows/panes
|
|
85
|
+
- `GET /api/tmux/pane/{session}/{window}/{pane}?scrollback=1000` - Get pane content with configurable scrollback
|
|
86
|
+
|
|
87
|
+
**Files:**
|
|
88
|
+
- `GET /api/files?path=.` - List directory contents (relative to project root)
|
|
89
|
+
- `GET /api/files/content?path=file.txt` - Render file content (max 10MB) with fields `path`, `render_mode`, `html`, and `metadata`
|
|
90
|
+
|
|
91
|
+
## Design Principles
|
|
92
|
+
|
|
93
|
+
1. **Eink-first**: No animations, high contrast, instant updates
|
|
94
|
+
2. **Minimal overhead**: Direct tmux capture, simple file operations
|
|
95
|
+
3. **Space-efficient**: Every pixel counts on small eink screens
|
|
96
|
+
4. **Single-page**: All content fits viewport, reduces refresh artifacts
|
|
97
|
+
|
|
98
|
+
## Project Structure
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
vibe-reader/
|
|
102
|
+
backend/
|
|
103
|
+
main.py # FastAPI app with router includes
|
|
104
|
+
tmux.py # Tmux endpoints
|
|
105
|
+
files.py # File browser endpoints
|
|
106
|
+
frontend/
|
|
107
|
+
index.html # Single-page UI (Tmux/Files/Config views)
|
|
108
|
+
styles.css # Eink-optimized styles
|
|
109
|
+
app.js # Client logic with scrollbar management
|
|
110
|
+
pyproject.toml # uv configuration
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
## Requirements
|
|
115
|
+
|
|
116
|
+
- Python 3.13+
|
|
117
|
+
- tmux (for tmux viewing feature)
|
|
118
|
+
- Modern browser on eink device
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Backend package for FastAPI routers."""
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from fastapi import APIRouter, HTTPException
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from backend.rendering import render_text_content
|
|
6
|
+
|
|
7
|
+
router = APIRouter(prefix="/api/files", tags=["files"])
|
|
8
|
+
|
|
9
|
+
# Maximum file size to read (10MB)
|
|
10
|
+
MAX_FILE_SIZE = 10 * 1024 * 1024
|
|
11
|
+
|
|
12
|
+
def get_project_root() -> Path:
|
|
13
|
+
"""Get configurable project root. Defaults to current working directory."""
|
|
14
|
+
# Can be configured via environment variable
|
|
15
|
+
root = os.getenv("VIBE_READER_ROOT", os.getcwd())
|
|
16
|
+
return Path(root).resolve()
|
|
17
|
+
|
|
18
|
+
def resolve_and_validate_path(path: str, must_be_dir: bool = False, must_be_file: bool = False) -> Path:
|
|
19
|
+
"""Resolve path and validate it's within project root."""
|
|
20
|
+
project_root = get_project_root()
|
|
21
|
+
target_path = Path(path).resolve()
|
|
22
|
+
|
|
23
|
+
# Security check: ensure path is within project root
|
|
24
|
+
try:
|
|
25
|
+
target_path.relative_to(project_root)
|
|
26
|
+
except ValueError:
|
|
27
|
+
raise HTTPException(status_code=403, detail="Access denied: path outside project root")
|
|
28
|
+
|
|
29
|
+
if not target_path.exists():
|
|
30
|
+
raise HTTPException(status_code=404, detail="Path not found")
|
|
31
|
+
|
|
32
|
+
if must_be_dir and not target_path.is_dir():
|
|
33
|
+
raise HTTPException(status_code=400, detail="Path is not a directory")
|
|
34
|
+
|
|
35
|
+
if must_be_file and target_path.is_dir():
|
|
36
|
+
raise HTTPException(status_code=400, detail="Path is a directory")
|
|
37
|
+
|
|
38
|
+
return target_path
|
|
39
|
+
|
|
40
|
+
@router.get("")
|
|
41
|
+
async def list_files(path: str = "."):
|
|
42
|
+
"""List files in a directory"""
|
|
43
|
+
try:
|
|
44
|
+
target_path = resolve_and_validate_path(path, must_be_dir=True)
|
|
45
|
+
project_root = get_project_root()
|
|
46
|
+
|
|
47
|
+
files = []
|
|
48
|
+
for item in sorted(target_path.iterdir()):
|
|
49
|
+
# Return paths relative to project root for display
|
|
50
|
+
try:
|
|
51
|
+
relative_path = item.relative_to(project_root)
|
|
52
|
+
display_path = str(relative_path)
|
|
53
|
+
except ValueError:
|
|
54
|
+
display_path = str(item)
|
|
55
|
+
|
|
56
|
+
files.append({
|
|
57
|
+
"name": item.name,
|
|
58
|
+
"path": display_path,
|
|
59
|
+
"is_dir": item.is_dir()
|
|
60
|
+
})
|
|
61
|
+
return files
|
|
62
|
+
except HTTPException:
|
|
63
|
+
raise
|
|
64
|
+
except Exception as e:
|
|
65
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
66
|
+
|
|
67
|
+
@router.get("/content")
|
|
68
|
+
async def get_file_content(path: str):
|
|
69
|
+
"""Get content of a file"""
|
|
70
|
+
try:
|
|
71
|
+
target_path = resolve_and_validate_path(path, must_be_file=True)
|
|
72
|
+
project_root = get_project_root()
|
|
73
|
+
|
|
74
|
+
# Check file size before reading
|
|
75
|
+
file_size = target_path.stat().st_size
|
|
76
|
+
if file_size > MAX_FILE_SIZE:
|
|
77
|
+
raise HTTPException(status_code=400, detail=f"File too large (max {MAX_FILE_SIZE // 1024 // 1024}MB)")
|
|
78
|
+
|
|
79
|
+
content = target_path.read_text()
|
|
80
|
+
render_result = render_text_content(target_path, content)
|
|
81
|
+
|
|
82
|
+
# Return path relative to project root
|
|
83
|
+
try:
|
|
84
|
+
relative_path = target_path.relative_to(project_root)
|
|
85
|
+
display_path = str(relative_path)
|
|
86
|
+
except ValueError:
|
|
87
|
+
display_path = str(target_path)
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
"path": display_path,
|
|
91
|
+
"render_mode": render_result.mode,
|
|
92
|
+
"html": render_result.html,
|
|
93
|
+
"metadata": render_result.metadata,
|
|
94
|
+
}
|
|
95
|
+
except UnicodeDecodeError:
|
|
96
|
+
raise HTTPException(status_code=400, detail="Cannot read binary file")
|
|
97
|
+
except HTTPException:
|
|
98
|
+
raise
|
|
99
|
+
except Exception as e:
|
|
100
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
from fastapi.staticfiles import StaticFiles
|
|
3
|
+
from fastapi.responses import FileResponse
|
|
4
|
+
|
|
5
|
+
from backend.tmux import router as tmux_router
|
|
6
|
+
from backend.files import router as files_router
|
|
7
|
+
|
|
8
|
+
app = FastAPI()
|
|
9
|
+
|
|
10
|
+
# Include routers
|
|
11
|
+
app.include_router(tmux_router)
|
|
12
|
+
app.include_router(files_router)
|
|
13
|
+
|
|
14
|
+
# Serve frontend
|
|
15
|
+
app.mount("/static", StaticFiles(directory="frontend"), name="static")
|
|
16
|
+
|
|
17
|
+
@app.get("/")
|
|
18
|
+
async def root():
|
|
19
|
+
return FileResponse("frontend/index.html")
|
|
20
|
+
|
|
21
|
+
def main():
|
|
22
|
+
import uvicorn
|
|
23
|
+
uvicorn.run("backend.main:app", host="0.0.0.0", port=28000)
|
|
24
|
+
|
|
25
|
+
if __name__ == "__main__":
|
|
26
|
+
main()
|