aws-runtime-bridge 1.9.9 → 1.9.10

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/index.js CHANGED
@@ -269,10 +269,10 @@ app.use(cors({
269
269
  return;
270
270
  }
271
271
  // 允许 bridge 自身端口的来源(dashboard/登录页面同源请求)
272
+ // 无论主机名是 localhost、127.0.0.1 还是外部 IP,只要端口匹配就放行
272
273
  try {
273
274
  const url = new URL(origin);
274
- if ((url.hostname === "localhost" || url.hostname === "127.0.0.1") &&
275
- url.port === String(port)) {
275
+ if (url.port === String(port)) {
276
276
  callback(null, true);
277
277
  return;
278
278
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/routes/dashboard.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA+EH,eAAO,MAAM,eAAe,4CAAW,CAAC"}
1
+ {"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/routes/dashboard.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkFH,eAAO,MAAM,eAAe,4CAAW,CAAC"}
@@ -14,7 +14,7 @@ import { loadPersistedSessions } from "../services/terminal-persistence.js";
14
14
  import { createLogger } from "../utils/logger.js";
15
15
  import { getBridgeVersion } from "../config.js";
16
16
  import { sdkSessions } from "./terminal.js";
17
- import { createSession, validateSession, destroySession, validateCredentials, } from "../services/panel-auth.js";
17
+ import { createSession, validateSession, destroySession, validateCredentials, hasConfiguredCredentials, isDefaultCredentials, setPanelCredentials, } from "../services/panel-auth.js";
18
18
  const log = createLogger("dashboard");
19
19
  // ── 内存缓存(两级) ─────────────────────────────────────
20
20
  // 一级:完整响应缓存,匹配前端轮询间隔(10s),避免大部分请求走 I/O
@@ -119,9 +119,10 @@ dashboardRouter.post("/runtime/login", (req, res) => {
119
119
  return;
120
120
  }
121
121
  const token = createSession();
122
+ const needSetup = !hasConfiguredCredentials();
122
123
  // 设置 HttpOnly cookie,24 小时过期
123
124
  res.setHeader("Set-Cookie", `${COOKIE_NAME}=${token}; HttpOnly; SameSite=Lax; Path=/; Max-Age=86400`);
124
- res.json({ ok: true });
125
+ res.json({ ok: true, needSetup });
125
126
  });
126
127
  /** POST /runtime/logout 登出 */
127
128
  dashboardRouter.post("/runtime/logout", (req, res) => {
@@ -132,6 +133,40 @@ dashboardRouter.post("/runtime/logout", (req, res) => {
132
133
  res.setHeader("Set-Cookie", `${COOKIE_NAME}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`);
133
134
  res.json({ ok: true });
134
135
  });
136
+ /**
137
+ * GET /runtime/credentials-status
138
+ * 返回当前凭据是否仍是默认值 root/root。
139
+ * 需要面板登录会话。
140
+ */
141
+ dashboardRouter.get("/runtime/credentials-status", (req, res) => {
142
+ if (!requirePanelAuth(req, res))
143
+ return;
144
+ res.json({ ok: true, isDefault: isDefaultCredentials(), configured: hasConfiguredCredentials() });
145
+ });
146
+ /**
147
+ * POST /runtime/change-credentials
148
+ * 修改面板登录凭据,写入 ~/.aws-bridge/config.json。
149
+ * 需要面板登录会话。密码需两次一致且非空。
150
+ */
151
+ dashboardRouter.post("/runtime/change-credentials", (req, res) => {
152
+ if (!requirePanelAuth(req, res))
153
+ return;
154
+ const { username, password, passwordConfirm } = req.body || {};
155
+ if (!username || !String(username).trim()) {
156
+ res.status(400).json({ ok: false, error: "username required" });
157
+ return;
158
+ }
159
+ if (!password || !String(password).trim()) {
160
+ res.status(400).json({ ok: false, error: "password required" });
161
+ return;
162
+ }
163
+ if (String(password) !== String(passwordConfirm)) {
164
+ res.status(400).json({ ok: false, error: "passwords do not match" });
165
+ return;
166
+ }
167
+ setPanelCredentials(String(username).trim(), String(password));
168
+ res.json({ ok: true });
169
+ });
135
170
  /**
136
171
  * GET /runtime/dashboard
137
172
  * 聚合 bridge 所有状态数据。使用 2 秒缓存避免频繁 I/O,需要面板登录会话。
@@ -254,6 +289,7 @@ dashboardRouter.get("/login", (_req, res) => {
254
289
  /**
255
290
  * GET / 仪表盘主页
256
291
  * 需要面板登录会话,未登录时跳转到 /login。
292
+ * 当凭据仍是默认值 root/root 时跳转到 /setup 引导首次设置。
257
293
  * 支持 ?lang=en 切换英文,默认中文
258
294
  */
259
295
  dashboardRouter.get("/", (req, res) => {
@@ -262,10 +298,28 @@ dashboardRouter.get("/", (req, res) => {
262
298
  res.redirect("/login");
263
299
  return;
264
300
  }
301
+ // 如果 config.json 尚未配置过凭据,引导首次设置
302
+ if (!hasConfiguredCredentials()) {
303
+ res.redirect("/setup");
304
+ return;
305
+ }
265
306
  const lang = req.query.lang === "en" ? "en" : "zh";
266
307
  res.type("text/html; charset=utf-8");
267
308
  res.send(DASHBOARD_HTML.replace("{{LANG}}", lang));
268
309
  });
310
+ /**
311
+ * GET /setup 首次登录设置页面
312
+ * 需要面板登录会话。提供用户名/密码修改表单,也支持跳过(保持 root/root)。
313
+ */
314
+ dashboardRouter.get("/setup", (req, res) => {
315
+ const token = getSessionToken(req);
316
+ if (!token || !validateSession(token)) {
317
+ res.redirect("/login");
318
+ return;
319
+ }
320
+ res.type("text/html; charset=utf-8");
321
+ res.send(SETUP_HTML);
322
+ });
269
323
  // ======================================================================
270
324
  // DASHBOARD HTML — 单页自包含,支持中文/英文界面,默认中文
271
325
  // ======================================================================
@@ -1322,7 +1376,12 @@ async function doLogin(e) {
1322
1376
  });
1323
1377
 
1324
1378
  if (resp.ok) {
1325
- window.location.href = "/";
1379
+ const data = await resp.json();
1380
+ if (data.needSetup) {
1381
+ window.location.href = "/setup";
1382
+ } else {
1383
+ window.location.href = "/";
1384
+ }
1326
1385
  return;
1327
1386
  }
1328
1387
 
@@ -1345,3 +1404,294 @@ document.getElementById("loginForm").addEventListener("keydown", function(e) {
1345
1404
  </script>
1346
1405
  </body>
1347
1406
  </html>`;
1407
+ // ======================================================================
1408
+ // SETUP HTML — 首次登录设置页面
1409
+ // ======================================================================
1410
+ const SETUP_HTML = `<!DOCTYPE html>
1411
+ <html lang="zh-CN">
1412
+ <head>
1413
+ <meta charset="UTF-8">
1414
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1415
+ <title>Bridge Dashboard - Setup</title>
1416
+ <style>
1417
+ :root {
1418
+ --bg: #0f1117;
1419
+ --surface: #1a1d2e;
1420
+ --surface2: #232738;
1421
+ --border: #2d3248;
1422
+ --text: #e1e4f0;
1423
+ --text2: #8b90a8;
1424
+ --accent: #5b8def;
1425
+ --accent-hover: #4a7de0;
1426
+ --green: #43c6a8;
1427
+ --red: #f05454;
1428
+ --radius: 10px;
1429
+ }
1430
+ * { margin:0; padding:0; box-sizing:border-box; }
1431
+ body {
1432
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", sans-serif;
1433
+ background: var(--bg);
1434
+ color: var(--text);
1435
+ min-height: 100vh;
1436
+ display: flex;
1437
+ align-items: center;
1438
+ justify-content: center;
1439
+ padding: 20px;
1440
+ }
1441
+ .setup-card {
1442
+ background: var(--surface);
1443
+ border: 1px solid var(--border);
1444
+ border-radius: var(--radius);
1445
+ padding: 40px 36px;
1446
+ width: 400px;
1447
+ max-width: 100%;
1448
+ }
1449
+ .setup-card h1 {
1450
+ font-size: 20px;
1451
+ font-weight: 600;
1452
+ margin-bottom: 4px;
1453
+ text-align: center;
1454
+ }
1455
+ .setup-card p.subtitle {
1456
+ font-size: 13px;
1457
+ color: var(--text2);
1458
+ text-align: center;
1459
+ margin-bottom: 24px;
1460
+ }
1461
+ .badge {
1462
+ display: inline-block;
1463
+ background: rgba(240,160,48,0.12);
1464
+ color: #f0a030;
1465
+ font-size: 12px;
1466
+ padding: 2px 10px;
1467
+ border-radius: 4px;
1468
+ margin-bottom: 20px;
1469
+ text-align: center;
1470
+ width: 100%;
1471
+ }
1472
+ .form-group {
1473
+ margin-bottom: 18px;
1474
+ }
1475
+ .form-group label {
1476
+ display: block;
1477
+ font-size: 13px;
1478
+ font-weight: 500;
1479
+ margin-bottom: 6px;
1480
+ color: var(--text2);
1481
+ }
1482
+ .form-group input {
1483
+ width: 100%;
1484
+ padding: 10px 12px;
1485
+ background: var(--surface2);
1486
+ border: 1px solid var(--border);
1487
+ border-radius: 6px;
1488
+ color: var(--text);
1489
+ font-size: 14px;
1490
+ outline: none;
1491
+ transition: border-color .15s;
1492
+ }
1493
+ .form-group input:focus {
1494
+ border-color: var(--accent);
1495
+ }
1496
+ .form-group input::placeholder {
1497
+ color: var(--text2);
1498
+ opacity: 0.6;
1499
+ }
1500
+ .btn-row {
1501
+ display: flex;
1502
+ gap: 12px;
1503
+ margin-top: 24px;
1504
+ }
1505
+ .btn-row button {
1506
+ flex: 1;
1507
+ padding: 10px;
1508
+ border-radius: 6px;
1509
+ font-size: 14px;
1510
+ font-weight: 600;
1511
+ cursor: pointer;
1512
+ transition: all .15s;
1513
+ border: none;
1514
+ }
1515
+ .btn-primary {
1516
+ background: var(--accent);
1517
+ color: #fff;
1518
+ }
1519
+ .btn-primary:hover { background: var(--accent-hover); }
1520
+ .btn-primary:disabled {
1521
+ opacity: 0.5;
1522
+ cursor: not-allowed;
1523
+ }
1524
+ .btn-skip {
1525
+ background: var(--surface2);
1526
+ color: var(--text);
1527
+ border: 1px solid var(--border) !important;
1528
+ }
1529
+ .btn-skip:hover { background: var(--border); }
1530
+ .error-msg {
1531
+ background: rgba(240,84,84,0.12);
1532
+ color: var(--red);
1533
+ padding: 8px 12px;
1534
+ border-radius: 6px;
1535
+ font-size: 13px;
1536
+ margin-bottom: 16px;
1537
+ display: none;
1538
+ }
1539
+ .error-msg.visible { display: block; }
1540
+ .success-msg {
1541
+ background: rgba(67,198,168,0.12);
1542
+ color: var(--green);
1543
+ padding: 8px 12px;
1544
+ border-radius: 6px;
1545
+ font-size: 13px;
1546
+ margin-bottom: 16px;
1547
+ display: none;
1548
+ text-align: center;
1549
+ }
1550
+ .success-msg.visible { display: block; }
1551
+ .hint {
1552
+ font-size: 12px;
1553
+ color: var(--text2);
1554
+ margin-top: 16px;
1555
+ text-align: center;
1556
+ line-height: 1.6;
1557
+ }
1558
+ .spinner {
1559
+ display: inline-block;
1560
+ width: 14px;
1561
+ height: 14px;
1562
+ border: 2px solid var(--text2);
1563
+ border-top-color: var(--accent);
1564
+ border-radius: 50%;
1565
+ animation: spin .6s linear infinite;
1566
+ vertical-align: middle;
1567
+ margin-right: 6px;
1568
+ }
1569
+ @keyframes spin { to { transform: rotate(360deg); } }
1570
+ </style>
1571
+ </head>
1572
+ <body>
1573
+ <div class="setup-card" id="setupCard">
1574
+ <h1>Bridge Dashboard</h1>
1575
+ <p class="subtitle">First-time setup — 首次登录设置</p>
1576
+ <div class="badge">Default credentials detected. Please set a new username and password.<br>检测到默认凭据,请设置新的用户名和密码</div>
1577
+ <div class="error-msg" id="errorMsg"></div>
1578
+ <div class="success-msg" id="successMsg">Credentials saved! Redirecting... / 凭据已保存,正在跳转...</div>
1579
+ <form id="setupForm">
1580
+ <div class="form-group">
1581
+ <label for="username">Username / 用户名</label>
1582
+ <input type="text" id="username" name="username" value="root" autocomplete="username" autofocus>
1583
+ </div>
1584
+ <div class="form-group">
1585
+ <label for="password">New Password / 新密码</label>
1586
+ <input type="password" id="password" name="password" placeholder="Enter new password" autocomplete="new-password">
1587
+ </div>
1588
+ <div class="form-group">
1589
+ <label for="passwordConfirm">Confirm Password / 确认密码</label>
1590
+ <input type="password" id="passwordConfirm" name="passwordConfirm" placeholder="Enter password again" autocomplete="new-password">
1591
+ </div>
1592
+ <div class="btn-row">
1593
+ <button type="button" class="btn-skip" id="skipBtn" onclick="doSkip()">Skip / 跳过</button>
1594
+ <button type="submit" class="btn-primary" id="saveBtn">Save / 保存</button>
1595
+ </div>
1596
+ </form>
1597
+ <div class="hint">Skip to keep default credentials <b>root/root</b>. You can change them later via <code>~/.aws-bridge/config.json</code>.<br>跳过以保留默认凭据 <b>root/root</b>。</div>
1598
+ </div>
1599
+ <script>
1600
+ async function doSave(e) {
1601
+ e.preventDefault();
1602
+ const errorEl = document.getElementById("errorMsg");
1603
+ const saveBtn = document.getElementById("saveBtn");
1604
+ errorEl.classList.remove("visible");
1605
+
1606
+ const username = document.getElementById("username").value.trim();
1607
+ const password = document.getElementById("password").value;
1608
+ const passwordConfirm = document.getElementById("passwordConfirm").value;
1609
+
1610
+ if (!username) {
1611
+ errorEl.textContent = "Username is required / 用户名不能为空";
1612
+ errorEl.classList.add("visible");
1613
+ return;
1614
+ }
1615
+ if (!password) {
1616
+ errorEl.textContent = "Password is required / 密码不能为空";
1617
+ errorEl.classList.add("visible");
1618
+ return;
1619
+ }
1620
+ if (password !== passwordConfirm) {
1621
+ errorEl.textContent = "Passwords do not match / 两次输入的密码不一致";
1622
+ errorEl.classList.add("visible");
1623
+ return;
1624
+ }
1625
+ if (password.length < 4) {
1626
+ errorEl.textContent = "Password must be at least 4 characters / 密码至少4位";
1627
+ errorEl.classList.add("visible");
1628
+ return;
1629
+ }
1630
+
1631
+ saveBtn.disabled = true;
1632
+ saveBtn.innerHTML = '<span class="spinner"></span>Saving...';
1633
+
1634
+ try {
1635
+ const resp = await fetch("/runtime/change-credentials", {
1636
+ method: "POST",
1637
+ headers: { "Content-Type": "application/json" },
1638
+ body: JSON.stringify({ username, password, passwordConfirm }),
1639
+ });
1640
+ const data = await resp.json();
1641
+ if (data.ok) {
1642
+ document.getElementById("successMsg").classList.add("visible");
1643
+ document.getElementById("setupForm").style.display = "none";
1644
+ document.querySelector(".hint").style.display = "none";
1645
+ document.querySelector(".badge").style.display = "none";
1646
+ setTimeout(() => { window.location.href = "/"; }, 1500);
1647
+ } else {
1648
+ errorEl.textContent = data.error || "Save failed";
1649
+ errorEl.classList.add("visible");
1650
+ saveBtn.disabled = false;
1651
+ saveBtn.textContent = "Save / 保存";
1652
+ }
1653
+ } catch (err) {
1654
+ errorEl.textContent = "Network error, please try again / 网络错误,请重试";
1655
+ errorEl.classList.add("visible");
1656
+ saveBtn.disabled = false;
1657
+ saveBtn.textContent = "Save / 保存";
1658
+ }
1659
+ }
1660
+
1661
+ async function doSkip() {
1662
+ const skipBtn = document.getElementById("skipBtn");
1663
+ const errorEl = document.getElementById("errorMsg");
1664
+ skipBtn.disabled = true;
1665
+ skipBtn.textContent = "Skipping... / 跳过中...";
1666
+
1667
+ try {
1668
+ const resp = await fetch("/runtime/change-credentials", {
1669
+ method: "POST",
1670
+ headers: { "Content-Type": "application/json" },
1671
+ body: JSON.stringify({ username: "root", password: "root", passwordConfirm: "root" }),
1672
+ });
1673
+ const data = await resp.json();
1674
+ if (data.ok) {
1675
+ document.getElementById("successMsg").classList.add("visible");
1676
+ document.getElementById("setupForm").style.display = "none";
1677
+ document.querySelector(".hint").style.display = "none";
1678
+ document.querySelector(".badge").style.display = "none";
1679
+ setTimeout(() => { window.location.href = "/"; }, 1500);
1680
+ } else {
1681
+ errorEl.textContent = data.error || "Skip failed";
1682
+ errorEl.classList.add("visible");
1683
+ skipBtn.disabled = false;
1684
+ skipBtn.textContent = "Skip / 跳过";
1685
+ }
1686
+ } catch (err) {
1687
+ errorEl.textContent = "Network error, please try again / 网络错误,请重试";
1688
+ errorEl.classList.add("visible");
1689
+ skipBtn.disabled = false;
1690
+ skipBtn.textContent = "Skip / 跳过";
1691
+ }
1692
+ }
1693
+
1694
+ document.getElementById("setupForm").addEventListener("submit", doSave);
1695
+ </script>
1696
+ </body>
1697
+ </html>`;
@@ -22,5 +22,20 @@ export declare function getPanelCredentials(): PanelCredentials;
22
22
  export declare function validateCredentials(username: string, password: string): boolean;
23
23
  /** 清除凭据缓存(配置文件变更后调用) */
24
24
  export declare function clearCredentialsCache(): void;
25
+ /**
26
+ * 检查 config.json 中是否已配置过面板凭据(panelUsername/panelPassword)。
27
+ * 用于首次登录引导:如果未配置过(文件不存在或缺少字段),应引导用户设置。
28
+ * 已配置过的场景(包括值正好为 root/root)不再重复引导。
29
+ */
30
+ export declare function hasConfiguredCredentials(): boolean;
31
+ /**
32
+ * 判断当前凭据是否为默认值 root/root。
33
+ */
34
+ export declare function isDefaultCredentials(): boolean;
35
+ /**
36
+ * 写入新的面板凭据到 config.json,并更新缓存。
37
+ * 主流程:读取现有配置 → 合并新字段 → 写回文件 → 清缓存。
38
+ */
39
+ export declare function setPanelCredentials(username: string, password: string): void;
25
40
  export {};
26
41
  //# sourceMappingURL=panel-auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"panel-auth.d.ts","sourceRoot":"","sources":["../../src/services/panel-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAwCH,yBAAyB;AACzB,wBAAgB,aAAa,IAAI,MAAM,CAMtC;AAED,kCAAkC;AAClC,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAWzE;AAED,iBAAiB;AACjB,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAElD;AAID,UAAU,gBAAgB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AA0BD,mCAAmC;AACnC,wBAAgB,mBAAmB,IAAI,gBAAgB,CAWtD;AAED,cAAc;AACd,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAGT;AAED,wBAAwB;AACxB,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}
1
+ {"version":3,"file":"panel-auth.d.ts","sourceRoot":"","sources":["../../src/services/panel-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAwCH,yBAAyB;AACzB,wBAAgB,aAAa,IAAI,MAAM,CAMtC;AAED,kCAAkC;AAClC,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAWzE;AAED,iBAAiB;AACjB,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAElD;AAID,UAAU,gBAAgB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AA0BD,mCAAmC;AACnC,wBAAgB,mBAAmB,IAAI,gBAAgB,CAWtD;AAED,cAAc;AACd,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAGT;AAED,wBAAwB;AACxB,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAWlD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAG9C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,IAAI,CA8BN"}
@@ -7,7 +7,7 @@
7
7
  * 会话使用服务端内存存储 + HttpOnly Cookie 实现,无需外部依赖。
8
8
  */
9
9
  import { randomUUID } from "node:crypto";
10
- import { existsSync, readFileSync } from "node:fs";
10
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
11
11
  import os from "node:os";
12
12
  import path from "node:path";
13
13
  import { logger } from "../utils/logger.js";
@@ -103,3 +103,58 @@ export function validateCredentials(username, password) {
103
103
  export function clearCredentialsCache() {
104
104
  cachedCredentials = null;
105
105
  }
106
+ /**
107
+ * 检查 config.json 中是否已配置过面板凭据(panelUsername/panelPassword)。
108
+ * 用于首次登录引导:如果未配置过(文件不存在或缺少字段),应引导用户设置。
109
+ * 已配置过的场景(包括值正好为 root/root)不再重复引导。
110
+ */
111
+ export function hasConfiguredCredentials() {
112
+ const configPath = getConfigFilePath();
113
+ if (!existsSync(configPath))
114
+ return false;
115
+ try {
116
+ const raw = readFileSync(configPath, "utf-8");
117
+ const parsed = JSON.parse(raw);
118
+ return !!(parsed.panelUsername && parsed.panelPassword);
119
+ }
120
+ catch {
121
+ return false;
122
+ }
123
+ }
124
+ /**
125
+ * 判断当前凭据是否为默认值 root/root。
126
+ */
127
+ export function isDefaultCredentials() {
128
+ const creds = getPanelCredentials();
129
+ return creds.username === "root" && creds.password === "root";
130
+ }
131
+ /**
132
+ * 写入新的面板凭据到 config.json,并更新缓存。
133
+ * 主流程:读取现有配置 → 合并新字段 → 写回文件 → 清缓存。
134
+ */
135
+ export function setPanelCredentials(username, password) {
136
+ const configPath = getConfigFilePath();
137
+ const configDir = path.dirname(configPath);
138
+ // 读取现有配置
139
+ let existing = {};
140
+ if (existsSync(configPath)) {
141
+ try {
142
+ existing = JSON.parse(readFileSync(configPath, "utf-8"));
143
+ }
144
+ catch {
145
+ // 配置不可读则从头创建
146
+ }
147
+ }
148
+ existing.panelUsername = username;
149
+ existing.panelPassword = password;
150
+ // 确保目录存在
151
+ try {
152
+ mkdirSync(configDir, { recursive: true });
153
+ }
154
+ catch {
155
+ // 目录已存在
156
+ }
157
+ writeFileSync(configPath, JSON.stringify(existing, null, 2), "utf-8");
158
+ clearCredentialsCache();
159
+ logger.info("[panel-auth] 面板凭据已更新");
160
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-runtime-bridge",
3
- "version": "1.9.9",
3
+ "version": "1.9.10",
4
4
  "description": "AgentsWorkStudio runtime bridge service for machine-level agent runtime integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",