jorgex-stack 1.9.27 → 1.9.29

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 CHANGED
@@ -56,7 +56,7 @@ pnpm dlx jorgex-stack uninstall # remove managed files; keep Engram data i
56
56
 
57
57
  For development from a clone, run the same commands through `pnpm cli <command>` (see [Development](#development)).
58
58
 
59
- Every command supports `--dry-run`, `--yes`, and `--target-dir <dir>` for testing without touching the real config. Writes create automatic backups and verify idempotency; merges into user config are surgical (marked markdown sections, JSON/TOML upserts), so user-owned content is never touched.
59
+ Every command supports `--dry-run`, `--yes`, and `--target-dir <dir>` for testing without touching the real config. Writes create automatic backups and verify idempotency; merges into user config are surgical (marked markdown sections, JSON/TOML upserts), so user-owned content is never touched. `--yes` does not authorize downloading missing Engram; use `--engram` for that explicit consent. The interactive install asks before installing it, while dry-run and target-dir never download it.
60
60
 
61
61
  Runtime defaults are documented in [docs/references/permissions.md](docs/references/permissions.md) for permissions and [docs/references/models.md](docs/references/models.md) for the Sol primary default, field-level ownership and independent subagent routing. The quality policy and `jorgex.quality.receipt` contract are documented in [docs/references/quality-receipt.md](docs/references/quality-receipt.md). OpenCode remains provider-agnostic for subagents; its primary defaults to the OpenAI OAuth model `openai/gpt-5.6-sol` unless the user replaces it.
62
62
 
@@ -132,7 +132,7 @@ Install, sync and uninstall back up every managed file before changing it and ar
132
132
 
133
133
  The package owns Pi's native primary-model projection: `openai-codex/gpt-5.6-sol`, with a local `contextWindow` request of 872K. It merges only missing compatible fields, records field ownership in `PI_CODING_AGENT_DIR/jorgex-pi/sol-lifecycle.v1.json`, and cleanup removes only still-owned canonical values. Stack does not duplicate that package-owned settings/models logic. The 872K value is local OAuth metadata until a real long-context smoke test confirms backend acceptance; it is not the API context limit.
134
134
 
135
- Engram remains mandatory and user-owned. An existing binary is preserved. Interactive install may offer the native `brew`/`go`/release channel with explicit confirmation; `--yes` and non-TTY installs fail with a remedy when Engram is absent. The database and memories are never updated or deleted, and uninstall never deletes the Engram binary. 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.
135
+ Engram remains mandatory and user-owned. An existing binary is preserved. `install` resolves or installs the verified official v1.20.0 release before configuring any selected runtime; use `--engram` to authorize that download in non-interactive flows. The release installer writes only `~/.local/bin/engram` (or the platform equivalent), checks the pinned size and SHA-256, and does not use Brew or Go. `sync`, dry-run and `--target-dir` never download it. The database and memories are never updated or deleted, and uninstall never deletes the Engram binary. 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.
136
136
 
137
137
  Históricamente, Stack `1.9.7` reconocía el receipt exacto de Pi `npm:jorgex-pi@0.8.4`. Usa versiones exactas, nunca `latest`, y no edites receipts o hashes ni borres `HOME`, Engram o la proyección de otro runtime para forzar confianza. El pin y los comandos actuales de transición y rollback están en [docs/references/pi-runtime.md](docs/references/pi-runtime.md).
138
138
 
package/dist/cli.d.ts CHANGED
@@ -21,6 +21,7 @@ interface Flags {
21
21
  list: boolean;
22
22
  check: boolean;
23
23
  removeEngram: boolean;
24
+ engram: boolean;
24
25
  playwright: boolean;
25
26
  removePlaywright: boolean;
26
27
  devtools: boolean;
package/dist/cli.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
 
16
16
  // src/cli.ts
17
17
  import * as p6 from "@clack/prompts";
18
- import fs24 from "fs";
18
+ import fs25 from "fs";
19
19
  import { pathToFileURL as pathToFileURL2 } from "url";
20
20
 
21
21
  // src/install.ts
@@ -2154,7 +2154,7 @@ var PLAYWRIGHT_CLI = {
2154
2154
  version: "0.1.18",
2155
2155
  browserInstallAction: "install-browser"
2156
2156
  };
2157
- var PNPM_GLOBAL_BIN_REMEDY = "Ejecuta 'pnpm setup', abre una terminal nueva y reintenta. El stack no modifica PNPM_HOME ni PATH.";
2157
+ var PNPM_GLOBAL_BIN_REMEDY = "Ejecuta 'pnpm setup', abre una terminal nueva y reintenta. Sin consentimiento, el stack no modifica la configuraci\xF3n de la shell.";
2158
2158
  function resolvePnpmFailureRemedy(reason) {
2159
2159
  switch (reason) {
2160
2160
  case "pnpm-unavailable":
@@ -2197,6 +2197,34 @@ function resolvePnpmBin() {
2197
2197
  const pnpmCmd = lookPath("pnpm.cmd");
2198
2198
  return pnpmCmd !== null && !pnpmCmd.toLowerCase().endsWith(".ps1") ? pnpmCmd : null;
2199
2199
  }
2200
+ function resolvePnpmHome(env = process.env, platform = process.platform, homeDir = os.homedir()) {
2201
+ const paths = platform === "win32" ? path18.win32 : path18.posix;
2202
+ if (env.PNPM_HOME) return env.PNPM_HOME;
2203
+ if (env.XDG_DATA_HOME) return paths.join(env.XDG_DATA_HOME, "pnpm");
2204
+ if (platform === "darwin") return paths.join(homeDir, "Library", "pnpm");
2205
+ if (platform === "win32") return env.LOCALAPPDATA ? paths.join(env.LOCALAPPDATA, "pnpm") : paths.join(homeDir, ".pnpm");
2206
+ return paths.join(homeDir, ".local", "share", "pnpm");
2207
+ }
2208
+ function setupPnpmGlobal(pnpmBin, env = process.env, platform = process.platform, homeDir = os.homedir()) {
2209
+ const pnpmHome = resolvePnpmHome(env, platform, homeDir);
2210
+ const paths = platform === "win32" ? path18.win32 : path18.posix;
2211
+ if (!paths.isAbsolute(pnpmHome)) return { ok: false, reason: "PNPM_HOME debe ser una ruta absoluta; corrige la configuraci\xF3n antes de reintentar." };
2212
+ const pathEntries = [pnpmHome, paths.join(pnpmHome, "bin")];
2213
+ if (env.PATH !== void 0) pathEntries.push(env.PATH);
2214
+ const childEnv = {
2215
+ ...env,
2216
+ PNPM_HOME: pnpmHome,
2217
+ PATH: pathEntries.join(platform === "win32" ? ";" : ":")
2218
+ };
2219
+ const invocation = planDetectedBinCommand(pnpmBin, ["setup"]);
2220
+ if (invocation === null) return { ok: false, reason: "No se pudo preparar la invocaci\xF3n de pnpm setup." };
2221
+ try {
2222
+ execFileSync2(invocation.command, invocation.args, { stdio: "inherit", env: childEnv });
2223
+ return { ok: true, env: childEnv };
2224
+ } catch (error) {
2225
+ return { ok: false, reason: error instanceof Error ? error.message : String(error) };
2226
+ }
2227
+ }
2200
2228
  function isPlaywrightBrowserReady(env = process.env, platform = process.platform, homeDir = os.homedir()) {
2201
2229
  const configuredPath = env.PLAYWRIGHT_BROWSERS_PATH;
2202
2230
  const cacheDir = configuredPath ?? (platform === "win32" ? path18.join(env.LOCALAPPDATA ?? path18.join(homeDir, "AppData", "Local"), "ms-playwright") : platform === "darwin" ? path18.join(homeDir, "Library", "Caches", "ms-playwright") : path18.join(env.XDG_CACHE_HOME ?? path18.join(homeDir, ".cache"), "ms-playwright"));
@@ -2223,13 +2251,16 @@ function planPlaywrightCliCommand(action, pnpmBin) {
2223
2251
  return { command: pnpmBin, args: ["dlx", pinnedPackage, PLAYWRIGHT_CLI.browserInstallAction] };
2224
2252
  }
2225
2253
  }
2226
- function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin()) {
2254
+ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin(), env) {
2227
2255
  if (pnpmBin === null) return { ok: false, reason: "pnpm-unavailable" };
2228
2256
  if (action !== "install-browser") {
2229
2257
  const preflight = planDetectedBinCommand(pnpmBin, ["bin", "--global"]);
2230
2258
  if (preflight === null) return { ok: false, reason: "pnpm-command" };
2231
2259
  try {
2232
- execFileSync2(preflight.command, preflight.args, { stdio: "inherit" });
2260
+ execFileSync2(preflight.command, preflight.args, {
2261
+ stdio: "inherit",
2262
+ ...env === void 0 ? {} : { env }
2263
+ });
2233
2264
  } catch (error) {
2234
2265
  return { ok: false, reason: hasNonzeroProcessStatus(error) ? "pnpm-global-bin" : "pnpm-command" };
2235
2266
  }
@@ -2238,7 +2269,10 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin()) {
2238
2269
  const invocation = planDetectedBinCommand(command.command, command.args);
2239
2270
  if (invocation === null) return { ok: false, reason: "pnpm-command" };
2240
2271
  try {
2241
- execFileSync2(invocation.command, invocation.args, { stdio: "inherit" });
2272
+ execFileSync2(invocation.command, invocation.args, {
2273
+ stdio: "inherit",
2274
+ ...env === void 0 ? {} : { env }
2275
+ });
2242
2276
  return { ok: true };
2243
2277
  } catch {
2244
2278
  return { ok: false, reason: "action-failed" };
@@ -2439,8 +2473,8 @@ var ADAPTERS = {
2439
2473
  "claude-code": claudeCodeAdapter,
2440
2474
  codex: codexAdapter
2441
2475
  };
2442
- function executePlaywrightToolAction2(action) {
2443
- return executePlaywrightToolAction(action, resolvePnpmBin());
2476
+ function executePlaywrightToolAction2(action, pnpmBin = resolvePnpmBin(), env) {
2477
+ return executePlaywrightToolAction(action, pnpmBin, env);
2444
2478
  }
2445
2479
  function resolvePlaywrightToolPlan(consent) {
2446
2480
  if (consent.command !== "install" || consent.targetDir) return { actions: [] };
@@ -2560,15 +2594,16 @@ function collectAllCurrentTargets(mode = DEFAULT_INSTALL_MODE_PREFERENCE) {
2560
2594
  return { targets, complete, warnings };
2561
2595
  }
2562
2596
  async function runInstall(opts) {
2563
- p.intro(`jorgex-stack ${opts.dryRun ? "install (dry-run)" : "install"}`);
2597
+ const showSummary = opts.showSummary !== false;
2598
+ if (showSummary) p.intro(`jorgex-stack ${opts.dryRun ? "install (dry-run)" : "install"}`);
2564
2599
  const stackDir = stackRoot();
2565
- const engramBin = detectEngram();
2600
+ const engramBin = opts.engramBin === void 0 ? detectEngram() : opts.engramBin;
2566
2601
  const modePreference = opts.mode === void 0 ? opts.targetDir === void 0 ? loadInstallModePreference() : DEFAULT_INSTALL_MODE_PREFERENCE : normalizeInstallModePreference(opts.mode);
2567
2602
  const useManifest = opts.targetDir === void 0;
2568
2603
  const preferenceErrors = useManifest ? [...browserPreferenceErrors(), primaryModelOwnershipError()].filter((error) => error !== null) : [];
2569
2604
  if (preferenceErrors.length > 0) {
2570
2605
  for (const error of preferenceErrors) p.log.error(error);
2571
- p.outro("Install cancelado: corrige el estado de configuraci\xF3n indicado arriba antes de reintentar.");
2606
+ if (showSummary) p.outro("Install cancelado: corrige el estado de configuraci\xF3n indicado arriba antes de reintentar.");
2572
2607
  return 1;
2573
2608
  }
2574
2609
  const toolPlan = opts.playwrightToolConsent === void 0 ? null : resolvePlaywrightToolPlan({
@@ -2708,9 +2743,41 @@ async function runInstall(opts) {
2708
2743
  if (opts.dryRun) {
2709
2744
  p.log.info("Playwright CLI: instalaci\xF3n global y navegador previstos (dry-run; no se ejecutan).");
2710
2745
  } else if (exitCode === 0) {
2711
- const result = await runPlaywrightToolPlan(toolPlan, opts.playwrightToolDeps ?? {
2712
- run: async (action) => executePlaywrightToolAction2(action),
2713
- persistEnabled: (enabled) => savePlaywrightCliPreference(playwrightCliPreferenceFile(), enabled)
2746
+ const baseDeps = opts.playwrightToolDeps ?? {
2747
+ run: async (action, env) => {
2748
+ const pnpmBin = resolvePnpmBin();
2749
+ return executePlaywrightToolAction2(action, pnpmBin, env);
2750
+ },
2751
+ persistEnabled: (enabled) => savePlaywrightCliPreference(playwrightCliPreferenceFile(), enabled),
2752
+ setupPnpm: (pnpmBin) => setupPnpmGlobal(pnpmBin)
2753
+ };
2754
+ let setupAttempted = false;
2755
+ let preparedEnv;
2756
+ const result = await runPlaywrightToolPlan(toolPlan, {
2757
+ ...baseDeps,
2758
+ run: async (action) => {
2759
+ const first = await baseDeps.run(action, preparedEnv);
2760
+ if (first === true || first === false || first.ok || first.reason !== "pnpm-global-bin") return first;
2761
+ if (setupAttempted || opts.dryRun || opts.targetDir !== void 0) return first;
2762
+ const consent = opts.playwrightToolConsent;
2763
+ const canAskForSetup = consent?.interactive === true && !opts.yes;
2764
+ if (!canAskForSetup) return first;
2765
+ setupAttempted = true;
2766
+ const accepted = await p.confirm({
2767
+ message: "pnpm no tiene directorio global. \xBFEjecutar 'pnpm setup' ahora? Esto modificar\xE1 la configuraci\xF3n de tu shell.",
2768
+ initialValue: false
2769
+ });
2770
+ if (p.isCancel(accepted) || !accepted) return first;
2771
+ const pnpmBin = resolvePnpmBin();
2772
+ if (pnpmBin === null) return first;
2773
+ const setup = baseDeps.setupPnpm?.(pnpmBin) ?? setupPnpmGlobal(pnpmBin);
2774
+ if (!setup.ok) {
2775
+ p.log.error(`pnpm setup fall\xF3: ${setup.reason}`);
2776
+ return first;
2777
+ }
2778
+ preparedEnv = setup.env;
2779
+ return baseDeps.run(action, preparedEnv);
2780
+ }
2714
2781
  });
2715
2782
  if (!result.ok) {
2716
2783
  const pnpmRemedy = result.reason === void 0 ? null : resolvePnpmFailureRemedy(result.reason);
@@ -2763,7 +2830,9 @@ async function runInstall(opts) {
2763
2830
  if (useManifest && !opts.dryRun && exitCode === 0 && successfulRuns > 0) {
2764
2831
  saveInstallModePreference(installModePreferenceFile(), modePreference);
2765
2832
  }
2766
- p.outro(opts.dryRun ? exitCode === 0 ? "Dry-run: no se ha escrito nada." : "Dry-run completado con errores (revisa arriba)." : exitCode === 0 ? "Hecho." : "Install completado con errores (revisa arriba).");
2833
+ if (showSummary) {
2834
+ p.outro(opts.dryRun ? exitCode === 0 ? "Dry-run: no se ha escrito nada." : "Dry-run completado con errores (revisa arriba)." : exitCode === 0 ? "Hecho." : "Install completado con errores (revisa arriba).");
2835
+ }
2767
2836
  return exitCode;
2768
2837
  }
2769
2838
 
@@ -5155,16 +5224,16 @@ import { createHash } from "crypto";
5155
5224
  var pi_runtime_pin_default = {
5156
5225
  package: {
5157
5226
  name: "jorgex-pi",
5158
- version: "0.8.11",
5159
- source: "npm:jorgex-pi@0.8.11"
5227
+ version: "0.8.12",
5228
+ source: "npm:jorgex-pi@0.8.12"
5160
5229
  },
5161
5230
  provenance: {
5162
- commit: "8690d0cab6e4149c95e299b2ef3d7277b8b06263"
5231
+ commit: "a041e8f290021b5a6bb161db77e03663f37c82cb"
5163
5232
  },
5164
5233
  tarball: {
5165
- bytes: 89141979,
5166
- sha256: "f0b34be384e5c761043348e7eb561fe56d475ef61479a0a93cd563d28dd28d8b",
5167
- sha512: "ad76a61dc7388b3b077f89a77dc211596f3d82b8f44a503552ba0eff655cbfdc3ee99b395d647806a637e87ed731483e71e6cfef9c21f3945ce3b4b8f3bd7c57"
5234
+ bytes: 89141968,
5235
+ sha256: "eb7739852504bf4cd8cdd9f260c67da979728378a756db7f1ff6b0c1d60368c6",
5236
+ sha512: "a37b36e5c846e1694db3c7bd7e9acf23f39b8ba45d348bf039fd218f90f041a6d95268af2e3b6248fc4e759dfd6441df1c44490411c1e13c2b0cedbc0ef526b2"
5168
5237
  }
5169
5238
  };
5170
5239
 
@@ -6167,6 +6236,112 @@ async function runQualityPlan(input) {
6167
6236
  return { evaluation, receipt };
6168
6237
  }
6169
6238
 
6239
+ // src/lib/engram-install.ts
6240
+ import fs24 from "fs";
6241
+ import os5 from "os";
6242
+ import path33 from "path";
6243
+ import { execFileSync as execFileSync7 } from "child_process";
6244
+ import { createHash as createHash2 } from "crypto";
6245
+
6246
+ // src/lib/engram-release.json
6247
+ var engram_release_default = {
6248
+ assets: {
6249
+ darwin_arm64: {
6250
+ name: "engram_1.20.0_darwin_arm64.tar.gz",
6251
+ sha256: "2363d5012f23e58878f86c3ddcc1f63cfe9dcf3eec7f413e70eaafcbb9d394cc",
6252
+ size: 6949031
6253
+ },
6254
+ darwin_x64: {
6255
+ name: "engram_1.20.0_darwin_amd64.tar.gz",
6256
+ sha256: "3b9015dcfcdd9f823eb7ad0b590b1dbc4c25bb5d81dda3f84e623709815afe80",
6257
+ size: 7367005
6258
+ },
6259
+ linux_arm64: {
6260
+ name: "engram_1.20.0_linux_arm64.tar.gz",
6261
+ sha256: "7eb815910a76ae6cfa9a5d0161d3701e293dcca71f7743cffa62e236e5af59af",
6262
+ size: 6673857
6263
+ },
6264
+ linux_x64: {
6265
+ name: "engram_1.20.0_linux_amd64.tar.gz",
6266
+ sha256: "7dc3003318e303bee269a4772144f3ce01c8ec700bfd524aaec76770acd389ca",
6267
+ size: 7236400
6268
+ },
6269
+ windows_arm64: {
6270
+ name: "engram_1.20.0_windows_arm64.zip",
6271
+ sha256: "eb93c6d7d5a4020ddcf1ff956909f3e90694f6447519433316b10f964f8f37b5",
6272
+ size: 6723366
6273
+ },
6274
+ windows_x64: {
6275
+ name: "engram_1.20.0_windows_amd64.zip",
6276
+ sha256: "c945c47d58367f943642cb8451fa45f208174407839f1dda33a975a05d6e465b",
6277
+ size: 7416778
6278
+ }
6279
+ },
6280
+ version: "1.20.0"
6281
+ };
6282
+
6283
+ // src/lib/engram-install.ts
6284
+ async function installMissingEngram(options = {}) {
6285
+ const platform = options.platform ?? process.platform;
6286
+ const homeDir = options.homeDir ?? os5.homedir();
6287
+ if (!path33.isAbsolute(homeDir)) return { ok: false, reason: "El directorio personal debe ser absoluto." };
6288
+ const binaryName = platform === "win32" ? "engram.exe" : "engram";
6289
+ const bin = path33.join(homeDir, ".local", "bin", binaryName);
6290
+ let staging;
6291
+ try {
6292
+ const existing = fs24.lstatSync(bin, { throwIfNoEntry: false });
6293
+ if (existing) {
6294
+ if (!fs24.statSync(bin).isFile()) throw new Error(`El destino Engram no es un archivo: ${bin}`);
6295
+ fs24.accessSync(bin, platform === "win32" ? fs24.constants.F_OK : fs24.constants.X_OK);
6296
+ return { ok: true, bin };
6297
+ }
6298
+ const key = `${platform === "win32" ? "windows" : platform}_${options.arch ?? process.arch}`;
6299
+ const asset = engram_release_default.assets[key];
6300
+ if (!asset) throw new Error(`Engram no dispone de un binario aprobado para ${key}.`);
6301
+ const response = await (options.fetch ?? globalThis.fetch)(
6302
+ `https://github.com/Gentleman-Programming/engram/releases/download/v${engram_release_default.version}/${asset.name}`,
6303
+ { signal: AbortSignal.timeout(12e4) }
6304
+ );
6305
+ if (!response.ok || !response.body) throw new Error(`Descarga Engram fallida: HTTP ${response.status}.`);
6306
+ const reader = response.body.getReader();
6307
+ const chunks = [];
6308
+ let size = 0;
6309
+ try {
6310
+ while (true) {
6311
+ const chunk = await reader.read();
6312
+ if (chunk.done) break;
6313
+ size += chunk.value.byteLength;
6314
+ if (size > asset.size) throw new Error("El tama\xF1o descargado de Engram supera el aprobado.");
6315
+ chunks.push(chunk.value);
6316
+ }
6317
+ } finally {
6318
+ await reader.cancel();
6319
+ }
6320
+ const archive = Buffer.concat(chunks);
6321
+ if (size !== asset.size || createHash2("sha256").update(archive).digest("hex") !== asset.sha256) {
6322
+ throw new Error("Engram: tama\xF1o o hash SHA-256 no coincide con el artefacto aprobado.");
6323
+ }
6324
+ fs24.mkdirSync(path33.dirname(bin), { recursive: true });
6325
+ staging = fs24.mkdtempSync(path33.join(path33.dirname(bin), ".engram-install-"));
6326
+ const archivePath = path33.join(staging, asset.name);
6327
+ fs24.writeFileSync(archivePath, archive, { flag: "wx", mode: 384 });
6328
+ const binary = execFileSync7(resolveTarBin(), ["-xOf", archivePath, binaryName], {
6329
+ maxBuffer: 64 * 1024 * 1024,
6330
+ timeout: 3e4,
6331
+ stdio: ["ignore", "pipe", "pipe"]
6332
+ });
6333
+ if (binary.length === 0) throw new Error("El artefacto Engram no contiene un binario v\xE1lido.");
6334
+ const stagedBin = path33.join(staging, binaryName);
6335
+ fs24.writeFileSync(stagedBin, binary, { flag: "wx", mode: 493 });
6336
+ fs24.linkSync(stagedBin, bin);
6337
+ return { ok: true, bin };
6338
+ } catch (error) {
6339
+ return { ok: false, reason: error instanceof Error ? error.message : String(error) };
6340
+ } finally {
6341
+ if (staging) fs24.rmSync(staging, { recursive: true, force: true });
6342
+ }
6343
+ }
6344
+
6170
6345
  // src/cli.ts
6171
6346
  var VERSION = readPackageVersion();
6172
6347
  var COMMANDS = ["install", "sync", "models", "update", "doctor", "restore", "uninstall", "quality"];
@@ -6201,6 +6376,7 @@ function parseFlags(args, allowReceipt = false) {
6201
6376
  list: false,
6202
6377
  check: false,
6203
6378
  removeEngram: false,
6379
+ engram: false,
6204
6380
  playwright: false,
6205
6381
  removePlaywright: false,
6206
6382
  devtools: false,
@@ -6263,6 +6439,7 @@ function parseFlags(args, allowReceipt = false) {
6263
6439
  else if (arg === "--list") flags.list = true;
6264
6440
  else if (arg === "--check") flags.check = true;
6265
6441
  else if (arg === "--remove-engram") flags.removeEngram = true;
6442
+ else if (arg === "--engram") flags.engram = true;
6266
6443
  else if (arg === "--playwright") flags.playwright = true;
6267
6444
  else if (arg === "--remove-playwright") flags.removePlaywright = true;
6268
6445
  else if (arg === "--devtools") flags.devtools = true;
@@ -6413,7 +6590,34 @@ async function resolveRuntimes(flags, includeAvailablePi = false) {
6413
6590
  if (p6.isCancel(choice)) return null;
6414
6591
  return choice;
6415
6592
  }
6416
- async function runSelectedPi(operation, targetDir, yes = false) {
6593
+ async function resolveHostEngramForInstall(flags) {
6594
+ if (flags.targetDir !== void 0) return { ok: true, bin: null };
6595
+ const existing = resolvePiEngramBin();
6596
+ if (flags.dryRun) return { ok: true, bin: existing };
6597
+ if (existing !== null) return { ok: true, bin: existing };
6598
+ let accepted = flags.engram;
6599
+ if (!accepted && !flags.yes && process.stdin.isTTY === true && process.stdout.isTTY === true) {
6600
+ const answer = await p6.confirm({
6601
+ message: "Engram no est\xE1 instalado. \xBFDescargar e instalar ahora el binario oficial verificado?",
6602
+ initialValue: false
6603
+ });
6604
+ if (!p6.isCancel(answer)) accepted = answer === true;
6605
+ }
6606
+ if (!accepted) {
6607
+ return {
6608
+ ok: false,
6609
+ message: "Engram no est\xE1 instalado. Usa --engram para autorizar su instalaci\xF3n o inst\xE1lalo antes de reintentar."
6610
+ };
6611
+ }
6612
+ try {
6613
+ const result = await installMissingEngram();
6614
+ if (result.ok) return { ok: true, bin: result.bin };
6615
+ return { ok: false, message: `Engram: ${result.reason}` };
6616
+ } catch (error) {
6617
+ return { ok: false, message: `Engram: ${error instanceof Error ? error.message : String(error)}` };
6618
+ }
6619
+ }
6620
+ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBin) {
6417
6621
  if (targetDir === void 0 && operation !== "models") {
6418
6622
  const preferenceErrors = browserPreferenceErrors();
6419
6623
  if (preferenceErrors.length > 0) {
@@ -6430,7 +6634,7 @@ async function runSelectedPi(operation, targetDir, yes = false) {
6430
6634
  console.error("No se pudo verificar la versi\xF3n instalada de Pi sin ejecutarlo; revisa la instalaci\xF3n de Pi.");
6431
6635
  return 1;
6432
6636
  }
6433
- let engramBin = resolvePiEngramBin(targetDir);
6637
+ let engramBin = resolvedEngramBin === void 0 ? resolvePiEngramBin(targetDir) : resolvedEngramBin;
6434
6638
  if (operation === "install" && engramBin === null) {
6435
6639
  const requirement = await resolvePiEngramRequirement({
6436
6640
  targetDir,
@@ -6491,6 +6695,7 @@ Opciones:
6491
6695
  --dry-run Muestra el plan sin escribir nada
6492
6696
  --yes, -y No interactivo
6493
6697
  --playwright Autoriza Playwright CLI global y sus navegadores (requerido con --yes/sin TTY)
6698
+ --engram (install) autoriza instalar el binario Engram si falta
6494
6699
  --devtools (install/sync) activa Chrome DevTools MCP para los runtimes destino (opt-in)
6495
6700
  --no-devtools (install/sync) desactiva Chrome DevTools MCP (incompatible con --devtools)
6496
6701
  --remove-engram (uninstall) desregistra Engram de los runtimes;
@@ -6524,6 +6729,11 @@ Flags disponibles: jorgex-stack --help`
6524
6729
  return;
6525
6730
  }
6526
6731
  const { command, flags } = parsed;
6732
+ if (flags.engram && command !== "install") {
6733
+ console.error("--engram solo se admite durante install.");
6734
+ process.exitCode = 1;
6735
+ return;
6736
+ }
6527
6737
  if (command !== "quality" && flags.targetDir !== void 0 && flags.agents.length !== 1) {
6528
6738
  console.error("--target-dir requiere exactamente un runtime en --agents.");
6529
6739
  process.exitCode = 1;
@@ -6531,7 +6741,7 @@ Flags disponibles: jorgex-stack --help`
6531
6741
  }
6532
6742
  switch (command) {
6533
6743
  case "quality": {
6534
- if (flags.targetDir !== void 0 || flags.agents.length > 0 || flags.dryRun || flags.yes || flags.mode !== void 0 || flags.subagentConcurrency !== void 0 || flags.list || flags.check || flags.removeEngram || flags.playwright || flags.removePlaywright || flags.devtools || flags.noDevtools) {
6744
+ if (flags.targetDir !== void 0 || flags.agents.length > 0 || flags.dryRun || flags.yes || flags.mode !== void 0 || flags.subagentConcurrency !== void 0 || flags.list || flags.check || flags.removeEngram || flags.engram || flags.playwright || flags.removePlaywright || flags.devtools || flags.noDevtools) {
6535
6745
  console.error("quality solo admite <plan.json> y, opcionalmente, --receipt <path>.");
6536
6746
  process.exitCode = 1;
6537
6747
  return;
@@ -6547,7 +6757,7 @@ Flags disponibles: jorgex-stack --help`
6547
6757
  return;
6548
6758
  }
6549
6759
  try {
6550
- const plan = JSON.parse(fs24.readFileSync(flags.positional[0], "utf8"));
6760
+ const plan = JSON.parse(fs25.readFileSync(flags.positional[0], "utf8"));
6551
6761
  const result = await runQualityPlan(plan);
6552
6762
  const serialized = serializeQualityReceipt(result.receipt);
6553
6763
  if (flags.receipt === void 0) process.stdout.write(`${serialized}
@@ -6572,33 +6782,55 @@ Flags disponibles: jorgex-stack --help`
6572
6782
  }
6573
6783
  const fileRuntimes = runtimes.filter(isFileManagedRuntime);
6574
6784
  let exitCode = 0;
6575
- if (fileRuntimes.length > 0) {
6576
- const mode = await resolveInstallMode(flags);
6785
+ let completed = false;
6786
+ p6.intro(`jorgex-stack ${command}${flags.dryRun ? " (dry-run)" : ""}`);
6787
+ try {
6788
+ if (flags.targetDir === void 0) {
6789
+ const errors = browserPreferenceErrors();
6790
+ if (errors.length > 0) {
6791
+ for (const error of errors) p6.log.error(error);
6792
+ exitCode = 1;
6793
+ return;
6794
+ }
6795
+ }
6796
+ const mode = fileRuntimes.length > 0 ? await resolveInstallMode(flags) : void 0;
6577
6797
  if (mode === null) return;
6578
6798
  const devtoolsMcpSelection = await resolveDevtoolsMcpSelection(command, flags, fileRuntimes);
6579
- if (devtoolsMcpSelection === null) return;
6580
- const playwrightToolConsent = await resolvePlaywrightToolConsent(command, flags);
6581
- if (playwrightToolConsent === null) return;
6582
- const hasOpenCodeModels = await ensureOpenCodeModelsForInstall(command, flags, fileRuntimes);
6583
- if (!hasOpenCodeModels) {
6584
- process.exitCode = 1;
6799
+ if (devtoolsMcpSelection === null) {
6800
+ exitCode = process.exitCode === 1 ? 1 : 0;
6585
6801
  return;
6586
6802
  }
6587
- exitCode = await runInstall({
6588
- runtimes: fileRuntimes,
6589
- targetDir: flags.targetDir,
6590
- dryRun: flags.dryRun,
6591
- yes: flags.yes,
6592
- mode,
6593
- playwrightToolConsent,
6594
- devtoolsMcpSelection
6595
- });
6596
- }
6597
- let piCanRun = true;
6598
- if (command === "install" && fileRuntimes.length === 0 && runtimes.includes("pi")) {
6599
6803
  const playwrightToolConsent = await resolvePlaywrightToolConsent(command, flags);
6600
6804
  if (playwrightToolConsent === null) return;
6601
- if (resolvePlaywrightToolPlan(playwrightToolConsent).actions.length > 0) {
6805
+ if (!await ensureOpenCodeModelsForInstall(command, flags, fileRuntimes)) {
6806
+ exitCode = 1;
6807
+ return;
6808
+ }
6809
+ let engramBin;
6810
+ if (command === "install") {
6811
+ const engram = await resolveHostEngramForInstall(flags);
6812
+ if (!engram.ok) {
6813
+ p6.log.error(engram.message);
6814
+ exitCode = 1;
6815
+ return;
6816
+ }
6817
+ engramBin = engram.bin;
6818
+ }
6819
+ if (fileRuntimes.length > 0) {
6820
+ exitCode = await runInstall({
6821
+ runtimes: fileRuntimes,
6822
+ targetDir: flags.targetDir,
6823
+ dryRun: flags.dryRun,
6824
+ yes: flags.yes,
6825
+ mode,
6826
+ playwrightToolConsent,
6827
+ devtoolsMcpSelection,
6828
+ engramBin,
6829
+ showSummary: false
6830
+ });
6831
+ }
6832
+ let piCanRun = true;
6833
+ if (command === "install" && fileRuntimes.length === 0 && runtimes.includes("pi") && resolvePlaywrightToolPlan(playwrightToolConsent).actions.length > 0) {
6602
6834
  if (flags.dryRun) {
6603
6835
  p6.log.info("Playwright CLI: instalaci\xF3n global y navegador previstos (dry-run; no se ejecutan).");
6604
6836
  } else {
@@ -6607,17 +6839,31 @@ Flags disponibles: jorgex-stack --help`
6607
6839
  targetDir: flags.targetDir,
6608
6840
  dryRun: false,
6609
6841
  yes: flags.yes,
6610
- playwrightToolConsent
6842
+ playwrightToolConsent,
6843
+ engramBin,
6844
+ showSummary: false
6611
6845
  });
6612
6846
  piCanRun = exitCode === 0;
6613
6847
  }
6614
6848
  }
6849
+ if (runtimes.includes("pi") && piCanRun) {
6850
+ if (flags.dryRun) p6.log.info(`Pi: ${command} previsto; dry-run no ejecuta subprocess ni escribe receipt.`);
6851
+ else exitCode = Math.max(exitCode, await runSelectedPi(
6852
+ command,
6853
+ flags.targetDir,
6854
+ flags.yes,
6855
+ flags.targetDir === void 0 ? engramBin : void 0
6856
+ ));
6857
+ }
6858
+ completed = true;
6859
+ } catch (error) {
6860
+ p6.log.error(error instanceof Error ? error.message : String(error));
6861
+ exitCode = 1;
6862
+ } finally {
6863
+ if (process.exitCode === 1) exitCode = 1;
6864
+ process.exitCode = exitCode;
6865
+ p6.outro(exitCode !== 0 ? `${command} completado con errores (revisa arriba).` : !completed ? `${command} cancelado.` : flags.dryRun ? "Dry-run: no se ha escrito nada." : "Hecho.");
6615
6866
  }
6616
- if (runtimes.includes("pi") && piCanRun) {
6617
- if (flags.dryRun) p6.log.info(`Pi: ${command} previsto; dry-run no ejecuta subprocess ni escribe receipt.`);
6618
- else exitCode = Math.max(exitCode, await runSelectedPi(command, flags.targetDir, flags.yes));
6619
- }
6620
- process.exitCode = exitCode;
6621
6867
  return;
6622
6868
  }
6623
6869
  case "uninstall": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jorgex-stack",
3
- "version": "1.9.27",
3
+ "version": "1.9.29",
4
4
  "description": "Harness multi-agente portable: instala la config JorgeX (agentes, skills, hooks, Engram, MCPs) en Claude Code, Codex CLI, OpenCode y Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",