Install
openclaw skills install perkoon-transferThe agent data layer. Perkoon moves files between agents and the physical world — agent to human, agent to agent, agent to pipeline. P2P over WebRTC. Free. E...
openclaw skills install perkoon-transferFile transfer for humans and the things replacing them.
Your agent has compute, memory, and context. What it lacks is a jump gate to the physical world. Perkoon is that gate — P2P file transfer over WebRTC, engineered for autonomous machines from day one.
Not "AI-ready" the way a gas station is gourmet-ready. Actually built for machines — JSON event streams, structured exit codes, MCP native tools, an A2A protocol endpoint, and a state machine that doesn't need a browser, a mouse, or a soul.
Four integration methods — pick the one that fits your runtime:
| Method | Best for | Install size |
|---|---|---|
| MCP Server | Coding agents (Claude Code, Cursor, Windsurf, VS Code Copilot) | ~8 MB |
| CLI | Agents with shell access | ~8 MB |
| A2A Protocol | Agents with HTTP but no shell (ChatGPT, web agents) | Zero — just POST |
| Browser Automation | Full UI control via Playwright/Puppeteer | ~200 MB (Chromium) |
Three supported flight paths:
P2P transfers are free, unlimited, and encrypted. Both ends need to be online — that's the only constraint. For sensitive files, always use --password — without it, anyone with the share link can download.
If your host supports MCP, this is the fastest path. Three native tools, zero shell commands.
Install: npx -y @perkoon/mcp@0.1.3 (stdio transport, pinned version)
Tools provided:
send_file — Send a file. Returns session code + share URL. Waits for receiver.receive_file — Receive files from a session code. Saves to disk.check_session — Check if a session is active, expired, or not found.Configuration for common hosts:
{
"mcpServers": {
"perkoon": {
"command": "npx",
"args": ["-y", "@perkoon/mcp@0.1.3"]
}
}
}
.mcp.json in your projectclaude_desktop_config.jsonnpx -y @perkoon/mcp@0.1.3Once configured, just call the tools directly — no bash, no log polling, no background processes.
IMPORTANT: Use npx -y perkoon@0.3.6 (pinned version) to avoid dynamic fetching of unaudited code.
Before sending, confirm the file path and name with the user. Do not send files from sensitive directories without explicit user approval.
Replace /path/to/file with the actual file path:
rm -f /tmp/perkoon-send.log /tmp/perkoon-send.pid
npx -y perkoon@0.3.6 send /path/to/file --json --timeout 1800 > /tmp/perkoon-send.log 2>&1 &
echo $! > /tmp/perkoon-send.pid
# Wait for session code (up to 30s)
for i in $(seq 1 30); do
if grep -q session_created /tmp/perkoon-send.log 2>/dev/null; then
grep session_created /tmp/perkoon-send.log
break
fi
if grep -q '"event":"error"' /tmp/perkoon-send.log 2>/dev/null; then
echo "===SEND FAILED===" && cat /tmp/perkoon-send.log
break
fi
sleep 1
done
Output: {"event":"session_created","session_code":"K7MX4QPR9W2N","share_url":"https://perkoon.com/K7MX4QPR9W2N"}
Once session_created appears, share the URL with the user. Example: "Here's your file — open this link on any device to download it: https://perkoon.com/K7MX4QPR9W2N"
The receiver doesn't need to install anything. The link works in any browser.
For password-protected sends, add --password SECRET and share both the URL and password.
You MUST run this after sharing the link. Do NOT skip this step.
for i in $(seq 1 360); do
if grep -q transfer_complete /tmp/perkoon-send.log 2>/dev/null; then
echo "===TRANSFER COMPLETE===" && grep transfer_complete /tmp/perkoon-send.log
break
fi
if grep -q '"event":"error"' /tmp/perkoon-send.log 2>/dev/null; then
echo "===TRANSFER FAILED===" && grep error /tmp/perkoon-send.log
break
fi
if [ "$((i % 30))" -eq 0 ]; then
grep progress /tmp/perkoon-send.log 2>/dev/null | tail -1
fi
sleep 5
done
===TRANSFER COMPLETE=== → Tell the user: "File sent successfully!" Include speed and duration from the JSON.===TRANSFER FAILED=== → Tell the user what went wrong.When another agent creates a session via A2A and gives you a session code + sender key:
rm -f /tmp/perkoon-send.log /tmp/perkoon-send.pid
npx -y perkoon@0.3.6 send /path/to/file --session CODE --sender-key KEY --json --timeout 1800 > /tmp/perkoon-send.log 2>&1 &
echo $! > /tmp/perkoon-send.pid
Then monitor with the same Step 2 above. This joins an existing session as sender instead of creating a new one. The receiving agent runs npx -y perkoon@0.3.6 receive CODE --json on their end.
Replace CODE with the 12-character session code:
rm -f /tmp/perkoon-recv.log /tmp/perkoon-recv.pid
npx -y perkoon@0.3.6 receive CODE --json --overwrite --output /home/openclaw/.openclaw/workspace/received/ > /tmp/perkoon-recv.log 2>&1 &
echo $! > /tmp/perkoon-recv.pid
for i in $(seq 1 360); do
if grep -q transfer_complete /tmp/perkoon-recv.log 2>/dev/null; then
echo "===TRANSFER COMPLETE===" && grep transfer_complete /tmp/perkoon-recv.log
break
fi
if grep -q '"event":"error"' /tmp/perkoon-recv.log 2>/dev/null; then
echo "===TRANSFER FAILED===" && grep error /tmp/perkoon-recv.log
break
fi
sleep 5
done
For password-protected sessions, add --password SECRET.
===TRANSFER COMPLETE=== → Tell the user: "File received!" and the save path.===TRANSFER FAILED=== → Tell the user what went wrong.Files are saved to /home/openclaw/.openclaw/workspace/received/.
Stream a received file directly into another process — no disk write:
npx -y perkoon@0.3.6 receive CODE --output - > /path/to/destination
If you can make HTTP requests but can't run shell commands, use the A2A endpoint directly.
Endpoint: POST https://perkoon.com/a2a
Protocol: JSON-RPC 2.0
Agent Card: https://perkoon.com/.well-known/agent.json
{
"jsonrpc": "2.0",
"method": "message/send",
"id": "1",
"params": {
"message": {
"parts": [{ "type": "data", "data": { "skill": "describe" } }]
}
}
}
{
"jsonrpc": "2.0",
"method": "message/send",
"id": "1",
"params": {
"message": {
"parts": [{
"type": "data",
"data": {
"skill": "send-files",
"name": "quarterly-report"
}
}]
}
}
}
Returns sender_url (for browser upload) + CLI commands + sender_key for A2A sender joining.
If you have shell access, run the CLI command from the response. If not, share the sender_url with your user — they open it in any browser to upload.
{
"jsonrpc": "2.0",
"method": "message/send",
"id": "1",
"params": {
"message": {
"parts": [{
"type": "data",
"data": {
"skill": "receive-files",
"session_code": "K7MX4QPR9W2N"
}
}]
}
}
}
{
"jsonrpc": "2.0",
"method": "message/send",
"id": "1",
"params": {
"message": {
"parts": [{
"type": "data",
"data": {
"skill": "session-status",
"session_code": "K7MX4QPR9W2N"
}
}]
}
}
}
Include clientCapabilities in your DataPart to get optimized instructions:
{
"type": "data",
"data": {
"skill": "send-files",
"clientCapabilities": { "shellAccess": true, "nodeJs": true }
}
}
Agents declaring shell access get CLI commands. Web-only agents get browser URLs.
For agents that need full browser UI control. Heavier than CLI but gives visual verification.
Ready-to-run scripts (requires npm install playwright):
curl https://perkoon.com/perkoon_send.mjs > send.mjs && node send.mjs /path/to/file.zip
curl https://perkoon.com/perkoon_receive.mjs > receive.mjs && node receive.mjs SESSION_CODE ./output
https://perkoon.com/create, accept TOS, click StartlocalStorage.setItem('perkoon_skip_confirm_direct-confirm', 'true') and localStorage.setItem('perkoon_skip_confirm_staged-confirm', 'true')page.setInputFiles('[data-testid="file-input"]', filePath)page.waitForFunction(() => window.__perkoon?.participants?.length >= 2, null, { timeout: 300000 })[data-testid="start-transfer"]page.waitForFunction(() => window.__perkoon?.transfer?.status === 'complete', null, { timeout: 600000 })page.on('download', d => downloads.push(d))https://perkoon.com/{SESSION_CODE}?agent=true[data-testid="transfer-accept"], click [data-testid="transfer-tos-checkbox"] if visible, then click [data-testid="transfer-accept"]page.waitForFunction(() => window.__perkoon?.transfer?.status === 'complete', null, { timeout: 600000 })await download.saveAs('./received/' + basename(download.suggestedFilename()))| Flag | Description |
|---|---|
--json | Machine-readable JSON events (always use for automation) |
--session <code> | Join an existing session as sender (A2A agent-to-agent) |
--sender-key <key> | Auth key for --session (provided by session creator) |
--password <pw> | End-to-end password protection |
--timeout <sec> | Peer wait time (default: 300, use 1800 for sends) |
--output <dir> | Save directory (default: ./received) |
--output - | Stream to stdout (no disk write) |
--overwrite | Replace existing files |
--quiet | Suppress human-readable output |
Events appear in order on stdout when using --json:
| Event | Meaning | Key fields |
|---|---|---|
session_created | Ready — share the link now | session_code, share_url |
receiver_connected | Peer joined | |
webrtc_connected | Direct P2P link established | |
progress | Transfer in progress | percent, speed, eta |
transfer_complete | Done | duration_ms, speed |
error | Failed | message, exit_code |
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Bad arguments |
| 2 | File not found |
| 3 | Network/session error |
| 4 | Wrong password |
| 5 | Timeout — no peer joined |
--json for parseable outputsession_created appears, share the URL with the user--timeout 1800 for sends (30 min for the human to open the link)--overwrite for receivestransfer_complete or error — then tell the user the resultnpx -y perkoon@0.3.6 — never use @latest| URL | What it is |
|---|---|
https://perkoon.com/.well-known/agent.json | A2A agent card (machine-readable capabilities) |
https://perkoon.com/llms.txt | Full agent integration guide |
https://perkoon.com/automate | Human-readable automation docs |
https://www.npmjs.com/package/@perkoon/mcp | MCP server package |
https://www.npmjs.com/package/perkoon | CLI package |
npm i -g perkoon