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 +147 -0
- package/SKILL.md +72 -1
- package/dist/{cli-chunk-7UHHVT7Q.mjs → cli-chunk-DGHEB4P5.mjs} +7 -5
- package/dist/{cli-chunk-RBCU3NX4.mjs → cli-chunk-Q33YGIDO.mjs} +1 -1
- package/dist/{cli-chunk-EYQCDSPT.mjs → cli-chunk-S2TWL5LF.mjs} +13 -0
- package/dist/{cli-chunk-K2DYJXC5.mjs → cli-chunk-ZQ32C4LC.mjs} +4 -0
- package/dist/{cli-copy-NRXMTHID.mjs → cli-copy-BOF44DWQ.mjs} +1 -1
- package/dist/{cli-daemon-LU4LHDCH.mjs → cli-daemon-4YPQAC5X.mjs} +393 -12
- package/dist/{cli-shell-TE6DLBIB.mjs → cli-shell-45XRTE7O.mjs} +2 -2
- package/dist/{cli-window-LN32ONC4.mjs → cli-window-H5VHVL4I.mjs} +2 -2
- package/dist/cli.mjs +97 -13
- package/dist/protocols.js +12 -11
- package/package.json +1 -1
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-
|
|
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
|
|
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
|
|
224
|
-
<key>CFBundleVersion</key><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 !==
|
|
253
|
+
if (old !== bundleName()) rmSync(join(windowsDir(), old), { recursive: true, force: true });
|
|
252
254
|
}
|
|
253
255
|
}
|
|
254
256
|
|
|
@@ -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,
|