Skip to content

Repository files navigation

Portico

English | 简体中文

Cross-platform desktop SSH terminal with a first-class bridge for pasting local images into remote AI coding CLIs (Claude Code, Codex).

Copy a screenshot on your Mac → Portico uploads it over your own SSH/SFTP session → injects a provider-aware path prompt into the remote terminal. Cloudless by default: every byte travels over your SSH connection.

Portico — sessions, terminal, image shelf, and port forwards

Version0.1.1 (MVP)
StackElectron · React · xterm.js · ssh2 · node-pty
LicenseMIT
RepoSivanCola/Portico

Why Portico?

Remote AI CLIs run on a server; your screenshots live on the laptop. Portico closes that gap without a third-party image host:

  1. Stage images from the clipboard (⌘/Ctrl+Shift+V) or multi-file drop.
  2. Commit from the Image Shelf — SFTP upload to a content-addressed path.
  3. Inject the right text for Claude / Codex / shell (auto-detected, overridable).

Beyond the image bridge, Portico is a full multi-tab terminal: local shell or SSH, port forwards, session restore, and remote clipboard sync.

Features

AreaWhat you get
Image → remote AIMulti-image stage/commit, compress oversized images, Claude / Codex / shell adapters
SessionsLocal PTY or SSH; multi-tab rail; optional session restore on launch
SSHPassword / key / agent; ~/.ssh/config friendly; host-key check against known_hosts
tmuxAttach-if-exists or always; re-attach after restore
Port forwardingLocal (-L), remote (-R), SOCKS5 (-D); traffic counters; one-click forward for sniffed localhost URLs
ClipboardOSC 52 remote → Mac clipboard; optional tmux set-clipboard assist
UICommand palette, image shelf, settings center, EN / 简体中文
ModesTerminal only — plain terminal (image bridge, PF, provider detect off)
UpdatesDual channel (stable / beta); GitHub Releases feed via electron-updater

Image handoff model (MVP)

The reliable baseline is file-path-based: the remote AI reads

~/.portico/blobs/<sha256>.<ext>

(or ~/.portico-beta/blobs/... on the beta channel). Native clipboard-image simulation and a remote portico-agent helper are out of scope for this version.

Provider adapters (src/shared/adapters.ts):

ProviderInteractive sessionCommand mode
ClaudeAnalyze this image: <path>same
Codex<prompt>: <path> (path fallback)codex -i <path> "<prompt>"
Shell# image uploaded to <path>same

Detection is heuristic (banner / process name) and can be overridden from the top-bar provider pills.

Install

Download a published build from:

https://github.com/SivanCola/Portico/releases
PlatformArtifact
macOS Apple SiliconPortico-*-arm64.dmg / -arm64-mac.zip
macOS IntelPortico-*.dmg / -mac.zip
WindowsPortico.Setup.*.exe
LinuxPortico-*.AppImage, portico_*_amd64.deb

Or build from source below.

macOS: “cannot be opened” / damaged / Gatekeeper?

Current releases are not Apple Developer ID–signed or notarized, so macOS may block the app after download (“Apple could not verify Portico…” / “damaged”).

If you installed from GitHub Releases into /Applications, quit Portico, then run:

sudo xattr -rd com.apple.quarantine /Applications/Portico.app

For Portico Beta:

sudo xattr -rd com.apple.quarantine "/Applications/Portico Beta.app"

Then open the app again (or: right-click the app → Open, or System Settings → Privacy & Security → Open Anyway).

Develop

Requirements: Node.js 20+, npm.

npm install
npm run dev # electron-vite — stable channel app
npm run dev:beta # same, beta channel identity
npm run build # main + preload + renderer → out/ (stable)
npm run build:beta
npm run typecheck # tsc for node + web
npm test# vitest

Packaging (local)

npm run dist:stable # → dist/stable
npm run dist:beta # → dist/beta# or unpackaged dir only:
npm run pack:stable
npm run pack:beta

Key bindings

ShortcutAction
⌘/Ctrl + Shift + PCommand palette
⌘/Ctrl + Shift + VStage clipboard image(s) locally (no upload yet; repeat for more)
Enter in Image Shelf commit barUpload staged images, inject paths, submit to Claude/Codex
⌘/Ctrl + \Toggle tool sidebar (image shelf + port forwards)

More actions live in the command palette (updates, clear remote cache, tmux, open forward in browser, …).

Port forwarding

Enable under Settings → Port forwarding. Rules live in the right-hand tool sidebar.

ModeMeaningTypical use
Local (−L)This machine listens → tunnel to host:port on the serverClaude Code / Vite / Next preview on the remote host
Remote (−R)Server listens → tunnel back to a service on this machineWebhooks, local agents
SOCKS (−D)This machine runs a SOCKS5 proxy over SSHBrowse / curl as if from the remote host

SOCKS example — dynamic forward on local port 1080:

curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.me
# or system / browser proxy: socks5://127.0.0.1:1080

Each rule shows live traffic counters (↑ local→remote, ↓ remote→local). Click a row counter to reset that rule, or Reset traffic for the whole session.

Remote dev server workflow

  1. Connect over SSH and start a server (e.g. Claude Code opens localhost:5173 on the host).
  2. Portico sniffs terminal output for URLs like http://localhost:5173 and offers one-click Forward.
  3. Or add a rule manually: local port → 127.0.0.1 : remote port (same port both sides by default).
  4. Click the browser icon on a listening local forward, or use the palette Open port forward in browser.

Rules are persisted per session tab (with host/tmux restore), survive intentional disconnect (shown as stopped), and rebind on reconnect. Options:

  • Same port both sides / Auto local port when the preferred local port is busy
  • Pause / resume a single rule without deleting it
  • Advanced: label, bind address (127.0.0.1 default; 0.0.0.0 exposes LAN — use carefully)
  • Cross-tab collision detection for the same local listen port

Disable under Settings → Port forwarding, or use Terminal only to turn off image bridge + port forwards + provider detect together.

Session restore

Portico saves the left-rail tab layout to userData/sessions.json (no passwords). On launch it can:

  • reopen tabs and auto-reconnect SSH (key or agent)
  • tmux attach to each tab’s last tmux session
  • restore that tab’s port-forward rules

Toggle under Settings → Restore sessions on launch.

Storage

Remote blobs are content-addressed:

~/.portico/blobs/<sha256>.<ext> # stable
~/.portico-beta/blobs/<sha256>.<ext> # beta

Re-pasting the same image is a no-op. Clear Remote Portico Cache in the command palette deletes them. Soft cap: 8 MiB per image (recompress / downscale when oversized); up to 20 images per stage.

Architecture

src/
shared/ types, IPC contract, constants, adapters, hashing (env-agnostic, unit-tested)
main/ Electron main: SSH/SFTP, local PTY, clipboard, blob upload, port forwards, updates
preload/ contextBridge: typed window.portico API
renderer/src/ React + xterm.js: sessions, terminal, image shelf, settings, i18n

Release channels

Channel is selected at build time via PORTICO_RELEASE_CHANNEL (stable is default):

ChannelApp nameappIdRemote blob dirUpdate feedOutput dir
stablePorticocom.portico.app~/.portico/blobslatestdist/stable
betaPortico Betacom.portico.app.beta~/.portico-beta/blobsbetadist/beta

Stable and Beta are fully isolated (app identity, userData / localStorage, remote caches) so both can be installed side by side.

Auto-updates

Packaged builds check GitHub Releases for SivanCola/Portico. Beta auto-downloads new prereleases and prompts to restart; stable only receives latest releases. In development, update checks report “updates disabled in dev.” Palette: Check for Updates / Restart to Install Update.

Releasing

Tag-driven CI (.github/workflows/release.yml):

TagChannelGitHub Release
vX.Y.Z (e.g. v0.1.1)stablenormal release → latest feed
vX.Y.Z-beta.N (e.g. v0.1.1-beta.1)betaprereleasebeta feed

The workflow requires package.jsonversion to equal the tag (minus v) before building, so updater metadata never carries the wrong version.

# example: cut a stable release after bumping package.json to 0.1.1
git tag v0.1.1
git push origin v0.1.1

Test plan mapping

ConcernWhere
Clipboard stage (bitmap + files) → commitsrc/main/clipboard.ts, portico-controller.ts (stage / commitStaged)
Spaces / non-ASCII pathssrc/shared/hash.test.ts (shellQuote, blobPath)
Reconnect-safe teardownsrc/main/ssh-session.ts (disconnect)
Claude / Codex adapterssrc/shared/adapters.test.ts
Oversized-image compress / rejectsrc/main/clipboard.ts (normalizeNative) + blob-uploader.ts
Normal text pasteterminal onDatasendInput
Port forwards / SOCKSsrc/main/port-forwarder.ts, socks5.ts
Session restoresrc/main/session-store.ts
Host keyssrc/main/host-key.ts
OSC 52 clipboardsrc/main/osc52.ts

License

MIT.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages