ostacky 0.7.0 → 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.0",
549
+ version: "0.7.2",
524
550
  repo: "JaimeHoracio/Ostacky",
525
- tag: "v0.7.0",
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.0: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
531
- version: "0.7.0",
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.0: Paso 1.5 controller expandido con verificación post-instalación y troubleshooting.",
540
- version: "0.7.0",
541
- sha256: "c957627dfafabfdb9da2717546b996d1d8856f08a6e4c48caf72c562363b566b"
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.0",
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.0: 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.0",
557
- sha256: "0ec6fa0fc2900c78b884258168309653dea4b77ded3596bb56866f689ec2404c"
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.0",
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.0",
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.0",
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.0",
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.0",
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.0",
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.0",
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.0",
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.0",
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.0",
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.0",
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.0",
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.0",
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.0",
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.0",
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.0",
671
- sha256: "e5090e475729f21871bb25bce738000f3616955ed3edb3ba926dd740d3298948"
696
+ version: "0.7.2",
697
+ sha256: "376dc90fa27ab7e5947747f20ccf2cc76f9326c77b567fda388252a60d25c431"
672
698
  }
673
699
  ]
674
700
  };
@@ -1045,7 +1071,8 @@ __export(exports_stack, {
1045
1071
  installEngram: () => installEngram,
1046
1072
  installCodeGraph: () => installCodeGraph,
1047
1073
  buildLocalMcpCommand: () => buildLocalMcpCommand,
1048
- buildEngramDownloadUrl: () => buildEngramDownloadUrl
1074
+ buildEngramDownloadUrl: () => buildEngramDownloadUrl,
1075
+ OPENSPEC_NPM_PACKAGE: () => OPENSPEC_NPM_PACKAGE
1049
1076
  });
1050
1077
  import { chmodSync, copyFileSync as copyFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync4, rmSync as rmSync4, unlinkSync as unlinkSync3 } from "fs";
1051
1078
  import { basename as basename2, dirname as dirname5, join as join7, resolve as resolve2 } from "path";
@@ -1185,20 +1212,56 @@ async function installCodeGraph(toolsDir) {
1185
1212
  }
1186
1213
  function setupOpenSpec(projectRoot = findProjectRoot()) {
1187
1214
  const useBun = isCommandAvailable("bun");
1188
- try {
1189
- const invocation = getCommandInvocation(useBun ? "bunx" : "npx", useBun ? ["openspec", "init", "--tools", "opencode", "--force"] : ["--yes", "openspec", "init", "--tools", "opencode", "--force"]);
1190
- execFileSync3(invocation.command, invocation.args, {
1191
- stdio: "pipe",
1192
- timeout: 120000,
1193
- cwd: projectRoot
1194
- });
1215
+ const fail = (msg) => ({ success: false, message: msg });
1216
+ const tryDirect = () => {
1217
+ try {
1218
+ const invocation = getCommandInvocation(useBun ? "bunx" : "npx", useBun ? [OPENSPEC_NPM_PACKAGE, "init", "--tools", "opencode", "--force"] : ["--yes", OPENSPEC_NPM_PACKAGE, "init", "--tools", "opencode", "--force"]);
1219
+ execFileSync3(invocation.command, invocation.args, {
1220
+ stdio: "pipe",
1221
+ timeout: 120000,
1222
+ cwd: projectRoot
1223
+ });
1224
+ return true;
1225
+ } catch {
1226
+ return false;
1227
+ }
1228
+ };
1229
+ const tryInstallGlobal = () => {
1230
+ if (!useBun && !isCommandAvailable("npm")) {
1231
+ return { ok: false, error: "Ni bun ni npm están disponibles para instalar OpenSpec globalmente" };
1232
+ }
1233
+ const pkgManager = useBun ? "bun" : "npm";
1234
+ const installArgs = useBun ? ["add", "-g", OPENSPEC_NPM_PACKAGE] : ["install", "-g", OPENSPEC_NPM_PACKAGE];
1235
+ try {
1236
+ const invocation = getCommandInvocation(pkgManager, installArgs);
1237
+ execFileSync3(invocation.command, invocation.args, { stdio: "pipe", timeout: 120000 });
1238
+ } catch (installErr) {
1239
+ return { ok: false, error: `Instalación global falló: ${installErr.message}` };
1240
+ }
1241
+ try {
1242
+ const retryInvocation = getCommandInvocation("openspec", ["init", "--tools", "opencode", "--force"]);
1243
+ execFileSync3(retryInvocation.command, retryInvocation.args, {
1244
+ stdio: "pipe",
1245
+ timeout: 120000,
1246
+ cwd: projectRoot
1247
+ });
1248
+ return { ok: true };
1249
+ } catch (retryErr) {
1250
+ return { ok: false, error: `Reintento con binario global falló: ${retryErr.message}` };
1251
+ }
1252
+ };
1253
+ if (tryDirect()) {
1195
1254
  return { success: true, message: "OpenSpec configurado para OpenCode" };
1196
- } catch (e2) {
1255
+ }
1256
+ const fallback = tryInstallGlobal();
1257
+ if (fallback.ok) {
1197
1258
  return {
1198
- success: false,
1199
- message: `Error configurando OpenSpec: ${e2.message}`
1259
+ success: true,
1260
+ message: `OpenSpec configurado para OpenCode (binario instalado globalmente con ${useBun ? "bun" : "npm"})`
1200
1261
  };
1201
1262
  }
1263
+ const manualCmd = useBun ? `bun add -g ${OPENSPEC_NPM_PACKAGE}` : `npm install -g ${OPENSPEC_NPM_PACKAGE}`;
1264
+ return fail(`Error configurando OpenSpec: ni bunx/npx ni la instalación global resolvieron el binario. ` + `Solución manual: ejecuta \`${manualCmd}\` y luego corré /install-stack de nuevo. ` + `Detalle: ${fallback.error}`);
1202
1265
  }
1203
1266
  async function installEngram(toolsDir) {
1204
1267
  const location = resolveToolInstallLocation(toolsDir);
@@ -1422,6 +1485,7 @@ function verifyMcpServers(projectRoot) {
1422
1485
  }
1423
1486
  return result;
1424
1487
  }
1488
+ var OPENSPEC_NPM_PACKAGE = "@fission-ai/openspec";
1425
1489
  var init_stack = __esm(() => {
1426
1490
  init_fs();
1427
1491
  init_config();
@@ -1430,7 +1494,7 @@ var init_stack = __esm(() => {
1430
1494
  // package.json
1431
1495
  var package_default = {
1432
1496
  name: "ostacky",
1433
- version: "0.7.0",
1497
+ version: "0.7.2",
1434
1498
  description: "Instalador interactivo de agentes y comandos para OpenCode",
1435
1499
  type: "module",
1436
1500
  bin: {
@@ -2430,6 +2494,7 @@ import { dirname as dirname7 } from "path";
2430
2494
  init_github();
2431
2495
  init_fs();
2432
2496
  init_lockfile();
2497
+ import { homedir as homedir2 } from "os";
2433
2498
 
2434
2499
  // src/installer.ts
2435
2500
  init_github();
@@ -2486,7 +2551,13 @@ function validateMcpModule(nodeExecutable, serverPath, cwd) {
2486
2551
  throw new Error(`El MCP no pasó la validación de carga: ${error.message}`);
2487
2552
  }
2488
2553
  }
2489
- async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath) {
2554
+ var DEFAULT_PROBE_OPTIONS = {
2555
+ requiredTools: ["ping", "start_request"],
2556
+ exerciseWrite: true
2557
+ };
2558
+ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, options = {}) {
2559
+ const requiredTools = options.requiredTools ?? DEFAULT_PROBE_OPTIONS.requiredTools;
2560
+ const exerciseWrite = options.exerciseWrite ?? requiredTools.includes("start_request");
2490
2561
  try {
2491
2562
  await new Promise((resolve2, reject) => {
2492
2563
  const child = spawn(nodeExecutable, [serverPath], {
@@ -2502,6 +2573,7 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath) {
2502
2573
  let stderr = "";
2503
2574
  let stdoutBuffer = "";
2504
2575
  const requestTimeout = 1e4;
2576
+ let exitTimeout;
2505
2577
  const finish = (error) => {
2506
2578
  if (settled)
2507
2579
  return;
@@ -2510,9 +2582,18 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath) {
2510
2582
  try {
2511
2583
  child.stdin?.end();
2512
2584
  } catch {}
2513
- 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);
2514
2592
  child.kill();
2515
- error ? reject(error) : resolve2();
2593
+ exitTimeout = setTimeout(onExit, 500);
2594
+ } else {
2595
+ error ? reject(error) : resolve2();
2596
+ }
2516
2597
  };
2517
2598
  const fail = (message) => finish(new Error(`${message}${stderr ? `: ${stderr.trim()}` : ""}`));
2518
2599
  const send = (message) => {
@@ -2555,17 +2636,22 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath) {
2555
2636
  }
2556
2637
  if (message.id === 2) {
2557
2638
  const tools = message.result?.tools ?? [];
2558
- if (!tools.some((tool) => tool.name === "ping") || !tools.some((tool) => tool.name === "start_request")) {
2559
- fail("El MCP inició pero no expuso las tools requeridas");
2639
+ const missing = requiredTools.filter((name) => !tools.some((tool) => tool.name === name));
2640
+ if (missing.length > 0) {
2641
+ fail(`El MCP inició pero no expuso las tools requeridas: ${missing.join(", ")}`);
2560
2642
  return;
2561
2643
  }
2562
- send({
2563
- jsonrpc: "2.0",
2564
- id: 3,
2565
- method: "tools/call",
2566
- params: { name: "start_request", arguments: { requestId: "installer-probe" } }
2567
- });
2568
- continue;
2644
+ if (exerciseWrite) {
2645
+ send({
2646
+ jsonrpc: "2.0",
2647
+ id: 3,
2648
+ method: "tools/call",
2649
+ params: { name: "start_request", arguments: { requestId: "installer-probe" } }
2650
+ });
2651
+ continue;
2652
+ }
2653
+ finish();
2654
+ return;
2569
2655
  }
2570
2656
  if (message.id === 3) {
2571
2657
  const result = message.result;
@@ -2584,15 +2670,35 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath) {
2584
2670
  params: {
2585
2671
  protocolVersion: "2025-03-26",
2586
2672
  capabilities: {},
2587
- clientInfo: { name: "ostacky-installer", version: "0.7.0" }
2673
+ clientInfo: { name: "ostacky-installer", version: "0.7.2" }
2588
2674
  }
2589
2675
  });
2590
2676
  });
2591
2677
  } finally {
2592
- if (statePath && existsSync5(statePath))
2593
- rmSync3(statePath, { force: true });
2594
- if (statePath && existsSync5(statePath + ".backup"))
2595
- 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
+ }
2596
2702
  }
2597
2703
  }
2598
2704
  function takeFileSnapshot(path) {
@@ -2674,8 +2780,8 @@ async function installSkill(item, manifest, paths) {
2674
2780
  const treeHash = computeTreeHash(src);
2675
2781
  if (item.sha256 && treeHash !== item.sha256) {
2676
2782
  throw new Error(`Tree hash inválido para skill "${item.name}"
2677
- ` + ` esperado: ${item.sha256}
2678
- ` + ` recibido: ${treeHash}`);
2783
+ esperado: ${item.sha256}
2784
+ recibido: ${treeHash}`);
2679
2785
  }
2680
2786
  const dest = join6(paths.skills, item.name);
2681
2787
  if (existsSync5(dest)) {
@@ -2715,8 +2821,8 @@ async function installMcpServer(item, manifest, paths) {
2715
2821
  const treeHash = computeTreeHash(src);
2716
2822
  if (item.sha256 && treeHash !== item.sha256) {
2717
2823
  throw new Error(`Tree hash inválido para MCP server "${item.name}"
2718
- ` + ` esperado: ${item.sha256}
2719
- ` + ` recibido: ${treeHash}`);
2824
+ esperado: ${item.sha256}
2825
+ recibido: ${treeHash}`);
2720
2826
  }
2721
2827
  const nodeExecutable = getVerifiedNodeExecutable();
2722
2828
  const projectRoot = dirname4(paths.root);
@@ -2754,7 +2860,9 @@ async function installMcpServer(item, manifest, paths) {
2754
2860
  throw new Error(`El MCP ${item.name} no contiene index.js después de instalarse.`);
2755
2861
  }
2756
2862
  validateMcpModule(nodeExecutable, stagedServerPath, staging);
2757
- await probeMcpServer(nodeExecutable, stagedServerPath, staging, item.name === "ostacky-controller" ? join6(staging, ".probe-state.json") : undefined);
2863
+ const isController = item.name === "ostacky-controller";
2864
+ const probeOptions = isController ? { requiredTools: ["ping", "start_request"], exerciseWrite: true } : { requiredTools: [], exerciseWrite: false };
2865
+ await probeMcpServer(nodeExecutable, stagedServerPath, staging, isController ? join6(staging, ".probe-state.json") : undefined, probeOptions);
2758
2866
  const configPath = findOpenCodeConfig(projectRoot) ?? join6(projectRoot, "opencode.json");
2759
2867
  const configSnapshot = takeFileSnapshot(configPath);
2760
2868
  const lockfileSnapshot = takeFileSnapshot(getLockfilePath(paths.root));
@@ -2918,25 +3026,63 @@ function printPostInstallSteps() {
2918
3026
  ].join(`
2919
3027
  `), "Próximos pasos");
2920
3028
  }
2921
- async function resolveOpenCodePaths() {
2922
- const existing = findOpenCodeDir();
2923
- if (existing) {
2924
- const paths2 = ensureOpenCodePaths(existing);
2925
- ye(existing, "Directorio .opencode encontrado");
2926
- 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
+ }
2927
3054
  }
2928
- const projectRoot = findProjectRoot();
2929
- ye(`No se encontró .opencode
2930
- Raíz detectada: ${projectRoot}`, "Aviso");
2931
- const create = await me({
2932
- 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"
2933
3066
  });
2934
- onCancel(create);
2935
- if (!create)
2936
- return null;
2937
- const paths = createOpenCodeDir(projectRoot);
2938
- v2.success(`Estructura creada en ${paths.root}`);
2939
- 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() + "/");
2940
3086
  }
2941
3087
  function getOrphanedItems(manifest, paths) {
2942
3088
  const lockfile = readLockfile(paths.root);
@@ -3054,7 +3200,13 @@ async function doInstallStack(toolsDir, projectRoot) {
3054
3200
  async function doInstallAll(manifest, paths) {
3055
3201
  const spin = L2();
3056
3202
  let errors = 0;
3057
- 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
+ }
3058
3210
  for (const agent of manifest.agents) {
3059
3211
  spin.start(`Descargando agente: ${agent.name} (${agent.version})`);
3060
3212
  try {
@@ -3099,17 +3251,22 @@ async function doInstallAll(manifest, paths) {
3099
3251
  errors++;
3100
3252
  }
3101
3253
  }
3102
- v2.info("Instalando stack de herramientas...");
3103
- const stackOk = await doInstallStack(paths.tools, dirname6(paths.root));
3104
- if (!stackOk)
3105
- errors++;
3106
- const missingTools = [];
3107
- const codegraphDir = join8(paths.tools, "codegraph");
3108
- const engramDir = join8(paths.tools, "engram");
3109
- if (!existsSync7(codegraphDir) || !findBinaryInDir(codegraphDir, "codegraph"))
3110
- missingTools.push("CodeGraph");
3111
- if (!existsSync7(engramDir) || !findBinaryInDir(engramDir, "engram"))
3112
- 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
+ }
3113
3270
  if (missingTools.length > 0) {
3114
3271
  v2.warn(`Faltan herramientas del stack: ${missingTools.join(", ")}.
3115
3272
  ` + "Ejecutá `/install-stack` desde el agente para instalarlas manualmente.");
@@ -3624,10 +3781,10 @@ async function doUninstallMcpByName(name, paths) {
3624
3781
  }
3625
3782
 
3626
3783
  // src/prompts/index.ts
3627
- async function runInteractiveMenu() {
3784
+ async function runInteractiveMenu(scope) {
3628
3785
  we(" OpenCode Installer ");
3629
3786
  const manifest = await loadManifest();
3630
- const paths = await resolveOpenCodePaths();
3787
+ const paths = await resolveOpenCodePaths(scope ?? null);
3631
3788
  if (!paths) {
3632
3789
  fe("Instalación cancelada.");
3633
3790
  return;
@@ -3675,6 +3832,12 @@ async function runInteractiveMenu() {
3675
3832
  fe("Listo.");
3676
3833
  break;
3677
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
+ }
3678
3841
  ensureToolDirs(paths.tools, ["codegraph", "engram", "context7"]);
3679
3842
  const stackOk = await doInstallStack(paths.tools, dirname7(paths.root));
3680
3843
  if (!stackOk)
@@ -3697,10 +3860,10 @@ async function runInteractiveMenu() {
3697
3860
  break;
3698
3861
  }
3699
3862
  }
3700
- async function runInstallCommand() {
3863
+ async function runInstallCommand(scope) {
3701
3864
  we(" OpenCode Installer ");
3702
3865
  const manifest = await loadManifest();
3703
- const paths = await resolveOpenCodePaths();
3866
+ const paths = await resolveOpenCodePaths(scope ?? null);
3704
3867
  if (!paths) {
3705
3868
  fe("Cancelado.");
3706
3869
  return;
@@ -3710,10 +3873,10 @@ async function runInstallCommand() {
3710
3873
  printPostInstallSteps();
3711
3874
  fe(process.exitCode ? "Instalación parcial." : "Instalación completada.");
3712
3875
  }
3713
- async function runAddAgentCommand() {
3876
+ async function runAddAgentCommand(scope) {
3714
3877
  we(" OpenCode Installer ");
3715
3878
  const manifest = await loadManifest();
3716
- const paths = await resolveOpenCodePaths();
3879
+ const paths = await resolveOpenCodePaths(scope ?? null);
3717
3880
  if (!paths) {
3718
3881
  fe("Cancelado.");
3719
3882
  return;
@@ -3722,10 +3885,10 @@ async function runAddAgentCommand() {
3722
3885
  printPostInstallSteps();
3723
3886
  fe("Listo.");
3724
3887
  }
3725
- async function runAddCommandCommand() {
3888
+ async function runAddCommandCommand(scope) {
3726
3889
  we(" OpenCode Installer ");
3727
3890
  const manifest = await loadManifest();
3728
- const paths = await resolveOpenCodePaths();
3891
+ const paths = await resolveOpenCodePaths(scope ?? null);
3729
3892
  if (!paths) {
3730
3893
  fe("Cancelado.");
3731
3894
  return;
@@ -3734,10 +3897,10 @@ async function runAddCommandCommand() {
3734
3897
  printPostInstallSteps();
3735
3898
  fe("Listo.");
3736
3899
  }
3737
- async function runAddSkillCommand() {
3900
+ async function runAddSkillCommand(scope) {
3738
3901
  we(" OpenCode Installer ");
3739
3902
  const manifest = await loadManifest();
3740
- const paths = await resolveOpenCodePaths();
3903
+ const paths = await resolveOpenCodePaths(scope ?? null);
3741
3904
  if (!paths) {
3742
3905
  fe("Cancelado.");
3743
3906
  return;
@@ -3746,10 +3909,10 @@ async function runAddSkillCommand() {
3746
3909
  printPostInstallSteps();
3747
3910
  fe("Listo.");
3748
3911
  }
3749
- async function runAddMcpCommand() {
3912
+ async function runAddMcpCommand(scope) {
3750
3913
  we(" OpenCode Installer ");
3751
3914
  const manifest = await loadManifest();
3752
- const paths = await resolveOpenCodePaths();
3915
+ const paths = await resolveOpenCodePaths(scope ?? null);
3753
3916
  if (!paths) {
3754
3917
  fe("Cancelado.");
3755
3918
  return;
@@ -3758,9 +3921,15 @@ async function runAddMcpCommand() {
3758
3921
  printPostInstallSteps();
3759
3922
  fe("Listo.");
3760
3923
  }
3761
- 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
+ }
3762
3931
  we(" OpenCode Installer — Stack ");
3763
- const paths = await resolveOpenCodePaths();
3932
+ const paths = await resolveOpenCodePaths(scope ?? null);
3764
3933
  if (!paths) {
3765
3934
  fe("Cancelado.");
3766
3935
  return;
@@ -3771,9 +3940,9 @@ async function runInstallStackCommand() {
3771
3940
  process.exitCode = 1;
3772
3941
  fe(stackOk ? "Stack instalado." : "Stack instalado parcialmente.");
3773
3942
  }
3774
- async function runUninstallStackCommand() {
3943
+ async function runUninstallStackCommand(scope) {
3775
3944
  we(" OpenCode Installer — Stack ");
3776
- const paths = await resolveOpenCodePaths();
3945
+ const paths = await resolveOpenCodePaths(scope ?? null);
3777
3946
  if (!paths) {
3778
3947
  fe("Cancelado.");
3779
3948
  return;
@@ -3795,10 +3964,10 @@ async function runUninstallStackCommand() {
3795
3964
  }
3796
3965
  fe("Stack desinstalado.");
3797
3966
  }
3798
- async function runUpdateCommand() {
3967
+ async function runUpdateCommand(scope) {
3799
3968
  we(" OpenCode Installer ");
3800
3969
  const manifest = await loadLatestManifest();
3801
- const paths = await resolveOpenCodePaths();
3970
+ const paths = await resolveOpenCodePaths(scope ?? null);
3802
3971
  if (!paths) {
3803
3972
  fe("Cancelado.");
3804
3973
  return;
@@ -3806,9 +3975,9 @@ async function runUpdateCommand() {
3806
3975
  await doUpdate(manifest, paths);
3807
3976
  fe("Actualización completada.");
3808
3977
  }
3809
- async function runUninstallCommand() {
3978
+ async function runUninstallCommand(scope) {
3810
3979
  we(" OpenCode Installer ");
3811
- const paths = await resolveOpenCodePaths();
3980
+ const paths = await resolveOpenCodePaths(scope ?? null);
3812
3981
  if (!paths) {
3813
3982
  fe("Cancelado.");
3814
3983
  return;
@@ -3816,9 +3985,9 @@ async function runUninstallCommand() {
3816
3985
  await doUninstall(paths);
3817
3986
  fe("Desinstalación completada.");
3818
3987
  }
3819
- async function runUninstallAgentCommand(name) {
3988
+ async function runUninstallAgentCommand(name, scope) {
3820
3989
  we(" OpenCode Installer ");
3821
- const paths = await resolveOpenCodePaths();
3990
+ const paths = await resolveOpenCodePaths(scope ?? null);
3822
3991
  if (!paths) {
3823
3992
  fe("Cancelado.");
3824
3993
  return;
@@ -3851,9 +4020,9 @@ async function runUninstallAgentCommand(name) {
3851
4020
  }
3852
4021
  fe("Listo.");
3853
4022
  }
3854
- async function runUninstallCommandCommand(name) {
4023
+ async function runUninstallCommandCommand(name, scope) {
3855
4024
  we(" OpenCode Installer ");
3856
- const paths = await resolveOpenCodePaths();
4025
+ const paths = await resolveOpenCodePaths(scope ?? null);
3857
4026
  if (!paths) {
3858
4027
  fe("Cancelado.");
3859
4028
  return;
@@ -3886,9 +4055,9 @@ async function runUninstallCommandCommand(name) {
3886
4055
  }
3887
4056
  fe("Listo.");
3888
4057
  }
3889
- async function runUninstallSkillCommand(name) {
4058
+ async function runUninstallSkillCommand(name, scope) {
3890
4059
  we(" OpenCode Installer ");
3891
- const paths = await resolveOpenCodePaths();
4060
+ const paths = await resolveOpenCodePaths(scope ?? null);
3892
4061
  if (!paths) {
3893
4062
  fe("Cancelado.");
3894
4063
  return;
@@ -3921,9 +4090,9 @@ async function runUninstallSkillCommand(name) {
3921
4090
  }
3922
4091
  fe("Listo.");
3923
4092
  }
3924
- async function runUninstallMcpCommand(name) {
4093
+ async function runUninstallMcpCommand(name, scope) {
3925
4094
  we(" OpenCode Installer ");
3926
- const paths = await resolveOpenCodePaths();
4095
+ const paths = await resolveOpenCodePaths(scope ?? null);
3927
4096
  if (!paths) {
3928
4097
  fe("Cancelado.");
3929
4098
  return;
@@ -3962,67 +4131,110 @@ var HELP = `
3962
4131
  ostacky — Instalador de agentes, comandos, skills y MCPs para OpenCode
3963
4132
 
3964
4133
  Uso:
3965
- npx ostacky Menú interactivo (instalación completa)
3966
- npx ostacky install Instalar TODO (agente + skills + MCPs + CodeGraph + OpenSpec + Engram + Context7)
3967
- npx ostacky add agent Agregar agente(s)
3968
- npx ostacky add command Agregar command(s)
3969
- npx ostacky add skill Agregar skill(s)
3970
- npx ostacky add mcp Agregar MCP server(s)
3971
- npx ostacky install-stack Instalar solo el stack de herramientas (CodeGraph, OpenSpec, Engram, Context7)
3972
- npx ostacky uninstall-stack Remover la configuración del stack del proyecto
3973
- npx ostacky update Actualizar instalación
3974
- npx ostacky uninstall Desinstalar todo
3975
- npx ostacky uninstall agent Desinstalar agente(s)
3976
- npx ostacky uninstall command Desinstalar command(s)
3977
- npx ostacky uninstall skill Desinstalar skill(s)
3978
- 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)
3979
4148
  npx ostacky --help Mostrar esta ayuda
3980
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
3981
4156
  `.trim();
3982
- 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;
3983
4190
  async function main() {
3984
4191
  switch (cmd) {
3985
4192
  case "install":
3986
- await runInstallCommand();
4193
+ await runInstallCommand(scope);
3987
4194
  break;
3988
4195
  case "install-stack":
3989
- 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);
3990
4202
  break;
3991
4203
  case "uninstall-stack":
3992
- await runUninstallStackCommand();
4204
+ await runUninstallStackCommand(scope);
3993
4205
  break;
3994
4206
  case "add":
3995
4207
  if (subcmd === "agent") {
3996
- await runAddAgentCommand();
4208
+ await runAddAgentCommand(scope);
3997
4209
  } else if (subcmd === "command") {
3998
- await runAddCommandCommand();
4210
+ await runAddCommandCommand(scope);
3999
4211
  } else if (subcmd === "skill") {
4000
- await runAddSkillCommand();
4212
+ await runAddSkillCommand(scope);
4001
4213
  } else if (subcmd === "mcp") {
4002
- await runAddMcpCommand();
4214
+ await runAddMcpCommand(scope);
4003
4215
  } else {
4004
4216
  console.error(`Tipo desconocido: "${subcmd}". Usa 'agent', 'command', 'skill' o 'mcp'.`);
4005
4217
  process.exit(1);
4006
4218
  }
4007
4219
  break;
4008
4220
  case "update":
4009
- await runUpdateCommand();
4221
+ await runUpdateCommand(scope);
4010
4222
  break;
4011
4223
  case "uninstall":
4012
4224
  if (subcmd === "agent") {
4013
- const name = process.argv[4];
4014
- await runUninstallAgentCommand(name);
4225
+ const name = argvNoScope[4];
4226
+ await runUninstallAgentCommand(name, scope);
4015
4227
  } else if (subcmd === "command") {
4016
- const name = process.argv[4];
4017
- await runUninstallCommandCommand(name);
4228
+ const name = argvNoScope[4];
4229
+ await runUninstallCommandCommand(name, scope);
4018
4230
  } else if (subcmd === "skill") {
4019
- const name = process.argv[4];
4020
- await runUninstallSkillCommand(name);
4231
+ const name = argvNoScope[4];
4232
+ await runUninstallSkillCommand(name, scope);
4021
4233
  } else if (subcmd === "mcp") {
4022
- const name = process.argv[4];
4023
- await runUninstallMcpCommand(name);
4234
+ const name = argvNoScope[4];
4235
+ await runUninstallMcpCommand(name, scope);
4024
4236
  } else if (subcmd === undefined) {
4025
- await runUninstallCommand();
4237
+ await runUninstallCommand(scope);
4026
4238
  } else {
4027
4239
  console.error(`Subcomando desconocido: "${subcmd}". Usa 'agent', 'command', 'skill', 'mcp' o nada.`);
4028
4240
  process.exit(1);
@@ -4041,7 +4253,7 @@ async function main() {
4041
4253
  console.error(`Comando desconocido: "${cmd}". Usá --help para ver los comandos disponibles.`);
4042
4254
  process.exit(1);
4043
4255
  }
4044
- await runInteractiveMenu();
4256
+ await runInteractiveMenu(scope);
4045
4257
  }
4046
4258
  }
4047
4259
  main().catch((e2) => {