@runeya/runeya 2.0.145 → 2.0.147

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/agent/index.js +36 -25
  2. package/agent/index.js.map +1 -1
  3. package/agent-manager-46C4NIQ2.js +13 -0
  4. package/chat-CMNDMXIP.js +28 -0
  5. package/{chunk-ONVAH65J.js → chunk-2NE52CL5.js} +422 -2172
  6. package/chunk-2NE52CL5.js.map +1 -0
  7. package/chunk-4D3GCBPO.js +462 -0
  8. package/chunk-4D3GCBPO.js.map +1 -0
  9. package/{chunk-NCO4OOYT.js → chunk-4LJQIQMN.js} +2 -2
  10. package/{chunk-QF56Z6LI.js → chunk-5XG3X2TM.js} +29 -1481
  11. package/chunk-5XG3X2TM.js.map +1 -0
  12. package/chunk-6IBPK6UE.js +1063 -0
  13. package/chunk-6IBPK6UE.js.map +1 -0
  14. package/chunk-7QXP4FXD.js +969 -0
  15. package/chunk-7QXP4FXD.js.map +1 -0
  16. package/chunk-DA2DZBTL.js +43 -0
  17. package/chunk-DA2DZBTL.js.map +1 -0
  18. package/chunk-DFPZ4MTI.js +1041 -0
  19. package/chunk-DFPZ4MTI.js.map +1 -0
  20. package/{chunk-Q4RPY3VW.js → chunk-JNK6H5KH.js} +185 -1014
  21. package/chunk-JNK6H5KH.js.map +1 -0
  22. package/{chunk-FE53XX4M.js → chunk-NPMSBYXC.js} +3 -3
  23. package/{chunk-KS7A5CTW.js → chunk-VPSQJOSN.js} +12 -2
  24. package/{chunk-KS7A5CTW.js.map → chunk-VPSQJOSN.js.map} +1 -1
  25. package/chunk-WDHLEO5S.js +809 -0
  26. package/chunk-WDHLEO5S.js.map +1 -0
  27. package/{companion-HBYSY2D5.js → companion-XKMNRIJB.js} +10 -5
  28. package/environment-resolver-ZDTF3MHI.js +18 -0
  29. package/external-NMKQPROK.js +387 -0
  30. package/external-NMKQPROK.js.map +1 -0
  31. package/index.js +15 -6
  32. package/index.js.map +1 -1
  33. package/{install-gate-JAEK4BDZ.js → install-gate-OJJEUU4E.js} +2 -2
  34. package/mcp-ready-TAPBAOZY.js +9 -0
  35. package/mcp-server/external.d.ts +44 -0
  36. package/mcp-server/external.js +2679 -0
  37. package/mcp-server/external.js.map +1 -0
  38. package/mcp-server/index.js +159 -39
  39. package/mcp-server/index.js.map +1 -1
  40. package/mcp-server/launch.d.ts +25 -1
  41. package/mcp-server/launch.js +22 -1
  42. package/mcp-server/launch.js.map +1 -1
  43. package/package.json +1 -1
  44. package/project-store-FLXTAD3J.js +23 -0
  45. package/project-store-FLXTAD3J.js.map +1 -0
  46. package/settings-manager-XPLBO35P.js +10 -0
  47. package/settings-manager-XPLBO35P.js.map +1 -0
  48. package/{src-IODFUZ7A.js → src-KWO3CKZU.js} +1083 -551
  49. package/src-KWO3CKZU.js.map +1 -0
  50. package/{vault-resolver-PN3TYCLJ.js → vault-resolver-E7KPKA5C.js} +2 -2
  51. package/vault-resolver-E7KPKA5C.js.map +1 -0
  52. package/web/assets/{AgentScalarChatInterface.vue-npQgDxeP.js → AgentScalarChatInterface.vue-DKBinB_l.js} +2 -2
  53. package/web/assets/AgentTerminalPage-CixU7x6O.js +1 -0
  54. package/web/assets/AiChatModals-BvGUd-QD.css +1 -0
  55. package/web/assets/AiChatModals-C-hXImkO.js +4 -0
  56. package/web/assets/AppLayout-At9wk0sx.js +1 -0
  57. package/web/assets/{AppLayout-tsw5Ow0w.css → AppLayout-BlhwflMq.css} +1 -1
  58. package/web/assets/AuditPage-BkqbHxjY.js +1 -0
  59. package/web/assets/CloudAccountPage-D5C2SCS9.js +1 -0
  60. package/web/assets/CloudMessagingPage-Bi57GGHO.js +2 -0
  61. package/web/assets/DashboardPage-dEQop9ql.js +1 -0
  62. package/web/assets/DirectoryPickerDialog-DIMSeDhw.js +1 -0
  63. package/web/assets/{EditorIcon-CypGLV6w.js → EditorIcon-W3Szca4J.js} +1 -1
  64. package/web/assets/EnvironmentForm-C5emavNh.js +1 -0
  65. package/web/assets/EnvironmentFormPage-BIoy8fsW.js +1 -0
  66. package/web/assets/EnvironmentListPage-BDYeh7XF.js +1 -0
  67. package/web/assets/{FlagChip-R_T5qYpL.js → FlagChip-DPtIBCjQ.js} +1 -1
  68. package/web/assets/KanbanBoardPage-BlCcjBwV.js +4 -0
  69. package/web/assets/KanbanListPage-DbXONxfC.js +1 -0
  70. package/web/assets/LaunchProjectPage-Cg92hHwQ.js +1 -0
  71. package/web/assets/LaunchServicesPage-CO8gOB9D.js +1 -0
  72. package/web/assets/LaunchServicesPage-h5o3zLcy.css +1 -0
  73. package/web/assets/{LaunchShell-CGNYr-do.js → LaunchShell-CDb21ihZ.js} +1 -1
  74. package/web/assets/LogViewer-DsusKk2b.js +3 -0
  75. package/web/assets/OrgKeyPrompt-CX9K7r9I.js +1 -0
  76. package/web/assets/ProcessControls-B7hWVqiR.css +1 -0
  77. package/web/assets/ProcessControls-TEZutuxq.js +1 -0
  78. package/web/assets/ProcessDetailPage-Cj23FECq.js +1 -0
  79. package/web/assets/ProcessMetrics-1P6iE2Il.js +1 -0
  80. package/web/assets/ProjectCreateDialog-D3aMWKfw.js +1 -0
  81. package/web/assets/{ProjectLocationGate-bUd_tFpI.js → ProjectLocationGate-DU0Jj8Nw.js} +1 -1
  82. package/web/assets/ProjectPublishDialog-D7sNKei3.js +1 -0
  83. package/web/assets/{RandomJobTestPage-BFNC827R.js → RandomJobTestPage-DQUMRHyf.js} +1 -1
  84. package/web/assets/{ScenarioEditorPage-vMoRt0Qn.css → ScenarioEditorPage-BSjSX4bw.css} +1 -1
  85. package/web/assets/ScenarioEditorPage-DvCdcY9m.js +14 -0
  86. package/web/assets/SendToChatDialog-BdWKjYmv.js +1 -0
  87. package/web/assets/ServiceControlsBar-B_J5m-Kc.js +1 -0
  88. package/web/assets/ServiceDetailPage-DhJcjFfY.js +2 -0
  89. package/web/assets/{ServiceDetailPage-COdaCZ6r.css → ServiceDetailPage-DnSf5cm9.css} +1 -1
  90. package/web/assets/ServiceFormPage-fp8NCHSv.js +1 -0
  91. package/web/assets/SettingsPage-NH2fQap4.css +1 -0
  92. package/web/assets/SettingsPage-s28BodWe.js +2086 -0
  93. package/web/assets/UnlockPage-BXXIPHW_.js +1 -0
  94. package/web/assets/VaultMigrationPage-BfNuKtkJ.js +1 -0
  95. package/web/assets/{VersionBadge-DBquJl76.js → VersionBadge-CW7czacg.js} +1 -1
  96. package/web/assets/{WikiPage-GtwpJPqf.js → WikiPage-BiwpCM3E.js} +1 -1
  97. package/web/assets/WikiView-CMch_06Z.js +1 -0
  98. package/web/assets/{add-scalar-classes-DhSSqZ5l.js → add-scalar-classes-Ckanjq1h.js} +14 -14
  99. package/web/assets/{agent.store-Co44dMZf.js → agent.store-piokQ9Dt.js} +1 -1
  100. package/web/assets/{auth-Bk1GP5sB.js → auth-BT_jf-5e.js} +2 -2
  101. package/web/assets/cloud-chat.store-CDKh4tIy.js +1 -0
  102. package/web/assets/{cloud-client-0zD_NI6q.js → cloud-client-CzPdZLDq.js} +2 -2
  103. package/web/assets/{cloud-session-Cjw54Y--.js → cloud-session-BYfn6YM4.js} +2 -2
  104. package/web/assets/floating-ui.dom-DF3oyfHd.js +1 -0
  105. package/web/assets/i18n-D0FT7rCj.js +1 -0
  106. package/web/assets/index-3LTzgeU-.css +1 -0
  107. package/web/assets/index-Ci2CNnit.js +309 -0
  108. package/web/assets/modal-iRaP1izE.js +5 -0
  109. package/web/assets/notification.store-CvvuTBVz.js +1 -0
  110. package/web/assets/{project-launch.store-B5nhdGdh.js → project-launch.store-CTmbl8UY.js} +1 -1
  111. package/web/assets/{project.store-Bm2JWNbd.js → project.store-CNC6_4r4.js} +1 -1
  112. package/web/assets/runtime-dom.esm-bundler-CyEVRK6B.js +1 -0
  113. package/web/assets/scenario.store-BIFQPXgl.js +7 -0
  114. package/web/assets/{service.store-XS50iGKo.js → service.store-B40U4QzI.js} +1 -1
  115. package/web/assets/settings-scope-CyWMCjRY.js +1 -0
  116. package/web/assets/settings.store-Dw6bAoWE.js +1 -0
  117. package/web/assets/{src-Cgix2VOi.js → src-CrNi4Pjg.js} +3 -3
  118. package/web/assets/src-DIBHat7I.js +3 -0
  119. package/web/assets/src-DtCW_XCh.js +1 -0
  120. package/web/assets/{src-BDZKZr9n.css → src-g1DRo8xZ.css} +1 -1
  121. package/web/assets/{trpc-CfuFkKgp.js → trpc-BAZGRZBD.js} +3 -3
  122. package/web/assets/{useCloudOrg-J8DAnocE.js → useCloudOrg-EnXFBwt1.js} +1 -1
  123. package/web/assets/useEnvVarAutocomplete-BxJh2Lw0.js +1 -0
  124. package/web/assets/useProjectCatalog-BrvUGv9A.js +1 -0
  125. package/web/assets/{useProjectCloudOrg-BnlKts7x.js → useProjectCloudOrg-Czxlg00k.js} +1 -1
  126. package/web/assets/{useServiceUrlHost-hKbKtPMu.js → useServiceUrlHost-Szz9y3rh.js} +1 -1
  127. package/web/assets/vue-i18n-o7yraQpR.js +3 -0
  128. package/web/index.html +21 -21
  129. package/agent-manager-THV3LJOF.js +0 -11
  130. package/chat-XYCHMTFE.js +0 -23
  131. package/chunk-ONVAH65J.js.map +0 -1
  132. package/chunk-Q4RPY3VW.js.map +0 -1
  133. package/chunk-QF56Z6LI.js.map +0 -1
  134. package/settings-manager-QVQRJO6V.js +0 -10
  135. package/src-IODFUZ7A.js.map +0 -1
  136. package/web/assets/AgentTerminalPage-BTH3yCS2.js +0 -1
  137. package/web/assets/AiChatModals-EfRuvId7.css +0 -1
  138. package/web/assets/AiChatModals-It9jnB26.js +0 -4
  139. package/web/assets/AppLayout-BTg2CADo.js +0 -1
  140. package/web/assets/AuditPage-2PKKGLZs.js +0 -1
  141. package/web/assets/CloudAccountPage-CHL61gLe.js +0 -1
  142. package/web/assets/CloudMessagingPage-cgKnABPH.js +0 -2
  143. package/web/assets/DashboardPage-CRntTMKY.js +0 -1
  144. package/web/assets/DirectoryPickerDialog-DCyhT3-W.js +0 -1
  145. package/web/assets/EnvironmentForm-C96PFWRE.js +0 -1
  146. package/web/assets/EnvironmentFormPage-ChorAd63.js +0 -1
  147. package/web/assets/EnvironmentListPage-Dw9ziaI8.js +0 -1
  148. package/web/assets/KanbanBoardPage-BZHbEj-q.js +0 -4
  149. package/web/assets/KanbanListPage-B0jlfKqs.js +0 -1
  150. package/web/assets/LaunchProjectPage-BVs1JwbV.js +0 -1
  151. package/web/assets/LaunchServicesPage-DoLDPqQq.css +0 -1
  152. package/web/assets/LaunchServicesPage-PBYJly9N.js +0 -1
  153. package/web/assets/LogViewer-BDObx29F.js +0 -3
  154. package/web/assets/OrgKeyPrompt-BbcwiwGw.js +0 -1
  155. package/web/assets/ProcessControls-BlpnVylq.css +0 -1
  156. package/web/assets/ProcessControls-DY0HTKde.js +0 -1
  157. package/web/assets/ProcessDetailPage-BZ9kIQqn.js +0 -1
  158. package/web/assets/ProcessMetrics-Crlfkelj.js +0 -1
  159. package/web/assets/ProjectCreateDialog-C1wVDPcK.js +0 -1
  160. package/web/assets/ProjectPublishDialog-APUS1WEN.js +0 -1
  161. package/web/assets/ScenarioEditorPage-DFVsMVu5.js +0 -14
  162. package/web/assets/SendToChatDialog-QHIkxNio.js +0 -1
  163. package/web/assets/ServiceControlsBar-_UQXb6g0.js +0 -1
  164. package/web/assets/ServiceDetailPage-DGIXgHwR.js +0 -2
  165. package/web/assets/ServiceFormPage-RliAfS3_.js +0 -1
  166. package/web/assets/SettingsPage-BSSHxxjM.js +0 -2086
  167. package/web/assets/SettingsPage-W8YszhUL.css +0 -1
  168. package/web/assets/UnlockPage-17EgfpJs.js +0 -1
  169. package/web/assets/VaultMigrationPage-DeMxpXXJ.js +0 -1
  170. package/web/assets/WikiView-BqaqxMxW.js +0 -1
  171. package/web/assets/cloud-chat.store-CadB14dZ.js +0 -1
  172. package/web/assets/editor.store-DmM6ZlTm.js +0 -1
  173. package/web/assets/floating-ui.dom-DWhsZyqT.js +0 -1
  174. package/web/assets/i18n-8FjeJaKW.js +0 -1
  175. package/web/assets/index-B9DJf2V0.js +0 -309
  176. package/web/assets/index-teBqdnuv.css +0 -1
  177. package/web/assets/modal-UK8xRSnh.js +0 -5
  178. package/web/assets/scenario.store-88w6P6s2.js +0 -7
  179. package/web/assets/settings-scope-CKPq86HZ.js +0 -1
  180. package/web/assets/settings.store-BCJ0LJQA.js +0 -1
  181. package/web/assets/src--GO-VSJ0.js +0 -5
  182. package/web/assets/src-DBipSD56.js +0 -1
  183. package/web/assets/useEnvVarAutocomplete-C6zOXYqi.js +0 -1
  184. package/web/assets/useProjectCatalog-BB5IR6o8.js +0 -1
  185. package/web/assets/vue-draggable-plus-BHsveB8w.css +0 -1
  186. package/web/assets/vue-draggable-plus-D5ni4jMk.js +0 -1
  187. /package/{agent-manager-THV3LJOF.js.map → agent-manager-46C4NIQ2.js.map} +0 -0
  188. /package/{chat-XYCHMTFE.js.map → chat-CMNDMXIP.js.map} +0 -0
  189. /package/{chunk-NCO4OOYT.js.map → chunk-4LJQIQMN.js.map} +0 -0
  190. /package/{chunk-FE53XX4M.js.map → chunk-NPMSBYXC.js.map} +0 -0
  191. /package/{companion-HBYSY2D5.js.map → companion-XKMNRIJB.js.map} +0 -0
  192. /package/{settings-manager-QVQRJO6V.js.map → environment-resolver-ZDTF3MHI.js.map} +0 -0
  193. /package/{install-gate-JAEK4BDZ.js.map → install-gate-OJJEUU4E.js.map} +0 -0
  194. /package/{vault-resolver-PN3TYCLJ.js.map → mcp-ready-TAPBAOZY.js.map} +0 -0
@@ -0,0 +1,462 @@
1
+ import {
2
+ StoreDiskCoordinator,
3
+ decryptValue,
4
+ ensureProjectsLoaded,
5
+ environmentStore,
6
+ getEncryptionKey,
7
+ isEncryptedValue,
8
+ isVaultClash,
9
+ isVaultConflicted,
10
+ markVaultConflict,
11
+ recordLocalRemoval,
12
+ rootOfProjectRef,
13
+ rootOfService,
14
+ stableEncryptValue
15
+ } from "./chunk-DFPZ4MTI.js";
16
+ import {
17
+ atomicWriteSecure,
18
+ fingerprintFiles
19
+ } from "./chunk-4LJQIQMN.js";
20
+ import {
21
+ allVaultRoots,
22
+ localVaultRoot,
23
+ projectVaultRoot,
24
+ vaultIdOfRoot
25
+ } from "./chunk-VPSQJOSN.js";
26
+ import {
27
+ ServiceConfigSchema
28
+ } from "./chunk-MZSDTA4A.js";
29
+
30
+ // ../server/src/services/service-store.ts
31
+ import { randomUUID } from "crypto";
32
+ import { readFile, rm } from "fs/promises";
33
+ import { join } from "path";
34
+ var FILENAME = "services.json";
35
+ function encryptLogSources(logSources, previous, vault) {
36
+ return logSources.map((src, i) => {
37
+ const prev = previous?.[i];
38
+ if (src.type === "mongo") {
39
+ if (isEncryptedValue(src.connectionString)) return src;
40
+ const was = prev?.type === "mongo" ? prev.connectionString : void 0;
41
+ return { ...src, connectionString: stableEncryptValue(src.connectionString, was, vault) };
42
+ }
43
+ if (src.type === "mysql" && src.password) {
44
+ if (isEncryptedValue(src.password)) return src;
45
+ const was = prev?.type === "mysql" ? prev.password : void 0;
46
+ return { ...src, password: stableEncryptValue(src.password, was, vault) };
47
+ }
48
+ return src;
49
+ });
50
+ }
51
+ function decryptLogSources(logSources, vault) {
52
+ return logSources.map((src) => {
53
+ if (src.type === "mongo" && isEncryptedValue(src.connectionString)) {
54
+ try {
55
+ return { ...src, connectionString: decryptValue(src.connectionString, vault) };
56
+ } catch {
57
+ console.error("[service-store] Failed to decrypt mongo connectionString");
58
+ return src;
59
+ }
60
+ }
61
+ if (src.type === "mysql" && src.password && isEncryptedValue(src.password)) {
62
+ try {
63
+ return { ...src, password: decryptValue(src.password, vault) };
64
+ } catch {
65
+ console.error("[service-store] Failed to decrypt mysql password");
66
+ return src;
67
+ }
68
+ }
69
+ return src;
70
+ });
71
+ }
72
+ function prepareForDisk(service, previous, vault) {
73
+ return {
74
+ ...service,
75
+ env: {},
76
+ envSecrets: [],
77
+ resolvedLogParsers: void 0,
78
+ resolvedCwd: void 0,
79
+ logSources: encryptLogSources(service.logSources ?? [], previous?.logSources, vault)
80
+ };
81
+ }
82
+ var ServiceStore = class {
83
+ services = /* @__PURE__ */ new Map();
84
+ loaded = false;
85
+ /**
86
+ * L'état du disque au dernier chargement — voir `ProjectStore.fingerprint` :
87
+ * plusieurs Runeya partagent la racine machine, et un store chargé une fois
88
+ * pour toutes ne verrait jamais les services créés par l'autre.
89
+ */
90
+ fingerprint = "";
91
+ disk = new StoreDiskCoordinator();
92
+ listeners = /* @__PURE__ */ new Set();
93
+ /**
94
+ * Last on-disk (encrypted) form per service id, so `prepareForDisk` can reuse
95
+ * existing ciphertext instead of re-encrypting unchanged credentials.
96
+ */
97
+ diskForm = /* @__PURE__ */ new Map();
98
+ onChange(fn) {
99
+ this.listeners.add(fn);
100
+ return () => this.listeners.delete(fn);
101
+ }
102
+ notify() {
103
+ for (const fn of this.listeners) fn();
104
+ }
105
+ /** Which vault each service was read from, used until its project is known. */
106
+ roots = /* @__PURE__ */ new Map();
107
+ /**
108
+ * L'organisation d'où vient un service reçu du cloud.
109
+ *
110
+ * Le coffre ne peut pas encore répondre : les contenus arrivent avant leur
111
+ * projet, donc `rootFor` désignerait le répertoire de lancement et
112
+ * chiffrerait les secrets de l'organisation avec la clé de la machine. Cette
113
+ * provenance tient le temps que le projet devienne local — après quoi elle
114
+ * dit la même chose que le coffre.
115
+ */
116
+ remoteOrgs = /* @__PURE__ */ new Map();
117
+ /**
118
+ * Oublier ce qui a été lu, pour tout relire sous la clé courante.
119
+ *
120
+ * Appelé après l'adoption d'une clé d'organisation : le disque vient d'être
121
+ * re-chiffré sous les pieds du store, si bien que sa mémoire décrit un état
122
+ * qui n'existe plus. La laisser en place n'affichait pas seulement des
123
+ * comptes périmés — la sauvegarde suivante réécrivait l'ancien chiffré
124
+ * par-dessus le nouveau, et défaisait l'adoption.
125
+ */
126
+ resetCache() {
127
+ this.services.clear();
128
+ this.diskForm.clear();
129
+ this.roots.clear();
130
+ this.remoteOrgs.clear();
131
+ this.loaded = false;
132
+ this.notify();
133
+ }
134
+ getFilePath(root) {
135
+ return join(root, FILENAME);
136
+ }
137
+ /**
138
+ * The vault a service must be written to: the one of the project owning it.
139
+ *
140
+ * A service carries no project id — ownership lives in `project.serviceIds` —
141
+ * so the registry answers, falling back to where the service was read and
142
+ * finally to the local vault.
143
+ */
144
+ /** La clé sous laquelle chiffrer ce service : son organisation, ou son coffre. */
145
+ vaultOf(serviceId) {
146
+ return this.remoteOrgs.get(serviceId) ?? vaultIdOfRoot(this.rootFor(serviceId));
147
+ }
148
+ rootFor(serviceId) {
149
+ return rootOfService(serviceId, this.roots.get(serviceId) ?? null) ?? localVaultRoot();
150
+ }
151
+ /** Ce que le disque porte : un fichier de services par coffre. */
152
+ async diskFingerprint() {
153
+ return fingerprintFiles((await allVaultRoots()).map((root) => this.getFilePath(root)));
154
+ }
155
+ /**
156
+ * Read every vault: the launch directory and each materialized cloud project.
157
+ *
158
+ * Roots come from the disk, not from the project store, so services load
159
+ * without waiting on projects.
160
+ */
161
+ async load() {
162
+ if (!getEncryptionKey()) return;
163
+ await this.disk.load(async () => {
164
+ const fingerprint = await this.diskFingerprint();
165
+ if (this.loaded && fingerprint === this.fingerprint) return null;
166
+ await ensureProjectsLoaded();
167
+ const next = await this.readVaults();
168
+ return () => {
169
+ this.services = next.services;
170
+ this.diskForm = next.diskForm;
171
+ this.roots = next.roots;
172
+ this.fingerprint = fingerprint;
173
+ this.loaded = true;
174
+ if (this.services.size > 0) this.notify();
175
+ };
176
+ });
177
+ }
178
+ async readVaults() {
179
+ const services = /* @__PURE__ */ new Map();
180
+ const diskForm = /* @__PURE__ */ new Map();
181
+ const roots = /* @__PURE__ */ new Map();
182
+ for (const root of await allVaultRoots()) {
183
+ try {
184
+ const raw = await readFile(this.getFilePath(root), "utf-8");
185
+ const data = JSON.parse(raw);
186
+ const clash = data.find((s) => isVaultClash(root, roots.get(s.id)));
187
+ if (clash) {
188
+ markVaultConflict(root, `service ${clash.id}`);
189
+ continue;
190
+ }
191
+ for (const s of data) {
192
+ if (s.commands) {
193
+ for (const cmd of s.commands) {
194
+ if (cmd.shell === void 0) cmd.shell = true;
195
+ }
196
+ }
197
+ diskForm.set(s.id, s);
198
+ services.set(s.id, {
199
+ ...s,
200
+ logSources: decryptLogSources(s.logSources ?? [], vaultIdOfRoot(root))
201
+ });
202
+ roots.set(s.id, root);
203
+ }
204
+ } catch {
205
+ }
206
+ }
207
+ return { services, diskForm, roots };
208
+ }
209
+ /**
210
+ * Write each vault that holds — or held — a service.
211
+ *
212
+ * A vault emptied by this save loses its file: a service that moved to
213
+ * another vault must not survive in the one it left, and a `[]` left behind
214
+ * would keep (or recreate) a `.runeya` in a directory that holds nothing.
215
+ */
216
+ save() {
217
+ return this.disk.save(() => this.writeVaults());
218
+ }
219
+ async writeVaults() {
220
+ const byRoot = /* @__PURE__ */ new Map();
221
+ for (const root of this.roots.values()) {
222
+ if (!isVaultConflicted(root)) byRoot.set(root, []);
223
+ }
224
+ byRoot.set(localVaultRoot(), byRoot.get(localVaultRoot()) ?? []);
225
+ for (const service of this.services.values()) {
226
+ const root = this.rootFor(service.id);
227
+ const prepared = prepareForDisk(service, this.diskForm.get(service.id), this.vaultOf(service.id));
228
+ this.diskForm.set(prepared.id, prepared);
229
+ this.roots.set(service.id, root);
230
+ if (isVaultConflicted(root)) continue;
231
+ const bucket = byRoot.get(root);
232
+ if (bucket) bucket.push(prepared);
233
+ else byRoot.set(root, [prepared]);
234
+ }
235
+ for (const [root, services] of byRoot) {
236
+ const filePath = this.getFilePath(root);
237
+ if (services.length === 0) {
238
+ await rm(filePath, { force: true }).catch(() => {
239
+ });
240
+ continue;
241
+ }
242
+ await atomicWriteSecure(filePath, JSON.stringify(services, null, 2));
243
+ }
244
+ this.fingerprint = await this.diskFingerprint();
245
+ }
246
+ /**
247
+ * Drop services from memory, without touching the disk.
248
+ *
249
+ * Used when their project stops being followed: its vault goes away whole,
250
+ * and services left in memory would be written back on the next save — into
251
+ * the local vault, since their owner is no longer known.
252
+ */
253
+ async forgetLocally(serviceIds) {
254
+ await this.load();
255
+ for (const id of serviceIds) {
256
+ this.services.delete(id);
257
+ this.diskForm.delete(id);
258
+ this.roots.delete(id);
259
+ this.remoteOrgs.delete(id);
260
+ }
261
+ this.notify();
262
+ }
263
+ /**
264
+ * Rewrite every vault, moving entities that now belong elsewhere.
265
+ *
266
+ * Called once the project store knows where projects live: entities read from
267
+ * the launch directory whose project turns out to be a cloud one are written
268
+ * into that project's vault, and disappear from the directory they came from.
269
+ */
270
+ async relocateAll() {
271
+ await this.load();
272
+ await this.save();
273
+ }
274
+ /**
275
+ * Réattribuer l'organisation de services dont le projet vient de changer de
276
+ * coffre.
277
+ *
278
+ * Un service porte son `orgId` en propre : laissé à `null` après le passage
279
+ * du projet dans une organisation, il partirait dans le doc en se disant
280
+ * local. La sauvegarde qui suit les écrit dans leur nouveau coffre et les
281
+ * retire de l'ancien, le registre ayant déjà été mis à jour par le projet.
282
+ */
283
+ async reassignOrg(serviceIds, orgId) {
284
+ await this.load();
285
+ for (const id of serviceIds) {
286
+ const service = this.services.get(id);
287
+ if (service) this.services.set(id, { ...service, orgId });
288
+ }
289
+ await this.save();
290
+ this.notify();
291
+ }
292
+ async list() {
293
+ await this.load();
294
+ return Array.from(this.services.values());
295
+ }
296
+ async get(id) {
297
+ await this.load();
298
+ return this.services.get(id) ?? null;
299
+ }
300
+ /**
301
+ * Service in its on-disk (sanitized) form: transient runtime env and plaintext
302
+ * secrets stripped. Used when exporting a project to the cloud so that no
303
+ * secret ever leaves the machine.
304
+ */
305
+ async getSanitized(id) {
306
+ await this.load();
307
+ const service = this.services.get(id);
308
+ return service ? prepareForDisk(service, this.diskForm.get(id), this.vaultOf(id)) : null;
309
+ }
310
+ /**
311
+ * `project` : le projet qui va l'adopter, quand on le connaît. Le service est
312
+ * alors écrit d'emblée dans son coffre — sans lui, il naissait dans le coffre
313
+ * de lancement et y laissait une copie une fois rattaché.
314
+ */
315
+ async create(input, project) {
316
+ await this.load();
317
+ const service = {
318
+ id: randomUUID(),
319
+ orgId: input.orgId ?? null,
320
+ agentId: input.agentId,
321
+ name: input.name,
322
+ commands: input.commands ?? [],
323
+ cwd: input.cwd,
324
+ ports: input.ports ?? [],
325
+ groups: input.groups ?? [],
326
+ description: input.description,
327
+ docsPath: input.docsPath,
328
+ url: input.url,
329
+ localUrls: input.localUrls ?? [],
330
+ openapiUrl: input.openapiUrl,
331
+ git: input.git,
332
+ meta: input.meta,
333
+ env: {},
334
+ envSecrets: [],
335
+ runner: input.runner ?? "native",
336
+ dockerConfig: input.dockerConfig,
337
+ healthCheck: input.healthCheck,
338
+ parserIds: input.parserIds ?? [],
339
+ shortcuts: input.shortcuts ?? [],
340
+ logSources: input.logSources ?? [],
341
+ autoRestart: input.autoRestart ?? false,
342
+ restartBackoffMs: input.restartBackoffMs ?? 1e3,
343
+ ...input.restartStrategy !== void 0 && { restartStrategy: input.restartStrategy },
344
+ ...input.maxRestarts !== void 0 && { maxRestarts: input.maxRestarts },
345
+ clearLogsOnStart: input.clearLogsOnStart ?? false
346
+ };
347
+ this.services.set(service.id, service);
348
+ if (project) this.roots.set(service.id, rootOfProjectRef(project));
349
+ await this.save();
350
+ this.notify();
351
+ return service;
352
+ }
353
+ async update(input) {
354
+ await this.load();
355
+ const existing = this.services.get(input.id);
356
+ if (!existing) return null;
357
+ const updated = {
358
+ ...existing,
359
+ ...input.agentId !== void 0 && { agentId: input.agentId ?? void 0 },
360
+ ...input.name !== void 0 && { name: input.name },
361
+ ...input.commands !== void 0 && { commands: input.commands },
362
+ ...input.cwd !== void 0 && { cwd: input.cwd ?? void 0 },
363
+ ...input.ports !== void 0 && { ports: input.ports },
364
+ ...input.groups !== void 0 && { groups: input.groups },
365
+ ...input.description !== void 0 && { description: input.description },
366
+ ...input.docsPath !== void 0 && { docsPath: input.docsPath },
367
+ ...input.url !== void 0 && { url: input.url },
368
+ ...input.localUrls !== void 0 && { localUrls: input.localUrls },
369
+ ...input.openapiUrl !== void 0 && { openapiUrl: input.openapiUrl },
370
+ ...input.git !== void 0 && { git: input.git ?? void 0 },
371
+ ...input.meta !== void 0 && { meta: input.meta ?? void 0 },
372
+ ...input.runner !== void 0 && { runner: input.runner },
373
+ ...input.dockerConfig !== void 0 && { dockerConfig: input.dockerConfig },
374
+ ...input.healthCheck !== void 0 && { healthCheck: input.healthCheck ?? void 0 },
375
+ ...input.autoRestart !== void 0 && { autoRestart: input.autoRestart },
376
+ ...input.clearLogsOnStart !== void 0 && { clearLogsOnStart: input.clearLogsOnStart },
377
+ ...input.restartBackoffMs !== void 0 && { restartBackoffMs: input.restartBackoffMs },
378
+ ...input.restartStrategy !== void 0 && { restartStrategy: input.restartStrategy },
379
+ ...input.maxRestarts !== void 0 && { maxRestarts: input.maxRestarts },
380
+ ...input.orgId !== void 0 && { orgId: input.orgId },
381
+ ...input.shortcuts !== void 0 && { shortcuts: input.shortcuts },
382
+ ...input.parserIds !== void 0 && { parserIds: input.parserIds },
383
+ ...input.logSources !== void 0 && { logSources: input.logSources }
384
+ };
385
+ this.services.set(input.id, updated);
386
+ await this.save();
387
+ this.notify();
388
+ return updated;
389
+ }
390
+ async delete(id) {
391
+ await this.load();
392
+ if (!this.services.has(id)) return false;
393
+ this.services.delete(id);
394
+ recordLocalRemoval("services", id);
395
+ await this.save();
396
+ this.notify();
397
+ await environmentStore.deleteByReference(id);
398
+ return true;
399
+ }
400
+ // ---- Cloud sync ------------------------------------------------------
401
+ /** The services of a project, in their on-disk form (what the cloud mirrors). */
402
+ async listSanitizedByIds(ids) {
403
+ await this.load();
404
+ const out = [];
405
+ for (const id of ids) {
406
+ const service = this.services.get(id);
407
+ if (service) out.push(prepareForDisk(service, this.diskForm.get(id), this.vaultOf(id)));
408
+ }
409
+ return out;
410
+ }
411
+ /**
412
+ * Apply a service received from the cloud, in its on-disk form.
413
+ *
414
+ * Validated rather than trusted: a peer running a different version — or a
415
+ * tampered doc — must not be able to write arbitrary shapes into
416
+ * `services.json`. Diff-guarded against the on-disk form so re-applying an
417
+ * identical service neither saves nor notifies (loop-safe).
418
+ */
419
+ async applyRemoteUpsert(id, fields, vault, projectId) {
420
+ await this.load();
421
+ if (vault) this.remoteOrgs.set(id, vault);
422
+ if (vault && projectId && !this.roots.has(id)) this.roots.set(id, projectVaultRoot(vault, projectId));
423
+ const parsed = ServiceConfigSchema.safeParse({ ...fields, id });
424
+ if (!parsed.success) {
425
+ console.error("[service-store] Rejected remote service %s:", id, parsed.error.message);
426
+ return { created: false };
427
+ }
428
+ const incoming = parsed.data;
429
+ const existing = this.services.get(id);
430
+ if (existing) {
431
+ const currentDisk = prepareForDisk(existing, this.diskForm.get(id), vault ?? this.vaultOf(id));
432
+ if (JSON.stringify(currentDisk) === JSON.stringify(incoming)) return { created: false };
433
+ }
434
+ this.diskForm.set(id, incoming);
435
+ this.services.set(id, {
436
+ ...incoming,
437
+ // The doc carries the on-disk form, where credentials are ciphertext;
438
+ // in memory they are expected in the clear, as `load()` keeps them.
439
+ logSources: decryptLogSources(incoming.logSources ?? [], vault ?? this.vaultOf(id))
440
+ });
441
+ await this.save();
442
+ this.notify();
443
+ return { created: !existing };
444
+ }
445
+ /** Delete a service that was removed on another peer. */
446
+ async applyRemoteDelete(id) {
447
+ await this.load();
448
+ if (!this.services.has(id)) return false;
449
+ this.services.delete(id);
450
+ this.diskForm.delete(id);
451
+ await this.save();
452
+ this.notify();
453
+ await environmentStore.deleteByReference(id);
454
+ return true;
455
+ }
456
+ };
457
+ var serviceStore = new ServiceStore();
458
+
459
+ export {
460
+ serviceStore
461
+ };
462
+ //# sourceMappingURL=chunk-4D3GCBPO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../server/src/services/service-store.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { readFile, rm } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { getEncryptionKey } from '../utils/encryption-key.js';\nimport { allVaultRoots, localVaultRoot , vaultIdOfRoot, projectVaultRoot } from './vault-resolver.js';\nimport { rootOfService, rootOfProjectRef, ensureProjectsLoaded } from './vault-registry.js';\nimport type { ProjectRef } from './project-key.js';\nimport { recordLocalRemoval } from './local-removals.js';\nimport { markVaultConflict, isVaultConflicted, isVaultClash } from './vault-conflicts.js';\nimport { fingerprintFiles } from './disk-freshness.js';\nimport { atomicWriteSecure } from './run-store-utils.js';\nimport { StoreDiskCoordinator } from './store-disk-coordinator.js';\nimport { environmentStore } from './environment-store.js';\nimport { stableEncryptValue, decryptValue, isEncryptedValue } from './crypto-helpers.js';\nimport { ServiceConfigSchema } from '@runeya/packages-shared';\nimport type { ServiceConfig, CreateService, UpdateService, LogSourceConfig } from '@runeya/packages-shared';\n\nconst FILENAME = 'services.json';\n\n/**\n * Encrypt sensitive log source credentials before writing to disk.\n *\n * `previous` is the last ciphertext written for the same sources: reusing it\n * when the plaintext is unchanged keeps the output byte-stable. Without it,\n * every save re-encrypts with a fresh IV, so an untouched service still looks\n * modified — which shows up as phantom git diffs, and as an endless sync loop\n * once the file is mirrored into a CRDT (each side keeps \"changing\" it).\n */\nfunction encryptLogSources(\n logSources: LogSourceConfig[],\n previous?: LogSourceConfig[],\n vault?: string,\n): LogSourceConfig[] {\n return logSources.map((src, i) => {\n const prev = previous?.[i];\n // Une valeur encore chiffrée est une valeur que le déchiffrement n'a pas su\n // ouvrir — clé absente, ou coffre lu sous une autre. La re-chiffrer\n // produirait un chiffré de chiffré, et la seule copie lisible serait\n // perdue : on la laisse telle quelle sur le disque.\n if (src.type === 'mongo') {\n if (isEncryptedValue(src.connectionString)) return src;\n const was = prev?.type === 'mongo' ? prev.connectionString : undefined;\n return { ...src, connectionString: stableEncryptValue(src.connectionString, was, vault) };\n }\n if (src.type === 'mysql' && src.password) {\n if (isEncryptedValue(src.password)) return src;\n const was = prev?.type === 'mysql' ? prev.password : undefined;\n return { ...src, password: stableEncryptValue(src.password, was, vault) };\n }\n return src;\n });\n}\n\n/** Decrypt sensitive log source credentials after reading from disk. */\nfunction decryptLogSources(logSources: LogSourceConfig[], vault?: string): LogSourceConfig[] {\n return logSources.map((src) => {\n if (src.type === 'mongo' && isEncryptedValue(src.connectionString)) {\n try {\n return { ...src, connectionString: decryptValue(src.connectionString, vault) };\n } catch {\n console.error('[service-store] Failed to decrypt mongo connectionString');\n return src;\n }\n }\n if (src.type === 'mysql' && src.password && isEncryptedValue(src.password)) {\n try {\n return { ...src, password: decryptValue(src.password, vault) };\n } catch {\n console.error('[service-store] Failed to decrypt mysql password');\n return src;\n }\n }\n return src;\n });\n}\n\n/**\n * Prepare service for disk: strip transient env/envSecrets/resolvedLogParsers\n * fields and encrypt log source credentials. `previous` is the service's last\n * on-disk form, so unchanged credentials keep their existing ciphertext.\n */\nfunction prepareForDisk(service: ServiceConfig, previous?: ServiceConfig, vault?: string): ServiceConfig {\n return {\n ...service,\n env: {},\n envSecrets: [],\n resolvedLogParsers: undefined,\n resolvedCwd: undefined,\n logSources: encryptLogSources(service.logSources ?? [], previous?.logSources, vault),\n };\n}\n\ntype ChangeListener = () => void;\n\nclass ServiceStore {\n private services: Map<string, ServiceConfig> = new Map();\n private loaded = false;\n /**\n * L'état du disque au dernier chargement — voir `ProjectStore.fingerprint` :\n * plusieurs Runeya partagent la racine machine, et un store chargé une fois\n * pour toutes ne verrait jamais les services créés par l'autre.\n */\n private fingerprint = '';\n private readonly disk = new StoreDiskCoordinator();\n private listeners: Set<ChangeListener> = new Set();\n /**\n * Last on-disk (encrypted) form per service id, so `prepareForDisk` can reuse\n * existing ciphertext instead of re-encrypting unchanged credentials.\n */\n private diskForm: Map<string, ServiceConfig> = new Map();\n\n onChange(fn: ChangeListener): () => void {\n this.listeners.add(fn);\n return () => this.listeners.delete(fn);\n }\n\n private notify(): void {\n for (const fn of this.listeners) fn();\n }\n\n /** Which vault each service was read from, used until its project is known. */\n private roots: Map<string, string> = new Map();\n /**\n * L'organisation d'où vient un service reçu du cloud.\n *\n * Le coffre ne peut pas encore répondre : les contenus arrivent avant leur\n * projet, donc `rootFor` désignerait le répertoire de lancement et\n * chiffrerait les secrets de l'organisation avec la clé de la machine. Cette\n * provenance tient le temps que le projet devienne local — après quoi elle\n * dit la même chose que le coffre.\n */\n private remoteOrgs: Map<string, string> = new Map();\n\n /**\n * Oublier ce qui a été lu, pour tout relire sous la clé courante.\n *\n * Appelé après l'adoption d'une clé d'organisation : le disque vient d'être\n * re-chiffré sous les pieds du store, si bien que sa mémoire décrit un état\n * qui n'existe plus. La laisser en place n'affichait pas seulement des\n * comptes périmés — la sauvegarde suivante réécrivait l'ancien chiffré\n * par-dessus le nouveau, et défaisait l'adoption.\n */\n resetCache(): void {\n this.services.clear();\n this.diskForm.clear();\n this.roots.clear();\n this.remoteOrgs.clear();\n this.loaded = false;\n // Les clients abonnés tiennent l'ancienne liste : sans ce signal, l'écran\n // ne se corrigerait qu'au prochain appel qu'il ferait de lui-même.\n this.notify();\n }\n\n private getFilePath(root: string): string {\n return join(root, FILENAME);\n }\n\n /**\n * The vault a service must be written to: the one of the project owning it.\n *\n * A service carries no project id — ownership lives in `project.serviceIds` —\n * so the registry answers, falling back to where the service was read and\n * finally to the local vault.\n */\n /** La clé sous laquelle chiffrer ce service : son organisation, ou son coffre. */\n private vaultOf(serviceId: string): string | undefined {\n return this.remoteOrgs.get(serviceId) ?? vaultIdOfRoot(this.rootFor(serviceId));\n }\n\n private rootFor(serviceId: string): string {\n // La provenance sert d'arbitre quand deux projets revendiquent le service.\n return rootOfService(serviceId, this.roots.get(serviceId) ?? null) ?? localVaultRoot();\n }\n\n /** Ce que le disque porte : un fichier de services par coffre. */\n private async diskFingerprint(): Promise<string> {\n return fingerprintFiles((await allVaultRoots()).map((root) => this.getFilePath(root)));\n }\n\n /**\n * Read every vault: the launch directory and each materialized cloud project.\n *\n * Roots come from the disk, not from the project store, so services load\n * without waiting on projects.\n */\n private async load(): Promise<void> {\n if (!getEncryptionKey()) return;\n await this.disk.load(async () => {\n const fingerprint = await this.diskFingerprint();\n if (this.loaded && fingerprint === this.fingerprint) return null;\n // Les projets d'abord : tant qu'ils sont inconnus, tout se résoudrait vers\n // le coffre de lancement et y serait réécrit à la première sauvegarde —\n // hors de l'organisation propriétaire.\n await ensureProjectsLoaded();\n const next = await this.readVaults();\n return () => {\n this.services = next.services;\n this.diskForm = next.diskForm;\n this.roots = next.roots;\n this.fingerprint = fingerprint;\n this.loaded = true;\n if (this.services.size > 0) this.notify();\n };\n });\n }\n\n private async readVaults(): Promise<{\n services: Map<string, ServiceConfig>;\n diskForm: Map<string, ServiceConfig>;\n roots: Map<string, string>;\n }> {\n const services = new Map<string, ServiceConfig>();\n const diskForm = new Map<string, ServiceConfig>();\n const roots = new Map<string, string>();\n for (const root of await allVaultRoots()) {\n try {\n const raw = await readFile(this.getFilePath(root), 'utf-8');\n const data: ServiceConfig[] = JSON.parse(raw);\n // Un coffre qui rapporte un service déjà tenu par un autre est une\n // copie du même projet dans une seconde organisation : on garde le\n // premier et on laisse le second intact plutôt que de l'écraser.\n const clash = data.find((s) => isVaultClash(root, roots.get(s.id)));\n if (clash) {\n markVaultConflict(root, `service ${clash.id}`);\n continue;\n }\n for (const s of data) {\n // Ensure shell defaults to true for legacy commands stored without it\n if (s.commands) {\n for (const cmd of s.commands) {\n if (cmd.shell === undefined) cmd.shell = true;\n }\n }\n diskForm.set(s.id, s);\n services.set(s.id, {\n ...s,\n logSources: decryptLogSources(s.logSources ?? [], vaultIdOfRoot(root)),\n });\n roots.set(s.id, root);\n }\n } catch {\n // No services in this vault yet.\n }\n }\n return { services, diskForm, roots };\n }\n\n /**\n * Write each vault that holds — or held — a service.\n *\n * A vault emptied by this save loses its file: a service that moved to\n * another vault must not survive in the one it left, and a `[]` left behind\n * would keep (or recreate) a `.runeya` in a directory that holds nothing.\n */\n private save(): Promise<void> {\n return this.disk.save(() => this.writeVaults());\n }\n\n private async writeVaults(): Promise<void> {\n const byRoot = new Map<string, ServiceConfig[]>();\n for (const root of this.roots.values()) {\n if (!isVaultConflicted(root)) byRoot.set(root, []);\n }\n byRoot.set(localVaultRoot(), byRoot.get(localVaultRoot()) ?? []);\n\n for (const service of this.services.values()) {\n const root = this.rootFor(service.id);\n // Le coffre décide de la clé : un service d'organisation est chiffré par\n // la sienne, pas par celle de la machine.\n const prepared = prepareForDisk(service, this.diskForm.get(service.id), this.vaultOf(service.id));\n this.diskForm.set(prepared.id, prepared);\n this.roots.set(service.id, root);\n if (isVaultConflicted(root)) continue;\n const bucket = byRoot.get(root);\n if (bucket) bucket.push(prepared);\n else byRoot.set(root, [prepared]);\n }\n\n for (const [root, services] of byRoot) {\n const filePath = this.getFilePath(root);\n if (services.length === 0) {\n // Un coffre vidé perd son fichier plutôt que d'en garder une liste\n // vide. Écrire `[]` recréait un `.runeya` dans le dépôt de lancement\n // alors que tous les projets vivent désormais dans le cloud — un\n // fichier fantôme qui apparaissait dans le git status de l'utilisateur.\n await rm(filePath, { force: true }).catch(() => {});\n continue;\n }\n await atomicWriteSecure(filePath, JSON.stringify(services, null, 2));\n }\n\n // Nos propres écritures deviennent la référence : sans ça, le chargement\n // suivant les prendrait pour celles d'une autre instance et relirait tout.\n this.fingerprint = await this.diskFingerprint();\n }\n\n /**\n * Drop services from memory, without touching the disk.\n *\n * Used when their project stops being followed: its vault goes away whole,\n * and services left in memory would be written back on the next save — into\n * the local vault, since their owner is no longer known.\n */\n async forgetLocally(serviceIds: string[]): Promise<void> {\n await this.load();\n for (const id of serviceIds) {\n this.services.delete(id);\n this.diskForm.delete(id);\n this.roots.delete(id);\n this.remoteOrgs.delete(id);\n }\n this.notify();\n }\n\n /**\n * Rewrite every vault, moving entities that now belong elsewhere.\n *\n * Called once the project store knows where projects live: entities read from\n * the launch directory whose project turns out to be a cloud one are written\n * into that project's vault, and disappear from the directory they came from.\n */\n async relocateAll(): Promise<void> {\n await this.load();\n await this.save();\n }\n\n /**\n * Réattribuer l'organisation de services dont le projet vient de changer de\n * coffre.\n *\n * Un service porte son `orgId` en propre : laissé à `null` après le passage\n * du projet dans une organisation, il partirait dans le doc en se disant\n * local. La sauvegarde qui suit les écrit dans leur nouveau coffre et les\n * retire de l'ancien, le registre ayant déjà été mis à jour par le projet.\n */\n async reassignOrg(serviceIds: string[], orgId: string | null): Promise<void> {\n await this.load();\n for (const id of serviceIds) {\n const service = this.services.get(id);\n if (service) this.services.set(id, { ...service, orgId });\n }\n await this.save();\n this.notify();\n }\n\n async list(): Promise<ServiceConfig[]> {\n await this.load();\n return Array.from(this.services.values());\n }\n\n async get(id: string): Promise<ServiceConfig | null> {\n await this.load();\n return this.services.get(id) ?? null;\n }\n\n /**\n * Service in its on-disk (sanitized) form: transient runtime env and plaintext\n * secrets stripped. Used when exporting a project to the cloud so that no\n * secret ever leaves the machine.\n */\n async getSanitized(id: string): Promise<ServiceConfig | null> {\n await this.load();\n const service = this.services.get(id);\n return service ? prepareForDisk(service, this.diskForm.get(id), this.vaultOf(id)) : null;\n }\n\n /**\n * `project` : le projet qui va l'adopter, quand on le connaît. Le service est\n * alors écrit d'emblée dans son coffre — sans lui, il naissait dans le coffre\n * de lancement et y laissait une copie une fois rattaché.\n */\n async create(input: CreateService, project?: ProjectRef): Promise<ServiceConfig> {\n await this.load();\n const service: ServiceConfig = {\n id: randomUUID(),\n orgId: input.orgId ?? null,\n agentId: input.agentId,\n name: input.name,\n commands: input.commands ?? [],\n cwd: input.cwd,\n ports: input.ports ?? [],\n groups: input.groups ?? [],\n description: input.description,\n docsPath: input.docsPath,\n url: input.url,\n localUrls: input.localUrls ?? [],\n openapiUrl: input.openapiUrl,\n git: input.git,\n meta: input.meta,\n env: {},\n envSecrets: [],\n runner: input.runner ?? 'native',\n dockerConfig: input.dockerConfig,\n healthCheck: input.healthCheck,\n parserIds: input.parserIds ?? [],\n shortcuts: input.shortcuts ?? [],\n logSources: input.logSources ?? [],\n autoRestart: input.autoRestart ?? false,\n restartBackoffMs: input.restartBackoffMs ?? 1000,\n ...(input.restartStrategy !== undefined && { restartStrategy: input.restartStrategy }),\n ...(input.maxRestarts !== undefined && { maxRestarts: input.maxRestarts }),\n clearLogsOnStart: input.clearLogsOnStart ?? false,\n };\n this.services.set(service.id, service);\n // Provenance donnée d'avance : `rootFor` s'y range tant qu'aucun projet ne\n // revendique encore le service.\n if (project) this.roots.set(service.id, rootOfProjectRef(project));\n await this.save();\n this.notify();\n return service;\n }\n\n async update(input: UpdateService): Promise<ServiceConfig | null> {\n await this.load();\n const existing = this.services.get(input.id);\n if (!existing) return null;\n const updated: ServiceConfig = {\n ...existing,\n ...(input.agentId !== undefined && { agentId: input.agentId ?? undefined }),\n ...(input.name !== undefined && { name: input.name }),\n ...(input.commands !== undefined && { commands: input.commands }),\n ...(input.cwd !== undefined && { cwd: input.cwd ?? undefined }),\n ...(input.ports !== undefined && { ports: input.ports }),\n ...(input.groups !== undefined && { groups: input.groups }),\n ...(input.description !== undefined && { description: input.description }),\n ...(input.docsPath !== undefined && { docsPath: input.docsPath }),\n ...(input.url !== undefined && { url: input.url }),\n ...(input.localUrls !== undefined && { localUrls: input.localUrls }),\n ...(input.openapiUrl !== undefined && { openapiUrl: input.openapiUrl }),\n ...(input.git !== undefined && { git: input.git ?? undefined }),\n ...(input.meta !== undefined && { meta: input.meta ?? undefined }),\n ...(input.runner !== undefined && { runner: input.runner }),\n ...(input.dockerConfig !== undefined && { dockerConfig: input.dockerConfig }),\n ...(input.healthCheck !== undefined && { healthCheck: input.healthCheck ?? undefined }),\n ...(input.autoRestart !== undefined && { autoRestart: input.autoRestart }),\n ...(input.clearLogsOnStart !== undefined && { clearLogsOnStart: input.clearLogsOnStart }),\n ...(input.restartBackoffMs !== undefined && { restartBackoffMs: input.restartBackoffMs }),\n ...(input.restartStrategy !== undefined && { restartStrategy: input.restartStrategy }),\n ...(input.maxRestarts !== undefined && { maxRestarts: input.maxRestarts }),\n ...(input.orgId !== undefined && { orgId: input.orgId }),\n ...(input.shortcuts !== undefined && { shortcuts: input.shortcuts }),\n ...(input.parserIds !== undefined && { parserIds: input.parserIds }),\n ...(input.logSources !== undefined && { logSources: input.logSources }),\n };\n this.services.set(input.id, updated);\n await this.save();\n this.notify();\n return updated;\n }\n\n async delete(id: string): Promise<boolean> {\n await this.load();\n if (!this.services.has(id)) return false;\n this.services.delete(id);\n recordLocalRemoval('services', id);\n await this.save();\n this.notify();\n // Clean up service-scoped environments\n await environmentStore.deleteByReference(id);\n return true;\n }\n\n // ---- Cloud sync ------------------------------------------------------\n\n /** The services of a project, in their on-disk form (what the cloud mirrors). */\n async listSanitizedByIds(ids: string[]): Promise<ServiceConfig[]> {\n await this.load();\n const out: ServiceConfig[] = [];\n for (const id of ids) {\n const service = this.services.get(id);\n if (service) out.push(prepareForDisk(service, this.diskForm.get(id), this.vaultOf(id)));\n }\n return out;\n }\n\n /**\n * Apply a service received from the cloud, in its on-disk form.\n *\n * Validated rather than trusted: a peer running a different version — or a\n * tampered doc — must not be able to write arbitrary shapes into\n * `services.json`. Diff-guarded against the on-disk form so re-applying an\n * identical service neither saves nor notifies (loop-safe).\n */\n async applyRemoteUpsert(\n id: string, fields: unknown, vault?: string, projectId?: string,\n ): Promise<{ created: boolean }> {\n await this.load();\n // Retenue avant tout : la sauvegarde qui suit doit chiffrer sous la même\n // clé que celle qui vient d'ouvrir, pas sous celle du coffre de repli.\n if (vault) this.remoteOrgs.set(id, vault);\n // Le projet que le doc lui donne, quand il arrive avant ce projet : sans\n // cette provenance, le service atterrissait dans le coffre de lancement et\n // y restait en double une fois le projet arrivé.\n if (vault && projectId && !this.roots.has(id)) this.roots.set(id, projectVaultRoot(vault, projectId));\n const parsed = ServiceConfigSchema.safeParse({ ...(fields as object), id });\n if (!parsed.success) {\n console.error('[service-store] Rejected remote service %s:', id, parsed.error.message);\n return { created: false };\n }\n const incoming = parsed.data;\n const existing = this.services.get(id);\n if (existing) {\n const currentDisk = prepareForDisk(existing, this.diskForm.get(id), vault ?? this.vaultOf(id));\n if (JSON.stringify(currentDisk) === JSON.stringify(incoming)) return { created: false };\n }\n this.diskForm.set(id, incoming);\n this.services.set(id, {\n ...incoming,\n // The doc carries the on-disk form, where credentials are ciphertext;\n // in memory they are expected in the clear, as `load()` keeps them.\n logSources: decryptLogSources(incoming.logSources ?? [], vault ?? this.vaultOf(id)),\n });\n await this.save();\n this.notify();\n return { created: !existing };\n }\n\n /** Delete a service that was removed on another peer. */\n async applyRemoteDelete(id: string): Promise<boolean> {\n await this.load();\n if (!this.services.has(id)) return false;\n this.services.delete(id);\n this.diskForm.delete(id);\n await this.save();\n this.notify();\n await environmentStore.deleteByReference(id);\n return true;\n }\n}\n\nexport const serviceStore = new ServiceStore();\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  machineRoot
3
- } from "./chunk-KS7A5CTW.js";
3
+ } from "./chunk-VPSQJOSN.js";
4
4
 
5
5
  // ../server/src/services/disk-freshness.ts
6
6
  import { stat } from "fs/promises";
@@ -134,4 +134,4 @@ export {
134
134
  atomicWriteJsonSecure,
135
135
  readJsonFileSafe
136
136
  };
137
- //# sourceMappingURL=chunk-NCO4OOYT.js.map
137
+ //# sourceMappingURL=chunk-4LJQIQMN.js.map