ostacky 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/assets/agents/ostacky.md +78 -218
- package/assets/commands/install-stack.md +25 -0
- package/assets/commands/opsx-sync.md +2 -1
- package/assets/mcp/ask-user-server/index.js +271 -0
- package/assets/mcp/ask-user-server/package.json +6 -0
- package/assets/mcp/ostacky-controller/controller.test.mjs +447 -0
- package/assets/mcp/ostacky-controller/index.js +1017 -0
- package/assets/skills/execution-mode-evaluation/SKILL.md +45 -55
- package/assets/skills/subagent-driven-development/SKILL.md +9 -1
- package/assets/skills/writing-plans/SKILL.md +3 -19
- package/dist/cli.js +160 -34
- package/manifest.json +44 -28
- package/package.json +1 -1
|
@@ -57,21 +57,17 @@ Si el cambio tiene **≤2 tasks de implementación** Y **no comparten archivos e
|
|
|
57
57
|
|
|
58
58
|
```json
|
|
59
59
|
{
|
|
60
|
-
"
|
|
61
|
-
"confidence": 1.0,
|
|
60
|
+
"recommendation": "INLINE",
|
|
62
61
|
"reasons": ["Cambio pequeño (≤2 tasks independientes sin archivos compartidos). Inline directo."],
|
|
63
62
|
"codegraphUsed": [],
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"hasExplicitContract": false
|
|
73
|
-
},
|
|
74
|
-
"phaseRecommendations": []
|
|
63
|
+
"taskCount": <N>,
|
|
64
|
+
"sharedFiles": {},
|
|
65
|
+
"fileClusters": [<cada task como cluster propio>],
|
|
66
|
+
"clusterCount": <taskCount>,
|
|
67
|
+
"sequentialDeps": [],
|
|
68
|
+
"estLines": <estimación>,
|
|
69
|
+
"hasExplicitContract": false,
|
|
70
|
+
"filesPerTask": {}
|
|
75
71
|
}
|
|
76
72
|
```
|
|
77
73
|
|
|
@@ -296,61 +292,55 @@ f. SI NINGUNA condición anterior se cumplió
|
|
|
296
292
|
|
|
297
293
|
---
|
|
298
294
|
|
|
299
|
-
### Paso 4: Devolver decisión
|
|
295
|
+
### Paso 4: Devolver decisión al controller
|
|
300
296
|
|
|
301
|
-
El output DEBE ser un JSON válido en este formato
|
|
297
|
+
El output DEBE ser un JSON válido en este formato, que se pasa a `controller.recordExecutionAnalysis()`:
|
|
302
298
|
|
|
303
299
|
```json
|
|
304
300
|
{
|
|
305
|
-
"
|
|
306
|
-
"confidence": 0.0 - 1.0,
|
|
301
|
+
"recommendation": "INLINE" | "SUBAGENT_DRIVEN",
|
|
307
302
|
"reasons": ["razón principal", "razón secundaria"],
|
|
308
303
|
"codegraphUsed": ["codegraph_context"],
|
|
309
|
-
"
|
|
310
|
-
"
|
|
311
|
-
"
|
|
312
|
-
"sharedFiles": {
|
|
313
|
-
"src/logging.ts": ["2.2", "2.4", "2.5"]
|
|
314
|
-
},
|
|
315
|
-
"fileClusters": [
|
|
316
|
-
["2.2", "2.4", "2.5"],
|
|
317
|
-
["2.1"],
|
|
318
|
-
["2.3"],
|
|
319
|
-
["3.1"]
|
|
320
|
-
],
|
|
321
|
-
"clusterCount": 4,
|
|
322
|
-
"sequentialDeps": [],
|
|
323
|
-
"estLines": 45,
|
|
324
|
-
"hasExplicitContract": false
|
|
304
|
+
"taskCount": 13,
|
|
305
|
+
"sharedFiles": {
|
|
306
|
+
"src/logging.ts": ["2.2", "2.4", "2.5"]
|
|
325
307
|
},
|
|
326
|
-
"
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
308
|
+
"fileClusters": [
|
|
309
|
+
["2.2", "2.4", "2.5"],
|
|
310
|
+
["2.1"],
|
|
311
|
+
["2.3"],
|
|
312
|
+
["3.1"]
|
|
313
|
+
],
|
|
314
|
+
"clusterCount": 4,
|
|
315
|
+
"sequentialDeps": [],
|
|
316
|
+
"estLines": 45,
|
|
317
|
+
"hasExplicitContract": false,
|
|
318
|
+
"filesPerTask": {
|
|
319
|
+
"2.1": ["src/init.ts"],
|
|
320
|
+
"2.2": ["src/logging.ts"]
|
|
321
|
+
}
|
|
338
322
|
}
|
|
339
323
|
```
|
|
340
324
|
|
|
341
|
-
**Campos:**
|
|
325
|
+
**Campos del snapshot (contrato con controller.buildExecutionSnapshot):**
|
|
342
326
|
|
|
343
327
|
| Campo | Descripción |
|
|
344
328
|
|-------|-------------|
|
|
345
|
-
| `
|
|
346
|
-
| `
|
|
347
|
-
| `
|
|
348
|
-
| `
|
|
349
|
-
| `
|
|
350
|
-
| `
|
|
351
|
-
| `
|
|
352
|
-
| `
|
|
353
|
-
| `
|
|
329
|
+
| `recommendation` | `INLINE` o `SUBAGENT_DRIVEN`. Basado en las reglas de precedencia. |
|
|
330
|
+
| `reasons` | Array de strings. La primera razón es la principal. |
|
|
331
|
+
| `codegraphUsed` | Lista de tool calls de CodeGraph ejecutados. |
|
|
332
|
+
| `filesPerTask` | Mapa: taskId → [archivos que modifica] |
|
|
333
|
+
| `sharedFiles` | Mapa: archivo → [tasks que lo tocan] |
|
|
334
|
+
| `fileClusters` | Componentes conectados por archivos compartidos. |
|
|
335
|
+
| `taskCount` | Total de tasks de implementación. |
|
|
336
|
+
| `clusterCount` | `clusterCount == 1` → todo conectado. `== taskCount` → todo independiente. |
|
|
337
|
+
| `sequentialDeps` | Dependencias secuenciales entre tasks. |
|
|
338
|
+
| `estLines` | Estimación conservadora de líneas totales. |
|
|
339
|
+
| `hasExplicitContract` | `true` si design.md tiene contratos explícitos entre tasks. |
|
|
340
|
+
|
|
341
|
+
**⚠️ Este skill provee ANÁLISIS y RECOMENDACIÓN, no autorización.** No preguntes al usuario el modo de ejecución — eso lo hace Ostacky (el coordinador) después de recibir este snapshot y mostrarlo al usuario con `ask_user`.
|
|
342
|
+
|
|
343
|
+
**Consistencia de campos:** `recommendation` es el campo canónico para el modo. Si generaste `mode` en versiones anteriores, migrá a `recommendation` para compatibilidad con el controller.
|
|
354
344
|
|
|
355
345
|
---
|
|
356
346
|
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: subagent-driven-development
|
|
3
|
-
description: Use when
|
|
3
|
+
description: Use when Ostacky has confirmed subagent-driven execution mode and dispatches you with clusters of independent tasks
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Subagent-Driven Development
|
|
7
7
|
|
|
8
|
+
**Prerequisite:** This skill runs AFTER the coordinator (Ostacky) has:
|
|
9
|
+
1. Performed execution analysis (via `execution-mode-evaluation` skill)
|
|
10
|
+
2. Presented evidence and recommendation to the user
|
|
11
|
+
3. Received explicit user confirmation for subagent-driven mode
|
|
12
|
+
4. Called `controller.consumeExecutionDecision({ mode: "SUBAGENT_DRIVEN" })`
|
|
13
|
+
|
|
14
|
+
Subagents are execution-only. They never create routing decisions, re-analyze execution mode, or duplicate discovery that the coordinator already resolved.
|
|
15
|
+
|
|
8
16
|
Execute plan by dispatching fresh subagent per task, with two-stage review after each: spec compliance review first, then code quality review.
|
|
9
17
|
|
|
10
18
|
**Why subagents:** You delegate tasks to specialized agents with isolated context. By precisely crafting their instructions and context, you ensure they stay focused and succeed at their task. They should never inherit your session's context or history — you construct exactly what they need. This also preserves your own context for coordination work.
|
|
@@ -49,7 +49,7 @@ This structure informs the task decomposition. Each task should produce self-con
|
|
|
49
49
|
```markdown
|
|
50
50
|
# [Feature Name] Implementation Plan
|
|
51
51
|
|
|
52
|
-
> **For agentic workers:**
|
|
52
|
+
> **For agentic workers:** Tasks use checkbox (`- [ ]`) syntax for tracking. Execution mode is determined by the coordinator after plan completion.
|
|
53
53
|
|
|
54
54
|
**Goal:** [One sentence describing what this builds]
|
|
55
55
|
|
|
@@ -131,22 +131,6 @@ After writing the complete plan, look at the spec with fresh eyes and check the
|
|
|
131
131
|
|
|
132
132
|
If you find issues, fix them inline. No need to re-review — just fix and move on. If you find a spec requirement with no task, add the task.
|
|
133
133
|
|
|
134
|
-
## Execution
|
|
134
|
+
## Execution
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
**"Plan complete and saved to `docs/superpowers/plans/<filename>.md`. Two execution options:**
|
|
139
|
-
|
|
140
|
-
**1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration
|
|
141
|
-
|
|
142
|
-
**2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints
|
|
143
|
-
|
|
144
|
-
**Which approach?"**
|
|
145
|
-
|
|
146
|
-
**If Subagent-Driven chosen:**
|
|
147
|
-
- **REQUIRED SUB-SKILL:** Use superpowers:subagent-driven-development
|
|
148
|
-
- Fresh subagent per task + two-stage review
|
|
149
|
-
|
|
150
|
-
**If Inline Execution chosen:**
|
|
151
|
-
- **REQUIRED SUB-SKILL:** Use superpowers:executing-plans
|
|
152
|
-
- Batch execution with checkpoints for review
|
|
136
|
+
Ostacky (the coordinator) handles execution-mode analysis and user confirmation after the plan is ready. This skill only writes the plan — it does NOT offer the user an execution mode choice. If an execution-mode prompt appears, defer to the coordinator.
|
package/dist/cli.js
CHANGED
|
@@ -161,7 +161,7 @@ var require_picocolors = __commonJS((exports, module) => {
|
|
|
161
161
|
// package.json
|
|
162
162
|
var package_default = {
|
|
163
163
|
name: "ostacky",
|
|
164
|
-
version: "0.
|
|
164
|
+
version: "0.5.0",
|
|
165
165
|
description: "Instalador interactivo de agentes y comandos para OpenCode",
|
|
166
166
|
type: "module",
|
|
167
167
|
bin: {
|
|
@@ -876,110 +876,126 @@ var L2 = () => {
|
|
|
876
876
|
import { join as join5 } from "path";
|
|
877
877
|
// manifest.json
|
|
878
878
|
var manifest_default = {
|
|
879
|
-
version: "0.
|
|
879
|
+
version: "0.5.0",
|
|
880
880
|
repo: "JaimeHoracio/Ostacky",
|
|
881
|
-
tag: "v0.
|
|
881
|
+
tag: "v0.5.0",
|
|
882
882
|
agents: [
|
|
883
883
|
{
|
|
884
884
|
name: "ostacky",
|
|
885
885
|
file: "assets/agents/ostacky.md",
|
|
886
|
-
description: "
|
|
887
|
-
version: "0.
|
|
888
|
-
sha256:
|
|
886
|
+
description: "Orquestador principal con ruteo por nivel de impacto, máquina de estados persistida (controller MCP), edición segura con 3 outcomes, y delegación en OpenSpec + Superpowers",
|
|
887
|
+
version: "0.5.0",
|
|
888
|
+
sha256: "355c729ee13f0cd99b9a16e0c203dd83808ec9fb5a764012521794011a91a9f1"
|
|
889
889
|
}
|
|
890
890
|
],
|
|
891
891
|
commands: [
|
|
892
892
|
{
|
|
893
893
|
name: "install-stack",
|
|
894
894
|
file: "assets/commands/install-stack.md",
|
|
895
|
-
description: "Instala el stack tecnológico del proyecto",
|
|
896
|
-
version: "0.
|
|
897
|
-
sha256:
|
|
895
|
+
description: "Instala el stack tecnológico del proyecto (CodeGraph, skills, OpenSpec, Engram, Context7, controller MCP)",
|
|
896
|
+
version: "0.5.0",
|
|
897
|
+
sha256: "e8b75efe505766d47dd4d38cfb055b6801552cd457d99bce018c196a346e4560"
|
|
898
898
|
},
|
|
899
899
|
{
|
|
900
900
|
name: "opsx-sync",
|
|
901
901
|
file: "assets/commands/opsx-sync.md",
|
|
902
|
-
description: "Sincroniza delta specs del change activo
|
|
903
|
-
version: "0.
|
|
904
|
-
sha256:
|
|
902
|
+
description: "Sincroniza delta specs del change activo sin inicializar CodeGraph si ya existe índice",
|
|
903
|
+
version: "0.5.0",
|
|
904
|
+
sha256: "ed9948f1910743b672e1dfad496bc96972a224a98b063232be39290d43068c50"
|
|
905
|
+
}
|
|
906
|
+
],
|
|
907
|
+
mcpServers: [
|
|
908
|
+
{
|
|
909
|
+
name: "ask-user",
|
|
910
|
+
file: "assets/mcp/ask-user-server/",
|
|
911
|
+
description: "MCP server con tool ask_user para consultar al usuario con bloqueo de ejecución",
|
|
912
|
+
version: "0.5.0",
|
|
913
|
+
sha256: "74deeadfde30dfd9e3fe5e6fb75ab9e024aa7150a40518b2f40354c392e8fb5d"
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
name: "ostacky-controller",
|
|
917
|
+
file: "assets/mcp/ostacky-controller/",
|
|
918
|
+
description: "Máquina de estados persistida para Ostacky: validación de transiciones, consumo de decisiones, autorización de side effects, edición segura y snapshots reutilizables",
|
|
919
|
+
version: "0.5.0",
|
|
920
|
+
sha256: "7652bb70376a71157c35dda5a5c24f3671b0cf5922b2fef1065a40e7600d694e"
|
|
905
921
|
}
|
|
906
922
|
],
|
|
907
923
|
skills: [
|
|
908
924
|
{
|
|
909
925
|
name: "execution-mode-evaluation",
|
|
910
926
|
file: "assets/skills/execution-mode-evaluation/SKILL.md",
|
|
911
|
-
description: "Skill de
|
|
912
|
-
version: "0.
|
|
913
|
-
sha256: "
|
|
927
|
+
description: "Skill de análisis de modo de ejecución — output reconciliado con controller snapshot contract (recommendation field)",
|
|
928
|
+
version: "0.5.0",
|
|
929
|
+
sha256: "0bf115b939c906e276040a9bd741c1b84d658da29e3c3fa2cb5e74e4c72792a1"
|
|
914
930
|
},
|
|
915
931
|
{
|
|
916
932
|
name: "brainstorming",
|
|
917
933
|
file: "assets/skills/brainstorming/SKILL.md",
|
|
918
934
|
description: "Skill de descubrimiento y exploración (Superpowers)",
|
|
919
|
-
version: "0.
|
|
935
|
+
version: "0.5.0",
|
|
920
936
|
sha256: "ad15458c54912007ae3f708c698a11f0a4a58e2a7c7860a1ae8874db573c9e98"
|
|
921
937
|
},
|
|
922
938
|
{
|
|
923
939
|
name: "writing-plans",
|
|
924
940
|
file: "assets/skills/writing-plans/SKILL.md",
|
|
925
|
-
description: "Skill de planificación de implementación (Superpowers)",
|
|
926
|
-
version: "0.
|
|
927
|
-
sha256: "
|
|
941
|
+
description: "Skill de planificación de implementación (Superpowers) — execution handoff removido, Ostacky decide modo de ejecución",
|
|
942
|
+
version: "0.5.0",
|
|
943
|
+
sha256: "408bca4652677d59bb35cdbe7459b01555b4ca11bf44217d95f98f3641af88b2"
|
|
928
944
|
},
|
|
929
945
|
{
|
|
930
946
|
name: "tdd",
|
|
931
947
|
file: "assets/skills/tdd/SKILL.md",
|
|
932
948
|
description: "Skill de test-driven development (Superpowers)",
|
|
933
|
-
version: "0.
|
|
949
|
+
version: "0.5.0",
|
|
934
950
|
sha256: "7eacd8ee81dc5c0b85065c0392e37ee10314661f7edb59dd8e70a9e0dae8f371"
|
|
935
951
|
},
|
|
936
952
|
{
|
|
937
953
|
name: "subagent-driven-development",
|
|
938
954
|
file: "assets/skills/subagent-driven-development/SKILL.md",
|
|
939
|
-
description: "Skill de ejecución con subagentes (Superpowers)",
|
|
940
|
-
version: "0.
|
|
941
|
-
sha256: "
|
|
955
|
+
description: "Skill de ejecución con subagentes (Superpowers) — ejecuta solo después de confirmación del coordinador Ostacky",
|
|
956
|
+
version: "0.5.0",
|
|
957
|
+
sha256: "2d0c880deae34ab6882c93f6c8de666013498f0e84d7741116dd0835e2780a1b"
|
|
942
958
|
},
|
|
943
959
|
{
|
|
944
960
|
name: "dispatching-parallel-agents",
|
|
945
961
|
file: "assets/skills/dispatching-parallel-agents/SKILL.md",
|
|
946
962
|
description: "Skill de dispatch paralelo de agentes (Superpowers)",
|
|
947
|
-
version: "0.
|
|
963
|
+
version: "0.5.0",
|
|
948
964
|
sha256: "281edf0c38f358497c7e2066fa8217a2ba3e2a39b4205c4af0c41d328fc035a1"
|
|
949
965
|
},
|
|
950
966
|
{
|
|
951
967
|
name: "review",
|
|
952
968
|
file: "assets/skills/review/SKILL.md",
|
|
953
969
|
description: "Skill de revisión de código (Superpowers)",
|
|
954
|
-
version: "0.
|
|
970
|
+
version: "0.5.0",
|
|
955
971
|
sha256: "c78d3beff45e9bb6b5e9e1d7155910f379e082d7aaf3c61b90d651d09e0d2e2a"
|
|
956
972
|
},
|
|
957
973
|
{
|
|
958
974
|
name: "openspec-explore",
|
|
959
975
|
file: "assets/skills/openspec-explore/SKILL.md",
|
|
960
976
|
description: "Skill de exploración pre-spec (OpenSpec)",
|
|
961
|
-
version: "0.
|
|
977
|
+
version: "0.5.0",
|
|
962
978
|
sha256: "1c75cab8672c2eb8b12f27a0b31f0b7fdb73c21c72583c1e8a566e09fe4108a6"
|
|
963
979
|
},
|
|
964
980
|
{
|
|
965
981
|
name: "openspec-propose",
|
|
966
982
|
file: "assets/skills/openspec-propose/SKILL.md",
|
|
967
983
|
description: "Skill de generación de proposal (OpenSpec)",
|
|
968
|
-
version: "0.
|
|
984
|
+
version: "0.5.0",
|
|
969
985
|
sha256: "ab88e9aa22db09559ccf7987d08adc0469b7c6fbe46498e63a3b64926f19e66b"
|
|
970
986
|
},
|
|
971
987
|
{
|
|
972
988
|
name: "openspec-apply-change",
|
|
973
989
|
file: "assets/skills/openspec-apply-change/SKILL.md",
|
|
974
990
|
description: "Skill de aplicación de change (OpenSpec)",
|
|
975
|
-
version: "0.
|
|
991
|
+
version: "0.5.0",
|
|
976
992
|
sha256: "847c2f88a66e8d5946c1fec422a5367c89eca5340839b659bbaaeb6f0bdac487"
|
|
977
993
|
},
|
|
978
994
|
{
|
|
979
995
|
name: "openspec-archive-change",
|
|
980
996
|
file: "assets/skills/openspec-archive-change/SKILL.md",
|
|
981
997
|
description: "Skill de archivo de change (OpenSpec)",
|
|
982
|
-
version: "0.
|
|
998
|
+
version: "0.5.0",
|
|
983
999
|
sha256: "3d35905ec40a081b8fd77b144426e540c67b1f50cc5801b655313a4fdfaa087a"
|
|
984
1000
|
}
|
|
985
1001
|
]
|
|
@@ -1053,6 +1069,11 @@ function getBundledSkillPath(name) {
|
|
|
1053
1069
|
validateFilePath(name);
|
|
1054
1070
|
return join2(BUNDLED_SKILLS_DIR, name);
|
|
1055
1071
|
}
|
|
1072
|
+
var BUNDLED_MCP_DIR = join2(PACKAGE_ROOT, "assets", "mcp");
|
|
1073
|
+
function getBundledMcpPath(name) {
|
|
1074
|
+
validateFilePath(name);
|
|
1075
|
+
return join2(BUNDLED_MCP_DIR, name);
|
|
1076
|
+
}
|
|
1056
1077
|
function getRawUrl(repo, tag, path) {
|
|
1057
1078
|
return `${GITHUB_RAW}/${repo}/${tag}/${path}`;
|
|
1058
1079
|
}
|
|
@@ -1094,7 +1115,7 @@ async function fetchLatestManifest() {
|
|
|
1094
1115
|
}
|
|
1095
1116
|
async function downloadFile(manifest, filePath) {
|
|
1096
1117
|
validateFilePath(filePath);
|
|
1097
|
-
const item = [...manifest.agents, ...manifest.commands, ...manifest.skills ?? []].find((i) => i.file === filePath);
|
|
1118
|
+
const item = [...manifest.agents, ...manifest.commands, ...manifest.skills ?? [], ...manifest.mcpServers ?? []].find((i) => i.file === filePath);
|
|
1098
1119
|
const expectedHash = item?.sha256 ?? null;
|
|
1099
1120
|
const cached = getCached(manifest.repo, manifest.tag, filePath, expectedHash);
|
|
1100
1121
|
if (cached !== null)
|
|
@@ -1165,19 +1186,21 @@ function clearLockfile(opencodeRoot) {
|
|
|
1165
1186
|
const lockfile = readLockfile(opencodeRoot);
|
|
1166
1187
|
if (!lockfile) {
|
|
1167
1188
|
writeLockfile(opencodeRoot, {
|
|
1168
|
-
version: "0.
|
|
1189
|
+
version: "0.5.0",
|
|
1169
1190
|
lockedAt: new Date().toISOString(),
|
|
1170
1191
|
repo: "",
|
|
1171
1192
|
tag: "",
|
|
1172
1193
|
agents: {},
|
|
1173
1194
|
commands: {},
|
|
1174
|
-
skills: {}
|
|
1195
|
+
skills: {},
|
|
1196
|
+
mcpServers: {}
|
|
1175
1197
|
});
|
|
1176
1198
|
return;
|
|
1177
1199
|
}
|
|
1178
1200
|
lockfile.agents = {};
|
|
1179
1201
|
lockfile.commands = {};
|
|
1180
1202
|
lockfile.skills = {};
|
|
1203
|
+
lockfile.mcpServers = {};
|
|
1181
1204
|
lockfile.lockedAt = new Date().toISOString();
|
|
1182
1205
|
writeLockfile(opencodeRoot, lockfile);
|
|
1183
1206
|
}
|
|
@@ -1272,6 +1295,8 @@ function upsertLockfile(paths, type, item, manifest, contentHash) {
|
|
|
1272
1295
|
};
|
|
1273
1296
|
if (!existing.skills)
|
|
1274
1297
|
existing.skills = {};
|
|
1298
|
+
if (!existing.mcpServers)
|
|
1299
|
+
existing.mcpServers = {};
|
|
1275
1300
|
existing[type][item.name] = {
|
|
1276
1301
|
version: item.version,
|
|
1277
1302
|
installedAt: new Date().toISOString(),
|
|
@@ -1310,6 +1335,51 @@ async function installSkill(item, manifest, paths) {
|
|
|
1310
1335
|
copyDirRecursive(src, dest);
|
|
1311
1336
|
upsertLockfile(paths, "skills", item, manifest, treeHash);
|
|
1312
1337
|
}
|
|
1338
|
+
async function installMcpServer(item, manifest, paths) {
|
|
1339
|
+
const src = getBundledMcpPath(item.name);
|
|
1340
|
+
if (!existsSync3(src)) {
|
|
1341
|
+
throw new Error(`MCP server bundleado no encontrado: ${src}. ` + `¿Falta el directorio assets/mcp/${item.name}/?`);
|
|
1342
|
+
}
|
|
1343
|
+
const treeHash = computeTreeHash(src);
|
|
1344
|
+
if (item.sha256 && treeHash !== item.sha256) {
|
|
1345
|
+
throw new Error(`Tree hash inválido para MCP server "${item.name}"
|
|
1346
|
+
` + ` esperado: ${item.sha256}
|
|
1347
|
+
` + ` recibido: ${treeHash}`);
|
|
1348
|
+
}
|
|
1349
|
+
const dest = join4(paths.root, "mcp", item.name);
|
|
1350
|
+
if (existsSync3(dest)) {
|
|
1351
|
+
rmSync(dest, { recursive: true, force: true });
|
|
1352
|
+
}
|
|
1353
|
+
copyDirRecursive(src, dest);
|
|
1354
|
+
upsertLockfile(paths, "mcpServers", item, manifest, treeHash);
|
|
1355
|
+
const projectRoot = findProjectRoot();
|
|
1356
|
+
const configPath = findOpenCodeConfig(projectRoot);
|
|
1357
|
+
if (configPath) {
|
|
1358
|
+
const raw = readFileSync3(configPath, "utf-8");
|
|
1359
|
+
const config = JSON.parse(raw);
|
|
1360
|
+
if (!config.mcp)
|
|
1361
|
+
config.mcp = {};
|
|
1362
|
+
config.mcp[item.name] = {
|
|
1363
|
+
type: "local",
|
|
1364
|
+
command: ["node", `.opencode/mcp/${item.name}/index.js`],
|
|
1365
|
+
enabled: true
|
|
1366
|
+
};
|
|
1367
|
+
writeFileSync3(configPath, JSON.stringify(config, null, 2) + `
|
|
1368
|
+
`, "utf-8");
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
function findOpenCodeConfig(projectRoot) {
|
|
1372
|
+
const candidates = ["opencode.json", "opencode.jsonc"];
|
|
1373
|
+
for (const name of candidates) {
|
|
1374
|
+
const full = join4(projectRoot, name);
|
|
1375
|
+
if (existsSync3(full))
|
|
1376
|
+
return full;
|
|
1377
|
+
}
|
|
1378
|
+
return null;
|
|
1379
|
+
}
|
|
1380
|
+
function isMcpServerInstalled(name, paths) {
|
|
1381
|
+
return existsSync3(join4(paths.root, "mcp", name, "index.js"));
|
|
1382
|
+
}
|
|
1313
1383
|
function isAgentInstalled(name, paths) {
|
|
1314
1384
|
return existsSync3(join4(paths.agents, `${name}.md`));
|
|
1315
1385
|
}
|
|
@@ -1355,6 +1425,34 @@ function uninstallSkill(name, paths) {
|
|
|
1355
1425
|
removeFromLockfile(paths.root, "skills", name);
|
|
1356
1426
|
return true;
|
|
1357
1427
|
}
|
|
1428
|
+
function uninstallMcpServer(name, paths) {
|
|
1429
|
+
const dirPath = join4(paths.root, "mcp", name);
|
|
1430
|
+
try {
|
|
1431
|
+
if (existsSync3(dirPath)) {
|
|
1432
|
+
rmSync(dirPath, { recursive: true, force: true });
|
|
1433
|
+
}
|
|
1434
|
+
} catch {
|
|
1435
|
+
return false;
|
|
1436
|
+
}
|
|
1437
|
+
const projectRoot = findProjectRoot();
|
|
1438
|
+
const configPath = findOpenCodeConfig(projectRoot);
|
|
1439
|
+
if (configPath) {
|
|
1440
|
+
try {
|
|
1441
|
+
const raw = readFileSync3(configPath, "utf-8");
|
|
1442
|
+
const config = JSON.parse(raw);
|
|
1443
|
+
if (config.mcp && config.mcp[name]) {
|
|
1444
|
+
delete config.mcp[name];
|
|
1445
|
+
if (Object.keys(config.mcp).length === 0) {
|
|
1446
|
+
delete config.mcp;
|
|
1447
|
+
}
|
|
1448
|
+
writeFileSync3(configPath, JSON.stringify(config, null, 2) + `
|
|
1449
|
+
`, "utf-8");
|
|
1450
|
+
}
|
|
1451
|
+
} catch {}
|
|
1452
|
+
}
|
|
1453
|
+
removeFromLockfile(paths.root, "mcpServers", name);
|
|
1454
|
+
return true;
|
|
1455
|
+
}
|
|
1358
1456
|
function uninstallAll(paths) {
|
|
1359
1457
|
const lockfile = readLockfile(paths.root);
|
|
1360
1458
|
if (!lockfile)
|
|
@@ -1368,6 +1466,9 @@ function uninstallAll(paths) {
|
|
|
1368
1466
|
for (const name of Object.keys(lockfile.skills ?? {})) {
|
|
1369
1467
|
uninstallSkill(name, paths);
|
|
1370
1468
|
}
|
|
1469
|
+
for (const name of Object.keys(lockfile.mcpServers ?? {})) {
|
|
1470
|
+
uninstallMcpServer(name, paths);
|
|
1471
|
+
}
|
|
1371
1472
|
clearLockfile(paths.root);
|
|
1372
1473
|
}
|
|
1373
1474
|
function isCommandAvailable(cmd) {
|
|
@@ -1659,6 +1760,14 @@ function getUpdateCandidates(manifest, paths) {
|
|
|
1659
1760
|
candidates.push({ type: "skills", item, installedVersion: installed });
|
|
1660
1761
|
}
|
|
1661
1762
|
}
|
|
1763
|
+
for (const item of manifest.mcpServers ?? []) {
|
|
1764
|
+
if (!isMcpServerInstalled(item.name, paths))
|
|
1765
|
+
continue;
|
|
1766
|
+
const installed = getInstalledVersion(lockfile, "mcpServers", item.name);
|
|
1767
|
+
if (installed !== item.version) {
|
|
1768
|
+
candidates.push({ type: "mcpServers", item, installedVersion: installed });
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1662
1771
|
return candidates;
|
|
1663
1772
|
}
|
|
1664
1773
|
function formatVersionDiff(from, to) {
|
|
@@ -1672,6 +1781,8 @@ function kindLabel(type) {
|
|
|
1672
1781
|
return "command";
|
|
1673
1782
|
case "skills":
|
|
1674
1783
|
return "skill";
|
|
1784
|
+
case "mcpServers":
|
|
1785
|
+
return "MCP";
|
|
1675
1786
|
}
|
|
1676
1787
|
}
|
|
1677
1788
|
async function doInstallAll(manifest, paths) {
|
|
@@ -1707,6 +1818,16 @@ async function doInstallAll(manifest, paths) {
|
|
|
1707
1818
|
errors++;
|
|
1708
1819
|
}
|
|
1709
1820
|
}
|
|
1821
|
+
for (const mcp of manifest.mcpServers ?? []) {
|
|
1822
|
+
spin.start(`Instalando MCP server: ${mcp.name} (${mcp.version})`);
|
|
1823
|
+
try {
|
|
1824
|
+
await installMcpServer(mcp, manifest, paths);
|
|
1825
|
+
spin.stop(`MCP server instalado: ${mcp.name} (${mcp.version})`);
|
|
1826
|
+
} catch (e2) {
|
|
1827
|
+
spin.stop(`Error en ${mcp.name}: ${e2.message}`);
|
|
1828
|
+
errors++;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1710
1831
|
if (errors === 0) {
|
|
1711
1832
|
v2.info("Instalando stack de herramientas...");
|
|
1712
1833
|
await doInstallStack();
|
|
@@ -1834,8 +1955,10 @@ async function doUpdate(manifest, paths) {
|
|
|
1834
1955
|
await installAgent(item, manifest, paths);
|
|
1835
1956
|
} else if (type === "commands") {
|
|
1836
1957
|
await installCommand(item, manifest, paths);
|
|
1837
|
-
} else {
|
|
1958
|
+
} else if (type === "skills") {
|
|
1838
1959
|
await installSkill(item, manifest, paths);
|
|
1960
|
+
} else {
|
|
1961
|
+
await installMcpServer(item, manifest, paths);
|
|
1839
1962
|
}
|
|
1840
1963
|
spin.stop(`Actualizado: ${item.name} (${item.version})`);
|
|
1841
1964
|
updated++;
|
|
@@ -1963,7 +2086,7 @@ async function runUpdateCommand() {
|
|
|
1963
2086
|
}
|
|
1964
2087
|
async function doUninstallTotal(paths) {
|
|
1965
2088
|
const lockfile = readLockfile(paths.root);
|
|
1966
|
-
if (!lockfile || Object.keys(lockfile.agents).length === 0 && Object.keys(lockfile.commands).length === 0 && Object.keys(lockfile.skills ?? {}).length === 0) {
|
|
2089
|
+
if (!lockfile || Object.keys(lockfile.agents).length === 0 && Object.keys(lockfile.commands).length === 0 && Object.keys(lockfile.skills ?? {}).length === 0 && Object.keys(lockfile.mcpServers ?? {}).length === 0) {
|
|
1967
2090
|
v2.info("No hay nada instalado.");
|
|
1968
2091
|
return;
|
|
1969
2092
|
}
|
|
@@ -1977,6 +2100,9 @@ async function doUninstallTotal(paths) {
|
|
|
1977
2100
|
for (const name of Object.keys(lockfile.skills ?? {})) {
|
|
1978
2101
|
pathsToDelete.push(join5(paths.skills, name));
|
|
1979
2102
|
}
|
|
2103
|
+
for (const name of Object.keys(lockfile.mcpServers ?? {})) {
|
|
2104
|
+
pathsToDelete.push(join5(paths.root, "mcp", name));
|
|
2105
|
+
}
|
|
1980
2106
|
ye(pathsToDelete.join(`
|
|
1981
2107
|
`), `Se eliminarán ${pathsToDelete.length} archivo(s) / directorio(s)`);
|
|
1982
2108
|
const confirm = await me({
|