ai-remote 0.3.0 → 0.3.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/README.md +69 -20
- package/dist/cli.mjs +12 -4
- package/package.json +1 -1
- package/src/cli/cli.ts +4 -3
- package/src/cli/daemon.ts +6 -2
- package/src/cli/shell.ts +13 -0
- package/src/cli/viewer.ts +4 -1
package/README.md
CHANGED
|
@@ -1,32 +1,62 @@
|
|
|
1
1
|
# ai-remote
|
|
2
2
|
|
|
3
|
-
> Pure TypeScript protocol engines (RDP, VNC, SSH) and remote
|
|
3
|
+
> Pure TypeScript protocol engines (RDP, VNC, SSH) for Node.js, Cloudflare Workers, and web browsers — plus `npx ai-remote`, a CLI that drives a remote desktop and shows you what it is doing.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/ai-remote)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## 🚀 Quick Start (CLI)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Run immediately with `npx` (zero install):
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
```bash
|
|
13
|
+
# Connect and open a live supervision window:
|
|
14
|
+
AI_REMOTE_PASSWORD=secret npx ai-remote open 192.168.1.50 -u Administrator
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
# Take a screenshot:
|
|
17
|
+
AI_REMOTE_PASSWORD=secret npx ai-remote shot 192.168.1.50 -u Administrator -o screen.png
|
|
18
|
+
|
|
19
|
+
# Click or type on the remote desktop:
|
|
20
|
+
npx ai-remote click 192.168.1.50 500,300
|
|
21
|
+
npx ai-remote type 192.168.1.50 "notepad"
|
|
22
|
+
npx ai-remote key 192.168.1.50 Enter
|
|
23
|
+
|
|
24
|
+
# Run commands in the remote terminal over SSH:
|
|
25
|
+
npx ai-remote exec 192.168.1.50 "dir"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### CLI Commands
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
ai-remote open <host[:port]> connect, show a window, and stay open
|
|
32
|
+
ai-remote shot <host[:port]> screenshot the desktop
|
|
33
|
+
ai-remote click <host[:port]> X,Y click, --button right|middle, --double
|
|
34
|
+
ai-remote type <host[:port]> "text" type into whatever has focus
|
|
35
|
+
ai-remote key <host[:port]> <Code> press keys, e.g. MetaLeft, ControlLeft+KeyA
|
|
36
|
+
ai-remote exec <host[:port]> "cmd" run a command in the terminal (SSH)
|
|
37
|
+
ai-remote shell <host[:port]> an interactive terminal
|
|
38
|
+
ai-remote do <host[:port]> "steps" several actions in one go
|
|
39
|
+
ai-remote view <host[:port]> open the window on a running session
|
|
40
|
+
ai-remote view <host[:port]> --close close the window, session keeps running
|
|
41
|
+
ai-remote status | list | close [<host>]
|
|
42
|
+
ai-remote probe <host[:port]> is anything listening?
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Common Options
|
|
46
|
+
|
|
47
|
+
- `-u, --user NAME`: Account name on the host.
|
|
48
|
+
- `-d, --domain NAME`: Windows domain or computer name.
|
|
49
|
+
- `--ssh-user NAME`: Account for the SSH terminal (defaults to `--user`).
|
|
50
|
+
- `--ssh-port N`: SSH port (default: 22).
|
|
51
|
+
- `-s, --security MODE`: `auto` | `nla` | `tls` | `rdp` (default: `auto`).
|
|
52
|
+
- `-W, --width N` / `-H, --height N`: Desktop resolution (default: 1280x800).
|
|
53
|
+
- `-o, --out FILE`: Screenshot output file path (default: `screen.png`).
|
|
54
|
+
- `--headless` / `--no-view`: Run headless without opening the viewer window.
|
|
55
|
+
- `--json`: Output machine-readable JSON results on stdout.
|
|
56
|
+
|
|
57
|
+
---
|
|
28
58
|
|
|
29
|
-
## Installation
|
|
59
|
+
## 📦 Library Installation
|
|
30
60
|
|
|
31
61
|
```bash
|
|
32
62
|
pnpm add ai-remote
|
|
@@ -34,7 +64,7 @@ pnpm add ai-remote
|
|
|
34
64
|
npm install ai-remote
|
|
35
65
|
```
|
|
36
66
|
|
|
37
|
-
|
|
67
|
+
### Subpath Exports
|
|
38
68
|
|
|
39
69
|
| Export | Description |
|
|
40
70
|
|---|---|
|
|
@@ -45,6 +75,25 @@ npm install ai-remote
|
|
|
45
75
|
| `ai-remote/protocols/vnc` | VNC session and input management |
|
|
46
76
|
| `ai-remote/shared/*` | Direct access to shared models and utilities |
|
|
47
77
|
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## ✨ Features
|
|
81
|
+
|
|
82
|
+
- **RDP Protocol Engine:**
|
|
83
|
+
- Full X.224, MCS, and GCC capability negotiation.
|
|
84
|
+
- CredSSP v6 with NTLMv2 and TLS 1.2 client implementation over WebCrypto.
|
|
85
|
+
- Fast-path 16-bit interleaved RLE bitmap decoding.
|
|
86
|
+
- Shared clipboard support (`cliprdr` virtual channel).
|
|
87
|
+
- RemoteApp (RAIL) window orders and execution.
|
|
88
|
+
- **SSH Engine:**
|
|
89
|
+
- RFC 4253 binary packet protocol with `curve25519-sha256` / `ecdh-sha2-nistp256`.
|
|
90
|
+
- AEAD ciphers (`aes256-gcm@openssh.com`, `aes128-gcm@openssh.com`).
|
|
91
|
+
- Terminal interactive and automated agent command execution.
|
|
92
|
+
- **VNC Engine:**
|
|
93
|
+
- RFB protocol integration with Apple Diffie-Hellman handshake support.
|
|
94
|
+
- **AI Agent Automation:**
|
|
95
|
+
- Fast headless actions, structured screen capture, and auto-attached SSH shells.
|
|
96
|
+
|
|
48
97
|
## License
|
|
49
98
|
|
|
50
99
|
MIT © Leon Guo
|
package/dist/cli.mjs
CHANGED
|
@@ -1631,6 +1631,10 @@ var TcpTransport = class extends EventTarget {
|
|
|
1631
1631
|
};
|
|
1632
1632
|
|
|
1633
1633
|
// src/cli/shell.ts
|
|
1634
|
+
function bareUsername(username) {
|
|
1635
|
+
const withoutDomain = username.includes("\\") ? username.split("\\").pop() : username;
|
|
1636
|
+
return withoutDomain.split("@")[0].trim();
|
|
1637
|
+
}
|
|
1634
1638
|
var Shell = class {
|
|
1635
1639
|
options;
|
|
1636
1640
|
session;
|
|
@@ -7194,7 +7198,10 @@ async function startViewer(session, port, { launch = true, openShell } = {}) {
|
|
|
7194
7198
|
const detachShell = /* @__PURE__ */ new Map();
|
|
7195
7199
|
async function attachShell(peer) {
|
|
7196
7200
|
if (!openShell) {
|
|
7197
|
-
peer.send(JSON.stringify({
|
|
7201
|
+
peer.send(JSON.stringify({
|
|
7202
|
+
type: "shell-error",
|
|
7203
|
+
message: "This session has no terminal available."
|
|
7204
|
+
}));
|
|
7198
7205
|
return;
|
|
7199
7206
|
}
|
|
7200
7207
|
if (detachShell.has(peer)) return;
|
|
@@ -7441,7 +7448,7 @@ async function runDaemon(options) {
|
|
|
7441
7448
|
const started = new Shell({
|
|
7442
7449
|
host: options.host,
|
|
7443
7450
|
port: options.sshPort,
|
|
7444
|
-
username: options.sshUsername || options.username,
|
|
7451
|
+
username: options.sshUsername || bareUsername(options.username),
|
|
7445
7452
|
password: options.password
|
|
7446
7453
|
});
|
|
7447
7454
|
await started.connect();
|
|
@@ -7681,7 +7688,8 @@ Steps for "do", separated by semicolons:
|
|
|
7681
7688
|
Options
|
|
7682
7689
|
-u, --user NAME account on the host
|
|
7683
7690
|
-d, --domain NAME Windows domain or computer name
|
|
7684
|
-
--ssh-user NAME
|
|
7691
|
+
--ssh-user NAME only if the terminal signs in as someone else;
|
|
7692
|
+
by default it reuses --user and the same password
|
|
7685
7693
|
--ssh-port N SSH port (default: 22)
|
|
7686
7694
|
-s, --security MODE auto | nla | tls | rdp (default: auto)
|
|
7687
7695
|
-W, --width N desktop width (default: 1280)
|
|
@@ -7997,7 +8005,7 @@ async function interactiveShell(args, host) {
|
|
|
7997
8005
|
const shell = new Shell({
|
|
7998
8006
|
host,
|
|
7999
8007
|
port: Number(flag(args, "ssh-port") ?? 22),
|
|
8000
|
-
username: flag(args, "ssh-user") ?? flag(args, "u", "user") ?? "",
|
|
8008
|
+
username: flag(args, "ssh-user") ?? bareUsername(flag(args, "u", "user") ?? ""),
|
|
8001
8009
|
password: password(),
|
|
8002
8010
|
columns: process.stdout.columns ?? 120,
|
|
8003
8011
|
rows: process.stdout.rows ?? 30
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-remote",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.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": {
|
package/src/cli/cli.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { basename, resolve as resolvePath } from 'node:path';
|
|
11
11
|
import { spawn } from 'node:child_process';
|
|
12
12
|
import { readFileSync, readdirSync, existsSync, openSync, unlinkSync } from 'node:fs';
|
|
13
|
-
import { Shell } from './shell';
|
|
13
|
+
import { bareUsername, Shell } from './shell';
|
|
14
14
|
import { runDaemon } from './daemon';
|
|
15
15
|
import { request } from './ipc';
|
|
16
16
|
import { ensureRunDir, logPath, metaPath, RUN_DIR, sessionName, socketPath } from './paths';
|
|
@@ -59,7 +59,8 @@ Steps for "do", separated by semicolons:
|
|
|
59
59
|
Options
|
|
60
60
|
-u, --user NAME account on the host
|
|
61
61
|
-d, --domain NAME Windows domain or computer name
|
|
62
|
-
--ssh-user NAME
|
|
62
|
+
--ssh-user NAME only if the terminal signs in as someone else;
|
|
63
|
+
by default it reuses --user and the same password
|
|
63
64
|
--ssh-port N SSH port (default: 22)
|
|
64
65
|
-s, --security MODE auto | nla | tls | rdp (default: auto)
|
|
65
66
|
-W, --width N desktop width (default: 1280)
|
|
@@ -376,7 +377,7 @@ async function interactiveShell(args: Args, host: string): Promise<void> {
|
|
|
376
377
|
const shell = new Shell({
|
|
377
378
|
host,
|
|
378
379
|
port: Number(flag(args, 'ssh-port') ?? 22),
|
|
379
|
-
username: flag(args, 'ssh-user') ?? flag(args, 'u', 'user') ?? '',
|
|
380
|
+
username: flag(args, 'ssh-user') ?? bareUsername(flag(args, 'u', 'user') ?? ''),
|
|
380
381
|
password: password(),
|
|
381
382
|
columns: process.stdout.columns ?? 120,
|
|
382
383
|
rows: process.stdout.rows ?? 30,
|
package/src/cli/daemon.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import net from 'node:net';
|
|
14
14
|
import { unlinkSync, writeFileSync, existsSync } from 'node:fs';
|
|
15
15
|
import { RdpSession, type SessionOptions } from './session';
|
|
16
|
-
import { Shell } from './shell';
|
|
16
|
+
import { bareUsername, Shell } from './shell';
|
|
17
17
|
import { downscale, encodePng } from './png';
|
|
18
18
|
import { startViewer, type Viewer } from './viewer';
|
|
19
19
|
import { readMessages, writeMessage, type Request } from './ipc';
|
|
@@ -43,6 +43,10 @@ export async function runDaemon(options: DaemonOptions): Promise<void> {
|
|
|
43
43
|
* The terminal is opened on first use rather than at connect time. Most
|
|
44
44
|
* sessions never ask for one, and a second sign-in that nobody wanted is a
|
|
45
45
|
* second chance to lock an account out.
|
|
46
|
+
*
|
|
47
|
+
* It signs in as the desktop's own account and password by default. Needing a
|
|
48
|
+
* separate flag to get a terminal on the machine you are already looking at
|
|
49
|
+
* is a question with an obvious answer, so it is not asked.
|
|
46
50
|
*/
|
|
47
51
|
function openShell(): Promise<Shell> {
|
|
48
52
|
if (shell) return Promise.resolve(shell);
|
|
@@ -52,7 +56,7 @@ export async function runDaemon(options: DaemonOptions): Promise<void> {
|
|
|
52
56
|
const started = new Shell({
|
|
53
57
|
host: options.host,
|
|
54
58
|
port: options.sshPort,
|
|
55
|
-
username: options.sshUsername || options.username,
|
|
59
|
+
username: options.sshUsername || bareUsername(options.username),
|
|
56
60
|
password: options.password,
|
|
57
61
|
});
|
|
58
62
|
await started.connect();
|
package/src/cli/shell.ts
CHANGED
|
@@ -9,6 +9,19 @@
|
|
|
9
9
|
import { SshSession } from '../protocols/ssh/session';
|
|
10
10
|
import { TcpTransport } from './transport';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* The account name on its own.
|
|
14
|
+
*
|
|
15
|
+
* An RDP username may carry the machine or domain with it -- `DESKTOP-01\\owner`
|
|
16
|
+
* or `owner@corp` -- and SSH wants neither. The terminal defaults to the same
|
|
17
|
+
* account as the desktop, so this is what makes that default work rather than
|
|
18
|
+
* fail on a name that was perfectly good for RDP.
|
|
19
|
+
*/
|
|
20
|
+
export function bareUsername(username: string): string {
|
|
21
|
+
const withoutDomain = username.includes('\\') ? username.split('\\').pop()! : username;
|
|
22
|
+
return withoutDomain.split('@')[0].trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
12
25
|
export interface ShellOptions {
|
|
13
26
|
host: string;
|
|
14
27
|
port: number;
|
package/src/cli/viewer.ts
CHANGED
|
@@ -76,7 +76,10 @@ export async function startViewer(
|
|
|
76
76
|
*/
|
|
77
77
|
async function attachShell(peer: WebSocketPeer): Promise<void> {
|
|
78
78
|
if (!openShell) {
|
|
79
|
-
peer.send(JSON.stringify({
|
|
79
|
+
peer.send(JSON.stringify({
|
|
80
|
+
type: 'shell-error',
|
|
81
|
+
message: 'This session has no terminal available.',
|
|
82
|
+
}));
|
|
80
83
|
return;
|
|
81
84
|
}
|
|
82
85
|
if (detachShell.has(peer)) return;
|