Turn a local file into a temporary download link. Create a browser upload page so someone can send files back. No cloud, no accounts, no re-uploading what's already on disk.
# share a file that's already on the server
afb share ./report.docx --ttl 1h
# let someone upload files to the server
afb upload-session --ttl 1h --max-files 5Chat platforms have file-size limits, MIME blocks, and silent failures. Worst part: an agent generates a file on a server, you download it from the server, then upload to the chat. Every time.
Agent File Bridge inverts that. The agent runs a tiny server, points at a local path, and gets back a URL. No upload step. No S3 bucket. No cloud.
Agent: "I have a file at /tmp/output.docx"
→ afb share /tmp/output.docx
→ "https://files.example.com/d/abc123" (expires in 1h)
→ send that URL to anyone
Reverse direction works too — someone with a browser uploads files straight to the server's filesystem. Uploaded files get scanned for viruses before the agent touches them.
pip install agent-file-bridge
mkdir -p ./uploads
AFB_BASE_URL=http://127.0.0.1:8765 \
AFB_ALLOWED_ROOTS=$PWD,/tmp \
AFB_UPLOAD_DIR=$PWD/uploads \
afb serve --host 127.0.0.1 --port 8765Or install directly:
curl -fsSL https://raw.githubusercontent.com/wmyung/agent-file-bridge/main/install.sh | bashafb serve [--host] [--port] # start the server
afb share <path> [--ttl] [--max-downloads] # create a download link
afb upload-session [--ttl] [--max-files] [--max-size-mb] [--ext] [--webhook-url] # create an upload page
afb upload-status <session-id> # see what files arrivedEach link is a random bearer token. Anyone with the URL can access the file until it expires or hits the download limit.
Other tools (transfer.sh, Plik, etc.) make you upload a file to get a link. That works for humans, but it's wasted work for an agent that already has the file on disk.
| Tool | How it works | Best for |
|---|---|---|
| transfer.sh, Plik | Upload → link | Human-to-human |
| Magic Wormhole | CLI-to-CLI tunnel | Two technical users |
| File Browser | Full web file manager | Browsing files |
| S3 presigned URLs | Signed URL from object storage | Cloud workflows |
| Agent File Bridge | Point at a local path → link | Agent-to-human, agent receiving files |
Both sides of this tool — upload and download — depend on the same thing: the server must be reachable over HTTP. Here is what each direction needs and what changes based on your server environment.
afb share ./report.pdf
# → https://your-server/d/s0meT0kenSomeone with that URL sends a GET request to your server. That is all. No authentication, no client setup.
afb upload-session --ttl 1h
# → https://your-server/u/s0meT0ken
# → open in a browser → file picker → POST to serverSame requirement as download — the server must accept TCP connections from the person holding the URL.
| Server type | Public IP | Needs extra tooling? | Notes |
|---|---|---|---|
| VPS / cloud VM (DigitalOcean, Linode, AWS EC2, GCP, etc.) | ✅ Yes | Depends on firewall | If the firewall allows the port, the URL works globally as-is. If ports are locked down, open the firewall or use a tunnel. |
| Bare-metal server with public IP | ✅ Yes | No | Best case. afb serve --host 0.0.0.0, open the port, done. |
| Home server behind NAT | ❌ No | Yes — tunnel (ngrok, Cloudflare Tunnel, Tailscale Funnel) or port forwarding | The server has no public address. A tunnel relays traffic to it. |
| Office / corporate network | ❌ No | Yes — same as home NAT, plus possibly corporate firewall rules | Same problem as home NAT, often stricter egress rules. |
| Localhost (same machine) | N/A | No | AFB_BASE_URL=http://127.0.0.1:8765 — only the local machine can reach it. Great for agent ↔ human on the same computer. |
| LAN / VPN (same private network) | N/A | No | AFB_BASE_URL=http://192.168.x.x:8765 — anyone inside the same network can reach it. No public exposure. |
- Same machine (localhost):
pip install+afb serve→ everything works. Agent and user share the same filesystem. This is the simplest setup. - Same private network (LAN / Tailscale / ZeroTier):
afb serve --host 0.0.0.0→ anyone on the network can download and upload. No tunnel needed. - Public access from anywhere: You need a VPS or a server with a public IP, or a tunnel to a NAT-ed machine.
- Public IP but firewall locked: Open the port in the firewall, or use a tunnel. The server already has a public address — it just needs to be reachable.
A tunnel (ngrok, Cloudflare Tunnel, Tailscale Funnel) creates a public URL that relays traffic to your private server. This works everywhere but adds latency, a bandwidth limit, and a third-party dependency.
Direct access (public IP + open port) needs no relay — just the server itself. This is the production setup and what the tool assumes by default.
Upload and download are the same requirement: can the server accept an HTTP request from the person holding the URL? If yes, both work. If not, neither works. The solution is always the same — give the server a public endpoint, either by using a VPS, opening its port, or putting a tunnel in front of it.
Uploaded files are sent through ClamAV if clamd is running on the server. Files over 200 MB are skipped (threshold configurable via AFB_SCAN_MAX_SIZE_MB). Scan results appear in upload-status:
"scan": {"status": "clean", "detail": ""}Possible statuses: clean, infected, unscanned (clamd not configured or file too large), error (clamd unreachable or timed out).
Set AFB_CLAMAV_SOCKET to your clamd socket path (e.g. /var/run/clamav/clamd.ctl) to enable scanning.
When a user finishes uploading files, Agent File Bridge can POST the results to a URL. Set AFB_UPLOAD_WEBHOOK_URL globally, or pass --webhook-url per session:
afb upload-session --webhook-url http://localhost:8888/upload-callbackThe webhook receives {"session_id": "...", "files": [...]}. Errors are non-fatal — the upload always succeeds, the webhook is fire-and-forget.
POST /api/share — create a download link
{"path": "/tmp/file.docx", "ttl_seconds": 3600, "max_downloads": 1}POST /api/upload-sessions — create an upload page
{"ttl_seconds": 3600, "max_files": 5, "max_size_mb": 500, "webhook_url": "..."}GET /api/upload-sessions/{id} — check what was uploaded
Returns file paths, sizes, SHA-256 hashes, MIME types, and scan results.
Three patterns.
Send a file. Call afb share, get a URL, send to the user. Don't expose the local path.
Receive files. Call afb upload-session, send the URL, poll afb upload-status. Check SHA-256 and scan status before processing.
Defaults:
--ttl 1h
--max-downloads 1
--max-files 5
--max-size-mb 500
--ext .pdf,.docx,.xlsx,.csv,.zip,.png,.jpg,.txt| Variable | Default | |
|---|---|---|
AFB_BASE_URL |
— | Public URL shown in generated links |
AFB_SERVER |
http://127.0.0.1:8765 |
CLI target |
AFB_ALLOWED_ROOTS |
— | Directories that can be shared (comma-sep) |
AFB_UPLOAD_DIR |
./uploads |
Where uploaded files land |
AFB_DB |
./agent-file-bridge.sqlite3 |
Database path |
AFB_TOKEN_BYTES |
24 |
Random token length |
AFB_CLAMAV_SOCKET |
— | Path to clamd socket (enables scanning) |
AFB_SCAN_MAX_SIZE_MB |
200 |
Skip virus scan for files over this size |
AFB_UPLOAD_WEBHOOK_URL |
— | Default webhook URL for upload notifications |
pip install --upgrade agent-file-bridgeSchema migrations run automatically on server start. Back up your .sqlite3 file before upgrading.
Every URL is a bearer secret. Run behind HTTPS. Restrict AFB_ALLOWED_ROOTS. Use short TTLs. Never execute uploaded files. Enable ClamAV for untrusted uploads.
See SECURITY.md.