@co0ontty/wand 1.30.3 → 1.31.1
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 +39 -2
- package/dist/claude-sdk-runner.d.ts +31 -0
- package/dist/claude-sdk-runner.js +142 -0
- package/dist/cli.js +104 -0
- package/dist/git-quick-commit.js +18 -26
- package/dist/process-manager.d.ts +7 -0
- package/dist/process-manager.js +26 -2
- package/dist/prompt-optimizer.js +17 -26
- package/dist/server-session-routes.js +72 -3
- package/dist/server.js +1 -0
- package/dist/structured-session-manager.d.ts +24 -0
- package/dist/structured-session-manager.js +106 -7
- package/dist/tui/attach.js +7 -8
- package/dist/tui/commands.d.ts +24 -7
- package/dist/tui/commands.js +200 -86
- package/dist/tui/index.js +8 -8
- package/dist/tui/service-panel.js +3 -4
- package/dist/types.d.ts +2 -0
- package/dist/web-ui/content/scripts.js +927 -81
- package/dist/web-ui/content/styles.css +986 -141
- package/package.json +1 -1
package/dist/tui/commands.js
CHANGED
|
@@ -122,14 +122,44 @@ function clipboardCandidates() {
|
|
|
122
122
|
{ cmd: "xsel", args: ["--clipboard", "--input"] },
|
|
123
123
|
];
|
|
124
124
|
}
|
|
125
|
-
export
|
|
126
|
-
|
|
125
|
+
export const DEFAULT_SERVICE_SCOPE = "system";
|
|
126
|
+
/** 当前 process 是不是 root(POSIX)。Windows 永远返回 false。 */
|
|
127
|
+
function isRoot() {
|
|
128
|
+
const fn = process.getuid;
|
|
129
|
+
if (typeof fn !== "function")
|
|
130
|
+
return false;
|
|
131
|
+
try {
|
|
132
|
+
return fn.call(process) === 0;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** 自动检测哪个 scope 已经装了 unit;优先 system,找不到就 user。两个都没装返回 null。 */
|
|
139
|
+
function detectInstalledScope() {
|
|
140
|
+
if (existsSync(servicePathFor("system")))
|
|
141
|
+
return "system";
|
|
142
|
+
if (existsSync(servicePathFor("user")))
|
|
143
|
+
return "user";
|
|
144
|
+
return null;
|
|
127
145
|
}
|
|
128
|
-
|
|
146
|
+
/** 给定一个 opts.scope,如果没传就按 detect → default 顺序回退。 */
|
|
147
|
+
function resolveScope(opts) {
|
|
148
|
+
if (opts?.scope)
|
|
149
|
+
return opts.scope;
|
|
150
|
+
return detectInstalledScope() ?? DEFAULT_SERVICE_SCOPE;
|
|
151
|
+
}
|
|
152
|
+
export function isServiceInstalled(scope) {
|
|
153
|
+
if (scope)
|
|
154
|
+
return existsSync(servicePathFor(scope));
|
|
155
|
+
return detectInstalledScope() !== null;
|
|
156
|
+
}
|
|
157
|
+
export function serviceStatus(opts) {
|
|
158
|
+
const scope = resolveScope(opts);
|
|
129
159
|
if (process.platform === "linux")
|
|
130
|
-
return systemdStatus();
|
|
160
|
+
return systemdStatus(scope);
|
|
131
161
|
if (process.platform === "darwin")
|
|
132
|
-
return launchdStatus();
|
|
162
|
+
return launchdStatus(scope);
|
|
133
163
|
return {
|
|
134
164
|
installed: false,
|
|
135
165
|
state: "unsupported",
|
|
@@ -138,31 +168,38 @@ export function serviceStatus() {
|
|
|
138
168
|
platform: process.platform,
|
|
139
169
|
};
|
|
140
170
|
}
|
|
141
|
-
export function serviceStart() {
|
|
171
|
+
export function serviceStart(opts) {
|
|
172
|
+
const scope = resolveScope(opts);
|
|
142
173
|
if (process.platform === "linux")
|
|
143
|
-
return runSystemctl(["start", "wand.service"], "已启动");
|
|
174
|
+
return runSystemctl(scope, ["start", "wand.service"], "已启动");
|
|
144
175
|
if (process.platform === "darwin")
|
|
145
|
-
return launchctlLoad();
|
|
176
|
+
return launchctlLoad(scope);
|
|
146
177
|
return unsupported();
|
|
147
178
|
}
|
|
148
|
-
export function serviceStop() {
|
|
179
|
+
export function serviceStop(opts) {
|
|
180
|
+
const scope = resolveScope(opts);
|
|
149
181
|
if (process.platform === "linux")
|
|
150
|
-
return runSystemctl(["stop", "wand.service"], "已停止");
|
|
182
|
+
return runSystemctl(scope, ["stop", "wand.service"], "已停止");
|
|
151
183
|
if (process.platform === "darwin")
|
|
152
|
-
return launchctlUnload();
|
|
184
|
+
return launchctlUnload(scope);
|
|
153
185
|
return unsupported();
|
|
154
186
|
}
|
|
155
|
-
export function serviceRestart() {
|
|
187
|
+
export function serviceRestart(opts) {
|
|
188
|
+
const scope = resolveScope(opts);
|
|
156
189
|
if (process.platform === "linux")
|
|
157
|
-
return runSystemctl(["restart", "wand.service"], "已重启");
|
|
190
|
+
return runSystemctl(scope, ["restart", "wand.service"], "已重启");
|
|
158
191
|
if (process.platform === "darwin")
|
|
159
|
-
return launchdRestart();
|
|
192
|
+
return launchdRestart(scope);
|
|
160
193
|
return unsupported();
|
|
161
194
|
}
|
|
162
195
|
/** 取最近 N 行服务日志。 */
|
|
163
|
-
export function serviceLogs(lines = 80) {
|
|
196
|
+
export function serviceLogs(lines = 80, opts) {
|
|
197
|
+
const scope = resolveScope(opts);
|
|
164
198
|
if (process.platform === "linux") {
|
|
165
|
-
const
|
|
199
|
+
const args = scope === "user"
|
|
200
|
+
? ["--user", "-u", "wand.service", "-n", String(lines), "--no-pager"]
|
|
201
|
+
: ["-u", "wand.service", "-n", String(lines), "--no-pager"];
|
|
202
|
+
const r = spawnSync("journalctl", args, { encoding: "utf8", timeout: 10_000 });
|
|
166
203
|
if (r.status === 0)
|
|
167
204
|
return { ok: true, message: `journalctl 输出 ${lines} 行`, detail: r.stdout.trim() };
|
|
168
205
|
return { ok: false, message: "journalctl 调用失败", detail: r.stderr.trim() || `exit ${r.status}` };
|
|
@@ -175,22 +212,27 @@ export function serviceLogs(lines = 80) {
|
|
|
175
212
|
}
|
|
176
213
|
return unsupported();
|
|
177
214
|
}
|
|
178
|
-
|
|
179
|
-
|
|
215
|
+
/** systemctl 调用根据 scope 决定要不要 --user。system scope 也意味着调用方需要 root。 */
|
|
216
|
+
function systemctlBaseArgs(scope) {
|
|
217
|
+
return scope === "user" ? ["--user"] : [];
|
|
218
|
+
}
|
|
219
|
+
function systemdStatus(scope) {
|
|
220
|
+
const installed = isServiceInstalled(scope);
|
|
180
221
|
if (!installed) {
|
|
181
222
|
return {
|
|
182
223
|
installed: false,
|
|
183
224
|
state: "unknown",
|
|
184
|
-
description:
|
|
225
|
+
description: `未安装 (${scope} scope)`,
|
|
185
226
|
raw: "",
|
|
186
227
|
platform: "linux",
|
|
187
228
|
};
|
|
188
229
|
}
|
|
189
|
-
|
|
190
|
-
|
|
230
|
+
const base = systemctlBaseArgs(scope);
|
|
231
|
+
// 用 `is-active` + `show -p ...` 拿结构化数据
|
|
232
|
+
const active = spawnSync("systemctl", [...base, "is-active", "wand.service"], { encoding: "utf8" });
|
|
191
233
|
const stateRaw = (active.stdout || "").trim();
|
|
192
234
|
const show = spawnSync("systemctl", [
|
|
193
|
-
|
|
235
|
+
...base,
|
|
194
236
|
"show",
|
|
195
237
|
"wand.service",
|
|
196
238
|
"-p",
|
|
@@ -203,13 +245,13 @@ function systemdStatus() {
|
|
|
203
245
|
"MainPID",
|
|
204
246
|
], { encoding: "utf8" });
|
|
205
247
|
const props = parseSystemctlShow(show.stdout || "");
|
|
206
|
-
const status = spawnSync("systemctl", [
|
|
248
|
+
const status = spawnSync("systemctl", [...base, "status", "wand.service", "--no-pager", "-n", "5"], {
|
|
207
249
|
encoding: "utf8",
|
|
208
250
|
});
|
|
209
251
|
const sub = props.SubState || stateRaw;
|
|
210
252
|
const since = props.ActiveEnterTimestamp ? ` since ${props.ActiveEnterTimestamp}` : "";
|
|
211
253
|
const pid = props.MainPID && props.MainPID !== "0" ? ` · PID ${props.MainPID}` : "";
|
|
212
|
-
const desc =
|
|
254
|
+
const desc = `[${scope}] ${stateRaw}${sub ? ` (${sub})` : ""}${since}${pid}`;
|
|
213
255
|
let normalized = "unknown";
|
|
214
256
|
if (stateRaw === "active")
|
|
215
257
|
normalized = "active";
|
|
@@ -227,13 +269,13 @@ function systemdStatus() {
|
|
|
227
269
|
platform: "linux",
|
|
228
270
|
};
|
|
229
271
|
}
|
|
230
|
-
function launchdStatus() {
|
|
231
|
-
const installed = isServiceInstalled();
|
|
272
|
+
function launchdStatus(scope) {
|
|
273
|
+
const installed = isServiceInstalled(scope);
|
|
232
274
|
if (!installed) {
|
|
233
275
|
return {
|
|
234
276
|
installed: false,
|
|
235
277
|
state: "unknown",
|
|
236
|
-
description:
|
|
278
|
+
description: `未安装 (${scope} scope)`,
|
|
237
279
|
raw: "",
|
|
238
280
|
platform: "darwin",
|
|
239
281
|
};
|
|
@@ -244,18 +286,17 @@ function launchdStatus() {
|
|
|
244
286
|
return {
|
|
245
287
|
installed: true,
|
|
246
288
|
state: "inactive",
|
|
247
|
-
description:
|
|
289
|
+
description: `[${scope}] loaded 但未在运行(launchctl list 找不到)`,
|
|
248
290
|
raw: list.stderr || "",
|
|
249
291
|
platform: "darwin",
|
|
250
292
|
};
|
|
251
293
|
}
|
|
252
|
-
// launchctl list <label> 给出多行 plist 格式:包含 PID / LastExitStatus
|
|
253
294
|
const text = list.stdout;
|
|
254
295
|
const pidMatch = text.match(/"PID"\s*=\s*(\d+);/);
|
|
255
296
|
const exitMatch = text.match(/"LastExitStatus"\s*=\s*(-?\d+);/);
|
|
256
297
|
const pid = pidMatch ? Number(pidMatch[1]) : 0;
|
|
257
298
|
const lastExit = exitMatch ? Number(exitMatch[1]) : 0;
|
|
258
|
-
const desc = pid > 0 ? `running · PID ${pid}` : `stopped (last exit=${lastExit})`;
|
|
299
|
+
const desc = pid > 0 ? `[${scope}] running · PID ${pid}` : `[${scope}] stopped (last exit=${lastExit})`;
|
|
259
300
|
return {
|
|
260
301
|
installed: true,
|
|
261
302
|
state: pid > 0 ? "active" : "inactive",
|
|
@@ -264,48 +305,55 @@ function launchdStatus() {
|
|
|
264
305
|
platform: "darwin",
|
|
265
306
|
};
|
|
266
307
|
}
|
|
267
|
-
function runSystemctl(args, successWord) {
|
|
268
|
-
const
|
|
308
|
+
function runSystemctl(scope, args, successWord) {
|
|
309
|
+
const base = systemctlBaseArgs(scope);
|
|
310
|
+
const r = spawnSync("systemctl", [...base, ...args], { encoding: "utf8", timeout: 15_000 });
|
|
311
|
+
const scopeLabel = scope === "user" ? "--user " : "";
|
|
269
312
|
if (r.status === 0) {
|
|
270
|
-
return { ok: true, message: `systemctl
|
|
313
|
+
return { ok: true, message: `systemctl ${scopeLabel}${args.join(" ")} ${successWord}` };
|
|
271
314
|
}
|
|
315
|
+
// system scope 没拿到 root 是最常见错误,给一个明确提示
|
|
316
|
+
const stderr = (r.stderr || "").trim();
|
|
317
|
+
const hint = scope === "system" && !isRoot()
|
|
318
|
+
? "\n提示: 系统级服务操作需要 root,请用 sudo 重试。"
|
|
319
|
+
: "";
|
|
272
320
|
return {
|
|
273
321
|
ok: false,
|
|
274
|
-
message: `systemctl 失败 (exit ${r.status})`,
|
|
275
|
-
detail: ((r.stdout || "") + "\n" +
|
|
322
|
+
message: `systemctl ${scopeLabel}${args.join(" ")} 失败 (exit ${r.status})`,
|
|
323
|
+
detail: ((r.stdout || "") + "\n" + stderr + hint).trim(),
|
|
276
324
|
};
|
|
277
325
|
}
|
|
278
|
-
function launchctlLoad() {
|
|
279
|
-
const plist =
|
|
326
|
+
function launchctlLoad(scope) {
|
|
327
|
+
const plist = servicePathFor(scope);
|
|
280
328
|
if (!existsSync(plist))
|
|
281
|
-
return { ok: false, message:
|
|
329
|
+
return { ok: false, message: `未安装 (${plist} 不存在)` };
|
|
282
330
|
const r = spawnSync("launchctl", ["load", "-w", plist], { encoding: "utf8", timeout: 10_000 });
|
|
283
331
|
if (r.status === 0)
|
|
284
|
-
return { ok: true, message:
|
|
332
|
+
return { ok: true, message: `已 launchctl load (${scope})` };
|
|
285
333
|
return {
|
|
286
334
|
ok: false,
|
|
287
335
|
message: `launchctl load 失败 (exit ${r.status})`,
|
|
288
336
|
detail: ((r.stdout || "") + "\n" + (r.stderr || "")).trim(),
|
|
289
337
|
};
|
|
290
338
|
}
|
|
291
|
-
function launchctlUnload() {
|
|
292
|
-
const plist =
|
|
339
|
+
function launchctlUnload(scope) {
|
|
340
|
+
const plist = servicePathFor(scope);
|
|
293
341
|
if (!existsSync(plist))
|
|
294
|
-
return { ok: false, message:
|
|
342
|
+
return { ok: false, message: `未安装 (${plist} 不存在)` };
|
|
295
343
|
const r = spawnSync("launchctl", ["unload", plist], { encoding: "utf8", timeout: 10_000 });
|
|
296
344
|
if (r.status === 0)
|
|
297
|
-
return { ok: true, message:
|
|
345
|
+
return { ok: true, message: `已 launchctl unload (${scope})` };
|
|
298
346
|
return {
|
|
299
347
|
ok: false,
|
|
300
348
|
message: `launchctl unload 失败 (exit ${r.status})`,
|
|
301
349
|
detail: ((r.stdout || "") + "\n" + (r.stderr || "")).trim(),
|
|
302
350
|
};
|
|
303
351
|
}
|
|
304
|
-
function launchdRestart() {
|
|
305
|
-
const stop = launchctlUnload();
|
|
306
|
-
const start = launchctlLoad();
|
|
352
|
+
function launchdRestart(scope) {
|
|
353
|
+
const stop = launchctlUnload(scope);
|
|
354
|
+
const start = launchctlLoad(scope);
|
|
307
355
|
if (stop.ok && start.ok)
|
|
308
|
-
return { ok: true, message:
|
|
356
|
+
return { ok: true, message: `已 launchd 重启 (${scope})` };
|
|
309
357
|
return {
|
|
310
358
|
ok: false,
|
|
311
359
|
message: "launchd 重启失败",
|
|
@@ -326,25 +374,46 @@ function parseSystemctlShow(text) {
|
|
|
326
374
|
return out;
|
|
327
375
|
}
|
|
328
376
|
export function installService(ctx) {
|
|
377
|
+
const scope = ctx.scope ?? DEFAULT_SERVICE_SCOPE;
|
|
378
|
+
// system scope 需要 root(除了 Windows,那里两个都不支持)
|
|
379
|
+
if (scope === "system" && !isRoot() && process.platform !== "win32") {
|
|
380
|
+
return {
|
|
381
|
+
ok: false,
|
|
382
|
+
message: "系统级服务安装需要 root 权限",
|
|
383
|
+
detail: "请用 sudo 重跑,或传 --user 走用户级安装(不需要 root)。",
|
|
384
|
+
};
|
|
385
|
+
}
|
|
329
386
|
if (process.platform === "linux")
|
|
330
|
-
return
|
|
387
|
+
return installSystemdService(ctx, scope);
|
|
331
388
|
if (process.platform === "darwin")
|
|
332
|
-
return
|
|
389
|
+
return installLaunchdService(ctx, scope);
|
|
333
390
|
return { ok: false, message: `当前平台 ${process.platform} 暂不支持服务注册` };
|
|
334
391
|
}
|
|
335
|
-
export function uninstallService() {
|
|
392
|
+
export function uninstallService(opts) {
|
|
393
|
+
const scope = resolveScope(opts);
|
|
394
|
+
if (scope === "system" && !isRoot() && process.platform !== "win32") {
|
|
395
|
+
return {
|
|
396
|
+
ok: false,
|
|
397
|
+
message: "系统级服务卸载需要 root 权限",
|
|
398
|
+
detail: "请用 sudo 重跑。",
|
|
399
|
+
};
|
|
400
|
+
}
|
|
336
401
|
if (process.platform === "linux")
|
|
337
|
-
return
|
|
402
|
+
return uninstallSystemdService(scope);
|
|
338
403
|
if (process.platform === "darwin")
|
|
339
|
-
return
|
|
404
|
+
return uninstallLaunchdService(scope);
|
|
340
405
|
return { ok: false, message: `当前平台 ${process.platform} 暂不支持服务注册` };
|
|
341
406
|
}
|
|
342
|
-
function
|
|
407
|
+
function servicePathFor(scope) {
|
|
343
408
|
if (process.platform === "linux") {
|
|
344
|
-
return
|
|
409
|
+
return scope === "user"
|
|
410
|
+
? path.join(os.homedir(), ".config/systemd/user/wand.service")
|
|
411
|
+
: "/etc/systemd/system/wand.service";
|
|
345
412
|
}
|
|
346
413
|
if (process.platform === "darwin") {
|
|
347
|
-
return
|
|
414
|
+
return scope === "user"
|
|
415
|
+
? path.join(os.homedir(), "Library/LaunchAgents/com.wand.web.plist")
|
|
416
|
+
: "/Library/LaunchDaemons/com.wand.web.plist";
|
|
348
417
|
}
|
|
349
418
|
return "";
|
|
350
419
|
}
|
|
@@ -359,18 +428,26 @@ function resolveWandBin(ctx) {
|
|
|
359
428
|
return which.stdout.trim();
|
|
360
429
|
return "wand";
|
|
361
430
|
}
|
|
362
|
-
|
|
363
|
-
|
|
431
|
+
/** 当前 process 的真实用户名(system unit 里要写 User=)。 */
|
|
432
|
+
function currentUserName() {
|
|
433
|
+
try {
|
|
434
|
+
return os.userInfo().username || "root";
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
return process.env.USER || process.env.LOGNAME || "root";
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function installSystemdService(ctx, scope) {
|
|
441
|
+
const unitPath = servicePathFor(scope);
|
|
364
442
|
const wandBin = resolveWandBin(ctx);
|
|
365
443
|
const nodeBin = process.execPath;
|
|
366
|
-
|
|
367
|
-
//
|
|
368
|
-
|
|
369
|
-
// 新进程,把更新真正生效。
|
|
370
|
-
const unit = [
|
|
444
|
+
const nodeBinDir = path.dirname(nodeBin);
|
|
445
|
+
// 共同字段
|
|
446
|
+
const commonExec = [
|
|
371
447
|
"[Unit]",
|
|
372
448
|
"Description=wand web console",
|
|
373
|
-
"After=network.target",
|
|
449
|
+
"After=network-online.target",
|
|
450
|
+
"Wants=network-online.target",
|
|
374
451
|
"",
|
|
375
452
|
"[Service]",
|
|
376
453
|
"Type=simple",
|
|
@@ -378,11 +455,40 @@ function installSystemdUserService(ctx) {
|
|
|
378
455
|
`Environment=WAND_NO_TUI=1`,
|
|
379
456
|
"Restart=always",
|
|
380
457
|
"RestartSec=3",
|
|
381
|
-
"",
|
|
382
|
-
"
|
|
383
|
-
"
|
|
384
|
-
|
|
385
|
-
|
|
458
|
+
"StandardOutput=journal",
|
|
459
|
+
"StandardError=journal",
|
|
460
|
+
"SyslogIdentifier=wand",
|
|
461
|
+
];
|
|
462
|
+
// system scope 额外要:User= / HOME= / PATH=(systemd 系统级 service 默认 HOME=/root 是不行的,
|
|
463
|
+
// 而且 PATH 极简,nvm 装的 node spawn 出来的 npm 子进程找不到)
|
|
464
|
+
let unitLines;
|
|
465
|
+
if (scope === "system") {
|
|
466
|
+
const runUser = currentUserName();
|
|
467
|
+
const runHome = process.env.HOME || os.homedir();
|
|
468
|
+
unitLines = [
|
|
469
|
+
...commonExec,
|
|
470
|
+
`User=${runUser}`,
|
|
471
|
+
`WorkingDirectory=${runHome}`,
|
|
472
|
+
`Environment=HOME=${runHome}`,
|
|
473
|
+
`Environment=PATH=${nodeBinDir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`,
|
|
474
|
+
"OOMScoreAdjust=-500",
|
|
475
|
+
"",
|
|
476
|
+
"[Install]",
|
|
477
|
+
"WantedBy=multi-user.target",
|
|
478
|
+
"",
|
|
479
|
+
];
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
// user scope: 跑在 user@<uid>.service cgroup 内,HOME/PATH 自带
|
|
483
|
+
unitLines = [
|
|
484
|
+
...commonExec,
|
|
485
|
+
"",
|
|
486
|
+
"[Install]",
|
|
487
|
+
"WantedBy=default.target",
|
|
488
|
+
"",
|
|
489
|
+
];
|
|
490
|
+
}
|
|
491
|
+
const unit = unitLines.join("\n");
|
|
386
492
|
try {
|
|
387
493
|
mkdirSync(path.dirname(unitPath), { recursive: true });
|
|
388
494
|
writeFileSync(unitPath, unit, "utf8");
|
|
@@ -390,51 +496,59 @@ function installSystemdUserService(ctx) {
|
|
|
390
496
|
catch (err) {
|
|
391
497
|
return { ok: false, message: `写入 unit 失败: ${errMsg(err)}` };
|
|
392
498
|
}
|
|
393
|
-
const
|
|
394
|
-
const
|
|
499
|
+
const base = systemctlBaseArgs(scope);
|
|
500
|
+
const reload = spawnSync("systemctl", [...base, "daemon-reload"], { encoding: "utf8" });
|
|
501
|
+
const enable = spawnSync("systemctl", [...base, "enable", "--now", "wand.service"], { encoding: "utf8" });
|
|
502
|
+
const hints = scope === "user"
|
|
503
|
+
? "提示: 若需登出后保持运行,请运行 `loginctl enable-linger $USER`"
|
|
504
|
+
: "提示: 已写入系统级 unit;开机自启已 enable。";
|
|
395
505
|
const detail = [
|
|
506
|
+
`scope: ${scope}`,
|
|
396
507
|
`unit: ${unitPath}`,
|
|
397
508
|
`daemon-reload: ${reload.status === 0 ? "ok" : `failed (${reload.stderr.trim()})`}`,
|
|
398
509
|
`enable --now: ${enable.status === 0 ? "ok" : `failed (${enable.stderr.trim()})`}`,
|
|
399
510
|
"",
|
|
400
|
-
|
|
511
|
+
hints,
|
|
401
512
|
].join("\n");
|
|
402
513
|
if (enable.status !== 0) {
|
|
403
514
|
return {
|
|
404
515
|
ok: false,
|
|
405
|
-
message:
|
|
516
|
+
message: `已写入 unit,但 systemctl ${scope === "user" ? "--user " : ""}启用失败`,
|
|
406
517
|
detail,
|
|
407
518
|
};
|
|
408
519
|
}
|
|
409
520
|
return {
|
|
410
521
|
ok: true,
|
|
411
|
-
message: `已注册 systemd
|
|
522
|
+
message: `已注册 systemd ${scope === "user" ? "用户" : "系统"}服务: ${unitPath}`,
|
|
412
523
|
detail,
|
|
413
524
|
};
|
|
414
525
|
}
|
|
415
|
-
function
|
|
416
|
-
const unitPath =
|
|
526
|
+
function uninstallSystemdService(scope) {
|
|
527
|
+
const unitPath = servicePathFor(scope);
|
|
417
528
|
if (!existsSync(unitPath)) {
|
|
418
|
-
return { ok: false, message:
|
|
529
|
+
return { ok: false, message: `未检测到已安装的 systemd ${scope} 服务` };
|
|
419
530
|
}
|
|
420
|
-
const
|
|
531
|
+
const base = systemctlBaseArgs(scope);
|
|
532
|
+
const stop = spawnSync("systemctl", [...base, "disable", "--now", "wand.service"], { encoding: "utf8" });
|
|
421
533
|
try {
|
|
422
534
|
unlinkSync(unitPath);
|
|
423
535
|
}
|
|
424
536
|
catch (err) {
|
|
425
537
|
return { ok: false, message: `删除 unit 失败: ${errMsg(err)}` };
|
|
426
538
|
}
|
|
427
|
-
spawnSync("systemctl", [
|
|
539
|
+
spawnSync("systemctl", [...base, "daemon-reload"], { encoding: "utf8" });
|
|
428
540
|
return {
|
|
429
541
|
ok: true,
|
|
430
|
-
message:
|
|
542
|
+
message: `已卸载 systemd ${scope === "user" ? "用户" : "系统"}服务`,
|
|
431
543
|
detail: stop.status === 0 ? "disable --now: ok" : `disable --now: ${stop.stderr.trim()}`,
|
|
432
544
|
};
|
|
433
545
|
}
|
|
434
|
-
function
|
|
435
|
-
const plistPath =
|
|
546
|
+
function installLaunchdService(ctx, scope) {
|
|
547
|
+
const plistPath = servicePathFor(scope);
|
|
436
548
|
const wandBin = resolveWandBin(ctx);
|
|
437
549
|
const nodeBin = process.execPath;
|
|
550
|
+
// LaunchDaemon (system) 跑在 root,但 wand 数据应该归 ctx.configPath 的 owner;
|
|
551
|
+
// 简化处理:system 模式下不强制改 owner,让用户自己提前 chown。
|
|
438
552
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
439
553
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
440
554
|
<plist version="1.0">
|
|
@@ -474,13 +588,13 @@ function installLaunchdAgent(ctx) {
|
|
|
474
588
|
}
|
|
475
589
|
return {
|
|
476
590
|
ok: true,
|
|
477
|
-
message: `已注册 launchd
|
|
591
|
+
message: `已注册 launchd ${scope === "user" ? "用户代理" : "系统守护"}: ${plistPath}`,
|
|
478
592
|
};
|
|
479
593
|
}
|
|
480
|
-
function
|
|
481
|
-
const plistPath =
|
|
594
|
+
function uninstallLaunchdService(scope) {
|
|
595
|
+
const plistPath = servicePathFor(scope);
|
|
482
596
|
if (!existsSync(plistPath)) {
|
|
483
|
-
return { ok: false, message:
|
|
597
|
+
return { ok: false, message: `未检测到已安装的 launchd ${scope} 服务` };
|
|
484
598
|
}
|
|
485
599
|
const unload = spawnSync("launchctl", ["unload", "-w", plistPath], { encoding: "utf8" });
|
|
486
600
|
try {
|
|
@@ -491,7 +605,7 @@ function uninstallLaunchdAgent() {
|
|
|
491
605
|
}
|
|
492
606
|
return {
|
|
493
607
|
ok: true,
|
|
494
|
-
message:
|
|
608
|
+
message: `已卸载 launchd ${scope === "user" ? "用户代理" : "系统守护"}`,
|
|
495
609
|
detail: unload.status === 0 ? "unload: ok" : `unload: ${unload.stderr.trim()}`,
|
|
496
610
|
};
|
|
497
611
|
}
|
package/dist/tui/index.js
CHANGED
|
@@ -206,14 +206,14 @@ export function startTui(deps) {
|
|
|
206
206
|
layout.showToast("服务已安装,按 Shift+S 卸载", "warn", 2500);
|
|
207
207
|
return;
|
|
208
208
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
});
|
|
209
|
+
// 默认装 system-wide。非 root 时 installService 会返回明确错误,toast 自然展示。
|
|
210
|
+
const isRoot = typeof process.getuid === "function" ? process.getuid() === 0 : false;
|
|
211
|
+
const body = process.platform === "linux"
|
|
212
|
+
? `将写入 /etc/systemd/system/wand.service,systemctl enable --now,开机自启。\n${isRoot ? "当前是 root,可以直接装。" : "⚠ 需要 root,建议先 Ctrl+C 退出 TUI 再 sudo wand web 重新进。"}\n不想要 root?退出 TUI 跑 wand service:install --user (登出会被回收)。`
|
|
213
|
+
: process.platform === "darwin"
|
|
214
|
+
? `将写入 /Library/LaunchDaemons/com.wand.web.plist,launchctl load,开机自启。\n${isRoot ? "当前是 root,可以直接装。" : "⚠ 需要 root,建议先 Ctrl+C 退出 TUI 再 sudo wand web 重新进。"}`
|
|
215
|
+
: "当前平台暂不支持。";
|
|
216
|
+
const ok = await layout.confirm({ title: "注册为系统服务", body });
|
|
217
217
|
if (!ok)
|
|
218
218
|
return;
|
|
219
219
|
const r = await runOffMicrotask(() => installService({ configPath: deps.configPath }));
|
|
@@ -64,10 +64,9 @@ export function openServicePanel(deps) {
|
|
|
64
64
|
layout.showToast("服务已安装,按 u 先卸载再重装", "warn", 2500);
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
});
|
|
67
|
+
const isRoot = typeof process.getuid === "function" ? process.getuid() === 0 : false;
|
|
68
|
+
const body = `默认装 system-wide(/etc/systemd/system/ 或 /Library/LaunchDaemons/),开机自启、登出不停。${isRoot ? "" : "\n⚠ 需要 root,如果失败请退出 TUI 跑 sudo wand service:install。"}`;
|
|
69
|
+
const ok = await layout.confirm({ title: "注册服务", body });
|
|
71
70
|
if (!ok)
|
|
72
71
|
return;
|
|
73
72
|
handleResult("install", installService({ configPath }));
|
package/dist/types.d.ts
CHANGED
|
@@ -239,6 +239,8 @@ export interface CommandRequest {
|
|
|
239
239
|
cols?: number;
|
|
240
240
|
/** 创建会话时由前端测得的真实行数。 */
|
|
241
241
|
rows?: number;
|
|
242
|
+
/** 思考深度。null/缺省 视为 off(不启用思考)。 */
|
|
243
|
+
thinkingEffort?: "off" | "standard" | "deep" | "max" | null;
|
|
242
244
|
}
|
|
243
245
|
export interface InputRequest {
|
|
244
246
|
input?: string;
|