@co0ontty/wand 1.29.3 → 1.29.6
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/server.js
CHANGED
|
@@ -976,6 +976,10 @@ export async function startServer(config, configPath) {
|
|
|
976
976
|
res.setHeader("Content-Disposition", `attachment; filename="${encodeURIComponent(macosDmg.fileName)}"`);
|
|
977
977
|
createReadStream(macosDmg.filePath).pipe(res);
|
|
978
978
|
});
|
|
979
|
+
// Public probe so the unauthenticated browser does not log a 401 on /api/config
|
|
980
|
+
app.get("/api/session-check", (req, res) => {
|
|
981
|
+
res.json({ authed: validateSession(readSessionCookie(req)) });
|
|
982
|
+
});
|
|
979
983
|
app.use("/api", requireAuth);
|
|
980
984
|
// ── Config & Session info ──
|
|
981
985
|
app.get("/api/config", async (_req, res) => {
|
|
@@ -1148,14 +1148,24 @@
|
|
|
1148
1148
|
}
|
|
1149
1149
|
|
|
1150
1150
|
function restoreLoginSession() {
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1151
|
+
// Probe an unauthenticated endpoint first so an anonymous visit
|
|
1152
|
+
// does not leave a noisy 401 on /api/config in DevTools.
|
|
1153
|
+
fetch("/api/session-check", { credentials: "same-origin" })
|
|
1154
|
+
.then(function(res) { return res.ok ? res.json() : { authed: false }; })
|
|
1155
|
+
.then(function(info) {
|
|
1156
|
+
if (!info || !info.authed) {
|
|
1154
1157
|
state.loginChecked = true;
|
|
1155
1158
|
render();
|
|
1156
1159
|
return null;
|
|
1157
1160
|
}
|
|
1158
|
-
return
|
|
1161
|
+
return fetch("/api/config", { credentials: "same-origin" }).then(function(res) {
|
|
1162
|
+
if (!res.ok) {
|
|
1163
|
+
state.loginChecked = true;
|
|
1164
|
+
render();
|
|
1165
|
+
return null;
|
|
1166
|
+
}
|
|
1167
|
+
return res.json();
|
|
1168
|
+
});
|
|
1159
1169
|
})
|
|
1160
1170
|
.then(function(config) {
|
|
1161
1171
|
if (!config) return;
|
|
@@ -1286,6 +1296,7 @@
|
|
|
1286
1296
|
'<span class="shortcut-sep">·</span>' +
|
|
1287
1297
|
'<button class="shortcut-key" data-key="enter" type="button">↵</button>' +
|
|
1288
1298
|
'<button class="shortcut-key" data-key="ctrl_enter" type="button">C-↵</button>' +
|
|
1299
|
+
'<button class="shortcut-key" data-key="shift_tab" type="button" title="Shift+Tab(切换 plan / 自动接受 模式)">⇧⇥</button>' +
|
|
1289
1300
|
'<button class="shortcut-key" data-key="escape" type="button">Esc</button>';
|
|
1290
1301
|
}
|
|
1291
1302
|
|
|
@@ -1365,7 +1376,8 @@
|
|
|
1365
1376
|
'</div>' +
|
|
1366
1377
|
'<div class="login-subtitle">在浏览器中运行本机终端</div>' +
|
|
1367
1378
|
'</div>' +
|
|
1368
|
-
'<
|
|
1379
|
+
'<form id="login-form" class="login-body" autocomplete="on">' +
|
|
1380
|
+
'<input type="text" name="username" autocomplete="username" value="wand" tabindex="-1" aria-hidden="true" style="position:absolute;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none" readonly />' +
|
|
1369
1381
|
'<p class="login-hint">输入 Wand 访问密码以进入控制台。</p>' +
|
|
1370
1382
|
'<div class="field">' +
|
|
1371
1383
|
'<label class="field-label" for="password">密码</label>' +
|
|
@@ -1376,7 +1388,7 @@
|
|
|
1376
1388
|
'<p id="password-hint" class="hint">使用你在 Wand 中设置的访问密码。</p>' +
|
|
1377
1389
|
'<p id="login-error" class="error-message hidden" role="alert"></p>' +
|
|
1378
1390
|
'</div>' +
|
|
1379
|
-
'<button id="login-button" class="btn btn-primary btn-block">进入控制台</button>' +
|
|
1391
|
+
'<button id="login-button" type="submit" class="btn btn-primary btn-block">进入控制台</button>' +
|
|
1380
1392
|
(hasNativeSwitchServer() ?
|
|
1381
1393
|
'<button id="login-switch-server-button" class="btn btn-ghost btn-block login-switch-server" type="button">' +
|
|
1382
1394
|
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="8" rx="2"/><rect x="2" y="13" width="20" height="8" rx="2"/><line x1="6" y1="7" x2="6.01" y2="7"/><line x1="6" y1="17" x2="6.01" y2="17"/></svg>' +
|
|
@@ -1384,7 +1396,7 @@
|
|
|
1384
1396
|
'</button>'
|
|
1385
1397
|
: ''
|
|
1386
1398
|
) +
|
|
1387
|
-
'</
|
|
1399
|
+
'</form>' +
|
|
1388
1400
|
'</div>' +
|
|
1389
1401
|
'</div>';
|
|
1390
1402
|
}
|
|
@@ -1442,7 +1454,7 @@
|
|
|
1442
1454
|
'</div>' +
|
|
1443
1455
|
'<div class="sidebar-body">' +
|
|
1444
1456
|
'<div id="sessions-panel">' +
|
|
1445
|
-
'<div class="sessions-list" id="sessions-list">' +
|
|
1457
|
+
'<div class="sessions-list" id="sessions-list">' + renderSessionsListContent() + '</div>' +
|
|
1446
1458
|
'</div>' +
|
|
1447
1459
|
'</div>' +
|
|
1448
1460
|
'<div class="sidebar-footer">' +
|
|
@@ -2943,19 +2955,22 @@
|
|
|
2943
2955
|
'<p class="settings-card-desc">至少 6 个字符;保存后下次登录生效。</p>' +
|
|
2944
2956
|
'</div>' +
|
|
2945
2957
|
'</div>' +
|
|
2946
|
-
'<
|
|
2947
|
-
'<
|
|
2948
|
-
'<
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
'
|
|
2952
|
-
'<
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
'
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2958
|
+
'<form id="change-password-form" autocomplete="on" onsubmit="return false;">' +
|
|
2959
|
+
'<input type="text" name="username" autocomplete="username" value="wand" tabindex="-1" aria-hidden="true" style="position:absolute;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none" readonly />' +
|
|
2960
|
+
'<div class="field">' +
|
|
2961
|
+
'<label class="field-label" for="new-password">新密码</label>' +
|
|
2962
|
+
'<input id="new-password" type="password" class="field-input" placeholder="输入新密码(至少 6 个字符)" autocomplete="new-password" />' +
|
|
2963
|
+
'</div>' +
|
|
2964
|
+
'<div class="field">' +
|
|
2965
|
+
'<label class="field-label" for="confirm-password">确认密码</label>' +
|
|
2966
|
+
'<input id="confirm-password" type="password" class="field-input" placeholder="再次输入新密码" autocomplete="new-password" />' +
|
|
2967
|
+
'</div>' +
|
|
2968
|
+
'<div class="settings-card-actions">' +
|
|
2969
|
+
'<button id="save-password-button" class="btn btn-primary" type="submit">保存密码</button>' +
|
|
2970
|
+
'</div>' +
|
|
2971
|
+
'<p id="settings-error" class="error-message hidden"></p>' +
|
|
2972
|
+
'<p id="settings-success" class="hint settings-success-message hidden"></p>' +
|
|
2973
|
+
'</form>' +
|
|
2959
2974
|
'</div>' +
|
|
2960
2975
|
'<div class="settings-card">' +
|
|
2961
2976
|
'<div class="settings-card-head">' +
|
|
@@ -3091,6 +3106,35 @@
|
|
|
3091
3106
|
return groups.join("");
|
|
3092
3107
|
}
|
|
3093
3108
|
|
|
3109
|
+
function isSidebarNarrow() {
|
|
3110
|
+
return !!state.sidebarPinned && !isMobileLayout() && !!state.sidebarCollapsed;
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
function renderCollapsedSessionTiles() {
|
|
3114
|
+
var running = state.sessions.filter(function(s) {
|
|
3115
|
+
return !s.archived && s.status === "running";
|
|
3116
|
+
});
|
|
3117
|
+
running.sort(function(a, b) {
|
|
3118
|
+
var ta = a.startedAt ? new Date(a.startedAt).getTime() : 0;
|
|
3119
|
+
var tb = b.startedAt ? new Date(b.startedAt).getTime() : 0;
|
|
3120
|
+
return ta - tb;
|
|
3121
|
+
});
|
|
3122
|
+
if (running.length === 0) {
|
|
3123
|
+
return '<div class="sidebar-collapsed-empty" title="无运行中的会话">—</div>';
|
|
3124
|
+
}
|
|
3125
|
+
return '<div class="sidebar-collapsed-tiles">' +
|
|
3126
|
+
running.map(function(s, i) {
|
|
3127
|
+
var activeCls = s.id === state.selectedId ? " active" : "";
|
|
3128
|
+
var title = s.summary || s.command || ("会话 " + (i + 1));
|
|
3129
|
+
return '<button class="sidebar-collapsed-tile' + activeCls + '" type="button" data-collapsed-session-id="' + escapeHtml(s.id) + '" title="' + escapeHtml(title) + '">' + (i + 1) + '</button>';
|
|
3130
|
+
}).join("") +
|
|
3131
|
+
'</div>';
|
|
3132
|
+
}
|
|
3133
|
+
|
|
3134
|
+
function renderSessionsListContent() {
|
|
3135
|
+
return isSidebarNarrow() ? renderCollapsedSessionTiles() : renderSessions();
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3094
3138
|
function renderSessionManageBar() {
|
|
3095
3139
|
if (!state.sessionsManageMode) {
|
|
3096
3140
|
return '<div class="session-manage-bar">' +
|
|
@@ -5378,6 +5422,11 @@
|
|
|
5378
5422
|
var loginButton = document.getElementById("login-button");
|
|
5379
5423
|
if (loginButton) {
|
|
5380
5424
|
loginButton.addEventListener("click", login);
|
|
5425
|
+
var loginForm = document.getElementById("login-form");
|
|
5426
|
+
if (loginForm) loginForm.addEventListener("submit", function(e) {
|
|
5427
|
+
e.preventDefault();
|
|
5428
|
+
login();
|
|
5429
|
+
});
|
|
5381
5430
|
var loginSwitchServerBtn = document.getElementById("login-switch-server-button");
|
|
5382
5431
|
if (loginSwitchServerBtn) loginSwitchServerBtn.addEventListener("click", switchServer);
|
|
5383
5432
|
var passwordEl = document.getElementById("password");
|
|
@@ -6540,6 +6589,14 @@
|
|
|
6540
6589
|
var target = event.target;
|
|
6541
6590
|
if (!target || !(target instanceof Element)) return;
|
|
6542
6591
|
|
|
6592
|
+
var collapsedTile = target.closest(".sidebar-collapsed-tile");
|
|
6593
|
+
if (collapsedTile && collapsedTile instanceof HTMLElement && collapsedTile.dataset.collapsedSessionId) {
|
|
6594
|
+
event.preventDefault();
|
|
6595
|
+
event.stopPropagation();
|
|
6596
|
+
activateSessionItem(collapsedTile.dataset.collapsedSessionId);
|
|
6597
|
+
return;
|
|
6598
|
+
}
|
|
6599
|
+
|
|
6543
6600
|
var historyToggle = target.closest("#claude-history-toggle");
|
|
6544
6601
|
if (historyToggle) {
|
|
6545
6602
|
event.preventDefault();
|
|
@@ -8309,7 +8366,7 @@
|
|
|
8309
8366
|
updateSessionsList();
|
|
8310
8367
|
} else {
|
|
8311
8368
|
var listEl = document.getElementById("sessions-list");
|
|
8312
|
-
var rendered =
|
|
8369
|
+
var rendered = renderSessionsListContent();
|
|
8313
8370
|
if (listEl && listEl.innerHTML === rendered) {
|
|
8314
8371
|
var countEl = document.getElementById("session-count");
|
|
8315
8372
|
if (countEl) countEl.textContent = String(state.sessions.length);
|
|
@@ -8368,7 +8425,7 @@
|
|
|
8368
8425
|
function updateSessionsList() {
|
|
8369
8426
|
var listEl = document.getElementById("sessions-list");
|
|
8370
8427
|
var countEl = document.getElementById("session-count");
|
|
8371
|
-
if (listEl) listEl.innerHTML =
|
|
8428
|
+
if (listEl) listEl.innerHTML = renderSessionsListContent();
|
|
8372
8429
|
if (countEl) countEl.textContent = String(state.sessions.length);
|
|
8373
8430
|
updateShellChrome();
|
|
8374
8431
|
// Re-render cross-session queue (container may have been destroyed by DOM rebuild)
|
|
@@ -8637,7 +8694,15 @@
|
|
|
8637
8694
|
|
|
8638
8695
|
function toggleSidebarCollapsed() {
|
|
8639
8696
|
if (isMobileLayout()) return;
|
|
8640
|
-
|
|
8697
|
+
// 在 drawer 模式(未 pin)下点 collapse 视为「先固定、再收起为窄条」——
|
|
8698
|
+
// 用户直觉是「点了就该看到窄条」,过去这里 early return 让按钮看上去没反应。
|
|
8699
|
+
if (!state.sidebarPinned) {
|
|
8700
|
+
state.sidebarPinned = true;
|
|
8701
|
+
state.sessionsDrawerOpen = true;
|
|
8702
|
+
try {
|
|
8703
|
+
localStorage.setItem("wand-sidebar-pinned", "true");
|
|
8704
|
+
} catch (e) {}
|
|
8705
|
+
}
|
|
8641
8706
|
state.sidebarCollapsed = !state.sidebarCollapsed;
|
|
8642
8707
|
try {
|
|
8643
8708
|
localStorage.setItem("wand-sidebar-collapsed", String(state.sidebarCollapsed));
|
|
@@ -11969,6 +12034,7 @@
|
|
|
11969
12034
|
var ptySpecialKeyMap = {
|
|
11970
12035
|
space: " ",
|
|
11971
12036
|
tab: String.fromCharCode(9),
|
|
12037
|
+
shift_tab: String.fromCharCode(27) + "[Z",
|
|
11972
12038
|
backspace: String.fromCharCode(127),
|
|
11973
12039
|
home: String.fromCharCode(27) + "[H",
|
|
11974
12040
|
end: String.fromCharCode(27) + "[F",
|
|
@@ -12026,7 +12092,9 @@
|
|
|
12026
12092
|
return {
|
|
12027
12093
|
ctrl: event.ctrlKey,
|
|
12028
12094
|
alt: event.altKey,
|
|
12029
|
-
|
|
12095
|
+
// 仅对单字符键保留 shift(控制 toUpperCase 路径),
|
|
12096
|
+
// 但 Tab 特例:物理 Shift+Tab 要走 buildPtySequence 的 back-tab 分支。
|
|
12097
|
+
shift: event.shiftKey && (key.length === 1 || key === "tab"),
|
|
12030
12098
|
meta: event.metaKey
|
|
12031
12099
|
};
|
|
12032
12100
|
}
|
|
@@ -12249,6 +12317,8 @@
|
|
|
12249
12317
|
function buildPtySequence(key, modifiers) {
|
|
12250
12318
|
var mods = modifiers || { ctrl: false, alt: false, shift: false };
|
|
12251
12319
|
if (isModifierKey(key)) return "";
|
|
12320
|
+
// Shift+Tab → CSI Z (back-tab)。Claude Code 用它在 plan / 自动接受 模式间切换。
|
|
12321
|
+
if (key === "tab" && mods.shift) return String.fromCharCode(27) + "[Z";
|
|
12252
12322
|
var specialSequence = getPtySpecialSequence(key);
|
|
12253
12323
|
if (specialSequence) return specialSequence;
|
|
12254
12324
|
if (key.indexOf("ctrl_") === 0) {
|
|
@@ -13936,6 +14006,9 @@
|
|
|
13936
14006
|
function sendTerminalResize(cols, rows) {
|
|
13937
14007
|
if (!state.selectedId) return;
|
|
13938
14008
|
var selectedSess = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
14009
|
+
// 会话已被清除(如服务重启后 localStorage 还残留旧 id),后端 resize 会
|
|
14010
|
+
// 直接 400/404,console 留一条红色错误;这里提前剪掉,避免噪音。
|
|
14011
|
+
if (!selectedSess || selectedSess.status !== "running") return;
|
|
13939
14012
|
if (isStructuredSession(selectedSess)) return;
|
|
13940
14013
|
var nextSize = { cols: cols, rows: rows };
|
|
13941
14014
|
if (state.lastResize.cols !== nextSize.cols || state.lastResize.rows !== nextSize.rows) {
|