@pushpalsdev/cli 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/pushpals-cli.js +609 -53
  2. package/package.json +3 -2
  3. package/runtime/.env.example +73 -0
  4. package/runtime/configs/backend.toml +79 -0
  5. package/runtime/configs/default.toml +259 -0
  6. package/runtime/configs/dev.toml +2 -0
  7. package/runtime/configs/local.example.toml +124 -0
  8. package/runtime/prompts/localbuddy/local_quick_reply_json_system_suffix.md +2 -0
  9. package/runtime/prompts/localbuddy/local_quick_reply_system_prompt.md +10 -0
  10. package/runtime/prompts/localbuddy/local_quick_reply_user_prompt.md +2 -0
  11. package/runtime/prompts/localbuddy/localbuddy_planner_git_diff_section.md +3 -0
  12. package/runtime/prompts/localbuddy/localbuddy_planner_git_status_section.md +3 -0
  13. package/runtime/prompts/localbuddy/localbuddy_planner_output_contract.md +5 -0
  14. package/runtime/prompts/localbuddy/localbuddy_planner_user_prompt.md +2 -0
  15. package/runtime/prompts/localbuddy/localbuddy_system_prompt.md +110 -0
  16. package/runtime/prompts/remotebuddy/autonomy_ideation_system_prompt.md +60 -0
  17. package/runtime/prompts/remotebuddy/autonomy_planning_system_prompt.md +5 -0
  18. package/runtime/prompts/remotebuddy/autonomy_scoring_system_prompt.md +6 -0
  19. package/runtime/prompts/remotebuddy/codex_adapter_json_requirements.md +1 -0
  20. package/runtime/prompts/remotebuddy/codex_adapter_json_schema_intro.md +1 -0
  21. package/runtime/prompts/remotebuddy/codex_adapter_max_tokens_line.md +1 -0
  22. package/runtime/prompts/remotebuddy/codex_adapter_prompt_template.md +14 -0
  23. package/runtime/prompts/remotebuddy/context_packer_condensed_history_system_prompt.md +1 -0
  24. package/runtime/prompts/remotebuddy/context_packer_system_prompt.md +1 -0
  25. package/runtime/prompts/remotebuddy/context_packer_user_prompt.md +11 -0
  26. package/runtime/prompts/remotebuddy/fallback_file_system_prompt.md +1 -0
  27. package/runtime/prompts/remotebuddy/fallback_file_user_prompt.md +4 -0
  28. package/runtime/prompts/remotebuddy/planner_post_system_prompt.md +2 -0
  29. package/runtime/prompts/remotebuddy/planner_repair_suffix_prompt.md +1 -0
  30. package/runtime/prompts/remotebuddy/planner_repair_user_prompt.md +7 -0
  31. package/runtime/prompts/remotebuddy/remotebuddy_system_prompt.md +109 -0
  32. package/runtime/prompts/review_agent/fix_job_intro_line.md +1 -0
  33. package/runtime/prompts/review_agent/merge_conflict_context_intro_line.md +1 -0
  34. package/runtime/prompts/review_agent/merge_conflict_instruction.md +4 -0
  35. package/runtime/prompts/review_agent/review_prompt_template.md +18 -0
  36. package/runtime/prompts/review_agent/reviewer.md +39 -0
  37. package/runtime/prompts/shared/post_system_prompt.md +62 -0
  38. package/runtime/prompts/workerpals/codex_quality_critic_instruction_prompt.md +14 -0
  39. package/runtime/prompts/workerpals/commit_message_prompt.md +36 -0
  40. package/runtime/prompts/workerpals/commit_message_user_prompt.md +7 -0
  41. package/runtime/prompts/workerpals/miniswe_broker_system_prompt.md +33 -0
  42. package/runtime/prompts/workerpals/miniswe_broker_task_prompt.md +5 -0
  43. package/runtime/prompts/workerpals/miniswe_completion_requirement.md +1 -0
  44. package/runtime/prompts/workerpals/miniswe_context_compaction_retry_prompt.md +1 -0
  45. package/runtime/prompts/workerpals/miniswe_explicit_targets_block.md +2 -0
  46. package/runtime/prompts/workerpals/miniswe_recovery_guidance_base.md +4 -0
  47. package/runtime/prompts/workerpals/miniswe_recovery_guidance_blocker_line.md +1 -0
  48. package/runtime/prompts/workerpals/miniswe_strict_tool_use_guidance.md +6 -0
  49. package/runtime/prompts/workerpals/miniswe_supplemental_guidance_section.md +2 -0
  50. package/runtime/prompts/workerpals/miniswe_timeout_note.md +1 -0
  51. package/runtime/prompts/workerpals/miniswe_toolcall_retry_guidance.md +1 -0
  52. package/runtime/prompts/workerpals/openai_codex_default_system_prompt.md +4 -0
  53. package/runtime/prompts/workerpals/openai_codex_instruction_wrapper.md +5 -0
  54. package/runtime/prompts/workerpals/openai_codex_runtime_policy_appendix.md +5 -0
  55. package/runtime/prompts/workerpals/openai_codex_supplemental_guidance_section.md +2 -0
  56. package/runtime/prompts/workerpals/openai_codex_task_execute_system_prompt.md +12 -0
  57. package/runtime/prompts/workerpals/openhands_minimal_security_policy.j2 +8 -0
  58. package/runtime/prompts/workerpals/openhands_minimal_system_prompt.j2 +20 -0
  59. package/runtime/prompts/workerpals/openhands_strict_tool_use_message.md +1 -0
  60. package/runtime/prompts/workerpals/openhands_supplemental_guidance_message.md +2 -0
  61. package/runtime/prompts/workerpals/openhands_task_execute_fallback_system_prompt.md +1 -0
  62. package/runtime/prompts/workerpals/openhands_task_execute_system_prompt.md +21 -0
  63. package/runtime/prompts/workerpals/openhands_task_user_prompt.md +6 -0
  64. package/runtime/prompts/workerpals/openhands_timeout_note.md +1 -0
  65. package/runtime/prompts/workerpals/pr_description.md +42 -0
  66. package/runtime/prompts/workerpals/task_quality_critic_system_prompt.md +9 -0
  67. package/runtime/prompts/workerpals/task_quality_critic_user_prompt.md +17 -0
  68. package/runtime/prompts/workerpals/workerpals_system_prompt.md +115 -0
  69. package/runtime/protocol/schemas/approvals.schema.json +6 -0
  70. package/runtime/protocol/schemas/envelope.schema.json +96 -0
  71. package/runtime/protocol/schemas/events.schema.json +679 -0
  72. package/runtime/protocol/schemas/http.schema.json +50 -0
  73. package/runtime/vision.example.md +191 -0
@@ -2,10 +2,14 @@
2
2
  // @bun
3
3
 
4
4
  // ../../scripts/pushpals-cli.ts
5
- import { appendFileSync, chmodSync, cpSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
6
- import { dirname, join as join2, resolve as resolve2 } from "path";
5
+ import { appendFileSync, chmodSync, cpSync, existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
6
+ import { dirname, join as join2, resolve as resolve3 } from "path";
7
7
  import { createInterface } from "readline";
8
8
 
9
+ // ../shared/src/client_preflight.ts
10
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
11
+ import { relative, resolve as resolve2 } from "path";
12
+
9
13
  // ../shared/src/config.ts
10
14
  import { existsSync, readFileSync } from "fs";
11
15
  import { join, resolve, isAbsolute } from "path";
@@ -739,6 +743,316 @@ function loadPushPalsConfig(options = {}) {
739
743
  return config;
740
744
  }
741
745
 
746
+ // ../shared/src/vision.ts
747
+ var SECTION_HEADING_RE = /^##\s+(\d+)\)\s+(.+?)\s*$/;
748
+ var ONE_SENTENCE_PROMPT_RE = /^\>\s*\*\*One sentence:\*\*\s*(.+)\s*$/i;
749
+ var BLOCKQUOTE_RE = /^\>\s*(.+?)\s*$/;
750
+ function toLines(markdown) {
751
+ return String(markdown ?? "").replace(/\r\n/g, `
752
+ `).split(`
753
+ `);
754
+ }
755
+ function extractOneSentence(lines) {
756
+ let expectNextBlockquoteSentence = false;
757
+ for (const line of lines) {
758
+ const marker = line.match(ONE_SENTENCE_PROMPT_RE);
759
+ if (marker) {
760
+ const inline = marker[1].trim();
761
+ if (inline)
762
+ return inline;
763
+ expectNextBlockquoteSentence = true;
764
+ continue;
765
+ }
766
+ const block = line.match(BLOCKQUOTE_RE);
767
+ if (expectNextBlockquoteSentence) {
768
+ if (!block)
769
+ continue;
770
+ const text = block[1].trim();
771
+ if (!text)
772
+ continue;
773
+ if (/^Example:/i.test(text))
774
+ continue;
775
+ return text;
776
+ }
777
+ }
778
+ for (const line of lines) {
779
+ const block = line.match(BLOCKQUOTE_RE);
780
+ if (!block)
781
+ continue;
782
+ const text = block[1].trim();
783
+ if (!text)
784
+ continue;
785
+ if (/^\*\*One sentence:\*\*/i.test(text))
786
+ continue;
787
+ if (/^Example:/i.test(text))
788
+ continue;
789
+ return text;
790
+ }
791
+ return "";
792
+ }
793
+ function parseVisionDoc(markdown) {
794
+ const lines = toLines(markdown);
795
+ const sections = [];
796
+ let currentNumber = "";
797
+ let currentTitle = "";
798
+ let currentBody = [];
799
+ const flushCurrent = () => {
800
+ if (!currentNumber)
801
+ return;
802
+ sections.push({
803
+ number: currentNumber,
804
+ title: currentTitle,
805
+ markdown: currentBody.join(`
806
+ `).trim()
807
+ });
808
+ currentNumber = "";
809
+ currentTitle = "";
810
+ currentBody = [];
811
+ };
812
+ for (const line of lines) {
813
+ const heading = line.match(SECTION_HEADING_RE);
814
+ if (heading) {
815
+ flushCurrent();
816
+ currentNumber = heading[1];
817
+ currentTitle = heading[2].trim();
818
+ continue;
819
+ }
820
+ if (currentNumber) {
821
+ currentBody.push(line);
822
+ }
823
+ }
824
+ flushCurrent();
825
+ const sectionByNumber = {};
826
+ for (const section of sections) {
827
+ if (!sectionByNumber[section.number]) {
828
+ sectionByNumber[section.number] = section;
829
+ }
830
+ }
831
+ return {
832
+ oneSentence: extractOneSentence(lines),
833
+ sections,
834
+ sectionByNumber
835
+ };
836
+ }
837
+ function validateVisionDocStructure(markdown) {
838
+ const parsed = parseVisionDoc(markdown);
839
+ const missingSectionNumbers = [];
840
+ const errors = [];
841
+ if (!parsed.oneSentence) {
842
+ errors.push('Missing one-sentence vision line (expected near the top as a blockquote after "**One sentence:**").');
843
+ }
844
+ return {
845
+ ok: errors.length === 0,
846
+ sectionCount: parsed.sections.length,
847
+ hasOneSentence: Boolean(parsed.oneSentence),
848
+ missingSectionNumbers,
849
+ errors
850
+ };
851
+ }
852
+
853
+ // ../shared/src/client_preflight.ts
854
+ function runtimeHasConfigDir(runtimeRoot, dirName) {
855
+ const dirPath = resolve2(runtimeRoot, dirName);
856
+ return existsSync2(resolve2(dirPath, "default.toml")) || existsSync2(resolve2(dirPath, "local.example.toml")) || existsSync2(resolve2(dirPath, "local.toml"));
857
+ }
858
+ function resolveClientConfigDir(projectRoot, runtimeRoot, explicitConfigDir) {
859
+ if (explicitConfigDir && explicitConfigDir.trim()) {
860
+ return resolve2(explicitConfigDir);
861
+ }
862
+ const runtimeCanonical = resolve2(runtimeRoot, "configs");
863
+ if (runtimeHasConfigDir(runtimeRoot, "configs")) {
864
+ return runtimeCanonical;
865
+ }
866
+ const runtimeLegacy = resolve2(runtimeRoot, "config");
867
+ if (runtimeHasConfigDir(runtimeRoot, "config")) {
868
+ return runtimeLegacy;
869
+ }
870
+ const projectCanonical = resolve2(projectRoot, "configs");
871
+ if (runtimeHasConfigDir(projectRoot, "configs")) {
872
+ return projectCanonical;
873
+ }
874
+ const projectLegacy = resolve2(projectRoot, "config");
875
+ if (runtimeHasConfigDir(projectRoot, "config")) {
876
+ return projectLegacy;
877
+ }
878
+ return runtimeCanonical;
879
+ }
880
+ function toDisplayPath(currentRoot, pathValue) {
881
+ const rel = relative(currentRoot, pathValue);
882
+ if (!rel || rel === "")
883
+ return ".";
884
+ if (rel.startsWith(".."))
885
+ return pathValue;
886
+ return rel.replace(/\\/g, "/");
887
+ }
888
+ function quotePowerShell(pathValue) {
889
+ if (/^[A-Za-z0-9_./\\:-]+$/.test(pathValue))
890
+ return pathValue;
891
+ return `'${pathValue.replace(/'/g, "''")}'`;
892
+ }
893
+ function quoteBash(pathValue) {
894
+ if (/^[A-Za-z0-9_./\\:-]+$/.test(pathValue))
895
+ return pathValue;
896
+ return "'" + pathValue.replace(/'/g, `'"'"'`) + "'";
897
+ }
898
+ function buildCopyCommands(workspaceRoot, sourcePath, destPath) {
899
+ const displaySource = toDisplayPath(workspaceRoot, sourcePath);
900
+ const displayDest = toDisplayPath(workspaceRoot, destPath);
901
+ return {
902
+ windowsPowerShell: `Copy-Item ${quotePowerShell(displaySource)} ${quotePowerShell(displayDest)}`,
903
+ bash: `cp ${quoteBash(displaySource)} ${quoteBash(displayDest)}`
904
+ };
905
+ }
906
+ function evaluateClientRuntimePreflight(options) {
907
+ const projectRoot = resolve2(options.projectRoot);
908
+ const runtimeRoot = resolve2(options.runtimeRoot ?? projectRoot);
909
+ const configDir = resolveClientConfigDir(projectRoot, runtimeRoot, options.configDir);
910
+ const visionTemplateRoot = resolve2(options.visionTemplateRoot ?? runtimeRoot);
911
+ const config = options.config ?? loadPushPalsConfig({
912
+ projectRoot,
913
+ configDir,
914
+ reload: true
915
+ });
916
+ const issues = [];
917
+ const envPath = resolve2(runtimeRoot, ".env");
918
+ if (!existsSync2(envPath)) {
919
+ const envExamplePath = resolve2(runtimeRoot, ".env.example");
920
+ issues.push({
921
+ code: "missing_env_file",
922
+ message: `Missing required local env file: ${toDisplayPath(projectRoot, envPath)}.`,
923
+ copyCommands: existsSync2(envExamplePath) ? buildCopyCommands(projectRoot, envExamplePath, envPath) : undefined
924
+ });
925
+ }
926
+ const localTomlPath = resolve2(runtimeRoot, "configs", "local.toml");
927
+ const legacyLocalTomlPath = resolve2(runtimeRoot, "config", "local.toml");
928
+ if (!existsSync2(localTomlPath) && !existsSync2(legacyLocalTomlPath)) {
929
+ const localExamplePath = resolve2(runtimeRoot, "configs", "local.example.toml");
930
+ issues.push({
931
+ code: "missing_local_toml",
932
+ message: `Missing required local config file: ${toDisplayPath(projectRoot, localTomlPath)}.`,
933
+ copyCommands: existsSync2(localExamplePath) ? buildCopyCommands(projectRoot, localExamplePath, localTomlPath) : undefined
934
+ });
935
+ }
936
+ const autonomyEnabled = Boolean(config.remotebuddy.autonomy.enabled);
937
+ if (!autonomyEnabled) {
938
+ return {
939
+ ok: issues.length === 0,
940
+ projectRoot,
941
+ runtimeRoot,
942
+ config,
943
+ issues,
944
+ autonomyEnabled,
945
+ visionSummary: null
946
+ };
947
+ }
948
+ const visionPath = resolve2(projectRoot, "vision.md");
949
+ const visionTemplatePath = resolve2(visionTemplateRoot, "vision.example.md");
950
+ if (!existsSync2(visionPath)) {
951
+ issues.push({
952
+ code: "missing_vision_doc",
953
+ message: "Missing required autonomy vision file: vision.md " + "(required when remotebuddy.autonomy.enabled=true).",
954
+ copyCommands: existsSync2(visionTemplatePath) ? buildCopyCommands(projectRoot, visionTemplatePath, visionPath) : undefined
955
+ });
956
+ return {
957
+ ok: false,
958
+ projectRoot,
959
+ runtimeRoot,
960
+ config,
961
+ issues,
962
+ autonomyEnabled,
963
+ visionSummary: null
964
+ };
965
+ }
966
+ let rawVision = "";
967
+ try {
968
+ rawVision = readFileSync2(visionPath, "utf8");
969
+ } catch (err) {
970
+ issues.push({
971
+ code: "unreadable_vision_doc",
972
+ message: `Autonomy vision preflight failed: could not read vision.md.`,
973
+ detail: String(err)
974
+ });
975
+ return {
976
+ ok: false,
977
+ projectRoot,
978
+ runtimeRoot,
979
+ config,
980
+ issues,
981
+ autonomyEnabled,
982
+ visionSummary: null
983
+ };
984
+ }
985
+ const visionText = rawVision.trim();
986
+ if (!visionText) {
987
+ issues.push({
988
+ code: "empty_vision_doc",
989
+ message: "Autonomy vision preflight failed: vision.md is empty.",
990
+ detail: "Add repository vision/goals before startup."
991
+ });
992
+ return {
993
+ ok: false,
994
+ projectRoot,
995
+ runtimeRoot,
996
+ config,
997
+ issues,
998
+ autonomyEnabled,
999
+ visionSummary: null
1000
+ };
1001
+ }
1002
+ const validation = validateVisionDocStructure(visionText);
1003
+ if (!validation.ok) {
1004
+ issues.push({
1005
+ code: "invalid_vision_doc",
1006
+ message: "Autonomy vision preflight failed: vision.md is invalid.",
1007
+ detail: validation.errors.join(" ")
1008
+ });
1009
+ return {
1010
+ ok: false,
1011
+ projectRoot,
1012
+ runtimeRoot,
1013
+ config,
1014
+ issues,
1015
+ autonomyEnabled,
1016
+ visionSummary: null
1017
+ };
1018
+ }
1019
+ return {
1020
+ ok: issues.length === 0,
1021
+ projectRoot,
1022
+ runtimeRoot,
1023
+ config,
1024
+ issues,
1025
+ autonomyEnabled,
1026
+ visionSummary: {
1027
+ path: toDisplayPath(projectRoot, visionPath),
1028
+ chars: visionText.length,
1029
+ sectionCount: validation.sectionCount,
1030
+ validation
1031
+ }
1032
+ };
1033
+ }
1034
+ function formatClientRuntimePreflightLines(result, prefix) {
1035
+ const normalizedPrefix = prefix.trim();
1036
+ const lines = [];
1037
+ if (result.ok) {
1038
+ if (result.visionSummary) {
1039
+ lines.push(`${normalizedPrefix} Autonomy preflight: loaded ${result.visionSummary.path} ` + `(${result.visionSummary.chars} chars, ${result.visionSummary.sectionCount} section(s)).`);
1040
+ }
1041
+ return lines;
1042
+ }
1043
+ for (const issue of result.issues) {
1044
+ lines.push(`${normalizedPrefix} ${issue.message}`);
1045
+ if (issue.detail) {
1046
+ lines.push(`${normalizedPrefix} ${issue.detail}`);
1047
+ }
1048
+ if (issue.copyCommands) {
1049
+ lines.push(`${normalizedPrefix} Windows (PowerShell): ${issue.copyCommands.windowsPowerShell}`);
1050
+ lines.push(`${normalizedPrefix} Linux/macOS (bash): ${issue.copyCommands.bash}`);
1051
+ }
1052
+ }
1053
+ return lines;
1054
+ }
1055
+
742
1056
  // ../../scripts/pushpals-cli.ts
743
1057
  var DEFAULT_MONITOR_PORT = 8081;
744
1058
  var MONITOR_SCAN_PORTS = 32;
@@ -855,7 +1169,7 @@ function parsePositiveInt(value, fallback) {
855
1169
  return parsed;
856
1170
  }
857
1171
  function normalizePath(value) {
858
- const normalized = resolve2(value).replace(/\\/g, "/").replace(/\/+$/, "");
1172
+ const normalized = resolve3(value).replace(/\\/g, "/").replace(/\/+$/, "");
859
1173
  if (process.platform === "win32")
860
1174
  return normalized.toLowerCase();
861
1175
  return normalized;
@@ -883,11 +1197,39 @@ async function resolveCurrentGitRepoRoot(cwd) {
883
1197
  const root = await runGit(["rev-parse", "--show-toplevel"], cwd);
884
1198
  if (!root.ok || !root.stdout)
885
1199
  return null;
886
- return resolve2(root.stdout);
1200
+ return resolve3(root.stdout);
887
1201
  }
888
1202
  function resolveDefaultRuntimeRoot() {
889
1203
  const home = process.env.USERPROFILE || process.env.HOME || process.cwd();
890
- return resolve2(home, ".pushpals", "runtime");
1204
+ return resolve3(home, ".pushpals", "runtime");
1205
+ }
1206
+ function buildRuntimeAssetSource(root, protocolSchemasDir) {
1207
+ return {
1208
+ root,
1209
+ envExamplePath: join2(root, ".env.example"),
1210
+ visionExamplePath: join2(root, "vision.example.md"),
1211
+ configsDir: join2(root, "configs"),
1212
+ promptsDir: join2(root, "prompts"),
1213
+ protocolSchemasDir
1214
+ };
1215
+ }
1216
+ function isCompleteRuntimeAssetSource(source) {
1217
+ return existsSync3(source.envExamplePath) && existsSync3(source.visionExamplePath) && existsSync3(join2(source.configsDir, "default.toml")) && existsSync3(source.promptsDir) && existsSync3(join2(source.protocolSchemasDir, "envelope.schema.json")) && existsSync3(join2(source.protocolSchemasDir, "events.schema.json"));
1218
+ }
1219
+ function resolveBundledRuntimeAssetSource() {
1220
+ const candidates = [
1221
+ buildRuntimeAssetSource(resolve3(import.meta.dir, "..", "runtime"), resolve3(import.meta.dir, "..", "runtime", "protocol", "schemas")),
1222
+ buildRuntimeAssetSource(resolve3(import.meta.dir, ".."), resolve3(import.meta.dir, "..", "packages", "protocol", "src", "schemas")),
1223
+ buildRuntimeAssetSource(resolve3(import.meta.dir, "..", "packages", "cli", "runtime"), resolve3(import.meta.dir, "..", "packages", "cli", "runtime", "protocol", "schemas"))
1224
+ ];
1225
+ for (const candidate of candidates) {
1226
+ if (isCompleteRuntimeAssetSource(candidate))
1227
+ return candidate;
1228
+ }
1229
+ return null;
1230
+ }
1231
+ function repoLooksLikePushPalsSourceCheckout(repoRoot) {
1232
+ return existsSync3(join2(repoRoot, "configs", "default.toml")) || existsSync3(join2(repoRoot, "config", "default.toml"));
891
1233
  }
892
1234
  function parseSemverFromPackageVersion(value) {
893
1235
  const raw = String(value ?? "").trim();
@@ -937,18 +1279,56 @@ async function resolveRuntimeReleaseTag(explicitTag) {
937
1279
  }
938
1280
  }
939
1281
  function writeTextFileIfMissing(pathValue, text) {
940
- if (existsSync2(pathValue))
1282
+ if (existsSync3(pathValue))
941
1283
  return;
942
1284
  mkdirSync(dirname(pathValue), { recursive: true });
943
1285
  writeFileSync(pathValue, text, "utf8");
944
1286
  }
945
- function copyBundledRuntimeAssets(runtimeRoot) {
946
- const bundledRoot = resolve2(import.meta.dir, "..", "runtime");
947
- if (!existsSync2(bundledRoot))
1287
+ function copyRuntimeAssetBundle(source, runtimeRoot, force) {
1288
+ mkdirSync(runtimeRoot, { recursive: true });
1289
+ cpSync(source.envExamplePath, join2(runtimeRoot, ".env.example"), {
1290
+ force,
1291
+ errorOnExist: false
1292
+ });
1293
+ cpSync(source.visionExamplePath, join2(runtimeRoot, "vision.example.md"), {
1294
+ force,
1295
+ errorOnExist: false
1296
+ });
1297
+ cpSync(source.configsDir, join2(runtimeRoot, "configs"), {
1298
+ recursive: true,
1299
+ force,
1300
+ errorOnExist: false
1301
+ });
1302
+ cpSync(source.promptsDir, join2(runtimeRoot, "prompts"), {
1303
+ recursive: true,
1304
+ force,
1305
+ errorOnExist: false
1306
+ });
1307
+ cpSync(source.protocolSchemasDir, join2(runtimeRoot, "protocol", "schemas"), {
1308
+ recursive: true,
1309
+ force,
1310
+ errorOnExist: false
1311
+ });
1312
+ }
1313
+ function copyBundledRuntimeAssets(runtimeRoot, force = true) {
1314
+ const bundledSource = resolveBundledRuntimeAssetSource();
1315
+ if (!bundledSource)
948
1316
  return false;
949
- cpSync(bundledRoot, runtimeRoot, { recursive: true, force: true });
1317
+ copyRuntimeAssetBundle(bundledSource, runtimeRoot, force);
950
1318
  return true;
951
1319
  }
1320
+ function seedRuntimePreflightAssets(runtimeRoot) {
1321
+ copyBundledRuntimeAssets(runtimeRoot, false);
1322
+ writeTextFileIfMissing(join2(runtimeRoot, ".env"), `# Local PushPals runtime environment
1323
+ `);
1324
+ const localExamplePath = join2(runtimeRoot, "configs", "local.example.toml");
1325
+ if (existsSync3(localExamplePath)) {
1326
+ writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), readFileSync3(localExamplePath, "utf8"));
1327
+ } else {
1328
+ writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), `# Local PushPals runtime overrides
1329
+ `);
1330
+ }
1331
+ }
952
1332
  async function fetchTextFromUrl(url, timeoutMs = 20000) {
953
1333
  const response = await fetchWithTimeout(url, { headers: GITHUB_HEADERS }, timeoutMs);
954
1334
  if (!response.ok) {
@@ -963,7 +1343,7 @@ async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
963
1343
  throw new Error(`Failed to fetch runtime source tree for ${tag} (HTTP ${treeResponse.status})`);
964
1344
  }
965
1345
  const treePayload = await treeResponse.json();
966
- 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/"));
1346
+ const paths = (treePayload.tree ?? []).filter((entry) => entry.type === "blob" && typeof entry.path === "string").map((entry) => String(entry.path)).filter((pathValue) => pathValue === ".env.example" || pathValue === "vision.example.md" || pathValue.startsWith("configs/") || pathValue.startsWith("prompts/") || pathValue.startsWith("packages/protocol/src/schemas/"));
967
1347
  if (paths.length === 0) {
968
1348
  throw new Error(`Runtime source tree for ${tag} did not include prompts/config assets`);
969
1349
  }
@@ -978,14 +1358,14 @@ async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
978
1358
  }
979
1359
  async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
980
1360
  const markerPath = join2(runtimeRoot, ".runtime-assets-tag");
981
- const currentTag = existsSync2(markerPath) ? readFileSync2(markerPath, "utf8").trim() : "";
1361
+ const currentTag = existsSync3(markerPath) ? readFileSync3(markerPath, "utf8").trim() : "";
982
1362
  const protocolSchemasDir = join2(runtimeRoot, "protocol", "schemas");
983
- const hasProtocolSchemas = existsSync2(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync2(join2(protocolSchemasDir, "events.schema.json"));
984
- const hasAssets = existsSync2(join2(runtimeRoot, ".env.example")) && existsSync2(join2(runtimeRoot, "configs", "default.toml")) && existsSync2(join2(runtimeRoot, "prompts")) && hasProtocolSchemas;
1363
+ const hasProtocolSchemas = existsSync3(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync3(join2(protocolSchemasDir, "events.schema.json"));
1364
+ const hasAssets = existsSync3(join2(runtimeRoot, ".env.example")) && existsSync3(join2(runtimeRoot, "vision.example.md")) && existsSync3(join2(runtimeRoot, "configs", "default.toml")) && existsSync3(join2(runtimeRoot, "prompts")) && hasProtocolSchemas;
985
1365
  if (!hasAssets || currentTag !== runtimeTag) {
986
1366
  copyBundledRuntimeAssets(runtimeRoot);
987
- const hasProtocolSchemasAfterCopy = existsSync2(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync2(join2(protocolSchemasDir, "events.schema.json"));
988
- const hasAssetsAfterCopy = existsSync2(join2(runtimeRoot, ".env.example")) && existsSync2(join2(runtimeRoot, "configs", "default.toml")) && existsSync2(join2(runtimeRoot, "prompts")) && hasProtocolSchemasAfterCopy;
1367
+ const hasProtocolSchemasAfterCopy = existsSync3(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync3(join2(protocolSchemasDir, "events.schema.json"));
1368
+ const hasAssetsAfterCopy = existsSync3(join2(runtimeRoot, ".env.example")) && existsSync3(join2(runtimeRoot, "vision.example.md")) && existsSync3(join2(runtimeRoot, "configs", "default.toml")) && existsSync3(join2(runtimeRoot, "prompts")) && hasProtocolSchemasAfterCopy;
989
1369
  if (!hasAssetsAfterCopy) {
990
1370
  await downloadRuntimeAssetsFromSourceTag(runtimeRoot, runtimeTag);
991
1371
  }
@@ -995,36 +1375,130 @@ async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
995
1375
  writeTextFileIfMissing(join2(runtimeRoot, ".env"), `# Local PushPals runtime environment
996
1376
  `);
997
1377
  const localExamplePath = join2(runtimeRoot, "configs", "local.example.toml");
998
- if (existsSync2(localExamplePath)) {
999
- writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), readFileSync2(localExamplePath, "utf8"));
1378
+ if (existsSync3(localExamplePath)) {
1379
+ writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), readFileSync3(localExamplePath, "utf8"));
1000
1380
  } else {
1001
1381
  writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), `# Local PushPals runtime overrides
1002
1382
  `);
1003
1383
  }
1004
1384
  }
1385
+ function resolveDeferredRuntimeTagHint(explicitTag) {
1386
+ return String(explicitTag || process.env.PUSHPALS_RUNTIME_TAG || "").trim();
1387
+ }
1388
+ async function prepareCliRuntime(opts) {
1389
+ const runtimeRoot = resolve3(opts.runtimeRoot || process.env.PUSHPALS_RUNTIME_ROOT || resolveDefaultRuntimeRoot());
1390
+ if (repoLooksLikePushPalsSourceCheckout(opts.repoRoot)) {
1391
+ return {
1392
+ runtimeRoot,
1393
+ runtimeTag: "",
1394
+ runtimePreflight: evaluateClientRuntimePreflight({
1395
+ projectRoot: opts.repoRoot
1396
+ }),
1397
+ preflightUsesEmbeddedRuntime: false
1398
+ };
1399
+ }
1400
+ seedRuntimePreflightAssets(runtimeRoot);
1401
+ return {
1402
+ runtimeRoot,
1403
+ runtimeTag: resolveDeferredRuntimeTagHint(opts.runtimeTag),
1404
+ runtimePreflight: evaluateClientRuntimePreflight({
1405
+ projectRoot: opts.repoRoot,
1406
+ runtimeRoot,
1407
+ visionTemplateRoot: runtimeRoot
1408
+ }),
1409
+ preflightUsesEmbeddedRuntime: true
1410
+ };
1411
+ }
1412
+ function emitCliRuntimePreflight(result) {
1413
+ const lines = formatClientRuntimePreflightLines(result, "[pushpals]");
1414
+ if (result.ok) {
1415
+ for (const line of lines)
1416
+ console.log(line);
1417
+ return;
1418
+ }
1419
+ for (const line of lines)
1420
+ console.error(line);
1421
+ }
1005
1422
  function runtimeBinaryFilename(serviceName, platformKey) {
1006
1423
  const serviceToken = serviceName === "source_control_manager" ? "source-control-manager" : serviceName;
1007
1424
  const extension = platformKey.startsWith("windows-") ? ".exe" : "";
1008
1425
  return `pushpals-runtime-${serviceToken}-${platformKey}${extension}`;
1009
1426
  }
1010
1427
  function buildEmbeddedRuntimeEnv(baseEnv, opts) {
1428
+ const env = normalizeChildProcessEnv(baseEnv);
1429
+ const useRuntimeConfig = opts.useRuntimeConfig !== false;
1011
1430
  return {
1012
- ...baseEnv,
1431
+ ...env,
1013
1432
  PUSHPALS_REPO_ROOT_OVERRIDE: opts.repoRoot,
1014
1433
  PUSHPALS_PROJECT_ROOT_OVERRIDE: opts.repoRoot,
1015
- PUSHPALS_CONFIG_DIR_OVERRIDE: join2(opts.runtimeRoot, "configs"),
1016
- PUSHPALS_PROMPTS_ROOT_OVERRIDE: opts.runtimeRoot,
1434
+ ...useRuntimeConfig ? {
1435
+ PUSHPALS_CONFIG_DIR_OVERRIDE: join2(opts.runtimeRoot, "configs"),
1436
+ PUSHPALS_PROMPTS_ROOT_OVERRIDE: opts.runtimeRoot
1437
+ } : {
1438
+ PUSHPALS_PROMPTS_ROOT_OVERRIDE: opts.repoRoot
1439
+ },
1017
1440
  PUSHPALS_PROTOCOL_SCHEMAS_DIR: join2(opts.runtimeRoot, "protocol", "schemas"),
1018
- REMOTEBUDDY_AUTONOMY_ENABLED: String(baseEnv.REMOTEBUDDY_AUTONOMY_ENABLED ?? "").trim() || "false"
1441
+ ...typeof env.PUSHPALS_GIT_BIN === "string" && env.PUSHPALS_GIT_BIN.trim() ? { PUSHPALS_GIT_BIN: env.PUSHPALS_GIT_BIN.trim() } : {}
1019
1442
  };
1020
1443
  }
1444
+ function normalizeChildProcessEnv(baseEnv, platform = process.platform) {
1445
+ const env = {};
1446
+ for (const [key, value] of Object.entries(baseEnv)) {
1447
+ if (typeof value === "string")
1448
+ env[key] = value;
1449
+ }
1450
+ if (platform === "win32") {
1451
+ const resolvedPath = String(env.Path ?? env.PATH ?? process.env.Path ?? process.env.PATH ?? "").trim();
1452
+ if (resolvedPath) {
1453
+ env.Path = resolvedPath;
1454
+ env.PATH = resolvedPath;
1455
+ }
1456
+ const systemRoot = String(env.SystemRoot ?? env.SYSTEMROOT ?? process.env.SystemRoot ?? process.env.SYSTEMROOT ?? "").trim();
1457
+ if (systemRoot) {
1458
+ env.SystemRoot = systemRoot;
1459
+ env.SYSTEMROOT = systemRoot;
1460
+ }
1461
+ const comSpec = String(env.ComSpec ?? env.COMSPEC ?? process.env.ComSpec ?? process.env.COMSPEC ?? "").trim();
1462
+ if (comSpec) {
1463
+ env.ComSpec = comSpec;
1464
+ env.COMSPEC = comSpec;
1465
+ }
1466
+ }
1467
+ return env;
1468
+ }
1469
+ async function resolveCommandPath(command, cwd, env) {
1470
+ const lookupCommands = process.platform === "win32" ? [
1471
+ ["where.exe", command],
1472
+ ["where", command]
1473
+ ] : [["which", command]];
1474
+ for (const lookup of lookupCommands) {
1475
+ try {
1476
+ const proc = Bun.spawn(lookup, {
1477
+ cwd,
1478
+ env,
1479
+ stdout: "pipe",
1480
+ stderr: "ignore"
1481
+ });
1482
+ const [stdout, exitCode] = await Promise.all([
1483
+ new Response(proc.stdout).text(),
1484
+ proc.exited
1485
+ ]);
1486
+ if (exitCode !== 0)
1487
+ continue;
1488
+ const resolved = stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
1489
+ if (resolved)
1490
+ return resolved;
1491
+ } catch {}
1492
+ }
1493
+ return null;
1494
+ }
1021
1495
  function timestampFileToken() {
1022
1496
  return new Date().toISOString().replace(/[:.]/g, "-");
1023
1497
  }
1024
1498
  function readLogTail(logPath, maxLines = 40) {
1025
- if (!existsSync2(logPath))
1499
+ if (!existsSync3(logPath))
1026
1500
  return "";
1027
- const raw = readFileSync2(logPath, "utf8");
1501
+ const raw = readFileSync3(logPath, "utf8");
1028
1502
  const lines = raw.split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.length > 0);
1029
1503
  if (lines.length === 0)
1030
1504
  return "";
@@ -1058,7 +1532,7 @@ async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
1058
1532
  runtimeBinaries.sourceControlManager
1059
1533
  ];
1060
1534
  for (const binaryPath of requiredAssets) {
1061
- if (existsSync2(binaryPath))
1535
+ if (existsSync3(binaryPath))
1062
1536
  continue;
1063
1537
  const assetName = binaryPath.split(/[\\/]/).pop() || "";
1064
1538
  await downloadBinaryAsset(runtimeTag, assetName, binaryPath);
@@ -1125,11 +1599,18 @@ function spawnRuntimeService(name, command, cwd, env, logPath) {
1125
1599
  });
1126
1600
  return service;
1127
1601
  }
1602
+ function buildServiceStopCommand(pid, platform = process.platform) {
1603
+ if (platform === "win32" && typeof pid === "number" && pid > 0) {
1604
+ return ["taskkill", "/PID", String(pid), "/T", "/F"];
1605
+ }
1606
+ return null;
1607
+ }
1128
1608
  function stopRuntimeServices(services) {
1129
1609
  for (const service of services) {
1130
1610
  try {
1131
- if (process.platform === "win32" && typeof service.proc.pid === "number" && service.proc.pid > 0) {
1132
- Bun.spawnSync(["taskkill", "/PID", String(service.proc.pid), "/T", "/F"], {
1611
+ const stopCommand = buildServiceStopCommand(service.proc.pid, process.platform);
1612
+ if (stopCommand) {
1613
+ Bun.spawnSync(stopCommand, {
1133
1614
  stdin: "ignore",
1134
1615
  stdout: "ignore",
1135
1616
  stderr: "ignore"
@@ -1140,6 +1621,24 @@ function stopRuntimeServices(services) {
1140
1621
  } catch {}
1141
1622
  }
1142
1623
  }
1624
+ function isOptionalEmbeddedService(name) {
1625
+ return name === "source_control_manager";
1626
+ }
1627
+ async function canSpawnCommand(command, cwd, env) {
1628
+ try {
1629
+ const proc = Bun.spawn(command, {
1630
+ cwd,
1631
+ env,
1632
+ stdin: "ignore",
1633
+ stdout: "ignore",
1634
+ stderr: "ignore"
1635
+ });
1636
+ const exitCode = await proc.exited;
1637
+ return exitCode === 0;
1638
+ } catch {
1639
+ return false;
1640
+ }
1641
+ }
1143
1642
  async function repoHasRemote(repoRoot, remote) {
1144
1643
  const normalizedRemote = remote.trim();
1145
1644
  if (!normalizedRemote)
@@ -1193,17 +1692,26 @@ async function probeLocalBuddy(localAgentUrl, authToken) {
1193
1692
  return await fetchJsonWithTimeout(`${localAgentUrl}/healthz`, { headers: authHeaders(authToken) }, LOCALBUDDY_TIMEOUT_MS);
1194
1693
  }
1195
1694
  async function autoStartRuntimeServices(opts) {
1196
- const runtimeRoot = resolve2(opts.runtimeRoot || process.env.PUSHPALS_RUNTIME_ROOT || resolveDefaultRuntimeRoot());
1197
- const runtimeTag = await resolveRuntimeReleaseTag(opts.runtimeTag);
1198
- const runtimeBinaries = await ensureRuntimeBinaries(runtimeRoot, runtimeTag);
1199
- await ensureRuntimeAssets(runtimeRoot, runtimeTag);
1695
+ const { runtimePreflight } = opts.preparedRuntime;
1696
+ const runtimeRoot = opts.preparedRuntime.runtimeRoot;
1697
+ const runtimeTag = opts.preparedRuntime.runtimeTag || await resolveRuntimeReleaseTag(opts.requestedRuntimeTag);
1200
1698
  console.log(`[pushpals] LocalBuddy unavailable. Auto-starting runtime for repo: ${opts.repoRoot}`);
1201
1699
  console.log(`[pushpals] runtimeRoot=${runtimeRoot}`);
1202
1700
  console.log(`[pushpals] runtimeTag=${runtimeTag}`);
1701
+ if (!runtimePreflight.ok) {
1702
+ throw new Error("Embedded runtime preflight failed.");
1703
+ }
1704
+ await ensureRuntimeAssets(runtimeRoot, runtimeTag);
1705
+ const runtimeBinaries = await ensureRuntimeBinaries(runtimeRoot, runtimeTag);
1203
1706
  const runtimeEnv = buildEmbeddedRuntimeEnv(process.env, {
1204
1707
  repoRoot: opts.repoRoot,
1205
- runtimeRoot
1708
+ runtimeRoot,
1709
+ useRuntimeConfig: opts.preparedRuntime.preflightUsesEmbeddedRuntime
1206
1710
  });
1711
+ const resolvedGitBinary = await resolveCommandPath("git", opts.repoRoot, normalizeChildProcessEnv(process.env));
1712
+ if (resolvedGitBinary) {
1713
+ runtimeEnv.PUSHPALS_GIT_BIN = resolvedGitBinary;
1714
+ }
1207
1715
  const services = [];
1208
1716
  const runToken = timestampFileToken();
1209
1717
  const logDir = join2(runtimeRoot, "logs", "bootstrap");
@@ -1252,13 +1760,24 @@ ${tail}` : ""}`);
1252
1760
  console.log(`[pushpals] remotebuddy log: ${remotebuddyService.logPath}`);
1253
1761
  const scmHealthy = await probeSourceControlManager(opts.sourceControlManagerPort);
1254
1762
  const scmRemoteAvailable = await repoHasRemote(opts.repoRoot, opts.sourceControlManagerRemote);
1763
+ const gitProbeCommand = typeof runtimeEnv.PUSHPALS_GIT_BIN === "string" && runtimeEnv.PUSHPALS_GIT_BIN.trim() ? [runtimeEnv.PUSHPALS_GIT_BIN.trim(), "--version"] : ["git", "--version"];
1764
+ const gitAvailableForScm = await canSpawnCommand(gitProbeCommand, opts.repoRoot, runtimeEnv);
1255
1765
  if (!scmHealthy && scmRemoteAvailable) {
1256
- console.log("[pushpals] Starting embedded SourceControlManager...");
1257
- const sourceControlManagerService = spawnRuntimeService("source_control_manager", [runtimeBinaries.sourceControlManager, "--skip-clean-check"], opts.repoRoot, runtimeEnv, logPathFor("source_control_manager"));
1258
- services.push(sourceControlManagerService);
1259
- console.log(`[pushpals] source_control_manager log: ${sourceControlManagerService.logPath}`);
1766
+ if (!gitAvailableForScm) {
1767
+ console.warn("[pushpals] Git is not available to embedded SourceControlManager; skipping SCM startup.");
1768
+ } else {
1769
+ if (runtimeEnv.PUSHPALS_GIT_BIN) {
1770
+ console.log(`[pushpals] Embedded SourceControlManager git=${runtimeEnv.PUSHPALS_GIT_BIN}`);
1771
+ }
1772
+ console.log("[pushpals] Starting embedded SourceControlManager...");
1773
+ const sourceControlManagerService = spawnRuntimeService("source_control_manager", [runtimeBinaries.sourceControlManager, "--skip-clean-check"], opts.repoRoot, runtimeEnv, logPathFor("source_control_manager"));
1774
+ services.push(sourceControlManagerService);
1775
+ console.log(`[pushpals] source_control_manager log: ${sourceControlManagerService.logPath}`);
1776
+ }
1260
1777
  } else if (!scmRemoteAvailable) {
1261
1778
  console.log(`[pushpals] Repo has no git remote "${opts.sourceControlManagerRemote}"; skipping embedded SourceControlManager.`);
1779
+ } else if (!gitAvailableForScm) {
1780
+ console.warn("[pushpals] Git is not available to embedded SourceControlManager; skipping SCM startup.");
1262
1781
  } else {
1263
1782
  console.log("[pushpals] SourceControlManager already healthy; skipping embedded start.");
1264
1783
  }
@@ -1267,7 +1786,7 @@ ${tail}` : ""}`);
1267
1786
  for (let i = services.length - 1;i >= 0; i--) {
1268
1787
  const service = services[i];
1269
1788
  if (service.exited) {
1270
- if (service.name === "source_control_manager") {
1789
+ if (isOptionalEmbeddedService(service.name)) {
1271
1790
  console.warn(`[pushpals] Embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}); continuing without SCM.`);
1272
1791
  const tail2 = readLogTail(service.logPath);
1273
1792
  if (tail2) {
@@ -1288,9 +1807,20 @@ ${tail}` : ""}`);
1288
1807
  if (health?.ok) {
1289
1808
  const stabilityDeadline = Date.now() + DEFAULT_SERVICE_STABILITY_GRACE_MS;
1290
1809
  while (Date.now() < stabilityDeadline) {
1291
- for (const service of services) {
1810
+ for (let i = services.length - 1;i >= 0; i--) {
1811
+ const service = services[i];
1292
1812
  if (!service.exited)
1293
1813
  continue;
1814
+ if (isOptionalEmbeddedService(service.name)) {
1815
+ const tail2 = readLogTail(service.logPath);
1816
+ console.warn(`[pushpals] Embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}); continuing without SCM.`);
1817
+ if (tail2) {
1818
+ console.warn(`[pushpals] ${service.name} log tail:
1819
+ ${tail2}`);
1820
+ }
1821
+ services.splice(i, 1);
1822
+ continue;
1823
+ }
1294
1824
  const tail = readLogTail(service.logPath);
1295
1825
  stopRuntimeServices(services);
1296
1826
  throw new Error(`Embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}). ` + `See ${service.logPath}${tail ? `
@@ -1308,10 +1838,10 @@ ${tail}` : ""}`);
1308
1838
  throw new Error(`Timed out waiting for LocalBuddy at ${opts.localAgentUrl} after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
1309
1839
  }
1310
1840
  function readCliState(pathValue) {
1311
- if (!existsSync2(pathValue))
1841
+ if (!existsSync3(pathValue))
1312
1842
  return {};
1313
1843
  try {
1314
- const raw = readFileSync2(pathValue, "utf8");
1844
+ const raw = readFileSync3(pathValue, "utf8");
1315
1845
  const parsed = JSON.parse(raw);
1316
1846
  if (!parsed || typeof parsed !== "object")
1317
1847
  return {};
@@ -1737,15 +2267,17 @@ async function runSessionStream(serverUrl, sessionId, authToken, print, signal)
1737
2267
  }
1738
2268
  }
1739
2269
  }
1740
- async function openMonitoringHub(url) {
1741
- let cmd = null;
1742
- if (process.platform === "win32") {
1743
- cmd = ["cmd", "/c", "start", "", url];
1744
- } else if (process.platform === "darwin") {
1745
- cmd = ["open", url];
1746
- } else {
1747
- cmd = ["xdg-open", url];
2270
+ function buildOpenMonitoringHubCommand(url, platform = process.platform) {
2271
+ if (platform === "win32") {
2272
+ return ["cmd", "/c", "start", "", url];
2273
+ }
2274
+ if (platform === "darwin") {
2275
+ return ["open", url];
1748
2276
  }
2277
+ return ["xdg-open", url];
2278
+ }
2279
+ async function openMonitoringHub(url) {
2280
+ const cmd = buildOpenMonitoringHubCommand(url, process.platform);
1749
2281
  const proc = Bun.spawn(cmd, {
1750
2282
  stdin: "ignore",
1751
2283
  stdout: "ignore",
@@ -1760,7 +2292,6 @@ async function main() {
1760
2292
  const parsed = parseArgs(argv);
1761
2293
  if (!parsed)
1762
2294
  return;
1763
- const config = loadPushPalsConfig();
1764
2295
  const cwd = process.cwd();
1765
2296
  const repoRoot = await resolveCurrentGitRepoRoot(cwd);
1766
2297
  if (!repoRoot) {
@@ -1769,6 +2300,25 @@ async function main() {
1769
2300
  console.error("[pushpals] Run from a repo directory, or initialize one with `git init`.");
1770
2301
  process.exit(1);
1771
2302
  }
2303
+ const preparedRuntime = await prepareCliRuntime({
2304
+ repoRoot,
2305
+ runtimeRoot: parsed.runtimeRoot,
2306
+ runtimeTag: parsed.runtimeTag
2307
+ });
2308
+ console.log("[pushpals] Running runtime preflight...");
2309
+ console.log(`[pushpals] runtimeRoot=${preparedRuntime.runtimeRoot}`);
2310
+ if (preparedRuntime.runtimeTag) {
2311
+ console.log(`[pushpals] runtimeTag=${preparedRuntime.runtimeTag}`);
2312
+ } else if (!preparedRuntime.preflightUsesEmbeddedRuntime) {
2313
+ console.log("[pushpals] runtimeTag=(deferred; using repo config for preflight)");
2314
+ } else {
2315
+ console.log("[pushpals] runtimeTag=(deferred until embedded auto-start is needed)");
2316
+ }
2317
+ emitCliRuntimePreflight(preparedRuntime.runtimePreflight);
2318
+ if (!preparedRuntime.runtimePreflight.ok) {
2319
+ process.exit(1);
2320
+ }
2321
+ const config = preparedRuntime.runtimePreflight.config;
1772
2322
  const serverUrl = normalizeUrl(parsed.serverUrl ?? process.env.PUSHPALS_SERVER_URL, config.server.url);
1773
2323
  const localAgentUrl = normalizeUrl(parsed.localAgentUrl ?? process.env.EXPO_PUBLIC_LOCAL_AGENT_URL, config.client.localAgentUrl);
1774
2324
  const sessionId = String(parsed.sessionId ?? process.env.PUSHPALS_SESSION_ID ?? config.sessionId).trim();
@@ -1790,8 +2340,8 @@ async function main() {
1790
2340
  sourceControlManagerPort: config.sourceControlManager.port,
1791
2341
  sourceControlManagerRemote: config.sourceControlManager.remote,
1792
2342
  authToken,
1793
- runtimeRoot: parsed.runtimeRoot,
1794
- runtimeTag: parsed.runtimeTag
2343
+ preparedRuntime,
2344
+ requestedRuntimeTag: parsed.runtimeTag
1795
2345
  });
1796
2346
  health = await probeLocalBuddy(localAgentUrl, authToken);
1797
2347
  } catch (err) {
@@ -1808,7 +2358,7 @@ async function main() {
1808
2358
  }
1809
2359
  process.exit(1);
1810
2360
  }
1811
- const localBuddyRepo = health.repo ? resolve2(health.repo) : "";
2361
+ const localBuddyRepo = health.repo ? resolve3(health.repo) : "";
1812
2362
  if (!localBuddyRepo) {
1813
2363
  stopAutoStartedServices();
1814
2364
  console.error("[pushpals] LocalBuddy health response did not include repo path.");
@@ -1826,7 +2376,7 @@ async function main() {
1826
2376
  if (sessionId && sessionId !== localBuddySessionId) {
1827
2377
  console.warn(`[pushpals] Requested sessionId=${sessionId}, but LocalBuddy is currently attached to sessionId=${localBuddySessionId}.`);
1828
2378
  }
1829
- const statePath = resolve2(repoRoot, ".git", "pushpals-cli-state.json");
2379
+ const statePath = resolve3(repoRoot, ".git", "pushpals-cli-state.json");
1830
2380
  const saved = readCliState(statePath);
1831
2381
  const preferredHubUrl = normalizeUrl(parsed.monitoringHubUrl ?? process.env.PUSHPALS_MONITOR_URL ?? saved.monitoringHubUrl ?? "");
1832
2382
  const monitorPort = parsePositiveInt(process.env.PUSHPALS_CLIENT_PORT, DEFAULT_MONITOR_PORT);
@@ -1951,6 +2501,12 @@ if (import.meta.main) {
1951
2501
  }
1952
2502
  export {
1953
2503
  startEmbeddedMonitoringHub,
2504
+ resolveCommandPath,
2505
+ resolveBundledRuntimeAssetSource,
2506
+ prepareCliRuntime,
2507
+ normalizeChildProcessEnv,
2508
+ buildServiceStopCommand,
2509
+ buildOpenMonitoringHubCommand,
1954
2510
  buildEmbeddedRuntimeEnv,
1955
2511
  buildEmbeddedMonitoringHubHtml
1956
2512
  };