@pushpalsdev/cli 1.0.9 → 1.0.11

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.
@@ -6,12 +6,12 @@ import {
6
6
  appendFileSync,
7
7
  chmodSync,
8
8
  cpSync,
9
- existsSync as existsSync3,
9
+ existsSync as existsSync4,
10
10
  mkdirSync,
11
- readFileSync as readFileSync3,
11
+ readFileSync as readFileSync4,
12
12
  writeFileSync
13
13
  } from "fs";
14
- import { dirname, extname, join as join2, resolve as resolve3 } from "path";
14
+ import { basename, delimiter, dirname, extname, join as join2, resolve as resolve4 } from "path";
15
15
  import { createInterface } from "readline";
16
16
 
17
17
  // ../shared/src/client_preflight.ts
@@ -21,12 +21,53 @@ import { relative, resolve as resolve2 } from "path";
21
21
  // ../shared/src/config.ts
22
22
  import { existsSync, readFileSync } from "fs";
23
23
  import { join, resolve, isAbsolute } from "path";
24
+
25
+ // ../shared/src/local_network.ts
26
+ var DEFAULT_LOCAL_LOOPBACK_HOST = "127.0.0.1";
27
+ function isLoopbackHost(hostname) {
28
+ const normalized = String(hostname ?? "").trim().toLowerCase().replace(/^\[(.*)\]$/, "$1");
29
+ return normalized === "127.0.0.1" || normalized === "::1" || normalized === "localhost";
30
+ }
31
+ function normalizeLoopbackHost(hostname) {
32
+ const normalized = String(hostname ?? "").trim().toLowerCase().replace(/^\[(.*)\]$/, "$1");
33
+ if (isLoopbackHost(normalized))
34
+ return DEFAULT_LOCAL_LOOPBACK_HOST;
35
+ return DEFAULT_LOCAL_LOOPBACK_HOST;
36
+ }
37
+ function normalizeLoopbackHttpUrl(value, fallbackPort) {
38
+ const fallback = `http://${DEFAULT_LOCAL_LOOPBACK_HOST}:${Math.max(1, fallbackPort)}`;
39
+ const text = String(value ?? "").trim();
40
+ if (!text)
41
+ return fallback;
42
+ try {
43
+ const parsed = new URL(text);
44
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
45
+ return fallback;
46
+ }
47
+ parsed.protocol = "http:";
48
+ parsed.username = "";
49
+ parsed.password = "";
50
+ parsed.hostname = normalizeLoopbackHost(parsed.hostname);
51
+ parsed.pathname = "/";
52
+ parsed.search = "";
53
+ parsed.hash = "";
54
+ if (!parsed.port) {
55
+ parsed.port = String(Math.max(1, fallbackPort));
56
+ }
57
+ return parsed.toString().replace(/\/+$/, "");
58
+ } catch {
59
+ return fallback;
60
+ }
61
+ }
62
+
63
+ // ../shared/src/config.ts
24
64
  var PROJECT_ROOT = resolve(import.meta.dir, "..", "..", "..");
25
65
  var DEFAULT_CONFIG_DIR = "configs";
26
66
  var LEGACY_CONFIG_DIR = "config";
27
67
  var TRUTHY = new Set(["1", "true", "yes", "on"]);
28
68
  var FALSY = new Set(["0", "false", "no", "off"]);
29
69
  var DEFAULT_WORKERPALS_QUALITY_CRITIC_MIN_SCORE = 8;
70
+ var DEFAULT_WORKERPALS_QUALITY_MAX_AUTO_REVISIONS = 1;
30
71
  var DEFAULT_WORKERPALS_FILE_MODIFYING_JOBS = ["task.execute"];
31
72
  var DEFAULT_WORKERPALS_OUTPUT_MAX_CHARS = 192 * 1024;
32
73
  var DEFAULT_WORKERPALS_OUTPUT_MAX_LINES = 600;
@@ -294,13 +335,14 @@ function loadPushPalsConfig(options = {}) {
294
335
  const remotebuddyDbPath = resolvePathFromRoot(projectRoot, firstNonEmpty(process.env.REMOTEBUDDY_DB_PATH, asString(pathsNode.remotebuddy_db_path, join(dataDir, "remotebuddy-state.db"))));
295
336
  const serverNode = getObject(merged, "server");
296
337
  const serverPort = Math.max(1, asInt(parseIntEnv("PUSHPALS_PORT") ?? serverNode.port, 3001));
297
- const serverUrl = firstNonEmpty(process.env.PUSHPALS_SERVER_URL, asString(serverNode.url, `http://localhost:${serverPort}`), `http://localhost:${serverPort}`);
298
- const serverHost = asString(serverNode.host, "0.0.0.0");
338
+ const serverUrl = normalizeLoopbackHttpUrl(firstNonEmpty(process.env.PUSHPALS_SERVER_URL, asString(serverNode.url, `http://127.0.0.1:${serverPort}`), `http://127.0.0.1:${serverPort}`), serverPort);
339
+ const serverHost = normalizeLoopbackHost(firstNonEmpty(process.env.PUSHPALS_HOST, asString(serverNode.host, "127.0.0.1")));
299
340
  const debugHttp = parseBoolEnv("PUSHPALS_DEBUG_HTTP") ?? asBoolean(serverNode.debug_http, false);
300
341
  const staleClaimTtlMs = Math.max(5000, asInt(parseIntEnv("PUSHPALS_STALE_CLAIM_TTL_MS") ?? serverNode.stale_claim_ttl_ms, 120000));
301
342
  const staleClaimSweepIntervalMs = Math.max(1000, asInt(parseIntEnv("PUSHPALS_STALE_CLAIM_SWEEP_INTERVAL_MS") ?? serverNode.stale_claim_sweep_interval_ms, 5000));
302
343
  const globalStatusHeartbeatMs = parseIntEnv("PUSHPALS_STATUS_HEARTBEAT_MS");
303
344
  const localNode = getObject(merged, "localbuddy");
345
+ const localEnabled = parseBoolEnv("LOCALBUDDY_ENABLED") ?? asBoolean(localNode.enabled, false);
304
346
  const localPort = Math.max(1, asInt(parseIntEnv("LOCAL_AGENT_PORT") ?? localNode.port, 3003));
305
347
  const localStatusHeartbeatMs = Math.max(0, asInt(parseIntEnv("LOCALBUDDY_STATUS_HEARTBEAT_MS") ?? globalStatusHeartbeatMs ?? localNode.status_heartbeat_ms, 120000));
306
348
  const localLlm = resolveLlmConfig(localNode, "LOCALBUDDY", {
@@ -377,7 +419,7 @@ function loadPushPalsConfig(options = {}) {
377
419
  const workerMinisweTimeoutMs = Math.max(1e4, asInt(parseIntEnv("WORKERPALS_MINISWE_TIMEOUT_MS") ?? workerNode.miniswe_timeout_ms, 1800000));
378
420
  const workerOpenAICodexPython = firstNonEmpty(process.env.PUSHPALS_OPENAI_CODEX_PYTHON, asString(workerNode.openai_codex_python, "python"), "python");
379
421
  const workerOpenAICodexTimeoutMs = Math.max(1e4, asInt(workerNode.openai_codex_timeout_ms, 7200000));
380
- const workerQualityMaxAutoRevisions = Math.max(0, Math.min(10, asInt(parseIntEnv("WORKERPALS_QUALITY_MAX_AUTO_REVISIONS") ?? workerNode.quality_max_auto_revisions, 4)));
422
+ const workerQualityMaxAutoRevisions = Math.max(0, Math.min(10, asInt(parseIntEnv("WORKERPALS_QUALITY_MAX_AUTO_REVISIONS") ?? workerNode.quality_max_auto_revisions, DEFAULT_WORKERPALS_QUALITY_MAX_AUTO_REVISIONS)));
381
423
  const workerFileModifyingJobs = (() => {
382
424
  const envRaw = firstNonEmpty(process.env.WORKERPALS_FILE_MODIFYING_JOBS);
383
425
  const parsed = envRaw ? envRaw.split(",").map((entry) => entry.trim()).filter(Boolean) : asStringArray(workerNode.file_modifying_jobs);
@@ -539,6 +581,7 @@ function loadPushPalsConfig(options = {}) {
539
581
  staleClaimSweepIntervalMs
540
582
  },
541
583
  localbuddy: {
584
+ enabled: localEnabled,
542
585
  port: localPort,
543
586
  statusHeartbeatMs: localStatusHeartbeatMs,
544
587
  llm: localLlm
@@ -742,7 +785,7 @@ function loadPushPalsConfig(options = {}) {
742
785
  portConflictPolicy: startupPortConflictPolicy
743
786
  },
744
787
  client: {
745
- localAgentUrl: firstNonEmpty(process.env.EXPO_PUBLIC_LOCAL_AGENT_URL, asString(clientNode.local_agent_url, `http://localhost:${localPort}`), `http://localhost:${localPort}`),
788
+ localAgentUrl: normalizeLoopbackHttpUrl(firstNonEmpty(process.env.EXPO_PUBLIC_LOCAL_AGENT_URL, asString(clientNode.local_agent_url, `http://127.0.0.1:${localPort}`), `http://127.0.0.1:${localPort}`), localPort),
746
789
  traceTailLines: Math.max(10, asInt(parseIntEnv("EXPO_PUBLIC_PUSHPALS_TRACE_TAIL_LINES") ?? clientNode.trace_tail_lines, 100))
747
790
  }
748
791
  };
@@ -1061,6 +1104,46 @@ function formatClientRuntimePreflightLines(result, prefix) {
1061
1104
  return lines;
1062
1105
  }
1063
1106
 
1107
+ // ../shared/src/repo.ts
1108
+ import { existsSync as existsSync3, readFileSync as readFileSync3, statSync } from "fs";
1109
+ import { resolve as resolve3 } from "path";
1110
+ function resolveDotGitEntry(repoRoot) {
1111
+ return resolve3(repoRoot, ".git");
1112
+ }
1113
+ function resolveGitMetadataDir(repoRoot) {
1114
+ const dotGitPath = resolveDotGitEntry(repoRoot);
1115
+ if (!existsSync3(dotGitPath))
1116
+ return null;
1117
+ try {
1118
+ const stat = statSync(dotGitPath);
1119
+ if (stat.isDirectory()) {
1120
+ return dotGitPath;
1121
+ }
1122
+ if (!stat.isFile()) {
1123
+ return null;
1124
+ }
1125
+ } catch {
1126
+ return null;
1127
+ }
1128
+ try {
1129
+ const firstLine = readFileSync3(dotGitPath, "utf8").split(/\r?\n/, 1)[0] ?? "";
1130
+ const match = firstLine.match(/^gitdir:\s*(.+)\s*$/i);
1131
+ if (!match)
1132
+ return null;
1133
+ const gitDir = resolve3(repoRoot, match[1].trim());
1134
+ return existsSync3(gitDir) ? gitDir : null;
1135
+ } catch {
1136
+ return null;
1137
+ }
1138
+ }
1139
+ function resolveGitStateFilePath(repoRoot, fileName) {
1140
+ const gitMetadataDir = resolveGitMetadataDir(repoRoot);
1141
+ const normalizedFileName = String(fileName ?? "").trim();
1142
+ if (!gitMetadataDir || !normalizedFileName)
1143
+ return null;
1144
+ return resolve3(gitMetadataDir, normalizedFileName);
1145
+ }
1146
+
1064
1147
  // ../../scripts/pushpals-cli.ts
1065
1148
  var DEFAULT_MONITOR_PORT = 8081;
1066
1149
  var MONITOR_SCAN_PORTS = 32;
@@ -1081,6 +1164,29 @@ var GITHUB_HEADERS = {
1081
1164
  "User-Agent": "pushpals-cli"
1082
1165
  };
1083
1166
  var stateVersion = 1;
1167
+ var cliTimestampedConsoleInstalled = false;
1168
+ function formatTimestampedCliLine(line, at = new Date) {
1169
+ const text = String(line ?? "");
1170
+ if (!text.startsWith("[pushpals]") && !text.startsWith("[localbuddy]")) {
1171
+ return text;
1172
+ }
1173
+ return `[${at.toISOString()}]${text}`;
1174
+ }
1175
+ function installTimestampedCliConsole() {
1176
+ if (cliTimestampedConsoleInstalled)
1177
+ return;
1178
+ cliTimestampedConsoleInstalled = true;
1179
+ const patch = (original) => (...args) => {
1180
+ if (args.length > 0 && typeof args[0] === "string") {
1181
+ args[0] = formatTimestampedCliLine(args[0]);
1182
+ }
1183
+ return original(...args);
1184
+ };
1185
+ console.log = patch(console.log.bind(console));
1186
+ console.warn = patch(console.warn.bind(console));
1187
+ console.error = patch(console.error.bind(console));
1188
+ }
1189
+ installTimestampedCliConsole();
1084
1190
  function logCliInvocation(argv) {
1085
1191
  const startedAt = new Date().toISOString();
1086
1192
  const cliVersion = String(process.env.PUSHPALS_CLI_PACKAGE_VERSION ?? "").trim() || "unknown";
@@ -1106,6 +1212,7 @@ function printUsage() {
1106
1212
  console.log(" --runtime-tag <tag> Override runtime release tag (e.g. v1.0.2)");
1107
1213
  console.log(" --no-auto-start Disable runtime auto-start when LocalBuddy is down");
1108
1214
  console.log(" --no-stream Disable live session event stream");
1215
+ console.log(" --runtime-only Start the local runtime and wait for shutdown without opening the interactive chat");
1109
1216
  console.log(" -h, --help Show this help");
1110
1217
  console.log("");
1111
1218
  console.log("Chat commands:");
@@ -1120,7 +1227,7 @@ function printUsage() {
1120
1227
  console.log(" - LocalBuddy must be attached to the same repo root.");
1121
1228
  }
1122
1229
  function parseArgs(argv) {
1123
- const options = { noAutoStart: false, noStream: false };
1230
+ const options = { noAutoStart: false, noStream: false, runtimeOnly: false };
1124
1231
  for (let i = 0;i < argv.length; i++) {
1125
1232
  const arg = argv[i];
1126
1233
  if (arg === "-h" || arg === "--help") {
@@ -1135,6 +1242,10 @@ function parseArgs(argv) {
1135
1242
  options.noAutoStart = true;
1136
1243
  continue;
1137
1244
  }
1245
+ if (arg === "--runtime-only") {
1246
+ options.runtimeOnly = true;
1247
+ continue;
1248
+ }
1138
1249
  if (arg === "--server-url") {
1139
1250
  options.serverUrl = argv[++i];
1140
1251
  continue;
@@ -1170,6 +1281,30 @@ function normalizeUrl(value, fallback = "") {
1170
1281
  const selected = text || fallback;
1171
1282
  return selected.replace(/\/+$/, "");
1172
1283
  }
1284
+ function normalizeLoopbackUrl(value, fallback) {
1285
+ const selected = normalizeUrl(value, fallback);
1286
+ if (!selected)
1287
+ return "";
1288
+ try {
1289
+ const parsed = new URL(selected);
1290
+ parsed.protocol = "http:";
1291
+ parsed.username = "";
1292
+ parsed.password = "";
1293
+ parsed.hostname = "127.0.0.1";
1294
+ return parsed.toString().replace(/\/+$/, "");
1295
+ } catch {
1296
+ return normalizeUrl(fallback);
1297
+ }
1298
+ }
1299
+ function isLoopbackUrl(value) {
1300
+ try {
1301
+ const parsed = new URL(normalizeUrl(value));
1302
+ const hostname = String(parsed.hostname ?? "").trim().toLowerCase();
1303
+ return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
1304
+ } catch {
1305
+ return false;
1306
+ }
1307
+ }
1173
1308
  function parsePositiveInt(value, fallback) {
1174
1309
  const parsed = Number.parseInt(String(value ?? "").trim(), 10);
1175
1310
  if (!Number.isFinite(parsed) || parsed <= 0)
@@ -1177,7 +1312,7 @@ function parsePositiveInt(value, fallback) {
1177
1312
  return parsed;
1178
1313
  }
1179
1314
  function normalizePath(value) {
1180
- const normalized = resolve3(value).replace(/\\/g, "/").replace(/\/+$/, "");
1315
+ const normalized = resolve4(value).replace(/\\/g, "/").replace(/\/+$/, "");
1181
1316
  if (process.platform === "win32")
1182
1317
  return normalized.toLowerCase();
1183
1318
  return normalized;
@@ -1205,11 +1340,11 @@ async function resolveCurrentGitRepoRoot(cwd) {
1205
1340
  const root = await runGit(["rev-parse", "--show-toplevel"], cwd);
1206
1341
  if (!root.ok || !root.stdout)
1207
1342
  return null;
1208
- return resolve3(root.stdout);
1343
+ return resolve4(root.stdout);
1209
1344
  }
1210
1345
  function resolveDefaultRuntimeRoot() {
1211
1346
  const home = process.env.USERPROFILE || process.env.HOME || process.cwd();
1212
- return resolve3(home, ".pushpals", "runtime");
1347
+ return resolve4(home, ".pushpals", "runtime");
1213
1348
  }
1214
1349
  function buildRuntimeAssetSource(root, protocolSchemasDir) {
1215
1350
  return {
@@ -1222,13 +1357,13 @@ function buildRuntimeAssetSource(root, protocolSchemasDir) {
1222
1357
  };
1223
1358
  }
1224
1359
  function isCompleteRuntimeAssetSource(source) {
1225
- return existsSync3(source.envExamplePath) && existsSync3(source.visionExamplePath) && existsSync3(join2(source.configsDir, "default.toml")) && existsSync3(source.promptsDir) && existsSync3(join2(source.protocolSchemasDir, "envelope.schema.json")) && existsSync3(join2(source.protocolSchemasDir, "events.schema.json"));
1360
+ return existsSync4(source.envExamplePath) && existsSync4(source.visionExamplePath) && existsSync4(join2(source.configsDir, "default.toml")) && existsSync4(source.promptsDir) && existsSync4(join2(source.protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(source.protocolSchemasDir, "events.schema.json"));
1226
1361
  }
1227
1362
  function resolveBundledRuntimeAssetSource() {
1228
1363
  const candidates = [
1229
- buildRuntimeAssetSource(resolve3(import.meta.dir, "..", "runtime"), resolve3(import.meta.dir, "..", "runtime", "protocol", "schemas")),
1230
- buildRuntimeAssetSource(resolve3(import.meta.dir, ".."), resolve3(import.meta.dir, "..", "packages", "protocol", "src", "schemas")),
1231
- buildRuntimeAssetSource(resolve3(import.meta.dir, "..", "packages", "cli", "runtime"), resolve3(import.meta.dir, "..", "packages", "cli", "runtime", "protocol", "schemas"))
1364
+ buildRuntimeAssetSource(resolve4(import.meta.dir, "..", "runtime"), resolve4(import.meta.dir, "..", "runtime", "protocol", "schemas")),
1365
+ buildRuntimeAssetSource(resolve4(import.meta.dir, ".."), resolve4(import.meta.dir, "..", "packages", "protocol", "src", "schemas")),
1366
+ buildRuntimeAssetSource(resolve4(import.meta.dir, "..", "packages", "cli", "runtime"), resolve4(import.meta.dir, "..", "packages", "cli", "runtime", "protocol", "schemas"))
1232
1367
  ];
1233
1368
  for (const candidate of candidates) {
1234
1369
  if (isCompleteRuntimeAssetSource(candidate))
@@ -1237,12 +1372,12 @@ function resolveBundledRuntimeAssetSource() {
1237
1372
  return null;
1238
1373
  }
1239
1374
  function looksLikeMonitoringHubBuild(root) {
1240
- return existsSync3(join2(root, "index.html")) && existsSync3(join2(root, "_expo"));
1375
+ return existsSync4(join2(root, "index.html")) && existsSync4(join2(root, "_expo"));
1241
1376
  }
1242
1377
  function resolveBundledMonitoringHubRoot() {
1243
1378
  const candidates = [
1244
- resolve3(import.meta.dir, "..", "monitor-ui"),
1245
- resolve3(import.meta.dir, "..", "packages", "cli", "monitor-ui")
1379
+ resolve4(import.meta.dir, "..", "monitor-ui"),
1380
+ resolve4(import.meta.dir, "..", "packages", "cli", "monitor-ui")
1246
1381
  ];
1247
1382
  for (const candidate of candidates) {
1248
1383
  if (looksLikeMonitoringHubBuild(candidate))
@@ -1252,12 +1387,12 @@ function resolveBundledMonitoringHubRoot() {
1252
1387
  }
1253
1388
  function resolveCliSourceCheckoutRoot() {
1254
1389
  const candidates = [
1255
- resolve3(import.meta.dir, ".."),
1256
- resolve3(import.meta.dir, "..", ".."),
1257
- resolve3(import.meta.dir, "..", "..", "..")
1390
+ resolve4(import.meta.dir, ".."),
1391
+ resolve4(import.meta.dir, "..", ".."),
1392
+ resolve4(import.meta.dir, "..", "..", "..")
1258
1393
  ];
1259
1394
  for (const candidate of candidates) {
1260
- if (existsSync3(join2(candidate, "package.json")) && existsSync3(join2(candidate, "apps", "client", "app.json")) && existsSync3(join2(candidate, "scripts", "sync-cli-monitor-ui.ts"))) {
1395
+ if (existsSync4(join2(candidate, "package.json")) && existsSync4(join2(candidate, "apps", "client", "app.json")) && existsSync4(join2(candidate, "scripts", "sync-cli-monitor-ui.ts"))) {
1261
1396
  return candidate;
1262
1397
  }
1263
1398
  }
@@ -1287,7 +1422,7 @@ async function ensureBundledMonitoringHubRoot() {
1287
1422
  return resolveBundledMonitoringHubRoot();
1288
1423
  }
1289
1424
  function repoLooksLikePushPalsSourceCheckout(repoRoot) {
1290
- return existsSync3(join2(repoRoot, "configs", "default.toml")) || existsSync3(join2(repoRoot, "config", "default.toml"));
1425
+ return existsSync4(join2(repoRoot, "configs", "default.toml")) || existsSync4(join2(repoRoot, "config", "default.toml"));
1291
1426
  }
1292
1427
  function parseSemverFromPackageVersion(value) {
1293
1428
  const raw = String(value ?? "").trim();
@@ -1325,6 +1460,7 @@ async function resolveRuntimeReleaseTag(explicitTag) {
1325
1460
  if (fromEnv)
1326
1461
  return fromEnv;
1327
1462
  const packageVersion = parseSemverFromPackageVersion(process.env.PUSHPALS_CLI_PACKAGE_VERSION);
1463
+ console.log("[pushpals] Resolving embedded runtime release tag from GitHub...");
1328
1464
  try {
1329
1465
  return await fetchLatestReleaseTag();
1330
1466
  } catch (err) {
@@ -1337,7 +1473,7 @@ async function resolveRuntimeReleaseTag(explicitTag) {
1337
1473
  }
1338
1474
  }
1339
1475
  function writeTextFileIfMissing(pathValue, text) {
1340
- if (existsSync3(pathValue))
1476
+ if (existsSync4(pathValue))
1341
1477
  return;
1342
1478
  mkdirSync(dirname(pathValue), { recursive: true });
1343
1479
  writeFileSync(pathValue, text, "utf8");
@@ -1380,8 +1516,8 @@ function seedRuntimePreflightAssets(runtimeRoot) {
1380
1516
  writeTextFileIfMissing(join2(runtimeRoot, ".env"), `# Local PushPals runtime environment
1381
1517
  `);
1382
1518
  const localExamplePath = join2(runtimeRoot, "configs", "local.example.toml");
1383
- if (existsSync3(localExamplePath)) {
1384
- writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), readFileSync3(localExamplePath, "utf8"));
1519
+ if (existsSync4(localExamplePath)) {
1520
+ writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), readFileSync4(localExamplePath, "utf8"));
1385
1521
  } else {
1386
1522
  writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), `# Local PushPals runtime overrides
1387
1523
  `);
@@ -1395,6 +1531,7 @@ async function fetchTextFromUrl(url, timeoutMs = 20000) {
1395
1531
  return await response.text();
1396
1532
  }
1397
1533
  async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
1534
+ console.log(`[pushpals] Downloading embedded runtime assets from source tag ${tag}...`);
1398
1535
  const treeUrl = `${GITHUB_API_URL}/git/trees/${encodeURIComponent(tag)}?recursive=1`;
1399
1536
  const treeResponse = await fetchWithTimeout(treeUrl, { headers: GITHUB_HEADERS }, 30000);
1400
1537
  if (!treeResponse.ok) {
@@ -1415,16 +1552,19 @@ async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
1415
1552
  }
1416
1553
  }
1417
1554
  async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
1555
+ console.log(`[pushpals] Preparing embedded runtime assets for ${runtimeTag}...`);
1418
1556
  const markerPath = join2(runtimeRoot, ".runtime-assets-tag");
1419
- const currentTag = existsSync3(markerPath) ? readFileSync3(markerPath, "utf8").trim() : "";
1557
+ const currentTag = existsSync4(markerPath) ? readFileSync4(markerPath, "utf8").trim() : "";
1420
1558
  const protocolSchemasDir = join2(runtimeRoot, "protocol", "schemas");
1421
- const hasProtocolSchemas = existsSync3(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync3(join2(protocolSchemasDir, "events.schema.json"));
1422
- const hasAssets = existsSync3(join2(runtimeRoot, ".env.example")) && existsSync3(join2(runtimeRoot, "vision.example.md")) && existsSync3(join2(runtimeRoot, "configs", "default.toml")) && existsSync3(join2(runtimeRoot, "prompts")) && hasProtocolSchemas;
1559
+ const hasProtocolSchemas = existsSync4(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(protocolSchemasDir, "events.schema.json"));
1560
+ const hasAssets = existsSync4(join2(runtimeRoot, ".env.example")) && existsSync4(join2(runtimeRoot, "vision.example.md")) && existsSync4(join2(runtimeRoot, "configs", "default.toml")) && existsSync4(join2(runtimeRoot, "prompts")) && hasProtocolSchemas;
1423
1561
  if (!hasAssets || currentTag !== runtimeTag) {
1562
+ console.log(`[pushpals] Embedded runtime assets ${hasAssets ? "are stale" : "are missing"}; refreshing bundle...`);
1424
1563
  copyBundledRuntimeAssets(runtimeRoot);
1425
- const hasProtocolSchemasAfterCopy = existsSync3(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync3(join2(protocolSchemasDir, "events.schema.json"));
1426
- const hasAssetsAfterCopy = existsSync3(join2(runtimeRoot, ".env.example")) && existsSync3(join2(runtimeRoot, "vision.example.md")) && existsSync3(join2(runtimeRoot, "configs", "default.toml")) && existsSync3(join2(runtimeRoot, "prompts")) && hasProtocolSchemasAfterCopy;
1564
+ const hasProtocolSchemasAfterCopy = existsSync4(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(protocolSchemasDir, "events.schema.json"));
1565
+ const hasAssetsAfterCopy = existsSync4(join2(runtimeRoot, ".env.example")) && existsSync4(join2(runtimeRoot, "vision.example.md")) && existsSync4(join2(runtimeRoot, "configs", "default.toml")) && existsSync4(join2(runtimeRoot, "prompts")) && hasProtocolSchemasAfterCopy;
1427
1566
  if (!hasAssetsAfterCopy) {
1567
+ console.log("[pushpals] Bundled runtime assets are incomplete; falling back to release source downloads...");
1428
1568
  await downloadRuntimeAssetsFromSourceTag(runtimeRoot, runtimeTag);
1429
1569
  }
1430
1570
  writeFileSync(markerPath, `${runtimeTag}
@@ -1433,18 +1573,19 @@ async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
1433
1573
  writeTextFileIfMissing(join2(runtimeRoot, ".env"), `# Local PushPals runtime environment
1434
1574
  `);
1435
1575
  const localExamplePath = join2(runtimeRoot, "configs", "local.example.toml");
1436
- if (existsSync3(localExamplePath)) {
1437
- writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), readFileSync3(localExamplePath, "utf8"));
1576
+ if (existsSync4(localExamplePath)) {
1577
+ writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), readFileSync4(localExamplePath, "utf8"));
1438
1578
  } else {
1439
1579
  writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), `# Local PushPals runtime overrides
1440
1580
  `);
1441
1581
  }
1582
+ console.log("[pushpals] Embedded runtime assets are ready.");
1442
1583
  }
1443
1584
  function resolveDeferredRuntimeTagHint(explicitTag) {
1444
1585
  return String(explicitTag || process.env.PUSHPALS_RUNTIME_TAG || "").trim();
1445
1586
  }
1446
1587
  async function prepareCliRuntime(opts) {
1447
- const runtimeRoot = resolve3(opts.runtimeRoot || process.env.PUSHPALS_RUNTIME_ROOT || resolveDefaultRuntimeRoot());
1588
+ const runtimeRoot = resolve4(opts.runtimeRoot || process.env.PUSHPALS_RUNTIME_ROOT || resolveDefaultRuntimeRoot());
1448
1589
  if (repoLooksLikePushPalsSourceCheckout(opts.repoRoot)) {
1449
1590
  return {
1450
1591
  runtimeRoot,
@@ -1496,6 +1637,7 @@ function buildEmbeddedRuntimeEnv(baseEnv, opts) {
1496
1637
  PUSHPALS_PROMPTS_ROOT_OVERRIDE: opts.repoRoot
1497
1638
  },
1498
1639
  PUSHPALS_PROTOCOL_SCHEMAS_DIR: join2(opts.runtimeRoot, "protocol", "schemas"),
1640
+ ...opts.forceLocalBuddyEnabled ? { LOCALBUDDY_ENABLED: "1" } : {},
1499
1641
  ...typeof env.PUSHPALS_GIT_BIN === "string" && env.PUSHPALS_GIT_BIN.trim() ? { PUSHPALS_GIT_BIN: env.PUSHPALS_GIT_BIN.trim() } : {}
1500
1642
  };
1501
1643
  }
@@ -1551,9 +1693,9 @@ function timestampFileToken() {
1551
1693
  return new Date().toISOString().replace(/[:.]/g, "-");
1552
1694
  }
1553
1695
  function readLogTail(logPath, maxLines = 40) {
1554
- if (!existsSync3(logPath))
1696
+ if (!existsSync4(logPath))
1555
1697
  return "";
1556
- const raw = readFileSync3(logPath, "utf8");
1698
+ const raw = readFileSync4(logPath, "utf8");
1557
1699
  const lines = raw.split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.length > 0);
1558
1700
  if (lines.length === 0)
1559
1701
  return "";
@@ -1561,6 +1703,7 @@ function readLogTail(logPath, maxLines = 40) {
1561
1703
  `);
1562
1704
  }
1563
1705
  async function downloadBinaryAsset(tag, assetName, outPath) {
1706
+ console.log(`[pushpals] Downloading embedded runtime binary ${assetName} from ${tag}...`);
1564
1707
  const url = `${GITHUB_RELEASE_URL}/${encodeURIComponent(tag)}/${assetName}`;
1565
1708
  const response = await fetchWithTimeout(url, { headers: GITHUB_HEADERS }, 60000);
1566
1709
  if (!response.ok) {
@@ -1572,6 +1715,7 @@ async function downloadBinaryAsset(tag, assetName, outPath) {
1572
1715
  }
1573
1716
  async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
1574
1717
  const platformKey = resolveRuntimePlatformKey();
1718
+ console.log(`[pushpals] Preparing embedded runtime binaries for ${runtimeTag} (${platformKey})...`);
1575
1719
  const binDir = join2(runtimeRoot, "bin", `${runtimeTag}-${platformKey}`);
1576
1720
  mkdirSync(binDir, { recursive: true });
1577
1721
  const runtimeBinaries = {
@@ -1586,11 +1730,13 @@ async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
1586
1730
  runtimeBinaries.remotebuddy,
1587
1731
  runtimeBinaries.sourceControlManager
1588
1732
  ];
1733
+ let downloadedCount = 0;
1589
1734
  for (const binaryPath of requiredAssets) {
1590
- if (existsSync3(binaryPath))
1735
+ if (existsSync4(binaryPath))
1591
1736
  continue;
1592
1737
  const assetName = binaryPath.split(/[\\/]/).pop() || "";
1593
1738
  await downloadBinaryAsset(runtimeTag, assetName, binaryPath);
1739
+ downloadedCount++;
1594
1740
  }
1595
1741
  if (process.platform !== "win32") {
1596
1742
  for (const binaryPath of requiredAssets) {
@@ -1599,6 +1745,12 @@ async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
1599
1745
  } catch {}
1600
1746
  }
1601
1747
  }
1748
+ if (downloadedCount === 0) {
1749
+ console.log("[pushpals] Embedded runtime binaries are already present.");
1750
+ } else {
1751
+ console.log(`[pushpals] Embedded runtime binaries downloaded: ${downloadedCount}.`);
1752
+ }
1753
+ console.log("[pushpals] Embedded runtime binaries are ready.");
1602
1754
  return runtimeBinaries;
1603
1755
  }
1604
1756
  function spawnRuntimeService(name, command, cwd, env, logPath) {
@@ -1676,6 +1828,34 @@ function stopRuntimeServices(services) {
1676
1828
  } catch {}
1677
1829
  }
1678
1830
  }
1831
+ function prependExecutableDirToPath(env, executablePath, platform = process.platform) {
1832
+ const resolvedPath = String(executablePath ?? "").trim();
1833
+ if (!resolvedPath)
1834
+ return env;
1835
+ if (!resolvedPath.includes("/") && !resolvedPath.includes("\\")) {
1836
+ return env;
1837
+ }
1838
+ const executableDir = dirname(resolvedPath);
1839
+ const existingPath = platform === "win32" ? String(env.Path ?? env.PATH ?? "") : String(env.PATH ?? "");
1840
+ const pathEntries = existingPath.split(delimiter).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1841
+ const hasDir = pathEntries.some((entry) => entry.toLowerCase() === executableDir.toLowerCase());
1842
+ const nextPath = hasDir ? existingPath : [executableDir, ...pathEntries].join(delimiter);
1843
+ if (platform === "win32") {
1844
+ env.Path = nextPath;
1845
+ env.PATH = nextPath;
1846
+ } else {
1847
+ env.PATH = nextPath;
1848
+ }
1849
+ return env;
1850
+ }
1851
+ function applyResolvedGitBinaryToRuntimeEnv(env, resolvedGitBinary, platform = process.platform) {
1852
+ const resolvedPath = String(resolvedGitBinary ?? "").trim();
1853
+ if (!resolvedPath)
1854
+ return env;
1855
+ prependExecutableDirToPath(env, resolvedPath, platform);
1856
+ env.PUSHPALS_GIT_BIN = basename(resolvedPath);
1857
+ return env;
1858
+ }
1679
1859
  function isOptionalEmbeddedService(name) {
1680
1860
  return name === "source_control_manager";
1681
1861
  }
@@ -1738,18 +1918,34 @@ async function fetchJsonWithTimeout(url, init = {}, timeoutMs = HTTP_TIMEOUT_MS)
1738
1918
  return null;
1739
1919
  }
1740
1920
  }
1741
- function authHeaders(authToken) {
1742
- if (!authToken)
1743
- return {};
1744
- return { Authorization: `Bearer ${authToken}` };
1921
+ function buildClientTransportQuery(cursor, client) {
1922
+ const params = new URLSearchParams;
1923
+ if (cursor > 0)
1924
+ params.set("after", String(cursor));
1925
+ params.set("clientId", client.clientId);
1926
+ params.set("clientKind", client.kind);
1927
+ params.set("clientLabel", client.label);
1928
+ params.set("clientVersion", client.version);
1929
+ params.set("clientPlatform", client.platform);
1930
+ params.set("clientRepoRoot", client.repoRoot);
1931
+ const query = params.toString();
1932
+ return query ? `?${query}` : "";
1745
1933
  }
1746
- async function probeLocalBuddy(localAgentUrl, authToken) {
1747
- return await fetchJsonWithTimeout(`${localAgentUrl}/healthz`, { headers: authHeaders(authToken) }, LOCALBUDDY_TIMEOUT_MS);
1934
+ function createRuntimeClientId(prefix) {
1935
+ if (typeof crypto?.randomUUID === "function") {
1936
+ return `${prefix}-${crypto.randomUUID()}`;
1937
+ }
1938
+ return `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
1939
+ }
1940
+ async function probeLocalBuddy(localAgentUrl) {
1941
+ return await fetchJsonWithTimeout(`${localAgentUrl}/healthz`, {}, LOCALBUDDY_TIMEOUT_MS);
1748
1942
  }
1749
1943
  async function autoStartRuntimeServices(opts) {
1750
1944
  const { runtimePreflight } = opts.preparedRuntime;
1751
1945
  const runtimeRoot = opts.preparedRuntime.runtimeRoot;
1752
1946
  const runtimeTag = opts.preparedRuntime.runtimeTag || await resolveRuntimeReleaseTag(opts.requestedRuntimeTag);
1947
+ const requireLocalBuddy = opts.requireLocalBuddy ?? true;
1948
+ const localBuddyEnabled = requireLocalBuddy || Boolean(runtimePreflight.config.localbuddy.enabled);
1753
1949
  console.log(`[pushpals] LocalBuddy unavailable. Auto-starting runtime for repo: ${opts.repoRoot}`);
1754
1950
  console.log(`[pushpals] runtimeRoot=${runtimeRoot}`);
1755
1951
  console.log(`[pushpals] runtimeTag=${runtimeTag}`);
@@ -1761,11 +1957,15 @@ async function autoStartRuntimeServices(opts) {
1761
1957
  const runtimeEnv = buildEmbeddedRuntimeEnv(process.env, {
1762
1958
  repoRoot: opts.repoRoot,
1763
1959
  runtimeRoot,
1764
- useRuntimeConfig: opts.preparedRuntime.preflightUsesEmbeddedRuntime
1960
+ useRuntimeConfig: opts.preparedRuntime.preflightUsesEmbeddedRuntime,
1961
+ forceLocalBuddyEnabled: requireLocalBuddy
1765
1962
  });
1963
+ if (runtimeEnv.PUSHPALS_GIT_BIN) {
1964
+ applyResolvedGitBinaryToRuntimeEnv(runtimeEnv, runtimeEnv.PUSHPALS_GIT_BIN);
1965
+ }
1766
1966
  const resolvedGitBinary = await resolveCommandPath("git", opts.repoRoot, normalizeChildProcessEnv(process.env));
1767
1967
  if (resolvedGitBinary) {
1768
- runtimeEnv.PUSHPALS_GIT_BIN = resolvedGitBinary;
1968
+ applyResolvedGitBinaryToRuntimeEnv(runtimeEnv, resolvedGitBinary);
1769
1969
  }
1770
1970
  const services = [];
1771
1971
  const runToken = timestampFileToken();
@@ -1805,10 +2005,17 @@ ${tail}` : ""}`);
1805
2005
  } else {
1806
2006
  console.log("[pushpals] Server already healthy; skipping embedded server start.");
1807
2007
  }
1808
- console.log("[pushpals] Starting embedded LocalBuddy...");
1809
- const localbuddyService = spawnRuntimeService("localbuddy", [runtimeBinaries.localbuddy], opts.repoRoot, runtimeEnv, logPathFor("localbuddy"));
1810
- services.push(localbuddyService);
1811
- console.log(`[pushpals] localbuddy log: ${localbuddyService.logPath}`);
2008
+ if (localBuddyEnabled) {
2009
+ if (requireLocalBuddy && !runtimePreflight.config.localbuddy.enabled) {
2010
+ console.log("[pushpals] LocalBuddy is disabled in config; forcing it on for this CLI session.");
2011
+ }
2012
+ console.log("[pushpals] Starting embedded LocalBuddy...");
2013
+ const localbuddyService = spawnRuntimeService("localbuddy", [runtimeBinaries.localbuddy], opts.repoRoot, runtimeEnv, logPathFor("localbuddy"));
2014
+ services.push(localbuddyService);
2015
+ console.log(`[pushpals] localbuddy log: ${localbuddyService.logPath}`);
2016
+ } else {
2017
+ console.log("[pushpals] Embedded LocalBuddy disabled by runtime config; skipping start.");
2018
+ }
1812
2019
  console.log("[pushpals] Starting embedded RemoteBuddy...");
1813
2020
  const remotebuddyService = spawnRuntimeService("remotebuddy", [runtimeBinaries.remotebuddy], opts.repoRoot, runtimeEnv, logPathFor("remotebuddy"));
1814
2021
  services.push(remotebuddyService);
@@ -1858,8 +2065,8 @@ ${tail2}`);
1858
2065
  ${tail}` : ""}`);
1859
2066
  }
1860
2067
  }
1861
- const health = await probeLocalBuddy(opts.localAgentUrl, opts.authToken);
1862
- if (health?.ok) {
2068
+ const health = localBuddyEnabled ? await probeLocalBuddy(opts.localAgentUrl) : null;
2069
+ if (!requireLocalBuddy || localBuddyEnabled && health?.ok) {
1863
2070
  const stabilityDeadline = Date.now() + DEFAULT_SERVICE_STABILITY_GRACE_MS;
1864
2071
  while (Date.now() < stabilityDeadline) {
1865
2072
  for (let i = services.length - 1;i >= 0; i--) {
@@ -1890,13 +2097,16 @@ ${tail}` : ""}`);
1890
2097
  await Bun.sleep(DEFAULT_RUNTIME_BOOT_POLL_MS);
1891
2098
  }
1892
2099
  stopRuntimeServices(services);
2100
+ if (!localBuddyEnabled) {
2101
+ throw new Error(`Timed out waiting for embedded runtime readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
2102
+ }
1893
2103
  throw new Error(`Timed out waiting for LocalBuddy at ${opts.localAgentUrl} after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
1894
2104
  }
1895
2105
  function readCliState(pathValue) {
1896
- if (!existsSync3(pathValue))
2106
+ if (!existsSync4(pathValue))
1897
2107
  return {};
1898
2108
  try {
1899
- const raw = readFileSync3(pathValue, "utf8");
2109
+ const raw = readFileSync4(pathValue, "utf8");
1900
2110
  const parsed = JSON.parse(raw);
1901
2111
  if (!parsed || typeof parsed !== "object")
1902
2112
  return {};
@@ -1922,6 +2132,9 @@ function writeCliState(pathValue, state) {
1922
2132
  writeFileSync(pathValue, `${JSON.stringify(payload, null, 2)}
1923
2133
  `, "utf8");
1924
2134
  }
2135
+ function resolveCliStatePath(repoRoot) {
2136
+ return resolveGitStateFilePath(repoRoot, "pushpals-cli-state.json");
2137
+ }
1925
2138
  async function looksLikeMonitoringHub(url) {
1926
2139
  try {
1927
2140
  const response = await fetchWithTimeout(url, {}, 700);
@@ -1942,7 +2155,9 @@ function buildMonitoringHubRuntimeBootstrap(opts) {
1942
2155
  serverUrl: opts.serverUrl,
1943
2156
  localAgentUrl: opts.localAgentUrl,
1944
2157
  sessionId: opts.sessionId,
1945
- authToken: opts.authToken
2158
+ clientId: `cli-monitor-${opts.sessionId}`,
2159
+ clientKind: "cli_monitor",
2160
+ clientLabel: "CLI Monitor"
1946
2161
  };
1947
2162
  }
1948
2163
  function injectMonitoringHubBootstrap(html, bootstrap) {
@@ -1983,19 +2198,19 @@ function monitoringHubContentType(pathValue) {
1983
2198
  }
1984
2199
  }
1985
2200
  function resolveMonitoringHubAssetPath(assetRoot, pathname) {
1986
- const root = resolve3(assetRoot);
2201
+ const root = resolve4(assetRoot);
1987
2202
  const rootPrefix = `${root}${root.endsWith("\\") || root.endsWith("/") ? "" : process.platform === "win32" ? "\\" : "/"}`;
1988
2203
  const decodedPath = decodeURIComponent(pathname);
1989
2204
  const trimmedPath = decodedPath === "/" ? "/index.html" : decodedPath;
1990
2205
  const relativePath = trimmedPath.replace(/^\/+/, "");
1991
- const candidatePath = resolve3(root, relativePath);
2206
+ const candidatePath = resolve4(root, relativePath);
1992
2207
  if (candidatePath !== root && !candidatePath.startsWith(rootPrefix))
1993
2208
  return null;
1994
- if (existsSync3(candidatePath))
2209
+ if (existsSync4(candidatePath))
1995
2210
  return candidatePath;
1996
2211
  if (!extname(relativePath)) {
1997
- const nestedIndexPath = resolve3(root, relativePath, "index.html");
1998
- if ((nestedIndexPath === root || nestedIndexPath.startsWith(rootPrefix)) && existsSync3(nestedIndexPath)) {
2212
+ const nestedIndexPath = resolve4(root, relativePath, "index.html");
2213
+ if ((nestedIndexPath === root || nestedIndexPath.startsWith(rootPrefix)) && existsSync4(nestedIndexPath)) {
1999
2214
  return nestedIndexPath;
2000
2215
  }
2001
2216
  return join2(root, "index.html");
@@ -2004,10 +2219,10 @@ function resolveMonitoringHubAssetPath(assetRoot, pathname) {
2004
2219
  }
2005
2220
  async function serveBundledMonitoringHub(assetRoot, pathname, bootstrap) {
2006
2221
  const assetPath = resolveMonitoringHubAssetPath(assetRoot, pathname);
2007
- if (!assetPath || !existsSync3(assetPath))
2222
+ if (!assetPath || !existsSync4(assetPath))
2008
2223
  return null;
2009
2224
  if (assetPath.endsWith("index.html")) {
2010
- const html = injectMonitoringHubBootstrap(readFileSync3(assetPath, "utf8"), bootstrap);
2225
+ const html = injectMonitoringHubBootstrap(readFileSync4(assetPath, "utf8"), bootstrap);
2011
2226
  return new Response(html, {
2012
2227
  headers: {
2013
2228
  "content-type": "text/html; charset=utf-8",
@@ -2156,9 +2371,9 @@ function buildEmbeddedMonitoringHubHtml(opts) {
2156
2371
  </body>
2157
2372
  </html>`;
2158
2373
  }
2159
- async function proxyMonitoringHubRequest(serverUrl, authToken, pathValue) {
2374
+ async function proxyMonitoringHubRequest(serverUrl, pathValue) {
2160
2375
  const target = `${serverUrl}${pathValue}`;
2161
- const upstream = await fetchWithTimeout(target, { headers: authHeaders(authToken) }, 1e4);
2376
+ const upstream = await fetchWithTimeout(target, {}, 1e4);
2162
2377
  const body = await upstream.text();
2163
2378
  return new Response(body, {
2164
2379
  status: upstream.status,
@@ -2177,14 +2392,14 @@ async function startEmbeddedMonitoringHub(opts) {
2177
2392
  const bootstrap = buildMonitoringHubRuntimeBootstrap({
2178
2393
  serverUrl: opts.serverUrl,
2179
2394
  localAgentUrl: opts.localAgentUrl,
2180
- sessionId: opts.sessionId,
2181
- authToken: opts.authToken
2395
+ sessionId: opts.sessionId
2182
2396
  });
2183
2397
  const candidatePorts = Array.from({ length: MONITOR_SCAN_PORTS }, (_, index) => opts.preferredPort + index).concat(0);
2184
2398
  for (const port of candidatePorts) {
2185
2399
  try {
2186
2400
  const server = Bun.serve({
2187
2401
  port,
2402
+ hostname: "127.0.0.1",
2188
2403
  idleTimeout: 30,
2189
2404
  fetch: async (req) => {
2190
2405
  const url = new URL(req.url);
@@ -2197,16 +2412,16 @@ async function startEmbeddedMonitoringHub(opts) {
2197
2412
  });
2198
2413
  }
2199
2414
  if (url.pathname === "/api/status") {
2200
- return await proxyMonitoringHubRequest(opts.serverUrl, opts.authToken, "/system/status");
2415
+ return await proxyMonitoringHubRequest(opts.serverUrl, "/system/status");
2201
2416
  }
2202
2417
  if (url.pathname === "/api/requests") {
2203
- return await proxyMonitoringHubRequest(opts.serverUrl, opts.authToken, "/requests?status=all&limit=20");
2418
+ return await proxyMonitoringHubRequest(opts.serverUrl, "/requests?status=all&limit=20");
2204
2419
  }
2205
2420
  if (url.pathname === "/api/jobs") {
2206
- return await proxyMonitoringHubRequest(opts.serverUrl, opts.authToken, "/jobs?status=all&limit=20");
2421
+ return await proxyMonitoringHubRequest(opts.serverUrl, "/jobs?status=all&limit=20");
2207
2422
  }
2208
2423
  if (url.pathname === "/api/completions") {
2209
- return await proxyMonitoringHubRequest(opts.serverUrl, opts.authToken, "/completions?status=all&limit=20");
2424
+ return await proxyMonitoringHubRequest(opts.serverUrl, "/completions?status=all&limit=20");
2210
2425
  }
2211
2426
  const bundledResponse = await serveBundledMonitoringHub(monitoringHubAssetRoot, url.pathname, bootstrap);
2212
2427
  if (bundledResponse)
@@ -2227,10 +2442,13 @@ async function startEmbeddedMonitoringHub(opts) {
2227
2442
  async function resolveMonitoringHub(opts) {
2228
2443
  const explicit = normalizeUrl(opts.preferredUrl);
2229
2444
  if (explicit) {
2230
- if (await looksLikeMonitoringHub(explicit)) {
2445
+ if (!isLoopbackUrl(explicit)) {
2446
+ console.warn(`[pushpals] Preferred monitoring hub ${explicit} is not local; ignoring it and starting a local monitor instead.`);
2447
+ } else if (await looksLikeMonitoringHub(explicit)) {
2231
2448
  return { url: explicit, port: 0, stop: () => {}, embedded: false };
2449
+ } else {
2450
+ console.warn(`[pushpals] Preferred monitoring hub ${explicit} is unavailable; starting embedded monitor instead.`);
2232
2451
  }
2233
- console.warn(`[pushpals] Preferred monitoring hub ${explicit} is unavailable; starting embedded monitor instead.`);
2234
2452
  }
2235
2453
  for (let port = opts.fallbackPort;port < opts.fallbackPort + MONITOR_SCAN_PORTS; port++) {
2236
2454
  const candidate = `http://127.0.0.1:${port}`;
@@ -2350,12 +2568,11 @@ function formatSessionEventLine(event) {
2350
2568
  }
2351
2569
  return null;
2352
2570
  }
2353
- async function runSessionStream(serverUrl, sessionId, authToken, print, signal) {
2571
+ async function runSessionStream(serverUrl, sessionId, client, print, signal) {
2354
2572
  let cursor = 0;
2355
2573
  while (!signal.aborted) {
2356
- const headers = authHeaders(authToken);
2357
2574
  try {
2358
- const response = await fetchWithTimeout(`${serverUrl}/sessions/${encodeURIComponent(sessionId)}/events${cursor > 0 ? `?after=${cursor}` : ""}`, { headers }, 15000);
2575
+ const response = await fetchWithTimeout(`${serverUrl}/sessions/${encodeURIComponent(sessionId)}/events${buildClientTransportQuery(cursor, client)}`, {}, 15000);
2359
2576
  if (!response.ok || !response.body) {
2360
2577
  print(`[pushpals] Session stream unavailable: HTTP ${response.status}`);
2361
2578
  await Bun.sleep(SSE_RECONNECT_MS);
@@ -2469,10 +2686,9 @@ async function main() {
2469
2686
  process.exit(1);
2470
2687
  }
2471
2688
  const config = preparedRuntime.runtimePreflight.config;
2472
- const serverUrl = normalizeUrl(parsed.serverUrl ?? process.env.PUSHPALS_SERVER_URL, config.server.url);
2473
- const localAgentUrl = normalizeUrl(parsed.localAgentUrl ?? process.env.EXPO_PUBLIC_LOCAL_AGENT_URL, config.client.localAgentUrl);
2689
+ const serverUrl = normalizeLoopbackUrl(parsed.serverUrl ?? process.env.PUSHPALS_SERVER_URL, config.server.url);
2690
+ const localAgentUrl = normalizeLoopbackUrl(parsed.localAgentUrl ?? process.env.EXPO_PUBLIC_LOCAL_AGENT_URL, config.client.localAgentUrl);
2474
2691
  const sessionId = String(parsed.sessionId ?? process.env.PUSHPALS_SESSION_ID ?? config.sessionId).trim();
2475
- const authToken = config.authToken;
2476
2692
  let autoStartedServices = [];
2477
2693
  const stopAutoStartedServices = () => {
2478
2694
  if (autoStartedServices.length === 0)
@@ -2480,8 +2696,10 @@ async function main() {
2480
2696
  stopRuntimeServices(autoStartedServices);
2481
2697
  autoStartedServices = [];
2482
2698
  };
2483
- let health = await probeLocalBuddy(localAgentUrl, authToken);
2484
- if (!health?.ok && !parsed.noAutoStart) {
2699
+ let serverHealthy = await probeServer(serverUrl);
2700
+ let health = await probeLocalBuddy(localAgentUrl);
2701
+ const runtimeNeedsAutoStart = parsed.runtimeOnly ? !serverHealthy : !health?.ok;
2702
+ if (runtimeNeedsAutoStart && !parsed.noAutoStart) {
2485
2703
  try {
2486
2704
  autoStartedServices = await autoStartRuntimeServices({
2487
2705
  repoRoot,
@@ -2489,45 +2707,58 @@ async function main() {
2489
2707
  localAgentUrl,
2490
2708
  sourceControlManagerPort: config.sourceControlManager.port,
2491
2709
  sourceControlManagerRemote: config.sourceControlManager.remote,
2492
- authToken,
2493
2710
  preparedRuntime,
2494
- requestedRuntimeTag: parsed.runtimeTag
2711
+ requestedRuntimeTag: parsed.runtimeTag,
2712
+ requireLocalBuddy: !parsed.runtimeOnly
2495
2713
  });
2496
- health = await probeLocalBuddy(localAgentUrl, authToken);
2714
+ serverHealthy = await probeServer(serverUrl);
2715
+ health = await probeLocalBuddy(localAgentUrl);
2497
2716
  } catch (err) {
2498
2717
  console.error(`[pushpals] Auto-start failed: ${String(err)}`);
2499
2718
  stopAutoStartedServices();
2500
2719
  }
2501
2720
  }
2502
- if (!health?.ok) {
2503
- console.error(`[pushpals] LocalBuddy is unavailable at ${localAgentUrl}.`);
2721
+ if (parsed.runtimeOnly && !serverHealthy) {
2722
+ console.error(`[pushpals] Server is unavailable at ${serverUrl}.`);
2504
2723
  if (parsed.noAutoStart) {
2505
2724
  console.error("[pushpals] Auto-start is disabled (--no-auto-start).");
2506
2725
  } else {
2507
- console.error("[pushpals] Auto-start could not bring LocalBuddy online.");
2726
+ console.error("[pushpals] Auto-start could not bring the embedded runtime online.");
2508
2727
  }
2509
2728
  process.exit(1);
2510
2729
  }
2511
- const localBuddyRepo = health.repo ? resolve3(health.repo) : "";
2512
- if (!localBuddyRepo) {
2513
- stopAutoStartedServices();
2514
- console.error("[pushpals] LocalBuddy health response did not include repo path.");
2515
- process.exit(1);
2516
- }
2517
- if (normalizePath(localBuddyRepo) !== normalizePath(repoRoot)) {
2518
- stopAutoStartedServices();
2519
- console.error("[pushpals] Repo mismatch detected.");
2520
- console.error(`[pushpals] currentRepo=${repoRoot}`);
2521
- console.error(`[pushpals] localBuddyRepo=${localBuddyRepo}`);
2522
- console.error("[pushpals] LocalBuddy must run against the same repo. Start PushPals from this repo and retry.");
2730
+ if (!parsed.runtimeOnly && !health?.ok) {
2731
+ console.error(`[pushpals] LocalBuddy is unavailable at ${localAgentUrl}.`);
2732
+ if (parsed.noAutoStart) {
2733
+ console.error("[pushpals] Auto-start is disabled (--no-auto-start).");
2734
+ } else {
2735
+ console.error("[pushpals] Auto-start could not bring LocalBuddy online.");
2736
+ }
2523
2737
  process.exit(1);
2524
2738
  }
2525
- const localBuddySessionId = health.sessionId && String(health.sessionId).trim() ? String(health.sessionId).trim() : sessionId;
2526
- if (sessionId && sessionId !== localBuddySessionId) {
2527
- console.warn(`[pushpals] Requested sessionId=${sessionId}, but LocalBuddy is currently attached to sessionId=${localBuddySessionId}.`);
2739
+ let localBuddySessionId = sessionId;
2740
+ if (!parsed.runtimeOnly) {
2741
+ const localBuddyRepo = health?.repo ? resolve4(health.repo) : "";
2742
+ if (!localBuddyRepo) {
2743
+ stopAutoStartedServices();
2744
+ console.error("[pushpals] LocalBuddy health response did not include repo path.");
2745
+ process.exit(1);
2746
+ }
2747
+ if (normalizePath(localBuddyRepo) !== normalizePath(repoRoot)) {
2748
+ stopAutoStartedServices();
2749
+ console.error("[pushpals] Repo mismatch detected.");
2750
+ console.error(`[pushpals] currentRepo=${repoRoot}`);
2751
+ console.error(`[pushpals] localBuddyRepo=${localBuddyRepo}`);
2752
+ console.error("[pushpals] LocalBuddy must run against the same repo. Start PushPals from this repo and retry.");
2753
+ process.exit(1);
2754
+ }
2755
+ localBuddySessionId = health?.sessionId && String(health.sessionId).trim() ? String(health.sessionId).trim() : sessionId;
2756
+ if (sessionId && sessionId !== localBuddySessionId) {
2757
+ console.warn(`[pushpals] Requested sessionId=${sessionId}, but LocalBuddy is currently attached to sessionId=${localBuddySessionId}.`);
2758
+ }
2528
2759
  }
2529
- const statePath = resolve3(repoRoot, ".git", "pushpals-cli-state.json");
2530
- const saved = readCliState(statePath);
2760
+ const statePath = resolveCliStatePath(repoRoot);
2761
+ const saved = statePath ? readCliState(statePath) : {};
2531
2762
  const preferredHubUrl = normalizeUrl(parsed.monitoringHubUrl ?? process.env.PUSHPALS_MONITOR_URL ?? saved.monitoringHubUrl ?? "");
2532
2763
  const monitorPort = parsePositiveInt(process.env.PUSHPALS_CLIENT_PORT, DEFAULT_MONITOR_PORT);
2533
2764
  const monitoringHub = await resolveMonitoringHub({
@@ -2535,32 +2766,48 @@ async function main() {
2535
2766
  fallbackPort: monitorPort,
2536
2767
  serverUrl,
2537
2768
  localAgentUrl,
2538
- sessionId: localBuddySessionId,
2539
- authToken
2769
+ sessionId: localBuddySessionId
2540
2770
  });
2541
2771
  const monitoringHubUrl = monitoringHub?.url ?? "";
2542
- writeCliState(statePath, {
2543
- monitoringHubUrl: monitoringHubUrl || undefined,
2544
- serverUrl,
2545
- localAgentUrl,
2546
- sessionId: localBuddySessionId,
2547
- repoRoot
2548
- });
2772
+ if (statePath) {
2773
+ writeCliState(statePath, {
2774
+ monitoringHubUrl: monitoringHubUrl || undefined,
2775
+ serverUrl,
2776
+ localAgentUrl,
2777
+ sessionId: localBuddySessionId,
2778
+ repoRoot
2779
+ });
2780
+ } else {
2781
+ console.warn("[pushpals] Could not resolve git metadata dir; skipping CLI state persistence.");
2782
+ }
2549
2783
  console.log("[pushpals] Connected.");
2550
2784
  if (monitoringHubUrl) {
2551
- console.log(`monitoringHubUrl=${monitoringHubUrl}`);
2785
+ console.log(`[pushpals] monitoringHubUrl=${monitoringHubUrl}`);
2552
2786
  if (monitoringHub?.embedded) {
2553
2787
  console.log("[pushpals] Embedded monitoring hub is running.");
2554
2788
  }
2555
2789
  } else {
2556
- console.log("monitoringHubUrl=unavailable");
2557
- }
2558
- console.log(`serverUrl=${serverUrl}`);
2559
- console.log(`localAgentUrl=${localAgentUrl}`);
2560
- console.log(`sessionId=${localBuddySessionId}`);
2561
- console.log(`repoRoot=${repoRoot}`);
2562
- console.log(`cliStateFile=${statePath}`);
2563
- console.log("[pushpals] Type a message and press Enter. Use /exit or exit to quit.");
2790
+ console.log("[pushpals] monitoringHubUrl=unavailable");
2791
+ }
2792
+ console.log(`[pushpals] serverUrl=${serverUrl}`);
2793
+ console.log(`[pushpals] localAgentUrl=${localAgentUrl}`);
2794
+ console.log(`[pushpals] sessionId=${localBuddySessionId}`);
2795
+ console.log(`[pushpals] repoRoot=${repoRoot}`);
2796
+ console.log(`[pushpals] cliStateFile=${statePath ?? "unavailable"}`);
2797
+ if (parsed.runtimeOnly) {
2798
+ console.log("[pushpals] runtimeOnly=true");
2799
+ } else {
2800
+ console.log("[pushpals] Type a message and press Enter. Use /exit or exit to quit.");
2801
+ }
2802
+ const cliVersion = String(process.env.PUSHPALS_CLI_PACKAGE_VERSION ?? "").trim() || "unknown";
2803
+ const cliClient = {
2804
+ clientId: createRuntimeClientId("cli"),
2805
+ kind: "cli",
2806
+ label: "CLI",
2807
+ version: cliVersion,
2808
+ platform: `${process.platform}/${process.arch}`,
2809
+ repoRoot
2810
+ };
2564
2811
  const streamAbort = new AbortController;
2565
2812
  let rl = null;
2566
2813
  const printIncoming = (line) => {
@@ -2575,7 +2822,7 @@ ${line}
2575
2822
  }
2576
2823
  console.log(line);
2577
2824
  };
2578
- const streamTask = parsed.noStream ? Promise.resolve() : runSessionStream(serverUrl, localBuddySessionId, authToken, printIncoming, streamAbort.signal);
2825
+ const streamTask = parsed.noStream ? Promise.resolve() : parsed.runtimeOnly ? Promise.resolve() : runSessionStream(serverUrl, localBuddySessionId, cliClient, printIncoming, streamAbort.signal);
2579
2826
  let shuttingDown = false;
2580
2827
  const requestStop = () => {
2581
2828
  if (shuttingDown)
@@ -2596,6 +2843,39 @@ ${line}
2596
2843
  process.once("SIGINT", requestStop);
2597
2844
  process.once("SIGTERM", requestStop);
2598
2845
  process.once("exit", requestStop);
2846
+ if (parsed.runtimeOnly) {
2847
+ console.log("[pushpals] Runtime-only mode is active. Send `exit` on stdin or terminate the process to stop.");
2848
+ await new Promise((resolveStop) => {
2849
+ let resolved = false;
2850
+ const finish = () => {
2851
+ if (resolved)
2852
+ return;
2853
+ resolved = true;
2854
+ resolveStop();
2855
+ };
2856
+ process.once("SIGINT", finish);
2857
+ process.once("SIGTERM", finish);
2858
+ const runtimeOnlyInput = createInterface({
2859
+ input: process.stdin,
2860
+ output: process.stdout,
2861
+ terminal: false
2862
+ });
2863
+ runtimeOnlyInput.on("line", (line) => {
2864
+ if (!isCliExitCommand(line))
2865
+ return;
2866
+ requestStop();
2867
+ runtimeOnlyInput.close();
2868
+ finish();
2869
+ });
2870
+ runtimeOnlyInput.on("close", () => {
2871
+ requestStop();
2872
+ finish();
2873
+ });
2874
+ });
2875
+ requestStop();
2876
+ await Promise.race([streamTask, Bun.sleep(2000)]);
2877
+ return;
2878
+ }
2599
2879
  rl = createInterface({
2600
2880
  input: process.stdin,
2601
2881
  output: process.stdout,
@@ -2614,16 +2894,16 @@ ${line}
2614
2894
  break;
2615
2895
  }
2616
2896
  if (text === "/hub") {
2617
- console.log(monitoringHubUrl ? `monitoringHubUrl=${monitoringHubUrl}` : "monitoringHubUrl=unavailable");
2897
+ console.log(monitoringHubUrl ? `[pushpals] monitoringHubUrl=${monitoringHubUrl}` : "[pushpals] monitoringHubUrl=unavailable");
2618
2898
  rl.prompt();
2619
2899
  continue;
2620
2900
  }
2621
2901
  if (text === "/status") {
2622
- console.log(`serverUrl=${serverUrl}`);
2623
- console.log(`localAgentUrl=${localAgentUrl}`);
2624
- console.log(`sessionId=${localBuddySessionId}`);
2625
- console.log(`repoRoot=${repoRoot}`);
2626
- console.log(monitoringHubUrl ? `monitoringHubUrl=${monitoringHubUrl}` : "monitoringHubUrl=unavailable");
2902
+ console.log(`[pushpals] serverUrl=${serverUrl}`);
2903
+ console.log(`[pushpals] localAgentUrl=${localAgentUrl}`);
2904
+ console.log(`[pushpals] sessionId=${localBuddySessionId}`);
2905
+ console.log(`[pushpals] repoRoot=${repoRoot}`);
2906
+ console.log(monitoringHubUrl ? `[pushpals] monitoringHubUrl=${monitoringHubUrl}` : "[pushpals] monitoringHubUrl=unavailable");
2627
2907
  rl.prompt();
2628
2908
  continue;
2629
2909
  }
@@ -2656,14 +2936,17 @@ if (import.meta.main) {
2656
2936
  export {
2657
2937
  startEmbeddedMonitoringHub,
2658
2938
  resolveCommandPath,
2939
+ resolveCliStatePath,
2659
2940
  resolveBundledRuntimeAssetSource,
2660
2941
  resolveBundledMonitoringHubRoot,
2661
2942
  prepareCliRuntime,
2662
2943
  normalizeChildProcessEnv,
2663
2944
  isCliExitCommand,
2664
2945
  injectMonitoringHubBootstrap,
2946
+ formatTimestampedCliLine,
2665
2947
  buildServiceStopCommand,
2666
2948
  buildOpenMonitoringHubCommand,
2667
2949
  buildEmbeddedRuntimeEnv,
2668
- buildEmbeddedMonitoringHubHtml
2950
+ buildEmbeddedMonitoringHubHtml,
2951
+ applyResolvedGitBinaryToRuntimeEnv
2669
2952
  };