ostacky 0.7.4 → 0.8.1
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 +62 -27
- package/assets/agents/ostacky.md +45 -512
- package/assets/commands/install-stack.md +2 -2
- package/assets/docs/engram-protocol.md +79 -0
- package/assets/docs/ostacky-reference.md +79 -0
- package/assets/mcp/ostacky-controller/index.js +169 -69
- package/assets/mcp/ostacky-controller/package.json +1 -1
- package/assets/plugins/engram.ts +47 -79
- package/assets/plugins/ostacky-plugin.ts +694 -0
- package/assets/skills/brainstorming/SKILL.md +14 -14
- package/assets/skills/execution-mode-evaluation/SKILL.md +9 -9
- package/dist/cli.js +388 -106
- package/manifest.json +30 -30
- package/package.json +1 -1
- package/assets/plugins/ostacky-guard.ts +0 -212
package/dist/cli.js
CHANGED
|
@@ -293,6 +293,36 @@ var init_security = __esm(() => {
|
|
|
293
293
|
});
|
|
294
294
|
|
|
295
295
|
// src/fs.ts
|
|
296
|
+
var exports_fs = {};
|
|
297
|
+
__export(exports_fs, {
|
|
298
|
+
USER_AGENT: () => USER_AGENT,
|
|
299
|
+
checkBunAvailability: () => checkBunAvailability,
|
|
300
|
+
computeTreeHash: () => computeTreeHash,
|
|
301
|
+
copyDirRecursive: () => copyDirRecursive,
|
|
302
|
+
createOpenCodeDir: () => createOpenCodeDir,
|
|
303
|
+
detectPlatformTarget: () => detectPlatformTarget,
|
|
304
|
+
downloadAndExtract: () => downloadAndExtract,
|
|
305
|
+
downloadAndExtractWithRetry: () => downloadAndExtractWithRetry,
|
|
306
|
+
downloadToFile: () => downloadToFile,
|
|
307
|
+
downloadWithRetry: () => downloadWithRetry,
|
|
308
|
+
ensureOpenCodePaths: () => ensureOpenCodePaths,
|
|
309
|
+
ensureToolDirs: () => ensureToolDirs,
|
|
310
|
+
findBinaryInDir: () => findBinaryInDir,
|
|
311
|
+
findExecutablePath: () => findExecutablePath,
|
|
312
|
+
findOpenCodeDir: () => findOpenCodeDir,
|
|
313
|
+
findProjectRoot: () => findProjectRoot,
|
|
314
|
+
getBunInstallCommand: () => getBunInstallCommand,
|
|
315
|
+
getCommandInvocation: () => getCommandInvocation,
|
|
316
|
+
getEngramReleaseTarget: () => getEngramReleaseTarget,
|
|
317
|
+
getExecutableName: () => getExecutableName,
|
|
318
|
+
getExecutableNames: () => getExecutableNames,
|
|
319
|
+
getGlobalOpenCodeDir: () => getGlobalOpenCodeDir,
|
|
320
|
+
getOpenCodeDirForScope: () => getOpenCodeDirForScope,
|
|
321
|
+
isCommandAvailable: () => isCommandAvailable,
|
|
322
|
+
parseScopeArg: () => parseScopeArg,
|
|
323
|
+
promoteStagedDirectory: () => promoteStagedDirectory,
|
|
324
|
+
shouldRetryDownload: () => shouldRetryDownload
|
|
325
|
+
});
|
|
296
326
|
import {
|
|
297
327
|
existsSync,
|
|
298
328
|
mkdirSync,
|
|
@@ -366,6 +396,22 @@ function getOpenCodeDirForScope(scope, cwd = process.cwd()) {
|
|
|
366
396
|
}
|
|
367
397
|
return getGlobalOpenCodeDir();
|
|
368
398
|
}
|
|
399
|
+
function parseScopeArg(argv = process.argv) {
|
|
400
|
+
for (let i = 0;i < argv.length; i++) {
|
|
401
|
+
const arg = argv[i];
|
|
402
|
+
if (arg === "--scope" && i + 1 < argv.length) {
|
|
403
|
+
const v3 = argv[i + 1];
|
|
404
|
+
if (v3 === "local" || v3 === "global" || v3 === "auto")
|
|
405
|
+
return v3;
|
|
406
|
+
}
|
|
407
|
+
if (arg.startsWith("--scope=")) {
|
|
408
|
+
const v3 = arg.split("=")[1];
|
|
409
|
+
if (v3 === "local" || v3 === "global" || v3 === "auto")
|
|
410
|
+
return v3;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
369
415
|
function ensureOpenCodePaths(opencodeDir) {
|
|
370
416
|
const paths = {
|
|
371
417
|
root: opencodeDir,
|
|
@@ -389,6 +435,9 @@ function ensureToolDirs(toolsDir, toolNames) {
|
|
|
389
435
|
mkdirSync(dir, { recursive: true });
|
|
390
436
|
}
|
|
391
437
|
}
|
|
438
|
+
function createOpenCodeDir(baseDir) {
|
|
439
|
+
return ensureOpenCodePaths(join(baseDir, ".opencode"));
|
|
440
|
+
}
|
|
392
441
|
function copyDirRecursive(src, dest, skipGenerated = false) {
|
|
393
442
|
if (!existsSync(dest))
|
|
394
443
|
mkdirSync(dest, { recursive: true });
|
|
@@ -449,6 +498,44 @@ function findExecutablePath(cmd) {
|
|
|
449
498
|
return null;
|
|
450
499
|
}
|
|
451
500
|
}
|
|
501
|
+
function getBunInstallCommand() {
|
|
502
|
+
const platform = process.platform;
|
|
503
|
+
if (platform === "darwin" || platform === "linux") {
|
|
504
|
+
return {
|
|
505
|
+
command: "curl -fsSL https://bun.com/install | bash",
|
|
506
|
+
note: platform === "linux" ? "Requires 'unzip' package (sudo apt install unzip)" : undefined
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
if (platform === "win32") {
|
|
510
|
+
return {
|
|
511
|
+
command: 'powershell -c "irm bun.sh/install.ps1|iex"',
|
|
512
|
+
note: "Requires Windows 10 v1809 or later"
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
command: "npm install -g bun",
|
|
517
|
+
note: "Cross-platform fallback"
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function checkBunAvailability() {
|
|
521
|
+
const bunPath = findExecutablePath("bun");
|
|
522
|
+
if (bunPath) {
|
|
523
|
+
let version;
|
|
524
|
+
try {
|
|
525
|
+
version = execFileSync(bunPath, ["--version"], {
|
|
526
|
+
encoding: "utf-8",
|
|
527
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
528
|
+
}).trim();
|
|
529
|
+
} catch {}
|
|
530
|
+
return { available: true, path: bunPath, version };
|
|
531
|
+
}
|
|
532
|
+
const { command, note } = getBunInstallCommand();
|
|
533
|
+
return {
|
|
534
|
+
available: false,
|
|
535
|
+
installCommand: command,
|
|
536
|
+
installNote: note
|
|
537
|
+
};
|
|
538
|
+
}
|
|
452
539
|
function detectPlatformTarget(platform = process.platform, arch = process.arch) {
|
|
453
540
|
let os;
|
|
454
541
|
let cpu;
|
|
@@ -570,6 +657,23 @@ function downloadToFile(url, dest, timeoutMs = 180000) {
|
|
|
570
657
|
});
|
|
571
658
|
});
|
|
572
659
|
}
|
|
660
|
+
async function downloadWithRetry(url, dest, maxRetries = 2, timeoutMs = 180000) {
|
|
661
|
+
let lastError = null;
|
|
662
|
+
for (let attempt = 0;attempt <= maxRetries; attempt++) {
|
|
663
|
+
try {
|
|
664
|
+
await downloadToFile(url, dest, timeoutMs);
|
|
665
|
+
return;
|
|
666
|
+
} catch (err) {
|
|
667
|
+
lastError = err;
|
|
668
|
+
if (attempt < maxRetries && shouldRetryDownload(lastError)) {
|
|
669
|
+
const delay = 1000 * Math.pow(2, attempt);
|
|
670
|
+
console.error(`[download] Intento ${attempt + 1} falló: ${lastError.message}. Reintentando en ${delay}ms...`);
|
|
671
|
+
await new Promise((r2) => setTimeout(r2, delay));
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
throw lastError;
|
|
676
|
+
}
|
|
573
677
|
async function downloadAndExtract(url, destDir, stripComponents = 1, timeoutMs = 180000) {
|
|
574
678
|
const tmp = join(dirname(destDir), `.${basename(destDir)}.download-${Date.now()}-${process.pid}`);
|
|
575
679
|
if (!existsSync(tmp))
|
|
@@ -640,31 +744,31 @@ var init_fs = __esm(() => {
|
|
|
640
744
|
var manifest_default;
|
|
641
745
|
var init_manifest = __esm(() => {
|
|
642
746
|
manifest_default = {
|
|
643
|
-
version: "0.
|
|
747
|
+
version: "0.8.1",
|
|
644
748
|
repo: "JaimeHoracio/Ostacky",
|
|
645
|
-
tag: "v0.
|
|
749
|
+
tag: "v0.8.1",
|
|
646
750
|
agents: [
|
|
647
751
|
{
|
|
648
752
|
name: "ostacky",
|
|
649
753
|
file: "assets/agents/ostacky.md",
|
|
650
|
-
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.
|
|
651
|
-
version: "0.
|
|
652
|
-
sha256: "
|
|
754
|
+
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.8.1: prune de skills obsoletas, lastHandoff, getAvailableTransitions, consecutiveFailures real, mem_session_summary automático al cierre.",
|
|
755
|
+
version: "0.8.1",
|
|
756
|
+
sha256: "1dea56ffe3866e830c5138b44ff3e7531324ddd2784b1d34a8956614a53c5ec9"
|
|
653
757
|
}
|
|
654
758
|
],
|
|
655
759
|
commands: [
|
|
656
760
|
{
|
|
657
761
|
name: "install-stack",
|
|
658
762
|
file: "assets/commands/install-stack.md",
|
|
659
|
-
description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram,
|
|
660
|
-
version: "0.
|
|
661
|
-
sha256: "
|
|
763
|
+
description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, controller plugin). v0.8.1: controller plugin como alma hard-gate, Context7 removido del stack.",
|
|
764
|
+
version: "0.8.1",
|
|
765
|
+
sha256: "43ad96a63ceab8730cf0459838e7fad2953333307cfc664a849ca39f7ba7d532"
|
|
662
766
|
},
|
|
663
767
|
{
|
|
664
768
|
name: "opsx-sync",
|
|
665
769
|
file: "assets/commands/opsx-sync.md",
|
|
666
770
|
description: "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
|
|
667
|
-
version: "0.
|
|
771
|
+
version: "0.8.1",
|
|
668
772
|
sha256: "fe0158478f2ca63b315037a85fc1632b77868532e319af6c7384285441767d64"
|
|
669
773
|
}
|
|
670
774
|
],
|
|
@@ -672,15 +776,15 @@ var init_manifest = __esm(() => {
|
|
|
672
776
|
{
|
|
673
777
|
name: "ostacky-controller",
|
|
674
778
|
file: "assets/mcp/ostacky-controller/",
|
|
675
|
-
description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.
|
|
676
|
-
version: "0.
|
|
677
|
-
sha256: "
|
|
779
|
+
description: "Máquina de estados persistida con @modelcontextprotocol/server SDK. v0.8.1: 22 tools (incluye set_handoff, get_handoff, clear_handoff, get_available_transitions funcional). Bugfixes: consecutiveFailures real, lastHandoff state, defaultChoice persistido. Robustez: degraded mode automático tras 3 fallos, persistence condicional para Nivel 0, prune de skills obsoletas.",
|
|
780
|
+
version: "0.8.1",
|
|
781
|
+
sha256: "ec554bc97f9d9b1e64c0b42813547dedc923e86e326c43ae87afab88ba249b90"
|
|
678
782
|
},
|
|
679
783
|
{
|
|
680
784
|
name: "openspec",
|
|
681
785
|
file: "assets/mcp/openspec/",
|
|
682
786
|
description: "MCP server local para OpenSpec - proposal, apply, archive, sync de cambios",
|
|
683
|
-
version: "0.
|
|
787
|
+
version: "0.8.1",
|
|
684
788
|
sha256: "fa4be28bfc1e75db8f1a037e2580f04a60066c80e8e5934e90e1041020d04609"
|
|
685
789
|
}
|
|
686
790
|
],
|
|
@@ -689,105 +793,105 @@ var init_manifest = __esm(() => {
|
|
|
689
793
|
name: "brainstorming",
|
|
690
794
|
file: "assets/skills/brainstorming/SKILL.md",
|
|
691
795
|
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)",
|
|
692
|
-
version: "0.
|
|
693
|
-
sha256: "
|
|
796
|
+
version: "0.8.1",
|
|
797
|
+
sha256: "87f8d6ed28205f9272792d3865d8cd3232ab5e511a604d6d63d41e3a02a5f422"
|
|
694
798
|
},
|
|
695
799
|
{
|
|
696
800
|
name: "execution-mode-evaluation",
|
|
697
801
|
file: "assets/skills/execution-mode-evaluation/SKILL.md",
|
|
698
802
|
description: "Skill de análisis de modo de ejecución — output reconciliado con controller snapshot contract (recommendation field)",
|
|
699
|
-
version: "0.
|
|
700
|
-
sha256: "
|
|
803
|
+
version: "0.8.1",
|
|
804
|
+
sha256: "7b89dda29927c88f1eb13ffcea541a12423384c172f66c4f3d9b61efe0c7a163"
|
|
701
805
|
},
|
|
702
806
|
{
|
|
703
807
|
name: "tdd",
|
|
704
808
|
file: "assets/skills/tdd/SKILL.md",
|
|
705
809
|
description: "Skill de test-driven development (Superpowers)",
|
|
706
|
-
version: "0.
|
|
810
|
+
version: "0.8.1",
|
|
707
811
|
sha256: "aa412298980b7826165c211145c1b8e9135f68f36247bb3cb96d0a0eae274486"
|
|
708
812
|
},
|
|
709
813
|
{
|
|
710
814
|
name: "subagent-driven-development",
|
|
711
815
|
file: "assets/skills/subagent-driven-development/SKILL.md",
|
|
712
816
|
description: "Skill de ejecución con subagentes (Superpowers) — ejecuta solo después de confirmación del coordinador Ostacky",
|
|
713
|
-
version: "0.
|
|
817
|
+
version: "0.8.1",
|
|
714
818
|
sha256: "1a42d714a9a13faf05f0bf7b580e8d837c2e77a5633839542ccce603023419e8"
|
|
715
819
|
},
|
|
716
820
|
{
|
|
717
821
|
name: "dispatching-parallel-agents",
|
|
718
822
|
file: "assets/skills/dispatching-parallel-agents/SKILL.md",
|
|
719
823
|
description: "Skill de dispatch paralelo de agentes (Superpowers)",
|
|
720
|
-
version: "0.
|
|
824
|
+
version: "0.8.1",
|
|
721
825
|
sha256: "3c8a66d51ae2e719e877d02c3dac32bd804722c3bb9227c258ecae78252a20ab"
|
|
722
826
|
},
|
|
723
827
|
{
|
|
724
828
|
name: "review",
|
|
725
829
|
file: "assets/skills/review/SKILL.md",
|
|
726
830
|
description: "Skill de revisión de código (Superpowers)",
|
|
727
|
-
version: "0.
|
|
831
|
+
version: "0.8.1",
|
|
728
832
|
sha256: "b19650ed4d1d4d9857a4dd5b7e08e91328a1a1fc9c45bcc2fb600fa9d0213279"
|
|
729
833
|
},
|
|
730
834
|
{
|
|
731
835
|
name: "receiving-code-review",
|
|
732
836
|
file: "assets/skills/receiving-code-review/SKILL.md",
|
|
733
837
|
description: "Skill de recibir y procesar feedback de code review",
|
|
734
|
-
version: "0.
|
|
838
|
+
version: "0.8.1",
|
|
735
839
|
sha256: "d761e884e71d8d3476ac734d287cae403a4024506a7d12e7361a448217c0a831"
|
|
736
840
|
},
|
|
737
841
|
{
|
|
738
842
|
name: "openspec-propose",
|
|
739
843
|
file: "assets/skills/openspec-propose/SKILL.md",
|
|
740
844
|
description: "Skill de generación de proposal (OpenSpec)",
|
|
741
|
-
version: "0.
|
|
845
|
+
version: "0.8.1",
|
|
742
846
|
sha256: "bb59100b9fd3c9f9a1ec6509fa975ec55bf7fc737df4fe73bb3e9bbc217b83f7"
|
|
743
847
|
},
|
|
744
848
|
{
|
|
745
849
|
name: "openspec-apply-change",
|
|
746
850
|
file: "assets/skills/openspec-apply-change/SKILL.md",
|
|
747
851
|
description: "Skill de aplicación de change (OpenSpec)",
|
|
748
|
-
version: "0.
|
|
852
|
+
version: "0.8.1",
|
|
749
853
|
sha256: "dfc823bf89fc7505e91ab6dee9c1f004be38a410bd3d88fb67b211b1b6cbb1d0"
|
|
750
854
|
},
|
|
751
855
|
{
|
|
752
856
|
name: "openspec-archive-change",
|
|
753
857
|
file: "assets/skills/openspec-archive-change/SKILL.md",
|
|
754
858
|
description: "Skill de archivo de change (OpenSpec)",
|
|
755
|
-
version: "0.
|
|
859
|
+
version: "0.8.1",
|
|
756
860
|
sha256: "16e4b561de7747283663fed602e506abf502452c49a8d4b86d7a5539c40f0195"
|
|
757
861
|
},
|
|
758
862
|
{
|
|
759
863
|
name: "openspec-explore",
|
|
760
864
|
file: "assets/skills/openspec-explore/SKILL.md",
|
|
761
865
|
description: "Modo explore para OpenSpec — thinking partner para explorar ideas, investigar problemas y clarificar requisitos antes/durante un cambio",
|
|
762
|
-
version: "0.
|
|
866
|
+
version: "0.8.1",
|
|
763
867
|
sha256: "37ae4aaf17ea71a395bab6dc4d9d61b9a49d7910f6692535ae8553244c46880b"
|
|
764
868
|
},
|
|
765
869
|
{
|
|
766
870
|
name: "using-git-worktrees",
|
|
767
871
|
file: "assets/skills/using-git-worktrees/SKILL.md",
|
|
768
872
|
description: "Skill de uso de git worktrees para aislamiento de trabajo",
|
|
769
|
-
version: "0.
|
|
873
|
+
version: "0.8.1",
|
|
770
874
|
sha256: "93341bc1b7c053618a8b6dc07e3615b77990d7b549a0201f5835d95fef67ce13"
|
|
771
875
|
},
|
|
772
876
|
{
|
|
773
877
|
name: "using-superpowers",
|
|
774
878
|
file: "assets/skills/using-superpowers/SKILL.md",
|
|
775
879
|
description: "Skill de orquestación de Superpowers skills",
|
|
776
|
-
version: "0.
|
|
880
|
+
version: "0.8.1",
|
|
777
881
|
sha256: "7e54536f96d2a379185a10bfc1e970850caa2561b6d8aca7080f0defea0381a4"
|
|
778
882
|
},
|
|
779
883
|
{
|
|
780
884
|
name: "writing-skills",
|
|
781
885
|
file: "assets/skills/writing-skills/SKILL.md",
|
|
782
886
|
description: "Skill de creación y edición de skills",
|
|
783
|
-
version: "0.
|
|
887
|
+
version: "0.8.1",
|
|
784
888
|
sha256: "3d76b906cee518a2b809febb70db95697a35b9f368986bb504b4120c3bfb437a"
|
|
785
889
|
},
|
|
786
890
|
{
|
|
787
891
|
name: "graceful-degradation",
|
|
788
892
|
file: "assets/skills/graceful-degradation/SKILL.md",
|
|
789
893
|
description: "Skill de degradación graceful cuando múltiples tools están indisponibles",
|
|
790
|
-
version: "0.
|
|
894
|
+
version: "0.8.1",
|
|
791
895
|
sha256: "40f929e8032b70d51b3548bddcc383b931d6044fd9d4acce8a5b9f7acd014f59"
|
|
792
896
|
}
|
|
793
897
|
]
|
|
@@ -829,6 +933,19 @@ var init_cache = __esm(() => {
|
|
|
829
933
|
});
|
|
830
934
|
|
|
831
935
|
// src/github.ts
|
|
936
|
+
var exports_github = {};
|
|
937
|
+
__export(exports_github, {
|
|
938
|
+
BUNDLED_MCP_DIR: () => BUNDLED_MCP_DIR,
|
|
939
|
+
BUNDLED_SKILLS_DIR: () => BUNDLED_SKILLS_DIR,
|
|
940
|
+
PACKAGE_ROOT: () => PACKAGE_ROOT,
|
|
941
|
+
downloadFile: () => downloadFile,
|
|
942
|
+
fetchLatestManifest: () => fetchLatestManifest,
|
|
943
|
+
fetchLatestReleaseTag: () => fetchLatestReleaseTag,
|
|
944
|
+
fetchManifest: () => fetchManifest,
|
|
945
|
+
getBundledMcpPath: () => getBundledMcpPath,
|
|
946
|
+
getBundledSkillPath: () => getBundledSkillPath,
|
|
947
|
+
getRawUrl: () => getRawUrl
|
|
948
|
+
});
|
|
832
949
|
import { fileURLToPath } from "url";
|
|
833
950
|
import { dirname as dirname3, join as join3 } from "path";
|
|
834
951
|
function getBundledSkillPath(name) {
|
|
@@ -1143,6 +1260,12 @@ function patchOpenCodeConfig(projectRoot = findProjectRoot()) {
|
|
|
1143
1260
|
delete config.plugin;
|
|
1144
1261
|
changed = true;
|
|
1145
1262
|
}
|
|
1263
|
+
if (config.mcp && typeof config.mcp === "object" && "context7" in config.mcp) {
|
|
1264
|
+
delete config.mcp.context7;
|
|
1265
|
+
if (Object.keys(config.mcp).length === 0)
|
|
1266
|
+
delete config.mcp;
|
|
1267
|
+
changed = true;
|
|
1268
|
+
}
|
|
1146
1269
|
if (changed) {
|
|
1147
1270
|
writeOpenCodeConfig(configPath, config);
|
|
1148
1271
|
return { success: true, message: "Config actualizada (plugin legacy eliminado)" };
|
|
@@ -1162,7 +1285,6 @@ __export(exports_stack, {
|
|
|
1162
1285
|
installCodeGraph: () => installCodeGraph,
|
|
1163
1286
|
installEngram: () => installEngram,
|
|
1164
1287
|
installStack: () => installStack,
|
|
1165
|
-
setupContext7: () => setupContext7,
|
|
1166
1288
|
setupOpenSpec: () => setupOpenSpec,
|
|
1167
1289
|
uninstallEngramConfig: () => uninstallEngramConfig,
|
|
1168
1290
|
uninstallStackConfig: () => uninstallStackConfig,
|
|
@@ -1216,6 +1338,15 @@ function copyEngramPlugin(projectRoot) {
|
|
|
1216
1338
|
mkdirSync4(pluginsDir, { recursive: true });
|
|
1217
1339
|
copyFileSync3(pluginSource, join7(pluginsDir, "engram.ts"));
|
|
1218
1340
|
}
|
|
1341
|
+
function copyOstackyControllerPlugin(projectRoot) {
|
|
1342
|
+
const pluginSource = join7(PACKAGE_ROOT, "assets", "plugins", "ostacky-plugin.ts");
|
|
1343
|
+
const pluginsDir = join7(projectRoot, ".opencode", "plugins");
|
|
1344
|
+
if (!existsSync6(pluginSource)) {
|
|
1345
|
+
throw new Error(`Plugin bundleado de OstackyController no encontrado: ${pluginSource}`);
|
|
1346
|
+
}
|
|
1347
|
+
mkdirSync4(pluginsDir, { recursive: true });
|
|
1348
|
+
copyFileSync3(pluginSource, join7(pluginsDir, "ostacky-plugin.ts"));
|
|
1349
|
+
}
|
|
1219
1350
|
function buildEngramDownloadUrl(tag, platform = process.platform, arch = process.arch) {
|
|
1220
1351
|
const target = getEngramReleaseTarget(platform, arch);
|
|
1221
1352
|
if (!target)
|
|
@@ -1439,6 +1570,7 @@ async function installEngram(toolsDir) {
|
|
|
1439
1570
|
try {
|
|
1440
1571
|
runTool(localBin, ["--version"], projectRoot, 1e4);
|
|
1441
1572
|
copyEngramPlugin(projectRoot);
|
|
1573
|
+
copyOstackyControllerPlugin(projectRoot);
|
|
1442
1574
|
configureLocalTool(projectRoot, "engram", buildLocalMcpCommand(localBin, ["mcp"]));
|
|
1443
1575
|
} catch (error) {
|
|
1444
1576
|
return failAfterExtraction(`Engram fue instalado pero no se pudo verificar o configurar: ${error.message}`);
|
|
@@ -1446,44 +1578,15 @@ async function installEngram(toolsDir) {
|
|
|
1446
1578
|
archivePromotion?.commit();
|
|
1447
1579
|
return { success: true, message: "Engram instalado localmente y configurado para OpenCode (MCP + plugin)" };
|
|
1448
1580
|
}
|
|
1449
|
-
function setupContext7(toolsDir) {
|
|
1450
|
-
const location = resolveToolInstallLocation(toolsDir);
|
|
1451
|
-
const { projectRoot } = location;
|
|
1452
|
-
const ctx7ToolDir = join7(location.toolsDir, "context7");
|
|
1453
|
-
if (!existsSync6(ctx7ToolDir))
|
|
1454
|
-
mkdirSync4(ctx7ToolDir, { recursive: true });
|
|
1455
|
-
try {
|
|
1456
|
-
ensureMcpEntryAtProjectRoot(projectRoot, "context7", {
|
|
1457
|
-
type: "remote",
|
|
1458
|
-
url: "https://mcp.context7.com/mcp",
|
|
1459
|
-
enabled: true
|
|
1460
|
-
});
|
|
1461
|
-
} catch (error) {
|
|
1462
|
-
return { success: false, message: `No se pudo registrar Context7: ${error.message}` };
|
|
1463
|
-
}
|
|
1464
|
-
const useBun = isCommandAvailable("bun");
|
|
1465
|
-
try {
|
|
1466
|
-
const invocation = getCommandInvocation(useBun ? "bunx" : "npx", useBun ? ["ctx7", "setup", "--opencode"] : ["--yes", "ctx7", "setup", "--opencode"]);
|
|
1467
|
-
execFileSync3(invocation.command, invocation.args, {
|
|
1468
|
-
stdio: "pipe",
|
|
1469
|
-
timeout: 60000,
|
|
1470
|
-
cwd: projectRoot
|
|
1471
|
-
});
|
|
1472
|
-
return { success: true, message: "Context7 configurado (MCP + skill instalado)" };
|
|
1473
|
-
} catch {
|
|
1474
|
-
return {
|
|
1475
|
-
success: true,
|
|
1476
|
-
message: "Context7 MCP registrado (skill opcional no instalada — corrí `npx ctx7 setup --opencode` manualmente si la querés)"
|
|
1477
|
-
};
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
1581
|
async function installStack(toolsDir) {
|
|
1481
1582
|
const { projectRoot } = resolveToolInstallLocation(toolsDir);
|
|
1583
|
+
try {
|
|
1584
|
+
copyOstackyControllerPlugin(projectRoot);
|
|
1585
|
+
} catch {}
|
|
1482
1586
|
return {
|
|
1483
1587
|
codegraph: await installCodeGraph(toolsDir),
|
|
1484
1588
|
openspec: setupOpenSpec(projectRoot),
|
|
1485
1589
|
engram: await installEngram(toolsDir),
|
|
1486
|
-
context7: setupContext7(toolsDir),
|
|
1487
1590
|
config: patchOpenCodeConfig(projectRoot)
|
|
1488
1591
|
};
|
|
1489
1592
|
}
|
|
@@ -1518,7 +1621,7 @@ function uninstallStackConfig(paths) {
|
|
|
1518
1621
|
const mcp = config.mcp;
|
|
1519
1622
|
let changed = false;
|
|
1520
1623
|
if (mcp) {
|
|
1521
|
-
for (const name of ["codegraph", "
|
|
1624
|
+
for (const name of ["codegraph", "engram", "context7"]) {
|
|
1522
1625
|
if (name in mcp) {
|
|
1523
1626
|
delete mcp[name];
|
|
1524
1627
|
removed.push(`mcp.${name}`);
|
|
@@ -1588,7 +1691,7 @@ var init_stack = __esm(() => {
|
|
|
1588
1691
|
// package.json
|
|
1589
1692
|
var package_default = {
|
|
1590
1693
|
name: "ostacky",
|
|
1591
|
-
version: "0.
|
|
1694
|
+
version: "0.8.1",
|
|
1592
1695
|
description: "Instalador interactivo de agentes y comandos para OpenCode",
|
|
1593
1696
|
type: "module",
|
|
1594
1697
|
bin: {
|
|
@@ -2763,7 +2866,7 @@ async function probeMcpServer(nodeExecutable, serverPath, cwd, statePath, option
|
|
|
2763
2866
|
params: {
|
|
2764
2867
|
protocolVersion: "2025-03-26",
|
|
2765
2868
|
capabilities: {},
|
|
2766
|
-
clientInfo: { name: "ostacky-installer", version: "0.
|
|
2869
|
+
clientInfo: { name: "ostacky-installer", version: "0.8.1" }
|
|
2767
2870
|
}
|
|
2768
2871
|
});
|
|
2769
2872
|
});
|
|
@@ -3072,12 +3175,13 @@ function uninstallAll(paths) {
|
|
|
3072
3175
|
for (const name of Object.keys(lockfile.mcpServers ?? {})) {
|
|
3073
3176
|
uninstallMcpServer(name, paths);
|
|
3074
3177
|
}
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3178
|
+
const ostackyPlugins = ["ostacky-plugin.ts", "engram.ts", "ostacky-guard.ts", "ostacky-controller.ts"];
|
|
3179
|
+
for (const file of ostackyPlugins) {
|
|
3180
|
+
const fp = join6(paths.plugins, file);
|
|
3181
|
+
if (existsSync5(fp)) {
|
|
3182
|
+
try {
|
|
3183
|
+
unlinkSync2(fp);
|
|
3184
|
+
} catch {}
|
|
3081
3185
|
}
|
|
3082
3186
|
}
|
|
3083
3187
|
clearLockfile(paths.root);
|
|
@@ -3277,11 +3381,6 @@ async function doInstallStack(toolsDir, projectRoot) {
|
|
|
3277
3381
|
spin.stop(eng.success ? `✓ ${eng.message}` : `✗ ${eng.message}`);
|
|
3278
3382
|
if (!eng.success)
|
|
3279
3383
|
allOk = false;
|
|
3280
|
-
spin.start("Configurando Context7...");
|
|
3281
|
-
const ctx = setupContext7(toolsDir);
|
|
3282
|
-
spin.stop(ctx.success ? `✓ ${ctx.message}` : `✗ ${ctx.message}`);
|
|
3283
|
-
if (!ctx.success)
|
|
3284
|
-
allOk = false;
|
|
3285
3384
|
spin.start("Verificando configuración...");
|
|
3286
3385
|
const { patchOpenCodeConfig: patchOpenCodeConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
|
|
3287
3386
|
const cfg = patchOpenCodeConfig2(resolvedProjectRoot);
|
|
@@ -3298,9 +3397,9 @@ async function doInstallAll(manifest, paths) {
|
|
|
3298
3397
|
let errors = 0;
|
|
3299
3398
|
const isGlobal = isGlobalScope(paths);
|
|
3300
3399
|
if (!isGlobal) {
|
|
3301
|
-
ensureToolDirs(paths.tools, ["codegraph", "engram"
|
|
3400
|
+
ensureToolDirs(paths.tools, ["codegraph", "engram"]);
|
|
3302
3401
|
} else {
|
|
3303
|
-
v2.info("Scope global detectado: el stack (CodeGraph/Engram)
|
|
3402
|
+
v2.info("Scope global detectado: el stack (CodeGraph/Engram) y plugins (ostacky-controller, engram) permanecen siempre en <proyecto>/.opencode — se omite instalación de stack global.");
|
|
3304
3403
|
v2.info("Para instalar el stack, ejecutá 'npx ostacky install-stack --scope local' dentro de cada proyecto.");
|
|
3305
3404
|
}
|
|
3306
3405
|
for (const agent of manifest.agents) {
|
|
@@ -3347,6 +3446,49 @@ async function doInstallAll(manifest, paths) {
|
|
|
3347
3446
|
errors++;
|
|
3348
3447
|
}
|
|
3349
3448
|
}
|
|
3449
|
+
try {
|
|
3450
|
+
const { copyFileSync: copyFileSync4, mkdirSync: mkdirSync5, existsSync: existsSync8, rmSync: rmSync5 } = await import("fs");
|
|
3451
|
+
const { join: join9 } = await import("path");
|
|
3452
|
+
const { PACKAGE_ROOT: PACKAGE_ROOT2 } = await Promise.resolve().then(() => (init_github(), exports_github));
|
|
3453
|
+
const { findProjectRoot: findProjectRoot3 } = await Promise.resolve().then(() => (init_fs(), exports_fs));
|
|
3454
|
+
const src = join9(PACKAGE_ROOT2, "assets", "plugins", "ostacky-plugin.ts");
|
|
3455
|
+
const dest = join9(paths.plugins, "ostacky-plugin.ts");
|
|
3456
|
+
if (existsSync8(src)) {
|
|
3457
|
+
mkdirSync5(paths.plugins, { recursive: true });
|
|
3458
|
+
copyFileSync4(src, dest);
|
|
3459
|
+
}
|
|
3460
|
+
for (const legacy of ["ostacky-guard.ts", "ostacky-controller.ts"]) {
|
|
3461
|
+
const lp = join9(paths.plugins, legacy);
|
|
3462
|
+
if (existsSync8(lp))
|
|
3463
|
+
try {
|
|
3464
|
+
rmSync5(lp, { force: true });
|
|
3465
|
+
} catch {}
|
|
3466
|
+
}
|
|
3467
|
+
const srcEng = join9(PACKAGE_ROOT2, "assets", "plugins", "engram.ts");
|
|
3468
|
+
const destEng = join9(paths.plugins, "engram.ts");
|
|
3469
|
+
if (existsSync8(srcEng)) {
|
|
3470
|
+
mkdirSync5(paths.plugins, { recursive: true });
|
|
3471
|
+
copyFileSync4(srcEng, destEng);
|
|
3472
|
+
}
|
|
3473
|
+
if (isGlobal) {
|
|
3474
|
+
try {
|
|
3475
|
+
const projRoot = findProjectRoot3();
|
|
3476
|
+
const localPlugins = join9(projRoot, ".opencode", "plugins");
|
|
3477
|
+
mkdirSync5(localPlugins, { recursive: true });
|
|
3478
|
+
if (existsSync8(src))
|
|
3479
|
+
copyFileSync4(src, join9(localPlugins, "ostacky-plugin.ts"));
|
|
3480
|
+
if (existsSync8(srcEng))
|
|
3481
|
+
copyFileSync4(srcEng, join9(localPlugins, "engram.ts"));
|
|
3482
|
+
for (const legacy of ["ostacky-guard.ts", "ostacky-controller.ts"]) {
|
|
3483
|
+
const lp = join9(localPlugins, legacy);
|
|
3484
|
+
if (existsSync8(lp))
|
|
3485
|
+
try {
|
|
3486
|
+
rmSync5(lp, { force: true });
|
|
3487
|
+
} catch {}
|
|
3488
|
+
}
|
|
3489
|
+
} catch {}
|
|
3490
|
+
}
|
|
3491
|
+
} catch {}
|
|
3350
3492
|
let stackOk = true;
|
|
3351
3493
|
let missingTools = [];
|
|
3352
3494
|
if (isGlobal) {
|
|
@@ -3584,10 +3726,10 @@ async function doUninstallTotal(paths) {
|
|
|
3584
3726
|
for (const name of Object.keys(lockfile.mcpServers ?? {})) {
|
|
3585
3727
|
pathsToDelete.push(join9(paths.mcp, name));
|
|
3586
3728
|
}
|
|
3587
|
-
ye(pathsToDelete.join(`
|
|
3588
|
-
`), `Se eliminarán ${pathsToDelete.length}
|
|
3729
|
+
ye([`Scope: ${paths.root}`, ...pathsToDelete].join(`
|
|
3730
|
+
`), `Se eliminarán ${pathsToDelete.length} item(s) trackeados en lockfile (safe-delete)`);
|
|
3589
3731
|
const confirm = await me({
|
|
3590
|
-
message: `¿Confirmar desinstalación de ${pathsToDelete.length} item(s)?`
|
|
3732
|
+
message: `¿Confirmar desinstalación de ${pathsToDelete.length} item(s) en ${paths.root}?`
|
|
3591
3733
|
});
|
|
3592
3734
|
onCancel(confirm);
|
|
3593
3735
|
if (!confirm) {
|
|
@@ -3595,7 +3737,7 @@ async function doUninstallTotal(paths) {
|
|
|
3595
3737
|
return;
|
|
3596
3738
|
}
|
|
3597
3739
|
uninstallAll(paths);
|
|
3598
|
-
v2.success(`${pathsToDelete.length} item(s) eliminado(s).`);
|
|
3740
|
+
v2.success(`${pathsToDelete.length} item(s) eliminado(s) + plugins Ostacky-owned limpiados.`);
|
|
3599
3741
|
const cleanEngram = await me({
|
|
3600
3742
|
message: "¿Deseas remover la configuración de Engram del proyecto (mcp.engram)?"
|
|
3601
3743
|
});
|
|
@@ -3876,9 +4018,122 @@ async function doUninstallMcpByName(name, paths) {
|
|
|
3876
4018
|
}
|
|
3877
4019
|
}
|
|
3878
4020
|
|
|
4021
|
+
// src/opencode.ts
|
|
4022
|
+
init_fs();
|
|
4023
|
+
import { execSync, execFileSync as execFileSync4 } from "node:child_process";
|
|
4024
|
+
function isOpencodeInstalled() {
|
|
4025
|
+
if (isCommandAvailable("opencode"))
|
|
4026
|
+
return true;
|
|
4027
|
+
try {
|
|
4028
|
+
execFileSync4("opencode", ["--version"], { stdio: "ignore" });
|
|
4029
|
+
return true;
|
|
4030
|
+
} catch {
|
|
4031
|
+
return false;
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
function getOpencodeInstallCommand(platform = process.platform, _arch = process.arch) {
|
|
4035
|
+
if (platform === "win32") {
|
|
4036
|
+
return {
|
|
4037
|
+
display: "npm install -g opencode-ai",
|
|
4038
|
+
command: "npm",
|
|
4039
|
+
args: ["install", "-g", "opencode-ai"],
|
|
4040
|
+
note: "Alternativas Windows: choco install opencode | scoop install opencode | mise use -g github:anomalyco/opencode (ver https://opencode.ai/download). WSL recomendado."
|
|
4041
|
+
};
|
|
4042
|
+
}
|
|
4043
|
+
if (platform === "darwin") {
|
|
4044
|
+
return {
|
|
4045
|
+
display: "curl -fsSL https://opencode.ai/install | bash",
|
|
4046
|
+
command: "bash",
|
|
4047
|
+
args: ["-c", "curl -fsSL https://opencode.ai/install | bash"],
|
|
4048
|
+
note: "Alternativa macOS: brew install anomalyco/tap/opencode (tap oficial, más actualizado que brew install opencode)"
|
|
4049
|
+
};
|
|
4050
|
+
}
|
|
4051
|
+
return {
|
|
4052
|
+
display: "curl -fsSL https://opencode.ai/install | bash",
|
|
4053
|
+
command: "bash",
|
|
4054
|
+
args: ["-c", "curl -fsSL https://opencode.ai/install | bash"],
|
|
4055
|
+
note: "Alternativas Linux: npm install -g opencode-ai | bun add -g opencode-ai | brew install anomalyco/tap/opencode | paru -S opencode (Arch)"
|
|
4056
|
+
};
|
|
4057
|
+
}
|
|
4058
|
+
async function ensureOpencodeInstalled() {
|
|
4059
|
+
if (isOpencodeInstalled())
|
|
4060
|
+
return;
|
|
4061
|
+
const platform = process.platform;
|
|
4062
|
+
const info = getOpencodeInstallCommand(platform);
|
|
4063
|
+
v2.warn("OpenCode no detectado en este sistema.");
|
|
4064
|
+
ye(`${info.display}
|
|
4065
|
+
Docs: https://opencode.ai/download${info.note ? `
|
|
4066
|
+
${info.note}` : ""}`, `Instalación requerida (${platform})`);
|
|
4067
|
+
const shouldInstall = await me({
|
|
4068
|
+
message: "Ostacky no funciona sin OpenCode. ¿Querés instalar OpenCode ahora?",
|
|
4069
|
+
initialValue: true
|
|
4070
|
+
});
|
|
4071
|
+
if (BD(shouldInstall) || !shouldInstall) {
|
|
4072
|
+
fe("Instalación cancelada. Instalá OpenCode manualmente y volvé a ejecutar: https://opencode.ai/download");
|
|
4073
|
+
process.exit(1);
|
|
4074
|
+
}
|
|
4075
|
+
const spin = L2();
|
|
4076
|
+
spin.start(`Instalando OpenCode (${info.display})...`);
|
|
4077
|
+
try {
|
|
4078
|
+
if (platform === "win32") {
|
|
4079
|
+
execSync("npm install -g opencode-ai", { stdio: "inherit", shell: "cmd.exe" });
|
|
4080
|
+
} else {
|
|
4081
|
+
if (!isCommandAvailable("curl")) {
|
|
4082
|
+
spin.stop("curl no disponible");
|
|
4083
|
+
v2.warn("curl no está instalado — se intentará con npm como fallback.");
|
|
4084
|
+
execSync("npm install -g opencode-ai", { stdio: "inherit", shell: "/bin/bash" });
|
|
4085
|
+
} else {
|
|
4086
|
+
execSync("curl -fsSL https://opencode.ai/install | bash", { stdio: "inherit", shell: "/bin/bash" });
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
spin.stop("Instalación ejecutada, verificando...");
|
|
4090
|
+
} catch (e2) {
|
|
4091
|
+
spin.stop("Fallo la instalación automática.");
|
|
4092
|
+
const msg = e2.message ?? String(e2);
|
|
4093
|
+
v2.error(`No se pudo instalar OpenCode automáticamente: ${msg}`);
|
|
4094
|
+
if (platform === "win32") {
|
|
4095
|
+
ye([
|
|
4096
|
+
"Intentá manualmente una de estas opciones:",
|
|
4097
|
+
" npm install -g opencode-ai",
|
|
4098
|
+
" choco install opencode",
|
|
4099
|
+
" scoop install opencode",
|
|
4100
|
+
" (WSL) curl -fsSL https://opencode.ai/install | bash",
|
|
4101
|
+
"Luego verificá: opencode --version",
|
|
4102
|
+
"Docs: https://opencode.ai/download"
|
|
4103
|
+
].join(`
|
|
4104
|
+
`), "Acción manual requerida");
|
|
4105
|
+
} else {
|
|
4106
|
+
ye([
|
|
4107
|
+
"Intentá manualmente:",
|
|
4108
|
+
` ${info.display}`,
|
|
4109
|
+
" npm install -g opencode-ai (si no tenés curl)",
|
|
4110
|
+
" brew install anomalyco/tap/opencode (macOS/Linux con brew)",
|
|
4111
|
+
"Luego verificá: opencode --version",
|
|
4112
|
+
"Docs: https://opencode.ai/download"
|
|
4113
|
+
].join(`
|
|
4114
|
+
`), "Acción manual requerida");
|
|
4115
|
+
}
|
|
4116
|
+
process.exit(1);
|
|
4117
|
+
}
|
|
4118
|
+
if (!isOpencodeInstalled()) {
|
|
4119
|
+
v2.error("OpenCode aún no está disponible en el PATH después de la instalación.");
|
|
4120
|
+
ye([
|
|
4121
|
+
"Probá cerrar y reabrir la terminal y ejecutar:",
|
|
4122
|
+
" opencode --version",
|
|
4123
|
+
"Si persiste, instalá manualmente:",
|
|
4124
|
+
` ${info.display}`,
|
|
4125
|
+
"Docs: https://opencode.ai/download"
|
|
4126
|
+
].join(`
|
|
4127
|
+
`), "Verificación falló");
|
|
4128
|
+
process.exit(1);
|
|
4129
|
+
}
|
|
4130
|
+
v2.success("OpenCode instalado correctamente ✓");
|
|
4131
|
+
}
|
|
4132
|
+
|
|
3879
4133
|
// src/prompts/index.ts
|
|
3880
4134
|
async function runInteractiveMenu(scope) {
|
|
3881
4135
|
we(" OpenCode Installer ");
|
|
4136
|
+
await ensureOpencodeInstalled();
|
|
3882
4137
|
const manifest = await loadManifest();
|
|
3883
4138
|
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3884
4139
|
if (!paths) {
|
|
@@ -3893,7 +4148,7 @@ async function runInteractiveMenu(scope) {
|
|
|
3893
4148
|
{ value: "command", label: "Instalar command" },
|
|
3894
4149
|
{ value: "skill", label: "Instalar skill" },
|
|
3895
4150
|
{ value: "mcp", label: "Instalar MCP server" },
|
|
3896
|
-
{ value: "stack", label: "Instalar stack de herramientas (CodeGraph, Engram
|
|
4151
|
+
{ value: "stack", label: "Instalar stack de herramientas (CodeGraph, Engram)" },
|
|
3897
4152
|
{ value: "update", label: "Actualizar instalación" },
|
|
3898
4153
|
{ value: "uninstall", label: "Desinstalar" },
|
|
3899
4154
|
{ value: "exit", label: "Salir" }
|
|
@@ -3934,7 +4189,7 @@ async function runInteractiveMenu(scope) {
|
|
|
3934
4189
|
fe("Cancelado.");
|
|
3935
4190
|
break;
|
|
3936
4191
|
}
|
|
3937
|
-
ensureToolDirs(paths.tools, ["codegraph", "engram"
|
|
4192
|
+
ensureToolDirs(paths.tools, ["codegraph", "engram"]);
|
|
3938
4193
|
const stackOk = await doInstallStack(paths.tools, dirname7(paths.root));
|
|
3939
4194
|
if (!stackOk)
|
|
3940
4195
|
process.exitCode = 1;
|
|
@@ -3958,6 +4213,7 @@ async function runInteractiveMenu(scope) {
|
|
|
3958
4213
|
}
|
|
3959
4214
|
async function runInstallCommand(scope) {
|
|
3960
4215
|
we(" OpenCode Installer ");
|
|
4216
|
+
await ensureOpencodeInstalled();
|
|
3961
4217
|
const manifest = await loadManifest();
|
|
3962
4218
|
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3963
4219
|
if (!paths) {
|
|
@@ -3971,6 +4227,7 @@ async function runInstallCommand(scope) {
|
|
|
3971
4227
|
}
|
|
3972
4228
|
async function runAddAgentCommand(scope) {
|
|
3973
4229
|
we(" OpenCode Installer ");
|
|
4230
|
+
await ensureOpencodeInstalled();
|
|
3974
4231
|
const manifest = await loadManifest();
|
|
3975
4232
|
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3976
4233
|
if (!paths) {
|
|
@@ -3983,6 +4240,7 @@ async function runAddAgentCommand(scope) {
|
|
|
3983
4240
|
}
|
|
3984
4241
|
async function runAddCommandCommand(scope) {
|
|
3985
4242
|
we(" OpenCode Installer ");
|
|
4243
|
+
await ensureOpencodeInstalled();
|
|
3986
4244
|
const manifest = await loadManifest();
|
|
3987
4245
|
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
3988
4246
|
if (!paths) {
|
|
@@ -3995,6 +4253,7 @@ async function runAddCommandCommand(scope) {
|
|
|
3995
4253
|
}
|
|
3996
4254
|
async function runAddSkillCommand(scope) {
|
|
3997
4255
|
we(" OpenCode Installer ");
|
|
4256
|
+
await ensureOpencodeInstalled();
|
|
3998
4257
|
const manifest = await loadManifest();
|
|
3999
4258
|
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
4000
4259
|
if (!paths) {
|
|
@@ -4007,6 +4266,7 @@ async function runAddSkillCommand(scope) {
|
|
|
4007
4266
|
}
|
|
4008
4267
|
async function runAddMcpCommand(scope) {
|
|
4009
4268
|
we(" OpenCode Installer ");
|
|
4269
|
+
await ensureOpencodeInstalled();
|
|
4010
4270
|
const manifest = await loadManifest();
|
|
4011
4271
|
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
4012
4272
|
if (!paths) {
|
|
@@ -4018,6 +4278,7 @@ async function runAddMcpCommand(scope) {
|
|
|
4018
4278
|
fe("Listo.");
|
|
4019
4279
|
}
|
|
4020
4280
|
async function runInstallStackCommand(scope) {
|
|
4281
|
+
await ensureOpencodeInstalled();
|
|
4021
4282
|
if (scope === "global") {
|
|
4022
4283
|
v2.error("install-stack requiere scope local; el stack vive en <proyecto>/.opencode/tools");
|
|
4023
4284
|
fe("Usá: npx ostacky install-stack --scope local");
|
|
@@ -4037,7 +4298,7 @@ async function runInstallStackCommand(scope) {
|
|
|
4037
4298
|
process.exitCode = 1;
|
|
4038
4299
|
return;
|
|
4039
4300
|
}
|
|
4040
|
-
ensureToolDirs(paths.tools, ["codegraph", "engram"
|
|
4301
|
+
ensureToolDirs(paths.tools, ["codegraph", "engram"]);
|
|
4041
4302
|
const stackOk = await doInstallStack(paths.tools, dirname7(paths.root));
|
|
4042
4303
|
if (!stackOk)
|
|
4043
4304
|
process.exitCode = 1;
|
|
@@ -4051,7 +4312,7 @@ async function runUninstallStackCommand(scope) {
|
|
|
4051
4312
|
return;
|
|
4052
4313
|
}
|
|
4053
4314
|
const confirm = await me({
|
|
4054
|
-
message: "¿Remover la configuración del stack (CodeGraph, Engram
|
|
4315
|
+
message: "¿Remover la configuración del stack (CodeGraph, Engram) del proyecto? Los binarios globales no se tocan."
|
|
4055
4316
|
});
|
|
4056
4317
|
onCancel(confirm);
|
|
4057
4318
|
if (!confirm) {
|
|
@@ -4069,6 +4330,7 @@ async function runUninstallStackCommand(scope) {
|
|
|
4069
4330
|
}
|
|
4070
4331
|
async function runUpdateCommand(scope) {
|
|
4071
4332
|
we(" OpenCode Installer ");
|
|
4333
|
+
await ensureOpencodeInstalled();
|
|
4072
4334
|
const manifest = await loadLatestManifest();
|
|
4073
4335
|
const paths = await resolveOpenCodePaths(scope ?? null);
|
|
4074
4336
|
if (!paths) {
|
|
@@ -4232,18 +4494,18 @@ async function runUninstallMcpCommand(name, scope) {
|
|
|
4232
4494
|
// src/cli.ts
|
|
4233
4495
|
init_fs();
|
|
4234
4496
|
import { existsSync as existsSync8, statSync as statSync3, readFileSync as readFileSync6, readdirSync as readdirSync3 } from "node:fs";
|
|
4235
|
-
import { join as
|
|
4497
|
+
import { join as join10, dirname as dirname8 } from "node:path";
|
|
4236
4498
|
var HELP = `
|
|
4237
4499
|
ostacky — Instalador de agentes, comandos, skills y MCPs para OpenCode
|
|
4238
4500
|
|
|
4239
4501
|
Uso:
|
|
4240
4502
|
npx ostacky [--scope local|global|auto] Menú interactivo (instalación completa, pregunta local vs global, default local)
|
|
4241
|
-
npx ostacky install [--scope local|global|auto] Instalar TODO (agente + skills + MCPs + CodeGraph + OpenSpec + Engram
|
|
4503
|
+
npx ostacky install [--scope local|global|auto] Instalar TODO (agente + skills + MCPs + CodeGraph + OpenSpec + Engram)
|
|
4242
4504
|
npx ostacky add agent [--scope local|global|auto] Agregar agente(s)
|
|
4243
4505
|
npx ostacky add command [--scope ...] Agregar command(s)
|
|
4244
4506
|
npx ostacky add skill [--scope ...] Agregar skill(s)
|
|
4245
4507
|
npx ostacky add mcp [--scope ...] Agregar MCP server(s)
|
|
4246
|
-
npx ostacky install-stack [--scope local|auto] Instalar solo el stack de herramientas (CodeGraph, OpenSpec, Engram
|
|
4508
|
+
npx ostacky install-stack [--scope local|auto] Instalar solo el stack de herramientas (CodeGraph, OpenSpec, Engram) — global bloquea con error
|
|
4247
4509
|
npx ostacky uninstall-stack [--scope local|global|auto] Remover la configuración del stack del proyecto
|
|
4248
4510
|
npx ostacky doctor Diagnostica locks, tools, state health
|
|
4249
4511
|
npx ostacky status [--json] Muestra estado del controller sin MCP
|
|
@@ -4262,7 +4524,7 @@ Scope:
|
|
|
4262
4524
|
--scope auto Elige local si existe .opencode o .git, si no global
|
|
4263
4525
|
Sin flag Pregunta interactiva local (default) vs global
|
|
4264
4526
|
`.trim();
|
|
4265
|
-
function
|
|
4527
|
+
function parseScopeArg2(argv = process.argv) {
|
|
4266
4528
|
for (let i = 0;i < argv.length; i++) {
|
|
4267
4529
|
const arg = argv[i];
|
|
4268
4530
|
if (arg === "--scope" && i + 1 < argv.length) {
|
|
@@ -4292,13 +4554,13 @@ function withoutScopeArgs(argv) {
|
|
|
4292
4554
|
}
|
|
4293
4555
|
return out;
|
|
4294
4556
|
}
|
|
4295
|
-
var scope =
|
|
4557
|
+
var scope = parseScopeArg2();
|
|
4296
4558
|
var argvNoScope = withoutScopeArgs(process.argv);
|
|
4297
4559
|
var [, , cmd, subcmd] = argvNoScope;
|
|
4298
4560
|
async function runDoctorCommand() {
|
|
4299
4561
|
const cwd = process.cwd();
|
|
4300
|
-
const opencodeDir = findOpenCodeDir(cwd) ||
|
|
4301
|
-
const statePath =
|
|
4562
|
+
const opencodeDir = findOpenCodeDir(cwd) || join10(cwd, ".opencode");
|
|
4563
|
+
const statePath = join10(opencodeDir, "ostacky-state.json");
|
|
4302
4564
|
let hasError = false;
|
|
4303
4565
|
let hasWarn = false;
|
|
4304
4566
|
const check = (label, ok, warn = false) => {
|
|
@@ -4312,14 +4574,30 @@ async function runDoctorCommand() {
|
|
|
4312
4574
|
hasError = true;
|
|
4313
4575
|
}
|
|
4314
4576
|
};
|
|
4577
|
+
const pluginPaths = [
|
|
4578
|
+
join10(cwd, "assets", "plugins", "ostacky-plugin.ts"),
|
|
4579
|
+
join10(opencodeDir, "plugins", "ostacky-plugin.ts"),
|
|
4580
|
+
join10(cwd, ".opencode", "plugins", "ostacky-plugin.ts"),
|
|
4581
|
+
join10(cwd, "assets", "plugins", "ostacky-controller.ts"),
|
|
4582
|
+
join10(opencodeDir, "plugins", "ostacky-controller.ts"),
|
|
4583
|
+
join10(cwd, ".opencode", "plugins", "ostacky-controller.ts")
|
|
4584
|
+
];
|
|
4585
|
+
const pluginActive = pluginPaths.some((p2) => existsSync8(p2));
|
|
4315
4586
|
try {
|
|
4316
4587
|
if (!existsSync8(statePath)) {
|
|
4317
|
-
|
|
4588
|
+
if (pluginActive)
|
|
4589
|
+
console.log(`✅ controller: plugin active (no state yet)`);
|
|
4590
|
+
else
|
|
4591
|
+
check("controller: state file missing", false, true);
|
|
4318
4592
|
} else {
|
|
4319
4593
|
const stat = statSync3(statePath);
|
|
4320
4594
|
const raw = readFileSync6(statePath, "utf-8");
|
|
4321
4595
|
const parsed = JSON.parse(raw);
|
|
4322
|
-
|
|
4596
|
+
if (pluginActive) {
|
|
4597
|
+
console.log(`✅ controller: plugin active (rev ${parsed.revision || 0} state ${parsed.state || "unknown"})`);
|
|
4598
|
+
} else {
|
|
4599
|
+
check(`controller: OK (rev ${parsed.revision || 0} state ${parsed.state || "unknown"})`, true);
|
|
4600
|
+
}
|
|
4323
4601
|
if (parsed.degraded) {
|
|
4324
4602
|
console.log("⚠️ degraded: true (persistido)");
|
|
4325
4603
|
hasWarn = true;
|
|
@@ -4375,8 +4653,8 @@ async function runDoctorCommand() {
|
|
|
4375
4653
|
check(`controller: ${e2.message}`, false);
|
|
4376
4654
|
}
|
|
4377
4655
|
try {
|
|
4378
|
-
const lockPid =
|
|
4379
|
-
const lockTs =
|
|
4656
|
+
const lockPid = join10(opencodeDir, "ostacky-state.json.lock.pid");
|
|
4657
|
+
const lockTs = join10(opencodeDir, "ostacky-state.json.lock.timestamp");
|
|
4380
4658
|
if (existsSync8(lockPid) || existsSync8(lockTs)) {
|
|
4381
4659
|
let ageStr = "";
|
|
4382
4660
|
try {
|
|
@@ -4401,15 +4679,15 @@ async function runDoctorCommand() {
|
|
|
4401
4679
|
}
|
|
4402
4680
|
const tools = ["codegraph", "engram"];
|
|
4403
4681
|
for (const t of tools) {
|
|
4404
|
-
const p2 =
|
|
4682
|
+
const p2 = join10(opencodeDir, "tools", t, "bin", t);
|
|
4405
4683
|
const pExe = p2 + ".exe";
|
|
4406
4684
|
check(`tool ${t}: ${existsSync8(p2) || existsSync8(pExe) ? "found" : "missing"}`, existsSync8(p2) || existsSync8(pExe), true);
|
|
4407
4685
|
}
|
|
4408
4686
|
try {
|
|
4409
|
-
const manifest = JSON.parse(readFileSync6(
|
|
4687
|
+
const manifest = JSON.parse(readFileSync6(join10(cwd, "manifest.json"), "utf-8"));
|
|
4410
4688
|
const expected = manifest.mcpServers?.find((x2) => x2.name === "ostacky-controller")?.sha256;
|
|
4411
4689
|
if (expected) {
|
|
4412
|
-
const actual = computeTreeHash(
|
|
4690
|
+
const actual = computeTreeHash(join10(cwd, "assets", "mcp", "ostacky-controller"));
|
|
4413
4691
|
check(`manifest hash: ${expected.slice(0, 8)} vs actual ${actual.slice(0, 8)}`, expected === actual);
|
|
4414
4692
|
if (expected !== actual)
|
|
4415
4693
|
console.log(" Run: bun run hash:update");
|
|
@@ -4424,7 +4702,7 @@ async function runDoctorCommand() {
|
|
|
4424
4702
|
} catch {}
|
|
4425
4703
|
}
|
|
4426
4704
|
try {
|
|
4427
|
-
const cacheDir =
|
|
4705
|
+
const cacheDir = join10(opencodeDir, "cache", "codegraph");
|
|
4428
4706
|
if (!existsSync8(cacheDir)) {
|
|
4429
4707
|
console.log("ℹ️ cache: no cache dir yet (ok)");
|
|
4430
4708
|
} else {
|
|
@@ -4432,7 +4710,7 @@ async function runDoctorCommand() {
|
|
|
4432
4710
|
let total = 0;
|
|
4433
4711
|
for (const f2 of files) {
|
|
4434
4712
|
try {
|
|
4435
|
-
total += statSync3(
|
|
4713
|
+
total += statSync3(join10(cacheDir, f2)).size;
|
|
4436
4714
|
} catch {}
|
|
4437
4715
|
}
|
|
4438
4716
|
const totalMB = (total / 1048576).toFixed(2);
|
|
@@ -4453,7 +4731,7 @@ async function runDoctorCommand() {
|
|
|
4453
4731
|
console.log(`⚠️ cache: check failed ${e2.message}`);
|
|
4454
4732
|
}
|
|
4455
4733
|
try {
|
|
4456
|
-
const secPath =
|
|
4734
|
+
const secPath = join10(cwd, "src", "security.ts");
|
|
4457
4735
|
if (!existsSync8(secPath)) {
|
|
4458
4736
|
console.log("⚠️ src/security.ts: missing (source-of-truth)");
|
|
4459
4737
|
hasWarn = true;
|
|
@@ -4485,8 +4763,8 @@ async function runDoctorCommand() {
|
|
|
4485
4763
|
async function runStatusCommand(args) {
|
|
4486
4764
|
const isJson = args.includes("--json");
|
|
4487
4765
|
const cwd = process.cwd();
|
|
4488
|
-
const opencodeDir = findOpenCodeDir(cwd) ||
|
|
4489
|
-
const statePath =
|
|
4766
|
+
const opencodeDir = findOpenCodeDir(cwd) || join10(cwd, ".opencode");
|
|
4767
|
+
const statePath = join10(opencodeDir, "ostacky-state.json");
|
|
4490
4768
|
if (!existsSync8(statePath)) {
|
|
4491
4769
|
console.log(isJson ? JSON.stringify({ error: "no state" }) : "No state file");
|
|
4492
4770
|
return;
|
|
@@ -4509,6 +4787,10 @@ async function runStatusCommand(args) {
|
|
|
4509
4787
|
}
|
|
4510
4788
|
}
|
|
4511
4789
|
async function main() {
|
|
4790
|
+
const needsOpencode = !cmd || cmd === "install" || cmd === "install-stack" || cmd === "update" || cmd === "add" && ["agent", "command", "skill", "mcp"].includes(subcmd ?? "");
|
|
4791
|
+
if (needsOpencode) {
|
|
4792
|
+
await ensureOpencodeInstalled();
|
|
4793
|
+
}
|
|
4512
4794
|
switch (cmd) {
|
|
4513
4795
|
case "install":
|
|
4514
4796
|
await runInstallCommand(scope);
|