@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.
- package/dist/pushpals-cli.js +135 -19
- package/package.json +1 -1
package/dist/pushpals-cli.js
CHANGED
|
@@ -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
|
-
|
|
913
|
-
return
|
|
914
|
-
|
|
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
|
|
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
|
-
|
|
962
|
-
|
|
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: "
|
|
1029
|
-
stderr: "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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();
|