@wendongfly/myhi 1.3.191 → 1.3.193

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/dist/admin.html CHANGED
@@ -5,61 +5,73 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>myhi 管理</title>
7
7
  <script src="/lib/machine.js"></script>
8
+ <script src="/theme.js"></script>
9
+ <script>
10
+ // 主题与其他页面一致:浏览器选过的优先,否则服务端默认(新装浅色、老机器深色)
11
+ (function(){try{var t=localStorage.getItem('myhi_theme')||window.MYHI_DEFAULT_THEME||'dark';
12
+ if(t==='system') t=matchMedia('(prefers-color-scheme: light)').matches?'light':'dark';
13
+ if(t!=='dark') document.documentElement.setAttribute('data-theme',t);}catch(e){}})();
14
+ </script>
8
15
  <style>
16
+ :root { --bg:#0d1117; --surface:#161b22; --text:#e6edf3; --text2:#b1bac4; --text3:#8b949e; --muted:#6e7681;
17
+ --line:rgba(255,255,255,0.06); --border:rgba(255,255,255,0.12); --border2:#30363d; --accent:#7c3aed; }
18
+ :root[data-theme="light"] { --bg:#f6f8fa; --surface:#ffffff; --text:#1f2328; --text2:#424a53; --text3:#57606a; --muted:#6e7781;
19
+ --line:rgba(0,0,0,0.06); --border:#d0d7de; --border2:#d0d7de; --accent:#8250df; }
20
+ :root[data-theme="black"] { --bg:#000000; --surface:#0a0a0a; --border2:#262626; }
9
21
  * { margin: 0; padding: 0; box-sizing: border-box; }
10
- body { font-family: -apple-system, system-ui, sans-serif; background: #0d1117; color: #e6edf3; min-height: 100vh; }
22
+ body { font-family: -apple-system, system-ui, sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; }
11
23
  .login-wrap { display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 1rem; }
12
- .login-box { background: #161b22; border: 1px solid rgba(255,255,255,0.1); border-radius: 16px; padding: 2rem; width: 100%; max-width: 360px; }
24
+ .login-box { background: var(--surface); border: 1px solid var(--border); border-radius: 16px; padding: 2rem; width: 100%; max-width: 360px; }
13
25
  .login-box h2 { text-align: center; margin-bottom: 1.5rem; font-size: 1.2rem; }
14
- .login-box input { width: 100%; padding: 0.8rem; background: #0d1117; border: 1px solid rgba(255,255,255,0.15); border-radius: 10px; color: #e6edf3; font-size: 1rem; margin-bottom: 1rem; }
15
- .login-box button { width: 100%; padding: 0.8rem; background: #7c3aed; color: #fff; border: none; border-radius: 10px; font-size: 1rem; font-weight: 600; cursor: pointer; }
26
+ .login-box input { width: 100%; padding: 0.8rem; background: var(--bg); border: 1px solid var(--border); border-radius: 10px; color: var(--text); font-size: 1rem; margin-bottom: 1rem; }
27
+ .login-box button { width: 100%; padding: 0.8rem; background: var(--accent); color: #fff; border: none; border-radius: 10px; font-size: 1rem; font-weight: 600; cursor: pointer; }
16
28
  .login-box .err { color: #f85149; font-size: 0.85rem; text-align: center; margin-bottom: 0.5rem; min-height: 1.2em; }
17
29
  .wrap { max-width: 640px; margin: 0 auto; padding: 1rem; }
18
- .header { display: flex; align-items: center; justify-content: space-between; padding: 1rem 0; border-bottom: 1px solid rgba(255,255,255,0.08); margin-bottom: 1.5rem; }
30
+ .header { display: flex; align-items: center; justify-content: space-between; padding: 1rem 0; border-bottom: 1px solid var(--line); margin-bottom: 1.5rem; }
19
31
  .header h1 { font-size: 1.1rem; }
20
- .header .logout { background: none; border: 1px solid rgba(255,255,255,0.15); color: #8b949e; padding: 0.3rem 0.8rem; border-radius: 8px; cursor: pointer; font-size: 0.8rem; }
21
- .ver-card { background: #161b22; border: 1px solid rgba(255,255,255,0.1); border-radius: 14px; padding: 1.2rem 1.5rem; margin-bottom: 1.2rem; }
22
- .ver-big { font-size: 2rem; font-weight: 800; color: #7c3aed; }
23
- .ver-sub { font-size: 0.85rem; color: #8b949e; margin-top: 0.5rem; }
32
+ .header .logout { background: none; border: 1px solid var(--border); color: var(--text3); padding: 0.3rem 0.8rem; border-radius: 8px; cursor: pointer; font-size: 0.8rem; }
33
+ .ver-card { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 1.2rem 1.5rem; margin-bottom: 1.2rem; }
34
+ .ver-big { font-size: 2rem; font-weight: 800; color: var(--accent); }
35
+ .ver-sub { font-size: 0.85rem; color: var(--text3); margin-top: 0.5rem; }
24
36
  .ver-sub b { color: #3fb950; }
25
37
  .actions { display: flex; gap: 0.8rem; margin-bottom: 1.5rem; }
26
38
  .actions button { flex: 1; padding: 0.8rem; border: none; border-radius: 10px; font-size: 0.9rem; font-weight: 600; cursor: pointer; }
27
- .btn-up { background: #7c3aed; color: #fff; }
28
- .btn-up:disabled { background: #333; color: #666; cursor: not-allowed; }
39
+ .btn-up { background: var(--accent); color: #fff; }
40
+ .btn-up:disabled { background: var(--border2); color: var(--muted); cursor: not-allowed; }
29
41
  .btn-up.glow { background: #3fb950; animation: pulse 2s infinite; }
30
- .btn-re { background: rgba(255,255,255,0.08); color: #e6edf3; border: 1px solid rgba(255,255,255,0.15) !important; }
42
+ .btn-re { background: var(--line); color: var(--text); border: 1px solid var(--border) !important; }
31
43
  @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.7} }
32
- .card { background: #161b22; border: 1px solid rgba(255,255,255,0.1); border-radius: 14px; padding: 1rem 1.2rem; margin-bottom: 1rem; }
44
+ .card { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 1rem 1.2rem; margin-bottom: 1rem; }
33
45
  .card h3 { font-size: 0.9rem; font-weight: 700; margin-bottom: 0.8rem; }
34
- .row { font-size: 0.82rem; color: #b1bac4; padding: 0.5rem 0; line-height: 1.5; display: flex; justify-content: space-between; align-items: center; }
35
- .row + .row { border-top: 1px solid rgba(255,255,255,0.04); }
46
+ .row { font-size: 0.82rem; color: var(--text2); padding: 0.5rem 0; line-height: 1.5; display: flex; justify-content: space-between; align-items: center; }
47
+ .row + .row { border-top: 1px solid var(--line); }
36
48
  .row-actions { display: flex; gap: 0.4rem; }
37
- .row-actions button { background: none; border: 1px solid rgba(255,255,255,0.12); color: #8b949e; padding: 0.2rem 0.5rem; border-radius: 6px; cursor: pointer; font-size: 0.75rem; }
38
- .row-actions button:hover { color: #e6edf3; border-color: rgba(255,255,255,0.3); }
49
+ .row-actions button { background: none; border: 1px solid var(--border); color: var(--text3); padding: 0.2rem 0.5rem; border-radius: 6px; cursor: pointer; font-size: 0.75rem; }
50
+ .row-actions button:hover { color: var(--text); border-color: var(--border2); }
39
51
  .row-actions .del { color: #f85149; border-color: rgba(248,81,73,0.3); }
40
- .inp { padding: 0.5rem 0.7rem; background: #0d1117; border: 1px solid rgba(255,255,255,0.15); border-radius: 8px; color: #e6edf3; font-size: 0.85rem; width: 100%; }
52
+ .inp { padding: 0.5rem 0.7rem; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-size: 0.85rem; width: 100%; }
41
53
  .inp-row { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.5rem; }
42
54
  .inp-row .inp { flex: 1; }
43
- .btn-sm { padding: 0.5rem 1rem; background: #7c3aed; color: #fff; border: none; border-radius: 8px; cursor: pointer; font-size: 0.85rem; white-space: nowrap; }
44
- .btn-sm.ghost { background: transparent; color: #b1bac4; border: 1px solid rgba(255,255,255,0.15); }
55
+ .btn-sm { padding: 0.5rem 1rem; background: var(--accent); color: #fff; border: none; border-radius: 8px; cursor: pointer; font-size: 0.85rem; white-space: nowrap; }
56
+ .btn-sm.ghost { background: transparent; color: var(--text2); border: 1px solid var(--border); }
45
57
  .mode-status { font-size: 0.85rem; line-height: 1.6; padding: 0.6rem 0.75rem; border-radius: 10px; background: rgba(124,58,237,0.1); border: 1px solid rgba(124,58,237,0.25); margin-bottom: 0.5rem; }
46
- .mode-status b { color: #e6edf3; }
47
- .mode-status small { display: block; color: #8b949e; font-size: 0.75rem; }
58
+ .mode-status b { color: var(--text); }
59
+ .mode-status small { display: block; color: var(--text3); font-size: 0.75rem; }
48
60
  .sw { position: relative; width: 38px; height: 22px; flex-shrink: 0; }
49
61
  .sw input { opacity: 0; width: 0; height: 0; }
50
- .sw span { position: absolute; inset: 0; background: #30363d; border-radius: 11px; cursor: pointer; transition: 0.2s; }
62
+ .sw span { position: absolute; inset: 0; background: var(--border2); border-radius: 11px; cursor: pointer; transition: 0.2s; }
51
63
  .sw span::before { content: ''; position: absolute; width: 16px; height: 16px; left: 3px; top: 3px; background: #fff; border-radius: 50%; transition: 0.2s; }
52
- .sw input:checked + span { background: #7c3aed; }
64
+ .sw input:checked + span { background: var(--accent); }
53
65
  .sw input:checked + span::before { transform: translateX(16px); }
54
- .dir-pick { border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; max-height: 220px; overflow-y: auto; margin-bottom: 0.5rem; font-size: 0.8rem; }
55
- .dir-pick div { padding: 0.4rem 0.6rem; cursor: pointer; color: #b1bac4; }
56
- .dir-pick div:hover { background: rgba(255,255,255,0.05); }
57
- .dir-pick .cur { color: #8b949e; font-size: 0.72rem; cursor: default; border-bottom: 1px solid rgba(255,255,255,0.06); }
66
+ .dir-pick { border: 1px solid var(--border); border-radius: 8px; max-height: 220px; overflow-y: auto; margin-bottom: 0.5rem; font-size: 0.8rem; }
67
+ .dir-pick div { padding: 0.4rem 0.6rem; cursor: pointer; color: var(--text2); }
68
+ .dir-pick div:hover { background: var(--line); }
69
+ .dir-pick .cur { color: var(--text3); font-size: 0.72rem; cursor: default; border-bottom: 1px solid var(--line); }
58
70
  .msg { font-size: 0.8rem; margin-top: 0.3rem; min-height: 1.2em; }
59
71
  .msg-ok { color: #3fb950; }
60
72
  .msg-err { color: #f85149; }
61
- .status-msg { text-align: center; padding: 1rem; color: #8b949e; font-size: 0.85rem; }
62
- .uptime { font-size: 0.78rem; color: #6e7681; text-align: center; margin-top: 1rem; }
73
+ .status-msg { text-align: center; padding: 1rem; color: var(--text3); font-size: 0.85rem; }
74
+ .uptime { font-size: 0.78rem; color: var(--muted); text-align: center; margin-top: 1rem; }
63
75
  </style>
64
76
  </head>
65
77
  <body>
@@ -81,7 +93,7 @@
81
93
 
82
94
  <!-- 版本 + 升级 -->
83
95
  <div class="ver-card">
84
- <div style="font-size:0.8rem;color:#8b949e">当前版本</div>
96
+ <div style="font-size:0.8rem;color:var(--text3)">当前版本</div>
85
97
  <div class="ver-big" id="v-cur">--</div>
86
98
  <div class="ver-sub" id="v-latest"></div>
87
99
  </div>
@@ -93,7 +105,7 @@
93
105
 
94
106
  <!-- Claude Code 版本 + 升级 -->
95
107
  <div class="ver-card">
96
- <div style="font-size:0.8rem;color:#8b949e">Claude Code 版本</div>
108
+ <div style="font-size:0.8rem;color:var(--text3)">Claude Code 版本</div>
97
109
  <div class="ver-big" id="cl-cur">--</div>
98
110
  <div class="ver-sub" id="cl-latest"></div>
99
111
  </div>
@@ -105,13 +117,13 @@
105
117
  <!-- 在线用户 -->
106
118
  <div class="card">
107
119
  <h3>在线用户</h3>
108
- <div id="online-users"><div class="row" style="color:#6e7681">加载中...</div></div>
120
+ <div id="online-users"><div class="row" style="color:var(--muted)">加载中...</div></div>
109
121
  </div>
110
122
 
111
123
  <!-- 活跃会话 -->
112
124
  <div class="card">
113
125
  <h3>活跃会话</h3>
114
- <div id="active-sessions"><div class="row" style="color:#6e7681">加载中...</div></div>
126
+ <div id="active-sessions"><div class="row" style="color:var(--muted)">加载中...</div></div>
115
127
  </div>
116
128
 
117
129
  <!-- 用户管理 -->
@@ -120,7 +132,7 @@
120
132
  <div id="mode-status" class="mode-status"></div>
121
133
  <div id="user-list"></div>
122
134
  <div id="excl-row" class="row" style="display:none">
123
- <span>独占模式<br><small style="color:#6e7681">同一时间只允许一个人登录,别人登录会被挡住</small></span>
135
+ <span>独占模式<br><small style="color:var(--muted)">同一时间只允许一个人登录,别人登录会被挡住</small></span>
124
136
  <label class="sw"><input type="checkbox" id="excl-toggle" onchange="setExclusive(this.checked)"><span></span></label>
125
137
  </div>
126
138
  <button class="btn-sm" id="add-open" onclick="openAddMember()" style="margin-top:0.6rem;width:100%"></button>
@@ -141,7 +153,7 @@
141
153
  </div>
142
154
  </div>
143
155
  <div class="msg" id="u-msg"></div>
144
- <div id="mode-foot" style="font-size:0.72rem;color:#6e7681;margin-top:0.5rem;line-height:1.5"></div>
156
+ <div id="mode-foot" style="font-size:0.72rem;color:var(--muted);margin-top:0.5rem;line-height:1.5"></div>
145
157
  </div>
146
158
 
147
159
  <!-- 修改管理密码 -->
@@ -157,8 +169,8 @@
157
169
  <!-- 大模型网关 -->
158
170
  <div class="card">
159
171
  <h3>大模型网关</h3>
160
- <div style="font-size:0.75rem;color:#6e7681;margin-bottom:0.6rem;line-height:1.5">
161
- 配置后新建的 Claude 会话自动注入 <code style="background:#161b22;padding:0.1em 0.3em;border-radius:3px">ANTHROPIC_BASE_URL</code> / <code style="background:#161b22;padding:0.1em 0.3em;border-radius:3px">ANTHROPIC_API_KEY</code>
172
+ <div style="font-size:0.75rem;color:var(--muted);margin-bottom:0.6rem;line-height:1.5">
173
+ 配置后新建的 Claude 会话自动注入 <code style="background:var(--surface);padding:0.1em 0.3em;border-radius:3px">ANTHROPIC_BASE_URL</code> / <code style="background:var(--surface);padding:0.1em 0.3em;border-radius:3px">ANTHROPIC_API_KEY</code>
162
174
  </div>
163
175
  <div class="inp-row">
164
176
  <input type="text" class="inp" id="gw-url" placeholder="网关 URL(如 https://gateway.example.com)">
@@ -173,11 +185,11 @@
173
185
  <!-- 命名密钥库 -->
174
186
  <div class="card">
175
187
  <h3>密钥库(Secret Vault)</h3>
176
- <div style="font-size:0.75rem;color:#6e7681;margin-bottom:0.6rem;line-height:1.5">
188
+ <div style="font-size:0.75rem;color:var(--muted);margin-bottom:0.6rem;line-height:1.5">
177
189
  集中存 GitLab PAT / 部署 token / 网关 key / 证书等敏感值,spawn 会话时注入成环境变量,AI/命令用
178
- <code style="background:#161b22;padding:0.1em 0.3em;border-radius:3px">$名字</code>
190
+ <code style="background:var(--surface);padding:0.1em 0.3em;border-radius:3px">$名字</code>
179
191
  引用——密钥值不进聊天、不落 transcript。类型选「文件」(证书等)时注入的是路径
180
- <code style="background:#161b22;padding:0.1em 0.3em;border-radius:3px">$MYHI_SECRET_名字</code>。
192
+ <code style="background:var(--surface);padding:0.1em 0.3em;border-radius:3px">$MYHI_SECRET_名字</code>。
181
193
  作用域「项目」仅注给该 cwd 的会话(最小权限)。<b>密钥值绝不写进记忆/CLAUDE.md。</b>
182
194
  </div>
183
195
  <div id="sec-list" style="margin-bottom:0.6rem"></div>
@@ -245,22 +257,22 @@ async function loadSecrets() {
245
257
  const d = await r.json();
246
258
  const box = document.getElementById('sec-list');
247
259
  let html = '';
248
- const row = (s, scope, cwd) => `<div style="display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0;border-bottom:1px solid #21262d;font-size:0.8rem">
249
- <span style="color:#e6edf3;font-family:monospace">${escHtml(s.name)}</span>
250
- <span style="color:#6e7681">${s.type === 'file' ? '📄文件' : '🔑变量'}</span>
251
- <span style="color:#6e7681;font-family:monospace;flex:1">${escHtml(s.preview)}</span>
260
+ const row = (s, scope, cwd) => `<div style="display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0;border-bottom:1px solid var(--line);font-size:0.8rem">
261
+ <span style="color:var(--text);font-family:monospace">${escHtml(s.name)}</span>
262
+ <span style="color:var(--muted)">${s.type === 'file' ? '📄文件' : '🔑变量'}</span>
263
+ <span style="color:var(--muted);font-family:monospace;flex:1">${escHtml(s.preview)}</span>
252
264
  <button class="btn-sm" style="padding:0.1rem 0.5rem" onclick="delSecret('${escHtml(s.name)}','${scope}','${escHtml(cwd || '')}')">删</button>
253
265
  </div>`;
254
266
  if (d.global && d.global.length) {
255
- html += '<div style="color:#8b949e;font-size:0.72rem;margin:0.2rem 0">全局</div>';
267
+ html += '<div style="color:var(--text3);font-size:0.72rem;margin:0.2rem 0">全局</div>';
256
268
  html += d.global.map(s => row(s, 'global', '')).join('');
257
269
  }
258
270
  for (const p of (d.projects || [])) {
259
271
  if (!p.secrets.length) continue;
260
- html += `<div style="color:#8b949e;font-size:0.72rem;margin:0.4rem 0 0.2rem;font-family:monospace">项目 ${escHtml(p.cwd)}</div>`;
272
+ html += `<div style="color:var(--text3);font-size:0.72rem;margin:0.4rem 0 0.2rem;font-family:monospace">项目 ${escHtml(p.cwd)}</div>`;
261
273
  html += p.secrets.map(s => row(s, 'project', p.cwd)).join('');
262
274
  }
263
- box.innerHTML = html || '<div style="color:#6e7681;font-size:0.78rem">(暂无密钥)</div>';
275
+ box.innerHTML = html || '<div style="color:var(--muted);font-size:0.78rem">(暂无密钥)</div>';
264
276
  } catch {}
265
277
  }
266
278
 
@@ -356,11 +368,16 @@ async function refresh() {
356
368
  const cr = await fetch('/api/claude/version', { headers: H() });
357
369
  if (cr.ok) {
358
370
  const cd = await cr.json();
359
- document.getElementById('cl-cur').textContent = cd.current ? ('v' + cd.current) : '未检测到';
371
+ document.getElementById('cl-cur').textContent = cd.current ? ('v' + cd.current) : '未安装';
360
372
  const clBtn = document.getElementById('btn-cl-up');
373
+ _clInstalled = !!cd.current;
361
374
  if (cd.upgrade?.running) {
362
- document.getElementById('cl-latest').innerHTML = '<span style="color:#58a6ff">升级中...</span>';
363
- clBtn.disabled = true; clBtn.textContent = '升级中...';
375
+ document.getElementById('cl-latest').innerHTML = '<span style="color:#58a6ff">' + (cd.current ? '升级' : '安装') + '中...</span>';
376
+ clBtn.disabled = true; clBtn.textContent = (cd.current ? '升级' : '安装') + '中...';
377
+ } else if (!cd.current) {
378
+ // 没装(npm 装 myhi 不会顺带装它):别显示「已是最新」,给安装按钮
379
+ document.getElementById('cl-latest').innerHTML = '<span style="color:#f0883e">AI 任务需要它,这台电脑上还没有</span>' + (cd.latest ? ',最新版 v' + cd.latest : '');
380
+ clBtn.disabled = false; clBtn.classList.add('glow'); clBtn.textContent = '安装 Claude Code';
364
381
  } else if (cd.updateAvailable) {
365
382
  document.getElementById('cl-latest').innerHTML = '最新版本: <b>v' + cd.latest + '</b>';
366
383
  clBtn.disabled = false; clBtn.classList.add('glow'); clBtn.textContent = '升级到 v' + cd.latest;
@@ -374,23 +391,23 @@ async function refresh() {
374
391
  // 在线用户
375
392
  const ouEl = document.getElementById('online-users');
376
393
  if (!d.onlineUsers?.length) {
377
- ouEl.innerHTML = '<div class="row" style="color:#6e7681">无在线用户</div>';
394
+ ouEl.innerHTML = '<div class="row" style="color:var(--muted)">无在线用户</div>';
378
395
  } else {
379
396
  ouEl.innerHTML = d.onlineUsers.map(u =>
380
- `<div class="row"><span>${esc(u.name)}</span><span style="color:#6e7681">${esc(u.role)}</span></div>`
397
+ `<div class="row"><span>${esc(u.name)}</span><span style="color:var(--muted)">${esc(u.role)}</span></div>`
381
398
  ).join('');
382
399
  }
383
400
 
384
401
  // 活跃会话
385
402
  const asEl = document.getElementById('active-sessions');
386
403
  if (!d.sessions?.length) {
387
- asEl.innerHTML = '<div class="row" style="color:#6e7681">无活跃会话</div>';
404
+ asEl.innerHTML = '<div class="row" style="color:var(--muted)">无活跃会话</div>';
388
405
  } else {
389
406
  asEl.innerHTML = d.sessions.map(s => {
390
407
  const tag = s.mode === 'agent'
391
408
  ? '<span style="display:inline-block;font-size:0.7rem;padding:0.1rem 0.4rem;border-radius:4px;background:rgba(124,58,237,0.2);color:#a78bfa">Agent</span>'
392
409
  : '<span style="display:inline-block;font-size:0.7rem;padding:0.1rem 0.4rem;border-radius:4px;background:rgba(63,185,80,0.2);color:#3fb950">PTY</span>';
393
- return `<div class="row"><span>${tag} ${esc(s.title)} <span style="color:#6e7681">${esc(s.owner||'')}</span></span><span style="color:#6e7681">${s.viewers}人</span></div>`;
410
+ return `<div class="row"><span>${tag} ${esc(s.title)} <span style="color:var(--muted)">${esc(s.owner||'')}</span></span><span style="color:var(--muted)">${s.viewers}人</span></div>`;
394
411
  }).join('');
395
412
  }
396
413
 
@@ -420,7 +437,7 @@ function renderUsers(users, exclusive) {
420
437
  const el = document.getElementById('user-list');
421
438
  el.innerHTML = users.map(u => `
422
439
  <div class="row">
423
- <span><b style="color:#e6edf3">${esc(u.name)}</b> <span style="color:#6e7681;font-size:0.72rem">${u.dir ? '📁 ' + esc(u.dir) : '不限目录'}</span></span>
440
+ <span><b style="color:var(--text)">${esc(u.name)}</b> <span style="color:var(--muted);font-size:0.72rem">${u.dir ? '📁 ' + esc(u.dir) : '不限目录'}</span></span>
424
441
  <div class="row-actions">
425
442
  <button onclick="editUserPwd('${esc(u.name)}')">重置密码</button>
426
443
  <button class="del" onclick="delUser('${esc(u.name)}')">删除</button>
@@ -562,10 +579,14 @@ async function doUpgrade(force) {
562
579
  } catch { fail('连接失败'); }
563
580
  }
564
581
 
582
+ let _clInstalled = true;
565
583
  async function doClaudeUpgrade() {
566
584
  const btn = document.getElementById('btn-cl-up'), msg = document.getElementById('cl-msg');
567
- if (!confirm('升级 Claude Code?\nmyhi 会用 npm --include=optional 装全平台二进制,避免占位符坑。\n不影响正在跑的会话,升级后新查询生效。')) return;
568
- btn.disabled = true; btn.textContent = '升级中...'; msg.className = 'status-msg'; msg.textContent = '正在下载安装(~260MB,可能几分钟)...'; msg.style.display = '';
585
+ const verb = _clInstalled ? '升级' : '安装';
586
+ if (!confirm(_clInstalled
587
+ ? '升级 Claude Code?\nmyhi 会用 npm --include=optional 装全平台二进制,避免占位符坑。\n不影响正在跑的会话,升级后新查询生效。'
588
+ : '安装 Claude Code(myhi 的 AI 引擎)?\n下载约 260MB,需要几分钟。装好后还要在这台电脑上运行一次 claude 登录,或配置下方的大模型网关。')) return;
589
+ btn.disabled = true; btn.textContent = verb + '中...'; msg.className = 'status-msg'; msg.textContent = '正在下载安装(~260MB,可能几分钟)...'; msg.style.display = '';
569
590
  try {
570
591
  const r = await fetch('/api/claude/update', { method: 'POST', headers: H() });
571
592
  const d = await r.json();
@@ -576,7 +597,7 @@ async function doClaudeUpgrade() {
576
597
  const s = await sr.json();
577
598
  if (!s.running) {
578
599
  clearInterval(poll);
579
- if (s.ok) { msg.textContent = '已升级到 v' + s.version; setTimeout(refresh, 500); }
600
+ if (s.ok) { msg.textContent = (_clInstalled ? '已升级到 v' : '已安装 v') + s.version + (_clInstalled ? '' : '。还差一步:在这台电脑上运行一次 claude 登录,或配置下方的大模型网关'); setTimeout(refresh, 500); }
580
601
  else { msg.textContent = '失败: ' + (s.error||''); btn.disabled = false; btn.textContent = '重试'; }
581
602
  }
582
603
  } catch {}