Repository files navigation

Wrapper

One command to make any terminal you open reachable from your phone or another device.

Wrapper transparently wraps every interactive shell session you open (zsh, bash, or fish) so an authenticated device can mirror it on demand. The wrapping itself is invisible: your dotfiles, prompt, plugins, and history all behave exactly as before.

A session never leaves your machine until you decide to share it. Inside a wrapped shell, Ctrl+\ s opens a relay tunnel and Ctrl+\ u closes it again. A second device then attaches to that session and sees the live terminal.

How it works in one minute

  1. You open a terminal. Your rc file runs wrapper shell-host, which spawns your real shell inside a pseudo-terminal (PTY) and starts a tiny local WebSocket server bound to 127.0.0.1. Nothing is exposed yet.
  2. From the same machine, wrapper attach connects to that local server and mirrors the session. The transport stays on loopback and does not need the relay or Pro; released builds still authorize the signed-in session owner.
  3. When you press Ctrl+\ s, the CLI asks the Convex backend for a short-lived host ticket, connects to the relay on Fly.io, marks the session shared, and prints a secret share code. You join your own devices with wrapper attach --relay --id <id>; anyone else enters the code in a hidden prompt, so knowing the session id alone is not enough.
  4. By default viewer input prefers a direct WebRTC data channel for lower latency. The host keeps relay output available for fallback and mixed viewers, and you can force relay-only mode with WRAPPER_P2P=0.

Repository layout

This is a Bun and Turborepo monorepo.

apps/
cli/ Wrapper CLI: shell wrapping, session registry, local + relay attach, device auth, default-on P2P
relay/ Relay service: authenticated WebSocket routing for shared sessions, deployed on Fly.io
web/ Next.js app on Vercel: landing page, device-login approval, onboarding, Pro upgrade
docs/ Mintlify documentation source, published at docs.wrapper.sh
mobile/ Git submodule pointer to the native SwiftUI iPhone/iPad viewer repository
packages/
protocol/ Zod wire schema shared by every wrapper component (JSON frames + WebRTC signal)
backend/ Convex backend: Better Auth, session lifecycle, relay tickets, onboarding, billing
terminal/ Bun-native PTY layer using the wrapper-pty-helper binary
logger/ Consola logging plus opt-in PostHog telemetry
typescript-config/ Single-source tsconfig presets
tools/
pty-helper/ C source and Makefile for the wrapper-pty-helper binary shipped with the CLI

The CLI is the heart of the project. See apps/cli/README.md for how the wrapping flow works and what every command does. The transport layer (relay WebSocket and the default direct WebRTC path) is documented in apps/cli/transport/README.md.

Where to read next

TopicDocument
CLI commands, keystrokes, env varsapps/cli/README.md
Relay + direct P2P transportsapps/cli/transport/README.md
Relay service and Fly deployapps/relay/README.md
Convex backend (auth, sessions, tickets, billing)packages/backend/README.md
Wire protocolpackages/protocol/README.md
PTY internalspackages/terminal/README.md
Logging and telemetrypackages/logger/README.md
Dev and prod environments, deploy automationENVIRONMENTS.md
Production operations, incidents, backups, SLOsOPERATIONS.md
Public documentationdocs.wrapper.sh
Documentation sourceapps/docs (run bun run --cwd apps/docs dev)

https://wrapper.sh is the canonical production website, auth, installer, legal, and support origin. https://docs.wrapper.sh is the canonical public documentation origin.

Status

The CLI core, the Convex auth and backend, the relay transport, and the web onboarding flow are implemented in this repository. Sharing attempts a direct WebRTC P2P data path by default, with the relay kept online for signaling and automatic fallback (WRAPPER_P2P=0 forces relay-only mode). The terminal title shows the role, session, and active transport without reserving a screen row. The separate apps/mobile submodule now contains a Simulator-ready native iPhone/iPad viewer MVP: device authorization, owner and guest join flows, SwiftTerm rendering, relay transport, native WebRTC, and adaptive navigation. It uses Swift 6.0 language mode with complete strict concurrency on Xcode 26. CLI v0.1.4 is published on GitHub Releases. Signed-device validation and App Store review remain pre-release work.

The active focus is operational hardening:

  • rotate HOMEBREW_TAP_TOKEN so release workflow can keep the tap in sync
  • keep dependency audit, lint, format, types, auth, and relay checks green
  • complete the TestFlight public beta for the iOS viewer, then App Store review
  • finish the remaining hosted-ops items in OPERATIONS.md

Local development

Requirements:

  • Bun 1.3.5 or newer for runtime, package management, and bundling.
  • A POSIX system (macOS or Linux). On Windows, run Wrapper inside WSL.

Environment templates are included:

  • .env.example (shared)
  • apps/cli/.env.example
  • apps/relay/.env.example
  • packages/backend/.env.example

Clone with the mobile submodule:

git clone --recurse-submodules https://github.com/heycupola/wrapper.git
cd wrapper
# For an existing clone:
git submodule update --init --recursive

The mobile repository has its own git history. The parent repository tracks only its commit pointer. For mobile development on macOS, install the prerequisites in apps/mobile/README.md, then generate the ignored Xcode project:

make -C apps/mobile bootstrap
bun install # one-time
bun run check-types # typecheck every package
bun run lint # oxlint
bun run format # oxfmt --write# try the wrapping flow locallycd apps/cli && NODE_ENV=development bun run index.ts shell-host

NODE_ENV=development moves every on-disk path into a wrapper-dev namespace under XDG state (or %APPDATA%\wrapper-dev\ on Windows), points the relay and auth URLs at localhost, mirrors logs to stderr, and writes rc-file patches into a throwaway directory. A developer running the CLI locally can never corrupt a real installation's registry, logs, or rc files.

Setting CI to any value disables telemetry and console output. For the full list of CLI environment variables, see apps/cli/README.md.

Tooling

  • Bun for runtime, package management, and bundling.
  • Turborepo for task orchestration and caching.
  • oxlint and oxfmt for linting and formatting (no ESLint, no Prettier).
  • Lefthook for git hooks (pre-commit oxfmt and oxlint, pre-push checks).
  • Catalog dependencies so shared packages such as react, next, zod, and typescript use a single pinned version across the workspace.
  • bun run audit for advisory checks. Werift's abandoned ip dependency is replaced by the tested packages/ip compatibility shim backed by ipaddr.js. The audit script ignores only Bun's name-based false positive for that private shim.

License

This repository is MIT © 2026 Cupola Labs, LLC.

The iOS app in apps/mobile is a separate repository and is not covered by this MIT license.

Security issues must be reported privately according to SECURITY.md. Hosted-service policies are published at wrapper.sh/privacy-policy and wrapper.sh/terms-of-service.

About

A terminal layer that connects and orchestrates AI tools across your devices.

Resources

Security policy

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Wrapper

One command to make any terminal you open reachable from your phone or another device.

Wrapper transparently wraps every interactive shell session you open (zsh, bash, or fish) so an authenticated device can mirror it on demand. The wrapping itself is invisible: your dotfiles, prompt, plugins, and history all behave exactly as before.

A session never leaves your machine until you decide to share it. Inside a wrapped shell, Ctrl+\ s opens a relay tunnel and Ctrl+\ u closes it again. A second device then attaches to that session and sees the live terminal.

How it works in one minute

  1. You open a terminal. Your rc file runs wrapper shell-host, which spawns your real shell inside a pseudo-terminal (PTY) and starts a tiny local WebSocket server bound to 127.0.0.1. Nothing is exposed yet.
  2. From the same machine, wrapper attach connects to that local server and mirrors the session. The transport stays on loopback and does not need the relay or Pro; released builds still authorize the signed-in session owner.
  3. When you press Ctrl+\ s, the CLI asks the Convex backend for a short-lived host ticket, connects to the relay on Fly.io, marks the session shared, and prints a secret share code. You join your own devices with wrapper attach --relay --id <id>; anyone else enters the code in a hidden prompt, so knowing the session id alone is not enough.
  4. By default viewer input prefers a direct WebRTC data channel for lower latency. The host keeps relay output available for fallback and mixed viewers, and you can force relay-only mode with WRAPPER_P2P=0.

Repository layout

This is a Bun and Turborepo monorepo.

apps/
cli/ Wrapper CLI: shell wrapping, session registry, local + relay attach, device auth, default-on P2P
relay/ Relay service: authenticated WebSocket routing for shared sessions, deployed on Fly.io
web/ Next.js app on Vercel: landing page, device-login approval, onboarding, Pro upgrade
docs/ Mintlify documentation source, published at docs.wrapper.sh
mobile/ Git submodule pointer to the native SwiftUI iPhone/iPad viewer repository
packages/
protocol/ Zod wire schema shared by every wrapper component (JSON frames + WebRTC signal)
backend/ Convex backend: Better Auth, session lifecycle, relay tickets, onboarding, billing
terminal/ Bun-native PTY layer using the wrapper-pty-helper binary
logger/ Consola logging plus opt-in PostHog telemetry
typescript-config/ Single-source tsconfig presets
tools/
pty-helper/ C source and Makefile for the wrapper-pty-helper binary shipped with the CLI

The CLI is the heart of the project. See apps/cli/README.md for how the wrapping flow works and what every command does. The transport layer (relay WebSocket and the default direct WebRTC path) is documented in apps/cli/transport/README.md.

Where to read next

TopicDocument
CLI commands, keystrokes, env varsapps/cli/README.md
Relay + direct P2P transportsapps/cli/transport/README.md
Relay service and Fly deployapps/relay/README.md
Convex backend (auth, sessions, tickets, billing)packages/backend/README.md
Wire protocolpackages/protocol/README.md
PTY internalspackages/terminal/README.md
Logging and telemetrypackages/logger/README.md
Dev and prod environments, deploy automationENVIRONMENTS.md
Production operations, incidents, backups, SLOsOPERATIONS.md
Public documentationdocs.wrapper.sh
Documentation sourceapps/docs (run bun run --cwd apps/docs dev)

https://wrapper.sh is the canonical production website, auth, installer, legal, and support origin. https://docs.wrapper.sh is the canonical public documentation origin.

Status

The CLI core, the Convex auth and backend, the relay transport, and the web onboarding flow are implemented in this repository. Sharing attempts a direct WebRTC P2P data path by default, with the relay kept online for signaling and automatic fallback (WRAPPER_P2P=0 forces relay-only mode). The terminal title shows the role, session, and active transport without reserving a screen row. The separate apps/mobile submodule now contains a Simulator-ready native iPhone/iPad viewer MVP: device authorization, owner and guest join flows, SwiftTerm rendering, relay transport, native WebRTC, and adaptive navigation. It uses Swift 6.0 language mode with complete strict concurrency on Xcode 26. CLI v0.1.4 is published on GitHub Releases. Signed-device validation and App Store review remain pre-release work.

The active focus is operational hardening:

  • rotate HOMEBREW_TAP_TOKEN so release workflow can keep the tap in sync
  • keep dependency audit, lint, format, types, auth, and relay checks green
  • complete the TestFlight public beta for the iOS viewer, then App Store review
  • finish the remaining hosted-ops items in OPERATIONS.md

Local development

Requirements:

  • Bun 1.3.5 or newer for runtime, package management, and bundling.
  • A POSIX system (macOS or Linux). On Windows, run Wrapper inside WSL.

Environment templates are included:

  • .env.example (shared)
  • apps/cli/.env.example
  • apps/relay/.env.example
  • packages/backend/.env.example

Clone with the mobile submodule:

git clone --recurse-submodules https://github.com/heycupola/wrapper.git
cd wrapper
# For an existing clone:
git submodule update --init --recursive

The mobile repository has its own git history. The parent repository tracks only its commit pointer. For mobile development on macOS, install the prerequisites in apps/mobile/README.md, then generate the ignored Xcode project:

make -C apps/mobile bootstrap
bun install # one-time
bun run check-types # typecheck every package
bun run lint # oxlint
bun run format # oxfmt --write# try the wrapping flow locallycd apps/cli && NODE_ENV=development bun run index.ts shell-host

NODE_ENV=development moves every on-disk path into a wrapper-dev namespace under XDG state (or %APPDATA%\wrapper-dev\ on Windows), points the relay and auth URLs at localhost, mirrors logs to stderr, and writes rc-file patches into a throwaway directory. A developer running the CLI locally can never corrupt a real installation's registry, logs, or rc files.

Setting CI to any value disables telemetry and console output. For the full list of CLI environment variables, see apps/cli/README.md.

Tooling

  • Bun for runtime, package management, and bundling.
  • Turborepo for task orchestration and caching.
  • oxlint and oxfmt for linting and formatting (no ESLint, no Prettier).
  • Lefthook for git hooks (pre-commit oxfmt and oxlint, pre-push checks).
  • Catalog dependencies so shared packages such as react, next, zod, and typescript use a single pinned version across the workspace.
  • bun run audit for advisory checks. Werift's abandoned ip dependency is replaced by the tested packages/ip compatibility shim backed by ipaddr.js. The audit script ignores only Bun's name-based false positive for that private shim.

License

This repository is MIT © 2026 Cupola Labs, LLC.

The iOS app in apps/mobile is a separate repository and is not covered by this MIT license.

Security issues must be reported privately according to SECURITY.md. Hosted-service policies are published at wrapper.sh/privacy-policy and wrapper.sh/terms-of-service.

About

A terminal layer that connects and orchestrates AI tools across your devices.

Resources

Security policy

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Wrapper

One command to make any terminal you open reachable from your phone or another device.

Wrapper transparently wraps every interactive shell session you open (zsh, bash, or fish) so an authenticated device can mirror it on demand. The wrapping itself is invisible: your dotfiles, prompt, plugins, and history all behave exactly as before.

A session never leaves your machine until you decide to share it. Inside a wrapped shell, Ctrl+\ s opens a relay tunnel and Ctrl+\ u closes it again. A second device then attaches to that session and sees the live terminal.

How it works in one minute

  1. You open a terminal. Your rc file runs wrapper shell-host, which spawns your real shell inside a pseudo-terminal (PTY) and starts a tiny local WebSocket server bound to 127.0.0.1. Nothing is exposed yet.
  2. From the same machine, wrapper attach connects to that local server and mirrors the session. The transport stays on loopback and does not need the relay or Pro; released builds still authorize the signed-in session owner.
  3. When you press Ctrl+\ s, the CLI asks the Convex backend for a short-lived host ticket, connects to the relay on Fly.io, marks the session shared, and prints a secret share code. You join your own devices with wrapper attach --relay --id <id>; anyone else enters the code in a hidden prompt, so knowing the session id alone is not enough.
  4. By default viewer input prefers a direct WebRTC data channel for lower latency. The host keeps relay output available for fallback and mixed viewers, and you can force relay-only mode with WRAPPER_P2P=0.

Repository layout

This is a Bun and Turborepo monorepo.

apps/
cli/ Wrapper CLI: shell wrapping, session registry, local + relay attach, device auth, default-on P2P
relay/ Relay service: authenticated WebSocket routing for shared sessions, deployed on Fly.io
web/ Next.js app on Vercel: landing page, device-login approval, onboarding, Pro upgrade
docs/ Mintlify documentation source, published at docs.wrapper.sh
mobile/ Git submodule pointer to the native SwiftUI iPhone/iPad viewer repository
packages/
protocol/ Zod wire schema shared by every wrapper component (JSON frames + WebRTC signal)
backend/ Convex backend: Better Auth, session lifecycle, relay tickets, onboarding, billing
terminal/ Bun-native PTY layer using the wrapper-pty-helper binary
logger/ Consola logging plus opt-in PostHog telemetry
typescript-config/ Single-source tsconfig presets
tools/
pty-helper/ C source and Makefile for the wrapper-pty-helper binary shipped with the CLI

The CLI is the heart of the project. See apps/cli/README.md for how the wrapping flow works and what every command does. The transport layer (relay WebSocket and the default direct WebRTC path) is documented in apps/cli/transport/README.md.

Where to read next

TopicDocument
CLI commands, keystrokes, env varsapps/cli/README.md
Relay + direct P2P transportsapps/cli/transport/README.md
Relay service and Fly deployapps/relay/README.md
Convex backend (auth, sessions, tickets, billing)packages/backend/README.md
Wire protocolpackages/protocol/README.md
PTY internalspackages/terminal/README.md
Logging and telemetrypackages/logger/README.md
Dev and prod environments, deploy automationENVIRONMENTS.md
Production operations, incidents, backups, SLOsOPERATIONS.md
Public documentationdocs.wrapper.sh
Documentation sourceapps/docs (run bun run --cwd apps/docs dev)

https://wrapper.sh is the canonical production website, auth, installer, legal, and support origin. https://docs.wrapper.sh is the canonical public documentation origin.

Status

The CLI core, the Convex auth and backend, the relay transport, and the web onboarding flow are implemented in this repository. Sharing attempts a direct WebRTC P2P data path by default, with the relay kept online for signaling and automatic fallback (WRAPPER_P2P=0 forces relay-only mode). The terminal title shows the role, session, and active transport without reserving a screen row. The separate apps/mobile submodule now contains a Simulator-ready native iPhone/iPad viewer MVP: device authorization, owner and guest join flows, SwiftTerm rendering, relay transport, native WebRTC, and adaptive navigation. It uses Swift 6.0 language mode with complete strict concurrency on Xcode 26. CLI v0.1.4 is published on GitHub Releases. Signed-device validation and App Store review remain pre-release work.

The active focus is operational hardening:

  • rotate HOMEBREW_TAP_TOKEN so release workflow can keep the tap in sync
  • keep dependency audit, lint, format, types, auth, and relay checks green
  • complete the TestFlight public beta for the iOS viewer, then App Store review
  • finish the remaining hosted-ops items in OPERATIONS.md

Local development

Requirements:

  • Bun 1.3.5 or newer for runtime, package management, and bundling.
  • A POSIX system (macOS or Linux). On Windows, run Wrapper inside WSL.

Environment templates are included:

  • .env.example (shared)
  • apps/cli/.env.example
  • apps/relay/.env.example
  • packages/backend/.env.example

Clone with the mobile submodule:

git clone --recurse-submodules https://github.com/heycupola/wrapper.git
cd wrapper
# For an existing clone:
git submodule update --init --recursive

The mobile repository has its own git history. The parent repository tracks only its commit pointer. For mobile development on macOS, install the prerequisites in apps/mobile/README.md, then generate the ignored Xcode project:

make -C apps/mobile bootstrap
bun install # one-time
bun run check-types # typecheck every package
bun run lint # oxlint
bun run format # oxfmt --write# try the wrapping flow locallycd apps/cli && NODE_ENV=development bun run index.ts shell-host

NODE_ENV=development moves every on-disk path into a wrapper-dev namespace under XDG state (or %APPDATA%\wrapper-dev\ on Windows), points the relay and auth URLs at localhost, mirrors logs to stderr, and writes rc-file patches into a throwaway directory. A developer running the CLI locally can never corrupt a real installation's registry, logs, or rc files.

Setting CI to any value disables telemetry and console output. For the full list of CLI environment variables, see apps/cli/README.md.

Tooling

  • Bun for runtime, package management, and bundling.
  • Turborepo for task orchestration and caching.
  • oxlint and oxfmt for linting and formatting (no ESLint, no Prettier).
  • Lefthook for git hooks (pre-commit oxfmt and oxlint, pre-push checks).
  • Catalog dependencies so shared packages such as react, next, zod, and typescript use a single pinned version across the workspace.
  • bun run audit for advisory checks. Werift's abandoned ip dependency is replaced by the tested packages/ip compatibility shim backed by ipaddr.js. The audit script ignores only Bun's name-based false positive for that private shim.

License

This repository is MIT © 2026 Cupola Labs, LLC.

The iOS app in apps/mobile is a separate repository and is not covered by this MIT license.

Security issues must be reported privately according to SECURITY.md. Hosted-service policies are published at wrapper.sh/privacy-policy and wrapper.sh/terms-of-service.

About

A terminal layer that connects and orchestrates AI tools across your devices.

Resources

Security policy

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Wrapper

One command to make any terminal you open reachable from your phone or another device.

Wrapper transparently wraps every interactive shell session you open (zsh, bash, or fish) so an authenticated device can mirror it on demand. The wrapping itself is invisible: your dotfiles, prompt, plugins, and history all behave exactly as before.

A session never leaves your machine until you decide to share it. Inside a wrapped shell, Ctrl+\ s opens a relay tunnel and Ctrl+\ u closes it again. A second device then attaches to that session and sees the live terminal.

How it works in one minute

  1. You open a terminal. Your rc file runs wrapper shell-host, which spawns your real shell inside a pseudo-terminal (PTY) and starts a tiny local WebSocket server bound to 127.0.0.1. Nothing is exposed yet.
  2. From the same machine, wrapper attach connects to that local server and mirrors the session. The transport stays on loopback and does not need the relay or Pro; released builds still authorize the signed-in session owner.
  3. When you press Ctrl+\ s, the CLI asks the Convex backend for a short-lived host ticket, connects to the relay on Fly.io, marks the session shared, and prints a secret share code. You join your own devices with wrapper attach --relay --id <id>; anyone else enters the code in a hidden prompt, so knowing the session id alone is not enough.
  4. By default viewer input prefers a direct WebRTC data channel for lower latency. The host keeps relay output available for fallback and mixed viewers, and you can force relay-only mode with WRAPPER_P2P=0.

Repository layout

This is a Bun and Turborepo monorepo.

apps/
cli/ Wrapper CLI: shell wrapping, session registry, local + relay attach, device auth, default-on P2P
relay/ Relay service: authenticated WebSocket routing for shared sessions, deployed on Fly.io
web/ Next.js app on Vercel: landing page, device-login approval, onboarding, Pro upgrade
docs/ Mintlify documentation source, published at docs.wrapper.sh
mobile/ Git submodule pointer to the native SwiftUI iPhone/iPad viewer repository
packages/
protocol/ Zod wire schema shared by every wrapper component (JSON frames + WebRTC signal)
backend/ Convex backend: Better Auth, session lifecycle, relay tickets, onboarding, billing
terminal/ Bun-native PTY layer using the wrapper-pty-helper binary
logger/ Consola logging plus opt-in PostHog telemetry
typescript-config/ Single-source tsconfig presets
tools/
pty-helper/ C source and Makefile for the wrapper-pty-helper binary shipped with the CLI

The CLI is the heart of the project. See apps/cli/README.md for how the wrapping flow works and what every command does. The transport layer (relay WebSocket and the default direct WebRTC path) is documented in apps/cli/transport/README.md.

Where to read next

TopicDocument
CLI commands, keystrokes, env varsapps/cli/README.md
Relay + direct P2P transportsapps/cli/transport/README.md
Relay service and Fly deployapps/relay/README.md
Convex backend (auth, sessions, tickets, billing)packages/backend/README.md
Wire protocolpackages/protocol/README.md
PTY internalspackages/terminal/README.md
Logging and telemetrypackages/logger/README.md
Dev and prod environments, deploy automationENVIRONMENTS.md
Production operations, incidents, backups, SLOsOPERATIONS.md
Public documentationdocs.wrapper.sh
Documentation sourceapps/docs (run bun run --cwd apps/docs dev)

https://wrapper.sh is the canonical production website, auth, installer, legal, and support origin. https://docs.wrapper.sh is the canonical public documentation origin.

Status

The CLI core, the Convex auth and backend, the relay transport, and the web onboarding flow are implemented in this repository. Sharing attempts a direct WebRTC P2P data path by default, with the relay kept online for signaling and automatic fallback (WRAPPER_P2P=0 forces relay-only mode). The terminal title shows the role, session, and active transport without reserving a screen row. The separate apps/mobile submodule now contains a Simulator-ready native iPhone/iPad viewer MVP: device authorization, owner and guest join flows, SwiftTerm rendering, relay transport, native WebRTC, and adaptive navigation. It uses Swift 6.0 language mode with complete strict concurrency on Xcode 26. CLI v0.1.4 is published on GitHub Releases. Signed-device validation and App Store review remain pre-release work.

The active focus is operational hardening:

  • rotate HOMEBREW_TAP_TOKEN so release workflow can keep the tap in sync
  • keep dependency audit, lint, format, types, auth, and relay checks green
  • complete the TestFlight public beta for the iOS viewer, then App Store review
  • finish the remaining hosted-ops items in OPERATIONS.md

Local development

Requirements:

  • Bun 1.3.5 or newer for runtime, package management, and bundling.
  • A POSIX system (macOS or Linux). On Windows, run Wrapper inside WSL.

Environment templates are included:

  • .env.example (shared)
  • apps/cli/.env.example
  • apps/relay/.env.example
  • packages/backend/.env.example

Clone with the mobile submodule:

git clone --recurse-submodules https://github.com/heycupola/wrapper.git
cd wrapper
# For an existing clone:
git submodule update --init --recursive

The mobile repository has its own git history. The parent repository tracks only its commit pointer. For mobile development on macOS, install the prerequisites in apps/mobile/README.md, then generate the ignored Xcode project:

make -C apps/mobile bootstrap
bun install # one-time
bun run check-types # typecheck every package
bun run lint # oxlint
bun run format # oxfmt --write# try the wrapping flow locallycd apps/cli && NODE_ENV=development bun run index.ts shell-host

NODE_ENV=development moves every on-disk path into a wrapper-dev namespace under XDG state (or %APPDATA%\wrapper-dev\ on Windows), points the relay and auth URLs at localhost, mirrors logs to stderr, and writes rc-file patches into a throwaway directory. A developer running the CLI locally can never corrupt a real installation's registry, logs, or rc files.

Setting CI to any value disables telemetry and console output. For the full list of CLI environment variables, see apps/cli/README.md.

Tooling

  • Bun for runtime, package management, and bundling.
  • Turborepo for task orchestration and caching.
  • oxlint and oxfmt for linting and formatting (no ESLint, no Prettier).
  • Lefthook for git hooks (pre-commit oxfmt and oxlint, pre-push checks).
  • Catalog dependencies so shared packages such as react, next, zod, and typescript use a single pinned version across the workspace.
  • bun run audit for advisory checks. Werift's abandoned ip dependency is replaced by the tested packages/ip compatibility shim backed by ipaddr.js. The audit script ignores only Bun's name-based false positive for that private shim.

License

This repository is MIT © 2026 Cupola Labs, LLC.

The iOS app in apps/mobile is a separate repository and is not covered by this MIT license.

Security issues must be reported privately according to SECURITY.md. Hosted-service policies are published at wrapper.sh/privacy-policy and wrapper.sh/terms-of-service.

About

A terminal layer that connects and orchestrates AI tools across your devices.

Resources

Security policy

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Wrapper

One command to make any terminal you open reachable from your phone or another device.

Wrapper transparently wraps every interactive shell session you open (zsh, bash, or fish) so an authenticated device can mirror it on demand. The wrapping itself is invisible: your dotfiles, prompt, plugins, and history all behave exactly as before.

A session never leaves your machine until you decide to share it. Inside a wrapped shell, Ctrl+\ s opens a relay tunnel and Ctrl+\ u closes it again. A second device then attaches to that session and sees the live terminal.

How it works in one minute

  1. You open a terminal. Your rc file runs wrapper shell-host, which spawns your real shell inside a pseudo-terminal (PTY) and starts a tiny local WebSocket server bound to 127.0.0.1. Nothing is exposed yet.
  2. From the same machine, wrapper attach connects to that local server and mirrors the session. The transport stays on loopback and does not need the relay or Pro; released builds still authorize the signed-in session owner.
  3. When you press Ctrl+\ s, the CLI asks the Convex backend for a short-lived host ticket, connects to the relay on Fly.io, marks the session shared, and prints a secret share code. You join your own devices with wrapper attach --relay --id <id>; anyone else enters the code in a hidden prompt, so knowing the session id alone is not enough.
  4. By default viewer input prefers a direct WebRTC data channel for lower latency. The host keeps relay output available for fallback and mixed viewers, and you can force relay-only mode with WRAPPER_P2P=0.

Repository layout

This is a Bun and Turborepo monorepo.

apps/
cli/ Wrapper CLI: shell wrapping, session registry, local + relay attach, device auth, default-on P2P
relay/ Relay service: authenticated WebSocket routing for shared sessions, deployed on Fly.io
web/ Next.js app on Vercel: landing page, device-login approval, onboarding, Pro upgrade
docs/ Mintlify documentation source, published at docs.wrapper.sh
mobile/ Git submodule pointer to the native SwiftUI iPhone/iPad viewer repository
packages/
protocol/ Zod wire schema shared by every wrapper component (JSON frames + WebRTC signal)
backend/ Convex backend: Better Auth, session lifecycle, relay tickets, onboarding, billing
terminal/ Bun-native PTY layer using the wrapper-pty-helper binary
logger/ Consola logging plus opt-in PostHog telemetry
typescript-config/ Single-source tsconfig presets
tools/
pty-helper/ C source and Makefile for the wrapper-pty-helper binary shipped with the CLI

The CLI is the heart of the project. See apps/cli/README.md for how the wrapping flow works and what every command does. The transport layer (relay WebSocket and the default direct WebRTC path) is documented in apps/cli/transport/README.md.

Where to read next

TopicDocument
CLI commands, keystrokes, env varsapps/cli/README.md
Relay + direct P2P transportsapps/cli/transport/README.md
Relay service and Fly deployapps/relay/README.md
Convex backend (auth, sessions, tickets, billing)packages/backend/README.md
Wire protocolpackages/protocol/README.md
PTY internalspackages/terminal/README.md
Logging and telemetrypackages/logger/README.md
Dev and prod environments, deploy automationENVIRONMENTS.md
Production operations, incidents, backups, SLOsOPERATIONS.md
Public documentationdocs.wrapper.sh
Documentation sourceapps/docs (run bun run --cwd apps/docs dev)

https://wrapper.sh is the canonical production website, auth, installer, legal, and support origin. https://docs.wrapper.sh is the canonical public documentation origin.

Status

The CLI core, the Convex auth and backend, the relay transport, and the web onboarding flow are implemented in this repository. Sharing attempts a direct WebRTC P2P data path by default, with the relay kept online for signaling and automatic fallback (WRAPPER_P2P=0 forces relay-only mode). The terminal title shows the role, session, and active transport without reserving a screen row. The separate apps/mobile submodule now contains a Simulator-ready native iPhone/iPad viewer MVP: device authorization, owner and guest join flows, SwiftTerm rendering, relay transport, native WebRTC, and adaptive navigation. It uses Swift 6.0 language mode with complete strict concurrency on Xcode 26. CLI v0.1.4 is published on GitHub Releases. Signed-device validation and App Store review remain pre-release work.

The active focus is operational hardening:

  • rotate HOMEBREW_TAP_TOKEN so release workflow can keep the tap in sync
  • keep dependency audit, lint, format, types, auth, and relay checks green
  • complete the TestFlight public beta for the iOS viewer, then App Store review
  • finish the remaining hosted-ops items in OPERATIONS.md

Local development

Requirements:

  • Bun 1.3.5 or newer for runtime, package management, and bundling.
  • A POSIX system (macOS or Linux). On Windows, run Wrapper inside WSL.

Environment templates are included:

  • .env.example (shared)
  • apps/cli/.env.example
  • apps/relay/.env.example
  • packages/backend/.env.example

Clone with the mobile submodule:

git clone --recurse-submodules https://github.com/heycupola/wrapper.git
cd wrapper
# For an existing clone:
git submodule update --init --recursive

The mobile repository has its own git history. The parent repository tracks only its commit pointer. For mobile development on macOS, install the prerequisites in apps/mobile/README.md, then generate the ignored Xcode project:

make -C apps/mobile bootstrap
bun install # one-time
bun run check-types # typecheck every package
bun run lint # oxlint
bun run format # oxfmt --write# try the wrapping flow locallycd apps/cli && NODE_ENV=development bun run index.ts shell-host

NODE_ENV=development moves every on-disk path into a wrapper-dev namespace under XDG state (or %APPDATA%\wrapper-dev\ on Windows), points the relay and auth URLs at localhost, mirrors logs to stderr, and writes rc-file patches into a throwaway directory. A developer running the CLI locally can never corrupt a real installation's registry, logs, or rc files.

Setting CI to any value disables telemetry and console output. For the full list of CLI environment variables, see apps/cli/README.md.

Tooling

  • Bun for runtime, package management, and bundling.
  • Turborepo for task orchestration and caching.
  • oxlint and oxfmt for linting and formatting (no ESLint, no Prettier).
  • Lefthook for git hooks (pre-commit oxfmt and oxlint, pre-push checks).
  • Catalog dependencies so shared packages such as react, next, zod, and typescript use a single pinned version across the workspace.
  • bun run audit for advisory checks. Werift's abandoned ip dependency is replaced by the tested packages/ip compatibility shim backed by ipaddr.js. The audit script ignores only Bun's name-based false positive for that private shim.

License

This repository is MIT © 2026 Cupola Labs, LLC.

The iOS app in apps/mobile is a separate repository and is not covered by this MIT license.

Security issues must be reported privately according to SECURITY.md. Hosted-service policies are published at wrapper.sh/privacy-policy and wrapper.sh/terms-of-service.

About

A terminal layer that connects and orchestrates AI tools across your devices.

Resources

Security policy

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Wrapper

One command to make any terminal you open reachable from your phone or another device.

Wrapper transparently wraps every interactive shell session you open (zsh, bash, or fish) so an authenticated device can mirror it on demand. The wrapping itself is invisible: your dotfiles, prompt, plugins, and history all behave exactly as before.

A session never leaves your machine until you decide to share it. Inside a wrapped shell, Ctrl+\ s opens a relay tunnel and Ctrl+\ u closes it again. A second device then attaches to that session and sees the live terminal.

How it works in one minute

  1. You open a terminal. Your rc file runs wrapper shell-host, which spawns your real shell inside a pseudo-terminal (PTY) and starts a tiny local WebSocket server bound to 127.0.0.1. Nothing is exposed yet.
  2. From the same machine, wrapper attach connects to that local server and mirrors the session. The transport stays on loopback and does not need the relay or Pro; released builds still authorize the signed-in session owner.
  3. When you press Ctrl+\ s, the CLI asks the Convex backend for a short-lived host ticket, connects to the relay on Fly.io, marks the session shared, and prints a secret share code. You join your own devices with wrapper attach --relay --id <id>; anyone else enters the code in a hidden prompt, so knowing the session id alone is not enough.
  4. By default viewer input prefers a direct WebRTC data channel for lower latency. The host keeps relay output available for fallback and mixed viewers, and you can force relay-only mode with WRAPPER_P2P=0.

Repository layout

This is a Bun and Turborepo monorepo.

apps/
cli/ Wrapper CLI: shell wrapping, session registry, local + relay attach, device auth, default-on P2P
relay/ Relay service: authenticated WebSocket routing for shared sessions, deployed on Fly.io
web/ Next.js app on Vercel: landing page, device-login approval, onboarding, Pro upgrade
docs/ Mintlify documentation source, published at docs.wrapper.sh
mobile/ Git submodule pointer to the native SwiftUI iPhone/iPad viewer repository
packages/
protocol/ Zod wire schema shared by every wrapper component (JSON frames + WebRTC signal)
backend/ Convex backend: Better Auth, session lifecycle, relay tickets, onboarding, billing
terminal/ Bun-native PTY layer using the wrapper-pty-helper binary
logger/ Consola logging plus opt-in PostHog telemetry
typescript-config/ Single-source tsconfig presets
tools/
pty-helper/ C source and Makefile for the wrapper-pty-helper binary shipped with the CLI

The CLI is the heart of the project. See apps/cli/README.md for how the wrapping flow works and what every command does. The transport layer (relay WebSocket and the default direct WebRTC path) is documented in apps/cli/transport/README.md.

Where to read next

TopicDocument
CLI commands, keystrokes, env varsapps/cli/README.md
Relay + direct P2P transportsapps/cli/transport/README.md
Relay service and Fly deployapps/relay/README.md
Convex backend (auth, sessions, tickets, billing)packages/backend/README.md
Wire protocolpackages/protocol/README.md
PTY internalspackages/terminal/README.md
Logging and telemetrypackages/logger/README.md
Dev and prod environments, deploy automationENVIRONMENTS.md
Production operations, incidents, backups, SLOsOPERATIONS.md
Public documentationdocs.wrapper.sh
Documentation sourceapps/docs (run bun run --cwd apps/docs dev)

https://wrapper.sh is the canonical production website, auth, installer, legal, and support origin. https://docs.wrapper.sh is the canonical public documentation origin.

Status

The CLI core, the Convex auth and backend, the relay transport, and the web onboarding flow are implemented in this repository. Sharing attempts a direct WebRTC P2P data path by default, with the relay kept online for signaling and automatic fallback (WRAPPER_P2P=0 forces relay-only mode). The terminal title shows the role, session, and active transport without reserving a screen row. The separate apps/mobile submodule now contains a Simulator-ready native iPhone/iPad viewer MVP: device authorization, owner and guest join flows, SwiftTerm rendering, relay transport, native WebRTC, and adaptive navigation. It uses Swift 6.0 language mode with complete strict concurrency on Xcode 26. CLI v0.1.4 is published on GitHub Releases. Signed-device validation and App Store review remain pre-release work.

The active focus is operational hardening:

  • rotate HOMEBREW_TAP_TOKEN so release workflow can keep the tap in sync
  • keep dependency audit, lint, format, types, auth, and relay checks green
  • complete the TestFlight public beta for the iOS viewer, then App Store review
  • finish the remaining hosted-ops items in OPERATIONS.md

Local development

Requirements:

  • Bun 1.3.5 or newer for runtime, package management, and bundling.
  • A POSIX system (macOS or Linux). On Windows, run Wrapper inside WSL.

Environment templates are included:

  • .env.example (shared)
  • apps/cli/.env.example
  • apps/relay/.env.example
  • packages/backend/.env.example

Clone with the mobile submodule:

git clone --recurse-submodules https://github.com/heycupola/wrapper.git
cd wrapper
# For an existing clone:
git submodule update --init --recursive

The mobile repository has its own git history. The parent repository tracks only its commit pointer. For mobile development on macOS, install the prerequisites in apps/mobile/README.md, then generate the ignored Xcode project:

make -C apps/mobile bootstrap
bun install # one-time
bun run check-types # typecheck every package
bun run lint # oxlint
bun run format # oxfmt --write# try the wrapping flow locallycd apps/cli && NODE_ENV=development bun run index.ts shell-host

NODE_ENV=development moves every on-disk path into a wrapper-dev namespace under XDG state (or %APPDATA%\wrapper-dev\ on Windows), points the relay and auth URLs at localhost, mirrors logs to stderr, and writes rc-file patches into a throwaway directory. A developer running the CLI locally can never corrupt a real installation's registry, logs, or rc files.

Setting CI to any value disables telemetry and console output. For the full list of CLI environment variables, see apps/cli/README.md.

Tooling

  • Bun for runtime, package management, and bundling.
  • Turborepo for task orchestration and caching.
  • oxlint and oxfmt for linting and formatting (no ESLint, no Prettier).
  • Lefthook for git hooks (pre-commit oxfmt and oxlint, pre-push checks).
  • Catalog dependencies so shared packages such as react, next, zod, and typescript use a single pinned version across the workspace.
  • bun run audit for advisory checks. Werift's abandoned ip dependency is replaced by the tested packages/ip compatibility shim backed by ipaddr.js. The audit script ignores only Bun's name-based false positive for that private shim.

License

This repository is MIT © 2026 Cupola Labs, LLC.

The iOS app in apps/mobile is a separate repository and is not covered by this MIT license.

Security issues must be reported privately according to SECURITY.md. Hosted-service policies are published at wrapper.sh/privacy-policy and wrapper.sh/terms-of-service.

About

A terminal layer that connects and orchestrates AI tools across your devices.

Resources

Security policy

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Wrapper

One command to make any terminal you open reachable from your phone or another device.

Wrapper transparently wraps every interactive shell session you open (zsh, bash, or fish) so an authenticated device can mirror it on demand. The wrapping itself is invisible: your dotfiles, prompt, plugins, and history all behave exactly as before.

A session never leaves your machine until you decide to share it. Inside a wrapped shell, Ctrl+\ s opens a relay tunnel and Ctrl+\ u closes it again. A second device then attaches to that session and sees the live terminal.

How it works in one minute

  1. You open a terminal. Your rc file runs wrapper shell-host, which spawns your real shell inside a pseudo-terminal (PTY) and starts a tiny local WebSocket server bound to 127.0.0.1. Nothing is exposed yet.
  2. From the same machine, wrapper attach connects to that local server and mirrors the session. The transport stays on loopback and does not need the relay or Pro; released builds still authorize the signed-in session owner.
  3. When you press Ctrl+\ s, the CLI asks the Convex backend for a short-lived host ticket, connects to the relay on Fly.io, marks the session shared, and prints a secret share code. You join your own devices with wrapper attach --relay --id <id>; anyone else enters the code in a hidden prompt, so knowing the session id alone is not enough.
  4. By default viewer input prefers a direct WebRTC data channel for lower latency. The host keeps relay output available for fallback and mixed viewers, and you can force relay-only mode with WRAPPER_P2P=0.

Repository layout

This is a Bun and Turborepo monorepo.

apps/
cli/ Wrapper CLI: shell wrapping, session registry, local + relay attach, device auth, default-on P2P
relay/ Relay service: authenticated WebSocket routing for shared sessions, deployed on Fly.io
web/ Next.js app on Vercel: landing page, device-login approval, onboarding, Pro upgrade
docs/ Mintlify documentation source, published at docs.wrapper.sh
mobile/ Git submodule pointer to the native SwiftUI iPhone/iPad viewer repository
packages/
protocol/ Zod wire schema shared by every wrapper component (JSON frames + WebRTC signal)
backend/ Convex backend: Better Auth, session lifecycle, relay tickets, onboarding, billing
terminal/ Bun-native PTY layer using the wrapper-pty-helper binary
logger/ Consola logging plus opt-in PostHog telemetry
typescript-config/ Single-source tsconfig presets
tools/
pty-helper/ C source and Makefile for the wrapper-pty-helper binary shipped with the CLI

The CLI is the heart of the project. See apps/cli/README.md for how the wrapping flow works and what every command does. The transport layer (relay WebSocket and the default direct WebRTC path) is documented in apps/cli/transport/README.md.

Where to read next

TopicDocument
CLI commands, keystrokes, env varsapps/cli/README.md
Relay + direct P2P transportsapps/cli/transport/README.md
Relay service and Fly deployapps/relay/README.md
Convex backend (auth, sessions, tickets, billing)packages/backend/README.md
Wire protocolpackages/protocol/README.md
PTY internalspackages/terminal/README.md
Logging and telemetrypackages/logger/README.md
Dev and prod environments, deploy automationENVIRONMENTS.md
Production operations, incidents, backups, SLOsOPERATIONS.md
Public documentationdocs.wrapper.sh
Documentation sourceapps/docs (run bun run --cwd apps/docs dev)

https://wrapper.sh is the canonical production website, auth, installer, legal, and support origin. https://docs.wrapper.sh is the canonical public documentation origin.

Status

The CLI core, the Convex auth and backend, the relay transport, and the web onboarding flow are implemented in this repository. Sharing attempts a direct WebRTC P2P data path by default, with the relay kept online for signaling and automatic fallback (WRAPPER_P2P=0 forces relay-only mode). The terminal title shows the role, session, and active transport without reserving a screen row. The separate apps/mobile submodule now contains a Simulator-ready native iPhone/iPad viewer MVP: device authorization, owner and guest join flows, SwiftTerm rendering, relay transport, native WebRTC, and adaptive navigation. It uses Swift 6.0 language mode with complete strict concurrency on Xcode 26. CLI v0.1.4 is published on GitHub Releases. Signed-device validation and App Store review remain pre-release work.

The active focus is operational hardening:

  • rotate HOMEBREW_TAP_TOKEN so release workflow can keep the tap in sync
  • keep dependency audit, lint, format, types, auth, and relay checks green
  • complete the TestFlight public beta for the iOS viewer, then App Store review
  • finish the remaining hosted-ops items in OPERATIONS.md

Local development

Requirements:

  • Bun 1.3.5 or newer for runtime, package management, and bundling.
  • A POSIX system (macOS or Linux). On Windows, run Wrapper inside WSL.

Environment templates are included:

  • .env.example (shared)
  • apps/cli/.env.example
  • apps/relay/.env.example
  • packages/backend/.env.example

Clone with the mobile submodule:

git clone --recurse-submodules https://github.com/heycupola/wrapper.git
cd wrapper
# For an existing clone:
git submodule update --init --recursive

The mobile repository has its own git history. The parent repository tracks only its commit pointer. For mobile development on macOS, install the prerequisites in apps/mobile/README.md, then generate the ignored Xcode project:

make -C apps/mobile bootstrap
bun install # one-time
bun run check-types # typecheck every package
bun run lint # oxlint
bun run format # oxfmt --write# try the wrapping flow locallycd apps/cli && NODE_ENV=development bun run index.ts shell-host

NODE_ENV=development moves every on-disk path into a wrapper-dev namespace under XDG state (or %APPDATA%\wrapper-dev\ on Windows), points the relay and auth URLs at localhost, mirrors logs to stderr, and writes rc-file patches into a throwaway directory. A developer running the CLI locally can never corrupt a real installation's registry, logs, or rc files.

Setting CI to any value disables telemetry and console output. For the full list of CLI environment variables, see apps/cli/README.md.

Tooling

  • Bun for runtime, package management, and bundling.
  • Turborepo for task orchestration and caching.
  • oxlint and oxfmt for linting and formatting (no ESLint, no Prettier).
  • Lefthook for git hooks (pre-commit oxfmt and oxlint, pre-push checks).
  • Catalog dependencies so shared packages such as react, next, zod, and typescript use a single pinned version across the workspace.
  • bun run audit for advisory checks. Werift's abandoned ip dependency is replaced by the tested packages/ip compatibility shim backed by ipaddr.js. The audit script ignores only Bun's name-based false positive for that private shim.

License

This repository is MIT © 2026 Cupola Labs, LLC.

The iOS app in apps/mobile is a separate repository and is not covered by this MIT license.

Security issues must be reported privately according to SECURITY.md. Hosted-service policies are published at wrapper.sh/privacy-policy and wrapper.sh/terms-of-service.

About

A terminal layer that connects and orchestrates AI tools across your devices.

Resources

Security policy

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Wrapper

One command to make any terminal you open reachable from your phone or another device.

Wrapper transparently wraps every interactive shell session you open (zsh, bash, or fish) so an authenticated device can mirror it on demand. The wrapping itself is invisible: your dotfiles, prompt, plugins, and history all behave exactly as before.

A session never leaves your machine until you decide to share it. Inside a wrapped shell, Ctrl+\ s opens a relay tunnel and Ctrl+\ u closes it again. A second device then attaches to that session and sees the live terminal.

How it works in one minute

  1. You open a terminal. Your rc file runs wrapper shell-host, which spawns your real shell inside a pseudo-terminal (PTY) and starts a tiny local WebSocket server bound to 127.0.0.1. Nothing is exposed yet.
  2. From the same machine, wrapper attach connects to that local server and mirrors the session. The transport stays on loopback and does not need the relay or Pro; released builds still authorize the signed-in session owner.
  3. When you press Ctrl+\ s, the CLI asks the Convex backend for a short-lived host ticket, connects to the relay on Fly.io, marks the session shared, and prints a secret share code. You join your own devices with wrapper attach --relay --id <id>; anyone else enters the code in a hidden prompt, so knowing the session id alone is not enough.
  4. By default viewer input prefers a direct WebRTC data channel for lower latency. The host keeps relay output available for fallback and mixed viewers, and you can force relay-only mode with WRAPPER_P2P=0.

Repository layout

This is a Bun and Turborepo monorepo.

apps/
cli/ Wrapper CLI: shell wrapping, session registry, local + relay attach, device auth, default-on P2P
relay/ Relay service: authenticated WebSocket routing for shared sessions, deployed on Fly.io
web/ Next.js app on Vercel: landing page, device-login approval, onboarding, Pro upgrade
docs/ Mintlify documentation source, published at docs.wrapper.sh
mobile/ Git submodule pointer to the native SwiftUI iPhone/iPad viewer repository
packages/
protocol/ Zod wire schema shared by every wrapper component (JSON frames + WebRTC signal)
backend/ Convex backend: Better Auth, session lifecycle, relay tickets, onboarding, billing
terminal/ Bun-native PTY layer using the wrapper-pty-helper binary
logger/ Consola logging plus opt-in PostHog telemetry
typescript-config/ Single-source tsconfig presets
tools/
pty-helper/ C source and Makefile for the wrapper-pty-helper binary shipped with the CLI

The CLI is the heart of the project. See apps/cli/README.md for how the wrapping flow works and what every command does. The transport layer (relay WebSocket and the default direct WebRTC path) is documented in apps/cli/transport/README.md.

Where to read next

TopicDocument
CLI commands, keystrokes, env varsapps/cli/README.md
Relay + direct P2P transportsapps/cli/transport/README.md
Relay service and Fly deployapps/relay/README.md
Convex backend (auth, sessions, tickets, billing)packages/backend/README.md
Wire protocolpackages/protocol/README.md
PTY internalspackages/terminal/README.md
Logging and telemetrypackages/logger/README.md
Dev and prod environments, deploy automationENVIRONMENTS.md
Production operations, incidents, backups, SLOsOPERATIONS.md
Public documentationdocs.wrapper.sh
Documentation sourceapps/docs (run bun run --cwd apps/docs dev)

https://wrapper.sh is the canonical production website, auth, installer, legal, and support origin. https://docs.wrapper.sh is the canonical public documentation origin.

Status

The CLI core, the Convex auth and backend, the relay transport, and the web onboarding flow are implemented in this repository. Sharing attempts a direct WebRTC P2P data path by default, with the relay kept online for signaling and automatic fallback (WRAPPER_P2P=0 forces relay-only mode). The terminal title shows the role, session, and active transport without reserving a screen row. The separate apps/mobile submodule now contains a Simulator-ready native iPhone/iPad viewer MVP: device authorization, owner and guest join flows, SwiftTerm rendering, relay transport, native WebRTC, and adaptive navigation. It uses Swift 6.0 language mode with complete strict concurrency on Xcode 26. CLI v0.1.4 is published on GitHub Releases. Signed-device validation and App Store review remain pre-release work.

The active focus is operational hardening:

  • rotate HOMEBREW_TAP_TOKEN so release workflow can keep the tap in sync
  • keep dependency audit, lint, format, types, auth, and relay checks green
  • complete the TestFlight public beta for the iOS viewer, then App Store review
  • finish the remaining hosted-ops items in OPERATIONS.md

Local development

Requirements:

  • Bun 1.3.5 or newer for runtime, package management, and bundling.
  • A POSIX system (macOS or Linux). On Windows, run Wrapper inside WSL.

Environment templates are included:

  • .env.example (shared)
  • apps/cli/.env.example
  • apps/relay/.env.example
  • packages/backend/.env.example

Clone with the mobile submodule:

git clone --recurse-submodules https://github.com/heycupola/wrapper.git
cd wrapper
# For an existing clone:
git submodule update --init --recursive

The mobile repository has its own git history. The parent repository tracks only its commit pointer. For mobile development on macOS, install the prerequisites in apps/mobile/README.md, then generate the ignored Xcode project:

make -C apps/mobile bootstrap
bun install # one-time
bun run check-types # typecheck every package
bun run lint # oxlint
bun run format # oxfmt --write# try the wrapping flow locallycd apps/cli && NODE_ENV=development bun run index.ts shell-host

NODE_ENV=development moves every on-disk path into a wrapper-dev namespace under XDG state (or %APPDATA%\wrapper-dev\ on Windows), points the relay and auth URLs at localhost, mirrors logs to stderr, and writes rc-file patches into a throwaway directory. A developer running the CLI locally can never corrupt a real installation's registry, logs, or rc files.

Setting CI to any value disables telemetry and console output. For the full list of CLI environment variables, see apps/cli/README.md.

Tooling

  • Bun for runtime, package management, and bundling.
  • Turborepo for task orchestration and caching.
  • oxlint and oxfmt for linting and formatting (no ESLint, no Prettier).
  • Lefthook for git hooks (pre-commit oxfmt and oxlint, pre-push checks).
  • Catalog dependencies so shared packages such as react, next, zod, and typescript use a single pinned version across the workspace.
  • bun run audit for advisory checks. Werift's abandoned ip dependency is replaced by the tested packages/ip compatibility shim backed by ipaddr.js. The audit script ignores only Bun's name-based false positive for that private shim.

License

This repository is MIT © 2026 Cupola Labs, LLC.

The iOS app in apps/mobile is a separate repository and is not covered by this MIT license.

Security issues must be reported privately according to SECURITY.md. Hosted-service policies are published at wrapper.sh/privacy-policy and wrapper.sh/terms-of-service.

About

A terminal layer that connects and orchestrates AI tools across your devices.

Resources

Security policy

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages