From ccffbd1252efa17200153f17e538744b4e7d178c Mon Sep 17 00:00:00 2001 From: jazzymc Date: Sun, 8 Nov 2026 07:39:12 +0000 Subject: [PATCH] Mac bridge: ssh to the Mac, omac, clip, screenshot hotkey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ssh mac works from this box. The key had been offered and refused for a day because it had never reached the Mac — an earlier paste ran only part of its command. Fixed by pulling the key over the Mac -> Unraid link that already worked, so it never passed through the clipboard. Two new commands in shell/bin, installed to /root/.local/bin at boot: - omac translates an Unraid path to its Mac counterpart through the shared Syncthing folders, then opens it, prints it, emits a file:// URL or an OSC 8 hyperlink, or verifies it has synced. It refuses rather than guesses outside a shared folder. Literal paths are tried before symlink-resolved ones, after readlink -m was found to resolve skills/memory out of the shared tree. - clip puts text into the Mac's pasteboard over OSC 52, walking up the process tree to find a pty and wrapping for tmux passthrough. Also records the Hammerspoon screenshot hotkey on the Mac side, how hc ls from the Mac addresses terminals on both machines, and that screencapture cannot work over ssh because macOS withholds Screen Recording from a non-GUI session. Verified end to end: a PNG made here, copied to the Mac, pushed back with hc put unraid/t13, named by shot and read in-session byte-intact. Co-Authored-By: Claude Opus 5 --- docs/12-DEVELOPMENT-ENVIRONMENT.md | 33 ++++++++++++ docs/14-HERDR-REMOTE-MACHINES.md | 81 ++++++++++++++++++++++++------ docs/CHANGELOG.md | 11 ++++ 3 files changed, 110 insertions(+), 15 deletions(-) diff --git a/docs/12-DEVELOPMENT-ENVIRONMENT.md b/docs/12-DEVELOPMENT-ENVIRONMENT.md index ee6365e..539d2c7 100644 --- a/docs/12-DEVELOPMENT-ENVIRONMENT.md +++ b/docs/12-DEVELOPMENT-ENVIRONMENT.md @@ -275,6 +275,39 @@ failure. Named directories shorten the share paths (`~projects`, `~ampeco`, `~appdata`, `~vault`, `~flash`), and `cdpath` makes `cd backend` work from anywhere, matching the `tmux-work` bookmarks. +### Commands in `shell/bin/` + +`install.sh` installs everything in `/boot/config/shell/bin/` to +`/root/.local/bin` at mode 755. These are real executables, not shell +functions, because a non-interactive shell — an agent's tool call, `ssh box +cmd`, cron — never sources `.bashrc` but does inherit `PATH`. + +| Command | Purpose | +|---------|---------| +| `shot` | Name the files `hc put` dropped into this Herdr terminal | +| `omac` | Act on an Unraid file as its Mac counterpart: open, path, `file://` URL, OSC 8 link, or verify it has synced | +| `clip` | Put text into the Mac's pasteboard from a session here, over OSC 52 | + +`omac` translates through the shared Syncthing folders. The Unraid paths in +Syncthing's `config.xml` are *container* paths and must be resolved through the +`syncthing` container's mounts (`/data1` → `/mnt/user/projects`): + +| Folder id | Unraid | Mac | +|-----------|--------|-----| +| `jmmgo-qzpqp` (obsidian) | `/mnt/user/projects/obsidian` | `/Users/kaloyandanchev/Obsidian` | +| `homelab` | `/mnt/user/projects/homelab` | `/Users/kaloyandanchev/Homelab` | +| `ampeco` | `/mnt/user/projects/ampeco` | `/Users/kaloyandanchev/Ampeco` | +| `personal` | `/mnt/user/projects/personal` | `/Users/kaloyandanchev/Personal` | +| `claude-skills` | `/mnt/user/appdata/claude-code/.claude/skills` | `/Users/kaloyandanchev/.claude/skills` | +| `cooperator` | `/mnt/user/appdata/cooperator` | `/Users/kaloyandanchev/.cooperator` | + +`/mnt/user/projects/infrastructure` is **not** synced, so this repo has no Mac +counterpart and cannot be click-opened. `omac` reports that rather than +returning a path that does not exist. + +Full detail, including the Hammerspoon screenshot hotkey on the Mac, is in +`14-HERDR-REMOTE-MACHINES.md`. + ### Persistence `/` is a RAM rootfs, so every dotfile in `/root` is rebuilt at boot and anything diff --git a/docs/14-HERDR-REMOTE-MACHINES.md b/docs/14-HERDR-REMOTE-MACHINES.md index 637684a..0a289e7 100644 --- a/docs/14-HERDR-REMOTE-MACHINES.md +++ b/docs/14-HERDR-REMOTE-MACHINES.md @@ -208,23 +208,74 @@ inherit `PATH`. With no drops it prints the `hc put` command that would create one. -### Reaching the Mac from here (not yet set up) +### Reaching the Mac from here -`hc machine ls` on this core returns `{"machines":[]}` — the relationship is -one-directional from the Mac today. Registering the Mac here would add -`hc file read|write|edit --on mac` ("an agent's hands there") and -`hc exec --on mac`, which is how a session on this box gets at Mac files -without a mount. Two caveats: `hc file read` is **text only**, so Mac images -must travel by `hc put`; and the Mac is a laptop, so register it by its tailnet -name (`100.68.118.59`, `kaloyans-macbook-air`) and expect it to be unreachable -when asleep. +Set up 2026-11-08. `ssh mac` works from this box, by key over Tailscale. -Verified 2026-11-07: the Mac answers over Tailscale in 16 ms (direct, via -`192.168.20.101`) and port 22 is open, so macOS Remote Login is already on. It -does **not** advertise Tailscale SSH (`sshHostKeys: false`; macOS cannot act as -a Tailscale SSH server), so unlike this box there is no keyless path — it needs -this host's `unraid@xtrm` key in the Mac's `authorized_keys`. A commented `Host -mac` entry is staged in `/boot/config/ssh/root/config`. +| Property | Value | +|----------|-------| +| Alias | `mac`, `macbook` in `/boot/config/ssh/root/config` | +| Address | `100.68.118.59` — the tailnet IP, not a LAN one, because it is a laptop | +| User | `kaloyandanchev` (uid 501, in `com.apple.access_ssh`) | +| Key | `/root/.ssh/id_ed25519`, `SHA256:DT7YBFfdI9+Ccb4Dfet+Q/xXkhqfmqeQi6ecRpSFQSY` | +| Auth | key only — macOS cannot be a Tailscale SSH server, so there is no keyless path | + +The key had been offered and refused for a day. The cause was mundane: it had +never reached the Mac, because an earlier paste ran only part of its command. +The fix avoided the clipboard entirely by pulling the key over the link that +already worked in the other direction: + +```bash +# on the Mac +K=$(ssh unraid 'cat /root/.ssh/id_ed25519.pub') +grep -qF "$K" ~/.ssh/authorized_keys || echo "$K" >> ~/.ssh/authorized_keys +``` + +`hc machine ls` on **this** core still returns `{"machines":[]}` and was left +that way: ssh covers what registering would have bought, and `hc file read` is +text-only so Mac images would still have to travel by `hc put`. + +**What ssh to the Mac cannot do:** `screencapture` fails with `could not create +image from rect`. macOS withholds Screen Recording permission from a non-GUI +session. Screen capture therefore belongs to Hammerspoon, in the GUI session. + +### Addressing terminals across both machines + +`hc ls --json` **from the Mac** lists every live terminal on both machines in +one array — local ones as `t7`, this box's as `unraid/t13` — with `status`, +`agent`, `cwd` and a `where` label. The envelope is +`{"hidden_finished":N,"machines":[...],"terminals":[...]}`. + +That is what makes a single Mac-side hotkey able to target any session +anywhere, without knowing in advance which machine is in front of the user. + +### The screenshot hotkey (Mac side) + +Appended to `~/.hammerspoon/init.lua` on 2026-11-08 (160 → 266 lines, backup +`init.lua.bak-20261108-093027`). + +| Key | Action | +|-----|--------| +| `Cmd+Shift+U` | Clipboard image, or capture a region if there is none; pick a terminal; push and prompt | +| `Cmd+Ctrl+U` | The same, straight to the terminal chosen last time | + +It runs `hc put --json `, parses the returned path, then +`hc send "Look at the screenshot at "` so the agent is told +without anything being typed. + +Implementation notes that matter if it is ever edited: + +- `hs.pasteboard.readImage()` reads the pasteboard natively — no `pngpaste`, no + Homebrew, which keeps the "no second environment" constraint. +- Every `hc` call goes through `hs.task`, never `hs.execute`: a put to this box + travels over ssh, and blocking Hammerspoon's main thread would freeze every + other hotkey. +- Bindings avoid the `Cmd+Shift` letters the read-aloud hotkeys already use + (Z, X, D, A, R). + +Verified end to end on 2026-11-08: a 24×24 PNG made on this box, copied to the +Mac, pushed back with `hc put unraid/t13`, named by `shot` and read in-session +byte-intact. ## Notes diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 0913df8..90f1c64 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,17 @@ **Purpose:** Major infrastructure events only. Minor changes are in git commit messages. +--- +## 2026-11-08 + +### Mac ↔ Unraid bridge: screenshots in, clipboard and click-to-open out +- **[FIXED]** `ssh mac` from this box now works. The key had been offered and refused for a day; the cause was that it had never reached the Mac, because an earlier paste ran only part of its command. Fixed by pulling the key over the Mac → Unraid link that already worked, so it never passed through the clipboard. Auth is key-only: macOS cannot act as a Tailscale SSH server, unlike this box. +- **[HOST]** New `omac` (`/boot/config/shell/bin/omac` → `/root/.local/bin/omac`) translates an Unraid path to its Mac counterpart through the shared Syncthing folders and then opens it, prints it, emits a `file://` URL or an OSC 8 hyperlink, or verifies the file has actually synced. It refuses rather than guesses for anything outside a shared folder — `/mnt/user/projects/infrastructure` is not synced. A bug found in testing: `readlink -m` alone resolved `skills/memory` out of the shared tree via a symlink, so the literal path is now tried before the resolved one. +- **[HOST]** New `clip` (`shell/bin/clip` → `/root/.local/bin/clip`) puts text into the Mac's pasteboard by writing an OSC 52 escape to this session's pty — no ssh, no mount, no sync. Walks up the process tree to find a pty, since a tool call or pipeline has none of its own, and wraps for tmux passthrough when `$TMUX` is set. OSC 52 has no reply, so a `clipboard-write = deny` refusal cannot be detected from here. +- **[HOST]** Screenshot hotkey appended to the Mac's `~/.hammerspoon/init.lua` (160 → 266 lines, backup kept): `Cmd+Shift+U` picks a terminal and pushes the clipboard image, `Cmd+Ctrl+U` repeats to the last one. The picker lists every live terminal on **both** machines, because `hc ls` from the Mac addresses local terminals as `t7` and this box's as `unraid/t13`. Verified end to end — a PNG made here, sent to the Mac, pushed back with `hc put`, named by `shot` and read in-session byte-intact. +- **[HOST]** Documented that `screencapture` cannot work over ssh (`could not create image from rect`): macOS withholds Screen Recording permission from a non-GUI session. Screen capture belongs to Hammerspoon, in the GUI session. +- **[NOTE]** `hc` is 0.1.18 on the Mac and 0.1.17 here. + --- ## 2026-11-07