@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.
- package/dist/pushpals-cli.js +310 -32
- package/package.json +1 -1
package/dist/pushpals-cli.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1313
|
-
const
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
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, "&")
|
|
1417
|
+
.replace(/</g, "<")
|
|
1418
|
+
.replace(/>/g, ">");
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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=${
|
|
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
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
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
|
+
};
|