@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.
- package/dist/pushpals-cli.js +609 -53
- package/package.json +3 -2
- package/runtime/.env.example +73 -0
- package/runtime/configs/backend.toml +79 -0
- package/runtime/configs/default.toml +259 -0
- package/runtime/configs/dev.toml +2 -0
- package/runtime/configs/local.example.toml +124 -0
- package/runtime/prompts/localbuddy/local_quick_reply_json_system_suffix.md +2 -0
- package/runtime/prompts/localbuddy/local_quick_reply_system_prompt.md +10 -0
- package/runtime/prompts/localbuddy/local_quick_reply_user_prompt.md +2 -0
- package/runtime/prompts/localbuddy/localbuddy_planner_git_diff_section.md +3 -0
- package/runtime/prompts/localbuddy/localbuddy_planner_git_status_section.md +3 -0
- package/runtime/prompts/localbuddy/localbuddy_planner_output_contract.md +5 -0
- package/runtime/prompts/localbuddy/localbuddy_planner_user_prompt.md +2 -0
- package/runtime/prompts/localbuddy/localbuddy_system_prompt.md +110 -0
- package/runtime/prompts/remotebuddy/autonomy_ideation_system_prompt.md +60 -0
- package/runtime/prompts/remotebuddy/autonomy_planning_system_prompt.md +5 -0
- package/runtime/prompts/remotebuddy/autonomy_scoring_system_prompt.md +6 -0
- package/runtime/prompts/remotebuddy/codex_adapter_json_requirements.md +1 -0
- package/runtime/prompts/remotebuddy/codex_adapter_json_schema_intro.md +1 -0
- package/runtime/prompts/remotebuddy/codex_adapter_max_tokens_line.md +1 -0
- package/runtime/prompts/remotebuddy/codex_adapter_prompt_template.md +14 -0
- package/runtime/prompts/remotebuddy/context_packer_condensed_history_system_prompt.md +1 -0
- package/runtime/prompts/remotebuddy/context_packer_system_prompt.md +1 -0
- package/runtime/prompts/remotebuddy/context_packer_user_prompt.md +11 -0
- package/runtime/prompts/remotebuddy/fallback_file_system_prompt.md +1 -0
- package/runtime/prompts/remotebuddy/fallback_file_user_prompt.md +4 -0
- package/runtime/prompts/remotebuddy/planner_post_system_prompt.md +2 -0
- package/runtime/prompts/remotebuddy/planner_repair_suffix_prompt.md +1 -0
- package/runtime/prompts/remotebuddy/planner_repair_user_prompt.md +7 -0
- package/runtime/prompts/remotebuddy/remotebuddy_system_prompt.md +109 -0
- package/runtime/prompts/review_agent/fix_job_intro_line.md +1 -0
- package/runtime/prompts/review_agent/merge_conflict_context_intro_line.md +1 -0
- package/runtime/prompts/review_agent/merge_conflict_instruction.md +4 -0
- package/runtime/prompts/review_agent/review_prompt_template.md +18 -0
- package/runtime/prompts/review_agent/reviewer.md +39 -0
- package/runtime/prompts/shared/post_system_prompt.md +62 -0
- package/runtime/prompts/workerpals/codex_quality_critic_instruction_prompt.md +14 -0
- package/runtime/prompts/workerpals/commit_message_prompt.md +36 -0
- package/runtime/prompts/workerpals/commit_message_user_prompt.md +7 -0
- package/runtime/prompts/workerpals/miniswe_broker_system_prompt.md +33 -0
- package/runtime/prompts/workerpals/miniswe_broker_task_prompt.md +5 -0
- package/runtime/prompts/workerpals/miniswe_completion_requirement.md +1 -0
- package/runtime/prompts/workerpals/miniswe_context_compaction_retry_prompt.md +1 -0
- package/runtime/prompts/workerpals/miniswe_explicit_targets_block.md +2 -0
- package/runtime/prompts/workerpals/miniswe_recovery_guidance_base.md +4 -0
- package/runtime/prompts/workerpals/miniswe_recovery_guidance_blocker_line.md +1 -0
- package/runtime/prompts/workerpals/miniswe_strict_tool_use_guidance.md +6 -0
- package/runtime/prompts/workerpals/miniswe_supplemental_guidance_section.md +2 -0
- package/runtime/prompts/workerpals/miniswe_timeout_note.md +1 -0
- package/runtime/prompts/workerpals/miniswe_toolcall_retry_guidance.md +1 -0
- package/runtime/prompts/workerpals/openai_codex_default_system_prompt.md +4 -0
- package/runtime/prompts/workerpals/openai_codex_instruction_wrapper.md +5 -0
- package/runtime/prompts/workerpals/openai_codex_runtime_policy_appendix.md +5 -0
- package/runtime/prompts/workerpals/openai_codex_supplemental_guidance_section.md +2 -0
- package/runtime/prompts/workerpals/openai_codex_task_execute_system_prompt.md +12 -0
- package/runtime/prompts/workerpals/openhands_minimal_security_policy.j2 +8 -0
- package/runtime/prompts/workerpals/openhands_minimal_system_prompt.j2 +20 -0
- package/runtime/prompts/workerpals/openhands_strict_tool_use_message.md +1 -0
- package/runtime/prompts/workerpals/openhands_supplemental_guidance_message.md +2 -0
- package/runtime/prompts/workerpals/openhands_task_execute_fallback_system_prompt.md +1 -0
- package/runtime/prompts/workerpals/openhands_task_execute_system_prompt.md +21 -0
- package/runtime/prompts/workerpals/openhands_task_user_prompt.md +6 -0
- package/runtime/prompts/workerpals/openhands_timeout_note.md +1 -0
- package/runtime/prompts/workerpals/pr_description.md +42 -0
- package/runtime/prompts/workerpals/task_quality_critic_system_prompt.md +9 -0
- package/runtime/prompts/workerpals/task_quality_critic_user_prompt.md +17 -0
- package/runtime/prompts/workerpals/workerpals_system_prompt.md +115 -0
- package/runtime/protocol/schemas/approvals.schema.json +6 -0
- package/runtime/protocol/schemas/envelope.schema.json +96 -0
- package/runtime/protocol/schemas/events.schema.json +679 -0
- package/runtime/protocol/schemas/http.schema.json +50 -0
- package/runtime/vision.example.md +191 -0
package/dist/pushpals-cli.js
CHANGED
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// ../../scripts/pushpals-cli.ts
|
|
5
|
-
import { appendFileSync, chmodSync, cpSync, existsSync as
|
|
6
|
-
import { dirname, join as join2, resolve as
|
|
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 =
|
|
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
|
|
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
|
|
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 (
|
|
1282
|
+
if (existsSync3(pathValue))
|
|
941
1283
|
return;
|
|
942
1284
|
mkdirSync(dirname(pathValue), { recursive: true });
|
|
943
1285
|
writeFileSync(pathValue, text, "utf8");
|
|
944
1286
|
}
|
|
945
|
-
function
|
|
946
|
-
|
|
947
|
-
|
|
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
|
-
|
|
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 =
|
|
1361
|
+
const currentTag = existsSync3(markerPath) ? readFileSync3(markerPath, "utf8").trim() : "";
|
|
982
1362
|
const protocolSchemasDir = join2(runtimeRoot, "protocol", "schemas");
|
|
983
|
-
const hasProtocolSchemas =
|
|
984
|
-
const hasAssets =
|
|
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 =
|
|
988
|
-
const hasAssetsAfterCopy =
|
|
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 (
|
|
999
|
-
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"),
|
|
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
|
-
...
|
|
1431
|
+
...env,
|
|
1013
1432
|
PUSHPALS_REPO_ROOT_OVERRIDE: opts.repoRoot,
|
|
1014
1433
|
PUSHPALS_PROJECT_ROOT_OVERRIDE: opts.repoRoot,
|
|
1015
|
-
|
|
1016
|
-
|
|
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
|
-
|
|
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 (!
|
|
1499
|
+
if (!existsSync3(logPath))
|
|
1026
1500
|
return "";
|
|
1027
|
-
const raw =
|
|
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 (
|
|
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
|
-
|
|
1132
|
-
|
|
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
|
|
1197
|
-
const
|
|
1198
|
-
const
|
|
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
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
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
|
|
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 (
|
|
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 (!
|
|
1841
|
+
if (!existsSync3(pathValue))
|
|
1312
1842
|
return {};
|
|
1313
1843
|
try {
|
|
1314
|
-
const raw =
|
|
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
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
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
|
-
|
|
1794
|
-
|
|
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 ?
|
|
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 =
|
|
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
|
};
|