@runeya/runeya 2.0.145 → 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.
Files changed (116) hide show
  1. package/agent/index.js +26 -21
  2. package/agent/index.js.map +1 -1
  3. package/agent-manager-R7BPQO6D.js +11 -0
  4. package/{chat-XYCHMTFE.js → chat-LPML22J5.js} +8 -7
  5. package/{chunk-NCO4OOYT.js → chunk-4LJQIQMN.js} +2 -2
  6. package/chunk-6IBPK6UE.js +1063 -0
  7. package/chunk-6IBPK6UE.js.map +1 -0
  8. package/{chunk-QF56Z6LI.js → chunk-KV4GJKXU.js} +3 -3
  9. package/{chunk-FE53XX4M.js → chunk-NPMSBYXC.js} +3 -3
  10. package/{chunk-ONVAH65J.js → chunk-SEPAURTQ.js} +283 -1266
  11. package/chunk-SEPAURTQ.js.map +1 -0
  12. package/{chunk-Q4RPY3VW.js → chunk-UD42LACH.js} +25 -11
  13. package/{chunk-Q4RPY3VW.js.map → chunk-UD42LACH.js.map} +1 -1
  14. package/{chunk-KS7A5CTW.js → chunk-VPSQJOSN.js} +12 -2
  15. package/{chunk-KS7A5CTW.js.map → chunk-VPSQJOSN.js.map} +1 -1
  16. package/{companion-HBYSY2D5.js → companion-W3UMZWGH.js} +5 -5
  17. package/external-NMKQPROK.js +387 -0
  18. package/external-NMKQPROK.js.map +1 -0
  19. package/index.js +15 -6
  20. package/index.js.map +1 -1
  21. package/{install-gate-JAEK4BDZ.js → install-gate-PVMGJDWE.js} +2 -2
  22. package/mcp-server/external.d.ts +44 -0
  23. package/mcp-server/external.js +2679 -0
  24. package/mcp-server/external.js.map +1 -0
  25. package/mcp-server/index.js +135 -39
  26. package/mcp-server/index.js.map +1 -1
  27. package/mcp-server/launch.d.ts +19 -1
  28. package/mcp-server/launch.js +20 -0
  29. package/mcp-server/launch.js.map +1 -1
  30. package/package.json +1 -1
  31. package/settings-manager-XPLBO35P.js +10 -0
  32. package/{src-IODFUZ7A.js → src-LEDGW5KP.js} +951 -499
  33. package/src-LEDGW5KP.js.map +1 -0
  34. package/{vault-resolver-PN3TYCLJ.js → vault-resolver-E7KPKA5C.js} +2 -2
  35. package/web/assets/{AgentScalarChatInterface.vue-npQgDxeP.js → AgentScalarChatInterface.vue-BYszptaB.js} +1 -1
  36. package/web/assets/{AgentTerminalPage-BTH3yCS2.js → AgentTerminalPage-C3TJ1NL9.js} +1 -1
  37. package/web/assets/{AiChatModals-It9jnB26.js → AiChatModals-D3scRE3S.js} +1 -1
  38. package/web/assets/{AppLayout-BTg2CADo.js → AppLayout-BMQUjPKe.js} +1 -1
  39. package/web/assets/{AuditPage-2PKKGLZs.js → AuditPage-CKcExBws.js} +1 -1
  40. package/web/assets/{CloudAccountPage-CHL61gLe.js → CloudAccountPage-B6oPg0BZ.js} +1 -1
  41. package/web/assets/{CloudMessagingPage-cgKnABPH.js → CloudMessagingPage-C5fwVe-F.js} +1 -1
  42. package/web/assets/{DashboardPage-CRntTMKY.js → DashboardPage-9RV7sx3e.js} +1 -1
  43. package/web/assets/{DirectoryPickerDialog-DCyhT3-W.js → DirectoryPickerDialog-C06BXyfJ.js} +1 -1
  44. package/web/assets/{EnvironmentForm-C96PFWRE.js → EnvironmentForm-CAvDoira.js} +1 -1
  45. package/web/assets/{EnvironmentFormPage-ChorAd63.js → EnvironmentFormPage-BmAxhN-m.js} +1 -1
  46. package/web/assets/{EnvironmentListPage-Dw9ziaI8.js → EnvironmentListPage-B8uATPBI.js} +1 -1
  47. package/web/assets/{KanbanBoardPage-BZHbEj-q.js → KanbanBoardPage-CQsOkRUf.js} +3 -3
  48. package/web/assets/{KanbanListPage-B0jlfKqs.js → KanbanListPage-CWZqkjD6.js} +1 -1
  49. package/web/assets/{LaunchProjectPage-BVs1JwbV.js → LaunchProjectPage-saSmC7Pr.js} +1 -1
  50. package/web/assets/{LaunchServicesPage-PBYJly9N.js → LaunchServicesPage-BkKyLMEz.js} +1 -1
  51. package/web/assets/{LaunchShell-CGNYr-do.js → LaunchShell-DUrSunB4.js} +1 -1
  52. package/web/assets/{LogViewer-BDObx29F.js → LogViewer-CZBYkH9J.js} +1 -1
  53. package/web/assets/{OrgKeyPrompt-BbcwiwGw.js → OrgKeyPrompt-DzQ7pwbs.js} +1 -1
  54. package/web/assets/{ProcessControls-DY0HTKde.js → ProcessControls-BEeoNlNV.js} +1 -1
  55. package/web/assets/{ProcessDetailPage-BZ9kIQqn.js → ProcessDetailPage-CQgu5uKm.js} +1 -1
  56. package/web/assets/{ProjectCreateDialog-C1wVDPcK.js → ProjectCreateDialog-BolrhRD-.js} +1 -1
  57. package/web/assets/{ProjectLocationGate-bUd_tFpI.js → ProjectLocationGate-DhMVdIjN.js} +1 -1
  58. package/web/assets/{ProjectPublishDialog-APUS1WEN.js → ProjectPublishDialog-DsZNWrfz.js} +1 -1
  59. package/web/assets/{RandomJobTestPage-BFNC827R.js → RandomJobTestPage-BUS2Ty6-.js} +1 -1
  60. package/web/assets/{ScenarioEditorPage-DFVsMVu5.js → ScenarioEditorPage-MIAGh2us.js} +1 -1
  61. package/web/assets/{SendToChatDialog-QHIkxNio.js → SendToChatDialog-BxWsyv29.js} +1 -1
  62. package/web/assets/{ServiceControlsBar-_UQXb6g0.js → ServiceControlsBar-DqDPAZt7.js} +1 -1
  63. package/web/assets/{ServiceDetailPage-DGIXgHwR.js → ServiceDetailPage-hkSAzZ-l.js} +1 -1
  64. package/web/assets/{ServiceFormPage-RliAfS3_.js → ServiceFormPage-DXqhqXg8.js} +1 -1
  65. package/web/assets/SettingsPage-4pKUgKPm.css +1 -0
  66. package/web/assets/SettingsPage-DS2YhMIM.js +2086 -0
  67. package/web/assets/{UnlockPage-17EgfpJs.js → UnlockPage-s8SgVcKk.js} +1 -1
  68. package/web/assets/{VaultMigrationPage-DeMxpXXJ.js → VaultMigrationPage-s0b44MDM.js} +1 -1
  69. package/web/assets/{VersionBadge-DBquJl76.js → VersionBadge-BmArk24p.js} +1 -1
  70. package/web/assets/{WikiPage-GtwpJPqf.js → WikiPage-DADu8WUA.js} +1 -1
  71. package/web/assets/{WikiView-BqaqxMxW.js → WikiView-DtGwIt_Q.js} +1 -1
  72. package/web/assets/{add-scalar-classes-DhSSqZ5l.js → add-scalar-classes-dWEWPlFD.js} +1 -1
  73. package/web/assets/{agent.store-Co44dMZf.js → agent.store-CKLAkF9a.js} +1 -1
  74. package/web/assets/{auth-Bk1GP5sB.js → auth-Cg9BWTfK.js} +2 -2
  75. package/web/assets/{cloud-chat.store-CadB14dZ.js → cloud-chat.store-b-BHGz7I.js} +1 -1
  76. package/web/assets/{cloud-client-0zD_NI6q.js → cloud-client-D_zCiW0s.js} +2 -2
  77. package/web/assets/{cloud-session-Cjw54Y--.js → cloud-session-DOaDpZTy.js} +2 -2
  78. package/web/assets/{editor.store-DmM6ZlTm.js → editor.store-DCAZSeVG.js} +1 -1
  79. package/web/assets/i18n-D0afQEGs.js +1 -0
  80. package/web/assets/{index-B9DJf2V0.js → index-D8VRbyoP.js} +36 -36
  81. package/web/assets/index-DsezNbSM.css +1 -0
  82. package/web/assets/{modal-UK8xRSnh.js → modal-qYgTMkYb.js} +1 -1
  83. package/web/assets/{project-launch.store-B5nhdGdh.js → project-launch.store-y7JBMNiE.js} +1 -1
  84. package/web/assets/{project.store-Bm2JWNbd.js → project.store-DGJr0Qr9.js} +1 -1
  85. package/web/assets/{scenario.store-88w6P6s2.js → scenario.store-YWHL2_wu.js} +3 -3
  86. package/web/assets/{service.store-XS50iGKo.js → service.store-5jWsJxPx.js} +1 -1
  87. package/web/assets/{settings-scope-CKPq86HZ.js → settings-scope-Bv7QGPrt.js} +1 -1
  88. package/web/assets/{settings.store-BCJ0LJQA.js → settings.store-bEsB7oKb.js} +1 -1
  89. package/web/assets/{src-DBipSD56.js → src-C-D_y7ou.js} +1 -1
  90. package/web/assets/{src-Cgix2VOi.js → src-Dnwk6NpV.js} +2 -2
  91. package/web/assets/{src-BDZKZr9n.css → src-g1DRo8xZ.css} +1 -1
  92. package/web/assets/{trpc-CfuFkKgp.js → trpc-Drabzwpd.js} +3 -3
  93. package/web/assets/{useCloudOrg-J8DAnocE.js → useCloudOrg-BAQEtFPl.js} +1 -1
  94. package/web/assets/{useEnvVarAutocomplete-C6zOXYqi.js → useEnvVarAutocomplete-BqGTODUv.js} +1 -1
  95. package/web/assets/{useProjectCatalog-BB5IR6o8.js → useProjectCatalog-yocjIKt6.js} +1 -1
  96. package/web/assets/{useProjectCloudOrg-BnlKts7x.js → useProjectCloudOrg-CEN9-_OA.js} +1 -1
  97. package/web/assets/{useServiceUrlHost-hKbKtPMu.js → useServiceUrlHost-BuoPEXZX.js} +1 -1
  98. package/web/assets/{vue-draggable-plus-D5ni4jMk.js → vue-draggable-plus-DX4rTBiu.js} +1 -1
  99. package/web/index.html +17 -17
  100. package/agent-manager-THV3LJOF.js +0 -11
  101. package/chunk-ONVAH65J.js.map +0 -1
  102. package/settings-manager-QVQRJO6V.js +0 -10
  103. package/src-IODFUZ7A.js.map +0 -1
  104. package/web/assets/SettingsPage-BSSHxxjM.js +0 -2086
  105. package/web/assets/SettingsPage-W8YszhUL.css +0 -1
  106. package/web/assets/i18n-8FjeJaKW.js +0 -1
  107. package/web/assets/index-teBqdnuv.css +0 -1
  108. /package/{agent-manager-THV3LJOF.js.map → agent-manager-R7BPQO6D.js.map} +0 -0
  109. /package/{chat-XYCHMTFE.js.map → chat-LPML22J5.js.map} +0 -0
  110. /package/{chunk-NCO4OOYT.js.map → chunk-4LJQIQMN.js.map} +0 -0
  111. /package/{chunk-QF56Z6LI.js.map → chunk-KV4GJKXU.js.map} +0 -0
  112. /package/{chunk-FE53XX4M.js.map → chunk-NPMSBYXC.js.map} +0 -0
  113. /package/{companion-HBYSY2D5.js.map → companion-W3UMZWGH.js.map} +0 -0
  114. /package/{install-gate-JAEK4BDZ.js.map → install-gate-PVMGJDWE.js.map} +0 -0
  115. /package/{settings-manager-QVQRJO6V.js.map → settings-manager-XPLBO35P.js.map} +0 -0
  116. /package/{vault-resolver-PN3TYCLJ.js.map → vault-resolver-E7KPKA5C.js.map} +0 -0
@@ -19,6 +19,7 @@ import {
19
19
  applyPersistedLanMode,
20
20
  attachServiceToProject,
21
21
  auditService,
22
+ buildKanbanCardPrompt,
22
23
  buildServerBuiltins,
23
24
  canEnvAction,
24
25
  chatRouter,
@@ -49,6 +50,8 @@ import {
49
50
  readCloudIdentity,
50
51
  registerHttpServer,
51
52
  requireEnvAction,
53
+ requireHuman,
54
+ requireLoopback,
52
55
  requireLoopbackOrToken,
53
56
  requireServiceAction,
54
57
  resolveEffectiveAgentId,
@@ -62,10 +65,11 @@ import {
62
65
  soleAccountScope,
63
66
  startCliSession,
64
67
  vaultOfProject
65
- } from "./chunk-ONVAH65J.js";
68
+ } from "./chunk-SEPAURTQ.js";
69
+ import "./chunk-6IBPK6UE.js";
66
70
  import {
67
71
  settingsManager
68
- } from "./chunk-FE53XX4M.js";
72
+ } from "./chunk-NPMSBYXC.js";
69
73
  import {
70
74
  CONVERSATIONS_DIRNAME,
71
75
  LocalProjectRefusedError,
@@ -83,13 +87,14 @@ import {
83
87
  resolveAgentCwd,
84
88
  resolveConversationCwd,
85
89
  resolveWorkspaceCwd,
90
+ setMcpClientCheck,
86
91
  signJwt,
87
92
  startClaudeTranscriptWatcher,
88
93
  startCodexTranscriptWatcher,
89
94
  stopClaudeTranscriptWatcher,
90
95
  stopCodexTranscriptWatcher,
91
96
  verifyJwt
92
- } from "./chunk-Q4RPY3VW.js";
97
+ } from "./chunk-UD42LACH.js";
93
98
  import {
94
99
  agentManager,
95
100
  agentSpawner,
@@ -128,10 +133,11 @@ import {
128
133
  stableEncryptValue,
129
134
  traefikManager,
130
135
  unregisterInstance
131
- } from "./chunk-QF56Z6LI.js";
136
+ } from "./chunk-KV4GJKXU.js";
132
137
  import {
133
138
  decryptValueWith,
134
139
  encryptValueWith,
140
+ isCliAvailable,
135
141
  resolveCliInvocation,
136
142
  whichCommand
137
143
  } from "./chunk-OZBSOSAS.js";
@@ -140,7 +146,7 @@ import {
140
146
  renameWithRetry,
141
147
  resolveConversationRunDir,
142
148
  sweepStaleTempFiles
143
- } from "./chunk-NCO4OOYT.js";
149
+ } from "./chunk-4LJQIQMN.js";
144
150
  import "./chunk-MD3VW4HQ.js";
145
151
  import {
146
152
  allVaultRoots,
@@ -148,6 +154,7 @@ import {
148
154
  env,
149
155
  forgetOrgLogo,
150
156
  keyRoot,
157
+ keyedFilePath,
151
158
  legacyMachineFilePath,
152
159
  legacyVaultRoot,
153
160
  listCloudVaults,
@@ -167,7 +174,7 @@ import {
167
174
  rememberOrgName,
168
175
  stampOrgCloud,
169
176
  vaultFor
170
- } from "./chunk-KS7A5CTW.js";
177
+ } from "./chunk-VPSQJOSN.js";
171
178
  import {
172
179
  AGENT_LONG_FETCH_TIMEOUT,
173
180
  AGENT_STOP_FETCH_TIMEOUT,
@@ -211,7 +218,7 @@ import {
211
218
  import express from "express";
212
219
  import cors from "cors";
213
220
  import { createServer } from "http";
214
- import { join as join17, resolve as resolve3 } from "path";
221
+ import { join as join18, resolve as resolve4 } from "path";
215
222
  import { WebSocketServer } from "ws";
216
223
  import { createExpressMiddleware } from "@trpc/server/adapters/express";
217
224
  import { applyWSSHandler } from "@trpc/server/adapters/ws";
@@ -226,7 +233,16 @@ import { z } from "zod";
226
233
  import { execFile } from "child_process";
227
234
  import { promisify } from "util";
228
235
  import { existsSync, mkdtempSync, readFileSync, rmdirSync } from "fs";
229
- import { dirname, isAbsolute, join, relative } from "path";
236
+ import { dirname, join } from "path";
237
+
238
+ // ../server/src/utils/is-inside.ts
239
+ import { isAbsolute, relative } from "path";
240
+ function isInside(dir, path) {
241
+ const rel = relative(dir, path);
242
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
243
+ }
244
+
245
+ // ../server/src/services/npm-cli.ts
230
246
  var execFileAsync = promisify(execFile);
231
247
  var PACKAGE_NAME = "@runeya/runeya";
232
248
  var INSTALL_TIMEOUT_MS = 10 * 6e4;
@@ -332,10 +348,6 @@ function installPrefix(globalPrefix, platform = process.platform) {
332
348
  const modules = platform === "win32" ? join(globalPrefix, "node_modules") : join(globalPrefix, "lib", "node_modules");
333
349
  return isWritable(modules) ? globalPrefix : userPrefix();
334
350
  }
335
- function isInside(dir, path) {
336
- const rel = relative(dir, path);
337
- return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
338
- }
339
351
  function updatePrefix(globalPrefix, runningEntry) {
340
352
  if (isInside(userPrefix(), runningEntry)) return userPrefix();
341
353
  if (!globalPrefix) return null;
@@ -642,6 +654,23 @@ function onCurrentProjectChanged(listener) {
642
654
  return () => listeners.delete(listener);
643
655
  }
644
656
 
657
+ // ../server/src/services/project-containing.ts
658
+ import { resolve } from "path";
659
+ function projectContaining(paths, path) {
660
+ const target = resolve(path);
661
+ let best = null;
662
+ for (const [key, dir] of Object.entries(paths)) {
663
+ const root = resolve(dir);
664
+ if (root !== target && !isInside(root, target)) continue;
665
+ if (!best || root.length > best.depth) best = { key, depth: root.length };
666
+ }
667
+ if (!best) return null;
668
+ const sep2 = best.key.indexOf(":");
669
+ if (sep2 === -1) return { id: best.key };
670
+ const owner = best.key.slice(0, sep2);
671
+ return { id: best.key.slice(sep2 + 1), orgId: owner === "local" ? null : owner };
672
+ }
673
+
645
674
  // ../server/src/services/org-keys.ts
646
675
  import { mkdir } from "fs/promises";
647
676
 
@@ -950,6 +979,13 @@ async function assertReadable(orgId) {
950
979
  throw err;
951
980
  }
952
981
  }
982
+ async function localProjectPaths() {
983
+ const paths = await projectPathStore.all();
984
+ for (const project of await projectStore.list()) {
985
+ if (!project.orgId) paths[`local:${project.id}`] = dirname2(vaultFor(project).root);
986
+ }
987
+ return paths;
988
+ }
953
989
  var projectRouter = router({
954
990
  list: protectedProcedure.query(async () => {
955
991
  return projectStore.list();
@@ -1231,12 +1267,16 @@ var projectRouter = router({
1231
1267
  * Le dossier de chaque projet sur cette machine, par clé `org:projet`
1232
1268
  * (`local:<id>` pour un projet local, dont le dossier contient son `.runeya`).
1233
1269
  */
1234
- localPaths: protectedProcedure.query(async () => {
1235
- const paths = await projectPathStore.all();
1236
- for (const project of await projectStore.list()) {
1237
- if (!project.orgId) paths[`local:${project.id}`] = dirname2(vaultFor(project).root);
1238
- }
1239
- return paths;
1270
+ localPaths: protectedProcedure.query(() => localProjectPaths()),
1271
+ /**
1272
+ * Le projet dont le dossier contient ce chemin, ou `null`. Sert d'ancrage à
1273
+ * un outil IA lancé hors de Runeya (`runeya mcp`), qui sait seulement dans
1274
+ * quel dossier il tourne.
1275
+ */
1276
+ containing: protectedProcedure.input(z5.object({ path: z5.string().min(1).max(4096) })).query(async ({ input }) => {
1277
+ const ref = projectContaining(await localProjectPaths(), input.path);
1278
+ const project = ref ? await projectStore.get(ref).catch(() => null) : null;
1279
+ return project ? { projectId: project.id, orgId: project.orgId ?? null, name: project.name } : null;
1240
1280
  }),
1241
1281
  getLocalPath: protectedProcedure.input(z5.object({
1242
1282
  id: z5.string().min(1).max(128),
@@ -1334,8 +1374,8 @@ var projectRouter = router({
1334
1374
  touched.clear();
1335
1375
  yield { collections };
1336
1376
  } else {
1337
- await new Promise((resolve4) => {
1338
- wake = resolve4;
1377
+ await new Promise((resolve5) => {
1378
+ wake = resolve5;
1339
1379
  });
1340
1380
  }
1341
1381
  }
@@ -1483,13 +1523,13 @@ var agentRouter = router({
1483
1523
  yield { agentId: agent.config.id, status: agent.status };
1484
1524
  }
1485
1525
  const queue = [];
1486
- let resolve4 = null;
1526
+ let resolve5 = null;
1487
1527
  const handler = (event) => {
1488
1528
  if (queue.length >= SUBSCRIPTION_QUEUE_MAX) queue.shift();
1489
1529
  queue.push(event);
1490
- if (resolve4) {
1491
- resolve4();
1492
- resolve4 = null;
1530
+ if (resolve5) {
1531
+ resolve5();
1532
+ resolve5 = null;
1493
1533
  }
1494
1534
  };
1495
1535
  agentManager.on("status", handler);
@@ -1499,7 +1539,7 @@ var agentRouter = router({
1499
1539
  yield queue.shift();
1500
1540
  } else {
1501
1541
  await new Promise((r) => {
1502
- resolve4 = r;
1542
+ resolve5 = r;
1503
1543
  });
1504
1544
  }
1505
1545
  }
@@ -1618,14 +1658,14 @@ async function listAllProcesses() {
1618
1658
  var SUBSCRIPTION_QUEUE_MAX2 = LOG_BUFFER_SIZE;
1619
1659
  async function* forwardEvents(eventName, filter) {
1620
1660
  const queue = [];
1621
- let resolve4 = null;
1661
+ let resolve5 = null;
1622
1662
  const handler = (data) => {
1623
1663
  if (filter && !filter(data)) return;
1624
1664
  if (queue.length >= SUBSCRIPTION_QUEUE_MAX2) queue.shift();
1625
1665
  queue.push(data);
1626
- if (resolve4) {
1627
- resolve4();
1628
- resolve4 = null;
1666
+ if (resolve5) {
1667
+ resolve5();
1668
+ resolve5 = null;
1629
1669
  }
1630
1670
  };
1631
1671
  agentManager.on(eventName, handler);
@@ -1635,7 +1675,7 @@ async function* forwardEvents(eventName, filter) {
1635
1675
  yield queue.shift();
1636
1676
  } else {
1637
1677
  await new Promise((r) => {
1638
- resolve4 = r;
1678
+ resolve5 = r;
1639
1679
  });
1640
1680
  }
1641
1681
  }
@@ -1645,7 +1685,7 @@ async function* forwardEvents(eventName, filter) {
1645
1685
  }
1646
1686
  async function* forwardLogBatches(agentId, processId) {
1647
1687
  const queue = [];
1648
- let resolve4 = null;
1688
+ let resolve5 = null;
1649
1689
  const handler = (data) => {
1650
1690
  if (data.agentId !== agentId) return;
1651
1691
  const batch = data.batch;
@@ -1654,9 +1694,9 @@ async function* forwardLogBatches(agentId, processId) {
1654
1694
  if (filtered.length === 0) return;
1655
1695
  if (queue.length >= SUBSCRIPTION_QUEUE_MAX2) queue.shift();
1656
1696
  queue.push(filtered);
1657
- if (resolve4) {
1658
- resolve4();
1659
- resolve4 = null;
1697
+ if (resolve5) {
1698
+ resolve5();
1699
+ resolve5 = null;
1660
1700
  }
1661
1701
  };
1662
1702
  agentManager.on("process:log", handler);
@@ -1666,7 +1706,7 @@ async function* forwardLogBatches(agentId, processId) {
1666
1706
  yield queue.shift();
1667
1707
  } else {
1668
1708
  await new Promise((r) => {
1669
- resolve4 = r;
1709
+ resolve5 = r;
1670
1710
  });
1671
1711
  }
1672
1712
  }
@@ -2688,8 +2728,8 @@ var settingsRouter = router({
2688
2728
  pending = false;
2689
2729
  yield listWithOrigin(await logParserStore.list(vault), vault);
2690
2730
  } else {
2691
- await new Promise((resolve4) => {
2692
- wake = resolve4;
2731
+ await new Promise((resolve5) => {
2732
+ wake = resolve5;
2693
2733
  });
2694
2734
  }
2695
2735
  }
@@ -3046,7 +3086,7 @@ var settingsRouter = router({
3046
3086
  }
3047
3087
  } catch {
3048
3088
  }
3049
- await new Promise((resolve4) => setTimeout(resolve4, 500));
3089
+ await new Promise((resolve5) => setTimeout(resolve5, 500));
3050
3090
  }
3051
3091
  }),
3052
3092
  /**
@@ -3233,7 +3273,7 @@ import { z as z13 } from "zod";
3233
3273
  import { TRPCError as TRPCError9 } from "@trpc/server";
3234
3274
  import { execFile as execFile3, spawn as spawn2 } from "child_process";
3235
3275
  import { stat as stat2 } from "fs/promises";
3236
- import { resolve, join as join4 } from "path";
3276
+ import { resolve as resolve2, join as join4 } from "path";
3237
3277
  import { homedir as homedir2 } from "os";
3238
3278
 
3239
3279
  // ../server/src/services/user-config-manager.ts
@@ -3328,9 +3368,9 @@ async function getServiceCwd(serviceId) {
3328
3368
  return resolveCwd(await resolveWorkspaceCwd({ serviceId }));
3329
3369
  }
3330
3370
  async function isCommandAvailable(command) {
3331
- return new Promise((resolve4) => {
3371
+ return new Promise((resolve5) => {
3332
3372
  execFile3(whichCommand(), [command], { timeout: 5e3, windowsHide: true }, (err) => {
3333
- resolve4(!err);
3373
+ resolve5(!err);
3334
3374
  });
3335
3375
  });
3336
3376
  }
@@ -3401,7 +3441,7 @@ var editorRouter = router({
3401
3441
  throw new TRPCError9({ code: "BAD_REQUEST", message: `Unknown editor: ${input.editorKey}` });
3402
3442
  }
3403
3443
  const expanded = input.path.replace(/^~(\/|$)/, `${process.env["HOME"] ?? "/tmp"}$1`).replace(/\$\{(\w+)\}|\$(\w+)/g, (_, a, b) => process.env[a || b] ?? "");
3404
- const resolvedPath = resolve(expanded || ".");
3444
+ const resolvedPath = resolve2(expanded || ".");
3405
3445
  try {
3406
3446
  await stat2(resolvedPath);
3407
3447
  } catch (err) {
@@ -3708,7 +3748,7 @@ Rules:
3708
3748
  onStatusChange: protectedProcedure.input(z15.object({ ...gitTargetShape })).subscription(async function* ({ input }) {
3709
3749
  const { agentId, cwd } = await resolveGitContext(input);
3710
3750
  const queue = [];
3711
- let resolve4 = null;
3751
+ let resolve5 = null;
3712
3752
  let done = false;
3713
3753
  let unsubscribe;
3714
3754
  try {
@@ -3718,16 +3758,16 @@ Rules:
3718
3758
  { cwd },
3719
3759
  (data) => {
3720
3760
  queue.push(data);
3721
- if (resolve4) {
3722
- resolve4();
3723
- resolve4 = null;
3761
+ if (resolve5) {
3762
+ resolve5();
3763
+ resolve5 = null;
3724
3764
  }
3725
3765
  },
3726
3766
  () => {
3727
3767
  done = true;
3728
- if (resolve4) {
3729
- resolve4();
3730
- resolve4 = null;
3768
+ if (resolve5) {
3769
+ resolve5();
3770
+ resolve5 = null;
3731
3771
  }
3732
3772
  }
3733
3773
  );
@@ -3743,7 +3783,7 @@ Rules:
3743
3783
  yield queue.shift();
3744
3784
  } else {
3745
3785
  await new Promise((r) => {
3746
- resolve4 = r;
3786
+ resolve5 = r;
3747
3787
  });
3748
3788
  }
3749
3789
  }
@@ -4091,21 +4131,21 @@ async function* tapBus(conversationId, signal) {
4091
4131
  }
4092
4132
  if (bus.done) return;
4093
4133
  if (signal.aborted) return;
4094
- await new Promise((resolve4) => {
4134
+ await new Promise((resolve5) => {
4095
4135
  const onAbort = () => {
4096
4136
  bus.listeners.delete(notify);
4097
- resolve4();
4137
+ resolve5();
4098
4138
  };
4099
4139
  const notify = () => {
4100
4140
  signal.removeEventListener("abort", onAbort);
4101
- resolve4();
4141
+ resolve5();
4102
4142
  };
4103
4143
  bus.listeners.add(notify);
4104
4144
  signal.addEventListener("abort", onAbort, { once: true });
4105
4145
  if (cursor < bus.baseIndex + bus.buffer.length || bus.done || signal.aborted) {
4106
4146
  bus.listeners.delete(notify);
4107
4147
  signal.removeEventListener("abort", onAbort);
4108
- resolve4();
4148
+ resolve5();
4109
4149
  }
4110
4150
  });
4111
4151
  }
@@ -4615,9 +4655,9 @@ async function runNodeCall(conversationId, node, systemContext, userMessage, run
4615
4655
  publish2({ type: "node_waiting_input", nodeId: node.id, question: result.tokenMessage });
4616
4656
  await readme?.(`- \u2753 [${formatTs()}] **${node.label}** \u2014 waiting for user input
4617
4657
  `);
4618
- const answer = await new Promise((resolve4, reject) => {
4658
+ const answer = await new Promise((resolve5, reject) => {
4619
4659
  const key = pendingNodeKey(conversationId, node.id);
4620
- pendingNodeInputs.set(key, resolve4);
4660
+ pendingNodeInputs.set(key, resolve5);
4621
4661
  signal.addEventListener("abort", () => {
4622
4662
  pendingNodeInputs.delete(key);
4623
4663
  reject(new Error("Aborted"));
@@ -5022,9 +5062,9 @@ ${outputs.get(node.id) ?? ""}`.trim();
5022
5062
  publish2({ type: "node_waiting_input", nodeId: node.id, question });
5023
5063
  await readme(`- \u2753 [${ts()}] **${node.label || node.id}** \u2014 waiting for user input
5024
5064
  `);
5025
- const answer = await new Promise((resolve4, reject) => {
5065
+ const answer = await new Promise((resolve5, reject) => {
5026
5066
  const key = pendingNodeKey(conversationId, node.id);
5027
- pendingNodeInputs.set(key, resolve4);
5067
+ pendingNodeInputs.set(key, resolve5);
5028
5068
  signal.addEventListener("abort", () => {
5029
5069
  pendingNodeInputs.delete(key);
5030
5070
  reject(new Error("Aborted"));
@@ -5577,21 +5617,21 @@ async function* tapJob(job, signal) {
5577
5617
  yield evt;
5578
5618
  }
5579
5619
  if (job.done || signal.aborted) return;
5580
- await new Promise((resolve4) => {
5620
+ await new Promise((resolve5) => {
5581
5621
  const onAbort = () => {
5582
5622
  job.listeners.delete(notify);
5583
- resolve4();
5623
+ resolve5();
5584
5624
  };
5585
5625
  const notify = () => {
5586
5626
  signal.removeEventListener("abort", onAbort);
5587
- resolve4();
5627
+ resolve5();
5588
5628
  };
5589
5629
  job.listeners.add(notify);
5590
5630
  signal.addEventListener("abort", onAbort, { once: true });
5591
5631
  if (cursor < job.baseIndex + job.buffer.length || job.done || signal.aborted) {
5592
5632
  job.listeners.delete(notify);
5593
5633
  signal.removeEventListener("abort", onAbort);
5594
- resolve4();
5634
+ resolve5();
5595
5635
  }
5596
5636
  });
5597
5637
  }
@@ -5743,9 +5783,9 @@ var diagnosticsRouter = router({
5743
5783
  try {
5744
5784
  while (!signal?.aborted) {
5745
5785
  if (queue.length === 0) {
5746
- await new Promise((resolve4) => {
5747
- wake = resolve4;
5748
- signal?.addEventListener("abort", () => resolve4(), { once: true });
5786
+ await new Promise((resolve5) => {
5787
+ wake = resolve5;
5788
+ signal?.addEventListener("abort", () => resolve5(), { once: true });
5749
5789
  });
5750
5790
  wake = null;
5751
5791
  continue;
@@ -5817,11 +5857,395 @@ var diagnosticsRouter = router({
5817
5857
  })
5818
5858
  });
5819
5859
 
5820
- // ../server/src/trpc/routers/kanban-queue.ts
5860
+ // ../server/src/trpc/routers/mcp-pairing.ts
5861
+ import { z as z20 } from "zod";
5862
+ import { TRPCError as TRPCError17 } from "@trpc/server";
5863
+
5864
+ // ../server/src/services/mcp-pairing.ts
5865
+ import { createHash, randomBytes as randomBytes2, randomInt, randomUUID as randomUUID5, timingSafeEqual } from "crypto";
5866
+ import { readFile as readFile3 } from "fs/promises";
5821
5867
  import { z as z19 } from "zod";
5868
+ var REQUEST_TTL_MS = 2 * 60 * 1e3;
5869
+ var MCP_TOKEN_TTL = "15m";
5870
+ var FILE = "mcp-clients.json";
5871
+ var McpClientSchema = z19.object({
5872
+ id: z19.string().uuid(),
5873
+ name: z19.string().min(1).max(100),
5874
+ cwd: z19.string().max(4096),
5875
+ /** SHA-256 du secret, en hexadécimal. */
5876
+ secretHash: z19.string().regex(/^[0-9a-f]{64}$/),
5877
+ createdAt: z19.string().max(64)
5878
+ });
5879
+ var McpClientsFileSchema = z19.object({ clients: z19.array(McpClientSchema).max(500) });
5880
+ function hashSecret(secret) {
5881
+ return createHash("sha256").update(secret).digest("hex");
5882
+ }
5883
+ var McpPairing = class {
5884
+ constructor(filePath, now = Date.now) {
5885
+ this.filePath = filePath;
5886
+ this.now = now;
5887
+ }
5888
+ requests = /* @__PURE__ */ new Map();
5889
+ /**
5890
+ * Relu à chaque accès, sans copie en mémoire. Plusieurs Runeya d'un poste
5891
+ * partagent ce fichier : une copie mémoire ferait réécrire à l'une la liste
5892
+ * d'avant l'accord donné par l'autre, et cacherait ses révocations. Le
5893
+ * fichier tient en quelques lignes ; le relire coûte moins qu'un tel oubli.
5894
+ */
5895
+ async load() {
5896
+ const path = await this.filePath();
5897
+ let text;
5898
+ try {
5899
+ text = await readFile3(path, "utf8");
5900
+ } catch (err) {
5901
+ if (err.code === "ENOENT") return [];
5902
+ throw err;
5903
+ }
5904
+ const unreadable = () => new Error(`${path} est illisible : les acc\xE8s MCP ne sont pas modifi\xE9s.`);
5905
+ let raw;
5906
+ try {
5907
+ raw = JSON.parse(text);
5908
+ } catch {
5909
+ throw unreadable();
5910
+ }
5911
+ const parsed = McpClientsFileSchema.safeParse(raw);
5912
+ if (!parsed.success) throw unreadable();
5913
+ return parsed.data.clients;
5914
+ }
5915
+ async save(clients) {
5916
+ await atomicWriteSecure(await this.filePath(), JSON.stringify({ clients }, null, 2));
5917
+ }
5918
+ sweep() {
5919
+ for (const [id, r] of this.requests) {
5920
+ if (this.now() - r.createdAt > REQUEST_TTL_MS) this.requests.delete(id);
5921
+ }
5922
+ }
5923
+ toPublic(r) {
5924
+ return { id: r.id, name: r.name, cwd: r.cwd, code: r.code, expiresAt: r.createdAt + REQUEST_TTL_MS };
5925
+ }
5926
+ /** Une demande d'accès. Refusée tant qu'une autre attend une réponse. */
5927
+ request(name, cwd) {
5928
+ this.sweep();
5929
+ if ([...this.requests.values()].some((r2) => r2.status === "pending")) {
5930
+ throw new Error("Une autre demande d'acc\xE8s attend d\xE9j\xE0 une r\xE9ponse dans Runeya.");
5931
+ }
5932
+ const r = {
5933
+ id: randomUUID5(),
5934
+ name,
5935
+ cwd,
5936
+ code: String(randomInt(0, 1e4)).padStart(4, "0"),
5937
+ createdAt: this.now(),
5938
+ status: "pending"
5939
+ };
5940
+ this.requests.set(r.id, r);
5941
+ return this.toPublic(r);
5942
+ }
5943
+ /**
5944
+ * Où en est une demande. Le secret n'est rendu qu'une fois : la demande est
5945
+ * oubliée dès qu'il est remis.
5946
+ */
5947
+ status(requestId) {
5948
+ this.sweep();
5949
+ const r = this.requests.get(requestId);
5950
+ if (!r) return { status: "expired" };
5951
+ if (r.status === "approved") {
5952
+ this.requests.delete(requestId);
5953
+ return { status: "approved", secret: r.secret };
5954
+ }
5955
+ if (r.status === "denied") this.requests.delete(requestId);
5956
+ return { status: r.status };
5957
+ }
5958
+ pending() {
5959
+ this.sweep();
5960
+ return [...this.requests.values()].filter((r) => r.status === "pending").map((r) => this.toPublic(r));
5961
+ }
5962
+ async decide(requestId, approve) {
5963
+ this.sweep();
5964
+ const r = this.requests.get(requestId);
5965
+ if (!r || r.status !== "pending") throw new Error("Cette demande n'existe plus ou a expir\xE9.");
5966
+ if (!approve) {
5967
+ r.status = "denied";
5968
+ return;
5969
+ }
5970
+ const id = randomUUID5();
5971
+ const secret = `${id}.${randomBytes2(32).toString("hex")}`;
5972
+ const clients = await this.load();
5973
+ await this.save([...clients, {
5974
+ id,
5975
+ name: r.name,
5976
+ cwd: r.cwd,
5977
+ secretHash: hashSecret(secret),
5978
+ createdAt: new Date(this.now()).toISOString()
5979
+ }]);
5980
+ r.status = "approved";
5981
+ r.secret = secret;
5982
+ }
5983
+ /** Échange le secret d'un client appairé contre un jeton court. */
5984
+ async exchange(secret) {
5985
+ const id = secret.split(".")[0] ?? "";
5986
+ const client = (await this.load()).find((c) => c.id === id);
5987
+ const expected = Buffer.from(client?.secretHash ?? "0".repeat(64), "hex");
5988
+ const match = timingSafeEqual(expected, Buffer.from(hashSecret(secret), "hex"));
5989
+ if (!client || !match) throw new Error("Appairage inconnu ou r\xE9voqu\xE9.");
5990
+ return signJwt({ mcpClientId: client.id, ttl: MCP_TOKEN_TTL });
5991
+ }
5992
+ /** Faux aussi quand le fichier est illisible : dans le doute, l'accès est refusé. */
5993
+ async isPaired(clientId) {
5994
+ return this.load().then((clients) => clients.some((c) => c.id === clientId), () => false);
5995
+ }
5996
+ async list() {
5997
+ return (await this.load()).map(({ secretHash: _, ...rest }) => rest);
5998
+ }
5999
+ async revoke(clientId) {
6000
+ const clients = await this.load();
6001
+ await this.save(clients.filter((c) => c.id !== clientId));
6002
+ }
6003
+ };
6004
+ var mcpPairing = new McpPairing(() => keyedFilePath(FILE));
6005
+
6006
+ // ../server/src/trpc/routers/mcp-pairing.ts
6007
+ function asBadRequest(fn) {
6008
+ return Promise.resolve().then(fn).catch((err) => {
6009
+ throw new TRPCError17({ code: "BAD_REQUEST", message: err.message });
6010
+ });
6011
+ }
6012
+ var mcpPairingRouter = router({
6013
+ request: publicProcedure.input(z20.object({ name: z20.string().min(1).max(100), cwd: z20.string().max(4096) })).mutation(({ ctx, input }) => {
6014
+ requireLoopback(ctx);
6015
+ if (isCiMode()) {
6016
+ throw new TRPCError17({
6017
+ code: "PRECONDITION_FAILED",
6018
+ message: "Runeya tourne en mode CI : l'appairage y est d\xE9sactiv\xE9. Passe une cl\xE9 d'API dans RUNEYA_API_TOKEN."
6019
+ });
6020
+ }
6021
+ return asBadRequest(() => mcpPairing.request(input.name, input.cwd));
6022
+ }),
6023
+ status: publicProcedure.input(z20.object({ requestId: z20.string().uuid() })).query(({ ctx, input }) => {
6024
+ requireLoopback(ctx);
6025
+ return mcpPairing.status(input.requestId);
6026
+ }),
6027
+ exchange: publicProcedure.input(z20.object({ secret: z20.string().min(1).max(200) })).mutation(({ ctx, input }) => {
6028
+ requireLoopback(ctx);
6029
+ return mcpPairing.exchange(input.secret).then((token) => ({ token })).catch(() => {
6030
+ throw new TRPCError17({ code: "UNAUTHORIZED", message: "Unknown or revoked MCP pairing." });
6031
+ });
6032
+ }),
6033
+ pending: protectedProcedure.query(({ ctx }) => {
6034
+ requireHuman(ctx);
6035
+ return mcpPairing.pending();
6036
+ }),
6037
+ decide: protectedProcedure.input(z20.object({ requestId: z20.string().uuid(), approve: z20.boolean() })).mutation(({ ctx, input }) => {
6038
+ requireHuman(ctx);
6039
+ return asBadRequest(() => mcpPairing.decide(input.requestId, input.approve));
6040
+ }),
6041
+ clients: protectedProcedure.query(({ ctx }) => {
6042
+ requireHuman(ctx);
6043
+ return mcpPairing.list();
6044
+ }),
6045
+ revoke: protectedProcedure.input(z20.object({ id: z20.string().uuid() })).mutation(({ ctx, input }) => {
6046
+ requireHuman(ctx);
6047
+ return mcpPairing.revoke(input.id);
6048
+ })
6049
+ });
6050
+
6051
+ // ../server/src/trpc/routers/ai-clients.ts
6052
+ import { z as z21 } from "zod";
6053
+ import { TRPCError as TRPCError18 } from "@trpc/server";
6054
+
6055
+ // ../server/src/services/ai-client-mcp.ts
6056
+ import { execFile as execFile4 } from "child_process";
6057
+ import { existsSync as existsSync2 } from "fs";
6058
+ import { mkdir as mkdir5, readFile as readFile4, realpath } from "fs/promises";
6059
+ import { homedir as homedir3 } from "os";
6060
+ import { dirname as dirname4, join as join7 } from "path";
6061
+ var SERVER_NAME = "runeya";
6062
+ function mcpEntry(override = env.MCP_COMMAND ?? null, home = env.HOME_DIR, platform = process.platform) {
6063
+ const [command, ...args] = override ? override.split(/\s+/) : ["runeya"];
6064
+ const envPart = home ? { env: { RUNEYA_HOME: home } } : {};
6065
+ if (platform === "win32" && command !== "node" && !/\.exe$/i.test(command)) {
6066
+ return { command: "cmd", args: ["/c", command, ...args, "mcp"], ...envPart };
6067
+ }
6068
+ return { command, args: [...args, "mcp"], ...envPart };
6069
+ }
6070
+ function envFlags(flag, entry) {
6071
+ return Object.entries(entry.env ?? {}).flatMap(([k, v]) => [flag, `${k}=${v}`]);
6072
+ }
6073
+ function vscodeUserDir(home) {
6074
+ if (process.platform === "darwin") return join7(home, "Library", "Application Support", "Code", "User");
6075
+ if (process.platform === "win32") return join7(process.env["APPDATA"] ?? join7(home, "AppData", "Roaming"), "Code", "User");
6076
+ return join7(home, ".config", "Code", "User");
6077
+ }
6078
+ function claudeDesktopDir(home) {
6079
+ if (process.platform === "darwin") return join7(home, "Library", "Application Support", "Claude");
6080
+ if (process.platform === "win32") return join7(process.env["APPDATA"] ?? join7(home, "AppData", "Roaming"), "Claude");
6081
+ return join7(home, ".config", "Claude");
6082
+ }
6083
+ var AI_CLIENTS = [
6084
+ {
6085
+ id: "claude-code",
6086
+ label: "Claude Code",
6087
+ kind: "cli",
6088
+ binary: "claude",
6089
+ configFile: (home) => join7(home, ".claude.json"),
6090
+ isInstalled: (content) => {
6091
+ try {
6092
+ const servers2 = JSON.parse(content).mcpServers;
6093
+ return Boolean(servers2 && SERVER_NAME in servers2);
6094
+ } catch {
6095
+ return false;
6096
+ }
6097
+ },
6098
+ // `-e` prend plusieurs valeurs : placé avant le nom, il l'avalerait. `--` l'arrête.
6099
+ addArgs: (e) => ["mcp", "add", "--scope", "user", SERVER_NAME, ...envFlags("-e", e), "--", e.command, ...e.args],
6100
+ removeArgs: ["mcp", "remove", "--scope", "user", SERVER_NAME]
6101
+ },
6102
+ {
6103
+ id: "codex",
6104
+ label: "Codex",
6105
+ kind: "cli",
6106
+ binary: "codex",
6107
+ configFile: (home) => join7(home, ".codex", "config.toml"),
6108
+ isInstalled: (content) => new RegExp(`^\\s*\\[mcp_servers\\.${SERVER_NAME}\\]`, "m").test(content),
6109
+ addArgs: (e) => ["mcp", "add", ...envFlags("--env", e), SERVER_NAME, "--", e.command, ...e.args],
6110
+ removeArgs: ["mcp", "remove", SERVER_NAME]
6111
+ },
6112
+ { id: "cursor", label: "Cursor", kind: "json", detectDir: (h) => join7(h, ".cursor"), configFile: (h) => join7(h, ".cursor", "mcp.json") },
6113
+ { id: "gemini", label: "Gemini CLI", kind: "json", detectDir: (h) => join7(h, ".gemini"), configFile: (h) => join7(h, ".gemini", "settings.json") },
6114
+ {
6115
+ id: "windsurf",
6116
+ label: "Windsurf",
6117
+ kind: "json",
6118
+ detectDir: (h) => join7(h, ".codeium", "windsurf"),
6119
+ configFile: (h) => join7(h, ".codeium", "windsurf", "mcp_config.json")
6120
+ },
6121
+ {
6122
+ id: "claude-desktop",
6123
+ label: "Claude Desktop",
6124
+ kind: "json",
6125
+ detectDir: claudeDesktopDir,
6126
+ configFile: (h) => join7(claudeDesktopDir(h), "claude_desktop_config.json")
6127
+ },
6128
+ {
6129
+ id: "vscode",
6130
+ label: "VS Code",
6131
+ kind: "json",
6132
+ detectDir: vscodeUserDir,
6133
+ configFile: (h) => join7(vscodeUserDir(h), "mcp.json"),
6134
+ serversKey: "servers",
6135
+ entryFields: { type: "stdio" }
6136
+ }
6137
+ ];
6138
+ async function readText(path) {
6139
+ try {
6140
+ return await readFile4(path, "utf8");
6141
+ } catch {
6142
+ return null;
6143
+ }
6144
+ }
6145
+ async function readJsonConfig(path) {
6146
+ const text = await readText(path);
6147
+ if (text === null || text.trim() === "") return {};
6148
+ const parsed = JSON.parse(text);
6149
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
6150
+ throw new Error(`${path} ne contient pas un objet JSON.`);
6151
+ }
6152
+ return parsed;
6153
+ }
6154
+ function servers(config, key = "mcpServers") {
6155
+ const value = config[key];
6156
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
6157
+ }
6158
+ async function setJsonEntry(path, present, entry = mcpEntry(), key = "mcpServers", entryFields = {}) {
6159
+ const config = await readJsonConfig(path);
6160
+ const current2 = config[key];
6161
+ if (current2 !== void 0 && (!current2 || typeof current2 !== "object" || Array.isArray(current2))) {
6162
+ throw new Error(`${path} : \xAB ${key} \xBB n'est pas un objet, le fichier n'est pas modifi\xE9.`);
6163
+ }
6164
+ const next = { ...servers(config, key) };
6165
+ if (present) next[SERVER_NAME] = { ...entryFields, ...entry };
6166
+ else delete next[SERVER_NAME];
6167
+ const target = await realpath(path).catch(() => path);
6168
+ await mkdir5(dirname4(target), { recursive: true });
6169
+ await atomicWriteSecure(target, `${JSON.stringify({ ...config, [key]: next }, null, 2)}
6170
+ `);
6171
+ }
6172
+ function run(binary, args) {
6173
+ const { command, args: fullArgs } = resolveCliInvocation(binary, args);
6174
+ return new Promise((resolve5, reject) => {
6175
+ execFile4(command, fullArgs, { timeout: 2e4, windowsHide: true }, (err, _stdout, stderr) => {
6176
+ if (err) reject(new Error(`${binary} ${args.join(" ")} : ${String(stderr).trim() || err.message}`));
6177
+ else resolve5();
6178
+ });
6179
+ });
6180
+ }
6181
+ async function isRuneyaMcpAvailable(entry = mcpEntry()) {
6182
+ const { command, args } = resolveCliInvocation(entry.command, [...entry.args.slice(0, -1), "--help"]);
6183
+ return new Promise((resolve5) => {
6184
+ execFile4(command, args, { timeout: 1e4, windowsHide: true }, (err, stdout) => {
6185
+ resolve5(!err && /^\s+mcp\s/m.test(String(stdout)));
6186
+ });
6187
+ });
6188
+ }
6189
+ async function isDetected(client, home) {
6190
+ if (client.kind === "cli") return isCliAvailable(client.binary);
6191
+ return existsSync2(client.detectDir(home));
6192
+ }
6193
+ async function isInstalled(client, home) {
6194
+ if (client.kind === "cli") {
6195
+ const content = await readText(client.configFile(home));
6196
+ return content !== null && client.isInstalled(content);
6197
+ }
6198
+ try {
6199
+ return SERVER_NAME in servers(await readJsonConfig(client.configFile(home)), client.serversKey);
6200
+ } catch {
6201
+ return false;
6202
+ }
6203
+ }
6204
+ async function listAiClients(home = homedir3()) {
6205
+ const [runeyaMcpAvailable, statuses] = await Promise.all([
6206
+ isRuneyaMcpAvailable(),
6207
+ Promise.all(AI_CLIENTS.map(async (client) => {
6208
+ if (!await isDetected(client, home)) return null;
6209
+ return { id: client.id, label: client.label, installed: await isInstalled(client, home), configFile: client.configFile(home) };
6210
+ }))
6211
+ ]);
6212
+ return { runeyaMcpAvailable, clients: statuses.filter((s) => s !== null) };
6213
+ }
6214
+ async function setAiClientMcp(id, present, home = homedir3()) {
6215
+ const client = AI_CLIENTS.find((c) => c.id === id);
6216
+ if (!client) throw new Error(`Outil IA inconnu : ${id}`);
6217
+ if (present && !await isRuneyaMcpAvailable()) {
6218
+ const { command, args } = mcpEntry();
6219
+ throw new Error(`La commande \xAB ${[command, ...args].join(" ")} \xBB est introuvable ou ne conna\xEEt pas \xAB mcp \xBB : installez une version de Runeya qui la fournit.`);
6220
+ }
6221
+ if (client.kind === "json") return setJsonEntry(client.configFile(home), present, mcpEntry(), client.serversKey, client.entryFields);
6222
+ if (await isInstalled(client, home)) await run(client.binary, client.removeArgs);
6223
+ if (present) await run(client.binary, client.addArgs(mcpEntry()));
6224
+ }
6225
+
6226
+ // ../server/src/trpc/routers/ai-clients.ts
6227
+ var AiClientIdSchema = z21.enum(["claude-code", "codex", "cursor", "gemini", "windsurf", "claude-desktop", "vscode"]);
6228
+ var aiClientsRouter = router({
6229
+ list: protectedProcedure.query(({ ctx }) => {
6230
+ requireHuman(ctx);
6231
+ return listAiClients();
6232
+ }),
6233
+ setInstalled: protectedProcedure.input(z21.object({ id: AiClientIdSchema, installed: z21.boolean() })).mutation(async ({ ctx, input }) => {
6234
+ requireHuman(ctx);
6235
+ try {
6236
+ await setAiClientMcp(input.id, input.installed);
6237
+ } catch (err) {
6238
+ throw new TRPCError18({ code: "BAD_REQUEST", message: err.message });
6239
+ }
6240
+ return listAiClients();
6241
+ })
6242
+ });
6243
+
6244
+ // ../server/src/trpc/routers/kanban-queue.ts
6245
+ import { z as z22 } from "zod";
5822
6246
 
5823
6247
  // ../server/src/services/kanban-queue.ts
5824
- import { randomUUID as randomUUID5 } from "crypto";
6248
+ import { randomUUID as randomUUID6 } from "crypto";
5825
6249
  import { EventEmitter as EventEmitter2 } from "events";
5826
6250
  var KanbanQueueService = class extends EventEmitter2 {
5827
6251
  items = [];
@@ -5849,7 +6273,7 @@ var KanbanQueueService = class extends EventEmitter2 {
5849
6273
  return { id: alreadyRunning.id, addedAt: alreadyRunning.startedAt, ...item };
5850
6274
  }
5851
6275
  const queueItem = {
5852
- id: randomUUID5(),
6276
+ id: randomUUID6(),
5853
6277
  ...item,
5854
6278
  addedAt: (/* @__PURE__ */ new Date()).toISOString()
5855
6279
  };
@@ -5877,9 +6301,9 @@ var KanbanQueueService = class extends EventEmitter2 {
5877
6301
  }
5878
6302
  const runIdx = this.running.findIndex((r) => r.id === itemId);
5879
6303
  if (runIdx >= 0) {
5880
- const run = this.running[runIdx];
5881
- abortScenario(run.conversationId);
5882
- activeSessionRegistry.kill(run.conversationId);
6304
+ const run2 = this.running[runIdx];
6305
+ abortScenario(run2.conversationId);
6306
+ activeSessionRegistry.kill(run2.conversationId);
5883
6307
  this.running.splice(runIdx, 1);
5884
6308
  this.emit("changed");
5885
6309
  this.tick();
@@ -5889,9 +6313,9 @@ var KanbanQueueService = class extends EventEmitter2 {
5889
6313
  }
5890
6314
  stopAll() {
5891
6315
  this.items = [];
5892
- for (const run of this.running) {
5893
- abortScenario(run.conversationId);
5894
- activeSessionRegistry.kill(run.conversationId);
6316
+ for (const run2 of this.running) {
6317
+ abortScenario(run2.conversationId);
6318
+ activeSessionRegistry.kill(run2.conversationId);
5895
6319
  }
5896
6320
  this.running = [];
5897
6321
  this.emit("changed");
@@ -6010,50 +6434,50 @@ var kanbanQueue = new KanbanQueueService();
6010
6434
  // ../server/src/trpc/routers/kanban-queue.ts
6011
6435
  var kanbanQueueRouter = router({
6012
6436
  getState: protectedProcedure.query(() => kanbanQueue.getState()),
6013
- add: protectedProcedure.input(z19.object({
6014
- boardId: z19.string().max(128),
6015
- cardId: z19.string().max(128),
6016
- runner: z19.enum(["claude-code", "codex"]).optional(),
6017
- model: z19.string().max(128).optional(),
6018
- scenarioId: z19.string().max(128).optional(),
6437
+ add: protectedProcedure.input(z22.object({
6438
+ boardId: z22.string().max(128),
6439
+ cardId: z22.string().max(128),
6440
+ runner: z22.enum(["claude-code", "codex"]).optional(),
6441
+ model: z22.string().max(128).optional(),
6442
+ scenarioId: z22.string().max(128).optional(),
6019
6443
  // Le projet ouvert quand la carte a été mise en file : la conversation
6020
6444
  // qui en naîtra lui appartient, plutôt que de rester orpheline et de
6021
6445
  // s'afficher dans tous les projets.
6022
- projectId: z19.string().max(128).optional(),
6023
- orgId: z19.string().max(128).nullable().optional()
6446
+ projectId: z22.string().max(128).optional(),
6447
+ orgId: z22.string().max(128).nullable().optional()
6024
6448
  })).mutation(({ input }) => kanbanQueue.add(input)),
6025
- addColumn: protectedProcedure.input(z19.object({
6026
- boardId: z19.string().max(128),
6027
- columnId: z19.string().max(128),
6449
+ addColumn: protectedProcedure.input(z22.object({
6450
+ boardId: z22.string().max(128),
6451
+ columnId: z22.string().max(128),
6028
6452
  // Les cartes visibles côté écran : une colonne filtrée n'envoie qu'elles.
6029
- cardIds: z19.array(z19.string().max(128)).max(5e3).optional(),
6030
- runner: z19.enum(["claude-code", "codex"]).optional(),
6031
- model: z19.string().max(128).optional(),
6032
- scenarioId: z19.string().max(128).optional(),
6033
- projectId: z19.string().max(128).optional(),
6034
- orgId: z19.string().max(128).nullable().optional()
6453
+ cardIds: z22.array(z22.string().max(128)).max(5e3).optional(),
6454
+ runner: z22.enum(["claude-code", "codex"]).optional(),
6455
+ model: z22.string().max(128).optional(),
6456
+ scenarioId: z22.string().max(128).optional(),
6457
+ projectId: z22.string().max(128).optional(),
6458
+ orgId: z22.string().max(128).nullable().optional()
6035
6459
  })).mutation(async ({ input }) => {
6036
6460
  const { boardId, columnId, ...options } = input;
6037
6461
  return kanbanQueue.addColumn(boardId, columnId, options);
6038
6462
  }),
6039
- remove: protectedProcedure.input(z19.object({ itemId: z19.string().max(128) })).mutation(({ input }) => ({ removed: kanbanQueue.remove(input.itemId) })),
6463
+ remove: protectedProcedure.input(z22.object({ itemId: z22.string().max(128) })).mutation(({ input }) => ({ removed: kanbanQueue.remove(input.itemId) })),
6040
6464
  stopAll: protectedProcedure.mutation(() => {
6041
6465
  kanbanQueue.stopAll();
6042
6466
  return { ok: true };
6043
6467
  }),
6044
- setMaxConcurrent: protectedProcedure.input(z19.object({ max: z19.number().int().min(1).max(20) })).mutation(({ input }) => {
6468
+ setMaxConcurrent: protectedProcedure.input(z22.object({ max: z22.number().int().min(1).max(20) })).mutation(({ input }) => {
6045
6469
  kanbanQueue.setMaxConcurrent(input.max);
6046
6470
  return kanbanQueue.getState();
6047
6471
  }),
6048
6472
  onChanged: protectedProcedure.subscription(async function* () {
6049
6473
  const queue = [];
6050
- let resolve4 = null;
6474
+ let resolve5 = null;
6051
6475
  const handler = () => {
6052
6476
  if (queue.length >= 50) queue.shift();
6053
6477
  queue.push(kanbanQueue.getState());
6054
- if (resolve4) {
6055
- resolve4();
6056
- resolve4 = null;
6478
+ if (resolve5) {
6479
+ resolve5();
6480
+ resolve5 = null;
6057
6481
  }
6058
6482
  };
6059
6483
  kanbanQueue.on("changed", handler);
@@ -6064,7 +6488,7 @@ var kanbanQueueRouter = router({
6064
6488
  yield queue.shift();
6065
6489
  } else {
6066
6490
  await new Promise((r) => {
6067
- resolve4 = r;
6491
+ resolve5 = r;
6068
6492
  });
6069
6493
  }
6070
6494
  }
@@ -6075,12 +6499,12 @@ var kanbanQueueRouter = router({
6075
6499
  });
6076
6500
 
6077
6501
  // ../server/src/trpc/routers/kanban.ts
6078
- import { mkdir as mkdir5, readdir, rm, stat as stat3, writeFile as writeFile4 } from "fs/promises";
6079
- import { createHash } from "crypto";
6502
+ import { mkdir as mkdir6, readdir, rm, stat as stat3, writeFile as writeFile4 } from "fs/promises";
6503
+ import { createHash as createHash2 } from "crypto";
6080
6504
  import { tmpdir } from "os";
6081
- import { join as join7 } from "path";
6082
- import { z as z20 } from "zod";
6083
- import { TRPCError as TRPCError17 } from "@trpc/server";
6505
+ import { join as join8 } from "path";
6506
+ import { z as z23 } from "zod";
6507
+ import { TRPCError as TRPCError19 } from "@trpc/server";
6084
6508
  function summarizeCard(card, boardId, columnTitle) {
6085
6509
  return {
6086
6510
  boardId,
@@ -6121,14 +6545,14 @@ function safeDecode(value) {
6121
6545
  }
6122
6546
  var MAX_ATTACHMENT_BYTES = 55 * 1024 * 1024;
6123
6547
  function attachmentCacheDir() {
6124
- return join7(tmpdir(), "runeya-kanban-attachments");
6548
+ return join8(tmpdir(), "runeya-kanban-attachments");
6125
6549
  }
6126
6550
  var ATTACHMENT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
6127
6551
  async function purgeStaleAttachments(dir, ttlMs = ATTACHMENT_CACHE_TTL_MS) {
6128
6552
  try {
6129
6553
  const cutoff = Date.now() - ttlMs;
6130
6554
  for (const name of await readdir(dir)) {
6131
- const path = join7(dir, name);
6555
+ const path = join8(dir, name);
6132
6556
  const info = await stat3(path).catch(() => null);
6133
6557
  if (info && info.mtimeMs < cutoff) await rm(path, { force: true });
6134
6558
  }
@@ -6136,14 +6560,14 @@ async function purgeStaleAttachments(dir, ttlMs = ATTACHMENT_CACHE_TTL_MS) {
6136
6560
  }
6137
6561
  }
6138
6562
  function attachmentCacheDirs() {
6139
- return [attachmentCacheDir(), join7(machineRoot(), "kanban-attachments")];
6563
+ return [attachmentCacheDir(), join8(machineRoot(), "kanban-attachments")];
6140
6564
  }
6141
6565
  async function storeAttachment(id, name, body, dirs = attachmentCacheDirs()) {
6142
6566
  const refusals = [];
6143
6567
  for (const dir of dirs) {
6144
6568
  try {
6145
- await mkdir5(dir, { recursive: true });
6146
- const path = join7(dir, safeLocalName(id, name));
6569
+ await mkdir6(dir, { recursive: true });
6570
+ const path = join8(dir, safeLocalName(id, name));
6147
6571
  await writeFile4(path, body);
6148
6572
  void purgeStaleAttachments(dir);
6149
6573
  return path;
@@ -6151,13 +6575,13 @@ async function storeAttachment(id, name, body, dirs = attachmentCacheDirs()) {
6151
6575
  refusals.push(`${dir} (${err instanceof Error ? err.message : String(err)})`);
6152
6576
  }
6153
6577
  }
6154
- throw new TRPCError17({
6578
+ throw new TRPCError19({
6155
6579
  code: "INTERNAL_SERVER_ERROR",
6156
6580
  message: `Impossible d'\xE9crire la pi\xE8ce jointe sur cette machine. Emplacements essay\xE9s : ${refusals.join(" ; ")}. V\xE9rifiez les droits d'\xE9criture, ou l'espace disque.`
6157
6581
  });
6158
6582
  }
6159
6583
  function safeLocalName(id, name) {
6160
- const base = createHash("sha256").update(id).digest("hex").slice(0, 32);
6584
+ const base = createHash2("sha256").update(id).digest("hex").slice(0, 32);
6161
6585
  const ext = /\.([A-Za-z0-9]{1,12})$/.exec(name)?.[1];
6162
6586
  return ext ? `${base}.${ext.toLowerCase()}` : base;
6163
6587
  }
@@ -6172,9 +6596,9 @@ var kanbanRouter = router({
6172
6596
  * card graph, so listing them in full would be megabytes to answer "which
6173
6597
  * boards exist?". Use `getBoard` for the cards of one.
6174
6598
  */
6175
- listBoards: protectedProcedure.input(z20.object({
6176
- includeArchived: z20.boolean().optional(),
6177
- organizationId: z20.string().max(128).optional()
6599
+ listBoards: protectedProcedure.input(z23.object({
6600
+ includeArchived: z23.boolean().optional(),
6601
+ organizationId: z23.string().max(128).optional()
6178
6602
  }).optional()).query(async ({ input }) => {
6179
6603
  const { organizationId, boards } = await kanbanStore.list(
6180
6604
  input?.includeArchived ?? false,
@@ -6200,7 +6624,7 @@ var kanbanRouter = router({
6200
6624
  };
6201
6625
  }),
6202
6626
  /** One board: its columns, and the cards in each. */
6203
- getBoard: protectedProcedure.input(z20.object({ boardId: z20.string().max(128) })).query(async ({ input }) => {
6627
+ getBoard: protectedProcedure.input(z23.object({ boardId: z23.string().max(128) })).query(async ({ input }) => {
6204
6628
  const board = await kanbanStore.get(input.boardId);
6205
6629
  if (!board) return { board: null };
6206
6630
  const { links } = await kanbanStore.cardLinks(input.boardId);
@@ -6239,12 +6663,12 @@ var kanbanRouter = router({
6239
6663
  * organization — or one board when `boardId` is given. Archived boards are
6240
6664
  * left out: they are what the team decided to stop looking at.
6241
6665
  */
6242
- searchCards: protectedProcedure.input(z20.object({
6243
- query: z20.string().max(256).optional(),
6244
- createdBy: z20.string().min(1).max(200).optional(),
6245
- boardId: z20.string().max(128).optional(),
6246
- limit: z20.number().int().min(1).max(100).optional(),
6247
- organizationId: z20.string().max(128).optional()
6666
+ searchCards: protectedProcedure.input(z23.object({
6667
+ query: z23.string().max(256).optional(),
6668
+ createdBy: z23.string().min(1).max(200).optional(),
6669
+ boardId: z23.string().max(128).optional(),
6670
+ limit: z23.number().int().min(1).max(100).optional(),
6671
+ organizationId: z23.string().max(128).optional()
6248
6672
  }).refine((i) => !!i.query?.trim() || !!i.createdBy?.trim(), { message: "query or createdBy is required." })).query(async ({ input }) => {
6249
6673
  return kanbanStore.searchCards(input);
6250
6674
  }),
@@ -6252,10 +6676,10 @@ var kanbanRouter = router({
6252
6676
  * Les conversations de support liées à une carte, avec leur fil — ce que
6253
6677
  * le client a écrit à l'équipe, séparé des commentaires de la carte.
6254
6678
  */
6255
- getCardConversations: protectedProcedure.input(z20.object({
6256
- boardId: z20.string().max(128),
6257
- cardId: z20.string().max(128),
6258
- limit: z20.number().int().min(1).max(500).optional()
6679
+ getCardConversations: protectedProcedure.input(z23.object({
6680
+ boardId: z23.string().max(128),
6681
+ cardId: z23.string().max(128),
6682
+ limit: z23.number().int().min(1).max(500).optional()
6259
6683
  })).query(async ({ input }) => {
6260
6684
  const conversations = await kanbanStore.cardConversations(input.boardId, input.cardId);
6261
6685
  const limit = input.limit ?? 100;
@@ -6268,13 +6692,13 @@ var kanbanRouter = router({
6268
6692
  };
6269
6693
  }),
6270
6694
  /** Chercher une conversation chez les fournisseurs de l'organisation (lecture seule côté fournisseur). */
6271
- searchConversations: protectedProcedure.input(z20.object({ boardId: z20.string().max(128), query: z20.string().trim().min(1).max(500) })).query(async ({ input }) => ({ results: await kanbanStore.searchConversations(input.boardId, input.query) })),
6695
+ searchConversations: protectedProcedure.input(z23.object({ boardId: z23.string().max(128), query: z23.string().trim().min(1).max(500) })).query(async ({ input }) => ({ results: await kanbanStore.searchConversations(input.boardId, input.query) })),
6272
6696
  /** Lier une conversation trouvée à une carte. N'écrit rien chez le fournisseur. */
6273
- linkConversation: protectedProcedure.input(z20.object({
6274
- boardId: z20.string().max(128),
6275
- cardId: z20.string().max(128),
6276
- connectionId: z20.string().max(128),
6277
- externalId: z20.string().max(128)
6697
+ linkConversation: protectedProcedure.input(z23.object({
6698
+ boardId: z23.string().max(128),
6699
+ cardId: z23.string().max(128),
6700
+ connectionId: z23.string().max(128),
6701
+ externalId: z23.string().max(128)
6278
6702
  })).mutation(async ({ input }) => ({ conversations: await kanbanStore.linkConversation(input) })),
6279
6703
  /**
6280
6704
  * Le fil de commentaires d'une carte, relu à la demande.
@@ -6285,10 +6709,10 @@ var kanbanRouter = router({
6285
6709
  * qu'un `commentCount`, et rien n'en donnait le texte. Cette route est la
6286
6710
  * relecture, pour une seule carte : c'est le journal qui pèse, pas le reste.
6287
6711
  */
6288
- getCardComments: protectedProcedure.input(z20.object({
6289
- boardId: z20.string().max(128),
6290
- cardId: z20.string().max(128),
6291
- limit: z20.number().int().min(1).max(200).optional()
6712
+ getCardComments: protectedProcedure.input(z23.object({
6713
+ boardId: z23.string().max(128),
6714
+ cardId: z23.string().max(128),
6715
+ limit: z23.number().int().min(1).max(200).optional()
6292
6716
  })).query(async ({ input }) => {
6293
6717
  const history = await kanbanStore.cardHistory(input.boardId, input.cardId);
6294
6718
  const comments = history.filter((e) => e.type === "comment");
@@ -6317,18 +6741,18 @@ var kanbanRouter = router({
6317
6741
  * `getBoard` en donne déjà l'essentiel carte par carte ; cette route sert
6318
6742
  * quand c'est l'arbre lui-même qu'on veut, dates et auteurs compris.
6319
6743
  */
6320
- getCardLinks: protectedProcedure.input(z20.object({ boardId: z20.string().max(128) })).query(async ({ input }) => kanbanStore.cardLinks(input.boardId)),
6744
+ getCardLinks: protectedProcedure.input(z23.object({ boardId: z23.string().max(128) })).query(async ({ input }) => kanbanStore.cardLinks(input.boardId)),
6321
6745
  /**
6322
6746
  * Rattacher une carte à une carte parente, ou l'en détacher.
6323
6747
  *
6324
6748
  * Sans `parentCardId`, le lien est retiré. Le parent peut vivre sur un autre
6325
6749
  * tableau : `parentBoardId` le désigne alors, sinon c'est celui de la carte.
6326
6750
  */
6327
- setCardParent: protectedProcedure.input(z20.object({
6328
- boardId: z20.string().max(128),
6329
- cardId: z20.string().max(128),
6330
- parentCardId: z20.string().max(128).optional(),
6331
- parentBoardId: z20.string().max(128).optional()
6751
+ setCardParent: protectedProcedure.input(z23.object({
6752
+ boardId: z23.string().max(128),
6753
+ cardId: z23.string().max(128),
6754
+ parentCardId: z23.string().max(128).optional(),
6755
+ parentBoardId: z23.string().max(128).optional()
6332
6756
  })).mutation(async ({ input }) => {
6333
6757
  const ok = await kanbanStore.setCardParent(
6334
6758
  input.boardId,
@@ -6336,7 +6760,7 @@ var kanbanRouter = router({
6336
6760
  input.parentCardId ? { boardId: input.parentBoardId ?? input.boardId, cardId: input.parentCardId } : null
6337
6761
  );
6338
6762
  if (!ok) {
6339
- throw new TRPCError17({ code: "BAD_REQUEST", message: "The cloud refused the link (unknown card, or a cycle)." });
6763
+ throw new TRPCError19({ code: "BAD_REQUEST", message: "The cloud refused the link (unknown card, or a cycle)." });
6340
6764
  }
6341
6765
  return { ok: true };
6342
6766
  }),
@@ -6345,48 +6769,48 @@ var kanbanRouter = router({
6345
6769
  * `boardId` est le tableau où on la pose (ou d'où on la retire) ; la carte
6346
6770
  * reste sur son tableau d'origine.
6347
6771
  */
6348
- getCardPlacements: protectedProcedure.input(z20.object({ boardId: z20.string().max(128), cardId: z20.string().max(128) })).query(async ({ input }) => ({ placements: await kanbanStore.cardPlacements(input.boardId, input.cardId) })),
6349
- placeCard: protectedProcedure.input(z20.object({ boardId: z20.string().max(128), cardId: z20.string().max(128), column: z20.string().min(1).max(200) })).mutation(async ({ input }) => {
6772
+ getCardPlacements: protectedProcedure.input(z23.object({ boardId: z23.string().max(128), cardId: z23.string().max(128) })).query(async ({ input }) => ({ placements: await kanbanStore.cardPlacements(input.boardId, input.cardId) })),
6773
+ placeCard: protectedProcedure.input(z23.object({ boardId: z23.string().max(128), cardId: z23.string().max(128), column: z23.string().min(1).max(200) })).mutation(async ({ input }) => {
6350
6774
  const res = await kanbanStore.placeCard(input.boardId, input.cardId, input.column);
6351
6775
  if (!res.ok) {
6352
- throw new TRPCError17({
6776
+ throw new TRPCError19({
6353
6777
  code: "BAD_REQUEST",
6354
6778
  message: res.reason ?? "The cloud refused the placement (unknown card or column, archived card, or already on this board)."
6355
6779
  });
6356
6780
  }
6357
6781
  return { ok: true };
6358
6782
  }),
6359
- unplaceCard: protectedProcedure.input(z20.object({ boardId: z20.string().max(128), cardId: z20.string().max(128) })).mutation(async ({ input }) => {
6783
+ unplaceCard: protectedProcedure.input(z23.object({ boardId: z23.string().max(128), cardId: z23.string().max(128) })).mutation(async ({ input }) => {
6360
6784
  if (!await kanbanStore.unplaceCard(input.boardId, input.cardId)) {
6361
- throw new TRPCError17({ code: "NOT_FOUND", message: "This card is not placed on this board." });
6785
+ throw new TRPCError19({ code: "NOT_FOUND", message: "This card is not placed on this board." });
6362
6786
  }
6363
6787
  return { ok: true };
6364
6788
  }),
6365
- moveCard: protectedProcedure.input(z20.object({
6366
- boardId: z20.string().max(128),
6367
- cardId: z20.string().max(128),
6368
- targetColumn: z20.string().max(256)
6789
+ moveCard: protectedProcedure.input(z23.object({
6790
+ boardId: z23.string().max(128),
6791
+ cardId: z23.string().max(128),
6792
+ targetColumn: z23.string().max(256)
6369
6793
  })).mutation(async ({ input }) => {
6370
6794
  await executeKanbanCommands(input.boardId, input.cardId, [
6371
6795
  { type: "move_card", columnTitle: input.targetColumn }
6372
6796
  ]);
6373
6797
  return { ok: true };
6374
6798
  }),
6375
- addComment: protectedProcedure.input(z20.object({
6376
- boardId: z20.string().max(128),
6377
- cardId: z20.string().max(128),
6378
- comment: z20.string().max(1e5)
6799
+ addComment: protectedProcedure.input(z23.object({
6800
+ boardId: z23.string().max(128),
6801
+ cardId: z23.string().max(128),
6802
+ comment: z23.string().max(1e5)
6379
6803
  })).mutation(async ({ input }) => {
6380
6804
  await executeKanbanCommands(input.boardId, input.cardId, [
6381
6805
  { type: "add_comment", comment: input.comment }
6382
6806
  ]);
6383
6807
  return { ok: true };
6384
6808
  }),
6385
- addCard: protectedProcedure.input(z20.object({
6386
- boardId: z20.string().max(128),
6387
- column: z20.string().max(256),
6388
- title: z20.string().max(512),
6389
- content: z20.string().max(2e4).optional()
6809
+ addCard: protectedProcedure.input(z23.object({
6810
+ boardId: z23.string().max(128),
6811
+ column: z23.string().max(256),
6812
+ title: z23.string().max(512),
6813
+ content: z23.string().max(2e4).optional()
6390
6814
  })).mutation(async ({ input }) => {
6391
6815
  await executeKanbanBoardCommands(input.boardId, [
6392
6816
  { type: "add_card", columnTitle: input.column, cardTitle: input.title, cardContent: input.content }
@@ -6399,11 +6823,34 @@ var kanbanRouter = router({
6399
6823
  * `getBoard` rapatrie le tableau entier pour n'en regarder qu'une ; et il ne
6400
6824
  * porte pas les cartes rangées. Une carte s'adresse par son identifiant.
6401
6825
  */
6402
- getCard: protectedProcedure.input(z20.object({ boardId: z20.string().max(128), cardId: z20.string().max(128) })).query(async ({ input }) => {
6826
+ getCard: protectedProcedure.input(z23.object({ boardId: z23.string().max(128), cardId: z23.string().max(128) })).query(async ({ input }) => {
6403
6827
  const found = await kanbanStore.card(input.boardId, input.cardId);
6404
6828
  if (!found) throw new Error(`Carte ${input.cardId} introuvable sur le tableau ${input.boardId}.`);
6405
6829
  return { ...summarizeCard(found.card, input.boardId, ""), columnId: found.columnId };
6406
6830
  }),
6831
+ /**
6832
+ * Le contexte d'une carte pour un outil IA lancé hors de Runeya : le même
6833
+ * texte qu'une session lancée depuis la carte reçoit, construit au même
6834
+ * endroit (`buildKanbanCardPrompt`). L'outil le reçoit comme un message, pas
6835
+ * comme une consigne système : il guide le travail sans en avoir la force.
6836
+ */
6837
+ cardContext: protectedProcedure.input(z23.object({
6838
+ boardId: z23.string().max(128),
6839
+ cardId: z23.string().max(128),
6840
+ projectId: z23.string().max(128).optional(),
6841
+ orgId: z23.string().max(128).optional()
6842
+ })).query(async ({ input }) => {
6843
+ const board = await kanbanStore.get(input.boardId);
6844
+ if (!board) throw new TRPCError19({ code: "NOT_FOUND", message: `Tableau ${input.boardId} introuvable.` });
6845
+ if (!board.columns.some((col) => col.cards.some((c) => c.id === input.cardId))) {
6846
+ throw new TRPCError19({ code: "NOT_FOUND", message: `Carte ${input.cardId} introuvable sur le tableau ${input.boardId}.` });
6847
+ }
6848
+ const scope = {
6849
+ ...input.projectId ? { projectId: input.projectId } : {},
6850
+ ...input.orgId ? { orgId: input.orgId } : {}
6851
+ };
6852
+ return { context: await buildKanbanCardPrompt(board, input.cardId, scope) };
6853
+ }),
6407
6854
  /**
6408
6855
  * Modifier une carte.
6409
6856
  *
@@ -6413,16 +6860,16 @@ var kanbanRouter = router({
6413
6860
  * Un champ absent n'est pas touché ; `null` l'efface. La distinction compte —
6414
6861
  * changer un titre ne doit pas vider une couleur.
6415
6862
  */
6416
- updateCard: protectedProcedure.input(z20.object({
6417
- boardId: z20.string().max(128),
6418
- cardId: z20.string().max(128),
6419
- title: z20.string().min(1).max(512).optional(),
6420
- content: z20.string().max(1e5).optional(),
6421
- priority: z20.number().int().min(1).max(10).nullable().optional(),
6422
- color: z20.string().max(32).nullable().optional(),
6423
- groups: z20.array(z20.string().min(1).max(60)).max(30).optional(),
6424
- deadline: z20.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional(),
6425
- assignees: z20.array(z20.string().max(128)).max(50).optional()
6863
+ updateCard: protectedProcedure.input(z23.object({
6864
+ boardId: z23.string().max(128),
6865
+ cardId: z23.string().max(128),
6866
+ title: z23.string().min(1).max(512).optional(),
6867
+ content: z23.string().max(1e5).optional(),
6868
+ priority: z23.number().int().min(1).max(10).nullable().optional(),
6869
+ color: z23.string().max(32).nullable().optional(),
6870
+ groups: z23.array(z23.string().min(1).max(60)).max(30).optional(),
6871
+ deadline: z23.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional(),
6872
+ assignees: z23.array(z23.string().max(128)).max(50).optional()
6426
6873
  })).mutation(async ({ input }) => {
6427
6874
  const { boardId, cardId, content, ...rest } = input;
6428
6875
  const patch = { ...rest };
@@ -6433,10 +6880,10 @@ var kanbanRouter = router({
6433
6880
  return summarizeCard(card, boardId, "");
6434
6881
  }),
6435
6882
  /** Ranger une carte, ou la reprendre. */
6436
- setCardArchived: protectedProcedure.input(z20.object({
6437
- boardId: z20.string().max(128),
6438
- cardId: z20.string().max(128),
6439
- archived: z20.boolean()
6883
+ setCardArchived: protectedProcedure.input(z23.object({
6884
+ boardId: z23.string().max(128),
6885
+ cardId: z23.string().max(128),
6886
+ archived: z23.boolean()
6440
6887
  })).mutation(async ({ input }) => {
6441
6888
  const ok = await kanbanStore.setCardArchived(input.boardId, input.cardId, input.archived);
6442
6889
  if (!ok) throw new Error(`La carte ${input.cardId} n'a pas pu \xEAtre ${input.archived ? "archiv\xE9e" : "reprise"}.`);
@@ -6448,48 +6895,48 @@ var kanbanRouter = router({
6448
6895
  * Sans cette liste, assigner revient à deviner un identifiant — et l'agent
6449
6896
  * n'a aucun moyen de le trouver.
6450
6897
  */
6451
- listMembers: protectedProcedure.input(z20.object({ organizationId: z20.string().max(128).optional() })).query(async ({ input }) => ({ members: await kanbanStore.members(input.organizationId) })),
6898
+ listMembers: protectedProcedure.input(z23.object({ organizationId: z23.string().max(128).optional() })).query(async ({ input }) => ({ members: await kanbanStore.members(input.organizationId) })),
6452
6899
  /**
6453
6900
  * Fetch one card attachment from the cloud and drop it on this machine, so an
6454
6901
  * agent can open it with its own file tools. The bytes never go through the
6455
6902
  * prompt: the card context lists the attachments, this returns a path.
6456
6903
  */
6457
- downloadAttachment: protectedProcedure.input(z20.object({
6458
- attachmentId: z20.string().max(128),
6904
+ downloadAttachment: protectedProcedure.input(z23.object({
6905
+ attachmentId: z23.string().max(128),
6459
6906
  // La pièce jointe appartient à une organisation : la nommer dit sous quel
6460
6907
  // compte la demander.
6461
- orgId: z20.string().max(128).optional(),
6908
+ orgId: z23.string().max(128).optional(),
6462
6909
  // À défaut, le tableau d'où vient la carte suffit : il désigne un compte
6463
6910
  // comme le fait n'importe quel autre appel du kanban. Sans lui, la route
6464
6911
  // retombait sur le compte unique du poste et refusait de servir dès qu'il
6465
6912
  // y en avait plusieurs — alors que l'agent, qui n'a qu'un identifiant de
6466
6913
  // fichier, n'avait aucun moyen de nommer l'organisation.
6467
- boardId: z20.string().max(128).optional()
6914
+ boardId: z23.string().max(128).optional()
6468
6915
  })).mutation(async ({ input }) => {
6469
6916
  const id = input.attachmentId;
6470
6917
  if (!ATTACHMENT_ID_REGEX.test(id)) {
6471
- throw new TRPCError17({ code: "BAD_REQUEST", message: "Invalid attachment id" });
6918
+ throw new TRPCError19({ code: "BAD_REQUEST", message: "Invalid attachment id" });
6472
6919
  }
6473
6920
  const res = await cloudFetch2(`/api/kanban/file/${id}`, {
6474
6921
  timeoutMs: 12e4,
6475
6922
  accountId: await attachmentScope(input)
6476
6923
  });
6477
6924
  if (res.status === 404) {
6478
- throw new TRPCError17({ code: "NOT_FOUND", message: `Attachment ${id} not found` });
6925
+ throw new TRPCError19({ code: "NOT_FOUND", message: `Attachment ${id} not found` });
6479
6926
  }
6480
6927
  if (!res.ok) {
6481
- throw new TRPCError17({
6928
+ throw new TRPCError19({
6482
6929
  code: res.status === 403 ? "FORBIDDEN" : "INTERNAL_SERVER_ERROR",
6483
6930
  message: `Cloud refused the attachment (HTTP ${res.status})`
6484
6931
  });
6485
6932
  }
6486
6933
  const declared = Number(res.headers.get("content-length") ?? 0);
6487
6934
  if (declared > MAX_ATTACHMENT_BYTES) {
6488
- throw new TRPCError17({ code: "PAYLOAD_TOO_LARGE", message: "Attachment too large to download" });
6935
+ throw new TRPCError19({ code: "PAYLOAD_TOO_LARGE", message: "Attachment too large to download" });
6489
6936
  }
6490
6937
  const body = Buffer.from(await res.arrayBuffer());
6491
6938
  if (body.length > MAX_ATTACHMENT_BYTES) {
6492
- throw new TRPCError17({ code: "PAYLOAD_TOO_LARGE", message: "Attachment too large to download" });
6939
+ throw new TRPCError19({ code: "PAYLOAD_TOO_LARGE", message: "Attachment too large to download" });
6493
6940
  }
6494
6941
  const contentType = res.headers.get("content-type") ?? "application/octet-stream";
6495
6942
  const disposition = res.headers.get("content-disposition") ?? "";
@@ -6501,21 +6948,21 @@ var kanbanRouter = router({
6501
6948
  });
6502
6949
 
6503
6950
  // ../server/src/trpc/routers/terminal.ts
6504
- import { z as z21 } from "zod";
6505
- import { TRPCError as TRPCError18 } from "@trpc/server";
6951
+ import { z as z24 } from "zod";
6952
+ import { TRPCError as TRPCError20 } from "@trpc/server";
6506
6953
  var terminalRouter = router({
6507
6954
  /** Create a PTY shell session on the target agent */
6508
- create: protectedProcedure.input(z21.object({
6509
- agentId: z21.string().max(128),
6510
- cwd: z21.string().max(512).optional(),
6955
+ create: protectedProcedure.input(z24.object({
6956
+ agentId: z24.string().max(128),
6957
+ cwd: z24.string().max(512).optional(),
6511
6958
  /** Anchors the terminal when no explicit cwd is given. */
6512
- serviceId: z21.string().max(128).optional(),
6513
- projectId: z21.string().max(128).optional(),
6959
+ serviceId: z24.string().max(128).optional(),
6960
+ projectId: z24.string().max(128).optional(),
6514
6961
  /** L'organisation du projet : un id seul peut en nommer un par organisation. */
6515
- orgId: z21.string().max(128).nullable().optional(),
6516
- cols: z21.number().int().min(1).max(512).optional(),
6517
- rows: z21.number().int().min(1).max(256).optional(),
6518
- title: z21.string().max(128).optional()
6962
+ orgId: z24.string().max(128).nullable().optional(),
6963
+ cols: z24.number().int().min(1).max(512).optional(),
6964
+ rows: z24.number().int().min(1).max(256).optional(),
6965
+ title: z24.string().max(128).optional()
6519
6966
  })).mutation(async ({ input }) => {
6520
6967
  const result = await agentManager.agentMutation(
6521
6968
  input.agentId,
@@ -6536,17 +6983,17 @@ var terminalRouter = router({
6536
6983
  return result;
6537
6984
  }),
6538
6985
  /** Les sessions PTY encore vivantes sur l'agent (onglets à retrouver) */
6539
- list: protectedProcedure.input(z21.object({ agentId: z21.string().max(128) })).query(async ({ input }) => {
6986
+ list: protectedProcedure.input(z24.object({ agentId: z24.string().max(128) })).query(async ({ input }) => {
6540
6987
  return agentManager.agentQuery(
6541
6988
  input.agentId,
6542
6989
  "terminal.list"
6543
6990
  );
6544
6991
  }),
6545
6992
  /** Renommer un onglet */
6546
- rename: protectedProcedure.input(z21.object({
6547
- agentId: z21.string().max(128),
6548
- sessionId: z21.string().max(128),
6549
- title: z21.string().max(128)
6993
+ rename: protectedProcedure.input(z24.object({
6994
+ agentId: z24.string().max(128),
6995
+ sessionId: z24.string().max(128),
6996
+ title: z24.string().max(128)
6550
6997
  })).mutation(async ({ input }) => {
6551
6998
  await agentManager.agentMutation(
6552
6999
  input.agentId,
@@ -6556,10 +7003,10 @@ var terminalRouter = router({
6556
7003
  return { ok: true };
6557
7004
  }),
6558
7005
  /** Send keystrokes / data to a PTY session */
6559
- write: protectedProcedure.input(z21.object({
6560
- agentId: z21.string().max(128),
6561
- sessionId: z21.string().max(128),
6562
- data: z21.string().max(65536)
7006
+ write: protectedProcedure.input(z24.object({
7007
+ agentId: z24.string().max(128),
7008
+ sessionId: z24.string().max(128),
7009
+ data: z24.string().max(65536)
6563
7010
  })).mutation(async ({ input }) => {
6564
7011
  await agentManager.agentMutation(
6565
7012
  input.agentId,
@@ -6569,11 +7016,11 @@ var terminalRouter = router({
6569
7016
  return { ok: true };
6570
7017
  }),
6571
7018
  /** Resize the PTY */
6572
- resize: protectedProcedure.input(z21.object({
6573
- agentId: z21.string().max(128),
6574
- sessionId: z21.string().max(128),
6575
- cols: z21.number().int().min(1).max(512),
6576
- rows: z21.number().int().min(1).max(256)
7019
+ resize: protectedProcedure.input(z24.object({
7020
+ agentId: z24.string().max(128),
7021
+ sessionId: z24.string().max(128),
7022
+ cols: z24.number().int().min(1).max(512),
7023
+ rows: z24.number().int().min(1).max(256)
6577
7024
  })).mutation(async ({ input }) => {
6578
7025
  await agentManager.agentMutation(
6579
7026
  input.agentId,
@@ -6583,9 +7030,9 @@ var terminalRouter = router({
6583
7030
  return { ok: true };
6584
7031
  }),
6585
7032
  /** Destroy a PTY session */
6586
- destroy: protectedProcedure.input(z21.object({
6587
- agentId: z21.string().max(128),
6588
- sessionId: z21.string().max(128)
7033
+ destroy: protectedProcedure.input(z24.object({
7034
+ agentId: z24.string().max(128),
7035
+ sessionId: z24.string().max(128)
6589
7036
  })).mutation(async ({ input }) => {
6590
7037
  await agentManager.agentMutation(
6591
7038
  input.agentId,
@@ -6595,14 +7042,14 @@ var terminalRouter = router({
6595
7042
  return { ok: true };
6596
7043
  }),
6597
7044
  /** Stream PTY output — proxied via AgentWSBridge dynamic subscription */
6598
- onData: protectedProcedure.input(z21.object({
6599
- agentId: z21.string().max(128),
6600
- sessionId: z21.string().max(128),
7045
+ onData: protectedProcedure.input(z24.object({
7046
+ agentId: z24.string().max(128),
7047
+ sessionId: z24.string().max(128),
6601
7048
  /** Rejouer l'historique de la session avant le flux temps réel. */
6602
- replay: z21.boolean().optional()
7049
+ replay: z24.boolean().optional()
6603
7050
  })).subscription(async function* ({ input }) {
6604
7051
  const queue = [];
6605
- let resolve4 = null;
7052
+ let resolve5 = null;
6606
7053
  let done = false;
6607
7054
  let unsubscribe;
6608
7055
  try {
@@ -6612,21 +7059,21 @@ var terminalRouter = router({
6612
7059
  { sessionId: input.sessionId, replay: input.replay },
6613
7060
  (data) => {
6614
7061
  if (typeof data === "string") queue.push(data);
6615
- if (resolve4) {
6616
- resolve4();
6617
- resolve4 = null;
7062
+ if (resolve5) {
7063
+ resolve5();
7064
+ resolve5 = null;
6618
7065
  }
6619
7066
  },
6620
7067
  () => {
6621
7068
  done = true;
6622
- if (resolve4) {
6623
- resolve4();
6624
- resolve4 = null;
7069
+ if (resolve5) {
7070
+ resolve5();
7071
+ resolve5 = null;
6625
7072
  }
6626
7073
  }
6627
7074
  );
6628
7075
  } catch (err) {
6629
- throw new TRPCError18({
7076
+ throw new TRPCError20({
6630
7077
  code: "BAD_REQUEST",
6631
7078
  message: `Cannot subscribe to terminal: ${err.message}`
6632
7079
  });
@@ -6637,7 +7084,7 @@ var terminalRouter = router({
6637
7084
  yield queue.shift();
6638
7085
  } else {
6639
7086
  await new Promise((r) => {
6640
- resolve4 = r;
7087
+ resolve5 = r;
6641
7088
  });
6642
7089
  }
6643
7090
  }
@@ -6648,27 +7095,27 @@ var terminalRouter = router({
6648
7095
  });
6649
7096
 
6650
7097
  // ../server/src/trpc/routers/utility.ts
6651
- import { z as z22 } from "zod";
7098
+ import { z as z25 } from "zod";
6652
7099
  var MAX_WAIT_MS = 5 * 60 * 1e3;
6653
7100
  var utilityRouter = router({
6654
- wait: protectedProcedure.input(z22.object({
6655
- ms: z22.number().int().min(0).max(MAX_WAIT_MS)
7101
+ wait: protectedProcedure.input(z25.object({
7102
+ ms: z25.number().int().min(0).max(MAX_WAIT_MS)
6656
7103
  })).mutation(async ({ input }) => {
6657
- await new Promise((resolve4) => setTimeout(resolve4, input.ms));
7104
+ await new Promise((resolve5) => setTimeout(resolve5, input.ms));
6658
7105
  return { waited: input.ms };
6659
7106
  })
6660
7107
  });
6661
7108
 
6662
7109
  // ../server/src/trpc/routers/wiki.ts
6663
- import { z as z23 } from "zod";
6664
- import { TRPCError as TRPCError19 } from "@trpc/server";
6665
- import { readFile as readFile3, readdir as readdir2, stat as stat4, realpath, writeFile as writeFile5, mkdir as mkdir6, rename, rm as rm2 } from "fs/promises";
6666
- import { resolve as resolvePath, join as join8, relative as relative2, sep, dirname as dirname4 } from "path";
6667
- var SCOPE_SCHEMA = z23.union([
6668
- z23.literal("global"),
6669
- z23.string().regex(/^service:[A-Za-z0-9_-]{1,128}$/, "Invalid service scope")
7110
+ import { z as z26 } from "zod";
7111
+ import { TRPCError as TRPCError21 } from "@trpc/server";
7112
+ import { readFile as readFile5, readdir as readdir2, stat as stat4, realpath as realpath2, writeFile as writeFile5, mkdir as mkdir7, rename, rm as rm2 } from "fs/promises";
7113
+ import { resolve as resolvePath, join as join9, relative as relative2, sep, dirname as dirname5 } from "path";
7114
+ var SCOPE_SCHEMA = z26.union([
7115
+ z26.literal("global"),
7116
+ z26.string().regex(/^service:[A-Za-z0-9_-]{1,128}$/, "Invalid service scope")
6670
7117
  ]);
6671
- var REL_PATH_SCHEMA = z23.string().min(1).max(512).regex(/^[A-Za-z0-9_./-]+$/).refine((p) => !p.includes("..") && !p.startsWith("/"), {
7118
+ var REL_PATH_SCHEMA = z26.string().min(1).max(512).regex(/^[A-Za-z0-9_./-]+$/).refine((p) => !p.includes("..") && !p.startsWith("/"), {
6672
7119
  message: "Path must be a relative path without traversal"
6673
7120
  });
6674
7121
  var MAX_FILES = 500;
@@ -6688,36 +7135,36 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
6688
7135
  "target",
6689
7136
  "out"
6690
7137
  ]);
6691
- var DocsEntrySchema = z23.lazy(() => z23.union([
6692
- z23.object({ type: z23.literal("file"), name: z23.string(), path: z23.string() }),
6693
- z23.object({ type: z23.literal("dir"), name: z23.string(), path: z23.string(), children: z23.array(DocsEntrySchema) })
7138
+ var DocsEntrySchema = z26.lazy(() => z26.union([
7139
+ z26.object({ type: z26.literal("file"), name: z26.string(), path: z26.string() }),
7140
+ z26.object({ type: z26.literal("dir"), name: z26.string(), path: z26.string(), children: z26.array(DocsEntrySchema) })
6694
7141
  ]));
6695
7142
  async function resolveScope(scope) {
6696
7143
  if (scope === "global") {
6697
7144
  const settings = await settingsManager.getSettings();
6698
7145
  const sub = (settings.globalDocsPath?.trim() || "wiki").replace(/^[\\/]+|[\\/]+$/g, "");
6699
7146
  if (sub.includes("..") || sub.includes("\0")) {
6700
- throw new TRPCError19({ code: "BAD_REQUEST", message: "Sous-chemin invalide" });
7147
+ throw new TRPCError21({ code: "BAD_REQUEST", message: "Sous-chemin invalide" });
6701
7148
  }
6702
7149
  const dataDirAbs = resolvePath(env.DATA_DIR);
6703
- await mkdir6(dataDirAbs, { recursive: true });
6704
- const base = dirname4(await realpath(dataDirAbs));
7150
+ await mkdir7(dataDirAbs, { recursive: true });
7151
+ const base = dirname5(await realpath2(dataDirAbs));
6705
7152
  const target = resolvePath(base, sub);
6706
7153
  if (target !== base && !(target + sep).startsWith(base + sep)) {
6707
- throw new TRPCError19({ code: "FORBIDDEN", message: "Hors du dossier de donn\xE9es du serveur" });
7154
+ throw new TRPCError21({ code: "FORBIDDEN", message: "Hors du dossier de donn\xE9es du serveur" });
6708
7155
  }
6709
- await mkdir6(target, { recursive: true });
6710
- const root = await realpath(target);
7156
+ await mkdir7(target, { recursive: true });
7157
+ const root = await realpath2(target);
6711
7158
  if (root !== base && !(root + sep).startsWith(base + sep)) {
6712
- throw new TRPCError19({ code: "FORBIDDEN", message: "Le dossier wiki r\xE9sout hors du dossier de donn\xE9es" });
7159
+ throw new TRPCError21({ code: "FORBIDDEN", message: "Le dossier wiki r\xE9sout hors du dossier de donn\xE9es" });
6713
7160
  }
6714
7161
  return { mode: "local", root };
6715
7162
  }
6716
7163
  const serviceId = scope.slice("service:".length);
6717
7164
  const service = await serviceStore.get(serviceId);
6718
- if (!service) throw new TRPCError19({ code: "NOT_FOUND", message: "Service introuvable" });
7165
+ if (!service) throw new TRPCError21({ code: "NOT_FOUND", message: "Service introuvable" });
6719
7166
  const agentId = await resolveEffectiveAgentId(service);
6720
- if (!agentId) throw new TRPCError19({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9 pour ce service" });
7167
+ if (!agentId) throw new TRPCError21({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9 pour ce service" });
6721
7168
  const cwd = await resolveWorkspaceCwd({ serviceId, override: service.docsPath?.trim() || void 0 });
6722
7169
  return { mode: "agent", agentId, cwd };
6723
7170
  }
@@ -6733,7 +7180,7 @@ async function walkLocal(root, dir, depth, counter) {
6733
7180
  for (const e of entries2) {
6734
7181
  if (counter.n >= MAX_FILES) break;
6735
7182
  if (e.name.startsWith(".") && e.name !== ".") continue;
6736
- const abs = join8(dir, e.name);
7183
+ const abs = join9(dir, e.name);
6737
7184
  const rel = relative2(root, abs).split(sep).join("/");
6738
7185
  if (e.isDirectory()) {
6739
7186
  if (IGNORED_DIRS.has(e.name)) continue;
@@ -6752,35 +7199,35 @@ async function walkLocal(root, dir, depth, counter) {
6752
7199
  }
6753
7200
  async function resolveSafeLocal(root, relPath, requireExist) {
6754
7201
  if (relPath.includes("..") || relPath.startsWith("/")) {
6755
- throw new TRPCError19({ code: "BAD_REQUEST", message: "Chemin invalide" });
7202
+ throw new TRPCError21({ code: "BAD_REQUEST", message: "Chemin invalide" });
6756
7203
  }
6757
7204
  const target = resolvePath(root, relPath);
6758
7205
  if (requireExist) {
6759
7206
  let real;
6760
7207
  try {
6761
- real = await realpath(target);
7208
+ real = await realpath2(target);
6762
7209
  } catch {
6763
- throw new TRPCError19({ code: "NOT_FOUND", message: "Chemin introuvable" });
7210
+ throw new TRPCError21({ code: "NOT_FOUND", message: "Chemin introuvable" });
6764
7211
  }
6765
7212
  if (real !== root && !(real + sep).startsWith(root + sep)) {
6766
- throw new TRPCError19({ code: "FORBIDDEN", message: "Hors du dossier autoris\xE9" });
7213
+ throw new TRPCError21({ code: "FORBIDDEN", message: "Hors du dossier autoris\xE9" });
6767
7214
  }
6768
7215
  return real;
6769
7216
  }
6770
- const parent = dirname4(target);
7217
+ const parent = dirname5(target);
6771
7218
  let realParent;
6772
7219
  try {
6773
- realParent = await realpath(parent);
7220
+ realParent = await realpath2(parent);
6774
7221
  } catch {
6775
7222
  realParent = parent;
6776
7223
  }
6777
7224
  if (realParent !== root && !(realParent + sep).startsWith(root + sep)) {
6778
- throw new TRPCError19({ code: "FORBIDDEN", message: "Hors du dossier autoris\xE9" });
7225
+ throw new TRPCError21({ code: "FORBIDDEN", message: "Hors du dossier autoris\xE9" });
6779
7226
  }
6780
7227
  return resolvePath(realParent, target.slice(parent.length).replace(/^[\\/]+/, ""));
6781
7228
  }
6782
7229
  var wikiRouter = router({
6783
- list: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA })).output(z23.object({ root: z23.string(), entries: z23.array(DocsEntrySchema), truncated: z23.boolean() })).query(async ({ input }) => {
7230
+ list: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA })).output(z26.object({ root: z26.string(), entries: z26.array(DocsEntrySchema), truncated: z26.boolean() })).query(async ({ input }) => {
6784
7231
  const target = await resolveScope(input.scope);
6785
7232
  if (target.mode === "agent") {
6786
7233
  return agentManager.agentQuery(target.agentId, "wiki.listRepoMarkdown", { cwd: target.cwd });
@@ -6789,9 +7236,9 @@ var wikiRouter = router({
6789
7236
  const entries2 = await walkLocal(target.root, target.root, 0, counter);
6790
7237
  return { root: target.root, entries: entries2, truncated: counter.n >= MAX_FILES };
6791
7238
  }),
6792
- read: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).output(z23.object({ path: z23.string(), content: z23.string() })).query(async ({ input }) => {
7239
+ read: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).output(z26.object({ path: z26.string(), content: z26.string() })).query(async ({ input }) => {
6793
7240
  if (!/\.(md|markdown)$/i.test(input.path)) {
6794
- throw new TRPCError19({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
7241
+ throw new TRPCError21({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
6795
7242
  }
6796
7243
  const target = await resolveScope(input.scope);
6797
7244
  if (target.mode === "agent") {
@@ -6799,27 +7246,27 @@ var wikiRouter = router({
6799
7246
  }
6800
7247
  const file = await resolveSafeLocal(target.root, input.path, true);
6801
7248
  const s = await stat4(file);
6802
- if (!s.isFile()) throw new TRPCError19({ code: "BAD_REQUEST", message: "Pas un fichier" });
6803
- if (s.size > MAX_FILE_BYTES) throw new TRPCError19({ code: "PAYLOAD_TOO_LARGE", message: "Fichier trop volumineux" });
6804
- const content = await readFile3(file, "utf-8");
7249
+ if (!s.isFile()) throw new TRPCError21({ code: "BAD_REQUEST", message: "Pas un fichier" });
7250
+ if (s.size > MAX_FILE_BYTES) throw new TRPCError21({ code: "PAYLOAD_TOO_LARGE", message: "Fichier trop volumineux" });
7251
+ const content = await readFile5(file, "utf-8");
6805
7252
  return { path: input.path, content };
6806
7253
  }),
6807
- write: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA, content: z23.string().max(5e6) })).mutation(async ({ input }) => {
7254
+ write: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA, content: z26.string().max(5e6) })).mutation(async ({ input }) => {
6808
7255
  if (!/\.(md|markdown)$/i.test(input.path)) {
6809
- throw new TRPCError19({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
7256
+ throw new TRPCError21({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
6810
7257
  }
6811
7258
  const target = await resolveScope(input.scope);
6812
7259
  if (target.mode === "agent") {
6813
7260
  return agentManager.agentMutation(target.agentId, "wiki.writeMarkdown", { cwd: target.cwd, path: input.path, content: input.content });
6814
7261
  }
6815
7262
  const file = await resolveSafeLocal(target.root, input.path, false);
6816
- await mkdir6(dirname4(file), { recursive: true });
7263
+ await mkdir7(dirname5(file), { recursive: true });
6817
7264
  await writeFile5(file, input.content, "utf-8");
6818
7265
  return { path: input.path, ok: true };
6819
7266
  }),
6820
- createFile: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
7267
+ createFile: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
6821
7268
  if (!/\.(md|markdown)$/i.test(input.path)) {
6822
- throw new TRPCError19({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
7269
+ throw new TRPCError21({ code: "BAD_REQUEST", message: "Seuls les .md/.markdown sont autoris\xE9s" });
6823
7270
  }
6824
7271
  const target = await resolveScope(input.scope);
6825
7272
  if (target.mode === "agent") {
@@ -6828,24 +7275,24 @@ var wikiRouter = router({
6828
7275
  const file = await resolveSafeLocal(target.root, input.path, false);
6829
7276
  try {
6830
7277
  await stat4(file);
6831
- throw new TRPCError19({ code: "CONFLICT", message: "Fichier d\xE9j\xE0 existant" });
7278
+ throw new TRPCError21({ code: "CONFLICT", message: "Fichier d\xE9j\xE0 existant" });
6832
7279
  } catch (err) {
6833
- if (err instanceof TRPCError19) throw err;
7280
+ if (err instanceof TRPCError21) throw err;
6834
7281
  }
6835
- await mkdir6(dirname4(file), { recursive: true });
7282
+ await mkdir7(dirname5(file), { recursive: true });
6836
7283
  await writeFile5(file, "", "utf-8");
6837
7284
  return { path: input.path, ok: true };
6838
7285
  }),
6839
- createDir: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
7286
+ createDir: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
6840
7287
  const target = await resolveScope(input.scope);
6841
7288
  if (target.mode === "agent") {
6842
7289
  return agentManager.agentMutation(target.agentId, "wiki.createDir", { cwd: target.cwd, path: input.path });
6843
7290
  }
6844
7291
  const dir = await resolveSafeLocal(target.root, input.path, false);
6845
- await mkdir6(dir, { recursive: true });
7292
+ await mkdir7(dir, { recursive: true });
6846
7293
  return { path: input.path, ok: true };
6847
7294
  }),
6848
- rename: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, fromPath: REL_PATH_SCHEMA, toPath: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
7295
+ rename: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, fromPath: REL_PATH_SCHEMA, toPath: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
6849
7296
  const target = await resolveScope(input.scope);
6850
7297
  if (target.mode === "agent") {
6851
7298
  return agentManager.agentMutation(target.agentId, "wiki.renamePath", { cwd: target.cwd, fromPath: input.fromPath, toPath: input.toPath });
@@ -6854,34 +7301,34 @@ var wikiRouter = router({
6854
7301
  const to = await resolveSafeLocal(target.root, input.toPath, false);
6855
7302
  try {
6856
7303
  await stat4(to);
6857
- throw new TRPCError19({ code: "CONFLICT", message: "La cible existe d\xE9j\xE0" });
7304
+ throw new TRPCError21({ code: "CONFLICT", message: "La cible existe d\xE9j\xE0" });
6858
7305
  } catch (err) {
6859
- if (err instanceof TRPCError19) throw err;
7306
+ if (err instanceof TRPCError21) throw err;
6860
7307
  }
6861
- await mkdir6(dirname4(to), { recursive: true });
7308
+ await mkdir7(dirname5(to), { recursive: true });
6862
7309
  await rename(from, to);
6863
7310
  return { fromPath: input.fromPath, toPath: input.toPath, ok: true };
6864
7311
  }),
6865
- ensureRoot: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA })).mutation(async ({ input }) => {
7312
+ ensureRoot: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA })).mutation(async ({ input }) => {
6866
7313
  if (input.scope === "global") {
6867
7314
  await resolveScope("global");
6868
7315
  return { ok: true };
6869
7316
  }
6870
7317
  const serviceId = input.scope.slice("service:".length);
6871
7318
  const service = await serviceStore.get(serviceId);
6872
- if (!service) throw new TRPCError19({ code: "NOT_FOUND", message: "Service introuvable" });
7319
+ if (!service) throw new TRPCError21({ code: "NOT_FOUND", message: "Service introuvable" });
6873
7320
  const agentId = await resolveEffectiveAgentId(service);
6874
- if (!agentId) throw new TRPCError19({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9" });
7321
+ if (!agentId) throw new TRPCError21({ code: "BAD_REQUEST", message: "Aucun agent configur\xE9" });
6875
7322
  const cwd = await resolveWorkspaceCwd({ serviceId, override: service.docsPath?.trim() || void 0 });
6876
7323
  return agentManager.agentMutation(agentId, "wiki.ensureRoot", { cwd });
6877
7324
  }),
6878
- delete: protectedProcedure.input(z23.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
7325
+ delete: protectedProcedure.input(z26.object({ scope: SCOPE_SCHEMA, path: REL_PATH_SCHEMA })).mutation(async ({ input }) => {
6879
7326
  const target = await resolveScope(input.scope);
6880
7327
  if (target.mode === "agent") {
6881
7328
  return agentManager.agentMutation(target.agentId, "wiki.deletePath", { cwd: target.cwd, path: input.path });
6882
7329
  }
6883
7330
  const fp = await resolveSafeLocal(target.root, input.path, true);
6884
- if (fp === target.root) throw new TRPCError19({ code: "FORBIDDEN", message: "Impossible de supprimer la racine" });
7331
+ if (fp === target.root) throw new TRPCError21({ code: "FORBIDDEN", message: "Impossible de supprimer la racine" });
6885
7332
  await rm2(fp, { recursive: true, force: true });
6886
7333
  return { path: input.path, ok: true };
6887
7334
  })
@@ -6903,14 +7350,14 @@ var configRouter = router({
6903
7350
  });
6904
7351
 
6905
7352
  // ../server/src/trpc/routers/cloud-sync.ts
6906
- import { z as z24 } from "zod";
6907
- import { TRPCError as TRPCError20 } from "@trpc/server";
7353
+ import { z as z27 } from "zod";
7354
+ import { TRPCError as TRPCError22 } from "@trpc/server";
6908
7355
 
6909
7356
  // ../server/src/services/org-sync-scope.ts
6910
7357
  import { access } from "fs/promises";
6911
- import { join as join9 } from "path";
7358
+ import { join as join10 } from "path";
6912
7359
  async function isMaterialized(root) {
6913
- return access(join9(root, "project.json")).then(() => true, () => false);
7360
+ return access(join10(root, "project.json")).then(() => true, () => false);
6914
7361
  }
6915
7362
  async function orgsToSync() {
6916
7363
  const [stamps, stampedAccounts, accounts] = await Promise.all([
@@ -6985,23 +7432,23 @@ async function reconcileOrgCanary(orgId) {
6985
7432
  }
6986
7433
 
6987
7434
  // ../server/src/services/cloud-sync-bridge.ts
6988
- import { randomUUID as randomUUID7 } from "crypto";
6989
- import { mkdir as mkdir7, rm as rm3 } from "fs/promises";
7435
+ import { randomUUID as randomUUID8 } from "crypto";
7436
+ import { mkdir as mkdir8, rm as rm3 } from "fs/promises";
6990
7437
  import * as Y2 from "yjs";
6991
7438
 
6992
7439
  // ../server/src/services/sync-doc-cache.ts
6993
7440
  import { readFileSync as readFileSync2, renameSync, rmSync, writeFileSync, mkdirSync } from "fs";
6994
- import { readFile as readFile4 } from "fs/promises";
6995
- import { dirname as dirname5, join as join10 } from "path";
6996
- import { randomUUID as randomUUID6 } from "crypto";
7441
+ import { readFile as readFile6 } from "fs/promises";
7442
+ import { dirname as dirname6, join as join11 } from "path";
7443
+ import { randomUUID as randomUUID7 } from "crypto";
6997
7444
  import * as Y from "yjs";
6998
- var FILE = "sync-doc.bin";
7445
+ var FILE2 = "sync-doc.bin";
6999
7446
  function cachePath(orgId) {
7000
- return join10(orgVaultRoot(orgId), FILE);
7447
+ return join11(orgVaultRoot(orgId), FILE2);
7001
7448
  }
7002
7449
  async function readSyncDoc(orgId) {
7003
7450
  try {
7004
- return new Uint8Array(await readFile4(cachePath(orgId)));
7451
+ return new Uint8Array(await readFile6(cachePath(orgId)));
7005
7452
  } catch {
7006
7453
  return null;
7007
7454
  }
@@ -7024,8 +7471,8 @@ function writeSyncDocSync(orgId, state) {
7024
7471
  existing = new Uint8Array(readFileSync2(path));
7025
7472
  } catch {
7026
7473
  }
7027
- mkdirSync(dirname5(path), { recursive: true });
7028
- const tmp = `${path}.${process.pid}.${randomUUID6()}.tmp`;
7474
+ mkdirSync(dirname6(path), { recursive: true });
7475
+ const tmp = `${path}.${process.pid}.${randomUUID7()}.tmp`;
7029
7476
  try {
7030
7477
  writeFileSync(tmp, mergeWith(existing, state), { mode: 384 });
7031
7478
  renameSync(tmp, path);
@@ -7078,7 +7525,7 @@ var OrgSyncConnection = class {
7078
7525
  services = getCollection(this.doc, "services");
7079
7526
  environments = getCollection(this.doc, "environments");
7080
7527
  logParsers = getCollection(this.doc, PARSERS);
7081
- conn = randomUUID7();
7528
+ conn = randomUUID8();
7082
7529
  link;
7083
7530
  stopped = false;
7084
7531
  initializing = true;
@@ -7128,8 +7575,8 @@ var OrgSyncConnection = class {
7128
7575
  * Réglée quand le document tient l'état d'ouverture du cloud — ou quand il ne
7129
7576
  * l'aura pas : session refusée, app trop ancienne, connexion arrêtée.
7130
7577
  */
7131
- opened = new Promise((resolve4) => {
7132
- this.markOpened = resolve4;
7578
+ opened = new Promise((resolve5) => {
7579
+ this.markOpened = resolve5;
7133
7580
  });
7134
7581
  /** Store applications started by remote events and not finished yet. */
7135
7582
  applying = /* @__PURE__ */ new Set();
@@ -7511,7 +7958,7 @@ var OrgSyncConnection = class {
7511
7958
  }
7512
7959
  const isLocal = existing?.orgId === this.link.orgId;
7513
7960
  if (!inDoc && !isLocal) return false;
7514
- await mkdir7(projectVaultRoot(this.link.orgId, projectId), { recursive: true });
7961
+ await mkdir8(projectVaultRoot(this.link.orgId, projectId), { recursive: true });
7515
7962
  this.tracked.add(projectId);
7516
7963
  if (inDoc) await this.pullFromDoc([projectId]);
7517
7964
  else await this.reconcileLocalToDoc();
@@ -7990,8 +8437,8 @@ var CloudSyncBridge = class {
7990
8437
  let timer;
7991
8438
  await Promise.race([
7992
8439
  conn.opened,
7993
- new Promise((resolve4) => {
7994
- timer = setTimeout(resolve4, timeoutMs);
8440
+ new Promise((resolve5) => {
8441
+ timer = setTimeout(resolve5, timeoutMs);
7995
8442
  })
7996
8443
  ]);
7997
8444
  clearTimeout(timer);
@@ -8008,9 +8455,9 @@ var CloudSyncBridge = class {
8008
8455
  var cloudSyncBridge = new CloudSyncBridge();
8009
8456
 
8010
8457
  // ../server/src/services/key-adoption.ts
8011
- import { readdir as readdir3, readFile as readFile5, writeFile as writeFile6, mkdir as mkdir8, copyFile, rename as rename2, stat as stat5 } from "fs/promises";
8012
- import { dirname as dirname6, join as join11 } from "path";
8013
- import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes2 } from "crypto";
8458
+ import { readdir as readdir3, readFile as readFile7, writeFile as writeFile6, mkdir as mkdir9, copyFile, rename as rename2, stat as stat5 } from "fs/promises";
8459
+ import { dirname as dirname7, join as join12 } from "path";
8460
+ import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes3 } from "crypto";
8014
8461
  var ALGORITHM = "aes-256-gcm";
8015
8462
  function isAgentTriplet(value) {
8016
8463
  const hex = (v) => typeof v === "string" && /^[0-9a-f]+$/i.test(v);
@@ -8022,7 +8469,7 @@ function decryptTriplet(key, t) {
8022
8469
  return decipher.update(t.encrypted, "hex", "utf-8") + decipher.final("utf-8");
8023
8470
  }
8024
8471
  function encryptTriplet(key, plaintext) {
8025
- const iv = randomBytes2(16);
8472
+ const iv = randomBytes3(16);
8026
8473
  const cipher = createCipheriv2(ALGORITHM, key, iv);
8027
8474
  const encrypted = cipher.update(plaintext, "utf-8", "hex") + cipher.final("hex");
8028
8475
  return { encrypted, iv: iv.toString("hex"), authTag: cipher.getAuthTag().toString("hex") };
@@ -8076,7 +8523,7 @@ async function jsonNamesIn(dir) {
8076
8523
  async function jsonFilesIn(root) {
8077
8524
  const names = await jsonNamesIn(root);
8078
8525
  for (const sub of ENCRYPTED_SUBDIRS) {
8079
- names.push(...(await jsonNamesIn(join11(root, sub))).map((n) => join11(sub, n)));
8526
+ names.push(...(await jsonNamesIn(join12(root, sub))).map((n) => join12(sub, n)));
8080
8527
  }
8081
8528
  return names;
8082
8529
  }
@@ -8114,10 +8561,10 @@ async function adoptOrgKey(orgId, newKey, masterPassword, now) {
8114
8561
  const touchedRoots = /* @__PURE__ */ new Set();
8115
8562
  for (const vaultRoot of await rootsOfOrg(orgId)) {
8116
8563
  for (const name of await jsonFilesIn(vaultRoot)) {
8117
- const path = join11(vaultRoot, name);
8564
+ const path = join12(vaultRoot, name);
8118
8565
  let parsed;
8119
8566
  try {
8120
- parsed = JSON.parse(await readFile5(path, "utf-8"));
8567
+ parsed = JSON.parse(await readFile7(path, "utf-8"));
8121
8568
  } catch {
8122
8569
  continue;
8123
8570
  }
@@ -8131,15 +8578,15 @@ async function adoptOrgKey(orgId, newKey, masterPassword, now) {
8131
8578
  const stamp = now.toISOString().replace(/[:.]/g, "-").substring(0, 19);
8132
8579
  const backups = [];
8133
8580
  for (const vaultRoot of touchedRoots) {
8134
- const backupDir = join11(vaultRoot, `backup-key-${stamp}`);
8135
- await mkdir8(backupDir, { recursive: true });
8581
+ const backupDir = join12(vaultRoot, `backup-key-${stamp}`);
8582
+ await mkdir9(backupDir, { recursive: true });
8136
8583
  for (const name of await jsonFilesIn(vaultRoot)) {
8137
- await mkdir8(dirname6(join11(backupDir, name)), { recursive: true });
8138
- await copyFile(join11(vaultRoot, name), join11(backupDir, name));
8584
+ await mkdir9(dirname7(join12(backupDir, name)), { recursive: true });
8585
+ await copyFile(join12(vaultRoot, name), join12(backupDir, name));
8139
8586
  }
8140
8587
  for (const name of [".encryption.key.enc", ".runeya-verify"]) {
8141
- if (await exists(join11(vaultRoot, name))) {
8142
- await copyFile(join11(vaultRoot, name), join11(backupDir, name));
8588
+ if (await exists(join12(vaultRoot, name))) {
8589
+ await copyFile(join12(vaultRoot, name), join12(backupDir, name));
8143
8590
  }
8144
8591
  }
8145
8592
  backups.push(backupDir);
@@ -8158,7 +8605,7 @@ async function adoptOrgKey(orgId, newKey, masterPassword, now) {
8158
8605
  return { converted: pending.map(({ path, values }) => ({ file: path, values })), backups };
8159
8606
  }
8160
8607
  async function resetOrgKeyToFresh(orgId, masterPassword, now) {
8161
- const freshKey = randomBytes2(32);
8608
+ const freshKey = randomBytes3(32);
8162
8609
  if (isVaultLocked(orgId) || !getEncryptionKey(orgId)) {
8163
8610
  await readEncryptedKey(await keyRoot(), masterPassword);
8164
8611
  await installOrgKey(orgId, freshKey, masterPassword);
@@ -8194,7 +8641,7 @@ async function assertOrgReadable(orgId) {
8194
8641
  await assertOrgVaultReadable(orgId);
8195
8642
  } catch (err) {
8196
8643
  if (err instanceof OrgVaultUnreadableError) {
8197
- throw new TRPCError20({ code: "FORBIDDEN", message: err.message });
8644
+ throw new TRPCError22({ code: "FORBIDDEN", message: err.message });
8198
8645
  }
8199
8646
  throw err;
8200
8647
  }
@@ -8204,10 +8651,10 @@ async function assertOrgKey(orgId) {
8204
8651
  await reconcileOrgCanary(orgId);
8205
8652
  } catch (err) {
8206
8653
  if (err instanceof OrgKeyMismatchError) {
8207
- throw new TRPCError20({ code: "FORBIDDEN", message: err.message });
8654
+ throw new TRPCError22({ code: "FORBIDDEN", message: err.message });
8208
8655
  }
8209
8656
  if (err instanceof CanaryUnavailableError) {
8210
- throw new TRPCError20({ code: "PRECONDITION_FAILED", message: err.message });
8657
+ throw new TRPCError22({ code: "PRECONDITION_FAILED", message: err.message });
8211
8658
  }
8212
8659
  throw err;
8213
8660
  }
@@ -8224,14 +8671,14 @@ var cloudSyncRouter = router({
8224
8671
  * "open this project" is the whole gesture.
8225
8672
  */
8226
8673
  trackProject: protectedProcedure.input(
8227
- z24.object({
8228
- orgId: z24.string().min(1).max(128),
8229
- cloudUrl: z24.string().url().max(2048),
8230
- projectId: z24.string().min(1).max(128),
8674
+ z27.object({
8675
+ orgId: z27.string().min(1).max(128),
8676
+ cloudUrl: z27.string().url().max(2048),
8677
+ projectId: z27.string().min(1).max(128),
8231
8678
  // Le compte sous lequel cette organisation a été listée. Tamponné avec
8232
8679
  // elle : c'est ce qui permettra à tout appel ultérieur de retrouver le
8233
8680
  // bon token sans qu'on ait à le lui redire.
8234
- accountId: z24.string().max(64).optional()
8681
+ accountId: z27.string().max(64).optional()
8235
8682
  })
8236
8683
  ).mutation(async ({ input }) => {
8237
8684
  await assertOrgReadable(input.orgId);
@@ -8253,7 +8700,7 @@ var cloudSyncRouter = router({
8253
8700
  return { pulled, state: syncState(input.orgId) };
8254
8701
  } catch (err) {
8255
8702
  if (err instanceof ProjectHeldElsewhereError) {
8256
- throw new TRPCError20({ code: "CONFLICT", message: err.code, cause: err });
8703
+ throw new TRPCError22({ code: "CONFLICT", message: err.code, cause: err });
8257
8704
  }
8258
8705
  throw err;
8259
8706
  }
@@ -8272,22 +8719,22 @@ var cloudSyncRouter = router({
8272
8719
  * fusionné.
8273
8720
  */
8274
8721
  publishProject: protectedProcedure.input(
8275
- z24.object({
8276
- orgId: z24.string().min(1).max(128),
8277
- cloudUrl: z24.string().url().max(2048),
8278
- projectId: z24.string().min(1).max(128),
8722
+ z27.object({
8723
+ orgId: z27.string().min(1).max(128),
8724
+ cloudUrl: z27.string().url().max(2048),
8725
+ projectId: z27.string().min(1).max(128),
8279
8726
  // Le compte sous lequel cette organisation a été listée. Tamponné avec
8280
8727
  // elle : c'est ce qui permettra à tout appel ultérieur de retrouver le
8281
8728
  // bon token sans qu'on ait à le lui redire.
8282
- accountId: z24.string().max(64).optional()
8729
+ accountId: z27.string().max(64).optional()
8283
8730
  })
8284
8731
  ).mutation(async ({ input }) => {
8285
8732
  const local = await projectStore.get({ id: input.projectId, orgId: null });
8286
8733
  if (!local) {
8287
- throw new TRPCError20({ code: "NOT_FOUND", message: "PROJECT_NOT_LOCAL" });
8734
+ throw new TRPCError22({ code: "NOT_FOUND", message: "PROJECT_NOT_LOCAL" });
8288
8735
  }
8289
8736
  if (await projectStore.get({ id: input.projectId, orgId: input.orgId })) {
8290
- throw new TRPCError20({ code: "CONFLICT", message: "PROJECT_ALREADY_IN_ORG" });
8737
+ throw new TRPCError22({ code: "CONFLICT", message: "PROJECT_ALREADY_IN_ORG" });
8291
8738
  }
8292
8739
  await assertOrgReadable(input.orgId);
8293
8740
  const connected = cloudSyncBridge.states().some((s) => s.orgId === input.orgId);
@@ -8312,7 +8759,7 @@ var cloudSyncRouter = router({
8312
8759
  await cloudSyncBridge.followProject(input.orgId, input.projectId);
8313
8760
  } catch (err) {
8314
8761
  if (err instanceof ProjectHeldElsewhereError) {
8315
- throw new TRPCError20({ code: "CONFLICT", message: err.code, cause: err });
8762
+ throw new TRPCError22({ code: "CONFLICT", message: err.code, cause: err });
8316
8763
  }
8317
8764
  throw err;
8318
8765
  }
@@ -8326,16 +8773,16 @@ var cloudSyncRouter = router({
8326
8773
  * publié. Le cloud, lui, ne peut pas répondre — il n'a que du chiffré sous
8327
8774
  * les yeux — donc le canari est rapatrié et jugé ici.
8328
8775
  */
8329
- checkOrgKey: protectedProcedure.input(z24.object({
8330
- orgId: z24.string().min(1).max(128),
8331
- keyHex: z24.string().length(64).regex(/^[0-9a-f]+$/i)
8776
+ checkOrgKey: protectedProcedure.input(z27.object({
8777
+ orgId: z27.string().min(1).max(128),
8778
+ keyHex: z27.string().length(64).regex(/^[0-9a-f]+$/i)
8332
8779
  })).query(async ({ input }) => {
8333
8780
  const res = await cloudFetch2(
8334
8781
  `/api/auth/organization/canary?organizationId=${encodeURIComponent(input.orgId)}`,
8335
8782
  { method: "GET", accountId: await accountForOrgScope(input.orgId) }
8336
8783
  );
8337
8784
  if (!res.ok) {
8338
- throw new TRPCError20({ code: "BAD_GATEWAY", message: await cloudMessage(res) || `CANARY_READ_FAILED (${res.status})` });
8785
+ throw new TRPCError22({ code: "BAD_GATEWAY", message: await cloudMessage(res) || `CANARY_READ_FAILED (${res.status})` });
8339
8786
  }
8340
8787
  const { canary } = await res.json();
8341
8788
  if (typeof canary !== "string") return { matches: false, published: false };
@@ -8350,7 +8797,7 @@ var cloudSyncRouter = router({
8350
8797
  * que l'interface puisse proposer ni l'adoption ni la remise à zéro — le seul
8351
8798
  * chemin qui la répare. Rien n'est écrit ici, ni sur le disque ni sur le cloud.
8352
8799
  */
8353
- orgKeyStatus: protectedProcedure.input(z24.object({ orgId: z24.string().min(1).max(128) })).query(async ({ input }) => {
8800
+ orgKeyStatus: protectedProcedure.input(z27.object({ orgId: z27.string().min(1).max(128) })).query(async ({ input }) => {
8354
8801
  const key = getEncryptionKey(input.orgId);
8355
8802
  if (!key) return { status: "unknown" };
8356
8803
  const res = await cloudFetch2(
@@ -8373,10 +8820,10 @@ var cloudSyncRouter = router({
8373
8820
  * Bornée au coffre de l'organisation et à ceux de ses projets : les autres
8374
8821
  * organisations et la machine gardent leur clé.
8375
8822
  */
8376
- adoptOrgKey: protectedProcedure.input(z24.object({
8377
- orgId: z24.string().min(1).max(128),
8378
- keyHex: z24.string().length(64).regex(/^[0-9a-f]+$/i),
8379
- masterPassword: z24.string().min(1).max(1024)
8823
+ adoptOrgKey: protectedProcedure.input(z27.object({
8824
+ orgId: z27.string().min(1).max(128),
8825
+ keyHex: z27.string().length(64).regex(/^[0-9a-f]+$/i),
8826
+ masterPassword: z27.string().min(1).max(1024)
8380
8827
  })).mutation(async ({ input }) => {
8381
8828
  const report = await adoptOrgKey(
8382
8829
  input.orgId,
@@ -8406,14 +8853,14 @@ var cloudSyncRouter = router({
8406
8853
  * Ce qui a déjà été synchronisé sous l'ancienne clé de référence reste
8407
8854
  * illisible : aucune clé d'ici ne l'ouvre. L'interface le dit avant.
8408
8855
  */
8409
- resetOrgKey: protectedProcedure.input(z24.object({
8410
- orgId: z24.string().min(1).max(128),
8411
- masterPassword: z24.string().min(1).max(1024)
8856
+ resetOrgKey: protectedProcedure.input(z27.object({
8857
+ orgId: z27.string().min(1).max(128),
8858
+ masterPassword: z27.string().min(1).max(1024)
8412
8859
  })).mutation(async ({ input }) => {
8413
8860
  const { keyHex } = await resetOrgKeyToFresh(input.orgId, input.masterPassword, /* @__PURE__ */ new Date());
8414
8861
  const canary = await readVerifyFile(orgVaultRoot(input.orgId));
8415
8862
  if (!canary) {
8416
- throw new TRPCError20({
8863
+ throw new TRPCError22({
8417
8864
  code: "PRECONDITION_FAILED",
8418
8865
  message: "Le coffre de cette organisation n'a pas re\xE7u sa cl\xE9."
8419
8866
  });
@@ -8425,7 +8872,7 @@ var cloudSyncRouter = router({
8425
8872
  });
8426
8873
  if (!res.ok) {
8427
8874
  const detail = await cloudMessage(res);
8428
- throw new TRPCError20({
8875
+ throw new TRPCError22({
8429
8876
  code: res.status === 403 ? "FORBIDDEN" : "BAD_GATEWAY",
8430
8877
  message: detail || `ORG_KEY_RESET_FAILED (${res.status})`
8431
8878
  });
@@ -8441,9 +8888,9 @@ var cloudSyncRouter = router({
8441
8888
  * every other member and for this one on another machine.
8442
8889
  */
8443
8890
  untrackProject: protectedProcedure.input(
8444
- z24.object({
8445
- orgId: z24.string().min(1).max(128),
8446
- projectId: z24.string().min(1).max(128)
8891
+ z27.object({
8892
+ orgId: z27.string().min(1).max(128),
8893
+ projectId: z27.string().min(1).max(128)
8447
8894
  })
8448
8895
  ).mutation(async ({ input }) => {
8449
8896
  await cloudSyncBridge.unfollowProject(input.orgId, input.projectId);
@@ -8459,9 +8906,9 @@ var cloudSyncRouter = router({
8459
8906
  * membre, ce qui évite d'effacer les fichiers si le cloud a refusé l'ordre.
8460
8907
  */
8461
8908
  deleteProjectFromOrg: protectedProcedure.input(
8462
- z24.object({
8463
- orgId: z24.string().min(1).max(128),
8464
- projectId: z24.string().min(1).max(128)
8909
+ z27.object({
8910
+ orgId: z27.string().min(1).max(128),
8911
+ projectId: z27.string().min(1).max(128)
8465
8912
  })
8466
8913
  ).mutation(async ({ input }) => {
8467
8914
  let res;
@@ -8474,8 +8921,8 @@ var cloudSyncRouter = router({
8474
8921
  accountId: await accountForOrgScope(input.orgId)
8475
8922
  });
8476
8923
  } catch (err) {
8477
- if (err instanceof TRPCError20 && err.code === "PRECONDITION_FAILED") {
8478
- throw new TRPCError20({ code: "PRECONDITION_FAILED", message: "CLOUD_NOT_LINKED" });
8924
+ if (err instanceof TRPCError22 && err.code === "PRECONDITION_FAILED") {
8925
+ throw new TRPCError22({ code: "PRECONDITION_FAILED", message: "CLOUD_NOT_LINKED" });
8479
8926
  }
8480
8927
  throw err;
8481
8928
  }
@@ -8483,12 +8930,12 @@ var cloudSyncRouter = router({
8483
8930
  const detail = await cloudMessage(res);
8484
8931
  if (res.status === 403) {
8485
8932
  const code = /not a member/i.test(detail) ? "PROJECT_NOT_MEMBER" : "PROJECT_DELETE_FORBIDDEN";
8486
- throw new TRPCError20({ code: "FORBIDDEN", message: code, cause: new Error(detail) });
8933
+ throw new TRPCError22({ code: "FORBIDDEN", message: code, cause: new Error(detail) });
8487
8934
  }
8488
8935
  if (res.status === 404) {
8489
- throw new TRPCError20({ code: "NOT_FOUND", message: "PROJECT_NOT_IN_ORG", cause: new Error(detail) });
8936
+ throw new TRPCError22({ code: "NOT_FOUND", message: "PROJECT_NOT_IN_ORG", cause: new Error(detail) });
8490
8937
  }
8491
- throw new TRPCError20({
8938
+ throw new TRPCError22({
8492
8939
  code: "BAD_GATEWAY",
8493
8940
  message: detail || `PROJECT_DELETE_FAILED (${res.status})`
8494
8941
  });
@@ -8517,10 +8964,10 @@ var cloudSyncRouter = router({
8517
8964
  * d'un autre. Même forme que `rememberOrgNames`, et pour la même raison :
8518
8965
  * ce que seul le cloud sait, on le recopie quand il le dit.
8519
8966
  */
8520
- rememberOrgAccounts: protectedProcedure.input(z24.object({
8521
- orgs: z24.array(z24.object({
8522
- orgId: z24.string().min(1).max(128),
8523
- accountId: z24.string().min(1).max(64)
8967
+ rememberOrgAccounts: protectedProcedure.input(z27.object({
8968
+ orgs: z27.array(z27.object({
8969
+ orgId: z27.string().min(1).max(128),
8970
+ accountId: z27.string().min(1).max(64)
8524
8971
  })).max(200)
8525
8972
  })).mutation(async ({ input }) => {
8526
8973
  const candidates = /* @__PURE__ */ new Map();
@@ -8547,10 +8994,10 @@ var cloudSyncRouter = router({
8547
8994
  }
8548
8995
  return { remembered };
8549
8996
  }),
8550
- rememberOrgNames: protectedProcedure.input(z24.object({
8551
- orgs: z24.array(z24.object({
8552
- orgId: z24.string().min(1).max(128),
8553
- name: z24.string().min(1).max(100)
8997
+ rememberOrgNames: protectedProcedure.input(z27.object({
8998
+ orgs: z27.array(z27.object({
8999
+ orgId: z27.string().min(1).max(128),
9000
+ name: z27.string().min(1).max(100)
8554
9001
  })).max(200)
8555
9002
  })).mutation(async ({ input }) => {
8556
9003
  for (const { orgId, name } of input.orgs) await rememberOrgName(orgId, name);
@@ -8580,7 +9027,7 @@ var cloudSyncRouter = router({
8580
9027
  * Un 404 efface le logo retenu — l'organisation en a retiré un — tandis qu'un
8581
9028
  * réseau muet laisse en place ce qui est déjà là : hors ligne, on garde.
8582
9029
  */
8583
- cacheOrgLogos: protectedProcedure.input(z24.object({ orgIds: z24.array(z24.string().min(1).max(128)).max(200) })).mutation(async ({ input }) => {
9030
+ cacheOrgLogos: protectedProcedure.input(z27.object({ orgIds: z27.array(z27.string().min(1).max(128)).max(200) })).mutation(async ({ input }) => {
8584
9031
  let cached = 0;
8585
9032
  for (const orgId of input.orgIds) {
8586
9033
  const base = await orgCloudUrl(orgId);
@@ -8637,22 +9084,22 @@ var cloudSyncRouter = router({
8637
9084
 
8638
9085
  // ../server/src/trpc/routers/filesystem.ts
8639
9086
  import { readdir as readdir4, stat as stat6 } from "fs/promises";
8640
- import { homedir as homedir3 } from "os";
8641
- import { isAbsolute as isAbsolute3, join as join12, resolve as resolve2, dirname as dirname7, basename } from "path";
8642
- import { z as z25 } from "zod";
9087
+ import { homedir as homedir4 } from "os";
9088
+ import { isAbsolute as isAbsolute3, join as join13, resolve as resolve3, dirname as dirname8, basename } from "path";
9089
+ import { z as z28 } from "zod";
8643
9090
  var MAX_ENTRIES2 = 500;
8644
- var PATH_SCHEMA = z25.string().min(1).max(4096);
9091
+ var PATH_SCHEMA = z28.string().min(1).max(4096);
8645
9092
  function safeResolve(input) {
8646
9093
  if (input.includes("\0")) throw new Error("Chemin invalide");
8647
- const expanded = input.startsWith("~") ? join12(homedir3(), input.slice(1)) : input;
9094
+ const expanded = input.startsWith("~") ? join13(homedir4(), input.slice(1)) : input;
8648
9095
  if (!isAbsolute3(expanded)) throw new Error("Le chemin doit \xEAtre absolu");
8649
- return resolve2(expanded);
9096
+ return resolve3(expanded);
8650
9097
  }
8651
9098
  var filesystemRouter = router({
8652
9099
  /** Where a picker should open when the user has chosen nothing yet. */
8653
9100
  suggestedRoots: protectedProcedure.query(async () => {
8654
9101
  const roots = [
8655
- { label: "home", path: homedir3() },
9102
+ { label: "home", path: homedir4() },
8656
9103
  // The launch directory, taken from the vault rather than the process.
8657
9104
  { label: "cwd", path: await resolveWorkspaceCwd({}) }
8658
9105
  ];
@@ -8665,7 +9112,7 @@ var filesystemRouter = router({
8665
9112
  * Hidden entries are skipped: they are noise for choosing a workspace, and
8666
9113
  * `.runeya` itself would invite placing a project inside another's data.
8667
9114
  */
8668
- listDirectories: protectedProcedure.input(z25.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
9115
+ listDirectories: protectedProcedure.input(z28.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
8669
9116
  const dir = safeResolve(input.path);
8670
9117
  const info = await stat6(dir).catch(() => null);
8671
9118
  if (!info?.isDirectory()) {
@@ -8680,13 +9127,13 @@ var filesystemRouter = router({
8680
9127
  const truncated = names.length > MAX_ENTRIES2;
8681
9128
  const entries2 = [];
8682
9129
  for (const name of names.slice(0, MAX_ENTRIES2)) {
8683
- const path = join12(dir, name);
9130
+ const path = join13(dir, name);
8684
9131
  entries2.push({ name, path, hasRuneyaProject: await holdsRuneyaProject(path) });
8685
9132
  }
8686
9133
  return {
8687
9134
  path: dir,
8688
9135
  // Null at the filesystem root, which is what stops "up" from looping.
8689
- parent: dirname7(dir) === dir ? null : dirname7(dir),
9136
+ parent: dirname8(dir) === dir ? null : dirname8(dir),
8690
9137
  name: basename(dir) || dir,
8691
9138
  entries: entries2,
8692
9139
  truncated,
@@ -8698,7 +9145,7 @@ var filesystemRouter = router({
8698
9145
  * qui s'y trouve, ou rien (déjà dans la liste, lié à un projet d'organisation,
8699
9146
  * racine machine).
8700
9147
  */
8701
- checkLocalProjectTarget: protectedProcedure.input(z25.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
9148
+ checkLocalProjectTarget: protectedProcedure.input(z28.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
8702
9149
  const dir = safeResolve(input.path);
8703
9150
  const info = await stat6(dir).catch(() => null);
8704
9151
  if (!info?.isDirectory()) return { path: dir, status: "missing", projectName: null };
@@ -8710,10 +9157,10 @@ var filesystemRouter = router({
8710
9157
  * Two projects in one directory is what makes services run against the wrong
8711
9158
  * code, so a directory that already holds one is refused outright.
8712
9159
  */
8713
- checkProjectTarget: protectedProcedure.input(z25.object({
9160
+ checkProjectTarget: protectedProcedure.input(z28.object({
8714
9161
  path: PATH_SCHEMA,
8715
- projectId: z25.string().min(1).max(128),
8716
- orgId: z25.string().max(128).nullable().optional()
9162
+ projectId: z28.string().min(1).max(128),
9163
+ orgId: z28.string().max(128).nullable().optional()
8717
9164
  })).query(async ({ input }) => {
8718
9165
  const dir = safeResolve(input.path);
8719
9166
  const info = await stat6(dir).catch(() => null);
@@ -8732,15 +9179,15 @@ var filesystemRouter = router({
8732
9179
  });
8733
9180
 
8734
9181
  // ../server/src/trpc/routers/desktop.ts
8735
- import { z as z26 } from "zod";
9182
+ import { z as z29 } from "zod";
8736
9183
  import { isAbsolute as isAbsolute5 } from "path";
8737
- import { TRPCError as TRPCError21 } from "@trpc/server";
9184
+ import { TRPCError as TRPCError23 } from "@trpc/server";
8738
9185
 
8739
9186
  // ../server/src/services/desktop-launcher.ts
8740
- import { execFile as execFile4 } from "child_process";
9187
+ import { execFile as execFile5 } from "child_process";
8741
9188
  import { isAbsolute as isAbsolute4 } from "path";
8742
9189
  import { promisify as promisify3 } from "util";
8743
- var exec = promisify3(execFile4);
9190
+ var exec = promisify3(execFile5);
8744
9191
  var INSTALL_TIMEOUT_MS2 = 3e4;
8745
9192
  var cli = null;
8746
9193
  function registerCli(location) {
@@ -8818,45 +9265,45 @@ var desktopRouter = router({
8818
9265
  * dessinée par le serveur. Sans cette annonce elle garderait la teinte figée
8819
9266
  * du fichier livré avec le CLI. Rien n'est persisté — voir `tray-accent.ts`.
8820
9267
  */
8821
- setTrayAccent: protectedProcedure.input(z26.object({
9268
+ setTrayAccent: protectedProcedure.input(z29.object({
8822
9269
  // `#rgb` ou `#rrggbb` : la forme est vérifiée ici, pas rabotée plus loin.
8823
- color: z26.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/).nullable()
9270
+ color: z29.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/).nullable()
8824
9271
  })).mutation(({ input }) => {
8825
9272
  setTrayAccent(input.color);
8826
9273
  return { ok: true };
8827
9274
  }),
8828
- installLauncher: protectedProcedure.input(z26.object({
8829
- name: z26.string().trim().min(1).max(120),
9275
+ installLauncher: protectedProcedure.input(z29.object({
9276
+ name: z29.string().trim().min(1).max(120),
8830
9277
  /**
8831
9278
  * `null` : raccourci global, sans dossier épinglé. Sinon un chemin
8832
9279
  * absolu, refusé autrement : le dossier devient un argument positionnel
8833
9280
  * du CLI, où une valeur commençant par un tiret passerait pour un drapeau.
8834
9281
  */
8835
- workingDir: z26.string().max(4096).refine(isAbsolute5, {
9282
+ workingDir: z29.string().max(4096).refine(isAbsolute5, {
8836
9283
  message: "Le dossier de travail doit \xEAtre un chemin absolu."
8837
9284
  }).nullable(),
8838
- port: z26.number().int().min(1).max(65535),
8839
- agentPort: z26.number().int().min(1).max(65535),
9285
+ port: z29.number().int().min(1).max(65535),
9286
+ agentPort: z29.number().int().min(1).max(65535),
8840
9287
  /** Projet ouvert directement par le raccourci (`--project`). Absent : liste des projets. */
8841
- projectId: z26.string().max(128).regex(LAUNCHER_PROJECT_ID_PATTERN).nullable().optional()
9288
+ projectId: z29.string().max(128).regex(LAUNCHER_PROJECT_ID_PATTERN).nullable().optional()
8842
9289
  })).mutation(async ({ input }) => {
8843
9290
  try {
8844
9291
  return await installLauncher(input);
8845
9292
  } catch (err) {
8846
9293
  const message = err.message;
8847
9294
  if (message === "launcher-working-dir-not-absolute") {
8848
- throw new TRPCError21({
9295
+ throw new TRPCError23({
8849
9296
  code: "BAD_REQUEST",
8850
9297
  message: "Le dossier de travail doit \xEAtre un chemin absolu."
8851
9298
  });
8852
9299
  }
8853
9300
  if (message === "launcher-unavailable") {
8854
- throw new TRPCError21({
9301
+ throw new TRPCError23({
8855
9302
  code: "PRECONDITION_FAILED",
8856
9303
  message: "Cette Runeya ne sait pas o\xF9 trouver son CLI : cr\xE9ez le raccourci avec `runeya install-desktop`."
8857
9304
  });
8858
9305
  }
8859
- throw new TRPCError21({ code: "INTERNAL_SERVER_ERROR", message });
9306
+ throw new TRPCError23({ code: "INTERNAL_SERVER_ERROR", message });
8860
9307
  }
8861
9308
  })
8862
9309
  });
@@ -8868,6 +9315,8 @@ var appRouter = router({
8868
9315
  appUpdate: appUpdateRouter,
8869
9316
  changelog: changelogRouter,
8870
9317
  diagnostics: diagnosticsRouter,
9318
+ mcpPairing: mcpPairingRouter,
9319
+ aiClients: aiClientsRouter,
8871
9320
  project: projectRouter,
8872
9321
  service: serviceRouter,
8873
9322
  agent: agentRouter,
@@ -8956,8 +9405,8 @@ async function ensureFilePermissions() {
8956
9405
  }
8957
9406
 
8958
9407
  // ../server/src/services/vault-migration.ts
8959
- import { readdir as readdir5, readFile as readFile6, writeFile as writeFile7, mkdir as mkdir9, copyFile as copyFile2, unlink, rename as rename3, stat as stat8 } from "fs/promises";
8960
- import { join as join13 } from "path";
9408
+ import { readdir as readdir5, readFile as readFile8, writeFile as writeFile7, mkdir as mkdir10, copyFile as copyFile2, unlink, rename as rename3, stat as stat8 } from "fs/promises";
9409
+ import { join as join14 } from "path";
8961
9410
  var KEY_FILES = [".encryption.key.enc", ".runeya-verify"];
8962
9411
  var MACHINE_FILES = [
8963
9412
  "agents.json",
@@ -8987,17 +9436,17 @@ async function openLegacyVault(legacyRoot, masterPassword) {
8987
9436
  }
8988
9437
  async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machineKey, now) {
8989
9438
  const stamp = now.toISOString().replace(/[:.]/g, "-").substring(0, 19);
8990
- const backupDir = join13(legacyRoot, `backup-${stamp}`);
8991
- await mkdir9(backupDir, { recursive: true });
9439
+ const backupDir = join14(legacyRoot, `backup-${stamp}`);
9440
+ await mkdir10(backupDir, { recursive: true });
8992
9441
  const jsonFiles = await vaultJsonFiles(legacyRoot);
8993
9442
  for (const name of [...jsonFiles, ...KEY_FILES]) {
8994
- if (await exists2(join13(legacyRoot, name))) {
8995
- await copyFile2(join13(legacyRoot, name), join13(backupDir, name));
9443
+ if (await exists2(join14(legacyRoot, name))) {
9444
+ await copyFile2(join14(legacyRoot, name), join14(backupDir, name));
8996
9445
  }
8997
9446
  }
8998
9447
  const rekeyed = [];
8999
9448
  for (const name of jsonFiles) {
9000
- const raw = await readFile6(join13(legacyRoot, name), "utf-8");
9449
+ const raw = await readFile8(join14(legacyRoot, name), "utf-8");
9001
9450
  let parsed;
9002
9451
  try {
9003
9452
  parsed = JSON.parse(raw);
@@ -9007,13 +9456,13 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
9007
9456
  const counter = { n: 0 };
9008
9457
  const converted = rekeyValue(parsed, legacyKey, machineKey, counter);
9009
9458
  if (counter.n === 0) continue;
9010
- const tmp = join13(legacyRoot, `${name}.tmp`);
9459
+ const tmp = join14(legacyRoot, `${name}.tmp`);
9011
9460
  await writeFile7(tmp, JSON.stringify(converted, null, 2), "utf-8");
9012
- await rename3(tmp, join13(legacyRoot, name));
9461
+ await rename3(tmp, join14(legacyRoot, name));
9013
9462
  rekeyed.push({ file: name, values: counter.n });
9014
9463
  }
9015
9464
  for (const { file, values } of rekeyed) {
9016
- const parsed = JSON.parse(await readFile6(join13(legacyRoot, file), "utf-8"));
9465
+ const parsed = JSON.parse(await readFile8(join14(legacyRoot, file), "utf-8"));
9017
9466
  const check = { n: 0 };
9018
9467
  rekeyValue(parsed, machineKey, machineKey, check);
9019
9468
  if (check.n !== values) {
@@ -9022,13 +9471,13 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
9022
9471
  );
9023
9472
  }
9024
9473
  }
9025
- await mkdir9(machineRootDir, { recursive: true });
9474
+ await mkdir10(machineRootDir, { recursive: true });
9026
9475
  const moved = [];
9027
9476
  const skipped = [];
9028
9477
  for (const name of MACHINE_FILES) {
9029
- const from = join13(legacyRoot, name);
9478
+ const from = join14(legacyRoot, name);
9030
9479
  if (!await exists2(from)) continue;
9031
- const to = join13(machineRootDir, name);
9480
+ const to = join14(machineRootDir, name);
9032
9481
  if (await exists2(to)) {
9033
9482
  skipped.push(name);
9034
9483
  continue;
@@ -9038,15 +9487,15 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
9038
9487
  moved.push(name);
9039
9488
  }
9040
9489
  for (const name of KEY_FILES) {
9041
- const path = join13(legacyRoot, name);
9490
+ const path = join14(legacyRoot, name);
9042
9491
  if (await exists2(path)) await unlink(path);
9043
9492
  }
9044
9493
  return { backupDir, rekeyed, moved, skipped };
9045
9494
  }
9046
9495
 
9047
9496
  // ../server/src/services/settings-migration.ts
9048
- import { readFile as readFile7, writeFile as writeFile8, rename as rename4, unlink as unlink2, mkdir as mkdir10, chmod as chmod4, stat as stat9 } from "fs/promises";
9049
- import { join as join14 } from "path";
9497
+ import { readFile as readFile9, writeFile as writeFile8, rename as rename4, unlink as unlink2, mkdir as mkdir11, chmod as chmod4, stat as stat9 } from "fs/promises";
9498
+ import { join as join15 } from "path";
9050
9499
  var SETTINGS_FILE = "settings.json";
9051
9500
  var SETTINGS_PRIVATE_FILE = "settings.private.json";
9052
9501
  async function exists3(path) {
@@ -9059,9 +9508,9 @@ async function legacySource(filename) {
9059
9508
  return null;
9060
9509
  }
9061
9510
  async function moveInto(root, filename, from) {
9062
- await mkdir10(root, { recursive: true });
9063
- const to = join14(root, filename);
9064
- const contents = await readFile7(from, "utf-8");
9511
+ await mkdir11(root, { recursive: true });
9512
+ const to = join15(root, filename);
9513
+ const contents = await readFile9(from, "utf-8");
9065
9514
  const tmp = to + ".tmp";
9066
9515
  await writeFile8(tmp, contents, "utf-8");
9067
9516
  await rename4(tmp, to);
@@ -9085,7 +9534,7 @@ async function migrateSettingsIntoVaults() {
9085
9534
  const orgId = orgs[0];
9086
9535
  const root = orgVaultRoot(orgId);
9087
9536
  for (const { filename, from } of sources) {
9088
- if (await exists3(join14(root, filename))) continue;
9537
+ if (await exists3(join15(root, filename))) continue;
9089
9538
  await moveInto(root, filename, from);
9090
9539
  report.files.push(filename);
9091
9540
  }
@@ -9098,17 +9547,17 @@ async function migrateSettingsIntoVaults() {
9098
9547
 
9099
9548
  // ../server/src/migrations/migrate-from-legacy.ts
9100
9549
  import { createRequire } from "module";
9101
- import { randomBytes as randomBytes3, createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3 } from "crypto";
9102
- import { randomUUID as randomUUID8 } from "crypto";
9550
+ import { randomBytes as randomBytes4, createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3 } from "crypto";
9551
+ import { randomUUID as randomUUID9 } from "crypto";
9103
9552
  import {
9104
- readFile as readFile8,
9553
+ readFile as readFile10,
9105
9554
  writeFile as writeFile9,
9106
9555
  rename as rename5,
9107
- mkdir as mkdir11,
9556
+ mkdir as mkdir12,
9108
9557
  access as access2,
9109
9558
  readdir as readdir6
9110
9559
  } from "fs/promises";
9111
- import { join as join15, dirname as dirname8 } from "path";
9560
+ import { join as join16, dirname as dirname9 } from "path";
9112
9561
  var _require = createRequire(import.meta.url);
9113
9562
  var PARSER_ID_MAP = {
9114
9563
  "stack-monitor-parser-jsons": "native:json",
@@ -9121,7 +9570,7 @@ var PARSER_ID_MAP = {
9121
9570
  function encryptValue2(plaintext) {
9122
9571
  const key = getEncryptionKey();
9123
9572
  if (!key) throw new Error("[legacy-migration] Encryption key not loaded");
9124
- const iv = randomBytes3(16);
9573
+ const iv = randomBytes4(16);
9125
9574
  const cipher = createCipheriv3("aes-256-gcm", key, iv);
9126
9575
  let enc = cipher.update(plaintext, "utf-8", "hex");
9127
9576
  enc += cipher.final("hex");
@@ -9225,7 +9674,7 @@ async function getSodium() {
9225
9674
  return _sodium;
9226
9675
  }
9227
9676
  async function decryptLegacyFile(filePath, key, sodium) {
9228
- const raw = (await readFile8(filePath, "utf-8")).trim();
9677
+ const raw = (await readFile10(filePath, "utf-8")).trim();
9229
9678
  if (!raw || raw === "[]" || raw === "{}") {
9230
9679
  try {
9231
9680
  return JSON.parse(raw);
@@ -9250,7 +9699,7 @@ async function atomicWrite(path, data) {
9250
9699
  }
9251
9700
  async function runLegacyMigration(dataDir) {
9252
9701
  const legacyRoot = env.DATA_DIR;
9253
- const legacyKeyPath = join15(legacyRoot, "dbs", "encryption-key.json");
9702
+ const legacyKeyPath = join16(legacyRoot, "dbs", "encryption-key.json");
9254
9703
  try {
9255
9704
  await access2(legacyKeyPath);
9256
9705
  } catch {
@@ -9260,18 +9709,18 @@ async function runLegacyMigration(dataDir) {
9260
9709
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
9261
9710
  const legacyTmp = `${legacyRoot}-tmp`;
9262
9711
  await rename5(legacyRoot, legacyTmp);
9263
- await mkdir11(dataDir, { recursive: true });
9264
- const legacyOld = join15(dataDir, `backup-${timestamp}`);
9712
+ await mkdir12(dataDir, { recursive: true });
9713
+ const legacyOld = join16(dataDir, `backup-${timestamp}`);
9265
9714
  await rename5(legacyTmp, legacyOld);
9266
9715
  console.log(`[legacy-migration] Legacy data backed up to ${legacyOld}`);
9267
- const keyJson = JSON.parse(await readFile8(join15(legacyOld, "dbs", "encryption-key.json"), "utf-8"));
9716
+ const keyJson = JSON.parse(await readFile10(join16(legacyOld, "dbs", "encryption-key.json"), "utf-8"));
9268
9717
  const sodium = await getSodium();
9269
9718
  const sodiumKey = sodium.from_base64(keyJson.encryptionKey);
9270
9719
  const keyBuffer = Buffer.from(sodiumKey);
9271
9720
  const runyeaKeyHex = keyBuffer.toString("hex");
9272
9721
  await setEncryptionKey(runyeaKeyHex);
9273
9722
  console.log("[legacy-migration] Encryption key loaded in memory (temporary)");
9274
- const dbsDir = join15(legacyOld, "dbs");
9723
+ const dbsDir = join16(legacyOld, "dbs");
9275
9724
  async function readEncrypted(filePath) {
9276
9725
  try {
9277
9726
  return await decryptLegacyFile(filePath, sodiumKey, sodium);
@@ -9280,29 +9729,29 @@ async function runLegacyMigration(dataDir) {
9280
9729
  return null;
9281
9730
  }
9282
9731
  }
9283
- const envsDir = join15(dbsDir, "envs");
9732
+ const envsDir = join16(dbsDir, "envs");
9284
9733
  const envFiles = (await readdir6(envsDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9285
9734
  const legacyEnvs = {};
9286
9735
  for (const file of envFiles) {
9287
9736
  const name = file.replace(".encrypted.json", "");
9288
- legacyEnvs[name] = await readEncrypted(join15(envsDir, file));
9737
+ legacyEnvs[name] = await readEncrypted(join16(envsDir, file));
9289
9738
  }
9290
- const servicesDir = join15(dbsDir, "services");
9739
+ const servicesDir = join16(dbsDir, "services");
9291
9740
  const serviceFiles = (await readdir6(servicesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9292
9741
  const legacyServices = {};
9293
9742
  for (const file of serviceFiles) {
9294
9743
  const name = file.replace(".encrypted.json", "");
9295
9744
  if (!name) continue;
9296
- const data = await readEncrypted(join15(servicesDir, file));
9745
+ const data = await readEncrypted(join16(servicesDir, file));
9297
9746
  if (data?.label) legacyServices[name] = data;
9298
9747
  }
9299
- const overridesDir = join15(dbsDir, "overrides");
9748
+ const overridesDir = join16(dbsDir, "overrides");
9300
9749
  const overrideFiles = (await readdir6(overridesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9301
9750
  const legacyServiceOverrides = {};
9302
9751
  const legacyEnvOverrides = {};
9303
9752
  for (const file of overrideFiles) {
9304
9753
  const baseName = file.replace(".encrypted.json", "");
9305
- const data = await readEncrypted(join15(overridesDir, file));
9754
+ const data = await readEncrypted(join16(overridesDir, file));
9306
9755
  if (!data) continue;
9307
9756
  if (baseName.endsWith("-envs")) {
9308
9757
  legacyServiceOverrides[baseName.slice(0, -5)] = data;
@@ -9310,18 +9759,18 @@ async function runLegacyMigration(dataDir) {
9310
9759
  legacyEnvOverrides[baseName.slice(0, -12)] = data;
9311
9760
  }
9312
9761
  }
9313
- const parsersDir = join15(dbsDir, "parsers");
9762
+ const parsersDir = join16(dbsDir, "parsers");
9314
9763
  const parserFiles = (await readdir6(parsersDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
9315
9764
  const legacyParsers = [];
9316
9765
  for (const file of parserFiles) {
9317
- const data = await readEncrypted(join15(parsersDir, file));
9766
+ const data = await readEncrypted(join16(parsersDir, file));
9318
9767
  if (data?.id) legacyParsers.push(data);
9319
9768
  }
9320
- const projectMonoId = randomUUID8();
9769
+ const projectMonoId = randomUUID9();
9321
9770
  const envIdMap = {};
9322
9771
  const labelToKey = {};
9323
9772
  for (const [name, legacyEnv] of Object.entries(legacyEnvs)) {
9324
- envIdMap[name] = randomUUID8();
9773
+ envIdMap[name] = randomUUID9();
9325
9774
  if (legacyEnv?.label) labelToKey[legacyEnv.label] = name;
9326
9775
  }
9327
9776
  const runeEnvs = [];
@@ -9346,7 +9795,7 @@ async function runLegacyMigration(dataDir) {
9346
9795
  if (hasInvalidInterpolation(value)) continue;
9347
9796
  const cleaned = cleanVarKey(key);
9348
9797
  if (inheritedVars[cleaned] === value) continue;
9349
- const varId = randomUUID8();
9798
+ const varId = randomUUID9();
9350
9799
  variables[varId] = makeVariable(cleaned, value);
9351
9800
  varIdLookup.set(`${envIdMap[name]}:${cleaned}`, varId);
9352
9801
  }
@@ -9367,7 +9816,7 @@ async function runLegacyMigration(dataDir) {
9367
9816
  const serviceIdMap = {};
9368
9817
  const runeServices = [];
9369
9818
  for (const [name, legacy] of Object.entries(legacyServices)) {
9370
- const id = randomUUID8();
9819
+ const id = randomUUID9();
9371
9820
  serviceIdMap[name] = id;
9372
9821
  const rootPath = legacy.rootPath || "";
9373
9822
  const rootPathCwd = rootPath && rootPath !== "." ? rootPath : "";
@@ -9376,7 +9825,7 @@ async function runLegacyMigration(dataDir) {
9376
9825
  const parts = [cmd.spawnCmd, ...cmd.spawnArgs || []].filter(Boolean);
9377
9826
  if (!parts.length) continue;
9378
9827
  commands.push({
9379
- id: cmd.id || randomUUID8(),
9828
+ id: cmd.id || randomUUID9(),
9380
9829
  label: "Launch",
9381
9830
  command: parts.join(" "),
9382
9831
  cwd: cmd.spawnOptions?.cwd || "",
@@ -9437,7 +9886,7 @@ async function runLegacyMigration(dataDir) {
9437
9886
  const legacyBootstrapCmds = container.bootstrap?.commands || [];
9438
9887
  if (legacyBootstrapCmds.length > 0) {
9439
9888
  dockerConfig.bootstrapCommands = legacyBootstrapCmds.filter((bs) => bs.cmd || bs.entrypoint).map((bs, idx) => ({
9440
- id: bs.id || randomUUID8(),
9889
+ id: bs.id || randomUUID9(),
9441
9890
  label: bs.label || `Bootstrap ${idx + 1}`,
9442
9891
  command: bs.cmd || "",
9443
9892
  ...bs.entrypoint ? { entrypoint: bs.entrypoint } : {},
@@ -9485,7 +9934,7 @@ async function runLegacyMigration(dataDir) {
9485
9934
  cwd: serviceCwd,
9486
9935
  runner: dockerConfig ? "docker" : "native",
9487
9936
  shortcuts: (legacy.shortcuts || []).filter((s) => s.spawnCmd || s.label).map((s) => ({
9488
- id: s.id || randomUUID8(),
9937
+ id: s.id || randomUUID9(),
9489
9938
  label: s.label || s.spawnCmd || "",
9490
9939
  command: [s.spawnCmd, ...s.spawnArgs || []].filter(Boolean).join(" "),
9491
9940
  ...s.spawnOptions?.cwd ? { cwd: s.spawnOptions.cwd } : {}
@@ -9525,7 +9974,7 @@ async function runLegacyMigration(dataDir) {
9525
9974
  );
9526
9975
  if (!svcScopedEnv) {
9527
9976
  svcScopedEnv = {
9528
- id: randomUUID8(),
9977
+ id: randomUUID9(),
9529
9978
  projectId: projectMonoId,
9530
9979
  name: `${service?.name || name} (${globalEnv.name})`,
9531
9980
  scope: "service",
@@ -9549,7 +9998,7 @@ async function runLegacyMigration(dataDir) {
9549
9998
  const varPost = typeof varData === "object" ? String(varData.suffix ?? "") : "";
9550
9999
  if (!varKey || existingKeys.has(cleanVarKey(varKey))) continue;
9551
10000
  const cleaned = cleanVarKey(varKey);
9552
- const varId = randomUUID8();
10001
+ const varId = randomUUID9();
9553
10002
  svcScopedEnv.variables[varId] = makeVariable(cleaned, varValue, varPre, varPost);
9554
10003
  existingKeys.add(cleaned);
9555
10004
  }
@@ -9569,7 +10018,7 @@ async function runLegacyMigration(dataDir) {
9569
10018
  );
9570
10019
  if (!svcScopedEnv) {
9571
10020
  svcScopedEnv = {
9572
- id: randomUUID8(),
10021
+ id: randomUUID9(),
9573
10022
  projectId: projectMonoId,
9574
10023
  name: `${service?.name || serviceName} (${globalEnv.name})`,
9575
10024
  scope: "service",
@@ -9590,7 +10039,7 @@ async function runLegacyMigration(dataDir) {
9590
10039
  const baseEntry = Object.entries(svcScopedEnv.variables).find(([, v]) => v.key === cleaned);
9591
10040
  let varId = baseEntry?.[0];
9592
10041
  if (!varId) {
9593
- varId = randomUUID8();
10042
+ varId = randomUUID9();
9594
10043
  svcScopedEnv.variables[varId] = {
9595
10044
  key: cleaned,
9596
10045
  value: { value: "", pre: "", post: "", isSecret: false, shareWithAi: false, injectIntoAi: false, required: false, overrideOnly: false, description: "" }
@@ -9620,13 +10069,13 @@ async function runLegacyMigration(dataDir) {
9620
10069
  flatOverrides[varId] = makeVariable(cleaned, value);
9621
10070
  }
9622
10071
  } else {
9623
- flatOverrides[randomUUID8()] = makeVariable(cleaned, value);
10072
+ flatOverrides[randomUUID9()] = makeVariable(cleaned, value);
9624
10073
  }
9625
10074
  } else {
9626
10075
  const cleaned = cleanVarKey(key);
9627
10076
  const existingVarId = Object.entries(env2.variables).find(([, v]) => v.key === cleaned)?.[0];
9628
10077
  if (!existingVarId) {
9629
- const varId = randomUUID8();
10078
+ const varId = randomUUID9();
9630
10079
  env2.variables[varId] = makeVariable(cleaned, value);
9631
10080
  varIdLookup.set(`${envId}:${cleaned}`, varId);
9632
10081
  }
@@ -9655,7 +10104,7 @@ async function runLegacyMigration(dataDir) {
9655
10104
  }
9656
10105
  let existingSettings = {};
9657
10106
  try {
9658
- existingSettings = JSON.parse(await readFile8(join15(dataDir, "settings.json"), "utf-8"));
10107
+ existingSettings = JSON.parse(await readFile10(join16(dataDir, "settings.json"), "utf-8"));
9659
10108
  } catch {
9660
10109
  }
9661
10110
  const migratedParsers = legacyParsers.map((p) => ({
@@ -9676,11 +10125,11 @@ ${p.transform ?? ""}`,
9676
10125
  ...migratedParsers
9677
10126
  ]
9678
10127
  };
9679
- await atomicWrite(join15(dataDir, "projects.json"), runeProjects);
9680
- await atomicWrite(join15(dataDir, "services.json"), runeServices);
9681
- await atomicWrite(join15(dataDir, "environments.json"), runeEnvs);
9682
- await atomicWrite(join15(dataDir, "environment-overrides.json"), flatOverrides);
9683
- await atomicWrite(join15(dataDir, "settings.json"), newSettings);
10128
+ await atomicWrite(join16(dataDir, "projects.json"), runeProjects);
10129
+ await atomicWrite(join16(dataDir, "services.json"), runeServices);
10130
+ await atomicWrite(join16(dataDir, "environments.json"), runeEnvs);
10131
+ await atomicWrite(join16(dataDir, "environment-overrides.json"), flatOverrides);
10132
+ await atomicWrite(join16(dataDir, "settings.json"), newSettings);
9684
10133
  const svcEnvCount = runeEnvs.filter((e) => e.scope === "service").length;
9685
10134
  console.log(`[legacy-migration] Written: ${runeProjects.length} projects, ${runeServices.length} services, ${runeEnvs.length} environments (${svcEnvCount} service-scoped)`);
9686
10135
  console.log(`[legacy-migration] Overrides: ${Object.keys(flatOverrides).length}, custom parsers: ${migratedParsers.length} (all disabled \u2014 rewrite required)`);
@@ -9694,9 +10143,9 @@ async function findLegacyKeyInBackup(dataDir) {
9694
10143
  const entries2 = await readdir6(dataDir);
9695
10144
  const backupDirs = entries2.filter((e) => e.startsWith("backup-")).sort((a, b) => a < b ? -1 : a > b ? 1 : 0).reverse();
9696
10145
  for (const dir of backupDirs) {
9697
- const keyJsonPath = join15(dataDir, dir, "dbs", "encryption-key.json");
10146
+ const keyJsonPath = join16(dataDir, dir, "dbs", "encryption-key.json");
9698
10147
  try {
9699
- const raw = await readFile8(keyJsonPath, "utf-8");
10148
+ const raw = await readFile10(keyJsonPath, "utf-8");
9700
10149
  const parsed = JSON.parse(raw);
9701
10150
  if (!parsed.encryptionKey) continue;
9702
10151
  const sodium = await getSodium();
@@ -9714,11 +10163,11 @@ async function findLegacyKeyInBackup(dataDir) {
9714
10163
  return null;
9715
10164
  }
9716
10165
  async function runOverrideOnlyMigration(dataDir) {
9717
- const { resolve: resolve4 } = await import("path");
9718
- const abDataDir = resolve4(dataDir);
10166
+ const { resolve: resolve5 } = await import("path");
10167
+ const abDataDir = resolve5(dataDir);
9719
10168
  const candidateDirs = [
9720
- join15(dirname8(env.DATA_DIR), ".runeya", "dbs", "overrides"),
9721
- join15(abDataDir, "dbs", "overrides")
10169
+ join16(dirname9(env.DATA_DIR), ".runeya", "dbs", "overrides"),
10170
+ join16(abDataDir, "dbs", "overrides")
9722
10171
  ];
9723
10172
  let overridesDir = null;
9724
10173
  let overrideFiles = [];
@@ -9734,7 +10183,7 @@ async function runOverrideOnlyMigration(dataDir) {
9734
10183
  }
9735
10184
  }
9736
10185
  if (!overridesDir || overrideFiles.length === 0) return false;
9737
- const legacyKeyPath = join15(overridesDir, "..", "encryption-key.json");
10186
+ const legacyKeyPath = join16(overridesDir, "..", "encryption-key.json");
9738
10187
  try {
9739
10188
  await access2(legacyKeyPath);
9740
10189
  return false;
@@ -9752,7 +10201,7 @@ async function runOverrideOnlyMigration(dataDir) {
9752
10201
  const envNameToId = {};
9753
10202
  let envsRaw;
9754
10203
  try {
9755
- envsRaw = JSON.parse(await readFile8(join15(abDataDir, "environments.json"), "utf-8"));
10204
+ envsRaw = JSON.parse(await readFile10(join16(abDataDir, "environments.json"), "utf-8"));
9756
10205
  for (const _env of envsRaw) {
9757
10206
  const env2 = _env;
9758
10207
  if (env2["name"] && env2["id"]) {
@@ -9771,7 +10220,7 @@ async function runOverrideOnlyMigration(dataDir) {
9771
10220
  let servicesRaw = [];
9772
10221
  const serviceNameToId = {};
9773
10222
  try {
9774
- servicesRaw = JSON.parse(await readFile8(join15(abDataDir, "services.json"), "utf-8"));
10223
+ servicesRaw = JSON.parse(await readFile10(join16(abDataDir, "services.json"), "utf-8"));
9775
10224
  for (const _svc of servicesRaw) {
9776
10225
  const svc = _svc;
9777
10226
  if (svc["name"] && svc["id"]) serviceNameToId[svc["name"]] = svc["id"];
@@ -9788,7 +10237,7 @@ async function runOverrideOnlyMigration(dataDir) {
9788
10237
  const envName = baseName.slice(0, -"-environment".length);
9789
10238
  let data;
9790
10239
  try {
9791
- data = await decryptLegacyFile(join15(overridesDir, file), sodiumKey, sodium);
10240
+ data = await decryptLegacyFile(join16(overridesDir, file), sodiumKey, sodium);
9792
10241
  } catch (err) {
9793
10242
  console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
9794
10243
  continue;
@@ -9810,12 +10259,12 @@ async function runOverrideOnlyMigration(dataDir) {
9810
10259
  if (isSameAsBase(value, baseVar)) continue;
9811
10260
  flatOverrides[varId] = makeVariable(cleaned, value);
9812
10261
  } else {
9813
- flatOverrides[randomUUID8()] = makeVariable(cleaned, value);
10262
+ flatOverrides[randomUUID9()] = makeVariable(cleaned, value);
9814
10263
  }
9815
10264
  }
9816
10265
  }
9817
10266
  if (Object.keys(flatOverrides).length > 0) {
9818
- await atomicWrite(join15(abDataDir, "environment-overrides.json"), flatOverrides);
10267
+ await atomicWrite(join16(abDataDir, "environment-overrides.json"), flatOverrides);
9819
10268
  console.log(`[override-migration] Written ${Object.keys(flatOverrides).length} override(s) to environment-overrides.json`);
9820
10269
  }
9821
10270
  let envsModified = false;
@@ -9825,7 +10274,7 @@ async function runOverrideOnlyMigration(dataDir) {
9825
10274
  const serviceName = baseName.slice(0, -"-envs".length);
9826
10275
  let data;
9827
10276
  try {
9828
- data = await decryptLegacyFile(join15(overridesDir, file), sodiumKey, sodium);
10277
+ data = await decryptLegacyFile(join16(overridesDir, file), sodiumKey, sodium);
9829
10278
  } catch (err) {
9830
10279
  console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
9831
10280
  continue;
@@ -9847,7 +10296,7 @@ async function runOverrideOnlyMigration(dataDir) {
9847
10296
  );
9848
10297
  if (!svcScopedEnv) {
9849
10298
  svcScopedEnv = {
9850
- id: randomUUID8(),
10299
+ id: randomUUID9(),
9851
10300
  projectId: globalEnv["projectId"],
9852
10301
  name: `${service?.["name"] || serviceName} (${globalEnv["name"]})`,
9853
10302
  scope: "service",
@@ -9869,7 +10318,7 @@ async function runOverrideOnlyMigration(dataDir) {
9869
10318
  const baseEntry = Object.entries(svcScopedEnv["variables"]).find(([, v]) => v.key === cleaned);
9870
10319
  let varId = baseEntry?.[0];
9871
10320
  if (!varId) {
9872
- varId = randomUUID8();
10321
+ varId = randomUUID9();
9873
10322
  svcScopedEnv["variables"][varId] = {
9874
10323
  key: cleaned,
9875
10324
  value: { value: "", pre: "", post: "", isSecret: false, shareWithAi: false, injectIntoAi: false, required: false, overrideOnly: false, description: "" }
@@ -9884,7 +10333,7 @@ async function runOverrideOnlyMigration(dataDir) {
9884
10333
  }
9885
10334
  }
9886
10335
  if (envsModified) {
9887
- await atomicWrite(join15(abDataDir, "environments.json"), envsRaw);
10336
+ await atomicWrite(join16(abDataDir, "environments.json"), envsRaw);
9888
10337
  console.log("[override-migration] Merged service overrides into environments.json");
9889
10338
  }
9890
10339
  const migratedDir = overridesDir.replace(/overrides$/, "overrides-migrated");
@@ -9899,7 +10348,7 @@ async function runOverrideOnlyMigration(dataDir) {
9899
10348
  }
9900
10349
 
9901
10350
  // ../server/src/services/server-state.ts
9902
- import { join as join16 } from "path";
10351
+ import { join as join17 } from "path";
9903
10352
  import { stat as stat10 } from "fs/promises";
9904
10353
  var ENCRYPTED_KEY_FILENAME2 = ".encryption.key.enc";
9905
10354
  var VERIFY_FILENAME2 = ".runeya-verify";
@@ -9909,22 +10358,22 @@ async function getServerState() {
9909
10358
  let hasVerify = false;
9910
10359
  let hasData = false;
9911
10360
  try {
9912
- await stat10(join16(dataDir, ENCRYPTED_KEY_FILENAME2));
10361
+ await stat10(join17(dataDir, ENCRYPTED_KEY_FILENAME2));
9913
10362
  hasEncFile = true;
9914
10363
  } catch {
9915
10364
  }
9916
10365
  try {
9917
- await stat10(join16(dataDir, VERIFY_FILENAME2));
10366
+ await stat10(join17(dataDir, VERIFY_FILENAME2));
9918
10367
  hasVerify = true;
9919
10368
  } catch {
9920
10369
  }
9921
10370
  try {
9922
- await stat10(join16(dataDir, "projects.json"));
10371
+ await stat10(join17(dataDir, "projects.json"));
9923
10372
  hasData = true;
9924
10373
  } catch {
9925
10374
  }
9926
10375
  try {
9927
- await stat10(join16(dataDir, "project.json"));
10376
+ await stat10(join17(dataDir, "project.json"));
9928
10377
  hasData = true;
9929
10378
  } catch {
9930
10379
  }
@@ -10230,7 +10679,7 @@ function getOpenApiDocument() {
10230
10679
 
10231
10680
  // ../server/src/create-server.ts
10232
10681
  import rateLimit from "express-rate-limit";
10233
- import { existsSync as existsSync2, createReadStream } from "fs";
10682
+ import { existsSync as existsSync3, createReadStream } from "fs";
10234
10683
  import multer from "multer";
10235
10684
 
10236
10685
  // ../server/src/services/image-cleanup.service.ts
@@ -10327,6 +10776,7 @@ var llmsLimiter = rateLimit({
10327
10776
  async function createLocalServer() {
10328
10777
  installLogCapture();
10329
10778
  generateJwtSecret();
10779
+ setMcpClientCheck((clientId) => mcpPairing.isPaired(clientId));
10330
10780
  const ciMode = await loadFromEnv();
10331
10781
  await runLegacyMigration(env.DATA_DIR);
10332
10782
  const keyDir = await keyRoot();
@@ -10345,10 +10795,10 @@ async function createLocalServer() {
10345
10795
  console.log("[server] Premier lancement \u2014 setup requis");
10346
10796
  }
10347
10797
  }
10348
- const absoluteDataDir = resolve3(env.DATA_DIR);
10798
+ const absoluteDataDir = resolve4(env.DATA_DIR);
10349
10799
  console.log(`[server] Local project directory: ${absoluteDataDir}`);
10350
10800
  console.log(`[server] Machine directory: ${machineRoot()}`);
10351
- if (existsSync2(env.DATA_DIR)) {
10801
+ if (existsSync3(env.DATA_DIR)) {
10352
10802
  await ensureDataGitignore(env.DATA_DIR);
10353
10803
  }
10354
10804
  await settingsManager.getSettings();
@@ -10436,7 +10886,7 @@ async function createLocalServer() {
10436
10886
  machineRoot(),
10437
10887
  ...orgRoots,
10438
10888
  ...vaultRoots,
10439
- ...[machineRoot(), ...vaultRoots].map((root) => join17(root, CONVERSATIONS_DIRNAME))
10889
+ ...[machineRoot(), ...vaultRoots].map((root) => join18(root, CONVERSATIONS_DIRNAME))
10440
10890
  ]);
10441
10891
  if (removed > 0) console.log(`[server] ${removed} fichier(s) temporaire(s) orphelin(s) retir\xE9(s)`);
10442
10892
  })().catch((err) => console.warn("[server] Temp file sweep failed:", err));
@@ -10697,13 +11147,14 @@ async function createLocalServer() {
10697
11147
  app.post("/api/auth/refresh", refreshLimiter, async (req, res) => {
10698
11148
  const auth = req.headers.authorization;
10699
11149
  const jwt = typeof auth === "string" && auth.startsWith("Bearer ") ? auth.slice(7) : "";
11150
+ let mcpClientId;
10700
11151
  try {
10701
- await verifyJwt(jwt);
11152
+ ({ mcpClientId } = await verifyJwt(jwt, { allowMcp: true }));
10702
11153
  } catch {
10703
11154
  res.status(401).json({ error: "Session invalide" });
10704
11155
  return;
10705
11156
  }
10706
- res.json({ token: await signJwt() });
11157
+ res.json({ token: await signJwt(mcpClientId ? { mcpClientId, ttl: MCP_TOKEN_TTL } : {}) });
10707
11158
  });
10708
11159
  app.post("/api/unlock", authLimiter, csrfMiddleware, express.json({ limit: "10kb" }), async (req, res) => {
10709
11160
  try {
@@ -10761,8 +11212,8 @@ async function createLocalServer() {
10761
11212
  }
10762
11213
  encKey = Buffer.from(encryptionKeyHex, "hex");
10763
11214
  } else {
10764
- const { randomBytes: randomBytes4 } = await import("crypto");
10765
- encKey = randomBytes4(32);
11215
+ const { randomBytes: randomBytes5 } = await import("crypto");
11216
+ encKey = randomBytes5(32);
10766
11217
  }
10767
11218
  }
10768
11219
  await writeEncryptedKey(await keyRoot(), masterPassword, encKey);
@@ -11084,16 +11535,16 @@ async function createLocalServer() {
11084
11535
  ]);
11085
11536
  server.closeAllConnections();
11086
11537
  await Promise.race([
11087
- new Promise((resolve4) => wss.close(() => resolve4())),
11088
- new Promise((resolve4) => setTimeout(resolve4, 2e3))
11538
+ new Promise((resolve5) => wss.close(() => resolve5())),
11539
+ new Promise((resolve5) => setTimeout(resolve5, 2e3))
11089
11540
  ]);
11090
11541
  await Promise.race([
11091
- new Promise((resolve4) => server.close(() => resolve4())),
11092
- new Promise((resolve4) => setTimeout(resolve4, 2e3))
11542
+ new Promise((resolve5) => server.close(() => resolve5())),
11543
+ new Promise((resolve5) => setTimeout(resolve5, 2e3))
11093
11544
  ]);
11094
11545
  console.log("[server] Shutdown complete");
11095
11546
  };
11096
- const { initCompanion } = await import("./companion-HBYSY2D5.js");
11547
+ const { initCompanion } = await import("./companion-W3UMZWGH.js");
11097
11548
  initCompanion();
11098
11549
  return { app, server, wss, shutdown };
11099
11550
  }
@@ -11112,7 +11563,7 @@ var PullEnvError = class extends Error {
11112
11563
  };
11113
11564
  async function promptPassword() {
11114
11565
  process.stderr.write("Master password: ");
11115
- return new Promise((resolve4) => {
11566
+ return new Promise((resolve5) => {
11116
11567
  const stdin = process.stdin;
11117
11568
  const wasRaw = stdin.isRaw;
11118
11569
  if (stdin.isTTY) stdin.setRawMode(true);
@@ -11125,7 +11576,7 @@ async function promptPassword() {
11125
11576
  stdin.pause();
11126
11577
  stdin.removeListener("data", onData);
11127
11578
  process.stderr.write("\n");
11128
- resolve4(input);
11579
+ resolve5(input);
11129
11580
  } else if (ch === "") {
11130
11581
  process.exit(1);
11131
11582
  } else if (ch === "\x7F" || ch === "\b") {
@@ -11223,8 +11674,8 @@ function onTrayServicesChanged(listener) {
11223
11674
  }
11224
11675
 
11225
11676
  // ../server/src/services/tray-icon.ts
11226
- import { mkdir as mkdir12, writeFile as writeFile10, rename as rename6 } from "fs/promises";
11227
- import { join as join18 } from "path";
11677
+ import { mkdir as mkdir13, writeFile as writeFile10, rename as rename6 } from "fs/promises";
11678
+ import { join as join19 } from "path";
11228
11679
 
11229
11680
  // ../server/src/services/png-to-ico.ts
11230
11681
  function isPng(bytes) {
@@ -11384,7 +11835,7 @@ var FETCH_TIMEOUT_MS = 3e3;
11384
11835
  var CACHE_TTL_MS = 10 * 6e4;
11385
11836
  var cache2 = /* @__PURE__ */ new Map();
11386
11837
  function trayIconDir() {
11387
- return join18(machineRoot(), "tray-icons");
11838
+ return join19(machineRoot(), "tray-icons");
11388
11839
  }
11389
11840
  async function resolveTrayIcon(platform = process.platform) {
11390
11841
  const orgId = getCurrentProject()?.orgId;
@@ -11412,8 +11863,8 @@ async function renderThemedIcon(platform) {
11412
11863
  }
11413
11864
  async function writeTrayIcon(stem, png, platform) {
11414
11865
  const dir = trayIconDir();
11415
- await mkdir12(dir, { recursive: true });
11416
- const path = join18(dir, stem);
11866
+ await mkdir13(dir, { recursive: true });
11867
+ const path = join19(dir, stem);
11417
11868
  if (platform === "win32") {
11418
11869
  const ico = pngToIco(png);
11419
11870
  return ico ? writeAtomic(`${path}.ico`, ico) : null;
@@ -11455,7 +11906,7 @@ if (process.env["RUNEYA_DATA_DIR"]) {
11455
11906
  console.log(`[server] Working directory: ${resolved} (from RUNEYA_DATA_DIR)`);
11456
11907
  }
11457
11908
  function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500) {
11458
- return new Promise((resolve4, reject) => {
11909
+ return new Promise((resolve5, reject) => {
11459
11910
  let attempts = 0;
11460
11911
  const wssErrorHandler = () => {
11461
11912
  };
@@ -11476,7 +11927,7 @@ function listenWithRetry(server, port, host, wss, maxRetries = 20, delay = 500)
11476
11927
  server.listen(port, host, () => {
11477
11928
  server.removeListener("error", onError);
11478
11929
  wss?.removeListener("error", wssErrorHandler);
11479
- resolve4();
11930
+ resolve5();
11480
11931
  });
11481
11932
  };
11482
11933
  tryListen();
@@ -11489,6 +11940,7 @@ async function main() {
11489
11940
  console.log("Hello World 2");
11490
11941
  const { server, wss, shutdown } = await createLocalServer();
11491
11942
  await listenWithRetry(server, env.PORT, env.HOST, wss);
11943
+ await registerInstance();
11492
11944
  console.log(`[server] Local server listening on ${env.HOST}:${env.PORT}`);
11493
11945
  console.log(`[server] tRPC HTTP: http://${env.HOST}:${env.PORT}/api/trpc`);
11494
11946
  console.log(`[server] tRPC WS: ws://${env.HOST}:${env.PORT}`);
@@ -11548,4 +12000,4 @@ export {
11548
12000
  stopProcess,
11549
12001
  userPrefix
11550
12002
  };
11551
- //# sourceMappingURL=src-IODFUZ7A.js.map
12003
+ //# sourceMappingURL=src-LEDGW5KP.js.map