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
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
openWindow,
|
|
5
5
|
readCurrentDisplaySize,
|
|
6
6
|
runWindow
|
|
7
|
-
} from "./cli-chunk-
|
|
8
|
-
import "./cli-chunk-
|
|
7
|
+
} from "./cli-chunk-DGHEB4P5.mjs";
|
|
8
|
+
import "./cli-chunk-ZQ32C4LC.mjs";
|
|
9
9
|
export {
|
|
10
10
|
detectCurrentDisplaySize,
|
|
11
11
|
nativeWindowAvailable,
|
package/dist/cli.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
RUN_DIR,
|
|
7
7
|
SIDE_PANEL_WIDTH,
|
|
8
|
+
VERSION,
|
|
8
9
|
currentPath,
|
|
9
10
|
ensureRunDir,
|
|
10
11
|
fullscreenRdpSize,
|
|
@@ -12,7 +13,7 @@ import {
|
|
|
12
13
|
metaPath,
|
|
13
14
|
sessionName,
|
|
14
15
|
socketPath
|
|
15
|
-
} from "./cli-chunk-
|
|
16
|
+
} from "./cli-chunk-ZQ32C4LC.mjs";
|
|
16
17
|
|
|
17
18
|
// src/cli/cli.ts
|
|
18
19
|
import { basename, resolve as resolvePath } from "node:path";
|
|
@@ -169,6 +170,8 @@ The rest drive that session, and take no host:
|
|
|
169
170
|
${NAME} key <Code> press keys, e.g. MetaLeft, ControlLeft+KeyA
|
|
170
171
|
${NAME} exec "cmd" run a command in the terminal (SSH)
|
|
171
172
|
${NAME} shell an interactive terminal
|
|
173
|
+
${NAME} reconnect a new terminal: fresh login, fresh PATH
|
|
174
|
+
${NAME} clipboard [on|off] share this machine's clipboard both ways
|
|
172
175
|
${NAME} cp <src> <dst> copy files; a remote path starts with ':'
|
|
173
176
|
${NAME} do "steps" several actions in one go
|
|
174
177
|
${NAME} vnc give this session a screen over VNC
|
|
@@ -182,6 +185,7 @@ The rest drive that session, and take no host:
|
|
|
182
185
|
${NAME} forget [<host[:port]>] throw one away; --all throws all
|
|
183
186
|
${NAME} close close it; --all closes every session
|
|
184
187
|
${NAME} probe [<host[:port]>] is anything listening?
|
|
188
|
+
${NAME} --version which version this is
|
|
185
189
|
|
|
186
190
|
Copying files, like scp but with the session standing in for the host:
|
|
187
191
|
${NAME} cp ./setup.exe :C:/Users/owner/Desktop/ to the session in hand
|
|
@@ -217,6 +221,11 @@ Options for open
|
|
|
217
221
|
--fullscreen fill the display; RDP uses its current resolution
|
|
218
222
|
--side-panel open the SSH terminal docked on the right
|
|
219
223
|
--audio play the remote computer's sound in the window (RDP)
|
|
224
|
+
--no-clipboard do not share this machine's clipboard (shared by default)
|
|
225
|
+
--keys MODE adapt | literal (default: adapt)
|
|
226
|
+
adapt swaps Control and Command when one end is a Mac,
|
|
227
|
+
so Command-C copies on Windows and Control-C copies on
|
|
228
|
+
a Mac; literal sends every key through as itself
|
|
220
229
|
--view-port N port for the window (default: 7373)
|
|
221
230
|
--idle MINUTES close an unused session (default: 0, never)
|
|
222
231
|
--headless no window (same as --no-view)
|
|
@@ -230,6 +239,7 @@ Options for open
|
|
|
230
239
|
Options for the rest
|
|
231
240
|
--session NAME which session to drive (default: the last opened)
|
|
232
241
|
-r, --recursive for cp: copy a directory
|
|
242
|
+
--reconnect for exec: a new terminal first, then the command
|
|
233
243
|
-o, --out FILE where a screenshot goes (default: screen.png)
|
|
234
244
|
--max-edge N shrink a screenshot to fit N
|
|
235
245
|
--json machine-readable result on stdout
|
|
@@ -277,7 +287,8 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
277
287
|
"ssh-port",
|
|
278
288
|
"settle",
|
|
279
289
|
"vnc-port",
|
|
280
|
-
"port"
|
|
290
|
+
"port",
|
|
291
|
+
"keys"
|
|
281
292
|
]);
|
|
282
293
|
function parse(argv) {
|
|
283
294
|
const flags = {};
|
|
@@ -377,7 +388,7 @@ function refuseTarget(command, token) {
|
|
|
377
388
|
);
|
|
378
389
|
}
|
|
379
390
|
var DESKTOP_ONLY = /* @__PURE__ */ new Set(["shot", "click", "type", "key", "do"]);
|
|
380
|
-
var TAKES_NOTHING = /* @__PURE__ */ new Set(["shot", "view", "status", "shell", "close", "vnc"]);
|
|
391
|
+
var TAKES_NOTHING = /* @__PURE__ */ new Set(["shot", "view", "status", "shell", "close", "vnc", "reconnect"]);
|
|
381
392
|
var COORDINATE = /^-?\d+\s*,\s*-?\d+$/;
|
|
382
393
|
function refuseLegacyTarget(args) {
|
|
383
394
|
const { command, rest } = args;
|
|
@@ -405,7 +416,17 @@ async function ask(name, op, args = {}, timeoutMs) {
|
|
|
405
416
|
return response.result;
|
|
406
417
|
}
|
|
407
418
|
async function tell(session, op, args = {}, timeoutMs) {
|
|
408
|
-
const result = await ask(session.name, op, args, timeoutMs)
|
|
419
|
+
const result = await ask(session.name, op, args, timeoutMs).catch((error) => {
|
|
420
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
421
|
+
if (/^Unknown operation/.test(message)) {
|
|
422
|
+
throw new CliError(
|
|
423
|
+
`The session ${session.name} does not support \`${op}\`: it was started by an older version of ${NAME} and is still running it.
|
|
424
|
+
${NAME} close --session ${session.name} then open it again`,
|
|
425
|
+
12
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
throw error;
|
|
429
|
+
});
|
|
409
430
|
if (result === null) {
|
|
410
431
|
cleanup(session.name);
|
|
411
432
|
throw new CliError(`The session ${session.name} is no longer answering. It has been cleaned up.`, 12);
|
|
@@ -418,7 +439,7 @@ async function startSession(args, name, host, port, secret) {
|
|
|
418
439
|
const explicitHeight = flag(args, "H", "height");
|
|
419
440
|
let fullscreenSize = null;
|
|
420
441
|
if (mode === "rdp" && has(args, "fullscreen") && (!explicitWidth || !explicitHeight)) {
|
|
421
|
-
const { detectCurrentDisplaySize } = await import("./cli-window-
|
|
442
|
+
const { detectCurrentDisplaySize } = await import("./cli-window-H5VHVL4I.mjs");
|
|
422
443
|
const display = await detectCurrentDisplaySize();
|
|
423
444
|
if (display) fullscreenSize = fullscreenRdpSize(display, has(args, "side-panel"));
|
|
424
445
|
}
|
|
@@ -454,12 +475,15 @@ async function startSession(args, name, host, port, secret) {
|
|
|
454
475
|
// Zero unless asked for: a session stays open until it is closed. An agent
|
|
455
476
|
// that pauses to think is not an agent that has finished.
|
|
456
477
|
"--idle",
|
|
457
|
-
flag(args, "idle") ?? "0"
|
|
478
|
+
flag(args, "idle") ?? "0",
|
|
479
|
+
"--keys",
|
|
480
|
+
flag(args, "keys") ?? "adapt"
|
|
458
481
|
];
|
|
459
482
|
if (mode !== "rdp") daemonArgs.push(`--${mode}`);
|
|
460
483
|
for (const identity of args.identities) daemonArgs.push("-i", identity);
|
|
461
484
|
if (has(args, "no-view", "headless")) daemonArgs.push("--no-view");
|
|
462
485
|
if (has(args, "no-open")) daemonArgs.push("--no-open");
|
|
486
|
+
if (has(args, "no-clipboard")) daemonArgs.push("--no-clipboard");
|
|
463
487
|
if (has(args, "watch-only", "observe")) daemonArgs.push("--watch-only");
|
|
464
488
|
if (has(args, "tab")) daemonArgs.push("--tab");
|
|
465
489
|
if (has(args, "fullscreen")) daemonArgs.push("--fullscreen");
|
|
@@ -527,13 +551,18 @@ function sessionFailure(name, died, secret, mode) {
|
|
|
527
551
|
}
|
|
528
552
|
async function main() {
|
|
529
553
|
const args = parse(process.argv.slice(2));
|
|
554
|
+
if (has(args, "v", "version") || args.command === "version") {
|
|
555
|
+
process.stdout.write(`${NAME} ${VERSION}
|
|
556
|
+
`);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
530
559
|
if (!args.command || has(args, "h", "help")) {
|
|
531
560
|
process.stdout.write(USAGE);
|
|
532
561
|
return;
|
|
533
562
|
}
|
|
534
563
|
const json = has(args, "json");
|
|
535
564
|
if (args.command === "__display-size") {
|
|
536
|
-
const { readCurrentDisplaySize } = await import("./cli-window-
|
|
565
|
+
const { readCurrentDisplaySize } = await import("./cli-window-H5VHVL4I.mjs");
|
|
537
566
|
const size = await readCurrentDisplaySize();
|
|
538
567
|
if (size) process.stdout.write(`${JSON.stringify(size)}
|
|
539
568
|
`);
|
|
@@ -542,7 +571,7 @@ async function main() {
|
|
|
542
571
|
if (args.command === "__session") {
|
|
543
572
|
const mode = modeFor(args, args.rest[0]);
|
|
544
573
|
const { host, port } = splitTarget(args.rest[0] ?? "", defaultPort(mode));
|
|
545
|
-
const { runDaemon } = await import("./cli-daemon-
|
|
574
|
+
const { runDaemon } = await import("./cli-daemon-4YPQAC5X.mjs");
|
|
546
575
|
await runDaemon({
|
|
547
576
|
name: sessionName(host, port, flag(args, "name", "session")),
|
|
548
577
|
host,
|
|
@@ -568,12 +597,14 @@ async function main() {
|
|
|
568
597
|
// RDP is the only protocol here with an audio channel, and a session with
|
|
569
598
|
// no window has nowhere to play what the channel would carry.
|
|
570
599
|
audio: has(args, "audio", "sound") && mode === "rdp" && !has(args, "no-view", "headless"),
|
|
571
|
-
idleMs: Math.max(0, Number(flag(args, "idle") ?? 0)) * 6e4
|
|
600
|
+
idleMs: Math.max(0, Number(flag(args, "idle") ?? 0)) * 6e4,
|
|
601
|
+
keys: flag(args, "keys") === "literal" ? "literal" : "adapt",
|
|
602
|
+
clipboard: !has(args, "no-clipboard")
|
|
572
603
|
});
|
|
573
604
|
return;
|
|
574
605
|
}
|
|
575
606
|
if (args.command === "__window") {
|
|
576
|
-
const { runWindow } = await import("./cli-window-
|
|
607
|
+
const { runWindow } = await import("./cli-window-H5VHVL4I.mjs");
|
|
577
608
|
await runWindow(process.argv.slice(3));
|
|
578
609
|
return;
|
|
579
610
|
}
|
|
@@ -620,6 +651,10 @@ Saved, nothing open (\`${NAME} open <host>\` needs no password):
|
|
|
620
651
|
if (args.command === "open") {
|
|
621
652
|
const target = args.rest[0];
|
|
622
653
|
if (!target) throw new CliError(`Name the machine to connect to: \`${NAME} open <host[:port]>\``, 12);
|
|
654
|
+
const keys = flag(args, "keys");
|
|
655
|
+
if (keys && keys !== "adapt" && keys !== "literal") {
|
|
656
|
+
throw new CliError(`--keys takes "adapt" or "literal", not "${keys}".`, 12);
|
|
657
|
+
}
|
|
623
658
|
const mode = modeFor(args, target);
|
|
624
659
|
const { host, port } = splitTarget(target, defaultPort(mode));
|
|
625
660
|
const asked = flag(args, "session", "name");
|
|
@@ -665,6 +700,11 @@ Saved, nothing open (\`${NAME} open <host>\` needs no password):
|
|
|
665
700
|
sidePanel: has(args, "side-panel")
|
|
666
701
|
}));
|
|
667
702
|
}
|
|
703
|
+
const shared = await ask(name2, "clipboard", {});
|
|
704
|
+
Object.assign(status, { clipboard: shared });
|
|
705
|
+
if (has(args, "clipboard") && !shared?.available) {
|
|
706
|
+
process.stderr.write("The clipboard cannot be shared with this session (it needs a desktop at the far end and a clipboard tool here).\n");
|
|
707
|
+
}
|
|
668
708
|
setCurrent(name2);
|
|
669
709
|
report(json, "open", {
|
|
670
710
|
host,
|
|
@@ -682,6 +722,14 @@ Saved, nothing open (\`${NAME} open <host>\` needs no password):
|
|
|
682
722
|
if (prompted && !saved) {
|
|
683
723
|
process.stderr.write("Add --save next time to keep that password.\n");
|
|
684
724
|
}
|
|
725
|
+
if (status.keys?.swapped && status.keys?.describe) {
|
|
726
|
+
process.stderr.write(`${status.keys.describe}
|
|
727
|
+
`);
|
|
728
|
+
}
|
|
729
|
+
if (status.clipboard?.on) {
|
|
730
|
+
process.stderr.write(`Clipboard shared both ways (${status.clipboard.tool}). \`${NAME} clipboard off\` stops it; --no-clipboard opens without it.
|
|
731
|
+
`);
|
|
732
|
+
}
|
|
685
733
|
return;
|
|
686
734
|
}
|
|
687
735
|
if (args.command === "probe") {
|
|
@@ -759,6 +807,32 @@ Saved, nothing open (\`${NAME} open <host>\` needs no password):
|
|
|
759
807
|
}
|
|
760
808
|
const session = currentSession(args);
|
|
761
809
|
const { name } = session;
|
|
810
|
+
if (args.command === "reconnect") {
|
|
811
|
+
const before = await ask(name, "status", {}, 5e3);
|
|
812
|
+
const result = await tell(session, "shell-reconnect");
|
|
813
|
+
report(json, "reconnect", {
|
|
814
|
+
name,
|
|
815
|
+
host: session.host,
|
|
816
|
+
port: session.port,
|
|
817
|
+
replaced: result.replaced,
|
|
818
|
+
previous: before?.shell ?? "unknown",
|
|
819
|
+
shell: result.shell
|
|
820
|
+
});
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (args.command === "clipboard") {
|
|
824
|
+
const asked = args.rest[0]?.toLowerCase();
|
|
825
|
+
if (asked && !["on", "off"].includes(asked)) {
|
|
826
|
+
throw new CliError(`\`${NAME} clipboard\` takes "on", "off", or nothing to report.`, 12);
|
|
827
|
+
}
|
|
828
|
+
const state = await tell(
|
|
829
|
+
session,
|
|
830
|
+
"clipboard",
|
|
831
|
+
asked ? { on: asked === "on" } : {}
|
|
832
|
+
);
|
|
833
|
+
report(json, "clipboard", { name, ...state });
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
762
836
|
if (args.command === "shell") {
|
|
763
837
|
await interactiveShell(args, session);
|
|
764
838
|
return;
|
|
@@ -886,6 +960,7 @@ Open the host again without --ssh for a desktop.`,
|
|
|
886
960
|
refuseSessionHost(args, "exec", session);
|
|
887
961
|
const command = args.rest.join(" ");
|
|
888
962
|
if (!command) throw new CliError("exec needs a command to run", 12);
|
|
963
|
+
if (has(args, "reconnect", "fresh")) await tell(session, "shell-reconnect");
|
|
889
964
|
const result = await tell(session, "exec", { command });
|
|
890
965
|
Object.assign(summary, result);
|
|
891
966
|
if (!json) {
|
|
@@ -911,7 +986,7 @@ function report(json, command, summary) {
|
|
|
911
986
|
`);
|
|
912
987
|
}
|
|
913
988
|
async function sshLogin(args, session, what) {
|
|
914
|
-
const { bareUsername } = await import("./cli-shell-
|
|
989
|
+
const { bareUsername } = await import("./cli-shell-45XRTE7O.mjs");
|
|
915
990
|
const username = bareUsername(flag(args, "ssh-user") ?? session.sshUser ?? "");
|
|
916
991
|
if (!username) {
|
|
917
992
|
throw new CliError(
|
|
@@ -993,7 +1068,7 @@ async function copyFiles(args, json) {
|
|
|
993
1068
|
upload,
|
|
994
1069
|
walkLocal,
|
|
995
1070
|
walkRemote
|
|
996
|
-
} = await import("./cli-copy-
|
|
1071
|
+
} = await import("./cli-copy-BOF44DWQ.mjs");
|
|
997
1072
|
const from = resolveEndpoint(args, parseEndpoint(args.rest[0]));
|
|
998
1073
|
const to = resolveEndpoint(args, parseEndpoint(args.rest[1]));
|
|
999
1074
|
if (!from.endpoint.remote && !to.endpoint.remote) {
|
|
@@ -1071,7 +1146,7 @@ async function copyFiles(args, json) {
|
|
|
1071
1146
|
}
|
|
1072
1147
|
}
|
|
1073
1148
|
async function interactiveShell(args, session) {
|
|
1074
|
-
const { Shell } = await import("./cli-shell-
|
|
1149
|
+
const { Shell } = await import("./cli-shell-45XRTE7O.mjs");
|
|
1075
1150
|
const login = await sshLogin(args, session, "a terminal");
|
|
1076
1151
|
const { username, port, identities } = login;
|
|
1077
1152
|
const prompts = prompting(args);
|
|
@@ -1172,6 +1247,15 @@ The password is saved in ${summary.saved}.` : ""}`;
|
|
|
1172
1247
|
const what = files === 1 ? "" : `${files} files, `;
|
|
1173
1248
|
return `Copied ${what}${summary.size} from ${summary.from} to ${summary.to}${summary.rate ? ` at ${summary.rate}` : ""}.`;
|
|
1174
1249
|
}
|
|
1250
|
+
if (command === "clipboard") {
|
|
1251
|
+
if (!summary.available) {
|
|
1252
|
+
return "The clipboard cannot be shared with this session: it needs a desktop at the far end and a clipboard tool on this machine.";
|
|
1253
|
+
}
|
|
1254
|
+
return summary.on ? `Clipboard sharing is on for ${summary.name} (${summary.tool}). Copy here and paste there, or the other way round.` : `Clipboard sharing is off for ${summary.name}.`;
|
|
1255
|
+
}
|
|
1256
|
+
if (command === "reconnect") {
|
|
1257
|
+
return `${summary.replaced ? "Replaced" : "Opened"} the terminal on ${summary.name}. It is a new login, so it sees whatever the PATH says now -- and has forgotten the working directory and any exported variables.`;
|
|
1258
|
+
}
|
|
1175
1259
|
if (command === "shot") return `Wrote ${summary.file} (${summary.width}x${summary.height}) from ${summary.name}.`;
|
|
1176
1260
|
if (command === "click") {
|
|
1177
1261
|
const { x, y } = summary.clicked;
|