@pushpalsdev/cli 1.0.8 → 1.0.10
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 +525 -122
- package/monitor-ui/+not-found.html +440 -0
- package/monitor-ui/_expo/.routes.json +3 -0
- package/monitor-ui/_expo/static/js/web/entry-1d5a07c47473852fe9754bfe3e6da301.js +1219 -0
- package/monitor-ui/_expo/static/js/web/index-5650b6cee60075cb63c1efc40dcf7683.js +6 -0
- package/monitor-ui/_sitemap.html +440 -0
- package/monitor-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
- package/monitor-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
- package/monitor-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
- package/monitor-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
- package/monitor-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
- package/monitor-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
- package/monitor-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
- package/monitor-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
- package/monitor-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
- package/monitor-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
- package/monitor-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
- package/monitor-ui/assets/__node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
- package/monitor-ui/assets/__node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
- package/monitor-ui/assets/__node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
- package/monitor-ui/assets/__node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
- package/monitor-ui/assets/__node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
- package/monitor-ui/assets/__node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
- package/monitor-ui/assets/__node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
- package/monitor-ui/favicon.ico +0 -0
- package/monitor-ui/index.html +440 -0
- package/monitor-ui/modal.html +440 -0
- package/package.json +3 -2
- package/runtime/.env.example +4 -4
- package/runtime/configs/default.toml +5 -4
- package/runtime/configs/local.example.toml +6 -1
package/dist/pushpals-cli.js
CHANGED
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// ../../scripts/pushpals-cli.ts
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
appendFileSync,
|
|
7
|
+
chmodSync,
|
|
8
|
+
cpSync,
|
|
9
|
+
existsSync as existsSync4,
|
|
10
|
+
mkdirSync,
|
|
11
|
+
readFileSync as readFileSync4,
|
|
12
|
+
writeFileSync
|
|
13
|
+
} from "fs";
|
|
14
|
+
import { dirname, extname, join as join2, resolve as resolve4 } from "path";
|
|
7
15
|
import { createInterface } from "readline";
|
|
8
16
|
|
|
9
17
|
// ../shared/src/client_preflight.ts
|
|
@@ -13,12 +21,53 @@ import { relative, resolve as resolve2 } from "path";
|
|
|
13
21
|
// ../shared/src/config.ts
|
|
14
22
|
import { existsSync, readFileSync } from "fs";
|
|
15
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
|
|
16
64
|
var PROJECT_ROOT = resolve(import.meta.dir, "..", "..", "..");
|
|
17
65
|
var DEFAULT_CONFIG_DIR = "configs";
|
|
18
66
|
var LEGACY_CONFIG_DIR = "config";
|
|
19
67
|
var TRUTHY = new Set(["1", "true", "yes", "on"]);
|
|
20
68
|
var FALSY = new Set(["0", "false", "no", "off"]);
|
|
21
69
|
var DEFAULT_WORKERPALS_QUALITY_CRITIC_MIN_SCORE = 8;
|
|
70
|
+
var DEFAULT_WORKERPALS_QUALITY_MAX_AUTO_REVISIONS = 1;
|
|
22
71
|
var DEFAULT_WORKERPALS_FILE_MODIFYING_JOBS = ["task.execute"];
|
|
23
72
|
var DEFAULT_WORKERPALS_OUTPUT_MAX_CHARS = 192 * 1024;
|
|
24
73
|
var DEFAULT_WORKERPALS_OUTPUT_MAX_LINES = 600;
|
|
@@ -286,13 +335,14 @@ function loadPushPalsConfig(options = {}) {
|
|
|
286
335
|
const remotebuddyDbPath = resolvePathFromRoot(projectRoot, firstNonEmpty(process.env.REMOTEBUDDY_DB_PATH, asString(pathsNode.remotebuddy_db_path, join(dataDir, "remotebuddy-state.db"))));
|
|
287
336
|
const serverNode = getObject(merged, "server");
|
|
288
337
|
const serverPort = Math.max(1, asInt(parseIntEnv("PUSHPALS_PORT") ?? serverNode.port, 3001));
|
|
289
|
-
const serverUrl = firstNonEmpty(process.env.PUSHPALS_SERVER_URL, asString(serverNode.url, `http://
|
|
290
|
-
const serverHost = asString(serverNode.host, "
|
|
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")));
|
|
291
340
|
const debugHttp = parseBoolEnv("PUSHPALS_DEBUG_HTTP") ?? asBoolean(serverNode.debug_http, false);
|
|
292
341
|
const staleClaimTtlMs = Math.max(5000, asInt(parseIntEnv("PUSHPALS_STALE_CLAIM_TTL_MS") ?? serverNode.stale_claim_ttl_ms, 120000));
|
|
293
342
|
const staleClaimSweepIntervalMs = Math.max(1000, asInt(parseIntEnv("PUSHPALS_STALE_CLAIM_SWEEP_INTERVAL_MS") ?? serverNode.stale_claim_sweep_interval_ms, 5000));
|
|
294
343
|
const globalStatusHeartbeatMs = parseIntEnv("PUSHPALS_STATUS_HEARTBEAT_MS");
|
|
295
344
|
const localNode = getObject(merged, "localbuddy");
|
|
345
|
+
const localEnabled = parseBoolEnv("LOCALBUDDY_ENABLED") ?? asBoolean(localNode.enabled, false);
|
|
296
346
|
const localPort = Math.max(1, asInt(parseIntEnv("LOCAL_AGENT_PORT") ?? localNode.port, 3003));
|
|
297
347
|
const localStatusHeartbeatMs = Math.max(0, asInt(parseIntEnv("LOCALBUDDY_STATUS_HEARTBEAT_MS") ?? globalStatusHeartbeatMs ?? localNode.status_heartbeat_ms, 120000));
|
|
298
348
|
const localLlm = resolveLlmConfig(localNode, "LOCALBUDDY", {
|
|
@@ -369,7 +419,7 @@ function loadPushPalsConfig(options = {}) {
|
|
|
369
419
|
const workerMinisweTimeoutMs = Math.max(1e4, asInt(parseIntEnv("WORKERPALS_MINISWE_TIMEOUT_MS") ?? workerNode.miniswe_timeout_ms, 1800000));
|
|
370
420
|
const workerOpenAICodexPython = firstNonEmpty(process.env.PUSHPALS_OPENAI_CODEX_PYTHON, asString(workerNode.openai_codex_python, "python"), "python");
|
|
371
421
|
const workerOpenAICodexTimeoutMs = Math.max(1e4, asInt(workerNode.openai_codex_timeout_ms, 7200000));
|
|
372
|
-
const workerQualityMaxAutoRevisions = Math.max(0, Math.min(10, asInt(parseIntEnv("WORKERPALS_QUALITY_MAX_AUTO_REVISIONS") ?? workerNode.quality_max_auto_revisions,
|
|
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)));
|
|
373
423
|
const workerFileModifyingJobs = (() => {
|
|
374
424
|
const envRaw = firstNonEmpty(process.env.WORKERPALS_FILE_MODIFYING_JOBS);
|
|
375
425
|
const parsed = envRaw ? envRaw.split(",").map((entry) => entry.trim()).filter(Boolean) : asStringArray(workerNode.file_modifying_jobs);
|
|
@@ -531,6 +581,7 @@ function loadPushPalsConfig(options = {}) {
|
|
|
531
581
|
staleClaimSweepIntervalMs
|
|
532
582
|
},
|
|
533
583
|
localbuddy: {
|
|
584
|
+
enabled: localEnabled,
|
|
534
585
|
port: localPort,
|
|
535
586
|
statusHeartbeatMs: localStatusHeartbeatMs,
|
|
536
587
|
llm: localLlm
|
|
@@ -734,7 +785,7 @@ function loadPushPalsConfig(options = {}) {
|
|
|
734
785
|
portConflictPolicy: startupPortConflictPolicy
|
|
735
786
|
},
|
|
736
787
|
client: {
|
|
737
|
-
localAgentUrl: firstNonEmpty(process.env.EXPO_PUBLIC_LOCAL_AGENT_URL, asString(clientNode.local_agent_url, `http://
|
|
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),
|
|
738
789
|
traceTailLines: Math.max(10, asInt(parseIntEnv("EXPO_PUBLIC_PUSHPALS_TRACE_TAIL_LINES") ?? clientNode.trace_tail_lines, 100))
|
|
739
790
|
}
|
|
740
791
|
};
|
|
@@ -1053,6 +1104,46 @@ function formatClientRuntimePreflightLines(result, prefix) {
|
|
|
1053
1104
|
return lines;
|
|
1054
1105
|
}
|
|
1055
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
|
+
|
|
1056
1147
|
// ../../scripts/pushpals-cli.ts
|
|
1057
1148
|
var DEFAULT_MONITOR_PORT = 8081;
|
|
1058
1149
|
var MONITOR_SCAN_PORTS = 32;
|
|
@@ -1073,6 +1164,29 @@ var GITHUB_HEADERS = {
|
|
|
1073
1164
|
"User-Agent": "pushpals-cli"
|
|
1074
1165
|
};
|
|
1075
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();
|
|
1076
1190
|
function logCliInvocation(argv) {
|
|
1077
1191
|
const startedAt = new Date().toISOString();
|
|
1078
1192
|
const cliVersion = String(process.env.PUSHPALS_CLI_PACKAGE_VERSION ?? "").trim() || "unknown";
|
|
@@ -1098,6 +1212,7 @@ function printUsage() {
|
|
|
1098
1212
|
console.log(" --runtime-tag <tag> Override runtime release tag (e.g. v1.0.2)");
|
|
1099
1213
|
console.log(" --no-auto-start Disable runtime auto-start when LocalBuddy is down");
|
|
1100
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");
|
|
1101
1216
|
console.log(" -h, --help Show this help");
|
|
1102
1217
|
console.log("");
|
|
1103
1218
|
console.log("Chat commands:");
|
|
@@ -1112,7 +1227,7 @@ function printUsage() {
|
|
|
1112
1227
|
console.log(" - LocalBuddy must be attached to the same repo root.");
|
|
1113
1228
|
}
|
|
1114
1229
|
function parseArgs(argv) {
|
|
1115
|
-
const options = { noAutoStart: false, noStream: false };
|
|
1230
|
+
const options = { noAutoStart: false, noStream: false, runtimeOnly: false };
|
|
1116
1231
|
for (let i = 0;i < argv.length; i++) {
|
|
1117
1232
|
const arg = argv[i];
|
|
1118
1233
|
if (arg === "-h" || arg === "--help") {
|
|
@@ -1127,6 +1242,10 @@ function parseArgs(argv) {
|
|
|
1127
1242
|
options.noAutoStart = true;
|
|
1128
1243
|
continue;
|
|
1129
1244
|
}
|
|
1245
|
+
if (arg === "--runtime-only") {
|
|
1246
|
+
options.runtimeOnly = true;
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1130
1249
|
if (arg === "--server-url") {
|
|
1131
1250
|
options.serverUrl = argv[++i];
|
|
1132
1251
|
continue;
|
|
@@ -1162,6 +1281,30 @@ function normalizeUrl(value, fallback = "") {
|
|
|
1162
1281
|
const selected = text || fallback;
|
|
1163
1282
|
return selected.replace(/\/+$/, "");
|
|
1164
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
|
+
}
|
|
1165
1308
|
function parsePositiveInt(value, fallback) {
|
|
1166
1309
|
const parsed = Number.parseInt(String(value ?? "").trim(), 10);
|
|
1167
1310
|
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
@@ -1169,7 +1312,7 @@ function parsePositiveInt(value, fallback) {
|
|
|
1169
1312
|
return parsed;
|
|
1170
1313
|
}
|
|
1171
1314
|
function normalizePath(value) {
|
|
1172
|
-
const normalized =
|
|
1315
|
+
const normalized = resolve4(value).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1173
1316
|
if (process.platform === "win32")
|
|
1174
1317
|
return normalized.toLowerCase();
|
|
1175
1318
|
return normalized;
|
|
@@ -1197,11 +1340,11 @@ async function resolveCurrentGitRepoRoot(cwd) {
|
|
|
1197
1340
|
const root = await runGit(["rev-parse", "--show-toplevel"], cwd);
|
|
1198
1341
|
if (!root.ok || !root.stdout)
|
|
1199
1342
|
return null;
|
|
1200
|
-
return
|
|
1343
|
+
return resolve4(root.stdout);
|
|
1201
1344
|
}
|
|
1202
1345
|
function resolveDefaultRuntimeRoot() {
|
|
1203
1346
|
const home = process.env.USERPROFILE || process.env.HOME || process.cwd();
|
|
1204
|
-
return
|
|
1347
|
+
return resolve4(home, ".pushpals", "runtime");
|
|
1205
1348
|
}
|
|
1206
1349
|
function buildRuntimeAssetSource(root, protocolSchemasDir) {
|
|
1207
1350
|
return {
|
|
@@ -1214,13 +1357,13 @@ function buildRuntimeAssetSource(root, protocolSchemasDir) {
|
|
|
1214
1357
|
};
|
|
1215
1358
|
}
|
|
1216
1359
|
function isCompleteRuntimeAssetSource(source) {
|
|
1217
|
-
return
|
|
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"));
|
|
1218
1361
|
}
|
|
1219
1362
|
function resolveBundledRuntimeAssetSource() {
|
|
1220
1363
|
const candidates = [
|
|
1221
|
-
buildRuntimeAssetSource(
|
|
1222
|
-
buildRuntimeAssetSource(
|
|
1223
|
-
buildRuntimeAssetSource(
|
|
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"))
|
|
1224
1367
|
];
|
|
1225
1368
|
for (const candidate of candidates) {
|
|
1226
1369
|
if (isCompleteRuntimeAssetSource(candidate))
|
|
@@ -1228,8 +1371,58 @@ function resolveBundledRuntimeAssetSource() {
|
|
|
1228
1371
|
}
|
|
1229
1372
|
return null;
|
|
1230
1373
|
}
|
|
1374
|
+
function looksLikeMonitoringHubBuild(root) {
|
|
1375
|
+
return existsSync4(join2(root, "index.html")) && existsSync4(join2(root, "_expo"));
|
|
1376
|
+
}
|
|
1377
|
+
function resolveBundledMonitoringHubRoot() {
|
|
1378
|
+
const candidates = [
|
|
1379
|
+
resolve4(import.meta.dir, "..", "monitor-ui"),
|
|
1380
|
+
resolve4(import.meta.dir, "..", "packages", "cli", "monitor-ui")
|
|
1381
|
+
];
|
|
1382
|
+
for (const candidate of candidates) {
|
|
1383
|
+
if (looksLikeMonitoringHubBuild(candidate))
|
|
1384
|
+
return candidate;
|
|
1385
|
+
}
|
|
1386
|
+
return null;
|
|
1387
|
+
}
|
|
1388
|
+
function resolveCliSourceCheckoutRoot() {
|
|
1389
|
+
const candidates = [
|
|
1390
|
+
resolve4(import.meta.dir, ".."),
|
|
1391
|
+
resolve4(import.meta.dir, "..", ".."),
|
|
1392
|
+
resolve4(import.meta.dir, "..", "..", "..")
|
|
1393
|
+
];
|
|
1394
|
+
for (const candidate of candidates) {
|
|
1395
|
+
if (existsSync4(join2(candidate, "package.json")) && existsSync4(join2(candidate, "apps", "client", "app.json")) && existsSync4(join2(candidate, "scripts", "sync-cli-monitor-ui.ts"))) {
|
|
1396
|
+
return candidate;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return null;
|
|
1400
|
+
}
|
|
1401
|
+
function exportBundledMonitoringHubFromSourceCheckout(sourceRoot) {
|
|
1402
|
+
const exportScriptPath = join2(sourceRoot, "scripts", "sync-cli-monitor-ui.ts");
|
|
1403
|
+
console.log("[pushpals] Packaged monitor UI missing; exporting the shared client monitor...");
|
|
1404
|
+
const proc = Bun.spawnSync([process.execPath, exportScriptPath], {
|
|
1405
|
+
cwd: sourceRoot,
|
|
1406
|
+
stdout: "inherit",
|
|
1407
|
+
stderr: "inherit",
|
|
1408
|
+
env: process.env
|
|
1409
|
+
});
|
|
1410
|
+
if (proc.exitCode !== 0) {
|
|
1411
|
+
throw new Error(`Failed to export packaged monitor UI from source checkout (exit ${proc.exitCode || 1})`);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
async function ensureBundledMonitoringHubRoot() {
|
|
1415
|
+
const existingRoot = resolveBundledMonitoringHubRoot();
|
|
1416
|
+
if (existingRoot)
|
|
1417
|
+
return existingRoot;
|
|
1418
|
+
const sourceRoot = resolveCliSourceCheckoutRoot();
|
|
1419
|
+
if (!sourceRoot)
|
|
1420
|
+
return null;
|
|
1421
|
+
exportBundledMonitoringHubFromSourceCheckout(sourceRoot);
|
|
1422
|
+
return resolveBundledMonitoringHubRoot();
|
|
1423
|
+
}
|
|
1231
1424
|
function repoLooksLikePushPalsSourceCheckout(repoRoot) {
|
|
1232
|
-
return
|
|
1425
|
+
return existsSync4(join2(repoRoot, "configs", "default.toml")) || existsSync4(join2(repoRoot, "config", "default.toml"));
|
|
1233
1426
|
}
|
|
1234
1427
|
function parseSemverFromPackageVersion(value) {
|
|
1235
1428
|
const raw = String(value ?? "").trim();
|
|
@@ -1267,6 +1460,7 @@ async function resolveRuntimeReleaseTag(explicitTag) {
|
|
|
1267
1460
|
if (fromEnv)
|
|
1268
1461
|
return fromEnv;
|
|
1269
1462
|
const packageVersion = parseSemverFromPackageVersion(process.env.PUSHPALS_CLI_PACKAGE_VERSION);
|
|
1463
|
+
console.log("[pushpals] Resolving embedded runtime release tag from GitHub...");
|
|
1270
1464
|
try {
|
|
1271
1465
|
return await fetchLatestReleaseTag();
|
|
1272
1466
|
} catch (err) {
|
|
@@ -1279,7 +1473,7 @@ async function resolveRuntimeReleaseTag(explicitTag) {
|
|
|
1279
1473
|
}
|
|
1280
1474
|
}
|
|
1281
1475
|
function writeTextFileIfMissing(pathValue, text) {
|
|
1282
|
-
if (
|
|
1476
|
+
if (existsSync4(pathValue))
|
|
1283
1477
|
return;
|
|
1284
1478
|
mkdirSync(dirname(pathValue), { recursive: true });
|
|
1285
1479
|
writeFileSync(pathValue, text, "utf8");
|
|
@@ -1322,8 +1516,8 @@ function seedRuntimePreflightAssets(runtimeRoot) {
|
|
|
1322
1516
|
writeTextFileIfMissing(join2(runtimeRoot, ".env"), `# Local PushPals runtime environment
|
|
1323
1517
|
`);
|
|
1324
1518
|
const localExamplePath = join2(runtimeRoot, "configs", "local.example.toml");
|
|
1325
|
-
if (
|
|
1326
|
-
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"),
|
|
1519
|
+
if (existsSync4(localExamplePath)) {
|
|
1520
|
+
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), readFileSync4(localExamplePath, "utf8"));
|
|
1327
1521
|
} else {
|
|
1328
1522
|
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), `# Local PushPals runtime overrides
|
|
1329
1523
|
`);
|
|
@@ -1337,6 +1531,7 @@ async function fetchTextFromUrl(url, timeoutMs = 20000) {
|
|
|
1337
1531
|
return await response.text();
|
|
1338
1532
|
}
|
|
1339
1533
|
async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
|
|
1534
|
+
console.log(`[pushpals] Downloading embedded runtime assets from source tag ${tag}...`);
|
|
1340
1535
|
const treeUrl = `${GITHUB_API_URL}/git/trees/${encodeURIComponent(tag)}?recursive=1`;
|
|
1341
1536
|
const treeResponse = await fetchWithTimeout(treeUrl, { headers: GITHUB_HEADERS }, 30000);
|
|
1342
1537
|
if (!treeResponse.ok) {
|
|
@@ -1357,16 +1552,19 @@ async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
|
|
|
1357
1552
|
}
|
|
1358
1553
|
}
|
|
1359
1554
|
async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
|
|
1555
|
+
console.log(`[pushpals] Preparing embedded runtime assets for ${runtimeTag}...`);
|
|
1360
1556
|
const markerPath = join2(runtimeRoot, ".runtime-assets-tag");
|
|
1361
|
-
const currentTag =
|
|
1557
|
+
const currentTag = existsSync4(markerPath) ? readFileSync4(markerPath, "utf8").trim() : "";
|
|
1362
1558
|
const protocolSchemasDir = join2(runtimeRoot, "protocol", "schemas");
|
|
1363
|
-
const hasProtocolSchemas =
|
|
1364
|
-
const hasAssets =
|
|
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;
|
|
1365
1561
|
if (!hasAssets || currentTag !== runtimeTag) {
|
|
1562
|
+
console.log(`[pushpals] Embedded runtime assets ${hasAssets ? "are stale" : "are missing"}; refreshing bundle...`);
|
|
1366
1563
|
copyBundledRuntimeAssets(runtimeRoot);
|
|
1367
|
-
const hasProtocolSchemasAfterCopy =
|
|
1368
|
-
const hasAssetsAfterCopy =
|
|
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;
|
|
1369
1566
|
if (!hasAssetsAfterCopy) {
|
|
1567
|
+
console.log("[pushpals] Bundled runtime assets are incomplete; falling back to release source downloads...");
|
|
1370
1568
|
await downloadRuntimeAssetsFromSourceTag(runtimeRoot, runtimeTag);
|
|
1371
1569
|
}
|
|
1372
1570
|
writeFileSync(markerPath, `${runtimeTag}
|
|
@@ -1375,18 +1573,19 @@ async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
|
|
|
1375
1573
|
writeTextFileIfMissing(join2(runtimeRoot, ".env"), `# Local PushPals runtime environment
|
|
1376
1574
|
`);
|
|
1377
1575
|
const localExamplePath = join2(runtimeRoot, "configs", "local.example.toml");
|
|
1378
|
-
if (
|
|
1379
|
-
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"),
|
|
1576
|
+
if (existsSync4(localExamplePath)) {
|
|
1577
|
+
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), readFileSync4(localExamplePath, "utf8"));
|
|
1380
1578
|
} else {
|
|
1381
1579
|
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), `# Local PushPals runtime overrides
|
|
1382
1580
|
`);
|
|
1383
1581
|
}
|
|
1582
|
+
console.log("[pushpals] Embedded runtime assets are ready.");
|
|
1384
1583
|
}
|
|
1385
1584
|
function resolveDeferredRuntimeTagHint(explicitTag) {
|
|
1386
1585
|
return String(explicitTag || process.env.PUSHPALS_RUNTIME_TAG || "").trim();
|
|
1387
1586
|
}
|
|
1388
1587
|
async function prepareCliRuntime(opts) {
|
|
1389
|
-
const runtimeRoot =
|
|
1588
|
+
const runtimeRoot = resolve4(opts.runtimeRoot || process.env.PUSHPALS_RUNTIME_ROOT || resolveDefaultRuntimeRoot());
|
|
1390
1589
|
if (repoLooksLikePushPalsSourceCheckout(opts.repoRoot)) {
|
|
1391
1590
|
return {
|
|
1392
1591
|
runtimeRoot,
|
|
@@ -1479,10 +1678,7 @@ async function resolveCommandPath(command, cwd, env) {
|
|
|
1479
1678
|
stdout: "pipe",
|
|
1480
1679
|
stderr: "ignore"
|
|
1481
1680
|
});
|
|
1482
|
-
const [stdout, exitCode] = await Promise.all([
|
|
1483
|
-
new Response(proc.stdout).text(),
|
|
1484
|
-
proc.exited
|
|
1485
|
-
]);
|
|
1681
|
+
const [stdout, exitCode] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
1486
1682
|
if (exitCode !== 0)
|
|
1487
1683
|
continue;
|
|
1488
1684
|
const resolved = stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
|
|
@@ -1496,9 +1692,9 @@ function timestampFileToken() {
|
|
|
1496
1692
|
return new Date().toISOString().replace(/[:.]/g, "-");
|
|
1497
1693
|
}
|
|
1498
1694
|
function readLogTail(logPath, maxLines = 40) {
|
|
1499
|
-
if (!
|
|
1695
|
+
if (!existsSync4(logPath))
|
|
1500
1696
|
return "";
|
|
1501
|
-
const raw =
|
|
1697
|
+
const raw = readFileSync4(logPath, "utf8");
|
|
1502
1698
|
const lines = raw.split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.length > 0);
|
|
1503
1699
|
if (lines.length === 0)
|
|
1504
1700
|
return "";
|
|
@@ -1506,6 +1702,7 @@ function readLogTail(logPath, maxLines = 40) {
|
|
|
1506
1702
|
`);
|
|
1507
1703
|
}
|
|
1508
1704
|
async function downloadBinaryAsset(tag, assetName, outPath) {
|
|
1705
|
+
console.log(`[pushpals] Downloading embedded runtime binary ${assetName} from ${tag}...`);
|
|
1509
1706
|
const url = `${GITHUB_RELEASE_URL}/${encodeURIComponent(tag)}/${assetName}`;
|
|
1510
1707
|
const response = await fetchWithTimeout(url, { headers: GITHUB_HEADERS }, 60000);
|
|
1511
1708
|
if (!response.ok) {
|
|
@@ -1517,6 +1714,7 @@ async function downloadBinaryAsset(tag, assetName, outPath) {
|
|
|
1517
1714
|
}
|
|
1518
1715
|
async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
|
|
1519
1716
|
const platformKey = resolveRuntimePlatformKey();
|
|
1717
|
+
console.log(`[pushpals] Preparing embedded runtime binaries for ${runtimeTag} (${platformKey})...`);
|
|
1520
1718
|
const binDir = join2(runtimeRoot, "bin", `${runtimeTag}-${platformKey}`);
|
|
1521
1719
|
mkdirSync(binDir, { recursive: true });
|
|
1522
1720
|
const runtimeBinaries = {
|
|
@@ -1531,11 +1729,13 @@ async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
|
|
|
1531
1729
|
runtimeBinaries.remotebuddy,
|
|
1532
1730
|
runtimeBinaries.sourceControlManager
|
|
1533
1731
|
];
|
|
1732
|
+
let downloadedCount = 0;
|
|
1534
1733
|
for (const binaryPath of requiredAssets) {
|
|
1535
|
-
if (
|
|
1734
|
+
if (existsSync4(binaryPath))
|
|
1536
1735
|
continue;
|
|
1537
1736
|
const assetName = binaryPath.split(/[\\/]/).pop() || "";
|
|
1538
1737
|
await downloadBinaryAsset(runtimeTag, assetName, binaryPath);
|
|
1738
|
+
downloadedCount++;
|
|
1539
1739
|
}
|
|
1540
1740
|
if (process.platform !== "win32") {
|
|
1541
1741
|
for (const binaryPath of requiredAssets) {
|
|
@@ -1544,6 +1744,12 @@ async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
|
|
|
1544
1744
|
} catch {}
|
|
1545
1745
|
}
|
|
1546
1746
|
}
|
|
1747
|
+
if (downloadedCount === 0) {
|
|
1748
|
+
console.log("[pushpals] Embedded runtime binaries are already present.");
|
|
1749
|
+
} else {
|
|
1750
|
+
console.log(`[pushpals] Embedded runtime binaries downloaded: ${downloadedCount}.`);
|
|
1751
|
+
}
|
|
1752
|
+
console.log("[pushpals] Embedded runtime binaries are ready.");
|
|
1547
1753
|
return runtimeBinaries;
|
|
1548
1754
|
}
|
|
1549
1755
|
function spawnRuntimeService(name, command, cwd, env, logPath) {
|
|
@@ -1683,18 +1889,34 @@ async function fetchJsonWithTimeout(url, init = {}, timeoutMs = HTTP_TIMEOUT_MS)
|
|
|
1683
1889
|
return null;
|
|
1684
1890
|
}
|
|
1685
1891
|
}
|
|
1686
|
-
function
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1892
|
+
function buildClientTransportQuery(cursor, client) {
|
|
1893
|
+
const params = new URLSearchParams;
|
|
1894
|
+
if (cursor > 0)
|
|
1895
|
+
params.set("after", String(cursor));
|
|
1896
|
+
params.set("clientId", client.clientId);
|
|
1897
|
+
params.set("clientKind", client.kind);
|
|
1898
|
+
params.set("clientLabel", client.label);
|
|
1899
|
+
params.set("clientVersion", client.version);
|
|
1900
|
+
params.set("clientPlatform", client.platform);
|
|
1901
|
+
params.set("clientRepoRoot", client.repoRoot);
|
|
1902
|
+
const query = params.toString();
|
|
1903
|
+
return query ? `?${query}` : "";
|
|
1904
|
+
}
|
|
1905
|
+
function createRuntimeClientId(prefix) {
|
|
1906
|
+
if (typeof crypto?.randomUUID === "function") {
|
|
1907
|
+
return `${prefix}-${crypto.randomUUID()}`;
|
|
1908
|
+
}
|
|
1909
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
1690
1910
|
}
|
|
1691
|
-
async function probeLocalBuddy(localAgentUrl
|
|
1692
|
-
return await fetchJsonWithTimeout(`${localAgentUrl}/healthz`, {
|
|
1911
|
+
async function probeLocalBuddy(localAgentUrl) {
|
|
1912
|
+
return await fetchJsonWithTimeout(`${localAgentUrl}/healthz`, {}, LOCALBUDDY_TIMEOUT_MS);
|
|
1693
1913
|
}
|
|
1694
1914
|
async function autoStartRuntimeServices(opts) {
|
|
1695
1915
|
const { runtimePreflight } = opts.preparedRuntime;
|
|
1696
1916
|
const runtimeRoot = opts.preparedRuntime.runtimeRoot;
|
|
1697
1917
|
const runtimeTag = opts.preparedRuntime.runtimeTag || await resolveRuntimeReleaseTag(opts.requestedRuntimeTag);
|
|
1918
|
+
const localBuddyEnabled = Boolean(runtimePreflight.config.localbuddy.enabled);
|
|
1919
|
+
const requireLocalBuddy = opts.requireLocalBuddy ?? true;
|
|
1698
1920
|
console.log(`[pushpals] LocalBuddy unavailable. Auto-starting runtime for repo: ${opts.repoRoot}`);
|
|
1699
1921
|
console.log(`[pushpals] runtimeRoot=${runtimeRoot}`);
|
|
1700
1922
|
console.log(`[pushpals] runtimeTag=${runtimeTag}`);
|
|
@@ -1750,10 +1972,14 @@ ${tail}` : ""}`);
|
|
|
1750
1972
|
} else {
|
|
1751
1973
|
console.log("[pushpals] Server already healthy; skipping embedded server start.");
|
|
1752
1974
|
}
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1975
|
+
if (localBuddyEnabled) {
|
|
1976
|
+
console.log("[pushpals] Starting embedded LocalBuddy...");
|
|
1977
|
+
const localbuddyService = spawnRuntimeService("localbuddy", [runtimeBinaries.localbuddy], opts.repoRoot, runtimeEnv, logPathFor("localbuddy"));
|
|
1978
|
+
services.push(localbuddyService);
|
|
1979
|
+
console.log(`[pushpals] localbuddy log: ${localbuddyService.logPath}`);
|
|
1980
|
+
} else {
|
|
1981
|
+
console.log("[pushpals] Embedded LocalBuddy disabled by runtime config; skipping start.");
|
|
1982
|
+
}
|
|
1757
1983
|
console.log("[pushpals] Starting embedded RemoteBuddy...");
|
|
1758
1984
|
const remotebuddyService = spawnRuntimeService("remotebuddy", [runtimeBinaries.remotebuddy], opts.repoRoot, runtimeEnv, logPathFor("remotebuddy"));
|
|
1759
1985
|
services.push(remotebuddyService);
|
|
@@ -1803,8 +2029,8 @@ ${tail2}`);
|
|
|
1803
2029
|
${tail}` : ""}`);
|
|
1804
2030
|
}
|
|
1805
2031
|
}
|
|
1806
|
-
const health = await probeLocalBuddy(opts.localAgentUrl
|
|
1807
|
-
if (health?.ok) {
|
|
2032
|
+
const health = localBuddyEnabled ? await probeLocalBuddy(opts.localAgentUrl) : null;
|
|
2033
|
+
if (!requireLocalBuddy || localBuddyEnabled && health?.ok) {
|
|
1808
2034
|
const stabilityDeadline = Date.now() + DEFAULT_SERVICE_STABILITY_GRACE_MS;
|
|
1809
2035
|
while (Date.now() < stabilityDeadline) {
|
|
1810
2036
|
for (let i = services.length - 1;i >= 0; i--) {
|
|
@@ -1835,13 +2061,16 @@ ${tail}` : ""}`);
|
|
|
1835
2061
|
await Bun.sleep(DEFAULT_RUNTIME_BOOT_POLL_MS);
|
|
1836
2062
|
}
|
|
1837
2063
|
stopRuntimeServices(services);
|
|
2064
|
+
if (!localBuddyEnabled) {
|
|
2065
|
+
throw new Error(`Timed out waiting for embedded runtime readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
|
|
2066
|
+
}
|
|
1838
2067
|
throw new Error(`Timed out waiting for LocalBuddy at ${opts.localAgentUrl} after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
|
|
1839
2068
|
}
|
|
1840
2069
|
function readCliState(pathValue) {
|
|
1841
|
-
if (!
|
|
2070
|
+
if (!existsSync4(pathValue))
|
|
1842
2071
|
return {};
|
|
1843
2072
|
try {
|
|
1844
|
-
const raw =
|
|
2073
|
+
const raw = readFileSync4(pathValue, "utf8");
|
|
1845
2074
|
const parsed = JSON.parse(raw);
|
|
1846
2075
|
if (!parsed || typeof parsed !== "object")
|
|
1847
2076
|
return {};
|
|
@@ -1867,6 +2096,9 @@ function writeCliState(pathValue, state) {
|
|
|
1867
2096
|
writeFileSync(pathValue, `${JSON.stringify(payload, null, 2)}
|
|
1868
2097
|
`, "utf8");
|
|
1869
2098
|
}
|
|
2099
|
+
function resolveCliStatePath(repoRoot) {
|
|
2100
|
+
return resolveGitStateFilePath(repoRoot, "pushpals-cli-state.json");
|
|
2101
|
+
}
|
|
1870
2102
|
async function looksLikeMonitoringHub(url) {
|
|
1871
2103
|
try {
|
|
1872
2104
|
const response = await fetchWithTimeout(url, {}, 700);
|
|
@@ -1882,6 +2114,93 @@ async function looksLikeMonitoringHub(url) {
|
|
|
1882
2114
|
return false;
|
|
1883
2115
|
}
|
|
1884
2116
|
}
|
|
2117
|
+
function buildMonitoringHubRuntimeBootstrap(opts) {
|
|
2118
|
+
return {
|
|
2119
|
+
serverUrl: opts.serverUrl,
|
|
2120
|
+
localAgentUrl: opts.localAgentUrl,
|
|
2121
|
+
sessionId: opts.sessionId,
|
|
2122
|
+
clientId: `cli-monitor-${opts.sessionId}`,
|
|
2123
|
+
clientKind: "cli_monitor",
|
|
2124
|
+
clientLabel: "CLI Monitor"
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
function injectMonitoringHubBootstrap(html, bootstrap) {
|
|
2128
|
+
const payload = jsonHtmlBootstrap(bootstrap);
|
|
2129
|
+
const script = `<script>globalThis.__PUSHPALS_WEB_BOOTSTRAP__=${payload};</script>`;
|
|
2130
|
+
if (html.includes("</head>")) {
|
|
2131
|
+
return html.replace("</head>", `${script}</head>`);
|
|
2132
|
+
}
|
|
2133
|
+
return `${script}${html}`;
|
|
2134
|
+
}
|
|
2135
|
+
function monitoringHubContentType(pathValue) {
|
|
2136
|
+
switch (extname(pathValue).toLowerCase()) {
|
|
2137
|
+
case ".html":
|
|
2138
|
+
return "text/html; charset=utf-8";
|
|
2139
|
+
case ".js":
|
|
2140
|
+
return "application/javascript; charset=utf-8";
|
|
2141
|
+
case ".css":
|
|
2142
|
+
return "text/css; charset=utf-8";
|
|
2143
|
+
case ".json":
|
|
2144
|
+
return "application/json; charset=utf-8";
|
|
2145
|
+
case ".svg":
|
|
2146
|
+
return "image/svg+xml";
|
|
2147
|
+
case ".png":
|
|
2148
|
+
return "image/png";
|
|
2149
|
+
case ".jpg":
|
|
2150
|
+
case ".jpeg":
|
|
2151
|
+
return "image/jpeg";
|
|
2152
|
+
case ".ico":
|
|
2153
|
+
return "image/x-icon";
|
|
2154
|
+
case ".woff2":
|
|
2155
|
+
return "font/woff2";
|
|
2156
|
+
case ".ttf":
|
|
2157
|
+
return "font/ttf";
|
|
2158
|
+
case ".map":
|
|
2159
|
+
return "application/json; charset=utf-8";
|
|
2160
|
+
default:
|
|
2161
|
+
return "application/octet-stream";
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
function resolveMonitoringHubAssetPath(assetRoot, pathname) {
|
|
2165
|
+
const root = resolve4(assetRoot);
|
|
2166
|
+
const rootPrefix = `${root}${root.endsWith("\\") || root.endsWith("/") ? "" : process.platform === "win32" ? "\\" : "/"}`;
|
|
2167
|
+
const decodedPath = decodeURIComponent(pathname);
|
|
2168
|
+
const trimmedPath = decodedPath === "/" ? "/index.html" : decodedPath;
|
|
2169
|
+
const relativePath = trimmedPath.replace(/^\/+/, "");
|
|
2170
|
+
const candidatePath = resolve4(root, relativePath);
|
|
2171
|
+
if (candidatePath !== root && !candidatePath.startsWith(rootPrefix))
|
|
2172
|
+
return null;
|
|
2173
|
+
if (existsSync4(candidatePath))
|
|
2174
|
+
return candidatePath;
|
|
2175
|
+
if (!extname(relativePath)) {
|
|
2176
|
+
const nestedIndexPath = resolve4(root, relativePath, "index.html");
|
|
2177
|
+
if ((nestedIndexPath === root || nestedIndexPath.startsWith(rootPrefix)) && existsSync4(nestedIndexPath)) {
|
|
2178
|
+
return nestedIndexPath;
|
|
2179
|
+
}
|
|
2180
|
+
return join2(root, "index.html");
|
|
2181
|
+
}
|
|
2182
|
+
return null;
|
|
2183
|
+
}
|
|
2184
|
+
async function serveBundledMonitoringHub(assetRoot, pathname, bootstrap) {
|
|
2185
|
+
const assetPath = resolveMonitoringHubAssetPath(assetRoot, pathname);
|
|
2186
|
+
if (!assetPath || !existsSync4(assetPath))
|
|
2187
|
+
return null;
|
|
2188
|
+
if (assetPath.endsWith("index.html")) {
|
|
2189
|
+
const html = injectMonitoringHubBootstrap(readFileSync4(assetPath, "utf8"), bootstrap);
|
|
2190
|
+
return new Response(html, {
|
|
2191
|
+
headers: {
|
|
2192
|
+
"content-type": "text/html; charset=utf-8",
|
|
2193
|
+
"cache-control": "no-store"
|
|
2194
|
+
}
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
return new Response(Bun.file(assetPath), {
|
|
2198
|
+
headers: {
|
|
2199
|
+
"content-type": monitoringHubContentType(assetPath),
|
|
2200
|
+
"cache-control": "no-store"
|
|
2201
|
+
}
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
1885
2204
|
function buildEmbeddedMonitoringHubHtml(opts) {
|
|
1886
2205
|
const bootstrap = jsonHtmlBootstrap(opts);
|
|
1887
2206
|
return `<!doctype html>
|
|
@@ -2016,9 +2335,9 @@ function buildEmbeddedMonitoringHubHtml(opts) {
|
|
|
2016
2335
|
</body>
|
|
2017
2336
|
</html>`;
|
|
2018
2337
|
}
|
|
2019
|
-
async function proxyMonitoringHubRequest(serverUrl,
|
|
2338
|
+
async function proxyMonitoringHubRequest(serverUrl, pathValue) {
|
|
2020
2339
|
const target = `${serverUrl}${pathValue}`;
|
|
2021
|
-
const upstream = await fetchWithTimeout(target, {
|
|
2340
|
+
const upstream = await fetchWithTimeout(target, {}, 1e4);
|
|
2022
2341
|
const body = await upstream.text();
|
|
2023
2342
|
return new Response(body, {
|
|
2024
2343
|
status: upstream.status,
|
|
@@ -2029,7 +2348,12 @@ async function proxyMonitoringHubRequest(serverUrl, authToken, pathValue) {
|
|
|
2029
2348
|
});
|
|
2030
2349
|
}
|
|
2031
2350
|
async function startEmbeddedMonitoringHub(opts) {
|
|
2032
|
-
const
|
|
2351
|
+
const monitoringHubAssetRoot = opts.assetRoot === undefined ? await ensureBundledMonitoringHubRoot() : opts.assetRoot;
|
|
2352
|
+
if (!monitoringHubAssetRoot || !looksLikeMonitoringHubBuild(monitoringHubAssetRoot)) {
|
|
2353
|
+
console.error("[pushpals] Unified monitoring hub assets are unavailable; build or export the packaged client monitor first.");
|
|
2354
|
+
return null;
|
|
2355
|
+
}
|
|
2356
|
+
const bootstrap = buildMonitoringHubRuntimeBootstrap({
|
|
2033
2357
|
serverUrl: opts.serverUrl,
|
|
2034
2358
|
localAgentUrl: opts.localAgentUrl,
|
|
2035
2359
|
sessionId: opts.sessionId
|
|
@@ -2039,32 +2363,33 @@ async function startEmbeddedMonitoringHub(opts) {
|
|
|
2039
2363
|
try {
|
|
2040
2364
|
const server = Bun.serve({
|
|
2041
2365
|
port,
|
|
2366
|
+
hostname: "127.0.0.1",
|
|
2042
2367
|
idleTimeout: 30,
|
|
2043
2368
|
fetch: async (req) => {
|
|
2044
2369
|
const url = new URL(req.url);
|
|
2045
|
-
if (url.pathname === "/") {
|
|
2046
|
-
return new Response(html, {
|
|
2047
|
-
headers: {
|
|
2048
|
-
"content-type": "text/html; charset=utf-8",
|
|
2049
|
-
"cache-control": "no-store"
|
|
2050
|
-
}
|
|
2051
|
-
});
|
|
2052
|
-
}
|
|
2053
2370
|
if (url.pathname === "/healthz") {
|
|
2054
|
-
return Response.json({
|
|
2371
|
+
return Response.json({
|
|
2372
|
+
ok: true,
|
|
2373
|
+
port,
|
|
2374
|
+
serverUrl: opts.serverUrl,
|
|
2375
|
+
sessionId: opts.sessionId
|
|
2376
|
+
});
|
|
2055
2377
|
}
|
|
2056
2378
|
if (url.pathname === "/api/status") {
|
|
2057
|
-
return await proxyMonitoringHubRequest(opts.serverUrl,
|
|
2379
|
+
return await proxyMonitoringHubRequest(opts.serverUrl, "/system/status");
|
|
2058
2380
|
}
|
|
2059
2381
|
if (url.pathname === "/api/requests") {
|
|
2060
|
-
return await proxyMonitoringHubRequest(opts.serverUrl,
|
|
2382
|
+
return await proxyMonitoringHubRequest(opts.serverUrl, "/requests?status=all&limit=20");
|
|
2061
2383
|
}
|
|
2062
2384
|
if (url.pathname === "/api/jobs") {
|
|
2063
|
-
return await proxyMonitoringHubRequest(opts.serverUrl,
|
|
2385
|
+
return await proxyMonitoringHubRequest(opts.serverUrl, "/jobs?status=all&limit=20");
|
|
2064
2386
|
}
|
|
2065
2387
|
if (url.pathname === "/api/completions") {
|
|
2066
|
-
return await proxyMonitoringHubRequest(opts.serverUrl,
|
|
2388
|
+
return await proxyMonitoringHubRequest(opts.serverUrl, "/completions?status=all&limit=20");
|
|
2067
2389
|
}
|
|
2390
|
+
const bundledResponse = await serveBundledMonitoringHub(monitoringHubAssetRoot, url.pathname, bootstrap);
|
|
2391
|
+
if (bundledResponse)
|
|
2392
|
+
return bundledResponse;
|
|
2068
2393
|
return new Response("Not found", { status: 404 });
|
|
2069
2394
|
}
|
|
2070
2395
|
});
|
|
@@ -2081,10 +2406,13 @@ async function startEmbeddedMonitoringHub(opts) {
|
|
|
2081
2406
|
async function resolveMonitoringHub(opts) {
|
|
2082
2407
|
const explicit = normalizeUrl(opts.preferredUrl);
|
|
2083
2408
|
if (explicit) {
|
|
2084
|
-
if (
|
|
2409
|
+
if (!isLoopbackUrl(explicit)) {
|
|
2410
|
+
console.warn(`[pushpals] Preferred monitoring hub ${explicit} is not local; ignoring it and starting a local monitor instead.`);
|
|
2411
|
+
} else if (await looksLikeMonitoringHub(explicit)) {
|
|
2085
2412
|
return { url: explicit, port: 0, stop: () => {}, embedded: false };
|
|
2413
|
+
} else {
|
|
2414
|
+
console.warn(`[pushpals] Preferred monitoring hub ${explicit} is unavailable; starting embedded monitor instead.`);
|
|
2086
2415
|
}
|
|
2087
|
-
console.warn(`[pushpals] Preferred monitoring hub ${explicit} is unavailable; starting embedded monitor instead.`);
|
|
2088
2416
|
}
|
|
2089
2417
|
for (let port = opts.fallbackPort;port < opts.fallbackPort + MONITOR_SCAN_PORTS; port++) {
|
|
2090
2418
|
const candidate = `http://127.0.0.1:${port}`;
|
|
@@ -2204,12 +2532,11 @@ function formatSessionEventLine(event) {
|
|
|
2204
2532
|
}
|
|
2205
2533
|
return null;
|
|
2206
2534
|
}
|
|
2207
|
-
async function runSessionStream(serverUrl, sessionId,
|
|
2535
|
+
async function runSessionStream(serverUrl, sessionId, client, print, signal) {
|
|
2208
2536
|
let cursor = 0;
|
|
2209
2537
|
while (!signal.aborted) {
|
|
2210
|
-
const headers = authHeaders(authToken);
|
|
2211
2538
|
try {
|
|
2212
|
-
const response = await fetchWithTimeout(`${serverUrl}/sessions/${encodeURIComponent(sessionId)}/events${cursor
|
|
2539
|
+
const response = await fetchWithTimeout(`${serverUrl}/sessions/${encodeURIComponent(sessionId)}/events${buildClientTransportQuery(cursor, client)}`, {}, 15000);
|
|
2213
2540
|
if (!response.ok || !response.body) {
|
|
2214
2541
|
print(`[pushpals] Session stream unavailable: HTTP ${response.status}`);
|
|
2215
2542
|
await Bun.sleep(SSE_RECONNECT_MS);
|
|
@@ -2286,6 +2613,10 @@ async function openMonitoringHub(url) {
|
|
|
2286
2613
|
const code = await proc.exited;
|
|
2287
2614
|
return code === 0;
|
|
2288
2615
|
}
|
|
2616
|
+
function isCliExitCommand(text) {
|
|
2617
|
+
const normalized = String(text ?? "").trim().toLowerCase();
|
|
2618
|
+
return normalized === "/exit" || normalized === "/quit" || normalized === "exit" || normalized === "quit";
|
|
2619
|
+
}
|
|
2289
2620
|
async function main() {
|
|
2290
2621
|
const argv = process.argv.slice(2);
|
|
2291
2622
|
logCliInvocation(argv);
|
|
@@ -2319,10 +2650,9 @@ async function main() {
|
|
|
2319
2650
|
process.exit(1);
|
|
2320
2651
|
}
|
|
2321
2652
|
const config = preparedRuntime.runtimePreflight.config;
|
|
2322
|
-
const serverUrl =
|
|
2323
|
-
const localAgentUrl =
|
|
2653
|
+
const serverUrl = normalizeLoopbackUrl(parsed.serverUrl ?? process.env.PUSHPALS_SERVER_URL, config.server.url);
|
|
2654
|
+
const localAgentUrl = normalizeLoopbackUrl(parsed.localAgentUrl ?? process.env.EXPO_PUBLIC_LOCAL_AGENT_URL, config.client.localAgentUrl);
|
|
2324
2655
|
const sessionId = String(parsed.sessionId ?? process.env.PUSHPALS_SESSION_ID ?? config.sessionId).trim();
|
|
2325
|
-
const authToken = config.authToken;
|
|
2326
2656
|
let autoStartedServices = [];
|
|
2327
2657
|
const stopAutoStartedServices = () => {
|
|
2328
2658
|
if (autoStartedServices.length === 0)
|
|
@@ -2330,8 +2660,10 @@ async function main() {
|
|
|
2330
2660
|
stopRuntimeServices(autoStartedServices);
|
|
2331
2661
|
autoStartedServices = [];
|
|
2332
2662
|
};
|
|
2333
|
-
let
|
|
2334
|
-
|
|
2663
|
+
let serverHealthy = await probeServer(serverUrl);
|
|
2664
|
+
let health = await probeLocalBuddy(localAgentUrl);
|
|
2665
|
+
const runtimeNeedsAutoStart = parsed.runtimeOnly ? !serverHealthy : !health?.ok;
|
|
2666
|
+
if (runtimeNeedsAutoStart && !parsed.noAutoStart) {
|
|
2335
2667
|
try {
|
|
2336
2668
|
autoStartedServices = await autoStartRuntimeServices({
|
|
2337
2669
|
repoRoot,
|
|
@@ -2339,45 +2671,58 @@ async function main() {
|
|
|
2339
2671
|
localAgentUrl,
|
|
2340
2672
|
sourceControlManagerPort: config.sourceControlManager.port,
|
|
2341
2673
|
sourceControlManagerRemote: config.sourceControlManager.remote,
|
|
2342
|
-
authToken,
|
|
2343
2674
|
preparedRuntime,
|
|
2344
|
-
requestedRuntimeTag: parsed.runtimeTag
|
|
2675
|
+
requestedRuntimeTag: parsed.runtimeTag,
|
|
2676
|
+
requireLocalBuddy: !parsed.runtimeOnly
|
|
2345
2677
|
});
|
|
2346
|
-
|
|
2678
|
+
serverHealthy = await probeServer(serverUrl);
|
|
2679
|
+
health = await probeLocalBuddy(localAgentUrl);
|
|
2347
2680
|
} catch (err) {
|
|
2348
2681
|
console.error(`[pushpals] Auto-start failed: ${String(err)}`);
|
|
2349
2682
|
stopAutoStartedServices();
|
|
2350
2683
|
}
|
|
2351
2684
|
}
|
|
2352
|
-
if (!
|
|
2353
|
-
console.error(`[pushpals]
|
|
2685
|
+
if (parsed.runtimeOnly && !serverHealthy) {
|
|
2686
|
+
console.error(`[pushpals] Server is unavailable at ${serverUrl}.`);
|
|
2354
2687
|
if (parsed.noAutoStart) {
|
|
2355
2688
|
console.error("[pushpals] Auto-start is disabled (--no-auto-start).");
|
|
2356
2689
|
} else {
|
|
2357
|
-
console.error("[pushpals] Auto-start could not bring
|
|
2690
|
+
console.error("[pushpals] Auto-start could not bring the embedded runtime online.");
|
|
2358
2691
|
}
|
|
2359
2692
|
process.exit(1);
|
|
2360
2693
|
}
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
stopAutoStartedServices();
|
|
2369
|
-
console.error("[pushpals] Repo mismatch detected.");
|
|
2370
|
-
console.error(`[pushpals] currentRepo=${repoRoot}`);
|
|
2371
|
-
console.error(`[pushpals] localBuddyRepo=${localBuddyRepo}`);
|
|
2372
|
-
console.error("[pushpals] LocalBuddy must run against the same repo. Start PushPals from this repo and retry.");
|
|
2694
|
+
if (!parsed.runtimeOnly && !health?.ok) {
|
|
2695
|
+
console.error(`[pushpals] LocalBuddy is unavailable at ${localAgentUrl}.`);
|
|
2696
|
+
if (parsed.noAutoStart) {
|
|
2697
|
+
console.error("[pushpals] Auto-start is disabled (--no-auto-start).");
|
|
2698
|
+
} else {
|
|
2699
|
+
console.error("[pushpals] Auto-start could not bring LocalBuddy online.");
|
|
2700
|
+
}
|
|
2373
2701
|
process.exit(1);
|
|
2374
2702
|
}
|
|
2375
|
-
|
|
2376
|
-
if (
|
|
2377
|
-
|
|
2703
|
+
let localBuddySessionId = sessionId;
|
|
2704
|
+
if (!parsed.runtimeOnly) {
|
|
2705
|
+
const localBuddyRepo = health?.repo ? resolve4(health.repo) : "";
|
|
2706
|
+
if (!localBuddyRepo) {
|
|
2707
|
+
stopAutoStartedServices();
|
|
2708
|
+
console.error("[pushpals] LocalBuddy health response did not include repo path.");
|
|
2709
|
+
process.exit(1);
|
|
2710
|
+
}
|
|
2711
|
+
if (normalizePath(localBuddyRepo) !== normalizePath(repoRoot)) {
|
|
2712
|
+
stopAutoStartedServices();
|
|
2713
|
+
console.error("[pushpals] Repo mismatch detected.");
|
|
2714
|
+
console.error(`[pushpals] currentRepo=${repoRoot}`);
|
|
2715
|
+
console.error(`[pushpals] localBuddyRepo=${localBuddyRepo}`);
|
|
2716
|
+
console.error("[pushpals] LocalBuddy must run against the same repo. Start PushPals from this repo and retry.");
|
|
2717
|
+
process.exit(1);
|
|
2718
|
+
}
|
|
2719
|
+
localBuddySessionId = health?.sessionId && String(health.sessionId).trim() ? String(health.sessionId).trim() : sessionId;
|
|
2720
|
+
if (sessionId && sessionId !== localBuddySessionId) {
|
|
2721
|
+
console.warn(`[pushpals] Requested sessionId=${sessionId}, but LocalBuddy is currently attached to sessionId=${localBuddySessionId}.`);
|
|
2722
|
+
}
|
|
2378
2723
|
}
|
|
2379
|
-
const statePath =
|
|
2380
|
-
const saved = readCliState(statePath);
|
|
2724
|
+
const statePath = resolveCliStatePath(repoRoot);
|
|
2725
|
+
const saved = statePath ? readCliState(statePath) : {};
|
|
2381
2726
|
const preferredHubUrl = normalizeUrl(parsed.monitoringHubUrl ?? process.env.PUSHPALS_MONITOR_URL ?? saved.monitoringHubUrl ?? "");
|
|
2382
2727
|
const monitorPort = parsePositiveInt(process.env.PUSHPALS_CLIENT_PORT, DEFAULT_MONITOR_PORT);
|
|
2383
2728
|
const monitoringHub = await resolveMonitoringHub({
|
|
@@ -2385,32 +2730,48 @@ async function main() {
|
|
|
2385
2730
|
fallbackPort: monitorPort,
|
|
2386
2731
|
serverUrl,
|
|
2387
2732
|
localAgentUrl,
|
|
2388
|
-
sessionId: localBuddySessionId
|
|
2389
|
-
authToken
|
|
2733
|
+
sessionId: localBuddySessionId
|
|
2390
2734
|
});
|
|
2391
2735
|
const monitoringHubUrl = monitoringHub?.url ?? "";
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2736
|
+
if (statePath) {
|
|
2737
|
+
writeCliState(statePath, {
|
|
2738
|
+
monitoringHubUrl: monitoringHubUrl || undefined,
|
|
2739
|
+
serverUrl,
|
|
2740
|
+
localAgentUrl,
|
|
2741
|
+
sessionId: localBuddySessionId,
|
|
2742
|
+
repoRoot
|
|
2743
|
+
});
|
|
2744
|
+
} else {
|
|
2745
|
+
console.warn("[pushpals] Could not resolve git metadata dir; skipping CLI state persistence.");
|
|
2746
|
+
}
|
|
2399
2747
|
console.log("[pushpals] Connected.");
|
|
2400
2748
|
if (monitoringHubUrl) {
|
|
2401
|
-
console.log(`monitoringHubUrl=${monitoringHubUrl}`);
|
|
2749
|
+
console.log(`[pushpals] monitoringHubUrl=${monitoringHubUrl}`);
|
|
2402
2750
|
if (monitoringHub?.embedded) {
|
|
2403
2751
|
console.log("[pushpals] Embedded monitoring hub is running.");
|
|
2404
2752
|
}
|
|
2405
2753
|
} else {
|
|
2406
|
-
console.log("monitoringHubUrl=unavailable");
|
|
2407
|
-
}
|
|
2408
|
-
console.log(`serverUrl=${serverUrl}`);
|
|
2409
|
-
console.log(`localAgentUrl=${localAgentUrl}`);
|
|
2410
|
-
console.log(`sessionId=${localBuddySessionId}`);
|
|
2411
|
-
console.log(`repoRoot=${repoRoot}`);
|
|
2412
|
-
console.log(`cliStateFile=${statePath}`);
|
|
2413
|
-
|
|
2754
|
+
console.log("[pushpals] monitoringHubUrl=unavailable");
|
|
2755
|
+
}
|
|
2756
|
+
console.log(`[pushpals] serverUrl=${serverUrl}`);
|
|
2757
|
+
console.log(`[pushpals] localAgentUrl=${localAgentUrl}`);
|
|
2758
|
+
console.log(`[pushpals] sessionId=${localBuddySessionId}`);
|
|
2759
|
+
console.log(`[pushpals] repoRoot=${repoRoot}`);
|
|
2760
|
+
console.log(`[pushpals] cliStateFile=${statePath ?? "unavailable"}`);
|
|
2761
|
+
if (parsed.runtimeOnly) {
|
|
2762
|
+
console.log("[pushpals] runtimeOnly=true");
|
|
2763
|
+
} else {
|
|
2764
|
+
console.log("[pushpals] Type a message and press Enter. Use /exit or exit to quit.");
|
|
2765
|
+
}
|
|
2766
|
+
const cliVersion = String(process.env.PUSHPALS_CLI_PACKAGE_VERSION ?? "").trim() || "unknown";
|
|
2767
|
+
const cliClient = {
|
|
2768
|
+
clientId: createRuntimeClientId("cli"),
|
|
2769
|
+
kind: "cli",
|
|
2770
|
+
label: "CLI",
|
|
2771
|
+
version: cliVersion,
|
|
2772
|
+
platform: `${process.platform}/${process.arch}`,
|
|
2773
|
+
repoRoot
|
|
2774
|
+
};
|
|
2414
2775
|
const streamAbort = new AbortController;
|
|
2415
2776
|
let rl = null;
|
|
2416
2777
|
const printIncoming = (line) => {
|
|
@@ -2425,23 +2786,60 @@ ${line}
|
|
|
2425
2786
|
}
|
|
2426
2787
|
console.log(line);
|
|
2427
2788
|
};
|
|
2428
|
-
const streamTask = parsed.noStream ? Promise.resolve() : runSessionStream(serverUrl, localBuddySessionId,
|
|
2789
|
+
const streamTask = parsed.noStream ? Promise.resolve() : parsed.runtimeOnly ? Promise.resolve() : runSessionStream(serverUrl, localBuddySessionId, cliClient, printIncoming, streamAbort.signal);
|
|
2429
2790
|
let shuttingDown = false;
|
|
2430
2791
|
const requestStop = () => {
|
|
2431
2792
|
if (shuttingDown)
|
|
2432
2793
|
return;
|
|
2433
2794
|
shuttingDown = true;
|
|
2795
|
+
console.log("[pushpals] Shutting down CLI session...");
|
|
2434
2796
|
streamAbort.abort();
|
|
2435
2797
|
if (rl)
|
|
2436
2798
|
rl.close();
|
|
2437
2799
|
try {
|
|
2438
2800
|
monitoringHub?.stop();
|
|
2439
2801
|
} catch {}
|
|
2802
|
+
if (autoStartedServices.length > 0) {
|
|
2803
|
+
console.log("[pushpals] Stopping embedded runtime services...");
|
|
2804
|
+
}
|
|
2440
2805
|
stopAutoStartedServices();
|
|
2441
2806
|
};
|
|
2442
2807
|
process.once("SIGINT", requestStop);
|
|
2443
2808
|
process.once("SIGTERM", requestStop);
|
|
2444
2809
|
process.once("exit", requestStop);
|
|
2810
|
+
if (parsed.runtimeOnly) {
|
|
2811
|
+
console.log("[pushpals] Runtime-only mode is active. Send `exit` on stdin or terminate the process to stop.");
|
|
2812
|
+
await new Promise((resolveStop) => {
|
|
2813
|
+
let resolved = false;
|
|
2814
|
+
const finish = () => {
|
|
2815
|
+
if (resolved)
|
|
2816
|
+
return;
|
|
2817
|
+
resolved = true;
|
|
2818
|
+
resolveStop();
|
|
2819
|
+
};
|
|
2820
|
+
process.once("SIGINT", finish);
|
|
2821
|
+
process.once("SIGTERM", finish);
|
|
2822
|
+
const runtimeOnlyInput = createInterface({
|
|
2823
|
+
input: process.stdin,
|
|
2824
|
+
output: process.stdout,
|
|
2825
|
+
terminal: false
|
|
2826
|
+
});
|
|
2827
|
+
runtimeOnlyInput.on("line", (line) => {
|
|
2828
|
+
if (!isCliExitCommand(line))
|
|
2829
|
+
return;
|
|
2830
|
+
requestStop();
|
|
2831
|
+
runtimeOnlyInput.close();
|
|
2832
|
+
finish();
|
|
2833
|
+
});
|
|
2834
|
+
runtimeOnlyInput.on("close", () => {
|
|
2835
|
+
requestStop();
|
|
2836
|
+
finish();
|
|
2837
|
+
});
|
|
2838
|
+
});
|
|
2839
|
+
requestStop();
|
|
2840
|
+
await Promise.race([streamTask, Bun.sleep(2000)]);
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2445
2843
|
rl = createInterface({
|
|
2446
2844
|
input: process.stdin,
|
|
2447
2845
|
output: process.stdout,
|
|
@@ -2455,21 +2853,21 @@ ${line}
|
|
|
2455
2853
|
rl.prompt();
|
|
2456
2854
|
continue;
|
|
2457
2855
|
}
|
|
2458
|
-
if (text
|
|
2856
|
+
if (isCliExitCommand(text)) {
|
|
2459
2857
|
requestStop();
|
|
2460
2858
|
break;
|
|
2461
2859
|
}
|
|
2462
2860
|
if (text === "/hub") {
|
|
2463
|
-
console.log(monitoringHubUrl ? `monitoringHubUrl=${monitoringHubUrl}` : "monitoringHubUrl=unavailable");
|
|
2861
|
+
console.log(monitoringHubUrl ? `[pushpals] monitoringHubUrl=${monitoringHubUrl}` : "[pushpals] monitoringHubUrl=unavailable");
|
|
2464
2862
|
rl.prompt();
|
|
2465
2863
|
continue;
|
|
2466
2864
|
}
|
|
2467
2865
|
if (text === "/status") {
|
|
2468
|
-
console.log(`serverUrl=${serverUrl}`);
|
|
2469
|
-
console.log(`localAgentUrl=${localAgentUrl}`);
|
|
2470
|
-
console.log(`sessionId=${localBuddySessionId}`);
|
|
2471
|
-
console.log(`repoRoot=${repoRoot}`);
|
|
2472
|
-
console.log(monitoringHubUrl ? `monitoringHubUrl=${monitoringHubUrl}` : "monitoringHubUrl=unavailable");
|
|
2866
|
+
console.log(`[pushpals] serverUrl=${serverUrl}`);
|
|
2867
|
+
console.log(`[pushpals] localAgentUrl=${localAgentUrl}`);
|
|
2868
|
+
console.log(`[pushpals] sessionId=${localBuddySessionId}`);
|
|
2869
|
+
console.log(`[pushpals] repoRoot=${repoRoot}`);
|
|
2870
|
+
console.log(monitoringHubUrl ? `[pushpals] monitoringHubUrl=${monitoringHubUrl}` : "[pushpals] monitoringHubUrl=unavailable");
|
|
2473
2871
|
rl.prompt();
|
|
2474
2872
|
continue;
|
|
2475
2873
|
}
|
|
@@ -2502,9 +2900,14 @@ if (import.meta.main) {
|
|
|
2502
2900
|
export {
|
|
2503
2901
|
startEmbeddedMonitoringHub,
|
|
2504
2902
|
resolveCommandPath,
|
|
2903
|
+
resolveCliStatePath,
|
|
2505
2904
|
resolveBundledRuntimeAssetSource,
|
|
2905
|
+
resolveBundledMonitoringHubRoot,
|
|
2506
2906
|
prepareCliRuntime,
|
|
2507
2907
|
normalizeChildProcessEnv,
|
|
2908
|
+
isCliExitCommand,
|
|
2909
|
+
injectMonitoringHubBootstrap,
|
|
2910
|
+
formatTimestampedCliLine,
|
|
2508
2911
|
buildServiceStopCommand,
|
|
2509
2912
|
buildOpenMonitoringHubCommand,
|
|
2510
2913
|
buildEmbeddedRuntimeEnv,
|