@wendongfly/myhi 1.0.112 → 1.0.114

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
@@ -244,6 +244,191 @@ if (cmd === 'attach') {
244
244
  `);
245
245
  }
246
246
 
247
+ } else if (cmd === 'ssh') {
248
+ // SSH 账号管理:为 myhi 用户创建系统 SSH 账号,隔离目录权限
249
+ const { existsSync } = await import('fs');
250
+ const { platform } = await import('os');
251
+ const usersFile = join(configDir, 'users.json');
252
+ const isWindows = platform() === 'win32';
253
+
254
+ function loadUsersFile() {
255
+ try { if (existsSync(usersFile)) return JSON.parse(readFileSync(usersFile, 'utf8')); } catch {}
256
+ return { users: {} };
257
+ }
258
+ function saveUsersFile(data) {
259
+ mkdirSync(configDir, { recursive: true });
260
+ writeFileSync(usersFile, JSON.stringify(data, null, 2), { mode: 0o600 });
261
+ }
262
+
263
+ function run(cmd, opts = {}) {
264
+ try {
265
+ return execSync(cmd, { encoding: 'utf8', stdio: opts.silent ? 'pipe' : 'inherit', ...opts }).trim();
266
+ } catch (e) {
267
+ if (!opts.ignoreError) { console.error(`[myhi] 命令失败: ${cmd}\n${e.stderr || e.message}`); process.exit(1); }
268
+ return '';
269
+ }
270
+ }
271
+
272
+ function findUserByName(name) {
273
+ const data = loadUsersFile();
274
+ for (const [pwd, info] of Object.entries(data.users || {})) {
275
+ if (info.name === name) return { pwd, ...info };
276
+ }
277
+ return null;
278
+ }
279
+
280
+ const sub = args[args.indexOf('ssh') + 1] || process.argv[3];
281
+
282
+ if (sub === 'add' || sub === 'setup') {
283
+ const myName = process.argv[4];
284
+ const sshPass = process.argv[5];
285
+ if (!myName) {
286
+ console.log('用法: myhi ssh add <myhi用户名> [SSH密码]');
287
+ console.log('示例: myhi ssh add 张三 mypassword');
288
+ console.log('\n将为 myhi 用户创建系统 SSH 账号,绑定到该用户的项目目录');
289
+ process.exit(1);
290
+ }
291
+
292
+ const user = findUserByName(myName);
293
+ if (!user) { console.log(`[myhi] 未找到 myhi 用户 "${myName}",请先 myhi user add`); process.exit(1); }
294
+
295
+ const dir = user.dir;
296
+ // SSH 用户名:myhi-<name> (ASCII 化)
297
+ const sshUser = 'myhi-' + myName.replace(/[^a-zA-Z0-9_-]/g, () => Math.random().toString(36)[2]);
298
+ const sshPassword = sshPass || Math.random().toString(36).slice(2, 10) + 'A1!';
299
+
300
+ console.log(`[myhi] 创建 SSH 账号...`);
301
+ console.log(` myhi 用户: ${myName}`);
302
+ console.log(` 项目目录: ${dir}`);
303
+ console.log(` SSH 用户: ${sshUser}`);
304
+
305
+ if (isWindows) {
306
+ // ── Windows ──
307
+ // 创建本地用户
308
+ run(`net user "${sshUser}" "${sshPassword}" /add /y`, { silent: true, ignoreError: true });
309
+ run(`net user "${sshUser}" "${sshPassword}" /y`, { silent: true, ignoreError: true });
310
+
311
+ // 设置目录权限
312
+ mkdirSync(dir, { recursive: true });
313
+ // 上级目录:允许穿越(Traverse)但不允许列目录
314
+ const parent = join(dir, '..');
315
+ run(`icacls "${parent}" /grant "${sshUser}:(RC,S,X,RA,REA)"`, { silent: true, ignoreError: true });
316
+ // 用户自己的目录:完全控制
317
+ run(`icacls "${dir}" /grant "${sshUser}:(OI)(CI)F"`, { silent: true });
318
+
319
+ // 在用户 home 下创建 project 符号链接
320
+ const userHome = `C:\\Users\\${sshUser}`;
321
+ if (!existsSync(userHome)) mkdirSync(userHome, { recursive: true });
322
+ const link = join(userHome, 'project');
323
+ if (!existsSync(link)) {
324
+ run(`cmd /c mklink /D "${link}" "${dir}"`, { silent: true, ignoreError: true });
325
+ }
326
+
327
+ // 确保 OpenSSH Server 已启动
328
+ run('powershell -Command "Start-Service sshd 2>$null; Set-Service -Name sshd -StartupType Automatic 2>$null"', { silent: true, ignoreError: true });
329
+
330
+ } else {
331
+ // ── Linux / Ubuntu ──
332
+ // 创建系统用户,home 指向项目目录
333
+ run(`id "${sshUser}" 2>/dev/null || useradd -m -d "/home/${sshUser}" -s /bin/bash "${sshUser}"`, { silent: true, ignoreError: true });
334
+ run(`echo "${sshUser}:${sshPassword}" | chpasswd`, { silent: true });
335
+
336
+ mkdirSync(dir, { recursive: true });
337
+
338
+ // 在用户 home 下创建 project 符号链接
339
+ const homeDir = `/home/${sshUser}`;
340
+ const link = `${homeDir}/project`;
341
+ run(`ln -sfn "${dir}" "${link}"`, { silent: true, ignoreError: true });
342
+ run(`chown -h ${sshUser}:${sshUser} "${link}"`, { silent: true, ignoreError: true });
343
+
344
+ // 项目目录:设置为该用户所有
345
+ run(`chown -R ${sshUser}:${sshUser} "${dir}"`, { silent: true, ignoreError: true });
346
+
347
+ // 上级目录权限:移除 others 的读和执行
348
+ const parent = join(dir, '..');
349
+ run(`chmod 711 "${parent}"`, { silent: true, ignoreError: true });
350
+ // 用户自己的目录
351
+ run(`chmod 700 "${dir}"`, { silent: true, ignoreError: true });
352
+
353
+ // 确保 SSH 服务运行
354
+ run('systemctl enable ssh 2>/dev/null; systemctl start ssh 2>/dev/null || service ssh start 2>/dev/null', { silent: true, ignoreError: true });
355
+ }
356
+
357
+ // 保存 SSH 映射到 users.json
358
+ const data = loadUsersFile();
359
+ if (data.users[user.pwd]) {
360
+ data.users[user.pwd].sshUser = sshUser;
361
+ saveUsersFile(data);
362
+ }
363
+
364
+ console.log(`\n ✓ SSH 账号创建成功!`);
365
+ console.log(` ┌─────────────────────────────────`);
366
+ console.log(` │ SSH 用户名: ${sshUser}`);
367
+ console.log(` │ SSH 密码: ${sshPassword}`);
368
+ console.log(` │ 项目目录: ${dir}`);
369
+ console.log(` └─────────────────────────────────`);
370
+ console.log(`\n VS Code 连接方式:`);
371
+ console.log(` 1. 安装 Remote-SSH 扩展`);
372
+ console.log(` 2. 按 F1 → "Remote-SSH: Connect to Host"`);
373
+ console.log(` 3. 输入: ${sshUser}@<服务器IP>`);
374
+ console.log(` 4. 打开目录: ~/project`);
375
+
376
+ } else if (sub === 'list' || sub === 'ls') {
377
+ const data = loadUsersFile();
378
+ const entries = Object.entries(data.users || {}).filter(([, info]) => info.sshUser);
379
+ if (entries.length === 0) {
380
+ console.log('[myhi] 还没有 SSH 账号,使用 myhi ssh add <用户名> 创建');
381
+ } else {
382
+ console.log('\n myhi用户\tSSH账号\t\t\t目录');
383
+ console.log(' ────────\t───────\t\t\t────');
384
+ for (const [, info] of entries) {
385
+ console.log(` ${info.name}\t\t${info.sshUser}\t\t${info.dir}`);
386
+ }
387
+ console.log();
388
+ }
389
+
390
+ } else if (sub === 'remove' || sub === 'rm') {
391
+ const myName = process.argv[4];
392
+ if (!myName) { console.log('用法: myhi ssh remove <myhi用户名>'); process.exit(1); }
393
+
394
+ const user = findUserByName(myName);
395
+ if (!user || !user.sshUser) { console.log(`[myhi] 未找到 "${myName}" 的 SSH 账号`); process.exit(1); }
396
+
397
+ const sshUser = user.sshUser;
398
+ console.log(`[myhi] 删除 SSH 账号: ${sshUser}`);
399
+
400
+ if (isWindows) {
401
+ run(`net user "${sshUser}" /delete`, { silent: true, ignoreError: true });
402
+ } else {
403
+ run(`userdel "${sshUser}" 2>/dev/null`, { silent: true, ignoreError: true });
404
+ // 不删除 home,避免误删项目数据
405
+ }
406
+
407
+ const data = loadUsersFile();
408
+ if (data.users[user.pwd]) {
409
+ delete data.users[user.pwd].sshUser;
410
+ saveUsersFile(data);
411
+ }
412
+ console.log(`[myhi] 已删除 SSH 账号 "${sshUser}"(项目目录保留)`);
413
+
414
+ } else {
415
+ console.log(`
416
+ SSH 账号管理:为 myhi 用户创建独立的 SSH 开发账号
417
+
418
+ myhi ssh add <用户名> [密码] 创建 SSH 账号(绑定到 myhi 用户的项目目录)
419
+ myhi ssh list 列出所有 SSH 账号
420
+ myhi ssh remove <用户名> 删除 SSH 账号(保留项目目录)
421
+
422
+ 示例:
423
+ myhi user add 1234 张三 /data/projects/zhangsan # 先添加 myhi 用户
424
+ myhi ssh add 张三 sshpass123 # 再创建 SSH 账号
425
+ myhi ssh list # 查看所有 SSH 账号
426
+
427
+ 员工使用 VS Code Remote-SSH 连接后打开 ~/project 即可开发。
428
+ 每个用户只能访问自己的项目目录,无法查看其他用户的文件。
429
+ `);
430
+ }
431
+
247
432
  } else if (cmd === 'kick') {
248
433
  // 踢出会话中的用户
249
434
  const { createRequire } = await import('module');
@@ -365,6 +550,11 @@ myhi — 基于 Web 的终端共享工具
365
550
  myhi exclusive on 开启独占模式(一次只能一个账号)
366
551
  myhi exclusive off 关闭独占模式(多人同时使用,默认)
367
552
 
553
+ SSH 开发:
554
+ myhi ssh add <用户名> [密码] 为 myhi 用户创建 SSH 账号
555
+ myhi ssh list 列出所有 SSH 账号
556
+ myhi ssh remove <用户名> 删除 SSH 账号
557
+
368
558
  远程连接:
369
559
  myhi attach 列出活跃会话,交互式选择后附加
370
560
  myhi attach <id> 直接附加到指定会话
package/dist/index.js CHANGED
@@ -363,7 +363,7 @@ Content-Length: `+T+`\r
363
363
  helper = store --file ${l.replace(/\\/g,"/")}
364
364
  [user]
365
365
  name = ${g}
366
- `,{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&&_.data.role!=="admin"){_.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(`
366
+ `,{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(`
367
367
  \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(`
368
368
  \u5730\u5740: http://${p}:${_}`),console.log(` \u5BC6\u7801: ${PASSWORD} (\u7F16\u8F91 ~/.myhi/password \u53EF\u4FEE\u6539)`),console.log(`
369
369
  \u626B\u63CF\u4E8C\u7EF4\u7801\u5728\u624B\u673A\u4E0A\u6253\u5F00:
package/dist/package.json CHANGED
@@ -1 +1 @@
1
- {"type":"module","version":"1.0.112"}
1
+ {"type":"module","version":"1.0.114"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wendongfly/myhi",
3
- "version": "1.0.112",
3
+ "version": "1.0.114",
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",