@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 +96 -10
- package/dist/chat.html +63 -2
- package/dist/index.html +4 -2
- package/dist/index.js +4 -4
- package/dist/package.json +1 -1
- package/package.json +1 -1
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>:
|
|
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:
|
|
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` | `
|
|
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
|
-
|
|
440
|
-
|
|
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
|
-
|
|
595
|
-
|
|
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.
|
|
1
|
+
{"type":"module","version":"1.0.118"}
|
package/package.json
CHANGED