cicy-desktop 2.1.324 → 2.1.326
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cicy-desktop",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.326",
|
|
4
4
|
"description": "CiCy - AI-powered operating system browser",
|
|
5
5
|
"main": "src/main.js",
|
|
6
6
|
"bin": {
|
|
@@ -144,10 +144,10 @@
|
|
|
144
144
|
"//optionalDependencies": "(2026-06 回调): mac/linux 改回 native cicy-code(:8008,colima 在 16G mac 上压垮内存)→ 重新内置 cicy-code-<plat> / cicy-mihomo-<plat>,localbin.fromBundle 零网络 seed(npm 仅作更新通道,带 npmmirror→npmjs 回退)。这些由 scripts/sync-runtime-deps.cjs 在 tag-push 时同步到最新版。npm 的 os/cpu 字段保证每个平台只装自己那份。Windows 仍走 docker(WSL :8009),它那份 cicy-code-windows 用不到但 bundle 着无害。",
|
|
145
145
|
"optionalDependencies": {
|
|
146
146
|
"electron": "41.0.3",
|
|
147
|
-
"cicy-code-darwin-x64": "2.3.
|
|
148
|
-
"cicy-code-darwin-arm64": "2.3.
|
|
149
|
-
"cicy-code-linux-x64": "2.3.
|
|
150
|
-
"cicy-code-linux-arm64": "2.3.
|
|
147
|
+
"cicy-code-darwin-x64": "2.3.594",
|
|
148
|
+
"cicy-code-darwin-arm64": "2.3.594",
|
|
149
|
+
"cicy-code-linux-x64": "2.3.594",
|
|
150
|
+
"cicy-code-linux-arm64": "2.3.594",
|
|
151
151
|
"cicy-code-windows-x64": "2.3.193",
|
|
152
152
|
"cicy-mihomo-darwin-x64": "1.10.4",
|
|
153
153
|
"cicy-mihomo-darwin-arm64": "1.10.4",
|
package/scripts/r2.mjs
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
// list has no wrangler equivalent, so it goes through the REST API.
|
|
24
24
|
|
|
25
25
|
import { spawnSync } from "node:child_process";
|
|
26
|
+
import { pathToFileURL } from "node:url";
|
|
26
27
|
import { existsSync, statSync } from "node:fs";
|
|
27
28
|
|
|
28
29
|
const BUCKET = process.env.R2_BUCKET || "cicy-assets-poc";
|
|
@@ -42,12 +43,23 @@ function needCreds() {
|
|
|
42
43
|
if (!ACCOUNT || !TOKEN) die("R2_ACCOUNT_ID and R2_API_TOKEN must be set");
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
// Windows: npx is npx.cmd, which Node only spawns with shell:true — and then
|
|
47
|
+
// cmd.exe re-parses the argv, so anything with a space ("CiCy Desktop Setup
|
|
48
|
+
// 2.1.324.exe") must be quoted or it arrives as several arguments.
|
|
49
|
+
export function shellQuote(arg, platform = process.platform) {
|
|
50
|
+
const s = String(arg);
|
|
51
|
+
if (platform !== "win32" || !/[\s"&|<>^()]/.test(s)) return s;
|
|
52
|
+
return `"${s.replace(/"/g, '\\"')}"`;
|
|
53
|
+
}
|
|
54
|
+
|
|
45
55
|
function wrangler(args) {
|
|
46
56
|
needCreds();
|
|
47
|
-
const
|
|
48
|
-
const
|
|
57
|
+
const isWin = process.platform === "win32";
|
|
58
|
+
const npx = isWin ? "npx.cmd" : "npx";
|
|
59
|
+
const argv = ["--yes", WRANGLER, ...args, "--remote"].map((a) => shellQuote(a));
|
|
60
|
+
const r = spawnSync(npx, argv, {
|
|
49
61
|
stdio: "inherit",
|
|
50
|
-
shell:
|
|
62
|
+
shell: isWin,
|
|
51
63
|
env: { ...process.env, CLOUDFLARE_ACCOUNT_ID: ACCOUNT, CLOUDFLARE_API_TOKEN: TOKEN },
|
|
52
64
|
});
|
|
53
65
|
return r.status == null ? 1 : r.status;
|
|
@@ -122,4 +134,5 @@ async function main() {
|
|
|
122
134
|
}
|
|
123
135
|
}
|
|
124
136
|
|
|
125
|
-
|
|
137
|
+
// Only run when executed directly (tests import shellQuote).
|
|
138
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) main();
|
|
@@ -43,12 +43,19 @@ function readDaemonB64() {
|
|
|
43
43
|
module.exports = (registerTool) => {
|
|
44
44
|
registerTool(
|
|
45
45
|
"desktop_snapshot",
|
|
46
|
-
"返回整屏桌面截图(base64 JPEG
|
|
47
|
-
z.object({
|
|
48
|
-
|
|
46
|
+
"返回整屏桌面截图(base64 JPEG)。默认读 desktop-snapshot daemon 写的 desktop.b64(600px/q60,秒回、不弹 consent / 屏幕录制);请求更高 maxWidth/quality 时 mac/linux 即时抓屏。",
|
|
47
|
+
z.object({
|
|
48
|
+
maxWidth: z.number().optional().describe("最大宽度(px),默认 600"),
|
|
49
|
+
quality: z.number().optional().describe("JPEG 质量 20-95,默认 60"),
|
|
50
|
+
}),
|
|
51
|
+
async ({ maxWidth, quality }) => {
|
|
49
52
|
try {
|
|
50
53
|
const fresh = readDaemonB64();
|
|
51
|
-
|
|
54
|
+
// The daemon file is ALWAYS the cheap 600px/q60 preset. Serving it for a
|
|
55
|
+
// request that asked for 高清/超清 would silently ignore the user's 画质
|
|
56
|
+
// choice, so only take the fast path when the cached preset is enough.
|
|
57
|
+
const cacheOk = snap.matchesDaemonPreset(maxWidth, quality);
|
|
58
|
+
if (cacheOk && fresh && fresh.ageMs <= FRESH_MS) {
|
|
52
59
|
return { content: [{ type: "text", text: fresh.b64 }] };
|
|
53
60
|
}
|
|
54
61
|
|
|
@@ -60,10 +67,10 @@ module.exports = (registerTool) => {
|
|
|
60
67
|
// mac/linux:进程内即时抓屏(honor snapshotEnabled — mac 默认关避免授权弹窗)。
|
|
61
68
|
if (process.platform !== "win32" && snap.snapshotEnabled()) {
|
|
62
69
|
try {
|
|
63
|
-
const r = await snap.captureB64(maxWidth);
|
|
70
|
+
const r = await snap.captureB64(maxWidth, quality);
|
|
64
71
|
return { content: [{ type: "text", text: r.b64 }] };
|
|
65
72
|
} catch (e) {
|
|
66
|
-
if (fresh) return { content: [{ type: "text", text: fresh.b64 }] }; // stale but real
|
|
73
|
+
if (fresh && cacheOk) return { content: [{ type: "text", text: fresh.b64 }] }; // stale but real
|
|
67
74
|
throw e;
|
|
68
75
|
}
|
|
69
76
|
}
|
|
@@ -116,16 +116,37 @@ async function captureOnce() {
|
|
|
116
116
|
// desktop.b64 file is missing/stale. NOT valid on win32 in the main process —
|
|
117
117
|
// desktopCapturer needs the --disable-gpu daemon there (see grabScreenImage);
|
|
118
118
|
// the tool guards that and reads the daemon file instead.
|
|
119
|
-
|
|
119
|
+
//
|
|
120
|
+
// maxWidth/quality are the cloud UI's 画质 picker (流畅 480/q45 → 超清 1920/q92).
|
|
121
|
+
// The daemon file is always the cheap 600px/q60 preset, so a caller asking for
|
|
122
|
+
// anything else MUST come through here to actually get it.
|
|
123
|
+
async function captureB64(maxWidth, quality) {
|
|
120
124
|
const mw = maxWidth > 0 ? maxWidth : MAX_W;
|
|
125
|
+
const q = clampQuality(quality);
|
|
121
126
|
let img = await grabScreenImage();
|
|
122
127
|
const o = img.getSize();
|
|
123
128
|
if (o.width > mw) img = img.resize({ width: mw, quality: "good" });
|
|
124
|
-
const jpeg = img.toJPEG(
|
|
129
|
+
const jpeg = img.toJPEG(q);
|
|
125
130
|
if (!jpeg || jpeg.length < 256) throw new Error("encoded jpeg too small");
|
|
126
131
|
return { b64: jpeg.toString("base64"), w: o.width, h: o.height, bytes: jpeg.length };
|
|
127
132
|
}
|
|
128
133
|
|
|
134
|
+
// electron's toJPEG rejects anything outside 0-100; keep a sane floor so a bad
|
|
135
|
+
// caller can't ask for an unreadable 1-quality frame.
|
|
136
|
+
function clampQuality(q) {
|
|
137
|
+
const n = Number(q);
|
|
138
|
+
if (!Number.isFinite(n) || n <= 0) return QUALITY;
|
|
139
|
+
return Math.min(95, Math.max(20, Math.round(n)));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Does this request match what the daemon file already holds? If so the cached
|
|
143
|
+
// frame is good enough and we skip a live grab; if not (the user picked 高清),
|
|
144
|
+
// the caller must capture live or it would silently serve the coarse preset.
|
|
145
|
+
function matchesDaemonPreset(maxWidth, quality) {
|
|
146
|
+
const mw = maxWidth > 0 ? maxWidth : MAX_W;
|
|
147
|
+
return mw <= MAX_W && clampQuality(quality) <= QUALITY;
|
|
148
|
+
}
|
|
149
|
+
|
|
129
150
|
// ── parent (started from main.js) ─────────────────────────────────────────────
|
|
130
151
|
let child = null;
|
|
131
152
|
let timer = null;
|
|
@@ -235,4 +256,4 @@ if (process.env.CICY_SNAP_DAEMON === "1") {
|
|
|
235
256
|
app.on("render-process-gone", () => app.quit());
|
|
236
257
|
}
|
|
237
258
|
|
|
238
|
-
module.exports = { startDesktopSnapshots, stopDesktopSnapshots, snapDir, captureOnce, captureOnceWin, captureB64, snapshotEnabled, MAX_W };
|
|
259
|
+
module.exports = { startDesktopSnapshots, stopDesktopSnapshots, snapDir, captureOnce, captureOnceWin, captureB64, snapshotEnabled, matchesDaemonPreset, MAX_W, QUALITY };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const test = require("node:test");
|
|
2
|
+
const assert = require("node:assert");
|
|
3
|
+
|
|
4
|
+
// scripts/r2.mjs is ESM and runs main() on load; pull just the exported helper.
|
|
5
|
+
async function load() { const m = await import("../scripts/r2.mjs"); return m.shellQuote; }
|
|
6
|
+
|
|
7
|
+
test("shellQuote: Windows paths with spaces are quoted for cmd.exe re-parsing", async () => {
|
|
8
|
+
const q = await load();
|
|
9
|
+
assert.strictEqual(q("CiCy Desktop Setup 2.1.324.exe", "win32"), '"CiCy Desktop Setup 2.1.324.exe"');
|
|
10
|
+
assert.strictEqual(q("releases/win/CiCy-Desktop-Setup-2.1.324.exe", "win32"), "releases/win/CiCy-Desktop-Setup-2.1.324.exe");
|
|
11
|
+
assert.strictEqual(q('a "b" c', "win32"), '"a \\"b\\" c"');
|
|
12
|
+
assert.strictEqual(q("CiCy Desktop Setup 2.1.324.exe", "linux"), "CiCy Desktop Setup 2.1.324.exe");
|
|
13
|
+
});
|