theforge-cli 0.1.0 → 0.2.0

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/dist/index.js CHANGED
@@ -95,7 +95,7 @@ var require_windows = __commonJS({
95
95
  module.exports = isexe;
96
96
  isexe.sync = sync;
97
97
  var fs2 = __require("fs");
98
- function checkPathExt(path45, options) {
98
+ function checkPathExt(path49, options) {
99
99
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
100
100
  if (!pathext) {
101
101
  return true;
@@ -106,25 +106,25 @@ var require_windows = __commonJS({
106
106
  }
107
107
  for (var i3 = 0; i3 < pathext.length; i3++) {
108
108
  var p = pathext[i3].toLowerCase();
109
- if (p && path45.substr(-p.length).toLowerCase() === p) {
109
+ if (p && path49.substr(-p.length).toLowerCase() === p) {
110
110
  return true;
111
111
  }
112
112
  }
113
113
  return false;
114
114
  }
115
- function checkStat(stat, path45, options) {
115
+ function checkStat(stat, path49, options) {
116
116
  if (!stat.isSymbolicLink() && !stat.isFile()) {
117
117
  return false;
118
118
  }
119
- return checkPathExt(path45, options);
119
+ return checkPathExt(path49, options);
120
120
  }
121
- function isexe(path45, options, cb) {
122
- fs2.stat(path45, function(er, stat) {
123
- cb(er, er ? false : checkStat(stat, path45, options));
121
+ function isexe(path49, options, cb) {
122
+ fs2.stat(path49, function(er, stat) {
123
+ cb(er, er ? false : checkStat(stat, path49, options));
124
124
  });
125
125
  }
126
- function sync(path45, options) {
127
- return checkStat(fs2.statSync(path45), path45, options);
126
+ function sync(path49, options) {
127
+ return checkStat(fs2.statSync(path49), path49, options);
128
128
  }
129
129
  }
130
130
  });
@@ -136,13 +136,13 @@ var require_mode = __commonJS({
136
136
  module.exports = isexe;
137
137
  isexe.sync = sync;
138
138
  var fs2 = __require("fs");
139
- function isexe(path45, options, cb) {
140
- fs2.stat(path45, function(er, stat) {
139
+ function isexe(path49, options, cb) {
140
+ fs2.stat(path49, function(er, stat) {
141
141
  cb(er, er ? false : checkStat(stat, options));
142
142
  });
143
143
  }
144
- function sync(path45, options) {
145
- return checkStat(fs2.statSync(path45), options);
144
+ function sync(path49, options) {
145
+ return checkStat(fs2.statSync(path49), options);
146
146
  }
147
147
  function checkStat(stat, options) {
148
148
  return stat.isFile() && checkMode(stat, options);
@@ -176,7 +176,7 @@ var require_isexe = __commonJS({
176
176
  }
177
177
  module.exports = isexe;
178
178
  isexe.sync = sync;
179
- function isexe(path45, options, cb) {
179
+ function isexe(path49, options, cb) {
180
180
  if (typeof options === "function") {
181
181
  cb = options;
182
182
  options = {};
@@ -186,7 +186,7 @@ var require_isexe = __commonJS({
186
186
  throw new TypeError("callback not provided");
187
187
  }
188
188
  return new Promise(function(resolve, reject) {
189
- isexe(path45, options || {}, function(er, is) {
189
+ isexe(path49, options || {}, function(er, is) {
190
190
  if (er) {
191
191
  reject(er);
192
192
  } else {
@@ -195,7 +195,7 @@ var require_isexe = __commonJS({
195
195
  });
196
196
  });
197
197
  }
198
- core(path45, options || {}, function(er, is) {
198
+ core(path49, options || {}, function(er, is) {
199
199
  if (er) {
200
200
  if (er.code === "EACCES" || options && options.ignoreErrors) {
201
201
  er = null;
@@ -205,9 +205,9 @@ var require_isexe = __commonJS({
205
205
  cb(er, is);
206
206
  });
207
207
  }
208
- function sync(path45, options) {
208
+ function sync(path49, options) {
209
209
  try {
210
- return core.sync(path45, options || {});
210
+ return core.sync(path49, options || {});
211
211
  } catch (er) {
212
212
  if (options && options.ignoreErrors || er.code === "EACCES") {
213
213
  return false;
@@ -224,7 +224,7 @@ var require_which = __commonJS({
224
224
  "node_modules/which/which.js"(exports, module) {
225
225
  "use strict";
226
226
  var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
227
- var path45 = __require("path");
227
+ var path49 = __require("path");
228
228
  var COLON = isWindows ? ";" : ":";
229
229
  var isexe = require_isexe();
230
230
  var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
@@ -262,7 +262,7 @@ var require_which = __commonJS({
262
262
  return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
263
263
  const ppRaw = pathEnv[i3];
264
264
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
265
- const pCmd = path45.join(pathPart, cmd);
265
+ const pCmd = path49.join(pathPart, cmd);
266
266
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
267
267
  resolve(subStep(p, i3, 0));
268
268
  });
@@ -289,7 +289,7 @@ var require_which = __commonJS({
289
289
  for (let i3 = 0; i3 < pathEnv.length; i3++) {
290
290
  const ppRaw = pathEnv[i3];
291
291
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
292
- const pCmd = path45.join(pathPart, cmd);
292
+ const pCmd = path49.join(pathPart, cmd);
293
293
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
294
294
  for (let j = 0; j < pathExt.length; j++) {
295
295
  const cur = p + pathExt[j];
@@ -337,7 +337,7 @@ var require_path_key = __commonJS({
337
337
  var require_resolveCommand = __commonJS({
338
338
  "node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) {
339
339
  "use strict";
340
- var path45 = __require("path");
340
+ var path49 = __require("path");
341
341
  var which = require_which();
342
342
  var getPathKey = require_path_key();
343
343
  function resolveCommandAttempt(parsed, withoutPathExt) {
@@ -348,14 +348,14 @@ var require_resolveCommand = __commonJS({
348
348
  if (shouldSwitchCwd) {
349
349
  try {
350
350
  process.chdir(parsed.options.cwd);
351
- } catch (err) {
351
+ } catch (err2) {
352
352
  }
353
353
  }
354
354
  let resolved;
355
355
  try {
356
356
  resolved = which.sync(parsed.command, {
357
357
  path: env[getPathKey({ env })],
358
- pathExt: withoutPathExt ? path45.delimiter : void 0
358
+ pathExt: withoutPathExt ? path49.delimiter : void 0
359
359
  });
360
360
  } catch (e) {
361
361
  } finally {
@@ -364,7 +364,7 @@ var require_resolveCommand = __commonJS({
364
364
  }
365
365
  }
366
366
  if (resolved) {
367
- resolved = path45.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
367
+ resolved = path49.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
368
368
  }
369
369
  return resolved;
370
370
  }
@@ -418,8 +418,8 @@ var require_shebang_command = __commonJS({
418
418
  if (!match) {
419
419
  return null;
420
420
  }
421
- const [path45, argument] = match[0].replace(/#! ?/, "").split(" ");
422
- const binary = path45.split("/").pop();
421
+ const [path49, argument] = match[0].replace(/#! ?/, "").split(" ");
422
+ const binary = path49.split("/").pop();
423
423
  if (binary === "env") {
424
424
  return argument;
425
425
  }
@@ -454,7 +454,7 @@ var require_readShebang = __commonJS({
454
454
  var require_parse = __commonJS({
455
455
  "node_modules/cross-spawn/lib/parse.js"(exports, module) {
456
456
  "use strict";
457
- var path45 = __require("path");
457
+ var path49 = __require("path");
458
458
  var resolveCommand2 = require_resolveCommand();
459
459
  var escape = require_escape();
460
460
  var readShebang = require_readShebang();
@@ -479,7 +479,7 @@ var require_parse = __commonJS({
479
479
  const needsShell = !isExecutableRegExp.test(commandFile);
480
480
  if (parsed.options.forceShell || needsShell) {
481
481
  const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
482
- parsed.command = path45.normalize(parsed.command);
482
+ parsed.command = path49.normalize(parsed.command);
483
483
  parsed.command = escape.command(parsed.command);
484
484
  parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
485
485
  const shellCommand = [parsed.command].concat(parsed.args).join(" ");
@@ -533,9 +533,9 @@ var require_enoent = __commonJS({
533
533
  const originalEmit = cp.emit;
534
534
  cp.emit = function(name, arg1) {
535
535
  if (name === "exit") {
536
- const err = verifyENOENT(arg1, parsed);
537
- if (err) {
538
- return originalEmit.call(cp, "error", err);
536
+ const err2 = verifyENOENT(arg1, parsed);
537
+ if (err2) {
538
+ return originalEmit.call(cp, "error", err2);
539
539
  }
540
540
  }
541
541
  return originalEmit.apply(cp, arguments);
@@ -569,7 +569,7 @@ var require_cross_spawn = __commonJS({
569
569
  var cp = __require("child_process");
570
570
  var parse = require_parse();
571
571
  var enoent = require_enoent();
572
- function spawn2(command, args, options) {
572
+ function spawn3(command, args, options) {
573
573
  const parsed = parse(command, args, options);
574
574
  const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
575
575
  enoent.hookChildProcess(spawned, parsed);
@@ -581,8 +581,8 @@ var require_cross_spawn = __commonJS({
581
581
  result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
582
582
  return result;
583
583
  }
584
- module.exports = spawn2;
585
- module.exports.spawn = spawn2;
584
+ module.exports = spawn3;
585
+ module.exports.spawn = spawn3;
586
586
  module.exports.sync = spawnSync2;
587
587
  module.exports._parse = parse;
588
588
  module.exports._enoent = enoent;
@@ -7044,13 +7044,13 @@ var logOutputSync = ({ serializedResult, fdNumber, state, verboseInfo, encoding,
7044
7044
  }
7045
7045
  };
7046
7046
  var writeToFiles = (serializedResult, stdioItems, outputFiles) => {
7047
- for (const { path: path45, append } of stdioItems.filter(({ type }) => FILE_TYPES.has(type))) {
7048
- const pathString = typeof path45 === "string" ? path45 : path45.toString();
7047
+ for (const { path: path49, append } of stdioItems.filter(({ type }) => FILE_TYPES.has(type))) {
7048
+ const pathString = typeof path49 === "string" ? path49 : path49.toString();
7049
7049
  if (append || outputFiles.has(pathString)) {
7050
- appendFileSync2(path45, serializedResult);
7050
+ appendFileSync2(path49, serializedResult);
7051
7051
  } else {
7052
7052
  outputFiles.add(pathString);
7053
- writeFileSync2(path45, serializedResult);
7053
+ writeFileSync2(path49, serializedResult);
7054
7054
  }
7055
7055
  }
7056
7056
  };
@@ -9446,8 +9446,12 @@ var {
9446
9446
  // src/utils/system.ts
9447
9447
  import { existsSync as existsSync3 } from "fs";
9448
9448
  import { mkdir } from "fs/promises";
9449
+ import os from "os";
9449
9450
  import path8 from "path";
9450
9451
  var DEFAULT_WORKSPACE = "./forge-workspace";
9452
+ function cliHome() {
9453
+ return process.env.FORGE_CLI_HOME || os.homedir();
9454
+ }
9451
9455
  var WIN_SETUP_CANDIDATES = ["setup.ps1", "install.ps1"];
9452
9456
  var POSIX_SETUP_CANDIDATES = ["setup.sh", "install.sh"];
9453
9457
  function resolveTargetDir(input, cwd = process.cwd()) {
@@ -9590,8 +9594,8 @@ async function runStreamed(argv, cwd, onLine, timeoutMs) {
9590
9594
  }
9591
9595
  function errorDetail(error) {
9592
9596
  if (error && typeof error === "object") {
9593
- const err = error;
9594
- return redactSecrets(err.stderr?.trim() || err.shortMessage || err.message || String(error));
9597
+ const err2 = error;
9598
+ return redactSecrets(err2.stderr?.trim() || err2.shortMessage || err2.message || String(error));
9595
9599
  }
9596
9600
  return redactSecrets(String(error));
9597
9601
  }
@@ -9606,16 +9610,16 @@ function compareVersions(a3, b) {
9606
9610
  return b.localeCompare(a3, void 0, { numeric: true });
9607
9611
  }
9608
9612
  async function updateCheck(repoDir) {
9613
+ const up = await execa("git", ["rev-parse", "--abbrev-ref", "@{u}"], {
9614
+ cwd: repoDir,
9615
+ reject: false
9616
+ });
9617
+ if (up.exitCode !== 0) return { behind: 0, ahead: 0, failure: "no-upstream" };
9609
9618
  try {
9610
9619
  await execa("git", ["fetch", "--quiet", "--tags"], { cwd: repoDir, env: NO_PROMPT });
9611
9620
  } catch (error) {
9612
9621
  return { behind: 0, ahead: 0, failure: classifyGitFailure(errorDetail(error)) };
9613
9622
  }
9614
- const up = await execa("git", ["rev-parse", "--abbrev-ref", "@{u}"], {
9615
- cwd: repoDir,
9616
- reject: false
9617
- });
9618
- if (up.exitCode !== 0) return { behind: 0, ahead: 0, failure: "unknown" };
9619
9623
  const counts = await execa("git", ["rev-list", "--left-right", "--count", "HEAD...@{u}"], {
9620
9624
  cwd: repoDir,
9621
9625
  reject: false
@@ -9777,10 +9781,26 @@ var S = {
9777
9781
  errRefDirty: "Working tree com altera\xE7\xF5es locais \u2014 n\xE3o \xE9 seguro trocar de revis\xE3o",
9778
9782
  refCheckedOut: "Revis\xE3o",
9779
9783
  errRefNeedsOne: "--ref/--version exigem exatamente uma Forja",
9784
+ errMethodUnsupported: "--method registry s\xF3 \xE9 suportado em install|uninstall|update|reinstall|rollback|pin|unpin",
9785
+ errRegistryReinstallNeedsNames: "reinstall --method registry exige nomes, --only ou --all (Forjas instaladas via registry)",
9786
+ errRegistryRollbackNeedsNames: "rollback --method registry exige nomes, --only ou --all (Forjas instaladas via registry)",
9787
+ errRegistryPinNeedsNames: "pin --method registry exige nomes, --only ou --all (Forjas instaladas via registry)",
9788
+ errRegistryUnpinNeedsNames: "unpin --method registry exige nomes, --only ou --all (Forjas instaladas via registry)",
9789
+ registryPinnedAt: "fixada em",
9790
+ errRegistryNeedsNames: "informe as Forjas (ex.: forge-cli install api-forge --method registry) ou --all",
9791
+ errRegistryUninstallNeedsNames: "uninstall --method registry exige nomes, --only ou --all (Forjas instaladas via registry)",
9792
+ registryUninstallConfirm: "remover venvs e receipts das Forjas instaladas via PyPI",
9793
+ registryUninstallNeedsYes: "Nada foi removido. Reexecute com --yes para confirmar.",
9794
+ registryIgnoredFlags: "flags ignoradas no m\xE9todo registry",
9795
+ registryStaleCache: "cache do PyPI expirado \u2014 metadados podem estar desatualizados",
9796
+ registryOfflineMissing: "artefato ausente no cache local \u2014 instala\xE7\xE3o offline imposs\xEDvel",
9797
+ registryHashMismatch: "digest sha256 diverge do publicado no PyPI \u2014 instala\xE7\xE3o abortada",
9798
+ registryVerifyFailed: "(verify falhou)",
9780
9799
  errRefExclusive: "Use apenas um entre --ref e --version",
9781
9800
  errRefInvalid: "Revis\xE3o/vers\xE3o inv\xE1lida",
9782
9801
  updateCheckTitle: "Verifica\xE7\xE3o de atualiza\xE7\xF5es",
9783
9802
  updateCheckFailed: "n\xE3o foi poss\xEDvel verificar",
9803
+ updateNoUpstream: "sem upstream configurado",
9784
9804
  updateCommitsBehind: "commit(s) dispon\xEDveis",
9785
9805
  updateUpToDate: "atualizado",
9786
9806
  updateNothing: "Nenhuma Forja clonada neste diret\xF3rio \u2014 nada para atualizar",
@@ -10013,13 +10033,17 @@ Uso:
10013
10033
  forge-cli doctor [--fix] Diagnostica; --fix repara os que falharem
10014
10034
  forge-cli list Lista o cat\xE1logo e o \xFAltimo estado conhecido
10015
10035
  forge-cli install [forjas] Instala uma ou mais Forjas (sem nomes: interativo)
10036
+ --method registry instala do PyPI em venv isolada
10016
10037
  forge-cli clone <forjas> S\xF3 clona/sincroniza; n\xE3o executa scripts
10017
10038
  forge-cli update [forjas] Atualiza (pull + setup); --check s\xF3 verifica
10039
+ --method registry: mais nova no PyPI; respeita pin; --check plano
10018
10040
  forge-cli versions [forjas] Lista tags/vers\xF5es e revis\xE3o atual
10019
10041
  forge-cli reinstall <forjas> Reexecuta o setup mantendo a revis\xE3o atual; --clean recria o clone
10042
+ --method registry: reinstala a mesma vers\xE3o do receipt (drift)
10020
10043
  forge-cli pin <forja> --version <tag> Fixa a vers\xE3o (update/install respeitam)
10021
10044
  forge-cli unpin <forja> Remove a fixa\xE7\xE3o
10022
10045
  forge-cli rollback <forja> Volta o checkout ao HEAD anterior registrado
10046
+ --method registry: desempilha a vers\xE3o anterior do receipt
10023
10047
  forge-cli uninstall <forjas> Remove clones limpos (backup de .env/configs; exige --yes)
10024
10048
  forge-cli components [forjas] Mostra o instalador nativo e as flags suportadas por Forja
10025
10049
  forge-cli help Mostra esta ajuda
@@ -10028,6 +10052,7 @@ Uso:
10028
10052
  forge-cli recommend Recomenda\xE7\xF5es explic\xE1veis a partir dos sinais
10029
10053
  forge-cli env <verbo> [forja] Intelig\xEAncia de .env: inspect|plan|init|validate|migrate|doctor
10030
10054
  forge-cli hosts [verbo] Hosts de IA: list|plan|register|validate|rollback
10055
+ forge-cli mcp [forja\u2026] Comando MCP por Forja (clone ou venv registry); --verify testa --version
10031
10056
  forge-cli self install Registra forge-cli no PATH do usu\xE1rio
10032
10057
  forge-cli self uninstall Remove o registro do PATH
10033
10058
  forge-cli self update Atualiza o pr\xF3prio CLI conforme o m\xE9todo de instala\xE7\xE3o
@@ -10038,6 +10063,7 @@ Uso:
10038
10063
  forge-cli bundle <verbo> Portabilidade: export [chaves-n\xE3o-secretas...]|import|verify
10039
10064
  forge-cli learning <verbo> Observa\xE7\xF5es locais opt-in: status|enable|disable|report
10040
10065
  forge-cli uninstall Sem nomes: remove o registro do PATH (legado)
10066
+ forge-cli registry Lista o cat\xE1logo PyPI e o que est\xE1 instalado via registry
10041
10067
 
10042
10068
  Op\xE7\xF5es:
10043
10069
  --dir <caminho> Diret\xF3rio de instala\xE7\xE3o (padr\xE3o: ./forge-workspace)
@@ -10056,6 +10082,8 @@ Op\xE7\xF5es:
10056
10082
  --shallow Clona com git clone --depth 1 (s\xF3 clones novos)
10057
10083
  --ref <rev> Fixa a Forja (uma s\xF3) em commit/tag (checkout destacado)
10058
10084
  --version <tag> Fixa a Forja (uma s\xF3) em uma vers\xE3o (tag, com ou sem 'v')
10085
+ --method <m> M\xE9todo de instala\xE7\xE3o: git (clone+setup) ou registry (PyPI+venv)
10086
+ --offline No m\xE9todo registry: resolve s\xF3 pelo cache local do PyPI
10059
10087
  --clean Com 'reinstall': remove e reclona na mesma revis\xE3o (com backup)
10060
10088
  --profile <p> Instalador nativo da Forja: minimal|recommended|full
10061
10089
  --scope <s> Instalador nativo da Forja: project|workspace|user
@@ -10145,9 +10173,11 @@ function parseCliArgs(argv) {
10145
10173
  clean: { type: "boolean" },
10146
10174
  ref: { type: "string" },
10147
10175
  version: { type: "string" },
10176
+ method: { type: "string" },
10148
10177
  "trust-custom": { type: "boolean" },
10149
10178
  "run-after-sync-failure": { type: "boolean" },
10150
10179
  rollback: { type: "boolean" },
10180
+ verify: { type: "boolean" },
10151
10181
  help: { type: "boolean", short: "h" }
10152
10182
  },
10153
10183
  strict: true,
@@ -10179,6 +10209,9 @@ function parseCliArgs(argv) {
10179
10209
  if (ref !== void 0 && !isValidRef(ref)) {
10180
10210
  throw new CliError(`${S.errRefInvalid}: ${ref}`);
10181
10211
  }
10212
+ if (values.method !== void 0 && !["git", "registry"].includes(values.method)) {
10213
+ throw new CliError(`--method inv\xE1lido: ${values.method} (use git|registry)`);
10214
+ }
10182
10215
  return {
10183
10216
  dir: values.dir,
10184
10217
  all: values.all ?? false,
@@ -10202,6 +10235,7 @@ function parseCliArgs(argv) {
10202
10235
  check: values.check ?? false,
10203
10236
  native: values.native ?? false,
10204
10237
  rollback: values.rollback ?? false,
10238
+ verify: values.verify ?? false,
10205
10239
  host: values.host,
10206
10240
  gatewayProfile: values["gateway-profile"],
10207
10241
  verbose: values.verbose ?? false,
@@ -10211,6 +10245,7 @@ function parseCliArgs(argv) {
10211
10245
  components: values.components,
10212
10246
  clean: values.clean ?? false,
10213
10247
  ref,
10248
+ method: values.method,
10214
10249
  trustCustom: values["trust-custom"] ?? false,
10215
10250
  runAfterSyncFailure: values["run-after-sync-failure"] ?? false,
10216
10251
  positionals
@@ -10265,6 +10300,7 @@ function resolveCommand(cli) {
10265
10300
  if (head === "recommend") return { command: "recommend", names: rest };
10266
10301
  if (head === "env") return { command: "env", names: rest };
10267
10302
  if (head === "hosts") return { command: "hosts", names: rest };
10303
+ if (head === "mcp") return { command: "mcp", names: rest };
10268
10304
  if (head === "wizard") return { command: "wizard", names: rest };
10269
10305
  if (head === "lock") return { command: "lock", names: rest };
10270
10306
  if (head === "profile") return { command: "profile", names: rest };
@@ -10275,6 +10311,7 @@ function resolveCommand(cli) {
10275
10311
  if (head === "ai") return { command: "ai", names: rest };
10276
10312
  if (head === "bundle") return { command: "bundle", names: rest };
10277
10313
  if (head === "learning") return { command: "learning", names: rest };
10314
+ if (head === "registry") return { command: "registry", names: rest };
10278
10315
  if (head === "uninstall") return { command: "uninstall", names: rest };
10279
10316
  if (REPO_COMMANDS.has(head)) return { command: head, names: rest };
10280
10317
  return { command: "unknown", names: [], unknown: head };
@@ -10420,6 +10457,18 @@ var MENU_ITEMS = [
10420
10457
  { value: "doctor", label: S.menuDoctor },
10421
10458
  { value: "exit", label: S.menuExit }
10422
10459
  ];
10460
+ var REGISTRY_MENU_ACTIONS = [
10461
+ "install",
10462
+ "update",
10463
+ "reinstall",
10464
+ "uninstall",
10465
+ "status",
10466
+ "exit"
10467
+ ];
10468
+ function menuItemsFor(cli) {
10469
+ if (cli.method !== "registry") return MENU_ITEMS;
10470
+ return MENU_ITEMS.filter((i3) => REGISTRY_MENU_ACTIONS.includes(i3.value));
10471
+ }
10423
10472
  function shouldShowMenu(cli, tty3) {
10424
10473
  return tty3 && cli.positionals.length === 0 && !cli.help && !cli.plan && !isNonInteractive(cli);
10425
10474
  }
@@ -10440,12 +10489,12 @@ function actionToPositionals(action, names) {
10440
10489
  return [action, ...names];
10441
10490
  }
10442
10491
  }
10443
- async function askMenu(io = {}) {
10492
+ async function askMenu(io = {}, items = MENU_ITEMS) {
10444
10493
  const choice = await select({
10445
10494
  ...io.input ? { input: io.input } : {},
10446
10495
  ...io.output ? { output: io.output } : {},
10447
10496
  message: S.menuTitle,
10448
- options: MENU_ITEMS.map((item) => ({
10497
+ options: items.map((item) => ({
10449
10498
  value: item.value,
10450
10499
  label: item.label,
10451
10500
  hint: item.hint
@@ -10458,6 +10507,15 @@ async function askMenu(io = {}) {
10458
10507
  async function applyMenu(cli, deps) {
10459
10508
  const action = await deps.ask();
10460
10509
  if (action === "exit") return null;
10510
+ if (cli.method === "registry") {
10511
+ if (action === "status") return { ...cli, positionals: ["registry"] };
10512
+ if (action === "install") {
10513
+ const names3 = deps.pickRegistryCatalog ? await deps.pickRegistryCatalog() : [];
10514
+ return { ...cli, positionals: ["install", ...names3] };
10515
+ }
10516
+ const names2 = deps.pickRegistryInstalled ? await deps.pickRegistryInstalled() : [];
10517
+ return { ...cli, positionals: [action, ...names2] };
10518
+ }
10461
10519
  if (action === "install") return cli;
10462
10520
  const dir = cli.dir ? deps.resolveDir(cli.dir) : await deps.workspace();
10463
10521
  const names = actionNeedsRepos(action) ? await deps.pickRepos(dir) : [];
@@ -11460,8 +11518,8 @@ var figlet = (() => {
11460
11518
  const generatedTxt = fontOpts ? generateText(fontName, _reworkFontOpts(fontOpts, options), txt) : "";
11461
11519
  if (next) next(null, generatedTxt);
11462
11520
  return generatedTxt;
11463
- } catch (err) {
11464
- const error = err instanceof Error ? err : new Error(String(err));
11521
+ } catch (err2) {
11522
+ const error = err2 instanceof Error ? err2 : new Error(String(err2));
11465
11523
  if (next) {
11466
11524
  next(error);
11467
11525
  return "";
@@ -11487,8 +11545,8 @@ var figlet = (() => {
11487
11545
  const result = [fontOpts, font.comment || ""];
11488
11546
  if (callback) callback(null, fontOpts, font.comment);
11489
11547
  return result;
11490
- } catch (err) {
11491
- const error = err instanceof Error ? err : new Error(String(err));
11548
+ } catch (err2) {
11549
+ const error = err2 instanceof Error ? err2 : new Error(String(err2));
11492
11550
  if (callback) {
11493
11551
  callback(error);
11494
11552
  return null;
@@ -11586,12 +11644,12 @@ var figlet = (() => {
11586
11644
  if (callback) callback(null, result);
11587
11645
  return result;
11588
11646
  } catch (error) {
11589
- const err = error instanceof Error ? error : new Error(String(error));
11647
+ const err2 = error instanceof Error ? error : new Error(String(error));
11590
11648
  if (callback) {
11591
- callback(err);
11649
+ callback(err2);
11592
11650
  return null;
11593
11651
  }
11594
- throw err;
11652
+ throw err2;
11595
11653
  }
11596
11654
  };
11597
11655
  me.loadFontSync = function(name) {
@@ -11610,9 +11668,9 @@ var figlet = (() => {
11610
11668
  }
11611
11669
  if (callback) callback();
11612
11670
  } catch (error) {
11613
- const err = error instanceof Error ? error : new Error(String(error));
11671
+ const err2 = error instanceof Error ? error : new Error(String(error));
11614
11672
  if (callback) {
11615
- callback(err);
11673
+ callback(err2);
11616
11674
  return;
11617
11675
  }
11618
11676
  throw error;
@@ -11647,10 +11705,10 @@ nodeFiglet.loadFont = function(name, callback) {
11647
11705
  resolve(nodeFiglet.figFonts[actualFontName].options);
11648
11706
  return;
11649
11707
  }
11650
- fs.readFile(path9.join(nodeFiglet.defaults().fontPath, actualFontName + ".flf"), { encoding: "utf-8" }, (err, fontData) => {
11651
- if (err) {
11652
- if (callback) callback(err);
11653
- reject(err);
11708
+ fs.readFile(path9.join(nodeFiglet.defaults().fontPath, actualFontName + ".flf"), { encoding: "utf-8" }, (err2, fontData) => {
11709
+ if (err2) {
11710
+ if (callback) callback(err2);
11711
+ reject(err2);
11654
11712
  return;
11655
11713
  }
11656
11714
  fontData = fontData + "";
@@ -11675,10 +11733,10 @@ nodeFiglet.loadFontSync = function(font) {
11675
11733
  nodeFiglet.fonts = function(next) {
11676
11734
  return new Promise((resolve, reject) => {
11677
11735
  const fontList2 = [];
11678
- fs.readdir(nodeFiglet.defaults().fontPath, (err, files) => {
11679
- if (err) {
11680
- next && next(err);
11681
- reject(err);
11736
+ fs.readdir(nodeFiglet.defaults().fontPath, (err2, files) => {
11737
+ if (err2) {
11738
+ next && next(err2);
11739
+ reject(err2);
11682
11740
  return;
11683
11741
  }
11684
11742
  files.forEach((file) => {
@@ -14210,26 +14268,26 @@ var ContractValidator = class {
14210
14268
  }
14211
14269
  return this.validate(declared, document);
14212
14270
  }
14213
- check(value, schema, path45, errs) {
14271
+ check(value, schema, path49, errs) {
14214
14272
  if (typeof schema.$ref === "string") {
14215
14273
  const target = schema.$ref;
14216
14274
  if (!target.startsWith("forge/")) {
14217
- errs.push({ code: "CONTRACT-REF", message: `external ref refused: ${target}`, path: path45 });
14275
+ errs.push({ code: "CONTRACT-REF", message: `external ref refused: ${target}`, path: path49 });
14218
14276
  return;
14219
14277
  }
14220
14278
  const resolved = this.schemas[target];
14221
14279
  if (!resolved) {
14222
- errs.push({ code: "CONTRACT-UNKNOWN", message: `unknown contract: ${target}`, path: path45 });
14280
+ errs.push({ code: "CONTRACT-UNKNOWN", message: `unknown contract: ${target}`, path: path49 });
14223
14281
  return;
14224
14282
  }
14225
- this.check(value, resolved, path45, errs);
14283
+ this.check(value, resolved, path49, errs);
14226
14284
  return;
14227
14285
  }
14228
14286
  if ("const" in schema && value !== schema.const) {
14229
14287
  errs.push({
14230
14288
  code: "CONTRACT-CONST",
14231
14289
  message: `expected ${JSON.stringify(schema.const)}`,
14232
- path: path45
14290
+ path: path49
14233
14291
  });
14234
14292
  return;
14235
14293
  }
@@ -14237,7 +14295,7 @@ var ContractValidator = class {
14237
14295
  errs.push({
14238
14296
  code: "CONTRACT-TYPE",
14239
14297
  message: `expected type ${JSON.stringify(schema.type)}`,
14240
- path: path45
14298
+ path: path49
14241
14299
  });
14242
14300
  return;
14243
14301
  }
@@ -14245,27 +14303,27 @@ var ContractValidator = class {
14245
14303
  errs.push({
14246
14304
  code: "CONTRACT-ENUM",
14247
14305
  message: `value not in ${JSON.stringify(schema.enum)}`,
14248
- path: path45
14306
+ path: path49
14249
14307
  });
14250
14308
  return;
14251
14309
  }
14252
14310
  if (typeof value === "string") {
14253
14311
  if (typeof schema.pattern === "string" && !new RegExp(schema.pattern).test(value)) {
14254
- errs.push({ code: "CONTRACT-PATTERN", message: `does not match ${schema.pattern}`, path: path45 });
14312
+ errs.push({ code: "CONTRACT-PATTERN", message: `does not match ${schema.pattern}`, path: path49 });
14255
14313
  }
14256
14314
  if (typeof schema.minLength === "number" && value.length < schema.minLength) {
14257
- errs.push({ code: "CONTRACT-LENGTH", message: "string too short", path: path45 });
14315
+ errs.push({ code: "CONTRACT-LENGTH", message: "string too short", path: path49 });
14258
14316
  }
14259
14317
  if (typeof schema.maxLength === "number" && value.length > schema.maxLength) {
14260
- errs.push({ code: "CONTRACT-LENGTH", message: "string too long", path: path45 });
14318
+ errs.push({ code: "CONTRACT-LENGTH", message: "string too long", path: path49 });
14261
14319
  }
14262
14320
  }
14263
14321
  if (typeof value === "number") {
14264
14322
  if (typeof schema.min === "number" && value < schema.min) {
14265
- errs.push({ code: "CONTRACT-RANGE", message: `< ${schema.min}`, path: path45 });
14323
+ errs.push({ code: "CONTRACT-RANGE", message: `< ${schema.min}`, path: path49 });
14266
14324
  }
14267
14325
  if (typeof schema.max === "number" && value > schema.max) {
14268
- errs.push({ code: "CONTRACT-RANGE", message: `> ${schema.max}`, path: path45 });
14326
+ errs.push({ code: "CONTRACT-RANGE", message: `> ${schema.max}`, path: path49 });
14269
14327
  }
14270
14328
  }
14271
14329
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
@@ -14275,7 +14333,7 @@ var ContractValidator = class {
14275
14333
  errs.push({
14276
14334
  code: "CONTRACT-REQUIRED",
14277
14335
  message: `missing '${req}'`,
14278
- path: `${path45}/${req}`
14336
+ path: `${path49}/${req}`
14279
14337
  });
14280
14338
  }
14281
14339
  }
@@ -14283,25 +14341,25 @@ var ContractValidator = class {
14283
14341
  const additional = schema.additionalProperties;
14284
14342
  for (const [key, val] of Object.entries(obj)) {
14285
14343
  if (key in props) {
14286
- this.check(val, props[key], `${path45}/${key}`, errs);
14344
+ this.check(val, props[key], `${path49}/${key}`, errs);
14287
14345
  } else if (additional === false) {
14288
14346
  errs.push({
14289
14347
  code: "CONTRACT-UNKNOWN-FIELD",
14290
14348
  message: `unexpected '${key}'`,
14291
- path: `${path45}/${key}`
14349
+ path: `${path49}/${key}`
14292
14350
  });
14293
14351
  } else if (typeof additional === "object" && additional !== null) {
14294
- this.check(val, additional, `${path45}/${key}`, errs);
14352
+ this.check(val, additional, `${path49}/${key}`, errs);
14295
14353
  }
14296
14354
  }
14297
14355
  }
14298
14356
  if (Array.isArray(value)) {
14299
14357
  if (typeof schema.minItems === "number" && value.length < schema.minItems) {
14300
- errs.push({ code: "CONTRACT-SIZE", message: "too few items", path: path45 });
14358
+ errs.push({ code: "CONTRACT-SIZE", message: "too few items", path: path49 });
14301
14359
  }
14302
14360
  const itemSchema = schema.items;
14303
14361
  if (typeof itemSchema === "object" && itemSchema !== null) {
14304
- value.forEach((item, i3) => this.check(item, itemSchema, `${path45}/${i3}`, errs));
14362
+ value.forEach((item, i3) => this.check(item, itemSchema, `${path49}/${i3}`, errs));
14305
14363
  }
14306
14364
  }
14307
14365
  }
@@ -15259,10 +15317,681 @@ async function uninstall(opts = {}) {
15259
15317
  return { dirRemoved, shimsRemoved: dirRemoved, profilesTouched, pathRemoved };
15260
15318
  }
15261
15319
 
15262
- // src/compat.ts
15320
+ // src/cmd/query.ts
15321
+ import os2 from "os";
15322
+ import { existsSync as existsSync12 } from "fs";
15323
+
15324
+ // src/registry/install.ts
15263
15325
  import { createHash } from "crypto";
15264
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
15326
+ import {
15327
+ chmodSync as chmodSync2,
15328
+ existsSync as existsSync10,
15329
+ mkdirSync as mkdirSync4,
15330
+ readdirSync as readdirSync3,
15331
+ readFileSync as readFileSync9,
15332
+ renameSync as renameSync2,
15333
+ rmSync as rmSync2,
15334
+ writeFileSync as writeFileSync4
15335
+ } from "fs";
15336
+ import path18 from "path";
15337
+
15338
+ // src/registry/pypi.ts
15339
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync8 } from "fs";
15265
15340
  import path17 from "path";
15341
+
15342
+ // src/registry/versions.ts
15343
+ var VERSION_RE = /^(\d+(?:\.\d+)*)(?:(a|b|rc)(\d+))?(?:\.post(\d+))?(?:\.dev(\d+))?$/i;
15344
+ function parseVersion2(input) {
15345
+ const m3 = VERSION_RE.exec(input.trim());
15346
+ if (!m3) return null;
15347
+ const release = m3[1].split(".").map((x) => Number.parseInt(x, 10));
15348
+ if (release.some((n4) => !Number.isFinite(n4))) return null;
15349
+ const out = { release };
15350
+ if (m3[2]) out.pre = { tag: m3[2].toLowerCase(), n: Number.parseInt(m3[3], 10) };
15351
+ if (m3[4]) out.post = Number.parseInt(m3[4], 10);
15352
+ if (m3[5]) out.dev = Number.parseInt(m3[5], 10);
15353
+ return out;
15354
+ }
15355
+ var PRE_ORDER = { a: 0, b: 1, rc: 2 };
15356
+ function cmpRelease(a3, b) {
15357
+ const len = Math.max(a3.length, b.length);
15358
+ for (let i3 = 0; i3 < len; i3++) {
15359
+ const d = (a3[i3] ?? 0) - (b[i3] ?? 0);
15360
+ if (d !== 0) return d < 0 ? -1 : 1;
15361
+ }
15362
+ return 0;
15363
+ }
15364
+ function phaseRank(v) {
15365
+ if (v.dev !== void 0 && !v.pre && v.post === void 0) return 0;
15366
+ if (v.pre) return 1;
15367
+ if (v.post !== void 0) return 3;
15368
+ return 2;
15369
+ }
15370
+ function compareVersions3(a3, b) {
15371
+ const rel2 = cmpRelease(a3.release, b.release);
15372
+ if (rel2 !== 0) return rel2;
15373
+ const aPhase = phaseRank(a3);
15374
+ const bPhase = phaseRank(b);
15375
+ if (aPhase !== bPhase) return aPhase < bPhase ? -1 : 1;
15376
+ if (aPhase === 0) return (a3.dev ?? 0) - (b.dev ?? 0);
15377
+ if (aPhase === 1) {
15378
+ const tag2 = PRE_ORDER[a3.pre.tag] - PRE_ORDER[b.pre.tag];
15379
+ if (tag2 !== 0) return tag2;
15380
+ const pre = a3.pre.n - b.pre.n;
15381
+ if (pre !== 0) return pre;
15382
+ }
15383
+ if (aPhase === 1 || aPhase === 3) {
15384
+ const post = (a3.post ?? 0) - (b.post ?? 0);
15385
+ if (post !== 0) return post;
15386
+ }
15387
+ if (a3.dev === void 0 && b.dev === void 0) return 0;
15388
+ if (a3.dev === void 0) return 1;
15389
+ if (b.dev === void 0) return -1;
15390
+ return a3.dev - b.dev;
15391
+ }
15392
+ function isPrerelease(v) {
15393
+ return v.pre !== void 0 || v.dev !== void 0;
15394
+ }
15395
+ var SPEC_RE = /^(~=|==|!=|>=|<=|>|<)\s*([0-9][0-9a-zA-Z.]*?)(\.\*)?$/;
15396
+ function parseSpecifier(input) {
15397
+ const m3 = SPEC_RE.exec(input.trim());
15398
+ if (!m3) return null;
15399
+ const version = parseVersion2(m3[2]);
15400
+ if (!version) return null;
15401
+ const wildcard = Boolean(m3[3]);
15402
+ if (wildcard && m3[1] !== "==" && m3[1] !== "!=") return null;
15403
+ if (m3[1] === "~=" && version.release.length < 2) return null;
15404
+ return { op: m3[1], version, wildcard };
15405
+ }
15406
+ function matchWildcard(candidate, spec) {
15407
+ if (candidate.length < spec.length) return false;
15408
+ return spec.every((n4, i3) => candidate[i3] === n4);
15409
+ }
15410
+ function evalSpecifier(candidate, spec) {
15411
+ if (spec.wildcard) {
15412
+ const hit = matchWildcard(candidate.release, spec.version.release);
15413
+ return spec.op === "==" ? hit : !hit;
15414
+ }
15415
+ const cmp4 = compareVersions3(candidate, spec.version);
15416
+ switch (spec.op) {
15417
+ case "==":
15418
+ return cmp4 === 0;
15419
+ case "!=":
15420
+ return cmp4 !== 0;
15421
+ case ">=":
15422
+ return cmp4 >= 0;
15423
+ case ">":
15424
+ return cmp4 > 0;
15425
+ case "<=":
15426
+ return cmp4 <= 0;
15427
+ case "<":
15428
+ return cmp4 < 0;
15429
+ case "~=": {
15430
+ if (cmp4 < 0) return false;
15431
+ const bound = [...spec.version.release];
15432
+ bound.pop();
15433
+ bound[bound.length - 1] += 1;
15434
+ return cmpRelease(candidate.release, bound) < 0;
15435
+ }
15436
+ }
15437
+ }
15438
+ function satisfiesRequiresPython(requiresPython, candidate) {
15439
+ const parts = requiresPython.split(",").map((s) => s.trim()).filter(Boolean);
15440
+ if (!parts.length) return true;
15441
+ let anyUnknown = false;
15442
+ for (const part of parts) {
15443
+ const spec = parseSpecifier(part);
15444
+ if (!spec) {
15445
+ anyUnknown = true;
15446
+ continue;
15447
+ }
15448
+ if (!evalSpecifier(candidate, spec)) return false;
15449
+ }
15450
+ return anyUnknown ? null : true;
15451
+ }
15452
+ function latestStable(versions) {
15453
+ let best = null;
15454
+ let bestParsed = null;
15455
+ for (const raw of versions) {
15456
+ const parsed = parseVersion2(raw);
15457
+ if (!parsed || isPrerelease(parsed)) continue;
15458
+ if (!bestParsed || compareVersions3(parsed, bestParsed) > 0) {
15459
+ best = raw;
15460
+ bestParsed = parsed;
15461
+ }
15462
+ }
15463
+ return best;
15464
+ }
15465
+
15466
+ // src/registry/pypi.ts
15467
+ var PYPI_JSON = "https://pypi.org/pypi";
15468
+ var CACHE_TTL_MS = 60 * 60 * 1e3;
15469
+ function pypiCacheFile(cacheDir, dist) {
15470
+ return path17.join(cacheDir, `${dist}.json`);
15471
+ }
15472
+ function asString(v) {
15473
+ return typeof v === "string" && v.length ? v : void 0;
15474
+ }
15475
+ function parseFiles(raw) {
15476
+ if (!Array.isArray(raw)) return [];
15477
+ const out = [];
15478
+ for (const item of raw) {
15479
+ if (!item || typeof item !== "object") continue;
15480
+ const rec = item;
15481
+ const filename = asString(rec.filename);
15482
+ const url = asString(rec.url);
15483
+ if (!filename || !url) continue;
15484
+ const digests = rec.digests;
15485
+ out.push({
15486
+ filename,
15487
+ url,
15488
+ sha256: digests ? asString(digests.sha256) : void 0,
15489
+ packagetype: asString(rec.packagetype),
15490
+ requiresPython: asString(rec.requires_python) ?? void 0,
15491
+ yanked: rec.yanked === true || typeof rec.yanked === "string" ? true : void 0
15492
+ });
15493
+ }
15494
+ return out;
15495
+ }
15496
+ function readCache(cacheDir, dist) {
15497
+ try {
15498
+ const raw = readFileSync8(pypiCacheFile(cacheDir, dist), "utf8");
15499
+ const wrap = JSON.parse(raw);
15500
+ if (typeof wrap.fetchedAt !== "number" || !wrap.payload) return null;
15501
+ const age = Date.now() - wrap.fetchedAt;
15502
+ return { payload: wrap.payload, fresh: age >= 0 && age < CACHE_TTL_MS };
15503
+ } catch {
15504
+ return null;
15505
+ }
15506
+ }
15507
+ function writeCache(cacheDir, dist, payload) {
15508
+ try {
15509
+ mkdirSync3(cacheDir, { recursive: true });
15510
+ writeFileAtomic(
15511
+ pypiCacheFile(cacheDir, dist),
15512
+ JSON.stringify({ fetchedAt: Date.now(), payload })
15513
+ );
15514
+ } catch {
15515
+ }
15516
+ }
15517
+ var MAX_PAYLOAD_BYTES = 16 * 1024 * 1024;
15518
+ async function defaultFetch(url) {
15519
+ const res = await fetch(url, {
15520
+ headers: { accept: "application/json" },
15521
+ signal: AbortSignal.timeout(15e3)
15522
+ });
15523
+ const declared = Number(res.headers.get("content-length") ?? "0");
15524
+ if (declared > MAX_PAYLOAD_BYTES) {
15525
+ void res.body?.cancel();
15526
+ throw new Error(`payload excede ${MAX_PAYLOAD_BYTES} bytes`);
15527
+ }
15528
+ const reader = res.body?.getReader();
15529
+ if (!reader) {
15530
+ const body = await res.text();
15531
+ if (body.length > MAX_PAYLOAD_BYTES) {
15532
+ throw new Error(`payload excede ${MAX_PAYLOAD_BYTES} bytes`);
15533
+ }
15534
+ return { status: res.status, body };
15535
+ }
15536
+ const chunks = [];
15537
+ let total = 0;
15538
+ for (; ; ) {
15539
+ const { done, value } = await reader.read();
15540
+ if (done) break;
15541
+ total += value.byteLength;
15542
+ if (total > MAX_PAYLOAD_BYTES) {
15543
+ await reader.cancel();
15544
+ throw new Error(`payload excede ${MAX_PAYLOAD_BYTES} bytes`);
15545
+ }
15546
+ chunks.push(value);
15547
+ }
15548
+ return { status: res.status, body: new TextDecoder().decode(Buffer.concat(chunks)) };
15549
+ }
15550
+ var RETRYABLE = (status) => status === 429 || status >= 500;
15551
+ async function fetchWithRetry(fetchImpl, url) {
15552
+ let lastError;
15553
+ for (let attempt = 0; attempt < 3; attempt++) {
15554
+ try {
15555
+ const res = await fetchImpl(url);
15556
+ if (!RETRYABLE(res.status) || attempt === 2) return res;
15557
+ } catch (error) {
15558
+ lastError = error;
15559
+ if (attempt === 2) throw error;
15560
+ }
15561
+ await new Promise((resolve) => setTimeout(resolve, 250 * 2 ** attempt));
15562
+ }
15563
+ throw lastError;
15564
+ }
15565
+ async function resolveRelease(dist, opts) {
15566
+ const fetchImpl = opts.fetchImpl ?? defaultFetch;
15567
+ let payload;
15568
+ let fromCache = false;
15569
+ let staleCache = false;
15570
+ if (!opts.offline) {
15571
+ try {
15572
+ const res = await fetchWithRetry(fetchImpl, `${PYPI_JSON}/${encodeURIComponent(dist)}/json`);
15573
+ if (res.status === 404) return { kind: "not-found", dist };
15574
+ if (res.status !== 200) {
15575
+ const cached = readCache(opts.cacheDir, dist);
15576
+ if (cached) {
15577
+ payload = cached.payload;
15578
+ fromCache = true;
15579
+ staleCache = !cached.fresh;
15580
+ } else {
15581
+ return { kind: "network", detail: `PyPI respondeu HTTP ${res.status}` };
15582
+ }
15583
+ } else {
15584
+ const parsed = JSON.parse(res.body);
15585
+ if (typeof parsed?.releases !== "object" || !parsed.releases) {
15586
+ const cached = readCache(opts.cacheDir, dist);
15587
+ if (cached) {
15588
+ payload = cached.payload;
15589
+ fromCache = true;
15590
+ staleCache = !cached.fresh;
15591
+ } else {
15592
+ return { kind: "malformed", dist, detail: "resposta sem campo releases" };
15593
+ }
15594
+ } else {
15595
+ if (!opts.ephemeral) writeCache(opts.cacheDir, dist, parsed);
15596
+ payload = parsed;
15597
+ }
15598
+ }
15599
+ } catch (error) {
15600
+ const cached = readCache(opts.cacheDir, dist);
15601
+ if (cached) {
15602
+ payload = cached.payload;
15603
+ fromCache = true;
15604
+ staleCache = !cached.fresh;
15605
+ } else {
15606
+ return {
15607
+ kind: "network",
15608
+ detail: error instanceof Error ? error.message : String(error)
15609
+ };
15610
+ }
15611
+ }
15612
+ } else {
15613
+ const cached = readCache(opts.cacheDir, dist);
15614
+ if (!cached) return { kind: "offline-miss", dist };
15615
+ payload = cached.payload;
15616
+ fromCache = true;
15617
+ staleCache = !cached.fresh;
15618
+ }
15619
+ const releases = payload?.releases;
15620
+ if (!payload || typeof releases !== "object" || !releases) {
15621
+ return { kind: "malformed", dist, detail: "resposta sem campo releases" };
15622
+ }
15623
+ const installable = Object.keys(releases).filter((v) => {
15624
+ const files2 = parseFiles(releases[v]);
15625
+ if (!files2.length) return false;
15626
+ return files2.some((f) => !f.yanked);
15627
+ });
15628
+ const latest = latestStable(installable);
15629
+ let version = opts.pin ?? latest;
15630
+ if (!version) {
15631
+ return { kind: "malformed", dist, detail: "nenhuma vers\xE3o est\xE1vel instal\xE1vel" };
15632
+ }
15633
+ if (!Object.hasOwn(releases, version) && opts.pin) {
15634
+ const pinned = parseVersion2(version);
15635
+ if (pinned) {
15636
+ const canonical2 = Object.keys(releases).filter((k) => {
15637
+ const parsed = parseVersion2(k);
15638
+ return parsed !== null && compareVersions3(parsed, pinned) === 0;
15639
+ }).sort().at(0);
15640
+ if (canonical2) version = canonical2;
15641
+ }
15642
+ }
15643
+ if (!Object.hasOwn(releases, version)) {
15644
+ return {
15645
+ kind: "version-not-found",
15646
+ dist,
15647
+ requested: opts.pin ?? version,
15648
+ available: Object.keys(releases).filter((v) => parseVersion2(v)).sort((a3, b) => compareVersions3(parseVersion2(a3), parseVersion2(b)))
15649
+ };
15650
+ }
15651
+ const files = parseFiles(releases[version]);
15652
+ const requiresPython = files.find((f) => f.packagetype === "bdist_wheel" && f.requiresPython)?.requiresPython ?? files.find((f) => f.requiresPython)?.requiresPython ?? (version === latest ? asString(payload.info?.requires_python) : void 0);
15653
+ return {
15654
+ dist,
15655
+ version,
15656
+ requiresPython,
15657
+ files,
15658
+ source: opts.pin ? "pinned" : fromCache ? "cache" : "latest-stable",
15659
+ staleCache: staleCache || void 0,
15660
+ yanked: files.length && files.every((f) => f.yanked) ? true : void 0
15661
+ };
15662
+ }
15663
+
15664
+ // src/registry/install.ts
15665
+ function forgesRoot(home) {
15666
+ return path18.join(home, ".forge-cli", "forges");
15667
+ }
15668
+ function forgeStatePath(home) {
15669
+ return path18.join(forgesRoot(home), "state.json");
15670
+ }
15671
+ function forgeVenv(home, name) {
15672
+ return path18.join(forgesRoot(home), name, "venv");
15673
+ }
15674
+ function receiptPath(home, name) {
15675
+ return path18.join(forgesRoot(home), name, "install.json");
15676
+ }
15677
+ function loadForgeState(home) {
15678
+ const file = forgeStatePath(home);
15679
+ if (!existsSync10(file)) return { forges: {} };
15680
+ try {
15681
+ const raw = JSON.parse(readFileSync9(file, "utf8"));
15682
+ if (typeof raw.forges !== "object" || !raw.forges || Array.isArray(raw.forges)) {
15683
+ return { forges: {} };
15684
+ }
15685
+ const forges = {};
15686
+ for (const [k, v] of Object.entries(raw.forges)) {
15687
+ if (v && typeof v === "object" && !Array.isArray(v)) forges[k] = v;
15688
+ }
15689
+ return { ...raw, forges };
15690
+ } catch {
15691
+ try {
15692
+ writeFileSync4(`${file}.corrupt`, readFileSync9(file), { flag: "wx" });
15693
+ } catch {
15694
+ }
15695
+ return { forges: {} };
15696
+ }
15697
+ }
15698
+ function ghostEntry(name, receipt) {
15699
+ return {
15700
+ name,
15701
+ dist: receipt.dist,
15702
+ bin: path18.basename(receipt.bin).replace(/\.exe$/i, ""),
15703
+ minPython: receipt.python.split(".").slice(0, 2).join("."),
15704
+ origin: "official"
15705
+ };
15706
+ }
15707
+ function saveForgeState(home, state) {
15708
+ mkdirSync4(forgesRoot(home), { recursive: true });
15709
+ if (process.platform !== "win32") {
15710
+ try {
15711
+ chmodSync2(forgesRoot(home), 448);
15712
+ chmodSync2(path18.dirname(forgesRoot(home)), 448);
15713
+ } catch {
15714
+ }
15715
+ }
15716
+ writeFileAtomic(forgeStatePath(home), `${JSON.stringify(state, null, 2)}
15717
+ `);
15718
+ }
15719
+ function wheelCacheDir(cacheDir, dist) {
15720
+ return path18.join(path18.dirname(cacheDir), "wheels", dist);
15721
+ }
15722
+ function venvBin(venvDir, platform2 = process.platform) {
15723
+ return platform2 === "win32" ? path18.join(venvDir, "Scripts") : path18.join(venvDir, "bin");
15724
+ }
15725
+ function venvPython(venvDir, platform2 = process.platform) {
15726
+ return path18.join(venvBin(venvDir, platform2), platform2 === "win32" ? "python.exe" : "python");
15727
+ }
15728
+ async function execaRun(cmd, args, opts = {}) {
15729
+ const res = await execa(cmd, args, {
15730
+ reject: false,
15731
+ timeout: opts.timeoutMs ?? 3e5
15732
+ });
15733
+ return {
15734
+ exitCode: res.exitCode ?? 1,
15735
+ stdout: res.stdout,
15736
+ // Timeout preserva o stderr real quando o processo já escreveu algo.
15737
+ stderr: res.timedOut ? `timeout ap\xF3s ${opts.timeoutMs ?? 3e5}ms${res.stderr ? `: ${res.stderr}` : ""}` : res.stderr
15738
+ };
15739
+ }
15740
+ function selectInterpreter(entry2, resolved, pythons) {
15741
+ const floor = parseVersion2(entry2.minPython);
15742
+ let sawUnknownSpec = false;
15743
+ const usable = pythons.filter((p) => {
15744
+ if (floor && compareVersions3(p.parsed, floor) < 0) return false;
15745
+ if (!resolved.requiresPython) return true;
15746
+ const sat = satisfiesRequiresPython(resolved.requiresPython, p.parsed);
15747
+ if (sat === null) {
15748
+ sawUnknownSpec = true;
15749
+ return false;
15750
+ }
15751
+ return sat;
15752
+ });
15753
+ usable.sort((a3, b) => compareVersions3(b.parsed, a3.parsed));
15754
+ const stable = usable.filter((p) => !isPrerelease(p.parsed));
15755
+ const candidates = stable.length ? stable : usable;
15756
+ if (!candidates.length) {
15757
+ return {
15758
+ error: sawUnknownSpec ? {
15759
+ kind: "specifier-unknown",
15760
+ dist: entry2.dist,
15761
+ version: resolved.version,
15762
+ requires: resolved.requiresPython
15763
+ } : {
15764
+ kind: "python-incompatible",
15765
+ dist: entry2.dist,
15766
+ version: resolved.version,
15767
+ requires: [resolved.requiresPython, `>=${entry2.minPython} (piso forge-cli)`].filter(Boolean).join(" e "),
15768
+ have: pythons.map((p) => p.version).join(", ")
15769
+ }
15770
+ };
15771
+ }
15772
+ return { python: candidates[0] };
15773
+ }
15774
+ function checkDistHashes(distFiles, wheels) {
15775
+ const have = existsSync10(wheels) ? readdirSync3(wheels) : [];
15776
+ let verifiedHash;
15777
+ let verifiedFile;
15778
+ for (const f of distFiles) {
15779
+ if (!have.includes(f.filename) || !f.sha256) continue;
15780
+ const digest = createHash("sha256").update(readFileSync9(path18.join(wheels, f.filename))).digest("hex");
15781
+ if (digest !== f.sha256) {
15782
+ const culprit = path18.join(wheels, f.filename);
15783
+ try {
15784
+ rmSync2(`${culprit}.rejected`, { force: true });
15785
+ renameSync2(culprit, `${culprit}.rejected`);
15786
+ } catch {
15787
+ }
15788
+ return {
15789
+ detail: `${f.filename}: sha256 ${digest.slice(0, 12)}\u2026 != PyPI ${f.sha256.slice(0, 12)}\u2026 \u2014 artefato movido para ${f.filename}.rejected`
15790
+ };
15791
+ }
15792
+ if (f.packagetype === "bdist_wheel" || verifiedFile === void 0) {
15793
+ verifiedFile = f.filename;
15794
+ verifiedHash = digest;
15795
+ }
15796
+ }
15797
+ return { verifiedHash, verifiedFile };
15798
+ }
15799
+ function distArtifactFiles(files, dist) {
15800
+ const norm2 = dist.toLowerCase();
15801
+ const prefixes = [`${norm2.replace(/[-_.]/g, "_")}-`, `${norm2.replace(/[-_.]/g, "-")}-`];
15802
+ return files.filter((f) => prefixes.some((p) => f.filename.toLowerCase().startsWith(p)));
15803
+ }
15804
+ async function registryInstall(entry2, pin, deps) {
15805
+ const run = deps.run ?? execaRun;
15806
+ const platform2 = deps.platform ?? process.platform;
15807
+ if (!deps.pythons.length) {
15808
+ return { stage: "no-python", detail: "nenhum interpretador Python detectado" };
15809
+ }
15810
+ const venvDir = forgeVenv(deps.home, entry2.name);
15811
+ if (!deps.clean && existsSync10(venvDir)) {
15812
+ return {
15813
+ stage: "already-installed",
15814
+ detail: `${entry2.name}: j\xE1 instalado em ${venvDir} \u2014 reinstale com --clean`
15815
+ };
15816
+ }
15817
+ const resolved = await resolveRelease(entry2.dist, {
15818
+ pin,
15819
+ offline: deps.offline,
15820
+ cacheDir: deps.cacheDir,
15821
+ fetchImpl: deps.fetchImpl
15822
+ });
15823
+ if ("kind" in resolved) return { stage: "resolve", error: resolved };
15824
+ const picked = selectInterpreter(entry2, resolved, deps.pythons);
15825
+ if ("error" in picked) return { stage: "resolve", error: picked.error };
15826
+ const python = picked.python;
15827
+ if (deps.clean) {
15828
+ rmSync2(path18.join(forgesRoot(deps.home), entry2.name), { recursive: true, force: true });
15829
+ const st = loadForgeState(deps.home);
15830
+ if (st.forges[entry2.name]) {
15831
+ delete st.forges[entry2.name];
15832
+ saveForgeState(deps.home, st);
15833
+ }
15834
+ }
15835
+ const wheels = wheelCacheDir(deps.cacheDir, entry2.dist);
15836
+ const forgeDir = path18.join(forgesRoot(deps.home), entry2.name);
15837
+ const cleanup = () => {
15838
+ rmSync2(venvDir, { recursive: true, force: true });
15839
+ if (existsSync10(forgeDir) && !readdirSync3(forgeDir).length) {
15840
+ rmSync2(forgeDir, { recursive: true, force: true });
15841
+ }
15842
+ };
15843
+ const venv = await run(python.exe[0], [...python.exe.slice(1), "-m", "venv", venvDir]);
15844
+ if (venv.exitCode !== 0) {
15845
+ cleanup();
15846
+ return {
15847
+ stage: "venv",
15848
+ detail: redactSecrets(venv.stderr ?? venv.stdout ?? "falha ao criar venv")
15849
+ };
15850
+ }
15851
+ const vpy = venvPython(venvDir, platform2);
15852
+ const spec = `${entry2.dist}==${resolved.version}`;
15853
+ mkdirSync4(wheels, { recursive: true });
15854
+ const distFiles = distArtifactFiles(resolved.files, entry2.dist);
15855
+ let verifiedHash;
15856
+ let verifiedFile;
15857
+ if (deps.offline) {
15858
+ const have = existsSync10(wheels) ? readdirSync3(wheels) : [];
15859
+ if (!distFiles.length || !distFiles.some((f) => have.includes(f.filename))) {
15860
+ cleanup();
15861
+ return {
15862
+ stage: "offline-missing-artifact",
15863
+ detail: `${spec}: sem wheel/sdist em ${wheels} \u2014 instale online uma vez`
15864
+ };
15865
+ }
15866
+ const check = checkDistHashes(distFiles, wheels);
15867
+ if ("detail" in check) {
15868
+ cleanup();
15869
+ return { stage: "hash-mismatch", detail: check.detail };
15870
+ }
15871
+ verifiedHash = check.verifiedHash;
15872
+ verifiedFile = check.verifiedFile;
15873
+ } else {
15874
+ const dl = await run(vpy, [
15875
+ "-m",
15876
+ "pip",
15877
+ "download",
15878
+ "--disable-pip-version-check",
15879
+ "--no-input",
15880
+ "--dest",
15881
+ wheels,
15882
+ spec
15883
+ ]);
15884
+ if (dl.exitCode !== 0) {
15885
+ cleanup();
15886
+ return {
15887
+ stage: "download",
15888
+ // stderr do pip pode ecoar credenciais de PIP_INDEX_URL — redação
15889
+ // vale para stdout-JSON, --report e log humano.
15890
+ detail: redactSecrets((dl.stderr ?? dl.stdout ?? "falha no pip download").trim())
15891
+ };
15892
+ }
15893
+ const check = checkDistHashes(distFiles, wheels);
15894
+ if ("detail" in check) {
15895
+ cleanup();
15896
+ return { stage: "hash-mismatch", detail: check.detail };
15897
+ }
15898
+ verifiedHash = check.verifiedHash;
15899
+ verifiedFile = check.verifiedFile;
15900
+ }
15901
+ if (!verifiedFile) {
15902
+ cleanup();
15903
+ return {
15904
+ stage: "hash-mismatch",
15905
+ detail: `${spec}: nenhum artefato da dist com sha256 verific\xE1vel nos metadados`
15906
+ };
15907
+ }
15908
+ const pipArgs = [
15909
+ "-m",
15910
+ "pip",
15911
+ "install",
15912
+ "--disable-pip-version-check",
15913
+ "--no-input",
15914
+ // `--find-links` só no offline: online as dependências resolvem no
15915
+ // índice, não em wheels cacheadas potencialmente velhas/adulteradas.
15916
+ // Limite conhecido (§11.2): offline, deps transitivas resolvem por
15917
+ // nome no --find-links sem pin de hash.
15918
+ ...deps.offline ? ["--no-index", "--find-links", wheels] : [],
15919
+ // Caminho explícito do artefato verificado — pip não resolve por nome,
15920
+ // então um arquivo plantado com a mesma dist==versão nunca é escolhido.
15921
+ path18.join(wheels, verifiedFile)
15922
+ ];
15923
+ const pip = await run(vpy, pipArgs);
15924
+ if (pip.exitCode !== 0) {
15925
+ cleanup();
15926
+ return {
15927
+ stage: "pip",
15928
+ detail: redactSecrets((pip.stderr ?? pip.stdout ?? "falha no pip install").trim())
15929
+ };
15930
+ }
15931
+ const binPath = path18.join(
15932
+ venvBin(venvDir, platform2),
15933
+ platform2 === "win32" ? `${entry2.bin}.exe` : entry2.bin
15934
+ );
15935
+ const verifyCmd = [binPath, "--help"];
15936
+ const verify = await run(binPath, ["--help"]);
15937
+ const receipt = {
15938
+ forge: entry2.name,
15939
+ method: "pypi",
15940
+ dist: entry2.dist,
15941
+ version: resolved.version,
15942
+ requiresPython: resolved.requiresPython,
15943
+ python: python.version,
15944
+ source: resolved.source,
15945
+ venv: venvDir,
15946
+ bin: binPath,
15947
+ installedAt: (deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()))(),
15948
+ verify: { command: verifyCmd, ok: verify.exitCode === 0 },
15949
+ files: resolved.files.map((f) => ({
15950
+ filename: f.filename,
15951
+ sha256: f.sha256,
15952
+ packagetype: f.packagetype
15953
+ })),
15954
+ verifiedHash,
15955
+ provenance: {
15956
+ registry: "pypi.org",
15957
+ file: verifiedFile,
15958
+ url: resolved.files.find((f) => f.filename === verifiedFile)?.url,
15959
+ resolvedVia: deps.offline ? "offline-cache" : "pypi"
15960
+ },
15961
+ yanked: resolved.yanked,
15962
+ staleCache: resolved.staleCache
15963
+ };
15964
+ mkdirSync4(path18.dirname(receiptPath(deps.home, entry2.name)), { recursive: true });
15965
+ writeFileAtomic(receiptPath(deps.home, entry2.name), `${JSON.stringify(receipt, null, 2)}
15966
+ `);
15967
+ const state = loadForgeState(deps.home);
15968
+ state.forges[entry2.name] = receipt;
15969
+ saveForgeState(deps.home, state);
15970
+ if (verify.exitCode !== 0) {
15971
+ return { stage: "verify", detail: `${entry2.bin} --help saiu com c\xF3digo ${verify.exitCode}` };
15972
+ }
15973
+ return receipt;
15974
+ }
15975
+ function registryUninstall(target, deps) {
15976
+ const dir = resolveInside(forgesRoot(deps.home), target.name);
15977
+ if (!dir)
15978
+ return { removed: false, receiptRemoved: false, cacheRemoved: false, refused: "unsafe-name" };
15979
+ const removed = existsSync10(dir);
15980
+ if (removed) rmSync2(dir, { recursive: true, force: true });
15981
+ const wheels = target.dist && isSafeRepoName(target.dist) ? resolveInside(path18.join(deps.home, ".forge-cli", "cache", "wheels"), target.dist) : null;
15982
+ const cacheRemoved = wheels !== null && existsSync10(wheels);
15983
+ if (wheels) rmSync2(wheels, { recursive: true, force: true });
15984
+ const state = loadForgeState(deps.home);
15985
+ const hadReceipt = Object.hasOwn(state.forges, target.name);
15986
+ if (hadReceipt) delete state.forges[target.name];
15987
+ saveForgeState(deps.home, state);
15988
+ return { removed, receiptRemoved: hadReceipt, cacheRemoved };
15989
+ }
15990
+
15991
+ // src/compat.ts
15992
+ import { createHash as createHash2 } from "crypto";
15993
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
15994
+ import path19 from "path";
15266
15995
  var ECOSYSTEM_SCHEMA = "forge/EcosystemCompatibility/v1";
15267
15996
  function isRecord(v) {
15268
15997
  return typeof v === "object" && v !== null && !Array.isArray(v);
@@ -15349,10 +16078,10 @@ function manifestProblems(doc) {
15349
16078
  return problems;
15350
16079
  }
15351
16080
  function loadCompatManifest(manifestPath) {
15352
- if (!existsSync10(manifestPath)) return null;
16081
+ if (!existsSync11(manifestPath)) return null;
15353
16082
  let raw;
15354
16083
  try {
15355
- raw = readFileSync8(manifestPath, "utf-8");
16084
+ raw = readFileSync10(manifestPath, "utf-8");
15356
16085
  } catch {
15357
16086
  return null;
15358
16087
  }
@@ -15369,12 +16098,12 @@ function loadCompatManifest(manifestPath) {
15369
16098
  release: d.release,
15370
16099
  components: d.components,
15371
16100
  contracts: d.contracts,
15372
- fileSha256: createHash("sha256").update(raw).digest("hex"),
16101
+ fileSha256: createHash2("sha256").update(raw).digest("hex"),
15373
16102
  path: manifestPath
15374
16103
  };
15375
16104
  }
15376
16105
  function defaultManifestPath(workspaceDir) {
15377
- return path17.join(workspaceDir, "the-forge", "ecosystem-compatibility.json");
16106
+ return path19.join(workspaceDir, "the-forge", "ecosystem-compatibility.json");
15378
16107
  }
15379
16108
  function compatVerdict(manifest, component, version) {
15380
16109
  const entry2 = manifest.components.find((c4) => c4.component === component);
@@ -15382,21 +16111,21 @@ function compatVerdict(manifest, component, version) {
15382
16111
  return entry2.version === version ? "certified" : "incompatible";
15383
16112
  }
15384
16113
  function readDeclaredVersion(repoDir) {
15385
- const pkg = path17.join(repoDir, "package.json");
15386
- if (existsSync10(pkg)) {
16114
+ const pkg = path19.join(repoDir, "package.json");
16115
+ if (existsSync11(pkg)) {
15387
16116
  try {
15388
- const parsed = JSON.parse(readFileSync8(pkg, "utf-8"));
16117
+ const parsed = JSON.parse(readFileSync10(pkg, "utf-8"));
15389
16118
  if (isRecord(parsed) && typeof parsed.version === "string" && parsed.version) {
15390
16119
  return parsed.version;
15391
16120
  }
15392
16121
  } catch {
15393
16122
  }
15394
16123
  }
15395
- const pyproject = path17.join(repoDir, "pyproject.toml");
15396
- if (existsSync10(pyproject)) {
16124
+ const pyproject = path19.join(repoDir, "pyproject.toml");
16125
+ if (existsSync11(pyproject)) {
15397
16126
  try {
15398
16127
  const table = /^\[project\][^\n]*\n((?:(?!\[)[^\n]*(?:\n|$))*)/m.exec(
15399
- readFileSync8(pyproject, "utf-8")
16128
+ readFileSync10(pyproject, "utf-8")
15400
16129
  );
15401
16130
  const m3 = table && /^version\s*=\s*"([^"]+)"/m.exec(table[1]);
15402
16131
  if (m3) return m3[1];
@@ -15410,25 +16139,49 @@ function readDeclaredVersion(repoDir) {
15410
16139
  async function runListCommand(cli) {
15411
16140
  const targetDir = resolveTargetDir(cli.dir);
15412
16141
  const state = loadState(targetDir);
15413
- const body = catalogFor(targetDir).map((repo) => listLine(repo, state?.repos[repo.name])).join("\n");
16142
+ const registry = loadForgeState(os2.homedir()).forges;
16143
+ const body = catalogFor(targetDir).map((repo) => {
16144
+ const line = listLine(repo, state?.repos[repo.name]);
16145
+ const reg = registry[repo.name];
16146
+ return reg ? `${line} ${theme.dim(`+ pypi ${reg.dist}==${reg.version}`)}` : line;
16147
+ }).join("\n");
15414
16148
  if (cli.jsonReport) {
15415
16149
  emitJson(
15416
16150
  catalogFor(targetDir).map((repo) => ({
15417
16151
  name: repo.name,
15418
16152
  url: repo.url,
15419
16153
  description: repo.description,
15420
- last: state?.repos[repo.name] ?? null
16154
+ last: state?.repos[repo.name] ?? null,
16155
+ registry: registry[repo.name] ? { dist: registry[repo.name].dist, version: registry[repo.name].version } : null
15421
16156
  }))
15422
16157
  );
15423
16158
  }
15424
16159
  note(body, S.listTitle);
15425
16160
  process.exit(0);
15426
16161
  }
16162
+ function mergeRegistryPhase(infos, registry) {
16163
+ for (const i3 of infos) {
16164
+ const reg = Object.hasOwn(registry, i3.name) ? registry[i3.name] : void 0;
16165
+ if (!reg || typeof reg !== "object") continue;
16166
+ const tag2 = `pypi ${reg.dist ?? "?"}==${reg.version ?? "?"}`;
16167
+ const binMissing = typeof reg.bin !== "string" || !existsSync12(reg.bin);
16168
+ if (binMissing) {
16169
+ if (i3.phase === "not-installed" || i3.phase === "installed") i3.phase = "drifted";
16170
+ i3.describe = [i3.describe, `${tag2} (bin ausente)`].filter(Boolean).join(" + ");
16171
+ } else if (i3.phase === "not-installed") {
16172
+ i3.phase = "installed";
16173
+ i3.describe = `${tag2}${reg.verify?.ok ? "" : " (verify falhou)"}`;
16174
+ } else {
16175
+ i3.describe = [i3.describe, tag2].filter(Boolean).join(" + ");
16176
+ }
16177
+ }
16178
+ }
15427
16179
  async function runStatusCommand(cli, names) {
15428
16180
  const targetDir = resolveTargetDir(cli.dir);
15429
16181
  const catalog = catalogFor(targetDir);
15430
16182
  const wanted = names.length ? catalog.filter((r2) => names.includes(r2.name)) : catalog;
15431
16183
  const infos = await collectStatus(wanted, targetDir, loadState(targetDir));
16184
+ mergeRegistryPhase(infos, loadForgeState(os2.homedir()).forges);
15432
16185
  const label = {
15433
16186
  "not-installed": S.statusNotInstalled,
15434
16187
  cloned: S.statusCloned,
@@ -15477,7 +16230,8 @@ async function runUpdateCheckCommand(cli, names) {
15477
16230
  const compat = compatOf(repo.name, dir);
15478
16231
  const check = await updateCheck(dir);
15479
16232
  if (check.failure) {
15480
- lines.push(`${repo.name} \u2014 ${theme.warn(`${S.updateCheckFailed} (${check.failure})`)}`);
16233
+ const label = check.failure === "no-upstream" ? S.updateNoUpstream : `${S.updateCheckFailed} (${check.failure})`;
16234
+ lines.push(`${repo.name} \u2014 ${theme.warn(label)}`);
15481
16235
  rows.push({ name: repo.name, state: "unknown", behind: 0, ahead: 0, failure: check.failure });
15482
16236
  } else if (check.behind > 0) {
15483
16237
  pending++;
@@ -15548,21 +16302,21 @@ async function runVersionsCommand(cli, names) {
15548
16302
  }
15549
16303
 
15550
16304
  // src/doctor2/index.ts
15551
- import os3 from "os";
15552
- import path28 from "path";
16305
+ import os5 from "os";
16306
+ import path30 from "path";
15553
16307
 
15554
16308
  // src/hosts/claude.ts
15555
- import path19 from "path";
16309
+ import path21 from "path";
15556
16310
 
15557
16311
  // src/hosts/types.ts
15558
- import { copyFileSync as copyFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync9, readdirSync as readdirSync3, rmSync as rmSync2 } from "fs";
15559
- import os from "os";
15560
- import path18 from "path";
16312
+ import { copyFileSync as copyFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync11, readdirSync as readdirSync4, rmSync as rmSync3 } from "fs";
16313
+ import os3 from "os";
16314
+ import path20 from "path";
15561
16315
  var defaultProbe = {
15562
16316
  output: (command, args) => commandOutput(command, args)
15563
16317
  };
15564
16318
  function hostHome(home) {
15565
- return home || os.homedir();
16319
+ return home || os3.homedir();
15566
16320
  }
15567
16321
  function parseVersionOutput(out) {
15568
16322
  if (!out) return null;
@@ -15572,7 +16326,7 @@ function parseVersionOutput(out) {
15572
16326
  return first && first !== "ok" ? first : null;
15573
16327
  }
15574
16328
  function existingConfigPaths(home, rels) {
15575
- return rels.map((rel2) => path18.join(home, rel2)).filter((p) => existsSync11(p));
16329
+ return rels.map((rel2) => path20.join(home, rel2)).filter((p) => existsSync13(p));
15576
16330
  }
15577
16331
  async function detectHost(home, spec) {
15578
16332
  let binary = false;
@@ -15615,9 +16369,9 @@ async function detectHost(home, spec) {
15615
16369
  };
15616
16370
  }
15617
16371
  function readJsonObject(file) {
15618
- if (!existsSync11(file)) return null;
16372
+ if (!existsSync13(file)) return null;
15619
16373
  try {
15620
- const parsed = JSON.parse(readFileSync9(file, "utf8"));
16374
+ const parsed = JSON.parse(readFileSync11(file, "utf8"));
15621
16375
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : "invalid";
15622
16376
  } catch {
15623
16377
  return "invalid";
@@ -15653,22 +16407,22 @@ function backupTimestamp(d = /* @__PURE__ */ new Date()) {
15653
16407
  return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}${p(d.getMilliseconds(), 3)}`;
15654
16408
  }
15655
16409
  function backupsRoot(home) {
15656
- return path18.join(artifactsRoot(home), "backups");
16410
+ return path20.join(artifactsRoot(home), "backups");
15657
16411
  }
15658
16412
  function backupDirFor(home, hostId) {
15659
- return path18.join(backupsRoot(home), `hosts-${hostId}-${backupTimestamp()}`);
16413
+ return path20.join(backupsRoot(home), `hosts-${hostId}-${backupTimestamp()}`);
15660
16414
  }
15661
16415
  function backupTargets(dir, hostId, targets) {
15662
- mkdirSync3(dir, { recursive: true });
16416
+ mkdirSync5(dir, { recursive: true });
15663
16417
  const files = targets.map((target) => {
15664
- const name = path18.basename(target);
15665
- const absent = !existsSync11(target);
15666
- if (absent) writeFileAtomic(path18.join(dir, `${name}.absent`), "");
15667
- else copyFileSync2(target, path18.join(dir, name));
16418
+ const name = path20.basename(target);
16419
+ const absent = !existsSync13(target);
16420
+ if (absent) writeFileAtomic(path20.join(dir, `${name}.absent`), "");
16421
+ else copyFileSync2(target, path20.join(dir, name));
15668
16422
  return { backup: name, target, absent };
15669
16423
  });
15670
16424
  writeFileAtomic(
15671
- path18.join(dir, "manifest.json"),
16425
+ path20.join(dir, "manifest.json"),
15672
16426
  `${JSON.stringify({ host: hostId, files }, null, 2)}
15673
16427
  `
15674
16428
  );
@@ -15676,23 +16430,23 @@ function backupTargets(dir, hostId, targets) {
15676
16430
  function latestBackupDir(home, hostId) {
15677
16431
  let names;
15678
16432
  try {
15679
- names = readdirSync3(backupsRoot(home));
16433
+ names = readdirSync4(backupsRoot(home));
15680
16434
  } catch {
15681
16435
  return null;
15682
16436
  }
15683
16437
  const hit = names.filter((n4) => n4.startsWith(`hosts-${hostId}-`)).sort().reverse()[0];
15684
- return hit ? path18.join(backupsRoot(home), hit) : null;
16438
+ return hit ? path20.join(backupsRoot(home), hit) : null;
15685
16439
  }
15686
16440
  function restoreBackupDir(dir) {
15687
- const manifest = JSON.parse(readFileSync9(path18.join(dir, "manifest.json"), "utf8"));
16441
+ const manifest = JSON.parse(readFileSync11(path20.join(dir, "manifest.json"), "utf8"));
15688
16442
  const restored = [];
15689
16443
  const removed = [];
15690
16444
  for (const entry2 of manifest.files ?? []) {
15691
16445
  if (entry2.absent) {
15692
- rmSync2(entry2.target, { force: true });
16446
+ rmSync3(entry2.target, { force: true });
15693
16447
  removed.push(entry2.target);
15694
16448
  } else {
15695
- writeFileAtomic(entry2.target, readFileSync9(path18.join(dir, entry2.backup), "utf8"));
16449
+ writeFileAtomic(entry2.target, readFileSync11(path20.join(dir, entry2.backup), "utf8"));
15696
16450
  restored.push(entry2.target);
15697
16451
  }
15698
16452
  }
@@ -15741,7 +16495,7 @@ async function applyHostPlan(home, plan, resolveWrite, opts = {}) {
15741
16495
  );
15742
16496
  for (const w of writes) {
15743
16497
  try {
15744
- mkdirSync3(path18.dirname(w.target), { recursive: true });
16498
+ mkdirSync5(path20.dirname(w.target), { recursive: true });
15745
16499
  writeFileAtomic(w.target, w.content);
15746
16500
  applied.push(w.target);
15747
16501
  } catch (error) {
@@ -15788,9 +16542,9 @@ async function rollbackHostPlan(home, plan, backup) {
15788
16542
 
15789
16543
  // src/hosts/claude.ts
15790
16544
  var CONFIG_FILE = ".claude.json";
15791
- var CONFIG_REL = [CONFIG_FILE, ".claude", path19.join(".claude", "settings.json")];
16545
+ var CONFIG_REL = [CONFIG_FILE, ".claude", path21.join(".claude", "settings.json")];
15792
16546
  function targetFor(home) {
15793
- return path19.join(home, CONFIG_FILE);
16547
+ return path21.join(home, CONFIG_FILE);
15794
16548
  }
15795
16549
  function createClaudeAdapter(probe = defaultProbe) {
15796
16550
  return {
@@ -15858,12 +16612,12 @@ function createClaudeAdapter(probe = defaultProbe) {
15858
16612
  var claudeAdapter = createClaudeAdapter();
15859
16613
 
15860
16614
  // src/hosts/codex.ts
15861
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
15862
- import path20 from "path";
15863
- var CONFIG_FILE2 = path20.join(".codex", "config.toml");
16615
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
16616
+ import path22 from "path";
16617
+ var CONFIG_FILE2 = path22.join(".codex", "config.toml");
15864
16618
  var CONFIG_REL2 = [CONFIG_FILE2, ".codex"];
15865
16619
  function targetFor2(home) {
15866
- return path20.join(home, CONFIG_FILE2);
16620
+ return path22.join(home, CONFIG_FILE2);
15867
16621
  }
15868
16622
  function escapeRe(s) {
15869
16623
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -15929,7 +16683,7 @@ function createCodexAdapter(probe = defaultProbe) {
15929
16683
  authArgv: ["codex", "login", "status"],
15930
16684
  hasIntegration: (dir) => {
15931
16685
  const file = targetFor2(dir);
15932
- return existsSync12(file) && hasAnyCodexMcpSection(readFileSync10(file, "utf8"));
16686
+ return existsSync14(file) && hasAnyCodexMcpSection(readFileSync12(file, "utf8"));
15933
16687
  }
15934
16688
  });
15935
16689
  },
@@ -15938,7 +16692,7 @@ function createCodexAdapter(probe = defaultProbe) {
15938
16692
  if (!argv?.length) return null;
15939
16693
  const h3 = hostHome(home);
15940
16694
  const target = targetFor2(h3);
15941
- const existing = existsSync12(target) ? readFileSync10(target, "utf8") : null;
16695
+ const existing = existsSync14(target) ? readFileSync12(target, "utf8") : null;
15942
16696
  if (existing !== null && hasCodexMcpSection(existing, forge.forge_id)) {
15943
16697
  return {
15944
16698
  host: "codex",
@@ -15969,7 +16723,7 @@ function createCodexAdapter(probe = defaultProbe) {
15969
16723
  plan,
15970
16724
  (action) => {
15971
16725
  if (!action.target || !action.argv?.length) return "skip";
15972
- const existing = existsSync12(action.target) ? readFileSync10(action.target, "utf8") : null;
16726
+ const existing = existsSync14(action.target) ? readFileSync12(action.target, "utf8") : null;
15973
16727
  if (existing !== null && hasCodexMcpSection(existing, plan.forge_id)) {
15974
16728
  return "duplicate";
15975
16729
  }
@@ -15986,13 +16740,13 @@ ${block2}
15986
16740
  validateIntegration: async (forge_id, home) => {
15987
16741
  const file = targetFor2(hostHome(home));
15988
16742
  const checks = [];
15989
- if (!existsSync12(file)) {
16743
+ if (!existsSync14(file)) {
15990
16744
  checks.push({ id: "config-file", result: "fail", detail: "absent" });
15991
16745
  checks.push({ id: "mcp-section", result: "fail", detail: forge_id });
15992
16746
  return { ok: false, checks };
15993
16747
  }
15994
16748
  checks.push({ id: "config-file", result: "pass", detail: file });
15995
- const content = readFileSync10(file, "utf8");
16749
+ const content = readFileSync12(file, "utf8");
15996
16750
  if (!hasCodexMcpSection(content, forge_id)) {
15997
16751
  checks.push({ id: "mcp-section", result: "fail", detail: forge_id });
15998
16752
  return { ok: false, checks };
@@ -16013,12 +16767,12 @@ ${block2}
16013
16767
  var codexAdapter = createCodexAdapter();
16014
16768
 
16015
16769
  // src/hosts/copilot.ts
16016
- import path21 from "path";
16017
- import { existsSync as existsSync13 } from "fs";
16018
- var CONFIG_FILE3 = path21.join(".copilot", "mcp-config.json");
16019
- var CONFIG_REL3 = [".copilot", CONFIG_FILE3, path21.join(".config", "gh")];
16770
+ import path23 from "path";
16771
+ import { existsSync as existsSync15 } from "fs";
16772
+ var CONFIG_FILE3 = path23.join(".copilot", "mcp-config.json");
16773
+ var CONFIG_REL3 = [".copilot", CONFIG_FILE3, path23.join(".config", "gh")];
16020
16774
  function targetFor3(home) {
16021
- return path21.join(home, CONFIG_FILE3);
16775
+ return path23.join(home, CONFIG_FILE3);
16022
16776
  }
16023
16777
  function createCopilotAdapter(probe = defaultProbe) {
16024
16778
  return {
@@ -16042,7 +16796,7 @@ function createCopilotAdapter(probe = defaultProbe) {
16042
16796
  if (!argv?.length) return null;
16043
16797
  const h3 = hostHome(home);
16044
16798
  const target = targetFor3(h3);
16045
- if (!existsSync13(target)) return null;
16799
+ if (!existsSync15(target)) return null;
16046
16800
  if (hasMcpEntry(target, forge.forge_id)) {
16047
16801
  return {
16048
16802
  host: "copilot",
@@ -16088,11 +16842,11 @@ function createCopilotAdapter(probe = defaultProbe) {
16088
16842
  var copilotAdapter = createCopilotAdapter();
16089
16843
 
16090
16844
  // src/hosts/cursor.ts
16091
- import path22 from "path";
16092
- var CONFIG_FILE4 = path22.join(".cursor", "mcp.json");
16845
+ import path24 from "path";
16846
+ var CONFIG_FILE4 = path24.join(".cursor", "mcp.json");
16093
16847
  var CONFIG_REL4 = [CONFIG_FILE4, ".cursor"];
16094
16848
  function targetFor4(home) {
16095
- return path22.join(home, CONFIG_FILE4);
16849
+ return path24.join(home, CONFIG_FILE4);
16096
16850
  }
16097
16851
  function createCursorAdapter(probe = defaultProbe) {
16098
16852
  return {
@@ -16158,8 +16912,8 @@ function createCursorAdapter(probe = defaultProbe) {
16158
16912
  var cursorAdapter = createCursorAdapter();
16159
16913
 
16160
16914
  // src/hosts/devin.ts
16161
- import path23 from "path";
16162
- var CONFIG_REL5 = [".devin", path23.join(".config", "devin")];
16915
+ import path25 from "path";
16916
+ var CONFIG_REL5 = [".devin", path25.join(".config", "devin")];
16163
16917
  function createDevinAdapter(probe = defaultProbe) {
16164
16918
  return {
16165
16919
  id: "devin",
@@ -16210,7 +16964,7 @@ async function detectAll(home) {
16210
16964
  }
16211
16965
 
16212
16966
  // src/doctor2/context.ts
16213
- import { readdirSync as readdirSync4, readFileSync as readFileSync11 } from "fs";
16967
+ import { readdirSync as readdirSync5, readFileSync as readFileSync13 } from "fs";
16214
16968
  var DEFAULT_MAX_RECEIPTS = 20;
16215
16969
  var DEFAULT_MAX_ENTRIES = 50;
16216
16970
  function mkCheck(now, check) {
@@ -16218,14 +16972,14 @@ function mkCheck(now, check) {
16218
16972
  }
16219
16973
  function listDir(dir) {
16220
16974
  try {
16221
- return readdirSync4(dir).sort();
16975
+ return readdirSync5(dir).sort();
16222
16976
  } catch {
16223
16977
  return null;
16224
16978
  }
16225
16979
  }
16226
16980
  function readJson(file) {
16227
16981
  try {
16228
- return { ok: true, value: JSON.parse(readFileSync11(file, "utf8")) };
16982
+ return { ok: true, value: JSON.parse(readFileSync13(file, "utf8")) };
16229
16983
  } catch (error) {
16230
16984
  return { ok: false, error: errMsg(error) };
16231
16985
  }
@@ -16240,13 +16994,13 @@ function errMsg(error) {
16240
16994
  }
16241
16995
 
16242
16996
  // src/doctor2/domains.ts
16243
- import { existsSync as existsSync17, readdirSync as readdirSync6, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
16244
- import os2 from "os";
16245
- import path27 from "path";
16997
+ import { existsSync as existsSync19, readdirSync as readdirSync7, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
16998
+ import os4 from "os";
16999
+ import path29 from "path";
16246
17000
 
16247
17001
  // src/envintel/validate.ts
16248
- import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
16249
- import path25 from "path";
17002
+ import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
17003
+ import path27 from "path";
16250
17004
 
16251
17005
  // src/envintel/parse.ts
16252
17006
  var ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -16332,8 +17086,8 @@ function formatEntry(key, value) {
16332
17086
  }
16333
17087
 
16334
17088
  // src/envintel/schema.ts
16335
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
16336
- import path24 from "path";
17089
+ import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
17090
+ import path26 from "path";
16337
17091
  var SECRET_RE = /KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL/;
16338
17092
  var KIND_RULES = [
16339
17093
  { kind: "port", re: /(^|_)PORT($|_)/ },
@@ -16380,10 +17134,10 @@ function validPattern(p) {
16380
17134
  }
16381
17135
  }
16382
17136
  function readSchemaFile(repoDir) {
16383
- const file = path24.join(repoDir, "forge.env.schema.json");
16384
- if (!existsSync14(file)) return [];
17137
+ const file = path26.join(repoDir, "forge.env.schema.json");
17138
+ if (!existsSync16(file)) return [];
16385
17139
  try {
16386
- const parsed = JSON.parse(readFileSync12(file, "utf8"));
17140
+ const parsed = JSON.parse(readFileSync14(file, "utf8"));
16387
17141
  const list = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.vars) ? parsed.vars : parsed && typeof parsed === "object" ? Object.entries(parsed).map(([key, v]) => ({
16388
17142
  key,
16389
17143
  ...v && typeof v === "object" ? v : {}
@@ -16395,9 +17149,9 @@ function readSchemaFile(repoDir) {
16395
17149
  }
16396
17150
  function deriveSchema(repoDir, envKeys) {
16397
17151
  const specs = /* @__PURE__ */ new Map();
16398
- const exampleFile = path24.join(repoDir, ".env.example");
16399
- if (existsSync14(exampleFile)) {
16400
- for (const entry2 of parseEnv(readFileSync12(exampleFile, "utf8")).entries) {
17152
+ const exampleFile = path26.join(repoDir, ".env.example");
17153
+ if (existsSync16(exampleFile)) {
17154
+ for (const entry2 of parseEnv(readFileSync14(exampleFile, "utf8")).entries) {
16401
17155
  if (specs.has(entry2.key)) continue;
16402
17156
  const kind = inferKind(entry2.key, entry2.value || void 0);
16403
17157
  const secret = kind === "secret";
@@ -16460,8 +17214,8 @@ function checkValue(spec, value, opts = {}) {
16460
17214
  case "path": {
16461
17215
  if (!value.trim()) return false;
16462
17216
  if (!opts.checkPaths) return true;
16463
- const p = path25.isAbsolute(value) ? value : path25.resolve(opts.baseDir ?? ".", value);
16464
- return existsSync15(p);
17217
+ const p = path27.isAbsolute(value) ? value : path27.resolve(opts.baseDir ?? ".", value);
17218
+ return existsSync17(p);
16465
17219
  }
16466
17220
  case "bool":
16467
17221
  return /^(true|false|0|1|yes|no)$/i.test(value.trim());
@@ -16512,9 +17266,9 @@ function analyzeKey(key, spec, entries, opts) {
16512
17266
  }
16513
17267
  function analyzeEnv(repoDir, envKeys, opts = {}) {
16514
17268
  const schema = deriveSchema(repoDir, envKeys);
16515
- const envPath = path25.join(repoDir, ".env");
17269
+ const envPath = path27.join(repoDir, ".env");
16516
17270
  const specByKey = new Map(schema.vars.map((v) => [v.key, v]));
16517
- const envFile = existsSync15(envPath) ? parseEnv(readFileSync13(envPath, "utf8")) : null;
17271
+ const envFile = existsSync17(envPath) ? parseEnv(readFileSync15(envPath, "utf8")) : null;
16518
17272
  const findings = [];
16519
17273
  const vars = [];
16520
17274
  const byKey = /* @__PURE__ */ new Map();
@@ -16595,9 +17349,9 @@ function inspectEnv(repoDir, envKeys) {
16595
17349
  }
16596
17350
 
16597
17351
  // src/maintenance.ts
16598
- import { existsSync as existsSync16, mkdirSync as mkdirSync4, readFileSync as readFileSync14, readdirSync as readdirSync5 } from "fs";
16599
- import { createHash as createHash2 } from "crypto";
16600
- import path26 from "path";
17352
+ import { existsSync as existsSync18, mkdirSync as mkdirSync6, readFileSync as readFileSync16, readdirSync as readdirSync6 } from "fs";
17353
+ import { createHash as createHash3 } from "crypto";
17354
+ import path28 from "path";
16601
17355
  function envLockDigest(repoDir) {
16602
17356
  const names = [
16603
17357
  ".env.example",
@@ -16607,12 +17361,12 @@ function envLockDigest(repoDir) {
16607
17361
  "pyproject.toml"
16608
17362
  ];
16609
17363
  const files = [];
16610
- const hash = createHash2("sha256");
17364
+ const hash = createHash3("sha256");
16611
17365
  for (const name of names) {
16612
- const file = path26.join(repoDir, name);
16613
- if (!existsSync16(file)) continue;
17366
+ const file = path28.join(repoDir, name);
17367
+ if (!existsSync18(file)) continue;
16614
17368
  files.push(name);
16615
- const content = readFileSync14(file);
17369
+ const content = readFileSync16(file);
16616
17370
  hash.update(name).update("\0").update(content).update("\0");
16617
17371
  }
16618
17372
  return { files, digest: hash.digest("hex") };
@@ -16624,31 +17378,31 @@ function writeEnvLock(targetDir, repos) {
16624
17378
  forges: {}
16625
17379
  };
16626
17380
  for (const repo of repos) {
16627
- const dir = path26.join(targetDir, repo.name);
16628
- if (!existsSync16(dir)) continue;
17381
+ const dir = path28.join(targetDir, repo.name);
17382
+ if (!existsSync18(dir)) continue;
16629
17383
  const { files, digest } = envLockDigest(dir);
16630
17384
  const head = loadState(targetDir)?.repos[repo.name]?.head;
16631
17385
  lock.forges[repo.name] = { env_files: files, digest, ...head ? { head } : {} };
16632
17386
  }
16633
- const file = path26.join(artifactsRoot(targetDir), "env.lock.json");
16634
- mkdirSync4(path26.dirname(file), { recursive: true });
17387
+ const file = path28.join(artifactsRoot(targetDir), "env.lock.json");
17388
+ mkdirSync6(path28.dirname(file), { recursive: true });
16635
17389
  writeFileAtomic(file, JSON.stringify(lock, null, 2));
16636
17390
  return lock;
16637
17391
  }
16638
17392
  function checkEnvLock(targetDir, repos) {
16639
- const file = path26.join(artifactsRoot(targetDir), "env.lock.json");
16640
- if (!existsSync16(file)) return repos.map((r2) => ({ forge: r2.name, state: "untracked" }));
17393
+ const file = path28.join(artifactsRoot(targetDir), "env.lock.json");
17394
+ if (!existsSync18(file)) return repos.map((r2) => ({ forge: r2.name, state: "untracked" }));
16641
17395
  let lock;
16642
17396
  try {
16643
- lock = JSON.parse(readFileSync14(file, "utf8"));
17397
+ lock = JSON.parse(readFileSync16(file, "utf8"));
16644
17398
  } catch {
16645
17399
  return repos.map((r2) => ({ forge: r2.name, state: "untracked" }));
16646
17400
  }
16647
17401
  return repos.map((repo) => {
16648
17402
  const entry2 = lock.forges?.[repo.name];
16649
17403
  if (!entry2) return { forge: repo.name, state: "untracked" };
16650
- const dir = path26.join(targetDir, repo.name);
16651
- if (!existsSync16(dir)) return { forge: repo.name, state: "absent" };
17404
+ const dir = path28.join(targetDir, repo.name);
17405
+ if (!existsSync18(dir)) return { forge: repo.name, state: "absent" };
16652
17406
  const { digest } = envLockDigest(dir);
16653
17407
  return { forge: repo.name, state: digest === entry2.digest ? "match" : "drifted" };
16654
17408
  });
@@ -16666,13 +17420,13 @@ function exportProfile(targetDir, dest) {
16666
17420
  ])
16667
17421
  )
16668
17422
  };
16669
- mkdirSync4(path26.dirname(path26.resolve(dest)), { recursive: true });
17423
+ mkdirSync6(path28.dirname(path28.resolve(dest)), { recursive: true });
16670
17424
  writeFileAtomic(dest, JSON.stringify(profile, null, 2));
16671
17425
  return profile;
16672
17426
  }
16673
17427
  function importProfile(file) {
16674
17428
  try {
16675
- const parsed = JSON.parse(readFileSync14(file, "utf8"));
17429
+ const parsed = JSON.parse(readFileSync16(file, "utf8"));
16676
17430
  if (parsed?.schema !== "forge-cli/Profile/v1" || typeof parsed.repos !== "object") {
16677
17431
  return { error: "invalid-profile" };
16678
17432
  }
@@ -16750,7 +17504,7 @@ function systemChecks(ctx) {
16750
17504
  result: "pass",
16751
17505
  severity: "info",
16752
17506
  diagnosis: "plataforma detectada",
16753
- evidence: [`platform=${process.platform}`, `arch=${os2.arch()}`, `release=${os2.release()}`],
17507
+ evidence: [`platform=${process.platform}`, `arch=${os4.arch()}`, `release=${os4.release()}`],
16754
17508
  reversible: null
16755
17509
  })
16756
17510
  );
@@ -16760,23 +17514,23 @@ function systemChecks(ctx) {
16760
17514
  let readEvidence;
16761
17515
  let writeEvidence = "write=n\xE3o sondado (leitura falhou)";
16762
17516
  try {
16763
- readdirSync6(dir);
17517
+ readdirSync7(dir);
16764
17518
  readable2 = true;
16765
17519
  readEvidence = "read=ok";
16766
17520
  } catch (error) {
16767
17521
  readEvidence = `read=falha (${errMsg(error)})`;
16768
17522
  }
16769
17523
  if (readable2) {
16770
- const probe = path27.join(dir, `.forge-doctor2-probe-${process.pid}`);
17524
+ const probe = path29.join(dir, `.forge-doctor2-probe-${process.pid}`);
16771
17525
  try {
16772
- writeFileSync4(probe, "probe\n", "utf8");
16773
- rmSync3(probe, { force: true });
17526
+ writeFileSync5(probe, "probe\n", "utf8");
17527
+ rmSync4(probe, { force: true });
16774
17528
  writable2 = true;
16775
17529
  writeEvidence = "write=ok";
16776
17530
  } catch (error) {
16777
17531
  writeEvidence = `write=falha (${errMsg(error)})`;
16778
17532
  try {
16779
- rmSync3(probe, { force: true });
17533
+ rmSync4(probe, { force: true });
16780
17534
  } catch {
16781
17535
  }
16782
17536
  }
@@ -16797,12 +17551,12 @@ function systemChecks(ctx) {
16797
17551
  );
16798
17552
  const required = [
16799
17553
  { label: "home", dir: ctx.home },
16800
- { label: "tmpdir", dir: os2.tmpdir() }
17554
+ { label: "tmpdir", dir: os4.tmpdir() }
16801
17555
  ];
16802
17556
  const dirEvidence = [];
16803
17557
  let dirFailed = 0;
16804
17558
  for (const { label, dir: d } of required) {
16805
- if (!existsSync17(d)) {
17559
+ if (!existsSync19(d)) {
16806
17560
  dirFailed += 1;
16807
17561
  dirEvidence.push(`${label}=${d}: ausente`);
16808
17562
  continue;
@@ -16860,7 +17614,7 @@ function backupScan(ctx) {
16860
17614
  over = true;
16861
17615
  break;
16862
17616
  }
16863
- const full = path27.join(dir, name);
17617
+ const full = path29.join(dir, name);
16864
17618
  try {
16865
17619
  if (!statSync3(full).isDirectory()) continue;
16866
17620
  } catch {
@@ -16869,16 +17623,16 @@ function backupScan(ctx) {
16869
17623
  if (restorableIf(full)) restorable.push(`${kind}: ${full}`);
16870
17624
  }
16871
17625
  };
16872
- scan(path27.join(artifactsRoot(ctx.root), "backups"), "workspace-backup", dirHasFiles);
17626
+ scan(path29.join(artifactsRoot(ctx.root), "backups"), "workspace-backup", dirHasFiles);
16873
17627
  scan(
16874
- path27.join(ctx.home, ".forge-cli", "backups"),
17628
+ path29.join(ctx.home, ".forge-cli", "backups"),
16875
17629
  "host-backup",
16876
- (full) => existsSync17(path27.join(full, "manifest.json"))
17630
+ (full) => existsSync19(path29.join(full, "manifest.json"))
16877
17631
  );
16878
17632
  for (const repo of ctx.catalog) {
16879
17633
  const dir = resolveInside(ctx.root, repo.name);
16880
17634
  if (!dir || over) continue;
16881
- scan(path27.join(dir, ".forge-cli", "backups"), `env-backup(${repo.name})`, dirHasFiles);
17635
+ scan(path29.join(dir, ".forge-cli", "backups"), `env-backup(${repo.name})`, dirHasFiles);
16882
17636
  }
16883
17637
  if (over) ctx.truncated.value = true;
16884
17638
  return { restorable, scanned };
@@ -16886,7 +17640,7 @@ function backupScan(ctx) {
16886
17640
  function installationChecks(ctx) {
16887
17641
  const checks = [];
16888
17642
  const forgeDir = artifactsRoot(ctx.root);
16889
- const forgeDirPresent = existsSync17(forgeDir);
17643
+ const forgeDirPresent = existsSync19(forgeDir);
16890
17644
  const stateFile = stateFileFor(ctx.root);
16891
17645
  checks.push(
16892
17646
  mkCheck(
@@ -16899,7 +17653,7 @@ function installationChecks(ctx) {
16899
17653
  diagnosis: "diret\xF3rio de estado .forge-cli presente",
16900
17654
  evidence: [
16901
17655
  `${forgeDir} presente`,
16902
- existsSync17(stateFile) ? "state.json presente" : "state.json ausente"
17656
+ existsSync19(stateFile) ? "state.json presente" : "state.json ausente"
16903
17657
  ],
16904
17658
  reversible: null
16905
17659
  } : {
@@ -16914,8 +17668,8 @@ function installationChecks(ctx) {
16914
17668
  }
16915
17669
  )
16916
17670
  );
16917
- const forgeJson = path27.join(ctx.root, "forge.json");
16918
- if (!existsSync17(forgeJson)) {
17671
+ const forgeJson = path29.join(ctx.root, "forge.json");
17672
+ if (!existsSync19(forgeJson)) {
16919
17673
  checks.push(
16920
17674
  mkCheck(ctx.now, {
16921
17675
  id: "installation.forge-json.present",
@@ -16964,10 +17718,10 @@ function installationChecks(ctx) {
16964
17718
  );
16965
17719
  }
16966
17720
  }
16967
- const lockFile = path27.join(forgeDir, "env.lock.json");
16968
- const lockParsed = existsSync17(lockFile) ? readJson(lockFile) : null;
17721
+ const lockFile = path29.join(forgeDir, "env.lock.json");
17722
+ const lockParsed = existsSync19(lockFile) ? readJson(lockFile) : null;
16969
17723
  const lockObj = lockParsed?.ok && lockParsed.value !== null && typeof lockParsed.value === "object" ? lockParsed.value : null;
16970
- if (!forgeDirPresent || !existsSync17(lockFile)) {
17724
+ if (!forgeDirPresent || !existsSync19(lockFile)) {
16971
17725
  checks.push(
16972
17726
  mkCheck(ctx.now, {
16973
17727
  id: "installation.lockfile.present",
@@ -17012,7 +17766,7 @@ function installationChecks(ctx) {
17012
17766
  );
17013
17767
  }
17014
17768
  const trackedForges = lockObj ? Object.keys(lockObj.forges ?? {}) : [];
17015
- if (!existsSync17(lockFile) || !lockObj || !trackedForges.length) {
17769
+ if (!existsSync19(lockFile) || !lockObj || !trackedForges.length) {
17016
17770
  checks.push(
17017
17771
  mkCheck(ctx.now, {
17018
17772
  id: "installation.lockfile.drift",
@@ -17021,7 +17775,7 @@ function installationChecks(ctx) {
17021
17775
  severity: "info",
17022
17776
  diagnosis: "deriva do lockfile n\xE3o verific\xE1vel",
17023
17777
  evidence: [
17024
- !existsSync17(lockFile) ? "sem lockfile para recomputar (.forge-cli/env.lock.json ausente)" : !lockObj ? "lockfile ileg\xEDvel \u2014 digest n\xE3o pode ser recomputado" : "lockfile n\xE3o cobre nenhuma Forja \u2014 nada para recomputar"
17778
+ !existsSync19(lockFile) ? "sem lockfile para recomputar (.forge-cli/env.lock.json ausente)" : !lockObj ? "lockfile ileg\xEDvel \u2014 digest n\xE3o pode ser recomputado" : "lockfile n\xE3o cobre nenhuma Forja \u2014 nada para recomputar"
17025
17779
  ],
17026
17780
  reversible: null
17027
17781
  })
@@ -17043,17 +17797,17 @@ function installationChecks(ctx) {
17043
17797
  })
17044
17798
  );
17045
17799
  }
17046
- const ledgerDir = path27.join(ctx.home, ".forge", "installations");
17800
+ const ledgerDir = path29.join(ctx.home, ".forge", "installations");
17047
17801
  const ledgerNames = listDir(ledgerDir);
17048
17802
  if (ledgerNames === null) {
17049
17803
  checks.push(
17050
17804
  mkCheck(ctx.now, {
17051
17805
  id: "installation.ledger.readable",
17052
17806
  domain: "installation",
17053
- result: existsSync17(ledgerDir) ? "warn" : "skip",
17054
- severity: existsSync17(ledgerDir) ? "medium" : "info",
17055
- diagnosis: existsSync17(ledgerDir) ? "ledger de instala\xE7\xF5es existe mas n\xE3o p\xF4de ser lido" : "sem ledger de instala\xE7\xF5es (~/.forge/installations ausente)",
17056
- evidence: [`${ledgerDir} ${existsSync17(ledgerDir) ? "ileg\xEDvel" : "ausente"}`],
17807
+ result: existsSync19(ledgerDir) ? "warn" : "skip",
17808
+ severity: existsSync19(ledgerDir) ? "medium" : "info",
17809
+ diagnosis: existsSync19(ledgerDir) ? "ledger de instala\xE7\xF5es existe mas n\xE3o p\xF4de ser lido" : "sem ledger de instala\xE7\xF5es (~/.forge/installations ausente)",
17810
+ evidence: [`${ledgerDir} ${existsSync19(ledgerDir) ? "ileg\xEDvel" : "ausente"}`],
17057
17811
  reversible: null
17058
17812
  })
17059
17813
  );
@@ -17181,7 +17935,7 @@ function environmentChecks(ctx) {
17181
17935
  const targets = [{ label: "<workspace>", dir: ctx.root }];
17182
17936
  for (const repo of ctx.catalog) {
17183
17937
  const dir = resolveInside(ctx.root, repo.name);
17184
- if (dir && existsSync17(dir)) targets.push({ label: repo.name, dir, envKeys: repo.envKeys });
17938
+ if (dir && existsSync19(dir)) targets.push({ label: repo.name, dir, envKeys: repo.envKeys });
17185
17939
  }
17186
17940
  const inspected = targets.map((t2) => ({
17187
17941
  ...t2,
@@ -17393,16 +18147,16 @@ async function hostsChecks(ctx) {
17393
18147
  }
17394
18148
  function operationsChecks(ctx) {
17395
18149
  const checks = [];
17396
- const reportsDir = path27.join(artifactsRoot(ctx.root), "reports");
18150
+ const reportsDir = path29.join(artifactsRoot(ctx.root), "reports");
17397
18151
  const allReports = (listDir(reportsDir) ?? []).filter((n4) => n4.endsWith(".json"));
17398
18152
  const capped = allReports.length > ctx.maxReceipts;
17399
18153
  if (capped) ctx.truncated.value = true;
17400
18154
  const scannedNames = [...allReports].sort().reverse().slice(0, ctx.maxReceipts);
17401
- const parsedReports = scannedNames.map((n4) => readJson(path27.join(reportsDir, n4))).filter((r2) => r2.ok).map((r2) => r2.value);
18155
+ const parsedReports = scannedNames.map((n4) => readJson(path29.join(reportsDir, n4))).filter((r2) => r2.ok).map((r2) => r2.value);
17402
18156
  checks.push(
17403
18157
  mkCheck(
17404
18158
  ctx.now,
17405
- !existsSync17(reportsDir) ? {
18159
+ !existsSync19(reportsDir) ? {
17406
18160
  id: "operations.receipts.recent",
17407
18161
  domain: "operations",
17408
18162
  result: "skip",
@@ -17438,7 +18192,7 @@ function operationsChecks(ctx) {
17438
18192
  if (entry2.status === "error") failures.set(name, (failures.get(name) ?? 0) + 1);
17439
18193
  }
17440
18194
  const recurring = [...failures.entries()].filter(([, n4]) => n4 >= 2);
17441
- const scannedAnything = existsSync17(reportsDir) || ctx.state !== null;
18195
+ const scannedAnything = existsSync19(reportsDir) || ctx.state !== null;
17442
18196
  if (!scannedAnything) {
17443
18197
  checks.push(
17444
18198
  mkCheck(ctx.now, {
@@ -17474,7 +18228,7 @@ function operationsChecks(ctx) {
17474
18228
  );
17475
18229
  const recFiles = boundEntries(ctx, allRecFiles).shown;
17476
18230
  const recEvidence = recFiles.map((f) => {
17477
- const parsed = readJson(path27.join(artifactsRoot(ctx.root), f));
18231
+ const parsed = readJson(path29.join(artifactsRoot(ctx.root), f));
17478
18232
  return `recovery file: ${f}${parsed.ok ? "" : " (ileg\xEDvel)"}`;
17479
18233
  });
17480
18234
  checks.push(
@@ -17654,10 +18408,10 @@ function defaultCatalog(root) {
17654
18408
  }
17655
18409
  async function runDoctor2(root, opts = {}) {
17656
18410
  const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
17657
- const resolved = path28.resolve(root);
18411
+ const resolved = path30.resolve(root);
17658
18412
  const ctx = {
17659
18413
  root: resolved,
17660
- home: opts.home ?? os3.homedir(),
18414
+ home: opts.home ?? os5.homedir(),
17661
18415
  now,
17662
18416
  catalog: opts.catalog ?? defaultCatalog(resolved),
17663
18417
  adapters: opts.adapters ?? hostAdapters(),
@@ -17714,9 +18468,9 @@ async function runDoctor2(root, opts = {}) {
17714
18468
  }
17715
18469
 
17716
18470
  // src/heal/fixes.ts
17717
- import { existsSync as existsSync18, mkdirSync as mkdirSync5, readdirSync as readdirSync7, readFileSync as readFileSync15, statSync as statSync4 } from "fs";
17718
- import path29 from "path";
17719
- var rel = (root, abs) => path29.relative(root, abs) || ".";
18471
+ import { existsSync as existsSync20, mkdirSync as mkdirSync7, readdirSync as readdirSync8, readFileSync as readFileSync17, statSync as statSync4 } from "fs";
18472
+ import path31 from "path";
18473
+ var rel = (root, abs) => path31.relative(root, abs) || ".";
17720
18474
  function stampOf(iso) {
17721
18475
  const m3 = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/.exec(iso);
17722
18476
  return m3 ? `${m3[1]}${m3[2]}${m3[3]}-${m3[4]}${m3[5]}${m3[6]}` : iso.replace(/[^\d]/g, "").slice(0, 14);
@@ -17731,9 +18485,9 @@ var forgeDirRepair = {
17731
18485
  plan(ctx) {
17732
18486
  const base = artifactsRoot(ctx.root);
17733
18487
  return ["", ...FORGE_DIR_SUBDIRS].map((sub) => {
17734
- const dir = sub ? path29.join(base, sub) : base;
18488
+ const dir = sub ? path31.join(base, sub) : base;
17735
18489
  return {
17736
- description: `create directory ${rel(ctx.root, dir)}${existsSync18(dir) ? " (already present)" : ""}`,
18490
+ description: `create directory ${rel(ctx.root, dir)}${existsSync20(dir) ? " (already present)" : ""}`,
17737
18491
  touches: [rel(ctx.root, dir)]
17738
18492
  };
17739
18493
  });
@@ -17743,9 +18497,9 @@ var forgeDirRepair = {
17743
18497
  const base = artifactsRoot(ctx.root);
17744
18498
  const created = [];
17745
18499
  for (const sub of ["", ...FORGE_DIR_SUBDIRS]) {
17746
- const dir = sub ? path29.join(base, sub) : base;
17747
- if (existsSync18(dir)) continue;
17748
- mkdirSync5(dir, { recursive: true });
18500
+ const dir = sub ? path31.join(base, sub) : base;
18501
+ if (existsSync20(dir)) continue;
18502
+ mkdirSync7(dir, { recursive: true });
17749
18503
  created.push(rel(ctx.root, dir));
17750
18504
  }
17751
18505
  return created.length ? `created ${created.length} directorie(s): ${created.join(", ")}` : "all directories already present";
@@ -17753,7 +18507,7 @@ var forgeDirRepair = {
17753
18507
  async verify(ctx) {
17754
18508
  const base = artifactsRoot(ctx.root);
17755
18509
  return ["", ...FORGE_DIR_SUBDIRS].every((sub) => {
17756
- const dir = sub ? path29.join(base, sub) : base;
18510
+ const dir = sub ? path31.join(base, sub) : base;
17757
18511
  try {
17758
18512
  return statSync4(dir).isDirectory();
17759
18513
  } catch {
@@ -17764,12 +18518,12 @@ var forgeDirRepair = {
17764
18518
  };
17765
18519
  var EVIDENCE_KEY_RE = /^[A-Z_][A-Z0-9_]*$/;
17766
18520
  function envPathOf(root) {
17767
- return path29.join(root, ".env");
18521
+ return path31.join(root, ".env");
17768
18522
  }
17769
18523
  function presentKeys(root) {
17770
18524
  const file = envPathOf(root);
17771
- if (!existsSync18(file)) return /* @__PURE__ */ new Set();
17772
- return new Set(parseEnv(readFileSync15(file, "utf8")).entries.map((e) => e.key));
18525
+ if (!existsSync20(file)) return /* @__PURE__ */ new Set();
18526
+ return new Set(parseEnv(readFileSync17(file, "utf8")).entries.map((e) => e.key));
17773
18527
  }
17774
18528
  function missingEnvKeys(root, check) {
17775
18529
  const missing = /* @__PURE__ */ new Set();
@@ -17789,9 +18543,9 @@ function keysToAppend(root, check) {
17789
18543
  }
17790
18544
  function backupEnvFile(ctx, current) {
17791
18545
  const stamp = stampOf(ctx.now?.() ?? (/* @__PURE__ */ new Date()).toISOString());
17792
- const dir = path29.join(artifactsRoot(ctx.root), "backups", `env-${stamp}`);
17793
- mkdirSync5(dir, { recursive: true });
17794
- writeFileAtomic(path29.join(dir, ".env"), current);
18546
+ const dir = path31.join(artifactsRoot(ctx.root), "backups", `env-${stamp}`);
18547
+ mkdirSync7(dir, { recursive: true });
18548
+ writeFileAtomic(path31.join(dir, ".env"), current);
17795
18549
  return dir;
17796
18550
  }
17797
18551
  var envMissingKeyRepair = {
@@ -17804,9 +18558,9 @@ var envMissingKeyRepair = {
17804
18558
  const root = ctx.root;
17805
18559
  const toAppend = keysToAppend(root, ctx.check);
17806
18560
  const steps = [];
17807
- if (existsSync18(envPathOf(root))) {
18561
+ if (existsSync20(envPathOf(root))) {
17808
18562
  steps.push({
17809
- description: `backup .env to ${rel(root, path29.join(artifactsRoot(root), "backups"))}/env-<ts>/.env`,
18563
+ description: `backup .env to ${rel(root, path31.join(artifactsRoot(root), "backups"))}/env-<ts>/.env`,
17810
18564
  touches: [".env", `${rel(root, artifactsRoot(root))}/backups/`]
17811
18565
  });
17812
18566
  }
@@ -17823,8 +18577,8 @@ var envMissingKeyRepair = {
17823
18577
  const root = ctx.root;
17824
18578
  if (ctx.dryRun) return "dry-run \u2014 not applied";
17825
18579
  const file = envPathOf(root);
17826
- const exists = existsSync18(file);
17827
- const current = exists ? readFileSync15(file, "utf8") : "";
18580
+ const exists = existsSync20(file);
18581
+ const current = exists ? readFileSync17(file, "utf8") : "";
17828
18582
  const env = parseEnv(current);
17829
18583
  const present = new Set(env.entries.map((e) => e.key));
17830
18584
  const toAppend = keysToAppend(root, ctx.check).filter((k) => !present.has(k));
@@ -17841,8 +18595,8 @@ var envMissingKeyRepair = {
17841
18595
  },
17842
18596
  async verify(ctx) {
17843
18597
  const file = envPathOf(ctx.root);
17844
- if (!existsSync18(file)) return false;
17845
- const present = new Set(parseEnv(readFileSync15(file, "utf8")).entries.map((e) => e.key));
18598
+ if (!existsSync20(file)) return false;
18599
+ const present = new Set(parseEnv(readFileSync17(file, "utf8")).entries.map((e) => e.key));
17846
18600
  return missingEnvKeys(ctx.root, ctx.check).every((k) => present.has(k));
17847
18601
  }
17848
18602
  };
@@ -17858,7 +18612,7 @@ var lockfileDriftRepair = {
17858
18612
  return [
17859
18613
  {
17860
18614
  description: `run \`${LOCKFILE_REGEN_COMMAND}\` to regenerate .forge-cli/env.lock.json`,
17861
- touches: [path29.join(".forge-cli", "env.lock.json")]
18615
+ touches: [path31.join(".forge-cli", "env.lock.json")]
17862
18616
  },
17863
18617
  {
17864
18618
  description: `run \`${LOCKFILE_CHECK_COMMAND}\` to confirm the drift cleared`,
@@ -17878,14 +18632,14 @@ var lockfileDriftRepair = {
17878
18632
  var ENV_BACKUP_RE = /^env-\d{8}-\d{6}$/;
17879
18633
  var REPO_BACKUP_RE = /^([a-z0-9][a-z0-9._-]*)-\d{10,}$/i;
17880
18634
  function listBackups(root) {
17881
- const base = path29.join(artifactsRoot(root), "backups");
17882
- if (!existsSync18(base)) return [];
18635
+ const base = path31.join(artifactsRoot(root), "backups");
18636
+ if (!existsSync20(base)) return [];
17883
18637
  const out = [];
17884
- for (const entry2 of readdirSync7(base, { withFileTypes: true })) {
18638
+ for (const entry2 of readdirSync8(base, { withFileTypes: true })) {
17885
18639
  if (!entry2.isDirectory()) continue;
17886
- const dir = path29.join(base, entry2.name);
18640
+ const dir = path31.join(base, entry2.name);
17887
18641
  try {
17888
- const files = readdirSync7(dir, { withFileTypes: true }).filter((f) => f.isFile()).map((f) => f.name).sort();
18642
+ const files = readdirSync8(dir, { withFileTypes: true }).filter((f) => f.isFile()).map((f) => f.name).sort();
17889
18643
  if (!files.length) continue;
17890
18644
  out.push({ name: entry2.name, dir, files, mtimeMs: statSync4(dir).mtimeMs });
17891
18645
  } catch {
@@ -17901,18 +18655,18 @@ function restoreTargetFor(backupDirName, fileName) {
17901
18655
  return fileName;
17902
18656
  }
17903
18657
  function pickBackupFile(ctx) {
17904
- const backupsBase = path29.join(artifactsRoot(ctx.root), "backups");
18658
+ const backupsBase = path31.join(artifactsRoot(ctx.root), "backups");
17905
18659
  if (ctx.backup) {
17906
- const abs = path29.resolve(ctx.root, ctx.backup);
17907
- const inside = path29.relative(backupsBase, abs);
17908
- if (inside.startsWith("..") || path29.isAbsolute(inside)) {
18660
+ const abs = path31.resolve(ctx.root, ctx.backup);
18661
+ const inside = path31.relative(backupsBase, abs);
18662
+ if (inside.startsWith("..") || path31.isAbsolute(inside)) {
17909
18663
  throw new Error(`selected backup escapes .forge-cli/backups: ${ctx.backup}`);
17910
18664
  }
17911
- if (!existsSync18(abs) || !statSync4(abs).isFile()) {
18665
+ if (!existsSync20(abs) || !statSync4(abs).isFile()) {
17912
18666
  throw new Error(`selected backup not found: ${ctx.backup}`);
17913
18667
  }
17914
- const dirName = inside.split(path29.sep)[0] ?? "";
17915
- const fileName2 = path29.basename(abs);
18668
+ const dirName = inside.split(path31.sep)[0] ?? "";
18669
+ const fileName2 = path31.basename(abs);
17916
18670
  return { src: abs, rel: inside, dest: restoreTargetFor(dirName, fileName2) };
17917
18671
  }
17918
18672
  const candidates = listBackups(ctx.root);
@@ -17921,7 +18675,7 @@ function pickBackupFile(ctx) {
17921
18675
  if (!cand) throw new Error("no backups available under .forge-cli/backups");
17922
18676
  const fileName = cand.files.includes(".env") ? ".env" : cand.files[0];
17923
18677
  return {
17924
- src: path29.join(cand.dir, fileName),
18678
+ src: path31.join(cand.dir, fileName),
17925
18679
  rel: `${cand.name}/${fileName}`,
17926
18680
  dest: restoreTargetFor(cand.name, fileName)
17927
18681
  };
@@ -17937,7 +18691,7 @@ var backupRestoreRepair = {
17937
18691
  if (!candidates.length) {
17938
18692
  return [
17939
18693
  {
17940
- description: `no backups found under ${rel(ctx.root, path29.join(artifactsRoot(ctx.root), "backups"))}/`,
18694
+ description: `no backups found under ${rel(ctx.root, path31.join(artifactsRoot(ctx.root), "backups"))}/`,
17941
18695
  touches: []
17942
18696
  }
17943
18697
  ];
@@ -17963,20 +18717,20 @@ var backupRestoreRepair = {
17963
18717
  async apply(ctx) {
17964
18718
  if (ctx.dryRun) return "dry-run \u2014 not applied";
17965
18719
  const picked = pickBackupFile(ctx);
17966
- const dest = path29.resolve(ctx.root, picked.dest);
17967
- const relDest = path29.relative(ctx.root, dest);
17968
- if (relDest.startsWith("..") || path29.isAbsolute(relDest)) {
18720
+ const dest = path31.resolve(ctx.root, picked.dest);
18721
+ const relDest = path31.relative(ctx.root, dest);
18722
+ if (relDest.startsWith("..") || path31.isAbsolute(relDest)) {
17969
18723
  throw new Error(`restore target escapes root: ${picked.dest}`);
17970
18724
  }
17971
- mkdirSync5(path29.dirname(dest), { recursive: true });
17972
- writeFileAtomic(dest, readFileSync15(picked.src, "utf8"));
18725
+ mkdirSync7(path31.dirname(dest), { recursive: true });
18726
+ writeFileAtomic(dest, readFileSync17(picked.src, "utf8"));
17973
18727
  return `restored ${picked.rel} \u2192 ${relDest}`;
17974
18728
  },
17975
18729
  async verify(ctx) {
17976
18730
  try {
17977
18731
  const picked = pickBackupFile(ctx);
17978
- const dest = path29.resolve(ctx.root, picked.dest);
17979
- return existsSync18(dest) && readFileSync15(dest, "utf8") === readFileSync15(picked.src, "utf8");
18732
+ const dest = path31.resolve(ctx.root, picked.dest);
18733
+ return existsSync20(dest) && readFileSync17(dest, "utf8") === readFileSync17(picked.src, "utf8");
17980
18734
  } catch {
17981
18735
  return false;
17982
18736
  }
@@ -18006,11 +18760,11 @@ function classifyFindings(checks) {
18006
18760
  }
18007
18761
 
18008
18762
  // src/heal/flow.ts
18009
- import { mkdirSync as mkdirSync6 } from "fs";
18010
- import path30 from "path";
18763
+ import { mkdirSync as mkdirSync8 } from "fs";
18764
+ import path32 from "path";
18011
18765
  function healReceiptPath(root, generatedAt) {
18012
18766
  const stamp = generatedAt.replace(/[:.]/g, "-");
18013
- return path30.join(artifactsRoot(root), "receipts", `heal-${stamp}.json`);
18767
+ return path32.join(artifactsRoot(root), "receipts", `heal-${stamp}.json`);
18014
18768
  }
18015
18769
  async function planRepairs(checks, opts) {
18016
18770
  const classified = classifyFindings(checks);
@@ -18144,7 +18898,7 @@ async function runHealFlow(checks, opts) {
18144
18898
  verifiedAll: entries.every((e) => e.verified === true)
18145
18899
  };
18146
18900
  const file = healReceiptPath(opts.root, generatedAt);
18147
- mkdirSync6(path30.dirname(file), { recursive: true });
18901
+ mkdirSync8(path32.dirname(file), { recursive: true });
18148
18902
  writeFileAtomic(file, redactSecrets(JSON.stringify(receipt, null, 2)));
18149
18903
  return receipt;
18150
18904
  }
@@ -18227,46 +18981,1056 @@ async function runDoctor2Command(cli) {
18227
18981
  return runHealCommand({ ...cli, plan: cli.plan });
18228
18982
  }
18229
18983
 
18230
- // src/lifecycle.ts
18231
- import { copyFileSync as copyFileSync3, existsSync as existsSync19, mkdirSync as mkdirSync7, readdirSync as readdirSync8, rmSync as rmSync4 } from "fs";
18232
- import path31 from "path";
18233
- function configFilesToBackup(repoDir) {
18234
- const out = /* @__PURE__ */ new Set();
18235
- if (existsSync19(path31.join(repoDir, ".env"))) out.add(".env");
18236
- for (const file of readdirSync8(repoDir)) {
18237
- if (file === ".env.example" || !file.includes(".example.")) continue;
18238
- const real = file.replace(".example.", ".");
18239
- if (existsSync19(path31.join(repoDir, real))) out.add(real);
18240
- }
18241
- return [...out];
18242
- }
18243
- async function planUninstall(targetDir, repo) {
18244
- const dir = resolveInside(targetDir, repo.name);
18245
- if (!dir)
18246
- return { kind: "refused", dir: path31.join(targetDir, repo.name), reason: "invalid-name" };
18247
- if (isLinkOrJunction(dir)) return { kind: "refused", dir, reason: "symlink" };
18248
- if (!existsSync19(dir)) return { kind: "absent", dir };
18249
- if (!hasGitRepo(dir)) return { kind: "refused", dir, reason: "non-git" };
18250
- const remote = await commandOutput("git", ["-C", dir, "remote", "get-url", "origin"]);
18251
- if (remote && remote !== "ok" && !sameRemote(remote, repo.url)) {
18252
- return { kind: "refused", dir, reason: "remote-mismatch" };
18253
- }
18254
- if (await isGitDirty(dir)) return { kind: "refused", dir, reason: "dirty" };
18255
- return { kind: "ready", dir, backups: configFilesToBackup(dir) };
18256
- }
18257
- function executeUninstall(targetDir, repo, plan) {
18258
- const expected = resolveInside(targetDir, repo.name);
18984
+ // src/cmd/registry.ts
18985
+ import path33 from "path";
18986
+ import { existsSync as existsSync21, mkdirSync as mkdirSync9 } from "fs";
18987
+
18988
+ // src/registry/catalog.ts
18989
+ var REGISTRY_PACKAGES = [
18990
+ {
18991
+ name: "the-forge",
18992
+ dist: "theforge-core",
18993
+ bin: "theforge",
18994
+ minPython: "3.11",
18995
+ origin: "official"
18996
+ },
18997
+ {
18998
+ name: "api-forge",
18999
+ dist: "apiforge-af",
19000
+ bin: "apiforge",
19001
+ minPython: "3.12",
19002
+ mcp: ["{binDir}/apiforge-mcp"],
19003
+ origin: "official"
19004
+ },
19005
+ {
19006
+ name: "platform-forge",
19007
+ dist: "platformforge",
19008
+ bin: "platformforge",
19009
+ minPython: "3.10",
19010
+ mcp: ["{binDir}/platformforge-mcp"],
19011
+ origin: "official"
19012
+ },
19013
+ {
19014
+ name: "spark-forge-aws",
19015
+ dist: "sparkforge-aws",
19016
+ bin: "sparkforge-aws",
19017
+ minPython: "3.10",
19018
+ mcp: ["{bin}", "mcp", "serve"],
19019
+ origin: "official"
19020
+ },
19021
+ {
19022
+ name: "spark-forge-azure",
19023
+ dist: "sparkforge-azure",
19024
+ bin: "sparkforge-azure",
19025
+ minPython: "3.10",
19026
+ mcp: ["{bin}", "mcp", "serve"],
19027
+ origin: "official"
19028
+ },
19029
+ {
19030
+ name: "forge-doctor-data",
19031
+ dist: "forge-doctor-data",
19032
+ bin: "forge-doctor-data",
19033
+ minPython: "3.11",
19034
+ mcp: ["{bin}", "mcp"],
19035
+ origin: "official"
19036
+ },
19037
+ {
19038
+ name: "forge-doctor-api",
19039
+ dist: "forge-doctor-api",
19040
+ bin: "forge-doctor-api",
19041
+ minPython: "3.11",
19042
+ mcp: ["{bin}", "mcp"],
19043
+ origin: "official"
19044
+ }
19045
+ ];
19046
+ var BY_NAME = new Map(REGISTRY_PACKAGES.map((e) => [e.name, e]));
19047
+ var BY_DIST = new Map(REGISTRY_PACKAGES.map((e) => [e.dist, e]));
19048
+ function registryEntry(nameOrDist) {
19049
+ return BY_NAME.get(nameOrDist) ?? BY_DIST.get(nameOrDist) ?? null;
19050
+ }
19051
+ function registryNames() {
19052
+ return REGISTRY_PACKAGES.map((e) => e.name);
19053
+ }
19054
+
19055
+ // src/cmd/registry.ts
19056
+ async function probePythons(platform2 = process.platform, probe = commandOutput) {
19057
+ const seen = /* @__PURE__ */ new Set();
19058
+ const out = [];
19059
+ for (const c4 of pythonCandidates(platform2)) {
19060
+ const raw = await probe(c4.exe, [...c4.args, "--version"]);
19061
+ const m3 = raw?.match(/Python\s+(\d+\.\d+\.\d+(?:[a-z]+\d+)?)/);
19062
+ if (!m3) continue;
19063
+ const parsed = parseVersion2(m3[1]);
19064
+ if (!parsed || seen.has(m3[1])) continue;
19065
+ seen.add(m3[1]);
19066
+ out.push({ exe: [c4.exe, ...c4.args], version: m3[1], parsed });
19067
+ }
19068
+ out.sort((a3, b) => compareVersions3(b.parsed, a3.parsed));
19069
+ return out;
19070
+ }
19071
+ function describeResolveError(e) {
19072
+ switch (e.kind) {
19073
+ case "not-found":
19074
+ return `dist ${e.dist} n\xE3o existe no PyPI`;
19075
+ case "version-not-found":
19076
+ return `${e.dist}==${e.requested} n\xE3o existe \u2014 dispon\xEDveis: ${e.available.slice(-5).join(", ")}`;
19077
+ case "python-incompatible":
19078
+ return `${e.dist} ${e.version} exige python ${e.requires} (detectado ${e.have})`;
19079
+ case "specifier-unknown":
19080
+ return `${e.dist} ${e.version}: requires_python fora do subconjunto suportado (${e.requires})`;
19081
+ case "offline-miss":
19082
+ return `${e.dist}: sem cache local e --offline ativo \u2014 rode online uma vez`;
19083
+ case "malformed":
19084
+ return `${e.dist}: metadados fora do formato esperado (${e.detail})`;
19085
+ case "network":
19086
+ return `rede/PyPI: ${e.detail}`;
19087
+ }
19088
+ }
19089
+ function describeInstallError(e) {
19090
+ if (e.stage === "resolve") return describeResolveError(e.error);
19091
+ if (e.stage === "offline-missing-artifact") return `${S.registryOfflineMissing}: ${e.detail}`;
19092
+ if (e.stage === "hash-mismatch") return `${S.registryHashMismatch}: ${e.detail}`;
19093
+ if (e.stage === "already-installed") return e.detail;
19094
+ return `${e.stage}: ${e.detail}`;
19095
+ }
19096
+ function registryCacheDir(home = cliHome()) {
19097
+ return path33.join(home, ".forge-cli", "cache", "pypi");
19098
+ }
19099
+ var REGISTRY_IGNORED_FLAGS = [
19100
+ ["dir", "--dir"],
19101
+ // --profile/--scope/--components/--host/--gateway-profile/--native são
19102
+ // honrados via delegação ao instalador nativo da Forja (fase 2).
19103
+ ["skipVerify", "--skip-verify"],
19104
+ ["skipScripts", "--skip-scripts"],
19105
+ ["retryFailed", "--retry-failed"],
19106
+ ["shallow", "--shallow"],
19107
+ ["fix", "--fix"],
19108
+ ["rollback", "--rollback"],
19109
+ ["trustCustom", "--trust-custom"],
19110
+ ["runAfterSyncFailure", "--run-after-sync-failure"],
19111
+ ["onlyChanged", "--only-changed"],
19112
+ ["verbose", "--verbose"]
19113
+ ];
19114
+ function ignoredRegistryFlags(cli) {
19115
+ const out = [];
19116
+ for (const [key, label] of REGISTRY_IGNORED_FLAGS) {
19117
+ if (cli[key] === void 0 || cli[key] === false) continue;
19118
+ out.push(label);
19119
+ }
19120
+ if (cli.timeoutSet) out.push("--timeout");
19121
+ if (cli.jobsSet) out.push("--jobs");
19122
+ return out;
19123
+ }
19124
+ function registryQuiet(cli) {
19125
+ return cli.jsonReport && cli.reportPath === void 0;
19126
+ }
19127
+ function validateRegistryNativeFlags(cli) {
19128
+ return validateNativeOptions({
19129
+ profile: cli.profile,
19130
+ scope: cli.scope,
19131
+ components: cli.components,
19132
+ host: cli.host,
19133
+ gatewayProfile: cli.gatewayProfile
19134
+ });
19135
+ }
19136
+ async function delegateNative(cli, entry2, receipt, op, run = execaRun) {
19137
+ const wants = cli.native || hasNativeRequest({
19138
+ profile: cli.profile,
19139
+ scope: cli.scope,
19140
+ components: cli.components,
19141
+ host: cli.host,
19142
+ gatewayProfile: cli.gatewayProfile
19143
+ });
19144
+ if (!wants) return void 0;
19145
+ const repo = FORGE_REPOS.find((r2) => r2.name === entry2.name);
19146
+ const built = repo && (op === "install" ? buildNativeInstall(repo, {
19147
+ profile: cli.profile,
19148
+ scope: cli.scope,
19149
+ components: cli.components,
19150
+ host: cli.host,
19151
+ gatewayProfile: cli.gatewayProfile,
19152
+ approve: cli.yes
19153
+ }) : buildNativeOp(repo, "update", cli.yes ?? false));
19154
+ if (!built) {
19155
+ return { argv: [], ran: false, skipped: "sem instalador nativo documentado" };
19156
+ }
19157
+ if (built.argv[0] === "{python}") {
19158
+ return {
19159
+ argv: built.argv,
19160
+ ran: false,
19161
+ skipped: "instalador por script do reposit\xF3rio (scripts/forge_install.py) \u2014 indispon\xEDvel no m\xE9todo registry"
19162
+ };
19163
+ }
19164
+ const argv = [receipt.bin, ...built.argv.slice(1)];
19165
+ const res = await run(argv[0], argv.slice(1));
19166
+ const ignored = "ignored" in built ? built.ignored : [];
19167
+ return {
19168
+ argv,
19169
+ ran: true,
19170
+ dryRun: built.dryRun,
19171
+ exitCode: res.exitCode,
19172
+ ...ignored.length ? { ignored } : {}
19173
+ };
19174
+ }
19175
+ function persistReceipt(home, receipt) {
19176
+ writeFileAtomic(receiptPath(home, receipt.forge), `${JSON.stringify(receipt, null, 2)}
19177
+ `);
19178
+ const state = loadForgeState(home);
19179
+ if (Object.hasOwn(state.forges, receipt.forge)) {
19180
+ state.forges[receipt.forge] = receipt;
19181
+ saveForgeState(home, state);
19182
+ }
19183
+ }
19184
+ function writeRegistryReport(reportPath, doc) {
19185
+ const file = path33.resolve(reportPath);
19186
+ mkdirSync9(path33.dirname(file), { recursive: true });
19187
+ writeFileAtomic(file, redactSecrets(JSON.stringify(doc, null, 2)));
19188
+ }
19189
+ async function runRegistryInstall(cli, names, deps = {}) {
19190
+ const home = deps.home ?? cliHome();
19191
+ const quiet = registryQuiet(cli);
19192
+ const reportTo = (doc2) => {
19193
+ if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, doc2);
19194
+ };
19195
+ const entries = names.map((n4) => ({ name: n4, entry: registryEntry(n4) }));
19196
+ const unknown = entries.filter((e) => !e.entry);
19197
+ for (const e of entries) if (e.entry) e.name = e.entry.name;
19198
+ const seen = /* @__PURE__ */ new Set();
19199
+ const deduped = entries.filter((e) => {
19200
+ const key = e.entry?.name ?? e.name;
19201
+ if (seen.has(key)) return false;
19202
+ seen.add(key);
19203
+ return true;
19204
+ });
19205
+ if (unknown.length) {
19206
+ const msg = `${unknown.map((u4) => u4.name).join(", ")}: sem dist no cat\xE1logo registry \u2014 use o m\xE9todo git`;
19207
+ reportTo({ error: msg });
19208
+ if (quiet) emitJson({ error: msg }, 2);
19209
+ log.error(msg);
19210
+ process.exit(2);
19211
+ }
19212
+ const ignored = ignoredRegistryFlags(cli);
19213
+ const warnings = ignored.length ? [`${S.registryIgnoredFlags}: ${ignored.join(", ")}`] : [];
19214
+ if (warnings.length && !quiet) log.warn(warnings[0]);
19215
+ const nativeErr = validateRegistryNativeFlags(cli);
19216
+ if (nativeErr) {
19217
+ reportTo({ error: nativeErr, warnings });
19218
+ if (quiet) emitJson({ error: nativeErr, warnings }, 1);
19219
+ log.error(nativeErr);
19220
+ process.exit(1);
19221
+ return [];
19222
+ }
19223
+ const pin = cli.ref?.replace(/^v(?=\d)/, "");
19224
+ const pythons = deps.pythons ?? await probePythons();
19225
+ if (!pythons.length) {
19226
+ const msg = "Nenhum interpretador Python detectado \u2014 instale Python \u22653.10";
19227
+ reportTo({ error: msg, warnings });
19228
+ if (quiet) emitJson({ error: msg, warnings }, 1);
19229
+ log.error(msg);
19230
+ process.exit(1);
19231
+ }
19232
+ if (!quiet) log.info(`Python detectado: ${pythons.map((p) => p.version).join(", ")}`);
19233
+ if (cli.plan || cli.check) {
19234
+ const planned = [];
19235
+ for (const { name, entry: entry2 } of deduped) {
19236
+ const resolved = await resolveRelease(entry2.dist, {
19237
+ pin,
19238
+ offline: cli.offline,
19239
+ cacheDir: registryCacheDir(home),
19240
+ fetchImpl: deps.fetchImpl,
19241
+ ephemeral: true
19242
+ // P0-002: plan não persiste nem cache
19243
+ });
19244
+ if ("kind" in resolved) {
19245
+ planned.push({ forge: name, ok: false, detail: describeResolveError(resolved) });
19246
+ continue;
19247
+ }
19248
+ const picked = selectInterpreter(entry2, resolved, pythons);
19249
+ if ("error" in picked) {
19250
+ planned.push({ forge: name, ok: false, detail: describeResolveError(picked.error) });
19251
+ continue;
19252
+ }
19253
+ planned.push({
19254
+ forge: name,
19255
+ ok: true,
19256
+ dist: entry2.dist,
19257
+ version: resolved.version,
19258
+ requiresPython: resolved.requiresPython,
19259
+ python: picked.python.version,
19260
+ venv: forgeVenv(home, name),
19261
+ wheels: wheelCacheDir(registryCacheDir(home), entry2.dist),
19262
+ source: resolved.source,
19263
+ staleCache: resolved.staleCache,
19264
+ yanked: resolved.yanked
19265
+ });
19266
+ }
19267
+ const planDoc = { method: "pypi", plan: true, planned, warnings };
19268
+ reportTo(planDoc);
19269
+ if (quiet) {
19270
+ emitJson(planDoc, planned.every((p) => p.ok) ? 0 : 1);
19271
+ }
19272
+ note(
19273
+ planned.map(
19274
+ (p) => p.ok ? `${p.forge} \u2192 ${p.dist}==${p.version} (python ${p.python}, venv ${p.venv})` : `${p.forge} \u2192 falhou: ${p.detail}`
19275
+ ).join("\n"),
19276
+ // venv/receipt/state não são escritos; o cache de metadados do
19277
+ // PyPI pode ser atualizado (mesma regra de `update --check`).
19278
+ "Plano (venv e estado n\xE3o alterados)"
19279
+ );
19280
+ if (planned.some((p) => !p.ok)) process.exit(1);
19281
+ outro(`${icons.ok} plano resolvido \u2014 execute sem --plan para instalar`);
19282
+ return planned.map((p) => ({
19283
+ forge: p.forge,
19284
+ ok: p.ok,
19285
+ detail: p.ok ? "planned" : p.detail ?? "falhou",
19286
+ version: p.version
19287
+ }));
19288
+ }
19289
+ if (!quiet && !cli.yes && process.stdout.isTTY && process.stdin.isTTY) {
19290
+ const lines = [];
19291
+ for (const { name, entry: entry2 } of deduped) {
19292
+ const r2 = await resolveRelease(entry2.dist, {
19293
+ pin,
19294
+ offline: cli.offline,
19295
+ cacheDir: registryCacheDir(home),
19296
+ fetchImpl: deps.fetchImpl
19297
+ });
19298
+ lines.push(
19299
+ "kind" in r2 ? `${name} \u2192 ${entry2.dist} (${describeResolveError(r2)})` : `${name} \u2192 ${r2.dist}==${r2.version} (venv isolada em ${forgeVenv(home, name)})`
19300
+ );
19301
+ }
19302
+ note(lines.join("\n"), "Instala\xE7\xE3o registry-first");
19303
+ const answer = await confirm({ message: S.confirmRun });
19304
+ if (isCancel(answer) || !answer) {
19305
+ if (isCancel(answer)) exitCancelled();
19306
+ log.warn("Nada foi instalado.");
19307
+ process.exit(0);
19308
+ return [];
19309
+ }
19310
+ }
19311
+ const results = [];
19312
+ for (const { name, entry: entry2 } of deduped) {
19313
+ const spin = spinner();
19314
+ if (!quiet) spin.start(`${name}: resolvendo ${entry2.dist}\u2026`);
19315
+ const receipt = await registryInstall(entry2, pin, {
19316
+ home,
19317
+ cacheDir: registryCacheDir(home),
19318
+ offline: cli.offline,
19319
+ pythons,
19320
+ clean: cli.clean,
19321
+ fetchImpl: deps.fetchImpl,
19322
+ run: deps.run
19323
+ });
19324
+ if ("stage" in receipt) {
19325
+ if (!quiet) spin.stop(`${name}: ${theme.err("falhou")}`);
19326
+ results.push({ forge: name, ok: false, detail: describeInstallError(receipt) });
19327
+ continue;
19328
+ }
19329
+ receipt.native = await delegateNative(cli, entry2, receipt, "install", deps.run);
19330
+ if (receipt.native) {
19331
+ persistReceipt(home, receipt);
19332
+ const n4 = receipt.native;
19333
+ if (!quiet) {
19334
+ if (n4.skipped) log.warn(`${name}: ${n4.skipped}`);
19335
+ else if (n4.exitCode !== 0) log.warn(`${name}: instalador nativo saiu ${n4.exitCode}`);
19336
+ else
19337
+ log.info(`${name}: nativo ${n4.dryRun ? "(dry-run)" : "aplicado"} \u2014 ${n4.argv.join(" ")}`);
19338
+ }
19339
+ if (n4.skipped) warnings.push(`${name}: ${n4.skipped}`);
19340
+ if (n4.ran && n4.exitCode !== 0) warnings.push(`${name}: instalador nativo saiu ${n4.exitCode}`);
19341
+ }
19342
+ if (receipt.staleCache) {
19343
+ const w = `${S.registryStaleCache} (${entry2.dist})`;
19344
+ warnings.push(w);
19345
+ if (!quiet) log.warn(w);
19346
+ }
19347
+ if (!quiet) {
19348
+ spin.stop(
19349
+ `${name}: ${theme.ok("instalado")} ${theme.dim(`${receipt.dist}==${receipt.version}`)}`
19350
+ );
19351
+ }
19352
+ results.push({
19353
+ forge: name,
19354
+ ok: true,
19355
+ version: receipt.version,
19356
+ detail: `${receipt.dist}==${receipt.version} em ${receipt.venv}`,
19357
+ ...receipt.native ? { native: receipt.native } : {}
19358
+ });
19359
+ }
19360
+ const doc = { method: "pypi", results, warnings };
19361
+ reportTo(doc);
19362
+ if (cli.reportPath !== void 0) {
19363
+ log.info(`${S.reportSaved}: ${theme.dim(path33.resolve(cli.reportPath))}`);
19364
+ }
19365
+ if (quiet) {
19366
+ emitJson(doc, results.every((r2) => r2.ok) ? 0 : 1);
19367
+ }
19368
+ for (const r2 of results) {
19369
+ if (!r2.ok) log.error(`${r2.forge}: ${r2.detail}`);
19370
+ }
19371
+ if (results.some((r2) => !r2.ok)) process.exit(1);
19372
+ const bins = results.map((r2) => {
19373
+ const inst = loadForgeState(home).forges[r2.forge];
19374
+ return inst ? `${r2.forge} \u2192 ${inst.bin}` : "";
19375
+ });
19376
+ note(bins.filter(Boolean).join("\n"), "Para usar, adicione ao PATH ou invoque o caminho");
19377
+ outro(`${icons.ok} instala\xE7\xE3o registry-first conclu\xEDda`);
19378
+ return results;
19379
+ }
19380
+ function runRegistryList(cli, deps = {}) {
19381
+ const home = deps.home ?? cliHome();
19382
+ const state = loadForgeState(home);
19383
+ const wanted = deps.names?.length ? new Set(deps.names.map((n4) => registryEntry(n4)?.name).filter((n4) => Boolean(n4))) : null;
19384
+ if (deps.names?.length) {
19385
+ const bad = deps.names.filter((n4) => !registryEntry(n4));
19386
+ if (bad.length) {
19387
+ const msg = `${bad.join(", ")}: fora do cat\xE1logo registry`;
19388
+ if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, { error: msg });
19389
+ if (registryQuiet(cli)) emitJson({ error: msg }, 2);
19390
+ log.error(msg);
19391
+ process.exit(2);
19392
+ }
19393
+ }
19394
+ const rows = REGISTRY_PACKAGES.filter((e) => !wanted || wanted.has(e.name)).map((e) => {
19395
+ const inst = state.forges[e.name];
19396
+ const binOk = inst ? typeof inst.bin === "string" && existsSync21(inst.bin) : null;
19397
+ return {
19398
+ forge: e.name,
19399
+ dist: e.dist,
19400
+ installed: inst?.version ?? null,
19401
+ method: inst?.method ?? null,
19402
+ verifyOk: inst ? inst.verify?.ok ?? null : null,
19403
+ binPresent: binOk,
19404
+ bin: inst ? binOk ? inst.bin : `${inst.bin ?? "?"} (ausente)` : null,
19405
+ minPython: e.minPython
19406
+ };
19407
+ });
19408
+ if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, { forges: rows });
19409
+ if (registryQuiet(cli)) emitJson({ forges: rows });
19410
+ note(
19411
+ rows.map(
19412
+ (r2) => `${r2.installed ? icons.ok : "\xB7"} ${r2.forge.padEnd(20)} ${r2.dist.padEnd(20)} ${(r2.installed ?? "\u2014") + (r2.installed && r2.verifyOk === false ? ` ${S.registryVerifyFailed}` : "")}`
19413
+ ).join("\n"),
19414
+ "Forjas (registry)"
19415
+ );
19416
+ }
19417
+ function resolveRegistryTargets(cli, names, installed) {
19418
+ if (names.length) return names;
19419
+ if (cli.only?.length) return cli.only;
19420
+ if (cli.all) return installed;
19421
+ return [];
19422
+ }
19423
+ async function runRegistryUninstall(cli, names, deps = {}) {
19424
+ const home = deps.home ?? cliHome();
19425
+ const quiet = registryQuiet(cli);
19426
+ const reportTo = (doc2) => {
19427
+ if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, doc2);
19428
+ };
19429
+ const state = loadForgeState(home).forges;
19430
+ const installed = Object.keys(state);
19431
+ const targets = resolveRegistryTargets(cli, names, installed);
19432
+ if (!targets.length) {
19433
+ reportTo({ error: S.errRegistryUninstallNeedsNames });
19434
+ if (quiet) emitJson({ error: S.errRegistryUninstallNeedsNames }, 2);
19435
+ log.error(S.errRegistryUninstallNeedsNames);
19436
+ process.exit(2);
19437
+ }
19438
+ const ignored = ignoredRegistryFlags(cli);
19439
+ const warnings = ignored.length ? [`${S.registryIgnoredFlags}: ${ignored.join(", ")}`] : [];
19440
+ if (warnings.length && !quiet) log.warn(warnings[0]);
19441
+ const plan = [];
19442
+ const unsafe = [];
19443
+ const seen = /* @__PURE__ */ new Set();
19444
+ for (const raw of targets) {
19445
+ const entry2 = registryEntry(raw);
19446
+ const name = entry2?.name ?? raw;
19447
+ if (!isSafeRepoName(name)) {
19448
+ unsafe.push(raw);
19449
+ continue;
19450
+ }
19451
+ if (seen.has(name)) continue;
19452
+ seen.add(name);
19453
+ const receipt = Object.hasOwn(state, name) ? state[name] : void 0;
19454
+ plan.push({
19455
+ name,
19456
+ dist: entry2?.dist ?? receipt?.dist,
19457
+ known: Boolean(entry2 ?? receipt),
19458
+ installed: installed.includes(name)
19459
+ });
19460
+ }
19461
+ const unknown = [...plan.filter((p) => !p.known).map((p) => p.name), ...unsafe];
19462
+ const errText = [
19463
+ ...plan.filter((p) => !p.known).map((p) => `${p.name}: sem dist no cat\xE1logo registry`),
19464
+ ...unsafe.map((u4) => `${u4}: nome inseguro no state \u2014 recusado (path traversal)`)
19465
+ ].join(" | ");
19466
+ const removals = plan.filter((p) => p.installed);
19467
+ const skipped = plan.filter((p) => p.known && !p.installed).map((p) => p.name);
19468
+ warnings.push(...skipped.map((n4) => `${n4}: n\xE3o estava instalado via registry \u2014 ignorado`));
19469
+ if (!quiet) {
19470
+ for (const u4 of plan.filter((p) => !p.known).map((p) => p.name))
19471
+ log.error(`${u4}: sem dist no cat\xE1logo registry`);
19472
+ for (const u4 of unsafe) log.error(`${u4}: nome inseguro no state \u2014 recusado (path traversal)`);
19473
+ for (const w of warnings.slice(ignored.length ? 1 : 0)) log.warn(w);
19474
+ }
19475
+ if (!removals.length) {
19476
+ const doc2 = {
19477
+ method: "pypi",
19478
+ removed: [],
19479
+ skipped,
19480
+ warnings,
19481
+ ...errText ? { error: errText } : {}
19482
+ };
19483
+ reportTo(doc2);
19484
+ if (quiet) emitJson(doc2, unknown.length ? 2 : 0);
19485
+ if (unknown.length) process.exit(2);
19486
+ log.info("Nada a remover.");
19487
+ return;
19488
+ }
19489
+ if (!quiet) {
19490
+ note(
19491
+ removals.map((r2) => `${r2.name} \u2192 ${r2.dist ?? "?"} (venv + wheel cache + receipt em ~/.forge-cli/)`).join("\n"),
19492
+ S.registryUninstallConfirm
19493
+ );
19494
+ }
19495
+ if (!cli.yes) {
19496
+ const needYesDoc = {
19497
+ error: S.registryUninstallNeedsYes,
19498
+ planned: removals.map((r2) => r2.name),
19499
+ skipped,
19500
+ warnings
19501
+ };
19502
+ if (quiet) {
19503
+ emitJson(needYesDoc, 2);
19504
+ }
19505
+ if (!(process.stdin.isTTY && process.stdout.isTTY)) {
19506
+ reportTo(needYesDoc);
19507
+ log.warn(S.registryUninstallNeedsYes);
19508
+ process.exit(0);
19509
+ return;
19510
+ }
19511
+ const answer = await confirm({ message: S.registryUninstallConfirm });
19512
+ if (isCancel(answer) || !answer) {
19513
+ if (isCancel(answer)) exitCancelled();
19514
+ reportTo(needYesDoc);
19515
+ log.warn(S.registryUninstallNeedsYes);
19516
+ process.exit(0);
19517
+ return;
19518
+ }
19519
+ }
19520
+ const results = [];
19521
+ for (const r2 of removals) {
19522
+ let native;
19523
+ if (cli.native) {
19524
+ const repo = FORGE_REPOS.find((x) => x.name === r2.name);
19525
+ const receipt = Object.hasOwn(state, r2.name) ? state[r2.name] : void 0;
19526
+ const op = repo && buildNativeOp(repo, "uninstall", cli.yes ?? false);
19527
+ if (!op) {
19528
+ native = { argv: [], ran: false, skipped: "sem op nativa de uninstall documentada" };
19529
+ } else if (op.argv[0] === "{python}" || !receipt) {
19530
+ native = {
19531
+ argv: op.argv,
19532
+ ran: false,
19533
+ skipped: "op nativa por script do reposit\xF3rio \u2014 indispon\xEDvel no m\xE9todo registry"
19534
+ };
19535
+ } else {
19536
+ const argv = [receipt.bin, ...op.argv.slice(1)];
19537
+ const res2 = await (deps.run ?? execaRun)(argv[0], argv.slice(1));
19538
+ native = { argv, ran: true, dryRun: op.dryRun, exitCode: res2.exitCode };
19539
+ if (res2.exitCode !== 0) warnings.push(`${r2.name}: uninstall nativo saiu ${res2.exitCode}`);
19540
+ }
19541
+ }
19542
+ const res = registryUninstall({ name: r2.name, dist: r2.dist }, { home });
19543
+ if (res.refused) log.error(`${r2.name}: recusado \u2014 ${res.refused}`);
19544
+ results.push({
19545
+ forge: r2.name,
19546
+ removed: res.removed,
19547
+ cacheRemoved: res.cacheRemoved,
19548
+ ...native ? { native } : {}
19549
+ });
19550
+ }
19551
+ const doc = {
19552
+ method: "pypi",
19553
+ results,
19554
+ skipped,
19555
+ warnings,
19556
+ ...errText ? { error: errText } : {}
19557
+ };
19558
+ reportTo(doc);
19559
+ if (quiet) emitJson(doc, unknown.length ? 2 : 0);
19560
+ for (const r2 of results) {
19561
+ if (r2.removed) log.success(`${r2.forge}: removido`);
19562
+ }
19563
+ if (unknown.length) process.exit(2);
19564
+ }
19565
+ function entryFor(name, receipt) {
19566
+ return registryEntry(name) ?? ghostEntry(name, receipt);
19567
+ }
19568
+ function lifecycleTargets(cli, names, home) {
19569
+ const state = loadForgeState(home).forges;
19570
+ const targets = resolveRegistryTargets(cli, names, Object.keys(state));
19571
+ const ok = [];
19572
+ const unknown = [];
19573
+ const skipped = [];
19574
+ const seen = /* @__PURE__ */ new Set();
19575
+ for (const raw of targets) {
19576
+ const entry2 = registryEntry(raw);
19577
+ const name = entry2?.name ?? raw;
19578
+ if (!isSafeRepoName(name) || seen.has(name)) {
19579
+ if (!seen.has(name) && !isSafeRepoName(name)) unknown.push(raw);
19580
+ continue;
19581
+ }
19582
+ seen.add(name);
19583
+ const receipt = Object.hasOwn(state, name) ? state[name] : void 0;
19584
+ if (!receipt) {
19585
+ if (entry2) skipped.push(name);
19586
+ else unknown.push(name);
19587
+ continue;
19588
+ }
19589
+ ok.push({ name, receipt });
19590
+ }
19591
+ return { ok, unknown, skipped };
19592
+ }
19593
+ async function runRegistryLifecycle(cli, names, deps) {
19594
+ const home = deps.home ?? cliHome();
19595
+ const quiet = registryQuiet(cli);
19596
+ const reportTo = (doc2) => {
19597
+ if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, doc2);
19598
+ };
19599
+ const { ok: targets, unknown, skipped } = lifecycleTargets(cli, names, home);
19600
+ if (!targets.length && !skipped.length && !unknown.length) {
19601
+ const msg = "nenhuma Forja instalada via registry \u2014 nada a atualizar";
19602
+ reportTo({ error: msg });
19603
+ if (quiet) emitJson({ error: msg }, 2);
19604
+ log.error(msg);
19605
+ process.exit(2);
19606
+ return;
19607
+ }
19608
+ const ignored = ignoredRegistryFlags(cli);
19609
+ const warnings = [
19610
+ ...skipped.map((n4) => `${n4}: n\xE3o estava instalado via registry \u2014 ignorado`),
19611
+ ...ignored.length ? [`${S.registryIgnoredFlags}: ${ignored.join(", ")}`] : []
19612
+ ];
19613
+ const pinVersion2 = cli.ref?.replace(/^v(?=\d)/, "");
19614
+ const isCheck = cli.plan || cli.check;
19615
+ const items = [];
19616
+ for (const t2 of targets) {
19617
+ const entry2 = entryFor(t2.name, t2.receipt);
19618
+ if (t2.receipt.pinned && !pinVersion2) {
19619
+ items.push({
19620
+ ...t2,
19621
+ entry: entry2,
19622
+ action: "update",
19623
+ from: t2.receipt.version,
19624
+ detail: `${S.registryPinnedAt} ${t2.receipt.pinned}`
19625
+ });
19626
+ continue;
19627
+ }
19628
+ const resolved = await resolveRelease(entry2.dist, {
19629
+ pin: pinVersion2,
19630
+ offline: cli.offline,
19631
+ cacheDir: registryCacheDir(home),
19632
+ fetchImpl: deps.fetchImpl,
19633
+ ephemeral: isCheck
19634
+ // P0-002: update --check não persiste nem cache
19635
+ });
19636
+ if ("kind" in resolved) {
19637
+ items.push({
19638
+ ...t2,
19639
+ entry: entry2,
19640
+ action: "update",
19641
+ from: t2.receipt.version,
19642
+ detail: describeResolveError(resolved)
19643
+ });
19644
+ continue;
19645
+ }
19646
+ const same = resolved.version === t2.receipt.version;
19647
+ items.push({
19648
+ ...t2,
19649
+ entry: entry2,
19650
+ action: "update",
19651
+ from: t2.receipt.version,
19652
+ to: resolved.version,
19653
+ detail: same ? "j\xE1 na mais recente" : void 0
19654
+ });
19655
+ }
19656
+ if (isCheck) {
19657
+ const doc2 = {
19658
+ method: "pypi",
19659
+ plan: true,
19660
+ updates: items.map((i3) => ({
19661
+ forge: i3.name,
19662
+ from: i3.from,
19663
+ to: i3.to ?? null,
19664
+ status: i3.detail ?? "update"
19665
+ })),
19666
+ skipped,
19667
+ warnings,
19668
+ ...unknown.length ? { error: `${unknown.join(", ")}: sem dist no cat\xE1logo registry` } : {}
19669
+ };
19670
+ reportTo(doc2);
19671
+ if (quiet) emitJson(doc2, unknown.length ? 2 : 0);
19672
+ note(
19673
+ items.map((i3) => `${i3.name}: ${i3.from} \u2192 ${i3.to ?? "\u2014"}${i3.detail ? ` (${i3.detail})` : ""}`).join("\n"),
19674
+ "Plano de update"
19675
+ );
19676
+ if (unknown.length) process.exit(2);
19677
+ return;
19678
+ }
19679
+ const actionable = items.filter((i3) => i3.to && i3.to !== i3.from);
19680
+ if (!quiet) {
19681
+ for (const i3 of items.filter((i4) => i4.detail)) log.warn(`${i3.name}: ${i3.detail}`);
19682
+ for (const w of warnings) log.warn(w);
19683
+ note(
19684
+ actionable.length ? actionable.map((i3) => `${i3.name}: ${i3.from} \u2192 ${i3.to}`).join("\n") : "nada a atualizar",
19685
+ "Update registry-first"
19686
+ );
19687
+ }
19688
+ if (!actionable.length) {
19689
+ const doc2 = {
19690
+ method: "pypi",
19691
+ updated: [],
19692
+ skipped,
19693
+ warnings,
19694
+ ...unknown.length ? { error: `${unknown.join(", ")}: sem dist no cat\xE1logo registry` } : {}
19695
+ };
19696
+ reportTo(doc2);
19697
+ if (quiet) emitJson(doc2, unknown.length ? 2 : skipped.length && !items.length ? 1 : 0);
19698
+ if (!quiet) log.info("Nada a atualizar.");
19699
+ if (unknown.length) process.exit(2);
19700
+ if (skipped.length && !items.length) process.exit(1);
19701
+ return;
19702
+ }
19703
+ if (!quiet && !cli.yes && process.stdout.isTTY && process.stdin.isTTY) {
19704
+ const answer = await confirm({ message: S.confirmRun });
19705
+ if (isCancel(answer) || !answer) {
19706
+ if (isCancel(answer)) exitCancelled();
19707
+ reportTo({ error: S.registryUninstallNeedsYes });
19708
+ log.warn(S.registryUninstallNeedsYes);
19709
+ process.exit(0);
19710
+ return;
19711
+ }
19712
+ }
19713
+ const pythons = deps.pythons ?? await probePythons();
19714
+ const results = [];
19715
+ const state = loadForgeState(home);
19716
+ for (const i3 of actionable) {
19717
+ const spin = spinner();
19718
+ if (!quiet) spin.start(`${i3.name}: ${i3.from} \u2192 ${i3.to}\u2026`);
19719
+ const receipt = await registryInstall(i3.entry, i3.to, {
19720
+ home,
19721
+ cacheDir: registryCacheDir(home),
19722
+ offline: cli.offline,
19723
+ pythons,
19724
+ clean: true,
19725
+ fetchImpl: deps.fetchImpl,
19726
+ run: deps.run
19727
+ });
19728
+ if ("stage" in receipt) {
19729
+ if (!quiet) spin.stop(`${i3.name}: ${theme.err("falhou")} (${describeInstallError(receipt)})`);
19730
+ results.push({
19731
+ forge: i3.name,
19732
+ ok: false,
19733
+ from: i3.from,
19734
+ to: i3.to,
19735
+ detail: describeInstallError(receipt)
19736
+ });
19737
+ continue;
19738
+ }
19739
+ const prev = state.forges[i3.name];
19740
+ receipt.history = [
19741
+ ...i3.receipt.history ?? [],
19742
+ { version: i3.from, installedAt: i3.receipt.installedAt }
19743
+ ];
19744
+ receipt.pinned = prev?.pinned;
19745
+ receipt.native = await delegateNative(cli, i3.entry, receipt, "update", deps.run);
19746
+ if (receipt.native?.skipped) warnings.push(`${i3.name}: ${receipt.native.skipped}`);
19747
+ if (receipt.native?.ran && receipt.native.exitCode !== 0)
19748
+ warnings.push(`${i3.name}: instalador nativo saiu ${receipt.native.exitCode}`);
19749
+ state.forges[i3.name] = receipt;
19750
+ if (!quiet) spin.stop(`${i3.name}: ${theme.ok(`${i3.from} \u2192 ${i3.to}`)}`);
19751
+ results.push({ forge: i3.name, ok: true, from: i3.from, to: i3.to, bin: receipt.bin });
19752
+ }
19753
+ saveForgeState(home, state);
19754
+ const doc = {
19755
+ method: "pypi",
19756
+ updated: results,
19757
+ skipped,
19758
+ warnings,
19759
+ ...unknown.length ? { error: `${unknown.join(", ")}: sem dist no cat\xE1logo registry` } : {}
19760
+ };
19761
+ reportTo(doc);
19762
+ if (quiet) emitJson(doc, results.every((r2) => r2.ok) ? unknown.length ? 2 : 0 : 1);
19763
+ if (results.some((r2) => !r2.ok)) process.exit(1);
19764
+ if (unknown.length) process.exit(2);
19765
+ }
19766
+ async function runRegistryUpdate(cli, names, deps = {}) {
19767
+ await runRegistryLifecycle(cli, names, deps);
19768
+ }
19769
+ async function runRegistryReinstall(cli, names, deps = {}) {
19770
+ const home = deps.home ?? cliHome();
19771
+ const quiet = registryQuiet(cli);
19772
+ const reportTo = (doc2) => {
19773
+ if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, doc2);
19774
+ };
19775
+ const { ok: targets, unknown, skipped } = lifecycleTargets(cli, names, home);
19776
+ const ignored = ignoredRegistryFlags(cli);
19777
+ const warnings = [
19778
+ ...skipped.map((n4) => `${n4}: n\xE3o instalado via registry \u2014 ignorado`),
19779
+ ...ignored.length ? [`${S.registryIgnoredFlags}: ${ignored.join(", ")}`] : []
19780
+ ];
19781
+ if (!targets.length) {
19782
+ const msg = S.errRegistryReinstallNeedsNames;
19783
+ reportTo({ error: msg, warnings, ...unknown.length ? { unknown } : {} });
19784
+ if (quiet) emitJson({ error: msg, warnings, unknown }, unknown.length ? 2 : 1);
19785
+ log.error(msg);
19786
+ process.exit(unknown.length ? 2 : 1);
19787
+ return;
19788
+ }
19789
+ if (cli.plan || cli.check) {
19790
+ const doc2 = {
19791
+ method: "pypi",
19792
+ plan: true,
19793
+ reinstalls: targets.map((t2) => ({ forge: t2.name, version: t2.receipt.version })),
19794
+ skipped,
19795
+ warnings
19796
+ };
19797
+ reportTo(doc2);
19798
+ if (quiet) emitJson(doc2, 0);
19799
+ note(
19800
+ targets.map((t2) => `${t2.name}==${t2.receipt.version} (reinstall limpo)`).join("\n"),
19801
+ "Plano de reinstall"
19802
+ );
19803
+ return;
19804
+ }
19805
+ const pythons = deps.pythons ?? await probePythons();
19806
+ const results = [];
19807
+ const state = loadForgeState(home);
19808
+ for (const t2 of targets) {
19809
+ const spin = spinner();
19810
+ if (!quiet) spin.start(`${t2.name}: reinstall ${t2.receipt.version}\u2026`);
19811
+ const receipt = await registryInstall(entryFor(t2.name, t2.receipt), t2.receipt.version, {
19812
+ home,
19813
+ cacheDir: registryCacheDir(home),
19814
+ offline: cli.offline,
19815
+ pythons,
19816
+ clean: true,
19817
+ fetchImpl: deps.fetchImpl,
19818
+ run: deps.run
19819
+ });
19820
+ if ("stage" in receipt) {
19821
+ if (!quiet) spin.stop(`${t2.name}: ${theme.err("falhou")} (${describeInstallError(receipt)})`);
19822
+ results.push({ forge: t2.name, ok: false, detail: describeInstallError(receipt) });
19823
+ continue;
19824
+ }
19825
+ receipt.pinned = t2.receipt.pinned;
19826
+ receipt.history = t2.receipt.history;
19827
+ receipt.native = await delegateNative(
19828
+ cli,
19829
+ entryFor(t2.name, t2.receipt),
19830
+ receipt,
19831
+ "update",
19832
+ deps.run
19833
+ );
19834
+ if (receipt.native?.skipped) warnings.push(`${t2.name}: ${receipt.native.skipped}`);
19835
+ if (receipt.native?.ran && receipt.native.exitCode !== 0)
19836
+ warnings.push(`${t2.name}: instalador nativo saiu ${receipt.native.exitCode}`);
19837
+ state.forges[t2.name] = receipt;
19838
+ if (!quiet) spin.stop(`${t2.name}: ${theme.ok("reinstalado")} (${receipt.version})`);
19839
+ results.push({ forge: t2.name, ok: true, version: receipt.version, bin: receipt.bin });
19840
+ }
19841
+ saveForgeState(home, state);
19842
+ const doc = {
19843
+ method: "pypi",
19844
+ reinstalled: results,
19845
+ skipped,
19846
+ warnings,
19847
+ ...unknown.length ? { error: `${unknown.join(", ")}: sem dist no cat\xE1logo registry` } : {}
19848
+ };
19849
+ reportTo(doc);
19850
+ if (quiet) emitJson(doc, results.every((r2) => r2.ok) ? unknown.length ? 2 : 0 : 1);
19851
+ if (results.some((r2) => !r2.ok)) process.exit(1);
19852
+ if (unknown.length) process.exit(2);
19853
+ }
19854
+ async function runRegistryRollback(cli, names, deps = {}) {
19855
+ const home = deps.home ?? cliHome();
19856
+ const quiet = registryQuiet(cli);
19857
+ const reportTo = (doc2) => {
19858
+ if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, doc2);
19859
+ };
19860
+ const { ok: targets, unknown, skipped } = lifecycleTargets(cli, names, home);
19861
+ const ignored = ignoredRegistryFlags(cli);
19862
+ const warnings = [
19863
+ ...skipped.map((n4) => `${n4}: n\xE3o instalado via registry \u2014 ignorado`),
19864
+ ...ignored.length ? [`${S.registryIgnoredFlags}: ${ignored.join(", ")}`] : []
19865
+ ];
19866
+ const plan = targets.map((t2) => {
19867
+ const prev = t2.receipt.history?.at(-1);
19868
+ return {
19869
+ name: t2.name,
19870
+ receipt: t2.receipt,
19871
+ to: prev?.version,
19872
+ detail: prev ? void 0 : S.rollbackNoPrevious
19873
+ };
19874
+ });
19875
+ const actionable = plan.filter((p) => p.to);
19876
+ if (cli.plan || cli.check) {
19877
+ const doc2 = {
19878
+ method: "pypi",
19879
+ plan: true,
19880
+ rollbacks: plan.map((p) => ({
19881
+ forge: p.name,
19882
+ from: p.receipt.version,
19883
+ to: p.to ?? null,
19884
+ status: p.detail ?? "rollback"
19885
+ })),
19886
+ skipped,
19887
+ warnings
19888
+ };
19889
+ reportTo(doc2);
19890
+ if (quiet) emitJson(doc2, 0);
19891
+ note(
19892
+ plan.map(
19893
+ (p) => `${p.name}: ${p.receipt.version} \u2192 ${p.to ?? "\u2014"}${p.detail ? ` (${p.detail})` : ""}`
19894
+ ).join("\n"),
19895
+ "Plano de rollback"
19896
+ );
19897
+ return;
19898
+ }
19899
+ if (!actionable.length) {
19900
+ const msg = targets.length ? `${S.rollbackNoPrevious} \u2014 ${targets.map((t2) => t2.name).join(", ")}` : S.errRegistryRollbackNeedsNames;
19901
+ reportTo({ error: msg, warnings, ...unknown.length ? { unknown } : {} });
19902
+ if (quiet) emitJson({ error: msg, warnings, unknown }, 1);
19903
+ log.error(msg);
19904
+ process.exit(1);
19905
+ return;
19906
+ }
19907
+ const pythons = deps.pythons ?? await probePythons();
19908
+ const results = [];
19909
+ const state = loadForgeState(home);
19910
+ for (const p of actionable) {
19911
+ const spin = spinner();
19912
+ if (!quiet) spin.start(`${p.name}: ${p.receipt.version} \u2192 ${p.to}\u2026`);
19913
+ const receipt = await registryInstall(entryFor(p.name, p.receipt), p.to, {
19914
+ home,
19915
+ cacheDir: registryCacheDir(home),
19916
+ offline: cli.offline,
19917
+ pythons,
19918
+ clean: true,
19919
+ fetchImpl: deps.fetchImpl,
19920
+ run: deps.run
19921
+ });
19922
+ if ("stage" in receipt) {
19923
+ if (!quiet) spin.stop(`${p.name}: ${theme.err("falhou")} (${describeInstallError(receipt)})`);
19924
+ results.push({ forge: p.name, ok: false, detail: describeInstallError(receipt) });
19925
+ continue;
19926
+ }
19927
+ const rest = (p.receipt.history ?? []).slice(0, -1);
19928
+ receipt.history = [...rest, { version: p.receipt.version, installedAt: p.receipt.installedAt }];
19929
+ receipt.pinned = p.receipt.pinned;
19930
+ state.forges[p.name] = receipt;
19931
+ if (!quiet) spin.stop(`${p.name}: ${theme.ok(`${p.receipt.version} \u2192 ${p.to}`)}`);
19932
+ results.push({ forge: p.name, ok: true, from: p.receipt.version, to: p.to, bin: receipt.bin });
19933
+ }
19934
+ saveForgeState(home, state);
19935
+ const doc = {
19936
+ method: "pypi",
19937
+ rolledBack: results,
19938
+ skipped,
19939
+ warnings,
19940
+ ...unknown.length ? { error: `${unknown.join(", ")}: sem dist no cat\xE1logo registry` } : {}
19941
+ };
19942
+ reportTo(doc);
19943
+ if (quiet) emitJson(doc, results.every((r2) => r2.ok) ? unknown.length ? 2 : 0 : 1);
19944
+ if (results.some((r2) => !r2.ok)) process.exit(1);
19945
+ if (unknown.length) process.exit(2);
19946
+ }
19947
+ async function runRegistryPin(cli, names, pin, deps = {}) {
19948
+ const home = deps.home ?? cliHome();
19949
+ const quiet = registryQuiet(cli);
19950
+ const reportTo = (doc2) => {
19951
+ if (cli.reportPath !== void 0) writeRegistryReport(cli.reportPath, doc2);
19952
+ };
19953
+ const { ok: targets, unknown, skipped } = lifecycleTargets(cli, names, home);
19954
+ const ignored = ignoredRegistryFlags(cli);
19955
+ const warnings = [
19956
+ ...skipped.map((n4) => `${n4}: n\xE3o instalado via registry \u2014 ignorado`),
19957
+ ...ignored.length ? [`${S.registryIgnoredFlags}: ${ignored.join(", ")}`] : []
19958
+ ];
19959
+ if (!targets.length) {
19960
+ const msg = pin ? S.errRegistryPinNeedsNames : S.errRegistryUnpinNeedsNames;
19961
+ reportTo({ error: msg, warnings, unknown });
19962
+ if (quiet) emitJson({ error: msg, warnings, unknown }, unknown.length ? 2 : 1);
19963
+ log.error(msg);
19964
+ process.exit(unknown.length ? 2 : 1);
19965
+ return;
19966
+ }
19967
+ const state = loadForgeState(home);
19968
+ const done = [];
19969
+ for (const t2 of targets) {
19970
+ if (pin) {
19971
+ state.forges[t2.name].pinned = t2.receipt.version;
19972
+ done.push({ forge: t2.name, pinned: t2.receipt.version });
19973
+ } else {
19974
+ delete state.forges[t2.name].pinned;
19975
+ done.push({ forge: t2.name, pinned: null });
19976
+ }
19977
+ }
19978
+ saveForgeState(home, state);
19979
+ const doc = {
19980
+ method: "pypi",
19981
+ [pin ? "pinned" : "unpinned"]: done,
19982
+ skipped,
19983
+ warnings,
19984
+ ...unknown.length ? { error: `${unknown.join(", ")}: sem dist no cat\xE1logo registry` } : {}
19985
+ };
19986
+ reportTo(doc);
19987
+ if (quiet) emitJson(doc, unknown.length ? 2 : 0);
19988
+ for (const d of done) {
19989
+ log.success(pin ? `${d.forge}: ${S.pinDone} ${d.pinned}` : `${d.forge}: ${S.unpinDone}`);
19990
+ }
19991
+ if (unknown.length) process.exit(2);
19992
+ }
19993
+
19994
+ // src/lifecycle.ts
19995
+ import { copyFileSync as copyFileSync3, existsSync as existsSync22, mkdirSync as mkdirSync10, readdirSync as readdirSync9, rmSync as rmSync5 } from "fs";
19996
+ import path34 from "path";
19997
+ function configFilesToBackup(repoDir) {
19998
+ const out = /* @__PURE__ */ new Set();
19999
+ if (existsSync22(path34.join(repoDir, ".env"))) out.add(".env");
20000
+ for (const file of readdirSync9(repoDir)) {
20001
+ if (file === ".env.example" || !file.includes(".example.")) continue;
20002
+ const real = file.replace(".example.", ".");
20003
+ if (existsSync22(path34.join(repoDir, real))) out.add(real);
20004
+ }
20005
+ return [...out];
20006
+ }
20007
+ async function planUninstall(targetDir, repo) {
20008
+ const dir = resolveInside(targetDir, repo.name);
20009
+ if (!dir)
20010
+ return { kind: "refused", dir: path34.join(targetDir, repo.name), reason: "invalid-name" };
20011
+ if (isLinkOrJunction(dir)) return { kind: "refused", dir, reason: "symlink" };
20012
+ if (!existsSync22(dir)) return { kind: "absent", dir };
20013
+ if (!hasGitRepo(dir)) return { kind: "refused", dir, reason: "non-git" };
20014
+ const remote = await commandOutput("git", ["-C", dir, "remote", "get-url", "origin"]);
20015
+ if (remote && remote !== "ok" && !sameRemote(remote, repo.url)) {
20016
+ return { kind: "refused", dir, reason: "remote-mismatch" };
20017
+ }
20018
+ if (await isGitDirty(dir)) return { kind: "refused", dir, reason: "dirty" };
20019
+ return { kind: "ready", dir, backups: configFilesToBackup(dir) };
20020
+ }
20021
+ function executeUninstall(targetDir, repo, plan) {
20022
+ const expected = resolveInside(targetDir, repo.name);
18259
20023
  if (!expected || expected !== plan.dir) throw new Error("plano de remo\xE7\xE3o inconsistente");
18260
20024
  let backupDir = null;
18261
20025
  if (plan.backups.length) {
18262
- backupDir = path31.join(artifactsRoot(targetDir), "backups", `${repo.name}-${Date.now()}`);
20026
+ backupDir = path34.join(artifactsRoot(targetDir), "backups", `${repo.name}-${Date.now()}`);
18263
20027
  for (const file of plan.backups) {
18264
- const dest = path31.join(backupDir, file);
18265
- mkdirSync7(path31.dirname(dest), { recursive: true });
18266
- copyFileSync3(path31.join(plan.dir, file), dest);
20028
+ const dest = path34.join(backupDir, file);
20029
+ mkdirSync10(path34.dirname(dest), { recursive: true });
20030
+ copyFileSync3(path34.join(plan.dir, file), dest);
18267
20031
  }
18268
20032
  }
18269
- rmSync4(plan.dir, { recursive: true, force: true });
20033
+ rmSync5(plan.dir, { recursive: true, force: true });
18270
20034
  forgetRepo(targetDir, repo.name);
18271
20035
  return backupDir;
18272
20036
  }
@@ -18286,13 +20050,13 @@ async function rollbackRepo(targetDir, repo) {
18286
20050
  }
18287
20051
 
18288
20052
  // src/self.ts
18289
- import path32 from "path";
20053
+ import path35 from "path";
18290
20054
  function norm(p) {
18291
- return path32.resolve(p).replace(/\\/g, "/").toLowerCase();
20055
+ return path35.resolve(p).replace(/\\/g, "/").toLowerCase();
18292
20056
  }
18293
20057
  function detectInstallMethod(scriptPath, home) {
18294
20058
  const script = norm(scriptPath);
18295
- if (script.startsWith(`${norm(path32.join(home, ".forge-cli", "bin"))}/`)) return "shim";
20059
+ if (script.startsWith(`${norm(path35.join(home, ".forge-cli", "bin"))}/`)) return "shim";
18296
20060
  if (script.includes("/node_modules/")) return "npm-global";
18297
20061
  return "source";
18298
20062
  }
@@ -18474,14 +20238,14 @@ async function runUninstallCommand() {
18474
20238
  }
18475
20239
 
18476
20240
  // src/config.ts
18477
- import { existsSync as existsSync20, readFileSync as readFileSync16 } from "fs";
18478
- import path33 from "path";
20241
+ import { existsSync as existsSync23, readFileSync as readFileSync18 } from "fs";
20242
+ import path36 from "path";
18479
20243
  function loadConfig(targetDir) {
18480
- const file = path33.join(targetDir, "forge-cli.config.json");
18481
- if (!existsSync20(file)) return null;
20244
+ const file = path36.join(targetDir, "forge-cli.config.json");
20245
+ if (!existsSync23(file)) return null;
18482
20246
  let parsed;
18483
20247
  try {
18484
- parsed = JSON.parse(readFileSync16(file, "utf8"));
20248
+ parsed = JSON.parse(readFileSync18(file, "utf8"));
18485
20249
  } catch {
18486
20250
  return { error: "invalid-json" };
18487
20251
  }
@@ -18524,12 +20288,12 @@ function loadConfig(targetDir) {
18524
20288
  }
18525
20289
 
18526
20290
  // src/plan.ts
18527
- import { existsSync as existsSync21, readdirSync as readdirSync9 } from "fs";
18528
- import path34 from "path";
20291
+ import { existsSync as existsSync24, readdirSync as readdirSync10 } from "fs";
20292
+ import path37 from "path";
18529
20293
  function planLines(repo, targetDir) {
18530
- const repoDir = path34.join(targetDir, repo.name);
20294
+ const repoDir = path37.join(targetDir, repo.name);
18531
20295
  const lines = [];
18532
- if (!existsSync21(repoDir)) {
20296
+ if (!existsSync24(repoDir)) {
18533
20297
  lines.push(`${S.planSync}: git clone (${S.planDetectAfter})`);
18534
20298
  for (const step of [
18535
20299
  S.phaseEnv,
@@ -18552,7 +20316,7 @@ function planLines(repo, targetDir) {
18552
20316
  lines.push(
18553
20317
  `${S.phaseEnv}: ${envAction === "exists" ? S.planEnvKeep : envAction === "copied" ? S.planEnvCopy : S.planEnvStub}`
18554
20318
  );
18555
- const examples = readdirSync9(repoDir).filter(
20319
+ const examples = readdirSync10(repoDir).filter(
18556
20320
  (f) => f !== ".env.example" && f.includes(".example.")
18557
20321
  );
18558
20322
  lines.push(
@@ -18568,11 +20332,11 @@ function planLines(repo, targetDir) {
18568
20332
  return lines;
18569
20333
  }
18570
20334
  async function executionSummary(repo, targetDir, native, platform2 = process.platform) {
18571
- const repoDir = path34.join(targetDir, repo.name);
20335
+ const repoDir = path37.join(targetDir, repo.name);
18572
20336
  const trust = repo.origin === "custom" ? "custom" : "builtin";
18573
20337
  const commands = [];
18574
20338
  let revision;
18575
- if (!existsSync21(repoDir) || !hasGitRepo(repoDir)) {
20339
+ if (!existsSync24(repoDir) || !hasGitRepo(repoDir)) {
18576
20340
  commands.push(`git clone -- ${repo.url}`, S.planScriptsAfterClone);
18577
20341
  } else {
18578
20342
  revision = (await currentHead(repoDir))?.slice(0, 7);
@@ -18802,8 +20566,8 @@ ${planLines(repo, targetDir).map((line) => ` ${theme.dim(line)}`).join("\n")}`
18802
20566
  }
18803
20567
 
18804
20568
  // src/cmd/env.ts
18805
- import { existsSync as existsSync23, readdirSync as readdirSync11 } from "fs";
18806
- import path36 from "path";
20569
+ import { existsSync as existsSync26, readdirSync as readdirSync12 } from "fs";
20570
+ import path39 from "path";
18807
20571
 
18808
20572
  // src/envintel/doctor.ts
18809
20573
  function envDoctor(repoDir, envKeys) {
@@ -18852,8 +20616,8 @@ function envDoctor(repoDir, envKeys) {
18852
20616
 
18853
20617
  // src/envintel/migrate.ts
18854
20618
  import { randomBytes } from "crypto";
18855
- import { existsSync as existsSync22, mkdirSync as mkdirSync8, readdirSync as readdirSync10, readFileSync as readFileSync17 } from "fs";
18856
- import path35 from "path";
20619
+ import { existsSync as existsSync25, mkdirSync as mkdirSync11, readdirSync as readdirSync11, readFileSync as readFileSync19 } from "fs";
20620
+ import path38 from "path";
18857
20621
  function timestamp2(d = /* @__PURE__ */ new Date()) {
18858
20622
  const p = (n4) => String(n4).padStart(2, "0");
18859
20623
  return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
@@ -18867,13 +20631,13 @@ function addValue(spec, exampleEmpty) {
18867
20631
  }
18868
20632
  function planMigration(repoDir, targetSchema) {
18869
20633
  const schema = targetSchema ?? deriveSchema(repoDir);
18870
- const envPath = path35.join(repoDir, ".env");
18871
- const env = existsSync22(envPath) ? parseEnv(readFileSync17(envPath, "utf8")) : null;
20634
+ const envPath = path38.join(repoDir, ".env");
20635
+ const env = existsSync25(envPath) ? parseEnv(readFileSync19(envPath, "utf8")) : null;
18872
20636
  const present = new Set(env?.entries.map((e) => e.key) ?? []);
18873
- const examplePath = path35.join(repoDir, ".env.example");
20637
+ const examplePath = path38.join(repoDir, ".env.example");
18874
20638
  const exampleEmpty = /* @__PURE__ */ new Set();
18875
- if (existsSync22(examplePath)) {
18876
- for (const e of parseEnv(readFileSync17(examplePath, "utf8")).entries) {
20639
+ if (existsSync25(examplePath)) {
20640
+ for (const e of parseEnv(readFileSync19(examplePath, "utf8")).entries) {
18877
20641
  if (e.value === "") exampleEmpty.add(e.key);
18878
20642
  }
18879
20643
  }
@@ -18901,19 +20665,19 @@ function planMigration(repoDir, targetSchema) {
18901
20665
  return { actions, removesRequireApproval: true };
18902
20666
  }
18903
20667
  function backupDirFor2(repoDir, ts) {
18904
- return path35.join(artifactsRoot(repoDir), "backups", `env-${ts}`);
20668
+ return path38.join(artifactsRoot(repoDir), "backups", `env-${ts}`);
18905
20669
  }
18906
20670
  function backupEnv(repoDir, content) {
18907
20671
  const dir = backupDirFor2(repoDir, timestamp2());
18908
- mkdirSync8(dir, { recursive: true });
18909
- const file = path35.join(dir, ".env");
20672
+ mkdirSync11(dir, { recursive: true });
20673
+ const file = path38.join(dir, ".env");
18910
20674
  writeFileAtomic(file, content);
18911
20675
  return dir;
18912
20676
  }
18913
20677
  function applyMigration(repoDir, plan, opts = {}) {
18914
- const envPath = path35.join(repoDir, ".env");
18915
- const exists = existsSync22(envPath);
18916
- const current = exists ? readFileSync17(envPath, "utf8") : "";
20678
+ const envPath = path38.join(repoDir, ".env");
20679
+ const exists = existsSync25(envPath);
20680
+ const current = exists ? readFileSync19(envPath, "utf8") : "";
18917
20681
  const env = parseEnv(current);
18918
20682
  const present = new Set(env.entries.map((e) => e.key));
18919
20683
  const removes = plan.actions.filter((a3) => a3.kind === "remove-requested" && present.has(a3.key));
@@ -18943,16 +20707,16 @@ function applyMigration(repoDir, plan, opts = {}) {
18943
20707
  };
18944
20708
  }
18945
20709
  function latestEnvBackup(repoDir) {
18946
- const root = path35.join(artifactsRoot(repoDir), "backups");
18947
- if (!existsSync22(root)) return null;
18948
- const candidates = readdirSync10(root, { withFileTypes: true }).filter((d) => d.isDirectory() && /^env-\d{8}-\d{6}$/.test(d.name)).map((d) => d.name).sort();
20710
+ const root = path38.join(artifactsRoot(repoDir), "backups");
20711
+ if (!existsSync25(root)) return null;
20712
+ const candidates = readdirSync11(root, { withFileTypes: true }).filter((d) => d.isDirectory() && /^env-\d{8}-\d{6}$/.test(d.name)).map((d) => d.name).sort();
18949
20713
  const last = candidates.at(-1);
18950
- return last ? path35.join(root, last) : null;
20714
+ return last ? path38.join(root, last) : null;
18951
20715
  }
18952
20716
  function rollbackEnv(repoDir, backupDir) {
18953
- const src = path35.join(backupDir, ".env");
18954
- if (!existsSync22(src)) return false;
18955
- writeFileAtomic(path35.join(repoDir, ".env"), readFileSync17(src, "utf8"));
20717
+ const src = path38.join(backupDir, ".env");
20718
+ if (!existsSync25(src)) return false;
20719
+ writeFileAtomic(path38.join(repoDir, ".env"), readFileSync19(src, "utf8"));
18956
20720
  return true;
18957
20721
  }
18958
20722
 
@@ -18960,9 +20724,9 @@ function rollbackEnv(repoDir, backupDir) {
18960
20724
  var VERBS = /* @__PURE__ */ new Set(["inspect", "plan", "init", "validate", "migrate", "doctor"]);
18961
20725
  function envCandidates(dir) {
18962
20726
  try {
18963
- return readdirSync11(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).filter((name) => {
20727
+ return readdirSync12(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).filter((name) => {
18964
20728
  const r2 = resolveInside(dir, name);
18965
- return r2 !== null && (existsSync23(path36.join(r2, ".env")) || existsSync23(path36.join(r2, ".env.example")));
20729
+ return r2 !== null && (existsSync26(path39.join(r2, ".env")) || existsSync26(path39.join(r2, ".env.example")));
18966
20730
  }).sort();
18967
20731
  } catch {
18968
20732
  return [];
@@ -19009,7 +20773,7 @@ async function runEnvCommand(cli, args) {
19009
20773
  envKeys = catalogFor(targetDir).find((r2) => r2.name === name)?.envKeys;
19010
20774
  } else {
19011
20775
  const cwd = process.cwd();
19012
- if (existsSync23(path36.join(cwd, ".env")) || existsSync23(path36.join(cwd, ".env.example"))) {
20776
+ if (existsSync26(path39.join(cwd, ".env")) || existsSync26(path39.join(cwd, ".env.example"))) {
19013
20777
  repoDir = cwd;
19014
20778
  }
19015
20779
  }
@@ -19019,7 +20783,7 @@ async function runEnvCommand(cli, args) {
19019
20783
  if (found.length) note(found.join("\n"), S.envTitle);
19020
20784
  return 2;
19021
20785
  }
19022
- const repo = name ?? path36.basename(repoDir);
20786
+ const repo = name ?? path39.basename(repoDir);
19023
20787
  if (verb === "inspect") {
19024
20788
  const res2 = inspectEnv(repoDir, envKeys);
19025
20789
  if (json) {
@@ -19061,13 +20825,13 @@ async function runEnvCommand(cli, args) {
19061
20825
  note(lines.join("\n"), `${S.envDoctorTitle} \u2014 ${repo}`);
19062
20826
  return res2.checks.some((c4) => c4.result === "fail") ? 1 : 0;
19063
20827
  }
19064
- if (!existsSync23(repoDir)) {
20828
+ if (!existsSync26(repoDir)) {
19065
20829
  log.error(`${repo}: ${S.statusNotInstalled}`);
19066
20830
  return 2;
19067
20831
  }
19068
20832
  const schema = deriveSchema(repoDir, envKeys);
19069
20833
  const secretKeys = new Set(schema.vars.filter((v) => v.secret).map((v) => v.key));
19070
- const envPath = path36.join(repoDir, ".env");
20834
+ const envPath = path39.join(repoDir, ".env");
19071
20835
  if (verb === "plan") {
19072
20836
  const plan2 = planMigration(repoDir, schema);
19073
20837
  const shown = maskedActions(plan2, secretKeys);
@@ -19090,7 +20854,7 @@ async function runEnvCommand(cli, args) {
19090
20854
  return 0;
19091
20855
  }
19092
20856
  if (verb === "init") {
19093
- if (existsSync23(envPath)) {
20857
+ if (existsSync26(envPath)) {
19094
20858
  if (json) console.log(JSON.stringify({ command: "env init", repo, created: false }));
19095
20859
  else log.info(S.envInitExists);
19096
20860
  return 0;
@@ -19163,6 +20927,165 @@ async function runEnvCommand(cli, args) {
19163
20927
  return 0;
19164
20928
  }
19165
20929
 
20930
+ // src/cmd/hosts.ts
20931
+ import path40 from "path";
20932
+ import { existsSync as existsSync27, readdirSync as readdirSync13 } from "fs";
20933
+
20934
+ // src/mcp/handshake.ts
20935
+ import { spawn as spawn2 } from "child_process";
20936
+ import { createInterface as createInterface2 } from "readline";
20937
+ var PROTOCOL_VERSION = "2024-11-05";
20938
+ var STEP_MS = 8e3;
20939
+ var MAX_LINE = 1 << 20;
20940
+ var MAX_LINES = 64;
20941
+ var err = (code, detail) => ({
20942
+ ok: false,
20943
+ levels: [],
20944
+ tools: null,
20945
+ serverInfo: null,
20946
+ protocolVersion: null,
20947
+ error: { code, detail: detail.slice(0, 300) }
20948
+ });
20949
+ function probeMcpServer(argv) {
20950
+ return new Promise((resolve) => {
20951
+ if (!argv.length || !argv[0]) {
20952
+ resolve(err("argv-empty", "comando MCP vazio"));
20953
+ return;
20954
+ }
20955
+ let child;
20956
+ try {
20957
+ child = spawn2(argv[0], argv.slice(1), { stdio: ["pipe", "pipe", "pipe"], shell: false });
20958
+ } catch (e) {
20959
+ resolve(err("spawn-failed", e instanceof Error ? e.message : String(e)));
20960
+ return;
20961
+ }
20962
+ const levels = [];
20963
+ let tools = null;
20964
+ let serverInfo = null;
20965
+ let protocolVersion = null;
20966
+ let stdoutGarbage = false;
20967
+ let done = false;
20968
+ const finish = (r2) => {
20969
+ if (done) return;
20970
+ done = true;
20971
+ clearTimeout(timer);
20972
+ child.kill("SIGTERM");
20973
+ resolve(r2);
20974
+ };
20975
+ const timer = setTimeout(() => {
20976
+ finish({
20977
+ ...err("timeout", `sem resposta em ${STEP_MS}ms (n\xEDveis: ${levels.join(",") || "nenhum"})`),
20978
+ levels
20979
+ });
20980
+ }, STEP_MS);
20981
+ const pending = /* @__PURE__ */ new Map();
20982
+ const rl = createInterface2({ input: child.stdout, crlfDelay: Infinity });
20983
+ let lines = 0;
20984
+ rl.on("line", (line) => {
20985
+ if (line.length > MAX_LINE || ++lines > MAX_LINES) {
20986
+ stdoutGarbage = true;
20987
+ finish({
20988
+ ...err("stdout-oversized", "sa\xEDda stdout excedeu limites \u2014 n\xE3o \xE9 um MCP stdio limpo"),
20989
+ levels
20990
+ });
20991
+ return;
20992
+ }
20993
+ let msg;
20994
+ try {
20995
+ msg = JSON.parse(line);
20996
+ } catch {
20997
+ stdoutGarbage = true;
20998
+ finish({
20999
+ ...err("stdout-non-json", "server escreveu n\xE3o-JSON no stdout (logs indevidos?)"),
21000
+ levels
21001
+ });
21002
+ return;
21003
+ }
21004
+ const id = msg.id;
21005
+ if (typeof id === "number" && pending.has(id)) {
21006
+ pending.get(id)(msg);
21007
+ pending.delete(id);
21008
+ }
21009
+ });
21010
+ child.on("error", (e) => finish({ ...err("spawn-failed", e.message), levels }));
21011
+ child.on("exit", (code) => {
21012
+ if (!done)
21013
+ finish({ ...err("server-exit", `exit ${code} antes do handshake completar`), levels });
21014
+ });
21015
+ const stderr = [];
21016
+ child.stderr?.on("data", (d) => {
21017
+ if (stderr.join("").length < 4096) stderr.push(d.toString("utf8"));
21018
+ });
21019
+ const request = (method, params, id) => new Promise((ok, bad) => {
21020
+ pending.set(id, ok);
21021
+ const stepTimer = setTimeout(() => {
21022
+ pending.delete(id);
21023
+ bad(new Error(`${method}: timeout por etapa`));
21024
+ }, STEP_MS);
21025
+ setTimeout(() => clearTimeout(stepTimer), STEP_MS + 50);
21026
+ child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
21027
+ `);
21028
+ });
21029
+ const notify = (method) => child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method })}
21030
+ `);
21031
+ void (async () => {
21032
+ try {
21033
+ const init = await request(
21034
+ "initialize",
21035
+ {
21036
+ protocolVersion: PROTOCOL_VERSION,
21037
+ capabilities: {},
21038
+ clientInfo: { name: "forge-cli", version: "0.1.0" }
21039
+ },
21040
+ 1
21041
+ );
21042
+ const res = init.result ?? {};
21043
+ if (init.error || typeof res !== "object" || !res.protocolVersion) {
21044
+ finish(
21045
+ err(
21046
+ "initialize-invalid",
21047
+ `resposta initialize inv\xE1lida: ${JSON.stringify(init).slice(0, 200)}`
21048
+ )
21049
+ );
21050
+ return;
21051
+ }
21052
+ protocolVersion = String(res.protocolVersion);
21053
+ const si = res.serverInfo;
21054
+ serverInfo = si ? { name: String(si.name ?? ""), version: String(si.version ?? "") } : null;
21055
+ levels.push("MCP_INITIALIZED");
21056
+ notify("notifications/initialized");
21057
+ const list = await request("tools/list", {}, 2);
21058
+ const tl = (list.result ?? {}).tools;
21059
+ if (list.error || !Array.isArray(tl)) {
21060
+ finish({
21061
+ ...err("tools-list-invalid", "tools/list ausente ou malformado"),
21062
+ levels,
21063
+ protocolVersion,
21064
+ serverInfo
21065
+ });
21066
+ return;
21067
+ }
21068
+ for (const t2 of tl) {
21069
+ if (typeof t2.name !== "string") {
21070
+ finish({
21071
+ ...err("tool-schema-invalid", "tool sem name:string"),
21072
+ levels,
21073
+ protocolVersion,
21074
+ serverInfo
21075
+ });
21076
+ return;
21077
+ }
21078
+ }
21079
+ tools = tl.length;
21080
+ levels.push("TOOLS_DISCOVERED");
21081
+ finish({ ok: !stdoutGarbage, levels, tools, serverInfo, protocolVersion, error: null });
21082
+ } catch (e) {
21083
+ finish({ ...err("handshake-failed", e instanceof Error ? e.message : String(e)), levels });
21084
+ }
21085
+ })();
21086
+ });
21087
+ }
21088
+
19166
21089
  // src/cmd/hosts.ts
19167
21090
  var STATE_LABEL = {
19168
21091
  absent: S.hostAbsent,
@@ -19189,9 +21112,26 @@ function planLines3(plan) {
19189
21112
  if (plan.copyable_command) lines.push(`${S.hostCopyCommand} ${plan.copyable_command}`);
19190
21113
  return lines.join("\n");
19191
21114
  }
19192
- function forgeDescriptor(targetDir, name) {
21115
+ function forgeDescriptor(targetDir, name, home = cliHome()) {
19193
21116
  const dir = resolveInside(targetDir, name);
19194
21117
  const manifest = dir ? readForgeManifest(dir) : null;
21118
+ if (manifest?.mcpCommand?.length) {
21119
+ return { forge_id: manifest.forgeId ?? name, mcp_command: manifest.mcpCommand };
21120
+ }
21121
+ const receipt = loadForgeState(home).forges[name];
21122
+ const entry2 = registryEntry(name);
21123
+ if (receipt?.bin && entry2?.mcp?.length) {
21124
+ const binDir = path40.dirname(receipt.bin);
21125
+ const mcp_command = entry2.mcp.map((tok) => {
21126
+ if (tok === "{bin}") return receipt.bin;
21127
+ if (tok.startsWith("{binDir}/")) {
21128
+ const script = tok.slice("{binDir}/".length);
21129
+ return path40.join(binDir, process.platform === "win32" ? `${script}.exe` : script);
21130
+ }
21131
+ return tok;
21132
+ });
21133
+ return { forge_id: name, mcp_command };
21134
+ }
19195
21135
  return { forge_id: manifest?.forgeId ?? name, mcp_command: manifest?.mcpCommand };
19196
21136
  }
19197
21137
  async function runHostsCommand(cli, args, home) {
@@ -19214,7 +21154,7 @@ async function runHostsCommand(cli, args, home) {
19214
21154
  return 2;
19215
21155
  }
19216
21156
  const targetDir = resolveTargetDir(cli.dir);
19217
- const forge = forgeDescriptor(targetDir, forgeName);
21157
+ const forge = forgeDescriptor(targetDir, forgeName, home);
19218
21158
  if (verb === "plan" || verb === "register") {
19219
21159
  const plan = await adapter.planIntegration(forge, targetDir, hostHome(home));
19220
21160
  if (!plan) {
@@ -19287,11 +21227,79 @@ async function runHostsCommand(cli, args, home) {
19287
21227
  log.error(`${S.errUnknownCommand}: ${args.join(" ")}`);
19288
21228
  return 2;
19289
21229
  }
21230
+ async function runMcpCommand(cli, args, home = cliHome()) {
21231
+ const json = cli.jsonReport;
21232
+ const verify = cli.verify;
21233
+ const names = args.filter((a3) => !a3.startsWith("-"));
21234
+ const targetDir = resolveTargetDir(cli.dir);
21235
+ const candidates = names.length > 0 ? names : [
21236
+ .../* @__PURE__ */ new Set([
21237
+ ...Object.keys(loadForgeState(home).forges),
21238
+ ...existsSync27(targetDir) ? readdirSync13(targetDir, { withFileTypes: true }).filter((d) => d.isDirectory() && isSafeRepoName(d.name)).map((d) => d.name) : []
21239
+ ])
21240
+ ].sort();
21241
+ if (candidates.length === 0) {
21242
+ if (json) process.stdout.write(`${JSON.stringify({ mcp: [] })}
21243
+ `);
21244
+ else log.warn("nenhuma Forja instalada/encontrada para MCP");
21245
+ return 1;
21246
+ }
21247
+ const results = [];
21248
+ for (const name of candidates) {
21249
+ if (!isSafeRepoName(name)) {
21250
+ results.push({ forge: name, ok: false, detail: "nome inseguro" });
21251
+ continue;
21252
+ }
21253
+ const forge = forgeDescriptor(targetDir, name, home);
21254
+ const argv = forge.mcp_command;
21255
+ if (!argv?.length) {
21256
+ results.push({ forge: name, ok: false, detail: "sem mcp_command" });
21257
+ continue;
21258
+ }
21259
+ const binPresent = path40.isAbsolute(argv[0]) ? existsSync27(argv[0]) : true;
21260
+ const item = {
21261
+ forge: name,
21262
+ forge_id: forge.forge_id,
21263
+ mcp_command: argv,
21264
+ binPresent,
21265
+ ok: binPresent
21266
+ };
21267
+ if (!binPresent) item.detail = "bin\xE1rio MCP ausente na venv";
21268
+ if (verify && binPresent && path40.isAbsolute(argv[0])) {
21269
+ const probe = await probeMcpServer(argv);
21270
+ item.probe = {
21271
+ ok: probe.ok,
21272
+ levels: probe.levels,
21273
+ tools: probe.tools,
21274
+ server: probe.serverInfo,
21275
+ protocol: probe.protocolVersion,
21276
+ error: probe.error
21277
+ };
21278
+ if (!probe.ok) {
21279
+ item.ok = false;
21280
+ item.detail = probe.error ? `${probe.error.code}: ${probe.error.detail}` : "probe falhou";
21281
+ }
21282
+ }
21283
+ results.push(item);
21284
+ }
21285
+ if (json) {
21286
+ process.stdout.write(`${JSON.stringify({ mcp: results }, null, 2)}
21287
+ `);
21288
+ } else {
21289
+ for (const r2 of results) {
21290
+ const rec = r2;
21291
+ if (rec.ok) log.info(`${icons.ok} ${rec.forge}: ${rec.mcp_command?.join(" ")}`);
21292
+ else log.warn(`${rec.forge}: ${rec.detail ?? "indispon\xEDvel"}`);
21293
+ }
21294
+ }
21295
+ const okCount = results.filter((r2) => r2.ok).length;
21296
+ return okCount === results.length ? 0 : okCount > 0 ? 0 : 1;
21297
+ }
19290
21298
 
19291
21299
  // src/discovery.ts
19292
- import { existsSync as existsSync24, readdirSync as readdirSync12, readFileSync as readFileSync18, statSync as statSync5 } from "fs";
19293
- import os4 from "os";
19294
- import path37 from "path";
21300
+ import { existsSync as existsSync28, readdirSync as readdirSync14, readFileSync as readFileSync20, statSync as statSync5 } from "fs";
21301
+ import os6 from "os";
21302
+ import path41 from "path";
19295
21303
  var DEFAULT_LIMITS = {
19296
21304
  maxDepth: 3,
19297
21305
  maxEntriesPerDir: 500,
@@ -19384,7 +21392,7 @@ function readBoundedText(file) {
19384
21392
  try {
19385
21393
  const st = statSync5(file);
19386
21394
  if (!st.isFile() || st.size > MAX_FILE_BYTES) return null;
19387
- const text2 = readFileSync18(file, "utf8");
21395
+ const text2 = readFileSync20(file, "utf8");
19388
21396
  if (text2.includes(String.fromCharCode(0))) return null;
19389
21397
  return text2;
19390
21398
  } catch {
@@ -19440,7 +21448,7 @@ function walkWorkspace(root, limits, budgetLeft) {
19440
21448
  const { dir, rel: rel2, depth } = current;
19441
21449
  let entries;
19442
21450
  try {
19443
- entries = readdirSync12(dir, { withFileTypes: true }).sort(
21451
+ entries = readdirSync14(dir, { withFileTypes: true }).sort(
19444
21452
  (a3, b) => a3.name < b.name ? -1 : a3.name > b.name ? 1 : 0
19445
21453
  );
19446
21454
  } catch {
@@ -19456,7 +21464,7 @@ function walkWorkspace(root, limits, budgetLeft) {
19456
21464
  return { markers, envFiles, topDirs, signals: signals2, truncated };
19457
21465
  }
19458
21466
  const lower = entry2.name.toLowerCase();
19459
- const full = path37.join(dir, entry2.name);
21467
+ const full = path41.join(dir, entry2.name);
19460
21468
  const relPath = rel2 ? `${rel2}/${entry2.name}` : entry2.name;
19461
21469
  if (entry2.isDirectory()) {
19462
21470
  if (skip.has(lower) || isLinkOrJunction(full)) continue;
@@ -19579,10 +21587,10 @@ function registryState(entry2) {
19579
21587
  function discoverForge(name, dir, state) {
19580
21588
  const sources = [];
19581
21589
  const git = !!dir && hasGitRepo(dir);
19582
- const manifest = dir && existsSync24(dir) ? readForgeManifest(dir) : null;
21590
+ const manifest = dir && existsSync28(dir) ? readForgeManifest(dir) : null;
19583
21591
  const entry2 = state?.repos[name];
19584
21592
  if (entry2 && entry2.phase !== "not-installed") {
19585
- const dirExists = !!dir && existsSync24(dir);
21593
+ const dirExists = !!dir && existsSync28(dir);
19586
21594
  sources.push({
19587
21595
  origin: "manifest",
19588
21596
  // registro diz instalada mas o checkout sumiu → evidência falhando
@@ -19625,7 +21633,7 @@ async function discoverEnvironment(targetDir, opts) {
19625
21633
  const start = now();
19626
21634
  const limits = { ...DEFAULT_LIMITS, ...opts?.limits };
19627
21635
  const budgetLeft = () => limits.timeoutMs - (now() - start);
19628
- const root = path37.resolve(targetDir);
21636
+ const root = path41.resolve(targetDir);
19629
21637
  const walk = walkWorkspace(root, limits, budgetLeft);
19630
21638
  const tools = await probeTools(process.platform, budgetLeft);
19631
21639
  const hosts = await probeHosts(budgetLeft);
@@ -19642,7 +21650,7 @@ async function discoverEnvironment(targetDir, opts) {
19642
21650
  const dir = resolveInside(root, dirName);
19643
21651
  if (!dir) continue;
19644
21652
  const git = hasGitRepo(dir);
19645
- const manifest = existsSync24(path37.join(dir, "forge.json"));
21653
+ const manifest = existsSync28(path41.join(dir, "forge.json"));
19646
21654
  if (!git && !manifest) continue;
19647
21655
  presence.push({ name: dirName, path: dirName, git, manifest });
19648
21656
  if (!catalogNames.has(dirName) && manifest) {
@@ -19659,8 +21667,8 @@ async function discoverEnvironment(targetDir, opts) {
19659
21667
  generated_at: new Date(now()).toISOString(),
19660
21668
  os: {
19661
21669
  platform: process.platform,
19662
- release: os4.release(),
19663
- arch: os4.arch(),
21670
+ release: os6.release(),
21671
+ arch: os6.arch(),
19664
21672
  shell: process.env.SHELL ?? process.env.ComSpec ?? null
19665
21673
  },
19666
21674
  tools,
@@ -20289,11 +22297,11 @@ async function answerRepair(targetDir, repos, opts = {}) {
20289
22297
  }
20290
22298
 
20291
22299
  // src/intel/context.ts
20292
- import { existsSync as existsSync25, readdirSync as readdirSync13, readFileSync as readFileSync19, statSync as statSync6 } from "fs";
20293
- import path38 from "path";
22300
+ import { existsSync as existsSync29, readdirSync as readdirSync15, readFileSync as readFileSync21, statSync as statSync6 } from "fs";
22301
+ import path42 from "path";
20294
22302
 
20295
22303
  // src/planner.ts
20296
- import { createHash as createHash3 } from "crypto";
22304
+ import { createHash as createHash4 } from "crypto";
20297
22305
  var PLAN_TTL_MS = 15 * 60 * 1e3;
20298
22306
  var SELECTABLE_STEPS = [
20299
22307
  "clone",
@@ -20319,7 +22327,7 @@ function canonicalJson(doc) {
20319
22327
  return JSON.stringify(canonicalize(doc));
20320
22328
  }
20321
22329
  function planHash(plan) {
20322
- return createHash3("sha256").update(canonicalJson(plan), "utf8").digest("hex");
22330
+ return createHash4("sha256").update(canonicalJson(plan), "utf8").digest("hex");
20323
22331
  }
20324
22332
  function buildPlan(input) {
20325
22333
  const now = input.now ?? /* @__PURE__ */ new Date();
@@ -20388,10 +22396,10 @@ function safeScalar(value) {
20388
22396
  return redactSecrets(value) === value ? value : null;
20389
22397
  }
20390
22398
  function safeConfig(targetDir) {
20391
- const file = path38.join(targetDir, "forge-cli.config.json");
22399
+ const file = path42.join(targetDir, "forge-cli.config.json");
20392
22400
  let raw;
20393
22401
  try {
20394
- raw = JSON.parse(readFileSync19(file, "utf8"));
22402
+ raw = JSON.parse(readFileSync21(file, "utf8"));
20395
22403
  } catch {
20396
22404
  return {};
20397
22405
  }
@@ -20405,10 +22413,10 @@ function safeConfig(targetDir) {
20405
22413
  return out;
20406
22414
  }
20407
22415
  function readManifestObject(repoDir) {
20408
- const file = path38.join(repoDir, "forge.json");
22416
+ const file = path42.join(repoDir, "forge.json");
20409
22417
  try {
20410
- if (!existsSync25(file) || statSync6(file).size > MAX_MANIFEST_BYTES) return null;
20411
- const raw = JSON.parse(readFileSync19(file, "utf8"));
22418
+ if (!existsSync29(file) || statSync6(file).size > MAX_MANIFEST_BYTES) return null;
22419
+ const raw = JSON.parse(readFileSync21(file, "utf8"));
20412
22420
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
20413
22421
  } catch {
20414
22422
  return null;
@@ -20441,10 +22449,10 @@ function manifestCapabilities(repoDir, repoName, out) {
20441
22449
  }
20442
22450
  }
20443
22451
  function activePlans(targetDir, nowIso) {
20444
- const dir = path38.join(artifactsRoot(targetDir), "plans");
22452
+ const dir = path42.join(artifactsRoot(targetDir), "plans");
20445
22453
  let names;
20446
22454
  try {
20447
- names = readdirSync13(dir).filter((n4) => n4.endsWith(".json")).sort();
22455
+ names = readdirSync15(dir).filter((n4) => n4.endsWith(".json")).sort();
20448
22456
  } catch {
20449
22457
  return [];
20450
22458
  }
@@ -20452,7 +22460,7 @@ function activePlans(targetDir, nowIso) {
20452
22460
  const out = [];
20453
22461
  for (const name of names.slice(0, MAX_PLAN_FILES)) {
20454
22462
  try {
20455
- const doc = JSON.parse(readFileSync19(path38.join(dir, name), "utf8"));
22463
+ const doc = JSON.parse(readFileSync21(path42.join(dir, name), "utf8"));
20456
22464
  if (!doc || typeof doc !== "object" || Array.isArray(doc)) continue;
20457
22465
  const plan = doc;
20458
22466
  if (typeof plan.plan_id !== "string" || !plan.plan_id) continue;
@@ -20523,7 +22531,7 @@ async function buildForgeContext(targetDir, repos, opts = {}) {
20523
22531
  const byName = new Map(report.forges.map((f) => [f.name, f]));
20524
22532
  const capabilities = {};
20525
22533
  const forjas = [...repos].sort((a3, b) => cmp2(a3.name, b.name)).map((repo) => {
20526
- const dir = path38.join(targetDir, repo.name);
22534
+ const dir = path42.join(targetDir, repo.name);
20527
22535
  manifestCapabilities(dir, repo.name, capabilities);
20528
22536
  const discovered = byName.get(repo.name);
20529
22537
  return {
@@ -20577,8 +22585,8 @@ async function buildForgeContext(targetDir, repos, opts = {}) {
20577
22585
  }
20578
22586
 
20579
22587
  // src/intel/predict.ts
20580
- import { existsSync as existsSync26, readFileSync as readFileSync20, statSync as statSync7 } from "fs";
20581
- import path39 from "path";
22588
+ import { existsSync as existsSync30, readFileSync as readFileSync22, statSync as statSync7 } from "fs";
22589
+ import path43 from "path";
20582
22590
  var MAX_MANIFEST_BYTES2 = 64 * 1024;
20583
22591
  var SEMVERISH = /^v?\d+\.\d+\.\d+/;
20584
22592
  var ev2 = (source, fact) => ({
@@ -20588,10 +22596,10 @@ var ev2 = (source, fact) => ({
20588
22596
  var cmp3 = (a3, b) => a3 < b ? -1 : a3 > b ? 1 : 0;
20589
22597
  var mk2 = (id, kind, subject, certainty, version, evidence, explanation) => ({ id, kind, subject, certainty, version, evidence, explanation });
20590
22598
  function readManifest(repoDir) {
20591
- const file = path39.join(repoDir, "forge.json");
22599
+ const file = path43.join(repoDir, "forge.json");
20592
22600
  try {
20593
- if (!existsSync26(file) || statSync7(file).size > MAX_MANIFEST_BYTES2) return null;
20594
- const raw = JSON.parse(readFileSync20(file, "utf8"));
22601
+ if (!existsSync30(file) || statSync7(file).size > MAX_MANIFEST_BYTES2) return null;
22602
+ const raw = JSON.parse(readFileSync22(file, "utf8"));
20595
22603
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
20596
22604
  } catch {
20597
22605
  return null;
@@ -20640,7 +22648,7 @@ function declaredRefs(targetDir, repoName, m3) {
20640
22648
  }
20641
22649
  try {
20642
22650
  const lock = JSON.parse(
20643
- readFileSync20(path39.join(artifactsRoot(targetDir), "env.lock.json"), "utf8")
22651
+ readFileSync22(path43.join(artifactsRoot(targetDir), "env.lock.json"), "utf8")
20644
22652
  );
20645
22653
  const head = lock?.forges?.[repoName]?.head;
20646
22654
  if (typeof head === "string" && SEMVERISH.test(head)) {
@@ -20907,7 +22915,7 @@ async function predictCompatibility(targetDir, repos, opts = {}) {
20907
22915
  const allClaims = [];
20908
22916
  for (const repo of sorted) {
20909
22917
  const dir = resolveInside(targetDir, repo.name);
20910
- if (!dir || !existsSync26(dir)) continue;
22918
+ if (!dir || !existsSync30(dir)) continue;
20911
22919
  const m3 = readManifest(dir);
20912
22920
  manifests.set(repo.name, m3);
20913
22921
  if (m3) out.push(...schemaChange(repo.name, m3));
@@ -20919,7 +22927,7 @@ async function predictCompatibility(targetDir, repos, opts = {}) {
20919
22927
  out.push(...envVarFindings("<workspace>", targetDir));
20920
22928
  for (const repo of sorted) {
20921
22929
  const dir = resolveInside(targetDir, repo.name);
20922
- if (!dir || !existsSync26(dir)) continue;
22930
+ if (!dir || !existsSync30(dir)) continue;
20923
22931
  out.push(...envVarFindings(repo.name, dir, repo.envKeys));
20924
22932
  }
20925
22933
  out.push(...installConflict(allClaims));
@@ -20927,13 +22935,13 @@ async function predictCompatibility(targetDir, repos, opts = {}) {
20927
22935
  }
20928
22936
 
20929
22937
  // src/ai/bridge.ts
20930
- import { mkdirSync as mkdirSync10 } from "fs";
20931
- import path41 from "path";
22938
+ import { mkdirSync as mkdirSync13 } from "fs";
22939
+ import path45 from "path";
20932
22940
 
20933
22941
  // src/ai/cache.ts
20934
- import { createHash as createHash4 } from "crypto";
20935
- import { existsSync as existsSync27, mkdirSync as mkdirSync9, readFileSync as readFileSync21, readdirSync as readdirSync14, rmSync as rmSync5 } from "fs";
20936
- import path40 from "path";
22942
+ import { createHash as createHash5 } from "crypto";
22943
+ import { existsSync as existsSync31, mkdirSync as mkdirSync12, readFileSync as readFileSync23, readdirSync as readdirSync16, rmSync as rmSync6 } from "fs";
22944
+ import path44 from "path";
20937
22945
 
20938
22946
  // src/ai/types.ts
20939
22947
  var AI_PROPOSAL_SCHEMA = "forge/AiProposal/v1";
@@ -21016,7 +23024,7 @@ function validateProposal(proposal, catalog) {
21016
23024
  var AI_CACHE_MAX_ENTRIES = 32;
21017
23025
  var KEY_RE = /^[a-f0-9]{64}$/;
21018
23026
  function aiCacheDir(rootDir) {
21019
- return path40.join(artifactsRoot(rootDir), "ai", "cache");
23027
+ return path44.join(artifactsRoot(rootDir), "ai", "cache");
21020
23028
  }
21021
23029
  function canonical(value) {
21022
23030
  if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
@@ -21027,18 +23035,18 @@ function canonical(value) {
21027
23035
  return JSON.stringify(value) ?? "null";
21028
23036
  }
21029
23037
  function aiCacheKey(payload) {
21030
- return createHash4("sha256").update(canonical(payload)).digest("hex");
23038
+ return createHash5("sha256").update(canonical(payload)).digest("hex");
21031
23039
  }
21032
23040
  function entryFile(rootDir, key) {
21033
23041
  if (!KEY_RE.test(key)) return null;
21034
- return path40.join(aiCacheDir(rootDir), `${key}.json`);
23042
+ return path44.join(aiCacheDir(rootDir), `${key}.json`);
21035
23043
  }
21036
23044
  function aiCacheGet(rootDir, key, opts) {
21037
23045
  if (opts.ttlMs <= 0) return null;
21038
23046
  const file = entryFile(rootDir, key);
21039
- if (!file || !existsSync27(file)) return null;
23047
+ if (!file || !existsSync31(file)) return null;
21040
23048
  try {
21041
- const entry2 = JSON.parse(readFileSync21(file, "utf8"));
23049
+ const entry2 = JSON.parse(readFileSync23(file, "utf8"));
21042
23050
  const now = opts.now ?? Date.now();
21043
23051
  if (typeof entry2?.at !== "number" || now - entry2.at > opts.ttlMs || now < entry2.at) {
21044
23052
  return null;
@@ -21052,7 +23060,7 @@ function aiCacheSet(rootDir, key, proposal, opts = {}) {
21052
23060
  const file = entryFile(rootDir, key);
21053
23061
  if (!file) return;
21054
23062
  const dir = aiCacheDir(rootDir);
21055
- mkdirSync9(dir, { recursive: true });
23063
+ mkdirSync12(dir, { recursive: true });
21056
23064
  const entry2 = { at: opts.now ?? Date.now(), proposal };
21057
23065
  writeFileAtomic(file, JSON.stringify(entry2));
21058
23066
  evictOldest(dir, opts.maxEntries ?? AI_CACHE_MAX_ENTRIES);
@@ -21061,19 +23069,19 @@ function evictOldest(dir, maxEntries) {
21061
23069
  if (maxEntries < 1) return;
21062
23070
  let files;
21063
23071
  try {
21064
- files = readdirSync14(dir).filter((f) => KEY_RE.test(path40.basename(f, ".json")));
23072
+ files = readdirSync16(dir).filter((f) => KEY_RE.test(path44.basename(f, ".json")));
21065
23073
  } catch {
21066
23074
  return;
21067
23075
  }
21068
23076
  if (files.length <= maxEntries) return;
21069
- const aged = files.map((file) => ({ file, at: entryAge(path40.join(dir, file)) })).sort((a3, b) => a3.at - b.at || a3.file.localeCompare(b.file));
23077
+ const aged = files.map((file) => ({ file, at: entryAge(path44.join(dir, file)) })).sort((a3, b) => a3.at - b.at || a3.file.localeCompare(b.file));
21070
23078
  for (const { file } of aged.slice(0, aged.length - maxEntries)) {
21071
- rmSync5(path40.join(dir, file), { force: true });
23079
+ rmSync6(path44.join(dir, file), { force: true });
21072
23080
  }
21073
23081
  }
21074
23082
  function entryAge(file) {
21075
23083
  try {
21076
- const entry2 = JSON.parse(readFileSync21(file, "utf8"));
23084
+ const entry2 = JSON.parse(readFileSync23(file, "utf8"));
21077
23085
  return typeof entry2?.at === "number" ? entry2.at : 0;
21078
23086
  } catch {
21079
23087
  return 0;
@@ -21125,18 +23133,18 @@ function sanitizeForAi(input) {
21125
23133
  }
21126
23134
  function assertNoSecrets(input) {
21127
23135
  const hits = /* @__PURE__ */ new Set();
21128
- const visit = (value, path45) => {
23136
+ const visit = (value, path49) => {
21129
23137
  if (typeof value === "string") {
21130
- if (redactSecrets(value) !== value) hits.add(path45 || "<root>");
23138
+ if (redactSecrets(value) !== value) hits.add(path49 || "<root>");
21131
23139
  return;
21132
23140
  }
21133
23141
  if (Array.isArray(value)) {
21134
- value.forEach((item, index) => visit(item, `${path45}[${index}]`));
23142
+ value.forEach((item, index) => visit(item, `${path49}[${index}]`));
21135
23143
  return;
21136
23144
  }
21137
23145
  if (value && typeof value === "object") {
21138
23146
  for (const [key, child] of Object.entries(value)) {
21139
- const childPath = path45 ? `${path45}.${key}` : key;
23147
+ const childPath = path49 ? `${path49}.${key}` : key;
21140
23148
  if (isDeniedKey(key)) {
21141
23149
  hits.add(childPath);
21142
23150
  continue;
@@ -21193,16 +23201,16 @@ async function runAiBridge(req, cfg, deps) {
21193
23201
  if (Buffer.byteLength(body, "utf8") > cfg.maxContextBytes) {
21194
23202
  return { ok: false, reason: "bridge-error", detail: "context-too-large" };
21195
23203
  }
21196
- const aiDir = path41.join(artifactsRoot(rootDir), "ai");
21197
- mkdirSync10(aiDir, { recursive: true });
21198
- const contextFile = path41.join(aiDir, `context-${digest}.json`);
23204
+ const aiDir = path45.join(artifactsRoot(rootDir), "ai");
23205
+ mkdirSync13(aiDir, { recursive: true });
23206
+ const contextFile = path45.join(aiDir, `context-${digest}.json`);
21199
23207
  writeFileAtomic(contextFile, body);
21200
23208
  const argv = command.map((part) => part === "{context}" ? contextFile : part);
21201
23209
  if (!command.includes("{context}")) argv.push(contextFile);
21202
- const spawn2 = deps.spawnFn ?? execaSpawn;
23210
+ const spawn3 = deps.spawnFn ?? execaSpawn;
21203
23211
  let res;
21204
23212
  try {
21205
- res = await spawn2(argv, { timeoutMs: cfg.timeoutMs, cwd: rootDir });
23213
+ res = await spawn3(argv, { timeoutMs: cfg.timeoutMs, cwd: rootDir });
21206
23214
  } catch (error) {
21207
23215
  return { ok: false, reason: "bridge-error", detail: cap(errorDetail(error)) };
21208
23216
  }
@@ -21236,18 +23244,18 @@ async function runAiBridge(req, cfg, deps) {
21236
23244
  }
21237
23245
 
21238
23246
  // src/portability/bundle.ts
21239
- import { existsSync as existsSync28, mkdirSync as mkdirSync11, readFileSync as readFileSync22 } from "fs";
21240
- import path42 from "path";
23247
+ import { existsSync as existsSync32, mkdirSync as mkdirSync14, readFileSync as readFileSync24 } from "fs";
23248
+ import path46 from "path";
21241
23249
 
21242
23250
  // src/portability/manifest.ts
21243
- import { createHash as createHash5 } from "crypto";
23251
+ import { createHash as createHash6 } from "crypto";
21244
23252
  var INTEGRITY_MANIFEST_SCHEMA = "forge/IntegrityManifest/v1";
21245
23253
  var SAFE_ENTRY_PATH = /^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/;
21246
23254
  function isSafeEntryPath(p) {
21247
23255
  return typeof p === "string" && SAFE_ENTRY_PATH.test(p) && !p.split("/").includes("..");
21248
23256
  }
21249
23257
  function sha256(data) {
21250
- return createHash5("sha256").update(data).digest("hex");
23258
+ return createHash6("sha256").update(data).digest("hex");
21251
23259
  }
21252
23260
  function entryDigest(entries) {
21253
23261
  return sha256(canonicalJson({ entries }));
@@ -21324,19 +23332,19 @@ var SecretKeysRefusedError = class extends Error {
21324
23332
  function writeJson(dest, doc) {
21325
23333
  const content = JSON.stringify(doc, null, 2);
21326
23334
  writeFileAtomic(dest, content);
21327
- return { path: path42.basename(dest), content };
23335
+ return { path: path46.basename(dest), content };
21328
23336
  }
21329
23337
  function pinVersion(repoDir) {
21330
23338
  for (const file of ["forge.json", "package.json"]) {
21331
23339
  try {
21332
- const parsed = JSON.parse(readFileSync22(path42.join(repoDir, file), "utf8"));
23340
+ const parsed = JSON.parse(readFileSync24(path46.join(repoDir, file), "utf8"));
21333
23341
  const v = parsed?.version;
21334
23342
  if (typeof v === "string" && v) return v;
21335
23343
  } catch {
21336
23344
  }
21337
23345
  }
21338
23346
  try {
21339
- const pyproject = readFileSync22(path42.join(repoDir, "pyproject.toml"), "utf8");
23347
+ const pyproject = readFileSync24(path46.join(repoDir, "pyproject.toml"), "utf8");
21340
23348
  const m3 = /^version\s*=\s*"([^"]+)"/m.exec(pyproject);
21341
23349
  if (m3) return m3[1];
21342
23350
  } catch {
@@ -21347,8 +23355,8 @@ function collectPins(targetDir, repos) {
21347
23355
  const state = loadState(targetDir);
21348
23356
  const pins = /* @__PURE__ */ new Map();
21349
23357
  for (const repo of repos) {
21350
- const repoDir = path42.join(targetDir, repo.name);
21351
- const version = existsSync28(repoDir) ? pinVersion(repoDir) : void 0;
23358
+ const repoDir = path46.join(targetDir, repo.name);
23359
+ const version = existsSync32(repoDir) ? pinVersion(repoDir) : void 0;
21352
23360
  const head = state?.repos[repo.name]?.head;
21353
23361
  const ref = state?.pins?.[repo.name] ?? head;
21354
23362
  pins.set(repo.name, {
@@ -21374,15 +23382,15 @@ async function exportBundle(targetDir, repos, dest, opts) {
21374
23382
  }
21375
23383
  const schemaByRepo = /* @__PURE__ */ new Map();
21376
23384
  for (const repo of repos) {
21377
- const repoDir = path42.join(targetDir, repo.name);
21378
- const schema = existsSync28(repoDir) ? deriveSchema(repoDir, repo.envKeys) : { vars: [] };
23385
+ const repoDir = path46.join(targetDir, repo.name);
23386
+ const schema = existsSync32(repoDir) ? deriveSchema(repoDir, repo.envKeys) : { vars: [] };
21379
23387
  schemaByRepo.set(repo.name, schema);
21380
23388
  for (const v of schema.vars) {
21381
23389
  if (v.secret && whitelist.includes(v.key)) refused.add(v.key);
21382
23390
  }
21383
23391
  }
21384
23392
  if (refused.size) throw new SecretKeysRefusedError([...refused]);
21385
- mkdirSync11(dest, { recursive: true });
23393
+ mkdirSync14(dest, { recursive: true });
21386
23394
  const inputs = [];
21387
23395
  const produced = [
21388
23396
  BUNDLE_FILES.envelope,
@@ -21395,30 +23403,30 @@ async function exportBundle(targetDir, repos, dest, opts) {
21395
23403
  BUNDLE_FILES.integrity
21396
23404
  ].sort();
21397
23405
  inputs.push(
21398
- writeJson(path42.join(dest, BUNDLE_FILES.envelope), {
23406
+ writeJson(path46.join(dest, BUNDLE_FILES.envelope), {
21399
23407
  schema: BUNDLE_SCHEMA,
21400
23408
  generatedAt: now,
21401
23409
  files: produced
21402
23410
  })
21403
23411
  );
21404
- const profile = { ...exportProfile(targetDir, path42.join(dest, BUNDLE_FILES.profile)) };
23412
+ const profile = { ...exportProfile(targetDir, path46.join(dest, BUNDLE_FILES.profile)) };
21405
23413
  const normalizedProfile = { ...profile, exported_at: now };
21406
- inputs.push(writeJson(path42.join(dest, BUNDLE_FILES.profile), normalizedProfile));
23414
+ inputs.push(writeJson(path46.join(dest, BUNDLE_FILES.profile), normalizedProfile));
21407
23415
  const lock = { ...writeEnvLock(targetDir, repos), generated_at: now };
21408
- inputs.push(writeJson(path42.join(dest, BUNDLE_FILES.lock), lock));
21409
- inputs.push(writeJson(path42.join(dest, BUNDLE_FILES.lockCheck), checkEnvLock(targetDir, repos)));
23416
+ inputs.push(writeJson(path46.join(dest, BUNDLE_FILES.lock), lock));
23417
+ inputs.push(writeJson(path46.join(dest, BUNDLE_FILES.lockCheck), checkEnvLock(targetDir, repos)));
21410
23418
  const envKeys = {};
21411
23419
  for (const repo of repos) {
21412
23420
  const keys = [...new Set((schemaByRepo.get(repo.name)?.vars ?? []).map((v) => v.key))].sort();
21413
23421
  envKeys[repo.name] = keys;
21414
23422
  }
21415
- inputs.push(writeJson(path42.join(dest, BUNDLE_FILES.envKeys), envKeys));
23423
+ inputs.push(writeJson(path46.join(dest, BUNDLE_FILES.envKeys), envKeys));
21416
23424
  if (whitelist.length) {
21417
23425
  const values = {};
21418
23426
  for (const repo of repos) {
21419
- const envFile = path42.join(targetDir, repo.name, ".env");
21420
- if (!existsSync28(envFile)) continue;
21421
- const entries = parseEnv(readFileSync22(envFile, "utf8")).entries;
23427
+ const envFile = path46.join(targetDir, repo.name, ".env");
23428
+ if (!existsSync32(envFile)) continue;
23429
+ const entries = parseEnv(readFileSync24(envFile, "utf8")).entries;
21422
23430
  const found = {};
21423
23431
  for (const key of whitelist) {
21424
23432
  const entry2 = entries.find((e) => e.key === key);
@@ -21426,16 +23434,16 @@ async function exportBundle(targetDir, repos, dest, opts) {
21426
23434
  }
21427
23435
  if (Object.keys(found).length) values[repo.name] = found;
21428
23436
  }
21429
- inputs.push(writeJson(path42.join(dest, BUNDLE_FILES.envValues), values));
23437
+ inputs.push(writeJson(path46.join(dest, BUNDLE_FILES.envValues), values));
21430
23438
  }
21431
- inputs.push(writeJson(path42.join(dest, BUNDLE_FILES.pins), collectPins(targetDir, repos)));
23439
+ inputs.push(writeJson(path46.join(dest, BUNDLE_FILES.pins), collectPins(targetDir, repos)));
21432
23440
  const manifest = buildIntegrityManifest(inputs, { now });
21433
- inputs.push(writeJson(path42.join(dest, BUNDLE_FILES.integrity), manifest));
23441
+ inputs.push(writeJson(path46.join(dest, BUNDLE_FILES.integrity), manifest));
21434
23442
  return { ok: true, dir: dest, files: produced, manifest };
21435
23443
  }
21436
23444
  function readJsonFile(file) {
21437
23445
  try {
21438
- return JSON.parse(readFileSync22(file, "utf8"));
23446
+ return JSON.parse(readFileSync24(file, "utf8"));
21439
23447
  } catch {
21440
23448
  return null;
21441
23449
  }
@@ -21484,40 +23492,40 @@ function readPins(file) {
21484
23492
  function importBundle(src, opts) {
21485
23493
  const read = (p) => {
21486
23494
  try {
21487
- return readFileSync22(path42.join(src, p));
23495
+ return readFileSync24(path46.join(src, p));
21488
23496
  } catch {
21489
23497
  return null;
21490
23498
  }
21491
23499
  };
21492
- const manifestRaw = readJsonFile(path42.join(src, BUNDLE_FILES.integrity));
23500
+ const manifestRaw = readJsonFile(path46.join(src, BUNDLE_FILES.integrity));
21493
23501
  const manifest = manifestRaw && typeof manifestRaw === "object" && manifestRaw.schema === "forge/IntegrityManifest/v1" ? manifestRaw : null;
21494
23502
  const verify = opts?.verifyIntegrity === false ? { ok: true } : verifyIntegrityManifest(manifest, read);
21495
- const profile = importProfile(path42.join(src, BUNDLE_FILES.profile));
23503
+ const profile = importProfile(path46.join(src, BUNDLE_FILES.profile));
21496
23504
  return {
21497
23505
  dir: src,
21498
23506
  verify,
21499
23507
  manifest,
21500
23508
  profile: "error" in profile ? null : profile,
21501
- lockfile: readLockfile(path42.join(src, BUNDLE_FILES.lock)),
21502
- lockCheck: readLockCheck(path42.join(src, BUNDLE_FILES.lockCheck)),
21503
- envKeys: readStringMap(path42.join(src, BUNDLE_FILES.envKeys)),
21504
- envValues: readValueMap(path42.join(src, BUNDLE_FILES.envValues)),
21505
- pins: readPins(path42.join(src, BUNDLE_FILES.pins))
23509
+ lockfile: readLockfile(path46.join(src, BUNDLE_FILES.lock)),
23510
+ lockCheck: readLockCheck(path46.join(src, BUNDLE_FILES.lockCheck)),
23511
+ envKeys: readStringMap(path46.join(src, BUNDLE_FILES.envKeys)),
23512
+ envValues: readValueMap(path46.join(src, BUNDLE_FILES.envValues)),
23513
+ pins: readPins(path46.join(src, BUNDLE_FILES.pins))
21506
23514
  };
21507
23515
  }
21508
23516
 
21509
23517
  // src/intel/learning.ts
21510
- import { existsSync as existsSync29, mkdirSync as mkdirSync12, readFileSync as readFileSync23 } from "fs";
21511
- import path43 from "path";
23518
+ import { existsSync as existsSync33, mkdirSync as mkdirSync15, readFileSync as readFileSync25 } from "fs";
23519
+ import path47 from "path";
21512
23520
  var LEARNING_SCHEMA = "forge-cli/LearningStore/v1";
21513
23521
  function learningFile(targetDir) {
21514
- return path43.join(artifactsRoot(targetDir), "learning.json");
23522
+ return path47.join(artifactsRoot(targetDir), "learning.json");
21515
23523
  }
21516
23524
  function readStore(targetDir) {
21517
23525
  const file = learningFile(targetDir);
21518
- if (!existsSync29(file)) return { schema: LEARNING_SCHEMA, enabled: false, observations: [] };
23526
+ if (!existsSync33(file)) return { schema: LEARNING_SCHEMA, enabled: false, observations: [] };
21519
23527
  try {
21520
- const parsed = JSON.parse(readFileSync23(file, "utf8"));
23528
+ const parsed = JSON.parse(readFileSync25(file, "utf8"));
21521
23529
  if (parsed?.schema !== LEARNING_SCHEMA || !Array.isArray(parsed.observations)) {
21522
23530
  return { schema: LEARNING_SCHEMA, enabled: false, observations: [] };
21523
23531
  }
@@ -21534,7 +23542,7 @@ function readStore(targetDir) {
21534
23542
  }
21535
23543
  function writeStore(targetDir, store) {
21536
23544
  const file = learningFile(targetDir);
21537
- mkdirSync12(path43.dirname(file), { recursive: true });
23545
+ mkdirSync15(path47.dirname(file), { recursive: true });
21538
23546
  writeFileAtomic(file, JSON.stringify(store, null, 2));
21539
23547
  }
21540
23548
  function learningStatus(targetDir) {
@@ -21689,12 +23697,12 @@ async function runBundleCommand(cli) {
21689
23697
  if (cli.jsonReport) emitJson(result);
21690
23698
  log.success(`${S.bundleExported}: ${dest}`);
21691
23699
  return 0;
21692
- } catch (err) {
21693
- if (err instanceof SecretKeysRefusedError) {
21694
- log.error(`${S.bundleSecretRefused}: ${err.keys.join(", ")}`);
23700
+ } catch (err2) {
23701
+ if (err2 instanceof SecretKeysRefusedError) {
23702
+ log.error(`${S.bundleSecretRefused}: ${err2.keys.join(", ")}`);
21695
23703
  return 1;
21696
23704
  }
21697
- throw err;
23705
+ throw err2;
21698
23706
  }
21699
23707
  }
21700
23708
  if (verb === "import" || verb === "verify") {
@@ -21705,8 +23713,8 @@ async function runBundleCommand(cli) {
21705
23713
  let result;
21706
23714
  try {
21707
23715
  result = importBundle(dest);
21708
- } catch (err) {
21709
- log.error(`${S.bundleInvalid}: ${err instanceof Error ? err.message : String(err)}`);
23716
+ } catch (err2) {
23717
+ log.error(`${S.bundleInvalid}: ${err2 instanceof Error ? err2.message : String(err2)}`);
21710
23718
  return 1;
21711
23719
  }
21712
23720
  if (cli.jsonReport) emitJson(result);
@@ -21755,8 +23763,8 @@ async function runLearningCommand(cli) {
21755
23763
  }
21756
23764
 
21757
23765
  // src/wizard.ts
21758
- import { existsSync as existsSync30 } from "fs";
21759
- import path44 from "path";
23766
+ import { existsSync as existsSync34 } from "fs";
23767
+ import path48 from "path";
21760
23768
  var STEP_LABELS = [
21761
23769
  S.wizardStepDetect,
21762
23770
  S.wizardStepRecommend,
@@ -21795,8 +23803,8 @@ function renderOperationPlan(plan) {
21795
23803
  return lines.join("\n");
21796
23804
  }
21797
23805
  function planActionsFor(repo, targetDir, steps) {
21798
- const repoDir = path44.join(targetDir, repo.name);
21799
- const cloned = existsSync30(repoDir) && hasGitRepo(repoDir);
23806
+ const repoDir = path48.join(targetDir, repo.name);
23807
+ const cloned = existsSync34(repoDir) && hasGitRepo(repoDir);
21800
23808
  const actions = [
21801
23809
  {
21802
23810
  kind: "clone",
@@ -21816,7 +23824,7 @@ function planActionsFor(repo, targetDir, steps) {
21816
23824
  actions.push({
21817
23825
  kind: "env",
21818
23826
  label: "provisionar .env (nunca sobrescreve)",
21819
- paths: [path44.join(repo.name, ".env")],
23827
+ paths: [path48.join(repo.name, ".env")],
21820
23828
  reversible: false
21821
23829
  });
21822
23830
  if (steps.includes("skills")) {
@@ -21917,7 +23925,7 @@ async function runWizard(cli) {
21917
23925
  const planForges = repos.map((repo) => ({
21918
23926
  forge_id: repo.name,
21919
23927
  repo: repo.name,
21920
- target_dir: path44.join(targetDir, repo.name),
23928
+ target_dir: path48.join(targetDir, repo.name),
21921
23929
  actions: filterSteps(planActionsFor(repo, targetDir, steps), steps)
21922
23930
  }));
21923
23931
  const plan = buildPlan({
@@ -21965,12 +23973,16 @@ var JSON_QUERY_COMMANDS = /* @__PURE__ */ new Set([
21965
23973
  "recommend",
21966
23974
  "env",
21967
23975
  "hosts",
23976
+ "mcp",
21968
23977
  "lock",
21969
23978
  "profile",
21970
23979
  "recover",
21971
23980
  "ask",
21972
23981
  "predict",
21973
23982
  "context",
23983
+ "install",
23984
+ "uninstall",
23985
+ "registry",
21974
23986
  "ai",
21975
23987
  "bundle",
21976
23988
  "learning"
@@ -21997,7 +24009,7 @@ async function main() {
21997
24009
  }
21998
24010
  if (shouldShowMenu(cli, Boolean(process.stdin.isTTY && process.stdout.isTTY))) {
21999
24011
  const next = await applyMenu(cli, {
22000
- ask: () => askMenu(),
24012
+ ask: () => askMenu({}, menuItemsFor(cli)),
22001
24013
  workspace: askWorkspace,
22002
24014
  resolveDir: resolveTargetDir,
22003
24015
  pickRepos: async (dir) => {
@@ -22008,6 +24020,33 @@ async function main() {
22008
24020
  status: new Map(infos.map((i3) => [i3.name, i3]))
22009
24021
  });
22010
24022
  return chosen.map((r2) => r2.name);
24023
+ },
24024
+ pickRegistryCatalog: async () => {
24025
+ const picked = await multiselect({
24026
+ message: "Forjas para instalar do PyPI (registry-first)",
24027
+ options: REGISTRY_PACKAGES.map((e) => ({
24028
+ value: e.name,
24029
+ label: e.name,
24030
+ hint: `${e.dist} \xB7 python >=${e.minPython}`
24031
+ })),
24032
+ required: true
24033
+ });
24034
+ if (isCancel(picked)) exitCancelled();
24035
+ return picked;
24036
+ },
24037
+ pickRegistryInstalled: async () => {
24038
+ const installed = Object.keys(loadForgeState(cliHome()).forges);
24039
+ if (!installed.length) {
24040
+ log.warn("Nenhuma Forja instalada via registry ainda \u2014 instale primeiro.");
24041
+ return [];
24042
+ }
24043
+ const picked = await multiselect({
24044
+ message: "Forjas instaladas (registry)",
24045
+ options: installed.map((n4) => ({ value: n4, label: n4 })),
24046
+ required: true
24047
+ });
24048
+ if (isCancel(picked)) exitCancelled();
24049
+ return picked;
22011
24050
  }
22012
24051
  });
22013
24052
  if (!next) {
@@ -22023,6 +24062,54 @@ async function main() {
22023
24062
  console.log(usage());
22024
24063
  process.exit(2);
22025
24064
  }
24065
+ const REGISTRY_COMMANDS = /* @__PURE__ */ new Set([
24066
+ "install",
24067
+ "uninstall",
24068
+ "update",
24069
+ "reinstall",
24070
+ "rollback",
24071
+ "pin",
24072
+ "unpin"
24073
+ ]);
24074
+ if (cli.method === "registry" && !REGISTRY_COMMANDS.has(resolved.command)) {
24075
+ log.error(S.errMethodUnsupported);
24076
+ process.exit(2);
24077
+ }
24078
+ if (cli.method === "registry" && resolved.command === "update") {
24079
+ await runRegistryUpdate(cli, resolved.names);
24080
+ return;
24081
+ }
24082
+ if (cli.method === "registry" && resolved.command === "reinstall") {
24083
+ await runRegistryReinstall(cli, resolved.names);
24084
+ return;
24085
+ }
24086
+ if (cli.method === "registry" && resolved.command === "rollback") {
24087
+ await runRegistryRollback(cli, resolved.names);
24088
+ return;
24089
+ }
24090
+ if (cli.method === "registry" && resolved.command === "pin") {
24091
+ await runRegistryPin(cli, resolved.names, true);
24092
+ return;
24093
+ }
24094
+ if (cli.method === "registry" && resolved.command === "unpin") {
24095
+ await runRegistryPin(cli, resolved.names, false);
24096
+ return;
24097
+ }
24098
+ const METHOD_COMMANDS = /* @__PURE__ */ new Set([
24099
+ "run",
24100
+ "install",
24101
+ "clone",
24102
+ "uninstall",
24103
+ "update",
24104
+ "reinstall",
24105
+ "pin",
24106
+ "unpin",
24107
+ "rollback",
24108
+ "wizard"
24109
+ ]);
24110
+ if (cli.method === "git" && !METHOD_COMMANDS.has(resolved.command)) {
24111
+ log.warn(`--method git n\xE3o tem efeito em "${resolved.command}" \u2014 ignorado`);
24112
+ }
22026
24113
  if (resolved.command === "doctor") {
22027
24114
  process.exitCode = await runDoctor2Command(cli);
22028
24115
  return;
@@ -22047,6 +24134,10 @@ async function main() {
22047
24134
  process.exitCode = await runHostsCommand(cli, resolved.names);
22048
24135
  return;
22049
24136
  }
24137
+ if (resolved.command === "mcp") {
24138
+ process.exitCode = await runMcpCommand(cli, resolved.names);
24139
+ return;
24140
+ }
22050
24141
  if (resolved.command === "wizard") {
22051
24142
  if (isNonInteractive(cli)) {
22052
24143
  log.error(S.errCloneNeedsNames);
@@ -22087,6 +24178,10 @@ async function main() {
22087
24178
  process.exitCode = await runBundleCommand(cli);
22088
24179
  return;
22089
24180
  }
24181
+ if (resolved.command === "registry") {
24182
+ runRegistryList(cli, { names: resolved.names });
24183
+ return;
24184
+ }
22090
24185
  if (resolved.command === "learning") {
22091
24186
  process.exitCode = await runLearningCommand(cli);
22092
24187
  return;
@@ -22151,6 +24246,10 @@ async function main() {
22151
24246
  await runUninstallCommand();
22152
24247
  return;
22153
24248
  }
24249
+ if (resolved.command === "uninstall" && cli.method === "registry") {
24250
+ await runRegistryUninstall(cli, resolved.names);
24251
+ return;
24252
+ }
22154
24253
  if (resolved.command === "uninstall" && !resolved.names.length && !isNonInteractive(cli)) {
22155
24254
  log.warn(S.warnLegacyUninstall);
22156
24255
  await runUninstallCommand();
@@ -22176,6 +24275,19 @@ async function main() {
22176
24275
  log.error(S.errReinstallNeedsNames);
22177
24276
  process.exit(2);
22178
24277
  }
24278
+ if (resolved.command === "install" && cli.method === "registry") {
24279
+ const names = resolved.names.length ? resolved.names : cli.all ? registryNames() : cli.only ?? [];
24280
+ if (!names.length) {
24281
+ log.error(S.errRegistryNeedsNames);
24282
+ process.exit(2);
24283
+ }
24284
+ if (cli.ref && new Set(names.map((n4) => registryEntry(n4)?.name ?? n4)).size !== 1) {
24285
+ log.error(S.errRefNeedsOne);
24286
+ process.exit(2);
24287
+ }
24288
+ await runRegistryInstall(cli, names);
24289
+ return;
24290
+ }
22179
24291
  if (resolved.names.length) cli = { ...cli, only: [...cli.only ?? [], ...resolved.names] };
22180
24292
  const cloneOnly = resolved.command === "clone";
22181
24293
  if (cloneOnly && !isNonInteractive(cli)) {