@pushpalsdev/cli 1.0.7 → 1.0.8

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 +128 -18
  2. package/package.json +1 -1
@@ -1425,9 +1425,10 @@ function runtimeBinaryFilename(serviceName, platformKey) {
1425
1425
  return `pushpals-runtime-${serviceToken}-${platformKey}${extension}`;
1426
1426
  }
1427
1427
  function buildEmbeddedRuntimeEnv(baseEnv, opts) {
1428
+ const env = normalizeChildProcessEnv(baseEnv);
1428
1429
  const useRuntimeConfig = opts.useRuntimeConfig !== false;
1429
1430
  return {
1430
- ...baseEnv,
1431
+ ...env,
1431
1432
  PUSHPALS_REPO_ROOT_OVERRIDE: opts.repoRoot,
1432
1433
  PUSHPALS_PROJECT_ROOT_OVERRIDE: opts.repoRoot,
1433
1434
  ...useRuntimeConfig ? {
@@ -1436,9 +1437,61 @@ function buildEmbeddedRuntimeEnv(baseEnv, opts) {
1436
1437
  } : {
1437
1438
  PUSHPALS_PROMPTS_ROOT_OVERRIDE: opts.repoRoot
1438
1439
  },
1439
- PUSHPALS_PROTOCOL_SCHEMAS_DIR: join2(opts.runtimeRoot, "protocol", "schemas")
1440
+ PUSHPALS_PROTOCOL_SCHEMAS_DIR: join2(opts.runtimeRoot, "protocol", "schemas"),
1441
+ ...typeof env.PUSHPALS_GIT_BIN === "string" && env.PUSHPALS_GIT_BIN.trim() ? { PUSHPALS_GIT_BIN: env.PUSHPALS_GIT_BIN.trim() } : {}
1440
1442
  };
1441
1443
  }
1444
+ function normalizeChildProcessEnv(baseEnv, platform = process.platform) {
1445
+ const env = {};
1446
+ for (const [key, value] of Object.entries(baseEnv)) {
1447
+ if (typeof value === "string")
1448
+ env[key] = value;
1449
+ }
1450
+ if (platform === "win32") {
1451
+ const resolvedPath = String(env.Path ?? env.PATH ?? process.env.Path ?? process.env.PATH ?? "").trim();
1452
+ if (resolvedPath) {
1453
+ env.Path = resolvedPath;
1454
+ env.PATH = resolvedPath;
1455
+ }
1456
+ const systemRoot = String(env.SystemRoot ?? env.SYSTEMROOT ?? process.env.SystemRoot ?? process.env.SYSTEMROOT ?? "").trim();
1457
+ if (systemRoot) {
1458
+ env.SystemRoot = systemRoot;
1459
+ env.SYSTEMROOT = systemRoot;
1460
+ }
1461
+ const comSpec = String(env.ComSpec ?? env.COMSPEC ?? process.env.ComSpec ?? process.env.COMSPEC ?? "").trim();
1462
+ if (comSpec) {
1463
+ env.ComSpec = comSpec;
1464
+ env.COMSPEC = comSpec;
1465
+ }
1466
+ }
1467
+ return env;
1468
+ }
1469
+ async function resolveCommandPath(command, cwd, env) {
1470
+ const lookupCommands = process.platform === "win32" ? [
1471
+ ["where.exe", command],
1472
+ ["where", command]
1473
+ ] : [["which", command]];
1474
+ for (const lookup of lookupCommands) {
1475
+ try {
1476
+ const proc = Bun.spawn(lookup, {
1477
+ cwd,
1478
+ env,
1479
+ stdout: "pipe",
1480
+ stderr: "ignore"
1481
+ });
1482
+ const [stdout, exitCode] = await Promise.all([
1483
+ new Response(proc.stdout).text(),
1484
+ proc.exited
1485
+ ]);
1486
+ if (exitCode !== 0)
1487
+ continue;
1488
+ const resolved = stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
1489
+ if (resolved)
1490
+ return resolved;
1491
+ } catch {}
1492
+ }
1493
+ return null;
1494
+ }
1442
1495
  function timestampFileToken() {
1443
1496
  return new Date().toISOString().replace(/[:.]/g, "-");
1444
1497
  }
@@ -1546,11 +1599,18 @@ function spawnRuntimeService(name, command, cwd, env, logPath) {
1546
1599
  });
1547
1600
  return service;
1548
1601
  }
1602
+ function buildServiceStopCommand(pid, platform = process.platform) {
1603
+ if (platform === "win32" && typeof pid === "number" && pid > 0) {
1604
+ return ["taskkill", "/PID", String(pid), "/T", "/F"];
1605
+ }
1606
+ return null;
1607
+ }
1549
1608
  function stopRuntimeServices(services) {
1550
1609
  for (const service of services) {
1551
1610
  try {
1552
- if (process.platform === "win32" && typeof service.proc.pid === "number" && service.proc.pid > 0) {
1553
- Bun.spawnSync(["taskkill", "/PID", String(service.proc.pid), "/T", "/F"], {
1611
+ const stopCommand = buildServiceStopCommand(service.proc.pid, process.platform);
1612
+ if (stopCommand) {
1613
+ Bun.spawnSync(stopCommand, {
1554
1614
  stdin: "ignore",
1555
1615
  stdout: "ignore",
1556
1616
  stderr: "ignore"
@@ -1561,6 +1621,24 @@ function stopRuntimeServices(services) {
1561
1621
  } catch {}
1562
1622
  }
1563
1623
  }
1624
+ function isOptionalEmbeddedService(name) {
1625
+ return name === "source_control_manager";
1626
+ }
1627
+ async function canSpawnCommand(command, cwd, env) {
1628
+ try {
1629
+ const proc = Bun.spawn(command, {
1630
+ cwd,
1631
+ env,
1632
+ stdin: "ignore",
1633
+ stdout: "ignore",
1634
+ stderr: "ignore"
1635
+ });
1636
+ const exitCode = await proc.exited;
1637
+ return exitCode === 0;
1638
+ } catch {
1639
+ return false;
1640
+ }
1641
+ }
1564
1642
  async function repoHasRemote(repoRoot, remote) {
1565
1643
  const normalizedRemote = remote.trim();
1566
1644
  if (!normalizedRemote)
@@ -1630,6 +1708,10 @@ async function autoStartRuntimeServices(opts) {
1630
1708
  runtimeRoot,
1631
1709
  useRuntimeConfig: opts.preparedRuntime.preflightUsesEmbeddedRuntime
1632
1710
  });
1711
+ const resolvedGitBinary = await resolveCommandPath("git", opts.repoRoot, normalizeChildProcessEnv(process.env));
1712
+ if (resolvedGitBinary) {
1713
+ runtimeEnv.PUSHPALS_GIT_BIN = resolvedGitBinary;
1714
+ }
1633
1715
  const services = [];
1634
1716
  const runToken = timestampFileToken();
1635
1717
  const logDir = join2(runtimeRoot, "logs", "bootstrap");
@@ -1678,13 +1760,24 @@ ${tail}` : ""}`);
1678
1760
  console.log(`[pushpals] remotebuddy log: ${remotebuddyService.logPath}`);
1679
1761
  const scmHealthy = await probeSourceControlManager(opts.sourceControlManagerPort);
1680
1762
  const scmRemoteAvailable = await repoHasRemote(opts.repoRoot, opts.sourceControlManagerRemote);
1763
+ const gitProbeCommand = typeof runtimeEnv.PUSHPALS_GIT_BIN === "string" && runtimeEnv.PUSHPALS_GIT_BIN.trim() ? [runtimeEnv.PUSHPALS_GIT_BIN.trim(), "--version"] : ["git", "--version"];
1764
+ const gitAvailableForScm = await canSpawnCommand(gitProbeCommand, opts.repoRoot, runtimeEnv);
1681
1765
  if (!scmHealthy && scmRemoteAvailable) {
1682
- console.log("[pushpals] Starting embedded SourceControlManager...");
1683
- const sourceControlManagerService = spawnRuntimeService("source_control_manager", [runtimeBinaries.sourceControlManager, "--skip-clean-check"], opts.repoRoot, runtimeEnv, logPathFor("source_control_manager"));
1684
- services.push(sourceControlManagerService);
1685
- console.log(`[pushpals] source_control_manager log: ${sourceControlManagerService.logPath}`);
1766
+ if (!gitAvailableForScm) {
1767
+ console.warn("[pushpals] Git is not available to embedded SourceControlManager; skipping SCM startup.");
1768
+ } else {
1769
+ if (runtimeEnv.PUSHPALS_GIT_BIN) {
1770
+ console.log(`[pushpals] Embedded SourceControlManager git=${runtimeEnv.PUSHPALS_GIT_BIN}`);
1771
+ }
1772
+ console.log("[pushpals] Starting embedded SourceControlManager...");
1773
+ const sourceControlManagerService = spawnRuntimeService("source_control_manager", [runtimeBinaries.sourceControlManager, "--skip-clean-check"], opts.repoRoot, runtimeEnv, logPathFor("source_control_manager"));
1774
+ services.push(sourceControlManagerService);
1775
+ console.log(`[pushpals] source_control_manager log: ${sourceControlManagerService.logPath}`);
1776
+ }
1686
1777
  } else if (!scmRemoteAvailable) {
1687
1778
  console.log(`[pushpals] Repo has no git remote "${opts.sourceControlManagerRemote}"; skipping embedded SourceControlManager.`);
1779
+ } else if (!gitAvailableForScm) {
1780
+ console.warn("[pushpals] Git is not available to embedded SourceControlManager; skipping SCM startup.");
1688
1781
  } else {
1689
1782
  console.log("[pushpals] SourceControlManager already healthy; skipping embedded start.");
1690
1783
  }
@@ -1693,7 +1786,7 @@ ${tail}` : ""}`);
1693
1786
  for (let i = services.length - 1;i >= 0; i--) {
1694
1787
  const service = services[i];
1695
1788
  if (service.exited) {
1696
- if (service.name === "source_control_manager") {
1789
+ if (isOptionalEmbeddedService(service.name)) {
1697
1790
  console.warn(`[pushpals] Embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}); continuing without SCM.`);
1698
1791
  const tail2 = readLogTail(service.logPath);
1699
1792
  if (tail2) {
@@ -1714,9 +1807,20 @@ ${tail}` : ""}`);
1714
1807
  if (health?.ok) {
1715
1808
  const stabilityDeadline = Date.now() + DEFAULT_SERVICE_STABILITY_GRACE_MS;
1716
1809
  while (Date.now() < stabilityDeadline) {
1717
- for (const service of services) {
1810
+ for (let i = services.length - 1;i >= 0; i--) {
1811
+ const service = services[i];
1718
1812
  if (!service.exited)
1719
1813
  continue;
1814
+ if (isOptionalEmbeddedService(service.name)) {
1815
+ const tail2 = readLogTail(service.logPath);
1816
+ console.warn(`[pushpals] Embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}); continuing without SCM.`);
1817
+ if (tail2) {
1818
+ console.warn(`[pushpals] ${service.name} log tail:
1819
+ ${tail2}`);
1820
+ }
1821
+ services.splice(i, 1);
1822
+ continue;
1823
+ }
1720
1824
  const tail = readLogTail(service.logPath);
1721
1825
  stopRuntimeServices(services);
1722
1826
  throw new Error(`Embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}). ` + `See ${service.logPath}${tail ? `
@@ -2163,15 +2267,17 @@ async function runSessionStream(serverUrl, sessionId, authToken, print, signal)
2163
2267
  }
2164
2268
  }
2165
2269
  }
2166
- async function openMonitoringHub(url) {
2167
- let cmd = null;
2168
- if (process.platform === "win32") {
2169
- cmd = ["cmd", "/c", "start", "", url];
2170
- } else if (process.platform === "darwin") {
2171
- cmd = ["open", url];
2172
- } else {
2173
- cmd = ["xdg-open", url];
2270
+ function buildOpenMonitoringHubCommand(url, platform = process.platform) {
2271
+ if (platform === "win32") {
2272
+ return ["cmd", "/c", "start", "", url];
2273
+ }
2274
+ if (platform === "darwin") {
2275
+ return ["open", url];
2174
2276
  }
2277
+ return ["xdg-open", url];
2278
+ }
2279
+ async function openMonitoringHub(url) {
2280
+ const cmd = buildOpenMonitoringHubCommand(url, process.platform);
2175
2281
  const proc = Bun.spawn(cmd, {
2176
2282
  stdin: "ignore",
2177
2283
  stdout: "ignore",
@@ -2395,8 +2501,12 @@ if (import.meta.main) {
2395
2501
  }
2396
2502
  export {
2397
2503
  startEmbeddedMonitoringHub,
2504
+ resolveCommandPath,
2398
2505
  resolveBundledRuntimeAssetSource,
2399
2506
  prepareCliRuntime,
2507
+ normalizeChildProcessEnv,
2508
+ buildServiceStopCommand,
2509
+ buildOpenMonitoringHubCommand,
2400
2510
  buildEmbeddedRuntimeEnv,
2401
2511
  buildEmbeddedMonitoringHubHtml
2402
2512
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushpalsdev/cli",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "PushPals terminal CLI for LocalBuddy -> RemoteBuddy orchestration",
5
5
  "license": "MIT",
6
6
  "repository": {