jorgex-stack 1.9.58 → 1.9.59

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 +2 -2
  2. package/dist/cli.js +181 -97
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -146,9 +146,9 @@ Install, sync and uninstall back up every managed file before changing it and ar
146
146
 
147
147
  The package owns Pi's native primary-model projection: `openai-codex/gpt-5.6-sol`, with a local `contextWindow` request of 872K. It merges only missing compatible fields, records field ownership in `PI_CODING_AGENT_DIR/jorgex-pi/sol-lifecycle.v1.json`, and cleanup removes only still-owned canonical values. Stack does not duplicate that package-owned settings/models logic. The 872K value is local OAuth metadata until a real long-context smoke test confirms backend acceptance; it is not the API context limit.
148
148
 
149
- Engram remains mandatory and user-owned. An existing binary is preserved. `install` resolves or installs the verified official v2.0.0 release before configuring any selected runtime; use `--engram` to authorize that download in non-interactive flows. The release installer writes only `~/.local/bin/engram` (or the platform equivalent), checks the pinned size and SHA-256, and does not use Brew or Go. `sync`, dry-run and `--target-dir` never download it. The database and memories are never updated or deleted, and uninstall preserves the official Engram artifacts and never removes the Engram binary. Under `--target-dir`, Stack accepts only `<target>/bin/engram`, isolates Pi/Home/XDG/AppData/temp/npm-cache paths inside the target, and never consults the host Engram or Pi configuration.
149
+ Engram remains mandatory and user-owned. An existing binary is always preserved. When it is missing and the user authorizes installation, Stack resolves GitHub's current official latest stable release at runtime; GitHub's `releases/latest` endpoint excludes prereleases and drafts, and the installer never uses a branch. It matches the exact platform/architecture asset and validates its live metadata —published state, expected name, positive size and SHA-256— before publishing; missing network or metadata fails closed, with no static or offline fallback. Use `--engram` to authorize that download in non-interactive flows. The release installer writes only `~/.local/bin/engram` (or the platform equivalent) and does not use Brew or Go. `sync`, dry-run and `--target-dir` never download it. Update remains explicit and does not replace an existing binary implicitly. The database and memories are never updated or deleted, and uninstall preserves the official Engram artifacts and never removes the Engram binary. Under `--target-dir`, Stack accepts only `<target>/bin/engram`, isolates Pi/Home/XDG/AppData/temp/npm-cache paths inside the target, and never consults the host Engram or Pi configuration. This rolling channel does not mean that every upstream release was pre-reviewed by Stack.
150
150
 
151
- An existing binary older than stable 2.0.0 would make Claude's setup write the obsolete `mcp/engram.json`, so the Claude preflight blocks it before setup and recommends updating to Engram 2.0.0+ and rerunning `install`. Stack never replaces an existing binary automatically. The official Claude plugin contributes hooks and its skill; `engram setup claude-code` registers a separate user-scoped MCP, not a bundled MCP. This integration does not claim an authenticated model-tool smoke.
151
+ Claude's official plugin still requires stable Engram 2.0.0 or newer: an existing binary below that minimum would make its setup write the obsolete `mcp/engram.json`, so the Claude preflight blocks it before setup. Stack never replaces an existing binary automatically; update it explicitly and rerun `install`. The official Claude plugin contributes hooks and its skill; `engram setup claude-code` registers a separate user-scoped MCP, not a bundled MCP. This integration does not claim an authenticated model-tool smoke.
152
152
 
153
153
  ### Integración oficial de Engram
154
154
 
package/dist/cli.js CHANGED
@@ -3180,16 +3180,28 @@ function githubHeaders() {
3180
3180
  ...authHeaders()
3181
3181
  };
3182
3182
  }
3183
+ var ENGRAM_REPO = "Gentleman-Programming/engram";
3184
+ var ENGRAM_RELEASES_LATEST_URL = `https://api.github.com/repos/${ENGRAM_REPO}/releases/latest`;
3185
+ var ENGRAM_DOWNLOAD_PREFIX = `https://github.com/${ENGRAM_REPO}/releases/download/`;
3186
+ function expectedEngramAssetName(version, platform, arch) {
3187
+ const osPart = platform === "win32" ? "windows" : platform === "linux" ? "linux" : platform === "darwin" ? "darwin" : null;
3188
+ const archPart = arch === "x64" ? "amd64" : arch === "arm64" ? "arm64" : null;
3189
+ if (osPart === null || archPart === null) return null;
3190
+ const ext = osPart === "windows" ? "zip" : "tar.gz";
3191
+ return `engram_${version}_${osPart}_${archPart}.${ext}`;
3192
+ }
3193
+ async function fetchLatestGithubRelease(repo, fetchFn = globalThis.fetch) {
3194
+ const res = await fetchFn(`https://api.github.com/repos/${repo}/releases/latest`, {
3195
+ headers: githubHeaders(),
3196
+ signal: AbortSignal.timeout(1e4)
3197
+ });
3198
+ if (!res.ok && (res.status === 403 || res.status === 429)) rateLimitHit = true;
3199
+ return res;
3200
+ }
3183
3201
  async function latestGithubRelease(repo) {
3184
3202
  try {
3185
- const res = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {
3186
- headers: githubHeaders(),
3187
- signal: AbortSignal.timeout(1e4)
3188
- });
3189
- if (!res.ok) {
3190
- if (res.status === 403 || res.status === 429) rateLimitHit = true;
3191
- return null;
3192
- }
3203
+ const res = await fetchLatestGithubRelease(repo);
3204
+ if (!res.ok) return null;
3193
3205
  const data = await res.json();
3194
3206
  return data.tag_name?.replace(/^v/, "") ?? null;
3195
3207
  } catch {
@@ -4603,16 +4615,16 @@ async function resolvePiEngramRequirement(input, deps) {
4603
4615
  };
4604
4616
  }
4605
4617
  const accepted = await deps.confirm({
4606
- message: "Engram es obligatorio para JorgeX Pi. \xBFInstalar ahora el binario mediante el canal nativo?",
4618
+ message: "Engram es obligatorio para JorgeX Pi. \xBFInstalar ahora el binario oficial verificado?",
4607
4619
  initialValue: false
4608
4620
  });
4609
4621
  if (!accepted) return { kind: "offer", accepted: false };
4610
- const installed = await deps.installNative({ version: "2.0.0", channels: ["brew", "go", "url"] });
4611
- if (!installed) {
4622
+ const installed = await deps.installShared();
4623
+ if (!installed.ok) {
4612
4624
  return {
4613
4625
  kind: "blocked",
4614
4626
  reason: "engram-install-failed",
4615
- remedy: "Instala Engram manualmente o configura ENGRAM_BIN antes de reintentar."
4627
+ remedy: `La instalaci\xF3n de Engram fall\xF3: ${installed.reason} Instala Engram manualmente o configura ENGRAM_BIN antes de reintentar.`
4616
4628
  };
4617
4629
  }
4618
4630
  const detected = deps.detectHost();
@@ -5657,45 +5669,9 @@ import os5 from "os";
5657
5669
  import path25 from "path";
5658
5670
  import { execFileSync as execFileSync6 } from "child_process";
5659
5671
  import { createHash as createHash3 } from "crypto";
5660
-
5661
- // src/lib/engram-release.json
5662
- var engram_release_default = {
5663
- assets: {
5664
- darwin_arm64: {
5665
- name: "engram_2.0.0_darwin_arm64.tar.gz",
5666
- sha256: "793c695153e0e76144dc53b006a7039771bd24115385856578bc4d6506d46d7f",
5667
- size: 7210321
5668
- },
5669
- darwin_x64: {
5670
- name: "engram_2.0.0_darwin_amd64.tar.gz",
5671
- sha256: "f1dcc343ac241c5001617113279b49dec698005550b538250ccf21655fc728ac",
5672
- size: 7678428
5673
- },
5674
- linux_arm64: {
5675
- name: "engram_2.0.0_linux_arm64.tar.gz",
5676
- sha256: "a942e73ab424faaa6e2785d1563e0d9d7f20739944dae0c50071223301d44333",
5677
- size: 6926960
5678
- },
5679
- linux_x64: {
5680
- name: "engram_2.0.0_linux_amd64.tar.gz",
5681
- sha256: "23be1c2ce9739c455097ff864736213717b925b3e8821a988dfc619685a5abd5",
5682
- size: 7544949
5683
- },
5684
- windows_arm64: {
5685
- name: "engram_2.0.0_windows_arm64.zip",
5686
- sha256: "e4d9074ebf8a839573f62d7bd2719421fc428ab25a9affe82ca4b2d5555183d1",
5687
- size: 6980876
5688
- },
5689
- windows_x64: {
5690
- name: "engram_2.0.0_windows_amd64.zip",
5691
- sha256: "fba31e7221702f213954c2081aa83b38833e0e9b824a981f976f8cee2c74548c",
5692
- size: 7738896
5693
- }
5694
- },
5695
- version: "2.0.0"
5696
- };
5697
-
5698
- // src/lib/engram-install.ts
5672
+ var MAX_ENGRAM_ARCHIVE_BYTES = 64 * 1024 * 1024;
5673
+ var STABLE_TAG = /^v\d+\.\d+\.\d+$/;
5674
+ var DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
5699
5675
  async function installMissingEngram(options = {}) {
5700
5676
  const platform = options.platform ?? process.platform;
5701
5677
  const homeDir = options.homeDir ?? os5.homedir();
@@ -5703,58 +5679,159 @@ async function installMissingEngram(options = {}) {
5703
5679
  const binaryName = platform === "win32" ? "engram.exe" : "engram";
5704
5680
  const bin = path25.join(homeDir, ".local", "bin", binaryName);
5705
5681
  let staging;
5682
+ let outcome;
5706
5683
  try {
5707
5684
  const existing = fs19.lstatSync(bin, { throwIfNoEntry: false });
5708
5685
  if (existing) {
5709
5686
  if (!fs19.statSync(bin).isFile()) throw new Error(`El destino Engram no es un archivo: ${bin}`);
5710
5687
  fs19.accessSync(bin, platform === "win32" ? fs19.constants.F_OK : fs19.constants.X_OK);
5711
- return { ok: true, bin };
5712
- }
5713
- const key = `${platform === "win32" ? "windows" : platform}_${options.arch ?? process.arch}`;
5714
- const asset = engram_release_default.assets[key];
5715
- if (!asset) throw new Error(`Engram no dispone de un binario aprobado para ${key}.`);
5716
- const response = await (options.fetch ?? globalThis.fetch)(
5717
- `https://github.com/Gentleman-Programming/engram/releases/download/v${engram_release_default.version}/${asset.name}`,
5718
- { signal: AbortSignal.timeout(12e4) }
5719
- );
5720
- if (!response.ok || !response.body) throw new Error(`Descarga Engram fallida: HTTP ${response.status}.`);
5721
- const reader = response.body.getReader();
5722
- const chunks = [];
5723
- let size = 0;
5724
- try {
5725
- while (true) {
5726
- const chunk = await reader.read();
5727
- if (chunk.done) break;
5728
- size += chunk.value.byteLength;
5729
- if (size > asset.size) throw new Error("El tama\xF1o descargado de Engram supera el aprobado.");
5730
- chunks.push(chunk.value);
5688
+ outcome = { ok: true, bin };
5689
+ } else {
5690
+ const fetchFn = options.fetch ?? globalThis.fetch;
5691
+ let releaseRes;
5692
+ try {
5693
+ releaseRes = await fetchLatestGithubRelease(ENGRAM_REPO, fetchFn);
5694
+ } catch (error) {
5695
+ const cause = error instanceof Error ? error.message : String(error);
5696
+ throw new Error(
5697
+ `Engram: error de red al consultar los metadatos del release (latest): ${cause}. Reintenta la instalaci\xF3n.`
5698
+ );
5731
5699
  }
5732
- } finally {
5733
- await reader.cancel();
5734
- }
5735
- const archive = Buffer.concat(chunks);
5736
- if (size !== asset.size || createHash3("sha256").update(archive).digest("hex") !== asset.sha256) {
5737
- throw new Error("Engram: tama\xF1o o hash SHA-256 no coincide con el artefacto aprobado.");
5738
- }
5739
- fs19.mkdirSync(path25.dirname(bin), { recursive: true });
5740
- staging = fs19.mkdtempSync(path25.join(path25.dirname(bin), ".engram-install-"));
5741
- const archivePath = path25.join(staging, asset.name);
5742
- fs19.writeFileSync(archivePath, archive, { flag: "wx", mode: 384 });
5743
- const binary = execFileSync6(resolveTarBin(), ["-xOf", archivePath, binaryName], {
5744
- maxBuffer: 64 * 1024 * 1024,
5745
- timeout: 3e4,
5746
- stdio: ["ignore", "pipe", "pipe"]
5747
- });
5748
- if (binary.length === 0) throw new Error("El artefacto Engram no contiene un binario v\xE1lido.");
5749
- const stagedBin = path25.join(staging, binaryName);
5750
- fs19.writeFileSync(stagedBin, binary, { flag: "wx", mode: 493 });
5751
- fs19.linkSync(stagedBin, bin);
5752
- return { ok: true, bin };
5700
+ if (!releaseRes.ok) {
5701
+ if (releaseRes.status === 429) {
5702
+ throw new Error(
5703
+ "Engram: metadatos del release no disponibles (HTTP 429 rate limit en la consulta latest/metadatos). Espera y reintenta; define GH_TOKEN o inicia sesi\xF3n con gh auth login para elevar el l\xEDmite."
5704
+ );
5705
+ }
5706
+ if (releaseRes.status === 403) {
5707
+ throw new Error(
5708
+ "Engram: metadatos del release no disponibles (HTTP 403 en la consulta latest/metadatos). Posible rate limit o falta de autenticaci\xF3n; define GH_TOKEN o usa gh auth login y reintenta."
5709
+ );
5710
+ }
5711
+ throw new Error(
5712
+ `Engram: metadatos del release no disponibles (HTTP ${releaseRes.status} en la consulta latest).`
5713
+ );
5714
+ }
5715
+ let raw;
5716
+ try {
5717
+ raw = await releaseRes.json();
5718
+ } catch (error) {
5719
+ if (error instanceof SyntaxError) {
5720
+ throw new Error("Engram: metadatos del release inv\xE1lidos: JSON no parseable.");
5721
+ }
5722
+ const cause = error instanceof Error ? error.message : String(error);
5723
+ const hint = error instanceof Error && error.name === "AbortError" ? "interrumpida (red/cancelada)" : "de red";
5724
+ throw new Error(
5725
+ `Engram: lectura de metadatos del release ${hint}: ${cause}. Reintenta la instalaci\xF3n.`
5726
+ );
5727
+ }
5728
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
5729
+ throw new Error("Engram: metadatos del release inv\xE1lidos: se esperaba un objeto JSON.");
5730
+ }
5731
+ const releaseJson = raw;
5732
+ const tag = releaseJson.tag_name;
5733
+ if (typeof tag !== "string" || !STABLE_TAG.test(tag)) {
5734
+ throw new Error(`Engram: tag del release inv\xE1lido: ${String(tag)}.`);
5735
+ }
5736
+ const version = tag.slice(1);
5737
+ const expectedName = expectedEngramAssetName(version, platform, options.arch ?? process.arch);
5738
+ if (expectedName === null) {
5739
+ throw new Error(`Engram no dispone de un binario aprobado para ${platform}_${options.arch ?? process.arch}.`);
5740
+ }
5741
+ if (!Array.isArray(releaseJson.assets)) throw new Error("Engram: metadatos del release sin assets.");
5742
+ const matches = releaseJson.assets.filter((asset2) => asset2?.name === expectedName);
5743
+ if (matches.length !== 1) throw new Error("Engram: asset exacto ausente o ambiguo en el release.");
5744
+ const asset = matches[0];
5745
+ if (asset.state !== "uploaded") throw new Error("Engram: asset no publicado.");
5746
+ if (!Number.isInteger(asset.size) || asset.size <= 0) {
5747
+ throw new Error("Engram: tama\xF1o del asset inv\xE1lido.");
5748
+ }
5749
+ const expectedUrl = `${ENGRAM_DOWNLOAD_PREFIX}${tag}/${expectedName}`;
5750
+ if (typeof asset.browser_download_url !== "string" || asset.browser_download_url !== expectedUrl) {
5751
+ throw new Error("Engram: URL del asset no oficial.");
5752
+ }
5753
+ if (typeof asset.digest !== "string" || !DIGEST_RE.test(asset.digest)) {
5754
+ throw new Error("Engram: digest del asset inv\xE1lido.");
5755
+ }
5756
+ const approvedSize = asset.size;
5757
+ if (approvedSize > MAX_ENGRAM_ARCHIVE_BYTES) {
5758
+ throw new Error(
5759
+ `Engram: tama\xF1o del asset aprobado (${approvedSize} bytes) excede el l\xEDmite de 64 MiB (${MAX_ENGRAM_ARCHIVE_BYTES} bytes).`
5760
+ );
5761
+ }
5762
+ const approvedSha = asset.digest.slice("sha256:".length);
5763
+ const response = await fetchFn(asset.browser_download_url, {
5764
+ signal: AbortSignal.timeout(12e4)
5765
+ });
5766
+ if (!response.ok || !response.body) throw new Error(`Descarga Engram fallida: HTTP ${response.status}.`);
5767
+ const reader = response.body.getReader();
5768
+ const chunks = [];
5769
+ let size = 0;
5770
+ let readError;
5771
+ try {
5772
+ try {
5773
+ while (true) {
5774
+ const chunk = await reader.read();
5775
+ if (chunk.done) break;
5776
+ size += chunk.value.byteLength;
5777
+ if (size > approvedSize) throw new Error("El tama\xF1o descargado de Engram supera el aprobado.");
5778
+ chunks.push(chunk.value);
5779
+ }
5780
+ } catch (error) {
5781
+ readError = error;
5782
+ throw error;
5783
+ }
5784
+ } finally {
5785
+ try {
5786
+ await reader.cancel();
5787
+ } catch (cancelError) {
5788
+ const cancelMsg = cancelError instanceof Error ? cancelError.message : String(cancelError);
5789
+ if (readError !== void 0) {
5790
+ const primaryMsg = readError instanceof Error ? readError.message : String(readError);
5791
+ throw new Error(`${primaryMsg} (adem\xE1s fall\xF3 la cancelaci\xF3n del stream: ${cancelMsg})`);
5792
+ }
5793
+ throw cancelError;
5794
+ }
5795
+ }
5796
+ const archive = Buffer.concat(chunks);
5797
+ if (size !== approvedSize || createHash3("sha256").update(archive).digest("hex") !== approvedSha) {
5798
+ throw new Error("Engram: tama\xF1o o hash SHA-256 no coincide con el artefacto aprobado.");
5799
+ }
5800
+ fs19.mkdirSync(path25.dirname(bin), { recursive: true });
5801
+ staging = fs19.mkdtempSync(path25.join(path25.dirname(bin), ".engram-install-"));
5802
+ const archivePath = path25.join(staging, expectedName);
5803
+ fs19.writeFileSync(archivePath, archive, { flag: "wx", mode: 384 });
5804
+ const binary = execFileSync6(resolveTarBin(), ["-xOf", archivePath, binaryName], {
5805
+ maxBuffer: 64 * 1024 * 1024,
5806
+ timeout: 3e4,
5807
+ stdio: ["ignore", "pipe", "pipe"]
5808
+ });
5809
+ if (binary.length === 0) throw new Error("El artefacto Engram no contiene un binario v\xE1lido.");
5810
+ const stagedBin = path25.join(staging, binaryName);
5811
+ fs19.writeFileSync(stagedBin, binary, { flag: "wx", mode: 493 });
5812
+ fs19.linkSync(stagedBin, bin);
5813
+ outcome = { ok: true, bin };
5814
+ }
5753
5815
  } catch (error) {
5754
- return { ok: false, reason: error instanceof Error ? error.message : String(error) };
5755
- } finally {
5756
- if (staging) fs19.rmSync(staging, { recursive: true, force: true });
5816
+ outcome = { ok: false, reason: error instanceof Error ? error.message : String(error) };
5817
+ }
5818
+ if (staging !== void 0) {
5819
+ try {
5820
+ fs19.rmSync(staging, { recursive: true, force: true });
5821
+ } catch (cleanupError) {
5822
+ const cleanupMsg = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
5823
+ if (outcome !== void 0 && outcome.ok) {
5824
+ outcome = {
5825
+ ok: true,
5826
+ bin: outcome.bin,
5827
+ warning: `Limpieza del directorio temporal ${staging} fallida tras instalar Engram: ${cleanupMsg}. El binario qued\xF3 instalado en ${outcome.bin}.`
5828
+ };
5829
+ } else if (outcome !== void 0 && !outcome.ok) {
5830
+ outcome = { ok: false, reason: `${outcome.reason} (adem\xE1s fall\xF3 la limpieza temporal: ${cleanupMsg})` };
5831
+ }
5832
+ }
5757
5833
  }
5834
+ return outcome ?? { ok: false, reason: "Engram: instalaci\xF3n finaliz\xF3 sin resultado verificable." };
5758
5835
  }
5759
5836
 
5760
5837
  // src/cli.ts
@@ -6088,7 +6165,10 @@ async function resolveHostEngramForInstall(flags) {
6088
6165
  }
6089
6166
  try {
6090
6167
  const result = await installMissingEngram();
6091
- if (result.ok) return { ok: true, bin: result.bin };
6168
+ if (result.ok) {
6169
+ if (result.warning) p6.log.warn(result.warning);
6170
+ return { ok: true, bin: result.bin };
6171
+ }
6092
6172
  return { ok: false, message: `Engram: ${result.reason}` };
6093
6173
  } catch (error) {
6094
6174
  return { ok: false, message: `Engram: ${error instanceof Error ? error.message : String(error)}` };
@@ -6143,7 +6223,11 @@ async function runSelectedPi(options) {
6143
6223
  const answer = await p6.confirm({ message, initialValue });
6144
6224
  return !p6.isCancel(answer) && answer;
6145
6225
  },
6146
- installNative: async ({ version }) => updateEngram("Gentleman-Programming/engram", version)
6226
+ installShared: async () => {
6227
+ const result2 = await installMissingEngram();
6228
+ if (result2.ok && result2.warning) p6.log.warn(result2.warning);
6229
+ return result2;
6230
+ }
6147
6231
  });
6148
6232
  if (requirement.kind !== "existing") {
6149
6233
  console.error(requirement.kind === "offer" ? "Pi: instalaci\xF3n cancelada; Engram sigue siendo obligatorio." : `Pi: ${requirement.reason}. ${requirement.remedy}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jorgex-stack",
3
- "version": "1.9.58",
3
+ "version": "1.9.59",
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",