airpocket 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -0
- package/LICENSE +21 -0
- package/README.md +256 -2
- package/SECURITY.md +114 -0
- package/package.json +42 -4
- package/src/agentconfig.js +300 -0
- package/src/behind.js +86 -0
- package/src/bridges.js +270 -0
- package/src/cli.js +92 -0
- package/src/doctor.js +215 -0
- package/src/hook.js +198 -0
- package/src/loginitem.js +122 -0
- package/src/models.js +173 -0
- package/src/ownterm.js +299 -0
- package/src/ports.js +48 -0
- package/src/proc.js +184 -0
- package/src/public/app.css +581 -0
- package/src/public/app.js +1729 -0
- package/src/public/fonts/OFL-B612.txt +93 -0
- package/src/public/fonts/OFL-IBM-Plex.txt +93 -0
- package/src/public/fonts/b612-400.woff2 +0 -0
- package/src/public/fonts/b612-700.woff2 +0 -0
- package/src/public/fonts/b612-mono-400.woff2 +0 -0
- package/src/public/fonts/b612-mono-700.woff2 +0 -0
- package/src/public/fonts/plex-400.woff2 +0 -0
- package/src/public/fonts/plex-500.woff2 +0 -0
- package/src/public/fonts/plex-600.woff2 +0 -0
- package/src/public/icon-180.png +0 -0
- package/src/public/icon-512.png +0 -0
- package/src/public/icon-maskable-512.png +0 -0
- package/src/public/index.html +46 -0
- package/src/public/launch/375x667@2.jpg +0 -0
- package/src/public/launch/375x812@3.jpg +0 -0
- package/src/public/launch/390x844@3.jpg +0 -0
- package/src/public/launch/393x852@3.jpg +0 -0
- package/src/public/launch/402x874@3.jpg +0 -0
- package/src/public/launch/414x736@3.jpg +0 -0
- package/src/public/launch/414x896@2.jpg +0 -0
- package/src/public/launch/414x896@3.jpg +0 -0
- package/src/public/launch/420x912@3.jpg +0 -0
- package/src/public/launch/428x926@3.jpg +0 -0
- package/src/public/launch/430x932@3.jpg +0 -0
- package/src/public/launch/440x956@3.jpg +0 -0
- package/src/public/manifest.webmanifest +31 -0
- package/src/public/map.svg +1 -0
- package/src/public/md.js +162 -0
- package/src/public/qr.js +605 -0
- package/src/public/sw.js +35 -0
- package/src/public/where.js +22 -0
- package/src/secrets.js +8 -0
- package/src/server.js +1744 -0
- package/src/service.js +541 -0
- package/src/setup.js +837 -0
- package/src/shellalias.js +109 -0
- package/src/store.js +122 -0
- package/src/tailscale.js +139 -0
- package/src/tmux.conf +64 -0
- package/src/uploads.js +124 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Every change people using Airpocket would notice, newest first. The form is
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## 0.1.1 - 2026-11-06
|
|
8
|
+
|
|
9
|
+
A fix for typing from the phone. Reported from a real install: the
|
|
10
|
+
conversation kept updating on the phone, and a reply answered "Something went
|
|
11
|
+
wrong on the Mac."
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **A terminal that fails as the keys land now says which terminal, and what
|
|
16
|
+
its own program said**, instead of the one message nobody can act on. Reading
|
|
17
|
+
a session and typing into it are different paths: the conversation comes from
|
|
18
|
+
hook events and session files and never touches a terminal, so a failure was
|
|
19
|
+
always in the last step, and that step had nothing around it. Sending a
|
|
20
|
+
message, pressing a key and running a session command are all guarded now,
|
|
21
|
+
and point at `airpocket doctor`.
|
|
22
|
+
- **A session in a Superset terminal is read-only up front when Superset isn't
|
|
23
|
+
installed**, rather than looking writable and failing at the keystroke.
|
|
24
|
+
Airpocket was choosing that route from the ids on record without asking
|
|
25
|
+
whether Superset was still there. The list now says why in a few words, and
|
|
26
|
+
says the thing that helps rather than suggesting `airpocket run`, which
|
|
27
|
+
wouldn't.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`airpocket doctor` says when sessions in Superset terminals are
|
|
32
|
+
read-only**, counting only the ones that would really lose their route: an
|
|
33
|
+
agent started with `airpocket run` inside a Superset terminal is reached
|
|
34
|
+
through its tmux pane and is left alone.
|
|
35
|
+
|
|
36
|
+
## 0.1.0 - 2026-09-17
|
|
37
|
+
|
|
38
|
+
The first version. Airpocket runs on each Mac where you use Claude Code or
|
|
39
|
+
Codex and makes those sessions reachable from a web app on your phone, over
|
|
40
|
+
your own Tailscale network. There is no Airpocket server, account or relay.
|
|
41
|
+
macOS only, Node 20 or newer.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- **One list** of every Claude Code and Codex session on every paired Mac,
|
|
46
|
+
with the ones waiting on you first: finished, waiting for input, needing
|
|
47
|
+
permission. Each row shows the model, effort, permission mode and whether a
|
|
48
|
+
turn is running. Agents running on the Mac that Airpocket hasn't heard from
|
|
49
|
+
yet are counted, with a line on how to bring them in.
|
|
50
|
+
- **The conversation**, read from the agents' own session files rather than the
|
|
51
|
+
terminal: your messages, the replies, and one short label per step, updated
|
|
52
|
+
live. Codex turns are shown while they run.
|
|
53
|
+
- **Reply and control** from the phone: send messages and photos, press keys,
|
|
54
|
+
pick options in on-screen menus, and change model, effort, permission mode
|
|
55
|
+
and fast mode from each agent's own model list. Commands such as compact,
|
|
56
|
+
resume and clear come from a fixed table on the Mac, and are refused while a
|
|
57
|
+
turn runs, a menu is open or unsent text sits in the box.
|
|
58
|
+
- **New sessions** in a project, on a new branch or in an existing workspace,
|
|
59
|
+
for Claude Code or Codex, when Superset is installed.
|
|
60
|
+
- **Files a session wrote, edited or linked**: text, Markdown, code, images,
|
|
61
|
+
PDFs and HTML pages, limited to the session's own folder and checked by
|
|
62
|
+
content. PDFs and pages open from a separate file-only origin through
|
|
63
|
+
two-minute links, and pages there run no scripts.
|
|
64
|
+
- **`airpocket run claude|codex`**: starts the agent in a tmux session of
|
|
65
|
+
Airpocket's own, so the phone can answer a session started in any terminal.
|
|
66
|
+
`airpocket attach` brings back one whose window was closed. Sessions in
|
|
67
|
+
Superset or an existing tmux pane can be answered without it; anything else
|
|
68
|
+
is shown read-only.
|
|
69
|
+
- **`airpocket setup`**: checks the Mac first and stops before changing
|
|
70
|
+
anything if something is missing, then installs the hooks for Claude Code
|
|
71
|
+
and Codex (merged into their settings, with a backup), a login item, the app
|
|
72
|
+
published inside the tailnet with `tailscale serve`, optional `claude` and
|
|
73
|
+
`codex` shell aliases, and a pairing code with its QR. `--dry-run` prints
|
|
74
|
+
the plan.
|
|
75
|
+
- **`airpocket doctor`**: checks the install and says what to do about each
|
|
76
|
+
problem. **`airpocket uninstall`** removes what setup added, taking only its
|
|
77
|
+
own entries out of the agents' config files.
|
|
78
|
+
- **Pairing** with a six-digit code or its QR, one token per phone and per
|
|
79
|
+
Mac, stored on the Mac only as a hash. `airpocket devices` lists paired
|
|
80
|
+
phones and `airpocket revoke` cuts one off at once.
|
|
81
|
+
- **A home-screen web app** with no App Store. It still opens when the Mac
|
|
82
|
+
that served it is asleep, follows Reduce Motion and Reduce Transparency, and
|
|
83
|
+
ships its own fonts.
|
|
84
|
+
- **Security documentation**: [SECURITY.md](SECURITY.md) for reporting a
|
|
85
|
+
problem, and a threat model and review checklist in the repository's `docs`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oncel Ozgul
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,257 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center"><img src="docs/images/banner.png" width="100%" alt="Airpocket: the dart and the wordmark on a dark chart, over the line "Your agents fly the missions. This is the cockpit.""></h1>
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="#install-macos">Install</a> ·
|
|
5
|
+
<a href="https://airpocket.sh/#how">How it works</a> ·
|
|
6
|
+
<a href="#security-in-one-paragraph">Security</a> ·
|
|
7
|
+
<a href="https://airpocket.sh/#faq">FAQ</a> ·
|
|
8
|
+
<a href="https://airpocket.sh">airpocket.sh</a> ·
|
|
9
|
+
<a href="https://github.com/airpocket-sh/airpocket/releases">Releases</a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-D7C49E?style=flat-square&labelColor=1B2118" alt="License: MIT"></a>
|
|
14
|
+
<a href="https://github.com/airpocket-sh/airpocket/actions/workflows/test.yml"><img src="https://github.com/airpocket-sh/airpocket/actions/workflows/test.yml/badge.svg?branch=main" alt="Tests on main"></a>
|
|
15
|
+
<img src="https://img.shields.io/badge/platform-macOS-D7C49E?style=flat-square&labelColor=1B2118" alt="Platform: macOS">
|
|
16
|
+
<img src="https://img.shields.io/badge/node-20%2B-D7C49E?style=flat-square&labelColor=1B2118" alt="Node 20 or newer">
|
|
17
|
+
<img src="https://img.shields.io/badge/dependencies-none-8FD14F?style=flat-square&labelColor=1B2118" alt="Dependencies: none">
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
Your coding agents, on your phone. Every Claude Code and Codex session on every
|
|
21
|
+
one of your Macs, in one list, with the ones waiting on you first. Read the
|
|
22
|
+
conversation, reply, answer questions, switch models, start new sessions and
|
|
23
|
+
open the files they made, from anywhere.
|
|
24
|
+
|
|
25
|
+
Nothing sits in between: your phone talks to your Macs over your own
|
|
26
|
+
[Tailscale](https://tailscale.com) network. There is no Airpocket server, no
|
|
27
|
+
account and no relay. It works in the terminal you already use. What it needs:
|
|
28
|
+
a Mac with Node 20 or newer and Claude Code or Codex, and Tailscale on the Mac
|
|
29
|
+
and on the phone.
|
|
30
|
+
|
|
31
|
+
<p align="center"><img src="docs/images/phones.png" width="100%" alt="Three phones running Airpocket. First, the session list: three sessions need you (one asking permission to use Bash, one asking which Stripe API version to target, one finished) above two open ones. Second, a session asking "Do you want to proceed?" with three numbered answers and a reply box. Third, a TypeScript file the agent wrote, open with line numbers."></p>
|
|
32
|
+
|
|
33
|
+
> **Status: 0.1.1, young.** It does what this page says and it is tested, but
|
|
34
|
+
> it has run on few Macs other than its author's. See
|
|
35
|
+
> [docs/PLAN.md](docs/PLAN.md) for how it was built, and
|
|
36
|
+
> [CONTRIBUTING.md](CONTRIBUTING.md) to send a change.
|
|
37
|
+
|
|
38
|
+
## What it will do
|
|
39
|
+
|
|
40
|
+
- **One list** of every session on every paired Mac: needs you, open, earlier.
|
|
41
|
+
- **The conversation**, not the raw terminal: messages, answers, one line per
|
|
42
|
+
step, updated live, including Codex mid-turn.
|
|
43
|
+
- **Reply and control**: send messages, press keys, pick options on screen,
|
|
44
|
+
change model, effort and permission mode, compact, resume and more.
|
|
45
|
+
- **Start sessions** in a project, on a new branch or an existing workspace
|
|
46
|
+
(with Superset).
|
|
47
|
+
- **Open files** a session wrote or linked: text, Markdown, code, images,
|
|
48
|
+
PDFs, pages, under strict rules.
|
|
49
|
+
- **Send a photo**: a screenshot, a whiteboard, the thing on your desk, from
|
|
50
|
+
the phone's camera or its library, with or without a message.
|
|
51
|
+
- **A web app**, added to your home screen. No App Store.
|
|
52
|
+
|
|
53
|
+
<p align="center">
|
|
54
|
+
<img src="site/shots/mfd1-sessions-one-waiting.png" width="280" alt="The session list for one Mac: under Needs you, a Claude Code session needing permission to use Bash and a Codex session asking which Stripe API version to target; under Open, a Claude Code session working, on Opus 5 with high effort.">
|
|
55
|
+
|
|
56
|
+
<img src="site/shots/mfd3-controls-model-effort.png" width="280" alt="The Controls sheet for a Claude Code session: Opus 5 active and Sonnet 5 picked to apply next, effort set to high, permission mode buttons, and session commands including Compact, Resume and Clear.">
|
|
57
|
+
</p>
|
|
58
|
+
|
|
59
|
+
## Any terminal
|
|
60
|
+
|
|
61
|
+
It works in the terminal you already use: Terminal, iTerm2, Ghostty, Warp, a VS
|
|
62
|
+
Code or Cursor pane. The one change is how you start the agent:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
airpocket run claude # instead of claude; anything after the name goes to it
|
|
66
|
+
airpocket run codex resume --last
|
|
67
|
+
airpocket attach # back to one you closed the window on
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Setup offers to alias `claude` and `codex` to that, and then you type exactly
|
|
71
|
+
what you typed before. Sessions in Superset or tmux need no change: the phone
|
|
72
|
+
answers them as they are.
|
|
73
|
+
|
|
74
|
+
<p align="center">
|
|
75
|
+
<img src="site/shots/mfd2-question-answered.png" width="280" alt="A Claude Code session on the phone, asking before it runs a shell command: "Do you want to proceed?" with the numbered answers Yes; Yes, and don't ask again for npm test commands; and No, and tell Claude what to do differently. A reply box sits underneath.">
|
|
76
|
+
</p>
|
|
77
|
+
|
|
78
|
+
Why `run` is there: the phone can watch a session from its transcript and hooks
|
|
79
|
+
alone, but to answer one Airpocket has to type into its terminal, and a tmux
|
|
80
|
+
pane or a Superset terminal is what lets it. `run` starts the agent in a tmux
|
|
81
|
+
session of Airpocket's own (its own socket in Airpocket's data folder, never
|
|
82
|
+
your tmux server or `~/.tmux.conf`), tuned to stay out of sight: no status bar,
|
|
83
|
+
no prefix key, Esc with no delay, Shift+Enter, the mouse wheel for scrollback,
|
|
84
|
+
copying to the clipboard. Closing the window doesn't stop the agent: the phone
|
|
85
|
+
can still answer it, and `airpocket attach` brings it back. Inside tmux or
|
|
86
|
+
Superset already, `run` starts the agent there directly.
|
|
87
|
+
|
|
88
|
+
A session started the plain way, outside all of that, still shows on the phone,
|
|
89
|
+
read-only, with a line saying how to bring it in.
|
|
90
|
+
|
|
91
|
+
## Install (macOS)
|
|
92
|
+
|
|
93
|
+
**Airpocket runs on each Mac whose sessions you want to see.** Install it and
|
|
94
|
+
run setup on every one of them, and pair your phone with each. There is no
|
|
95
|
+
shared server and nothing passes between your Macs: the phone keeps one token
|
|
96
|
+
per Mac and asks each one itself.
|
|
97
|
+
|
|
98
|
+
You need, on each Mac: macOS, Node 20 or newer,
|
|
99
|
+
[Tailscale](https://tailscale.com/download/mac) signed in with **MagicDNS and
|
|
100
|
+
HTTPS certificates turned on for your tailnet**, and Claude Code or Codex. On
|
|
101
|
+
the phone: Tailscale, signed in to the same tailnet. Setup also checks for tmux,
|
|
102
|
+
which `airpocket run` uses (above); if it's missing, `brew install tmux`.
|
|
103
|
+
|
|
104
|
+
### Which tailnet you land in
|
|
105
|
+
|
|
106
|
+
Tailscale sorts people by the email domain they sign in with, so signing in
|
|
107
|
+
with a **work address puts you in your company's tailnet**, not one of your
|
|
108
|
+
own. There you are a member: an admin has to approve your device, the admin
|
|
109
|
+
console won't open for you, and MagicDNS and HTTPS certificates — both
|
|
110
|
+
tailnet-wide, both needed here — are someone else's switch to flip. Setup stops
|
|
111
|
+
with that in plain words rather than half-installing.
|
|
112
|
+
|
|
113
|
+
Signing in with a **personal account** gives you your own tailnet where you are
|
|
114
|
+
the admin, and turning HTTPS certificates on is one switch on the
|
|
115
|
+
[DNS page](https://console.tailscale.com/admin/dns). Either is fine — a shared
|
|
116
|
+
tailnet only needs its admin to do it once, for everyone. Issuing a certificate
|
|
117
|
+
publishes the machine's DNS name in public certificate-transparency logs; what
|
|
118
|
+
it serves stays inside your tailnet.
|
|
119
|
+
|
|
120
|
+
Sharing a tailnet doesn't share sessions: every Mac still asks the phone for a
|
|
121
|
+
pairing code of its own, and hands out its own token.
|
|
122
|
+
|
|
123
|
+
### Install it
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
npm install -g airpocket
|
|
127
|
+
airpocket setup --dry-run # every change it would make; changes nothing
|
|
128
|
+
airpocket setup
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
No dependencies: the tarball is Airpocket and the fonts the phone app uses,
|
|
132
|
+
and nothing else.
|
|
133
|
+
|
|
134
|
+
### Or from a clone
|
|
135
|
+
|
|
136
|
+
To read the code as you run it, or to send a change:
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
git clone https://github.com/airpocket-sh/airpocket.git
|
|
140
|
+
cd airpocket
|
|
141
|
+
npm install -g . # the airpocket command, from your clone
|
|
142
|
+
airpocket setup
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Either way, setup copies Airpocket into its data folder and runs that copy, so
|
|
146
|
+
the service keeps working if you move or delete the clone (see
|
|
147
|
+
[Keeping it up to date](#keeping-it-up-to-date)).
|
|
148
|
+
|
|
149
|
+
Setup checks first and stops with a plain explanation if something is
|
|
150
|
+
missing. Then it:
|
|
151
|
+
|
|
152
|
+
- adds Airpocket's hooks to `~/.claude/settings.json` and `~/.codex/hooks.json`
|
|
153
|
+
(or under `$CLAUDE_CONFIG_DIR` and `$CODEX_HOME`). It merges: every other
|
|
154
|
+
setting and hook stays as it was, a timestamped backup is written next to the
|
|
155
|
+
file first, and a file that isn't valid JSON is left alone. Running it again
|
|
156
|
+
adds nothing twice.
|
|
157
|
+
- adds a login item (`~/Library/LaunchAgents/sh.airpocket.service.plist`) that
|
|
158
|
+
starts Airpocket at login and brings it back after a crash. It runs the copy
|
|
159
|
+
of Airpocket kept in its data folder, not your clone, so moving or deleting
|
|
160
|
+
the clone doesn't stop it.
|
|
161
|
+
- publishes the app and its file-only origin inside your tailnet with
|
|
162
|
+
`tailscale serve` (never `funnel`), on ports nothing else uses: 443 and
|
|
163
|
+
8443 when they are free.
|
|
164
|
+
- asks whether to add two aliases to your shell profile (`~/.zshrc` or
|
|
165
|
+
`~/.bash_profile`), so `claude` and `codex` start where the phone can
|
|
166
|
+
answer them (above). `--alias` or `--no-alias` decides without asking.
|
|
167
|
+
Marked, backed up first, and removed by uninstall.
|
|
168
|
+
- prints a pairing code and its QR.
|
|
169
|
+
|
|
170
|
+
Codex asks you to review new hooks the next time it starts: choose to trust
|
|
171
|
+
Airpocket's, or open `/hooks`. A Codex session shows up on the phone once
|
|
172
|
+
you've sent it a first message. In Superset it's different: Superset's `codex`
|
|
173
|
+
wrapper starts Codex with the trust check bypassed, so the hooks run there
|
|
174
|
+
without the review (and the phone's "not trusted yet" line is about Codex
|
|
175
|
+
started anywhere else).
|
|
176
|
+
|
|
177
|
+
Claude Code picks up the hooks while it runs, so sessions started before setup
|
|
178
|
+
need no restart: each appears the next time you prompt it. Codex reads its
|
|
179
|
+
hooks only when it starts, so restart a Codex session that was running before
|
|
180
|
+
setup. Until then the phone says how many Claude Code and how many Codex
|
|
181
|
+
agents are running that haven't checked in, and whether Codex's hooks still
|
|
182
|
+
need trusting; setup prints the same when it finishes.
|
|
183
|
+
|
|
184
|
+
```sh
|
|
185
|
+
airpocket doctor # checks everything, says what to do; exits 1 if something is broken
|
|
186
|
+
airpocket pair # a new pairing code
|
|
187
|
+
airpocket devices # paired phones; airpocket revoke <id> cuts one off
|
|
188
|
+
airpocket uninstall # removes exactly what setup added; asks before deleting paired phones
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Tested so far:** setup, doctor and uninstall against a temporary home folder
|
|
192
|
+
(with the real launchd in a trial), Claude Code end to end with real
|
|
193
|
+
sessions, and Codex against recorded session files and a real Codex's hooks
|
|
194
|
+
(installed, trusted, firing). Codex with real model replies driven from the
|
|
195
|
+
phone has not been run end to end yet. `airpocket run` with a real Claude in a
|
|
196
|
+
hidden terminal window (keys, paste, resize, scrollback, detach and attach).
|
|
197
|
+
|
|
198
|
+
### Keeping it up to date
|
|
199
|
+
|
|
200
|
+
```sh
|
|
201
|
+
cd airpocket
|
|
202
|
+
git pull
|
|
203
|
+
airpocket setup
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The `airpocket` command runs from your clone, but the service, the hooks and
|
|
207
|
+
the login item run the copy setup made in the data folder. A `git pull` alone
|
|
208
|
+
changes the command and nothing else; running setup again replaces the copy
|
|
209
|
+
and restarts the service on it. Paired phones stay paired; the pairing code
|
|
210
|
+
setup prints at the end can be ignored.
|
|
211
|
+
|
|
212
|
+
`airpocket doctor` warns when the version the service runs differs from the
|
|
213
|
+
command's. It compares the version numbers in `package.json`, so a pull that
|
|
214
|
+
doesn't change the version passes unnoticed: run setup after every pull.
|
|
215
|
+
|
|
216
|
+
## Photos
|
|
217
|
+
|
|
218
|
+
The message box has a photo button. Pick from the library or take one, up to
|
|
219
|
+
four to a message, and send it with or without words.
|
|
220
|
+
|
|
221
|
+
The phone shrinks each photo to 2200px and re-encodes it before it goes, which
|
|
222
|
+
also drops what the camera wrote into the file, the place it was taken
|
|
223
|
+
included. The Mac keeps it in Airpocket's own data folder
|
|
224
|
+
(`~/Library/Application Support/Airpocket/uploads`, 0600), never in the project
|
|
225
|
+
the agent is working in, and types the path into the session as part of your
|
|
226
|
+
message, so the agent reads it with the tools it already has. Photos are
|
|
227
|
+
deleted after a week, and only the last forty are kept.
|
|
228
|
+
|
|
229
|
+
Because they sit outside the project, the agent may ask before reading one —
|
|
230
|
+
answer that from the phone like any other prompt. To stop it asking, allow that
|
|
231
|
+
one folder in `~/.claude/settings.json`:
|
|
232
|
+
|
|
233
|
+
```json
|
|
234
|
+
{ "permissions": { "allow": ["Read(//Users/you/Library/Application Support/Airpocket/uploads/**)"] } }
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Security in one paragraph
|
|
238
|
+
|
|
239
|
+
Airpocket listens on 127.0.0.1 and is published only inside your tailnet. Each
|
|
240
|
+
phone gets its own token for a one-time pairing code; the Mac keeps only
|
|
241
|
+
hashes. The phone never sends a file path or command text: it names a session
|
|
242
|
+
and an action, and the Mac looks up the rest in its own records. Files are
|
|
243
|
+
limited to what a session made or mentioned, inside its own folder, checked by
|
|
244
|
+
content, and PDFs and pages open from a separate file-only origin through
|
|
245
|
+
two-minute links.
|
|
246
|
+
|
|
247
|
+
**Know this before you pair.** A paired phone can type anything into your
|
|
248
|
+
agents, including shell commands (in Claude Code and Codex, a message that
|
|
249
|
+
starts with `!` runs as one), and start new sessions. Its token doesn't expire:
|
|
250
|
+
it works until you revoke it on that Mac. `airpocket devices` lists the paired
|
|
251
|
+
phones, and `airpocket revoke <id>` cuts one off at once. Pair only phones you
|
|
252
|
+
control, and revoke a lost one straight away.
|
|
253
|
+
|
|
254
|
+
## License
|
|
255
|
+
|
|
256
|
+
MIT. The fonts in `src/public/fonts` (B612, B612 Mono, IBM Plex Sans) are under the
|
|
257
|
+
SIL Open Font License; their licences sit next to them.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
Airpocket lets a phone read and type into coding agents on your Macs, so its
|
|
4
|
+
security matters more than its size suggests. Thank you for looking.
|
|
5
|
+
|
|
6
|
+
## Reporting a vulnerability
|
|
7
|
+
|
|
8
|
+
**Please don't open a public issue, discussion or pull request for a security
|
|
9
|
+
problem.**
|
|
10
|
+
|
|
11
|
+
Report it privately through GitHub: on this repository, open the **Security**
|
|
12
|
+
tab and choose **Report a vulnerability**. Only the maintainers can see the
|
|
13
|
+
report and the conversation that follows.
|
|
14
|
+
|
|
15
|
+
If that route isn't open to you — GitHub only offers it on a public
|
|
16
|
+
repository, and not everyone wants a GitHub account to report a bug — email
|
|
17
|
+
**info@airpocket.sh** instead. It reaches the same people.
|
|
18
|
+
|
|
19
|
+
A good report says:
|
|
20
|
+
|
|
21
|
+
- what the problem is, and what someone could do with it;
|
|
22
|
+
- where it is (file, function, route or command);
|
|
23
|
+
- the version or commit you tested;
|
|
24
|
+
- how to see it happen on your own Mac, phone and tailnet.
|
|
25
|
+
|
|
26
|
+
Leave out real device tokens, pairing codes, hook secrets, tailnet names,
|
|
27
|
+
email addresses and anyone's conversations or files. Replace them with
|
|
28
|
+
placeholders.
|
|
29
|
+
|
|
30
|
+
## What to expect
|
|
31
|
+
|
|
32
|
+
- **Within 7 days:** we confirm we received the report.
|
|
33
|
+
- **Within 14 days:** a first assessment: in scope or not, how serious, and
|
|
34
|
+
what happens next.
|
|
35
|
+
- **Until it's fixed:** an update at least every 14 days.
|
|
36
|
+
- **A fix** as soon as its severity calls for it, released with a GitHub
|
|
37
|
+
security advisory that describes the problem and the fix.
|
|
38
|
+
- **Disclosure:** please keep the details private until the advisory is out,
|
|
39
|
+
or 90 days after your report, whichever comes first. If a fix needs longer,
|
|
40
|
+
we'll ask you and agree on a date together.
|
|
41
|
+
- **Credit** in the advisory, under the name you choose, if you want it.
|
|
42
|
+
|
|
43
|
+
Airpocket is a free project. There is no bug bounty.
|
|
44
|
+
|
|
45
|
+
## Supported versions
|
|
46
|
+
|
|
47
|
+
Only the latest release gets security fixes. Fixes are not backported. Until
|
|
48
|
+
the first release, that means the `main` branch.
|
|
49
|
+
|
|
50
|
+
## Scope
|
|
51
|
+
|
|
52
|
+
In scope:
|
|
53
|
+
|
|
54
|
+
- The Airpocket service: the phone app server and its API, the file-only
|
|
55
|
+
server and the hook endpoint.
|
|
56
|
+
- The hook (`src/hook.js`), the `airpocket` command, and the setup and
|
|
57
|
+
uninstall steps once they ship.
|
|
58
|
+
- The phone web app (`src/public`).
|
|
59
|
+
- Anything that breaks a promise in [docs/THREAT-MODEL.md](docs/THREAT-MODEL.md)
|
|
60
|
+
or in the posture below. For example: reaching the API without a valid
|
|
61
|
+
token; opening a file outside the file rules; typing into a terminal that
|
|
62
|
+
isn't the agent's; a token, secret or file's contents in a log; data leaving
|
|
63
|
+
the user's own machines.
|
|
64
|
+
|
|
65
|
+
Out of scope:
|
|
66
|
+
|
|
67
|
+
- A Mac account that is already compromised, including any process running as
|
|
68
|
+
that user.
|
|
69
|
+
- Physical access to an unlocked phone.
|
|
70
|
+
- What a user's own agent chooses to do.
|
|
71
|
+
- Bugs in Tailscale, Claude Code, Codex, Superset, tmux, macOS or iOS. Please
|
|
72
|
+
report those to their makers. If Airpocket *uses* one of them unsafely, that
|
|
73
|
+
is in scope.
|
|
74
|
+
- Setups Airpocket doesn't make: `tailscale funnel`, listening on addresses
|
|
75
|
+
other than 127.0.0.1, another proxy in front of it.
|
|
76
|
+
- Denial of service by someone already on your tailnet or your Mac.
|
|
77
|
+
- The residual risks already listed in the threat model, unless you show one
|
|
78
|
+
is worse than written there.
|
|
79
|
+
|
|
80
|
+
When testing, use only Macs, phones and tailnets that are yours. Don't read,
|
|
81
|
+
change or keep other people's data; if you come across any, stop and tell us.
|
|
82
|
+
We won't pursue anyone who follows this policy in good faith.
|
|
83
|
+
|
|
84
|
+
## Standing posture
|
|
85
|
+
|
|
86
|
+
These hold for every release. A change that breaks one of them won't be
|
|
87
|
+
accepted.
|
|
88
|
+
|
|
89
|
+
- **Tailnet only.** Every server listens on 127.0.0.1. `airpocket setup`
|
|
90
|
+
publishes the app and the file-only origin with `tailscale serve`, which
|
|
91
|
+
reaches only devices on your tailnet. Airpocket never uses `tailscale
|
|
92
|
+
funnel` and never publishes anything to the public internet. The hook
|
|
93
|
+
endpoint is never published at all.
|
|
94
|
+
- **No cloud.** There is no Airpocket server, relay or hosted service. Your
|
|
95
|
+
phone talks straight to your Macs over your own Tailscale network.
|
|
96
|
+
- **No account.** Nothing to sign up for. Each phone pairs with each Mac using
|
|
97
|
+
a short-lived code shown on that Mac, and gets its own token; the Mac keeps
|
|
98
|
+
only a hash of it.
|
|
99
|
+
- **Nothing leaves your machines.** Airpocket has no telemetry, analytics,
|
|
100
|
+
crash reporting or update checks, and makes no network requests except
|
|
101
|
+
between your phone and your Macs. Your conversations, files and tokens stay
|
|
102
|
+
on your devices. Your agents still talk to their own providers, as they do
|
|
103
|
+
without Airpocket, and Tailscale's coordination service knows which devices
|
|
104
|
+
you have, not what they send.
|
|
105
|
+
- **The phone asks, the Mac decides.** The phone names a session and an
|
|
106
|
+
action. It never sends a file path, a terminal, or the text of a Controls
|
|
107
|
+
command; the Mac looks those up in its own records and fixed tables.
|
|
108
|
+
- **One way in, one place to land.** The only thing a phone can put on the Mac
|
|
109
|
+
is a photo for a session, and it lands in Airpocket's own data folder, 0600,
|
|
110
|
+
never in a project. The bytes have to be an image the agents can read, the
|
|
111
|
+
phone gets a ref rather than a path, and old photos are swept.
|
|
112
|
+
|
|
113
|
+
How Airpocket defends each of these, and what it knowingly doesn't, is in
|
|
114
|
+
[docs/THREAT-MODEL.md](docs/THREAT-MODEL.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "airpocket",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Your Claude Code and Codex sessions, from every Mac, on your phone.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude-code",
|
|
7
|
+
"codex",
|
|
8
|
+
"tailscale",
|
|
9
|
+
"iphone",
|
|
10
|
+
"terminal",
|
|
11
|
+
"agents",
|
|
12
|
+
"macos"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://airpocket.sh",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/airpocket-sh/airpocket/issues"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/airpocket-sh/airpocket.git"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "Oncel Ozgul",
|
|
24
|
+
"main": "src/server.js",
|
|
25
|
+
"bin": {
|
|
26
|
+
"airpocket": "src/cli.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"src/",
|
|
30
|
+
"SECURITY.md",
|
|
31
|
+
"CHANGELOG.md"
|
|
32
|
+
],
|
|
33
|
+
"os": [
|
|
34
|
+
"darwin"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"start": "node src/server.js",
|
|
41
|
+
"test": "node --test",
|
|
42
|
+
"smoke": "node .github/smoke.js"
|
|
43
|
+
}
|
|
44
|
+
}
|