@pushpalsdev/cli 1.0.5 → 1.0.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.
Files changed (2) hide show
  1. package/dist/pushpals-cli.js +310 -32
  2. package/package.json +1 -1
@@ -742,12 +742,14 @@ function loadPushPalsConfig(options = {}) {
742
742
  // ../../scripts/pushpals-cli.ts
743
743
  var DEFAULT_MONITOR_PORT = 8081;
744
744
  var MONITOR_SCAN_PORTS = 32;
745
+ var MONITOR_POLL_MS = 2000;
745
746
  var HTTP_TIMEOUT_MS = 2500;
746
747
  var LOCALBUDDY_TIMEOUT_MS = 4000;
747
748
  var SSE_RECONNECT_MS = 1500;
748
749
  var DEFAULT_RUNTIME_BOOT_TIMEOUT_MS = 90000;
749
750
  var DEFAULT_RUNTIME_BOOT_POLL_MS = 1000;
750
751
  var DEFAULT_SERVER_BOOT_TIMEOUT_MS = 20000;
752
+ var DEFAULT_SERVICE_STABILITY_GRACE_MS = 4000;
751
753
  var GITHUB_OWNER = "PushPalsDev";
752
754
  var GITHUB_REPO = "pushpals";
753
755
  var GITHUB_API_URL = `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}`;
@@ -858,6 +860,9 @@ function normalizePath(value) {
858
860
  return normalized.toLowerCase();
859
861
  return normalized;
860
862
  }
863
+ function jsonHtmlBootstrap(value) {
864
+ return JSON.stringify(value).replace(/</g, "\\u003c");
865
+ }
861
866
  async function runGit(args, cwd) {
862
867
  const proc = Bun.spawn(["git", ...args], {
863
868
  cwd,
@@ -1002,6 +1007,17 @@ function runtimeBinaryFilename(serviceName, platformKey) {
1002
1007
  const extension = platformKey.startsWith("windows-") ? ".exe" : "";
1003
1008
  return `pushpals-runtime-${serviceToken}-${platformKey}${extension}`;
1004
1009
  }
1010
+ function buildEmbeddedRuntimeEnv(baseEnv, opts) {
1011
+ return {
1012
+ ...baseEnv,
1013
+ PUSHPALS_REPO_ROOT_OVERRIDE: opts.repoRoot,
1014
+ PUSHPALS_PROJECT_ROOT_OVERRIDE: opts.repoRoot,
1015
+ PUSHPALS_CONFIG_DIR_OVERRIDE: join2(opts.runtimeRoot, "configs"),
1016
+ PUSHPALS_PROMPTS_ROOT_OVERRIDE: opts.runtimeRoot,
1017
+ PUSHPALS_PROTOCOL_SCHEMAS_DIR: join2(opts.runtimeRoot, "protocol", "schemas"),
1018
+ REMOTEBUDDY_AUTONOMY_ENABLED: String(baseEnv.REMOTEBUDDY_AUTONOMY_ENABLED ?? "").trim() || "false"
1019
+ };
1020
+ }
1005
1021
  function timestampFileToken() {
1006
1022
  return new Date().toISOString().replace(/[:.]/g, "-");
1007
1023
  }
@@ -1112,10 +1128,25 @@ function spawnRuntimeService(name, command, cwd, env, logPath) {
1112
1128
  function stopRuntimeServices(services) {
1113
1129
  for (const service of services) {
1114
1130
  try {
1115
- service.proc.kill();
1131
+ if (process.platform === "win32" && typeof service.proc.pid === "number" && service.proc.pid > 0) {
1132
+ Bun.spawnSync(["taskkill", "/PID", String(service.proc.pid), "/T", "/F"], {
1133
+ stdin: "ignore",
1134
+ stdout: "ignore",
1135
+ stderr: "ignore"
1136
+ });
1137
+ } else {
1138
+ service.proc.kill();
1139
+ }
1116
1140
  } catch {}
1117
1141
  }
1118
1142
  }
1143
+ async function repoHasRemote(repoRoot, remote) {
1144
+ const normalizedRemote = remote.trim();
1145
+ if (!normalizedRemote)
1146
+ return false;
1147
+ const result = await runGit(["remote", "get-url", normalizedRemote], repoRoot);
1148
+ return result.ok && Boolean(result.stdout);
1149
+ }
1119
1150
  async function probeServer(serverUrl) {
1120
1151
  try {
1121
1152
  const response = await fetchWithTimeout(`${serverUrl}/healthz`, {}, HTTP_TIMEOUT_MS);
@@ -1169,14 +1200,10 @@ async function autoStartRuntimeServices(opts) {
1169
1200
  console.log(`[pushpals] LocalBuddy unavailable. Auto-starting runtime for repo: ${opts.repoRoot}`);
1170
1201
  console.log(`[pushpals] runtimeRoot=${runtimeRoot}`);
1171
1202
  console.log(`[pushpals] runtimeTag=${runtimeTag}`);
1172
- const runtimeEnv = {
1173
- ...process.env,
1174
- PUSHPALS_REPO_ROOT_OVERRIDE: opts.repoRoot,
1175
- PUSHPALS_PROJECT_ROOT_OVERRIDE: opts.repoRoot,
1176
- PUSHPALS_CONFIG_DIR_OVERRIDE: join2(runtimeRoot, "configs"),
1177
- PUSHPALS_PROMPTS_ROOT_OVERRIDE: runtimeRoot,
1178
- PUSHPALS_PROTOCOL_SCHEMAS_DIR: join2(runtimeRoot, "protocol", "schemas")
1179
- };
1203
+ const runtimeEnv = buildEmbeddedRuntimeEnv(process.env, {
1204
+ repoRoot: opts.repoRoot,
1205
+ runtimeRoot
1206
+ });
1180
1207
  const services = [];
1181
1208
  const runToken = timestampFileToken();
1182
1209
  const logDir = join2(runtimeRoot, "logs", "bootstrap");
@@ -1224,11 +1251,14 @@ ${tail}` : ""}`);
1224
1251
  services.push(remotebuddyService);
1225
1252
  console.log(`[pushpals] remotebuddy log: ${remotebuddyService.logPath}`);
1226
1253
  const scmHealthy = await probeSourceControlManager(opts.sourceControlManagerPort);
1227
- if (!scmHealthy) {
1254
+ const scmRemoteAvailable = await repoHasRemote(opts.repoRoot, opts.sourceControlManagerRemote);
1255
+ if (!scmHealthy && scmRemoteAvailable) {
1228
1256
  console.log("[pushpals] Starting embedded SourceControlManager...");
1229
1257
  const sourceControlManagerService = spawnRuntimeService("source_control_manager", [runtimeBinaries.sourceControlManager, "--skip-clean-check"], opts.repoRoot, runtimeEnv, logPathFor("source_control_manager"));
1230
1258
  services.push(sourceControlManagerService);
1231
1259
  console.log(`[pushpals] source_control_manager log: ${sourceControlManagerService.logPath}`);
1260
+ } else if (!scmRemoteAvailable) {
1261
+ console.log(`[pushpals] Repo has no git remote "${opts.sourceControlManagerRemote}"; skipping embedded SourceControlManager.`);
1232
1262
  } else {
1233
1263
  console.log("[pushpals] SourceControlManager already healthy; skipping embedded start.");
1234
1264
  }
@@ -1256,6 +1286,19 @@ ${tail}` : ""}`);
1256
1286
  }
1257
1287
  const health = await probeLocalBuddy(opts.localAgentUrl, opts.authToken);
1258
1288
  if (health?.ok) {
1289
+ const stabilityDeadline = Date.now() + DEFAULT_SERVICE_STABILITY_GRACE_MS;
1290
+ while (Date.now() < stabilityDeadline) {
1291
+ for (const service of services) {
1292
+ if (!service.exited)
1293
+ continue;
1294
+ const tail = readLogTail(service.logPath);
1295
+ stopRuntimeServices(services);
1296
+ throw new Error(`Embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}). ` + `See ${service.logPath}${tail ? `
1297
+ --- ${service.name} log tail ---
1298
+ ${tail}` : ""}`);
1299
+ }
1300
+ await Bun.sleep(250);
1301
+ }
1259
1302
  console.log("[pushpals] Embedded runtime is ready.");
1260
1303
  return services;
1261
1304
  }
@@ -1309,17 +1352,221 @@ async function looksLikeMonitoringHub(url) {
1309
1352
  return false;
1310
1353
  }
1311
1354
  }
1312
- async function resolveMonitoringHubUrl(preferredUrl, fallbackPort) {
1313
- const explicit = normalizeUrl(preferredUrl);
1314
- if (explicit)
1315
- return explicit;
1316
- const basePort = fallbackPort;
1317
- for (let port = basePort;port < basePort + MONITOR_SCAN_PORTS; port++) {
1318
- const candidate = `http://localhost:${port}`;
1319
- if (await looksLikeMonitoringHub(candidate))
1320
- return candidate;
1355
+ function buildEmbeddedMonitoringHubHtml(opts) {
1356
+ const bootstrap = jsonHtmlBootstrap(opts);
1357
+ return `<!doctype html>
1358
+ <html lang="en">
1359
+ <head>
1360
+ <meta charset="utf-8" />
1361
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1362
+ <title>PushPals CLI Monitor</title>
1363
+ <style>
1364
+ :root { color-scheme: dark; --bg:#08111b; --panel:#112235; --panel2:#16324a; --line:#2b5876; --fg:#edf6ff; --muted:#90b5d6; --accent:#58d8c3; --warn:#ffbf5f; --bad:#ff7f7f; }
1365
+ * { box-sizing:border-box; }
1366
+ body { margin:0; font-family:Consolas, "SFMono-Regular", monospace; background:radial-gradient(circle at top, #0d2233, var(--bg) 56%); color:var(--fg); }
1367
+ main { max-width:1200px; margin:0 auto; padding:24px; }
1368
+ h1,h2 { margin:0 0 12px; }
1369
+ p { color:var(--muted); }
1370
+ .row { display:grid; gap:16px; margin-top:16px; }
1371
+ .cards { grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); }
1372
+ .panels { grid-template-columns:repeat(auto-fit,minmax(320px,1fr)); }
1373
+ .card, .panel { border:1px solid var(--line); background:linear-gradient(180deg,var(--panel),var(--panel2)); border-radius:16px; padding:16px; box-shadow:0 12px 40px rgba(0,0,0,.22); }
1374
+ .label { font-size:12px; letter-spacing:.12em; text-transform:uppercase; color:var(--muted); margin-bottom:10px; }
1375
+ .value { font-size:32px; font-weight:700; color:var(--accent); }
1376
+ .sub { margin-top:8px; color:var(--muted); white-space:pre-wrap; word-break:break-word; }
1377
+ .list { display:grid; gap:10px; margin-top:12px; }
1378
+ .item { border:1px solid rgba(88,216,195,.18); border-radius:12px; padding:12px; background:rgba(8,17,27,.42); }
1379
+ .meta { display:flex; flex-wrap:wrap; gap:8px; margin:12px 0 0; }
1380
+ .pill { border:1px solid var(--line); border-radius:999px; padding:6px 10px; color:var(--muted); }
1381
+ a { color:var(--accent); }
1382
+ </style>
1383
+ </head>
1384
+ <body>
1385
+ <main>
1386
+ <h1>PushPals CLI Monitor</h1>
1387
+ <p>Lightweight embedded monitor for CLI-managed runtimes.</p>
1388
+ <div class="meta" id="meta"></div>
1389
+ <section class="row cards" id="cards"></section>
1390
+ <section class="row panels">
1391
+ <div class="panel">
1392
+ <h2>Requests</h2>
1393
+ <div id="requests" class="list"></div>
1394
+ </div>
1395
+ <div class="panel">
1396
+ <h2>Jobs</h2>
1397
+ <div id="jobs" class="list"></div>
1398
+ </div>
1399
+ <div class="panel">
1400
+ <h2>Completions</h2>
1401
+ <div id="completions" class="list"></div>
1402
+ </div>
1403
+ </section>
1404
+ </main>
1405
+ <script>
1406
+ const boot = ${bootstrap};
1407
+ const pollMs = ${MONITOR_POLL_MS};
1408
+ const metaEl = document.getElementById("meta");
1409
+ const cardsEl = document.getElementById("cards");
1410
+ const requestsEl = document.getElementById("requests");
1411
+ const jobsEl = document.getElementById("jobs");
1412
+ const completionsEl = document.getElementById("completions");
1413
+
1414
+ function esc(value) {
1415
+ return String(value ?? "")
1416
+ .replace(/&/g, "&amp;")
1417
+ .replace(/</g, "&lt;")
1418
+ .replace(/>/g, "&gt;");
1419
+ }
1420
+
1421
+ async function fetchJson(path) {
1422
+ const res = await fetch(path, { cache: "no-store" });
1423
+ if (!res.ok) throw new Error(path + " -> HTTP " + res.status);
1424
+ return await res.json();
1425
+ }
1426
+
1427
+ function setList(target, rows, emptyLabel, formatter) {
1428
+ if (!Array.isArray(rows) || rows.length === 0) {
1429
+ target.innerHTML = '<div class="item">' + esc(emptyLabel) + "</div>";
1430
+ return;
1431
+ }
1432
+ target.innerHTML = rows.map((row) => '<div class="item">' + formatter(row) + "</div>").join("");
1433
+ }
1434
+
1435
+ function renderStatus(status) {
1436
+ const workers = status?.workers ?? {};
1437
+ const queues = status?.queues ?? {};
1438
+ const runtime = status?.runtime ?? {};
1439
+ const repo = status?.repo ?? {};
1440
+ const llmUsage = status?.llmUsage ?? {};
1441
+ const cards = [
1442
+ { label: "Server uptime", value: Math.round((Number(runtime.uptimeMs ?? 0) / 60000)) + "m", sub: runtime.startedAt ?? "unknown" },
1443
+ { label: "Workers online", value: String(workers.online ?? 0), sub: "busy " + String(workers.busy ?? 0) + " | idle " + String(workers.idle ?? 0) },
1444
+ { label: "Pending requests", value: String(queues.requests?.pending ?? 0), sub: "claimed " + String(queues.requests?.claimed ?? 0) },
1445
+ { label: "Pending jobs", value: String(queues.jobs?.pending ?? 0), sub: "claimed " + String(queues.jobs?.claimed ?? 0) },
1446
+ { label: "Completions", value: String(queues.completions?.pending ?? 0), sub: "processed " + String(queues.completions?.processed ?? 0) },
1447
+ { label: "LLM usage (24h)", value: String(llmUsage.totalTokens ?? 0), sub: "calls " + String(llmUsage.totalCalls ?? 0) }
1448
+ ];
1449
+ cardsEl.innerHTML = cards.map((card) => '<div class="card"><div class="label">' + esc(card.label) + '</div><div class="value">' + esc(card.value) + '</div><div class="sub">' + esc(card.sub) + '</div></div>').join("");
1450
+ metaEl.innerHTML = [
1451
+ '<span class="pill">server ' + esc(boot.serverUrl) + '</span>',
1452
+ '<span class="pill">localbuddy ' + esc(boot.localAgentUrl) + '</span>',
1453
+ '<span class="pill">session ' + esc(boot.sessionId) + '</span>',
1454
+ '<span class="pill">repo ' + esc(repo?.root ?? repo?.remoteUrl ?? "current repo") + '</span>'
1455
+ ].join("");
1456
+ }
1457
+
1458
+ function render() {
1459
+ Promise.all([
1460
+ fetchJson('/api/status'),
1461
+ fetchJson('/api/requests'),
1462
+ fetchJson('/api/jobs'),
1463
+ fetchJson('/api/completions')
1464
+ ]).then(([status, requests, jobs, completions]) => {
1465
+ renderStatus(status);
1466
+ setList(requestsEl, requests?.requests?.slice(0, 8), 'No requests', (row) =>
1467
+ '<strong>' + esc(row?.priority ?? 'request') + '</strong><div class="sub">' +
1468
+ esc((row?.status ?? 'unknown') + ' | ' + (row?.id ?? '')) + '</div><div class="sub">' +
1469
+ esc(String(row?.prompt ?? '').slice(0, 220)) + '</div>');
1470
+ setList(jobsEl, jobs?.jobs?.slice(0, 8), 'No jobs', (row) =>
1471
+ '<strong>' + esc(row?.kind ?? 'job') + '</strong><div class="sub">' +
1472
+ esc((row?.status ?? 'unknown') + ' | worker ' + (row?.workerId ?? '--')) + '</div><div class="sub">' +
1473
+ esc((row?.summary ?? row?.error ?? row?.id ?? '').slice(0, 220)) + '</div>');
1474
+ setList(completionsEl, completions?.completions?.slice(0, 8), 'No completions', (row) =>
1475
+ '<strong>' + esc(row?.status ?? 'completion') + '</strong><div class="sub">' +
1476
+ esc((row?.jobId ?? '') + ' | ' + (row?.commitSha ?? '')) + '</div><div class="sub">' +
1477
+ esc((row?.message ?? '').slice(0, 220)) + '</div>');
1478
+ }).catch((err) => {
1479
+ cardsEl.innerHTML = '<div class="card"><div class="label">Monitor error</div><div class="sub">' + esc(err?.message ?? err) + '</div></div>';
1480
+ });
1481
+ }
1482
+
1483
+ render();
1484
+ setInterval(render, pollMs);
1485
+ </script>
1486
+ </body>
1487
+ </html>`;
1488
+ }
1489
+ async function proxyMonitoringHubRequest(serverUrl, authToken, pathValue) {
1490
+ const target = `${serverUrl}${pathValue}`;
1491
+ const upstream = await fetchWithTimeout(target, { headers: authHeaders(authToken) }, 1e4);
1492
+ const body = await upstream.text();
1493
+ return new Response(body, {
1494
+ status: upstream.status,
1495
+ headers: {
1496
+ "content-type": String(upstream.headers.get("content-type") ?? "application/json"),
1497
+ "cache-control": "no-store"
1498
+ }
1499
+ });
1500
+ }
1501
+ async function startEmbeddedMonitoringHub(opts) {
1502
+ const html = buildEmbeddedMonitoringHubHtml({
1503
+ serverUrl: opts.serverUrl,
1504
+ localAgentUrl: opts.localAgentUrl,
1505
+ sessionId: opts.sessionId
1506
+ });
1507
+ const candidatePorts = Array.from({ length: MONITOR_SCAN_PORTS }, (_, index) => opts.preferredPort + index).concat(0);
1508
+ for (const port of candidatePorts) {
1509
+ try {
1510
+ const server = Bun.serve({
1511
+ port,
1512
+ idleTimeout: 30,
1513
+ fetch: async (req) => {
1514
+ const url = new URL(req.url);
1515
+ if (url.pathname === "/") {
1516
+ return new Response(html, {
1517
+ headers: {
1518
+ "content-type": "text/html; charset=utf-8",
1519
+ "cache-control": "no-store"
1520
+ }
1521
+ });
1522
+ }
1523
+ if (url.pathname === "/healthz") {
1524
+ return Response.json({ ok: true, port, serverUrl: opts.serverUrl, sessionId: opts.sessionId });
1525
+ }
1526
+ if (url.pathname === "/api/status") {
1527
+ return await proxyMonitoringHubRequest(opts.serverUrl, opts.authToken, "/system/status");
1528
+ }
1529
+ if (url.pathname === "/api/requests") {
1530
+ return await proxyMonitoringHubRequest(opts.serverUrl, opts.authToken, "/requests?status=all&limit=20");
1531
+ }
1532
+ if (url.pathname === "/api/jobs") {
1533
+ return await proxyMonitoringHubRequest(opts.serverUrl, opts.authToken, "/jobs?status=all&limit=20");
1534
+ }
1535
+ if (url.pathname === "/api/completions") {
1536
+ return await proxyMonitoringHubRequest(opts.serverUrl, opts.authToken, "/completions?status=all&limit=20");
1537
+ }
1538
+ return new Response("Not found", { status: 404 });
1539
+ }
1540
+ });
1541
+ return {
1542
+ url: `http://127.0.0.1:${server.port}`,
1543
+ port: Number(server.port),
1544
+ embedded: true,
1545
+ stop: () => server.stop(true)
1546
+ };
1547
+ } catch {}
1548
+ }
1549
+ return null;
1550
+ }
1551
+ async function resolveMonitoringHub(opts) {
1552
+ const explicit = normalizeUrl(opts.preferredUrl);
1553
+ if (explicit) {
1554
+ if (await looksLikeMonitoringHub(explicit)) {
1555
+ return { url: explicit, port: 0, stop: () => {}, embedded: false };
1556
+ }
1557
+ console.warn(`[pushpals] Preferred monitoring hub ${explicit} is unavailable; starting embedded monitor instead.`);
1558
+ }
1559
+ for (let port = opts.fallbackPort;port < opts.fallbackPort + MONITOR_SCAN_PORTS; port++) {
1560
+ const candidate = `http://127.0.0.1:${port}`;
1561
+ if (await looksLikeMonitoringHub(candidate)) {
1562
+ return { url: candidate, port, stop: () => {}, embedded: false };
1563
+ }
1564
+ }
1565
+ const embedded = await startEmbeddedMonitoringHub(opts);
1566
+ if (!embedded) {
1567
+ console.warn("[pushpals] Embedded monitoring hub could not start on any expected local port.");
1321
1568
  }
1322
- return `http://localhost:${basePort}`;
1569
+ return embedded;
1323
1570
  }
1324
1571
  async function sendMessageToLocalBuddy(localAgentUrl, text) {
1325
1572
  let response;
@@ -1493,8 +1740,7 @@ async function runSessionStream(serverUrl, sessionId, authToken, print, signal)
1493
1740
  async function openMonitoringHub(url) {
1494
1741
  let cmd = null;
1495
1742
  if (process.platform === "win32") {
1496
- const escaped = url.replace(/'/g, "''");
1497
- cmd = ["powershell", "-NoProfile", "-Command", `Start-Process '${escaped}'`];
1743
+ cmd = ["cmd", "/c", "start", "", url];
1498
1744
  } else if (process.platform === "darwin") {
1499
1745
  cmd = ["open", url];
1500
1746
  } else {
@@ -1542,6 +1788,7 @@ async function main() {
1542
1788
  serverUrl,
1543
1789
  localAgentUrl,
1544
1790
  sourceControlManagerPort: config.sourceControlManager.port,
1791
+ sourceControlManagerRemote: config.sourceControlManager.remote,
1545
1792
  authToken,
1546
1793
  runtimeRoot: parsed.runtimeRoot,
1547
1794
  runtimeTag: parsed.runtimeTag
@@ -1583,16 +1830,31 @@ async function main() {
1583
1830
  const saved = readCliState(statePath);
1584
1831
  const preferredHubUrl = normalizeUrl(parsed.monitoringHubUrl ?? process.env.PUSHPALS_MONITOR_URL ?? saved.monitoringHubUrl ?? "");
1585
1832
  const monitorPort = parsePositiveInt(process.env.PUSHPALS_CLIENT_PORT, DEFAULT_MONITOR_PORT);
1586
- const monitoringHubUrl = await resolveMonitoringHubUrl(preferredHubUrl, monitorPort);
1833
+ const monitoringHub = await resolveMonitoringHub({
1834
+ preferredUrl: preferredHubUrl,
1835
+ fallbackPort: monitorPort,
1836
+ serverUrl,
1837
+ localAgentUrl,
1838
+ sessionId: localBuddySessionId,
1839
+ authToken
1840
+ });
1841
+ const monitoringHubUrl = monitoringHub?.url ?? "";
1587
1842
  writeCliState(statePath, {
1588
- monitoringHubUrl,
1843
+ monitoringHubUrl: monitoringHubUrl || undefined,
1589
1844
  serverUrl,
1590
1845
  localAgentUrl,
1591
1846
  sessionId: localBuddySessionId,
1592
1847
  repoRoot
1593
1848
  });
1594
1849
  console.log("[pushpals] Connected.");
1595
- console.log(`monitoringHubUrl=${monitoringHubUrl}`);
1850
+ if (monitoringHubUrl) {
1851
+ console.log(`monitoringHubUrl=${monitoringHubUrl}`);
1852
+ if (monitoringHub?.embedded) {
1853
+ console.log("[pushpals] Embedded monitoring hub is running.");
1854
+ }
1855
+ } else {
1856
+ console.log("monitoringHubUrl=unavailable");
1857
+ }
1596
1858
  console.log(`serverUrl=${serverUrl}`);
1597
1859
  console.log(`localAgentUrl=${localAgentUrl}`);
1598
1860
  console.log(`sessionId=${localBuddySessionId}`);
@@ -1622,10 +1884,14 @@ ${line}
1622
1884
  streamAbort.abort();
1623
1885
  if (rl)
1624
1886
  rl.close();
1887
+ try {
1888
+ monitoringHub?.stop();
1889
+ } catch {}
1625
1890
  stopAutoStartedServices();
1626
1891
  };
1627
1892
  process.once("SIGINT", requestStop);
1628
1893
  process.once("SIGTERM", requestStop);
1894
+ process.once("exit", requestStop);
1629
1895
  rl = createInterface({
1630
1896
  input: process.stdin,
1631
1897
  output: process.stdout,
@@ -1644,20 +1910,25 @@ ${line}
1644
1910
  break;
1645
1911
  }
1646
1912
  if (text === "/hub") {
1647
- console.log(`monitoringHubUrl=${monitoringHubUrl}`);
1913
+ console.log(monitoringHubUrl ? `monitoringHubUrl=${monitoringHubUrl}` : "monitoringHubUrl=unavailable");
1648
1914
  rl.prompt();
1649
1915
  continue;
1650
1916
  }
1651
1917
  if (text === "/status") {
1652
1918
  console.log(`serverUrl=${serverUrl}`);
1653
1919
  console.log(`localAgentUrl=${localAgentUrl}`);
1654
- console.log(`sessionId=${sessionId}`);
1920
+ console.log(`sessionId=${localBuddySessionId}`);
1655
1921
  console.log(`repoRoot=${repoRoot}`);
1656
- console.log(`monitoringHubUrl=${monitoringHubUrl}`);
1922
+ console.log(monitoringHubUrl ? `monitoringHubUrl=${monitoringHubUrl}` : "monitoringHubUrl=unavailable");
1657
1923
  rl.prompt();
1658
1924
  continue;
1659
1925
  }
1660
1926
  if (text === "/open") {
1927
+ if (!monitoringHubUrl) {
1928
+ console.log("[pushpals] Monitoring hub is unavailable.");
1929
+ rl.prompt();
1930
+ continue;
1931
+ }
1661
1932
  const opened = await openMonitoringHub(monitoringHubUrl);
1662
1933
  console.log(opened ? `[pushpals] Opened ${monitoringHubUrl}` : `[pushpals] Failed to open browser. Use this link: ${monitoringHubUrl}`);
1663
1934
  rl.prompt();
@@ -1672,7 +1943,14 @@ ${line}
1672
1943
  requestStop();
1673
1944
  await Promise.race([streamTask, Bun.sleep(2000)]);
1674
1945
  }
1675
- main().catch((err) => {
1676
- console.error(`[pushpals] Fatal: ${String(err)}`);
1677
- process.exit(1);
1678
- });
1946
+ if (import.meta.main) {
1947
+ main().catch((err) => {
1948
+ console.error(`[pushpals] Fatal: ${String(err)}`);
1949
+ process.exit(1);
1950
+ });
1951
+ }
1952
+ export {
1953
+ startEmbeddedMonitoringHub,
1954
+ buildEmbeddedRuntimeEnv,
1955
+ buildEmbeddedMonitoringHubHtml
1956
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushpalsdev/cli",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "PushPals terminal CLI for LocalBuddy -> RemoteBuddy orchestration",
5
5
  "license": "MIT",
6
6
  "repository": {