What a repository is
A Thumper repository holds code and weights together. Source files live in text lanes that merge at the character level; large binaries are chunked and deduplicated into the object store. Every listing on the catalog is backed by one repository, and the same address serves both: the public page at /apps/<owner>/<name> and the developer view at /apps/<owner>/<name>/dev. The kinds are app, lib, model, modelpack and dataset.
Repository classes
A repository is created as private plaintext, published public, private end-to-end encrypted, or a public fork. Public repositories are readable by anyone signed in; private E2EE repositories are opaque to the server, which stores and routes authenticated updates it cannot read.
The developer view
| Tab | What it shows | Address |
|---|---|---|
| Overview | README, latest seal, linked listing, stars and watchers | /<kind>/<owner>/<name>/dev |
| Code | Files at a branch or seal; blame per file | ?tab=code |
| Issues | Issues and proposals recorded against the repository | ?tab=issues |
| Workflows | Native check runs (thumper-ctl repo run/list/view) | ?tab=workflows |
| Releases | Signed publications and their files | ?tab=releases |
| Security | Advisories, franking evidence, protection rules | ?tab=security |
| Insights | Dependencies and dependents | ?tab=insights |
| Settings | General, Listing, Upstream source, Marketplace, Access, Automation, Checks, Lifecycle | ?tab=settings |
.thumper vs .git
Thumper does not replace git. A synced folder can be a git repository at the same time: git keeps its objects in .git/, Thumper keeps its own state in .thumper/, and neither directory is ever uploaded. thumper push walks the working copy, moves the branch's live frontier one file at a time (each update signed by this machine's registered device key) and then seals the frontier into a signed, content-addressed commit — the seal is what Code, Commits and clones read.
| Path | Owner | Behaviour |
|---|---|---|
| .thumper/ | The working copy's link to its repository: repo.json (server, repository id, owner/name, branch, class) and state.json (last seal, the pushed tree as path → BLAKE3) | Never uploaded, never versioned: it is in the default ignore list. Plain JSON — `cat .thumper/repo.json` is the inspection tool. Deleting it only unlinks the copy; `thumper repo init OWNER/NAME` relinks. |
| .git/ | A normal git repository, if you keep one | Never uploaded, never rewritten. Thumper reads .gitignore but does not touch git objects, hooks or refs. |
| .oxen/ | Oxen's internals, only if you also use Oxen in the same folder | Ignored like .git/. Thumper never writes it; its own state stays under .thumper/. |
| .thumperignore | Your ignore rules for Thumper only | Highest precedence. gitignore syntax; !pattern re-includes. |
| .stignore / .gitignore | Syncthing and git ignore files you may already have | Honoured in that order after .thumperignore, before the built-in defaults. |
.git directory, add a .thumperignore for anything you do not want synced, and link the GitHub or Hugging Face upstream under Settings › Upstream source. Pulls land on a draft branch; nothing is published automatically.Ignore files and file types
Ignore precedence
Four sources are merged, highest first: .thumperignore, .stignore, .gitignore, then the built-in defaults. Inside one file the last matching line wins, so !keep.txt can re-include a file an earlier *.txt excluded. A trailing slash limits a pattern to directories; a pattern without a slash matches at any depth.
# outputs regenerate; do not sync themoutputs/*.log# but keep the one sample the README links to!outputs/sample.png
Built-in defaults
.DS_Store Thumbs.db desktop.ini $RECYCLE.BIN/ *.swp *.swo *.tmp *~ .git/ .hg/ .svn/ .idea/ .vscode/ node_modules/ .npm/ .yarn/ .pnpm-store/ dist/ build/ target/ .venv/ venv/ __pycache__/ *.pyc .pytest_cache/ .mypy_cache/ .ruff_cache/ *.egg-info/ .tox/ .thumper/ .oxen/
How file types are stored
There is no extension list to configure. Thumper decides by content and size: text files become text lanes, everything else is chunked bytes. The rules below are the shipped constants.
| Kind | Storage | Limits |
|---|---|---|
| Source and config (text) | One text lane per path inside the branch document; character-level merge; blame per seal | Versioned in workspace history up to 25 MiB per file. |
| Weights, datasets, media (bytes) | Content-defined chunks: 8 KiB minimum, 64 KiB target, 128 KiB maximum; identical chunks are stored once | Stored in the object store, referenced by manifest; never diffed as text. |
| Binary files pushed with the CLI (images, small weights) | One blob lane per path: the branch holds a BLAKE3 pointer, the bytes live in the repository's content store and are stored once per digest | Up to 16 MiB per file and 256 MiB per tree; cloned and pulled byte-identical; shown inline in the Code tab when they are raster images. |
| App-scoped seals | A manifest may raise its per-file cap, clamped to 100 MiB | The total seal budget still applies. |
| Conflicting edits from two devices | Kept side by side, never overwritten | The loser is renamed <stem>.sync-conflict-<timestamp>-<id>.<ext>. |
Heads, seals, branches
Every branch has two heads. The live frontier moves on every edit from any authorised device or agent. The sealed head is an immutable seal: a signed commit with parents, a content digest, and provenance (who, from which device, under which capability). Auto-checkpoints between seals are cheap and disposable; a seal squashes the run since the last seal into one named commit.
| Concept | Git analogue | Notes |
|---|---|---|
| live frontier | working copy | shared, continuous; there is no private copy to push |
| auto-checkpoint | jj's @ change | frontier marker per agent turn or idle debounce; not a GC root |
| seal | commit | content-addressed, signed, single linearisation point per ref |
| ref | branch or tag | moved by compare-and-swap; a stale head answers 409 |
| proposal | pull request | recorded under Issues; merge itself is behind TR_REPO_MERGE_ENABLED |
Publishing and listings
A listing is a catalog row that points at a repository. Publishing a listing without signed publication files is allowed: the page exists, the download button does not. Signed publications appear under Releases and make the app installable from the desktop app.
Settings panels
General (default branch, handle), Listing (name, category, screenshots, README image policy), Upstream source (link a GitHub or Hugging Face repository; pulls are staged as drafts every hour), Marketplace (list for sale), Access (collaborators and invitations), Automation and Checks (checks that must pass), Lifecycle (make public or private, archive, delete).
README, licence and stars on the public page
The public page renders the upstream README with images stripped by default (the publisher can allow images from the upstream repository, or all images), the SPDX licence, and the upstream star count. All three refresh on the hourly upstream poll.
API and CLI
Repository routes live under /api/v1/repos. Every route checks the session, the repository's visibility and the caller's capability before it does anything; a repository you may not see answers the same 404 as one that does not exist. Mutations with a bearer token must be request-bound.
| Route | Purpose | Requires |
|---|---|---|
| GET /api/v1/public/{kind}/{owner}/{name} | Public coordinates of a listing or repository | Anonymous |
| GET /cli/install.sh, /cli/install.ps1, /cli/release.json, /cli/download/{artifact} | CLI distribution: Linux builds from this server's image plus the macOS, Windows and Linux-aarch64 builds the release workflow publishes, each with size, SHA-256 and the commit it was built from (what the installers and `thumper upgrade` use) | Anonymous |
| POST /api/v1/auth/cli/device-code, /approve, /token | thumper login: device-authorization grant; the browser approves, the CLI receives a session sealed to its transport key | Public start/claim; approval needs the browser session |
| POST /api/v1/repos | Create a repository (name, class, default branch) | Session |
| POST /api/v1/repos/device-key-challenges, /device-keys | Register this machine's Ed25519 device key (X25519 challenge, proof of possession) | Session |
| POST /api/v1/repos/{id}/branches/{b}/live/bootstrap, /proof-context, /updates | Live branch: snapshot + replica id, the signing inputs for one file change, then the device-signed Loro update | Session; TR_REPO_LIVE_TEXT |
| PUT / GET /api/v1/repos/{id}/payloads/{blake3} | Blob lane: content-addressed bytes for binary files (≤16 MiB each); the branch carries only the pointer | Session; write capability; not for private E2EE repositories |
| POST /api/v1/repos/{id}/branches/{b}/seal/prepare, /finalize | Two-phase seal: the server returns the exact bytes, the device key signs them | Session; TR_REPO_SEALING |
| GET /api/v1/repos/{id}/commits, /tree/{sha}, /blob/{sha}/{path}, /raw/{sha}/{path}, /archive/{sha}.tar.gz | History, tree, file, raw bytes and the whole tree as one tarball at a seal (what the Code tab, `thumper repo clone` and `git clone` read) | Anonymous for public repositories |
| GET / PATCH / DELETE /api/v1/repos/{id} | Read, update, soft-delete | Session, owner for writes |
| GET / POST /api/v1/repos/{id}/releases/{v}/assets, GET / DELETE …/assets/{name} | Release assets: list, upload (raw body, ≤128 MiB), download, remove | Anonymous reads for public repositories; writer for uploads |
| GET / POST /api/v1/repos/{id}/refs | Branches and tags; creating a ref validates the head in the same transaction | Session |
| GET /api/v1/repos/{id}/log | Seal history for a ref (`?unverified=1` for ids and messages without per-seal verification) | Session |
| GET /api/v1/repos/{id}/history-pack?from=&limit=&skip= | A slice of the ancestor walk from a seal in one download: manifest.jsonl + every blob once (what git clone uses); `skip` continues the same walk, so side branches behind merges are never lost | Public read |
| GET /api/v1/repos/{id}/seals/{seal_id} | One seal with its provenance | Session |
| GET /api/v1/repos/{id}/seals/{seal_id}/blame/{path} | Blame at a seal | Session; TR_REPO_SEALING |
| GET /api/v1/repos/{id}/compare | Compare two refs or seals | Session; TR_REPO_SEALING |
| POST /api/v1/repos/{id}/merge-preview, /merge | Guarded two-parent merge | Session; TR_REPO_MERGE_ENABLED |
| PUT / DELETE /api/v1/repos/{id}/star, /watch | Star or watch a repository | Session |
| GET /api/v1/social/{listing_id} | Star and watch counts of a listing | Anonymous; TR_SOCIAL_ENABLED |
| GET /api/v1/git-proxy/{host}/{path} | Same-origin relay for browser-side git clone of an allow-listed upstream | TR_GIT_CLONE_ENABLED |
The thumper CLI
Sign in once per day from the terminal: thumper login prints a key fingerprint and opens /cli/authorize in your browser; approving there gives this terminal a session that is a child of your browser session (same account, same limits, expires with it). The first push registers the machine's device key; every change and every seal is signed with it — no password or token ever sits in the terminal.
# install (Linux, macOS): picks the build for your platform from /cli/release.json and verifies its SHA-256 firstcurl -fsSL https://app.thumper.run/cli/install.sh | sh# install (Windows PowerShell)irm https://app.thumper.run/cli/install.ps1 | iex# from source, any platform: cargo build --release -p thumper-ctl --no-default-features# later: thumper upgrade --check / thumper upgrade# sign in (device code; the browser approves)thumper login# create a public repository and link this folderthumper repo create my-app --public# or link a folder to a repository you already havethumper repo init OWNER/NAME# push: signed per-file updates, then a signed sealthumper push -m "first import"thumper repo status# clone the sealed tree of a public repositorythumper repo clone OWNER/NAME [-b BRANCH]# or plain git through the remote helper the installer links (one git commit per seal, one seal per pushed commit; lightweight tags too)git clone thumper://OWNER/NAMEgit remote add thumper thumper://OWNER/NAME && git push thumper main v1.0.0# this machine's device key, and raw API calls with the sessionthumper repo device statusthumper repo api /api/v1/handles/me# bring the sealed head down into a linked folder (refuses to overwrite local edits without --force)thumper pull# branches, proposals and issues from the terminalthumper repo branch feature-x --from mainthumper pr create -R OWNER/NAME --head feature-x --base main -t "Add X"thumper pr diff -R OWNER/NAME 7thumper pr checkout 7 # switch the working copy to the proposal's branch and pull itthumper pr checks 7 && thumper pr view 7 --webthumper repo tag create v1.0.0 --branch main && thumper repo tag listthumper repo branch-delete feature-x # never the default branchthumper repo edit -d "What this repository is" --default-branch mainthumper repo protect set main --require-review --lock-deletions --required-approvals 1thumper repo collaborator add HANDLE --role write # admin | write | triage | readthumper repo access list # grant / decline requests by idthumper pr merge -R OWNER/NAME 7 --delete-branch # fast-forward the base (a seal signed by this device), accept, drop the branchthumper pr merge 7 --squash # base moved: three-way merge sealed once (conflicts are listed, never auto-resolved)thumper pr merge 7 --rebase # base moved: each branch seal replayed onto the base, messages keptthumper pr merge 7 --merge # base moved: two-parent merge seal via the server's merge lane (gh's default; needs branches that edited different files)thumper pr update-branch 7 # merge the base INTO the branch first, so review sees current base contentthumper pr review 7 --approve -b "LGTM" # approvals count toward protected-branch rulesthumper issue create -R OWNER/NAME -t "Crash on start"thumper label create bug -c E60076 && thumper issue label add 12 bugthumper issue edit 12 -t "Crash on start (Linux)"# repository administrationthumper repo listthumper repo visibility private --repo OWNER/NAMEthumper repo fork OWNER/NAMEthumper repo release create 1.0.0 -R OWNER/NAME --commit <seal>thumper repo release upload 1.0.0 dist/app.tar.gz && thumper repo release download 1.0.0 -D ./outthumper repo webhook create https://example.com/hook -R OWNER/NAME --events push,releasethumper --yes repo delete OWNER/NAME# native check runs recorded against a repositorythumper repo run list --repo OWNER/NAME --limit 20thumper repo run watch --repo OWNER/NAME 42 --timeout 600thumper repo run rerun 42 -R OWNER/NAME && thumper repo checks list -R OWNER/NAME
git and gh coverage
| You know | Thumper | Notes |
|---|---|---|
| git clone / fetch / pull / push | git clone thumper://OWNER/NAME · git push thumper main | git-remote-thumper (installed with the CLI): one commit per seal, one seal per commit; two-parent merge seals import as git merge commits (a pushed git merge commit becomes a single-parent seal — real merges are made with pr merge --merge); branches and lightweight tags; annotated tags are pushed as lightweight; symlinks and submodules are refused and the executable bit is not stored (the tree has no file modes) |
| git push --delete | git push thumper --delete NAME (or thumper repo branch-delete NAME · thumper repo tag delete NAME) | deletes the branch or tag on the server; the default branch and protected patterns are refused, and the seals stay reachable by id |
| gh auth login / logout / status | thumper login · logout · auth status | device-code flow; the session is a child of your browser session |
| gh repo create / clone / view / list / rename / edit / delete / fork | thumper repo create · clone · view [--web] · list · rename · edit · delete · fork | |
| gh repo set-default, visibility | thumper repo edit --default-branch · repo visibility | |
| gh repo archive / unarchive / delete | thumper repo archive · unarchive · delete · restore ID | an archived repository refuses every write — push, git push and pr merge — until it is unarchived; delete prints the id that restores it while the purge window is open |
| Repository transfer | thumper repo transfer HANDLE [--org] · transfer-list · transfer-accept ID | ownership moves only when the recipient accepts |
| Collaborator invitations (invitee side) | thumper repo invite accept · decline | the owner's side is repo collaborator add · remove · role |
| gh search repos | thumper repo search QUERY · repo search --trending · --recent · repo list --owner HANDLE | public search needs no session |
| Security reports and advisories | thumper repo reports list · decide ID --triaged|--published|--dismissed; thumper advisories [ID] | only a repository's owner triages its reports; advisories are public |
| Marketplace crosslink | thumper repo listing | the listing linked to a repository, if any |
| Private paths / paid paths | thumper repo paths list · set PATH --public|--private|--open [--price-cents N] · remove · timeline | the blob, tree and history-pack routes hide private paths from non-writers |
| Forks, dependencies, seal artifacts | thumper repo forks · deps · artifacts SEAL | |
| gh auth token | thumper auth token list · revoke ID | tokens are created in Settings → Tokens |
| gh ruleset (branch protection) | thumper repo protect list · set · clear | require-review, require-checks, lock-writes, lock-deletions, required approvals; a reviewer who requested changes blocks acceptance on a branch that requires review |
| Collaborators / access requests | thumper repo collaborator list · add · remove · role; thumper repo access list · request · grant · decline | invites by @handle; roles admin · write · triage · read |
| Download ZIP | GET /api/v1/repos/{id}/archive/{sha}.tar.gz · Code tab → Clone → Download | |
| gh pr create / list / status / view / diff / checkout / checks / review / merge / close / reopen / edit / comment | thumper pr … (same verbs, plus update-branch; create --fill; list --author @me -L N; checks --watch --exit-status; review --approve | --request-changes | -b) | merge fast-forwards as a seal signed by your device; when the base has moved, --merge produces a true two-parent merge seal through the server's merge lane (command authority + your device signature), --squash merges three-way and seals once, --rebase replays each seal; conflicts are refused with the file list and nothing lands |
| gh pr create --draft / gh pr ready | thumper pr create --draft · pr ready [--undo] | a draft cannot be approved or merged; the author or a writer marks it ready |
| gh issue create / list / status / view / edit / close / reopen / comment | thumper issue … (same verbs) | issue status shows the ones you opened and the open ones by others |
| Reactions (gh has none — `gh api` only) | thumper issue react N [+1|-1|tada|heart|rocket|eyes] [--remove]; thumper pr react N … | the six the server accepts; no argument lists the counts and marks yours |
| gh label list / create / edit / delete / clone; gh issue edit --add-label | thumper label list · create · edit --name-to/-c · delete · clone SOURCE [--force]; thumper issue|pr label add · remove [--create] | deleting a label detaches it everywhere |
| gh release list / create / view / edit / delete / upload / download | thumper repo release list · create · view · edit --notes|--notes-file · delete · upload · download · delete-asset | assets up to 128 MiB each, stored once by BLAKE3 in the repository content store; edit rewrites the notes only — a release names an exact commit |
| gh run list / view / watch / cancel / rerun | thumper repo run list · view · watch · cancel · rerun · create | runs are the canonical check set recorded per commit, not arbitrary Actions YAML; no log download |
| gh workflow enable / disable | thumper repo checks list · enable · disable | |
| gh api | thumper repo api [-X] [-d] PATH | request-bound session bearer |
| gh secret / variable | — | checks are recorded, not executed, so there is nothing to inject secrets into |
| gh browse | thumper repo view --web · pr view --web · issue view --web | |
| Anonymous reads of public repositories | repo view · clone · branch · tag list · checks list · pr/issue list · git clone | no session needed; suspended or killswitched repositories stay offline for everyone |
| gh completion | thumper completions <shell> | |
| gh upgrade (brew/apt) | thumper upgrade [--check] | verified against /cli/release.json |
.thumperignore with the same rules as the sync engine; files over the caps are reported as skipped rather than silently dropped. Files removed locally are removed from the branch unless you pass --no-delete; a push that would overwrite a seal you have not pulled is refused (see thumper pull).Feature flags
Each surface is behind an environment flag on the node. A route behind a flag that is off answers 404, indistinguishable from a route that does not exist. /api/v1/client-config reports the flags a client should honour.
| Flag | Enables | app.thumper.run |
|---|---|---|
| TR_REPOS_ENABLED | Mounts /api/v1/repos and the developer view | on at app.thumper.run |
| TR_MIRROR_SYNC_ENABLED | Upstream links: poll GitHub or Hugging Face, stage drafts, refresh README and stars | on |
| TR_GIT_CLONE_ENABLED | Browser-side git clone through the same-origin relay | on |
| TR_FORK_ENABLED, TR_TIME_MACHINE_ENABLED | Fork button and the Time Machine view | on |
| TR_REPO_SEALING | Signed seals (thumper push), compare and blame | on |
| TR_REPO_MERGE_ENABLED | Merge preview and merge | off |
| TR_REPO_LIVE_TEXT | Live branch updates (thumper push, live text lanes) | on |
| TR_LAKEFS_S3_CONDITIONAL_PUT | native (If-None-Match) or emulated put-if-absent for object stores that answer 501, e.g. Backblaze B2 | emulated |
| TR_SOCIAL_ENABLED | Stars and watchers on listing pages | see /api/v1/client-config |