orquestra-mcp 1.44.0 → 1.46.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 +55 -24
- package/bin/orquestra-mcp.mjs +3 -3
- package/package.json +2 -2
- package/src/api.mjs +8 -5
- package/src/configFile.mjs +20 -6
- package/src/platformPlaybooks.mjs +1 -1
- package/src/server.mjs +53 -3
- package/src/setup.mjs +75 -29
package/README.md
CHANGED
|
@@ -12,25 +12,34 @@ configurar/sincronizar un proyecto completo sin crear duplicados.
|
|
|
12
12
|
Ver `CLAUDE.md` para contexto de desarrollo. Ver la memoria del proyecto
|
|
13
13
|
`orquestra-mcp-api-plan` para las 8 fases del plan MCP/API completo.
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Empezar (macOS, Linux y Windows)
|
|
16
|
+
|
|
17
|
+
Necesitas una cuenta en [app.orquestra.me](https://app.orquestra.me) y Node 18+.
|
|
18
|
+
|
|
19
|
+
**1. Conecta tu cuenta y tu proyecto** — desde la carpeta de tu proyecto:
|
|
16
20
|
|
|
17
21
|
```bash
|
|
18
|
-
|
|
19
|
-
node bin/orquestra-mcp.mjs setup
|
|
22
|
+
npx orquestra-mcp@latest setup
|
|
20
23
|
```
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
Abre el navegador para confirmar tu sesión (Google o email/password) y luego:
|
|
26
|
+
|
|
27
|
+
- Si **no tienes workspace**, te pide su nombre y después el de tu primer proyecto.
|
|
28
|
+
- Si **perteneces a varios workspaces**, te deja elegir en cuál trabajar (elegir aquí
|
|
29
|
+
no cambia el que tienes abierto en la web).
|
|
30
|
+
- Eliges un proyecto de ese workspace o creas uno nuevo.
|
|
31
|
+
|
|
32
|
+
La conexión se guarda **por carpeta** en `~/.orquestra/mcp-config.json`: puedes tener
|
|
33
|
+
distintos proyectos (o cuentas) en distintas carpetas. Para conectar otra carpeta o
|
|
34
|
+
cambiar de proyecto, vuelve a correr el setup ahí.
|
|
24
35
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- Si **ya tienes** uno o varios: te deja elegir cuál usar por default
|
|
29
|
-
desde la misma terminal.
|
|
36
|
+
**2. Regístralo en tu agente** (ver abajo: Claude Code, Claude Desktop, Cursor, Codex,
|
|
37
|
+
Gemini CLI). Usa `orquestra-mcp@latest` para que `npx` no se quede con una versión vieja
|
|
38
|
+
de su caché.
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
(
|
|
40
|
+
**Ver la versión / actualizar:** `npm view orquestra-mcp version` muestra la última
|
|
41
|
+
publicada. Con `@latest` cada arranque la usa; el servidor que ya está corriendo conserva
|
|
42
|
+
la suya hasta que reconectas (en Claude Code: `/mcp` → reconectar).
|
|
34
43
|
|
|
35
44
|
Variable opcional `ORQUESTRA_WEB_URL` para apuntar el login a otro lugar
|
|
36
45
|
(ej. `http://localhost:5173` mientras desarrollas `orquestra-web` local en
|
|
@@ -71,35 +80,57 @@ Variables de entorno (todas opcionales si ya corriste `setup` — se leen de
|
|
|
71
80
|
|
|
72
81
|
### Claude Code
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
macOS / Linux:
|
|
75
84
|
|
|
76
85
|
```bash
|
|
77
|
-
claude mcp add orquestra --
|
|
86
|
+
claude mcp add orquestra -- npx -y orquestra-mcp@latest
|
|
78
87
|
```
|
|
79
88
|
|
|
80
|
-
|
|
89
|
+
Windows (PowerShell) — `npx` necesita `cmd /c` delante:
|
|
81
90
|
|
|
82
|
-
```
|
|
83
|
-
claude mcp add orquestra
|
|
84
|
-
-e ORQUESTRA_API_TOKEN=orq_... \
|
|
85
|
-
-e ORQUESTRA_WORKSPACE_ID=... \
|
|
86
|
-
-- node /ruta/absoluta/a/orquestra-mcp/bin/orquestra-mcp.mjs
|
|
91
|
+
```powershell
|
|
92
|
+
claude mcp add orquestra -- cmd /c npx -y orquestra-mcp@latest
|
|
87
93
|
```
|
|
88
94
|
|
|
95
|
+
Agrega `--scope user` después de `add` para tenerlo en todos tus proyectos. Si PowerShell
|
|
96
|
+
dice que `claude` no se reconoce, falta instalar Claude Code:
|
|
97
|
+
`npm install -g @anthropic-ai/claude-code` (y vuelve a abrir la terminal). Si ya lo habías
|
|
98
|
+
agregado sin `@latest`, primero `claude mcp remove orquestra`.
|
|
99
|
+
|
|
89
100
|
### Claude Desktop (`claude_desktop_config.json`)
|
|
90
101
|
|
|
102
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
103
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
104
|
+
|
|
91
105
|
```json
|
|
92
106
|
{
|
|
93
107
|
"mcpServers": {
|
|
94
108
|
"orquestra": {
|
|
95
|
-
"command": "
|
|
96
|
-
"args": ["
|
|
109
|
+
"command": "npx",
|
|
110
|
+
"args": ["-y", "orquestra-mcp@latest"]
|
|
97
111
|
}
|
|
98
112
|
}
|
|
99
113
|
}
|
|
100
114
|
```
|
|
101
115
|
|
|
102
|
-
|
|
116
|
+
Cierra la app por completo (en Windows también desde el ícono junto al reloj) y vuelve a
|
|
117
|
+
abrirla.
|
|
118
|
+
|
|
119
|
+
### Cursor, Codex CLI, Gemini CLI
|
|
120
|
+
|
|
121
|
+
Mismo comando (`npx -y orquestra-mcp@latest`), cambia el archivo — ver
|
|
122
|
+
[orquestra.me/mcp#instalacion](https://www.orquestra.me/mcp#instalacion). En Windows, si
|
|
123
|
+
el agente no encuentra `npx`, usa `"command": "cmd"` con
|
|
124
|
+
`"args": ["/c", "npx", "-y", "orquestra-mcp@latest"]`.
|
|
125
|
+
|
|
126
|
+
### Sin `setup` (variables de entorno)
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
claude mcp add orquestra \
|
|
130
|
+
-e ORQUESTRA_API_TOKEN=orq_... \
|
|
131
|
+
-e ORQUESTRA_WORKSPACE_ID=... \
|
|
132
|
+
-- npx -y orquestra-mcp@latest
|
|
133
|
+
```
|
|
103
134
|
|
|
104
135
|
## Tools
|
|
105
136
|
|
package/bin/orquestra-mcp.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
3
|
-
import { createServer } from '../src/server.mjs'
|
|
3
|
+
import { createServer, createSetupNeededServer } from '../src/server.mjs'
|
|
4
4
|
import { runSetup } from '../src/setup.mjs'
|
|
5
5
|
import { runVaultSync } from '../src/vaultSync.mjs'
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ const HELP_TEXT = `orquestra-mcp -- Servidor MCP de Orquestra (132 tools)
|
|
|
9
9
|
Uso:
|
|
10
10
|
npx orquestra-mcp Inicia el servidor MCP por stdio -- lo usa tu cliente
|
|
11
11
|
MCP (Claude Code, Claude Desktop, etc.), no se corre a mano
|
|
12
|
-
npx orquestra-mcp setup Login por navegador +
|
|
12
|
+
npx orquestra-mcp setup Login por navegador + conecta esta carpeta a un proyecto
|
|
13
13
|
npx orquestra-mcp vault sync <carpeta> [--apply]
|
|
14
14
|
Sincroniza la base de conocimiento del workspace con una
|
|
15
15
|
carpeta de tu vault de Obsidian (la 1ra vez solo muestra el plan)
|
|
@@ -50,7 +50,7 @@ async function main() {
|
|
|
50
50
|
server = createServer()
|
|
51
51
|
} catch (err) {
|
|
52
52
|
console.error(`[orquestra-mcp] ${err.message}`)
|
|
53
|
-
|
|
53
|
+
server = createSetupNeededServer(err.message)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const transport = new StdioServerTransport()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orquestra-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.46.1",
|
|
4
4
|
"description": "Servidor MCP de Orquestra -- 135 tools para leer y escribir tareas, ideas, changelog, infraestructura, contenido, QA y más desde un agente de IA",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=18"
|
|
17
17
|
},
|
|
18
|
-
"homepage": "https://orquestra.me/mcp",
|
|
18
|
+
"homepage": "https://www.orquestra.me/mcp",
|
|
19
19
|
"keywords": [
|
|
20
20
|
"mcp",
|
|
21
21
|
"model-context-protocol",
|
package/src/api.mjs
CHANGED
|
@@ -27,12 +27,15 @@ export function loadConfig() {
|
|
|
27
27
|
if (!apiToken) missing.push('ORQUESTRA_API_TOKEN')
|
|
28
28
|
if (!workspaceId) missing.push('ORQUESTRA_WORKSPACE_ID')
|
|
29
29
|
if (missing.length) {
|
|
30
|
+
const folders = Object.keys(fileConfig?.projects || {})
|
|
30
31
|
throw new Error(
|
|
31
|
-
`
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
`
|
|
32
|
+
`Esta carpeta (${process.cwd()}) no está conectada a Orquestra. ` +
|
|
33
|
+
(folders.length
|
|
34
|
+
? `Carpetas conectadas en ${CONFIG_PATH}: ${folders.join(' | ')}. `
|
|
35
|
+
: `No hay ninguna carpeta conectada (${CONFIG_PATH} no existe o está vacío). `) +
|
|
36
|
+
`Solución: abre una terminal en esta carpeta y corre "npx orquestra-mcp@latest setup" ` +
|
|
37
|
+
`(o ábrela desde una carpeta que ya esté conectada), y después reconecta el MCP. ` +
|
|
38
|
+
`Alternativa sin setup: variables de entorno ${missing.join(' y ')}.`
|
|
36
39
|
)
|
|
37
40
|
}
|
|
38
41
|
|
package/src/configFile.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
2
2
|
import { homedir } from 'os'
|
|
3
|
-
import { join, dirname,
|
|
3
|
+
import { join, dirname, win32, posix } from 'path'
|
|
4
4
|
|
|
5
5
|
export const CONFIG_PATH = join(homedir(), '.orquestra', 'mcp-config.json')
|
|
6
6
|
|
|
@@ -63,13 +63,27 @@ export function upsertProjectMapping({ apiBase, apiToken, workspaceId, folderPat
|
|
|
63
63
|
// gana, así que un repo individual con su propio mapeo (y su propia cuenta)
|
|
64
64
|
// puede sobreescribir el de la carpeta contenedora que lo engloba. Devuelve
|
|
65
65
|
// el objeto {apiBase, apiToken, workspaceId, projectId} de esa carpeta.
|
|
66
|
-
|
|
66
|
+
// En Windows las rutas no distinguen mayúsculas, y VS Code arranca el
|
|
67
|
+
// servidor con la unidad en minúscula ("c:\Users\...") aunque setup haya
|
|
68
|
+
// guardado "C:\Users\..." (o con "/" en vez de "\"). Comparar exacto hacía
|
|
69
|
+
// que no encontrara la carpeta y el servidor se cerrara al arrancar
|
|
70
|
+
// ("Connection closed" en Claude Code, 21 oct 2026). `platform` es para tests.
|
|
71
|
+
export function folderKey(folderPath, platform = process.platform) {
|
|
72
|
+
const isWin = platform === 'win32'
|
|
73
|
+
const resolved = (isWin ? win32 : posix).resolve(folderPath)
|
|
74
|
+
const trimmed = resolved.length > 1 && !/^[a-z]:\\$/i.test(resolved) ? resolved.replace(/[\\/]+$/, '') : resolved
|
|
75
|
+
return isWin ? trimmed.toLowerCase() : trimmed
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function resolveProjectForCwd(projects, cwd, platform = process.platform) {
|
|
67
79
|
if (!projects) return undefined
|
|
80
|
+
const sep = platform === 'win32' ? '\\' : '/'
|
|
81
|
+
const target = folderKey(cwd, platform)
|
|
68
82
|
let best = null
|
|
69
83
|
for (const folderPath of Object.keys(projects)) {
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
if (!best ||
|
|
84
|
+
const key = folderKey(folderPath, platform)
|
|
85
|
+
if (target !== key && !target.startsWith(key.endsWith(sep) ? key : key + sep)) continue
|
|
86
|
+
if (!best || key.length > best.key.length) best = { key, folderPath }
|
|
73
87
|
}
|
|
74
|
-
return best ? projects[best] : undefined
|
|
88
|
+
return best ? projects[best.folderPath] : undefined
|
|
75
89
|
}
|
|
@@ -526,7 +526,7 @@ export const PLATFORM_PLAYBOOKS = {
|
|
|
526
526
|
{
|
|
527
527
|
title: 'Medir y corregir Core Web Vitals en PageSpeed Insights (móvil)',
|
|
528
528
|
description: 'Correr PageSpeed Insights sobre la home y las páginas clave. Objetivo: LCP < 2.5s, CLS < 0.1, INP < 200ms. Atacar primero imágenes sin comprimir, fuentes bloqueantes y JS sin dividir.',
|
|
529
|
-
type: 'tech', priority: 'medium', tags: ['seo', 'web', 'performance'], offsetDays: 5,
|
|
529
|
+
type: 'tech', priority: 'medium', tags: ['seo', 'web', 'performance'], offsetDays: 5, checkKey: 'core_web_vitals',
|
|
530
530
|
},
|
|
531
531
|
{
|
|
532
532
|
title: 'Fijar dominio canónico y redirecciones 301 (www / http → https)',
|
package/src/server.mjs
CHANGED
|
@@ -161,6 +161,22 @@ const SCOPE_EVIDENCE = {
|
|
|
161
161
|
notApplicableReason: z.string().optional().describe('Motivo de "no aplica" (obligatorio si notApplicable es true)'),
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
// Sin conexión para la carpeta (loadConfig falló): antes el proceso se cerraba
|
|
165
|
+
// y el cliente solo mostraba "Connection closed", sin el motivo. Ahora arranca
|
|
166
|
+
// con una sola tool que explica qué pasa y cómo arreglarlo, para que el agente
|
|
167
|
+
// se lo diga al usuario.
|
|
168
|
+
export function createSetupNeededServer(reason) {
|
|
169
|
+
const server = new McpServer(
|
|
170
|
+
{ name: 'orquestra-mcp', version: PKG_VERSION },
|
|
171
|
+
{ instructions: `Orquestra no está conectado en esta carpeta, así que sus herramientas no están disponibles. Si el usuario pide algo de Orquestra, llama a diagnostico_conexion y explícale cómo arreglarlo. Motivo: ${reason}` },
|
|
172
|
+
)
|
|
173
|
+
server.registerTool('diagnostico_conexion', {
|
|
174
|
+
description: 'Explica por qué Orquestra no está conectado en esta carpeta y cómo arreglarlo.',
|
|
175
|
+
inputSchema: {},
|
|
176
|
+
}, async () => ({ content: [{ type: 'text', text: `orquestra-mcp ${PKG_VERSION}: ${reason}` }] }))
|
|
177
|
+
return server
|
|
178
|
+
}
|
|
179
|
+
|
|
164
180
|
export function createServer() {
|
|
165
181
|
const config = loadConfig()
|
|
166
182
|
const server = new McpServer({ name: 'orquestra-mcp', version: PKG_VERSION }, { instructions: SERVER_INSTRUCTIONS })
|
|
@@ -337,6 +353,26 @@ export function createServer() {
|
|
|
337
353
|
{}
|
|
338
354
|
)
|
|
339
355
|
|
|
356
|
+
// PageSpeed Insights por distribución web (ORQ-377). La última medición
|
|
357
|
+
// también viene en list_distributions (campo `pagespeed` de cada una).
|
|
358
|
+
registerRead(
|
|
359
|
+
'get_pagespeed',
|
|
360
|
+
'/v1/pagespeed',
|
|
361
|
+
'Historial de mediciones de PageSpeed Insights (móvil) de una distribución web, más reciente primero: scores de Performance/Accesibilidad/Buenas prácticas/SEO, métricas de laboratorio (LCP, CLS, TBT), Core Web Vitals de usuarios reales (CrUX: LCP, INP, CLS con categoría FAST/AVERAGE/SLOW; null si el sitio no tiene tráfico suficiente), oportunidades con ahorro estimado y auditorías que fallan. Se mide solo cada lunes; para una medición nueva usa measure_pagespeed.',
|
|
362
|
+
{
|
|
363
|
+
distributionId: z.string().describe('Distribución type="web" con domain configurado (ver list_distributions)'),
|
|
364
|
+
limit: z.number().int().min(1).max(100).optional().describe('Cuántas mediciones traer (default 20)'),
|
|
365
|
+
},
|
|
366
|
+
(args, projectId) => ({ projectId, distributionId: args.distributionId, limit: args.limit })
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
registerCreate(
|
|
370
|
+
'measure_pagespeed',
|
|
371
|
+
'/v1/pagespeed',
|
|
372
|
+
'Pide una medición nueva de PageSpeed Insights (móvil) para una distribución web con dominio. Corre en segundo plano (10-40 s): responde de inmediato con status "running"; el resultado aparece después en el campo `pagespeed` de la distribución (list_distributions) y en get_pagespeed. 409 si ya hay una en curso.',
|
|
373
|
+
{ distributionId: z.string().describe('Distribución type="web" con domain configurado') }
|
|
374
|
+
)
|
|
375
|
+
|
|
340
376
|
registerRead(
|
|
341
377
|
'get_infra_status',
|
|
342
378
|
'/v1/infra',
|
|
@@ -2637,12 +2673,12 @@ export function createServer() {
|
|
|
2637
2673
|
|
|
2638
2674
|
registerRead('list_agents', '/v1/agents',
|
|
2639
2675
|
'Lista los agentes (kind+máquina+stack+ruta local) configurados en un proyecto. Úsala antes de create_agent o de create_agent_run con stack, para saber si ya existe uno.',
|
|
2640
|
-
{ kind: z.enum(AGENT_KINDS).optional().describe('Filtra por kind: "qa" (lint/test/build/E2E), "security" (npm audit + gitleaks), "performance" (build size + Lighthouse) o "screenshots" (captura de pantallas para el checklist de publicación, solo ios/android)') },
|
|
2676
|
+
{ kind: z.enum(AGENT_KINDS).optional().describe('Filtra por kind: "qa" (lint/test/build/E2E), "security" (npm audit + gitleaks), "performance" (build size + Lighthouse: performance/accesibilidad/buenas prácticas/SEO + LCP/CLS/TBT) o "screenshots" (captura de pantallas para el checklist de publicación, solo ios/android)') },
|
|
2641
2677
|
(args, projectId) => ({ projectId, ...(args.kind ? { kind: args.kind } : {}) }))
|
|
2642
2678
|
|
|
2643
2679
|
registerCreate('create_agent', '/v1/agents', 'Crea un agente: asocia un kind ("qa" | "security" | "performance" | "screenshots") a una máquina (runner, ver Workspace → Máquinas), un stack de este proyecto y una ruta de checkout en ESA máquina. kind="performance" además necesita perfUrl.', {
|
|
2644
2680
|
name: z.string().describe('Nombre a mostrar, ej. "Agente web"'),
|
|
2645
|
-
kind: z.enum(AGENT_KINDS).default('qa').describe('"qa" (lint/test/build/E2E), "security" (npm audit + gitleaks), "performance" (build size + Lighthouse) o "screenshots" (captura de pantallas para tiendas, solo stack ios/android) -- default "qa"'),
|
|
2681
|
+
kind: z.enum(AGENT_KINDS).default('qa').describe('"qa" (lint/test/build/E2E), "security" (npm audit + gitleaks), "performance" (build size + Lighthouse: performance/accesibilidad/buenas prácticas/SEO + LCP/CLS/TBT) o "screenshots" (captura de pantallas para tiendas, solo stack ios/android) -- default "qa"'),
|
|
2646
2682
|
runnerId: z.string().describe('ID de la máquina emparejada -- ver list_agents o Workspace → Máquinas'),
|
|
2647
2683
|
stack: z.enum(AGENT_STACKS).optional().describe('Requerido para los 4 kinds. security/performance solo tienen lógica real para "web" hoy; screenshots solo para "ios"/"android"'),
|
|
2648
2684
|
localPath: z.string().optional().describe('Requerido para los 4 kinds -- ruta absoluta del checkout en esa máquina'),
|
|
@@ -2657,7 +2693,14 @@ export function createServer() {
|
|
|
2657
2693
|
plannedRunAt: z.string().optional().describe('YYYY-MM-DD -- recordatorio manual de "próxima corrida planeada". Ningún kind tiene scheduling real (todo trigger es manual/push): esto NO dispara ninguna corrida sola, solo alimenta el calendario global.'),
|
|
2658
2694
|
perfUrl: z.string().optional().describe('Requerido si kind="performance" -- URL viva (ej. staging/prod) contra la que corre Lighthouse'),
|
|
2659
2695
|
maxBundleSizeKb: z.number().optional().describe('Específico de kind="performance" -- umbral en KB para el paso de tamaño de build (default 5000)'),
|
|
2660
|
-
minPerfScore: z.number().optional().describe('Específico de kind="performance" -- score mínimo de Lighthouse (0-100) para pasar (default 50)'),
|
|
2696
|
+
minPerfScore: z.number().optional().describe('Específico de kind="performance" -- score mínimo de Lighthouse en Performance (0-100) para pasar (default 50)'),
|
|
2697
|
+
perfUrls: z.array(z.string()).optional().describe('Específico de kind="performance" -- URLs ADICIONALES a perfUrl (ej. /precios, /blog): Lighthouse corre un paso por cada una'),
|
|
2698
|
+
minAccessibilityScore: z.number().optional().describe('Específico de kind="performance" -- score mínimo de Accesibilidad (0-100, default 90)'),
|
|
2699
|
+
minBestPracticesScore: z.number().optional().describe('Específico de kind="performance" -- score mínimo de Buenas prácticas (0-100, default 90)'),
|
|
2700
|
+
minSeoScore: z.number().optional().describe('Específico de kind="performance" -- score mínimo de SEO (0-100, default 90)'),
|
|
2701
|
+
maxLcpMs: z.number().optional().describe('Específico de kind="performance" -- LCP máximo en ms (default 2500, umbral "bueno" de Google)'),
|
|
2702
|
+
maxCls: z.number().optional().describe('Específico de kind="performance" -- CLS máximo (default 0.1)'),
|
|
2703
|
+
maxTbtMs: z.number().optional().describe('Específico de kind="performance" -- TBT máximo en ms (default 200, proxy de laboratorio de INP)'),
|
|
2661
2704
|
})
|
|
2662
2705
|
|
|
2663
2706
|
registerUpdate('update_agent', '/v1/agents', 'Edita un agente existente.', {
|
|
@@ -2678,6 +2721,13 @@ export function createServer() {
|
|
|
2678
2721
|
perfUrl: z.string().optional().describe('Ver create_agent -- específico de kind="performance"'),
|
|
2679
2722
|
maxBundleSizeKb: z.number().optional(),
|
|
2680
2723
|
minPerfScore: z.number().optional(),
|
|
2724
|
+
perfUrls: z.array(z.string()).optional().describe('Ver create_agent -- reemplaza la lista completa, [] la vacía'),
|
|
2725
|
+
minAccessibilityScore: z.number().optional(),
|
|
2726
|
+
minBestPracticesScore: z.number().optional(),
|
|
2727
|
+
minSeoScore: z.number().optional(),
|
|
2728
|
+
maxLcpMs: z.number().optional(),
|
|
2729
|
+
maxCls: z.number().optional(),
|
|
2730
|
+
maxTbtMs: z.number().optional(),
|
|
2681
2731
|
})
|
|
2682
2732
|
|
|
2683
2733
|
registerCreate('create_agent_run', '/v1/agent-runs', 'Dispara una corrida de agente (qa: lint/tests/build/E2E; security: npm audit + gitleaks; performance: build size + Lighthouse; screenshots: captura de pantallas para el checklist de publicación) en la rama indicada. Necesita al menos un agente activo para el stack (o falla explícito -- ver list_agents/create_agent). La corrida queda "queued" -- nunca asumas que pasó, confirma con get_agent_run.', {
|
package/src/setup.mjs
CHANGED
|
@@ -78,22 +78,72 @@ export async function runSetup() {
|
|
|
78
78
|
const agentLabelAnswer = await prompt('¿Cómo se llama este agente/herramienta? [Claude Code]: ')
|
|
79
79
|
const agentLabel = agentLabelAnswer.trim() || 'Claude Code'
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
81
|
+
// askIfMissing: si no hay workspace la API no crea nada solo -- avisa
|
|
82
|
+
// (needsWorkspace) y aquí se le pide el nombre al usuario.
|
|
83
|
+
const callBootstrap = async (extra = {}) => {
|
|
84
|
+
const res = await fetch(`${apiBase}/v1/bootstrap`, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: { Authorization: `Bearer ${idToken}`, 'Content-Type': 'application/json' },
|
|
87
|
+
body: JSON.stringify({ agentLabel, askIfMissing: true, ...extra }),
|
|
88
|
+
})
|
|
89
|
+
const body = await res.json()
|
|
90
|
+
if (!res.ok) throw new Error(body?.error || `HTTP ${res.status}`)
|
|
91
|
+
return body
|
|
92
|
+
}
|
|
93
|
+
const askName = async (question) => {
|
|
94
|
+
for (;;) {
|
|
95
|
+
const name = (await prompt(question)).trim()
|
|
96
|
+
if (name) return name
|
|
97
|
+
console.log('El nombre no puede estar vacío.')
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let body = await callBootstrap()
|
|
102
|
+
if (body.needsWorkspaceChoice) {
|
|
103
|
+
// Miembro de varios workspaces: elegir aquí no cambia el workspace
|
|
104
|
+
// actual de la web, solo a cuál queda conectada esta carpeta.
|
|
105
|
+
const { workspaces, currentWorkspaceId } = body
|
|
106
|
+
console.log('\nPerteneces a varios workspaces. ¿En cuál quieres trabajar desde esta carpeta?')
|
|
107
|
+
workspaces.forEach((w, i) => console.log(` ${i + 1}. ${w.name}${w.id === currentWorkspaceId ? ' (el que tienes abierto en la web)' : ''}`))
|
|
108
|
+
const currentIdx = workspaces.findIndex(w => w.id === currentWorkspaceId) + 1
|
|
109
|
+
let idx
|
|
110
|
+
for (;;) {
|
|
111
|
+
const answer = (await prompt(currentIdx ? `Número [Enter para usar "${workspaces[currentIdx - 1].name}"]: ` : 'Número: ')).trim()
|
|
112
|
+
idx = answer === '' && currentIdx ? currentIdx : parseInt(answer, 10)
|
|
113
|
+
if (idx >= 1 && idx <= workspaces.length) break
|
|
114
|
+
console.log('Selección inválida.')
|
|
115
|
+
}
|
|
116
|
+
body = await callBootstrap({ workspaceId: workspaces[idx - 1].id })
|
|
117
|
+
} else if (body.needsWorkspace) {
|
|
118
|
+
console.log('\nTodavía no tienes un workspace en Orquestra. Primero vamos a crearlo')
|
|
119
|
+
console.log('(agrupa todos tus proyectos: tu empresa, tu equipo o tu nombre si trabajas solo).')
|
|
120
|
+
const workspaceName = await askName('Nombre del workspace: ')
|
|
121
|
+
body = await callBootstrap({ workspaceName })
|
|
122
|
+
console.log(`Workspace "${workspaceName}" creado.`)
|
|
123
|
+
}
|
|
88
124
|
|
|
89
|
-
const { workspaceId, token,
|
|
125
|
+
const { workspaceId, token, projects } = body
|
|
126
|
+
// La API de producción anterior a ORQ-388 no manda workspaceName.
|
|
127
|
+
const wsLabel = body.workspaceName ? ` del workspace "${body.workspaceName}"` : ''
|
|
90
128
|
let defaultProjectId
|
|
91
129
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
130
|
+
const createProject = async (name) => {
|
|
131
|
+
const res2 = await fetch(`${apiBase}/v1/projects?workspaceId=${workspaceId}`, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
134
|
+
body: JSON.stringify({ name }),
|
|
135
|
+
})
|
|
136
|
+
const newProject = await res2.json()
|
|
137
|
+
if (!res2.ok) throw new Error(newProject?.error || `HTTP ${res2.status}`)
|
|
138
|
+
console.log(`Proyecto "${newProject.name}"${wsLabel} creado y configurado como default.`)
|
|
139
|
+
return newProject.id
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (projects.length === 0) {
|
|
143
|
+
console.log(`\nAhora crea el proyecto${wsLabel} que este MCP va a usar (puedes agregar más después).`)
|
|
144
|
+
defaultProjectId = await createProject(await askName('Nombre del proyecto: '))
|
|
95
145
|
} else {
|
|
96
|
-
console.log(
|
|
146
|
+
console.log(`\nElige el proyecto${wsLabel} que este MCP va a usar por default:`)
|
|
97
147
|
console.log(' 0. (crear un proyecto nuevo)')
|
|
98
148
|
projects.forEach((p, i) => console.log(` ${i + 1}. ${p.name}`))
|
|
99
149
|
const singleDefaultIdx = projects.length === 1 ? 1 : null
|
|
@@ -105,20 +155,10 @@ export async function runSetup() {
|
|
|
105
155
|
if (Number.isNaN(idx) || idx < 0 || idx > projects.length) throw new Error('Selección inválida')
|
|
106
156
|
|
|
107
157
|
if (idx === 0) {
|
|
108
|
-
|
|
109
|
-
if (!name.trim()) throw new Error('El nombre no puede estar vacío')
|
|
110
|
-
const res2 = await fetch(`${apiBase}/v1/projects?workspaceId=${workspaceId}`, {
|
|
111
|
-
method: 'POST',
|
|
112
|
-
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
113
|
-
body: JSON.stringify({ name: name.trim() }),
|
|
114
|
-
})
|
|
115
|
-
const newProject = await res2.json()
|
|
116
|
-
if (!res2.ok) throw new Error(newProject?.error || `HTTP ${res2.status}`)
|
|
117
|
-
defaultProjectId = newProject.id
|
|
118
|
-
console.log(`Proyecto "${newProject.name}" creado y configurado como default.`)
|
|
158
|
+
defaultProjectId = await createProject(await askName('Nombre del proyecto nuevo: '))
|
|
119
159
|
} else {
|
|
120
160
|
defaultProjectId = projects[idx - 1].id
|
|
121
|
-
console.log(`Usando tu proyecto "${projects[idx - 1].name}" como default.`)
|
|
161
|
+
console.log(`Usando tu proyecto "${projects[idx - 1].name}"${wsLabel} como default.`)
|
|
122
162
|
}
|
|
123
163
|
}
|
|
124
164
|
|
|
@@ -135,12 +175,18 @@ export async function runSetup() {
|
|
|
135
175
|
|
|
136
176
|
upsertProjectMapping({ apiBase, apiToken: token, workspaceId, folderPath, projectId: defaultProjectId })
|
|
137
177
|
console.log(`\nListo. "${folderPath}" (y sus subcarpetas) ya apunta a este proyecto -- guardado en ${CONFIG_PATH}.`)
|
|
178
|
+
// En Windows Claude Code necesita `cmd /c` para lanzar npx, y el config de
|
|
179
|
+
// Claude Desktop vive en %APPDATA%. `@latest` evita que npx reuse una
|
|
180
|
+
// versión vieja de su caché.
|
|
181
|
+
const isWindows = process.platform === 'win32'
|
|
138
182
|
console.log('\nSiguiente paso -- conéctalo a tu agente de IA:\n')
|
|
139
183
|
console.log(' Claude Code:')
|
|
140
|
-
console.log(
|
|
141
|
-
console.log('
|
|
142
|
-
console.log('
|
|
184
|
+
console.log(` claude mcp add orquestra -- ${isWindows ? 'cmd /c ' : ''}npx -y orquestra-mcp@latest`)
|
|
185
|
+
if (isWindows) console.log(' (si "claude" no se reconoce: npm install -g @anthropic-ai/claude-code y abre otra terminal)')
|
|
186
|
+
console.log(' Después reinicia Claude Code o reconéctalo con /mcp.\n')
|
|
187
|
+
console.log(` Claude Desktop -- agrega esto a ${isWindows ? '%APPDATA%\\Claude\\' : '~/Library/Application Support/Claude/'}claude_desktop_config.json:`)
|
|
188
|
+
console.log(' { "mcpServers": { "orquestra": { "command": "npx", "args": ["-y", "orquestra-mcp@latest"] } } }\n')
|
|
143
189
|
console.log(' ¿Usas Cursor, ChatGPT/Codex o Gemini CLI? La config de cada uno está en:')
|
|
144
|
-
console.log(' https://orquestra.me/mcp#instalacion\n')
|
|
145
|
-
console.log('Docs completas y las
|
|
190
|
+
console.log(' https://www.orquestra.me/mcp#instalacion\n')
|
|
191
|
+
console.log('Docs completas y todas las tools disponibles: https://www.orquestra.me/mcp')
|
|
146
192
|
}
|