jorgex-stack 1.9.39 → 1.9.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/dist/cli.js +207 -59
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -146,7 +146,7 @@ Históricamente, Stack `1.9.7` reconocía el receipt exacto de Pi `npm:jorgex-pi
146
146
 
147
147
  Pi puede consumirse inmediatamente después de publicar y verificar el artefacto adoptado. El pin exacto, la procedencia, los SHA-256/SHA-512, el SRI, la compatibilidad y el procedimiento de rollback siguen siendo obligatorios; no uses `latest` ni una versión aproximada.
148
148
 
149
- `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).
149
+ `update --agents pi` only runs the Pi package lifecycle; it does not enter the global Stack updater. `update --check --agents pi` performs a read-only check of the Pi package and local registration metadata through the package runner. It does not compare Stack's projection, run the browser smoke check, or mutate Pi state; use `doctor --agents pi` for the complete package-and-projection diagnosis. 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).
150
150
 
151
151
  ### Estilo global de escritura
152
152
 
@@ -186,8 +186,8 @@ Under the hood, `--playwright` runs two `pnpm` argv-only plans back to back: `pn
186
186
 
187
187
  Daily operation:
188
188
 
189
- - `sync` reconciles configuration without installing global tools or browsers; if Playwright is enabled but the binary or browser cache is `missing`, it warns and points to `install --playwright`; if the cache is `unreadable`, the warning includes its resolved path and filesystem error code. The cache probe retains the path for both states and an error code when the filesystem provides one. Under `--target-dir`, `sync`/`install`/`uninstall` never read or write the real browser state (`~/.jorgex-stack/playwright-cli.json` and `~/.jorgex-stack/devtools-mcp.json` are untouched, `detectPlaywrightCli()` is not called, no MCP ownership is persisted).
190
- - `doctor` reports the Playwright CLI state (`disabled`, `healthy`, `missing:package`, `missing:browser`, `unreadable`, `broken`, `outdated`) without opening sites or repairing state. An `unreadable` browser cache includes its exact path and filesystem error code so permissions or another local cause can be investigated. If either preference file is corrupt, doctor prints the exact path and the remedy (`Corrige o borra ese archivo antes de reintentar`) before any other browser check; in that case `install`/`uninstall`/`update`/`update --check` will abort with exit 1 until the file is fixed, so the corruption cannot be reconciled destructively.
189
+ - `sync` reconciles configuration without installing global tools or browsers; if Playwright is enabled but the binary or browser cache is `missing`, it warns and points to `install --playwright`; if the cache is `unreadable`, the warning includes its resolved path and filesystem error code. If the local Chromium launch check fails, `sync` removes the projected Playwright guide, preserves the enabled preference, and points to `install --playwright` for repair. The cache probe retains the path for both states and an error code when the filesystem provides one. Under `--target-dir`, `sync`/`install`/`uninstall` never read or write the real browser state (`~/.jorgex-stack/playwright-cli.json` and `~/.jorgex-stack/devtools-mcp.json` are untouched, `detectPlaywrightCli()` is not called, no MCP ownership is persisted).
190
+ - `doctor` reports the Playwright CLI state (`disabled`, `healthy`, `missing:package`, `missing:browser`, `unreadable`, `broken`, `outdated`) and, when the opt-in package and browser cache are present, verifies one local headless Chromium launch against `about:blank`; it does not open external sites or repair state. An `unreadable` browser cache includes its exact path and filesystem error code so permissions or another local cause can be investigated. If either preference file is corrupt, doctor prints the exact path and the remedy (`Corrige o borra ese archivo antes de reintentar`) before any other browser check; in that case `install`/`uninstall`/`update`/`update --check` will abort with exit 1 until the file is fixed, so the corruption cannot be reconciled destructively.
191
191
  - `update --check` only inspects Playwright CLI when its preference is `enabled` (a binary appearing in `PATH` is not consent). It compares the installed version against the approved pin `0.1.18` — it does not consult npm latest, and a Playwright CLI binary-only update does not require `sync` afterwards.
192
192
  - `uninstall` preserves the global `@playwright/cli` package and all browser data by default; `--remove-playwright` removes the package only (never the browser cache, profiles, cookies, storage state, traces, screenshots or videos). If `pnpm remove --global @playwright/cli` exits non-zero, `uninstall` reports the failure instead of a success outro. For DevTools MCP, ownership is released only after the corresponding unmerge is applied; if no unmerge action is written, the ownership marker is preserved for a later retry.
193
193
  - `install`/`sync` also inject (and `disable`/`uninstall` remove) managed capability sections in `AGENTS.md` (OpenCode, Codex) or `CLAUDE.md` (Claude Code). File runtimes keep `jorgex:playwright`, `jorgex:chrome-devtools` and `jorgex:context7` independent; Pi 0.8.18 adapts them to its legacy `jorgex:browser`/base projection. User content outside managed markers is preserved. `--target-dir` never reads the real preferences, so by default no section is emitted in target-dir runs — but explicit flags like `--devtools` simulate the MCP entry (and its DevTools block) inside the temp target without touching the real global state, and `install --dry-run --playwright` projects the Playwright section into the plan preview without installing anything. If reconciliation detects a partial or ambiguous system-prompt state, the CLI exits non-zero and recommends repairing it with `sync` or a repeated install; no write is attempted before marker validation. See [docs/references/browser-automation.md](docs/references/browser-automation.md) §2.7 for the full lifecycle.
package/dist/cli.js CHANGED
@@ -681,9 +681,9 @@ function ensureModelMapFile() {
681
681
  import path7 from "path";
682
682
  import { existsSync as existsSync2, statSync } from "fs";
683
683
  import { execFileSync } from "child_process";
684
- function lookPath(cmd) {
684
+ function lookPath(cmd, env = process.env) {
685
685
  const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ".ps1", ""] : [""];
686
- for (const dir of (process.env.PATH ?? "").split(path7.delimiter)) {
686
+ for (const dir of (env.PATH ?? "").split(path7.delimiter)) {
687
687
  if (!dir) continue;
688
688
  for (const ext of exts) {
689
689
  const candidate = path7.join(dir, cmd + ext);
@@ -2348,10 +2348,11 @@ function resolvePlaywrightCliState({ binPath, versionOutput }) {
2348
2348
  detectedVersion
2349
2349
  };
2350
2350
  }
2351
- function detectPlaywrightCli() {
2352
- const binPath = lookPath(PLAYWRIGHT_CLI.bin);
2351
+ function detectPlaywrightCli(env) {
2352
+ const actualEnv = env ?? process.env;
2353
+ const binPath = env === void 0 ? lookPath(PLAYWRIGHT_CLI.bin) : lookPath(PLAYWRIGHT_CLI.bin, env);
2353
2354
  if (binPath === null) {
2354
- const pnpmHome = resolvePnpmHome();
2355
+ const pnpmHome = resolvePnpmHome(actualEnv);
2355
2356
  const name = process.platform === "win32" ? `${PLAYWRIGHT_CLI.bin}.cmd` : PLAYWRIGHT_CLI.bin;
2356
2357
  const known = [path20.join(pnpmHome, name), path20.join(pnpmHome, "bin", name)].find((file) => {
2357
2358
  try {
@@ -2364,13 +2365,13 @@ function detectPlaywrightCli() {
2364
2365
  }
2365
2366
  return resolvePlaywrightCliState({
2366
2367
  binPath,
2367
- versionOutput: binPath ? runDetectedBin(binPath, ["--version"], 5e3, { NO_UPDATE_NOTIFIER: "1" }) : null
2368
+ versionOutput: binPath ? runDetectedBin(binPath, ["--version"], 5e3, { ...actualEnv, NO_UPDATE_NOTIFIER: "1" }) : null
2368
2369
  });
2369
2370
  }
2370
- function resolvePnpmBin() {
2371
- const pnpm = lookPath("pnpm");
2371
+ function resolvePnpmBin(env) {
2372
+ const pnpm = env === void 0 ? lookPath("pnpm") : lookPath("pnpm", env);
2372
2373
  if (pnpm !== null && !pnpm.toLowerCase().endsWith(".ps1")) return pnpm;
2373
- const pnpmCmd = lookPath("pnpm.cmd");
2374
+ const pnpmCmd = env === void 0 ? lookPath("pnpm.cmd") : lookPath("pnpm.cmd", env);
2374
2375
  return pnpmCmd !== null && !pnpmCmd.toLowerCase().endsWith(".ps1") ? pnpmCmd : null;
2375
2376
  }
2376
2377
  function resolvePnpmHome(env = process.env, platform = process.platform, homeDir = os.homedir()) {
@@ -2502,6 +2503,30 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin(), env) {
2502
2503
  }
2503
2504
  }
2504
2505
 
2506
+ // src/lib/playwright-capability.ts
2507
+ function inspectPlaywrightCapability(options = {}) {
2508
+ const env = options.env ?? process.env;
2509
+ const cli = options.env === void 0 ? detectPlaywrightCli() : detectPlaywrightCli(env);
2510
+ const browserCache = options.env === void 0 ? isPlaywrightBrowserReady() : isPlaywrightBrowserReady(env);
2511
+ let browserVerified = false;
2512
+ if (cli.status === "current" && browserCache.status === "ready") {
2513
+ if (options.browserVerified === true) {
2514
+ browserVerified = true;
2515
+ } else {
2516
+ const pnpmBin = options.env === void 0 ? resolvePnpmBin() : resolvePnpmBin(env);
2517
+ if (pnpmBin !== null) {
2518
+ try {
2519
+ browserVerified = verifyPlaywrightBrowser(pnpmBin, env);
2520
+ } catch {
2521
+ browserVerified = false;
2522
+ }
2523
+ }
2524
+ }
2525
+ }
2526
+ const effective = cli.status === "current" && cli.binPath !== null && (options.browserVerified === true || browserCache.status === "ready") && browserVerified;
2527
+ return { cli, browserCache, browserVerified, effective };
2528
+ }
2529
+
2505
2530
  // src/lib/tool-preferences.ts
2506
2531
  import fs16 from "fs";
2507
2532
  import path21 from "path";
@@ -2762,7 +2787,7 @@ function persistConfigurationOwnershipChanges(runtime, configDir, plan) {
2762
2787
  const primaryFile = primaryModelOwnershipFile();
2763
2788
  for (const [field, owned] of primary) savePrimaryModelOwnership(primaryFile, runtime, configDir, field, owned);
2764
2789
  }
2765
- function makeContext(adapter, configDir, mode = DEFAULT_INSTALL_MODE_PREFERENCE, useBrowserPreferences = true) {
2790
+ function makeContext(adapter, configDir, mode = DEFAULT_INSTALL_MODE_PREFERENCE, useBrowserPreferences = true, playwrightCapability) {
2766
2791
  const models = loadModelMap()[adapter.id];
2767
2792
  if (!models) return null;
2768
2793
  return {
@@ -2774,7 +2799,7 @@ function makeContext(adapter, configDir, mode = DEFAULT_INSTALL_MODE_PREFERENCE,
2774
2799
  models,
2775
2800
  warnings: [],
2776
2801
  enabledMcpServers: enabledMcpServers(adapter.id, void 0, useBrowserPreferences),
2777
- playwrightCliEnabled: useBrowserPreferences && loadPlaywrightCliPreference(playwrightCliPreferenceFile(), adapter.id) === true,
2802
+ playwrightCliEnabled: useBrowserPreferences && loadPlaywrightCliPreference(playwrightCliPreferenceFile(), adapter.id) === true && (playwrightCapability ?? true),
2778
2803
  ownedMcpServers: ownedMcpServers(adapter.id, useBrowserPreferences),
2779
2804
  ownedPrimaryModelFields: useBrowserPreferences ? loadPrimaryModelOwnership(primaryModelOwnershipFile(), adapter.id, configDir) : /* @__PURE__ */ new Set()
2780
2805
  };
@@ -2809,14 +2834,14 @@ function applyChanges(changes, onOwnershipWritten) {
2809
2834
  } else copyFile(action.source, action.target);
2810
2835
  }
2811
2836
  }
2812
- function collectAllCurrentTargets(mode = DEFAULT_INSTALL_MODE_PREFERENCE) {
2837
+ function collectAllCurrentTargets(mode = DEFAULT_INSTALL_MODE_PREFERENCE, playwrightCapability) {
2813
2838
  const targets = /* @__PURE__ */ new Set();
2814
2839
  let complete = true;
2815
2840
  const warnings = [];
2816
2841
  for (const adapter of Object.values(ADAPTERS)) {
2817
2842
  const detection = adapter.detect();
2818
2843
  if (!detection.installed) continue;
2819
- const ctx = makeContext(adapter, detection.configDir, mode);
2844
+ const ctx = makeContext(adapter, detection.configDir, mode, true, playwrightCapability);
2820
2845
  if (!ctx) {
2821
2846
  complete = false;
2822
2847
  warnings.push(`${adapter.name}: limpieza de hu\xE9rfanos deshabilitada \u2014 falta contexto/model-map instalable para este runtime.`);
@@ -2873,6 +2898,10 @@ async function runInstall(opts) {
2873
2898
  });
2874
2899
  const projectPlaywrightPrompt = opts.dryRun && toolPlan?.persistEnabledOnSuccess === true;
2875
2900
  const hasFileRuntimes = opts.runtimes.length > 0;
2901
+ const shouldInspectPlaywright = useManifest && !opts.dryRun && (toolPlan === null || toolPlan.actions.length === 0) && loadPlaywrightCliPreference() === true;
2902
+ const playwrightCapability = opts.dryRun || !useManifest ? void 0 : opts.playwrightCapability ?? (shouldInspectPlaywright ? inspectPlaywrightCapability() : void 0);
2903
+ const effectivePlaywright = playwrightCapability?.effective;
2904
+ const plannedPlaywright = effectivePlaywright ?? (toolPlan !== null && toolPlan.actions.length > 0 ? false : void 0);
2876
2905
  const modelMap = hasFileRuntimes ? loadModelMap() : {};
2877
2906
  if (preparedStyle !== void 0) {
2878
2907
  try {
@@ -2885,7 +2914,7 @@ async function runInstall(opts) {
2885
2914
  }
2886
2915
  if (hasFileRuntimes && useManifest && !opts.dryRun) ensureModelMapFile();
2887
2916
  p.log.info(engramBin ? `Engram detectado: ${engramBin} (se respeta, D7)` : "Engram NO detectado.");
2888
- const current = hasFileRuntimes && useManifest ? collectAllCurrentTargets(modePreference) : { targets: /* @__PURE__ */ new Set(), complete: false, warnings: [] };
2917
+ const current = hasFileRuntimes && useManifest ? collectAllCurrentTargets(modePreference, plannedPlaywright) : { targets: /* @__PURE__ */ new Set(), complete: false, warnings: [] };
2889
2918
  const canOrphan = hasFileRuntimes && useManifest && current.complete;
2890
2919
  const canonicalMcp = loadCanonicalMcp(stackDir);
2891
2920
  const canonicalHooks = loadCanonicalHooks(stackDir);
@@ -2924,7 +2953,7 @@ async function runInstall(opts) {
2924
2953
  models,
2925
2954
  warnings: [],
2926
2955
  enabledMcpServers: enabledMcpServers(id, opts.devtoolsMcpSelection?.[id], useManifest),
2927
- playwrightCliEnabled: projectPlaywrightPrompt ? opts.playwrightToolConsent?.runtimeSelection?.[id] ?? true : useManifest && loadPlaywrightCliPreference(playwrightCliPreferenceFile(), id) === true,
2956
+ playwrightCliEnabled: projectPlaywrightPrompt ? opts.playwrightToolConsent?.runtimeSelection?.[id] ?? true : useManifest && loadPlaywrightCliPreference(playwrightCliPreferenceFile(), id) === true && (plannedPlaywright ?? true),
2928
2957
  ownedMcpServers: ownedMcpServers(id, useManifest),
2929
2958
  ownedPrimaryModelFields: useManifest ? loadPrimaryModelOwnership(primaryModelOwnershipFile(), id, configDir) : /* @__PURE__ */ new Set()
2930
2959
  };
@@ -3090,21 +3119,23 @@ async function runInstall(opts) {
3090
3119
  exitCode = 1;
3091
3120
  p.log.error("Playwright CLI y navegador se han instalado y la preferencia est\xE1 activada, pero la gu\xEDa de navegador qued\xF3 en estado parcial. Ejecuta 'jorgex-stack sync' para repararla.");
3092
3121
  } else {
3122
+ const verified = preparedEnv === void 0 ? inspectPlaywrightCapability({ browserVerified: true }) : inspectPlaywrightCapability({ browserVerified: true, env: preparedEnv });
3123
+ if (verified.effective && verified.cli.status === "current" && verified.cli.binPath !== null && verified.cli.detectedVersion !== null && verified.browserCache.status === "ready") {
3124
+ opts.onPlaywrightCapability?.(verified);
3125
+ }
3093
3126
  p.log.success("Playwright CLI instalado y arranque de Chromium verificado.");
3094
3127
  }
3095
3128
  }
3096
3129
  }
3097
- } else if (useManifest && opts.playwrightToolConsent?.command === "sync" && loadPlaywrightCliPreference() === true) {
3098
- const cli = detectPlaywrightCli();
3099
- if (cli.status !== "current") {
3130
+ } else if (playwrightCapability !== void 0 && !playwrightCapability.effective) {
3131
+ if (playwrightCapability.cli.status !== "current") {
3100
3132
  p.log.warn("Playwright CLI sigue habilitado, pero el paquete no est\xE1 listo; sync no instala herramientas. Ejecuta 'jorgex-stack install --playwright'.");
3133
+ } else if (playwrightCapability.browserCache.status === "unreadable") {
3134
+ p.log.warn(`Playwright CLI sigue habilitado, pero no se puede leer la cach\xE9 de navegadores en ${playwrightCapability.browserCache.path} (${playwrightCapability.browserCache.errorCode}). Revisa permisos o ejecuta 'jorgex-stack install --playwright'.`);
3135
+ } else if (playwrightCapability.browserCache.status === "missing") {
3136
+ p.log.warn("Playwright CLI sigue habilitado, pero falta el navegador; sync no descarga navegadores. Ejecuta 'jorgex-stack install --playwright'.");
3101
3137
  } else {
3102
- const browserCache = isPlaywrightBrowserReady();
3103
- if (browserCache.status === "unreadable") {
3104
- p.log.warn(`Playwright CLI sigue habilitado, pero no se puede leer la cach\xE9 de navegadores en ${browserCache.path} (${browserCache.errorCode}). Revisa permisos o ejecuta 'jorgex-stack install --playwright'.`);
3105
- } else if (browserCache.status === "missing") {
3106
- p.log.warn("Playwright CLI sigue habilitado, pero falta el navegador; sync no descarga navegadores. Ejecuta 'jorgex-stack install --playwright'.");
3107
- }
3138
+ p.log.warn("Playwright CLI sigue habilitado, pero Chromium no arranca; se ha retirado la gu\xEDa. Ejecuta 'jorgex-stack install --playwright' para repararlo.");
3108
3139
  }
3109
3140
  }
3110
3141
  if (useManifest && !opts.dryRun && exitCode === 0 && successfulRuns > 0) {
@@ -4376,6 +4407,7 @@ function resolvePlaywrightDoctorState(input) {
4376
4407
  if (input.browserCache?.status === "unreadable") {
4377
4408
  return { status: "unreadable", path: input.browserCache.path, errorCode: input.browserCache.errorCode };
4378
4409
  }
4410
+ if (input.browserVerified === false && input.browserCache?.status === "ready") return { status: "broken" };
4379
4411
  if (!input.browserReady) return { status: "missing", missing: "browser" };
4380
4412
  return { status: "healthy" };
4381
4413
  }
@@ -4471,21 +4503,28 @@ async function runDoctor(options = {}) {
4471
4503
  p3.log.error(primaryOwnershipError);
4472
4504
  problems++;
4473
4505
  }
4506
+ let effectivePlaywright;
4474
4507
  if (preferenceErrors.length > 0) {
4475
4508
  for (const error of preferenceErrors) p3.log.error(error);
4476
4509
  problems += preferenceErrors.length;
4510
+ } else if (options.dryRun) {
4511
+ p3.log.info("Playwright CLI: comprobaci\xF3n omitida en dry-run; no se eval\xFAa el estado del paquete ni del navegador.");
4477
4512
  } else {
4478
- const browserCache = isPlaywrightBrowserReady();
4513
+ const enabled = loadPlaywrightCliPreference();
4514
+ const capability = options.playwrightCapability ?? (enabled === true ? inspectPlaywrightCapability() : void 0);
4515
+ effectivePlaywright = capability?.effective;
4516
+ const cli = capability?.cli ?? { status: "absent" };
4479
4517
  const playwright = resolvePlaywrightDoctorState({
4480
- enabled: loadPlaywrightCliPreference(),
4481
- cli: detectPlaywrightCli(),
4482
- browserReady: browserCache.status === "ready",
4483
- browserCache
4518
+ enabled,
4519
+ cli,
4520
+ browserReady: capability?.effective ?? false,
4521
+ browserVerified: capability?.browserVerified,
4522
+ browserCache: capability?.browserCache
4484
4523
  });
4485
4524
  if (playwright.status === "disabled") {
4486
4525
  p3.log.info("Playwright CLI: deshabilitado (opcional). Usa 'install --playwright' para instalarlo de forma expl\xEDcita.");
4487
4526
  } else if (playwright.status === "healthy") {
4488
- p3.log.success("Playwright CLI: paquete y cach\xE9 de Chromium detectados (doctor no prueba el arranque).");
4527
+ p3.log.success("Playwright CLI: paquete y arranque de Chromium verificados.");
4489
4528
  } else if (playwright.status === "not-in-path") {
4490
4529
  p3.log.warn("Playwright CLI: instalado en el directorio de pnpm, pero fuera del PATH de esta terminal. Abre una terminal nueva tras pnpm setup; no hace falta reinstalarlo.");
4491
4530
  problems++;
@@ -4494,7 +4533,8 @@ async function runDoctor(options = {}) {
4494
4533
  p3.log.warn(`Playwright CLI: habilitado, pero falta ${target} \u2192 ejecuta 'jorgex-stack install --playwright'.`);
4495
4534
  problems++;
4496
4535
  } else if (playwright.status === "broken") {
4497
- p3.log.error("Playwright CLI: el binario detectado no responde correctamente \u2192 ejecuta 'jorgex-stack install --playwright'.");
4536
+ const launchFailed = capability?.cli.status === "current" && capability.browserCache.status === "ready" && capability.browserVerified === false;
4537
+ p3.log.error(`Playwright CLI: ${launchFailed ? "Chromium no arranca" : "el binario detectado no responde correctamente"} \u2192 ejecuta 'jorgex-stack install --playwright'.`);
4498
4538
  problems++;
4499
4539
  } else if (playwright.status === "unreadable") {
4500
4540
  p3.log.error(`Playwright CLI: no se puede leer la cach\xE9 de navegadores en ${playwright.path} (${playwright.errorCode}) \u2192 revisa permisos o ejecuta 'jorgex-stack install --playwright'.`);
@@ -4505,7 +4545,7 @@ async function runDoctor(options = {}) {
4505
4545
  }
4506
4546
  }
4507
4547
  const manifest = readManifest();
4508
- const current = collectAllCurrentTargets(modePreference);
4548
+ const current = collectAllCurrentTargets(modePreference, effectivePlaywright);
4509
4549
  if (!current.complete || current.warnings.length > 0) {
4510
4550
  p3.log.warn("Limpieza de hu\xE9rfanos deshabilitada: no se pudo construir el plan completo de todos los runtimes.");
4511
4551
  for (const warning of current.warnings) p3.log.warn(warning);
@@ -4521,7 +4561,7 @@ async function runDoctor(options = {}) {
4521
4561
  const capabilityReport = adapter.reportCapabilities(detection.configDir);
4522
4562
  const capabilitySummary = capabilityReport.capabilities.map((capability) => `${capability.id}=${capability.state}`).join(", ");
4523
4563
  p3.log.info(`${adapter.name}: capabilities diagnostic (${capabilitySummary}); no certifica enforcement local.`);
4524
- const ctx = makeContext(adapter, detection.configDir, modePreference);
4564
+ const ctx = makeContext(adapter, detection.configDir, modePreference, true, effectivePlaywright);
4525
4565
  if (!ctx) continue;
4526
4566
  ctx.writingStyle = writingStyle;
4527
4567
  let pending;
@@ -5300,6 +5340,7 @@ async function runInteractiveUpdate(localVersion, yes, dryRun = false, includeBr
5300
5340
  let exitCode = 0;
5301
5341
  let appliedUpdates = false;
5302
5342
  const updated = [];
5343
+ let playwrightCapability;
5303
5344
  const maintainer = isGitClone();
5304
5345
  const spin = p4.spinner();
5305
5346
  spin.start("Consultando versiones upstream\u2026");
@@ -5519,6 +5560,7 @@ ${diff}`);
5519
5560
  p4.log.success("Playwright CLI actualizado al pin aprobado.");
5520
5561
  appliedUpdates = true;
5521
5562
  updated.push("playwright-cli");
5563
+ playwrightCapability = inspectPlaywrightCapability({ browserVerified: true });
5522
5564
  } else {
5523
5565
  const failedResult = packageResult.ok ? browserResult : packageResult;
5524
5566
  const pnpmRemedy = failedResult && !failedResult.ok ? resolvePnpmFailureRemedy(failedResult.reason) : null;
@@ -5555,7 +5597,12 @@ ${diff}`);
5555
5597
  }
5556
5598
  }
5557
5599
  p4.outro(exitCode === 0 ? "Update completado." : "Update completado con errores (revisa arriba).");
5558
- return { exitCode, appliedUpdates, syncRequired: resolveUpdateSyncRequired(updated) };
5600
+ return {
5601
+ exitCode,
5602
+ appliedUpdates,
5603
+ syncRequired: resolveUpdateSyncRequired(updated),
5604
+ ...playwrightCapability === void 0 ? {} : { playwrightCapability }
5605
+ };
5559
5606
  }
5560
5607
 
5561
5608
  // src/models-picker.ts
@@ -6430,10 +6477,16 @@ async function runManagedPiSystem(input) {
6430
6477
  const {
6431
6478
  devtoolsMcpEnabled: explicitDevtools,
6432
6479
  playwrightCliEnabled: explicitPlaywright,
6480
+ playwrightCapability,
6481
+ packageOnly,
6433
6482
  writingStyle: suppliedStyle,
6434
6483
  writingStyleMode,
6435
6484
  ...runtimeInput
6436
6485
  } = input;
6486
+ if (input.operation === "doctor" && packageOnly) {
6487
+ const result2 = managedPackageResult(await runPiRuntimeSystem(runtimeInput));
6488
+ return result2.kind === "manual-existing" ? manualExistingResult(result2) : result2;
6489
+ }
6437
6490
  const readsStyle = input.operation !== "uninstall" && input.operation !== "models";
6438
6491
  if (input.operation !== "models") {
6439
6492
  try {
@@ -6448,8 +6501,11 @@ async function runManagedPiSystem(input) {
6448
6501
  const writingStyle = style && mode === "programmatic" ? { ...style, content: null } : style;
6449
6502
  const devtoolsMcpEnabled = explicitDevtools ?? (input.targetDir === void 0 && loadDevtoolsMcpPreference(devtoolsMcpPreferenceFile(), "pi"));
6450
6503
  const supportsPlaywright = PI_RUNTIME_CANDIDATE.contract.capabilities.includes("playwright-handoff-v1");
6451
- const playwrightCliEnabled = input.targetDir === void 0 && supportsPlaywright && (explicitPlaywright ?? loadPlaywrightCliPreference(void 0, "pi") === true);
6452
- const playwrightCliCommand = playwrightCliEnabled && input.operation !== "uninstall" && input.operation !== "models" ? detectPlaywrightCli().binPath : null;
6504
+ const persistedPlaywright = input.targetDir === void 0 && loadPlaywrightCliPreference(void 0, "pi") === true;
6505
+ const selectedPlaywright = explicitPlaywright ?? persistedPlaywright;
6506
+ const playwrightCapabilityEffective = playwrightCapability?.effective === true;
6507
+ const playwrightCliEnabled = input.targetDir === void 0 && supportsPlaywright && selectedPlaywright && playwrightCapabilityEffective;
6508
+ const playwrightCliCommand = playwrightCliEnabled && input.operation !== "uninstall" && input.operation !== "models" ? playwrightCapability?.cli.binPath ?? null : null;
6453
6509
  const projectionInput = {
6454
6510
  writingStyle,
6455
6511
  targetDir: input.targetDir,
@@ -7001,6 +7057,9 @@ async function ensureOpenCodeModelsForInstall(command, flags, runtimes) {
7001
7057
  );
7002
7058
  return false;
7003
7059
  }
7060
+ function shouldInspectPlaywrightCapability(targetDir, dryRun) {
7061
+ return targetDir === void 0 && !dryRun && browserPreferenceErrors().length === 0 && loadPlaywrightCliPreference() === true;
7062
+ }
7004
7063
  function parseFlags(args, allowReceipt = false) {
7005
7064
  const flags = {
7006
7065
  agents: [],
@@ -7310,7 +7369,19 @@ async function resolveHostEngramForInstall(flags) {
7310
7369
  return { ok: false, message: `Engram: ${error instanceof Error ? error.message : String(error)}` };
7311
7370
  }
7312
7371
  }
7313
- async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBin, devtoolsMcpEnabled, writingStyle, modePreference, playwrightCliEnabled) {
7372
+ async function runSelectedPi(options) {
7373
+ const {
7374
+ operation,
7375
+ targetDir,
7376
+ yes = false,
7377
+ resolvedEngramBin,
7378
+ devtoolsMcpEnabled,
7379
+ writingStyle: suppliedWritingStyle,
7380
+ modePreference,
7381
+ playwrightCliEnabled,
7382
+ playwrightCapability
7383
+ } = options;
7384
+ let writingStyle = suppliedWritingStyle;
7314
7385
  if (targetDir === void 0 && operation !== "models") {
7315
7386
  const preferenceErrors = browserPreferenceErrors();
7316
7387
  if (preferenceErrors.length > 0) {
@@ -7356,6 +7427,7 @@ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBi
7356
7427
  engramBin = requirement.bin;
7357
7428
  }
7358
7429
  const result = await runManagedPiSystem({
7430
+ ...options.packageOnly ? { packageOnly: true } : {},
7359
7431
  writingStyle,
7360
7432
  writingStyleMode: modePreference?.mode,
7361
7433
  operation,
@@ -7363,7 +7435,8 @@ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBi
7363
7435
  detected: { executable: detected.executable, version: detected.version },
7364
7436
  engramBin,
7365
7437
  ...devtoolsMcpEnabled === void 0 ? {} : { devtoolsMcpEnabled },
7366
- ...playwrightCliEnabled === void 0 ? {} : { playwrightCliEnabled }
7438
+ ...playwrightCliEnabled === void 0 ? {} : { playwrightCliEnabled },
7439
+ ...playwrightCapability === void 0 ? {} : { playwrightCapability }
7367
7440
  });
7368
7441
  if (result.kind === "blocked") {
7369
7442
  const paths = "paths" in result ? `: ${result.paths.join(", ")}` : "";
@@ -7521,6 +7594,11 @@ Flags disponibles: jorgex-stack --help`
7521
7594
  exitCode = process.exitCode === 1 ? 1 : 0;
7522
7595
  return;
7523
7596
  }
7597
+ const playwrightToolPlan = resolvePlaywrightToolPlan(playwrightToolConsent);
7598
+ let playwrightCapability = shouldInspectPlaywrightCapability(flags.targetDir, flags.dryRun) && playwrightToolPlan.actions.length === 0 ? inspectPlaywrightCapability() : void 0;
7599
+ const capturePlaywrightCapability = (snapshot) => {
7600
+ playwrightCapability = snapshot;
7601
+ };
7524
7602
  p6.log.info(`Estilo de escritura: ${writingStyle.sourcePath}${flags.dryRun ? " (instalaci\xF3n prevista; sin escrituras)" : ""}.`);
7525
7603
  applyWritingStyle(writingStyle, flags.dryRun);
7526
7604
  if (!await ensureOpenCodeModelsForInstall(command, flags, fileRuntimes)) {
@@ -7548,11 +7626,13 @@ Flags disponibles: jorgex-stack --help`
7548
7626
  playwrightToolConsent,
7549
7627
  devtoolsMcpSelection,
7550
7628
  engramBin,
7629
+ ...playwrightCapability === void 0 ? {} : { playwrightCapability },
7630
+ ...playwrightToolPlan.actions.length === 0 ? {} : { onPlaywrightCapability: capturePlaywrightCapability },
7551
7631
  showSummary: false
7552
7632
  });
7553
7633
  }
7554
7634
  let piCanRun = true;
7555
- if (command === "install" && fileRuntimes.length === 0 && runtimes.includes("pi") && resolvePlaywrightToolPlan(playwrightToolConsent).actions.length > 0) {
7635
+ if (command === "install" && fileRuntimes.length === 0 && runtimes.includes("pi") && playwrightToolPlan.actions.length > 0) {
7556
7636
  if (flags.dryRun) {
7557
7637
  p6.log.info("Playwright CLI: instalaci\xF3n global y navegador previstos (dry-run; no se ejecutan).");
7558
7638
  } else {
@@ -7564,6 +7644,7 @@ Flags disponibles: jorgex-stack --help`
7564
7644
  yes: flags.yes,
7565
7645
  playwrightToolConsent,
7566
7646
  engramBin,
7647
+ onPlaywrightCapability: capturePlaywrightCapability,
7567
7648
  showSummary: false
7568
7649
  });
7569
7650
  piCanRun = exitCode === 0;
@@ -7572,16 +7653,17 @@ Flags disponibles: jorgex-stack --help`
7572
7653
  if (runtimes.includes("pi") && piCanRun) {
7573
7654
  if (flags.dryRun) p6.log.info(`Pi: ${command} previsto; dry-run no ejecuta subprocess ni escribe receipt.`);
7574
7655
  else {
7575
- const piExitCode = await runSelectedPi(
7576
- command,
7577
- flags.targetDir,
7578
- flags.yes,
7579
- flags.targetDir === void 0 ? engramBin : void 0,
7580
- devtoolsMcpSelection.pi,
7656
+ const piExitCode = await runSelectedPi({
7657
+ operation: command,
7658
+ targetDir: flags.targetDir,
7659
+ yes: flags.yes,
7660
+ resolvedEngramBin: flags.targetDir === void 0 ? engramBin : void 0,
7661
+ devtoolsMcpEnabled: devtoolsMcpSelection.pi,
7581
7662
  writingStyle,
7582
- mode,
7583
- flags.targetDir === void 0 && exitCode === 0 && resolvePlaywrightToolPlan(playwrightToolConsent).actions.length > 0 ? playwrightToolConsent.runtimeSelection?.pi : void 0
7584
- );
7663
+ modePreference: mode,
7664
+ playwrightCliEnabled: flags.targetDir === void 0 && exitCode === 0 && playwrightToolPlan.actions.length > 0 ? playwrightToolConsent.runtimeSelection?.pi : void 0,
7665
+ playwrightCapability
7666
+ });
7585
7667
  exitCode = Math.max(exitCode, piExitCode);
7586
7668
  }
7587
7669
  }
@@ -7623,7 +7705,7 @@ Flags disponibles: jorgex-stack --help`
7623
7705
  }) : 0;
7624
7706
  if (runtimes.includes("pi")) {
7625
7707
  if (flags.dryRun) p6.log.info("Pi: uninstall previsto; dry-run conserva paquete y receipt.");
7626
- else exitCode = Math.max(exitCode, await runSelectedPi("uninstall", flags.targetDir));
7708
+ else exitCode = Math.max(exitCode, await runSelectedPi({ operation: "uninstall", targetDir: flags.targetDir }));
7627
7709
  }
7628
7710
  process.exitCode = exitCode;
7629
7711
  return;
@@ -7636,8 +7718,25 @@ Flags disponibles: jorgex-stack --help`
7636
7718
  ...Object.values(ADAPTERS).filter((adapter) => adapter.detect().installed).map((adapter) => adapter.id),
7637
7719
  ...piSelected ? ["pi"] : []
7638
7720
  ] : [...Object.keys(ADAPTERS), ...piSelected ? ["pi"] : []];
7639
- let exitCode = await runDoctor({ targetDir: flags.targetDir, runtimes: doctorRuntimes, mode });
7640
- if (piSelected) exitCode = Math.max(exitCode, await runSelectedPi("doctor", flags.targetDir, false, void 0, void 0, void 0, mode));
7721
+ const doctorCapability = shouldInspectPlaywrightCapability(flags.targetDir, flags.dryRun) ? inspectPlaywrightCapability() : void 0;
7722
+ let exitCode = await runDoctor({
7723
+ targetDir: flags.targetDir,
7724
+ runtimes: doctorRuntimes,
7725
+ mode,
7726
+ ...flags.dryRun ? { dryRun: true } : {},
7727
+ ...doctorCapability === void 0 ? {} : { playwrightCapability: doctorCapability }
7728
+ });
7729
+ if (piSelected) {
7730
+ if (flags.dryRun) p6.log.info("Pi: doctor previsto; dry-run no ejecuta subprocess ni smoke de Playwright.");
7731
+ else {
7732
+ exitCode = Math.max(exitCode, await runSelectedPi({
7733
+ operation: "doctor",
7734
+ targetDir: flags.targetDir,
7735
+ modePreference: mode,
7736
+ playwrightCapability: doctorCapability
7737
+ }));
7738
+ }
7739
+ }
7641
7740
  process.exitCode = exitCode;
7642
7741
  return;
7643
7742
  }
@@ -7646,7 +7745,16 @@ Flags disponibles: jorgex-stack --help`
7646
7745
  const piExplicit = flags.agents.includes("pi");
7647
7746
  const fileRuntimeExplicit = flags.agents.some(isFileManagedRuntime);
7648
7747
  let exitCode = flags.agents.length === 0 || fileRuntimeExplicit ? await runUpdateCheck(VERSION, flags.targetDir === void 0) : 0;
7649
- if (piExplicit) exitCode = Math.max(exitCode, await runSelectedPi("doctor", flags.targetDir));
7748
+ if (piExplicit) {
7749
+ if (flags.dryRun) p6.log.info("Pi: doctor previsto; dry-run no ejecuta subprocess ni smoke de Playwright.");
7750
+ else {
7751
+ exitCode = Math.max(exitCode, await runSelectedPi({
7752
+ operation: "doctor",
7753
+ targetDir: flags.targetDir,
7754
+ packageOnly: true
7755
+ }));
7756
+ }
7757
+ }
7650
7758
  process.exitCode = exitCode;
7651
7759
  return;
7652
7760
  }
@@ -7671,8 +7779,15 @@ Flags disponibles: jorgex-stack --help`
7671
7779
  { rootDir: flags.targetDir }
7672
7780
  );
7673
7781
  applyWritingStyle(writingStyle, flags.dryRun);
7782
+ let updateCapability = shouldInspectPlaywrightCapability(flags.targetDir, flags.dryRun) ? inspectPlaywrightCapability() : void 0;
7674
7783
  if (fileRuntimes.length === 0 && runtimes.includes("pi")) {
7675
- const piExitCode = await runSelectedPi("update", flags.targetDir, false, void 0, void 0, writingStyle, mode);
7784
+ const piExitCode = await runSelectedPi({
7785
+ operation: "update",
7786
+ targetDir: flags.targetDir,
7787
+ writingStyle,
7788
+ modePreference: mode,
7789
+ playwrightCapability: updateCapability
7790
+ });
7676
7791
  process.exitCode = piExitCode;
7677
7792
  persistSuccessfulGlobalMode(mode, flags.targetDir, flags.dryRun, piExitCode);
7678
7793
  return;
@@ -7685,7 +7800,8 @@ Flags disponibles: jorgex-stack --help`
7685
7800
  targetDir: flags.targetDir,
7686
7801
  dryRun: flags.dryRun,
7687
7802
  yes: true,
7688
- mode
7803
+ mode,
7804
+ ...updateCapability === void 0 ? {} : { playwrightCapability: updateCapability }
7689
7805
  });
7690
7806
  if (code !== 0) {
7691
7807
  process.exitCode = code;
@@ -7701,12 +7817,35 @@ Flags disponibles: jorgex-stack --help`
7701
7817
  flags.targetDir === void 0
7702
7818
  );
7703
7819
  process.exitCode = result.exitCode;
7820
+ let playwrightReconciled = false;
7821
+ if (result.exitCode === 0 && result.playwrightCapability !== void 0) {
7822
+ updateCapability = result.playwrightCapability;
7823
+ if (fileRuntimes.length > 0 && canSync) {
7824
+ const code = await runInstall({
7825
+ runtimes: fileRuntimes,
7826
+ writingStyle,
7827
+ targetDir: flags.targetDir,
7828
+ dryRun: false,
7829
+ yes: true,
7830
+ mode,
7831
+ playwrightCapability: updateCapability
7832
+ });
7833
+ process.exitCode = Math.max(process.exitCode ?? 0, code);
7834
+ playwrightReconciled = true;
7835
+ }
7836
+ }
7704
7837
  if (result.exitCode === 0 && runtimes.includes("pi")) {
7705
- process.exitCode = Math.max(process.exitCode, await runSelectedPi("update", flags.targetDir, false, void 0, void 0, writingStyle, mode));
7838
+ process.exitCode = Math.max(process.exitCode ?? 0, await runSelectedPi({
7839
+ operation: "update",
7840
+ targetDir: flags.targetDir,
7841
+ writingStyle,
7842
+ modePreference: mode,
7843
+ playwrightCapability: updateCapability
7844
+ }));
7706
7845
  }
7707
7846
  if (result.syncRequired && fileRuntimes.length > 0 && (result.exitCode !== 0 || !canSync)) {
7708
7847
  p6.log.warn("Skills/stack actualizados, pero el sync con los runtimes sigue pendiente. Ejecuta jorgex-stack sync --mode human|programmatic.");
7709
- } else if (result.exitCode === 0 && result.syncRequired && fileRuntimes.length > 0 && canSync && !flags.yes && process.stdout.isTTY) {
7848
+ } else if (!playwrightReconciled && result.exitCode === 0 && result.syncRequired && fileRuntimes.length > 0 && canSync && !flags.yes && process.stdout.isTTY) {
7710
7849
  const apply = await p6.confirm({ message: "\xBFRe-aplicar a los runtimes ahora? (sync)" });
7711
7850
  if (!p6.isCancel(apply) && apply) {
7712
7851
  process.exitCode = await runInstall({
@@ -7715,7 +7854,8 @@ Flags disponibles: jorgex-stack --help`
7715
7854
  targetDir: flags.targetDir,
7716
7855
  dryRun: false,
7717
7856
  yes: false,
7718
- mode
7857
+ mode,
7858
+ ...updateCapability === void 0 ? {} : { playwrightCapability: updateCapability }
7719
7859
  });
7720
7860
  } else {
7721
7861
  console.log("Sin aplicar. Cuando quieras: jorgex-stack sync");
@@ -7738,7 +7878,7 @@ Flags disponibles: jorgex-stack --help`
7738
7878
  }
7739
7879
  const fileRuntimes = runtimes.filter(isFileManagedRuntime);
7740
7880
  let code = fileRuntimes.length > 0 ? await runModelsPicker({ yes: flags.yes, runtimes: fileRuntimes }) : 0;
7741
- if (runtimes.includes("pi")) code = Math.max(code, await runSelectedPi("models", flags.targetDir));
7881
+ if (runtimes.includes("pi")) code = Math.max(code, await runSelectedPi({ operation: "models", targetDir: flags.targetDir }));
7742
7882
  process.exitCode = code;
7743
7883
  if (code === 0 && fileRuntimes.length > 0 && !flags.yes && process.stdout.isTTY) {
7744
7884
  const apply = await p6.confirm({ message: "\xBFAplicar ahora los modelos a los agentes instalados? (sync)" });
@@ -7753,7 +7893,15 @@ Flags disponibles: jorgex-stack --help`
7753
7893
  }
7754
7894
  const mode = await resolveInstallMode(flags, false);
7755
7895
  if (mode === null) return;
7756
- process.exitCode = await runInstall({ runtimes: fileRuntimes, targetDir: flags.targetDir, dryRun: flags.dryRun, yes: false, mode });
7896
+ const playwrightCapability = shouldInspectPlaywrightCapability(flags.targetDir, flags.dryRun) ? inspectPlaywrightCapability() : void 0;
7897
+ process.exitCode = await runInstall({
7898
+ runtimes: fileRuntimes,
7899
+ targetDir: flags.targetDir,
7900
+ dryRun: flags.dryRun,
7901
+ yes: false,
7902
+ mode,
7903
+ ...playwrightCapability === void 0 ? {} : { playwrightCapability }
7904
+ });
7757
7905
  } else {
7758
7906
  console.log("Sin aplicar. Cuando quieras: jorgex-stack sync");
7759
7907
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jorgex-stack",
3
- "version": "1.9.39",
3
+ "version": "1.9.40",
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",