@wendongfly/myhi 1.0.113 → 1.0.115
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 +190 -0
- package/dist/chat.html +101 -0
- package/dist/index.js +7 -1
- package/dist/package.json +1 -1
- package/package.json +1 -1
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/chat.html
CHANGED
|
@@ -280,6 +280,7 @@
|
|
|
280
280
|
<button class="sk sk-claude" onclick="doCompact()">压缩</button>
|
|
281
281
|
<button class="sk sk-claude" data-cmd="/clear">清除</button>
|
|
282
282
|
<button class="sk sk-claude" onclick="doRename()">命名</button>
|
|
283
|
+
<button class="sk sk-claude" onclick="openGitSheet()" style="color:#3fb950">提交</button>
|
|
283
284
|
<button class="sk" data-send="ctrl-c">Ctrl+C</button>
|
|
284
285
|
<button class="sk" data-send="esc">Esc</button>
|
|
285
286
|
</div>
|
|
@@ -290,6 +291,7 @@
|
|
|
290
291
|
<button class="sk sk-claude" onclick="openResumeSheet()">恢复</button>
|
|
291
292
|
<button class="sk sk-claude" onclick="doClear()">清除</button>
|
|
292
293
|
<button class="sk sk-claude" onclick="doSlashCmd('/rename')">命名</button>
|
|
294
|
+
<button class="sk sk-claude" onclick="openGitSheet()" style="color:#3fb950">提交</button>
|
|
293
295
|
</div>
|
|
294
296
|
</div>
|
|
295
297
|
|
|
@@ -347,6 +349,29 @@
|
|
|
347
349
|
</div>
|
|
348
350
|
</div>
|
|
349
351
|
|
|
352
|
+
<div id="git-sheet" class="action-sheet">
|
|
353
|
+
<div class="action-sheet-backdrop" onclick="closeGitSheet()"></div>
|
|
354
|
+
<div class="action-sheet-box">
|
|
355
|
+
<div class="action-sheet-title">提交代码到 GitLab</div>
|
|
356
|
+
<div style="padding:0.3rem 0.2rem;display:flex;flex-direction:column;gap:0.4rem">
|
|
357
|
+
<input id="git-url" class="slash-inp" placeholder="GitLab 仓库地址 (https://gitlab.com/...)" autocomplete="off">
|
|
358
|
+
<div style="display:flex;gap:0.4rem">
|
|
359
|
+
<input id="git-user" class="slash-inp" placeholder="用户名" autocomplete="off" style="flex:1">
|
|
360
|
+
<input id="git-pass" class="slash-inp" placeholder="密码/Token" type="password" autocomplete="off" style="flex:1">
|
|
361
|
+
</div>
|
|
362
|
+
<div style="display:flex;gap:0.4rem">
|
|
363
|
+
<input id="git-branch" class="slash-inp" placeholder="分支 (默认 main)" autocomplete="off" style="flex:1">
|
|
364
|
+
<input id="git-msg" class="slash-inp" placeholder="提交说明" autocomplete="off" style="flex:2">
|
|
365
|
+
</div>
|
|
366
|
+
<label style="display:flex;align-items:center;gap:0.4rem;font-size:0.75rem;color:#8b949e;padding:0 0.2rem">
|
|
367
|
+
<input type="checkbox" id="git-save" checked> 记住此项目的仓库信息
|
|
368
|
+
</label>
|
|
369
|
+
</div>
|
|
370
|
+
<button id="git-submit-btn" class="action-sheet-cancel" style="background:#3fb950;color:#000;font-weight:600;margin-bottom:0.4rem" onclick="submitGitPush()">提交并推送</button>
|
|
371
|
+
<button class="action-sheet-cancel" onclick="closeGitSheet()">取消</button>
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
|
|
350
375
|
<div id="status-overlay">连接中...</div>
|
|
351
376
|
|
|
352
377
|
<script type="module">
|
|
@@ -1534,6 +1559,82 @@
|
|
|
1534
1559
|
addStatusMessage('会话已命名为: ' + title);
|
|
1535
1560
|
});
|
|
1536
1561
|
|
|
1562
|
+
// ── Git 提交推送 ──────────────────────────────
|
|
1563
|
+
const GIT_STORAGE_KEY = 'myhi-git-' + SESSION_ID;
|
|
1564
|
+
|
|
1565
|
+
window.openGitSheet = async function() {
|
|
1566
|
+
// 从 localStorage 恢复保存的信息
|
|
1567
|
+
const saved = JSON.parse(localStorage.getItem(GIT_STORAGE_KEY) || '{}');
|
|
1568
|
+
document.getElementById('git-url').value = saved.url || '';
|
|
1569
|
+
document.getElementById('git-user').value = saved.username || '';
|
|
1570
|
+
document.getElementById('git-pass').value = saved.password || '';
|
|
1571
|
+
document.getElementById('git-branch').value = saved.branch || '';
|
|
1572
|
+
document.getElementById('git-msg').value = '';
|
|
1573
|
+
|
|
1574
|
+
// 尝试从服务器获取已配置的 remote 信息
|
|
1575
|
+
if (!saved.url) {
|
|
1576
|
+
try {
|
|
1577
|
+
const resp = await fetch(`/api/git/info?sessionId=${SESSION_ID}`);
|
|
1578
|
+
const info = await resp.json();
|
|
1579
|
+
if (info.url) document.getElementById('git-url').value = info.url;
|
|
1580
|
+
if (info.branch) document.getElementById('git-branch').value = info.branch;
|
|
1581
|
+
if (info.username) document.getElementById('git-user').value = info.username;
|
|
1582
|
+
} catch {}
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
document.getElementById('git-sheet').classList.add('open');
|
|
1586
|
+
setTimeout(() => document.getElementById('git-msg').focus(), 100);
|
|
1587
|
+
};
|
|
1588
|
+
|
|
1589
|
+
window.closeGitSheet = function() {
|
|
1590
|
+
document.getElementById('git-sheet').classList.remove('open');
|
|
1591
|
+
};
|
|
1592
|
+
|
|
1593
|
+
window.submitGitPush = async function() {
|
|
1594
|
+
const url = document.getElementById('git-url').value.trim();
|
|
1595
|
+
const username = document.getElementById('git-user').value.trim();
|
|
1596
|
+
const password = document.getElementById('git-pass').value.trim();
|
|
1597
|
+
const branch = document.getElementById('git-branch').value.trim() || 'main';
|
|
1598
|
+
const message = document.getElementById('git-msg').value.trim();
|
|
1599
|
+
const save = document.getElementById('git-save').checked;
|
|
1600
|
+
|
|
1601
|
+
if (!message) { document.getElementById('git-msg').focus(); return; }
|
|
1602
|
+
|
|
1603
|
+
// 保存凭据
|
|
1604
|
+
if (save && url) {
|
|
1605
|
+
localStorage.setItem(GIT_STORAGE_KEY, JSON.stringify({ url, username, password, branch }));
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
const btn = document.getElementById('git-submit-btn');
|
|
1609
|
+
btn.disabled = true;
|
|
1610
|
+
btn.textContent = '提交中...';
|
|
1611
|
+
|
|
1612
|
+
try {
|
|
1613
|
+
const resp = await fetch('/api/git/push', {
|
|
1614
|
+
method: 'POST',
|
|
1615
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1616
|
+
body: JSON.stringify({ sessionId: SESSION_ID, url: url || undefined, username: username || undefined, password: password || undefined, message, branch }),
|
|
1617
|
+
});
|
|
1618
|
+
const data = await resp.json();
|
|
1619
|
+
closeGitSheet();
|
|
1620
|
+
if (data.ok) {
|
|
1621
|
+
addStatusMessage(data.message);
|
|
1622
|
+
} else {
|
|
1623
|
+
addStatusMessage('提交失败: ' + data.error);
|
|
1624
|
+
}
|
|
1625
|
+
} catch (e) {
|
|
1626
|
+
addStatusMessage('提交失败: ' + e.message);
|
|
1627
|
+
} finally {
|
|
1628
|
+
btn.disabled = false;
|
|
1629
|
+
btn.textContent = '提交并推送';
|
|
1630
|
+
}
|
|
1631
|
+
};
|
|
1632
|
+
|
|
1633
|
+
// Enter 键在提交说明框提交
|
|
1634
|
+
document.getElementById('git-msg')?.addEventListener('keydown', (e) => {
|
|
1635
|
+
if (e.key === 'Enter') { e.preventDefault(); submitGitPush(); }
|
|
1636
|
+
});
|
|
1637
|
+
|
|
1537
1638
|
// ── Agent 模式快捷功能 ──────────────────────────
|
|
1538
1639
|
let _totalAgentCost = 0;
|
|
1539
1640
|
// 追踪 result 消息中的费用
|
package/dist/index.js
CHANGED
|
@@ -359,7 +359,13 @@ Content-Length: `+T+`\r
|
|
|
359
359
|
`).filter(Boolean).slice(-20);for(const l of h)try{const d=JSON.parse(l);if(d.type==="human"&&d.message?.content){const a=typeof d.message.content=="string"?d.message.content:d.message.content.find(n=>n.type==="text")?.text||"";a&&this._history.push({type:"user",content:a,timestamp:d.timestamp})}else d.type==="assistant"&&d.message?.content?this._history.push({type:"assistant",message:d.message,timestamp:d.timestamp}):d.type==="result"&&this._history.push({type:"result",total_cost_usd:d.costUSD||d.total_cost_usd,timestamp:d.timestamp})}catch{}return}}catch(p){console.warn("[agent] \u52A0\u8F7D\u6062\u590D\u4F1A\u8BDD\u5386\u53F2\u5931\u8D25:",p.message)}}get isBusy(){return this._busy}addViewer(p,o){this._viewers.add(p),o&&this._viewerNames.set(p,o)}removeViewer(p){this._viewers.delete(p),this._viewerNames.delete(p)}get viewerCount(){return new Set(this._viewerNames.values()).size||this._viewers.size}takeControl(p,o){this.controlHolder=p,this.controlHolderName=o||null,this.lastInputTime=Date.now()}releaseControl(){this.controlHolder=null,this.controlHolderName=null,this.lastInputTime=null}isController(p){return this.controlHolder===p}toJSON(){return{id:this.id,title:this.title,cwd:this.cwd,createdAt:this.createdAt,mode:"agent",alive:this.alive,viewers:this.viewerCount,controlHolder:this.controlHolder,controlHolderName:this.controlHolderName,permissionMode:this.permissionMode,owner:this.owner,claudeSessionId:this.claudeSessionId,busy:this._busy,usage:this._usage}}}let _claudeSessionsCache=null,_claudeSessionsCacheTime=0;const CLAUDE_SESSIONS_CACHE_TTL=3e4;function listLocalClaudeSessions(_){const p=Date.now();if(_claudeSessionsCache&&p-_claudeSessionsCacheTime<CLAUDE_SESSIONS_CACHE_TTL){if(!_)return _claudeSessionsCache;const h=(0,external_path_.resolve)((0,external_path_.normalize)(_)).replace(/[\\/]+$/,"");return _claudeSessionsCache.filter(l=>(0,external_path_.resolve)((0,external_path_.normalize)(l.projectPath)).replace(/[\\/]+$/,"").startsWith(h))}const o=[],g=_?(0,external_path_.resolve)((0,external_path_.normalize)(_)).replace(/[\\/]+$/,""):null;try{const h=(0,external_path_.join)((0,external_os_.homedir)(),".claude","projects");if(!(0,external_fs_.existsSync)(h))return o;for(const l of(0,external_fs_.readdirSync)(h,{withFileTypes:!0})){if(!l.isDirectory())continue;const d=(0,external_path_.join)(h,l.name);try{for(const a of(0,external_fs_.readdirSync)(d)){if(!a.endsWith(".jsonl"))continue;const n=a.replace(".jsonl",""),r=(0,external_path_.join)(d,a);try{const t=(0,external_fs_.readFileSync)(r,"utf8").split(`
|
|
360
360
|
`).filter(Boolean);let e=null,c=null,s=0,i="";for(const u of t)try{const v=JSON.parse(u);if(e||(e=v),c=v,s++,!i&&v.type==="human"&&v.message?.content){const b=v.message.content;if(typeof b=="string")i=b.slice(0,120);else if(Array.isArray(b)){const w=b.find(E=>E.type==="text");w&&(i=w.text?.slice(0,120)||"")}}}catch{}let f=l.name;if(process.platform==="win32"&&/^[A-Za-z]--/.test(f)?f=f[0]+":\\"+f.slice(3).replace(/-/g,"\\"):f.startsWith("-")&&(f=f.replace(/-/g,"/")),g&&!(0,external_path_.resolve)((0,external_path_.normalize)(f)).replace(/[\\/]+$/,"").startsWith(g))continue;o.push({sessionId:n,projectDir:l.name,projectPath:f,messageCount:s,summary:i,createdAt:e?.timestamp||null,updatedAt:c?.timestamp||null})}catch{}}}catch{}}}catch{}o.sort((h,l)=>new Date(l.updatedAt||0).getTime()-new Date(h.updatedAt||0).getTime());const m=o.slice(0,50);return _claudeSessionsCache=m,_claudeSessionsCacheTime=Date.now(),m.slice(0,20)}const DEFAULT_SHELL=process.platform==="win32"?process.env.SHELL||"powershell.exe":process.env.SHELL||"/bin/bash",CONFIG_DIR=(0,external_path_.join)((0,external_os_.homedir)(),".myhi"),SESSIONS_FILE=(0,external_path_.join)(CONFIG_DIR,"sessions.json");function loadSavedConfigs(){try{return JSON.parse((0,external_fs_.readFileSync)(SESSIONS_FILE,"utf8"))}catch{return[]}}function writeSavedConfigs(_){try{(0,external_fs_.mkdirSync)(CONFIG_DIR,{recursive:!0});const p=SESSIONS_FILE+".tmp";(0,external_fs_.writeFileSync)(p,JSON.stringify(_,null,2),{mode:384}),(0,external_fs_.renameSync)(p,SESSIONS_FILE)}catch(p){console.error("[sessions] \u6301\u4E45\u5316\u5199\u5165\u5931\u8D25:",p.message)}}class Session extends external_events_.EventEmitter{constructor(p,o={}){super(),this.id=p,this.createdAt=new Date().toISOString(),this.title=o.title||DEFAULT_SHELL,this.cwd=o.cwd||process.env.MYHI_CWD||process.cwd(),this.initCmd=o.initCmd||null,this.permissionMode=o.permissionMode||"default",this.owner=o.owner||null,this.cols=o.cols||120,this.rows=o.rows||30,this._viewers=new Set,this._viewerNames=new Map,this.controlHolder=null,this.controlHolderName=null,this.lastInputTime=null;const g={...process.env};if(delete g.CLAUDECODE,delete g.CLAUDE_CODE_ENTRYPOINT,delete g.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC,o.userDir){const h=(0,external_path_.join)(o.userDir,".gitconfig");(0,external_fs_.existsSync)(h)&&(g.GIT_CONFIG_GLOBAL=h)}this._pty=external_node_pty_namespaceObject.spawn(DEFAULT_SHELL,[],{name:"xterm-256color",cols:this.cols,rows:this.rows,cwd:this.cwd,env:g,useConpty:!1}),this._scrollback="";const m=100*1024;if(this._pty.onData(h=>{this.emit("data",h),this._scrollback+=h,this._scrollback.length>m&&(this._scrollback=this._scrollback.slice(this._scrollback.length-m))}),this._pty.onExit(({exitCode:h})=>{this.exitCode=h,this.emit("exit",h)}),o.initCmd){const h=o.initCmd;let l=!1;const d=a=>{!l&&/[$>#\]]\s*$/.test(a.trimEnd())&&(l=!0,this._pty.off("data",d),setTimeout(()=>this._pty.write(h+"\r"),120))};this._pty.onData(d),setTimeout(()=>{l||(l=!0,this._pty.write(h+"\r"))},3e3)}}write(p){p!=null&&this._pty.write(p)}resize(p,o){this.cols=p,this.rows=o,this._pty.resize(p,o)}kill(){try{this._pty.kill()}catch{}}addViewer(p,o){this._viewers.add(p),o&&this._viewerNames.set(p,o)}removeViewer(p){this._viewers.delete(p),this._viewerNames.delete(p)}get viewerCount(){return new Set(this._viewerNames.values()).size||this._viewers.size}takeControl(p,o){this.controlHolder=p,this.controlHolderName=o||null,this.lastInputTime=Date.now()}releaseControl(){this.controlHolder=null,this.controlHolderName=null,this.lastInputTime=null}isController(p){return this.controlHolder===p}toJSON(){return{id:this.id,title:this.title,cwd:this.cwd,createdAt:this.createdAt,cols:this.cols,rows:this.rows,viewers:this.viewerCount,alive:this.exitCode===void 0,controlHolder:this.controlHolder,controlHolderName:this.controlHolderName,permissionMode:this.permissionMode,owner:this.owner}}}class SessionManager{constructor(){this._sessions=new Map,this._agentSessions=new Map;for(const p of loadSavedConfigs())try{if(p.mode==="agent"){if(p.claudeSessionId){const o=(0,external_crypto_.randomUUID)(),g=new AgentSession(o,{...p,resumeSessionId:p.claudeSessionId});g.on("session-id",()=>this._persist()),this._agentSessions.set(o,g),console.log(`[\u4F1A\u8BDD] \u6062\u590D Agent "${p.title}" (claude: ${p.claudeSessionId.slice(0,8)}...)`)}}else this._spawn((0,external_crypto_.randomUUID)(),p,!1)}catch(o){console.warn(`[\u4F1A\u8BDD] \u6062\u590D "${p.title}" \u5931\u8D25:`,o.message)}}_spawn(p,o,g=!0){const m=new Session(p,o);return this._sessions.set(p,m),m.on("exit",()=>{setTimeout(()=>this._sessions.delete(p),300*1e3)}),g&&this._persist(),m}create(p={}){return this._spawn((0,external_crypto_.randomUUID)(),p,!0)}createAgent(p={}){const o=(0,external_crypto_.randomUUID)(),g=new AgentSession(o,p);return this._agentSessions.set(o,g),g.on("session-id",()=>this._persist()),this._persist(),g}get(p){return this._sessions.get(p)||this._agentSessions.get(p)}list(p,o=!1){let g=[...this._sessions.values()].map(h=>({...h.toJSON(),mode:"pty"})),m=[...this._agentSessions.values()].map(h=>h.toJSON());return p&&!o&&(g=g.filter(h=>!h.owner||h.owner===p),m=m.filter(h=>!h.owner||h.owner===p)),[...g,...m]}listSessions(){return[...this._sessions.values(),...this._agentSessions.values()]}kill(p){const o=this._sessions.get(p);if(o){o.kill(),this._sessions.delete(p),this._persist();return}const g=this._agentSessions.get(p);g&&(g.kill(),this._agentSessions.delete(p),this._persist())}_persist(){const p=[...this._sessions.values()].filter(g=>g.exitCode===void 0).map(g=>({mode:"pty",title:g.title,cwd:g.cwd,initCmd:g.initCmd||void 0,permissionMode:g.permissionMode||void 0,owner:g.owner||void 0})),o=[...this._agentSessions.values()].filter(g=>g.alive).map(g=>({mode:"agent",title:g.title,cwd:g.cwd,permissionMode:g.permissionMode||void 0,owner:g.owner||void 0,userDir:g.userDir||void 0,claudeSessionId:g.claudeSessionId||void 0}));writeSavedConfigs([...p,...o])}}const roles_CONFIG_DIR=(0,external_path_.join)((0,external_os_.homedir)(),".myhi"),ROLES_FILE=(0,external_path_.join)(roles_CONFIG_DIR,"roles.json"),USERS_FILE=(0,external_path_.join)(roles_CONFIG_DIR,"users.json"),ROLE_LEVELS={admin:3,operator:2,viewer:1};let rolesConfig=null;function loadRoles(){try{if((0,external_fs_.existsSync)(ROLES_FILE))return rolesConfig=JSON.parse((0,external_fs_.readFileSync)(ROLES_FILE,"utf8")),rolesConfig}catch(_){console.warn("[\u89D2\u8272] \u52A0\u8F7D roles.json \u5931\u8D25:",_.message)}return rolesConfig=null,null}function lookupPassword(_){if(!rolesConfig?.passwords)return null;const p=rolesConfig.passwords[_];return p?{name:p.name||"\u7528\u6237",role:p.role||"viewer"}:null}function hasPermission(_,p){return(ROLE_LEVELS[_]||0)>=(ROLE_LEVELS[p]||0)}function isMultiUserMode(){return rolesConfig!==null&&rolesConfig.passwords&&Object.keys(rolesConfig.passwords).length>0}function getDefaultRole(){return rolesConfig?.defaultRole||"admin"}let usersConfig=null;function loadUsers(){try{if((0,external_fs_.existsSync)(USERS_FILE))return usersConfig=JSON.parse((0,external_fs_.readFileSync)(USERS_FILE,"utf8")),usersConfig}catch(_){console.warn("[\u7528\u6237] \u52A0\u8F7D users.json \u5931\u8D25:",_.message)}return usersConfig=null,null}function hasUsers(){return usersConfig!==null&&usersConfig.users&&Object.keys(usersConfig.users).length>0}function isExclusiveMode(){return hasUsers()&&usersConfig.exclusive===!0}function setExclusiveMode(_){usersConfig||(usersConfig={users:{}}),usersConfig.exclusive=!!_,_writeUsers()}function lookupUser(_){if(!usersConfig?.users)return null;const p=usersConfig.users[_];return p?{name:p.name||"\u7528\u6237",dir:p.dir}:null}function listUsers(){return usersConfig?.users?Object.entries(usersConfig.users).map(([_,p])=>({password:_.slice(0,2)+"*".repeat(_.length-2),name:p.name,dir:p.dir})):[]}function addUser(_,p,o){usersConfig||(usersConfig={users:{}}),usersConfig.users||(usersConfig.users={}),usersConfig.users[_]={name:p,dir:o},_writeUsers()}function removeUser(_){return!usersConfig?.users||!usersConfig.users[_]?!1:(delete usersConfig.users[_],_writeUsers(),!0)}function _writeUsers(){(0,external_fs_.mkdirSync)(roles_CONFIG_DIR,{recursive:!0}),(0,external_fs_.writeFileSync)(USERS_FILE,JSON.stringify(usersConfig,null,2),{mode:384})}const server_dirname=(0,external_path_.dirname)((0,external_url_.fileURLToPath)(import.meta.url));let PORT=parseInt(process.env.PORT,10)||12300;const HOST=process.env.HOST||"0.0.0.0",PORT_EXPLICIT=!!process.env.PORT,configDir=(0,external_path_.join)((0,external_os_.homedir)(),".myhi");(0,external_fs_.mkdirSync)(configDir,{recursive:!0});const pidFile=(0,external_path_.join)(configDir,"daemon.pid"),IS_DAEMON_CHILD=!!process.env.MYHI_DAEMON;(function _(){if(!IS_DAEMON_CHILD){try{const p=parseInt((0,external_fs_.readFileSync)(pidFile,"utf8").trim(),10);if(p&&p!==process.pid){let o=!1;try{process.kill(p,0),o=!0}catch{}if(o){console.log(`[myhi] \u68C0\u6D4B\u5230\u5DF2\u8FD0\u884C\u7684\u5B9E\u4F8B (PID ${p})\uFF0C\u6B63\u5728\u505C\u6B62...`);try{process.platform==="win32"?(0,external_child_process_namespaceObject.execSync)(`taskkill /PID ${p} /F /T`,{stdio:"pipe",timeout:5e3}):process.kill(p,"SIGTERM")}catch(h){console.warn(`[myhi] \u505C\u6B62\u65E7\u8FDB\u7A0B\u5931\u8D25: ${h.message?.split(`
|
|
361
361
|
`)[0]||h}`)}const g=Date.now()+5e3;for(;Date.now()<g;){try{process.kill(p,0)}catch{break}const h=Date.now()+200;for(;Date.now()<h;);}let m=!1;try{process.kill(p,0),m=!0}catch{}m&&(console.error(`[myhi] \u65E0\u6CD5\u505C\u6B62\u65E7\u8FDB\u7A0B (PID ${p})\uFF0C\u8BF7\u624B\u52A8\u6267\u884C\uFF1A`),console.error(" myhi stop"),console.error(` \u6216: ${process.platform==="win32"?`taskkill /PID ${p} /F`:`kill -9 ${p}`}`),process.exit(1))}}}catch{}IS_DAEMON_CHILD||(0,external_fs_.writeFileSync)(pidFile,String(process.pid))}})();function cleanupPid(){try{parseInt((0,external_fs_.readFileSync)(pidFile,"utf8").trim(),10)===process.pid&&(0,external_fs_.unlinkSync)(pidFile)}catch{}}function gracefulShutdown(){try{if(typeof manager<"u")for(const _ of manager.listSessions())try{_.kill()}catch{}}catch{}cleanupPid()}process.on("exit",cleanupPid),process.on("SIGTERM",()=>{gracefulShutdown(),process.exit(0)}),process.on("SIGINT",()=>{gracefulShutdown(),process.exit(0)}),process.on("uncaughtException",_=>{console.error("[myhi] \u672A\u6355\u83B7\u5F02\u5E38:",_.message),gracefulShutdown(),process.exit(1)}),process.on("unhandledRejection",_=>{console.error("[myhi] \u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:",_)});function readOrCreate(_,p){try{if((0,external_fs_.existsSync)(_))return(0,external_fs_.readFileSync)(_,"utf8").trim()}catch{}const o=p();return(0,external_fs_.writeFileSync)(_,o,{mode:384}),o}const TOKEN=readOrCreate((0,external_path_.join)(configDir,"token"),()=>(0,external_crypto_.randomBytes)(16).toString("hex"));let PASSWORD=readOrCreate((0,external_path_.join)(configDir,"password"),()=>String(Math.floor(1e3+Math.random()*9e3)));loadRoles(),loadUsers();let activeUser=null;const userSessions=new Map;userSessions.set(TOKEN,{role:"admin",name:"\u7BA1\u7406\u5458",permanent:!0});const SESSION_MAX_AGE=10080*60*1e3;setInterval(()=>{const _=Date.now();for(const[p,o]of userSessions)o.permanent||(!o.lastUsed||_-o.lastUsed>SESSION_MAX_AGE)&&userSessions.delete(p)},3600*1e3);function getTailscaleIP(){try{return(0,external_child_process_namespaceObject.execSync)("tailscale ip -4",{timeout:3e3,stdio:"pipe"}).toString().trim().split(`
|
|
362
|
-
`)[0]}catch{}try{if(process.platform==="win32"){const p=(0,external_child_process_namespaceObject.execSync)("route print 0.0.0.0",{timeout:3e3,stdio:"pipe"}).toString().match(/0\.0\.0\.0\s+0\.0\.0\.0\s+\S+\s+(\d+\.\d+\.\d+\.\d+)/);if(p)return p[1]}else try{const p=(0,external_child_process_namespaceObject.execSync)("ip route get 1.1.1.1",{timeout:3e3,stdio:"pipe"}).toString().match(/src\s+(\d+\.\d+\.\d+\.\d+)/);if(p)return p[1]}catch{const _=(0,external_child_process_namespaceObject.execSync)("route -n get default 2>/dev/null || route -n 2>/dev/null",{timeout:3e3,stdio:"pipe"}).toString(),p=_.match(/interface:\s*(\S+)/i)||_.match(/0\.0\.0\.0\s+(\d+\.\d+\.\d+\.\d+)/);if(p){const o=(0,external_os_.networkInterfaces)()[p[1]];if(o){const g=o.find(m=>m.family==="IPv4"&&!m.internal);if(g)return g.address}if(/^\d+\.\d+\.\d+\.\d+$/.test(p[1]))return p[1]}}}catch{}for(const _ of Object.values((0,external_os_.networkInterfaces)()))for(const p of _)if(p.family==="IPv4"&&!p.internal)return p.address;return"localhost"}function parseCookies(_=""){const p={};for(const o of _.split(";")){const g=o.indexOf("=");g>0&&(p[o.slice(0,g).trim()]=o.slice(g+1).trim())}return p}const SESSION_COOKIE="myhi_sid",COOKIE_OPTS="HttpOnly; SameSite=Strict; Path=/; Max-Age=31536000";function setSessionCookie(_,p){_.setHeader("Set-Cookie",`${SESSION_COOKIE}=${p||TOKEN}; ${COOKIE_OPTS}`)}function getSessionToken(_){return parseCookies(_)[SESSION_COOKIE]||null}function hasValidSession(_){const p=getSessionToken(_);if(p&&userSessions.has(p)){const o=userSessions.get(p);return o&&(o.lastUsed=Date.now()),!0}return!1}function getUserInfo(_){const p=getSessionToken(_);return p?userSessions.get(p):null}const app=express();app.set("trust proxy",!0),app.use((_,p,o)=>{p.setHeader("Referrer-Policy","no-referrer"),p.setHeader("X-Content-Type-Options","nosniff"),o()});const httpServer=(0,external_http_.createServer)(app),_corsAllowed=/^https?:\/\/(localhost|127\.0\.0\.1|\[::1\]|10\.\d+\.\d+\.\d+|172\.(1[6-9]|2\d|3[01])\.\d+\.\d+|192\.168\.\d+\.\d+|100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.\d+\.\d+)(:\d+)?$/,io=new Server({cors:{origin:(_,p)=>{if(!_||_corsAllowed.test(_))return p(null,!0);p(new Error("CORS \u4E0D\u5141\u8BB8\u6B64\u6765\u6E90"),!1)},credentials:!0},transports:["websocket","polling"],pingTimeout:6e4,pingInterval:25e3,upgradeTimeout:3e4,maxHttpBufferSize:5e6});io.attach(httpServer);const manager=new SessionManager,AUTO_ATTACH=process.env.MYHI_AUTO_ATTACH!=="0",_autoSpawned=new Set;function spawnLocalTerminal(_,p){if(!AUTO_ATTACH||_autoSpawned.has(_)||!process.env.DISPLAY&&process.platform==="linux")return;_autoSpawned.add(_);const o=__nccwpck_require__.ab+"attach.js",g=process.execPath,m=p||"myhi";if(process.platform==="win32"){const h=`"${g}" "${o}" ${_}`,l=(0,external_child_process_namespaceObject.spawn)("wt.exe",["-w","0","new-tab","--title",m,"--","cmd","/k",h],{detached:!0,stdio:"ignore"});l.unref(),l.on("error",d=>{console.warn("[attach] wt.exe \u5931\u8D25\uFF0C\u56DE\u9000\u5230 cmd:",d.message),(0,external_child_process_namespaceObject.spawn)("cmd.exe",["/c","start",`"${m}"`,"cmd","/k",h],{detached:!0,stdio:"ignore"}).unref()})}else if(process.platform==="darwin"){const h=`tell application "Terminal" to do script "${g} ${o} ${_}"`;(0,external_child_process_namespaceObject.spawn)("osascript",["-e",h],{detached:!0,stdio:"ignore"}).unref()}else{const h=`${g} "${o}" ${_}`;for(const[l,d]of[["gnome-terminal",["--","bash","-c",`${h}; exec bash`]],["xterm",["-title",m,"-e",h]],["konsole",["--new-tab","-e",h]]]){const a=(0,external_child_process_namespaceObject.spawn)(l,d,{detached:!0,stdio:"ignore"});a.unref(),a.on("error",()=>{});break}}}const uploadDir=(0,external_path_.join)(configDir,"uploads");(0,external_fs_.mkdirSync)(uploadDir,{recursive:!0});function checkAuth(_,p,o){const g=_.query.token;if(g&&(g===TOKEN||userSessions.has(g))){const m=userSessions.get(g);if(m?.onetime){userSessions.delete(g);const l=(0,external_crypto_.randomBytes)(16).toString("hex");userSessions.set(l,{role:m.role,name:m.name,lastUsed:Date.now()}),setSessionCookie(p,l)}else setSessionCookie(p,g===TOKEN?TOKEN:g);const h=_.path+(Object.keys(_.query).filter(l=>l!=="token").length?"?"+new URLSearchParams(Object.fromEntries(Object.entries(_.query).filter(([l])=>l!=="token"))):"");return p.redirect(h)}if(hasValidSession(_.headers.cookie)){if(isExclusiveMode()&&activeUser){const m=getSessionToken(_.headers.cookie);if(m!==TOKEN){const h=userSessions.get(m);if(!h||h.name!==activeUser.name)return p.setHeader("Set-Cookie",`${SESSION_COOKIE}=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0`),p.redirect("/login")}}return o()}p.redirect("/login")}const _require=(0,external_module_namespaceObject.createRequire)(import.meta.url);function pkgDirSafe(_){try{return(0,external_path_.dirname)(_require.resolve(`${_}/package.json`))}catch{return null}}const libDir=(0,external_path_.join)(server_dirname,"lib"),staticOpts={maxAge:864e5};app.use("/lib/xterm",express.static(pkgDirSafe("xterm")||(0,external_path_.join)(libDir,"xterm"),staticOpts)),app.use("/lib/xterm-fit",express.static(pkgDirSafe("xterm-addon-fit")||(0,external_path_.join)(libDir,"xterm-fit"),staticOpts)),app.use("/lib/xterm-links",express.static(pkgDirSafe("xterm-addon-web-links")||(0,external_path_.join)(libDir,"xterm-links"),staticOpts));const _loginAttempts=new Map,LOGIN_MAX_ATTEMPTS=5,LOGIN_LOCKOUT_MS=300*1e3;function checkLoginRate(_){const p=Date.now(),o=_loginAttempts.get(_);return!o||p>o.resetAt?!0:o.count<LOGIN_MAX_ATTEMPTS}function recordLoginFailure(_){const p=Date.now(),o=_loginAttempts.get(_);!o||p>o.resetAt?_loginAttempts.set(_,{count:1,resetAt:p+LOGIN_LOCKOUT_MS}):o.count++}function clearLoginFailures(_){_loginAttempts.delete(_)}setInterval(()=>{const _=Date.now();for(const[p,o]of _loginAttempts)_>o.resetAt&&_loginAttempts.delete(p)},600*1e3),app.get("/login",(_,p)=>{p.sendFile(__nccwpck_require__.ab+"login.html")}),app.post("/login",express.urlencoded({extended:!1}),(_,p)=>{const o=_.ip;if(!checkLoginRate(o))return p.redirect("/login?error=locked");loadRoles(),loadUsers();const g=_.body?.password,m=_.query.from,h=m&&m.startsWith("/")&&!m.startsWith("//")?m:"/";if(hasUsers()){const d=lookupUser(g);if(!d)return recordLoginFailure(o),p.redirect("/login?error=1");if(isExclusiveMode()&&activeUser&&activeUser.name!==d.name)return p.redirect("/login?error=occupied&user="+encodeURIComponent(activeUser.name));clearLoginFailures(o);const a=(0,external_crypto_.randomBytes)(16).toString("hex");return userSessions.set(a,{role:"operator",name:d.name,dir:d.dir,lastUsed:Date.now()}),isExclusiveMode()&&(activeUser?activeUser.tokens.add(a):activeUser={name:d.name,dir:d.dir,tokens:new Set([a]),loginAt:Date.now()}),setSessionCookie(p,a),console.log(`[\u767B\u5F55] ${d.name} \u767B\u5F55\uFF08${isExclusiveMode()?"\u72EC\u5360":"\u5171\u4EAB"}\u6A21\u5F0F\uFF09`),p.redirect(h)}const l=lookupPassword(g);if(l){clearLoginFailures(o);const d=(0,external_crypto_.randomBytes)(16).toString("hex");return userSessions.set(d,{role:l.role,name:l.name,lastUsed:Date.now()}),setSessionCookie(p,d),p.redirect(h)}if(g===PASSWORD){clearLoginFailures(o);const d=(0,external_crypto_.randomBytes)(16).toString("hex");return userSessions.set(d,{role:"admin",name:"\u7BA1\u7406\u5458",lastUsed:Date.now()}),setSessionCookie(p,d),p.redirect(h)}recordLoginFailure(o),p.redirect("/login?error=1")}),app.post("/logout",(_,p)=>{const o=getSessionToken(_.headers.cookie);o&&(activeUser&&activeUser.tokens.has(o)&&(activeUser.tokens.delete(o),activeUser.tokens.size===0?(console.log(`[\u9000\u51FA] ${activeUser.name} \u5168\u90E8\u8BBE\u5907\u9000\u51FA\uFF0C\u91CA\u653E\u767B\u5F55\u72B6\u6001\uFF08\u4F1A\u8BDD\u4FDD\u7559\uFF09`),activeUser=null):console.log(`[\u9000\u51FA] ${activeUser.name} \u4E00\u4E2A\u8BBE\u5907\u9000\u51FA\uFF08\u5269\u4F59 ${activeUser.tokens.size} \u4E2A\uFF09`)),userSessions.delete(o)),p.setHeader("Set-Cookie",`${SESSION_COOKIE}=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0`),p.redirect("/login")}),app.get("/api/status",(_,p)=>{if(isExclusiveMode()&&activeUser)return p.json({occupied:!0,userName:activeUser.name,releasing:!!_exclusiveReleaseTimer});if(hasUsers()&&!isExclusiveMode()){const o=new Set;for(const[,g]of io.sockets.sockets){if(!g.data.userName||g.data.userName==="\u7BA1\u7406\u5458")continue;const m=g.handshake.headers.cookie;m&&!hasValidSession(m)||o.add(g.data.userName)}return p.json({occupied:!1,onlineUsers:[...o]})}p.json({occupied:!1})}),app.get("/admin",(_,p)=>p.sendFile(__nccwpck_require__.ab+"admin.html")),app.get("/",checkAuth,(_,p)=>p.sendFile(__nccwpck_require__.ab+"index.html")),app.get("/terminal/:id",checkAuth,(_,p)=>p.sendFile(__nccwpck_require__.ab+"chat.html")),app.get("/terminal-raw/:id",checkAuth,(_,p)=>p.sendFile(__nccwpck_require__.ab+"terminal.html")),app.use("/lib",express.static(__nccwpck_require__.ab+"lib",staticOpts)),app.get("/api/sessions",checkAuth,(_,p)=>{const o=getUserInfo(_.headers.cookie);p.json(manager.list(o?.name,o?.role==="admin"))}),app.get("/api/usage",(_,p)=>{try{const o=manager.listSessions().filter(m=>m.mode==="agent"&&m.alive).map(m=>({id:m.id,title:m.title,owner:m.owner,usage:m._usage}));let g=null;for(const m of o)if(m.usage?.rateLimitInfo){g=m.usage.rateLimitInfo;break}p.json({rateLimit:g,activeSessions:o})}catch(o){p.status(500).json({error:o.message})}}),app.post("/api/sessions",checkAuth,express.json(),(_,p)=>{const o=getUserInfo(_.headers.cookie),g=manager.create({..._.body,owner:o?.name,userDir:o?.dir});p.status(201).json(g.toJSON())}),app.delete("/api/sessions/:id",checkAuth,(_,p)=>{manager.kill(_.params.id),p.status(204).end()}),app.get("/api/claude-sessions",checkAuth,(_,p)=>{const o=getUserInfo(_.headers.cookie);p.json(listLocalClaudeSessions(o?.dir))}),app.get("/api/dirs",checkAuth,(_,p)=>{const o=getUserInfo(_.headers.cookie);if(!o||o.role!=="admin")return p.status(403).json({error:"\u6CA1\u6709\u76EE\u5F55\u6D4F\u89C8\u6743\u9650"});const g=o.dir?(0,external_path_.resolve)((0,external_path_.normalize)(o.dir)):null,m=process.platform==="win32"?"\\":"/";let h=_.query.path||g||process.env.MYHI_CWD||(0,external_os_.homedir)(),l=(0,external_path_.resolve)((0,external_path_.normalize)(h)).replace(/[/\\]+$/,"");process.platform==="win32"&&/^[A-Za-z]:$/.test(l)&&(l+="\\"),g&&!l.startsWith(g)&&(l=g);try{const d=(0,external_fs_.readdirSync)(l,{withFileTypes:!0}).filter(r=>r.isDirectory()&&!r.name.startsWith(".")).map(r=>({name:r.name,path:l.replace(/[/\\]+$/,"")+m+r.name})).sort((r,t)=>r.name.localeCompare(t.name)),a=(0,external_path_.join)(l,".."),n=!g||(0,external_path_.resolve)((0,external_path_.normalize)(a)).startsWith(g);p.json({current:l,parent:n&&a!==l?a:null,dirs:d})}catch(d){p.status(400).json({error:d.message})}}),app.get("/api/me",checkAuth,(_,p)=>{const o=getUserInfo(_.headers.cookie);p.json({name:o?.name||"\u7528\u6237",exclusive:isExclusiveMode(),hasUsers:hasUsers(),dir:o?.dir||null})});function getPkgVersion(){for(const _ of["../package.json","./package.json","package.json"])try{const p=JSON.parse((0,external_fs_.readFileSync)((0,external_path_.join)(server_dirname,_),"utf8")).version;if(p&&p!=="0.0.0")return p}catch{}return"0.0.0"}const PKG_VERSION=getPkgVersion();let _latestVersion=null,_lastVersionCheck=0;const VERSION_CHECK_INTERVAL=600*1e3;async function checkLatestVersion(){const _=Date.now();if(_latestVersion&&_-_lastVersionCheck<VERSION_CHECK_INTERVAL)return _latestVersion;try{const p=await fetch("https://registry.npmjs.org/@wendongfly/myhi/latest",{headers:{Accept:"application/json"},signal:AbortSignal.timeout(8e3)});p.ok&&(_latestVersion=(await p.json()).version,_lastVersionCheck=_)}catch{}return _latestVersion}app.get("/api/version",async(_,p)=>{const o=getPkgVersion(),g=await checkLatestVersion();p.json({current:o,latest:g||o,updateAvailable:g&&g!==o})});const _adminTokens=new Set,SERVER_START_TIME=Date.now();function checkAdminAuth(_,p,o){const g=_.headers.authorization;if(g&&g.startsWith("Bearer ")&&_adminTokens.has(g.slice(7)))return o();p.status(401).json({error:"\u672A\u6388\u6743"})}app.post("/api/admin/login",express.json(),(_,p)=>{const{password:o}=_.body||{};if(o===PASSWORD){const g=(0,external_crypto_.randomBytes)(16).toString("hex");return _adminTokens.add(g),p.json({ok:!0,token:g})}p.json({ok:!1,error:"\u5BC6\u7801\u9519\u8BEF"})}),app.get("/api/admin/status",checkAdminAuth,async(_,p)=>{const o=await checkLatestVersion();loadUsers();const g=[],m=new Set;for(const[,l]of io.sockets.sockets)!l.data.userName||m.has(l.data.userName)||(m.add(l.data.userName),g.push({name:l.data.userName,role:l.data.role||"viewer"}));const h=manager.listSessions().filter(l=>l.alive!==!1).map(l=>({id:l.id,title:l.title,mode:l.mode||"pty",owner:l.owner,viewers:l.viewerCount||0}));p.json({version:{current:getPkgVersion(),latest:o||getPkgVersion(),updateAvailable:o&&o!==getPkgVersion()},onlineUsers:g,sessions:h,users:listUsers(),uptime:Math.floor((Date.now()-SERVER_START_TIME)/1e3)})}),app.post("/api/admin/upgrade",checkAdminAuth,(_,p)=>{process.send?(process.send({type:"upgrade"}),p.json({ok:!0,message:"\u5347\u7EA7\u4E2D\uFF0C\u670D\u52A1\u5C06\u81EA\u52A8\u91CD\u542F..."})):(p.json({ok:!0,message:"\u5347\u7EA7\u4E2D..."}),setTimeout(()=>{try{const o=process.platform==="win32"?"npm.cmd install -g @wendongfly/myhi@latest":"sudo npm install -g @wendongfly/myhi@latest 2>&1 || npm install -g @wendongfly/myhi@latest";(0,external_child_process_namespaceObject.execSync)(o,{encoding:"utf8",timeout:12e4}),process.exit(0)}catch(o){console.error("[myhi] \u5347\u7EA7\u5931\u8D25:",o.message)}},500))}),app.post("/api/admin/restart",checkAdminAuth,(_,p)=>{p.json({ok:!0}),setTimeout(()=>{process.send?process.send({type:"restart"}):process.exit(0)},300)}),app.post("/api/admin/user",checkAdminAuth,express.json(),(_,p)=>{const{password:o,name:g,dir:m}=_.body||{};if(!o||!g||!m)return p.json({ok:!1,error:"\u5BC6\u7801\u3001\u540D\u79F0\u3001\u76EE\u5F55\u90FD\u5FC5\u586B"});loadUsers(),addUser(o,g,m);try{(0,external_fs_.mkdirSync)(m,{recursive:!0});const h=(0,external_path_.join)(m,".gitconfig"),l=(0,external_path_.join)(m,".git-credentials");(0,external_fs_.existsSync)(h)||(0,external_fs_.writeFileSync)(h,`[credential]
|
|
362
|
+
`)[0]}catch{}try{if(process.platform==="win32"){const p=(0,external_child_process_namespaceObject.execSync)("route print 0.0.0.0",{timeout:3e3,stdio:"pipe"}).toString().match(/0\.0\.0\.0\s+0\.0\.0\.0\s+\S+\s+(\d+\.\d+\.\d+\.\d+)/);if(p)return p[1]}else try{const p=(0,external_child_process_namespaceObject.execSync)("ip route get 1.1.1.1",{timeout:3e3,stdio:"pipe"}).toString().match(/src\s+(\d+\.\d+\.\d+\.\d+)/);if(p)return p[1]}catch{const _=(0,external_child_process_namespaceObject.execSync)("route -n get default 2>/dev/null || route -n 2>/dev/null",{timeout:3e3,stdio:"pipe"}).toString(),p=_.match(/interface:\s*(\S+)/i)||_.match(/0\.0\.0\.0\s+(\d+\.\d+\.\d+\.\d+)/);if(p){const o=(0,external_os_.networkInterfaces)()[p[1]];if(o){const g=o.find(m=>m.family==="IPv4"&&!m.internal);if(g)return g.address}if(/^\d+\.\d+\.\d+\.\d+$/.test(p[1]))return p[1]}}}catch{}for(const _ of Object.values((0,external_os_.networkInterfaces)()))for(const p of _)if(p.family==="IPv4"&&!p.internal)return p.address;return"localhost"}function parseCookies(_=""){const p={};for(const o of _.split(";")){const g=o.indexOf("=");g>0&&(p[o.slice(0,g).trim()]=o.slice(g+1).trim())}return p}const SESSION_COOKIE="myhi_sid",COOKIE_OPTS="HttpOnly; SameSite=Strict; Path=/; Max-Age=31536000";function setSessionCookie(_,p){_.setHeader("Set-Cookie",`${SESSION_COOKIE}=${p||TOKEN}; ${COOKIE_OPTS}`)}function getSessionToken(_){return parseCookies(_)[SESSION_COOKIE]||null}function hasValidSession(_){const p=getSessionToken(_);if(p&&userSessions.has(p)){const o=userSessions.get(p);return o&&(o.lastUsed=Date.now()),!0}return!1}function getUserInfo(_){const p=getSessionToken(_);return p?userSessions.get(p):null}const app=express();app.set("trust proxy",!0),app.use((_,p,o)=>{p.setHeader("Referrer-Policy","no-referrer"),p.setHeader("X-Content-Type-Options","nosniff"),o()});const httpServer=(0,external_http_.createServer)(app),_corsAllowed=/^https?:\/\/(localhost|127\.0\.0\.1|\[::1\]|10\.\d+\.\d+\.\d+|172\.(1[6-9]|2\d|3[01])\.\d+\.\d+|192\.168\.\d+\.\d+|100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.\d+\.\d+)(:\d+)?$/,io=new Server({cors:{origin:(_,p)=>{if(!_||_corsAllowed.test(_))return p(null,!0);p(new Error("CORS \u4E0D\u5141\u8BB8\u6B64\u6765\u6E90"),!1)},credentials:!0},transports:["websocket","polling"],pingTimeout:6e4,pingInterval:25e3,upgradeTimeout:3e4,maxHttpBufferSize:5e6});io.attach(httpServer);const manager=new SessionManager,AUTO_ATTACH=process.env.MYHI_AUTO_ATTACH!=="0",_autoSpawned=new Set;function spawnLocalTerminal(_,p){if(!AUTO_ATTACH||_autoSpawned.has(_)||!process.env.DISPLAY&&process.platform==="linux")return;_autoSpawned.add(_);const o=__nccwpck_require__.ab+"attach.js",g=process.execPath,m=p||"myhi";if(process.platform==="win32"){const h=`"${g}" "${o}" ${_}`,l=(0,external_child_process_namespaceObject.spawn)("wt.exe",["-w","0","new-tab","--title",m,"--","cmd","/k",h],{detached:!0,stdio:"ignore"});l.unref(),l.on("error",d=>{console.warn("[attach] wt.exe \u5931\u8D25\uFF0C\u56DE\u9000\u5230 cmd:",d.message),(0,external_child_process_namespaceObject.spawn)("cmd.exe",["/c","start",`"${m}"`,"cmd","/k",h],{detached:!0,stdio:"ignore"}).unref()})}else if(process.platform==="darwin"){const h=`tell application "Terminal" to do script "${g} ${o} ${_}"`;(0,external_child_process_namespaceObject.spawn)("osascript",["-e",h],{detached:!0,stdio:"ignore"}).unref()}else{const h=`${g} "${o}" ${_}`;for(const[l,d]of[["gnome-terminal",["--","bash","-c",`${h}; exec bash`]],["xterm",["-title",m,"-e",h]],["konsole",["--new-tab","-e",h]]]){const a=(0,external_child_process_namespaceObject.spawn)(l,d,{detached:!0,stdio:"ignore"});a.unref(),a.on("error",()=>{});break}}}const uploadDir=(0,external_path_.join)(configDir,"uploads");(0,external_fs_.mkdirSync)(uploadDir,{recursive:!0});function checkAuth(_,p,o){const g=_.query.token;if(g&&(g===TOKEN||userSessions.has(g))){const m=userSessions.get(g);if(m?.onetime){userSessions.delete(g);const l=(0,external_crypto_.randomBytes)(16).toString("hex");userSessions.set(l,{role:m.role,name:m.name,lastUsed:Date.now()}),setSessionCookie(p,l)}else setSessionCookie(p,g===TOKEN?TOKEN:g);const h=_.path+(Object.keys(_.query).filter(l=>l!=="token").length?"?"+new URLSearchParams(Object.fromEntries(Object.entries(_.query).filter(([l])=>l!=="token"))):"");return p.redirect(h)}if(hasValidSession(_.headers.cookie)){if(isExclusiveMode()&&activeUser){const m=getSessionToken(_.headers.cookie);if(m!==TOKEN){const h=userSessions.get(m);if(!h||h.name!==activeUser.name)return p.setHeader("Set-Cookie",`${SESSION_COOKIE}=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0`),p.redirect("/login")}}return o()}p.redirect("/login")}const _require=(0,external_module_namespaceObject.createRequire)(import.meta.url);function pkgDirSafe(_){try{return(0,external_path_.dirname)(_require.resolve(`${_}/package.json`))}catch{return null}}const libDir=(0,external_path_.join)(server_dirname,"lib"),staticOpts={maxAge:864e5};app.use("/lib/xterm",express.static(pkgDirSafe("xterm")||(0,external_path_.join)(libDir,"xterm"),staticOpts)),app.use("/lib/xterm-fit",express.static(pkgDirSafe("xterm-addon-fit")||(0,external_path_.join)(libDir,"xterm-fit"),staticOpts)),app.use("/lib/xterm-links",express.static(pkgDirSafe("xterm-addon-web-links")||(0,external_path_.join)(libDir,"xterm-links"),staticOpts));const _loginAttempts=new Map,LOGIN_MAX_ATTEMPTS=5,LOGIN_LOCKOUT_MS=300*1e3;function checkLoginRate(_){const p=Date.now(),o=_loginAttempts.get(_);return!o||p>o.resetAt?!0:o.count<LOGIN_MAX_ATTEMPTS}function recordLoginFailure(_){const p=Date.now(),o=_loginAttempts.get(_);!o||p>o.resetAt?_loginAttempts.set(_,{count:1,resetAt:p+LOGIN_LOCKOUT_MS}):o.count++}function clearLoginFailures(_){_loginAttempts.delete(_)}setInterval(()=>{const _=Date.now();for(const[p,o]of _loginAttempts)_>o.resetAt&&_loginAttempts.delete(p)},600*1e3),app.get("/login",(_,p)=>{p.sendFile(__nccwpck_require__.ab+"login.html")}),app.post("/login",express.urlencoded({extended:!1}),(_,p)=>{const o=_.ip;if(!checkLoginRate(o))return p.redirect("/login?error=locked");loadRoles(),loadUsers();const g=_.body?.password,m=_.query.from,h=m&&m.startsWith("/")&&!m.startsWith("//")?m:"/";if(hasUsers()){const d=lookupUser(g);if(!d)return recordLoginFailure(o),p.redirect("/login?error=1");if(isExclusiveMode()&&activeUser&&activeUser.name!==d.name)return p.redirect("/login?error=occupied&user="+encodeURIComponent(activeUser.name));clearLoginFailures(o);const a=(0,external_crypto_.randomBytes)(16).toString("hex");return userSessions.set(a,{role:"operator",name:d.name,dir:d.dir,lastUsed:Date.now()}),isExclusiveMode()&&(activeUser?activeUser.tokens.add(a):activeUser={name:d.name,dir:d.dir,tokens:new Set([a]),loginAt:Date.now()}),setSessionCookie(p,a),console.log(`[\u767B\u5F55] ${d.name} \u767B\u5F55\uFF08${isExclusiveMode()?"\u72EC\u5360":"\u5171\u4EAB"}\u6A21\u5F0F\uFF09`),p.redirect(h)}const l=lookupPassword(g);if(l){clearLoginFailures(o);const d=(0,external_crypto_.randomBytes)(16).toString("hex");return userSessions.set(d,{role:l.role,name:l.name,lastUsed:Date.now()}),setSessionCookie(p,d),p.redirect(h)}if(g===PASSWORD){clearLoginFailures(o);const d=(0,external_crypto_.randomBytes)(16).toString("hex");return userSessions.set(d,{role:"admin",name:"\u7BA1\u7406\u5458",lastUsed:Date.now()}),setSessionCookie(p,d),p.redirect(h)}recordLoginFailure(o),p.redirect("/login?error=1")}),app.post("/logout",(_,p)=>{const o=getSessionToken(_.headers.cookie);o&&(activeUser&&activeUser.tokens.has(o)&&(activeUser.tokens.delete(o),activeUser.tokens.size===0?(console.log(`[\u9000\u51FA] ${activeUser.name} \u5168\u90E8\u8BBE\u5907\u9000\u51FA\uFF0C\u91CA\u653E\u767B\u5F55\u72B6\u6001\uFF08\u4F1A\u8BDD\u4FDD\u7559\uFF09`),activeUser=null):console.log(`[\u9000\u51FA] ${activeUser.name} \u4E00\u4E2A\u8BBE\u5907\u9000\u51FA\uFF08\u5269\u4F59 ${activeUser.tokens.size} \u4E2A\uFF09`)),userSessions.delete(o)),p.setHeader("Set-Cookie",`${SESSION_COOKIE}=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0`),p.redirect("/login")}),app.get("/api/status",(_,p)=>{if(isExclusiveMode()&&activeUser)return p.json({occupied:!0,userName:activeUser.name,releasing:!!_exclusiveReleaseTimer});if(hasUsers()&&!isExclusiveMode()){const o=new Set;for(const[,g]of io.sockets.sockets){if(!g.data.userName||g.data.userName==="\u7BA1\u7406\u5458")continue;const m=g.handshake.headers.cookie;m&&!hasValidSession(m)||o.add(g.data.userName)}return p.json({occupied:!1,onlineUsers:[...o]})}p.json({occupied:!1})}),app.get("/admin",(_,p)=>p.sendFile(__nccwpck_require__.ab+"admin.html")),app.get("/",checkAuth,(_,p)=>p.sendFile(__nccwpck_require__.ab+"index.html")),app.get("/terminal/:id",checkAuth,(_,p)=>p.sendFile(__nccwpck_require__.ab+"chat.html")),app.get("/terminal-raw/:id",checkAuth,(_,p)=>p.sendFile(__nccwpck_require__.ab+"terminal.html")),app.use("/lib",express.static(__nccwpck_require__.ab+"lib",staticOpts)),app.get("/api/sessions",checkAuth,(_,p)=>{const o=getUserInfo(_.headers.cookie);p.json(manager.list(o?.name,o?.role==="admin"))}),app.get("/api/usage",(_,p)=>{try{const o=manager.listSessions().filter(m=>m.mode==="agent"&&m.alive).map(m=>({id:m.id,title:m.title,owner:m.owner,usage:m._usage}));let g=null;for(const m of o)if(m.usage?.rateLimitInfo){g=m.usage.rateLimitInfo;break}p.json({rateLimit:g,activeSessions:o})}catch(o){p.status(500).json({error:o.message})}}),app.post("/api/sessions",checkAuth,express.json(),(_,p)=>{const o=getUserInfo(_.headers.cookie),g=manager.create({..._.body,owner:o?.name,userDir:o?.dir});p.status(201).json(g.toJSON())}),app.delete("/api/sessions/:id",checkAuth,(_,p)=>{manager.kill(_.params.id),p.status(204).end()}),app.get("/api/claude-sessions",checkAuth,(_,p)=>{const o=getUserInfo(_.headers.cookie);p.json(listLocalClaudeSessions(o?.dir))}),app.get("/api/dirs",checkAuth,(_,p)=>{const o=getUserInfo(_.headers.cookie);if(!o||o.role!=="admin")return p.status(403).json({error:"\u6CA1\u6709\u76EE\u5F55\u6D4F\u89C8\u6743\u9650"});const g=o.dir?(0,external_path_.resolve)((0,external_path_.normalize)(o.dir)):null,m=process.platform==="win32"?"\\":"/";let h=_.query.path||g||process.env.MYHI_CWD||(0,external_os_.homedir)(),l=(0,external_path_.resolve)((0,external_path_.normalize)(h)).replace(/[/\\]+$/,"");process.platform==="win32"&&/^[A-Za-z]:$/.test(l)&&(l+="\\"),g&&!l.startsWith(g)&&(l=g);try{const d=(0,external_fs_.readdirSync)(l,{withFileTypes:!0}).filter(r=>r.isDirectory()&&!r.name.startsWith(".")).map(r=>({name:r.name,path:l.replace(/[/\\]+$/,"")+m+r.name})).sort((r,t)=>r.name.localeCompare(t.name)),a=(0,external_path_.join)(l,".."),n=!g||(0,external_path_.resolve)((0,external_path_.normalize)(a)).startsWith(g);p.json({current:l,parent:n&&a!==l?a:null,dirs:d})}catch(d){p.status(400).json({error:d.message})}}),app.get("/api/me",checkAuth,(_,p)=>{const o=getUserInfo(_.headers.cookie);p.json({name:o?.name||"\u7528\u6237",exclusive:isExclusiveMode(),hasUsers:hasUsers(),dir:o?.dir||null})});function getPkgVersion(){for(const _ of["../package.json","./package.json","package.json"])try{const p=JSON.parse((0,external_fs_.readFileSync)((0,external_path_.join)(server_dirname,_),"utf8")).version;if(p&&p!=="0.0.0")return p}catch{}return"0.0.0"}const PKG_VERSION=getPkgVersion();let _latestVersion=null,_lastVersionCheck=0;const VERSION_CHECK_INTERVAL=600*1e3;async function checkLatestVersion(){const _=Date.now();if(_latestVersion&&_-_lastVersionCheck<VERSION_CHECK_INTERVAL)return _latestVersion;try{const p=await fetch("https://registry.npmjs.org/@wendongfly/myhi/latest",{headers:{Accept:"application/json"},signal:AbortSignal.timeout(8e3)});p.ok&&(_latestVersion=(await p.json()).version,_lastVersionCheck=_)}catch{}return _latestVersion}app.get("/api/version",async(_,p)=>{const o=getPkgVersion(),g=await checkLatestVersion();p.json({current:o,latest:g||o,updateAvailable:g&&g!==o})}),app.post("/api/git/push",checkAuth,express.json(),async(_,p)=>{const{sessionId:o,url:g,username:m,password:h,message:l,branch:d}=_.body||{};if(!o||!l)return p.status(400).json({ok:!1,error:"\u7F3A\u5C11\u53C2\u6570"});const a=manager.get(o);if(!a)return p.status(404).json({ok:!1,error:"\u4F1A\u8BDD\u4E0D\u5B58\u5728"});const n=a.cwd||process.cwd(),r=process.platform==="win32",t="git";function e(c,s={}){return new Promise((i,f)=>{const u={...process.env,GIT_TERMINAL_PROMPT:"0"},v=(0,external_path_.join)(n,".gitconfig");(0,external_fs_.existsSync)(v)&&(u.GIT_CONFIG_GLOBAL=v);const b=(0,external_child_process_namespaceObject.spawn)(t,c,{cwd:n,env:u,timeout:6e4});let w="",E="";b.stdout.on("data",A=>w+=A),b.stderr.on("data",A=>E+=A),b.on("close",A=>A===0?i(w.trim()):f(new Error(E.trim()||`exit ${A}`))),b.on("error",f)})}try{try{await e(["rev-parse","--git-dir"])}catch{await e(["init"])}if(g&&m&&h){const f=new URL(g);f.username=encodeURIComponent(m),f.password=encodeURIComponent(h);const u=f.toString(),v=(0,external_path_.join)(n,".git-credentials");(0,external_fs_.writeFileSync)(v,u+`
|
|
363
|
+
`,{mode:384});const b=(0,external_path_.join)(n,".gitconfig"),E=`[credential]
|
|
364
|
+
helper = store --file ${v.replace(/\\/g,"/")}
|
|
365
|
+
[user]
|
|
366
|
+
name = ${m}
|
|
367
|
+
email = ${m}@myhi
|
|
368
|
+
`;(0,external_fs_.writeFileSync)(b,E,{mode:384});try{await e(["remote","remove","origin"])}catch{}await e(["remote","add","origin",g])}if(await e(["add","-A"]),!await e(["status","--porcelain"]))return p.json({ok:!0,message:"\u6CA1\u6709\u9700\u8981\u63D0\u4EA4\u7684\u53D8\u66F4",pushed:!1});await e(["commit","-m",l]);const s=d||"main";try{await e(["push","-u","origin",s])}catch(f){if(f.message.includes("src refspec")||f.message.includes("does not match"))await e(["checkout","-b",s]),await e(["push","-u","origin",s]);else throw f}const i=await e(["log","--oneline","-1"]);p.json({ok:!0,message:`\u5DF2\u63D0\u4EA4\u5E76\u63A8\u9001: ${i}`,pushed:!0})}catch(c){p.json({ok:!1,error:c.message})}}),app.get("/api/git/info",checkAuth,(_,p)=>{const o=_.query.sessionId,g=o?manager.get(o):null;if(!g)return p.json({});const m=g.cwd||process.cwd();try{const h=(0,external_child_process_namespaceObject.execSync)("git remote get-url origin",{cwd:m,encoding:"utf8",timeout:5e3}).trim(),l=(0,external_child_process_namespaceObject.execSync)("git branch --show-current",{cwd:m,encoding:"utf8",timeout:5e3}).trim();let d="";const a=(0,external_path_.join)(m,".gitconfig");if((0,external_fs_.existsSync)(a)){const r=(0,external_fs_.readFileSync)(a,"utf8").match(/name\s*=\s*(.+)/);r&&(d=r[1].trim())}p.json({url:h,branch:l||"main",username:d})}catch{p.json({})}});const _adminTokens=new Set,SERVER_START_TIME=Date.now();function checkAdminAuth(_,p,o){const g=_.headers.authorization;if(g&&g.startsWith("Bearer ")&&_adminTokens.has(g.slice(7)))return o();p.status(401).json({error:"\u672A\u6388\u6743"})}app.post("/api/admin/login",express.json(),(_,p)=>{const{password:o}=_.body||{};if(o===PASSWORD){const g=(0,external_crypto_.randomBytes)(16).toString("hex");return _adminTokens.add(g),p.json({ok:!0,token:g})}p.json({ok:!1,error:"\u5BC6\u7801\u9519\u8BEF"})}),app.get("/api/admin/status",checkAdminAuth,async(_,p)=>{const o=await checkLatestVersion();loadUsers();const g=[],m=new Set;for(const[,l]of io.sockets.sockets)!l.data.userName||m.has(l.data.userName)||(m.add(l.data.userName),g.push({name:l.data.userName,role:l.data.role||"viewer"}));const h=manager.listSessions().filter(l=>l.alive!==!1).map(l=>({id:l.id,title:l.title,mode:l.mode||"pty",owner:l.owner,viewers:l.viewerCount||0}));p.json({version:{current:getPkgVersion(),latest:o||getPkgVersion(),updateAvailable:o&&o!==getPkgVersion()},onlineUsers:g,sessions:h,users:listUsers(),uptime:Math.floor((Date.now()-SERVER_START_TIME)/1e3)})}),app.post("/api/admin/upgrade",checkAdminAuth,(_,p)=>{process.send?(process.send({type:"upgrade"}),p.json({ok:!0,message:"\u5347\u7EA7\u4E2D\uFF0C\u670D\u52A1\u5C06\u81EA\u52A8\u91CD\u542F..."})):(p.json({ok:!0,message:"\u5347\u7EA7\u4E2D..."}),setTimeout(()=>{try{const o=process.platform==="win32"?"npm.cmd install -g @wendongfly/myhi@latest":"sudo npm install -g @wendongfly/myhi@latest 2>&1 || npm install -g @wendongfly/myhi@latest";(0,external_child_process_namespaceObject.execSync)(o,{encoding:"utf8",timeout:12e4}),process.exit(0)}catch(o){console.error("[myhi] \u5347\u7EA7\u5931\u8D25:",o.message)}},500))}),app.post("/api/admin/restart",checkAdminAuth,(_,p)=>{p.json({ok:!0}),setTimeout(()=>{process.send?process.send({type:"restart"}):process.exit(0)},300)}),app.post("/api/admin/user",checkAdminAuth,express.json(),(_,p)=>{const{password:o,name:g,dir:m}=_.body||{};if(!o||!g||!m)return p.json({ok:!1,error:"\u5BC6\u7801\u3001\u540D\u79F0\u3001\u76EE\u5F55\u90FD\u5FC5\u586B"});loadUsers(),addUser(o,g,m);try{(0,external_fs_.mkdirSync)(m,{recursive:!0});const h=(0,external_path_.join)(m,".gitconfig"),l=(0,external_path_.join)(m,".git-credentials");(0,external_fs_.existsSync)(h)||(0,external_fs_.writeFileSync)(h,`[credential]
|
|
363
369
|
helper = store --file ${l.replace(/\\/g,"/")}
|
|
364
370
|
[user]
|
|
365
371
|
name = ${g}
|
package/dist/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"module","version":"1.0.
|
|
1
|
+
{"type":"module","version":"1.0.115"}
|
package/package.json
CHANGED