Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/node-cli-experiment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: node-cli-experiment
on:
pull_request:
paths:
- 'experiments/node-cli/**'
- 'session_peer.py'
- 'tests/fixtures/compatibility-v1.json'
- '.github/workflows/node-cli-experiment.yml'
push:
branches: [main]
paths:
- 'experiments/node-cli/**'
- 'session_peer.py'
- 'tests/fixtures/compatibility-v1.json'
- '.github/workflows/node-cli-experiment.yml'
permissions:
contents: read
jobs:
conformance:
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest]
node: ['22', '24']
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: experiments/node-cli
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ matrix.node }}
cache: npm
cache-dependency-path: experiments/node-cli/package-lock.json
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.13'
- run: npm ci --ignore-scripts
- run: npm test
- run: npm audit --omit=dev
- run: npm run test:package
33 changes: 33 additions & 0 deletions experiments/node-cli/.github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: cli
on: [push, pull_request]
permissions:
contents: read
jobs:
contract:
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest]
node: ['22', '24']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: abruption/session-peer
ref: 47c23713d0a2a3c11ebde6186afd8c43489b8b65
path: .reference-python
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ matrix.node }}
cache: npm
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.13'
- run: npm ci --ignore-scripts
- run: npm test
env:
SESSION_PEER_PYTHON_ROOT: ${{ github.workspace }}/.reference-python
- run: npm run test:package
- run: npm audit
4 changes: 4 additions & 0 deletions experiments/node-cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
dist/
*.tgz
.reference-python/
21 changes: 21 additions & 0 deletions experiments/node-cli/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 abruption

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
119 changes: 119 additions & 0 deletions experiments/node-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# session-peer-ts

Python-free **client CLI preview** for local and SSH messaging to Claude Code and
Codex. This is not a Relay server or a full replacement for the Python product.
Public npm publication remains disabled (`private: true`).

## Scope and prerequisites

- macOS/Linux, Node 22.13+ within 22.x or Node 24.x. Windows and Node 26 are not
supported. A native flock dependency provides prebuilt binaries; x64/arm64
installation requires a matching binary. It never falls back to checking only
that a lock file exists.
- Claude: a running native TUI with a local inbox. Codex: a saved thread with one
stable live writer, native `codex queue`, `lsof` and `ps`. Saved rows alone do
not authorize delivery. Explicit home selection is mandatory.
- SSH: OpenSSH and this **same preview version installed explicitly on the
remote host**, with supported Node. No Python fallback, source streaming,
remote runtime download or automatic installation.
- Unsupported: Relay client/server, Control, MCP, wake, inactive queue opt-in,
Antigravity, updates and implicit multi-agent discovery. Unsupported options
fail explicitly; existing Python installs and hosted services are unchanged.

The architecture evaluation in Python
[#186](https://github.com/abruption/session-peer/issues/186) compared an explicit
Python wrapper, an independent Node client, and no npm distribution. This selects
an independent client: Python is only needed by development conformance tests.
A public release still needs a maintenance and publication decision.

## Build and use

```sh
npm ci --ignore-scripts
npm run build
node dist/cli.js list --agent claude --json
node dist/cli.js list --agent codex --codex-home "$HOME/.codex" --json
node dist/cli.js send --to CLAUDE_PID --message 'hello' --dry-run --json
node dist/cli.js send --to codex:UUID --codex-home "$HOME/.codex" --message 'hello' --dry-run --json
```

Remove `--dry-run` only when delivery is intended. Omit `--message`, or use
`--message -`, for UTF-8 stdin. Output is JSON only (`--json` or
`--output-format json`). Claude targets may be PIDs, `claude:PID`, or unambiguous
ASCII names (case-insensitive). Use a PID for Unicode names; full Unicode
casefold parity is deferred. `--codex-bin` selects an executable, never shell
text. `--all` is listing-only. The bin is `session-peer-ts`, not `session-peer`.

```sh
node dist/cli.js send --host user@machine --remote-bin /absolute/path/session-peer-ts \
--to CLAUDE_PID --message 'hello' --dry-run --json
```

The default remote command is `session-peer-ts` on remote PATH. An absolute
`--remote-bin` may name an operator-owned wrapper selecting a supported Node.
SSH uses existing configuration/keys with BatchMode and StrictHostKeyChecking;
it never approves new host keys. No arbitrary `--ssh-opt` is accepted. IPv6
literals and Tailscale canonical-name enrichment are deferred; use an SSH
alias/hostname. Remote version is checked before one JSON request is sent over
stdin. Message text never becomes remote shell command arguments.

## Delivery and reply safety

- Claude re-resolves one reachable target before one native JSON-line write.
`posted` is write completion, not ACK. PID/socket metadata is not consumption.
- Codex checks selected/default/Orca/configured homes, actual nonblocking OS
flock, file identity, same-user owner and process start time across two
samples. Unknown evidence, multiple live writers and inactive-only copies are
refused. Inventory and ownership are revalidated immediately before queueing.
No transcript is read, lock deleted or owner process signaled.
- `queued` is queue acceptance only; `consumptionConfirmed` is always false.
Response loss, timeout or native nonzero exit after spawn is conservatively
`status:unknown, submitted:null, retryAllowed:false`. Never automatically resend.
- Refusal before submission has `submitted:false`. Dry-run is `validated`.
Success/error/usage exit codes are 0/1/2 and JSON uses the v1 envelope. Failure
diagnostics are fixed codes; no raw exceptions, native stderr or messages.
- Actual ACK must be checked separately in the receiving TUI, not inferred from
queued/submitted or by polling transcripts.

`--to 'session-peer://v1/reply?...'` accepts strict local/SSH Claude/Codex reply
URIs as data. Unknown/duplicate fields, malformed encoding, unsafe hosts and
conflicting explicit routes are refused. `--reply-address URI` adds an explicit
structured return address; it does not establish reverse SSH access. No route
is invented. `--no-reply-to` omits a new address. A valid CODEX_THREAD_ID or
CODEX_SESSION_ID adds an informational From header unless `--no-from` is given.
Unknown senders are not invented; peer headers are metadata, not authority.
No executable Reply command is synthesized.

## Tests

Point `SESSION_PEER_PYTHON_ROOT` at a checkout of Python v1.0.2, commit
`47c23713d0a2a3c11ebde6186afd8c43489b8b65`. CI pins this commit and compares the
vendored compatibility fixture to it. Development tests require Python 3.9+,
a C compiler and lsof. None is an implicit runtime installer.

```sh
SESSION_PEER_PYTHON_ROOT=/path/to/python-reference npm test
npm run test:package
npm audit
```

Fixtures cover Python discovery/schema/exit contracts, actual temporary SQLite
DBs, Unix inbox sockets and native-process kernel locks. Native fixture evidence
is separate from real TUI/SSH observations in VALIDATION.md. Tests check one-shot
submission, ambiguous writers, wrong owners, dry-run, response loss and unsafe
reply rejection without production state. Set TASK_TEMP for temporary files.

Package tests allowlist tarball files, compare repeat-pack SHA-256, clean-install
into a temporary prefix, run empty-PATH discovery and uninstall. The native
dependency normally has an install script/fallback compilation; the verified
prebuilt path uses `--ignore-scripts`. This is not a pure-JavaScript package.
SQLite reads are read-only/query-only, but WAL readers can participate in shared
memory bookkeeping; this is not a snapshot reader.

## Release boundaries

Preview versioning is independent of Python's 1.0.2 contract reference. This is
not complete Python parity. Windows, wider architectures, full discovery,
Unicode names, implicit reply identity and optional transports require separate
work. Public npm naming, provenance, release automation and long-term ownership
must be decided before publication. Python and Relay retain independent releases.
63 changes: 63 additions & 0 deletions experiments/node-cli/VALIDATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Validation record — 2026-09-27 KST

This is preview evidence, not an npm release or a claim of complete Python parity.
Python reference: v1.0.2, commit `47c23713d0a2a3c11ebde6186afd8c43489b8b65`.

## Automated checks

- 14 tests pass locally on macOS arm64 / Node 22.14.0.
- Real fixture SQLite DBs, Unix inbox sockets and separate C processes holding
actual advisory locks; Python is the discovery oracle, not the implementation.
- Refused inactive/ambiguous/wrong-owner cases; one native fixture queue call;
native nonzero after spawn and SSH response loss remain unknown with no retry.
- Package file allowlist, same-build repeat SHA-256, clean prefix installation,
empty-PATH discovery and uninstall pass. npm audit reports 0 vulnerabilities.
- CI defines macOS/Linux × Node 22/24; see the repository Actions results for
those runs rather than treating this local check as their substitute.

## Real TUI and SSH checks

Dedicated, newly created Claude/Codex test TUIs only. The user approved trusting
the two otherwise empty test folders. Agent permissions were not changed.
Each route passed discovery and no-delivery dry-run before exactly one send.
ACKs below were observed as separate **assistant response lines in the owned
live tmux panes**, not copied from submitted prompts, queues or transcript files.

| Route | Native target | Submission | Actual response |
| --- | --- | --- | --- |
| Mac mini local | Claude Code 2.1.281 | posted | `ACK TS186-LOCAL-CLAUDE-8fc721d0` |
| Mac mini local | Codex CLI 0.157.1 | queued | `ACK TS186-LOCAL-CODEX-cb042ac9` |
| Mac mini → MacBook SSH | Claude Code 2.1.281 | posted | `ACK TS186-SSH-CLAUDE-f0275ade` |
| Mac mini → MacBook SSH, structured Reply-To URI as destination | Codex CLI 0.157.1 | queued | `ACK TS186-SSH-CODEX-a771e96c` |

Local Node: 22.14.0. Remote MacBook: 24.20.0, selected by a task-owned wrapper
because its normal PATH selected unsupported Node 26. KR Linux Node 24.16.0
also passed real SSH version negotiation and empty Claude discovery. KR native
agent delivery was not tested. No service was restarted or global CLI replaced.

The CLI itself still reports `consumptionConfirmed:false`; external ACK review
does not change that contract. This is a one-shot functional test, not a soak.
Local self-SSH was denied by the existing SSH authentication configuration; no
credentials or host-key bypass were introduced. Reverse SSH was not asserted.

## Exact runtime build checked on both Macs

Compiled module SHA-256 values matched between the local build and the isolated
MacBook installation used for the SSH ACK tests:

```text
0b708f1211330ff489b6a405e0ebb71d2b3b392acdb19c216dba18e7b23016c7 cli.js
1d8c41dfc174d950e2196c64f93b324f09befa388a5b303439c390a526ffda48 discovery.js
c3187c99c46364730e46e35ef23d8f3a37e541593b2a6f9c3945dce979e7e99c process.js
b716fed8fe6c8c04047f8485a6e3f982d6e64e5638df570b3b8c2a128f88c81e protocol.js
dd946024a61ce8721c273d47e911494c0c8facd62facd9b791a451b90cb702bf send.js
c441567e83c64c7d5735708936e9a42b68dd9ea0cdf0ef95538da7919d550496 writer.js
```

## Not covered / not released

Windows, native x64 agent interaction, IPv6 literals, Relay/MCP/wake, full Python
CLI parity, automatic reverse-route detection, npm registry publishing and
provenance are outside this evidence. Keep `private:true` until a separate
publication decision. Native dependency prebuild availability must be tested
on each architecture claimed by a future release.
80 changes: 80 additions & 0 deletions experiments/node-cli/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading