✓ Verified 📱 Social Media ✓ Enhanced Data

Localsend

Send and receive files to/from nearby devices using the LocalSend protocol.

Rating
4.3 (300 reviews)
Downloads
800 downloads
Version
1.0.0

Overview

Send and receive files to/from nearby devices using the LocalSend protocol.

Key Features

1

— Pick target device (if not already selected)

2

— Choose what to send

3

— Snapshot current files

4

— Start receiver in background

5

— Confirm ready with buttons

6

— Monitor for incoming files

7

— Post-receive confirmation (MANDATORY)

8

— Stop receiver

Complete Documentation

View Source →

LocalSend

Interactive file transfer between devices on the local network using real Telegram inline keyboard buttons. Works with any device running the LocalSend app (Android, iOS, Windows, macOS, Linux).

Install

The localsend-cli is a zero-dependency Python CLI. Install from GitHub:

bash
curl -fsSL https://raw.githubusercontent.com/Chordlini/localsend-cli/master/localsend-cli -o ~/.local/bin/localsend-cli
chmod +x ~/.local/bin/localsend-cli

Full docs: https://github.com/Chordlini/localsend-cli

Requires Python 3.8+ and openssl (for TLS).


Telegram Button Format

All menus MUST use OpenClaw's inline button format. Send buttons alongside your message using this structure:

json
buttons: [
  [{ "text": "Label", "callback_data": "ls:action" }],
  [{ "text": "Row 2", "callback_data": "ls:other" }]
]
  • Outer array = rows of buttons
  • Inner array = buttons per row (max 3 per row for readability)
  • Prefix all callback_data with ls: to namespace this skill
  • When user taps a button, you receive: callback_data: ls:action

State Awareness (CRITICAL)

This skill uses conversational state. Track where you are in the flow:

StateMeaningNext user input should be treated as...
idleNo active flowNormal message — respond normally
awaiting_fileAsked user to drop/specify a file to sendThe file to send — do NOT comment on it, describe it, or react to it. Immediately use it as the send payload.
awaiting_textAsked user to type text to sendThe text payload — send it, don't discuss it
awaiting_confirmWaiting for send confirmationExpect ls:confirm-send or ls:menu
receivingReceiver is activeMonitor for incoming files
RULES:
  • When in awaiting_file state and user sends an image/file/path → treat it as the file to send. Show confirmation buttons immediately.
  • When in awaiting_text state and user types anything → treat it as the text to send.
  • NEVER comment on, describe, or react to a file/image when you're in awaiting_file state.
  • State resets to idle when user taps ls:menu or the flow completes.

On Trigger: Main Menu

When the user types /localsend or mentions sending/receiving files locally, send this message with real inline buttons:

Message:

text
📡 LocalSend — File Transfer

Buttons:

json
buttons: [
  [
    { "text": "📤 Send", "callback_data": "ls:send" },
    { "text": "📥 Receive", "callback_data": "ls:receive" }
  ],
  [
    { "text": "🔍 Scan Devices", "callback_data": "ls:devices" }
  ]
]

Do NOT run any commands yet. Wait for the button tap.


Flow: Scan Devices

Trigger: callback_data: ls:devices or user says "scan", "discover", "find devices"

  • Run:
bash
localsend-cli discover --json -t 2
  • Devices found — create one button per device, plus Refresh and Back:
Message:
text
📡 Found 3 devices:

Buttons (one device per row):

json
buttons: [
     [{ "text": "📱 Fast Potato — 192.168.0.148", "callback_data": "ls:dev:Fast Potato" }],
     [{ "text": "💻 Rami-Desktop — 192.168.0.100", "callback_data": "ls:dev:Rami-Desktop" }],
     [{ "text": "🖥️ Living Room PC — 192.168.0.105", "callback_data": "ls:dev:Living Room PC" }],
     [
       { "text": "🔄 Refresh", "callback_data": "ls:devices" },
       { "text": "⬅️ Back", "callback_data": "ls:menu" }
     ]
   ]
  • No devices found:
Message:
text
📡 No devices found.
   Make sure LocalSend is open on the other device and both are on the same WiFi.

Buttons:

json
buttons: [
     [
       { "text": "🔄 Try Again", "callback_data": "ls:devices" },
       { "text": "⬅️ Back", "callback_data": "ls:menu" }
     ]
   ]
  • User taps a device (callback_data: ls:dev:DEVICENAME) — store it as the selected target. Show action menu:
Message:
text
✅ Selected: Fast Potato (192.168.0.148)
   What do you want to do?

Buttons:

json
buttons: [
     [
       { "text": "📄 Send File", "callback_data": "ls:sendfile" },
       { "text": "📝 Send Text", "callback_data": "ls:sendtext" }
     ],
     [
       { "text": "📦 Send Multiple", "callback_data": "ls:sendmulti" },
       { "text": "⬅️ Back", "callback_data": "ls:devices" }
     ]
   ]


Flow: Send

Trigger: callback_data: ls:send

Step 1 — Pick target device (if not already selected)

Run discover and show device picker (see Scan Devices flow above).

Step 2 — Choose what to send

Message:

text
Send to Fast Potato:

Buttons:

json
buttons: [
  [
    { "text": "📄 Send File", "callback_data": "ls:sendfile" },
    { "text": "📝 Send Text", "callback_data": "ls:sendtext" }
  ],
  [
    { "text": "📦 Send Multiple", "callback_data": "ls:sendmulti" },
    { "text": "⬅️ Back", "callback_data": "ls:menu" }
  ]
]

Send File (callback_data: ls:sendfile)

  • Ask: "Send me the file, drop a path, or tell me which file to send"
  • User provides file path or sends a file via chat
  • Get file size with stat or ls -lh
  • Confirm with buttons:
Message:
text
📤 Send to Fast Potato?
   📄 project.zip — 4.2 MB

Buttons:

json
buttons: [
     [
       { "text": "✅ Send", "callback_data": "ls:confirm-send" },
       { "text": "❌ Cancel", "callback_data": "ls:menu" }
     ]
   ]
  • On confirm, run:
bash
localsend-cli send --to "Fast Potato" /path/to/project.zip
  • Report result:
Message:
text
✅ Sent project.zip (4.2 MB) to Fast Potato

Buttons:

json
buttons: [
     [
       { "text": "📤 Send Another", "callback_data": "ls:send" },
       { "text": "⬅️ Menu", "callback_data": "ls:menu" }
     ]
   ]

Send Text (callback_data: ls:sendtext)

  • Ask: "Type the text you want to send:"
  • User types their message
  • Write text to temp file, send:
bash
echo "user's text" > /tmp/localsend-text.txt
   localsend-cli send --to "Fast Potato" /tmp/localsend-text.txt
   rm /tmp/localsend-text.txt
  • Confirm:
Message:
text
✅ Text sent to Fast Potato

Buttons:

json
buttons: [
     [
       { "text": "📝 Send More Text", "callback_data": "ls:sendtext" },
       { "text": "📤 Send File", "callback_data": "ls:sendfile" }
     ],
     [{ "text": "⬅️ Menu", "callback_data": "ls:menu" }]
   ]

Send Multiple (callback_data: ls:sendmulti)

  • Ask: "List the files or give me a glob pattern (e.g. ~/Screenshots/*.png)"
  • User provides paths or pattern
  • Expand glob, list files with sizes:
Message:
text
📦 Send 5 files to Fast Potato?
   📄 photo1.jpg — 2.1 MB
   📄 photo2.jpg — 1.8 MB
   📄 photo3.jpg — 3.2 MB
   📄 photo4.jpg — 2.5 MB
   📄 photo5.jpg — 1.9 MB
   📊 Total: 11.5 MB

Buttons:

json
buttons: [
     [
       { "text": "✅ Send All", "callback_data": "ls:confirm-send" },
       { "text": "❌ Cancel", "callback_data": "ls:menu" }
     ]
   ]
  • On confirm, run:
bash
localsend-cli send --to "Fast Potato" photo1.jpg photo2.jpg photo3.jpg photo4.jpg photo5.jpg
  • Report:
Message:
text
✅ Sent 5 files (11.5 MB) to Fast Potato

Buttons:

json
buttons: [
     [
       { "text": "📤 Send More", "callback_data": "ls:send" },
       { "text": "⬅️ Menu", "callback_data": "ls:menu" }
     ]
   ]


Flow: Receive

Trigger: callback_data: ls:receive or user says "receive", "start receiving", "listen"

Step 1 — Snapshot current files

bash
ls -1 /home/rami/.openclaw/workspace/_incoming/ > /tmp/localsend-before.txt

Step 2 — Start receiver in background

bash
localsend-cli --alias openclaw-workspace receive --save-dir /home/rami/.openclaw/workspace/_incoming/ -y

Run with run_in_background: true. Store the task ID.

CRITICAL: --alias MUST come BEFORE receive (global flag).

Step 3 — Confirm ready with buttons

Message:

text
📡 Receiver active — "openclaw-workspace"
📁 Saving to: ~/incoming/
✅ Auto-accept: ON

Send files from your device whenever ready.

Buttons:

json
buttons: [
  [
    { "text": "🛑 Stop", "callback_data": "ls:stop" },
    { "text": "🔄 Status", "callback_data": "ls:status" }
  ]
]

Step 4 — Monitor for incoming files

Poll every 3 seconds for new files:

bash
ls -1 /home/rami/.openclaw/workspace/_incoming/ > /tmp/localsend-after.txt
diff /tmp/localsend-before.txt /tmp/localsend-after.txt

Step 5 — Post-receive confirmation (MANDATORY)

When file(s) arrive, immediately present in chat with inline buttons.

Single file:

Message:

text
✅ Received from Fast Potato:
📄 portfolio.zip — 240 MB
📁 Saved to: ~/incoming/portfolio.zip

Buttons (contextual by file type):

json
buttons: [
  [
    { "text": "📂 Extract", "callback_data": "ls:extract" },
    { "text": "🚀 Deploy", "callback_data": "ls:deploy" }
  ],
  [
    { "text": "📥 Receive More", "callback_data": "ls:receive" },
    { "text": "🛑 Stop", "callback_data": "ls:stop" }
  ]
]

Image file — show inline preview:

Message:

text
✅ Received from Fast Potato:
🖼️ screenshot.png — 2.1 MB
Include MEDIA:~/incoming/screenshot.png for inline preview.

Buttons:

json
buttons: [
  [
    { "text": "📂 Open Folder", "callback_data": "ls:openfolder" },
    { "text": "📥 Receive More", "callback_data": "ls:receive" }
  ],
  [{ "text": "🛑 Stop", "callback_data": "ls:stop" }]
]

Multiple files:

Message:

text
✅ Received 3 files from Fast Potato:
📄 app.apk — 45 MB
📄 README.md — 2 KB
🖼️ icon.png — 128 KB
📊 Total: 45.1 MB

Buttons:

json
buttons: [
  [
    { "text": "📂 Show All", "callback_data": "ls:showall" },
    { "text": "📥 Receive More", "callback_data": "ls:receive" }
  ],
  [{ "text": "🛑 Stop", "callback_data": "ls:stop" }]
]

Contextual button rules by file type:

  • .zip, .tar.gz → add 📂 Extract button
  • .png, .jpg, .gif, .webp → show MEDIA: inline + 📂 Open Folder
  • .apk → add 📱 Install button
  • .html, .js, .py → add 👁️ Preview button
  • website archives → add 🚀 Deploy button

Step 6 — Stop receiver

Trigger: callback_data: ls:stop

  • Kill the background task using stored task ID
  • Confirm:
Message:
text
🛑 Receiver stopped.

Buttons:

json
buttons: [
     [
       { "text": "📡 Restart", "callback_data": "ls:receive" },
       { "text": "⬅️ Menu", "callback_data": "ls:menu" }
     ]
   ]


Flow: Status Check

Trigger: callback_data: ls:status

Check if receiver is running and count new files:

bash
ls -1 /home/rami/.openclaw/workspace/_incoming/ > /tmp/localsend-after.txt
diff /tmp/localsend-before.txt /tmp/localsend-after.txt | grep "^>" | wc -l

Message:

text
📡 Receiver: Running (12 min)
📁 Files received: 2
📊 Total: 242 MB

Buttons:

json
buttons: [
  [
    { "text": "🛑 Stop", "callback_data": "ls:stop" },
    { "text": "📂 Show Files", "callback_data": "ls:showall" }
  ]
]


Callback Data Reference

callback_dataAction
ls:menuShow main menu
ls:sendStart send flow
ls:receiveStart receive flow
ls:devicesDiscover devices
ls:dev:DEVICENAMESelect a specific device
ls:sendfileSend single file
ls:sendtextSend text message
ls:sendmultiSend multiple files
ls:confirm-sendConfirm and execute send
ls:stopStop receiver
ls:statusCheck receiver status
ls:extractExtract received archive
ls:deployDeploy received website
ls:openfolderOpen save directory
ls:showallList all received files

CLI Reference

CommandUsage
Discoverlocalsend-cli discover --json -t 2
Sendlocalsend-cli send --to "DEVICE" file1 file2 ...
Receivelocalsend-cli --alias NAME receive --save-dir DIR -y
FlagScopeDescription
--alias NAMEGlobal (before subcommand)Device name to advertise
--to NAMEsendTarget device (case-insensitive substring)
-t NdiscoverScan duration in seconds (use 2 for speed)
--jsondiscoverMachine-readable output
--save-dir DIRreceiveSave location (default: ~/Downloads)
-yreceiveAuto-accept transfers

Troubleshooting

ProblemFix
unrecognized arguments: --aliasMove --alias BEFORE the subcommand
No devices foundOpen LocalSend on target, same WiFi, screen on
Port 53317 in useNormal — CLI auto-falls back to 53318/53319
Transfer declined (403)Use -y on receiver side
Transfer hangsLarge file on slow WiFi — be patient

Reference

  • CLI repo & docs: https://github.com/Chordlini/localsend-cli
  • LocalSend protocol: references/protocol.md or https://github.com/localsend/protocol

Installation

Terminal bash

openclaw install localsend
    
Copied!

💻Code Examples

chmod +x ~/.local/bin/localsend-cli

chmod-x-localbinlocalsend-cli.txt
Full docs: https://github.com/Chordlini/localsend-cli

Requires Python 3.8+ and `openssl` (for TLS).

---

## Telegram Button Format

All menus MUST use OpenClaw's inline button format. Send buttons alongside your message using this structure:

]

.txt
- Outer array = rows of buttons
- Inner array = buttons per row (max 3 per row for readability)
- Prefix all callback_data with `ls:` to namespace this skill
- When user taps a button, you receive: `callback_data: ls:action`

---

## State Awareness (CRITICAL)

This skill uses conversational state. Track where you are in the flow:

| State | Meaning | Next user input should be treated as... |
|-------|---------|----------------------------------------|
| `idle` | No active flow | Normal message — respond normally |
| `awaiting_file` | Asked user to drop/specify a file to send | **The file to send** — do NOT comment on it, describe it, or react to it. Immediately use it as the send payload. |
| `awaiting_text` | Asked user to type text to send | **The text payload** — send it, don't discuss it |
| `awaiting_confirm` | Waiting for send confirmation | Expect `ls:confirm-send` or `ls:menu` |
| `receiving` | Receiver is active | Monitor for incoming files |

**RULES:**
- When in `awaiting_file` state and user sends an image/file/path → treat it as the file to send. Show confirmation buttons immediately.
- When in `awaiting_text` state and user types anything → treat it as the text to send.
- NEVER comment on, describe, or react to a file/image when you're in `awaiting_file` state.
- State resets to `idle` when user taps `ls:menu` or the flow completes.

---

## On Trigger: Main Menu

When the user types `/localsend` or mentions sending/receiving files locally, send this message **with real inline buttons**:

**Message:**

]

.txt
Do NOT run any commands yet. Wait for the button tap.

---

## Flow: Scan Devices

**Trigger:** `callback_data: ls:devices` or user says "scan", "discover", "find devices"

1. Run:

**Buttons:**

buttons.json
buttons: [
  [
    { "text": "📄 Send File", "callback_data": "ls:sendfile" },
    { "text": "📝 Send Text", "callback_data": "ls:sendtext" }
  ],
  [
    { "text": "📦 Send Multiple", "callback_data": "ls:sendmulti" },
    { "text": "⬅️ Back", "callback_data": "ls:menu" }
  ]
]

localsend-cli --alias openclaw-workspace receive --save-dir /home/rami/.openclaw/workspace/_incoming/ -y

localsend-cli---alias-openclaw-workspace-receive---save-dir-homeramiopenclawworkspaceincoming--y.txt
Run with `run_in_background: true`. Store the task ID.

**CRITICAL:** `--alias` MUST come BEFORE `receive` (global flag).

### Step 3 — Confirm ready with buttons

**Message:**

]

.txt
### Step 4 — Monitor for incoming files

Poll every 3 seconds for new files:

diff /tmp/localsend-before.txt /tmp/localsend-after.txt

diff-tmplocalsend-beforetxt-tmplocalsend-aftertxt.txt
### Step 5 — Post-receive confirmation (MANDATORY)

When file(s) arrive, **immediately present in chat with inline buttons**.

**Single file:**

Message:

]

.txt
**Image file — show inline preview:**

Message:

🖼️ screenshot.png — 2.1 MB

-screenshotpng--21-mb.txt
Include `MEDIA:~/incoming/screenshot.png` for inline preview.

Buttons:

]

.txt
**Multiple files:**

Message:

Tags

#communication

Quick Info

Category Social Media
Model Claude 3.5
Complexity One-Click
Author chordlini
Last Updated 3/10/2026
🚀
Optimized for
Claude 3.5
🧠

Ready to Install?

Get started with this skill in seconds

openclaw install localsend