ostacky 0.7.1 → 0.7.3
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/README.md +18 -11
- package/assets/agents/ostacky.md +44 -14
- package/assets/commands/install-stack.md +20 -1
- package/assets/mcp/ostacky-controller/index.js +1294 -147
- package/assets/mcp/ostacky-controller/package.json +1 -1
- package/assets/plugins/engram.ts +118 -8
- package/assets/plugins/ostacky-guard.ts +131 -0
- package/assets/skills/execution-mode-evaluation/SKILL.md +9 -1
- package/assets/skills/graceful-degradation/SKILL.md +13 -0
- package/assets/skills/using-git-worktrees/SKILL.md +8 -0
- package/dist/cli.js +479 -138
- package/manifest.json +31 -31
- package/package.json +1 -1
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) {
|
|
@@ -232,6 +235,11 @@ function findOpenCodeDir(startDir = process.cwd()) {
|
|
|
232
235
|
}
|
|
233
236
|
}
|
|
234
237
|
function findProjectRoot(startDir = process.cwd()) {
|
|
238
|
+
try {
|
|
239
|
+
const out = execFileSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8", cwd: startDir, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
240
|
+
if (out && existsSync(out))
|
|
241
|
+
return resolve(out);
|
|
242
|
+
} catch {}
|
|
235
243
|
let current = resolve(startDir);
|
|
236
244
|
while (true) {
|
|
237
245
|
if (existsSync(join(current, ".opencode")) || existsSync(join(current, ".git"))) {
|
|
@@ -243,6 +251,32 @@ function findProjectRoot(startDir = process.cwd()) {
|
|
|
243
251
|
current = parent;
|
|
244
252
|
}
|
|
245
253
|
}
|
|
254
|
+
function getGlobalOpenCodeDir(platform = process.platform, home = homedir()) {
|
|
255
|
+
if (platform === "win32") {
|
|
256
|
+
const appData = process.env.APPDATA ?? win32.join(home, "AppData", "Roaming");
|
|
257
|
+
return win32.join(appData, "opencode");
|
|
258
|
+
}
|
|
259
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? join(home, ".config");
|
|
260
|
+
return join(xdg, "opencode");
|
|
261
|
+
}
|
|
262
|
+
function getOpenCodeDirForScope(scope, cwd = process.cwd()) {
|
|
263
|
+
if (scope === "global")
|
|
264
|
+
return getGlobalOpenCodeDir();
|
|
265
|
+
if (scope === "local") {
|
|
266
|
+
const existing2 = findOpenCodeDir(cwd);
|
|
267
|
+
if (existing2)
|
|
268
|
+
return existing2;
|
|
269
|
+
return join(findProjectRoot(cwd), ".opencode");
|
|
270
|
+
}
|
|
271
|
+
const existing = findOpenCodeDir(cwd);
|
|
272
|
+
if (existing)
|
|
273
|
+
return existing;
|
|
274
|
+
const root = findProjectRoot(cwd);
|
|
275
|
+
if (existsSync(join(root, ".opencode")) || existsSync(join(root, ".git"))) {
|
|
276
|
+
return join(root, ".opencode");
|
|
277
|
+
}
|
|
278
|
+
return getGlobalOpenCodeDir();
|
|
279
|
+
}
|
|
246
280
|
function ensureOpenCodePaths(opencodeDir) {
|
|
247
281
|
const paths = {
|
|
248
282
|
root: opencodeDir,
|
|
@@ -266,9 +300,6 @@ function ensureToolDirs(toolsDir, toolNames) {
|
|
|
266
300
|
mkdirSync(dir, { recursive: true });
|
|
267
301
|
}
|
|
268
302
|
}
|
|
269
|
-
function createOpenCodeDir(baseDir) {
|
|
270
|
-
return ensureOpenCodePaths(join(baseDir, ".opencode"));
|
|
271
|
-
}
|
|
272
303
|
function copyDirRecursive(src, dest, skipGenerated = false) {
|
|
273
304
|
if (!existsSync(dest))
|
|
274
305
|
mkdirSync(dest, { recursive: true });
|
|
@@ -451,7 +482,7 @@ function downloadToFile(url, dest, timeoutMs = 180000) {
|
|
|
451
482
|
});
|
|
452
483
|
}
|
|
453
484
|
async function downloadAndExtract(url, destDir, stripComponents = 1, timeoutMs = 180000) {
|
|
454
|
-
const tmp = join(dirname(destDir), `.${basename(destDir)}.download-${Date.now()}`);
|
|
485
|
+
const tmp = join(dirname(destDir), `.${basename(destDir)}.download-${Date.now()}-${process.pid}`);
|
|
455
486
|
if (!existsSync(tmp))
|
|
456
487
|
mkdirSync(tmp, { recursive: true });
|
|
457
488
|
const archivePath = join(tmp, url.endsWith(".zip") ? "archive.zip" : "archive.tar.gz");
|
|
@@ -520,31 +551,31 @@ var init_fs = __esm(() => {
|
|
|
520
551
|
var manifest_default;
|
|
521
552
|
var init_manifest = __esm(() => {
|
|
522
553
|
manifest_default = {
|
|
523
|
-
version: "0.7.
|
|
554
|
+
version: "0.7.3",
|
|
524
555
|
repo: "JaimeHoracio/Ostacky",
|
|
525
|
-
tag: "v0.7.
|
|
556
|
+
tag: "v0.7.3",
|
|
526
557
|
agents: [
|
|
527
558
|
{
|
|
528
559
|
name: "ostacky",
|
|
529
560
|
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.
|
|
531
|
-
version: "0.7.
|
|
532
|
-
sha256: "
|
|
561
|
+
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.3: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
|
|
562
|
+
version: "0.7.3",
|
|
563
|
+
sha256: "b6dc55a9571f9626e7912ac493cc3bfe6b0798a597f706904b2106a4a9acfcd5"
|
|
533
564
|
}
|
|
534
565
|
],
|
|
535
566
|
commands: [
|
|
536
567
|
{
|
|
537
568
|
name: "install-stack",
|
|
538
569
|
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.
|
|
540
|
-
version: "0.7.
|
|
541
|
-
sha256: "
|
|
570
|
+
description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, Context7, controller MCP con SDK oficial). v0.7.3: Paso 1.5 controller expandido con verificación post-instalación y troubleshooting.",
|
|
571
|
+
version: "0.7.3",
|
|
572
|
+
sha256: "0d4bc0938110c40633b041847148d8212e34a5b5876752825cfd6e186c328b8b"
|
|
542
573
|
},
|
|
543
574
|
{
|
|
544
575
|
name: "opsx-sync",
|
|
545
576
|
file: "assets/commands/opsx-sync.md",
|
|
546
577
|
description: "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
|
|
547
|
-
version: "0.7.
|
|
578
|
+
version: "0.7.3",
|
|
548
579
|
sha256: "fe0158478f2ca63b315037a85fc1632b77868532e319af6c7384285441767d64"
|
|
549
580
|
}
|
|
550
581
|
],
|
|
@@ -552,15 +583,15 @@ var init_manifest = __esm(() => {
|
|
|
552
583
|
{
|
|
553
584
|
name: "ostacky-controller",
|
|
554
585
|
file: "assets/mcp/ostacky-controller/",
|
|
555
|
-
description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.7.
|
|
556
|
-
version: "0.7.
|
|
557
|
-
sha256: "
|
|
586
|
+
description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.7.3: 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.",
|
|
587
|
+
version: "0.7.3",
|
|
588
|
+
sha256: "d723afc107c8a36721e7f04dc1c16694a617cd562492e1ece362482c9023b945"
|
|
558
589
|
},
|
|
559
590
|
{
|
|
560
591
|
name: "openspec",
|
|
561
592
|
file: "assets/mcp/openspec/",
|
|
562
593
|
description: "MCP server local para OpenSpec - proposal, apply, archive, sync de cambios",
|
|
563
|
-
version: "0.7.
|
|
594
|
+
version: "0.7.3",
|
|
564
595
|
sha256: "fa4be28bfc1e75db8f1a037e2580f04a60066c80e8e5934e90e1041020d04609"
|
|
565
596
|
}
|
|
566
597
|
],
|
|
@@ -569,106 +600,106 @@ var init_manifest = __esm(() => {
|
|
|
569
600
|
name: "brainstorming",
|
|
570
601
|
file: "assets/skills/brainstorming/SKILL.md",
|
|
571
602
|
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.
|
|
603
|
+
version: "0.7.3",
|
|
573
604
|
sha256: "333810ca63450e8ab795e8c5a58e645101e788d79210ca8ac36e10d11be39585"
|
|
574
605
|
},
|
|
575
606
|
{
|
|
576
607
|
name: "execution-mode-evaluation",
|
|
577
608
|
file: "assets/skills/execution-mode-evaluation/SKILL.md",
|
|
578
609
|
description: "Skill de análisis de modo de ejecución — output reconciliado con controller snapshot contract (recommendation field)",
|
|
579
|
-
version: "0.7.
|
|
580
|
-
sha256: "
|
|
610
|
+
version: "0.7.3",
|
|
611
|
+
sha256: "e17f1e0309572841dc1fa537b1f32c7468eb70b3acf453b9ff5cc4fa8d94fd39"
|
|
581
612
|
},
|
|
582
613
|
{
|
|
583
614
|
name: "tdd",
|
|
584
615
|
file: "assets/skills/tdd/SKILL.md",
|
|
585
616
|
description: "Skill de test-driven development (Superpowers)",
|
|
586
|
-
version: "0.7.
|
|
617
|
+
version: "0.7.3",
|
|
587
618
|
sha256: "aa412298980b7826165c211145c1b8e9135f68f36247bb3cb96d0a0eae274486"
|
|
588
619
|
},
|
|
589
620
|
{
|
|
590
621
|
name: "subagent-driven-development",
|
|
591
622
|
file: "assets/skills/subagent-driven-development/SKILL.md",
|
|
592
623
|
description: "Skill de ejecución con subagentes (Superpowers) — ejecuta solo después de confirmación del coordinador Ostacky",
|
|
593
|
-
version: "0.7.
|
|
624
|
+
version: "0.7.3",
|
|
594
625
|
sha256: "1a42d714a9a13faf05f0bf7b580e8d837c2e77a5633839542ccce603023419e8"
|
|
595
626
|
},
|
|
596
627
|
{
|
|
597
628
|
name: "dispatching-parallel-agents",
|
|
598
629
|
file: "assets/skills/dispatching-parallel-agents/SKILL.md",
|
|
599
630
|
description: "Skill de dispatch paralelo de agentes (Superpowers)",
|
|
600
|
-
version: "0.7.
|
|
631
|
+
version: "0.7.3",
|
|
601
632
|
sha256: "3c8a66d51ae2e719e877d02c3dac32bd804722c3bb9227c258ecae78252a20ab"
|
|
602
633
|
},
|
|
603
634
|
{
|
|
604
635
|
name: "review",
|
|
605
636
|
file: "assets/skills/review/SKILL.md",
|
|
606
637
|
description: "Skill de revisión de código (Superpowers)",
|
|
607
|
-
version: "0.7.
|
|
638
|
+
version: "0.7.3",
|
|
608
639
|
sha256: "b19650ed4d1d4d9857a4dd5b7e08e91328a1a1fc9c45bcc2fb600fa9d0213279"
|
|
609
640
|
},
|
|
610
641
|
{
|
|
611
642
|
name: "receiving-code-review",
|
|
612
643
|
file: "assets/skills/receiving-code-review/SKILL.md",
|
|
613
644
|
description: "Skill de recibir y procesar feedback de code review",
|
|
614
|
-
version: "0.7.
|
|
645
|
+
version: "0.7.3",
|
|
615
646
|
sha256: "d761e884e71d8d3476ac734d287cae403a4024506a7d12e7361a448217c0a831"
|
|
616
647
|
},
|
|
617
648
|
{
|
|
618
649
|
name: "openspec-propose",
|
|
619
650
|
file: "assets/skills/openspec-propose/SKILL.md",
|
|
620
651
|
description: "Skill de generación de proposal (OpenSpec)",
|
|
621
|
-
version: "0.7.
|
|
652
|
+
version: "0.7.3",
|
|
622
653
|
sha256: "bb59100b9fd3c9f9a1ec6509fa975ec55bf7fc737df4fe73bb3e9bbc217b83f7"
|
|
623
654
|
},
|
|
624
655
|
{
|
|
625
656
|
name: "openspec-apply-change",
|
|
626
657
|
file: "assets/skills/openspec-apply-change/SKILL.md",
|
|
627
658
|
description: "Skill de aplicación de change (OpenSpec)",
|
|
628
|
-
version: "0.7.
|
|
659
|
+
version: "0.7.3",
|
|
629
660
|
sha256: "dfc823bf89fc7505e91ab6dee9c1f004be38a410bd3d88fb67b211b1b6cbb1d0"
|
|
630
661
|
},
|
|
631
662
|
{
|
|
632
663
|
name: "openspec-archive-change",
|
|
633
664
|
file: "assets/skills/openspec-archive-change/SKILL.md",
|
|
634
665
|
description: "Skill de archivo de change (OpenSpec)",
|
|
635
|
-
version: "0.7.
|
|
666
|
+
version: "0.7.3",
|
|
636
667
|
sha256: "16e4b561de7747283663fed602e506abf502452c49a8d4b86d7a5539c40f0195"
|
|
637
668
|
},
|
|
638
669
|
{
|
|
639
670
|
name: "openspec-explore",
|
|
640
671
|
file: "assets/skills/openspec-explore/SKILL.md",
|
|
641
672
|
description: "Modo explore para OpenSpec — thinking partner para explorar ideas, investigar problemas y clarificar requisitos antes/durante un cambio",
|
|
642
|
-
version: "0.7.
|
|
673
|
+
version: "0.7.3",
|
|
643
674
|
sha256: "37ae4aaf17ea71a395bab6dc4d9d61b9a49d7910f6692535ae8553244c46880b"
|
|
644
675
|
},
|
|
645
676
|
{
|
|
646
677
|
name: "using-git-worktrees",
|
|
647
678
|
file: "assets/skills/using-git-worktrees/SKILL.md",
|
|
648
679
|
description: "Skill de uso de git worktrees para aislamiento de trabajo",
|
|
649
|
-
version: "0.7.
|
|
650
|
-
sha256: "
|
|
680
|
+
version: "0.7.3",
|
|
681
|
+
sha256: "93341bc1b7c053618a8b6dc07e3615b77990d7b549a0201f5835d95fef67ce13"
|
|
651
682
|
},
|
|
652
683
|
{
|
|
653
684
|
name: "using-superpowers",
|
|
654
685
|
file: "assets/skills/using-superpowers/SKILL.md",
|
|
655
686
|
description: "Skill de orquestación de Superpowers skills",
|
|
656
|
-
version: "0.7.
|
|
687
|
+
version: "0.7.3",
|
|
657
688
|
sha256: "7e54536f96d2a379185a10bfc1e970850caa2561b6d8aca7080f0defea0381a4"
|
|
658
689
|
},
|
|
659
690
|
{
|
|
660
691
|
name: "writing-skills",
|
|
661
692
|
file: "assets/skills/writing-skills/SKILL.md",
|
|
662
693
|
description: "Skill de creación y edición de skills",
|
|
663
|
-
version: "0.7.
|
|
694
|
+
version: "0.7.3",
|
|
664
695
|
sha256: "3d76b906cee518a2b809febb70db95697a35b9f368986bb504b4120c3bfb437a"
|
|
665
696
|
},
|
|
666
697
|
{
|
|
667
698
|
name: "graceful-degradation",
|
|
668
699
|
file: "assets/skills/graceful-degradation/SKILL.md",
|
|
669
700
|
description: "Skill de degradación graceful cuando múltiples tools están indisponibles",
|
|
670
|
-
version: "0.7.
|
|
671
|
-
sha256: "
|
|
701
|
+
version: "0.7.3",
|
|
702
|
+
sha256: "630b87c16d78212c865696ca5df6abb0834606095ffe0e80cff8e04350f01054"
|
|
672
703
|
}
|
|
673
704
|
]
|
|
674
705
|
};
|
|
@@ -1468,7 +1499,7 @@ var init_stack = __esm(() => {
|
|
|
1468
1499
|
// package.json
|
|
1469
1500
|
var package_default = {
|
|
1470
1501
|
name: "ostacky",
|
|
1471
|
-
version: "0.7.
|
|
1502
|
+
version: "0.7.3",
|
|
1472
1503
|
description: "Instalador interactivo de agentes y comandos para OpenCode",
|
|
1473
1504
|
type: "module",
|
|
1474
1505
|
bin: {
|
|
@@ -2546,6 +2577,7 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
|
|
|
2546
2577
|
let stderr = "";
|
|
2547
2578
|
let stdoutBuffer = "";
|
|
2548
2579
|
const requestTimeout = 1e4;
|
|
2580
|
+
let exitTimeout;
|
|
2549
2581
|
const finish = (error) => {
|
|
2550
2582
|
if (settled)
|
|
2551
2583
|
return;
|
|
@@ -2554,9 +2586,18 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
|
|
|
2554
2586
|
try {
|
|
2555
2587
|
child.stdin?.end();
|
|
2556
2588
|
} catch {}
|
|
2557
|
-
if (child.exitCode === null)
|
|
2589
|
+
if (child.exitCode === null) {
|
|
2590
|
+
const onExit = () => {
|
|
2591
|
+
if (exitTimeout)
|
|
2592
|
+
clearTimeout(exitTimeout);
|
|
2593
|
+
error ? reject(error) : resolve2();
|
|
2594
|
+
};
|
|
2595
|
+
child.once("exit", onExit);
|
|
2558
2596
|
child.kill();
|
|
2559
|
-
|
|
2597
|
+
exitTimeout = setTimeout(onExit, 500);
|
|
2598
|
+
} else {
|
|
2599
|
+
error ? reject(error) : resolve2();
|
|
2600
|
+
}
|
|
2560
2601
|
};
|
|
2561
2602
|
const fail = (message) => finish(new Error(`${message}${stderr ? `: ${stderr.trim()}` : ""}`));
|
|
2562
2603
|
const send = (message) => {
|
|
@@ -2633,15 +2674,35 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
|
|
|
2633
2674
|
params: {
|
|
2634
2675
|
protocolVersion: "2025-03-26",
|
|
2635
2676
|
capabilities: {},
|
|
2636
|
-
clientInfo: { name: "ostacky-installer", version: "0.7.
|
|
2677
|
+
clientInfo: { name: "ostacky-installer", version: "0.7.3" }
|
|
2637
2678
|
}
|
|
2638
2679
|
});
|
|
2639
2680
|
});
|
|
2640
2681
|
} finally {
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2682
|
+
await new Promise((r2) => setTimeout(r2, 200));
|
|
2683
|
+
if (statePath) {
|
|
2684
|
+
for (const p2 of [
|
|
2685
|
+
statePath,
|
|
2686
|
+
statePath + ".backup",
|
|
2687
|
+
statePath + ".lock.pid",
|
|
2688
|
+
statePath + ".lock.timestamp",
|
|
2689
|
+
statePath + ".tmp." + process.pid
|
|
2690
|
+
]) {
|
|
2691
|
+
try {
|
|
2692
|
+
if (existsSync5(p2))
|
|
2693
|
+
rmSync3(p2, { force: true });
|
|
2694
|
+
} catch {}
|
|
2695
|
+
}
|
|
2696
|
+
try {
|
|
2697
|
+
const { dirname: dirname5, join: join7 } = await import("path");
|
|
2698
|
+
const handoff = join7(dirname5(statePath), ".ostacky-handoff-compaction.json");
|
|
2699
|
+
if (existsSync5(handoff)) {
|
|
2700
|
+
try {
|
|
2701
|
+
rmSync3(handoff, { force: true });
|
|
2702
|
+
} catch {}
|
|
2703
|
+
}
|
|
2704
|
+
} catch {}
|
|
2705
|
+
}
|
|
2645
2706
|
}
|
|
2646
2707
|
}
|
|
2647
2708
|
function takeFileSnapshot(path) {
|
|
@@ -2723,8 +2784,8 @@ async function installSkill(item, manifest, paths) {
|
|
|
2723
2784
|
const treeHash = computeTreeHash(src);
|
|
2724
2785
|
if (item.sha256 && treeHash !== item.sha256) {
|
|
2725
2786
|
throw new Error(`Tree hash inválido para skill "${item.name}"
|
|
2726
|
-
|
|
2727
|
-
|
|
2787
|
+
esperado: ${item.sha256}
|
|
2788
|
+
recibido: ${treeHash}`);
|
|
2728
2789
|
}
|
|
2729
2790
|
const dest = join6(paths.skills, item.name);
|
|
2730
2791
|
if (existsSync5(dest)) {
|
|
@@ -2764,8 +2825,8 @@ async function installMcpServer(item, manifest, paths) {
|
|
|
2764
2825
|
const treeHash = computeTreeHash(src);
|
|
2765
2826
|
if (item.sha256 && treeHash !== item.sha256) {
|
|
2766
2827
|
throw new Error(`Tree hash inválido para MCP server "${item.name}"
|
|
2767
|
-
|
|
2768
|
-
|
|
2828
|
+
esperado: ${item.sha256}
|
|
2829
|
+
recibido: ${treeHash}`);
|
|
2769
2830
|
}
|
|
2770
2831
|
const nodeExecutable = getVerifiedNodeExecutable();
|
|
2771
2832
|
const projectRoot = dirname4(paths.root);
|
|
@@ -2969,25 +3030,66 @@ function printPostInstallSteps() {
|
|
|
2969
3030
|
].join(`
|
|
2970
3031
|
`), "Próximos pasos");
|
|
2971
3032
|
}
|
|
2972
|
-
async function resolveOpenCodePaths() {
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
const
|
|
2976
|
-
|
|
2977
|
-
|
|
3033
|
+
async function resolveOpenCodePaths(scope) {
|
|
3034
|
+
if (scope === "local" || scope === "global" || scope === "auto") {
|
|
3035
|
+
const dir2 = getOpenCodeDirForScope(scope);
|
|
3036
|
+
const isGlobalDir = dir2.replace(/\\/g, "/") === getGlobalOpenCodeDir().replace(/\\/g, "/");
|
|
3037
|
+
try {
|
|
3038
|
+
const paths = ensureOpenCodePaths(dir2);
|
|
3039
|
+
if (scope === "global")
|
|
3040
|
+
ye(dir2, "Instalación global");
|
|
3041
|
+
else if (scope === "auto")
|
|
3042
|
+
ye(dir2, `Scope auto → ${isGlobalDir ? "global" : "local"}`);
|
|
3043
|
+
else
|
|
3044
|
+
ye(dir2, "Instalación local");
|
|
3045
|
+
return paths;
|
|
3046
|
+
} catch (e2) {
|
|
3047
|
+
const msg = e2.message ?? "";
|
|
3048
|
+
if ((scope === "global" || scope === "auto" && isGlobalDir) && (msg.includes("EACCES") || msg.toLowerCase().includes("permission"))) {
|
|
3049
|
+
v2.warn(`No se pudo escribir en global (${dir2}): ${msg}. ¿Instalar local?`);
|
|
3050
|
+
const retry = await me({ message: "¿Reintentar como instalación local?" });
|
|
3051
|
+
onCancel(retry);
|
|
3052
|
+
if (retry) {
|
|
3053
|
+
const localDir2 = getOpenCodeDirForScope("local");
|
|
3054
|
+
return ensureOpenCodePaths(localDir2);
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
throw e2;
|
|
3058
|
+
}
|
|
2978
3059
|
}
|
|
2979
|
-
const
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
const
|
|
2983
|
-
|
|
3060
|
+
const cwd = process.cwd();
|
|
3061
|
+
const localDir = getOpenCodeDirForScope("local", cwd);
|
|
3062
|
+
const globalDir = getGlobalOpenCodeDir();
|
|
3063
|
+
const hasLocal = !!findOpenCodeDir(cwd);
|
|
3064
|
+
const scopeChoice = await de({
|
|
3065
|
+
message: `¿Instalar en proyecto local (${localDir}) o global (${globalDir})?`,
|
|
3066
|
+
options: [
|
|
3067
|
+
{ value: "local", label: "Local", hint: `${localDir} (recomendado)` },
|
|
3068
|
+
{ value: "global", label: "Global", hint: globalDir }
|
|
3069
|
+
],
|
|
3070
|
+
initialValue: "local"
|
|
2984
3071
|
});
|
|
2985
|
-
onCancel(
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
3072
|
+
onCancel(scopeChoice);
|
|
3073
|
+
const chosen = scopeChoice;
|
|
3074
|
+
const dir = getOpenCodeDirForScope(chosen, cwd);
|
|
3075
|
+
try {
|
|
3076
|
+
return ensureOpenCodePaths(dir);
|
|
3077
|
+
} catch (e2) {
|
|
3078
|
+
const msg = e2.message ?? "";
|
|
3079
|
+
if (chosen === "global" && (msg.includes("EACCES") || msg.includes("permission"))) {
|
|
3080
|
+
v2.warn(`No se pudo escribir en global (${dir}): ${msg}.`);
|
|
3081
|
+
const retry = await me({ message: "¿Instalar local en su lugar?" });
|
|
3082
|
+
onCancel(retry);
|
|
3083
|
+
if (retry)
|
|
3084
|
+
return ensureOpenCodePaths(getOpenCodeDirForScope("local", cwd));
|
|
3085
|
+
}
|
|
3086
|
+
throw e2;
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
function isGlobalScope(paths) {
|
|
3090
|
+
const globalDir = getGlobalOpenCodeDir().replace(/\\/g, "/");
|
|
3091
|
+
const root = paths.root.replace(/\\/g, "/");
|
|
3092
|
+
return root === globalDir || root.startsWith(globalDir + "/");
|
|
2991
3093
|
}
|
|
2992
3094
|
function getOrphanedItems(manifest, paths) {
|
|
2993
3095
|
const lockfile = readLockfile(paths.root);
|
|
@@ -3105,7 +3207,13 @@ async function doInstallStack(toolsDir, projectRoot) {
|
|
|
3105
3207
|
async function doInstallAll(manifest, paths) {
|
|
3106
3208
|
const spin = L2();
|
|
3107
3209
|
let errors = 0;
|
|
3108
|
-
|
|
3210
|
+
const isGlobal = isGlobalScope(paths);
|
|
3211
|
+
if (!isGlobal) {
|
|
3212
|
+
ensureToolDirs(paths.tools, ["codegraph", "engram", "context7"]);
|
|
3213
|
+
} else {
|
|
3214
|
+
v2.info("Scope global detectado: el stack (CodeGraph/Engram) permanece siempre en <proyecto>/.opencode/tools — se omite instalación de stack global.");
|
|
3215
|
+
v2.info("Para instalar el stack, ejecutá 'npx ostacky install-stack --scope local' dentro de cada proyecto.");
|
|
3216
|
+
}
|
|
3109
3217
|
for (const agent of manifest.agents) {
|
|
3110
3218
|
spin.start(`Descargando agente: ${agent.name} (${agent.version})`);
|
|
3111
3219
|
try {
|
|
@@ -3150,17 +3258,22 @@ async function doInstallAll(manifest, paths) {
|
|
|
3150
3258
|
errors++;
|
|
3151
3259
|
}
|
|
3152
3260
|
}
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
if (
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3261
|
+
let stackOk = true;
|
|
3262
|
+
let missingTools = [];
|
|
3263
|
+
if (isGlobal) {
|
|
3264
|
+
stackOk = true;
|
|
3265
|
+
} else {
|
|
3266
|
+
v2.info("Instalando stack de herramientas...");
|
|
3267
|
+
stackOk = await doInstallStack(paths.tools, dirname6(paths.root));
|
|
3268
|
+
if (!stackOk)
|
|
3269
|
+
errors++;
|
|
3270
|
+
const codegraphDir = join8(paths.tools, "codegraph");
|
|
3271
|
+
const engramDir = join8(paths.tools, "engram");
|
|
3272
|
+
if (!existsSync7(codegraphDir) || !findBinaryInDir(codegraphDir, "codegraph"))
|
|
3273
|
+
missingTools.push("CodeGraph");
|
|
3274
|
+
if (!existsSync7(engramDir) || !findBinaryInDir(engramDir, "engram"))
|
|
3275
|
+
missingTools.push("Engram");
|
|
3276
|
+
}
|
|
3164
3277
|
if (missingTools.length > 0) {
|
|
3165
3278
|
v2.warn(`Faltan herramientas del stack: ${missingTools.join(", ")}.
|
|
3166
3279
|
` + "Ejecutá `/install-stack` desde el agente para instalarlas manualmente.");
|
|
@@ -3675,10 +3788,10 @@ async function doUninstallMcpByName(name, paths) {
|
|
|
3675
3788
|
}
|
|
3676
3789
|
|
|
3677
3790
|
// src/prompts/index.ts
|
|
3678
|
-
async function runInteractiveMenu() {
|
|
3791
|
+
async function runInteractiveMenu(scope) {
|
|
3679
3792
|
we(" OpenCode Installer ");
|
|
3680
3793
|
const manifest = await loadManifest();
|
|
3681
|
-
const paths = await resolveOpenCodePaths();
|
|
3794
|
+
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3682
3795
|
if (!paths) {
|
|
3683
3796
|
fe("Instalación cancelada.");
|
|
3684
3797
|
return;
|
|
@@ -3726,6 +3839,12 @@ async function runInteractiveMenu() {
|
|
|
3726
3839
|
fe("Listo.");
|
|
3727
3840
|
break;
|
|
3728
3841
|
case "stack": {
|
|
3842
|
+
if (isGlobalScope(paths)) {
|
|
3843
|
+
v2.error("install-stack requiere scope local; el stack vive en <proyecto>/.opencode/tools");
|
|
3844
|
+
v2.info(`Elegiste global (${paths.root}) — el stack debe instalarse por proyecto local.`);
|
|
3845
|
+
fe("Cancelado.");
|
|
3846
|
+
break;
|
|
3847
|
+
}
|
|
3729
3848
|
ensureToolDirs(paths.tools, ["codegraph", "engram", "context7"]);
|
|
3730
3849
|
const stackOk = await doInstallStack(paths.tools, dirname7(paths.root));
|
|
3731
3850
|
if (!stackOk)
|
|
@@ -3748,10 +3867,10 @@ async function runInteractiveMenu() {
|
|
|
3748
3867
|
break;
|
|
3749
3868
|
}
|
|
3750
3869
|
}
|
|
3751
|
-
async function runInstallCommand() {
|
|
3870
|
+
async function runInstallCommand(scope) {
|
|
3752
3871
|
we(" OpenCode Installer ");
|
|
3753
3872
|
const manifest = await loadManifest();
|
|
3754
|
-
const paths = await resolveOpenCodePaths();
|
|
3873
|
+
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3755
3874
|
if (!paths) {
|
|
3756
3875
|
fe("Cancelado.");
|
|
3757
3876
|
return;
|
|
@@ -3761,10 +3880,10 @@ async function runInstallCommand() {
|
|
|
3761
3880
|
printPostInstallSteps();
|
|
3762
3881
|
fe(process.exitCode ? "Instalación parcial." : "Instalación completada.");
|
|
3763
3882
|
}
|
|
3764
|
-
async function runAddAgentCommand() {
|
|
3883
|
+
async function runAddAgentCommand(scope) {
|
|
3765
3884
|
we(" OpenCode Installer ");
|
|
3766
3885
|
const manifest = await loadManifest();
|
|
3767
|
-
const paths = await resolveOpenCodePaths();
|
|
3886
|
+
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3768
3887
|
if (!paths) {
|
|
3769
3888
|
fe("Cancelado.");
|
|
3770
3889
|
return;
|
|
@@ -3773,10 +3892,10 @@ async function runAddAgentCommand() {
|
|
|
3773
3892
|
printPostInstallSteps();
|
|
3774
3893
|
fe("Listo.");
|
|
3775
3894
|
}
|
|
3776
|
-
async function runAddCommandCommand() {
|
|
3895
|
+
async function runAddCommandCommand(scope) {
|
|
3777
3896
|
we(" OpenCode Installer ");
|
|
3778
3897
|
const manifest = await loadManifest();
|
|
3779
|
-
const paths = await resolveOpenCodePaths();
|
|
3898
|
+
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3780
3899
|
if (!paths) {
|
|
3781
3900
|
fe("Cancelado.");
|
|
3782
3901
|
return;
|
|
@@ -3785,10 +3904,10 @@ async function runAddCommandCommand() {
|
|
|
3785
3904
|
printPostInstallSteps();
|
|
3786
3905
|
fe("Listo.");
|
|
3787
3906
|
}
|
|
3788
|
-
async function runAddSkillCommand() {
|
|
3907
|
+
async function runAddSkillCommand(scope) {
|
|
3789
3908
|
we(" OpenCode Installer ");
|
|
3790
3909
|
const manifest = await loadManifest();
|
|
3791
|
-
const paths = await resolveOpenCodePaths();
|
|
3910
|
+
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3792
3911
|
if (!paths) {
|
|
3793
3912
|
fe("Cancelado.");
|
|
3794
3913
|
return;
|
|
@@ -3797,10 +3916,10 @@ async function runAddSkillCommand() {
|
|
|
3797
3916
|
printPostInstallSteps();
|
|
3798
3917
|
fe("Listo.");
|
|
3799
3918
|
}
|
|
3800
|
-
async function runAddMcpCommand() {
|
|
3919
|
+
async function runAddMcpCommand(scope) {
|
|
3801
3920
|
we(" OpenCode Installer ");
|
|
3802
3921
|
const manifest = await loadManifest();
|
|
3803
|
-
const paths = await resolveOpenCodePaths();
|
|
3922
|
+
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3804
3923
|
if (!paths) {
|
|
3805
3924
|
fe("Cancelado.");
|
|
3806
3925
|
return;
|
|
@@ -3809,22 +3928,35 @@ async function runAddMcpCommand() {
|
|
|
3809
3928
|
printPostInstallSteps();
|
|
3810
3929
|
fe("Listo.");
|
|
3811
3930
|
}
|
|
3812
|
-
async function runInstallStackCommand() {
|
|
3931
|
+
async function runInstallStackCommand(scope) {
|
|
3932
|
+
if (scope === "global") {
|
|
3933
|
+
v2.error("install-stack requiere scope local; el stack vive en <proyecto>/.opencode/tools");
|
|
3934
|
+
fe("Usá: npx ostacky install-stack --scope local");
|
|
3935
|
+
process.exitCode = 1;
|
|
3936
|
+
return;
|
|
3937
|
+
}
|
|
3813
3938
|
we(" OpenCode Installer — Stack ");
|
|
3814
|
-
const paths = await resolveOpenCodePaths();
|
|
3939
|
+
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3815
3940
|
if (!paths) {
|
|
3816
3941
|
fe("Cancelado.");
|
|
3817
3942
|
return;
|
|
3818
3943
|
}
|
|
3944
|
+
if (isGlobalScope(paths)) {
|
|
3945
|
+
v2.error("install-stack requiere scope local; el stack vive en <proyecto>/.opencode/tools");
|
|
3946
|
+
v2.info(`Scope resuelto a global (${paths.root}) — el stack debe instalarse por proyecto local.`);
|
|
3947
|
+
fe("Cancelado.");
|
|
3948
|
+
process.exitCode = 1;
|
|
3949
|
+
return;
|
|
3950
|
+
}
|
|
3819
3951
|
ensureToolDirs(paths.tools, ["codegraph", "engram", "context7"]);
|
|
3820
3952
|
const stackOk = await doInstallStack(paths.tools, dirname7(paths.root));
|
|
3821
3953
|
if (!stackOk)
|
|
3822
3954
|
process.exitCode = 1;
|
|
3823
3955
|
fe(stackOk ? "Stack instalado." : "Stack instalado parcialmente.");
|
|
3824
3956
|
}
|
|
3825
|
-
async function runUninstallStackCommand() {
|
|
3957
|
+
async function runUninstallStackCommand(scope) {
|
|
3826
3958
|
we(" OpenCode Installer — Stack ");
|
|
3827
|
-
const paths = await resolveOpenCodePaths();
|
|
3959
|
+
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3828
3960
|
if (!paths) {
|
|
3829
3961
|
fe("Cancelado.");
|
|
3830
3962
|
return;
|
|
@@ -3846,10 +3978,10 @@ async function runUninstallStackCommand() {
|
|
|
3846
3978
|
}
|
|
3847
3979
|
fe("Stack desinstalado.");
|
|
3848
3980
|
}
|
|
3849
|
-
async function runUpdateCommand() {
|
|
3981
|
+
async function runUpdateCommand(scope) {
|
|
3850
3982
|
we(" OpenCode Installer ");
|
|
3851
3983
|
const manifest = await loadLatestManifest();
|
|
3852
|
-
const paths = await resolveOpenCodePaths();
|
|
3984
|
+
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3853
3985
|
if (!paths) {
|
|
3854
3986
|
fe("Cancelado.");
|
|
3855
3987
|
return;
|
|
@@ -3857,9 +3989,9 @@ async function runUpdateCommand() {
|
|
|
3857
3989
|
await doUpdate(manifest, paths);
|
|
3858
3990
|
fe("Actualización completada.");
|
|
3859
3991
|
}
|
|
3860
|
-
async function runUninstallCommand() {
|
|
3992
|
+
async function runUninstallCommand(scope) {
|
|
3861
3993
|
we(" OpenCode Installer ");
|
|
3862
|
-
const paths = await resolveOpenCodePaths();
|
|
3994
|
+
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3863
3995
|
if (!paths) {
|
|
3864
3996
|
fe("Cancelado.");
|
|
3865
3997
|
return;
|
|
@@ -3867,9 +3999,9 @@ async function runUninstallCommand() {
|
|
|
3867
3999
|
await doUninstall(paths);
|
|
3868
4000
|
fe("Desinstalación completada.");
|
|
3869
4001
|
}
|
|
3870
|
-
async function runUninstallAgentCommand(name) {
|
|
4002
|
+
async function runUninstallAgentCommand(name, scope) {
|
|
3871
4003
|
we(" OpenCode Installer ");
|
|
3872
|
-
const paths = await resolveOpenCodePaths();
|
|
4004
|
+
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3873
4005
|
if (!paths) {
|
|
3874
4006
|
fe("Cancelado.");
|
|
3875
4007
|
return;
|
|
@@ -3902,9 +4034,9 @@ async function runUninstallAgentCommand(name) {
|
|
|
3902
4034
|
}
|
|
3903
4035
|
fe("Listo.");
|
|
3904
4036
|
}
|
|
3905
|
-
async function runUninstallCommandCommand(name) {
|
|
4037
|
+
async function runUninstallCommandCommand(name, scope) {
|
|
3906
4038
|
we(" OpenCode Installer ");
|
|
3907
|
-
const paths = await resolveOpenCodePaths();
|
|
4039
|
+
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3908
4040
|
if (!paths) {
|
|
3909
4041
|
fe("Cancelado.");
|
|
3910
4042
|
return;
|
|
@@ -3937,9 +4069,9 @@ async function runUninstallCommandCommand(name) {
|
|
|
3937
4069
|
}
|
|
3938
4070
|
fe("Listo.");
|
|
3939
4071
|
}
|
|
3940
|
-
async function runUninstallSkillCommand(name) {
|
|
4072
|
+
async function runUninstallSkillCommand(name, scope) {
|
|
3941
4073
|
we(" OpenCode Installer ");
|
|
3942
|
-
const paths = await resolveOpenCodePaths();
|
|
4074
|
+
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3943
4075
|
if (!paths) {
|
|
3944
4076
|
fe("Cancelado.");
|
|
3945
4077
|
return;
|
|
@@ -3972,9 +4104,9 @@ async function runUninstallSkillCommand(name) {
|
|
|
3972
4104
|
}
|
|
3973
4105
|
fe("Listo.");
|
|
3974
4106
|
}
|
|
3975
|
-
async function runUninstallMcpCommand(name) {
|
|
4107
|
+
async function runUninstallMcpCommand(name, scope) {
|
|
3976
4108
|
we(" OpenCode Installer ");
|
|
3977
|
-
const paths = await resolveOpenCodePaths();
|
|
4109
|
+
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3978
4110
|
if (!paths) {
|
|
3979
4111
|
fe("Cancelado.");
|
|
3980
4112
|
return;
|
|
@@ -4009,76 +4141,285 @@ async function runUninstallMcpCommand(name) {
|
|
|
4009
4141
|
}
|
|
4010
4142
|
|
|
4011
4143
|
// src/cli.ts
|
|
4144
|
+
init_fs();
|
|
4145
|
+
import { existsSync as existsSync8, statSync as statSync3, readFileSync as readFileSync6 } from "node:fs";
|
|
4146
|
+
import { join as join11, dirname as dirname8 } from "node:path";
|
|
4012
4147
|
var HELP = `
|
|
4013
4148
|
ostacky — Instalador de agentes, comandos, skills y MCPs para OpenCode
|
|
4014
4149
|
|
|
4015
4150
|
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
|
|
4025
|
-
npx ostacky
|
|
4026
|
-
npx ostacky
|
|
4027
|
-
npx ostacky uninstall
|
|
4028
|
-
npx ostacky uninstall
|
|
4029
|
-
npx ostacky uninstall
|
|
4151
|
+
npx ostacky [--scope local|global|auto] Menú interactivo (instalación completa, pregunta local vs global, default local)
|
|
4152
|
+
npx ostacky install [--scope local|global|auto] Instalar TODO (agente + skills + MCPs + CodeGraph + OpenSpec + Engram + Context7)
|
|
4153
|
+
npx ostacky add agent [--scope local|global|auto] Agregar agente(s)
|
|
4154
|
+
npx ostacky add command [--scope ...] Agregar command(s)
|
|
4155
|
+
npx ostacky add skill [--scope ...] Agregar skill(s)
|
|
4156
|
+
npx ostacky add mcp [--scope ...] Agregar MCP server(s)
|
|
4157
|
+
npx ostacky install-stack [--scope local|auto] Instalar solo el stack de herramientas (CodeGraph, OpenSpec, Engram, Context7) — global bloquea con error
|
|
4158
|
+
npx ostacky uninstall-stack [--scope local|global|auto] Remover la configuración del stack del proyecto
|
|
4159
|
+
npx ostacky doctor Diagnostica locks, tools, state health
|
|
4160
|
+
npx ostacky status [--json] Muestra estado del controller sin MCP
|
|
4161
|
+
npx ostacky update [--scope ...] Actualizar instalación
|
|
4162
|
+
npx ostacky uninstall [--scope ...] Desinstalar todo
|
|
4163
|
+
npx ostacky uninstall agent [--scope ...] Desinstalar agente(s)
|
|
4164
|
+
npx ostacky uninstall command [--scope ...] Desinstalar command(s)
|
|
4165
|
+
npx ostacky uninstall skill [--scope ...] Desinstalar skill(s)
|
|
4166
|
+
npx ostacky uninstall mcp [--scope ...] Desinstalar MCP server(s)
|
|
4030
4167
|
npx ostacky --help Mostrar esta ayuda
|
|
4031
4168
|
npx ostacky --version Mostrar versión
|
|
4169
|
+
|
|
4170
|
+
Scope:
|
|
4171
|
+
--scope local Escribe en <proyecto>/.opencode (recomendado, default al preguntar)
|
|
4172
|
+
--scope global Escribe en ~/.config/opencode (o %APPDATA%\\opencode en Windows)
|
|
4173
|
+
--scope auto Elige local si existe .opencode o .git, si no global
|
|
4174
|
+
Sin flag Pregunta interactiva local (default) vs global
|
|
4032
4175
|
`.trim();
|
|
4033
|
-
|
|
4176
|
+
function parseScopeArg(argv = process.argv) {
|
|
4177
|
+
for (let i = 0;i < argv.length; i++) {
|
|
4178
|
+
const arg = argv[i];
|
|
4179
|
+
if (arg === "--scope" && i + 1 < argv.length) {
|
|
4180
|
+
const v3 = argv[i + 1];
|
|
4181
|
+
if (v3 === "local" || v3 === "global" || v3 === "auto")
|
|
4182
|
+
return v3;
|
|
4183
|
+
}
|
|
4184
|
+
if (arg.startsWith("--scope=")) {
|
|
4185
|
+
const v3 = arg.split("=")[1];
|
|
4186
|
+
if (v3 === "local" || v3 === "global" || v3 === "auto")
|
|
4187
|
+
return v3;
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
return null;
|
|
4191
|
+
}
|
|
4192
|
+
function withoutScopeArgs(argv) {
|
|
4193
|
+
const out = [];
|
|
4194
|
+
for (let i = 0;i < argv.length; i++) {
|
|
4195
|
+
const arg = argv[i];
|
|
4196
|
+
if (arg === "--scope" && i + 1 < argv.length) {
|
|
4197
|
+
i++;
|
|
4198
|
+
continue;
|
|
4199
|
+
}
|
|
4200
|
+
if (arg.startsWith("--scope="))
|
|
4201
|
+
continue;
|
|
4202
|
+
out.push(arg);
|
|
4203
|
+
}
|
|
4204
|
+
return out;
|
|
4205
|
+
}
|
|
4206
|
+
var scope = parseScopeArg();
|
|
4207
|
+
var argvNoScope = withoutScopeArgs(process.argv);
|
|
4208
|
+
var [, , cmd, subcmd] = argvNoScope;
|
|
4209
|
+
async function runDoctorCommand() {
|
|
4210
|
+
const cwd = process.cwd();
|
|
4211
|
+
const opencodeDir = findOpenCodeDir(cwd) || join11(cwd, ".opencode");
|
|
4212
|
+
const statePath = join11(opencodeDir, "ostacky-state.json");
|
|
4213
|
+
let hasError = false;
|
|
4214
|
+
let hasWarn = false;
|
|
4215
|
+
const check = (label, ok, warn = false) => {
|
|
4216
|
+
if (ok)
|
|
4217
|
+
console.log(`✅ ${label}: OK`);
|
|
4218
|
+
else if (warn) {
|
|
4219
|
+
console.log(`⚠️ ${label}`);
|
|
4220
|
+
hasWarn = true;
|
|
4221
|
+
} else {
|
|
4222
|
+
console.log(`❌ ${label}`);
|
|
4223
|
+
hasError = true;
|
|
4224
|
+
}
|
|
4225
|
+
};
|
|
4226
|
+
try {
|
|
4227
|
+
if (!existsSync8(statePath)) {
|
|
4228
|
+
check("controller: state file missing", false, true);
|
|
4229
|
+
} else {
|
|
4230
|
+
const stat = statSync3(statePath);
|
|
4231
|
+
const raw = readFileSync6(statePath, "utf-8");
|
|
4232
|
+
const parsed = JSON.parse(raw);
|
|
4233
|
+
check(`controller: OK (rev ${parsed.revision || 0} state ${parsed.state || "unknown"})`, true);
|
|
4234
|
+
if (parsed.degraded) {
|
|
4235
|
+
console.log("⚠️ degraded: true (persistido)");
|
|
4236
|
+
hasWarn = true;
|
|
4237
|
+
}
|
|
4238
|
+
if (parsed.degradedEditsCount > 0)
|
|
4239
|
+
console.log(`⚠️ degraded: confirmation not audited in controller (degradedEditsCount=${parsed.degradedEditsCount})`);
|
|
4240
|
+
if (parsed.codegraphBypassCount > 0)
|
|
4241
|
+
console.log(`⚠️ codegraphBypassCount=${parsed.codegraphBypassCount} (inefficient: codegraph bypass)`);
|
|
4242
|
+
if (parsed.stateOversizedCount > 0)
|
|
4243
|
+
console.log(`⚠️ stateOversizedCount=${parsed.stateOversizedCount} snapshots perdidos`);
|
|
4244
|
+
if (parsed.sensitiveAccess)
|
|
4245
|
+
console.log(`ℹ️ sensitiveAccess: allowed=${parsed.sensitiveAccess.allowed || 0} denied=${parsed.sensitiveAccess.denied || 0} blocked=${parsed.sensitiveAccess.blockedAttempts || 0}`);
|
|
4246
|
+
if (parsed.deniedFiles && Object.keys(parsed.deniedFiles).length) {
|
|
4247
|
+
console.log(`ℹ️ denied files: ${Object.keys(parsed.deniedFiles).join(", ")} (denied by user)`);
|
|
4248
|
+
}
|
|
4249
|
+
if (parsed.staleContentAttempts > 0)
|
|
4250
|
+
console.log(`⚠️ staleContentAttempts=${parsed.staleContentAttempts}`);
|
|
4251
|
+
if (parsed.completeWithoutValidateCount > 0)
|
|
4252
|
+
console.log(`⚠️ completeWithoutValidateCount=${parsed.completeWithoutValidateCount}`);
|
|
4253
|
+
if (stat.size > 2 * 1024 * 1024) {
|
|
4254
|
+
console.log("⚠️ state file >2MB (oversized)");
|
|
4255
|
+
hasWarn = true;
|
|
4256
|
+
}
|
|
4257
|
+
const auditSize = (parsed.audit || []).length;
|
|
4258
|
+
if (auditSize > 500)
|
|
4259
|
+
console.log(`⚠️ audit large: ${auditSize}`);
|
|
4260
|
+
try {
|
|
4261
|
+
const { statfsSync } = await import("node:fs");
|
|
4262
|
+
if (typeof statfsSync === "function") {
|
|
4263
|
+
const s = statfsSync(dirname8(statePath));
|
|
4264
|
+
const freeMB = Math.floor(s.bfree * s.bsize / 1048576);
|
|
4265
|
+
if (freeMB < 100) {
|
|
4266
|
+
console.log(`⚠️ Disco casi lleno: ${freeMB}MB libres`);
|
|
4267
|
+
hasWarn = true;
|
|
4268
|
+
} else
|
|
4269
|
+
console.log(`ℹ️ diskFreeMB: ${freeMB}`);
|
|
4270
|
+
}
|
|
4271
|
+
} catch {}
|
|
4272
|
+
if ((parsed.state === "EXECUTING_INLINE" || parsed.state === "EXECUTING_SUBAGENTS") && parsed.expectedTasks) {
|
|
4273
|
+
const pending = parsed.expectedTasks.filter((id) => !parsed.tasks?.[id] || parsed.tasks[id].status !== "COMPLETED").length;
|
|
4274
|
+
const lastHandoffAge = parsed.lastHandoff ? Date.now() - parsed.lastHandoff.ts : Infinity;
|
|
4275
|
+
if (pending === 0 && lastHandoffAge > 60000) {
|
|
4276
|
+
console.log("⚠️ EXECUTING_* con pending==0 y lastHandoff >60s sin progreso — sugerir implementation_complete manual");
|
|
4277
|
+
hasWarn = true;
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
} catch (e2) {
|
|
4282
|
+
check(`controller: ${e2.message}`, false);
|
|
4283
|
+
}
|
|
4284
|
+
try {
|
|
4285
|
+
const lockPid = join11(opencodeDir, "ostacky-state.json.lock.pid");
|
|
4286
|
+
const lockTs = join11(opencodeDir, "ostacky-state.json.lock.timestamp");
|
|
4287
|
+
if (existsSync8(lockPid) || existsSync8(lockTs)) {
|
|
4288
|
+
let ageStr = "";
|
|
4289
|
+
try {
|
|
4290
|
+
const ts = parseInt(readFileSync6(lockTs, "utf-8"), 10);
|
|
4291
|
+
const age = Date.now() - ts;
|
|
4292
|
+
ageStr = `${Math.floor(age / 1000)}s`;
|
|
4293
|
+
const pid = readFileSync6(lockPid, "utf-8").trim();
|
|
4294
|
+
let alive = false;
|
|
4295
|
+
try {
|
|
4296
|
+
process.kill(parseInt(pid, 10), 0);
|
|
4297
|
+
alive = true;
|
|
4298
|
+
} catch {}
|
|
4299
|
+
check(`lock: PID ${pid} age ${ageStr} alive=${alive}`, !alive || age > 15000, true);
|
|
4300
|
+
} catch {
|
|
4301
|
+
check("lock: exists (no timestamp)", false, true);
|
|
4302
|
+
}
|
|
4303
|
+
} else {
|
|
4304
|
+
check("lock: no active lock", true);
|
|
4305
|
+
}
|
|
4306
|
+
} catch {
|
|
4307
|
+
check("lock: check failed", false, true);
|
|
4308
|
+
}
|
|
4309
|
+
const tools = ["codegraph", "engram"];
|
|
4310
|
+
for (const t of tools) {
|
|
4311
|
+
const p2 = join11(opencodeDir, "tools", t, "bin", t);
|
|
4312
|
+
const pExe = p2 + ".exe";
|
|
4313
|
+
check(`tool ${t}: ${existsSync8(p2) || existsSync8(pExe) ? "found" : "missing"}`, existsSync8(p2) || existsSync8(pExe), true);
|
|
4314
|
+
}
|
|
4315
|
+
try {
|
|
4316
|
+
const manifest = JSON.parse(readFileSync6(join11(cwd, "manifest.json"), "utf-8"));
|
|
4317
|
+
const expected = manifest.mcpServers?.find((x2) => x2.name === "ostacky-controller")?.sha256;
|
|
4318
|
+
if (expected) {
|
|
4319
|
+
const actual = computeTreeHash(join11(cwd, "assets", "mcp", "ostacky-controller"));
|
|
4320
|
+
check(`manifest hash: ${expected.slice(0, 8)} vs actual ${actual.slice(0, 8)}`, expected === actual);
|
|
4321
|
+
if (expected !== actual)
|
|
4322
|
+
console.log(" Run: bun run hash:update");
|
|
4323
|
+
}
|
|
4324
|
+
} catch {
|
|
4325
|
+
check("manifest: not found", false, true);
|
|
4326
|
+
}
|
|
4327
|
+
if (existsSync8(statePath)) {
|
|
4328
|
+
try {
|
|
4329
|
+
const s = JSON.parse(readFileSync6(statePath, "utf-8"));
|
|
4330
|
+
if (s.allowedFiles || s.deniedFiles) {}
|
|
4331
|
+
} catch {}
|
|
4332
|
+
}
|
|
4333
|
+
if (hasError)
|
|
4334
|
+
process.exit(1);
|
|
4335
|
+
if (hasWarn)
|
|
4336
|
+
process.exit(0);
|
|
4337
|
+
}
|
|
4338
|
+
async function runStatusCommand(args) {
|
|
4339
|
+
const isJson = args.includes("--json");
|
|
4340
|
+
const cwd = process.cwd();
|
|
4341
|
+
const opencodeDir = findOpenCodeDir(cwd) || join11(cwd, ".opencode");
|
|
4342
|
+
const statePath = join11(opencodeDir, "ostacky-state.json");
|
|
4343
|
+
if (!existsSync8(statePath)) {
|
|
4344
|
+
console.log(isJson ? JSON.stringify({ error: "no state" }) : "No state file");
|
|
4345
|
+
return;
|
|
4346
|
+
}
|
|
4347
|
+
try {
|
|
4348
|
+
const parsed = JSON.parse(readFileSync6(statePath, "utf-8"));
|
|
4349
|
+
const completed = Object.values(parsed.tasks || {}).filter((t) => t.status === "COMPLETED").length;
|
|
4350
|
+
const expected = parsed.expectedTaskCount ?? parsed.expectedTasks?.length ?? Object.keys(parsed.tasks || {}).length;
|
|
4351
|
+
const degraded = parsed.degraded ? " degraded" : "";
|
|
4352
|
+
const lastHandoff = parsed.lastHandoff ? ` lastHandoff: ${parsed.lastHandoff.summary?.slice(0, 60)}` : "";
|
|
4353
|
+
if (isJson) {
|
|
4354
|
+
console.log(JSON.stringify({ state: parsed.state, revision: parsed.revision, degraded: !!parsed.degraded, tasks: `${completed}/${expected}`, lastHandoff: parsed.lastHandoff }, null, 2));
|
|
4355
|
+
} else {
|
|
4356
|
+
console.log(`${parsed.state} rev ${parsed.revision}${degraded} tasks ${completed}/${expected}${lastHandoff}`);
|
|
4357
|
+
if (parsed.lastProposal)
|
|
4358
|
+
console.log(`lastProposal: ${parsed.lastProposal.summary} shownToUser=${parsed.lastProposal.shownToUser}`);
|
|
4359
|
+
}
|
|
4360
|
+
} catch (e2) {
|
|
4361
|
+
console.log(`Error reading state: ${e2.message}`);
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4034
4364
|
async function main() {
|
|
4035
4365
|
switch (cmd) {
|
|
4036
4366
|
case "install":
|
|
4037
|
-
await runInstallCommand();
|
|
4367
|
+
await runInstallCommand(scope);
|
|
4038
4368
|
break;
|
|
4039
4369
|
case "install-stack":
|
|
4040
|
-
|
|
4370
|
+
if (scope === "global") {
|
|
4371
|
+
console.error("Error: install-stack requiere scope local; el stack vive en <proyecto>/.opencode/tools");
|
|
4372
|
+
console.error("Sugerencia: ejecutá 'npx ostacky install-stack --scope local' dentro de cada proyecto.");
|
|
4373
|
+
process.exit(1);
|
|
4374
|
+
}
|
|
4375
|
+
await runInstallStackCommand(scope);
|
|
4041
4376
|
break;
|
|
4042
4377
|
case "uninstall-stack":
|
|
4043
|
-
await runUninstallStackCommand();
|
|
4378
|
+
await runUninstallStackCommand(scope);
|
|
4044
4379
|
break;
|
|
4045
4380
|
case "add":
|
|
4046
4381
|
if (subcmd === "agent") {
|
|
4047
|
-
await runAddAgentCommand();
|
|
4382
|
+
await runAddAgentCommand(scope);
|
|
4048
4383
|
} else if (subcmd === "command") {
|
|
4049
|
-
await runAddCommandCommand();
|
|
4384
|
+
await runAddCommandCommand(scope);
|
|
4050
4385
|
} else if (subcmd === "skill") {
|
|
4051
|
-
await runAddSkillCommand();
|
|
4386
|
+
await runAddSkillCommand(scope);
|
|
4052
4387
|
} else if (subcmd === "mcp") {
|
|
4053
|
-
await runAddMcpCommand();
|
|
4388
|
+
await runAddMcpCommand(scope);
|
|
4054
4389
|
} else {
|
|
4055
4390
|
console.error(`Tipo desconocido: "${subcmd}". Usa 'agent', 'command', 'skill' o 'mcp'.`);
|
|
4056
4391
|
process.exit(1);
|
|
4057
4392
|
}
|
|
4058
4393
|
break;
|
|
4059
4394
|
case "update":
|
|
4060
|
-
await runUpdateCommand();
|
|
4395
|
+
await runUpdateCommand(scope);
|
|
4061
4396
|
break;
|
|
4062
4397
|
case "uninstall":
|
|
4063
4398
|
if (subcmd === "agent") {
|
|
4064
|
-
const name =
|
|
4065
|
-
await runUninstallAgentCommand(name);
|
|
4399
|
+
const name = argvNoScope[4];
|
|
4400
|
+
await runUninstallAgentCommand(name, scope);
|
|
4066
4401
|
} else if (subcmd === "command") {
|
|
4067
|
-
const name =
|
|
4068
|
-
await runUninstallCommandCommand(name);
|
|
4402
|
+
const name = argvNoScope[4];
|
|
4403
|
+
await runUninstallCommandCommand(name, scope);
|
|
4069
4404
|
} else if (subcmd === "skill") {
|
|
4070
|
-
const name =
|
|
4071
|
-
await runUninstallSkillCommand(name);
|
|
4405
|
+
const name = argvNoScope[4];
|
|
4406
|
+
await runUninstallSkillCommand(name, scope);
|
|
4072
4407
|
} else if (subcmd === "mcp") {
|
|
4073
|
-
const name =
|
|
4074
|
-
await runUninstallMcpCommand(name);
|
|
4408
|
+
const name = argvNoScope[4];
|
|
4409
|
+
await runUninstallMcpCommand(name, scope);
|
|
4075
4410
|
} else if (subcmd === undefined) {
|
|
4076
|
-
await runUninstallCommand();
|
|
4411
|
+
await runUninstallCommand(scope);
|
|
4077
4412
|
} else {
|
|
4078
4413
|
console.error(`Subcomando desconocido: "${subcmd}". Usa 'agent', 'command', 'skill', 'mcp' o nada.`);
|
|
4079
4414
|
process.exit(1);
|
|
4080
4415
|
}
|
|
4081
4416
|
break;
|
|
4417
|
+
case "doctor":
|
|
4418
|
+
await runDoctorCommand();
|
|
4419
|
+
break;
|
|
4420
|
+
case "status":
|
|
4421
|
+
await runStatusCommand(argvNoScope.slice(3));
|
|
4422
|
+
break;
|
|
4082
4423
|
case "--help":
|
|
4083
4424
|
case "-h":
|
|
4084
4425
|
console.log(HELP);
|
|
@@ -4092,7 +4433,7 @@ async function main() {
|
|
|
4092
4433
|
console.error(`Comando desconocido: "${cmd}". Usá --help para ver los comandos disponibles.`);
|
|
4093
4434
|
process.exit(1);
|
|
4094
4435
|
}
|
|
4095
|
-
await runInteractiveMenu();
|
|
4436
|
+
await runInteractiveMenu(scope);
|
|
4096
4437
|
}
|
|
4097
4438
|
}
|
|
4098
4439
|
main().catch((e2) => {
|