Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

hey-dave

A terminal coding-agent harness that runs D.A.V.E. (Digital Assistant for Various Endeavors) natively — not as a plugin inside someone else's harness. D.A.V.E. is an independent implementation derived from the legacy skill bundle in thesawdawg/agent-skills at commit ca9118c; see src/hey_dave/dave/NOTICE for attribution.

D.A.V.E. is a personal assistant persona — opinionated, occasionally witty, not a productivity product. The banner shown at startup says so on purpose: unstable, opinionated, and sometimes unhelpful. Not a productivity tool.

An animated, watch-only tour lives in docs/walkthrough/ (open index.html from a checkout in a browser). Nine goal paths, from setup to delegation, each with scripted terminal scenes and a strip showing what the harness does underneath.

Install

Python 3.14+ required. hey-dave is in alpha: releases are pre-releases, so installers need to be told to take one.

# uv tool (recommended)
uv tool install --prerelease=allow hey-dave

# or pip into any environment
pip install --pre hey-dave

# or from a checkout
uv tool install .

# Docker (includes SearXNG and Ollama profiles)
docker compose -f docker/compose.yaml run --rm dave --version

Upgrading from dave-harness. This project used to be called dave-harness. Remove the old tool (uv tool uninstall dave-harness) and install hey-dave; the command is still dave. On its first start, dave moves your folders to the new name: ~/.config/dave-harness, ~/.local/share/dave-harness (sessions and the vault) and ~/.local/state/dave-harness become …/hey-dave, and a project's .dave-harness/ becomes .hey-dave/ when you open it. Each move is printed. Folders not yet moved keep working, and DAVE_HARNESS_CONFIG is still read when HEY_DAVE_CONFIG is not set. Hook commands now see HEY_DAVE=1 instead of DAVE_HARNESS=1.

First run

dave init      # nine-step guided setup: models, permissions, tracker, vault
dave init --vibe   # a model, then D.A.V.E. from three plain-language answers
dave doctor    # environment + config health checks (--json for machines)
dave           # the TUI
dave --resume  # the TUI, starting with a list of sessions to pick up
dave run "summarise this repo"   # headless

The wizard guides you through setup. Each step opens with a progress rail (── ●●◉○○○ 3/6 vault ───) and a short card that says what the step is for and what to pick. Every answer says what it leads to, and typing ? at any question explains it in more detail.

The first step asks where D.A.V.E.'s model will run. It looks for a Codex sign-in (~/.codex/auth.json), and for Ollama and LM Studio running on this machine, then lists what it found next to Ollama on another computer and any OpenAI-compatible API (OpenAI, OpenRouter, vLLM…). Found servers show how many models they have, and Ollama models without tool support are marked. For an API key it asks for the name of the environment variable, not the key, and says whether that variable is set. Then you pick a permissions preset (cautious or developer), a tracker backend and a search backend, and the wizard initialises the D.A.V.E. vault. Every question is a numbered list: Enter takes the default, a number picks, and open questions end with "type your own…". Everything is shown as a diff and written only after confirmation.

dave init --vibe (or /init vibe in the TUI, once a profile works) sets up D.A.V.E. from three answers instead of the permission, tracker, search and persona steps. Its first four steps (models, profiles, vault, theme) need no model, and it will not move past the first until one is set up:

  1. What is this D.A.V.E.'s purpose?
  2. What is D.A.V.E. explicitly denied?
  3. What should I know about you? (optional)

The model may ask up to two short follow-ups per answer, then proposes settings. Examples:

  • "ask before any git commit" becomes the rule permissions.ask: bash(git commit*).
  • "keep everything in this directory" becomes permissions.confine: "project". File writes outside the project are denied, and shell commands that name outside paths ask.
  • Anything no rule can express is saved as an advisory restriction. The model is told about it, but nothing enforces it.

dave init --vibe also asks for a theme. The same theme colours the TUI and the interview itself, and telling the interview about contrast or colour needs can propose one. Both wizards are in colour: step headings, questions, option numbers and the default each have their own. NO_COLOR turns terminal colour off.

You tick what to keep. Suggested allow rules start unticked, and the harness refuses broad allow rules and any change to providers, secrets or existing deny rules. Nothing is written until you approve, and the interview is not saved.

Using the TUI

Key Does
Enter / Shift+Enter send / new line; during a turn Enter queues the message (up to 3, tui.limits.queue) and it is sent when the turn ends
Up (first line) / Down (last line) step through sent-message history; with messages queued and the input empty, Up pulls the last one back to edit and Esc drops it
/ open the command list — Up/Down/PageUp/PageDown move, Tab completes, Enter runs, Esc closes
@ open the file list — Tab or Enter completes the path (a folder opens its entries), Esc closes
! run a shell command yourself: ! git status shows its output in the chat; !! make test also sends the output with your next message
PageUp / PageDown scroll the transcript (the input box always keeps focus)
shift+tab toggle plan mode (between turns)
ctrl+p command palette
ctrl+b show or hide the sidebar; under 110 columns, open or close it as a drawer (Esc closes it too)
ctrl+t pending tracker proposals
ctrl+o the activity list: every tool call, injected command text and event in full (on a [pasted …] placeholder: expand it)
ctrl+c with text selected: copy it. Otherwise cancel the turn (queued messages pause: Enter on an empty input sends the next); while idle, clears a paused queue first, and twice quits
right-click with text selected: copy it; otherwise paste
ctrl+v / alt+v paste text / paste an image from the clipboard

The indicator beside the input shows what is running (thinking, the current tool, waiting for you, subagents) and for how long.

Shell commands. Start a message with ! to run it in your shell instead of sending it: ! echo "Hello" shows Hello and [exit 0 · 0.0s] in the chat. It runs in the session's folder with your environment, and D.A.V.E. never sees it. Use !! to share the output: !! pytest -x runs it and attaches the output to your next message ("why does this fail?"). Permission rules don't apply, since you typed it, and the model can never run a ! command. Each command starts fresh (cd does not carry over), ctrl+c stops it, and during a turn it waits in the queue. Over Telegram ! is refused.

Screen sizes. The TUI is fully usable from 80×24 and fits the space it has:

  • From 110 columns: the sidebar sits beside the chat: 32 columns, or about a quarter of a wide screen (up to 56).
  • Below 110 columns: the chat gets the whole width. ctrl+b opens the sidebar as a drawer over the chat, and Esc closes it. The header shows what the sidebar would, on one line: the D.A.V.E. focus and its minutes (amber or red when you drift), your tasks, running subagents. The activity indicator moves to one row under the input.
  • The input box is one line tall until you type more; it grows up to 8 lines (4 on a short screen).
  • The header and status bars drop their least important fields rather than being cut off; ctx % and the error count always show.
  • Every picker and card (/model, /theme, /resume, /music config, /vault config, permission and plan cards…) fits the screen, with its keys in view.
  • Permission cards show the change. An edit or write card shows a colored diff against the file on disk (+/− counts, line numbers), and says so when the edit would fail. Long diffs show their first lines; e expands the rest.

Make it yours. The top-level tui section of the config changes the layout and keys, and applies as soon as it is saved:

"tui": {
  "sidebar": { "sections": ["now", "tasks", "activity"], "position": "left", "shown": true },
  "header": ["name", "mode", "ctx", "usage", "profile"], // fields, in order
  "status": ["tokens", "music"],                    // the error count always shows
  "keys": { "sidebar": "ctrl+s", "permission.allow_once": "y" },
  "layout": { "sidebar_inline_from": 120, "composer_lines": 12 },
  "skin": { "placeholder": "Ask D.A.V.E.", "sections": { "now": "Today" } },
  "skins": { "matrix": { "rain": false } },
  "banner": false
}
  • sidebar: which sections show, in what order, on which side, and whether it starts open. D.A.V.E.'s six (dave, now, goals, today, proposals, subagents) scroll together as one panel.
  • header and status: which fields the bars show.
  • keys: any key in the list above, and every card's letters. /keys lists what can be changed. Esc, Enter, ctrl+c and the arrows stay fixed, and a key that would type into the input or clash on a screen is refused. The hints on each card show the key you chose.
  • layout: the sizes above: where the sidebar goes inline, how wide it is, how tall the input grows.
  • skin (every theme) and skins.<theme> (one theme): the words and glyphs a theme uses: the header name, sidebar headings, activity tags, spinner, placeholder, rain, and tune, the built-in music it plays.
  • themes: your own themes (below).
  • banner: false turns the startup banner off.

The chat holds only the conversation. Tool calls, the text a slash command injects for the model, and harness events (compaction, warnings, subagent reports) are listed under Activity at the bottom of the sidebar, in time order, each tagged with its type — TOOL, CTX (injected context) or EVT — in its own colour. Click a line, or press ctrl+o, to see it in full. There, c copies the entry to your clipboard, and s saves it to a file under ~/.local/state/hey-dave/exports/.

Copy and paste.

  • Copy. Drag over any text (the chat, Activity, the input box, an open card) to select it, then press ctrl+c or right-click. A toast says what was copied and how. With nothing selected, ctrl+c still cancels or quits. shift+drag makes your terminal's own selection instead, for its own ctrl+shift+c.
  • /copy copies without the mouse, word for word as it was said:
    • /copy: D.A.V.E.'s last reply, as markdown;
    • /copy 2: the reply before it;
    • /copy code: the last code block of the last reply, without the fences (/copy code 2 is the one before);
    • /copy all: the whole conversation.
  • How it reaches the clipboard. Copying uses clip.exe under WSL, pbcopy on macOS, wl-copy or xclip/xsel on Linux, and otherwise the terminal (OSC 52, which some terminals ignore). harness.clipboard: native or osc52 forces one way.
  • Paste with ctrl+v, or right-click with nothing selected. A long paste (over 20 lines or 2,000 characters, harness.paste) shows as [pasted #1 · 240 lines]:
    • the model still gets all of it, after your words;
    • Backspace deletes the placeholder whole;
    • ctrl+o on it puts the text back in the box.
  • Images. alt+v pastes the image on your clipboard (a screenshot, say) as [image #1]. It is sent as an image with your message and saved under ~/.local/state/hey-dave/pastes/. That file is deleted once no saved session uses it.

@ puts files and folders into the context. Type @src/app.py (or @"notes/big plan.md" for a path with spaces) anywhere in a message:

  • A file is sent to the model with its contents, after your words.
  • A folder is sent as a list of the files in it. In a git repository, files your .gitignore excludes are left out.
  • An image (.png, .jpg, .gif, .webp, up to 5 MB, 4 per message) is sent as an image, and so is one D.A.V.E. opens with read.
  • Typing @name lists matches from the folder you're in and from anywhere in the project, skipping what .gitignore excludes. You can still type the full path to an ignored file to attach it.

The chat keeps what you typed. Each attachment is a CTX » @path row in Activity. Some things are refused, with a warning in Activity:

  • a path your permissions.deny rules block;
  • other binary files, and .bmp images;
  • files over 4 MB.

A file longer than 100,000 characters is cut off, and the model is told where, so it can read the rest. dave run "explain @src/app.py" works the same way. It also works in the arguments of commands from files (D.A.V.E., skill and your own commands): /mycheck @src/app.py attaches the file after the command's text. A command can turn that off with mentions: false in its frontmatter. Built-in commands like /title take @ as plain text.

Slash commands:

  • /help lists them; /<command> --help (or /help <command>) shows a command's details, subcommands and examples without running it.

  • A command missing a required argument shows its usage instead of running.

  • /resume picks up a saved session. It lists this project's sessions, most recently active first. Each row has the title, when you last used it and how many messages it holds, plus the first line of your last message and of the last reply. Under the list, a preview shows the highlighted session's last few messages. Type to filter by title, text or id; Tab switches to every project; Enter resumes; Esc stays where you are. dave --resume starts the TUI with the same list, /resume <id> and dave --resume <id> go straight to one, and dave --continue takes the latest. /sessions prints the same details for the last ten.

  • /model opens a picker of profiles and every provider's models; /model <profile> [model], /model <model> and /model <provider>/<model> switch directly. The choice is kept with the session.

  • /rewind undoes D.A.V.E.'s file changes, turn by turn, even without git. Pick a turn to put the files it (and later turns) changed back as they were before it:

    • a also cuts the conversation back to that message, and puts the message back in the input box;
    • c cuts only the conversation.

    Files you changed since D.A.V.E. wrote them are shown as conflicts and need a second yes. Changes made by shell commands are not recorded, so turns where bash ran say so. A rewind can itself be rewound. Settings live under harness.checkpoints.

  • /init vibe configures D.A.V.E. from three answers (see First run).

  • /theme opens a theme picker that previews as you move; /theme <name> switches directly. Both save harness.theme. Each theme shows its WCAG contrast rating (AAA or AA). Every theme is tuned for readability: all text, including placeholders and muted text, reaches at least AA on every background it sits on, with muted text at 6:1 or better. The dark classics are adjusted the same way and keep their colours wherever they already passed. The themes come in five groups, plus your own:

    • D.A.V.E.: dave-dark (the default) and dave-light.

    • Accessible: dave-high-contrast and dave-high-contrast-light (AAA), and dave-colorblind and dave-colorblind-light (Okabe–Ito colour-blind-safe colours, AA).

    • Dark classics: Dracula, Nord, Gruvbox, Tokyo Night, Catppuccin Mocha, One Dark, Monokai, Solarized Dark and Rosé Pine.

    • Immersive: matrix, dnd and lotr. These restyle the whole TUI, not just its colours:

      • matrix is The Matrix: green phosphor on black, digital rain down the left edge, square frames, // OPERATOR-style headings, EXEC/LOAD/SYS activity tags and a katakana spinner.
      • dnd is Dungeons & Dragons: torchlit parchment and gold, double frames, headings such as "The Party" and "Adventure Log" in Fraktur, CAST/LORE/OMEN tags and a dice spinner.
      • lotr is The Lord of the Rings: gold on the green-black of a Shire night, round frames like hobbit doors, slow glowing runes down the left edge, headings such as "The Fellowship" and "The Red Book", DEED/TOME/SIGN tags, a rune spinner, and background music.
    • Avengers: iron-man, captain-america, thor and hulk. Immersive like the three above, and each plays its own music:

      • iron-man: the armour's cherry red and gold, frames with a lit title bar like a heads-up display, hex telemetry down the left edge, headings such as "J.A.R.V.I.S." and "Iron Legion", FIRE/SCAN/PING tags, and a hard-rock riff.
      • captain-america: the shield's navy, white and red, heavy-rimmed frames, star headings such as "The Mission" and "Field Log", OPS/INTL/COMM tags, and a brass march.
      • thor: storm blue, gold and red, heavy iron frames, lightning and runes down the left edge, headings such as "The All-Father", BOLT/RUNE/HORN tags, and a thunder saga with horns and a choir.
      • hulk: gamma green and purple, solid slab frames, headings that shout ("SMASH LIST", "RAGE LOG"), HIT/LOOK/ROAR tags, a rage-meter spinner, and a rampage that breaks into a lonely piano.

      A terminal app can't change your font. These themes use Unicode letterforms instead (Fraktur, half-width katakana, runes), which need a font that has them or your terminal's fallback. Windows Terminal and most Linux terminals have one. With TEXTUAL_ANIMATIONS=none the rain holds still.

    • Yours: themes you define under tui.themes in the config. Each starts from a built-in theme (base) and takes its frames, words, music and banner quotes, with any colours and skin parts you set on top:

      "tui": { "themes": {
        "ocean": {
          "base": "matrix",                    // unset: dave-dark
          "description": "deep sea blues",     // shown by /theme
          "colours": { "primary": "#4aa3ff", "background": "#001018", "muted": "#7fa0b8" },
          "skin": { "app_name": "~ dave ~", "tune": "thor" }
        },
        "nord": { "colours": { "accent": "#ffcc00" } }   // your version of Nord
      } }
      

      The colour roles are primary, secondary, accent, success, warning, error, foreground, background, surface, panel, muted and border, as #rrggbb. Your themes are made readable like the rest: a colour that falls under AA is lightened or darkened just enough, and /theme shows the rating. They are listed under Yours, and a theme named after a built-in replaces it. A change applies at once, even to the theme on screen; /dave can make it for you.

  • /music plays music while you work.

    • /music config is the easiest way to set it up. It opens a screen where you pick a source and fill in its settings. It checks what works as you type: the player, the folder's tracks, and the Spotify sign-in and open devices. Nothing plays while you set up. Enter saves, and ctrl+l signs in to Spotify.

    • Or use /music source … to pick what plays. The choice is saved:

    • tune (the default): the theme's own music, for lotr and the four Avengers. It starts with the theme, changes when you switch to another of them, and stops when you switch to a theme without music. Each piece is original (none is a film's score), synthesised once and cached: a folk air for lotr, a rock riff, a march, a thunder saga and a rampage for the Avengers. Set harness.music.file to play your own audio file instead, in any of those themes.

    • local <folder>: your own .mp3 (or .flac, .ogg, .m4a, …) files, in any theme, as a playlist. For example, /music source local ~/Music/focus.

    • spotify: your Spotify account. The harness controls Spotify wherever it's open (the desktop app, your phone, a speaker), and the music plays there. You need Premium and your own client ID:

      1. Create an app at https://developer.spotify.com/dashboard. Tick Web API, and add the redirect URI http://127.0.0.1:8898/callback.
      2. Run /music login <client-id> and sign in in the browser.
      3. If the page after sign-in doesn't load, paste its address: /music login <address>.

      The tokens stay in the state directory (0600), and /music logout deletes them. /music play <link> plays a Spotify playlist, album or track link.

    • youtube (saved as media): the system media keys. They control YouTube in a browser or the YouTube Music app, or any other player that has the media session: Windows' under WSL, or playerctl on Linux. You start what plays in that player; the harness only plays, pauses and skips.

  • Controls:

    • /music says what's playing, where, and at what volume.
    • on, off, play, pause, resume, next, prev, volume <0-100>, shuffle on|off and repeat off|all|one work where the source allows.
    • F7/F8/F9 are previous, play/pause and next.
    • The status bar shows the current track, for example ♪ 02 Second (2/12).
  • The harness's own player (for tune and local) is the first one it finds: ffplay (from ffmpeg), mpv, pw-play, paplay, afplay or aplay.

    • Choose one with harness.music.player, and set the volume with /music volume (harness.music.volume, default 40).
    • On WSL, ffplay works through WSLg's sound server.
    • It never starts loud. Before starting on its own, it reads your system volume (Windows' volume on WSL, PipeWire, PulseAudio or ALSA on Linux, or macOS's). If system × music volume would be over 25%, it holds and asks: lower the volume, or /music on to play anyway. That confirms it for the rest of the session.
  • DAVE_NO_AUDIO=1 keeps the harness silent: no player, no media keys, no Spotify calls. harness.music.player: "none" silences the harness's player only.

  • /copy copies a reply, a code block or the conversation (see Copy and paste above).

  • /errors lists what went wrong this session (see When something goes wrong below).

  • /status summarises the session: profile, context (and images in it), focus, subagents, waiting reports and proposals, and remote state.

  • /remote connects the open session to a chat app (see Remote).

  • /vault shows vault sync; /vault config sets it up; /vault sync, /vault login [client_secret….json|client-id], /vault logout [--forget-client] (see Sync across devices below).

  • D.A.V.E. commands: /brief, /focus, /item, /sidequest, /park, /check, /delegate, /mission, /project, /promise, /intake, /review, /standup, /supervise.

dave run "/<command> --help" prints the same help headless.

Permission cards

When D.A.V.E. needs your yes, the card shows the call and how long an answer can last:

Key Does
a allow this call
s / p / g allow this session / for this project / everywhere: every project, every open and future session
d deny: d again denies this call; s / p / g deny for that long; Esc goes back
n deny and tell the model why
b widen what s/p/g save: the exact call, its prefix (bash(git status*), a folder, mcp(redmine:*)), or the whole tool (bash(*)). The card shows the rule
Esc deny this call

Project answers go to .hey-dave/config.json, everywhere answers to ~/.config/hey-dave/config.json, as permissions.allow or permissions.deny. A file path saved everywhere is absolute. A project's rules add to the global ones and never hide them. Every open session picks up a saved rule on its next call. /permissions lists the rules by scope and /permissions remove <rule> takes one out; /permissions reset-session drops this session's. Over /remote answers are for this call only.

Plan mode

Plan first, then change things. /plan add a --json flag to dave doctor (or shift+tab, then type) puts the session in plan mode:

  • D.A.V.E. can read, search and run look-only commands: git status, git log, git diff, git show, ls, cat, head, tail, wc, rg, grep and find. Change the list with permissions.plan_bash. Every other command, every write and edit, and redirection are denied, whatever your allow rules say. So are find -delete/-exec and D.A.V.E. state writes. A listed command still needs your usual allow rule or a yes on its card.
  • The header says mode: plan and the input box takes the theme's warning colour.
  • It ends with a plan card:
    • a approves: plan mode ends and D.A.V.E. implements the plan in a new turn, so /rewind can undo it as one step;
    • m also saves the numbered steps as the open mission's plan, ready for /supervise. It asks first;
    • k keeps planning, with your feedback;
    • d or Esc discards the plan and stays in plan mode.

/plan or shift+tab toggles plan mode, and /plan on|off sets it. The mode is saved with the session. Over /remote the card has Approve and Discard buttons, and any message is feedback. dave run --plan "<task>" prints the plan and stops (--json emits plan_proposed).

Unfocused sessions

General work, with your projects held back. /unfocused is for work that belongs to no task: a quick script, a question, tidying notes.

  • D.A.V.E. works normally, but files inside your registered projects are read-only. Writes and edits there are refused. Shell commands that run in a project or name a path in one ask first, unless they only look (the permissions.plan_bash list). Subagents inherit the hold.
  • Your current focus is paused under a general detour, so the time shows in the ledger and the weekly review as general time, not drift. After the drift threshold D.A.V.E. mentions it once per period, then stays quiet.
  • Setting a focus ends unfocused mode and lifts the hold: picking a task is how you get back into a project. /unfocused off ends it and returns to the paused focus.
  • Each session's unfocused periods are recorded in the vault under sessions/general/<device>/<yyyy-mm>/<session-id>.md: when, why each ended, the files changed and how many calls were held back. Transcripts stay in the normal session store.
  • The header says mode: unfocused. The mode is saved with the session, and a resume starts a new period. With no focus at session start, D.A.V.E. offers it once. It needs a vault.

Task list

On work of three or more steps D.A.V.E. keeps a short checklist with todo_write. It shows in the sidebar's Tasks section, between the D.A.V.E. panel and Activity: ☐ to do, ◐ in progress, ☑ done, and a count in the heading (Tasks 2/5). The list belongs to this session only. It is saved with the session, and it is not your Now list or a mission's plan. It hides while empty, works in plan mode, and is switched off under /supervise, where the mission's steps are the plan.

Notifications

When you're in another window, the terminal tells you that D.A.V.E.:

  • finished a turn that took 15 s or more (harness.notify_after);
  • is waiting on a permission card or a plan card;
  • has a subagent report ready.

Nothing fires while the dave window has focus, unless you set harness.notify_when_focused: true.

  • harness.notify: "bell" (the default) rings the terminal bell. Windows Terminal flashes the tab or plays its bell sound, depending on your profile's bell setting.

  • "desktop" shows an OS notification with a short text such as turn finished (42s). It never includes tool output. dave doctor shows the route it picked for your terminal:

    Terminal Route
    Windows Terminal under WSL a Windows toast (via powershell.exe)
    Windows Terminal with compatibility.allowOSC777 on set harness.notify_via: "osc777"
    iTerm2, Apple Terminal, ConEmu OSC 9
    WezTerm, Ghostty, Warp, GNOME Terminal and other VTE terminals OSC 777
    kitty, foot OSC 99
    anything else the bell

    Inside tmux, add set -g allow-passthrough on.

  • "off" turns notifications off. /remote has its own list (remote.notify).

When something goes wrong

A failure shows as a card in the chat, never as a silent freeze or a crash:

✗ Internal error while showing a tool result — KeyError: 'path'
  kept: the turn is still running; nothing lost
  next: /errors copy 1 to report it
  • The status bar counts errors, and /errors lists them. /errors 1 shows the newest in full, and /errors copy 1 copies a bug report with secrets removed.
  • The full traceback is in the log. /errors log prints where the log, crash reports and stall logs are.
  • Nothing typed is lost. A reply that was streaming when the app closed comes back on resume, marked as interrupted. Your unsent message and queued messages come back too, with the queue paused.
  • If the app ever does close on an error, it says so in the terminal. It prints the command to resume your session (dave --resume <id>) and the path of a crash report with secrets removed. The next start also notes that the last run did not close cleanly.
  • When quitting seems stuck, press ctrl+c again. When the whole app seems stuck, run dave debug stacks in another terminal. It prints what every part of the app is doing, even while it is frozen, and doesn't stop it.
  • dave doctor also checks where sessions are saved: whether the folder is writable, whether there is disk space, and whether it is on a slow filesystem (WSL's /mnt/c, network drives). It also lists runs that did not close cleanly and recent crash reports.
  • A subagent that has been silent for 5 minutes shows idle on its sidebar card. If the disk fills up, the session is kept in memory and saved once there is room again.
  • A frozen screen is noticed. If the app stops responding for 2 s, what it was doing is written to a file. After 30 s, the bell rings once and the terminal title says not responding. When it recovers, a card says how long it was frozen and where. When the model is silent, the indicator says waiting for <model> · 35s, then no response for 60s · ctrl+c cancels.
  • ctrl+c always stops a turn, even when the model has gone silent. After about 1.5 s the turn is cut off, and the text so far is kept.
  • When a provider fails and the request is retried, the indicator says so (retrying 2/3 after network error). Set harness.turn_stall_seconds to stop a model that has gone silent and retry once.

Remote

/remote connects the open TUI session to Telegram, so you can follow it, approve single actions and talk to D.A.V.E. from your phone. Nothing polls or leaves the machine until you configure a channel and run /remote on. The bridge stops when the TUI closes.

Setup:

  1. In Telegram, message @BotFather, send /newbot, and copy the token.
  2. Export it: export DAVE_TELEGRAM_TOKEN=<token> (the config only names the variable, remote.telegram.bot_token_env).
  3. dave config set remote.channel telegram, then start dave.
  4. In the TUI, /remote pair shows a 6-digit code. Send /pair <code> to your bot in a private chat within 5 minutes. Your Telegram user ID is stored in remote.telegram.allowed_user_id.
  5. /remote test sends a test message. dave doctor checks the token and the pairing.

In the chat:

  • Plain text is a prompt, and /command args runs a slash command. During a turn the phone is told busy: the terminal's message queue is terminal only.
  • /cancel stops the running turn. /help lists the commands allowed remotely.
  • Permission cards arrive with Allow once / Deny / Deny + reason. Whichever of the terminal or the chat answers first wins.

Remote can never grant a session or project rule, or Allow writes on a delegate card. /quit, /resume, /export and a few other commands reply "terminal only". Only the paired user in a private chat is heard; everything else is dropped silently.

Telegram bot chats are not end-to-end encrypted. With remote.share: "summary" (the default), only these are sent:

  • replies;
  • the commands you are asked to approve;
  • paths, diffstats and tool outcomes.

"full" also sends tool output and diffs as files. remote.notify picks the notifications: turn_finished, permission, report_ready, error and drift. remote.autostart: true starts the bridge with the TUI. Details are in docs/spec/10-remote-channels.md.

Configuration

One JSON file: ~/.config/hey-dave/config.json (project overrides in .hey-dave/config.json; ${ENV_VAR} references for secrets — never literal keys). Manage it with dave config get|set|validate. Details: docs/spec/01-config.md.

{
  "providers": { "codex": { "type": "codex" } },
  "profiles":  { "main":  { "provider": "codex", "model": "gpt-5-codex" } },
  "harness":   { "default_profile": "main" },
  "roles":     { "scout": "main" },          // roster role → profile routing
  "permissions": { "allow": ["bash(git status*)"] },
  "mcpServers": { "redmine": { "command": "…" } },
  "tracker":   { "backend": "local" }
}

Provider kinds: codex (Codex CLI auth), openai_compat, ollama, scripted (tests). Every kind lists its models from the server: codex asks the ChatGPT backend for your account's models, as the Codex CLI does.

Limits and timeouts

The sizes, counts and waits the harness used to fix are settings, each next to the feature it tunes, with the old value as the default:

  • harness.tools: the longest bash timeout, the read size cap, glob and search caps, the task list's length, a hook's timeout;
  • harness.shell (! and !!), harness.mentions (@), harness.context (compaction, AGENTS.md), harness.housekeeping (backups, crash reports, dave doctor's disk and crash checks);
  • tui.limits: the queue, the activity log, the busy line's "waiting", the notification gap, the sidebar refresh, /resume's preview;
  • remote.limits: messages a minute, the pairing window and attempts;
  • per provider max_attempts (and Ollama's capability_ttl_hours, Codex's refresh_early_seconds), plugins.web_fetch.max_redirects.

Each has a hard range, so no value switches a guard off, and raising a remote rate or pairing limit through /dave is flagged as widening access. /dave reads them all (harness_guide limits lists every one).

Set up from the chat: /dave

/dave <request> asks D.A.V.E. to work on the harness itself, without leaving the TUI. For example:

  • /dave help me add my Ollama box as a provider;
  • /dave route the critic role to the local model;
  • /dave why is web search failing?

It enters harness mode (mode: harness in the header), and your replies continue it until /dave off.

  • What it can do. In this mode D.A.V.E. can only read the config (with secrets hidden), read built-in setup guides, run read-only checks, and propose changes. It has no file, shell or web tools.
  • What it can see. Every section of the config and what each holds, whether set or not, with each key's type; every key id you can remap; each helper role as packaged; every command you can turn off. It can also read D.A.V.E.'s own settings in the vault (your name, hours, review day, which helpers are on…) and which of D.A.V.E.'s texts are your versions, but it can't change those here: it tells you how (/dave off and ask, /init vibe, or dave prompts copy).
  • The card. Every change comes as a card: tick the items you want and press Enter; f sends feedback and Esc rejects.
    • Anything that widens access starts unticked and needs you to type allow: an allow rule, a removed deny rule, turning confine off.
    • New MCP servers and skill folders (which run commands) and new provider hosts are flagged with a ⚠.
  • API keys are never written into the config. D.A.V.E. uses an api_key_env name, and the card lets you paste the key for this session only; export it in your shell profile to keep it.
  • After you apply, the files are backed up to ~/.local/state/hey-dave/config-backups/, and the change reloads at once. Plugins, MCP servers and skills load at the next start. Checks then run (the new provider's model list, for instance), and D.A.V.E. fixes what failed.
  • /dave undo puts the files back. See docs/spec/02-providers.md.

Images go to every provider. Ollama is asked whether the model has vision; for the others, set "images": false on a profile whose model can't take them. The model then gets a short note instead of the image. Sessions keep the image's path and hash, not its bytes, so an image moved or edited later is sent as a note, not a different picture.

External agents (Claude, Devin)

D.A.V.E. can hand a charge to an external coding agent over ACP — Claude Code through the claude-agent-acp adapter, or Devin through devin acp — instead of a harness subagent. They are workers, not model connections: they never answer D.A.V.E.'s own turns, and they run with your own sign-in. There is no credential field; the harness never logs in for them, and the agent process gets your environment unchanged (a key the harness resolved for a profile is not forwarded — if your shell exports ANTHROPIC_API_KEY, Claude's worker bills that key, not your plan; the card and dave doctor say which).

Setup:

  • Claude Code: claude installed and signed in, then the adapter — dave plugins install @agentclientprotocol/claude-agent-acp --latest-stable (into the harness's npm prefix; needs Node ≥ 22).
  • Devin: the devin CLI installed and signed in (Pro, Max or Teams); devin acp is Cognition's own ACP entry point. agents.providers.devin.cloud: true runs devin acp --cloud.
  • Check: dave doctor gives each enabled provider an agent:<id> line — the binary (PATH, then the npm prefix), version, Node floor, an initialize handshake and the sign-in method. It never opens a session, signs in, or installs anything. Bare /agent in the TUI lists the same, plus the external workers running.

Use:

  • /agent claude fix the login redirect dispatches a charge (the implementer role, or --role critic). The dispatch card always comes first — provider, role, model, local or cloud, the billing method, and a warning when the provider can act outside the harness — because it spends your agent's own quota; no allow rule skips it. One charge per provider at a time (agents.providers.<id>.max_concurrent); more queue.
  • D.A.V.E. can dispatch too: delegate(agent="claude") for one charge, or agents.routes to route a role ("agents": {"routes": {"implementer": "claude"}}). agent="local" forces the harness worker. quartermaster and scribe always stay local — their work is the vault's own tools.
  • Reports come back like any subagent's and list the files changed. /agent stop [id] cancels a running worker; delegate(resume="<id>") sends a follow-up charge (a live worker is reused; after a restart the stored ACP session is reloaded — nothing respawns on its own).
  • Headless: dave run "/agent claude <charge>". --approve agent pre-approves only the dispatch card — the agent's own file and terminal asks still deny, so unattended runs stay read-only unless you also pass --approve allow.

What the harness enforces for an external charge:

  • Its file writes, reads and terminals go through your permission rules and cards. "Allow always" stays a harness grant — the agent keeps asking. ACP-mediated writes are checkpointed, so /rewind undoes them. git push, the vault, .hey-dave/ and the agents' credential stores (~/.claude/.credentials.json, ~/.config/devin/) are hard-denied, reads included.
  • Only safe modes are ever selected (default for Claude, ask for Devin; plan for read-only roles) — bypass modes are refused, and an agent-initiated switch to one is put back.
  • Claude is a bypass provider: its SDK edits files and runs commands itself, so only its permission requests reach the harness — and allow rules in your own ~/.claude/settings.json can approve its actions without a card. The dispatch card warns, and the report lists what changed outside the harness's sight (a git diff), marked as not covered by /rewind. plan mode is the stricter lever.

Policy links (checked 2026-09-26 — they change, so dave doctor prints the date too):

Other ACP agents can be added by config alone (agents.providers.<id>.command). Details: spec 03 External agents.

Extending

  • Skills — markdown SKILL.md under skills.roots (default ~/.config/hey-dave/skills, ~/.agents/skills, ./.hey-dave/skills); slash commands and hooks too. dave skills list. Command files can declare usage, help and examples frontmatter so --help and argument checks work for them. docs/spec/04.
  • Bundled skill: archify — ask D.A.V.E. for an architecture, workflow, sequence, dataflow or state diagram and it writes a validated, interactive HTML page (pan, zoom, search, light and dark, exports). It ships with the harness and needs Node.js 18+ on PATH; without it the skill is hidden and dave doctor says so. Your own archify in a skills root replaces it; skills.disabled: ["archify"] turns it off. MIT, from tt-a1i/archify, pinned to the harness release with no update check.
  • Plugins — Python entry points (hey_dave.plugins) or built-ins; dave plugins list. docs/spec/05-plugins.md.
  • MCP — paste a Claude-Desktop mcpServers block unchanged; tools appear as mcp__<server>__<tool>. docs/spec/05.
  • Trackers — local (file-backed) or redmine_mcp (Redmine over MCP); outward writes are always proposed and approved individually (/tracker panel). dave tracker list. docs/spec/05.
  • Web search — searxng or brave backends; dave search "query".
  • Web pages — web_fetch reads a page as markdown: the main content, with headings, code and links, minus menus, footers and scripts.
    • Each new host asks first (GET <url> on the card). Allow a host for good with network(web_fetch:docs.python.org), or every host with network(web_fetch:*).
    • Internal addresses (localhost, 10.x, 192.168.x, cloud metadata) are refused, even after a redirect. Set plugins.web_fetch.allow_private: true for intranet docs.
    • Page text is marked untrusted, and D.A.V.E. is told never to follow instructions in it.
    • Only HTML, text, markdown, CSV and JSON are read; PDFs and images are refused. Limits are under plugins.web_fetch (20 s, 2 MB, 20,000 characters). It works in plan mode. Turn it off with plugins.disabled: ["web_fetch"].

Using your Claude Code plugins

hey-dave can read Claude Code plugin directories — nothing runs until you enable one, and foreign code never runs inside the harness's own process.

  • dave plugins list shows what was detected: plugins.sources entries always, plus ~/.claude/plugins/ marketplace installs and this project's own .claude-plugin/ (set plugins.detect: false to scan only named entries). Each source starts as available.
  • dave plugins enable claude:<name> enables it (disable takes it back), or add {"format": "claude", "path": "…", "enabled": true} to plugins.sources yourself. /plugins shows the same list in the TUI.
  • What then works: the plugin's skills, slash commands (/<plugin>:<name>), hooks (SessionStart, PreToolUse, PostToolUse, Stop) and MCP servers (mcp__<plugin>-<server>__<tool>). Anything the format has that the harness cannot run — unknown hook events, LSP servers, output styles, themes — is listed as not run/unsupported, never an error.
  • Hooks can only tighten: deny refuses the call, ask forces the approval card, and allow is ignored — the permission gate decides.
  • Agents become delegate roles named <plugin>:<name> that start read-only — no shell, no writes — until you bound them with dave.roster.<plugin>:<name>.
  • dave plugins install <pkg>@<ver> (or --latest-stable, which takes the newest release at least 7 days old) installs an MCP server's npm package into the harness's own prefix, with install scripts off by default — nothing is ever installed automatically.

Using your Pi extensions

Pi extensions are TypeScript — hey-dave hosts them in a small vendored Node shim, one process per extension, so nothing foreign runs inside the harness itself. Needs Node 22+ on PATH (or installed into the npm prefix).

  • dave plugins list finds ~/.pi/agent/extensions/, this project's .pi/extensions/, ~/.pi/agent/skills/, .pi/skills/, and mcpServers in ~/.pi/agent/settings.json. Without Node 22+ an extension shows unavailable: needs node ≥22.
  • dave plugins enable pi:<name> loads the extension once to check it — that's the moment its code first runs — and refuses extensions that register no tool.
  • What then works: the extension's tools, arriving as mcp__pi_<ext>__<tool> under the normal MCP permission kind (plan mode blocks them); skills roots and settings.json MCP servers import as pi:skills/pi:project-skills/pi:mcp.
  • What is listed but never run: everything else the extension registered — events (on:<event>, including tool_call), commands, shortcuts, flags, providers, renderers, messages, session entries and ctx.ui. The shim records them as unsupported rather than emulating them.
  • A missing dependency shows cannot find module '<x>'; when the extension ships a package.json, dave plugins install --from pi:<name> runs npm install in its directory (scripts off by default).

D.A.V.E. state

The vault lives at $DAVE_VAULT, else dave.vault_path, else ~/.local/share/hey-dave/vault/ — an append-only JSONL journal plus derived views, written only by hey_dave.dave.state. Inspect it without starting a session:

dave brief       dave today       dave focus show
dave mission list   dave review   dave vault doctor

Goals and tasks are durable vault items. `/focus new annual|deadline|daily

<title>` creates and focuses one. `/sidequest <title>` captures an unplanned Daily Task for today without changing focus or Now; it accepts `--priority` and `--due`. Use `/item` to list, add, inspect, edit, link, complete, reopen, reschedule, backlog or skip items, `/item now ` to propose one for Now, and `/item plan` to see goals Now does not serve yet. The CLI equivalents are `dave focus new`, `dave sidequest`, and `dave item`. Annual Goals have a calendar year, Deadline Goals have a due date, and Daily Tasks have a work day.

Now stays the one commitment list. A task is on plan when it or a goal it supports is in the ranked list; anything else, a Daily Task included, counts as drift. Priority expresses importance and never promotes anything: an Urgent item outside Now makes D.A.V.E. ask whether it belongs there, through the usual diff. The sidebar's Goals section shows each open Annual Goal's progress and how long since it last moved; Today carries unfinished tasks forward, marks which goal each serves or that it was a sidequest, and holds priorities.max_today before D.A.V.E. asks what to move. The weekly review sets planned work against sidequests, and on review.plan_day D.A.V.E. offers to pick the week's goals for Now. Dates never complete items automatically, and classifying an old focus ref is offered once, never required.

D.A.V.E.'s own settings live in the vault's config.json and are set with dave config set dave.vault.<key> <value> (or /init vibe, or by asking D.A.V.E.). Each one takes effect:

  • user.timezone (an IANA name such as Europe/Berlin) decides what "today" is for the journal, promises, the brief and the review; empty uses the machine's zone. user.work_hours (09:00-17:00) makes the brief say when you are working outside them.
  • priorities.max_now caps Now: D.A.V.E. cannot grow it past the cap, and the sidebar shows that many. priorities.ranking_factors is what it ranks by, in order.
  • review.day is the weekday the brief calls the weekly review due.
  • priorities.max_today caps Today (5): a task over it is still saved, and D.A.V.E. asks what to reschedule. review.plan_day is the weekday it asks which goals this week's Now serves; review.deadline_horizon_days is how far ahead Deadline Goals show in the brief.
  • kanban.boards names your intake boards; once the last intake is kanban.stale_after_days old, the brief says the boards may be stale.

Make D.A.V.E. your own

Its words. Every prompt D.A.V.E. runs on (dave.md, persona.md, first-run.md, supervisor.md, the six references), the ten role contracts and its slash commands can be replaced or added to from the vault, so your versions sync with it:

dave prompts list                    # every file, and whether yours replaces or adds to it
dave prompts copy persona            # puts persona.md in <vault>/harness/prompts/ to edit
dave prompts copy roles/critic --append   # starts roles/critic.append.md: added after the packaged text
dave prompts show roles/critic       # the text in use

<name>.md replaces the packaged file; <name>.append.md is added after it, so upgrades to the packaged text still reach you. A new file in harness/commands/ adds a command. D.A.V.E. can't edit these files: the vault is off limits to its write tools.

Its helpers' limits. Each role's tools, shell commands, write paths and dave_* actions can be replaced in the config:

"dave": { "roster": {
  "critic": { "bash_allow": ["git diff*", "uv run pytest*"] },
  "scout":  { "tools": ["read", "glob", "grep"] }
} }

A role never gets delegate, or writes to priorities, promises, focus or the config, whatever this says. When D.A.V.E. proposes one of these changes, it shows as widens access, unticked.

Its commands. "commands": {"disabled": ["standup", "music"]} turns slash commands off: they disappear from the list and /help, and typing one says it is off. /help, /quit, /keys, /dave, /errors and /init always stay.

Sync across devices (Google Drive)

One D.A.V.E. on every machine: the vault stays a local folder that works offline, and the harness mirrors it to a folder in your Google Drive. Each device adds only to its own journal, so what you log on the laptop shows up on the desktop. A file edited on two devices keeps both versions: Drive's in place, yours beside it as <name>.conflict-<device>-<time>.md, and D.A.V.E. mentions it until you merge it.

Setup, once per Google account, then /vault login on each device:

  1. In Google Cloud, enable the Google Drive API. Set up the OAuth consent screen (External, add yourself) and publish it: while it says Testing, Google signs you out every 7 days.
  2. Create an OAuth client ID of type Desktop app, then Download JSON (client_secret_….json).
  3. In the TUI, /vault login ~/Downloads/client_secret_….json: it keeps the client on this device, turns sync on and opens the browser. Or /vault config: a setup screen with these steps (ctrl+o opens the console), a field for the file, and checks that change nothing; ctrl+l signs in with the screen open and shows how it goes, with a box for the redirect address if its page doesn't load. From a shell: dave vault login --client-file ~/Downloads/client_secret_….json.
  4. /vault sync (it also syncs on its own from then on).

The client ID and secret stay on the device (the state directory, 0600, never the config), so signing in again later, after a logout or when the sign-in expires, is just /vault login: nothing to retype. /vault and the setup screen say when a sign-in expired, or when Google says it will; /vault logout --forget-client also deletes the saved client.

Use the same client on every device. The harness asks only for drive.file: it sees the files it created, nothing else in your Drive. Tokens are kept in the state directory (0600), never in the config or the vault. In the TUI it syncs at start, shortly after D.A.V.E. writes, every 5 minutes and at exit (dave.sync.auto, interval_minutes, push_delay_seconds). /vault shows where it stands, and dave vault sync --dry-run shows what would move. Two machines with the same hostname need DAVE_DEVICE set on one of them. Deletions don't sync: remove a file on every device. See docs/spec/06-dave-state.md Sync.

Docker

docker compose -f docker/compose.yaml run --rm dave            # TUI
docker compose -f docker/compose.yaml --profile search up -d   # SearXNG
docker compose -f docker/compose.yaml --profile ollama up -d   # Ollama

Volumes: ~/.config/hey-dave → /config (create it first — Docker makes missing bind sources root-owned), named dave-data → /data (the vault lives here), dave-state → /state, the directory you run compose from (or $PROJECT_DIR) → /work, and ~/.codex read-write (Codex token refresh writes back to the mounted file).

SearXNG needs a secret in the environment and refuses to start without one:

export SEARXNG_SECRET=$(python3 -c "import secrets; print(secrets.token_hex(32))")
docker compose -f docker/compose.yaml --profile search up -d
dave config set plugins.web_search.searxng.base_url http://searxng:8080   # inside compose

From the dave container SearXNG is http://searxng:8080 — localhost there is the harness container itself. A host Ollama is reachable as http://host.docker.internal:11434; dave discover inside Docker only scans the compose network. Non-interactive provisioning: dave init --non-interactive --from config.json. See docs/spec/08-packaging-deploy.md.

Development

bash scripts/validate.sh  # frozen sync, quality gate, clean installed-wheel smoke

Releases publish themselves: bump the version in pyproject.toml and src/hey_dave/__init__.py, commit, then push a tag such as v0.1.0a2. GitHub Actions runs the full gate, checks the tag matches the version, and publishes to PyPI through trusted publishing. See spec 08 for the one-time PyPI and GitHub setup.

Tests use temporary personal-state paths and block external sockets by default. MCP and Spotify callback integrations are explicitly marked for loopback TCP. See the dependency policy for update boundaries and CI validation for the shared gate.

Normative specs and the staged plan live under docs/ — see docs/README.md. Interactive diagrams of the design (the system map, a turn, the permission gate, delegation, persistence and the stage roadmap) are in docs/diagrams/; bash scripts/diagrams.sh re-renders them from their JSON sources.

To try features by hand, user-test-docs/setup.sh builds a throwaway demo project in ~/dave-demo (a small calculator with a planted bug, test images, and files that exercise @ limits). user-test-docs/CHECKLIST.md lists the manual checks, each with what you should see.

Resuming a session from another project asks before loading that project's settings, skills and plugins. Headless resume requires starting in the saved project. Changes to dave.vault_path take effect after restarting D.A.V.E.

--continue resumes this project's most recently active saved session. Session listing reflects renamed titles and profile/model changes. An interrupted final session write can be recovered; its damaged bytes are preserved in a private .corrupt sidecar before the next write. Corruption in the middle produces a file-and-line error while other sessions remain discoverable.

Streaming replies use batched Markdown rendering. Recursive searches skip what your .gitignore ignores (or, without ripgrep and git, folders like node_modules and .venv). They are cancellable and bounded: at most 10,000 files are searched, output stops at 1 MiB, and a content search has 60 seconds. A search that reaches a limit still returns what it found, with a note to narrow the path or pattern. Deny rules hold for every file a search opens. Allowing a search once covers the files it finds; files an ask rule covers share one extra prompt. The session picker searches the newest 50 sessions per scope and reuses unchanged summaries between openings.

License

hey-dave is free for noncommercial use under the PolyForm Noncommercial License 1.0.0: personal projects, study, research, hobby use, and use by charities, schools, public research and government bodies. Using it for a commercial purpose, including work for a company, needs a separate license from the author. The bundled Archify skill and the Pi shim's inlined dependencies keep their own licenses (MIT, ISC, BSD-3-Clause, OFL-1.1); their texts ship with the package.

Release files for hey-dave 0.1.0a2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hey-dave 0.1.0a2
File Size Uploaded
hey_dave-0.1.0a2.tar.gz 1.9 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for hey-dave 0.1.0a2
File Interpreter ABI Platform
hey_dave-0.1.0a2-py3-none-any.whl Python 3 none any Details

Total release size: 3.9 MB

Release files / hey_dave-0.1.0a2.tar.gz

Download URL hey_dave-0.1.0a2.tar.gz
Size 1.9 MB
Tags Source
SHA-256 checksum
How to use checksums
ddd8cd4fb6ba87334ff5ff413d74b9b24fa2edf307fe18430654d2c5bb07fd92
BLAKE2b-256 checksum
How to use checksums
22c4c6e0560658e23a13da33f5152d2026b0dc451f403b6c117ba436e6b5406e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / hey_dave-0.1.0a2-py3-none-any.whl

Download URL hey_dave-0.1.0a2-py3-none-any.whl
Size 2.0 MB
Tags Python 3
SHA-256 checksum
How to use checksums
03118bbc5ed0bfd6359c9c7989b1c43c0563a8155ebc1506b1867802cc82ed06
BLAKE2b-256 checksum
How to use checksums
dd403d1a9c9b97fbe315c85af29f2a2cff77ebadcdbb551fb7062fe188887fc1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0a2 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page