Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
603e6dd
Modernize for macOS Sequoia: Universal Binary + configurable terminal…
nathandale Feb 20, 2026
573ce9f
Add SSH Address Book Manager with LAN scanner
nathandale Feb 20, 2026
f4742e4
Fix import/export and add hostname resolution to LAN scanner
nathandale Feb 20, 2026
ff04436
Redesign Manager window for native macOS appearance
nathandale Feb 20, 2026
4bffe40
Fix double terminal window and sort LAN scan by last IP octet
nathandale Feb 20, 2026
6ab6e13
Add Save to Address Book from LAN scanner
nathandale Feb 20, 2026
f448946
Respect per-server terminal override in openHost:
nathandale Feb 20, 2026
dc45361
Detect installed terminals via Launch Services instead of hardcoding
nathandale Feb 20, 2026
8fbb568
Fix Ghostty launch: use NSWorkspace + sh -c, expand tilde in key path
nathandale Feb 20, 2026
8e6f7f8
Rewrite README with full feature documentation
nathandale Feb 20, 2026
846a2e1
Fix: allow multiple sessions from the same bookmark
nathandale Feb 20, 2026
17725cf
Add Initial Dir field to SSH bookmarks
nathandale Feb 20, 2026
dc4162b
Fix multiple sessions: use NSTask instead of createsNewApplicationIns…
nathandale Feb 22, 2026
b7677c1
Update About window: version 1.3.3, fork attribution, remove homepage
nathandale Feb 22, 2026
c020040
Refactor terminal launching and update About window for Nathan Dale's…
nathandale Feb 24, 2026
273cf8c
Release v1.4.0: Major rewrite for modern macOS
nathandale Feb 24, 2026
89e8ce7
Release v1.4.1: Fix terminal multiple windows and update About window
nathandale Feb 24, 2026
2d00adb
Release v1.4.2: Final fix for terminal windows and About window cleanup
nathandale Feb 24, 2026
c4e60ea
Release v1.4.3: Add fork attribution to About window
nathandale Feb 24, 2026
2898332
Release v1.4.4: Fix Ghostty window launching
nathandale Feb 24, 2026
76d6456
Release v1.4.5: Fix Ghostty invalid field errors
nathandale Feb 24, 2026
6c6cb0e
Release v1.4.6: Fix Ghostty multiple windows via IPC
nathandale Feb 24, 2026
d90b7a5
Release v1.4.7: Fix Ghostty TCC prompt and multiple windows
nathandale Feb 24, 2026
474d6ce
Release v1.4.8: Unify terminal launch path, isolate Ghostty arg diffe…
nathandale Feb 24, 2026
9ca460d
Release v1.4.9: Fix Ghostty TCC by switching to NSWorkspace
nathandale Feb 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 30 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,36 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
- The ability to open multiple, or same command(s) off one menu item. https://github.com/fitztrev/shuttle/issues/236
- The ability to add a second json.config file
- The ability to add ```[---]``` in the name of a command to add a line seperator
- Adding a new apple script which will allow running commands in the background with screen
- @philippetev Changes to iTerm applescripts to fix issues with settings in iTerm's Preferences/General
- French translations by @anivon
- @anivon localize Error parsing config message is JSON is invalid
- @blackadmin version typos in about window.
- @ChrisMoriarty add the ability to set the terminal window position and size
## [1.4.3] - 2026-02-24
### Changed
- **About Window**: Added "Fork by Nathan Dale" attribution as requested.

## [1.4.2] - 2026-02-24
### Fixed
- **Terminal Multiple Windows**: Robust fix for terminals like Ghostty where subsequent connection requests previously did nothing. Now uses `NSAppleScript` to call the terminal binary with `+open`, correctly triggering new windows in existing instances.
- **Privacy**: Bypasses "App Management" privacy prompts by delegating execution to the system script runner.
- **About Window**: Removed copyright labels entirely and made the GitHub repository link a larger, more accessible clickable button.

## [1.4.1] - 2026-02-24
### Fixed
- **Terminal Multiple Windows**: Fixed a bug where subsequent connection requests failed to open new windows in terminals like Ghostty. Now uses `/usr/bin/open` to correctly handle IPC/instance communication.
- **Copyright Attribution**: Removed "Nathan Dale" from the copyright as requested; reverted to original author Trevor Fitzgerald.
- **About Window**: Made the GitHub repository link clickable.

## [1.4.0] - 2026-02-24
### Added
- **Major Rewrite**: Rebuilt for modern macOS (12+) as a Universal Binary (arm64 + x86_64).
- **SSH Manager**: New native split-view window to manage servers and categories without editing JSON manually.
- **LAN Scanner**: Subnet scanner (GCD-based) and Bonjour/mDNS discovery for local SSH hosts.
- **Modern Terminal Support**: Native support for Ghostty, Alacritty, kitty, Warp, Rio, and Hyper.
- **Improved Terminal Launching**:
- Fixed "App Management" privacy prompts by using `NSAppleScript` instead of `osascript` sub-processes.
- Improved grouping in the Dock for Ghostty and other third-party terminals using `NSWorkspace`.
- **Per-Server Settings**: Ability to override the terminal and specify an initial directory on a per-host basis.

### Changed
- Migrated configuration to a flat `servers` + `categories` JSON structure for easier management.
- Updated "About" window with new repository links and attribution.

## [1.2.9] - 2016-10-18
### Added
Expand Down
76 changes: 76 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

**Shuttle** is a native macOS menu bar app (Objective-C/Cocoa) that acts as an SSH address book and local network scanner. It lives in the menu bar with no Dock icon. The app is a Universal Binary (arm64 + x86_64) targeting macOS 12+.

## Build

```bash
xcodebuild -scheme Shuttle -configuration Release build
```

Output lands in `~/Library/Developer/Xcode/DerivedData/Shuttle-*/Build/Products/Release/Shuttle.app`.

There is no automated test suite — testing is manual. Test fixtures are in `tests/` (sample JSON config, SSH config files).

## Architecture

All meaningful logic lives in two files:

- **`Shuttle/AppDelegate.m`** (~1,100 lines) — the entire application core:
- Config loading/watching (`~/.shuttle.json`)
- Menu bar construction
- Terminal detection via `NSWorkspace`/Launch Services
- SSH command generation and terminal launching
- LAN scanner (GCD parallel port-22 sweep + Bonjour/mDNS via `NSNetServiceBrowser`)

- **`Shuttle/ServerManagerWindowController.m`** (~400 lines) — the "SSH Manager" split-view window:
- Left sidebar: `NSOutlineView` with categories as group headers and servers as children
- Right pane: form for editing server fields
- Writes changes back to `~/.shuttle.json` and triggers a menu reload via AppDelegate

Supporting files: `AboutWindowController`, `LaunchAtLoginController`, `MainMenu.xib`, `AboutWindowController.xib`.

## Configuration Schema

The user config lives at `~/.shuttle.json`. The modern format:

```json
{
"terminal": "ghostty",
"editor": "default",
"launch_at_login": false,
"show_ssh_config_hosts": false,
"categories": ["Work", "Personal"],
"servers": [
{
"name": "My Server",
"hostname": "example.com",
"user": "admin",
"port": 22,
"identity_file": "~/.ssh/id_ed25519",
"category": "Work",
"terminal": "iterm"
}
]
}
```

The default config template is at `Shuttle/shuttle.default.json`.

## Terminal Support

Terminals are detected at runtime by querying Launch Services for bundle IDs (see `installedTerminals` in `AppDelegate.m`). Launching strategy varies by terminal:

- **Terminal.app, iTerm2**: AppleScript
- **Ghostty, Alacritty, kitty, Hyper, Rio**: `/usr/bin/open -b <bundleID> --args ...` via `NSTask`; ensures single-instance grouping and multiple window support without privacy prompts.
- **Warp**: URL scheme (`warp://action/new_tab?command=…`)

Per-server terminal overrides are respected in `openHost:`.

## LAN Scanner

`scanLAN` runs 50 concurrent GCD async probes with a 300ms timeout per IP across the local subnet. Results are merged with Bonjour (`_ssh._tcp.`) discoveries, reverse-DNS resolved, deduplicated by hostname, and sorted by last IP octet. Results are cached until the user triggers "Scan Now".
231 changes: 171 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,177 @@
# Shuttle

[![Join the chat at https://gitter.im/fitztrev/shuttle](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/fitztrev/shuttle?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

A simple shortcut menu for macOS

[http://fitztrev.github.io/shuttle/](http://fitztrev.github.io/shuttle/)

![How Shuttle works](https://raw.githubusercontent.com/fitztrev/shuttle/gh-pages/images/how-shuttle-works.gif)

**Sidenote**: *Many people ask, so here's how I have [my terminal setup](https://github.com/fitztrev/shuttle/wiki/My-Terminal-Prompt).*

## Installation

1. Download [Shuttle](http://fitztrev.github.io/shuttle/)
2. Copy to Applications

## Help
See the [Wiki](https://github.com/fitztrev/shuttle/wiki) pages.

## Roadmap

* Cloud hosting integration
* AWS, Rackspace, Digital Ocean, etc
* Using their APIs, automatically add all of your machines to the menu
* Preferences panel for easier configuration
* Update notifications
* Keyboard hotkeys
* Open menu
* Select host option within menu

## Contributors

This project was created by [Trevor Fitzgerald](https://github.com/fitztrev). I owe many thanks to the following people who have helped make Shuttle even better.

(In alphabetical order)

* [Alexis NIVON](https://github.com/anivon)
* [Alex Carter](https://github.com/blazeworx)
* [bihicheng](https://github.com/bihicheng)
* [Dave Eddy](https://github.com/bahamas10)
* [Dmitry Filimonov](https://github.com/petethepig)
* [Frank Enderle](https://github.com/fenderle)
* [Jack Weeden](https://github.com/jackbot)
* [Justin Swanson](https://github.com/geeksunny)
* [Kees Fransen](https://github.com/keesfransen)
* Marco Aurélio
* [Martin Grund](https://github.com/grundprinzip)
* [Matt Turner](https://github.com/thshdw)
* [Michael Davis](https://github.com/mpdavis)
* [Morton Fox](https://github.com/mortonfox)
* [Pluwen](https://github.com/pluwen)
* Rebecca Dominguez
* [Rui Rodrigues](https://github.com/rmrodrigues)
* [Ryan Cohen](https://github.com/imryan)
* [Stefan Jansen](https://github.com/steffex)
* Thomas Rosenstein
* [Thoro](https://github.com/Thoro)
* [Tibor Bödecs](https://github.com/tib)
* [welsonla](https://github.com/welsonla)
A macOS menu bar SSH address book. Click the icon, connect to a server. No terminal juggling, no remembering hostnames.

Built for macOS 12+ as a Universal Binary (native on Apple Silicon and Intel).

---

## What It Does

Shuttle lives in your menu bar as a small icon. Click it and you get an organized list of your SSH servers, grouped by category. Click a server and it opens a terminal session — no typing, no copy-pasting, no `~/.ssh/config` hunting.

It also scans your local network for hosts with SSH open, lets you browse and connect to them, and lets you save discoveries directly into your address book with one click.

---

## Features

### SSH Address Book
- Store servers with a display name, hostname (or `.local` name or IP), username, port, SSH key, and category
- Servers are grouped under user-defined categories (e.g. LOCAL SERVERS, REMOTE SERVERS, PRODUCTION, etc.)
- All data lives in a simple JSON file at `~/.shuttle.json` — human-readable, version-controllable, easy to share

### Manager Window
Accessible from **Settings → Manager…** in the menu bar. A native macOS split-view window modelled after Contacts.app:

- **Left sidebar** — translucent sidebar with category group headers and server rows. `+`/`−` segmented control to add or remove servers. **New Category** button to create groupings.
- **Right detail pane** — form with Name, Hostname, User, Port, SSH Key picker (auto-populated from `~/.ssh/`), Category, and Terminal override. Save with Return or the Save button. Delete with confirmation.
- Changes are written to `~/.shuttle.json` immediately and the menu bar refreshes automatically.

### Multi-Terminal Support
Shuttle detects which terminal apps are installed on your machine at launch time using macOS Launch Services — it only shows you what you actually have. Supported terminals:

| Terminal | How it opens |
|---|---|
| Terminal.app | AppleScript — runs in existing front window, no duplicate windows |
| iTerm2 | AppleScript — new window with default profile |
| Ghostty | `NSWorkspace` + `sh -c` — no App Management privacy prompts |
| Alacritty | `NSWorkspace` + `sh -c` |
| kitty | `NSWorkspace` + `sh -c` |
| Warp | URL scheme (`warp://action/new_tab?command=…`) |
| Hyper | `NSWorkspace` + `sh -c` |
| Rio | `NSWorkspace` + `sh -c` |

The global default terminal is set in `~/.shuttle.json` with the `"terminal"` key. Individual servers can override it with their own `"terminal"` field — useful when you want most servers in Terminal.app but specific ones in Ghostty.

### Per-Server SSH Key
Each server can specify an SSH identity file. The key is picked from a dropdown populated from your `~/.ssh/` directory — only real private key files are shown (`.pub` files, `known_hosts`, `config`, and `authorized_keys` are excluded). The path is stored and expanded correctly — no tilde issues.

### LAN SSH Scanner
Shuttle can scan your local `/24` subnet for hosts with port 22 open:

- Runs an async parallel sweep using GCD with up to 50 concurrent probes (300ms timeout per host)
- Simultaneously runs an `NSNetServiceBrowser` search for `_ssh._tcp.` Bonjour/mDNS services — this finds `.local` hostnames (Macs, Raspberry Pis running `avahi-daemon`, NAS devices, etc.) without needing an IP sweep
- After the port scan, each discovered IP gets a reverse DNS lookup (`getnameinfo`) to resolve its hostname
- Results from both sources are merged and deduplicated by hostname
- Displayed sorted by last octet of IP address (`.1`, `.2` … `.254`)
- Bonjour-only entries (hostname known, IP not yet resolved) appear at the bottom

Each discovered host shows as a submenu with two options:
- **Connect** — opens an SSH session immediately
- **Save to Address Book…** — prompts for a display name and category, then writes the server to `~/.shuttle.json`

The submenu also shows how long ago the scan ran and a **Scan Now** option to re-scan on demand. The first scan triggers automatically when you open the menu for the first time.

### Import / Export
- **Settings → Import** — choose a `.json` file to replace your current config. The current config is backed up first; if the import fails, it is automatically restored.
- **Settings → Export** — save your current `~/.shuttle.json` to any location with a file picker. Defaults to `shuttle.json`.

---

## Configuration

Shuttle uses `~/.shuttle.json`. On first launch, a default file is created automatically.

### Full example

```json
{
"terminal": "ghostty",
"editor": "default",
"launch_at_login": false,
"show_ssh_config_hosts": false,
"categories": [
"LOCAL SERVERS",
"REMOTE SERVERS",
"PRODUCTION"
],
"servers": [
{
"name": "Web Dev",
"hostname": "wp-dev.local",
"user": "nathandale",
"identity_file": "~/.ssh/id_ed25519",
"category": "LOCAL SERVERS",
"terminal": "ghostty"
},
{
"name": "Production DB",
"hostname": "db1.example.com",
"user": "deploy",
"port": 2222,
"identity_file": "~/.ssh/prod_key",
"category": "PRODUCTION"
}
]
}
```

### Keys

| Key | Values | Description |
|---|---|---|
| `terminal` | `terminal`, `iterm`, `ghostty`, `alacritty`, `kitty`, `warp`, `hyper`, `rio` | Default terminal for all connections |
| `editor` | `default`, `nano`, `vi`, etc. | Editor used by Settings → Edit |
| `launch_at_login` | `true` / `false` | Start Shuttle when you log in |
| `show_ssh_config_hosts` | `true` / `false` | Show hosts from `~/.ssh/config` in the menu (legacy mode) |
| `categories` | array of strings | Category names, in display order |
| `servers` | array of server objects | Your address book entries |

### Server object keys

| Key | Required | Description |
|---|---|---|
| `name` | yes | Display name shown in the menu |
| `hostname` | yes | Hostname, `.local` mDNS name, or IP address |
| `user` | no | SSH username |
| `port` | no | SSH port (omit for default 22) |
| `identity_file` | no | Path to private key, e.g. `~/.ssh/id_ed25519` |
| `category` | yes | Must match an entry in `categories` |
| `terminal` | no | Per-server terminal override — takes precedence over global setting |

---

## Building from Source

Requires Xcode 14+ on macOS 12 Monterey or later.

```bash
git clone https://github.com/nathandale/shuttle.git
cd shuttle
xcodebuild -scheme Shuttle -configuration Release build
```

The built app lands at:
```
~/Library/Developer/Xcode/DerivedData/Shuttle-*/Build/Products/Release/Shuttle.app
```

The app is a Universal Binary — runs natively on both Apple Silicon (arm64) and Intel (x86_64). Deployment target is macOS 12.0.

---

## How It Differs from the Original

This fork of [fitztrev/shuttle](https://github.com/fitztrev/shuttle) is a significant rewrite:

| | Original | This Fork |
|---|---|---|
| Architecture | x86_64 only | Universal Binary (arm64 + x86_64) |
| macOS target | 10.8 | 12.0 |
| Terminal launching | AppleScript `.scpt` files | `NSWorkspace` + AppleScript one-liners |
| Terminal selection | Hardcoded | Detected at runtime via Launch Services |
| Config format | Recursive `hosts` tree | Flat `servers` + `categories` arrays |
| Server management | Hand-edit JSON only | Native Manager window |
| LAN discovery | None | Bonjour/mDNS + port-22 sweep + reverse DNS |
| SSH key selection | Manual in JSON | Dropdown from `~/.ssh/` |
| Per-server terminal | Not supported | Supported |

---

## Credits

Shuttle was inspired by [SSHMenu](http://sshmenu.sourceforge.net/), the GNOME applet for Linux.
Originally created by [Trevor Fitzgerald](https://github.com/fitztrev) and contributors.
Forked and rewritten by Nathan Dale with Claude Code.

I also looked to projects such as [MLBMenu](https://github.com/markolson/MLB-Menu) and [QuickSmileText](https://github.com/scturtle/QuickSmileText) for direction on building a Cocoa app for the status bar.
Inspired by [SSHMenu](http://sshmenu.sourceforge.net/) for Linux.
Loading