@co0ontty/wand 1.55.0 → 1.55.1-beta.g00c1381
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 +0 -1
- package/dist/build-info.json +4 -4
- package/dist/cert.js +1 -1
- package/dist/server.js +34 -42
- package/dist/types.d.ts +1 -1
- package/dist/version-utils.d.ts +16 -0
- package/dist/version-utils.js +41 -2
- package/dist/web-ui/content/scripts.js +32 -36
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +3 -3
- package/dist/web-ui/index.js +0 -9
- package/package.json +2 -2
- package/dist/avatar.d.ts +0 -15
- package/dist/avatar.js +0 -102
- package/dist/pwa.d.ts +0 -2
- package/dist/pwa.js +0 -120
- package/dist/web-ui/content/icon-192.png +0 -0
- package/dist/web-ui/content/icon-512.png +0 -0
- package/dist/web-ui/content/scripts.js.bak +0 -21807
package/README.md
CHANGED
|
@@ -76,7 +76,6 @@ bash <(curl -Ls https://raw.githubusercontent.com/co0ontty/wand/master/install.s
|
|
|
76
76
|
|
|
77
77
|
### 部署与访问
|
|
78
78
|
|
|
79
|
-
- **PWA 支持** — 可添加到主屏幕作为独立应用使用
|
|
80
79
|
- **Android 客户端** — WebView 壳应用,支持加密连接码分发、APK 自动更新检查、原生通知推送、启动器图标切换
|
|
81
80
|
- **HTTPS** — 可选自签证书,适合远程或移动端访问
|
|
82
81
|
- **版本管理** — 内置更新检查与升级提示
|
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-06-
|
|
4
|
-
"version": "1.55.
|
|
5
|
-
"channel": "
|
|
2
|
+
"commit": "00c1381e078849c38a88064dfa899a9729012399",
|
|
3
|
+
"builtAt": "2026-06-12T04:06:55.082Z",
|
|
4
|
+
"version": "1.55.1-beta.g00c1381",
|
|
5
|
+
"channel": "beta"
|
|
6
6
|
}
|
package/dist/cert.js
CHANGED
|
@@ -164,7 +164,7 @@ export function ensureCertificates(configDir, options = {}) {
|
|
|
164
164
|
const fingerprint = computeFingerprint(ssl.cert);
|
|
165
165
|
process.stdout.write(`[wand] 证书已写入 ${paths.certPath}\n` +
|
|
166
166
|
`[wand] SHA-256 指纹: ${fingerprint}\n` +
|
|
167
|
-
`[wand]
|
|
167
|
+
`[wand] 注意:自签证书浏览器会标红;可将它导入受信任根证书以消除警告。\n` +
|
|
168
168
|
`[wand] HTTPS 启用后可通过 GET /cert/server.crt 在客户端下载安装。\n`);
|
|
169
169
|
return { ...ssl, certPath: paths.certPath, fingerprint, userProvided: false };
|
|
170
170
|
}
|
package/dist/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
-
import { compareSemver, extractSemver } from "./version-utils.js";
|
|
2
|
+
import { compareApkInstallOrder, compareSemver, extractSemver } from "./version-utils.js";
|
|
3
3
|
import compression from "compression";
|
|
4
4
|
import express from "express";
|
|
5
5
|
import { createReadStream, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
@@ -11,7 +11,6 @@ import { promisify } from "node:util";
|
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
import process from "node:process";
|
|
13
13
|
import { WebSocketServer } from "ws";
|
|
14
|
-
import { ensureAvatarSeed, getAvatarSvg } from "./avatar.js";
|
|
15
14
|
import { createSession, readSessionCookie, revokeSession, SESSION_COOKIE_HTTP, SESSION_COOKIE_HTTPS, SESSION_COOKIE_LEGACY, setAuthStorage, validateSession, } from "./auth.js";
|
|
16
15
|
import { ensureCertificates } from "./cert.js";
|
|
17
16
|
import { buildChildEnv } from "./env-utils.js";
|
|
@@ -20,7 +19,6 @@ import { getCachedModels, refreshModels } from "./models.js";
|
|
|
20
19
|
import { ProcessManager } from "./process-manager.js";
|
|
21
20
|
import { SessionLogger } from "./session-logger.js";
|
|
22
21
|
import { StructuredSessionManager } from "./structured-session-manager.js";
|
|
23
|
-
import { generatePwaManifest, generateServiceWorker } from "./pwa.js";
|
|
24
22
|
import { getErrorMessage, registerClaudeHistoryRoutes, registerSessionRoutes } from "./server-session-routes.js";
|
|
25
23
|
import { checkPackageUpdateAsync, installPackageGloballyAsync, normalizeUpdateChannel, resolveGlobalWandCli, } from "./npm-update-utils.js";
|
|
26
24
|
import { repairServiceUnitAfterUpdate } from "./service-self-repair.js";
|
|
@@ -140,30 +138,39 @@ async function fetchGitHubLatestApk(forceRefresh = false) {
|
|
|
140
138
|
}
|
|
141
139
|
}
|
|
142
140
|
async function resolveLatestApkVersion(configDir, config) {
|
|
143
|
-
//
|
|
141
|
+
// local 与 github 两个来源都看,按安装序取真正更新的那个(持平偏向 local:同源下载更快)。
|
|
142
|
+
// 旧逻辑是「local 存在就一票否决」——本地目录留着旧包时,会把线上新版压住不提示。
|
|
144
143
|
const localApk = await resolveAndroidApkAsset(configDir, config);
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
const local = localApk && localApk.version
|
|
145
|
+
? {
|
|
147
146
|
version: localApk.version,
|
|
148
147
|
downloadUrl: localApk.downloadUrl,
|
|
149
148
|
fileName: localApk.fileName,
|
|
150
149
|
size: localApk.size,
|
|
151
150
|
source: "local",
|
|
152
|
-
}
|
|
151
|
+
}
|
|
152
|
+
: null;
|
|
153
|
+
let github = null;
|
|
154
|
+
try {
|
|
155
|
+
const ghApk = await fetchGitHubLatestApk();
|
|
156
|
+
if (ghApk) {
|
|
157
|
+
github = {
|
|
158
|
+
version: ghApk.version,
|
|
159
|
+
downloadUrl: ghApk.downloadUrl,
|
|
160
|
+
fileName: ghApk.fileName,
|
|
161
|
+
size: ghApk.size,
|
|
162
|
+
source: "github",
|
|
163
|
+
releaseNotes: ghApk.releaseNotes,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
153
166
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (ghApk) {
|
|
157
|
-
return {
|
|
158
|
-
version: ghApk.version,
|
|
159
|
-
downloadUrl: ghApk.downloadUrl,
|
|
160
|
-
fileName: ghApk.fileName,
|
|
161
|
-
size: ghApk.size,
|
|
162
|
-
source: "github",
|
|
163
|
-
releaseNotes: ghApk.releaseNotes,
|
|
164
|
-
};
|
|
167
|
+
catch {
|
|
168
|
+
// GitHub 不可达时静默回退 local
|
|
165
169
|
}
|
|
166
|
-
|
|
170
|
+
if (local && github) {
|
|
171
|
+
return compareApkInstallOrder(github.version, local.version) > 0 ? github : local;
|
|
172
|
+
}
|
|
173
|
+
return local ?? github;
|
|
167
174
|
}
|
|
168
175
|
let cachedGitHubDmg = null;
|
|
169
176
|
let gitHubDmgCacheTs = 0;
|
|
@@ -526,13 +533,15 @@ async function resolveAndroidApkAsset(configDir, config) {
|
|
|
526
533
|
fileStat,
|
|
527
534
|
};
|
|
528
535
|
}));
|
|
529
|
-
//
|
|
536
|
+
// 按版本号选"最新", 而非修改时间 —— cp/rsync/解压/checkout 都可能让低版本号文件的
|
|
530
537
|
// mtime 更新, 用 mtime 会把旧版本号当成 latest 上报。版本相同或都无版本号时退回 mtime。
|
|
538
|
+
// 注意用安装序比较(同三段时 debug > release,镜像 versionCode),不是标准 semver:
|
|
539
|
+
// wand-v1.55.0.apk 与 wand-v1.55.0-debug.x.apk 并存时,debug 才是装得上的更新包。
|
|
531
540
|
candidates.sort((a, b) => {
|
|
532
541
|
const va = extractAndroidApkVersion(a.entry.name);
|
|
533
542
|
const vb = extractAndroidApkVersion(b.entry.name);
|
|
534
543
|
if (va && vb) {
|
|
535
|
-
const cmp =
|
|
544
|
+
const cmp = compareApkInstallOrder(vb, va);
|
|
536
545
|
if (cmp !== 0)
|
|
537
546
|
return cmp;
|
|
538
547
|
}
|
|
@@ -925,7 +934,6 @@ export async function startServer(config, configPath) {
|
|
|
925
934
|
const storage = new WandStorage(resolveDatabasePath(configPath));
|
|
926
935
|
setAuthStorage(storage);
|
|
927
936
|
const configDir = resolveConfigDir(configPath);
|
|
928
|
-
const avatarSeed = await ensureAvatarSeed(configDir);
|
|
929
937
|
const processes = new ProcessManager(config, storage, configDir);
|
|
930
938
|
const structuredLogger = new SessionLogger(configDir, config.shortcutLogMaxBytes);
|
|
931
939
|
const structuredSessions = new StructuredSessionManager(storage, config, structuredLogger);
|
|
@@ -943,7 +951,7 @@ export async function startServer(config, configPath) {
|
|
|
943
951
|
app.get("/vendor/wterm/wterm.bundle.js", (req, res) => sendEmbeddedVendorAsset("/vendor/wterm/wterm.bundle.js", req, res));
|
|
944
952
|
app.get("/vendor/wterm/terminal.css", (req, res) => sendEmbeddedVendorAsset("/vendor/wterm/terminal.css", req, res));
|
|
945
953
|
app.get("/vendor/qrcode/qrcode.bundle.js", (req, res) => sendEmbeddedVendorAsset("/vendor/qrcode/qrcode.bundle.js", req, res));
|
|
946
|
-
// ── Web UI
|
|
954
|
+
// ── Web UI endpoints ──
|
|
947
955
|
app.get("/", (_req, res) => {
|
|
948
956
|
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
949
957
|
res.type("html").send(renderApp(configPath));
|
|
@@ -992,24 +1000,6 @@ export async function startServer(config, configPath) {
|
|
|
992
1000
|
res.status(404).end();
|
|
993
1001
|
}
|
|
994
1002
|
});
|
|
995
|
-
app.get("/manifest.json", (_req, res) => {
|
|
996
|
-
res.setHeader("Content-Type", "application/manifest+json");
|
|
997
|
-
res.send(generatePwaManifest());
|
|
998
|
-
});
|
|
999
|
-
for (const [route, size] of [["/icon.svg", 192], ["/icon-192.png", 192], ["/icon-512.png", 512]]) {
|
|
1000
|
-
app.get(route, (_req, res) => {
|
|
1001
|
-
res.type("image/svg+xml").send(getAvatarSvg(avatarSeed, size));
|
|
1002
|
-
});
|
|
1003
|
-
}
|
|
1004
|
-
app.get("/sw.js", (_req, res) => {
|
|
1005
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
1006
|
-
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
1007
|
-
res.setHeader("Service-Worker-Allowed", "/");
|
|
1008
|
-
res.send(generateServiceWorker());
|
|
1009
|
-
});
|
|
1010
|
-
app.get("/offline", (_req, res) => {
|
|
1011
|
-
res.type("html").send(renderApp(configPath));
|
|
1012
|
-
});
|
|
1013
1003
|
// ── Auth routes ──
|
|
1014
1004
|
app.post("/api/login", (req, res) => {
|
|
1015
1005
|
const clientIp = req.ip || req.socket.remoteAddress || "unknown";
|
|
@@ -1091,7 +1081,9 @@ export async function startServer(config, configPath) {
|
|
|
1091
1081
|
res.json({ updateAvailable: false, currentVersion, latestVersion: null, downloadUrl: null, source: null });
|
|
1092
1082
|
return;
|
|
1093
1083
|
}
|
|
1094
|
-
|
|
1084
|
+
// 安装序比较(镜像 versionCode),不是标准 semver:只在系统安装器真能装上时才提示,
|
|
1085
|
+
// 避免「提示升级 → 下载 → 被按降级拒装」的死循环(如已装 1.55.0-debug 提示装 1.55.0)。
|
|
1086
|
+
const updateAvailable = compareApkInstallOrder(latest.version, currentVersion) > 0;
|
|
1095
1087
|
res.json({
|
|
1096
1088
|
updateAvailable,
|
|
1097
1089
|
currentVersion,
|
package/dist/types.d.ts
CHANGED
package/dist/version-utils.d.ts
CHANGED
|
@@ -14,3 +14,19 @@ export declare function extractSemver(text: string): string | null;
|
|
|
14
14
|
* 贴近标准 semver,避免 debug.MMDDHHMM 后缀因纯字典序而跨月/跨年排反。
|
|
15
15
|
*/
|
|
16
16
|
export declare function compareSemver(a: string, b: string): number;
|
|
17
|
+
/**
|
|
18
|
+
* Android APK 安装序比较 —— 与 android/app/build.gradle 的 computeVersionCode 镜像一致,
|
|
19
|
+
* 回答「这个包能否覆盖安装到那个包之上 / 该不该提示升级」。返回正数 = a 比 b 新。
|
|
20
|
+
*
|
|
21
|
+
* 与标准 semver(compareSemver)的关键差异:同主版本三段时,带 `-debug` 后缀的包
|
|
22
|
+
* 【更新】而不是更旧 —— debug 包是 tag 之后的 master 构建(versionCode = base+1,
|
|
23
|
+
* release 是 base+0),系统安装器只认 versionCode。若在这里沿用 semver 的
|
|
24
|
+
* 「prerelease < release」规则,就会提示用户从 debug「升级」到同号 release,
|
|
25
|
+
* 下载后被系统按降级拒装。
|
|
26
|
+
*
|
|
27
|
+
* - 三段数值比较优先;
|
|
28
|
+
* - 同三段:带 -debug > 不带(镜像 versionCode base+1 > base+0);
|
|
29
|
+
* - 两个 debug:按后缀分段比较(debug.MMDDHHMM 时间戳数值比),versionCode 相同、
|
|
30
|
+
* 系统允许互装,比较结果只用于「是否提示更新」。
|
|
31
|
+
*/
|
|
32
|
+
export declare function compareApkInstallOrder(a: string, b: string): number;
|
package/dist/version-utils.js
CHANGED
|
@@ -37,8 +37,12 @@ export function compareSemver(a, b) {
|
|
|
37
37
|
return -1;
|
|
38
38
|
if (!pa.pre && !pb.pre)
|
|
39
39
|
return 0;
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
return comparePrereleaseSegments(pa.pre, pb.pre);
|
|
41
|
+
}
|
|
42
|
+
/** 按 `.` 分段比较 prerelease 后缀:数字段数值比、非数字段字典序、段少者更小。 */
|
|
43
|
+
function comparePrereleaseSegments(preA, preB) {
|
|
44
|
+
const segA = preA.split(".");
|
|
45
|
+
const segB = preB.split(".");
|
|
42
46
|
const segLen = Math.max(segA.length, segB.length);
|
|
43
47
|
for (let i = 0; i < segLen; i++) {
|
|
44
48
|
const sa = segA[i];
|
|
@@ -64,3 +68,38 @@ export function compareSemver(a, b) {
|
|
|
64
68
|
}
|
|
65
69
|
return 0;
|
|
66
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Android APK 安装序比较 —— 与 android/app/build.gradle 的 computeVersionCode 镜像一致,
|
|
73
|
+
* 回答「这个包能否覆盖安装到那个包之上 / 该不该提示升级」。返回正数 = a 比 b 新。
|
|
74
|
+
*
|
|
75
|
+
* 与标准 semver(compareSemver)的关键差异:同主版本三段时,带 `-debug` 后缀的包
|
|
76
|
+
* 【更新】而不是更旧 —— debug 包是 tag 之后的 master 构建(versionCode = base+1,
|
|
77
|
+
* release 是 base+0),系统安装器只认 versionCode。若在这里沿用 semver 的
|
|
78
|
+
* 「prerelease < release」规则,就会提示用户从 debug「升级」到同号 release,
|
|
79
|
+
* 下载后被系统按降级拒装。
|
|
80
|
+
*
|
|
81
|
+
* - 三段数值比较优先;
|
|
82
|
+
* - 同三段:带 -debug > 不带(镜像 versionCode base+1 > base+0);
|
|
83
|
+
* - 两个 debug:按后缀分段比较(debug.MMDDHHMM 时间戳数值比),versionCode 相同、
|
|
84
|
+
* 系统允许互装,比较结果只用于「是否提示更新」。
|
|
85
|
+
*/
|
|
86
|
+
export function compareApkInstallOrder(a, b) {
|
|
87
|
+
const parse = (v) => {
|
|
88
|
+
const [main, ...rest] = v.replace(/^v/, "").split("-");
|
|
89
|
+
const pre = rest.join("-");
|
|
90
|
+
const mainParts = main.split(".").map((n) => Number(n) || 0);
|
|
91
|
+
return { mainParts, pre, isDebug: pre.startsWith("debug") };
|
|
92
|
+
};
|
|
93
|
+
const pa = parse(a);
|
|
94
|
+
const pb = parse(b);
|
|
95
|
+
for (let i = 0; i < 3; i++) {
|
|
96
|
+
const diff = (pa.mainParts[i] || 0) - (pb.mainParts[i] || 0);
|
|
97
|
+
if (diff !== 0)
|
|
98
|
+
return diff;
|
|
99
|
+
}
|
|
100
|
+
if (pa.isDebug !== pb.isDebug)
|
|
101
|
+
return pa.isDebug ? 1 : -1;
|
|
102
|
+
if (pa.isDebug && pb.isDebug)
|
|
103
|
+
return comparePrereleaseSegments(pa.pre, pb.pre);
|
|
104
|
+
return 0;
|
|
105
|
+
}
|