ai-remote 0.6.0 → 0.6.1
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/SKILL.md +2 -2
- package/dist/{cli-chunk-P4645KKB.mjs → cli-chunk-CD47NHZT.mjs} +1 -1
- package/dist/{cli-chunk-TKKENOSJ.mjs → cli-chunk-MURUADW4.mjs} +1 -1
- package/dist/cli-daemon-SD2AS34J.mjs +9872 -0
- package/dist/{cli-window-AWXZ5NJX.mjs → cli-window-G5ESQTF5.mjs} +2 -2
- package/dist/cli.mjs +106 -14
- package/dist/computer.d.ts +108 -9
- package/dist/computer.js +3 -3
- package/package.json +1 -1
- package/dist/cli-daemon-3L36TBIB.mjs +0 -9377
|
@@ -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-CD47NHZT.mjs";
|
|
8
|
+
import "./cli-chunk-MURUADW4.mjs";
|
|
9
9
|
export {
|
|
10
10
|
detectCurrentDisplaySize,
|
|
11
11
|
nativeWindowAvailable,
|
package/dist/cli.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
metaPath,
|
|
14
14
|
sessionName,
|
|
15
15
|
socketPath
|
|
16
|
-
} from "./cli-chunk-
|
|
16
|
+
} from "./cli-chunk-MURUADW4.mjs";
|
|
17
17
|
|
|
18
18
|
// src/cli/cli.ts
|
|
19
19
|
import { resolve as resolvePath } from "node:path";
|
|
@@ -62,7 +62,14 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
62
62
|
"vnc-port",
|
|
63
63
|
"port",
|
|
64
64
|
"keys",
|
|
65
|
-
"region"
|
|
65
|
+
"region",
|
|
66
|
+
// How an input is performed rather than where: how long a button or chord is
|
|
67
|
+
// held, how many times, what is held down with it, and the floor between one
|
|
68
|
+
// action and the next.
|
|
69
|
+
"hold",
|
|
70
|
+
"count",
|
|
71
|
+
"with",
|
|
72
|
+
"pace"
|
|
66
73
|
]);
|
|
67
74
|
var OPTIONAL_VALUE_FLAGS = { shot: /\.(png|jpe?g)$/i };
|
|
68
75
|
function parse(argv) {
|
|
@@ -105,6 +112,22 @@ var shotFile = (args) => {
|
|
|
105
112
|
if (value === void 0) return void 0;
|
|
106
113
|
return typeof value === "string" && value || flag(args, "o", "out") || "screen.png";
|
|
107
114
|
};
|
|
115
|
+
function clickAction(args) {
|
|
116
|
+
const spec = args.rest[0];
|
|
117
|
+
if (!spec) throw new CliError("click needs a position, e.g. click 640,400", 12);
|
|
118
|
+
const [x, y] = spec.split(",").map(Number);
|
|
119
|
+
return {
|
|
120
|
+
type: "click",
|
|
121
|
+
x,
|
|
122
|
+
y,
|
|
123
|
+
mouse_button: flag(args, "button") ?? "left",
|
|
124
|
+
count: has(args, "double") ? 2 : Number(flag(args, "count") ?? 1),
|
|
125
|
+
intervalMs: Number(flag(args, "pace") ?? 0),
|
|
126
|
+
duration: Number(flag(args, "hold") ?? 0),
|
|
127
|
+
...flag(args, "with") ? { key: flag(args, "with") } : {}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
var audioEnabled = (args, mode) => mode === "rdp" && !has(args, "no-view", "headless", "no-audio");
|
|
108
131
|
|
|
109
132
|
// src/cli/prompt.ts
|
|
110
133
|
function canPrompt() {
|
|
@@ -250,6 +273,7 @@ The rest drive that session, and take no host:
|
|
|
250
273
|
|
|
251
274
|
${NAME} shot screenshot the desktop
|
|
252
275
|
${NAME} click X,Y click, --button right|middle, --double
|
|
276
|
+
--hold MS, --count N, --with Shift
|
|
253
277
|
${NAME} type "text" type into whatever has focus
|
|
254
278
|
${NAME} key <Code> press keys, e.g. MetaLeft, ControlLeft+KeyA
|
|
255
279
|
${NAME} exec "cmd" run a command in the terminal (SSH)
|
|
@@ -261,6 +285,7 @@ The rest drive that session, and take no host:
|
|
|
261
285
|
${NAME} batch actions.json structured actions; - reads JSON from stdin
|
|
262
286
|
${NAME} vnc give this session a screen over VNC
|
|
263
287
|
(--vnc-port N, default 5900)
|
|
288
|
+
${NAME} input off take the pointer from the window (on gives it back)
|
|
264
289
|
${NAME} view open the window on the session
|
|
265
290
|
${NAME} view --watch-only a window that watches rather than drives
|
|
266
291
|
${NAME} view --close close the window, session keeps running
|
|
@@ -307,7 +332,7 @@ Options for open
|
|
|
307
332
|
-H, --height N desktop height (default: 800)
|
|
308
333
|
--fullscreen fill the display; RDP uses its current resolution
|
|
309
334
|
--side-panel open the SSH terminal docked on the right
|
|
310
|
-
--audio
|
|
335
|
+
--no-audio disable remote sound (enabled by default for windowed RDP)
|
|
311
336
|
--no-clipboard do not share this machine's clipboard (shared by default)
|
|
312
337
|
--insecure-host-key
|
|
313
338
|
accept an SSH host key that does not match the one on
|
|
@@ -342,6 +367,29 @@ Options for the rest
|
|
|
342
367
|
--region X,Y,W,H capture a desktop region; --json reports its origin
|
|
343
368
|
--json machine-readable result on stdout
|
|
344
369
|
|
|
370
|
+
Performing an input rather than merely sending it
|
|
371
|
+
--hold MS for click and key: how long the button or chord stays
|
|
372
|
+
down. A press and a release with nothing between is
|
|
373
|
+
not a held key, and a control that measures how long
|
|
374
|
+
it was held cannot be worked without this
|
|
375
|
+
--count N for click: repeat it, --pace between repeats
|
|
376
|
+
--with CHORD for click: a modifier held for that one action,
|
|
377
|
+
e.g. --with ShiftLeft
|
|
378
|
+
--animate for click, key and batch: draw the pointer travelling
|
|
379
|
+
to each target in the window, and hold the input until
|
|
380
|
+
it lands. Slower on purpose, and does nothing at all
|
|
381
|
+
with no window open
|
|
382
|
+
--pace MS a floor between one action and the next. ChatGPT's
|
|
383
|
+
computer use waits 100ms after every action; this
|
|
384
|
+
waits none unless asked, because a batch exists to
|
|
385
|
+
spend one turn on many inputs
|
|
386
|
+
--exclusive take the pointer from the window for the length of
|
|
387
|
+
this command and give it back after. A desktop has one
|
|
388
|
+
pointer: a hand that moves it mid-batch moves what the
|
|
389
|
+
agent is aiming at. \`${NAME} input off\` holds it
|
|
390
|
+
open instead, and either way the window's control
|
|
391
|
+
button takes it back
|
|
392
|
+
|
|
345
393
|
Signing in
|
|
346
394
|
A terminal signs in with a key where it can: with no --identity, ssh-agent is
|
|
347
395
|
asked for what it is holding and ~/.ssh/id_ed25519, id_ecdsa and id_rsa are
|
|
@@ -473,7 +521,7 @@ async function startSession(args, name, host, port, secret, account = "") {
|
|
|
473
521
|
const explicitHeight = flag(args, "H", "height");
|
|
474
522
|
let fullscreenSize = null;
|
|
475
523
|
if (mode === "rdp" && has(args, "fullscreen") && (!explicitWidth || !explicitHeight)) {
|
|
476
|
-
const { detectCurrentDisplaySize } = await import("./cli-window-
|
|
524
|
+
const { detectCurrentDisplaySize } = await import("./cli-window-G5ESQTF5.mjs");
|
|
477
525
|
const display = await detectCurrentDisplaySize();
|
|
478
526
|
if (display) fullscreenSize = fullscreenRdpSize(display, has(args, "side-panel"));
|
|
479
527
|
}
|
|
@@ -523,13 +571,14 @@ async function startSession(args, name, host, port, secret, account = "") {
|
|
|
523
571
|
if (has(args, "tab")) daemonArgs.push("--tab");
|
|
524
572
|
if (has(args, "fullscreen")) daemonArgs.push("--fullscreen");
|
|
525
573
|
if (has(args, "side-panel") && mode !== "ssh") daemonArgs.push("--side-panel");
|
|
526
|
-
if (has(args, "audio"
|
|
574
|
+
if (has(args, "no-audio")) daemonArgs.push("--no-audio");
|
|
575
|
+
if (audioEnabled(args, mode) || has(args, "audio", "sound")) {
|
|
527
576
|
if (mode !== "rdp") {
|
|
528
577
|
process.stderr.write(`--audio is RDP only; a ${mode.toUpperCase()} session has no audio channel.
|
|
529
578
|
`);
|
|
530
579
|
} else if (has(args, "no-view", "headless")) {
|
|
531
580
|
process.stderr.write("--audio needs a window to play into; ignoring it for a headless session.\n");
|
|
532
|
-
} else {
|
|
581
|
+
} else if (audioEnabled(args, mode)) {
|
|
533
582
|
daemonArgs.push("--audio");
|
|
534
583
|
}
|
|
535
584
|
}
|
|
@@ -597,7 +646,7 @@ async function main() {
|
|
|
597
646
|
}
|
|
598
647
|
const json = has(args, "json");
|
|
599
648
|
if (args.command === "__display-size") {
|
|
600
|
-
const { readCurrentDisplaySize } = await import("./cli-window-
|
|
649
|
+
const { readCurrentDisplaySize } = await import("./cli-window-G5ESQTF5.mjs");
|
|
601
650
|
const size = await readCurrentDisplaySize();
|
|
602
651
|
if (size) process.stdout.write(`${JSON.stringify(size)}
|
|
603
652
|
`);
|
|
@@ -606,7 +655,7 @@ async function main() {
|
|
|
606
655
|
if (args.command === "__session") {
|
|
607
656
|
const mode = modeFor(args, args.rest[0]);
|
|
608
657
|
const { host, port } = splitTarget(args.rest[0] ?? "", defaultPort(mode));
|
|
609
|
-
const { runDaemon } = await import("./cli-daemon-
|
|
658
|
+
const { runDaemon } = await import("./cli-daemon-SD2AS34J.mjs");
|
|
610
659
|
await runDaemon({
|
|
611
660
|
name: sessionName(host, port, flag(args, "name", "session")),
|
|
612
661
|
host,
|
|
@@ -631,7 +680,7 @@ async function main() {
|
|
|
631
680
|
watchOnly: has(args, "watch-only", "observe"),
|
|
632
681
|
// RDP is the only protocol here with an audio channel, and a session with
|
|
633
682
|
// no window has nowhere to play what the channel would carry.
|
|
634
|
-
audio:
|
|
683
|
+
audio: audioEnabled(args, mode),
|
|
635
684
|
idleMs: Math.max(0, Number(flag(args, "idle") ?? 0)) * 6e4,
|
|
636
685
|
keys: flag(args, "keys") === "literal" ? "literal" : "adapt",
|
|
637
686
|
clipboard: !has(args, "no-clipboard"),
|
|
@@ -640,7 +689,7 @@ async function main() {
|
|
|
640
689
|
return;
|
|
641
690
|
}
|
|
642
691
|
if (args.command === "__window") {
|
|
643
|
-
const { runWindow } = await import("./cli-window-
|
|
692
|
+
const { runWindow } = await import("./cli-window-G5ESQTF5.mjs");
|
|
644
693
|
await runWindow(process.argv.slice(3));
|
|
645
694
|
return;
|
|
646
695
|
}
|
|
@@ -984,6 +1033,12 @@ Open the host again without --ssh for a desktop.`,
|
|
|
984
1033
|
region: flag(args, "region"),
|
|
985
1034
|
maxEdge: Number(flag(args, "max-edge") ?? 0)
|
|
986
1035
|
};
|
|
1036
|
+
const pacing = () => ({
|
|
1037
|
+
animate: has(args, "animate"),
|
|
1038
|
+
postActionSleepMs: Number(flag(args, "pace") ?? 0),
|
|
1039
|
+
// Hold the pointer for the length of this command and give it back after.
|
|
1040
|
+
exclusive: has(args, "exclusive")
|
|
1041
|
+
});
|
|
987
1042
|
switch (args.command) {
|
|
988
1043
|
case "status": {
|
|
989
1044
|
Object.assign(summary, await tell(session, "status", {}, 1e4));
|
|
@@ -1012,8 +1067,11 @@ Open the host again without --ssh for a desktop.`,
|
|
|
1012
1067
|
if (!spec) throw new CliError(`click needs a position, e.g. \`${NAME} click 640,400\``, 12);
|
|
1013
1068
|
const [x, y] = spec.split(",").map(Number);
|
|
1014
1069
|
const button = flag(args, "button") ?? "left";
|
|
1015
|
-
|
|
1016
|
-
|
|
1070
|
+
Object.assign(summary, await tell(session, "batch", {
|
|
1071
|
+
actions: [clickAction(args)],
|
|
1072
|
+
...pacing(),
|
|
1073
|
+
...observation(shotFile(args))
|
|
1074
|
+
}));
|
|
1017
1075
|
Object.assign(summary, { clicked: { x, y, button } });
|
|
1018
1076
|
break;
|
|
1019
1077
|
}
|
|
@@ -1023,6 +1081,7 @@ Open the host again without --ssh for a desktop.`,
|
|
|
1023
1081
|
if (!text) throw new CliError("type needs some text", 12);
|
|
1024
1082
|
Object.assign(summary, await tell(session, "batch", {
|
|
1025
1083
|
actions: [{ type: "type", text }],
|
|
1084
|
+
...pacing(),
|
|
1026
1085
|
...observation(shotFile(args))
|
|
1027
1086
|
}));
|
|
1028
1087
|
Object.assign(summary, { typed: text.length });
|
|
@@ -1030,7 +1089,12 @@ Open the host again without --ssh for a desktop.`,
|
|
|
1030
1089
|
}
|
|
1031
1090
|
case "key": {
|
|
1032
1091
|
if (!args.rest.length) throw new CliError("key needs at least one code, e.g. MetaLeft", 12);
|
|
1033
|
-
|
|
1092
|
+
const duration = Number(flag(args, "hold") ?? 0);
|
|
1093
|
+
Object.assign(summary, await tell(session, "batch", {
|
|
1094
|
+
actions: args.rest.map((chord) => ({ type: "key", chord, duration })),
|
|
1095
|
+
...pacing(),
|
|
1096
|
+
...observation(shotFile(args))
|
|
1097
|
+
}));
|
|
1034
1098
|
Object.assign(summary, { keys: args.rest });
|
|
1035
1099
|
break;
|
|
1036
1100
|
}
|
|
@@ -1051,7 +1115,27 @@ Open the host again without --ssh for a desktop.`,
|
|
|
1051
1115
|
} catch (error) {
|
|
1052
1116
|
throw new CliError(`${file === "-" ? "stdin" : file} is not valid JSON: ${error.message}`, 12);
|
|
1053
1117
|
}
|
|
1054
|
-
Object.assign(summary, await tell(session, "batch", {
|
|
1118
|
+
Object.assign(summary, await tell(session, "batch", {
|
|
1119
|
+
actions,
|
|
1120
|
+
...pacing(),
|
|
1121
|
+
...observation(shotFile(args))
|
|
1122
|
+
}));
|
|
1123
|
+
break;
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Who may drive: the people watching, or nobody but the agent.
|
|
1127
|
+
*
|
|
1128
|
+
* A remote desktop has one pointer, so an agent working while a hand rests
|
|
1129
|
+
* on the trackpad is two things pulling at the same cursor. `off` takes it
|
|
1130
|
+
* away from the windows; the button in the window still takes it back, and
|
|
1131
|
+
* the next batch is told that it happened.
|
|
1132
|
+
*/
|
|
1133
|
+
case "input": {
|
|
1134
|
+
const word = args.rest[0];
|
|
1135
|
+
if (word !== void 0 && word !== "on" && word !== "off") {
|
|
1136
|
+
throw new CliError(`input takes on or off, e.g. \`${NAME} input off\``, 12);
|
|
1137
|
+
}
|
|
1138
|
+
Object.assign(summary, await tell(session, "input", word === void 0 ? {} : { on: word === "on" }));
|
|
1055
1139
|
break;
|
|
1056
1140
|
}
|
|
1057
1141
|
case "exec": {
|
|
@@ -1370,6 +1454,14 @@ The password is saved in ${summary.saved}.` : ""
|
|
|
1370
1454
|
if (command === "type") return `Typed ${summary.typed} characters.${looked}`;
|
|
1371
1455
|
if (command === "batch") return `Ran ${summary.actions} actions (${summary.clicks} clicks) in ${summary.dispatchMs} ms of dispatch.${looked}`;
|
|
1372
1456
|
if (command === "key") return `Pressed ${summary.keys.join(", ")}.${looked}`;
|
|
1457
|
+
if (command === "input") {
|
|
1458
|
+
const count = Number(summary.viewers ?? 0);
|
|
1459
|
+
const windows = `${count} window${count === 1 ? "" : "s"}`;
|
|
1460
|
+
if (summary.humanInput) {
|
|
1461
|
+
return count ? `The keyboard and pointer are shared: ${windows} may drive.` : "The keyboard and pointer are shared with whoever is watching. No window is open.";
|
|
1462
|
+
}
|
|
1463
|
+
return count ? `The agent has the keyboard and pointer; ${windows} can only watch. The control button in the window takes them back.` : "The agent has the keyboard and pointer. No window is open, and one that opens will start watching.";
|
|
1464
|
+
}
|
|
1373
1465
|
if (command === "do") return `Ran ${summary.steps.length} steps: ${summary.steps.join(" \u2192 ")}${looked}`;
|
|
1374
1466
|
return "Done.";
|
|
1375
1467
|
}
|
package/dist/computer.d.ts
CHANGED
|
@@ -2,34 +2,102 @@ export type ComputerPoint = {
|
|
|
2
2
|
x: number;
|
|
3
3
|
y: number;
|
|
4
4
|
};
|
|
5
|
+
/** The wire's 0/1/2, or the name for it. */
|
|
6
|
+
export type ComputerButton = 0 | 1 | 2 | 'left' | 'middle' | 'right' | 'l' | 'm' | 'r';
|
|
7
|
+
/** A modifier chord held for the length of one action, e.g. `ShiftLeft`. */
|
|
8
|
+
type Modified = {
|
|
9
|
+
key?: string;
|
|
10
|
+
};
|
|
5
11
|
export type ComputerAction = ({
|
|
6
12
|
type: 'click';
|
|
7
|
-
button?:
|
|
13
|
+
button?: ComputerButton;
|
|
14
|
+
/** ChatGPT's spelling of `button`. */
|
|
15
|
+
mouse_button?: ComputerButton;
|
|
8
16
|
count?: number;
|
|
17
|
+
/** ChatGPT's spelling of `count`. */
|
|
18
|
+
click_count?: number;
|
|
9
19
|
intervalMs?: number;
|
|
10
|
-
|
|
20
|
+
/** Milliseconds to hold the button down for each click. */
|
|
21
|
+
duration?: number;
|
|
22
|
+
} & ComputerPoint & Modified) | ({
|
|
11
23
|
type: 'move';
|
|
12
|
-
} & ComputerPoint) | ({
|
|
24
|
+
} & ComputerPoint & Modified) | ({
|
|
13
25
|
type: 'scroll';
|
|
26
|
+
/** Wheel notches, positive right and down. */
|
|
14
27
|
dx?: number;
|
|
15
28
|
dy?: number;
|
|
16
|
-
|
|
29
|
+
/** ChatGPT's spelling: a direction and a distance in pixels. */
|
|
30
|
+
direction?: 'up' | 'down' | 'left' | 'right' | 'u' | 'd' | 'l' | 'r';
|
|
31
|
+
pixels?: number;
|
|
32
|
+
} & ComputerPoint & Modified) | {
|
|
17
33
|
type: 'type';
|
|
18
34
|
text: string;
|
|
19
35
|
chunkSize?: number;
|
|
20
36
|
intervalMs?: number;
|
|
21
37
|
} | {
|
|
22
38
|
type: 'key';
|
|
23
|
-
chord
|
|
39
|
+
chord?: string;
|
|
40
|
+
/** ChatGPT's spelling of `chord`. */
|
|
41
|
+
key?: string;
|
|
42
|
+
/** Milliseconds to hold the chord before releasing it. */
|
|
43
|
+
duration?: number;
|
|
24
44
|
} | {
|
|
25
45
|
type: 'wait';
|
|
26
46
|
ms: number;
|
|
27
|
-
} | {
|
|
47
|
+
} | ({
|
|
28
48
|
type: 'drag';
|
|
29
49
|
path: ComputerPoint[];
|
|
30
|
-
button?:
|
|
50
|
+
button?: ComputerButton;
|
|
31
51
|
intervalMs?: number;
|
|
52
|
+
} & Modified)
|
|
53
|
+
/**
|
|
54
|
+
* A drag held open across calls, which is ChatGPT's `drag_handle()`.
|
|
55
|
+
*
|
|
56
|
+
* `drag` is one gesture with a known shape. These three are for the drag
|
|
57
|
+
* whose ending depends on what the drag itself reveals: press, take a
|
|
58
|
+
* screenshot, decide, and only then release. The button stays down on the
|
|
59
|
+
* host between calls, so something has to end it -- the session lets go if it
|
|
60
|
+
* closes first, but nothing else will.
|
|
61
|
+
*/
|
|
62
|
+
| ({
|
|
63
|
+
type: 'drag-start';
|
|
64
|
+
button?: ComputerButton;
|
|
65
|
+
} & ComputerPoint) | ({
|
|
66
|
+
type: 'drag-move';
|
|
67
|
+
} & ComputerPoint) | {
|
|
68
|
+
type: 'drag-end';
|
|
32
69
|
};
|
|
70
|
+
export interface BatchOptions {
|
|
71
|
+
/**
|
|
72
|
+
* Wait for the drawn cursor to reach each target before the input goes out.
|
|
73
|
+
*
|
|
74
|
+
* Only means anything while a viewer is attached; there is no cursor to watch
|
|
75
|
+
* otherwise and nothing to wait for. It makes a batch slower on purpose, in
|
|
76
|
+
* exchange for a click that a person watching sees land.
|
|
77
|
+
*/
|
|
78
|
+
animate?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* A floor between consecutive actions, ChatGPT's `post_action_sleep_ms`.
|
|
81
|
+
*
|
|
82
|
+
* Theirs is 100ms and always on. This defaults to none: a batch exists to
|
|
83
|
+
* spend one model turn on many inputs, and `dispatchMs` is a published
|
|
84
|
+
* measurement. Ask for it when the far side needs the breathing room.
|
|
85
|
+
*/
|
|
86
|
+
postActionSleepMs?: number;
|
|
87
|
+
/**
|
|
88
|
+
* Take the keyboard and pointer from the viewer for the length of this batch.
|
|
89
|
+
*
|
|
90
|
+
* A remote desktop has one pointer. An agent does not get its own -- neither
|
|
91
|
+
* protocol has a second one to give it -- so a hand resting on a trackpad is
|
|
92
|
+
* moving the same cursor the agent is aiming, and a click that lands where
|
|
93
|
+
* the hand left it is worse than one that does not land at all. This holds
|
|
94
|
+
* the input for the batch and gives it back afterwards.
|
|
95
|
+
*
|
|
96
|
+
* Not a lock. The control button in the window takes it back at once, and
|
|
97
|
+
* `interference` in the next result says that it happened.
|
|
98
|
+
*/
|
|
99
|
+
exclusive?: boolean;
|
|
100
|
+
}
|
|
33
101
|
export interface ObservationOptions {
|
|
34
102
|
region?: {
|
|
35
103
|
x: number;
|
|
@@ -62,6 +130,21 @@ export interface ComputerBatchResult {
|
|
|
62
130
|
clicks: number;
|
|
63
131
|
/** Input dispatch and requested pacing; not proof of application completion. */
|
|
64
132
|
dispatchMs: number;
|
|
133
|
+
/** A `drag-start` is still holding a button down on the host. */
|
|
134
|
+
dragging: boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Input events that arrived from a viewer while this batch ran.
|
|
137
|
+
*
|
|
138
|
+
* Zero is the ordinary answer. Anything else is somebody using the machine at
|
|
139
|
+
* the same time, and their pointer is the same pointer: treat the actions in
|
|
140
|
+
* this batch as having possibly landed somewhere else, look before carrying
|
|
141
|
+
* on, and consider `exclusive` or `takeInput()` if it keeps happening.
|
|
142
|
+
* Refused input counts, so this stays a useful signal after the input has
|
|
143
|
+
* been taken away.
|
|
144
|
+
*/
|
|
145
|
+
interference: number;
|
|
146
|
+
/** Whether the people watching may drive right now. */
|
|
147
|
+
humanInput: boolean;
|
|
65
148
|
}
|
|
66
149
|
/** Map a point in a returned image back to full-desktop coordinates. */
|
|
67
150
|
export declare function desktopPoint(image: ComputerScreenshot, point: ComputerPoint): ComputerPoint;
|
|
@@ -71,7 +154,23 @@ export declare class RemoteComputer {
|
|
|
71
154
|
private call;
|
|
72
155
|
screenshot(options?: ObservationOptions): Promise<ComputerScreenshot>;
|
|
73
156
|
/** One IPC call, no reconnect or model invocation between actions. */
|
|
74
|
-
act(actions: ComputerAction[], options?: ObservationOptions): Promise<ComputerBatchResult & ComputerScreenshot>;
|
|
157
|
+
act(actions: ComputerAction[], options?: ObservationOptions & BatchOptions): Promise<ComputerBatchResult & ComputerScreenshot>;
|
|
158
|
+
/**
|
|
159
|
+
* Take the keyboard and pointer from every window, until given back.
|
|
160
|
+
*
|
|
161
|
+
* For an agent that has seen `interference` and decided to work alone.
|
|
162
|
+
* `releaseInput()` undoes it, and so does the control button in the window --
|
|
163
|
+
* which is the point: the person at the machine is never locked out of it.
|
|
164
|
+
*/
|
|
165
|
+
takeInput(): Promise<{
|
|
166
|
+
humanInput: boolean;
|
|
167
|
+
viewers: number;
|
|
168
|
+
}>;
|
|
169
|
+
/** Give the keyboard and pointer back to whoever is watching. */
|
|
170
|
+
releaseInput(): Promise<{
|
|
171
|
+
humanInput: boolean;
|
|
172
|
+
viewers: number;
|
|
173
|
+
}>;
|
|
75
174
|
/** Explicitly skip observation for a known sequence; inspect when needed. */
|
|
76
|
-
dispatch(actions: ComputerAction[]): Promise<ComputerBatchResult>;
|
|
175
|
+
dispatch(actions: ComputerAction[], options?: BatchOptions): Promise<ComputerBatchResult>;
|
|
77
176
|
}
|
package/dist/computer.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import g from"node:net";import{StringDecoder as f}from"node:string_decoder";function w(
|
|
2
|
-
`);if(s===-1)return;let o=n.slice(0,s);if(n=n.slice(s+1),!!o.trim())try{e(JSON.parse(o))}catch{}}})}var x=(
|
|
3
|
-
`)};function p(
|
|
1
|
+
import g from"node:net";import{StringDecoder as f}from"node:string_decoder";function w(r,e){let t=new f("utf8"),n="";r.on("data",u=>{for(n+=t.write(u);;){let s=n.indexOf(`
|
|
2
|
+
`);if(s===-1)return;let o=n.slice(0,s);if(n=n.slice(s+1),!!o.trim())try{e(JSON.parse(o))}catch{}}})}var x=(r,e)=>{r.write(`${JSON.stringify(e)}
|
|
3
|
+
`)};function p(r,e,t={},n=12e4){return new Promise((u,s)=>{let o=g.connect(r),a=!1,m=(i,b)=>{a||(a=!0,clearTimeout(l),o.destroy(),i?s(i):u(b))},l=setTimeout(()=>m(new Error(`The session did not answer "${e}" in time.`)),n);o.on("connect",()=>x(o,{id:1,op:e,args:t})),o.on("error",i=>{m(Object.assign(i,{notRunning:i.code==="ENOENT"||i.code==="ECONNREFUSED"}))}),w(o,i=>m(null,i))})}import{homedir as y}from"node:os";import{join as c}from"node:path";var k=c(y(),".ai-remote"),C=c(k,"run");var h=r=>c(C,`${r}.sock`);function N(r,e){if(!Number.isFinite(e.x)||!Number.isFinite(e.y)||e.x<0||e.y<0||e.x>=r.width||e.y>=r.height)throw new Error("Point must be inside the screenshot.");let t=r.viewport??{x:0,y:0,width:r.desktopWidth,height:r.desktopHeight};return{x:Math.min(t.x+t.width-1,Math.floor(t.x+e.x*t.width/r.width)),y:Math.min(t.y+t.height-1,Math.floor(t.y+e.y*t.height/r.height))}}var d=class{session;constructor(e){if(!/^[\w.-]+$/.test(e)||e==="."||e==="..")throw new Error("Use a session name from ai-remote status --json.");this.session=e}async call(e,t){let n=await p(h(this.session),e,t);if(!n.ok)throw new Error(n.error??"Remote operation failed.");return n.result}screenshot(e={}){return this.call("shot",{...e})}act(e,t={}){return this.call("batch",{...t,actions:e})}takeInput(){return this.call("input",{on:!1})}releaseInput(){return this.call("input",{on:!0})}dispatch(e,t={}){return this.call("batch",{...t,actions:e,observe:!1})}};export{d as RemoteComputer,N as desktopPoint};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-remote",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "RDP, VNC and SSH protocol engines that run in Node, a Worker or a browser -- plus `npx ai-remote`, a CLI that drives a machine and shows you what it is doing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|