@pushpalsdev/cli 1.0.4 → 1.0.5

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 +135 -19
  2. package/package.json +1 -1
@@ -2,7 +2,7 @@
2
2
  // @bun
3
3
 
4
4
  // ../../scripts/pushpals-cli.ts
5
- import { chmodSync, cpSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
5
+ import { appendFileSync, chmodSync, cpSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
6
6
  import { dirname, join as join2, resolve as resolve2 } from "path";
7
7
  import { createInterface } from "readline";
8
8
 
@@ -747,6 +747,7 @@ var LOCALBUDDY_TIMEOUT_MS = 4000;
747
747
  var SSE_RECONNECT_MS = 1500;
748
748
  var DEFAULT_RUNTIME_BOOT_TIMEOUT_MS = 90000;
749
749
  var DEFAULT_RUNTIME_BOOT_POLL_MS = 1000;
750
+ var DEFAULT_SERVER_BOOT_TIMEOUT_MS = 20000;
750
751
  var GITHUB_OWNER = "PushPalsDev";
751
752
  var GITHUB_REPO = "pushpals";
752
753
  var GITHUB_API_URL = `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}`;
@@ -756,6 +757,16 @@ var GITHUB_HEADERS = {
756
757
  "User-Agent": "pushpals-cli"
757
758
  };
758
759
  var stateVersion = 1;
760
+ function logCliInvocation(argv) {
761
+ const startedAt = new Date().toISOString();
762
+ const cliVersion = String(process.env.PUSHPALS_CLI_PACKAGE_VERSION ?? "").trim() || "unknown";
763
+ const argsText = argv.length > 0 ? argv.join(" ") : "(none)";
764
+ console.log(`[pushpals] invocation=${startedAt}`);
765
+ console.log(`[pushpals] version=${cliVersion} runtime=bun@${Bun.version}`);
766
+ console.log(`[pushpals] platform=${process.platform}/${process.arch}`);
767
+ console.log(`[pushpals] cwd=${process.cwd()}`);
768
+ console.log(`[pushpals] args=${argsText}`);
769
+ }
759
770
  function printUsage() {
760
771
  console.log("PushPals CLI");
761
772
  console.log("");
@@ -909,9 +920,16 @@ async function resolveRuntimeReleaseTag(explicitTag) {
909
920
  if (fromEnv)
910
921
  return fromEnv;
911
922
  const packageVersion = parseSemverFromPackageVersion(process.env.PUSHPALS_CLI_PACKAGE_VERSION);
912
- if (packageVersion)
913
- return `v${packageVersion}`;
914
- return await fetchLatestReleaseTag();
923
+ try {
924
+ return await fetchLatestReleaseTag();
925
+ } catch (err) {
926
+ if (packageVersion) {
927
+ const fallbackTag = `v${packageVersion}`;
928
+ console.warn(`[pushpals] Could not resolve latest runtime tag; falling back to package version tag ${fallbackTag}: ${String(err)}`);
929
+ return fallbackTag;
930
+ }
931
+ throw err;
932
+ }
915
933
  }
916
934
  function writeTextFileIfMissing(pathValue, text) {
917
935
  if (existsSync2(pathValue))
@@ -940,7 +958,7 @@ async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
940
958
  throw new Error(`Failed to fetch runtime source tree for ${tag} (HTTP ${treeResponse.status})`);
941
959
  }
942
960
  const treePayload = await treeResponse.json();
943
- const paths = (treePayload.tree ?? []).filter((entry) => entry.type === "blob" && typeof entry.path === "string").map((entry) => String(entry.path)).filter((pathValue) => pathValue === ".env.example" || pathValue.startsWith("configs/") || pathValue.startsWith("prompts/"));
961
+ const paths = (treePayload.tree ?? []).filter((entry) => entry.type === "blob" && typeof entry.path === "string").map((entry) => String(entry.path)).filter((pathValue) => pathValue === ".env.example" || pathValue.startsWith("configs/") || pathValue.startsWith("prompts/") || pathValue.startsWith("packages/protocol/src/schemas/"));
944
962
  if (paths.length === 0) {
945
963
  throw new Error(`Runtime source tree for ${tag} did not include prompts/config assets`);
946
964
  }
@@ -948,7 +966,7 @@ async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
948
966
  for (const pathValue of sorted) {
949
967
  const rawUrl = `https://raw.githubusercontent.com/${GITHUB_OWNER}/${GITHUB_REPO}/${encodeURIComponent(tag)}/${pathValue}`;
950
968
  const body = await fetchTextFromUrl(rawUrl, 20000);
951
- const outPath = join2(runtimeRoot, pathValue);
969
+ const outPath = pathValue.startsWith("packages/protocol/src/schemas/") ? join2(runtimeRoot, "protocol", "schemas", pathValue.slice("packages/protocol/src/schemas/".length)) : join2(runtimeRoot, pathValue);
952
970
  mkdirSync(dirname(outPath), { recursive: true });
953
971
  writeFileSync(outPath, body, "utf8");
954
972
  }
@@ -956,10 +974,14 @@ async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
956
974
  async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
957
975
  const markerPath = join2(runtimeRoot, ".runtime-assets-tag");
958
976
  const currentTag = existsSync2(markerPath) ? readFileSync2(markerPath, "utf8").trim() : "";
959
- const hasAssets = existsSync2(join2(runtimeRoot, ".env.example")) && existsSync2(join2(runtimeRoot, "configs", "default.toml")) && existsSync2(join2(runtimeRoot, "prompts"));
977
+ const protocolSchemasDir = join2(runtimeRoot, "protocol", "schemas");
978
+ const hasProtocolSchemas = existsSync2(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync2(join2(protocolSchemasDir, "events.schema.json"));
979
+ const hasAssets = existsSync2(join2(runtimeRoot, ".env.example")) && existsSync2(join2(runtimeRoot, "configs", "default.toml")) && existsSync2(join2(runtimeRoot, "prompts")) && hasProtocolSchemas;
960
980
  if (!hasAssets || currentTag !== runtimeTag) {
961
- const copied = copyBundledRuntimeAssets(runtimeRoot);
962
- if (!copied) {
981
+ copyBundledRuntimeAssets(runtimeRoot);
982
+ const hasProtocolSchemasAfterCopy = existsSync2(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync2(join2(protocolSchemasDir, "events.schema.json"));
983
+ const hasAssetsAfterCopy = existsSync2(join2(runtimeRoot, ".env.example")) && existsSync2(join2(runtimeRoot, "configs", "default.toml")) && existsSync2(join2(runtimeRoot, "prompts")) && hasProtocolSchemasAfterCopy;
984
+ if (!hasAssetsAfterCopy) {
963
985
  await downloadRuntimeAssetsFromSourceTag(runtimeRoot, runtimeTag);
964
986
  }
965
987
  writeFileSync(markerPath, `${runtimeTag}
@@ -980,6 +1002,19 @@ function runtimeBinaryFilename(serviceName, platformKey) {
980
1002
  const extension = platformKey.startsWith("windows-") ? ".exe" : "";
981
1003
  return `pushpals-runtime-${serviceToken}-${platformKey}${extension}`;
982
1004
  }
1005
+ function timestampFileToken() {
1006
+ return new Date().toISOString().replace(/[:.]/g, "-");
1007
+ }
1008
+ function readLogTail(logPath, maxLines = 40) {
1009
+ if (!existsSync2(logPath))
1010
+ return "";
1011
+ const raw = readFileSync2(logPath, "utf8");
1012
+ const lines = raw.split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.length > 0);
1013
+ if (lines.length === 0)
1014
+ return "";
1015
+ return lines.slice(-maxLines).join(`
1016
+ `);
1017
+ }
983
1018
  async function downloadBinaryAsset(tag, assetName, outPath) {
984
1019
  const url = `${GITHUB_RELEASE_URL}/${encodeURIComponent(tag)}/${assetName}`;
985
1020
  const response = await fetchWithTimeout(url, { headers: GITHUB_HEADERS }, 60000);
@@ -1021,16 +1056,50 @@ async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
1021
1056
  }
1022
1057
  return runtimeBinaries;
1023
1058
  }
1024
- function spawnRuntimeService(name, command, cwd, env) {
1059
+ function spawnRuntimeService(name, command, cwd, env, logPath) {
1060
+ writeFileSync(logPath, `[pushpals] service=${name} command=${command.join(" ")} cwd=${cwd}
1061
+ `, "utf8");
1025
1062
  const proc = Bun.spawn(command, {
1026
1063
  cwd,
1027
1064
  env,
1028
- stdout: "ignore",
1029
- stderr: "ignore"
1065
+ stdout: "pipe",
1066
+ stderr: "pipe"
1030
1067
  });
1068
+ const pipeToLog = async (stream, channel) => {
1069
+ if (!stream)
1070
+ return;
1071
+ const reader = stream.getReader();
1072
+ const decoder = new TextDecoder;
1073
+ let pending = "";
1074
+ while (true) {
1075
+ const { done, value } = await reader.read();
1076
+ if (done)
1077
+ break;
1078
+ const chunk = decoder.decode(value, { stream: true });
1079
+ if (!chunk)
1080
+ continue;
1081
+ pending += chunk;
1082
+ const lines = pending.split(/\r?\n/);
1083
+ pending = lines.pop() ?? "";
1084
+ for (const line of lines) {
1085
+ appendFileSync(logPath, `[${channel}] ${line}
1086
+ `, "utf8");
1087
+ }
1088
+ }
1089
+ const rest = decoder.decode();
1090
+ if (rest)
1091
+ pending += rest;
1092
+ if (pending.trim().length > 0) {
1093
+ appendFileSync(logPath, `[${channel}] ${pending.trimEnd()}
1094
+ `, "utf8");
1095
+ }
1096
+ };
1097
+ pipeToLog(proc.stdout, "stdout");
1098
+ pipeToLog(proc.stderr, "stderr");
1031
1099
  const service = {
1032
1100
  name,
1033
1101
  proc,
1102
+ logPath,
1034
1103
  exited: false,
1035
1104
  exitCode: null
1036
1105
  };
@@ -1105,24 +1174,61 @@ async function autoStartRuntimeServices(opts) {
1105
1174
  PUSHPALS_REPO_ROOT_OVERRIDE: opts.repoRoot,
1106
1175
  PUSHPALS_PROJECT_ROOT_OVERRIDE: opts.repoRoot,
1107
1176
  PUSHPALS_CONFIG_DIR_OVERRIDE: join2(runtimeRoot, "configs"),
1108
- PUSHPALS_PROMPTS_ROOT_OVERRIDE: runtimeRoot
1177
+ PUSHPALS_PROMPTS_ROOT_OVERRIDE: runtimeRoot,
1178
+ PUSHPALS_PROTOCOL_SCHEMAS_DIR: join2(runtimeRoot, "protocol", "schemas")
1109
1179
  };
1110
1180
  const services = [];
1181
+ const runToken = timestampFileToken();
1182
+ const logDir = join2(runtimeRoot, "logs", "bootstrap");
1183
+ mkdirSync(logDir, { recursive: true });
1184
+ const logPathFor = (name) => join2(logDir, `${runToken}-${name}.log`);
1111
1185
  const serverHealthy = await probeServer(opts.serverUrl);
1112
1186
  if (!serverHealthy) {
1113
1187
  console.log("[pushpals] Starting embedded server...");
1114
- services.push(spawnRuntimeService("server", [runtimeBinaries.server], opts.repoRoot, runtimeEnv));
1188
+ const serverService = spawnRuntimeService("server", [runtimeBinaries.server], opts.repoRoot, runtimeEnv, logPathFor("server"));
1189
+ services.push(serverService);
1190
+ console.log(`[pushpals] server log: ${serverService.logPath}`);
1191
+ const serverDeadline = Date.now() + DEFAULT_SERVER_BOOT_TIMEOUT_MS;
1192
+ let serverIsReady = false;
1193
+ while (Date.now() < serverDeadline) {
1194
+ if (serverService.exited) {
1195
+ const tail = readLogTail(serverService.logPath);
1196
+ stopRuntimeServices(services);
1197
+ throw new Error(`Embedded server exited during bootstrap (code=${serverService.exitCode ?? "unknown"}). ` + `See ${serverService.logPath}${tail ? `
1198
+ --- server log tail ---
1199
+ ${tail}` : ""}`);
1200
+ }
1201
+ if (await probeServer(opts.serverUrl)) {
1202
+ serverIsReady = true;
1203
+ break;
1204
+ }
1205
+ await Bun.sleep(DEFAULT_RUNTIME_BOOT_POLL_MS);
1206
+ }
1207
+ if (!serverIsReady) {
1208
+ const tail = readLogTail(serverService.logPath);
1209
+ stopRuntimeServices(services);
1210
+ throw new Error(`Embedded server did not become healthy within ${DEFAULT_SERVER_BOOT_TIMEOUT_MS}ms. ` + `See ${serverService.logPath}${tail ? `
1211
+ --- server log tail ---
1212
+ ${tail}` : ""}`);
1213
+ }
1214
+ console.log("[pushpals] Embedded server is healthy.");
1115
1215
  } else {
1116
1216
  console.log("[pushpals] Server already healthy; skipping embedded server start.");
1117
1217
  }
1118
1218
  console.log("[pushpals] Starting embedded LocalBuddy...");
1119
- services.push(spawnRuntimeService("localbuddy", [runtimeBinaries.localbuddy], opts.repoRoot, runtimeEnv));
1219
+ const localbuddyService = spawnRuntimeService("localbuddy", [runtimeBinaries.localbuddy], opts.repoRoot, runtimeEnv, logPathFor("localbuddy"));
1220
+ services.push(localbuddyService);
1221
+ console.log(`[pushpals] localbuddy log: ${localbuddyService.logPath}`);
1120
1222
  console.log("[pushpals] Starting embedded RemoteBuddy...");
1121
- services.push(spawnRuntimeService("remotebuddy", [runtimeBinaries.remotebuddy], opts.repoRoot, runtimeEnv));
1223
+ const remotebuddyService = spawnRuntimeService("remotebuddy", [runtimeBinaries.remotebuddy], opts.repoRoot, runtimeEnv, logPathFor("remotebuddy"));
1224
+ services.push(remotebuddyService);
1225
+ console.log(`[pushpals] remotebuddy log: ${remotebuddyService.logPath}`);
1122
1226
  const scmHealthy = await probeSourceControlManager(opts.sourceControlManagerPort);
1123
1227
  if (!scmHealthy) {
1124
1228
  console.log("[pushpals] Starting embedded SourceControlManager...");
1125
- services.push(spawnRuntimeService("source_control_manager", [runtimeBinaries.sourceControlManager, "--skip-clean-check"], opts.repoRoot, runtimeEnv));
1229
+ const sourceControlManagerService = spawnRuntimeService("source_control_manager", [runtimeBinaries.sourceControlManager, "--skip-clean-check"], opts.repoRoot, runtimeEnv, logPathFor("source_control_manager"));
1230
+ services.push(sourceControlManagerService);
1231
+ console.log(`[pushpals] source_control_manager log: ${sourceControlManagerService.logPath}`);
1126
1232
  } else {
1127
1233
  console.log("[pushpals] SourceControlManager already healthy; skipping embedded start.");
1128
1234
  }
@@ -1133,11 +1239,19 @@ async function autoStartRuntimeServices(opts) {
1133
1239
  if (service.exited) {
1134
1240
  if (service.name === "source_control_manager") {
1135
1241
  console.warn(`[pushpals] Embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}); continuing without SCM.`);
1242
+ const tail2 = readLogTail(service.logPath);
1243
+ if (tail2) {
1244
+ console.warn(`[pushpals] ${service.name} log tail:
1245
+ ${tail2}`);
1246
+ }
1136
1247
  services.splice(i, 1);
1137
1248
  continue;
1138
1249
  }
1250
+ const tail = readLogTail(service.logPath);
1139
1251
  stopRuntimeServices(services);
1140
- throw new Error(`Embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"})`);
1252
+ throw new Error(`Embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}). ` + `See ${service.logPath}${tail ? `
1253
+ --- ${service.name} log tail ---
1254
+ ${tail}` : ""}`);
1141
1255
  }
1142
1256
  }
1143
1257
  const health = await probeLocalBuddy(opts.localAgentUrl, opts.authToken);
@@ -1395,7 +1509,9 @@ async function openMonitoringHub(url) {
1395
1509
  return code === 0;
1396
1510
  }
1397
1511
  async function main() {
1398
- const parsed = parseArgs(process.argv.slice(2));
1512
+ const argv = process.argv.slice(2);
1513
+ logCliInvocation(argv);
1514
+ const parsed = parseArgs(argv);
1399
1515
  if (!parsed)
1400
1516
  return;
1401
1517
  const config = loadPushPalsConfig();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushpalsdev/cli",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "PushPals terminal CLI for LocalBuddy -> RemoteBuddy orchestration",
5
5
  "license": "MIT",
6
6
  "repository": {