@runeya/runeya 2.0.144 → 2.0.146
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 +33 -22
- package/agent/index.js.map +1 -1
- package/agent-manager-R7BPQO6D.js +11 -0
- package/chat-LPML22J5.js +24 -0
- package/{chunk-7L6AQUBR.js → chunk-4LJQIQMN.js} +6 -2
- package/{chunk-7L6AQUBR.js.map → chunk-4LJQIQMN.js.map} +1 -1
- package/{chunk-7HIPFSOA.js → chunk-57P45FNY.js} +11 -3
- package/chunk-57P45FNY.js.map +1 -0
- package/chunk-6IBPK6UE.js +1063 -0
- package/chunk-6IBPK6UE.js.map +1 -0
- package/{chunk-GXE5TLLJ.js → chunk-KV4GJKXU.js} +17 -17
- package/{chunk-XAFOP677.js → chunk-MZSDTA4A.js} +480 -233
- package/chunk-MZSDTA4A.js.map +1 -0
- package/{chunk-UR3HL373.js → chunk-NPMSBYXC.js} +8 -8
- package/{chunk-P73PTVII.js → chunk-SEPAURTQ.js} +369 -1282
- package/chunk-SEPAURTQ.js.map +1 -0
- package/{chunk-NSS47H26.js → chunk-UD42LACH.js} +348 -78
- package/chunk-UD42LACH.js.map +1 -0
- package/{chunk-IXO5KIO5.js → chunk-VPSQJOSN.js} +23 -2
- package/{chunk-IXO5KIO5.js.map → chunk-VPSQJOSN.js.map} +1 -1
- package/{companion-2LHKU577.js → companion-W3UMZWGH.js} +11 -7
- package/{desktop-installer-page-6JDSXK2E.js → desktop-installer-page-I5FHFDES.js} +11 -8
- package/desktop-installer-page-I5FHFDES.js.map +1 -0
- package/{desktop-launcher-YPNRQMIS.js → desktop-launcher-GD4QPM66.js} +3 -3
- package/external-NMKQPROK.js +387 -0
- package/external-NMKQPROK.js.map +1 -0
- package/index.js +34 -12
- package/index.js.map +1 -1
- package/{install-gate-2NNH7RM2.js → install-gate-PVMGJDWE.js} +2 -2
- 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 +142 -40
- package/mcp-server/index.js.map +1 -1
- package/mcp-server/launch.d.ts +19 -1
- package/mcp-server/launch.js +20 -0
- package/mcp-server/launch.js.map +1 -1
- package/package.json +1 -1
- package/settings-manager-XPLBO35P.js +10 -0
- package/src-DCCEUWE6.js +385 -0
- package/{src-5TEXF3SU.js → src-LEDGW5KP.js} +1029 -536
- package/src-LEDGW5KP.js.map +1 -0
- package/{vault-resolver-VK6W5J6W.js → vault-resolver-E7KPKA5C.js} +3 -2
- package/vault-resolver-E7KPKA5C.js.map +1 -0
- package/web/assets/{AgentScalarChatInterface.vue-B-SjOsdj.js → AgentScalarChatInterface.vue-BYszptaB.js} +1 -1
- package/web/assets/{AgentTerminalPage-JzC8DUgu.js → AgentTerminalPage-C3TJ1NL9.js} +1 -1
- package/web/assets/{AiChatModals-D1928AxG.js → AiChatModals-D3scRE3S.js} +1 -1
- package/web/assets/AppLayout-BMQUjPKe.js +1 -0
- package/web/assets/{AppLayout-8sBPr3od.css → AppLayout-tsw5Ow0w.css} +1 -1
- package/web/assets/{AuditPage-BZIJ3_Kr.js → AuditPage-CKcExBws.js} +1 -1
- package/web/assets/{CloudAccountPage-DBv470sT.js → CloudAccountPage-B6oPg0BZ.js} +1 -1
- package/web/assets/{CloudMessagingPage-qCI8-UDL.js → CloudMessagingPage-C5fwVe-F.js} +1 -1
- package/web/assets/DashboardPage-9RV7sx3e.js +1 -0
- package/web/assets/{DashboardPage-D9-PpC3F.css → DashboardPage-FvNuC_e4.css} +1 -1
- package/web/assets/{DirectoryPickerDialog-B5DvDfQP.js → DirectoryPickerDialog-C06BXyfJ.js} +1 -1
- package/web/assets/{EnvironmentForm-Bw8Djnqm.js → EnvironmentForm-CAvDoira.js} +1 -1
- package/web/assets/{EnvironmentFormPage-B59uiYtp.js → EnvironmentFormPage-BmAxhN-m.js} +1 -1
- package/web/assets/{EnvironmentListPage-W9zLDZAg.js → EnvironmentListPage-B8uATPBI.js} +1 -1
- package/web/assets/KanbanBoardPage-CQsOkRUf.js +4 -0
- package/web/assets/KanbanBoardPage-eHmsrFGA.css +1 -0
- package/web/assets/{KanbanListPage-Bn9k8Rpc.js → KanbanListPage-CWZqkjD6.js} +1 -1
- package/web/assets/{LaunchProjectPage-Dc7v1UjQ.js → LaunchProjectPage-saSmC7Pr.js} +1 -1
- package/web/assets/LaunchServicesPage-BkKyLMEz.js +1 -0
- package/web/assets/LaunchServicesPage-DoLDPqQq.css +1 -0
- package/web/assets/{LaunchShell-LonTJpb3.js → LaunchShell-DUrSunB4.js} +1 -1
- package/web/assets/{LogViewer-DkuqWYkQ.js → LogViewer-CZBYkH9J.js} +1 -1
- package/web/assets/{OrgKeyPrompt-By1VjlSN.js → OrgKeyPrompt-DzQ7pwbs.js} +1 -1
- package/web/assets/{ProcessControls-C0skzKGL.js → ProcessControls-BEeoNlNV.js} +1 -1
- package/web/assets/{ProcessDetailPage-BvPX6z_X.js → ProcessDetailPage-CQgu5uKm.js} +1 -1
- package/web/assets/{ProjectCreateDialog-C4o9u15L.js → ProjectCreateDialog-BolrhRD-.js} +1 -1
- package/web/assets/{ProjectLocationGate-BJ1CSQt0.js → ProjectLocationGate-DhMVdIjN.js} +1 -1
- package/web/assets/{ProjectPublishDialog-Cp-zXdGm.js → ProjectPublishDialog-DsZNWrfz.js} +1 -1
- package/web/assets/{RandomJobTestPage-DF4a1U13.js → RandomJobTestPage-BUS2Ty6-.js} +1 -1
- package/web/assets/ScenarioEditorPage-MIAGh2us.js +14 -0
- package/web/assets/{SendToChatDialog-Dg2SHkVD.js → SendToChatDialog-BxWsyv29.js} +1 -1
- package/web/assets/{ServiceControlsBar-q2VHWaxK.js → ServiceControlsBar-DqDPAZt7.js} +1 -1
- package/web/assets/ServiceDetailPage-hkSAzZ-l.js +2 -0
- package/web/assets/{ServiceFormPage-tT_n7WUy.js → ServiceFormPage-DXqhqXg8.js} +1 -1
- package/web/assets/SettingsPage-4pKUgKPm.css +1 -0
- package/web/assets/SettingsPage-DS2YhMIM.js +2086 -0
- package/web/assets/{UnlockPage-BKAdMJkb.js → UnlockPage-s8SgVcKk.js} +1 -1
- package/web/assets/{VaultMigrationPage-D9KUTTss.js → VaultMigrationPage-s0b44MDM.js} +1 -1
- package/web/assets/{VersionBadge-moKP5bAv.js → VersionBadge-BmArk24p.js} +1 -1
- package/web/assets/{WikiPage-hLUndDxo.js → WikiPage-DADu8WUA.js} +1 -1
- package/web/assets/{WikiView-rrjXRR72.js → WikiView-DtGwIt_Q.js} +1 -1
- package/web/assets/{add-scalar-classes-CBfRr8B2.js → add-scalar-classes-dWEWPlFD.js} +2 -2
- package/web/assets/{agent.store-B3dVemmT.js → agent.store-CKLAkF9a.js} +1 -1
- package/web/assets/app-mode-BVpTuh0i.js +1 -0
- package/web/assets/auth-Cg9BWTfK.js +2 -0
- package/web/assets/{cloud-chat.store-Wg1poFem.js → cloud-chat.store-b-BHGz7I.js} +1 -1
- package/web/assets/{cloud-client-DQlUaoAw.js → cloud-client-D_zCiW0s.js} +2 -2
- package/web/assets/{cloud-session-DRUqcSGP.js → cloud-session-DOaDpZTy.js} +2 -2
- package/web/assets/dist-N4nrnbnA.js +1 -0
- package/web/assets/{editor.store-SoH48ux2.js → editor.store-DCAZSeVG.js} +1 -1
- package/web/assets/i18n-D0afQEGs.js +1 -0
- package/web/assets/{index-D_x2s8Ut.js → index-D8VRbyoP.js} +101 -101
- package/web/assets/index-DsezNbSM.css +1 -0
- package/web/assets/{modal-CpsVK_dA.js → modal-qYgTMkYb.js} +1 -1
- package/web/assets/{project-launch.store-C3fZeRGb.js → project-launch.store-y7JBMNiE.js} +1 -1
- package/web/assets/{project.store-BEk4fciH.js → project.store-DGJr0Qr9.js} +1 -1
- package/web/assets/{scenario.store-D-JrRiVO.js → scenario.store-YWHL2_wu.js} +3 -3
- package/web/assets/{service.store-B5uYZtyM.js → service.store-5jWsJxPx.js} +1 -1
- package/web/assets/{settings-scope-Blnwj3rR.js → settings-scope-Bv7QGPrt.js} +1 -1
- package/web/assets/{settings.store-Bo3FYs1o.js → settings.store-bEsB7oKb.js} +1 -1
- package/web/assets/{src-CAwAaB5j.js → src-C-D_y7ou.js} +1 -1
- package/web/assets/{src-DValZNJU.js → src-Dnwk6NpV.js} +5 -5
- package/web/assets/src-g1DRo8xZ.css +1 -0
- package/web/assets/{trpc-DbsE5GvQ.js → trpc-Drabzwpd.js} +3 -3
- package/web/assets/{useCloudOrg-DygUBG1a.js → useCloudOrg-BAQEtFPl.js} +1 -1
- package/web/assets/{useEnvVarAutocomplete-DmYNOXgd.js → useEnvVarAutocomplete-BqGTODUv.js} +1 -1
- package/web/assets/{useProjectCatalog-D2mHB7dg.js → useProjectCatalog-yocjIKt6.js} +1 -1
- package/web/assets/{useProjectCloudOrg-avTCVold.js → useProjectCloudOrg-CEN9-_OA.js} +1 -1
- package/web/assets/{useServiceUrlHost-Q3fzGox3.js → useServiceUrlHost-BuoPEXZX.js} +1 -1
- package/web/assets/{vue-draggable-plus-WgrMXXmE.js → vue-draggable-plus-DX4rTBiu.js} +1 -1
- package/web/index.html +19 -18
- package/agent-manager-M4NA5YFD.js +0 -11
- package/chat-X3X527EX.js +0 -23
- package/chunk-7HIPFSOA.js.map +0 -1
- package/chunk-NSS47H26.js.map +0 -1
- package/chunk-P73PTVII.js.map +0 -1
- package/chunk-XAFOP677.js.map +0 -1
- package/desktop-installer-page-6JDSXK2E.js.map +0 -1
- package/settings-manager-32SVMAFN.js +0 -10
- package/src-5TEXF3SU.js.map +0 -1
- package/web/assets/AppLayout-CVQIBYqg.js +0 -1
- package/web/assets/DashboardPage-D0UZM0mr.js +0 -1
- package/web/assets/KanbanBoardPage-DRACMk_D.css +0 -1
- package/web/assets/KanbanBoardPage-DwPqoJDJ.js +0 -4
- package/web/assets/LaunchServicesPage-CXUS9O3y.js +0 -1
- package/web/assets/LaunchServicesPage-DJDIN8HU.css +0 -1
- package/web/assets/ScenarioEditorPage-Dl-gEkJI.js +0 -14
- package/web/assets/ServiceDetailPage-CJ8WaFod.js +0 -2
- package/web/assets/SettingsPage-CWakkv0V.js +0 -2086
- package/web/assets/SettingsPage-aGSxZw1n.css +0 -1
- package/web/assets/auth-BCrvnP1U.js +0 -2
- package/web/assets/dist-DoChtT9t.js +0 -1
- package/web/assets/i18n-BszFg0ko.js +0 -1
- package/web/assets/index-C_JV7fYy.css +0 -1
- package/web/assets/src-DuBXN1hu.css +0 -1
- /package/{agent-manager-M4NA5YFD.js.map → agent-manager-R7BPQO6D.js.map} +0 -0
- /package/{chat-X3X527EX.js.map → chat-LPML22J5.js.map} +0 -0
- /package/{chunk-GXE5TLLJ.js.map → chunk-KV4GJKXU.js.map} +0 -0
- /package/{chunk-UR3HL373.js.map → chunk-NPMSBYXC.js.map} +0 -0
- /package/{companion-2LHKU577.js.map → companion-W3UMZWGH.js.map} +0 -0
- /package/{desktop-launcher-YPNRQMIS.js.map → desktop-launcher-GD4QPM66.js.map} +0 -0
- /package/{install-gate-2NNH7RM2.js.map → install-gate-PVMGJDWE.js.map} +0 -0
- /package/{settings-manager-32SVMAFN.js.map → settings-manager-XPLBO35P.js.map} +0 -0
- /package/{vault-resolver-VK6W5J6W.js.map → src-DCCEUWE6.js.map} +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
agentManager
|
|
3
|
+
} from "./chunk-KV4GJKXU.js";
|
|
4
|
+
import "./chunk-OZBSOSAS.js";
|
|
5
|
+
import "./chunk-4LJQIQMN.js";
|
|
6
|
+
import "./chunk-VPSQJOSN.js";
|
|
7
|
+
import "./chunk-MZSDTA4A.js";
|
|
8
|
+
export {
|
|
9
|
+
agentManager
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=agent-manager-R7BPQO6D.js.map
|
package/chat-LPML22J5.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chatRouter,
|
|
3
|
+
cliToken,
|
|
4
|
+
startClaudeCodeSession,
|
|
5
|
+
startCliSession,
|
|
6
|
+
startCodexSession
|
|
7
|
+
} from "./chunk-SEPAURTQ.js";
|
|
8
|
+
import "./chunk-6IBPK6UE.js";
|
|
9
|
+
import "./chunk-NPMSBYXC.js";
|
|
10
|
+
import "./chunk-UD42LACH.js";
|
|
11
|
+
import "./chunk-KV4GJKXU.js";
|
|
12
|
+
import "./chunk-OZBSOSAS.js";
|
|
13
|
+
import "./chunk-4LJQIQMN.js";
|
|
14
|
+
import "./chunk-MD3VW4HQ.js";
|
|
15
|
+
import "./chunk-VPSQJOSN.js";
|
|
16
|
+
import "./chunk-MZSDTA4A.js";
|
|
17
|
+
export {
|
|
18
|
+
chatRouter,
|
|
19
|
+
cliToken,
|
|
20
|
+
startClaudeCodeSession,
|
|
21
|
+
startCliSession,
|
|
22
|
+
startCodexSession
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=chat-LPML22J5.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
machineRoot
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-VPSQJOSN.js";
|
|
4
4
|
|
|
5
5
|
// ../server/src/services/disk-freshness.ts
|
|
6
6
|
import { stat } from "fs/promises";
|
|
@@ -26,6 +26,9 @@ async function hasChangedSinceRead(path) {
|
|
|
26
26
|
function wasRead(path) {
|
|
27
27
|
return seen.has(path);
|
|
28
28
|
}
|
|
29
|
+
function forgetFile(path) {
|
|
30
|
+
seen.delete(path);
|
|
31
|
+
}
|
|
29
32
|
async function fingerprintFiles(paths) {
|
|
30
33
|
const stamps = await Promise.all(paths.map(async (path) => {
|
|
31
34
|
try {
|
|
@@ -122,6 +125,7 @@ export {
|
|
|
122
125
|
rememberFile,
|
|
123
126
|
hasChangedSinceRead,
|
|
124
127
|
wasRead,
|
|
128
|
+
forgetFile,
|
|
125
129
|
fingerprintFiles,
|
|
126
130
|
resolveConversationRunDir,
|
|
127
131
|
renameWithRetry,
|
|
@@ -130,4 +134,4 @@ export {
|
|
|
130
134
|
atomicWriteJsonSecure,
|
|
131
135
|
readJsonFileSafe
|
|
132
136
|
};
|
|
133
|
-
//# sourceMappingURL=chunk-
|
|
137
|
+
//# sourceMappingURL=chunk-4LJQIQMN.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../server/src/services/disk-freshness.ts","../../server/src/services/run-store-utils.ts"],"sourcesContent":["import { stat } from 'node:fs/promises';\n\n/**\n * Le fichier a-t-il changé depuis qu'on l'a lu ?\n *\n * Les stores gardent de côté ce qu'ils n'ont pas su déchiffrer, pour le\n * réécrire tel quel plutôt que de l'effacer. Cette copie est la bonne réponse\n * tant que personne d'autre ne touche au fichier — et la mauvaise dès que\n * quelqu'un le répare par-dessous : la copie, elle, ne se périme jamais, et la\n * sauvegarde suivante réinstalle l'état d'avant la réparation.\n *\n * C'est arrivé deux fois de suite sur le même coffre : une conversion de clé\n * menée sur le disque, défaite quelques minutes plus tard par un store qui\n * tenait encore l'ancien chiffré en mémoire.\n *\n * D'où ce repère : l'empreinte du fichier au moment de la lecture. Si elle a\n * bougé, la copie mémoire ne fait plus foi et doit être abandonnée.\n */\n\ninterface Fingerprint {\n mtimeMs: number;\n size: number;\n /**\n * L'inode du chemin, qui change à chaque `rename` par-dessus.\n *\n * La date et la taille laissent passer le motif d'écriture de tout le dépôt :\n * les stores réécrivent le fichier entier, donc une modification rend souvent\n * le même nombre d'octets, et deux instances qui réagissent au même événement\n * écrivent dans la même milliseconde. Le fichier temporaire renommé\n * par-dessus, lui, installe toujours un autre inode.\n *\n * Là où le système n'en expose pas (il rend alors 0), le repère retombe sur\n * la date et la taille — ce qu'il valait avant.\n */\n ino: number;\n}\n\nconst seen = new Map<string, Fingerprint>();\n\n/** Retenir l'état d'un fichier au moment où on vient de le lire. */\nexport async function rememberFile(path: string): Promise<void> {\n try {\n const s = await stat(path);\n seen.set(path, { mtimeMs: s.mtimeMs, size: s.size, ino: s.ino });\n } catch {\n // Pas encore de fichier : rien à retenir, et rien à réémettre non plus.\n seen.delete(path);\n }\n}\n\n/**\n * Vrai si le fichier a changé — ou si l'on ne sait pas.\n *\n * L'ignorance compte comme un changement : réécrire une copie dont on ne peut\n * plus prouver qu'elle correspond au disque est précisément le geste à éviter.\n */\nexport async function hasChangedSinceRead(path: string): Promise<boolean> {\n const known = seen.get(path);\n if (!known) return true;\n try {\n const s = await stat(path);\n return s.mtimeMs !== known.mtimeMs || s.size !== known.size || s.ino !== known.ino;\n } catch {\n return true;\n }\n}\n\n/**\n * A-t-on lu ce fichier ?\n *\n * `hasChangedSinceRead` tient l'ignorance pour un changement : c'est juste\n * quand il s'agit de décider si une copie mémoire fait encore foi. Ça ne l'est\n * pas pour décider d'écrire — un fichier jamais lu est le cas d'un coffre neuf,\n * et le traiter comme « modifié sous nos pieds » revenait à ne jamais écrire la\n * première fois. Les appelants qui protègent une écriture demandent donc les\n * deux : connu, et inchangé.\n */\nexport function wasRead(path: string): boolean {\n return seen.has(path);\n}\n\n/** Oublier un fichier — après un rechargement complet, par exemple. */\nexport function forgetFile(path: string): void {\n seen.delete(path);\n}\n\n/**\n * L'empreinte d'un ensemble de fichiers : lesquels, et dans quel état.\n *\n * Le repère ci-dessus répond pour un fichier qu'on a lu soi-même ; celui-ci\n * répond pour un store dont le contenu vient de plusieurs fichiers à la fois —\n * un par coffre — et dont la question est « le disque a-t-il bougé depuis mon\n * dernier chargement ? ». Comparer deux empreintes coûte quelques `stat` ;\n * recharger coûte autant de lectures, d'analyses et de déchiffrements.\n *\n * L'absence d'un fichier en fait partie : un coffre disparu change l'empreinte\n * autant qu'un coffre modifié.\n */\nexport async function fingerprintFiles(paths: string[]): Promise<string> {\n const stamps = await Promise.all(paths.map(async (path) => {\n try {\n const s = await stat(path);\n // L'inode en fait partie pour la raison décrite sur `Fingerprint` : un\n // fichier renommé par-dessus la cible peut n'avoir bougé ni en taille ni\n // en date, et reste pourtant un autre contenu.\n return `${path}:${s.mtimeMs}:${s.size}:${s.ino}`;\n } catch {\n return `${path}:-`;\n }\n }));\n return stamps.sort().join('|');\n}\n","import { randomUUID } from 'node:crypto';\nimport { chmod, mkdir, readdir, rename, rm, stat, writeFile, readFile } from 'node:fs/promises';\nimport { dirname, join, resolve, sep } from 'node:path';\nimport { machineRoot } from './vault-resolver.js';\n\nconst SAFE_PATH_SEGMENT_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;\n\nexport function resolveConversationRunDir(rootDirName: string, conversationId: string): string {\n if (!SAFE_PATH_SEGMENT_RE.test(conversationId)) {\n throw new Error('Invalid conversationId');\n }\n // Machine-wide, like the conversations these runs belong to: using the launch\n // directory would put a `.runeya` in a repository just for asking the AI a\n // question about a cloud project.\n const base = resolve(machineRoot(), rootDirName);\n const dir = resolve(base, conversationId);\n if (!dir.startsWith(base + sep)) {\n throw new Error('Invalid conversationId');\n }\n return dir;\n}\n\n/** Codes que Windows renvoie tant qu'un autre descripteur tient la cible ouverte. */\nconst TRANSIENT_RENAME_CODES = new Set(['EPERM', 'EACCES', 'EBUSY']);\nconst RENAME_RETRY_BUDGET_MS = 2000;\n\n/**\n * Sous Windows, remplacer un fichier qu'un lecteur concurrent, un antivirus ou\n * l'indexeur tient ouvert échoue quelques millisecondes. Réessayer avec un recul\n * croissant, borné : au-delà, le refus est réel et remonte.\n */\nexport async function renameWithRetry(from: string, to: string): Promise<void> {\n const deadline = Date.now() + RENAME_RETRY_BUDGET_MS;\n for (let delay = 10; ; delay = Math.min(delay * 2, 200)) {\n try {\n await rename(from, to);\n return;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (!code || !TRANSIENT_RENAME_CODES.has(code) || Date.now() + delay > deadline) throw err;\n await new Promise((r) => setTimeout(r, delay));\n }\n }\n}\n\nexport async function atomicWriteSecure(filePath: string, content: string | Uint8Array): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n // Un nom par écriture : deux Runeya d'un même poste écrivent les mêmes\n // fichiers, et un `.tmp` commun se fait renommer sous le nez de l'autre.\n const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n try {\n await writeFile(tmpPath, content, typeof content === 'string' ? 'utf-8' : undefined);\n await renameWithRetry(tmpPath, filePath);\n } catch (err) {\n // Un nom unique ne se réécrit jamais : laissé là, chaque échec en ajouterait un.\n await rm(tmpPath, { force: true }).catch(() => {});\n throw err;\n }\n await chmod(filePath, 0o600);\n}\n\n/** Le nom qu'`atomicWriteSecure` donne à ses fichiers temporaires : `<fichier>.<pid>.<uuid>.tmp`. */\nconst OWN_TEMP_RE = /\\.\\d+\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\.tmp$/;\nconst STALE_TEMP_MS = 3600_000;\n\n/**\n * Retirer les fichiers temporaires qu'un arrêt brutal a laissés.\n *\n * Un nom par écriture ne se réécrit jamais : chaque arrêt en pleine écriture en\n * laissait un pour toujours. Seuls les nôtres sont concernés, et seulement\n * passé une heure — un plus récent peut appartenir à une écriture en cours,\n * celle d'une autre Runeya du même poste.\n */\nexport async function sweepStaleTempFiles(dirs: string[]): Promise<number> {\n let removed = 0;\n const limit = Date.now() - STALE_TEMP_MS;\n for (const dir of dirs) {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n continue;\n }\n for (const name of names.filter((n) => OWN_TEMP_RE.test(n))) {\n const path = join(dir, name);\n try {\n if ((await stat(path)).mtimeMs > limit) continue;\n await rm(path, { force: true });\n removed += 1;\n } catch {\n // Disparu entre-temps : rien à retirer.\n }\n }\n }\n return removed;\n}\n\nexport async function atomicWriteJsonSecure(filePath: string, data: unknown, pretty = false): Promise<void> {\n await atomicWriteSecure(filePath, JSON.stringify(data, null, pretty ? 2 : 0));\n}\n\nexport async function readJsonFileSafe<T>(filePath: string): Promise<T | null> {\n try {\n const raw = await readFile(filePath, 'utf-8');\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["stat","stat"]}
|
|
1
|
+
{"version":3,"sources":["../../server/src/services/disk-freshness.ts","../../server/src/services/run-store-utils.ts"],"sourcesContent":["import { stat } from 'node:fs/promises';\n\n/**\n * Le fichier a-t-il changé depuis qu'on l'a lu ?\n *\n * Les stores gardent de côté ce qu'ils n'ont pas su déchiffrer, pour le\n * réécrire tel quel plutôt que de l'effacer. Cette copie est la bonne réponse\n * tant que personne d'autre ne touche au fichier — et la mauvaise dès que\n * quelqu'un le répare par-dessous : la copie, elle, ne se périme jamais, et la\n * sauvegarde suivante réinstalle l'état d'avant la réparation.\n *\n * C'est arrivé deux fois de suite sur le même coffre : une conversion de clé\n * menée sur le disque, défaite quelques minutes plus tard par un store qui\n * tenait encore l'ancien chiffré en mémoire.\n *\n * D'où ce repère : l'empreinte du fichier au moment de la lecture. Si elle a\n * bougé, la copie mémoire ne fait plus foi et doit être abandonnée.\n */\n\ninterface Fingerprint {\n mtimeMs: number;\n size: number;\n /**\n * L'inode du chemin, qui change à chaque `rename` par-dessus.\n *\n * La date et la taille laissent passer le motif d'écriture de tout le dépôt :\n * les stores réécrivent le fichier entier, donc une modification rend souvent\n * le même nombre d'octets, et deux instances qui réagissent au même événement\n * écrivent dans la même milliseconde. Le fichier temporaire renommé\n * par-dessus, lui, installe toujours un autre inode.\n *\n * Là où le système n'en expose pas (il rend alors 0), le repère retombe sur\n * la date et la taille — ce qu'il valait avant.\n */\n ino: number;\n}\n\nconst seen = new Map<string, Fingerprint>();\n\n/** Retenir l'état d'un fichier au moment où on vient de le lire. */\nexport async function rememberFile(path: string): Promise<void> {\n try {\n const s = await stat(path);\n seen.set(path, { mtimeMs: s.mtimeMs, size: s.size, ino: s.ino });\n } catch {\n // Pas encore de fichier : rien à retenir, et rien à réémettre non plus.\n seen.delete(path);\n }\n}\n\n/**\n * Vrai si le fichier a changé — ou si l'on ne sait pas.\n *\n * L'ignorance compte comme un changement : réécrire une copie dont on ne peut\n * plus prouver qu'elle correspond au disque est précisément le geste à éviter.\n */\nexport async function hasChangedSinceRead(path: string): Promise<boolean> {\n const known = seen.get(path);\n if (!known) return true;\n try {\n const s = await stat(path);\n return s.mtimeMs !== known.mtimeMs || s.size !== known.size || s.ino !== known.ino;\n } catch {\n return true;\n }\n}\n\n/**\n * A-t-on lu ce fichier ?\n *\n * `hasChangedSinceRead` tient l'ignorance pour un changement : c'est juste\n * quand il s'agit de décider si une copie mémoire fait encore foi. Ça ne l'est\n * pas pour décider d'écrire — un fichier jamais lu est le cas d'un coffre neuf,\n * et le traiter comme « modifié sous nos pieds » revenait à ne jamais écrire la\n * première fois. Les appelants qui protègent une écriture demandent donc les\n * deux : connu, et inchangé.\n */\nexport function wasRead(path: string): boolean {\n return seen.has(path);\n}\n\n/** Oublier un fichier — après un rechargement complet, par exemple. */\nexport function forgetFile(path: string): void {\n seen.delete(path);\n}\n\n/**\n * L'empreinte d'un ensemble de fichiers : lesquels, et dans quel état.\n *\n * Le repère ci-dessus répond pour un fichier qu'on a lu soi-même ; celui-ci\n * répond pour un store dont le contenu vient de plusieurs fichiers à la fois —\n * un par coffre — et dont la question est « le disque a-t-il bougé depuis mon\n * dernier chargement ? ». Comparer deux empreintes coûte quelques `stat` ;\n * recharger coûte autant de lectures, d'analyses et de déchiffrements.\n *\n * L'absence d'un fichier en fait partie : un coffre disparu change l'empreinte\n * autant qu'un coffre modifié.\n */\nexport async function fingerprintFiles(paths: string[]): Promise<string> {\n const stamps = await Promise.all(paths.map(async (path) => {\n try {\n const s = await stat(path);\n // L'inode en fait partie pour la raison décrite sur `Fingerprint` : un\n // fichier renommé par-dessus la cible peut n'avoir bougé ni en taille ni\n // en date, et reste pourtant un autre contenu.\n return `${path}:${s.mtimeMs}:${s.size}:${s.ino}`;\n } catch {\n return `${path}:-`;\n }\n }));\n return stamps.sort().join('|');\n}\n","import { randomUUID } from 'node:crypto';\nimport { chmod, mkdir, readdir, rename, rm, stat, writeFile, readFile } from 'node:fs/promises';\nimport { dirname, join, resolve, sep } from 'node:path';\nimport { machineRoot } from './vault-resolver.js';\n\nconst SAFE_PATH_SEGMENT_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;\n\nexport function resolveConversationRunDir(rootDirName: string, conversationId: string): string {\n if (!SAFE_PATH_SEGMENT_RE.test(conversationId)) {\n throw new Error('Invalid conversationId');\n }\n // Machine-wide, like the conversations these runs belong to: using the launch\n // directory would put a `.runeya` in a repository just for asking the AI a\n // question about a cloud project.\n const base = resolve(machineRoot(), rootDirName);\n const dir = resolve(base, conversationId);\n if (!dir.startsWith(base + sep)) {\n throw new Error('Invalid conversationId');\n }\n return dir;\n}\n\n/** Codes que Windows renvoie tant qu'un autre descripteur tient la cible ouverte. */\nconst TRANSIENT_RENAME_CODES = new Set(['EPERM', 'EACCES', 'EBUSY']);\nconst RENAME_RETRY_BUDGET_MS = 2000;\n\n/**\n * Sous Windows, remplacer un fichier qu'un lecteur concurrent, un antivirus ou\n * l'indexeur tient ouvert échoue quelques millisecondes. Réessayer avec un recul\n * croissant, borné : au-delà, le refus est réel et remonte.\n */\nexport async function renameWithRetry(from: string, to: string): Promise<void> {\n const deadline = Date.now() + RENAME_RETRY_BUDGET_MS;\n for (let delay = 10; ; delay = Math.min(delay * 2, 200)) {\n try {\n await rename(from, to);\n return;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (!code || !TRANSIENT_RENAME_CODES.has(code) || Date.now() + delay > deadline) throw err;\n await new Promise((r) => setTimeout(r, delay));\n }\n }\n}\n\nexport async function atomicWriteSecure(filePath: string, content: string | Uint8Array): Promise<void> {\n await mkdir(dirname(filePath), { recursive: true });\n // Un nom par écriture : deux Runeya d'un même poste écrivent les mêmes\n // fichiers, et un `.tmp` commun se fait renommer sous le nez de l'autre.\n const tmpPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n try {\n await writeFile(tmpPath, content, typeof content === 'string' ? 'utf-8' : undefined);\n await renameWithRetry(tmpPath, filePath);\n } catch (err) {\n // Un nom unique ne se réécrit jamais : laissé là, chaque échec en ajouterait un.\n await rm(tmpPath, { force: true }).catch(() => {});\n throw err;\n }\n await chmod(filePath, 0o600);\n}\n\n/** Le nom qu'`atomicWriteSecure` donne à ses fichiers temporaires : `<fichier>.<pid>.<uuid>.tmp`. */\nconst OWN_TEMP_RE = /\\.\\d+\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\.tmp$/;\nconst STALE_TEMP_MS = 3600_000;\n\n/**\n * Retirer les fichiers temporaires qu'un arrêt brutal a laissés.\n *\n * Un nom par écriture ne se réécrit jamais : chaque arrêt en pleine écriture en\n * laissait un pour toujours. Seuls les nôtres sont concernés, et seulement\n * passé une heure — un plus récent peut appartenir à une écriture en cours,\n * celle d'une autre Runeya du même poste.\n */\nexport async function sweepStaleTempFiles(dirs: string[]): Promise<number> {\n let removed = 0;\n const limit = Date.now() - STALE_TEMP_MS;\n for (const dir of dirs) {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch {\n continue;\n }\n for (const name of names.filter((n) => OWN_TEMP_RE.test(n))) {\n const path = join(dir, name);\n try {\n if ((await stat(path)).mtimeMs > limit) continue;\n await rm(path, { force: true });\n removed += 1;\n } catch {\n // Disparu entre-temps : rien à retirer.\n }\n }\n }\n return removed;\n}\n\nexport async function atomicWriteJsonSecure(filePath: string, data: unknown, pretty = false): Promise<void> {\n await atomicWriteSecure(filePath, JSON.stringify(data, null, pretty ? 2 : 0));\n}\n\nexport async function readJsonFileSafe<T>(filePath: string): Promise<T | null> {\n try {\n const raw = await readFile(filePath, 'utf-8');\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["stat","stat"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
isLauncherProjectId,
|
|
2
3
|
launcherIdFromName
|
|
3
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-MZSDTA4A.js";
|
|
4
5
|
|
|
5
6
|
// src/desktop-launcher.ts
|
|
6
7
|
import { mkdir, writeFile, rm, copyFile, access, readdir, readFile } from "fs/promises";
|
|
@@ -15,6 +16,10 @@ function launcherArgs(opts) {
|
|
|
15
16
|
if (opts.agentPort !== null && opts.agentPort !== (opts.port ?? 4e3) + 1) {
|
|
16
17
|
args.push("--agent-port", String(opts.agentPort));
|
|
17
18
|
}
|
|
19
|
+
if (opts.projectId) {
|
|
20
|
+
if (!isLauncherProjectId(opts.projectId)) throw new Error(`Id de projet invalide : ${JSON.stringify(opts.projectId)}`);
|
|
21
|
+
args.push("--project", opts.projectId);
|
|
22
|
+
}
|
|
18
23
|
return args;
|
|
19
24
|
}
|
|
20
25
|
function launcherWorkingDir(workingDir) {
|
|
@@ -246,7 +251,10 @@ function parseLauncherCommand(tokens) {
|
|
|
246
251
|
};
|
|
247
252
|
const port = flag("--port");
|
|
248
253
|
const agentPort = flag("--agent-port") ?? (port === null ? null : port + 1);
|
|
249
|
-
|
|
254
|
+
const projectIndex = args.indexOf("--project");
|
|
255
|
+
const rawProject = projectIndex === -1 ? void 0 : args[projectIndex + 1];
|
|
256
|
+
const projectId = rawProject !== void 0 && isLauncherProjectId(rawProject) ? rawProject : null;
|
|
257
|
+
return { nodePath, cliEntry, port, agentPort, projectId };
|
|
250
258
|
}
|
|
251
259
|
function pinnedDir(dir) {
|
|
252
260
|
return dir === homedir() ? null : dir;
|
|
@@ -458,4 +466,4 @@ export {
|
|
|
458
466
|
uninstallDesktopLauncher,
|
|
459
467
|
listDesktopLaunchers
|
|
460
468
|
};
|
|
461
|
-
//# sourceMappingURL=chunk-
|
|
469
|
+
//# sourceMappingURL=chunk-57P45FNY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/desktop-launcher.ts"],"sourcesContent":["/**\n * Cross-platform desktop launcher (`runeya install-desktop`).\n *\n * Deliberately an explicit sub-command rather than a postinstall hook: npm 12\n * blocks lifecycle scripts by default, so a launcher created that way would be\n * silently missing. Here the user asks for it, and gets the paths back.\n *\n * Nothing is embedded — the launcher points at the Node binary and the CLI\n * entry point currently in use. If the user switches Node version manager,\n * re-running the command re-pins the paths.\n */\nimport { mkdir, writeFile, rm, copyFile, access, readdir, readFile } from 'node:fs/promises';\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { homedir } from 'node:os';\nimport { join, dirname } from 'node:path';\n\nimport {\n slugifyLauncherName,\n launcherIdFromName,\n deriveLauncherIdentity,\n isLauncherProjectId,\n} from '@runeya/packages-shared';\n\nconst exec = promisify(execFile);\n\n/**\n * Le nommage d'un lanceur vit dans `packages/shared` : le serveur en a besoin\n * pour proposer le même nom depuis l'interface. Réexporté ici sous les noms\n * locaux, qui sont ceux que le reste du CLI appelle.\n */\nexport {\n slugifyLauncherName as slugify,\n launcherIdFromName as idFromName,\n deriveLauncherIdentity as deriveIdentity,\n};\n\nexport type InstallOptions = {\n /** Absolute path to the Node binary the launcher must use. */\n nodePath: string;\n /** Absolute path to the CLI entry point (dist/index.js). */\n cliEntry: string;\n /** Directory holding the CLI dist — used to find the bundled icons in assets/. */\n distDir: string;\n /** Working directory to pin, or null to let the desktop decide. */\n workingDir: string | null;\n /** Display name, e.g. \"Runeya\" or \"Runeya — my-stack\". */\n name: string;\n /** Filesystem-safe id derived from the name, e.g. \"runeya-my-stack\". */\n id: string;\n /** Port baked into the launcher, or null to follow Runeya's default. */\n port: number | null;\n /** Agent port, or null to let Runeya derive it from `port + 1`. */\n agentPort: number | null;\n /** Project the launcher opens directly (`--project`), or null for the project list. */\n projectId?: string | null;\n};\n\n/**\n * Arguments the launcher passes to the CLI.\n *\n * `--agent-port` is omitted when it is exactly what Runeya would derive on its\n * own, so the command line stays readable and keeps following the derivation\n * if it ever changes.\n */\nexport function launcherArgs(opts: Pick<InstallOptions, 'port' | 'agentPort' | 'projectId'>): string[] {\n const args = ['--from-desktop'];\n if (opts.port !== null) args.push('--port', String(opts.port));\n if (opts.agentPort !== null && opts.agentPort !== (opts.port ?? 4000) + 1) {\n args.push('--agent-port', String(opts.agentPort));\n }\n if (opts.projectId) {\n // Dernier rempart : l'id finit dans un fichier exécutable.\n if (!isLauncherProjectId(opts.projectId)) throw new Error(`Id de projet invalide : ${JSON.stringify(opts.projectId)}`);\n args.push('--project', opts.projectId);\n }\n return args;\n}\n\nexport type LauncherResult = {\n /** Files created or removed, for reporting back to the user. */\n paths: string[];\n /** Non-fatal problems (missing icon, desktop database refresh failed…). */\n warnings: string[];\n};\n\n/**\n * Le dossier de travail d'un lanceur, avec un repli qui n'est jamais la racine.\n *\n * Sans dossier épinglé, un lanceur hérite du cwd du système — `/` pour le\n * Finder comme pour systemd — et Runeya y placerait son `.runeya`. La racine\n * est en lecture seule sous macOS et interdite d'écriture sous Linux : le home\n * est le seul repli toujours disponible. Le CLI applique la même garde à\n * l'exécution, pour les lanceurs posés avant ce correctif.\n */\nexport function launcherWorkingDir(workingDir: string | null): string {\n return workingDir ?? homedir();\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Best-effort command: never fails the install, just reports a warning. */\nasync function tryExec(file: string, args: string[], warnings: string[]): Promise<void> {\n try {\n await exec(file, args);\n } catch {\n warnings.push(`Commande optionnelle échouée : ${file} (le lanceur est créé, le menu peut mettre un instant à l'afficher)`);\n }\n}\n\n// ─── Linux ───────────────────────────────────────────────────────────────────\n\n/**\n * Quote a value for the `Exec=` key of a .desktop file.\n * Per the Desktop Entry spec, inside a quoted argument the characters\n * `\"`, backtick, `$` and `\\` must each be preceded by a backslash.\n */\nexport function desktopExecQuote(value: string): string {\n return `\"${value.replace(/[\"`$\\\\]/g, (c) => `\\\\${c}`)}\"`;\n}\n\n/** Escape a value for a non-Exec .desktop key (only backslashes are special). */\nfunction desktopValue(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/\\n/g, ' ');\n}\n\nfunction xdgDataHome(): string {\n return process.env['XDG_DATA_HOME'] || join(homedir(), '.local/share');\n}\n\nfunction linuxPaths(id: string) {\n const dataHome = xdgDataHome();\n const appsDir = join(dataHome, 'applications');\n return {\n appsDir,\n entry: join(appsDir, `${id}.desktop`),\n iconDir: join(dataHome, 'icons/hicolor/scalable/apps'),\n icon: join(dataHome, `icons/hicolor/scalable/apps/${id}.svg`),\n };\n}\n\nasync function installLinux(opts: InstallOptions): Promise<LauncherResult> {\n const paths: string[] = [];\n const warnings: string[] = [];\n const p = linuxPaths(opts.id);\n\n // The icon is an SVG, which the scalable hicolor directory takes as-is.\n const sourceIcon = join(opts.distDir, 'assets/icon.svg');\n let iconName = 'application-x-executable';\n if (await exists(sourceIcon)) {\n await mkdir(p.iconDir, { recursive: true });\n await copyFile(sourceIcon, p.icon);\n paths.push(p.icon);\n iconName = opts.id;\n } else {\n warnings.push(`Icône introuvable (${sourceIcon}), icône générique utilisée`);\n }\n\n // --from-desktop: nobody is watching a terminal, so startup failures must be\n // reported in a browser window rather than written to a discarded stderr.\n const exec_ = [opts.nodePath, opts.cliEntry, ...launcherArgs(opts)]\n .map(desktopExecQuote)\n .join(' ');\n const lines = [\n '[Desktop Entry]',\n 'Type=Application',\n `Name=${desktopValue(opts.name)}`,\n 'Comment=Process and service manager',\n `Exec=${exec_}`,\n `Icon=${iconName}`,\n 'Terminal=false',\n 'Categories=Development;',\n 'StartupNotify=true',\n // Marker used by uninstall-desktop to refuse deleting a hand-written entry\n 'X-Runeya-Managed=true',\n ];\n lines.push(`Path=${desktopValue(launcherWorkingDir(opts.workingDir))}`);\n\n await mkdir(p.appsDir, { recursive: true });\n await writeFile(p.entry, lines.join('\\n') + '\\n', { mode: 0o755 });\n paths.push(p.entry);\n\n await tryExec('update-desktop-database', [p.appsDir], warnings);\n\n return { paths, warnings };\n}\n\nasync function uninstallLinux(id: string): Promise<LauncherResult> {\n const paths: string[] = [];\n const warnings: string[] = [];\n const p = linuxPaths(id);\n for (const target of [p.entry, p.icon]) {\n if (await exists(target)) {\n await rm(target, { force: true });\n paths.push(target);\n }\n }\n if (paths.length) await tryExec('update-desktop-database', [p.appsDir], warnings);\n return { paths, warnings };\n}\n\n// ─── macOS ───────────────────────────────────────────────────────────────────\n\n/** Escape a value destined for a <string> node of an Info.plist. */\nfunction plistString(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n}\n\n/** Single-quote a value for the POSIX shell inside the .app stub script. */\nfunction shQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction macAppPath(name: string): string {\n return join(homedir(), 'Applications', `${name}.app`);\n}\n\nasync function installMac(opts: InstallOptions): Promise<LauncherResult> {\n const warnings: string[] = [];\n const appPath = macAppPath(opts.name);\n const macosDir = join(appPath, 'Contents/MacOS');\n const resourcesDir = join(appPath, 'Contents/Resources');\n const execName = opts.id;\n\n await mkdir(macosDir, { recursive: true });\n await mkdir(resourcesDir, { recursive: true });\n\n // The bundle executable is a shell stub: no compiled code, no signing needed.\n // A locally generated .app carries no quarantine attribute, so Gatekeeper\n // lets it run even unsigned.\n const stub = [\n '#!/bin/sh',\n '# Generated by `runeya install-desktop` — safe to delete.',\n `cd ${shQuote(launcherWorkingDir(opts.workingDir))} || exit 1`,\n `exec ${[opts.nodePath, opts.cliEntry, ...launcherArgs(opts)].map(shQuote).join(' ')} \"$@\"`,\n '',\n ].filter(Boolean).join('\\n');\n await writeFile(join(macosDir, execName), stub, { mode: 0o755 });\n\n // CFBundleIconFile is only emitted when the .icns actually made it into the\n // bundle — a key pointing at a missing file makes Finder fall back silently,\n // which is harder to diagnose than no key at all.\n const sourceIcns = join(opts.distDir, 'assets/icon.icns');\n let iconKey = '';\n if (await exists(sourceIcns)) {\n await copyFile(sourceIcns, join(resourcesDir, `${execName}.icns`));\n iconKey = `\\n <key>CFBundleIconFile</key><string>${plistString(execName)}</string>`;\n } else {\n warnings.push(`Icône introuvable (${sourceIcns}), icône générique utilisée`);\n }\n\n const plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>CFBundleName</key><string>${plistString(opts.name)}</string>\n <key>CFBundleDisplayName</key><string>${plistString(opts.name)}</string>\n <key>CFBundleExecutable</key><string>${plistString(execName)}</string>\n <key>CFBundleIdentifier</key><string>dev.runeya.launcher.${plistString(opts.id)}</string>\n <key>CFBundlePackageType</key><string>APPL</string>${iconKey}\n <key>CFBundleInfoDictionaryVersion</key><string>6.0</string>\n <key>CFBundleShortVersionString</key><string>1.0</string>\n <key>NSHighResolutionCapable</key><true/>\n</dict>\n</plist>\n`;\n await writeFile(join(appPath, 'Contents/Info.plist'), plist);\n\n // Ask LaunchServices to notice the new bundle so Spotlight finds it right away.\n await tryExec(\n '/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister',\n ['-f', appPath],\n warnings,\n );\n\n return { paths: [appPath], warnings };\n}\n\nasync function uninstallMac(name: string): Promise<LauncherResult> {\n const appPath = macAppPath(name);\n if (!(await exists(appPath))) return { paths: [], warnings: [] };\n await rm(appPath, { recursive: true, force: true });\n return { paths: [appPath], warnings: [] };\n}\n\n// ─── Windows ─────────────────────────────────────────────────────────────────\n\n/** Single-quote a value for a PowerShell literal string. */\nexport function psQuote(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`;\n}\n\nfunction windowsLnkPath(name: string): string {\n const appData = process.env['APPDATA'] || join(homedir(), 'AppData/Roaming');\n return join(appData, 'Microsoft/Windows/Start Menu/Programs', `${name}.lnk`);\n}\n\n/**\n * Où vit le script qui lance Runeya sans console.\n *\n * Hors du dossier du menu Démarrer — un `.vbs` posé là apparaîtrait dans la\n * recherche Windows à côté du raccourci — et hors du `dist` du paquet, qu'une\n * réinstallation npm remplace : le script y disparaîtrait sans que rien ne le\n * recrée, faute de postinstall (npm 12 les bloque).\n */\nfunction windowsVbsPath(id: string): string {\n const localAppData = process.env['LOCALAPPDATA'] || join(homedir(), 'AppData/Local');\n return join(localAppData, 'Runeya', `${id}.vbs`);\n}\n\n/** Double-quote a value for a VBScript string literal. */\nexport function vbsQuote(value: string): string {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n}\n\n/**\n * Le script qui lance Runeya **sans fenêtre**.\n *\n * `node.exe` est une application console : Windows lui alloue une console, et\n * un raccourci ne sait que la réduire (`WindowStyle = 7`) — d'où une fenêtre de\n * commande dans la barre des tâches pendant tout l'usage de l'application.\n *\n * `wscript.exe`, lui, est une application graphique : il n'ouvre aucune console\n * et lance sa cible en `SW_HIDE` (le `0` de `Run`). La console de Node existe\n * donc toujours — c'est ce qui compte : **elle est seulement cachée, pas\n * absente**, et tout ce que Runeya lance ensuite (l'agent, git, docker, les\n * services) l'hérite sans rien afficher. C'est la même intention que le\n * `windowsHide` des runners, posée une fois pour toute la descendance.\n *\n * Le `False` rend la main immédiatement : `wscript` sort, Runeya reste.\n */\nexport function windowsVbsScript(\n opts: Pick<InstallOptions, 'nodePath' | 'cliEntry' | 'port' | 'agentPort' | 'projectId'> & { workingDir: string | null },\n): string {\n const command = [opts.nodePath, opts.cliEntry, ...launcherArgs(opts)]\n .map((part) => `\"${part}\"`)\n .join(' ');\n return [\n // Commentaires en ASCII : wscript lit un .vbs sans BOM comme de l'ANSI, et\n // des accents s'y afficheraient de travers à qui l'ouvre.\n \"' Runeya launcher - created by 'runeya install-desktop', do not edit.\",\n \"' wscript.exe opens no console; the 0 below starts Runeya hidden.\",\n 'Set sh = CreateObject(\"WScript.Shell\")',\n `sh.CurrentDirectory = ${vbsQuote(launcherWorkingDir(opts.workingDir))}`,\n `sh.Run ${vbsQuote(command)}, 0, False`,\n '',\n ].join('\\n');\n}\n\nasync function runPowerShell(script: string, warnings: string[]): Promise<void> {\n // -EncodedCommand avoids every layer of cmd.exe quoting.\n const encoded = Buffer.from(script, 'utf16le').toString('base64');\n try {\n await exec('powershell.exe', ['-NoProfile', '-NonInteractive', '-EncodedCommand', encoded]);\n } catch (err) {\n warnings.push(`PowerShell a échoué : ${(err as Error).message}`);\n throw err;\n }\n}\n\nasync function installWindows(opts: InstallOptions): Promise<LauncherResult> {\n const warnings: string[] = [];\n const lnk = windowsLnkPath(opts.name);\n\n // The .ico is referenced in place inside the installed dist rather than\n // copied: the shortcut already breaks if that dist moves, so a second copy\n // would only add a file to leave behind.\n const icoPath = join(opts.distDir, 'assets/icon.ico');\n const iconPath = (await exists(icoPath)) ? icoPath : null;\n if (!iconPath) warnings.push(`Icône introuvable (${icoPath}), icône de Node utilisée`);\n\n // Le raccourci ne lance pas Node : il lance `wscript.exe`, qui lance Node\n // sans console. Un raccourci pointant sur `node.exe` laissait une fenêtre de\n // commande dans la barre des tâches, `WindowStyle` ne sachant que la réduire.\n const vbs = windowsVbsPath(opts.id);\n await mkdir(dirname(vbs), { recursive: true });\n await writeFile(vbs, windowsVbsScript(opts), 'utf8');\n\n const wscript = join(process.env['SystemRoot'] || 'C:\\\\Windows', 'System32', 'wscript.exe');\n\n const script = [\n `$ws = New-Object -ComObject WScript.Shell`,\n `$sc = $ws.CreateShortcut(${psQuote(lnk)})`,\n `$sc.TargetPath = ${psQuote(wscript)}`,\n `$sc.Arguments = ${psQuote(`\"${vbs}\"`)}`,\n `$sc.WorkingDirectory = ${psQuote(launcherWorkingDir(opts.workingDir))}`,\n `$sc.Description = 'Runeya — process and service manager'`,\n // L'icône reste celle de Runeya : elle est déclarée à part de la cible.\n iconPath ? `$sc.IconLocation = ${psQuote(iconPath)}` : '',\n `$sc.Save()`,\n ].filter(Boolean).join('\\n');\n\n await mkdir(dirname(lnk), { recursive: true });\n await runPowerShell(script, warnings);\n\n return { paths: [lnk, vbs], warnings };\n}\n\nasync function uninstallWindows(name: string, id: string): Promise<LauncherResult> {\n // Les deux fichiers, et chacun pour lui-même : un raccourci posé avant que le\n // lanceur ne passe par `wscript` n'a pas de `.vbs` à côté de lui, et son\n // absence n'est pas une raison de laisser le raccourci en place.\n const removed: string[] = [];\n for (const path of [windowsLnkPath(name), windowsVbsPath(id)]) {\n if (!(await exists(path))) continue;\n await rm(path, { force: true });\n removed.push(path);\n }\n return { paths: removed, warnings: [] };\n}\n\n// ─── Reading back the launchers already installed ────────────────────────────\n\n/**\n * Un raccourci déjà posé, tel qu'on le relit sur le disque.\n *\n * Les fichiers écrits par `install-desktop` sont la seule source : pas de\n * registre à côté, qui dériverait dès qu'un raccourci est supprimé à la main.\n */\nexport type InstalledLauncher = {\n id: string;\n name: string;\n /** `null` : raccourci global (il démarre dans le dossier personnel). */\n workingDir: string | null;\n port: number | null;\n agentPort: number | null;\n /** Projet ouvert directement (`--project`), ou null. */\n projectId: string | null;\n nodePath: string;\n cliEntry: string;\n /** Les fichiers qui le composent — ceux qu'une suppression retirerait. */\n paths: string[];\n};\n\n/**\n * Les réglages d'un raccourci, relus dans la ligne de commande qu'il lance :\n * `node cli --from-desktop [--port N] [--agent-port M] [--project ID]`.\n *\n * `--agent-port` absent veut dire « port + 1 », comme le dérive Runeya — c'est\n * pour cela que `launcherArgs` l'omet.\n */\nexport function parseLauncherCommand(\n tokens: string[],\n): Pick<InstalledLauncher, 'nodePath' | 'cliEntry' | 'port' | 'agentPort' | 'projectId'> | null {\n const [nodePath, cliEntry, ...args] = tokens;\n if (!nodePath || !cliEntry) return null;\n const flag = (name: string): number | null => {\n const i = args.indexOf(name);\n if (i === -1) return null;\n const value = Number(args[i + 1]);\n return Number.isInteger(value) && value >= 1 && value <= 65_535 ? value : null;\n };\n const port = flag('--port');\n const agentPort = flag('--agent-port') ?? (port === null ? null : port + 1);\n // Un id illisible est ignoré plutôt que recopié dans un raccourci recréé.\n const projectIndex = args.indexOf('--project');\n const rawProject = projectIndex === -1 ? undefined : args[projectIndex + 1];\n const projectId = rawProject !== undefined && isLauncherProjectId(rawProject) ? rawProject : null;\n return { nodePath, cliEntry, port, agentPort, projectId };\n}\n\n/** Le dossier d'un raccourci, rendu à `null` quand il est le repli global. */\nfunction pinnedDir(dir: string): string | null {\n return dir === homedir() ? null : dir;\n}\n\n/**\n * Découper la clé `Exec=` d'un `.desktop` — l'inverse de `desktopExecQuote` :\n * arguments séparés par des espaces, entre guillemets doubles, où `\\x` vaut `x`.\n */\nexport function splitDesktopExec(exec: string): string[] {\n const tokens: string[] = [];\n let current = '';\n let quoted = false;\n let started = false;\n for (let i = 0; i < exec.length; i++) {\n const ch = exec[i]!;\n if (quoted && ch === '\\\\' && i + 1 < exec.length) { current += exec[++i]; continue; }\n if (ch === '\"') { quoted = !quoted; started = true; continue; }\n if (!quoted && ch === ' ') {\n if (started) tokens.push(current);\n current = ''; started = false;\n continue;\n }\n current += ch; started = true;\n }\n if (started) tokens.push(current);\n return tokens;\n}\n\n/** Relire un `.desktop` écrit par `installLinux`. `null` si ce n'en est pas un. */\nexport function parseDesktopEntry(id: string, text: string, path: string): InstalledLauncher | null {\n const keys = new Map<string, string>();\n for (const line of text.split('\\n')) {\n const eq = line.indexOf('=');\n if (eq > 0 && !keys.has(line.slice(0, eq))) keys.set(line.slice(0, eq), line.slice(eq + 1));\n }\n if (keys.get('X-Runeya-Managed') !== 'true') return null;\n const command = parseLauncherCommand(splitDesktopExec(keys.get('Exec') ?? ''));\n if (!command) return null;\n const unescape = (v: string) => v.replace(/\\\\\\\\/g, '\\\\');\n return {\n id,\n name: unescape(keys.get('Name') ?? id),\n workingDir: keys.has('Path') ? pinnedDir(unescape(keys.get('Path')!)) : null,\n ...command,\n paths: [path, linuxPaths(id).icon],\n };\n}\n\nasync function listLinux(): Promise<InstalledLauncher[]> {\n const { appsDir } = linuxPaths('');\n const found: InstalledLauncher[] = [];\n for (const file of await readdir(appsDir).catch(() => [] as string[])) {\n if (!file.endsWith('.desktop')) continue;\n const path = join(appsDir, file);\n const text = await readFile(path, 'utf8').catch(() => '');\n const launcher = parseDesktopEntry(file.slice(0, -'.desktop'.length), text, path);\n if (!launcher) continue;\n // L'icône n'est listée que si elle est là : c'est ce qu'on retirerait.\n if (!(await exists(launcher.paths[1]!))) launcher.paths.length = 1;\n found.push(launcher);\n }\n return found;\n}\n\n/** Découper une ligne shell faite d'arguments passés par `shQuote`. */\nfunction splitShQuoted(line: string): string[] {\n const tokens: string[] = [];\n for (const match of line.matchAll(/'((?:[^']|'\\\\'')*)'/g)) {\n tokens.push(match[1]!.replace(/'\\\\''/g, \"'\"));\n }\n return tokens;\n}\n\n/** Relire un `.app` écrit par `installMac` à partir de son plist et de son script. */\nexport function parseMacApp(plist: string, stub: string, path: string): InstalledLauncher | null {\n const key = (name: string): string | null => {\n const m = plist.match(new RegExp(`<key>${name}</key>\\\\s*<string>([^<]*)</string>`));\n return m ? m[1]!.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&') : null;\n };\n const identifier = key('CFBundleIdentifier');\n const prefix = 'dev.runeya.launcher.';\n if (!identifier?.startsWith(prefix)) return null;\n\n const lines = stub.split('\\n');\n const cd = lines.find((l) => l.startsWith('cd '));\n const execLine = lines.find((l) => l.startsWith('exec '));\n const command = execLine ? parseLauncherCommand(splitShQuoted(execLine)) : null;\n if (!command) return null;\n const dir = cd ? splitShQuoted(cd)[0] : undefined;\n return {\n id: identifier.slice(prefix.length),\n name: key('CFBundleName') ?? identifier.slice(prefix.length),\n workingDir: dir ? pinnedDir(dir) : null,\n ...command,\n paths: [path],\n };\n}\n\nasync function listMac(): Promise<InstalledLauncher[]> {\n const dir = join(homedir(), 'Applications');\n const found: InstalledLauncher[] = [];\n for (const entry of await readdir(dir).catch(() => [] as string[])) {\n if (!entry.endsWith('.app')) continue;\n const path = join(dir, entry);\n const plist = await readFile(join(path, 'Contents/Info.plist'), 'utf8').catch(() => '');\n const exe = plist.match(/<key>CFBundleExecutable<\\/key>\\s*<string>([^<]*)<\\/string>/)?.[1];\n if (!exe) continue;\n const stub = await readFile(join(path, 'Contents/MacOS', exe), 'utf8').catch(() => '');\n const launcher = parseMacApp(plist, stub, path);\n if (launcher) found.push(launcher);\n }\n return found;\n}\n\n/** Relire un `.vbs` écrit par `windowsVbsScript` : l'inverse de ses `vbsQuote`. */\nexport function parseVbs(id: string, text: string): Omit<InstalledLauncher, 'name' | 'paths'> | null {\n const literal = (prefix: string): string | null => {\n const line = text.split(/\\r?\\n/).find((l) => l.startsWith(prefix));\n const m = line?.slice(prefix.length).match(/^\"((?:[^\"]|\"\")*)\"/);\n return m ? m[1]!.replace(/\"\"/g, '\"') : null;\n };\n const dir = literal('sh.CurrentDirectory = ');\n const run = literal('sh.Run ');\n if (dir === null || run === null) return null;\n const tokens = [...run.matchAll(/\"([^\"]*)\"/g)].map((m) => m[1]!);\n const command = parseLauncherCommand(tokens);\n return command ? { id, workingDir: pinnedDir(dir), ...command } : null;\n}\n\nasync function listWindows(): Promise<InstalledLauncher[]> {\n const vbsDir = dirname(windowsVbsPath('x'));\n const lnkDir = dirname(windowsLnkPath('x'));\n // Le `.vbs` porte l'id, le raccourci du menu Démarrer le nom affiché. L'id\n // étant une fonction pure du nom, c'est lui qui les apparie — sans avoir à\n // demander à PowerShell ce que vise chaque raccourci.\n const lnkById = new Map<string, string>();\n for (const file of await readdir(lnkDir).catch(() => [] as string[])) {\n if (file.toLowerCase().endsWith('.lnk')) lnkById.set(launcherIdFromName(file.slice(0, -4)), file.slice(0, -4));\n }\n const found: InstalledLauncher[] = [];\n for (const file of await readdir(vbsDir).catch(() => [] as string[])) {\n if (!file.toLowerCase().endsWith('.vbs')) continue;\n const id = file.slice(0, -4);\n const parsed = parseVbs(id, await readFile(join(vbsDir, file), 'utf8').catch(() => ''));\n if (!parsed) continue;\n const name = lnkById.get(id);\n found.push({\n ...parsed,\n // Sans raccourci dans le menu, il ne reste que l'id pour le nommer.\n name: name ?? id,\n paths: [...(name ? [windowsLnkPath(name)] : []), join(vbsDir, file)],\n });\n }\n return found;\n}\n\n/** Ce qui empêcherait un raccourci de démarrer, en mots d'utilisateur. */\nexport async function launcherProblems(launcher: InstalledLauncher): Promise<string[]> {\n const problems: string[] = [];\n if (!(await exists(launcher.nodePath))) problems.push(`Node introuvable : ${launcher.nodePath}`);\n if (!(await exists(launcher.cliEntry))) problems.push(`Runeya introuvable : ${launcher.cliEntry}`);\n if (launcher.workingDir && !(await exists(launcher.workingDir))) problems.push(`Dossier introuvable : ${launcher.workingDir}`);\n return problems;\n}\n\n// ─── Entry points ────────────────────────────────────────────────────────────\n\nexport async function installDesktopLauncher(opts: InstallOptions): Promise<LauncherResult> {\n switch (process.platform) {\n case 'linux':\n case 'freebsd':\n case 'openbsd':\n return installLinux(opts);\n case 'darwin':\n return installMac(opts);\n case 'win32':\n return installWindows(opts);\n default:\n throw new Error(`Plateforme non supportée : ${process.platform}`);\n }\n}\n\nexport async function uninstallDesktopLauncher(name: string, id: string): Promise<LauncherResult> {\n switch (process.platform) {\n case 'linux':\n case 'freebsd':\n case 'openbsd':\n return uninstallLinux(id);\n case 'darwin':\n return uninstallMac(name);\n case 'win32':\n return uninstallWindows(name, id);\n default:\n throw new Error(`Plateforme non supportée : ${process.platform}`);\n }\n}\n\n/** Les raccourcis que `install-desktop` a posés sur cette machine. */\nexport async function listDesktopLaunchers(): Promise<InstalledLauncher[]> {\n switch (process.platform) {\n case 'linux':\n case 'freebsd':\n case 'openbsd':\n return listLinux();\n case 'darwin':\n return listMac();\n case 'win32':\n return listWindows();\n default:\n return [];\n }\n}\n"],"mappings":";;;;;;AAWA,SAAS,OAAO,WAAW,IAAI,UAAU,QAAQ,SAAS,gBAAgB;AAC1E,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAC1B,SAAS,eAAe;AACxB,SAAS,MAAM,eAAe;AAS9B,IAAM,OAAO,UAAU,QAAQ;AAyCxB,SAAS,aAAa,MAA0E;AACrG,QAAM,OAAO,CAAC,gBAAgB;AAC9B,MAAI,KAAK,SAAS,KAAM,MAAK,KAAK,UAAU,OAAO,KAAK,IAAI,CAAC;AAC7D,MAAI,KAAK,cAAc,QAAQ,KAAK,eAAe,KAAK,QAAQ,OAAQ,GAAG;AACzE,SAAK,KAAK,gBAAgB,OAAO,KAAK,SAAS,CAAC;AAAA,EAClD;AACA,MAAI,KAAK,WAAW;AAElB,QAAI,CAAC,oBAAoB,KAAK,SAAS,EAAG,OAAM,IAAI,MAAM,2BAA2B,KAAK,UAAU,KAAK,SAAS,CAAC,EAAE;AACrH,SAAK,KAAK,aAAa,KAAK,SAAS;AAAA,EACvC;AACA,SAAO;AACT;AAkBO,SAAS,mBAAmB,YAAmC;AACpE,SAAO,cAAc,QAAQ;AAC/B;AAEA,eAAe,OAAO,MAAgC;AACpD,MAAI;AACF,UAAM,OAAO,IAAI;AACjB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAe,QAAQ,MAAc,MAAgB,UAAmC;AACtF,MAAI;AACF,UAAM,KAAK,MAAM,IAAI;AAAA,EACvB,QAAQ;AACN,aAAS,KAAK,wCAAkC,IAAI,8EAAqE;AAAA,EAC3H;AACF;AASO,SAAS,iBAAiB,OAAuB;AACtD,SAAO,IAAI,MAAM,QAAQ,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;AACvD;AAGA,SAAS,aAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,OAAO,MAAM,EAAE,QAAQ,OAAO,GAAG;AACxD;AAEA,SAAS,cAAsB;AAC7B,SAAO,QAAQ,IAAI,eAAe,KAAK,KAAK,QAAQ,GAAG,cAAc;AACvE;AAEA,SAAS,WAAW,IAAY;AAC9B,QAAM,WAAW,YAAY;AAC7B,QAAM,UAAU,KAAK,UAAU,cAAc;AAC7C,SAAO;AAAA,IACL;AAAA,IACA,OAAO,KAAK,SAAS,GAAG,EAAE,UAAU;AAAA,IACpC,SAAS,KAAK,UAAU,6BAA6B;AAAA,IACrD,MAAM,KAAK,UAAU,+BAA+B,EAAE,MAAM;AAAA,EAC9D;AACF;AAEA,eAAe,aAAa,MAA+C;AACzE,QAAM,QAAkB,CAAC;AACzB,QAAM,WAAqB,CAAC;AAC5B,QAAM,IAAI,WAAW,KAAK,EAAE;AAG5B,QAAM,aAAa,KAAK,KAAK,SAAS,iBAAiB;AACvD,MAAI,WAAW;AACf,MAAI,MAAM,OAAO,UAAU,GAAG;AAC5B,UAAM,MAAM,EAAE,SAAS,EAAE,WAAW,KAAK,CAAC;AAC1C,UAAM,SAAS,YAAY,EAAE,IAAI;AACjC,UAAM,KAAK,EAAE,IAAI;AACjB,eAAW,KAAK;AAAA,EAClB,OAAO;AACL,aAAS,KAAK,yBAAsB,UAAU,yCAA6B;AAAA,EAC7E;AAIA,QAAM,QAAQ,CAAC,KAAK,UAAU,KAAK,UAAU,GAAG,aAAa,IAAI,CAAC,EAC/D,IAAI,gBAAgB,EACpB,KAAK,GAAG;AACX,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA,QAAQ,aAAa,KAAK,IAAI,CAAC;AAAA,IAC/B;AAAA,IACA,QAAQ,KAAK;AAAA,IACb,QAAQ,QAAQ;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,EACF;AACA,QAAM,KAAK,QAAQ,aAAa,mBAAmB,KAAK,UAAU,CAAC,CAAC,EAAE;AAEtE,QAAM,MAAM,EAAE,SAAS,EAAE,WAAW,KAAK,CAAC;AAC1C,QAAM,UAAU,EAAE,OAAO,MAAM,KAAK,IAAI,IAAI,MAAM,EAAE,MAAM,IAAM,CAAC;AACjE,QAAM,KAAK,EAAE,KAAK;AAElB,QAAM,QAAQ,2BAA2B,CAAC,EAAE,OAAO,GAAG,QAAQ;AAE9D,SAAO,EAAE,OAAO,SAAS;AAC3B;AAEA,eAAe,eAAe,IAAqC;AACjE,QAAM,QAAkB,CAAC;AACzB,QAAM,WAAqB,CAAC;AAC5B,QAAM,IAAI,WAAW,EAAE;AACvB,aAAW,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,GAAG;AACtC,QAAI,MAAM,OAAO,MAAM,GAAG;AACxB,YAAM,GAAG,QAAQ,EAAE,OAAO,KAAK,CAAC;AAChC,YAAM,KAAK,MAAM;AAAA,IACnB;AAAA,EACF;AACA,MAAI,MAAM,OAAQ,OAAM,QAAQ,2BAA2B,CAAC,EAAE,OAAO,GAAG,QAAQ;AAChF,SAAO,EAAE,OAAO,SAAS;AAC3B;AAKA,SAAS,YAAY,OAAuB;AAC1C,SAAO,MACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM;AACzB;AAGA,SAAS,QAAQ,OAAuB;AACtC,SAAO,IAAI,MAAM,QAAQ,MAAM,OAAO,CAAC;AACzC;AAEA,SAAS,WAAW,MAAsB;AACxC,SAAO,KAAK,QAAQ,GAAG,gBAAgB,GAAG,IAAI,MAAM;AACtD;AAEA,eAAe,WAAW,MAA+C;AACvE,QAAM,WAAqB,CAAC;AAC5B,QAAM,UAAU,WAAW,KAAK,IAAI;AACpC,QAAM,WAAW,KAAK,SAAS,gBAAgB;AAC/C,QAAM,eAAe,KAAK,SAAS,oBAAoB;AACvD,QAAM,WAAW,KAAK;AAEtB,QAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AACzC,QAAM,MAAM,cAAc,EAAE,WAAW,KAAK,CAAC;AAK7C,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IACA,MAAM,QAAQ,mBAAmB,KAAK,UAAU,CAAC,CAAC;AAAA,IAClD,QAAQ,CAAC,KAAK,UAAU,KAAK,UAAU,GAAG,aAAa,IAAI,CAAC,EAAE,IAAI,OAAO,EAAE,KAAK,GAAG,CAAC;AAAA,IACpF;AAAA,EACF,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAC3B,QAAM,UAAU,KAAK,UAAU,QAAQ,GAAG,MAAM,EAAE,MAAM,IAAM,CAAC;AAK/D,QAAM,aAAa,KAAK,KAAK,SAAS,kBAAkB;AACxD,MAAI,UAAU;AACd,MAAI,MAAM,OAAO,UAAU,GAAG;AAC5B,UAAM,SAAS,YAAY,KAAK,cAAc,GAAG,QAAQ,OAAO,CAAC;AACjE,cAAU;AAAA,uCAA0C,YAAY,QAAQ,CAAC;AAAA,EAC3E,OAAO;AACL,aAAS,KAAK,yBAAsB,UAAU,yCAA6B;AAAA,EAC7E;AAEA,QAAM,QAAQ;AAAA;AAAA;AAAA;AAAA,mCAImB,YAAY,KAAK,IAAI,CAAC;AAAA,0CACf,YAAY,KAAK,IAAI,CAAC;AAAA,yCACvB,YAAY,QAAQ,CAAC;AAAA,6DACD,YAAY,KAAK,EAAE,CAAC;AAAA,uDAC1B,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAO5D,QAAM,UAAU,KAAK,SAAS,qBAAqB,GAAG,KAAK;AAG3D,QAAM;AAAA,IACJ;AAAA,IACA,CAAC,MAAM,OAAO;AAAA,IACd;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,CAAC,OAAO,GAAG,SAAS;AACtC;AAEA,eAAe,aAAa,MAAuC;AACjE,QAAM,UAAU,WAAW,IAAI;AAC/B,MAAI,CAAE,MAAM,OAAO,OAAO,EAAI,QAAO,EAAE,OAAO,CAAC,GAAG,UAAU,CAAC,EAAE;AAC/D,QAAM,GAAG,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAClD,SAAO,EAAE,OAAO,CAAC,OAAO,GAAG,UAAU,CAAC,EAAE;AAC1C;AAKO,SAAS,QAAQ,OAAuB;AAC7C,SAAO,IAAI,MAAM,QAAQ,MAAM,IAAI,CAAC;AACtC;AAEA,SAAS,eAAe,MAAsB;AAC5C,QAAM,UAAU,QAAQ,IAAI,SAAS,KAAK,KAAK,QAAQ,GAAG,iBAAiB;AAC3E,SAAO,KAAK,SAAS,yCAAyC,GAAG,IAAI,MAAM;AAC7E;AAUA,SAAS,eAAe,IAAoB;AAC1C,QAAM,eAAe,QAAQ,IAAI,cAAc,KAAK,KAAK,QAAQ,GAAG,eAAe;AACnF,SAAO,KAAK,cAAc,UAAU,GAAG,EAAE,MAAM;AACjD;AAGO,SAAS,SAAS,OAAuB;AAC9C,SAAO,IAAI,MAAM,QAAQ,MAAM,IAAI,CAAC;AACtC;AAkBO,SAAS,iBACd,MACQ;AACR,QAAM,UAAU,CAAC,KAAK,UAAU,KAAK,UAAU,GAAG,aAAa,IAAI,CAAC,EACjE,IAAI,CAAC,SAAS,IAAI,IAAI,GAAG,EACzB,KAAK,GAAG;AACX,SAAO;AAAA;AAAA;AAAA,IAGL;AAAA,IACA;AAAA,IACA;AAAA,IACA,yBAAyB,SAAS,mBAAmB,KAAK,UAAU,CAAC,CAAC;AAAA,IACtE,UAAU,SAAS,OAAO,CAAC;AAAA,IAC3B;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAEA,eAAe,cAAc,QAAgB,UAAmC;AAE9E,QAAM,UAAU,OAAO,KAAK,QAAQ,SAAS,EAAE,SAAS,QAAQ;AAChE,MAAI;AACF,UAAM,KAAK,kBAAkB,CAAC,cAAc,mBAAmB,mBAAmB,OAAO,CAAC;AAAA,EAC5F,SAAS,KAAK;AACZ,aAAS,KAAK,+BAA0B,IAAc,OAAO,EAAE;AAC/D,UAAM;AAAA,EACR;AACF;AAEA,eAAe,eAAe,MAA+C;AAC3E,QAAM,WAAqB,CAAC;AAC5B,QAAM,MAAM,eAAe,KAAK,IAAI;AAKpC,QAAM,UAAU,KAAK,KAAK,SAAS,iBAAiB;AACpD,QAAM,WAAY,MAAM,OAAO,OAAO,IAAK,UAAU;AACrD,MAAI,CAAC,SAAU,UAAS,KAAK,yBAAsB,OAAO,iCAA2B;AAKrF,QAAM,MAAM,eAAe,KAAK,EAAE;AAClC,QAAM,MAAM,QAAQ,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;AAC7C,QAAM,UAAU,KAAK,iBAAiB,IAAI,GAAG,MAAM;AAEnD,QAAM,UAAU,KAAK,QAAQ,IAAI,YAAY,KAAK,eAAe,YAAY,aAAa;AAE1F,QAAM,SAAS;AAAA,IACb;AAAA,IACA,4BAA4B,QAAQ,GAAG,CAAC;AAAA,IACxC,oBAAoB,QAAQ,OAAO,CAAC;AAAA,IACpC,mBAAmB,QAAQ,IAAI,GAAG,GAAG,CAAC;AAAA,IACtC,0BAA0B,QAAQ,mBAAmB,KAAK,UAAU,CAAC,CAAC;AAAA,IACtE;AAAA;AAAA,IAEA,WAAW,sBAAsB,QAAQ,QAAQ,CAAC,KAAK;AAAA,IACvD;AAAA,EACF,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAE3B,QAAM,MAAM,QAAQ,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;AAC7C,QAAM,cAAc,QAAQ,QAAQ;AAEpC,SAAO,EAAE,OAAO,CAAC,KAAK,GAAG,GAAG,SAAS;AACvC;AAEA,eAAe,iBAAiB,MAAc,IAAqC;AAIjF,QAAM,UAAoB,CAAC;AAC3B,aAAW,QAAQ,CAAC,eAAe,IAAI,GAAG,eAAe,EAAE,CAAC,GAAG;AAC7D,QAAI,CAAE,MAAM,OAAO,IAAI,EAAI;AAC3B,UAAM,GAAG,MAAM,EAAE,OAAO,KAAK,CAAC;AAC9B,YAAQ,KAAK,IAAI;AAAA,EACnB;AACA,SAAO,EAAE,OAAO,SAAS,UAAU,CAAC,EAAE;AACxC;AAgCO,SAAS,qBACd,QAC8F;AAC9F,QAAM,CAAC,UAAU,UAAU,GAAG,IAAI,IAAI;AACtC,MAAI,CAAC,YAAY,CAAC,SAAU,QAAO;AACnC,QAAM,OAAO,CAAC,SAAgC;AAC5C,UAAM,IAAI,KAAK,QAAQ,IAAI;AAC3B,QAAI,MAAM,GAAI,QAAO;AACrB,UAAM,QAAQ,OAAO,KAAK,IAAI,CAAC,CAAC;AAChC,WAAO,OAAO,UAAU,KAAK,KAAK,SAAS,KAAK,SAAS,QAAS,QAAQ;AAAA,EAC5E;AACA,QAAM,OAAO,KAAK,QAAQ;AAC1B,QAAM,YAAY,KAAK,cAAc,MAAM,SAAS,OAAO,OAAO,OAAO;AAEzE,QAAM,eAAe,KAAK,QAAQ,WAAW;AAC7C,QAAM,aAAa,iBAAiB,KAAK,SAAY,KAAK,eAAe,CAAC;AAC1E,QAAM,YAAY,eAAe,UAAa,oBAAoB,UAAU,IAAI,aAAa;AAC7F,SAAO,EAAE,UAAU,UAAU,MAAM,WAAW,UAAU;AAC1D;AAGA,SAAS,UAAU,KAA4B;AAC7C,SAAO,QAAQ,QAAQ,IAAI,OAAO;AACpC;AAMO,SAAS,iBAAiBA,OAAwB;AACvD,QAAM,SAAmB,CAAC;AAC1B,MAAI,UAAU;AACd,MAAI,SAAS;AACb,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,IAAIA,MAAK,QAAQ,KAAK;AACpC,UAAM,KAAKA,MAAK,CAAC;AACjB,QAAI,UAAU,OAAO,QAAQ,IAAI,IAAIA,MAAK,QAAQ;AAAE,iBAAWA,MAAK,EAAE,CAAC;AAAG;AAAA,IAAU;AACpF,QAAI,OAAO,KAAK;AAAE,eAAS,CAAC;AAAQ,gBAAU;AAAM;AAAA,IAAU;AAC9D,QAAI,CAAC,UAAU,OAAO,KAAK;AACzB,UAAI,QAAS,QAAO,KAAK,OAAO;AAChC,gBAAU;AAAI,gBAAU;AACxB;AAAA,IACF;AACA,eAAW;AAAI,cAAU;AAAA,EAC3B;AACA,MAAI,QAAS,QAAO,KAAK,OAAO;AAChC,SAAO;AACT;AAGO,SAAS,kBAAkB,IAAY,MAAc,MAAwC;AAClG,QAAM,OAAO,oBAAI,IAAoB;AACrC,aAAW,QAAQ,KAAK,MAAM,IAAI,GAAG;AACnC,UAAM,KAAK,KAAK,QAAQ,GAAG;AAC3B,QAAI,KAAK,KAAK,CAAC,KAAK,IAAI,KAAK,MAAM,GAAG,EAAE,CAAC,EAAG,MAAK,IAAI,KAAK,MAAM,GAAG,EAAE,GAAG,KAAK,MAAM,KAAK,CAAC,CAAC;AAAA,EAC5F;AACA,MAAI,KAAK,IAAI,kBAAkB,MAAM,OAAQ,QAAO;AACpD,QAAM,UAAU,qBAAqB,iBAAiB,KAAK,IAAI,MAAM,KAAK,EAAE,CAAC;AAC7E,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,WAAW,CAAC,MAAc,EAAE,QAAQ,SAAS,IAAI;AACvD,SAAO;AAAA,IACL;AAAA,IACA,MAAM,SAAS,KAAK,IAAI,MAAM,KAAK,EAAE;AAAA,IACrC,YAAY,KAAK,IAAI,MAAM,IAAI,UAAU,SAAS,KAAK,IAAI,MAAM,CAAE,CAAC,IAAI;AAAA,IACxE,GAAG;AAAA,IACH,OAAO,CAAC,MAAM,WAAW,EAAE,EAAE,IAAI;AAAA,EACnC;AACF;AAEA,eAAe,YAA0C;AACvD,QAAM,EAAE,QAAQ,IAAI,WAAW,EAAE;AACjC,QAAM,QAA6B,CAAC;AACpC,aAAW,QAAQ,MAAM,QAAQ,OAAO,EAAE,MAAM,MAAM,CAAC,CAAa,GAAG;AACrE,QAAI,CAAC,KAAK,SAAS,UAAU,EAAG;AAChC,UAAM,OAAO,KAAK,SAAS,IAAI;AAC/B,UAAM,OAAO,MAAM,SAAS,MAAM,MAAM,EAAE,MAAM,MAAM,EAAE;AACxD,UAAM,WAAW,kBAAkB,KAAK,MAAM,GAAG,CAAC,WAAW,MAAM,GAAG,MAAM,IAAI;AAChF,QAAI,CAAC,SAAU;AAEf,QAAI,CAAE,MAAM,OAAO,SAAS,MAAM,CAAC,CAAE,EAAI,UAAS,MAAM,SAAS;AACjE,UAAM,KAAK,QAAQ;AAAA,EACrB;AACA,SAAO;AACT;AAGA,SAAS,cAAc,MAAwB;AAC7C,QAAM,SAAmB,CAAC;AAC1B,aAAW,SAAS,KAAK,SAAS,sBAAsB,GAAG;AACzD,WAAO,KAAK,MAAM,CAAC,EAAG,QAAQ,UAAU,GAAG,CAAC;AAAA,EAC9C;AACA,SAAO;AACT;AAGO,SAAS,YAAY,OAAe,MAAc,MAAwC;AAC/F,QAAM,MAAM,CAAC,SAAgC;AAC3C,UAAM,IAAI,MAAM,MAAM,IAAI,OAAO,QAAQ,IAAI,oCAAoC,CAAC;AAClF,WAAO,IAAI,EAAE,CAAC,EAAG,QAAQ,SAAS,GAAG,EAAE,QAAQ,SAAS,GAAG,EAAE,QAAQ,UAAU,GAAG,IAAI;AAAA,EACxF;AACA,QAAM,aAAa,IAAI,oBAAoB;AAC3C,QAAM,SAAS;AACf,MAAI,CAAC,YAAY,WAAW,MAAM,EAAG,QAAO;AAE5C,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,QAAM,KAAK,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,KAAK,CAAC;AAChD,QAAM,WAAW,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO,CAAC;AACxD,QAAM,UAAU,WAAW,qBAAqB,cAAc,QAAQ,CAAC,IAAI;AAC3E,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,MAAM,KAAK,cAAc,EAAE,EAAE,CAAC,IAAI;AACxC,SAAO;AAAA,IACL,IAAI,WAAW,MAAM,OAAO,MAAM;AAAA,IAClC,MAAM,IAAI,cAAc,KAAK,WAAW,MAAM,OAAO,MAAM;AAAA,IAC3D,YAAY,MAAM,UAAU,GAAG,IAAI;AAAA,IACnC,GAAG;AAAA,IACH,OAAO,CAAC,IAAI;AAAA,EACd;AACF;AAEA,eAAe,UAAwC;AACrD,QAAM,MAAM,KAAK,QAAQ,GAAG,cAAc;AAC1C,QAAM,QAA6B,CAAC;AACpC,aAAW,SAAS,MAAM,QAAQ,GAAG,EAAE,MAAM,MAAM,CAAC,CAAa,GAAG;AAClE,QAAI,CAAC,MAAM,SAAS,MAAM,EAAG;AAC7B,UAAM,OAAO,KAAK,KAAK,KAAK;AAC5B,UAAM,QAAQ,MAAM,SAAS,KAAK,MAAM,qBAAqB,GAAG,MAAM,EAAE,MAAM,MAAM,EAAE;AACtF,UAAM,MAAM,MAAM,MAAM,4DAA4D,IAAI,CAAC;AACzF,QAAI,CAAC,IAAK;AACV,UAAM,OAAO,MAAM,SAAS,KAAK,MAAM,kBAAkB,GAAG,GAAG,MAAM,EAAE,MAAM,MAAM,EAAE;AACrF,UAAM,WAAW,YAAY,OAAO,MAAM,IAAI;AAC9C,QAAI,SAAU,OAAM,KAAK,QAAQ;AAAA,EACnC;AACA,SAAO;AACT;AAGO,SAAS,SAAS,IAAY,MAAgE;AACnG,QAAM,UAAU,CAAC,WAAkC;AACjD,UAAM,OAAO,KAAK,MAAM,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,WAAW,MAAM,CAAC;AACjE,UAAM,IAAI,MAAM,MAAM,OAAO,MAAM,EAAE,MAAM,mBAAmB;AAC9D,WAAO,IAAI,EAAE,CAAC,EAAG,QAAQ,OAAO,GAAG,IAAI;AAAA,EACzC;AACA,QAAM,MAAM,QAAQ,wBAAwB;AAC5C,QAAM,MAAM,QAAQ,SAAS;AAC7B,MAAI,QAAQ,QAAQ,QAAQ,KAAM,QAAO;AACzC,QAAM,SAAS,CAAC,GAAG,IAAI,SAAS,YAAY,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAE;AAC/D,QAAM,UAAU,qBAAqB,MAAM;AAC3C,SAAO,UAAU,EAAE,IAAI,YAAY,UAAU,GAAG,GAAG,GAAG,QAAQ,IAAI;AACpE;AAEA,eAAe,cAA4C;AACzD,QAAM,SAAS,QAAQ,eAAe,GAAG,CAAC;AAC1C,QAAM,SAAS,QAAQ,eAAe,GAAG,CAAC;AAI1C,QAAM,UAAU,oBAAI,IAAoB;AACxC,aAAW,QAAQ,MAAM,QAAQ,MAAM,EAAE,MAAM,MAAM,CAAC,CAAa,GAAG;AACpE,QAAI,KAAK,YAAY,EAAE,SAAS,MAAM,EAAG,SAAQ,IAAI,mBAAmB,KAAK,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK,MAAM,GAAG,EAAE,CAAC;AAAA,EAC/G;AACA,QAAM,QAA6B,CAAC;AACpC,aAAW,QAAQ,MAAM,QAAQ,MAAM,EAAE,MAAM,MAAM,CAAC,CAAa,GAAG;AACpE,QAAI,CAAC,KAAK,YAAY,EAAE,SAAS,MAAM,EAAG;AAC1C,UAAM,KAAK,KAAK,MAAM,GAAG,EAAE;AAC3B,UAAM,SAAS,SAAS,IAAI,MAAM,SAAS,KAAK,QAAQ,IAAI,GAAG,MAAM,EAAE,MAAM,MAAM,EAAE,CAAC;AACtF,QAAI,CAAC,OAAQ;AACb,UAAM,OAAO,QAAQ,IAAI,EAAE;AAC3B,UAAM,KAAK;AAAA,MACT,GAAG;AAAA;AAAA,MAEH,MAAM,QAAQ;AAAA,MACd,OAAO,CAAC,GAAI,OAAO,CAAC,eAAe,IAAI,CAAC,IAAI,CAAC,GAAI,KAAK,QAAQ,IAAI,CAAC;AAAA,IACrE,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAGA,eAAsB,iBAAiB,UAAgD;AACrF,QAAM,WAAqB,CAAC;AAC5B,MAAI,CAAE,MAAM,OAAO,SAAS,QAAQ,EAAI,UAAS,KAAK,sBAAsB,SAAS,QAAQ,EAAE;AAC/F,MAAI,CAAE,MAAM,OAAO,SAAS,QAAQ,EAAI,UAAS,KAAK,wBAAwB,SAAS,QAAQ,EAAE;AACjG,MAAI,SAAS,cAAc,CAAE,MAAM,OAAO,SAAS,UAAU,EAAI,UAAS,KAAK,yBAAyB,SAAS,UAAU,EAAE;AAC7H,SAAO;AACT;AAIA,eAAsB,uBAAuB,MAA+C;AAC1F,UAAQ,QAAQ,UAAU;AAAA,IACxB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,aAAa,IAAI;AAAA,IAC1B,KAAK;AACH,aAAO,WAAW,IAAI;AAAA,IACxB,KAAK;AACH,aAAO,eAAe,IAAI;AAAA,IAC5B;AACE,YAAM,IAAI,MAAM,iCAA8B,QAAQ,QAAQ,EAAE;AAAA,EACpE;AACF;AAEA,eAAsB,yBAAyB,MAAc,IAAqC;AAChG,UAAQ,QAAQ,UAAU;AAAA,IACxB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,eAAe,EAAE;AAAA,IAC1B,KAAK;AACH,aAAO,aAAa,IAAI;AAAA,IAC1B,KAAK;AACH,aAAO,iBAAiB,MAAM,EAAE;AAAA,IAClC;AACE,YAAM,IAAI,MAAM,iCAA8B,QAAQ,QAAQ,EAAE;AAAA,EACpE;AACF;AAGA,eAAsB,uBAAqD;AACzE,UAAQ,QAAQ,UAAU;AAAA,IACxB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,UAAU;AAAA,IACnB,KAAK;AACH,aAAO,QAAQ;AAAA,IACjB,KAAK;AACH,aAAO,YAAY;AAAA,IACrB;AACE,aAAO,CAAC;AAAA,EACZ;AACF;","names":["exec"]}
|