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