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.
@@ -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()