jorgex-stack 1.2.1 → 1.2.3
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/README.md +22 -2
- package/dist/cli.js +1008 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# JorgeX Stack
|
|
2
2
|
|
|
3
|
-
Portable multi-agent harness: one configuration source — 15 agents, 17 skills, hooks, persistent memory ([Engram](https://github.com/Gentleman-Programming/engram)), MCPs, and system prompt — installable with one command in **Claude Code**, **Codex CLI**, and **
|
|
3
|
+
Portable multi-agent harness: one configuration source — 15 agents, 17 skills, hooks, persistent memory ([Engram](https://github.com/Gentleman-Programming/engram)), MCPs, and system prompt — installable with one command in **Claude Code**, **Codex CLI**, **OpenCode**, and **Pi**.
|
|
4
4
|
|
|
5
5
|
> Inspired by [gentle-ai](https://github.com/Gentleman-Programming/gentle-ai), rebuilt for the JorgeX stack.
|
|
6
6
|
|
|
@@ -75,7 +75,7 @@ Flags:
|
|
|
75
75
|
pnpm dlx jorgex-stack install --mode programmatic --subagent-concurrency serial --yes
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
This installs into all detected runtimes. To be explicit, add `--agents opencode,claude-code,codex` or a comma-separated subset. Always pass `--mode programmatic`; without `--mode`, `--yes` and non-TTY installs default to `human`.
|
|
78
|
+
This installs into all detected runtimes. To be explicit, add `--agents opencode,claude-code,codex,pi` or a comma-separated subset. Always pass `--mode programmatic`; without `--mode`, `--yes` and non-TTY installs default to `human`.
|
|
79
79
|
|
|
80
80
|
OpenCode also requires an existing selection in `~/.jorgex-stack/model-map.json`; run `pnpm dlx jorgex-stack models --agents opencode` interactively once before a headless install.
|
|
81
81
|
|
|
@@ -95,6 +95,26 @@ Programmatic mode does **not** provide:
|
|
|
95
95
|
- Any special stdout streaming guarantee — the runtime's normal output rules apply.
|
|
96
96
|
- Telemetry, JSONL streams, or runtime token-budget enforcement.
|
|
97
97
|
|
|
98
|
+
### Pi runtime
|
|
99
|
+
|
|
100
|
+
Pi is package-managed rather than file-managed. Stack supports the exact published package **`jorgex-pi@0.2.2`** and keeps Pi out of the adapter/component manifest and model map.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pnpm dlx jorgex-stack install --agents pi
|
|
104
|
+
pnpm dlx jorgex-stack doctor --agents pi
|
|
105
|
+
pnpm dlx jorgex-stack models --agents pi
|
|
106
|
+
pnpm dlx jorgex-stack sync --agents pi
|
|
107
|
+
pnpm dlx jorgex-stack uninstall --agents pi
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Stack downloads the frozen registry tarball, verifies its exact size plus SHA-256/SHA-512, backs up Pi's `settings.json`, and only then asks Pi to install that local file. Pi's own package-manager invocation is the narrow runtime exception to the repository's pnpm-only rule; the Stack lifecycle never launches npm directly. The managed Pi package entry is the exact source object `{ "source": "npm:jorgex-pi@0.2.2", "skills": [] }`: Pi discovers the canonical shared skills from `~/.agents/skills`, so the package copy is disabled and does not create duplicate skill loading. A scope-bound receipt under `~/.jorgex-stack/pi-receipt.json` records ownership only after the package runner reports a healthy install and stores the verified Engram executable as `engram.binary`, using the schema v1 consumed by `jorgex-pi@0.2.2`. Receipts created before the Engram binding existed require deliberate removal with the previous Stack release followed by reinstall; they are never adopted automatically. Manual, duplicate, divergent, partial, corrupt, copied-to-another-scope, or unknown-history state fails closed and is never adopted or removed silently.
|
|
111
|
+
|
|
112
|
+
Engram remains mandatory and user-owned. An existing binary is preserved. Interactive install may offer the existing native `brew`/`go`/release channel with a default-No confirmation; `--yes` and non-TTY installs fail with a remedy when Engram is absent. No Pi lifecycle operation updates or deletes the Engram database or memories. Under `--target-dir`, Stack accepts only `<target>/bin/engram`, isolates Pi/Home/XDG/AppData/temp/npm-cache paths inside the target, and never consults the host Engram or Pi configuration.
|
|
113
|
+
|
|
114
|
+
The 24-hour npm maturity rule applies to the managed adoption boundary: development and PR validation may start against the exact published artifact, but merging the adoption PR, publishing that Stack adoption, and running the real managed installation wait until the package has been public for at least 24 hours unless Jorge explicitly documents an exception in the PR.
|
|
115
|
+
|
|
116
|
+
`update --agents pi` only runs the Pi package lifecycle; it does not enter the global Stack updater. `update --check --agents pi` is a read-only Pi doctor. Uninstall runs package cleanup, backs up Pi's settings before removal, removes only the exact receipt-owned package after verifying absence, and preserves all companion/user state. Full behavior, failure states and troubleshooting are in [docs/references/pi-runtime.md](docs/references/pi-runtime.md).
|
|
117
|
+
|
|
98
118
|
### Browser automation
|
|
99
119
|
|
|
100
120
|
Browser automation is opt-in and explicit. The legacy `agent-browser` integration has been removed; rely on the two surfaces below.
|
package/dist/cli.js
CHANGED
|
@@ -3713,6 +3713,883 @@ function readPackageMetadata() {
|
|
|
3713
3713
|
return { name, version };
|
|
3714
3714
|
}
|
|
3715
3715
|
|
|
3716
|
+
// src/lib/pi-runtime.ts
|
|
3717
|
+
import os5 from "os";
|
|
3718
|
+
import path30 from "path";
|
|
3719
|
+
import fs22 from "fs";
|
|
3720
|
+
import { spawnSync } from "child_process";
|
|
3721
|
+
import { createHash } from "crypto";
|
|
3722
|
+
|
|
3723
|
+
// src/lib/pi-package-lifecycle.ts
|
|
3724
|
+
import path29 from "path";
|
|
3725
|
+
var REQUIRED_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
3726
|
+
"foundation-contract-v1",
|
|
3727
|
+
"runner-json-v1"
|
|
3728
|
+
]);
|
|
3729
|
+
function sameRecord(left, right) {
|
|
3730
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
3731
|
+
}
|
|
3732
|
+
function ownership(receipt) {
|
|
3733
|
+
return { receipt, adapters: false, manifest: false, modelMap: false };
|
|
3734
|
+
}
|
|
3735
|
+
function blocked(input, reason) {
|
|
3736
|
+
return {
|
|
3737
|
+
kind: "blocked",
|
|
3738
|
+
reason,
|
|
3739
|
+
receiptPath: input.scope.receiptPath,
|
|
3740
|
+
ownership: ownership(input.receiptJson !== null)
|
|
3741
|
+
};
|
|
3742
|
+
}
|
|
3743
|
+
function packageSource(entry) {
|
|
3744
|
+
if (typeof entry === "string") return entry;
|
|
3745
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return null;
|
|
3746
|
+
const source = Reflect.get(entry, "source");
|
|
3747
|
+
return typeof source === "string" ? source : null;
|
|
3748
|
+
}
|
|
3749
|
+
function isJorgeXPiSource(source) {
|
|
3750
|
+
return source.includes("jorgex-pi");
|
|
3751
|
+
}
|
|
3752
|
+
function parsePackageSources(settingsJson) {
|
|
3753
|
+
try {
|
|
3754
|
+
const parsed = JSON.parse(settingsJson);
|
|
3755
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3756
|
+
const packages = Reflect.get(parsed, "packages");
|
|
3757
|
+
if (!Array.isArray(packages)) return null;
|
|
3758
|
+
const sources = packages.map((entry) => ({ entry, source: packageSource(entry) }));
|
|
3759
|
+
return sources.every((value) => value.source !== null) ? sources : null;
|
|
3760
|
+
} catch {
|
|
3761
|
+
return null;
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
function isExactManagedPackage(entry, source) {
|
|
3765
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return false;
|
|
3766
|
+
const keys = Object.keys(entry);
|
|
3767
|
+
const skills = Reflect.get(entry, "skills");
|
|
3768
|
+
return keys.length === 2 && keys.includes("source") && keys.includes("skills") && Reflect.get(entry, "source") === source && Array.isArray(skills) && skills.length === 0;
|
|
3769
|
+
}
|
|
3770
|
+
function expectedReceipt(candidate, state, scope, engramBin) {
|
|
3771
|
+
return {
|
|
3772
|
+
schemaVersion: 1,
|
|
3773
|
+
state,
|
|
3774
|
+
candidate: {
|
|
3775
|
+
package: candidate.package,
|
|
3776
|
+
tarball: candidate.tarball,
|
|
3777
|
+
provenance: candidate.provenance
|
|
3778
|
+
},
|
|
3779
|
+
scope,
|
|
3780
|
+
engram: { binary: engramBin }
|
|
3781
|
+
};
|
|
3782
|
+
}
|
|
3783
|
+
function parseReceiptShape(receiptJson) {
|
|
3784
|
+
try {
|
|
3785
|
+
const parsed = JSON.parse(receiptJson);
|
|
3786
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3787
|
+
const schemaVersion = Reflect.get(parsed, "schemaVersion");
|
|
3788
|
+
if (schemaVersion !== 1) return null;
|
|
3789
|
+
const state = Reflect.get(parsed, "state");
|
|
3790
|
+
const candidate = Reflect.get(parsed, "candidate");
|
|
3791
|
+
if (state !== "installing" && state !== "installed" || candidate === null || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
3792
|
+
return null;
|
|
3793
|
+
}
|
|
3794
|
+
const packageValue = Reflect.get(candidate, "package");
|
|
3795
|
+
const tarball = Reflect.get(candidate, "tarball");
|
|
3796
|
+
const provenance = Reflect.get(candidate, "provenance");
|
|
3797
|
+
const scope = Reflect.get(parsed, "scope");
|
|
3798
|
+
const engram = Reflect.get(parsed, "engram");
|
|
3799
|
+
if (packageValue === null || typeof packageValue !== "object" || tarball === null || typeof tarball !== "object" || provenance === null || typeof provenance !== "object" || scope === null || typeof scope !== "object" || Array.isArray(scope)) {
|
|
3800
|
+
return null;
|
|
3801
|
+
}
|
|
3802
|
+
const source = Reflect.get(packageValue, "source");
|
|
3803
|
+
const name = Reflect.get(packageValue, "name");
|
|
3804
|
+
const version = Reflect.get(packageValue, "version");
|
|
3805
|
+
const scopeKind = Reflect.get(scope, "kind");
|
|
3806
|
+
const codingAgentDir = Reflect.get(scope, "codingAgentDir");
|
|
3807
|
+
if (name !== "jorgex-pi" || typeof version !== "string" || typeof source !== "string" || source !== `npm:jorgex-pi@${version}` || scopeKind !== "real" && scopeKind !== "target-dir" || typeof codingAgentDir !== "string") {
|
|
3808
|
+
return null;
|
|
3809
|
+
}
|
|
3810
|
+
if (engram === void 0) return "upgrade-required";
|
|
3811
|
+
if (engram === null || typeof engram !== "object" || Array.isArray(engram) || typeof Reflect.get(engram, "binary") !== "string" || !path29.isAbsolute(Reflect.get(engram, "binary"))) {
|
|
3812
|
+
return null;
|
|
3813
|
+
}
|
|
3814
|
+
return parsed;
|
|
3815
|
+
} catch {
|
|
3816
|
+
return null;
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
function parseReceipt(receiptJson, candidate, scope, engramBin) {
|
|
3820
|
+
const parsed = parseReceiptShape(receiptJson);
|
|
3821
|
+
if (parsed === null || parsed === "upgrade-required") return parsed;
|
|
3822
|
+
const expected = expectedReceipt(candidate, parsed.state, scope, engramBin);
|
|
3823
|
+
return sameRecord(parsed, expected) ? expected : null;
|
|
3824
|
+
}
|
|
3825
|
+
function candidateIsValid(candidate, observed) {
|
|
3826
|
+
return candidate.package.name === "jorgex-pi" && candidate.package.source === `npm:${candidate.package.name}@${candidate.package.version}` && candidate.contract.schemaVersion === 1 && candidate.contract.runner.schemaVersion === 1 && candidate.contract.runner.bin === "jorgex-pi" && candidate.contract.runner.maxStdoutBytes === 65536 && candidate.contract.managedExternalWrites.length === 0 && [...REQUIRED_CAPABILITIES].every((capability) => candidate.contract.capabilities.includes(capability)) && sameRecord(candidate.tarball, observed);
|
|
3827
|
+
}
|
|
3828
|
+
function planPiPackageLifecycle(input) {
|
|
3829
|
+
if (!candidateIsValid(input.candidate, input.observedTarball)) {
|
|
3830
|
+
return blocked(input, "tarball-integrity");
|
|
3831
|
+
}
|
|
3832
|
+
if (!input.candidate.pi.testedVersions.includes(input.pi.version)) {
|
|
3833
|
+
return blocked(input, "unsupported-pi-version");
|
|
3834
|
+
}
|
|
3835
|
+
if (input.engramBin === null) return blocked(input, "engram-missing");
|
|
3836
|
+
const sources = parsePackageSources(input.pi.settingsJson);
|
|
3837
|
+
if (sources === null) return blocked(input, "settings-corrupt");
|
|
3838
|
+
const matchingSources = sources.filter(({ source }) => isJorgeXPiSource(source));
|
|
3839
|
+
const exactSources = matchingSources.filter(({ source }) => source === input.candidate.package.source);
|
|
3840
|
+
if (exactSources.length > 1) return blocked(input, "duplicate-package");
|
|
3841
|
+
if (matchingSources.some(({ source }) => source !== input.candidate.package.source)) {
|
|
3842
|
+
return blocked(input, "source-divergent");
|
|
3843
|
+
}
|
|
3844
|
+
let receipt = null;
|
|
3845
|
+
if (input.receiptJson !== null) {
|
|
3846
|
+
const parsedReceipt = parseReceipt(input.receiptJson, input.candidate, {
|
|
3847
|
+
kind: input.scope.kind,
|
|
3848
|
+
codingAgentDir: path29.resolve(input.scope.codingAgentDir)
|
|
3849
|
+
}, input.engramBin);
|
|
3850
|
+
if (parsedReceipt === "upgrade-required") return blocked(input, "receipt-upgrade-required");
|
|
3851
|
+
if (parsedReceipt === null) return blocked(input, "receipt-corrupt");
|
|
3852
|
+
receipt = parsedReceipt;
|
|
3853
|
+
if (receipt.state === "installing") return blocked(input, "partial-state");
|
|
3854
|
+
const exactSource = exactSources[0];
|
|
3855
|
+
if (exactSources.length !== 1 || exactSource === void 0 || !isExactManagedPackage(exactSource.entry, input.candidate.package.source)) {
|
|
3856
|
+
return blocked(input, "source-divergent");
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
if (exactSources.length === 1 && receipt === null) {
|
|
3860
|
+
return {
|
|
3861
|
+
kind: "manual-existing",
|
|
3862
|
+
receiptPath: input.scope.receiptPath,
|
|
3863
|
+
ownership: ownership(false)
|
|
3864
|
+
};
|
|
3865
|
+
}
|
|
3866
|
+
if (exactSources.length === 1 && receipt !== null) {
|
|
3867
|
+
return {
|
|
3868
|
+
kind: "ready",
|
|
3869
|
+
receiptPath: input.scope.receiptPath,
|
|
3870
|
+
ownership: ownership(true)
|
|
3871
|
+
};
|
|
3872
|
+
}
|
|
3873
|
+
return {
|
|
3874
|
+
kind: "install",
|
|
3875
|
+
receiptPath: input.scope.receiptPath,
|
|
3876
|
+
invocation: {
|
|
3877
|
+
executable: input.pi.executable,
|
|
3878
|
+
args: ["install", input.candidate.package.source, "--no-approve"],
|
|
3879
|
+
environment: input.scope.environment
|
|
3880
|
+
},
|
|
3881
|
+
receipt: expectedReceipt(input.candidate, "installing", {
|
|
3882
|
+
kind: input.scope.kind,
|
|
3883
|
+
codingAgentDir: path29.resolve(input.scope.codingAgentDir)
|
|
3884
|
+
}, input.engramBin),
|
|
3885
|
+
ownership: ownership(true)
|
|
3886
|
+
};
|
|
3887
|
+
}
|
|
3888
|
+
function parseRunnerRecord(stdout, stderr, command, candidate, packageRunner) {
|
|
3889
|
+
if (stderr !== "" || !stdout.endsWith("\n") || Buffer.byteLength(stdout) > candidate.contract.runner.maxStdoutBytes) {
|
|
3890
|
+
return null;
|
|
3891
|
+
}
|
|
3892
|
+
const body = stdout.slice(0, -1);
|
|
3893
|
+
if (body === "" || body.includes("\n") || body.includes("\r")) return null;
|
|
3894
|
+
try {
|
|
3895
|
+
const parsed = JSON.parse(body);
|
|
3896
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3897
|
+
const record = parsed;
|
|
3898
|
+
if (record.schemaVersion !== candidate.contract.runner.schemaVersion || record.command !== command || record.ok !== true || record.package === null || typeof record.package !== "object" || record.package.name !== candidate.package.name || record.package.version !== candidate.package.version || typeof record.package.root !== "string" || !path29.isAbsolute(record.package.root) || path29.resolve(packageRunner) !== path29.resolve(record.package.root, "bin", "jorgex-pi.mjs")) {
|
|
3899
|
+
return null;
|
|
3900
|
+
}
|
|
3901
|
+
return record;
|
|
3902
|
+
} catch {
|
|
3903
|
+
return null;
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
function runPackageCommand(input, deps, command) {
|
|
3907
|
+
const result = deps.run({
|
|
3908
|
+
executable: input.packageRunner,
|
|
3909
|
+
args: [command, "--json"],
|
|
3910
|
+
environment: input.environment
|
|
3911
|
+
});
|
|
3912
|
+
if (result.exitCode !== 0) return { kind: "blocked", reason: "runner-unhealthy" };
|
|
3913
|
+
return parseRunnerRecord(result.stdout, result.stderr, command, input.candidate, input.packageRunner) ?? { kind: "blocked", reason: "runner-output" };
|
|
3914
|
+
}
|
|
3915
|
+
function isBlockedResult(value) {
|
|
3916
|
+
return "kind" in value;
|
|
3917
|
+
}
|
|
3918
|
+
function executePiPackageLifecycle(input, deps) {
|
|
3919
|
+
if (input.plan.kind === "manual-existing") return { kind: "manual-existing" };
|
|
3920
|
+
if (input.operation === "install") {
|
|
3921
|
+
if (input.plan.kind !== "install" || input.plan.receipt === void 0 || input.plan.invocation === void 0) {
|
|
3922
|
+
return { kind: "blocked", reason: "runner-unhealthy" };
|
|
3923
|
+
}
|
|
3924
|
+
deps.writeReceipt(input.plan.receipt);
|
|
3925
|
+
const installed = deps.run(input.plan.invocation);
|
|
3926
|
+
if (installed.exitCode !== 0 || installed.stderr !== "") {
|
|
3927
|
+
return { kind: "blocked", reason: "pi-install-failed" };
|
|
3928
|
+
}
|
|
3929
|
+
const doctor = runPackageCommand(input, deps, "doctor");
|
|
3930
|
+
if (isBlockedResult(doctor)) return doctor;
|
|
3931
|
+
const doctorResult = doctor.result;
|
|
3932
|
+
if (doctorResult === null || typeof doctorResult !== "object" || Reflect.get(doctorResult, "healthy") !== true) {
|
|
3933
|
+
return { kind: "blocked", reason: "runner-unhealthy" };
|
|
3934
|
+
}
|
|
3935
|
+
const receipt = { ...input.plan.receipt, state: "installed" };
|
|
3936
|
+
deps.writeReceipt(receipt);
|
|
3937
|
+
return { kind: "installed", receipt };
|
|
3938
|
+
}
|
|
3939
|
+
if (input.plan.kind !== "ready") return { kind: "blocked", reason: "runner-unhealthy" };
|
|
3940
|
+
const command = runPackageCommand(input, deps, input.operation);
|
|
3941
|
+
if (isBlockedResult(command)) return command;
|
|
3942
|
+
if (input.operation === "sync") {
|
|
3943
|
+
const result = command.result;
|
|
3944
|
+
if (result === null || typeof result !== "object" || Reflect.get(result, "changed") !== false) {
|
|
3945
|
+
return { kind: "blocked", reason: "runner-unhealthy" };
|
|
3946
|
+
}
|
|
3947
|
+
return { kind: "synced", actions: [] };
|
|
3948
|
+
}
|
|
3949
|
+
const models = command.result;
|
|
3950
|
+
if (models === null || typeof models !== "object" || Reflect.get(models, "mode") !== "inherit-session" || !sameRecord(Reflect.get(models, "tiers"), ["strong", "standard", "cheap"])) {
|
|
3951
|
+
return { kind: "blocked", reason: "runner-unhealthy" };
|
|
3952
|
+
}
|
|
3953
|
+
return { kind: "models", models: { mode: "inherit-session", tiers: ["strong", "standard", "cheap"] } };
|
|
3954
|
+
}
|
|
3955
|
+
function receiptUpgradeRequired() {
|
|
3956
|
+
return {
|
|
3957
|
+
kind: "blocked",
|
|
3958
|
+
reason: "receipt-upgrade-required",
|
|
3959
|
+
remedy: "El receipt no enlaza Engram; usa la versi\xF3n anterior de Stack para desinstalarlo y luego reinstala."
|
|
3960
|
+
};
|
|
3961
|
+
}
|
|
3962
|
+
function validateOwnedOperationState(input) {
|
|
3963
|
+
const sources = parsePackageSources(input.detected.settingsJson);
|
|
3964
|
+
if (sources === null) return { kind: "blocked", reason: "settings-corrupt" };
|
|
3965
|
+
const matchingSources = sources.filter(({ source: source2 }) => isJorgeXPiSource(source2));
|
|
3966
|
+
if (matchingSources.length > 1) return { kind: "blocked", reason: "duplicate-package" };
|
|
3967
|
+
if (input.receiptJson === null) {
|
|
3968
|
+
return { kind: "blocked", reason: matchingSources.length === 1 ? "manual-existing" : "source-divergent" };
|
|
3969
|
+
}
|
|
3970
|
+
const parsedReceipt = parseReceiptShape(input.receiptJson);
|
|
3971
|
+
if (parsedReceipt === "upgrade-required") return receiptUpgradeRequired();
|
|
3972
|
+
if (parsedReceipt === null) return { kind: "blocked", reason: "receipt-corrupt" };
|
|
3973
|
+
const receipt = parsedReceipt;
|
|
3974
|
+
if (receipt.state !== "installed") return { kind: "blocked", reason: "partial-state" };
|
|
3975
|
+
const accepted = input.registry.acceptedCandidates ?? [input.registry.candidate];
|
|
3976
|
+
if (!accepted.some((candidate) => sameRecord(receipt.candidate, {
|
|
3977
|
+
package: candidate.package,
|
|
3978
|
+
tarball: candidate.tarball,
|
|
3979
|
+
provenance: candidate.provenance
|
|
3980
|
+
}))) {
|
|
3981
|
+
return { kind: "blocked", reason: "receipt-untrusted" };
|
|
3982
|
+
}
|
|
3983
|
+
if (receipt.scope.kind !== (input.paths.targetDir ? "target-dir" : "real") || path29.resolve(receipt.scope.codingAgentDir) !== path29.resolve(input.paths.codingAgentDir)) {
|
|
3984
|
+
return { kind: "blocked", reason: "source-divergent" };
|
|
3985
|
+
}
|
|
3986
|
+
if (input.engramBin !== null && path29.resolve(receipt.engram.binary) !== path29.resolve(input.engramBin)) {
|
|
3987
|
+
return { kind: "blocked", reason: "receipt-corrupt" };
|
|
3988
|
+
}
|
|
3989
|
+
const source = receipt.candidate.package.source;
|
|
3990
|
+
const matchingSource = matchingSources[0];
|
|
3991
|
+
if (matchingSources.length !== 1 || matchingSource === void 0 || matchingSource.source !== source || !isExactManagedPackage(matchingSource.entry, source)) {
|
|
3992
|
+
return { kind: "blocked", reason: "source-divergent" };
|
|
3993
|
+
}
|
|
3994
|
+
return { receipt, source };
|
|
3995
|
+
}
|
|
3996
|
+
function operationWasBlocked(value) {
|
|
3997
|
+
return "kind" in value;
|
|
3998
|
+
}
|
|
3999
|
+
function runManagedRunner(input, deps, command) {
|
|
4000
|
+
const result = deps.run({
|
|
4001
|
+
executable: input.detected.packageRunner,
|
|
4002
|
+
args: [command, "--json"],
|
|
4003
|
+
environment: input.paths.environment
|
|
4004
|
+
});
|
|
4005
|
+
if (result.exitCode !== 0) return { kind: "blocked", reason: "runner-unhealthy" };
|
|
4006
|
+
const parsed = parseRunnerRecord(
|
|
4007
|
+
result.stdout,
|
|
4008
|
+
result.stderr,
|
|
4009
|
+
command,
|
|
4010
|
+
input.registry.candidate,
|
|
4011
|
+
input.detected.packageRunner
|
|
4012
|
+
);
|
|
4013
|
+
return parsed ?? { kind: "blocked", reason: "runner-output" };
|
|
4014
|
+
}
|
|
4015
|
+
function managedRunnerWasBlocked(value) {
|
|
4016
|
+
return "kind" in value;
|
|
4017
|
+
}
|
|
4018
|
+
function runPiPackageManagedOperation(input, deps) {
|
|
4019
|
+
if (input.engramBin === null && input.operation !== "uninstall") {
|
|
4020
|
+
return {
|
|
4021
|
+
kind: "blocked",
|
|
4022
|
+
reason: "engram-missing",
|
|
4023
|
+
remedy: "Instala Engram o configura un ENGRAM_BIN absoluto antes de reintentar."
|
|
4024
|
+
};
|
|
4025
|
+
}
|
|
4026
|
+
const owned = validateOwnedOperationState(input);
|
|
4027
|
+
if (operationWasBlocked(owned)) return owned;
|
|
4028
|
+
if (input.operation === "doctor") {
|
|
4029
|
+
if (!sameRecord(owned.receipt.candidate, {
|
|
4030
|
+
package: input.registry.candidate.package,
|
|
4031
|
+
tarball: input.registry.candidate.tarball,
|
|
4032
|
+
provenance: input.registry.candidate.provenance
|
|
4033
|
+
})) {
|
|
4034
|
+
return { kind: "blocked", reason: "source-divergent" };
|
|
4035
|
+
}
|
|
4036
|
+
const doctor = runManagedRunner(input, deps, "doctor");
|
|
4037
|
+
if (managedRunnerWasBlocked(doctor)) return doctor;
|
|
4038
|
+
const result = doctor.result;
|
|
4039
|
+
return result !== null && typeof result === "object" && Reflect.get(result, "healthy") === true ? { kind: "healthy" } : { kind: "blocked", reason: "runner-unhealthy" };
|
|
4040
|
+
}
|
|
4041
|
+
if (input.operation === "uninstall") {
|
|
4042
|
+
if (!sameRecord(owned.receipt.candidate, {
|
|
4043
|
+
package: input.registry.candidate.package,
|
|
4044
|
+
tarball: input.registry.candidate.tarball,
|
|
4045
|
+
provenance: input.registry.candidate.provenance
|
|
4046
|
+
})) {
|
|
4047
|
+
return { kind: "blocked", reason: "source-divergent" };
|
|
4048
|
+
}
|
|
4049
|
+
const cleanup = runManagedRunner(input, deps, "cleanup");
|
|
4050
|
+
if (managedRunnerWasBlocked(cleanup)) return cleanup;
|
|
4051
|
+
deps.backupSettings();
|
|
4052
|
+
const removed = deps.run({
|
|
4053
|
+
executable: input.detected.executable,
|
|
4054
|
+
args: ["remove", owned.source, "--no-approve"],
|
|
4055
|
+
environment: input.paths.environment
|
|
4056
|
+
});
|
|
4057
|
+
if (removed.exitCode !== 0 || removed.stderr !== "") return { kind: "blocked", reason: "remove-failed" };
|
|
4058
|
+
if (!deps.isPackageAbsent()) return { kind: "blocked", reason: "absence-unverified" };
|
|
4059
|
+
deps.deleteReceipt();
|
|
4060
|
+
return { kind: "uninstalled" };
|
|
4061
|
+
}
|
|
4062
|
+
const nextSource = input.registry.candidate.package.source;
|
|
4063
|
+
if (nextSource === owned.source) return { kind: "healthy" };
|
|
4064
|
+
return {
|
|
4065
|
+
kind: "blocked",
|
|
4066
|
+
reason: "verified-update-required",
|
|
4067
|
+
remedy: "A cross-version Pi update requires verified replacement and rollback tgz artifacts."
|
|
4068
|
+
};
|
|
4069
|
+
}
|
|
4070
|
+
|
|
4071
|
+
// src/lib/pi-runtime.ts
|
|
4072
|
+
var PI_RUNTIME_CANDIDATE = {
|
|
4073
|
+
package: {
|
|
4074
|
+
name: "jorgex-pi",
|
|
4075
|
+
version: "0.2.2",
|
|
4076
|
+
source: "npm:jorgex-pi@0.2.2"
|
|
4077
|
+
},
|
|
4078
|
+
provenance: {
|
|
4079
|
+
commit: "99631aa3712f51a625d196e949e48e27f55031a2"
|
|
4080
|
+
},
|
|
4081
|
+
tarball: {
|
|
4082
|
+
bytes: 89101513,
|
|
4083
|
+
sha256: "e1c6b63719995cf7ba2c96c3b753f19d8f2f0be74f2af9bc319576b7383913f4",
|
|
4084
|
+
sha512: "7b81dc1eb6030d562c70857dcf739798df94c88bddd240b2752c558fc1d21403faa411aa88e182a01664a17e06e2caeef35f1507eff45c97f4acc521469c45a1"
|
|
4085
|
+
},
|
|
4086
|
+
pi: {
|
|
4087
|
+
testedVersions: ["0.84.2"]
|
|
4088
|
+
},
|
|
4089
|
+
contract: {
|
|
4090
|
+
schemaVersion: 1,
|
|
4091
|
+
capabilities: [
|
|
4092
|
+
"foundation-contract-v1",
|
|
4093
|
+
"stack-snapshot-v1",
|
|
4094
|
+
"runtime-agents-v1",
|
|
4095
|
+
"permission-gated-tools-v1",
|
|
4096
|
+
"structured-questions-v1",
|
|
4097
|
+
"web-access-v1",
|
|
4098
|
+
"goal-continuation-v1",
|
|
4099
|
+
"mcp-adapter-v1",
|
|
4100
|
+
"engram-runtime-tools-v1",
|
|
4101
|
+
"runner-json-v1",
|
|
4102
|
+
"tui-branding-v1"
|
|
4103
|
+
],
|
|
4104
|
+
runner: {
|
|
4105
|
+
bin: "jorgex-pi",
|
|
4106
|
+
commands: ["status", "doctor", "models", "sync", "cleanup"],
|
|
4107
|
+
schemaVersion: 1,
|
|
4108
|
+
maxStdoutBytes: 65536
|
|
4109
|
+
},
|
|
4110
|
+
managedExternalWrites: []
|
|
4111
|
+
}
|
|
4112
|
+
};
|
|
4113
|
+
var PI_RUNTIME_REGISTRY = {
|
|
4114
|
+
pi: {
|
|
4115
|
+
id: "pi",
|
|
4116
|
+
kind: "package-managed",
|
|
4117
|
+
source: PI_RUNTIME_CANDIDATE.package.source,
|
|
4118
|
+
tarball: PI_RUNTIME_CANDIDATE.tarball,
|
|
4119
|
+
pi: PI_RUNTIME_CANDIDATE.pi,
|
|
4120
|
+
candidate: PI_RUNTIME_CANDIDATE,
|
|
4121
|
+
acceptedCandidates: [PI_RUNTIME_CANDIDATE]
|
|
4122
|
+
}
|
|
4123
|
+
};
|
|
4124
|
+
async function resolvePiEngramRequirement(input, deps) {
|
|
4125
|
+
if (input.targetDir !== void 0) {
|
|
4126
|
+
const targetBin = deps.detectTarget(input.targetDir);
|
|
4127
|
+
return targetBin === null ? {
|
|
4128
|
+
kind: "blocked",
|
|
4129
|
+
reason: "engram-missing-target",
|
|
4130
|
+
remedy: "A\xF1ade el binario Engram dentro del target-dir antes de reintentar."
|
|
4131
|
+
} : { kind: "existing", bin: targetBin, scope: "target-dir" };
|
|
4132
|
+
}
|
|
4133
|
+
const existing = deps.detectHost();
|
|
4134
|
+
if (existing !== null) return { kind: "existing", bin: existing, scope: "host" };
|
|
4135
|
+
if (input.yes || !input.interactive) {
|
|
4136
|
+
return {
|
|
4137
|
+
kind: "blocked",
|
|
4138
|
+
reason: "engram-required",
|
|
4139
|
+
remedy: "Instala Engram de forma interactiva o configura ENGRAM_BIN antes de reintentar."
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
4142
|
+
const accepted = await deps.confirm({
|
|
4143
|
+
message: "Engram es obligatorio para JorgeX Pi. \xBFInstalar ahora el binario mediante el canal nativo?",
|
|
4144
|
+
initialValue: false
|
|
4145
|
+
});
|
|
4146
|
+
if (!accepted) return { kind: "offer", accepted: false };
|
|
4147
|
+
const installed = await deps.installNative({ version: "1.20.0", channels: ["brew", "go", "url"] });
|
|
4148
|
+
if (!installed) {
|
|
4149
|
+
return {
|
|
4150
|
+
kind: "blocked",
|
|
4151
|
+
reason: "engram-install-failed",
|
|
4152
|
+
remedy: "Instala Engram manualmente o configura ENGRAM_BIN antes de reintentar."
|
|
4153
|
+
};
|
|
4154
|
+
}
|
|
4155
|
+
const detected = deps.detectHost();
|
|
4156
|
+
return detected === null ? {
|
|
4157
|
+
kind: "blocked",
|
|
4158
|
+
reason: "engram-install-unverified",
|
|
4159
|
+
remedy: "La instalaci\xF3n termin\xF3, pero Engram no qued\xF3 detectable; configura ENGRAM_BIN."
|
|
4160
|
+
} : { kind: "existing", bin: detected, scope: "host" };
|
|
4161
|
+
}
|
|
4162
|
+
function flatCandidateReceipt(candidate, scope, state, engramBin) {
|
|
4163
|
+
const match = /^npm:jorgex-pi@([^\s]+)$/.exec(candidate.source);
|
|
4164
|
+
const packageValue = candidate.package ?? {
|
|
4165
|
+
name: "jorgex-pi",
|
|
4166
|
+
version: match?.[1] ?? PI_RUNTIME_CANDIDATE.package.version,
|
|
4167
|
+
source: candidate.source
|
|
4168
|
+
};
|
|
4169
|
+
return {
|
|
4170
|
+
schemaVersion: 1,
|
|
4171
|
+
state,
|
|
4172
|
+
candidate: {
|
|
4173
|
+
package: packageValue,
|
|
4174
|
+
tarball: { bytes: candidate.bytes, sha256: candidate.sha256, sha512: candidate.sha512 },
|
|
4175
|
+
provenance: candidate.provenance ?? { commit: PI_RUNTIME_CANDIDATE.provenance.commit }
|
|
4176
|
+
},
|
|
4177
|
+
scope,
|
|
4178
|
+
engram: { binary: engramBin }
|
|
4179
|
+
};
|
|
4180
|
+
}
|
|
4181
|
+
function normalizeInstalledSource(settingsJson, alias, canonical) {
|
|
4182
|
+
try {
|
|
4183
|
+
const parsed = JSON.parse(settingsJson);
|
|
4184
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
4185
|
+
const packages = Reflect.get(parsed, "packages");
|
|
4186
|
+
if (!Array.isArray(packages)) return null;
|
|
4187
|
+
const hasCanonical = packages.some((entry) => entry === canonical || entry !== null && typeof entry === "object" && !Array.isArray(entry) && Reflect.get(entry, "source") === canonical);
|
|
4188
|
+
if (packages.filter((entry) => entry === alias).length !== 1 || hasCanonical) return null;
|
|
4189
|
+
Reflect.set(parsed, "packages", packages.map((entry) => entry === alias ? { source: canonical, skills: [] } : entry));
|
|
4190
|
+
return JSON.stringify(parsed);
|
|
4191
|
+
} catch {
|
|
4192
|
+
return null;
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
function healthyDoctor(stdout, stderr, packageRunner, candidate) {
|
|
4196
|
+
if (stderr !== "" || !stdout.endsWith("\n") || stdout.slice(0, -1).includes("\n")) return false;
|
|
4197
|
+
try {
|
|
4198
|
+
const record = JSON.parse(stdout.slice(0, -1));
|
|
4199
|
+
if (record === null || typeof record !== "object" || Array.isArray(record)) return false;
|
|
4200
|
+
const packageValue = Reflect.get(record, "package");
|
|
4201
|
+
const result = Reflect.get(record, "result");
|
|
4202
|
+
return Reflect.get(record, "schemaVersion") === 1 && Reflect.get(record, "command") === "doctor" && Reflect.get(record, "ok") === true && packageValue !== null && typeof packageValue === "object" && Reflect.get(packageValue, "name") === "jorgex-pi" && Reflect.get(packageValue, "version") === (candidate.package?.version ?? /^npm:jorgex-pi@([^\s]+)$/.exec(candidate.source)?.[1]) && path30.resolve(packageRunner) === path30.resolve(String(Reflect.get(packageValue, "root")), "bin", "jorgex-pi.mjs") && result !== null && typeof result === "object" && Reflect.get(result, "healthy") === true;
|
|
4203
|
+
} catch {
|
|
4204
|
+
return false;
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
function installPiFromVerifiedTarball(input, deps) {
|
|
4208
|
+
const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.piExecutable) : targetPaths(input.targetDir, input.engramBin, input.piExecutable);
|
|
4209
|
+
const destination = input.targetDir === void 0 ? path30.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path30.join(path30.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
|
|
4210
|
+
const artifact = deps.download(destination);
|
|
4211
|
+
if (artifact.bytes !== input.candidate.bytes || artifact.sha256 !== input.candidate.sha256 || artifact.sha512 !== input.candidate.sha512) {
|
|
4212
|
+
return { kind: "blocked", reason: "tarball-integrity" };
|
|
4213
|
+
}
|
|
4214
|
+
deps.backupSettings();
|
|
4215
|
+
const scope = {
|
|
4216
|
+
kind: input.targetDir === void 0 ? "real" : "target-dir",
|
|
4217
|
+
codingAgentDir: path30.resolve(paths.codingAgentDir)
|
|
4218
|
+
};
|
|
4219
|
+
const installing = flatCandidateReceipt(input.candidate, scope, "installing", input.engramBin);
|
|
4220
|
+
deps.writeReceiptAtomic(`${JSON.stringify(installing)}
|
|
4221
|
+
`);
|
|
4222
|
+
const alias = `npm:jorgex-pi@file:${artifact.path}`;
|
|
4223
|
+
const installed = deps.run({
|
|
4224
|
+
executable: input.piExecutable,
|
|
4225
|
+
args: ["install", alias, "--no-approve"],
|
|
4226
|
+
environment: paths.environment
|
|
4227
|
+
});
|
|
4228
|
+
if (installed.exitCode !== 0 || installed.stderr !== "") return { kind: "blocked", reason: "pi-install-failed" };
|
|
4229
|
+
const normalized = normalizeInstalledSource(deps.readSettings(), alias, input.candidate.source);
|
|
4230
|
+
if (normalized === null) return { kind: "blocked", reason: "settings-corrupt" };
|
|
4231
|
+
deps.rewriteSettings(normalized);
|
|
4232
|
+
const doctor = deps.run({
|
|
4233
|
+
executable: process.execPath,
|
|
4234
|
+
args: [paths.packageRunner, "doctor", "--json"],
|
|
4235
|
+
environment: paths.environment
|
|
4236
|
+
});
|
|
4237
|
+
if (doctor.exitCode !== 0 || !healthyDoctor(doctor.stdout, doctor.stderr, paths.packageRunner, input.candidate)) {
|
|
4238
|
+
return { kind: "blocked", reason: "runner-unhealthy" };
|
|
4239
|
+
}
|
|
4240
|
+
const receipt = flatCandidateReceipt(input.candidate, scope, "installed", input.engramBin);
|
|
4241
|
+
deps.writeReceiptAtomic(`${JSON.stringify(receipt)}
|
|
4242
|
+
`);
|
|
4243
|
+
return { kind: "installed", receipt };
|
|
4244
|
+
}
|
|
4245
|
+
function runtimePath(piExecutable) {
|
|
4246
|
+
const entries = process.platform === "win32" ? [piExecutable === void 0 ? null : path30.dirname(piExecutable), path30.dirname(process.execPath), process.env.SystemRoot ? path30.join(process.env.SystemRoot, "System32") : null] : [piExecutable === void 0 ? null : path30.dirname(piExecutable), path30.dirname(process.execPath), "/usr/local/bin", "/usr/bin", "/bin"];
|
|
4247
|
+
return [...new Set(entries.filter((entry) => entry !== null))].join(path30.delimiter);
|
|
4248
|
+
}
|
|
4249
|
+
function targetPaths(targetDir, engramBin, piExecutable) {
|
|
4250
|
+
const root = path30.resolve(targetDir);
|
|
4251
|
+
const codingAgentDir = path30.join(root, "pi-agent");
|
|
4252
|
+
const home = path30.join(root, "home");
|
|
4253
|
+
const temporary = path30.join(root, "tmp");
|
|
4254
|
+
return {
|
|
4255
|
+
codingAgentDir,
|
|
4256
|
+
receiptPath: path30.join(root, "state", "pi-receipt.json"),
|
|
4257
|
+
packageRunner: path30.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
|
|
4258
|
+
environment: {
|
|
4259
|
+
HOME: home,
|
|
4260
|
+
USERPROFILE: home,
|
|
4261
|
+
APPDATA: path30.join(root, "appdata"),
|
|
4262
|
+
LOCALAPPDATA: path30.join(root, "localappdata"),
|
|
4263
|
+
XDG_CONFIG_HOME: path30.join(root, "xdg-config"),
|
|
4264
|
+
XDG_DATA_HOME: path30.join(root, "xdg-data"),
|
|
4265
|
+
XDG_CACHE_HOME: path30.join(root, "xdg-cache"),
|
|
4266
|
+
TEMP: temporary,
|
|
4267
|
+
TMP: temporary,
|
|
4268
|
+
TMPDIR: temporary,
|
|
4269
|
+
npm_config_cache: path30.join(root, "npm-cache"),
|
|
4270
|
+
NPM_CONFIG_IGNORE_SCRIPTS: "true",
|
|
4271
|
+
NPM_CONFIG_UPDATE_NOTIFIER: "false",
|
|
4272
|
+
PI_CODING_AGENT_DIR: codingAgentDir,
|
|
4273
|
+
...engramBin === null ? {} : { ENGRAM_BIN: engramBin },
|
|
4274
|
+
PATH: runtimePath(piExecutable)
|
|
4275
|
+
}
|
|
4276
|
+
};
|
|
4277
|
+
}
|
|
4278
|
+
function userPaths(engramBin, piExecutable) {
|
|
4279
|
+
const home = os5.homedir();
|
|
4280
|
+
const codingAgentDir = process.env.PI_CODING_AGENT_DIR ?? path30.join(home, ".pi", "agent");
|
|
4281
|
+
return {
|
|
4282
|
+
codingAgentDir,
|
|
4283
|
+
receiptPath: path30.join(dataDir(), "pi-receipt.json"),
|
|
4284
|
+
packageRunner: path30.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
|
|
4285
|
+
environment: {
|
|
4286
|
+
HOME: home,
|
|
4287
|
+
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME ?? path30.join(home, ".config"),
|
|
4288
|
+
XDG_CACHE_HOME: process.env.XDG_CACHE_HOME ?? path30.join(home, ".cache"),
|
|
4289
|
+
TMPDIR: os5.tmpdir(),
|
|
4290
|
+
NPM_CONFIG_IGNORE_SCRIPTS: "true",
|
|
4291
|
+
NPM_CONFIG_UPDATE_NOTIFIER: "false",
|
|
4292
|
+
PI_CODING_AGENT_DIR: codingAgentDir,
|
|
4293
|
+
...engramBin === null ? {} : { ENGRAM_BIN: engramBin },
|
|
4294
|
+
PATH: runtimePath(piExecutable)
|
|
4295
|
+
}
|
|
4296
|
+
};
|
|
4297
|
+
}
|
|
4298
|
+
function persistReturnedReceipt(result, paths, deps) {
|
|
4299
|
+
if (result.receipt !== void 0) {
|
|
4300
|
+
deps.writeReceiptAtomic(paths.receiptPath, `${JSON.stringify(result.receipt)}
|
|
4301
|
+
`);
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
function runPiRuntime(input, deps) {
|
|
4305
|
+
if (input.engramBin === null && input.operation !== "uninstall") {
|
|
4306
|
+
return {
|
|
4307
|
+
kind: "blocked",
|
|
4308
|
+
reason: "engram-missing",
|
|
4309
|
+
remedy: "Instala Engram o configura un ENGRAM_BIN absoluto antes de reintentar."
|
|
4310
|
+
};
|
|
4311
|
+
}
|
|
4312
|
+
if (input.operation === "install" && input.verifiedArtifact === void 0) {
|
|
4313
|
+
return { kind: "blocked", reason: "tarball-integrity" };
|
|
4314
|
+
}
|
|
4315
|
+
const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.detected.executable) : targetPaths(input.targetDir, input.engramBin, input.detected.executable);
|
|
4316
|
+
const settingsJson = deps.readSettings(path30.join(paths.codingAgentDir, "settings.json"));
|
|
4317
|
+
const receiptJson = deps.readReceipt(paths.receiptPath);
|
|
4318
|
+
const lifecycleInput = {
|
|
4319
|
+
candidate: PI_RUNTIME_CANDIDATE,
|
|
4320
|
+
observedTarball: input.verifiedArtifact ?? PI_RUNTIME_CANDIDATE.tarball,
|
|
4321
|
+
pi: {
|
|
4322
|
+
executable: input.detected.executable,
|
|
4323
|
+
version: input.detected.version,
|
|
4324
|
+
packageRunner: paths.packageRunner,
|
|
4325
|
+
settingsJson
|
|
4326
|
+
},
|
|
4327
|
+
engramBin: input.engramBin,
|
|
4328
|
+
receiptJson,
|
|
4329
|
+
scope: {
|
|
4330
|
+
kind: input.targetDir === void 0 ? "real" : "target-dir",
|
|
4331
|
+
codingAgentDir: paths.codingAgentDir,
|
|
4332
|
+
receiptPath: paths.receiptPath,
|
|
4333
|
+
environment: paths.environment
|
|
4334
|
+
}
|
|
4335
|
+
};
|
|
4336
|
+
if (input.operation === "install" || input.operation === "sync" || input.operation === "models") {
|
|
4337
|
+
const plan = deps.prepare(lifecycleInput);
|
|
4338
|
+
const result2 = deps.execute({
|
|
4339
|
+
operation: input.operation,
|
|
4340
|
+
plan,
|
|
4341
|
+
candidate: PI_RUNTIME_CANDIDATE,
|
|
4342
|
+
packageRunner: paths.packageRunner,
|
|
4343
|
+
environment: paths.environment
|
|
4344
|
+
});
|
|
4345
|
+
persistReturnedReceipt(result2, paths, deps);
|
|
4346
|
+
return result2;
|
|
4347
|
+
}
|
|
4348
|
+
const result = deps.operate({
|
|
4349
|
+
operation: input.operation,
|
|
4350
|
+
interactive: false,
|
|
4351
|
+
registry: PI_RUNTIME_REGISTRY.pi,
|
|
4352
|
+
detected: {
|
|
4353
|
+
executable: input.detected.executable,
|
|
4354
|
+
packageRunner: paths.packageRunner,
|
|
4355
|
+
settingsJson
|
|
4356
|
+
},
|
|
4357
|
+
engramBin: input.engramBin,
|
|
4358
|
+
receiptJson,
|
|
4359
|
+
paths: {
|
|
4360
|
+
targetDir: input.targetDir !== void 0,
|
|
4361
|
+
codingAgentDir: paths.codingAgentDir,
|
|
4362
|
+
receiptPath: paths.receiptPath,
|
|
4363
|
+
environment: paths.environment
|
|
4364
|
+
},
|
|
4365
|
+
removeArgs: ["remove", PI_RUNTIME_CANDIDATE.package.source, "--no-approve"]
|
|
4366
|
+
});
|
|
4367
|
+
persistReturnedReceipt(result, paths, deps);
|
|
4368
|
+
return result;
|
|
4369
|
+
}
|
|
4370
|
+
function readJsonFile(file) {
|
|
4371
|
+
return JSON.parse(fs22.readFileSync(file, "utf8"));
|
|
4372
|
+
}
|
|
4373
|
+
function packageVersionFromExecutable(executable) {
|
|
4374
|
+
let current;
|
|
4375
|
+
try {
|
|
4376
|
+
current = path30.dirname(fs22.realpathSync(executable));
|
|
4377
|
+
} catch {
|
|
4378
|
+
return null;
|
|
4379
|
+
}
|
|
4380
|
+
for (let depth = 0; depth < 8; depth++) {
|
|
4381
|
+
const manifests = [
|
|
4382
|
+
path30.join(current, "package.json"),
|
|
4383
|
+
path30.join(current, "node_modules", "@earendil-works", "pi-coding-agent", "package.json")
|
|
4384
|
+
];
|
|
4385
|
+
for (const manifest of manifests) {
|
|
4386
|
+
try {
|
|
4387
|
+
const parsed = readJsonFile(manifest);
|
|
4388
|
+
if (parsed !== null && typeof parsed === "object" && Reflect.get(parsed, "name") === "@earendil-works/pi-coding-agent" && typeof Reflect.get(parsed, "version") === "string") {
|
|
4389
|
+
return Reflect.get(parsed, "version");
|
|
4390
|
+
}
|
|
4391
|
+
} catch {
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
const parent = path30.dirname(current);
|
|
4395
|
+
if (parent === current) break;
|
|
4396
|
+
current = parent;
|
|
4397
|
+
}
|
|
4398
|
+
return null;
|
|
4399
|
+
}
|
|
4400
|
+
function detectPiRuntime() {
|
|
4401
|
+
const executable = lookPath("pi");
|
|
4402
|
+
const home = os5.homedir();
|
|
4403
|
+
return {
|
|
4404
|
+
id: "pi",
|
|
4405
|
+
name: "Pi",
|
|
4406
|
+
installed: executable !== null,
|
|
4407
|
+
executable,
|
|
4408
|
+
version: executable === null ? null : packageVersionFromExecutable(executable),
|
|
4409
|
+
codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path30.join(home, ".pi", "agent")
|
|
4410
|
+
};
|
|
4411
|
+
}
|
|
4412
|
+
function hasManagedPiRuntime(targetDir) {
|
|
4413
|
+
const receipt = targetDir === void 0 ? path30.join(dataDir(), "pi-receipt.json") : path30.join(path30.resolve(targetDir), "state", "pi-receipt.json");
|
|
4414
|
+
return fs22.statSync(receipt, { throwIfNoEntry: false })?.isFile() === true;
|
|
4415
|
+
}
|
|
4416
|
+
function resolvePiEngramBin(targetDir) {
|
|
4417
|
+
if (targetDir === void 0) return detectEngram();
|
|
4418
|
+
const candidate = path30.join(path30.resolve(targetDir), "bin", process.platform === "win32" ? "engram.exe" : "engram");
|
|
4419
|
+
return fs22.statSync(candidate, { throwIfNoEntry: false })?.isFile() ? candidate : null;
|
|
4420
|
+
}
|
|
4421
|
+
function readOptional(file, fallback) {
|
|
4422
|
+
try {
|
|
4423
|
+
return fs22.readFileSync(file, "utf8");
|
|
4424
|
+
} catch (error) {
|
|
4425
|
+
if (error.code === "ENOENT") return fallback;
|
|
4426
|
+
throw error;
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
function hashPiTarball(file) {
|
|
4430
|
+
const descriptor = fs22.openSync(file, "r");
|
|
4431
|
+
const sha256 = createHash("sha256");
|
|
4432
|
+
const sha512 = createHash("sha512");
|
|
4433
|
+
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
4434
|
+
let bytes = 0;
|
|
4435
|
+
try {
|
|
4436
|
+
while (true) {
|
|
4437
|
+
const read = fs22.readSync(descriptor, buffer, 0, buffer.length, null);
|
|
4438
|
+
if (read === 0) break;
|
|
4439
|
+
bytes += read;
|
|
4440
|
+
const chunk = buffer.subarray(0, read);
|
|
4441
|
+
sha256.update(chunk);
|
|
4442
|
+
sha512.update(chunk);
|
|
4443
|
+
}
|
|
4444
|
+
} finally {
|
|
4445
|
+
fs22.closeSync(descriptor);
|
|
4446
|
+
}
|
|
4447
|
+
return { path: file, bytes, sha256: sha256.digest("hex"), sha512: sha512.digest("hex") };
|
|
4448
|
+
}
|
|
4449
|
+
async function acquirePiTarball(destination) {
|
|
4450
|
+
const existing = fs22.statSync(destination, { throwIfNoEntry: false });
|
|
4451
|
+
if (existing?.isFile()) {
|
|
4452
|
+
const observed = hashPiTarball(destination);
|
|
4453
|
+
if (observed.bytes === PI_RUNTIME_CANDIDATE.tarball.bytes && observed.sha256 === PI_RUNTIME_CANDIDATE.tarball.sha256 && observed.sha512 === PI_RUNTIME_CANDIDATE.tarball.sha512) {
|
|
4454
|
+
return observed;
|
|
4455
|
+
}
|
|
4456
|
+
fs22.rmSync(destination, { force: true });
|
|
4457
|
+
}
|
|
4458
|
+
fs22.mkdirSync(path30.dirname(destination), { recursive: true });
|
|
4459
|
+
const partial = `${destination}.partial-${process.pid}`;
|
|
4460
|
+
const response = await fetch(`https://registry.npmjs.org/jorgex-pi/-/jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`, {
|
|
4461
|
+
redirect: "error",
|
|
4462
|
+
headers: { accept: "application/octet-stream" }
|
|
4463
|
+
});
|
|
4464
|
+
if (!response.ok || response.body === null) throw new Error(`No se pudo descargar jorgex-pi@${PI_RUNTIME_CANDIDATE.package.version} (${response.status}).`);
|
|
4465
|
+
const descriptor = fs22.openSync(partial, "wx", 384);
|
|
4466
|
+
let bytes = 0;
|
|
4467
|
+
try {
|
|
4468
|
+
for await (const chunk of response.body) {
|
|
4469
|
+
const buffer = Buffer.from(chunk);
|
|
4470
|
+
bytes += buffer.length;
|
|
4471
|
+
if (bytes > PI_RUNTIME_CANDIDATE.tarball.bytes) throw new Error("El tarball de jorgex-pi excede el tama\xF1o fijado.");
|
|
4472
|
+
let offset = 0;
|
|
4473
|
+
while (offset < buffer.length) offset += fs22.writeSync(descriptor, buffer, offset);
|
|
4474
|
+
}
|
|
4475
|
+
} catch (error) {
|
|
4476
|
+
fs22.closeSync(descriptor);
|
|
4477
|
+
fs22.rmSync(partial, { force: true });
|
|
4478
|
+
throw error;
|
|
4479
|
+
}
|
|
4480
|
+
fs22.closeSync(descriptor);
|
|
4481
|
+
fs22.renameSync(partial, destination);
|
|
4482
|
+
return hashPiTarball(destination);
|
|
4483
|
+
}
|
|
4484
|
+
function runProcess(invocation) {
|
|
4485
|
+
const planned = /\.mjs$/i.test(invocation.executable) ? { command: process.execPath, args: [invocation.executable, ...invocation.args] } : planDetectedBinCommand(invocation.executable, invocation.args);
|
|
4486
|
+
if (planned === null) return { exitCode: 1, stdout: "", stderr: "unsafe executable" };
|
|
4487
|
+
const result = spawnSync(planned.command, planned.args, {
|
|
4488
|
+
encoding: "utf8",
|
|
4489
|
+
env: invocation.environment,
|
|
4490
|
+
shell: false,
|
|
4491
|
+
timeout: 12e4,
|
|
4492
|
+
maxBuffer: PI_RUNTIME_CANDIDATE.contract.runner.maxStdoutBytes + 1,
|
|
4493
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4494
|
+
});
|
|
4495
|
+
return {
|
|
4496
|
+
exitCode: result.status ?? 1,
|
|
4497
|
+
stdout: result.stdout ?? "",
|
|
4498
|
+
stderr: result.stderr ?? (result.error?.message ?? "")
|
|
4499
|
+
};
|
|
4500
|
+
}
|
|
4501
|
+
async function runPiRuntimeSystem(input) {
|
|
4502
|
+
if (input.engramBin === null && input.operation !== "uninstall") return runPiRuntime(input, {
|
|
4503
|
+
readSettings: () => {
|
|
4504
|
+
throw new Error("unreachable");
|
|
4505
|
+
},
|
|
4506
|
+
readReceipt: () => {
|
|
4507
|
+
throw new Error("unreachable");
|
|
4508
|
+
},
|
|
4509
|
+
writeReceiptAtomic: () => {
|
|
4510
|
+
throw new Error("unreachable");
|
|
4511
|
+
},
|
|
4512
|
+
prepare: () => {
|
|
4513
|
+
throw new Error("unreachable");
|
|
4514
|
+
},
|
|
4515
|
+
execute: () => {
|
|
4516
|
+
throw new Error("unreachable");
|
|
4517
|
+
},
|
|
4518
|
+
operate: () => {
|
|
4519
|
+
throw new Error("unreachable");
|
|
4520
|
+
}
|
|
4521
|
+
});
|
|
4522
|
+
const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.detected.executable) : targetPaths(input.targetDir, input.engramBin, input.detected.executable);
|
|
4523
|
+
if (input.operation === "install") {
|
|
4524
|
+
if (input.engramBin === null) {
|
|
4525
|
+
return {
|
|
4526
|
+
kind: "blocked",
|
|
4527
|
+
reason: "engram-required",
|
|
4528
|
+
remedy: "Instala Engram o configura un ENGRAM_BIN absoluto antes de reintentar."
|
|
4529
|
+
};
|
|
4530
|
+
}
|
|
4531
|
+
const destination = input.targetDir === void 0 ? path30.join(dataDir(), "packages", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`) : path30.join(path30.resolve(input.targetDir), "downloads", `jorgex-pi-${PI_RUNTIME_CANDIDATE.package.version}.tgz`);
|
|
4532
|
+
let artifact;
|
|
4533
|
+
try {
|
|
4534
|
+
artifact = await acquirePiTarball(destination);
|
|
4535
|
+
} catch (error) {
|
|
4536
|
+
return { kind: "blocked", reason: "tarball-download", remedy: error instanceof Error ? error.message : String(error) };
|
|
4537
|
+
}
|
|
4538
|
+
return installPiFromVerifiedTarball({
|
|
4539
|
+
targetDir: input.targetDir,
|
|
4540
|
+
piExecutable: input.detected.executable,
|
|
4541
|
+
engramBin: input.engramBin,
|
|
4542
|
+
candidate: {
|
|
4543
|
+
source: PI_RUNTIME_CANDIDATE.package.source,
|
|
4544
|
+
...PI_RUNTIME_CANDIDATE.tarball,
|
|
4545
|
+
package: PI_RUNTIME_CANDIDATE.package,
|
|
4546
|
+
provenance: PI_RUNTIME_CANDIDATE.provenance
|
|
4547
|
+
}
|
|
4548
|
+
}, {
|
|
4549
|
+
download: () => artifact,
|
|
4550
|
+
backupSettings: () => createBackup(
|
|
4551
|
+
[path30.join(paths.codingAgentDir, "settings.json")],
|
|
4552
|
+
"pi-package-install",
|
|
4553
|
+
input.targetDir === void 0 ? void 0 : path30.join(path30.resolve(input.targetDir), "backups")
|
|
4554
|
+
),
|
|
4555
|
+
run: runProcess,
|
|
4556
|
+
readSettings: () => readOptional(path30.join(paths.codingAgentDir, "settings.json"), '{"packages":[]}'),
|
|
4557
|
+
rewriteSettings: (content) => writeText(path30.join(paths.codingAgentDir, "settings.json"), `${content}
|
|
4558
|
+
`),
|
|
4559
|
+
writeReceiptAtomic: (content) => writeText(paths.receiptPath, content)
|
|
4560
|
+
});
|
|
4561
|
+
}
|
|
4562
|
+
const packageRoot = path30.dirname(path30.dirname(paths.packageRunner));
|
|
4563
|
+
const writeReceipt = (receipt) => {
|
|
4564
|
+
writeText(paths.receiptPath, `${JSON.stringify(receipt, null, 2)}
|
|
4565
|
+
`);
|
|
4566
|
+
};
|
|
4567
|
+
const deps = {
|
|
4568
|
+
readSettings: (file) => readOptional(file, '{"packages":[]}'),
|
|
4569
|
+
readReceipt: (file) => readOptional(file, null),
|
|
4570
|
+
writeReceiptAtomic: (file, content) => writeText(file, content),
|
|
4571
|
+
prepare: (value) => planPiPackageLifecycle(value),
|
|
4572
|
+
execute: (value) => executePiPackageLifecycle(
|
|
4573
|
+
value,
|
|
4574
|
+
{ writeReceipt, run: runProcess }
|
|
4575
|
+
),
|
|
4576
|
+
operate: (value) => runPiPackageManagedOperation(
|
|
4577
|
+
value,
|
|
4578
|
+
{
|
|
4579
|
+
backupSettings: () => createBackup(
|
|
4580
|
+
[path30.join(paths.codingAgentDir, "settings.json")],
|
|
4581
|
+
"pi-package-uninstall",
|
|
4582
|
+
input.targetDir === void 0 ? void 0 : path30.join(path30.resolve(input.targetDir), "backups")
|
|
4583
|
+
),
|
|
4584
|
+
run: runProcess,
|
|
4585
|
+
isPackageAbsent: () => !fs22.existsSync(packageRoot),
|
|
4586
|
+
deleteReceipt: () => fs22.rmSync(paths.receiptPath, { force: true })
|
|
4587
|
+
}
|
|
4588
|
+
)
|
|
4589
|
+
};
|
|
4590
|
+
return runPiRuntime(input, deps);
|
|
4591
|
+
}
|
|
4592
|
+
|
|
3716
4593
|
// src/cli.ts
|
|
3717
4594
|
var VERSION = readPackageVersion();
|
|
3718
4595
|
var COMMANDS = ["install", "sync", "models", "update", "doctor", "restore", "uninstall"];
|
|
@@ -3911,19 +4788,71 @@ function parseCliArgs(argv) {
|
|
|
3911
4788
|
if (flags.unknownFlags.length > 0) return { action: "unknown-flags", command, flags };
|
|
3912
4789
|
return { action: "run", command, flags };
|
|
3913
4790
|
}
|
|
3914
|
-
|
|
4791
|
+
function isFileManagedRuntime(runtime) {
|
|
4792
|
+
return runtime !== "pi";
|
|
4793
|
+
}
|
|
4794
|
+
async function resolveRuntimes(flags, includeAvailablePi = false) {
|
|
3915
4795
|
if (flags.agents.length > 0) return flags.agents;
|
|
3916
|
-
const detected = Object.values(ADAPTERS).filter((
|
|
4796
|
+
const detected = Object.values(ADAPTERS).filter((adapter) => adapter.detect().installed).map((adapter) => ({ id: adapter.id, name: adapter.name }));
|
|
4797
|
+
const pi = detectPiRuntime();
|
|
4798
|
+
if (pi.installed && (includeAvailablePi || hasManagedPiRuntime(flags.targetDir))) {
|
|
4799
|
+
detected.push({ id: "pi", name: "Pi" });
|
|
4800
|
+
}
|
|
3917
4801
|
if (detected.length === 0) return [];
|
|
3918
|
-
if (flags.yes || !process.stdout.isTTY || flags.targetDir !== void 0) return detected.map((
|
|
4802
|
+
if (flags.yes || !process.stdout.isTTY || flags.targetDir !== void 0) return detected.map((runtime) => runtime.id);
|
|
3919
4803
|
const choice = await p6.multiselect({
|
|
3920
4804
|
message: "\xBFEn qu\xE9 runtimes? (detectados en esta m\xE1quina)",
|
|
3921
|
-
options: detected.map((
|
|
3922
|
-
initialValues: detected.map((
|
|
4805
|
+
options: detected.map((runtime) => ({ value: runtime.id, label: runtime.name })),
|
|
4806
|
+
initialValues: detected.map((runtime) => runtime.id)
|
|
3923
4807
|
});
|
|
3924
4808
|
if (p6.isCancel(choice)) return null;
|
|
3925
4809
|
return choice;
|
|
3926
4810
|
}
|
|
4811
|
+
async function runSelectedPi(operation, targetDir, yes = false) {
|
|
4812
|
+
const detected = detectPiRuntime();
|
|
4813
|
+
if (!detected.installed || detected.executable === null) {
|
|
4814
|
+
console.error("Pi no detectado. Instala el runtime Pi antes de gestionar jorgex-pi.");
|
|
4815
|
+
return 1;
|
|
4816
|
+
}
|
|
4817
|
+
if (detected.version === null) {
|
|
4818
|
+
console.error("No se pudo verificar la versi\xF3n instalada de Pi sin ejecutarlo; revisa la instalaci\xF3n de Pi.");
|
|
4819
|
+
return 1;
|
|
4820
|
+
}
|
|
4821
|
+
let engramBin = resolvePiEngramBin(targetDir);
|
|
4822
|
+
if (operation === "install" && engramBin === null) {
|
|
4823
|
+
const requirement = await resolvePiEngramRequirement({
|
|
4824
|
+
targetDir,
|
|
4825
|
+
interactive: process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
4826
|
+
yes
|
|
4827
|
+
}, {
|
|
4828
|
+
detectHost: () => resolvePiEngramBin(),
|
|
4829
|
+
detectTarget: (root) => resolvePiEngramBin(root),
|
|
4830
|
+
confirm: async ({ message, initialValue }) => {
|
|
4831
|
+
const answer = await p6.confirm({ message, initialValue });
|
|
4832
|
+
return !p6.isCancel(answer) && answer;
|
|
4833
|
+
},
|
|
4834
|
+
installNative: async ({ version }) => updateEngram("Gentleman-Programming/engram", version)
|
|
4835
|
+
});
|
|
4836
|
+
if (requirement.kind !== "existing") {
|
|
4837
|
+
console.error(requirement.kind === "offer" ? "Pi: instalaci\xF3n cancelada; Engram sigue siendo obligatorio." : `Pi: ${requirement.reason}. ${requirement.remedy}`);
|
|
4838
|
+
return 1;
|
|
4839
|
+
}
|
|
4840
|
+
engramBin = requirement.bin;
|
|
4841
|
+
}
|
|
4842
|
+
const result = await runPiRuntimeSystem({
|
|
4843
|
+
operation,
|
|
4844
|
+
targetDir,
|
|
4845
|
+
detected: { executable: detected.executable, version: detected.version },
|
|
4846
|
+
engramBin
|
|
4847
|
+
});
|
|
4848
|
+
if (result.kind === "blocked") {
|
|
4849
|
+
console.error(`Pi: ${result.reason ?? "operaci\xF3n bloqueada"}${result.remedy ? `. ${result.remedy}` : ""}`);
|
|
4850
|
+
return 1;
|
|
4851
|
+
}
|
|
4852
|
+
if (result.kind === "models" && result.models !== void 0) console.log(JSON.stringify(result.models));
|
|
4853
|
+
else p6.log.success(`Pi: ${result.kind}.`);
|
|
4854
|
+
return 0;
|
|
4855
|
+
}
|
|
3927
4856
|
function printHelp() {
|
|
3928
4857
|
console.log(`jorgex-stack v${VERSION}
|
|
3929
4858
|
|
|
@@ -3941,7 +4870,7 @@ Comandos:
|
|
|
3941
4870
|
desregistrarlo exige --remove-engram o el s\xED expl\xEDcito
|
|
3942
4871
|
|
|
3943
4872
|
Opciones:
|
|
3944
|
-
--agents, -a opencode,claude-code,codex Runtimes destino (default: detectados)
|
|
4873
|
+
--agents, -a opencode,claude-code,codex,pi Runtimes destino (default: detectados)
|
|
3945
4874
|
--mode human|programmatic Modo de instalaci\xF3n (default: preferencia guardada o human)
|
|
3946
4875
|
--subagent-concurrency serial|parallel Concurrencia de subagentes en modo programmatic
|
|
3947
4876
|
--target-dir <dir> Dir alternativo (pruebas de paridad; requiere 1 runtime)
|
|
@@ -3988,34 +4917,42 @@ Flags disponibles: jorgex-stack --help`
|
|
|
3988
4917
|
switch (command) {
|
|
3989
4918
|
case "install":
|
|
3990
4919
|
case "sync": {
|
|
3991
|
-
const
|
|
3992
|
-
if (mode === null) return;
|
|
3993
|
-
const runtimes = await resolveRuntimes(flags);
|
|
4920
|
+
const runtimes = await resolveRuntimes(flags, command === "install");
|
|
3994
4921
|
if (runtimes === null) return;
|
|
3995
4922
|
if (runtimes.length === 0) {
|
|
3996
|
-
console.error("Ning\xFAn runtime detectado (opencode, claude-code, codex).");
|
|
4923
|
+
console.error("Ning\xFAn runtime detectado (opencode, claude-code, codex, pi).");
|
|
3997
4924
|
process.exitCode = 1;
|
|
3998
4925
|
return;
|
|
3999
4926
|
}
|
|
4000
|
-
const
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4927
|
+
const fileRuntimes = runtimes.filter(isFileManagedRuntime);
|
|
4928
|
+
let exitCode = 0;
|
|
4929
|
+
if (fileRuntimes.length > 0) {
|
|
4930
|
+
const mode = await resolveInstallMode(flags);
|
|
4931
|
+
if (mode === null) return;
|
|
4932
|
+
const devtoolsMcpSelection = await resolveDevtoolsMcpSelection(command, flags, fileRuntimes);
|
|
4933
|
+
if (devtoolsMcpSelection === null) return;
|
|
4934
|
+
const playwrightToolConsent = await resolvePlaywrightToolConsent(command, flags);
|
|
4935
|
+
if (playwrightToolConsent === null) return;
|
|
4936
|
+
const hasOpenCodeModels = await ensureOpenCodeModelsForInstall(command, flags, fileRuntimes);
|
|
4937
|
+
if (!hasOpenCodeModels) {
|
|
4938
|
+
process.exitCode = 1;
|
|
4939
|
+
return;
|
|
4940
|
+
}
|
|
4941
|
+
exitCode = await runInstall({
|
|
4942
|
+
runtimes: fileRuntimes,
|
|
4943
|
+
targetDir: flags.targetDir,
|
|
4944
|
+
dryRun: flags.dryRun,
|
|
4945
|
+
yes: flags.yes,
|
|
4946
|
+
mode,
|
|
4947
|
+
playwrightToolConsent,
|
|
4948
|
+
devtoolsMcpSelection
|
|
4949
|
+
});
|
|
4008
4950
|
}
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
mode,
|
|
4015
|
-
playwrightToolConsent,
|
|
4016
|
-
devtoolsMcpSelection
|
|
4017
|
-
});
|
|
4018
|
-
process.exitCode = installExitCode;
|
|
4951
|
+
if (runtimes.includes("pi")) {
|
|
4952
|
+
if (flags.dryRun) p6.log.info(`Pi: ${command} previsto; dry-run no ejecuta subprocess ni escribe receipt.`);
|
|
4953
|
+
else exitCode = Math.max(exitCode, await runSelectedPi(command, flags.targetDir, flags.yes));
|
|
4954
|
+
}
|
|
4955
|
+
process.exitCode = exitCode;
|
|
4019
4956
|
return;
|
|
4020
4957
|
}
|
|
4021
4958
|
case "uninstall": {
|
|
@@ -4026,41 +4963,56 @@ Flags disponibles: jorgex-stack --help`
|
|
|
4026
4963
|
process.exitCode = 1;
|
|
4027
4964
|
return;
|
|
4028
4965
|
}
|
|
4029
|
-
|
|
4030
|
-
|
|
4966
|
+
const fileRuntimes = runtimes.filter(isFileManagedRuntime);
|
|
4967
|
+
let exitCode = fileRuntimes.length > 0 || flags.removePlaywright ? await runUninstall({
|
|
4968
|
+
runtimes: fileRuntimes,
|
|
4031
4969
|
targetDir: flags.targetDir,
|
|
4032
4970
|
dryRun: flags.dryRun,
|
|
4033
4971
|
yes: flags.yes,
|
|
4034
4972
|
removeEngram: flags.removeEngram,
|
|
4035
4973
|
removePlaywright: flags.removePlaywright
|
|
4036
|
-
});
|
|
4974
|
+
}) : 0;
|
|
4975
|
+
if (runtimes.includes("pi")) {
|
|
4976
|
+
if (flags.dryRun) p6.log.info("Pi: uninstall previsto; dry-run conserva paquete y receipt.");
|
|
4977
|
+
else exitCode = Math.max(exitCode, await runSelectedPi("uninstall", flags.targetDir));
|
|
4978
|
+
}
|
|
4979
|
+
process.exitCode = exitCode;
|
|
4037
4980
|
return;
|
|
4038
4981
|
}
|
|
4039
4982
|
case "doctor": {
|
|
4040
|
-
|
|
4983
|
+
const fileDoctorSelected = flags.agents.length === 0 || flags.agents.some(isFileManagedRuntime);
|
|
4984
|
+
let exitCode = fileDoctorSelected ? await runDoctor() : 0;
|
|
4985
|
+
const piSelected = flags.agents.includes("pi") || flags.agents.length === 0 && detectPiRuntime().installed && hasManagedPiRuntime(flags.targetDir);
|
|
4986
|
+
if (piSelected) exitCode = Math.max(exitCode, await runSelectedPi("doctor", flags.targetDir));
|
|
4987
|
+
process.exitCode = exitCode;
|
|
4041
4988
|
return;
|
|
4042
4989
|
}
|
|
4043
4990
|
case "update": {
|
|
4044
|
-
if (flags.check) {
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
process.exitCode =
|
|
4991
|
+
if (flags.check || flags.dryRun) {
|
|
4992
|
+
const piExplicit = flags.agents.includes("pi");
|
|
4993
|
+
const fileRuntimeExplicit = flags.agents.some(isFileManagedRuntime);
|
|
4994
|
+
let exitCode = flags.agents.length === 0 || fileRuntimeExplicit ? await runUpdateCheck(VERSION, flags.targetDir === void 0) : 0;
|
|
4995
|
+
if (piExplicit) exitCode = Math.max(exitCode, await runSelectedPi("doctor", flags.targetDir));
|
|
4996
|
+
process.exitCode = exitCode;
|
|
4050
4997
|
return;
|
|
4051
4998
|
}
|
|
4052
4999
|
const runtimes = await resolveRuntimes(flags);
|
|
4053
5000
|
if (runtimes === null) return;
|
|
5001
|
+
const fileRuntimes = runtimes.filter(isFileManagedRuntime);
|
|
5002
|
+
if (fileRuntimes.length === 0 && runtimes.includes("pi")) {
|
|
5003
|
+
process.exitCode = await runSelectedPi("update", flags.targetDir);
|
|
5004
|
+
return;
|
|
5005
|
+
}
|
|
4054
5006
|
const preferenceFile = installModePreferenceFile();
|
|
4055
5007
|
const explicitMode = flags.mode !== void 0 || flags.subagentConcurrency !== void 0;
|
|
4056
5008
|
const hasSavedMode = hasInstallModePreference(preferenceFile);
|
|
4057
5009
|
const canResolveMode = flags.targetDir !== void 0 || explicitMode || hasSavedMode;
|
|
4058
|
-
const mode =
|
|
5010
|
+
const mode = fileRuntimes.length > 0 && canResolveMode ? await resolveInstallMode(flags, false) : DEFAULT_INSTALL_MODE_PREFERENCE;
|
|
4059
5011
|
if (mode === null) return;
|
|
4060
|
-
const canSync =
|
|
4061
|
-
if (
|
|
5012
|
+
const canSync = fileRuntimes.length === 0 || canResolveMode;
|
|
5013
|
+
if (fileRuntimes.length > 0 && canSync) {
|
|
4062
5014
|
const code = await runInstall({
|
|
4063
|
-
runtimes,
|
|
5015
|
+
runtimes: fileRuntimes,
|
|
4064
5016
|
targetDir: flags.targetDir,
|
|
4065
5017
|
dryRun: flags.dryRun,
|
|
4066
5018
|
yes: true,
|
|
@@ -4070,7 +5022,7 @@ Flags disponibles: jorgex-stack --help`
|
|
|
4070
5022
|
process.exitCode = code;
|
|
4071
5023
|
return;
|
|
4072
5024
|
}
|
|
4073
|
-
} else if (
|
|
5025
|
+
} else if (fileRuntimes.length > 0) {
|
|
4074
5026
|
console.error("No hay modo guardado; se omite el sync previo y se contin\xFAa con update. Usa --mode expl\xEDcito si quieres sincronizar.");
|
|
4075
5027
|
}
|
|
4076
5028
|
const result = await runInteractiveUpdate(
|
|
@@ -4080,13 +5032,16 @@ Flags disponibles: jorgex-stack --help`
|
|
|
4080
5032
|
flags.targetDir === void 0
|
|
4081
5033
|
);
|
|
4082
5034
|
process.exitCode = result.exitCode;
|
|
4083
|
-
if (result.
|
|
5035
|
+
if (result.exitCode === 0 && runtimes.includes("pi")) {
|
|
5036
|
+
process.exitCode = Math.max(process.exitCode, await runSelectedPi("update", flags.targetDir));
|
|
5037
|
+
}
|
|
5038
|
+
if (result.syncRequired && fileRuntimes.length > 0 && (result.exitCode !== 0 || !canSync)) {
|
|
4084
5039
|
p6.log.warn("Skills/stack actualizados, pero el sync con los runtimes sigue pendiente. Ejecuta jorgex-stack sync --mode human|programmatic.");
|
|
4085
|
-
} else if (result.exitCode === 0 && result.syncRequired &&
|
|
5040
|
+
} else if (result.exitCode === 0 && result.syncRequired && fileRuntimes.length > 0 && canSync && !flags.yes && process.stdout.isTTY) {
|
|
4086
5041
|
const apply = await p6.confirm({ message: "\xBFRe-aplicar a los runtimes ahora? (sync)" });
|
|
4087
5042
|
if (!p6.isCancel(apply) && apply) {
|
|
4088
5043
|
process.exitCode = await runInstall({
|
|
4089
|
-
runtimes,
|
|
5044
|
+
runtimes: fileRuntimes,
|
|
4090
5045
|
targetDir: flags.targetDir,
|
|
4091
5046
|
dryRun: false,
|
|
4092
5047
|
yes: false,
|
|
@@ -4095,7 +5050,7 @@ Flags disponibles: jorgex-stack --help`
|
|
|
4095
5050
|
} else {
|
|
4096
5051
|
console.log("Sin aplicar. Cuando quieras: jorgex-stack sync");
|
|
4097
5052
|
}
|
|
4098
|
-
} else if (result.exitCode === 0 && result.syncRequired &&
|
|
5053
|
+
} else if (result.exitCode === 0 && result.syncRequired && fileRuntimes.length > 0 && canSync && (flags.yes || !process.stdout.isTTY)) {
|
|
4099
5054
|
console.log("Skills/stack actualizados. Ejecuta jorgex-stack sync para aplicarlos a los runtimes.");
|
|
4100
5055
|
}
|
|
4101
5056
|
return;
|
|
@@ -4104,13 +5059,15 @@ Flags disponibles: jorgex-stack --help`
|
|
|
4104
5059
|
const runtimes = await resolveRuntimes(flags);
|
|
4105
5060
|
if (runtimes === null) return;
|
|
4106
5061
|
if (runtimes.length === 0) {
|
|
4107
|
-
console.error("Ning\xFAn runtime detectado (opencode, claude-code, codex).");
|
|
5062
|
+
console.error("Ning\xFAn runtime detectado (opencode, claude-code, codex, pi).");
|
|
4108
5063
|
process.exitCode = 1;
|
|
4109
5064
|
return;
|
|
4110
5065
|
}
|
|
4111
|
-
const
|
|
5066
|
+
const fileRuntimes = runtimes.filter(isFileManagedRuntime);
|
|
5067
|
+
let code = fileRuntimes.length > 0 ? await runModelsPicker({ yes: flags.yes, runtimes: fileRuntimes }) : 0;
|
|
5068
|
+
if (runtimes.includes("pi")) code = Math.max(code, await runSelectedPi("models", flags.targetDir));
|
|
4112
5069
|
process.exitCode = code;
|
|
4113
|
-
if (code === 0 && !flags.yes && process.stdout.isTTY) {
|
|
5070
|
+
if (code === 0 && fileRuntimes.length > 0 && !flags.yes && process.stdout.isTTY) {
|
|
4114
5071
|
const apply = await p6.confirm({ message: "\xBFAplicar ahora los modelos a los agentes instalados? (sync)" });
|
|
4115
5072
|
if (!p6.isCancel(apply) && apply) {
|
|
4116
5073
|
const preferenceFile = installModePreferenceFile();
|
|
@@ -4123,7 +5080,7 @@ Flags disponibles: jorgex-stack --help`
|
|
|
4123
5080
|
}
|
|
4124
5081
|
const mode = await resolveInstallMode(flags, false);
|
|
4125
5082
|
if (mode === null) return;
|
|
4126
|
-
process.exitCode = await runInstall({ runtimes, targetDir: flags.targetDir, dryRun: flags.dryRun, yes: false, mode });
|
|
5083
|
+
process.exitCode = await runInstall({ runtimes: fileRuntimes, targetDir: flags.targetDir, dryRun: flags.dryRun, yes: false, mode });
|
|
4127
5084
|
} else {
|
|
4128
5085
|
console.log("Sin aplicar. Cuando quieras: jorgex-stack sync");
|
|
4129
5086
|
}
|
package/package.json
CHANGED