ai-remote 0.4.14 → 0.4.16

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,14 @@ 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 reconnect a new terminal: fresh login, fresh PATH
115
+ ai-remote clipboard [on|off] share this machine's clipboard both ways
116
+ ai-remote cp <src> <dst> copy files; a remote path starts with ':'
51
117
  ai-remote do "steps" several actions in one go
52
118
  ai-remote view open the window on the session
53
119
  ai-remote view --close close the window, session keeps running
54
120
  ai-remote status what the session is doing
55
- ai-remote list every session that is open
121
+ ai-remote list every session open, and every password kept
56
122
  ai-remote saved machines with a password kept
57
123
  ai-remote forget [<host[:port]>] throw one away; --all throws all
58
124
  ai-remote close close it; --all closes every session
@@ -75,6 +141,22 @@ npx ai-remote shot --session desk
75
141
  npx ai-remote close --all
76
142
  ```
77
143
 
144
+ `list` answers both halves of "what can I get back to". Every open session, with
145
+ `password saved` against the ones that have one:
146
+
147
+ ```
148
+ * desk 192.168.1.50:3389 1920x1080 pid 6551 http://127.0.0.1:7373/?t=... password saved (login keychain)
149
+ build 192.168.1.60:3389 1280x800 pid 6808
150
+
151
+ Saved, nothing open (`ai-remote open <host>` needs no password):
152
+ owner@192.168.1.70:3389 login keychain saved 2026-08-28
153
+ ```
154
+
155
+ The second block is the machines with a password kept but nothing running --
156
+ `open` on one of those needs nothing else. It reads the index beside the
157
+ keystore rather than the keystore, so a listing never raises a keychain prompt;
158
+ `saved` shows that block on its own.
159
+
78
160
  `probe` is the one command besides `open` that still takes an address, because
79
161
  it answers whether a session could be opened at all; with no address it probes
80
162
  the current session's host.
@@ -134,6 +216,113 @@ npx ai-remote exec "uname -a"
134
216
  npx ai-remote shell # Ctrl-] to leave
135
217
  ```
136
218
 
219
+ ### The clipboard
220
+
221
+ Off by default, and turned on either from the **clipboard button in the window's
222
+ toolbar** or from a command line:
223
+
224
+ ```bash
225
+ npx ai-remote clipboard on # both directions, until turned off
226
+ npx ai-remote clipboard # what it is doing now
227
+ npx ai-remote open 192.168.1.50 -u owner --clipboard
228
+ ```
229
+
230
+ While it is on, text copied on this machine reaches the remote clipboard and text
231
+ copied there reaches this one. Only a desktop carries a clipboard -- RDP
232
+ negotiates a `cliprdr` channel and VNC has cut-text in the protocol -- so a
233
+ terminal-only session has none and the toolbar button does not appear.
234
+
235
+ **The bridge is the session's, not the page's.** The obvious place to put it
236
+ would be the viewer, using the browser's `navigator.clipboard` -- but a browser
237
+ only grants a page the clipboard while that page is focused, and the moment
238
+ somebody wants the remote clipboard is exactly the moment they have clicked away
239
+ into a local application to paste it. So the session does the copying, on this
240
+ machine, whether or not a window is open; the toolbar button turns it on rather
241
+ than doing it. Two windows on one session are two views of one switch.
242
+
243
+ Two consequences worth knowing:
244
+
245
+ - **It polls.** No platform gives Node a clipboard-change event, so the local
246
+ side is read twice a second while sharing is on. That is why it is a toggle
247
+ and not always on, and why off is a real off: the timer is not running and the
248
+ host's clipboard messages are dropped.
249
+ - **Nothing goes round in circles.** Text that arrived from the host is not then
250
+ noticed by the poller and pushed back to it, and what was already on the
251
+ clipboard when sharing started is not treated as a change -- so turning
252
+ sharing on does not send something copied an hour ago.
253
+
254
+ It uses whatever the platform has: `pbcopy`/`pbpaste` on macOS,
255
+ `Set-Clipboard`/`Get-Clipboard` on Windows, and `wl-clipboard`, `xclip` or
256
+ `xsel` on Linux. A machine with none reports the clipboard as unavailable and
257
+ the button stays hidden rather than offering a switch that cannot work.
258
+
259
+ Sharing sits behind the same control gate as keyboard and pointer input: a
260
+ watch-only viewer can read the screen and cannot reach either clipboard.
261
+
262
+ ### A new terminal, after installing something
263
+
264
+ A shell inherits its environment when it signs in, and nothing can change a
265
+ running process's inherited environment from outside it. So a program installed
266
+ a moment ago is simply not on the PATH of the shell that installed it, and
267
+ running the command again will not find it however many times it is tried.
268
+
269
+ `reconnect` is the fix, and it is the same one a person reaches for -- close the
270
+ terminal, open another:
271
+
272
+ ```bash
273
+ npx ai-remote exec "winget install OpenJS.NodeJS"
274
+ npx ai-remote reconnect
275
+ npx ai-remote exec "node --version"
276
+
277
+ npx ai-remote exec --reconnect "node --version" # or both in one call
278
+ ```
279
+
280
+ The session, the desktop and the window stay exactly where they are; only the
281
+ terminal is new. That also means the working directory and any exported
282
+ variables are gone, which is the point -- it is a new login, not a refresh.
283
+
284
+ On Windows a fresh login can still inherit a stale environment, because sshd
285
+ built its own environment block when the service started. Reading the PATH
286
+ straight out of the registry works regardless:
287
+
288
+ ```bash
289
+ npx ai-remote exec '$env:Path = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Environment]::GetEnvironmentVariable("Path","User")'
290
+ ```
291
+
292
+ ### Copying files
293
+
294
+ `cp` is `scp` with the session standing in for the host: a remote path starts
295
+ with a colon, and anything else is on this machine.
296
+
297
+ ```bash
298
+ npx ai-remote cp ./setup.exe :C:/Users/owner/Desktop/ # to the session in hand
299
+ npx ai-remote cp :/var/log/syslog ./syslog # from it
300
+ npx ai-remote cp -r ./site :/srv/www # a whole directory
301
+ npx ai-remote cp desk:/tmp/a.txt build:/tmp/a.txt # between two sessions
302
+ ```
303
+
304
+ SFTP underneath, which is what `scp` itself uses since OpenSSH 9 deprecated its
305
+ own wire protocol. So the host needs no `scp` binary, a filename with a space or
306
+ a quote in it is a length-prefixed string rather than a quoting problem, and
307
+ nothing passes through a shell -- which is why a Windows host needs no special
308
+ case here even though `exec` needs several.
309
+
310
+ Two rules about the grammar:
311
+
312
+ - **A one-letter prefix is a drive, not a session.** `C:/Users/owner/x.txt` is a
313
+ path on this machine, and always will be. Two or more characters before the
314
+ colon name a session -- by its name, or by its host.
315
+ - **A destination that already exists as a directory takes the source's name
316
+ under it.** `cp a.txt :/tmp` is `/tmp/a.txt`, and `cp -r ./site :/srv/www`
317
+ with `www` already there is `/srv/www/site`. A destination that does not exist
318
+ is the name the copy takes.
319
+
320
+ Chunks are 32 KB with eight in flight, so a transfer gets the bandwidth the link
321
+ has rather than one round trip per chunk. The mode travels with the file. The
322
+ transfer is its own SSH connection rather than the session's, so a large copy
323
+ does not stall the terminal somebody is watching. `--json` reports `bytes`,
324
+ `files` and `rate`.
325
+
137
326
  ### Signing in
138
327
 
139
328
  Keys first, then a password.
@@ -166,6 +355,7 @@ where a decrypted key belongs, and it is offered from there.
166
355
  - `-s, --security MODE`: `auto` | `nla` | `tls` | `rdp` (default: `auto`).
167
356
  - `-W, --width N` / `-H, --height N`: Desktop resolution (default: 1280x800).
168
357
  - `--idle MINUTES`: Close an unused session (default: 0, never).
358
+ - `--clipboard`: Share this machine's clipboard with the desktop, both ways.
169
359
  - `--headless` / `--no-view`: Run headless without opening the viewer window.
170
360
  - `--no-reuse`: A second session beside one that is already open.
171
361
  - `--save`: Keep the password for next time, once it has worked.
@@ -174,6 +364,8 @@ where a decrypted key belongs, and it is offered from there.
174
364
 
175
365
  - `--session NAME`: Which session to drive (default: the one opened last).
176
366
  - `-o, --out FILE`: Screenshot output file path (default: `screen.png`).
367
+ - `-r, --recursive`: For `cp`, copy a directory.
368
+ - `--reconnect`: For `exec`, open a new terminal first, then run the command.
177
369
  - `--max-edge N`: Shrink a screenshot to fit N.
178
370
  - `--json`: Output machine-readable JSON results on stdout.
179
371
 
@@ -216,6 +408,7 @@ npm install ai-remote
216
408
  - RFC 4253 binary packet protocol with `curve25519-sha256` / `ecdh-sha2-nistp256`.
217
409
  - AEAD ciphers (`aes256-gcm@openssh.com`, `aes128-gcm@openssh.com`).
218
410
  - Terminal interactive and automated agent command execution.
411
+ - SFTP version 3 client on a subsystem channel, for file transfer.
219
412
  - **VNC Engine:**
220
413
  - RFB protocol integration with Apple Diffie-Hellman handshake support.
221
414
  - **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
@@ -128,6 +149,71 @@ leave) — that one is for a human, not for an agent, because it never returns.
128
149
  Reach for the GUI when the task is genuinely graphical. Reach for `exec` for
129
150
  anything a shell can do.
130
151
 
152
+ **After installing anything, reconnect.** A shell's environment is fixed when it
153
+ signs in, so a `node` installed a moment ago is not on the PATH of the shell that
154
+ installed it, and asking again will never find it:
155
+
156
+ ```bash
157
+ npx ai-remote exec "winget install OpenJS.NodeJS"
158
+ npx ai-remote reconnect # a new login, so a new PATH
159
+ npx ai-remote exec "node --version"
160
+
161
+ npx ai-remote exec --reconnect "node --version" # or both in one call
162
+ ```
163
+
164
+ The session, the desktop and the window all stay put — only the terminal is new,
165
+ which also means the working directory and any exported variables are gone. On
166
+ Windows, if a fresh login still cannot see it, sshd handed down its own stale
167
+ environment; read the PATH straight out of the registry instead:
168
+
169
+ ```bash
170
+ npx ai-remote exec '$env:Path = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Environment]::GetEnvironmentVariable("Path","User")'
171
+ ```
172
+
173
+ Note `&&` is not valid in PowerShell 5.1 — use `;` to chain, or `if ($?) { ... }`
174
+ when the second command should only run if the first worked.
175
+
176
+ ## The clipboard
177
+
178
+ Off by default. The **clipboard button in the window's toolbar** turns it on, or
179
+ from a command line:
180
+
181
+ ```bash
182
+ npx ai-remote clipboard on # both directions, until turned off
183
+ npx ai-remote clipboard # what it is doing now
184
+ npx ai-remote open HOST -u USER --clipboard # on from the start
185
+ ```
186
+
187
+ While it is on, text copied on this machine reaches the remote clipboard and
188
+ text copied there reaches this one — so a password, a path or a stack trace
189
+ moves without being typed. Only a desktop has a clipboard: RDP negotiates one
190
+ and VNC has it in the protocol, while an `--ssh` session has none and the button
191
+ does not appear.
192
+
193
+ Turn it off before copying anything on this machine that the remote end should
194
+ not see. Off is a real off — nothing is read and nothing is written.
195
+
196
+ ## Copying files
197
+
198
+ `cp` is scp with the session standing in for the host. A remote path starts with
199
+ a colon; anything else is on this machine.
200
+
201
+ ```bash
202
+ npx ai-remote cp ./setup.exe :C:/Users/owner/Desktop/ # to the session in hand
203
+ npx ai-remote cp :/var/log/syslog ./syslog # from it
204
+ npx ai-remote cp -r ./site :/srv/www # a directory
205
+ npx ai-remote cp desk:/tmp/a.txt build:/tmp/a.txt # between two sessions
206
+ ```
207
+
208
+ SFTP underneath, so the host needs no `scp` binary, spaces in filenames need no
209
+ quoting, and nothing goes through a shell. Two rules worth knowing: a one-letter
210
+ prefix is a drive, not a session, so `C:/Users/owner/x.txt` is local; and a
211
+ destination that already exists as a directory takes the source's name under it,
212
+ so `cp -r ./site :/srv/www` with `www` there gives `/srv/www/site`.
213
+
214
+ Uploading is how you get a script onto a machine, which is usually better than
215
+ typing one into a GUI. `--json` reports `bytes`, `files` and `rate`.
216
+
131
217
  ## A Mac, or anything else that speaks VNC
132
218
 
133
219
  `--vnc` opens the screen over VNC instead of RDP; port 5900 implies it. Every
@@ -188,10 +274,17 @@ command line is visible to every process on the machine.
188
274
 
189
275
  ```bash
190
276
  npx ai-remote open 192.168.1.20 -u owner --save # keep it, once it has worked
191
- npx ai-remote saved # what is kept, and where
277
+ npx ai-remote list # sessions, each marked if it has one
278
+ npx ai-remote saved # only what is kept, and where
192
279
  npx ai-remote forget 192.168.1.20 # one machine; --all for every one
193
280
  ```
194
281
 
282
+ `list` answers both halves of "what can I get back to": every open session, with
283
+ `password saved` against the ones that have one, and then the machines with a
284
+ password kept but nothing open — those need no `-u` or password to reopen. It
285
+ reads the index beside the keystore, not the keystore, so it never raises a
286
+ keychain prompt.
287
+
195
288
  `--save` writes to the system keystore — the login keychain on macOS, the desktop
196
289
  keyring on Linux — and where there is neither, to an encrypted file under
197
290
  `~/.config/ai-remote`; the command says which it got. That file is proof against
@@ -208,7 +301,8 @@ The session opened last is the one commands drive. With several open, name them:
208
301
  npx ai-remote open 192.168.1.20 -u owner --session desk
209
302
  npx ai-remote open 192.168.1.30 -u owner --session build
210
303
 
211
- npx ai-remote list # * marks the one commands will use
304
+ npx ai-remote list # * marks the one commands will use,
305
+ # and it also lists saved passwords
212
306
  npx ai-remote shot --session desk # a name, a host, or host:port
213
307
  npx ai-remote close --all # or `close` for the current one
214
308
  ```
@@ -240,7 +334,14 @@ fresh `shot` rather than trusting one from before you paused.
240
334
  authority either way, so a session nobody watches is not a lesser session.
241
335
  - `--fullscreen` fills the display and makes RDP use the current monitor's
242
336
  logical resolution. Add `--side-panel` to `open` to start with a fixed-width
243
- SSH terminal docked on the right; its 420 pixels are removed from RDP width.
337
+ SSH terminal docked on the right; its 610 pixels -- eighty columns, which is
338
+ what a Windows shell assumes it has -- are removed from the RDP width, and the
339
+ divider between the panes can be dragged.
340
+ - `--audio` (RDP only) asks the host to redirect the session's sound and plays
341
+ it in the window; the toolbar's speaker button mutes it. Off unless asked for:
342
+ the stream costs bandwidth in both directions, and Windows stops playing
343
+ through the host's own speakers while it is redirected. A headless session
344
+ ignores the flag, having nowhere to play anything.
244
345
  - `ai-remote view` adds a window to a headless session later (`view --watch-only`
245
346
  for an observer one); `view --close` takes it away without ending the session.
246
347
  - `--view-port N` moves the window's local server off 7373, which matters only
@@ -304,7 +405,7 @@ what `status` reports rather than what you asked for.
304
405
  ```bash
305
406
  export AI_REMOTE_PASSWORD=...
306
407
  npx ai-remote open HOST[:PORT] -u USER [-d DOMAIN] [--session NAME] [--fullscreen] [--side-panel]
307
- [-W 1920 -H 1080] [-s auto|nla|tls|rdp] [--save] [--idle N]
408
+ [-W 1920 -H 1080] [-s auto|nla|tls|rdp] [--save] [--idle N] [--audio]
308
409
  npx ai-remote open HOST --ssh -u USER [-i ~/.ssh/id_ed25519] # a terminal, no desktop
309
410
  npx ai-remote open HOST --vnc -u USER # a screen over VNC
310
411
  npx ai-remote vnc [--vnc-port N] # ...or add one to this session
@@ -313,7 +414,10 @@ npx ai-remote click X,Y [--button right|middle] [--double]
313
414
  npx ai-remote type "text"
314
415
  npx ai-remote key ControlLeft+KeyA
315
416
  npx ai-remote do "key MetaLeft; wait 800; type notepad; shot s.png"
316
- npx ai-remote exec "command"
417
+ npx ai-remote exec "command" [--reconnect]
418
+ npx ai-remote reconnect # a new login: fresh PATH
419
+ npx ai-remote clipboard [on|off] # share text both ways
420
+ npx ai-remote cp [-r] SRC DST # ':path' is on the session
317
421
  npx ai-remote view [--watch-only] [--close]
318
422
  npx ai-remote status | list | close [--all]
319
423
  npx ai-remote saved | forget [HOST[:PORT]] [--all]
@@ -337,5 +441,6 @@ node packages/core/dist/cli.mjs open HOST -u USER --headless
337
441
 
338
442
  The CLI's own source is [`packages/core/src/cli/`](src/cli/):
339
443
  `cli.ts` is the grammar, `sessions.ts` decides which session a command drives,
340
- `daemon.ts` is the process that holds the connection, and `viewer.ts` plus
341
- `window.ts` are the window. `pnpm test` covers them.
444
+ `daemon.ts` is the process that holds the connection, `copy.ts` is `cp` over the
445
+ SFTP client in [`protocols/ssh/sftp.ts`](src/protocols/ssh/sftp.ts), and
446
+ `viewer.ts` plus `window.ts` are the window. `pnpm test` covers them.