Skip to content

jrf/Beam

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Beam

A macOS menu bar app that captures a screenshot, uploads it to a remote host over SSH, and copies the resulting absolute remote path to the clipboard — ready to paste into a terminal running Claude Code (or anything else) over SSH.

Replicates this shell flow as a one-click menu bar action:

local="/tmp/beam_$(date +%s).png"
remote=".cache/screenshots/ss_$(date +%s).png"
ssh "$host" "mkdir -p ~/.cache/screenshots"
screencapture -i "$local"
scp "$local" "$host:$remote"
ssh "$host" "echo \$HOME/$remote" | pbcopy
rm "$local"

Requirements

  • macOS Tahoe 26 (26.0+) — the project's deployment target is 26.0
  • Xcode 26 with the Swift 6 toolchain
  • Existing SSH keys / ~/.ssh/config set up for your remote hosts (the app bundles no credentials)

Build & run

Via Xcode (GUI)

  1. Open Beam.xcodeproj in Xcode 26.
  2. Select the Beam scheme and a My Mac destination.
  3. In the target's Signing & Capabilities tab, set your Team (the project ships with team unset). Automatically manage signing is on by default.
  4. Build & run (⌘R). The app installs no Dock icon — look for the camera.viewfinder glyph in the menu bar.

Via Command-Line (using just)

If you have just installed, you can build, run, and install the app from your terminal using the provided justfile:

  • just build: Compile a debug build (ad-hoc signed).
  • just run: Build and run the debug version in-place.
  • just install: Build the release version, copy it to your ~/Applications/ folder, and launch it.
  • just stop: Stop any running instance of Beam.
  • just clean: Clean up all build artifacts and the build/ directory.
  • just icons: Regenerate the app icons from Beam.png (requires imagemagick).

The first capture attempt prompts for Screen Recording permission. macOS won't let screencapture do anything visible until that's granted; once you've toggled it on in System Settings → Privacy & Security → Screen Recording, the app will pick it up on the next click. On Tahoe the grant may be re-confirmed periodically — the app rechecks CGPreflightScreenCaptureAccess() on every invocation and deep-links you to the settings pane on denial.

Configuring hosts

Preferences… (⌘, from the menu) opens a list editor where you can add, edit, and remove hosts. Each host has:

  • Display name — what shows up in the menu
  • SSH destination — an alias from ~/.ssh/config (e.g. my-server-alias) or a raw user@hostname. The chevron button next to the field offers a picker populated from ~/.ssh/config (Host directives + one level of Include globs; wildcard patterns like * are skipped).
  • Remote directory — where to drop the screenshot. Default ~/.cache/screenshots. ~/ means the remote $HOME; the ~/ prefix is stripped before scp/sftp transfer (since not all SFTP servers expand tildes) but is kept in the UI for clarity.
  • Filename template — controls the on-remote filename. Default {date}/ss_{ts}.png. Tokens: {date} = YYYY-MM-DD, {ts} = unix seconds. The directory part of the template (e.g. {date}/) is created on the remote with mkdir -p before upload.

The config is persisted as JSON at ~/Library/Application Support/Beam/config.json.

Capture modes

Each host's menu expands into a submenu:

  • Regionscreencapture -i, interactive marquee (the default and what the global hotkey triggers).
  • Full Screen — captures all displays without prompting.
  • Window — interactive window-pick (screencapture -iW).
  • Delayed… — 3 / 5 / 10 second countdown before an interactive region capture.
  • Upload File… — skip screencapture; choose a local file via open panel and upload it as-is, preserving its extension.

Recent uploads

The menu shows a Recent submenu of the last 10 successful upload paths. Click one to re-copy it to the clipboard. Useful if the upload's been clobbered by something else on your clipboard.

SSH connection reuse (ControlMaster)

Beam tells ssh/scp to use OpenSSH connection sharing:

-o ControlMaster=auto
-o ControlPath=~/Library/Caches/Beam/cm/cm-%C
-o ControlPersist=600

The first capture to a host pays the full handshake; subsequent captures within 10 minutes reuse the same TCP connection and feel near-instant.

Finder integration

After installing the app, you'll find Upload to Beam in any file's right-click → Services menu (and in the Services menu of any app's menu bar). It uploads the selected files to the last-used host. Provided by the standard NSServices mechanism — no separate extension target.

If the menu item doesn't appear right away, macOS rescans services on login or when you run /System/Library/CoreServices/pbs -update.

URL scheme

The beam:// URL scheme accepts:

  • beam://prefs — open the Preferences window
  • beam://capture — capture-and-upload for the last-used host
  • beam://upload?path=/abs/path&host=<id-or-name> — upload an existing file; host is optional (defaults to last-used)

Useful from launchers like Raycast, Alfred, or Stream Deck.

Global hotkey

Default: ⌃⌥⌘S — triggers capture-and-upload for the last-used host. Toggle and re-bind in Preferences: click the hotkey field, then press a combo with at least one modifier. Esc cancels. The new chord is saved and re-bound immediately. (Under the hood the field stores Carbon key codes from <Carbon/HIToolbox/Events.h> in config.json.)

How the SSH agent reaches the GUI process

GUI-launched processes inherit a stripped environment from launchd, so SSH_AUTH_SOCK and the PATH most users have in their shell rc files are typically not set. ~/.ssh/config host aliases also won't resolve if ssh can't find the agent. Beam works around this in ProcessRunner.swift:

  1. Start from ProcessInfo.processInfo.environment.
  2. Ensure HOME is set (so ssh finds ~/.ssh/config) and PATH is sane.
  3. If SSH_AUTH_SOCK is unset, ask launchctl getenv SSH_AUTH_SOCK.
  4. Fall back to launching /bin/zsh -lc 'printf %s "$SSH_AUTH_SOCK"' so a login shell's init scripts get a chance to set it.
  5. Pass ssh/scp -F ~/.ssh/config explicitly to make config use deterministic.

If uploads fail silently after a build, check ~/Library/Logs/... or run the same scp from the terminal — that's almost always where the problem lives.

To make SSH_AUTH_SOCK available to GUI apps system-wide (recommended if you use the agent for anything else), add this to your shell rc:

launchctl setenv SSH_AUTH_SOCK "$SSH_AUTH_SOCK"

…or set it once at login via a LaunchAgent.

Notarization & re-signing

The project has Hardened Runtime enabled (required for notarization). Sandboxing is explicitly off — the app shells out to /usr/sbin/screencapture, /usr/bin/ssh, /usr/bin/scp, /bin/zsh, and /bin/launchctl, which the sandbox would block.

If you re-sign or notarize the app, the Screen Recording grant in Privacy & Security is tied to the code-signing identity. Changing the identity invalidates the grant — macOS will ask again on the next capture. Same goes for changing the bundle identifier.

Layout

Beam/
├── main.swift                            NSApp bootstrap
├── AppDelegate.swift                     status item, menu, prefs window
├── Notifier.swift                        UNUserNotificationCenter wrapper
├── Models/Host.swift                     Codable host entry
├── Storage/
│   ├── HostsStore.swift                  JSON-backed @Published store
│   └── SSHConfigParser.swift             ~/.ssh/config Host alias extractor
├── Capture/
│   ├── CaptureCoordinator.swift          orchestrates capture → upload → copy
│   ├── CaptureKind.swift                 region/full/window/delayed enum
│   ├── FilenameTemplate.swift            {date}/{ts} token expansion
│   ├── RemoteUploader.swift              mkdir → scp → resolve, with ControlMaster
│   ├── ProcessRunner.swift               Process wrapper, SSH_AUTH_SOCK propagation
│   └── ScreenRecordingPermission.swift   CGPreflight/CGRequest helpers
├── UI/
│   ├── PreferencesView.swift             SwiftUI Form, Liquid Glass by default
│   └── HotKeyRecorder.swift              click-to-record hotkey field
├── HotKey/HotKey.swift                   Carbon RegisterEventHotKey wrapper
├── Info.plist                            LSUIElement = YES
└── Beam.entitlements               sandbox off, hardened runtime via build setting

About

macOS menu bar app: screenshot → SSH upload → remote path to clipboard

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors