botmux 2.75.1 → 2.76.1

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/cli.js CHANGED
@@ -42,6 +42,7 @@ import { invalidWorkingDirs } from './utils/working-dir.js';
42
42
  import { firstPositional } from './cli/arg-utils.js';
43
43
  import { dispatchPrimaryMessage, findStdinAliasAttachment, sendFileAttachments } from './cli/send-dispatch.js';
44
44
  import { buildPm2SpawnCommand } from './cli/pm2-command.js';
45
+ import { callDashboard } from './cli/dashboard-endpoint.js';
45
46
  import { rejectLikelyWindowsStdinMojibake } from './cli/stdin-encoding.js';
46
47
  import { formatBotInfoEntriesForCli, formatChatBotsForCli, } from './cli/bots-list-output.js';
47
48
  import { buildFooterAddressing, hasKnownBotMention, knownBotOpenIdsFromCrossRef, orderedFooterRecipients, } from './utils/bot-routing.js';
@@ -1368,47 +1369,18 @@ function cmdUpgrade() {
1368
1369
  }
1369
1370
  }
1370
1371
  /**
1371
- * Call one of the dashboard's loopback HMAC `/__cli/*` endpoints.
1372
- * - `/__cli/rotate` mints a fresh token and returns its URL, invalidating the
1373
- * previously-issued link.
1374
- * - `/__cli/current` returns the existing token's URL WITHOUT rotating (404 →
1375
- * no token has ever been minted → `no-active-token`).
1376
- * Returns { ok: true, url } on success, or { ok: false, reason } so callers can
1377
- * decide how to surface the failure (hard error vs soft hint).
1372
+ * Call one of the dashboard's loopback HMAC `/__cli/*` endpoints. Thin wrapper
1373
+ * over {@link callDashboard}, which handles 404 disambiguation and self-heals a
1374
+ * stale `.dashboard-port` that points at the wrong service (e.g. daemon IPC).
1375
+ * See `src/cli/dashboard-endpoint.ts` for the why.
1378
1376
  */
1379
1377
  async function callDashboardEndpoint(path) {
1380
- const SECRET_PATH = join(CONFIG_DIR, '.dashboard-secret');
1381
- if (!existsSync(SECRET_PATH))
1382
- return { ok: false, reason: 'no-secret' };
1383
- const secret = readFileSync(SECRET_PATH, 'utf8').trim();
1384
- const ts = Math.floor(Date.now() / 1000).toString();
1385
- const nonce = randomBytes(8).toString('hex');
1386
- const sig = createHmac('sha256', secret).update(`${ts}:${nonce}`).digest('base64url');
1387
- const portFile = join(CONFIG_DIR, '.dashboard-port');
1388
- const port = (existsSync(portFile) ? readFileSync(portFile, 'utf8').trim() : '')
1389
- || process.env.BOTMUX_DASHBOARD_PORT
1390
- || '7891';
1391
- let res;
1392
- try {
1393
- res = await fetch(`http://127.0.0.1:${port}${path}`, {
1394
- method: 'POST',
1395
- headers: {
1396
- 'X-Botmux-Cli-Ts': ts,
1397
- 'X-Botmux-Cli-Nonce': nonce,
1398
- 'X-Botmux-Cli-Auth': sig,
1399
- },
1400
- });
1401
- }
1402
- catch {
1403
- return { ok: false, reason: 'unreachable' };
1404
- }
1405
- if (res.status === 404)
1406
- return { ok: false, reason: 'no-active-token' };
1407
- if (!res.ok) {
1408
- return { ok: false, reason: 'http-error', detail: `${res.status} ${await res.text()}` };
1409
- }
1410
- const body = await res.json();
1411
- return { ok: true, url: body.url };
1378
+ return callDashboard({
1379
+ configDir: CONFIG_DIR,
1380
+ defaultPort: 7891,
1381
+ envPort: process.env.BOTMUX_DASHBOARD_PORT,
1382
+ path,
1383
+ });
1412
1384
  }
1413
1385
  /**
1414
1386
  * Best-effort dashboard hint printed after start/restart. Reads the LIVE link
@@ -1428,8 +1400,10 @@ async function printDashboardHintWithRetry() {
1428
1400
  return;
1429
1401
  }
1430
1402
  // Terminal states — file-backed secret/token won't appear mid-poll, unlike
1431
- // a not-yet-listening port. Don't spin on them.
1432
- if (last.reason === 'no-secret' || last.reason === 'no-active-token')
1403
+ // a not-yet-listening port. `wrong-service` means the port file points at a
1404
+ // non-dashboard server and discovery already failed to find it, so retrying
1405
+ // won't help either. Don't spin on any of them.
1406
+ if (last.reason === 'no-secret' || last.reason === 'no-active-token' || last.reason === 'wrong-service')
1433
1407
  break;
1434
1408
  await new Promise(r => setTimeout(r, stepMs));
1435
1409
  }
@@ -1440,6 +1414,9 @@ async function printDashboardHintWithRetry() {
1440
1414
  else if (last?.reason === 'no-secret') {
1441
1415
  console.log(' 面板: dashboard 凭证未就绪,启动后可用 `botmux dashboard` 获取链接');
1442
1416
  }
1417
+ else if (last?.reason === 'wrong-service') {
1418
+ console.log(' 面板: `botmux dashboard`(端口文件可能已失效,必要时 `botmux restart` 刷新)');
1419
+ }
1443
1420
  else {
1444
1421
  console.log(' 面板: `botmux dashboard`(daemon 启动中,稍后可获取链接)');
1445
1422
  }
@@ -1455,15 +1432,24 @@ async function cmdDashboard() {
1455
1432
  console.log(r.url);
1456
1433
  return;
1457
1434
  }
1435
+ const portFile = join(CONFIG_DIR, '.dashboard-port');
1436
+ const recordedPort = (existsSync(portFile) ? readFileSync(portFile, 'utf8').trim() : '')
1437
+ || process.env.BOTMUX_DASHBOARD_PORT
1438
+ || '7891';
1458
1439
  if (r.reason === 'no-secret') {
1459
1440
  console.error('Dashboard not initialised. Run `botmux restart` first.');
1460
1441
  }
1461
1442
  else if (r.reason === 'unreachable') {
1462
- const portFile = join(CONFIG_DIR, '.dashboard-port');
1463
- const port = (existsSync(portFile) ? readFileSync(portFile, 'utf8').trim() : '')
1464
- || process.env.BOTMUX_DASHBOARD_PORT
1465
- || '7891';
1466
- console.error(`dashboard process not reachable on 127.0.0.1:${port} \`botmux restart\` will start it`);
1443
+ console.error(`dashboard process not reachable on 127.0.0.1:${recordedPort} — \`botmux restart\` will start it`);
1444
+ }
1445
+ else if (r.reason === 'wrong-service') {
1446
+ // 127.0.0.1:<port> answered, but it isn't the dashboard (typically the
1447
+ // daemon IPC server holding a port the stale .dashboard-port points at),
1448
+ // and rediscovery across the probe range found no dashboard either.
1449
+ console.error(`127.0.0.1:${recordedPort} 上的服务不是 dashboard(端口文件 ~/.botmux/.dashboard-port 已失效,可能指向了 daemon IPC)。` +
1450
+ '运行 `botmux restart` 重启 dashboard 并刷新端口文件。');
1451
+ if (r.detail)
1452
+ console.error(` 详情: ${r.detail}`);
1467
1453
  }
1468
1454
  else {
1469
1455
  // `no-active-token` can't occur on rotate (it always mints); fall through.