@co0ontty/wand 1.41.3 → 1.42.0
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/build-info.json +3 -3
- package/dist/npm-update-utils.d.ts +2 -2
- package/dist/npm-update-utils.js +191 -58
- package/dist/pwa.js +2 -22
- package/dist/server.js +49 -27
- package/dist/structured-session-manager.d.ts +3 -3
- package/dist/structured-session-manager.js +9 -7
- package/dist/types.d.ts +1 -1
- package/dist/update-helper.d.ts +18 -0
- package/dist/update-helper.js +238 -0
- package/dist/web-ui/content/scripts.js +9 -3
- package/dist/web-ui/embedded-assets.d.ts +23 -0
- package/dist/web-ui/embedded-assets.js +27 -0
- package/dist/web-ui/index.d.ts +2 -0
- package/dist/web-ui/index.js +3 -26
- package/dist/web-ui/scripts.js +5 -6
- package/dist/web-ui/styles.js +4 -5
- package/package.json +5 -4
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { chmodSync, existsSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { detectInstalledScope } from "./tui/commands.js";
|
|
7
|
+
function shellQuote(value) {
|
|
8
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
9
|
+
}
|
|
10
|
+
function shellArray(values) {
|
|
11
|
+
return values.map((value) => shellQuote(value)).join(" ");
|
|
12
|
+
}
|
|
13
|
+
function boolShell(value) {
|
|
14
|
+
return value ? "1" : "0";
|
|
15
|
+
}
|
|
16
|
+
function resolveBashPath() {
|
|
17
|
+
return existsSync("/bin/bash") ? "/bin/bash" : "bash";
|
|
18
|
+
}
|
|
19
|
+
function detectServiceScope() {
|
|
20
|
+
try {
|
|
21
|
+
return detectInstalledScope();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function buildHelperScript(opts, logPath, serviceScope) {
|
|
28
|
+
const isSystemdManaged = !!process.env.INVOCATION_ID;
|
|
29
|
+
const isLaunchdManaged = !!process.env.LAUNCHD_SOCKET || !!process.env.XPC_SERVICE_NAME;
|
|
30
|
+
const shouldStartService = serviceScope !== null;
|
|
31
|
+
const shouldRestartStandalone = !shouldStartService;
|
|
32
|
+
const npmCache = opts.env.npm_config_cache || path.join(os.homedir(), ".npm");
|
|
33
|
+
const timeoutSec = Math.max(30, Math.ceil((opts.timeoutMs ?? 300_000) / 1000));
|
|
34
|
+
const cliArgArray = opts.cliArgs;
|
|
35
|
+
return `#!/usr/bin/env bash
|
|
36
|
+
set -u
|
|
37
|
+
LOG=${shellQuote(logPath)}
|
|
38
|
+
exec >>"$LOG" 2>&1
|
|
39
|
+
echo "[wand-update] started at $(date -Is)"
|
|
40
|
+
echo "[wand-update] parent pid: ${opts.parentPid}"
|
|
41
|
+
echo "[wand-update] install spec: ${opts.installSpec}"
|
|
42
|
+
|
|
43
|
+
export PATH=${shellQuote(opts.env.PATH || process.env.PATH || "")}
|
|
44
|
+
export HOME=${shellQuote(opts.env.HOME || os.homedir())}
|
|
45
|
+
export npm_config_cache=${shellQuote(npmCache)}
|
|
46
|
+
CONFIG_PATH=${shellQuote(opts.configPath)}
|
|
47
|
+
INSTALL_SPEC=${shellQuote(opts.installSpec)}
|
|
48
|
+
PARENT_PID=${opts.parentPid}
|
|
49
|
+
SERVICE_SCOPE=${shellQuote(serviceScope ?? "")}
|
|
50
|
+
SHOULD_START_SERVICE=${boolShell(shouldStartService)}
|
|
51
|
+
SHOULD_RESTART_STANDALONE=${boolShell(shouldRestartStandalone)}
|
|
52
|
+
TIMEOUT_SEC=${timeoutSec}
|
|
53
|
+
CLI_ARGS=(${shellArray(cliArgArray)})
|
|
54
|
+
|
|
55
|
+
run() {
|
|
56
|
+
echo "+ $*"
|
|
57
|
+
"$@"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
run_best_effort() {
|
|
61
|
+
echo "+ $*"
|
|
62
|
+
"$@" || true
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
wait_for_parent_exit() {
|
|
66
|
+
local pid="$1"
|
|
67
|
+
local i=0
|
|
68
|
+
while kill -0 "$pid" 2>/dev/null; do
|
|
69
|
+
i=$((i + 1))
|
|
70
|
+
if [ "$i" -ge 120 ]; then
|
|
71
|
+
echo "[wand-update] parent still alive after 60s; continuing"
|
|
72
|
+
return 0
|
|
73
|
+
fi
|
|
74
|
+
sleep 0.5
|
|
75
|
+
done
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
clean_leftovers() {
|
|
79
|
+
local npm_root
|
|
80
|
+
npm_root="$(npm root -g 2>/dev/null || true)"
|
|
81
|
+
if [ -n "$npm_root" ] && [ -d "$npm_root/@co0ontty" ]; then
|
|
82
|
+
find "$npm_root/@co0ontty" -maxdepth 1 -name ".wand-*" -type d -print -exec rm -rf {} + 2>/dev/null || true
|
|
83
|
+
fi
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
npm_install_wand() {
|
|
87
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
88
|
+
run timeout "$TIMEOUT_SEC" npm install -g "$INSTALL_SPEC"
|
|
89
|
+
else
|
|
90
|
+
run npm install -g "$INSTALL_SPEC"
|
|
91
|
+
fi
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
npm_install_wand_force() {
|
|
95
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
96
|
+
run timeout "$TIMEOUT_SEC" npm install -g --force "$INSTALL_SPEC"
|
|
97
|
+
else
|
|
98
|
+
run npm install -g --force "$INSTALL_SPEC"
|
|
99
|
+
fi
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
restart_or_start_service() {
|
|
103
|
+
if [ "$SHOULD_START_SERVICE" = "1" ]; then
|
|
104
|
+
if [ "$SERVICE_SCOPE" = "user" ]; then
|
|
105
|
+
run_best_effort wand service:install --user -c "$CONFIG_PATH"
|
|
106
|
+
run_best_effort systemctl --user restart wand.service
|
|
107
|
+
else
|
|
108
|
+
run_best_effort wand service:install -c "$CONFIG_PATH"
|
|
109
|
+
run_best_effort systemctl restart wand.service
|
|
110
|
+
fi
|
|
111
|
+
return
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
if [ "$SHOULD_RESTART_STANDALONE" = "1" ]; then
|
|
115
|
+
echo "[wand-update] starting standalone process"
|
|
116
|
+
cd ${shellQuote(opts.cwd)}
|
|
117
|
+
local global_cli
|
|
118
|
+
global_cli="$(npm root -g 2>/dev/null)/@co0ontty/wand/dist/cli.js"
|
|
119
|
+
if [ -f "$global_cli" ]; then
|
|
120
|
+
nohup ${shellQuote(process.execPath)} "$global_cli" "$\{CLI_ARGS[@]}" >>"$LOG" 2>&1 &
|
|
121
|
+
else
|
|
122
|
+
nohup wand "$\{CLI_ARGS[@]}" >>"$LOG" 2>&1 &
|
|
123
|
+
fi
|
|
124
|
+
fi
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
main() {
|
|
128
|
+
if [ "$SERVICE_SCOPE" = "user" ]; then
|
|
129
|
+
run_best_effort systemctl --user stop wand.service
|
|
130
|
+
elif [ "$SERVICE_SCOPE" = "system" ]; then
|
|
131
|
+
run_best_effort systemctl stop wand.service
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
wait_for_parent_exit "$PARENT_PID"
|
|
135
|
+
clean_leftovers
|
|
136
|
+
|
|
137
|
+
echo "[wand-update] installing"
|
|
138
|
+
if ! npm_install_wand; then
|
|
139
|
+
echo "[wand-update] first install failed; retrying with uninstall + force install"
|
|
140
|
+
clean_leftovers
|
|
141
|
+
run_best_effort npm uninstall -g @co0ontty/wand
|
|
142
|
+
clean_leftovers
|
|
143
|
+
npm_install_wand_force
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
run wand init -c "$CONFIG_PATH"
|
|
147
|
+
restart_or_start_service
|
|
148
|
+
echo "[wand-update] completed at $(date -Is)"
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
main
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
function spawnDetached(scriptPath, _logPath, env, serviceScope) {
|
|
155
|
+
const baseEnv = { ...env };
|
|
156
|
+
const trySpawn = (cmd, args, method) => {
|
|
157
|
+
try {
|
|
158
|
+
const child = spawn(cmd, args, {
|
|
159
|
+
detached: true,
|
|
160
|
+
stdio: "ignore",
|
|
161
|
+
env: baseEnv,
|
|
162
|
+
});
|
|
163
|
+
child.unref();
|
|
164
|
+
return { pid: child.pid, method };
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
if (process.platform === "linux") {
|
|
171
|
+
const unitName = `wand-update-${process.pid}-${Date.now()}`;
|
|
172
|
+
const bashPath = resolveBashPath();
|
|
173
|
+
const runTransient = (args, method) => {
|
|
174
|
+
const res = spawnSync("systemd-run", args, {
|
|
175
|
+
encoding: "utf8",
|
|
176
|
+
timeout: 5000,
|
|
177
|
+
env: baseEnv,
|
|
178
|
+
stdio: "ignore",
|
|
179
|
+
});
|
|
180
|
+
return res.status === 0 ? { method } : null;
|
|
181
|
+
};
|
|
182
|
+
const userService = runTransient(["--user", `--unit=${unitName}`, "--quiet", "--collect", bashPath, scriptPath], "systemd-run --user") ??
|
|
183
|
+
runTransient(["--user", `--unit=${unitName}`, "--quiet", bashPath, scriptPath], "systemd-run --user");
|
|
184
|
+
if (userService)
|
|
185
|
+
return userService;
|
|
186
|
+
const systemService = runTransient([`--unit=${unitName}`, "--quiet", "--collect", bashPath, scriptPath], "systemd-run") ??
|
|
187
|
+
runTransient([`--unit=${unitName}`, "--quiet", bashPath, scriptPath], "systemd-run");
|
|
188
|
+
if (systemService)
|
|
189
|
+
return systemService;
|
|
190
|
+
if (serviceScope) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const setsid = spawnSync("setsid", ["--version"], { encoding: "utf8", timeout: 2000 });
|
|
194
|
+
if (setsid.status === 0) {
|
|
195
|
+
const result = trySpawn("setsid", [bashPath, scriptPath], "setsid");
|
|
196
|
+
if (result)
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (process.platform === "darwin") {
|
|
201
|
+
const result = trySpawn("nohup", [resolveBashPath(), scriptPath], "nohup");
|
|
202
|
+
if (result)
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
return trySpawn(resolveBashPath(), [scriptPath], "bash detached");
|
|
206
|
+
}
|
|
207
|
+
export function startDetachedUpdateHelper(opts) {
|
|
208
|
+
const dir = mkdtempSync(path.join(os.tmpdir(), "wand-update-"));
|
|
209
|
+
const scriptPath = path.join(dir, "update.sh");
|
|
210
|
+
const logPath = path.join(dir, "update.log");
|
|
211
|
+
const serviceScope = detectServiceScope();
|
|
212
|
+
writeFileSync(scriptPath, buildHelperScript(opts, logPath, serviceScope), { encoding: "utf8", mode: 0o700 });
|
|
213
|
+
chmodSync(scriptPath, 0o700);
|
|
214
|
+
const spawned = spawnDetached(scriptPath, logPath, opts.env, serviceScope);
|
|
215
|
+
if (!spawned) {
|
|
216
|
+
return {
|
|
217
|
+
started: false,
|
|
218
|
+
scriptPath,
|
|
219
|
+
logPath,
|
|
220
|
+
message: "无法启动独立更新 helper。",
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
started: true,
|
|
225
|
+
scriptPath,
|
|
226
|
+
logPath,
|
|
227
|
+
pid: spawned.pid,
|
|
228
|
+
message: `独立更新 helper 已启动 (${spawned.method})。日志: ${logPath}`,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
export function canUseDetachedUpdateHelper() {
|
|
232
|
+
if (process.platform === "win32")
|
|
233
|
+
return false;
|
|
234
|
+
if (existsSync("/bin/bash"))
|
|
235
|
+
return true;
|
|
236
|
+
const res = spawnSync("bash", ["--version"], { encoding: "utf8", timeout: 2000 });
|
|
237
|
+
return res.status === 0;
|
|
238
|
+
}
|
|
@@ -8888,7 +8888,7 @@
|
|
|
8888
8888
|
// 标签直接用 Claude CLI 原生 magic word:think / think hard / ultrathink。
|
|
8889
8889
|
// 这样用户一眼能对上官方文档里的思考强度档位,PTY 模式下也是这几个词被注入到 prompt 前缀。
|
|
8890
8890
|
var THINKING_LEVELS = [
|
|
8891
|
-
{ id: "off", label: "off", hint: "不启用思考(CLI 无前缀;SDK 关闭 thinking;Codex
|
|
8891
|
+
{ id: "off", label: "off", hint: "不启用思考(CLI 无前缀;SDK 关闭 thinking;Codex minimal)" },
|
|
8892
8892
|
{ id: "standard", label: "think", hint: "Claude CLI: think · SDK budget 4096 · Codex low" },
|
|
8893
8893
|
{ id: "deep", label: "think hard", hint: "Claude CLI: think hard · SDK budget 16000 · Codex medium" },
|
|
8894
8894
|
{ id: "max", label: "ultrathink", hint: "Claude CLI: ultrathink · SDK budget 31999 · Codex high" }
|
|
@@ -11322,7 +11322,9 @@
|
|
|
11322
11322
|
msgEl.classList.remove("hidden");
|
|
11323
11323
|
}
|
|
11324
11324
|
updateBtn.classList.add("hidden");
|
|
11325
|
-
if (data.
|
|
11325
|
+
if (data.detachedUpdate) {
|
|
11326
|
+
showRestartOverlay();
|
|
11327
|
+
} else if (data.restartRequired !== false) {
|
|
11326
11328
|
performRestart(null, msgEl);
|
|
11327
11329
|
} else {
|
|
11328
11330
|
// \u670d\u52a1\u7aef\u660e\u786e\u8868\u793a\u4e0d\u9700\u8981\u91cd\u542f\uff0c\u4fdd\u7559\u624b\u52a8\u91cd\u542f\u6309\u94ae
|
|
@@ -22089,6 +22091,10 @@
|
|
|
22089
22091
|
setSubtitle((data.message || "\u66f4\u65b0\u5b8c\u6210") + "\uff0c\u6b63\u5728\u91cd\u542f\u670d\u52a1\u2026");
|
|
22090
22092
|
setStatus("");
|
|
22091
22093
|
if (actionLabel) actionLabel.textContent = "\u6b63\u5728\u91cd\u542f\u2026";
|
|
22094
|
+
if (data.detachedUpdate) {
|
|
22095
|
+
showRestartOverlay();
|
|
22096
|
+
return;
|
|
22097
|
+
}
|
|
22092
22098
|
if (data.restartRequired === false) {
|
|
22093
22099
|
setProgress(false);
|
|
22094
22100
|
card.classList.remove("is-busy");
|
|
@@ -22174,7 +22180,7 @@
|
|
|
22174
22180
|
document.body.appendChild(overlay);
|
|
22175
22181
|
|
|
22176
22182
|
var attempts = 0;
|
|
22177
|
-
var maxAttempts =
|
|
22183
|
+
var maxAttempts = 180; // 180 * 2s = 6min; beta git installs can be slow
|
|
22178
22184
|
var timer = setInterval(function() {
|
|
22179
22185
|
attempts++;
|
|
22180
22186
|
fetch("/api/config", { credentials: "same-origin" })
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const EMBEDDED_WEB_ASSET_VERSION = "ed9dad22c425";
|
|
2
|
+
export declare const EMBEDDED_WEB_ASSETS: {
|
|
3
|
+
readonly scriptsJs: string;
|
|
4
|
+
readonly stylesCss: string;
|
|
5
|
+
readonly vendor: {
|
|
6
|
+
readonly "/vendor/wterm/wterm.bundle.js": {
|
|
7
|
+
readonly content: string;
|
|
8
|
+
readonly contentType: "application/javascript";
|
|
9
|
+
readonly hash: "5c8595b1";
|
|
10
|
+
};
|
|
11
|
+
readonly "/vendor/wterm/terminal.css": {
|
|
12
|
+
readonly content: string;
|
|
13
|
+
readonly contentType: "text/css; charset=utf-8";
|
|
14
|
+
readonly hash: "e6459118";
|
|
15
|
+
};
|
|
16
|
+
readonly "/vendor/qrcode/qrcode.bundle.js": {
|
|
17
|
+
readonly content: string;
|
|
18
|
+
readonly contentType: "application/javascript";
|
|
19
|
+
readonly hash: "8be76aad";
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export type EmbeddedVendorAssetPath = keyof typeof EMBEDDED_WEB_ASSETS.vendor;
|