jorgex-stack 1.9.31 → 1.9.32

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
@@ -120,7 +120,7 @@ pnpm dlx jorgex-stack@1.9.7 sync --agents pi
120
120
  pnpm dlx jorgex-stack@1.9.7 uninstall --agents pi
121
121
  ```
122
122
 
123
- Stack downloads the frozen registry tarball, verifies its exact size plus SHA-256/SHA-512, backs up Pi's `settings.json`, and only then asks Pi to install that local file. The historical `0.8.0` tarball was `89128340` bytes; the exact adopted artifact and integrity values are authoritative in `src/lib/pi-runtime-pin.json`, while the lifecycle remains authoritative in `src/lib/pi-runtime.ts`. Pi's own package-manager invocation is the narrow runtime exception to the repository's pnpm-only rule; the Stack lifecycle never launches npm directly. After the package is healthy, Stack projects the shared resources into Pi: marked `jorgex:system-prompt` and `jorgex:engram-protocol` sections in `~/.pi/agent/AGENTS.md`, canonical skills under `~/.agents/skills`, and `~/.pi/agent/prompts/lean-audit.md`. When the managed Playwright preference is active, the projection also adds or removes the marked `jorgex:browser` section dynamically. The Pi-only `install --agents pi --playwright` flow installs and persists that Playwright capability just like the other harnesses. The adopted Pi `0.8.13` candidate also supports the optional Chrome DevTools handoff; Stack persists the selected handoff as part of the Pi projection and records its SHA-256 in the optional `devtools.sha256` field of `~/.jorgex-stack/pi-projection-receipt.json` after a successful operation. Context7 remains outside the Pi scope. The published `1.9.7` entry `{ "source": "npm:jorgex-pi@0.8.4", "skills": [], "prompts": [] }` and the `0.8.5` snapshot reference are historical; they do not describe the adopted pin. Filters are applied only after this projection exists, so the package does not duplicate shared resources. Package ownership is recorded separately in `~/.jorgex-stack/pi-receipt.json`; projection ownership is recorded in `~/.jorgex-stack/pi-projection-receipt.json`. Package receipts reject manual, duplicate, divergent, partial, corrupt, copied-to-another-scope, or unknown-history state. Projection cleanup requires an exact scope-bound ownership receipt; DevTools conflicts preserve the handoff for review.
123
+ Stack downloads the frozen registry tarball, verifies its exact size plus SHA-256/SHA-512, backs up Pi's `settings.json`, and only then asks Pi to install that local file. The historical `0.8.0` tarball was `89128340` bytes; the exact adopted artifact and integrity values are authoritative in `src/lib/pi-runtime-pin.json`, while the lifecycle remains authoritative in `src/lib/pi-runtime.ts`. Pi's own package-manager invocation is the narrow runtime exception to the repository's pnpm-only rule; the Stack lifecycle never launches npm directly. After the package is healthy, Stack projects the shared resources into Pi: marked `jorgex:system-prompt` and `jorgex:engram-protocol` sections in `~/.pi/agent/AGENTS.md`, canonical skills under `~/.agents/skills`, and `~/.pi/agent/prompts/lean-audit.md`. When the managed Playwright preference is active, the projection also adds or removes the marked `jorgex:browser` section dynamically. The global Playwright package and Chromium cache are shared by the machine; `--playwright-runtimes` controls which runtime receives the guide. Pi is selectable only when the adopted package declares `playwright-handoff-v1`; otherwise Stack reports that it can install the global capability but cannot project it into Pi yet. The adopted Pi `0.8.13` candidate also supports the optional Chrome DevTools handoff; Stack persists the selected handoff as part of the Pi projection and records its SHA-256 in the optional `devtools.sha256` field of `~/.jorgex-stack/pi-projection-receipt.json` after a successful operation. Context7 remains outside the Pi scope. The published `1.9.7` entry `{ "source": "npm:jorgex-pi@0.8.4", "skills": [], "prompts": [] }` and the `0.8.5` snapshot reference are historical; they do not describe the adopted pin. Filters are applied only after this projection exists, so the package does not duplicate shared resources. Package ownership is recorded separately in `~/.jorgex-stack/pi-receipt.json`; projection ownership is recorded in `~/.jorgex-stack/pi-projection-receipt.json`. Package receipts reject manual, duplicate, divergent, partial, corrupt, copied-to-another-scope, or unknown-history state. Projection cleanup requires an exact scope-bound ownership receipt; DevTools conflicts preserve the handoff for review.
124
124
 
125
125
  Historically, the published Pi 0.8.0 direct-package snapshot added `work-audit`: the snapshot grew from **17 to 18 skill trees** (96 to 97 files), and the active runtime allowlist grew from **16 to 17 skills**. The historical Pi 0.8.5 snapshot reference records 18 skill trees and 98 files; it does not describe the adopted pin. Reference F2-A is included while the private F1 skills remain preserved. `playwright-cli` remains in the snapshot but inactive because browser automation is a separate opt-in integration.
126
126
 
@@ -156,6 +156,9 @@ pnpm dlx jorgex-stack install
156
156
  # Non-interactive / agent: --playwright authorizes the global install.
157
157
  pnpm dlx jorgex-stack install --yes --playwright
158
158
 
159
+ # Instala el paquete compartido y activa la guía solo en estos runtimes.
160
+ pnpm dlx jorgex-stack install --playwright --playwright-runtimes=opencode,claude-code
161
+
159
162
  # Enable Chrome DevTools MCP explicitly per runtime.
160
163
  pnpm dlx jorgex-stack install --devtools
161
164
  pnpm dlx jorgex-stack install --no-devtools
@@ -163,7 +166,7 @@ pnpm dlx jorgex-stack install --agents pi --devtools
163
166
  pnpm dlx jorgex-stack sync --agents pi --no-devtools
164
167
  ```
165
168
 
166
- Under the hood, `--playwright` runs two `pnpm` argv-only plans back to back: `pnpm add --global @playwright/cli@0.1.18` (the package) and then `pnpm dlx @playwright/cli@0.1.18 install-browser` (the browser binary cache). Removal is `pnpm remove --global @playwright/cli` (no version suffix). If installation fails, the error identifies the failed phase — global package, browser download, or preference persistence — and recommends `jorgex-stack install --playwright`; the preference is not marked enabled unless the complete plan succeeds.
169
+ Under the hood, `--playwright` runs two `pnpm` argv-only plans back to back: `pnpm add --global @playwright/cli@0.1.18` (the package) and then `pnpm dlx @playwright/cli@0.1.18 install-browser chromium` (the Chromium cache). It then verifies that the pinned package launches Chromium headless against `about:blank`. Removal is `pnpm remove --global @playwright/cli` (no version suffix). If installation fails, the error identifies the failed phase — global package, browser download, browser launch, or preference persistence — and recommends `jorgex-stack install --playwright`; the preference is not marked enabled unless the complete plan succeeds.
167
170
 
168
171
  Daily operation:
169
172
 
@@ -179,7 +182,7 @@ Daily operation:
179
182
 
180
183
  1. **Stack** (jorgex-stack): detects whether it is a git clone or a global install, then offers an update with confirmation.
181
184
  2. **Engram** (binary): detects the installed version and offers an update through the **native channel** (brew -> `go install` -> release URL). Nothing needs to be stopped: as in upstream macOS/Linux, live processes keep using the old version until clients restart; on Windows, the in-use `.exe` is rotated by rename before installation. **Automatic DB backup before updating**. The database and memories are never touched.
182
- 3. **Playwright CLI** (only when explicitly enabled): compares the detected binary with the approved bundle pin and offers to realign it with explicit confirmation. The realignment re-applies **both** plans — `pnpm add --global @playwright/cli@0.1.18` (package) and `pnpm dlx @playwright/cli@0.1.18 install-browser` (browser cache) — and fails closed if either step returns non-zero. The error identifies whether the package-update or browser-download phase failed and recommends `jorgex-stack install --playwright` to retry both; a Playwright update does not require `sync`.
185
+ 3. **Playwright CLI** (only when explicitly enabled): compares the detected binary with the approved bundle pin and offers to realign it with explicit confirmation. The realignment re-applies **both** plans — `pnpm add --global @playwright/cli@0.1.18` (package) and `pnpm dlx @playwright/cli@0.1.18 install-browser chromium` (Chromium cache) — and fails closed if either step returns non-zero. The error identifies whether the package-update or browser-download phase failed and recommends `jorgex-stack install --playwright` to retry both; a Playwright update does not require `sync`.
183
186
  4. **Vendored skills** (maintainer only): third-party skills ship **pinned** with the stack version, so the installed package never reaches out to their upstreams. Only when running from a git clone (`pnpm cli update`) does `update` scan the upstreams in `upstreams.json`, download to a temp directory, **show a mandatory diff**, and ask for confirmation. A moved upstream is only a candidate until that review is accepted and a deliberate re-pin is made for a future release; it is never treated as an accepted official update automatically. Skills with local changes (`modified: true`) warn and require double confirmation.
184
187
 
185
188
  Usage:
package/dist/cli.d.ts CHANGED
@@ -23,6 +23,7 @@ interface Flags {
23
23
  removeEngram: boolean;
24
24
  engram: boolean;
25
25
  playwright: boolean;
26
+ playwrightRuntimes?: SelectableRuntimeId[];
26
27
  removePlaywright: boolean;
27
28
  devtools: boolean;
28
29
  noDevtools: boolean;
package/dist/cli.js CHANGED
@@ -2165,6 +2165,8 @@ function resolvePnpmFailureRemedy(reason) {
2165
2165
  return PNPM_GLOBAL_BIN_REMEDY;
2166
2166
  case "action-failed":
2167
2167
  return null;
2168
+ case "browser-launch":
2169
+ return "Chromium se descarg\xF3, pero no ha podido arrancar. Revisa el error de lanzamiento y las dependencias de tu sistema antes de reintentar.";
2168
2170
  }
2169
2171
  }
2170
2172
  function hasNonzeroProcessStatus(error) {
@@ -2186,6 +2188,18 @@ function resolvePlaywrightCliState({ binPath, versionOutput }) {
2186
2188
  }
2187
2189
  function detectPlaywrightCli() {
2188
2190
  const binPath = lookPath(PLAYWRIGHT_CLI.bin);
2191
+ if (binPath === null) {
2192
+ const pnpmHome = resolvePnpmHome();
2193
+ const name = process.platform === "win32" ? `${PLAYWRIGHT_CLI.bin}.cmd` : PLAYWRIGHT_CLI.bin;
2194
+ const known = [path18.join(pnpmHome, name), path18.join(pnpmHome, "bin", name)].find((file) => {
2195
+ try {
2196
+ return fs13.statSync(file).isFile();
2197
+ } catch {
2198
+ return false;
2199
+ }
2200
+ });
2201
+ if (known) return { status: "not-in-path", binPath: known, detectedVersion: null };
2202
+ }
2189
2203
  return resolvePlaywrightCliState({
2190
2204
  binPath,
2191
2205
  versionOutput: binPath ? runDetectedBin(binPath, ["--version"], 5e3, { NO_UPDATE_NOTIFIER: "1" }) : null
@@ -2248,7 +2262,44 @@ function planPlaywrightCliCommand(action, pnpmBin) {
2248
2262
  case "remove":
2249
2263
  return { command: pnpmBin, args: ["remove", "--global", PLAYWRIGHT_CLI.packageName] };
2250
2264
  case "install-browser":
2251
- return { command: pnpmBin, args: ["dlx", pinnedPackage, PLAYWRIGHT_CLI.browserInstallAction] };
2265
+ return { command: pnpmBin, args: ["dlx", pinnedPackage, PLAYWRIGHT_CLI.browserInstallAction, "chromium"] };
2266
+ }
2267
+ }
2268
+ function verifyPlaywrightBrowser(pnpmBin, env = process.env, cwd) {
2269
+ const rootCommand = planDetectedBinCommand(pnpmBin, ["root", "--global"]);
2270
+ if (rootCommand === null) return false;
2271
+ try {
2272
+ const root = execFileSync2(rootCommand.command, rootCommand.args, {
2273
+ encoding: "utf8",
2274
+ timeout: 5e3,
2275
+ env,
2276
+ cwd,
2277
+ stdio: ["ignore", "pipe", "inherit"]
2278
+ }).trim();
2279
+ if (!path18.isAbsolute(root)) return false;
2280
+ const packageFile = fs13.realpathSync(path18.join(root, "@playwright", "cli", "package.json"));
2281
+ const installed = JSON.parse(fs13.readFileSync(packageFile, "utf8"));
2282
+ if (installed.name !== PLAYWRIGHT_CLI.packageName || installed.version !== PLAYWRIGHT_CLI.version) return false;
2283
+ const probe = `
2284
+ const { createRequire } = require('node:module');
2285
+ const { chromium } = createRequire(process.argv[1])('playwright');
2286
+ (async () => {
2287
+ const browser = await chromium.launch({ headless: true, timeout: 15000 });
2288
+ try {
2289
+ const page = await browser.newPage();
2290
+ await page.goto('about:blank', { timeout: 5000 });
2291
+ } finally { await browser.close(); }
2292
+ })().catch(error => { console.error(error.message); process.exitCode = 1; });
2293
+ `;
2294
+ execFileSync2(process.execPath, ["-e", probe, packageFile], {
2295
+ timeout: 25e3,
2296
+ stdio: "inherit",
2297
+ cwd,
2298
+ env: { ...env, NO_UPDATE_NOTIFIER: "1" }
2299
+ });
2300
+ return true;
2301
+ } catch {
2302
+ return false;
2252
2303
  }
2253
2304
  }
2254
2305
  function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin(), env) {
@@ -2270,14 +2321,22 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin(), env) {
2270
2321
  const command = planPlaywrightCliCommand(action, pnpmBin);
2271
2322
  const invocation = planDetectedBinCommand(command.command, command.args);
2272
2323
  if (invocation === null) return { ok: false, reason: "pnpm-command" };
2324
+ const cwd = action === "install-browser" ? fs13.mkdtempSync(path18.join(os.tmpdir(), "jorgex-playwright-")) : void 0;
2325
+ const childEnv = action === "install-browser" ? { ...env ?? process.env, NO_UPDATE_NOTIFIER: "1" } : env ?? process.env;
2273
2326
  try {
2274
2327
  execFileSync2(invocation.command, invocation.args, {
2275
2328
  stdio: "inherit",
2276
- ...env === void 0 ? {} : { env }
2329
+ env: childEnv,
2330
+ ...cwd === void 0 ? {} : { cwd }
2277
2331
  });
2332
+ if (action === "install-browser" && !verifyPlaywrightBrowser(pnpmBin, childEnv, cwd)) {
2333
+ return { ok: false, reason: "browser-launch" };
2334
+ }
2278
2335
  return { ok: true };
2279
2336
  } catch {
2280
2337
  return { ok: false, reason: "action-failed" };
2338
+ } finally {
2339
+ if (cwd !== void 0) fs13.rmSync(cwd, { recursive: true, force: true });
2281
2340
  }
2282
2341
  }
2283
2342
 
@@ -2287,6 +2346,7 @@ import path19 from "path";
2287
2346
  var PLAYWRIGHT_CLI_PREFERENCE_VERSION = 1;
2288
2347
  var DEVTOOLS_MCP_PREFERENCE_VERSION = 1;
2289
2348
  var PRIMARY_MODEL_OWNERSHIP_VERSION = 1;
2349
+ var PLAYWRIGHT_RUNTIMES = ["opencode", "claude-code", "codex", "pi"];
2290
2350
  function readPreference(file) {
2291
2351
  try {
2292
2352
  return { raw: fs14.readFileSync(file, "utf8"), errorCode: null };
@@ -2301,8 +2361,11 @@ function playwrightCliPreferenceFile(stateDir = dataDir()) {
2301
2361
  function parsePlaywrightCliPreference(raw) {
2302
2362
  try {
2303
2363
  const value = JSON.parse(raw);
2304
- if (value.version !== PLAYWRIGHT_CLI_PREFERENCE_VERSION || typeof value.enabled !== "boolean") return void 0;
2305
- return value.enabled;
2364
+ if (!isRecord2(value)) return void 0;
2365
+ if (value.version === 1 && typeof value.enabled === "boolean") return { version: 1, enabled: value.enabled };
2366
+ if (value.version !== 2 || !isRecord2(value.enabled)) return void 0;
2367
+ if (Object.entries(value.enabled).some(([runtime, enabled]) => !PLAYWRIGHT_RUNTIMES.includes(runtime) || typeof enabled !== "boolean")) return void 0;
2368
+ return { version: 2, enabled: value.enabled };
2306
2369
  } catch {
2307
2370
  return void 0;
2308
2371
  }
@@ -2315,15 +2378,28 @@ function playwrightCliPreferenceError(file = playwrightCliPreferenceFile()) {
2315
2378
  if (raw === null || parsePlaywrightCliPreference(raw) !== void 0) return null;
2316
2379
  return `Playwright CLI: preferencia inv\xE1lida en ${file}. Corrige o borra ese archivo antes de reintentar.`;
2317
2380
  }
2318
- function loadPlaywrightCliPreference(file = playwrightCliPreferenceFile()) {
2381
+ function loadPlaywrightCliPreference(file = playwrightCliPreferenceFile(), runtime) {
2319
2382
  const { raw } = readPreference(file);
2320
- if (raw === null) return void 0;
2321
- return parsePlaywrightCliPreference(raw);
2383
+ const state = raw === null ? void 0 : parsePlaywrightCliPreference(raw);
2384
+ if (state === void 0) return void 0;
2385
+ if (state.version === 1) return state.enabled;
2386
+ return runtime === void 0 ? Object.values(state.enabled).some(Boolean) : state.enabled[runtime] === true;
2322
2387
  }
2323
- function savePlaywrightCliPreference(file, enabled) {
2388
+ function savePlaywrightCliPreference(file, enabled, selection) {
2324
2389
  const error = playwrightCliPreferenceError(file);
2325
2390
  if (error !== null) throw new Error(error);
2326
- writeText(file, JSON.stringify({ version: PLAYWRIGHT_CLI_PREFERENCE_VERSION, enabled }) + "\n");
2391
+ const { raw } = readPreference(file);
2392
+ const previous = raw === null ? void 0 : parsePlaywrightCliPreference(raw);
2393
+ if (selection === void 0 && previous?.version !== 2) {
2394
+ writeText(file, JSON.stringify({ version: PLAYWRIGHT_CLI_PREFERENCE_VERSION, enabled }) + "\n");
2395
+ return;
2396
+ }
2397
+ const inherited = previous?.version === 2 ? previous.enabled : Object.fromEntries(PLAYWRIGHT_RUNTIMES.map((runtime) => [runtime, previous?.enabled === true]));
2398
+ const choices = { ...inherited, ...selection };
2399
+ const state = { version: 2, enabled: enabled ? choices : Object.fromEntries(Object.keys(choices).map((runtime) => [runtime, false])) };
2400
+ const content = JSON.stringify(state) + "\n";
2401
+ if (parsePlaywrightCliPreference(content) === void 0) throw new Error("Playwright CLI: selecci\xF3n de runtimes inv\xE1lida.");
2402
+ writeText(file, content);
2327
2403
  }
2328
2404
  function devtoolsMcpPreferenceFile(stateDir = dataDir()) {
2329
2405
  return path19.join(stateDir, "devtools-mcp.json");
@@ -2536,7 +2612,7 @@ function makeContext(adapter, configDir, mode = DEFAULT_INSTALL_MODE_PREFERENCE,
2536
2612
  models,
2537
2613
  warnings: [],
2538
2614
  enabledMcpServers: enabledMcpServers(adapter.id, void 0, useBrowserPreferences),
2539
- playwrightCliEnabled: useBrowserPreferences && loadPlaywrightCliPreference() === true,
2615
+ playwrightCliEnabled: useBrowserPreferences && loadPlaywrightCliPreference(playwrightCliPreferenceFile(), adapter.id) === true,
2540
2616
  ownedMcpServers: ownedMcpServers(adapter.id, useBrowserPreferences),
2541
2617
  ownedPrimaryModelFields: useBrowserPreferences ? loadPrimaryModelOwnership(primaryModelOwnershipFile(), adapter.id, configDir) : /* @__PURE__ */ new Set()
2542
2618
  };
@@ -2655,7 +2731,7 @@ async function runInstall(opts) {
2655
2731
  models,
2656
2732
  warnings: [],
2657
2733
  enabledMcpServers: enabledMcpServers(id, opts.devtoolsMcpSelection?.[id], useManifest),
2658
- playwrightCliEnabled: projectPlaywrightPrompt || useManifest && loadPlaywrightCliPreference() === true,
2734
+ playwrightCliEnabled: projectPlaywrightPrompt ? opts.playwrightToolConsent?.runtimeSelection?.[id] ?? true : useManifest && loadPlaywrightCliPreference(playwrightCliPreferenceFile(), id) === true,
2659
2735
  ownedMcpServers: ownedMcpServers(id, useManifest),
2660
2736
  ownedPrimaryModelFields: useManifest ? loadPrimaryModelOwnership(primaryModelOwnershipFile(), id, configDir) : /* @__PURE__ */ new Set()
2661
2737
  };
@@ -2750,7 +2826,7 @@ async function runInstall(opts) {
2750
2826
  const pnpmBin = resolvePnpmBin();
2751
2827
  return executePlaywrightToolAction2(action, pnpmBin, env);
2752
2828
  },
2753
- persistEnabled: (enabled) => savePlaywrightCliPreference(playwrightCliPreferenceFile(), enabled),
2829
+ persistEnabled: (enabled) => savePlaywrightCliPreference(playwrightCliPreferenceFile(), enabled, opts.playwrightToolConsent?.runtimeSelection),
2754
2830
  setupPnpm: (pnpmBin) => setupPnpmGlobal(pnpmBin)
2755
2831
  };
2756
2832
  let setupAttempted = false;
@@ -2778,6 +2854,7 @@ async function runInstall(opts) {
2778
2854
  return first;
2779
2855
  }
2780
2856
  preparedEnv = setup.env;
2857
+ p.log.info("pnpm preparado para esta instalaci\xF3n. Abre una terminal nueva al terminar para que otras herramientas y doctor reciban el PATH actualizado.");
2781
2858
  return baseDeps.run(action, preparedEnv);
2782
2859
  }
2783
2860
  });
@@ -2789,7 +2866,11 @@ async function runInstall(opts) {
2789
2866
  } else {
2790
2867
  let promptReconciliationFailed = false;
2791
2868
  for (const { adapter, ctx } of successfulContexts) {
2792
- const browserCtx = { ...ctx, playwrightCliEnabled: true, warnings: [] };
2869
+ const browserCtx = {
2870
+ ...ctx,
2871
+ playwrightCliEnabled: opts.playwrightToolConsent?.runtimeSelection?.[adapter.id] ?? true,
2872
+ warnings: []
2873
+ };
2793
2874
  try {
2794
2875
  const browserChanges = diffPlan(planSystemPrompt(adapter, browserCtx)).filter((change) => change.status !== "unchanged");
2795
2876
  if (browserChanges.length === 0) continue;
@@ -2812,7 +2893,7 @@ async function runInstall(opts) {
2812
2893
  exitCode = 1;
2813
2894
  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.");
2814
2895
  } else {
2815
- p.log.success("Playwright CLI y navegador instalados.");
2896
+ p.log.success("Playwright CLI instalado y arranque de Chromium verificado.");
2816
2897
  }
2817
2898
  }
2818
2899
  }
@@ -3954,6 +4035,7 @@ function engramVersion(bin) {
3954
4035
  }
3955
4036
  function resolvePlaywrightDoctorState(input) {
3956
4037
  if (input.enabled !== true) return { status: "disabled" };
4038
+ if (input.cli.status === "not-in-path") return { status: "not-in-path" };
3957
4039
  if (input.cli.status === "absent") return { status: "missing", missing: "package" };
3958
4040
  if (input.cli.status === "broken") return { status: "broken" };
3959
4041
  if (input.cli.status === "outdated") return { status: "outdated" };
@@ -4014,7 +4096,10 @@ async function runDoctor() {
4014
4096
  if (playwright.status === "disabled") {
4015
4097
  p3.log.info("Playwright CLI: deshabilitado (opcional). Usa 'install --playwright' para instalarlo de forma expl\xEDcita.");
4016
4098
  } else if (playwright.status === "healthy") {
4017
- p3.log.success("Playwright CLI: paquete y navegador listos.");
4099
+ p3.log.success("Playwright CLI: paquete y cach\xE9 de Chromium detectados (doctor no prueba el arranque).");
4100
+ } else if (playwright.status === "not-in-path") {
4101
+ 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.");
4102
+ problems++;
4018
4103
  } else if (playwright.status === "missing") {
4019
4104
  const target = playwright.missing === "package" ? "el paquete global" : "el navegador de Playwright";
4020
4105
  p3.log.warn(`Playwright CLI: habilitado, pero falta ${target} \u2192 ejecuta 'jorgex-stack install --playwright'.`);
@@ -5480,9 +5565,12 @@ function normalizeInstalledSource(settingsJson, alias, canonical) {
5480
5565
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
5481
5566
  const packages = Reflect.get(parsed, "packages");
5482
5567
  if (!Array.isArray(packages)) return null;
5483
- const hasCanonical = packages.some((entry) => entry === canonical || entry !== null && typeof entry === "object" && !Array.isArray(entry) && Reflect.get(entry, "source") === canonical);
5484
- if (packages.filter((entry) => entry === alias).length !== 1 || hasCanonical) return null;
5485
- Reflect.set(parsed, "packages", packages.map((entry) => entry === alias ? canonical : entry));
5568
+ const sourceOf = (entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) ? Reflect.get(entry, "source") : entry;
5569
+ if (packages.filter((entry) => sourceOf(entry) === alias).length !== 1 || packages.some((entry) => sourceOf(entry) === canonical)) return null;
5570
+ Reflect.set(parsed, "packages", packages.map((entry) => {
5571
+ if (sourceOf(entry) !== alias) return entry;
5572
+ return typeof entry === "string" ? canonical : { ...entry, source: canonical };
5573
+ }));
5486
5574
  return JSON.stringify(parsed);
5487
5575
  } catch {
5488
5576
  return null;
@@ -5945,7 +6033,7 @@ function managedPackageResult(result) {
5945
6033
  async function runManagedPiSystem(input) {
5946
6034
  const { devtoolsMcpEnabled: explicitDevtools, ...runtimeInput } = input;
5947
6035
  const devtoolsMcpEnabled = explicitDevtools ?? (input.targetDir === void 0 && loadDevtoolsMcpPreference(devtoolsMcpPreferenceFile(), "pi"));
5948
- const playwrightCliEnabled = input.targetDir === void 0 && loadPlaywrightCliPreference() === true;
6036
+ const playwrightCliEnabled = input.targetDir === void 0 && loadPlaywrightCliPreference(void 0, "pi") === true;
5949
6037
  const projectionInput = {
5950
6038
  targetDir: input.targetDir,
5951
6039
  packageSource: PI_RUNTIME_CANDIDATE.package.source,
@@ -6472,6 +6560,7 @@ var VERSION = readPackageVersion();
6472
6560
  var COMMANDS = ["install", "sync", "models", "update", "doctor", "restore", "uninstall", "quality"];
6473
6561
  var QUALITY_REJECTED_VALUE_FLAGS = /* @__PURE__ */ new Set([
6474
6562
  "--agents",
6563
+ "--playwright-runtimes",
6475
6564
  "-a",
6476
6565
  "--target-dir",
6477
6566
  "--mode",
@@ -6557,7 +6646,13 @@ function parseFlags(args, allowReceipt = false) {
6557
6646
  flags.subagentConcurrency = value ?? "";
6558
6647
  i = nextIndex;
6559
6648
  } else if (arg.startsWith("--subagent-concurrency=")) flags.subagentConcurrency = arg.slice(23);
6560
- else if (arg === "--dry-run") flags.dryRun = true;
6649
+ else if (arg === "--playwright-runtimes") {
6650
+ const [value, nextIndex] = readValue(i);
6651
+ flags.playwrightRuntimes = (value ?? "").split(",").filter(Boolean);
6652
+ i = nextIndex;
6653
+ } else if (arg.startsWith("--playwright-runtimes=")) {
6654
+ flags.playwrightRuntimes = arg.slice("--playwright-runtimes=".length).split(",").filter(Boolean);
6655
+ } else if (arg === "--dry-run") flags.dryRun = true;
6561
6656
  else if (arg === "--yes" || arg === "-y") flags.yes = true;
6562
6657
  else if (arg === "--help" || arg === "-h") flags.help = true;
6563
6658
  else if (arg === "--version" || arg === "-v") flags.version = true;
@@ -6635,8 +6730,25 @@ Corrige o borra ${preferenceFile}, o vuelve a ejecutar con --mode human|programm
6635
6730
  subagentConcurrency: concurrency
6636
6731
  };
6637
6732
  }
6638
- async function resolvePlaywrightToolConsent(command, flags) {
6733
+ async function resolvePlaywrightToolConsent(command, flags, runtimes) {
6639
6734
  const interactive = Boolean(process.stdout.isTTY);
6735
+ const supported = runtimes.filter((runtime) => runtime !== "pi" || PI_RUNTIME_CANDIDATE.contract.capabilities.includes("playwright-handoff-v1"));
6736
+ if (flags.playwrightRuntimes !== void 0) {
6737
+ const requested = flags.playwrightRuntimes;
6738
+ let error;
6739
+ if (requested.length === 0) error = "--playwright-runtimes requiere al menos un runtime.";
6740
+ for (const runtime of requested) {
6741
+ if (!["opencode", "claude-code", "codex", "pi"].includes(runtime)) error = `Runtime Playwright desconocido: ${runtime}.`;
6742
+ else if (!runtimes.includes(runtime)) error = `El runtime ${runtime} no est\xE1 en --agents/destinos de esta instalaci\xF3n.`;
6743
+ else if (!supported.includes(runtime)) error = "Pi requiere un candidato con playwright-handoff-v1 para activar Playwright.";
6744
+ if (error) break;
6745
+ }
6746
+ if (error) {
6747
+ console.error(error);
6748
+ process.exitCode = 1;
6749
+ return null;
6750
+ }
6751
+ }
6640
6752
  let confirmed = false;
6641
6753
  if (command === "install" && interactive && !flags.yes && !flags.dryRun && flags.targetDir === void 0) {
6642
6754
  const answer = await p6.confirm({
@@ -6646,13 +6758,33 @@ async function resolvePlaywrightToolConsent(command, flags) {
6646
6758
  if (p6.isCancel(answer)) return null;
6647
6759
  confirmed = answer === true;
6648
6760
  }
6761
+ let runtimeSelection;
6762
+ const approved = interactive && !flags.yes ? confirmed : flags.yes && flags.playwright;
6763
+ if (command === "install" && approved && supported.length > 0) {
6764
+ let selected = flags.playwrightRuntimes ?? supported;
6765
+ if (interactive && !flags.yes && !flags.dryRun && flags.targetDir === void 0 && flags.playwrightRuntimes === void 0) {
6766
+ const answer = await p6.multiselect({
6767
+ message: "\xBFEn qu\xE9 runtimes activar la gu\xEDa de Playwright? (instalaci\xF3n global compartida)",
6768
+ required: false,
6769
+ options: supported.map((runtime) => ({ value: runtime, label: runtime === "pi" ? "Pi" : ADAPTERS[runtime]?.name ?? runtime })),
6770
+ initialValues: supported.filter((runtime) => loadPlaywrightCliPreference(void 0, runtime) === true)
6771
+ });
6772
+ if (p6.isCancel(answer)) return null;
6773
+ selected = answer;
6774
+ }
6775
+ runtimeSelection = Object.fromEntries(supported.map((runtime) => [runtime, selected.includes(runtime)]));
6776
+ }
6777
+ if (command === "install" && approved && runtimes.includes("pi") && !supported.includes("pi")) {
6778
+ p6.log.info("La activaci\xF3n de Playwright en Pi requiere la pr\xF3xima adopci\xF3n del paquete Pi; el binario global s\xED puede instalarse.");
6779
+ }
6649
6780
  return {
6650
6781
  command,
6651
6782
  interactive,
6652
6783
  yes: flags.yes,
6653
6784
  targetDir: flags.targetDir !== void 0,
6654
6785
  explicitToolSelection: flags.playwright,
6655
- confirmed
6786
+ confirmed,
6787
+ ...runtimeSelection === void 0 ? {} : { runtimeSelection }
6656
6788
  };
6657
6789
  }
6658
6790
  async function resolveDevtoolsMcpSelection(command, flags, runtimes) {
@@ -6827,6 +6959,7 @@ Opciones:
6827
6959
  --no-devtools (install/sync) desactiva Chrome DevTools MCP (incompatible con --devtools)
6828
6960
  --remove-engram (uninstall) desregistra Engram de los runtimes;
6829
6961
  memorias y binario quedan intactos igualmente
6962
+ --playwright-runtimes <csv> Activa su gu\xEDa s\xF3lo en estos runtimes de --agents (con --playwright)
6830
6963
  --remove-playwright (uninstall) retira solo el paquete global de Playwright;
6831
6964
  nunca perfiles, cach\xE9 ni navegadores
6832
6965
  --receipt <path> (quality) escribe el receipt en ese path de forma at\xF3mica
@@ -6861,6 +6994,11 @@ Flags disponibles: jorgex-stack --help`
6861
6994
  process.exitCode = 1;
6862
6995
  return;
6863
6996
  }
6997
+ if (flags.playwrightRuntimes !== void 0 && (command !== "install" || !flags.playwright)) {
6998
+ console.error("--playwright-runtimes requiere install --playwright.");
6999
+ process.exitCode = 1;
7000
+ return;
7001
+ }
6864
7002
  if (command !== "quality" && flags.targetDir !== void 0 && flags.agents.length !== 1) {
6865
7003
  console.error("--target-dir requiere exactamente un runtime en --agents.");
6866
7004
  process.exitCode = 1;
@@ -6927,8 +7065,11 @@ Flags disponibles: jorgex-stack --help`
6927
7065
  exitCode = process.exitCode === 1 ? 1 : 0;
6928
7066
  return;
6929
7067
  }
6930
- const playwrightToolConsent = await resolvePlaywrightToolConsent(command, flags);
6931
- if (playwrightToolConsent === null) return;
7068
+ const playwrightToolConsent = await resolvePlaywrightToolConsent(command, flags, runtimes);
7069
+ if (playwrightToolConsent === null) {
7070
+ exitCode = process.exitCode === 1 ? 1 : 0;
7071
+ return;
7072
+ }
6932
7073
  if (!await ensureOpenCodeModelsForInstall(command, flags, fileRuntimes)) {
6933
7074
  exitCode = 1;
6934
7075
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jorgex-stack",
3
- "version": "1.9.31",
3
+ "version": "1.9.32",
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",