session-peer 0.6.0__py3-none-any.whl

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.
@@ -0,0 +1,348 @@
1
+ Metadata-Version: 2.5
2
+ Name: session-peer
3
+ Version: 0.6.0
4
+ Summary: Message coding agent sessions locally and over SSH
5
+ Project-URL: Homepage, https://github.com/abruption/session-peer
6
+ Project-URL: Repository, https://github.com/abruption/session-peer
7
+ Project-URL: Issues, https://github.com/abruption/session-peer/issues
8
+ Author-email: YEONGUK LEE <hashcode@kakao.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: claude-code,cli,codex,cross-session,messaging,ssh
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Software Development
25
+ Classifier: Topic :: System :: Networking
26
+ Classifier: Topic :: Utilities
27
+ Requires-Python: >=3.9
28
+ Description-Content-Type: text/markdown
29
+
30
+ # session-peer
31
+
32
+ Local and SSH messaging for coding agent sessions. This project continues cc-peer;
33
+ its Git history and issue numbers are preserved.
34
+
35
+ ## Codex sessions
36
+
37
+ ```bash
38
+ session-peer list --agent codex
39
+ session-peer list --agent codex --host worker
40
+ session-peer send --to codex:<full-thread-uuid> "message"
41
+ session-peer send --host worker --to codex:<full-thread-uuid> "message"
42
+ session-peer send --to codex:<full-thread-uuid> --dry-run "message"
43
+ ```
44
+
45
+ The default agent remains Claude. Codex discovery reads `state_5.sqlite` using a
46
+ read-only SQLite connection. This internal schema is experimental, tested with
47
+ Codex CLI 0.154.0 on macOS; the core CI also runs on Linux and Windows. Saved
48
+ sessions are not necessarily active. `--all` includes archived threads.
49
+
50
+ `--codex-home` overrides the destination's `CODEX_HOME` (default `~/.codex`).
51
+ `--codex-bin` overrides its PATH lookup of `codex`. On SSH these are remote paths;
52
+ session-peer itself need not be installed remotely, but Codex must be installed.
53
+
54
+ Submission uses `codex queue`, never direct database writes. `queued` means the
55
+ CLI accepted the submission, not that a turn consumed it or acknowledged it.
56
+ session-peer does not wake or resume sessions. Queue DB writes and Claude socket
57
+ connections may require approval in the caller's execution environment; the tool
58
+ does not change sandbox or inbound policies. A timeout has an unknown submission
59
+ outcome: inspect the destination before retrying.
60
+
61
+ Codex messages are limited to 32 KiB of UTF-8 including sender/reply headers, as a
62
+ session-peer portability policy rather than a measured Codex server limit. NUL
63
+ characters cannot be passed as CLI arguments. `--dry-run` verifies the executable
64
+ and saved target but cannot guarantee a later submission will succeed.
65
+
66
+ Codex list JSON retains the `{sessions, version}` local envelope. Each entry has
67
+ `agent`, `id`, `name` (first line, at most 120 characters), `cwd`, `updatedAt` (Unix seconds), and `archived`. Send adds
68
+ `target: {agent, id}` and `status: queued` (or `validated` under dry-run) to the
69
+ existing `{ok, chars, dryRun}` envelope; `queueId` is optional. Remote results
70
+ retain host attribution and status. Claude output stays compatible. A missing
71
+ dry-run target returns exit 2; CLI execution/submission failures return exit 1.
72
+ Existing identity/reply detection identifies Claude senders, not Codex senders.
73
+
74
+ ## Moving from cc-peer
75
+
76
+ Install the new product explicitly: `pipx install session-peer` (or
77
+ `uv tool install session-peer`, or `python -m pip install session-peer` in a virtual
78
+ environment). No `cc-peer` command alias is installed. Both products can coexist.
79
+ After checking your workflows, remove the old package with the same manager that
80
+ installed it, e.g. `pipx uninstall cc-peer`. For a script installation, use the
81
+ `install.sh --uninstall` from the pinned cc-peer v0.5.1 tag; check its paths before
82
+ running it. New uninstall only removes session-peer files.
83
+
84
+ The standalone installer places the program in
85
+ `~/.local/share/session-peer/session_peer.py`, its CLI link in `~/.local/bin`, and
86
+ its Claude skill in `${CLAUDE_CONFIG_DIR:-~/.claude}/skills/session-peer`. Pip
87
+ installs only the CLI. Claude/Codex configuration and old installations are not
88
+ migrated or removed automatically. `SESSION_PEER_REPLY_HOST` takes precedence over
89
+ the compatibility input `CC_PEER_REPLY_HOST`.
90
+
91
+ `cc-peer` 0.5.1 is the final Claude-only compatibility line, not an ongoing feature
92
+ or security-maintenance promise. The frozen root `cc_peer.py` is retained in tags
93
+ for old self-update URLs but is excluded from the new wheel and sdist. Its local
94
+ update command directs users here instead of installing a different product.
95
+
96
+ Package-managed installations must use their package manager to upgrade.
97
+ `session-peer update` replaces only independently installed scripts; remote
98
+ updates push the standalone program to the destination's neutral data directory.
99
+
100
+
101
+ [![PyPI](https://img.shields.io/pypi/v/session-peer)](https://pypi.org/project/session-peer/)
102
+ [![CI](https://github.com/abruption/session-peer/actions/workflows/ci.yml/badge.svg)](https://github.com/abruption/session-peer/actions/workflows/ci.yml)
103
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
104
+ [![Python 3.9+](https://img.shields.io/pypi/pyversions/session-peer)](https://pypi.org/project/session-peer/)
105
+
106
+ For Claude Code, the original SSH inbox workflow remains available:
107
+
108
+ ```console
109
+ $ session-peer list --host build-server
110
+ Sessions on build-server:
111
+ NAME PID STATUS CWD
112
+ api-worker 4011 idle /srv/api
113
+ migration-watch 4614 busy /srv/api
114
+
115
+ $ session-peer send --host build-server --to api-worker "Schema migration landed; rebase is safe now."
116
+ Posted to api-worker's inbox on build-server (44 chars).
117
+ ```
118
+
119
+ ## Use the official feature first
120
+
121
+ Claude Code has [cross-session messaging](https://code.claude.com/docs/en/cross-session-messaging) built in, and it already covers most cases:
122
+
123
+ | Target | How the official feature delivers it |
124
+ | :-- | :-- |
125
+ | Same machine | Per-session Unix socket. Never touches Anthropic's servers. |
126
+ | Another machine | **Through Anthropic's servers**, over [Remote Control](https://code.claude.com/docs/en/remote-control). |
127
+ | Claude on the web | Through Anthropic's servers. |
128
+
129
+ If Remote Control works for you, **use it** — it needs no scripts and it gives the receiving Claude a reply address.
130
+
131
+ `session-peer` is for the cases where it isn't available:
132
+
133
+ - **Bedrock / Vertex / Foundry** — Remote Control is disabled on those providers.
134
+ - **API-key auth** — finding sessions beyond this machine needs a claude.ai sign-in.
135
+ - **Air-gapped or compliance-bound networks** — where relaying through a third party is the problem.
136
+ - **Unattended workers** — a headless box nobody is around to connect to Remote Control.
137
+
138
+ If none of those describe you, you probably don't need this.
139
+
140
+ ## How it works
141
+
142
+ Claude Code binds a Unix domain socket per session as that session's inbox, and documents it under [*The session's inbox socket*](https://code.claude.com/docs/en/cross-session-messaging#the-sessions-inbox-socket) — explicitly *"when you want a script or hook to post into a session"*. Posting is one JSON line:
143
+
144
+ ```json
145
+ {"type":"user","message":{"role":"user","content":"your message"}}
146
+ ```
147
+
148
+ That socket is local to its machine, and forwarding it doesn't help: Claude Code verifies the peer process and uid on the connection, so `ssh -L` gets refused as *"an endpoint that isn't the expected process"*.
149
+
150
+ So `session-peer` doesn't forward the socket. It runs the same write **inside a remote shell**, where the connection is local again. `--host` pipes this script over SSH to `python3 -`, so nothing needs to be installed on the remote machine.
151
+
152
+ ### Read the socket path; never guess it
153
+
154
+ Session records live in `~/.claude/sessions/<pid>.json` and carry the socket path. It is not always `/tmp/cc-socks/` — on two Ubuntu hosts running the same Claude Code build, one bound under `/tmp/cc-socks/` and the other under `/run/user/1001/cc-socks/`. Claude Code also falls back to a private per-user directory when it rejects the one it would have used. `session-peer` reads `messagingSocketPath` out of the record and treats a live PID with no bound socket as unreachable.
155
+
156
+ ## Install
157
+
158
+ Python 3.9+, standard library only — no external dependencies.
159
+
160
+ ### pip
161
+
162
+ ```bash
163
+ pip install session-peer
164
+ ```
165
+
166
+ Or with [pipx](https://pipx.pypa.io/) for an isolated install:
167
+
168
+ ```bash
169
+ pipx install session-peer
170
+ ```
171
+
172
+ pip installs the `session-peer` command but not the [Claude Code skill](#the-skill).
173
+ To add the skill so Claude can use session-peer on its own:
174
+
175
+ ```bash
176
+ mkdir -p ~/.claude/skills/session-peer
177
+ curl -fsSL -o ~/.claude/skills/session-peer/SKILL.md \
178
+ https://raw.githubusercontent.com/abruption/session-peer/main/skills/session-peer/SKILL.md
179
+ ```
180
+
181
+ ### install.sh
182
+
183
+ Installs both the command and the skill in one step. Use this for air-gapped
184
+ hosts or remote deployment over SSH:
185
+
186
+ ```bash
187
+ git clone https://github.com/abruption/session-peer && cd session-peer
188
+
189
+ ./install.sh # this machine
190
+ ./install.sh --host build-server # a remote machine, over SSH
191
+ ./install.sh --host web-01 --host db # several at once
192
+ ```
193
+
194
+ That places `session_peer.py` in `~/.local/share/session-peer/`, installs the
195
+ [Claude Code skill](skills/session-peer/SKILL.md) in `~/.claude/skills/session-peer/`,
196
+ and links `~/.local/bin/session-peer`. Existing cc-peer files are preserved.
197
+ Remove it with `./install.sh --uninstall [--host ...]`.
198
+
199
+ `session-peer update` refreshes this machine from the latest GitHub release. For another machine,
200
+ `./install.sh --host <host>` pushes this copy over SSH — deliberately, since a target with no
201
+ route to GitHub is one of the cases this tool exists for. `session-peer update --host` reports what
202
+ that machine has rather than trying to make it fetch.
203
+
204
+ **Remote installs push the files over the SSH connection itself**, so the target needs
205
+ no internet access — which matters, since air-gapped hosts are one of the reasons this
206
+ exists. It needs `python3` and your SSH access, nothing more.
207
+
208
+ Or skip the installer entirely and copy the one file:
209
+
210
+ ```bash
211
+ curl -O https://raw.githubusercontent.com/abruption/session-peer/main/session_peer.py
212
+ chmod +x session_peer.py
213
+ ```
214
+
215
+ ### The skill
216
+
217
+ Installing puts a skill next to the script, so Claude picks the target session and
218
+ writes the message itself when you ask it to reach a session on another box. The
219
+ script keeps the deterministic part — resolving a session, writing the socket — and
220
+ the skill only decides *what to send where*. Discovery leans on a session-record
221
+ schema that isn't part of Claude Code's documented interface, so it is described in
222
+ prose the agent can adapt rather than hardcoded logic that silently breaks.
223
+
224
+ ## Usage
225
+
226
+ ```bash
227
+ session-peer list # sessions on this machine
228
+ session-peer list --host web-01 # sessions over there
229
+ session-peer list --host web-01 --all # include stale records / no inbox
230
+
231
+ session-peer send --to api-worker "message" # local session
232
+ session-peer send --host web-01 --to api-worker "message"
233
+ session-peer send --host web-01 --to 4011 "message" # address by pid
234
+ git log --oneline -5 | session-peer send --host web-01 --to api-worker - # stdin
235
+
236
+ session-peer send --host web-01 --to api-worker --dry-run "x" # resolve only
237
+ session-peer list --host web-01 --json # machine-readable
238
+
239
+ # Versions. list --host reports what that machine has installed and flags a
240
+ # mismatch, since a host left behind by a release won't say so on its own.
241
+ session-peer update --check # is there a newer release?
242
+ session-peer update # replace this installation
243
+ session-peer update --host web-01 # report what's over there
244
+ session-peer send --host web-01 --ssh-opt=-p --ssh-opt=2222 --to api-worker "..." # note the '='
245
+
246
+ # Envelope. Sends carry who they're from and how to answer, both resolved from
247
+ # the session session-peer is running inside.
248
+ session-peer send --host web-01 --to api-worker --no-reply-to "..." # no return address
249
+ session-peer send --host web-01 --to api-worker --no-from "..." # no From: header
250
+ session-peer send --host web-01 --to api-worker --reply-to 100.64.0.5 "..." # state the address
251
+ ```
252
+
253
+ Exit codes: `0` posted, `1` error, `2` no such session, `130` interrupted (Ctrl-C).
254
+
255
+ ### Environment variables
256
+
257
+ | Variable | Effect |
258
+ | :-- | :-- |
259
+ | `SESSION_PEER_REPLY_HOST` | Override the reply address. Resolution order: `--reply-to` flag → `SESSION_PEER_REPLY_HOST` → auto-detected Tailscale IP. Useful on VPNs where Tailscale isn't installed — set it once instead of passing `--reply-to` on every call. |
260
+ | `CLAUDE_CONFIG_DIR` | Where Claude Code keeps its config (default `~/.claude`). Respected by `session-peer list` for session discovery and by `install.sh` for skill placement. |
261
+ | `ANTHROPIC_CONFIG_DIR` | Fallback if `CLAUDE_CONFIG_DIR` is unset. |
262
+
263
+ ## The receiving side decides what happens next
264
+
265
+ **"Posted" is not "delivered."** Writing to the socket succeeds; whether Claude ever reads the message is up to that session's [inbound controls](https://code.claude.com/docs/en/cross-session-messaging#control-inbound-messages).
266
+
267
+ The default that surprises people: **a session running with `--dangerously-skip-permissions` holds every incoming message for its user's approval**, unless the sender identifies itself as also bypassing. A script can't make that claim, so on an unattended bypass-mode worker your message sits in an approval dialog and expires after `dialogExpiry` (5 minutes by default).
268
+
269
+ For a worker meant to take messages unattended, set this in its settings:
270
+
271
+ ```json
272
+ { "crossSessionInbound": "accept" }
273
+ ```
274
+
275
+ Or start it with `--settings '{"crossSessionInbound":"accept"}'`. Do this deliberately: it means anything that can write to that socket can start a turn on that machine.
276
+
277
+ Two things worth knowing before you set it:
278
+
279
+ - **It applies to sessions that are already running.** No restart needed. Measured on two sessions
280
+ up for 144h and 4h that predated the setting entirely — both took a posted message ~3s later with
281
+ no approval dialog, while still showing `⏵⏵ bypass permissions on`.
282
+ - **User settings are per OS user, not per session.** Putting `accept` in `~/.claude/settings.json`
283
+ opens *every* session that user runs, not just the worker you meant. Scope it with project
284
+ settings or `--settings` if you want one session to accept and the rest to keep asking.
285
+
286
+ ## Why not `tmux send-keys`?
287
+
288
+ `ssh host 'tmux send-keys -t sess "msg" Enter'` needs no script, and for a quick nudge it's fine. It breaks down as soon as timing or payload get interesting:
289
+
290
+ | | `tmux send-keys` | inbox socket |
291
+ | :-- | :-- | :-- |
292
+ | Session is mid-tool-call | Keystrokes land wherever focus is — possibly a subprocess's stdin | Queued, read at a turn boundary |
293
+ | Quotes, backticks, `$`, newlines, emoji | Shell and terminal both get a say | Delivered byte-for-byte in JSON |
294
+ | Rapid sends | Races | Native queue, burst limits, duplicate drop |
295
+ | Attribution | Looks like the user typed it | Arrives marked as another session, and can't approve permissions |
296
+
297
+ That last row matters: a message posted to the inbox [cannot answer a permission prompt or change configuration](https://code.claude.com/docs/en/cross-session-messaging#how-a-session-treats-an-incoming-message). Text typed via `send-keys` is indistinguishable from you.
298
+
299
+ ## Limits
300
+
301
+ - **The receiver is told who sent the message.** Claude Code records a socket-posted message
302
+ with `from: "unknown"` — it has a field for the sender and nothing to put in it. Sends
303
+ therefore open with `From: <user>@<host> (<session>)`. What Claude Code already supplies,
304
+ session-peer does not repeat: it prefaces peer messages and appends its own guidance about what
305
+ a peer may ask for, so duplicating either would compound with every hop.
306
+ - **Replies depend on SSH working the other way.** Sends append a `Reply:` line naming this
307
+ machine's tailnet address and this session, so the receiver can answer — but only if that
308
+ machine can SSH back. When it can't, neither side is told. The line grants nothing on its own:
309
+ anyone who can reply could already have sent unprompted. What it adds is *knowing* that.
310
+ - **No discovery across a bastion.** `--host` is a single SSH hop; chain it yourself with an SSH config `ProxyJump`.
311
+ - **Same OS user.** The socket is restricted to the user that owns the session, so `session-peer` gives you nothing you couldn't already do with your own shell on that host. It is not a privilege-escalation path — but it does mean anyone with that shell can start a turn.
312
+ - **`--host` and `--ssh-opt` are as trusted as your ssh config.** They are handed to `ssh`, so whoever controls them controls where you connect. Values that would make ssh run a local command (`ProxyCommand` and friends) are refused, and a `--host` starting with `-` is rejected outright — but if you allowlist `session-peer` for an agent, treat it as granting SSH, not just messaging. Message bodies and session names carry no such risk: they are quoted before they reach any shell.
313
+ - **Windows support.** Native Windows sessions use named pipes instead of Unix sockets, and require an auth line before the message. Both are handled automatically — the auth token is read from the session's `.key` file. `install.sh` is POSIX sh and won't run on Windows; use `pip install session-peer` there instead.
314
+
315
+ ## Tests
316
+
317
+ ```bash
318
+ python3 -m unittest test_session_peer -v
319
+ ```
320
+
321
+ No network, no SSH, no Claude Code — standard library only. CI runs them on Ubuntu and
322
+ macOS against Python 3.9 and 3.13, plus `shellcheck` on the installer.
323
+
324
+ The cases cover what has actually been wrong here: the argument quoting that stops `--to`
325
+ reaching a remote shell, the caps that weren't enforced under `--dry-run`, and the reply
326
+ line's user and absolute path. A regression in any of those is silent otherwise.
327
+
328
+ ## Verified
329
+
330
+ Claude Code **v2.1.263** across five machines over Tailscale SSH — two macOS 26 (Apple silicon),
331
+ two Ubuntu 24.04 (arm64, Oracle Ampere A1 in separate regions), and one Windows 10 22H2. What was
332
+ actually exercised:
333
+
334
+ - Posting from macOS to Linux sessions in two regions; each landed in the receiving transcript as
335
+ `type: user` with `origin.kind: "peer"`.
336
+ - Payload integrity — quotes, backticks, `$HOME`, and emoji arrive byte-for-byte.
337
+ - The held path: a bypass-mode session raised an approval dialog, then logged
338
+ `Released 1 held cross-session message` once approved.
339
+ - Both socket layouts in the wild: `/tmp/cc-socks/` on one Ubuntu host, `/run/user/1001/cc-socks/`
340
+ on another running the same build.
341
+ - Windows named pipe transport (`\\.\pipe\LOCAL\cc-msg-<hash>`) with mandatory auth line read from
342
+ the session's `.key` file. `list`, `send`, and `--host` all verified on the Windows machine.
343
+
344
+ The session record schema (`~/.claude/sessions/*.json`) is not part of Claude Code's documented interface and can change between releases. The socket protocol is documented; discovery is inference. If a release moves things, `session-peer list --all` is the first thing to run.
345
+
346
+ ## License
347
+
348
+ MIT
@@ -0,0 +1,6 @@
1
+ session_peer.py,sha256=nPx7k0B_V8bTrHt0WN8VRdwOAaFqBdz6KRdkiXg0IRM,44192
2
+ session_peer-0.6.0.dist-info/METADATA,sha256=Dh4LVrkNPSza9RPysSEBq6PhOFFQmwU-wMLWpVvRIg0,19566
3
+ session_peer-0.6.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
4
+ session_peer-0.6.0.dist-info/entry_points.txt,sha256=EY0Fi-cI3hpIZDlScsUtIAkxIOQwq9mUzd04dq5UhVw,51
5
+ session_peer-0.6.0.dist-info/licenses/LICENSE,sha256=LHgMHmT4Xg7KGB7WD_Bt3DbD1GLrR9XvorXQZQa5Q3w,1066
6
+ session_peer-0.6.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ session-peer = session_peer:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 abruption
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.