@wendongfly/myhi 1.0.116 → 1.0.118

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 CHANGED
@@ -13,6 +13,9 @@
13
13
  - **AI Agent 集成** —— 内置 Claude Code CLI,Web 界面中直接与 AI 对话编程、审批文件变更
14
14
  - **角色权限管理** —— admin / operator / viewer 三级角色,多种认证模式
15
15
  - **多用户隔离** —— 独立用户目录、独立 Git 配置,支持独占模式
16
+ - **SSH 开发账号** —— 为每个用户创建独立的 SSH 账号,VS Code Remote-SSH 远程开发
17
+ - **Git 一键提交** —— 内置 Git 提交推送功能,支持每个项目独立的 GitLab 账号
18
+ - **自动更新** —— Web 界面检测新版本,一键更新服务器
16
19
  - **会话持久化** —— 会话断线重连、服务重启自动恢复
17
20
  - **零配置启动** —— 一条命令启动,自动创建会话并附加终端
18
21
  - **局域网 / Tailscale** —— 自动检测网络,点对点直连,QR 码快速连接
@@ -41,7 +44,7 @@ myhi -d
41
44
  myhi -p 8080
42
45
  ```
43
46
 
44
- 启动后,终端会输出访问地址和 QR 码。在同一局域网内的手机浏览器扫码或输入 `http://<你的IP>:3000` 即可访问。
47
+ 启动后,终端会输出访问地址和 QR 码。在同一局域网内的手机浏览器扫码或输入 `http://<你的IP>:12300` 即可访问。
45
48
 
46
49
  首次启动会在 `~/.myhi/password` 生成一个 4 位数默认密码,终端中会显示。
47
50
 
@@ -81,11 +84,45 @@ myhi user remove <密码> # 删除用户
81
84
  myhi exclusive on|off # 开启/关闭独占模式
82
85
  ```
83
86
 
87
+ ### SSH 开发账号
88
+
89
+ 为 myhi 用户创建系统 SSH 账号,员工可通过 VS Code Remote-SSH 远程开发,每人只能访问自己的项目目录(支持 Windows 和 Ubuntu)。
90
+
91
+ ```bash
92
+ myhi ssh add <用户名> [密码] # 创建 SSH 账号(绑定到 myhi 用户的项目目录)
93
+ myhi ssh list # 列出所有 SSH 账号
94
+ myhi ssh remove <用户名> # 删除 SSH 账号(保留项目目录)
95
+ ```
96
+
97
+ **示例:**
98
+
99
+ ```bash
100
+ # 1. 先添加 myhi 用户
101
+ myhi user add 1234 张三 /data/projects/zhangsan
102
+
103
+ # 2. 为他创建 SSH 账号
104
+ myhi ssh add 张三 sshpass123
105
+ # 输出:
106
+ # ✓ SSH 账号创建成功!
107
+ # │ SSH 用户名: myhi-zhangsan
108
+ # │ SSH 密码: sshpass123
109
+ # │ 项目目录: /data/projects/zhangsan
110
+ ```
111
+
112
+ **目录隔离:**
113
+
114
+ | 平台 | 隔离方式 |
115
+ |------|---------|
116
+ | Windows | NTFS 权限:上级目录仅允许穿越,子目录完全控制 |
117
+ | Ubuntu | `chmod 711` 上级目录 + `chmod 700` 用户目录 + `chown` |
118
+
119
+ 员工在 VS Code 中连接后打开 `~/project` 即可开发,无法查看其他用户的文件。
120
+
84
121
  ### 远程附加
85
122
 
86
123
  ```bash
87
124
  # 连接到远程服务器的会话
88
- MYHI_SERVER=http://192.168.1.100:3000 myhi attach
125
+ MYHI_SERVER=http://192.168.1.100:12300 myhi attach
89
126
  ```
90
127
 
91
128
  ---
@@ -94,7 +131,7 @@ MYHI_SERVER=http://192.168.1.100:3000 myhi attach
94
131
 
95
132
  | 变量 | 默认值 | 说明 |
96
133
  |------|--------|------|
97
- | `PORT` | `3000` | 监听端口 |
134
+ | `PORT` | `12300` | 监听端口 |
98
135
  | `HOST` | `0.0.0.0` | 监听地址 |
99
136
  | `MYHI_AUTO_ATTACH` | `1` | 启动时自动打开本地终端窗口并附加会话 |
100
137
  | `MYHI_CWD` | 当前目录 | 新建会话的工作目录 |
@@ -114,6 +151,7 @@ MYHI_SERVER=http://192.168.1.100:3000 myhi attach
114
151
  - 快捷操作:打开、删除、重命名、踢出用户
115
152
  - 新建 PTY 会话或 AI Agent 会话(支持文件夹选择器)
116
153
  - 用量统计(7 天活动数据)
154
+ - **版本更新提示** —— 检测到新版本时显示更新横幅,一键更新
117
155
 
118
156
  ### 聊天模式(`/terminal/:id`)
119
157
 
@@ -122,12 +160,14 @@ MYHI_SERVER=http://192.168.1.100:3000 myhi attach
122
160
  - 用户消息 vs AI 回复的对话气泡
123
161
  - ANSI 48 色彩色终端输出渲染
124
162
  - Markdown 渲染(加粗、代码块、列表)
125
- - 工具调用可视化(可折叠分组)
163
+ - 工具调用可视化(可折叠分组,支持**一键展开/收起全部**)
126
164
  - AI 思考过程(可展开查看)
127
165
  - 文件差异查看器
128
166
  - 权限请求卡片(允许 / 拒绝按钮)
129
167
  - 图片上传(粘贴或选择,最大 20MB)
130
168
  - 实时消息流式显示
169
+ - **ESC 取消** —— Agent 处理中按 ESC 或点击停止按钮中断操作
170
+ - **Git 提交** —— 快捷按钮一键提交代码到 GitLab,每个项目独立凭据
131
171
 
132
172
  ### 终端模式(`/terminal-raw/:id`)
133
173
 
@@ -227,7 +267,7 @@ myhi 集成了 Claude Code CLI,可以在 Web 界面中直接与 AI 进行编
227
267
  3. 在聊天界面中输入自然语言指令
228
268
  4. AI 以流式方式返回回复,包括:
229
269
  - **文本回复** —— Markdown 格式
230
- - **工具调用** —— 文件读写、命令执行等(可折叠查看)
270
+ - **工具调用** —— 文件读写、命令执行等(可折叠查看,支持一键展开全部)
231
271
  - **思考过程** —— AI 的推理过程(可展开)
232
272
  - **权限请求** —— 需要你批准的操作(允许/拒绝按钮)
233
273
  - **文件差异** —— 代码变更的 diff 视图
@@ -242,12 +282,51 @@ Agent 会话支持三种权限模式,可在界面中切换:
242
282
  | `plan` | 规划模式,AI 只分析不执行 |
243
283
  | `bypassPermissions` | 信任模式,自动批准所有操作 |
244
284
 
285
+ ### 中断与取消
286
+
287
+ - **ESC 键** —— 在 Agent 处理中按 ESC 中断当前操作
288
+ - **停止按钮** —— Agent 忙时发送按钮自动变为红色停止按钮,点击取消
289
+
245
290
  ### 会话恢复
246
291
 
247
292
  Agent 会话支持断线重连,重新打开时自动加载最近的对话历史(最多 500 条消息)。
248
293
 
249
294
  ---
250
295
 
296
+ ## Git 提交推送
297
+
298
+ 聊天界面的快捷键栏提供**"提交"**按钮,一键将代码提交并推送到 GitLab/GitHub。
299
+
300
+ ### 功能特点
301
+
302
+ - **每个项目独立凭据** —— 不同项目可使用不同的 GitLab 账号,互不干扰
303
+ - **凭据记忆** —— 勾选"记住"后自动回填仓库地址和用户名
304
+ - **自动初始化** —— 目录未初始化时自动执行 `git init`
305
+ - **分支管理** —— 支持指定推送分支(默认 main)
306
+
307
+ ### 凭据存储
308
+
309
+ 每个项目的 Git 凭据存储在项目目录下,与系统全局 Git 配置隔离:
310
+
311
+ ```
312
+ ~/myhi/张三/
313
+ ├── .gitconfig # 项目级 Git 配置(credential helper + user)
314
+ ├── .git-credentials # 项目级凭据文件(仅本人可读)
315
+ └── ...
316
+ ```
317
+
318
+ ---
319
+
320
+ ## 自动更新
321
+
322
+ myhi 支持在 Web 界面中检测和执行版本更新。
323
+
324
+ - **仪表盘**:页面加载时和每 10 分钟检查 npm 最新版本,有新版本时显示黄色更新横幅,点击"更新"按钮自动执行 `npm install -g @wendongfly/myhi@latest`
325
+ - **聊天页**:状态栏显示"有更新"提示,点击跳转到仪表盘执行更新
326
+ - 更新完成后服务器自动重启,页面自动刷新
327
+
328
+ ---
329
+
251
330
  ## 安全特性
252
331
 
253
332
  | 特性 | 说明 |
@@ -260,6 +339,7 @@ Agent 会话支持断线重连,重新打开时自动加载最近的对话历
260
339
  | CORS 限制 | 仅允许局域网 (RFC1918) 和 Tailscale 网段 |
261
340
  | 路径遍历防护 | 目录浏览接口阻止 `..` 路径穿越 |
262
341
  | 进程隔离 | 每个用户独立 Git 配置,防止凭据交叉 |
342
+ | SSH 目录隔离 | NTFS/chmod 权限确保用户只能访问自己的项目目录 |
263
343
  | 环境变量清理 | PTY 进程剥离 Claude Code 相关变量,防止嵌套 |
264
344
 
265
345
  ---
@@ -296,7 +376,7 @@ Agent 会话支持断线重连,重新打开时自动加载最近的对话历
296
376
  ~/.myhi/
297
377
  ├── token # 主认证 token(32 位十六进制)
298
378
  ├── password # 默认 4 位数登录密码
299
- ├── users.json # 用户账号配置(可选)
379
+ ├── users.json # 用户账号配置(可选,含 SSH 账号映射)
300
380
  ├── roles.json # 角色权限配置(可选)
301
381
  ├── sessions.json # 会话持久化(重启后自动恢复)
302
382
  ├── uploads/ # 通过 Web 上传的图片
@@ -326,6 +406,10 @@ Agent 会话支持断线重连,重新打开时自动加载最近的对话历
326
406
  | `/api/dirs` | GET | 是 | 目录浏览(仅 admin) |
327
407
  | `/api/me` | GET | 是 | 当前用户信息 |
328
408
  | `/api/usage` | GET | 否 | AI 用量统计 |
409
+ | `/api/version` | GET | 是 | 版本信息(当前版本 + 最新版本) |
410
+ | `/api/update` | POST | 是 | 触发自动更新(仅 admin) |
411
+ | `/api/git/info` | GET | 是 | 获取项目 Git remote 信息 |
412
+ | `/api/git/push` | POST | 是 | Git 提交并推送 |
329
413
  | `/upload` | POST | 是 | 图片上传(最大 20MB) |
330
414
  | `/qr/:sessionId` | GET | 是 | QR 码 SVG(一次性 token) |
331
415
 
@@ -405,9 +489,11 @@ Agent 会话支持断线重连,重新打开时自动加载最近的对话历
405
489
 
406
490
  为团队提供受控的服务器访问:
407
491
  - 每人独立账号和工作目录
492
+ - SSH 账号支持 VS Code Remote-SSH 远程开发
408
493
  - 权限分级(admin 全权限,viewer 只读)
409
494
  - 独占模式防止操作冲突
410
495
  - admin 可审计在线用户并踢出
496
+ - 每个项目独立的 Git 凭据,一键提交到 GitLab
411
497
 
412
498
  ---
413
499
 
@@ -460,16 +546,16 @@ npm start # 运行构建产物
460
546
 
461
547
  ```
462
548
  myhi/
463
- ├── bin/myhi.js # CLI 入口:命令解析与分发
549
+ ├── bin/myhi.js # CLI 入口:命令解析与分发(含 ssh 子命令)
464
550
  ├── src/
465
- │ ├── server.js # 主服务器:路由、Socket.IO、认证
551
+ │ ├── server.js # 主服务器:路由、Socket.IO、认证、Git API
466
552
  │ ├── sessions.js # 会话管理:PTY、Agent、持久化
467
553
  │ ├── agent.js # Claude Code CLI 集成
468
554
  │ ├── roles.js # 角色与用户管理
469
555
  │ └── attach.js # CLI 附加客户端
470
556
  ├── public/
471
- │ ├── index.html # 仪表盘
472
- │ ├── chat.html # 聊天式界面
557
+ │ ├── index.html # 仪表盘(含版本更新检测)
558
+ │ ├── chat.html # 聊天式界面(含 Git 提交、ESC 取消)
473
559
  │ ├── terminal.html # xterm.js 终端
474
560
  │ ├── login.html # 登录页
475
561
  │ └── lib/ # xterm.js 等前端库
package/dist/chat.html CHANGED
@@ -195,6 +195,13 @@
195
195
  .action-sheet-item .desc { font-size: 0.72rem; color: #8b949e; }
196
196
  .action-sheet-item .check { color: #3fb950; font-size: 0.9rem; }
197
197
  .action-sheet-cancel { display: block; width: 100%; margin-top: 0.5rem; padding: 0.65rem; background: #21262d; border: none; border-radius: 10px; color: #8b949e; font-size: 0.85rem; cursor: pointer; text-align: center; }
198
+ .mem-file { margin-bottom: 0.5rem; border: 1px solid #21262d; border-radius: 8px; overflow: hidden; }
199
+ .mem-file-header { display: flex; align-items: center; gap: 0.35rem; padding: 0.4rem 0.6rem; background: #21262d; cursor: pointer; font-size: 0.75rem; color: #58a6ff; user-select: none; }
200
+ .mem-file-header:hover { background: #30363d; }
201
+ .mem-file-header .arrow { transition: transform 0.15s; font-size: 0.6rem; }
202
+ .mem-file-header .arrow.open { transform: rotate(90deg); }
203
+ .mem-file-body { display: none; padding: 0.5rem 0.6rem; background: #0d1117; font-family: 'SF Mono','Consolas',monospace; font-size: 0.72rem; line-height: 1.5; white-space: pre-wrap; word-break: break-word; color: #c9d1d9; max-height: 300px; overflow-y: auto; }
204
+ .mem-file-body.open { display: block; }
198
205
  .slash-inp { width: 100%; padding: 0.65rem 0.8rem; background: #0d1117; border: 1px solid #30363d; border-radius: 10px; color: #e6edf3; font-size: 0.9rem; outline: none; }
199
206
  .slash-inp:focus { border-color: #7c3aed; }
200
207
 
@@ -294,6 +301,7 @@
294
301
  <button class="sk sk-claude" onclick="doClear()">清除</button>
295
302
  <button class="sk sk-claude" onclick="doSlashCmd('/rename')">命名</button>
296
303
  <button class="sk sk-claude" onclick="openGitSheet()" style="color:#3fb950">提交</button>
304
+ <button class="sk sk-claude" onclick="showMemory()">记忆</button>
297
305
  </div>
298
306
  </div>
299
307
 
@@ -374,6 +382,17 @@
374
382
  </div>
375
383
  </div>
376
384
 
385
+ <div id="memory-sheet" class="action-sheet">
386
+ <div class="action-sheet-backdrop" onclick="closeMemorySheet()"></div>
387
+ <div class="action-sheet-box" style="max-height:80vh;display:flex;flex-direction:column">
388
+ <div class="action-sheet-title">Claude 记忆</div>
389
+ <div id="memory-content" style="flex:1;overflow-y:auto;padding:0.3rem 0;font-size:0.78rem;color:#c9d1d9;scrollbar-width:thin">
390
+ <div style="text-align:center;color:#8b949e;padding:2rem 0">加载中...</div>
391
+ </div>
392
+ <button class="action-sheet-cancel" onclick="closeMemorySheet()">关闭</button>
393
+ </div>
394
+ </div>
395
+
377
396
  <div id="status-overlay">连接中...</div>
378
397
 
379
398
  <script type="module">
@@ -436,8 +455,10 @@
436
455
  };
437
456
  window.doLogout = () => {
438
457
  if (!confirm('确定退出登录?')) return;
439
- socket.disconnect();
440
- fetch('/logout', { method: 'POST' }).then(() => { location.href = '/login'; });
458
+ fetch('/logout', { method: 'POST' }).finally(() => {
459
+ socket.disconnect();
460
+ location.href = '/login';
461
+ });
441
462
  };
442
463
 
443
464
  // ── 状态 ──────────────────────────────────────
@@ -1666,6 +1687,46 @@
1666
1687
  if (e.key === 'Enter') { e.preventDefault(); submitGitPush(); }
1667
1688
  });
1668
1689
 
1690
+ // ── Claude 记忆查看 ──────────────────────────────
1691
+ window.showMemory = async function() {
1692
+ const container = document.getElementById('memory-content');
1693
+ container.innerHTML = '<div style="text-align:center;color:#8b949e;padding:2rem 0">加载中...</div>';
1694
+ document.getElementById('memory-sheet').classList.add('open');
1695
+
1696
+ try {
1697
+ const resp = await fetch(`/api/memory?sessionId=${SESSION_ID}`);
1698
+ const data = await resp.json();
1699
+ const files = data.files || [];
1700
+
1701
+ if (files.length === 0) {
1702
+ container.innerHTML = '<div style="text-align:center;color:#8b949e;padding:2rem 0">暂无记忆文件</div>';
1703
+ return;
1704
+ }
1705
+
1706
+ container.innerHTML = '';
1707
+ for (const f of files) {
1708
+ const card = document.createElement('div');
1709
+ card.className = 'mem-file';
1710
+ const header = document.createElement('div');
1711
+ header.className = 'mem-file-header';
1712
+ header.innerHTML = `<span class="arrow">▶</span> ${escHtml(f.name)}`;
1713
+ const body = document.createElement('div');
1714
+ body.className = 'mem-file-body';
1715
+ body.textContent = f.content;
1716
+ header.onclick = () => { body.classList.toggle('open'); header.querySelector('.arrow').classList.toggle('open'); };
1717
+ card.appendChild(header);
1718
+ card.appendChild(body);
1719
+ container.appendChild(card);
1720
+ }
1721
+ } catch (e) {
1722
+ container.innerHTML = `<div style="text-align:center;color:#f85149;padding:2rem 0">加载失败: ${escHtml(e.message)}</div>`;
1723
+ }
1724
+ };
1725
+
1726
+ window.closeMemorySheet = function() {
1727
+ document.getElementById('memory-sheet').classList.remove('open');
1728
+ };
1729
+
1669
1730
  // ── Agent 模式快捷功能 ──────────────────────────
1670
1731
  let _totalAgentCost = 0;
1671
1732
  // 追踪 result 消息中的费用
package/dist/index.html CHANGED
@@ -591,8 +591,10 @@
591
591
 
592
592
  function doLogout() {
593
593
  if (!confirm('确定退出登录?')) return;
594
- socket.disconnect();
595
- fetch('/logout', { method: 'POST' }).then(() => { location.href = '/login'; });
594
+ fetch('/logout', { method: 'POST' }).finally(() => {
595
+ socket.disconnect();
596
+ location.href = '/login';
597
+ });
596
598
  }
597
599
 
598
600
  // HTML 转义,防止 XSS
package/dist/index.js CHANGED
@@ -357,19 +357,19 @@ Content-Length: `+T+`\r
357
357
  `),!0}catch{return!1}}_spawnProcess(){this._keepAliveTimer&&(clearInterval(this._keepAliveTimer),this._keepAliveTimer=null);const p=findClaude(),o=this._buildArgs(),g=this._buildEnv(),m=process.platform==="win32"?process.env.ComSpec||"cmd.exe":p,h=process.platform==="win32"?["/c",p,...o]:o;console.log(`[agent] \u542F\u52A8\u6301\u4E45\u8FDB\u7A0B: ${p} (\u4F1A\u8BDD: ${this.claudeSessionId||"\u65B0\u5EFA"})`),this._proc=(0,external_child_process_namespaceObject.spawn)(m,h,{cwd:this.cwd,env:g,stdio:["pipe","pipe","pipe"]}),this._buffer="",this._proc.stdout.on("data",l=>{this._buffer+=l.toString();const d=this._buffer.split(`
358
358
  `);this._buffer=d.pop()||"";for(const a of d){const n=a.replace(/\r$/,"");if(n)try{const r=JSON.parse(n);this._handleMessage(r)}catch{}}}),this._proc.stderr.on("data",l=>{const d=l.toString().trim();d&&(console.error("[agent:stderr]",d),!d.includes("Warning:")&&!d.includes("DeprecationWarning")&&this.emit("agent:error",{message:d}))}),this._proc.on("close",l=>{if(!this._interrupted&&this._buffer.trim())try{const d=JSON.parse(this._buffer.trim());this._handleMessage(d)}catch{}this._keepAliveTimer&&(clearInterval(this._keepAliveTimer),this._keepAliveTimer=null),this._proc=null,this._buffer="",console.log(`[agent] \u8FDB\u7A0B\u9000\u51FA (code ${l}), \u4F1A\u8BDD: ${this.claudeSessionId||"?"}`),this._busy&&(this._busy=!1,this._interrupted=!1,this.emit("agent:busy",!1),this._pendingReject&&(this._pendingReject(new Error(`Claude \u8FDB\u7A0B\u9000\u51FA (code ${l})`)),this._pendingResolve=null,this._pendingReject=null)),this.alive&&l!==0&&l!==null&&this.emit("agent:error",{message:`Claude \u8FDB\u7A0B\u5F02\u5E38\u9000\u51FA (code ${l})\uFF0C\u4E0B\u6B21\u67E5\u8BE2\u5C06\u81EA\u52A8\u91CD\u542F`})}),this._proc.on("error",l=>{this._keepAliveTimer&&(clearInterval(this._keepAliveTimer),this._keepAliveTimer=null),this._proc=null,this._busy=!1,this.emit("agent:busy",!1),this.emit("agent:error",{message:`\u542F\u52A8 Claude \u5931\u8D25: ${l.message}`}),this._pendingReject&&(this._pendingReject(l),this._pendingResolve=null,this._pendingReject=null)}),this._keepAliveTimer=setInterval(()=>{this._writeStdin({type:"keep_alive"})},3e4)}async query(p){if(this._busy){this.emit("agent:error",{message:"\u6B63\u5728\u5904\u7406\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210"});return}return this._busy=!0,this._interrupted=!1,this.emit("agent:busy",!0),this._history.push({type:"user",content:p,timestamp:Date.now()}),this._proc||this._spawnProcess(),new Promise((o,g)=>{this._pendingResolve=o,this._pendingReject=g,this._writeStdin({type:"user",message:{role:"user",content:p}})||(this._busy=!1,this.emit("agent:busy",!1),this._pendingResolve=null,this._pendingReject=null,g(new Error("Claude \u8FDB\u7A0B\u4E0D\u53EF\u5199")))})}respondPermission(p,o){o?this._writeStdin({type:"control_response",response:{request_id:p,subtype:"success",response:{}}}):this._writeStdin({type:"control_response",response:{request_id:p,subtype:"error",error:"User denied permission"}})}_handleMessage(p){const o=!!this.claudeSessionId;p.session_id&&!this.claudeSessionId&&(this.claudeSessionId=p.session_id),p.type==="system"&&p.subtype==="init"&&p.session_id&&(this.claudeSessionId=p.session_id),!o&&this.claudeSessionId&&this.emit("session-id",this.claudeSessionId);const g={...p,timestamp:Date.now()};JSON.stringify(g).length>10240&&(g._truncated=!0),this._history.push(g);const h=500;if(this._history.length>h&&(this._history=this._history.slice(-h)),this.emit("agent:message",p),p.type==="rate_limit_event"&&p.rate_limit_info&&(this._usage.rateLimitInfo=p.rate_limit_info),p.type==="result"&&(this._usage.queryCount++,p.total_cost_usd&&(this._usage.totalCostUSD+=p.total_cost_usd),p.usage&&(this._usage.totalInputTokens+=p.usage.input_tokens||0,this._usage.totalOutputTokens+=p.usage.output_tokens||0,this._usage.totalCacheReadTokens+=p.usage.cache_read_input_tokens||0,this._usage.totalCacheCreationTokens+=p.usage.cache_creation_input_tokens||0),p.modelUsage))for(const[l,d]of Object.entries(p.modelUsage))this._usage.modelUsage[l]||(this._usage.modelUsage[l]={inputTokens:0,outputTokens:0,costUSD:0}),this._usage.modelUsage[l].inputTokens+=d.inputTokens||0,this._usage.modelUsage[l].outputTokens+=d.outputTokens||0,this._usage.modelUsage[l].costUSD+=d.costUSD||0;p.type==="result"&&(this._busy=!1,this._interrupted=!1,this.emit("agent:busy",!1),this._pendingResolve&&(this._pendingResolve(0),this._pendingResolve=null,this._pendingReject=null))}setPermissionMode(p){if(this.permissionMode!==p&&(this.permissionMode=p,!this._busy&&this._proc)){this._keepAliveTimer&&(clearInterval(this._keepAliveTimer),this._keepAliveTimer=null);try{this._proc.kill("SIGTERM")}catch{}this._proc=null}}interrupt(){if(this._proc&&this._busy){this._interrupted=!0;try{this._proc.kill("SIGINT")}catch{try{this._proc.kill("SIGTERM")}catch{}}this.emit("agent:message",{type:"system",subtype:"interrupted",message:"\u67E5\u8BE2\u5DF2\u4E2D\u65AD"})}}kill(){if(this.alive=!1,this._keepAliveTimer&&(clearInterval(this._keepAliveTimer),this._keepAliveTimer=null),this._proc){try{this._proc.kill("SIGTERM")}catch{}this._proc=null}if(this._pendingReject){try{this._pendingReject(new Error("\u4F1A\u8BDD\u5DF2\u7EC8\u6B62"))}catch{}this._pendingResolve=null,this._pendingReject=null}this._busy=!1}_loadResumedHistory(){try{const p=(0,external_path_.join)((0,external_os_.homedir)(),".claude","projects");if(!(0,external_fs_.existsSync)(p))return;for(const o of(0,external_fs_.readdirSync)(p,{withFileTypes:!0})){if(!o.isDirectory())continue;const g=(0,external_path_.join)(p,o.name,this.claudeSessionId+".jsonl");if(!(0,external_fs_.existsSync)(g))continue;const h=(0,external_fs_.readFileSync)(g,"utf8").split(`
359
359
  `).filter(Boolean).slice(-20);for(const l of h)try{const d=JSON.parse(l);if(d.type==="human"&&d.message?.content){const a=typeof d.message.content=="string"?d.message.content:d.message.content.find(n=>n.type==="text")?.text||"";a&&this._history.push({type:"user",content:a,timestamp:d.timestamp})}else d.type==="assistant"&&d.message?.content?this._history.push({type:"assistant",message:d.message,timestamp:d.timestamp}):d.type==="result"&&this._history.push({type:"result",total_cost_usd:d.costUSD||d.total_cost_usd,timestamp:d.timestamp})}catch{}return}}catch(p){console.warn("[agent] \u52A0\u8F7D\u6062\u590D\u4F1A\u8BDD\u5386\u53F2\u5931\u8D25:",p.message)}}get isBusy(){return this._busy}addViewer(p,o){this._viewers.add(p),o&&this._viewerNames.set(p,o)}removeViewer(p){this._viewers.delete(p),this._viewerNames.delete(p)}get viewerCount(){return new Set(this._viewerNames.values()).size||this._viewers.size}takeControl(p,o){this.controlHolder=p,this.controlHolderName=o||null,this.lastInputTime=Date.now()}releaseControl(){this.controlHolder=null,this.controlHolderName=null,this.lastInputTime=null}isController(p){return this.controlHolder===p}toJSON(){return{id:this.id,title:this.title,cwd:this.cwd,createdAt:this.createdAt,mode:"agent",alive:this.alive,viewers:this.viewerCount,controlHolder:this.controlHolder,controlHolderName:this.controlHolderName,permissionMode:this.permissionMode,owner:this.owner,claudeSessionId:this.claudeSessionId,busy:this._busy,usage:this._usage}}}let _claudeSessionsCache=null,_claudeSessionsCacheTime=0;const CLAUDE_SESSIONS_CACHE_TTL=3e4;function listLocalClaudeSessions(_){const p=Date.now();if(_claudeSessionsCache&&p-_claudeSessionsCacheTime<CLAUDE_SESSIONS_CACHE_TTL){if(!_)return _claudeSessionsCache;const h=(0,external_path_.resolve)((0,external_path_.normalize)(_)).replace(/[\\/]+$/,"");return _claudeSessionsCache.filter(l=>(0,external_path_.resolve)((0,external_path_.normalize)(l.projectPath)).replace(/[\\/]+$/,"").startsWith(h))}const o=[],g=_?(0,external_path_.resolve)((0,external_path_.normalize)(_)).replace(/[\\/]+$/,""):null;try{const h=(0,external_path_.join)((0,external_os_.homedir)(),".claude","projects");if(!(0,external_fs_.existsSync)(h))return o;for(const l of(0,external_fs_.readdirSync)(h,{withFileTypes:!0})){if(!l.isDirectory())continue;const d=(0,external_path_.join)(h,l.name);try{for(const a of(0,external_fs_.readdirSync)(d)){if(!a.endsWith(".jsonl"))continue;const n=a.replace(".jsonl",""),r=(0,external_path_.join)(d,a);try{const t=(0,external_fs_.readFileSync)(r,"utf8").split(`
360
- `).filter(Boolean);let e=null,c=null,s=0,i="";for(const u of t)try{const v=JSON.parse(u);if(e||(e=v),c=v,s++,!i&&v.type==="human"&&v.message?.content){const b=v.message.content;if(typeof b=="string")i=b.slice(0,120);else if(Array.isArray(b)){const w=b.find(E=>E.type==="text");w&&(i=w.text?.slice(0,120)||"")}}}catch{}let f=l.name;if(process.platform==="win32"&&/^[A-Za-z]--/.test(f)?f=f[0]+":\\"+f.slice(3).replace(/-/g,"\\"):f.startsWith("-")&&(f=f.replace(/-/g,"/")),g&&!(0,external_path_.resolve)((0,external_path_.normalize)(f)).replace(/[\\/]+$/,"").startsWith(g))continue;o.push({sessionId:n,projectDir:l.name,projectPath:f,messageCount:s,summary:i,createdAt:e?.timestamp||null,updatedAt:c?.timestamp||null})}catch{}}}catch{}}}catch{}o.sort((h,l)=>new Date(l.updatedAt||0).getTime()-new Date(h.updatedAt||0).getTime());const m=o.slice(0,50);return _claudeSessionsCache=m,_claudeSessionsCacheTime=Date.now(),m.slice(0,20)}const DEFAULT_SHELL=process.platform==="win32"?process.env.SHELL||"powershell.exe":process.env.SHELL||"/bin/bash",CONFIG_DIR=(0,external_path_.join)((0,external_os_.homedir)(),".myhi"),SESSIONS_FILE=(0,external_path_.join)(CONFIG_DIR,"sessions.json");function loadSavedConfigs(){try{return JSON.parse((0,external_fs_.readFileSync)(SESSIONS_FILE,"utf8"))}catch{return[]}}function writeSavedConfigs(_){try{(0,external_fs_.mkdirSync)(CONFIG_DIR,{recursive:!0});const p=SESSIONS_FILE+".tmp";(0,external_fs_.writeFileSync)(p,JSON.stringify(_,null,2),{mode:384}),(0,external_fs_.renameSync)(p,SESSIONS_FILE)}catch(p){console.error("[sessions] \u6301\u4E45\u5316\u5199\u5165\u5931\u8D25:",p.message)}}class Session extends external_events_.EventEmitter{constructor(p,o={}){super(),this.id=p,this.createdAt=new Date().toISOString(),this.title=o.title||DEFAULT_SHELL,this.cwd=o.cwd||process.env.MYHI_CWD||process.cwd(),this.initCmd=o.initCmd||null,this.permissionMode=o.permissionMode||"default",this.owner=o.owner||null,this.cols=o.cols||120,this.rows=o.rows||30,this._viewers=new Set,this._viewerNames=new Map,this.controlHolder=null,this.controlHolderName=null,this.lastInputTime=null;const g={...process.env};if(delete g.CLAUDECODE,delete g.CLAUDE_CODE_ENTRYPOINT,delete g.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC,o.userDir){const h=(0,external_path_.join)(o.userDir,".gitconfig");(0,external_fs_.existsSync)(h)&&(g.GIT_CONFIG_GLOBAL=h)}this._pty=external_node_pty_namespaceObject.spawn(DEFAULT_SHELL,[],{name:"xterm-256color",cols:this.cols,rows:this.rows,cwd:this.cwd,env:g,useConpty:!1}),this._scrollback="";const m=100*1024;if(this._pty.onData(h=>{this.emit("data",h),this._scrollback+=h,this._scrollback.length>m&&(this._scrollback=this._scrollback.slice(this._scrollback.length-m))}),this._pty.onExit(({exitCode:h})=>{this.exitCode=h,this.emit("exit",h)}),o.initCmd){const h=o.initCmd;let l=!1;const d=a=>{!l&&/[$>#\]]\s*$/.test(a.trimEnd())&&(l=!0,this._pty.off("data",d),setTimeout(()=>this._pty.write(h+"\r"),120))};this._pty.onData(d),setTimeout(()=>{l||(l=!0,this._pty.write(h+"\r"))},3e3)}}write(p){p!=null&&this._pty.write(p)}resize(p,o){this.cols=p,this.rows=o,this._pty.resize(p,o)}kill(){try{this._pty.kill()}catch{}}addViewer(p,o){this._viewers.add(p),o&&this._viewerNames.set(p,o)}removeViewer(p){this._viewers.delete(p),this._viewerNames.delete(p)}get viewerCount(){return new Set(this._viewerNames.values()).size||this._viewers.size}takeControl(p,o){this.controlHolder=p,this.controlHolderName=o||null,this.lastInputTime=Date.now()}releaseControl(){this.controlHolder=null,this.controlHolderName=null,this.lastInputTime=null}isController(p){return this.controlHolder===p}toJSON(){return{id:this.id,title:this.title,cwd:this.cwd,createdAt:this.createdAt,cols:this.cols,rows:this.rows,viewers:this.viewerCount,alive:this.exitCode===void 0,controlHolder:this.controlHolder,controlHolderName:this.controlHolderName,permissionMode:this.permissionMode,owner:this.owner}}}class SessionManager{constructor(){this._sessions=new Map,this._agentSessions=new Map;for(const p of loadSavedConfigs())try{if(p.mode==="agent"){if(p.claudeSessionId){const o=(0,external_crypto_.randomUUID)(),g=new AgentSession(o,{...p,resumeSessionId:p.claudeSessionId});g.on("session-id",()=>this._persist()),this._agentSessions.set(o,g),console.log(`[\u4F1A\u8BDD] \u6062\u590D Agent "${p.title}" (claude: ${p.claudeSessionId.slice(0,8)}...)`)}}else this._spawn((0,external_crypto_.randomUUID)(),p,!1)}catch(o){console.warn(`[\u4F1A\u8BDD] \u6062\u590D "${p.title}" \u5931\u8D25:`,o.message)}}_spawn(p,o,g=!0){const m=new Session(p,o);return this._sessions.set(p,m),m.on("exit",()=>{setTimeout(()=>this._sessions.delete(p),300*1e3)}),g&&this._persist(),m}create(p={}){return this._spawn((0,external_crypto_.randomUUID)(),p,!0)}createAgent(p={}){const o=(0,external_crypto_.randomUUID)(),g=new AgentSession(o,p);return this._agentSessions.set(o,g),g.on("session-id",()=>this._persist()),this._persist(),g}get(p){return this._sessions.get(p)||this._agentSessions.get(p)}list(p,o=!1){let g=[...this._sessions.values()].map(h=>({...h.toJSON(),mode:"pty"})),m=[...this._agentSessions.values()].map(h=>h.toJSON());return p&&!o&&(g=g.filter(h=>!h.owner||h.owner===p),m=m.filter(h=>!h.owner||h.owner===p)),[...g,...m]}listSessions(){return[...this._sessions.values(),...this._agentSessions.values()]}kill(p){const o=this._sessions.get(p);if(o){o.kill(),this._sessions.delete(p),this._persist();return}const g=this._agentSessions.get(p);g&&(g.kill(),this._agentSessions.delete(p),this._persist())}_persist(){const p=[...this._sessions.values()].filter(g=>g.exitCode===void 0).map(g=>({mode:"pty",title:g.title,cwd:g.cwd,initCmd:g.initCmd||void 0,permissionMode:g.permissionMode||void 0,owner:g.owner||void 0})),o=[...this._agentSessions.values()].filter(g=>g.alive).map(g=>({mode:"agent",title:g.title,cwd:g.cwd,permissionMode:g.permissionMode||void 0,owner:g.owner||void 0,userDir:g.userDir||void 0,claudeSessionId:g.claudeSessionId||void 0}));writeSavedConfigs([...p,...o])}}const roles_CONFIG_DIR=(0,external_path_.join)((0,external_os_.homedir)(),".myhi"),ROLES_FILE=(0,external_path_.join)(roles_CONFIG_DIR,"roles.json"),USERS_FILE=(0,external_path_.join)(roles_CONFIG_DIR,"users.json"),ROLE_LEVELS={admin:3,operator:2,viewer:1};let rolesConfig=null;function loadRoles(){try{if((0,external_fs_.existsSync)(ROLES_FILE))return rolesConfig=JSON.parse((0,external_fs_.readFileSync)(ROLES_FILE,"utf8")),rolesConfig}catch(_){console.warn("[\u89D2\u8272] \u52A0\u8F7D roles.json \u5931\u8D25:",_.message)}return rolesConfig=null,null}function lookupPassword(_){if(!rolesConfig?.passwords)return null;const p=rolesConfig.passwords[_];return p?{name:p.name||"\u7528\u6237",role:p.role||"viewer"}:null}function hasPermission(_,p){return(ROLE_LEVELS[_]||0)>=(ROLE_LEVELS[p]||0)}function isMultiUserMode(){return rolesConfig!==null&&rolesConfig.passwords&&Object.keys(rolesConfig.passwords).length>0}function getDefaultRole(){return rolesConfig?.defaultRole||"admin"}let usersConfig=null;function loadUsers(){try{if((0,external_fs_.existsSync)(USERS_FILE))return usersConfig=JSON.parse((0,external_fs_.readFileSync)(USERS_FILE,"utf8")),usersConfig}catch(_){console.warn("[\u7528\u6237] \u52A0\u8F7D users.json \u5931\u8D25:",_.message)}return usersConfig=null,null}function hasUsers(){return usersConfig!==null&&usersConfig.users&&Object.keys(usersConfig.users).length>0}function isExclusiveMode(){return hasUsers()&&usersConfig.exclusive===!0}function setExclusiveMode(_){usersConfig||(usersConfig={users:{}}),usersConfig.exclusive=!!_,_writeUsers()}function lookupUser(_){if(!usersConfig?.users)return null;const p=usersConfig.users[_];return p?{name:p.name||"\u7528\u6237",dir:p.dir}:null}function listUsers(){return usersConfig?.users?Object.entries(usersConfig.users).map(([_,p])=>({password:_.slice(0,2)+"*".repeat(_.length-2),name:p.name,dir:p.dir})):[]}function addUser(_,p,o){usersConfig||(usersConfig={users:{}}),usersConfig.users||(usersConfig.users={}),usersConfig.users[_]={name:p,dir:o},_writeUsers()}function removeUser(_){return!usersConfig?.users||!usersConfig.users[_]?!1:(delete usersConfig.users[_],_writeUsers(),!0)}function _writeUsers(){(0,external_fs_.mkdirSync)(roles_CONFIG_DIR,{recursive:!0}),(0,external_fs_.writeFileSync)(USERS_FILE,JSON.stringify(usersConfig,null,2),{mode:384})}const server_dirname=(0,external_path_.dirname)((0,external_url_.fileURLToPath)(import.meta.url));let PORT=parseInt(process.env.PORT,10)||12300;const HOST=process.env.HOST||"0.0.0.0",PORT_EXPLICIT=!!process.env.PORT,configDir=(0,external_path_.join)((0,external_os_.homedir)(),".myhi");(0,external_fs_.mkdirSync)(configDir,{recursive:!0});const pidFile=(0,external_path_.join)(configDir,"daemon.pid"),IS_DAEMON_CHILD=!!process.env.MYHI_DAEMON;(function _(){if(!IS_DAEMON_CHILD){try{const p=parseInt((0,external_fs_.readFileSync)(pidFile,"utf8").trim(),10);if(p&&p!==process.pid){let o=!1;try{process.kill(p,0),o=!0}catch{}if(o){console.log(`[myhi] \u68C0\u6D4B\u5230\u5DF2\u8FD0\u884C\u7684\u5B9E\u4F8B (PID ${p})\uFF0C\u6B63\u5728\u505C\u6B62...`);try{process.platform==="win32"?(0,external_child_process_namespaceObject.execSync)(`taskkill /PID ${p} /F /T`,{stdio:"pipe",timeout:5e3}):process.kill(p,"SIGTERM")}catch(h){console.warn(`[myhi] \u505C\u6B62\u65E7\u8FDB\u7A0B\u5931\u8D25: ${h.message?.split(`
361
- `)[0]||h}`)}const g=Date.now()+5e3;for(;Date.now()<g;){try{process.kill(p,0)}catch{break}const h=Date.now()+200;for(;Date.now()<h;);}let m=!1;try{process.kill(p,0),m=!0}catch{}m&&(console.error(`[myhi] \u65E0\u6CD5\u505C\u6B62\u65E7\u8FDB\u7A0B (PID ${p})\uFF0C\u8BF7\u624B\u52A8\u6267\u884C\uFF1A`),console.error(" myhi stop"),console.error(` \u6216: ${process.platform==="win32"?`taskkill /PID ${p} /F`:`kill -9 ${p}`}`),process.exit(1))}}}catch{}IS_DAEMON_CHILD||(0,external_fs_.writeFileSync)(pidFile,String(process.pid))}})();function cleanupPid(){try{parseInt((0,external_fs_.readFileSync)(pidFile,"utf8").trim(),10)===process.pid&&(0,external_fs_.unlinkSync)(pidFile)}catch{}}function gracefulShutdown(){try{if(typeof manager<"u")for(const _ of manager.listSessions())try{_.kill()}catch{}}catch{}cleanupPid()}process.on("exit",cleanupPid),process.on("SIGTERM",()=>{gracefulShutdown(),process.exit(0)}),process.on("SIGINT",()=>{gracefulShutdown(),process.exit(0)}),process.on("uncaughtException",_=>{console.error("[myhi] \u672A\u6355\u83B7\u5F02\u5E38:",_.message),gracefulShutdown(),process.exit(1)}),process.on("unhandledRejection",_=>{console.error("[myhi] \u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:",_)});function readOrCreate(_,p){try{if((0,external_fs_.existsSync)(_))return(0,external_fs_.readFileSync)(_,"utf8").trim()}catch{}const o=p();return(0,external_fs_.writeFileSync)(_,o,{mode:384}),o}const TOKEN=readOrCreate((0,external_path_.join)(configDir,"token"),()=>(0,external_crypto_.randomBytes)(16).toString("hex"));let PASSWORD=readOrCreate((0,external_path_.join)(configDir,"password"),()=>String(Math.floor(1e3+Math.random()*9e3)));loadRoles(),loadUsers();let activeUser=null;const userSessions=new Map;userSessions.set(TOKEN,{role:"admin",name:"\u7BA1\u7406\u5458",permanent:!0});const SESSION_MAX_AGE=10080*60*1e3;setInterval(()=>{const _=Date.now();for(const[p,o]of userSessions)o.permanent||(!o.lastUsed||_-o.lastUsed>SESSION_MAX_AGE)&&userSessions.delete(p)},3600*1e3);function getTailscaleIP(){try{return(0,external_child_process_namespaceObject.execSync)("tailscale ip -4",{timeout:3e3,stdio:"pipe"}).toString().trim().split(`
360
+ `).filter(Boolean);let e=null,c=null,s=0,i="";for(const u of t)try{const v=JSON.parse(u);if(e||(e=v),c=v,s++,!i&&v.type==="human"&&v.message?.content){const b=v.message.content;if(typeof b=="string")i=b.slice(0,120);else if(Array.isArray(b)){const w=b.find(E=>E.type==="text");w&&(i=w.text?.slice(0,120)||"")}}}catch{}let f=l.name;if(process.platform==="win32"&&/^[A-Za-z]--/.test(f)?f=f[0]+":\\"+f.slice(3).replace(/-/g,"\\"):f.startsWith("-")&&(f=f.replace(/-/g,"/")),g&&!(0,external_path_.resolve)((0,external_path_.normalize)(f)).replace(/[\\/]+$/,"").startsWith(g))continue;o.push({sessionId:n,projectDir:l.name,projectPath:f,messageCount:s,summary:i,createdAt:e?.timestamp||null,updatedAt:c?.timestamp||null})}catch{}}}catch{}}}catch{}o.sort((h,l)=>new Date(l.updatedAt||0).getTime()-new Date(h.updatedAt||0).getTime());const m=o.slice(0,50);return _claudeSessionsCache=m,_claudeSessionsCacheTime=Date.now(),m.slice(0,20)}const DEFAULT_SHELL=process.platform==="win32"?process.env.SHELL||"powershell.exe":process.env.SHELL||"/bin/bash",CONFIG_DIR=(0,external_path_.join)((0,external_os_.homedir)(),".myhi"),SESSIONS_FILE=(0,external_path_.join)(CONFIG_DIR,"sessions.json");function loadSavedConfigs(){try{return JSON.parse((0,external_fs_.readFileSync)(SESSIONS_FILE,"utf8"))}catch{return[]}}function writeSavedConfigs(_){try{(0,external_fs_.mkdirSync)(CONFIG_DIR,{recursive:!0});const p=SESSIONS_FILE+".tmp";(0,external_fs_.writeFileSync)(p,JSON.stringify(_,null,2),{mode:384}),(0,external_fs_.renameSync)(p,SESSIONS_FILE)}catch(p){console.error("[sessions] \u6301\u4E45\u5316\u5199\u5165\u5931\u8D25:",p.message)}}class Session extends external_events_.EventEmitter{constructor(p,o={}){super(),this.id=p,this.createdAt=new Date().toISOString(),this.title=o.title||DEFAULT_SHELL,this.cwd=o.cwd||process.env.MYHI_CWD||process.cwd(),this.initCmd=o.initCmd||null,this.permissionMode=o.permissionMode||"default",this.owner=o.owner||null,this.cols=o.cols||120,this.rows=o.rows||30,this._viewers=new Set,this._viewerNames=new Map,this.controlHolder=null,this.controlHolderName=null,this.lastInputTime=null;const g={...process.env};if(delete g.CLAUDECODE,delete g.CLAUDE_CODE_ENTRYPOINT,delete g.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC,o.userDir){const h=(0,external_path_.join)(o.userDir,".gitconfig");(0,external_fs_.existsSync)(h)&&(g.GIT_CONFIG_GLOBAL=h)}this._pty=external_node_pty_namespaceObject.spawn(DEFAULT_SHELL,[],{name:"xterm-256color",cols:this.cols,rows:this.rows,cwd:this.cwd,env:g,useConpty:!1}),this._scrollback="";const m=100*1024;if(this._pty.onData(h=>{this.emit("data",h),this._scrollback+=h,this._scrollback.length>m&&(this._scrollback=this._scrollback.slice(this._scrollback.length-m))}),this._pty.onExit(({exitCode:h})=>{this.exitCode=h,this.emit("exit",h)}),o.initCmd){const h=o.initCmd;let l=!1;const d=a=>{!l&&/[$>#\]]\s*$/.test(a.trimEnd())&&(l=!0,this._pty.off("data",d),setTimeout(()=>this._pty.write(h+"\r"),120))};this._pty.onData(d),setTimeout(()=>{l||(l=!0,this._pty.write(h+"\r"))},3e3)}}write(p){p!=null&&this._pty.write(p)}resize(p,o){this.cols=p,this.rows=o,this._pty.resize(p,o)}kill(){try{this._pty.kill()}catch{}}addViewer(p,o){this._viewers.add(p),o&&this._viewerNames.set(p,o)}removeViewer(p){this._viewers.delete(p),this._viewerNames.delete(p)}get viewerCount(){return new Set(this._viewerNames.values()).size||this._viewers.size}takeControl(p,o){this.controlHolder=p,this.controlHolderName=o||null,this.lastInputTime=Date.now()}releaseControl(){this.controlHolder=null,this.controlHolderName=null,this.lastInputTime=null}isController(p){return this.controlHolder===p}toJSON(){return{id:this.id,title:this.title,cwd:this.cwd,createdAt:this.createdAt,cols:this.cols,rows:this.rows,viewers:this.viewerCount,alive:this.exitCode===void 0,controlHolder:this.controlHolder,controlHolderName:this.controlHolderName,permissionMode:this.permissionMode,owner:this.owner}}}class SessionManager{constructor(){this._sessions=new Map,this._agentSessions=new Map;for(const p of loadSavedConfigs())try{if(p.mode==="agent"){if(p.claudeSessionId){const o=(0,external_crypto_.randomUUID)(),g=new AgentSession(o,{...p,resumeSessionId:p.claudeSessionId});g.on("session-id",()=>this._persist()),this._agentSessions.set(o,g),console.log(`[\u4F1A\u8BDD] \u6062\u590D Agent "${p.title}" (claude: ${p.claudeSessionId.slice(0,8)}...)`)}}else this._spawn((0,external_crypto_.randomUUID)(),p,!1)}catch(o){console.warn(`[\u4F1A\u8BDD] \u6062\u590D "${p.title}" \u5931\u8D25:`,o.message)}}_spawn(p,o,g=!0){const m=new Session(p,o);return this._sessions.set(p,m),m.on("exit",()=>{setTimeout(()=>this._sessions.delete(p),300*1e3)}),g&&this._persist(),m}create(p={}){return this._spawn((0,external_crypto_.randomUUID)(),p,!0)}createAgent(p={}){const o=(0,external_crypto_.randomUUID)(),g=new AgentSession(o,p);return this._agentSessions.set(o,g),g.on("session-id",()=>this._persist()),this._persist(),g}get(p){return this._sessions.get(p)||this._agentSessions.get(p)}list(p,o=!1){let g=[...this._sessions.values()].map(h=>({...h.toJSON(),mode:"pty"})),m=[...this._agentSessions.values()].map(h=>h.toJSON());return p&&!o&&(g=g.filter(h=>!h.owner||h.owner===p),m=m.filter(h=>!h.owner||h.owner===p)),[...g,...m]}listSessions(){return[...this._sessions.values(),...this._agentSessions.values()]}kill(p){const o=this._sessions.get(p);if(o){o.kill(),this._sessions.delete(p),this._persist();return}const g=this._agentSessions.get(p);g&&(g.kill(),this._agentSessions.delete(p),this._persist())}persist(){this._persist()}_persist(){const p=[...this._sessions.values()].filter(g=>g.exitCode===void 0).map(g=>({mode:"pty",title:g.title,cwd:g.cwd,initCmd:g.initCmd||void 0,permissionMode:g.permissionMode||void 0,owner:g.owner||void 0})),o=[...this._agentSessions.values()].filter(g=>g.alive).map(g=>({mode:"agent",title:g.title,cwd:g.cwd,permissionMode:g.permissionMode||void 0,owner:g.owner||void 0,userDir:g.userDir||void 0,claudeSessionId:g.claudeSessionId||void 0}));writeSavedConfigs([...p,...o])}}const roles_CONFIG_DIR=(0,external_path_.join)((0,external_os_.homedir)(),".myhi"),ROLES_FILE=(0,external_path_.join)(roles_CONFIG_DIR,"roles.json"),USERS_FILE=(0,external_path_.join)(roles_CONFIG_DIR,"users.json"),ROLE_LEVELS={admin:3,operator:2,viewer:1};let rolesConfig=null;function loadRoles(){try{if((0,external_fs_.existsSync)(ROLES_FILE))return rolesConfig=JSON.parse((0,external_fs_.readFileSync)(ROLES_FILE,"utf8")),rolesConfig}catch(_){console.warn("[\u89D2\u8272] \u52A0\u8F7D roles.json \u5931\u8D25:",_.message)}return rolesConfig=null,null}function lookupPassword(_){if(!rolesConfig?.passwords)return null;const p=rolesConfig.passwords[_];return p?{name:p.name||"\u7528\u6237",role:p.role||"viewer"}:null}function hasPermission(_,p){return(ROLE_LEVELS[_]||0)>=(ROLE_LEVELS[p]||0)}function isMultiUserMode(){return rolesConfig!==null&&rolesConfig.passwords&&Object.keys(rolesConfig.passwords).length>0}function getDefaultRole(){return rolesConfig?.defaultRole||"admin"}let usersConfig=null;function loadUsers(){try{if((0,external_fs_.existsSync)(USERS_FILE))return usersConfig=JSON.parse((0,external_fs_.readFileSync)(USERS_FILE,"utf8")),usersConfig}catch(_){console.warn("[\u7528\u6237] \u52A0\u8F7D users.json \u5931\u8D25:",_.message)}return usersConfig=null,null}function hasUsers(){return usersConfig!==null&&usersConfig.users&&Object.keys(usersConfig.users).length>0}function isExclusiveMode(){return hasUsers()&&usersConfig.exclusive===!0}function setExclusiveMode(_){usersConfig||(usersConfig={users:{}}),usersConfig.exclusive=!!_,_writeUsers()}function lookupUser(_){if(!usersConfig?.users)return null;const p=usersConfig.users[_];return p?{name:p.name||"\u7528\u6237",dir:p.dir}:null}function listUsers(){return usersConfig?.users?Object.entries(usersConfig.users).map(([_,p])=>({password:_.slice(0,2)+"*".repeat(_.length-2),name:p.name,dir:p.dir})):[]}function addUser(_,p,o){usersConfig||(usersConfig={users:{}}),usersConfig.users||(usersConfig.users={}),usersConfig.users[_]={name:p,dir:o},_writeUsers()}function removeUser(_){return!usersConfig?.users||!usersConfig.users[_]?!1:(delete usersConfig.users[_],_writeUsers(),!0)}function _writeUsers(){(0,external_fs_.mkdirSync)(roles_CONFIG_DIR,{recursive:!0}),(0,external_fs_.writeFileSync)(USERS_FILE,JSON.stringify(usersConfig,null,2),{mode:384})}const server_dirname=(0,external_path_.dirname)((0,external_url_.fileURLToPath)(import.meta.url));let PORT=parseInt(process.env.PORT,10)||12300;const HOST=process.env.HOST||"0.0.0.0",PORT_EXPLICIT=!!process.env.PORT,configDir=(0,external_path_.join)((0,external_os_.homedir)(),".myhi");(0,external_fs_.mkdirSync)(configDir,{recursive:!0});const pidFile=(0,external_path_.join)(configDir,"daemon.pid"),IS_DAEMON_CHILD=!!process.env.MYHI_DAEMON;(function _(){if(!IS_DAEMON_CHILD){try{const p=parseInt((0,external_fs_.readFileSync)(pidFile,"utf8").trim(),10);if(p&&p!==process.pid){let o=!1;try{process.kill(p,0),o=!0}catch{}if(o){console.log(`[myhi] \u68C0\u6D4B\u5230\u5DF2\u8FD0\u884C\u7684\u5B9E\u4F8B (PID ${p})\uFF0C\u6B63\u5728\u505C\u6B62...`);try{process.platform==="win32"?(0,external_child_process_namespaceObject.execSync)(`taskkill /PID ${p} /F /T`,{stdio:"pipe",timeout:5e3}):process.kill(p,"SIGTERM")}catch(h){console.warn(`[myhi] \u505C\u6B62\u65E7\u8FDB\u7A0B\u5931\u8D25: ${h.message?.split(`
361
+ `)[0]||h}`)}const g=Date.now()+5e3;for(;Date.now()<g;){try{process.kill(p,0)}catch{break}const h=Date.now()+200;for(;Date.now()<h;);}let m=!1;try{process.kill(p,0),m=!0}catch{}m&&(console.error(`[myhi] \u65E0\u6CD5\u505C\u6B62\u65E7\u8FDB\u7A0B (PID ${p})\uFF0C\u8BF7\u624B\u52A8\u6267\u884C\uFF1A`),console.error(" myhi stop"),console.error(` \u6216: ${process.platform==="win32"?`taskkill /PID ${p} /F`:`kill -9 ${p}`}`),process.exit(1))}}}catch{}IS_DAEMON_CHILD||(0,external_fs_.writeFileSync)(pidFile,String(process.pid))}})();function cleanupPid(){try{parseInt((0,external_fs_.readFileSync)(pidFile,"utf8").trim(),10)===process.pid&&(0,external_fs_.unlinkSync)(pidFile)}catch{}}function gracefulShutdown(){try{if(typeof manager<"u"){try{manager.persist()}catch{}for(const _ of manager.listSessions())try{_.kill()}catch{}}}catch{}cleanupPid()}process.on("exit",cleanupPid),process.on("SIGTERM",()=>{gracefulShutdown(),process.exit(0)}),process.on("SIGINT",()=>{gracefulShutdown(),process.exit(0)}),process.on("uncaughtException",_=>{console.error("[myhi] \u672A\u6355\u83B7\u5F02\u5E38:",_.message),gracefulShutdown(),process.exit(1)}),process.on("unhandledRejection",_=>{console.error("[myhi] \u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:",_)});function readOrCreate(_,p){try{if((0,external_fs_.existsSync)(_))return(0,external_fs_.readFileSync)(_,"utf8").trim()}catch{}const o=p();return(0,external_fs_.writeFileSync)(_,o,{mode:384}),o}const TOKEN=readOrCreate((0,external_path_.join)(configDir,"token"),()=>(0,external_crypto_.randomBytes)(16).toString("hex"));let PASSWORD=readOrCreate((0,external_path_.join)(configDir,"password"),()=>String(Math.floor(1e3+Math.random()*9e3)));loadRoles(),loadUsers();let activeUser=null;const userSessions=new Map;userSessions.set(TOKEN,{role:"admin",name:"\u7BA1\u7406\u5458",permanent:!0});const SESSION_MAX_AGE=10080*60*1e3;setInterval(()=>{const _=Date.now();for(const[p,o]of userSessions)o.permanent||(!o.lastUsed||_-o.lastUsed>SESSION_MAX_AGE)&&userSessions.delete(p)},3600*1e3);function getTailscaleIP(){try{return(0,external_child_process_namespaceObject.execSync)("tailscale ip -4",{timeout:3e3,stdio:"pipe"}).toString().trim().split(`
362
362
  `)[0]}catch{}try{if(process.platform==="win32"){const p=(0,external_child_process_namespaceObject.execSync)("route print 0.0.0.0",{timeout:3e3,stdio:"pipe"}).toString().match(/0\.0\.0\.0\s+0\.0\.0\.0\s+\S+\s+(\d+\.\d+\.\d+\.\d+)/);if(p)return p[1]}else try{const p=(0,external_child_process_namespaceObject.execSync)("ip route get 1.1.1.1",{timeout:3e3,stdio:"pipe"}).toString().match(/src\s+(\d+\.\d+\.\d+\.\d+)/);if(p)return p[1]}catch{const _=(0,external_child_process_namespaceObject.execSync)("route -n get default 2>/dev/null || route -n 2>/dev/null",{timeout:3e3,stdio:"pipe"}).toString(),p=_.match(/interface:\s*(\S+)/i)||_.match(/0\.0\.0\.0\s+(\d+\.\d+\.\d+\.\d+)/);if(p){const o=(0,external_os_.networkInterfaces)()[p[1]];if(o){const g=o.find(m=>m.family==="IPv4"&&!m.internal);if(g)return g.address}if(/^\d+\.\d+\.\d+\.\d+$/.test(p[1]))return p[1]}}}catch{}for(const _ of Object.values((0,external_os_.networkInterfaces)()))for(const p of _)if(p.family==="IPv4"&&!p.internal)return p.address;return"localhost"}function parseCookies(_=""){const p={};for(const o of _.split(";")){const g=o.indexOf("=");g>0&&(p[o.slice(0,g).trim()]=o.slice(g+1).trim())}return p}const SESSION_COOKIE="myhi_sid",COOKIE_OPTS="HttpOnly; SameSite=Strict; Path=/; Max-Age=31536000";function setSessionCookie(_,p){_.setHeader("Set-Cookie",`${SESSION_COOKIE}=${p||TOKEN}; ${COOKIE_OPTS}`)}function getSessionToken(_){return parseCookies(_)[SESSION_COOKIE]||null}function hasValidSession(_){const p=getSessionToken(_);if(p&&userSessions.has(p)){const o=userSessions.get(p);return o&&(o.lastUsed=Date.now()),!0}return!1}function getUserInfo(_){const p=getSessionToken(_);return p?userSessions.get(p):null}const app=express();app.set("trust proxy",!0),app.use((_,p,o)=>{p.setHeader("Referrer-Policy","no-referrer"),p.setHeader("X-Content-Type-Options","nosniff"),o()});const httpServer=(0,external_http_.createServer)(app),_corsAllowed=/^https?:\/\/(localhost|127\.0\.0\.1|\[::1\]|10\.\d+\.\d+\.\d+|172\.(1[6-9]|2\d|3[01])\.\d+\.\d+|192\.168\.\d+\.\d+|100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.\d+\.\d+)(:\d+)?$/,io=new Server({cors:{origin:(_,p)=>{if(!_||_corsAllowed.test(_))return p(null,!0);p(new Error("CORS \u4E0D\u5141\u8BB8\u6B64\u6765\u6E90"),!1)},credentials:!0},transports:["websocket","polling"],pingTimeout:6e4,pingInterval:25e3,upgradeTimeout:3e4,maxHttpBufferSize:5e6});io.attach(httpServer);const manager=new SessionManager,AUTO_ATTACH=process.env.MYHI_AUTO_ATTACH!=="0",_autoSpawned=new Set;function spawnLocalTerminal(_,p){if(!AUTO_ATTACH||_autoSpawned.has(_)||!process.env.DISPLAY&&process.platform==="linux")return;_autoSpawned.add(_);const o=__nccwpck_require__.ab+"attach.js",g=process.execPath,m=p||"myhi";if(process.platform==="win32"){const h=`"${g}" "${o}" ${_}`,l=(0,external_child_process_namespaceObject.spawn)("wt.exe",["-w","0","new-tab","--title",m,"--","cmd","/k",h],{detached:!0,stdio:"ignore"});l.unref(),l.on("error",d=>{console.warn("[attach] wt.exe \u5931\u8D25\uFF0C\u56DE\u9000\u5230 cmd:",d.message),(0,external_child_process_namespaceObject.spawn)("cmd.exe",["/c","start",`"${m}"`,"cmd","/k",h],{detached:!0,stdio:"ignore"}).unref()})}else if(process.platform==="darwin"){const h=`tell application "Terminal" to do script "${g} ${o} ${_}"`;(0,external_child_process_namespaceObject.spawn)("osascript",["-e",h],{detached:!0,stdio:"ignore"}).unref()}else{const h=`${g} "${o}" ${_}`;for(const[l,d]of[["gnome-terminal",["--","bash","-c",`${h}; exec bash`]],["xterm",["-title",m,"-e",h]],["konsole",["--new-tab","-e",h]]]){const a=(0,external_child_process_namespaceObject.spawn)(l,d,{detached:!0,stdio:"ignore"});a.unref(),a.on("error",()=>{});break}}}const uploadDir=(0,external_path_.join)(configDir,"uploads");(0,external_fs_.mkdirSync)(uploadDir,{recursive:!0});function checkAuth(_,p,o){const g=_.query.token;if(g&&(g===TOKEN||userSessions.has(g))){const m=userSessions.get(g);if(m?.onetime){userSessions.delete(g);const l=(0,external_crypto_.randomBytes)(16).toString("hex");userSessions.set(l,{role:m.role,name:m.name,lastUsed:Date.now()}),setSessionCookie(p,l)}else setSessionCookie(p,g===TOKEN?TOKEN:g);const h=_.path+(Object.keys(_.query).filter(l=>l!=="token").length?"?"+new URLSearchParams(Object.fromEntries(Object.entries(_.query).filter(([l])=>l!=="token"))):"");return p.redirect(h)}if(hasValidSession(_.headers.cookie)){if(isExclusiveMode()&&activeUser){const m=getSessionToken(_.headers.cookie);if(m!==TOKEN){const h=userSessions.get(m);if(!h||h.name!==activeUser.name)return p.setHeader("Set-Cookie",`${SESSION_COOKIE}=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0`),p.redirect("/login")}}return o()}p.redirect("/login")}const _require=(0,external_module_namespaceObject.createRequire)(import.meta.url);function pkgDirSafe(_){try{return(0,external_path_.dirname)(_require.resolve(`${_}/package.json`))}catch{return null}}const libDir=(0,external_path_.join)(server_dirname,"lib"),staticOpts={maxAge:864e5};app.use("/lib/xterm",express.static(pkgDirSafe("xterm")||(0,external_path_.join)(libDir,"xterm"),staticOpts)),app.use("/lib/xterm-fit",express.static(pkgDirSafe("xterm-addon-fit")||(0,external_path_.join)(libDir,"xterm-fit"),staticOpts)),app.use("/lib/xterm-links",express.static(pkgDirSafe("xterm-addon-web-links")||(0,external_path_.join)(libDir,"xterm-links"),staticOpts));const _loginAttempts=new Map,LOGIN_MAX_ATTEMPTS=5,LOGIN_LOCKOUT_MS=300*1e3;function checkLoginRate(_){const p=Date.now(),o=_loginAttempts.get(_);return!o||p>o.resetAt?!0:o.count<LOGIN_MAX_ATTEMPTS}function recordLoginFailure(_){const p=Date.now(),o=_loginAttempts.get(_);!o||p>o.resetAt?_loginAttempts.set(_,{count:1,resetAt:p+LOGIN_LOCKOUT_MS}):o.count++}function clearLoginFailures(_){_loginAttempts.delete(_)}setInterval(()=>{const _=Date.now();for(const[p,o]of _loginAttempts)_>o.resetAt&&_loginAttempts.delete(p)},600*1e3),app.get("/login",(_,p)=>{p.sendFile(__nccwpck_require__.ab+"login.html")}),app.post("/login",express.urlencoded({extended:!1}),(_,p)=>{const o=_.ip;if(!checkLoginRate(o))return p.redirect("/login?error=locked");loadRoles(),loadUsers();const g=_.body?.password,m=_.query.from,h=m&&m.startsWith("/")&&!m.startsWith("//")?m:"/";if(hasUsers()){const d=lookupUser(g);if(!d)return recordLoginFailure(o),p.redirect("/login?error=1");if(isExclusiveMode()&&activeUser&&activeUser.name!==d.name)return p.redirect("/login?error=occupied&user="+encodeURIComponent(activeUser.name));clearLoginFailures(o);const a=(0,external_crypto_.randomBytes)(16).toString("hex");return userSessions.set(a,{role:"operator",name:d.name,dir:d.dir,lastUsed:Date.now()}),isExclusiveMode()&&(activeUser?activeUser.tokens.add(a):activeUser={name:d.name,dir:d.dir,tokens:new Set([a]),loginAt:Date.now()}),setSessionCookie(p,a),console.log(`[\u767B\u5F55] ${d.name} \u767B\u5F55\uFF08${isExclusiveMode()?"\u72EC\u5360":"\u5171\u4EAB"}\u6A21\u5F0F\uFF09`),p.redirect(h)}const l=lookupPassword(g);if(l){clearLoginFailures(o);const d=(0,external_crypto_.randomBytes)(16).toString("hex");return userSessions.set(d,{role:l.role,name:l.name,lastUsed:Date.now()}),setSessionCookie(p,d),p.redirect(h)}if(g===PASSWORD){clearLoginFailures(o);const d=(0,external_crypto_.randomBytes)(16).toString("hex");return userSessions.set(d,{role:"admin",name:"\u7BA1\u7406\u5458",lastUsed:Date.now()}),setSessionCookie(p,d),p.redirect(h)}recordLoginFailure(o),p.redirect("/login?error=1")}),app.post("/logout",(_,p)=>{const o=getSessionToken(_.headers.cookie);o&&(activeUser&&activeUser.tokens.has(o)&&(activeUser.tokens.delete(o),activeUser.tokens.size===0?(console.log(`[\u9000\u51FA] ${activeUser.name} \u5168\u90E8\u8BBE\u5907\u9000\u51FA\uFF0C\u91CA\u653E\u767B\u5F55\u72B6\u6001\uFF08\u4F1A\u8BDD\u4FDD\u7559\uFF09`),activeUser=null):console.log(`[\u9000\u51FA] ${activeUser.name} \u4E00\u4E2A\u8BBE\u5907\u9000\u51FA\uFF08\u5269\u4F59 ${activeUser.tokens.size} \u4E2A\uFF09`)),userSessions.delete(o)),p.setHeader("Set-Cookie",`${SESSION_COOKIE}=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0`),p.redirect("/login")}),app.get("/api/status",(_,p)=>{if(isExclusiveMode()&&activeUser)return p.json({occupied:!0,userName:activeUser.name,releasing:!!_exclusiveReleaseTimer});if(hasUsers()&&!isExclusiveMode()){const o=new Set;for(const[,g]of io.sockets.sockets){if(!g.data.userName||g.data.userName==="\u7BA1\u7406\u5458")continue;const m=g.handshake.headers.cookie;m&&!hasValidSession(m)||o.add(g.data.userName)}return p.json({occupied:!1,onlineUsers:[...o]})}p.json({occupied:!1})}),app.get("/admin",(_,p)=>p.sendFile(__nccwpck_require__.ab+"admin.html")),app.get("/",checkAuth,(_,p)=>p.sendFile(__nccwpck_require__.ab+"index.html")),app.get("/terminal/:id",checkAuth,(_,p)=>p.sendFile(__nccwpck_require__.ab+"chat.html")),app.get("/terminal-raw/:id",checkAuth,(_,p)=>p.sendFile(__nccwpck_require__.ab+"terminal.html")),app.use("/lib",express.static(__nccwpck_require__.ab+"lib",staticOpts)),app.get("/api/sessions",checkAuth,(_,p)=>{const o=getUserInfo(_.headers.cookie);p.json(manager.list(o?.name,o?.role==="admin"))}),app.get("/api/usage",(_,p)=>{try{const o=manager.listSessions().filter(m=>m.mode==="agent"&&m.alive).map(m=>({id:m.id,title:m.title,owner:m.owner,usage:m._usage}));let g=null;for(const m of o)if(m.usage?.rateLimitInfo){g=m.usage.rateLimitInfo;break}p.json({rateLimit:g,activeSessions:o})}catch(o){p.status(500).json({error:o.message})}}),app.post("/api/sessions",checkAuth,express.json(),(_,p)=>{const o=getUserInfo(_.headers.cookie),g=manager.create({..._.body,owner:o?.name,userDir:o?.dir});p.status(201).json(g.toJSON())}),app.delete("/api/sessions/:id",checkAuth,(_,p)=>{manager.kill(_.params.id),p.status(204).end()}),app.get("/api/claude-sessions",checkAuth,(_,p)=>{const o=getUserInfo(_.headers.cookie);p.json(listLocalClaudeSessions(o?.dir))}),app.get("/api/dirs",checkAuth,(_,p)=>{const o=getUserInfo(_.headers.cookie);if(!o||o.role!=="admin")return p.status(403).json({error:"\u6CA1\u6709\u76EE\u5F55\u6D4F\u89C8\u6743\u9650"});const g=o.dir?(0,external_path_.resolve)((0,external_path_.normalize)(o.dir)):null,m=process.platform==="win32"?"\\":"/";let h=_.query.path||g||process.env.MYHI_CWD||(0,external_os_.homedir)(),l=(0,external_path_.resolve)((0,external_path_.normalize)(h)).replace(/[/\\]+$/,"");process.platform==="win32"&&/^[A-Za-z]:$/.test(l)&&(l+="\\"),g&&!l.startsWith(g)&&(l=g);try{const d=(0,external_fs_.readdirSync)(l,{withFileTypes:!0}).filter(r=>r.isDirectory()&&!r.name.startsWith(".")).map(r=>({name:r.name,path:l.replace(/[/\\]+$/,"")+m+r.name})).sort((r,t)=>r.name.localeCompare(t.name)),a=(0,external_path_.join)(l,".."),n=!g||(0,external_path_.resolve)((0,external_path_.normalize)(a)).startsWith(g);p.json({current:l,parent:n&&a!==l?a:null,dirs:d})}catch(d){p.status(400).json({error:d.message})}}),app.get("/api/me",checkAuth,(_,p)=>{const o=getUserInfo(_.headers.cookie);p.json({name:o?.name||"\u7528\u6237",exclusive:isExclusiveMode(),hasUsers:hasUsers(),dir:o?.dir||null})});function getPkgVersion(){for(const _ of["../package.json","./package.json","package.json"])try{const p=JSON.parse((0,external_fs_.readFileSync)((0,external_path_.join)(server_dirname,_),"utf8")).version;if(p&&p!=="0.0.0")return p}catch{}return"0.0.0"}const PKG_VERSION=getPkgVersion();let _latestVersion=null,_lastVersionCheck=0;const VERSION_CHECK_INTERVAL=600*1e3;async function checkLatestVersion(){const _=Date.now();if(_latestVersion&&_-_lastVersionCheck<VERSION_CHECK_INTERVAL)return _latestVersion;try{const p=await fetch("https://registry.npmjs.org/@wendongfly/myhi/latest",{headers:{Accept:"application/json"},signal:AbortSignal.timeout(8e3)});p.ok&&(_latestVersion=(await p.json()).version,_lastVersionCheck=_)}catch{}return _latestVersion}app.get("/api/version",async(_,p)=>{const o=getPkgVersion(),g=await checkLatestVersion();p.json({current:o,latest:g||o,updateAvailable:g&&g!==o})}),app.post("/api/git/push",checkAuth,express.json(),async(_,p)=>{const{sessionId:o,url:g,username:m,password:h,message:l,branch:d}=_.body||{};if(!o||!l)return p.status(400).json({ok:!1,error:"\u7F3A\u5C11\u53C2\u6570"});const a=manager.get(o);if(!a)return p.status(404).json({ok:!1,error:"\u4F1A\u8BDD\u4E0D\u5B58\u5728"});const n=a.cwd||process.cwd(),r=process.platform==="win32",t="git";function e(c,s={}){return new Promise((i,f)=>{const u={...process.env,GIT_TERMINAL_PROMPT:"0"},v=(0,external_path_.join)(n,".gitconfig");(0,external_fs_.existsSync)(v)&&(u.GIT_CONFIG_GLOBAL=v);const b=(0,external_child_process_namespaceObject.spawn)(t,c,{cwd:n,env:u,timeout:6e4});let w="",E="";b.stdout.on("data",A=>w+=A),b.stderr.on("data",A=>E+=A),b.on("close",A=>A===0?i(w.trim()):f(new Error(E.trim()||`exit ${A}`))),b.on("error",f)})}try{try{await e(["rev-parse","--git-dir"])}catch{await e(["init"])}if(g&&m&&h){const f=new URL(g);f.username=encodeURIComponent(m),f.password=encodeURIComponent(h);const u=f.toString(),v=(0,external_path_.join)(n,".git-credentials");(0,external_fs_.writeFileSync)(v,u+`
363
363
  `,{mode:384});const b=(0,external_path_.join)(n,".gitconfig"),E=`[credential]
364
364
  helper = store --file ${v.replace(/\\/g,"/")}
365
365
  [user]
366
366
  name = ${m}
367
367
  email = ${m}@myhi
368
- `;(0,external_fs_.writeFileSync)(b,E,{mode:384});try{await e(["remote","remove","origin"])}catch{}await e(["remote","add","origin",g])}if(await e(["add","-A"]),!await e(["status","--porcelain"]))return p.json({ok:!0,message:"\u6CA1\u6709\u9700\u8981\u63D0\u4EA4\u7684\u53D8\u66F4",pushed:!1});await e(["commit","-m",l]);const s=d||"main";try{await e(["push","-u","origin",s])}catch(f){if(f.message.includes("src refspec")||f.message.includes("does not match"))await e(["checkout","-b",s]),await e(["push","-u","origin",s]);else throw f}const i=await e(["log","--oneline","-1"]);p.json({ok:!0,message:`\u5DF2\u63D0\u4EA4\u5E76\u63A8\u9001: ${i}`,pushed:!0})}catch(c){p.json({ok:!1,error:c.message})}}),app.get("/api/git/info",checkAuth,(_,p)=>{const o=_.query.sessionId,g=o?manager.get(o):null;if(!g)return p.json({});const m=g.cwd||process.cwd();try{const h=(0,external_child_process_namespaceObject.execSync)("git remote get-url origin",{cwd:m,encoding:"utf8",timeout:5e3}).trim(),l=(0,external_child_process_namespaceObject.execSync)("git branch --show-current",{cwd:m,encoding:"utf8",timeout:5e3}).trim();let d="";const a=(0,external_path_.join)(m,".gitconfig");if((0,external_fs_.existsSync)(a)){const r=(0,external_fs_.readFileSync)(a,"utf8").match(/name\s*=\s*(.+)/);r&&(d=r[1].trim())}p.json({url:h,branch:l||"main",username:d})}catch{p.json({})}});const _adminTokens=new Set,SERVER_START_TIME=Date.now();function checkAdminAuth(_,p,o){const g=_.headers.authorization;if(g&&g.startsWith("Bearer ")&&_adminTokens.has(g.slice(7)))return o();p.status(401).json({error:"\u672A\u6388\u6743"})}app.post("/api/admin/login",express.json(),(_,p)=>{const{password:o}=_.body||{};if(o===PASSWORD){const g=(0,external_crypto_.randomBytes)(16).toString("hex");return _adminTokens.add(g),p.json({ok:!0,token:g})}p.json({ok:!1,error:"\u5BC6\u7801\u9519\u8BEF"})}),app.get("/api/admin/status",checkAdminAuth,async(_,p)=>{const o=await checkLatestVersion();loadUsers();const g=[],m=new Set;for(const[,l]of io.sockets.sockets)!l.data.userName||m.has(l.data.userName)||(m.add(l.data.userName),g.push({name:l.data.userName,role:l.data.role||"viewer"}));const h=manager.listSessions().filter(l=>l.alive!==!1).map(l=>({id:l.id,title:l.title,mode:l.mode||"pty",owner:l.owner,viewers:l.viewerCount||0}));p.json({version:{current:getPkgVersion(),latest:o||getPkgVersion(),updateAvailable:o&&o!==getPkgVersion()},onlineUsers:g,sessions:h,users:listUsers(),uptime:Math.floor((Date.now()-SERVER_START_TIME)/1e3)})}),app.post("/api/admin/upgrade",checkAdminAuth,(_,p)=>{process.send?(process.send({type:"upgrade"}),p.json({ok:!0,message:"\u5347\u7EA7\u4E2D\uFF0C\u670D\u52A1\u5C06\u81EA\u52A8\u91CD\u542F..."})):(p.json({ok:!0,message:"\u5347\u7EA7\u4E2D..."}),setTimeout(()=>{try{const o=process.platform==="win32"?"npm.cmd install -g @wendongfly/myhi@latest":"sudo npm install -g @wendongfly/myhi@latest 2>&1 || npm install -g @wendongfly/myhi@latest";(0,external_child_process_namespaceObject.execSync)(o,{encoding:"utf8",timeout:12e4}),process.exit(0)}catch(o){console.error("[myhi] \u5347\u7EA7\u5931\u8D25:",o.message)}},500))}),app.post("/api/admin/restart",checkAdminAuth,(_,p)=>{p.json({ok:!0}),setTimeout(()=>{process.send?process.send({type:"restart"}):process.exit(0)},300)}),app.post("/api/admin/user",checkAdminAuth,express.json(),(_,p)=>{const{password:o,name:g,dir:m}=_.body||{};if(!o||!g||!m)return p.json({ok:!1,error:"\u5BC6\u7801\u3001\u540D\u79F0\u3001\u76EE\u5F55\u90FD\u5FC5\u586B"});loadUsers(),addUser(o,g,m);try{(0,external_fs_.mkdirSync)(m,{recursive:!0});const h=(0,external_path_.join)(m,".gitconfig"),l=(0,external_path_.join)(m,".git-credentials");(0,external_fs_.existsSync)(h)||(0,external_fs_.writeFileSync)(h,`[credential]
368
+ `;(0,external_fs_.writeFileSync)(b,E,{mode:384});try{await e(["remote","remove","origin"])}catch{}await e(["remote","add","origin",g])}if(await e(["add","-A"]),!await e(["status","--porcelain"]))return p.json({ok:!0,message:"\u6CA1\u6709\u9700\u8981\u63D0\u4EA4\u7684\u53D8\u66F4",pushed:!1});await e(["commit","-m",l]);const s=d||"main";try{await e(["push","-u","origin",s])}catch(f){if(f.message.includes("src refspec")||f.message.includes("does not match"))await e(["checkout","-b",s]),await e(["push","-u","origin",s]);else throw f}const i=await e(["log","--oneline","-1"]);p.json({ok:!0,message:`\u5DF2\u63D0\u4EA4\u5E76\u63A8\u9001: ${i}`,pushed:!0})}catch(c){p.json({ok:!1,error:c.message})}}),app.get("/api/git/info",checkAuth,(_,p)=>{const o=_.query.sessionId,g=o?manager.get(o):null;if(!g)return p.json({});const m=g.cwd||process.cwd();try{const h=(0,external_child_process_namespaceObject.execSync)("git remote get-url origin",{cwd:m,encoding:"utf8",timeout:5e3}).trim(),l=(0,external_child_process_namespaceObject.execSync)("git branch --show-current",{cwd:m,encoding:"utf8",timeout:5e3}).trim();let d="";const a=(0,external_path_.join)(m,".gitconfig");if((0,external_fs_.existsSync)(a)){const r=(0,external_fs_.readFileSync)(a,"utf8").match(/name\s*=\s*(.+)/);r&&(d=r[1].trim())}p.json({url:h,branch:l||"main",username:d})}catch{p.json({})}}),app.get("/api/memory",checkAuth,(_,p)=>{const o=_.query.sessionId,g=o?manager.get(o):null;if(!g)return p.json({files:[]});const m=g.cwd||process.cwd(),h=[],l=(0,external_path_.join)(m,"CLAUDE.md");if((0,external_fs_.existsSync)(l))try{h.push({name:"CLAUDE.md",content:(0,external_fs_.readFileSync)(l,"utf8")})}catch{}const d=(0,external_path_.join)(m,".claude","settings.json");if((0,external_fs_.existsSync)(d))try{h.push({name:".claude/settings.json",content:(0,external_fs_.readFileSync)(d,"utf8")})}catch{}const a=(0,external_path_.join)((0,external_os_.homedir)(),".claude","CLAUDE.md");if((0,external_fs_.existsSync)(a))try{h.push({name:"~/.claude/CLAUDE.md",content:(0,external_fs_.readFileSync)(a,"utf8")})}catch{}const n=[(0,external_path_.join)((0,external_os_.homedir)(),".claude","memory")],r=(0,external_path_.join)((0,external_os_.homedir)(),".claude","projects");if((0,external_fs_.existsSync)(r))try{for(const t of(0,external_fs_.readdirSync)(r)){const e=(0,external_path_.join)(r,t,"memory");if((0,external_fs_.existsSync)(e)){const c=(0,external_path_.join)(r,t,"CLAUDE.md");if((0,external_fs_.existsSync)(c))try{h.push({name:`.claude/projects/${t}/CLAUDE.md`,content:(0,external_fs_.readFileSync)(c,"utf8")})}catch{}try{for(const s of(0,external_fs_.readdirSync)(e)){if(!s.endsWith(".md"))continue;const i=(0,external_path_.join)(e,s);try{h.push({name:`memory/${s}`,content:(0,external_fs_.readFileSync)(i,"utf8")})}catch{}}}catch{}}}}catch{}for(const t of n)if((0,external_fs_.existsSync)(t))try{for(const e of(0,external_fs_.readdirSync)(t)){if(!e.endsWith(".md"))continue;const c=(0,external_path_.join)(t,e);try{h.push({name:`~/.claude/memory/${e}`,content:(0,external_fs_.readFileSync)(c,"utf8")})}catch{}}}catch{}p.json({files:h})});const _adminTokens=new Set,SERVER_START_TIME=Date.now();function checkAdminAuth(_,p,o){const g=_.headers.authorization;if(g&&g.startsWith("Bearer ")&&_adminTokens.has(g.slice(7)))return o();p.status(401).json({error:"\u672A\u6388\u6743"})}app.post("/api/admin/login",express.json(),(_,p)=>{const{password:o}=_.body||{};if(o===PASSWORD){const g=(0,external_crypto_.randomBytes)(16).toString("hex");return _adminTokens.add(g),p.json({ok:!0,token:g})}p.json({ok:!1,error:"\u5BC6\u7801\u9519\u8BEF"})}),app.get("/api/admin/status",checkAdminAuth,async(_,p)=>{const o=await checkLatestVersion();loadUsers();const g=[],m=new Set;for(const[,l]of io.sockets.sockets)!l.data.userName||m.has(l.data.userName)||(m.add(l.data.userName),g.push({name:l.data.userName,role:l.data.role||"viewer"}));const h=manager.listSessions().filter(l=>l.alive!==!1).map(l=>({id:l.id,title:l.title,mode:l.mode||"pty",owner:l.owner,viewers:l.viewerCount||0}));p.json({version:{current:getPkgVersion(),latest:o||getPkgVersion(),updateAvailable:o&&o!==getPkgVersion()},onlineUsers:g,sessions:h,users:listUsers(),uptime:Math.floor((Date.now()-SERVER_START_TIME)/1e3)})}),app.post("/api/admin/upgrade",checkAdminAuth,(_,p)=>{process.send?(process.send({type:"upgrade"}),p.json({ok:!0,message:"\u5347\u7EA7\u4E2D\uFF0C\u670D\u52A1\u5C06\u81EA\u52A8\u91CD\u542F..."})):(p.json({ok:!0,message:"\u5347\u7EA7\u4E2D..."}),setTimeout(()=>{try{const o=process.platform==="win32"?"npm.cmd install -g @wendongfly/myhi@latest":"sudo npm install -g @wendongfly/myhi@latest 2>&1 || npm install -g @wendongfly/myhi@latest";(0,external_child_process_namespaceObject.execSync)(o,{encoding:"utf8",timeout:12e4}),process.exit(0)}catch(o){console.error("[myhi] \u5347\u7EA7\u5931\u8D25:",o.message)}},500))}),app.post("/api/admin/restart",checkAdminAuth,(_,p)=>{p.json({ok:!0}),setTimeout(()=>{process.send?process.send({type:"restart"}):process.exit(0)},300)}),app.post("/api/admin/user",checkAdminAuth,express.json(),(_,p)=>{const{password:o,name:g,dir:m}=_.body||{};if(!o||!g||!m)return p.json({ok:!1,error:"\u5BC6\u7801\u3001\u540D\u79F0\u3001\u76EE\u5F55\u90FD\u5FC5\u586B"});loadUsers(),addUser(o,g,m);try{(0,external_fs_.mkdirSync)(m,{recursive:!0});const h=(0,external_path_.join)(m,".gitconfig"),l=(0,external_path_.join)(m,".git-credentials");(0,external_fs_.existsSync)(h)||(0,external_fs_.writeFileSync)(h,`[credential]
369
369
  helper = store --file ${l.replace(/\\/g,"/")}
370
370
  [user]
371
371
  name = ${g}
372
- `,{mode:384})}catch{}console.log(`[\u7BA1\u7406] \u6DFB\u52A0\u7528\u6237 ${g} (\u76EE\u5F55: ${m})`),p.json({ok:!0})}),app.delete("/api/admin/user",checkAdminAuth,express.json(),(_,p)=>{const{name:o}=_.body||{};if(!o)return p.json({ok:!1,error:"\u8BF7\u6307\u5B9A\u7528\u6237\u540D\u79F0"});if(loadUsers(),!listUsers().find(h=>h.name===o))return p.json({ok:!1,error:"\u7528\u6237\u4E0D\u5B58\u5728"});try{const h=(0,external_path_.join)(configDir,"users.json"),l=JSON.parse((0,external_fs_.readFileSync)(h,"utf8"));for(const[d,a]of Object.entries(l.users||{}))if(a.name===o){delete l.users[d];break}(0,external_fs_.writeFileSync)(h,JSON.stringify(l,null,2),{mode:384}),loadUsers(),console.log(`[\u7BA1\u7406] \u5220\u9664\u7528\u6237 ${o}`),p.json({ok:!0})}catch(h){p.json({ok:!1,error:h.message})}}),app.post("/api/admin/user/password",checkAdminAuth,express.json(),(_,p)=>{const{name:o,password:g}=_.body||{};if(!o||!g)return p.json({ok:!1,error:"\u540D\u79F0\u548C\u65B0\u5BC6\u7801\u5FC5\u586B"});try{const m=(0,external_path_.join)(configDir,"users.json"),h=JSON.parse((0,external_fs_.readFileSync)(m,"utf8"));let l=null;for(const[d,a]of Object.entries(h.users||{}))if(a.name===o){l=a,delete h.users[d];break}if(!l)return p.json({ok:!1,error:"\u7528\u6237\u4E0D\u5B58\u5728"});h.users[g]=l,(0,external_fs_.writeFileSync)(m,JSON.stringify(h,null,2),{mode:384}),loadUsers(),console.log(`[\u7BA1\u7406] \u4FEE\u6539\u7528\u6237 ${o} \u7684\u5BC6\u7801`),p.json({ok:!0})}catch(m){p.json({ok:!1,error:m.message})}}),app.post("/api/admin/password",checkAdminAuth,express.json(),(_,p)=>{const{password:o}=_.body||{};if(!o||o.length<4)return p.json({ok:!1,error:"\u5BC6\u7801\u81F3\u5C114\u4F4D"});try{(0,external_fs_.writeFileSync)((0,external_path_.join)(configDir,"password"),o,{mode:384}),PASSWORD=o,p.json({ok:!0}),console.log("[\u7BA1\u7406] \u7BA1\u7406\u5BC6\u7801\u5DF2\u4FEE\u6539")}catch(g){p.json({ok:!1,error:g.message})}}),app.post("/upload",checkAuth,(_,p)=>{const o=_.query.sessionId,g=o?manager.get(o):null,m=g?.cwd?(0,external_path_.join)(g.cwd,"upload"):uploadDir;(0,external_fs_.mkdirSync)(m,{recursive:!0}),multer({storage:multer.diskStorage({destination:m,filename:(l,d,a)=>{const n=d.originalname.match(/\.[^.]+$/)?.[0]||".jpg";a(null,`${Date.now()}-${(0,external_crypto_.randomBytes)(3).toString("hex")}${n}`)}}),limits:{fileSize:20*1024*1024},fileFilter:(l,d,a)=>a(null,d.mimetype.startsWith("image/"))}).single("image")(_,p,l=>{if(l)return p.status(400).json({error:l.message});if(!_.file)return p.status(400).json({error:"\u6CA1\u6709\u56FE\u7247"});p.json({path:(0,external_path_.resolve)(_.file.path).replace(/\\/g,"/")})})}),app.get("/qr/:sessionId",checkAuth,async(_,p)=>{const o=getTailscaleIP(),g=(0,external_crypto_.randomBytes)(16).toString("hex");userSessions.set(g,{role:"admin",name:"\u626B\u7801\u7528\u6237",onetime:!0,lastUsed:Date.now()});const m=`http://${o}:${PORT}/terminal/${_.params.sessionId}?token=${g}`;try{const h=await lib.toString(m,{type:"svg"});p.setHeader("Content-Type","image/svg+xml"),p.send(h)}catch(h){p.status(500).send(h.message)}}),io.use((_,p)=>{const o=_.handshake.headers.cookie;if(hasValidSession(o)){const h=getUserInfo(o);return _.data.role=h?.role||"admin",_.data.userName=h?.name||"\u7528\u6237",_.data.dir=h?.dir||null,p()}const g=_.handshake.auth?.password;if(g){loadUsers();const h=lookupUser(g);if(h)return _.data.role="operator",_.data.userName=h.name,_.data.dir=h.dir,p()}if((_.handshake.auth?.token||_.handshake.query?.token)===TOKEN)return _.data.role="admin",_.data.userName="\u7BA1\u7406\u5458",p();p(new Error("\u672A\u6388\u6743"))});const EXCLUSIVE_RELEASE_DELAY=120*1e3;let _exclusiveReleaseTimer=null;function checkExclusiveRelease(){if(!isExclusiveMode()||!activeUser)return;let _=!1;for(const[,p]of io.sockets.sockets)if(p.data.userName===activeUser.name){_=!0;break}if(_){_exclusiveReleaseTimer&&(clearTimeout(_exclusiveReleaseTimer),_exclusiveReleaseTimer=null);return}_exclusiveReleaseTimer||(_exclusiveReleaseTimer=setTimeout(()=>{if(_exclusiveReleaseTimer=null,!!activeUser){for(const[,p]of io.sockets.sockets)if(p.data.userName===activeUser.name)return;console.log(`[\u72EC\u5360] ${activeUser.name} \u5DF2\u79BB\u7EBF\u8D85\u65F6\uFF0C\u91CA\u653E\u767B\u5F55\u72B6\u6001\uFF08\u4F1A\u8BDD\u4FDD\u7559\uFF09`);for(const p of activeUser.tokens)userSessions.delete(p);activeUser=null}},EXCLUSIVE_RELEASE_DELAY))}let _broadcastTimer=null;function broadcastSessions(){_broadcastTimer||(_broadcastTimer=setTimeout(()=>{_broadcastTimer=null;for(const[,_]of io.sockets.sockets)_.emit("sessions",manager.list(_.data.userName,_.data.role==="admin"))},100))}io.on("connection",_=>{const p=_.handshake.address?.replace("::ffff:","")||"?";console.log(`[\u8FDE\u63A5] ${_.data.userName}(${_.data.role}) \u4ECE ${p} \u63A5\u5165 id=${_.id}`),_exclusiveReleaseTimer&&activeUser&&_.data.userName===activeUser.name&&(clearTimeout(_exclusiveReleaseTimer),_exclusiveReleaseTimer=null);let o=null,g=null,m=null,h=null,l=null;function d(){o&&(o.isController(_.id)&&(o.releaseControl(),io.emit("control-changed",{sessionId:o.id,holder:null,holderName:null})),o.removeViewer(_.id),g&&o.off("data",g),m&&o.off("agent:message",m),h&&o.off("agent:busy",h),l&&o.off("agent:error",l),g=null,m=null,h=null,l=null)}_.on("join",a=>{const n=manager.get(a);if(!n){_.emit("error",{message:`\u4F1A\u8BDD ${a} \u672A\u627E\u5230`});return}if(d(),o=n,o.addViewer(_.id,_.data.userName),n.mode==="agent"?(m=r=>_.emit("agent:message",r),o.on("agent:message",m),h=r=>_.emit("agent:busy",r),o.on("agent:busy",h),l=r=>_.emit("agent:error",r),o.on("agent:error",l)):(g=r=>_.emit("output",r),o.on("data",g),o.once("exit",r=>{_.emit("session-exit",{code:r}),g&&o?.off("data",g)}),spawnLocalTerminal(a,n.title)),console.log(`[\u52A0\u5165] ${_.data.userName} \u52A0\u5165\u4F1A\u8BDD "${n.title}" (${a}) \u6A21\u5F0F=${n.mode||"pty"}`),_.emit("joined",{...n.toJSON(),role:_.data.role}),n.mode==="agent")n._history?.length&&_.emit("agent:history",n._history);else if(n._scrollback?.length){const t=n._scrollback;if(t.length<=4096)_.emit("output",t);else{let e=0;(function c(){e>=t.length||(_.emit("output",t.slice(e,e+4096)),e+=4096,setImmediate(c))})()}}broadcastSessions()}),_.on("disconnect",()=>{console.log(`[\u65AD\u5F00] ${_.data.userName} \u79BB\u5F00\u4F1A\u8BDD "${o?.title||"?"}" id=${_.id}`),d(),broadcastSessions(),checkExclusiveRelease()}),_.on("agent:query",async({prompt:a}={})=>{if(!o||o.mode!=="agent"){_.emit("agent:error",{message:"\u5F53\u524D\u4E0D\u662F Agent \u6A21\u5F0F\u4F1A\u8BDD"});return}if(!o.isController(_.id)){_.emit("control-denied",{reason:"\u4F60\u6CA1\u6709\u5F53\u524D\u4F1A\u8BDD\u7684\u63A7\u5236\u6743"});return}if(o.isBusy){_.emit("agent:error",{message:"\u6B63\u5728\u5904\u7406\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210"});return}try{await o.query(a)}catch(n){console.error("[agent:query] \u9519\u8BEF:",n.message);try{_.connected&&_.emit("agent:error",{message:n.message})}catch{}}}),_.on("agent:interrupt",()=>{!o||o.mode!=="agent"||o.interrupt()}),_.on("agent:permission",({requestId:a,allow:n}={})=>{if(!(!o||o.mode!=="agent")){if(!o.isController(_.id)){_.emit("control-denied",{reason:"\u4F60\u6CA1\u6709\u5F53\u524D\u4F1A\u8BDD\u7684\u63A7\u5236\u6743"});return}o.respondPermission(a,n)}}),_.on("create-agent",(a,n)=>{if(!hasPermission(_.data.role,"operator")){typeof n=="function"&&n({ok:!1,error:"\u6CA1\u6709\u521B\u5EFA\u4F1A\u8BDD\u7684\u6743\u9650"});return}if(_.data.dir){const t=(0,external_path_.resolve)((0,external_path_.normalize)(_.data.dir)),e=a?.cwd?(0,external_path_.resolve)((0,external_path_.normalize)(a.cwd)):null;if(e&&e!==t&&!e.startsWith(t+external_path_.sep)){typeof n=="function"&&n({ok:!1,error:"\u4E0D\u80FD\u5728\u7ED1\u5B9A\u76EE\u5F55\u4E4B\u5916\u521B\u5EFA\u4F1A\u8BDD"});return}e||(a={...a,cwd:t})}const r=a?.cwd||process.env.MYHI_CWD||process.cwd();if(!(0,external_fs_.existsSync)(r))try{(0,external_fs_.mkdirSync)(r,{recursive:!0}),console.log(`[\u521B\u5EFA] \u81EA\u52A8\u521B\u5EFA\u76EE\u5F55: ${r}`)}catch(t){typeof n=="function"&&n({ok:!1,error:`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25: ${t.message}`});return}try{const t=manager.createAgent({...a,owner:_.data.userName,userDir:_.data.dir});console.log(`[\u4F1A\u8BDD] ${_.data.userName} \u521B\u5EFA Agent \u4F1A\u8BDD "${t.title}" (${t.id})`),broadcastSessions(),typeof n=="function"&&n({ok:!0,session:t.toJSON()})}catch(t){typeof n=="function"&&n({ok:!1,error:t.message})}}),_.on("input",a=>{if(!(a==null||!o)&&o.mode!=="agent"){if(!o.isController(_.id)){_.emit("control-denied",{reason:"\u4F60\u6CA1\u6709\u5F53\u524D\u4F1A\u8BDD\u7684\u63A7\u5236\u6743"});return}o.lastInputTime=Date.now(),o.write(a)}}),_.on("take-control",({sessionId:a}={})=>{const n=a?manager.get(a):o;if(n){if(!hasPermission(_.data.role,"operator")){_.emit("control-denied",{reason:"\u4F60\u7684\u89D2\u8272\u6CA1\u6709\u63A7\u5236\u6743\u9650"});return}if(n.controlHolder&&n.controlHolder!==_.id){const r=n.controlHolderName===_.data.userName;if(_.data.role!=="admin"&&!r){_.emit("control-denied",{reason:"\u5176\u4ED6\u7528\u6237\u6B63\u5728\u63A7\u5236\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u91CA\u653E"});return}}n.takeControl(_.id,_.data.userName),console.log(`[\u63A7\u5236] ${_.data.userName} \u83B7\u53D6\u4F1A\u8BDD "${n.title}" \u7684\u63A7\u5236\u6743`),io.emit("control-changed",{sessionId:n.id,holder:_.id,holderName:_.data.userName})}}),_.on("release-control",({sessionId:a}={})=>{const n=a?manager.get(a):o;n&&n.isController(_.id)&&(console.log(`[\u63A7\u5236] ${_.data.userName} \u91CA\u653E\u4F1A\u8BDD "${n.title}" \u7684\u63A7\u5236\u6743`),n.releaseControl(),io.emit("control-changed",{sessionId:n.id,holder:null,holderName:null}))}),_.on("set-mode",({sessionId:a,mode:n}={})=>{const r=a?manager.get(a):o;r&&(r.mode==="agent"&&r.setPermissionMode?(r.setPermissionMode(n),console.log(`[\u6A21\u5F0F] ${_.data.userName} \u5207\u6362\u4F1A\u8BDD "${r.title}" \u4E3A ${n}\uFF08Agent \u8FDB\u7A0B\u5C06\u91CD\u542F\uFF09`)):r.permissionMode=n,io.emit("mode-changed",{sessionId:r.id,mode:n}))}),_.on("rename",({sessionId:a,title:n}={})=>{const r=a?manager.get(a):o;!r||!n||(r.title=n,io.emit("session-renamed",{sessionId:r.id,title:n}),broadcastSessions())}),_.on("resize",({cols:a,rows:n})=>o?.resize(a,n)),_.on("list",()=>_.emit("sessions",manager.list(_.data.userName,_.data.role==="admin"))),_.on("dirs",(a,n)=>{if(typeof n!="function")return;const r=_.data.dir?(0,external_path_.resolve)((0,external_path_.normalize)(_.data.dir)):null,t=process.platform==="win32"?"\\":"/";let e=(a||r||process.env.MYHI_CWD||(0,external_os_.homedir)()).replace(/[/\\]+$/,"")||(0,external_os_.homedir)();process.platform==="win32"&&/^[A-Za-z]:$/.test(e)&&(e+="\\");const c=(0,external_path_.resolve)((0,external_path_.normalize)(e));r&&c!==r&&!c.startsWith(r+t)&&(e=r);try{const s=(0,external_fs_.readdirSync)(e,{withFileTypes:!0}).filter(v=>v.isDirectory()&&!v.name.startsWith(".")).map(v=>({name:v.name,path:e.replace(/[/\\]+$/,"")+t+v.name})).sort((v,b)=>v.name.localeCompare(b.name)),i=(0,external_path_.join)(e,".."),f=(0,external_path_.resolve)((0,external_path_.normalize)(i)),u=!r||f===r||f.startsWith(r+t);n({ok:!0,current:e,parent:u&&i!==e?i:null,dirs:s})}catch(s){n({ok:!1,error:s.message})}}),_.on("create",(a,n)=>{if(!hasPermission(_.data.role,"operator")){typeof n=="function"&&n({ok:!1,error:"\u6CA1\u6709\u521B\u5EFA\u4F1A\u8BDD\u7684\u6743\u9650"});return}if(_.data.dir){const t=(0,external_path_.resolve)((0,external_path_.normalize)(_.data.dir)),e=a?.cwd?(0,external_path_.resolve)((0,external_path_.normalize)(a.cwd)):null;if(e&&e!==t&&!e.startsWith(t+external_path_.sep)){typeof n=="function"&&n({ok:!1,error:"\u4E0D\u80FD\u5728\u7ED1\u5B9A\u76EE\u5F55\u4E4B\u5916\u521B\u5EFA\u4F1A\u8BDD"});return}e||(a={...a,cwd:t})}const r=a?.cwd||process.env.MYHI_CWD||process.cwd();if(!(0,external_fs_.existsSync)(r))try{(0,external_fs_.mkdirSync)(r,{recursive:!0}),console.log(`[\u521B\u5EFA] \u81EA\u52A8\u521B\u5EFA\u76EE\u5F55: ${r}`)}catch(t){typeof n=="function"&&n({ok:!1,error:`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25: ${t.message}`});return}try{const t=manager.create({...a,owner:_.data.userName,userDir:_.data.dir});console.log(`[\u4F1A\u8BDD] ${_.data.userName} \u521B\u5EFA PTY \u4F1A\u8BDD "${t.title}" (${t.id})`),broadcastSessions(),typeof n=="function"&&n({ok:!0,session:t.toJSON()})}catch(t){console.error("[\u521B\u5EFA] PTY \u542F\u52A8\u5931\u8D25:",t.message),typeof n=="function"&&n({ok:!1,error:t.message})}}),_.on("kill",a=>{if(!hasPermission(_.data.role,"operator")){_.emit("error",{message:"\u6CA1\u6709\u5220\u9664\u4F1A\u8BDD\u7684\u6743\u9650"});return}console.log(`[\u4F1A\u8BDD] ${_.data.userName} \u5220\u9664\u4F1A\u8BDD ${a}`),manager.kill(a),_autoSpawned.delete(a),broadcastSessions()}),_.on("kick-user",({socketId:a,sessionId:n}={},r)=>{if(!hasPermission(_.data.role,"admin")){typeof r=="function"&&r({ok:!1,error:"\u6CA1\u6709\u8E22\u51FA\u7528\u6237\u7684\u6743\u9650"});return}const t=io.sockets.sockets.get(a);if(!t){typeof r=="function"&&r({ok:!1,error:"\u76EE\u6807\u7528\u6237\u4E0D\u5728\u7EBF"});return}const e=t.data.userName||"\u672A\u77E5";console.log(`[\u8E22\u51FA] ${_.data.userName} \u8E22\u51FA\u4E86\u7528\u6237 ${e} (${a})`),t.emit("kicked",{reason:`\u4F60\u5DF2\u88AB\u7BA1\u7406\u5458 ${_.data.userName} \u8E22\u51FA`}),t.disconnect(!0),typeof r=="function"&&r({ok:!0,name:e})}),_.on("list-viewers",({sessionId:a}={},n)=>{if(typeof n!="function")return;const r=a?manager.get(a):o;if(!r){n({ok:!1,error:"\u4F1A\u8BDD\u4E0D\u5B58\u5728"});return}const t=[];for(const e of r._viewers){const c=io.sockets.sockets.get(e);c&&t.push({socketId:e,userName:c.data.userName||"\u672A\u77E5",role:c.data.role||"viewer",isController:r.controlHolder===e})}n({ok:!0,viewers:t})})});const CONTROL_TIMEOUT=300*1e3;setInterval(()=>{for(const _ of manager.listSessions()){_.controlHolder&&(!io.sockets.sockets.get(_.controlHolder)||_.lastInputTime&&Date.now()-_.lastInputTime>CONTROL_TIMEOUT)&&(_.releaseControl(),io.emit("control-changed",{sessionId:_.id,holder:null,holderName:null}));for(const p of _._viewers)io.sockets.sockets.has(p)||_.removeViewer(p)}},60*1e3);function showStartupInfo(){const _=httpServer.address().port;PORT=_;const p=getTailscaleIP(),o=`http://${p}:${_}/admin`;console.log(`
372
+ `,{mode:384})}catch{}console.log(`[\u7BA1\u7406] \u6DFB\u52A0\u7528\u6237 ${g} (\u76EE\u5F55: ${m})`),p.json({ok:!0})}),app.delete("/api/admin/user",checkAdminAuth,express.json(),(_,p)=>{const{name:o}=_.body||{};if(!o)return p.json({ok:!1,error:"\u8BF7\u6307\u5B9A\u7528\u6237\u540D\u79F0"});if(loadUsers(),!listUsers().find(h=>h.name===o))return p.json({ok:!1,error:"\u7528\u6237\u4E0D\u5B58\u5728"});try{const h=(0,external_path_.join)(configDir,"users.json"),l=JSON.parse((0,external_fs_.readFileSync)(h,"utf8"));for(const[d,a]of Object.entries(l.users||{}))if(a.name===o){delete l.users[d];break}(0,external_fs_.writeFileSync)(h,JSON.stringify(l,null,2),{mode:384}),loadUsers(),console.log(`[\u7BA1\u7406] \u5220\u9664\u7528\u6237 ${o}`),p.json({ok:!0})}catch(h){p.json({ok:!1,error:h.message})}}),app.post("/api/admin/user/password",checkAdminAuth,express.json(),(_,p)=>{const{name:o,password:g}=_.body||{};if(!o||!g)return p.json({ok:!1,error:"\u540D\u79F0\u548C\u65B0\u5BC6\u7801\u5FC5\u586B"});try{const m=(0,external_path_.join)(configDir,"users.json"),h=JSON.parse((0,external_fs_.readFileSync)(m,"utf8"));let l=null;for(const[d,a]of Object.entries(h.users||{}))if(a.name===o){l=a,delete h.users[d];break}if(!l)return p.json({ok:!1,error:"\u7528\u6237\u4E0D\u5B58\u5728"});h.users[g]=l,(0,external_fs_.writeFileSync)(m,JSON.stringify(h,null,2),{mode:384}),loadUsers(),console.log(`[\u7BA1\u7406] \u4FEE\u6539\u7528\u6237 ${o} \u7684\u5BC6\u7801`),p.json({ok:!0})}catch(m){p.json({ok:!1,error:m.message})}}),app.post("/api/admin/password",checkAdminAuth,express.json(),(_,p)=>{const{password:o}=_.body||{};if(!o||o.length<4)return p.json({ok:!1,error:"\u5BC6\u7801\u81F3\u5C114\u4F4D"});try{(0,external_fs_.writeFileSync)((0,external_path_.join)(configDir,"password"),o,{mode:384}),PASSWORD=o,p.json({ok:!0}),console.log("[\u7BA1\u7406] \u7BA1\u7406\u5BC6\u7801\u5DF2\u4FEE\u6539")}catch(g){p.json({ok:!1,error:g.message})}}),app.post("/upload",checkAuth,(_,p)=>{const o=_.query.sessionId,g=o?manager.get(o):null,m=g?.cwd?(0,external_path_.join)(g.cwd,"upload"):uploadDir;(0,external_fs_.mkdirSync)(m,{recursive:!0}),multer({storage:multer.diskStorage({destination:m,filename:(l,d,a)=>{const n=d.originalname.match(/\.[^.]+$/)?.[0]||".jpg";a(null,`${Date.now()}-${(0,external_crypto_.randomBytes)(3).toString("hex")}${n}`)}}),limits:{fileSize:20*1024*1024},fileFilter:(l,d,a)=>a(null,d.mimetype.startsWith("image/"))}).single("image")(_,p,l=>{if(l)return p.status(400).json({error:l.message});if(!_.file)return p.status(400).json({error:"\u6CA1\u6709\u56FE\u7247"});p.json({path:(0,external_path_.resolve)(_.file.path).replace(/\\/g,"/")})})}),app.get("/qr/:sessionId",checkAuth,async(_,p)=>{const o=getTailscaleIP(),g=(0,external_crypto_.randomBytes)(16).toString("hex");userSessions.set(g,{role:"admin",name:"\u626B\u7801\u7528\u6237",onetime:!0,lastUsed:Date.now()});const m=`http://${o}:${PORT}/terminal/${_.params.sessionId}?token=${g}`;try{const h=await lib.toString(m,{type:"svg"});p.setHeader("Content-Type","image/svg+xml"),p.send(h)}catch(h){p.status(500).send(h.message)}}),io.use((_,p)=>{const o=_.handshake.headers.cookie;if(hasValidSession(o)){const h=getUserInfo(o);return _.data.role=h?.role||"admin",_.data.userName=h?.name||"\u7528\u6237",_.data.dir=h?.dir||null,p()}const g=_.handshake.auth?.password;if(g){loadUsers();const h=lookupUser(g);if(h)return _.data.role="operator",_.data.userName=h.name,_.data.dir=h.dir,p()}if((_.handshake.auth?.token||_.handshake.query?.token)===TOKEN)return _.data.role="admin",_.data.userName="\u7BA1\u7406\u5458",p();p(new Error("\u672A\u6388\u6743"))});const EXCLUSIVE_RELEASE_DELAY=120*1e3;let _exclusiveReleaseTimer=null;function checkExclusiveRelease(){if(!isExclusiveMode()||!activeUser)return;let _=!1;for(const[,p]of io.sockets.sockets)if(p.data.userName===activeUser.name){_=!0;break}if(_){_exclusiveReleaseTimer&&(clearTimeout(_exclusiveReleaseTimer),_exclusiveReleaseTimer=null);return}_exclusiveReleaseTimer||(_exclusiveReleaseTimer=setTimeout(()=>{if(_exclusiveReleaseTimer=null,!!activeUser){for(const[,p]of io.sockets.sockets)if(p.data.userName===activeUser.name)return;console.log(`[\u72EC\u5360] ${activeUser.name} \u5DF2\u79BB\u7EBF\u8D85\u65F6\uFF0C\u91CA\u653E\u767B\u5F55\u72B6\u6001\uFF08\u4F1A\u8BDD\u4FDD\u7559\uFF09`);for(const p of activeUser.tokens)userSessions.delete(p);activeUser=null}},EXCLUSIVE_RELEASE_DELAY))}let _broadcastTimer=null;function broadcastSessions(){_broadcastTimer||(_broadcastTimer=setTimeout(()=>{_broadcastTimer=null;for(const[,_]of io.sockets.sockets)_.emit("sessions",manager.list(_.data.userName,_.data.role==="admin"))},100))}io.on("connection",_=>{const p=_.handshake.address?.replace("::ffff:","")||"?";console.log(`[\u8FDE\u63A5] ${_.data.userName}(${_.data.role}) \u4ECE ${p} \u63A5\u5165 id=${_.id}`),_exclusiveReleaseTimer&&activeUser&&_.data.userName===activeUser.name&&(clearTimeout(_exclusiveReleaseTimer),_exclusiveReleaseTimer=null);let o=null,g=null,m=null,h=null,l=null;function d(){o&&(o.isController(_.id)&&(o.releaseControl(),io.emit("control-changed",{sessionId:o.id,holder:null,holderName:null})),o.removeViewer(_.id),g&&o.off("data",g),m&&o.off("agent:message",m),h&&o.off("agent:busy",h),l&&o.off("agent:error",l),g=null,m=null,h=null,l=null)}_.on("join",a=>{const n=manager.get(a);if(!n){_.emit("error",{message:`\u4F1A\u8BDD ${a} \u672A\u627E\u5230`});return}if(d(),o=n,o.addViewer(_.id,_.data.userName),n.mode==="agent"?(m=r=>_.emit("agent:message",r),o.on("agent:message",m),h=r=>_.emit("agent:busy",r),o.on("agent:busy",h),l=r=>_.emit("agent:error",r),o.on("agent:error",l)):(g=r=>_.emit("output",r),o.on("data",g),o.once("exit",r=>{_.emit("session-exit",{code:r}),g&&o?.off("data",g)}),spawnLocalTerminal(a,n.title)),console.log(`[\u52A0\u5165] ${_.data.userName} \u52A0\u5165\u4F1A\u8BDD "${n.title}" (${a}) \u6A21\u5F0F=${n.mode||"pty"}`),_.emit("joined",{...n.toJSON(),role:_.data.role}),n.mode==="agent")n._history?.length&&_.emit("agent:history",n._history);else if(n._scrollback?.length){const t=n._scrollback;if(t.length<=4096)_.emit("output",t);else{let e=0;(function c(){e>=t.length||(_.emit("output",t.slice(e,e+4096)),e+=4096,setImmediate(c))})()}}broadcastSessions()}),_.on("disconnect",()=>{console.log(`[\u65AD\u5F00] ${_.data.userName} \u79BB\u5F00\u4F1A\u8BDD "${o?.title||"?"}" id=${_.id}`),d(),broadcastSessions(),checkExclusiveRelease(),manager.persist()}),_.on("agent:query",async({prompt:a}={})=>{if(!o||o.mode!=="agent"){_.emit("agent:error",{message:"\u5F53\u524D\u4E0D\u662F Agent \u6A21\u5F0F\u4F1A\u8BDD"});return}if(!o.isController(_.id)){_.emit("control-denied",{reason:"\u4F60\u6CA1\u6709\u5F53\u524D\u4F1A\u8BDD\u7684\u63A7\u5236\u6743"});return}if(o.isBusy){_.emit("agent:error",{message:"\u6B63\u5728\u5904\u7406\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210"});return}try{await o.query(a)}catch(n){console.error("[agent:query] \u9519\u8BEF:",n.message);try{_.connected&&_.emit("agent:error",{message:n.message})}catch{}}}),_.on("agent:interrupt",()=>{!o||o.mode!=="agent"||o.interrupt()}),_.on("agent:permission",({requestId:a,allow:n}={})=>{if(!(!o||o.mode!=="agent")){if(!o.isController(_.id)){_.emit("control-denied",{reason:"\u4F60\u6CA1\u6709\u5F53\u524D\u4F1A\u8BDD\u7684\u63A7\u5236\u6743"});return}o.respondPermission(a,n)}}),_.on("create-agent",(a,n)=>{if(!hasPermission(_.data.role,"operator")){typeof n=="function"&&n({ok:!1,error:"\u6CA1\u6709\u521B\u5EFA\u4F1A\u8BDD\u7684\u6743\u9650"});return}if(_.data.dir){const t=(0,external_path_.resolve)((0,external_path_.normalize)(_.data.dir)),e=a?.cwd?(0,external_path_.resolve)((0,external_path_.normalize)(a.cwd)):null;if(e&&e!==t&&!e.startsWith(t+external_path_.sep)){typeof n=="function"&&n({ok:!1,error:"\u4E0D\u80FD\u5728\u7ED1\u5B9A\u76EE\u5F55\u4E4B\u5916\u521B\u5EFA\u4F1A\u8BDD"});return}e||(a={...a,cwd:t})}const r=a?.cwd||process.env.MYHI_CWD||process.cwd();if(!(0,external_fs_.existsSync)(r))try{(0,external_fs_.mkdirSync)(r,{recursive:!0}),console.log(`[\u521B\u5EFA] \u81EA\u52A8\u521B\u5EFA\u76EE\u5F55: ${r}`)}catch(t){typeof n=="function"&&n({ok:!1,error:`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25: ${t.message}`});return}try{const t=manager.createAgent({...a,owner:_.data.userName,userDir:_.data.dir});console.log(`[\u4F1A\u8BDD] ${_.data.userName} \u521B\u5EFA Agent \u4F1A\u8BDD "${t.title}" (${t.id})`),broadcastSessions(),typeof n=="function"&&n({ok:!0,session:t.toJSON()})}catch(t){typeof n=="function"&&n({ok:!1,error:t.message})}}),_.on("input",a=>{if(!(a==null||!o)&&o.mode!=="agent"){if(!o.isController(_.id)){_.emit("control-denied",{reason:"\u4F60\u6CA1\u6709\u5F53\u524D\u4F1A\u8BDD\u7684\u63A7\u5236\u6743"});return}o.lastInputTime=Date.now(),o.write(a)}}),_.on("take-control",({sessionId:a}={})=>{const n=a?manager.get(a):o;if(n){if(!hasPermission(_.data.role,"operator")){_.emit("control-denied",{reason:"\u4F60\u7684\u89D2\u8272\u6CA1\u6709\u63A7\u5236\u6743\u9650"});return}if(n.controlHolder&&n.controlHolder!==_.id){const r=n.controlHolderName===_.data.userName;if(_.data.role!=="admin"&&!r){_.emit("control-denied",{reason:"\u5176\u4ED6\u7528\u6237\u6B63\u5728\u63A7\u5236\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u91CA\u653E"});return}}n.takeControl(_.id,_.data.userName),console.log(`[\u63A7\u5236] ${_.data.userName} \u83B7\u53D6\u4F1A\u8BDD "${n.title}" \u7684\u63A7\u5236\u6743`),io.emit("control-changed",{sessionId:n.id,holder:_.id,holderName:_.data.userName})}}),_.on("release-control",({sessionId:a}={})=>{const n=a?manager.get(a):o;n&&n.isController(_.id)&&(console.log(`[\u63A7\u5236] ${_.data.userName} \u91CA\u653E\u4F1A\u8BDD "${n.title}" \u7684\u63A7\u5236\u6743`),n.releaseControl(),io.emit("control-changed",{sessionId:n.id,holder:null,holderName:null}))}),_.on("set-mode",({sessionId:a,mode:n}={})=>{const r=a?manager.get(a):o;r&&(r.mode==="agent"&&r.setPermissionMode?(r.setPermissionMode(n),console.log(`[\u6A21\u5F0F] ${_.data.userName} \u5207\u6362\u4F1A\u8BDD "${r.title}" \u4E3A ${n}\uFF08Agent \u8FDB\u7A0B\u5C06\u91CD\u542F\uFF09`)):r.permissionMode=n,io.emit("mode-changed",{sessionId:r.id,mode:n}),manager.persist())}),_.on("rename",({sessionId:a,title:n}={})=>{const r=a?manager.get(a):o;!r||!n||(r.title=n,io.emit("session-renamed",{sessionId:r.id,title:n}),broadcastSessions(),manager.persist())}),_.on("resize",({cols:a,rows:n})=>o?.resize(a,n)),_.on("list",()=>_.emit("sessions",manager.list(_.data.userName,_.data.role==="admin"))),_.on("dirs",(a,n)=>{if(typeof n!="function")return;const r=_.data.dir?(0,external_path_.resolve)((0,external_path_.normalize)(_.data.dir)):null,t=process.platform==="win32"?"\\":"/";let e=(a||r||process.env.MYHI_CWD||(0,external_os_.homedir)()).replace(/[/\\]+$/,"")||(0,external_os_.homedir)();process.platform==="win32"&&/^[A-Za-z]:$/.test(e)&&(e+="\\");const c=(0,external_path_.resolve)((0,external_path_.normalize)(e));r&&c!==r&&!c.startsWith(r+t)&&(e=r);try{const s=(0,external_fs_.readdirSync)(e,{withFileTypes:!0}).filter(v=>v.isDirectory()&&!v.name.startsWith(".")).map(v=>({name:v.name,path:e.replace(/[/\\]+$/,"")+t+v.name})).sort((v,b)=>v.name.localeCompare(b.name)),i=(0,external_path_.join)(e,".."),f=(0,external_path_.resolve)((0,external_path_.normalize)(i)),u=!r||f===r||f.startsWith(r+t);n({ok:!0,current:e,parent:u&&i!==e?i:null,dirs:s})}catch(s){n({ok:!1,error:s.message})}}),_.on("create",(a,n)=>{if(!hasPermission(_.data.role,"operator")){typeof n=="function"&&n({ok:!1,error:"\u6CA1\u6709\u521B\u5EFA\u4F1A\u8BDD\u7684\u6743\u9650"});return}if(_.data.dir){const t=(0,external_path_.resolve)((0,external_path_.normalize)(_.data.dir)),e=a?.cwd?(0,external_path_.resolve)((0,external_path_.normalize)(a.cwd)):null;if(e&&e!==t&&!e.startsWith(t+external_path_.sep)){typeof n=="function"&&n({ok:!1,error:"\u4E0D\u80FD\u5728\u7ED1\u5B9A\u76EE\u5F55\u4E4B\u5916\u521B\u5EFA\u4F1A\u8BDD"});return}e||(a={...a,cwd:t})}const r=a?.cwd||process.env.MYHI_CWD||process.cwd();if(!(0,external_fs_.existsSync)(r))try{(0,external_fs_.mkdirSync)(r,{recursive:!0}),console.log(`[\u521B\u5EFA] \u81EA\u52A8\u521B\u5EFA\u76EE\u5F55: ${r}`)}catch(t){typeof n=="function"&&n({ok:!1,error:`\u521B\u5EFA\u76EE\u5F55\u5931\u8D25: ${t.message}`});return}try{const t=manager.create({...a,owner:_.data.userName,userDir:_.data.dir});console.log(`[\u4F1A\u8BDD] ${_.data.userName} \u521B\u5EFA PTY \u4F1A\u8BDD "${t.title}" (${t.id})`),broadcastSessions(),typeof n=="function"&&n({ok:!0,session:t.toJSON()})}catch(t){console.error("[\u521B\u5EFA] PTY \u542F\u52A8\u5931\u8D25:",t.message),typeof n=="function"&&n({ok:!1,error:t.message})}}),_.on("kill",a=>{if(!hasPermission(_.data.role,"operator")){_.emit("error",{message:"\u6CA1\u6709\u5220\u9664\u4F1A\u8BDD\u7684\u6743\u9650"});return}console.log(`[\u4F1A\u8BDD] ${_.data.userName} \u5220\u9664\u4F1A\u8BDD ${a}`),manager.kill(a),_autoSpawned.delete(a),broadcastSessions()}),_.on("kick-user",({socketId:a,sessionId:n}={},r)=>{if(!hasPermission(_.data.role,"admin")){typeof r=="function"&&r({ok:!1,error:"\u6CA1\u6709\u8E22\u51FA\u7528\u6237\u7684\u6743\u9650"});return}const t=io.sockets.sockets.get(a);if(!t){typeof r=="function"&&r({ok:!1,error:"\u76EE\u6807\u7528\u6237\u4E0D\u5728\u7EBF"});return}const e=t.data.userName||"\u672A\u77E5";console.log(`[\u8E22\u51FA] ${_.data.userName} \u8E22\u51FA\u4E86\u7528\u6237 ${e} (${a})`),t.emit("kicked",{reason:`\u4F60\u5DF2\u88AB\u7BA1\u7406\u5458 ${_.data.userName} \u8E22\u51FA`}),t.disconnect(!0),typeof r=="function"&&r({ok:!0,name:e})}),_.on("list-viewers",({sessionId:a}={},n)=>{if(typeof n!="function")return;const r=a?manager.get(a):o;if(!r){n({ok:!1,error:"\u4F1A\u8BDD\u4E0D\u5B58\u5728"});return}const t=[];for(const e of r._viewers){const c=io.sockets.sockets.get(e);c&&t.push({socketId:e,userName:c.data.userName||"\u672A\u77E5",role:c.data.role||"viewer",isController:r.controlHolder===e})}n({ok:!0,viewers:t})})});const CONTROL_TIMEOUT=300*1e3;setInterval(()=>{for(const _ of manager.listSessions()){_.controlHolder&&(!io.sockets.sockets.get(_.controlHolder)||_.lastInputTime&&Date.now()-_.lastInputTime>CONTROL_TIMEOUT)&&(_.releaseControl(),io.emit("control-changed",{sessionId:_.id,holder:null,holderName:null}));for(const p of _._viewers)io.sockets.sockets.has(p)||_.removeViewer(p)}},60*1e3);function showStartupInfo(){const _=httpServer.address().port;PORT=_;const p=getTailscaleIP(),o=`http://${p}:${_}/admin`;console.log(`
373
373
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`),console.log(" myhi \u2014 \u57FA\u4E8E Tailscale \u7684 Web \u7EC8\u7AEF"),console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),console.log(`
374
374
  \u5730\u5740: http://${p}:${_}`),console.log(` \u5BC6\u7801: ${PASSWORD} (\u7F16\u8F91 ~/.myhi/password \u53EF\u4FEE\u6539)`),console.log(`
375
375
  \u626B\u63CF\u4E8C\u7EF4\u7801\u5728\u624B\u673A\u4E0A\u6253\u5F00:
package/dist/package.json CHANGED
@@ -1 +1 @@
1
- {"type":"module","version":"1.0.116"}
1
+ {"type":"module","version":"1.0.118"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wendongfly/myhi",
3
- "version": "1.0.116",
3
+ "version": "1.0.118",
4
4
  "description": "Web-based terminal sharing with chat UI — control your terminal from phone via LAN/Tailscale",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",