Skip to content

scao7/duckling-cli

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

19 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

duckling

                                                              ____
                                                          ___/    \__
   __    __    __    __    __    __    __                /   o      \
  (o>   (o>   (o>   (o>   (o>   (o>   (o>                \_         >
   ~~    ~~    ~~    ~~    ~~    ~~    ~~                  \_______/
                                                             ||  ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Drive Claude Code from Telegram. Spawn Claude sessions, watch plans evolve, answer questions with one tap β€” all from your phone.

npm version License: MIT Node

English Β· δΈ­ζ–‡

npm i -g duckling-cli   # install
duckling setup          # pair with @DucklingCli_Bot via QR
duckling start          # run daemon β†’ chat with the bot

Published on npm as duckling-cli. Requires Node 18.17+ and a working claude install (uses your existing OAuth β€” no extra API key).


duckling is a tiny daemon that runs the official Claude Agent SDK on your machine and bridges it to Telegram. You send a prompt from your phone β†’ Claude works on your computer β†’ results, plans, and questions stream back into the chat with tap-to-answer buttons.

There's nothing to host. We run one shared bot (@DucklingCli_Bot) and one Cloudflare Worker relay. You install one npm package, scan a QR code, you're done.

Why?

Claude Code is great when you're at your desk. The problem starts when you're not:

  • πŸš‡ On the train, watching a long task tick through a TODO list.
  • πŸ›οΈ In bed, and Claude paused on AskUserQuestion waiting for a one-tap answer.
  • πŸƒ Out for a run, and you want to kick off a refactor before you forget.

duckling solves exactly this. It does not replace your terminal. SSH is still better for typing code. duckling is for ambient access β€” "is it done yet", "approve this", "kill the bad branch."

Quick start

There are two ways to use duckling. Pick one.

Path A β€” Use the shared bot πŸ¦† (recommended)

You pair with our hosted bot (@DucklingCli_Bot) and our Cloudflare Worker. Zero infra setup on your side.

npm i -g duckling-cli           # one-time install
duckling setup                  # one-time pairing (QR + Telegram tap)
cd /path/to/your/project        # ← run duckling start from here
duckling start                  # daemon picks up THIS dir as its cwd

What happens:

  1. npm i -g duckling-cli installs the CLI globally.
  2. duckling setup prints a QR + a https://t.me/DucklingCli_Bot?start=… link. Scan/click β†’ tap Start in Telegram β†’ paired. Config writes to ~/.config/duckling/config.json.
  3. cd into the project you want the daemon to work in. This matters: the daemon's working directory is what you've cd'd into when you ran duckling start. The Agent SDK spawns Claude sessions in that directory, and /new's picker shows the same session pool your terminal claude has been using in that directory (~/.claude/projects/<encoded-cwd>/).
  4. duckling start brings up the daemon, which connects to our shared relay. It prints the cwd it locked onto, so you can sanity-check.

You did not need a Cloudflare account, a Telegram bot, or any deploy step. Inference still runs on your own Claude OAuth locally β€” the relay only forwards control events, not model traffic.

To switch projects: duckling stop && cd /other/project && duckling start.

Path B β€” Run your own bot + Worker

If you'd rather not depend on the shared relay (private team bot, paranoid about an intermediary, etc.), you self-host. The CLI is identical; only the maintainer (you) does an extra one-time Cloudflare deploy.

Maintainer, once:

git clone https://github.com/scao7/duckling-cli.git
cd duckling-cli
npm install
# Follow DEPLOY.md β€” 5 wrangler commands:
#   wrangler login
#   wrangler secret put TELEGRAM_BOT_TOKEN     # from BotFather
#   wrangler secret put TG_WEBHOOK_SECRET      # random string
#   wrangler deploy                            # prints your relay URL
#   curl … setWebhook                          # point Telegram at the worker

Each user (anyone pointed at your fork), once:

npm i -g duckling-cli
export DUCKLING_RELAY_URL=https://your-relay.your-subdomain.workers.dev
duckling setup
duckling start

DUCKLING_RELAY_URL redirects pairing + the daemon's WebSocket to your Worker instead of ours. After pairing, the URL is baked into ~/.config/duckling/config.json β€” no need to keep exporting it.

Full recipe + cost calculator (spoiler: $0 on Cloudflare's free tier for small teams) in DEPLOY.md.

Both paths require

  • Node 18.17+
  • A working claude install, logged in. The Agent SDK uses your existing Claude OAuth β€” no separate API key, no extra cost. If claude --version works on your machine, you're set.

After setup β€” talk to the bot

Two ways to start a task:

One-shot: type the whole task with the command.

You:   /new refactor the auth middleware to use the new token format
Bot:   πŸ“‹ refactor-auth-middleware           ← appears + edits in place
       ⬜ Read existing middleware
       ⬜ Adapt to new token shape
       ⬜ Update tests
       ⬜ Run lint + tests
Bot:   ❓ refactor-auth-middleware Β· Token source
       Should I read tokens from headers only, or also cookies?
       [ headers only ] [ headers + cookies ]   ← tap one
       …                                        ← silent while it works
Bot:   βœ… refactor-auth-middleware Β· completed Β· 4m12s Β· $0.0341

Tap-only: just /new (no args). Bot shows a picker of sessions you already have on this machine β€” pick to resume, or tap "βž• ζ–°ε‘˜ε·₯" for a blank one:

You:   /new
Bot:   πŸ¦† 派活给谁?选一δΈͺζ—§δ»»εŠ‘ζŽ₯着干,ζˆ–θ€…η‚Ή ζ–°ε‘˜ε·₯ εΌ€δΈͺε…¨ζ–°ηš„γ€‚
       [ refactor auth middleware Β· 12m ago  ]
       [ write a quicksort        Β· 1h ago   ]
       [ debug the login flow     Β· yesterday]
       [ βž• ζ–°ε‘˜ε·₯                            ]    ← tap = "ε‘˜ε·₯一号" spawns

The resumable rows come straight from ~/.claude/projects/<your-cwd>/*.jsonl β€” exactly the sessions your terminal claude has been working in. Pick one and it forks (won't pollute the original transcript); tap "ζ–°ε‘˜ε·₯" and a blank session named ε‘˜ε·₯一号 (ε‘˜ε·₯二号, etc.) is spawned, waiting for your next message to use as its first task.

Four message types ever: plan, question, done, error. No "I'm now reading file X", no "Running npm test...", no anchor banners. The chat is for milestones, not chit-chat.

What you can do

The full command list

Command What it does
/new No args β†’ picker (resume an existing session or spawn ζ–°ε‘˜ε·₯). With args (/new <task>) β†’ spawn + start on that task right away.
/kill Stop a task. No args β†’ picker of running sessions; with args (/kill <name>) β†’ kill that one directly.
/help Three-line cheatsheet

That's the whole / menu. Three commands. Anything more would mean adding "things to fiddle with on your phone" β€” which is exactly what this tool refuses to do.

Without typing commands

  • Direct message β€” once a session is current, anything you type without a leading / continues that task ("look, also handle the case where the token is empty"). If you used /new to spawn a blank ε‘˜ε·₯ with no task yet, your next message becomes its first task.
  • One-tap decisions β€” when Claude calls AskUserQuestion, options become inline buttons. No typing required.
  • Picker-everywhere β€” both /new and /kill pop tappable lists when used without args. You never have to memorise session names or IDs.

Design rules duckling lives by

  1. Production happens at the desk. This tool serves the moments you're not at the desk. Nothing else.
  2. Make the user do less, not more. Pickers > typing. Defaults > config. Auto > manual.
  3. No feature that pulls the user back to the phone. If it tempts you to "just check in", it's wrong.
  4. Fewer messages is better. Only plan, approve, done, error, plus questions Claude genuinely cannot answer without you.
  5. Subordinate, not chat buddy. An employee doesn't IM their boss play-by-play; they report at milestones.
  6. Test before adding a feature: does this let the user leave their computer, or force them to stare at the phone? Stare β†’ no.
  7. North star: send a task before bed, wake up to one "done" message.

If you fork and find yourself wanting to add notifications, dashboards, "live tail" mode, status bars, anything that buzzes more than once per task β€” re-read the seven rules. duckling is built around them.

How it works

   Your phone                Cloudflare Worker             Your computer
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚ Telegram     │◀── Bot ───▢│ duckling-relay│◀── WS ───▢│ duckling daemon  β”‚
 β”‚  @Duckling…  β”‚            β”‚  + DOs        β”‚           β”‚  β”” Agent SDK     β”‚
 β”‚              β”‚            β”‚               β”‚           β”‚     β”” session 1  β”‚
 β”‚              β”‚            β”‚               β”‚           β”‚     β”” session 2  β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                  β”‚ OAuth
                                                                  β–Ό
                                                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                                          β”‚ Claude (your sub)β”‚
                                                          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  • The daemon runs the SDK in-process. One Session per /new. Each Session has its own input stream β€” turns come from you (TG), responses stream out as discrete events.
  • The relay is a Cloudflare Worker that fans out per-user Durable Objects. It owns the Telegram webhook, holds your hibernated WebSocket, and renders SDK events into TG messages.
  • Anthropic SDK calls never touch the relay. Inference goes from your machine straight to Claude using your own OAuth subscription. The relay is a control-plane only.

Privacy & security

  • Your code never leaves your machine unless Claude itself decides to read or write it, in which case the file path / preview travels through the relay as part of a tool_use event. Tool outputs (e.g. test results, file contents) don't.
  • The relay forwards and forgets. Durable Object state is limited to pairing tokens, device records, the latest sessions snapshot, and short-lived question contexts. No code, no transcripts.
  • Self-host if you don't trust the shared relay. The Worker is the whole stack β€” npx wrangler deploy and you own the data plane. See DEPLOY.md.
  • Auth on the daemon side is a deviceToken, opaque to you, revocable from the relay. It's the only secret on your machine.

Self-hosting

The default points at the shared relay we operate. If you'd rather run your own:

# One-time, as the maintainer of your own bot
git clone https://github.com/scao7/duckling-cli.git
cd duckling-cli
npm install
# follow DEPLOY.md β€” five commands: wrangler login + secret put + deploy + setWebhook

Users of your fork override the default with:

export DUCKLING_RELAY_URL=https://your-relay.workers.dev
duckling setup

Full recipe + cost calculator (spoiler: $0 on Cloudflare's free tier for small teams) in DEPLOY.md.

Architecture

Layer What Code
CLI duckling setup|start|stop|status src/cli/
Daemon SDK runner, session manager, WS client src/daemon/
Worker TG webhook, pairing, fan-out to UserDO src/worker/
Shared Wire protocol (DaemonToRelay / RelayToDaemon) src/shared/protocol.ts

The codebase is small (~1500 LoC of TS) and self-contained β€” no framework on either side, just ws + commander + Cloudflare Durable Objects.

See CLAUDE.md for the design doc: architecture decisions, wire protocol, and why duckling is "just a relay" β€” no approval gates, no second permission system.

Development

git clone https://github.com/scao7/duckling-cli.git
cd duckling-cli
npm install
npm run build        # tsc + worker typecheck

# CLI:
node dist/cli/index.js setup
node dist/cli/index.js start

# Worker (local dev, no TG webhook β€” useful for /pair/* and /healthz):
npm run worker:dev

# Deploy after changes:
npm run worker:deploy

Roadmap

Things on the table, not yet shipped:

  • duckling attach β€” hand a claude session you started in an SSH terminal off to the bot, so the chat picks up that conversation's history.
  • Multi-user on a single machine β€” currently one daemon per Linux user (separate ~/.config/duckling/).
  • Reply-to-route β€” let TG message-replies target a specific session without /switch.
  • Auto-archive β€” old sessions linger forever; add a TTL.
  • Diff rendering β€” Edit/Write previews as code blocks (short) or images (long).

PRs welcome. Open an issue first if it's bigger than a quick fix.

Contributing

When sending a PR:

  • Run npm run build (must pass β€” strict TypeScript on both sides).
  • If the change touches the wire protocol, update both src/shared/protocol.ts and any handlers in src/daemon/ and src/worker/.

License

MIT β€” fork freely. The shared bot/relay is a convenience, not a moat.

Acknowledgements

Built on top of @anthropic-ai/claude-agent-sdk. Inspired by the pattern in openclaw-claude-code-plugin of using the SDK's streaming-input mode for multi-turn.

About

Drive Claude Code from Telegram. Daemon runs the Claude Agent SDK per session; events forwarded to TG with inline buttons. Shared Cloudflare Worker relay.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors