@skylandnpm/octopus-proxy 0.1.66 → 0.1.68
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/dist/{chunk-IV5ZGRAM.js → chunk-6G2W4KWC.js} +135 -62
- package/dist/{chunk-UFLXQ4HQ.js → chunk-OTKNRBZ7.js} +310 -61
- package/dist/cli.js +2 -2
- package/dist/main.js +2 -2
- package/dist/remote-worker.js +44 -157
- package/package.json +1 -1
|
@@ -4,15 +4,16 @@ import {
|
|
|
4
4
|
PlatformDesktop,
|
|
5
5
|
REMOTE_DEFAULTS,
|
|
6
6
|
RemoteConfigSchema,
|
|
7
|
+
RemotePlatformRegistry,
|
|
7
8
|
RemoteQualitySchema,
|
|
8
9
|
SignalSchema,
|
|
9
10
|
__commonJS,
|
|
10
11
|
__toESM,
|
|
11
12
|
assertDirectSignal,
|
|
12
|
-
|
|
13
|
+
nativeDependencies,
|
|
13
14
|
remoteSessionBudget,
|
|
14
15
|
z
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-OTKNRBZ7.js";
|
|
16
17
|
|
|
17
18
|
// ../../node_modules/.pnpm/ajv@8.17.1/node_modules/ajv/dist/compile/codegen/code.js
|
|
18
19
|
var require_code = __commonJS({
|
|
@@ -7812,18 +7813,22 @@ async function startDeviceControl(directory, devices, connections) {
|
|
|
7812
7813
|
}
|
|
7813
7814
|
|
|
7814
7815
|
// src/daemon.ts
|
|
7815
|
-
import { mkdir as
|
|
7816
|
-
import { join as
|
|
7816
|
+
import { mkdir as mkdir10 } from "node:fs/promises";
|
|
7817
|
+
import { join as join16 } from "node:path";
|
|
7817
7818
|
|
|
7818
7819
|
// src/workspace-diff.ts
|
|
7819
7820
|
import { execFile as execFile2 } from "node:child_process";
|
|
7820
7821
|
import { promisify as promisify2 } from "node:util";
|
|
7821
|
-
import { realpath } from "node:fs/promises";
|
|
7822
|
+
import { realpath, stat as stat3 } from "node:fs/promises";
|
|
7822
7823
|
import { relative, resolve } from "node:path";
|
|
7823
7824
|
var execute = promisify2(execFile2);
|
|
7824
7825
|
var MAX_PATCH = 256 * 1024;
|
|
7825
7826
|
var MAX_TOTAL = 1024 * 1024;
|
|
7826
|
-
|
|
7827
|
+
var MAX_LINES = 2e3;
|
|
7828
|
+
async function workspaceDiff(workspace, project, page = {}) {
|
|
7829
|
+
const offset = page.paged ? page.offset ?? 0 : 0;
|
|
7830
|
+
const limit = page.paged ? page.limit ?? 20 : 200;
|
|
7831
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > 200) throw new Error("Invalid diff page");
|
|
7827
7832
|
const root = await workspace.root(project);
|
|
7828
7833
|
const git = async (...args) => (await execute("git", ["--no-optional-locks", "-c", "core.quotePath=false", ...args], {
|
|
7829
7834
|
cwd: root,
|
|
@@ -7867,13 +7872,8 @@ async function workspaceDiff(workspace, project) {
|
|
|
7867
7872
|
if (!base) throw new Error("\u65E0\u6CD5\u8BFB\u53D6 Git \u6BD4\u8F83\u57FA\u51C6");
|
|
7868
7873
|
const files = [];
|
|
7869
7874
|
let bytes = 0;
|
|
7870
|
-
for (const entry of entries.slice(
|
|
7875
|
+
for (const entry of entries.slice(offset, offset + limit)) {
|
|
7871
7876
|
const change = { ...entry, patch: "", additions: 0, deletions: 0 };
|
|
7872
|
-
if (bytes >= MAX_TOTAL) {
|
|
7873
|
-
change.notice = "\u53D8\u66F4\u8F83\u591A\uFF0C\u672A\u52A0\u8F7D\u6B64\u6587\u4EF6\u5185\u5BB9";
|
|
7874
|
-
files.push(change);
|
|
7875
|
-
continue;
|
|
7876
|
-
}
|
|
7877
7877
|
try {
|
|
7878
7878
|
if (entry.status === "??") {
|
|
7879
7879
|
const read = await workspace.read(project, entry.file, 0, MAX_PATCH);
|
|
@@ -7890,16 +7890,30 @@ ${lines.map((line) => `+${line}`).join("\n")}
|
|
|
7890
7890
|
`;
|
|
7891
7891
|
}
|
|
7892
7892
|
} else {
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7893
|
+
let currentSize = 0, baseSize = 0;
|
|
7894
|
+
try {
|
|
7895
|
+
currentSize = (await stat3(await workspace.path(project, entry.file))).size;
|
|
7896
|
+
} catch (error) {
|
|
7897
|
+
if (error.code !== "ENOENT") throw error;
|
|
7898
|
+
}
|
|
7899
|
+
try {
|
|
7900
|
+
baseSize = Number((await git("cat-file", "-s", `${base}:${prefix ? `${prefix}/` : ""}${entry.file}`)).trim());
|
|
7901
|
+
} catch {
|
|
7902
|
+
}
|
|
7903
|
+
if (Math.max(currentSize, baseSize) > MAX_PATCH) change.notice = "\u6587\u4EF6\u8D85\u8FC7 256 KiB\uFF0C\u672A\u52A0\u8F7D\u5DEE\u5F02\uFF0C\u8BF7\u5728\u6587\u4EF6\u9762\u677F\u6216\u672C\u5730 Git \u67E5\u770B";
|
|
7904
|
+
else {
|
|
7905
|
+
change.patch = await git("diff", "--no-ext-diff", "--no-textconv", "--no-renames", "--no-color", "--relative", "--unified=3", base, "--", entry.file);
|
|
7906
|
+
change.binary = change.patch.includes("Binary files ");
|
|
7898
7907
|
}
|
|
7899
7908
|
}
|
|
7900
7909
|
} catch (error) {
|
|
7901
7910
|
change.notice = error instanceof Error ? error.message.slice(0, 250) : "\u65E0\u6CD5\u52A0\u8F7D\u6587\u4EF6\u5DEE\u5F02";
|
|
7902
7911
|
}
|
|
7912
|
+
if (Buffer.byteLength(change.patch) > MAX_PATCH || change.patch.split("\n").length > MAX_LINES) {
|
|
7913
|
+
change.patch = "";
|
|
7914
|
+
change.notice = "\u5DEE\u5F02\u8D85\u8FC7 256 KiB \u6216 2000 \u884C\uFF0C\u672A\u52A0\u8F7D\u5DEE\u5F02\uFF0C\u8BF7\u5728\u672C\u5730 Git \u67E5\u770B";
|
|
7915
|
+
}
|
|
7916
|
+
if (bytes + Buffer.byteLength(change.patch) > MAX_TOTAL) break;
|
|
7903
7917
|
let inHunk = false;
|
|
7904
7918
|
for (const line of change.patch.split("\n")) {
|
|
7905
7919
|
if (line.startsWith("@@ ")) inHunk = true;
|
|
@@ -7909,12 +7923,13 @@ ${lines.map((line) => `+${line}`).join("\n")}
|
|
|
7909
7923
|
bytes += Buffer.byteLength(change.patch);
|
|
7910
7924
|
files.push(change);
|
|
7911
7925
|
}
|
|
7912
|
-
|
|
7926
|
+
const nextOffset = offset + files.length < entries.length ? offset + files.length : void 0;
|
|
7927
|
+
return { scope: "workspace", files, total: entries.length, nextOffset, truncated: nextOffset !== void 0 || files.some((file) => file.notice), directory: prefix || ".", capturedAt: Date.now() };
|
|
7913
7928
|
}
|
|
7914
7929
|
|
|
7915
7930
|
// src/workspace.ts
|
|
7916
7931
|
import { constants } from "node:fs";
|
|
7917
|
-
import { mkdir as mkdir2, open as open2, readdir, realpath as realpath2, stat as
|
|
7932
|
+
import { mkdir as mkdir2, open as open2, readdir, realpath as realpath2, stat as stat4, link, unlink as unlink3 } from "node:fs/promises";
|
|
7918
7933
|
import { basename, dirname, extname, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep } from "node:path";
|
|
7919
7934
|
import { createHash as createHash2 } from "node:crypto";
|
|
7920
7935
|
var mimeOf = (name) => ({ ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp", ".svg": "image/svg+xml", ".md": "text/markdown", ".txt": "text/plain", ".json": "application/json", ".pdf": "application/pdf" })[extname(name).toLowerCase()] ?? "application/octet-stream";
|
|
@@ -7949,7 +7964,7 @@ var Workspace = class {
|
|
|
7949
7964
|
const name = relative2(await this.root(id), resolve2(full, e.name));
|
|
7950
7965
|
try {
|
|
7951
7966
|
const safe = await this.path(id, name);
|
|
7952
|
-
const info = await
|
|
7967
|
+
const info = await stat4(safe);
|
|
7953
7968
|
return { name: e.name, path: name, type: info.isDirectory() ? "directory" : "file", size: info.size };
|
|
7954
7969
|
} catch {
|
|
7955
7970
|
return null;
|
|
@@ -8046,7 +8061,7 @@ var Workspace = class {
|
|
|
8046
8061
|
|
|
8047
8062
|
// src/uploads.ts
|
|
8048
8063
|
import { constants as constants2 } from "node:fs";
|
|
8049
|
-
import { open as open3, readdir as readdir2, unlink as unlink4, stat as
|
|
8064
|
+
import { open as open3, readdir as readdir2, unlink as unlink4, stat as stat5 } from "node:fs/promises";
|
|
8050
8065
|
import { basename as basename2, dirname as dirname3, join as join4 } from "node:path";
|
|
8051
8066
|
import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
|
|
8052
8067
|
|
|
@@ -8105,7 +8120,7 @@ var Uploads = class {
|
|
|
8105
8120
|
if (!u || u.projectId !== projectId) throw new Error("Upload not found in this project");
|
|
8106
8121
|
if (u.state === "pending" && u.offset === u.size) {
|
|
8107
8122
|
try {
|
|
8108
|
-
await
|
|
8123
|
+
await stat5(u.temp);
|
|
8109
8124
|
} catch (e) {
|
|
8110
8125
|
if (e.code !== "ENOENT") throw e;
|
|
8111
8126
|
const target = await this.workspace.path(projectId, u.destination);
|
|
@@ -8130,7 +8145,7 @@ var Uploads = class {
|
|
|
8130
8145
|
} else destination = `${String(input.directory ?? ".")}/${name}`;
|
|
8131
8146
|
const dest = await this.workspace.path(projectId, destination, true);
|
|
8132
8147
|
try {
|
|
8133
|
-
await
|
|
8148
|
+
await stat5(dest);
|
|
8134
8149
|
throw new Error("Destination exists; rename the file");
|
|
8135
8150
|
} catch (e) {
|
|
8136
8151
|
if (e.code !== "ENOENT") throw e;
|
|
@@ -8513,7 +8528,7 @@ function pageHistory(input, payload) {
|
|
|
8513
8528
|
// src/artifacts.ts
|
|
8514
8529
|
import { createHash as createHash5 } from "node:crypto";
|
|
8515
8530
|
import { join as join6 } from "node:path";
|
|
8516
|
-
import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir4, stat as
|
|
8531
|
+
import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir4, stat as stat6 } from "node:fs/promises";
|
|
8517
8532
|
var Artifacts = class {
|
|
8518
8533
|
constructor(workspace, directory, emit) {
|
|
8519
8534
|
this.workspace = workspace;
|
|
@@ -8537,6 +8552,12 @@ var Artifacts = class {
|
|
|
8537
8552
|
scan(project) {
|
|
8538
8553
|
return this.serial(project, () => this.scanUnlocked(project));
|
|
8539
8554
|
}
|
|
8555
|
+
async scanPage(project, offset = 0, limit = 20) {
|
|
8556
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > 200) throw new Error("Invalid artifact page");
|
|
8557
|
+
const all = (await this.scan(project)).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
8558
|
+
const items = all.slice(offset, offset + limit).map((item) => ({ ...item, revisions: item.revisions.slice(-1) }));
|
|
8559
|
+
return { items, total: all.length, nextOffset: offset + items.length < all.length ? offset + items.length : void 0 };
|
|
8560
|
+
}
|
|
8540
8561
|
async scanUnlocked(project) {
|
|
8541
8562
|
await this.workspace.root(project);
|
|
8542
8563
|
const index = await readJson(this.index(project)) ?? {}, seen = /* @__PURE__ */ new Set();
|
|
@@ -8585,7 +8606,7 @@ var Artifacts = class {
|
|
|
8585
8606
|
}
|
|
8586
8607
|
for (const item of Object.values(index)) if (!seen.has(item.path) && !item.revisions.at(-1)?.deleted) {
|
|
8587
8608
|
try {
|
|
8588
|
-
await
|
|
8609
|
+
await stat6(await this.workspace.path(project, item.path));
|
|
8589
8610
|
} catch (e) {
|
|
8590
8611
|
if (e.code === "ENOENT") {
|
|
8591
8612
|
item.revisions.push({ hash: "", timestamp: (/* @__PURE__ */ new Date()).toISOString(), deleted: true });
|
|
@@ -8596,7 +8617,7 @@ var Artifacts = class {
|
|
|
8596
8617
|
}
|
|
8597
8618
|
if (changed) {
|
|
8598
8619
|
await atomicJson(this.index(project), index);
|
|
8599
|
-
this.emit(project, "artifacts.updated", Object.
|
|
8620
|
+
this.emit(project, "artifacts.updated", { total: Object.keys(index).length });
|
|
8600
8621
|
}
|
|
8601
8622
|
return Object.values(index);
|
|
8602
8623
|
}
|
|
@@ -8607,6 +8628,14 @@ var Artifacts = class {
|
|
|
8607
8628
|
if (!/^[a-f0-9]{64}$/.test(hash) || !(await this.history(project, path)).some((r) => r.hash === hash)) throw new Error("Unknown revision");
|
|
8608
8629
|
return readFile4(join6(this.directory, project, "blobs", hash), "utf8");
|
|
8609
8630
|
}
|
|
8631
|
+
async preview(project, path, hash) {
|
|
8632
|
+
if (!/^[a-f0-9]{64}$/.test(hash) || !(await this.history(project, path)).some((r) => r.hash === hash)) throw new Error("Unknown revision");
|
|
8633
|
+
const blob = join6(this.directory, project, "blobs", hash);
|
|
8634
|
+
if ((await stat6(blob)).size > 256 * 1024) return { content: "", notice: "\u6587\u4EF6\u8D85\u8FC7 256 KiB\uFF0C\u5DF2\u8DF3\u8FC7\u9884\u89C8\uFF0C\u8BF7\u5728\u6587\u4EF6\u9762\u677F\u6216\u672C\u5730\u67E5\u770B\u3002" };
|
|
8635
|
+
const content = await readFile4(blob, "utf8");
|
|
8636
|
+
if (content.split("\n").length > 2e3) return { content: "", notice: "\u6587\u4EF6\u8D85\u8FC7 2000 \u884C\uFF0C\u5DF2\u8DF3\u8FC7\u9884\u89C8\uFF0C\u8BF7\u5728\u6587\u4EF6\u9762\u677F\u6216\u672C\u5730\u67E5\u770B\u3002" };
|
|
8637
|
+
return { content };
|
|
8638
|
+
}
|
|
8610
8639
|
associate(project, path, sessionId) {
|
|
8611
8640
|
return this.serial(project, () => this.associateUnlocked(project, path, sessionId));
|
|
8612
8641
|
}
|
|
@@ -8628,7 +8657,7 @@ import { TLSSocket, createSecureContext } from "node:tls";
|
|
|
8628
8657
|
|
|
8629
8658
|
// src/host-auth.ts
|
|
8630
8659
|
import { spawn } from "node:child_process";
|
|
8631
|
-
import { chmod as chmod3, mkdir as mkdir5, rename as rename3, stat as
|
|
8660
|
+
import { chmod as chmod3, mkdir as mkdir5, rename as rename3, stat as stat7, unlink as unlink5 } from "node:fs/promises";
|
|
8632
8661
|
import { userInfo, platform as platform2 } from "node:os";
|
|
8633
8662
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
8634
8663
|
import { join as join7 } from "node:path";
|
|
@@ -8643,8 +8672,8 @@ async function ensureHostAuth(stateDirectory) {
|
|
|
8643
8672
|
if (system !== "darwin" && system !== "linux") throw new Error("Host authentication is supported only on macOS and Linux");
|
|
8644
8673
|
const mac = system === "darwin";
|
|
8645
8674
|
const source = fileURLToPath2(new URL(mac ? "../native/auth-macos.m" : "../native/auth-linux.c", import.meta.url));
|
|
8646
|
-
const sourceInfo = await
|
|
8647
|
-
const existing = await
|
|
8675
|
+
const sourceInfo = await stat7(source);
|
|
8676
|
+
const existing = await stat7(target).catch(() => null);
|
|
8648
8677
|
if (existing?.isFile() && existing.mtimeMs >= sourceInfo.mtimeMs && (existing.mode & 63) === 0) return target;
|
|
8649
8678
|
const temporary = `${target}.${randomUUID6()}`;
|
|
8650
8679
|
const args = mac ? ["-O2", "-framework", "Foundation", "-framework", "OpenDirectory", source, "-o", target] : ["-O2", source, "-o", temporary, "-lpam"];
|
|
@@ -13617,7 +13646,7 @@ function createAdapter(runtime, workspace, uploads, emit) {
|
|
|
13617
13646
|
}
|
|
13618
13647
|
|
|
13619
13648
|
// src/project-registry.ts
|
|
13620
|
-
import { realpath as realpath6, readFile as readFile9, stat as
|
|
13649
|
+
import { realpath as realpath6, readFile as readFile9, stat as stat8 } from "node:fs/promises";
|
|
13621
13650
|
import { isAbsolute as isAbsolute4 } from "node:path";
|
|
13622
13651
|
import { isDeepStrictEqual } from "node:util";
|
|
13623
13652
|
var ProjectMutationSchema = ProjectSchema.extend({
|
|
@@ -13705,7 +13734,7 @@ var ProjectRegistry = class {
|
|
|
13705
13734
|
const raw = await this.readCurrent();
|
|
13706
13735
|
const directory = expandHome(parsed.directory);
|
|
13707
13736
|
if (!isAbsolute4(directory)) throw new Error("\u9879\u76EE\u76EE\u5F55\u5FC5\u987B\u662F Proxy \u4E3B\u673A\u4E0A\u7684\u7EDD\u5BF9\u8DEF\u5F84");
|
|
13708
|
-
const info = await
|
|
13737
|
+
const info = await stat8(directory).catch(() => void 0);
|
|
13709
13738
|
if (!info?.isDirectory()) throw new Error(`\u9879\u76EE\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u76EE\u5F55\uFF1A${directory}`);
|
|
13710
13739
|
const canonical = await realpath6(directory);
|
|
13711
13740
|
if (this.config.projects.some((project2) => project2.id === parsed.id)) throw new Error(`\u9879\u76EE ID \u5DF2\u5B58\u5728\uFF1A${parsed.id}`);
|
|
@@ -13724,7 +13753,7 @@ var ProjectRegistry = class {
|
|
|
13724
13753
|
if (index < 0) throw new Error(`\u672A\u77E5\u6388\u6743\u9879\u76EE\uFF1A${parsed.id}`);
|
|
13725
13754
|
const directory = expandHome(parsed.directory);
|
|
13726
13755
|
if (!isAbsolute4(directory)) throw new Error("\u9879\u76EE\u76EE\u5F55\u5FC5\u987B\u662F Proxy \u4E3B\u673A\u4E0A\u7684\u7EDD\u5BF9\u8DEF\u5F84");
|
|
13727
|
-
const info = await
|
|
13756
|
+
const info = await stat8(directory).catch(() => void 0);
|
|
13728
13757
|
if (!info?.isDirectory()) throw new Error(`\u9879\u76EE\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u76EE\u5F55\uFF1A${directory}`);
|
|
13729
13758
|
const canonical = await realpath6(directory);
|
|
13730
13759
|
for (const project of this.config.projects) {
|
|
@@ -13755,7 +13784,7 @@ var ProjectRegistry = class {
|
|
|
13755
13784
|
|
|
13756
13785
|
// src/terminal.ts
|
|
13757
13786
|
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
13758
|
-
import { access as access2, stat as
|
|
13787
|
+
import { access as access2, stat as stat10 } from "node:fs/promises";
|
|
13759
13788
|
import { constants as constants5 } from "node:fs";
|
|
13760
13789
|
import { platform as platform3, userInfo as userInfo3 } from "node:os";
|
|
13761
13790
|
import { delimiter } from "node:path";
|
|
@@ -13763,7 +13792,7 @@ import * as pty from "node-pty";
|
|
|
13763
13792
|
|
|
13764
13793
|
// src/terminal-support.ts
|
|
13765
13794
|
import { createRequire } from "node:module";
|
|
13766
|
-
import { access, chmod as chmod4, stat as
|
|
13795
|
+
import { access, chmod as chmod4, stat as stat9 } from "node:fs/promises";
|
|
13767
13796
|
import { constants as constants4 } from "node:fs";
|
|
13768
13797
|
import { dirname as dirname6, resolve as resolve4 } from "node:path";
|
|
13769
13798
|
var require2 = createRequire(import.meta.url);
|
|
@@ -13773,7 +13802,7 @@ function terminalHelperPath() {
|
|
|
13773
13802
|
return resolve4(dirname6(utilsPath), native.dir, "spawn-helper");
|
|
13774
13803
|
}
|
|
13775
13804
|
async function prepareTerminalHelper(path, fix) {
|
|
13776
|
-
const info = await
|
|
13805
|
+
const info = await stat9(path);
|
|
13777
13806
|
if (!info.isFile()) throw new Error(`\u7EC8\u7AEF\u8F85\u52A9\u7A0B\u5E8F\u4E0D\u662F\u666E\u901A\u6587\u4EF6\uFF1A${path}`);
|
|
13778
13807
|
try {
|
|
13779
13808
|
await access(path, constants4.X_OK);
|
|
@@ -13832,7 +13861,7 @@ var TerminalManager = class {
|
|
|
13832
13861
|
const count = [...this.terminals.values()].filter((t) => t.owner === owner).length;
|
|
13833
13862
|
if (count >= 8) throw new Error("Terminal limit reached");
|
|
13834
13863
|
const cwd = await this.workspace.path(project, typeof p.cwd === "string" ? p.cwd : ".");
|
|
13835
|
-
if (!(await
|
|
13864
|
+
if (!(await stat10(cwd)).isDirectory()) throw new Error(`\u7EC8\u7AEF\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u662F\u76EE\u5F55\uFF1A${cwd}`);
|
|
13836
13865
|
const shell = process.env.SHELL || (platform3() === "darwin" ? "/bin/zsh" : "/bin/sh");
|
|
13837
13866
|
try {
|
|
13838
13867
|
await access2(shell, constants5.X_OK);
|
|
@@ -13959,8 +13988,49 @@ var PlatformDesktopSession = class {
|
|
|
13959
13988
|
|
|
13960
13989
|
// src/remote-manager.ts
|
|
13961
13990
|
import { fork } from "node:child_process";
|
|
13962
|
-
import { randomBytes as randomBytes2, randomUUID as
|
|
13991
|
+
import { randomBytes as randomBytes2, randomUUID as randomUUID19 } from "node:crypto";
|
|
13992
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
13993
|
+
|
|
13994
|
+
// src/remote-native.ts
|
|
13995
|
+
import { chmod as chmod5, mkdir as mkdir9, readFile as readFile10, rename as rename5, rm as rm3, stat as stat11, writeFile as writeFile7 } from "node:fs/promises";
|
|
13996
|
+
import { join as join14 } from "node:path";
|
|
13963
13997
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
13998
|
+
import { createHash as createHash10, randomUUID as randomUUID18 } from "node:crypto";
|
|
13999
|
+
async function ensureRemoteHelper(stateDirectory, adapter = RemotePlatformRegistry.standard.resolve()) {
|
|
14000
|
+
await mkdir9(stateDirectory, { recursive: true, mode: 448 });
|
|
14001
|
+
const build = adapter.helperBuild();
|
|
14002
|
+
const source = fileURLToPath4(new URL(`../native/${build.source}`, import.meta.url));
|
|
14003
|
+
const target = join14(stateDirectory, "octopus-remote-desktop");
|
|
14004
|
+
const stamp = `${target}.sha256`;
|
|
14005
|
+
const fingerprint = createHash10("sha256").update(await readFile10(source)).update(JSON.stringify([adapter.context.platform, adapter.context.arch, build.flags])).digest("hex");
|
|
14006
|
+
const existing = await stat11(target).catch(() => void 0);
|
|
14007
|
+
if (existing?.isFile() && (existing.mode & 63) === 0 && await readFile10(stamp, "utf8").catch(() => "") === fingerprint) return target;
|
|
14008
|
+
const temporary = `${target}.${randomUUID18()}`, temporaryStamp = `${temporary}.sha256`;
|
|
14009
|
+
const args = build.args(source, temporary);
|
|
14010
|
+
try {
|
|
14011
|
+
await nativeDependencies.execute(build.compiler, args, { timeout: 6e4, maxBuffer: 16384 });
|
|
14012
|
+
await chmod5(temporary, 448);
|
|
14013
|
+
await writeFile7(temporaryStamp, fingerprint, { mode: 384 });
|
|
14014
|
+
await rename5(temporary, target);
|
|
14015
|
+
await rename5(temporaryStamp, stamp);
|
|
14016
|
+
return target;
|
|
14017
|
+
} catch {
|
|
14018
|
+
throw new Error(build.error);
|
|
14019
|
+
} finally {
|
|
14020
|
+
await Promise.all([rm3(temporary, { force: true }), rm3(temporaryStamp, { force: true })]);
|
|
14021
|
+
}
|
|
14022
|
+
}
|
|
14023
|
+
async function probeRemote(stateDirectory, config, adapter = RemotePlatformRegistry.standard.resolve()) {
|
|
14024
|
+
const platformError = adapter.platformError();
|
|
14025
|
+
if (platformError) throw new Error(platformError);
|
|
14026
|
+
await import("@roamhq/wrtc").catch(() => {
|
|
14027
|
+
throw new Error("WebRTC \u539F\u751F\u4F9D\u8D56\u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u65B0\u5B89\u88C5\u65B0\u7248 Proxy");
|
|
14028
|
+
});
|
|
14029
|
+
const helper = await ensureRemoteHelper(stateDirectory, adapter);
|
|
14030
|
+
const result = JSON.parse((await nativeDependencies.execute(helper, ["check"], { timeout: 5e3, maxBuffer: 4096 })).stdout);
|
|
14031
|
+
await adapter.checkDependencies(config);
|
|
14032
|
+
return { helper, ...result };
|
|
14033
|
+
}
|
|
13964
14034
|
|
|
13965
14035
|
// src/remote-stun.ts
|
|
13966
14036
|
import { Resolver } from "node:dns/promises";
|
|
@@ -14027,13 +14097,13 @@ async function discoverRemoteStunUrls(hubUrl, configured, resolve5 = resolvePubl
|
|
|
14027
14097
|
|
|
14028
14098
|
// src/remote-manager.ts
|
|
14029
14099
|
function spawnWorker(message) {
|
|
14030
|
-
const worker =
|
|
14100
|
+
const worker = fileURLToPath5(new URL(import.meta.url.endsWith(".ts") ? "./remote-worker.ts" : "./remote-worker.js", import.meta.url));
|
|
14031
14101
|
const child = fork(worker, [], { stdio: ["ignore", "ignore", "ignore", "ipc"], execArgv: import.meta.url.endsWith(".ts") ? ["--import", "tsx"] : [] });
|
|
14032
14102
|
child.send({ kind: "init", ...message });
|
|
14033
14103
|
return child;
|
|
14034
14104
|
}
|
|
14035
14105
|
var RemoteManager = class {
|
|
14036
|
-
constructor(host, desktop, desktopSession, send, touch, workerFactory = spawnWorker, probe = probeRemote, discoverStun = discoverRemoteStunUrls) {
|
|
14106
|
+
constructor(host, desktop, desktopSession, send, touch, workerFactory = spawnWorker, probe = probeRemote, discoverStun = discoverRemoteStunUrls, platformRegistry = RemotePlatformRegistry.standard) {
|
|
14037
14107
|
this.host = host;
|
|
14038
14108
|
this.desktop = desktop;
|
|
14039
14109
|
this.desktopSession = desktopSession;
|
|
@@ -14042,23 +14112,25 @@ var RemoteManager = class {
|
|
|
14042
14112
|
this.workerFactory = workerFactory;
|
|
14043
14113
|
this.probe = probe;
|
|
14044
14114
|
this.discoverStun = discoverStun;
|
|
14115
|
+
this.platform = platformRegistry.resolve();
|
|
14045
14116
|
this.config = RemoteConfigSchema.parse(host.desktopRemote ?? {});
|
|
14046
14117
|
}
|
|
14047
14118
|
current;
|
|
14048
14119
|
stopping = false;
|
|
14049
14120
|
config;
|
|
14121
|
+
platform;
|
|
14050
14122
|
stunUrls() {
|
|
14051
14123
|
return this.config.stunUrls ?? defaultRemoteStunUrls(this.host.hubUrl);
|
|
14052
14124
|
}
|
|
14053
14125
|
async status() {
|
|
14054
|
-
const base = { qualityModes: RemoteQualitySchema.options, platform:
|
|
14126
|
+
const base = { qualityModes: RemoteQualitySchema.options, platform: this.platform.context.platform, passwordInput: this.platform.passwordInput, enabled: this.config.enabled, busy: !!this.current, stunUrls: this.stunUrls(), budget: { ...REMOTE_DEFAULTS, totalBitrate: this.config.totalBitrate, videoMaxBitrate: this.config.videoMaxBitrate } };
|
|
14055
14127
|
if (!this.config.enabled) return { ...base, available: false, message: "\u4E3B\u673A\u5DF2\u5173\u95ED\u8FDC\u7A0B\u63A7\u5236" };
|
|
14056
14128
|
try {
|
|
14057
|
-
const permissions = await this.probe(this.host.stateDirectory, this.config);
|
|
14058
|
-
const
|
|
14059
|
-
const available =
|
|
14129
|
+
const permissions = await this.probe(this.host.stateDirectory, this.config, this.platform);
|
|
14130
|
+
const message = this.platform.unavailableReason(permissions);
|
|
14131
|
+
const available = message === void 0;
|
|
14060
14132
|
if (available) await this.desktopSession.sessionIdentity();
|
|
14061
|
-
return { ...base, available, permissions, ...!available ? { message
|
|
14133
|
+
return { ...base, available, permissions, ...!available ? { message } : {} };
|
|
14062
14134
|
} catch (error) {
|
|
14063
14135
|
return { ...base, available: false, message: error instanceof Error ? error.message : "\u8FDC\u63A7\u4F9D\u8D56\u4E0D\u53EF\u7528" };
|
|
14064
14136
|
}
|
|
@@ -14094,15 +14166,16 @@ var RemoteManager = class {
|
|
|
14094
14166
|
if (this.current) throw new Error("\u4E3B\u673A\u6B63\u5728\u88AB\u63A7\u5236");
|
|
14095
14167
|
const quality = RemoteQualitySchema.parse(payload.quality ?? "hd");
|
|
14096
14168
|
const budget = remoteSessionBudget(this.config, quality, payload.quality === void 0);
|
|
14097
|
-
const session = { remoteId:
|
|
14169
|
+
const session = { remoteId: randomUUID19(), owner, closed: false, signaling: Promise.resolve(), signals: 0, signalWindow: Date.now(), candidates: 0 };
|
|
14098
14170
|
this.current = session;
|
|
14099
14171
|
try {
|
|
14100
14172
|
const [permissions, stunUrls] = await Promise.all([
|
|
14101
|
-
this.probe(this.host.stateDirectory, this.config),
|
|
14173
|
+
this.probe(this.host.stateDirectory, this.config, this.platform),
|
|
14102
14174
|
this.discoverStun(this.host.hubUrl, this.config.stunUrls)
|
|
14103
14175
|
]);
|
|
14104
14176
|
if (!permissions.session || !permissions.capture || !permissions.input) throw new Error("\u8FDC\u63A7\u9700\u8981\u4E3B\u673A\u56FE\u5F62\u4F1A\u8BDD\u3001\u5C4F\u5E55\u5F55\u5236\u53CA\u8F85\u52A9\u529F\u80FD\u6743\u9650");
|
|
14105
|
-
|
|
14177
|
+
const unavailable = this.platform.unavailableReason(permissions);
|
|
14178
|
+
if (unavailable) throw new Error(unavailable);
|
|
14106
14179
|
const identity = await this.desktopSession.sessionIdentity();
|
|
14107
14180
|
const display = (await this.desktop.list()).find((d) => d.id === payload.displayId);
|
|
14108
14181
|
if (!display) throw new Error("\u663E\u793A\u5668\u5DF2\u65AD\u5F00\uFF0C\u8BF7\u5237\u65B0\u663E\u793A\u5668\u5217\u8868");
|
|
@@ -14173,16 +14246,16 @@ var RemoteManager = class {
|
|
|
14173
14246
|
};
|
|
14174
14247
|
|
|
14175
14248
|
// src/tool-selection.ts
|
|
14176
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
14177
|
-
import { access as access3, constants as constants6, readFile as
|
|
14249
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID20 } from "node:crypto";
|
|
14250
|
+
import { access as access3, constants as constants6, readFile as readFile11 } from "node:fs/promises";
|
|
14178
14251
|
import { platform as platform5 } from "node:os";
|
|
14179
14252
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
14180
14253
|
|
|
14181
14254
|
// src/node-resolution.ts
|
|
14182
|
-
import { realpath as realpath7, stat as
|
|
14255
|
+
import { realpath as realpath7, stat as stat12 } from "node:fs/promises";
|
|
14183
14256
|
import { execFile as execFile7 } from "node:child_process";
|
|
14184
14257
|
import { promisify as promisify7 } from "node:util";
|
|
14185
|
-
import { join as
|
|
14258
|
+
import { join as join15 } from "node:path";
|
|
14186
14259
|
import { homedir as homedir2, platform as platform4 } from "node:os";
|
|
14187
14260
|
var run4 = promisify7(execFile7);
|
|
14188
14261
|
var VERSION_MANAGED_SEGMENTS = /* @__PURE__ */ new Set([".nvm", "nvm", ".volta", "volta", ".fnm", "fnm", ".asdf", "asdf", ".mise", "mise"]);
|
|
@@ -14226,15 +14299,15 @@ async function resolveNodePath(options = {}) {
|
|
|
14226
14299
|
return { nodePath: null, source: "exec" };
|
|
14227
14300
|
}
|
|
14228
14301
|
function toolSearchDirs(kind) {
|
|
14229
|
-
const dirs = [...describeTool(kind).searchDirs,
|
|
14302
|
+
const dirs = [...describeTool(kind).searchDirs, join15(homedir2(), ".local", "bin"), join15(homedir2(), ".opencode", "bin"), join15(homedir2(), ".codex", "bin")];
|
|
14230
14303
|
return [...new Set(dirs)];
|
|
14231
14304
|
}
|
|
14232
14305
|
async function resolveCommandOnPath(name, extraDirs = []) {
|
|
14233
14306
|
const dirs = [...(process.env.PATH ?? "").split(":"), ...extraDirs].filter(Boolean);
|
|
14234
14307
|
for (const dir of new Set(dirs)) {
|
|
14235
|
-
const full =
|
|
14308
|
+
const full = join15(dir, name);
|
|
14236
14309
|
try {
|
|
14237
|
-
const info = await
|
|
14310
|
+
const info = await stat12(full);
|
|
14238
14311
|
if (info.isFile() && info.mode & 73) return full;
|
|
14239
14312
|
} catch {
|
|
14240
14313
|
}
|
|
@@ -14317,7 +14390,7 @@ var ToolSelection = class {
|
|
|
14317
14390
|
});
|
|
14318
14391
|
}
|
|
14319
14392
|
modelCommand(projectId) {
|
|
14320
|
-
return { type: "command", id:
|
|
14393
|
+
return { type: "command", id: randomUUID20(), botId: this.config.botId, operation: "configuration.models", projectId, payload: {} };
|
|
14321
14394
|
}
|
|
14322
14395
|
profiles(raw) {
|
|
14323
14396
|
return raw.toolProfiles && typeof raw.toolProfiles === "object" && !Array.isArray(raw.toolProfiles) ? raw.toolProfiles : {};
|
|
@@ -14359,7 +14432,7 @@ var ToolSelection = class {
|
|
|
14359
14432
|
if (!isDeepStrictEqual2(JSON.parse(JSON.stringify(disk.tool)), JSON.parse(JSON.stringify(this.config.tool))) || !isDeepStrictEqual2(disk.projects, this.config.projects) || disk.botId !== this.config.botId || disk.secretKey !== this.config.secretKey) {
|
|
14360
14433
|
throw new Error("Proxy \u914D\u7F6E\u5DF2\u5728\u4E3B\u673A\u4E0A\u88AB\u4FEE\u6539\uFF1B\u8BF7\u91CD\u542F Proxy \u540E\u518D\u5207\u6362\u667A\u80FD\u4F53");
|
|
14361
14434
|
}
|
|
14362
|
-
return JSON.parse(await
|
|
14435
|
+
return JSON.parse(await readFile11(this.configPath, "utf8"));
|
|
14363
14436
|
}
|
|
14364
14437
|
async release(instance) {
|
|
14365
14438
|
instance.adapter.close();
|
|
@@ -14376,8 +14449,8 @@ var ToolSelection = class {
|
|
|
14376
14449
|
// src/daemon.ts
|
|
14377
14450
|
async function runDaemon(configPath, authenticate = verifyHostPassword) {
|
|
14378
14451
|
const config = await loadConfig(configPath);
|
|
14379
|
-
await
|
|
14380
|
-
const workspace = new Workspace(config.projects), uploads = new Uploads(workspace,
|
|
14452
|
+
await mkdir10(config.stateDirectory, { recursive: true, mode: 448 });
|
|
14453
|
+
const workspace = new Workspace(config.projects), uploads = new Uploads(workspace, join16(config.stateDirectory, "uploads")), journal = new Journal(join16(config.stateDirectory, "journal"));
|
|
14381
14454
|
let secureChat;
|
|
14382
14455
|
function emit(projectId, event, data) {
|
|
14383
14456
|
secureChat?.publish(projectId, event, data);
|
|
@@ -14399,7 +14472,7 @@ async function runDaemon(configPath, authenticate = verifyHostPassword) {
|
|
|
14399
14472
|
});
|
|
14400
14473
|
return { runtime, adapter };
|
|
14401
14474
|
});
|
|
14402
|
-
const artifacts = new Artifacts(workspace,
|
|
14475
|
+
const artifacts = new Artifacts(workspace, join16(config.stateDirectory, "artifacts"), emit);
|
|
14403
14476
|
const projectRegistry = () => new ProjectRegistry(configPath, config, tools.current.runtime, tools.current.adapter, (resetProjects) => {
|
|
14404
14477
|
for (const id of resetProjects) terminals.closeProject(id);
|
|
14405
14478
|
});
|
|
@@ -14444,7 +14517,7 @@ async function runDaemon(configPath, authenticate = verifyHostPassword) {
|
|
|
14444
14517
|
workspace.project(project);
|
|
14445
14518
|
if (c.operation === "workspace.list") return p.paged === true ? workspace.listPage(project, String(p.path ?? "."), p.cursor === void 0 ? "" : p.cursor, p.limit === void 0 ? 200 : Number(p.limit)) : workspace.list(project, String(p.path ?? "."));
|
|
14446
14519
|
if (c.operation === "workspace.read") return workspace.read(project, String(p.path), Number(p.offset ?? 0), Number(p.length ?? 512 * 1024));
|
|
14447
|
-
if (c.operation === "workspace.diff") return workspaceDiff(workspace, project);
|
|
14520
|
+
if (c.operation === "workspace.diff") return workspaceDiff(workspace, project, { paged: p.paged === true, offset: Number(p.offset ?? 0), limit: Number(p.limit ?? 20) });
|
|
14448
14521
|
if (c.operation === "workspace.search") return workspace.search(project, String(p.query));
|
|
14449
14522
|
if (c.operation === "workspace.write") return workspace.write(project, String(p.path), String(p.content), String(p.expectedHash));
|
|
14450
14523
|
if (c.operation === "uploads.init") {
|
|
@@ -14458,9 +14531,9 @@ async function runDaemon(configPath, authenticate = verifyHostPassword) {
|
|
|
14458
14531
|
if (c.operation === "uploads.chunk") return uploads.chunk(project, String(p.id), Number(p.offset), String(p.data));
|
|
14459
14532
|
if (c.operation === "uploads.complete") return uploads.complete(project, String(p.id));
|
|
14460
14533
|
if (c.operation === "uploads.cancel") return uploads.cancel(project, String(p.id));
|
|
14461
|
-
if (c.operation === "artifacts.list") return artifacts.scan(project);
|
|
14534
|
+
if (c.operation === "artifacts.list") return p.paged === true ? artifacts.scanPage(project, Number(p.offset ?? 0), Number(p.limit ?? 20)) : artifacts.scan(project);
|
|
14462
14535
|
if (c.operation === "artifacts.history") return artifacts.history(project, String(p.path));
|
|
14463
|
-
if (c.operation === "artifacts.read") return artifacts.read(project, String(p.path), String(p.hash));
|
|
14536
|
+
if (c.operation === "artifacts.read") return p.preview === true ? artifacts.preview(project, String(p.path), String(p.hash)) : artifacts.read(project, String(p.path), String(p.hash));
|
|
14464
14537
|
if (c.operation === "artifacts.associate") {
|
|
14465
14538
|
if (!c.sessionId) throw new Error("Session required");
|
|
14466
14539
|
await adapter.session(project, c.sessionId);
|
|
@@ -4449,66 +4449,16 @@ var DesktopManager = class {
|
|
|
4449
4449
|
}
|
|
4450
4450
|
};
|
|
4451
4451
|
|
|
4452
|
-
// src/remote-
|
|
4452
|
+
// src/remote/native-process.ts
|
|
4453
4453
|
import { execFile as execFile2, spawn } from "node:child_process";
|
|
4454
4454
|
import { promisify as promisify2 } from "node:util";
|
|
4455
|
-
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename as rename2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
|
|
4456
|
-
import { join as join2 } from "node:path";
|
|
4457
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4458
|
-
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
4459
4455
|
var exec = promisify2(execFile2);
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
}
|
|
4466
|
-
}
|
|
4467
|
-
async function ensureRemoteHelper(stateDirectory) {
|
|
4468
|
-
await mkdir2(stateDirectory, { recursive: true, mode: 448 });
|
|
4469
|
-
const mac = process.platform === "darwin";
|
|
4470
|
-
const source = fileURLToPath2(new URL(mac ? "../native/remote-macos.m" : "../native/remote-x11.c", import.meta.url));
|
|
4471
|
-
const target = join2(stateDirectory, "octopus-remote-desktop");
|
|
4472
|
-
const stamp = `${target}.sha256`;
|
|
4473
|
-
const flags = mac ? ["-O2", "-fobjc-arc", "-mmacosx-version-min=12.3", "-framework", "Foundation", "-framework", "AppKit", "-framework", "ScreenCaptureKit", "-framework", "CoreMedia", "-framework", "CoreVideo", "-framework", "ApplicationServices", "-framework", "Carbon", "-framework", "IOKit", "-framework", "VideoToolbox"] : ["-O2", "-lX11", "-lXtst"];
|
|
4474
|
-
const fingerprint = createHash2("sha256").update(await readFile2(source)).update(JSON.stringify([process.platform, process.arch, flags])).digest("hex");
|
|
4475
|
-
const existing = await stat2(target).catch(() => void 0);
|
|
4476
|
-
if (existing?.isFile() && (existing.mode & 63) === 0 && await readFile2(stamp, "utf8").catch(() => "") === fingerprint) return target;
|
|
4477
|
-
const temporary = `${target}.${randomUUID2()}`, temporaryStamp = `${temporary}.sha256`;
|
|
4478
|
-
const args = mac ? [...flags, source, "-o", temporary] : ["-O2", source, "-o", temporary, "-lX11", "-lXtst"];
|
|
4479
|
-
try {
|
|
4480
|
-
await exec(mac ? "/usr/bin/clang" : "cc", args, { timeout: 6e4, maxBuffer: 16384 });
|
|
4481
|
-
await chmod2(temporary, 448);
|
|
4482
|
-
await writeFile2(temporaryStamp, fingerprint, { mode: 384 });
|
|
4483
|
-
await rename2(temporary, target);
|
|
4484
|
-
await rename2(temporaryStamp, stamp);
|
|
4485
|
-
return target;
|
|
4486
|
-
} catch {
|
|
4487
|
-
throw new Error(mac ? "\u8FDC\u63A7\u8F85\u52A9\u7A0B\u5E8F\u7F16\u8BD1\u5931\u8D25\uFF0C\u8BF7\u5B89\u88C5 Xcode Command Line Tools" : "\u8FDC\u63A7\u8F85\u52A9\u7A0B\u5E8F\u7F16\u8BD1\u5931\u8D25\uFF0C\u8BF7\u5B89\u88C5 cc\u3001libx11-dev\u3001libxtst-dev");
|
|
4488
|
-
} finally {
|
|
4489
|
-
await Promise.all([rm2(temporary, { force: true }), rm2(temporaryStamp, { force: true })]);
|
|
4490
|
-
}
|
|
4491
|
-
}
|
|
4492
|
-
async function probeRemote(stateDirectory, config) {
|
|
4493
|
-
const platformError = remotePlatformError();
|
|
4494
|
-
if (platformError) throw new Error(platformError);
|
|
4495
|
-
await import("@roamhq/wrtc").catch(() => {
|
|
4496
|
-
throw new Error("WebRTC \u539F\u751F\u4F9D\u8D56\u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u65B0\u5B89\u88C5\u65B0\u7248 Proxy");
|
|
4497
|
-
});
|
|
4498
|
-
const helper = await ensureRemoteHelper(stateDirectory);
|
|
4499
|
-
const result = JSON.parse((await exec(helper, ["check"], { timeout: 5e3, maxBuffer: 4096 })).stdout);
|
|
4500
|
-
if (process.platform === "linux") {
|
|
4501
|
-
const version = await exec(config.ffmpeg, ["-version"], { timeout: 5e3, maxBuffer: 32768 }).catch(() => {
|
|
4502
|
-
throw new Error("\u8BF7\u5B89\u88C5\u652F\u6301 x11grab \u7684 LGPL FFmpeg");
|
|
4503
|
-
});
|
|
4504
|
-
assertLgplFFmpeg(version.stdout);
|
|
4505
|
-
const devices = await exec(config.ffmpeg, ["-hide_banner", "-devices"], { timeout: 5e3, maxBuffer: 32768 });
|
|
4506
|
-
if (!devices.stdout.includes("x11grab")) throw new Error("FFmpeg \u7F3A\u5C11 x11grab \u91C7\u96C6\u652F\u6301");
|
|
4507
|
-
}
|
|
4508
|
-
return { helper, ...result };
|
|
4509
|
-
}
|
|
4510
|
-
function nativeProcess(helper, displayId, video) {
|
|
4511
|
-
return spawn(helper, ["serve", displayId, String(video.width), String(video.height), String(video.fps)], { stdio: ["pipe", "pipe", "pipe", "pipe"] });
|
|
4456
|
+
var nativeDependencies = {
|
|
4457
|
+
execute: async (file, args, options) => ({ stdout: String((await exec(file, args, options)).stdout) }),
|
|
4458
|
+
spawn
|
|
4459
|
+
};
|
|
4460
|
+
function nativeProcess(helper, displayId, video, dependencies = nativeDependencies) {
|
|
4461
|
+
return dependencies.spawn(helper, ["serve", displayId, String(video.width), String(video.height), String(video.fps)], { stdio: ["pipe", "pipe", "pipe", "pipe"] });
|
|
4512
4462
|
}
|
|
4513
4463
|
function terminateNative(child) {
|
|
4514
4464
|
if (!child) return;
|
|
@@ -4519,6 +4469,307 @@ function terminateNative(child) {
|
|
|
4519
4469
|
child.once("exit", () => clearTimeout(timer));
|
|
4520
4470
|
}
|
|
4521
4471
|
|
|
4472
|
+
// src/remote/native-session.ts
|
|
4473
|
+
var NativeSession = class {
|
|
4474
|
+
constructor(options, dependencies = nativeDependencies) {
|
|
4475
|
+
this.options = options;
|
|
4476
|
+
this.dependencies = dependencies;
|
|
4477
|
+
this.video = options.video;
|
|
4478
|
+
}
|
|
4479
|
+
helper;
|
|
4480
|
+
stopped = false;
|
|
4481
|
+
video;
|
|
4482
|
+
starting;
|
|
4483
|
+
start() {
|
|
4484
|
+
return this.starting ??= this.startOnce().catch((error) => {
|
|
4485
|
+
this.stop();
|
|
4486
|
+
throw error;
|
|
4487
|
+
});
|
|
4488
|
+
}
|
|
4489
|
+
async startOnce() {
|
|
4490
|
+
if (this.stopped) return;
|
|
4491
|
+
await this.prepare();
|
|
4492
|
+
if (this.stopped) return;
|
|
4493
|
+
const child = nativeProcess(this.options.helper, this.options.display.id, this.video, this.dependencies);
|
|
4494
|
+
this.helper = child;
|
|
4495
|
+
child.on("error", () => this.fail("\u4E3B\u673A\u8F93\u5165\u8F85\u52A9\u7A0B\u5E8F\u4E0D\u53EF\u7528"));
|
|
4496
|
+
child.on("exit", () => this.fail("\u4E3B\u673A\u6743\u9650\u3001\u663E\u793A\u5668\u6216\u56FE\u5F62\u4F1A\u8BDD\u5DF2\u53D8\u5316"));
|
|
4497
|
+
child.stdin?.on("error", () => this.fail("\u4E3B\u673A\u8F93\u5165\u8FDE\u63A5\u5DF2\u5173\u95ED"));
|
|
4498
|
+
let lines = "";
|
|
4499
|
+
child.stdout?.on("data", (chunk) => {
|
|
4500
|
+
if (this.stopped) return;
|
|
4501
|
+
lines += String(chunk);
|
|
4502
|
+
if (lines.length > 15e5) {
|
|
4503
|
+
this.fail("\u4E3B\u673A\u526A\u8D34\u677F\u8D85\u8FC7\u4E0A\u9650");
|
|
4504
|
+
return;
|
|
4505
|
+
}
|
|
4506
|
+
let end;
|
|
4507
|
+
while (!this.stopped && (end = lines.indexOf("\n")) >= 0) {
|
|
4508
|
+
const line = lines.slice(0, end);
|
|
4509
|
+
lines = lines.slice(end + 1);
|
|
4510
|
+
try {
|
|
4511
|
+
const message = JSON.parse(line);
|
|
4512
|
+
if (message.kind === "error") {
|
|
4513
|
+
this.fail("\u4E3B\u673A\u6743\u9650\u3001\u663E\u793A\u5668\u6216\u56FE\u5F62\u4F1A\u8BDD\u5DF2\u53D8\u5316");
|
|
4514
|
+
return;
|
|
4515
|
+
}
|
|
4516
|
+
this.options.onMessage(message);
|
|
4517
|
+
} catch {
|
|
4518
|
+
this.fail("\u4E3B\u673A\u8F85\u52A9\u7A0B\u5E8F\u8FD4\u56DE\u65E0\u6548\u6570\u636E");
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
});
|
|
4522
|
+
this.startCapture(child);
|
|
4523
|
+
}
|
|
4524
|
+
async prepare() {
|
|
4525
|
+
}
|
|
4526
|
+
stopCapture() {
|
|
4527
|
+
}
|
|
4528
|
+
fail(message) {
|
|
4529
|
+
if (this.stopped) return;
|
|
4530
|
+
this.stop();
|
|
4531
|
+
this.options.onError(message);
|
|
4532
|
+
}
|
|
4533
|
+
writeCommand(line) {
|
|
4534
|
+
if (this.stopped || !this.helper?.stdin || this.helper.stdin.destroyed) return;
|
|
4535
|
+
if (this.helper.stdin.writableLength > 2 * 1024 * 1024) {
|
|
4536
|
+
this.fail("\u4E3B\u673A\u8F93\u5165\u5904\u7406\u8D85\u65F6");
|
|
4537
|
+
return;
|
|
4538
|
+
}
|
|
4539
|
+
this.helper.stdin.write(`${line}
|
|
4540
|
+
`);
|
|
4541
|
+
}
|
|
4542
|
+
input(input) {
|
|
4543
|
+
switch (input.kind) {
|
|
4544
|
+
case "pointer":
|
|
4545
|
+
this.writeCommand(`pointer ${input.x} ${input.y}`);
|
|
4546
|
+
break;
|
|
4547
|
+
case "button":
|
|
4548
|
+
this.writeCommand(`button ${input.x} ${input.y} ${input.button} ${input.down ? 1 : 0}`);
|
|
4549
|
+
break;
|
|
4550
|
+
case "wheel":
|
|
4551
|
+
this.writeCommand(`wheel ${input.x} ${input.y} ${Math.round(input.dx)} ${Math.round(input.dy)}`);
|
|
4552
|
+
break;
|
|
4553
|
+
case "key":
|
|
4554
|
+
this.writeCommand(`key ${input.code} ${input.down ? 1 : 0}`);
|
|
4555
|
+
break;
|
|
4556
|
+
case "text":
|
|
4557
|
+
this.writeCommand(`text ${input.terminal ? 1 : 0} ${Buffer.from(input.text).toString("base64")}`);
|
|
4558
|
+
break;
|
|
4559
|
+
case "release":
|
|
4560
|
+
this.writeCommand("release");
|
|
4561
|
+
break;
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
stop() {
|
|
4565
|
+
if (this.stopped) return;
|
|
4566
|
+
this.stopped = true;
|
|
4567
|
+
this.stopCapture();
|
|
4568
|
+
const helper = this.helper;
|
|
4569
|
+
this.helper = void 0;
|
|
4570
|
+
terminateNative(helper);
|
|
4571
|
+
}
|
|
4572
|
+
};
|
|
4573
|
+
|
|
4574
|
+
// src/remote/platform-adapter.ts
|
|
4575
|
+
function unsupportedPlatform(context) {
|
|
4576
|
+
return `\u8FDC\u63A7\u4E0D\u652F\u6301\u5F53\u524D\u5E73\u53F0 ${context.platform}/${context.arch}\uFF1B\u9700\u8981 macOS 12.3+ \u6216 Linux X11\uFF08x64/ARM64\uFF09`;
|
|
4577
|
+
}
|
|
4578
|
+
function missingPermissions(permissions) {
|
|
4579
|
+
if (!permissions.session || !permissions.capture || !permissions.input) return "\u8BF7\u68C0\u67E5\u4E3B\u673A\u56FE\u5F62\u4F1A\u8BDD\uFF0C\u5E76\u4E3A\u5B9E\u9645\u8FD0\u884C Proxy \u7684\u7A0B\u5E8F\u6388\u6743\u5C4F\u5E55\u5F55\u5236\u548C\u8F85\u52A9\u529F\u80FD";
|
|
4580
|
+
}
|
|
4581
|
+
|
|
4582
|
+
// src/remote/linux-x11-adapter.ts
|
|
4583
|
+
var LinuxX11RemoteAdapter = class {
|
|
4584
|
+
constructor(context, dependencies = nativeDependencies) {
|
|
4585
|
+
this.context = context;
|
|
4586
|
+
this.dependencies = dependencies;
|
|
4587
|
+
}
|
|
4588
|
+
passwordInput = false;
|
|
4589
|
+
platformError() {
|
|
4590
|
+
if (!["arm64", "x64"].includes(this.context.arch)) return unsupportedPlatform(this.context);
|
|
4591
|
+
const env = this.context.env;
|
|
4592
|
+
if (env.WAYLAND_DISPLAY || env.XDG_SESSION_TYPE === "wayland") return "\u8FDC\u63A7\u6682\u4E0D\u652F\u6301 Wayland\uFF0C\u8BF7\u4F7F\u7528 X11 \u684C\u9762\u4F1A\u8BDD";
|
|
4593
|
+
if (!env.DISPLAY) return "Proxy \u672A\u8FDE\u63A5 X11 \u56FE\u5F62\u4F1A\u8BDD\uFF1B\u8BF7\u4E3A\u5B9E\u9645\u8FD0\u884C Proxy \u7684\u670D\u52A1\u8BBE\u7F6E\u684C\u9762\u7528\u6237\u7684 DISPLAY \u548C XAUTHORITY\uFF0C\u5BB9\u5668\u4E2D\u8BF7\u5148\u542F\u52A8 Xvfb/\u684C\u9762\u670D\u52A1";
|
|
4594
|
+
}
|
|
4595
|
+
helperBuild() {
|
|
4596
|
+
return { source: "remote-x11.c", compiler: "cc", flags: ["-O2", "-lX11", "-lXtst"], args: (source, target) => ["-O2", source, "-o", target, "-lX11", "-lXtst"], error: "\u8FDC\u63A7\u8F85\u52A9\u7A0B\u5E8F\u7F16\u8BD1\u5931\u8D25\uFF0C\u8BF7\u5B89\u88C5 cc\u3001libx11-dev\u3001libxtst-dev" };
|
|
4597
|
+
}
|
|
4598
|
+
async checkDependencies(config) {
|
|
4599
|
+
const execute = this.dependencies.execute;
|
|
4600
|
+
const version = await execute(config.ffmpeg, ["-version"], { timeout: 5e3, maxBuffer: 32768 }).catch(() => {
|
|
4601
|
+
throw new Error("\u8BF7\u5B89\u88C5\u652F\u6301 x11grab \u7684 LGPL FFmpeg");
|
|
4602
|
+
});
|
|
4603
|
+
assertLgplFFmpeg(version.stdout);
|
|
4604
|
+
const devices = await execute(config.ffmpeg, ["-hide_banner", "-devices"], { timeout: 5e3, maxBuffer: 32768 });
|
|
4605
|
+
if (!devices.stdout.includes("x11grab")) throw new Error("FFmpeg \u7F3A\u5C11 x11grab \u91C7\u96C6\u652F\u6301");
|
|
4606
|
+
}
|
|
4607
|
+
unavailableReason = missingPermissions;
|
|
4608
|
+
createSession(options) {
|
|
4609
|
+
return new LinuxX11RemoteSession(options, this.context, this.dependencies);
|
|
4610
|
+
}
|
|
4611
|
+
};
|
|
4612
|
+
var LinuxX11RemoteSession = class extends NativeSession {
|
|
4613
|
+
constructor(options, context, dependencies) {
|
|
4614
|
+
super(options, dependencies);
|
|
4615
|
+
this.context = context;
|
|
4616
|
+
}
|
|
4617
|
+
capture;
|
|
4618
|
+
geometry;
|
|
4619
|
+
async prepare() {
|
|
4620
|
+
const displays = parseXrandr((await this.dependencies.execute("xrandr", ["--listactivemonitors"], { timeout: 5e3 })).stdout);
|
|
4621
|
+
if (this.stopped) return;
|
|
4622
|
+
const display = displays.find((d) => d.id === this.options.display.id);
|
|
4623
|
+
if (!display || display.width !== this.options.display.width || display.height !== this.options.display.height) throw new Error("display");
|
|
4624
|
+
this.geometry = { x: display.x, y: display.y, width: display.width, height: display.height };
|
|
4625
|
+
}
|
|
4626
|
+
startCapture(_child) {
|
|
4627
|
+
const g = this.geometry;
|
|
4628
|
+
this.writeCommand(`geometry ${g.x} ${g.y} ${g.width} ${g.height}`);
|
|
4629
|
+
this.captureFrames();
|
|
4630
|
+
}
|
|
4631
|
+
captureFrames() {
|
|
4632
|
+
this.stopCapture();
|
|
4633
|
+
if (this.stopped) return;
|
|
4634
|
+
const g = this.geometry, { width, height, fps } = this.video;
|
|
4635
|
+
const child = this.dependencies.spawn(this.options.config.ffmpeg, ["-nostdin", "-hide_banner", "-loglevel", "error", "-f", "x11grab", "-framerate", String(fps), "-video_size", `${g.width}x${g.height}`, "-grab_x", String(g.x), "-grab_y", String(g.y), "-draw_mouse", "1", "-i", this.context.env.DISPLAY, "-vf", `scale=${width}:${height}`, "-pix_fmt", "yuv420p", "-f", "rawvideo", "pipe:1"], { stdio: ["ignore", "pipe", "ignore"], env: this.context.env });
|
|
4636
|
+
this.capture = child;
|
|
4637
|
+
const current = () => !this.stopped && this.capture === child;
|
|
4638
|
+
let buffer = Buffer.alloc(0);
|
|
4639
|
+
const length = width * height * 1.5;
|
|
4640
|
+
child.stdout.on("data", (chunk) => {
|
|
4641
|
+
if (!current()) return;
|
|
4642
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
4643
|
+
if (buffer.length > length * 4) {
|
|
4644
|
+
this.fail("\u4E3B\u673A\u91C7\u96C6\u7F13\u51B2\u533A\u8D85\u9650");
|
|
4645
|
+
return;
|
|
4646
|
+
}
|
|
4647
|
+
if (buffer.length >= length) {
|
|
4648
|
+
const count = Math.floor(buffer.length / length), frame = buffer.subarray((count - 1) * length, count * length);
|
|
4649
|
+
buffer = buffer.subarray(count * length);
|
|
4650
|
+
this.options.onFrame({ width, height, data: new Uint8Array(frame) });
|
|
4651
|
+
}
|
|
4652
|
+
});
|
|
4653
|
+
child.on("error", () => {
|
|
4654
|
+
if (current()) this.fail("FFmpeg \u91C7\u96C6\u5931\u8D25");
|
|
4655
|
+
});
|
|
4656
|
+
child.on("exit", () => {
|
|
4657
|
+
if (current()) this.fail("\u663E\u793A\u5668\u5DF2\u53D8\u5316\u6216 FFmpeg \u91C7\u96C6\u5931\u8D25");
|
|
4658
|
+
});
|
|
4659
|
+
}
|
|
4660
|
+
setVideo(video) {
|
|
4661
|
+
this.video = video;
|
|
4662
|
+
if (this.helper && !this.stopped) this.captureFrames();
|
|
4663
|
+
}
|
|
4664
|
+
stopCapture() {
|
|
4665
|
+
const child = this.capture;
|
|
4666
|
+
this.capture = void 0;
|
|
4667
|
+
terminateNative(child);
|
|
4668
|
+
}
|
|
4669
|
+
};
|
|
4670
|
+
|
|
4671
|
+
// src/remote/macos-adapter.ts
|
|
4672
|
+
var MAC_LOCK_CAPTURE_ERROR = "\u9501\u5C4F\u8FDC\u63A7\u9700\u8981 macOS 14 \u53CA\u4EE5\u4E0A\u53CA\u65B0\u7248 Xcode Command Line Tools\uFF1B\u5F53\u524D\u4E3B\u673A\u8BF7\u5148\u672C\u5730\u89E3\u9501";
|
|
4673
|
+
var MacOSRemoteAdapter = class {
|
|
4674
|
+
constructor(context, dependencies = nativeDependencies) {
|
|
4675
|
+
this.context = context;
|
|
4676
|
+
this.dependencies = dependencies;
|
|
4677
|
+
}
|
|
4678
|
+
passwordInput = true;
|
|
4679
|
+
platformError() {
|
|
4680
|
+
if (!["arm64", "x64"].includes(this.context.arch)) return unsupportedPlatform(this.context);
|
|
4681
|
+
}
|
|
4682
|
+
helperBuild() {
|
|
4683
|
+
const flags = ["-O2", "-fobjc-arc", "-mmacosx-version-min=12.3", "-framework", "Foundation", "-framework", "AppKit", "-framework", "ScreenCaptureKit", "-framework", "CoreMedia", "-framework", "CoreVideo", "-framework", "ApplicationServices", "-framework", "Carbon", "-framework", "IOKit", "-framework", "VideoToolbox"];
|
|
4684
|
+
return { source: "remote-macos.m", compiler: "/usr/bin/clang", flags, args: (source, target) => [...flags, source, "-o", target], error: "\u8FDC\u63A7\u8F85\u52A9\u7A0B\u5E8F\u7F16\u8BD1\u5931\u8D25\uFF0C\u8BF7\u5B89\u88C5 Xcode Command Line Tools" };
|
|
4685
|
+
}
|
|
4686
|
+
async checkDependencies(_config) {
|
|
4687
|
+
}
|
|
4688
|
+
unavailableReason(permissions) {
|
|
4689
|
+
if (permissions.locked === true && permissions.lockCapture === false) return MAC_LOCK_CAPTURE_ERROR;
|
|
4690
|
+
return missingPermissions(permissions);
|
|
4691
|
+
}
|
|
4692
|
+
createSession(options) {
|
|
4693
|
+
return new MacOSRemoteSession(options, this.dependencies);
|
|
4694
|
+
}
|
|
4695
|
+
};
|
|
4696
|
+
var MacOSRemoteSession = class extends NativeSession {
|
|
4697
|
+
startCapture(child) {
|
|
4698
|
+
const stream = child.stdio[3];
|
|
4699
|
+
let buffer = Buffer.alloc(0);
|
|
4700
|
+
stream.on("data", (chunk) => {
|
|
4701
|
+
if (this.stopped) return;
|
|
4702
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
4703
|
+
if (buffer.length > REMOTE_DEFAULTS.maxWidth * REMOTE_DEFAULTS.maxHeight * 3 + 24) {
|
|
4704
|
+
this.fail("\u8FDC\u63A7\u753B\u9762\u7F13\u51B2\u533A\u8D85\u9650");
|
|
4705
|
+
return;
|
|
4706
|
+
}
|
|
4707
|
+
let latest;
|
|
4708
|
+
while (buffer.length >= 12) {
|
|
4709
|
+
const width = buffer.readUInt32BE(0), height = buffer.readUInt32BE(4), length = buffer.readUInt32BE(8);
|
|
4710
|
+
if (!width || !height || width > REMOTE_DEFAULTS.maxWidth || height > REMOTE_DEFAULTS.maxHeight || width % 2 || height % 2 || length !== width * height * 1.5) {
|
|
4711
|
+
this.fail("\u4E3B\u673A\u753B\u9762\u683C\u5F0F\u65E0\u6548");
|
|
4712
|
+
return;
|
|
4713
|
+
}
|
|
4714
|
+
if (buffer.length < length + 12) break;
|
|
4715
|
+
latest = { width, height, data: new Uint8Array(buffer.subarray(12, 12 + length)) };
|
|
4716
|
+
buffer = buffer.subarray(12 + length);
|
|
4717
|
+
}
|
|
4718
|
+
if (latest) this.options.onFrame(latest);
|
|
4719
|
+
});
|
|
4720
|
+
}
|
|
4721
|
+
setVideo(video) {
|
|
4722
|
+
this.video = video;
|
|
4723
|
+
this.writeCommand(`video ${video.width} ${video.height} ${video.fps}`);
|
|
4724
|
+
}
|
|
4725
|
+
input(input) {
|
|
4726
|
+
if (input.kind === "password") this.writeCommand(`password ${Buffer.from(input.text).toString("base64")}`);
|
|
4727
|
+
else super.input(input);
|
|
4728
|
+
}
|
|
4729
|
+
};
|
|
4730
|
+
|
|
4731
|
+
// src/remote/unsupported-adapter.ts
|
|
4732
|
+
var UnsupportedRemoteAdapter = class {
|
|
4733
|
+
constructor(context) {
|
|
4734
|
+
this.context = context;
|
|
4735
|
+
}
|
|
4736
|
+
passwordInput = false;
|
|
4737
|
+
platformError() {
|
|
4738
|
+
return unsupportedPlatform(this.context);
|
|
4739
|
+
}
|
|
4740
|
+
helperBuild() {
|
|
4741
|
+
throw new Error(this.platformError());
|
|
4742
|
+
}
|
|
4743
|
+
async checkDependencies(_config) {
|
|
4744
|
+
throw new Error(this.platformError());
|
|
4745
|
+
}
|
|
4746
|
+
unavailableReason(_permissions) {
|
|
4747
|
+
return this.platformError();
|
|
4748
|
+
}
|
|
4749
|
+
createSession(_options) {
|
|
4750
|
+
throw new Error(this.platformError());
|
|
4751
|
+
}
|
|
4752
|
+
};
|
|
4753
|
+
|
|
4754
|
+
// src/remote/platform-registry.ts
|
|
4755
|
+
var RemotePlatformRegistry = class _RemotePlatformRegistry {
|
|
4756
|
+
static standard = new _RemotePlatformRegistry({
|
|
4757
|
+
darwin: (context) => new MacOSRemoteAdapter(context),
|
|
4758
|
+
linux: (context) => new LinuxX11RemoteAdapter(context)
|
|
4759
|
+
});
|
|
4760
|
+
factories;
|
|
4761
|
+
constructor(factories) {
|
|
4762
|
+
this.factories = Object.freeze({ ...factories });
|
|
4763
|
+
}
|
|
4764
|
+
withAdapter(platform, factory) {
|
|
4765
|
+
return new _RemotePlatformRegistry({ ...this.factories, [platform]: factory });
|
|
4766
|
+
}
|
|
4767
|
+
resolve(context = { platform: process.platform, arch: process.arch, env: process.env }) {
|
|
4768
|
+
const factory = Object.hasOwn(this.factories, context.platform) ? this.factories[context.platform] : void 0;
|
|
4769
|
+
return factory?.(context) ?? new UnsupportedRemoteAdapter(context);
|
|
4770
|
+
}
|
|
4771
|
+
};
|
|
4772
|
+
|
|
4522
4773
|
export {
|
|
4523
4774
|
__commonJS,
|
|
4524
4775
|
__toESM,
|
|
@@ -4539,10 +4790,8 @@ export {
|
|
|
4539
4790
|
DESKTOP_PERMISSION_GUIDANCE,
|
|
4540
4791
|
requestDesktopPermission,
|
|
4541
4792
|
checkDesktop,
|
|
4542
|
-
parseXrandr,
|
|
4543
4793
|
PlatformDesktop,
|
|
4544
4794
|
DesktopManager,
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
terminateNative
|
|
4795
|
+
nativeDependencies,
|
|
4796
|
+
RemotePlatformRegistry
|
|
4548
4797
|
};
|
package/dist/cli.js
CHANGED
|
@@ -35,13 +35,13 @@ import {
|
|
|
35
35
|
supportedPlatform,
|
|
36
36
|
systemdUnitPath,
|
|
37
37
|
toolSearchDirs
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-6G2W4KWC.js";
|
|
39
39
|
import {
|
|
40
40
|
DESKTOP_PERMISSION_GUIDANCE,
|
|
41
41
|
PlatformDesktop,
|
|
42
42
|
checkDesktop,
|
|
43
43
|
requestDesktopPermission
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-OTKNRBZ7.js";
|
|
45
45
|
|
|
46
46
|
// src/cli.ts
|
|
47
47
|
import { spawn as spawn2 } from "node:child_process";
|
package/dist/main.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import {
|
|
3
3
|
ConfigError,
|
|
4
4
|
runDaemon
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-6G2W4KWC.js";
|
|
6
|
+
import "./chunk-OTKNRBZ7.js";
|
|
7
7
|
|
|
8
8
|
// src/main.ts
|
|
9
9
|
runDaemon(process.env.OCTOPUS_PROXY_CONFIG ?? "proxy.json").catch((error) => {
|
package/dist/remote-worker.js
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
InputSchema,
|
|
4
|
-
REMOTE_DEFAULTS,
|
|
5
4
|
REMOTE_LEGACY_PROFILE,
|
|
6
5
|
REMOTE_QUALITY_PROFILES,
|
|
7
6
|
RemoteInputOrder,
|
|
7
|
+
RemotePlatformRegistry,
|
|
8
8
|
SignalSchema,
|
|
9
9
|
assertDirectSignal,
|
|
10
10
|
capVideoSdp,
|
|
11
11
|
fitVideo,
|
|
12
|
-
nativeProcess,
|
|
13
|
-
parseXrandr,
|
|
14
12
|
remoteResolutionTiers,
|
|
15
|
-
remoteSessionBudget
|
|
16
|
-
|
|
17
|
-
} from "./chunk-UFLXQ4HQ.js";
|
|
13
|
+
remoteSessionBudget
|
|
14
|
+
} from "./chunk-OTKNRBZ7.js";
|
|
18
15
|
|
|
19
16
|
// src/remote-worker.ts
|
|
20
17
|
import rtc from "@roamhq/wrtc";
|
|
21
|
-
import { spawn, execFile } from "node:child_process";
|
|
22
|
-
import { promisify } from "node:util";
|
|
23
18
|
import { timingSafeEqual } from "node:crypto";
|
|
24
19
|
|
|
25
20
|
// src/remote-budget.ts
|
|
@@ -158,7 +153,6 @@ var RemoteLiveness = class {
|
|
|
158
153
|
};
|
|
159
154
|
|
|
160
155
|
// src/remote-worker.ts
|
|
161
|
-
var exec = promisify(execFile);
|
|
162
156
|
var post = (kind, data = {}) => {
|
|
163
157
|
if (process.connected) process.send?.({ kind, ...data });
|
|
164
158
|
};
|
|
@@ -207,8 +201,7 @@ var RemotePeer = class {
|
|
|
207
201
|
source = new rtc.nonstandard.RTCVideoSource({ isScreencast: true });
|
|
208
202
|
track = this.source.createTrack();
|
|
209
203
|
sender;
|
|
210
|
-
|
|
211
|
-
capture;
|
|
204
|
+
native;
|
|
212
205
|
channels = /* @__PURE__ */ new Map();
|
|
213
206
|
ready = false;
|
|
214
207
|
ended = false;
|
|
@@ -239,7 +232,6 @@ var RemotePeer = class {
|
|
|
239
232
|
legacy;
|
|
240
233
|
quality;
|
|
241
234
|
budget;
|
|
242
|
-
geometry;
|
|
243
235
|
video;
|
|
244
236
|
signal(raw) {
|
|
245
237
|
let stage = "validation";
|
|
@@ -379,155 +371,54 @@ var RemotePeer = class {
|
|
|
379
371
|
if (channel?.readyState === "open") channel.send(JSON.stringify({ ...value, remoteId: this.init.remoteId, revision: 1 }));
|
|
380
372
|
}
|
|
381
373
|
async startNative() {
|
|
382
|
-
if (process.platform === "linux") {
|
|
383
|
-
const displays = parseXrandr((await exec("xrandr", ["--listactivemonitors"], { timeout: 5e3 })).stdout);
|
|
384
|
-
if (this.ended) return;
|
|
385
|
-
const display = displays.find((d) => d.id === this.init.display.id);
|
|
386
|
-
if (!display || display.width !== this.init.display.width || display.height !== this.init.display.height) throw new Error("display");
|
|
387
|
-
this.geometry = { x: display.x, y: display.y, width: display.width, height: display.height };
|
|
388
|
-
}
|
|
389
374
|
if (this.ended) return;
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
if (lines.length > 15e5) {
|
|
401
|
-
this.stop("\u4E3B\u673A\u526A\u8D34\u677F\u8D85\u8FC7\u4E0A\u9650");
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
let end;
|
|
405
|
-
while ((end = lines.indexOf("\n")) >= 0) {
|
|
406
|
-
const line = lines.slice(0, end);
|
|
407
|
-
lines = lines.slice(end + 1);
|
|
408
|
-
try {
|
|
409
|
-
const message = JSON.parse(line);
|
|
410
|
-
if (message.kind === "clipboard" && typeof message.id === "string" && typeof message.data === "string") {
|
|
411
|
-
const callback = this.nativePending.get(message.id);
|
|
412
|
-
this.nativePending.delete(message.id);
|
|
413
|
-
callback?.(Buffer.from(message.data, "base64").toString("utf8"));
|
|
414
|
-
}
|
|
415
|
-
if (message.kind === "clipboard-written" && this.pendingClipboard?.id === message.id) {
|
|
416
|
-
this.pendingClipboard = void 0;
|
|
417
|
-
this.send("clipboard", { kind: "clipboard.done", id: message.id });
|
|
418
|
-
}
|
|
419
|
-
if (message.kind === "clipboard-error") {
|
|
420
|
-
if (typeof message.id === "string") this.nativePending.delete(message.id);
|
|
421
|
-
this.pendingClipboard = void 0;
|
|
422
|
-
this.send("clipboard", { kind: "clipboard.error", id: message.id, message: "\u4E3B\u673A\u526A\u8D34\u677F\u8BFB\u53D6\u5931\u8D25\u6216\u8D85\u8FC7 1 MiB" });
|
|
423
|
-
}
|
|
424
|
-
if (message.kind === "input-error") this.send("control", { kind: "input.error", message: "\u4E3B\u673A\u9501\u5C4F\u72B6\u6001\u5DF2\u53D8\u5316\uFF0C\u5DF2\u505C\u6B62\u8F93\u5165\u5BC6\u7801\uFF1B\u8BF7\u68C0\u67E5\u8FDC\u7AEF\u754C\u9762" });
|
|
425
|
-
if (message.kind === "error") this.stop("\u4E3B\u673A\u6743\u9650\u3001\u663E\u793A\u5668\u6216\u56FE\u5F62\u4F1A\u8BDD\u5DF2\u53D8\u5316");
|
|
426
|
-
} catch {
|
|
427
|
-
this.stop("\u4E3B\u673A\u8F85\u52A9\u7A0B\u5E8F\u8FD4\u56DE\u65E0\u6548\u6570\u636E");
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
if (process.platform === "darwin") this.macFrames(child.stdio[3]);
|
|
432
|
-
else {
|
|
433
|
-
const g = this.geometry;
|
|
434
|
-
this.writeNative(`geometry ${g.x} ${g.y} ${g.width} ${g.height}`);
|
|
435
|
-
this.linuxFrames();
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
macFrames(stream) {
|
|
439
|
-
let buffer = Buffer.alloc(0);
|
|
440
|
-
stream.on("data", (chunk) => {
|
|
441
|
-
if (this.ended) return;
|
|
442
|
-
buffer = Buffer.concat([buffer, chunk]);
|
|
443
|
-
if (buffer.length > REMOTE_DEFAULTS.maxWidth * REMOTE_DEFAULTS.maxHeight * 3 + 24) {
|
|
444
|
-
this.stop("\u8FDC\u63A7\u753B\u9762\u7F13\u51B2\u533A\u8D85\u9650");
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
let latest;
|
|
448
|
-
while (buffer.length >= 12) {
|
|
449
|
-
const width = buffer.readUInt32BE(0), height = buffer.readUInt32BE(4), length = buffer.readUInt32BE(8);
|
|
450
|
-
if (!width || !height || width > REMOTE_DEFAULTS.maxWidth || height > REMOTE_DEFAULTS.maxHeight || width % 2 || height % 2 || length !== width * height * 1.5) {
|
|
451
|
-
this.stop("\u4E3B\u673A\u753B\u9762\u683C\u5F0F\u65E0\u6548");
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
if (buffer.length < length + 12) break;
|
|
455
|
-
latest = { width, height, data: new Uint8Array(buffer.subarray(12, 12 + length)) };
|
|
456
|
-
buffer = buffer.subarray(12 + length);
|
|
457
|
-
}
|
|
458
|
-
if (latest && this.direct) {
|
|
459
|
-
this.firstFrameAt ||= Date.now();
|
|
460
|
-
this.frame(latest);
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
linuxFrames() {
|
|
465
|
-
const old = this.capture;
|
|
466
|
-
this.capture = void 0;
|
|
467
|
-
old?.kill("SIGTERM");
|
|
468
|
-
const g = this.geometry, { width, height, fps } = this.video;
|
|
469
|
-
const child = spawn(this.init.config.ffmpeg, ["-nostdin", "-hide_banner", "-loglevel", "error", "-f", "x11grab", "-framerate", String(fps), "-video_size", `${g.width}x${g.height}`, "-grab_x", String(g.x), "-grab_y", String(g.y), "-draw_mouse", "1", "-i", process.env.DISPLAY, "-vf", `scale=${width}:${height}`, "-pix_fmt", "yuv420p", "-f", "rawvideo", "pipe:1"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
470
|
-
this.capture = child;
|
|
471
|
-
let buffer = Buffer.alloc(0);
|
|
472
|
-
const length = width * height * 1.5;
|
|
473
|
-
child.stdout.on("data", (chunk) => {
|
|
474
|
-
if (this.capture !== child || this.ended) return;
|
|
475
|
-
buffer = Buffer.concat([buffer, chunk]);
|
|
476
|
-
if (buffer.length > length * 4) {
|
|
477
|
-
this.stop("\u4E3B\u673A\u91C7\u96C6\u7F13\u51B2\u533A\u8D85\u9650");
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
if (buffer.length >= length) {
|
|
481
|
-
const count = Math.floor(buffer.length / length), frame = buffer.subarray((count - 1) * length, count * length);
|
|
482
|
-
if (this.direct) {
|
|
375
|
+
const adapter = RemotePlatformRegistry.standard.resolve();
|
|
376
|
+
const error = adapter.platformError();
|
|
377
|
+
if (error) throw new Error(error);
|
|
378
|
+
const session = adapter.createSession({
|
|
379
|
+
helper: this.init.helper,
|
|
380
|
+
display: this.init.display,
|
|
381
|
+
video: this.video,
|
|
382
|
+
config: this.init.config,
|
|
383
|
+
onFrame: (frame) => {
|
|
384
|
+
if (!this.ended && this.direct) {
|
|
483
385
|
this.firstFrameAt ||= Date.now();
|
|
484
|
-
this.frame(
|
|
386
|
+
this.frame(frame);
|
|
485
387
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (this.capture === child) this.stop("\u663E\u793A\u5668\u5DF2\u53D8\u5316\u6216 FFmpeg \u91C7\u96C6\u5931\u8D25");
|
|
388
|
+
},
|
|
389
|
+
onMessage: (message) => {
|
|
390
|
+
if (!this.ended) this.nativeMessage(message);
|
|
391
|
+
},
|
|
392
|
+
onError: (message) => this.stop(message)
|
|
492
393
|
});
|
|
394
|
+
this.native = session;
|
|
395
|
+
await session.start();
|
|
396
|
+
}
|
|
397
|
+
nativeMessage(message) {
|
|
398
|
+
if (message.kind === "clipboard" && typeof message.id === "string" && typeof message.data === "string") {
|
|
399
|
+
const callback = this.nativePending.get(message.id);
|
|
400
|
+
this.nativePending.delete(message.id);
|
|
401
|
+
callback?.(Buffer.from(message.data, "base64").toString("utf8"));
|
|
402
|
+
}
|
|
403
|
+
if (message.kind === "clipboard-written" && this.pendingClipboard?.id === message.id) {
|
|
404
|
+
this.pendingClipboard = void 0;
|
|
405
|
+
this.send("clipboard", { kind: "clipboard.done", id: message.id });
|
|
406
|
+
}
|
|
407
|
+
if (message.kind === "clipboard-error") {
|
|
408
|
+
if (typeof message.id === "string") this.nativePending.delete(message.id);
|
|
409
|
+
this.pendingClipboard = void 0;
|
|
410
|
+
this.send("clipboard", { kind: "clipboard.error", id: message.id, message: "\u4E3B\u673A\u526A\u8D34\u677F\u8BFB\u53D6\u5931\u8D25\u6216\u8D85\u8FC7 1 MiB" });
|
|
411
|
+
}
|
|
412
|
+
if (message.kind === "input-error") this.send("control", { kind: "input.error", message: "\u4E3B\u673A\u9501\u5C4F\u72B6\u6001\u5DF2\u53D8\u5316\uFF0C\u5DF2\u505C\u6B62\u8F93\u5165\u5BC6\u7801\uFF1B\u8BF7\u68C0\u67E5\u8FDC\u7AEF\u754C\u9762" });
|
|
493
413
|
}
|
|
494
414
|
frame(frame) {
|
|
495
415
|
if (this.legacy || this.frameGate.accept(frame, Date.now())) this.source.onFrame(frame);
|
|
496
416
|
}
|
|
497
417
|
writeNative(line) {
|
|
498
|
-
|
|
499
|
-
if (this.helper.stdin.writableLength > 2 * 1024 * 1024) {
|
|
500
|
-
this.stop("\u4E3B\u673A\u8F93\u5165\u5904\u7406\u8D85\u65F6");
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
this.helper.stdin.write(`${line}
|
|
504
|
-
`);
|
|
418
|
+
this.native?.writeCommand(line);
|
|
505
419
|
}
|
|
506
420
|
input(input) {
|
|
507
|
-
|
|
508
|
-
case "pointer":
|
|
509
|
-
this.writeNative(`pointer ${input.x} ${input.y}`);
|
|
510
|
-
break;
|
|
511
|
-
case "button":
|
|
512
|
-
this.writeNative(`button ${input.x} ${input.y} ${input.button} ${input.down ? 1 : 0}`);
|
|
513
|
-
break;
|
|
514
|
-
case "wheel":
|
|
515
|
-
this.writeNative(`wheel ${input.x} ${input.y} ${Math.round(input.dx)} ${Math.round(input.dy)}`);
|
|
516
|
-
break;
|
|
517
|
-
case "key":
|
|
518
|
-
this.writeNative(`key ${input.code} ${input.down ? 1 : 0}`);
|
|
519
|
-
break;
|
|
520
|
-
case "password":
|
|
521
|
-
if (process.platform !== "darwin") return;
|
|
522
|
-
this.writeNative(`password ${Buffer.from(input.text).toString("base64")}`);
|
|
523
|
-
break;
|
|
524
|
-
case "text":
|
|
525
|
-
this.writeNative(`text ${input.terminal ? 1 : 0} ${Buffer.from(input.text).toString("base64")}`);
|
|
526
|
-
break;
|
|
527
|
-
case "release":
|
|
528
|
-
this.writeNative("release");
|
|
529
|
-
break;
|
|
530
|
-
}
|
|
421
|
+
this.native?.input(input);
|
|
531
422
|
}
|
|
532
423
|
clipboard(value) {
|
|
533
424
|
if (typeof value.id !== "string" || !/^[a-zA-Z0-9_-]{1,64}$/.test(value.id)) throw new Error("clipboard id");
|
|
@@ -609,10 +500,7 @@ var RemotePeer = class {
|
|
|
609
500
|
const video = this.budget.video(this.init.display.width, this.init.display.height);
|
|
610
501
|
const changed = video.width !== this.video.width || video.height !== this.video.height || video.fps !== this.video.fps;
|
|
611
502
|
this.video = video;
|
|
612
|
-
if (changed && this.ready)
|
|
613
|
-
if (process.platform === "darwin") this.writeNative(`video ${video.width} ${video.height} ${video.fps}`);
|
|
614
|
-
else this.linuxFrames();
|
|
615
|
-
}
|
|
503
|
+
if (changed && this.ready) this.native?.setVideo(video);
|
|
616
504
|
}
|
|
617
505
|
async encoding() {
|
|
618
506
|
if (this.parameterMode === "sdp") return;
|
|
@@ -677,9 +565,8 @@ var RemotePeer = class {
|
|
|
677
565
|
clearInterval(this.timer);
|
|
678
566
|
clearInterval(this.clipboardTimer);
|
|
679
567
|
clearTimeout(this.handshake);
|
|
680
|
-
this.
|
|
681
|
-
|
|
682
|
-
this.capture?.kill("SIGTERM");
|
|
568
|
+
this.native?.stop();
|
|
569
|
+
this.native = void 0;
|
|
683
570
|
this.track.stop();
|
|
684
571
|
for (const c of this.channels.values()) c.close();
|
|
685
572
|
this.pc.close();
|