TST Desk is a desktop application that gives you an AI coworker in a window: you point it at a folder, type what you want, and watch it work — reading files, running commands, and asking before it...
Overview
TST Desk is a desktop application that gives you an AI coworker in a window: you point it at a folder, type what you want, and watch it work — reading files, running commands, and asking before it does anything consequential. It looks and feels like Claude Desktop with a Cowork-style workspace, but it runs on models you choose through any OpenAI-compatible endpoint, billed to your API key, with a live cost meter in the title bar. There is no server, no hosted backend, no account, and no subscription — and pointed at a local model server it needs no API key and no network at all. v0.1.0 is on the releases page. Each platform gets these artifacts, built by .github/workflows/package.yml: GitHub Releases rewrites spaces in Tauri's TST Desk_… names to dots; SHA256SUMS.txt uses the download names. 1. Download the artifact for your platform from the releases page. 2. Verify it against the published SHA256SUMS.txt. 3. Install it: - — open the .dmg, drag to Applications.
README
TST Desk
An open-source, bring-your-own-model desktop agent workspace.
TST Desk is a desktop application that gives you an AI coworker in a window: you point it at a folder, type what you want, and watch it work — reading files, running commands, and asking before it does anything consequential. It looks and feels like Claude Desktop with a Cowork-style workspace, but it runs on models you choose through any OpenAI-compatible endpoint, billed to your API key, with a live cost meter in the title bar. There is no server, no hosted backend, no account, and no subscription — and pointed at a local model server it needs no API key and no network at all.
Install
v0.1.0
is on the releases page. Each platform gets these artifacts, built by
.github/workflows/package.yml:
| Platform | Artifact |
|---|---|
| macOS (Apple silicon) | TST.Desk_0.1.0_aarch64.dmg |
| macOS (Intel) | TST.Desk_0.1.0_x64.dmg |
| Linux (x86_64) | TST.Desk_0.1.0_amd64.AppImage and TST.Desk_0.1.0_amd64.deb |
| Linux (ARM64) | TST.Desk_0.1.0_aarch64.AppImage and TST.Desk_0.1.0_arm64.deb |
| Windows | TST.Desk_0.1.0_x64_en-US.msi |
GitHub Releases rewrites spaces in Tauri’s TST Desk_… names to dots;
SHA256SUMS.txt uses the download names.
- Download the artifact for your platform from the releases page.
- Verify it against the published
SHA256SUMS.txt. - Install it:
- macOS — open the
.dmg, drag TST Desk to Applications. - Linux —
chmod +xthe AppImage and run it, orsudo dpkg -ithe.deb. - Windows — run the
.msi.
- macOS — open the
- First launch will warn you, because v0.1 builds are unsigned. See Installing an unsigned build below for the per-platform click-through.
The app also runs from source — see Development.
Quickstart
Five minutes, launch to first result. The first-run wizard is: welcome → API key → preset → workspace → done.
- Download it, install it, open it. Install above has the per-platform steps; clearing the unsigned-build warning on first launch is a one-time click.
- Paste an API key. The wizard links to where to get one and validates it with a single
cheap live call. The key goes into your OS keychain, not a file.
Or skip this entirely: pick the
localpreset instead. It ships pointed at Ollama’s default loopback port, and for a loopback endpoint no key is asked for and none is sent. (vLLM or EZER: pick thevllmpreset instead — it ships pointed athttp://127.0.0.1:8000/v1. A different port is abase_urledit inconfig.yaml.) - Pick a preset.
tst-default,budget,local, orvllm. Settings’ preset is the default for new sessions. The title bar can retarget the open chat at another catalog preset without rewriting Settings. Changing the model behind a tier is still a settings change and applies to new sessions — a running session keeps the slugs it opened with. - Pick a workspace. A native folder picker. The folder is the unit of work — TST Desk
scaffolds a
.tst/directory in it with a commented default config, resolves anyAGENTS.mdorCLAUDE.mdit finds, and shows you the resolved instruction stack. - Type a request. You get streamed output in the chat pane, an activity timeline on the right, and an approval card whenever it wants to do something that needs your say-so. Approve it, and you have your first result.
Nothing in those five steps creates an account anywhere.
Grok Build engine
TST Desk can wrap the installed Grok Build CLI
instead of its native three-tier loop. Settings → Engine → Grok Build. New sessions then
spawn grok agent stdio over ACP; the window, approvals, coworker mode, and timeline stay
TST Desk’s. The CLI is not replaced: grok, grok -p, and grok agent stdio keep working
on their own, sessions still live under ~/.grok, and grok --resume still opens the TUI.
Grok credentials stay in ~/.grok/auth.json — never in TST Desk’s config.
What it costs
You are billed by your provider for the tokens you use. Nothing is billed by us, because there is nobody to bill you — see The promises.
The shipped stack routes three tiers: a brain for planning turns, a worker for
token-heavy edits, and a validator for review. Cheap tiers do the volume; the expensive tier
only plans. Four presets ship, and the prices below are the ones in
core/tstd/config.yaml, in dollars per million tokens:
| Preset | Tier | Input | Output | Cache read |
|---|---|---|---|---|
tst-default |
brain |
$2.80 | $14.00 | $0.30 |
tst-default |
worker |
$0.07 | $0.17 | $0.07 |
tst-default |
validator |
$0.44 | $0.87 | $0.44 |
budget |
brain |
$0.15 | $0.60 | $0.015 |
budget |
worker |
$0.07 | $0.17 | $0.07 |
budget |
validator |
$0.44 | $0.87 | $0.44 |
local |
brain |
$0.00 | $0.00 | $0.00 |
local |
worker |
$0.00 | $0.00 | $0.00 |
local |
validator |
$0.00 | $0.00 | $0.00 |
vllm |
brain |
$0.00 | $0.00 | $0.00 |
vllm |
worker |
$0.00 | $0.00 | $0.00 |
vllm |
validator |
$0.00 | $0.00 | $0.00 |
Model slugs are deliberately not restated here. The landscape moves weekly, so
core/tstd/config.yaml is the one place they live — read it there, and change them there
without waiting for a release.
A worked turn. Token counts below are illustrative, but the prices and the arithmetic are
the shipped ones — this is compute_call_details run on
tst-default’s brain. A turn sending 20,000 prompt tokens, of which the provider reports
15,000 served from cache, and generating 1,500 tokens:
5,000 uncached prompt × $2.80/M = $0.0140
15,000 cached prompt × $0.30/M = $0.0045
1,500 completion × $14.00/M = $0.0210
-------
$0.0395
Cache-read tokens are always priced at the cache rate, never the input rate — and only when the provider actually reports them. A provider that says nothing about cache reuse is billed as if there was none, so the meter never quietly understates what you owe.
Per session. The spec estimates the three-tier stack at roughly $0.36 per working
session (docs/tst-desk-spec.md §1). Treat that as an estimate, not a measurement: it is a
design-time figure, and this build has not published one of its own. What the build does give
you is the real one — a live meter in the title bar showing spend per turn, per session and per
day, broken down by tier, plus a spend cap that pauses the session when it is hit.
Per session, on local. Zero. Every price in the local preset is 0.0, so the meter
reads $0.00 and means it.
The promises
These are not marketing lines. They are architectural constraints, written down as prime
directives in AGENTS.md §2 and enforced in code.
- No account. There is nothing to sign up for. The only credential in the system is your own
provider API key, and on the
localpreset there is not even that:config.py’srequires_api_key()returns false when every tier points at loopback, and the provider client is then built withapi_key=Noneand sends noAuthorizationheader at all. - No server. The daemon binds
127.0.0.1by default.ws.py’svalidate_interface()refuses every other interface unlessremote.bindnames a Tailscale address (never0.0.0.0).start()takes nohostparameter.core/tests/test_security_suite.pyasserts both. The webview holds its end of the same boundary: every inbound frame is validated down to its event type (client.tsdrops and counts anything outside the known union), while payload shapes are trusted to the token-authenticated daemon peer — the only writer of that socket, and the other half of this repo. - No subscription. You pay your model provider per token and nobody else. There is no billing code in this repository because there is nothing to bill.
- No telemetry. No analytics, no phone-home, no crash reporting. The only outbound HTTP the
daemon makes is to the
base_urlyou configured: there is exactly one chat client (provider.py), and its endpoint comes from config, never from a literal in the source. Model discovery refuses any non-loopback endpoint before it sends (discovery.py).core/tests/test_outbound_hosts.pyenumerates every destination the process can open — source literals, the live sending paths, and import-time sockets — and fails if a host appears that configuration did not name. - Your key never touches disk. It lives in the OS keychain — Keychain, Secret Service, or
Credential Manager (
keychain.py). Every log and audit path runs through one shared redactor (logging.py), andcore/tests/test_credential_hygiene.pydrives a real daemon with a canary key and asserts it appears in no wire payload, no log record, and no byte of any file under the data directory — and in no audit row, where the credential flow produced a database to scan. - Your conversation sits on disk unencrypted. Each session writes its full model
conversation — your messages, the assistant’s replies, and every tool call with its arguments
and result — as plaintext JSON to
/sessions//conversation.json(session_persist.py; e.g.~/Library/Application Support/com.thatsimpletech.tstdesk/sessions/on macOS), written owner-only and without passing through the credential redactor that guards logs and audit rows. That is deliberate, not an oversight: reviving a session after a crash needs the actual chat, and local-first means your OS user account is the boundary. There is no knob that turns the file off or moves it on its own — start the daemon with a different--data-dirto relocate it with the rest of the data directory, or delete the session to delete its copy. - Files the agent sends are kept with the session.
send_filekeeps a copy of each file under/sessions//artifacts/, owner-only, until you delete the session, so a card opens the bytes that were sent even after a rebuild overwrites the original. The copy is your file verbatim; it is not redacted, and it never goes to a log or the audit database. Limits: 512 MiB per file and 4 GiB per session (session.send_file_max_bytes,session.send_file_session_max_bytes). - The agent cannot rewrite its own rules.
AGENTS.md,CLAUDE.mdand.tst/rules/**are refused by the filesystem tool itself (tools/boundary.py), ahead of the workspace check and ahead of any approval — so nowritable_pathsgrant can open them.
How it compares
Against Claude Desktop, Cowork, and hosted agent workspaces generally. These are good products built by large teams, and this table is only useful if it says so.
| TST Desk | Claude Desktop / Cowork / hosted workspaces | |
|---|---|---|
| Setup | Get an API key, paste it, pick a folder | Sign in and go. Genuinely less work |
| Polish | v0.1, in development, unsigned binaries that update from GitHub releases after a checksum check | Years of QA, signed, notarized, auto-updating |
| Support | GitHub issues and whoever shows up | A company, a support channel, an SLA |
| Infrastructure | Yours to run; local models need local hardware | Managed. Nothing to provision, nothing to keep warm |
| Model quality out of the box | Whatever you pick and pay for | Frontier models in a harness tuned for them |
| Mobile / sync | None | Usually both |
| Pricing | Per token, metered live — no floor, and a spend cap that pauses you | Flat subscription — predictable, and cheaper if you are a heavy user |
| Model choice | Any OpenAI-compatible endpoint; three tiers you pick, with routing pinnable mid-session | The vendor’s models |
| Whose key | Yours. You see the bill and the meter | The vendor’s, folded into the subscription |
| Offline | Runs against a local model server — no key asked for, none sent | No |
| Audit trail | Append-only SQLite on your disk — INSERT-only by construction; model calls export to CSV or JSONL | The vendor’s logs, on the vendor’s terms |
| Steering | AGENTS.md hierarchy you own, with an inspector showing the resolved stack and its token cost |
Project instructions, less visible |
| Source | Apache-2.0. Fork it, audit it, change the model table | Closed |
| Account | None | Required |
Where they genuinely win: nothing to configure, nothing to keep running, and a product that has been shipping long enough to have had its rough edges filed off. If you want an agent workspace that just works today, buy one of theirs. TST Desk is for people who want to own the key, the model choice, the audit trail, and the code.
Status
v0.1.0 is tagged (tstdesk-v0.1.0); installers are on
the releases page.
GitHub Actions is budget-capped at $20/month. Default CI is three
Linux jobs; five-platform Package runs only on demand or on a new
tstdesk-v* tag — see docs/ci-actions-blocked.md.
App releases tag as tstdesk-v* (TD-4812) so a package tag like
v0.2.0 (the tst-cu-mcp server, not the app) cannot fire the app
workflow. The app also runs from source.
What works: streaming chat, the three-tier router, the steering assembler and instruction
inspector, filesystem and shell tools behind a decision classifier and approval cards, path
boundaries and spend/time/iteration caps, the live cost meter and usage export, the
append-only audit log, agent memory that rides the brain prompt and accepts distilled
updates, sessions that revive from disk after a restart or a closed window (close vs quit
below), headless runs via tst run, computer-use — a screenshot and clicks through the
same classifier, behind a kill switch — on macOS, Windows, and Linux X11 via tst-cu-mcp (browser
path on any OS), the agent sends files to the chat (send_file) as cards you can open, save,
or download to the paired phone, where an APK opens the installer, remote attach over
Tailscale (never 0.0.0.0) with Slack/ntfy notify
and the scheduler rail, the vllm preset and UI-TARS grounding for local pixel loops,
a validating CHARTER.md schema and the project-home charter editor, the
autonomy engine (unattended runner, circuit breakers, hard-required container),
slash commands, SKILL.md, plan lock, MCP loading,
Design mode on browser and desktop frames,
plus the session rail, settings, diagnostics, and keyless local-model support.
What is not built yet, and is not claimed anywhere above: Wayland desktop
capture.
Those are later stories — see docs/tst-desk-spec.md §9 for
the phasing.
Close vs Quit
Closing the window is not quitting the app. The daemon keeps working after the window is gone.
- macOS — the red traffic-light closes (hides) the window; the Dock icon stays. Click the Dock icon to show the window again. ⌘Q or Quit TST Desk from the menu or Dock is Quit: the daemon shuts down and is reaped.
- Windows — the title-bar X or Alt+F4 hides the window.
tstdkeeps running. Opening the app again shows the window, or a new host process attaches to the live daemon instead of starting a second one. Quit TST Desk (File menu or ⌘K palette) shuts the daemon down. - Linux — the window close button hides the window the same way. Quit TST Desk
from the app menu or the command palette is what stops
tstd.
The first time you close the window: that hides TST Desk. Quit TST Desk is what stops it.
OS notifications for an approval or a finished turn still fire while the window is hidden. While hidden, a running session or a parked approval badges the Dock (macOS) or the taskbar tooltip (Windows / Linux). A tray icon shows the running-session count; Quit TST Desk lives in the tray menu too. Clicking the app icon shows the window and focuses the approval if one is waiting.
See docs/tst-desk-backlog.md for the current milestone and
docs/tst-desk-spec.md for the full architecture spec. How the pieces
fit together — the daemon/shell split, the wire protocol, the extension points, and how to add a
tool — is in docs/architecture.md. Every configuration key is
documented in docs/configuration.md, and writing AGENTS.md and
path-scoped rules is covered in docs/steering.md. Where Windows behaves
differently — path forms, 8.3 short names, file permissions, killing a command — is in
docs/windows.md.
Development
Prerequisites
-
Python 3.11+
-
uv (Python package manager)
-
Rust 1.77+ (for the Tauri host)
-
Node.js 24+ (for the frontend)
-
Linux only — WebKit/GTK headers for the Tauri host, plus
secret-toolfor the keychain:sudo apt-get install -y \ libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev \ patchelf pkgconf libsecret-toolsOn Ubuntu 26.04, install
libayatana-appindicator3-dev(notlibappindicator3-dev): the older name removesnetwork-manager-applet.
Setup
# Python core
cd core
uv sync
# Frontend
cd ../ui
npm install
Running it
# From ui/ — builds the Rust host, starts the daemon as a sidecar, opens the window
npm run tauri:dev
# Optional: one headless turn against the same daemon (starts tstd if needed)
cd core && uv run tst run --workspace /path/to/folder --message "say hi"
# Follow a session already running on that daemon (Ctrl+C detaches)
uv run tst attach
Tests
cd core && uv run pytest # Python core
cd ui && npm test # frontend
Pre-commit hooks
This repository uses pre-commit to run linting, formatting, and secret detection on every commit.
# Install hooks (one-time, from the core/ directory)
cd core
uv run pre-commit install
The hooks will then run automatically on git commit:
- ruff check — lint Python files
- ruff format — format Python files
- Secret detection — blocks commits containing API keys, tokens, passwords, and other credential-like patterns
To bypass the hooks (e.g. for a false positive):
git commit --no-verify
Installing an unsigned build
v0.1 builds are not code-signed or notarized (see DECISIONS.md, 2026-08-14, and
docs/signing.md for the v0.1 refusal, future CI wiring, and
where release secrets would live).
Each OS will warn on first launch; that is expected, not a defect.
- macOS — Gatekeeper blocks the unsigned app. Right-click the app and
choose Open, then confirm in the dialog (once per install). If the file
was download-quarantined and the Open trick does not appear:
xattr -d com.apple.quarantine "/Applications/TST Desk.app". - Windows — SmartScreen shows “Windows protected your PC”. Click More info → Run anyway.
- Linux — no signing warning. For the AppImage,
chmod +xand run; the.debinstalls withsudo dpkg -i.
Updates
A packaged app checks GitHub releases
for a newer tstdesk-v* tag when it opens, and every four hours after that.
TST Desk → Check for Updates… (File menu on Windows and Linux) and
Check for updates in the command palette do the same thing on demand.
When a newer release exists, the app downloads the installer for this
machine and installs it only if the file matches SHA256SUMS.txt on that
release. macOS and AppImage builds replace themselves when you choose
Restart to update. Windows runs the MSI. A .deb opens in the system
installer. The check sends no account and no identifier. Turn it off under
Settings → About. A build you are running from a source checkout reports
the release and does not replace itself.
License
Recommended Tools
Try a different keyword or remove a filter.
Install
npx skillfish add thatsimpletech/tst-desk