@cobdfamily/dogcow
v1.0.202606100241
Published
Command-line REPL wrapper around an agentic-coding CLI with an interactive permission gate. Named after Clarus the DogCow. Moof!
Readme
dogcow
Command-line front-end named after Clarus the DogCow. It wraps an agentic-coding CLI in a small REPL and adds an interactive permission gate so tool use can be approved from the console. Claude Code is the default backend; Gemini and Codex ship too, and any other CLI can be added as a drop-in file (see Backends).
Files
dogcow.js— main entrypoint and permission daemondogcow-permissionshook.js— PreToolUse hook client (relays to the daemon)permissions.js— rule parsing, matching, and prompt renderingbackends.js— backend discovery, normalized-event contract, config storebackends/*.js— one descriptor per backend (claude,gemini,codex)askquestion.js— AskUserQuestion picker answer-mapping (see deferred.md)sessions.js— persistent per-cwd Claude session id + transcript listing (see Sessions)silliness.js/clarus_phrases.json— random status phrasesmoof.js— optional Moof sound effect
Run
node dogcow.jsType a request (capitalized text, or prefixed with @dogcow / /ask)
to send it to the active backend; type a shell command to run it directly.
Backends
A backend is a CLI dogcow drives. Each is a self-contained ES module in
backends/ that default-exports a descriptor (spawn args + a parseEvent
mapping the CLI's stream to dogcow's normalized events). Discovery loads every
*.js from the repo backends/ dir and from
$XDG_CONFIG_HOME/dogcow/backends/ (defaulting to ~/.config/dogcow/backends/),
where a same-named file overrides a built-in. So:
- install only what you need — a backend is available iff its file is
present; delete
backends/gemini.jsand gemini is gone. - add a backend — drop one
*.jsin your user backends dir. No core edit, no publish step. Copy an existing descriptor as a template.
Shipped: claude (default), gemini, codex. When more than one backend is
installed the active one prefixes the prompt (claude] ); with a single backend
the prompt is just ] (which one is replying is obvious).
/backend— list backends and mark the active one./backend <name>— switch backend (persisted; effective next turn).
Permission gate vs. native approval
The numbered permission gate (below) is built on Claude Code's external
PreToolUse hook, which dials back to dogcow over a socket. Only Claude exposes
that contract, so descriptors declare a gate:
gate: "hook"(claude) — the full numbered gate.gate: "native"(gemini, codex) — dogcow does not gate; the CLI's own approval mode owns allow/deny (gemini --approval-mode …,codex --sandbox …). dogcow renders the stream, and indefault/prompting modes the backend asks in prose — answerable as your next message (same as Claude's headlessAskUserQuestionbehaviour). Seedeferred.md.
Accounts
Multiple accounts per backend are profiles — env overlays (credential vars +
per-backend config dir) selected at spawn time. They live in
~/.config/dogcow/config.json:
{
"active": { "backend": "claude", "profile": "default" },
"backends": {
"claude": { "profiles": { "default": {}, "team": { "env": { "CLAUDE_CONFIG_DIR": "/home/you/.claude-team" } } } },
"gemini": { "approvalMode": "default", "profiles": { "personal": { "env": { "GEMINI_API_KEY": "…" } } } },
"codex": { "sandbox": "workspace-write", "profiles": { "work": { "env": { "CODEX_HOME": "/home/you/.codex-work" } } } }
}
}/account— list profiles for the active backend./account <name>— switch profile (persisted; effective next turn).
Backend-specific knobs (approvalMode, sandbox) sit on the backend and are
read by that descriptor.
Models
Pick which model the active backend runs. Each backend declares its own
selectable models; for claude these are the current Claude models:
/model— list the active backend's models (current marked*)./model <alias|id>— switch model (persisted; effective next turn).
claude] /model
models for claude:
* fable claude-fable-5 Fable 5 (most capable; premium-priced)
opus claude-opus-4-8 Opus 4.8 (most capable 4.x)
sonnet claude-sonnet-4-6 Sonnet 4.6 (balanced)
haiku claude-haiku-4-5 Haiku 4.5 (fastest)Either the short alias (/model opus, always the latest of that tier) or the
full id (/model claude-opus-4-8, a pinned version) is accepted; the full id is
what's persisted, to config.backends.<name>.model. With no override set,
dogcow passes no --model and the backend CLI's own default model is used. A
backend that declares no models reports that selection is unsupported.
Sessions
For the claude backend, dogcow pins one persistent session per working
directory instead of resuming "the most recent conversation" (--continue),
which is ambiguous if anything else uses claude in the same project. The
pinned id is stored in ~/.config/dogcow/sessions.json; the first turn creates
the session (--session-id) and later turns resume it (--resume), so the same
conversation is picked up across restarts.
Because dogcow uses Claude's own session store
(~/.claude/projects/<dir>/<id>.jsonl), sessions can be managed like
claude --resume:
/session— list this directory's sessions (active marked*), newest first, with each session's first message./session <n>— switch the active session to the nth listed./session new— rotate to a fresh session (no memory flush;/newdoes both)./session delete <n>— delete a session's transcript. Deleting the active one rotates to a fresh session.
Other backends are unaffected: gemini is stateless per turn and codex
resumes its own last session, so /session reports that it is claude-only.
Slash commands
Typing /name [args] runs commands/name.sh (with the args passed
through) — e.g. /clear runs commands/clear.sh. Drop any executable-or-
not *.sh in commands/ to add a command; they run with dogcow's
environment (so DOGCOW_SOCK etc. are available). If no matching script
exists the line falls through to the normal rules, so the built-in
/queue / /clearq (below), /ask …, and absolute paths like
/usr/bin/foo still work.
Bundled commands:
/clear— clear the screen and scrollback./new— wrap up and start fresh. It runs the prompt incommands/new.mdagainst the current session (edits allowed) so Claude saves anything worth keeping to its long-term memory, clears the screen, and rotates dogcow to a fresh session (see Sessions). Editcommands/new.mdto change what gets remembered.
The /queue / /clearq (message queue), /backend, /account, /model, and
/session commands are built into dogcow rather than scripts in commands/.
Message queue
While a turn is running you can keep typing. Each line is buffered and
dispatched in order as the current turn finishes, the same way the
claude CLI queues messages. While a turn is in flight:
/queue— list the pending messages/clearq— drop everything queued
A permission prompt that appears mid-turn still reads your answer normally; the answer is not mistaken for a queued message.
Sounds
moof.js plays classic Mac OS sounds from ~/Library/Sounds
(Moof.aiff, Boing.aiff, Wild-eep.aiff) and is a silent no-op when
they are absent. The sounds are Apple's and are not shipped in this
repo. fetch-sounds.js pulls them, per user, from a public Internet
Archive item and saves them under the names the player expects:
npm run sounds # the three sounds dogcow plays
node fetch-sounds.js --all # plus the rest of the classic setLinux playback additionally needs sox (play); macOS uses the
built-in afplay.
Permission gate
When the wrapped claude wants to use a tool, the PreToolUse hook
connects back to the daemon over a Unix socket and you get a numbered
prompt:
1. Yes
2. Yes, and don't ask again for `Bash(curl *)`
...
N. No, and tell me what to do differentlyFor the gate to fire, dogcow-permissionshook.js must be registered
as a PreToolUse hook in a settings file claude reads (e.g.
~/.claude/settings.json):
{
"hooks": {
"PreToolUse": [
{
"matcher": "*",
"hooks": [
{ "type": "command",
"command": "node /absolute/path/to/dogcow/dogcow-permissionshook.js" }
]
}
]
}
}The hook is a no-op unless DOGCOW_SOCK is set (the daemon sets it),
so it is safe to register globally. "Don't ask again" choices are
persisted to $XDG_CONFIG_HOME/dogcow/settings.local.json (defaulting
to ~/.config/dogcow/settings.local.json), so the rule store stays
writable even when dogcow itself is installed to a root-owned prefix.
License
AGPL-3.0 — see LICENSE.
