@runeya/runeya 2.0.51 → 2.0.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/index.js +58 -17
- package/agent/index.js.map +1 -1
- package/agent-manager-RGDHYM64.js +7 -0
- package/{chunk-NVKGIBJN.js → chunk-EL75GG7O.js} +338 -117
- package/chunk-EL75GG7O.js.map +1 -0
- package/index.js +1 -1
- package/package.json +1 -1
- package/{src-R6JOPDNL.js → src-4GK5OK3K.js} +1012 -372
- package/src-4GK5OK3K.js.map +1 -0
- package/web/assets/AgentScalarChatInterface.vue-Cz4cGFw_.js +13 -0
- package/web/assets/AgentTerminalPage-ChRcIGvk.js +1 -0
- package/web/assets/AiChatPage-jeAEz3pL.js +1 -0
- package/web/assets/AppLayout-C8-7N9YJ.js +1 -0
- package/web/assets/{AppLayout-D1bdzemo.css → AppLayout-CDh-jiJ3.css} +1 -1
- package/web/assets/AuditPage-DfJlEoaI.js +1 -0
- package/web/assets/CloudAccountPage--6fr9upU.js +1 -0
- package/web/assets/CloudAccountPage-CI6Vvfrv.js +1 -0
- package/web/assets/CloudAccountPage-CVAMkCOF.css +1 -0
- package/web/assets/CloudMessagingPage-CWz2XG-O.js +2 -0
- package/web/assets/DashboardPage-Bkla6ZI9.js +1 -0
- package/web/assets/{EditorIcon-dUXSIPv3.js → EditorIcon-BYTcFJnr.js} +2 -2
- package/web/assets/EnvironmentForm-Tody7LuY.js +108 -0
- package/web/assets/EnvironmentFormPage-r_Hn2SiC.js +1 -0
- package/web/assets/EnvironmentListPage-BPQL4Fdp.js +1 -0
- package/web/assets/EnvironmentSelector-CsXJXDJE.js +1 -0
- package/web/assets/KanbanBoardPage-C2Cj_u1W.js +4 -0
- package/web/assets/KanbanBoardPage-ZIYuV1kG.css +1 -0
- package/web/assets/KanbanListPage-DG04Ci-t.js +1 -0
- package/web/assets/LaunchProjectPage-ZU10tRlq.js +1 -0
- package/web/assets/LaunchServicesPage-rLishQca.js +1 -0
- package/web/assets/LogViewer-DkR3A0v0.js +3 -0
- package/web/assets/{ProcessControls-t9skQ_zd.js → ProcessControls-Dkf-zJF8.js} +1 -1
- package/web/assets/ProcessDetailPage-vDdHOtno.js +1 -0
- package/web/assets/{ProcessMetrics-DL00NyoX.js → ProcessMetrics-BxwLdytO.js} +2 -2
- package/web/assets/ProjectDetailPage-Bu2gDzDU.js +1 -0
- package/web/assets/ProjectListPage-zlXxla_K.js +1 -0
- package/web/assets/RandomJobTestPage-DkxLX-Uy.js +2 -0
- package/web/assets/ScenarioEditorPage-Dlgug19y.js +14 -0
- package/web/assets/SendToChatDialog-BbMjL5KU.js +1 -0
- package/web/assets/ServiceControlsBar-D9mA1wIv.js +1 -0
- package/web/assets/ServiceDetailPage-CPtHs7te.js +2 -0
- package/web/assets/ServiceFormPage-D6zoG4d2.js +1 -0
- package/web/assets/SettingsPage-BL1cbj_d.js +2062 -0
- package/web/assets/{SettingsPage-C9WtnczY.css → SettingsPage-BY3dG-wn.css} +1 -1
- package/web/assets/UnlockPage-tTNE4QTc.js +1 -0
- package/web/assets/VariablesTable-C8UT5LGj.js +1 -0
- package/web/assets/VaultMigrationPage-ee6mGXkE.js +1 -0
- package/web/assets/VersionBadge-XGdKeJYP.js +1 -0
- package/web/assets/WikiPage-DBxWCk1C.js +1 -0
- package/web/assets/WikiView-CtMG-Mpi.js +1 -0
- package/web/assets/{add-scalar-classes-CEf8wgbL.js → add-scalar-classes-CjP6yoH9.js} +26 -26
- package/web/assets/auth-errors-C7Lmr_5E.js +1 -0
- package/web/assets/autocomplete-DLtfuYAg.js +422 -0
- package/web/assets/{basic-shape-diamond.svg-BxBPwaOm.js → basic-shape-diamond.svg-Dwn3EJf7.js} +1 -1
- package/web/assets/{basic-shape-hexagon.svg-Du908vLS.js → basic-shape-hexagon.svg-kpOvfzAi.js} +1 -1
- package/web/assets/{basic-shape-primary-circle-ellipse-round.svg-CfFm5Pl7.js → basic-shape-primary-circle-ellipse-round.svg-CZpeeE8h.js} +1 -1
- package/web/assets/{basic-shape-primary-square-rectangle.svg-CP0kG7Ut.js → basic-shape-primary-square-rectangle.svg-lIzT-3bQ.js} +1 -1
- package/web/assets/{basic-shape-shield.svg-DxTsLdrS.js → basic-shape-shield.svg-DB-n6Wmn.js} +1 -1
- package/web/assets/{browser-DV9-oWjy.js → browser-Dm_lNzZ-.js} +1 -1
- package/web/assets/{browser-BvCyKum4.js → browser-wjfUiObI.js} +1 -1
- package/web/assets/card-C3zlSqpG.js +32 -0
- package/web/assets/{checkbox-D5bej0ao.js → checkbox-lVXx5U1s.js} +3 -3
- package/web/assets/{chip-D8vO8NID.js → chip-Cc40pDDT.js} +2 -2
- package/web/assets/client-CN9LxBfq.js +24 -0
- package/web/assets/cloud-chat.store-CKtcP8mx.js +1 -0
- package/web/assets/{cloud-session-tALRHewK.js → cloud-session-luM5H14C.js} +1 -1
- package/web/assets/column-DlQNKY_S.js +907 -0
- package/web/assets/{computer-device-desktop-monitor.svg-82Glaw7U.js → computer-device-desktop-monitor.svg-B8fVLOqV.js} +1 -1
- package/web/assets/{computer-device-desktop.svg-BZb-XFcK.js → computer-device-desktop.svg-CKeybOQk.js} +1 -1
- package/web/assets/{computer-device-laptop.svg-BnptbmCn.js → computer-device-laptop.svg-C5xV7_Nt.js} +1 -1
- package/web/assets/{computer-device-mobile-phone-android-samsung-back.svg-g23ZYYHm.js → computer-device-mobile-phone-android-samsung-back.svg-5CRKvrQx.js} +1 -1
- package/web/assets/{computer-device-mobile-phone-android-samsung.svg-B5fBZlIN.js → computer-device-mobile-phone-android-samsung.svg-Da4aZrXd.js} +1 -1
- package/web/assets/{computer-device-mobile-phone-iphone-x-back.svg-BsKZAUH_.js → computer-device-mobile-phone-iphone-x-back.svg-D9phyiQZ.js} +1 -1
- package/web/assets/{computer-device-mobile-phone-iphone-x.svg-GVv-OoXC.js → computer-device-mobile-phone-iphone-x.svg-V7yMTZCu.js} +1 -1
- package/web/assets/{computer-device-mobile-tablet-touch.svg-CpyRybCz.js → computer-device-mobile-tablet-touch.svg-CGMdXdex.js} +1 -1
- package/web/assets/{computer-device-mobile-tablet.svg-C_6zzbHC.js → computer-device-mobile-tablet.svg-66hlusLK.js} +1 -1
- package/web/assets/{computer-device-network-ethernet-cat6.svg-s_MRa0bJ.js → computer-device-network-ethernet-cat6.svg-BhuzmqfI.js} +1 -1
- package/web/assets/{computer-device-network-lan-www.svg-31TS3sqT.js → computer-device-network-lan-www.svg-BXLNIvQ6.js} +1 -1
- package/web/assets/{computer-device-network-wifi-connection.svg-BfBnT-iK.js → computer-device-network-wifi-connection.svg-CaVzq_6z.js} +1 -1
- package/web/assets/{computer-device-network-wifi-router.svg-BNQJ_FEI.js → computer-device-network-wifi-router.svg-DasSXsmT.js} +1 -1
- package/web/assets/{cssMode-IplHgA8x.js → cssMode-CokQPPhF.js} +1 -1
- package/web/assets/{divider-D-QC8_57.js → divider-3st3S0cz.js} +2 -2
- package/web/assets/{ecology-science-erlenmeyer-flask.svg-CKCEQFIq.js → ecology-science-erlenmeyer-flask.svg-0xfq6Yyi.js} +1 -1
- package/web/assets/{fieldset-DpQHSMKf.js → fieldset-CvZZBOcz.js} +2 -2
- package/web/assets/{freemarker2-DHUVsWtT.js → freemarker2-1YMoAedE.js} +1 -1
- package/web/assets/{handlebars-pE00Hjr_.js → handlebars-wAW8puhT.js} +1 -1
- package/web/assets/{html-DMtmtVNV.js → html-DdFlw76f.js} +1 -1
- package/web/assets/{htmlMode-DFeVLqUM.js → htmlMode-Cy1vm8Ag.js} +1 -1
- package/web/assets/{image-flash-lightning.svg-NAFI_e2J.js → image-flash-lightning.svg-COFAE0wR.js} +1 -1
- package/web/assets/{image-picture-flower.svg-B6ZUCTNS.js → image-picture-flower.svg-ezqcE4KS.js} +1 -1
- package/web/assets/{index-D7XqcLhE.css → index-CICxLkjp.css} +1 -1
- package/web/assets/{index-a1iwaPG1.js → index-Ccntdgfe.js} +290 -290
- package/web/assets/{interface-alert-exclamation-diamond.svg-CwHTxp3a.js → interface-alert-exclamation-diamond.svg-CHGW-cLp.js} +1 -1
- package/web/assets/{interface-alert-exclamation-triangle-warning.svg-Bi-1vcWG.js → interface-alert-exclamation-triangle-warning.svg-DvPTCqs0.js} +1 -1
- package/web/assets/{interface-alert-information-circle.svg-CQnbLMfS.js → interface-alert-information-circle.svg-jX4BkfAJ.js} +1 -1
- package/web/assets/{interface-award-crown.svg-CFtMz4dE.js → interface-award-crown.svg-DtuQ-cID.js} +1 -1
- package/web/assets/{interface-bookmark.svg-7SV2pxyN.js → interface-bookmark-tag.svg-CAFVyAoV.js} +1 -1
- package/web/assets/{interface-bookmark-tag.svg-DumdjnW1.js → interface-bookmark.svg-BneowIGx.js} +1 -1
- package/web/assets/{interface-calendar-date-one.svg-e7-h1b3y.js → interface-calendar-date-one.svg-CfpOGCx4.js} +1 -1
- package/web/assets/{interface-content-book-open-pages.svg-DwLQV4lf.js → interface-content-book-open-pages.svg-Cl1dxQ4s.js} +1 -1
- package/web/assets/{interface-content-book-page.svg-CAKDQ2x-.js → interface-content-book-page.svg-CEhybPQh.js} +1 -1
- package/web/assets/{interface-content-file.svg-DZEwGNLB.js → interface-content-file.svg-HK08zzYO.js} +1 -1
- package/web/assets/interface-content-folder.svg-BD3Sy4hk.js +1 -0
- package/web/assets/{interface-content-folder.svg-BnFYmNMO.js → interface-content-folder.svg-DuncK7Kw.js} +1 -1
- package/web/assets/{interface-copy-clipboard.svg-9UwhLON-.js → interface-copy-clipboard.svg-CHxj1VUB.js} +1 -1
- package/web/assets/{interface-edit-attachment.svg-C1_u9TNx.js → interface-edit-attachment.svg-DQ-TGp2N.js} +1 -1
- package/web/assets/{interface-edit-binocular.svg-DoQnsqHf.js → interface-edit-binocular.svg-CIUJcI__.js} +1 -1
- package/web/assets/{interface-edit-magic-wand.svg-C7wTd6JH.js → interface-edit-magic-wand.svg-Bn7wsTVd.js} +1 -1
- package/web/assets/{interface-edit-tool-paint-roller.svg-BZ4hqe-1.js → interface-edit-tool-paint-roller.svg-BLR1HoWs.js} +1 -1
- package/web/assets/{interface-edit-tool-pencil.svg-D1bysygW.js → interface-edit-tool-pencil.svg-DM3xZUtj.js} +1 -1
- package/web/assets/{interface-favorite-award.svg-0Thu2k9M.js → interface-favorite-award.svg-Bc8_APGD.js} +1 -1
- package/web/assets/{interface-favorite-flag.svg-DI4_vrUQ.js → interface-favorite-flag.svg-BBV01eyw.js} +1 -1
- package/web/assets/{interface-favorite-heart.svg-DpwKtPuU.js → interface-favorite-heart.svg-DpFl3KSk.js} +1 -1
- package/web/assets/{interface-favorite-star.svg-D-udxHVN.js → interface-favorite-star.svg-CaFUQ9jK.js} +1 -1
- package/web/assets/{interface-favorite-stars-sparkles.svg-Dc4Z9Mjg.js → interface-favorite-stars-sparkles.svg-19cy1b4g.js} +1 -1
- package/web/assets/{interface-hierarchy-flowchart.svg-vfERX-oc.js → interface-hierarchy-flowchart.svg-Btiu5TuK.js} +1 -1
- package/web/assets/{interface-home-house.svg-BY98XY-a.js → interface-home-house.svg-Bp4aVUgs.js} +1 -1
- package/web/assets/{interface-hyperlink.svg-8D6WX1Q9.js → interface-hyperlink.svg-Df34T3Eu.js} +1 -1
- package/web/assets/{interface-lighting-brightness.svg-DUoPpiwx.js → interface-lighting-brightness.svg-CBJnLIw7.js} +1 -1
- package/web/assets/{interface-lock-closed.svg-CzFKU_Z4.js → interface-lock-closed.svg-C082mEtr.js} +1 -1
- package/web/assets/{interface-lock-open-unlock.svg-BnrVx0lo.js → interface-lock-open-unlock.svg-ktQ_lN_X.js} +1 -1
- package/web/assets/{interface-login-key.svg-DAfv-MMA.js → interface-login-key.svg-1JZV_q3n.js} +1 -1
- package/web/assets/{interface-search.svg-Q2hCozQb.js → interface-search.svg-BWOepCe2.js} +1 -1
- package/web/assets/{interface-setting-cog.svg-Bvq0dvZR.js → interface-setting-cog.svg-Ck7-QPf8.js} +1 -1
- package/web/assets/{interface-share-megaphone-bullhorn.svg-fUmta8NQ.js → interface-share-megaphone-bullhorn.svg-B4XWLcvf.js} +1 -1
- package/web/assets/{interface-share-rocket.svg-BKSZkx2r.js → interface-share-rocket.svg-jsXjbfvo.js} +1 -1
- package/web/assets/{interface-share-satellite.svg-CB_uwNHS.js → interface-share-satellite.svg-DYc-pAdI.js} +1 -1
- package/web/assets/{interface-share-space-ship.svg-CcUB1z0B.js → interface-share-space-ship.svg-azKKMF8L.js} +1 -1
- package/web/assets/{interface-share.svg-Bug50vV0.js → interface-share.svg-k0lPxn1t.js} +1 -1
- package/web/assets/{interface-signal-square.svg-zg41vf79.js → interface-signal-square.svg-eCO4ev15.js} +1 -1
- package/web/assets/{interface-time-clock-circle.svg-a4omIVlV.js → interface-time-clock-circle.svg-C0Sm0_j9.js} +1 -1
- package/web/assets/{interface-time-hour-glass.svg-3zmzbQg_.js → interface-time-hour-glass.svg-hfRVIC1r.js} +1 -1
- package/web/assets/{interface-users-multiple.svg-BdPX2m6Y.js → interface-users-multiple.svg-Dm9UimfL.js} +1 -1
- package/web/assets/{interface-weather-moon.svg-D9_m-QTi.js → interface-weather-moon.svg-BhxFHPbN.js} +1 -1
- package/web/assets/{javascript-BJlgvmgy.js → javascript-_AR6wrF0.js} +1 -1
- package/web/assets/{jsonMode-D0zJGMKu.js → jsonMode-CYWBTJpV.js} +2 -2
- package/web/assets/{liquid-B0fONmvZ.js → liquid-Doe_Qoxn.js} +1 -1
- package/web/assets/{lspLanguageFeatures-CrwGD4mx.js → lspLanguageFeatures-TK5k9ahW.js} +2 -2
- package/web/assets/{mail-chat-bubble-square.svg-DZaD3FQ3.js → mail-chat-bubble-square.svg-CitsmxJ_.js} +1 -1
- package/web/assets/{mail-send-email-paper-airplane.svg-DkbRfjXY.js → mail-send-email-paper-airplane.svg-CYDMogt4.js} +1 -1
- package/web/assets/{mail-send-envelope.svg-5c8j8mB_.js → mail-send-envelope.svg-s-A4ytfv.js} +1 -1
- package/web/assets/{mdx-CKW2c2uF.js → mdx-CSf3fjQb.js} +1 -1
- package/web/assets/{message-DZ0RuJXv.js → message-5nViABpL.js} +2 -2
- package/web/assets/{minus-C_n6M3oC.js → minus-mDLJQeXY.js} +2 -2
- package/web/assets/modal-DcxJxueq.js +5 -0
- package/web/assets/modal-n2wMDd7F.js +1 -0
- package/web/assets/{money-cashier-receipt.svg-nNTup6TP.js → money-cashier-receipt.svg-B3w4dcvy.js} +1 -1
- package/web/assets/{money-currency-dollar-pay.svg-D6ct80fW.js → money-currency-dollar-pay.svg-By5pgatC.js} +1 -1
- package/web/assets/{money-graph-arrow-increase.svg-DrQ3RzKG.js → money-graph-arrow-increase.svg-JJK_oYl6.js} +1 -1
- package/web/assets/{money-graph-bar-chart-increase.svg-CgI8xnRe.js → money-graph-bar-chart-increase.svg-BMvEIVpy.js} +1 -1
- package/web/assets/multiselect-CqGmdE3J.js +251 -0
- package/web/assets/{nature-ecology-leaf.svg-SIUCD6fX.js → nature-ecology-leaf.svg-CGCHa8bD.js} +1 -1
- package/web/assets/{password-1NqZx53t.js → password-CSX8oB5M.js} +4 -4
- package/web/assets/{phone-telephone.svg-DuLAx6_w.js → phone-telephone.svg-CA8_wOQH.js} +1 -1
- package/web/assets/{plus-CP-dlA9U.js → plus-CGTKvVZ7.js} +6 -6
- package/web/assets/process.store-CTLjlYjT.js +1 -0
- package/web/assets/{programming-bug.svg-DzOMyrsK.js → programming-bug.svg-Cizii8p1.js} +1 -1
- package/web/assets/{programming-cloud.svg-CB7hznLf.js → programming-cloud.svg-pnwxJHaK.js} +1 -1
- package/web/assets/{programming-computer-database-server.svg-Vdd0DgKt.js → programming-computer-database-server.svg-CNt3Jbql.js} +1 -1
- package/web/assets/{programming-computer-database.svg-CEqheAaf.js → programming-computer-database.svg-ESSkZX3t.js} +1 -1
- package/web/assets/{programming-module-four-layout.svg-BdPzuTtB.js → programming-module-four-layout.svg-BGbJvE0O.js} +1 -1
- package/web/assets/{programming-module-three.svg-PwBqCXu4.js → programming-module-three.svg-D3gOTAlw.js} +1 -1
- package/web/assets/{programming-module.svg-DsBi6ejo.js → programming-module.svg-CSHFfsFS.js} +1 -1
- package/web/assets/{programming-script-code.svg-Broms4WV.js → programming-script-code.svg-DjFoDv0c.js} +1 -1
- package/web/assets/{python-DfCB9qU4.js → python-MEGp0q_z.js} +1 -1
- package/web/assets/{razor-BBh-Aao5.js → razor-CEazjH4x.js} +1 -1
- package/web/assets/router-BmJL8z_u.js +1 -0
- package/web/assets/settings-scope-BGuX4AI9.js +1 -0
- package/web/assets/settings.store-a49Jijfe.js +1 -0
- package/web/assets/{shopping-cart.svg-CKdnb6hR.js → shopping-cart.svg-Cu7M1oni.js} +1 -1
- package/web/assets/{shopping-gift-present.svg-DIxbK0pG.js → shopping-gift-present.svg-Cdnu9uAQ.js} +1 -1
- package/web/assets/{shopping-shipping-box-parcel-package.svg-C-9ntsF7.js → shopping-shipping-box-parcel-package.svg-C6BL6pCC.js} +1 -1
- package/web/assets/{src-C77fweBS.js → src-BKhMPhsU.js} +13 -13
- package/web/assets/src-DQvVUJe0.js +1 -0
- package/web/assets/{tabpanel-C0aUv4Tp.js → tabpanel-B9VkBnSU.js} +3 -3
- package/web/assets/{tag-new-circle.svg-D9mnL-uB.js → tag-new-circle.svg-BpXDXKC4.js} +1 -1
- package/web/assets/{travel-map-earth-globe.svg-B1pf_UKK.js → travel-map-earth-globe.svg-B_t8kO2f.js} +1 -1
- package/web/assets/trpc-B-qv_zm2.js +1 -0
- package/web/assets/{tsMode-CBiaVW1A.js → tsMode-lBRYpyez.js} +6 -6
- package/web/assets/{typescript-DSa5kNRs.js → typescript-DGOxNj9C.js} +1 -1
- package/web/assets/useAiChatDrawer-xv2HAiHA.js +1 -0
- package/web/assets/useCloudOrg-_Z_zfd63.js +1 -0
- package/web/assets/useProjectCatalog-CNbze2zg.js +1 -0
- package/web/assets/{xml-BYwyGk9G.js → xml-Cqgx2kWf.js} +1 -1
- package/web/assets/{yaml-Dl2OSYTO.js → yaml-CIs4cY7e.js} +1 -1
- package/web/index.html +2 -2
- package/agent-manager-KX5MSTAK.js +0 -7
- package/chunk-NVKGIBJN.js.map +0 -1
- package/src-R6JOPDNL.js.map +0 -1
- package/web/assets/AgentScalarChatInterface.vue-D53JSFzC.js +0 -13
- package/web/assets/AgentTerminalPage-zuXFRGB0.js +0 -1
- package/web/assets/AiChatPage-BjC9iQnH.js +0 -1
- package/web/assets/AppLayout-fVYVDGr-.js +0 -1
- package/web/assets/AuditPage-DvhG8fxh.js +0 -1
- package/web/assets/CloudAccountPage-CE3b0q5v.js +0 -1
- package/web/assets/CloudAccountPage-ChVg2wnJ.css +0 -1
- package/web/assets/CloudAccountPage-YkGuA0nb.js +0 -1
- package/web/assets/CloudMessagingPage-okqqdltV.js +0 -2
- package/web/assets/DashboardPage-CmZITjvU.js +0 -1
- package/web/assets/EnvironmentForm-_y5JoRHH.js +0 -108
- package/web/assets/EnvironmentFormPage-DYhDZdPj.js +0 -1
- package/web/assets/EnvironmentListPage-soh5FJDb.js +0 -1
- package/web/assets/EnvironmentSelector-CQNLsssr.js +0 -1
- package/web/assets/KanbanBoardPage-BvMPl2FW.css +0 -1
- package/web/assets/KanbanBoardPage-C3cG7v4z.js +0 -4
- package/web/assets/KanbanListPage-xyv-oiyq.js +0 -1
- package/web/assets/LaunchProjectPage-DKqMjh-V.js +0 -1
- package/web/assets/LaunchServicesPage-IhWdB_Gw.js +0 -1
- package/web/assets/LogViewer-ChElXmMu.js +0 -3
- package/web/assets/ProcessDetailPage-DFv0ESg1.js +0 -1
- package/web/assets/ProjectDetailPage-DEXvg1pg.js +0 -1
- package/web/assets/ProjectListPage-h0AQ_RCw.js +0 -1
- package/web/assets/RandomJobTestPage-Dp7A7Qgx.js +0 -2
- package/web/assets/ScenarioEditorPage-D0_VpZUT.js +0 -14
- package/web/assets/SendToChatDialog-BpJetpLA.js +0 -1
- package/web/assets/ServiceControlsBar-qiCaxnab.js +0 -1
- package/web/assets/ServiceDetailPage-BD8D0CuN.js +0 -2
- package/web/assets/ServiceFormPage-Dax40DkX.js +0 -1
- package/web/assets/SettingsPage-zriZbuPv.js +0 -2062
- package/web/assets/UnlockPage-BYtu3w_t.js +0 -1
- package/web/assets/VariablesTable-IvhXwdsn.js +0 -1
- package/web/assets/VaultMigrationPage-6Id4Fmeo.js +0 -1
- package/web/assets/VersionBadge-B-PtNx51.js +0 -1
- package/web/assets/WikiPage-DqqlIbM6.js +0 -1
- package/web/assets/WikiView-BZvgaTQp.js +0 -1
- package/web/assets/auth-errors-B510-08J.js +0 -1
- package/web/assets/autocomplete-DpSrZtZQ.js +0 -422
- package/web/assets/card-TJ-av0Pi.js +0 -32
- package/web/assets/client-D5eMRPpD.js +0 -24
- package/web/assets/cloud-chat.store-DzSz0QMb.js +0 -1
- package/web/assets/column-BAToyYIb.js +0 -907
- package/web/assets/interface-content-folder.svg-BF9F5qf2.js +0 -1
- package/web/assets/modal-C5SccGq4.js +0 -1
- package/web/assets/modal-CvqNyAvh.js +0 -5
- package/web/assets/multiselect-CSd05WUd.js +0 -251
- package/web/assets/process.store-B_vfVoW1.js +0 -1
- package/web/assets/router-CEOd6ZAd.js +0 -1
- package/web/assets/settings.store-BfNYjk_y.js +0 -1
- package/web/assets/src-CXyRAwtR.js +0 -1
- package/web/assets/trpc-BNJN8Syq.js +0 -1
- package/web/assets/useAiChatDrawer-DJh6hREC.js +0 -1
- package/web/assets/useCloudOrg-DYG5BAW1.js +0 -1
- package/web/assets/useProjectCatalog-Dyg12Vcr.js +0 -1
- /package/{agent-manager-KX5MSTAK.js.map → agent-manager-RGDHYM64.js.map} +0 -0
- /package/web/assets/{abap-DMmvWZUy.js → abap-DT2sAUGb.js} +0 -0
- /package/web/assets/{apex-D89PiTzU.js → apex-BqR8ejP0.js} +0 -0
- /package/web/assets/{azcli-ChnjTTA5.js → azcli-DK_jNPxf.js} +0 -0
- /package/web/assets/{bat-uLzQ_FhK.js → bat-BLmcleEN.js} +0 -0
- /package/web/assets/{bicep-CMOK7Q1v.js → bicep-DkR6Nnyy.js} +0 -0
- /package/web/assets/{cameligo-D1AyojFH.js → cameligo-DfT1QA_V.js} +0 -0
- /package/web/assets/{clojure-D9JoUgXn.js → clojure-BbsGZ_q_.js} +0 -0
- /package/web/assets/{coffee-D-5sSToL.js → coffee-CZQPrkXM.js} +0 -0
- /package/web/assets/{cpp-BqT_t_y9.js → cpp-CZLFjmrQ.js} +0 -0
- /package/web/assets/{csharp-CuToYI91.js → csharp-CljM85nx.js} +0 -0
- /package/web/assets/{csp-CffcxvlO.js → csp-B7ItwhrE.js} +0 -0
- /package/web/assets/{css-CUD0biQW.js → css-CIuVviKc.js} +0 -0
- /package/web/assets/{cypher-ByyWL_9U.js → cypher-Lk1MiloH.js} +0 -0
- /package/web/assets/{dart-B__ipcUn.js → dart-B83vLjrb.js} +0 -0
- /package/web/assets/{dockerfile-DS-_8bWw.js → dockerfile--LImnfYW.js} +0 -0
- /package/web/assets/{ecl-DWsNCBUj.js → ecl-C1mPHzto.js} +0 -0
- /package/web/assets/{elixir-OfjEy-3g.js → elixir-F7VkySYx.js} +0 -0
- /package/web/assets/{flow9-BPX22hxS.js → flow9-CyDLjcIP.js} +0 -0
- /package/web/assets/{fsharp-CVJcfIUn.js → fsharp-BtAy52j8.js} +0 -0
- /package/web/assets/{go-CfRHZ3yg.js → go-nAky3Nvu.js} +0 -0
- /package/web/assets/{graphql-BZaO3ttK.js → graphql-BFHB5AhE.js} +0 -0
- /package/web/assets/{hcl-BF9-qUku.js → hcl-DiNGWUmy.js} +0 -0
- /package/web/assets/{ini-BUvw6xhr.js → ini-D1Fg5XrG.js} +0 -0
- /package/web/assets/{java-BZ5BNd2x.js → java-C-10mSK7.js} +0 -0
- /package/web/assets/{julia-DN38cgYT.js → julia-Bqx3lIQk.js} +0 -0
- /package/web/assets/{kotlin-CjEJKT8q.js → kotlin-BBx7H7X0.js} +0 -0
- /package/web/assets/{less-DMEH_Nwq.js → less-BFHUOKEO.js} +0 -0
- /package/web/assets/{lexon-CKQUX8Ov.js → lexon-CZNaGS2_.js} +0 -0
- /package/web/assets/{lua-CuWjKLJ0.js → lua-iuAeZ15y.js} +0 -0
- /package/web/assets/{m3-CEp1bVrM.js → m3-Dotjo0U8.js} +0 -0
- /package/web/assets/{markdown-DxtQ_puq.js → markdown-B_31SQ_4.js} +0 -0
- /package/web/assets/{mips-Dx7qydpO.js → mips-Dp4rVQ6A.js} +0 -0
- /package/web/assets/{msdax-DpNCaSiO.js → msdax-DGvgi_My.js} +0 -0
- /package/web/assets/{mysql-C3xsBDPP.js → mysql-jqX2Izgu.js} +0 -0
- /package/web/assets/{objective-c-BbaVHCX1.js → objective-c-KWePe42v.js} +0 -0
- /package/web/assets/{pascal-sDju2faq.js → pascal-k9Augcc6.js} +0 -0
- /package/web/assets/{pascaligo-ChCdOH6b.js → pascaligo-CijZuMoC.js} +0 -0
- /package/web/assets/{perl-BCgLFj01.js → perl-CtKbbdqv.js} +0 -0
- /package/web/assets/{pgsql-DtKby69f.js → pgsql-IYBGBY8p.js} +0 -0
- /package/web/assets/{php-NYvR17Qb.js → php-CZgzKas5.js} +0 -0
- /package/web/assets/{pla-gSg-RBG0.js → pla-DxEUZCU5.js} +0 -0
- /package/web/assets/{postiats-BQxHyDfy.js → postiats-Cf9oispl.js} +0 -0
- /package/web/assets/{powerquery-DzDmbVZN.js → powerquery-Cyri2j7y.js} +0 -0
- /package/web/assets/{powershell-yy4Q5BEQ.js → powershell-CezsOW84.js} +0 -0
- /package/web/assets/{protobuf-B_hIk9q0.js → protobuf-BVSVLozM.js} +0 -0
- /package/web/assets/{pug-CdeEb1ft.js → pug-tdDHxxGI.js} +0 -0
- /package/web/assets/{qsharp-CiSquEgT.js → qsharp-BbxYBU73.js} +0 -0
- /package/web/assets/{r-D1Ocz1x4.js → r-CMiogh24.js} +0 -0
- /package/web/assets/{redis-D42Y12yz.js → redis-Be7YRs2n.js} +0 -0
- /package/web/assets/{redshift-CS62MYfG.js → redshift-BMtedC4Y.js} +0 -0
- /package/web/assets/{restructuredtext-CKFSMEXw.js → restructuredtext-BeqA_Nw7.js} +0 -0
- /package/web/assets/{ruby-C4gTaeU3.js → ruby-DQh1qjXf.js} +0 -0
- /package/web/assets/{rust-C_f2AiHQ.js → rust-Dtb7SJq9.js} +0 -0
- /package/web/assets/{sb-CdNdpP1r.js → sb-YjmuT7ii.js} +0 -0
- /package/web/assets/{scala-N4EWaFTL.js → scala-3ZhdCU1r.js} +0 -0
- /package/web/assets/{scheme-CttIARxx.js → scheme-Ck6PwJVN.js} +0 -0
- /package/web/assets/{scss-v0wtbxaE.js → scss-CdSZkceb.js} +0 -0
- /package/web/assets/{shell-BJdEWXbc.js → shell-D951-SzL.js} +0 -0
- /package/web/assets/{solidity-DKDa59zZ.js → solidity-BtE4jdUZ.js} +0 -0
- /package/web/assets/{sophia--Kw-vDpr.js → sophia-CPgh17t9.js} +0 -0
- /package/web/assets/{sparql-BI8jS9Pc.js → sparql-D5V7fRaF.js} +0 -0
- /package/web/assets/{sql-Dz6z--6O.js → sql-CIe64i2G.js} +0 -0
- /package/web/assets/{st-CT8D05gv.js → st-BUZL4NkZ.js} +0 -0
- /package/web/assets/{swift-Ayb3KzWx.js → swift-BGpAy-yZ.js} +0 -0
- /package/web/assets/{systemverilog-BtWJM9O5.js → systemverilog-CYBqw_20.js} +0 -0
- /package/web/assets/{tcl-BJvUWpbV.js → tcl-CqmcDc3_.js} +0 -0
- /package/web/assets/{twig-c3YaBCU6.js → twig-BWVIPIPE.js} +0 -0
- /package/web/assets/{typespec-gFCWLBoZ.js → typespec-mTM8EdlQ.js} +0 -0
- /package/web/assets/{uuid-B3B8HIK9.js → uuid-aTVnbu2y.js} +0 -0
- /package/web/assets/{vb-CVtfWXV2.js → vb-xvgQ7mYU.js} +0 -0
- /package/web/assets/{w3c-keyname-nn0G3QC2.js → w3c-keyname-CjzYiphr.js} +0 -0
- /package/web/assets/{wgsl-C_huaGQy.js → wgsl-C8m-CvBD.js} +0 -0
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
allVaultRoots,
|
|
36
36
|
clearEncryptionKey,
|
|
37
37
|
clearPendingLegacyKey,
|
|
38
|
+
clearVaultLock,
|
|
38
39
|
decryptValue,
|
|
39
40
|
decryptValueWith,
|
|
40
41
|
decryptVariableSlots,
|
|
@@ -60,10 +61,12 @@ import {
|
|
|
60
61
|
legacyMachineFilePath,
|
|
61
62
|
legacyVaultRoot,
|
|
62
63
|
listCloudVaults,
|
|
64
|
+
listOrgVaults,
|
|
63
65
|
loadFromEnv,
|
|
64
66
|
localVaultRoot,
|
|
65
67
|
machineFilePath,
|
|
66
68
|
machineRoot,
|
|
69
|
+
markVaultLocked,
|
|
67
70
|
nativeParsers,
|
|
68
71
|
orgVaultRoot,
|
|
69
72
|
projectKey,
|
|
@@ -71,8 +74,10 @@ import {
|
|
|
71
74
|
readEntity,
|
|
72
75
|
readOrgClouds,
|
|
73
76
|
refOfProjectAmong,
|
|
77
|
+
refsOfProjectId,
|
|
74
78
|
registerProjectLoader,
|
|
75
79
|
rootOfProjectAmong,
|
|
80
|
+
rootOfProjectRef,
|
|
76
81
|
serviceStore,
|
|
77
82
|
setEncryptionKey,
|
|
78
83
|
setEncryptionKeyInMemory,
|
|
@@ -83,8 +88,9 @@ import {
|
|
|
83
88
|
stampOrgCloud,
|
|
84
89
|
traefikManager,
|
|
85
90
|
vaultFor,
|
|
91
|
+
vaultIdOfRoot,
|
|
86
92
|
writeEntity
|
|
87
|
-
} from "./chunk-
|
|
93
|
+
} from "./chunk-EL75GG7O.js";
|
|
88
94
|
|
|
89
95
|
// ../server/src/create-server.ts
|
|
90
96
|
import express from "express";
|
|
@@ -127,7 +133,7 @@ async function verifyJwt(token) {
|
|
|
127
133
|
// ../server/src/services/api-key-store.ts
|
|
128
134
|
import { randomBytes as randomBytes2, randomUUID, timingSafeEqual } from "crypto";
|
|
129
135
|
import { readFile, writeFile, rename, mkdir, chmod } from "fs/promises";
|
|
130
|
-
import { dirname } from "path";
|
|
136
|
+
import { dirname, join } from "path";
|
|
131
137
|
function maskApiKey(apiKey) {
|
|
132
138
|
const { key, ...rest } = apiKey;
|
|
133
139
|
return { ...rest, keyPreview: key.slice(-4) };
|
|
@@ -136,47 +142,66 @@ var FILE_PUBLIC = "api-keys.json";
|
|
|
136
142
|
var FILE_PRIVATE = "api-keys.private.json";
|
|
137
143
|
var ApiKeyStore = class {
|
|
138
144
|
keys = /* @__PURE__ */ new Map();
|
|
145
|
+
/** Le propriétaire de chaque clé, d'après le coffre où elle a été lue. */
|
|
146
|
+
owners = /* @__PURE__ */ new Map();
|
|
147
|
+
/** Tous les coffres rencontrés — voir `AgentStore.knownOwners`, même raison. */
|
|
148
|
+
knownOwners = /* @__PURE__ */ new Set([null]);
|
|
139
149
|
loaded = false;
|
|
140
150
|
/**
|
|
141
151
|
* Encrypted at rest, so the file sits beside the key that opens it — read
|
|
142
152
|
* under a different key it would decrypt to blanks, then be saved back blank.
|
|
143
153
|
*/
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Le fichier d'un propriétaire. Côté machine il reste collé à la clé qui
|
|
156
|
+
* l'ouvre (`keyedFilePath`) ; côté organisation, c'est son coffre.
|
|
157
|
+
*/
|
|
158
|
+
filePath(isPublic, owner) {
|
|
159
|
+
const name = isPublic ? FILE_PUBLIC : FILE_PRIVATE;
|
|
160
|
+
if (owner === null) return keyedFilePath(name);
|
|
161
|
+
return Promise.resolve(join(orgVaultRoot(owner), name));
|
|
162
|
+
}
|
|
163
|
+
async persistFile(isPublic, owner) {
|
|
164
|
+
const path = await this.filePath(isPublic, owner);
|
|
165
|
+
await mkdir(dirname(path), { recursive: true });
|
|
166
|
+
const keys = Array.from(this.keys.values()).filter((k) => k.isPublic === isPublic && (this.owners.get(k.id) ?? null) === owner).map((k) => ({ ...k, key: encryptValue(k.key, owner ?? void 0) }));
|
|
154
167
|
const tmp = path + ".tmp";
|
|
155
168
|
await writeFile(tmp, JSON.stringify({ keys }, null, 2), "utf-8");
|
|
156
169
|
await rename(tmp, path);
|
|
157
170
|
await chmod(path, 384);
|
|
158
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Réécrire ce fichier pour chaque propriétaire connu.
|
|
174
|
+
*
|
|
175
|
+
* Y compris ceux qui n'ont plus de clé : sinon une clé supprimée — ou dont le
|
|
176
|
+
* propriétaire a changé — survivrait dans son ancien coffre.
|
|
177
|
+
*/
|
|
159
178
|
async persist(isPublic) {
|
|
160
|
-
await this.persistFile(isPublic);
|
|
179
|
+
for (const owner of this.knownOwners) await this.persistFile(isPublic, owner);
|
|
161
180
|
}
|
|
162
|
-
async loadFile(path) {
|
|
181
|
+
async loadFile(path, owner) {
|
|
163
182
|
try {
|
|
164
183
|
const raw = await readFile(path, "utf-8");
|
|
165
184
|
const parsed = ApiKeysFileSchema.safeParse(JSON.parse(raw));
|
|
166
185
|
if (parsed.success) {
|
|
167
186
|
for (const k of parsed.data.keys) {
|
|
168
|
-
const key = isEncryptedValue(k.key) ? decryptValue(k.key) : k.key;
|
|
187
|
+
const key = isEncryptedValue(k.key) ? decryptValue(k.key, owner ?? void 0) : k.key;
|
|
169
188
|
this.keys.set(k.id, { ...k, key });
|
|
189
|
+
this.owners.set(k.id, owner);
|
|
190
|
+
this.knownOwners.add(owner);
|
|
170
191
|
}
|
|
171
192
|
}
|
|
172
193
|
} catch {
|
|
173
194
|
}
|
|
174
195
|
}
|
|
196
|
+
/** La machine, puis chaque organisation dont un coffre existe sur ce disque. */
|
|
175
197
|
async load() {
|
|
176
198
|
if (this.loaded) return;
|
|
177
199
|
this.loaded = true;
|
|
178
|
-
|
|
179
|
-
|
|
200
|
+
const owners = [null, ...await listOrgVaults()];
|
|
201
|
+
for (const owner of owners) {
|
|
202
|
+
await this.loadFile(await this.filePath(true, owner), owner);
|
|
203
|
+
await this.loadFile(await this.filePath(false, owner), owner);
|
|
204
|
+
}
|
|
180
205
|
}
|
|
181
206
|
/**
|
|
182
207
|
* Keys as exposed to callers — secrets stripped. Use `reveal` to obtain one
|
|
@@ -191,7 +216,7 @@ var ApiKeyStore = class {
|
|
|
191
216
|
await this.load();
|
|
192
217
|
return this.keys.get(id)?.key ?? null;
|
|
193
218
|
}
|
|
194
|
-
async create(name, scopes, isPublic) {
|
|
219
|
+
async create(name, scopes, isPublic, owner = null) {
|
|
195
220
|
await this.load();
|
|
196
221
|
const apiKey = {
|
|
197
222
|
id: `apikey-${randomUUID()}`,
|
|
@@ -202,6 +227,8 @@ var ApiKeyStore = class {
|
|
|
202
227
|
isPublic
|
|
203
228
|
};
|
|
204
229
|
this.keys.set(apiKey.id, apiKey);
|
|
230
|
+
this.owners.set(apiKey.id, owner);
|
|
231
|
+
this.knownOwners.add(owner);
|
|
205
232
|
await this.persist(isPublic);
|
|
206
233
|
return apiKey;
|
|
207
234
|
}
|
|
@@ -212,8 +239,8 @@ var ApiKeyStore = class {
|
|
|
212
239
|
const updated = { ...existing, name, scopes, isPublic };
|
|
213
240
|
this.keys.set(id, updated);
|
|
214
241
|
if (existing.isPublic !== isPublic) {
|
|
215
|
-
await this.
|
|
216
|
-
await this.
|
|
242
|
+
await this.persist(existing.isPublic);
|
|
243
|
+
await this.persist(isPublic);
|
|
217
244
|
} else {
|
|
218
245
|
await this.persist(isPublic);
|
|
219
246
|
}
|
|
@@ -224,6 +251,7 @@ var ApiKeyStore = class {
|
|
|
224
251
|
const existing = this.keys.get(id);
|
|
225
252
|
if (!existing) return false;
|
|
226
253
|
this.keys.delete(id);
|
|
254
|
+
this.owners.delete(id);
|
|
227
255
|
await this.persist(existing.isPublic);
|
|
228
256
|
return true;
|
|
229
257
|
}
|
|
@@ -450,11 +478,11 @@ import { TRPCError as TRPCError2 } from "@trpc/server";
|
|
|
450
478
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
451
479
|
import { EventEmitter } from "events";
|
|
452
480
|
import { readFile as readFile2, writeFile as writeFile3, rename as rename2, mkdir as mkdir2, chmod as chmod2, rm } from "fs/promises";
|
|
453
|
-
import { join as
|
|
481
|
+
import { join as join3 } from "path";
|
|
454
482
|
|
|
455
483
|
// ../server/src/utils/ensure-data-gitignore.ts
|
|
456
484
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
457
|
-
import { join } from "path";
|
|
485
|
+
import { join as join2 } from "path";
|
|
458
486
|
var GITIGNORE_CONTENT = `
|
|
459
487
|
#################
|
|
460
488
|
# Do not edit this file #
|
|
@@ -491,7 +519,7 @@ conversations.private.json
|
|
|
491
519
|
*.log.*
|
|
492
520
|
`;
|
|
493
521
|
async function ensureDataGitignore(dataDir) {
|
|
494
|
-
const gitignorePath =
|
|
522
|
+
const gitignorePath = join2(dataDir, ".gitignore");
|
|
495
523
|
await writeFile2(gitignorePath, GITIGNORE_CONTENT, "utf-8");
|
|
496
524
|
}
|
|
497
525
|
|
|
@@ -506,14 +534,14 @@ async function readLocalProject() {
|
|
|
506
534
|
const root = localVaultRoot();
|
|
507
535
|
let current = null;
|
|
508
536
|
try {
|
|
509
|
-
const raw = await readFile2(
|
|
537
|
+
const raw = await readFile2(join3(root, FILENAME), "utf-8");
|
|
510
538
|
const parsed = ProjectSchema.safeParse(JSON.parse(raw));
|
|
511
539
|
if (parsed.success) current = parsed.data;
|
|
512
540
|
} catch {
|
|
513
541
|
}
|
|
514
542
|
let legacy;
|
|
515
543
|
try {
|
|
516
|
-
legacy = JSON.parse(await readFile2(
|
|
544
|
+
legacy = JSON.parse(await readFile2(join3(root, LEGACY_FILENAME), "utf-8"));
|
|
517
545
|
} catch {
|
|
518
546
|
return { local: current, strayCloud: [] };
|
|
519
547
|
}
|
|
@@ -523,7 +551,7 @@ async function readLocalProject() {
|
|
|
523
551
|
if (locals.length > 1) {
|
|
524
552
|
const names = locals.map((p) => `"${p.name}" (${p.id})`).join(", ");
|
|
525
553
|
throw new Error(
|
|
526
|
-
`${
|
|
554
|
+
`${join3(root, LEGACY_FILENAME)} holds ${locals.length} local projects: ${names}. A directory now holds a single local project \u2014 move the others to their own directory, or into an organization, then start again.`
|
|
527
555
|
);
|
|
528
556
|
}
|
|
529
557
|
return { local: locals[0] ?? null, strayCloud };
|
|
@@ -532,7 +560,7 @@ async function readCloudProjects() {
|
|
|
532
560
|
const found = [];
|
|
533
561
|
for (const { orgId, projectId, root } of await listCloudVaults()) {
|
|
534
562
|
try {
|
|
535
|
-
const raw = await readFile2(
|
|
563
|
+
const raw = await readFile2(join3(root, FILENAME), "utf-8");
|
|
536
564
|
const parsed = ProjectSchema.safeParse({ ...JSON.parse(raw), id: projectId, orgId });
|
|
537
565
|
if (parsed.success) found.push({ project: parsed.data, root });
|
|
538
566
|
} catch {
|
|
@@ -562,7 +590,7 @@ var ProjectStore = class {
|
|
|
562
590
|
/** Emits `'saved'` after every persisted mutation, for the cloud sync bridge. */
|
|
563
591
|
events = new EventEmitter();
|
|
564
592
|
getFilePath(root = localVaultRoot()) {
|
|
565
|
-
return
|
|
593
|
+
return join3(root, FILENAME);
|
|
566
594
|
}
|
|
567
595
|
async writeAtomic(filePath, data) {
|
|
568
596
|
const tmpPath = filePath + ".tmp";
|
|
@@ -643,7 +671,7 @@ var ProjectStore = class {
|
|
|
643
671
|
await rm(this.getFilePath(), { force: true }).catch(() => {
|
|
644
672
|
});
|
|
645
673
|
}
|
|
646
|
-
await rename2(
|
|
674
|
+
await rename2(join3(localVaultRoot(), LEGACY_FILENAME), join3(localVaultRoot(), LEGACY_FILENAME + ".bak")).catch(() => {
|
|
647
675
|
});
|
|
648
676
|
for (const { project, root } of cloud) {
|
|
649
677
|
await mkdir2(root, { recursive: true });
|
|
@@ -732,6 +760,40 @@ var ProjectStore = class {
|
|
|
732
760
|
await this.save();
|
|
733
761
|
return updated;
|
|
734
762
|
}
|
|
763
|
+
/**
|
|
764
|
+
* Faire passer le projet local de ce dossier dans une organisation.
|
|
765
|
+
*
|
|
766
|
+
* Le seul déplacement de coffre qu'on s'autorise, et il ne va que dans ce
|
|
767
|
+
* sens : le projet quitte `<cwd>/.runeya` pour
|
|
768
|
+
* `~/.runeya/orgs/<org>/projects/<id>/`, en gardant son identifiant. Il n'y a
|
|
769
|
+
* donc jamais deux copies à réconcilier — c'est ce qui rendait « publier »
|
|
770
|
+
* dangereux tant que le projet restait aussi local.
|
|
771
|
+
*
|
|
772
|
+
* Refusé si l'organisation porte déjà ce projet : la copie d'ici écraserait
|
|
773
|
+
* celle du cloud, et ces deux histoires ne sont pas à nous de les fusionner.
|
|
774
|
+
*
|
|
775
|
+
* Ce qui suit le projet est écrit par les stores eux-mêmes : `save()` réécrit
|
|
776
|
+
* chaque coffre — le fichier local disparaît, celui de l'org apparaît — puis
|
|
777
|
+
* les contenus sont relogés depuis le registre, qui pointe désormais ailleurs.
|
|
778
|
+
*/
|
|
779
|
+
async moveToOrg(projectId, orgId) {
|
|
780
|
+
await this.load();
|
|
781
|
+
const local = this.projects.get(projectKey({ id: projectId, orgId: null }));
|
|
782
|
+
if (!local) throw new Error("PROJECT_NOT_LOCAL");
|
|
783
|
+
if (this.projects.get(projectKey({ id: projectId, orgId }))) {
|
|
784
|
+
throw new Error("PROJECT_ALREADY_IN_ORG");
|
|
785
|
+
}
|
|
786
|
+
const moved = { ...local, orgId };
|
|
787
|
+
this.projects.delete(projectKey(local));
|
|
788
|
+
forgetProject(local);
|
|
789
|
+
this.projects.set(projectKey(moved), moved);
|
|
790
|
+
setProjectVault(moved, vaultFor(moved).root);
|
|
791
|
+
setProjectServices(moved, moved.serviceIds ?? []);
|
|
792
|
+
await this.save();
|
|
793
|
+
await serviceStore.reassignOrg(moved.serviceIds ?? [], orgId);
|
|
794
|
+
await environmentStore.relocateAll();
|
|
795
|
+
return moved;
|
|
796
|
+
}
|
|
735
797
|
async delete(target) {
|
|
736
798
|
await this.load();
|
|
737
799
|
const found = this.find(target);
|
|
@@ -902,7 +964,7 @@ registerProjectLoader(() => projectStore.list().then(() => void 0));
|
|
|
902
964
|
|
|
903
965
|
// ../server/src/services/environment-overrides-store.ts
|
|
904
966
|
import { readFile as readFile3, writeFile as writeFile4, rename as rename3, mkdir as mkdir3, chmod as chmod3 } from "fs/promises";
|
|
905
|
-
import { dirname as dirname2, join as
|
|
967
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
906
968
|
var FILENAME2 = "environment-overrides.json";
|
|
907
969
|
var EnvironmentOverridesStore = class {
|
|
908
970
|
data = {};
|
|
@@ -956,7 +1018,7 @@ var EnvironmentOverridesStore = class {
|
|
|
956
1018
|
const sources = [
|
|
957
1019
|
this.getLegacyFilePath(),
|
|
958
1020
|
await keyedFilePath(FILENAME2),
|
|
959
|
-
...roots.map((root) =>
|
|
1021
|
+
...roots.map((root) => join4(root, FILENAME2))
|
|
960
1022
|
];
|
|
961
1023
|
for (const path of sources) {
|
|
962
1024
|
let parsed;
|
|
@@ -972,7 +1034,8 @@ var EnvironmentOverridesStore = class {
|
|
|
972
1034
|
const decrypted = decryptVariableSlots(
|
|
973
1035
|
parsed,
|
|
974
1036
|
"environment-overrides",
|
|
975
|
-
(variableId) => this.unreadableSecrets.add(variableId)
|
|
1037
|
+
(variableId) => this.unreadableSecrets.add(variableId),
|
|
1038
|
+
vaultIdOfRoot(dirname2(path))
|
|
976
1039
|
);
|
|
977
1040
|
for (const [id, override] of Object.entries(decrypted)) {
|
|
978
1041
|
this.data[id] = { value: override.value };
|
|
@@ -1004,14 +1067,15 @@ var EnvironmentOverridesStore = class {
|
|
|
1004
1067
|
for (const [root, overrides] of byRoot) {
|
|
1005
1068
|
const encrypted = this.keepUnreadableSecrets(encryptVariableSlots(
|
|
1006
1069
|
overrides,
|
|
1007
|
-
this.diskEncryptedSlots
|
|
1070
|
+
this.diskEncryptedSlots,
|
|
1071
|
+
vaultIdOfRoot(root)
|
|
1008
1072
|
));
|
|
1009
1073
|
Object.assign(
|
|
1010
1074
|
this.diskEncryptedSlots,
|
|
1011
1075
|
extractEncryptedVariableSlots(encrypted)
|
|
1012
1076
|
);
|
|
1013
1077
|
await mkdir3(root, { recursive: true });
|
|
1014
|
-
const path =
|
|
1078
|
+
const path = join4(root, FILENAME2);
|
|
1015
1079
|
const tmpPath = path + ".tmp";
|
|
1016
1080
|
await writeFile4(tmpPath, JSON.stringify(encrypted, null, 2), "utf-8");
|
|
1017
1081
|
await rename3(tmpPath, path);
|
|
@@ -1540,7 +1604,7 @@ var auditService = new AuditService();
|
|
|
1540
1604
|
|
|
1541
1605
|
// ../server/src/services/project-path-store.ts
|
|
1542
1606
|
import { readFile as readFile5, writeFile as writeFile5, rename as rename5, mkdir as mkdir5, chmod as chmod4, stat as stat2 } from "fs/promises";
|
|
1543
|
-
import { isAbsolute, join as
|
|
1607
|
+
import { isAbsolute, join as join5, resolve } from "path";
|
|
1544
1608
|
var FILENAME3 = "project-paths.json";
|
|
1545
1609
|
var ProjectPathStore = class {
|
|
1546
1610
|
paths = null;
|
|
@@ -1610,7 +1674,7 @@ function isPathMap(value) {
|
|
|
1610
1674
|
async function holdsRuneyaProject(path) {
|
|
1611
1675
|
for (const candidate of ["project.json", "projects.json"]) {
|
|
1612
1676
|
try {
|
|
1613
|
-
await stat2(
|
|
1677
|
+
await stat2(join5(path, ".runeya", candidate));
|
|
1614
1678
|
return true;
|
|
1615
1679
|
} catch {
|
|
1616
1680
|
}
|
|
@@ -1896,27 +1960,61 @@ import { TRPCError as TRPCError3 } from "@trpc/server";
|
|
|
1896
1960
|
|
|
1897
1961
|
// ../server/src/services/settings-manager.ts
|
|
1898
1962
|
import { readFile as readFile6, writeFile as writeFile6, rename as rename6, mkdir as mkdir6, chmod as chmod5 } from "fs/promises";
|
|
1963
|
+
import { join as join6 } from "path";
|
|
1899
1964
|
var SETTINGS_FILE = "settings.json";
|
|
1900
1965
|
var SETTINGS_PRIVATE_FILE = "settings.private.json";
|
|
1901
1966
|
var SCHEMA_VERSION_FILE = "schema-version.json";
|
|
1902
1967
|
var CURRENT_SCHEMA_VERSION = 4;
|
|
1903
1968
|
var SettingsManager = class {
|
|
1904
|
-
settings =
|
|
1905
|
-
settingsPrivate =
|
|
1969
|
+
settings = /* @__PURE__ */ new Map();
|
|
1970
|
+
settingsPrivate = /* @__PURE__ */ new Map();
|
|
1906
1971
|
schemaVersion = null;
|
|
1907
|
-
|
|
1908
|
-
|
|
1972
|
+
/**
|
|
1973
|
+
* Le coffre visé, sous forme de racine sur le disque.
|
|
1974
|
+
*
|
|
1975
|
+
* Sans organisation, la racine machine — pas le répertoire de lancement. Ce
|
|
1976
|
+
* n'est pas un reste de l'ancien monde : y écrire forcerait un `.runeya` à
|
|
1977
|
+
* exister chez quelqu'un qui ne travaille que sur des projets cloud, ce que
|
|
1978
|
+
* les coffres ont justement supprimé (et ce qu'un test garde).
|
|
1979
|
+
*/
|
|
1980
|
+
rootOf(vault) {
|
|
1981
|
+
return vault ? orgVaultRoot(vault) : machineRoot();
|
|
1909
1982
|
}
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1983
|
+
cacheKey(vault) {
|
|
1984
|
+
return vault ?? "";
|
|
1985
|
+
}
|
|
1986
|
+
async atomicWrite(filename, data, vault) {
|
|
1987
|
+
const root = this.rootOf(vault);
|
|
1988
|
+
await mkdir6(root, { recursive: true });
|
|
1989
|
+
const filePath = join6(root, filename);
|
|
1913
1990
|
const tmpPath = filePath + ".tmp";
|
|
1914
1991
|
await writeFile6(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
1915
1992
|
await rename6(tmpPath, filePath);
|
|
1916
1993
|
await chmod5(filePath, 384);
|
|
1917
1994
|
}
|
|
1918
|
-
/**
|
|
1919
|
-
|
|
1995
|
+
/**
|
|
1996
|
+
* Lire le fichier de CE coffre, et rien d'autre.
|
|
1997
|
+
*
|
|
1998
|
+
* Pas de repli sur un autre emplacement : un réglage lu ailleurs que là où il
|
|
1999
|
+
* est écrit fait deux vérités pour un même fait, et l'accord entre les deux
|
|
2000
|
+
* devient une chose de plus à tenir. Ce que les versions précédentes ont
|
|
2001
|
+
* écrit ailleurs est déplacé une fois pour toutes par
|
|
2002
|
+
* `migrateSettingsIntoVaults`, pas relu indéfiniment.
|
|
2003
|
+
*/
|
|
2004
|
+
async readJson(filename, vault) {
|
|
2005
|
+
try {
|
|
2006
|
+
return JSON.parse(await readFile6(join6(this.rootOf(vault), filename), "utf-8"));
|
|
2007
|
+
} catch {
|
|
2008
|
+
return null;
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
/** Vide les caches — après un déplacement de fichiers sous nos pieds. */
|
|
2012
|
+
forget() {
|
|
2013
|
+
this.settings.clear();
|
|
2014
|
+
this.settingsPrivate.clear();
|
|
2015
|
+
}
|
|
2016
|
+
/** Un fichier qui appartient au poste, pas à un coffre. */
|
|
2017
|
+
async readMachineJson(filename) {
|
|
1920
2018
|
for (const path of [machineFilePath(filename), legacyMachineFilePath(filename)]) {
|
|
1921
2019
|
try {
|
|
1922
2020
|
return JSON.parse(await readFile6(path, "utf-8"));
|
|
@@ -1925,43 +2023,53 @@ var SettingsManager = class {
|
|
|
1925
2023
|
}
|
|
1926
2024
|
return null;
|
|
1927
2025
|
}
|
|
1928
|
-
async getSettings() {
|
|
1929
|
-
|
|
1930
|
-
|
|
2026
|
+
async getSettings(vault) {
|
|
2027
|
+
const key = this.cacheKey(vault);
|
|
2028
|
+
let current = this.settings.get(key);
|
|
2029
|
+
if (!current) {
|
|
2030
|
+
const raw = await this.readJson(SETTINGS_FILE, vault);
|
|
1931
2031
|
const parsed = raw ? SettingsSchema.safeParse(raw) : null;
|
|
1932
2032
|
if (parsed?.success) {
|
|
1933
|
-
|
|
2033
|
+
current = parsed.data;
|
|
2034
|
+
this.settings.set(key, current);
|
|
1934
2035
|
} else {
|
|
1935
|
-
|
|
1936
|
-
await this.saveSettings(
|
|
2036
|
+
current = SettingsSchema.parse({ mode: env.MODE, cloudUrl: env.CLOUD_URL ?? null });
|
|
2037
|
+
await this.saveSettings(current, vault);
|
|
1937
2038
|
}
|
|
1938
2039
|
}
|
|
1939
|
-
return
|
|
2040
|
+
return current;
|
|
1940
2041
|
}
|
|
1941
|
-
async saveSettings(settings) {
|
|
1942
|
-
this.settings
|
|
1943
|
-
await this.atomicWrite(SETTINGS_FILE, settings);
|
|
2042
|
+
async saveSettings(settings, vault) {
|
|
2043
|
+
this.settings.set(this.cacheKey(vault), settings);
|
|
2044
|
+
await this.atomicWrite(SETTINGS_FILE, settings, vault);
|
|
1944
2045
|
}
|
|
1945
|
-
async getPrivateSettings() {
|
|
1946
|
-
|
|
1947
|
-
|
|
2046
|
+
async getPrivateSettings(vault) {
|
|
2047
|
+
const key = this.cacheKey(vault);
|
|
2048
|
+
let current = this.settingsPrivate.get(key);
|
|
2049
|
+
if (!current) {
|
|
2050
|
+
const raw = await this.readJson(SETTINGS_PRIVATE_FILE, vault);
|
|
1948
2051
|
const parsed = raw ? SettingsPrivateSchema.safeParse(raw) : null;
|
|
1949
2052
|
if (parsed?.success) {
|
|
1950
|
-
|
|
2053
|
+
current = parsed.data;
|
|
2054
|
+
this.settingsPrivate.set(key, current);
|
|
1951
2055
|
} else {
|
|
1952
|
-
|
|
1953
|
-
await this.savePrivateSettings(
|
|
2056
|
+
current = SettingsPrivateSchema.parse({});
|
|
2057
|
+
await this.savePrivateSettings(current, vault);
|
|
1954
2058
|
}
|
|
1955
2059
|
}
|
|
1956
|
-
return
|
|
2060
|
+
return current;
|
|
1957
2061
|
}
|
|
1958
|
-
async savePrivateSettings(settings) {
|
|
1959
|
-
this.settingsPrivate
|
|
1960
|
-
await this.atomicWrite(SETTINGS_PRIVATE_FILE, settings);
|
|
2062
|
+
async savePrivateSettings(settings, vault) {
|
|
2063
|
+
this.settingsPrivate.set(this.cacheKey(vault), settings);
|
|
2064
|
+
await this.atomicWrite(SETTINGS_PRIVATE_FILE, settings, vault);
|
|
1961
2065
|
}
|
|
2066
|
+
/**
|
|
2067
|
+
* La version de schéma reste au niveau machine, et ce n'est pas un oubli :
|
|
2068
|
+
* elle ne configure rien, elle date les migrations des données de ce poste.
|
|
2069
|
+
*/
|
|
1962
2070
|
async getSchemaVersion() {
|
|
1963
2071
|
if (!this.schemaVersion) {
|
|
1964
|
-
const raw = await this.
|
|
2072
|
+
const raw = await this.readMachineJson(SCHEMA_VERSION_FILE);
|
|
1965
2073
|
const parsed = raw ? SchemaVersionSchema.safeParse(raw) : null;
|
|
1966
2074
|
if (parsed?.success) {
|
|
1967
2075
|
this.schemaVersion = parsed.data;
|
|
@@ -1974,7 +2082,12 @@ var SettingsManager = class {
|
|
|
1974
2082
|
}
|
|
1975
2083
|
async saveSchemaVersion(sv) {
|
|
1976
2084
|
this.schemaVersion = sv;
|
|
1977
|
-
await
|
|
2085
|
+
await mkdir6(machineRoot(), { recursive: true });
|
|
2086
|
+
const filePath = machineFilePath(SCHEMA_VERSION_FILE);
|
|
2087
|
+
const tmpPath = filePath + ".tmp";
|
|
2088
|
+
await writeFile6(tmpPath, JSON.stringify(sv, null, 2), "utf-8");
|
|
2089
|
+
await rename6(tmpPath, filePath);
|
|
2090
|
+
await chmod5(filePath, 384);
|
|
1978
2091
|
}
|
|
1979
2092
|
getCurrentSchemaVersion() {
|
|
1980
2093
|
return CURRENT_SCHEMA_VERSION;
|
|
@@ -1986,7 +2099,7 @@ var settingsManager = new SettingsManager();
|
|
|
1986
2099
|
import { isAbsolute as isAbsolute3 } from "path";
|
|
1987
2100
|
|
|
1988
2101
|
// ../server/src/services/workspace-cwd.ts
|
|
1989
|
-
import { isAbsolute as isAbsolute2, join as
|
|
2102
|
+
import { isAbsolute as isAbsolute2, join as join7, dirname as dirname3 } from "path";
|
|
1990
2103
|
async function projectRoot(ref) {
|
|
1991
2104
|
const project = await projectStore.get(ref);
|
|
1992
2105
|
if (!project?.orgId) return null;
|
|
@@ -2014,7 +2127,7 @@ async function resolveWorkspaceCwd(target) {
|
|
|
2014
2127
|
function anchor(base, raw) {
|
|
2015
2128
|
if (!raw || raw === ".") return base;
|
|
2016
2129
|
if (raw.startsWith("~") || raw.startsWith("$") || isAbsolute2(raw)) return raw;
|
|
2017
|
-
return
|
|
2130
|
+
return join7(base, raw);
|
|
2018
2131
|
}
|
|
2019
2132
|
function localVaultRootParent() {
|
|
2020
2133
|
return dirname3(localVaultRoot());
|
|
@@ -2272,13 +2385,13 @@ var serviceRouter = router({
|
|
|
2272
2385
|
remote: service.git?.remote,
|
|
2273
2386
|
checkReachability: input.checkReachability
|
|
2274
2387
|
});
|
|
2275
|
-
const { exists:
|
|
2388
|
+
const { exists: exists4, actualRemote, unreachable, resolvedCwd, unresolvedVars } = agentResult;
|
|
2276
2389
|
if (unresolvedVars.length > 0) {
|
|
2277
2390
|
return { exists: false, canClone: false, unresolvedVars, resolvedCwd, gitMismatch: null };
|
|
2278
2391
|
}
|
|
2279
|
-
const canClone = !!service.git?.remote && (!
|
|
2392
|
+
const canClone = !!service.git?.remote && (!exists4 || actualRemote === null);
|
|
2280
2393
|
let gitMismatch = null;
|
|
2281
|
-
if (
|
|
2394
|
+
if (exists4 && service.git?.remote && !canClone) {
|
|
2282
2395
|
if (actualRemote === null) {
|
|
2283
2396
|
gitMismatch = { actual: null, configured: service.git.remote };
|
|
2284
2397
|
} else if (actualRemote !== service.git.remote) {
|
|
@@ -2287,7 +2400,7 @@ var serviceRouter = router({
|
|
|
2287
2400
|
gitMismatch = { actual: actualRemote, configured: service.git.remote, unreachable: true };
|
|
2288
2401
|
}
|
|
2289
2402
|
}
|
|
2290
|
-
return { exists:
|
|
2403
|
+
return { exists: exists4, canClone, unresolvedVars: [], resolvedCwd, gitMismatch };
|
|
2291
2404
|
}),
|
|
2292
2405
|
cloneRepository: protectedProcedure.input(z3.object({ id: z3.string().max(128) })).mutation(async ({ input }) => {
|
|
2293
2406
|
const service = await serviceStore.get(input.id);
|
|
@@ -2376,10 +2489,18 @@ var agentRouter = router({
|
|
|
2376
2489
|
z4.object({
|
|
2377
2490
|
name: z4.string().min(1).max(100),
|
|
2378
2491
|
url: z4.string().url().max(2048),
|
|
2379
|
-
passphrase: z4.string().min(1).max(1024)
|
|
2492
|
+
passphrase: z4.string().min(1).max(1024),
|
|
2493
|
+
// À quelle organisation rattacher l'agent. Absent = la machine, ce que
|
|
2494
|
+
// font l'agent local et tout client antérieur à ce champ.
|
|
2495
|
+
orgId: z4.string().min(1).max(128).nullable().optional()
|
|
2380
2496
|
})
|
|
2381
2497
|
).mutation(async ({ input }) => {
|
|
2382
|
-
const config = await agentManager.register(
|
|
2498
|
+
const config = await agentManager.register(
|
|
2499
|
+
input.name,
|
|
2500
|
+
input.url,
|
|
2501
|
+
input.passphrase,
|
|
2502
|
+
input.orgId ?? null
|
|
2503
|
+
);
|
|
2383
2504
|
return { ...config, passphrase: "***" };
|
|
2384
2505
|
}),
|
|
2385
2506
|
/** Update an existing agent */
|
|
@@ -3120,11 +3241,29 @@ var environmentRouter = router({
|
|
|
3120
3241
|
import { promises as dns } from "dns";
|
|
3121
3242
|
import { z as z9 } from "zod";
|
|
3122
3243
|
import { TRPCError as TRPCError7 } from "@trpc/server";
|
|
3244
|
+
|
|
3245
|
+
// ../server/src/services/settings-scope.ts
|
|
3246
|
+
async function vaultOfProject(projectId) {
|
|
3247
|
+
if (!projectId) return void 0;
|
|
3248
|
+
await ensureProjectsLoaded();
|
|
3249
|
+
const refs = refsOfProjectId(projectId);
|
|
3250
|
+
if (refs.length !== 1) return void 0;
|
|
3251
|
+
return refs[0]?.orgId ?? void 0;
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
// ../server/src/trpc/routers/settings.ts
|
|
3123
3255
|
var NotificationPreferencesPatchSchema = NotificationPreferencesSchema.partial().strict();
|
|
3124
3256
|
function maskApiKey2(plaintext) {
|
|
3125
3257
|
if (plaintext.length <= 8) return "****";
|
|
3126
3258
|
return plaintext.slice(0, 4) + "****" + plaintext.slice(-4);
|
|
3127
3259
|
}
|
|
3260
|
+
var ScopeShape = {
|
|
3261
|
+
orgId: z9.string().max(128).nullable().optional(),
|
|
3262
|
+
projectId: z9.string().max(128).optional()
|
|
3263
|
+
};
|
|
3264
|
+
async function scopeOf(input) {
|
|
3265
|
+
return input?.orgId ?? await vaultOfProject(input?.projectId);
|
|
3266
|
+
}
|
|
3128
3267
|
var settingsRouter = router({
|
|
3129
3268
|
/**
|
|
3130
3269
|
* Get all settings
|
|
@@ -3162,15 +3301,16 @@ var settingsRouter = router({
|
|
|
3162
3301
|
/**
|
|
3163
3302
|
* Get notification preferences
|
|
3164
3303
|
*/
|
|
3165
|
-
getNotificationPreferences: protectedProcedure.query(async () => {
|
|
3166
|
-
const priv = await settingsManager.getPrivateSettings();
|
|
3304
|
+
getNotificationPreferences: protectedProcedure.input(z9.object(ScopeShape).optional()).query(async ({ input }) => {
|
|
3305
|
+
const priv = await settingsManager.getPrivateSettings(await scopeOf(input));
|
|
3167
3306
|
return NotificationPreferencesSchema.parse(priv.notifications ?? {});
|
|
3168
3307
|
}),
|
|
3169
3308
|
/**
|
|
3170
3309
|
* Update notification preferences (granular mute)
|
|
3171
3310
|
*/
|
|
3172
|
-
updateNotificationPreferences: protectedProcedure.input(NotificationPreferencesPatchSchema).mutation(async ({ input }) => {
|
|
3173
|
-
const
|
|
3311
|
+
updateNotificationPreferences: protectedProcedure.input(NotificationPreferencesPatchSchema.extend(ScopeShape)).mutation(async ({ input }) => {
|
|
3312
|
+
const vault = await scopeOf(input);
|
|
3313
|
+
const priv = await settingsManager.getPrivateSettings(vault);
|
|
3174
3314
|
const currentPrefs = NotificationPreferencesSchema.parse(priv.notifications ?? {});
|
|
3175
3315
|
const updatedPrefs = {
|
|
3176
3316
|
...currentPrefs,
|
|
@@ -3179,7 +3319,7 @@ var settingsRouter = router({
|
|
|
3179
3319
|
await settingsManager.savePrivateSettings({
|
|
3180
3320
|
...priv,
|
|
3181
3321
|
notifications: updatedPrefs
|
|
3182
|
-
});
|
|
3322
|
+
}, vault);
|
|
3183
3323
|
return updatedPrefs;
|
|
3184
3324
|
}),
|
|
3185
3325
|
/**
|
|
@@ -3290,13 +3430,14 @@ var settingsRouter = router({
|
|
|
3290
3430
|
/**
|
|
3291
3431
|
* List AI providers (API keys are masked)
|
|
3292
3432
|
*/
|
|
3293
|
-
listAIProviders: protectedProcedure.query(async () => {
|
|
3294
|
-
const
|
|
3433
|
+
listAIProviders: protectedProcedure.input(z9.object(ScopeShape).optional()).query(async ({ input }) => {
|
|
3434
|
+
const vault = await scopeOf(input);
|
|
3435
|
+
const priv = await settingsManager.getPrivateSettings(vault);
|
|
3295
3436
|
return (priv.aiProviders ?? []).map((p) => {
|
|
3296
3437
|
let maskedKey = "(aucune)";
|
|
3297
3438
|
if (p.encryptedApiKey) {
|
|
3298
3439
|
try {
|
|
3299
|
-
maskedKey = maskApiKey2(decryptValue(p.encryptedApiKey));
|
|
3440
|
+
maskedKey = maskApiKey2(decryptValue(p.encryptedApiKey, vault));
|
|
3300
3441
|
} catch {
|
|
3301
3442
|
maskedKey = "(cl\xE9 invalide)";
|
|
3302
3443
|
}
|
|
@@ -3312,12 +3453,14 @@ var settingsRouter = router({
|
|
|
3312
3453
|
provider: AIProviderSchema.shape.provider,
|
|
3313
3454
|
apiKey: z9.string().max(500).optional(),
|
|
3314
3455
|
// optional for providers like Ollama
|
|
3315
|
-
model: z9.string().max(200).optional()
|
|
3456
|
+
model: z9.string().max(200).optional(),
|
|
3457
|
+
...ScopeShape
|
|
3316
3458
|
})).mutation(async ({ input }) => {
|
|
3317
3459
|
if (input.provider !== "ollama" && !input.apiKey?.trim()) {
|
|
3318
3460
|
throw new TRPCError7({ code: "BAD_REQUEST", message: "Une cl\xE9 API est requise pour ce provider." });
|
|
3319
3461
|
}
|
|
3320
|
-
const
|
|
3462
|
+
const vault = await scopeOf(input);
|
|
3463
|
+
const priv = await settingsManager.getPrivateSettings(vault);
|
|
3321
3464
|
const id = `aiprovider-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
3322
3465
|
const now = /* @__PURE__ */ new Date();
|
|
3323
3466
|
const plainKey = input.apiKey ?? "";
|
|
@@ -3325,7 +3468,9 @@ var settingsRouter = router({
|
|
|
3325
3468
|
id,
|
|
3326
3469
|
name: input.name,
|
|
3327
3470
|
provider: input.provider,
|
|
3328
|
-
|
|
3471
|
+
// Chiffrée par la clé de l'organisation : c'est ce qui permet de
|
|
3472
|
+
// partager le fournisseur qu'elle paie sans partager la clé du poste.
|
|
3473
|
+
encryptedApiKey: plainKey ? encryptValue(plainKey, vault) : "",
|
|
3329
3474
|
model: input.model,
|
|
3330
3475
|
active: (priv.aiProviders ?? []).length === 0,
|
|
3331
3476
|
// first one is active by default
|
|
@@ -3335,7 +3480,7 @@ var settingsRouter = router({
|
|
|
3335
3480
|
await settingsManager.savePrivateSettings({
|
|
3336
3481
|
...priv,
|
|
3337
3482
|
aiProviders: [...priv.aiProviders ?? [], newProvider]
|
|
3338
|
-
});
|
|
3483
|
+
}, vault);
|
|
3339
3484
|
return { ...newProvider, maskedKey: plainKey ? maskApiKey2(plainKey) : "(aucune)", encryptedApiKey: void 0 };
|
|
3340
3485
|
}),
|
|
3341
3486
|
/**
|
|
@@ -3346,14 +3491,16 @@ var settingsRouter = router({
|
|
|
3346
3491
|
name: z9.string().min(1).max(200).optional(),
|
|
3347
3492
|
provider: AIProviderSchema.shape.provider.optional(),
|
|
3348
3493
|
apiKey: z9.string().min(1).max(500).optional(),
|
|
3349
|
-
model: z9.string().max(200).optional()
|
|
3494
|
+
model: z9.string().max(200).optional(),
|
|
3495
|
+
...ScopeShape
|
|
3350
3496
|
})).mutation(async ({ input }) => {
|
|
3351
|
-
const
|
|
3497
|
+
const vault = await scopeOf(input);
|
|
3498
|
+
const priv = await settingsManager.getPrivateSettings(vault);
|
|
3352
3499
|
const providers = priv.aiProviders ?? [];
|
|
3353
3500
|
const idx = providers.findIndex((p) => p.id === input.id);
|
|
3354
3501
|
if (idx === -1) throw new TRPCError7({ code: "NOT_FOUND", message: `AI provider not found: ${input.id}` });
|
|
3355
3502
|
const existing = providers[idx];
|
|
3356
|
-
const encryptedApiKey = input.apiKey ? stableEncryptValue(input.apiKey, existing.encryptedApiKey) : existing.encryptedApiKey;
|
|
3503
|
+
const encryptedApiKey = input.apiKey ? stableEncryptValue(input.apiKey, existing.encryptedApiKey, vault) : existing.encryptedApiKey;
|
|
3357
3504
|
const updated = {
|
|
3358
3505
|
...existing,
|
|
3359
3506
|
...input.name ? { name: input.name } : {},
|
|
@@ -3364,11 +3511,11 @@ var settingsRouter = router({
|
|
|
3364
3511
|
};
|
|
3365
3512
|
const newProviders = [...providers];
|
|
3366
3513
|
newProviders[idx] = updated;
|
|
3367
|
-
await settingsManager.savePrivateSettings({ ...priv, aiProviders: newProviders });
|
|
3514
|
+
await settingsManager.savePrivateSettings({ ...priv, aiProviders: newProviders }, vault);
|
|
3368
3515
|
let maskedKey = "(aucune)";
|
|
3369
3516
|
if (updated.encryptedApiKey) {
|
|
3370
3517
|
try {
|
|
3371
|
-
maskedKey = maskApiKey2(decryptValue(updated.encryptedApiKey));
|
|
3518
|
+
maskedKey = maskApiKey2(decryptValue(updated.encryptedApiKey, vault));
|
|
3372
3519
|
} catch {
|
|
3373
3520
|
}
|
|
3374
3521
|
}
|
|
@@ -3377,26 +3524,28 @@ var settingsRouter = router({
|
|
|
3377
3524
|
/**
|
|
3378
3525
|
* Delete AI provider
|
|
3379
3526
|
*/
|
|
3380
|
-
deleteAIProvider: protectedProcedure.input(z9.object({ id: z9.string().min(1).max(128) })).mutation(async ({ input }) => {
|
|
3381
|
-
const
|
|
3527
|
+
deleteAIProvider: protectedProcedure.input(z9.object({ id: z9.string().min(1).max(128), ...ScopeShape })).mutation(async ({ input }) => {
|
|
3528
|
+
const vault = await scopeOf(input);
|
|
3529
|
+
const priv = await settingsManager.getPrivateSettings(vault);
|
|
3382
3530
|
const providers = priv.aiProviders ?? [];
|
|
3383
3531
|
const filtered = providers.filter((p) => p.id !== input.id);
|
|
3384
3532
|
if (filtered.length === providers.length) throw new TRPCError7({ code: "NOT_FOUND", message: `AI provider not found: ${input.id}` });
|
|
3385
3533
|
const deletedWasActive = providers.find((p) => p.id === input.id)?.active ?? false;
|
|
3386
3534
|
if (deletedWasActive && filtered.length > 0) filtered[0].active = true;
|
|
3387
|
-
await settingsManager.savePrivateSettings({ ...priv, aiProviders: filtered });
|
|
3535
|
+
await settingsManager.savePrivateSettings({ ...priv, aiProviders: filtered }, vault);
|
|
3388
3536
|
return { deleted: true };
|
|
3389
3537
|
}),
|
|
3390
3538
|
/**
|
|
3391
3539
|
* Set one provider as active (clears active flag on all others)
|
|
3392
3540
|
*/
|
|
3393
|
-
setActiveAIProvider: protectedProcedure.input(z9.object({ id: z9.string().min(1).max(128) })).mutation(async ({ input }) => {
|
|
3394
|
-
const
|
|
3541
|
+
setActiveAIProvider: protectedProcedure.input(z9.object({ id: z9.string().min(1).max(128), ...ScopeShape })).mutation(async ({ input }) => {
|
|
3542
|
+
const vault = await scopeOf(input);
|
|
3543
|
+
const priv = await settingsManager.getPrivateSettings(vault);
|
|
3395
3544
|
const providers = priv.aiProviders ?? [];
|
|
3396
3545
|
const idx = providers.findIndex((p) => p.id === input.id);
|
|
3397
3546
|
if (idx === -1) throw new TRPCError7({ code: "NOT_FOUND", message: `AI provider not found: ${input.id}` });
|
|
3398
3547
|
const updated = providers.map((p) => ({ ...p, active: p.id === input.id }));
|
|
3399
|
-
await settingsManager.savePrivateSettings({ ...priv, aiProviders: updated });
|
|
3548
|
+
await settingsManager.savePrivateSettings({ ...priv, aiProviders: updated }, vault);
|
|
3400
3549
|
return { id: input.id, active: true };
|
|
3401
3550
|
}),
|
|
3402
3551
|
// ---------------------------------------------------------------------------
|
|
@@ -3693,15 +3842,15 @@ import { z as z11 } from "zod";
|
|
|
3693
3842
|
import { TRPCError as TRPCError8 } from "@trpc/server";
|
|
3694
3843
|
import { execFile as execFile2, spawn } from "child_process";
|
|
3695
3844
|
import { stat as stat3 } from "fs/promises";
|
|
3696
|
-
import { resolve as resolve2, join as
|
|
3845
|
+
import { resolve as resolve2, join as join9 } from "path";
|
|
3697
3846
|
import { homedir as homedir2 } from "os";
|
|
3698
3847
|
|
|
3699
3848
|
// ../server/src/services/user-config-manager.ts
|
|
3700
3849
|
import { readFile as readFile7, writeFile as writeFile7, rename as rename7, mkdir as mkdir7, chmod as chmod6 } from "fs/promises";
|
|
3701
|
-
import { join as
|
|
3850
|
+
import { join as join8 } from "path";
|
|
3702
3851
|
import { homedir } from "os";
|
|
3703
3852
|
import { z as z10 } from "zod";
|
|
3704
|
-
var CONFIG_DIR =
|
|
3853
|
+
var CONFIG_DIR = join8(homedir(), ".runeya");
|
|
3705
3854
|
var CONFIG_FILE = "runeya.conf";
|
|
3706
3855
|
var EditorPreferencesSchema = z10.object({
|
|
3707
3856
|
default: z10.string().max(64).optional()
|
|
@@ -3725,7 +3874,7 @@ var UserConfigManager = class {
|
|
|
3725
3874
|
}
|
|
3726
3875
|
async atomicWrite(data) {
|
|
3727
3876
|
await this.ensureDir();
|
|
3728
|
-
const filePath =
|
|
3877
|
+
const filePath = join8(CONFIG_DIR, CONFIG_FILE);
|
|
3729
3878
|
const tmpPath = filePath + ".tmp";
|
|
3730
3879
|
await writeFile7(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
3731
3880
|
await rename7(tmpPath, filePath);
|
|
@@ -3734,7 +3883,7 @@ var UserConfigManager = class {
|
|
|
3734
3883
|
async getConfig() {
|
|
3735
3884
|
if (!this.config) {
|
|
3736
3885
|
try {
|
|
3737
|
-
const raw = await readFile7(
|
|
3886
|
+
const raw = await readFile7(join8(CONFIG_DIR, CONFIG_FILE), "utf-8");
|
|
3738
3887
|
const parsed = UserConfigSchema.safeParse(JSON.parse(raw));
|
|
3739
3888
|
this.config = parsed.success ? parsed.data : {};
|
|
3740
3889
|
} catch {
|
|
@@ -3887,7 +4036,7 @@ var editorRouter = router({
|
|
|
3887
4036
|
throw new TRPCError8({ code: "BAD_REQUEST", message: `Unknown editor: ${input.editorKey}` });
|
|
3888
4037
|
}
|
|
3889
4038
|
const cwd = await getServiceCwd(input.serviceId);
|
|
3890
|
-
const filePath =
|
|
4039
|
+
const filePath = join9(cwd, input.relativePath);
|
|
3891
4040
|
if (!filePath.startsWith(cwd)) {
|
|
3892
4041
|
throw new TRPCError8({ code: "BAD_REQUEST", message: "Path traversal not allowed" });
|
|
3893
4042
|
}
|
|
@@ -3911,7 +4060,7 @@ var editorRouter = router({
|
|
|
3911
4060
|
import { z as z12 } from "zod";
|
|
3912
4061
|
import { TRPCError as TRPCError9 } from "@trpc/server";
|
|
3913
4062
|
import { mkdir as mkdir8, writeFile as writeFile8 } from "fs/promises";
|
|
3914
|
-
import { join as
|
|
4063
|
+
import { join as join10, dirname as dirname4 } from "path";
|
|
3915
4064
|
var ALLOWED_SKILL_PATHS = /* @__PURE__ */ new Set([
|
|
3916
4065
|
".claude/skills/runeya/SKILL.md",
|
|
3917
4066
|
".cursor/rules/runeya.mdc",
|
|
@@ -3924,8 +4073,8 @@ var KeyInputSchema = z12.object({
|
|
|
3924
4073
|
});
|
|
3925
4074
|
var apiKeysRouter = router({
|
|
3926
4075
|
list: protectedProcedure.query(() => apiKeyStore.list()),
|
|
3927
|
-
create: protectedProcedure.input(KeyInputSchema).mutation(
|
|
3928
|
-
({ input }) => apiKeyStore.create(input.name, input.scopes, input.isPublic)
|
|
4076
|
+
create: protectedProcedure.input(KeyInputSchema.extend({ orgId: z12.string().min(1).max(128).nullable().optional() })).mutation(
|
|
4077
|
+
({ input }) => apiKeyStore.create(input.name, input.scopes, input.isPublic, input.orgId ?? null)
|
|
3929
4078
|
),
|
|
3930
4079
|
/**
|
|
3931
4080
|
* Renvoie le secret d'une seule clé, sur demande explicite. `list` ne le
|
|
@@ -3958,7 +4107,7 @@ var apiKeysRouter = router({
|
|
|
3958
4107
|
if (!ALLOWED_SKILL_PATHS.has(input.relativePath)) {
|
|
3959
4108
|
throw new TRPCError9({ code: "BAD_REQUEST", message: "Chemin non autoris\xE9" });
|
|
3960
4109
|
}
|
|
3961
|
-
const abs =
|
|
4110
|
+
const abs = join10(await resolveWorkspaceCwd({}), input.relativePath);
|
|
3962
4111
|
await mkdir8(dirname4(abs), { recursive: true });
|
|
3963
4112
|
await writeFile8(abs, input.content, { encoding: "utf-8", mode: 420 });
|
|
3964
4113
|
return { path: input.relativePath };
|
|
@@ -4289,12 +4438,12 @@ function buildApiDocs(apiBaseUrl, apiToken) {
|
|
|
4289
4438
|
import { readdir as readdir2, access, readFile as readFile9 } from "fs/promises";
|
|
4290
4439
|
import { watch } from "fs";
|
|
4291
4440
|
import { homedir as homedir3 } from "os";
|
|
4292
|
-
import { join as
|
|
4441
|
+
import { join as join12 } from "path";
|
|
4293
4442
|
|
|
4294
4443
|
// ../server/src/services/conversation-store.ts
|
|
4295
4444
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4296
4445
|
import { readFile as readFile8, writeFile as writeFile9, rename as rename8, mkdir as mkdir9, chmod as chmod7 } from "fs/promises";
|
|
4297
|
-
import { dirname as dirname5, join as
|
|
4446
|
+
import { dirname as dirname5, join as join11 } from "path";
|
|
4298
4447
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
4299
4448
|
var FILENAME4 = "conversations.private.json";
|
|
4300
4449
|
async function localProjectId() {
|
|
@@ -4338,19 +4487,19 @@ var ConversationStore = class {
|
|
|
4338
4487
|
async ensureDir() {
|
|
4339
4488
|
await mkdir9(dirname5(await this.getFilePath()), { recursive: true });
|
|
4340
4489
|
}
|
|
4341
|
-
encryptMessages(messages) {
|
|
4342
|
-
return encryptValue(JSON.stringify(messages));
|
|
4490
|
+
encryptMessages(messages, vault) {
|
|
4491
|
+
return encryptValue(JSON.stringify(messages), vault);
|
|
4343
4492
|
}
|
|
4344
|
-
decryptMessages(encrypted) {
|
|
4345
|
-
const json = decryptValue(encrypted);
|
|
4493
|
+
decryptMessages(encrypted, vault) {
|
|
4494
|
+
const json = decryptValue(encrypted, vault);
|
|
4346
4495
|
return JSON.parse(json);
|
|
4347
4496
|
}
|
|
4348
|
-
toDisk(conv) {
|
|
4497
|
+
toDisk(conv, vault) {
|
|
4349
4498
|
return {
|
|
4350
4499
|
id: conv.id,
|
|
4351
4500
|
projectId: conv.projectId,
|
|
4352
4501
|
title: conv.title,
|
|
4353
|
-
encryptedMessages: this.encryptMessages(conv.messages),
|
|
4502
|
+
encryptedMessages: this.encryptMessages(conv.messages, vault),
|
|
4354
4503
|
isCli: conv.isCli,
|
|
4355
4504
|
runner: conv.runner,
|
|
4356
4505
|
providerId: conv.providerId,
|
|
@@ -4365,13 +4514,13 @@ var ConversationStore = class {
|
|
|
4365
4514
|
updatedAt: conv.updatedAt
|
|
4366
4515
|
};
|
|
4367
4516
|
}
|
|
4368
|
-
fromDisk(disk) {
|
|
4517
|
+
fromDisk(disk, vault) {
|
|
4369
4518
|
const workflowType = (disk.workflowType === "scenario" ? "scenario" : void 0) ?? (disk.scenarioId ? "scenario" : void 0);
|
|
4370
4519
|
return {
|
|
4371
4520
|
id: disk.id,
|
|
4372
4521
|
projectId: disk.projectId,
|
|
4373
4522
|
title: disk.title,
|
|
4374
|
-
messages: this.decryptMessages(disk.encryptedMessages),
|
|
4523
|
+
messages: this.decryptMessages(disk.encryptedMessages, vault),
|
|
4375
4524
|
isCli: disk.isCli ?? disk.isClaudeCode ?? false,
|
|
4376
4525
|
runner: disk.runner,
|
|
4377
4526
|
providerId: disk.providerId,
|
|
@@ -4405,7 +4554,7 @@ var ConversationStore = class {
|
|
|
4405
4554
|
const legacyPaths = [this.getLegacyFilePath(), await keyedFilePath(FILENAME4)];
|
|
4406
4555
|
const sources = [
|
|
4407
4556
|
...legacyPaths.map((path) => ({ path, legacy: true })),
|
|
4408
|
-
...vaults.map((root) => ({ path:
|
|
4557
|
+
...vaults.map((root) => ({ path: join11(root, FILENAME4), legacy: false }))
|
|
4409
4558
|
];
|
|
4410
4559
|
for (const { path, legacy } of sources) {
|
|
4411
4560
|
let data;
|
|
@@ -4417,7 +4566,7 @@ var ConversationStore = class {
|
|
|
4417
4566
|
const inherited = legacy ? await localProjectId() : null;
|
|
4418
4567
|
for (const item of data) {
|
|
4419
4568
|
try {
|
|
4420
|
-
const conv = this.fromDisk(item);
|
|
4569
|
+
const conv = this.fromDisk(item, vaultIdOfRoot(dirname5(path)));
|
|
4421
4570
|
if (!conv.projectId && inherited) conv.projectId = inherited;
|
|
4422
4571
|
this.conversations.set(item.id, conv);
|
|
4423
4572
|
this.roots.set(item.id, dirname5(path));
|
|
@@ -4441,12 +4590,12 @@ var ConversationStore = class {
|
|
|
4441
4590
|
const root = await this.rootFor(conv);
|
|
4442
4591
|
this.roots.set(conv.id, root);
|
|
4443
4592
|
const bucket = byRoot.get(root) ?? [];
|
|
4444
|
-
bucket.push(this.toDisk(conv));
|
|
4593
|
+
bucket.push(this.toDisk(conv, vaultIdOfRoot(root)));
|
|
4445
4594
|
byRoot.set(root, bucket);
|
|
4446
4595
|
}
|
|
4447
4596
|
for (const [root, data] of byRoot) {
|
|
4448
4597
|
await mkdir9(root, { recursive: true });
|
|
4449
|
-
const filePath =
|
|
4598
|
+
const filePath = join11(root, FILENAME4);
|
|
4450
4599
|
const tmpPath = `${filePath}.tmp`;
|
|
4451
4600
|
await writeFile9(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
4452
4601
|
await rename8(tmpPath, filePath);
|
|
@@ -4477,6 +4626,16 @@ var ConversationStore = class {
|
|
|
4477
4626
|
const ref = refOfProjectAmong(conv.projectId, known);
|
|
4478
4627
|
return { id: ref.id, orgId: ref.orgId ?? null };
|
|
4479
4628
|
}
|
|
4629
|
+
/**
|
|
4630
|
+
* Réécrire chaque coffre, en déplaçant ce qui appartient désormais ailleurs.
|
|
4631
|
+
*
|
|
4632
|
+
* Appelé quand un projet a changé de coffre : ses conversations sont écrites
|
|
4633
|
+
* dans le nouveau, et le fichier d'où elles viennent est réécrit sans elles.
|
|
4634
|
+
*/
|
|
4635
|
+
async relocateAll() {
|
|
4636
|
+
await this.load();
|
|
4637
|
+
await this.save();
|
|
4638
|
+
}
|
|
4480
4639
|
async save() {
|
|
4481
4640
|
const write = this.saveQueue.then(() => this.saveInternal());
|
|
4482
4641
|
this.saveQueue = write.catch(() => {
|
|
@@ -4487,6 +4646,39 @@ var ConversationStore = class {
|
|
|
4487
4646
|
await this.load();
|
|
4488
4647
|
return Array.from(this.conversations.values()).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
4489
4648
|
}
|
|
4649
|
+
/**
|
|
4650
|
+
* Les conversations d'un projet.
|
|
4651
|
+
*
|
|
4652
|
+
* Celle qui ne nomme aucun projet — écrite avant le champ, ou qu'une
|
|
4653
|
+
* migration n'a pas su attribuer — n'est rendue que dans les projets de son
|
|
4654
|
+
* propre coffre : elle reste atteignable là où elle a été écrite, sans se
|
|
4655
|
+
* déverser dans toutes les organisations de la machine.
|
|
4656
|
+
*/
|
|
4657
|
+
async listForProject(ref) {
|
|
4658
|
+
const belongs = await this.matcherForProject(ref);
|
|
4659
|
+
return (await this.list()).filter(belongs);
|
|
4660
|
+
}
|
|
4661
|
+
/**
|
|
4662
|
+
* Le test d'appartenance à un projet, préparé une fois.
|
|
4663
|
+
*
|
|
4664
|
+
* Le flux temps réel en a besoin autant que la liste : une conversation qui
|
|
4665
|
+
* arrive par événement doit passer le même filtre, sans quoi elle se
|
|
4666
|
+
* réinvite dans un projet qui ne l'a jamais demandée.
|
|
4667
|
+
*/
|
|
4668
|
+
async matcherForProject(ref) {
|
|
4669
|
+
await this.load();
|
|
4670
|
+
let projectRoot2 = null;
|
|
4671
|
+
try {
|
|
4672
|
+
projectRoot2 = ref.orgId === void 0 ? rootOfProjectAmong(ref.id, null) : rootOfProjectRef({ id: ref.id, orgId: ref.orgId ?? null });
|
|
4673
|
+
} catch {
|
|
4674
|
+
projectRoot2 = null;
|
|
4675
|
+
}
|
|
4676
|
+
return (conv) => {
|
|
4677
|
+
if (conv.projectId) return conv.projectId === ref.id;
|
|
4678
|
+
if (!projectRoot2) return true;
|
|
4679
|
+
return this.roots.get(conv.id) === projectRoot2;
|
|
4680
|
+
};
|
|
4681
|
+
}
|
|
4490
4682
|
async get(id) {
|
|
4491
4683
|
await this.load();
|
|
4492
4684
|
return this.conversations.get(id) ?? null;
|
|
@@ -4575,7 +4767,7 @@ var ConversationStore = class {
|
|
|
4575
4767
|
var conversationStore = new ConversationStore();
|
|
4576
4768
|
|
|
4577
4769
|
// ../server/src/services/claude-transcript-sync.ts
|
|
4578
|
-
var PROJECTS_DIR =
|
|
4770
|
+
var PROJECTS_DIR = join12(homedir3(), ".claude", "projects");
|
|
4579
4771
|
var DEFAULT_TITLES = /* @__PURE__ */ new Set(["", "New conversation", "Nouvelle conversation"]);
|
|
4580
4772
|
async function findClaudeSessionFile(sessionId) {
|
|
4581
4773
|
let entries;
|
|
@@ -4586,7 +4778,7 @@ async function findClaudeSessionFile(sessionId) {
|
|
|
4586
4778
|
}
|
|
4587
4779
|
const filename = `${sessionId}.jsonl`;
|
|
4588
4780
|
for (const dir of entries) {
|
|
4589
|
-
const candidate =
|
|
4781
|
+
const candidate = join12(PROJECTS_DIR, dir, filename);
|
|
4590
4782
|
try {
|
|
4591
4783
|
await access(candidate);
|
|
4592
4784
|
return candidate;
|
|
@@ -4749,7 +4941,7 @@ async function startClaudeTranscriptWatcher() {
|
|
|
4749
4941
|
}
|
|
4750
4942
|
for (const dir of topLevel) {
|
|
4751
4943
|
try {
|
|
4752
|
-
const w = watch(
|
|
4944
|
+
const w = watch(join12(PROJECTS_DIR, dir), (_event, filename) => {
|
|
4753
4945
|
handleFsEvent(filename ? filename.toString() : null);
|
|
4754
4946
|
});
|
|
4755
4947
|
w.on("error", () => {
|
|
@@ -4775,8 +4967,8 @@ function stopClaudeTranscriptWatcher() {
|
|
|
4775
4967
|
// ../server/src/services/codex-sessions.ts
|
|
4776
4968
|
import { readdir as readdir3 } from "fs/promises";
|
|
4777
4969
|
import { homedir as homedir4 } from "os";
|
|
4778
|
-
import { join as
|
|
4779
|
-
var SESSIONS_DIR =
|
|
4970
|
+
import { join as join13 } from "path";
|
|
4971
|
+
var SESSIONS_DIR = join13(homedir4(), ".codex", "sessions");
|
|
4780
4972
|
async function findCodexSessionFile(sessionId) {
|
|
4781
4973
|
const suffix = `-${sessionId}.jsonl`;
|
|
4782
4974
|
async function walk(dir) {
|
|
@@ -4787,7 +4979,7 @@ async function findCodexSessionFile(sessionId) {
|
|
|
4787
4979
|
return null;
|
|
4788
4980
|
}
|
|
4789
4981
|
for (const entry of entries) {
|
|
4790
|
-
const full =
|
|
4982
|
+
const full = join13(dir, entry.name);
|
|
4791
4983
|
if (entry.isDirectory()) {
|
|
4792
4984
|
const found = await walk(full);
|
|
4793
4985
|
if (found) return found;
|
|
@@ -5607,34 +5799,34 @@ function normalizeNode(raw) {
|
|
|
5607
5799
|
};
|
|
5608
5800
|
}
|
|
5609
5801
|
var ScenarioStore = class {
|
|
5610
|
-
async list() {
|
|
5802
|
+
async list(vault) {
|
|
5611
5803
|
const [settings, priv] = await Promise.all([
|
|
5612
|
-
settingsManager.getSettings(),
|
|
5613
|
-
settingsManager.getPrivateSettings()
|
|
5804
|
+
settingsManager.getSettings(vault),
|
|
5805
|
+
settingsManager.getPrivateSettings(vault)
|
|
5614
5806
|
]);
|
|
5615
5807
|
return [...settings.scenarios ?? [], ...priv.scenarios ?? []];
|
|
5616
5808
|
}
|
|
5617
|
-
async get(id) {
|
|
5618
|
-
const list = await this.list();
|
|
5809
|
+
async get(id, vault) {
|
|
5810
|
+
const list = await this.list(vault);
|
|
5619
5811
|
return list.find((e) => e.id === id) ?? null;
|
|
5620
5812
|
}
|
|
5621
|
-
async create(scenario) {
|
|
5813
|
+
async create(scenario, vault) {
|
|
5622
5814
|
const normalized = this.normalizeScenario(scenario);
|
|
5623
5815
|
const parsed = ScenarioSchema.parse(normalized);
|
|
5624
5816
|
this.validateHierarchy(parsed);
|
|
5625
5817
|
if (parsed.isPublished) {
|
|
5626
|
-
const settings = await settingsManager.getSettings();
|
|
5627
|
-
await settingsManager.saveSettings({ ...settings, scenarios: [...settings.scenarios ?? [], parsed] });
|
|
5818
|
+
const settings = await settingsManager.getSettings(vault);
|
|
5819
|
+
await settingsManager.saveSettings({ ...settings, scenarios: [...settings.scenarios ?? [], parsed] }, vault);
|
|
5628
5820
|
} else {
|
|
5629
|
-
const priv = await settingsManager.getPrivateSettings();
|
|
5630
|
-
await settingsManager.savePrivateSettings({ ...priv, scenarios: [...priv.scenarios ?? [], parsed] });
|
|
5821
|
+
const priv = await settingsManager.getPrivateSettings(vault);
|
|
5822
|
+
await settingsManager.savePrivateSettings({ ...priv, scenarios: [...priv.scenarios ?? [], parsed] }, vault);
|
|
5631
5823
|
}
|
|
5632
5824
|
return parsed;
|
|
5633
5825
|
}
|
|
5634
|
-
async update(id, patch) {
|
|
5826
|
+
async update(id, patch, vault) {
|
|
5635
5827
|
const [settings, priv] = await Promise.all([
|
|
5636
|
-
settingsManager.getSettings(),
|
|
5637
|
-
settingsManager.getPrivateSettings()
|
|
5828
|
+
settingsManager.getSettings(vault),
|
|
5829
|
+
settingsManager.getPrivateSettings(vault)
|
|
5638
5830
|
]);
|
|
5639
5831
|
const publicScenarios = [...settings.scenarios ?? []];
|
|
5640
5832
|
const privateScenarios = [...priv.scenarios ?? []];
|
|
@@ -5650,29 +5842,29 @@ var ScenarioStore = class {
|
|
|
5650
5842
|
const isNowPublic = parsed.isPublished;
|
|
5651
5843
|
if (wasPublic && isNowPublic) {
|
|
5652
5844
|
publicScenarios[publicIdx] = parsed;
|
|
5653
|
-
await settingsManager.saveSettings({ ...settings, scenarios: publicScenarios });
|
|
5845
|
+
await settingsManager.saveSettings({ ...settings, scenarios: publicScenarios }, vault);
|
|
5654
5846
|
} else if (!wasPublic && !isNowPublic) {
|
|
5655
5847
|
privateScenarios[privateIdx] = parsed;
|
|
5656
|
-
await settingsManager.savePrivateSettings({ ...priv, scenarios: privateScenarios });
|
|
5848
|
+
await settingsManager.savePrivateSettings({ ...priv, scenarios: privateScenarios }, vault);
|
|
5657
5849
|
} else if (wasPublic && !isNowPublic) {
|
|
5658
5850
|
publicScenarios.splice(publicIdx, 1);
|
|
5659
5851
|
await Promise.all([
|
|
5660
|
-
settingsManager.saveSettings({ ...settings, scenarios: publicScenarios }),
|
|
5661
|
-
settingsManager.savePrivateSettings({ ...priv, scenarios: [...privateScenarios, parsed] })
|
|
5852
|
+
settingsManager.saveSettings({ ...settings, scenarios: publicScenarios }, vault),
|
|
5853
|
+
settingsManager.savePrivateSettings({ ...priv, scenarios: [...privateScenarios, parsed] }, vault)
|
|
5662
5854
|
]);
|
|
5663
5855
|
} else {
|
|
5664
5856
|
privateScenarios.splice(privateIdx, 1);
|
|
5665
5857
|
await Promise.all([
|
|
5666
|
-
settingsManager.savePrivateSettings({ ...priv, scenarios: privateScenarios }),
|
|
5667
|
-
settingsManager.saveSettings({ ...settings, scenarios: [...publicScenarios, parsed] })
|
|
5858
|
+
settingsManager.savePrivateSettings({ ...priv, scenarios: privateScenarios }, vault),
|
|
5859
|
+
settingsManager.saveSettings({ ...settings, scenarios: [...publicScenarios, parsed] }, vault)
|
|
5668
5860
|
]);
|
|
5669
5861
|
}
|
|
5670
5862
|
return parsed;
|
|
5671
5863
|
}
|
|
5672
|
-
async delete(id) {
|
|
5864
|
+
async delete(id, vault) {
|
|
5673
5865
|
const [settings, priv] = await Promise.all([
|
|
5674
|
-
settingsManager.getSettings(),
|
|
5675
|
-
settingsManager.getPrivateSettings()
|
|
5866
|
+
settingsManager.getSettings(vault),
|
|
5867
|
+
settingsManager.getPrivateSettings(vault)
|
|
5676
5868
|
]);
|
|
5677
5869
|
const publicFiltered = (settings.scenarios ?? []).filter((e) => e.id !== id);
|
|
5678
5870
|
const privateFiltered = (priv.scenarios ?? []).filter((e) => e.id !== id);
|
|
@@ -5680,8 +5872,8 @@ var ScenarioStore = class {
|
|
|
5680
5872
|
const removedFromPrivate = privateFiltered.length < (priv.scenarios ?? []).length;
|
|
5681
5873
|
if (!removedFromPublic && !removedFromPrivate) return false;
|
|
5682
5874
|
await Promise.all([
|
|
5683
|
-
removedFromPublic ? settingsManager.saveSettings({ ...settings, scenarios: publicFiltered }) : Promise.resolve(),
|
|
5684
|
-
removedFromPrivate ? settingsManager.savePrivateSettings({ ...priv, scenarios: privateFiltered }) : Promise.resolve()
|
|
5875
|
+
removedFromPublic ? settingsManager.saveSettings({ ...settings, scenarios: publicFiltered }, vault) : Promise.resolve(),
|
|
5876
|
+
removedFromPrivate ? settingsManager.savePrivateSettings({ ...priv, scenarios: privateFiltered }, vault) : Promise.resolve()
|
|
5685
5877
|
]);
|
|
5686
5878
|
return true;
|
|
5687
5879
|
}
|
|
@@ -5748,7 +5940,7 @@ var scenarioStore = new ScenarioStore();
|
|
|
5748
5940
|
|
|
5749
5941
|
// ../server/src/services/scenario-run-store.ts
|
|
5750
5942
|
import { mkdir as mkdir11, rm as rm2, readFile as readFile11 } from "fs/promises";
|
|
5751
|
-
import { join as
|
|
5943
|
+
import { join as join14 } from "path";
|
|
5752
5944
|
|
|
5753
5945
|
// ../server/src/services/run-store-utils.ts
|
|
5754
5946
|
import { chmod as chmod8, mkdir as mkdir10, rename as rename9, writeFile as writeFile10, readFile as readFile10 } from "fs/promises";
|
|
@@ -5825,8 +6017,8 @@ var ScenarioRunStore = class _ScenarioRunStore {
|
|
|
5825
6017
|
"- Use read-only references from linked upstream files as context.",
|
|
5826
6018
|
""
|
|
5827
6019
|
].join("\n");
|
|
5828
|
-
await atomicWriteSecure(
|
|
5829
|
-
await atomicWriteSecure(
|
|
6020
|
+
await atomicWriteSecure(join14(dir, "README.md"), overview);
|
|
6021
|
+
await atomicWriteSecure(join14(dir, "USER_REQUEST.md"), `# User request
|
|
5830
6022
|
|
|
5831
6023
|
${userInput.trim() || "(empty)"}
|
|
5832
6024
|
`);
|
|
@@ -5852,7 +6044,7 @@ ${userInput.trim() || "(empty)"}
|
|
|
5852
6044
|
"- Waiting for assignment.",
|
|
5853
6045
|
""
|
|
5854
6046
|
].join("\n");
|
|
5855
|
-
await atomicWriteSecure(
|
|
6047
|
+
await atomicWriteSecure(join14(dir, fileName), content);
|
|
5856
6048
|
}
|
|
5857
6049
|
}
|
|
5858
6050
|
async deleteRunDir(conversationId) {
|
|
@@ -5920,18 +6112,18 @@ ${userInput.trim() || "(empty)"}
|
|
|
5920
6112
|
...Object.keys(runState.nodeAgentIds).length > 0 ? { nodeAgentIds: { ...runState.nodeAgentIds } } : {},
|
|
5921
6113
|
...Object.keys(runState.waitingInputs).length > 0 ? { waitingInputs: { ...runState.waitingInputs } } : {}
|
|
5922
6114
|
};
|
|
5923
|
-
await atomicWriteJsonSecure(
|
|
6115
|
+
await atomicWriteJsonSecure(join14(dir, "run-state.json"), data);
|
|
5924
6116
|
} catch {
|
|
5925
6117
|
}
|
|
5926
6118
|
}
|
|
5927
6119
|
async loadPersistedRunState(conversationId) {
|
|
5928
6120
|
const dir = this.getRunDir(conversationId);
|
|
5929
|
-
return readJsonFileSafe(
|
|
6121
|
+
return readJsonFileSafe(join14(dir, "run-state.json"));
|
|
5930
6122
|
}
|
|
5931
6123
|
async readUserRequest(conversationId) {
|
|
5932
6124
|
try {
|
|
5933
6125
|
const dir = this.getRunDir(conversationId);
|
|
5934
|
-
const content = await readFile11(
|
|
6126
|
+
const content = await readFile11(join14(dir, "USER_REQUEST.md"), "utf-8");
|
|
5935
6127
|
return content.replace(/^#\s*User request\s*\n+/, "").trim() || null;
|
|
5936
6128
|
} catch {
|
|
5937
6129
|
return null;
|
|
@@ -5940,7 +6132,7 @@ ${userInput.trim() || "(empty)"}
|
|
|
5940
6132
|
async readReadme(conversationId) {
|
|
5941
6133
|
try {
|
|
5942
6134
|
const dir = this.getRunDir(conversationId);
|
|
5943
|
-
return await readFile11(
|
|
6135
|
+
return await readFile11(join14(dir, "README.md"), "utf-8");
|
|
5944
6136
|
} catch {
|
|
5945
6137
|
return null;
|
|
5946
6138
|
}
|
|
@@ -5949,14 +6141,14 @@ ${userInput.trim() || "(empty)"}
|
|
|
5949
6141
|
const dir = this.getRunDir(conversationId);
|
|
5950
6142
|
await mkdir11(dir, { recursive: true });
|
|
5951
6143
|
await atomicWriteJsonSecure(
|
|
5952
|
-
|
|
6144
|
+
join14(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE),
|
|
5953
6145
|
scenario,
|
|
5954
6146
|
true
|
|
5955
6147
|
);
|
|
5956
6148
|
}
|
|
5957
6149
|
async loadScenarioSnapshot(conversationId) {
|
|
5958
6150
|
const dir = this.getRunDir(conversationId);
|
|
5959
|
-
return readJsonFileSafe(
|
|
6151
|
+
return readJsonFileSafe(join14(dir, _ScenarioRunStore.SCENARIO_SNAPSHOT_FILE));
|
|
5960
6152
|
}
|
|
5961
6153
|
};
|
|
5962
6154
|
var scenarioRunStore = new ScenarioRunStore();
|
|
@@ -5977,12 +6169,12 @@ var DEFAULT_MODELS = {
|
|
|
5977
6169
|
mistral: "mistral-large-latest",
|
|
5978
6170
|
ollama: "llama3"
|
|
5979
6171
|
};
|
|
5980
|
-
async function getActiveProvider(providerId) {
|
|
5981
|
-
const priv = await settingsManager.getPrivateSettings();
|
|
6172
|
+
async function getActiveProvider(providerId, vault) {
|
|
6173
|
+
const priv = await settingsManager.getPrivateSettings(vault);
|
|
5982
6174
|
const providers = priv.aiProviders ?? [];
|
|
5983
6175
|
const selected = providerId ? providers.find((p) => p.id === providerId) : providers.find((p) => p.active);
|
|
5984
6176
|
if (!selected) return null;
|
|
5985
|
-
const plaintextKey = selected.encryptedApiKey ? decryptValue(selected.encryptedApiKey) : "";
|
|
6177
|
+
const plaintextKey = selected.encryptedApiKey ? decryptValue(selected.encryptedApiKey, vault) : "";
|
|
5986
6178
|
return { provider: selected, plaintextKey };
|
|
5987
6179
|
}
|
|
5988
6180
|
async function fetchProviderModels(provider, plaintextKey) {
|
|
@@ -6004,7 +6196,7 @@ async function fetchProviderModels(provider, plaintextKey) {
|
|
|
6004
6196
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
6005
6197
|
import { mkdir as mkdir12, writeFile as writeFile11, readFile as readFile12, unlink as unlink2, readdir as readdir4 } from "fs/promises";
|
|
6006
6198
|
import { existsSync } from "fs";
|
|
6007
|
-
import { join as
|
|
6199
|
+
import { join as join15, extname, resolve as resolve4, sep as sep2 } from "path";
|
|
6008
6200
|
var ALLOWED_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
6009
6201
|
"image/png",
|
|
6010
6202
|
"image/jpeg",
|
|
@@ -6041,7 +6233,7 @@ var ImageStorageService = class {
|
|
|
6041
6233
|
const imageId = randomUUID4();
|
|
6042
6234
|
const safeExt = ext.replace(/[^a-z.]/g, "");
|
|
6043
6235
|
const fileName = `${imageId}${safeExt}`;
|
|
6044
|
-
const filePath =
|
|
6236
|
+
const filePath = join15(this.dir, fileName);
|
|
6045
6237
|
const resolvedPath = resolve4(filePath);
|
|
6046
6238
|
const resolvedDir = resolve4(this.dir);
|
|
6047
6239
|
if (resolvedPath !== resolvedDir && !resolvedPath.startsWith(resolvedDir + sep2)) {
|
|
@@ -6067,7 +6259,7 @@ var ImageStorageService = class {
|
|
|
6067
6259
|
const files = await readdir4(this.dir);
|
|
6068
6260
|
const metaFile = files.find((f) => f.startsWith(imageId) && f.endsWith(".meta.json"));
|
|
6069
6261
|
if (!metaFile) return null;
|
|
6070
|
-
const raw = JSON.parse(await readFile12(
|
|
6262
|
+
const raw = JSON.parse(await readFile12(join15(this.dir, metaFile), "utf-8"));
|
|
6071
6263
|
if (expectedContextId !== void 0 && raw.contextId !== expectedContextId) return null;
|
|
6072
6264
|
if (!existsSync(raw.filePath)) return null;
|
|
6073
6265
|
return { filePath: raw.filePath, mimeType: raw.mimeType, contextId: raw.contextId };
|
|
@@ -6093,7 +6285,7 @@ var ImageStorageService = class {
|
|
|
6093
6285
|
await Promise.all(
|
|
6094
6286
|
files.filter((f) => f.endsWith(".meta.json")).map(async (file) => {
|
|
6095
6287
|
try {
|
|
6096
|
-
const raw = JSON.parse(await readFile12(
|
|
6288
|
+
const raw = JSON.parse(await readFile12(join15(this.dir, file), "utf-8"));
|
|
6097
6289
|
if (raw.contextId === contextId) {
|
|
6098
6290
|
await unlink2(raw.filePath).catch(() => {
|
|
6099
6291
|
});
|
|
@@ -6123,7 +6315,7 @@ var ImageStorageService = class {
|
|
|
6123
6315
|
for (const file of files) {
|
|
6124
6316
|
if (!file.endsWith(".meta.json")) continue;
|
|
6125
6317
|
try {
|
|
6126
|
-
const raw = JSON.parse(await readFile12(
|
|
6318
|
+
const raw = JSON.parse(await readFile12(join15(this.dir, file), "utf-8"));
|
|
6127
6319
|
if (now - raw.createdAt > ttlMs) expired.push(raw);
|
|
6128
6320
|
} catch {
|
|
6129
6321
|
}
|
|
@@ -7002,7 +7194,7 @@ function tailTranscript(opts) {
|
|
|
7002
7194
|
|
|
7003
7195
|
// ../server/src/services/ai-runners/claude-pty-runner.ts
|
|
7004
7196
|
async function getAgentManager() {
|
|
7005
|
-
const { agentManager: agentManager2 } = await import("./agent-manager-
|
|
7197
|
+
const { agentManager: agentManager2 } = await import("./agent-manager-RGDHYM64.js");
|
|
7006
7198
|
return agentManager2;
|
|
7007
7199
|
}
|
|
7008
7200
|
function buildPtyArgs(params, sessionId, opts) {
|
|
@@ -7655,7 +7847,7 @@ async function shutdownCodexAppServer() {
|
|
|
7655
7847
|
}
|
|
7656
7848
|
}
|
|
7657
7849
|
async function getAgentManager2() {
|
|
7658
|
-
const { agentManager: agentManager2 } = await import("./agent-manager-
|
|
7850
|
+
const { agentManager: agentManager2 } = await import("./agent-manager-RGDHYM64.js");
|
|
7659
7851
|
return agentManager2;
|
|
7660
7852
|
}
|
|
7661
7853
|
function parseRuneyaCurlCommand(command) {
|
|
@@ -8516,12 +8708,24 @@ async function conversationCwd(conv) {
|
|
|
8516
8708
|
}
|
|
8517
8709
|
var chatRouter = router({
|
|
8518
8710
|
conversations: router({
|
|
8519
|
-
|
|
8520
|
-
|
|
8711
|
+
/**
|
|
8712
|
+
* Le flux des conversations du projet ouvert.
|
|
8713
|
+
*
|
|
8714
|
+
* Filtré comme `list` : l'instantané initial remplace la liste du client,
|
|
8715
|
+
* et sans le même filtre il y reversait toutes les conversations de la
|
|
8716
|
+
* machine, une seconde après un chargement pourtant filtré.
|
|
8717
|
+
*/
|
|
8718
|
+
onChanged: protectedProcedure.input(z15.object({
|
|
8719
|
+
projectId: Id128Schema.optional(),
|
|
8720
|
+
orgId: Id128Schema.nullable().optional()
|
|
8721
|
+
}).optional()).subscription(async function* ({ input }) {
|
|
8722
|
+
const belongs = input?.projectId ? await conversationStore.matcherForProject({ id: input.projectId, orgId: input.orgId }) : () => true;
|
|
8723
|
+
const initial = (await conversationStore.list()).filter(belongs);
|
|
8521
8724
|
yield { type: "initial", conversations: initial };
|
|
8522
8725
|
const queue = [];
|
|
8523
8726
|
let resolve7 = null;
|
|
8524
8727
|
const handler = (event) => {
|
|
8728
|
+
if (event.type !== "deleted" && !belongs(event.conversation)) return;
|
|
8525
8729
|
if (queue.length >= 500) queue.shift();
|
|
8526
8730
|
queue.push(event);
|
|
8527
8731
|
if (resolve7) {
|
|
@@ -8550,13 +8754,17 @@ var chatRouter = router({
|
|
|
8550
8754
|
* A conversation belongs to the project it was opened from, so the caller
|
|
8551
8755
|
* says which one it is looking at. Ones that name no project — written
|
|
8552
8756
|
* before the field existed, or that a migration could not attribute — are
|
|
8553
|
-
* returned
|
|
8757
|
+
* returned only for the projects of the vault they were read from: still
|
|
8758
|
+
* reachable where they were written, without leaking into every other
|
|
8759
|
+
* organization's projects.
|
|
8554
8760
|
*/
|
|
8555
|
-
list: protectedProcedure.input(z15.object({
|
|
8556
|
-
|
|
8761
|
+
list: protectedProcedure.input(z15.object({
|
|
8762
|
+
projectId: Id128Schema.optional(),
|
|
8763
|
+
orgId: Id128Schema.nullable().optional()
|
|
8764
|
+
}).optional()).query(async ({ input }) => {
|
|
8557
8765
|
const projectId = input?.projectId;
|
|
8558
|
-
if (!projectId) return
|
|
8559
|
-
return
|
|
8766
|
+
if (!projectId) return conversationStore.list();
|
|
8767
|
+
return conversationStore.listForProject({ id: projectId, orgId: input?.orgId });
|
|
8560
8768
|
}),
|
|
8561
8769
|
get: protectedProcedure.input(z15.object({ id: Id128Schema })).query(async ({ input }) => {
|
|
8562
8770
|
const conv = await conversationStore.get(input.id);
|
|
@@ -8579,7 +8787,10 @@ var chatRouter = router({
|
|
|
8579
8787
|
})).mutation(async ({ input }) => {
|
|
8580
8788
|
let scenario = null;
|
|
8581
8789
|
if (input.scenarioId) {
|
|
8582
|
-
scenario = await scenarioStore.get(
|
|
8790
|
+
scenario = await scenarioStore.get(
|
|
8791
|
+
input.scenarioId,
|
|
8792
|
+
input.orgId ?? await vaultOfProject(input.projectId)
|
|
8793
|
+
);
|
|
8583
8794
|
if (!scenario) throw new TRPCError11({ code: "NOT_FOUND", message: "Scenario not found" });
|
|
8584
8795
|
}
|
|
8585
8796
|
const conv = await conversationStore.create(
|
|
@@ -8644,8 +8855,17 @@ var chatRouter = router({
|
|
|
8644
8855
|
return conv;
|
|
8645
8856
|
})
|
|
8646
8857
|
}),
|
|
8647
|
-
listModels: protectedProcedure.input(z15.object({
|
|
8648
|
-
|
|
8858
|
+
listModels: protectedProcedure.input(z15.object({
|
|
8859
|
+
providerId: Id128Schema.optional(),
|
|
8860
|
+
// Le projet dit de quelle organisation lire les fournisseurs ; absent,
|
|
8861
|
+
// ce sont ceux du poste.
|
|
8862
|
+
projectId: Id128Schema.optional(),
|
|
8863
|
+
orgId: z15.string().max(128).nullable().optional()
|
|
8864
|
+
}).optional()).query(async ({ input }) => {
|
|
8865
|
+
const active = await getActiveProvider(
|
|
8866
|
+
input?.providerId,
|
|
8867
|
+
input?.orgId ?? await vaultOfProject(input?.projectId)
|
|
8868
|
+
);
|
|
8649
8869
|
if (!active) return [];
|
|
8650
8870
|
try {
|
|
8651
8871
|
return await fetchProviderModels(active.provider, active.plaintextKey);
|
|
@@ -8674,9 +8894,14 @@ var chatRouter = router({
|
|
|
8674
8894
|
enhanceScenarioPrompt: protectedProcedure.input(z15.object({
|
|
8675
8895
|
text: z15.string().max(1e4),
|
|
8676
8896
|
providerId: Id128Schema.optional(),
|
|
8677
|
-
model: z15.string().max(200).optional()
|
|
8897
|
+
model: z15.string().max(200).optional(),
|
|
8898
|
+
projectId: Id128Schema.optional(),
|
|
8899
|
+
orgId: z15.string().max(128).nullable().optional()
|
|
8678
8900
|
})).mutation(async ({ input }) => {
|
|
8679
|
-
const active = await getActiveProvider(
|
|
8901
|
+
const active = await getActiveProvider(
|
|
8902
|
+
input.providerId,
|
|
8903
|
+
input.orgId ?? await vaultOfProject(input.projectId)
|
|
8904
|
+
);
|
|
8680
8905
|
if (!active) throw new TRPCError11({ code: "BAD_REQUEST", message: "No AI provider configured" });
|
|
8681
8906
|
const systemPrompt = `You are an expert at writing clear, concise user request briefs for AI-powered workflows.
|
|
8682
8907
|
When given a rough user request, rewrite it to be:
|
|
@@ -8970,7 +9195,8 @@ ${input.prompt}` : input.prompt;
|
|
|
8970
9195
|
throw new TRPCError11({ code: "BAD_REQUEST", message: "Messages are required for new messages" });
|
|
8971
9196
|
}
|
|
8972
9197
|
const conv = await conversationStore.get(input.conversationId);
|
|
8973
|
-
const
|
|
9198
|
+
const convRef = conv ? await conversationStore.projectRefOf(conv) : null;
|
|
9199
|
+
const active = await getActiveProvider(conv?.providerId, convRef?.orgId ?? void 0);
|
|
8974
9200
|
if (!active) {
|
|
8975
9201
|
throw new TRPCError11({
|
|
8976
9202
|
code: "PRECONDITION_FAILED",
|
|
@@ -9603,7 +9829,10 @@ var gitRouter = router({
|
|
|
9603
9829
|
if (!diff.trim()) {
|
|
9604
9830
|
throw new TRPCError12({ code: "BAD_REQUEST", message: "No staged changes to generate a commit message from" });
|
|
9605
9831
|
}
|
|
9606
|
-
const active = await getActiveProvider(
|
|
9832
|
+
const active = await getActiveProvider(
|
|
9833
|
+
input.providerId,
|
|
9834
|
+
input.orgId ?? await vaultOfProject(input.projectId)
|
|
9835
|
+
);
|
|
9607
9836
|
if (!active) {
|
|
9608
9837
|
throw new TRPCError12({ code: "BAD_REQUEST", message: "No AI provider configured" });
|
|
9609
9838
|
}
|
|
@@ -9820,7 +10049,7 @@ async function* streamProviderText(input) {
|
|
|
9820
10049
|
|
|
9821
10050
|
// ../server/src/trpc/ai-generation-procedure-utils.ts
|
|
9822
10051
|
async function* streamWithActiveProvider(opts) {
|
|
9823
|
-
const active = await getActiveProvider(opts.providerId);
|
|
10052
|
+
const active = await getActiveProvider(opts.providerId, opts.vault);
|
|
9824
10053
|
if (!active) throw new TRPCError14({ code: "BAD_REQUEST", message: "Aucun fournisseur IA configur\xE9" });
|
|
9825
10054
|
const { provider, plaintextKey } = active;
|
|
9826
10055
|
const model = opts.model || provider.model || DEFAULT_MODELS[provider.provider] || "";
|
|
@@ -9848,7 +10077,7 @@ async function* streamWithActiveProvider(opts) {
|
|
|
9848
10077
|
// ../server/src/services/scenario-runner.ts
|
|
9849
10078
|
import { mkdir as mkdir14 } from "fs/promises";
|
|
9850
10079
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
9851
|
-
import { join as
|
|
10080
|
+
import { join as join16 } from "path";
|
|
9852
10081
|
|
|
9853
10082
|
// ../server/src/services/scenario-presets.ts
|
|
9854
10083
|
var BUILT_IN_PRESETS = [
|
|
@@ -10626,7 +10855,7 @@ async function appendNodeOutput(conversationId, node, output) {
|
|
|
10626
10855
|
if (!dir) return;
|
|
10627
10856
|
const slug = node.label.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 60) || "member";
|
|
10628
10857
|
const safeId = node.id.replace(/[^a-z0-9_-]/gi, "-").slice(0, 80);
|
|
10629
|
-
const filePath =
|
|
10858
|
+
const filePath = join16(dir, `${slug}_${safeId}.md`);
|
|
10630
10859
|
const content = `## Output
|
|
10631
10860
|
|
|
10632
10861
|
${output}
|
|
@@ -10641,7 +10870,7 @@ async function appendToReadme(conversationId, bus, text) {
|
|
|
10641
10870
|
try {
|
|
10642
10871
|
await mkdir14(dir, { recursive: true });
|
|
10643
10872
|
const { appendFile: appendFile2 } = await import("fs/promises");
|
|
10644
|
-
await appendFile2(
|
|
10873
|
+
await appendFile2(join16(dir, "README.md"), line, "utf-8");
|
|
10645
10874
|
} catch {
|
|
10646
10875
|
}
|
|
10647
10876
|
publishToBus(bus, { type: "readme_append", text: line });
|
|
@@ -10661,7 +10890,7 @@ async function writeFinalMd(conversationId, bus, userRequest, content) {
|
|
|
10661
10890
|
|
|
10662
10891
|
${content}
|
|
10663
10892
|
`;
|
|
10664
|
-
const filePath =
|
|
10893
|
+
const filePath = join16(dir, "FINAL.md");
|
|
10665
10894
|
await atomicWriteSecure(filePath, fullContent);
|
|
10666
10895
|
await appendToReadme(
|
|
10667
10896
|
conversationId,
|
|
@@ -10719,7 +10948,7 @@ async function runScenario(conversationId, scenarioId, userRequest, runOverrides
|
|
|
10719
10948
|
};
|
|
10720
10949
|
var isSubtreeComplete = isSubtreeComplete2;
|
|
10721
10950
|
const scenario = await (async () => {
|
|
10722
|
-
const base = await scenarioRunStore.loadScenarioSnapshot(conversationId) ?? await scenarioStore.get(scenarioId);
|
|
10951
|
+
const base = await scenarioRunStore.loadScenarioSnapshot(conversationId) ?? await scenarioStore.get(scenarioId, runOverrides?.orgId ?? void 0);
|
|
10723
10952
|
if (!base) throw new Error(`Scenario ${scenarioId} not found`);
|
|
10724
10953
|
return applyRunOverridesToScenario(base, runOverrides);
|
|
10725
10954
|
})();
|
|
@@ -11328,9 +11557,19 @@ Modify the scenario. Keep existing ids when possible.`;
|
|
|
11328
11557
|
}
|
|
11329
11558
|
return `Generate an scenario org-chart based on: ${userPrompt}`;
|
|
11330
11559
|
}
|
|
11560
|
+
var ScopeShape2 = {
|
|
11561
|
+
orgId: z17.string().max(128).nullable().optional(),
|
|
11562
|
+
projectId: Id128Schema.optional()
|
|
11563
|
+
};
|
|
11564
|
+
async function scopeOf2(input) {
|
|
11565
|
+
return input?.orgId ?? await vaultOfProject(input?.projectId);
|
|
11566
|
+
}
|
|
11331
11567
|
var scenarioRouter = router({
|
|
11332
|
-
list: protectedProcedure.query(() => scenarioStore.list()),
|
|
11333
|
-
get: protectedProcedure.input(z17.object({ id: Id128Schema })).query(({ input }) => getOrNotFound(
|
|
11568
|
+
list: protectedProcedure.input(z17.object(ScopeShape2).optional()).query(async ({ input }) => scenarioStore.list(await scopeOf2(input))),
|
|
11569
|
+
get: protectedProcedure.input(z17.object({ id: Id128Schema, ...ScopeShape2 })).query(async ({ input }) => getOrNotFound(
|
|
11570
|
+
async () => scenarioStore.get(input.id, await scopeOf2(input)),
|
|
11571
|
+
"Scenario not found"
|
|
11572
|
+
)),
|
|
11334
11573
|
create: protectedProcedure.input(z17.object({
|
|
11335
11574
|
name: NameSchema,
|
|
11336
11575
|
icon: z17.string().max(64).optional(),
|
|
@@ -11340,10 +11579,11 @@ var scenarioRouter = router({
|
|
|
11340
11579
|
nodes: z17.array(ScenarioNodeSchema).min(1).max(500),
|
|
11341
11580
|
traversalMode: z17.enum(["hierarchical", "topological"]).optional(),
|
|
11342
11581
|
presetId: z17.string().max(128).optional(),
|
|
11343
|
-
isPublished: z17.boolean().optional()
|
|
11582
|
+
isPublished: z17.boolean().optional(),
|
|
11583
|
+
...ScopeShape2
|
|
11344
11584
|
})).mutation(({ input }) => {
|
|
11345
11585
|
const now = Date.now();
|
|
11346
|
-
return createOrBadRequest(() => scenarioStore.create({
|
|
11586
|
+
return createOrBadRequest(async () => scenarioStore.create({
|
|
11347
11587
|
id: randomUUID8(),
|
|
11348
11588
|
name: input.name,
|
|
11349
11589
|
icon: input.icon,
|
|
@@ -11368,9 +11608,10 @@ var scenarioRouter = router({
|
|
|
11368
11608
|
nodes: z17.array(ScenarioNodeSchema).min(1).max(500).optional(),
|
|
11369
11609
|
traversalMode: z17.enum(["hierarchical", "topological"]).optional(),
|
|
11370
11610
|
presetId: z17.string().max(128).optional(),
|
|
11371
|
-
isPublished: z17.boolean().optional()
|
|
11611
|
+
isPublished: z17.boolean().optional(),
|
|
11612
|
+
...ScopeShape2
|
|
11372
11613
|
})).mutation(({ input }) => updateOrThrow(
|
|
11373
|
-
() => scenarioStore.update(input.id, {
|
|
11614
|
+
async () => scenarioStore.update(input.id, {
|
|
11374
11615
|
name: input.name,
|
|
11375
11616
|
icon: input.icon,
|
|
11376
11617
|
description: input.description,
|
|
@@ -11381,10 +11622,13 @@ var scenarioRouter = router({
|
|
|
11381
11622
|
presetId: input.presetId,
|
|
11382
11623
|
isPublished: input.isPublished,
|
|
11383
11624
|
updatedAt: Date.now()
|
|
11384
|
-
}),
|
|
11625
|
+
}, await scopeOf2(input)),
|
|
11626
|
+
"Scenario not found"
|
|
11627
|
+
)),
|
|
11628
|
+
delete: protectedProcedure.input(z17.object({ id: Id128Schema, ...ScopeShape2 })).mutation(({ input }) => deleteOrNotFound(
|
|
11629
|
+
async () => scenarioStore.delete(input.id, await scopeOf2(input)),
|
|
11385
11630
|
"Scenario not found"
|
|
11386
11631
|
)),
|
|
11387
|
-
delete: protectedProcedure.input(z17.object({ id: Id128Schema })).mutation(({ input }) => deleteOrNotFound(() => scenarioStore.delete(input.id), "Scenario not found")),
|
|
11388
11632
|
run: protectedProcedure.input(z17.object({
|
|
11389
11633
|
conversationId: ConversationIdSchema,
|
|
11390
11634
|
scenarioId: Id128Schema,
|
|
@@ -11398,7 +11642,7 @@ var scenarioRouter = router({
|
|
|
11398
11642
|
orgId: z17.string().max(128).nullable().optional(),
|
|
11399
11643
|
resume: z17.boolean().optional()
|
|
11400
11644
|
})).subscription(async function* ({ input, signal }) {
|
|
11401
|
-
const scenario = await scenarioStore.get(input.scenarioId);
|
|
11645
|
+
const scenario = await scenarioStore.get(input.scenarioId, await scopeOf2(input));
|
|
11402
11646
|
if (!scenario) throw new TRPCError15({ code: "NOT_FOUND", message: "Scenario not found" });
|
|
11403
11647
|
const canStart = input.userRequest.trim().length > 0 || !!input.resume;
|
|
11404
11648
|
if (!isScenarioRunning(input.conversationId) && canStart) {
|
|
@@ -11718,11 +11962,14 @@ var KanbanQueueService = class extends EventEmitter4 {
|
|
|
11718
11962
|
item.scenarioId ? "scenario" : void 0,
|
|
11719
11963
|
item.runner,
|
|
11720
11964
|
item.boardId,
|
|
11721
|
-
item.cardId
|
|
11965
|
+
item.cardId,
|
|
11966
|
+
void 0,
|
|
11967
|
+
item.projectId,
|
|
11968
|
+
item.orgId
|
|
11722
11969
|
);
|
|
11723
11970
|
const cardContent = card.title + (card.markdown ? "\n\n" + card.markdown : "");
|
|
11724
11971
|
if (item.scenarioId) {
|
|
11725
|
-
const scenario = await scenarioStore.get(item.scenarioId);
|
|
11972
|
+
const scenario = await scenarioStore.get(item.scenarioId, item.orgId ?? void 0);
|
|
11726
11973
|
if (scenario) {
|
|
11727
11974
|
await scenarioRunStore.initializeConversationWorkspace(conv.id, scenario, cardContent);
|
|
11728
11975
|
}
|
|
@@ -11795,14 +12042,21 @@ var kanbanQueueRouter = router({
|
|
|
11795
12042
|
cardId: z19.string().max(128),
|
|
11796
12043
|
runner: z19.enum(["claude-code", "codex"]).optional(),
|
|
11797
12044
|
model: z19.string().max(128).optional(),
|
|
11798
|
-
scenarioId: z19.string().max(128).optional()
|
|
12045
|
+
scenarioId: z19.string().max(128).optional(),
|
|
12046
|
+
// Le projet ouvert quand la carte a été mise en file : la conversation
|
|
12047
|
+
// qui en naîtra lui appartient, plutôt que de rester orpheline et de
|
|
12048
|
+
// s'afficher dans tous les projets.
|
|
12049
|
+
projectId: z19.string().max(128).optional(),
|
|
12050
|
+
orgId: z19.string().max(128).nullable().optional()
|
|
11799
12051
|
})).mutation(({ input }) => kanbanQueue.add(input)),
|
|
11800
12052
|
addColumn: protectedProcedure.input(z19.object({
|
|
11801
12053
|
boardId: z19.string().max(128),
|
|
11802
12054
|
columnId: z19.string().max(128),
|
|
11803
12055
|
runner: z19.enum(["claude-code", "codex"]).optional(),
|
|
11804
12056
|
model: z19.string().max(128).optional(),
|
|
11805
|
-
scenarioId: z19.string().max(128).optional()
|
|
12057
|
+
scenarioId: z19.string().max(128).optional(),
|
|
12058
|
+
projectId: z19.string().max(128).optional(),
|
|
12059
|
+
orgId: z19.string().max(128).nullable().optional()
|
|
11806
12060
|
})).mutation(async ({ input }) => {
|
|
11807
12061
|
const { boardId, columnId, ...options } = input;
|
|
11808
12062
|
return kanbanQueue.addColumn(boardId, columnId, options);
|
|
@@ -11849,7 +12103,7 @@ var kanbanQueueRouter = router({
|
|
|
11849
12103
|
import { mkdir as mkdir15, readdir as readdir5, rm as rm3, stat as stat4, writeFile as writeFile13 } from "fs/promises";
|
|
11850
12104
|
import { createHash } from "crypto";
|
|
11851
12105
|
import { tmpdir } from "os";
|
|
11852
|
-
import { join as
|
|
12106
|
+
import { join as join17 } from "path";
|
|
11853
12107
|
import { z as z20 } from "zod";
|
|
11854
12108
|
import { TRPCError as TRPCError17 } from "@trpc/server";
|
|
11855
12109
|
function summarizeCard(card, boardId, columnTitle) {
|
|
@@ -11900,14 +12154,14 @@ function safeDecode(value) {
|
|
|
11900
12154
|
}
|
|
11901
12155
|
var MAX_ATTACHMENT_BYTES = 55 * 1024 * 1024;
|
|
11902
12156
|
function attachmentCacheDir() {
|
|
11903
|
-
return
|
|
12157
|
+
return join17(tmpdir(), "runeya-kanban-attachments");
|
|
11904
12158
|
}
|
|
11905
12159
|
var ATTACHMENT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
11906
12160
|
async function purgeStaleAttachments(dir, ttlMs = ATTACHMENT_CACHE_TTL_MS) {
|
|
11907
12161
|
try {
|
|
11908
12162
|
const cutoff = Date.now() - ttlMs;
|
|
11909
12163
|
for (const name of await readdir5(dir)) {
|
|
11910
|
-
const path =
|
|
12164
|
+
const path = join17(dir, name);
|
|
11911
12165
|
const info = await stat4(path).catch(() => null);
|
|
11912
12166
|
if (info && info.mtimeMs < cutoff) await rm3(path, { force: true });
|
|
11913
12167
|
}
|
|
@@ -11915,14 +12169,14 @@ async function purgeStaleAttachments(dir, ttlMs = ATTACHMENT_CACHE_TTL_MS) {
|
|
|
11915
12169
|
}
|
|
11916
12170
|
}
|
|
11917
12171
|
function attachmentCacheDirs() {
|
|
11918
|
-
return [attachmentCacheDir(),
|
|
12172
|
+
return [attachmentCacheDir(), join17(machineRoot(), "kanban-attachments")];
|
|
11919
12173
|
}
|
|
11920
12174
|
async function storeAttachment(id, name, body, dirs = attachmentCacheDirs()) {
|
|
11921
12175
|
const refusals = [];
|
|
11922
12176
|
for (const dir of dirs) {
|
|
11923
12177
|
try {
|
|
11924
12178
|
await mkdir15(dir, { recursive: true });
|
|
11925
|
-
const path =
|
|
12179
|
+
const path = join17(dir, safeLocalName(id, name));
|
|
11926
12180
|
await writeFile13(path, body);
|
|
11927
12181
|
void purgeStaleAttachments(dir);
|
|
11928
12182
|
return path;
|
|
@@ -12242,7 +12496,7 @@ var utilityRouter = router({
|
|
|
12242
12496
|
import { z as z23 } from "zod";
|
|
12243
12497
|
import { TRPCError as TRPCError19 } from "@trpc/server";
|
|
12244
12498
|
import { readFile as readFile14, readdir as readdir6, stat as stat5, realpath, writeFile as writeFile14, mkdir as mkdir16, rename as rename11, rm as rm4 } from "fs/promises";
|
|
12245
|
-
import { resolve as resolvePath, join as
|
|
12499
|
+
import { resolve as resolvePath, join as join18, relative, sep as sep3, dirname as dirname8 } from "path";
|
|
12246
12500
|
var SCOPE_SCHEMA = z23.union([
|
|
12247
12501
|
z23.literal("global"),
|
|
12248
12502
|
z23.string().regex(/^service:[A-Za-z0-9_-]{1,128}$/, "Invalid service scope")
|
|
@@ -12312,7 +12566,7 @@ async function walkLocal(root, dir, depth, counter) {
|
|
|
12312
12566
|
for (const e of entries) {
|
|
12313
12567
|
if (counter.n >= MAX_FILES) break;
|
|
12314
12568
|
if (e.name.startsWith(".") && e.name !== ".") continue;
|
|
12315
|
-
const abs =
|
|
12569
|
+
const abs = join18(dir, e.name);
|
|
12316
12570
|
const rel = relative(root, abs).split(sep3).join("/");
|
|
12317
12571
|
if (e.isDirectory()) {
|
|
12318
12572
|
if (IGNORED_DIRS.has(e.name)) continue;
|
|
@@ -12478,29 +12732,10 @@ var configRouter = router({
|
|
|
12478
12732
|
import { z as z24 } from "zod";
|
|
12479
12733
|
import { TRPCError as TRPCError20 } from "@trpc/server";
|
|
12480
12734
|
|
|
12481
|
-
// ../server/src/services/org-sync-scope.ts
|
|
12482
|
-
import { access as access2 } from "fs/promises";
|
|
12483
|
-
import { join as join17 } from "path";
|
|
12484
|
-
async function isMaterialized(root) {
|
|
12485
|
-
return access2(join17(root, "project.json")).then(() => true, () => false);
|
|
12486
|
-
}
|
|
12487
|
-
async function orgsToSync() {
|
|
12488
|
-
const cloudUrl = getCloudApiUrl();
|
|
12489
|
-
const stamps = await readOrgClouds();
|
|
12490
|
-
const orgIds = /* @__PURE__ */ new Set();
|
|
12491
|
-
for (const vault of await listCloudVaults()) {
|
|
12492
|
-
if (orgIds.has(vault.orgId)) continue;
|
|
12493
|
-
const stamped = stamps[vault.orgId];
|
|
12494
|
-
if (stamped && stamped.replace(/\/+$/, "") !== cloudUrl) continue;
|
|
12495
|
-
if (await isMaterialized(vault.root)) orgIds.add(vault.orgId);
|
|
12496
|
-
}
|
|
12497
|
-
return Array.from(orgIds, (orgId) => ({ orgId, cloudUrl }));
|
|
12498
|
-
}
|
|
12499
|
-
|
|
12500
12735
|
// ../server/src/services/master-password.ts
|
|
12501
12736
|
import { randomBytes as randomBytes4, createCipheriv, createDecipheriv } from "crypto";
|
|
12502
12737
|
import { readFile as readFile15, writeFile as writeFile15, chmod as chmod10, stat as stat6, rename as rename12 } from "fs/promises";
|
|
12503
|
-
import { join as
|
|
12738
|
+
import { join as join19 } from "path";
|
|
12504
12739
|
import { Entry } from "@napi-rs/keyring";
|
|
12505
12740
|
import { hashRaw as argon2HashRaw } from "@node-rs/argon2";
|
|
12506
12741
|
var KEYRING_SERVICE = "runeya";
|
|
@@ -12533,7 +12768,7 @@ function aesDecrypt(key, data) {
|
|
|
12533
12768
|
}
|
|
12534
12769
|
async function getPepperFromFile(dataDir) {
|
|
12535
12770
|
try {
|
|
12536
|
-
const raw = await readKeyFile(
|
|
12771
|
+
const raw = await readKeyFile(join19(dataDir, PEPPER_FILENAME));
|
|
12537
12772
|
const hex = raw.toString("utf-8").trim();
|
|
12538
12773
|
if (/^[0-9a-f]{64}$/i.test(hex)) return Buffer.from(hex, "hex");
|
|
12539
12774
|
} catch {
|
|
@@ -12541,7 +12776,7 @@ async function getPepperFromFile(dataDir) {
|
|
|
12541
12776
|
return null;
|
|
12542
12777
|
}
|
|
12543
12778
|
async function writePepperToFile(dataDir, pepper) {
|
|
12544
|
-
const filePath =
|
|
12779
|
+
const filePath = join19(dataDir, PEPPER_FILENAME);
|
|
12545
12780
|
const tmpPath = filePath + ".tmp";
|
|
12546
12781
|
await writeFile15(tmpPath, pepper.toString("hex"), "utf-8");
|
|
12547
12782
|
await chmod10(tmpPath, 384);
|
|
@@ -12585,26 +12820,26 @@ async function deriveKey(masterPassword, pepper, salt) {
|
|
|
12585
12820
|
});
|
|
12586
12821
|
return Buffer.from(derived);
|
|
12587
12822
|
}
|
|
12588
|
-
async function writeEncryptedKey(dataDir, masterPassword, encryptionKey) {
|
|
12589
|
-
const pepper = await getOrCreatePepper(
|
|
12823
|
+
async function writeEncryptedKey(dataDir, masterPassword, encryptionKey, pepperDir = dataDir) {
|
|
12824
|
+
const pepper = await getOrCreatePepper(pepperDir);
|
|
12590
12825
|
const salt = randomBytes4(16);
|
|
12591
12826
|
const derivedKey = await deriveKey(masterPassword, pepper, salt);
|
|
12592
12827
|
const payload = aesEncrypt(derivedKey, encryptionKey);
|
|
12593
12828
|
const fileContent = Buffer.concat([salt, payload]);
|
|
12594
12829
|
const encoded = fileContent.toString("base64");
|
|
12595
|
-
const filePath =
|
|
12830
|
+
const filePath = join19(dataDir, ENCRYPTED_KEY_FILENAME);
|
|
12596
12831
|
const tmpPath = filePath + ".tmp";
|
|
12597
12832
|
await writeFile15(tmpPath, encoded, "utf-8");
|
|
12598
12833
|
await chmod10(tmpPath, 384);
|
|
12599
12834
|
await rename12(tmpPath, filePath);
|
|
12600
12835
|
}
|
|
12601
|
-
async function readEncryptedKey(dataDir, masterPassword) {
|
|
12602
|
-
const filePath =
|
|
12836
|
+
async function readEncryptedKey(dataDir, masterPassword, pepperDir = dataDir) {
|
|
12837
|
+
const filePath = join19(dataDir, ENCRYPTED_KEY_FILENAME);
|
|
12603
12838
|
const raw = await readKeyFile(filePath);
|
|
12604
12839
|
const fileContent = Buffer.from(raw.toString("utf-8").trim(), "base64");
|
|
12605
12840
|
const salt = fileContent.subarray(0, 16);
|
|
12606
12841
|
const payload = fileContent.subarray(16);
|
|
12607
|
-
const pepper = await getPepperFromKeychain(
|
|
12842
|
+
const pepper = await getPepperFromKeychain(pepperDir);
|
|
12608
12843
|
const derivedKey = await deriveKey(masterPassword, pepper, salt);
|
|
12609
12844
|
return aesDecrypt(derivedKey, payload);
|
|
12610
12845
|
}
|
|
@@ -12619,13 +12854,13 @@ async function getPepperFromKeychain(dataDir) {
|
|
|
12619
12854
|
}
|
|
12620
12855
|
async function writeVerifyFile(dataDir, encryptionKey) {
|
|
12621
12856
|
const payload = aesEncrypt(encryptionKey, Buffer.from(CANARY_PLAINTEXT, "utf-8"));
|
|
12622
|
-
const filePath =
|
|
12857
|
+
const filePath = join19(dataDir, VERIFY_FILENAME);
|
|
12623
12858
|
const tmpPath = filePath + ".tmp";
|
|
12624
12859
|
await writeFile15(tmpPath, payload.toString("base64"), "utf-8");
|
|
12625
12860
|
await rename12(tmpPath, filePath);
|
|
12626
12861
|
}
|
|
12627
12862
|
async function verifyEncryptionKey(dataDir, encryptionKey) {
|
|
12628
|
-
const filePath =
|
|
12863
|
+
const filePath = join19(dataDir, VERIFY_FILENAME);
|
|
12629
12864
|
const raw = await readKeyFile(filePath);
|
|
12630
12865
|
if (!matchesCanary(raw.toString("utf-8"), encryptionKey)) {
|
|
12631
12866
|
throw new Error("Cl\xE9 d'encryption incorrecte");
|
|
@@ -12641,14 +12876,14 @@ function matchesCanary(canaryBase64, encryptionKey) {
|
|
|
12641
12876
|
}
|
|
12642
12877
|
async function readVerifyFile(dataDir) {
|
|
12643
12878
|
try {
|
|
12644
|
-
return (await readKeyFile(
|
|
12879
|
+
return (await readKeyFile(join19(dataDir, VERIFY_FILENAME))).toString("utf-8").trim();
|
|
12645
12880
|
} catch {
|
|
12646
12881
|
return null;
|
|
12647
12882
|
}
|
|
12648
12883
|
}
|
|
12649
12884
|
async function hasVerifyFile(dataDir) {
|
|
12650
12885
|
try {
|
|
12651
|
-
await stat6(
|
|
12886
|
+
await stat6(join19(dataDir, VERIFY_FILENAME));
|
|
12652
12887
|
return true;
|
|
12653
12888
|
} catch {
|
|
12654
12889
|
return false;
|
|
@@ -12656,13 +12891,32 @@ async function hasVerifyFile(dataDir) {
|
|
|
12656
12891
|
}
|
|
12657
12892
|
async function hasEncryptedKeyFile(dataDir) {
|
|
12658
12893
|
try {
|
|
12659
|
-
await stat6(
|
|
12894
|
+
await stat6(join19(dataDir, ENCRYPTED_KEY_FILENAME));
|
|
12660
12895
|
return true;
|
|
12661
12896
|
} catch {
|
|
12662
12897
|
return false;
|
|
12663
12898
|
}
|
|
12664
12899
|
}
|
|
12665
12900
|
|
|
12901
|
+
// ../server/src/services/org-sync-scope.ts
|
|
12902
|
+
import { access as access2 } from "fs/promises";
|
|
12903
|
+
import { join as join20 } from "path";
|
|
12904
|
+
async function isMaterialized(root) {
|
|
12905
|
+
return access2(join20(root, "project.json")).then(() => true, () => false);
|
|
12906
|
+
}
|
|
12907
|
+
async function orgsToSync() {
|
|
12908
|
+
const cloudUrl = getCloudApiUrl();
|
|
12909
|
+
const stamps = await readOrgClouds();
|
|
12910
|
+
const orgIds = /* @__PURE__ */ new Set();
|
|
12911
|
+
for (const vault of await listCloudVaults()) {
|
|
12912
|
+
if (orgIds.has(vault.orgId)) continue;
|
|
12913
|
+
const stamped = stamps[vault.orgId];
|
|
12914
|
+
if (stamped && stamped.replace(/\/+$/, "") !== cloudUrl) continue;
|
|
12915
|
+
if (await isMaterialized(vault.root)) orgIds.add(vault.orgId);
|
|
12916
|
+
}
|
|
12917
|
+
return Array.from(orgIds, (orgId) => ({ orgId, cloudUrl }));
|
|
12918
|
+
}
|
|
12919
|
+
|
|
12666
12920
|
// ../server/src/services/org-canary.ts
|
|
12667
12921
|
var CanaryUnavailableError = class extends Error {
|
|
12668
12922
|
code = "CANARY_UNAVAILABLE";
|
|
@@ -12681,9 +12935,9 @@ var OrgKeyMismatchError = class extends Error {
|
|
|
12681
12935
|
}
|
|
12682
12936
|
};
|
|
12683
12937
|
async function reconcileOrgCanary(orgId) {
|
|
12684
|
-
const key = getEncryptionKey();
|
|
12685
|
-
if (!key) throw new CanaryUnavailableError("
|
|
12686
|
-
const local = await readVerifyFile(await keyRoot());
|
|
12938
|
+
const key = getEncryptionKey(orgId);
|
|
12939
|
+
if (!key) throw new CanaryUnavailableError("Cl\xE9 de l'organisation non charg\xE9e.");
|
|
12940
|
+
const local = await readVerifyFile(orgVaultRoot(orgId)) ?? await readVerifyFile(await keyRoot());
|
|
12687
12941
|
if (!local) throw new CanaryUnavailableError("This machine has no key canary yet.");
|
|
12688
12942
|
const res = await cloudFetch("/api/auth/organization/canary", {
|
|
12689
12943
|
method: "POST",
|
|
@@ -12707,10 +12961,10 @@ import * as Y from "yjs";
|
|
|
12707
12961
|
// ../server/src/services/org-lock.ts
|
|
12708
12962
|
import { open as open2, readFile as readFile16, unlink as unlink4, mkdir as mkdir17 } from "fs/promises";
|
|
12709
12963
|
import { readFileSync, unlinkSync } from "fs";
|
|
12710
|
-
import { join as
|
|
12964
|
+
import { join as join21 } from "path";
|
|
12711
12965
|
var held = /* @__PURE__ */ new Set();
|
|
12712
12966
|
function lockPath(orgId) {
|
|
12713
|
-
return
|
|
12967
|
+
return join21(orgVaultRoot(orgId), "org.lock");
|
|
12714
12968
|
}
|
|
12715
12969
|
function isRunning(pid) {
|
|
12716
12970
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -13225,11 +13479,11 @@ var OrgSyncConnection = class {
|
|
|
13225
13479
|
}
|
|
13226
13480
|
async upsertLocally(name, id, fields) {
|
|
13227
13481
|
if (name === "services") {
|
|
13228
|
-
await serviceStore.applyRemoteUpsert(id, fields);
|
|
13482
|
+
await serviceStore.applyRemoteUpsert(id, fields, this.link.orgId);
|
|
13229
13483
|
return;
|
|
13230
13484
|
}
|
|
13231
13485
|
if (name === "environments") {
|
|
13232
|
-
await environmentStore.applyRemoteUpsert(id, fields);
|
|
13486
|
+
await environmentStore.applyRemoteUpsert(id, fields, this.link.orgId);
|
|
13233
13487
|
return;
|
|
13234
13488
|
}
|
|
13235
13489
|
const result = await projectStore.applyRemoteUpsert(id, {
|
|
@@ -13350,6 +13604,14 @@ var CloudSyncBridge = class {
|
|
|
13350
13604
|
connections = /* @__PURE__ */ new Map();
|
|
13351
13605
|
/** Orgs another instance holds the lock on — reported, not retried blindly. */
|
|
13352
13606
|
locked = /* @__PURE__ */ new Set();
|
|
13607
|
+
/**
|
|
13608
|
+
* Orgs whose key this machine does not have.
|
|
13609
|
+
*
|
|
13610
|
+
* Held here for the same reason as `locked`: the connection is never opened,
|
|
13611
|
+
* so there is nothing to ask for its state — and an org that cannot sync
|
|
13612
|
+
* until a human supplies a key must not read as merely "stopped".
|
|
13613
|
+
*/
|
|
13614
|
+
keyMismatch = /* @__PURE__ */ new Set();
|
|
13353
13615
|
started = false;
|
|
13354
13616
|
/**
|
|
13355
13617
|
* Start syncing every organization this machine holds a project of.
|
|
@@ -13369,7 +13631,13 @@ var CloudSyncBridge = class {
|
|
|
13369
13631
|
return;
|
|
13370
13632
|
}
|
|
13371
13633
|
for (const link of links) await this.connect(link);
|
|
13372
|
-
|
|
13634
|
+
const connected = this.connections.size;
|
|
13635
|
+
if (links.length > 0) {
|
|
13636
|
+
const skipped = links.length - connected;
|
|
13637
|
+
console.log(
|
|
13638
|
+
`[cloud-sync] syncing ${connected}/${links.length} organization(s)` + (skipped > 0 ? ` \u2014 ${skipped} not syncing (see the errors above)` : "")
|
|
13639
|
+
);
|
|
13640
|
+
}
|
|
13373
13641
|
}
|
|
13374
13642
|
/**
|
|
13375
13643
|
* Reconnect every link whose session had been rejected. Called after a cloud
|
|
@@ -13379,6 +13647,26 @@ var CloudSyncBridge = class {
|
|
|
13379
13647
|
const paused = Array.from(this.connections.values()).filter((c) => c.getState() === "unauthenticated").map((c) => c.getLink());
|
|
13380
13648
|
for (const link of paused) await this.connect(link);
|
|
13381
13649
|
}
|
|
13650
|
+
/**
|
|
13651
|
+
* Tout reconnecter, quel que soit l'état où chaque organisation s'était
|
|
13652
|
+
* arrêtée.
|
|
13653
|
+
*
|
|
13654
|
+
* `resumeAll` ne reprend que les sessions rejetées ; après un changement de
|
|
13655
|
+
* clé, ce sont justement les organisations écartées pour clé différente qu'il
|
|
13656
|
+
* faut retenter — et elles n'ont pas de connexion à reprendre, seulement un
|
|
13657
|
+
* lien sur le disque.
|
|
13658
|
+
*/
|
|
13659
|
+
async restartAll() {
|
|
13660
|
+
this.keyMismatch.clear();
|
|
13661
|
+
let links = [];
|
|
13662
|
+
try {
|
|
13663
|
+
links = await orgsToSync();
|
|
13664
|
+
} catch (err) {
|
|
13665
|
+
console.error("[cloud-sync] could not read which organizations to sync:", err.message);
|
|
13666
|
+
return;
|
|
13667
|
+
}
|
|
13668
|
+
for (const link of links) await this.connect(link);
|
|
13669
|
+
}
|
|
13382
13670
|
/** Per-org sync state, for the UI. */
|
|
13383
13671
|
states() {
|
|
13384
13672
|
const live = Array.from(this.connections.entries()).map(([orgId, conn]) => ({
|
|
@@ -13387,7 +13675,8 @@ var CloudSyncBridge = class {
|
|
|
13387
13675
|
lastIncompatibleVersion: conn.getLastIncompatibleVersion()
|
|
13388
13676
|
}));
|
|
13389
13677
|
const locked = Array.from(this.locked).filter((orgId) => !this.connections.has(orgId)).map((orgId) => ({ orgId, state: "locked", lastIncompatibleVersion: null }));
|
|
13390
|
-
|
|
13678
|
+
const mismatched = Array.from(this.keyMismatch).filter((orgId) => !this.connections.has(orgId)).map((orgId) => ({ orgId, state: "key-mismatch", lastIncompatibleVersion: null }));
|
|
13679
|
+
return [...live, ...locked, ...mismatched];
|
|
13391
13680
|
}
|
|
13392
13681
|
/**
|
|
13393
13682
|
* Follow one project of an org on this machine, pulling it now.
|
|
@@ -13421,6 +13710,7 @@ var CloudSyncBridge = class {
|
|
|
13421
13710
|
} catch (err) {
|
|
13422
13711
|
if (err instanceof OrgKeyMismatchError) {
|
|
13423
13712
|
console.error("[cloud-sync] org %s: %s", link.orgId, err.message);
|
|
13713
|
+
this.keyMismatch.add(link.orgId);
|
|
13424
13714
|
return;
|
|
13425
13715
|
}
|
|
13426
13716
|
console.warn(
|
|
@@ -13429,6 +13719,7 @@ var CloudSyncBridge = class {
|
|
|
13429
13719
|
err.message
|
|
13430
13720
|
);
|
|
13431
13721
|
}
|
|
13722
|
+
this.keyMismatch.delete(link.orgId);
|
|
13432
13723
|
const lock = await acquireOrgLock(link.orgId).catch((err) => {
|
|
13433
13724
|
console.error("[cloud-sync] could not take the lock for org %s:", link.orgId, err.message);
|
|
13434
13725
|
return { acquired: false, heldBy: 0 };
|
|
@@ -13463,11 +13754,185 @@ var CloudSyncBridge = class {
|
|
|
13463
13754
|
for (const conn of this.connections.values()) conn.stop();
|
|
13464
13755
|
this.connections.clear();
|
|
13465
13756
|
this.locked.clear();
|
|
13757
|
+
this.keyMismatch.clear();
|
|
13466
13758
|
releaseAllOrgLocksSync();
|
|
13467
13759
|
}
|
|
13468
13760
|
};
|
|
13469
13761
|
var cloudSyncBridge = new CloudSyncBridge();
|
|
13470
13762
|
|
|
13763
|
+
// ../server/src/services/key-adoption.ts
|
|
13764
|
+
import { readdir as readdir7, readFile as readFile17, writeFile as writeFile16, mkdir as mkdir20, copyFile, rename as rename13, stat as stat7 } from "fs/promises";
|
|
13765
|
+
import { join as join22 } from "path";
|
|
13766
|
+
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes5 } from "crypto";
|
|
13767
|
+
|
|
13768
|
+
// ../server/src/services/org-keys.ts
|
|
13769
|
+
import { mkdir as mkdir19 } from "fs/promises";
|
|
13770
|
+
async function unlockOrgKeys(masterPassword) {
|
|
13771
|
+
const machineKey = getEncryptionKey();
|
|
13772
|
+
const report = { opened: [], seeded: [], locked: [] };
|
|
13773
|
+
if (!machineKey) return report;
|
|
13774
|
+
for (const orgId of await listOrgVaults()) {
|
|
13775
|
+
const root = orgVaultRoot(orgId);
|
|
13776
|
+
if (!masterPassword) {
|
|
13777
|
+
setEncryptionKeyInMemory(machineKey, orgId);
|
|
13778
|
+
continue;
|
|
13779
|
+
}
|
|
13780
|
+
const canary = await readVerifyFile(root);
|
|
13781
|
+
if (canary) {
|
|
13782
|
+
try {
|
|
13783
|
+
const key = await readEncryptedKey(root, masterPassword, await keyRoot());
|
|
13784
|
+
if (!matchesCanary(canary, key)) {
|
|
13785
|
+
markVaultLocked(orgId);
|
|
13786
|
+
report.locked.push(orgId);
|
|
13787
|
+
continue;
|
|
13788
|
+
}
|
|
13789
|
+
setEncryptionKeyInMemory(key, orgId);
|
|
13790
|
+
report.opened.push(orgId);
|
|
13791
|
+
} catch {
|
|
13792
|
+
markVaultLocked(orgId);
|
|
13793
|
+
report.locked.push(orgId);
|
|
13794
|
+
}
|
|
13795
|
+
continue;
|
|
13796
|
+
}
|
|
13797
|
+
await mkdir19(root, { recursive: true });
|
|
13798
|
+
await writeEncryptedKey(root, masterPassword, machineKey, await keyRoot());
|
|
13799
|
+
await writeVerifyFile(root, machineKey);
|
|
13800
|
+
setEncryptionKeyInMemory(machineKey, orgId);
|
|
13801
|
+
report.seeded.push(orgId);
|
|
13802
|
+
}
|
|
13803
|
+
return report;
|
|
13804
|
+
}
|
|
13805
|
+
async function installOrgKey(orgId, key, masterPassword) {
|
|
13806
|
+
const root = orgVaultRoot(orgId);
|
|
13807
|
+
await mkdir19(root, { recursive: true });
|
|
13808
|
+
const pepperDir = await keyRoot();
|
|
13809
|
+
await writeEncryptedKey(root, masterPassword, key, pepperDir);
|
|
13810
|
+
const readBack = await readEncryptedKey(root, masterPassword, pepperDir);
|
|
13811
|
+
if (!readBack.equals(key)) {
|
|
13812
|
+
throw new Error("V\xE9rification du fichier de cl\xE9 de l'organisation \xE9chou\xE9e.");
|
|
13813
|
+
}
|
|
13814
|
+
await writeVerifyFile(root, key);
|
|
13815
|
+
setEncryptionKeyInMemory(key, orgId);
|
|
13816
|
+
}
|
|
13817
|
+
|
|
13818
|
+
// ../server/src/services/key-adoption.ts
|
|
13819
|
+
var ALGORITHM = "aes-256-gcm";
|
|
13820
|
+
function isAgentTriplet(value) {
|
|
13821
|
+
const hex = (v) => typeof v === "string" && /^[0-9a-f]+$/i.test(v);
|
|
13822
|
+
return hex(value["encrypted"]) && hex(value["iv"]) && hex(value["authTag"]);
|
|
13823
|
+
}
|
|
13824
|
+
function decryptTriplet(key, t2) {
|
|
13825
|
+
const decipher = createDecipheriv2(ALGORITHM, key, Buffer.from(t2.iv, "hex"), { authTagLength: 16 });
|
|
13826
|
+
decipher.setAuthTag(Buffer.from(t2.authTag, "hex"));
|
|
13827
|
+
return decipher.update(t2.encrypted, "hex", "utf-8") + decipher.final("utf-8");
|
|
13828
|
+
}
|
|
13829
|
+
function encryptTriplet(key, plaintext) {
|
|
13830
|
+
const iv = randomBytes5(16);
|
|
13831
|
+
const cipher = createCipheriv2(ALGORITHM, key, iv);
|
|
13832
|
+
const encrypted = cipher.update(plaintext, "utf-8", "hex") + cipher.final("hex");
|
|
13833
|
+
return { encrypted, iv: iv.toString("hex"), authTag: cipher.getAuthTag().toString("hex") };
|
|
13834
|
+
}
|
|
13835
|
+
function rekeyJsonValue(value, from, to, counter) {
|
|
13836
|
+
if (typeof value === "string") {
|
|
13837
|
+
if (!isEncryptedValue(value)) return value;
|
|
13838
|
+
let plaintext;
|
|
13839
|
+
try {
|
|
13840
|
+
plaintext = decryptValueWith(from, value);
|
|
13841
|
+
} catch {
|
|
13842
|
+
return value;
|
|
13843
|
+
}
|
|
13844
|
+
counter.n += 1;
|
|
13845
|
+
return encryptValueWith(to, plaintext);
|
|
13846
|
+
}
|
|
13847
|
+
if (Array.isArray(value)) return value.map((v) => rekeyJsonValue(v, from, to, counter));
|
|
13848
|
+
if (value && typeof value === "object") {
|
|
13849
|
+
const record = value;
|
|
13850
|
+
if (isAgentTriplet(record)) {
|
|
13851
|
+
try {
|
|
13852
|
+
const plaintext = decryptTriplet(from, record);
|
|
13853
|
+
counter.n += 1;
|
|
13854
|
+
return { ...record, ...encryptTriplet(to, plaintext) };
|
|
13855
|
+
} catch {
|
|
13856
|
+
return record;
|
|
13857
|
+
}
|
|
13858
|
+
}
|
|
13859
|
+
const out = {};
|
|
13860
|
+
for (const [k, v] of Object.entries(record)) out[k] = rekeyJsonValue(v, from, to, counter);
|
|
13861
|
+
return out;
|
|
13862
|
+
}
|
|
13863
|
+
return value;
|
|
13864
|
+
}
|
|
13865
|
+
async function rootsOfOrg(orgId) {
|
|
13866
|
+
const roots = /* @__PURE__ */ new Set([orgVaultRoot(orgId)]);
|
|
13867
|
+
for (const vault of await listCloudVaults()) {
|
|
13868
|
+
if (vault.orgId === orgId) roots.add(vault.root);
|
|
13869
|
+
}
|
|
13870
|
+
return Array.from(roots);
|
|
13871
|
+
}
|
|
13872
|
+
async function jsonFilesIn(root) {
|
|
13873
|
+
try {
|
|
13874
|
+
const entries = await readdir7(root, { withFileTypes: true });
|
|
13875
|
+
return entries.filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => e.name);
|
|
13876
|
+
} catch {
|
|
13877
|
+
return [];
|
|
13878
|
+
}
|
|
13879
|
+
}
|
|
13880
|
+
async function exists(path) {
|
|
13881
|
+
return stat7(path).then(() => true, () => false);
|
|
13882
|
+
}
|
|
13883
|
+
async function adoptOrgKey(orgId, newKey, masterPassword, now) {
|
|
13884
|
+
const currentKey = getEncryptionKey(orgId);
|
|
13885
|
+
if (!currentKey) {
|
|
13886
|
+
throw new Error("La cl\xE9 actuelle de cette organisation n'est pas charg\xE9e.");
|
|
13887
|
+
}
|
|
13888
|
+
if (newKey.equals(currentKey)) {
|
|
13889
|
+
throw new Error("Cette cl\xE9 est d\xE9j\xE0 celle de l'organisation.");
|
|
13890
|
+
}
|
|
13891
|
+
const machineRootDir = await keyRoot();
|
|
13892
|
+
await readEncryptedKey(machineRootDir, masterPassword);
|
|
13893
|
+
const pending = [];
|
|
13894
|
+
const touchedRoots = /* @__PURE__ */ new Set();
|
|
13895
|
+
for (const vaultRoot of await rootsOfOrg(orgId)) {
|
|
13896
|
+
for (const name of await jsonFilesIn(vaultRoot)) {
|
|
13897
|
+
const path = join22(vaultRoot, name);
|
|
13898
|
+
let parsed;
|
|
13899
|
+
try {
|
|
13900
|
+
parsed = JSON.parse(await readFile17(path, "utf-8"));
|
|
13901
|
+
} catch {
|
|
13902
|
+
continue;
|
|
13903
|
+
}
|
|
13904
|
+
const counter = { n: 0 };
|
|
13905
|
+
const converted = rekeyJsonValue(parsed, currentKey, newKey, counter);
|
|
13906
|
+
if (counter.n === 0) continue;
|
|
13907
|
+
pending.push({ path, content: JSON.stringify(converted, null, 2), values: counter.n });
|
|
13908
|
+
touchedRoots.add(vaultRoot);
|
|
13909
|
+
}
|
|
13910
|
+
}
|
|
13911
|
+
const stamp = now.toISOString().replace(/[:.]/g, "-").substring(0, 19);
|
|
13912
|
+
const backups = [];
|
|
13913
|
+
for (const vaultRoot of touchedRoots) {
|
|
13914
|
+
const backupDir = join22(vaultRoot, `backup-key-${stamp}`);
|
|
13915
|
+
await mkdir20(backupDir, { recursive: true });
|
|
13916
|
+
for (const name of await jsonFilesIn(vaultRoot)) {
|
|
13917
|
+
await copyFile(join22(vaultRoot, name), join22(backupDir, name));
|
|
13918
|
+
}
|
|
13919
|
+
for (const name of [".encryption.key.enc", ".runeya-verify"]) {
|
|
13920
|
+
if (await exists(join22(vaultRoot, name))) {
|
|
13921
|
+
await copyFile(join22(vaultRoot, name), join22(backupDir, name));
|
|
13922
|
+
}
|
|
13923
|
+
}
|
|
13924
|
+
backups.push(backupDir);
|
|
13925
|
+
}
|
|
13926
|
+
for (const item of pending) {
|
|
13927
|
+
const tmp = item.path + ".tmp";
|
|
13928
|
+
await writeFile16(tmp, item.content, "utf-8");
|
|
13929
|
+
await rename13(tmp, item.path);
|
|
13930
|
+
}
|
|
13931
|
+
await installOrgKey(orgId, newKey, masterPassword);
|
|
13932
|
+
clearVaultLock(orgId);
|
|
13933
|
+
return { converted: pending.map(({ path, values }) => ({ file: path, values })), backups };
|
|
13934
|
+
}
|
|
13935
|
+
|
|
13471
13936
|
// ../server/src/trpc/routers/cloud-sync.ts
|
|
13472
13937
|
async function cloudMessage(res) {
|
|
13473
13938
|
try {
|
|
@@ -13530,6 +13995,134 @@ var cloudSyncRouter = router({
|
|
|
13530
13995
|
throw err;
|
|
13531
13996
|
}
|
|
13532
13997
|
}),
|
|
13998
|
+
/**
|
|
13999
|
+
* Faire passer le projet local de cette machine dans une organisation.
|
|
14000
|
+
*
|
|
14001
|
+
* Le projet n'est pas copié : il déménage. Son coffre quitte
|
|
14002
|
+
* `<cwd>/.runeya` pour celui de l'organisation, en gardant son identifiant,
|
|
14003
|
+
* puis il est suivi comme n'importe quel projet cloud — la réconciliation le
|
|
14004
|
+
* pousse alors dans le doc, services et environnements compris.
|
|
14005
|
+
*
|
|
14006
|
+
* Le déménagement est ce qui rend le geste sûr : il ne peut jamais y avoir
|
|
14007
|
+
* deux copies du même projet, donc rien à écraser. Un projet que
|
|
14008
|
+
* l'organisation porte déjà sous le même identifiant est refusé plutôt que
|
|
14009
|
+
* fusionné.
|
|
14010
|
+
*/
|
|
14011
|
+
publishProject: protectedProcedure.input(
|
|
14012
|
+
z24.object({
|
|
14013
|
+
orgId: z24.string().min(1).max(128),
|
|
14014
|
+
cloudUrl: z24.string().url().max(2048),
|
|
14015
|
+
projectId: z24.string().min(1).max(128)
|
|
14016
|
+
})
|
|
14017
|
+
).mutation(async ({ input }) => {
|
|
14018
|
+
const local = await projectStore.get({ id: input.projectId, orgId: null });
|
|
14019
|
+
if (!local) {
|
|
14020
|
+
throw new TRPCError20({ code: "NOT_FOUND", message: "PROJECT_NOT_LOCAL" });
|
|
14021
|
+
}
|
|
14022
|
+
if (await projectStore.get({ id: input.projectId, orgId: input.orgId })) {
|
|
14023
|
+
throw new TRPCError20({ code: "CONFLICT", message: "PROJECT_ALREADY_IN_ORG" });
|
|
14024
|
+
}
|
|
14025
|
+
const connected = cloudSyncBridge.states().some((s) => s.orgId === input.orgId);
|
|
14026
|
+
if (!connected) {
|
|
14027
|
+
await assertOrgKey(input.orgId);
|
|
14028
|
+
await stampOrgCloud(input.orgId, input.cloudUrl);
|
|
14029
|
+
await cloudSyncBridge.connect({ orgId: input.orgId, cloudUrl: input.cloudUrl });
|
|
14030
|
+
}
|
|
14031
|
+
await projectStore.moveToOrg(input.projectId, input.orgId);
|
|
14032
|
+
await conversationStore.relocateAll();
|
|
14033
|
+
try {
|
|
14034
|
+
await cloudSyncBridge.followProject(input.orgId, input.projectId);
|
|
14035
|
+
} catch (err) {
|
|
14036
|
+
if (err instanceof ProjectHeldElsewhereError) {
|
|
14037
|
+
throw new TRPCError20({ code: "CONFLICT", message: err.code, cause: err });
|
|
14038
|
+
}
|
|
14039
|
+
throw err;
|
|
14040
|
+
}
|
|
14041
|
+
return { state: syncState(input.orgId) };
|
|
14042
|
+
}),
|
|
14043
|
+
/**
|
|
14044
|
+
* Cette clé est-elle celle de l'organisation ? Rien n'est écrit.
|
|
14045
|
+
*
|
|
14046
|
+
* L'étape à faire avant tout le reste : on ne re-chiffre pas une machine
|
|
14047
|
+
* entière sur une clé dont on n'a pas la preuve qu'elle ouvre le canari
|
|
14048
|
+
* publié. Le cloud, lui, ne peut pas répondre — il n'a que du chiffré sous
|
|
14049
|
+
* les yeux — donc le canari est rapatrié et jugé ici.
|
|
14050
|
+
*/
|
|
14051
|
+
checkOrgKey: protectedProcedure.input(z24.object({
|
|
14052
|
+
orgId: z24.string().min(1).max(128),
|
|
14053
|
+
keyHex: z24.string().length(64).regex(/^[0-9a-f]+$/i)
|
|
14054
|
+
})).query(async ({ input }) => {
|
|
14055
|
+
const res = await cloudFetch2(
|
|
14056
|
+
`/api/auth/organization/canary?organizationId=${encodeURIComponent(input.orgId)}`,
|
|
14057
|
+
{ method: "GET" }
|
|
14058
|
+
);
|
|
14059
|
+
if (!res.ok) {
|
|
14060
|
+
throw new TRPCError20({ code: "BAD_GATEWAY", message: await cloudMessage(res) || `CANARY_READ_FAILED (${res.status})` });
|
|
14061
|
+
}
|
|
14062
|
+
const { canary } = await res.json();
|
|
14063
|
+
if (typeof canary !== "string") return { matches: false, published: false };
|
|
14064
|
+
return { matches: matchesCanary(canary, Buffer.from(input.keyHex, "hex")), published: true };
|
|
14065
|
+
}),
|
|
14066
|
+
/**
|
|
14067
|
+
* Faire adopter à cette machine la clé d'une organisation.
|
|
14068
|
+
*
|
|
14069
|
+
* Le pendant de `resetOrgKey` : là où celle-ci fait plier l'organisation à la
|
|
14070
|
+
* clé d'ici, celle-là fait plier cette machine à la sienne — et c'est la
|
|
14071
|
+
* seule des deux qui rend lisible ce qui avait déjà été chiffré sous elle.
|
|
14072
|
+
*
|
|
14073
|
+
* Bornée au coffre de l'organisation et à ceux de ses projets : les autres
|
|
14074
|
+
* organisations et la machine gardent leur clé.
|
|
14075
|
+
*/
|
|
14076
|
+
adoptOrgKey: protectedProcedure.input(z24.object({
|
|
14077
|
+
orgId: z24.string().min(1).max(128),
|
|
14078
|
+
keyHex: z24.string().length(64).regex(/^[0-9a-f]+$/i),
|
|
14079
|
+
masterPassword: z24.string().min(1).max(1024)
|
|
14080
|
+
})).mutation(async ({ input }) => {
|
|
14081
|
+
const report = await adoptOrgKey(
|
|
14082
|
+
input.orgId,
|
|
14083
|
+
Buffer.from(input.keyHex, "hex"),
|
|
14084
|
+
input.masterPassword,
|
|
14085
|
+
/* @__PURE__ */ new Date()
|
|
14086
|
+
);
|
|
14087
|
+
await cloudSyncBridge.restartAll();
|
|
14088
|
+
return { converted: report.converted.length, backups: report.backups, state: syncState(input.orgId) };
|
|
14089
|
+
}),
|
|
14090
|
+
/**
|
|
14091
|
+
* Réinitialiser la clé de référence d'une organisation avec celle de cette
|
|
14092
|
+
* machine, puis relancer sa synchronisation.
|
|
14093
|
+
*
|
|
14094
|
+
* Le canari publié par le cloud est write-once : c'est ce qui empêche un
|
|
14095
|
+
* membre tenant la mauvaise clé d'enfermer les autres dehors. Le revers, c'est
|
|
14096
|
+
* qu'une clé machine qui change — réinstallation, `~/.runeya` perdu, migration
|
|
14097
|
+
* d'un ancien coffre — laisse l'organisation insynchronisable pour toujours.
|
|
14098
|
+
* Cette route est la sortie de secours, réservée au propriétaire côté cloud.
|
|
14099
|
+
*
|
|
14100
|
+
* Elle ne re-chiffre rien : ce qui a déjà été synchronisé sous l'ancienne clé
|
|
14101
|
+
* reste illisible. L'interface le dit avant de la déclencher.
|
|
14102
|
+
*/
|
|
14103
|
+
resetOrgKey: protectedProcedure.input(z24.object({ orgId: z24.string().min(1).max(128) })).mutation(async ({ input }) => {
|
|
14104
|
+
const canary = await readVerifyFile(await keyRoot());
|
|
14105
|
+
if (!canary) {
|
|
14106
|
+
throw new TRPCError20({
|
|
14107
|
+
code: "PRECONDITION_FAILED",
|
|
14108
|
+
message: "Cette machine n'a pas encore de cl\xE9 de chiffrement."
|
|
14109
|
+
});
|
|
14110
|
+
}
|
|
14111
|
+
const res = await cloudFetch2("/api/auth/organization/canary/reset", {
|
|
14112
|
+
method: "POST",
|
|
14113
|
+
body: { organizationId: input.orgId, canary }
|
|
14114
|
+
});
|
|
14115
|
+
if (!res.ok) {
|
|
14116
|
+
const detail = await cloudMessage(res);
|
|
14117
|
+
throw new TRPCError20({
|
|
14118
|
+
code: res.status === 403 ? "FORBIDDEN" : "BAD_GATEWAY",
|
|
14119
|
+
message: detail || `ORG_KEY_RESET_FAILED (${res.status})`
|
|
14120
|
+
});
|
|
14121
|
+
}
|
|
14122
|
+
const cloudUrl = (await orgsToSync()).find((l) => l.orgId === input.orgId)?.cloudUrl ?? getCloudApiUrl();
|
|
14123
|
+
await cloudSyncBridge.connect({ orgId: input.orgId, cloudUrl });
|
|
14124
|
+
return { state: syncState(input.orgId) };
|
|
14125
|
+
}),
|
|
13533
14126
|
/**
|
|
13534
14127
|
* Stop materializing one project of an org on this machine.
|
|
13535
14128
|
*
|
|
@@ -13621,15 +14214,15 @@ var cloudSyncRouter = router({
|
|
|
13621
14214
|
});
|
|
13622
14215
|
|
|
13623
14216
|
// ../server/src/trpc/routers/filesystem.ts
|
|
13624
|
-
import { readdir as
|
|
14217
|
+
import { readdir as readdir8, stat as stat8 } from "fs/promises";
|
|
13625
14218
|
import { homedir as homedir5 } from "os";
|
|
13626
|
-
import { isAbsolute as isAbsolute4, join as
|
|
14219
|
+
import { isAbsolute as isAbsolute4, join as join23, resolve as resolve5, dirname as dirname9, basename as basename2 } from "path";
|
|
13627
14220
|
import { z as z25 } from "zod";
|
|
13628
14221
|
var MAX_ENTRIES = 500;
|
|
13629
14222
|
var PATH_SCHEMA = z25.string().min(1).max(4096);
|
|
13630
14223
|
function safeResolve(input) {
|
|
13631
14224
|
if (input.includes("\0")) throw new Error("Chemin invalide");
|
|
13632
|
-
const expanded = input.startsWith("~") ?
|
|
14225
|
+
const expanded = input.startsWith("~") ? join23(homedir5(), input.slice(1)) : input;
|
|
13633
14226
|
if (!isAbsolute4(expanded)) throw new Error("Le chemin doit \xEAtre absolu");
|
|
13634
14227
|
return resolve5(expanded);
|
|
13635
14228
|
}
|
|
@@ -13652,20 +14245,20 @@ var filesystemRouter = router({
|
|
|
13652
14245
|
*/
|
|
13653
14246
|
listDirectories: protectedProcedure.input(z25.object({ path: PATH_SCHEMA })).query(async ({ input }) => {
|
|
13654
14247
|
const dir = safeResolve(input.path);
|
|
13655
|
-
const info = await
|
|
14248
|
+
const info = await stat8(dir).catch(() => null);
|
|
13656
14249
|
if (!info?.isDirectory()) {
|
|
13657
14250
|
throw new Error(`Ce n'est pas un r\xE9pertoire : ${dir}`);
|
|
13658
14251
|
}
|
|
13659
14252
|
let names;
|
|
13660
14253
|
try {
|
|
13661
|
-
names = (await
|
|
14254
|
+
names = (await readdir8(dir, { withFileTypes: true })).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort((a, b) => a.localeCompare(b));
|
|
13662
14255
|
} catch {
|
|
13663
14256
|
throw new Error(`R\xE9pertoire illisible : ${dir}`);
|
|
13664
14257
|
}
|
|
13665
14258
|
const truncated = names.length > MAX_ENTRIES;
|
|
13666
14259
|
const entries = [];
|
|
13667
14260
|
for (const name of names.slice(0, MAX_ENTRIES)) {
|
|
13668
|
-
const path =
|
|
14261
|
+
const path = join23(dir, name);
|
|
13669
14262
|
entries.push({ name, path, hasRuneyaProject: await holdsRuneyaProject(path) });
|
|
13670
14263
|
}
|
|
13671
14264
|
return {
|
|
@@ -13690,7 +14283,7 @@ var filesystemRouter = router({
|
|
|
13690
14283
|
orgId: z25.string().max(128).nullable().optional()
|
|
13691
14284
|
})).query(async ({ input }) => {
|
|
13692
14285
|
const dir = safeResolve(input.path);
|
|
13693
|
-
const info = await
|
|
14286
|
+
const info = await stat8(dir).catch(() => null);
|
|
13694
14287
|
if (!info?.isDirectory()) {
|
|
13695
14288
|
return { ok: false, reason: "missing", path: dir, otherProjectId: null };
|
|
13696
14289
|
}
|
|
@@ -13765,7 +14358,7 @@ var MigrationEngine = class {
|
|
|
13765
14358
|
var migrationEngine = new MigrationEngine();
|
|
13766
14359
|
|
|
13767
14360
|
// ../server/src/services/file-security.ts
|
|
13768
|
-
import { chmod as chmod11, stat as
|
|
14361
|
+
import { chmod as chmod11, stat as stat9 } from "fs/promises";
|
|
13769
14362
|
var SENSITIVE_FILES = [
|
|
13770
14363
|
"agents.json",
|
|
13771
14364
|
"settings.json",
|
|
@@ -13779,7 +14372,7 @@ async function ensureFilePermissions() {
|
|
|
13779
14372
|
for (const filename of SENSITIVE_FILES) {
|
|
13780
14373
|
for (const filepath of [machineFilePath(filename), legacyMachineFilePath(filename)]) {
|
|
13781
14374
|
try {
|
|
13782
|
-
const stats = await
|
|
14375
|
+
const stats = await stat9(filepath);
|
|
13783
14376
|
const mode = stats.mode & 511;
|
|
13784
14377
|
if (mode !== SECURE_MODE) {
|
|
13785
14378
|
console.warn("[security] File", filepath, "has mode", mode.toString(8) + ", fixing to 600");
|
|
@@ -13795,8 +14388,8 @@ async function ensureFilePermissions() {
|
|
|
13795
14388
|
}
|
|
13796
14389
|
|
|
13797
14390
|
// ../server/src/services/vault-migration.ts
|
|
13798
|
-
import { readdir as
|
|
13799
|
-
import { join as
|
|
14391
|
+
import { readdir as readdir9, readFile as readFile18, writeFile as writeFile17, mkdir as mkdir21, copyFile as copyFile2, unlink as unlink5, rename as rename14, stat as stat10 } from "fs/promises";
|
|
14392
|
+
import { join as join24 } from "path";
|
|
13800
14393
|
var KEY_FILES = [".encryption.key.enc", ".runeya-verify"];
|
|
13801
14394
|
var MACHINE_FILES = [
|
|
13802
14395
|
"agents.json",
|
|
@@ -13806,35 +14399,14 @@ var MACHINE_FILES = [
|
|
|
13806
14399
|
"api-keys.private.json",
|
|
13807
14400
|
"settings.private.json"
|
|
13808
14401
|
];
|
|
13809
|
-
|
|
13810
|
-
if (typeof value === "string") {
|
|
13811
|
-
if (!isEncryptedValue(value)) return value;
|
|
13812
|
-
let plaintext;
|
|
13813
|
-
try {
|
|
13814
|
-
plaintext = decryptValueWith(from, value);
|
|
13815
|
-
} catch {
|
|
13816
|
-
return value;
|
|
13817
|
-
}
|
|
13818
|
-
counter.n += 1;
|
|
13819
|
-
return encryptValueWith(to, plaintext);
|
|
13820
|
-
}
|
|
13821
|
-
if (Array.isArray(value)) return value.map((v) => rekeyValue(v, from, to, counter));
|
|
13822
|
-
if (value && typeof value === "object") {
|
|
13823
|
-
const out = {};
|
|
13824
|
-
for (const [k, v] of Object.entries(value)) {
|
|
13825
|
-
out[k] = rekeyValue(v, from, to, counter);
|
|
13826
|
-
}
|
|
13827
|
-
return out;
|
|
13828
|
-
}
|
|
13829
|
-
return value;
|
|
13830
|
-
}
|
|
14402
|
+
var rekeyValue = rekeyJsonValue;
|
|
13831
14403
|
async function vaultJsonFiles(root) {
|
|
13832
|
-
const entries = await
|
|
14404
|
+
const entries = await readdir9(root, { withFileTypes: true });
|
|
13833
14405
|
return entries.filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => e.name);
|
|
13834
14406
|
}
|
|
13835
|
-
async function
|
|
14407
|
+
async function exists2(path) {
|
|
13836
14408
|
try {
|
|
13837
|
-
await
|
|
14409
|
+
await stat10(path);
|
|
13838
14410
|
return true;
|
|
13839
14411
|
} catch {
|
|
13840
14412
|
return false;
|
|
@@ -13847,17 +14419,17 @@ async function openLegacyVault(legacyRoot, masterPassword) {
|
|
|
13847
14419
|
}
|
|
13848
14420
|
async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machineKey, now) {
|
|
13849
14421
|
const stamp = now.toISOString().replace(/[:.]/g, "-").substring(0, 19);
|
|
13850
|
-
const backupDir =
|
|
13851
|
-
await
|
|
14422
|
+
const backupDir = join24(legacyRoot, `backup-${stamp}`);
|
|
14423
|
+
await mkdir21(backupDir, { recursive: true });
|
|
13852
14424
|
const jsonFiles = await vaultJsonFiles(legacyRoot);
|
|
13853
14425
|
for (const name of [...jsonFiles, ...KEY_FILES]) {
|
|
13854
|
-
if (await
|
|
13855
|
-
await
|
|
14426
|
+
if (await exists2(join24(legacyRoot, name))) {
|
|
14427
|
+
await copyFile2(join24(legacyRoot, name), join24(backupDir, name));
|
|
13856
14428
|
}
|
|
13857
14429
|
}
|
|
13858
14430
|
const rekeyed = [];
|
|
13859
14431
|
for (const name of jsonFiles) {
|
|
13860
|
-
const raw = await
|
|
14432
|
+
const raw = await readFile18(join24(legacyRoot, name), "utf-8");
|
|
13861
14433
|
let parsed;
|
|
13862
14434
|
try {
|
|
13863
14435
|
parsed = JSON.parse(raw);
|
|
@@ -13867,13 +14439,13 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
|
|
|
13867
14439
|
const counter = { n: 0 };
|
|
13868
14440
|
const converted = rekeyValue(parsed, legacyKey, machineKey, counter);
|
|
13869
14441
|
if (counter.n === 0) continue;
|
|
13870
|
-
const tmp =
|
|
13871
|
-
await
|
|
13872
|
-
await
|
|
14442
|
+
const tmp = join24(legacyRoot, `${name}.tmp`);
|
|
14443
|
+
await writeFile17(tmp, JSON.stringify(converted, null, 2), "utf-8");
|
|
14444
|
+
await rename14(tmp, join24(legacyRoot, name));
|
|
13873
14445
|
rekeyed.push({ file: name, values: counter.n });
|
|
13874
14446
|
}
|
|
13875
14447
|
for (const { file, values } of rekeyed) {
|
|
13876
|
-
const parsed = JSON.parse(await
|
|
14448
|
+
const parsed = JSON.parse(await readFile18(join24(legacyRoot, file), "utf-8"));
|
|
13877
14449
|
const check = { n: 0 };
|
|
13878
14450
|
rekeyValue(parsed, machineKey, machineKey, check);
|
|
13879
14451
|
if (check.n !== values) {
|
|
@@ -13882,41 +14454,93 @@ async function migrateLegacyVault(legacyRoot, machineRootDir, legacyKey, machine
|
|
|
13882
14454
|
);
|
|
13883
14455
|
}
|
|
13884
14456
|
}
|
|
13885
|
-
await
|
|
14457
|
+
await mkdir21(machineRootDir, { recursive: true });
|
|
13886
14458
|
const moved = [];
|
|
13887
14459
|
const skipped = [];
|
|
13888
14460
|
for (const name of MACHINE_FILES) {
|
|
13889
|
-
const from =
|
|
13890
|
-
if (!await
|
|
13891
|
-
const to =
|
|
13892
|
-
if (await
|
|
14461
|
+
const from = join24(legacyRoot, name);
|
|
14462
|
+
if (!await exists2(from)) continue;
|
|
14463
|
+
const to = join24(machineRootDir, name);
|
|
14464
|
+
if (await exists2(to)) {
|
|
13893
14465
|
skipped.push(name);
|
|
13894
14466
|
continue;
|
|
13895
14467
|
}
|
|
13896
|
-
await
|
|
14468
|
+
await copyFile2(from, to);
|
|
13897
14469
|
await unlink5(from);
|
|
13898
14470
|
moved.push(name);
|
|
13899
14471
|
}
|
|
13900
14472
|
for (const name of KEY_FILES) {
|
|
13901
|
-
const path =
|
|
13902
|
-
if (await
|
|
14473
|
+
const path = join24(legacyRoot, name);
|
|
14474
|
+
if (await exists2(path)) await unlink5(path);
|
|
13903
14475
|
}
|
|
13904
14476
|
return { backupDir, rekeyed, moved, skipped };
|
|
13905
14477
|
}
|
|
13906
14478
|
|
|
14479
|
+
// ../server/src/services/settings-migration.ts
|
|
14480
|
+
import { readFile as readFile19, writeFile as writeFile18, rename as rename15, unlink as unlink6, mkdir as mkdir22, chmod as chmod12, stat as stat11 } from "fs/promises";
|
|
14481
|
+
import { join as join25 } from "path";
|
|
14482
|
+
var SETTINGS_FILE2 = "settings.json";
|
|
14483
|
+
var SETTINGS_PRIVATE_FILE2 = "settings.private.json";
|
|
14484
|
+
async function exists3(path) {
|
|
14485
|
+
return stat11(path).then(() => true, () => false);
|
|
14486
|
+
}
|
|
14487
|
+
async function legacySource(filename) {
|
|
14488
|
+
for (const path of [machineFilePath(filename), legacyMachineFilePath(filename)]) {
|
|
14489
|
+
if (await exists3(path)) return path;
|
|
14490
|
+
}
|
|
14491
|
+
return null;
|
|
14492
|
+
}
|
|
14493
|
+
async function moveInto(root, filename, from) {
|
|
14494
|
+
await mkdir22(root, { recursive: true });
|
|
14495
|
+
const to = join25(root, filename);
|
|
14496
|
+
const contents = await readFile19(from, "utf-8");
|
|
14497
|
+
const tmp = to + ".tmp";
|
|
14498
|
+
await writeFile18(tmp, contents, "utf-8");
|
|
14499
|
+
await rename15(tmp, to);
|
|
14500
|
+
await chmod12(to, 384);
|
|
14501
|
+
await unlink6(from);
|
|
14502
|
+
}
|
|
14503
|
+
async function migrateSettingsIntoVaults() {
|
|
14504
|
+
const report = { movedTo: null, files: [], ambiguous: [] };
|
|
14505
|
+
const sources = [];
|
|
14506
|
+
for (const filename of [SETTINGS_FILE2, SETTINGS_PRIVATE_FILE2]) {
|
|
14507
|
+
const from = await legacySource(filename);
|
|
14508
|
+
if (from) sources.push({ filename, from });
|
|
14509
|
+
}
|
|
14510
|
+
if (sources.length === 0) return report;
|
|
14511
|
+
const orgs = await listOrgVaults();
|
|
14512
|
+
if (orgs.length === 0) return report;
|
|
14513
|
+
if (orgs.length > 1) {
|
|
14514
|
+
report.ambiguous = orgs;
|
|
14515
|
+
return report;
|
|
14516
|
+
}
|
|
14517
|
+
const orgId = orgs[0];
|
|
14518
|
+
const root = orgVaultRoot(orgId);
|
|
14519
|
+
for (const { filename, from } of sources) {
|
|
14520
|
+
if (await exists3(join25(root, filename))) continue;
|
|
14521
|
+
await moveInto(root, filename, from);
|
|
14522
|
+
report.files.push(filename);
|
|
14523
|
+
}
|
|
14524
|
+
if (report.files.length > 0) {
|
|
14525
|
+
report.movedTo = orgId;
|
|
14526
|
+
settingsManager.forget();
|
|
14527
|
+
}
|
|
14528
|
+
return report;
|
|
14529
|
+
}
|
|
14530
|
+
|
|
13907
14531
|
// ../server/src/migrations/migrate-from-legacy.ts
|
|
13908
14532
|
import { createRequire } from "module";
|
|
13909
|
-
import { randomBytes as
|
|
14533
|
+
import { randomBytes as randomBytes6, createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3 } from "crypto";
|
|
13910
14534
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
13911
14535
|
import {
|
|
13912
|
-
readFile as
|
|
13913
|
-
writeFile as
|
|
13914
|
-
rename as
|
|
13915
|
-
mkdir as
|
|
14536
|
+
readFile as readFile20,
|
|
14537
|
+
writeFile as writeFile19,
|
|
14538
|
+
rename as rename16,
|
|
14539
|
+
mkdir as mkdir23,
|
|
13916
14540
|
access as access3,
|
|
13917
|
-
readdir as
|
|
14541
|
+
readdir as readdir10
|
|
13918
14542
|
} from "fs/promises";
|
|
13919
|
-
import { join as
|
|
14543
|
+
import { join as join26, dirname as dirname10 } from "path";
|
|
13920
14544
|
var _require = createRequire(import.meta.url);
|
|
13921
14545
|
var PARSER_ID_MAP = {
|
|
13922
14546
|
"stack-monitor-parser-jsons": "native:json",
|
|
@@ -13929,8 +14553,8 @@ var PARSER_ID_MAP = {
|
|
|
13929
14553
|
function encryptValue2(plaintext) {
|
|
13930
14554
|
const key = getEncryptionKey();
|
|
13931
14555
|
if (!key) throw new Error("[legacy-migration] Encryption key not loaded");
|
|
13932
|
-
const iv =
|
|
13933
|
-
const cipher =
|
|
14556
|
+
const iv = randomBytes6(16);
|
|
14557
|
+
const cipher = createCipheriv3("aes-256-gcm", key, iv);
|
|
13934
14558
|
let enc = cipher.update(plaintext, "utf-8", "hex");
|
|
13935
14559
|
enc += cipher.final("hex");
|
|
13936
14560
|
const authTag = cipher.getAuthTag().toString("hex");
|
|
@@ -13977,7 +14601,7 @@ function decryptMigrationValue(encrypted) {
|
|
|
13977
14601
|
const parts = encrypted.split(":");
|
|
13978
14602
|
if (parts.length !== 3) return null;
|
|
13979
14603
|
const [ivHex, authHex, ct] = parts;
|
|
13980
|
-
const dec =
|
|
14604
|
+
const dec = createDecipheriv3("aes-256-gcm", key, Buffer.from(ivHex, "hex"), { authTagLength: 16 });
|
|
13981
14605
|
dec.setAuthTag(Buffer.from(authHex, "hex"));
|
|
13982
14606
|
return dec.update(ct, "hex", "utf-8") + dec.final("utf-8");
|
|
13983
14607
|
} catch {
|
|
@@ -14025,7 +14649,7 @@ async function getSodium() {
|
|
|
14025
14649
|
return _sodium;
|
|
14026
14650
|
}
|
|
14027
14651
|
async function decryptLegacyFile(filePath, key, sodium) {
|
|
14028
|
-
const raw = (await
|
|
14652
|
+
const raw = (await readFile20(filePath, "utf-8")).trim();
|
|
14029
14653
|
if (!raw || raw === "[]" || raw === "{}") {
|
|
14030
14654
|
try {
|
|
14031
14655
|
return JSON.parse(raw);
|
|
@@ -14045,12 +14669,12 @@ async function decryptLegacyFile(filePath, key, sodium) {
|
|
|
14045
14669
|
}
|
|
14046
14670
|
async function atomicWrite(path, data) {
|
|
14047
14671
|
const tmp = path + ".tmp";
|
|
14048
|
-
await
|
|
14049
|
-
await
|
|
14672
|
+
await writeFile19(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
14673
|
+
await rename16(tmp, path);
|
|
14050
14674
|
}
|
|
14051
14675
|
async function runLegacyMigration(dataDir) {
|
|
14052
14676
|
const legacyRoot = env.DATA_DIR;
|
|
14053
|
-
const legacyKeyPath =
|
|
14677
|
+
const legacyKeyPath = join26(legacyRoot, "dbs", "encryption-key.json");
|
|
14054
14678
|
try {
|
|
14055
14679
|
await access3(legacyKeyPath);
|
|
14056
14680
|
} catch {
|
|
@@ -14058,20 +14682,20 @@ async function runLegacyMigration(dataDir) {
|
|
|
14058
14682
|
}
|
|
14059
14683
|
console.log("[legacy-migration] Legacy Runeya data detected, starting migration\u2026");
|
|
14060
14684
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
|
|
14061
|
-
const legacyTmp =
|
|
14062
|
-
await
|
|
14063
|
-
await
|
|
14064
|
-
const legacyOld =
|
|
14065
|
-
await
|
|
14685
|
+
const legacyTmp = join26(abCwd, ".runeya-tmp");
|
|
14686
|
+
await rename16(legacyRoot, legacyTmp);
|
|
14687
|
+
await mkdir23(dataDir, { recursive: true });
|
|
14688
|
+
const legacyOld = join26(dataDir, `backup-${timestamp}`);
|
|
14689
|
+
await rename16(legacyTmp, legacyOld);
|
|
14066
14690
|
console.log(`[legacy-migration] Legacy data backed up to ${legacyOld}`);
|
|
14067
|
-
const keyJson = JSON.parse(await
|
|
14691
|
+
const keyJson = JSON.parse(await readFile20(join26(legacyOld, "dbs", "encryption-key.json"), "utf-8"));
|
|
14068
14692
|
const sodium = await getSodium();
|
|
14069
14693
|
const sodiumKey = sodium.from_base64(keyJson.encryptionKey);
|
|
14070
14694
|
const keyBuffer = Buffer.from(sodiumKey);
|
|
14071
14695
|
const runyeaKeyHex = keyBuffer.toString("hex");
|
|
14072
14696
|
await setEncryptionKey(runyeaKeyHex);
|
|
14073
14697
|
console.log("[legacy-migration] Encryption key loaded in memory (temporary)");
|
|
14074
|
-
const dbsDir =
|
|
14698
|
+
const dbsDir = join26(legacyOld, "dbs");
|
|
14075
14699
|
async function readEncrypted(filePath) {
|
|
14076
14700
|
try {
|
|
14077
14701
|
return await decryptLegacyFile(filePath, sodiumKey, sodium);
|
|
@@ -14080,29 +14704,29 @@ async function runLegacyMigration(dataDir) {
|
|
|
14080
14704
|
return null;
|
|
14081
14705
|
}
|
|
14082
14706
|
}
|
|
14083
|
-
const envsDir =
|
|
14084
|
-
const envFiles = (await
|
|
14707
|
+
const envsDir = join26(dbsDir, "envs");
|
|
14708
|
+
const envFiles = (await readdir10(envsDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
|
|
14085
14709
|
const legacyEnvs = {};
|
|
14086
14710
|
for (const file of envFiles) {
|
|
14087
14711
|
const name = file.replace(".encrypted.json", "");
|
|
14088
|
-
legacyEnvs[name] = await readEncrypted(
|
|
14712
|
+
legacyEnvs[name] = await readEncrypted(join26(envsDir, file));
|
|
14089
14713
|
}
|
|
14090
|
-
const servicesDir =
|
|
14091
|
-
const serviceFiles = (await
|
|
14714
|
+
const servicesDir = join26(dbsDir, "services");
|
|
14715
|
+
const serviceFiles = (await readdir10(servicesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
|
|
14092
14716
|
const legacyServices = {};
|
|
14093
14717
|
for (const file of serviceFiles) {
|
|
14094
14718
|
const name = file.replace(".encrypted.json", "");
|
|
14095
14719
|
if (!name) continue;
|
|
14096
|
-
const data = await readEncrypted(
|
|
14720
|
+
const data = await readEncrypted(join26(servicesDir, file));
|
|
14097
14721
|
if (data?.label) legacyServices[name] = data;
|
|
14098
14722
|
}
|
|
14099
|
-
const overridesDir =
|
|
14100
|
-
const overrideFiles = (await
|
|
14723
|
+
const overridesDir = join26(dbsDir, "overrides");
|
|
14724
|
+
const overrideFiles = (await readdir10(overridesDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
|
|
14101
14725
|
const legacyServiceOverrides = {};
|
|
14102
14726
|
const legacyEnvOverrides = {};
|
|
14103
14727
|
for (const file of overrideFiles) {
|
|
14104
14728
|
const baseName = file.replace(".encrypted.json", "");
|
|
14105
|
-
const data = await readEncrypted(
|
|
14729
|
+
const data = await readEncrypted(join26(overridesDir, file));
|
|
14106
14730
|
if (!data) continue;
|
|
14107
14731
|
if (baseName.endsWith("-envs")) {
|
|
14108
14732
|
legacyServiceOverrides[baseName.slice(0, -5)] = data;
|
|
@@ -14110,11 +14734,11 @@ async function runLegacyMigration(dataDir) {
|
|
|
14110
14734
|
legacyEnvOverrides[baseName.slice(0, -12)] = data;
|
|
14111
14735
|
}
|
|
14112
14736
|
}
|
|
14113
|
-
const parsersDir =
|
|
14114
|
-
const parserFiles = (await
|
|
14737
|
+
const parsersDir = join26(dbsDir, "parsers");
|
|
14738
|
+
const parserFiles = (await readdir10(parsersDir).catch(() => [])).filter((f) => f.endsWith(".encrypted.json"));
|
|
14115
14739
|
const legacyParsers = [];
|
|
14116
14740
|
for (const file of parserFiles) {
|
|
14117
|
-
const data = await readEncrypted(
|
|
14741
|
+
const data = await readEncrypted(join26(parsersDir, file));
|
|
14118
14742
|
if (data?.id) legacyParsers.push(data);
|
|
14119
14743
|
}
|
|
14120
14744
|
const projectMonoId = randomUUID12();
|
|
@@ -14455,7 +15079,7 @@ async function runLegacyMigration(dataDir) {
|
|
|
14455
15079
|
}
|
|
14456
15080
|
let existingSettings = {};
|
|
14457
15081
|
try {
|
|
14458
|
-
existingSettings = JSON.parse(await
|
|
15082
|
+
existingSettings = JSON.parse(await readFile20(join26(dataDir, "settings.json"), "utf-8"));
|
|
14459
15083
|
} catch {
|
|
14460
15084
|
}
|
|
14461
15085
|
const migratedParsers = legacyParsers.map((p) => ({
|
|
@@ -14476,11 +15100,11 @@ ${p.transform ?? ""}`,
|
|
|
14476
15100
|
...migratedParsers
|
|
14477
15101
|
]
|
|
14478
15102
|
};
|
|
14479
|
-
await atomicWrite(
|
|
14480
|
-
await atomicWrite(
|
|
14481
|
-
await atomicWrite(
|
|
14482
|
-
await atomicWrite(
|
|
14483
|
-
await atomicWrite(
|
|
15103
|
+
await atomicWrite(join26(dataDir, "projects.json"), runeProjects);
|
|
15104
|
+
await atomicWrite(join26(dataDir, "services.json"), runeServices);
|
|
15105
|
+
await atomicWrite(join26(dataDir, "environments.json"), runeEnvs);
|
|
15106
|
+
await atomicWrite(join26(dataDir, "environment-overrides.json"), flatOverrides);
|
|
15107
|
+
await atomicWrite(join26(dataDir, "settings.json"), newSettings);
|
|
14484
15108
|
const svcEnvCount = runeEnvs.filter((e) => e.scope === "service").length;
|
|
14485
15109
|
console.log(`[legacy-migration] Written: ${runeProjects.length} projects, ${runeServices.length} services, ${runeEnvs.length} environments (${svcEnvCount} service-scoped)`);
|
|
14486
15110
|
console.log(`[legacy-migration] Overrides: ${Object.keys(flatOverrides).length}, custom parsers: ${migratedParsers.length} (all disabled \u2014 rewrite required)`);
|
|
@@ -14491,12 +15115,12 @@ ${p.transform ?? ""}`,
|
|
|
14491
15115
|
}
|
|
14492
15116
|
async function findLegacyKeyInBackup(dataDir) {
|
|
14493
15117
|
try {
|
|
14494
|
-
const entries = await
|
|
15118
|
+
const entries = await readdir10(dataDir);
|
|
14495
15119
|
const backupDirs = entries.filter((e) => e.startsWith("backup-")).sort((a, b) => a < b ? -1 : a > b ? 1 : 0).reverse();
|
|
14496
15120
|
for (const dir of backupDirs) {
|
|
14497
|
-
const keyJsonPath =
|
|
15121
|
+
const keyJsonPath = join26(dataDir, dir, "dbs", "encryption-key.json");
|
|
14498
15122
|
try {
|
|
14499
|
-
const raw = await
|
|
15123
|
+
const raw = await readFile20(keyJsonPath, "utf-8");
|
|
14500
15124
|
const parsed = JSON.parse(raw);
|
|
14501
15125
|
if (!parsed.encryptionKey) continue;
|
|
14502
15126
|
const sodium = await getSodium();
|
|
@@ -14517,14 +15141,14 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
14517
15141
|
const { resolve: resolve7 } = await import("path");
|
|
14518
15142
|
const abDataDir = resolve7(dataDir);
|
|
14519
15143
|
const candidateDirs = [
|
|
14520
|
-
|
|
14521
|
-
|
|
15144
|
+
join26(dirname10(env.DATA_DIR), ".runeya", "dbs", "overrides"),
|
|
15145
|
+
join26(abDataDir, "dbs", "overrides")
|
|
14522
15146
|
];
|
|
14523
15147
|
let overridesDir = null;
|
|
14524
15148
|
let overrideFiles = [];
|
|
14525
15149
|
for (const dir of candidateDirs) {
|
|
14526
15150
|
try {
|
|
14527
|
-
const files = (await
|
|
15151
|
+
const files = (await readdir10(dir)).filter((f) => f.endsWith(".encrypted.json"));
|
|
14528
15152
|
if (files.length > 0) {
|
|
14529
15153
|
overridesDir = dir;
|
|
14530
15154
|
overrideFiles = files;
|
|
@@ -14534,7 +15158,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
14534
15158
|
}
|
|
14535
15159
|
}
|
|
14536
15160
|
if (!overridesDir || overrideFiles.length === 0) return false;
|
|
14537
|
-
const legacyKeyPath =
|
|
15161
|
+
const legacyKeyPath = join26(overridesDir, "..", "encryption-key.json");
|
|
14538
15162
|
try {
|
|
14539
15163
|
await access3(legacyKeyPath);
|
|
14540
15164
|
return false;
|
|
@@ -14552,7 +15176,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
14552
15176
|
const envNameToId = {};
|
|
14553
15177
|
let envsRaw;
|
|
14554
15178
|
try {
|
|
14555
|
-
envsRaw = JSON.parse(await
|
|
15179
|
+
envsRaw = JSON.parse(await readFile20(join26(abDataDir, "environments.json"), "utf-8"));
|
|
14556
15180
|
for (const _env of envsRaw) {
|
|
14557
15181
|
const env2 = _env;
|
|
14558
15182
|
if (env2["name"] && env2["id"]) {
|
|
@@ -14571,7 +15195,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
14571
15195
|
let servicesRaw = [];
|
|
14572
15196
|
const serviceNameToId = {};
|
|
14573
15197
|
try {
|
|
14574
|
-
servicesRaw = JSON.parse(await
|
|
15198
|
+
servicesRaw = JSON.parse(await readFile20(join26(abDataDir, "services.json"), "utf-8"));
|
|
14575
15199
|
for (const _svc of servicesRaw) {
|
|
14576
15200
|
const svc = _svc;
|
|
14577
15201
|
if (svc["name"] && svc["id"]) serviceNameToId[svc["name"]] = svc["id"];
|
|
@@ -14588,7 +15212,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
14588
15212
|
const envName = baseName.slice(0, -"-environment".length);
|
|
14589
15213
|
let data;
|
|
14590
15214
|
try {
|
|
14591
|
-
data = await decryptLegacyFile(
|
|
15215
|
+
data = await decryptLegacyFile(join26(overridesDir, file), sodiumKey, sodium);
|
|
14592
15216
|
} catch (err) {
|
|
14593
15217
|
console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
|
|
14594
15218
|
continue;
|
|
@@ -14615,7 +15239,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
14615
15239
|
}
|
|
14616
15240
|
}
|
|
14617
15241
|
if (Object.keys(flatOverrides).length > 0) {
|
|
14618
|
-
await atomicWrite(
|
|
15242
|
+
await atomicWrite(join26(abDataDir, "environment-overrides.json"), flatOverrides);
|
|
14619
15243
|
console.log(`[override-migration] Written ${Object.keys(flatOverrides).length} override(s) to environment-overrides.json`);
|
|
14620
15244
|
}
|
|
14621
15245
|
let envsModified = false;
|
|
@@ -14625,7 +15249,7 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
14625
15249
|
const serviceName = baseName.slice(0, -"-envs".length);
|
|
14626
15250
|
let data;
|
|
14627
15251
|
try {
|
|
14628
|
-
data = await decryptLegacyFile(
|
|
15252
|
+
data = await decryptLegacyFile(join26(overridesDir, file), sodiumKey, sodium);
|
|
14629
15253
|
} catch (err) {
|
|
14630
15254
|
console.warn(`[override-migration] Failed to decrypt ${file}: ${err.message}`);
|
|
14631
15255
|
continue;
|
|
@@ -14684,12 +15308,12 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
14684
15308
|
}
|
|
14685
15309
|
}
|
|
14686
15310
|
if (envsModified) {
|
|
14687
|
-
await atomicWrite(
|
|
15311
|
+
await atomicWrite(join26(abDataDir, "environments.json"), envsRaw);
|
|
14688
15312
|
console.log("[override-migration] Merged service overrides into environments.json");
|
|
14689
15313
|
}
|
|
14690
15314
|
const migratedDir = overridesDir.replace(/overrides$/, "overrides-migrated");
|
|
14691
15315
|
try {
|
|
14692
|
-
await
|
|
15316
|
+
await rename16(overridesDir, migratedDir);
|
|
14693
15317
|
console.log(`[override-migration] Moved overrides/ \u2192 overrides-migrated/`);
|
|
14694
15318
|
} catch (err) {
|
|
14695
15319
|
console.warn(`[override-migration] Could not rename overrides dir: ${err.message}`);
|
|
@@ -14699,8 +15323,8 @@ async function runOverrideOnlyMigration(dataDir) {
|
|
|
14699
15323
|
}
|
|
14700
15324
|
|
|
14701
15325
|
// ../server/src/services/server-state.ts
|
|
14702
|
-
import { join as
|
|
14703
|
-
import { stat as
|
|
15326
|
+
import { join as join27 } from "path";
|
|
15327
|
+
import { stat as stat12 } from "fs/promises";
|
|
14704
15328
|
var ENCRYPTED_KEY_FILENAME2 = ".encryption.key.enc";
|
|
14705
15329
|
var VERIFY_FILENAME2 = ".runeya-verify";
|
|
14706
15330
|
async function getServerState() {
|
|
@@ -14709,22 +15333,22 @@ async function getServerState() {
|
|
|
14709
15333
|
let hasVerify = false;
|
|
14710
15334
|
let hasData = false;
|
|
14711
15335
|
try {
|
|
14712
|
-
await
|
|
15336
|
+
await stat12(join27(dataDir, ENCRYPTED_KEY_FILENAME2));
|
|
14713
15337
|
hasEncFile = true;
|
|
14714
15338
|
} catch {
|
|
14715
15339
|
}
|
|
14716
15340
|
try {
|
|
14717
|
-
await
|
|
15341
|
+
await stat12(join27(dataDir, VERIFY_FILENAME2));
|
|
14718
15342
|
hasVerify = true;
|
|
14719
15343
|
} catch {
|
|
14720
15344
|
}
|
|
14721
15345
|
try {
|
|
14722
|
-
await
|
|
15346
|
+
await stat12(join27(dataDir, "projects.json"));
|
|
14723
15347
|
hasData = true;
|
|
14724
15348
|
} catch {
|
|
14725
15349
|
}
|
|
14726
15350
|
try {
|
|
14727
|
-
await
|
|
15351
|
+
await stat12(join27(dataDir, "project.json"));
|
|
14728
15352
|
hasData = true;
|
|
14729
15353
|
} catch {
|
|
14730
15354
|
}
|
|
@@ -15158,9 +15782,25 @@ async function createLocalServer() {
|
|
|
15158
15782
|
credentials: true
|
|
15159
15783
|
}));
|
|
15160
15784
|
let postUnlockDone = false;
|
|
15161
|
-
async function runPostUnlock() {
|
|
15785
|
+
async function runPostUnlock(masterPassword = null) {
|
|
15162
15786
|
if (postUnlockDone) return;
|
|
15163
15787
|
postUnlockDone = true;
|
|
15788
|
+
const orgKeys = await unlockOrgKeys(masterPassword);
|
|
15789
|
+
if (orgKeys.seeded.length > 0) {
|
|
15790
|
+
console.log(`[org-keys] ${orgKeys.seeded.length} organisation(s) ont re\xE7u leur propre cl\xE9 de chiffrement`);
|
|
15791
|
+
}
|
|
15792
|
+
for (const orgId of orgKeys.locked) {
|
|
15793
|
+
console.error(`[org-keys] org ${orgId}: sa cl\xE9 ne s'ouvre pas avec ce master password \u2014 ce coffre reste ferm\xE9.`);
|
|
15794
|
+
}
|
|
15795
|
+
const settingsMove = await migrateSettingsIntoVaults();
|
|
15796
|
+
if (settingsMove.movedTo) {
|
|
15797
|
+
console.log(`[settings] r\xE9glages d\xE9plac\xE9s vers l'organisation ${settingsMove.movedTo} : ${settingsMove.files.join(", ")}`);
|
|
15798
|
+
}
|
|
15799
|
+
if (settingsMove.ambiguous.length > 0) {
|
|
15800
|
+
console.warn(
|
|
15801
|
+
`[settings] r\xE9glages encore au niveau du poste : ${settingsMove.ambiguous.length} organisations sur cette machine, impossible de deviner \xE0 laquelle ils appartiennent \u2014 r\xE9partissez-les depuis les r\xE9glages.`
|
|
15802
|
+
);
|
|
15803
|
+
}
|
|
15164
15804
|
await runOverrideOnlyMigration(env.DATA_DIR);
|
|
15165
15805
|
if (env.MODE === "local-simple") {
|
|
15166
15806
|
const projects = await projectStore.list();
|
|
@@ -15416,7 +16056,7 @@ async function createLocalServer() {
|
|
|
15416
16056
|
}
|
|
15417
16057
|
const encKey = await readEncryptedKey(await keyRoot(), masterPassword);
|
|
15418
16058
|
setEncryptionKeyInMemory(encKey);
|
|
15419
|
-
await runPostUnlock();
|
|
16059
|
+
await runPostUnlock(masterPassword);
|
|
15420
16060
|
const token = await signJwt();
|
|
15421
16061
|
res.json({ token });
|
|
15422
16062
|
} catch (err) {
|
|
@@ -15462,8 +16102,8 @@ async function createLocalServer() {
|
|
|
15462
16102
|
}
|
|
15463
16103
|
encKey = Buffer.from(encryptionKeyHex, "hex");
|
|
15464
16104
|
} else {
|
|
15465
|
-
const { randomBytes:
|
|
15466
|
-
encKey =
|
|
16105
|
+
const { randomBytes: randomBytes7 } = await import("crypto");
|
|
16106
|
+
encKey = randomBytes7(32);
|
|
15467
16107
|
}
|
|
15468
16108
|
}
|
|
15469
16109
|
await writeEncryptedKey(await keyRoot(), masterPassword, encKey);
|
|
@@ -15479,7 +16119,7 @@ async function createLocalServer() {
|
|
|
15479
16119
|
if (isLegacyMigration) {
|
|
15480
16120
|
clearPendingLegacyKey();
|
|
15481
16121
|
}
|
|
15482
|
-
await runPostUnlock();
|
|
16122
|
+
await runPostUnlock(masterPassword);
|
|
15483
16123
|
const token = await signJwt();
|
|
15484
16124
|
res.json({ token });
|
|
15485
16125
|
} catch (err) {
|
|
@@ -15938,4 +16578,4 @@ export {
|
|
|
15938
16578
|
createLocalServer,
|
|
15939
16579
|
pullEnv
|
|
15940
16580
|
};
|
|
15941
|
-
//# sourceMappingURL=src-
|
|
16581
|
+
//# sourceMappingURL=src-4GK5OK3K.js.map
|