ai-remote 0.4.15 → 0.4.17

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
@@ -111,6 +111,8 @@ why `npx` is still what the examples here use.
111
111
  ai-remote key <Code> press keys, e.g. MetaLeft, ControlLeft+KeyA
112
112
  ai-remote exec "cmd" run a command in the terminal (SSH)
113
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
114
116
  ai-remote cp <src> <dst> copy files; a remote path starts with ':'
115
117
  ai-remote do "steps" several actions in one go
116
118
  ai-remote view open the window on the session
@@ -214,6 +216,147 @@ npx ai-remote exec "uname -a"
214
216
  npx ai-remote shell # Ctrl-] to leave
215
217
  ```
216
218
 
219
+ ### Modifier keys between a Mac and a PC
220
+
221
+ A Mac's shortcut modifier is Command; Windows and Linux use Control. Send the
222
+ physical key through unchanged and Command-C on a Windows desktop opens the
223
+ Start menu and types a C, because Command is physically the same key as the
224
+ Windows logo key.
225
+
226
+ Microsoft's own Mac client sends it through unchanged and documents the
227
+ consequence: *"The Command key on the Mac keyboard equals the Windows key. To
228
+ perform actions that use the Command button on the Mac, you will need to use the
229
+ control button in Windows (for example Copy = Ctrl+C)."* That is consistent, and
230
+ it is also the thing people complain about most, because it asks the fingers to
231
+ learn a second shortcut modifier for one application.
232
+
233
+ So by default, when one end is a Mac and the other is not, **Control and Command
234
+ swap**:
235
+
236
+ | pressed on the Mac | arrives on the Windows/Linux desktop |
237
+ | --- | --- |
238
+ | Command | Control — `Command-C` copies |
239
+ | Control | the Windows key — `Control-D` shows the desktop |
240
+ | Option | Alt, unchanged |
241
+
242
+ and the mirror when a PC keyboard drives a Mac desktop (the VNC case):
243
+
244
+ | pressed on the PC | arrives on the Mac |
245
+ | --- | --- |
246
+ | Control | Command — `Control-C` copies |
247
+ | Windows key | Control |
248
+ | Alt | Option, unchanged |
249
+
250
+ A swap rather than a redirect, deliberately: it is a bijection, so the Windows
251
+ key is still reachable and nothing becomes impossible to press. Redirecting
252
+ Command to Control and leaving Control alone would give a Mac two Controls and
253
+ no Windows key at all.
254
+
255
+ ```bash
256
+ npx ai-remote open 192.168.1.50 -u owner # adapt, the default
257
+ npx ai-remote open 192.168.1.50 -u owner --keys literal # Microsoft's behaviour
258
+ ```
259
+
260
+ The far end is worked out rather than assumed: RDP is Windows, and for VNC only
261
+ macOS Screen Sharing offers Apple's Diffie-Hellman security type, which is
262
+ already what decides how far a scroll notch goes. Ends that agree -- Mac to Mac,
263
+ PC to PC, Linux to Windows -- are left alone entirely, and `status` reports what
264
+ the mapping is doing under `keys`.
265
+
266
+ **Only the window's live keyboard goes through this.** `key` and `do` name the
267
+ key they want *on the host*, so they are never remapped -- `key MetaLeft` opens
268
+ the Start menu on Windows whatever keyboard is in front of you, and
269
+ `key ControlLeft+KeyC` means Control-C there. Ctrl+Alt+Del is likewise a
270
+ sequence the session synthesizes from real Windows codes, not three keys somebody
271
+ pressed, so the swap does not turn it into Windows+Alt+Del.
272
+
273
+ ### The clipboard
274
+
275
+ **On by default.** Text copied on this machine reaches the remote clipboard and
276
+ text copied there reaches this one. The **clipboard button in the window's
277
+ toolbar** is the switch, and so is the command line:
278
+
279
+ ```bash
280
+ npx ai-remote clipboard # what it is doing now
281
+ npx ai-remote clipboard off # stop it for this session
282
+ npx ai-remote open 192.168.1.50 -u owner --no-clipboard
283
+ ```
284
+
285
+ Only a desktop carries a clipboard -- RDP negotiates a `cliprdr` channel and VNC
286
+ has cut-text in the protocol -- so a terminal-only session has none, the toolbar
287
+ button does not appear, and nothing is started.
288
+
289
+ Two things follow from it being a default rather than a choice, and both are
290
+ worth knowing:
291
+
292
+ - **Everything you copy locally is sent** while a session is open. That is the
293
+ point, and it is also the reason `open` prints one line saying so. If you are
294
+ about to copy something the remote machine should not have, `clipboard off`
295
+ first.
296
+ - **It polls, so it costs something.** No platform gives Node a
297
+ clipboard-change event, so the local side is read twice a second for as long
298
+ as the session is open. Measured on an M4 that is 5.7 ms per read, about 1% of
299
+ one core per session. The rate stays at twice a second rather than being
300
+ slowed to save it, because the failure it prevents -- pasting what was on the
301
+ clipboard *before* the thing you just copied -- is worse than the cost.
302
+
303
+ **The bridge is the session's, not the page's.** The obvious place to put it
304
+ would be the viewer, using the browser's `navigator.clipboard` -- but a browser
305
+ only grants a page the clipboard while that page is focused, and the moment
306
+ somebody wants the remote clipboard is exactly the moment they have clicked away
307
+ into a local application to paste it. So the session does the copying, on this
308
+ machine, whether or not a window is open; the toolbar button turns it on rather
309
+ than doing it. Two windows on one session are two views of one switch.
310
+
311
+ Two consequences worth knowing:
312
+
313
+ - **It polls.** No platform gives Node a clipboard-change event, so the local
314
+ side is read twice a second while sharing is on. That is why it is a toggle
315
+ and not always on, and why off is a real off: the timer is not running and the
316
+ host's clipboard messages are dropped.
317
+ - **Nothing goes round in circles.** Text that arrived from the host is not then
318
+ noticed by the poller and pushed back to it, and what was already on the
319
+ clipboard when sharing started is not treated as a change -- so turning
320
+ sharing on does not send something copied an hour ago.
321
+
322
+ It uses whatever the platform has: `pbcopy`/`pbpaste` on macOS,
323
+ `Set-Clipboard`/`Get-Clipboard` on Windows, and `wl-clipboard`, `xclip` or
324
+ `xsel` on Linux. A machine with none reports the clipboard as unavailable and
325
+ the button stays hidden rather than offering a switch that cannot work.
326
+
327
+ Sharing sits behind the same control gate as keyboard and pointer input: a
328
+ watch-only viewer can read the screen and cannot reach either clipboard.
329
+
330
+ ### A new terminal, after installing something
331
+
332
+ A shell inherits its environment when it signs in, and nothing can change a
333
+ running process's inherited environment from outside it. So a program installed
334
+ a moment ago is simply not on the PATH of the shell that installed it, and
335
+ running the command again will not find it however many times it is tried.
336
+
337
+ `reconnect` is the fix, and it is the same one a person reaches for -- close the
338
+ terminal, open another:
339
+
340
+ ```bash
341
+ npx ai-remote exec "winget install OpenJS.NodeJS"
342
+ npx ai-remote reconnect
343
+ npx ai-remote exec "node --version"
344
+
345
+ npx ai-remote exec --reconnect "node --version" # or both in one call
346
+ ```
347
+
348
+ The session, the desktop and the window stay exactly where they are; only the
349
+ terminal is new. That also means the working directory and any exported
350
+ variables are gone, which is the point -- it is a new login, not a refresh.
351
+
352
+ On Windows a fresh login can still inherit a stale environment, because sshd
353
+ built its own environment block when the service started. Reading the PATH
354
+ straight out of the registry works regardless:
355
+
356
+ ```bash
357
+ npx ai-remote exec '$env:Path = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Environment]::GetEnvironmentVariable("Path","User")'
358
+ ```
359
+
217
360
  ### Copying files
218
361
 
219
362
  `cp` is `scp` with the session standing in for the host: a remote path starts
@@ -280,6 +423,9 @@ where a decrypted key belongs, and it is offered from there.
280
423
  - `-s, --security MODE`: `auto` | `nla` | `tls` | `rdp` (default: `auto`).
281
424
  - `-W, --width N` / `-H, --height N`: Desktop resolution (default: 1280x800).
282
425
  - `--idle MINUTES`: Close an unused session (default: 0, never).
426
+ - `--no-clipboard`: Do not share this machine's clipboard. Shared by default.
427
+ - `--keys MODE`: `adapt` (default) swaps Control and Command when one end is a
428
+ Mac and the other is not; `literal` sends every key through as itself.
283
429
  - `--headless` / `--no-view`: Run headless without opening the viewer window.
284
430
  - `--no-reuse`: A second session beside one that is already open.
285
431
  - `--save`: Keep the password for next time, once it has worked.
@@ -289,6 +435,7 @@ where a decrypted key belongs, and it is offered from there.
289
435
  - `--session NAME`: Which session to drive (default: the one opened last).
290
436
  - `-o, --out FILE`: Screenshot output file path (default: `screen.png`).
291
437
  - `-r, --recursive`: For `cp`, copy a directory.
438
+ - `--reconnect`: For `exec`, open a new terminal first, then run the command.
292
439
  - `--max-edge N`: Shrink a screenshot to fit N.
293
440
  - `--json`: Output machine-readable JSON results on stdout.
294
441
 
package/SKILL.md CHANGED
@@ -114,6 +114,29 @@ screenshot full size and click the coordinates you read off it.
114
114
  image tokens and a 800px one is a fraction of that, usually with no loss of
115
115
  anything you needed to see.
116
116
 
117
+ ## Modifier keys between a Mac and a PC
118
+
119
+ When one end is a Mac and the other is not, **Control and Command swap** so the
120
+ shortcut key the fingers know keeps working:
121
+
122
+ | pressed on a Mac | arrives on Windows/Linux |
123
+ | --- | --- |
124
+ | Command | Control — so Command-C copies |
125
+ | Control | the Windows key |
126
+ | Option | Alt, unchanged |
127
+
128
+ and the mirror when a PC keyboard drives a Mac desktop: Control arrives as
129
+ Command, the Windows key as Control, Alt as Option.
130
+
131
+ `open --keys literal` turns it off and sends every key through as itself, which
132
+ is what Microsoft's own Mac client does — there, Command *is* the Windows key and
133
+ Windows shortcuts want Control.
134
+
135
+ **This applies only to the window's live keyboard.** `key` and `do` name the key
136
+ they want *on the host*, so they are never remapped: `key MetaLeft` opens the
137
+ Start menu on Windows whatever the local keyboard is, and `key ControlLeft+KeyC`
138
+ means Control-C there.
139
+
117
140
  ## Keys
118
141
 
119
142
  Key codes are DOM-style physical codes: `MetaLeft`, `Enter`, `Escape`, `Tab`,
@@ -149,6 +172,51 @@ leave) — that one is for a human, not for an agent, because it never returns.
149
172
  Reach for the GUI when the task is genuinely graphical. Reach for `exec` for
150
173
  anything a shell can do.
151
174
 
175
+ **After installing anything, reconnect.** A shell's environment is fixed when it
176
+ signs in, so a `node` installed a moment ago is not on the PATH of the shell that
177
+ installed it, and asking again will never find it:
178
+
179
+ ```bash
180
+ npx ai-remote exec "winget install OpenJS.NodeJS"
181
+ npx ai-remote reconnect # a new login, so a new PATH
182
+ npx ai-remote exec "node --version"
183
+
184
+ npx ai-remote exec --reconnect "node --version" # or both in one call
185
+ ```
186
+
187
+ The session, the desktop and the window all stay put — only the terminal is new,
188
+ which also means the working directory and any exported variables are gone. On
189
+ Windows, if a fresh login still cannot see it, sshd handed down its own stale
190
+ environment; read the PATH straight out of the registry instead:
191
+
192
+ ```bash
193
+ npx ai-remote exec '$env:Path = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Environment]::GetEnvironmentVariable("Path","User")'
194
+ ```
195
+
196
+ Note `&&` is not valid in PowerShell 5.1 — use `;` to chain, or `if ($?) { ... }`
197
+ when the second command should only run if the first worked.
198
+
199
+ ## The clipboard
200
+
201
+ **On by default**, both ways: text copied on this machine reaches the remote
202
+ clipboard and text copied there reaches this one, so a password, a path or a
203
+ stack trace moves without being typed.
204
+
205
+ ```bash
206
+ npx ai-remote clipboard # what it is doing now
207
+ npx ai-remote clipboard off # stop it for this session
208
+ npx ai-remote open HOST -u USER --no-clipboard # never start it
209
+ ```
210
+
211
+ The toolbar's clipboard button is the same switch. Only a desktop has a
212
+ clipboard — RDP negotiates one and VNC has it in the protocol — so an `--ssh`
213
+ session has none and the button does not appear.
214
+
215
+ **Tell the user it is on** if they are about to copy something on their own
216
+ machine that the remote end should not see: while sharing is on, everything
217
+ copied locally is sent. `clipboard off` is a real off — nothing is read and
218
+ nothing is written.
219
+
152
220
  ## Copying files
153
221
 
154
222
  `cp` is scp with the session standing in for the host. A remote path starts with
@@ -370,7 +438,10 @@ npx ai-remote click X,Y [--button right|middle] [--double]
370
438
  npx ai-remote type "text"
371
439
  npx ai-remote key ControlLeft+KeyA
372
440
  npx ai-remote do "key MetaLeft; wait 800; type notepad; shot s.png"
373
- npx ai-remote exec "command"
441
+ npx ai-remote exec "command" [--reconnect]
442
+ npx ai-remote reconnect # a new login: fresh PATH
443
+ npx ai-remote clipboard [on|off] # share text both ways
444
+ # open --keys adapt|literal modifier mapping
374
445
  npx ai-remote cp [-r] SRC DST # ':path' is on the session
375
446
  npx ai-remote view [--watch-only] [--close]
376
447
  npx ai-remote status | list | close [--all]
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  ROOT,
3
+ VERSION,
3
4
  isTitlebarDragPoint,
4
5
  logicalDisplaySize
5
- } from "./cli-chunk-K2DYJXC5.mjs";
6
+ } from "./cli-chunk-ZQ32C4LC.mjs";
6
7
 
7
8
  // src/cli/window.ts
8
9
  import { spawn } from "node:child_process";
@@ -208,7 +209,8 @@ var ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><def
208
209
  // src/cli/bundle.ts
209
210
  var APP_NAME = "ai-remote";
210
211
  var windowsDir = () => join(ROOT, "window");
211
- var bundleDir = () => join(windowsDir(), `v${ICON_VERSION}`, `${APP_NAME}.app`);
212
+ var bundleName = () => `v${VERSION}-${ICON_VERSION}`;
213
+ var bundleDir = () => join(windowsDir(), bundleName(), `${APP_NAME}.app`);
212
214
  var PLIST = `<?xml version="1.0" encoding="UTF-8"?>
213
215
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
214
216
  <plist version="1.0">
@@ -220,8 +222,8 @@ var PLIST = `<?xml version="1.0" encoding="UTF-8"?>
220
222
  <key>CFBundleIconFile</key><string>${APP_NAME}</string>
221
223
  <key>CFBundlePackageType</key><string>APPL</string>
222
224
  <key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
223
- <key>CFBundleShortVersionString</key><string>1.0</string>
224
- <key>CFBundleVersion</key><string>${ICON_VERSION}</string>
225
+ <key>CFBundleShortVersionString</key><string>${VERSION}</string>
226
+ <key>CFBundleVersion</key><string>${VERSION}</string>
225
227
  <key>NSHighResolutionCapable</key><true/>
226
228
  <key>LSApplicationCategoryType</key><string>public.app-category.utilities</string>
227
229
  </dict>
@@ -248,7 +250,7 @@ function build(app, executable) {
248
250
  writeFileSync(join(contents, "Resources", `${APP_NAME}.icns`), iconIcns());
249
251
  symlinkSync(process.execPath, executable);
250
252
  for (const old of readdirSync(windowsDir())) {
251
- if (old !== `v${ICON_VERSION}`) rmSync(join(windowsDir(), old), { recursive: true, force: true });
253
+ if (old !== bundleName()) rmSync(join(windowsDir(), old), { recursive: true, force: true });
252
254
  }
253
255
  }
254
256
 
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SshSession,
3
3
  TcpTransport
4
- } from "./cli-chunk-EYQCDSPT.mjs";
4
+ } from "./cli-chunk-S2TWL5LF.mjs";
5
5
 
6
6
  // src/cli/shell.ts
7
7
  function bareUsername(username) {
@@ -935,6 +935,18 @@ function filterDisplay(session, bytes) {
935
935
  }
936
936
  return shown ? encodeUtf8(shown) : NO_BYTES;
937
937
  }
938
+ function announceCommand(session, command) {
939
+ const shown = command.replace(/\r?\n/g, " \u23CE ").trimEnd();
940
+ if (!shown) return;
941
+ session.dispatchEvent(new CustomEvent("data", {
942
+ detail: {
943
+ bytes: NO_BYTES,
944
+ display: encodeUtf8(`\x1B[1m${shown}\x1B[0m\r
945
+ `),
946
+ isStderr: false
947
+ }
948
+ }));
949
+ }
938
950
  function releaseDisplayFilter(session) {
939
951
  const filter = session.displayFilter;
940
952
  session.displayFilter = null;
@@ -1092,6 +1104,7 @@ var SshSession = class extends EventTarget {
1092
1104
  this.commandInFlight = true;
1093
1105
  const startedAt = Date.now();
1094
1106
  this.displayFilter = { begin, end, stage: "before", pending: "" };
1107
+ announceCommand(this, text);
1095
1108
  this.dispatchEvent(new CustomEvent("command", {
1096
1109
  detail: { phase: "start", id, command: text }
1097
1110
  }));
@@ -1,3 +1,6 @@
1
+ // src/cli/generated/version.ts
2
+ var VERSION = "0.4.17";
3
+
1
4
  // src/cli/viewer-layout.ts
2
5
  var TERMINAL_COLUMNS = 80;
3
6
  var TERMINAL_CELL_WIDTH = 7.25;
@@ -48,6 +51,7 @@ var logPath = (name) => join(RUN_DIR, `${name}.log`);
48
51
  var currentPath = () => join(ROOT, "current");
49
52
 
50
53
  export {
54
+ VERSION,
51
55
  SIDE_PANEL_WIDTH,
52
56
  isTitlebarDragPoint,
53
57
  logicalDisplaySize,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SshSession,
3
3
  TcpTransport
4
- } from "./cli-chunk-EYQCDSPT.mjs";
4
+ } from "./cli-chunk-S2TWL5LF.mjs";
5
5
  import {
6
6
  SshReader,
7
7
  SshWriter