@runeya/runeya 2.0.145 → 2.0.147
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/agent/index.js +36 -25
- package/agent/index.js.map +1 -1
- package/agent-manager-46C4NIQ2.js +13 -0
- package/chat-CMNDMXIP.js +28 -0
- package/{chunk-ONVAH65J.js → chunk-2NE52CL5.js} +422 -2172
- package/chunk-2NE52CL5.js.map +1 -0
- package/chunk-4D3GCBPO.js +462 -0
- package/chunk-4D3GCBPO.js.map +1 -0
- package/{chunk-NCO4OOYT.js → chunk-4LJQIQMN.js} +2 -2
- package/{chunk-QF56Z6LI.js → chunk-5XG3X2TM.js} +29 -1481
- package/chunk-5XG3X2TM.js.map +1 -0
- package/chunk-6IBPK6UE.js +1063 -0
- package/chunk-6IBPK6UE.js.map +1 -0
- package/chunk-7QXP4FXD.js +969 -0
- package/chunk-7QXP4FXD.js.map +1 -0
- package/chunk-DA2DZBTL.js +43 -0
- package/chunk-DA2DZBTL.js.map +1 -0
- package/chunk-DFPZ4MTI.js +1041 -0
- package/chunk-DFPZ4MTI.js.map +1 -0
- package/{chunk-Q4RPY3VW.js → chunk-JNK6H5KH.js} +185 -1014
- package/chunk-JNK6H5KH.js.map +1 -0
- package/{chunk-FE53XX4M.js → chunk-NPMSBYXC.js} +3 -3
- package/{chunk-KS7A5CTW.js → chunk-VPSQJOSN.js} +12 -2
- package/{chunk-KS7A5CTW.js.map → chunk-VPSQJOSN.js.map} +1 -1
- package/chunk-WDHLEO5S.js +809 -0
- package/chunk-WDHLEO5S.js.map +1 -0
- package/{companion-HBYSY2D5.js → companion-XKMNRIJB.js} +10 -5
- package/environment-resolver-ZDTF3MHI.js +18 -0
- package/external-NMKQPROK.js +387 -0
- package/external-NMKQPROK.js.map +1 -0
- package/index.js +15 -6
- package/index.js.map +1 -1
- package/{install-gate-JAEK4BDZ.js → install-gate-OJJEUU4E.js} +2 -2
- package/mcp-ready-TAPBAOZY.js +9 -0
- package/mcp-server/external.d.ts +44 -0
- package/mcp-server/external.js +2679 -0
- package/mcp-server/external.js.map +1 -0
- package/mcp-server/index.js +159 -39
- package/mcp-server/index.js.map +1 -1
- package/mcp-server/launch.d.ts +25 -1
- package/mcp-server/launch.js +22 -1
- package/mcp-server/launch.js.map +1 -1
- package/package.json +1 -1
- package/project-store-FLXTAD3J.js +23 -0
- package/project-store-FLXTAD3J.js.map +1 -0
- package/settings-manager-XPLBO35P.js +10 -0
- package/settings-manager-XPLBO35P.js.map +1 -0
- package/{src-IODFUZ7A.js → src-KWO3CKZU.js} +1083 -551
- package/src-KWO3CKZU.js.map +1 -0
- package/{vault-resolver-PN3TYCLJ.js → vault-resolver-E7KPKA5C.js} +2 -2
- package/vault-resolver-E7KPKA5C.js.map +1 -0
- package/web/assets/{AgentScalarChatInterface.vue-npQgDxeP.js → AgentScalarChatInterface.vue-DKBinB_l.js} +2 -2
- package/web/assets/AgentTerminalPage-CixU7x6O.js +1 -0
- package/web/assets/AiChatModals-BvGUd-QD.css +1 -0
- package/web/assets/AiChatModals-C-hXImkO.js +4 -0
- package/web/assets/AppLayout-At9wk0sx.js +1 -0
- package/web/assets/{AppLayout-tsw5Ow0w.css → AppLayout-BlhwflMq.css} +1 -1
- package/web/assets/AuditPage-BkqbHxjY.js +1 -0
- package/web/assets/CloudAccountPage-D5C2SCS9.js +1 -0
- package/web/assets/CloudMessagingPage-Bi57GGHO.js +2 -0
- package/web/assets/DashboardPage-dEQop9ql.js +1 -0
- package/web/assets/DirectoryPickerDialog-DIMSeDhw.js +1 -0
- package/web/assets/{EditorIcon-CypGLV6w.js → EditorIcon-W3Szca4J.js} +1 -1
- package/web/assets/EnvironmentForm-C5emavNh.js +1 -0
- package/web/assets/EnvironmentFormPage-BIoy8fsW.js +1 -0
- package/web/assets/EnvironmentListPage-BDYeh7XF.js +1 -0
- package/web/assets/{FlagChip-R_T5qYpL.js → FlagChip-DPtIBCjQ.js} +1 -1
- package/web/assets/KanbanBoardPage-BlCcjBwV.js +4 -0
- package/web/assets/KanbanListPage-DbXONxfC.js +1 -0
- package/web/assets/LaunchProjectPage-Cg92hHwQ.js +1 -0
- package/web/assets/LaunchServicesPage-CO8gOB9D.js +1 -0
- package/web/assets/LaunchServicesPage-h5o3zLcy.css +1 -0
- package/web/assets/{LaunchShell-CGNYr-do.js → LaunchShell-CDb21ihZ.js} +1 -1
- package/web/assets/LogViewer-DsusKk2b.js +3 -0
- package/web/assets/OrgKeyPrompt-CX9K7r9I.js +1 -0
- package/web/assets/ProcessControls-B7hWVqiR.css +1 -0
- package/web/assets/ProcessControls-TEZutuxq.js +1 -0
- package/web/assets/ProcessDetailPage-Cj23FECq.js +1 -0
- package/web/assets/ProcessMetrics-1P6iE2Il.js +1 -0
- package/web/assets/ProjectCreateDialog-D3aMWKfw.js +1 -0
- package/web/assets/{ProjectLocationGate-bUd_tFpI.js → ProjectLocationGate-DU0Jj8Nw.js} +1 -1
- package/web/assets/ProjectPublishDialog-D7sNKei3.js +1 -0
- package/web/assets/{RandomJobTestPage-BFNC827R.js → RandomJobTestPage-DQUMRHyf.js} +1 -1
- package/web/assets/{ScenarioEditorPage-vMoRt0Qn.css → ScenarioEditorPage-BSjSX4bw.css} +1 -1
- package/web/assets/ScenarioEditorPage-DvCdcY9m.js +14 -0
- package/web/assets/SendToChatDialog-BdWKjYmv.js +1 -0
- package/web/assets/ServiceControlsBar-B_J5m-Kc.js +1 -0
- package/web/assets/ServiceDetailPage-DhJcjFfY.js +2 -0
- package/web/assets/{ServiceDetailPage-COdaCZ6r.css → ServiceDetailPage-DnSf5cm9.css} +1 -1
- package/web/assets/ServiceFormPage-fp8NCHSv.js +1 -0
- package/web/assets/SettingsPage-NH2fQap4.css +1 -0
- package/web/assets/SettingsPage-s28BodWe.js +2086 -0
- package/web/assets/UnlockPage-BXXIPHW_.js +1 -0
- package/web/assets/VaultMigrationPage-BfNuKtkJ.js +1 -0
- package/web/assets/{VersionBadge-DBquJl76.js → VersionBadge-CW7czacg.js} +1 -1
- package/web/assets/{WikiPage-GtwpJPqf.js → WikiPage-BiwpCM3E.js} +1 -1
- package/web/assets/WikiView-CMch_06Z.js +1 -0
- package/web/assets/{add-scalar-classes-DhSSqZ5l.js → add-scalar-classes-Ckanjq1h.js} +14 -14
- package/web/assets/{agent.store-Co44dMZf.js → agent.store-piokQ9Dt.js} +1 -1
- package/web/assets/{auth-Bk1GP5sB.js → auth-BT_jf-5e.js} +2 -2
- package/web/assets/cloud-chat.store-CDKh4tIy.js +1 -0
- package/web/assets/{cloud-client-0zD_NI6q.js → cloud-client-CzPdZLDq.js} +2 -2
- package/web/assets/{cloud-session-Cjw54Y--.js → cloud-session-BYfn6YM4.js} +2 -2
- package/web/assets/floating-ui.dom-DF3oyfHd.js +1 -0
- package/web/assets/i18n-D0FT7rCj.js +1 -0
- package/web/assets/index-3LTzgeU-.css +1 -0
- package/web/assets/index-Ci2CNnit.js +309 -0
- package/web/assets/modal-iRaP1izE.js +5 -0
- package/web/assets/notification.store-CvvuTBVz.js +1 -0
- package/web/assets/{project-launch.store-B5nhdGdh.js → project-launch.store-CTmbl8UY.js} +1 -1
- package/web/assets/{project.store-Bm2JWNbd.js → project.store-CNC6_4r4.js} +1 -1
- package/web/assets/runtime-dom.esm-bundler-CyEVRK6B.js +1 -0
- package/web/assets/scenario.store-BIFQPXgl.js +7 -0
- package/web/assets/{service.store-XS50iGKo.js → service.store-B40U4QzI.js} +1 -1
- package/web/assets/settings-scope-CyWMCjRY.js +1 -0
- package/web/assets/settings.store-Dw6bAoWE.js +1 -0
- package/web/assets/{src-Cgix2VOi.js → src-CrNi4Pjg.js} +3 -3
- package/web/assets/src-DIBHat7I.js +3 -0
- package/web/assets/src-DtCW_XCh.js +1 -0
- package/web/assets/{src-BDZKZr9n.css → src-g1DRo8xZ.css} +1 -1
- package/web/assets/{trpc-CfuFkKgp.js → trpc-BAZGRZBD.js} +3 -3
- package/web/assets/{useCloudOrg-J8DAnocE.js → useCloudOrg-EnXFBwt1.js} +1 -1
- package/web/assets/useEnvVarAutocomplete-BxJh2Lw0.js +1 -0
- package/web/assets/useProjectCatalog-BrvUGv9A.js +1 -0
- package/web/assets/{useProjectCloudOrg-BnlKts7x.js → useProjectCloudOrg-Czxlg00k.js} +1 -1
- package/web/assets/{useServiceUrlHost-hKbKtPMu.js → useServiceUrlHost-Szz9y3rh.js} +1 -1
- package/web/assets/vue-i18n-o7yraQpR.js +3 -0
- package/web/index.html +21 -21
- package/agent-manager-THV3LJOF.js +0 -11
- package/chat-XYCHMTFE.js +0 -23
- package/chunk-ONVAH65J.js.map +0 -1
- package/chunk-Q4RPY3VW.js.map +0 -1
- package/chunk-QF56Z6LI.js.map +0 -1
- package/settings-manager-QVQRJO6V.js +0 -10
- package/src-IODFUZ7A.js.map +0 -1
- package/web/assets/AgentTerminalPage-BTH3yCS2.js +0 -1
- package/web/assets/AiChatModals-EfRuvId7.css +0 -1
- package/web/assets/AiChatModals-It9jnB26.js +0 -4
- package/web/assets/AppLayout-BTg2CADo.js +0 -1
- package/web/assets/AuditPage-2PKKGLZs.js +0 -1
- package/web/assets/CloudAccountPage-CHL61gLe.js +0 -1
- package/web/assets/CloudMessagingPage-cgKnABPH.js +0 -2
- package/web/assets/DashboardPage-CRntTMKY.js +0 -1
- package/web/assets/DirectoryPickerDialog-DCyhT3-W.js +0 -1
- package/web/assets/EnvironmentForm-C96PFWRE.js +0 -1
- package/web/assets/EnvironmentFormPage-ChorAd63.js +0 -1
- package/web/assets/EnvironmentListPage-Dw9ziaI8.js +0 -1
- package/web/assets/KanbanBoardPage-BZHbEj-q.js +0 -4
- package/web/assets/KanbanListPage-B0jlfKqs.js +0 -1
- package/web/assets/LaunchProjectPage-BVs1JwbV.js +0 -1
- package/web/assets/LaunchServicesPage-DoLDPqQq.css +0 -1
- package/web/assets/LaunchServicesPage-PBYJly9N.js +0 -1
- package/web/assets/LogViewer-BDObx29F.js +0 -3
- package/web/assets/OrgKeyPrompt-BbcwiwGw.js +0 -1
- package/web/assets/ProcessControls-BlpnVylq.css +0 -1
- package/web/assets/ProcessControls-DY0HTKde.js +0 -1
- package/web/assets/ProcessDetailPage-BZ9kIQqn.js +0 -1
- package/web/assets/ProcessMetrics-Crlfkelj.js +0 -1
- package/web/assets/ProjectCreateDialog-C1wVDPcK.js +0 -1
- package/web/assets/ProjectPublishDialog-APUS1WEN.js +0 -1
- package/web/assets/ScenarioEditorPage-DFVsMVu5.js +0 -14
- package/web/assets/SendToChatDialog-QHIkxNio.js +0 -1
- package/web/assets/ServiceControlsBar-_UQXb6g0.js +0 -1
- package/web/assets/ServiceDetailPage-DGIXgHwR.js +0 -2
- package/web/assets/ServiceFormPage-RliAfS3_.js +0 -1
- package/web/assets/SettingsPage-BSSHxxjM.js +0 -2086
- package/web/assets/SettingsPage-W8YszhUL.css +0 -1
- package/web/assets/UnlockPage-17EgfpJs.js +0 -1
- package/web/assets/VaultMigrationPage-DeMxpXXJ.js +0 -1
- package/web/assets/WikiView-BqaqxMxW.js +0 -1
- package/web/assets/cloud-chat.store-CadB14dZ.js +0 -1
- package/web/assets/editor.store-DmM6ZlTm.js +0 -1
- package/web/assets/floating-ui.dom-DWhsZyqT.js +0 -1
- package/web/assets/i18n-8FjeJaKW.js +0 -1
- package/web/assets/index-B9DJf2V0.js +0 -309
- package/web/assets/index-teBqdnuv.css +0 -1
- package/web/assets/modal-UK8xRSnh.js +0 -5
- package/web/assets/scenario.store-88w6P6s2.js +0 -7
- package/web/assets/settings-scope-CKPq86HZ.js +0 -1
- package/web/assets/settings.store-BCJ0LJQA.js +0 -1
- package/web/assets/src--GO-VSJ0.js +0 -5
- package/web/assets/src-DBipSD56.js +0 -1
- package/web/assets/useEnvVarAutocomplete-C6zOXYqi.js +0 -1
- package/web/assets/useProjectCatalog-BB5IR6o8.js +0 -1
- package/web/assets/vue-draggable-plus-BHsveB8w.css +0 -1
- package/web/assets/vue-draggable-plus-D5ni4jMk.js +0 -1
- /package/{agent-manager-THV3LJOF.js.map → agent-manager-46C4NIQ2.js.map} +0 -0
- /package/{chat-XYCHMTFE.js.map → chat-CMNDMXIP.js.map} +0 -0
- /package/{chunk-NCO4OOYT.js.map → chunk-4LJQIQMN.js.map} +0 -0
- /package/{chunk-FE53XX4M.js.map → chunk-NPMSBYXC.js.map} +0 -0
- /package/{companion-HBYSY2D5.js.map → companion-XKMNRIJB.js.map} +0 -0
- /package/{settings-manager-QVQRJO6V.js.map → environment-resolver-ZDTF3MHI.js.map} +0 -0
- /package/{install-gate-JAEK4BDZ.js.map → install-gate-OJJEUU4E.js.map} +0 -0
- /package/{vault-resolver-PN3TYCLJ.js.map → mcp-ready-TAPBAOZY.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../packages/mcp-server/src/external.ts","../../../packages/mcp-server/src/project-anchors.ts","../../../packages/mcp-server/src/session-token.ts","../../../packages/mcp-server/src/instance-selector.ts","../../../packages/mcp-server/src/pairing-client.ts","../../../packages/mcp-server/src/server.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { callTrpc } from '@runeya/packages-ai-capabilities';\nimport { z } from 'zod';\nimport { createProjectAnchors } from './project-anchors.js';\nimport { startSessionToken } from './session-token.js';\nimport { createInstanceSelector, instanceUrl, type McpInstance } from './instance-selector.js';\nimport { PairingClient } from './pairing-client.js';\nimport { createRuneyaMcpServer, registerInstanceTools } from './server.js';\n\nexport interface ExternalMcpOptions {\n listInstances: () => Promise<McpInstance[]>;\n /** La racine machine de Runeya (`~/.runeya`) : le secret d'appairage y est rangé. */\n machineRoot?: string;\n}\n\n/**\n * Le MCP lancé par un client externe (`runeya mcp`) : Claude Code, Codex,\n * Cursor… configuré une fois, il suit les mises à jour du CLI.\n *\n * L'instance à appeler vient du registre machine (voir `instance-selector.ts`),\n * sauf si `RUNEYA_BASE_URL` la fixe. Le jeton vient de l'appairage avec cette\n * instance (voir `pairing-client.ts`), sauf si `RUNEYA_API_TOKEN` en donne un.\n */\nexport async function runExternalMcp(opts: ExternalMcpOptions): Promise<void> {\n const fixedUrl = process.env['RUNEYA_BASE_URL'];\n // Le registre porte aussi de quoi relancer une instance (argv, environnement) :\n // rien de cela n'a à entrer dans le contexte du modèle.\n const selector = createInstanceSelector(async () =>\n (await opts.listInstances()).map(({ pid, port, cwd, dataDir, version }) => ({ pid, port, cwd, dataDir, version })));\n // Une clé d'API n'est pas un JWT : `startSessionToken` ne la renouvelle pas,\n // et l'URL de renouvellement ne sert donc jamais.\n const sessionToken = startSessionToken(process.env['RUNEYA_API_TOKEN'] ?? '', fixedUrl ?? '');\n // `server` est déclaré plus bas : le nom n'est lu qu'au premier appel d'outil,\n // une fois la connexion MCP établie.\n const pairing = new PairingClient(\n // Même règle que `machineRoot()` du serveur : `RUNEYA_HOME` déplace la racine machine.\n join(opts.machineRoot ?? join(process.env['RUNEYA_HOME'] || homedir(), '.runeya'), 'mcp'),\n () => server.server.getClientVersion() ?? { name: '' },\n process.cwd(),\n );\n\n const envAnchor = {\n projectId: process.env['RUNEYA_PROJECT_ID'] || undefined,\n orgId: process.env['RUNEYA_ORG_ID'] || undefined,\n };\n const anchorFor = createProjectAnchors(envAnchor, process.cwd(), (baseUrl, authToken, path) =>\n callTrpc(baseUrl, 'project.containing', 'GET', { path }, authToken));\n\n const server = createRuneyaMcpServer({\n target: async (input) => {\n // Une URL fixée n'a pas de dossier de données connu : l'URL sert alors de clé\n // au secret d'appairage.\n const pid = typeof input['runeyaInstance'] === 'number' ? input['runeyaInstance'] : undefined;\n const { baseUrl, key } = fixedUrl\n ? { baseUrl: fixedUrl, key: fixedUrl }\n : await selector.instance(pid).then((i) => ({ baseUrl: instanceUrl(i), key: i.dataDir }));\n // Un jeton donné explicitement passe devant l'appairage.\n if (sessionToken.current()) {\n const authToken = sessionToken.current();\n return { baseUrl, authToken, projectAnchor: () => anchorFor(key, baseUrl, authToken) };\n }\n const authToken = await pairing.token({ baseUrl, key });\n return {\n baseUrl,\n authToken,\n projectAnchor: () => anchorFor(key, baseUrl, authToken),\n forgetToken: () => pairing.forget({ baseUrl, key }),\n };\n },\n sessionToken,\n projectAnchor: envAnchor,\n extraShape: fixedUrl ? undefined : {\n runeyaInstance: z.number().int().positive().optional().describe(\n 'The pid of the Runeya instance to call, from list_local_runeya_instances. Needed only when several instances are running: then pass it on every call of this conversation.',\n ),\n },\n });\n if (!fixedUrl) registerInstanceTools(server, selector);\n\n await server.connect(new StdioServerTransport());\n}\n","export interface ProjectAnchor {\n projectId?: string | undefined;\n orgId?: string | undefined;\n}\n\ntype Containing = (baseUrl: string, authToken: string, path: string) => Promise<unknown>;\n\n/**\n * Le projet d'ancrage d'un MCP lancé hors de Runeya : celui que nomme\n * `RUNEYA_PROJECT_ID`, sinon celui dont le dossier contient le dossier où\n * l'outil IA a été lancé (`project.containing`). Sans lui, `list_services` rend\n * tous les projets de la machine.\n *\n * Demandé une fois par instance (`key`). Hors de tout projet, rien n'est\n * ancré : l'outil a été lancé ailleurs, toute la machine est voulue. Un échec\n * de la demande, lui, fait échouer l'appel avec une consigne : se rabattre sur\n * toute la machine sans le dire referait le défaut que l'ancrage corrige. La\n * demande est refaite au prochain appel.\n */\nexport function createProjectAnchors(env: ProjectAnchor, cwd: string, containing: Containing) {\n const anchors = new Map<string, Promise<ProjectAnchor>>();\n return (key: string, baseUrl: string, authToken: string): Promise<ProjectAnchor> => {\n if (env.projectId) return Promise.resolve(env);\n let anchor = anchors.get(key);\n if (!anchor) {\n anchor = containing(baseUrl, authToken, cwd)\n .then((found) => {\n const p = found as { projectId: string; orgId: string | null } | null;\n return p ? { projectId: p.projectId, ...(p.orgId ? { orgId: p.orgId } : {}) } : {};\n })\n .catch((err: unknown) => {\n anchors.delete(key);\n throw new Error(`Impossible de savoir à quel projet Runeya appartient le dossier ${cwd} (${(err as Error).message}). Nomme le projet : appelle list_projects, puis passe son projectId.`);\n });\n anchors.set(key, anchor);\n }\n return anchor;\n };\n}\n","/**\n * Le jeton que le MCP présente au serveur Runeya, renouvelé avant qu'il expire.\n *\n * Le CLI reçoit un JWT à son lancement et garde le même processus MCP pendant\n * toute la conversation : sans renouvellement, les tools répondent 401 une fois\n * la durée du jeton écoulée (8 h par défaut). Même mécanisme que l'onglet :\n * `POST /api/auth/refresh` à la moitié de la durée restante. Un jeton déjà\n * expiré, par exemple après une veille plus longue que sa durée, n'est plus\n * renouvelable.\n */\n\nconst RETRY_DELAY_MS = 1000;\n\nexport interface SessionToken {\n /** Le jeton à envoyer maintenant. */\n current(): string;\n /** Vrai pour le jeton reçu au lancement, que le modèle peut recopier tel quel. */\n isInitial(token: string): boolean;\n stop(): void;\n}\n\n/** Expiry of a JWT, in Unix seconds. `null` when the token cannot be read. */\nexport function jwtExpiresAt(token: string): number | null {\n try {\n const payload = JSON.parse(Buffer.from(token.split('.')[1] ?? '', 'base64url').toString('utf8')) as { exp?: unknown };\n return typeof payload.exp === 'number' ? payload.exp : null;\n } catch {\n return null;\n }\n}\n\nexport function startSessionToken(initial: string, baseUrl: string, fetchImpl: typeof fetch = fetch): SessionToken {\n let token = initial;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let stopped = false;\n\n const arm = (delayMs: number) => {\n if (stopped) return;\n timer = setTimeout(() => { void refresh(); }, delayMs);\n // Le minuteur ne doit pas retenir le processus quand le CLI ferme stdin.\n timer.unref?.();\n };\n\n const schedule = () => {\n const exp = jwtExpiresAt(token);\n if (exp === null) return;\n const remainingMs = exp * 1000 - Date.now();\n if (remainingMs > 0) arm(remainingMs / 2);\n };\n\n const retry = () => {\n const exp = jwtExpiresAt(token);\n if (exp !== null && exp * 1000 - Date.now() > RETRY_DELAY_MS) arm(RETRY_DELAY_MS);\n };\n\n async function refresh(): Promise<void> {\n timer = null;\n let res: Response;\n try {\n res = await fetchImpl(`${baseUrl}/api/auth/refresh`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${token}` },\n signal: AbortSignal.timeout(5000),\n });\n } catch {\n retry();\n return;\n }\n if (res.status === 401) return;\n if (!res.ok) { retry(); return; }\n const body = await res.json().catch(() => null) as { token?: unknown } | null;\n if (typeof body?.token !== 'string') { retry(); return; }\n token = body.token;\n schedule();\n }\n\n if (initial) schedule();\n\n return {\n current: () => token,\n isInitial: (candidate) => candidate === initial,\n stop: () => {\n stopped = true;\n if (timer) clearTimeout(timer);\n timer = null;\n },\n };\n}\n","/**\n * Quelle instance Runeya le MCP appelle, quand il n'est pas lancé par Runeya.\n *\n * Lancé par Runeya, le MCP reçoit `RUNEYA_BASE_URL` et n'a rien à choisir.\n * Lancé par un client externe (`runeya mcp`), il lit le registre machine :\n * - aucune instance : rien à appeler, l'outil échoue en le disant ;\n * - une seule : elle est prise d'office ;\n * - plusieurs : l'appel doit nommer l'instance par son pid\n * (`runeyaInstance`), que l'utilisateur choisit dans la conversation.\n *\n * Le choix n'est pas gardé ici. Un client comme Cursor ou Claude Desktop garde\n * un seul process MCP pour toutes ses conversations : un choix mémorisé dans\n * l'une s'appliquerait en silence aux autres. Le modèle le porte donc dans\n * chaque appel, et chaque conversation a le sien.\n *\n * Le pid, pas le port : une instance qui s'arrête ne fait pas atterrir les\n * appels sur celle qui reprendrait son port. La liste est relue à chaque appel.\n */\n\n/** Ce dont le sélecteur a besoin d'une instance du registre. */\nexport interface McpInstance {\n pid: number;\n port: number;\n cwd: string;\n /** Le dossier de données de l'instance : stable d'un redémarrage à l'autre. */\n dataDir: string;\n version?: string | undefined;\n}\n\nexport interface InstanceSelector {\n /** L'instance à appeler (`pid` si l'appel en nomme une), ou une erreur qui dit quoi faire. */\n instance(pid?: number): Promise<McpInstance>;\n /** Les instances vivantes. */\n list(): Promise<McpInstance[]>;\n}\n\nexport function instanceUrl(i: McpInstance): string {\n return `http://localhost:${i.port}`;\n}\n\nfunction describe(i: McpInstance): string {\n return `pid ${i.pid} — port ${i.port} — ${i.cwd}${i.version ? ` (v${i.version})` : ''}`;\n}\n\nexport function createInstanceSelector(listInstances: () => Promise<McpInstance[]>): InstanceSelector {\n return {\n async instance(pid) {\n const instances = await listInstances();\n if (pid !== undefined) {\n const named = instances.find((i) => i.pid === pid);\n if (named) return named;\n throw new Error(`Aucune instance Runeya lancée avec le pid ${pid}. Instances en cours :\\n${instances.map((i) => `- ${describe(i)}`).join('\\n') || '(aucune)'}`);\n }\n if (instances.length === 0) {\n throw new Error(\"Runeya n'est pas lancé sur cette machine : aucune instance à contacter.\");\n }\n if (instances.length === 1) return instances[0]!;\n throw new Error(\n `Plusieurs instances Runeya sont lancées. Demande à l'utilisateur laquelle utiliser, puis passe son pid dans le paramètre runeyaInstance, à chaque appel de cette conversation :\\n${instances.map((i) => `- ${describe(i)}`).join('\\n')}`,\n );\n },\n\n list: listInstances,\n };\n}\n","import { createHash } from 'node:crypto';\nimport { mkdir, readFile, rm, writeFile, chmod } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { callTrpc } from '@runeya/packages-ai-capabilities';\nimport { jwtExpiresAt } from './session-token.js';\n\n/**\n * Le côté client de l'appairage (voir `apps/server/src/services/mcp-pairing.ts`).\n *\n * Aucun secret n'est écrit dans la config de Claude Code, Cursor… : le secret\n * d'appairage vit dans `~/.runeya/mcp/`, lisible par l'utilisateur seul, un\n * fichier par dossier de données d'instance et par client. Il survit donc aux redémarrages et\n * aux changements de port de l'instance.\n *\n * Un appel d'outil ne reste jamais bloqué en attendant l'accord : il échoue en\n * disant quoi faire, et l'appel suivant reprend où en est la demande.\n */\n\nexport interface ClientInfo {\n name: string;\n title?: string | undefined;\n}\n\n/**\n * Le nom montré à l'utilisateur dans la demande d'accès et la liste des accès :\n * le titre que le client annonce s'il en donne un, sinon son nom technique mis\n * en forme (`claude-code` → « Claude Code »).\n */\nexport function clientDisplayName(info: ClientInfo): string {\n const title = info.title?.trim();\n if (title) return title.slice(0, 100);\n const words = info.name.split(/[-_\\s]+/).filter(Boolean);\n const pretty = words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');\n return pretty.slice(0, 100) || 'Client MCP';\n}\n\nexport interface PairingTarget {\n baseUrl: string;\n /** Ce qui identifie l'instance d'un redémarrage à l'autre : son dossier de données. */\n key: string;\n}\n\ninterface PendingRequest {\n requestId: string;\n code: string;\n}\n\n/** Une marge pour ne pas envoyer un jeton qui expire en route. */\nconst EXPIRY_MARGIN_S = 30;\n\nexport class PairingClient {\n private tokens = new Map<string, string>();\n private pending = new Map<string, PendingRequest>();\n\n constructor(\n private readonly dir: string,\n /** Ce que le client annonce à la connexion MCP (`clientInfo`). */\n private readonly clientInfo: () => ClientInfo,\n private readonly cwd: string,\n private readonly fetchImpl: typeof fetch = fetch,\n ) {}\n\n /** Le nom technique du client : il distingue les secrets, il ne s'affiche pas. */\n private name(): string {\n return this.clientInfo().name.slice(0, 100) || 'Client MCP';\n }\n\n /**\n * Un secret par instance ET par client : accepter Claude Code ne donne pas\n * accès à Cursor, et chaque outil se révoque à part.\n */\n private secretFile(key: string): string {\n const id = createHash('sha256').update(`${key}\\0${this.name()}`).digest('hex').slice(0, 32);\n return join(this.dir, `${id}.json`);\n }\n\n private async readSecret(key: string): Promise<string | null> {\n try {\n const { secret } = JSON.parse(await readFile(this.secretFile(key), 'utf8')) as { secret?: unknown };\n return typeof secret === 'string' ? secret : null;\n } catch {\n return null;\n }\n }\n\n private async writeSecret(key: string, secret: string): Promise<void> {\n await mkdir(this.dir, { recursive: true, mode: 0o700 });\n const file = this.secretFile(key);\n await writeFile(file, JSON.stringify({ secret }), { mode: 0o600 });\n // `mode` ne s'applique qu'à la création : un fichier existant garde les siens.\n await chmod(file, 0o600);\n }\n\n private async call(target: PairingTarget, route: string, method: 'GET' | 'POST', input: Record<string, unknown>): Promise<unknown> {\n try {\n return await callTrpc(target.baseUrl, route, method, input, '', this.fetchImpl);\n } catch (err) {\n // Une instance antérieure à l'appairage ne connaît pas ces routes.\n if (/NOT_FOUND/.test((err as Error).message)) {\n throw new Error(`L'instance Runeya de ${target.baseUrl} est trop ancienne pour accepter ce client : mettez Runeya à jour, ou passez une clé d'API dans RUNEYA_API_TOKEN.`);\n }\n throw err;\n }\n }\n\n /**\n * Oublie le jeton en mémoire. Le serveur l'a refusé, typiquement après un\n * redémarrage (la clé de signature change) : le prochain `token()` le\n * réobtient avec le secret, sans nouvelle demande d'accès.\n */\n forget(target: PairingTarget): void {\n this.tokens.delete(target.key);\n }\n\n /** Un jeton pour cette instance, ou une erreur qui dit à l'utilisateur quoi faire. */\n async token(target: PairingTarget): Promise<string> {\n const cached = this.tokens.get(target.key);\n const exp = cached ? jwtExpiresAt(cached) : null;\n if (cached && exp !== null && exp - EXPIRY_MARGIN_S > Date.now() / 1000) return cached;\n this.tokens.delete(target.key);\n\n const secret = await this.readSecret(target.key);\n if (secret) {\n try {\n const { token } = await this.call(target, 'mcpPairing.exchange', 'POST', { secret }) as { token: string };\n this.tokens.set(target.key, token);\n return token;\n } catch (err) {\n if (!/UNAUTHORIZED/.test((err as Error).message)) throw err;\n // Appairage révoqué dans Runeya : le secret ne servira plus.\n await rm(this.secretFile(target.key), { force: true });\n }\n }\n\n return this.pair(target);\n }\n\n private async pair(target: PairingTarget): Promise<string> {\n const pending = this.pending.get(target.key);\n if (pending) {\n const { status, secret } = await this.call(target, 'mcpPairing.status', 'GET', { requestId: pending.requestId }) as { status: string; secret?: string };\n if (status === 'approved' && secret) {\n this.pending.delete(target.key);\n await this.writeSecret(target.key, secret);\n return this.token(target);\n }\n if (status === 'pending') {\n throw new Error(`Accès en attente : l'utilisateur doit accepter la demande dans Runeya (code ${pending.code}), puis relancer l'action.`);\n }\n this.pending.delete(target.key);\n if (status === 'denied') throw new Error('L\\'utilisateur a refusé l\\'accès dans Runeya.');\n // Expirée : on repart d'une nouvelle demande.\n }\n\n const { id, code } = await this.call(target, 'mcpPairing.request', 'POST', { name: clientDisplayName(this.clientInfo()), cwd: this.cwd }) as { id: string; code: string };\n this.pending.set(target.key, { requestId: id, code });\n throw new Error(`Runeya doit autoriser ce client. Une demande d'accès vient de s'afficher dans Runeya avec le code ${code} : demande à l'utilisateur de vérifier le code et d'accepter, puis relance l'action.`);\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport type { SessionToken } from './session-token.js';\nimport type { InstanceSelector } from './instance-selector.js';\nimport type { ProjectAnchor } from './project-anchors.js';\nimport { RUNEYA_CAPABILITIES, applyProjectAnchor, buildMcpShape, runCapability, toRawParams, type RuneyaCapability } from '@runeya/packages-ai-capabilities';\n\nexport interface RuneyaMcpOptions {\n /**\n * Le serveur Runeya à appeler, relu à chaque appel d'outil, et le jeton qui\n * lui est propre s'il en a un. Les deux viennent d'un même appel, pour viser\n * toujours la même instance.\n */\n target: (input: Record<string, unknown>) => Promise<{\n baseUrl: string;\n authToken?: string | undefined;\n /**\n * Le projet d'ancrage de cette instance. Une fonction : il n'est demandé que\n * pour un outil qui en a besoin, pour qu'un échec n'empêche pas d'appeler\n * `list_projects` et de nommer le projet à la main.\n */\n projectAnchor?: (() => Promise<ProjectAnchor>) | undefined;\n /** Le jeton a été refusé : l'oublier pour que le prochain `target()` en obtienne un neuf. */\n forgetToken?: (() => void) | undefined;\n }>;\n sessionToken: SessionToken;\n internalToken?: string;\n /** Le projet par défaut, quand `target` n'en donne pas. */\n projectAnchor: ProjectAnchor;\n /**\n * Paramètres ajoutés à chaque outil, lus par `target` et jamais transmis à la\n * route (le choix d'instance du MCP externe, `runeyaInstance`).\n */\n extraShape?: Record<string, z.ZodType> | undefined;\n}\n\n/**\n * L'outil accepte un projet ou une organisation et l'appel n'en donne pas : il\n * faut l'ancre. Les autres (`list_projects`, `wait`…) s'en passent.\n */\nexport function needsAnchor(cap: Pick<RuneyaCapability, 'params'>, params: Record<string, string | undefined>): boolean {\n const accepts = (name: string) => cap.params.some((p) => p.name === name);\n // Une portée nommée par l'appel (projet ou organisation) se passe d'ancre :\n // y ajouter le projet courant le mêlerait à une autre organisation.\n if (params['orgId'] || params['organizationId']) return false;\n if (accepts('projectId')) return !params['projectId'];\n return ['orgId', 'organizationId'].some((name) => accepts(name) && !params[name]);\n}\n\nconst RETRY = Symbol('retry');\n\ntype ToolResult = { content: { type: 'text'; text: string }[]; isError?: boolean };\n\nfunction text(value: unknown): ToolResult {\n return { content: [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }] };\n}\n\nfunction error(err: unknown): ToolResult {\n return { content: [{ type: 'text', text: `Error: ${(err as Error).message}` }], isError: true };\n}\n\n/** Le serveur MCP Runeya : un outil par capacité de `RUNEYA_CAPABILITIES`, et le prompt `work_on_card`. */\nexport function createRuneyaMcpServer(opts: RuneyaMcpOptions): McpServer {\n const server = new McpServer({ name: 'runeya', version: '1.0.0' });\n\n /**\n * Appeler une capacité : instance, jeton, ancrage, et une relance si le jeton\n * obtenu par `target` est refusé (instance redémarrée). Le seul chemin des\n * outils comme du prompt.\n */\n async function callCapability(cap: RuneyaCapability, input: Record<string, unknown>): Promise<unknown> {\n const rawParams = toRawParams(input, cap);\n const raw = rawParams['authToken'];\n // Le jeton du lancement, même recopié en clair par le modèle, cède la\n // place au jeton renouvelé.\n const explicit = raw && !raw.startsWith('$') && !opts.sessionToken.isInitial(raw) ? raw : null;\n const call = async () => {\n const target = await opts.target(input);\n const authToken = explicit ?? (target.authToken || opts.sessionToken.current() || opts.internalToken || undefined);\n if (!authToken) throw new Error('No auth token available');\n\n const anchor = target.projectAnchor && needsAnchor(cap, rawParams) ? await target.projectAnchor() : opts.projectAnchor;\n const params = applyProjectAnchor(cap, rawParams, anchor);\n try {\n return await runCapability(cap, target.baseUrl, params, authToken);\n } catch (err) {\n // Un jeton donné par l'appel ne se remplace pas.\n if (!explicit && target.forgetToken && /UNAUTHORIZED/.test((err as Error).message)) {\n target.forgetToken();\n return RETRY;\n }\n throw err;\n }\n };\n let result = await call();\n if (result === RETRY) result = await call();\n if (result === RETRY) throw new Error('Runeya refuse le jeton de ce client, même renouvelé.');\n return result;\n }\n\n for (const cap of RUNEYA_CAPABILITIES) {\n server.tool(cap.name, cap.description, { ...buildMcpShape(cap), ...opts.extraShape }, async (input) => {\n try {\n return text(await callCapability(cap, input as Record<string, unknown>));\n } catch (err) {\n return error(err);\n }\n });\n }\n\n registerWorkOnCardPrompt(server, (input) => callCapability(CARD_CONTEXT, input), opts.extraShape !== undefined);\n\n return server;\n}\n\nconst CARD_CONTEXT = RUNEYA_CAPABILITIES.find((c) => c.name === 'kanban_get_card_context')!;\n\n/**\n * Le prompt `work_on_card` : les clients l'affichent comme une commande\n * (`/mcp__runeya__work_on_card` dans Claude Code, `/mcp.runeya.work_on_card`\n * dans VS Code).\n *\n * Il rend le contexte de la carte comme un message de l'utilisateur : un prompt\n * MCP ne peut pas poser de consigne système. Les consignes du tableau guident\n * donc le travail sans la force qu'elles ont dans une session lancée depuis la\n * carte dans Runeya, et le message le dit.\n */\nfunction registerWorkOnCardPrompt(\n server: McpServer,\n cardContext: (input: Record<string, unknown>) => Promise<unknown>,\n withInstance: boolean,\n): void {\n const args = {\n boardId: z.string().max(128).describe('The kanban board ID'),\n cardId: z.string().max(128).describe('The card ID'),\n ...(withInstance ? { runeyaInstance: z.string().max(16).optional().describe('pid of the Runeya instance, when several are running') } : {}),\n };\n server.prompt(\n 'work_on_card',\n 'Work on a Runeya kanban card: loads the card, the board guidelines and the expected ending.',\n args,\n async (input) => {\n const { boardId, cardId, runeyaInstance } = input as { boardId: string; cardId: string; runeyaInstance?: string };\n const pid = runeyaInstance ? Number(runeyaInstance) : undefined;\n const { context } = await cardContext({ boardId, cardId, ...(pid ? { runeyaInstance: pid } : {}) }) as { context: string };\n return {\n description: `Carte ${cardId}`,\n messages: [{\n role: 'user' as const,\n content: {\n type: 'text' as const,\n text: `Je veux travailler sur cette carte du kanban Runeya. Voici son contexte, tel que Runeya le donne à une session lancée depuis la carte : suis ces consignes, en particulier la fin attendue (déplacer la carte si besoin, puis la commenter).\\n\\n${context}`,\n },\n }],\n };\n },\n );\n}\n\n/**\n * Les outils de choix d'instance, pour un MCP lancé hors de Runeya\n * (`runeya mcp`). Ils lisent le registre machine et ne passent par aucune\n * instance : ils doivent répondre avant qu'une instance soit choisie.\n */\nexport function registerInstanceTools(server: McpServer, selector: InstanceSelector): void {\n server.tool(\n 'list_local_runeya_instances',\n 'List the Runeya instances running on this machine, with their pid. When several are running, ask the user which one to use, then pass its pid as runeyaInstance on every call of this conversation.',\n {},\n async () => {\n try {\n return text(await selector.list());\n } catch (err) {\n return error(err);\n }\n },\n );\n}\n"],"mappings":"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","names":["join","z","join","authToken","z"]}
|
package/index.js
CHANGED
|
@@ -87,7 +87,7 @@ async function runDesktopInstaller(required) {
|
|
|
87
87
|
const { installDesktopLauncher, listDesktopLaunchers, uninstallDesktopLauncher, deriveIdentity } = await import("./desktop-launcher-GD4QPM66.js");
|
|
88
88
|
const { printInstallReport } = await import("./desktop-wizard-XIDDAG7L.js");
|
|
89
89
|
const { runInstallerPage } = await import("./desktop-installer-page-I5FHFDES.js");
|
|
90
|
-
const { createGlobalInstaller } = await import("./install-gate-
|
|
90
|
+
const { createGlobalInstaller } = await import("./install-gate-OJJEUU4E.js");
|
|
91
91
|
const current = { nodePath: process.execPath, cliEntry: fileURLToPath(import.meta.url) };
|
|
92
92
|
const outcome = await runInstallerPage({
|
|
93
93
|
openUrl: (url) => open(url),
|
|
@@ -143,7 +143,7 @@ if (subCommand === "install-desktop" || subCommand === "uninstall-desktop") {
|
|
|
143
143
|
}
|
|
144
144
|
const { name, id } = deriveIdentity(displayName, workingDir);
|
|
145
145
|
if (subCommand === "install-desktop") {
|
|
146
|
-
const { createGlobalInstaller, SKIP_INSTALL_CHECK_ENV } = await import("./install-gate-
|
|
146
|
+
const { createGlobalInstaller, SKIP_INSTALL_CHECK_ENV } = await import("./install-gate-OJJEUU4E.js");
|
|
147
147
|
if (process.env[SKIP_INSTALL_CHECK_ENV] !== "1") {
|
|
148
148
|
const target = { cliEntry: launcherBase.cliEntry };
|
|
149
149
|
const global = await (await createGlobalInstaller(target, version)).state();
|
|
@@ -178,7 +178,7 @@ if (subCommand === "install-desktop" || subCommand === "uninstall-desktop") {
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
if (subCommand === "instances") {
|
|
181
|
-
const { listInstances, killInstance, restartInstance } = await import("./src-
|
|
181
|
+
const { listInstances, killInstance, restartInstance } = await import("./src-KWO3CKZU.js");
|
|
182
182
|
const { runInstancesCommand } = await import("./instances-command-KZ5UGXOR.js");
|
|
183
183
|
process.exit(await runInstancesCommand(args.slice(1), {
|
|
184
184
|
listInstances,
|
|
@@ -192,6 +192,13 @@ if (subCommand === "instances") {
|
|
|
192
192
|
warn: (message) => console.error(message)
|
|
193
193
|
}));
|
|
194
194
|
}
|
|
195
|
+
if (subCommand === "mcp") {
|
|
196
|
+
const { listInstances } = await import("./src-KWO3CKZU.js");
|
|
197
|
+
const { runExternalMcp } = await import("./external-NMKQPROK.js");
|
|
198
|
+
await runExternalMcp({ listInstances });
|
|
199
|
+
await new Promise(() => {
|
|
200
|
+
});
|
|
201
|
+
}
|
|
195
202
|
var targetDir = args.find((a, i) => !a.startsWith("-") && !flagsWithValues.has(args[i - 1] ?? ""));
|
|
196
203
|
if (targetDir) {
|
|
197
204
|
const resolved = resolve(targetDir);
|
|
@@ -223,6 +230,8 @@ Commands:
|
|
|
223
230
|
instances [open|stop|restart <pid>]
|
|
224
231
|
List the Runeya running on this machine, and act on
|
|
225
232
|
one. --json for the raw records.
|
|
233
|
+
mcp Runeya MCP server on stdio, for Claude Code, Codex,
|
|
234
|
+
Cursor\u2026 Asks for access in the Runeya app on first use.
|
|
226
235
|
--from-desktop Set by desktop launchers: report startup failures
|
|
227
236
|
in a browser window instead of a silent exit
|
|
228
237
|
|
|
@@ -345,7 +354,7 @@ function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500)
|
|
|
345
354
|
});
|
|
346
355
|
}
|
|
347
356
|
async function main() {
|
|
348
|
-
const { mustOpenInstaller, hasDesktop, SKIP_INSTALL_CHECK_ENV } = await import("./install-gate-
|
|
357
|
+
const { mustOpenInstaller, hasDesktop, SKIP_INSTALL_CHECK_ENV } = await import("./install-gate-OJJEUU4E.js");
|
|
349
358
|
if (mustOpenInstaller({
|
|
350
359
|
isFromDesktop,
|
|
351
360
|
isRestartHandoff: process.env.RUNEYA_RESTART_HANDOFF === "1",
|
|
@@ -377,7 +386,7 @@ async function main() {
|
|
|
377
386
|
console.warn(`[cli] Journal sur disque indisponible : ${err.message}`);
|
|
378
387
|
}
|
|
379
388
|
}
|
|
380
|
-
const { createLocalServer, pullEnv, PullEnvError, registerInstance, registerCliLocation } = await import("./src-
|
|
389
|
+
const { createLocalServer, pullEnv, PullEnvError, registerInstance, registerCliLocation } = await import("./src-KWO3CKZU.js");
|
|
381
390
|
registerCliLocation({ execPath: process.execPath, cliEntry: fileURLToPath(import.meta.url) });
|
|
382
391
|
if (isPullEnv) {
|
|
383
392
|
if (!serviceArg) {
|
|
@@ -486,7 +495,7 @@ async function main() {
|
|
|
486
495
|
networkInfo,
|
|
487
496
|
setLanMode,
|
|
488
497
|
resolveTrayIcon
|
|
489
|
-
} = await import("./src-
|
|
498
|
+
} = await import("./src-KWO3CKZU.js");
|
|
490
499
|
tray = await startInstanceTray({
|
|
491
500
|
info: { cwd: process.cwd(), port, version, url: appUrl },
|
|
492
501
|
assetsDir: join(__dirname, "assets"),
|
package/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/port-resolution.ts"],"sourcesContent":["#!/usr/bin/env node\nimport type { Server } from 'node:http';\nimport type { WebSocketServer } from 'ws';\nimport type { Request, Response } from 'express';\nimport { fileURLToPath, URL as NodeURL } from 'node:url';\nimport { dirname, join, resolve } from 'node:path';\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport express from 'express';\nimport dotenv from 'dotenv'\nimport open from 'open';\nimport { resolvePort } from './port-resolution.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nconst { version } = JSON.parse(\n readFileSync(new NodeURL('./package.json', import.meta.url), 'utf8'),\n) as { version: string };\n\nfunction printStartupTable(port: number, appUrl: string): void {\n const rows: [string, string][] = [\n ['Version', version],\n ['Port', String(port)],\n ['Url', appUrl],\n ];\n\n const col0 = Math.max(...rows.map(([k]) => k.length));\n const col1 = Math.max(...rows.map(([, v]) => v.length));\n\n const pad = (s: string, n: number) => s.padEnd(n);\n const hr = (l: string, m: string, r: string, f: string) =>\n l + f.repeat(col0 + 2) + m + f.repeat(col1 + 2) + r;\n\n console.log('');\n console.log(hr('┌', '┬', '┐', '─'));\n for (const [key, value] of rows) {\n console.log(`│ ${pad(key, col0)} │ ${pad(value, col1)} │`);\n }\n console.log(hr('└', '┴', '┘', '─'));\n console.log('');\n}\n\n// Parse CLI flags\nconst args = process.argv.slice(2);\n\n/**\n * Set by the desktop launcher. It means nobody is watching a terminal, so a\n * startup failure has to be reported through a window of its own.\n */\nconst isFromDesktop = args.includes('--from-desktop');\n\n// Capture must start before anything logs, hence this early import.\nconst startupFailure = isFromDesktop ? await import('./startup-failure.js') : null;\nstartupFailure?.captureOutput();\n\nfunction getFlagValue(flags: string[]): string | undefined {\n for (const flag of flags) {\n const idx = args.indexOf(flag);\n if (idx !== -1 && idx + 1 < args.length) return args[idx + 1];\n }\n return undefined;\n}\n\n/**\n * Flags that mirror a RUNEYA_* environment variable. The flag always wins over\n * the variable, and both are equivalent otherwise.\n *\n * Secrets (RUNEYA_ENCRYPTION_KEY, RUNEYA_MASTER_PASSWORD) deliberately have no\n * flag: process arguments are world-readable through `ps` on most systems.\n */\nconst ENV_FLAGS: { flag: string; env: string }[] = [\n { flag: '--host', env: 'RUNEYA_HOST' },\n { flag: '--agent-port', env: 'RUNEYA_AGENT_PORT' },\n { flag: '--agent-host', env: 'RUNEYA_AGENT_HOST' },\n { flag: '--agent-binary-path', env: 'RUNEYA_AGENT_BINARY_PATH' },\n { flag: '--data-dir', env: 'RUNEYA_DATA_DIR' },\n { flag: '--mode', env: 'RUNEYA_MODE' },\n { flag: '--cloud-url', env: 'RUNEYA_CLOUD_URL' },\n { flag: '--cloud-app-url', env: 'RUNEYA_CLOUD_APP_URL' },\n { flag: '--cors-origin', env: 'RUNEYA_CORS_ORIGIN' },\n { flag: '--claude-pty', env: 'RUNEYA_CLAUDE_PTY' },\n { flag: '--project', env: 'RUNEYA_PROJECT_ID' },\n];\n\n// Flags taking a value — needed so their value is not mistaken for the target directory\nconst flagsWithValues = new Set([\n '--port',\n '--name',\n '-e', '--environment',\n '-s', '--service',\n ...ENV_FLAGS.map((f) => f.flag),\n]);\n\n// Apply value flags to their env var (flag wins over an already-set variable).\n// Done before dotenv so a .env file can never override an explicit flag.\nfor (const { flag, env: key } of ENV_FLAGS) {\n const value = getFlagValue([flag]);\n if (value !== undefined) process.env[key] = value;\n}\n// --debug is a boolean shortcut for RUNEYA_DEBUG=1\nif (args.includes('--debug')) process.env['RUNEYA_DEBUG'] = '1';\n\n/**\n * La page d'installation du raccourci. Rend quand on la quitte : le code de\n * sortie du process.\n *\n * `required` : ouverte par un `runeya` lancé hors raccourci (voir\n * `install-gate.ts`), et non par `runeya install-desktop`.\n */\nasync function runDesktopInstaller(required: boolean): Promise<number> {\n const { installDesktopLauncher, listDesktopLaunchers, uninstallDesktopLauncher, deriveIdentity } =\n await import('./desktop-launcher.js');\n const { printInstallReport } = await import('./desktop-wizard.js');\n const { runInstallerPage } = await import('./desktop-installer-page.js');\n const { createGlobalInstaller } = await import('./install-gate.js');\n\n // Ce que viseront les raccourcis : ce CLI, puis la copie globale une fois installée.\n const current = { nodePath: process.execPath, cliEntry: fileURLToPath(import.meta.url) };\n const outcome = await runInstallerPage({\n openUrl: (url) => open(url),\n install: (answers) => installDesktopLauncher({\n ...current,\n distDir: dirname(current.cliEntry),\n ...answers,\n ...deriveIdentity(answers.name, answers.workingDir),\n }),\n list: listDesktopLaunchers,\n uninstall: (launcher) => uninstallDesktopLauncher(launcher.name, launcher.id),\n current,\n global: await createGlobalInstaller(current, version),\n required,\n });\n if (!outcome) return 130; // annulé depuis la page ou par Ctrl+C\n const { answers, result } = outcome;\n printInstallReport({ ...answers, nodePath: process.execPath, result });\n return 0;\n}\n\n/**\n * `install-desktop` / `uninstall-desktop` — handled before anything else so the\n * sub-command name is never mistaken for a target directory, and so the current\n * working directory is still the user's when we capture it.\n *\n * Bare on a TTY, `install-desktop` opens a dedicated installer page served by a\n * short-lived local server — Runeya itself is not started. Any flag that\n * pre-answers a question installs directly so scripts stay unattended.\n */\nconst subCommand = args[0];\nif (subCommand === 'install-desktop' || subCommand === 'uninstall-desktop') {\n const { installDesktopLauncher, uninstallDesktopLauncher, deriveIdentity } =\n await import('./desktop-launcher.js');\n const { shouldOpenInstallPage, printInstallReport, printUninstallReport } =\n await import('./desktop-wizard.js');\n\n const { parsePort } = await import('./free-port.js');\n\n const rest = args.slice(1);\n // Every flag of this sub-command that takes a value: without this, the value\n // itself (a port number, a name) is mistaken for the target directory.\n const subFlagsWithValues = new Set(['--name', '--port', '--agent-port', '--project']);\n const positional = rest.find((a, i) => !a.startsWith('-') && !subFlagsWithValues.has(rest[i - 1] ?? ''));\n const nameFlag = getFlagValue(['--name']);\n\n // Ports baked into the shortcut. Null means \"follow Runeya's default\", so a\n // launcher created without them keeps tracking the default if it changes.\n const portFlagRaw = getFlagValue(['--port']);\n const agentPortFlagRaw = getFlagValue(['--agent-port']);\n for (const [flag, raw] of [['--port', portFlagRaw], ['--agent-port', agentPortFlagRaw]] as const) {\n if (raw !== undefined && parsePort(raw) === null) {\n console.error(`Erreur : ${flag} attend un entier entre 1 et 65535 (reçu ${JSON.stringify(raw)}).`);\n process.exit(1);\n }\n }\n const port = parsePort(portFlagRaw);\n const agentPort = parsePort(agentPortFlagRaw);\n\n // Le projet que le raccourci ouvre directement, sans passer par la liste.\n const projectId = getFlagValue(['--project']) ?? null;\n if (projectId !== null) {\n const { isLauncherProjectId } = await import('@runeya/packages-shared');\n if (!isLauncherProjectId(projectId)) {\n console.error(`Erreur : --project attend un id de projet (reçu ${JSON.stringify(projectId)}).`);\n process.exit(1);\n }\n }\n\n // The data directory is resolved from the working directory, so a launcher\n // that does not pin one would start Runeya in the desktop's cwd (usually the\n // home directory) and create a stray .runeya there. Pin the cwd by default.\n const workingDir = rest.includes('--no-cwd') ? null : resolve(positional ?? process.cwd());\n const displayName = nameFlag;\n\n const launcherBase = { nodePath: process.execPath, cliEntry: fileURLToPath(import.meta.url), distDir: __dirname };\n\n if (subCommand === 'install-desktop' && shouldOpenInstallPage(rest, positional !== undefined)) {\n process.exit(await runDesktopInstaller(false));\n }\n\n const { name, id } = deriveIdentity(displayName, workingDir);\n\n // Même règle que la page : un raccourci vers une copie npx ou un clone cesserait\n // de marcher le jour où elle disparaît. Un clone de développement passe par\n // RUNEYA_SKIP_INSTALL_CHECK=1, comme pour démarrer Runeya.\n if (subCommand === 'install-desktop') {\n const { createGlobalInstaller, SKIP_INSTALL_CHECK_ENV } = await import('./install-gate.js');\n if (process.env[SKIP_INSTALL_CHECK_ENV] !== '1') {\n // Une copie globale à jour déjà présente est retenue : le raccourci la vise.\n const target = { cliEntry: launcherBase.cliEntry };\n const global = await (await createGlobalInstaller(target, version)).state();\n launcherBase.cliEntry = target.cliEntry;\n launcherBase.distDir = dirname(target.cliEntry);\n if (!global.installed) {\n console.error('Erreur : ce Runeya n’est pas installé globalement, le raccourci ne marcherait plus une fois cette copie supprimée.');\n console.error('Lancez « runeya install-desktop » sans option pour l’installer depuis la page, ou « npm install -g @runeya/runeya ».');\n process.exit(1);\n }\n }\n }\n\n try {\n if (subCommand === 'install-desktop') {\n const result = await installDesktopLauncher({\n ...launcherBase,\n workingDir,\n name,\n id,\n port,\n agentPort,\n projectId,\n });\n printInstallReport({ name, workingDir, nodePath: process.execPath, port, agentPort, result });\n } else {\n printUninstallReport(name, await uninstallDesktopLauncher(name, id));\n }\n process.exit(0);\n } catch (err) {\n console.error(`Erreur : ${(err as Error).message}`);\n process.exit(1);\n }\n}\n\n/**\n * `runeya instances` — voir et piloter les Runeya de cette machine depuis un\n * terminal. Traité ici, avec les autres sous-commandes, pour que le mot\n * « instances » ne soit jamais pris pour un répertoire cible.\n *\n * Le pendant de l'icône de barre système, qui ne montre qu'une instance — la\n * sienne — et n'existe pas partout. Celle-ci les montre toutes, partout.\n *\n * Aucun serveur n'est démarré : le registre machine est un dossier de fichiers,\n * et le lire ne demande rien d'autre que de le lire.\n */\nif (subCommand === 'instances') {\n // Importé ici et pas en tête de fichier : charger `@runeya/apps-server` a un\n // coût, et un `runeya` ordinaire n'a pas à le payer pour une sous-commande\n // qu'il n'utilise pas.\n const { listInstances, killInstance, restartInstance } = await import('@runeya/apps-server');\n const { runInstancesCommand } = await import('./instances-command.js');\n\n process.exit(await runInstancesCommand(args.slice(1), {\n listInstances,\n stopInstance: killInstance,\n restartInstance: async (pid: number) => {\n const { pid: newPid, portReleased } = await restartInstance(pid);\n return { pid: newPid, portReleased };\n },\n openUrl: (url: string) => open(url),\n log: (message: string) => console.log(message),\n warn: (message: string) => console.error(message),\n }));\n}\n\n// Resolve target directory (first positional arg, skipping flag values)\nconst targetDir = args.find((a, i) => !a.startsWith('-') && !flagsWithValues.has(args[i - 1] ?? ''));\nif (targetDir) {\n const resolved = resolve(targetDir);\n process.chdir(resolved);\n console.log(`[cli] Working directory: ${resolved}`);\n} else if (process.env['RUNEYA_DATA_DIR']) {\n const resolved = resolve(process.env['RUNEYA_DATA_DIR'], '..');\n process.chdir(resolved);\n console.log(`[cli] Working directory: ${resolved} (from RUNEYA_DATA_DIR)`);\n} else if (process.cwd() === '/') {\n /**\n * On n'écrit jamais à la racine.\n *\n * Un lanceur global — installé sans dossier épinglé — démarre avec le cwd que\n * lui donne le système, et le Finder comme les services systemd donnent `/`.\n * `DATA_DIR` y vaudrait `/.runeya` : sous macOS la racine est le volume\n * système signé, monté en lecture seule, et la première écriture locale\n * échouerait en EROFS — que même root ne peut pas lever sans\n * `/etc/synthetic.conf` et un redémarrage. Sous Linux elle demanderait les\n * droits du superutilisateur pour un dossier qui n'a rien à faire là.\n *\n * Le repli vaut aussi pour les lanceurs DÉJÀ installés, que corriger le stub\n * ne rattraperait pas.\n */\n const home = homedir();\n process.chdir(home);\n console.log(`[cli] Working directory: ${home} (lanceur sans dossier épinglé)`);\n}\n\ndotenv.config({ quiet: true });\n\nif (args.includes('--help') || args.includes('-h')) {\n console.log(`Usage: runeya [path-to-your-stack] [options]\n\nCommands:\n install-desktop [path] Create a desktop/menu launcher for this machine\n Bare on a terminal, this opens a dedicated installer page.\n uninstall-desktop Remove a launcher created by install-desktop\n --name <label> Launcher name (default: \"Runeya — <folder>\")\n --no-cwd Global launcher — do not pin a working directory\n --port <n> Port baked into the launcher (default: Runeya's own)\n --agent-port <n> Agent port baked in (default: launcher port + 1)\n --project <id> Open this project directly (no project list)\n -y, --yes Skip the wizard and take the defaults\n instances [open|stop|restart <pid>]\n List the Runeya running on this machine, and act on\n one. --json for the raw records.\n --from-desktop Set by desktop launchers: report startup failures\n in a browser window instead of a silent exit\n\nOptions:\n --version Show version number\n --pe, --pull-env Print env vars for a service to stdout (requires -e and -s)\n -e, --environment <n> Environment name or id\n -s, --service <name> Service name or id\n --lan Expose server on the local network (shortcut for --host 0.0.0.0)\n --no-open Do not open browser on startup\n --no-tray Do not put this instance in the system tray\n --agent Run as agent only (no server, no UI) — for remote machines\n -h, --help Show this help message\n\nConfiguration:\n Each setting has a flag and an equivalent RUNEYA_ environment variable; the\n flag wins. Variables are prefixed so they cannot collide with those of the\n services you manage. A .env file in the working directory is loaded\n automatically, but never overrides a flag.\n\n Flag Environment variable Default\n --port <n> RUNEYA_HTTP_PORT 4000\n (binds the agent instead when --agent is used)\n --host <address> RUNEYA_HOST 127.0.0.1\n --agent-port <n> RUNEYA_AGENT_PORT server port + 1\n --agent-host <address> RUNEYA_AGENT_HOST 127.0.0.1\n --agent-binary-path <path> RUNEYA_AGENT_BINARY_PATH bundled with the CLI\n --data-dir <path> RUNEYA_DATA_DIR <cwd>/.runeya\n --mode <mode> RUNEYA_MODE local-simple\n 'local-simple' | 'cloud'\n --cloud-url <url> RUNEYA_CLOUD_URL —\n --cloud-app-url <url> RUNEYA_CLOUD_APP_URL —\n --cors-origin <origin> RUNEYA_CORS_ORIGIN —\n --lan RUNEYA_LAN_PROXY false\n --debug RUNEYA_DEBUG false\n --claude-pty <0|1> RUNEYA_CLAUDE_PTY 1\n --project <id> RUNEYA_PROJECT_ID —\n Open this project only: no project list, no switching\n PTY mode for the Claude Code runner\n\n Secrets have no flag — process arguments are readable by other users via 'ps':\n RUNEYA_ENCRYPTION_KEY 64-char hex key for CI/CD — unlocks secrets without a\n master password AND disables tRPC authentication.\n Never set this on a machine reachable by others.\n RUNEYA_MASTER_PASSWORD Master password used by --pull-env instead of prompting\n\n Started from a terminal, 'runeya' opens the desktop shortcut installer instead\n of Runeya itself. RUNEYA_SKIP_INSTALL_CHECK=1 starts Runeya anyway (servers,\n containers, CI).\n\n Standard variables Runeya also honours: NODE_ENV, CI (skips opening the browser).\n`);\n process.exit(0);\n}\n\nif (args.includes('--version')) {\n console.log(version);\n process.exit(0);\n}\n\n/**\n * L'icône de barre système, sauf `--no-tray`.\n *\n * Chaque Runeya pose la sienne au démarrage et la retire en s'arrêtant : une\n * icône par instance en cours. Là où la barre système n'existe pas (serveur\n * sans bureau, arm64, paquet optionnel absent), il n'y a rien à désactiver —\n * l'icône ne se pose pas, et Runeya démarre pareil.\n */\nconst noTray = args.includes('--no-tray');\n\nconst noOpen =\n args.includes('--no-open') ||\n !!process.env['VITEST'] ||\n !!process.env['CI'] ||\n process.env['NODE_ENV'] === 'test';\n\n// --pull-env flag — detect early so we can silence stdout before any log\nconst isPullEnv = args.includes('--pull-env') || args.includes('--pe');\n\n// In pull-env mode, redirect console.log/info to stderr so only KEY=VALUE goes to stdout\nif (isPullEnv) {\n const toStderr = (...a: unknown[]) => process.stderr.write(a.join(' ') + '\\n');\n console.log = toStderr;\n console.info = toStderr;\n}\n\nconst serviceArg = getFlagValue(['-s', '--service']);\nconst environmentArg = getFlagValue(['-e', '--environment']);\n\n// Configure environment BEFORE importing server (env is parsed at module load)\n// --agent mode: run as standalone agent only (no server, no UI)\nconst isAgentOnly = args.includes('--agent');\n\n// --port targets the server, except in agent mode where it binds the agent\nconst portFlag = getFlagValue(['--port']);\nif (portFlag) {\n process.env[isAgentOnly ? 'RUNEYA_AGENT_PORT' : 'RUNEYA_HTTP_PORT'] = portFlag;\n}\n\n// --lan binds to 0.0.0.0. As an explicit flag it outranks RUNEYA_HOST; coming\n// from RUNEYA_LAN_PROXY it only fills in a host nobody else set.\nconst hostFlag = getFlagValue(['--host']);\nconst isLan = args.includes('--lan');\nconst isLanProxy = isLan || process.env.RUNEYA_LAN_PROXY === 'true' || process.env.RUNEYA_LAN_PROXY === '1';\nif (isLanProxy) {\n process.env.RUNEYA_LAN_PROXY = 'true';\n if (!hostFlag && isLan) process.env.RUNEYA_HOST = '0.0.0.0';\n else process.env.RUNEYA_HOST ??= '0.0.0.0';\n}\n\n// In agent mode --host binds the agent rather than the server\nif (isAgentOnly) {\n if (hostFlag) {\n process.env.RUNEYA_AGENT_HOST = hostFlag;\n } else if (isLan) {\n process.env.RUNEYA_AGENT_HOST = '0.0.0.0';\n } else if (isLanProxy) {\n process.env.RUNEYA_AGENT_HOST ??= '0.0.0.0';\n }\n}\n\n// Agent binary shipped inside the CLI dist — overridable via --agent-binary-path\nprocess.env.RUNEYA_AGENT_BINARY_PATH ??= join(__dirname, 'agent/index.js');\n// Expose CLI version to server\nprocess.env.RUNEYA_VERSION = version;\n// Tell the server it was launched from a shortcut, so the UI can say so\nif (isFromDesktop) process.env.RUNEYA_FROM_DESKTOP = '1';\n// Data directory defaults to CWD/.runeya (server env.ts default)\n// so Runeya data lives alongside the target project.\n\n/**\n * Listen with retry for EADDRINUSE — handles the race condition when the CLI\n * restarts the server before the old process has fully released the port.\n *\n * The `wss` parameter is needed because the `ws` library re-emits HTTP server\n * errors on the WebSocketServer — without a handler the error becomes unhandled.\n */\nfunction listenWithRetry(\n server: Server,\n port: number,\n host: string,\n wss?: WebSocketServer,\n maxRetries = 20,\n delay = 500,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n let attempts = 0;\n\n // Absorb EADDRINUSE errors re-emitted by the ws library on the WSS\n // during the retry window. Without this, Node.js crashes on unhandled error.\n const wssErrorHandler = () => { /* absorbed — handled via server 'error' */ };\n wss?.on('error', wssErrorHandler);\n\n const tryListen = () => {\n const onError = (err: NodeJS.ErrnoException) => {\n // Clean up the 'listening' callback that server.listen() registered\n server.removeAllListeners('listening');\n\n if (err.code === 'EADDRINUSE' && attempts < maxRetries) {\n attempts++;\n console.log(`[cli] Port ${port} in use, retrying in ${delay}ms (${attempts}/${maxRetries})...`);\n setTimeout(tryListen, delay);\n } else {\n wss?.removeListener('error', wssErrorHandler);\n reject(err);\n }\n };\n\n server.once('error', onError);\n server.listen(port, host, () => {\n server.removeListener('error', onError);\n wss?.removeListener('error', wssErrorHandler);\n resolve();\n });\n };\n\n tryListen();\n });\n}\n\nasync function main() {\n // Hors raccourci, `runeya` mène à la page d'installation (voir install-gate.ts).\n const { mustOpenInstaller, hasDesktop, SKIP_INSTALL_CHECK_ENV } = await import('./install-gate.js');\n if (mustOpenInstaller({\n isFromDesktop,\n isRestartHandoff: process.env.RUNEYA_RESTART_HANDOFF === '1',\n startsServer: !isPullEnv && !isAgentOnly,\n env: process.env,\n })) {\n const { shouldOpenInstallPage } = await import('./desktop-wizard.js');\n if (!hasDesktop(process.platform, process.env)) {\n // Serveur, SSH : ni page à montrer, ni menu où poser un raccourci.\n console.error('Aucun bureau sur cette session (SSH ou serveur sans affichage) : pas de raccourci à créer ici.');\n console.error(`Pour démarrer Runeya directement : ${SKIP_INSTALL_CHECK_ENV}=1 runeya`);\n process.exit(1);\n }\n if (!shouldOpenInstallPage([], false)) {\n // Sans terminal (script, CI), personne ne verrait la page.\n console.error('Runeya se lance depuis un raccourci de bureau : lancez « runeya » dans un terminal pour le créer.');\n console.error(`Pour démarrer Runeya sans raccourci (serveur, CI) : ${SKIP_INSTALL_CHECK_ENV}=1 runeya`);\n process.exit(1);\n }\n process.exit(await runDesktopInstaller(true));\n }\n\n // Avant tout : ce que ce process hérite ici, le serveur, l'agent local et\n // chaque service lancé ensuite en héritent à leur tour. Un raccourci de bureau\n // reçoit le PATH de la session graphique, où ni les gestionnaires de version\n // ni les binaires installés par l'utilisateur (claude, codex) ne figurent.\n const { ensureNodeBinOnPath, ensureUserBinsOnPath, stampConsole } = await import('@runeya/packages-shared/node');\n ensureNodeBinOnPath();\n await ensureUserBinsOnPath();\n\n // Journaux datés du temps écoulé depuis le démarrage. Jamais en --pull-env :\n // sa sortie est lue par un script, pas par un humain.\n if (!isPullEnv) {\n stampConsole();\n // Sortie et crash recopiés sur disque : une instance relancée en\n // `stdio: 'ignore'` (bureau, mise à jour) n'a sinon aucune trace.\n try {\n const { installLogFile } = await import('./log-file.js');\n console.log(`[cli] Journal : ${installLogFile().logPath}`);\n } catch (err) {\n console.warn(`[cli] Journal sur disque indisponible : ${(err as Error).message}`);\n }\n }\n\n // Dynamic import ensures env is parsed AFTER we configure it\n const { createLocalServer, pullEnv, PullEnvError, registerInstance, registerCliLocation } = await import('@runeya/apps-server');\n\n // D'où ce CLI a été lancé, pour que l'interface puisse proposer `install-desktop`\n // sans que le serveur ait à deviner un exécutable.\n registerCliLocation({ execPath: process.execPath, cliEntry: fileURLToPath(import.meta.url) });\n\n // Handle --pull-env mode: resolve env vars and print, then exit\n if (isPullEnv) {\n if (!serviceArg) {\n console.error('Error: --service (-s) is required with --pull-env');\n process.exit(1);\n }\n if (!environmentArg) {\n console.error('Error: --environment (-e) is required with --pull-env');\n process.exit(1);\n }\n try {\n const output = await pullEnv({ service: serviceArg, environment: environmentArg });\n if (output) process.stdout.write(output + '\\n');\n process.exit(0);\n } catch (err: unknown) {\n if (err instanceof PullEnvError) {\n console.error(`Error: ${err.message}`);\n process.exit(err.exitCode);\n }\n throw err;\n }\n }\n\n // --agent mode: standalone agent, no server, no UI\n if (isAgentOnly) {\n const { spawn } = await import('node:child_process');\n const agentBin = join(__dirname, 'agent/index.js');\n const child = spawn(process.execPath, [agentBin], { stdio: 'inherit', windowsHide: true });\n child.on('exit', (code) => process.exit(code ?? 0));\n // SIGINT is already delivered to the child by the terminal (same process group).\n // Intercept it here only to suppress Node's default exit so we stay alive\n // until the child finishes its graceful shutdown.\n process.on('SIGINT', () => { /* wait for child to exit */ });\n process.on('SIGTERM', () => child.kill('SIGTERM'));\n return;\n }\n\n /**\n * Un relais de redémarrage (`appUpdate.restart`) est l'exception au garde-fou\n * ci-dessous : l'instance qui répond encore sur le port est précisément celle\n * qu'on remplace. On la laisse s'éteindre, `listenWithRetry` absorbe\n * l'EADDRINUSE du chevauchement. La marque est consommée ici pour ne pas se\n * propager aux services lancés ensuite, qui héritent de cet environnement.\n */\n const isRestartHandoff = process.env.RUNEYA_RESTART_HANDOFF === '1';\n delete process.env.RUNEYA_RESTART_HANDOFF;\n\n /**\n * A shortcut clicked twice must not fail.\n *\n * Starting a second instance on a port a Runeya already holds ends in\n * EADDRINUSE after ten seconds of retries — and from a launcher there is no\n * terminal to read that in. So the port is asked first: if Runeya answers,\n * the right move is to bring up the window that already exists.\n *\n * Only for launcher starts. From a terminal, `runeya` twice on one port is a\n * mistake worth reporting, not one worth papering over.\n */\n if (isFromDesktop && !isRestartHandoff) {\n const port = resolvePort();\n const { probeRuneya } = await import('./free-port.js');\n const existing = await probeRuneya(port);\n if (existing) {\n const url = `http://localhost:${port}`;\n console.log(`[cli] Runeya v${existing.version} déjà en cours sur le port ${port} — ouverture de ${url}`);\n if (!noOpen) await open(url);\n return;\n }\n }\n\n console.log('[cli] Starting Runeya...');\n\n // Create server (automatically spawns local agent)\n const { app, server, wss, shutdown } = await createLocalServer();\n\n // Serve static files from web frontend (embedded in CLI dist during build)\n const webDistPath = join(__dirname, 'web');\n\n /**\n * Les fichiers d'`assets/` portent un hash de leur contenu dans leur nom : une\n * mise à jour produit un NOUVEAU nom, jamais un contenu différent sous le même\n * nom. Ils peuvent donc être gardés indéfiniment, et `immutable` dit au\n * navigateur de ne même pas revalider.\n *\n * Sans cela, `express.static` répond `max-age=0` : chaque rechargement\n * redemande les 295 fichiers pour s'entendre répondre 304. Ce sont des\n * requêtes vides, mais elles occupent les six connexions qu'un navigateur\n * accorde par hôte — pour tous ses onglets à la fois. Avec plusieurs onglets\n * rechargés ensemble, l'ouverture du WebSocket attendait derrière cette file.\n */\n app.use('/assets', express.static(join(webDistPath, 'assets'), {\n immutable: true,\n maxAge: '1y',\n fallthrough: false,\n }));\n\n // Tout le reste (favicon…) : revalidation à chaque fois, ces noms ne changent\n // pas d'une version à l'autre.\n app.use(express.static(webDistPath, {\n index: false,\n setHeaders: (res) => res.setHeader('Cache-Control', 'no-cache'),\n }));\n\n // Fallback to index.html for client-side routing (Vue Router)\n // Express 5 requires explicit path patterns (not '*')\n //\n // JAMAIS mis en cache : c'est lui qui nomme les assets de la version courante.\n // Un index.html périmé rappellerait les fichiers de l'ancienne version — qui,\n // eux, ne sont plus là.\n //\n // `root` + nom relatif, jamais un chemin absolu : `sendFile` refuse tout\n // segment commençant par un point (`dotfiles: 'ignore'` par défaut) et\n // répond 404. Or l'installation la plus courante — `npx @runeya/runeya` —\n // vit sous `~/.npm/_npx/…`. Avec un chemin absolu, la page d'accueil était\n // donc introuvable sur toute machine lancée par npx (ou depuis `~/.nvm`),\n // alors que `/index.html` répondait : `express.static` ne teste, lui, que la\n // partie relative à sa racine.\n app.use((_req: Request, res: Response) => {\n res.setHeader('Cache-Control', 'no-cache');\n res.sendFile('index.html', { root: webDistPath });\n });\n\n const port = resolvePort();\n const host = process.env.RUNEYA_HOST ?? '127.0.0.1';\n\n await listenWithRetry(server, port, host, wss);\n\n // Announce this instance now that the port is really ours, so the settings\n // panel of any Runeya on this machine can list it.\n await registerInstance();\n\n /**\n * L'adresse d'écoute n'est pas une adresse de visite.\n *\n * `0.0.0.0` (et son équivalent IPv6 `::`) dit « toutes les interfaces » à\n * bind ; ce n'est pas une destination. Sous Linux une connexion vers elle\n * retombe sur la boucle locale, ce qui a longtemps masqué le problème — sous\n * Windows elle n'est pas routable du tout, et les navigateurs la bloquent\n * désormais ailleurs aussi. On écoute donc partout, mais on ouvre localhost.\n */\n const browseHost = host === '0.0.0.0' || host === '::' ? 'localhost' : host;\n const appUrl = `http://${browseHost}:${port}`;\n\n if (!noOpen) {\n await open(appUrl);\n }\n\n console.log('\\n🚀 Runeya is running!');\n if (isLanProxy) {\n console.log('🌐 LAN mode: server and service ports exposed on local network');\n }\n printStartupTable(port, appUrl);\n console.log('[cli] Press Ctrl+C to stop');\n\n // Assignée plus bas : l'arrêt doit pouvoir la retirer, et la poser demande\n // de savoir arrêter — les deux se référencent.\n let tray: { stop: () => Promise<void> } | null = null;\n\n let shuttingDown = false;\n const gracefulShutdown = async (signal: string) => {\n if (shuttingDown) {\n console.log(`[cli] Forced exit (second ${signal})`);\n process.exit(1);\n }\n shuttingDown = true;\n console.log(`\\n[cli] Received ${signal}, shutting down gracefully...`);\n\n // L'icône part en premier : elle annonce une instance vivante, et la\n // laisser pendant l'extinction inviterait à cliquer dans le vide.\n await tray?.stop();\n\n // In dev, exit immediately — ports are freed on process death,\n // and the 'exit' handler in agent-spawner SIGKILLs the agent.\n if (process.env.NODE_ENV === 'development') {\n process.exit(0);\n }\n\n await shutdown();\n console.log('[cli] Shutdown complete');\n process.exit(0);\n };\n\n process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));\n process.on('SIGINT', () => gracefulShutdown('SIGINT'));\n\n if (!noTray) {\n const { startInstanceTray } = await import('./tray.js');\n const {\n scheduleSelfRestart,\n listTrayServices,\n onTrayServicesChanged,\n startProcess,\n stopProcess,\n restartProcess,\n networkInfo,\n setLanMode,\n resolveTrayIcon,\n } = await import('@runeya/apps-server');\n\n tray = await startInstanceTray({\n info: { cwd: process.cwd(), port, version, url: appUrl },\n assetsDir: join(__dirname, 'assets'),\n openUrl: (target) => open(target),\n restart: () => scheduleSelfRestart(),\n stop: () => void gracefulShutdown('tray'),\n\n listServices: listTrayServices,\n startService: (id) => startProcess(id),\n // L'arrêt exige l'agent qui le fait tourner : il est dans la vue, et\n // seul un service en cours propose ce geste.\n stopService: async (id) => {\n const service = (await listTrayServices()).find((s) => s.id === id);\n if (!service?.agentId) throw new Error(`Aucun agent pour ${id}`);\n return stopProcess(id, service.agentId);\n },\n restartService: (id) => restartProcess(id),\n onServicesChanged: onTrayServicesChanged,\n // Le logo de l'organisation du projet ouvert, quand il y en a un : c'est ce\n // qui distingue deux Runeya dans la barre système.\n projectIcon: () => resolveTrayIcon(),\n\n lan: {\n read: () => {\n const info = networkInfo();\n // `lanUrls` est déjà ce qu'on voudrait lire au survol : des adresses\n // joignables, pas des IP brutes. Vide hors mode réseau local.\n return { enabled: info.lanEnabled, addresses: info.lanUrls };\n },\n toggle: (enabled) => setLanMode(enabled),\n },\n\n warn: (message) => console.log(message),\n });\n }\n}\n\nmain().catch(async (err) => {\n console.error('[cli] Fatal error:', err);\n\n // With a terminal, the message above is the report. Without one, it goes\n // nowhere: serve it as a page and open it in the browser instead.\n if (startupFailure) {\n try {\n const { diagnose, renderFailurePage, capturedLines, showStartupFailure } = startupFailure;\n const html = renderFailurePage(\n diagnose(err, resolvePort()),\n capturedLines(),\n {\n 'Version': version,\n 'Dossier de travail': process.cwd(),\n 'Node': process.execPath,\n 'Runeya': fileURLToPath(import.meta.url),\n },\n );\n await showStartupFailure(html);\n } catch (reportErr) {\n console.error('[cli] Impossible d\\'afficher le rapport d\\'erreur:', reportErr);\n }\n }\n\n process.exit(1);\n});\n","/**\n * Resolve the HTTP port Runeya should listen on.\n *\n * Only RUNEYA_HTTP_PORT is read (default 4000) — the generic PORT variable is\n * intentionally ignored so it can stay reserved for the user's own services.\n *\n * Throws if the resolved value is not an integer in [1, 65535].\n */\nexport function resolvePort(): number {\n const raw = process.env.RUNEYA_HTTP_PORT;\n if (raw === undefined) return 4000;\n\n const port = Number(raw);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error(\n `Invalid port: ${JSON.stringify(raw)}. Must be an integer between 1 and 65535.`,\n );\n }\n return port;\n}\n"],"mappings":"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","names":["answers","resolve","port"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/port-resolution.ts"],"sourcesContent":["#!/usr/bin/env node\nimport type { Server } from 'node:http';\nimport type { WebSocketServer } from 'ws';\nimport type { Request, Response } from 'express';\nimport { fileURLToPath, URL as NodeURL } from 'node:url';\nimport { dirname, join, resolve } from 'node:path';\nimport { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport express from 'express';\nimport dotenv from 'dotenv'\nimport open from 'open';\nimport { resolvePort } from './port-resolution.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nconst { version } = JSON.parse(\n readFileSync(new NodeURL('./package.json', import.meta.url), 'utf8'),\n) as { version: string };\n\nfunction printStartupTable(port: number, appUrl: string): void {\n const rows: [string, string][] = [\n ['Version', version],\n ['Port', String(port)],\n ['Url', appUrl],\n ];\n\n const col0 = Math.max(...rows.map(([k]) => k.length));\n const col1 = Math.max(...rows.map(([, v]) => v.length));\n\n const pad = (s: string, n: number) => s.padEnd(n);\n const hr = (l: string, m: string, r: string, f: string) =>\n l + f.repeat(col0 + 2) + m + f.repeat(col1 + 2) + r;\n\n console.log('');\n console.log(hr('┌', '┬', '┐', '─'));\n for (const [key, value] of rows) {\n console.log(`│ ${pad(key, col0)} │ ${pad(value, col1)} │`);\n }\n console.log(hr('└', '┴', '┘', '─'));\n console.log('');\n}\n\n// Parse CLI flags\nconst args = process.argv.slice(2);\n\n/**\n * Set by the desktop launcher. It means nobody is watching a terminal, so a\n * startup failure has to be reported through a window of its own.\n */\nconst isFromDesktop = args.includes('--from-desktop');\n\n// Capture must start before anything logs, hence this early import.\nconst startupFailure = isFromDesktop ? await import('./startup-failure.js') : null;\nstartupFailure?.captureOutput();\n\nfunction getFlagValue(flags: string[]): string | undefined {\n for (const flag of flags) {\n const idx = args.indexOf(flag);\n if (idx !== -1 && idx + 1 < args.length) return args[idx + 1];\n }\n return undefined;\n}\n\n/**\n * Flags that mirror a RUNEYA_* environment variable. The flag always wins over\n * the variable, and both are equivalent otherwise.\n *\n * Secrets (RUNEYA_ENCRYPTION_KEY, RUNEYA_MASTER_PASSWORD) deliberately have no\n * flag: process arguments are world-readable through `ps` on most systems.\n */\nconst ENV_FLAGS: { flag: string; env: string }[] = [\n { flag: '--host', env: 'RUNEYA_HOST' },\n { flag: '--agent-port', env: 'RUNEYA_AGENT_PORT' },\n { flag: '--agent-host', env: 'RUNEYA_AGENT_HOST' },\n { flag: '--agent-binary-path', env: 'RUNEYA_AGENT_BINARY_PATH' },\n { flag: '--data-dir', env: 'RUNEYA_DATA_DIR' },\n { flag: '--mode', env: 'RUNEYA_MODE' },\n { flag: '--cloud-url', env: 'RUNEYA_CLOUD_URL' },\n { flag: '--cloud-app-url', env: 'RUNEYA_CLOUD_APP_URL' },\n { flag: '--cors-origin', env: 'RUNEYA_CORS_ORIGIN' },\n { flag: '--claude-pty', env: 'RUNEYA_CLAUDE_PTY' },\n { flag: '--project', env: 'RUNEYA_PROJECT_ID' },\n];\n\n// Flags taking a value — needed so their value is not mistaken for the target directory\nconst flagsWithValues = new Set([\n '--port',\n '--name',\n '-e', '--environment',\n '-s', '--service',\n ...ENV_FLAGS.map((f) => f.flag),\n]);\n\n// Apply value flags to their env var (flag wins over an already-set variable).\n// Done before dotenv so a .env file can never override an explicit flag.\nfor (const { flag, env: key } of ENV_FLAGS) {\n const value = getFlagValue([flag]);\n if (value !== undefined) process.env[key] = value;\n}\n// --debug is a boolean shortcut for RUNEYA_DEBUG=1\nif (args.includes('--debug')) process.env['RUNEYA_DEBUG'] = '1';\n\n/**\n * La page d'installation du raccourci. Rend quand on la quitte : le code de\n * sortie du process.\n *\n * `required` : ouverte par un `runeya` lancé hors raccourci (voir\n * `install-gate.ts`), et non par `runeya install-desktop`.\n */\nasync function runDesktopInstaller(required: boolean): Promise<number> {\n const { installDesktopLauncher, listDesktopLaunchers, uninstallDesktopLauncher, deriveIdentity } =\n await import('./desktop-launcher.js');\n const { printInstallReport } = await import('./desktop-wizard.js');\n const { runInstallerPage } = await import('./desktop-installer-page.js');\n const { createGlobalInstaller } = await import('./install-gate.js');\n\n // Ce que viseront les raccourcis : ce CLI, puis la copie globale une fois installée.\n const current = { nodePath: process.execPath, cliEntry: fileURLToPath(import.meta.url) };\n const outcome = await runInstallerPage({\n openUrl: (url) => open(url),\n install: (answers) => installDesktopLauncher({\n ...current,\n distDir: dirname(current.cliEntry),\n ...answers,\n ...deriveIdentity(answers.name, answers.workingDir),\n }),\n list: listDesktopLaunchers,\n uninstall: (launcher) => uninstallDesktopLauncher(launcher.name, launcher.id),\n current,\n global: await createGlobalInstaller(current, version),\n required,\n });\n if (!outcome) return 130; // annulé depuis la page ou par Ctrl+C\n const { answers, result } = outcome;\n printInstallReport({ ...answers, nodePath: process.execPath, result });\n return 0;\n}\n\n/**\n * `install-desktop` / `uninstall-desktop` — handled before anything else so the\n * sub-command name is never mistaken for a target directory, and so the current\n * working directory is still the user's when we capture it.\n *\n * Bare on a TTY, `install-desktop` opens a dedicated installer page served by a\n * short-lived local server — Runeya itself is not started. Any flag that\n * pre-answers a question installs directly so scripts stay unattended.\n */\nconst subCommand = args[0];\nif (subCommand === 'install-desktop' || subCommand === 'uninstall-desktop') {\n const { installDesktopLauncher, uninstallDesktopLauncher, deriveIdentity } =\n await import('./desktop-launcher.js');\n const { shouldOpenInstallPage, printInstallReport, printUninstallReport } =\n await import('./desktop-wizard.js');\n\n const { parsePort } = await import('./free-port.js');\n\n const rest = args.slice(1);\n // Every flag of this sub-command that takes a value: without this, the value\n // itself (a port number, a name) is mistaken for the target directory.\n const subFlagsWithValues = new Set(['--name', '--port', '--agent-port', '--project']);\n const positional = rest.find((a, i) => !a.startsWith('-') && !subFlagsWithValues.has(rest[i - 1] ?? ''));\n const nameFlag = getFlagValue(['--name']);\n\n // Ports baked into the shortcut. Null means \"follow Runeya's default\", so a\n // launcher created without them keeps tracking the default if it changes.\n const portFlagRaw = getFlagValue(['--port']);\n const agentPortFlagRaw = getFlagValue(['--agent-port']);\n for (const [flag, raw] of [['--port', portFlagRaw], ['--agent-port', agentPortFlagRaw]] as const) {\n if (raw !== undefined && parsePort(raw) === null) {\n console.error(`Erreur : ${flag} attend un entier entre 1 et 65535 (reçu ${JSON.stringify(raw)}).`);\n process.exit(1);\n }\n }\n const port = parsePort(portFlagRaw);\n const agentPort = parsePort(agentPortFlagRaw);\n\n // Le projet que le raccourci ouvre directement, sans passer par la liste.\n const projectId = getFlagValue(['--project']) ?? null;\n if (projectId !== null) {\n const { isLauncherProjectId } = await import('@runeya/packages-shared');\n if (!isLauncherProjectId(projectId)) {\n console.error(`Erreur : --project attend un id de projet (reçu ${JSON.stringify(projectId)}).`);\n process.exit(1);\n }\n }\n\n // The data directory is resolved from the working directory, so a launcher\n // that does not pin one would start Runeya in the desktop's cwd (usually the\n // home directory) and create a stray .runeya there. Pin the cwd by default.\n const workingDir = rest.includes('--no-cwd') ? null : resolve(positional ?? process.cwd());\n const displayName = nameFlag;\n\n const launcherBase = { nodePath: process.execPath, cliEntry: fileURLToPath(import.meta.url), distDir: __dirname };\n\n if (subCommand === 'install-desktop' && shouldOpenInstallPage(rest, positional !== undefined)) {\n process.exit(await runDesktopInstaller(false));\n }\n\n const { name, id } = deriveIdentity(displayName, workingDir);\n\n // Même règle que la page : un raccourci vers une copie npx ou un clone cesserait\n // de marcher le jour où elle disparaît. Un clone de développement passe par\n // RUNEYA_SKIP_INSTALL_CHECK=1, comme pour démarrer Runeya.\n if (subCommand === 'install-desktop') {\n const { createGlobalInstaller, SKIP_INSTALL_CHECK_ENV } = await import('./install-gate.js');\n if (process.env[SKIP_INSTALL_CHECK_ENV] !== '1') {\n // Une copie globale à jour déjà présente est retenue : le raccourci la vise.\n const target = { cliEntry: launcherBase.cliEntry };\n const global = await (await createGlobalInstaller(target, version)).state();\n launcherBase.cliEntry = target.cliEntry;\n launcherBase.distDir = dirname(target.cliEntry);\n if (!global.installed) {\n console.error('Erreur : ce Runeya n’est pas installé globalement, le raccourci ne marcherait plus une fois cette copie supprimée.');\n console.error('Lancez « runeya install-desktop » sans option pour l’installer depuis la page, ou « npm install -g @runeya/runeya ».');\n process.exit(1);\n }\n }\n }\n\n try {\n if (subCommand === 'install-desktop') {\n const result = await installDesktopLauncher({\n ...launcherBase,\n workingDir,\n name,\n id,\n port,\n agentPort,\n projectId,\n });\n printInstallReport({ name, workingDir, nodePath: process.execPath, port, agentPort, result });\n } else {\n printUninstallReport(name, await uninstallDesktopLauncher(name, id));\n }\n process.exit(0);\n } catch (err) {\n console.error(`Erreur : ${(err as Error).message}`);\n process.exit(1);\n }\n}\n\n/**\n * `runeya instances` — voir et piloter les Runeya de cette machine depuis un\n * terminal. Traité ici, avec les autres sous-commandes, pour que le mot\n * « instances » ne soit jamais pris pour un répertoire cible.\n *\n * Le pendant de l'icône de barre système, qui ne montre qu'une instance — la\n * sienne — et n'existe pas partout. Celle-ci les montre toutes, partout.\n *\n * Aucun serveur n'est démarré : le registre machine est un dossier de fichiers,\n * et le lire ne demande rien d'autre que de le lire.\n */\nif (subCommand === 'instances') {\n // Importé ici et pas en tête de fichier : charger `@runeya/apps-server` a un\n // coût, et un `runeya` ordinaire n'a pas à le payer pour une sous-commande\n // qu'il n'utilise pas.\n const { listInstances, killInstance, restartInstance } = await import('@runeya/apps-server');\n const { runInstancesCommand } = await import('./instances-command.js');\n\n process.exit(await runInstancesCommand(args.slice(1), {\n listInstances,\n stopInstance: killInstance,\n restartInstance: async (pid: number) => {\n const { pid: newPid, portReleased } = await restartInstance(pid);\n return { pid: newPid, portReleased };\n },\n openUrl: (url: string) => open(url),\n log: (message: string) => console.log(message),\n warn: (message: string) => console.error(message),\n }));\n}\n\n/**\n * `runeya mcp` — le serveur MCP de Runeya pour un client lancé hors de Runeya\n * (Claude Code, Codex, Cursor…). La config du client appelle cette commande et\n * non un chemin de fichier : le MCP suit ainsi les mises à jour du CLI.\n *\n * Stdout porte le protocole MCP : rien d'autre ne doit y écrire.\n */\nif (subCommand === 'mcp') {\n const { listInstances } = await import('@runeya/apps-server');\n const { runExternalMcp } = await import('@runeya/packages-mcp-server/external');\n await runExternalMcp({ listInstances });\n // Le reste de ce fichier démarre un serveur : ne jamais l'atteindre. Une\n // promesse en attente ne retient pas le process, qui s'arrête quand le\n // client ferme stdin.\n await new Promise(() => {});\n}\n\n// Resolve target directory (first positional arg, skipping flag values)\nconst targetDir = args.find((a, i) => !a.startsWith('-') && !flagsWithValues.has(args[i - 1] ?? ''));\nif (targetDir) {\n const resolved = resolve(targetDir);\n process.chdir(resolved);\n console.log(`[cli] Working directory: ${resolved}`);\n} else if (process.env['RUNEYA_DATA_DIR']) {\n const resolved = resolve(process.env['RUNEYA_DATA_DIR'], '..');\n process.chdir(resolved);\n console.log(`[cli] Working directory: ${resolved} (from RUNEYA_DATA_DIR)`);\n} else if (process.cwd() === '/') {\n /**\n * On n'écrit jamais à la racine.\n *\n * Un lanceur global — installé sans dossier épinglé — démarre avec le cwd que\n * lui donne le système, et le Finder comme les services systemd donnent `/`.\n * `DATA_DIR` y vaudrait `/.runeya` : sous macOS la racine est le volume\n * système signé, monté en lecture seule, et la première écriture locale\n * échouerait en EROFS — que même root ne peut pas lever sans\n * `/etc/synthetic.conf` et un redémarrage. Sous Linux elle demanderait les\n * droits du superutilisateur pour un dossier qui n'a rien à faire là.\n *\n * Le repli vaut aussi pour les lanceurs DÉJÀ installés, que corriger le stub\n * ne rattraperait pas.\n */\n const home = homedir();\n process.chdir(home);\n console.log(`[cli] Working directory: ${home} (lanceur sans dossier épinglé)`);\n}\n\ndotenv.config({ quiet: true });\n\nif (args.includes('--help') || args.includes('-h')) {\n console.log(`Usage: runeya [path-to-your-stack] [options]\n\nCommands:\n install-desktop [path] Create a desktop/menu launcher for this machine\n Bare on a terminal, this opens a dedicated installer page.\n uninstall-desktop Remove a launcher created by install-desktop\n --name <label> Launcher name (default: \"Runeya — <folder>\")\n --no-cwd Global launcher — do not pin a working directory\n --port <n> Port baked into the launcher (default: Runeya's own)\n --agent-port <n> Agent port baked in (default: launcher port + 1)\n --project <id> Open this project directly (no project list)\n -y, --yes Skip the wizard and take the defaults\n instances [open|stop|restart <pid>]\n List the Runeya running on this machine, and act on\n one. --json for the raw records.\n mcp Runeya MCP server on stdio, for Claude Code, Codex,\n Cursor… Asks for access in the Runeya app on first use.\n --from-desktop Set by desktop launchers: report startup failures\n in a browser window instead of a silent exit\n\nOptions:\n --version Show version number\n --pe, --pull-env Print env vars for a service to stdout (requires -e and -s)\n -e, --environment <n> Environment name or id\n -s, --service <name> Service name or id\n --lan Expose server on the local network (shortcut for --host 0.0.0.0)\n --no-open Do not open browser on startup\n --no-tray Do not put this instance in the system tray\n --agent Run as agent only (no server, no UI) — for remote machines\n -h, --help Show this help message\n\nConfiguration:\n Each setting has a flag and an equivalent RUNEYA_ environment variable; the\n flag wins. Variables are prefixed so they cannot collide with those of the\n services you manage. A .env file in the working directory is loaded\n automatically, but never overrides a flag.\n\n Flag Environment variable Default\n --port <n> RUNEYA_HTTP_PORT 4000\n (binds the agent instead when --agent is used)\n --host <address> RUNEYA_HOST 127.0.0.1\n --agent-port <n> RUNEYA_AGENT_PORT server port + 1\n --agent-host <address> RUNEYA_AGENT_HOST 127.0.0.1\n --agent-binary-path <path> RUNEYA_AGENT_BINARY_PATH bundled with the CLI\n --data-dir <path> RUNEYA_DATA_DIR <cwd>/.runeya\n --mode <mode> RUNEYA_MODE local-simple\n 'local-simple' | 'cloud'\n --cloud-url <url> RUNEYA_CLOUD_URL —\n --cloud-app-url <url> RUNEYA_CLOUD_APP_URL —\n --cors-origin <origin> RUNEYA_CORS_ORIGIN —\n --lan RUNEYA_LAN_PROXY false\n --debug RUNEYA_DEBUG false\n --claude-pty <0|1> RUNEYA_CLAUDE_PTY 1\n --project <id> RUNEYA_PROJECT_ID —\n Open this project only: no project list, no switching\n PTY mode for the Claude Code runner\n\n Secrets have no flag — process arguments are readable by other users via 'ps':\n RUNEYA_ENCRYPTION_KEY 64-char hex key for CI/CD — unlocks secrets without a\n master password AND disables tRPC authentication.\n Never set this on a machine reachable by others.\n RUNEYA_MASTER_PASSWORD Master password used by --pull-env instead of prompting\n\n Started from a terminal, 'runeya' opens the desktop shortcut installer instead\n of Runeya itself. RUNEYA_SKIP_INSTALL_CHECK=1 starts Runeya anyway (servers,\n containers, CI).\n\n Standard variables Runeya also honours: NODE_ENV, CI (skips opening the browser).\n`);\n process.exit(0);\n}\n\nif (args.includes('--version')) {\n console.log(version);\n process.exit(0);\n}\n\n/**\n * L'icône de barre système, sauf `--no-tray`.\n *\n * Chaque Runeya pose la sienne au démarrage et la retire en s'arrêtant : une\n * icône par instance en cours. Là où la barre système n'existe pas (serveur\n * sans bureau, arm64, paquet optionnel absent), il n'y a rien à désactiver —\n * l'icône ne se pose pas, et Runeya démarre pareil.\n */\nconst noTray = args.includes('--no-tray');\n\nconst noOpen =\n args.includes('--no-open') ||\n !!process.env['VITEST'] ||\n !!process.env['CI'] ||\n process.env['NODE_ENV'] === 'test';\n\n// --pull-env flag — detect early so we can silence stdout before any log\nconst isPullEnv = args.includes('--pull-env') || args.includes('--pe');\n\n// In pull-env mode, redirect console.log/info to stderr so only KEY=VALUE goes to stdout\nif (isPullEnv) {\n const toStderr = (...a: unknown[]) => process.stderr.write(a.join(' ') + '\\n');\n console.log = toStderr;\n console.info = toStderr;\n}\n\nconst serviceArg = getFlagValue(['-s', '--service']);\nconst environmentArg = getFlagValue(['-e', '--environment']);\n\n// Configure environment BEFORE importing server (env is parsed at module load)\n// --agent mode: run as standalone agent only (no server, no UI)\nconst isAgentOnly = args.includes('--agent');\n\n// --port targets the server, except in agent mode where it binds the agent\nconst portFlag = getFlagValue(['--port']);\nif (portFlag) {\n process.env[isAgentOnly ? 'RUNEYA_AGENT_PORT' : 'RUNEYA_HTTP_PORT'] = portFlag;\n}\n\n// --lan binds to 0.0.0.0. As an explicit flag it outranks RUNEYA_HOST; coming\n// from RUNEYA_LAN_PROXY it only fills in a host nobody else set.\nconst hostFlag = getFlagValue(['--host']);\nconst isLan = args.includes('--lan');\nconst isLanProxy = isLan || process.env.RUNEYA_LAN_PROXY === 'true' || process.env.RUNEYA_LAN_PROXY === '1';\nif (isLanProxy) {\n process.env.RUNEYA_LAN_PROXY = 'true';\n if (!hostFlag && isLan) process.env.RUNEYA_HOST = '0.0.0.0';\n else process.env.RUNEYA_HOST ??= '0.0.0.0';\n}\n\n// In agent mode --host binds the agent rather than the server\nif (isAgentOnly) {\n if (hostFlag) {\n process.env.RUNEYA_AGENT_HOST = hostFlag;\n } else if (isLan) {\n process.env.RUNEYA_AGENT_HOST = '0.0.0.0';\n } else if (isLanProxy) {\n process.env.RUNEYA_AGENT_HOST ??= '0.0.0.0';\n }\n}\n\n// Agent binary shipped inside the CLI dist — overridable via --agent-binary-path\nprocess.env.RUNEYA_AGENT_BINARY_PATH ??= join(__dirname, 'agent/index.js');\n// Expose CLI version to server\nprocess.env.RUNEYA_VERSION = version;\n// Tell the server it was launched from a shortcut, so the UI can say so\nif (isFromDesktop) process.env.RUNEYA_FROM_DESKTOP = '1';\n// Data directory defaults to CWD/.runeya (server env.ts default)\n// so Runeya data lives alongside the target project.\n\n/**\n * Listen with retry for EADDRINUSE — handles the race condition when the CLI\n * restarts the server before the old process has fully released the port.\n *\n * The `wss` parameter is needed because the `ws` library re-emits HTTP server\n * errors on the WebSocketServer — without a handler the error becomes unhandled.\n */\nfunction listenWithRetry(\n server: Server,\n port: number,\n host: string,\n wss?: WebSocketServer,\n maxRetries = 20,\n delay = 500,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n let attempts = 0;\n\n // Absorb EADDRINUSE errors re-emitted by the ws library on the WSS\n // during the retry window. Without this, Node.js crashes on unhandled error.\n const wssErrorHandler = () => { /* absorbed — handled via server 'error' */ };\n wss?.on('error', wssErrorHandler);\n\n const tryListen = () => {\n const onError = (err: NodeJS.ErrnoException) => {\n // Clean up the 'listening' callback that server.listen() registered\n server.removeAllListeners('listening');\n\n if (err.code === 'EADDRINUSE' && attempts < maxRetries) {\n attempts++;\n console.log(`[cli] Port ${port} in use, retrying in ${delay}ms (${attempts}/${maxRetries})...`);\n setTimeout(tryListen, delay);\n } else {\n wss?.removeListener('error', wssErrorHandler);\n reject(err);\n }\n };\n\n server.once('error', onError);\n server.listen(port, host, () => {\n server.removeListener('error', onError);\n wss?.removeListener('error', wssErrorHandler);\n resolve();\n });\n };\n\n tryListen();\n });\n}\n\nasync function main() {\n // Hors raccourci, `runeya` mène à la page d'installation (voir install-gate.ts).\n const { mustOpenInstaller, hasDesktop, SKIP_INSTALL_CHECK_ENV } = await import('./install-gate.js');\n if (mustOpenInstaller({\n isFromDesktop,\n isRestartHandoff: process.env.RUNEYA_RESTART_HANDOFF === '1',\n startsServer: !isPullEnv && !isAgentOnly,\n env: process.env,\n })) {\n const { shouldOpenInstallPage } = await import('./desktop-wizard.js');\n if (!hasDesktop(process.platform, process.env)) {\n // Serveur, SSH : ni page à montrer, ni menu où poser un raccourci.\n console.error('Aucun bureau sur cette session (SSH ou serveur sans affichage) : pas de raccourci à créer ici.');\n console.error(`Pour démarrer Runeya directement : ${SKIP_INSTALL_CHECK_ENV}=1 runeya`);\n process.exit(1);\n }\n if (!shouldOpenInstallPage([], false)) {\n // Sans terminal (script, CI), personne ne verrait la page.\n console.error('Runeya se lance depuis un raccourci de bureau : lancez « runeya » dans un terminal pour le créer.');\n console.error(`Pour démarrer Runeya sans raccourci (serveur, CI) : ${SKIP_INSTALL_CHECK_ENV}=1 runeya`);\n process.exit(1);\n }\n process.exit(await runDesktopInstaller(true));\n }\n\n // Avant tout : ce que ce process hérite ici, le serveur, l'agent local et\n // chaque service lancé ensuite en héritent à leur tour. Un raccourci de bureau\n // reçoit le PATH de la session graphique, où ni les gestionnaires de version\n // ni les binaires installés par l'utilisateur (claude, codex) ne figurent.\n const { ensureNodeBinOnPath, ensureUserBinsOnPath, stampConsole } = await import('@runeya/packages-shared/node');\n ensureNodeBinOnPath();\n await ensureUserBinsOnPath();\n\n // Journaux datés du temps écoulé depuis le démarrage. Jamais en --pull-env :\n // sa sortie est lue par un script, pas par un humain.\n if (!isPullEnv) {\n stampConsole();\n // Sortie et crash recopiés sur disque : une instance relancée en\n // `stdio: 'ignore'` (bureau, mise à jour) n'a sinon aucune trace.\n try {\n const { installLogFile } = await import('./log-file.js');\n console.log(`[cli] Journal : ${installLogFile().logPath}`);\n } catch (err) {\n console.warn(`[cli] Journal sur disque indisponible : ${(err as Error).message}`);\n }\n }\n\n // Dynamic import ensures env is parsed AFTER we configure it\n const { createLocalServer, pullEnv, PullEnvError, registerInstance, registerCliLocation } = await import('@runeya/apps-server');\n\n // D'où ce CLI a été lancé, pour que l'interface puisse proposer `install-desktop`\n // sans que le serveur ait à deviner un exécutable.\n registerCliLocation({ execPath: process.execPath, cliEntry: fileURLToPath(import.meta.url) });\n\n // Handle --pull-env mode: resolve env vars and print, then exit\n if (isPullEnv) {\n if (!serviceArg) {\n console.error('Error: --service (-s) is required with --pull-env');\n process.exit(1);\n }\n if (!environmentArg) {\n console.error('Error: --environment (-e) is required with --pull-env');\n process.exit(1);\n }\n try {\n const output = await pullEnv({ service: serviceArg, environment: environmentArg });\n if (output) process.stdout.write(output + '\\n');\n process.exit(0);\n } catch (err: unknown) {\n if (err instanceof PullEnvError) {\n console.error(`Error: ${err.message}`);\n process.exit(err.exitCode);\n }\n throw err;\n }\n }\n\n // --agent mode: standalone agent, no server, no UI\n if (isAgentOnly) {\n const { spawn } = await import('node:child_process');\n const agentBin = join(__dirname, 'agent/index.js');\n const child = spawn(process.execPath, [agentBin], { stdio: 'inherit', windowsHide: true });\n child.on('exit', (code) => process.exit(code ?? 0));\n // SIGINT is already delivered to the child by the terminal (same process group).\n // Intercept it here only to suppress Node's default exit so we stay alive\n // until the child finishes its graceful shutdown.\n process.on('SIGINT', () => { /* wait for child to exit */ });\n process.on('SIGTERM', () => child.kill('SIGTERM'));\n return;\n }\n\n /**\n * Un relais de redémarrage (`appUpdate.restart`) est l'exception au garde-fou\n * ci-dessous : l'instance qui répond encore sur le port est précisément celle\n * qu'on remplace. On la laisse s'éteindre, `listenWithRetry` absorbe\n * l'EADDRINUSE du chevauchement. La marque est consommée ici pour ne pas se\n * propager aux services lancés ensuite, qui héritent de cet environnement.\n */\n const isRestartHandoff = process.env.RUNEYA_RESTART_HANDOFF === '1';\n delete process.env.RUNEYA_RESTART_HANDOFF;\n\n /**\n * A shortcut clicked twice must not fail.\n *\n * Starting a second instance on a port a Runeya already holds ends in\n * EADDRINUSE after ten seconds of retries — and from a launcher there is no\n * terminal to read that in. So the port is asked first: if Runeya answers,\n * the right move is to bring up the window that already exists.\n *\n * Only for launcher starts. From a terminal, `runeya` twice on one port is a\n * mistake worth reporting, not one worth papering over.\n */\n if (isFromDesktop && !isRestartHandoff) {\n const port = resolvePort();\n const { probeRuneya } = await import('./free-port.js');\n const existing = await probeRuneya(port);\n if (existing) {\n const url = `http://localhost:${port}`;\n console.log(`[cli] Runeya v${existing.version} déjà en cours sur le port ${port} — ouverture de ${url}`);\n if (!noOpen) await open(url);\n return;\n }\n }\n\n console.log('[cli] Starting Runeya...');\n\n // Create server (automatically spawns local agent)\n const { app, server, wss, shutdown } = await createLocalServer();\n\n // Serve static files from web frontend (embedded in CLI dist during build)\n const webDistPath = join(__dirname, 'web');\n\n /**\n * Les fichiers d'`assets/` portent un hash de leur contenu dans leur nom : une\n * mise à jour produit un NOUVEAU nom, jamais un contenu différent sous le même\n * nom. Ils peuvent donc être gardés indéfiniment, et `immutable` dit au\n * navigateur de ne même pas revalider.\n *\n * Sans cela, `express.static` répond `max-age=0` : chaque rechargement\n * redemande les 295 fichiers pour s'entendre répondre 304. Ce sont des\n * requêtes vides, mais elles occupent les six connexions qu'un navigateur\n * accorde par hôte — pour tous ses onglets à la fois. Avec plusieurs onglets\n * rechargés ensemble, l'ouverture du WebSocket attendait derrière cette file.\n */\n app.use('/assets', express.static(join(webDistPath, 'assets'), {\n immutable: true,\n maxAge: '1y',\n fallthrough: false,\n }));\n\n // Tout le reste (favicon…) : revalidation à chaque fois, ces noms ne changent\n // pas d'une version à l'autre.\n app.use(express.static(webDistPath, {\n index: false,\n setHeaders: (res) => res.setHeader('Cache-Control', 'no-cache'),\n }));\n\n // Fallback to index.html for client-side routing (Vue Router)\n // Express 5 requires explicit path patterns (not '*')\n //\n // JAMAIS mis en cache : c'est lui qui nomme les assets de la version courante.\n // Un index.html périmé rappellerait les fichiers de l'ancienne version — qui,\n // eux, ne sont plus là.\n //\n // `root` + nom relatif, jamais un chemin absolu : `sendFile` refuse tout\n // segment commençant par un point (`dotfiles: 'ignore'` par défaut) et\n // répond 404. Or l'installation la plus courante — `npx @runeya/runeya` —\n // vit sous `~/.npm/_npx/…`. Avec un chemin absolu, la page d'accueil était\n // donc introuvable sur toute machine lancée par npx (ou depuis `~/.nvm`),\n // alors que `/index.html` répondait : `express.static` ne teste, lui, que la\n // partie relative à sa racine.\n app.use((_req: Request, res: Response) => {\n res.setHeader('Cache-Control', 'no-cache');\n res.sendFile('index.html', { root: webDistPath });\n });\n\n const port = resolvePort();\n const host = process.env.RUNEYA_HOST ?? '127.0.0.1';\n\n await listenWithRetry(server, port, host, wss);\n\n // Announce this instance now that the port is really ours, so the settings\n // panel of any Runeya on this machine can list it.\n await registerInstance();\n\n /**\n * L'adresse d'écoute n'est pas une adresse de visite.\n *\n * `0.0.0.0` (et son équivalent IPv6 `::`) dit « toutes les interfaces » à\n * bind ; ce n'est pas une destination. Sous Linux une connexion vers elle\n * retombe sur la boucle locale, ce qui a longtemps masqué le problème — sous\n * Windows elle n'est pas routable du tout, et les navigateurs la bloquent\n * désormais ailleurs aussi. On écoute donc partout, mais on ouvre localhost.\n */\n const browseHost = host === '0.0.0.0' || host === '::' ? 'localhost' : host;\n const appUrl = `http://${browseHost}:${port}`;\n\n if (!noOpen) {\n await open(appUrl);\n }\n\n console.log('\\n🚀 Runeya is running!');\n if (isLanProxy) {\n console.log('🌐 LAN mode: server and service ports exposed on local network');\n }\n printStartupTable(port, appUrl);\n console.log('[cli] Press Ctrl+C to stop');\n\n // Assignée plus bas : l'arrêt doit pouvoir la retirer, et la poser demande\n // de savoir arrêter — les deux se référencent.\n let tray: { stop: () => Promise<void> } | null = null;\n\n let shuttingDown = false;\n const gracefulShutdown = async (signal: string) => {\n if (shuttingDown) {\n console.log(`[cli] Forced exit (second ${signal})`);\n process.exit(1);\n }\n shuttingDown = true;\n console.log(`\\n[cli] Received ${signal}, shutting down gracefully...`);\n\n // L'icône part en premier : elle annonce une instance vivante, et la\n // laisser pendant l'extinction inviterait à cliquer dans le vide.\n await tray?.stop();\n\n // In dev, exit immediately — ports are freed on process death,\n // and the 'exit' handler in agent-spawner SIGKILLs the agent.\n if (process.env.NODE_ENV === 'development') {\n process.exit(0);\n }\n\n await shutdown();\n console.log('[cli] Shutdown complete');\n process.exit(0);\n };\n\n process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));\n process.on('SIGINT', () => gracefulShutdown('SIGINT'));\n\n if (!noTray) {\n const { startInstanceTray } = await import('./tray.js');\n const {\n scheduleSelfRestart,\n listTrayServices,\n onTrayServicesChanged,\n startProcess,\n stopProcess,\n restartProcess,\n networkInfo,\n setLanMode,\n resolveTrayIcon,\n } = await import('@runeya/apps-server');\n\n tray = await startInstanceTray({\n info: { cwd: process.cwd(), port, version, url: appUrl },\n assetsDir: join(__dirname, 'assets'),\n openUrl: (target) => open(target),\n restart: () => scheduleSelfRestart(),\n stop: () => void gracefulShutdown('tray'),\n\n listServices: listTrayServices,\n startService: (id) => startProcess(id),\n // L'arrêt exige l'agent qui le fait tourner : il est dans la vue, et\n // seul un service en cours propose ce geste.\n stopService: async (id) => {\n const service = (await listTrayServices()).find((s) => s.id === id);\n if (!service?.agentId) throw new Error(`Aucun agent pour ${id}`);\n return stopProcess(id, service.agentId);\n },\n restartService: (id) => restartProcess(id),\n onServicesChanged: onTrayServicesChanged,\n // Le logo de l'organisation du projet ouvert, quand il y en a un : c'est ce\n // qui distingue deux Runeya dans la barre système.\n projectIcon: () => resolveTrayIcon(),\n\n lan: {\n read: () => {\n const info = networkInfo();\n // `lanUrls` est déjà ce qu'on voudrait lire au survol : des adresses\n // joignables, pas des IP brutes. Vide hors mode réseau local.\n return { enabled: info.lanEnabled, addresses: info.lanUrls };\n },\n toggle: (enabled) => setLanMode(enabled),\n },\n\n warn: (message) => console.log(message),\n });\n }\n}\n\nmain().catch(async (err) => {\n console.error('[cli] Fatal error:', err);\n\n // With a terminal, the message above is the report. Without one, it goes\n // nowhere: serve it as a page and open it in the browser instead.\n if (startupFailure) {\n try {\n const { diagnose, renderFailurePage, capturedLines, showStartupFailure } = startupFailure;\n const html = renderFailurePage(\n diagnose(err, resolvePort()),\n capturedLines(),\n {\n 'Version': version,\n 'Dossier de travail': process.cwd(),\n 'Node': process.execPath,\n 'Runeya': fileURLToPath(import.meta.url),\n },\n );\n await showStartupFailure(html);\n } catch (reportErr) {\n console.error('[cli] Impossible d\\'afficher le rapport d\\'erreur:', reportErr);\n }\n }\n\n process.exit(1);\n});\n","/**\n * Resolve the HTTP port Runeya should listen on.\n *\n * Only RUNEYA_HTTP_PORT is read (default 4000) — the generic PORT variable is\n * intentionally ignored so it can stay reserved for the user's own services.\n *\n * Throws if the resolved value is not an integer in [1, 65535].\n */\nexport function resolvePort(): number {\n const raw = process.env.RUNEYA_HTTP_PORT;\n if (raw === undefined) return 4000;\n\n const port = Number(raw);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error(\n `Invalid port: ${JSON.stringify(raw)}. Must be an integer between 1 and 65535.`,\n );\n }\n return port;\n}\n"],"mappings":"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","names":["answers","resolve","port"]}
|
|
@@ -19,7 +19,7 @@ function samePath(a, b, platform = process.platform) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
async function createGlobalInstaller(current, runningVersion) {
|
|
22
|
-
const { resolveNpm, probeVersion, installRuneyaGlobally, installedRuneya, compareSemver, installPrefix, userPrefix } = await import("./src-
|
|
22
|
+
const { resolveNpm, probeVersion, installRuneyaGlobally, installedRuneya, compareSemver, installPrefix, userPrefix } = await import("./src-KWO3CKZU.js");
|
|
23
23
|
const npm = resolveNpm();
|
|
24
24
|
async function prefixes() {
|
|
25
25
|
const global = await probeVersion(npm, ["prefix", "-g"]);
|
|
@@ -57,4 +57,4 @@ export {
|
|
|
57
57
|
hasDesktop,
|
|
58
58
|
mustOpenInstaller
|
|
59
59
|
};
|
|
60
|
-
//# sourceMappingURL=install-gate-
|
|
60
|
+
//# sourceMappingURL=install-gate-OJJEUU4E.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quelle instance Runeya le MCP appelle, quand il n'est pas lancé par Runeya.
|
|
3
|
+
*
|
|
4
|
+
* Lancé par Runeya, le MCP reçoit `RUNEYA_BASE_URL` et n'a rien à choisir.
|
|
5
|
+
* Lancé par un client externe (`runeya mcp`), il lit le registre machine :
|
|
6
|
+
* - aucune instance : rien à appeler, l'outil échoue en le disant ;
|
|
7
|
+
* - une seule : elle est prise d'office ;
|
|
8
|
+
* - plusieurs : l'appel doit nommer l'instance par son pid
|
|
9
|
+
* (`runeyaInstance`), que l'utilisateur choisit dans la conversation.
|
|
10
|
+
*
|
|
11
|
+
* Le choix n'est pas gardé ici. Un client comme Cursor ou Claude Desktop garde
|
|
12
|
+
* un seul process MCP pour toutes ses conversations : un choix mémorisé dans
|
|
13
|
+
* l'une s'appliquerait en silence aux autres. Le modèle le porte donc dans
|
|
14
|
+
* chaque appel, et chaque conversation a le sien.
|
|
15
|
+
*
|
|
16
|
+
* Le pid, pas le port : une instance qui s'arrête ne fait pas atterrir les
|
|
17
|
+
* appels sur celle qui reprendrait son port. La liste est relue à chaque appel.
|
|
18
|
+
*/
|
|
19
|
+
/** Ce dont le sélecteur a besoin d'une instance du registre. */
|
|
20
|
+
interface McpInstance {
|
|
21
|
+
pid: number;
|
|
22
|
+
port: number;
|
|
23
|
+
cwd: string;
|
|
24
|
+
/** Le dossier de données de l'instance : stable d'un redémarrage à l'autre. */
|
|
25
|
+
dataDir: string;
|
|
26
|
+
version?: string | undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ExternalMcpOptions {
|
|
30
|
+
listInstances: () => Promise<McpInstance[]>;
|
|
31
|
+
/** La racine machine de Runeya (`~/.runeya`) : le secret d'appairage y est rangé. */
|
|
32
|
+
machineRoot?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Le MCP lancé par un client externe (`runeya mcp`) : Claude Code, Codex,
|
|
36
|
+
* Cursor… configuré une fois, il suit les mises à jour du CLI.
|
|
37
|
+
*
|
|
38
|
+
* L'instance à appeler vient du registre machine (voir `instance-selector.ts`),
|
|
39
|
+
* sauf si `RUNEYA_BASE_URL` la fixe. Le jeton vient de l'appairage avec cette
|
|
40
|
+
* instance (voir `pairing-client.ts`), sauf si `RUNEYA_API_TOKEN` en donne un.
|
|
41
|
+
*/
|
|
42
|
+
declare function runExternalMcp(opts: ExternalMcpOptions): Promise<void>;
|
|
43
|
+
|
|
44
|
+
export { type ExternalMcpOptions, runExternalMcp };
|