@co0ontty/wand 1.72.0 → 1.74.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/dist/build-info.json +3 -3
- package/dist/claude-sdk-runner.d.ts +2 -0
- package/dist/claude-sdk-runner.js +2 -0
- package/dist/cli.js +231 -4
- package/dist/git-quick-commit.d.ts +13 -2
- package/dist/git-quick-commit.js +259 -17
- package/dist/models.js +11 -6
- package/dist/pidfile.js +17 -1
- package/dist/server-session-routes.js +12 -3
- package/dist/server.d.ts +7 -0
- package/dist/server.js +47 -7
- package/dist/tui/commands.js +56 -6
- package/dist/tui/index.js +2 -2
- package/dist/tui/ipc-server.js +20 -2
- package/dist/web-ui/content/scripts.js +32 -32
- 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/package.json +1 -1
package/dist/tui/commands.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 命令本身不直接写 stdout / stderr —— TUI 模式下 stderr 已经被 log-bus 劫持。
|
|
6
6
|
*/
|
|
7
7
|
import { spawn, spawnSync } from "node:child_process";
|
|
8
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync, unlinkSync } from "node:fs";
|
|
9
9
|
import os from "node:os";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import process from "node:process";
|
|
@@ -513,12 +513,59 @@ function currentUserName() {
|
|
|
513
513
|
return process.env.USER || process.env.LOGNAME || "root";
|
|
514
514
|
}
|
|
515
515
|
}
|
|
516
|
+
function ownerUserNameForPath(targetPath) {
|
|
517
|
+
const candidates = [targetPath, path.dirname(targetPath)];
|
|
518
|
+
for (const candidate of candidates) {
|
|
519
|
+
try {
|
|
520
|
+
const uid = statUid(candidate);
|
|
521
|
+
if (process.env.SUDO_UID === String(uid) && process.env.SUDO_USER) {
|
|
522
|
+
return process.env.SUDO_USER;
|
|
523
|
+
}
|
|
524
|
+
const resolved = spawnSync("id", ["-un", String(uid)], { encoding: "utf8", timeout: 3000 });
|
|
525
|
+
const name = (resolved.stdout || "").trim();
|
|
526
|
+
if (resolved.status === 0 && name)
|
|
527
|
+
return name;
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
/* try next candidate */
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return process.env.SUDO_USER || currentUserName();
|
|
534
|
+
}
|
|
535
|
+
function homeForUser(userName, fallback) {
|
|
536
|
+
if (!userName || userName === "root")
|
|
537
|
+
return fallback;
|
|
538
|
+
if (process.platform === "darwin") {
|
|
539
|
+
const resolved = spawnSync("dscl", [".", "-read", `/Users/${userName}`, "NFSHomeDirectory"], {
|
|
540
|
+
encoding: "utf8",
|
|
541
|
+
timeout: 3000,
|
|
542
|
+
});
|
|
543
|
+
const match = (resolved.stdout || "").match(/NFSHomeDirectory:\s*(.+)/);
|
|
544
|
+
if (resolved.status === 0 && match?.[1])
|
|
545
|
+
return match[1].trim();
|
|
546
|
+
}
|
|
547
|
+
if (process.platform === "linux") {
|
|
548
|
+
const resolved = spawnSync("getent", ["passwd", userName], { encoding: "utf8", timeout: 3000 });
|
|
549
|
+
const home = (resolved.stdout || "").split(":")[5];
|
|
550
|
+
if (resolved.status === 0 && home)
|
|
551
|
+
return home.trim();
|
|
552
|
+
}
|
|
553
|
+
return fallback;
|
|
554
|
+
}
|
|
555
|
+
function statUid(targetPath) {
|
|
556
|
+
return readFileStat(targetPath).uid;
|
|
557
|
+
}
|
|
558
|
+
function readFileStat(targetPath) {
|
|
559
|
+
return statSync(targetPath);
|
|
560
|
+
}
|
|
516
561
|
function installSystemdService(ctx, scope) {
|
|
517
562
|
const unitPath = servicePathFor(scope);
|
|
518
563
|
const wandBin = resolveWandBin(ctx);
|
|
519
564
|
const nodeBin = process.execPath;
|
|
520
565
|
const nodeBinDir = path.dirname(nodeBin);
|
|
521
|
-
const
|
|
566
|
+
const runUser = scope === "system" ? ownerUserNameForPath(ctx.configPath) : currentUserName();
|
|
567
|
+
const fallbackHome = process.env.HOME || os.homedir();
|
|
568
|
+
const runHome = scope === "system" ? homeForUser(runUser, fallbackHome) : fallbackHome;
|
|
522
569
|
// 关键:把调用 `wand service:install` 时的真实 PATH 写进 unit。
|
|
523
570
|
// 否则 systemd 默认 PATH 极简(system scope 之前写死 `nodeBin:/usr/local/...`,
|
|
524
571
|
// user scope 干脆没写),spawn 出的 claude/codex 子进程会撞 "command not found"
|
|
@@ -544,7 +591,6 @@ function installSystemdService(ctx, scope) {
|
|
|
544
591
|
];
|
|
545
592
|
let unitLines;
|
|
546
593
|
if (scope === "system") {
|
|
547
|
-
const runUser = currentUserName();
|
|
548
594
|
unitLines = [
|
|
549
595
|
...commonExec,
|
|
550
596
|
`User=${runUser}`,
|
|
@@ -628,16 +674,20 @@ function installLaunchdService(ctx, scope) {
|
|
|
628
674
|
const wandBin = resolveWandBin(ctx);
|
|
629
675
|
const nodeBin = process.execPath;
|
|
630
676
|
const nodeBinDir = path.dirname(nodeBin);
|
|
631
|
-
const
|
|
677
|
+
const runUser = scope === "system" ? ownerUserNameForPath(ctx.configPath) : currentUserName();
|
|
678
|
+
const fallbackHome = process.env.HOME || os.homedir();
|
|
679
|
+
const runHome = scope === "system" ? homeForUser(runUser, fallbackHome) : fallbackHome;
|
|
632
680
|
// 与 systemd 同理:launchd 默认 PATH 极简,spawn 出的 claude 会找不到。
|
|
633
681
|
const servicePath = buildServicePath(nodeBinDir, runHome);
|
|
634
|
-
|
|
635
|
-
|
|
682
|
+
const userNameField = scope === "system"
|
|
683
|
+
? ` <key>UserName</key><string>${runUser}</string>\n`
|
|
684
|
+
: "";
|
|
636
685
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
637
686
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
638
687
|
<plist version="1.0">
|
|
639
688
|
<dict>
|
|
640
689
|
<key>Label</key><string>com.wand.web</string>
|
|
690
|
+
${userNameField} <key>WorkingDirectory</key><string>${runHome}</string>
|
|
641
691
|
<key>ProgramArguments</key>
|
|
642
692
|
<array>
|
|
643
693
|
<string>${nodeBin}</string>
|
package/dist/tui/index.js
CHANGED
|
@@ -218,9 +218,9 @@ export function startTui(deps) {
|
|
|
218
218
|
// 默认装 system-wide。非 root 时 installService 会返回明确错误,toast 自然展示。
|
|
219
219
|
const isRoot = typeof process.getuid === "function" ? process.getuid() === 0 : false;
|
|
220
220
|
const body = process.platform === "linux"
|
|
221
|
-
? `将写入 /etc/systemd/system/wand.service,systemctl enable --now,开机自启。\n${isRoot ? "当前是 root,可以直接装。" : "⚠ 需要 root
|
|
221
|
+
? `将写入 /etc/systemd/system/wand.service,systemctl enable --now,开机自启。\n${isRoot ? "当前是 root,可以直接装。" : "⚠ 需要 root,请退出 TUI 后直接跑 sudo wand service:install。"}\n不想要 root?退出 TUI 跑 wand service:install --user (登出会被回收)。`
|
|
222
222
|
: process.platform === "darwin"
|
|
223
|
-
? `将写入 /Library/LaunchDaemons/com.wand.web.plist,launchctl load,开机自启。\n${isRoot ? "当前是 root,可以直接装。" : "⚠ 需要 root
|
|
223
|
+
? `将写入 /Library/LaunchDaemons/com.wand.web.plist,launchctl load,开机自启。\n${isRoot ? "当前是 root,可以直接装。" : "⚠ 需要 root,请退出 TUI 后直接跑 sudo wand service:install。"}`
|
|
224
224
|
: "当前平台暂不支持。";
|
|
225
225
|
const ok = await layout.confirm({ title: "注册为系统服务", body });
|
|
226
226
|
if (!ok)
|
package/dist/tui/ipc-server.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Windows 不支持(socketPath 返回空字符串),调用方应跳过 startIpcServer。
|
|
5
5
|
*/
|
|
6
6
|
import net from "node:net";
|
|
7
|
-
import { existsSync, unlinkSync } from "node:fs";
|
|
7
|
+
import { chmodSync, chownSync, existsSync, statSync, unlinkSync } from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
8
9
|
import { getErrorMessage } from "../error-utils.js";
|
|
9
10
|
export function startIpcServer(deps) {
|
|
10
11
|
if (!deps.socketPath)
|
|
@@ -83,7 +84,9 @@ export function startIpcServer(deps) {
|
|
|
83
84
|
// 不要崩进程;attach 模式不可用也不影响主功能。
|
|
84
85
|
process.stderr.write(`[wand] IPC server error: ${err.message}\n`);
|
|
85
86
|
});
|
|
86
|
-
server.listen(deps.socketPath)
|
|
87
|
+
server.listen(deps.socketPath, () => {
|
|
88
|
+
applySocketOwnership(deps.socketPath);
|
|
89
|
+
});
|
|
87
90
|
return {
|
|
88
91
|
close: async () => {
|
|
89
92
|
await new Promise((resolve) => {
|
|
@@ -99,3 +102,18 @@ export function startIpcServer(deps) {
|
|
|
99
102
|
},
|
|
100
103
|
};
|
|
101
104
|
}
|
|
105
|
+
function applySocketOwnership(socketPath) {
|
|
106
|
+
try {
|
|
107
|
+
const owner = statSync(path.dirname(socketPath));
|
|
108
|
+
chownSync(socketPath, owner.uid, owner.gid);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
/* best effort: non-root processes cannot chown, and usually already own the socket */
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
chmodSync(socketPath, 0o600);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
/* noop */
|
|
118
|
+
}
|
|
119
|
+
}
|