jorgex-stack 1.2.0 → 1.2.2
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 +24 -6
- package/dist/cli.js +983 -53
- package/package.json +1 -1
- package/stack/skills/diagnose/SKILL.md +31 -8
- package/stack/skills/mcp-builder/LICENSE.txt +2 -2
- package/stack/skills/playwright-cli/SKILL.md +2 -2
- package/stack/skills/playwright-cli/references/test-generation.md +3 -3
- package/stack/skills/skill-creator/LICENSE.txt +2 -2
- package/stack/skills/supabase/SKILL.md +35 -9
- package/stack/skills/supabase-postgres-best-practices/SKILL.md +1 -1
- package/stack/skills/tdd/SKILL.md +4 -0
- package/stack/skills/to-issues/SKILL.md +15 -1
- package/upstreams.json +19 -17
package/dist/cli.js
CHANGED
|
@@ -1654,7 +1654,7 @@ import { execFileSync as execFileSync2 } from "child_process";
|
|
|
1654
1654
|
var PLAYWRIGHT_CLI = {
|
|
1655
1655
|
packageName: "@playwright/cli",
|
|
1656
1656
|
bin: "playwright-cli",
|
|
1657
|
-
version: "0.1.
|
|
1657
|
+
version: "0.1.18",
|
|
1658
1658
|
browserInstallAction: "install-browser"
|
|
1659
1659
|
};
|
|
1660
1660
|
var PNPM_GLOBAL_BIN_REMEDY = "Ejecuta 'pnpm setup', abre una terminal nueva y reintenta. El stack no modifica PNPM_HOME ni PATH.";
|
|
@@ -3286,7 +3286,7 @@ async function runInteractiveUpdate(localVersion, yes, dryRun = false, includeBr
|
|
|
3286
3286
|
updateItems.push({
|
|
3287
3287
|
value: "playwright-cli",
|
|
3288
3288
|
label: `Playwright CLI: ${current} \u2192 pin aprobado`,
|
|
3289
|
-
hint: "pnpm add --global @playwright/cli@0.1.
|
|
3289
|
+
hint: "pnpm add --global @playwright/cli@0.1.18"
|
|
3290
3290
|
});
|
|
3291
3291
|
}
|
|
3292
3292
|
}
|
|
@@ -3713,6 +3713,856 @@ 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(packageSource);
|
|
3759
|
+
return sources.every((source) => source !== null) ? sources : null;
|
|
3760
|
+
} catch {
|
|
3761
|
+
return null;
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
function expectedReceipt(candidate, state, scope) {
|
|
3765
|
+
return {
|
|
3766
|
+
schemaVersion: 1,
|
|
3767
|
+
state,
|
|
3768
|
+
candidate: {
|
|
3769
|
+
package: candidate.package,
|
|
3770
|
+
tarball: candidate.tarball,
|
|
3771
|
+
provenance: candidate.provenance
|
|
3772
|
+
},
|
|
3773
|
+
scope
|
|
3774
|
+
};
|
|
3775
|
+
}
|
|
3776
|
+
function parseReceipt(receiptJson, candidate, scope) {
|
|
3777
|
+
try {
|
|
3778
|
+
const parsed = JSON.parse(receiptJson);
|
|
3779
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3780
|
+
const state = Reflect.get(parsed, "state");
|
|
3781
|
+
if (state !== "installing" && state !== "installed") return null;
|
|
3782
|
+
const expected = expectedReceipt(candidate, state, scope);
|
|
3783
|
+
return sameRecord(parsed, expected) ? expected : null;
|
|
3784
|
+
} catch {
|
|
3785
|
+
return null;
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
function candidateIsValid(candidate, observed) {
|
|
3789
|
+
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);
|
|
3790
|
+
}
|
|
3791
|
+
function planPiPackageLifecycle(input) {
|
|
3792
|
+
if (!candidateIsValid(input.candidate, input.observedTarball)) {
|
|
3793
|
+
return blocked(input, "tarball-integrity");
|
|
3794
|
+
}
|
|
3795
|
+
if (!input.candidate.pi.testedVersions.includes(input.pi.version)) {
|
|
3796
|
+
return blocked(input, "unsupported-pi-version");
|
|
3797
|
+
}
|
|
3798
|
+
if (input.engramBin === null) return blocked(input, "engram-missing");
|
|
3799
|
+
const sources = parsePackageSources(input.pi.settingsJson);
|
|
3800
|
+
if (sources === null) return blocked(input, "settings-corrupt");
|
|
3801
|
+
const matchingSources = sources.filter(isJorgeXPiSource);
|
|
3802
|
+
const exactSources = matchingSources.filter((source) => source === input.candidate.package.source);
|
|
3803
|
+
if (exactSources.length > 1) return blocked(input, "duplicate-package");
|
|
3804
|
+
if (matchingSources.some((source) => source !== input.candidate.package.source)) {
|
|
3805
|
+
return blocked(input, "source-divergent");
|
|
3806
|
+
}
|
|
3807
|
+
let receipt = null;
|
|
3808
|
+
if (input.receiptJson !== null) {
|
|
3809
|
+
receipt = parseReceipt(input.receiptJson, input.candidate, {
|
|
3810
|
+
kind: input.scope.kind,
|
|
3811
|
+
codingAgentDir: path29.resolve(input.scope.codingAgentDir)
|
|
3812
|
+
});
|
|
3813
|
+
if (receipt === null) return blocked(input, "receipt-corrupt");
|
|
3814
|
+
if (receipt.state === "installing") return blocked(input, "partial-state");
|
|
3815
|
+
if (exactSources.length !== 1) return blocked(input, "partial-state");
|
|
3816
|
+
}
|
|
3817
|
+
if (exactSources.length === 1 && receipt === null) {
|
|
3818
|
+
return {
|
|
3819
|
+
kind: "manual-existing",
|
|
3820
|
+
receiptPath: input.scope.receiptPath,
|
|
3821
|
+
ownership: ownership(false)
|
|
3822
|
+
};
|
|
3823
|
+
}
|
|
3824
|
+
if (exactSources.length === 1 && receipt !== null) {
|
|
3825
|
+
return {
|
|
3826
|
+
kind: "ready",
|
|
3827
|
+
receiptPath: input.scope.receiptPath,
|
|
3828
|
+
ownership: ownership(true)
|
|
3829
|
+
};
|
|
3830
|
+
}
|
|
3831
|
+
return {
|
|
3832
|
+
kind: "install",
|
|
3833
|
+
receiptPath: input.scope.receiptPath,
|
|
3834
|
+
invocation: {
|
|
3835
|
+
executable: input.pi.executable,
|
|
3836
|
+
args: ["install", input.candidate.package.source, "--no-approve"],
|
|
3837
|
+
environment: input.scope.environment
|
|
3838
|
+
},
|
|
3839
|
+
receipt: expectedReceipt(input.candidate, "installing", {
|
|
3840
|
+
kind: input.scope.kind,
|
|
3841
|
+
codingAgentDir: path29.resolve(input.scope.codingAgentDir)
|
|
3842
|
+
}),
|
|
3843
|
+
ownership: ownership(true)
|
|
3844
|
+
};
|
|
3845
|
+
}
|
|
3846
|
+
function parseRunnerRecord(stdout, stderr, command, candidate, packageRunner) {
|
|
3847
|
+
if (stderr !== "" || !stdout.endsWith("\n") || Buffer.byteLength(stdout) > candidate.contract.runner.maxStdoutBytes) {
|
|
3848
|
+
return null;
|
|
3849
|
+
}
|
|
3850
|
+
const body = stdout.slice(0, -1);
|
|
3851
|
+
if (body === "" || body.includes("\n") || body.includes("\r")) return null;
|
|
3852
|
+
try {
|
|
3853
|
+
const parsed = JSON.parse(body);
|
|
3854
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3855
|
+
const record = parsed;
|
|
3856
|
+
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")) {
|
|
3857
|
+
return null;
|
|
3858
|
+
}
|
|
3859
|
+
return record;
|
|
3860
|
+
} catch {
|
|
3861
|
+
return null;
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
function runPackageCommand(input, deps, command) {
|
|
3865
|
+
const result = deps.run({
|
|
3866
|
+
executable: input.packageRunner,
|
|
3867
|
+
args: [command, "--json"],
|
|
3868
|
+
environment: input.environment
|
|
3869
|
+
});
|
|
3870
|
+
if (result.exitCode !== 0) return { kind: "blocked", reason: "runner-unhealthy" };
|
|
3871
|
+
return parseRunnerRecord(result.stdout, result.stderr, command, input.candidate, input.packageRunner) ?? { kind: "blocked", reason: "runner-output" };
|
|
3872
|
+
}
|
|
3873
|
+
function isBlockedResult(value) {
|
|
3874
|
+
return "kind" in value;
|
|
3875
|
+
}
|
|
3876
|
+
function executePiPackageLifecycle(input, deps) {
|
|
3877
|
+
if (input.plan.kind === "manual-existing") return { kind: "manual-existing" };
|
|
3878
|
+
if (input.operation === "install") {
|
|
3879
|
+
if (input.plan.kind !== "install" || input.plan.receipt === void 0 || input.plan.invocation === void 0) {
|
|
3880
|
+
return { kind: "blocked", reason: "runner-unhealthy" };
|
|
3881
|
+
}
|
|
3882
|
+
deps.writeReceipt(input.plan.receipt);
|
|
3883
|
+
const installed = deps.run(input.plan.invocation);
|
|
3884
|
+
if (installed.exitCode !== 0 || installed.stderr !== "") {
|
|
3885
|
+
return { kind: "blocked", reason: "pi-install-failed" };
|
|
3886
|
+
}
|
|
3887
|
+
const doctor = runPackageCommand(input, deps, "doctor");
|
|
3888
|
+
if (isBlockedResult(doctor)) return doctor;
|
|
3889
|
+
const doctorResult = doctor.result;
|
|
3890
|
+
if (doctorResult === null || typeof doctorResult !== "object" || Reflect.get(doctorResult, "healthy") !== true) {
|
|
3891
|
+
return { kind: "blocked", reason: "runner-unhealthy" };
|
|
3892
|
+
}
|
|
3893
|
+
const receipt = { ...input.plan.receipt, state: "installed" };
|
|
3894
|
+
deps.writeReceipt(receipt);
|
|
3895
|
+
return { kind: "installed", receipt };
|
|
3896
|
+
}
|
|
3897
|
+
if (input.plan.kind !== "ready") return { kind: "blocked", reason: "runner-unhealthy" };
|
|
3898
|
+
const command = runPackageCommand(input, deps, input.operation);
|
|
3899
|
+
if (isBlockedResult(command)) return command;
|
|
3900
|
+
if (input.operation === "sync") {
|
|
3901
|
+
const result = command.result;
|
|
3902
|
+
if (result === null || typeof result !== "object" || Reflect.get(result, "changed") !== false) {
|
|
3903
|
+
return { kind: "blocked", reason: "runner-unhealthy" };
|
|
3904
|
+
}
|
|
3905
|
+
return { kind: "synced", actions: [] };
|
|
3906
|
+
}
|
|
3907
|
+
const models = command.result;
|
|
3908
|
+
if (models === null || typeof models !== "object" || Reflect.get(models, "mode") !== "inherit-session" || !sameRecord(Reflect.get(models, "tiers"), ["strong", "standard", "cheap"])) {
|
|
3909
|
+
return { kind: "blocked", reason: "runner-unhealthy" };
|
|
3910
|
+
}
|
|
3911
|
+
return { kind: "models", models: { mode: "inherit-session", tiers: ["strong", "standard", "cheap"] } };
|
|
3912
|
+
}
|
|
3913
|
+
function readReceiptCandidate(receiptJson) {
|
|
3914
|
+
try {
|
|
3915
|
+
const parsed = JSON.parse(receiptJson);
|
|
3916
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3917
|
+
const schemaVersion = Reflect.get(parsed, "schemaVersion");
|
|
3918
|
+
const state = Reflect.get(parsed, "state");
|
|
3919
|
+
const candidate = Reflect.get(parsed, "candidate");
|
|
3920
|
+
if (schemaVersion !== 1 || state !== "installing" && state !== "installed" || candidate === null || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
3921
|
+
return null;
|
|
3922
|
+
}
|
|
3923
|
+
const packageValue = Reflect.get(candidate, "package");
|
|
3924
|
+
const tarball = Reflect.get(candidate, "tarball");
|
|
3925
|
+
const provenance = Reflect.get(candidate, "provenance");
|
|
3926
|
+
const scope = Reflect.get(parsed, "scope");
|
|
3927
|
+
if (packageValue === null || typeof packageValue !== "object" || tarball === null || typeof tarball !== "object" || provenance === null || typeof provenance !== "object" || scope === null || typeof scope !== "object" || Array.isArray(scope)) {
|
|
3928
|
+
return null;
|
|
3929
|
+
}
|
|
3930
|
+
const source = Reflect.get(packageValue, "source");
|
|
3931
|
+
const name = Reflect.get(packageValue, "name");
|
|
3932
|
+
const version = Reflect.get(packageValue, "version");
|
|
3933
|
+
if (name !== "jorgex-pi" || typeof version !== "string" || typeof source !== "string" || source !== `npm:jorgex-pi@${version}`) {
|
|
3934
|
+
return null;
|
|
3935
|
+
}
|
|
3936
|
+
const scopeKind = Reflect.get(scope, "kind");
|
|
3937
|
+
const codingAgentDir = Reflect.get(scope, "codingAgentDir");
|
|
3938
|
+
if (scopeKind !== "real" && scopeKind !== "target-dir" || typeof codingAgentDir !== "string") return null;
|
|
3939
|
+
return parsed;
|
|
3940
|
+
} catch {
|
|
3941
|
+
return null;
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
function validateOwnedOperationState(input) {
|
|
3945
|
+
const sources = parsePackageSources(input.detected.settingsJson);
|
|
3946
|
+
if (sources === null) return { kind: "blocked", reason: "settings-corrupt" };
|
|
3947
|
+
const matchingSources = sources.filter(isJorgeXPiSource);
|
|
3948
|
+
if (matchingSources.length > 1) return { kind: "blocked", reason: "duplicate-package" };
|
|
3949
|
+
if (input.receiptJson === null) {
|
|
3950
|
+
return { kind: "blocked", reason: matchingSources.length === 1 ? "manual-existing" : "source-divergent" };
|
|
3951
|
+
}
|
|
3952
|
+
const receipt = readReceiptCandidate(input.receiptJson);
|
|
3953
|
+
if (receipt === null) return { kind: "blocked", reason: "receipt-corrupt" };
|
|
3954
|
+
if (receipt.state !== "installed") return { kind: "blocked", reason: "partial-state" };
|
|
3955
|
+
const accepted = input.registry.acceptedCandidates ?? [input.registry.candidate];
|
|
3956
|
+
if (!accepted.some((candidate) => sameRecord(receipt.candidate, {
|
|
3957
|
+
package: candidate.package,
|
|
3958
|
+
tarball: candidate.tarball,
|
|
3959
|
+
provenance: candidate.provenance
|
|
3960
|
+
}))) {
|
|
3961
|
+
return { kind: "blocked", reason: "receipt-untrusted" };
|
|
3962
|
+
}
|
|
3963
|
+
if (receipt.scope.kind !== (input.paths.targetDir ? "target-dir" : "real") || path29.resolve(receipt.scope.codingAgentDir) !== path29.resolve(input.paths.codingAgentDir)) {
|
|
3964
|
+
return { kind: "blocked", reason: "source-divergent" };
|
|
3965
|
+
}
|
|
3966
|
+
const source = receipt.candidate.package.source;
|
|
3967
|
+
if (matchingSources.length !== 1 || matchingSources[0] !== source) {
|
|
3968
|
+
return { kind: "blocked", reason: "source-divergent" };
|
|
3969
|
+
}
|
|
3970
|
+
return { receipt, source };
|
|
3971
|
+
}
|
|
3972
|
+
function operationWasBlocked(value) {
|
|
3973
|
+
return "kind" in value;
|
|
3974
|
+
}
|
|
3975
|
+
function runManagedRunner(input, deps, command) {
|
|
3976
|
+
const result = deps.run({
|
|
3977
|
+
executable: input.detected.packageRunner,
|
|
3978
|
+
args: [command, "--json"],
|
|
3979
|
+
environment: input.paths.environment
|
|
3980
|
+
});
|
|
3981
|
+
if (result.exitCode !== 0) return { kind: "blocked", reason: "runner-unhealthy" };
|
|
3982
|
+
const parsed = parseRunnerRecord(
|
|
3983
|
+
result.stdout,
|
|
3984
|
+
result.stderr,
|
|
3985
|
+
command,
|
|
3986
|
+
input.registry.candidate,
|
|
3987
|
+
input.detected.packageRunner
|
|
3988
|
+
);
|
|
3989
|
+
return parsed ?? { kind: "blocked", reason: "runner-output" };
|
|
3990
|
+
}
|
|
3991
|
+
function managedRunnerWasBlocked(value) {
|
|
3992
|
+
return "kind" in value;
|
|
3993
|
+
}
|
|
3994
|
+
function runPiPackageManagedOperation(input, deps) {
|
|
3995
|
+
if (input.engramBin === null && input.operation !== "uninstall") {
|
|
3996
|
+
return {
|
|
3997
|
+
kind: "blocked",
|
|
3998
|
+
reason: "engram-missing",
|
|
3999
|
+
remedy: "Instala Engram o configura un ENGRAM_BIN absoluto antes de reintentar."
|
|
4000
|
+
};
|
|
4001
|
+
}
|
|
4002
|
+
const owned = validateOwnedOperationState(input);
|
|
4003
|
+
if (operationWasBlocked(owned)) return owned;
|
|
4004
|
+
if (input.operation === "doctor") {
|
|
4005
|
+
if (!sameRecord(owned.receipt.candidate, {
|
|
4006
|
+
package: input.registry.candidate.package,
|
|
4007
|
+
tarball: input.registry.candidate.tarball,
|
|
4008
|
+
provenance: input.registry.candidate.provenance
|
|
4009
|
+
})) {
|
|
4010
|
+
return { kind: "blocked", reason: "source-divergent" };
|
|
4011
|
+
}
|
|
4012
|
+
const doctor = runManagedRunner(input, deps, "doctor");
|
|
4013
|
+
if (managedRunnerWasBlocked(doctor)) return doctor;
|
|
4014
|
+
const result = doctor.result;
|
|
4015
|
+
return result !== null && typeof result === "object" && Reflect.get(result, "healthy") === true ? { kind: "healthy" } : { kind: "blocked", reason: "runner-unhealthy" };
|
|
4016
|
+
}
|
|
4017
|
+
if (input.operation === "uninstall") {
|
|
4018
|
+
if (!sameRecord(owned.receipt.candidate, {
|
|
4019
|
+
package: input.registry.candidate.package,
|
|
4020
|
+
tarball: input.registry.candidate.tarball,
|
|
4021
|
+
provenance: input.registry.candidate.provenance
|
|
4022
|
+
})) {
|
|
4023
|
+
return { kind: "blocked", reason: "source-divergent" };
|
|
4024
|
+
}
|
|
4025
|
+
const cleanup = runManagedRunner(input, deps, "cleanup");
|
|
4026
|
+
if (managedRunnerWasBlocked(cleanup)) return cleanup;
|
|
4027
|
+
deps.backupSettings();
|
|
4028
|
+
const removed = deps.run({
|
|
4029
|
+
executable: input.detected.executable,
|
|
4030
|
+
args: ["remove", owned.source, "--no-approve"],
|
|
4031
|
+
environment: input.paths.environment
|
|
4032
|
+
});
|
|
4033
|
+
if (removed.exitCode !== 0 || removed.stderr !== "") return { kind: "blocked", reason: "remove-failed" };
|
|
4034
|
+
if (!deps.isPackageAbsent()) return { kind: "blocked", reason: "absence-unverified" };
|
|
4035
|
+
deps.deleteReceipt();
|
|
4036
|
+
return { kind: "uninstalled" };
|
|
4037
|
+
}
|
|
4038
|
+
const nextSource = input.registry.candidate.package.source;
|
|
4039
|
+
if (nextSource === owned.source) return { kind: "healthy" };
|
|
4040
|
+
return {
|
|
4041
|
+
kind: "blocked",
|
|
4042
|
+
reason: "verified-update-required",
|
|
4043
|
+
remedy: "A cross-version Pi update requires verified replacement and rollback tgz artifacts."
|
|
4044
|
+
};
|
|
4045
|
+
}
|
|
4046
|
+
|
|
4047
|
+
// src/lib/pi-runtime.ts
|
|
4048
|
+
var PI_RUNTIME_CANDIDATE = {
|
|
4049
|
+
package: {
|
|
4050
|
+
name: "jorgex-pi",
|
|
4051
|
+
version: "0.1.0",
|
|
4052
|
+
source: "npm:jorgex-pi@0.1.0"
|
|
4053
|
+
},
|
|
4054
|
+
provenance: {
|
|
4055
|
+
commit: "791db79e33efd6661899995b5491e4dff5caa363"
|
|
4056
|
+
},
|
|
4057
|
+
tarball: {
|
|
4058
|
+
bytes: 89066153,
|
|
4059
|
+
sha256: "6243bf8e3a8dbe7be9103d7ca9b03e196c41ac9eef6578f47ea6d03655366feb",
|
|
4060
|
+
sha512: "07590abec9e9594b001e28d75eb810259c4088f9f2f6d1d5b9fe456bb2d15a7259ff31ee225d5f1f59b27a1c337a1f1f8e3e57089656bf7bbad966e653110ddd"
|
|
4061
|
+
},
|
|
4062
|
+
pi: {
|
|
4063
|
+
testedVersions: ["0.84.2"]
|
|
4064
|
+
},
|
|
4065
|
+
contract: {
|
|
4066
|
+
schemaVersion: 1,
|
|
4067
|
+
capabilities: [
|
|
4068
|
+
"foundation-contract-v1",
|
|
4069
|
+
"stack-snapshot-v1",
|
|
4070
|
+
"runtime-agents-v1",
|
|
4071
|
+
"permission-gated-tools-v1",
|
|
4072
|
+
"structured-questions-v1",
|
|
4073
|
+
"web-access-v1",
|
|
4074
|
+
"goal-continuation-v1",
|
|
4075
|
+
"mcp-adapter-v1",
|
|
4076
|
+
"engram-runtime-tools-v1",
|
|
4077
|
+
"runner-json-v1"
|
|
4078
|
+
],
|
|
4079
|
+
runner: {
|
|
4080
|
+
bin: "jorgex-pi",
|
|
4081
|
+
commands: ["status", "doctor", "models", "sync", "cleanup"],
|
|
4082
|
+
schemaVersion: 1,
|
|
4083
|
+
maxStdoutBytes: 65536
|
|
4084
|
+
},
|
|
4085
|
+
managedExternalWrites: []
|
|
4086
|
+
}
|
|
4087
|
+
};
|
|
4088
|
+
var PI_RUNTIME_REGISTRY = {
|
|
4089
|
+
pi: {
|
|
4090
|
+
id: "pi",
|
|
4091
|
+
kind: "package-managed",
|
|
4092
|
+
source: PI_RUNTIME_CANDIDATE.package.source,
|
|
4093
|
+
tarball: PI_RUNTIME_CANDIDATE.tarball,
|
|
4094
|
+
pi: PI_RUNTIME_CANDIDATE.pi,
|
|
4095
|
+
candidate: PI_RUNTIME_CANDIDATE,
|
|
4096
|
+
acceptedCandidates: [PI_RUNTIME_CANDIDATE]
|
|
4097
|
+
}
|
|
4098
|
+
};
|
|
4099
|
+
async function resolvePiEngramRequirement(input, deps) {
|
|
4100
|
+
if (input.targetDir !== void 0) {
|
|
4101
|
+
const targetBin = deps.detectTarget(input.targetDir);
|
|
4102
|
+
return targetBin === null ? {
|
|
4103
|
+
kind: "blocked",
|
|
4104
|
+
reason: "engram-missing-target",
|
|
4105
|
+
remedy: "A\xF1ade el binario Engram dentro del target-dir antes de reintentar."
|
|
4106
|
+
} : { kind: "existing", bin: targetBin, scope: "target-dir" };
|
|
4107
|
+
}
|
|
4108
|
+
const existing = deps.detectHost();
|
|
4109
|
+
if (existing !== null) return { kind: "existing", bin: existing, scope: "host" };
|
|
4110
|
+
if (input.yes || !input.interactive) {
|
|
4111
|
+
return {
|
|
4112
|
+
kind: "blocked",
|
|
4113
|
+
reason: "engram-required",
|
|
4114
|
+
remedy: "Instala Engram de forma interactiva o configura ENGRAM_BIN antes de reintentar."
|
|
4115
|
+
};
|
|
4116
|
+
}
|
|
4117
|
+
const accepted = await deps.confirm({
|
|
4118
|
+
message: "Engram es obligatorio para JorgeX Pi. \xBFInstalar ahora el binario mediante el canal nativo?",
|
|
4119
|
+
initialValue: false
|
|
4120
|
+
});
|
|
4121
|
+
if (!accepted) return { kind: "offer", accepted: false };
|
|
4122
|
+
const installed = await deps.installNative({ version: "1.20.0", channels: ["brew", "go", "url"] });
|
|
4123
|
+
if (!installed) {
|
|
4124
|
+
return {
|
|
4125
|
+
kind: "blocked",
|
|
4126
|
+
reason: "engram-install-failed",
|
|
4127
|
+
remedy: "Instala Engram manualmente o configura ENGRAM_BIN antes de reintentar."
|
|
4128
|
+
};
|
|
4129
|
+
}
|
|
4130
|
+
const detected = deps.detectHost();
|
|
4131
|
+
return detected === null ? {
|
|
4132
|
+
kind: "blocked",
|
|
4133
|
+
reason: "engram-install-unverified",
|
|
4134
|
+
remedy: "La instalaci\xF3n termin\xF3, pero Engram no qued\xF3 detectable; configura ENGRAM_BIN."
|
|
4135
|
+
} : { kind: "existing", bin: detected, scope: "host" };
|
|
4136
|
+
}
|
|
4137
|
+
function flatCandidateReceipt(candidate, scope, state) {
|
|
4138
|
+
const match = /^npm:jorgex-pi@([^\s]+)$/.exec(candidate.source);
|
|
4139
|
+
const packageValue = candidate.package ?? {
|
|
4140
|
+
name: "jorgex-pi",
|
|
4141
|
+
version: match?.[1] ?? "0.1.0",
|
|
4142
|
+
source: candidate.source
|
|
4143
|
+
};
|
|
4144
|
+
return {
|
|
4145
|
+
schemaVersion: 1,
|
|
4146
|
+
state,
|
|
4147
|
+
candidate: {
|
|
4148
|
+
package: packageValue,
|
|
4149
|
+
tarball: { bytes: candidate.bytes, sha256: candidate.sha256, sha512: candidate.sha512 },
|
|
4150
|
+
provenance: candidate.provenance ?? { commit: PI_RUNTIME_CANDIDATE.provenance.commit }
|
|
4151
|
+
},
|
|
4152
|
+
scope
|
|
4153
|
+
};
|
|
4154
|
+
}
|
|
4155
|
+
function normalizeInstalledSource(settingsJson, alias, canonical) {
|
|
4156
|
+
try {
|
|
4157
|
+
const parsed = JSON.parse(settingsJson);
|
|
4158
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
4159
|
+
const packages = Reflect.get(parsed, "packages");
|
|
4160
|
+
if (!Array.isArray(packages)) return null;
|
|
4161
|
+
if (packages.filter((entry) => entry === alias).length !== 1 || packages.includes(canonical)) return null;
|
|
4162
|
+
Reflect.set(parsed, "packages", packages.map((entry) => entry === alias ? canonical : entry));
|
|
4163
|
+
return JSON.stringify(parsed);
|
|
4164
|
+
} catch {
|
|
4165
|
+
return null;
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
function healthyDoctor(stdout, stderr, packageRunner, candidate) {
|
|
4169
|
+
if (stderr !== "" || !stdout.endsWith("\n") || stdout.slice(0, -1).includes("\n")) return false;
|
|
4170
|
+
try {
|
|
4171
|
+
const record = JSON.parse(stdout.slice(0, -1));
|
|
4172
|
+
if (record === null || typeof record !== "object" || Array.isArray(record)) return false;
|
|
4173
|
+
const packageValue = Reflect.get(record, "package");
|
|
4174
|
+
const result = Reflect.get(record, "result");
|
|
4175
|
+
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;
|
|
4176
|
+
} catch {
|
|
4177
|
+
return false;
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
function installPiFromVerifiedTarball(input, deps) {
|
|
4181
|
+
const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.piExecutable) : targetPaths(input.targetDir, input.engramBin, input.piExecutable);
|
|
4182
|
+
const destination = input.targetDir === void 0 ? path30.join(dataDir(), "packages", "jorgex-pi-0.1.0.tgz") : path30.join(path30.resolve(input.targetDir), "downloads", "jorgex-pi-0.1.0.tgz");
|
|
4183
|
+
const artifact = deps.download(destination);
|
|
4184
|
+
if (artifact.bytes !== input.candidate.bytes || artifact.sha256 !== input.candidate.sha256 || artifact.sha512 !== input.candidate.sha512) {
|
|
4185
|
+
return { kind: "blocked", reason: "tarball-integrity" };
|
|
4186
|
+
}
|
|
4187
|
+
deps.backupSettings();
|
|
4188
|
+
const scope = {
|
|
4189
|
+
kind: input.targetDir === void 0 ? "real" : "target-dir",
|
|
4190
|
+
codingAgentDir: path30.resolve(paths.codingAgentDir)
|
|
4191
|
+
};
|
|
4192
|
+
const installing = flatCandidateReceipt(input.candidate, scope, "installing");
|
|
4193
|
+
deps.writeReceiptAtomic(`${JSON.stringify(installing)}
|
|
4194
|
+
`);
|
|
4195
|
+
const alias = `npm:jorgex-pi@file:${artifact.path}`;
|
|
4196
|
+
const installed = deps.run({
|
|
4197
|
+
executable: input.piExecutable,
|
|
4198
|
+
args: ["install", alias, "--no-approve"],
|
|
4199
|
+
environment: paths.environment
|
|
4200
|
+
});
|
|
4201
|
+
if (installed.exitCode !== 0 || installed.stderr !== "") return { kind: "blocked", reason: "pi-install-failed" };
|
|
4202
|
+
const normalized = normalizeInstalledSource(deps.readSettings(), alias, input.candidate.source);
|
|
4203
|
+
if (normalized === null) return { kind: "blocked", reason: "settings-corrupt" };
|
|
4204
|
+
deps.rewriteSettings(normalized);
|
|
4205
|
+
const doctor = deps.run({
|
|
4206
|
+
executable: process.execPath,
|
|
4207
|
+
args: [paths.packageRunner, "doctor", "--json"],
|
|
4208
|
+
environment: paths.environment
|
|
4209
|
+
});
|
|
4210
|
+
if (doctor.exitCode !== 0 || !healthyDoctor(doctor.stdout, doctor.stderr, paths.packageRunner, input.candidate)) {
|
|
4211
|
+
return { kind: "blocked", reason: "runner-unhealthy" };
|
|
4212
|
+
}
|
|
4213
|
+
const receipt = flatCandidateReceipt(input.candidate, scope, "installed");
|
|
4214
|
+
deps.writeReceiptAtomic(`${JSON.stringify(receipt)}
|
|
4215
|
+
`);
|
|
4216
|
+
return { kind: "installed", receipt };
|
|
4217
|
+
}
|
|
4218
|
+
function runtimePath(piExecutable) {
|
|
4219
|
+
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"];
|
|
4220
|
+
return [...new Set(entries.filter((entry) => entry !== null))].join(path30.delimiter);
|
|
4221
|
+
}
|
|
4222
|
+
function targetPaths(targetDir, engramBin, piExecutable) {
|
|
4223
|
+
const root = path30.resolve(targetDir);
|
|
4224
|
+
const codingAgentDir = path30.join(root, "pi-agent");
|
|
4225
|
+
const home = path30.join(root, "home");
|
|
4226
|
+
const temporary = path30.join(root, "tmp");
|
|
4227
|
+
return {
|
|
4228
|
+
codingAgentDir,
|
|
4229
|
+
receiptPath: path30.join(root, "state", "pi-receipt.json"),
|
|
4230
|
+
packageRunner: path30.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
|
|
4231
|
+
environment: {
|
|
4232
|
+
HOME: home,
|
|
4233
|
+
USERPROFILE: home,
|
|
4234
|
+
APPDATA: path30.join(root, "appdata"),
|
|
4235
|
+
LOCALAPPDATA: path30.join(root, "localappdata"),
|
|
4236
|
+
XDG_CONFIG_HOME: path30.join(root, "xdg-config"),
|
|
4237
|
+
XDG_DATA_HOME: path30.join(root, "xdg-data"),
|
|
4238
|
+
XDG_CACHE_HOME: path30.join(root, "xdg-cache"),
|
|
4239
|
+
TEMP: temporary,
|
|
4240
|
+
TMP: temporary,
|
|
4241
|
+
TMPDIR: temporary,
|
|
4242
|
+
npm_config_cache: path30.join(root, "npm-cache"),
|
|
4243
|
+
NPM_CONFIG_IGNORE_SCRIPTS: "true",
|
|
4244
|
+
NPM_CONFIG_UPDATE_NOTIFIER: "false",
|
|
4245
|
+
PI_CODING_AGENT_DIR: codingAgentDir,
|
|
4246
|
+
...engramBin === null ? {} : { ENGRAM_BIN: engramBin },
|
|
4247
|
+
PATH: runtimePath(piExecutable)
|
|
4248
|
+
}
|
|
4249
|
+
};
|
|
4250
|
+
}
|
|
4251
|
+
function userPaths(engramBin, piExecutable) {
|
|
4252
|
+
const home = os5.homedir();
|
|
4253
|
+
const codingAgentDir = process.env.PI_CODING_AGENT_DIR ?? path30.join(home, ".pi", "agent");
|
|
4254
|
+
return {
|
|
4255
|
+
codingAgentDir,
|
|
4256
|
+
receiptPath: path30.join(dataDir(), "pi-receipt.json"),
|
|
4257
|
+
packageRunner: path30.join(codingAgentDir, "npm", "node_modules", "jorgex-pi", "bin", "jorgex-pi.mjs"),
|
|
4258
|
+
environment: {
|
|
4259
|
+
HOME: home,
|
|
4260
|
+
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME ?? path30.join(home, ".config"),
|
|
4261
|
+
XDG_CACHE_HOME: process.env.XDG_CACHE_HOME ?? path30.join(home, ".cache"),
|
|
4262
|
+
TMPDIR: os5.tmpdir(),
|
|
4263
|
+
NPM_CONFIG_IGNORE_SCRIPTS: "true",
|
|
4264
|
+
NPM_CONFIG_UPDATE_NOTIFIER: "false",
|
|
4265
|
+
PI_CODING_AGENT_DIR: codingAgentDir,
|
|
4266
|
+
...engramBin === null ? {} : { ENGRAM_BIN: engramBin },
|
|
4267
|
+
PATH: runtimePath(piExecutable)
|
|
4268
|
+
}
|
|
4269
|
+
};
|
|
4270
|
+
}
|
|
4271
|
+
function persistReturnedReceipt(result, paths, deps) {
|
|
4272
|
+
if (result.receipt !== void 0) {
|
|
4273
|
+
deps.writeReceiptAtomic(paths.receiptPath, `${JSON.stringify(result.receipt)}
|
|
4274
|
+
`);
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
function runPiRuntime(input, deps) {
|
|
4278
|
+
if (input.engramBin === null && input.operation !== "uninstall") {
|
|
4279
|
+
return {
|
|
4280
|
+
kind: "blocked",
|
|
4281
|
+
reason: "engram-missing",
|
|
4282
|
+
remedy: "Instala Engram o configura un ENGRAM_BIN absoluto antes de reintentar."
|
|
4283
|
+
};
|
|
4284
|
+
}
|
|
4285
|
+
if (input.operation === "install" && input.verifiedArtifact === void 0) {
|
|
4286
|
+
return { kind: "blocked", reason: "tarball-integrity" };
|
|
4287
|
+
}
|
|
4288
|
+
const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.detected.executable) : targetPaths(input.targetDir, input.engramBin, input.detected.executable);
|
|
4289
|
+
const settingsJson = deps.readSettings(path30.join(paths.codingAgentDir, "settings.json"));
|
|
4290
|
+
const receiptJson = deps.readReceipt(paths.receiptPath);
|
|
4291
|
+
const lifecycleInput = {
|
|
4292
|
+
candidate: PI_RUNTIME_CANDIDATE,
|
|
4293
|
+
observedTarball: input.verifiedArtifact ?? PI_RUNTIME_CANDIDATE.tarball,
|
|
4294
|
+
pi: {
|
|
4295
|
+
executable: input.detected.executable,
|
|
4296
|
+
version: input.detected.version,
|
|
4297
|
+
packageRunner: paths.packageRunner,
|
|
4298
|
+
settingsJson
|
|
4299
|
+
},
|
|
4300
|
+
engramBin: input.engramBin,
|
|
4301
|
+
receiptJson,
|
|
4302
|
+
scope: {
|
|
4303
|
+
kind: input.targetDir === void 0 ? "real" : "target-dir",
|
|
4304
|
+
codingAgentDir: paths.codingAgentDir,
|
|
4305
|
+
receiptPath: paths.receiptPath,
|
|
4306
|
+
environment: paths.environment
|
|
4307
|
+
}
|
|
4308
|
+
};
|
|
4309
|
+
if (input.operation === "install" || input.operation === "sync" || input.operation === "models") {
|
|
4310
|
+
const plan = deps.prepare(lifecycleInput);
|
|
4311
|
+
const result2 = deps.execute({
|
|
4312
|
+
operation: input.operation,
|
|
4313
|
+
plan,
|
|
4314
|
+
candidate: PI_RUNTIME_CANDIDATE,
|
|
4315
|
+
packageRunner: paths.packageRunner,
|
|
4316
|
+
environment: paths.environment
|
|
4317
|
+
});
|
|
4318
|
+
persistReturnedReceipt(result2, paths, deps);
|
|
4319
|
+
return result2;
|
|
4320
|
+
}
|
|
4321
|
+
const result = deps.operate({
|
|
4322
|
+
operation: input.operation,
|
|
4323
|
+
interactive: false,
|
|
4324
|
+
registry: PI_RUNTIME_REGISTRY.pi,
|
|
4325
|
+
detected: {
|
|
4326
|
+
executable: input.detected.executable,
|
|
4327
|
+
packageRunner: paths.packageRunner,
|
|
4328
|
+
settingsJson
|
|
4329
|
+
},
|
|
4330
|
+
engramBin: input.engramBin,
|
|
4331
|
+
receiptJson,
|
|
4332
|
+
paths: {
|
|
4333
|
+
targetDir: input.targetDir !== void 0,
|
|
4334
|
+
codingAgentDir: paths.codingAgentDir,
|
|
4335
|
+
receiptPath: paths.receiptPath,
|
|
4336
|
+
environment: paths.environment
|
|
4337
|
+
},
|
|
4338
|
+
removeArgs: ["remove", PI_RUNTIME_CANDIDATE.package.source, "--no-approve"]
|
|
4339
|
+
});
|
|
4340
|
+
persistReturnedReceipt(result, paths, deps);
|
|
4341
|
+
return result;
|
|
4342
|
+
}
|
|
4343
|
+
function readJsonFile(file) {
|
|
4344
|
+
return JSON.parse(fs22.readFileSync(file, "utf8"));
|
|
4345
|
+
}
|
|
4346
|
+
function packageVersionFromExecutable(executable) {
|
|
4347
|
+
let current;
|
|
4348
|
+
try {
|
|
4349
|
+
current = path30.dirname(fs22.realpathSync(executable));
|
|
4350
|
+
} catch {
|
|
4351
|
+
return null;
|
|
4352
|
+
}
|
|
4353
|
+
for (let depth = 0; depth < 8; depth++) {
|
|
4354
|
+
const manifests = [
|
|
4355
|
+
path30.join(current, "package.json"),
|
|
4356
|
+
path30.join(current, "node_modules", "@earendil-works", "pi-coding-agent", "package.json")
|
|
4357
|
+
];
|
|
4358
|
+
for (const manifest of manifests) {
|
|
4359
|
+
try {
|
|
4360
|
+
const parsed = readJsonFile(manifest);
|
|
4361
|
+
if (parsed !== null && typeof parsed === "object" && Reflect.get(parsed, "name") === "@earendil-works/pi-coding-agent" && typeof Reflect.get(parsed, "version") === "string") {
|
|
4362
|
+
return Reflect.get(parsed, "version");
|
|
4363
|
+
}
|
|
4364
|
+
} catch {
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4367
|
+
const parent = path30.dirname(current);
|
|
4368
|
+
if (parent === current) break;
|
|
4369
|
+
current = parent;
|
|
4370
|
+
}
|
|
4371
|
+
return null;
|
|
4372
|
+
}
|
|
4373
|
+
function detectPiRuntime() {
|
|
4374
|
+
const executable = lookPath("pi");
|
|
4375
|
+
const home = os5.homedir();
|
|
4376
|
+
return {
|
|
4377
|
+
id: "pi",
|
|
4378
|
+
name: "Pi",
|
|
4379
|
+
installed: executable !== null,
|
|
4380
|
+
executable,
|
|
4381
|
+
version: executable === null ? null : packageVersionFromExecutable(executable),
|
|
4382
|
+
codingAgentDir: process.env.PI_CODING_AGENT_DIR ?? path30.join(home, ".pi", "agent")
|
|
4383
|
+
};
|
|
4384
|
+
}
|
|
4385
|
+
function hasManagedPiRuntime(targetDir) {
|
|
4386
|
+
const receipt = targetDir === void 0 ? path30.join(dataDir(), "pi-receipt.json") : path30.join(path30.resolve(targetDir), "state", "pi-receipt.json");
|
|
4387
|
+
return fs22.statSync(receipt, { throwIfNoEntry: false })?.isFile() === true;
|
|
4388
|
+
}
|
|
4389
|
+
function resolvePiEngramBin(targetDir) {
|
|
4390
|
+
if (targetDir === void 0) return detectEngram();
|
|
4391
|
+
const candidate = path30.join(path30.resolve(targetDir), "bin", process.platform === "win32" ? "engram.exe" : "engram");
|
|
4392
|
+
return fs22.statSync(candidate, { throwIfNoEntry: false })?.isFile() ? candidate : null;
|
|
4393
|
+
}
|
|
4394
|
+
function readOptional(file, fallback) {
|
|
4395
|
+
try {
|
|
4396
|
+
return fs22.readFileSync(file, "utf8");
|
|
4397
|
+
} catch (error) {
|
|
4398
|
+
if (error.code === "ENOENT") return fallback;
|
|
4399
|
+
throw error;
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
function hashPiTarball(file) {
|
|
4403
|
+
const descriptor = fs22.openSync(file, "r");
|
|
4404
|
+
const sha256 = createHash("sha256");
|
|
4405
|
+
const sha512 = createHash("sha512");
|
|
4406
|
+
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
4407
|
+
let bytes = 0;
|
|
4408
|
+
try {
|
|
4409
|
+
while (true) {
|
|
4410
|
+
const read = fs22.readSync(descriptor, buffer, 0, buffer.length, null);
|
|
4411
|
+
if (read === 0) break;
|
|
4412
|
+
bytes += read;
|
|
4413
|
+
const chunk = buffer.subarray(0, read);
|
|
4414
|
+
sha256.update(chunk);
|
|
4415
|
+
sha512.update(chunk);
|
|
4416
|
+
}
|
|
4417
|
+
} finally {
|
|
4418
|
+
fs22.closeSync(descriptor);
|
|
4419
|
+
}
|
|
4420
|
+
return { path: file, bytes, sha256: sha256.digest("hex"), sha512: sha512.digest("hex") };
|
|
4421
|
+
}
|
|
4422
|
+
async function acquirePiTarball(destination) {
|
|
4423
|
+
const existing = fs22.statSync(destination, { throwIfNoEntry: false });
|
|
4424
|
+
if (existing?.isFile()) {
|
|
4425
|
+
const observed = hashPiTarball(destination);
|
|
4426
|
+
if (observed.bytes === PI_RUNTIME_CANDIDATE.tarball.bytes && observed.sha256 === PI_RUNTIME_CANDIDATE.tarball.sha256 && observed.sha512 === PI_RUNTIME_CANDIDATE.tarball.sha512) {
|
|
4427
|
+
return observed;
|
|
4428
|
+
}
|
|
4429
|
+
fs22.rmSync(destination, { force: true });
|
|
4430
|
+
}
|
|
4431
|
+
fs22.mkdirSync(path30.dirname(destination), { recursive: true });
|
|
4432
|
+
const partial = `${destination}.partial-${process.pid}`;
|
|
4433
|
+
const response = await fetch("https://registry.npmjs.org/jorgex-pi/-/jorgex-pi-0.1.0.tgz", {
|
|
4434
|
+
redirect: "error",
|
|
4435
|
+
headers: { accept: "application/octet-stream" }
|
|
4436
|
+
});
|
|
4437
|
+
if (!response.ok || response.body === null) throw new Error(`No se pudo descargar jorgex-pi@0.1.0 (${response.status}).`);
|
|
4438
|
+
const descriptor = fs22.openSync(partial, "wx", 384);
|
|
4439
|
+
let bytes = 0;
|
|
4440
|
+
try {
|
|
4441
|
+
for await (const chunk of response.body) {
|
|
4442
|
+
const buffer = Buffer.from(chunk);
|
|
4443
|
+
bytes += buffer.length;
|
|
4444
|
+
if (bytes > PI_RUNTIME_CANDIDATE.tarball.bytes) throw new Error("El tarball de jorgex-pi excede el tama\xF1o fijado.");
|
|
4445
|
+
let offset = 0;
|
|
4446
|
+
while (offset < buffer.length) offset += fs22.writeSync(descriptor, buffer, offset);
|
|
4447
|
+
}
|
|
4448
|
+
} catch (error) {
|
|
4449
|
+
fs22.closeSync(descriptor);
|
|
4450
|
+
fs22.rmSync(partial, { force: true });
|
|
4451
|
+
throw error;
|
|
4452
|
+
}
|
|
4453
|
+
fs22.closeSync(descriptor);
|
|
4454
|
+
fs22.renameSync(partial, destination);
|
|
4455
|
+
return hashPiTarball(destination);
|
|
4456
|
+
}
|
|
4457
|
+
function runProcess(invocation) {
|
|
4458
|
+
const planned = /\.mjs$/i.test(invocation.executable) ? { command: process.execPath, args: [invocation.executable, ...invocation.args] } : planDetectedBinCommand(invocation.executable, invocation.args);
|
|
4459
|
+
if (planned === null) return { exitCode: 1, stdout: "", stderr: "unsafe executable" };
|
|
4460
|
+
const result = spawnSync(planned.command, planned.args, {
|
|
4461
|
+
encoding: "utf8",
|
|
4462
|
+
env: invocation.environment,
|
|
4463
|
+
shell: false,
|
|
4464
|
+
timeout: 12e4,
|
|
4465
|
+
maxBuffer: PI_RUNTIME_CANDIDATE.contract.runner.maxStdoutBytes + 1,
|
|
4466
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4467
|
+
});
|
|
4468
|
+
return {
|
|
4469
|
+
exitCode: result.status ?? 1,
|
|
4470
|
+
stdout: result.stdout ?? "",
|
|
4471
|
+
stderr: result.stderr ?? (result.error?.message ?? "")
|
|
4472
|
+
};
|
|
4473
|
+
}
|
|
4474
|
+
async function runPiRuntimeSystem(input) {
|
|
4475
|
+
if (input.engramBin === null && input.operation !== "uninstall") return runPiRuntime(input, {
|
|
4476
|
+
readSettings: () => {
|
|
4477
|
+
throw new Error("unreachable");
|
|
4478
|
+
},
|
|
4479
|
+
readReceipt: () => {
|
|
4480
|
+
throw new Error("unreachable");
|
|
4481
|
+
},
|
|
4482
|
+
writeReceiptAtomic: () => {
|
|
4483
|
+
throw new Error("unreachable");
|
|
4484
|
+
},
|
|
4485
|
+
prepare: () => {
|
|
4486
|
+
throw new Error("unreachable");
|
|
4487
|
+
},
|
|
4488
|
+
execute: () => {
|
|
4489
|
+
throw new Error("unreachable");
|
|
4490
|
+
},
|
|
4491
|
+
operate: () => {
|
|
4492
|
+
throw new Error("unreachable");
|
|
4493
|
+
}
|
|
4494
|
+
});
|
|
4495
|
+
const paths = input.targetDir === void 0 ? userPaths(input.engramBin, input.detected.executable) : targetPaths(input.targetDir, input.engramBin, input.detected.executable);
|
|
4496
|
+
if (input.operation === "install") {
|
|
4497
|
+
if (input.engramBin === null) {
|
|
4498
|
+
return {
|
|
4499
|
+
kind: "blocked",
|
|
4500
|
+
reason: "engram-required",
|
|
4501
|
+
remedy: "Instala Engram o configura un ENGRAM_BIN absoluto antes de reintentar."
|
|
4502
|
+
};
|
|
4503
|
+
}
|
|
4504
|
+
const destination = input.targetDir === void 0 ? path30.join(dataDir(), "packages", "jorgex-pi-0.1.0.tgz") : path30.join(path30.resolve(input.targetDir), "downloads", "jorgex-pi-0.1.0.tgz");
|
|
4505
|
+
let artifact;
|
|
4506
|
+
try {
|
|
4507
|
+
artifact = await acquirePiTarball(destination);
|
|
4508
|
+
} catch (error) {
|
|
4509
|
+
return { kind: "blocked", reason: "tarball-download", remedy: error instanceof Error ? error.message : String(error) };
|
|
4510
|
+
}
|
|
4511
|
+
return installPiFromVerifiedTarball({
|
|
4512
|
+
targetDir: input.targetDir,
|
|
4513
|
+
piExecutable: input.detected.executable,
|
|
4514
|
+
engramBin: input.engramBin,
|
|
4515
|
+
candidate: {
|
|
4516
|
+
source: PI_RUNTIME_CANDIDATE.package.source,
|
|
4517
|
+
...PI_RUNTIME_CANDIDATE.tarball,
|
|
4518
|
+
package: PI_RUNTIME_CANDIDATE.package,
|
|
4519
|
+
provenance: PI_RUNTIME_CANDIDATE.provenance
|
|
4520
|
+
}
|
|
4521
|
+
}, {
|
|
4522
|
+
download: () => artifact,
|
|
4523
|
+
backupSettings: () => createBackup(
|
|
4524
|
+
[path30.join(paths.codingAgentDir, "settings.json")],
|
|
4525
|
+
"pi-package-install",
|
|
4526
|
+
input.targetDir === void 0 ? void 0 : path30.join(path30.resolve(input.targetDir), "backups")
|
|
4527
|
+
),
|
|
4528
|
+
run: runProcess,
|
|
4529
|
+
readSettings: () => readOptional(path30.join(paths.codingAgentDir, "settings.json"), '{"packages":[]}'),
|
|
4530
|
+
rewriteSettings: (content) => writeText(path30.join(paths.codingAgentDir, "settings.json"), `${content}
|
|
4531
|
+
`),
|
|
4532
|
+
writeReceiptAtomic: (content) => writeText(paths.receiptPath, content)
|
|
4533
|
+
});
|
|
4534
|
+
}
|
|
4535
|
+
const packageRoot = path30.dirname(path30.dirname(paths.packageRunner));
|
|
4536
|
+
const writeReceipt = (receipt) => {
|
|
4537
|
+
writeText(paths.receiptPath, `${JSON.stringify(receipt, null, 2)}
|
|
4538
|
+
`);
|
|
4539
|
+
};
|
|
4540
|
+
const deps = {
|
|
4541
|
+
readSettings: (file) => readOptional(file, '{"packages":[]}'),
|
|
4542
|
+
readReceipt: (file) => readOptional(file, null),
|
|
4543
|
+
writeReceiptAtomic: (file, content) => writeText(file, content),
|
|
4544
|
+
prepare: (value) => planPiPackageLifecycle(value),
|
|
4545
|
+
execute: (value) => executePiPackageLifecycle(
|
|
4546
|
+
value,
|
|
4547
|
+
{ writeReceipt, run: runProcess }
|
|
4548
|
+
),
|
|
4549
|
+
operate: (value) => runPiPackageManagedOperation(
|
|
4550
|
+
value,
|
|
4551
|
+
{
|
|
4552
|
+
backupSettings: () => createBackup(
|
|
4553
|
+
[path30.join(paths.codingAgentDir, "settings.json")],
|
|
4554
|
+
"pi-package-uninstall",
|
|
4555
|
+
input.targetDir === void 0 ? void 0 : path30.join(path30.resolve(input.targetDir), "backups")
|
|
4556
|
+
),
|
|
4557
|
+
run: runProcess,
|
|
4558
|
+
isPackageAbsent: () => !fs22.existsSync(packageRoot),
|
|
4559
|
+
deleteReceipt: () => fs22.rmSync(paths.receiptPath, { force: true })
|
|
4560
|
+
}
|
|
4561
|
+
)
|
|
4562
|
+
};
|
|
4563
|
+
return runPiRuntime(input, deps);
|
|
4564
|
+
}
|
|
4565
|
+
|
|
3716
4566
|
// src/cli.ts
|
|
3717
4567
|
var VERSION = readPackageVersion();
|
|
3718
4568
|
var COMMANDS = ["install", "sync", "models", "update", "doctor", "restore", "uninstall"];
|
|
@@ -3911,19 +4761,71 @@ function parseCliArgs(argv) {
|
|
|
3911
4761
|
if (flags.unknownFlags.length > 0) return { action: "unknown-flags", command, flags };
|
|
3912
4762
|
return { action: "run", command, flags };
|
|
3913
4763
|
}
|
|
3914
|
-
|
|
4764
|
+
function isFileManagedRuntime(runtime) {
|
|
4765
|
+
return runtime !== "pi";
|
|
4766
|
+
}
|
|
4767
|
+
async function resolveRuntimes(flags, includeAvailablePi = false) {
|
|
3915
4768
|
if (flags.agents.length > 0) return flags.agents;
|
|
3916
|
-
const detected = Object.values(ADAPTERS).filter((
|
|
4769
|
+
const detected = Object.values(ADAPTERS).filter((adapter) => adapter.detect().installed).map((adapter) => ({ id: adapter.id, name: adapter.name }));
|
|
4770
|
+
const pi = detectPiRuntime();
|
|
4771
|
+
if (pi.installed && (includeAvailablePi || hasManagedPiRuntime(flags.targetDir))) {
|
|
4772
|
+
detected.push({ id: "pi", name: "Pi" });
|
|
4773
|
+
}
|
|
3917
4774
|
if (detected.length === 0) return [];
|
|
3918
|
-
if (flags.yes || !process.stdout.isTTY || flags.targetDir !== void 0) return detected.map((
|
|
4775
|
+
if (flags.yes || !process.stdout.isTTY || flags.targetDir !== void 0) return detected.map((runtime) => runtime.id);
|
|
3919
4776
|
const choice = await p6.multiselect({
|
|
3920
4777
|
message: "\xBFEn qu\xE9 runtimes? (detectados en esta m\xE1quina)",
|
|
3921
|
-
options: detected.map((
|
|
3922
|
-
initialValues: detected.map((
|
|
4778
|
+
options: detected.map((runtime) => ({ value: runtime.id, label: runtime.name })),
|
|
4779
|
+
initialValues: detected.map((runtime) => runtime.id)
|
|
3923
4780
|
});
|
|
3924
4781
|
if (p6.isCancel(choice)) return null;
|
|
3925
4782
|
return choice;
|
|
3926
4783
|
}
|
|
4784
|
+
async function runSelectedPi(operation, targetDir, yes = false) {
|
|
4785
|
+
const detected = detectPiRuntime();
|
|
4786
|
+
if (!detected.installed || detected.executable === null) {
|
|
4787
|
+
console.error("Pi no detectado. Instala el runtime Pi antes de gestionar jorgex-pi.");
|
|
4788
|
+
return 1;
|
|
4789
|
+
}
|
|
4790
|
+
if (detected.version === null) {
|
|
4791
|
+
console.error("No se pudo verificar la versi\xF3n instalada de Pi sin ejecutarlo; revisa la instalaci\xF3n de Pi.");
|
|
4792
|
+
return 1;
|
|
4793
|
+
}
|
|
4794
|
+
let engramBin = resolvePiEngramBin(targetDir);
|
|
4795
|
+
if (operation === "install" && engramBin === null) {
|
|
4796
|
+
const requirement = await resolvePiEngramRequirement({
|
|
4797
|
+
targetDir,
|
|
4798
|
+
interactive: process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
4799
|
+
yes
|
|
4800
|
+
}, {
|
|
4801
|
+
detectHost: () => resolvePiEngramBin(),
|
|
4802
|
+
detectTarget: (root) => resolvePiEngramBin(root),
|
|
4803
|
+
confirm: async ({ message, initialValue }) => {
|
|
4804
|
+
const answer = await p6.confirm({ message, initialValue });
|
|
4805
|
+
return !p6.isCancel(answer) && answer;
|
|
4806
|
+
},
|
|
4807
|
+
installNative: async ({ version }) => updateEngram("Gentleman-Programming/engram", version)
|
|
4808
|
+
});
|
|
4809
|
+
if (requirement.kind !== "existing") {
|
|
4810
|
+
console.error(requirement.kind === "offer" ? "Pi: instalaci\xF3n cancelada; Engram sigue siendo obligatorio." : `Pi: ${requirement.reason}. ${requirement.remedy}`);
|
|
4811
|
+
return 1;
|
|
4812
|
+
}
|
|
4813
|
+
engramBin = requirement.bin;
|
|
4814
|
+
}
|
|
4815
|
+
const result = await runPiRuntimeSystem({
|
|
4816
|
+
operation,
|
|
4817
|
+
targetDir,
|
|
4818
|
+
detected: { executable: detected.executable, version: detected.version },
|
|
4819
|
+
engramBin
|
|
4820
|
+
});
|
|
4821
|
+
if (result.kind === "blocked") {
|
|
4822
|
+
console.error(`Pi: ${result.reason ?? "operaci\xF3n bloqueada"}${result.remedy ? `. ${result.remedy}` : ""}`);
|
|
4823
|
+
return 1;
|
|
4824
|
+
}
|
|
4825
|
+
if (result.kind === "models" && result.models !== void 0) console.log(JSON.stringify(result.models));
|
|
4826
|
+
else p6.log.success(`Pi: ${result.kind}.`);
|
|
4827
|
+
return 0;
|
|
4828
|
+
}
|
|
3927
4829
|
function printHelp() {
|
|
3928
4830
|
console.log(`jorgex-stack v${VERSION}
|
|
3929
4831
|
|
|
@@ -3941,7 +4843,7 @@ Comandos:
|
|
|
3941
4843
|
desregistrarlo exige --remove-engram o el s\xED expl\xEDcito
|
|
3942
4844
|
|
|
3943
4845
|
Opciones:
|
|
3944
|
-
--agents, -a opencode,claude-code,codex Runtimes destino (default: detectados)
|
|
4846
|
+
--agents, -a opencode,claude-code,codex,pi Runtimes destino (default: detectados)
|
|
3945
4847
|
--mode human|programmatic Modo de instalaci\xF3n (default: preferencia guardada o human)
|
|
3946
4848
|
--subagent-concurrency serial|parallel Concurrencia de subagentes en modo programmatic
|
|
3947
4849
|
--target-dir <dir> Dir alternativo (pruebas de paridad; requiere 1 runtime)
|
|
@@ -3988,34 +4890,42 @@ Flags disponibles: jorgex-stack --help`
|
|
|
3988
4890
|
switch (command) {
|
|
3989
4891
|
case "install":
|
|
3990
4892
|
case "sync": {
|
|
3991
|
-
const
|
|
3992
|
-
if (mode === null) return;
|
|
3993
|
-
const runtimes = await resolveRuntimes(flags);
|
|
4893
|
+
const runtimes = await resolveRuntimes(flags, command === "install");
|
|
3994
4894
|
if (runtimes === null) return;
|
|
3995
4895
|
if (runtimes.length === 0) {
|
|
3996
|
-
console.error("Ning\xFAn runtime detectado (opencode, claude-code, codex).");
|
|
4896
|
+
console.error("Ning\xFAn runtime detectado (opencode, claude-code, codex, pi).");
|
|
3997
4897
|
process.exitCode = 1;
|
|
3998
4898
|
return;
|
|
3999
4899
|
}
|
|
4000
|
-
const
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4900
|
+
const fileRuntimes = runtimes.filter(isFileManagedRuntime);
|
|
4901
|
+
let exitCode = 0;
|
|
4902
|
+
if (fileRuntimes.length > 0) {
|
|
4903
|
+
const mode = await resolveInstallMode(flags);
|
|
4904
|
+
if (mode === null) return;
|
|
4905
|
+
const devtoolsMcpSelection = await resolveDevtoolsMcpSelection(command, flags, fileRuntimes);
|
|
4906
|
+
if (devtoolsMcpSelection === null) return;
|
|
4907
|
+
const playwrightToolConsent = await resolvePlaywrightToolConsent(command, flags);
|
|
4908
|
+
if (playwrightToolConsent === null) return;
|
|
4909
|
+
const hasOpenCodeModels = await ensureOpenCodeModelsForInstall(command, flags, fileRuntimes);
|
|
4910
|
+
if (!hasOpenCodeModels) {
|
|
4911
|
+
process.exitCode = 1;
|
|
4912
|
+
return;
|
|
4913
|
+
}
|
|
4914
|
+
exitCode = await runInstall({
|
|
4915
|
+
runtimes: fileRuntimes,
|
|
4916
|
+
targetDir: flags.targetDir,
|
|
4917
|
+
dryRun: flags.dryRun,
|
|
4918
|
+
yes: flags.yes,
|
|
4919
|
+
mode,
|
|
4920
|
+
playwrightToolConsent,
|
|
4921
|
+
devtoolsMcpSelection
|
|
4922
|
+
});
|
|
4008
4923
|
}
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
mode,
|
|
4015
|
-
playwrightToolConsent,
|
|
4016
|
-
devtoolsMcpSelection
|
|
4017
|
-
});
|
|
4018
|
-
process.exitCode = installExitCode;
|
|
4924
|
+
if (runtimes.includes("pi")) {
|
|
4925
|
+
if (flags.dryRun) p6.log.info(`Pi: ${command} previsto; dry-run no ejecuta subprocess ni escribe receipt.`);
|
|
4926
|
+
else exitCode = Math.max(exitCode, await runSelectedPi(command, flags.targetDir, flags.yes));
|
|
4927
|
+
}
|
|
4928
|
+
process.exitCode = exitCode;
|
|
4019
4929
|
return;
|
|
4020
4930
|
}
|
|
4021
4931
|
case "uninstall": {
|
|
@@ -4026,41 +4936,56 @@ Flags disponibles: jorgex-stack --help`
|
|
|
4026
4936
|
process.exitCode = 1;
|
|
4027
4937
|
return;
|
|
4028
4938
|
}
|
|
4029
|
-
|
|
4030
|
-
|
|
4939
|
+
const fileRuntimes = runtimes.filter(isFileManagedRuntime);
|
|
4940
|
+
let exitCode = fileRuntimes.length > 0 || flags.removePlaywright ? await runUninstall({
|
|
4941
|
+
runtimes: fileRuntimes,
|
|
4031
4942
|
targetDir: flags.targetDir,
|
|
4032
4943
|
dryRun: flags.dryRun,
|
|
4033
4944
|
yes: flags.yes,
|
|
4034
4945
|
removeEngram: flags.removeEngram,
|
|
4035
4946
|
removePlaywright: flags.removePlaywright
|
|
4036
|
-
});
|
|
4947
|
+
}) : 0;
|
|
4948
|
+
if (runtimes.includes("pi")) {
|
|
4949
|
+
if (flags.dryRun) p6.log.info("Pi: uninstall previsto; dry-run conserva paquete y receipt.");
|
|
4950
|
+
else exitCode = Math.max(exitCode, await runSelectedPi("uninstall", flags.targetDir));
|
|
4951
|
+
}
|
|
4952
|
+
process.exitCode = exitCode;
|
|
4037
4953
|
return;
|
|
4038
4954
|
}
|
|
4039
4955
|
case "doctor": {
|
|
4040
|
-
|
|
4956
|
+
const fileDoctorSelected = flags.agents.length === 0 || flags.agents.some(isFileManagedRuntime);
|
|
4957
|
+
let exitCode = fileDoctorSelected ? await runDoctor() : 0;
|
|
4958
|
+
const piSelected = flags.agents.includes("pi") || flags.agents.length === 0 && detectPiRuntime().installed && hasManagedPiRuntime(flags.targetDir);
|
|
4959
|
+
if (piSelected) exitCode = Math.max(exitCode, await runSelectedPi("doctor", flags.targetDir));
|
|
4960
|
+
process.exitCode = exitCode;
|
|
4041
4961
|
return;
|
|
4042
4962
|
}
|
|
4043
4963
|
case "update": {
|
|
4044
|
-
if (flags.check) {
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
process.exitCode =
|
|
4964
|
+
if (flags.check || flags.dryRun) {
|
|
4965
|
+
const piExplicit = flags.agents.includes("pi");
|
|
4966
|
+
const fileRuntimeExplicit = flags.agents.some(isFileManagedRuntime);
|
|
4967
|
+
let exitCode = flags.agents.length === 0 || fileRuntimeExplicit ? await runUpdateCheck(VERSION, flags.targetDir === void 0) : 0;
|
|
4968
|
+
if (piExplicit) exitCode = Math.max(exitCode, await runSelectedPi("doctor", flags.targetDir));
|
|
4969
|
+
process.exitCode = exitCode;
|
|
4050
4970
|
return;
|
|
4051
4971
|
}
|
|
4052
4972
|
const runtimes = await resolveRuntimes(flags);
|
|
4053
4973
|
if (runtimes === null) return;
|
|
4974
|
+
const fileRuntimes = runtimes.filter(isFileManagedRuntime);
|
|
4975
|
+
if (fileRuntimes.length === 0 && runtimes.includes("pi")) {
|
|
4976
|
+
process.exitCode = await runSelectedPi("update", flags.targetDir);
|
|
4977
|
+
return;
|
|
4978
|
+
}
|
|
4054
4979
|
const preferenceFile = installModePreferenceFile();
|
|
4055
4980
|
const explicitMode = flags.mode !== void 0 || flags.subagentConcurrency !== void 0;
|
|
4056
4981
|
const hasSavedMode = hasInstallModePreference(preferenceFile);
|
|
4057
4982
|
const canResolveMode = flags.targetDir !== void 0 || explicitMode || hasSavedMode;
|
|
4058
|
-
const mode =
|
|
4983
|
+
const mode = fileRuntimes.length > 0 && canResolveMode ? await resolveInstallMode(flags, false) : DEFAULT_INSTALL_MODE_PREFERENCE;
|
|
4059
4984
|
if (mode === null) return;
|
|
4060
|
-
const canSync =
|
|
4061
|
-
if (
|
|
4985
|
+
const canSync = fileRuntimes.length === 0 || canResolveMode;
|
|
4986
|
+
if (fileRuntimes.length > 0 && canSync) {
|
|
4062
4987
|
const code = await runInstall({
|
|
4063
|
-
runtimes,
|
|
4988
|
+
runtimes: fileRuntimes,
|
|
4064
4989
|
targetDir: flags.targetDir,
|
|
4065
4990
|
dryRun: flags.dryRun,
|
|
4066
4991
|
yes: true,
|
|
@@ -4070,7 +4995,7 @@ Flags disponibles: jorgex-stack --help`
|
|
|
4070
4995
|
process.exitCode = code;
|
|
4071
4996
|
return;
|
|
4072
4997
|
}
|
|
4073
|
-
} else if (
|
|
4998
|
+
} else if (fileRuntimes.length > 0) {
|
|
4074
4999
|
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
5000
|
}
|
|
4076
5001
|
const result = await runInteractiveUpdate(
|
|
@@ -4080,13 +5005,16 @@ Flags disponibles: jorgex-stack --help`
|
|
|
4080
5005
|
flags.targetDir === void 0
|
|
4081
5006
|
);
|
|
4082
5007
|
process.exitCode = result.exitCode;
|
|
4083
|
-
if (result.
|
|
5008
|
+
if (result.exitCode === 0 && runtimes.includes("pi")) {
|
|
5009
|
+
process.exitCode = Math.max(process.exitCode, await runSelectedPi("update", flags.targetDir));
|
|
5010
|
+
}
|
|
5011
|
+
if (result.syncRequired && fileRuntimes.length > 0 && (result.exitCode !== 0 || !canSync)) {
|
|
4084
5012
|
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 &&
|
|
5013
|
+
} else if (result.exitCode === 0 && result.syncRequired && fileRuntimes.length > 0 && canSync && !flags.yes && process.stdout.isTTY) {
|
|
4086
5014
|
const apply = await p6.confirm({ message: "\xBFRe-aplicar a los runtimes ahora? (sync)" });
|
|
4087
5015
|
if (!p6.isCancel(apply) && apply) {
|
|
4088
5016
|
process.exitCode = await runInstall({
|
|
4089
|
-
runtimes,
|
|
5017
|
+
runtimes: fileRuntimes,
|
|
4090
5018
|
targetDir: flags.targetDir,
|
|
4091
5019
|
dryRun: false,
|
|
4092
5020
|
yes: false,
|
|
@@ -4095,7 +5023,7 @@ Flags disponibles: jorgex-stack --help`
|
|
|
4095
5023
|
} else {
|
|
4096
5024
|
console.log("Sin aplicar. Cuando quieras: jorgex-stack sync");
|
|
4097
5025
|
}
|
|
4098
|
-
} else if (result.exitCode === 0 && result.syncRequired &&
|
|
5026
|
+
} else if (result.exitCode === 0 && result.syncRequired && fileRuntimes.length > 0 && canSync && (flags.yes || !process.stdout.isTTY)) {
|
|
4099
5027
|
console.log("Skills/stack actualizados. Ejecuta jorgex-stack sync para aplicarlos a los runtimes.");
|
|
4100
5028
|
}
|
|
4101
5029
|
return;
|
|
@@ -4104,13 +5032,15 @@ Flags disponibles: jorgex-stack --help`
|
|
|
4104
5032
|
const runtimes = await resolveRuntimes(flags);
|
|
4105
5033
|
if (runtimes === null) return;
|
|
4106
5034
|
if (runtimes.length === 0) {
|
|
4107
|
-
console.error("Ning\xFAn runtime detectado (opencode, claude-code, codex).");
|
|
5035
|
+
console.error("Ning\xFAn runtime detectado (opencode, claude-code, codex, pi).");
|
|
4108
5036
|
process.exitCode = 1;
|
|
4109
5037
|
return;
|
|
4110
5038
|
}
|
|
4111
|
-
const
|
|
5039
|
+
const fileRuntimes = runtimes.filter(isFileManagedRuntime);
|
|
5040
|
+
let code = fileRuntimes.length > 0 ? await runModelsPicker({ yes: flags.yes, runtimes: fileRuntimes }) : 0;
|
|
5041
|
+
if (runtimes.includes("pi")) code = Math.max(code, await runSelectedPi("models", flags.targetDir));
|
|
4112
5042
|
process.exitCode = code;
|
|
4113
|
-
if (code === 0 && !flags.yes && process.stdout.isTTY) {
|
|
5043
|
+
if (code === 0 && fileRuntimes.length > 0 && !flags.yes && process.stdout.isTTY) {
|
|
4114
5044
|
const apply = await p6.confirm({ message: "\xBFAplicar ahora los modelos a los agentes instalados? (sync)" });
|
|
4115
5045
|
if (!p6.isCancel(apply) && apply) {
|
|
4116
5046
|
const preferenceFile = installModePreferenceFile();
|
|
@@ -4123,7 +5053,7 @@ Flags disponibles: jorgex-stack --help`
|
|
|
4123
5053
|
}
|
|
4124
5054
|
const mode = await resolveInstallMode(flags, false);
|
|
4125
5055
|
if (mode === null) return;
|
|
4126
|
-
process.exitCode = await runInstall({ runtimes, targetDir: flags.targetDir, dryRun: flags.dryRun, yes: false, mode });
|
|
5056
|
+
process.exitCode = await runInstall({ runtimes: fileRuntimes, targetDir: flags.targetDir, dryRun: flags.dryRun, yes: false, mode });
|
|
4127
5057
|
} else {
|
|
4128
5058
|
console.log("Sin aplicar. Cuando quieras: jorgex-stack sync");
|
|
4129
5059
|
}
|