@eddiedao/ai-tunnel
v0.2.0
Published
Turn a labeled GitHub issue into an autonomous PR via a pluggable worktree environment (Orca) and coding harness (omp). Ships an interactive installer.
Maintainers
Readme
@eddiedao/ai-tunnel
Turn a labeled GitHub issue into an autonomous PR. A loopback webhook listener
receives GitHub issues.labeled events through a Cloudflare Tunnel, spins up a
worktree in a pluggable environment (Orca today), launches a pluggable
coding harness (omp today), and drives it to open a PR — with per-repo HMAC,
a sender allowlist, private-only posture, dedup, and concurrency/daily caps.
label an issue "AI handle"
│ GitHub issues webhook (per-repo HMAC)
Cloudflare Tunnel <your-host> ─► ai-tunnel serve (127.0.0.1:PORT)
│ admission: HMAC · sender · private-only · not-a-PR · dedup · caps
▼
environment worktree off origin/<default> → verify plan at tip → run-harness.sh (repo-scoped PAT, no SSH)
▼
harness (omp: /ak:vibe --advice --ship --beta) → PR + optional DiscordRequirements
- Node ≥ 22 (uses
Promise.withResolvers). - git, gh (authenticated), cloudflared — and, on the host that runs
the agent: the selected environment (Orca) and harness (omp), both
installed and working. See
ai-tunnel doctor.
Install
npm install -g @eddiedao/ai-tunnel
# or from a checkout:
npm install && npm run build && npm install -g .Quickstart
ai-tunnel init # interactive wizard: environment → harness → config → enroll → service
ai-tunnel doctor # verify environment, harness, binaries, gh auth, config
ai-tunnel serve # run the listener (or install it as a service)The wizard writes everything to a config home (default ~/.ai-tunnel,
override with --home or $AI_TUNNEL_HOME):
~/.ai-tunnel/
.env # config (mode 600)
creds # fine-grained PAT (mode 600) — sourced by run-harness.sh
secrets.json # per-repo webhook HMAC secrets (mode 600) — the allowlist
run-harness.sh # env wrapper: PAT-over-HTTPS, SSH disabled
git-askpass.sh
.state/ # dedup, audit (dispatch.jsonl), logsThen set up the Cloudflare Tunnel to route your hostname to
http://localhost:<PORT> (the wizard prints the exact commands).
Commands
| Command | Purpose |
| --- | --- |
| ai-tunnel init | Interactive setup wizard |
| ai-tunnel doctor | Health report (environment/harness probes, binaries, gh auth, config) |
| ai-tunnel enroll <owner/name> | Labels + per-repo HMAC secret + issues webhook |
| ai-tunnel serve | Run the webhook listener (foreground) |
| ai-tunnel service install\|uninstall\|restart\|status | Background service (systemd on Linux, launchd on macOS) |
| --home <path> | Override the config/state home for any command |
Trigger contract
- Commit and push a plan to the default branch under
plans/<...>/plan.md. - Put a marker in the issue body:
<!-- plan="plans/<...>/plan.md" -->(a folder path ending/also works). - As an allowed sender, apply the
AI handlelabel.
Labels flow AI handle → AI in progress → AI done (PR created) / AI blocked
(needs a human). A branch that already has an open PR is never reused — a
fresh worktree is forked so work never stacks onto a PR under review.
Extending it
Both layers are registries; add one file and register it.
- Environment (
src/environments/): implement theEnvironmentinterface (worktree lifecycle, session launch, PR lookup) and add it toenvironments/index.ts. Roadmap:herd. - Harness (
src/harnesses/): implement theHarnessinterface (probe,buildLaunch,buildPrompt) and add it toharnesses/index.ts.claudeandcodexare registered as coming soon (visible in the wizard, not selectable) until implemented.
Security
- Listener binds
127.0.0.1only; the sole ingress is the Cloudflare Tunnel. - Per-repo HMAC (
secrets.json); the enrolled set is the allowlist (fail-closed —serverefuses to start with zero repos / no senders). - Sender allowlist; private-repo-only unless
ALLOW_PUBLIC=true. - Repo-scoped fine-grained PAT injected over HTTPS by
run-harness.sh; SSH keys disabled so the agent cannot act as you across all repos. - The approved plan is verified at the worktree tip before launch; issue/PR text is treated as untrusted by the harness prompt.
.env,creds,secrets.jsonare mode600and never committed.
Publishing
npm run build
npm publish # publishConfig.access=public; scope must match your npm account/orgLicense
MIT
