reindent/jauvex
Your coding agents, side by side, by voice. Claude, Codex & Grok in one desktop app, with Jev for the fast decisions. - reindent/jauvex
3 stars · 2 forks
README
Jauvex Your coding agents, side by side, by voice. Claude, Codex and Grok in one desktop app, with Jev (TypeSafe) for the fast decisions. Jauvex Personal, version 1.2.1, for macOS; Apache License 2.0. Source: github.com/reindent/jauvex; site: jauvex.reindent.com. Made by Reindent (one human and agents). Jauvex is an Electron client for the Claude Code, Codex and Grok Build sessions on your Mac. Add a folder, pick up any of its sessions or start new ones with either provider, and talk to them: a voice channel that answers in three beats (a quick word, what it understood, a summary of the agent's answer), steers a working agent without interrupting it, names and starts agents by voice, and lets agents talk to each other inside the app. No server: the window talks to the main process over IPC, and your sessions stay where Claude Code and Codex keep them. What changed in each release: CHANGELOG.md. Installing One command in Terminal: curl -fsSL https://jauvex.reindent.com/install | sh
It needs Node 22.18 or newer. It downloads this source, checks its SHA-256 and builds Jauvex on your Mac: nothing prebuilt is downloaded, so there is nothing for Apple to notarize. Jauvex lands in Applications (~/Applications when /Applications is not writable), a real app with its own name, icon and microphone permission; the source and the build stay in ~/.jauvex/personal/app. Run the command again to update, with Jauvex closed, or let Jauvex do it: it asks jauvex.reindent.com which version is the latest (/api/personal/version), at launch and every six hours, and when a newer one is out the sidebar's footer says so ("1.2.0 is out") and the Jauvex agent asks you, once per version, whether to update now. On a yes (or "update the app" at any time) it runs node scripts/jauvex.ts update: refused while other agents work (--now on your word); otherwise the app fetches the same install command, checks that it installs the version offered, leaves it and a small runner in ~/.jauvex/personal/update/, hands the runner to launchd and quits. The runner waits for the app to exit, runs the install command (it rebuilds the app on your Mac and opens it), opens the old app again if it does not finish, and removes it; its log is update/update.log. Only the app the install command made updates itself; a clone updates with git (T-165). To remove it, quit it and delete Jauvex.app and ~/.jauvex/personal/app; its settings stay in ~/.jauvex/personal. To work on the code, clone this repository instead: npm start runs it from the clone, and npm run app makes the same app in tmp/mac-app/Jauvex.app (scripts/mac-app.ts: Electron's app renamed Jauvex, with the built app, the Whisper models, Claude and Codex inside, signed ad hoc on your Mac). Setting up on a fresh Mac Jauvex needs a few things that are not in this repository; the install command and npm start take care of most of them. The welcome screen (on the first start, and from Jauvex settings after) checks each of them and tells you what is missing.
Node 22.18 or newer (it runs the TypeScript scripts and checks as they are), then npm install in this folder (it also fetches the Electron binary; if the app ever says Electron.app does not exist, run npx install-electron). Claude or Codex, signed in: at least one is a must. Both binaries come with npm install (the Claude Agent SDK brings Claude Code, @openai/codex brings Codex), and Jauvex uses the account each one is signed in to on this Mac. You sign in with their own command lines, in Terminal: claude auth login (Claude Code; to install it, curl -fsSL https://claude.ai/install.sh | bash) or codex login (Codex; brew install codex). Jauvex signs no one in itself: Anthropic does not let apps built on its Agent SDK offer the Claude.ai login. The welcome screen and the accounts panel (the Jauvex button below the sidebar) say who is signed in and give the command; with one signed in, the Jauvex agent can walk you through the other. Grok, optional: Grok Build, xAI's coding agent, when it is on this Mac (curl -fsSL https://x.ai/cli/install.sh | bash, then grok login). Jauvex runs it as grok agent stdio with your Grok account and settings; without it, Grok is simply not offered. whisper.cpp for the ears: npm start installs it with Homebrew if it is missing and downloads the two models into models/ (git-ignored): ggml-small-q5_1.bin for the transcript and ggml-base-q5_1.bin for the live words while you speak. Each is checked against the size and SHA-256 Hugging Face lists for it (scripts/models.sh): a download goes to a .part file and becomes the model once it is whole, so one cut short is downloaded again the next time, never taken for done. Other models from huggingface.co/ggerganov/whisper.cpp can be dropped in the same folder and picked in the voice settings (ggml-large-v3-turbo-q5_0.bin, 574 MB, hears better and is still quick on Apple silicon). Without Whisper you can still type. A TypeSafe key for Jev, optional: in TYPESAFE_API_KEY or the file ~/.typesafe/token, the key alone (the way Hugging Face keeps its token; the older ~/.typesafe/jev still works). Without it the small voice model makes the decisions Jev would make, a little more slowly. The microphone: macOS asks the first time voice mode is switched on. The voice is macOS's System voice, through say. On a fresh Mac that is the basic Samantha, which sounds robotic: pick a Siri voice in System Settings > Accessibility > Spoken Content > System voice. An app cannot choose a Siri voice by itself (say -v falls back to Samantha); only that setting reaches them. The welcome screen checks this and has a button that opens the pane.
npm start # builds, then launches through macOS LaunchServices (start.sh) npm run dev # Vite + Electron with reload, for working on the UI
Put the folder somewhere plain, such as ~/Coding/jauvex: macOS protects Documents, Desktop, Downloads and iCloud Drive, and an app started there cannot read its own files until it has been granted access (npm start then starts it from the terminal instead, which works but attributes the permission prompts to the terminal). Only one copy may run at a time (the app holds a lock; a second launch focuses the first). It keeps its own state in its data folder, ~/.jauvex/personal (every copy, run from source or compiled; data/ below means that folder; CVC_DATA_DIR moves it), and uses ports 4340 (Vite, dev only) and 4341 (whisper-server), plus 4342 for the live words (Pro uses 4320 to 4322, so both can run side by side). Jauvex is built on your Mac from this source: npm start runs it from the Electron binary in node_modules, the install command as Jauvex.app. What it does today
Add folder: native folder dialog. Each folder is a project, like in Claude Code. + on a project: lists every Claude, Codex and Grok session recorded for that folder (title, first prompt, provider, branch, size, last activity), with a switch to see all, only Claude's or only Codex's (with counts) and each provider's mark on its rows. Tick the ones you want; they appear under the project in the sidebar. Folders fold, the sidebar resizes: a click on a folder's name (its folder icon open or closed) folds its sessions away, and it stays folded after a reload. The sidebar's right edge drags to any width from 220 to 560 px, kept too. Only a signed-in provider can be chosen: a provider that is not signed in on this Mac is greyed out, with the reason, in the provider selector, in the Jauvex agent's move selector and in the default-agent setting; a new session never starts on one. What agents are told about the thread: it is plain markdown, so no LaTeX (formulas and matrices as plain text or a code block), no Mermaid or other diagram languages (plain-text drawings in a code block), no raw HTML, local files as paths in backticks rather than links, images as markdown images. Terminal colour codes in tool output are stripped. A provider's failure is an error, not an answer: a turn that fails, or an answer that is the provider's own failure text (an organisation that disabled subscription access, an allowance run out, a network error), shows as a red card in the thread, and the voice says one line about it instead of summing it up. One provider per session, for life: a session imported from Claude continues with Claude, one imported from Codex continues with Codex, one from Grok with Grok (each session row carries its provider's own tiny mark, Claude's, OpenAI's or Grok's, and a Jev agent row TypeSafe's; the title bar has a chip. The marks are their owners' trademarks, used only to say whose session it is: the first three from @lobehub/icons-static-svg, MIT; TypeSafe's is its site icon, assets/typesafe.png). A new session lets you pick the provider in the composer until the first message is sent; the model and effort pickers follow the provider (effort: Claude's fixed levels, or the levels Codex or Grok reports for the chosen model; applies from the next message) (Codex models come from your account through model/list, Grok's from its agent's model list). Open a session: the conversation loads. User messages as bubbles, Claude's replies as text, tool calls and thinking folded into one-line rows you can expand (a Codex call shows the moment it starts and gets its result when it completes), harness plumbing (reminders, tool results, cross-session messages) hidden unless you toggle the eye icon. Long sessions page from the end ("Load earlier messages"). The Jauvex agent: one session that always exists, pinned at the top of the sidebar, with the app's own folder as its project and a briefing about the app itself. It is the entry point for everything about the app: restart it, update it (git pull, build, relaunch, on request), install what is missing, explain how it works, create agents and add folders (through the app's command line, below), and develop it for contributors. Every other agent is told it exists and to send it what concerns running the app, and that the app's code is theirs to work on too when the user asks and the source is in their folder (a Codex agent made a development agent once refused to read it, told that anything about the app was the Jauvex agent's). It runs on the default agent, keeps one session for life (ui.jauvexSession), and can be hidden in Jauvex settings (it still exists and still answers other agents). It moves between providers with its whole context (the only session that does, for now): the app keeps its own transcript of it (data/jauvex-transcript.json, user and assistant text only), the provider selector in its composer stays live, and the first message after a move carries the conversation so far as a prelude to a fresh session on the other provider, so Claude and Codex pick up where the other left off. Moving back works the same way. The thread shows the app's transcript, tool calls and results included (results trimmed), with a note at every move. A move ends voice mode (the voice engine and its acknowledgment model belong to the provider it started with): start it again when you like. Two ways to move, asked on the welcome when both providers are signed in and changeable in Jauvex settings: unified (the default, experimental: the app replays the whole conversation, and more can break) or handover (the leaving assistant writes a handover note in a visible turn, saved as data/jauvex-handover.md too, and the next provider starts from that note: cheaper on long histories, and the note says what mattered). The app's command line (node scripts/jauvex.ts <command>, in the install folder): every action in the app, for agents, the Jauvex agent above all. list (folders, sessions, agents, ids), add-folder, pick-folder (the folder dialog for the user; what they choose is added), new-agent (provider, folder, name, purpose, first message; unnamed, the provider is the Jauvex agent's own; with no first message it starts with its own introduction, since an agent exists once it has had one: on 2026-09-24 one ordered without it was an empty chat that vanished), open (a session, or the Jauvex agent, on screen; a session not in its folder's sidebar goes in it too: shown only as an open chat, seven sessions an agent opened dropped out one by one as other chats took the eight open places, T-152, the user, 2026-09-26), import (a folder's existing sessions into its sidebar without opening them, as its search button does; with no session named, the ones not listed yet: T-150, the user, 2026-09-26, after an agent asked to import sessions opened them over the chat he was in), send (a message into a session), rename, settings (default agent, Jauvex row, welcome next time), welcome, reload, restart. It writes a request file in data/commands/, the running app does the thing and answers in a result file, the script prints the JSON. Nothing but files: no port, no server. Default agent: the provider new sessions and the Jauvex agent start with. Asked on the welcome screen when both Claude and Codex are signed in (Start waits for the answer); set automatically when only one is; changeable in Jauvex settings. The right pane. A file or a link an agent shows (a markdown link, a path, a URL) opens in a pane on the right of the chat, never in the window itself: markdown rendered, text and code as they are, images shown, PDFs and web pages framed in a <webview> of their own that starts muted and stays muted and opens no windows. The pane has an "open outside" button (the Mac for files, the browser for pages), a close button, and a draggable width that is remembered. A navigation the app did not catch (a link in a place it does not watch) is stopped in the main process and sent to the pane too. Later the same pane takes terminals and browsers. Relative paths resolve against the session's folder; a link inside a file the pane shows resolves against that file's folder, and opens in the pane too (T-183). Boards (T-171): a folder's to-do lists, plain markdown in the folder (PROJECT.md, MARKETING.md, BOARD.md, ROADMAP.md, boards/*.md): ## Section headings (P0, P1, P2), items as - [ ] T-01 · Title — body with [ ] to do, [~] doing. A done task leaves the board, in the same edit, for the top of its done file beside it (PROJECT-DONE.md, boards/x-DONE.md), ending "— shipped YYYY-MM-DD (commit)"; a reopened one goes back to the top of the board's first section as to do; ids are unique across both files (T-174). A board written before brings its [x] tasks and its Done section there on its first write. The format has a version (T-173): a board's first line is <!-- boards: v1 --> (hidden when the markdown is shown); a board without it is v1 and gets it when the app writes it, and a board whose line names a newer version is shown as it is, never rewritten, with a note to update Jauvex. The file is the only truth and git its history; the agents keep them (every agent is told its folder's boards and how to keep them). They are listed under the folder's sessions, with how many items are done, and open as columns of cards, one per section, coloured by priority; a click on a card's circle moves it on (to do, doing, done: off to the done file); a click on the card shows its text; "Show done" adds the done file's tasks as a last column, where a circle reopens a task. Two views, switched in the board's header and remembered (T-180): Sections, a column per section, and Kanban, a lane per status (to do, doing, and done with Show done), each card tagged with its section; a circle moves a card to the next lane, in the file. A board's row, secondary click: Delete board, after a yes (the question names the file and its done file); both files go, and its view closes (T-179). A new board: the folder's options (New board), or new-board --name "..." [--folder ...] on the command line, which writes boards/<name>.md from a template and opens it. A board an agent writes shows up within seconds (on a refresh, when the window comes back to the front, and every 15 s); markdown with no items is not a board. Its own chat (T-199; the user, 2026-09-28: talking to boards, "it's very important"): under the board, "Talk to this board", by text or by voice, as under a Jev agent. A session of its own in the folder (the Jauvex agent's provider), told on every message what the board is now (the board itself, or, past 30 KB, to read the file) and the boards format; it adds, takes, moves and finishes items by editing the board's files, and the board redraws after every answer. Its session is kept in the app's state for that board's file (a board's folder is often a repository: nothing of ours is written there), and it is left out of the folder's list of agents. A folder reached through a symlink keeps its history too: Claude Code files a session by the real path, and the app looks there as well (tests/window/board-chat.test.ts). Images an agent shows (a markdown image with a local path, relative to its folder or absolute) load from the file and never overflow the thread (at most the thread's width and 60% of the window's height). Images in a message: paste a screenshot from the clipboard into the composer, drop image files on it, or pick them with the clip. They show as thumbnails until sent and in the thread after. Claude gets them inline; Codex gets each as a file under data/uploads/. Typed while a turn runs, they queue with their text: each queued bubble keeps the images pasted with it (shown as thumbnails), goes out with them when the turn ends or when its Send now hands it to the running turn, and the sent bubble shows them. An image never rides along with another queued message. PNG, JPEG, GIF and WebP. Sessions keep working when you look elsewhere: every chat you open stays mounted in the background (up to eight; idle ones make room, a working one never does). Opening another session or agent does not stop or lose the running turn, its row in the sidebar shows the moving mark while it works, and the answer is there when you come back. The microphone stays with the session it was started in (its orb waits at the bottom of the sidebar, see Voice). Jev agents (when a TypeSafe key is on this Mac): pick "Jev agent" in a new session's provider menu. It is not a chat, it follows Jev's own shape. Left, split in two: the state on top (text or JSON) and the questions below (JSON: noul yes/no, choice, score). Right: the output, each answer with its probabilities as bars, the confidence, the model, milliseconds and tokens; "Raw JSON" shows the reply as it came. Evaluate or Cmd+Enter. Edits save themselves, the last 20 runs stay one click away, and the agent lives in the sidebar (filled dot) with rename and delete. Jev stores nothing, so agents and runs are kept in data/state.json. A new agent starts with a working example. Trainer (optional): Jev cannot be talked to, so an agent can be coupled with Claude or Codex ("Trainer" next to Evaluate). A chat panel opens under the pad, with everything a chat has here: text, voice, models, effort, permissions. The trainer sees the state, the questions and the last output with every message, and changes the agent by answering with jev-state, jev-questions and jev-evaluate blocks, which the app applies and runs; it is shown the result (twice at most per request) so it can adjust. Its session stays out of the sidebar. The agent is a reusable classifier: the questions stay, the state changes. Secondary click on a session in the sidebar opens its menu: Rename, Copy session ID, Remove from sidebar (nothing is deleted: the session stays with its provider and comes back with +). Each session remembers its own composer: model, effort and permissions are kept per session (and per Jev agent's trainer) in data/state.json and come back with it, across restarts. A new session starts from the last choices made anywhere. Rename a session: that menu, the pencil next to its name in the title bar, or double-click the name there or in the sidebar. Enter saves, Escape cancels. The name is written where the provider ke