DeepSeek Harness Remote
DeepSeek Harness Remote 可將手機或另一台電腦與你運行 DeepSeek Harness 的電腦安全連接,隨時隨地繼續你的工作,方便隨時查看進度並繼續工作。
AI 翻譯
尚未收集儲存庫資訊。
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
Annotate PDFs, organize your knowledge
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Organize papers and knowledge, in one place. Self-hosted, multi-user, Logseq-inspired: read and annotate PDFs in your browser, keep the notes as a nested outliner, and link everything together.
Install · User guide · Try the demo · Website · Releases
Every picture below is clickable and opens the matching part of the user guide.
@ to attach a library page; its text stays in context for follow-ups.→ Guide: AI chat · The library agent
Your library lives on your server and opens from any browser: the office desktop, the iPad (with the Pencil), your phone. Reading position, open tabs and zoom follow your account from device to device.
→ Guide: Offline copies · Install as an app
Open a paper by pasting any link — arXiv, DOI, or a publisher page; Gamma finds the PDF and falls back to a legal open-access copy when the DOI is paywalled — or drag the file in. Then:
→ Guide: Highlight and annotate · Draw and type on the page · Follow links and translate
Highlights and free notes are the same kind of block, so a paper's notes and a plain page are edited the same way:
$…$ / $$…$$ math, code fences, callouts, tables, Mermaid diagrams and SVG figures render in place while the block you're on stays raw. Math gets bracket-pair coloring, \command autocomplete, and Tab hops between {} arguments./note and a sheet of paper opens among your notes; write on it with a stylus or the mouse, or show a page's sheets large as a notebook.[[page]] mentions, /page for a new page linked where you type, ![[block]] embeds that edit the source in place, backlinks, and a "/" menu for everything else.→ Guide: Notes
Ctrl+F counts the matches and Enter steps through them, marked in the page.Ctrl+P opens any page, folder or label as you type, typos forgiven; Ctrl+Enter there searches notes and PDFs, and Ctrl+Shift+P runs any command by name.Ctrl+F searches notes, highlights and the full text of every PDF at once; narrow with label and folder chips. Matching is forgiving: "3000" finds "3,000-qubit" across a line break.Neither one copies a paper, and a paper can have as many labels and folders as you like. The difference is how you'll come back to it.
A label is a word you'll search for. A paper on PDH locking gets the label pdh. A paper from Vladan's lab on PGC cooling gets vladan and pgc. Labels are flat, so keep them short: a technique, an author, a lab, a keyword. Click one, or type it after Ctrl+P, to list every paper that has it.
A folder is a label that nests, for a topic or a talk. If you're studying quantum computing, make a folder Quantum computing with Neutral atom, Superconducting qubits and Ion trap inside it. A paper filed in Neutral atom shows Quantum computing / Neutral atom next to its labels. If you're giving group meetings, make gm1, then gm2. A paper you present at both sits in both folders, and it is still one paper with one set of notes. You can also use a folder as a whole: share it, export its bibliography, or ask the AI about every paper in it.
A simple test: if you'd type it into search, make it a label. If you'd open it and work through it, make it a folder.
Coming from Zotero? A folder is a collection and a label is a tag. Importing or exporting a Zotero library keeps them that way.
→ Guide: Library · Labels or folders · Search · Metadata and citations
→ Guide: Share a page · Workspaces · Comments and inbox
→ Guide: Assistants · Gamma Connector · Import and export · Backups and upgrades
Get the Windows app from the Microsoft Store, the iPad app from TestFlight, or download standalone installers from GitHub Releases.
sudo apt install ./Gamma-<version>-linux-amd64.deb. Windows and Linux apps update themselves.gamma-connector-<version>.zip from the GitHub release, unzip it, then chrome://extensions → Developer mode → Load unpacked.main on every merge.docker run -d --name gamma -p 9001:9001 -v gamma-data:/data ghcr.io/tim4431/gamma:latest
Open http://localhost:9001 and log in as admin — a fresh instance seeds the account itself and prints its password once to the log (docker logs gamma). No environment variables needed.
Copy the template (the real file is gitignored, so local tweaks never land in commits) and start:
cp docker-compose.yml.example docker-compose.yml
docker compose up -d
Open http://localhost:9001 and log in with the seeded admin password from docker logs gamma (printed once on first start). Accounts, notes and uploaded PDFs live under the container's /data volume and survive upgrades. Behind a reverse proxy, set FORWARDED_ALLOW_IPS to the proxy (the template shows how), or every visitor shares the proxy's rate limits.
Upgrades: the template runs Watchtower beside Gamma, which pulls each new image and restarts the container; Gamma then upgrades its data directory as it starts, and each workspace as it is first opened, after snapshotting the databases. Remove that service to update by hand (docker compose pull && docker compose up -d). A very old data directory may need one intermediate release first; if so, the server shows a page at its usual address saying exactly which image to run once, and nothing is changed until you do (how upgrades work).
Backups: a workspace exports as one zip from Settings → Workspaces, and snapshots live in Settings → Backups. Administrators snapshot the whole instance from Settings → Server (restore those with the server stopped: manage.py backups --restore) and can keep off-site copies of the databases and uploaded files in an S3-compatible bucket (Settings → Backups → Off-site copies). See Backups and upgrades and the backup internals. If you bind-mount /data to a host folder, set PUID/PGID to your user's ids (id -u / id -g) so the files belong to you instead of root.
Users are managed in the app: sign in with an admin account → Settings → Users (create/delete accounts, reset passwords, grant or revoke the admin privilege — admin is a flag, not a special name). The CLI equivalent still works:
docker exec gamma python manage.py create-user alice her-password
docker exec gamma python manage.py set-admin alice on
docker exec gamma python manage.py list-users
For assistants (Codex, Claude Code) signing in to a remotely hosted Gamma, an administrator confirms the Public server URL once in Settings → Server; it applies immediately, without environment variables or a restart.
Requires Python 3.11+ and Node 18+.
Backend
cd backend
python -m venv venv && source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
python manage.py create-user admin yourpassword
python manage.py set-admin admin on # admin privilege → GUI user management
python manage.py setup # seeds the guest account
uvicorn app:app --host 127.0.0.1 --port 9001
Frontend
cd frontend
npm install
npm run dev # :5173, proxies /api → :9001
Frontend source is grouped by function (editor/, pdf/, settings/, and
others), with startup/session code in app/ and reused code in shared/.
See the frontend source map for file locations and
naming conventions, and the developer guide for
architecture, shared rules, and topic documentation.
Tests
For local changes, run tests for the affected modules and their direct consumers; see the test selection policy for targeted backend, frontend, and browser commands. The commands below run full suites, which also run in PR CI.
cd backend
pip install -r requirements-dev.txt
python -m pytest tests -q
In-process API tests against a throwaway data dir — auth, the block tree, metadata/BibTeX, PDF-annotation import, full-text search, sync, and export.
Frontend checks, from frontend/: npm test for module tests, npm run build
for the production bundle, and npm run e2e for the browser suite against an
isolated backend (requires backend dependencies and Playwright Chromium).
Production without Docker — build the frontend and let the backend serve it:
cd frontend && npm run build
cd ../backend
GAMMA_STATIC_DIR=../frontend/dist uvicorn app:app --host 127.0.0.1 --port 9001
Put a TLS-terminating reverse proxy (Caddy, nginx) in front of 9001 for a domain. If you use HTTP/3, consider limiting Caddy to protocols h1 h2 — a Chrome QUIC bug can make large PDFs crawl. A proxy's request-body limit (Cloudflare's 100 MB, nginx's client_max_body_size) does not cap your PDFs: a file past 32 MiB goes up in parts of that size, so the proxy only has to allow that much per request. Gamma itself caps no file's size, only an account's total storage when an admin sets a quota (Settings → Server). An upload a proxy does refuse says so in its error instead of looking like Gamma's refusal. Gamma's login throttle counts per client address, which it takes from the proxy's X-Forwarded-For only when the proxy's own address is in FORWARDED_ALLOW_IPS (below). Behind a proxy, set it to the proxy's address, or in Docker to the subnet of the compose network the proxy container shares with Gamma (pin that subnet in the compose file). Otherwise every visitor shares the proxy's rate-limit bucket. The build ships Brotli and gzip copies of its assets. The backend serves them itself, and a proxy that serves dist directly can too (serving the build).
| Variable | Required | Default | Description |
|---|---|---|---|
GAMMA_DATA_DIR | No | data/ at the repo root (/data in Docker) | Where users.db and the per-workspace data live |
GAMMA_STATIC_DIR | No | unset (/app/static in Docker) | Built frontend to serve as SPA; unset = API only |
GAMMA_PORT | No | 9001 | Listen port (Docker entrypoint only) |
FORWARDED_ALLOW_IPS | Behind a reverse proxy | 127.0.0.1 (Docker too) | Addresses of reverse proxies whose X-Forwarded-For / -Proto headers are believed (uvicorn's setting; comma list, networks allowed, * for any — only when nothing but the proxy can reach the port). Rate limits key on the client address this yields: behind a proxy that is not listed, every visitor shares the proxy's |
GAMMA_ADMIN_USER / GAMMA_ADMIN_PASSWORD | No | admin / random, printed to the log once | Overrides the account a fresh instance seeds itself at startup (only while no real accounts exist; never touched afterwards). Admins manage users from the GUI (Settings → Users) |
GAMMA_AI_ANTHROPIC_BASE_URL | No | https://api.anthropic.com | Default Anthropic-protocol endpoint, e.g. https://api.deepseek.com/anthropic |
GAMMA_AI_OPENAI_BASE_URL | No | https://api.openai.com | Default OpenAI-compatible endpoint |
GAMMA_SEARXNG_URL | No | unset | A SearXNG instance (JSON format enabled) that the assistant's web search uses for accounts without a search service of their own; it may be on the private network |
GAMMA_OPENALEX_API_KEY | No | unset | An OpenAlex key for accounts without their own: ten times the keyless daily budget for scholarly search, and no pauses under load |
AI is configured in the app, not the environment: each user adds provider entries under Settings → AI → Connections (pick the API format — Anthropic Messages or OpenAI Chat Completions — then a key, plus optional label, base URL, and model list), or connects a ChatGPT Plus/Pro subscription with Sign in with ChatGPT — OAuth, no key at all. Keys are stored server-side per user and never sent back to the browser. The base-URL variables above only change the per-protocol defaults shown in that dialog.
Published to GitHub Container Registry on every push to main (latest) and on version tags (v1.2.3 → 1.2.3, 1.2), for linux/amd64 and linux/arm64:
ghcr.io/tim4431/gamma
Multi-stage build: a Node stage compiles the frontend, the final Python image runs FastAPI serving both the API and the SPA on port 9001. See Dockerfile and .github/workflows/docker.yml.
Gamma is licensed under the GNU Affero General Public License v3.0 only
(AGPL-3.0-only). Third-party components and assets retain their respective licenses.