@wendongfly/myhi 1.0.85 → 1.0.87

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/bin/myhi.js CHANGED
@@ -339,9 +339,10 @@ myhi — 基于 Web 的终端共享工具
339
339
  myhi exclusive off 关闭独占模式(多人同时使用,默认)
340
340
 
341
341
  远程连接:
342
- myhi attach 列出活跃会话,交互式选择后附加
343
- myhi attach <id> 直接附加到指定会话
344
- myhi attach --new 创建新会话并附加
342
+ myhi attach 列出活跃会话,交互式选择后附加
343
+ myhi attach <id> 直接附加到指定会话
344
+ myhi attach --new 创建新会话并附加
345
+ myhi attach --password <密码> 用指定用户身份连接(看到该用户的会话)
345
346
  myhi kick 列出在线用户,交互式选择后踢出
346
347
 
347
348
  快捷键(attach 模式):
package/dist/attach.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{createRequire as e}from"module";if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=new URL(".",import.meta.url).pathname.slice(import.meta.url.match(/^file:\/\/\/\w:/)?1:0,-1)+"/";var t={};const s=e(import.meta.url)("module");const r=e(import.meta.url)("fs");const o=e(import.meta.url)("path");const n=e(import.meta.url)("os");const i=e(import.meta.url)("readline");const c=(0,s.createRequire)(import.meta.url);const{io:a}=c("socket.io-client");const p=process.env.MYHI_SERVER||"http://localhost:12300";const m=(0,r.readFileSync)((0,o.join)((0,n.homedir)(),".myhi","token"),"utf8").trim();function cleanup(e){try{process.stdin.setRawMode(false)}catch{}process.stdin.pause();e.disconnect()}function attach(e,t){e.emit("join",t);e.on("joined",(s=>{const r=s.mode==="agent";process.stderr.write(`\r\n[myhi] 已附加到 "${s.title}" (${t}) 模式=${r?"agent":"pty"}\r\n`);process.stderr.write("[myhi] 按 Ctrl+] 分离\r\n\r\n");e.emit("take-control",{sessionId:t});if(r){let t="";e.on("agent:message",(e=>{if(!e)return;switch(e.type){case"system":if(e.subtype==="init")process.stderr.write("[会话已连接]\r\n");else if(e.subtype==="interrupted")process.stderr.write("\r\n[已中断]\r\n");break;case"assistant":if(e.message?.content){for(const t of e.message.content){if(t.type==="text")process.stdout.write(t.text+"\r\n");else if(t.type==="tool_use"){process.stdout.write(`\r\n[工具] ${t.name}\r\n`);if(t.input){const e=typeof t.input==="string"?t.input:JSON.stringify(t.input,null,2);process.stdout.write(""+e.slice(0,500)+"\r\n")}}}}break;case"content_block_delta":if(e.delta?.text){process.stdout.write(e.delta.text);t+=e.delta.text}break;case"content_block_stop":if(t){process.stdout.write("\r\n");t=""}break;case"tool_use":process.stdout.write(`\r\n[工具] ${e.tool_name||e.name||"?"}\r\n`);break;case"tool_result":if(e.content){const t=typeof e.content==="string"?e.content:Array.isArray(e.content)?e.content.map((e=>e.text||"")).join(""):"";if(t)process.stdout.write(""+t.slice(0,1e3)+"\r\n")}break;case"result":process.stdout.write(`\r\n[完成] ${e.duration_ms?(e.duration_ms/1e3).toFixed(1)+"s":""} ${e.total_cost_usd?"$"+e.total_cost_usd.toFixed(4):""}\r\n\r\n`);break;case"control_request":process.stdout.write(`\r\n[权限请求] ${e.request?.tool_name||"?"}\r\n`);break}}));e.on("agent:history",(e=>{for(const t of e){if(t.type==="user")process.stdout.write(`> ${t.content}\r\n`);else if(t.type==="result")process.stdout.write(`[完成]\r\n`)}process.stdout.write("\r\n")}));e.on("agent:busy",(e=>{if(e)process.stderr.write("[思考中...]\r\n")}));e.on("agent:error",(e=>{process.stderr.write(`[错误] ${e.message}\r\n`)}));const s=(0,i.createInterface)({input:process.stdin,output:process.stdout,prompt:"> "});let r=false;e.on("agent:busy",(e=>{r=e;if(!e)s.prompt()}));s.prompt();s.on("line",(t=>{const o=t.trim();if(!o){if(!r)s.prompt();return}if(o==="/quit"||o==="/exit"||o==="/q"){process.stderr.write("[myhi] 已分离\r\n");cleanup(e);process.exit(0)}if(r){process.stderr.write("[正在处理中,请等待]\r\n");return}e.emit("agent:query",{prompt:o})}));s.on("close",(()=>{cleanup(e);process.exit(0)}))}else{try{process.stdin.setRawMode(true)}catch{}process.stdin.resume();process.stdin.setEncoding("binary");process.stdin.on("data",(t=>{if(t===""){process.stderr.write("\r\n[myhi] 已分离\r\n");cleanup(e);process.exit(0)}e.emit("input",t)}));e.on("output",(e=>{process.stdout.write(e,"binary")}));process.stdout.on("resize",(()=>{e.emit("resize",{cols:process.stdout.columns||80,rows:process.stdout.rows||24})}))}}));e.on("control-denied",(({reason:e})=>{process.stderr.write(`\r\n[myhi] 获取控制权失败: ${e}\r\n`)}));e.on("control-changed",(({holder:t,holderName:s})=>{if(t&&t!==e.id){process.stderr.write(`\r\n[myhi] ${s||"其他用户"} 已获取控制权,当前为只读\r\n`)}else if(!t){process.stderr.write("\r\n[myhi] 控制权已释放\r\n")}}));e.on("kicked",(({reason:t})=>{process.stderr.write(`\r\n[myhi] ${t||"你已被管理员踢出"}\r\n`);cleanup(e);process.exit(1)}));e.on("session-exit",(({code:t})=>{process.stderr.write(`\r\n[myhi] 会话已退出 (code ${t})\r\n`);cleanup(e);process.exit(0)}));e.on("error",(({message:t})=>{process.stderr.write(`\r\n[myhi] 错误: ${t}\r\n`);cleanup(e);process.exit(1)}))}async function pickSession(e){return new Promise(((t,s)=>{e.emit("list");e.once("sessions",(s=>{const r=s.filter((e=>e.alive));if(!r.length){process.stderr.write("[myhi] 没有活跃的会话。\n");cleanup(e);process.exit(0)}process.stdout.write("\n活跃会话:\n");r.forEach(((e,t)=>{const s=e.viewers>0?` (${e.viewers} 人在线)`:"";const r=e.mode==="agent"?" [Agent]":" [PTY]";process.stdout.write(` [${t+1}] ${e.title}${r}${s} — ${e.id}\n`)}));process.stdout.write("\n");const o=(0,i.createInterface)({input:process.stdin,output:process.stdout});o.question("选择会话编号: ",(s=>{o.close();const n=parseInt(s,10)-1;if(n<0||n>=r.length){process.stderr.write("[myhi] 无效的选择。\n");cleanup(e);process.exit(1)}t(r[n].id)}))}))}))}async function createAndAttach(e,t){return new Promise(((s,r)=>{e.emit("create",t,(t=>{if(!t?.ok){process.stderr.write(`[myhi] 创建失败: ${t?.error||"未知错误"}\n`);cleanup(e);process.exit(1)}process.stdout.write(`[myhi] 已创建会话 "${t.session.title}" — ${t.session.id}\n`);s(t.session.id)}))}))}async function promptNew(e){const t=(0,i.createInterface)({input:process.stdin,output:process.stdout});const ask=e=>new Promise((s=>t.question(e,s)));const s=(await ask("会话名称 [shell]: ")).trim()||"shell";const r=(await ask("启动命令(可选): ")).trim()||undefined;t.close();return createAndAttach(e,{title:s,initCmd:r})}const u=a(p,{transports:["websocket"],auth:{token:m}});u.on("connect_error",(e=>{process.stderr.write(`[myhi] 连接失败: ${e.message}\n`);process.exit(1)}));u.on("connect",(async()=>{const e=process.argv[2];if(e==="--new"){const e=process.argv[3];const t=process.argv[4]||undefined;const s=e?await createAndAttach(u,{title:e,initCmd:t}):await promptNew(u);attach(u,s)}else{const t=e||await pickSession(u);attach(u,t)}}));for(const e of["SIGINT","SIGTERM"]){process.on(e,(()=>{cleanup(u);process.exit(0)}))}process.on("exit",(()=>{try{process.stdin.setRawMode(false)}catch{}}));
2
+ import{createRequire as e}from"module";if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=new URL(".",import.meta.url).pathname.slice(import.meta.url.match(/^file:\/\/\/\w:/)?1:0,-1)+"/";var t={};const s=e(import.meta.url)("module");const r=e(import.meta.url)("fs");const o=e(import.meta.url)("path");const n=e(import.meta.url)("os");const i=e(import.meta.url)("readline");const c=(0,s.createRequire)(import.meta.url);const{io:p}=c("socket.io-client");const a=process.env.MYHI_SERVER||"http://localhost:12300";let m=null;const u=process.argv.slice(2);for(let e=0;e<u.length;e++){if((u[e]==="--password"||u[e]==="-pw")&&u[e+1]){m=u[e+1];u.splice(e,2);e--}}let d;if(m){d={password:m}}else{try{d={token:(0,r.readFileSync)((0,o.join)((0,n.homedir)(),".myhi","token"),"utf8").trim()}}catch{console.error("[myhi] 未找到 token,请使用 --password <密码> 或先启动服务器");process.exit(1)}}function cleanup(e){try{process.stdin.setRawMode(false)}catch{}process.stdin.pause();e.disconnect()}function attach(e,t){e.emit("join",t);e.on("joined",(s=>{const r=s.mode==="agent";process.stderr.write(`\r\n[myhi] 已附加到 "${s.title}" (${t}) 模式=${r?"agent":"pty"}\r\n`);process.stderr.write("[myhi] 按 Ctrl+] 分离\r\n\r\n");e.emit("take-control",{sessionId:t});if(r){let t="";e.on("agent:message",(e=>{if(!e)return;switch(e.type){case"system":if(e.subtype==="init")process.stderr.write("[会话已连接]\r\n");else if(e.subtype==="interrupted")process.stderr.write("\r\n[已中断]\r\n");break;case"assistant":if(e.message?.content){for(const t of e.message.content){if(t.type==="text")process.stdout.write(t.text+"\r\n");else if(t.type==="tool_use"){process.stdout.write(`\r\n[工具] ${t.name}\r\n`);if(t.input){const e=typeof t.input==="string"?t.input:JSON.stringify(t.input,null,2);process.stdout.write(""+e.slice(0,500)+"\r\n")}}}}break;case"content_block_delta":if(e.delta?.text){process.stdout.write(e.delta.text);t+=e.delta.text}break;case"content_block_stop":if(t){process.stdout.write("\r\n");t=""}break;case"tool_use":process.stdout.write(`\r\n[工具] ${e.tool_name||e.name||"?"}\r\n`);break;case"tool_result":if(e.content){const t=typeof e.content==="string"?e.content:Array.isArray(e.content)?e.content.map((e=>e.text||"")).join(""):"";if(t)process.stdout.write(""+t.slice(0,1e3)+"\r\n")}break;case"result":process.stdout.write(`\r\n[完成] ${e.duration_ms?(e.duration_ms/1e3).toFixed(1)+"s":""} ${e.total_cost_usd?"$"+e.total_cost_usd.toFixed(4):""}\r\n\r\n`);break;case"control_request":process.stdout.write(`\r\n[权限请求] ${e.request?.tool_name||"?"}\r\n`);break}}));e.on("agent:history",(e=>{for(const t of e){if(t.type==="user")process.stdout.write(`> ${t.content}\r\n`);else if(t.type==="result")process.stdout.write(`[完成]\r\n`)}process.stdout.write("\r\n")}));e.on("agent:busy",(e=>{if(e)process.stderr.write("[思考中...]\r\n")}));e.on("agent:error",(e=>{process.stderr.write(`[错误] ${e.message}\r\n`)}));const s=(0,i.createInterface)({input:process.stdin,output:process.stdout,prompt:"> "});let r=false;e.on("agent:busy",(e=>{r=e;if(!e)s.prompt()}));s.prompt();s.on("line",(t=>{const o=t.trim();if(!o){if(!r)s.prompt();return}if(o==="/quit"||o==="/exit"||o==="/q"){process.stderr.write("[myhi] 已分离\r\n");cleanup(e);process.exit(0)}if(r){process.stderr.write("[正在处理中,请等待]\r\n");return}e.emit("agent:query",{prompt:o})}));s.on("close",(()=>{cleanup(e);process.exit(0)}))}else{try{process.stdin.setRawMode(true)}catch{}process.stdin.resume();process.stdin.setEncoding("binary");process.stdin.on("data",(t=>{if(t===""){process.stderr.write("\r\n[myhi] 已分离\r\n");cleanup(e);process.exit(0)}e.emit("input",t)}));e.on("output",(e=>{process.stdout.write(e,"binary")}));process.stdout.on("resize",(()=>{e.emit("resize",{cols:process.stdout.columns||80,rows:process.stdout.rows||24})}))}}));e.on("control-denied",(({reason:e})=>{process.stderr.write(`\r\n[myhi] 获取控制权失败: ${e}\r\n`)}));e.on("control-changed",(({holder:t,holderName:s})=>{if(t&&t!==e.id){process.stderr.write(`\r\n[myhi] ${s||"其他用户"} 已获取控制权,当前为只读\r\n`)}else if(!t){process.stderr.write("\r\n[myhi] 控制权已释放\r\n")}}));e.on("kicked",(({reason:t})=>{process.stderr.write(`\r\n[myhi] ${t||"你已被管理员踢出"}\r\n`);cleanup(e);process.exit(1)}));e.on("session-exit",(({code:t})=>{process.stderr.write(`\r\n[myhi] 会话已退出 (code ${t})\r\n`);cleanup(e);process.exit(0)}));e.on("error",(({message:t})=>{process.stderr.write(`\r\n[myhi] 错误: ${t}\r\n`);cleanup(e);process.exit(1)}))}async function pickSession(e){return new Promise(((t,s)=>{e.emit("list");e.once("sessions",(s=>{const r=s.filter((e=>e.alive));if(!r.length){process.stderr.write("[myhi] 没有活跃的会话。\n");cleanup(e);process.exit(0)}process.stdout.write("\n活跃会话:\n");r.forEach(((e,t)=>{const s=e.viewers>0?` (${e.viewers} 人在线)`:"";const r=e.mode==="agent"?" [Agent]":" [PTY]";process.stdout.write(` [${t+1}] ${e.title}${r}${s} — ${e.id}\n`)}));process.stdout.write("\n");const o=(0,i.createInterface)({input:process.stdin,output:process.stdout});o.question("选择会话编号: ",(s=>{o.close();const n=parseInt(s,10)-1;if(n<0||n>=r.length){process.stderr.write("[myhi] 无效的选择。\n");cleanup(e);process.exit(1)}t(r[n].id)}))}))}))}async function createAndAttach(e,t){return new Promise(((s,r)=>{e.emit("create",t,(t=>{if(!t?.ok){process.stderr.write(`[myhi] 创建失败: ${t?.error||"未知错误"}\n`);cleanup(e);process.exit(1)}process.stdout.write(`[myhi] 已创建会话 "${t.session.title}" — ${t.session.id}\n`);s(t.session.id)}))}))}async function promptNew(e){const t=(0,i.createInterface)({input:process.stdin,output:process.stdout});const ask=e=>new Promise((s=>t.question(e,s)));const s=(await ask("会话名称 [shell]: ")).trim()||"shell";const r=(await ask("启动命令(可选): ")).trim()||undefined;t.close();return createAndAttach(e,{title:s,initCmd:r})}const l=p(a,{transports:["websocket","polling"],auth:d});l.on("connect_error",(e=>{process.stderr.write(`[myhi] 连接失败: ${e.message}\n`);process.exit(1)}));l.on("connect",(async()=>{const e=u[0];if(e==="--new"){const e=u[1];const t=u[2]||undefined;const s=e?await createAndAttach(l,{title:e,initCmd:t}):await promptNew(l);attach(l,s)}else{const t=e||await pickSession(l);attach(l,t)}}));for(const e of["SIGINT","SIGTERM"]){process.on(e,(()=>{cleanup(l);process.exit(0)}))}process.on("exit",(()=>{try{process.stdin.setRawMode(false)}catch{}}));
package/dist/chat.html CHANGED
@@ -344,36 +344,34 @@
344
344
  const H = 'style="font-size:0.9rem;font-weight:700;margin-bottom:0.5rem;color:#e6edf3"';
345
345
  const T = 'style="font-size:0.82rem;color:#b1bac4;line-height:1.6"';
346
346
  let html = '<div style="padding:1.5rem">';
347
- html += '<h3 style="margin:0 0 1.2rem;font-size:1.1rem;color:#fff;text-align:center">Claude 用量统计</h3>';
347
+ html += '<h3 style="margin:0 0 1.2rem;font-size:1.1rem;color:#fff;text-align:center">Claude 用量</h3>';
348
348
  if (data.rateLimit) {
349
349
  const rl = data.rateLimit;
350
- const remaining = Math.max(0, Math.ceil((new Date(rl.resetsAt * 1000) - Date.now()) / 60000));
350
+ const resetTime = new Date(rl.resetsAt * 1000);
351
+ const remaining = Math.max(0, Math.ceil((resetTime - Date.now()) / 60000));
352
+ const hours = Math.floor(remaining / 60);
353
+ const mins = remaining % 60;
354
+ const timeStr = hours > 0 ? `${hours}小时${mins}分钟` : `${mins}分钟`;
351
355
  const statusColor = rl.status === 'allowed' ? '#3fb950' : '#f85149';
352
- html += `<div ${C}><div ${H}>5小时窗口</div>`;
353
- html += `<div ${T}><span style="color:${statusColor};font-weight:600">${rl.status === 'allowed' ? '● 正常' : '● 受限'}</span> 重置: ${remaining} 分钟后</div></div>`;
356
+ const statusText = rl.status === 'allowed' ? '正常可用' : '已达上限';
357
+ html += `<div ${C}>`;
358
+ html += `<div ${H}>5 小时配额窗口</div>`;
359
+ html += `<div ${T}><span style="color:${statusColor};font-weight:700;font-size:0.95rem">${rl.status === 'allowed' ? '●' : '●'} ${statusText}</span></div>`;
360
+ html += `<div ${T}>窗口重置: <b style="color:#e6edf3">${timeStr}后</b>(${resetTime.toLocaleTimeString('zh-CN', {hour:'2-digit',minute:'2-digit'})})</div>`;
361
+ html += '</div>';
354
362
  } else {
355
- html += `<div ${C}><div ${H}>5小时窗口</div><div ${T}>发送 Agent 消息后可查看</div></div>`;
356
- }
357
- if (data.sevenDay) {
358
- const s = data.sevenDay;
359
- html += `<div ${C}><div ${H}>近 7 天</div>`;
360
- html += `<div ${T}>消息 <b style="color:#e6edf3">${s.messageCount.toLocaleString()}</b> 会话 <b style="color:#e6edf3">${s.sessionCount}</b> 工具调用 <b style="color:#e6edf3">${s.toolCallCount.toLocaleString()}</b></div></div>`;
363
+ html += `<div ${C}><div ${H}>5 小时配额窗口</div><div ${T}>发送一条 Agent 消息后可查看</div></div>`;
361
364
  }
362
365
  if (data.activeSessions?.length) {
363
- html += `<div ${C}><div ${H}>当前活跃会话</div>`;
366
+ html += `<div ${C}><div ${H}>当前会话</div>`;
364
367
  for (const s of data.activeSessions) {
365
368
  if (!s.usage) continue;
366
- html += `<div ${T}>${s.title} <b style="color:#e6edf3">${s.usage.queryCount}</b> 次查询 <b style="color:#e6edf3">$${s.usage.totalCostUSD.toFixed(4)}</b></div>`;
369
+ const u = s.usage;
370
+ html += `<div ${T}><b style="color:#e6edf3">${s.title}</b> (${s.owner || '管理员'})</div>`;
371
+ html += `<div style="font-size:0.78rem;color:#8b949e;margin:0.2rem 0 0.5rem;line-height:1.5">查询 <b style="color:#b1bac4">${u.queryCount}</b>次 输入 <b style="color:#b1bac4">${((u.totalInputTokens+u.totalCacheReadTokens)/1000).toFixed(0)}K</b> 输出 <b style="color:#b1bac4">${(u.totalOutputTokens/1000).toFixed(0)}K</b> 费用 <b style="color:#b1bac4">$${u.totalCostUSD.toFixed(4)}</b></div>`;
367
372
  }
368
373
  html += '</div>';
369
374
  }
370
- if (data.totalStats) {
371
- const t = data.totalStats;
372
- html += `<div ${C}><div ${H}>累计(截至 ${t.lastComputedDate || '未知'})</div>`;
373
- html += `<div ${T}>总会话 <b style="color:#e6edf3">${t.totalSessions}</b> 总消息 <b style="color:#e6edf3">${t.totalMessages?.toLocaleString()}</b></div>`;
374
- if (t.modelUsage) { for (const [m, u] of Object.entries(t.modelUsage)) { if (u.outputTokens > 0) html += `<div style="font-size:0.75rem;color:#8b949e;margin-top:0.3rem">${m.replace(/claude-/,'').replace(/-\d{8}$/,'')} 输入 ${(u.inputTokens/1000).toFixed(0)}K 输出 ${(u.outputTokens/1000).toFixed(0)}K</div>`; } }
375
- html += '</div>';
376
- }
377
375
  html += '<button onclick="this.closest(\'[data-usage-overlay]\').remove()" style="margin-top:0.8rem;width:100%;padding:0.7rem;background:#7c3aed;color:#fff;border:none;border-radius:10px;cursor:pointer;font-size:0.9rem;font-weight:600">关闭</button></div>';
378
376
  const overlay = document.createElement('div');
379
377
  overlay.setAttribute('data-usage-overlay', '');
package/dist/index.html CHANGED
@@ -540,36 +540,34 @@
540
540
  const H = 'style="font-size:0.9rem;font-weight:700;margin-bottom:0.5rem;color:#e6edf3"';
541
541
  const T = 'style="font-size:0.82rem;color:#b1bac4;line-height:1.6"';
542
542
  let html = '<div style="padding:1.5rem">';
543
- html += '<h3 style="margin:0 0 1.2rem;font-size:1.1rem;color:#fff;text-align:center">Claude 用量统计</h3>';
543
+ html += '<h3 style="margin:0 0 1.2rem;font-size:1.1rem;color:#fff;text-align:center">Claude 用量</h3>';
544
544
  if (data.rateLimit) {
545
545
  const rl = data.rateLimit;
546
- const remaining = Math.max(0, Math.ceil((new Date(rl.resetsAt * 1000) - Date.now()) / 60000));
546
+ const resetTime = new Date(rl.resetsAt * 1000);
547
+ const remaining = Math.max(0, Math.ceil((resetTime - Date.now()) / 60000));
548
+ const hours = Math.floor(remaining / 60);
549
+ const mins = remaining % 60;
550
+ const timeStr = hours > 0 ? `${hours}小时${mins}分钟` : `${mins}分钟`;
547
551
  const statusColor = rl.status === 'allowed' ? '#3fb950' : '#f85149';
548
- html += `<div ${C}><div ${H}>5小时窗口</div>`;
549
- html += `<div ${T}><span style="color:${statusColor};font-weight:600">${rl.status === 'allowed' ? '● 正常' : '● 受限'}</span> 重置: ${remaining} 分钟后</div></div>`;
552
+ const statusText = rl.status === 'allowed' ? '正常可用' : '已达上限';
553
+ html += `<div ${C}>`;
554
+ html += `<div ${H}>5 小时配额窗口</div>`;
555
+ html += `<div ${T}><span style="color:${statusColor};font-weight:700;font-size:0.95rem">● ${statusText}</span></div>`;
556
+ html += `<div ${T}>窗口重置: <b style="color:#e6edf3">${timeStr}后</b>(${resetTime.toLocaleTimeString('zh-CN', {hour:'2-digit',minute:'2-digit'})})</div>`;
557
+ html += '</div>';
550
558
  } else {
551
- html += `<div ${C}><div ${H}>5小时窗口</div><div ${T}>发送 Agent 消息后可查看</div></div>`;
552
- }
553
- if (data.sevenDay) {
554
- const s = data.sevenDay;
555
- html += `<div ${C}><div ${H}>近 7 天</div>`;
556
- html += `<div ${T}>消息 <b style="color:#e6edf3">${s.messageCount.toLocaleString()}</b> 会话 <b style="color:#e6edf3">${s.sessionCount}</b> 工具调用 <b style="color:#e6edf3">${s.toolCallCount.toLocaleString()}</b></div></div>`;
559
+ html += `<div ${C}><div ${H}>5 小时配额窗口</div><div ${T}>发送一条 Agent 消息后可查看</div></div>`;
557
560
  }
558
561
  if (data.activeSessions?.length) {
559
- html += `<div ${C}><div ${H}>当前活跃会话</div>`;
562
+ html += `<div ${C}><div ${H}>当前会话</div>`;
560
563
  for (const s of data.activeSessions) {
561
564
  if (!s.usage) continue;
562
- html += `<div ${T}>${s.title} <b style="color:#e6edf3">${s.usage.queryCount}</b> 次查询 <b style="color:#e6edf3">$${s.usage.totalCostUSD.toFixed(4)}</b></div>`;
565
+ const u = s.usage;
566
+ html += `<div ${T}><b style="color:#e6edf3">${s.title}</b> (${s.owner || '管理员'})</div>`;
567
+ html += `<div style="font-size:0.78rem;color:#8b949e;margin:0.2rem 0 0.5rem;line-height:1.5">查询 <b style="color:#b1bac4">${u.queryCount}</b>次 输入 <b style="color:#b1bac4">${((u.totalInputTokens+u.totalCacheReadTokens)/1000).toFixed(0)}K</b> 输出 <b style="color:#b1bac4">${(u.totalOutputTokens/1000).toFixed(0)}K</b> 费用 <b style="color:#b1bac4">$${u.totalCostUSD.toFixed(4)}</b></div>`;
563
568
  }
564
569
  html += '</div>';
565
570
  }
566
- if (data.totalStats) {
567
- const t = data.totalStats;
568
- html += `<div ${C}><div ${H}>累计(截至 ${t.lastComputedDate || '未知'})</div>`;
569
- html += `<div ${T}>总会话 <b style="color:#e6edf3">${t.totalSessions}</b> 总消息 <b style="color:#e6edf3">${t.totalMessages?.toLocaleString()}</b></div>`;
570
- if (t.modelUsage) { for (const [m, u] of Object.entries(t.modelUsage)) { if (u.outputTokens > 0) html += `<div style="font-size:0.75rem;color:#8b949e;margin-top:0.3rem">${m.replace(/claude-/,'').replace(/-\d{8}$/,'')} 输入 ${(u.inputTokens/1000).toFixed(0)}K 输出 ${(u.outputTokens/1000).toFixed(0)}K</div>`; } }
571
- html += '</div>';
572
- }
573
571
  html += '<button onclick="this.closest(\'[data-usage-overlay]\').remove()" style="margin-top:0.8rem;width:100%;padding:0.7rem;background:#7c3aed;color:#fff;border:none;border-radius:10px;cursor:pointer;font-size:0.9rem;font-weight:600">关闭</button></div>';
574
572
  const overlay = document.createElement('div');
575
573
  overlay.setAttribute('data-usage-overlay', '');
package/dist/index.js CHANGED
@@ -359,7 +359,7 @@ Content-Length: `+T+`\r
359
359
  `).filter(Boolean).slice(-20);for(const u of h)try{const d=JSON.parse(u);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,c){this._viewers.add(p),c&&this._viewerNames.set(p,c)}removeViewer(p){this._viewers.delete(p),this._viewerNames.delete(p)}get viewerCount(){return new Set(this._viewerNames.values()).size||this._viewers.size}takeControl(p,c){this.controlHolder=p,this.controlHolderName=c||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(S){const p=Date.now();if(_claudeSessionsCache&&p-_claudeSessionsCacheTime<CLAUDE_SESSIONS_CACHE_TTL){if(!S)return _claudeSessionsCache;const h=(0,external_path_.resolve)((0,external_path_.normalize)(S)).replace(/[\\/]+$/,"");return _claudeSessionsCache.filter(u=>(0,external_path_.resolve)((0,external_path_.normalize)(u.projectPath)).replace(/[\\/]+$/,"").startsWith(h))}const c=[],v=S?(0,external_path_.resolve)((0,external_path_.normalize)(S)).replace(/[\\/]+$/,""):null;try{const h=(0,external_path_.join)((0,external_os_.homedir)(),".claude","projects");if(!(0,external_fs_.existsSync)(h))return c;for(const u of(0,external_fs_.readdirSync)(h,{withFileTypes:!0})){if(!u.isDirectory())continue;const d=(0,external_path_.join)(h,u.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
360
  `).filter(Boolean);let e=null,o=null,s=0,i="";for(const l of t)try{const m=JSON.parse(l);if(e||(e=m),o=m,s++,!i&&m.type==="human"&&m.message?.content){const b=m.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=u.name;if(process.platform==="win32"&&/^[A-Za-z]--/.test(f)?f=f[0]+":\\"+f.slice(3).replace(/-/g,"\\"):f.startsWith("-")&&(f=f.replace(/-/g,"/")),v&&!(0,external_path_.resolve)((0,external_path_.normalize)(f)).replace(/[\\/]+$/,"").startsWith(v))continue;c.push({sessionId:n,projectDir:u.name,projectPath:f,messageCount:s,summary:i,createdAt:e?.timestamp||null,updatedAt:o?.timestamp||null})}catch{}}}catch{}}}catch{}c.sort((h,u)=>new Date(u.updatedAt||0).getTime()-new Date(h.updatedAt||0).getTime());const g=c.slice(0,50);return _claudeSessionsCache=g,_claudeSessionsCacheTime=Date.now(),g.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(S){try{(0,external_fs_.mkdirSync)(CONFIG_DIR,{recursive:!0});const p=SESSIONS_FILE+".tmp";(0,external_fs_.writeFileSync)(p,JSON.stringify(S,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,c={}){super(),this.id=p,this.createdAt=new Date().toISOString(),this.title=c.title||DEFAULT_SHELL,this.cwd=c.cwd||process.env.MYHI_CWD||process.cwd(),this.initCmd=c.initCmd||null,this.permissionMode=c.permissionMode||"default",this.owner=c.owner||null,this.cols=c.cols||120,this.rows=c.rows||30,this._viewers=new Set,this._viewerNames=new Map,this.controlHolder=null,this.controlHolderName=null,this.lastInputTime=null;const v={...process.env};if(delete v.CLAUDECODE,delete v.CLAUDE_CODE_ENTRYPOINT,delete v.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC,c.userDir){const h=(0,external_path_.join)(c.userDir,".gitconfig");(0,external_fs_.existsSync)(h)&&(v.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:v,useConpty:!1}),this._scrollback="";const g=100*1024;if(this._pty.onData(h=>{this.emit("data",h),this._scrollback+=h,this._scrollback.length>g&&(this._scrollback=this._scrollback.slice(this._scrollback.length-g))}),this._pty.onExit(({exitCode:h})=>{this.exitCode=h,this.emit("exit",h)}),c.initCmd){const h=c.initCmd;let u=!1;const d=a=>{!u&&/[$>#\]]\s*$/.test(a.trimEnd())&&(u=!0,this._pty.off("data",d),setTimeout(()=>this._pty.write(h+"\r"),120))};this._pty.onData(d),setTimeout(()=>{u||(u=!0,this._pty.write(h+"\r"))},3e3)}}write(p){p!=null&&this._pty.write(p)}resize(p,c){this.cols=p,this.rows=c,this._pty.resize(p,c)}kill(){try{this._pty.kill()}catch{}}addViewer(p,c){this._viewers.add(p),c&&this._viewerNames.set(p,c)}removeViewer(p){this._viewers.delete(p),this._viewerNames.delete(p)}get viewerCount(){return new Set(this._viewerNames.values()).size||this._viewers.size}takeControl(p,c){this.controlHolder=p,this.controlHolderName=c||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 c=(0,external_crypto_.randomUUID)(),v=new AgentSession(c,{...p,resumeSessionId:p.claudeSessionId});v.on("session-id",()=>this._persist()),this._agentSessions.set(c,v),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(c){console.warn(`[\u4F1A\u8BDD] \u6062\u590D "${p.title}" \u5931\u8D25:`,c.message)}}_spawn(p,c,v=!0){const g=new Session(p,c);return this._sessions.set(p,g),g.on("exit",()=>{setTimeout(()=>this._sessions.delete(p),300*1e3)}),v&&this._persist(),g}create(p={}){return this._spawn((0,external_crypto_.randomUUID)(),p,!0)}createAgent(p={}){const c=(0,external_crypto_.randomUUID)(),v=new AgentSession(c,p);return this._agentSessions.set(c,v),v.on("session-id",()=>this._persist()),this._persist(),v}get(p){return this._sessions.get(p)||this._agentSessions.get(p)}list(p,c=!1){let v=[...this._sessions.values()].map(h=>({...h.toJSON(),mode:"pty"})),g=[...this._agentSessions.values()].map(h=>h.toJSON());return p&&!c&&(v=v.filter(h=>!h.owner||h.owner===p),g=g.filter(h=>!h.owner||h.owner===p)),[...v,...g]}listSessions(){return[...this._sessions.values(),...this._agentSessions.values()]}kill(p){const c=this._sessions.get(p);if(c){c.kill(),this._sessions.delete(p),this._persist();return}const v=this._agentSessions.get(p);v&&(v.kill(),this._agentSessions.delete(p),this._persist())}_persist(){const p=[...this._sessions.values()].filter(v=>v.exitCode===void 0).map(v=>({mode:"pty",title:v.title,cwd:v.cwd,initCmd:v.initCmd||void 0,permissionMode:v.permissionMode||void 0,owner:v.owner||void 0})),c=[...this._agentSessions.values()].filter(v=>v.alive).map(v=>({mode:"agent",title:v.title,cwd:v.cwd,permissionMode:v.permissionMode||void 0,owner:v.owner||void 0,userDir:v.userDir||void 0,claudeSessionId:v.claudeSessionId||void 0}));writeSavedConfigs([...p,...c])}}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(S){console.warn("[\u89D2\u8272] \u52A0\u8F7D roles.json \u5931\u8D25:",S.message)}return rolesConfig=null,null}function lookupPassword(S){if(!rolesConfig?.passwords)return null;const p=rolesConfig.passwords[S];return p?{name:p.name||"\u7528\u6237",role:p.role||"viewer"}:null}function hasPermission(S,p){return(ROLE_LEVELS[S]||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(S){console.warn("[\u7528\u6237] \u52A0\u8F7D users.json \u5931\u8D25:",S.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(S){usersConfig||(usersConfig={users:{}}),usersConfig.exclusive=!!S,_writeUsers()}function lookupUser(S){if(!usersConfig?.users)return null;const p=usersConfig.users[S];return p?{name:p.name||"\u7528\u6237",dir:p.dir}:null}function listUsers(){return usersConfig?.users?Object.entries(usersConfig.users).map(([S,p])=>({password:S.slice(0,2)+"*".repeat(S.length-2),name:p.name,dir:p.dir})):[]}function addUser(S,p,c){usersConfig||(usersConfig={users:{}}),usersConfig.users||(usersConfig.users={}),usersConfig.users[S]={name:p,dir:c},_writeUsers()}function removeUser(S){return!usersConfig?.users||!usersConfig.users[S]?!1:(delete usersConfig.users[S],_writeUsers(),!0)}function _writeUsers(){mkdirSync(roles_CONFIG_DIR,{recursive:!0}),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");(function S(){try{const p=parseInt((0,external_fs_.readFileSync)(pidFile,"utf8").trim(),10);if(p&&p!==process.pid){let c=!1;try{process.kill(p,0),c=!0}catch{}if(c){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
361
  `)[0]||h}`)}const v=Date.now()+5e3;for(;Date.now()<v;){try{process.kill(p,0)}catch{break}const h=Date.now()+200;for(;Date.now()<h;);}let g=!1;try{process.kill(p,0),g=!0}catch{}g&&(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{}(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 S of manager.listSessions())try{S.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",S=>{console.error("[myhi] \u672A\u6355\u83B7\u5F02\u5E38:",S.message),gracefulShutdown(),process.exit(1)}),process.on("unhandledRejection",S=>{console.error("[myhi] \u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:",S)});function readOrCreate(S,p){try{if((0,external_fs_.existsSync)(S))return(0,external_fs_.readFileSync)(S,"utf8").trim()}catch{}const c=p();return(0,external_fs_.writeFileSync)(S,c,{mode:384}),c}const TOKEN=readOrCreate((0,external_path_.join)(configDir,"token"),()=>(0,external_crypto_.randomBytes)(16).toString("hex")),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 S=Date.now();for(const[p,c]of userSessions)c.permanent||(!c.lastUsed||S-c.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
- `)[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 S=(0,external_child_process_namespaceObject.execSync)("route -n get default 2>/dev/null || route -n 2>/dev/null",{timeout:3e3,stdio:"pipe"}).toString(),p=S.match(/interface:\s*(\S+)/i)||S.match(/0\.0\.0\.0\s+(\d+\.\d+\.\d+\.\d+)/);if(p){const c=(0,external_os_.networkInterfaces)()[p[1]];if(c){const v=c.find(g=>g.family==="IPv4"&&!g.internal);if(v)return v.address}if(/^\d+\.\d+\.\d+\.\d+$/.test(p[1]))return p[1]}}}catch{}for(const S of Object.values((0,external_os_.networkInterfaces)()))for(const p of S)if(p.family==="IPv4"&&!p.internal)return p.address;return"localhost"}function parseCookies(S=""){const p={};for(const c of S.split(";")){const v=c.indexOf("=");v>0&&(p[c.slice(0,v).trim()]=c.slice(v+1).trim())}return p}const SESSION_COOKIE="myhi_sid",COOKIE_OPTS="HttpOnly; SameSite=Strict; Path=/; Max-Age=31536000";function setSessionCookie(S,p){S.setHeader("Set-Cookie",`${SESSION_COOKIE}=${p||TOKEN}; ${COOKIE_OPTS}`)}function getSessionToken(S){return parseCookies(S)[SESSION_COOKIE]||null}function hasValidSession(S){const p=getSessionToken(S);if(p&&userSessions.has(p)){const c=userSessions.get(p);return c&&(c.lastUsed=Date.now()),!0}return!1}function getUserInfo(S){const p=getSessionToken(S);return p?userSessions.get(p):null}const app=express();app.set("trust proxy",!0),app.use((S,p,c)=>{p.setHeader("Referrer-Policy","no-referrer"),p.setHeader("X-Content-Type-Options","nosniff"),c()});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:(S,p)=>{if(!S||_corsAllowed.test(S))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(S,p){if(!AUTO_ATTACH||_autoSpawned.has(S)||!process.env.DISPLAY&&process.platform==="linux")return;_autoSpawned.add(S);const c=__nccwpck_require__.ab+"attach.js",v=process.execPath,g=p||"myhi";if(process.platform==="win32"){const h=`"${v}" "${c}" ${S}`,u=(0,external_child_process_namespaceObject.spawn)("wt.exe",["-w","0","new-tab","--title",g,"--","cmd","/k",h],{detached:!0,stdio:"ignore"});u.unref(),u.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",`"${g}"`,"cmd","/k",h],{detached:!0,stdio:"ignore"}).unref()})}else if(process.platform==="darwin"){const h=`tell application "Terminal" to do script "${v} ${c} ${S}"`;(0,external_child_process_namespaceObject.spawn)("osascript",["-e",h],{detached:!0,stdio:"ignore"}).unref()}else{const h=`${v} "${c}" ${S}`;for(const[u,d]of[["gnome-terminal",["--","bash","-c",`${h}; exec bash`]],["xterm",["-title",g,"-e",h]],["konsole",["--new-tab","-e",h]]]){const a=(0,external_child_process_namespaceObject.spawn)(u,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(S,p,c){const v=S.query.token;if(v&&(v===TOKEN||userSessions.has(v))){const g=userSessions.get(v);if(g?.onetime){userSessions.delete(v);const u=(0,external_crypto_.randomBytes)(16).toString("hex");userSessions.set(u,{role:g.role,name:g.name,lastUsed:Date.now()}),setSessionCookie(p,u)}else setSessionCookie(p,v===TOKEN?TOKEN:v);const h=S.path+(Object.keys(S.query).filter(u=>u!=="token").length?"?"+new URLSearchParams(Object.fromEntries(Object.entries(S.query).filter(([u])=>u!=="token"))):"");return p.redirect(h)}if(hasValidSession(S.headers.cookie)){if(isExclusiveMode()&&activeUser){const g=getSessionToken(S.headers.cookie);if(g!==TOKEN){const h=userSessions.get(g);if(!h||h.name!==activeUser.name)return p.setHeader("Set-Cookie",`${SESSION_COOKIE}=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0`),p.redirect("/login")}}return c()}p.redirect("/login")}const _require=(0,external_module_namespaceObject.createRequire)(import.meta.url);function pkgDirSafe(S){try{return(0,external_path_.dirname)(_require.resolve(`${S}/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(S){const p=Date.now(),c=_loginAttempts.get(S);return!c||p>c.resetAt?!0:c.count<LOGIN_MAX_ATTEMPTS}function recordLoginFailure(S){const p=Date.now(),c=_loginAttempts.get(S);!c||p>c.resetAt?_loginAttempts.set(S,{count:1,resetAt:p+LOGIN_LOCKOUT_MS}):c.count++}function clearLoginFailures(S){_loginAttempts.delete(S)}setInterval(()=>{const S=Date.now();for(const[p,c]of _loginAttempts)S>c.resetAt&&_loginAttempts.delete(p)},600*1e3),app.get("/login",(S,p)=>{p.sendFile(__nccwpck_require__.ab+"login.html")}),app.post("/login",express.urlencoded({extended:!1}),(S,p)=>{const c=S.ip;if(!checkLoginRate(c))return p.redirect("/login?error=locked");loadRoles(),loadUsers();const v=S.body?.password,g=S.query.from,h=g&&g.startsWith("/")&&!g.startsWith("//")?g:"/";if(hasUsers()){const d=lookupUser(v);if(!d)return recordLoginFailure(c),p.redirect("/login?error=1");if(isExclusiveMode()&&activeUser&&activeUser.name!==d.name)return p.redirect("/login?error=occupied&user="+encodeURIComponent(activeUser.name));clearLoginFailures(c);const a=(0,external_crypto_.randomBytes)(16).toString("hex");return userSessions.set(a,{role:"admin",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 u=lookupPassword(v);if(u){clearLoginFailures(c);const d=(0,external_crypto_.randomBytes)(16).toString("hex");return userSessions.set(d,{role:u.role,name:u.name,lastUsed:Date.now()}),setSessionCookie(p,d),p.redirect(h)}if(v===PASSWORD){clearLoginFailures(c);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(c),p.redirect("/login?error=1")}),app.post("/logout",(S,p)=>{const c=getSessionToken(S.headers.cookie);c&&(activeUser&&activeUser.tokens.has(c)&&(activeUser.tokens.delete(c),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(c)),p.setHeader("Set-Cookie",`${SESSION_COOKIE}=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0`),p.redirect("/login")}),app.get("/api/status",(S,p)=>{if(isExclusiveMode()&&activeUser)return p.json({occupied:!0,userName:activeUser.name,releasing:!!_exclusiveReleaseTimer});if(hasUsers()&&!isExclusiveMode()){const c=new Set;for(const[,v]of io.sockets.sockets){if(!v.data.userName||v.data.userName==="\u7BA1\u7406\u5458")continue;const g=v.handshake.headers.cookie;g&&!hasValidSession(g)||c.add(v.data.userName)}return p.json({occupied:!1,onlineUsers:[...c]})}p.json({occupied:!1})}),app.get("/",checkAuth,(S,p)=>p.sendFile(__nccwpck_require__.ab+"index.html")),app.get("/terminal/:id",checkAuth,(S,p)=>p.sendFile(__nccwpck_require__.ab+"chat.html")),app.get("/terminal-raw/:id",checkAuth,(S,p)=>p.sendFile(__nccwpck_require__.ab+"terminal.html")),app.use("/lib",express.static(__nccwpck_require__.ab+"lib",staticOpts)),app.get("/api/sessions",checkAuth,(S,p)=>{const c=getUserInfo(S.headers.cookie);p.json(manager.list(c?.name,c?.role==="admin"))}),app.get("/api/usage",(S,p)=>{try{const c=(0,external_path_.join)((0,external_os_.homedir)(),".claude","stats-cache.json");let v=null;(0,external_fs_.existsSync)(c)&&(v=JSON.parse((0,external_fs_.readFileSync)(c,"utf8")));const g=manager.listSessions().filter(r=>r.mode==="agent"&&r.alive).map(r=>({id:r.id,title:r.title,owner:r.owner,usage:r._usage})),h=new Date,u=new Date(h-10080*60*1e3).toISOString().slice(0,10),d=v?.dailyActivity?.filter(r=>r.date>=u)||[],a={messageCount:d.reduce((r,t)=>r+t.messageCount,0),sessionCount:d.reduce((r,t)=>r+t.sessionCount,0),toolCallCount:d.reduce((r,t)=>r+t.toolCallCount,0)};let n=null;for(const r of g)if(r.usage?.rateLimitInfo){n=r.usage.rateLimitInfo;break}p.json({rateLimit:n,sevenDay:a,activeSessions:g,totalStats:v?{totalSessions:v.totalSessions,totalMessages:v.totalMessages,modelUsage:v.modelUsage,firstSessionDate:v.firstSessionDate,lastComputedDate:v.lastComputedDate}:null})}catch(c){p.status(500).json({error:c.message})}}),app.post("/api/sessions",checkAuth,express.json(),(S,p)=>{const c=getUserInfo(S.headers.cookie),v=manager.create({...S.body,owner:c?.name,userDir:c?.dir});p.status(201).json(v.toJSON())}),app.delete("/api/sessions/:id",checkAuth,(S,p)=>{manager.kill(S.params.id),p.status(204).end()}),app.get("/api/claude-sessions",checkAuth,(S,p)=>{const c=getUserInfo(S.headers.cookie);p.json(listLocalClaudeSessions(c?.dir))}),app.get("/api/dirs",checkAuth,(S,p)=>{const c=getUserInfo(S.headers.cookie);if(!c||c.role!=="admin")return p.status(403).json({error:"\u6CA1\u6709\u76EE\u5F55\u6D4F\u89C8\u6743\u9650"});const v=c.dir?(0,external_path_.resolve)((0,external_path_.normalize)(c.dir)):null,g=process.platform==="win32"?"\\":"/";let h=S.query.path||v||process.env.MYHI_CWD||(0,external_os_.homedir)(),u=(0,external_path_.resolve)((0,external_path_.normalize)(h)).replace(/[/\\]+$/,"");process.platform==="win32"&&/^[A-Za-z]:$/.test(u)&&(u+="\\"),v&&!u.startsWith(v)&&(u=v);try{const d=(0,external_fs_.readdirSync)(u,{withFileTypes:!0}).filter(r=>r.isDirectory()&&!r.name.startsWith(".")).map(r=>({name:r.name,path:u.replace(/[/\\]+$/,"")+g+r.name})).sort((r,t)=>r.name.localeCompare(t.name)),a=(0,external_path_.join)(u,".."),n=!v||(0,external_path_.resolve)((0,external_path_.normalize)(a)).startsWith(v);p.json({current:u,parent:n&&a!==u?a:null,dirs:d})}catch(d){p.status(400).json({error:d.message})}}),app.get("/api/me",checkAuth,(S,p)=>{const c=getUserInfo(S.headers.cookie);p.json({name:c?.name||"\u7528\u6237",exclusive:isExclusiveMode(),hasUsers:hasUsers(),dir:c?.dir||null})}),app.post("/upload",checkAuth,(S,p)=>{const c=S.query.sessionId,v=c?manager.get(c):null,g=v?.cwd?(0,external_path_.join)(v.cwd,"upload"):uploadDir;(0,external_fs_.mkdirSync)(g,{recursive:!0}),multer({storage:multer.diskStorage({destination:g,filename:(u,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:(u,d,a)=>a(null,d.mimetype.startsWith("image/"))}).single("image")(S,p,u=>{if(u)return p.status(400).json({error:u.message});if(!S.file)return p.status(400).json({error:"\u6CA1\u6709\u56FE\u7247"});p.json({path:(0,external_path_.resolve)(S.file.path).replace(/\\/g,"/")})})}),app.get("/qr/:sessionId",checkAuth,async(S,p)=>{const c=getTailscaleIP(),v=(0,external_crypto_.randomBytes)(16).toString("hex");userSessions.set(v,{role:"admin",name:"\u626B\u7801\u7528\u6237",onetime:!0,lastUsed:Date.now()});const g=`http://${c}:${PORT}/terminal/${S.params.sessionId}?token=${v}`;try{const h=await lib.toString(g,{type:"svg"});p.setHeader("Content-Type","image/svg+xml"),p.send(h)}catch(h){p.status(500).send(h.message)}}),io.use((S,p)=>{const c=S.handshake.headers.cookie;if(hasValidSession(c)){const g=getUserInfo(c);return S.data.role=g?.role||"admin",S.data.userName=g?.name||"\u7528\u6237",S.data.dir=g?.dir||null,p()}if((S.handshake.auth?.token||S.handshake.query?.token)===TOKEN)return S.data.role="admin",S.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 S=!1;for(const[,p]of io.sockets.sockets)if(p.data.userName===activeUser.name){S=!0;break}if(S){_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[,S]of io.sockets.sockets)S.emit("sessions",manager.list(S.data.userName,S.data.role==="admin"))},100))}io.on("connection",S=>{const p=S.handshake.address?.replace("::ffff:","")||"?";console.log(`[\u8FDE\u63A5] ${S.data.userName}(${S.data.role}) \u4ECE ${p} \u63A5\u5165 id=${S.id}`),_exclusiveReleaseTimer&&activeUser&&S.data.userName===activeUser.name&&(clearTimeout(_exclusiveReleaseTimer),_exclusiveReleaseTimer=null);let c=null,v=null,g=null,h=null,u=null;function d(){c&&(c.isController(S.id)&&(c.releaseControl(),io.emit("control-changed",{sessionId:c.id,holder:null,holderName:null})),c.removeViewer(S.id),v&&c.off("data",v),g&&c.off("agent:message",g),h&&c.off("agent:busy",h),u&&c.off("agent:error",u),v=null,g=null,h=null,u=null)}S.on("join",a=>{const n=manager.get(a);if(!n){S.emit("error",{message:`\u4F1A\u8BDD ${a} \u672A\u627E\u5230`});return}if(d(),c=n,c.addViewer(S.id,S.data.userName),n.mode==="agent"?(g=r=>S.emit("agent:message",r),c.on("agent:message",g),h=r=>S.emit("agent:busy",r),c.on("agent:busy",h),u=r=>S.emit("agent:error",r),c.on("agent:error",u)):(v=r=>S.emit("output",r),c.on("data",v),c.once("exit",r=>{S.emit("session-exit",{code:r}),v&&c?.off("data",v)}),spawnLocalTerminal(a,n.title)),console.log(`[\u52A0\u5165] ${S.data.userName} \u52A0\u5165\u4F1A\u8BDD "${n.title}" (${a}) \u6A21\u5F0F=${n.mode||"pty"}`),S.emit("joined",{...n.toJSON(),role:S.data.role}),n.mode==="agent")n._history?.length&&S.emit("agent:history",n._history);else if(n._scrollback?.length){const t=n._scrollback;if(t.length<=4096)S.emit("output",t);else{let e=0;(function o(){e>=t.length||(S.emit("output",t.slice(e,e+4096)),e+=4096,setImmediate(o))})()}}broadcastSessions()}),S.on("disconnect",()=>{console.log(`[\u65AD\u5F00] ${S.data.userName} \u79BB\u5F00\u4F1A\u8BDD "${c?.title||"?"}" id=${S.id}`),d(),broadcastSessions(),checkExclusiveRelease()}),S.on("agent:query",async({prompt:a}={})=>{if(!c||c.mode!=="agent"){S.emit("agent:error",{message:"\u5F53\u524D\u4E0D\u662F Agent \u6A21\u5F0F\u4F1A\u8BDD"});return}if(!c.isController(S.id)){S.emit("control-denied",{reason:"\u4F60\u6CA1\u6709\u5F53\u524D\u4F1A\u8BDD\u7684\u63A7\u5236\u6743"});return}if(c.isBusy){S.emit("agent:error",{message:"\u6B63\u5728\u5904\u7406\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210"});return}try{await c.query(a)}catch(n){console.error("[agent:query] \u9519\u8BEF:",n.message);try{S.connected&&S.emit("agent:error",{message:n.message})}catch{}}}),S.on("agent:interrupt",()=>{!c||c.mode!=="agent"||c.interrupt()}),S.on("agent:permission",({requestId:a,allow:n}={})=>{if(!(!c||c.mode!=="agent")){if(!c.isController(S.id)){S.emit("control-denied",{reason:"\u4F60\u6CA1\u6709\u5F53\u524D\u4F1A\u8BDD\u7684\u63A7\u5236\u6743"});return}c.respondPermission(a,n)}}),S.on("create-agent",(a,n)=>{if(!hasPermission(S.data.role,"admin")){typeof n=="function"&&n({ok:!1,error:"\u6CA1\u6709\u521B\u5EFA\u4F1A\u8BDD\u7684\u6743\u9650"});return}if(S.data.dir){const t=(0,external_path_.resolve)((0,external_path_.normalize)(S.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:S.data.userName,userDir:S.data.dir});console.log(`[\u4F1A\u8BDD] ${S.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})}}),S.on("input",a=>{if(!(a==null||!c)&&c.mode!=="agent"){if(!c.isController(S.id)){S.emit("control-denied",{reason:"\u4F60\u6CA1\u6709\u5F53\u524D\u4F1A\u8BDD\u7684\u63A7\u5236\u6743"});return}c.lastInputTime=Date.now(),c.write(a)}}),S.on("take-control",({sessionId:a}={})=>{const n=a?manager.get(a):c;if(n){if(!hasPermission(S.data.role,"operator")){S.emit("control-denied",{reason:"\u4F60\u7684\u89D2\u8272\u6CA1\u6709\u63A7\u5236\u6743\u9650"});return}if(n.controlHolder&&n.controlHolder!==S.id&&S.data.role!=="admin"){S.emit("control-denied",{reason:"\u5176\u4ED6\u7528\u6237\u6B63\u5728\u63A7\u5236\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u91CA\u653E"});return}n.takeControl(S.id,S.data.userName),console.log(`[\u63A7\u5236] ${S.data.userName} \u83B7\u53D6\u4F1A\u8BDD "${n.title}" \u7684\u63A7\u5236\u6743`),io.emit("control-changed",{sessionId:n.id,holder:S.id,holderName:S.data.userName})}}),S.on("release-control",({sessionId:a}={})=>{const n=a?manager.get(a):c;n&&n.isController(S.id)&&(console.log(`[\u63A7\u5236] ${S.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}))}),S.on("set-mode",({sessionId:a,mode:n}={})=>{const r=a?manager.get(a):c;r&&(r.mode==="agent"&&r.setPermissionMode?(r.setPermissionMode(n),console.log(`[\u6A21\u5F0F] ${S.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}))}),S.on("rename",({sessionId:a,title:n}={})=>{const r=a?manager.get(a):c;!r||!n||(r.title=n,io.emit("session-renamed",{sessionId:r.id,title:n}),broadcastSessions())}),S.on("resize",({cols:a,rows:n})=>c?.resize(a,n)),S.on("list",()=>S.emit("sessions",manager.list(S.data.userName,S.data.role==="admin"))),S.on("dirs",(a,n)=>{if(typeof n!="function")return;const r=S.data.dir?(0,external_path_.resolve)((0,external_path_.normalize)(S.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 o=(0,external_path_.resolve)((0,external_path_.normalize)(e));r&&o!==r&&!o.startsWith(r+t)&&(e=r);try{const s=(0,external_fs_.readdirSync)(e,{withFileTypes:!0}).filter(m=>m.isDirectory()&&!m.name.startsWith(".")).map(m=>({name:m.name,path:e.replace(/[/\\]+$/,"")+t+m.name})).sort((m,b)=>m.name.localeCompare(b.name)),i=(0,external_path_.join)(e,".."),f=(0,external_path_.resolve)((0,external_path_.normalize)(i)),l=!r||f===r||f.startsWith(r+t);n({ok:!0,current:e,parent:l&&i!==e?i:null,dirs:s})}catch(s){n({ok:!1,error:s.message})}}),S.on("create",(a,n)=>{if(!hasPermission(S.data.role,"admin")){typeof n=="function"&&n({ok:!1,error:"\u6CA1\u6709\u521B\u5EFA\u4F1A\u8BDD\u7684\u6743\u9650"});return}if(S.data.dir){const t=(0,external_path_.resolve)((0,external_path_.normalize)(S.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:S.data.userName,userDir:S.data.dir});console.log(`[\u4F1A\u8BDD] ${S.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})}}),S.on("kill",a=>{if(!hasPermission(S.data.role,"admin")){S.emit("error",{message:"\u6CA1\u6709\u5220\u9664\u4F1A\u8BDD\u7684\u6743\u9650"});return}console.log(`[\u4F1A\u8BDD] ${S.data.userName} \u5220\u9664\u4F1A\u8BDD ${a}`),manager.kill(a),_autoSpawned.delete(a),broadcastSessions()}),S.on("kick-user",({socketId:a,sessionId:n}={},r)=>{if(!hasPermission(S.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] ${S.data.userName} \u8E22\u51FA\u4E86\u7528\u6237 ${e} (${a})`),t.emit("kicked",{reason:`\u4F60\u5DF2\u88AB\u7BA1\u7406\u5458 ${S.data.userName} \u8E22\u51FA`}),t.disconnect(!0),typeof r=="function"&&r({ok:!0,name:e})}),S.on("list-viewers",({sessionId:a}={},n)=>{if(typeof n!="function")return;const r=a?manager.get(a):c;if(!r){n({ok:!1,error:"\u4F1A\u8BDD\u4E0D\u5B58\u5728"});return}const t=[];for(const e of r._viewers){const o=io.sockets.sockets.get(e);o&&t.push({socketId:e,userName:o.data.userName||"\u672A\u77E5",role:o.data.role||"viewer",isController:r.controlHolder===e})}n({ok:!0,viewers:t})})});const CONTROL_TIMEOUT=300*1e3;setInterval(()=>{for(const S of manager.listSessions()){S.controlHolder&&(!io.sockets.sockets.get(S.controlHolder)||S.lastInputTime&&Date.now()-S.lastInputTime>CONTROL_TIMEOUT)&&(S.releaseControl(),io.emit("control-changed",{sessionId:S.id,holder:null,holderName:null}));for(const p of S._viewers)io.sockets.sockets.has(p)||S.removeViewer(p)}},60*1e3);function showStartupInfo(){const S=httpServer.address().port;PORT=S;const p=getTailscaleIP(),c=`http://${p}:${S}/?token=${TOKEN}`;console.log(`
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 S=(0,external_child_process_namespaceObject.execSync)("route -n get default 2>/dev/null || route -n 2>/dev/null",{timeout:3e3,stdio:"pipe"}).toString(),p=S.match(/interface:\s*(\S+)/i)||S.match(/0\.0\.0\.0\s+(\d+\.\d+\.\d+\.\d+)/);if(p){const c=(0,external_os_.networkInterfaces)()[p[1]];if(c){const v=c.find(g=>g.family==="IPv4"&&!g.internal);if(v)return v.address}if(/^\d+\.\d+\.\d+\.\d+$/.test(p[1]))return p[1]}}}catch{}for(const S of Object.values((0,external_os_.networkInterfaces)()))for(const p of S)if(p.family==="IPv4"&&!p.internal)return p.address;return"localhost"}function parseCookies(S=""){const p={};for(const c of S.split(";")){const v=c.indexOf("=");v>0&&(p[c.slice(0,v).trim()]=c.slice(v+1).trim())}return p}const SESSION_COOKIE="myhi_sid",COOKIE_OPTS="HttpOnly; SameSite=Strict; Path=/; Max-Age=31536000";function setSessionCookie(S,p){S.setHeader("Set-Cookie",`${SESSION_COOKIE}=${p||TOKEN}; ${COOKIE_OPTS}`)}function getSessionToken(S){return parseCookies(S)[SESSION_COOKIE]||null}function hasValidSession(S){const p=getSessionToken(S);if(p&&userSessions.has(p)){const c=userSessions.get(p);return c&&(c.lastUsed=Date.now()),!0}return!1}function getUserInfo(S){const p=getSessionToken(S);return p?userSessions.get(p):null}const app=express();app.set("trust proxy",!0),app.use((S,p,c)=>{p.setHeader("Referrer-Policy","no-referrer"),p.setHeader("X-Content-Type-Options","nosniff"),c()});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:(S,p)=>{if(!S||_corsAllowed.test(S))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(S,p){if(!AUTO_ATTACH||_autoSpawned.has(S)||!process.env.DISPLAY&&process.platform==="linux")return;_autoSpawned.add(S);const c=__nccwpck_require__.ab+"attach.js",v=process.execPath,g=p||"myhi";if(process.platform==="win32"){const h=`"${v}" "${c}" ${S}`,u=(0,external_child_process_namespaceObject.spawn)("wt.exe",["-w","0","new-tab","--title",g,"--","cmd","/k",h],{detached:!0,stdio:"ignore"});u.unref(),u.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",`"${g}"`,"cmd","/k",h],{detached:!0,stdio:"ignore"}).unref()})}else if(process.platform==="darwin"){const h=`tell application "Terminal" to do script "${v} ${c} ${S}"`;(0,external_child_process_namespaceObject.spawn)("osascript",["-e",h],{detached:!0,stdio:"ignore"}).unref()}else{const h=`${v} "${c}" ${S}`;for(const[u,d]of[["gnome-terminal",["--","bash","-c",`${h}; exec bash`]],["xterm",["-title",g,"-e",h]],["konsole",["--new-tab","-e",h]]]){const a=(0,external_child_process_namespaceObject.spawn)(u,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(S,p,c){const v=S.query.token;if(v&&(v===TOKEN||userSessions.has(v))){const g=userSessions.get(v);if(g?.onetime){userSessions.delete(v);const u=(0,external_crypto_.randomBytes)(16).toString("hex");userSessions.set(u,{role:g.role,name:g.name,lastUsed:Date.now()}),setSessionCookie(p,u)}else setSessionCookie(p,v===TOKEN?TOKEN:v);const h=S.path+(Object.keys(S.query).filter(u=>u!=="token").length?"?"+new URLSearchParams(Object.fromEntries(Object.entries(S.query).filter(([u])=>u!=="token"))):"");return p.redirect(h)}if(hasValidSession(S.headers.cookie)){if(isExclusiveMode()&&activeUser){const g=getSessionToken(S.headers.cookie);if(g!==TOKEN){const h=userSessions.get(g);if(!h||h.name!==activeUser.name)return p.setHeader("Set-Cookie",`${SESSION_COOKIE}=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0`),p.redirect("/login")}}return c()}p.redirect("/login")}const _require=(0,external_module_namespaceObject.createRequire)(import.meta.url);function pkgDirSafe(S){try{return(0,external_path_.dirname)(_require.resolve(`${S}/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(S){const p=Date.now(),c=_loginAttempts.get(S);return!c||p>c.resetAt?!0:c.count<LOGIN_MAX_ATTEMPTS}function recordLoginFailure(S){const p=Date.now(),c=_loginAttempts.get(S);!c||p>c.resetAt?_loginAttempts.set(S,{count:1,resetAt:p+LOGIN_LOCKOUT_MS}):c.count++}function clearLoginFailures(S){_loginAttempts.delete(S)}setInterval(()=>{const S=Date.now();for(const[p,c]of _loginAttempts)S>c.resetAt&&_loginAttempts.delete(p)},600*1e3),app.get("/login",(S,p)=>{p.sendFile(__nccwpck_require__.ab+"login.html")}),app.post("/login",express.urlencoded({extended:!1}),(S,p)=>{const c=S.ip;if(!checkLoginRate(c))return p.redirect("/login?error=locked");loadRoles(),loadUsers();const v=S.body?.password,g=S.query.from,h=g&&g.startsWith("/")&&!g.startsWith("//")?g:"/";if(hasUsers()){const d=lookupUser(v);if(!d)return recordLoginFailure(c),p.redirect("/login?error=1");if(isExclusiveMode()&&activeUser&&activeUser.name!==d.name)return p.redirect("/login?error=occupied&user="+encodeURIComponent(activeUser.name));clearLoginFailures(c);const a=(0,external_crypto_.randomBytes)(16).toString("hex");return userSessions.set(a,{role:"admin",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 u=lookupPassword(v);if(u){clearLoginFailures(c);const d=(0,external_crypto_.randomBytes)(16).toString("hex");return userSessions.set(d,{role:u.role,name:u.name,lastUsed:Date.now()}),setSessionCookie(p,d),p.redirect(h)}if(v===PASSWORD){clearLoginFailures(c);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(c),p.redirect("/login?error=1")}),app.post("/logout",(S,p)=>{const c=getSessionToken(S.headers.cookie);c&&(activeUser&&activeUser.tokens.has(c)&&(activeUser.tokens.delete(c),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(c)),p.setHeader("Set-Cookie",`${SESSION_COOKIE}=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0`),p.redirect("/login")}),app.get("/api/status",(S,p)=>{if(isExclusiveMode()&&activeUser)return p.json({occupied:!0,userName:activeUser.name,releasing:!!_exclusiveReleaseTimer});if(hasUsers()&&!isExclusiveMode()){const c=new Set;for(const[,v]of io.sockets.sockets){if(!v.data.userName||v.data.userName==="\u7BA1\u7406\u5458")continue;const g=v.handshake.headers.cookie;g&&!hasValidSession(g)||c.add(v.data.userName)}return p.json({occupied:!1,onlineUsers:[...c]})}p.json({occupied:!1})}),app.get("/",checkAuth,(S,p)=>p.sendFile(__nccwpck_require__.ab+"index.html")),app.get("/terminal/:id",checkAuth,(S,p)=>p.sendFile(__nccwpck_require__.ab+"chat.html")),app.get("/terminal-raw/:id",checkAuth,(S,p)=>p.sendFile(__nccwpck_require__.ab+"terminal.html")),app.use("/lib",express.static(__nccwpck_require__.ab+"lib",staticOpts)),app.get("/api/sessions",checkAuth,(S,p)=>{const c=getUserInfo(S.headers.cookie);p.json(manager.list(c?.name,c?.role==="admin"))}),app.get("/api/usage",(S,p)=>{try{const c=manager.listSessions().filter(g=>g.mode==="agent"&&g.alive).map(g=>({id:g.id,title:g.title,owner:g.owner,usage:g._usage}));let v=null;for(const g of c)if(g.usage?.rateLimitInfo){v=g.usage.rateLimitInfo;break}p.json({rateLimit:v,activeSessions:c})}catch(c){p.status(500).json({error:c.message})}}),app.post("/api/sessions",checkAuth,express.json(),(S,p)=>{const c=getUserInfo(S.headers.cookie),v=manager.create({...S.body,owner:c?.name,userDir:c?.dir});p.status(201).json(v.toJSON())}),app.delete("/api/sessions/:id",checkAuth,(S,p)=>{manager.kill(S.params.id),p.status(204).end()}),app.get("/api/claude-sessions",checkAuth,(S,p)=>{const c=getUserInfo(S.headers.cookie);p.json(listLocalClaudeSessions(c?.dir))}),app.get("/api/dirs",checkAuth,(S,p)=>{const c=getUserInfo(S.headers.cookie);if(!c||c.role!=="admin")return p.status(403).json({error:"\u6CA1\u6709\u76EE\u5F55\u6D4F\u89C8\u6743\u9650"});const v=c.dir?(0,external_path_.resolve)((0,external_path_.normalize)(c.dir)):null,g=process.platform==="win32"?"\\":"/";let h=S.query.path||v||process.env.MYHI_CWD||(0,external_os_.homedir)(),u=(0,external_path_.resolve)((0,external_path_.normalize)(h)).replace(/[/\\]+$/,"");process.platform==="win32"&&/^[A-Za-z]:$/.test(u)&&(u+="\\"),v&&!u.startsWith(v)&&(u=v);try{const d=(0,external_fs_.readdirSync)(u,{withFileTypes:!0}).filter(r=>r.isDirectory()&&!r.name.startsWith(".")).map(r=>({name:r.name,path:u.replace(/[/\\]+$/,"")+g+r.name})).sort((r,t)=>r.name.localeCompare(t.name)),a=(0,external_path_.join)(u,".."),n=!v||(0,external_path_.resolve)((0,external_path_.normalize)(a)).startsWith(v);p.json({current:u,parent:n&&a!==u?a:null,dirs:d})}catch(d){p.status(400).json({error:d.message})}}),app.get("/api/me",checkAuth,(S,p)=>{const c=getUserInfo(S.headers.cookie);p.json({name:c?.name||"\u7528\u6237",exclusive:isExclusiveMode(),hasUsers:hasUsers(),dir:c?.dir||null})}),app.post("/upload",checkAuth,(S,p)=>{const c=S.query.sessionId,v=c?manager.get(c):null,g=v?.cwd?(0,external_path_.join)(v.cwd,"upload"):uploadDir;(0,external_fs_.mkdirSync)(g,{recursive:!0}),multer({storage:multer.diskStorage({destination:g,filename:(u,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:(u,d,a)=>a(null,d.mimetype.startsWith("image/"))}).single("image")(S,p,u=>{if(u)return p.status(400).json({error:u.message});if(!S.file)return p.status(400).json({error:"\u6CA1\u6709\u56FE\u7247"});p.json({path:(0,external_path_.resolve)(S.file.path).replace(/\\/g,"/")})})}),app.get("/qr/:sessionId",checkAuth,async(S,p)=>{const c=getTailscaleIP(),v=(0,external_crypto_.randomBytes)(16).toString("hex");userSessions.set(v,{role:"admin",name:"\u626B\u7801\u7528\u6237",onetime:!0,lastUsed:Date.now()});const g=`http://${c}:${PORT}/terminal/${S.params.sessionId}?token=${v}`;try{const h=await lib.toString(g,{type:"svg"});p.setHeader("Content-Type","image/svg+xml"),p.send(h)}catch(h){p.status(500).send(h.message)}}),io.use((S,p)=>{const c=S.handshake.headers.cookie;if(hasValidSession(c)){const h=getUserInfo(c);return S.data.role=h?.role||"admin",S.data.userName=h?.name||"\u7528\u6237",S.data.dir=h?.dir||null,p()}const v=S.handshake.auth?.password;if(v){loadUsers();const h=lookupUser(v);if(h)return S.data.role="admin",S.data.userName=h.name,S.data.dir=h.dir,p()}if((S.handshake.auth?.token||S.handshake.query?.token)===TOKEN)return S.data.role="admin",S.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 S=!1;for(const[,p]of io.sockets.sockets)if(p.data.userName===activeUser.name){S=!0;break}if(S){_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[,S]of io.sockets.sockets)S.emit("sessions",manager.list(S.data.userName,S.data.role==="admin"))},100))}io.on("connection",S=>{const p=S.handshake.address?.replace("::ffff:","")||"?";console.log(`[\u8FDE\u63A5] ${S.data.userName}(${S.data.role}) \u4ECE ${p} \u63A5\u5165 id=${S.id}`),_exclusiveReleaseTimer&&activeUser&&S.data.userName===activeUser.name&&(clearTimeout(_exclusiveReleaseTimer),_exclusiveReleaseTimer=null);let c=null,v=null,g=null,h=null,u=null;function d(){c&&(c.isController(S.id)&&(c.releaseControl(),io.emit("control-changed",{sessionId:c.id,holder:null,holderName:null})),c.removeViewer(S.id),v&&c.off("data",v),g&&c.off("agent:message",g),h&&c.off("agent:busy",h),u&&c.off("agent:error",u),v=null,g=null,h=null,u=null)}S.on("join",a=>{const n=manager.get(a);if(!n){S.emit("error",{message:`\u4F1A\u8BDD ${a} \u672A\u627E\u5230`});return}if(d(),c=n,c.addViewer(S.id,S.data.userName),n.mode==="agent"?(g=r=>S.emit("agent:message",r),c.on("agent:message",g),h=r=>S.emit("agent:busy",r),c.on("agent:busy",h),u=r=>S.emit("agent:error",r),c.on("agent:error",u)):(v=r=>S.emit("output",r),c.on("data",v),c.once("exit",r=>{S.emit("session-exit",{code:r}),v&&c?.off("data",v)}),spawnLocalTerminal(a,n.title)),console.log(`[\u52A0\u5165] ${S.data.userName} \u52A0\u5165\u4F1A\u8BDD "${n.title}" (${a}) \u6A21\u5F0F=${n.mode||"pty"}`),S.emit("joined",{...n.toJSON(),role:S.data.role}),n.mode==="agent")n._history?.length&&S.emit("agent:history",n._history);else if(n._scrollback?.length){const t=n._scrollback;if(t.length<=4096)S.emit("output",t);else{let e=0;(function o(){e>=t.length||(S.emit("output",t.slice(e,e+4096)),e+=4096,setImmediate(o))})()}}broadcastSessions()}),S.on("disconnect",()=>{console.log(`[\u65AD\u5F00] ${S.data.userName} \u79BB\u5F00\u4F1A\u8BDD "${c?.title||"?"}" id=${S.id}`),d(),broadcastSessions(),checkExclusiveRelease()}),S.on("agent:query",async({prompt:a}={})=>{if(!c||c.mode!=="agent"){S.emit("agent:error",{message:"\u5F53\u524D\u4E0D\u662F Agent \u6A21\u5F0F\u4F1A\u8BDD"});return}if(!c.isController(S.id)){S.emit("control-denied",{reason:"\u4F60\u6CA1\u6709\u5F53\u524D\u4F1A\u8BDD\u7684\u63A7\u5236\u6743"});return}if(c.isBusy){S.emit("agent:error",{message:"\u6B63\u5728\u5904\u7406\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210"});return}try{await c.query(a)}catch(n){console.error("[agent:query] \u9519\u8BEF:",n.message);try{S.connected&&S.emit("agent:error",{message:n.message})}catch{}}}),S.on("agent:interrupt",()=>{!c||c.mode!=="agent"||c.interrupt()}),S.on("agent:permission",({requestId:a,allow:n}={})=>{if(!(!c||c.mode!=="agent")){if(!c.isController(S.id)){S.emit("control-denied",{reason:"\u4F60\u6CA1\u6709\u5F53\u524D\u4F1A\u8BDD\u7684\u63A7\u5236\u6743"});return}c.respondPermission(a,n)}}),S.on("create-agent",(a,n)=>{if(!hasPermission(S.data.role,"admin")){typeof n=="function"&&n({ok:!1,error:"\u6CA1\u6709\u521B\u5EFA\u4F1A\u8BDD\u7684\u6743\u9650"});return}if(S.data.dir){const t=(0,external_path_.resolve)((0,external_path_.normalize)(S.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:S.data.userName,userDir:S.data.dir});console.log(`[\u4F1A\u8BDD] ${S.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})}}),S.on("input",a=>{if(!(a==null||!c)&&c.mode!=="agent"){if(!c.isController(S.id)){S.emit("control-denied",{reason:"\u4F60\u6CA1\u6709\u5F53\u524D\u4F1A\u8BDD\u7684\u63A7\u5236\u6743"});return}c.lastInputTime=Date.now(),c.write(a)}}),S.on("take-control",({sessionId:a}={})=>{const n=a?manager.get(a):c;if(n){if(!hasPermission(S.data.role,"operator")){S.emit("control-denied",{reason:"\u4F60\u7684\u89D2\u8272\u6CA1\u6709\u63A7\u5236\u6743\u9650"});return}if(n.controlHolder&&n.controlHolder!==S.id&&S.data.role!=="admin"){S.emit("control-denied",{reason:"\u5176\u4ED6\u7528\u6237\u6B63\u5728\u63A7\u5236\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u91CA\u653E"});return}n.takeControl(S.id,S.data.userName),console.log(`[\u63A7\u5236] ${S.data.userName} \u83B7\u53D6\u4F1A\u8BDD "${n.title}" \u7684\u63A7\u5236\u6743`),io.emit("control-changed",{sessionId:n.id,holder:S.id,holderName:S.data.userName})}}),S.on("release-control",({sessionId:a}={})=>{const n=a?manager.get(a):c;n&&n.isController(S.id)&&(console.log(`[\u63A7\u5236] ${S.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}))}),S.on("set-mode",({sessionId:a,mode:n}={})=>{const r=a?manager.get(a):c;r&&(r.mode==="agent"&&r.setPermissionMode?(r.setPermissionMode(n),console.log(`[\u6A21\u5F0F] ${S.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}))}),S.on("rename",({sessionId:a,title:n}={})=>{const r=a?manager.get(a):c;!r||!n||(r.title=n,io.emit("session-renamed",{sessionId:r.id,title:n}),broadcastSessions())}),S.on("resize",({cols:a,rows:n})=>c?.resize(a,n)),S.on("list",()=>S.emit("sessions",manager.list(S.data.userName,S.data.role==="admin"))),S.on("dirs",(a,n)=>{if(typeof n!="function")return;const r=S.data.dir?(0,external_path_.resolve)((0,external_path_.normalize)(S.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 o=(0,external_path_.resolve)((0,external_path_.normalize)(e));r&&o!==r&&!o.startsWith(r+t)&&(e=r);try{const s=(0,external_fs_.readdirSync)(e,{withFileTypes:!0}).filter(m=>m.isDirectory()&&!m.name.startsWith(".")).map(m=>({name:m.name,path:e.replace(/[/\\]+$/,"")+t+m.name})).sort((m,b)=>m.name.localeCompare(b.name)),i=(0,external_path_.join)(e,".."),f=(0,external_path_.resolve)((0,external_path_.normalize)(i)),l=!r||f===r||f.startsWith(r+t);n({ok:!0,current:e,parent:l&&i!==e?i:null,dirs:s})}catch(s){n({ok:!1,error:s.message})}}),S.on("create",(a,n)=>{if(!hasPermission(S.data.role,"admin")){typeof n=="function"&&n({ok:!1,error:"\u6CA1\u6709\u521B\u5EFA\u4F1A\u8BDD\u7684\u6743\u9650"});return}if(S.data.dir){const t=(0,external_path_.resolve)((0,external_path_.normalize)(S.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:S.data.userName,userDir:S.data.dir});console.log(`[\u4F1A\u8BDD] ${S.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})}}),S.on("kill",a=>{if(!hasPermission(S.data.role,"admin")){S.emit("error",{message:"\u6CA1\u6709\u5220\u9664\u4F1A\u8BDD\u7684\u6743\u9650"});return}console.log(`[\u4F1A\u8BDD] ${S.data.userName} \u5220\u9664\u4F1A\u8BDD ${a}`),manager.kill(a),_autoSpawned.delete(a),broadcastSessions()}),S.on("kick-user",({socketId:a,sessionId:n}={},r)=>{if(!hasPermission(S.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] ${S.data.userName} \u8E22\u51FA\u4E86\u7528\u6237 ${e} (${a})`),t.emit("kicked",{reason:`\u4F60\u5DF2\u88AB\u7BA1\u7406\u5458 ${S.data.userName} \u8E22\u51FA`}),t.disconnect(!0),typeof r=="function"&&r({ok:!0,name:e})}),S.on("list-viewers",({sessionId:a}={},n)=>{if(typeof n!="function")return;const r=a?manager.get(a):c;if(!r){n({ok:!1,error:"\u4F1A\u8BDD\u4E0D\u5B58\u5728"});return}const t=[];for(const e of r._viewers){const o=io.sockets.sockets.get(e);o&&t.push({socketId:e,userName:o.data.userName||"\u672A\u77E5",role:o.data.role||"viewer",isController:r.controlHolder===e})}n({ok:!0,viewers:t})})});const CONTROL_TIMEOUT=300*1e3;setInterval(()=>{for(const S of manager.listSessions()){S.controlHolder&&(!io.sockets.sockets.get(S.controlHolder)||S.lastInputTime&&Date.now()-S.lastInputTime>CONTROL_TIMEOUT)&&(S.releaseControl(),io.emit("control-changed",{sessionId:S.id,holder:null,holderName:null}));for(const p of S._viewers)io.sockets.sockets.has(p)||S.removeViewer(p)}},60*1e3);function showStartupInfo(){const S=httpServer.address().port;PORT=S;const p=getTailscaleIP(),c=`http://${p}:${S}/?token=${TOKEN}`;console.log(`
363
363
  \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(`
364
364
  \u5730\u5740: http://${p}:${S}`),console.log(` \u5BC6\u7801: ${PASSWORD} (\u7F16\u8F91 ~/.myhi/password \u53EF\u4FEE\u6539)`),console.log(`
365
365
  \u626B\u63CF\u4E8C\u7EF4\u7801\u5728\u624B\u673A\u4E0A\u6253\u5F00:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wendongfly/myhi",
3
- "version": "1.0.85",
3
+ "version": "1.0.87",
4
4
  "description": "Web-based terminal sharing with chat UI — control your terminal from phone via LAN/Tailscale",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",