TreeMap
A free, open-source disk-space visualizer. Squarified treemap, duplicate hunting, disk-full forecasting, and cleanup that only ever moves files to the Trash.
A voice-first Mac assistant that uses the computer for you. Say jarhead, pass Touch ID, talk. MIT · macOS 14+ · Apple silicon
ProductivityTypeScript · SwiftGitHub @Kevin-Liu-01OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Voice-first Mac assistant that uses the computer for you. GPT-Live-1 voice behind a wake word; brains: Codex, Claude Code, an API or a local model; Swift hands in parallel threads; alarms and automations that fire while it sleeps; a notch island control surface; a Console into every coding-agent session; memory. Rewrites itself, with your yes.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
A voice-first Mac assistant that uses the computer for you.
Say "jarhead", pass Touch ID, talk. The voice is GPT-Live-1,
full duplex. The brain is whatever you already have a login for — Codex, Claude Code,
an API key, a local server. The hands are a Swift helper on the real Mac. A dithered
ASCII blob in the notch shows the work, and every step lands in an append-only ledger.Read the full README ↗Say "jarhead", pass Touch ID, talk. The voice is GPT-Live-1. It listens and speaks at the same time. The brain is whatever you already have a login for: Codex, Claude Code, an API key or a local server. The hands are a Swift helper on the real Mac. A dithered ASCII blob shows the work: in the notch on a MacBook that has one, free on the desktop elsewhere. Every step is written to an append-only ledger.
Quick start. One line (jarhead.kevinliu.studio; the script is
scripts/install.sh). It checks macOS 14+ on Apple silicon, Xcode's command line tools with
Swift 6.0 or newer (Xcode 16), Node 24 and pnpm 10. It installs none of them: a missing one prints its fix and stops
the script. Then it clones to ~/jarhead, runs the four commands below (without the landing page's dependencies) and
opens the app. Keys go into ~/.jarhead/env by Setup, never by the script. No signing certificate is needed: without
one the build signs ad-hoc, and macOS resets the permission grants on every rebuild.
curl -fsSL https://jarhead.kevinliu.studio/install.sh | sh
Or by hand. Signing, permissions and what it costs are under Install and run.
git clone https://github.com/Kevin-Liu-01/Jarhead.git && cd Jarhead
pnpm install --filter '!./site' && pnpm build:hands # Node ≥ 24, pnpm 10 (npm install -g pnpm@10), Xcode 16
pnpm build:mac # builds, signs (ad-hoc without a certificate), installs /Applications/Jarhead.app
open -a Jarhead # Setup opens: your OpenAI key, a brain, permissions
# then say "jarhead", pass Touch ID, talk
Every picture below is rendered by the app's own preview harnesses over fixed fake data
(scripts/make-readme-shots.sh regenerates them; nothing here is a photo of a desktop).
The Console shots are JPEGs: a dithered ground does not compress as PNG.
Press ◎ and the island folds out of the way while you draw; the peek reads
◎ Circle something · Esc. The blob outlines what you circled and comes back to the notch.
Asleep, a landed mark glows the lip amber and says ◎ 1 circled · Go to ask for six seconds.
▭ captures the front window whole, no drawing, awake or asleep. Two engine commands carry the
films: mark.remove {id} (the × on a film) and mark.window (the front window as a mark);
everything else rides the commands the Console already sends.
The blob flew to its target and stays there. The perch is where it came from; the notch is empty until it sleeps.
Three threads at once: Notes and Spotify on the background lane finish on their own, and Slack on the screen lane stops to ask before it sends, with its Stop in the Now panel.
One face per state, shared by the blob and the notch. Expressions change through a 90 ms blink.
A brain said show_rect with a label. The blob became the pen and drew it.
A true circle, five bands, the same Bayer matrix as the island, wearing the blob's ^ ^: flat paper chevrons boxed in flat ink, one cell pattern from 64 to 1024 (cell = size / 64), a hand bitmap at 32 and a dot pair at 16, the gleam above the eyes. pnpm build:icon renders it (scripts/icon-render.ts, pinned by scripts/__tests__/icon.test.ts). pnpm build:banner renders docs/media/banner.png from the same orb, with the same face. The banners at the top of this page are the website's, captured by site/scripts/make-cards.sh.
flowchart LR
K((you)) -- "mic PCM · wake word · circle" --> APP
subgraph APP["Jarhead.app (Swift)"]
direction TB
ORB["blob · notch island · capsule"]
CON["Console · Setup"]
WAKE["wake gate + ear<br/>on-device recogniser"]
OVL["overlay<br/>click-through, per display"]
end
APP <-- "unix socket<br/>[type u8][len u32] frames<br/>JSON · mic PCM · speaker PCM" --> D
subgraph D["jarheadd (TypeScript, tsx)"]
direction TB
E["Engine"]
L["Live client"]
DEL["Delegator + Reflexes"]
B["Brain<br/>codex · claude-code · anthropic-api<br/>openai-compatible · openai-responses · local"]
TR["ToolRunner + policy"]
W["Threads<br/>table · scheduler · brain pool"]
LED["Ledger<br/>~/.jarhead/ledger"]
AG["Agents<br/>sessions on disk and running"]
end
L <-- "wss · full duplex" --> OAI[("GPT-Live-1")]
TR --> H
subgraph H["jarhead-hands (Swift helper × 2)"]
HF["focus"]
HB["background"]
end
H --> MAC["the Mac<br/>ScreenCaptureKit · CGEvent · AX · Apple events"]
CLI["pnpm jarhead<br/>status · cmd · ledger · bench · dock · automations"] --> D
Speech goes to GPT-Live-1 and comes back as speech; nothing on that path waits for
a tool. GPT-Live-1 takes its own turns, so the engine decides, per voice turn and before
its first audible frame, whether it asked for that turn
(packages/engine/src/voice-attention.ts): a turn nobody asked for is kept on the
record, marked unheard, and dropped before the speaker. When you ask for something to
be done, Live delegates to the brain through the Delegator, which refuses a delegation
raised for room talk; the brain calls tools through one ToolRunner, which asks
policy.ts first and the Swift helper second. The reflex layer sits in front of the
brain and acts on the ear's partial transcripts for the commands a grammar can settle,
when they are said to Jarhead. Jarhead's own look (a screenshot, a circle) needs no
address. The app spawns the daemon, or attaches to one already running. A clean quit
stops the daemon the app started; after a crash it lingers 90 s for the relaunch. A
daemon you started yourself (pnpm jarheadd) keeps running.
packages/protocol the contract: snapshot, events, commands, ledger rows (Swift mirror: apps/mac/…/Model/Protocol.swift)
packages/live GPT-Live-1 client: session, typed events, appends, the voice instructions
packages/hands the toolset over the helper, HandsPool (focus + background), FocusLease, ConfirmationState
packages/hands/native jarhead-hands: the helper's ops as newline JSON, ScreenCaptureKit + CGEvent + AX + compiled Apple events
packages/agents sessions on disk and running (Claude Code, Codex, other CLIs); claude-code continues one
packages/brain Delegator, ToolRunner, one Brain per kind, reflexes, self-edit, the MCP bridge for Codex
packages/core config, env, ledger, trash, policy, latency marks
packages/memory the memory store: extraction after a conversation closes, embeddings (OpenAI or local) with a keyword fallback, retrieval, the 250/120-token renders, forget/restore
packages/engine the Engine: sessions, transport, threads (table, scheduler, brain pool), sleep, problems, snapshots
packages/daemon jarheadd: the Engine over a unix socket
packages/cli pnpm jarhead: doctor, status, ledger, bench, cmd, dock, automations, recipes, …
packages/install the in-place installer: bundle compare/rsync, Dock and LaunchServices hygiene
site the landing page (jarhead.kevinliu.studio): Next 16; Newsreader, Inter and JetBrains Mono; serves scripts/install.sh at /install.sh
apps/mac Jarhead.app: blob, notch, overlay, Console, Setup, audio, wake gate, permissions, crash guard, banners
Say it once while Jarhead is awake ("wake me at seven ten on weekdays", "twelve-minute timer for
the pasta", "when a PDF lands in Downloads, file it under Papers and tell me") and it reads one
line back. Then say night. The daemon carries it out from its 1 s tick with the agent
asleep: no Live session, no brain turn, nothing billed. An alarm rings on the notch island
with Snooze 10 · Done where Allow · Deny usually sit, a chime, a banner with the same two
buttons, and the line through the Mac's own voice; a watcher moves the PDF and chimes; a routine
opens Notes. What runs asleep is the run tier and nothing else:
chime · say · notify · open · file · run-recipe · press, plus wake-brain (one capped
headless brain turn, never the voice), opted into per row with its cost said before your yes.
Out of the box only chime · say · notify · open · file are switched on (Console › Settings ›
Automations, the While asleep chips). So "run the backup script every night at eleven" is
refused until you switch run-recipe on; then it asks once. A recipe that exits red is a
problem on the island ("backup failed 23:00 · recipe backup exit 1") with Run now, or Open
Console when a retry would fail the same way, until its next good run. Moving the row to the
Trash, Clear all under the Console's Problems, or a daemon restart clears it too: problems live
only in the daemon's memory. A recipe, a press or a brain wake asks once, at set-up;
anything that would need a yes when it fires is refused then, with the nearest safe kind
offered. Nothing fires once the daemon is gone: a clean quit stops the daemon the app started.
One you started in a terminal (pnpm jarheadd) keeps running and keeps firing. Open at login
(your press) brings it back with you, and missed fires say so, with Run now.
Rows are never deleted: Move to Trash, Restore. pnpm jarhead automations,
pnpm jarhead recipes (a recipe has the same Trash and restore), a doctor group,
docs/AUTOMATIONS.md.
Measured on this Mac, except the one row marked third party, and written down with its n and its date. Three kinds, kept apart:
pnpm jarhead bench drives the real Swift helper with a stand-in brain and
sends every acting op of the ear to a harmless cursor read; the model-path harness runs real Codex with canned hands.~/.jarhead/ledger): 41 delegations from 2026-09-12 to 09-28, after
the latency pass, unless a cell says otherwise.scripts/live-check.mts, below under Develop), run on
2026-10-06; the reports are JSON files in build/live-check/2026-10-06/.The median of an even n is the mean of the middle two. The bench, ledger --speed and the live-check print it.
Their saved runs (2026-09-11 to 10-06) printed the lower one (nearest rank): 4.4 s, 8.9 s, 3.8 s and 1.35 s where
this table says 4.5 s, 9.0 s, 3.9 s and 1.38 s. The bench rows with an even n (50, 20 and 30, on both dates) are
that lower one. The perf audit's ledger script read 12.6 s, 82 ms and 96 ms for 13.3 s, 83 ms and 98 ms.
Sources: docs/LATENCY.md, docs/latency/after.json,
docs/REDESIGN.md §12 · §13 · §16 · §20, packages/hands/native/README.md.
| what | harness | in use or live |
|---|---|---|
| ear final → hands dispatch (real helper) | 8 ms median · 29 ms p95 (n = 50, 2026-10-06, load average 160 to 200); 3 ms · 6 ms p95 on 2026-09-11 (n = 50) | not logged |
| ear partial → dispatch, prefire kinds (scroll, page, screenshot, circle; the 120 ms stability window included) | 128 ms median · 145 ms p95 (n = 20, 2026-10-06); 122 · 126 ms on 2026-09-11 (n = 20) | not logged |
| ear partial → dispatch, careful kinds (keys, edits, type, click; the 450 ms window included) | 462 ms median · 529 ms p95 (n = 30, 2026-10-06); 455 · 457 ms on 2026-09-11 (n = 30) | not logged |
| delegation → first visible action | 4.5 s median · 5.1 s p95 (Codex through the app-server, canned hands, n = 6, 2026-09-12) | 7.0 s median · 27.6 s p95 (n = 15); before the latency pass 12.5 s median · 17.5 s p90 (simple commands, n = 10, 2026-09-10 to 11) |
| delegation → verified completion | 9.0 s median · 25.6 s p95 (n = 10, 2026-09-12) | 13.3 s median · 50.0 s p95 (n = 28); before the pass 22.1 s · 40.7 s p90 (simple commands, n = 11, 2026-09-10 to 11) |
| one model generation (gpt-6-astra, the floor under the model path) | 3.9 s median · 6.0 s p95 (n = 24, 2026-09-12); 3.4 s · 5.9 s p90 over 35 controlled generations (2026-09-11) | |
| speech end → Live's delegation (Live's own transcription and decision) | live: 1.5 s median, 0.95 to 1.77 s (LC-10, n = 10 over two runs) | |
| spoken request that delegates → the first audible "looking." | live: 2.2 s median, 1.7 to 3.3 s (LC-10, n = 10 over two runs) | |
| typed line → first audible frame of the reply | live: 1.38 s median · 1.67 s p90 (LC-5, n = 10, at 98c7cfe); 1.89 s · 2.25 s in a run nine hours earlier (n = 10). The engine's share: 1 ms median to put the line on the wire, 0 ms from the first audible frame to the speaker sink. The app's playout cushion adds 120 to 200 ms | |
| GPT-Live-1 reply to speech | third party, not this Mac: 1.11 s median · 1.21 s p90 (Agora, the ChatGPT app on an iPhone 13, n = 30 per condition, 2026-07-09) | |
Go → the session open (socket to session.started) | live: 0.62 s median, 0.46 to 2.78 s (LC-1 to LC-10, n = 28 sessions) |
pnpm jarhead bench exits 1 when, with the real helper, the p95 to dispatch is over 250 ms for finals or prefire
partials or over 580 ms for careful partials (--no-gate reports it and exits 0).
Limits and settings, from the code:
| what | value |
|---|---|
| Live billing | $0.05 / min, per second of open session, muted or not; a closed session costs nothing |
| idle sleep | 10 min with no turn addressed to it (a setting; 0 turns it off), 30 min while a task or a thread runs · one clause, "going to sleep", 5 s before · past 30 min (or the idle setting, when longer) with no request of yours it sleeps whatever the room says |
| the exchange | a follow-up without the name counts within about 8 s · the first words within 30 s of a question Jarhead passes on from the brain, a thread or a timer, or asks in reply to a typed line, are its answer, whoever says them, but not while a confirmation waits · capped 2 min after the last name, typed line, circle, Go, line Jarhead was asked to say, or your reply to a line you heard |
| threads | main + 3 live · 25 steps / 180 s default · 40 / 300 cap · linger 30 s |
| the lease | hand-over after 3 s idle · a taker waits 1.5 s · a thread waits ≤ 8 s, three waits fail it |
| your hands | a key, click or scroll of yours holds the helper busy for 1500 ms · a type re-reads the front app and the focus every 50 ms |
| liveness | the app pings every 2 s · two unanswered: drop and reconnect · 8 s of silence: kill and respawn the daemon |
| crash | relaunch ≤ 3 in 10 min · daemon lingers 90 s · a clean quit stops it at once |
| tools · permissions · brains | 71 · 16 (7 required) · 6 + auto |
| retention | ledger forever (default) · screenshots 14 days · disk preflight 500 MB |
packages/core/src/policy.ts classifies every action, path, AppleScript and URL as run, confirm or refuse, each with a spoken reason. No tool is special-cased in the runner.ConfirmationState). No voice is verified. Destructive verbs (send, pay, delete, post, purchase) ask every time, in every lane.mkfs, diskutil erase, dd onto a device, shutdown, rm -rf / or ~, resets of other apps' TCC, and every secret store: ~/.jarhead/env, wake-auth.json, ~/.ssh, ~/.aws, keychains, browser cookies, .env*, ~/.codex/auth.json, ~/.claude/.credentials*. Any command that sweeps a folder holding one of those is refused too.~/.jarhead/env (mode 0600); snapshots carry presence, never values. Every process the brain spawns has the secret keys stripped; every text result is passed through a redactor before a model reads it.voice-attention.ts; engine room-talk-gate.test.ts).~), runs a recipe you approved once; it never opens the voice session and never spends a brain turn unless that one row says billed and you said yes to its cost. Nothing asks at fire time (a would-be question is a row), and nothing is set up by a : the yes is heard by voice or pressed in the Console, once, and spent on that row.The site is jarhead.kevinliu.studio (site/, a Next app; a plain pnpm install,
then pnpm -C site dev).
macOS 14+, Apple silicon, Xcode 16 or newer (Xcode 16 needs macOS 14.5 or later; the app's SwiftUI uses the macOS 15 SDK,
back-deployed to macOS 14, so scripts/install.sh refuses a Swift older than 6.0 before it compiles anything; CI builds the floor with the oldest Xcode 16 on macos-14), Node ≥ 24,
pnpm 10: npm install -g pnpm@10. Node 25 and newer ship no corepack; where your Node still has it, corepack enable
gives you the exact version package.json's packageManager names. An OPENAI_API_KEY for the voice; a brain
you are already signed in to. What running it costs is under Costs.
pnpm install --filter '!./site' # the landing page's dependencies left out; the app never uses them
pnpm build:hands # the Swift helper → build/jarhead-hands
pnpm build:mac # builds, signs, installs /Applications/Jarhead.app IN PLACE
open -a Jarhead # first launch opens Setup, which writes your key to ~/.jarhead/env
pnpm run doctor # keys, brain, permissions, sessions, signing, wake word, install, dock; red on the key until Setup has written it
~/.jarhead/env is plain KEY=value lines (# starts a comment), mode 0600, written by
Setup › Voice. Or create it by hand with OPENAI_API_KEY=… before the first launch and
Setup finds the key already there.
Moved the checkout? Run pnpm build:mac again: the bundle remembers where the repo
is, and a stale path stops the daemon (the error names JARHEAD_REPO).
A signing certificate is optional. Without one, pnpm build:mac signs ad-hoc and installs,
and macOS resets the permission grants on every rebuild. To keep them, sign with a real
identity before the first build:mac: an Apple Development or Developer ID identity in the
keychain is picked up on its own; without one, make a self-signed certificate (Keychain
Access › Certificate Assistant › Create a Certificate, type Code Signing, marked trusted
for Code Signing in Keychain Access › the certificate › Trust, so security find-identity
lists it) or set JARHEAD_SIGN_IDENTITY. An ad-hoc build the keychain fell back to never
installs over a copy a real identity signed; JARHEAD_SIGN_IDENTITY=- forces it.
pnpm build:mac prints the identity it picked, and how, before it signs; pnpm run doctor
names the identity on the installed app and warns on ad-hoc.
First launch opens Setup. Then say "jarhead", pass Touch ID, talk. Reopen Setup from the menu-bar icon › Set Up….
pnpm jarhead status # phase, session voice, brain, hands, permissions 16/16, agents by status (working · idle · blocked · done · ended · unknown · offline), threads N (M live), memory, problems
pnpm jarhead dock [--fix] # one Jarhead: Dock tiles + LaunchServices records; --fix restarts the Dock once
pnpm jarhead doctor # the same checks as pnpm run doctor (the memory group: counts, matching, the extractor model; the local group: server · model · embeddings; the privacy group: the four "where words go" rows)
pnpm jarhead models [--json] [--server URL] # the models on this Mac's local server (Ollama / LM Studio / llama.cpp): id · size · ctx · tools/vision/thinking/embedding · fit · which the brain and memory use; no daemon needed; nothing is pulled
pnpm jarhead brain # the brain setting, what runs now, and where words go (the four data-path rows)
pnpm jarhead brain local [<model>] [--server URL] # pick a local model as the brain through the running daemon (memory follows); empty model = best fit
pnpm jarhead brain <auto|codex|claude-code|anthropic-api|openai-responses|openai-compatible> [<model>] [--server URL]
pnpm jarhead cmd go|pause|resume|stop|interrupt|sleep [cause]|mute|unmute|agent.refresh|thread.stop|thread.pause|thread.resume # thread.* take <id|name>
pnpm jarhead ledger [YYYY-MM-DD] # a day in local time, no daemon needed; it only reads
pnpm jarhead ledger search "<words>" [--limit N] # over every live day, newest first
pnpm jarhead ledger trash <day> [--shots|--both] · restore <day> · sweep
pnpm jarhead memory [list] [--state live|forgotten|archived|merged|all] [--limit N] # what it knows about you, one sentence each
pnpm jarhead memory search "<words>" · forget <id> · restore <id> · add "<text>" [--kind k] · run # forget hides; nothing is deleted
pnpm jarhead agents # the sessions found on this Mac
pnpm jarhead bench [--runs N] [--no-gate] [--no-duck] [--fake-hands] # the tool path and the ear's 250 ms path; no request leaves the Mac
pnpm jarhead bench --brain [--runs N] [--effort low|medium|high|xhigh|max] [--json --out F] # Codex on your plan; refuses API spend without --allow-api-spend
pnpm jarhead probe "…" · say "…" · live · hands
The state directory is ~/.jarhead:
| path | what |
|---|---|
env | keys and knobs, mode 0600, written by Setup |
settings.json | what Setup and the Console set |
ledger/<day>.jsonl | everything that happened, append-only |
shots/ | what the brain saw, by day |
memory/ | what it knows about you: memory.jsonl (append-only), index.json, embeddings.jsonl |
automations/jobs.ndjson | the automations journal, append-only; compacted journals move to trash/automations/ |
trash/, trash/shots/ | days and screenshots that were moved, by rename |
crashes/<time>.txt | crash reports |
worktrees/ | self-edits in progress |
codex-home/ | the private CODEX_HOME |
shell/ | background run_shell logs |
wake-auth.json | the passphrase hash |
daemon.log, jarhead.sock | the daemon's log (rotated at 5 MB) and socket |
⌃⌥J | open the Console |
⌃⌥M | mute / unmute |
⌥⎋ | stop: cut everything, close the session, sleep |
⌥⇧Space | go / pause |
⌃⌥C | circle something on screen |
⌥⇧Return | type to Jarhead: the island's line while the blob is in the notch, else the Console's composer |
⌃⌥S | snooze the automation that is ringing (Settings › Automations › Snooze minutes; timers 5) |
⌃⌥R | Recording on / off (Settings › Audio) |
The letters take ⌃⌥, which types no character (apps/mac/Scripts/hotkey-check.sh checks every combo on the US
layout, in CI). ⌃⌥ is also VoiceOver's modifier, and a combo another app registered first stays that app's
(the failure is only logged). defaults write com.kevinliu.jarhead hotkeys.off -bool YES turns every global hotkey
off at the next launch except ⌥⎋ Stop.
⌥⇧Space is go / pause. On the US layout it is the one hotkey that types a character, a no-break space
(U+00A0). While Jarhead runs that combo never reaches a field; ⌥Space still types one.
apps/mac/Scripts/HotkeyCheckMain.swift allows this one combo.
In the Console: ⌘P go / pause, ⌘. stop. URLs: jarhead://go, jarhead://pause,
jarhead://stop.
| kind | how | needed for |
|---|---|---|
| Microphone, Speech Recognition | prompt · required | hearing you; the wake word and the ear |
| Screen Recording, Accessibility | prompt · required | screenshots; clicks, typing, reading controls |
| Input Monitoring | prompt · required | the keys watched while you circle or dictate |
| Full Disk Access | System Settings only · required | Mail, Safari, Messages, the Trash, every guarded folder |
| Automation | one prompt per target app, asked only for apps running at the time · required | the browser fast path and AppleScript |
| Notifications, Camera, Contacts, Calendars, Reminders, Local Network | prompt | banners; the camera; who, when, what is due; devices nearby |
| Desktop, Documents, Downloads | prompt | files there |
macOS grants nothing programmatically, so the sweep asks. A denied read comes back as one line
(macOS blocked this: Jarhead lacks Full Disk Access. Setup › Permissions › Ask for everything),
never a raw errno. pnpm jarhead status --permissions prints a row each.
Keys and knobs live in ~/.jarhead/env. Everything below is optional.
| variable | what it is for |
|---|---|
OPENAI_API_KEY | the voice; the openai-responses brain |
ANTHROPIC_API_KEY | the anthropic-api brain |
JARHEAD_BRAIN_BASE_URL, JARHEAD_BRAIN_API_KEY | the openai-compatible brain; under local, the URL pins the server instead of discovering it and the key is an LM Studio token; Ollama needs neither |
JARHEAD_BRAIN, JARHEAD_BRAIN_MODEL, JARHEAD_BRAIN_EFFORT | defaults for what Setup also sets |
JARHEAD_LIVE_MODEL, JARHEAD_VOICE | gpt-live-1, ballad (English; the accent is a setting) |
JARHEAD_MEMORY_MODEL | the Responses model that reads closed conversations for memory; unset, the memory module's default mini-class id runs (jarhead doctor checks it against your key's list and names the best *-mini to pin) |
JARHEAD_IDLE_SLEEP_MINUTES | the idle sleep default in minutes (10) until Settings saves a value; from then on settings.json wins |
JARHEAD_LOG_LEVEL | debug · info · warn · error (default info) |
| (automations) | no env knob: the master switch, the kinds allowed while asleep, quiet hours, Snooze minutes, Brain minutes per day, the recipes and Open at login live under automations in ~/.jarhead/settings.json (Console › Settings › Automations; pnpm jarhead recipes for the recipes: trash is Move to Trash, restore undoes it, nothing is deleted) |
JARHEAD_CLAUDE_BIN, JARHEAD_CODEX_BIN, JARHEAD_CURSOR_AGENT_BIN | the CLIs when they are not found. Codex is looked for on PATH, then inside ChatGPT.app (Contents/Resources/codex-cli/bin/codex, the layout since late September 2026), Codex.app, ~/.codex/bin, nvm, Homebrew and ~/.local/bin |
JARHEAD_CODEX_SIMPLE_EFFORT, , , |
Let Jarhead do it. "Jarhead, make your greeting one word shorter." self_edit
makes a worktree under ~/.jarhead/worktrees on a branch off main (refused while
main is dirty), hands the task to Codex, Claude Code or the brain's own file tools,
commits as Jarhead, runs pnpm install when the lockfile moved, typecheck, test,
and swift build when apps/mac changed, and speaks a summary: files, diff stat,
checks, rails touched. self_review shows the diff; self_apply asks "Apply the
change to Jarhead and restart it?" and fast-forwards main only on your yes;
self_discard drops it. Fifteen minutes per edit.
Or by hand.
pnpm run check # typecheck + tests + doctor; green before and after (the doctor needs a key in ~/.jarhead/env)
pnpm test # node:test over packages/*/src/**/*.test.ts
node --import tsx --test packages/core/src/__tests__/policy.test.ts
JARHEAD_AUTO_WAKE=0 pnpm jarheadd # the engine alone, quiet
cd apps/mac && swift build && JARHEAD_REPO=$PWD/../.. .build/debug/Jarhead # the app from a terminal (Terminal owns TCC then)
pnpm build:media # build:icon (the Dock icon, the contact strip docs/media/icon-sizes.png) + build:banner (docs/media/banner.png; the banners at the top come from site/scripts/make-cards.sh)
Protocol first: change packages/protocol/src/index.ts, then its Swift mirror
apps/mac/Sources/Jarhead/Model/Protocol.swift (apps/mac/Scripts/protocol-probe.sh
checks the mirror). CI (.github/workflows/check.yml) runs five jobs: typecheck + test
with the network fenced (JARHEAD_TEST_NET=strict) and the installer's dry run on macOS 15;
build:hands + swift build + that probe + the wake gate, sweep and hotkey checks on macOS 15;
a release build signed ad-hoc (JARHEAD_BUILD_ONLY=1, nothing installed) on macOS 15; the
same Swift build on the floor, macOS 14 with the oldest Xcode 16; and the site's typecheck and build.
Live checks. scripts/live-check.mts runs the paid checks LC-1 to LC-10 against the real
GPT-Live-1 server: a real engine over a temp state dir, fake hands on both helpers, a canned
brain, no microphone and no speaker. A spoken line is typed text turned into PCM by
gpt-4o-mini-tts and fed as mic frames; it is never played. Kevin runs them:
node --import tsx scripts/live-check.mts list # the plan and today's spend
node --import tsx scripts/live-check.mts LC-7 --i-accept-spend --cap-usd 1.00 # one paid check; `all` runs each in order
node --import tsx scripts/live-check.mts LC-7 --dry-run # the same scenario offline, against a scripted stand-in
node --import tsx scripts/rejudge.mts build/live-check/2026-10-06 # judge saved reports again with this checkout's judges, free
A live run refuses without both flags. The cap is the day's total across every check, at most
$1.00, kept in ~/.jarhead/live-check/spend.ndjson; each check also has its own cap in billed
seconds, and a watchdog ends the session at it. Each run writes one JSON report and the
engine's log to build/live-check/<day>/. On 2026-10-06 the harness counted $0.72 over 19 runs,
and the latest run of every check passed every hard assertion (LC-1, LC-3 and LC-8 last ran
at c7d4e63; LC-2, LC-4 and LC-9 at 48aa9a9; LC-5, LC-6, LC-7 and LC-10 at 98c7cfe).
Preview harnesses render the UI over fake data with no daemon, no session and no TCC. The screenshots above come from them:
apps/mac/Scripts/console-preview.sh <scenario> [out.png] # live · threads · conversation · jarhead · ledger · settings · problems · cleanup · search · light · …
apps/mac/Scripts/onboarding-preview.sh <step> [out.png] # welcome · voice · brain · permissions · wake · agents · done · all
ORB_NOTCH=1 ORB_SHOT_DIR=… apps/mac/Scripts/orb-preview.sh # the blob, the notch, the overlay; knobs in UI/Orb/OrbPreviewApp.swift
apps/mac/Scripts/orb-preview.sh --notch-checks # every rule of the island as `check: … OK`, one recipe per knob set; `notch sends:` at exit
scripts/make-readme-shots.sh # every README screenshot into docs/media, fixed names, ≤ 1600 px, ≤ 600 KB
Working rules for anyone, or anything, editing this repo: AGENTS.md.
codex runs on your ChatGPT plan through the Codex in ChatGPT.app or codex login, with no API dollars; claude-code on your Claude login; anthropic-api, openai-compatible and openai-responses bill their own APIs; local bills nothing; the voice still does. Under openai-responses the brain runs on GPT-Live-1's server side, so a delegation it raises for room talk can still spend tokens there, though the engine refuses its tool calls and drops its words.gpt-5-mini unless JARHEAD_MEMORY_MODEL or your key's model list picks another). It runs while Jarhead sleeps, once per closed conversation with at least four new lines of yours, with no daily cap: Kevin's ledger has 10 such reads on 2026-09-13, its busiest day. Items and searches are matched by embeddings on the same key; that cost has not been measured here. Nothing existing shrinks; what you save is explaining yourself again. With no key: rules and keywords, nothing leaves the Mac. With a local brain: the brain model reads closed conversations and a local embedding model matches items when one is pulled (ollama pull embeddinggemma), else keywords; nothing leaves for memory.pnpm jarhead bench sends no request off the Mac (cli bench-spends-nothing.test.ts). With the real helper, the ear's acting ops go to a harmless cursor read and the one acting call moves the pointer to where it already is. bench --brain runs on Codex (your plan) and refuses when Codex is not signed in unless you pass --allow-api-spend.docs/REDESIGN.md: architecture, every decision, dated.docs/LATENCY.md: the before and after numbers and the field side by side.docs/DEMO.md: a ninety-second single take.docs/AUTOMATIONS.md: alarms, timers, reminders, routines, watchers: what fires with the agent asleep, what asks once, what is refused, the CLI and the doctor group.docs/AUDIO.md: Jarhead and everyone else's sound: what the echo-cancellation unit is told and when it is released, the Recording switch, the ten-second check, the probes and what this Mac said, and Jarhead's own twelve sounds and when each plays.apps/mac/README.md: the native app: packaging, TCC, wake word, audio, wire protocol.packages/hands/native/README.md: the helper's protocol, ops, numbers.AGENTS.md: rules for agents editing this repo.MIT. See LICENSE.
codexcodex app-serverclaude-codeanthropic-apiopenai-compatibleopenai-responseslocaldocs/LOCAL.mdautoopen_url waits too, as does a shell line that runs open (not open -g), or an AppleScript or osascript that activates an app or sends keystrokes or clicks. An open run through xargs, find -exec or a variable is not caught. During a type, your key, click or scroll stops it, and so does the focus moving to another window or out of the text field; the result says how many characters landed. A thread is never refocused behind you.⌃⌥C, draw around anything. The mark snaps to the largest control under it and every brain gets the image with the task.- - O O ^ ^ u u x x).⌥⇧Return; Clear · Circle ◎ · Window ▭ · Ask as one strip), and the foot (one line of words: the meter as 4:12 · 7.2 min · $0.36 · today, asleep ☾ asleep · next Alarm 07:10, or a problem with its remedy; Console and Sleep). No bar rides the head or the foot. The peek carries glance chips (✋ Slack asks, ◎2, a problem's glyph, 2.3 min), never sentences. Drag the blob into the notch and it goes to sleep.~/.jarhead/ledger/<day>.jsonl. The Console shows only what was recorded. Search it from the rail or pnpm jarhead ledger search.~/.jarhead/memory, matched by embeddings, scored by recency and use. Each task gets at most 250 tokens of it, each session at most 120, never read back to you. Forget hides an item; nothing is deleted. Off with one switch: then nothing is extracted, injected or embedded.<Name> · English.~/.jarhead/trash by rename and come back the same way. A conversation in the Trash also hides what memory learned only from it, until Restore. Retention is a setting whose default is forever.~/.jarhead/crashes/, the app relaunches (at most three times in ten minutes), and the daemon lingers 90 s with the Codex thread warm. A clean quit stops the daemon at once.pnpm run doctor warns.pnpm build:mac installs in place with rsync: the running app keeps its inodes, the Dock pin keeps its bookmark and, signed with a real identity, TCC keeps its grants. A second launch hands off to the running app, which brings its Console forward, and exits. The hands helper is background-only and never shows in the Dock. pnpm jarhead dock audits the Dock; --fix repairs it.self_edit runs a coding agent in a git worktree of this repo, runs typecheck, tests and the Swift build, tells you what changed and which safety rails it touched, and applies only after your yes; a touched rail needs you to name it.~/.jarhead/env; the app only ever sees that they exist.docs/AUDIO.md §10).CODEX_HOME under ~/.jarhead with Jarhead's own base instructions, so a cold thread cost 10.7k input tokens instead of 22.3k (counted once by the real model, n = 1, 2026-09-12) and no stray AGENTS.md steers it. The orders and tools have grown since: about 11.4k by estimate now (brain w3-4-tool-parity.test, "BR-16").| tool round trip | 15 ms median · 188 ms p95 (frontmost_app through the runner and the real helper, n = 5, 2026-10-06) | 83 ms median · 518 ms p95 (n = 162); 75 · 692 ms over every day since 2026-09-10 (n = 590) |
| screenshot, end to end (ScreenCaptureKit) | quick (2000 px, 1.1 MP) 117 ms median · 305 ms p95 (n = 5); full 128 ms (n = 1); 2026-10-06, screen locked | 98 ms median · 311 ms p95 (n = 72) |
| a read while the acting helper types | 4 ms median · 16 ms p95 (n = 5, 2026-10-06) |
| stop: command → everything stopped | 1 ms median (n = 5, 2026-10-06) |
| cold Codex thread, input tokens | 22.3k → 10.7k (−52 %) with the private home and Jarhead's base prompt, counted once by the real model (n = 1, 2026-09-12); about 11.4k now by estimate (brain w3-4-tool-parity.test, "BR-16") |
failed--yespolicy.ts, brain.ts, instructions.ts, the wake gate, selfedit.ts, the runner, the shell and file tools, the confirmation handshake, build signing, the Codex sandbox flags, the Claude Code permission gate, SECRET_KEYS, and the core and brain module indexes that re-export them. A change that touches one applies only if you named it.docs/AUDIO.md).JARHEAD_CODEX_SERVICE_TIERJARHEAD_CODEX_PRIMEJARHEAD_CODEX_BASE| Codex tuning, all opt-in |
JARHEAD_AUTO_WAKE=0 | do not open a voice session on start; the daemon never auto-wakes while the wake gate is on, so this matters only with the gate off; set it for every headless test launch |
JARHEAD_NO_AUDIO=1 | never touch the microphone (headless launches) |
JARHEAD_STATE_DIR, JARHEAD_SOCKET, JARHEAD_REPO, JARHEAD_NODE, JARHEAD_HANDS_BIN | where things are |
JARHEAD_SIGN_IDENTITY | the code-signing identity (- forces ad-hoc); without it the pick is Apple Development, Developer ID, a certificate named for Jarhead, a Code Signing name, then the first listed, then ad-hoc; printed before anything is signed |
JARHEAD_BUILD_ONLY=1 | stop pnpm build:mac once the stage bundle is signed and verified; nothing under /Applications is read or written (CI, a dry run) |
JARHEAD_INSTALL_HYGIENE=0|fix | skip the Dock / LaunchServices pass, or repair the Dock |
JARHEAD_INSTALL_SNAPSHOT=1 | take a rollback snapshot (a Jarhead.app.zip archive under build/previous/) before the in-place install; default off, git is the rollback |
JARHEAD_LINGER_MS | how long the daemon waits for a relaunch (90 000) |
JARHEAD_PERMISSIONS_DRY_RUN=1 (_DENY) | log what would be asked, ask nothing |
JARHEAD_CRASH_TEST=exception|signal, JARHEAD_NO_RELAUNCH=1 | crash a dev build on purpose; keep it down |
JARHEAD_HANDS_DEBUG, JARHEAD_EAR_LOG | helper and ear tracing |