ai-remote 0.4.13 → 0.4.15
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 +117 -1
- package/SKILL.md +114 -10
- package/dist/cli-chunk-7UHHVT7Q.mjs +483 -0
- package/dist/cli-chunk-EYQCDSPT.mjs +1567 -0
- package/dist/cli-chunk-K2DYJXC5.mjs +63 -0
- package/dist/cli-chunk-RBCU3NX4.mjs +173 -0
- package/dist/cli-chunk-XN3E5SLX.mjs +51 -0
- package/dist/cli-chunk-XT2FISR5.mjs +201 -0
- package/dist/cli-copy-NRXMTHID.mjs +718 -0
- package/dist/cli-daemon-LU4LHDCH.mjs +8418 -0
- package/dist/cli-identities-ZILT4XCR.mjs +412 -0
- package/dist/cli-secrets-26D3F6EN.mjs +218 -0
- package/dist/cli-shell-TE6DLBIB.mjs +10 -0
- package/dist/cli-window-LN32ONC4.mjs +15 -0
- package/dist/cli.mjs +332 -11080
- package/dist/index.d.ts +10 -1
- package/dist/index.js +1 -1
- package/dist/protocols.d.ts +18 -1
- package/dist/protocols.js +14 -14
- package/package.json +1 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/cli/viewer-layout.ts
|
|
2
|
+
var TERMINAL_COLUMNS = 80;
|
|
3
|
+
var TERMINAL_CELL_WIDTH = 7.25;
|
|
4
|
+
var TERMINAL_PANE_CHROME = 30;
|
|
5
|
+
var SIDE_PANEL_WIDTH = TERMINAL_COLUMNS * TERMINAL_CELL_WIDTH + TERMINAL_PANE_CHROME;
|
|
6
|
+
var SIDE_PANEL_MIN_WIDTH = 40 * TERMINAL_CELL_WIDTH + TERMINAL_PANE_CHROME;
|
|
7
|
+
var TITLEBAR_HEIGHT = 34;
|
|
8
|
+
var TITLEBAR_TRAFFIC_LIGHTS_WIDTH = 72;
|
|
9
|
+
var TITLEBAR_CONTROLS_WIDTH = 154;
|
|
10
|
+
function isTitlebarDragPoint(point, innerWidth, scaleFactor) {
|
|
11
|
+
const scale = Number.isFinite(scaleFactor) && scaleFactor > 0 ? scaleFactor : 1;
|
|
12
|
+
return point.y >= 0 && point.y < TITLEBAR_HEIGHT * scale && point.x >= TITLEBAR_TRAFFIC_LIGHTS_WIDTH * scale && point.x < innerWidth - TITLEBAR_CONTROLS_WIDTH * scale;
|
|
13
|
+
}
|
|
14
|
+
function logicalDisplaySize(physical, scaleFactor) {
|
|
15
|
+
const scale = Number.isFinite(scaleFactor) && scaleFactor > 0 ? scaleFactor : 1;
|
|
16
|
+
return {
|
|
17
|
+
width: Math.max(1, Math.round(physical.width / scale)),
|
|
18
|
+
height: Math.max(1, Math.round(physical.height / scale))
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function fullscreenRdpSize(display, sidePanel) {
|
|
22
|
+
const availableWidth = Math.max(640, Math.floor(display.width) - (sidePanel ? SIDE_PANEL_WIDTH : 0));
|
|
23
|
+
const availableHeight = Math.max(480, Math.floor(display.height));
|
|
24
|
+
const scale = Math.min(1, 4096 / availableWidth, 2048 / availableHeight);
|
|
25
|
+
return {
|
|
26
|
+
width: Math.max(640, Math.floor(availableWidth * scale)) & ~3,
|
|
27
|
+
height: Math.max(480, Math.floor(availableHeight * scale)) & ~1
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/cli/paths.ts
|
|
32
|
+
import { mkdirSync } from "node:fs";
|
|
33
|
+
import { homedir } from "node:os";
|
|
34
|
+
import { join } from "node:path";
|
|
35
|
+
var ROOT = join(homedir(), ".ai-remote");
|
|
36
|
+
var RUN_DIR = join(ROOT, "run");
|
|
37
|
+
function ensureRunDir() {
|
|
38
|
+
mkdirSync(RUN_DIR, { recursive: true, mode: 448 });
|
|
39
|
+
return RUN_DIR;
|
|
40
|
+
}
|
|
41
|
+
function sessionName(host, port, explicit) {
|
|
42
|
+
if (explicit) return explicit.replace(/[^\w.-]/g, "_");
|
|
43
|
+
return `${host}_${port}`.replace(/[^\w.-]/g, "_");
|
|
44
|
+
}
|
|
45
|
+
var socketPath = (name) => join(RUN_DIR, `${name}.sock`);
|
|
46
|
+
var metaPath = (name) => join(RUN_DIR, `${name}.json`);
|
|
47
|
+
var logPath = (name) => join(RUN_DIR, `${name}.log`);
|
|
48
|
+
var currentPath = () => join(ROOT, "current");
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
SIDE_PANEL_WIDTH,
|
|
52
|
+
isTitlebarDragPoint,
|
|
53
|
+
logicalDisplaySize,
|
|
54
|
+
fullscreenRdpSize,
|
|
55
|
+
ROOT,
|
|
56
|
+
RUN_DIR,
|
|
57
|
+
ensureRunDir,
|
|
58
|
+
sessionName,
|
|
59
|
+
socketPath,
|
|
60
|
+
metaPath,
|
|
61
|
+
logPath,
|
|
62
|
+
currentPath
|
|
63
|
+
};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SshSession,
|
|
3
|
+
TcpTransport
|
|
4
|
+
} from "./cli-chunk-EYQCDSPT.mjs";
|
|
5
|
+
|
|
6
|
+
// src/cli/shell.ts
|
|
7
|
+
function bareUsername(username) {
|
|
8
|
+
const withoutDomain = username.includes("\\") ? username.split("\\").pop() : username;
|
|
9
|
+
return withoutDomain.split("@")[0].trim();
|
|
10
|
+
}
|
|
11
|
+
var Shell = class {
|
|
12
|
+
options;
|
|
13
|
+
session;
|
|
14
|
+
ready = false;
|
|
15
|
+
/** True once the connection has ended. A closed shell never reopens; a new one is made. */
|
|
16
|
+
closed = false;
|
|
17
|
+
lastError = "";
|
|
18
|
+
#sinks = /* @__PURE__ */ new Set();
|
|
19
|
+
/** What the shell has printed, so a viewer attaching late sees the scrollback. */
|
|
20
|
+
#scrollback = [];
|
|
21
|
+
#scrollbackBytes = 0;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.options = options;
|
|
24
|
+
this.session = new SshSession(`tcp://${options.host}:${options.port}`, {
|
|
25
|
+
username: options.username,
|
|
26
|
+
password: options.password ?? "",
|
|
27
|
+
identities: options.identities ?? [],
|
|
28
|
+
columns: options.columns ?? 120,
|
|
29
|
+
rows: options.rows ?? 30,
|
|
30
|
+
// Every host this reaches was named by the person running the command,
|
|
31
|
+
// on their own network. There is no stored known_hosts to compare with
|
|
32
|
+
// yet, so nothing useful can be decided here.
|
|
33
|
+
verifyHost: async () => true,
|
|
34
|
+
openTransport: () => new TcpTransport(options.host, options.port)
|
|
35
|
+
});
|
|
36
|
+
this.session.addEventListener("data", (event) => {
|
|
37
|
+
const bytes = event.detail.display ?? event.detail.bytes;
|
|
38
|
+
if (!bytes?.length) return;
|
|
39
|
+
this.#remember(bytes);
|
|
40
|
+
for (const sink of this.#sinks) sink(bytes);
|
|
41
|
+
});
|
|
42
|
+
this.session.addEventListener("ready", () => {
|
|
43
|
+
this.ready = true;
|
|
44
|
+
});
|
|
45
|
+
this.session.addEventListener("error", (event) => {
|
|
46
|
+
this.lastError = event.detail?.message || "";
|
|
47
|
+
});
|
|
48
|
+
this.session.addEventListener("close", () => {
|
|
49
|
+
this.ready = false;
|
|
50
|
+
this.closed = true;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** Everything printed so far, capped so a long-running shell cannot grow forever. */
|
|
54
|
+
get scrollback() {
|
|
55
|
+
const total = this.#scrollback.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
56
|
+
const out = new Uint8Array(total);
|
|
57
|
+
let offset = 0;
|
|
58
|
+
for (const chunk of this.#scrollback) {
|
|
59
|
+
out.set(chunk, offset);
|
|
60
|
+
offset += chunk.length;
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
/** Called when the connection ends, however it ends. Returns an unsubscribe. */
|
|
65
|
+
onClose(listener) {
|
|
66
|
+
if (this.closed) {
|
|
67
|
+
queueMicrotask(listener);
|
|
68
|
+
return () => {
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
this.session.addEventListener("close", listener);
|
|
72
|
+
return () => this.session.removeEventListener("close", listener);
|
|
73
|
+
}
|
|
74
|
+
onData(sink) {
|
|
75
|
+
this.#sinks.add(sink);
|
|
76
|
+
return () => this.#sinks.delete(sink);
|
|
77
|
+
}
|
|
78
|
+
connect(timeoutMs = 3e4) {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
let settled = false;
|
|
81
|
+
const finish = (error) => {
|
|
82
|
+
if (settled) return;
|
|
83
|
+
settled = true;
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
if (error) reject(error);
|
|
86
|
+
else resolve();
|
|
87
|
+
};
|
|
88
|
+
const timer = setTimeout(
|
|
89
|
+
() => finish(new Error(`The SSH host did not open a shell within ${Math.round(timeoutMs / 1e3)}s.`)),
|
|
90
|
+
timeoutMs
|
|
91
|
+
);
|
|
92
|
+
this.session.addEventListener("ready", () => finish());
|
|
93
|
+
this.session.addEventListener("close", (event) => {
|
|
94
|
+
finish(new Error(this.lastError || event.detail?.message || "The SSH connection closed during sign-in."));
|
|
95
|
+
});
|
|
96
|
+
this.session.connect();
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Run one command and wait for its output and exit status.
|
|
101
|
+
*
|
|
102
|
+
* The first command against a Windows host usually fails: the engine frames
|
|
103
|
+
* commands POSIX-style to find where the output starts and ends, cmd.exe
|
|
104
|
+
* rejects that, and the engine notes the shell family and asks to be called
|
|
105
|
+
* again. That is a detail of how the shell was discovered, not something a
|
|
106
|
+
* caller did wrong, so the retry happens here instead of reaching them.
|
|
107
|
+
*
|
|
108
|
+
* The wait in front is the same idea one step earlier. "Connected" and "able
|
|
109
|
+
* to take a command" are not the same moment on Windows: OpenSSH hands out
|
|
110
|
+
* cmd.exe, the engine swaps it for PowerShell, and readiness is withdrawn in
|
|
111
|
+
* between. A caller arriving in that window used to be told the shell was
|
|
112
|
+
* still starting -- true, and useless, because the only thing to do about it
|
|
113
|
+
* is wait. So it waits.
|
|
114
|
+
*/
|
|
115
|
+
async runCommand(command) {
|
|
116
|
+
await this.#waitForPrompt();
|
|
117
|
+
let result;
|
|
118
|
+
try {
|
|
119
|
+
result = await this.session.runCommand(command);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
122
|
+
if (!/run the command again|not a POSIX shell/i.test(message)) throw error;
|
|
123
|
+
await this.#waitForPrompt();
|
|
124
|
+
result = await this.session.runCommand(command);
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
output: result.output ?? "",
|
|
128
|
+
exitStatus: result.exitStatus ?? null,
|
|
129
|
+
timedOut: Boolean(result.timedOut),
|
|
130
|
+
durationMs: result.durationMs ?? null
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Wait until the shell will accept a framed command.
|
|
135
|
+
*
|
|
136
|
+
* Returns on the first check when the shell is already settled, which is the
|
|
137
|
+
* usual case, so this costs a waiting caller nothing.
|
|
138
|
+
*/
|
|
139
|
+
async #waitForPrompt(timeoutMs = 15e3) {
|
|
140
|
+
const deadline = Date.now() + timeoutMs;
|
|
141
|
+
while (Date.now() < deadline) {
|
|
142
|
+
if (this.session.shellOpen && this.session.readySignaled !== false) return;
|
|
143
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
144
|
+
}
|
|
145
|
+
throw new Error("The SSH shell did not become ready for a command.");
|
|
146
|
+
}
|
|
147
|
+
/** Raw keystrokes, for an interactive terminal. */
|
|
148
|
+
write(text) {
|
|
149
|
+
this.session.write(text);
|
|
150
|
+
}
|
|
151
|
+
resize(columns, rows) {
|
|
152
|
+
this.session.resize?.(columns, rows);
|
|
153
|
+
}
|
|
154
|
+
disconnect() {
|
|
155
|
+
try {
|
|
156
|
+
this.session.disconnect();
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
#remember(bytes) {
|
|
161
|
+
const CAP = 256 * 1024;
|
|
162
|
+
this.#scrollback.push(bytes);
|
|
163
|
+
this.#scrollbackBytes += bytes.length;
|
|
164
|
+
while (this.#scrollbackBytes > CAP && this.#scrollback.length > 1) {
|
|
165
|
+
this.#scrollbackBytes -= this.#scrollback.shift().length;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export {
|
|
171
|
+
bareUsername,
|
|
172
|
+
Shell
|
|
173
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// src/cli/ipc.ts
|
|
2
|
+
import net from "node:net";
|
|
3
|
+
import { StringDecoder } from "node:string_decoder";
|
|
4
|
+
function readMessages(socket, onMessage) {
|
|
5
|
+
const decoder = new StringDecoder("utf8");
|
|
6
|
+
let buffer = "";
|
|
7
|
+
socket.on("data", (chunk) => {
|
|
8
|
+
buffer += decoder.write(chunk);
|
|
9
|
+
for (; ; ) {
|
|
10
|
+
const newline = buffer.indexOf("\n");
|
|
11
|
+
if (newline === -1) return;
|
|
12
|
+
const line = buffer.slice(0, newline);
|
|
13
|
+
buffer = buffer.slice(newline + 1);
|
|
14
|
+
if (!line.trim()) continue;
|
|
15
|
+
try {
|
|
16
|
+
onMessage(JSON.parse(line));
|
|
17
|
+
} catch {
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
var writeMessage = (socket, value) => {
|
|
23
|
+
socket.write(`${JSON.stringify(value)}
|
|
24
|
+
`);
|
|
25
|
+
};
|
|
26
|
+
function request(path, op, args = {}, timeoutMs = 12e4) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const socket = net.connect(path);
|
|
29
|
+
let settled = false;
|
|
30
|
+
const finish = (error, response) => {
|
|
31
|
+
if (settled) return;
|
|
32
|
+
settled = true;
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
socket.destroy();
|
|
35
|
+
if (error) reject(error);
|
|
36
|
+
else resolve(response);
|
|
37
|
+
};
|
|
38
|
+
const timer = setTimeout(() => finish(new Error(`The session did not answer "${op}" in time.`)), timeoutMs);
|
|
39
|
+
socket.on("connect", () => writeMessage(socket, { id: 1, op, args }));
|
|
40
|
+
socket.on("error", (error) => {
|
|
41
|
+
finish(Object.assign(error, { notRunning: error.code === "ENOENT" || error.code === "ECONNREFUSED" }));
|
|
42
|
+
});
|
|
43
|
+
readMessages(socket, (message) => finish(null, message));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
readMessages,
|
|
49
|
+
writeMessage,
|
|
50
|
+
request
|
|
51
|
+
};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// src/protocols/ssh/wire.ts
|
|
2
|
+
var textEncoder = new TextEncoder();
|
|
3
|
+
var textDecoder = new TextDecoder();
|
|
4
|
+
function encodeUtf8(text) {
|
|
5
|
+
return textEncoder.encode(text);
|
|
6
|
+
}
|
|
7
|
+
function decodeUtf8(bytes) {
|
|
8
|
+
return textDecoder.decode(bytes);
|
|
9
|
+
}
|
|
10
|
+
function concatBytes(...parts) {
|
|
11
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
12
|
+
const out = new Uint8Array(total);
|
|
13
|
+
let offset = 0;
|
|
14
|
+
for (const part of parts) {
|
|
15
|
+
out.set(part, offset);
|
|
16
|
+
offset += part.length;
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
function toBase64(bytes) {
|
|
21
|
+
let binary = "";
|
|
22
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
23
|
+
return btoa(binary);
|
|
24
|
+
}
|
|
25
|
+
function fromBase64(text) {
|
|
26
|
+
const binary = atob(text);
|
|
27
|
+
const bytes = new Uint8Array(binary.length);
|
|
28
|
+
for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
|
|
29
|
+
return bytes;
|
|
30
|
+
}
|
|
31
|
+
function toBase64Url(bytes) {
|
|
32
|
+
return toBase64(bytes).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
33
|
+
}
|
|
34
|
+
var SshWriter = class {
|
|
35
|
+
bytes;
|
|
36
|
+
view;
|
|
37
|
+
offset = 0;
|
|
38
|
+
constructor(capacity = 256) {
|
|
39
|
+
this.bytes = new Uint8Array(new ArrayBuffer(capacity));
|
|
40
|
+
this.view = new DataView(this.bytes.buffer);
|
|
41
|
+
}
|
|
42
|
+
#ensure(extra) {
|
|
43
|
+
if (this.offset + extra <= this.bytes.length) return;
|
|
44
|
+
let capacity = Math.max(this.bytes.length * 2, 64);
|
|
45
|
+
while (capacity < this.offset + extra) capacity *= 2;
|
|
46
|
+
const grown = new Uint8Array(new ArrayBuffer(capacity));
|
|
47
|
+
grown.set(this.bytes.subarray(0, this.offset));
|
|
48
|
+
this.bytes = grown;
|
|
49
|
+
this.view = new DataView(grown.buffer);
|
|
50
|
+
}
|
|
51
|
+
u8(value) {
|
|
52
|
+
this.#ensure(1);
|
|
53
|
+
this.view.setUint8(this.offset, value);
|
|
54
|
+
this.offset += 1;
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
boolean(value) {
|
|
58
|
+
return this.u8(value ? 1 : 0);
|
|
59
|
+
}
|
|
60
|
+
u32(value) {
|
|
61
|
+
this.#ensure(4);
|
|
62
|
+
this.view.setUint32(this.offset, value >>> 0, false);
|
|
63
|
+
this.offset += 4;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A 64-bit unsigned integer, written from a JavaScript number.
|
|
68
|
+
*
|
|
69
|
+
* SFTP measures files and offsets in these. Written as two 32-bit halves
|
|
70
|
+
* rather than through a BigInt, because the values are byte counts that come
|
|
71
|
+
* from and go back to `number` at every other layer, and converting twice per
|
|
72
|
+
* chunk to satisfy a type is work with nothing to show for it. Above
|
|
73
|
+
* `Number.MAX_SAFE_INTEGER` -- eight petabytes -- this is wrong, and so is
|
|
74
|
+
* every other size in this codebase.
|
|
75
|
+
*/
|
|
76
|
+
u64(value) {
|
|
77
|
+
const whole = Math.floor(value);
|
|
78
|
+
return this.u32(Math.floor(whole / 4294967296)).u32(whole % 4294967296);
|
|
79
|
+
}
|
|
80
|
+
raw(bytes) {
|
|
81
|
+
this.#ensure(bytes.length);
|
|
82
|
+
this.bytes.set(bytes, this.offset);
|
|
83
|
+
this.offset += bytes.length;
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
/** A length-prefixed string. Text is encoded as UTF-8. */
|
|
87
|
+
string(value) {
|
|
88
|
+
const bytes = typeof value === "string" ? encodeUtf8(value) : value;
|
|
89
|
+
return this.u32(bytes.length).raw(bytes);
|
|
90
|
+
}
|
|
91
|
+
nameList(names) {
|
|
92
|
+
return this.string(names.join(","));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* An mpint: two's complement, big endian, with no leading zero bytes except
|
|
96
|
+
* the one that keeps a positive number from looking negative.
|
|
97
|
+
*/
|
|
98
|
+
mpint(bytes) {
|
|
99
|
+
let start = 0;
|
|
100
|
+
while (start < bytes.length && bytes[start] === 0) start++;
|
|
101
|
+
const magnitude = bytes.subarray(start);
|
|
102
|
+
if (magnitude.length === 0) return this.u32(0);
|
|
103
|
+
if ((magnitude[0] ?? 0) & 128) {
|
|
104
|
+
this.u32(magnitude.length + 1).u8(0);
|
|
105
|
+
return this.raw(magnitude);
|
|
106
|
+
}
|
|
107
|
+
return this.u32(magnitude.length).raw(magnitude);
|
|
108
|
+
}
|
|
109
|
+
get length() {
|
|
110
|
+
return this.offset;
|
|
111
|
+
}
|
|
112
|
+
take() {
|
|
113
|
+
return this.bytes.slice(0, this.offset);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
var SshReader = class {
|
|
117
|
+
bytes;
|
|
118
|
+
view;
|
|
119
|
+
offset;
|
|
120
|
+
constructor(bytes, offset = 0) {
|
|
121
|
+
this.bytes = bytes;
|
|
122
|
+
this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
123
|
+
this.offset = offset;
|
|
124
|
+
}
|
|
125
|
+
get remaining() {
|
|
126
|
+
return this.bytes.length - this.offset;
|
|
127
|
+
}
|
|
128
|
+
#need(count) {
|
|
129
|
+
if (count < 0 || this.remaining < count) {
|
|
130
|
+
throw new Error(`SSH: truncated packet (needed ${count} bytes, ${this.remaining} left)`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
u8() {
|
|
134
|
+
this.#need(1);
|
|
135
|
+
return this.view.getUint8(this.offset++);
|
|
136
|
+
}
|
|
137
|
+
boolean() {
|
|
138
|
+
return this.u8() !== 0;
|
|
139
|
+
}
|
|
140
|
+
u32() {
|
|
141
|
+
this.#need(4);
|
|
142
|
+
const value = this.view.getUint32(this.offset, false);
|
|
143
|
+
this.offset += 4;
|
|
144
|
+
return value;
|
|
145
|
+
}
|
|
146
|
+
/** A 64-bit unsigned integer as a number; see SshWriter#u64 on the range. */
|
|
147
|
+
u64() {
|
|
148
|
+
return this.u32() * 4294967296 + this.u32();
|
|
149
|
+
}
|
|
150
|
+
raw(count) {
|
|
151
|
+
this.#need(count);
|
|
152
|
+
const slice = this.bytes.subarray(this.offset, this.offset + count);
|
|
153
|
+
this.offset += count;
|
|
154
|
+
return slice;
|
|
155
|
+
}
|
|
156
|
+
/** The bytes of a length-prefixed string. */
|
|
157
|
+
stringBytes() {
|
|
158
|
+
return this.raw(this.u32());
|
|
159
|
+
}
|
|
160
|
+
string() {
|
|
161
|
+
return decodeUtf8(this.stringBytes());
|
|
162
|
+
}
|
|
163
|
+
nameList() {
|
|
164
|
+
const value = this.string();
|
|
165
|
+
return value ? value.split(",") : [];
|
|
166
|
+
}
|
|
167
|
+
/** An mpint's magnitude, with the sign byte removed. */
|
|
168
|
+
mpint() {
|
|
169
|
+
const bytes = this.stringBytes();
|
|
170
|
+
let start = 0;
|
|
171
|
+
while (start < bytes.length && bytes[start] === 0) start++;
|
|
172
|
+
return bytes.subarray(start);
|
|
173
|
+
}
|
|
174
|
+
skip(count) {
|
|
175
|
+
this.#need(count);
|
|
176
|
+
this.offset += count;
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
rest() {
|
|
180
|
+
return this.bytes.subarray(this.offset);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
function padStart(bytes, width) {
|
|
184
|
+
if (bytes.length === width) return bytes;
|
|
185
|
+
if (bytes.length > width) return bytes.subarray(bytes.length - width);
|
|
186
|
+
const out = new Uint8Array(width);
|
|
187
|
+
out.set(bytes, width - bytes.length);
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export {
|
|
192
|
+
encodeUtf8,
|
|
193
|
+
decodeUtf8,
|
|
194
|
+
concatBytes,
|
|
195
|
+
toBase64,
|
|
196
|
+
fromBase64,
|
|
197
|
+
toBase64Url,
|
|
198
|
+
SshWriter,
|
|
199
|
+
SshReader,
|
|
200
|
+
padStart
|
|
201
|
+
};
|