Two related additions for using omarchy-send beyond the focused TUI:
Headless send (no TUI / no TTY) — for scripts, cron, SSH sessions:
omarchy-send --to "<alias>" --message "<text>" [--send-pin N] [--wait 15s]
Resolves the peer by alias over multicast (discovery only, not the HTTP
receiver, so it co-exists with a running instance), sends, prints a
one-line result, and exits non-zero on not-found / failure. New
discovery.FindPeer/Snapshot and client.SendMessageSync (returns the error
directly, incl. ErrPinRequired).
Bugfix surfaced by the above: the official LocalSend client answers a
message prepare-upload with HTTP 204 No Content (the text rides in the
preview field, nothing to upload). prepareUpload only accepted 200, so
message sends to official peers failed with "prepare-upload status 204"
in BOTH the new headless path and the existing TUI. Now treats 204 as
success (empty response).
Desktop notifications: a running receiver raises a notify-send
notification on an incoming message or file offer, so mako shows it on
Omarchy/Hyprland even when the TUI isn't focused. New internal/notify
(best-effort; self-disabling on headless boxes with no notify-send /
session bus). Off-switch: --no-notify flag, cfg.NoNotify, and a Settings
'n' toggle wired to a live atomic gate via Controller.SetNotify so it
takes effect without a restart.
Tests: discovery/find_test, client/message_sync_test,
client/prepare_204_test, app/events_test. Race-clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Messages to a PIN-protected peer now prompt for the PIN and retry, like
file sends (was a silent failure). SendMessage takes a pin argument and the
TUI tracks whether a pending send is a message or files.
- Copy a received message to the clipboard with `y` (in the Messages tab or
while reading one).
- Send the clipboard as a message with `v` on a device: it opens the compose
box pre-filled with the clipboard text to review before sending.
- New internal/clipboard package shells out to wl-clipboard / xclip / xsel,
and falls back to tmux's paste buffer when running inside tmux on a headless
box (tmux load-buffer -w also reaches the outer clipboard via set-clipboard,
which omaterm enables by default). No system tool -> "clipboard unavailable".
- Route the standard logger to the debug log (or discard) at startup so stray
stdlib logging — e.g. net/http's "unsolicited response on idle channel"
notice after a peer sends a late response — can't paint over the TUI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds LocalSend-compatible text messaging alongside file transfer.
Wire format (matches the LocalSend app): a message is a single "file" with
fileType "text/plain" whose content rides in the prepare-upload `preview`
field. The receiver returns an empty token set, so nothing is uploaded — the
text is read straight from the preview.
- client: SendMessage builds that single-file prepare-upload (no body upload).
- server: detect a message (one text file with non-empty preview), surface it
on a new Messages() channel instead of saving a file, and respond with an
empty file set. Messages bypass the accept prompt (auto-received); the PIN
gate still applies.
- app: bridge the server's messages channel to the TUI as MessageMsg.
- tui: a new Messages tab lists received messages (enter to read full, d to
delete); press `m` on a device to compose and send one. Incoming messages
show a footer notice.
Tests: end-to-end send→receive (text intact, sender preserved, nothing written
to disk) and unit coverage of the message-detection rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the receiver cancelled a transfer partway through, the sender kept
uploading the remaining files of that batch (the loop continued past the
failure), and a newly started transfer never stopped the old goroutine —
so the old, cancelled transfer appeared to "carry on".
Fixes:
- Each send now runs under a cancellable context. A new transfer to a peer
supersedes (cancels) any still-running send to that same peer.
- A network/session error mid-batch aborts the rest of the batch, emitting
a clean Cancel for the un-sent files instead of pushing them anyway. A
local file-open error still skips only that file.
- Bounded connection timeouts (dial 10s, TLS 10s, response-header 30s) so a
vanished peer fails fast; the overall timeout stays off for large files.
Test: a receiver whose session is gone (returns 403) causes the sender to
make exactly one upload attempt and then abort, reporting the rest cancelled.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The sender only handled regular files: a staged directory would fail at
upload time, so users could only send individual files.
Now a staged directory is walked recursively and each file is advertised
with a name relative to the folder's parent (e.g. "Trip/day1/img.jpg"),
which is the LocalSend-compatible way to carry structure. The receiver
recreates those subdirectories under the receive dir, creating parents as
needed. The path-traversal guard is preserved: names are cleaned against a
leading "/" to collapse "..", and a containment check ensures the result
stays within the receive dir.
In the TUI send picker, "a" stages the folder currently being browsed;
staged folders are tagged in the panel and the help text is updated.
Tests: directory expansion produces relative names; an end-to-end folder
send recreates the structure on the receiver; destPath preserves subdirs,
rejects traversal, and de-duplicates within subfolders.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>