@pushpalsdev/cli 1.0.6 → 1.0.7

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 +483 -37
  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,77 @@ 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 useRuntimeConfig = opts.useRuntimeConfig !== false;
1011
1429
  return {
1012
1430
  ...baseEnv,
1013
1431
  PUSHPALS_REPO_ROOT_OVERRIDE: opts.repoRoot,
1014
1432
  PUSHPALS_PROJECT_ROOT_OVERRIDE: opts.repoRoot,
1015
- PUSHPALS_CONFIG_DIR_OVERRIDE: join2(opts.runtimeRoot, "configs"),
1016
- PUSHPALS_PROMPTS_ROOT_OVERRIDE: opts.runtimeRoot,
1017
- PUSHPALS_PROTOCOL_SCHEMAS_DIR: join2(opts.runtimeRoot, "protocol", "schemas"),
1018
- REMOTEBUDDY_AUTONOMY_ENABLED: String(baseEnv.REMOTEBUDDY_AUTONOMY_ENABLED ?? "").trim() || "false"
1433
+ ...useRuntimeConfig ? {
1434
+ PUSHPALS_CONFIG_DIR_OVERRIDE: join2(opts.runtimeRoot, "configs"),
1435
+ PUSHPALS_PROMPTS_ROOT_OVERRIDE: opts.runtimeRoot
1436
+ } : {
1437
+ PUSHPALS_PROMPTS_ROOT_OVERRIDE: opts.repoRoot
1438
+ },
1439
+ PUSHPALS_PROTOCOL_SCHEMAS_DIR: join2(opts.runtimeRoot, "protocol", "schemas")
1019
1440
  };
1020
1441
  }
1021
1442
  function timestampFileToken() {
1022
1443
  return new Date().toISOString().replace(/[:.]/g, "-");
1023
1444
  }
1024
1445
  function readLogTail(logPath, maxLines = 40) {
1025
- if (!existsSync2(logPath))
1446
+ if (!existsSync3(logPath))
1026
1447
  return "";
1027
- const raw = readFileSync2(logPath, "utf8");
1448
+ const raw = readFileSync3(logPath, "utf8");
1028
1449
  const lines = raw.split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.length > 0);
1029
1450
  if (lines.length === 0)
1030
1451
  return "";
@@ -1058,7 +1479,7 @@ async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
1058
1479
  runtimeBinaries.sourceControlManager
1059
1480
  ];
1060
1481
  for (const binaryPath of requiredAssets) {
1061
- if (existsSync2(binaryPath))
1482
+ if (existsSync3(binaryPath))
1062
1483
  continue;
1063
1484
  const assetName = binaryPath.split(/[\\/]/).pop() || "";
1064
1485
  await downloadBinaryAsset(runtimeTag, assetName, binaryPath);
@@ -1193,16 +1614,21 @@ async function probeLocalBuddy(localAgentUrl, authToken) {
1193
1614
  return await fetchJsonWithTimeout(`${localAgentUrl}/healthz`, { headers: authHeaders(authToken) }, LOCALBUDDY_TIMEOUT_MS);
1194
1615
  }
1195
1616
  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);
1617
+ const { runtimePreflight } = opts.preparedRuntime;
1618
+ const runtimeRoot = opts.preparedRuntime.runtimeRoot;
1619
+ const runtimeTag = opts.preparedRuntime.runtimeTag || await resolveRuntimeReleaseTag(opts.requestedRuntimeTag);
1200
1620
  console.log(`[pushpals] LocalBuddy unavailable. Auto-starting runtime for repo: ${opts.repoRoot}`);
1201
1621
  console.log(`[pushpals] runtimeRoot=${runtimeRoot}`);
1202
1622
  console.log(`[pushpals] runtimeTag=${runtimeTag}`);
1623
+ if (!runtimePreflight.ok) {
1624
+ throw new Error("Embedded runtime preflight failed.");
1625
+ }
1626
+ await ensureRuntimeAssets(runtimeRoot, runtimeTag);
1627
+ const runtimeBinaries = await ensureRuntimeBinaries(runtimeRoot, runtimeTag);
1203
1628
  const runtimeEnv = buildEmbeddedRuntimeEnv(process.env, {
1204
1629
  repoRoot: opts.repoRoot,
1205
- runtimeRoot
1630
+ runtimeRoot,
1631
+ useRuntimeConfig: opts.preparedRuntime.preflightUsesEmbeddedRuntime
1206
1632
  });
1207
1633
  const services = [];
1208
1634
  const runToken = timestampFileToken();
@@ -1308,10 +1734,10 @@ ${tail}` : ""}`);
1308
1734
  throw new Error(`Timed out waiting for LocalBuddy at ${opts.localAgentUrl} after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
1309
1735
  }
1310
1736
  function readCliState(pathValue) {
1311
- if (!existsSync2(pathValue))
1737
+ if (!existsSync3(pathValue))
1312
1738
  return {};
1313
1739
  try {
1314
- const raw = readFileSync2(pathValue, "utf8");
1740
+ const raw = readFileSync3(pathValue, "utf8");
1315
1741
  const parsed = JSON.parse(raw);
1316
1742
  if (!parsed || typeof parsed !== "object")
1317
1743
  return {};
@@ -1760,7 +2186,6 @@ async function main() {
1760
2186
  const parsed = parseArgs(argv);
1761
2187
  if (!parsed)
1762
2188
  return;
1763
- const config = loadPushPalsConfig();
1764
2189
  const cwd = process.cwd();
1765
2190
  const repoRoot = await resolveCurrentGitRepoRoot(cwd);
1766
2191
  if (!repoRoot) {
@@ -1769,6 +2194,25 @@ async function main() {
1769
2194
  console.error("[pushpals] Run from a repo directory, or initialize one with `git init`.");
1770
2195
  process.exit(1);
1771
2196
  }
2197
+ const preparedRuntime = await prepareCliRuntime({
2198
+ repoRoot,
2199
+ runtimeRoot: parsed.runtimeRoot,
2200
+ runtimeTag: parsed.runtimeTag
2201
+ });
2202
+ console.log("[pushpals] Running runtime preflight...");
2203
+ console.log(`[pushpals] runtimeRoot=${preparedRuntime.runtimeRoot}`);
2204
+ if (preparedRuntime.runtimeTag) {
2205
+ console.log(`[pushpals] runtimeTag=${preparedRuntime.runtimeTag}`);
2206
+ } else if (!preparedRuntime.preflightUsesEmbeddedRuntime) {
2207
+ console.log("[pushpals] runtimeTag=(deferred; using repo config for preflight)");
2208
+ } else {
2209
+ console.log("[pushpals] runtimeTag=(deferred until embedded auto-start is needed)");
2210
+ }
2211
+ emitCliRuntimePreflight(preparedRuntime.runtimePreflight);
2212
+ if (!preparedRuntime.runtimePreflight.ok) {
2213
+ process.exit(1);
2214
+ }
2215
+ const config = preparedRuntime.runtimePreflight.config;
1772
2216
  const serverUrl = normalizeUrl(parsed.serverUrl ?? process.env.PUSHPALS_SERVER_URL, config.server.url);
1773
2217
  const localAgentUrl = normalizeUrl(parsed.localAgentUrl ?? process.env.EXPO_PUBLIC_LOCAL_AGENT_URL, config.client.localAgentUrl);
1774
2218
  const sessionId = String(parsed.sessionId ?? process.env.PUSHPALS_SESSION_ID ?? config.sessionId).trim();
@@ -1790,8 +2234,8 @@ async function main() {
1790
2234
  sourceControlManagerPort: config.sourceControlManager.port,
1791
2235
  sourceControlManagerRemote: config.sourceControlManager.remote,
1792
2236
  authToken,
1793
- runtimeRoot: parsed.runtimeRoot,
1794
- runtimeTag: parsed.runtimeTag
2237
+ preparedRuntime,
2238
+ requestedRuntimeTag: parsed.runtimeTag
1795
2239
  });
1796
2240
  health = await probeLocalBuddy(localAgentUrl, authToken);
1797
2241
  } catch (err) {
@@ -1808,7 +2252,7 @@ async function main() {
1808
2252
  }
1809
2253
  process.exit(1);
1810
2254
  }
1811
- const localBuddyRepo = health.repo ? resolve2(health.repo) : "";
2255
+ const localBuddyRepo = health.repo ? resolve3(health.repo) : "";
1812
2256
  if (!localBuddyRepo) {
1813
2257
  stopAutoStartedServices();
1814
2258
  console.error("[pushpals] LocalBuddy health response did not include repo path.");
@@ -1826,7 +2270,7 @@ async function main() {
1826
2270
  if (sessionId && sessionId !== localBuddySessionId) {
1827
2271
  console.warn(`[pushpals] Requested sessionId=${sessionId}, but LocalBuddy is currently attached to sessionId=${localBuddySessionId}.`);
1828
2272
  }
1829
- const statePath = resolve2(repoRoot, ".git", "pushpals-cli-state.json");
2273
+ const statePath = resolve3(repoRoot, ".git", "pushpals-cli-state.json");
1830
2274
  const saved = readCliState(statePath);
1831
2275
  const preferredHubUrl = normalizeUrl(parsed.monitoringHubUrl ?? process.env.PUSHPALS_MONITOR_URL ?? saved.monitoringHubUrl ?? "");
1832
2276
  const monitorPort = parsePositiveInt(process.env.PUSHPALS_CLIENT_PORT, DEFAULT_MONITOR_PORT);
@@ -1951,6 +2395,8 @@ if (import.meta.main) {
1951
2395
  }
1952
2396
  export {
1953
2397
  startEmbeddedMonitoringHub,
2398
+ resolveBundledRuntimeAssetSource,
2399
+ prepareCliRuntime,
1954
2400
  buildEmbeddedRuntimeEnv,
1955
2401
  buildEmbeddedMonitoringHubHtml
1956
2402
  };