hitchrail 0.1.0__tar.gz

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.
Files changed (34) hide show
  1. hitchrail-0.1.0/LICENSE +21 -0
  2. hitchrail-0.1.0/PKG-INFO +317 -0
  3. hitchrail-0.1.0/README.md +293 -0
  4. hitchrail-0.1.0/pyproject.toml +197 -0
  5. hitchrail-0.1.0/pyproject.toml.orig +261 -0
  6. hitchrail-0.1.0/src/hitchrail/__init__.py +10 -0
  7. hitchrail-0.1.0/src/hitchrail/claude_ipc.py +571 -0
  8. hitchrail-0.1.0/src/hitchrail/cli.py +326 -0
  9. hitchrail-0.1.0/src/hitchrail/config.py +468 -0
  10. hitchrail-0.1.0/src/hitchrail/derive.py +261 -0
  11. hitchrail-0.1.0/src/hitchrail/discovery.py +359 -0
  12. hitchrail-0.1.0/src/hitchrail/engine.py +785 -0
  13. hitchrail-0.1.0/src/hitchrail/events.py +150 -0
  14. hitchrail-0.1.0/src/hitchrail/headers.py +157 -0
  15. hitchrail-0.1.0/src/hitchrail/hostnames.py +239 -0
  16. hitchrail-0.1.0/src/hitchrail/pages.py +93 -0
  17. hitchrail-0.1.0/src/hitchrail/procs.py +202 -0
  18. hitchrail-0.1.0/src/hitchrail/projectnames.py +133 -0
  19. hitchrail-0.1.0/src/hitchrail/py.typed +0 -0
  20. hitchrail-0.1.0/src/hitchrail/ram.py +126 -0
  21. hitchrail-0.1.0/src/hitchrail/security.py +409 -0
  22. hitchrail-0.1.0/src/hitchrail/server.py +513 -0
  23. hitchrail-0.1.0/src/hitchrail/sessions.py +207 -0
  24. hitchrail-0.1.0/src/hitchrail/tmux.py +522 -0
  25. hitchrail-0.1.0/src/hitchrail/web/app.css +404 -0
  26. hitchrail-0.1.0/src/hitchrail/web/app.js +1382 -0
  27. hitchrail-0.1.0/src/hitchrail/web/fonts/IBMPlexMono-400.woff2 +0 -0
  28. hitchrail-0.1.0/src/hitchrail/web/fonts/Karla-400.woff2 +0 -0
  29. hitchrail-0.1.0/src/hitchrail/web/fonts/Karla-600.woff2 +0 -0
  30. hitchrail-0.1.0/src/hitchrail/web/fonts/Karla-700.woff2 +0 -0
  31. hitchrail-0.1.0/src/hitchrail/web/fonts/ZillaSlab-500.woff2 +0 -0
  32. hitchrail-0.1.0/src/hitchrail/web/fonts/ZillaSlab-700.woff2 +0 -0
  33. hitchrail-0.1.0/src/hitchrail/web/grant.html +229 -0
  34. hitchrail-0.1.0/src/hitchrail/web/index.html +61 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 agigante80
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.
@@ -0,0 +1,317 @@
1
+ Metadata-Version: 2.4
2
+ Name: hitchrail
3
+ Version: 0.1.0
4
+ Summary: Start and stop headless Claude Code sessions across a folder of projects, from your phone.
5
+ Keywords: claude-code,tmux,session-manager,self-hosted
6
+ Author: agigante80
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Web Environment
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Dist: starlette>=1.6,<2
18
+ Requires-Dist: uvicorn>=0.52,<1
19
+ Requires-Dist: sse-starlette>=3.4,<4
20
+ Requires-Python: >=3.11
21
+ Project-URL: Homepage, https://github.com/agigante80/hitchrail
22
+ Project-URL: Issues, https://github.com/agigante80/hitchrail/issues
23
+ Description-Content-Type: text/markdown
24
+
25
+ # hitchrail
26
+
27
+ A web UI for starting and stopping headless Claude Code sessions across a
28
+ folder of projects. Open it on your phone, tap a folder, get a session link.
29
+
30
+ **Status: it runs.** Phases 0 to 6 are built and closed: the configuration and
31
+ its refusals, the folder discovery that makes the root a hard boundary, the
32
+ three security controls between a web page and a shell, the adapters, the
33
+ engine, the HTTP API and the browser interface. It has been driven from a real
34
+ phone against a real machine.
35
+
36
+ **It is not on PyPI yet**, so the `uvx` commands below do not work. Until they
37
+ do, run it from a clone: see [Run it](#run-it).
38
+
39
+ See [`docs/roadmap.md`](docs/roadmap.md) for what is left,
40
+ [`docs/superpowers/specs/2026-08-25-hitchrail-design.md`](docs/superpowers/specs/2026-08-25-hitchrail-design.md)
41
+ for the design, and [`docs/tech-guidelines.md`](docs/tech-guidelines.md) for the
42
+ engineering rules that govern the code.
43
+
44
+ ## What it will do
45
+
46
+ Point it at a directory. It lists every folder inside, shows which ones have a
47
+ live Claude Code session, and lets you start or stop one with a single tap. It
48
+ shows memory pressure, refuses to start a session that would exhaust the
49
+ machine, and tails a session's output when you want to know what it is doing.
50
+
51
+ Stopping is a sequence rather than a button: it asks the agent to wrap up, shows
52
+ you the wait, and keeps a kill control within reach the whole time if you would
53
+ rather not wait.
54
+
55
+ ## What it looks like
56
+
57
+ The phone case first, because it is the one this exists for.
58
+
59
+ | | |
60
+ |---|---|
61
+ | <img src="docs/screenshots/phone-list.png" alt="The project list on a phone: four folders showing running, stopped, detached with its pid, and stale" width="300"> | <img src="docs/screenshots/phone-list-dark.png" alt="The same list in the dark theme" width="300"> |
62
+
63
+ Four derived states in one listing: `running` with its memory and uptime,
64
+ `stopped`, `detached` with the pid of an agent that outlived its terminal, and
65
+ `stale` where a terminal outlived its agent.
66
+
67
+ <img src="docs/screenshots/desktop-list.png" alt="The same list at a desktop width" width="620">
68
+
69
+ These are captured from the running application against a scratch root, not
70
+ taken by hand: `uv run pytest -m screenshots` regenerates every one of them.
71
+
72
+ ## What it costs you to run this
73
+
74
+ Hitchrail starts `claude --dangerously-skip-permissions`. **Anyone who can reach
75
+ its API can run arbitrary code on that machine as you.**
76
+
77
+ Every control below is built and tested, including on a real socket rather than
78
+ only in theory, and the API is now behind them. None of it is optional, and
79
+ none of it is a reason to run this on a network you do not trust.
80
+
81
+ The browser interface is built. The list, search and filtering, starting, the
82
+ stop sequence with its escalation, the log tail, creating a folder, the memory
83
+ footer, live updates over SSE with reconnection, the token screen and the dark
84
+ theme all work in a browser, and the end to end tier drives them. The warning
85
+ above applies to all of it exactly as written.
86
+
87
+ It binds to loopback with no authentication by default. Binding it to any other
88
+ interface requires a token, and the server refuses to start without one. It
89
+ validates the `Host` header on every request, because a localhost service
90
+ without that check can be driven by any website you visit, through DNS
91
+ rebinding. Over plain HTTP on a LAN the token crosses the network in cleartext;
92
+ put a TLS terminating reverse proxy in front of it if that matters to you.
93
+
94
+ Behind such a proxy, tell Hitchrail the origin the browser will actually send,
95
+ because it cannot be derived: the scheme and the port are the proxy's, not
96
+ ours.
97
+
98
+ ```sh
99
+ hitchrail --root ~/dev --host 0.0.0.0 --allow-host box.lan \
100
+ --allow-origin https://box.lan
101
+ ```
102
+
103
+ A trailing root dot makes no difference here: `box.lan` and `box.lan.` name the
104
+ same machine, so either spelling is accepted and either is matched. Browsers do
105
+ send the dotted form, because typing `http://box.lan./` is a way to force
106
+ absolute resolution on a split horizon network.
107
+
108
+ Getting the token onto a phone is a link rather than 32 characters of typing.
109
+ Open `http://<address>:8787/grant#token=<token>` once. The token is everything
110
+ after the `#`, and a fragment is never sent to a server: not to Hitchrail, not
111
+ to a reverse proxy, and not in a `Referer` header. The page reads it in the
112
+ browser, trades it for a cookie, and clears the address bar.
113
+
114
+ `hitchrail` prints that link for every address it can be reached on, so it is
115
+ copied rather than typed.
116
+
117
+ Treat the link as a secret anyway, because it is one, and the phone it lands on
118
+ is where it now lives. What changed is the set of machines that write it down.
119
+ Every server side one is gone: this server's access log, any proxy in front,
120
+ and the `Referer` header on anything the page fetches.
121
+
122
+ The browser is narrowed rather than cleared, and the difference is worth
123
+ stating rather than rounding off. The page rewrites its own history entry, so
124
+ the entry does not keep the key. Pasting the link into the address bar is
125
+ another matter: that can leave a typed URL in autocomplete, and autocomplete
126
+ syncs. Open the link by tapping it rather than by pasting it, and the
127
+ distinction does not arise.
128
+
129
+ The older `?token=<token>` form is gone. It is a query parameter now, not a
130
+ credential: a request carrying one is refused like any other request with no
131
+ token, and it appears in the server's log like any other query string.
132
+
133
+ Hitchrail does not sandbox the sessions it starts. It is a launcher. The agent it
134
+ launches has whatever access you have.
135
+
136
+ **Whoever holds the token can cause characters to be typed into any agent
137
+ session under your root.** Stopping an agent works by sending it keystrokes
138
+ through its terminal, and an agent reading its own input cannot tell those from
139
+ you typing. That is what makes a gentle stop possible at all, and it is worth
140
+ reading rather than discovering. Hitchrail only ever sends the stop sequence,
141
+ and one test enforces that only the module owning it may send anything.
142
+
143
+ Hitchrail cannot end a `detached` agent, the state where a process outlived its
144
+ terminal. It shows the pid and stops there, because everything it can destroy
145
+ is addressed by the session name it created, and signalling a bare pid would be
146
+ the first thing outside that.
147
+
148
+ **Found a hole?** [`SECURITY.md`](SECURITY.md) says what is in scope, what is
149
+ this design rather than a bug, and where to report privately. Please do not
150
+ open a public issue for a vulnerability.
151
+
152
+ ## Prerequisites
153
+
154
+ Hitchrail is a launcher, so the things it launches have to already be there. It
155
+ does not vendor or install any of them.
156
+
157
+ | Needed | Why | Checked |
158
+ |---|---|---|
159
+ | **tmux** | every session Hitchrail starts lives in a tmux session; this is the whole mechanism, not an option | `tmux -V` |
160
+ | **Claude Code on `PATH`** | it is what Hitchrail runs. Configurable with `--agent-binary` | `claude --version` |
161
+ | **Linux** | memory pressure is read from `/proc/meminfo`, and the process table from `ps`. macOS has neither in this form, which is why the package declares `Operating System :: POSIX :: Linux` | |
162
+ | **Python 3.11+** | `uvx` and `pipx` handle this for you | `python3 --version` |
163
+
164
+ Installing Hitchrail with `uvx` will succeed on a machine with no tmux and no
165
+ Claude Code, because neither is a Python dependency. It will then fail at the
166
+ first attempt to start a session. Check the two commands above first.
167
+
168
+ ## Run it
169
+
170
+ Nothing is published yet, so this is the way in. It needs `uv`, `tmux` and
171
+ Claude Code on `PATH`, per the table above.
172
+
173
+ ```sh
174
+ git clone https://github.com/agigante80/hitchrail
175
+ cd hitchrail
176
+ uv run hitchrail --root ~/projects
177
+ ```
178
+
179
+ `--root` is the folder holding your projects. Every directory directly inside it
180
+ becomes a row. **Point it at a scratch folder the first time**: Hitchrail only
181
+ recognises the tmux sessions it started itself, so starting a project that
182
+ already has a session from another tool gives you a second agent in the same
183
+ directory.
184
+
185
+ On loopback that is all.
186
+
187
+ To open it from your phone you have to decide how the phone reaches it, and
188
+ there are three answers with an order to them. **[`docs/guides/phone-access.md`](docs/guides/phone-access.md)
189
+ is that decision**, best first: an overlay network such as `tailscale serve`,
190
+ which needs no inbound port and survives the machine changing networks; a named
191
+ LAN address; and never the wildcard. The default is loopback, so the safe thing
192
+ is what happens when you pass nothing.
193
+
194
+ The middle route is the one with no prerequisites, and it is the one shown
195
+ here. Bind to the machine's LAN address and it prints a link to tap:
196
+
197
+ ```sh
198
+ uv run hitchrail --root ~/projects --host 192.168.1.10
199
+ ```
200
+
201
+ ```
202
+ token: <generated>
203
+ Anyone with this token can run code on this machine as you.
204
+
205
+ Open one of these on your phone:
206
+ http://192.168.1.10:8787/grant#token=<generated>
207
+ ```
208
+
209
+ A token is generated and REQUIRED as soon as anything outside this machine can
210
+ reach Hitchrail. Binding off loopback is one way to say so; passing
211
+ `--allow-host` or `--allow-origin` for a name that is not loopback is the other,
212
+ because that is what you do to put Hitchrail behind a proxy such as
213
+ `tailscale serve`. In both cases the server refuses to start without one. Everything after the `#` stays in the browser and
214
+ reaches no server log. Over plain HTTP the cookie it becomes still crosses your
215
+ network in clear, so put TLS in front of it if that matters to you.
216
+
217
+ ### Where the token comes from
218
+
219
+ In order: `--token`, then `HITCHRAIL_TOKEN` in the environment, then one
220
+ generated for you and printed.
221
+
222
+ **Prefer the environment variable to the flag on any machine you share.** On
223
+ Linux `/proc/<pid>/cmdline` is world readable and `/proc/<pid>/environ` is not,
224
+ so `--token` shows your token to every other account on the box, and `ps` does
225
+ it for them without their having to try. The environment is readable only by
226
+ you and root.
227
+
228
+ `HITCHRAIL_TOKEN` set but empty is refused rather than treated as absent. An
229
+ operator who writes it into a file and leaves the value off has not configured
230
+ authentication, and Hitchrail says so instead of quietly generating one.
231
+
232
+ It is also what makes a long running Hitchrail usable: a generated token
233
+ changes on every start, so a service that restarts invalidates the link saved
234
+ on your phone. A token from the environment survives.
235
+
236
+ ### Keeping it running
237
+
238
+ Hitchrail dies when you close the terminal, and a phone is useful precisely
239
+ when you are not at the machine. `packaging/hitchrail.service` is a systemd
240
+ **user** unit template: copy it, edit the paths, and it survives logout and
241
+ reboot.
242
+
243
+ ```sh
244
+ cp packaging/hitchrail.service ~/.config/systemd/user/
245
+ systemctl --user enable --now hitchrail
246
+ loginctl enable-linger "$USER"
247
+ ```
248
+
249
+ A user unit rather than a system one, because Hitchrail spawns agents as you
250
+ and a `User=` invites running a shell as root. `Restart=on-failure` rather than
251
+ `always`, because a configuration Hitchrail refuses is a deliberate stop and a
252
+ boot loop would bury the reason. The token must come from the unit's
253
+ `EnvironmentFile` at mode 600: a generated one changes every restart and kills
254
+ the link on your phone, and the banner withholds it from the journal anyway.
255
+
256
+ **An always on service is a standing exposure rather than a session shaped
257
+ one.** Until now the window in which this was reachable was the window in which
258
+ you were watching it. Read [`docs/guides/phone-access.md`](docs/guides/phone-access.md)
259
+ before enabling it.
260
+
261
+ ## Install
262
+
263
+ **Not yet.** `hitchrail` is not on PyPI, so none of these work today. They are
264
+ here so the intended shape is on the record, and they become true at Phase 8.
265
+
266
+ Hitchrail is a Python package, so the equivalent of `npx` here is `uvx`:
267
+
268
+ ```sh
269
+ uvx hitchrail # run it, install nothing
270
+ uv tool install hitchrail # keep it on PATH
271
+ pipx install hitchrail # if you already live in pipx
272
+ ```
273
+
274
+ One word, no hyphen.
275
+
276
+ **The service route is `uv tool install`, not `uvx`.** `uvx` resolves and runs
277
+ out of a cache it is free to evict, which is what makes it good for trying
278
+ something and wrong for a unit: the systemd unit needs an executable path that
279
+ is still there next month. That is why the template's `ExecStart` names
280
+ `~/.local/bin/hitchrail`.
281
+
282
+ ## Working on it
283
+
284
+ ```sh
285
+ uv sync # set up
286
+ uv run pytest # tests
287
+ uv run ruff check # lint
288
+ uv run ruff format # format
289
+ uv run mypy # types
290
+ uv run lint-imports # module boundaries
291
+ ```
292
+
293
+ All five are blocking in CI on 3.11, 3.12 and 3.13. The last one is the
294
+ unusual one: it enforces that the engine layer never imports Starlette,
295
+ uvicorn, `sse_starlette`, the server or the CLI, so the engine stays testable
296
+ without HTTP. Import boundaries defended only by good intentions do not
297
+ survive.
298
+
299
+ ## Documents
300
+
301
+ | | |
302
+ |---|---|
303
+ | [`docs/api.md`](docs/api.md) | the HTTP API: routes, auth, and every error code |
304
+ | [`SECURITY.md`](SECURITY.md) | what is in scope, and where to report it privately |
305
+ | [`CONTRIBUTING.md`](CONTRIBUTING.md) | how a change is expected to arrive |
306
+ | [`CHANGELOG.md`](CHANGELOG.md) | what upgrading costs you |
307
+ | [`docs/releasing.md`](docs/releasing.md) | how a release is cut and published |
308
+ | [`AGENTS.md`](AGENTS.md) | the architecture and the non negotiables |
309
+
310
+ ## Not affiliated with Anthropic
311
+
312
+ Hitchrail is an independent open source tool. Claude and Claude Code are
313
+ trademarks of Anthropic.
314
+
315
+ ## Licence
316
+
317
+ MIT.
@@ -0,0 +1,293 @@
1
+ # hitchrail
2
+
3
+ A web UI for starting and stopping headless Claude Code sessions across a
4
+ folder of projects. Open it on your phone, tap a folder, get a session link.
5
+
6
+ **Status: it runs.** Phases 0 to 6 are built and closed: the configuration and
7
+ its refusals, the folder discovery that makes the root a hard boundary, the
8
+ three security controls between a web page and a shell, the adapters, the
9
+ engine, the HTTP API and the browser interface. It has been driven from a real
10
+ phone against a real machine.
11
+
12
+ **It is not on PyPI yet**, so the `uvx` commands below do not work. Until they
13
+ do, run it from a clone: see [Run it](#run-it).
14
+
15
+ See [`docs/roadmap.md`](docs/roadmap.md) for what is left,
16
+ [`docs/superpowers/specs/2026-08-25-hitchrail-design.md`](docs/superpowers/specs/2026-08-25-hitchrail-design.md)
17
+ for the design, and [`docs/tech-guidelines.md`](docs/tech-guidelines.md) for the
18
+ engineering rules that govern the code.
19
+
20
+ ## What it will do
21
+
22
+ Point it at a directory. It lists every folder inside, shows which ones have a
23
+ live Claude Code session, and lets you start or stop one with a single tap. It
24
+ shows memory pressure, refuses to start a session that would exhaust the
25
+ machine, and tails a session's output when you want to know what it is doing.
26
+
27
+ Stopping is a sequence rather than a button: it asks the agent to wrap up, shows
28
+ you the wait, and keeps a kill control within reach the whole time if you would
29
+ rather not wait.
30
+
31
+ ## What it looks like
32
+
33
+ The phone case first, because it is the one this exists for.
34
+
35
+ | | |
36
+ |---|---|
37
+ | <img src="docs/screenshots/phone-list.png" alt="The project list on a phone: four folders showing running, stopped, detached with its pid, and stale" width="300"> | <img src="docs/screenshots/phone-list-dark.png" alt="The same list in the dark theme" width="300"> |
38
+
39
+ Four derived states in one listing: `running` with its memory and uptime,
40
+ `stopped`, `detached` with the pid of an agent that outlived its terminal, and
41
+ `stale` where a terminal outlived its agent.
42
+
43
+ <img src="docs/screenshots/desktop-list.png" alt="The same list at a desktop width" width="620">
44
+
45
+ These are captured from the running application against a scratch root, not
46
+ taken by hand: `uv run pytest -m screenshots` regenerates every one of them.
47
+
48
+ ## What it costs you to run this
49
+
50
+ Hitchrail starts `claude --dangerously-skip-permissions`. **Anyone who can reach
51
+ its API can run arbitrary code on that machine as you.**
52
+
53
+ Every control below is built and tested, including on a real socket rather than
54
+ only in theory, and the API is now behind them. None of it is optional, and
55
+ none of it is a reason to run this on a network you do not trust.
56
+
57
+ The browser interface is built. The list, search and filtering, starting, the
58
+ stop sequence with its escalation, the log tail, creating a folder, the memory
59
+ footer, live updates over SSE with reconnection, the token screen and the dark
60
+ theme all work in a browser, and the end to end tier drives them. The warning
61
+ above applies to all of it exactly as written.
62
+
63
+ It binds to loopback with no authentication by default. Binding it to any other
64
+ interface requires a token, and the server refuses to start without one. It
65
+ validates the `Host` header on every request, because a localhost service
66
+ without that check can be driven by any website you visit, through DNS
67
+ rebinding. Over plain HTTP on a LAN the token crosses the network in cleartext;
68
+ put a TLS terminating reverse proxy in front of it if that matters to you.
69
+
70
+ Behind such a proxy, tell Hitchrail the origin the browser will actually send,
71
+ because it cannot be derived: the scheme and the port are the proxy's, not
72
+ ours.
73
+
74
+ ```sh
75
+ hitchrail --root ~/dev --host 0.0.0.0 --allow-host box.lan \
76
+ --allow-origin https://box.lan
77
+ ```
78
+
79
+ A trailing root dot makes no difference here: `box.lan` and `box.lan.` name the
80
+ same machine, so either spelling is accepted and either is matched. Browsers do
81
+ send the dotted form, because typing `http://box.lan./` is a way to force
82
+ absolute resolution on a split horizon network.
83
+
84
+ Getting the token onto a phone is a link rather than 32 characters of typing.
85
+ Open `http://<address>:8787/grant#token=<token>` once. The token is everything
86
+ after the `#`, and a fragment is never sent to a server: not to Hitchrail, not
87
+ to a reverse proxy, and not in a `Referer` header. The page reads it in the
88
+ browser, trades it for a cookie, and clears the address bar.
89
+
90
+ `hitchrail` prints that link for every address it can be reached on, so it is
91
+ copied rather than typed.
92
+
93
+ Treat the link as a secret anyway, because it is one, and the phone it lands on
94
+ is where it now lives. What changed is the set of machines that write it down.
95
+ Every server side one is gone: this server's access log, any proxy in front,
96
+ and the `Referer` header on anything the page fetches.
97
+
98
+ The browser is narrowed rather than cleared, and the difference is worth
99
+ stating rather than rounding off. The page rewrites its own history entry, so
100
+ the entry does not keep the key. Pasting the link into the address bar is
101
+ another matter: that can leave a typed URL in autocomplete, and autocomplete
102
+ syncs. Open the link by tapping it rather than by pasting it, and the
103
+ distinction does not arise.
104
+
105
+ The older `?token=<token>` form is gone. It is a query parameter now, not a
106
+ credential: a request carrying one is refused like any other request with no
107
+ token, and it appears in the server's log like any other query string.
108
+
109
+ Hitchrail does not sandbox the sessions it starts. It is a launcher. The agent it
110
+ launches has whatever access you have.
111
+
112
+ **Whoever holds the token can cause characters to be typed into any agent
113
+ session under your root.** Stopping an agent works by sending it keystrokes
114
+ through its terminal, and an agent reading its own input cannot tell those from
115
+ you typing. That is what makes a gentle stop possible at all, and it is worth
116
+ reading rather than discovering. Hitchrail only ever sends the stop sequence,
117
+ and one test enforces that only the module owning it may send anything.
118
+
119
+ Hitchrail cannot end a `detached` agent, the state where a process outlived its
120
+ terminal. It shows the pid and stops there, because everything it can destroy
121
+ is addressed by the session name it created, and signalling a bare pid would be
122
+ the first thing outside that.
123
+
124
+ **Found a hole?** [`SECURITY.md`](SECURITY.md) says what is in scope, what is
125
+ this design rather than a bug, and where to report privately. Please do not
126
+ open a public issue for a vulnerability.
127
+
128
+ ## Prerequisites
129
+
130
+ Hitchrail is a launcher, so the things it launches have to already be there. It
131
+ does not vendor or install any of them.
132
+
133
+ | Needed | Why | Checked |
134
+ |---|---|---|
135
+ | **tmux** | every session Hitchrail starts lives in a tmux session; this is the whole mechanism, not an option | `tmux -V` |
136
+ | **Claude Code on `PATH`** | it is what Hitchrail runs. Configurable with `--agent-binary` | `claude --version` |
137
+ | **Linux** | memory pressure is read from `/proc/meminfo`, and the process table from `ps`. macOS has neither in this form, which is why the package declares `Operating System :: POSIX :: Linux` | |
138
+ | **Python 3.11+** | `uvx` and `pipx` handle this for you | `python3 --version` |
139
+
140
+ Installing Hitchrail with `uvx` will succeed on a machine with no tmux and no
141
+ Claude Code, because neither is a Python dependency. It will then fail at the
142
+ first attempt to start a session. Check the two commands above first.
143
+
144
+ ## Run it
145
+
146
+ Nothing is published yet, so this is the way in. It needs `uv`, `tmux` and
147
+ Claude Code on `PATH`, per the table above.
148
+
149
+ ```sh
150
+ git clone https://github.com/agigante80/hitchrail
151
+ cd hitchrail
152
+ uv run hitchrail --root ~/projects
153
+ ```
154
+
155
+ `--root` is the folder holding your projects. Every directory directly inside it
156
+ becomes a row. **Point it at a scratch folder the first time**: Hitchrail only
157
+ recognises the tmux sessions it started itself, so starting a project that
158
+ already has a session from another tool gives you a second agent in the same
159
+ directory.
160
+
161
+ On loopback that is all.
162
+
163
+ To open it from your phone you have to decide how the phone reaches it, and
164
+ there are three answers with an order to them. **[`docs/guides/phone-access.md`](docs/guides/phone-access.md)
165
+ is that decision**, best first: an overlay network such as `tailscale serve`,
166
+ which needs no inbound port and survives the machine changing networks; a named
167
+ LAN address; and never the wildcard. The default is loopback, so the safe thing
168
+ is what happens when you pass nothing.
169
+
170
+ The middle route is the one with no prerequisites, and it is the one shown
171
+ here. Bind to the machine's LAN address and it prints a link to tap:
172
+
173
+ ```sh
174
+ uv run hitchrail --root ~/projects --host 192.168.1.10
175
+ ```
176
+
177
+ ```
178
+ token: <generated>
179
+ Anyone with this token can run code on this machine as you.
180
+
181
+ Open one of these on your phone:
182
+ http://192.168.1.10:8787/grant#token=<generated>
183
+ ```
184
+
185
+ A token is generated and REQUIRED as soon as anything outside this machine can
186
+ reach Hitchrail. Binding off loopback is one way to say so; passing
187
+ `--allow-host` or `--allow-origin` for a name that is not loopback is the other,
188
+ because that is what you do to put Hitchrail behind a proxy such as
189
+ `tailscale serve`. In both cases the server refuses to start without one. Everything after the `#` stays in the browser and
190
+ reaches no server log. Over plain HTTP the cookie it becomes still crosses your
191
+ network in clear, so put TLS in front of it if that matters to you.
192
+
193
+ ### Where the token comes from
194
+
195
+ In order: `--token`, then `HITCHRAIL_TOKEN` in the environment, then one
196
+ generated for you and printed.
197
+
198
+ **Prefer the environment variable to the flag on any machine you share.** On
199
+ Linux `/proc/<pid>/cmdline` is world readable and `/proc/<pid>/environ` is not,
200
+ so `--token` shows your token to every other account on the box, and `ps` does
201
+ it for them without their having to try. The environment is readable only by
202
+ you and root.
203
+
204
+ `HITCHRAIL_TOKEN` set but empty is refused rather than treated as absent. An
205
+ operator who writes it into a file and leaves the value off has not configured
206
+ authentication, and Hitchrail says so instead of quietly generating one.
207
+
208
+ It is also what makes a long running Hitchrail usable: a generated token
209
+ changes on every start, so a service that restarts invalidates the link saved
210
+ on your phone. A token from the environment survives.
211
+
212
+ ### Keeping it running
213
+
214
+ Hitchrail dies when you close the terminal, and a phone is useful precisely
215
+ when you are not at the machine. `packaging/hitchrail.service` is a systemd
216
+ **user** unit template: copy it, edit the paths, and it survives logout and
217
+ reboot.
218
+
219
+ ```sh
220
+ cp packaging/hitchrail.service ~/.config/systemd/user/
221
+ systemctl --user enable --now hitchrail
222
+ loginctl enable-linger "$USER"
223
+ ```
224
+
225
+ A user unit rather than a system one, because Hitchrail spawns agents as you
226
+ and a `User=` invites running a shell as root. `Restart=on-failure` rather than
227
+ `always`, because a configuration Hitchrail refuses is a deliberate stop and a
228
+ boot loop would bury the reason. The token must come from the unit's
229
+ `EnvironmentFile` at mode 600: a generated one changes every restart and kills
230
+ the link on your phone, and the banner withholds it from the journal anyway.
231
+
232
+ **An always on service is a standing exposure rather than a session shaped
233
+ one.** Until now the window in which this was reachable was the window in which
234
+ you were watching it. Read [`docs/guides/phone-access.md`](docs/guides/phone-access.md)
235
+ before enabling it.
236
+
237
+ ## Install
238
+
239
+ **Not yet.** `hitchrail` is not on PyPI, so none of these work today. They are
240
+ here so the intended shape is on the record, and they become true at Phase 8.
241
+
242
+ Hitchrail is a Python package, so the equivalent of `npx` here is `uvx`:
243
+
244
+ ```sh
245
+ uvx hitchrail # run it, install nothing
246
+ uv tool install hitchrail # keep it on PATH
247
+ pipx install hitchrail # if you already live in pipx
248
+ ```
249
+
250
+ One word, no hyphen.
251
+
252
+ **The service route is `uv tool install`, not `uvx`.** `uvx` resolves and runs
253
+ out of a cache it is free to evict, which is what makes it good for trying
254
+ something and wrong for a unit: the systemd unit needs an executable path that
255
+ is still there next month. That is why the template's `ExecStart` names
256
+ `~/.local/bin/hitchrail`.
257
+
258
+ ## Working on it
259
+
260
+ ```sh
261
+ uv sync # set up
262
+ uv run pytest # tests
263
+ uv run ruff check # lint
264
+ uv run ruff format # format
265
+ uv run mypy # types
266
+ uv run lint-imports # module boundaries
267
+ ```
268
+
269
+ All five are blocking in CI on 3.11, 3.12 and 3.13. The last one is the
270
+ unusual one: it enforces that the engine layer never imports Starlette,
271
+ uvicorn, `sse_starlette`, the server or the CLI, so the engine stays testable
272
+ without HTTP. Import boundaries defended only by good intentions do not
273
+ survive.
274
+
275
+ ## Documents
276
+
277
+ | | |
278
+ |---|---|
279
+ | [`docs/api.md`](docs/api.md) | the HTTP API: routes, auth, and every error code |
280
+ | [`SECURITY.md`](SECURITY.md) | what is in scope, and where to report it privately |
281
+ | [`CONTRIBUTING.md`](CONTRIBUTING.md) | how a change is expected to arrive |
282
+ | [`CHANGELOG.md`](CHANGELOG.md) | what upgrading costs you |
283
+ | [`docs/releasing.md`](docs/releasing.md) | how a release is cut and published |
284
+ | [`AGENTS.md`](AGENTS.md) | the architecture and the non negotiables |
285
+
286
+ ## Not affiliated with Anthropic
287
+
288
+ Hitchrail is an independent open source tool. Claude and Claude Code are
289
+ trademarks of Anthropic.
290
+
291
+ ## Licence
292
+
293
+ MIT.