ai-remote 0.4.13 → 0.4.15

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/README.md CHANGED
@@ -35,6 +35,69 @@ npx ai-remote exec "systemctl status nginx"
35
35
  npx ai-remote close
36
36
  ```
37
37
 
38
+ ### What a command costs, and where the time actually goes
39
+
40
+ Measured on an M4, `list` with two sessions open:
41
+
42
+ | | median |
43
+ | --- | --- |
44
+ | Node itself, starting and doing nothing | 24 ms |
45
+ | this CLI, run directly | 27 ms |
46
+ | `npx he --version` -- a package with **no dependencies** | 332 ms |
47
+ | `npx ai-remote list` | 328 ms |
48
+
49
+ The third row is the one that matters. A package with nothing in it costs the
50
+ same under `npx` as this one does, because that 300 ms is `npm exec` resolving
51
+ the spec, revalidating a packument against the registry and spawning -- work npm
52
+ does before the package it is running exists to it. No published version of any
53
+ package can be faster than that row, and this one is at it.
54
+
55
+ So the way to spend less of it is to invoke `npx` fewer times, not to make each
56
+ invocation cheaper. Batch the steps:
57
+
58
+ ```bash
59
+ # Four npx invocations: ~1200 ms, of which ~1200 ms is npm.
60
+ npx ai-remote key MetaLeft
61
+ npx ai-remote type notepad
62
+ npx ai-remote key Enter
63
+ npx ai-remote shot -o s.png
64
+
65
+ # The same four steps, one invocation: ~305 ms.
66
+ npx ai-remote do "key MetaLeft; wait 800; type notepad; key Enter; wait 1500; shot s.png"
67
+ ```
68
+
69
+ `ai-remote shell` is the same idea for a terminal: one invocation, a session
70
+ that stays open, and no npm between keystrokes.
71
+
72
+ Run `node bench/cli.bench.mjs` in this repository to get the table above for
73
+ your own machine -- including the no-dependency control, without which npm's
74
+ 300 ms reads like something this package could fix.
75
+
76
+ ### About the `npm notice run ...` lines
77
+
78
+ They are npm's, not this package's. `@npmcli/run-script` writes them
79
+ unconditionally at npm's default log level:
80
+
81
+ ```
82
+ @npmcli/run-script/lib/run-script-pkg.js:53
83
+ log.notice('run', `${cmd.trim()} ${args?.join(' ')}`.trim())
84
+ ```
85
+
86
+ There is no field in `package.json`, no `.npmrc` inside the tarball and no
87
+ publish flag that reaches them -- they are printed before this program starts.
88
+ Silencing them is a choice made where the command is run:
89
+
90
+ ```bash
91
+ npx --silent ai-remote list # this invocation only
92
+ NPM_CONFIG_LOGLEVEL=error npx ... # this shell only
93
+ npm config set loglevel error # this machine, all npm commands
94
+ npm install -g ai-remote # no npm in the path at all; use `remotectl`
95
+ ```
96
+
97
+ The last one also skips the 300 ms: `remotectl list` answers in about 27 ms. It
98
+ is a per-machine decision rather than something the package can carry, which is
99
+ why `npx` is still what the examples here use.
100
+
38
101
  ### CLI Commands
39
102
 
40
103
  ```
@@ -48,11 +111,12 @@ npx ai-remote close
48
111
  ai-remote key <Code> press keys, e.g. MetaLeft, ControlLeft+KeyA
49
112
  ai-remote exec "cmd" run a command in the terminal (SSH)
50
113
  ai-remote shell an interactive terminal
114
+ ai-remote cp <src> <dst> copy files; a remote path starts with ':'
51
115
  ai-remote do "steps" several actions in one go
52
116
  ai-remote view open the window on the session
53
117
  ai-remote view --close close the window, session keeps running
54
118
  ai-remote status what the session is doing
55
- ai-remote list every session that is open
119
+ ai-remote list every session open, and every password kept
56
120
  ai-remote saved machines with a password kept
57
121
  ai-remote forget [<host[:port]>] throw one away; --all throws all
58
122
  ai-remote close close it; --all closes every session
@@ -75,6 +139,22 @@ npx ai-remote shot --session desk
75
139
  npx ai-remote close --all
76
140
  ```
77
141
 
142
+ `list` answers both halves of "what can I get back to". Every open session, with
143
+ `password saved` against the ones that have one:
144
+
145
+ ```
146
+ * desk 192.168.1.50:3389 1920x1080 pid 6551 http://127.0.0.1:7373/?t=... password saved (login keychain)
147
+ build 192.168.1.60:3389 1280x800 pid 6808
148
+
149
+ Saved, nothing open (`ai-remote open <host>` needs no password):
150
+ owner@192.168.1.70:3389 login keychain saved 2026-08-28
151
+ ```
152
+
153
+ The second block is the machines with a password kept but nothing running --
154
+ `open` on one of those needs nothing else. It reads the index beside the
155
+ keystore rather than the keystore, so a listing never raises a keychain prompt;
156
+ `saved` shows that block on its own.
157
+
78
158
  `probe` is the one command besides `open` that still takes an address, because
79
159
  it answers whether a session could be opened at all; with no address it probes
80
160
  the current session's host.
@@ -134,6 +214,40 @@ npx ai-remote exec "uname -a"
134
214
  npx ai-remote shell # Ctrl-] to leave
135
215
  ```
136
216
 
217
+ ### Copying files
218
+
219
+ `cp` is `scp` with the session standing in for the host: a remote path starts
220
+ with a colon, and anything else is on this machine.
221
+
222
+ ```bash
223
+ npx ai-remote cp ./setup.exe :C:/Users/owner/Desktop/ # to the session in hand
224
+ npx ai-remote cp :/var/log/syslog ./syslog # from it
225
+ npx ai-remote cp -r ./site :/srv/www # a whole directory
226
+ npx ai-remote cp desk:/tmp/a.txt build:/tmp/a.txt # between two sessions
227
+ ```
228
+
229
+ SFTP underneath, which is what `scp` itself uses since OpenSSH 9 deprecated its
230
+ own wire protocol. So the host needs no `scp` binary, a filename with a space or
231
+ a quote in it is a length-prefixed string rather than a quoting problem, and
232
+ nothing passes through a shell -- which is why a Windows host needs no special
233
+ case here even though `exec` needs several.
234
+
235
+ Two rules about the grammar:
236
+
237
+ - **A one-letter prefix is a drive, not a session.** `C:/Users/owner/x.txt` is a
238
+ path on this machine, and always will be. Two or more characters before the
239
+ colon name a session -- by its name, or by its host.
240
+ - **A destination that already exists as a directory takes the source's name
241
+ under it.** `cp a.txt :/tmp` is `/tmp/a.txt`, and `cp -r ./site :/srv/www`
242
+ with `www` already there is `/srv/www/site`. A destination that does not exist
243
+ is the name the copy takes.
244
+
245
+ Chunks are 32 KB with eight in flight, so a transfer gets the bandwidth the link
246
+ has rather than one round trip per chunk. The mode travels with the file. The
247
+ transfer is its own SSH connection rather than the session's, so a large copy
248
+ does not stall the terminal somebody is watching. `--json` reports `bytes`,
249
+ `files` and `rate`.
250
+
137
251
  ### Signing in
138
252
 
139
253
  Keys first, then a password.
@@ -174,6 +288,7 @@ where a decrypted key belongs, and it is offered from there.
174
288
 
175
289
  - `--session NAME`: Which session to drive (default: the one opened last).
176
290
  - `-o, --out FILE`: Screenshot output file path (default: `screen.png`).
291
+ - `-r, --recursive`: For `cp`, copy a directory.
177
292
  - `--max-edge N`: Shrink a screenshot to fit N.
178
293
  - `--json`: Output machine-readable JSON results on stdout.
179
294
 
@@ -216,6 +331,7 @@ npm install ai-remote
216
331
  - RFC 4253 binary packet protocol with `curve25519-sha256` / `ecdh-sha2-nistp256`.
217
332
  - AEAD ciphers (`aes256-gcm@openssh.com`, `aes128-gcm@openssh.com`).
218
333
  - Terminal interactive and automated agent command execution.
334
+ - SFTP version 3 client on a subsystem channel, for file transfer.
219
335
  - **VNC Engine:**
220
336
  - RFB protocol integration with Apple Diffie-Hellman handshake support.
221
337
  - **AI Agent Automation:**
package/SKILL.md CHANGED
@@ -30,6 +30,27 @@ reconnects between acting and looking can never see the result of its own
30
30
  action. Reuse also makes each command effectively instant — a keystroke is
31
31
  ~0.15s against ~5s for a fresh RDP handshake.
32
32
 
33
+ **Batch steps into one invocation where you can.** `npx` costs about 300 ms per
34
+ call before this tool runs at all — npm resolving the package, not the package
35
+ doing anything, and the same 300 ms for a package with no code in it. The tool
36
+ itself answers in about 27 ms. So four steps run as four commands spend roughly a
37
+ second waiting on npm and a tenth of one doing the work:
38
+
39
+ ```bash
40
+ # ~1200 ms, nearly all npm
41
+ npx ai-remote key MetaLeft
42
+ npx ai-remote type notepad
43
+ npx ai-remote key Enter
44
+ npx ai-remote shot -o s.png
45
+
46
+ # ~305 ms, same four steps
47
+ npx ai-remote do "key MetaLeft; wait 800; type notepad; key Enter; wait 1500; shot s.png"
48
+ ```
49
+
50
+ Look between steps when you need to see the result before choosing the next one;
51
+ batch the runs of steps you already know. The `npm notice run ...` lines npx
52
+ prints are npm's own and harmless — ignore them, or add `--silent`.
53
+
33
54
  ## Working loop
34
55
 
35
56
  Act, then look. The session holds the framebuffer, so a screenshot taken after a
@@ -117,7 +138,9 @@ npx ai-remote exec "hostname"
117
138
  npx ai-remote exec "dir C:\\Users"
118
139
  ```
119
140
 
120
- `exec` reuses the session's own account and password, so it needs no flags. Its
141
+ `exec` reuses the session's own account and password, so it needs no flags
142
+ give `open` `--ssh-user NAME` or `--ssh-port N` only when the terminal signs in
143
+ as somebody else, or on a port other than 22. Its
121
144
  stdout is the command's output, and it exits 0 when the command did and 1 when
122
145
  it did not — the command's own status is in `exitStatus` under `--json` if you
123
146
  need the number. `ai-remote shell` opens an interactive terminal (Ctrl-] to
@@ -126,6 +149,27 @@ leave) — that one is for a human, not for an agent, because it never returns.
126
149
  Reach for the GUI when the task is genuinely graphical. Reach for `exec` for
127
150
  anything a shell can do.
128
151
 
152
+ ## Copying files
153
+
154
+ `cp` is scp with the session standing in for the host. A remote path starts with
155
+ a colon; anything else is on this machine.
156
+
157
+ ```bash
158
+ npx ai-remote cp ./setup.exe :C:/Users/owner/Desktop/ # to the session in hand
159
+ npx ai-remote cp :/var/log/syslog ./syslog # from it
160
+ npx ai-remote cp -r ./site :/srv/www # a directory
161
+ npx ai-remote cp desk:/tmp/a.txt build:/tmp/a.txt # between two sessions
162
+ ```
163
+
164
+ SFTP underneath, so the host needs no `scp` binary, spaces in filenames need no
165
+ quoting, and nothing goes through a shell. Two rules worth knowing: a one-letter
166
+ prefix is a drive, not a session, so `C:/Users/owner/x.txt` is local; and a
167
+ destination that already exists as a directory takes the source's name under it,
168
+ so `cp -r ./site :/srv/www` with `www` there gives `/srv/www/site`.
169
+
170
+ Uploading is how you get a script onto a machine, which is usually better than
171
+ typing one into a GUI. `--json` reports `bytes`, `files` and `rate`.
172
+
129
173
  ## A Mac, or anything else that speaks VNC
130
174
 
131
175
  `--vnc` opens the screen over VNC instead of RDP; port 5900 implies it. Every
@@ -141,7 +185,8 @@ npx ai-remote click 640,400
141
185
 
142
186
  A session that is already open gets a screen with `ai-remote vnc`, which takes
143
187
  no host because the session already knows the machine. That is the way to give a
144
- terminal session a desktop without closing it.
188
+ terminal session a desktop without closing it; `--vnc-port N` if the screen is
189
+ not on 5900.
145
190
 
146
191
  ## A host with no desktop
147
192
 
@@ -168,6 +213,42 @@ variable at all. `-i FILE` names a key instead, and repeats. A key with a
168
213
  passphrase is never read off disk: `ssh-add` it, and it is offered from the
169
214
  agent.
170
215
 
216
+ ## Passwords, and not typing them twice
217
+
218
+ A desktop always wants one; a terminal usually does not, because of the keys
219
+ above. Three places are tried, in this order:
220
+
221
+ 1. `AI_REMOTE_PASSWORD` in this shell, or `REMOTECTL_PASSWORD` under the other
222
+ name.
223
+ 2. Whatever `--save` kept for that machine.
224
+ 3. You, at the terminal — only when one is attached, and only after the first
225
+ two have come up empty.
226
+
227
+ This shell wins, so a password exported deliberately is never quietly overridden
228
+ by one saved months ago. There is no `--password` flag and there will not be: a
229
+ command line is visible to every process on the machine.
230
+
231
+ ```bash
232
+ npx ai-remote open 192.168.1.20 -u owner --save # keep it, once it has worked
233
+ npx ai-remote list # sessions, each marked if it has one
234
+ npx ai-remote saved # only what is kept, and where
235
+ npx ai-remote forget 192.168.1.20 # one machine; --all for every one
236
+ ```
237
+
238
+ `list` answers both halves of "what can I get back to": every open session, with
239
+ `password saved` against the ones that have one, and then the machines with a
240
+ password kept but nothing open — those need no `-u` or password to reopen. It
241
+ reads the index beside the keystore, not the keystore, so it never raises a
242
+ keychain prompt.
243
+
244
+ `--save` writes to the system keystore — the login keychain on macOS, the desktop
245
+ keyring on Linux — and where there is neither, to an encrypted file under
246
+ `~/.config/ai-remote`; the command says which it got. That file is proof against
247
+ a glance, not against somebody who can read your home directory.
248
+
249
+ `--no-prompt` (or `--batch`) turns a missing password into an immediate failure
250
+ instead of a question. Use it in anything unattended, where a prompt is a hang.
251
+
171
252
  ## Sessions
172
253
 
173
254
  The session opened last is the one commands drive. With several open, name them:
@@ -176,14 +257,20 @@ The session opened last is the one commands drive. With several open, name them:
176
257
  npx ai-remote open 192.168.1.20 -u owner --session desk
177
258
  npx ai-remote open 192.168.1.30 -u owner --session build
178
259
 
179
- npx ai-remote list # * marks the one commands will use
260
+ npx ai-remote list # * marks the one commands will use,
261
+ # and it also lists saved passwords
180
262
  npx ai-remote shot --session desk # a name, a host, or host:port
181
263
  npx ai-remote close --all # or `close` for the current one
182
264
  ```
183
265
 
184
- Nothing times out and nothing closes itself a session that an agent left
185
- mid-task is a feature, not a leak — so **close what you open** when the task is
186
- done, or tell the user it is still running.
266
+ `open` on a machine that already has a session attaches to that one instead of
267
+ building a second; `--no-reuse` is how you get two on purpose.
268
+
269
+ Nothing times out and nothing closes itself by default — a session that an agent
270
+ left mid-task is a feature, not a leak — so **close what you open** when the task
271
+ is done, or tell the user it is still running. `--idle MINUTES` at `open` time
272
+ sets one to close itself after that long unused, for when nobody will be back to
273
+ do it.
187
274
 
188
275
  ## Watching
189
276
 
@@ -203,9 +290,19 @@ fresh `shot` rather than trusting one from before you paused.
203
290
  authority either way, so a session nobody watches is not a lesser session.
204
291
  - `--fullscreen` fills the display and makes RDP use the current monitor's
205
292
  logical resolution. Add `--side-panel` to `open` to start with a fixed-width
206
- SSH terminal docked on the right; its 420 pixels are removed from RDP width.
293
+ SSH terminal docked on the right; its 610 pixels -- eighty columns, which is
294
+ what a Windows shell assumes it has -- are removed from the RDP width, and the
295
+ divider between the panes can be dragged.
296
+ - `--audio` (RDP only) asks the host to redirect the session's sound and plays
297
+ it in the window; the toolbar's speaker button mutes it. Off unless asked for:
298
+ the stream costs bandwidth in both directions, and Windows stops playing
299
+ through the host's own speakers while it is redirected. A headless session
300
+ ignores the flag, having nowhere to play anything.
207
301
  - `ai-remote view` adds a window to a headless session later (`view --watch-only`
208
302
  for an observer one); `view --close` takes it away without ending the session.
303
+ - `--view-port N` moves the window's local server off 7373, which matters only
304
+ when two sessions want it at once. `--no-open` keeps the window but does not
305
+ launch a browser at it.
209
306
 
210
307
  ## Reading results
211
308
 
@@ -242,6 +339,9 @@ the other half of the answer is a key: `ssh-add ~/.ssh/id_ed25519`, or
242
339
  `-i FILE`. The session's log names every key it offered and every key it passed
243
340
  over, and why.
244
341
 
342
+ `-s nla | tls | rdp` pins the RDP security layer when `auto` picks one the host
343
+ will not finish; it makes no difference over VNC or SSH.
344
+
245
345
  **Unreachable (10).** — `ai-remote probe <host>` is the one command besides
246
346
  `open` that still takes an address: it answers whether anything is listening
247
347
  before you spend a handshake finding out.
@@ -261,17 +361,20 @@ what `status` reports rather than what you asked for.
261
361
  ```bash
262
362
  export AI_REMOTE_PASSWORD=...
263
363
  npx ai-remote open HOST[:PORT] -u USER [-d DOMAIN] [--session NAME] [--fullscreen] [--side-panel]
364
+ [-W 1920 -H 1080] [-s auto|nla|tls|rdp] [--save] [--idle N] [--audio]
264
365
  npx ai-remote open HOST --ssh -u USER [-i ~/.ssh/id_ed25519] # a terminal, no desktop
265
366
  npx ai-remote open HOST --vnc -u USER # a screen over VNC
266
- npx ai-remote vnc # ...or add one to this session
367
+ npx ai-remote vnc [--vnc-port N] # ...or add one to this session
267
368
  npx ai-remote shot [-o FILE] [--max-edge N] [--json]
268
369
  npx ai-remote click X,Y [--button right|middle] [--double]
269
370
  npx ai-remote type "text"
270
371
  npx ai-remote key ControlLeft+KeyA
271
372
  npx ai-remote do "key MetaLeft; wait 800; type notepad; shot s.png"
272
373
  npx ai-remote exec "command"
374
+ npx ai-remote cp [-r] SRC DST # ':path' is on the session
273
375
  npx ai-remote view [--watch-only] [--close]
274
376
  npx ai-remote status | list | close [--all]
377
+ npx ai-remote saved | forget [HOST[:PORT]] [--all]
275
378
  npx ai-remote probe [HOST[:PORT]]
276
379
  ```
277
380
 
@@ -292,5 +395,6 @@ node packages/core/dist/cli.mjs open HOST -u USER --headless
292
395
 
293
396
  The CLI's own source is [`packages/core/src/cli/`](src/cli/):
294
397
  `cli.ts` is the grammar, `sessions.ts` decides which session a command drives,
295
- `daemon.ts` is the process that holds the connection, and `viewer.ts` plus
296
- `window.ts` are the window. `pnpm test` covers them.
398
+ `daemon.ts` is the process that holds the connection, `copy.ts` is `cp` over the
399
+ SFTP client in [`protocols/ssh/sftp.ts`](src/protocols/ssh/sftp.ts), and
400
+ `viewer.ts` plus `window.ts` are the window. `pnpm test` covers them.