@baleen37/ars 0.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 baleen37
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 ADDED
@@ -0,0 +1,232 @@
1
+ # ars
2
+
3
+ `ars` lists Claude Code and Codex sessions from configured SSH hosts, combines
4
+ them in one searchable picker, and resumes the selected native session. It is a
5
+ single local Go binary: remote hosts do not need an `ars` install, daemon,
6
+ cache, or background process.
7
+
8
+ ## Install
9
+
10
+ Install the latest release from npm:
11
+
12
+ ```sh
13
+ npm install -g @baleen37/ars
14
+ ```
15
+
16
+ The npm package includes native ars binaries for Apple Silicon, Linux x86-64,
17
+ and Linux arm64. It does not download an executable during installation. For a
18
+ Node-free install, download the matching archive from GitHub Releases, verify
19
+ it against `SHA256SUMS`, and place `ars` on `PATH`.
20
+
21
+ Prerequisites:
22
+
23
+ - Go as declared by `go.mod`, for building from source
24
+ - OpenSSH `ssh` locally and an OpenSSH-compatible server on each remote host
25
+ - `fzf` locally for interactive selection; `ars list --json` does not use it
26
+ - a POSIX `/bin/sh` plus `uname`, `mkdir`, `cat`, `chmod`, `rm`, and `rmdir` on
27
+ each remote host
28
+ - `claude` and/or `codex` on the remote `PATH`, with their native session
29
+ metadata under the remote user's home directory
30
+
31
+ Build the three collector assets and the local executable:
32
+
33
+ ```sh
34
+ go run ./cmd/ars-build
35
+ install -m 0755 ./ars ~/.local/bin/ars
36
+ ```
37
+
38
+ `ars-build --assets-only` creates exactly these embedded collectors before any
39
+ local `ars` build: `darwin/arm64`, `linux/amd64`, and `linux/arm64`. Generated
40
+ collector blobs and the root `ars` build artifact are local build outputs and
41
+ must not be committed.
42
+
43
+ ## Inventory
44
+
45
+ The default inventory is `${XDG_CONFIG_HOME}/ars/hosts` when
46
+ `XDG_CONFIG_HOME` is set, otherwise `~/.config/ars/hosts`. Put one OpenSSH
47
+ target on each line, in the order it should be reported:
48
+
49
+ ```text
50
+ # ~/.config/ars/hosts
51
+ devbox
52
+ deploy@example.internal
53
+ agent-mac
54
+ ```
55
+
56
+ Blank lines and comments are ignored. Targets may use names and aliases from
57
+ the user's SSH config. Duplicates, whitespace, control characters, a leading
58
+ dash, and targets over 255 bytes make the entire inventory invalid. `ars` does
59
+ not infer `localhost` or discover hosts.
60
+
61
+ ## Commands
62
+
63
+ There are exactly three command forms:
64
+
65
+ ```sh
66
+ ars # search sessions from every configured host
67
+ ars devbox # search one configured host
68
+ ars list --json # return all hosts, sessions, and errors as JSON
69
+ ```
70
+
71
+ Interactive rows contain a private numeric index followed by display-only
72
+ metadata. `fzf` returns only that opaque index; titles, paths, and host names
73
+ are never parsed back into identity. Enter resumes the selected session.
74
+ Canceling `fzf` exits zero without starting the resume SSH connection. A
75
+ healthy result with no sessions prints `No sessions found.` and does not open
76
+ `fzf`.
77
+
78
+ ## Session inclusion
79
+
80
+ `ars` compiles in only the Claude Code and Codex adapters:
81
+
82
+ - Claude reads direct regular files at
83
+ `~/.claude/projects/<project>/*.jsonl`. It includes canonical root session
84
+ IDs with an absolute CWD, uses only native custom/AI/agent titles, and
85
+ excludes internal, sidechain, and agent histories. It never derives a title
86
+ from prompt text.
87
+ - Codex recursively reads regular `.jsonl` files below `~/.codex/sessions`.
88
+ It includes only one valid `session_meta` with `thread_source=user` and
89
+ `source=cli` or `source=vscode`. Exec, subagent, and unknown sources are
90
+ excluded. Codex titles are empty in schema version 1.
91
+
92
+ Both adapters require their provider executable on the remote `PATH`, use the
93
+ metadata file modification time as `updated_at`, validate canonical UUIDs, and
94
+ deduplicate by host, provider, and native ID. A missing executable or metadata
95
+ tree is a healthy absent-provider result, not a host failure.
96
+
97
+ ## JSON schema version 1
98
+
99
+ Public JSON uses dedicated DTOs and always ends with a newline:
100
+
101
+ ```json
102
+ {
103
+ "schema_version": 1,
104
+ "hosts": [
105
+ {"target": "devbox", "status": "ok"},
106
+ {"target": "down", "status": "error"}
107
+ ],
108
+ "sessions": [
109
+ {
110
+ "host": "devbox",
111
+ "provider": "claude",
112
+ "native_id": "123e4567-e89b-42d3-a456-426614174000",
113
+ "updated_at": "2026-07-19T01:02:03Z",
114
+ "cwd": "/work/app",
115
+ "title": "Fix login"
116
+ }
117
+ ],
118
+ "errors": [
119
+ {"host": "down", "code": "ssh_failed", "message": "SSH collection failed"}
120
+ ]
121
+ }
122
+ ```
123
+
124
+ `schema_version` is `1`. Timestamps are UTC-capable RFC3339Nano strings.
125
+ `hosts` records every selected inventory target exactly once. `status=ok` with
126
+ an empty `sessions` array distinguishes a healthy empty host from an
127
+ unreachable host. Stable error codes are `ssh_timeout`, `ssh_failed`,
128
+ `unsupported_target`, `protocol_error`, and `resource_limit`.
129
+
130
+ ## SSH and failure behavior
131
+
132
+ Collection and resume deliberately use separate SSH modes:
133
+
134
+ - Collection is non-interactive (`BatchMode=yes`, no agent/X11/port
135
+ forwarding), uses separate probe and upload invocations that each make one
136
+ connection attempt, enforces existing host keys with
137
+ `StrictHostKeyChecking=yes`, uses a 5-second connect timeout, and gives the
138
+ full probe/upload/protocol flow 60 seconds per host. Unknown hosts fail; ars
139
+ never accepts or rewrites `known_hosts` entries.
140
+ - Resume runs `ssh -tt` with the user's normal SSH configuration,
141
+ authentication, and forwarding behavior. It executes only
142
+ `cd <saved-cwd> && exec claude --resume <uuid>` or
143
+ `cd <saved-cwd> && exec codex resume <uuid>`. Resume preserves the SSH exit
144
+ code when available.
145
+
146
+ Hosts are collected with at most four workers. One failed host produces a
147
+ structured error while healthy peer sessions remain usable. If every selected
148
+ host fails, `ars` exits non-zero and never opens `fzf`. A healthy empty host is
149
+ success. Provider-level corrupt records can yield a partial provider result
150
+ without discarding its valid sessions.
151
+
152
+ ## Bounds and cleanup
153
+
154
+ Collection is bounded and fail-closed:
155
+
156
+ - 64 KiB startup noise and 64 KiB per ARS/1 protocol line
157
+ - 16 MiB total collector stdout and 64 KiB diagnostic stderr
158
+ - 10,000 sessions total; provider traversal also caps discovered sessions at
159
+ 10,000
160
+ - 1 MiB maximum provider JSONL line and 64 directory levels for Codex
161
+ - 36 bytes for a native UUID, 4,096 bytes for CWD, and 1,024 bytes for title
162
+
163
+ The uploaded collector is written with `umask 077` to one nonce-specific
164
+ `${TMPDIR:-/tmp}/ars-<128-bit-nonce>` directory. EXIT/HUP/INT/TERM traps remove
165
+ only that exact collector file and directory. On an interrupted local command,
166
+ ars makes one separate, five-second exact cleanup attempt. A remote power loss
167
+ or `SIGKILL` can prevent both paths and leave that one nonce-specific private
168
+ directory; version 1 has no janitor and operators may inspect and remove only
169
+ that exact leftover.
170
+
171
+ ## Privacy
172
+
173
+ The collector returns validated metadata only: provider, native UUID,
174
+ modification time, saved CWD, and native title. Prompts, responses, tool input
175
+ or output, credentials, raw transcript lines, provider source paths, and
176
+ filenames never cross the ARS/1 boundary. Host diagnostics are bounded and
177
+ sanitized before public output. The saved CWD and native title are still
178
+ potentially sensitive metadata, so treat JSON output and terminal history
179
+ accordingly.
180
+
181
+ ## Verification
182
+
183
+ The complete automated release check is:
184
+
185
+ ```sh
186
+ go run ./cmd/ars-build --assets-only
187
+ go test ./...
188
+ go test -race ./...
189
+ go vet ./...
190
+ go run ./cmd/ars-build
191
+ ```
192
+
193
+ The ephemeral loopback sshd integration is disposable and opt-in:
194
+
195
+ ```sh
196
+ ARS_RUN_SSHD_INTEGRATION=1 go test ./internal/ssh -run TestEphemeralSSHDCollectsAndResumes -v
197
+ ```
198
+
199
+ It generates temporary host/client keys, `authorized_keys`, `known_hosts`, and
200
+ configs, and does not modify a system sshd or persistent SSH configuration.
201
+ Before release, also use two explicitly configured real hosts to verify one
202
+ Claude and one Codex resume, an unreachable peer beside a healthy host, a
203
+ healthy empty host, fzf cancellation, and nonce-specific cleanup after an
204
+ interrupt.
205
+
206
+ ## Release
207
+
208
+ Releases run after CI succeeds on `main`. Conventional `feat`, `fix`, `perf`,
209
+ and `BREAKING CHANGE` commits determine the next version; documentation,
210
+ chore, and test-only changes are no-ops. Rapid pushes may coalesce into one
211
+ pending release run; that run includes every commit since the last release tag.
212
+
213
+ The one-time npm setup is:
214
+
215
+ 1. publish `@baleen37/ars@0.0.0` with the `bootstrap` dist-tag
216
+ 2. configure npm Trusted Publishing for GitHub repository
217
+ `baleen37/agent-remote-sessions` and workflow `ci.yml`
218
+ 3. allow `npm publish`, then verify the first `main` release as `v1.0.0`
219
+
220
+ If publication is partial, inspect the Git tag, npm version, and GitHub Release
221
+ before changing state. Preserve any public npm version. Reconstruct a missing
222
+ GitHub Release from the same tag, or publish a missing npm package rebuilt from
223
+ that exact tag. For a missing npm version `X.Y.Z`, run:
224
+
225
+ ```sh
226
+ git switch --detach vX.Y.Z
227
+ go run ./cmd/ars-build --release X.Y.Z
228
+ npm login
229
+ npm publish ./dist/npm --access public
230
+ ```
231
+
232
+ Delete a failed tag only when neither registry published it.
package/bin/ars.js ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require("node:path");
4
+ const { spawnSync } = require("node:child_process");
5
+
6
+ const binaries = new Map([
7
+ ["darwin/arm64", "ars-darwin-arm64"],
8
+ ["linux/x64", "ars-linux-amd64"],
9
+ ["linux/arm64", "ars-linux-arm64"],
10
+ ]);
11
+
12
+ function binaryName(platform, arch) {
13
+ return binaries.get(`${platform}/${arch}`) ?? null;
14
+ }
15
+
16
+ function run(runtime = {
17
+ platform: process.platform,
18
+ arch: process.arch,
19
+ dirname: __dirname,
20
+ args: process.argv.slice(2),
21
+ spawnSync,
22
+ stderr: process.stderr,
23
+ kill: process.kill.bind(process),
24
+ pid: process.pid,
25
+ }) {
26
+ const name = binaryName(runtime.platform, runtime.arch);
27
+ if (name === null) {
28
+ runtime.stderr.write(
29
+ `ars: unsupported platform ${runtime.platform}/${runtime.arch}; ` +
30
+ "supported: darwin/arm64, linux/amd64, linux/arm64\n",
31
+ );
32
+ return 1;
33
+ }
34
+
35
+ const binary = path.join(runtime.dirname, "..", "vendor", name);
36
+ const result = runtime.spawnSync(binary, runtime.args, { stdio: "inherit" });
37
+ if (result.error) {
38
+ runtime.stderr.write(`ars: start native binary: ${result.error.message}\n`);
39
+ return 1;
40
+ }
41
+ if (result.signal) {
42
+ runtime.kill(runtime.pid, result.signal);
43
+ return 1;
44
+ }
45
+ return result.status ?? 1;
46
+ }
47
+
48
+ if (require.main === module) {
49
+ process.exitCode = run();
50
+ }
51
+
52
+ module.exports = { binaryName, run };
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "bin": {
3
+ "ars": "bin/ars.js"
4
+ },
5
+ "bugs": "https://github.com/baleen37/agent-remote-sessions/issues",
6
+ "description": "Search and resume Claude Code and Codex sessions across SSH hosts",
7
+ "engines": {
8
+ "node": "\u003e=18"
9
+ },
10
+ "files": [
11
+ "bin/ars.js",
12
+ "vendor/ars-*",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "homepage": "https://github.com/baleen37/agent-remote-sessions#readme",
17
+ "license": "MIT",
18
+ "name": "@baleen37/ars",
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/baleen37/agent-remote-sessions.git"
25
+ },
26
+ "version": "0.0.0"
27
+ }
Binary file
Binary file
Binary file