@co0ontty/wand 1.49.4 → 1.49.6-beta.gdf552c2

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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "commit": "6456cfb6702089c5ee8de3adfed235992a69fac2",
3
- "builtAt": "2026-06-05T00:58:27.023Z",
4
- "version": "1.49.4",
5
- "channel": "stable"
2
+ "commit": "df552c2ba1bf335ecc65a4ff3a4e908a49fcf9b3",
3
+ "builtAt": "2026-06-05T05:38:09.499Z",
4
+ "version": "1.49.6-beta.gdf552c2",
5
+ "channel": "beta"
6
6
  }
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import { query as sdkQuery, } from "@anthropic-ai/claude-agent-sdk";
4
4
  import { buildLanguageDirective } from "./language-prompt.js";
5
+ import { getErrorMessage } from "./error-utils.js";
5
6
  export class ClaudeRunError extends Error {
6
7
  code;
7
8
  constructor(message, code) {
@@ -125,7 +126,7 @@ export async function runClaudePrint(prompt, options) {
125
126
  if (abortController.signal.aborted) {
126
127
  throw new ClaudeRunError("Claude 调用超时。", "CLAUDE_TIMEOUT");
127
128
  }
128
- const message = err instanceof Error ? err.message : String(err);
129
+ const message = getErrorMessage(err);
129
130
  // SDK 找不到 native binary 时会抛 "Claude Code native binary not found"。
130
131
  // 极少数情况下也可能透出 ENOENT。两种都归到"CLI_MISSING",文案给用户。
131
132
  if (/Claude Code native binary not found|ENOENT/i.test(message)) {
package/dist/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import process from "node:process";
3
3
  import { hasConfigFile, isPreferenceKey, loadConfigWithStorage, resolveConfigPath, saveConfig, writePreferenceToStorage, } from "./config.js";
4
4
  import { readLiveInstance, removePidfile, removeSocketFile, socketPath, writePidfile, } from "./pidfile.js";
5
+ import { getErrorMessage } from "./error-utils.js";
5
6
  async function main() {
6
7
  const args = process.argv.slice(2);
7
8
  const command = args[0] || "help";
@@ -446,6 +447,6 @@ function printServiceResult(result, verbose) {
446
447
  }
447
448
  }
448
449
  main().catch((error) => {
449
- process.stderr.write(`[wand] ${error instanceof Error ? error.message : String(error)}\n`);
450
+ process.stderr.write(`[wand] ${getErrorMessage(error)}\n`);
450
451
  process.exitCode = 1;
451
452
  });
package/dist/config.d.ts CHANGED
@@ -41,3 +41,4 @@ export declare function applyStoragePreferences(config: WandConfig, storage: Wan
41
41
  export declare function writePreferenceToStorage(config: WandConfig, storage: WandStorage, key: PreferenceKey, value: unknown): void;
42
42
  export declare function normalizeCardDefaults(input: unknown): CardExpandDefaults;
43
43
  export declare function isExecutionMode(value: unknown): value is ExecutionMode;
44
+ export declare function normalizeMode(input: string | undefined, fallback: ExecutionMode): ExecutionMode;
package/dist/config.js CHANGED
@@ -474,6 +474,9 @@ function mergeWithDefaults(input) {
474
474
  export function isExecutionMode(value) {
475
475
  return value === "assist" || value === "agent" || value === "agent-max" || value === "auto-edit" || value === "default" || value === "full-access" || value === "native" || value === "managed";
476
476
  }
477
+ export function normalizeMode(input, fallback) {
478
+ return isExecutionMode(input) ? input : fallback;
479
+ }
477
480
  function normalizePresetCommand(command) {
478
481
  const trimmed = command.trim();
479
482
  if (trimmed === "cloud-code" || trimmed === "cloudcode" || trimmed === "claude code") {
@@ -12,6 +12,7 @@ import { chmodSync, existsSync, statSync } from "node:fs";
12
12
  import { createRequire } from "node:module";
13
13
  import path from "node:path";
14
14
  import process from "node:process";
15
+ import { getErrorMessage } from "./error-utils.js";
15
16
  const requireFromHere = createRequire(import.meta.url);
16
17
  export function ensureNodePtyHelperExecutable() {
17
18
  // spawn-helper is only used on Unix-likes. Windows uses winpty / conpty.
@@ -45,7 +46,7 @@ export function ensureNodePtyHelperExecutable() {
45
46
  process.stderr.write(`[wand] Restored +x on ${helper} (npm dropped the bit on install)\n`);
46
47
  }
47
48
  catch (err) {
48
- process.stderr.write(`[wand] Warning: could not chmod +x ${helper}: ${err instanceof Error ? err.message : String(err)}\n`
49
+ process.stderr.write(`[wand] Warning: could not chmod +x ${helper}: ${getErrorMessage(err)}\n`
49
50
  + `[wand] PTY sessions may fail to start. Run: chmod +x ${JSON.stringify(helper)}\n`);
50
51
  }
51
52
  }
@@ -0,0 +1 @@
1
+ export declare function getErrorMessage(err: unknown, fallback?: string): string;
@@ -0,0 +1,5 @@
1
+ export function getErrorMessage(err, fallback) {
2
+ if (err instanceof Error)
3
+ return err.message;
4
+ return fallback ?? String(err);
5
+ }
@@ -1,36 +1,17 @@
1
- import { execFileSync } from "node:child_process";
2
1
  import { existsSync } from "node:fs";
3
2
  import { ClaudeRunError, runClaudePrint } from "./claude-sdk-runner.js";
3
+ import { runGit as runGitBase, runGitRaw as runGitRawBase, getGitErrorMessage } from "./git-utils.js";
4
4
  const GIT_TIMEOUT_MS = 1500;
5
5
  const GIT_PUSH_TIMEOUT_MS = 30_000;
6
6
  const MAX_FILE_ENTRIES = 200;
7
7
  const CLAUDE_MESSAGE_TIMEOUT_MS = 30_000;
8
8
  const MAX_DIFF_FOR_AI = 100_000;
9
+ const GIT_MAX_BUFFER = 16 * 1024 * 1024;
9
10
  function runGit(args, cwd, timeoutMs = GIT_TIMEOUT_MS) {
10
- return execFileSync("git", args, {
11
- cwd,
12
- encoding: "utf8",
13
- stdio: ["ignore", "pipe", "pipe"],
14
- timeout: timeoutMs,
15
- maxBuffer: 16 * 1024 * 1024,
16
- }).trim();
11
+ return runGitBase(args, cwd, { timeout: timeoutMs, maxBuffer: GIT_MAX_BUFFER });
17
12
  }
18
13
  function runGitAllowEmpty(args, cwd, timeoutMs = GIT_TIMEOUT_MS) {
19
- return execFileSync("git", args, {
20
- cwd,
21
- encoding: "utf8",
22
- stdio: ["ignore", "pipe", "pipe"],
23
- timeout: timeoutMs,
24
- maxBuffer: 16 * 1024 * 1024,
25
- });
26
- }
27
- function getGitErrorMessage(error) {
28
- const e = error;
29
- if (e?.stderr && typeof e.stderr === "string")
30
- return e.stderr.trim() || e.message || "git 命令失败";
31
- if (e?.message)
32
- return e.message;
33
- return String(error);
14
+ return runGitRawBase(args, cwd, { timeout: timeoutMs, maxBuffer: GIT_MAX_BUFFER });
34
15
  }
35
16
  /** Throws `QuickCommitError` if `cwd` isn't an existing path inside a git work tree. */
36
17
  function assertGitWorkTree(cwd) {
@@ -0,0 +1,7 @@
1
+ export interface RunGitOptions {
2
+ timeout?: number;
3
+ maxBuffer?: number;
4
+ }
5
+ export declare function runGit(args: string[], cwd: string, opts?: RunGitOptions): string;
6
+ export declare function runGitRaw(args: string[], cwd: string, opts?: RunGitOptions): string;
7
+ export declare function getGitErrorMessage(error: unknown): string;
@@ -0,0 +1,27 @@
1
+ import { execFileSync } from "node:child_process";
2
+ export function runGit(args, cwd, opts = {}) {
3
+ return execFileSync("git", args, {
4
+ cwd,
5
+ encoding: "utf8",
6
+ stdio: ["ignore", "pipe", "pipe"],
7
+ timeout: opts.timeout,
8
+ maxBuffer: opts.maxBuffer,
9
+ }).trim();
10
+ }
11
+ export function runGitRaw(args, cwd, opts = {}) {
12
+ return execFileSync("git", args, {
13
+ cwd,
14
+ encoding: "utf8",
15
+ stdio: ["ignore", "pipe", "pipe"],
16
+ timeout: opts.timeout,
17
+ maxBuffer: opts.maxBuffer,
18
+ });
19
+ }
20
+ export function getGitErrorMessage(error) {
21
+ const e = error;
22
+ if (e?.stderr && typeof e.stderr === "string")
23
+ return e.stderr.trim() || e.message || "git 命令失败";
24
+ if (e?.message)
25
+ return e.message;
26
+ return String(error);
27
+ }
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync } from "node:fs";
2
- import { execFileSync } from "node:child_process";
3
2
  import path from "node:path";
3
+ import { runGit, getGitErrorMessage } from "./git-utils.js";
4
4
  const WORKTREE_MERGE_ERROR_CODES = {
5
5
  MISSING: "WORKTREE_MISSING",
6
6
  DIRTY: "WORKTREE_DIRTY",
@@ -19,13 +19,6 @@ export class WorktreeMergeError extends Error {
19
19
  this.name = "WorktreeMergeError";
20
20
  }
21
21
  }
22
- function runGit(args, cwd) {
23
- return execFileSync("git", args, {
24
- cwd,
25
- encoding: "utf8",
26
- stdio: ["ignore", "pipe", "pipe"],
27
- }).trim();
28
- }
29
22
  function sanitizeBranchSegment(value) {
30
23
  return value
31
24
  .toLowerCase()
@@ -37,15 +30,6 @@ function getCurrentBranch(repoRoot) {
37
30
  const branch = runGit(["branch", "--show-current"], repoRoot);
38
31
  return branch || "master";
39
32
  }
40
- function isGitCommandError(error) {
41
- return error instanceof Error;
42
- }
43
- function getGitCommandMessage(error) {
44
- if (isGitCommandError(error)) {
45
- return error.stderr?.trim() || error.stdout?.trim() || error.message;
46
- }
47
- return String(error);
48
- }
49
33
  function refExists(repoRoot, ref) {
50
34
  try {
51
35
  runGit(["rev-parse", "--verify", ref], repoRoot);
@@ -227,7 +211,7 @@ export function mergeSessionWorktree(options) {
227
211
  runGit(["merge", "--no-ff", "--no-edit", context.sourceBranch], context.repoRoot);
228
212
  }
229
213
  catch (error) {
230
- const message = getGitCommandMessage(error);
214
+ const message = getGitErrorMessage(error);
231
215
  throw new WorktreeMergeError(WORKTREE_MERGE_ERROR_CODES.CONFLICT, message || "合并失败,可能存在冲突。", {
232
216
  sourceBranch: context.sourceBranch,
233
217
  targetBranch: context.targetBranch,
@@ -252,7 +236,7 @@ export function mergeSessionWorktree(options) {
252
236
  };
253
237
  }
254
238
  catch (error) {
255
- throw new WorktreeMergeError(WORKTREE_MERGE_ERROR_CODES.CLEANUP_FAILED, getGitCommandMessage(error) || "已合并,但清理 worktree 失败。", {
239
+ throw new WorktreeMergeError(WORKTREE_MERGE_ERROR_CODES.CLEANUP_FAILED, getGitErrorMessage(error) || "已合并,但清理 worktree 失败。", {
256
240
  sourceBranch: context.sourceBranch,
257
241
  targetBranch: context.targetBranch,
258
242
  repoRoot: context.repoRoot,
@@ -19,6 +19,7 @@ import process from "node:process";
19
19
  import { promisify } from "node:util";
20
20
  import { whichSync } from "./path-repair.js";
21
21
  import { compareSemver } from "./version-utils.js";
22
+ import { getErrorMessage } from "./error-utils.js";
22
23
  const execFileAsync = promisify(execFile);
23
24
  export const PACKAGE_NAME = "@co0ontty/wand";
24
25
  const PACKAGE_SCOPE = "@co0ontty";
@@ -182,7 +183,7 @@ export function cleanupNpmLeftovers() {
182
183
  entries = readdirSync(scopeDir);
183
184
  }
184
185
  catch (err) {
185
- errors.push(`readdir ${scopeDir}: ${err instanceof Error ? err.message : String(err)}`);
186
+ errors.push(`readdir ${scopeDir}: ${getErrorMessage(err)}`);
186
187
  return { removed, errors };
187
188
  }
188
189
  // 残留目录形如 `.wand-PdFXStca`:以点开头 + 包基名 + 短横线 + 随机后缀
@@ -199,7 +200,7 @@ export function cleanupNpmLeftovers() {
199
200
  removed.push(fullPath);
200
201
  }
201
202
  catch (err) {
202
- errors.push(`rm ${fullPath}: ${err instanceof Error ? err.message : String(err)}`);
203
+ errors.push(`rm ${fullPath}: ${getErrorMessage(err)}`);
203
204
  }
204
205
  }
205
206
  return { removed, errors };
@@ -255,7 +256,7 @@ function validateGlobalWandInstall() {
255
256
  catch (err) {
256
257
  return {
257
258
  ok: false,
258
- message: `全局 wand CLI 无法设置执行权限: ${cliPath}: ${err instanceof Error ? err.message : String(err)}`,
259
+ message: `全局 wand CLI 无法设置执行权限: ${cliPath}: ${getErrorMessage(err)}`,
259
260
  };
260
261
  }
261
262
  }
@@ -288,7 +289,7 @@ function createGlobalInstallBackup(note) {
288
289
  }
289
290
  catch (err) {
290
291
  rmSync(backupRoot, { recursive: true, force: true });
291
- note?.(`[wand] 全局安装备份失败,继续尝试更新: ${err instanceof Error ? err.message : String(err)}`);
292
+ note?.(`[wand] 全局安装备份失败,继续尝试更新: ${getErrorMessage(err)}`);
292
293
  return { packageDir, backupDir: null };
293
294
  }
294
295
  }
@@ -311,7 +312,7 @@ function restoreGlobalInstallBackup(backup, note) {
311
312
  return true;
312
313
  }
313
314
  catch (err) {
314
- note?.(`[wand] 恢复更新前安装失败: ${err instanceof Error ? err.message : String(err)}`);
315
+ note?.(`[wand] 恢复更新前安装失败: ${getErrorMessage(err)}`);
315
316
  return false;
316
317
  }
317
318
  }
@@ -351,7 +352,7 @@ export async function installPackageGloballyAsync(pkg, timeoutMs, log) {
351
352
  return;
352
353
  }
353
354
  catch (error) {
354
- const msg = error instanceof Error ? error.message : String(error);
355
+ const msg = getErrorMessage(error);
355
356
  if (!isRecoverableInstallError(msg)) {
356
357
  throw error;
357
358
  }
@@ -368,7 +369,7 @@ export async function installPackageGloballyAsync(pkg, timeoutMs, log) {
368
369
  return;
369
370
  }
370
371
  catch (retryError) {
371
- const retryMsg = retryError instanceof Error ? retryError.message : String(retryError);
372
+ const retryMsg = getErrorMessage(retryError);
372
373
  if (!isRecoverableInstallError(retryMsg)) {
373
374
  throw retryError;
374
375
  }
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
2
  import { compareSemver } from "./version-utils.js";
3
+ import { getErrorMessage } from "./error-utils.js";
3
4
  import os from "node:os";
4
5
  import path from "node:path";
5
6
  import process from "node:process";
@@ -178,7 +179,7 @@ export async function deepRepairRuntimePath(result, opts = {}) {
178
179
  probe = await probeLoginShell(shell, opts.timeoutMs ?? DEEP_PROBE_TIMEOUT_MS);
179
180
  }
180
181
  catch (err) {
181
- result.warnings.push(`login shell 探测失败 (${shell}): ${err instanceof Error ? err.message : String(err)}`);
182
+ result.warnings.push(`login shell 探测失败 (${shell}): ${getErrorMessage(err)}`);
182
183
  result.deepProbe = "failed";
183
184
  return result;
184
185
  }
@@ -3,6 +3,7 @@ import { ProcessManager } from "./process-manager.js";
3
3
  import { StructuredSessionManager } from "./structured-session-manager.js";
4
4
  import { WandStorage } from "./storage.js";
5
5
  import { ExecutionMode, WandConfig } from "./types.js";
6
- export declare function getErrorMessage(error: unknown, fallback: string): string;
6
+ import { getErrorMessage } from "./error-utils.js";
7
+ export { getErrorMessage };
7
8
  export declare function registerSessionRoutes(app: Express, processes: ProcessManager, structured: StructuredSessionManager, storage: WandStorage, defaultMode: ExecutionMode, config: WandConfig, onSessionCreated?: (cwd: string | undefined | null) => void): void;
8
9
  export declare function registerClaudeHistoryRoutes(app: Express, processes: ProcessManager, storage: WandStorage): void;
@@ -1,10 +1,10 @@
1
1
  import express from "express";
2
2
  import { SessionInputError } from "./process-manager.js";
3
+ import { normalizeMode } from "./config.js";
3
4
  import { checkSessionWorktreeMergeability, cleanupSessionWorktree, getWorktreeMergeErrorCode, mergeSessionWorktree, WorktreeMergeError } from "./git-worktree.js";
4
5
  import { getGitStatus, QuickCommitError, runQuickCommit, runTagHead, runPush, generateCommitMessageOnly, } from "./git-quick-commit.js";
5
- export function getErrorMessage(error, fallback) {
6
- return error instanceof Error ? error.message : fallback;
7
- }
6
+ import { getErrorMessage } from "./error-utils.js";
7
+ export { getErrorMessage };
8
8
  function getInputErrorResponse(error, sessionId) {
9
9
  if (error instanceof SessionInputError) {
10
10
  const statusCode = error.code === "SESSION_NOT_FOUND" ? 404 : 409;
@@ -34,9 +34,6 @@ function getInputDebugMeta(error) {
34
34
  }
35
35
  return { error };
36
36
  }
37
- function normalizeMode(mode, defaultMode) {
38
- return mode ?? defaultMode;
39
- }
40
37
  function getHiddenClaudeSessionIds(storage) {
41
38
  const raw = storage.getConfigValue("hidden_claude_session_ids");
42
39
  if (!raw)
package/dist/server.js CHANGED
@@ -15,7 +15,7 @@ import { ensureAvatarSeed, getAvatarSvg } from "./avatar.js";
15
15
  import { createSession, readSessionCookie, revokeSession, SESSION_COOKIE_HTTP, SESSION_COOKIE_HTTPS, SESSION_COOKIE_LEGACY, setAuthStorage, validateSession, } from "./auth.js";
16
16
  import { ensureCertificates } from "./cert.js";
17
17
  import { buildChildEnv } from "./env-utils.js";
18
- import { isExecutionMode, PREFERENCE_KEYS, resolveConfigDir, saveConfig, writePreferenceToStorage, } from "./config.js";
18
+ import { isExecutionMode, normalizeMode, PREFERENCE_KEYS, resolveConfigDir, saveConfig, writePreferenceToStorage, } from "./config.js";
19
19
  import { getCachedModels, refreshModels } from "./models.js";
20
20
  import { ProcessManager } from "./process-manager.js";
21
21
  import { SessionLogger } from "./session-logger.js";
@@ -465,9 +465,6 @@ function decodeConnectCode(code) {
465
465
  return null;
466
466
  }
467
467
  }
468
- function normalizeMode(input, fallback) {
469
- return isExecutionMode(input) ? input : fallback;
470
- }
471
468
  /** Match a semver-looking token in a file name (with optional pre-release / build metadata). */
472
469
  function resolveAndroidApkDir(configDir, config) {
473
470
  const configuredDir = config.android?.apkDir?.trim();
@@ -652,7 +649,7 @@ process.on("uncaughtException", (err) => {
652
649
  process.exit(1);
653
650
  });
654
651
  process.on("unhandledRejection", (reason) => {
655
- const msg = reason instanceof Error ? reason.message : String(reason);
652
+ const msg = getErrorMessage(reason);
656
653
  wandError("未处理的异步错误", msg);
657
654
  });
658
655
  function wandError(label, message, suggestion) {
@@ -1,5 +1,6 @@
1
1
  import process from "node:process";
2
2
  import { detectInstalledScope, installService } from "./tui/commands.js";
3
+ import { getErrorMessage } from "./error-utils.js";
3
4
  export function repairServiceUnitAfterUpdate(configPath) {
4
5
  // 仅 Linux(systemd) / macOS(launchd) 有服务模型。
5
6
  if (process.platform !== "linux" && process.platform !== "darwin") {
@@ -37,7 +38,7 @@ export function repairServiceUnitAfterUpdate(configPath) {
37
38
  return {
38
39
  repaired: false,
39
40
  scope,
40
- message: `服务 unit 重写异常: ${err instanceof Error ? err.message : String(err)}`,
41
+ message: `服务 unit 重写异常: ${getErrorMessage(err)}`,
41
42
  };
42
43
  }
43
44
  }
@@ -8,6 +8,7 @@ import { query as sdkQuery } from "@anthropic-ai/claude-agent-sdk";
8
8
  import { prepareSessionWorktree } from "./git-worktree.js";
9
9
  import { truncateMessagesForTransport } from "./message-truncator.js";
10
10
  import { buildChildEnv, isRunningAsRoot } from "./env-utils.js";
11
+ import { getErrorMessage } from "./error-utils.js";
11
12
  import { buildLanguageDirective, buildManagedAutonomyDirective } from "./language-prompt.js";
12
13
  function defaultStructuredRunner(provider) {
13
14
  return provider === "codex" ? "codex-cli-exec" : "claude-cli-print";
@@ -740,7 +741,7 @@ export class StructuredSessionManager {
740
741
  return finished;
741
742
  }
742
743
  catch (error) {
743
- const message = error instanceof Error ? error.message : String(error);
744
+ const message = getErrorMessage(error);
744
745
  const current = this.sessions.get(id);
745
746
  if (!current)
746
747
  throw error;
@@ -2304,7 +2305,7 @@ export class StructuredSessionManager {
2304
2305
  kind: "claude-sdk-error",
2305
2306
  spawnedAt,
2306
2307
  closedAt: new Date().toISOString(),
2307
- error: err instanceof Error ? err.message : String(err),
2308
+ error: getErrorMessage(err),
2308
2309
  });
2309
2310
  throw err;
2310
2311
  }
@@ -14,6 +14,7 @@ import { checkPackageUpdateSync, getInstallSpecForChannel, installPackageGloball
14
14
  import { whichSync } from "../path-repair.js";
15
15
  import { computeRelaunch } from "../relaunch.js";
16
16
  import { ensureDatabaseFile, resolveDatabasePath, WandStorage } from "../storage.js";
17
+ import { getErrorMessage } from "../error-utils.js";
17
18
  // ─── 重启 ────────────────────────────────────────────────────────────────
18
19
  /**
19
20
  * 重启当前进程。
@@ -45,7 +46,7 @@ export function restartSelf() {
45
46
  return { ok: true, message: "重启中…新进程已派生" };
46
47
  }
47
48
  catch (err) {
48
- return { ok: false, message: `重启失败: ${errMsg(err)}` };
49
+ return { ok: false, message: `重启失败: ${getErrorMessage(err)}` };
49
50
  }
50
51
  }
51
52
  // ─── 检查 / 安装更新 ────────────────────────────────────────────────────
@@ -126,7 +127,7 @@ export function openInBrowser(url) {
126
127
  return { ok: true, message: `已在浏览器打开: ${url}` };
127
128
  }
128
129
  catch (err) {
129
- return { ok: false, message: `打开失败: ${errMsg(err)}` };
130
+ return { ok: false, message: `打开失败: ${getErrorMessage(err)}` };
130
131
  }
131
132
  }
132
133
  // ─── 复制到剪贴板 ───────────────────────────────────────────────────────
@@ -573,7 +574,7 @@ function installSystemdService(ctx, scope) {
573
574
  writeFileSync(unitPath, unit, "utf8");
574
575
  }
575
576
  catch (err) {
576
- return { ok: false, message: `写入 unit 失败: ${errMsg(err)}` };
577
+ return { ok: false, message: `写入 unit 失败: ${getErrorMessage(err)}` };
577
578
  }
578
579
  const base = systemctlBaseArgs(scope);
579
580
  const reload = spawnSync("systemctl", [...base, "daemon-reload"], { encoding: "utf8" });
@@ -613,7 +614,7 @@ function uninstallSystemdService(scope) {
613
614
  unlinkSync(unitPath);
614
615
  }
615
616
  catch (err) {
616
- return { ok: false, message: `删除 unit 失败: ${errMsg(err)}` };
617
+ return { ok: false, message: `删除 unit 失败: ${getErrorMessage(err)}` };
617
618
  }
618
619
  spawnSync("systemctl", [...base, "daemon-reload"], { encoding: "utf8" });
619
620
  return {
@@ -661,7 +662,7 @@ function installLaunchdService(ctx, scope) {
661
662
  writeFileSync(plistPath, plist, "utf8");
662
663
  }
663
664
  catch (err) {
664
- return { ok: false, message: `写入 plist 失败: ${errMsg(err)}` };
665
+ return { ok: false, message: `写入 plist 失败: ${getErrorMessage(err)}` };
665
666
  }
666
667
  const load = spawnSync("launchctl", ["load", "-w", plistPath], { encoding: "utf8" });
667
668
  if (load.status !== 0) {
@@ -686,7 +687,7 @@ function uninstallLaunchdService(scope) {
686
687
  unlinkSync(plistPath);
687
688
  }
688
689
  catch (err) {
689
- return { ok: false, message: `删除 plist 失败: ${errMsg(err)}` };
690
+ return { ok: false, message: `删除 plist 失败: ${getErrorMessage(err)}` };
690
691
  }
691
692
  return {
692
693
  ok: true,
@@ -695,7 +696,4 @@ function uninstallLaunchdService(scope) {
695
696
  };
696
697
  }
697
698
  // ─── 工具 ────────────────────────────────────────────────────────────────
698
- function errMsg(err) {
699
- return err instanceof Error ? err.message : String(err);
700
- }
701
699
  // compareSemver 已统一到 ../version-utils.ts
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import net from "node:net";
7
7
  import { existsSync, unlinkSync } from "node:fs";
8
+ import { getErrorMessage } from "../error-utils.js";
8
9
  export function startIpcServer(deps) {
9
10
  if (!deps.socketPath)
10
11
  return null;
@@ -70,7 +71,7 @@ export function startIpcServer(deps) {
70
71
  const err = {
71
72
  id: req.id,
72
73
  ok: false,
73
- error: e instanceof Error ? e.message : String(e),
74
+ error: getErrorMessage(e),
74
75
  };
75
76
  try {
76
77
  conn.write(JSON.stringify(err) + "\n");
@@ -1,4 +1,5 @@
1
1
  import { createRequire } from "node:module";
2
+ import { stripAnsi } from "../pty-text-utils.js";
2
3
  const require = createRequire(import.meta.url);
3
4
  // neo-blessed 是 CJS,没有匹配 @types/blessed 的 default export,统一当 any 用。
4
5
  const blessed = require("neo-blessed");
@@ -512,10 +513,6 @@ function ansiColorize(level, line) {
512
513
  return `\x1b[33m${cleaned}\x1b[39m`;
513
514
  return cleaned;
514
515
  }
515
- function stripAnsi(line) {
516
- // eslint-disable-next-line no-control-regex
517
- return line.replace(/\x1b\[[0-9;]*m/g, "");
518
- }
519
516
  function formatTs(ms) {
520
517
  const d = new Date(ms);
521
518
  const hh = String(d.getHours()).padStart(2, "0");