@runeya/runeya 2.0.140 → 2.0.142
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.map +1 -1
- package/agent-manager-M4NA5YFD.js +11 -0
- package/{chat-L7RRHXX5.js → chat-X3X527EX.js} +9 -7
- package/{chunk-GOMF7NWS.js → chunk-7L6AQUBR.js} +2 -2
- package/{chunk-O2KOX4LU.js → chunk-C2BGNVBT.js} +35 -9
- package/chunk-C2BGNVBT.js.map +1 -0
- package/{chunk-SYCUQDA4.js → chunk-GXE5TLLJ.js} +10 -4
- package/chunk-GXE5TLLJ.js.map +1 -0
- package/{chunk-EV3TBM7E.js → chunk-IXO5KIO5.js} +9 -2
- package/{chunk-EV3TBM7E.js.map → chunk-IXO5KIO5.js.map} +1 -1
- package/{chunk-QWX6R6AW.js → chunk-NSS47H26.js} +71 -16
- package/chunk-NSS47H26.js.map +1 -0
- package/{chunk-G4TOLDZB.js → chunk-P73PTVII.js} +95 -63
- package/chunk-P73PTVII.js.map +1 -0
- package/{chunk-6HSZPK2W.js → chunk-UR3HL373.js} +3 -3
- package/{companion-LQVLEHU7.js → companion-2LHKU577.js} +5 -5
- package/{desktop-installer-page-OKE4WY5Q.js → desktop-installer-page-6JDSXK2E.js} +226 -33
- package/desktop-installer-page-6JDSXK2E.js.map +1 -0
- package/{free-port-565NMFIZ.js → free-port-5UNHDWGE.js} +2 -2
- package/index.js +70 -22
- package/index.js.map +1 -1
- package/install-gate-2NNH7RM2.js +60 -0
- package/install-gate-2NNH7RM2.js.map +1 -0
- package/mcp-server/index.js +84 -10
- package/mcp-server/index.js.map +1 -1
- package/mcp-server/launch.d.ts +5 -4
- package/mcp-server/launch.js.map +1 -1
- package/package.json +1 -1
- package/settings-manager-32SVMAFN.js +10 -0
- package/{src-NVQHFMTQ.js → src-5TEXF3SU.js} +938 -809
- package/src-5TEXF3SU.js.map +1 -0
- package/{vault-resolver-B5P2OUI5.js → vault-resolver-VK6W5J6W.js} +2 -2
- package/web/assets/{AgentScalarChatInterface.vue-Dgobg9wk.js → AgentScalarChatInterface.vue-uNy1P-l4.js} +1 -1
- package/web/assets/{AgentTerminalPage-B43nSPSz.js → AgentTerminalPage-CFnALyb1.js} +1 -1
- package/web/assets/{AiChatModals-CVZ5ilmt.js → AiChatModals-B6svb69W.js} +1 -1
- package/web/assets/{AppLayout-B5yKLUj9.js → AppLayout-BKlJtfuQ.js} +1 -1
- package/web/assets/{AuditPage-KR5ydSRG.js → AuditPage-BsSpX2TJ.js} +1 -1
- package/web/assets/{CloudAccountPage-RNosmi99.js → CloudAccountPage-DabrwDsa.js} +1 -1
- package/web/assets/{CloudMessagingPage-Dp-FDdoH.js → CloudMessagingPage-BtOJO5FB.js} +1 -1
- package/web/assets/{DashboardPage-Bjj11oP_.js → DashboardPage-DMByzpgW.js} +1 -1
- package/web/assets/{DirectoryPickerDialog-C28UH35_.js → DirectoryPickerDialog-CDyk4Z_Q.js} +1 -1
- package/web/assets/{EnvironmentForm-Dj2T2DuS.js → EnvironmentForm-BlGWpU2_.js} +1 -1
- package/web/assets/{EnvironmentFormPage-B4Bt1bXS.js → EnvironmentFormPage-B-dpyO0_.js} +1 -1
- package/web/assets/{EnvironmentListPage-B_V9mEhR.js → EnvironmentListPage-BlaOiTVh.js} +1 -1
- package/web/assets/{KanbanBoardPage-CQLiin5v.js → KanbanBoardPage-C52zM-Zw.js} +3 -3
- package/web/assets/{KanbanListPage-ChD0Zj0Y.js → KanbanListPage-BmNdd1aP.js} +1 -1
- package/web/assets/{LaunchProjectPage-ByIfq7ka.js → LaunchProjectPage-B5sXxaet.js} +1 -1
- package/web/assets/{LaunchServicesPage-BweD0CWu.js → LaunchServicesPage-C1u6lV4l.js} +1 -1
- package/web/assets/{LaunchShell-C9jV0DDh.js → LaunchShell-CeyVMzL2.js} +1 -1
- package/web/assets/{LogViewer-Cv024Meq.js → LogViewer-CAvRwSZu.js} +2 -2
- package/web/assets/{OrgKeyPrompt-D95Zm3Tl.js → OrgKeyPrompt-NDfCQ-In.js} +1 -1
- package/web/assets/{ProcessControls-84xtt8ud.js → ProcessControls-D23gTUiv.js} +1 -1
- package/web/assets/{ProcessDetailPage-CqMk8XQv.js → ProcessDetailPage-vHpK0Eer.js} +1 -1
- package/web/assets/{ProjectCreateDialog-C4rLTHw0.js → ProjectCreateDialog-2PZX7tq2.js} +1 -1
- package/web/assets/{ProjectLocationGate-BtI0T-GU.js → ProjectLocationGate-fMAxwT0y.js} +1 -1
- package/web/assets/{ProjectPublishDialog-v5xz8t4N.js → ProjectPublishDialog-BylWoa0n.js} +1 -1
- package/web/assets/{RandomJobTestPage-CzT3LSMu.js → RandomJobTestPage-DUclmkBw.js} +1 -1
- package/web/assets/ScenarioEditorPage-DbEsfuZa.js +14 -0
- package/web/assets/{SendToChatDialog-BtsCq9hv.js → SendToChatDialog-DGuOMMrS.js} +1 -1
- package/web/assets/{ServiceControlsBar-BFemdioZ.js → ServiceControlsBar-D6M8hYFk.js} +1 -1
- package/web/assets/{ServiceDetailPage-DsGHPdUc.js → ServiceDetailPage-G-A9ryZD.js} +1 -1
- package/web/assets/{ServiceFormPage-x3jtyV1A.js → ServiceFormPage-BgHSW1XX.js} +1 -1
- package/web/assets/{SettingsPage-3tVWDaUN.js → SettingsPage-CPFBr7QE.js} +6 -6
- package/web/assets/{UnlockPage-CPcll-ov.js → UnlockPage-B4VEN9LN.js} +1 -1
- package/web/assets/{VaultMigrationPage-BMS4BcsA.js → VaultMigrationPage-9qDGbF9X.js} +1 -1
- package/web/assets/VersionBadge-CqHdCnuJ.js +1 -0
- package/web/assets/VersionBadge-CtFji3sv.css +1 -0
- package/web/assets/{WikiPage-CM1-hJvF.js → WikiPage-Pjosr1Vn.js} +1 -1
- package/web/assets/{WikiView-MQLuO7ey.js → WikiView-Bcxq2mQW.js} +1 -1
- package/web/assets/{add-scalar-classes-BDJ0Zhvc.js → add-scalar-classes-BnJYThbS.js} +2 -2
- package/web/assets/{agent.store-CEmOZ8XE.js → agent.store-DTyu_McS.js} +1 -1
- package/web/assets/auth-D_pCPFq7.js +2 -0
- package/web/assets/{cloud-chat.store-DhCEhagv.js → cloud-chat.store-CMklOB48.js} +1 -1
- package/web/assets/{cloud-client-CmrptmtM.js → cloud-client-CX0pW7Kt.js} +2 -2
- package/web/assets/cloud-session-ja0-uvVS.js +2 -0
- package/web/assets/{editor.store-Be5P5Eur.js → editor.store-BAjGG8Ji.js} +1 -1
- package/web/assets/i18n-CXvcscXP.js +1 -0
- package/web/assets/{index-CXnyET_X.js → index-DTrxphh3.js} +101 -101
- package/web/assets/index-eb4FRryj.css +1 -0
- package/web/assets/{modal-9BC14oRF.js → modal-Cvv2YgUo.js} +1 -1
- package/web/assets/{project-launch.store-cO8Wr508.js → project-launch.store-C7xfTlMc.js} +1 -1
- package/web/assets/{project.store-CpUEwUgF.js → project.store-ZpH_PaSy.js} +1 -1
- package/web/assets/scenario.store-F78cizck.js +7 -0
- package/web/assets/{service.store-ve_P4_2O.js → service.store-Dti7eDhR.js} +1 -1
- package/web/assets/{settings-scope-CcYu-GYo.js → settings-scope-CUuHrUpc.js} +1 -1
- package/web/assets/{settings.store-CdCOUAsL.js → settings.store-De1-3IG8.js} +1 -1
- package/web/assets/{src-T5uKA02c.js → src-C0KK-qsF.js} +1 -1
- package/web/assets/{src-RO7PD0gR.js → src-DXlxwMXe.js} +1 -1
- package/web/assets/{theme-CPaZfI9r.js → theme-BZCAxfJD.js} +1 -1
- package/web/assets/trpc-B5Yw_z6v.js +3 -0
- package/web/assets/{useCloudOrg-DaFUgt9j.js → useCloudOrg-QzX8ryg1.js} +1 -1
- package/web/assets/{useEnvVarAutocomplete-CXlyG01r.js → useEnvVarAutocomplete-BdGgINv9.js} +1 -1
- package/web/assets/{useProjectCatalog-1vLcCQlh.js → useProjectCatalog-Cr90BND4.js} +1 -1
- package/web/assets/{useProjectCloudOrg-MAfohLuM.js → useProjectCloudOrg-2wwByg2k.js} +1 -1
- package/web/assets/{useServiceUrlHost-D9k9s21s.js → useServiceUrlHost-CxbARDeg.js} +1 -1
- package/web/assets/{vue-draggable-plus-SmOauPHy.js → vue-draggable-plus-pTwVkhnN.js} +1 -1
- package/web/favicon.svg +1 -5
- package/web/index.html +18 -18
- package/agent-manager-RZI65MSV.js +0 -11
- package/chunk-G4TOLDZB.js.map +0 -1
- package/chunk-O2KOX4LU.js.map +0 -1
- package/chunk-QWX6R6AW.js.map +0 -1
- package/chunk-SYCUQDA4.js.map +0 -1
- package/desktop-installer-page-OKE4WY5Q.js.map +0 -1
- package/settings-manager-5JHSSKQ2.js +0 -10
- package/src-NVQHFMTQ.js.map +0 -1
- package/web/assets/ScenarioEditorPage-DrWcVOHg.js +0 -14
- package/web/assets/VersionBadge-54viPqlY.css +0 -1
- package/web/assets/VersionBadge-prnLNUGg.js +0 -1
- package/web/assets/auth-BpxI0N26.js +0 -2
- package/web/assets/cloud-session-ByODsQrA.js +0 -2
- package/web/assets/i18n-m0G6Pfwe.js +0 -1
- package/web/assets/index-Dsh11_J0.css +0 -1
- package/web/assets/scenario.store-B5uv1DSO.js +0 -7
- package/web/assets/trpc-BFA-mSM8.js +0 -3
- /package/{agent-manager-RZI65MSV.js.map → agent-manager-M4NA5YFD.js.map} +0 -0
- /package/{chat-L7RRHXX5.js.map → chat-X3X527EX.js.map} +0 -0
- /package/{chunk-GOMF7NWS.js.map → chunk-7L6AQUBR.js.map} +0 -0
- /package/{chunk-6HSZPK2W.js.map → chunk-UR3HL373.js.map} +0 -0
- /package/{companion-LQVLEHU7.js.map → companion-2LHKU577.js.map} +0 -0
- /package/{free-port-565NMFIZ.js.map → free-port-5UNHDWGE.js.map} +0 -0
- /package/{settings-manager-5JHSSKQ2.js.map → settings-manager-32SVMAFN.js.map} +0 -0
- /package/{vault-resolver-B5P2OUI5.js.map → vault-resolver-VK6W5J6W.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/desktop-installer-page.ts","../src/desktop-installer-html.ts"],"sourcesContent":["/**\n * La page d'installation du raccourci de bureau, servie par `runeya install-desktop`.\n *\n * Runeya n'est PAS démarrée : ni agent, ni répertoire de données. Un serveur\n * HTTP éphémère, sur la boucle locale et un port tiré par le système, sert une\n * page dédiée où l'on choisit le nom et les ports. Le raccourci créé ici est\n * toujours global ; celui d'un projet se crée depuis l'app, dans les réglages. Le raccourci\n * écrit, il s'arrête et rend la main au terminal.\n *\n * Deux gardes, parce qu'un port local est joignable par n'importe quel onglet :\n * l'en-tête `Host` doit nommer ce serveur (pas de rebinding DNS), et une\n * écriture doit venir de sa propre origine, en JSON — ce qu'un formulaire d'un\n * autre site ne peut pas envoyer sans pré-vol.\n */\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { isAbsolute } from 'node:path';\nimport type { AddressInfo } from 'node:net';\nimport { deriveIdentity, launcherProblems, type InstalledLauncher, type LauncherResult } from './desktop-launcher.js';\nimport { findFreePortPair, isPortFree } from './free-port.js';\nimport { shellArg, whereToFind } from './desktop-wizard.js';\nimport { installerHtml } from './desktop-installer-html.js';\n\n/** Les ports par défaut de Runeya, proposés parmi les choix. */\nconst DEFAULT_PORT = 4000;\n\n/** Au-delà, une requête n'est pas un formulaire de quatre champs. */\nconst MAX_BODY_BYTES = 16 * 1024;\n\nexport type InstallerAnswers = {\n workingDir: string | null;\n name: string;\n port: number | null;\n agentPort: number | null;\n};\n\n/** Ce que la page affiche avant tout choix. */\nexport type InstallerState = {\n /** Le nom proposé : celui d'un raccourci global. */\n name: string;\n /** Une paire libre tirée au hasard, `null` si aucune n'a été trouvée. */\n suggestedPort: number | null;\n defaultPort: number;\n defaultsFree: boolean;\n};\n\nfunction isPort(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= 65_535;\n}\n\n/**\n * Valider le corps d'une installation. Rejette plutôt que de corriger : un nom\n * trop long ou un port hors borne est une erreur à montrer, pas à raboter.\n */\nexport function parseInstallRequest(body: unknown): InstallerAnswers | string {\n if (typeof body !== 'object' || body === null) return 'Requête invalide.';\n const b = body as Record<string, unknown>;\n\n if (typeof b['name'] !== 'string') return 'Nom manquant.';\n const name = b['name'].trim();\n if (name.length === 0) return 'Le nom ne peut pas être vide.';\n if (name.length > 120) return 'Le nom ne doit pas dépasser 120 caractères.';\n\n const workingDir = b['workingDir'];\n if (workingDir !== null && (typeof workingDir !== 'string' || workingDir.length > 4096 || !isAbsolute(workingDir))) {\n return 'Le dossier de travail doit être un chemin absolu.';\n }\n\n const port = b['port'];\n const agentPort = b['agentPort'];\n if (port !== null && !isPort(port)) return 'Port de Runeya invalide.';\n if (agentPort !== null && !isPort(agentPort)) return 'Port de l’agent invalide.';\n if ((port === null) !== (agentPort === null)) return 'Les deux ports vont ensemble.';\n if (port !== null && port === agentPort) return 'Runeya et l’agent ne peuvent pas partager un port.';\n\n return { name, workingDir: workingDir as string | null, port: port as number | null, agentPort: agentPort as number | null };\n}\n\n/** L'en-tête `Host` nomme-t-il ce serveur ? */\nexport function isOwnHost(host: string | undefined, port: number): boolean {\n return host === `127.0.0.1:${port}` || host === `localhost:${port}`;\n}\n\nasync function readJson(req: IncomingMessage): Promise<unknown> {\n let size = 0;\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n size += (chunk as Buffer).length;\n if (size > MAX_BODY_BYTES) throw new Error('too-large');\n chunks.push(chunk as Buffer);\n }\n return JSON.parse(Buffer.concat(chunks).toString('utf8'));\n}\n\nfunction send(res: ServerResponse, status: number, body: unknown, type = 'application/json; charset=utf-8'): void {\n res.writeHead(status, {\n 'Content-Type': type,\n 'Cache-Control': 'no-store',\n 'X-Content-Type-Options': 'nosniff',\n 'Referrer-Policy': 'no-referrer',\n 'Content-Security-Policy': \"default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; connect-src 'self'; img-src data:\",\n });\n res.end(typeof body === 'string' ? body : JSON.stringify(body));\n}\n\nexport type InstallerOutcome = { answers: InstallerAnswers; result: LauncherResult };\n\n/** Un raccourci existant, tel que la page le montre. */\nexport type LauncherView = {\n id: string;\n name: string;\n workingDir: string | null;\n port: number | null;\n agentPort: number | null;\n paths: string[];\n /** Ce qui l'empêcherait de démarrer. */\n problems: string[];\n /** Il vise un autre Node ou un autre Runeya que celui-ci : le recréer le repointe. */\n outdated: boolean;\n};\n\nasync function toView(l: InstalledLauncher, current: { nodePath: string; cliEntry: string }): Promise<LauncherView> {\n return {\n id: l.id,\n name: l.name,\n workingDir: l.workingDir,\n port: l.port,\n agentPort: l.agentPort,\n paths: l.paths,\n problems: await launcherProblems(l),\n outdated: l.nodePath !== current.nodePath || l.cliEntry !== current.cliEntry,\n };\n}\n\n/** Le corps d'une action sur un raccourci existant : son id, rien d'autre. */\nfunction parseLauncherRef(body: unknown): string | null {\n const id = (body as { id?: unknown } | null)?.id;\n return typeof id === 'string' && id.length > 0 && id.length <= 200 ? id : null;\n}\n\n/**\n * Servir la page et attendre qu'on y installe — ou qu'on y renonce.\n *\n * `install` écrit le raccourci ; il est appelé ici, dans la requête, pour que la\n * page affiche le résultat réel et non une promesse. Rend `null` si l'on annule\n * depuis la page ou par Ctrl+C.\n */\nexport async function runInstallerPage(opts: {\n openUrl: (url: string) => Promise<unknown>;\n install: (answers: InstallerAnswers) => Promise<LauncherResult>;\n /** Les raccourcis déjà posés, relus à chaque demande. */\n list: () => Promise<InstalledLauncher[]>;\n uninstall: (launcher: InstalledLauncher) => Promise<LauncherResult>;\n /** Le Node et l'entrée du CLI en cours — ceux qu'un raccourci recréé visera. */\n current: { nodePath: string; cliEntry: string };\n}): Promise<InstallerOutcome | null> {\n const suggestedPort = await findFreePortPair();\n const state: InstallerState = {\n name: deriveIdentity(undefined, null).name,\n suggestedPort,\n defaultPort: DEFAULT_PORT,\n defaultsFree: (await isPortFree(DEFAULT_PORT)) && (await isPortFree(DEFAULT_PORT + 1)),\n };\n\n return new Promise((resolvePromise) => {\n let settled = false;\n let installing = false;\n\n const server = createServer((req, res) => {\n void handle(req, res).catch(() => send(res, 500, { error: 'Erreur interne.' }));\n });\n\n function finish(outcome: InstallerOutcome | null): void {\n if (settled) return;\n settled = true;\n process.off('SIGINT', onSigint);\n // Laisser partir la réponse en cours avant de couper les connexions.\n setImmediate(() => {\n server.close();\n server.closeAllConnections();\n resolvePromise(outcome);\n });\n }\n\n function onSigint(): void {\n console.log('\\nAnnulé.');\n finish(null);\n }\n\n async function handle(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const { port } = server.address() as AddressInfo;\n if (!isOwnHost(req.headers.host, port)) return send(res, 421, { error: 'Hôte inattendu.' });\n\n const url = new URL(req.url ?? '/', `http://127.0.0.1:${port}`);\n\n if (req.method === 'GET' && url.pathname === '/') {\n return send(res, 200, installerHtml(), 'text/html; charset=utf-8');\n }\n if (req.method === 'GET' && url.pathname === '/api/state') {\n return send(res, 200, state);\n }\n if (req.method === 'GET' && url.pathname === '/api/launchers') {\n const launchers = await opts.list();\n return send(res, 200, await Promise.all(launchers.map((l) => toView(l, opts.current))));\n }\n\n if (req.method === 'POST') {\n const origin = req.headers.origin;\n if (!origin || !isOwnHost(origin.replace(/^http:\\/\\//, ''), port)) {\n return send(res, 403, { error: 'Origine refusée.' });\n }\n if (!req.headers['content-type']?.startsWith('application/json')) {\n return send(res, 415, { error: 'JSON attendu.' });\n }\n\n if (url.pathname === '/api/cancel') {\n send(res, 200, { ok: true });\n console.log('\\nAnnulé depuis la page.');\n return finish(null);\n }\n\n if (url.pathname === '/api/uninstall' || url.pathname === '/api/reinstall') {\n if (installing || settled) return send(res, 409, { error: 'Une opération est déjà en cours.' });\n let body: unknown;\n try {\n body = await readJson(req);\n } catch {\n return send(res, 400, { error: 'Requête invalide.' });\n }\n const id = parseLauncherRef(body);\n // Seul un raccourci relu sur le disque peut être visé : un nom reçu\n // tel quel deviendrait un chemin à supprimer.\n const launcher = id === null ? undefined : (await opts.list()).find((l) => l.id === id);\n if (!launcher) return send(res, 404, { error: 'Raccourci introuvable.' });\n\n installing = true;\n try {\n const result = url.pathname === '/api/uninstall'\n ? await opts.uninstall(launcher)\n : await opts.install({\n name: launcher.name,\n workingDir: launcher.workingDir,\n port: launcher.port,\n agentPort: launcher.agentPort,\n });\n return send(res, 200, { paths: result.paths, warnings: result.warnings });\n } catch (err) {\n return send(res, 500, { error: (err as Error).message });\n } finally {\n installing = false;\n }\n }\n\n if (url.pathname === '/api/install') {\n if (installing || settled) return send(res, 409, { error: 'Installation déjà en cours.' });\n let body: unknown;\n try {\n body = await readJson(req);\n } catch {\n return send(res, 400, { error: 'Requête invalide.' });\n }\n const parsed = parseInstallRequest(body);\n if (typeof parsed === 'string') return send(res, 400, { error: parsed });\n // Toujours global d'ici : un raccourci vers un projet se crée depuis l'app.\n const answers: InstallerAnswers = { ...parsed, workingDir: null };\n\n installing = true;\n try {\n const result = await opts.install(answers);\n send(res, 200, {\n name: answers.name,\n paths: result.paths,\n warnings: result.warnings,\n whereToFind: whereToFind(),\n uninstall: `runeya uninstall-desktop --name ${shellArg(answers.name)}`,\n });\n return finish({ answers, result });\n } catch (err) {\n // L'échec reste sur la page : corriger un champ et réessayer.\n return send(res, 500, { error: (err as Error).message });\n } finally {\n installing = false;\n }\n }\n }\n\n send(res, 404, { error: 'Introuvable.' });\n }\n\n process.on('SIGINT', onSigint);\n server.listen(0, '127.0.0.1', () => {\n const { port } = server.address() as AddressInfo;\n const url = `http://127.0.0.1:${port}/`;\n console.log(`\\nInstallateur du raccourci Runeya : ${url}`);\n console.log('Choisissez les réglages dans la page. Ctrl+C pour annuler.');\n opts.openUrl(url).catch(() => {\n console.log('Impossible d’ouvrir le navigateur — ouvrez l’adresse ci-dessus.');\n });\n });\n });\n}\n","/**\n * La page de l'installateur de raccourci, autonome : ni police, ni script, ni\n * feuille externes — le serveur qui la sert refuse tout le reste (CSP), et elle\n * doit s'afficher hors ligne.\n *\n * Deux langues, choisies d'après le navigateur ; le français par défaut, comme\n * les messages du CLI.\n */\nexport function installerHtml(): string {\n return `<!doctype html>\n<html lang=\"fr\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Installer Runeya</title>\n<link rel=\"icon\" href=\"data:image/svg+xml,${encodeURIComponent(MARK)}\">\n<style>\n:root {\n --bg: #f6f6f9; --surface: #ffffff; --border: #e3e3ea; --text: #1d1d27;\n --muted: #6b6b7b; --primary: #6366f1; --primary-text: #ffffff;\n --danger: #dc2626; --ok: #16a34a; --warn: #b45309;\n --ease: cubic-bezier(0.23, 1, 0.32, 1);\n color-scheme: light;\n}\n@media (prefers-color-scheme: dark) {\n :root {\n --bg: #111118; --surface: #1a1a24; --border: #2c2c3a; --text: #ececf2;\n --muted: #9a9aab; --primary: #818cf8; --primary-text: #111118;\n --danger: #f87171; --ok: #4ade80; --warn: #fbbf24;\n color-scheme: dark;\n }\n}\n* { box-sizing: border-box; }\nbody {\n margin: 0; min-height: 100vh; display: flex; align-items: center; justify-content: center;\n padding: 3rem 1rem; background: var(--bg); color: var(--text);\n font: 15px/1.5 system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n}\nmain { width: 100%; max-width: 1600px; display: flex; flex-direction: column; gap: 1.5rem;\n animation: enter 400ms var(--ease) both; }\nheader { display: flex; flex-direction: column; align-items: center; text-align: center; gap: .5rem; }\nheader svg { width: 48px; height: 48px; }\nh1 { margin: .25rem 0 0; font-size: 1.375rem; font-weight: 650; letter-spacing: -.01em; }\n.lead { margin: 0; color: var(--muted); font-size: .875rem; }\n.card { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 1.25rem;\n display: flex; flex-direction: column; gap: 1.25rem; box-shadow: 0 1px 2px rgb(0 0 0 / .04); }\nfieldset { border: 0; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .5rem; }\nlegend, label.title { font-size: .75rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;\n color: var(--muted); margin-bottom: .5rem; padding: 0; }\n.choice { display: flex; gap: .75rem; align-items: flex-start; padding: .75rem .875rem; border: 1px solid var(--border);\n border-radius: 10px; cursor: pointer; transition: border-color 150ms ease, box-shadow 150ms ease; }\n.choice:has(input:checked) { border-color: var(--primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 15%, transparent); }\n.choice input { margin: .2rem 0 0; accent-color: var(--primary); }\n.choice strong { display: block; font-weight: 600; font-size: .9375rem; }\n.choice span { display: block; color: var(--muted); font-size: .8125rem; overflow-wrap: anywhere; }\n.choice .warn { color: var(--danger); }\ninput[type=text], input[type=number] { width: 100%; padding: .625rem .75rem; border: 1px solid var(--border);\n border-radius: 8px; background: var(--surface); color: var(--text); font: inherit; }\ninput:focus-visible, button:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }\n.ports { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem; }\n.ports label { font-size: .8125rem; color: var(--muted); display: flex; flex-direction: column; gap: .25rem; }\n[hidden] { display: none !important; }\n.actions { display: flex; gap: .75rem; }\nbutton { flex: 1; padding: .7rem 1rem; border-radius: 10px; border: 1px solid transparent; font: inherit;\n font-weight: 600; cursor: pointer; transition: transform 160ms var(--ease), opacity 150ms ease; }\nbutton:active { transform: scale(.97); }\nbutton:disabled { opacity: .6; cursor: default; }\n.primary { background: var(--primary); color: var(--primary-text); }\n.secondary { flex: 0 0 auto; background: transparent; color: var(--muted); border-color: var(--border); }\n.error { margin: 0; color: var(--danger); font-size: .875rem; }\n.done { text-align: center; align-items: center; width: 100%; max-width: 520px; align-self: center; }\n.done .check { width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center;\n background: color-mix(in srgb, var(--ok) 15%, var(--surface)); color: var(--ok); font-size: 1.4rem; }\n.done h2 { margin: 0; font-size: 1.125rem; }\n.done dl { width: 100%; margin: 0; text-align: left; display: flex; flex-direction: column; gap: .75rem; }\n.done dt { font-size: .75rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }\n.done dd { margin: .25rem 0 0; font-size: .875rem; overflow-wrap: anywhere; }\ncode { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .8125rem;\n background: color-mix(in srgb, var(--text) 7%, var(--surface)); padding: .15rem .4rem; border-radius: 6px; }\n.close { color: var(--muted); font-size: .8125rem; margin: 0; }\n.hint { margin: .375rem 0 0; color: var(--muted); font-size: .8125rem; }\n.split { display: grid; grid-template-columns: minmax(0, 520px); justify-content: center; gap: 1.5rem; align-items: start; }\n/*\n * Des raccourcis à gérer : la création reste au centre, à sa largeur ; les\n * existants prennent la colonne de droite, la gauche restant vide pour que le\n * centre ne bouge pas. En dessous de 1200 px, ils passent sous la création.\n */\n.split.has-existing { grid-template-columns: minmax(0, 1fr) minmax(0, 520px) minmax(0, 1fr); }\n.split.has-existing #form { grid-column: 2; grid-row: 1; }\n.split.has-existing .existing { grid-column: 3; grid-row: 1; }\n@media (max-width: 1200px) {\n .split.has-existing { grid-template-columns: minmax(0, 520px); }\n .split.has-existing #form, .split.has-existing .existing { grid-column: 1; grid-row: auto; }\n}\n.existing { gap: .75rem; order: 2; }\n.existing h2 { margin: 0; font-size: .75rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }\n.launcher-list { display: flex; flex-direction: column; gap: .5rem; }\n.launcher { display: flex; gap: .75rem; align-items: center; padding: .75rem .875rem; border: 1px solid var(--border); border-radius: 10px; }\n.launcher__body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .125rem; }\n.launcher__body strong { font-weight: 600; font-size: .9375rem; }\n.launcher__body span { color: var(--muted); font-size: .8125rem; overflow-wrap: anywhere; }\n.launcher__body .problem { color: var(--danger); }\n.launcher__body .stale { color: var(--warn); }\n.launcher__actions { display: flex; gap: .375rem; flex-shrink: 0; }\n.launcher__actions button { flex: 0 0 auto; padding: .4rem .7rem; font-size: .8125rem; background: transparent;\n color: var(--text); border-color: var(--border); }\n.launcher__actions .danger { color: var(--danger); }\n.launcher__actions .confirm { background: var(--danger); border-color: var(--danger); color: var(--primary-text); }\n@media (max-width: 480px) { .launcher { flex-direction: column; align-items: stretch; } }\n@keyframes enter { from { opacity: 0; transform: translateY(8px); } }\n@media (prefers-reduced-motion: reduce) { main { animation: none; } button:active { transform: none; } }\n@media (max-width: 480px) { .ports { grid-template-columns: 1fr; } }\n</style>\n</head>\n<body>\n<main>\n <header>\n ${MARK}\n <h1 data-t=\"title\"></h1>\n <p class=\"lead\" data-t=\"lead\"></p>\n </header>\n\n <div class=\"split\" id=\"split\">\n <section class=\"card existing\" id=\"existing\" hidden>\n <h2 data-t=\"existingTitle\"></h2>\n <div id=\"launchers\" class=\"launcher-list\"></div>\n <p class=\"error\" id=\"list-error\" role=\"alert\" hidden></p>\n </section>\n\n <form id=\"form\" class=\"card\" novalidate>\n\n <div>\n <label class=\"title\" for=\"name\" data-t=\"name\"></label>\n <input id=\"name\" type=\"text\" maxlength=\"120\" autocomplete=\"off\" required>\n <p class=\"hint\" data-t=\"globalHint\"></p>\n </div>\n\n <fieldset>\n <legend data-t=\"ports\"></legend>\n <label class=\"choice\" id=\"ports-dedicated-row\"><input type=\"radio\" name=\"ports\" value=\"dedicated\">\n <div><strong data-t=\"portsDedicated\"></strong><span id=\"ports-dedicated\"></span></div></label>\n <label class=\"choice\"><input type=\"radio\" name=\"ports\" value=\"default\">\n <div><strong data-t=\"portsDefault\"></strong><span id=\"ports-default\"></span></div></label>\n <label class=\"choice\"><input type=\"radio\" name=\"ports\" value=\"custom\">\n <div><strong data-t=\"portsCustom\"></strong><span data-t=\"portsCustomHint\"></span></div></label>\n <div class=\"ports\" id=\"custom\" hidden>\n <label><span data-t=\"portRuneya\"></span><input id=\"port\" type=\"number\" min=\"1\" max=\"65535\"></label>\n <label><span data-t=\"portAgent\"></span><input id=\"agent-port\" type=\"number\" min=\"1\" max=\"65535\"></label>\n </div>\n </fieldset>\n\n <p class=\"error\" id=\"error\" role=\"alert\" hidden></p>\n\n <div class=\"actions\">\n <button type=\"button\" class=\"secondary\" id=\"cancel\" data-t=\"cancel\"></button>\n <button type=\"submit\" class=\"primary\" id=\"submit\" data-t=\"install\"></button>\n </div>\n </form>\n </div>\n\n <section class=\"card done\" id=\"done\" hidden aria-live=\"polite\">\n <div class=\"check\" aria-hidden=\"true\">✓</div>\n <h2 id=\"done-title\"></h2>\n <dl>\n <div><dt data-t=\"where\"></dt><dd id=\"where\"></dd></div>\n <div><dt data-t=\"files\"></dt><dd id=\"files\"></dd></div>\n <div id=\"warnings-row\" hidden><dt data-t=\"warnings\"></dt><dd id=\"warnings\"></dd></div>\n <div><dt data-t=\"remove\"></dt><dd><code id=\"uninstall\"></code></dd></div>\n </dl>\n <p class=\"close\" data-t=\"closeTab\"></p>\n </section>\n\n <section class=\"card done\" id=\"cancelled\" hidden>\n <h2 data-t=\"cancelledTitle\"></h2>\n <p class=\"close\" data-t=\"closeTab\"></p>\n </section>\n</main>\n<script>\nconst T = {\n fr: {\n title: 'Installer Runeya',\n lead: 'Un lanceur dans le menu de votre bureau, pour démarrer Runeya sans terminal.',\n globalHint: 'Le raccourci démarre Runeya dans votre dossier personnel et range les données dans ~/.runeya. Pour un raccourci vers un projet, passez par les réglages de Runeya.',\n name: 'Nom du raccourci', ports: 'Ports',\n portsDedicated: 'Ports dédiés (recommandé)', portsDefault: 'Ports par défaut de Runeya',\n portsCustom: 'Choisir moi-même', portsCustomHint: 'Saisir le port de Runeya, puis celui de l’agent',\n portRuneya: 'Runeya', portAgent: 'Agent',\n dedicatedHint: (p) => p + ' et ' + (p + 1) + ', choisis parmi les ports libres',\n defaultFree: (p) => p + ' et ' + (p + 1) + ', libres en ce moment',\n defaultTaken: (p) => p + ' ou ' + (p + 1) + ' est occupé : le raccourci ne démarrera pas tant qu’il le reste',\n cancel: 'Fermer', install: 'Créer le raccourci', installing: 'Création…',\n existingTitle: 'Raccourcis existants', global: 'Global (dossier personnel)',\n portsOf: (p, a) => 'Ports ' + p + ' · ' + a, defaultPorts: 'Ports par défaut',\n outdated: 'Vise un autre Node ou une autre installation de Runeya',\n recreate: 'Recréer', removeAction: 'Supprimer', confirmRemove: 'Confirmer',\n working: '…',\n created: (n) => 'Raccourci « ' + n + ' » créé',\n where: 'Où le trouver', files: 'Fichiers écrits', warnings: 'Avertissements', remove: 'Pour le supprimer',\n closeTab: 'Vous pouvez fermer cet onglet.',\n cancelledTitle: 'Installateur fermé',\n nameRequired: 'Donnez un nom au raccourci.',\n portsInvalid: 'Deux ports différents entre 1 et 65535.',\n unreachable: 'L’installateur ne répond plus. Relancez « runeya install-desktop ».',\n },\n en: {\n title: 'Install Runeya',\n lead: 'A launcher in your desktop menu, to start Runeya without a terminal.',\n globalHint: 'The shortcut starts Runeya in your home folder and stores data in ~/.runeya. For a shortcut to a project, use the Runeya settings.',\n name: 'Shortcut name', ports: 'Ports',\n portsDedicated: 'Dedicated ports (recommended)', portsDefault: 'Runeya default ports',\n portsCustom: 'Choose myself', portsCustomHint: 'Enter the Runeya port, then the agent’s',\n portRuneya: 'Runeya', portAgent: 'Agent',\n dedicatedHint: (p) => p + ' and ' + (p + 1) + ', chosen among free ports',\n defaultFree: (p) => p + ' and ' + (p + 1) + ', currently free',\n defaultTaken: (p) => p + ' or ' + (p + 1) + ' is in use: the shortcut will not start until it is free',\n cancel: 'Close', install: 'Create shortcut', installing: 'Creating…',\n existingTitle: 'Existing shortcuts', global: 'Global (home folder)',\n portsOf: (p, a) => 'Ports ' + p + ' · ' + a, defaultPorts: 'Default ports',\n outdated: 'Points to another Node or another Runeya install',\n recreate: 'Recreate', removeAction: 'Remove', confirmRemove: 'Confirm',\n working: '…',\n created: (n) => 'Shortcut “' + n + '” created',\n where: 'Where to find it', files: 'Files written', warnings: 'Warnings', remove: 'To remove it',\n closeTab: 'You can close this tab.',\n cancelledTitle: 'Installer closed',\n nameRequired: 'Give the shortcut a name.',\n portsInvalid: 'Two different ports between 1 and 65535.',\n unreachable: 'The installer no longer answers. Run “runeya install-desktop” again.',\n },\n};\nconst lang = (navigator.language || 'fr').toLowerCase().startsWith('fr') ? 'fr' : 'en';\nconst t = T[lang];\ndocument.documentElement.lang = lang;\ndocument.title = t.title;\ndocument.querySelectorAll('[data-t]').forEach((el) => { el.textContent = t[el.dataset.t]; });\n\nconst $ = (id) => document.getElementById(id);\nconst form = $('form');\nlet state = null;\n\nasync function post(path, body) {\n const res = await fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });\n const data = await res.json().catch(() => ({}));\n if (!res.ok) throw new Error(data.error || res.statusText);\n return data;\n}\n\nfunction showError(message) {\n $('error').textContent = message;\n $('error').hidden = !message;\n}\n\nasync function load() {\n state = await (await fetch('/api/state')).json();\n if (state.suggestedPort === null) {\n $('ports-dedicated-row').hidden = true;\n form.elements.ports.value = 'default';\n } else {\n $('ports-dedicated').textContent = t.dedicatedHint(state.suggestedPort);\n form.elements.ports.value = 'dedicated';\n }\n $('ports-default').textContent = state.defaultsFree ? t.defaultFree(state.defaultPort) : t.defaultTaken(state.defaultPort);\n if (!state.defaultsFree) $('ports-default').classList.add('warn');\n $('port').value = state.suggestedPort ?? state.defaultPort;\n $('agent-port').value = (state.suggestedPort ?? state.defaultPort) + 1;\n $('name').value = state.name;\n $('name').focus();\n}\n\nform.addEventListener('change', (e) => {\n if (e.target.name === 'ports') $('custom').hidden = portsChoice() !== 'custom';\n});\n\nform.addEventListener('submit', async (e) => {\n e.preventDefault();\n showError('');\n const name = $('name').value.trim();\n if (!name) { showError(t.nameRequired); $('name').focus(); return; }\n\n let port = null, agentPort = null;\n if (portsChoice() === 'dedicated') { port = state.suggestedPort; agentPort = port + 1; }\n if (portsChoice() === 'custom') {\n port = Number($('port').value); agentPort = Number($('agent-port').value);\n const ok = [port, agentPort].every((p) => Number.isInteger(p) && p >= 1 && p <= 65535) && port !== agentPort;\n if (!ok) { showError(t.portsInvalid); return; }\n }\n\n const submit = $('submit');\n submit.disabled = true; $('cancel').disabled = true;\n submit.textContent = t.installing;\n try {\n const r = await post('/api/install', { name, workingDir: null, port, agentPort });\n $('done-title').textContent = t.created(r.name);\n $('where').textContent = r.whereToFind;\n $('files').replaceChildren();\n for (const p of r.paths) { const d = document.createElement('div'); d.textContent = p; $('files').append(d); }\n if (r.warnings.length) {\n $('warnings-row').hidden = false;\n $('warnings').textContent = r.warnings.join(' · ');\n }\n $('uninstall').textContent = r.uninstall;\n $('split').hidden = true; $('done').hidden = false;\n } catch (err) {\n showError(err instanceof TypeError ? t.unreachable : err.message);\n submit.disabled = false; $('cancel').disabled = false;\n submit.textContent = t.install;\n }\n});\n\n$('cancel').addEventListener('click', async () => {\n try { await post('/api/cancel', {}); } catch { /* déjà fermé */ }\n $('split').hidden = true; $('cancelled').hidden = false;\n});\n\nfunction el(tag, className, text) {\n const node = document.createElement(tag);\n if (className) node.className = className;\n if (text !== undefined) node.textContent = text;\n return node;\n}\n\nfunction listError(message) {\n $('list-error').textContent = message;\n $('list-error').hidden = !message;\n}\n\n/** Une action sur un raccourci existant ; la liste est relue ensuite. */\nasync function act(path, launcher, button) {\n listError('');\n const label = button.textContent;\n button.disabled = true; button.textContent = t.working;\n try {\n await post(path, { id: launcher.id });\n await loadLaunchers();\n } catch (err) {\n listError(err instanceof TypeError ? t.unreachable : err.message);\n button.disabled = false; button.textContent = label;\n }\n}\n\nfunction renderLauncher(l) {\n const row = el('div', 'launcher');\n const body = el('div', 'launcher__body');\n body.append(el('strong', '', l.name));\n body.append(el('span', '', l.workingDir ?? t.global));\n body.append(el('span', '', l.port === null ? t.defaultPorts : t.portsOf(l.port, l.agentPort)));\n for (const p of l.problems) body.append(el('span', 'problem', p));\n if (l.outdated && !l.problems.length) body.append(el('span', 'stale', t.outdated));\n row.append(body);\n\n const actions = el('div', 'launcher__actions');\n // Recréer n'a de sens que si le raccourci ne vise plus ce Runeya-ci.\n if (l.outdated || l.problems.length) {\n const recreate = el('button', '', t.recreate);\n recreate.type = 'button';\n recreate.addEventListener('click', () => act('/api/reinstall', l, recreate));\n actions.append(recreate);\n }\n // Deux temps, sans boîte de dialogue : le premier clic arme, le second supprime.\n const remove = el('button', 'danger', t.removeAction);\n remove.type = 'button';\n let armed = false;\n remove.addEventListener('click', () => {\n if (!armed) {\n armed = true; remove.textContent = t.confirmRemove; remove.className = 'confirm';\n setTimeout(() => { if (armed && !remove.disabled) { armed = false; remove.textContent = t.removeAction; remove.className = 'danger'; } }, 3000);\n return;\n }\n act('/api/uninstall', l, remove);\n });\n actions.append(remove);\n row.append(actions);\n return row;\n}\n\nasync function loadLaunchers() {\n const list = await (await fetch('/api/launchers')).json();\n $('launchers').replaceChildren(...list.map(renderLauncher));\n $('existing').hidden = list.length === 0;\n $('split').classList.toggle('has-existing', list.length > 0);\n}\n\nload().catch(() => showError(t.unreachable));\nloadLaunchers().catch(() => listError(t.unreachable));\n</script>\n</body>\n</html>`;\n}\n\n/** Le logo, tel que dans `assets/icon.svg`. */\nconst MARK = '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 28 28\" aria-hidden=\"true\"><rect width=\"28\" height=\"28\" rx=\"8\" fill=\"#6366f1\"/><path d=\"M7 20 L14 8 L21 20\" stroke=\"white\" stroke-width=\"2.5\" fill=\"none\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M10 16 L18 16\" stroke=\"white\" stroke-width=\"2.5\" stroke-linecap=\"round\"/></svg>';\n"],"mappings":"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","names":[]}
|