orquestra-mcp 1.50.0 → 1.51.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/bin/orquestra-mcp.mjs +11 -2
- package/package.json +1 -1
- package/src/api.mjs +14 -2
- package/src/configFile.mjs +90 -4
- package/src/server.mjs +15 -7
- package/src/setup.mjs +77 -20
- package/src/updateCheck.mjs +56 -0
package/bin/orquestra-mcp.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
3
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
|
+
import { startUpdateCheck } from '../src/updateCheck.mjs'
|
|
6
7
|
|
|
7
8
|
const HELP_TEXT = `orquestra-mcp -- Servidor MCP de Orquestra (132 tools)
|
|
8
9
|
|
|
@@ -10,6 +11,9 @@ Uso:
|
|
|
10
11
|
npx orquestra-mcp Inicia el servidor MCP por stdio -- lo usa tu cliente
|
|
11
12
|
MCP (Claude Code, Claude Desktop, etc.), no se corre a mano
|
|
12
13
|
npx orquestra-mcp setup Login por navegador + conecta esta carpeta a un proyecto
|
|
14
|
+
npx orquestra-mcp setup --agent codex
|
|
15
|
+
Lo mismo para una herramienta puntual (claude-code, codex,
|
|
16
|
+
cursor, gemini-cli...): cada una firma con su propio agente
|
|
13
17
|
npx orquestra-mcp vault sync <carpeta> [--apply]
|
|
14
18
|
Sincroniza la base de conocimiento del workspace con una
|
|
15
19
|
carpeta de tu vault de Obsidian (la 1ra vez solo muestra el plan)
|
|
@@ -17,7 +21,8 @@ Uso:
|
|
|
17
21
|
|
|
18
22
|
Config (la deja lista "setup"): ~/.orquestra/mcp-config.json
|
|
19
23
|
Alternativa manual (variables de entorno): ORQUESTRA_API_TOKEN, ORQUESTRA_WORKSPACE_ID,
|
|
20
|
-
ORQUESTRA_DEFAULT_PROJECT_ID
|
|
24
|
+
ORQUESTRA_DEFAULT_PROJECT_ID. ORQUESTRA_AGENT elige el token de una herramienta
|
|
25
|
+
(ej. codex) cuando la carpeta tiene varias conectadas.
|
|
21
26
|
|
|
22
27
|
Docs completas: https://orquestra.me/mcp
|
|
23
28
|
`
|
|
@@ -31,7 +36,8 @@ async function main() {
|
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
if (cmd === 'setup') {
|
|
34
|
-
|
|
39
|
+
const agentIdx = process.argv.indexOf('--agent')
|
|
40
|
+
await runSetup({ agent: agentIdx > 0 ? process.argv[agentIdx + 1] : process.env.ORQUESTRA_AGENT })
|
|
35
41
|
return
|
|
36
42
|
}
|
|
37
43
|
|
|
@@ -45,6 +51,9 @@ async function main() {
|
|
|
45
51
|
return
|
|
46
52
|
}
|
|
47
53
|
|
|
54
|
+
// Sin await: el aviso de versión nueva no debe atrasar el arranque.
|
|
55
|
+
startUpdateCheck()
|
|
56
|
+
|
|
48
57
|
let server
|
|
49
58
|
try {
|
|
50
59
|
server = createServer()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orquestra-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.51.0",
|
|
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": {
|
package/src/api.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readConfigFile, resolveProjectForCwd, CONFIG_PATH } from './configFile.mjs'
|
|
1
|
+
import { readConfigFile, resolveProjectForCwd, entryForAgent, agentKey, CONFIG_PATH } from './configFile.mjs'
|
|
2
2
|
import { request } from './http.mjs'
|
|
3
3
|
|
|
4
4
|
const DEFAULT_API_BASE = 'https://api.orquestra.me'
|
|
@@ -8,7 +8,19 @@ export function loadConfig() {
|
|
|
8
8
|
// Todo sale de la MISMA carpeta mapeada (ver resolveProjectForCwd) --
|
|
9
9
|
// apiToken/workspaceId/projectId siempre son de una sola cuenta, nunca
|
|
10
10
|
// el token de una carpeta con el proyecto de otra.
|
|
11
|
-
const
|
|
11
|
+
const folderEntry = resolveProjectForCwd(fileConfig?.projects, process.cwd())
|
|
12
|
+
// ORQUESTRA_AGENT (ORQ-415) elige el token de esa herramienta en la
|
|
13
|
+
// carpeta. Si no está conectada falla explícito: caer al token default
|
|
14
|
+
// haría que sus cambios queden firmados por otra herramienta.
|
|
15
|
+
const agent = agentKey(process.env.ORQUESTRA_AGENT)
|
|
16
|
+
const folderConfig = agent ? entryForAgent(folderEntry, agent) : folderEntry
|
|
17
|
+
if (agent && folderEntry && !folderConfig && !process.env.ORQUESTRA_API_TOKEN) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`La herramienta "${process.env.ORQUESTRA_AGENT}" (ORQUESTRA_AGENT) no está conectada en esta carpeta (${process.cwd()}), ` +
|
|
20
|
+
`solo otras. Para que sus cambios queden a su nombre y no al de otra herramienta, abre una terminal aquí, ` +
|
|
21
|
+
`corre "npx orquestra-mcp@latest setup" y elige esa herramienta; después reconecta el MCP.`
|
|
22
|
+
)
|
|
23
|
+
}
|
|
12
24
|
|
|
13
25
|
const apiBase = process.env.ORQUESTRA_API_BASE || folderConfig?.apiBase || DEFAULT_API_BASE
|
|
14
26
|
const apiToken = process.env.ORQUESTRA_API_TOKEN || folderConfig?.apiToken
|
package/src/configFile.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
2
|
-
import { homedir } from 'os'
|
|
2
|
+
import { homedir, hostname } from 'os'
|
|
3
|
+
import { randomBytes } from 'crypto'
|
|
3
4
|
import { join, dirname, win32, posix } from 'path'
|
|
4
5
|
|
|
5
6
|
export const CONFIG_PATH = join(homedir(), '.orquestra', 'mcp-config.json')
|
|
@@ -45,19 +46,104 @@ export function writeConfigFile(data) {
|
|
|
45
46
|
writeFileSync(CONFIG_PATH, JSON.stringify(data, null, 2), { mode: 0o600 })
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
// Identidad por herramienta (ORQ-415): cada herramienta (Claude Code, Codex,
|
|
50
|
+
// Cursor...) tiene su propio token -- su propio agente en Orquestra -- aunque
|
|
51
|
+
// trabajen en la misma carpeta. La entrada de la carpeta guarda la conexión
|
|
52
|
+
// "default" arriba (la que se usa sin ORQUESTRA_AGENT) con `agent` = su
|
|
53
|
+
// herramienta, y todas en `agents[clave]`. Una entrada de antes de ORQ-415 no
|
|
54
|
+
// trae `agent`: su token es el de "Claude Code", el default histórico.
|
|
55
|
+
// Mismo slug que agentSlug() de bootstrap.mjs en orquestra-infra.
|
|
56
|
+
export function agentKey(label) {
|
|
57
|
+
return String(label || '')
|
|
58
|
+
.normalize('NFD').replace(/[̀-ͯ]/g, '')
|
|
59
|
+
.toLowerCase().trim()
|
|
60
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
61
|
+
.replace(/^-+|-+$/g, '')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const LEGACY_AGENT = 'claude-code'
|
|
65
|
+
const defaultAgentOf = entry => entry?.agent || (entry?.apiToken ? LEGACY_AGENT : null)
|
|
66
|
+
const connectionOf = ({ apiBase, apiToken, workspaceId, projectId }) => ({ apiBase, apiToken, workspaceId, projectId })
|
|
67
|
+
|
|
68
|
+
// La conexión de UNA herramienta dentro de la entrada de una carpeta, o null
|
|
69
|
+
// si esa herramienta nunca se conectó ahí (nunca cae al token de otra: eso
|
|
70
|
+
// es justo lo que hacía firmar a Codex como Claude Code).
|
|
71
|
+
export function entryForAgent(entry, key) {
|
|
72
|
+
if (!entry || !key) return entry
|
|
73
|
+
if (entry.agents?.[key]) return entry.agents[key]
|
|
74
|
+
return defaultAgentOf(entry) === key ? connectionOf(entry) : null
|
|
75
|
+
}
|
|
76
|
+
|
|
48
77
|
// Guarda/actualiza la cuenta+proyecto de UNA carpeta sin pisar las demás --
|
|
49
78
|
// setup se puede correr varias veces, una por cada carpeta contenedora de
|
|
50
79
|
// repos (ver resolveProjectForCwd), y cada corrida solo debe tocar su
|
|
51
80
|
// propia entrada -- incluida la cuenta (apiToken/workspaceId), para poder
|
|
52
81
|
// tener una carpeta en una cuenta de Orquestra y otra carpeta en otra
|
|
53
|
-
// cuenta distinta al mismo tiempo.
|
|
54
|
-
|
|
82
|
+
// cuenta distinta al mismo tiempo. Con `agent` solo toca la conexión de esa
|
|
83
|
+
// herramienta: la default de la carpeta cambia únicamente si era la misma
|
|
84
|
+
// herramienta (o la carpeta no tenía ninguna).
|
|
85
|
+
export function upsertProjectMapping({ apiBase, apiToken, workspaceId, folderPath, projectId, agent }) {
|
|
55
86
|
const existing = readConfigFile() || { projects: {} }
|
|
56
87
|
const projects = { ...(existing.projects || {}) }
|
|
57
|
-
|
|
88
|
+
const conn = { apiBase, apiToken, workspaceId, projectId }
|
|
89
|
+
if (!agent) {
|
|
90
|
+
projects[folderPath] = conn
|
|
91
|
+
} else {
|
|
92
|
+
const prev = projects[folderPath]
|
|
93
|
+
const agents = { ...(prev?.agents || {}), [agent]: conn }
|
|
94
|
+
const keepDefault = prev && defaultAgentOf(prev) && defaultAgentOf(prev) !== agent
|
|
95
|
+
projects[folderPath] = keepDefault
|
|
96
|
+
? { ...connectionOf(prev), agent: defaultAgentOf(prev), agents }
|
|
97
|
+
: { ...conn, agent, agents }
|
|
98
|
+
}
|
|
58
99
|
writeConfigFile({ projects })
|
|
59
100
|
}
|
|
60
101
|
|
|
102
|
+
// El setup de una herramienta ya conocida en esta máquina revoca su token
|
|
103
|
+
// anterior (bootstrap en orquestra-infra): hay que ponerle el nuevo a TODAS
|
|
104
|
+
// las carpetas donde esa herramienta usa el mismo workspace, no solo a la del
|
|
105
|
+
// setup, o las demás quedarían con un token muerto. Devuelve las carpetas.
|
|
106
|
+
export function replaceAgentToken({ workspaceId, agent, apiToken }) {
|
|
107
|
+
const existing = readConfigFile()
|
|
108
|
+
if (!existing?.projects) return []
|
|
109
|
+
const updated = []
|
|
110
|
+
for (const [folderPath, entry] of Object.entries(existing.projects)) {
|
|
111
|
+
let touched = false
|
|
112
|
+
if (defaultAgentOf(entry) === agent && entry.workspaceId === workspaceId) { entry.apiToken = apiToken; touched = true }
|
|
113
|
+
const own = entry.agents?.[agent]
|
|
114
|
+
if (own && own.workspaceId === workspaceId) { own.apiToken = apiToken; touched = true }
|
|
115
|
+
if (touched) updated.push(folderPath)
|
|
116
|
+
}
|
|
117
|
+
if (updated.length) writeConfigFile(existing)
|
|
118
|
+
return updated
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Herramientas ya conectadas en esta máquina (en cualquier carpeta).
|
|
122
|
+
export function connectedAgents() {
|
|
123
|
+
const keys = new Set()
|
|
124
|
+
for (const entry of Object.values(readConfigFile()?.projects || {})) {
|
|
125
|
+
if (defaultAgentOf(entry)) keys.add(defaultAgentOf(entry))
|
|
126
|
+
for (const key of Object.keys(entry.agents || {})) keys.add(key)
|
|
127
|
+
}
|
|
128
|
+
return keys
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Identidad de la máquina (ORQ-415): un id aleatorio, no el serial ni la MAC
|
|
132
|
+
// -- solo distingue esta instalación ("Codex · MacBook-Pro" vs "Codex ·
|
|
133
|
+
// Mac-mini"). Se crea una vez y se reusa en cada setup.
|
|
134
|
+
export const MACHINE_PATH = join(homedir(), '.orquestra', 'machine.json')
|
|
135
|
+
|
|
136
|
+
export function readOrCreateMachine() {
|
|
137
|
+
try {
|
|
138
|
+
const saved = JSON.parse(readFileSync(MACHINE_PATH, 'utf8'))
|
|
139
|
+
if (/^[a-f0-9]{16,64}$/.test(saved.id)) return { id: saved.id, name: saved.name || hostname().replace(/\.local$/, '') }
|
|
140
|
+
} catch {}
|
|
141
|
+
const machine = { id: randomBytes(8).toString('hex'), name: hostname().replace(/\.local$/, '') }
|
|
142
|
+
mkdirSync(dirname(MACHINE_PATH), { recursive: true })
|
|
143
|
+
writeFileSync(MACHINE_PATH, JSON.stringify(machine, null, 2), { mode: 0o600 })
|
|
144
|
+
return machine
|
|
145
|
+
}
|
|
146
|
+
|
|
61
147
|
// Busca, subiendo desde cwd hacia la raíz, la carpeta mapeada más específica
|
|
62
148
|
// -- mismo criterio que .editorconfig/.gitconfig: el ancestro más cercano
|
|
63
149
|
// gana, así que un repo individual con su propio mapeo (y su propia cuenta)
|
package/src/server.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
|
5
5
|
import { z } from 'zod'
|
|
6
6
|
import { loadConfig, apiGet, apiPost, apiPatch, apiPatchPath, apiDelete, apiDeletePath, putToPresignedUrl, ApiError } from './api.mjs'
|
|
7
7
|
import { cancelContext } from './http.mjs'
|
|
8
|
+
import { updateNotice } from './updateCheck.mjs'
|
|
8
9
|
import { STAGES, PLAYBOOKS } from './stages.mjs'
|
|
9
10
|
import { FOUNDATIONS } from './foundations.mjs'
|
|
10
11
|
import { PLATFORM_PLAYBOOKS, TRACKING_TOOLS } from './platformPlaybooks.mjs'
|
|
@@ -104,7 +105,7 @@ const SETUP_GUIDE = {
|
|
|
104
105
|
// preguntar en vez de adivinar.
|
|
105
106
|
const SERVER_INSTRUCTIONS = `Este server conecta con Orquestra, la app de gestión de proyectos del usuario -- úsalo para mantener su proyecto sincronizado con el trabajo real, sin que tenga que pedírtelo cada vez.
|
|
106
107
|
|
|
107
|
-
Al empezar una sesión de trabajo en este proyecto, llama una vez a get_project_context para saber en qué fase está, qué tareas hay abiertas/vencidas/bloqueadas y qué se hizo recientemente. Úsalo como contexto de fondo, no lo repitas en cada mensaje.
|
|
108
|
+
Al empezar una sesión de trabajo en este proyecto, llama una vez a get_project_context para saber en qué fase está, qué tareas hay abiertas/vencidas/bloqueadas y qué se hizo recientemente. Úsalo como contexto de fondo, no lo repitas en cada mensaje. Si trae orquestraMcpUpdate, avísale al usuario UNA sola vez en la sesión que hay una versión nueva de este MCP y cómo actualizar (usa su campo message), sin interrumpir lo que estaba pidiendo.
|
|
108
109
|
|
|
109
110
|
Si get_project_context devuelve un proyecto vacío (sin módulos ni tareas), es la primera vez que se usa este MCP aquí -- pero "vacío en Orquestra" NO significa "recién empezado en la realidad". Antes de ofrecer nada, pregúntale al usuario: ¿este proyecto es una idea nueva, o ya existe y solo lo estás conectando por primera vez? Según la respuesta:
|
|
110
111
|
- Si es una idea nueva: llama a get_setup_guide y ofrece un playbook de etapa "pre" (get_stage_playbook + apply_stage_playbook) -- ahí sí basta con eso, el proyecto de verdad no tiene nada más que sincronizar todavía.
|
|
@@ -175,7 +176,10 @@ export function createSetupNeededServer(reason) {
|
|
|
175
176
|
server.registerTool('diagnostico_conexion', {
|
|
176
177
|
description: 'Explica por qué Orquestra no está conectado en esta carpeta y cómo arreglarlo.',
|
|
177
178
|
inputSchema: {},
|
|
178
|
-
}, async () =>
|
|
179
|
+
}, async () => {
|
|
180
|
+
const update = updateNotice(PKG_VERSION)
|
|
181
|
+
return { content: [{ type: 'text', text: `orquestra-mcp ${PKG_VERSION}: ${reason}${update ? `\n\n${update.message}` : ''}` }] }
|
|
182
|
+
})
|
|
179
183
|
return server
|
|
180
184
|
}
|
|
181
185
|
|
|
@@ -205,14 +209,14 @@ export function createServer() {
|
|
|
205
209
|
return projectId
|
|
206
210
|
}
|
|
207
211
|
|
|
208
|
-
function registerRead(name, path, description, extraSchema, buildParams) {
|
|
212
|
+
function registerRead(name, path, description, extraSchema, buildParams, decorate = data => data) {
|
|
209
213
|
server.registerTool(
|
|
210
214
|
name,
|
|
211
215
|
{ description, inputSchema: { projectId: z.string().optional().describe(PROJECT_ID_DESC), ...extraSchema } },
|
|
212
216
|
async (args) => {
|
|
213
217
|
try {
|
|
214
218
|
const data = await apiGet(config, path, buildParams(args, resolveProjectId(args.projectId)))
|
|
215
|
-
return textResult(data)
|
|
219
|
+
return textResult(decorate(data))
|
|
216
220
|
} catch (err) {
|
|
217
221
|
return errorResult(err)
|
|
218
222
|
}
|
|
@@ -264,7 +268,11 @@ export function createServer() {
|
|
|
264
268
|
'/v1/context',
|
|
265
269
|
'Resumen compacto del estado de un proyecto de Orquestra: fase, plataformas, tareas abiertas/vencidas/bloqueadas, módulos, ideas y changelog reciente.',
|
|
266
270
|
{},
|
|
267
|
-
(args, projectId) => ({ projectId })
|
|
271
|
+
(args, projectId) => ({ projectId }),
|
|
272
|
+
data => {
|
|
273
|
+
const update = updateNotice(PKG_VERSION)
|
|
274
|
+
return update ? { ...data, orquestraMcpUpdate: update } : data
|
|
275
|
+
}
|
|
268
276
|
)
|
|
269
277
|
|
|
270
278
|
registerRead(
|
|
@@ -295,10 +303,10 @@ export function createServer() {
|
|
|
295
303
|
registerRead(
|
|
296
304
|
'search',
|
|
297
305
|
'/v1/search',
|
|
298
|
-
'Busca por texto en tareas, actividades (reuniones/llamadas), notas, ideas y
|
|
306
|
+
'Busca por texto en tareas, actividades (reuniones/llamadas), notas, ideas, docs, riesgos, módulos, contenido y distribuciones de un proyecto de Orquestra -- en los mismos campos que pueden tener [[wikilinks]] (descripción, mitigación, criterios de cierre, puntos, verificaciones...). También por folio ("ORQ-123") o por clave de módulo ("AUTH-3") -- la exacta sale primero -- y sin importar acentos. Primero lo que coincide en el folio o el título, después en el cuerpo; tope de 30 resultados: si buscas ideas o docs en un proyecto con muchas tareas, pasa `types` para que no queden tapadas.',
|
|
299
307
|
{
|
|
300
308
|
q: z.string().describe('Texto a buscar'),
|
|
301
|
-
types: z.array(z.enum(['task', 'activity', 'note', 'idea', 'doc', 'knowledge'])).optional().describe('Limitar a estos tipos (opcional). Sin esto busca en todos. knowledge = solo la base vinculada a este proyecto.'),
|
|
309
|
+
types: z.array(z.enum(['task', 'activity', 'note', 'idea', 'doc', 'knowledge', 'risk', 'module', 'content', 'distribution'])).optional().describe('Limitar a estos tipos (opcional). Sin esto busca en todos. knowledge = solo la base vinculada a este proyecto.'),
|
|
302
310
|
},
|
|
303
311
|
(args, projectId) => ({ projectId, q: args.q, types: args.types?.join(',') })
|
|
304
312
|
)
|
package/src/setup.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { exec } from 'child_process'
|
|
|
4
4
|
import { createInterface } from 'readline'
|
|
5
5
|
import { homedir } from 'os'
|
|
6
6
|
import { resolve } from 'path'
|
|
7
|
-
import { upsertProjectMapping, CONFIG_PATH } from './configFile.mjs'
|
|
7
|
+
import { upsertProjectMapping, replaceAgentToken, connectedAgents, readOrCreateMachine, agentKey, CONFIG_PATH } from './configFile.mjs'
|
|
8
8
|
|
|
9
9
|
const DEFAULT_API_BASE = 'https://api.orquestra.me'
|
|
10
10
|
const DEFAULT_WEB_URL = 'https://app.orquestra.me'
|
|
@@ -15,12 +15,16 @@ function openBrowser(url) {
|
|
|
15
15
|
exec(`${cmd} "${url}"`, () => {})
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// Herramientas conocidas (ORQ-415): cada una queda como su propio agente en
|
|
19
|
+
// Orquestra ("Codex · MacBook-Pro") y con su propio token en la carpeta.
|
|
20
|
+
const TOOLS = ['Claude Code', 'Codex', 'Cursor', 'Gemini CLI', 'Claude Desktop']
|
|
21
|
+
|
|
18
22
|
function prompt(question) {
|
|
19
23
|
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
20
24
|
return new Promise(resolve => rl.question(question, answer => { rl.close(); resolve(answer) }))
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
export async function runSetup() {
|
|
27
|
+
export async function runSetup({ agent: presetAgent } = {}) {
|
|
24
28
|
const apiBase = process.env.ORQUESTRA_API_BASE || DEFAULT_API_BASE
|
|
25
29
|
const webUrl = process.env.ORQUESTRA_WEB_URL || DEFAULT_WEB_URL
|
|
26
30
|
const state = randomBytes(16).toString('hex')
|
|
@@ -75,8 +79,9 @@ export async function runSetup() {
|
|
|
75
79
|
const idToken = await idTokenPromise
|
|
76
80
|
console.log('Login recibido, configurando...')
|
|
77
81
|
|
|
78
|
-
const
|
|
79
|
-
const agentLabel =
|
|
82
|
+
const machine = readOrCreateMachine()
|
|
83
|
+
const agentLabel = await chooseTool(presetAgent, machine)
|
|
84
|
+
const agent = agentKey(agentLabel)
|
|
80
85
|
|
|
81
86
|
// askIfMissing: si no hay workspace la API no crea nada solo -- avisa
|
|
82
87
|
// (needsWorkspace) y aquí se le pide el nombre al usuario.
|
|
@@ -84,7 +89,7 @@ export async function runSetup() {
|
|
|
84
89
|
const res = await fetch(`${apiBase}/v1/bootstrap`, {
|
|
85
90
|
method: 'POST',
|
|
86
91
|
headers: { Authorization: `Bearer ${idToken}`, 'Content-Type': 'application/json' },
|
|
87
|
-
body: JSON.stringify({ agentLabel, askIfMissing: true, ...extra }),
|
|
92
|
+
body: JSON.stringify({ agentLabel, machineId: machine.id, machineName: machine.name, askIfMissing: true, ...extra }),
|
|
88
93
|
})
|
|
89
94
|
const body = await res.json()
|
|
90
95
|
if (!res.ok) throw new Error(body?.error || `HTTP ${res.status}`)
|
|
@@ -123,6 +128,14 @@ export async function runSetup() {
|
|
|
123
128
|
}
|
|
124
129
|
|
|
125
130
|
const { workspaceId, token, projects } = body
|
|
131
|
+
// La API anterior a ORQ-415 no manda `agent`.
|
|
132
|
+
// El token anterior ya quedó revocado en el servidor: se reemplaza en todas
|
|
133
|
+
// las carpetas de esta herramienta AHORA, antes de las preguntas, para que
|
|
134
|
+
// cancelar el setup a la mitad no deje carpetas con un token muerto.
|
|
135
|
+
const renewedFolders = body.agent?.known ? replaceAgentToken({ workspaceId, agent, apiToken: token }) : []
|
|
136
|
+
if (body.agent?.known) console.log(`\nYa te conocía: "${body.agent.name}". Renové su token (el anterior quedó revocado).`)
|
|
137
|
+
else if (body.agent?.adopted) console.log(`\nTu agente "${agentLabel}" de antes quedó ligado a esta máquina como "${body.agent.name}".`)
|
|
138
|
+
else if (body.agent) console.log(`\nNuevo agente en tu equipo de Orquestra: "${body.agent.name}".`)
|
|
126
139
|
// La API de producción anterior a ORQ-388 no manda workspaceName.
|
|
127
140
|
const wsLabel = body.workspaceName ? ` del workspace "${body.workspaceName}"` : ''
|
|
128
141
|
let defaultProjectId
|
|
@@ -173,20 +186,64 @@ export async function runSetup() {
|
|
|
173
186
|
: folderAnswer.trim()
|
|
174
187
|
const folderPath = expanded ? resolve(expanded) : process.cwd()
|
|
175
188
|
|
|
176
|
-
upsertProjectMapping({ apiBase, apiToken: token, workspaceId, folderPath, projectId: defaultProjectId })
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
console.log('\
|
|
183
|
-
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')
|
|
189
|
-
console.log(' ¿Usas Cursor, ChatGPT/Codex o Gemini CLI? La config de cada uno está en:')
|
|
190
|
-
console.log(' https://www.orquestra.me/mcp#instalacion\n')
|
|
189
|
+
upsertProjectMapping({ apiBase, apiToken: token, workspaceId, folderPath, projectId: defaultProjectId, agent })
|
|
190
|
+
const others = renewedFolders.filter(f => f !== folderPath)
|
|
191
|
+
console.log(`\nListo. "${folderPath}" (y sus subcarpetas) ya apunta a este proyecto con ${agentLabel} -- guardado en ${CONFIG_PATH}.`)
|
|
192
|
+
if (others.length) console.log(`También actualicé el token de ${agentLabel} en: ${others.join(', ')}.`)
|
|
193
|
+
console.log('\nSiguiente paso -- conéctalo a tu herramienta (si ya estaba conectada, solo reconecta el MCP):\n')
|
|
194
|
+
console.log(connectionHelp(agent, agentLabel))
|
|
195
|
+
console.log('\n¿Otra herramienta en esta misma carpeta? Corre de nuevo "npx orquestra-mcp@latest setup" y elígela:')
|
|
196
|
+
console.log('cada una firma con su propio agente.')
|
|
191
197
|
console.log('Docs completas y todas las tools disponibles: https://www.orquestra.me/mcp')
|
|
192
198
|
}
|
|
199
|
+
|
|
200
|
+
// Sin preguntar "¿cómo se llama?" cada vez: se elige de la lista (o viene con
|
|
201
|
+
// --agent) y se marca lo que ya está conectado en esta máquina.
|
|
202
|
+
async function chooseTool(preset, machine) {
|
|
203
|
+
if (preset?.trim()) return TOOLS.find(t => agentKey(t) === agentKey(preset)) || preset.trim()
|
|
204
|
+
const connected = connectedAgents()
|
|
205
|
+
const already = TOOLS.filter(t => connected.has(agentKey(t)))
|
|
206
|
+
console.log(`\nEsta máquina: ${machine.name}${already.length ? ` (ya conectadas: ${already.join(', ')})` : ''}`)
|
|
207
|
+
console.log('¿Para qué herramienta es esta conexión?')
|
|
208
|
+
TOOLS.forEach((t, i) => console.log(` ${i + 1}. ${t}${connected.has(agentKey(t)) ? ' (ya conectada: renueva su token)' : ''}`))
|
|
209
|
+
console.log(` ${TOOLS.length + 1}. Otra`)
|
|
210
|
+
for (;;) {
|
|
211
|
+
const answer = (await prompt('Número [Enter para Claude Code]: ')).trim()
|
|
212
|
+
const idx = answer === '' ? 1 : parseInt(answer, 10)
|
|
213
|
+
if (idx >= 1 && idx <= TOOLS.length) return TOOLS[idx - 1]
|
|
214
|
+
if (idx === TOOLS.length + 1) {
|
|
215
|
+
const name = (await prompt('Nombre de la herramienta: ')).trim()
|
|
216
|
+
if (name) return name
|
|
217
|
+
}
|
|
218
|
+
console.log('Selección inválida.')
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// La config de cada herramienta lleva ORQUESTRA_AGENT para que use SU token
|
|
223
|
+
// (sin eso usaría el default de la carpeta y firmaría como otra).
|
|
224
|
+
// En Windows Claude Code necesita `cmd /c` para lanzar npx, y el config de
|
|
225
|
+
// Claude Desktop vive en %APPDATA%. `@latest` evita que npx reuse una
|
|
226
|
+
// versión vieja de su caché.
|
|
227
|
+
function connectionHelp(agent, agentLabel) {
|
|
228
|
+
const isWindows = process.platform === 'win32'
|
|
229
|
+
const json = file => ` ${agentLabel} -- agrega esto a ${file}:\n` +
|
|
230
|
+
` { "mcpServers": { "orquestra": { "command": "npx", "args": ["-y", "orquestra-mcp@latest"], "env": { "ORQUESTRA_AGENT": "${agent}" } } } }`
|
|
231
|
+
if (agent === 'claude-code') {
|
|
232
|
+
return ' Claude Code:\n' +
|
|
233
|
+
` claude mcp add orquestra -e ORQUESTRA_AGENT=${agent} -- ${isWindows ? 'cmd /c ' : ''}npx -y orquestra-mcp@latest\n` +
|
|
234
|
+
(isWindows ? ' (si "claude" no se reconoce: npm install -g @anthropic-ai/claude-code y abre otra terminal)\n' : '') +
|
|
235
|
+
' (si ya estaba agregado: claude mcp remove orquestra y vuelve a agregarlo)\n' +
|
|
236
|
+
' Después reinicia Claude Code o reconéctalo con /mcp.'
|
|
237
|
+
}
|
|
238
|
+
if (agent === 'codex') {
|
|
239
|
+
return ' Codex -- en ~/.codex/config.toml:\n' +
|
|
240
|
+
' [mcp_servers.orquestra]\n' +
|
|
241
|
+
' command = "npx"\n' +
|
|
242
|
+
' args = ["-y", "orquestra-mcp@latest"]\n' +
|
|
243
|
+
` env = { ORQUESTRA_AGENT = "${agent}" }`
|
|
244
|
+
}
|
|
245
|
+
if (agent === 'cursor') return json('~/.cursor/mcp.json')
|
|
246
|
+
if (agent === 'gemini-cli') return json('~/.gemini/settings.json')
|
|
247
|
+
if (agent === 'claude-desktop') return json(`${isWindows ? '%APPDATA%\\Claude\\' : '~/Library/Application Support/Claude/'}claude_desktop_config.json`)
|
|
248
|
+
return json('la config de MCP de tu herramienta') + '\n Guía por herramienta: https://www.orquestra.me/mcp#instalacion'
|
|
249
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { homedir } from 'node:os'
|
|
3
|
+
import { dirname, join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
// Aviso de versión nueva (ORQ-415): al arrancar, el server pregunta a npm cuál
|
|
6
|
+
// es la última versión publicada (caché de 12 h en ~/.orquestra, así casi
|
|
7
|
+
// ningún arranque sale a la red) y get_project_context la devuelve en
|
|
8
|
+
// `orquestraMcpUpdate` para que el agente le diga al usuario cómo actualizar.
|
|
9
|
+
// Nunca bloquea el arranque ni falla: sin red simplemente no hay aviso.
|
|
10
|
+
const CACHE_PATH = join(homedir(), '.orquestra', 'update-check.json')
|
|
11
|
+
const CACHE_TTL_MS = 12 * 60 * 60 * 1000
|
|
12
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/orquestra-mcp/latest'
|
|
13
|
+
|
|
14
|
+
let latestKnown = null
|
|
15
|
+
|
|
16
|
+
export function isNewer(latest, current) {
|
|
17
|
+
const parts = v => String(v).split('-')[0].split('.').map(n => parseInt(n, 10) || 0)
|
|
18
|
+
const [a, b] = [parts(latest), parts(current)]
|
|
19
|
+
for (let i = 0; i < 3; i++) {
|
|
20
|
+
if ((a[i] || 0) !== (b[i] || 0)) return (a[i] || 0) > (b[i] || 0)
|
|
21
|
+
}
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function startUpdateCheck({ fetchImpl = fetch, now = Date.now() } = {}) {
|
|
26
|
+
if (process.env.ORQUESTRA_NO_UPDATE_CHECK) return null
|
|
27
|
+
try {
|
|
28
|
+
const cached = JSON.parse(readFileSync(CACHE_PATH, 'utf8'))
|
|
29
|
+
if (cached.latest && now - cached.checkedAt < CACHE_TTL_MS) return (latestKnown = cached.latest)
|
|
30
|
+
} catch {}
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetchImpl(REGISTRY_URL, { signal: AbortSignal.timeout(3000) })
|
|
33
|
+
if (!res.ok) return null
|
|
34
|
+
const { version } = await res.json()
|
|
35
|
+
if (!version) return null
|
|
36
|
+
latestKnown = version
|
|
37
|
+
mkdirSync(dirname(CACHE_PATH), { recursive: true })
|
|
38
|
+
writeFileSync(CACHE_PATH, JSON.stringify({ latest: version, checkedAt: now }))
|
|
39
|
+
return version
|
|
40
|
+
} catch {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function updateNotice(current) {
|
|
46
|
+
if (!latestKnown || !isNewer(latestKnown, current)) return null
|
|
47
|
+
return {
|
|
48
|
+
current,
|
|
49
|
+
latest: latestKnown,
|
|
50
|
+
message:
|
|
51
|
+
`Hay una versión nueva de orquestra-mcp (${latestKnown}; esta sesión usa ${current}). ` +
|
|
52
|
+
`Para actualizar, reinicia tu herramienta o reconecta el MCP (en Claude Code: /mcp → orquestra → Reconnect). ` +
|
|
53
|
+
`Si sigue la versión vieja, en la config del MCP cambia "orquestra-mcp" por "orquestra-mcp@latest" ` +
|
|
54
|
+
`(sin @latest, npx reusa la versión que tiene en caché). Novedades: https://www.orquestra.me/mcp`,
|
|
55
|
+
}
|
|
56
|
+
}
|