sessix-server 0.2.5 → 0.2.7

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/server.js CHANGED
@@ -52,7 +52,10 @@ var zh = {
52
52
  autoDiscoveryHint: " \u5982\u5728\u516C\u5171\u7F51\u7EDC\uFF0C\u5EFA\u8BAE\u5173\u95ED: SESSIX_AUTO_CONNECT=false npx sessix-server",
53
53
  autoDiscoveryOff: " \u2139\uFE0F \u81EA\u52A8\u53D1\u73B0\u5DF2\u5173\u95ED\uFF0C\u624B\u673A\u9700\u624B\u52A8\u8F93\u5165\u5730\u5740\u8FDE\u63A5",
54
54
  pairingOpen: " \u{1F513} \u914D\u5BF9\u6A21\u5F0F\u5DF2\u5F00\u542F\uFF085 \u5206\u949F\u5185\u6709\u6548\uFF09\u2014 \u6309 p \u91CD\u65B0\u5F00\u542F",
55
+ pressT: " \u{1F511} \u6309 t \u91CD\u7F6E Token\uFF08\u6CC4\u9732\u540E\u5237\u65B0\uFF09",
55
56
  pairingReopened: "\u{1F513} \u914D\u5BF9\u6A21\u5F0F\u5DF2\u91CD\u65B0\u5F00\u542F\uFF085 \u5206\u949F\uFF09",
57
+ tokenRegenerated: "\u{1F511} Token \u5DF2\u91CD\u7F6E\uFF0C\u6240\u6709\u5BA2\u6237\u7AEF\u5DF2\u65AD\u5F00\uFF0C\u8BF7\u91CD\u65B0\u626B\u7801\u914D\u5BF9",
58
+ tokenRegenerateFailed: "Token \u91CD\u7F6E\u5931\u8D25:",
56
59
  updateAvailable: "\u53D1\u73B0\u65B0\u7248\u672C v{{latest}}\uFF08\u5F53\u524D v{{current}}\uFF09\uFF0C\u8FD0\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\uFF1A",
57
60
  receivedSignal: "\u6536\u5230 {{signal}}\uFF0C\u6B63\u5728\u4F18\u96C5\u5173\u95ED...",
58
61
  goodbye: "\u6240\u6709\u670D\u52A1\u5DF2\u5173\u95ED\uFF0C\u518D\u89C1\uFF01",
@@ -81,7 +84,8 @@ var zh = {
81
84
  activityPushEnabled: "ActivityKit Push \u5DF2\u542F\u7528",
82
85
  activityPushFailed: "ActivityKit Push \u521D\u59CB\u5316\u5931\u8D25:",
83
86
  activityPushContinue: "\u7EE7\u7EED\u542F\u52A8\uFF08Live Activity \u540E\u53F0\u63A8\u9001\u4E0D\u53EF\u7528\uFF09",
84
- noActiveLoginProcess: "\u6CA1\u6709\u6D3B\u8DC3\u7684\u767B\u5F55\u8FDB\u7A0B"
87
+ noActiveLoginProcess: "\u6CA1\u6709\u6D3B\u8DC3\u7684\u767B\u5F55\u8FDB\u7A0B",
88
+ tokenRegenerated: "Token \u5DF2\u91CD\u7F6E: {{token}}"
85
89
  },
86
90
  ws: {
87
91
  started: "WebSocket \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 {{port}}",
@@ -158,7 +162,10 @@ var en = {
158
162
  autoDiscoveryHint: " On public networks, disable with: SESSIX_AUTO_CONNECT=false npx sessix-server",
159
163
  autoDiscoveryOff: " Auto-discovery disabled, phone must enter address manually",
160
164
  pairingOpen: " \u{1F513} Pairing mode open (5 min) \u2014 press p to reopen",
165
+ pressT: " \u{1F511} Press t to regenerate token (refresh after leak)",
161
166
  pairingReopened: "\u{1F513} Pairing mode reopened (5 min)",
167
+ tokenRegenerated: "\u{1F511} Token regenerated, all clients disconnected. Scan QR to re-pair",
168
+ tokenRegenerateFailed: "Token regeneration failed:",
162
169
  updateAvailable: "New version v{{latest}} available (current v{{current}}). Update with:",
163
170
  receivedSignal: "Received {{signal}}, graceful shutdown...",
164
171
  goodbye: "All services closed, goodbye!",
@@ -187,7 +194,8 @@ var en = {
187
194
  activityPushEnabled: "ActivityKit Push enabled",
188
195
  activityPushFailed: "ActivityKit Push init failed:",
189
196
  activityPushContinue: "Continuing startup (Live Activity background push unavailable)",
190
- noActiveLoginProcess: "No active login process"
197
+ noActiveLoginProcess: "No active login process",
198
+ tokenRegenerated: "Token regenerated: {{token}}"
191
199
  },
192
200
  ws: {
193
201
  started: "WebSocket server started on port {{port}}",
@@ -293,40 +301,89 @@ function t(key, params) {
293
301
  }
294
302
 
295
303
  // src/server.ts
296
- var import_uuid4 = require("uuid");
304
+ var import_uuid5 = require("uuid");
297
305
  var import_promises4 = require("fs/promises");
298
- var import_node_os5 = require("os");
299
- var import_node_path4 = require("path");
300
- var import_node_child_process2 = require("child_process");
306
+ var import_node_os6 = require("os");
307
+ var import_node_path5 = require("path");
308
+ var import_node_child_process5 = require("child_process");
301
309
  var import_node_util = require("util");
302
310
 
303
311
  // src/providers/ProcessProvider.ts
304
- var import_child_process2 = require("child_process");
312
+ var import_child_process = require("child_process");
305
313
  var import_readline = require("readline");
306
314
  var import_events = require("events");
307
- var import_node_os = require("os");
315
+ var import_node_os2 = require("os");
308
316
  var import_uuid = require("uuid");
309
317
 
310
318
  // src/utils/claudePath.ts
311
- var import_child_process = require("child_process");
319
+ var import_node_child_process2 = require("child_process");
320
+ var import_node_fs = require("fs");
321
+ var import_node_path = require("path");
322
+ var import_node_os = require("os");
323
+
324
+ // src/utils/platform.ts
325
+ var import_node_child_process = require("child_process");
326
+ var isWindows = process.platform === "win32";
327
+ function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
328
+ return new Promise((resolve) => {
329
+ if (proc.exitCode !== null || proc.signalCode !== null) {
330
+ resolve();
331
+ return;
332
+ }
333
+ const onExit = () => {
334
+ clearTimeout(timer);
335
+ resolve();
336
+ };
337
+ proc.once("exit", onExit);
338
+ if (isWindows) {
339
+ if (proc.pid) {
340
+ (0, import_node_child_process.spawn)("taskkill", ["/PID", String(proc.pid), "/T", "/F"], { stdio: "ignore" });
341
+ }
342
+ } else {
343
+ proc.kill("SIGTERM");
344
+ }
345
+ const timer = setTimeout(() => {
346
+ if (proc.exitCode === null && proc.signalCode === null) {
347
+ if (!isWindows) {
348
+ proc.kill("SIGKILL");
349
+ }
350
+ }
351
+ resolve();
352
+ }, timeoutMs);
353
+ });
354
+ }
355
+ function isNormalExit(code, signal) {
356
+ if (code === 0) return true;
357
+ if (isWindows) {
358
+ return code === 1;
359
+ }
360
+ return code === 143 || signal === "SIGTERM";
361
+ }
362
+
363
+ // src/utils/claudePath.ts
312
364
  function findClaudePath() {
313
365
  try {
314
- return (0, import_child_process.execSync)("which claude", { encoding: "utf-8" }).trim();
366
+ const cmd = isWindows ? "where claude" : "which claude";
367
+ return (0, import_node_child_process2.execSync)(cmd, { encoding: "utf-8" }).trim().split("\n")[0];
315
368
  } catch {
316
- const candidates = [
317
- `${process.env.HOME}/.local/bin/claude`,
318
- "/usr/local/bin/claude",
319
- "/opt/homebrew/bin/claude"
320
- ];
321
- for (const candidate of candidates) {
322
- try {
323
- (0, import_child_process.execSync)(`test -x "${candidate}"`);
324
- return candidate;
325
- } catch {
326
- }
369
+ }
370
+ const candidates = isWindows ? [
371
+ (0, import_node_path.join)(process.env.LOCALAPPDATA ?? "", "Programs", "claude", "claude.exe"),
372
+ (0, import_node_path.join)((0, import_node_os.homedir)(), "AppData", "Local", "Programs", "claude", "claude.exe"),
373
+ (0, import_node_path.join)((0, import_node_os.homedir)(), ".claude", "local", "claude.exe")
374
+ ] : [
375
+ (0, import_node_path.join)((0, import_node_os.homedir)(), ".local", "bin", "claude"),
376
+ "/usr/local/bin/claude",
377
+ "/opt/homebrew/bin/claude"
378
+ ];
379
+ for (const candidate of candidates) {
380
+ try {
381
+ (0, import_node_fs.accessSync)(candidate, import_node_fs.constants.X_OK);
382
+ return candidate;
383
+ } catch {
327
384
  }
328
- return "claude";
329
385
  }
386
+ return "claude";
330
387
  }
331
388
 
332
389
  // src/providers/ProcessProvider.ts
@@ -399,19 +456,7 @@ var ProcessProvider = class {
399
456
  entry.process.stdin?.end();
400
457
  } catch {
401
458
  }
402
- entry.process.kill("SIGTERM");
403
- await new Promise((resolve) => {
404
- const timeout = setTimeout(() => {
405
- if (entry.process.exitCode === null && entry.process.signalCode === null) {
406
- entry.process.kill("SIGKILL");
407
- }
408
- resolve();
409
- }, 3e3);
410
- entry.process.once("exit", () => {
411
- clearTimeout(timeout);
412
- resolve();
413
- });
414
- });
459
+ await killProcessCrossPlatform(entry.process);
415
460
  }
416
461
  this.emittedQuestionToolUseIds.delete(sessionId);
417
462
  this.activeSessions.delete(sessionId);
@@ -441,7 +486,7 @@ var ProcessProvider = class {
441
486
  entry.process.stdin?.end();
442
487
  } catch {
443
488
  }
444
- entry.process.kill("SIGTERM");
489
+ killProcessCrossPlatform(entry.process);
445
490
  }
446
491
  } else {
447
492
  console.log(`[ProcessProvider] Session ${sessionId}: process exited, respawning`);
@@ -523,7 +568,7 @@ var ProcessProvider = class {
523
568
  }
524
569
  const env = { ...process.env, SESSIX_SESSION_ID: sessionId };
525
570
  delete env.CLAUDECODE;
526
- const proc = (0, import_child_process2.spawn)(CLAUDE_PATH, args, {
571
+ const proc = (0, import_child_process.spawn)(CLAUDE_PATH, args, {
527
572
  cwd: projectPath,
528
573
  env,
529
574
  stdio: ["pipe", "pipe", "pipe"]
@@ -669,7 +714,7 @@ var ProcessProvider = class {
669
714
  entry.session.lastActiveAt = Date.now();
670
715
  const alreadyHasResult = entry.session.status === "idle" || entry.session.status === "error";
671
716
  if (alreadyHasResult) return;
672
- const isNormal = code === 0 || code === 143 || signal === "SIGTERM";
717
+ const isNormal = isNormalExit(code, signal);
673
718
  entry.session.status = isNormal ? "idle" : "error";
674
719
  if (!isNormal) {
675
720
  console.error(
@@ -735,8 +780,8 @@ ${context}`;
735
780
  return new Promise((resolve, reject) => {
736
781
  const env = { ...process.env };
737
782
  delete env.CLAUDECODE;
738
- const proc = (0, import_child_process2.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
739
- cwd: (0, import_node_os.homedir)(),
783
+ const proc = (0, import_child_process.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
784
+ cwd: (0, import_node_os2.homedir)(),
740
785
  env,
741
786
  stdio: ["pipe", "pipe", "pipe"]
742
787
  });
@@ -1507,6 +1552,13 @@ var WsBridge = class _WsBridge {
1507
1552
  getConnectionCount() {
1508
1553
  return this.wss.clients.size;
1509
1554
  }
1555
+ /** 更新 token 并断开所有现有连接(token 刷新后需重新配对) */
1556
+ updateToken(newToken) {
1557
+ this.token = newToken;
1558
+ for (const ws of this.wss.clients) {
1559
+ ws.close(4001, "Token regenerated");
1560
+ }
1561
+ }
1510
1562
  /** 优雅关闭 WebSocket 服务 */
1511
1563
  close() {
1512
1564
  return new Promise((resolve, reject) => {
@@ -1628,15 +1680,15 @@ var WsBridge = class _WsBridge {
1628
1680
 
1629
1681
  // src/approval/ApprovalProxy.ts
1630
1682
  var import_node_http = __toESM(require("http"));
1631
- var import_node_fs = __toESM(require("fs"));
1632
- var import_node_path = __toESM(require("path"));
1633
- var import_node_os2 = __toESM(require("os"));
1683
+ var import_node_fs2 = __toESM(require("fs"));
1684
+ var import_node_path2 = __toESM(require("path"));
1685
+ var import_node_os3 = __toESM(require("os"));
1634
1686
  var import_uuid3 = require("uuid");
1635
1687
  var ApprovalProxy = class _ApprovalProxy {
1636
1688
  server;
1637
1689
  token;
1638
1690
  port;
1639
- settingsPath = import_node_path.default.join(import_node_os2.default.homedir(), ".claude", "settings.json");
1691
+ settingsPath = import_node_path2.default.join(import_node_os3.default.homedir(), ".claude", "settings.json");
1640
1692
  /** 待处理的审批请求:requestId -> { resolve, timer, request } */
1641
1693
  pendingApprovals = /* @__PURE__ */ new Map();
1642
1694
  /** 审批请求回调(通知外部推送到手机) */
@@ -1741,7 +1793,7 @@ var ApprovalProxy = class _ApprovalProxy {
1741
1793
  isToolInClaudeSettings(toolName, projectPath) {
1742
1794
  const checkPath = (filepath) => {
1743
1795
  try {
1744
- const raw = import_node_fs.default.readFileSync(filepath, "utf-8");
1796
+ const raw = import_node_fs2.default.readFileSync(filepath, "utf-8");
1745
1797
  const settings = JSON.parse(raw);
1746
1798
  const allow = settings?.permissions?.allow ?? [];
1747
1799
  return allow.some((entry) => {
@@ -1755,24 +1807,24 @@ var ApprovalProxy = class _ApprovalProxy {
1755
1807
  }
1756
1808
  };
1757
1809
  if (projectPath) {
1758
- const projectSettingsPath = import_node_path.default.join(projectPath, ".claude", "settings.json");
1810
+ const projectSettingsPath = import_node_path2.default.join(projectPath, ".claude", "settings.json");
1759
1811
  if (checkPath(projectSettingsPath)) return true;
1760
1812
  }
1761
1813
  return checkPath(this.settingsPath);
1762
1814
  }
1763
1815
  /** 将工具写入 settings.json permissions.allow(项目级或全局) */
1764
1816
  addToClaudeSettings(projectPath, toolName) {
1765
- const targetPath = projectPath ? import_node_path.default.join(projectPath, ".claude", "settings.json") : this.settingsPath;
1817
+ const targetPath = projectPath ? import_node_path2.default.join(projectPath, ".claude", "settings.json") : this.settingsPath;
1766
1818
  try {
1767
1819
  if (projectPath) {
1768
- const dir = import_node_path.default.dirname(targetPath);
1769
- if (!import_node_fs.default.existsSync(dir)) {
1770
- import_node_fs.default.mkdirSync(dir, { recursive: true });
1820
+ const dir = import_node_path2.default.dirname(targetPath);
1821
+ if (!import_node_fs2.default.existsSync(dir)) {
1822
+ import_node_fs2.default.mkdirSync(dir, { recursive: true });
1771
1823
  }
1772
1824
  }
1773
1825
  let settings = {};
1774
1826
  try {
1775
- settings = JSON.parse(import_node_fs.default.readFileSync(targetPath, "utf-8"));
1827
+ settings = JSON.parse(import_node_fs2.default.readFileSync(targetPath, "utf-8"));
1776
1828
  } catch {
1777
1829
  }
1778
1830
  if (!settings.permissions) {
@@ -1786,7 +1838,7 @@ var ApprovalProxy = class _ApprovalProxy {
1786
1838
  const entry = `${toolName}(*)`;
1787
1839
  if (!allow.includes(entry)) {
1788
1840
  allow.push(entry);
1789
- import_node_fs.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
1841
+ import_node_fs2.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
1790
1842
  const label = projectPath ? `${projectPath}/.claude/settings.json` : "~/.claude/settings.json";
1791
1843
  console.log(`[ApprovalProxy] ${t("approval.alwaysAllowWritten", { entry, label })}`);
1792
1844
  }
@@ -1946,6 +1998,10 @@ var ApprovalProxy = class _ApprovalProxy {
1946
1998
  });
1947
1999
  }
1948
2000
  }
2001
+ /** 更新 token(token 刷新时调用) */
2002
+ updateToken(newToken) {
2003
+ this.token = newToken;
2004
+ }
1949
2005
  /** 返回连接 token(仅本机访问) */
1950
2006
  handleToken(req, res) {
1951
2007
  const remoteAddress = req.socket.remoteAddress;
@@ -2010,11 +2066,12 @@ var ApprovalProxy = class _ApprovalProxy {
2010
2066
 
2011
2067
  // src/mdns/MdnsService.ts
2012
2068
  var import_bonjour_service = __toESM(require("bonjour-service"));
2013
- var import_node_os3 = require("os");
2069
+ var import_node_os4 = require("os");
2014
2070
  function getLanAddresses() {
2015
2071
  const results = [];
2016
- for (const [name, addrs] of Object.entries((0, import_node_os3.networkInterfaces)())) {
2017
- if (name.startsWith("utun") || name.startsWith("lo")) continue;
2072
+ for (const [name, addrs] of Object.entries((0, import_node_os4.networkInterfaces)())) {
2073
+ if (name.startsWith("utun") || name === "lo") continue;
2074
+ if (isWindows && (name.startsWith("vEthernet") || name.includes("Loopback"))) continue;
2018
2075
  for (const addr of addrs ?? []) {
2019
2076
  if (addr.family === "IPv4" && !addr.internal) {
2020
2077
  results.push(addr.address);
@@ -2116,72 +2173,62 @@ var MdnsService = class {
2116
2173
 
2117
2174
  // src/hooks/HookInstaller.ts
2118
2175
  var import_promises2 = require("fs/promises");
2119
- var import_node_path2 = require("path");
2120
- var import_node_os4 = require("os");
2121
- var SESSIX_HOOKS_DIR = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".sessix", "hooks");
2122
- var HOOK_SCRIPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "approval-hook.sh");
2123
- var PERMISSION_ACCEPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "permission-accept.sh");
2124
- var CLAUDE_SETTINGS_PATH = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude", "settings.json");
2125
- var HOOK_COMMAND = "~/.sessix/hooks/approval-hook.sh";
2126
- var PERMISSION_ACCEPT_COMMAND = "~/.sessix/hooks/permission-accept.sh";
2127
- var HOOK_SCRIPT_TEMPLATE = `#!/bin/bash
2128
- # Sessix Approval Hook
2129
- # \u4EC5\u5728 Sessix \u7BA1\u7406\u7684\u4F1A\u8BDD\u4E2D\u6FC0\u6D3B
2130
-
2131
- if [ -z "$SESSIX_SESSION_ID" ]; then
2132
- exit 0
2133
- fi
2134
-
2135
- # \u4ECE stdin \u8BFB\u53D6 hook payload
2136
- PAYLOAD=$(cat)
2137
-
2138
- # \u83B7\u53D6\u9879\u76EE\u8DEF\u5F84\uFF08\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09
2139
- PROJECT_PATH="$PWD"
2140
-
2141
- # \u53D1\u9001\u5BA1\u6279\u8BF7\u6C42\u5230 Sessix \u670D\u52A1\u5668\uFF08\u957F\u8F6E\u8BE2\uFF0C\u8D85\u65F6\u65F6\u95F4 > 300s\uFF09
2142
- RESPONSE=$(curl -s -X POST "http://localhost:3746/hook/approval" \\
2143
- -H "Content-Type: application/json" \\
2144
- -d "{\\"sessionId\\": \\"$SESSIX_SESSION_ID\\", \\"projectPath\\": \\"$PROJECT_PATH\\", \\"payload\\": $PAYLOAD}" \\
2145
- --max-time 320 \\
2146
- 2>/dev/null)
2147
-
2148
- if [ $? -ne 0 ] || [ -z "$RESPONSE" ]; then
2149
- # \u5982\u679C Sessix \u670D\u52A1\u5668\u4E0D\u53EF\u7528\uFF0C\u9ED8\u8BA4\u653E\u884C\uFF08exit 0 = \u6279\u51C6\uFF09
2150
- exit 0
2151
- fi
2176
+ var import_node_path3 = require("path");
2177
+ var import_node_os5 = require("os");
2178
+ var SESSIX_HOOKS_DIR = (0, import_node_path3.join)((0, import_node_os5.homedir)(), ".sessix", "hooks");
2179
+ var HOOK_SCRIPT_PATH = (0, import_node_path3.join)(SESSIX_HOOKS_DIR, "approval-hook.js");
2180
+ var PERMISSION_ACCEPT_PATH = (0, import_node_path3.join)(SESSIX_HOOKS_DIR, "permission-accept.js");
2181
+ var CLAUDE_SETTINGS_PATH = (0, import_node_path3.join)((0, import_node_os5.homedir)(), ".claude", "settings.json");
2182
+ var HOOK_COMMAND = "node ~/.sessix/hooks/approval-hook.js";
2183
+ var PERMISSION_ACCEPT_COMMAND = "node ~/.sessix/hooks/permission-accept.js";
2184
+ var LEGACY_HOOK_COMMANDS = [
2185
+ "~/.sessix/hooks/approval-hook.sh",
2186
+ "~/.sessix/hooks/permission-accept.sh"
2187
+ ];
2188
+ var HOOK_SCRIPT_TEMPLATE = `#!/usr/bin/env node
2189
+ // Sessix Approval Hook
2190
+ // \u4EC5\u5728 Sessix \u7BA1\u7406\u7684\u4F1A\u8BDD\u4E2D\u6FC0\u6D3B
2152
2191
 
2153
- # \u89E3\u6790\u670D\u52A1\u5668\u54CD\u5E94
2154
- DECISION=$(echo "$RESPONSE" | grep -o '\\"decision\\":\\"[^"]*\\"' | cut -d'"' -f4)
2192
+ const sessionId = process.env.SESSIX_SESSION_ID
2193
+ if (!sessionId) process.exit(0)
2155
2194
 
2156
- if [ "$DECISION" = "allow" ]; then
2157
- # \u7528\u6237\u6279\u51C6\u6216\u670D\u52A1\u5668\u8D85\u65F6\u81EA\u52A8\u6279\u51C6
2158
- exit 0
2159
- elif [ "$DECISION" = "deny" ]; then
2160
- # \u7528\u6237\u660E\u786E\u62D2\u7EDD
2161
- exit 1
2162
- else
2163
- # \u672A\u77E5\u54CD\u5E94\uFF0C\u9ED8\u8BA4\u653E\u884C
2164
- exit 0
2165
- fi
2195
+ let payload = ''
2196
+ process.stdin.on('data', (chunk) => { payload += chunk })
2197
+ process.stdin.on('end', async () => {
2198
+ try {
2199
+ const res = await fetch('http://localhost:3746/hook/approval', {
2200
+ method: 'POST',
2201
+ headers: { 'Content-Type': 'application/json' },
2202
+ body: JSON.stringify({
2203
+ sessionId,
2204
+ projectPath: process.cwd(),
2205
+ payload: JSON.parse(payload),
2206
+ }),
2207
+ signal: AbortSignal.timeout(320000),
2208
+ })
2209
+ const data = await res.json()
2210
+ process.exit(data.decision === 'deny' ? 1 : 0)
2211
+ } catch {
2212
+ // Sessix \u670D\u52A1\u5668\u4E0D\u53EF\u7528\uFF0C\u9ED8\u8BA4\u653E\u884C
2213
+ process.exit(0)
2214
+ }
2215
+ })
2166
2216
  `;
2167
- var PERMISSION_ACCEPT_TEMPLATE = `#!/bin/bash
2168
- # Sessix PermissionRequest \u515C\u5E95
2169
- # \u81EA\u52A8\u63A5\u53D7\u6743\u9650\u8BF7\u6C42\uFF0C\u907F\u514D Sessix \u4F1A\u8BDD\u963B\u585E
2217
+ var PERMISSION_ACCEPT_TEMPLATE = `#!/usr/bin/env node
2218
+ // Sessix PermissionRequest \u515C\u5E95
2219
+ // \u81EA\u52A8\u63A5\u53D7\u6743\u9650\u8BF7\u6C42\uFF0C\u907F\u514D Sessix \u4F1A\u8BDD\u963B\u585E
2170
2220
 
2171
- if [ -z "$SESSIX_SESSION_ID" ]; then
2172
- exit 0
2173
- fi
2221
+ if (!process.env.SESSIX_SESSION_ID) process.exit(0)
2174
2222
 
2175
- # \u8F93\u51FA JSON \u51B3\u7B56\uFF0C\u81EA\u52A8\u63A5\u53D7\u6743\u9650\u8BF7\u6C42
2176
- echo '{"decision":"allow"}'
2177
- exit 0
2223
+ process.stdout.write('{"decision":"allow"}\\n')
2224
+ process.exit(0)
2178
2225
  `;
2179
2226
  var HookInstaller = class {
2180
2227
  /**
2181
2228
  * 安装 hook
2182
2229
  *
2183
2230
  * 1. 创建 ~/.sessix/hooks/ 目录
2184
- * 2. 写入 approval-hook.sh 脚本
2231
+ * 2. 写入 approval-hook.js 脚本
2185
2232
  * 3. 赋予执行权限
2186
2233
  * 4. 更新 Claude Code settings.json 添加 hook 配置
2187
2234
  */
@@ -2234,6 +2281,10 @@ var HookInstaller = class {
2234
2281
  async addHookToSettings() {
2235
2282
  let settings = await this.readClaudeSettings();
2236
2283
  let changed = false;
2284
+ for (const cmd of LEGACY_HOOK_COMMANDS) {
2285
+ this.removeHookCommand(settings, "PreToolUse", cmd);
2286
+ this.removeHookCommand(settings, "PermissionRequest", cmd);
2287
+ }
2237
2288
  if (!settings.hooks) {
2238
2289
  settings.hooks = {};
2239
2290
  }
@@ -2301,7 +2352,7 @@ var HookInstaller = class {
2301
2352
  * 写入 Claude Code settings.json
2302
2353
  */
2303
2354
  async writeClaudeSettings(settings) {
2304
- await (0, import_promises2.mkdir)((0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude"), { recursive: true });
2355
+ await (0, import_promises2.mkdir)((0, import_node_path3.join)((0, import_node_os5.homedir)(), ".claude"), { recursive: true });
2305
2356
  await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
2306
2357
  }
2307
2358
  /**
@@ -2310,11 +2361,11 @@ var HookInstaller = class {
2310
2361
  hasHookConfig(settings) {
2311
2362
  return this.hasPreToolUseConfig(settings) && this.hasPermissionRequestConfig(settings);
2312
2363
  }
2313
- /** 检查 PreToolUse 中是否有 approval-hook.sh */
2364
+ /** 检查 PreToolUse 中是否有 approval-hook.js */
2314
2365
  hasPreToolUseConfig(settings) {
2315
2366
  return this.hasHookEntry(settings?.hooks?.PreToolUse, HOOK_COMMAND);
2316
2367
  }
2317
- /** 检查 PermissionRequest 中是否有 permission-accept.sh */
2368
+ /** 检查 PermissionRequest 中是否有 permission-accept.js */
2318
2369
  hasPermissionRequestConfig(settings) {
2319
2370
  return this.hasHookEntry(settings?.hooks?.PermissionRequest, PERMISSION_ACCEPT_COMMAND);
2320
2371
  }
@@ -2328,7 +2379,7 @@ var HookInstaller = class {
2328
2379
  };
2329
2380
 
2330
2381
  // src/notification/NotificationService.ts
2331
- var import_node_path3 = require("path");
2382
+ var import_node_path4 = require("path");
2332
2383
  var NotificationService = class {
2333
2384
  constructor(sessionManager, expoChannel = null) {
2334
2385
  this.sessionManager = sessionManager;
@@ -2424,7 +2475,7 @@ var NotificationService = class {
2424
2475
  const dangerLevel = this.getDangerLevel(request.toolName);
2425
2476
  const isDangerous = dangerLevel === "danger" || dangerLevel === "write";
2426
2477
  const categoryId = isDangerous ? "APPROVAL_DANGEROUS" : "APPROVAL_NORMAL";
2427
- const projectName = (0, import_node_path3.basename)(
2478
+ const projectName = (0, import_node_path4.basename)(
2428
2479
  this.sessionManager.getActiveSessions().find((s) => s.id === request.sessionId)?.projectPath ?? ""
2429
2480
  );
2430
2481
  const pushTitle = isDangerous ? `\u26A0\uFE0F ${title}` : title;
@@ -2480,7 +2531,7 @@ var NotificationService = class {
2480
2531
  /** 从审批请求中提取操作目标的简短描述 */
2481
2532
  extractTarget(request) {
2482
2533
  const input = request.toolInput;
2483
- if (input.file_path) return (0, import_node_path3.basename)(String(input.file_path));
2534
+ if (input.file_path) return (0, import_node_path4.basename)(String(input.file_path));
2484
2535
  if (input.command) return String(input.command).slice(0, 40);
2485
2536
  return request.description.slice(0, 40);
2486
2537
  }
@@ -2583,7 +2634,7 @@ var NotificationService = class {
2583
2634
  getSessionTitle(sessionId) {
2584
2635
  const session = this.sessionManager.getActiveSessions().find((s) => s.id === sessionId);
2585
2636
  if (!session) return "Unknown";
2586
- return session.summary ?? (0, import_node_path3.basename)(session.projectPath);
2637
+ return session.summary ?? (0, import_node_path4.basename)(session.projectPath);
2587
2638
  }
2588
2639
  /** 获取会话的 YOLO 模式状态 */
2589
2640
  getYoloMode(sessionId) {
@@ -2591,9 +2642,9 @@ var NotificationService = class {
2591
2642
  }
2592
2643
  };
2593
2644
 
2594
- // src/notification/MacNotificationChannel.ts
2595
- var import_node_child_process = require("child_process");
2596
- var MacNotificationChannel = class {
2645
+ // src/notification/DesktopNotificationChannel.ts
2646
+ var import_node_child_process3 = require("child_process");
2647
+ var DesktopNotificationChannel = class {
2597
2648
  isAvailable() {
2598
2649
  return process.platform === "darwin";
2599
2650
  }
@@ -2604,9 +2655,9 @@ var MacNotificationChannel = class {
2604
2655
  const sound = payload.sound ?? "Ping";
2605
2656
  const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
2606
2657
  return new Promise((resolve) => {
2607
- (0, import_node_child_process.execFile)("osascript", ["-e", script], (err) => {
2658
+ (0, import_node_child_process3.execFile)("osascript", ["-e", script], (err) => {
2608
2659
  if (err) {
2609
- console.warn("[MacNotificationChannel] Send notification failed:", err.message);
2660
+ console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
2610
2661
  }
2611
2662
  resolve();
2612
2663
  });
@@ -3219,6 +3270,9 @@ var PairingManager = class {
3219
3270
  this.close();
3220
3271
  return result;
3221
3272
  }
3273
+ updateToken(newToken) {
3274
+ this.token = newToken;
3275
+ }
3222
3276
  getRemainingSeconds() {
3223
3277
  if (this._state !== "open") return 0;
3224
3278
  return Math.max(0, Math.ceil((this.deadline - Date.now()) / 1e3));
@@ -3232,11 +3286,11 @@ var PairingManager = class {
3232
3286
  };
3233
3287
 
3234
3288
  // src/auth/AuthManager.ts
3289
+ var import_child_process2 = require("child_process");
3235
3290
  var import_child_process3 = require("child_process");
3236
- var import_child_process4 = require("child_process");
3237
3291
  var import_util = require("util");
3238
3292
  var import_events2 = require("events");
3239
- var execFileAsync = (0, import_util.promisify)(import_child_process4.execFile);
3293
+ var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
3240
3294
  var CLAUDE_PATH2 = findClaudePath();
3241
3295
  var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
3242
3296
  var AuthManager = class extends import_events2.EventEmitter {
@@ -3267,7 +3321,7 @@ var AuthManager = class extends import_events2.EventEmitter {
3267
3321
  }
3268
3322
  this.clearLoginTimeout();
3269
3323
  this.urlSent = false;
3270
- const proc = (0, import_child_process3.spawn)(CLAUDE_PATH2, ["auth", "login"], {
3324
+ const proc = (0, import_child_process2.spawn)(CLAUDE_PATH2, ["auth", "login"], {
3271
3325
  env: { ...process.env, BROWSER: "echo" },
3272
3326
  stdio: ["pipe", "pipe", "pipe"]
3273
3327
  });
@@ -3351,17 +3405,122 @@ var AuthManager = class extends import_events2.EventEmitter {
3351
3405
 
3352
3406
  // src/server.ts
3353
3407
  var import_promises5 = require("fs/promises");
3408
+
3409
+ // src/terminal/TerminalExecutor.ts
3410
+ var import_node_child_process4 = require("child_process");
3411
+ var import_uuid4 = require("uuid");
3412
+ var EXEC_TIMEOUT_MS = 5 * 60 * 1e3;
3413
+ var TerminalExecutor = class {
3414
+ processes = /* @__PURE__ */ new Map();
3415
+ eventCallbacks = [];
3416
+ onEvent(callback) {
3417
+ this.eventCallbacks.push(callback);
3418
+ return () => {
3419
+ const idx = this.eventCallbacks.indexOf(callback);
3420
+ if (idx !== -1) this.eventCallbacks.splice(idx, 1);
3421
+ };
3422
+ }
3423
+ emit(event) {
3424
+ for (const cb of this.eventCallbacks) {
3425
+ try {
3426
+ cb(event);
3427
+ } catch (err) {
3428
+ console.error("[TerminalExecutor] Event callback error:", err);
3429
+ }
3430
+ }
3431
+ }
3432
+ exec(sessionId, command, cwd) {
3433
+ const execId = (0, import_uuid4.v4)();
3434
+ const shell = isWindows ? "powershell" : "bash";
3435
+ const args = isWindows ? ["-Command", command] : ["-c", command];
3436
+ const proc = (0, import_node_child_process4.spawn)(shell, args, {
3437
+ cwd,
3438
+ stdio: ["ignore", "pipe", "pipe"],
3439
+ env: { ...process.env }
3440
+ });
3441
+ this.processes.set(execId, proc);
3442
+ proc.stdout?.on("data", (chunk) => {
3443
+ this.emit({
3444
+ type: "terminal_output",
3445
+ sessionId,
3446
+ execId,
3447
+ stream: "stdout",
3448
+ data: chunk.toString()
3449
+ });
3450
+ });
3451
+ proc.stderr?.on("data", (chunk) => {
3452
+ this.emit({
3453
+ type: "terminal_output",
3454
+ sessionId,
3455
+ execId,
3456
+ stream: "stderr",
3457
+ data: chunk.toString()
3458
+ });
3459
+ });
3460
+ proc.on("exit", (code, signal) => {
3461
+ clearTimeout(timer);
3462
+ this.processes.delete(execId);
3463
+ this.emit({
3464
+ type: "terminal_exit",
3465
+ sessionId,
3466
+ execId,
3467
+ code,
3468
+ signal
3469
+ });
3470
+ });
3471
+ const timer = setTimeout(() => {
3472
+ if (this.processes.has(execId)) {
3473
+ killProcessCrossPlatform(proc);
3474
+ }
3475
+ }, EXEC_TIMEOUT_MS);
3476
+ console.log(`[TerminalExecutor] exec ${execId}: ${command.substring(0, 100)} (cwd: ${cwd})`);
3477
+ return execId;
3478
+ }
3479
+ kill(execId) {
3480
+ const proc = this.processes.get(execId);
3481
+ if (proc) {
3482
+ killProcessCrossPlatform(proc);
3483
+ console.log(`[TerminalExecutor] kill ${execId}`);
3484
+ }
3485
+ }
3486
+ destroy() {
3487
+ for (const [execId, proc] of this.processes) {
3488
+ killProcessCrossPlatform(proc);
3489
+ console.log(`[TerminalExecutor] cleanup ${execId}`);
3490
+ }
3491
+ this.processes.clear();
3492
+ this.eventCallbacks.length = 0;
3493
+ }
3494
+ };
3495
+
3496
+ // src/server.ts
3354
3497
  var WS_PORT = 3745;
3355
3498
  var HTTP_PORT = 3746;
3356
- var execAsync = (0, import_node_util.promisify)(import_node_child_process2.exec);
3499
+ var execAsync = (0, import_node_util.promisify)(import_node_child_process5.exec);
3357
3500
  async function killPortProcess(port) {
3358
3501
  try {
3359
- const { stdout } = await execAsync(`lsof -ti :${port}`);
3360
- const pids = stdout.trim().split("\n").filter((p) => p && /^\d+$/.test(p));
3361
- if (pids.length > 0) {
3362
- await execAsync(`kill -9 ${pids.join(" ")}`);
3363
- await new Promise((resolve) => setTimeout(resolve, 600));
3502
+ if (isWindows) {
3503
+ const { stdout } = await execAsync(
3504
+ `netstat -ano | findstr :${port} | findstr LISTENING`
3505
+ );
3506
+ const pids = /* @__PURE__ */ new Set();
3507
+ for (const line of stdout.trim().split("\n")) {
3508
+ const parts = line.trim().split(/\s+/);
3509
+ const pid = parts[parts.length - 1];
3510
+ if (pid && /^\d+$/.test(pid) && pid !== "0") pids.add(pid);
3511
+ }
3512
+ for (const pid of pids) {
3513
+ await execAsync(`taskkill /PID ${pid} /F`).catch(() => {
3514
+ });
3515
+ }
3516
+ } else {
3517
+ const { stdout } = await execAsync(`lsof -ti :${port}`);
3518
+ const pids = stdout.trim().split("\n").filter((p) => p && /^\d+$/.test(p));
3519
+ if (pids.length > 0) {
3520
+ await execAsync(`kill -9 ${pids.join(" ")}`);
3521
+ }
3364
3522
  }
3523
+ await new Promise((resolve) => setTimeout(resolve, 600));
3365
3524
  } catch {
3366
3525
  }
3367
3526
  }
@@ -3379,8 +3538,8 @@ async function createWithRetry(label, port, factory) {
3379
3538
  }
3380
3539
  }
3381
3540
  async function start(opts = {}) {
3382
- const configDir = (0, import_node_path4.join)((0, import_node_os5.homedir)(), ".sessix");
3383
- const tokenFile = (0, import_node_path4.join)(configDir, "token");
3541
+ const configDir = (0, import_node_path5.join)((0, import_node_os6.homedir)(), ".sessix");
3542
+ const tokenFile = (0, import_node_path5.join)(configDir, "token");
3384
3543
  let token;
3385
3544
  if (opts.token !== void 0) {
3386
3545
  token = opts.token;
@@ -3392,7 +3551,7 @@ async function start(opts = {}) {
3392
3551
  try {
3393
3552
  token = (await (0, import_promises4.readFile)(tokenFile, "utf8")).trim();
3394
3553
  } catch {
3395
- token = (0, import_uuid4.v4)();
3554
+ token = (0, import_uuid5.v4)();
3396
3555
  await (0, import_promises4.mkdir)(configDir, { recursive: true });
3397
3556
  await (0, import_promises4.writeFile)(tokenFile, token, "utf8");
3398
3557
  }
@@ -3400,10 +3559,24 @@ async function start(opts = {}) {
3400
3559
  }
3401
3560
  const provider = new ProcessProvider();
3402
3561
  const sessionManager = new SessionManager(provider);
3562
+ const terminalExecutor = new TerminalExecutor();
3563
+ const wsBridge = await createWithRetry(
3564
+ "WsBridge",
3565
+ WS_PORT,
3566
+ () => WsBridge.create({ port: WS_PORT, token })
3567
+ );
3568
+ const unreadSessionIds = /* @__PURE__ */ new Set();
3569
+ sessionManager.onEvent((event) => {
3570
+ if (event.type === "status_change" && (event.status === "idle" || event.status === "error")) {
3571
+ if (!wsBridge.isViewingSession(event.sessionId)) {
3572
+ unreadSessionIds.add(event.sessionId);
3573
+ }
3574
+ }
3575
+ });
3403
3576
  const expoChannel = new ExpoNotificationChannel();
3404
3577
  const notificationService = new NotificationService(sessionManager, expoChannel);
3405
3578
  notificationService.addChannel("expo", expoChannel, opts.enableExpoPush !== false);
3406
- notificationService.addChannel("mac", new MacNotificationChannel(), opts.enableMacNotification !== false);
3579
+ notificationService.addChannel("mac", new DesktopNotificationChannel(), opts.enableMacNotification !== false);
3407
3580
  if (opts.activityPush) {
3408
3581
  try {
3409
3582
  const activityChannel = new ActivityPushChannel(opts.activityPush);
@@ -3414,11 +3587,6 @@ async function start(opts = {}) {
3414
3587
  console.log(`[Server] ${t("server.activityPushContinue")}`);
3415
3588
  }
3416
3589
  }
3417
- const wsBridge = await createWithRetry(
3418
- "WsBridge",
3419
- WS_PORT,
3420
- () => WsBridge.create({ port: WS_PORT, token })
3421
- );
3422
3590
  const sessionFileWatcher = new SessionFileWatcher((event) => {
3423
3591
  wsBridge.broadcast(event);
3424
3592
  });
@@ -3430,7 +3598,7 @@ async function start(opts = {}) {
3430
3598
  let mdnsService = null;
3431
3599
  const pairingManager = new PairingManager({
3432
3600
  token,
3433
- serverName: (0, import_node_os5.hostname)(),
3601
+ serverName: (0, import_node_os6.hostname)(),
3434
3602
  version: "0.2.0",
3435
3603
  onStateChange: (state) => mdnsService?.updatePairingState(state)
3436
3604
  });
@@ -3447,7 +3615,6 @@ async function start(opts = {}) {
3447
3615
  });
3448
3616
  }
3449
3617
  });
3450
- const unreadSessionIds = /* @__PURE__ */ new Set();
3451
3618
  notificationService.setGlobalPendingCountProvider(
3452
3619
  () => approvalProxy.getPendingCount() + sessionManager.getAllPendingQuestions().length + unreadSessionIds.size
3453
3620
  );
@@ -3478,6 +3645,8 @@ async function start(opts = {}) {
3478
3645
  switch (event.type) {
3479
3646
  case "create_session": {
3480
3647
  await (0, import_promises4.mkdir)(event.projectPath, { recursive: true });
3648
+ const resumeId = event.resumeSessionId ?? event.newSessionId;
3649
+ if (resumeId) sessionFileWatcher.unwatch(resumeId);
3481
3650
  await sessionManager.createSession(
3482
3651
  event.projectPath,
3483
3652
  event.message,
@@ -3495,6 +3664,7 @@ async function start(opts = {}) {
3495
3664
  break;
3496
3665
  }
3497
3666
  case "send_message": {
3667
+ sessionFileWatcher.unwatch(event.sessionId);
3498
3668
  await sessionManager.sendMessage(event.sessionId, event.message, event.permissionMode, event.images);
3499
3669
  wsBridge.broadcast({
3500
3670
  type: "session_list",
@@ -3583,6 +3753,10 @@ async function start(opts = {}) {
3583
3753
  code: "PROJECT_LIST_ERROR"
3584
3754
  });
3585
3755
  }
3756
+ wsBridge.send(ws, {
3757
+ type: "session_list",
3758
+ sessions: sessionManager.getActiveSessions()
3759
+ });
3586
3760
  break;
3587
3761
  }
3588
3762
  case "list_sessions": {
@@ -3677,6 +3851,20 @@ async function start(opts = {}) {
3677
3851
  notificationService.setSoundPreferences(event.preferences);
3678
3852
  break;
3679
3853
  }
3854
+ case "terminal_exec": {
3855
+ const activeSession = sessionManager.getActiveSessions().find((s) => s.id === event.sessionId);
3856
+ const cwd = activeSession?.projectPath ?? sessionManager.getSessionProjectPath(event.sessionId);
3857
+ if (!cwd) {
3858
+ wsBridge.send(ws, { type: "error", code: "TERMINAL_EXEC_ERROR", message: "Session not found or no project path", sessionId: event.sessionId });
3859
+ break;
3860
+ }
3861
+ terminalExecutor.exec(event.sessionId, event.command, cwd);
3862
+ break;
3863
+ }
3864
+ case "terminal_kill": {
3865
+ terminalExecutor.kill(event.execId);
3866
+ break;
3867
+ }
3680
3868
  case "register_activity_push_token": {
3681
3869
  notificationService.addActivityPushToken(event.sessionId, event.token);
3682
3870
  break;
@@ -3748,12 +3936,14 @@ async function start(opts = {}) {
3748
3936
  sessionManager.onEvent((event) => {
3749
3937
  wsBridge.broadcast(event);
3750
3938
  if (event.type === "status_change" && (event.status === "idle" || event.status === "error")) {
3751
- if (!wsBridge.isViewingSession(event.sessionId)) {
3752
- unreadSessionIds.add(event.sessionId);
3939
+ if (unreadSessionIds.has(event.sessionId)) {
3753
3940
  broadcastUnreadSessions();
3754
3941
  }
3755
3942
  }
3756
3943
  });
3944
+ terminalExecutor.onEvent((event) => {
3945
+ wsBridge.broadcast(event);
3946
+ });
3757
3947
  wsBridge.onDisconnect(() => {
3758
3948
  if (wsBridge.getConnectionCount() === 0 && approvalProxy.getPendingCount() > 0) {
3759
3949
  approvalProxy.approveAll(t("server.phoneDisconnected"));
@@ -3800,12 +3990,17 @@ async function start(opts = {}) {
3800
3990
  }));
3801
3991
  const startMdns = () => {
3802
3992
  if (mdnsService) return;
3803
- mdnsService = new MdnsService({
3804
- wsPort: WS_PORT,
3805
- httpPort: HTTP_PORT,
3806
- pairing: pairingManager.state
3807
- });
3808
- mdnsService.start();
3993
+ try {
3994
+ mdnsService = new MdnsService({
3995
+ wsPort: WS_PORT,
3996
+ httpPort: HTTP_PORT,
3997
+ pairing: pairingManager.state
3998
+ });
3999
+ mdnsService.start();
4000
+ } catch (err) {
4001
+ console.warn(`[Server] mDNS failed to start (non-fatal): ${err.message}`);
4002
+ mdnsService = null;
4003
+ }
3809
4004
  };
3810
4005
  const stopMdns = () => {
3811
4006
  if (!mdnsService) return;
@@ -3848,6 +4043,7 @@ async function start(opts = {}) {
3848
4043
  await attempt(() => wsBridge.close(), "WebSocket");
3849
4044
  await attempt(() => approvalProxy.close(), "ApprovalProxy");
3850
4045
  await attempt(() => sessionManager.destroy(), "SessionManager");
4046
+ await attempt(() => terminalExecutor.destroy(), "TerminalExecutor");
3851
4047
  await attempt(() => notificationService.destroy(), "NotificationService");
3852
4048
  await attempt(() => sessionFileWatcher.destroy(), "SessionFileWatcher");
3853
4049
  if (errors.length > 0) {
@@ -3856,7 +4052,7 @@ async function start(opts = {}) {
3856
4052
  }
3857
4053
  console.log(`[Server] ${t("server.shutdownComplete")}`);
3858
4054
  };
3859
- return {
4055
+ const instance = {
3860
4056
  token,
3861
4057
  wsPort: WS_PORT,
3862
4058
  httpPort: HTTP_PORT,
@@ -3874,8 +4070,21 @@ async function start(opts = {}) {
3874
4070
  }
3875
4071
  },
3876
4072
  openPairing: (duration) => pairingManager.open(duration),
3877
- closePairing: () => pairingManager.close()
4073
+ closePairing: () => pairingManager.close(),
4074
+ regenerateToken: async () => {
4075
+ const newToken = (0, import_uuid5.v4)();
4076
+ await (0, import_promises4.mkdir)(configDir, { recursive: true });
4077
+ await (0, import_promises4.writeFile)(tokenFile, newToken, "utf8");
4078
+ instance.token = newToken;
4079
+ wsBridge.updateToken(newToken);
4080
+ approvalProxy.updateToken(newToken);
4081
+ pairingManager.updateToken(newToken);
4082
+ pairingManager.open();
4083
+ console.log(`[Server] ${t("server.tokenRegenerated", { token: newToken })}`);
4084
+ return newToken;
4085
+ }
3878
4086
  };
4087
+ return instance;
3879
4088
  }
3880
4089
  // Annotate the CommonJS export names for ESM import in node:
3881
4090
  0 && (module.exports = {