@pushpalsdev/cli 1.0.30 → 1.0.31

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.
@@ -1217,6 +1217,15 @@ var DEFAULT_RUNTIME_BOOT_TIMEOUT_MS = 90000;
1217
1217
  var DEFAULT_RUNTIME_BOOT_POLL_MS = 1000;
1218
1218
  var DEFAULT_SERVER_BOOT_TIMEOUT_MS = 20000;
1219
1219
  var DEFAULT_SERVICE_STABILITY_GRACE_MS = 4000;
1220
+ var WORKERPAL_STARTUP_READINESS_PROBE_MAX_MS = 15000;
1221
+ var EMBEDDED_RUNTIME_SAFETY_CAP_DISABLE_ENV = "PUSHPALS_DISABLE_EMBEDDED_SAFETY_CAPS";
1222
+ var EMBEDDED_RUNTIME_WINDOWS_SAFETY_CAPS = {
1223
+ REMOTEBUDDY_WORKERPAL_STARTUP_TIMEOUT_MS: "120000",
1224
+ WORKERPALS_DOCKER_AGENT_STARTUP_TIMEOUT_MS: "90000",
1225
+ WORKERPALS_SKIP_DOCKER_SELF_CHECK: "1",
1226
+ WORKERPALS_DOCKER_WARM_MEMORY_MB: "1024",
1227
+ WORKERPALS_DOCKER_WARM_CPUS: "1"
1228
+ };
1220
1229
  var GITHUB_OWNER = "PushPalsDev";
1221
1230
  var GITHUB_REPO = "pushpals";
1222
1231
  var GITHUB_API_URL = `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}`;
@@ -1235,6 +1244,98 @@ function formatTimestampedCliLine(line, at = new Date) {
1235
1244
  }
1236
1245
  return `[${at.toISOString()}]${text}`;
1237
1246
  }
1247
+ function formatRuntimeStartupTimingSummary(input) {
1248
+ const phaseSummary = input.phases.map((phase) => `${phase.name}=${Math.max(0, Math.floor(phase.durationMs))}ms(${phase.status.trim() || "unknown"})`).join(" ");
1249
+ const detail = typeof input.detail === "string" && input.detail.trim() ? ` detail=${input.detail.trim()}` : "";
1250
+ return `[pushpals] startup timing summary: outcome=${input.outcome} ` + `total=${Math.max(0, Math.floor(input.totalDurationMs))}ms${detail}` + (phaseSummary ? ` ${phaseSummary}` : "");
1251
+ }
1252
+ function describeWorkerExecutionReadiness(opts) {
1253
+ const onlineWorkers = Math.max(0, Math.floor(opts.onlineWorkers));
1254
+ const idleWorkers = Math.max(0, Math.floor(opts.idleWorkers));
1255
+ if (idleWorkers > 0) {
1256
+ return {
1257
+ state: "ready",
1258
+ detail: `${idleWorkers} idle / ${onlineWorkers} online`
1259
+ };
1260
+ }
1261
+ if (onlineWorkers > 0) {
1262
+ return {
1263
+ state: "warming",
1264
+ detail: `${idleWorkers} idle / ${onlineWorkers} online`,
1265
+ action: "Wait for WorkerPal warmup or active jobs to finish, then retry /status or send the request again."
1266
+ };
1267
+ }
1268
+ if (!opts.autoSpawnWorkerpals) {
1269
+ return {
1270
+ state: "blocked",
1271
+ detail: "No online WorkerPals are reported and auto-spawn is disabled.",
1272
+ action: "Start a WorkerPals backend manually or enable RemoteBuddy auto-spawn."
1273
+ };
1274
+ }
1275
+ if (opts.requireDocker && opts.dockerPrecheck?.status === "failed") {
1276
+ return {
1277
+ state: "blocked",
1278
+ detail: `Docker-backed WorkerPal auto-spawn is unavailable: ${opts.dockerPrecheck.detail}`,
1279
+ action: "Start Docker Desktop or the Docker daemon, then retry startup or rerun /status."
1280
+ };
1281
+ }
1282
+ return {
1283
+ state: "warming",
1284
+ detail: "No online WorkerPals are reported yet.",
1285
+ action: "Wait for WorkerPal auto-spawn/warmup to finish, then rerun /status."
1286
+ };
1287
+ }
1288
+ function formatWorkerExecutionReadinessLines(readiness) {
1289
+ const lines = [
1290
+ `[pushpals] workerExecution=${readiness.state} detail=${readiness.detail}`
1291
+ ];
1292
+ if (readiness.action) {
1293
+ lines.push(`[pushpals] workerExecutionAction=${readiness.action}`);
1294
+ }
1295
+ return lines;
1296
+ }
1297
+ function summarizeWorkerStatusRows(workers) {
1298
+ const onlineWorkers = workers.filter((worker) => Boolean(worker?.isOnline) && String(worker?.status ?? "").trim().toLowerCase() !== "offline");
1299
+ const idleWorkers = onlineWorkers.filter((worker) => Number(worker?.activeJobCount ?? 0) <= 0);
1300
+ return {
1301
+ onlineWorkers: onlineWorkers.length,
1302
+ idleWorkers: idleWorkers.length
1303
+ };
1304
+ }
1305
+ async function resolveWorkerExecutionReadiness(opts) {
1306
+ let workers = [];
1307
+ try {
1308
+ workers = await (opts.fetchWorkersFn ?? fetchWorkerStatusRows)(opts.serverUrl, opts.ttlMs);
1309
+ } catch (error) {
1310
+ return {
1311
+ state: "blocked",
1312
+ detail: `Unable to query WorkerPal status: ${String(error)}`,
1313
+ action: "Check runtime connectivity, then retry /status or restart the runtime."
1314
+ };
1315
+ }
1316
+ const { onlineWorkers, idleWorkers } = summarizeWorkerStatusRows(workers);
1317
+ let dockerPrecheck = opts.dockerPrecheck ?? null;
1318
+ const shouldProbeDockerAvailability = onlineWorkers === 0 && opts.autoSpawnWorkerpals && opts.dockerEnabled && opts.requireDocker && !dockerPrecheck && typeof opts.repoRoot === "string" && opts.repoRoot.trim().length > 0 && typeof opts.runtimeRoot === "string" && opts.runtimeRoot.trim().length > 0 && typeof opts.preflightUsesEmbeddedRuntime === "boolean";
1319
+ if (shouldProbeDockerAvailability) {
1320
+ dockerPrecheck = await (opts.precheckDockerAvailabilityFn ?? precheckWorkerpalDockerAvailability)({
1321
+ repoRoot: opts.repoRoot,
1322
+ runtimeRoot: opts.runtimeRoot,
1323
+ preflightUsesEmbeddedRuntime: opts.preflightUsesEmbeddedRuntime,
1324
+ autoSpawnWorkerpals: opts.autoSpawnWorkerpals,
1325
+ dockerEnabled: opts.dockerEnabled,
1326
+ requireDocker: opts.requireDocker,
1327
+ sessionId: opts.sessionId,
1328
+ baseEnv: opts.baseEnv
1329
+ });
1330
+ }
1331
+ return describeWorkerExecutionReadiness({
1332
+ autoSpawnWorkerpals: opts.autoSpawnWorkerpals,
1333
+ requireDocker: opts.dockerEnabled && opts.requireDocker,
1334
+ dockerPrecheck,
1335
+ onlineWorkers,
1336
+ idleWorkers
1337
+ });
1338
+ }
1238
1339
  function normalizeCliInteractiveMessage(input) {
1239
1340
  const trimmed = String(input ?? "").trim();
1240
1341
  const command = ASK_REMOTE_BUDDY_COMMAND.toLowerCase();
@@ -1792,8 +1893,15 @@ function copyBundledRuntimeAssets(runtimeRoot, force = true) {
1792
1893
  copyRuntimeAssetBundle(bundledSource, runtimeRoot, force);
1793
1894
  return true;
1794
1895
  }
1896
+ function hasSeededRuntimePreflightAssets(runtimeRoot) {
1897
+ const protocolSchemasDir = join2(runtimeRoot, "protocol", "schemas");
1898
+ const hasProtocolSchemas = existsSync4(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(protocolSchemasDir, "events.schema.json"));
1899
+ return existsSync4(join2(runtimeRoot, ".env.example")) && existsSync4(join2(runtimeRoot, "vision.example.md")) && existsSync4(join2(runtimeRoot, "configs", "default.toml")) && existsSync4(join2(runtimeRoot, "prompts")) && hasProtocolSchemas && isCompleteWorkerpalSandboxRoot(join2(runtimeRoot, "sandbox"));
1900
+ }
1795
1901
  function seedRuntimePreflightAssets(runtimeRoot) {
1796
- copyBundledRuntimeAssets(runtimeRoot, false);
1902
+ if (!hasSeededRuntimePreflightAssets(runtimeRoot)) {
1903
+ copyBundledRuntimeAssets(runtimeRoot, false);
1904
+ }
1797
1905
  writeTextFileIfMissing(join2(runtimeRoot, ".env"), `# Local PushPals runtime environment
1798
1906
  `);
1799
1907
  const localExamplePath = join2(runtimeRoot, "configs", "local.example.toml");
@@ -1946,12 +2054,19 @@ function cleanupLegacyRuntimeBinaryLayouts(runtimeRoot, platformKey, activeBinDi
1946
2054
  function buildEmbeddedRuntimeEnv(baseEnv, opts) {
1947
2055
  const env = normalizeChildProcessEnv(baseEnv);
1948
2056
  const useRuntimeConfig = opts.useRuntimeConfig !== false;
2057
+ const platform = opts.platform ?? process.platform;
1949
2058
  const inherited = { ...env };
1950
2059
  if (!useRuntimeConfig) {
1951
2060
  delete inherited.PUSHPALS_CONFIG_DIR_OVERRIDE;
1952
2061
  delete inherited.PUSHPALS_WORKERPALS_SANDBOX_ROOT;
1953
2062
  delete inherited.PUSHPALS_RUNTIME_TAG;
1954
2063
  }
2064
+ const disableEmbeddedSafetyCaps = parseBooleanFlag(env[EMBEDDED_RUNTIME_SAFETY_CAP_DISABLE_ENV]) === true;
2065
+ const shouldApplyEmbeddedWindowsSafetyCaps = useRuntimeConfig && platform === "win32" && !disableEmbeddedSafetyCaps;
2066
+ const embeddedWindowsSafetyCaps = shouldApplyEmbeddedWindowsSafetyCaps ? Object.fromEntries(Object.entries(EMBEDDED_RUNTIME_WINDOWS_SAFETY_CAPS).filter(([key]) => {
2067
+ const existing = env[key];
2068
+ return typeof existing !== "string" || existing.trim().length === 0;
2069
+ })) : {};
1955
2070
  return {
1956
2071
  ...inherited,
1957
2072
  PUSHPALS_REPO_ROOT_OVERRIDE: opts.repoRoot,
@@ -1966,12 +2081,23 @@ function buildEmbeddedRuntimeEnv(baseEnv, opts) {
1966
2081
  },
1967
2082
  PUSHPALS_PROTOCOL_SCHEMAS_DIR: join2(opts.runtimeRoot, "protocol", "schemas"),
1968
2083
  ...typeof opts.sessionId === "string" && opts.sessionId.trim() ? { PUSHPALS_SESSION_ID: opts.sessionId.trim() } : {},
2084
+ ...embeddedWindowsSafetyCaps,
1969
2085
  ...typeof env.PUSHPALS_GIT_BIN === "string" && env.PUSHPALS_GIT_BIN.trim() ? { PUSHPALS_GIT_BIN: env.PUSHPALS_GIT_BIN.trim() } : {},
1970
2086
  ...typeof env.PUSHPALS_GIT_BIN_ABSOLUTE === "string" && env.PUSHPALS_GIT_BIN_ABSOLUTE.trim() ? { PUSHPALS_GIT_BIN_ABSOLUTE: env.PUSHPALS_GIT_BIN_ABSOLUTE.trim() } : {},
1971
2087
  ...typeof env.PUSHPALS_DOCKER_BIN === "string" && env.PUSHPALS_DOCKER_BIN.trim() ? { PUSHPALS_DOCKER_BIN: env.PUSHPALS_DOCKER_BIN.trim() } : {},
1972
2088
  ...typeof env.PUSHPALS_DOCKER_BIN_ABSOLUTE === "string" && env.PUSHPALS_DOCKER_BIN_ABSOLUTE.trim() ? { PUSHPALS_DOCKER_BIN_ABSOLUTE: env.PUSHPALS_DOCKER_BIN_ABSOLUTE.trim() } : {}
1973
2089
  };
1974
2090
  }
2091
+ function parseBooleanFlag(raw) {
2092
+ const normalized = String(raw ?? "").trim().toLowerCase();
2093
+ if (!normalized)
2094
+ return null;
2095
+ if (["1", "true", "yes", "on", "y"].includes(normalized))
2096
+ return true;
2097
+ if (["0", "false", "no", "off", "n"].includes(normalized))
2098
+ return false;
2099
+ return null;
2100
+ }
1975
2101
  function normalizeChildProcessEnv(baseEnv, platform = process.platform) {
1976
2102
  const env = {};
1977
2103
  for (const [key, value] of Object.entries(baseEnv)) {
@@ -2840,6 +2966,12 @@ async function precheckWorkerpalDockerAvailability(opts) {
2840
2966
  function resolveWorkerpalCapacityTimeoutMs(config) {
2841
2967
  return Math.max(config.remotebuddy.waitForWorkerpalMs, config.remotebuddy.workerpalStartupTimeoutMs, config.remotebuddy.workerpalDocker ? config.workerpals.dockerAgentStartupTimeoutMs + 15000 : 0, 1e4);
2842
2968
  }
2969
+ function resolveWorkerpalStartupReadinessProbeTimeoutMs(config) {
2970
+ return Math.max(5000, Math.min(resolveWorkerpalCapacityTimeoutMs(config), WORKERPAL_STARTUP_READINESS_PROBE_MAX_MS));
2971
+ }
2972
+ function shouldRunEmbeddedRuntimeStartupPrechecks(opts) {
2973
+ return !opts.serverHealthy && !opts.noAutoStart;
2974
+ }
2843
2975
  async function checkGitRemoteConfigured(repoRoot, remote, env) {
2844
2976
  const normalizedRemote = String(remote ?? "").trim();
2845
2977
  if (!normalizedRemote) {
@@ -3193,15 +3325,14 @@ async function waitForWorkerpalCapacity(opts) {
3193
3325
  let lastObservedOnline = 0;
3194
3326
  while (Date.now() < deadline) {
3195
3327
  const workers = await (opts.fetchWorkersFn ?? fetchWorkerStatusRows)(opts.serverUrl, opts.ttlMs);
3196
- const onlineWorkers = workers.filter((worker) => Boolean(worker?.isOnline) && String(worker?.status ?? "").trim().toLowerCase() !== "offline");
3197
- const idleWorkers = onlineWorkers.filter((worker) => Number(worker?.activeJobCount ?? 0) <= 0);
3198
- if (onlineWorkers.length > 0) {
3199
- lastObservedOnline = Math.max(lastObservedOnline, onlineWorkers.length);
3328
+ const summary = summarizeWorkerStatusRows(workers);
3329
+ if (summary.onlineWorkers > 0) {
3330
+ lastObservedOnline = Math.max(lastObservedOnline, summary.onlineWorkers);
3200
3331
  }
3201
- if (idleWorkers.length > 0) {
3332
+ if (summary.idleWorkers > 0) {
3202
3333
  return {
3203
3334
  ok: true,
3204
- detail: `${idleWorkers.length} idle / ${onlineWorkers.length} online`
3335
+ detail: `${summary.idleWorkers} idle / ${summary.onlineWorkers} online`
3205
3336
  };
3206
3337
  }
3207
3338
  await (opts.sleepFn ?? Bun.sleep)(DEFAULT_RUNTIME_BOOT_POLL_MS);
@@ -3324,6 +3455,29 @@ async function autoStartRuntimeServices(opts) {
3324
3455
  applyResolvedGitBinaryToRuntimeEnv(runtimeEnv, resolvedGitBinary);
3325
3456
  }
3326
3457
  const services = [];
3458
+ const startupStartedAt = Date.now();
3459
+ const startupPhases = [];
3460
+ const recordStartupPhase = (name, startedAt, status) => {
3461
+ startupPhases.push({
3462
+ name,
3463
+ durationMs: Math.max(0, Date.now() - startedAt),
3464
+ status
3465
+ });
3466
+ };
3467
+ const emitStartupTimingSummary = (outcome, detail) => {
3468
+ const summary = formatRuntimeStartupTimingSummary({
3469
+ outcome,
3470
+ totalDurationMs: Date.now() - startupStartedAt,
3471
+ phases: startupPhases,
3472
+ detail
3473
+ });
3474
+ if (outcome === "failed") {
3475
+ console.warn(summary);
3476
+ } else {
3477
+ console.log(summary);
3478
+ }
3479
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, summary);
3480
+ };
3327
3481
  const runToken = timestampFileToken();
3328
3482
  const logDir = join2(runtimeRoot, "logs", "bootstrap");
3329
3483
  mkdirSync(logDir, { recursive: true });
@@ -3341,6 +3495,7 @@ async function autoStartRuntimeServices(opts) {
3341
3495
  console.log(`[pushpals] service log (source_control_manager)=${serviceLogPaths.source_control_manager}`);
3342
3496
  const serverHealthy = await probeServer(opts.serverUrl);
3343
3497
  if (!serverHealthy) {
3498
+ const serverPhaseStartedAt = Date.now();
3344
3499
  console.log("[pushpals] Starting embedded server...");
3345
3500
  const serverService = spawnRuntimeService("server", [runtimeBinaries.server], opts.repoRoot, runtimeEnv, serviceLogPaths.server, runtimeServicesLogPath);
3346
3501
  services.push(serverService);
@@ -3351,6 +3506,8 @@ async function autoStartRuntimeServices(opts) {
3351
3506
  if (serverService.exited) {
3352
3507
  const tail = readLogTail(serverService.logPath);
3353
3508
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded server exited during bootstrap (code=${serverService.exitCode ?? "unknown"}).`);
3509
+ recordStartupPhase("server", serverPhaseStartedAt, "exited");
3510
+ emitStartupTimingSummary("failed", "server exited during bootstrap");
3354
3511
  stopRuntimeServices(services);
3355
3512
  throw new Error(`Embedded server exited during bootstrap (code=${serverService.exitCode ?? "unknown"}). ` + `See ${serverService.logPath}${tail ? `
3356
3513
  --- server log tail ---
@@ -3365,29 +3522,38 @@ ${tail}` : ""}`);
3365
3522
  if (!serverIsReady) {
3366
3523
  const tail = readLogTail(serverService.logPath);
3367
3524
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded server did not become healthy within ${DEFAULT_SERVER_BOOT_TIMEOUT_MS}ms.`);
3525
+ recordStartupPhase("server", serverPhaseStartedAt, "timeout");
3526
+ emitStartupTimingSummary("failed", "server health timeout");
3368
3527
  stopRuntimeServices(services);
3369
3528
  throw new Error(`Embedded server did not become healthy within ${DEFAULT_SERVER_BOOT_TIMEOUT_MS}ms. ` + `See ${serverService.logPath}${tail ? `
3370
3529
  --- server log tail ---
3371
3530
  ${tail}` : ""}`);
3372
3531
  }
3532
+ recordStartupPhase("server", serverPhaseStartedAt, "started");
3373
3533
  console.log("[pushpals] Embedded server is healthy.");
3374
3534
  } else {
3535
+ recordStartupPhase("server", Date.now(), "reused");
3375
3536
  console.log("[pushpals] Server already healthy; skipping embedded server start.");
3376
3537
  appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] server already healthy; embedded server start skipped.");
3377
3538
  }
3378
3539
  if (localBuddyEnabled) {
3540
+ const localBuddyPhaseStartedAt = Date.now();
3379
3541
  console.log("[pushpals] Starting embedded LocalBuddy...");
3380
3542
  const localbuddyService = spawnRuntimeService("localbuddy", [runtimeBinaries.localbuddy], opts.repoRoot, runtimeEnv, serviceLogPaths.localbuddy, runtimeServicesLogPath);
3381
3543
  services.push(localbuddyService);
3382
3544
  console.log(`[pushpals] localbuddy log: ${localbuddyService.logPath}`);
3545
+ recordStartupPhase("localbuddy", localBuddyPhaseStartedAt, "started");
3383
3546
  } else {
3547
+ recordStartupPhase("localbuddy", Date.now(), "skipped");
3384
3548
  console.log("[pushpals] Embedded LocalBuddy disabled for this CLI session; skipping start.");
3385
3549
  appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] localbuddy disabled for this CLI session; embedded localbuddy start skipped.");
3386
3550
  }
3551
+ const remoteBuddyPhaseStartedAt = Date.now();
3387
3552
  console.log("[pushpals] Starting embedded RemoteBuddy...");
3388
3553
  const remotebuddyService = spawnRuntimeService("remotebuddy", [runtimeBinaries.remotebuddy], opts.repoRoot, runtimeEnv, serviceLogPaths.remotebuddy, runtimeServicesLogPath);
3389
3554
  services.push(remotebuddyService);
3390
3555
  console.log(`[pushpals] remotebuddy log: ${remotebuddyService.logPath}`);
3556
+ recordStartupPhase("remotebuddy", remoteBuddyPhaseStartedAt, "started");
3391
3557
  let lastReportedRemoteBuddyAutonomyState = "unknown";
3392
3558
  const reportRemoteBuddyAutonomousEngineState = () => {
3393
3559
  const autonomyState = readRemoteBuddyAutonomousEngineState(remotebuddyService.logPath);
@@ -3405,48 +3571,58 @@ ${tail}` : ""}`);
3405
3571
  };
3406
3572
  reportRemoteBuddyAutonomousEngineState();
3407
3573
  if (runtimePreflight.config.remotebuddy.autoSpawnWorkerpals) {
3408
- const workerpalReadyTimeoutMs = resolveWorkerpalCapacityTimeoutMs(runtimePreflight.config);
3574
+ const workerpalPhaseStartedAt = Date.now();
3575
+ const workerpalReadinessProbeTimeoutMs = resolveWorkerpalStartupReadinessProbeTimeoutMs(runtimePreflight.config);
3409
3576
  const workerpalCapacity = await waitForWorkerpalCapacity({
3410
3577
  serverUrl: opts.serverUrl,
3411
- timeoutMs: workerpalReadyTimeoutMs,
3578
+ timeoutMs: workerpalReadinessProbeTimeoutMs,
3412
3579
  ttlMs: runtimePreflight.config.remotebuddy.workerpalOnlineTtlMs
3413
3580
  });
3414
3581
  if (!workerpalCapacity.ok) {
3415
- const tail = readLogTail(remotebuddyService.logPath);
3416
- appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded workerpal capacity did not become available within ${workerpalReadyTimeoutMs}ms.`);
3417
- stopRuntimeServices(services);
3418
- throw new Error(`Embedded WorkerPal capacity did not become available within ${workerpalReadyTimeoutMs}ms (${workerpalCapacity.detail}). ` + `See ${remotebuddyService.logPath}${tail ? `
3419
- --- remotebuddy log tail ---
3420
- ${tail}` : ""}`);
3582
+ const startupProbeWarning = `embedded workerpal readiness probe did not find idle capacity within ${workerpalReadinessProbeTimeoutMs}ms ` + `(${workerpalCapacity.detail}); continuing startup while WorkerPal warmup finishes in the background.`;
3583
+ console.warn(`[pushpals] ${startupProbeWarning}`);
3584
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] ${startupProbeWarning}`);
3585
+ recordStartupPhase("workerpal", workerpalPhaseStartedAt, "deferred");
3586
+ } else {
3587
+ console.log(`[pushpals] Embedded WorkerPal capacity is ready (${workerpalCapacity.detail}).`);
3588
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded workerpal capacity ready (${workerpalCapacity.detail}).`);
3589
+ recordStartupPhase("workerpal", workerpalPhaseStartedAt, "ready");
3421
3590
  }
3422
- console.log(`[pushpals] Embedded WorkerPal capacity is ready (${workerpalCapacity.detail}).`);
3423
- appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded workerpal capacity ready (${workerpalCapacity.detail}).`);
3591
+ } else {
3592
+ recordStartupPhase("workerpal", Date.now(), "disabled");
3424
3593
  }
3425
3594
  const scmHealthy = await probeSourceControlManager(opts.sourceControlManagerPort);
3426
3595
  const scmGitProbe = await resolveSourceControlManagerGitProbe(opts.repoRoot, runtimeEnv, process.platform);
3427
3596
  const scmRemoteStatus = await checkGitRemoteConfigured(opts.repoRoot, opts.sourceControlManagerRemote, runtimeEnv);
3428
3597
  if (!scmHealthy) {
3598
+ const scmPhaseStartedAt = Date.now();
3429
3599
  if (!scmGitProbe.ok) {
3430
3600
  console.warn("[pushpals] Git is not available to embedded SourceControlManager; skipping SCM startup.");
3431
3601
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] source_control_manager skipped: git is unavailable in embedded runtime env (${scmGitProbe.detail}).`);
3602
+ recordStartupPhase("source_control_manager", scmPhaseStartedAt, "skipped_no_git");
3432
3603
  } else if (scmRemoteStatus.status === "error") {
3433
3604
  console.warn(`[pushpals] Could not inspect SourceControlManager git remote "${opts.sourceControlManagerRemote}"; skipping SCM startup.`);
3434
3605
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] source_control_manager skipped: remote "${opts.sourceControlManagerRemote}" could not be inspected (${scmRemoteStatus.detail}).`);
3606
+ recordStartupPhase("source_control_manager", scmPhaseStartedAt, "skipped_remote_error");
3435
3607
  } else if (scmRemoteStatus.status === "ok") {
3436
3608
  console.log(`[pushpals] Embedded SourceControlManager git=${scmGitProbe.detail}`);
3437
3609
  console.log("[pushpals] Starting embedded SourceControlManager...");
3438
3610
  const sourceControlManagerService = spawnRuntimeService("source_control_manager", [runtimeBinaries.sourceControlManager, "--skip-clean-check"], opts.repoRoot, runtimeEnv, serviceLogPaths.source_control_manager, runtimeServicesLogPath);
3439
3611
  services.push(sourceControlManagerService);
3440
3612
  console.log(`[pushpals] source_control_manager log: ${sourceControlManagerService.logPath}`);
3613
+ recordStartupPhase("source_control_manager", scmPhaseStartedAt, "started");
3441
3614
  } else {
3442
3615
  console.log(`[pushpals] Repo has no git remote "${opts.sourceControlManagerRemote}"; skipping embedded SourceControlManager.`);
3443
3616
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] source_control_manager skipped: repo has no remote "${opts.sourceControlManagerRemote}".`);
3617
+ recordStartupPhase("source_control_manager", scmPhaseStartedAt, "skipped_no_remote");
3444
3618
  }
3445
3619
  } else {
3620
+ recordStartupPhase("source_control_manager", Date.now(), "reused");
3446
3621
  console.log("[pushpals] SourceControlManager already healthy; skipping embedded start.");
3447
3622
  appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] source_control_manager already healthy; embedded start skipped.");
3448
3623
  }
3449
3624
  const deadline = Date.now() + DEFAULT_RUNTIME_BOOT_TIMEOUT_MS;
3625
+ const readinessPhaseStartedAt = Date.now();
3450
3626
  while (Date.now() < deadline) {
3451
3627
  reportRemoteBuddyAutonomousEngineState();
3452
3628
  for (let i = services.length - 1;i >= 0; i--) {
@@ -3467,6 +3643,8 @@ ${tail2}`);
3467
3643
  }
3468
3644
  const tail = readLogTail(service.logPath);
3469
3645
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}).`);
3646
+ recordStartupPhase("readiness", readinessPhaseStartedAt, "failed");
3647
+ emitStartupTimingSummary("failed", `${service.name} exited during startup`);
3470
3648
  stopRuntimeServices(services);
3471
3649
  throw new Error(`Embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}). ` + `See ${service.logPath}${tail ? `
3472
3650
  --- ${service.name} log tail ---
@@ -3499,6 +3677,8 @@ ${tail2}`);
3499
3677
  }
3500
3678
  const tail = readLogTail(service.logPath);
3501
3679
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}).`);
3680
+ recordStartupPhase("readiness", readinessPhaseStartedAt, "failed");
3681
+ emitStartupTimingSummary("failed", `${service.name} exited immediately after bootstrap`);
3502
3682
  stopRuntimeServices(services);
3503
3683
  throw new Error(`Embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}). ` + `See ${service.logPath}${tail ? `
3504
3684
  --- ${service.name} log tail ---
@@ -3507,6 +3687,8 @@ ${tail}` : ""}`);
3507
3687
  await Bun.sleep(250);
3508
3688
  }
3509
3689
  console.log("[pushpals] Embedded runtime is ready.");
3690
+ recordStartupPhase("readiness", readinessPhaseStartedAt, "ready");
3691
+ emitStartupTimingSummary("ready");
3510
3692
  appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] embedded runtime is ready.");
3511
3693
  return {
3512
3694
  services,
@@ -3519,13 +3701,19 @@ ${tail}` : ""}`);
3519
3701
  const remoteBuddyHealth = await probeRemoteBuddySessionConsumer(opts.serverUrl, opts.sessionId);
3520
3702
  if (!localBuddyEnabled && !remoteBuddyHealth.ok) {
3521
3703
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] timed out waiting for RemoteBuddy session consumer readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms (${remoteBuddyHealth.detail}).`);
3704
+ recordStartupPhase("readiness", readinessPhaseStartedAt, "timeout");
3705
+ emitStartupTimingSummary("failed", "remotebuddy readiness timeout");
3522
3706
  throw new Error(`Timed out waiting for RemoteBuddy session consumer readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms (${remoteBuddyHealth.detail})`);
3523
3707
  }
3524
3708
  if (!localBuddyEnabled) {
3525
3709
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] timed out waiting for embedded runtime readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms.`);
3710
+ recordStartupPhase("readiness", readinessPhaseStartedAt, "timeout");
3711
+ emitStartupTimingSummary("failed", "embedded runtime readiness timeout");
3526
3712
  throw new Error(`Timed out waiting for embedded runtime readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
3527
3713
  }
3528
3714
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] timed out waiting for LocalBuddy at ${opts.localAgentUrl} and RemoteBuddy session consumer after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms.`);
3715
+ recordStartupPhase("readiness", readinessPhaseStartedAt, "timeout");
3716
+ emitStartupTimingSummary("failed", "localbuddy and remotebuddy readiness timeout");
3529
3717
  throw new Error(`Timed out waiting for LocalBuddy at ${opts.localAgentUrl} and RemoteBuddy session consumer after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
3530
3718
  }
3531
3719
  function readCliState(pathValue) {
@@ -4127,32 +4315,59 @@ async function main() {
4127
4315
  } else {
4128
4316
  console.warn("[pushpals] RemoteBuddy autonomy is disabled in config (remotebuddy.autonomy.enabled=false); continuing.");
4129
4317
  }
4130
- const scmGitPrecheck = await precheckSourceControlManagerGitAvailability({
4131
- repoRoot,
4132
- remote: config.sourceControlManager.remote,
4133
- runtimeRoot: preparedRuntime.runtimeRoot,
4134
- preflightUsesEmbeddedRuntime: preparedRuntime.preflightUsesEmbeddedRuntime
4135
- });
4136
- if (scmGitPrecheck.status === "failed") {
4137
- console.error(`[pushpals] Precheck failed: embedded SourceControlManager git command is unavailable (${scmGitPrecheck.detail}).`);
4138
- process.exit(1);
4139
- }
4140
- const workerpalDockerPrecheck = await precheckWorkerpalDockerAvailability({
4141
- repoRoot,
4142
- runtimeRoot: preparedRuntime.runtimeRoot,
4143
- preflightUsesEmbeddedRuntime: preparedRuntime.preflightUsesEmbeddedRuntime,
4144
- autoSpawnWorkerpals: Boolean(config.remotebuddy.autoSpawnWorkerpals),
4145
- dockerEnabled: Boolean(config.remotebuddy.workerpalDocker),
4146
- requireDocker: Boolean(config.remotebuddy.workerpalRequireDocker),
4147
- baseEnv: scmGitPrecheck.env
4148
- });
4149
- const precheckPassed = await enforcePushpalsRemoteBranchPrecheck(repoRoot, config.sourceControlManager.remote, config.sourceControlManager.mainBranch);
4150
- if (!precheckPassed) {
4151
- process.exit(1);
4152
- }
4153
4318
  const serverUrl = normalizeLoopbackUrl(parsed.serverUrl ?? process.env.PUSHPALS_SERVER_URL, config.server.url);
4154
4319
  const localAgentUrl = normalizeLoopbackUrl(parsed.localAgentUrl ?? process.env.EXPO_PUBLIC_LOCAL_AGENT_URL, config.client.localAgentUrl);
4155
4320
  const sessionId = String(parsed.sessionId ?? process.env.PUSHPALS_SESSION_ID ?? config.sessionId).trim();
4321
+ let serverHealthy = await probeServer(serverUrl);
4322
+ const serverWasAlreadyHealthy = serverHealthy;
4323
+ const runEmbeddedRuntimeStartupPrechecks = shouldRunEmbeddedRuntimeStartupPrechecks({
4324
+ serverHealthy,
4325
+ noAutoStart: parsed.noAutoStart
4326
+ });
4327
+ if (runEmbeddedRuntimeStartupPrechecks) {
4328
+ const precheckPassed = await enforcePushpalsRemoteBranchPrecheck(repoRoot, config.sourceControlManager.remote, config.sourceControlManager.mainBranch);
4329
+ if (!precheckPassed) {
4330
+ process.exit(1);
4331
+ }
4332
+ }
4333
+ let scmGitPrecheck = {
4334
+ status: "skipped",
4335
+ detail: runEmbeddedRuntimeStartupPrechecks ? "embedded SourceControlManager startup precheck not run" : serverHealthy ? "embedded SourceControlManager startup precheck skipped because runtime is already healthy" : "embedded SourceControlManager startup precheck skipped because auto-start is disabled",
4336
+ env: buildEmbeddedRuntimeEnv(process.env, {
4337
+ repoRoot,
4338
+ runtimeRoot: preparedRuntime.runtimeRoot,
4339
+ useRuntimeConfig: preparedRuntime.preflightUsesEmbeddedRuntime,
4340
+ sessionId
4341
+ })
4342
+ };
4343
+ let workerpalDockerPrecheck = {
4344
+ status: "skipped",
4345
+ detail: runEmbeddedRuntimeStartupPrechecks ? "embedded WorkerPal Docker startup precheck not run" : serverHealthy ? "embedded WorkerPal Docker startup precheck skipped because runtime is already healthy" : "embedded WorkerPal Docker startup precheck skipped because auto-start is disabled",
4346
+ env: scmGitPrecheck.env
4347
+ };
4348
+ if (runEmbeddedRuntimeStartupPrechecks) {
4349
+ scmGitPrecheck = await precheckSourceControlManagerGitAvailability({
4350
+ repoRoot,
4351
+ remote: config.sourceControlManager.remote,
4352
+ runtimeRoot: preparedRuntime.runtimeRoot,
4353
+ preflightUsesEmbeddedRuntime: preparedRuntime.preflightUsesEmbeddedRuntime,
4354
+ sessionId
4355
+ });
4356
+ if (scmGitPrecheck.status === "failed") {
4357
+ console.error(`[pushpals] Precheck failed: embedded SourceControlManager git command is unavailable (${scmGitPrecheck.detail}).`);
4358
+ process.exit(1);
4359
+ }
4360
+ workerpalDockerPrecheck = await precheckWorkerpalDockerAvailability({
4361
+ repoRoot,
4362
+ runtimeRoot: preparedRuntime.runtimeRoot,
4363
+ preflightUsesEmbeddedRuntime: preparedRuntime.preflightUsesEmbeddedRuntime,
4364
+ autoSpawnWorkerpals: Boolean(config.remotebuddy.autoSpawnWorkerpals),
4365
+ dockerEnabled: Boolean(config.remotebuddy.workerpalDocker),
4366
+ requireDocker: Boolean(config.remotebuddy.workerpalRequireDocker),
4367
+ sessionId,
4368
+ baseEnv: scmGitPrecheck.env
4369
+ });
4370
+ }
4156
4371
  const cliVersion = String(process.env.PUSHPALS_CLI_PACKAGE_VERSION ?? "").trim() || "unknown";
4157
4372
  const cliClient = {
4158
4373
  clientId: createRuntimeClientId("cli"),
@@ -4196,6 +4411,31 @@ async function main() {
4196
4411
  console.log(`[pushpals] ${cleanup.detail} (${phase}).`);
4197
4412
  }
4198
4413
  };
4414
+ const reportWorkerExecutionReadiness = async () => {
4415
+ const readiness = await resolveWorkerExecutionReadiness({
4416
+ serverUrl,
4417
+ ttlMs: config.remotebuddy.workerpalOnlineTtlMs,
4418
+ autoSpawnWorkerpals: Boolean(config.remotebuddy.autoSpawnWorkerpals),
4419
+ dockerEnabled: Boolean(config.remotebuddy.workerpalDocker),
4420
+ requireDocker: Boolean(config.remotebuddy.workerpalRequireDocker),
4421
+ repoRoot,
4422
+ runtimeRoot: preparedRuntime.runtimeRoot,
4423
+ preflightUsesEmbeddedRuntime: preparedRuntime.preflightUsesEmbeddedRuntime,
4424
+ sessionId,
4425
+ dockerPrecheck: workerpalDockerPrecheck,
4426
+ baseEnv: workerpalDockerPrecheck.env
4427
+ });
4428
+ for (const line of formatWorkerExecutionReadinessLines(readiness)) {
4429
+ console.log(line);
4430
+ }
4431
+ return readiness;
4432
+ };
4433
+ const reportWorkerExecutionReadinessFromSnapshot = (readiness) => {
4434
+ for (const line of formatWorkerExecutionReadinessLines(readiness)) {
4435
+ console.log(line);
4436
+ }
4437
+ return readiness;
4438
+ };
4199
4439
  const stopAutoStartedServices = () => {
4200
4440
  if (autoStartedServices.length === 0)
4201
4441
  return;
@@ -4220,14 +4460,12 @@ async function main() {
4220
4460
  await cleanupWorkerpalWarmContainersIfNeeded("cli shutdown");
4221
4461
  await cleanupPushPalsGitWorktreesIfNeeded("cli shutdown");
4222
4462
  };
4223
- let serverHealthy = await probeServer(serverUrl);
4224
- const serverWasAlreadyHealthy = serverHealthy;
4225
4463
  if (!serverHealthy && workerpalDockerPrecheck.status === "failed") {
4226
4464
  console.error(`[pushpals] Precheck failed: Docker-backed WorkerPal auto-spawn is required but Docker is unavailable (${workerpalDockerPrecheck.detail}).`);
4227
4465
  console.error("[pushpals] Precheck failed: start Docker Desktop or the Docker daemon, then retry pushpals.");
4228
4466
  process.exit(1);
4229
4467
  }
4230
- if (workerpalDockerPrecheck.status !== "failed") {
4468
+ if (runEmbeddedRuntimeStartupPrechecks && workerpalDockerPrecheck.status !== "failed") {
4231
4469
  const workerpalImagePrecheck = await prepareEmbeddedWorkerpalDockerImageIfNeeded({
4232
4470
  preparedRuntime,
4233
4471
  config,
@@ -4247,8 +4485,10 @@ async function main() {
4247
4485
  detail: `No connected RemoteBuddy session consumer found for session ${sessionId}`
4248
4486
  };
4249
4487
  if (!serverHealthy) {
4250
- await cleanupWorkerpalWarmContainersIfNeeded("startup preflight");
4251
- await cleanupPushPalsGitWorktreesIfNeeded("startup preflight");
4488
+ if (runEmbeddedRuntimeStartupPrechecks) {
4489
+ await cleanupWorkerpalWarmContainersIfNeeded("startup preflight");
4490
+ await cleanupPushPalsGitWorktreesIfNeeded("startup preflight");
4491
+ }
4252
4492
  if (!parsed.noAutoStart) {
4253
4493
  try {
4254
4494
  const startedRuntime = await autoStartRuntimeServices({
@@ -4269,6 +4509,10 @@ async function main() {
4269
4509
  } catch (err) {
4270
4510
  console.error(`[pushpals] Auto-start failed: ${String(err)}`);
4271
4511
  stopAutoStartedServices();
4512
+ if (runEmbeddedRuntimeStartupPrechecks) {
4513
+ await cleanupWorkerpalWarmContainersIfNeeded("startup failure cleanup");
4514
+ await cleanupPushPalsGitWorktreesIfNeeded("startup failure cleanup");
4515
+ }
4272
4516
  }
4273
4517
  }
4274
4518
  if (!serverHealthy) {
@@ -4318,20 +4562,33 @@ async function main() {
4318
4562
  }
4319
4563
  const workerpalCapacity = await waitForWorkerpalCapacity({
4320
4564
  serverUrl,
4321
- timeoutMs: resolveWorkerpalCapacityTimeoutMs(config),
4565
+ timeoutMs: resolveWorkerpalStartupReadinessProbeTimeoutMs(config),
4322
4566
  ttlMs: config.remotebuddy.workerpalOnlineTtlMs
4323
4567
  });
4324
4568
  if (!workerpalCapacity.ok) {
4325
- stopAutoStartedServices();
4326
- console.error(`[pushpals] WorkerPal capacity is not ready for repo ${repoRoot}: ${workerpalCapacity.detail}.`);
4569
+ console.warn(`[pushpals] WorkerPal readiness probe did not find idle capacity yet (${workerpalCapacity.detail}).`);
4570
+ console.warn("[pushpals] Continuing startup; WorkerPal warmup may still be in progress and first task dispatch can be delayed.");
4327
4571
  if (workerpalDockerPrecheck.status === "failed") {
4328
- console.error(`[pushpals] Docker precheck detail: ${workerpalDockerPrecheck.detail}`);
4572
+ console.warn(`[pushpals] Docker precheck detail: ${workerpalDockerPrecheck.detail}`);
4329
4573
  } else if (serverWasAlreadyHealthy) {
4330
- console.error("[pushpals] A PushPals runtime is already serving this repo, but it does not currently have an idle WorkerPal available.");
4331
- console.error("[pushpals] Wait for a worker to become idle or restart the runtime after fixing WorkerPal startup.");
4574
+ console.warn("[pushpals] A PushPals runtime is already serving this repo, but it does not currently have an idle WorkerPal available.");
4575
+ console.warn("[pushpals] Wait for a worker to become idle or restart the runtime after fixing WorkerPal startup.");
4332
4576
  }
4333
- process.exit(1);
4334
4577
  }
4578
+ const startupWorkerExecutionReadiness = workerpalCapacity.ok ? {
4579
+ state: "ready",
4580
+ detail: workerpalCapacity.detail
4581
+ } : workerpalDockerPrecheck.status === "failed" ? describeWorkerExecutionReadiness({
4582
+ autoSpawnWorkerpals: Boolean(config.remotebuddy.autoSpawnWorkerpals),
4583
+ requireDocker: Boolean(config.remotebuddy.workerpalDocker) && Boolean(config.remotebuddy.workerpalRequireDocker),
4584
+ dockerPrecheck: workerpalDockerPrecheck,
4585
+ onlineWorkers: 0,
4586
+ idleWorkers: 0
4587
+ }) : {
4588
+ state: "warming",
4589
+ detail: workerpalCapacity.detail,
4590
+ action: "Wait for WorkerPal auto-spawn/warmup to finish, then rerun /status."
4591
+ };
4335
4592
  const saved = statePath ? readCliState(statePath) : {};
4336
4593
  pushpalsLogPath = pushpalsLogPath || (typeof saved.pushpalsLogPath === "string" ? saved.pushpalsLogPath : undefined);
4337
4594
  const preferredHubUrl = normalizeUrl(parsed.monitoringHubUrl ?? process.env.PUSHPALS_MONITOR_URL ?? saved.monitoringHubUrl ?? "");
@@ -4369,6 +4626,7 @@ async function main() {
4369
4626
  console.log(`[pushpals] repoRoot=${repoRoot}`);
4370
4627
  console.log(`[pushpals] pushpalsLog=${pushpalsLogPath ?? "unavailable"}`);
4371
4628
  console.log(`[pushpals] cliStateFile=${statePath ?? "unavailable"}`);
4629
+ reportWorkerExecutionReadinessFromSnapshot(startupWorkerExecutionReadiness);
4372
4630
  if (parsed.runtimeOnly) {
4373
4631
  console.log("[pushpals] runtimeOnly=true");
4374
4632
  } else {
@@ -4480,6 +4738,7 @@ ${line}
4480
4738
  console.log(`[pushpals] repoRoot=${repoRoot}`);
4481
4739
  console.log(`[pushpals] pushpalsLog=${pushpalsLogPath ?? "unavailable"}`);
4482
4740
  console.log(monitoringHubUrl ? `[pushpals] monitoringHubUrl=${monitoringHubUrl}` : "[pushpals] monitoringHubUrl=unavailable");
4741
+ await reportWorkerExecutionReadiness();
4483
4742
  rl.prompt();
4484
4743
  continue;
4485
4744
  }
@@ -4518,6 +4777,8 @@ if (import.meta.main) {
4518
4777
  export {
4519
4778
  waitForWorkerpalCapacity,
4520
4779
  startEmbeddedMonitoringHub,
4780
+ shouldRunEmbeddedRuntimeStartupPrechecks,
4781
+ resolveWorkerExecutionReadiness,
4521
4782
  resolveWindowsWhereExecutableCandidatesForEnv,
4522
4783
  resolveWindowsShellExecutableCandidatesForEnv,
4523
4784
  resolveRuntimeGitExecutableCandidates,
@@ -4538,13 +4799,16 @@ export {
4538
4799
  normalizeChildProcessEnv,
4539
4800
  isCliExitCommand,
4540
4801
  injectMonitoringHubBootstrap,
4802
+ formatWorkerExecutionReadinessLines,
4541
4803
  formatTimestampedCliLine,
4542
4804
  formatSessionEventLine,
4805
+ formatRuntimeStartupTimingSummary,
4543
4806
  extractRemoteBuddySessionConsumerHealth,
4544
4807
  extractRemoteBuddyAutonomousEngineState,
4545
4808
  ensureWorkerpalDockerImageReady,
4546
4809
  ensureRuntimeBinaries,
4547
4810
  downloadRuntimeAssetsFromSourceTag,
4811
+ describeWorkerExecutionReadiness,
4548
4812
  createSessionEventReplayFilter,
4549
4813
  copyTrackedRepoPath,
4550
4814
  cleanupLingeringWorkerpalWarmContainers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushpalsdev/cli",
3
- "version": "1.0.30",
3
+ "version": "1.0.31",
4
4
  "description": "PushPals terminal CLI for LocalBuddy -> RemoteBuddy orchestration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,6 +44,7 @@
44
44
  "format:check": "prettier --check .",
45
45
  "test:prompt-policy": "bun test tests/prompt-policy.enforcement.test.ts",
46
46
  "test:cli:integration": "bun test tests/cli.invocation-logging.test.ts tests/cli.runtime-bootstrap.test.ts tests/client.runtime-bootstrap.test.ts tests/shared.client-preflight.test.ts",
47
+ "test:cli:e2e": "bun test ./tests/integration/cli.e2e.ts",
47
48
  "test:root": "bun test tests",
48
49
  "test:protocol": "bun run tests/protocol.integration.ts",
49
50
  "test:integration": "python -u tests/integration/integration_controller.py --mode integration",