🦆 Self-hosted background AI worker that asks before anything risky. Queues tasks for Claude Code, Antigravity or Ollama, falls back between models, shows the exact command for approval. Zero dependencies + native macOS app.
翻译准备中
正在加载完整 README。
查看完整 README ↗You give Duffy a task from the web dashboard, the command line, a chat bridge or the
macOS app. Duffy runs it through the AI command-line agent you already use
(Claude Code claude -p, Antigravity agy, Ollama ollama run, or any other CLI),
falls back to the next model when one runs out of quota, and reports back.
When the agent wants to do something you should sign off on, such as sending an email, pushing code or deleting files, Duffy stops, shows you the exact command, and runs it only after you press Approve.
One Python file, standard library only. Happy on an old laptop or a small home server that never sleeps.
git clone https://github.com/benfirad/daak-duffy.git && cd daak-duffy
python3 duffy.py daemon # worker + dashboard on http://127.0.0.1:8899
python3 duffy.py add "Summarise yesterday's server logs and list anything unusual"
On a Mac, add the menu bar app with cd macos && ./build.command.
Most agent tools either answer while you watch, or run unattended and hope for the best. Duffy sits in between: autonomous for routine work, human-in-the-loop for anything with consequences.
| Chat with an AI | Agent running unattended | Duffy | |
|---|---|---|---|
| Works while you're away | ✗ | ✓ | ✓ |
| Queue, history and logs that survive restarts | ✗ | sometimes | ✓ |
| Asks before risky actions, shows the exact command | n/a | ✗ | ✓ |
| Switches model when one hits its quota | ✗ | rarely | ✓ |
| Uses the CLI and subscription you already have | ✗ | varies | ✓ |
| Runs on your own machine, no account, no cloud | ✗ | varies | ✓ |
Good for: inbox and newsletter summaries, research with sources, checking why a backup or cron job failed, keeping a home server healthy, small code chores in your repos, drafting replies that you approve before they go out.
| Task queue | SQLite queue with a full log per task. Survives restarts. |
| Approval gate | The agent ends its answer with [APPROVAL_REQUIRED: what and why | CMD: command]. Nothing runs until a human approves. |
| Model fallback | Give it a list of models; quota or rate-limit answers move it to the next one. |
| Dashboard | Queue, results, approve / reject / retry, light and dark mode. |
| Chat agents | Optional personas you can talk to; each reply runs through the same agent command. |
| Notifications | Any webhook that accepts {"chatId", "message"} — WhatsApp or Telegram bridges fit. |
| Office panel (optional) | office.py: approvals, health checks, launchd services and log tails on one screen. |
ai command (optional) | Asks Claude; when Claude's usage limit is hit it switches to Antigravity, then to Duffy, until the limit resets. |
| macOS app (optional) | Native menu bar app: chat with agents, queue tasks, approve commands, watch services. See below. |
flowchart LR
A["You: dashboard / CLI / chat"] --> Q[("Task queue")]
Q --> W["Duffy worker"]
W --> M{"Agent CLI<br/>model 1 → 2 → 3"}
M -->|report| D["Done + notify"]
M -->|APPROVAL_REQUIRED| G["Waiting for you"]
G -->|Approve| R["Run the exact command"] --> D
G -->|Reject| X["Rejected"]
A native SwiftUI menu bar app for people who would rather not keep a browser tab open. English or Turkish, follows your system language.
Build it with cd macos && ./build.command (macOS 13+, Xcode Command Line Tools, no Xcode project).
Details in macos/README.md.
Open http://127.0.0.1:8899 and queue a task, or from another terminal:
python3 duffy.py add "Summarise yesterday's server logs and list anything unusual"
python3 duffy.py list
python3 duffy.py show 1
By default Duffy calls agy --model <model> -p <prompt>. To use something else, set
agent_command in ~/.duffy/config.json (see below).
Copy config.example.json to ~/.duffy/config.json. Every key is
optional.
| Key | Default | Meaning |
|---|---|---|
agent_command | ["agy", "--model", "{model}", "-p", "{prompt}"] | Command run per attempt. {model}, {prompt} and {workspace} are filled in. |
models | ["default"] | Tried in order until one answers. |
quota_markers | quota / rate limit texts | Short answers containing these count as "try the next model". |
bind_hosts | ["127.0.0.1"] | Addresses the dashboard listens on. Add a VPN address (e.g. your Tailscale IP) to reach it from other devices. |
allowed_clients | [] (anyone who can connect) | IPs or CIDR ranges allowed to use the dashboard, e.g. ["100.64.0.0/10"]. |
notify.webhook_url, notify.chat_id | empty | Where to send "done" and "needs approval" messages. |
agents | one duffy persona | Chat personas: {"id": {"name": "...", "prompt": "..."}}. |
Examples for agent_command:
["claude", "-p", "{prompt}", "--model", "{model}"] // Claude Code
["ollama", "run", "{model}", "{prompt}"] // local models
["agy", "--model", "{model}", "-p", "{prompt}"] // Antigravity CLI
DUFFY_HOME, DUFFY_CONFIG, DUFFY_PORT and DUFFY_BIND override the file.
| Command | |
|---|---|
duffy.py daemon | Worker loop + dashboard |
duffy.py add "task" [-c category] | Queue a task |
duffy.py list / show <id> | Recent tasks / one task with its log |
duffy.py approve <id> / reject <id> / retry <id> | Decide on a gated task, or re-queue |
duffy.py step | Process one task and exit (handy for cron) |
The HTTP API mirrors these: GET /api/status, GET /api/logs?id=, and POST /api/add,
/api/approve, /api/reject, /api/retry, /api/chat with a JSON body.
以上为 README 的开头部分。其余内容请阅读原文。 查看完整 README ↗