Commit graph

6 commits

Author SHA1 Message Date
098c34c546 Clipboard send/copy, PIN for messages, and quiet stdlib logging
- 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>
2026-05-27 22:06:24 +01:00
7ab3f905ee Send and read plain-text messages
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>
2026-05-27 21:40:40 +01:00
6e7aac090c Stop a cancelled transfer from carrying on into the next one
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>
2026-05-27 21:20:30 +01:00
beb6b9040e Support sending folders, not just single files
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>
2026-05-27 21:00:52 +01:00
f69d3464a7 Generate a random friendly alias on first run
Previously the advertised device name defaulted to the machine hostname.
Since the alias is broadcast in plaintext over multicast to the whole
subnet every few seconds while the TUI is open, that leaks the hostname
to anyone on the network — a privacy problem on a laptop joining
untrusted Wi-Fi (the reason LocalSend randomises its aliases).

Now first run generates a random "Colour Object" name from public-domain
word lists (e.g. "Crimson Quasar"): 56 colours x 44 celestial objects =
2464 combinations. It is generated once and persisted, so a device keeps
the same name across restarts. The hostname is still carried in
DeviceModel, and the alias remains overridable via --alias or Settings.

Word lists are original and use only generic colours and public-domain
astronomy terms, so there is no copyright or trademark exposure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 20:44:55 +01:00
2dd81700c0 Initial commit: Omarchy-Send v0.1.0
LocalSend-compatible file-transfer TUI for headless Arch/Omarchy servers.

- Pure-stdlib implementation of the LocalSend v2 protocol (discovery,
  HTTPS with matching cert fingerprint, send/receive, PIN).
- Bubble Tea TUI: Devices, Transfers, Manage (received-file housekeeping)
  and Settings, theme-aware on Omarchy.
- Dual-mode install.sh: curl-pipe download or build-from-clone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 19:26:08 +01:00