@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 +21 -0
- package/README.md +232 -0
- package/bin/ars.js +52 -0
- package/package.json +27 -0
- package/vendor/ars-darwin-arm64 +0 -0
- package/vendor/ars-linux-amd64 +0 -0
- package/vendor/ars-linux-arm64 +0 -0
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
|