ostacky 0.7.1 → 0.7.2

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/cli.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
2
3
  var __create = Object.create;
3
4
  var __getProtoOf = Object.getPrototypeOf;
4
5
  var __defProp = Object.defineProperty;
@@ -44,6 +45,7 @@ var __export = (target, all) => {
44
45
  });
45
46
  };
46
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
47
49
 
48
50
  // node_modules/sisteransi/src/index.js
49
51
  var require_src = __commonJS((exports, module) => {
@@ -215,8 +217,9 @@ import {
215
217
  renameSync
216
218
  } from "fs";
217
219
  import { createHash as createHash2 } from "crypto";
218
- import { join, resolve, dirname, relative, basename } from "path";
220
+ import { join, resolve, dirname, relative, basename, win32 } from "path";
219
221
  import { execFileSync } from "child_process";
222
+ import { homedir } from "os";
220
223
  function findOpenCodeDir(startDir = process.cwd()) {
221
224
  let current = resolve(startDir);
222
225
  while (true) {
@@ -243,6 +246,32 @@ function findProjectRoot(startDir = process.cwd()) {
243
246
  current = parent;
244
247
  }
245
248
  }
249
+ function getGlobalOpenCodeDir(platform = process.platform, home = homedir()) {
250
+ if (platform === "win32") {
251
+ const appData = process.env.APPDATA ?? win32.join(home, "AppData", "Roaming");
252
+ return win32.join(appData, "opencode");
253
+ }
254
+ const xdg = process.env.XDG_CONFIG_HOME ?? join(home, ".config");
255
+ return join(xdg, "opencode");
256
+ }
257
+ function getOpenCodeDirForScope(scope, cwd = process.cwd()) {
258
+ if (scope === "global")
259
+ return getGlobalOpenCodeDir();
260
+ if (scope === "local") {
261
+ const existing2 = findOpenCodeDir(cwd);
262
+ if (existing2)
263
+ return existing2;
264
+ return join(findProjectRoot(cwd), ".opencode");
265
+ }
266
+ const existing = findOpenCodeDir(cwd);
267
+ if (existing)
268
+ return existing;
269
+ const root = findProjectRoot(cwd);
270
+ if (existsSync(join(root, ".opencode")) || existsSync(join(root, ".git"))) {
271
+ return join(root, ".opencode");
272
+ }
273
+ return getGlobalOpenCodeDir();
274
+ }
246
275
  function ensureOpenCodePaths(opencodeDir) {
247
276
  const paths = {
248
277
  root: opencodeDir,
@@ -266,9 +295,6 @@ function ensureToolDirs(toolsDir, toolNames) {
266
295
  mkdirSync(dir, { recursive: true });
267
296
  }
268
297
  }
269
- function createOpenCodeDir(baseDir) {
270
- return ensureOpenCodePaths(join(baseDir, ".opencode"));
271
- }
272
298
  function copyDirRecursive(src, dest, skipGenerated = false) {
273
299
  if (!existsSync(dest))
274
300
  mkdirSync(dest, { recursive: true });
@@ -451,7 +477,7 @@ function downloadToFile(url, dest, timeoutMs = 180000) {
451
477
  });
452
478
  }
453
479
  async function downloadAndExtract(url, destDir, stripComponents = 1, timeoutMs = 180000) {
454
- const tmp = join(dirname(destDir), `.${basename(destDir)}.download-${Date.now()}`);
480
+ const tmp = join(dirname(destDir), `.${basename(destDir)}.download-${Date.now()}-${process.pid}`);
455
481
  if (!existsSync(tmp))
456
482
  mkdirSync(tmp, { recursive: true });
457
483
  const archivePath = join(tmp, url.endsWith(".zip") ? "archive.zip" : "archive.tar.gz");
@@ -520,31 +546,31 @@ var init_fs = __esm(() => {
520
546
  var manifest_default;
521
547
  var init_manifest = __esm(() => {
522
548
  manifest_default = {
523
- version: "0.7.1",
549
+ version: "0.7.2",
524
550
  repo: "JaimeHoracio/Ostacky",
525
- tag: "v0.7.1",
551
+ tag: "v0.7.2",
526
552
  agents: [
527
553
  {
528
554
  name: "ostacky",
529
555
  file: "assets/agents/ostacky.md",
530
- description: "Orquestador con recuperación automática (nunca se congela), ruteo por nivel de impacto, controller MCP con SDK oficial, edición segura con fallback inline, y delegación en OpenSpec + Superpowers. v0.7.1: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
531
- version: "0.7.1",
532
- sha256: "f833e69b6eaa3dfe24de9addf6455b3da55f829b51cba3c7c90c65c69cda34c2"
556
+ description: "Orquestador con recuperación automática (nunca se congela), ruteo por nivel de impacto, controller MCP con SDK oficial, edición segura con fallback inline, y delegación en OpenSpec + Superpowers. v0.7.2: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
557
+ version: "0.7.2",
558
+ sha256: "1b6a5cbb49f1c6e977241f8ab86d1a045fdb8a04111bbc75633429a131c71acd"
533
559
  }
534
560
  ],
535
561
  commands: [
536
562
  {
537
563
  name: "install-stack",
538
564
  file: "assets/commands/install-stack.md",
539
- description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, Context7, controller MCP con SDK oficial). v0.7.1: Paso 1.5 controller expandido con verificación post-instalación y troubleshooting.",
540
- version: "0.7.1",
541
- sha256: "ef4244855cf733dfe50a2a8e7c436c5741f92574cc50f0fca49faed60480e013"
565
+ description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, Context7, controller MCP con SDK oficial). v0.7.2: Paso 1.5 controller expandido con verificación post-instalación y troubleshooting.",
566
+ version: "0.7.2",
567
+ sha256: "7277d333bfe65e674b9696fa5f887854e35824eb5b88420dd0f5bbde3bca7add"
542
568
  },
543
569
  {
544
570
  name: "opsx-sync",
545
571
  file: "assets/commands/opsx-sync.md",
546
572
  description: "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
547
- version: "0.7.1",
573
+ version: "0.7.2",
548
574
  sha256: "fe0158478f2ca63b315037a85fc1632b77868532e319af6c7384285441767d64"
549
575
  }
550
576
  ],
@@ -552,15 +578,15 @@ var init_manifest = __esm(() => {
552
578
  {
553
579
  name: "ostacky-controller",
554
580
  file: "assets/mcp/ostacky-controller/",
555
- description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.7.1: 22 tools (incluye set_handoff, get_handoff, clear_handoff, get_available_transitions funcional). Bugfixes: consecutiveFailures real, lastHandoff state, defaultChoice persistido. Robustez: degraded mode automático tras 3 fallos, persistence condicional para Nivel 0, prune de skills obsoletas.",
556
- version: "0.7.1",
557
- sha256: "0e639fb1d5bedf8b93b4e259782b9b7de68cd4bb89875e2c546279d17131b7b7"
581
+ description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.7.2: 22 tools (incluye set_handoff, get_handoff, clear_handoff, get_available_transitions funcional). Bugfixes: consecutiveFailures real, lastHandoff state, defaultChoice persistido. Robustez: degraded mode automático tras 3 fallos, persistence condicional para Nivel 0, prune de skills obsoletas.",
582
+ version: "0.7.2",
583
+ sha256: "34048819b8905dff24c731e02d813deb4fb7fbbb65e8b8df66e5c8db201492a0"
558
584
  },
559
585
  {
560
586
  name: "openspec",
561
587
  file: "assets/mcp/openspec/",
562
588
  description: "MCP server local para OpenSpec - proposal, apply, archive, sync de cambios",
563
- version: "0.7.1",
589
+ version: "0.7.2",
564
590
  sha256: "fa4be28bfc1e75db8f1a037e2580f04a60066c80e8e5934e90e1041020d04609"
565
591
  }
566
592
  ],
@@ -569,106 +595,106 @@ var init_manifest = __esm(() => {
569
595
  name: "brainstorming",
570
596
  file: "assets/skills/brainstorming/SKILL.md",
571
597
  description: "Skill unificado de pensamiento con dos modos: creative-design (producción de diseño → transición a implementación directa o openspec-propose) y open-exploration (exploración libre)",
572
- version: "0.7.1",
598
+ version: "0.7.2",
573
599
  sha256: "333810ca63450e8ab795e8c5a58e645101e788d79210ca8ac36e10d11be39585"
574
600
  },
575
601
  {
576
602
  name: "execution-mode-evaluation",
577
603
  file: "assets/skills/execution-mode-evaluation/SKILL.md",
578
604
  description: "Skill de análisis de modo de ejecución — output reconciliado con controller snapshot contract (recommendation field)",
579
- version: "0.7.1",
605
+ version: "0.7.2",
580
606
  sha256: "edf6fca0af52cce83f8fd08e7db9b3d79cc130b53fca505e90d3fbaf4909338b"
581
607
  },
582
608
  {
583
609
  name: "tdd",
584
610
  file: "assets/skills/tdd/SKILL.md",
585
611
  description: "Skill de test-driven development (Superpowers)",
586
- version: "0.7.1",
612
+ version: "0.7.2",
587
613
  sha256: "aa412298980b7826165c211145c1b8e9135f68f36247bb3cb96d0a0eae274486"
588
614
  },
589
615
  {
590
616
  name: "subagent-driven-development",
591
617
  file: "assets/skills/subagent-driven-development/SKILL.md",
592
618
  description: "Skill de ejecución con subagentes (Superpowers) — ejecuta solo después de confirmación del coordinador Ostacky",
593
- version: "0.7.1",
619
+ version: "0.7.2",
594
620
  sha256: "1a42d714a9a13faf05f0bf7b580e8d837c2e77a5633839542ccce603023419e8"
595
621
  },
596
622
  {
597
623
  name: "dispatching-parallel-agents",
598
624
  file: "assets/skills/dispatching-parallel-agents/SKILL.md",
599
625
  description: "Skill de dispatch paralelo de agentes (Superpowers)",
600
- version: "0.7.1",
626
+ version: "0.7.2",
601
627
  sha256: "3c8a66d51ae2e719e877d02c3dac32bd804722c3bb9227c258ecae78252a20ab"
602
628
  },
603
629
  {
604
630
  name: "review",
605
631
  file: "assets/skills/review/SKILL.md",
606
632
  description: "Skill de revisión de código (Superpowers)",
607
- version: "0.7.1",
633
+ version: "0.7.2",
608
634
  sha256: "b19650ed4d1d4d9857a4dd5b7e08e91328a1a1fc9c45bcc2fb600fa9d0213279"
609
635
  },
610
636
  {
611
637
  name: "receiving-code-review",
612
638
  file: "assets/skills/receiving-code-review/SKILL.md",
613
639
  description: "Skill de recibir y procesar feedback de code review",
614
- version: "0.7.1",
640
+ version: "0.7.2",
615
641
  sha256: "d761e884e71d8d3476ac734d287cae403a4024506a7d12e7361a448217c0a831"
616
642
  },
617
643
  {
618
644
  name: "openspec-propose",
619
645
  file: "assets/skills/openspec-propose/SKILL.md",
620
646
  description: "Skill de generación de proposal (OpenSpec)",
621
- version: "0.7.1",
647
+ version: "0.7.2",
622
648
  sha256: "bb59100b9fd3c9f9a1ec6509fa975ec55bf7fc737df4fe73bb3e9bbc217b83f7"
623
649
  },
624
650
  {
625
651
  name: "openspec-apply-change",
626
652
  file: "assets/skills/openspec-apply-change/SKILL.md",
627
653
  description: "Skill de aplicación de change (OpenSpec)",
628
- version: "0.7.1",
654
+ version: "0.7.2",
629
655
  sha256: "dfc823bf89fc7505e91ab6dee9c1f004be38a410bd3d88fb67b211b1b6cbb1d0"
630
656
  },
631
657
  {
632
658
  name: "openspec-archive-change",
633
659
  file: "assets/skills/openspec-archive-change/SKILL.md",
634
660
  description: "Skill de archivo de change (OpenSpec)",
635
- version: "0.7.1",
661
+ version: "0.7.2",
636
662
  sha256: "16e4b561de7747283663fed602e506abf502452c49a8d4b86d7a5539c40f0195"
637
663
  },
638
664
  {
639
665
  name: "openspec-explore",
640
666
  file: "assets/skills/openspec-explore/SKILL.md",
641
667
  description: "Modo explore para OpenSpec — thinking partner para explorar ideas, investigar problemas y clarificar requisitos antes/durante un cambio",
642
- version: "0.7.1",
668
+ version: "0.7.2",
643
669
  sha256: "37ae4aaf17ea71a395bab6dc4d9d61b9a49d7910f6692535ae8553244c46880b"
644
670
  },
645
671
  {
646
672
  name: "using-git-worktrees",
647
673
  file: "assets/skills/using-git-worktrees/SKILL.md",
648
674
  description: "Skill de uso de git worktrees para aislamiento de trabajo",
649
- version: "0.7.1",
675
+ version: "0.7.2",
650
676
  sha256: "11afa9123828fc5caa22137aff907cd99c751e99027fbd4c020751110a949281"
651
677
  },
652
678
  {
653
679
  name: "using-superpowers",
654
680
  file: "assets/skills/using-superpowers/SKILL.md",
655
681
  description: "Skill de orquestación de Superpowers skills",
656
- version: "0.7.1",
682
+ version: "0.7.2",
657
683
  sha256: "7e54536f96d2a379185a10bfc1e970850caa2561b6d8aca7080f0defea0381a4"
658
684
  },
659
685
  {
660
686
  name: "writing-skills",
661
687
  file: "assets/skills/writing-skills/SKILL.md",
662
688
  description: "Skill de creación y edición de skills",
663
- version: "0.7.1",
689
+ version: "0.7.2",
664
690
  sha256: "3d76b906cee518a2b809febb70db95697a35b9f368986bb504b4120c3bfb437a"
665
691
  },
666
692
  {
667
693
  name: "graceful-degradation",
668
694
  file: "assets/skills/graceful-degradation/SKILL.md",
669
695
  description: "Skill de degradación graceful cuando múltiples tools están indisponibles",
670
- version: "0.7.1",
671
- sha256: "e5090e475729f21871bb25bce738000f3616955ed3edb3ba926dd740d3298948"
696
+ version: "0.7.2",
697
+ sha256: "376dc90fa27ab7e5947747f20ccf2cc76f9326c77b567fda388252a60d25c431"
672
698
  }
673
699
  ]
674
700
  };
@@ -1468,7 +1494,7 @@ var init_stack = __esm(() => {
1468
1494
  // package.json
1469
1495
  var package_default = {
1470
1496
  name: "ostacky",
1471
- version: "0.7.1",
1497
+ version: "0.7.2",
1472
1498
  description: "Instalador interactivo de agentes y comandos para OpenCode",
1473
1499
  type: "module",
1474
1500
  bin: {
@@ -2468,6 +2494,7 @@ import { dirname as dirname7 } from "path";
2468
2494
  init_github();
2469
2495
  init_fs();
2470
2496
  init_lockfile();
2497
+ import { homedir as homedir2 } from "os";
2471
2498
 
2472
2499
  // src/installer.ts
2473
2500
  init_github();
@@ -2546,6 +2573,7 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
2546
2573
  let stderr = "";
2547
2574
  let stdoutBuffer = "";
2548
2575
  const requestTimeout = 1e4;
2576
+ let exitTimeout;
2549
2577
  const finish = (error) => {
2550
2578
  if (settled)
2551
2579
  return;
@@ -2554,9 +2582,18 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
2554
2582
  try {
2555
2583
  child.stdin?.end();
2556
2584
  } catch {}
2557
- if (child.exitCode === null)
2585
+ if (child.exitCode === null) {
2586
+ const onExit = () => {
2587
+ if (exitTimeout)
2588
+ clearTimeout(exitTimeout);
2589
+ error ? reject(error) : resolve2();
2590
+ };
2591
+ child.once("exit", onExit);
2558
2592
  child.kill();
2559
- error ? reject(error) : resolve2();
2593
+ exitTimeout = setTimeout(onExit, 500);
2594
+ } else {
2595
+ error ? reject(error) : resolve2();
2596
+ }
2560
2597
  };
2561
2598
  const fail = (message) => finish(new Error(`${message}${stderr ? `: ${stderr.trim()}` : ""}`));
2562
2599
  const send = (message) => {
@@ -2633,15 +2670,35 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
2633
2670
  params: {
2634
2671
  protocolVersion: "2025-03-26",
2635
2672
  capabilities: {},
2636
- clientInfo: { name: "ostacky-installer", version: "0.7.1" }
2673
+ clientInfo: { name: "ostacky-installer", version: "0.7.2" }
2637
2674
  }
2638
2675
  });
2639
2676
  });
2640
2677
  } finally {
2641
- if (statePath && existsSync5(statePath))
2642
- rmSync3(statePath, { force: true });
2643
- if (statePath && existsSync5(statePath + ".backup"))
2644
- rmSync3(statePath + ".backup", { force: true });
2678
+ await new Promise((r2) => setTimeout(r2, 200));
2679
+ if (statePath) {
2680
+ for (const p2 of [
2681
+ statePath,
2682
+ statePath + ".backup",
2683
+ statePath + ".lock.pid",
2684
+ statePath + ".lock.timestamp",
2685
+ statePath + ".tmp." + process.pid
2686
+ ]) {
2687
+ try {
2688
+ if (existsSync5(p2))
2689
+ rmSync3(p2, { force: true });
2690
+ } catch {}
2691
+ }
2692
+ try {
2693
+ const { dirname: dirname5, join: join7 } = await import("path");
2694
+ const handoff = join7(dirname5(statePath), ".ostacky-handoff-compaction.json");
2695
+ if (existsSync5(handoff)) {
2696
+ try {
2697
+ rmSync3(handoff, { force: true });
2698
+ } catch {}
2699
+ }
2700
+ } catch {}
2701
+ }
2645
2702
  }
2646
2703
  }
2647
2704
  function takeFileSnapshot(path) {
@@ -2723,8 +2780,8 @@ async function installSkill(item, manifest, paths) {
2723
2780
  const treeHash = computeTreeHash(src);
2724
2781
  if (item.sha256 && treeHash !== item.sha256) {
2725
2782
  throw new Error(`Tree hash inválido para skill "${item.name}"
2726
- ` + ` esperado: ${item.sha256}
2727
- ` + ` recibido: ${treeHash}`);
2783
+ esperado: ${item.sha256}
2784
+ recibido: ${treeHash}`);
2728
2785
  }
2729
2786
  const dest = join6(paths.skills, item.name);
2730
2787
  if (existsSync5(dest)) {
@@ -2764,8 +2821,8 @@ async function installMcpServer(item, manifest, paths) {
2764
2821
  const treeHash = computeTreeHash(src);
2765
2822
  if (item.sha256 && treeHash !== item.sha256) {
2766
2823
  throw new Error(`Tree hash inválido para MCP server "${item.name}"
2767
- ` + ` esperado: ${item.sha256}
2768
- ` + ` recibido: ${treeHash}`);
2824
+ esperado: ${item.sha256}
2825
+ recibido: ${treeHash}`);
2769
2826
  }
2770
2827
  const nodeExecutable = getVerifiedNodeExecutable();
2771
2828
  const projectRoot = dirname4(paths.root);
@@ -2969,25 +3026,63 @@ function printPostInstallSteps() {
2969
3026
  ].join(`
2970
3027
  `), "Próximos pasos");
2971
3028
  }
2972
- async function resolveOpenCodePaths() {
2973
- const existing = findOpenCodeDir();
2974
- if (existing) {
2975
- const paths2 = ensureOpenCodePaths(existing);
2976
- ye(existing, "Directorio .opencode encontrado");
2977
- return paths2;
3029
+ async function resolveOpenCodePaths(scope) {
3030
+ if (scope === "local" || scope === "global" || scope === "auto") {
3031
+ const dir2 = getOpenCodeDirForScope(scope);
3032
+ try {
3033
+ const paths = ensureOpenCodePaths(dir2);
3034
+ if (scope === "global")
3035
+ ye(dir2, "Instalación global");
3036
+ else if (scope === "auto")
3037
+ ye(dir2, `Scope auto → ${dir2.includes(homedir2()) && dir2.includes(".config") ? "global" : "local"}`);
3038
+ else
3039
+ ye(dir2, "Instalación local");
3040
+ return paths;
3041
+ } catch (e2) {
3042
+ const msg = e2.message ?? "";
3043
+ if (scope === "global" && (msg.includes("EACCES") || msg.includes("permission"))) {
3044
+ v2.warn(`No se pudo escribir en global (${dir2}): ${msg}. ¿Instalar local?`);
3045
+ const retry = await me({ message: "¿Reintentar como instalación local?" });
3046
+ onCancel(retry);
3047
+ if (retry) {
3048
+ const localDir2 = getOpenCodeDirForScope("local");
3049
+ return ensureOpenCodePaths(localDir2);
3050
+ }
3051
+ }
3052
+ throw e2;
3053
+ }
2978
3054
  }
2979
- const projectRoot = findProjectRoot();
2980
- ye(`No se encontró .opencode
2981
- Raíz detectada: ${projectRoot}`, "Aviso");
2982
- const create = await me({
2983
- message: "¿Deseas crear la estructura .opencode aquí?"
3055
+ const cwd = process.cwd();
3056
+ const localDir = getOpenCodeDirForScope("local", cwd);
3057
+ const globalDir = getGlobalOpenCodeDir();
3058
+ const hasLocal = !!findOpenCodeDir(cwd);
3059
+ const scopeChoice = await de({
3060
+ message: `¿Instalar en proyecto local (${localDir}) o global (${globalDir})?`,
3061
+ options: [
3062
+ { value: "local", label: "Local", hint: `${localDir} (recomendado)` },
3063
+ { value: "global", label: "Global", hint: globalDir }
3064
+ ],
3065
+ initialValue: "local"
2984
3066
  });
2985
- onCancel(create);
2986
- if (!create)
2987
- return null;
2988
- const paths = createOpenCodeDir(projectRoot);
2989
- v2.success(`Estructura creada en ${paths.root}`);
2990
- return paths;
3067
+ onCancel(scopeChoice);
3068
+ const chosen = scopeChoice;
3069
+ const dir = getOpenCodeDirForScope(chosen, cwd);
3070
+ try {
3071
+ return ensureOpenCodePaths(dir);
3072
+ } catch (e2) {
3073
+ const msg = e2.message ?? "";
3074
+ if (chosen === "global" && (msg.includes("EACCES") || msg.includes("permission"))) {
3075
+ v2.warn(`No se pudo escribir en global (${dir}): ${msg}.`);
3076
+ const retry = await me({ message: "¿Instalar local en su lugar?" });
3077
+ onCancel(retry);
3078
+ if (retry)
3079
+ return ensureOpenCodePaths(getOpenCodeDirForScope("local", cwd));
3080
+ }
3081
+ throw e2;
3082
+ }
3083
+ }
3084
+ function isGlobalScope(paths) {
3085
+ return paths.root === getGlobalOpenCodeDir() || paths.root.startsWith(getGlobalOpenCodeDir() + "/");
2991
3086
  }
2992
3087
  function getOrphanedItems(manifest, paths) {
2993
3088
  const lockfile = readLockfile(paths.root);
@@ -3105,7 +3200,13 @@ async function doInstallStack(toolsDir, projectRoot) {
3105
3200
  async function doInstallAll(manifest, paths) {
3106
3201
  const spin = L2();
3107
3202
  let errors = 0;
3108
- ensureToolDirs(paths.tools, ["codegraph", "engram", "context7"]);
3203
+ const isGlobal = paths.root === getGlobalOpenCodeDir() || paths.root.startsWith(getGlobalOpenCodeDir() + "/");
3204
+ if (!isGlobal) {
3205
+ ensureToolDirs(paths.tools, ["codegraph", "engram", "context7"]);
3206
+ } else {
3207
+ v2.info("Scope global detectado: el stack (CodeGraph/Engram) permanece siempre en <proyecto>/.opencode/tools — se omite instalación de stack global.");
3208
+ v2.info("Para instalar el stack, ejecutá 'npx ostacky install-stack --scope local' dentro de cada proyecto.");
3209
+ }
3109
3210
  for (const agent of manifest.agents) {
3110
3211
  spin.start(`Descargando agente: ${agent.name} (${agent.version})`);
3111
3212
  try {
@@ -3150,17 +3251,22 @@ async function doInstallAll(manifest, paths) {
3150
3251
  errors++;
3151
3252
  }
3152
3253
  }
3153
- v2.info("Instalando stack de herramientas...");
3154
- const stackOk = await doInstallStack(paths.tools, dirname6(paths.root));
3155
- if (!stackOk)
3156
- errors++;
3157
- const missingTools = [];
3158
- const codegraphDir = join8(paths.tools, "codegraph");
3159
- const engramDir = join8(paths.tools, "engram");
3160
- if (!existsSync7(codegraphDir) || !findBinaryInDir(codegraphDir, "codegraph"))
3161
- missingTools.push("CodeGraph");
3162
- if (!existsSync7(engramDir) || !findBinaryInDir(engramDir, "engram"))
3163
- missingTools.push("Engram");
3254
+ let stackOk = true;
3255
+ let missingTools = [];
3256
+ if (isGlobal) {
3257
+ stackOk = true;
3258
+ } else {
3259
+ v2.info("Instalando stack de herramientas...");
3260
+ stackOk = await doInstallStack(paths.tools, dirname6(paths.root));
3261
+ if (!stackOk)
3262
+ errors++;
3263
+ const codegraphDir = join8(paths.tools, "codegraph");
3264
+ const engramDir = join8(paths.tools, "engram");
3265
+ if (!existsSync7(codegraphDir) || !findBinaryInDir(codegraphDir, "codegraph"))
3266
+ missingTools.push("CodeGraph");
3267
+ if (!existsSync7(engramDir) || !findBinaryInDir(engramDir, "engram"))
3268
+ missingTools.push("Engram");
3269
+ }
3164
3270
  if (missingTools.length > 0) {
3165
3271
  v2.warn(`Faltan herramientas del stack: ${missingTools.join(", ")}.
3166
3272
  ` + "Ejecutá `/install-stack` desde el agente para instalarlas manualmente.");
@@ -3675,10 +3781,10 @@ async function doUninstallMcpByName(name, paths) {
3675
3781
  }
3676
3782
 
3677
3783
  // src/prompts/index.ts
3678
- async function runInteractiveMenu() {
3784
+ async function runInteractiveMenu(scope) {
3679
3785
  we(" OpenCode Installer ");
3680
3786
  const manifest = await loadManifest();
3681
- const paths = await resolveOpenCodePaths();
3787
+ const paths = await resolveOpenCodePaths(scope ?? null);
3682
3788
  if (!paths) {
3683
3789
  fe("Instalación cancelada.");
3684
3790
  return;
@@ -3726,6 +3832,12 @@ async function runInteractiveMenu() {
3726
3832
  fe("Listo.");
3727
3833
  break;
3728
3834
  case "stack": {
3835
+ if (isGlobalScope(paths)) {
3836
+ v2.error("install-stack requiere scope local; el stack vive en <proyecto>/.opencode/tools");
3837
+ v2.info(`Elegiste global (${paths.root}) — el stack debe instalarse por proyecto local.`);
3838
+ fe("Cancelado.");
3839
+ break;
3840
+ }
3729
3841
  ensureToolDirs(paths.tools, ["codegraph", "engram", "context7"]);
3730
3842
  const stackOk = await doInstallStack(paths.tools, dirname7(paths.root));
3731
3843
  if (!stackOk)
@@ -3748,10 +3860,10 @@ async function runInteractiveMenu() {
3748
3860
  break;
3749
3861
  }
3750
3862
  }
3751
- async function runInstallCommand() {
3863
+ async function runInstallCommand(scope) {
3752
3864
  we(" OpenCode Installer ");
3753
3865
  const manifest = await loadManifest();
3754
- const paths = await resolveOpenCodePaths();
3866
+ const paths = await resolveOpenCodePaths(scope ?? null);
3755
3867
  if (!paths) {
3756
3868
  fe("Cancelado.");
3757
3869
  return;
@@ -3761,10 +3873,10 @@ async function runInstallCommand() {
3761
3873
  printPostInstallSteps();
3762
3874
  fe(process.exitCode ? "Instalación parcial." : "Instalación completada.");
3763
3875
  }
3764
- async function runAddAgentCommand() {
3876
+ async function runAddAgentCommand(scope) {
3765
3877
  we(" OpenCode Installer ");
3766
3878
  const manifest = await loadManifest();
3767
- const paths = await resolveOpenCodePaths();
3879
+ const paths = await resolveOpenCodePaths(scope ?? null);
3768
3880
  if (!paths) {
3769
3881
  fe("Cancelado.");
3770
3882
  return;
@@ -3773,10 +3885,10 @@ async function runAddAgentCommand() {
3773
3885
  printPostInstallSteps();
3774
3886
  fe("Listo.");
3775
3887
  }
3776
- async function runAddCommandCommand() {
3888
+ async function runAddCommandCommand(scope) {
3777
3889
  we(" OpenCode Installer ");
3778
3890
  const manifest = await loadManifest();
3779
- const paths = await resolveOpenCodePaths();
3891
+ const paths = await resolveOpenCodePaths(scope ?? null);
3780
3892
  if (!paths) {
3781
3893
  fe("Cancelado.");
3782
3894
  return;
@@ -3785,10 +3897,10 @@ async function runAddCommandCommand() {
3785
3897
  printPostInstallSteps();
3786
3898
  fe("Listo.");
3787
3899
  }
3788
- async function runAddSkillCommand() {
3900
+ async function runAddSkillCommand(scope) {
3789
3901
  we(" OpenCode Installer ");
3790
3902
  const manifest = await loadManifest();
3791
- const paths = await resolveOpenCodePaths();
3903
+ const paths = await resolveOpenCodePaths(scope ?? null);
3792
3904
  if (!paths) {
3793
3905
  fe("Cancelado.");
3794
3906
  return;
@@ -3797,10 +3909,10 @@ async function runAddSkillCommand() {
3797
3909
  printPostInstallSteps();
3798
3910
  fe("Listo.");
3799
3911
  }
3800
- async function runAddMcpCommand() {
3912
+ async function runAddMcpCommand(scope) {
3801
3913
  we(" OpenCode Installer ");
3802
3914
  const manifest = await loadManifest();
3803
- const paths = await resolveOpenCodePaths();
3915
+ const paths = await resolveOpenCodePaths(scope ?? null);
3804
3916
  if (!paths) {
3805
3917
  fe("Cancelado.");
3806
3918
  return;
@@ -3809,9 +3921,15 @@ async function runAddMcpCommand() {
3809
3921
  printPostInstallSteps();
3810
3922
  fe("Listo.");
3811
3923
  }
3812
- async function runInstallStackCommand() {
3924
+ async function runInstallStackCommand(scope) {
3925
+ if (scope === "global") {
3926
+ v2.error("install-stack requiere scope local; el stack vive en <proyecto>/.opencode/tools");
3927
+ fe("Usá: npx ostacky install-stack --scope local");
3928
+ process.exitCode = 1;
3929
+ return;
3930
+ }
3813
3931
  we(" OpenCode Installer — Stack ");
3814
- const paths = await resolveOpenCodePaths();
3932
+ const paths = await resolveOpenCodePaths(scope ?? null);
3815
3933
  if (!paths) {
3816
3934
  fe("Cancelado.");
3817
3935
  return;
@@ -3822,9 +3940,9 @@ async function runInstallStackCommand() {
3822
3940
  process.exitCode = 1;
3823
3941
  fe(stackOk ? "Stack instalado." : "Stack instalado parcialmente.");
3824
3942
  }
3825
- async function runUninstallStackCommand() {
3943
+ async function runUninstallStackCommand(scope) {
3826
3944
  we(" OpenCode Installer — Stack ");
3827
- const paths = await resolveOpenCodePaths();
3945
+ const paths = await resolveOpenCodePaths(scope ?? null);
3828
3946
  if (!paths) {
3829
3947
  fe("Cancelado.");
3830
3948
  return;
@@ -3846,10 +3964,10 @@ async function runUninstallStackCommand() {
3846
3964
  }
3847
3965
  fe("Stack desinstalado.");
3848
3966
  }
3849
- async function runUpdateCommand() {
3967
+ async function runUpdateCommand(scope) {
3850
3968
  we(" OpenCode Installer ");
3851
3969
  const manifest = await loadLatestManifest();
3852
- const paths = await resolveOpenCodePaths();
3970
+ const paths = await resolveOpenCodePaths(scope ?? null);
3853
3971
  if (!paths) {
3854
3972
  fe("Cancelado.");
3855
3973
  return;
@@ -3857,9 +3975,9 @@ async function runUpdateCommand() {
3857
3975
  await doUpdate(manifest, paths);
3858
3976
  fe("Actualización completada.");
3859
3977
  }
3860
- async function runUninstallCommand() {
3978
+ async function runUninstallCommand(scope) {
3861
3979
  we(" OpenCode Installer ");
3862
- const paths = await resolveOpenCodePaths();
3980
+ const paths = await resolveOpenCodePaths(scope ?? null);
3863
3981
  if (!paths) {
3864
3982
  fe("Cancelado.");
3865
3983
  return;
@@ -3867,9 +3985,9 @@ async function runUninstallCommand() {
3867
3985
  await doUninstall(paths);
3868
3986
  fe("Desinstalación completada.");
3869
3987
  }
3870
- async function runUninstallAgentCommand(name) {
3988
+ async function runUninstallAgentCommand(name, scope) {
3871
3989
  we(" OpenCode Installer ");
3872
- const paths = await resolveOpenCodePaths();
3990
+ const paths = await resolveOpenCodePaths(scope ?? null);
3873
3991
  if (!paths) {
3874
3992
  fe("Cancelado.");
3875
3993
  return;
@@ -3902,9 +4020,9 @@ async function runUninstallAgentCommand(name) {
3902
4020
  }
3903
4021
  fe("Listo.");
3904
4022
  }
3905
- async function runUninstallCommandCommand(name) {
4023
+ async function runUninstallCommandCommand(name, scope) {
3906
4024
  we(" OpenCode Installer ");
3907
- const paths = await resolveOpenCodePaths();
4025
+ const paths = await resolveOpenCodePaths(scope ?? null);
3908
4026
  if (!paths) {
3909
4027
  fe("Cancelado.");
3910
4028
  return;
@@ -3937,9 +4055,9 @@ async function runUninstallCommandCommand(name) {
3937
4055
  }
3938
4056
  fe("Listo.");
3939
4057
  }
3940
- async function runUninstallSkillCommand(name) {
4058
+ async function runUninstallSkillCommand(name, scope) {
3941
4059
  we(" OpenCode Installer ");
3942
- const paths = await resolveOpenCodePaths();
4060
+ const paths = await resolveOpenCodePaths(scope ?? null);
3943
4061
  if (!paths) {
3944
4062
  fe("Cancelado.");
3945
4063
  return;
@@ -3972,9 +4090,9 @@ async function runUninstallSkillCommand(name) {
3972
4090
  }
3973
4091
  fe("Listo.");
3974
4092
  }
3975
- async function runUninstallMcpCommand(name) {
4093
+ async function runUninstallMcpCommand(name, scope) {
3976
4094
  we(" OpenCode Installer ");
3977
- const paths = await resolveOpenCodePaths();
4095
+ const paths = await resolveOpenCodePaths(scope ?? null);
3978
4096
  if (!paths) {
3979
4097
  fe("Cancelado.");
3980
4098
  return;
@@ -4013,67 +4131,110 @@ var HELP = `
4013
4131
  ostacky — Instalador de agentes, comandos, skills y MCPs para OpenCode
4014
4132
 
4015
4133
  Uso:
4016
- npx ostacky Menú interactivo (instalación completa)
4017
- npx ostacky install Instalar TODO (agente + skills + MCPs + CodeGraph + OpenSpec + Engram + Context7)
4018
- npx ostacky add agent Agregar agente(s)
4019
- npx ostacky add command Agregar command(s)
4020
- npx ostacky add skill Agregar skill(s)
4021
- npx ostacky add mcp Agregar MCP server(s)
4022
- npx ostacky install-stack Instalar solo el stack de herramientas (CodeGraph, OpenSpec, Engram, Context7)
4023
- npx ostacky uninstall-stack Remover la configuración del stack del proyecto
4024
- npx ostacky update Actualizar instalación
4025
- npx ostacky uninstall Desinstalar todo
4026
- npx ostacky uninstall agent Desinstalar agente(s)
4027
- npx ostacky uninstall command Desinstalar command(s)
4028
- npx ostacky uninstall skill Desinstalar skill(s)
4029
- npx ostacky uninstall mcp Desinstalar MCP server(s)
4134
+ npx ostacky [--scope local|global|auto] Menú interactivo (instalación completa, pregunta local vs global, default local)
4135
+ npx ostacky install [--scope local|global|auto] Instalar TODO (agente + skills + MCPs + CodeGraph + OpenSpec + Engram + Context7)
4136
+ npx ostacky add agent [--scope local|global|auto] Agregar agente(s)
4137
+ npx ostacky add command [--scope ...] Agregar command(s)
4138
+ npx ostacky add skill [--scope ...] Agregar skill(s)
4139
+ npx ostacky add mcp [--scope ...] Agregar MCP server(s)
4140
+ npx ostacky install-stack [--scope local|auto] Instalar solo el stack de herramientas (CodeGraph, OpenSpec, Engram, Context7) — global bloquea con error
4141
+ npx ostacky uninstall-stack [--scope local|global|auto] Remover la configuración del stack del proyecto
4142
+ npx ostacky update [--scope ...] Actualizar instalación
4143
+ npx ostacky uninstall [--scope ...] Desinstalar todo
4144
+ npx ostacky uninstall agent [--scope ...] Desinstalar agente(s)
4145
+ npx ostacky uninstall command [--scope ...] Desinstalar command(s)
4146
+ npx ostacky uninstall skill [--scope ...] Desinstalar skill(s)
4147
+ npx ostacky uninstall mcp [--scope ...] Desinstalar MCP server(s)
4030
4148
  npx ostacky --help Mostrar esta ayuda
4031
4149
  npx ostacky --version Mostrar versión
4150
+
4151
+ Scope:
4152
+ --scope local Escribe en <proyecto>/.opencode (recomendado, default al preguntar)
4153
+ --scope global Escribe en ~/.config/opencode (o %APPDATA%\\opencode en Windows)
4154
+ --scope auto Elige local si existe .opencode o .git, si no global
4155
+ Sin flag Pregunta interactiva local (default) vs global
4032
4156
  `.trim();
4033
- var [, , cmd, subcmd] = process.argv;
4157
+ function parseScopeArg(argv = process.argv) {
4158
+ for (let i = 0;i < argv.length; i++) {
4159
+ const arg = argv[i];
4160
+ if (arg === "--scope" && i + 1 < argv.length) {
4161
+ const v3 = argv[i + 1];
4162
+ if (v3 === "local" || v3 === "global" || v3 === "auto")
4163
+ return v3;
4164
+ }
4165
+ if (arg.startsWith("--scope=")) {
4166
+ const v3 = arg.split("=")[1];
4167
+ if (v3 === "local" || v3 === "global" || v3 === "auto")
4168
+ return v3;
4169
+ }
4170
+ }
4171
+ return null;
4172
+ }
4173
+ function withoutScopeArgs(argv) {
4174
+ const out = [];
4175
+ for (let i = 0;i < argv.length; i++) {
4176
+ const arg = argv[i];
4177
+ if (arg === "--scope" && i + 1 < argv.length) {
4178
+ i++;
4179
+ continue;
4180
+ }
4181
+ if (arg.startsWith("--scope="))
4182
+ continue;
4183
+ out.push(arg);
4184
+ }
4185
+ return out;
4186
+ }
4187
+ var scope = parseScopeArg();
4188
+ var argvNoScope = withoutScopeArgs(process.argv);
4189
+ var [, , cmd, subcmd] = argvNoScope;
4034
4190
  async function main() {
4035
4191
  switch (cmd) {
4036
4192
  case "install":
4037
- await runInstallCommand();
4193
+ await runInstallCommand(scope);
4038
4194
  break;
4039
4195
  case "install-stack":
4040
- await runInstallStackCommand();
4196
+ if (scope === "global") {
4197
+ console.error("Error: install-stack requiere scope local; el stack vive en <proyecto>/.opencode/tools");
4198
+ console.error("Sugerencia: ejecutá 'npx ostacky install-stack --scope local' dentro de cada proyecto.");
4199
+ process.exit(1);
4200
+ }
4201
+ await runInstallStackCommand(scope);
4041
4202
  break;
4042
4203
  case "uninstall-stack":
4043
- await runUninstallStackCommand();
4204
+ await runUninstallStackCommand(scope);
4044
4205
  break;
4045
4206
  case "add":
4046
4207
  if (subcmd === "agent") {
4047
- await runAddAgentCommand();
4208
+ await runAddAgentCommand(scope);
4048
4209
  } else if (subcmd === "command") {
4049
- await runAddCommandCommand();
4210
+ await runAddCommandCommand(scope);
4050
4211
  } else if (subcmd === "skill") {
4051
- await runAddSkillCommand();
4212
+ await runAddSkillCommand(scope);
4052
4213
  } else if (subcmd === "mcp") {
4053
- await runAddMcpCommand();
4214
+ await runAddMcpCommand(scope);
4054
4215
  } else {
4055
4216
  console.error(`Tipo desconocido: "${subcmd}". Usa 'agent', 'command', 'skill' o 'mcp'.`);
4056
4217
  process.exit(1);
4057
4218
  }
4058
4219
  break;
4059
4220
  case "update":
4060
- await runUpdateCommand();
4221
+ await runUpdateCommand(scope);
4061
4222
  break;
4062
4223
  case "uninstall":
4063
4224
  if (subcmd === "agent") {
4064
- const name = process.argv[4];
4065
- await runUninstallAgentCommand(name);
4225
+ const name = argvNoScope[4];
4226
+ await runUninstallAgentCommand(name, scope);
4066
4227
  } else if (subcmd === "command") {
4067
- const name = process.argv[4];
4068
- await runUninstallCommandCommand(name);
4228
+ const name = argvNoScope[4];
4229
+ await runUninstallCommandCommand(name, scope);
4069
4230
  } else if (subcmd === "skill") {
4070
- const name = process.argv[4];
4071
- await runUninstallSkillCommand(name);
4231
+ const name = argvNoScope[4];
4232
+ await runUninstallSkillCommand(name, scope);
4072
4233
  } else if (subcmd === "mcp") {
4073
- const name = process.argv[4];
4074
- await runUninstallMcpCommand(name);
4234
+ const name = argvNoScope[4];
4235
+ await runUninstallMcpCommand(name, scope);
4075
4236
  } else if (subcmd === undefined) {
4076
- await runUninstallCommand();
4237
+ await runUninstallCommand(scope);
4077
4238
  } else {
4078
4239
  console.error(`Subcomando desconocido: "${subcmd}". Usa 'agent', 'command', 'skill', 'mcp' o nada.`);
4079
4240
  process.exit(1);
@@ -4092,7 +4253,7 @@ async function main() {
4092
4253
  console.error(`Comando desconocido: "${cmd}". Usá --help para ver los comandos disponibles.`);
4093
4254
  process.exit(1);
4094
4255
  }
4095
- await runInteractiveMenu();
4256
+ await runInteractiveMenu(scope);
4096
4257
  }
4097
4258
  }
4098
4259
  main().catch((e2) => {