jorgex-stack 1.9.29 → 1.9.30

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 -1
  2. package/dist/cli.js +168 -42
  3. package/package.json +1 -1
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. Chrome DevTools MCP and Context7 remain 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`. Both receipts are scope-bound and fail closed for manual, duplicate, divergent, partial, corrupt, copied-to-another-scope, or unknown-history state.
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.
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
 
@@ -159,6 +159,8 @@ pnpm dlx jorgex-stack install --yes --playwright
159
159
  # Enable Chrome DevTools MCP explicitly per runtime.
160
160
  pnpm dlx jorgex-stack install --devtools
161
161
  pnpm dlx jorgex-stack install --no-devtools
162
+ pnpm dlx jorgex-stack install --agents pi --devtools
163
+ pnpm dlx jorgex-stack sync --agents pi --no-devtools
162
164
  ```
163
165
 
164
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.
package/dist/cli.js CHANGED
@@ -2341,7 +2341,7 @@ function parseDevtoolsMcpState(raw) {
2341
2341
  if (!isRecord2(value) || value.version !== DEVTOOLS_MCP_PREFERENCE_VERSION || !isRecord2(value.enabled) || !isRecord2(value.owned)) return null;
2342
2342
  const enabled = {};
2343
2343
  for (const [runtime, selected] of Object.entries(value.enabled)) {
2344
- if (!isRuntimeId(runtime) || typeof selected !== "boolean") return null;
2344
+ if (!isRuntimeId(runtime) && runtime !== "pi" || typeof selected !== "boolean") return null;
2345
2345
  enabled[runtime] = selected;
2346
2346
  }
2347
2347
  const owned = {};
@@ -2844,6 +2844,7 @@ import * as p2 from "@clack/prompts";
2844
2844
  // src/lib/pi-projection-lifecycle.ts
2845
2845
  import fs16 from "fs";
2846
2846
  import path23 from "path";
2847
+ import { createHash } from "crypto";
2847
2848
 
2848
2849
  // src/adapters/pi.ts
2849
2850
  import path21 from "path";
@@ -3307,14 +3308,33 @@ function projectionPlan(input, scope) {
3307
3308
  engramBin: input.engramBin,
3308
3309
  models: DEFAULT_MODEL_MAP.codex,
3309
3310
  warnings: [],
3310
- playwrightCliEnabled: input.playwrightCliEnabled
3311
+ playwrightCliEnabled: input.playwrightCliEnabled,
3312
+ enabledMcpServers: input.devtoolsMcpEnabled ? /* @__PURE__ */ new Set([DEVTOOLS_MCP_SERVER]) : void 0
3311
3313
  };
3312
3314
  const adapter = projectedPiAdapter(scope);
3313
- return [
3315
+ const actions = [
3314
3316
  ...planSystemPrompt(adapter, ctx),
3315
3317
  ...planSkills(adapter, ctx),
3316
3318
  ...planCommands(adapter, ctx)
3317
3319
  ];
3320
+ if (input.devtoolsMcpEnabled && input.pnpmBin) {
3321
+ const server = loadCanonicalMcp(input.stackDir).servers[DEVTOOLS_MCP_SERVER];
3322
+ if (server?.transport !== "stdio" || !Array.isArray(server.args)) throw new Error("Falta la configuraci\xF3n can\xF3nica de DevTools.");
3323
+ actions.push({ kind: "write", target: devtoolsPath(scope), content: `${JSON.stringify({
3324
+ schemaVersion: 1,
3325
+ enabled: true,
3326
+ command: input.pnpmBin,
3327
+ args: server.args
3328
+ }, null, 2)}
3329
+ ` });
3330
+ }
3331
+ return actions;
3332
+ }
3333
+ function devtoolsPath(scope) {
3334
+ return path23.resolve(scope.codingAgentDir, "jorgex-pi", "devtools.v1.json");
3335
+ }
3336
+ function contentHash(content) {
3337
+ return createHash("sha256").update(content).digest("hex");
3318
3338
  }
3319
3339
  function canonicalActionContent(action) {
3320
3340
  return action.kind === "write" ? action.content : fs16.readFileSync(action.source, "utf8");
@@ -3322,8 +3342,15 @@ function canonicalActionContent(action) {
3322
3342
  function hasActionDrift(action, deps) {
3323
3343
  return deps.readText(action.target) !== canonicalActionContent(action);
3324
3344
  }
3325
- function applyActions(actions, deps) {
3345
+ function applyActions(actions, deps, handoff) {
3326
3346
  for (const action of actions) {
3347
+ if (handoff && path23.resolve(action.target) === handoff.file) {
3348
+ try {
3349
+ if (deps.readText(handoff.file) !== handoff.previous) return devtoolsConflict(handoff.file);
3350
+ } catch {
3351
+ return devtoolsConflict(handoff.file);
3352
+ }
3353
+ }
3327
3354
  if (action.kind === "write") deps.writeText(action.target, action.content);
3328
3355
  else deps.copyFile(action.source, action.target);
3329
3356
  }
@@ -3340,6 +3367,7 @@ function receiptFor(plan, scope) {
3340
3367
  const owned = [...new Set(
3341
3368
  plan.map((action) => path23.resolve(action.target)).filter((target) => target !== systemPrompt)
3342
3369
  )];
3370
+ const handoff = plan.find((action) => path23.resolve(action.target) === devtoolsPath(scope));
3343
3371
  return {
3344
3372
  schemaVersion: 1,
3345
3373
  scope: {
@@ -3348,7 +3376,8 @@ function receiptFor(plan, scope) {
3348
3376
  codingAgentDir: scope.codingAgentDir,
3349
3377
  receiptFile: scope.receiptFile
3350
3378
  },
3351
- owned
3379
+ owned,
3380
+ ...handoff ? { devtools: { sha256: contentHash(canonicalActionContent(handoff)) } } : {}
3352
3381
  };
3353
3382
  }
3354
3383
  function receiptContent(receipt) {
@@ -3367,16 +3396,25 @@ function parseReceipt2(raw, expected) {
3367
3396
  const version = Reflect.get(receipt, "schemaVersion");
3368
3397
  const receivedScope = Reflect.get(receipt, "scope");
3369
3398
  const owned = Reflect.get(receipt, "owned");
3370
- if (version !== expected.schemaVersion || !hasExactKeys(receipt, ["schemaVersion", "scope", "owned"]) || receivedScope === null || typeof receivedScope !== "object" || Array.isArray(receivedScope) || !hasExactKeys(receivedScope, ["kind", "home", "codingAgentDir", "receiptFile"])) {
3371
- return null;
3372
- }
3373
- if (Reflect.get(receivedScope, "kind") !== expected.scope.kind || Reflect.get(receivedScope, "home") !== expected.scope.home || Reflect.get(receivedScope, "codingAgentDir") !== expected.scope.codingAgentDir || Reflect.get(receivedScope, "receiptFile") !== expected.scope.receiptFile || !Array.isArray(owned) || owned.length !== expected.owned.length) {
3399
+ const devtools = Reflect.get(receipt, "devtools");
3400
+ const hasDevtools = Object.hasOwn(receipt, "devtools");
3401
+ if (version !== expected.schemaVersion || !hasExactKeys(receipt, ["schemaVersion", "scope", "owned", ...hasDevtools ? ["devtools"] : []]) || receivedScope === null || typeof receivedScope !== "object" || Array.isArray(receivedScope) || !hasExactKeys(receivedScope, ["kind", "home", "codingAgentDir", "receiptFile"])) {
3374
3402
  return null;
3375
3403
  }
3376
- if (!owned.every((file, index) => typeof file === "string" && file === expected.owned[index])) {
3404
+ if (Reflect.get(receivedScope, "kind") !== expected.scope.kind || Reflect.get(receivedScope, "home") !== expected.scope.home || Reflect.get(receivedScope, "codingAgentDir") !== expected.scope.codingAgentDir || Reflect.get(receivedScope, "receiptFile") !== expected.scope.receiptFile || !Array.isArray(owned)) {
3377
3405
  return null;
3378
3406
  }
3379
- return expected;
3407
+ if (hasDevtools && (devtools === null || typeof devtools !== "object" || Array.isArray(devtools) || !hasExactKeys(devtools, ["sha256"]) || typeof Reflect.get(devtools, "sha256") !== "string" || !/^[a-f0-9]{64}$/.test(Reflect.get(devtools, "sha256")))) return null;
3408
+ const handoff = devtoolsPath(expected.scope);
3409
+ const expectedOwned = expected.owned.filter((file) => file !== handoff);
3410
+ if (hasDevtools) expectedOwned.push(handoff);
3411
+ if (owned.length !== expectedOwned.length || !owned.every((file, index) => typeof file === "string" && file === expectedOwned[index])) return null;
3412
+ return {
3413
+ schemaVersion: 1,
3414
+ scope: expected.scope,
3415
+ owned: expectedOwned,
3416
+ ...hasDevtools ? { devtools: { sha256: Reflect.get(devtools, "sha256") } } : {}
3417
+ };
3380
3418
  } catch {
3381
3419
  return null;
3382
3420
  }
@@ -3502,6 +3540,35 @@ function readProjectionReceipt(receiptFile, deps) {
3502
3540
  return { kind: "unreadable" };
3503
3541
  }
3504
3542
  }
3543
+ function devtoolsConflict(file) {
3544
+ return blocked2(
3545
+ "projection-devtools-conflict",
3546
+ [file],
3547
+ "El handoff de DevTools es ajeno o fue modificado. Cons\xE9rvalo y revisa su propiedad antes de reintentar; Stack no lo sobrescribe ni elimina."
3548
+ );
3549
+ }
3550
+ function inspectDevtools(input, scope, expected, deps) {
3551
+ const file = devtoolsPath(scope);
3552
+ const read = readProjectionReceipt(scope.receiptFile, deps);
3553
+ if (read.kind === "unreadable") return receiptUnreadable(scope.receiptFile);
3554
+ const previous = read.kind === "present" ? parseReceipt2(read.content, expected) : null;
3555
+ let content;
3556
+ try {
3557
+ content = deps.readText(file);
3558
+ } catch {
3559
+ return devtoolsConflict(file);
3560
+ }
3561
+ let recordedDevtools = false;
3562
+ if (read.kind === "present") {
3563
+ try {
3564
+ recordedDevtools = Object.hasOwn(JSON.parse(read.content), "devtools");
3565
+ } catch {
3566
+ }
3567
+ if (!previous && (input.devtoolsMcpEnabled || content !== null || recordedDevtools)) return receiptInvalid(scope.receiptFile);
3568
+ }
3569
+ if (content !== null && (!previous?.devtools || contentHash(content) !== previous.devtools.sha256)) return devtoolsConflict(file);
3570
+ return { kind: "checked", previous, content };
3571
+ }
3505
3572
  function preparePiProjectionUninstall(input, deps) {
3506
3573
  if (input.operation !== "uninstall") {
3507
3574
  return cleanupFailure([]);
@@ -3520,11 +3587,13 @@ function preparePiProjectionUninstall(input, deps) {
3520
3587
  const receiptRead = readProjectionReceipt(scope.receiptFile, deps);
3521
3588
  if (receiptRead.kind === "unreadable") return receiptUnreadable(scope.receiptFile);
3522
3589
  let ownedToRemove = [];
3590
+ let ownedReceipt = null;
3523
3591
  let receiptFile = null;
3524
3592
  let backupTargets = promptUpdate === null ? [] : [promptUpdate.file];
3525
3593
  if (receiptRead.kind === "present") {
3526
3594
  const receipt = parseReceipt2(receiptRead.content, expectedReceipt2);
3527
3595
  if (receipt === null) return receiptInvalid(scope.receiptFile);
3596
+ ownedReceipt = receipt;
3528
3597
  let retained;
3529
3598
  try {
3530
3599
  retained = manifestOwned(deps.readManifest());
@@ -3535,13 +3604,22 @@ function preparePiProjectionUninstall(input, deps) {
3535
3604
  receiptFile = path23.resolve(scope.receiptFile);
3536
3605
  backupTargets = [...backupTargets, ...receipt.owned, receiptFile];
3537
3606
  }
3607
+ const handoff = devtoolsPath(scope);
3608
+ let handoffContent;
3609
+ try {
3610
+ handoffContent = deps.readText(handoff);
3611
+ } catch {
3612
+ return devtoolsConflict(handoff);
3613
+ }
3614
+ if (handoffContent !== null && (!ownedReceipt?.devtools || contentHash(handoffContent) !== ownedReceipt.devtools.sha256)) return devtoolsConflict(handoff);
3538
3615
  const backup = backupExisting(backupTargets, deps);
3539
3616
  if (backup.kind === "backup-failed") return backupFailure(backup.paths);
3540
3617
  const token = {};
3541
3618
  preparedPiProjectionUninstalls.set(token, {
3542
3619
  prompt: promptUpdate,
3543
3620
  ownedToRemove,
3544
- receiptFile
3621
+ receiptFile,
3622
+ ...ownedReceipt?.devtools ? { devtools: { file: handoff, sha256: ownedReceipt.devtools.sha256 } } : {}
3545
3623
  });
3546
3624
  return { kind: "prepared", plan: token };
3547
3625
  }
@@ -3553,6 +3631,15 @@ function completePiProjectionUninstall(plan, deps) {
3553
3631
  if (prepared === void 0) {
3554
3632
  return cleanupFailure([]);
3555
3633
  }
3634
+ if (prepared.devtools) {
3635
+ let current;
3636
+ try {
3637
+ current = deps.readText(prepared.devtools.file);
3638
+ } catch {
3639
+ return devtoolsConflict(prepared.devtools.file);
3640
+ }
3641
+ if (current !== null && contentHash(current) !== prepared.devtools.sha256) return devtoolsConflict(prepared.devtools.file);
3642
+ }
3556
3643
  if (prepared.prompt !== null) {
3557
3644
  try {
3558
3645
  deps.writeText(prepared.prompt.file, prepared.prompt.content);
@@ -3562,6 +3649,10 @@ function completePiProjectionUninstall(plan, deps) {
3562
3649
  }
3563
3650
  for (const owned of prepared.ownedToRemove) {
3564
3651
  try {
3652
+ if (prepared.devtools?.file === owned) {
3653
+ const current = deps.readText(owned);
3654
+ if (current !== null && contentHash(current) !== prepared.devtools.sha256) return devtoolsConflict(owned);
3655
+ }
3565
3656
  deps.removeFile(owned);
3566
3657
  } catch {
3567
3658
  return cleanupFailure([owned]);
@@ -3582,13 +3673,23 @@ function runPiProjectionLifecycle(input, deps) {
3582
3673
  const prepared = preparePiProjectionUninstall(input, deps);
3583
3674
  return prepared.kind === "blocked" ? prepared : completePiProjectionUninstall(prepared.plan, deps);
3584
3675
  }
3676
+ if (input.devtoolsMcpEnabled && (!input.pnpmBin || !path23.isAbsolute(input.pnpmBin))) {
3677
+ return blocked2("projection-devtools-command", [devtoolsPath(scope)], "DevTools requiere un ejecutable pnpm absoluto disponible en PATH.");
3678
+ }
3585
3679
  const plan = projectionPlan(input, scope);
3586
3680
  assertPlanContained(plan, scope);
3587
3681
  const receipt = receiptFor(plan, scope);
3682
+ const devtools = inspectDevtools(input, scope, receipt, deps);
3683
+ if (devtools.kind === "blocked") return input.operation === "doctor" ? { kind: "drift", paths: devtools.paths } : devtools;
3684
+ const removeHandoff = !input.devtoolsMcpEnabled && devtools.previous?.devtools !== void 0 && devtools.content !== null;
3588
3685
  const expectedReceipt2 = receiptContent(receipt);
3589
3686
  const drifted = plan.filter((action) => hasActionDrift(action, deps));
3687
+ if (input.devtoolsMcpEnabled && devtools.previous?.devtools?.sha256 !== receipt.devtools?.sha256 && !drifted.some((action) => path23.resolve(action.target) === devtoolsPath(scope))) {
3688
+ return input.operation === "doctor" ? { kind: "drift", paths: [devtoolsPath(scope)] } : devtoolsConflict(devtoolsPath(scope));
3689
+ }
3590
3690
  if (input.operation === "doctor") {
3591
3691
  const paths = drifted.map((action) => path23.resolve(action.target));
3692
+ if (removeHandoff) paths.push(devtoolsPath(scope));
3592
3693
  const currentSettings2 = deps.readText(scope.settingsFile);
3593
3694
  if (currentSettings2 === null || filterProjectedPiPackage(currentSettings2, input.packageSource) !== currentSettings2) {
3594
3695
  paths.push(scope.settingsFile);
@@ -3604,19 +3705,30 @@ function runPiProjectionLifecycle(input, deps) {
3604
3705
  }
3605
3706
  const packageWillChange = filteredSettings !== null && filteredSettings !== currentSettings;
3606
3707
  const receiptChanged = deps.readText(scope.receiptFile) !== expectedReceipt2;
3607
- if (drifted.length > 0 || packageWillChange || receiptChanged) {
3708
+ if (drifted.length > 0 || packageWillChange || receiptChanged || removeHandoff) {
3608
3709
  const backup = backupExisting([
3609
3710
  ...drifted.map((action) => action.target),
3711
+ ...removeHandoff ? [devtoolsPath(scope)] : [],
3610
3712
  ...packageWillChange ? [scope.settingsFile] : [],
3611
3713
  ...receiptChanged ? [scope.receiptFile] : []
3612
3714
  ], deps);
3613
3715
  if (backup.kind === "backup-failed") return backupFailure(backup.paths);
3614
3716
  }
3615
- applyActions(drifted, deps);
3717
+ if (removeHandoff) {
3718
+ try {
3719
+ const current = deps.readText(devtoolsPath(scope));
3720
+ if (current !== null && contentHash(current) !== devtools.previous?.devtools?.sha256) return devtoolsConflict(devtoolsPath(scope));
3721
+ deps.removeFile(devtoolsPath(scope));
3722
+ } catch {
3723
+ return cleanupFailure([devtoolsPath(scope)]);
3724
+ }
3725
+ }
3726
+ const writeResult = applyActions(drifted, deps, input.devtoolsMcpEnabled ? { file: devtoolsPath(scope), previous: devtools.content } : void 0);
3727
+ if (writeResult) return writeResult;
3616
3728
  if (packageWillChange && filteredSettings !== null) deps.writeText(scope.settingsFile, filteredSettings);
3617
3729
  if (receiptChanged) deps.writeText(scope.receiptFile, expectedReceipt2);
3618
3730
  if (input.operation === "install") return { kind: "installed", receipt };
3619
- return { kind: "synced", changed: drifted.length > 0 || packageWillChange || receiptChanged };
3731
+ return { kind: "synced", changed: drifted.length > 0 || packageWillChange || receiptChanged || removeHandoff };
3620
3732
  }
3621
3733
  function systemProjectionLifecycle(input) {
3622
3734
  const targetRoot = input.targetDir === void 0 ? null : path23.resolve(input.targetDir);
@@ -3633,7 +3745,9 @@ function systemProjectionLifecycle(input) {
3633
3745
  packageSource: input.packageSource,
3634
3746
  stackDir: stackRoot(),
3635
3747
  engramBin: input.engramBin ?? "",
3636
- playwrightCliEnabled: input.playwrightCliEnabled
3748
+ playwrightCliEnabled: input.playwrightCliEnabled,
3749
+ devtoolsMcpEnabled: input.devtoolsMcpEnabled,
3750
+ pnpmBin: input.pnpmBin
3637
3751
  },
3638
3752
  deps: {
3639
3753
  readText: readTextOnlyIfMissing,
@@ -5218,22 +5332,22 @@ import os4 from "os";
5218
5332
  import path31 from "path";
5219
5333
  import fs23 from "fs";
5220
5334
  import { spawnSync } from "child_process";
5221
- import { createHash } from "crypto";
5335
+ import { createHash as createHash2 } from "crypto";
5222
5336
 
5223
5337
  // src/lib/pi-runtime-pin.json
5224
5338
  var pi_runtime_pin_default = {
5225
5339
  package: {
5226
5340
  name: "jorgex-pi",
5227
- version: "0.8.12",
5228
- source: "npm:jorgex-pi@0.8.12"
5341
+ version: "0.8.13",
5342
+ source: "npm:jorgex-pi@0.8.13"
5229
5343
  },
5230
5344
  provenance: {
5231
- commit: "a041e8f290021b5a6bb161db77e03663f37c82cb"
5345
+ commit: "e28a677da0a112925251cf11269b70cb5c8aa15e"
5232
5346
  },
5233
5347
  tarball: {
5234
- bytes: 89141968,
5235
- sha256: "eb7739852504bf4cd8cdd9f260c67da979728378a756db7f1ff6b0c1d60368c6",
5236
- sha512: "a37b36e5c846e1694db3c7bd7e9acf23f39b8ba45d348bf039fd218f90f041a6d95268af2e3b6248fc4e759dfd6441df1c44490411c1e13c2b0cedbc0ef526b2"
5348
+ bytes: 89143404,
5349
+ sha256: "6bbe877249b900af582c8881e0dd7993951ea1cef6e7252522c626c078de7478",
5350
+ sha512: "bb527d1f3812406f2184f778b22cca00ead33692fecac0012cf8cdcdc469b7cccf3c789c35b4327dd599d06542162bd07eefeeaaa40de19662ec5fc422b14af8"
5237
5351
  }
5238
5352
  };
5239
5353
 
@@ -5255,6 +5369,7 @@ var PI_RUNTIME_CANDIDATE = {
5255
5369
  "goal-continuation-v1",
5256
5370
  "mcp-adapter-v1",
5257
5371
  "engram-runtime-tools-v1",
5372
+ "chrome-devtools-handoff-v1",
5258
5373
  "runner-json-v1",
5259
5374
  "tui-branding-v1",
5260
5375
  "managed-primary-model-v1",
@@ -5607,8 +5722,8 @@ function readOptional(file, fallback) {
5607
5722
  }
5608
5723
  function hashPiTarball(file) {
5609
5724
  const descriptor = fs23.openSync(file, "r");
5610
- const sha2562 = createHash("sha256");
5611
- const sha512 = createHash("sha512");
5725
+ const sha2562 = createHash2("sha256");
5726
+ const sha512 = createHash2("sha512");
5612
5727
  const buffer = Buffer.allocUnsafe(1024 * 1024);
5613
5728
  let bytes = 0;
5614
5729
  try {
@@ -5826,36 +5941,44 @@ function managedPackageResult(result) {
5826
5941
  return result;
5827
5942
  }
5828
5943
  async function runManagedPiSystem(input) {
5944
+ const { devtoolsMcpEnabled: explicitDevtools, ...runtimeInput } = input;
5945
+ const devtoolsMcpEnabled = explicitDevtools ?? (input.targetDir === void 0 && loadDevtoolsMcpPreference(devtoolsMcpPreferenceFile(), "pi"));
5829
5946
  const playwrightCliEnabled = input.targetDir === void 0 && loadPlaywrightCliPreference() === true;
5830
5947
  const projectionInput = {
5831
5948
  targetDir: input.targetDir,
5832
5949
  packageSource: PI_RUNTIME_CANDIDATE.package.source,
5833
5950
  engramBin: input.engramBin,
5834
- playwrightCliEnabled
5951
+ playwrightCliEnabled,
5952
+ devtoolsMcpEnabled,
5953
+ pnpmBin: devtoolsMcpEnabled && input.operation !== "uninstall" ? resolvePnpmBin() : null
5835
5954
  };
5836
- return runManagedPiOperation(input.operation, {
5955
+ const result = await runManagedPiOperation(input.operation, {
5837
5956
  async runPackage(operation) {
5838
- return managedPackageResult(await runPiRuntimeSystem({ ...input, operation }));
5957
+ return managedPackageResult(await runPiRuntimeSystem({ ...runtimeInput, operation }));
5839
5958
  },
5840
5959
  runProjection(operation) {
5841
- const result = runPiProjectionLifecycleSystem({
5960
+ const result2 = runPiProjectionLifecycleSystem({
5842
5961
  operation,
5843
5962
  ...projectionInput
5844
5963
  });
5845
- return Promise.resolve(result.kind === "drift" ? {
5964
+ return Promise.resolve(result2.kind === "drift" ? {
5846
5965
  kind: "drift",
5847
- paths: result.paths,
5966
+ paths: result2.paths,
5848
5967
  remedy: "Ejecuta sync --agents pi para reparar la proyecci\xF3n de Pi."
5849
- } : result);
5968
+ } : result2);
5850
5969
  },
5851
5970
  prepareProjectionUninstall() {
5852
- const result = preparePiProjectionUninstallSystem({ operation: "uninstall", ...projectionInput });
5853
- return Promise.resolve(result.kind === "prepared" ? { kind: "prepared", token: result.plan } : result);
5971
+ const result2 = preparePiProjectionUninstallSystem({ operation: "uninstall", ...projectionInput });
5972
+ return Promise.resolve(result2.kind === "prepared" ? { kind: "prepared", token: result2.plan } : result2);
5854
5973
  },
5855
5974
  completeProjectionUninstall(token) {
5856
5975
  return Promise.resolve(completePiProjectionUninstallSystem(token, { operation: "uninstall", ...projectionInput }));
5857
5976
  }
5858
5977
  });
5978
+ if (input.targetDir === void 0 && explicitDevtools !== void 0 && (input.operation === "install" || input.operation === "sync") && result.kind !== "blocked") {
5979
+ saveDevtoolsMcpPreference(devtoolsMcpPreferenceFile(), "pi", explicitDevtools);
5980
+ }
5981
+ return result;
5859
5982
  }
5860
5983
 
5861
5984
  // src/lib/quality-runner.ts
@@ -6241,7 +6364,7 @@ import fs24 from "fs";
6241
6364
  import os5 from "os";
6242
6365
  import path33 from "path";
6243
6366
  import { execFileSync as execFileSync7 } from "child_process";
6244
- import { createHash as createHash2 } from "crypto";
6367
+ import { createHash as createHash3 } from "crypto";
6245
6368
 
6246
6369
  // src/lib/engram-release.json
6247
6370
  var engram_release_default = {
@@ -6318,7 +6441,7 @@ async function installMissingEngram(options = {}) {
6318
6441
  await reader.cancel();
6319
6442
  }
6320
6443
  const archive = Buffer.concat(chunks);
6321
- if (size !== asset.size || createHash2("sha256").update(archive).digest("hex") !== asset.sha256) {
6444
+ if (size !== asset.size || createHash3("sha256").update(archive).digest("hex") !== asset.sha256) {
6322
6445
  throw new Error("Engram: tama\xF1o o hash SHA-256 no coincide con el artefacto aprobado.");
6323
6446
  }
6324
6447
  fs24.mkdirSync(path33.dirname(bin), { recursive: true });
@@ -6544,8 +6667,9 @@ async function resolveDevtoolsMcpSelection(command, flags, runtimes) {
6544
6667
  }
6545
6668
  const file = devtoolsMcpPreferenceFile();
6546
6669
  const selected = await p6.multiselect({
6547
- message: "Chrome DevTools MCP avanzado (full: ~29 tools y ~5.8\u20137.7k tokens de schemas). \xBFEn qu\xE9 runtimes activarlo?",
6548
- options: runtimes.map((runtime) => ({ value: runtime, label: ADAPTERS[runtime]?.name ?? runtime })),
6670
+ message: "Chrome DevTools MCP avanzado (opcional). \xBFEn qu\xE9 runtimes activarlo?",
6671
+ required: false,
6672
+ options: runtimes.map((runtime) => ({ value: runtime, label: runtime === "pi" ? "Pi" : ADAPTERS[runtime]?.name ?? runtime })),
6549
6673
  initialValues: runtimes.filter((runtime) => loadDevtoolsMcpPreference(file, runtime))
6550
6674
  });
6551
6675
  if (p6.isCancel(selected)) return null;
@@ -6617,7 +6741,7 @@ async function resolveHostEngramForInstall(flags) {
6617
6741
  return { ok: false, message: `Engram: ${error instanceof Error ? error.message : String(error)}` };
6618
6742
  }
6619
6743
  }
6620
- async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBin) {
6744
+ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBin, devtoolsMcpEnabled) {
6621
6745
  if (targetDir === void 0 && operation !== "models") {
6622
6746
  const preferenceErrors = browserPreferenceErrors();
6623
6747
  if (preferenceErrors.length > 0) {
@@ -6659,7 +6783,8 @@ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBi
6659
6783
  operation,
6660
6784
  targetDir,
6661
6785
  detected: { executable: detected.executable, version: detected.version },
6662
- engramBin
6786
+ engramBin,
6787
+ ...devtoolsMcpEnabled === void 0 ? {} : { devtoolsMcpEnabled }
6663
6788
  });
6664
6789
  if (result.kind === "blocked") {
6665
6790
  const paths = "paths" in result ? `: ${result.paths.join(", ")}` : "";
@@ -6795,7 +6920,7 @@ Flags disponibles: jorgex-stack --help`
6795
6920
  }
6796
6921
  const mode = fileRuntimes.length > 0 ? await resolveInstallMode(flags) : void 0;
6797
6922
  if (mode === null) return;
6798
- const devtoolsMcpSelection = await resolveDevtoolsMcpSelection(command, flags, fileRuntimes);
6923
+ const devtoolsMcpSelection = await resolveDevtoolsMcpSelection(command, flags, runtimes);
6799
6924
  if (devtoolsMcpSelection === null) {
6800
6925
  exitCode = process.exitCode === 1 ? 1 : 0;
6801
6926
  return;
@@ -6852,7 +6977,8 @@ Flags disponibles: jorgex-stack --help`
6852
6977
  command,
6853
6978
  flags.targetDir,
6854
6979
  flags.yes,
6855
- flags.targetDir === void 0 ? engramBin : void 0
6980
+ flags.targetDir === void 0 ? engramBin : void 0,
6981
+ devtoolsMcpSelection.pi
6856
6982
  ));
6857
6983
  }
6858
6984
  completed = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jorgex-stack",
3
- "version": "1.9.29",
3
+ "version": "1.9.30",
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",