limixbot 0.3.12 → 0.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/dist/assets/{ActivityPanel-CJM61CWQ.js → ActivityPanel-CrqSA80q.js} +1 -1
  2. package/dist/assets/{App-DRnqqiJP.js → App-Bx6hl9Sg.js} +129 -129
  3. package/dist/assets/{AppEventEditor-1hupO_kL.js → AppEventEditor-DLfkpP1d.js} +1 -1
  4. package/dist/assets/{BotSettingsDialog-B56-kmya.js → BotSettingsDialog-rHcw4pr0.js} +1 -1
  5. package/dist/assets/{BrowserPanel-BXBygd1y.js → BrowserPanel-BgNsYU3s.js} +1 -1
  6. package/dist/assets/{BrowserProfilesManager-CzzGhZGz.js → BrowserProfilesManager-ByQtDUyM.js} +1 -1
  7. package/dist/assets/{CloudAddDialog-MMhMrVK3.js → CloudAddDialog-754PKKwo.js} +1 -1
  8. package/dist/assets/{ComputerFilesPane-Hpxjnk2e.js → ComputerFilesPane-IP5ib-4w.js} +1 -1
  9. package/dist/assets/{ComputerPanel-CMce64RT.js → ComputerPanel-CndWeumv.js} +2 -2
  10. package/dist/assets/{DataPanel-BQ7Ua1Oq.js → DataPanel-BWlfa8L7.js} +1 -1
  11. package/dist/assets/{DesktopViewer-BRzNtGEm.js → DesktopViewer-tgbnWJ7S.js} +1 -1
  12. package/dist/assets/{FilesView-DQYXl0zm.js → FilesView-PiPhchvH.js} +1 -1
  13. package/dist/assets/{GrokMoveDialog-Dk-pwczv.js → GrokMoveDialog-DGBkFh2_.js} +1 -1
  14. package/dist/assets/{InspectorPanel-CvnM0TFW.js → InspectorPanel-DMb9KlS1.js} +1 -1
  15. package/dist/assets/{KeyboardShortcutsModal-BQ8aAw16.js → KeyboardShortcutsModal-DcAnacT0.js} +1 -1
  16. package/dist/assets/{LiveDesktop-Cr7uAFoQ.js → LiveDesktop-pNuAQp7j.js} +1 -1
  17. package/dist/assets/{LocalVmWorkspace-BJ-i68MR.js → LocalVmWorkspace-rUDRW4sg.js} +1 -1
  18. package/dist/assets/{MacLocalControl-DE8MVZkU.js → MacLocalControl-D2_QV-or.js} +1 -1
  19. package/dist/assets/{MarkdownFilePreview-B_6azXNT.js → MarkdownFilePreview-DNNn-HhY.js} +1 -1
  20. package/dist/assets/{MenuMotion-BDYr4RxL.js → MenuMotion-CIhkQGNX.js} +1 -1
  21. package/dist/assets/{NewBotDialog-D9fUysLU.js → NewBotDialog-C1onlYty.js} +1 -1
  22. package/dist/assets/{PhoneAppDialog-DUv4Qx8n.js → PhoneAppDialog-DygQ7EJ1.js} +1 -1
  23. package/dist/assets/{PlacesRail-yZdo57pP.js → PlacesRail-DC3DtwQy.js} +1 -1
  24. package/dist/assets/{PluginsPanel-DYuWdWZd.js → PluginsPanel-BZv6B1JK.js} +1 -1
  25. package/dist/assets/{ProIntroduction-DapZkP7I.js → ProIntroduction-DeMFeFxH.js} +1 -1
  26. package/dist/assets/{RemoteAgentSettingsPanel-sNv3TbBd.js → RemoteAgentSettingsPanel-DyVQB35x.js} +1 -1
  27. package/dist/assets/{RoomPanel-DrlyOJKZ.js → RoomPanel-DEH49als.js} +1 -1
  28. package/dist/assets/{RoutinesPage-CIo30TVq.js → RoutinesPage-CRI83T_I.js} +1 -1
  29. package/dist/assets/SettingsModal-Dcdk0Qvv.js +11 -0
  30. package/dist/assets/{ShareTeamDialog-CH1ts7Uj.js → ShareTeamDialog-D5ML8qDY.js} +1 -1
  31. package/dist/assets/{SignedInSites-CisTAQW0.js → SignedInSites-Bjc69Ty-.js} +1 -1
  32. package/dist/assets/{TeamLibraryPanel-DvnKye05.js → TeamLibraryPanel-CVcmOn6k.js} +1 -1
  33. package/dist/assets/{TeamMapPage-JOiilgs0.js → TeamMapPage-C3-Cu90W.js} +1 -1
  34. package/dist/assets/{TriggersPage-DQ3HQ6_i.js → TriggersPage-DS2Ceion.js} +1 -1
  35. package/dist/assets/{VisibilitySection-DXfPp0Ql.js → VisibilitySection-DigpYJ5s.js} +1 -1
  36. package/dist/assets/{WebhookTrigger-BYeOyOpX.js → WebhookTrigger-BP28l-Jy.js} +1 -1
  37. package/dist/assets/{WorksOnGrid-8fCnUzpQ.js → WorksOnGrid-dVy5M1Fj.js} +1 -1
  38. package/dist/assets/{abnfDiagram-YUKMZFIV-Boj-Bkbs.js → abnfDiagram-YUKMZFIV-DE2rjoCc.js} +1 -1
  39. package/dist/assets/{activity-MpAxS_kw.js → activity-8Dgeq15H.js} +1 -1
  40. package/dist/assets/{architectureDiagram-47P4ROYG-Cl-ocqce.js → architectureDiagram-47P4ROYG-DrIn6OKQ.js} +1 -1
  41. package/dist/assets/{blockDiagram-E7TT5TSB-0nSQiv6g.js → blockDiagram-E7TT5TSB-BQdVPMCR.js} +1 -1
  42. package/dist/assets/{c4Diagram-CRA5TL53-Cx9iymkb.js → c4Diagram-CRA5TL53-DFb8MJmm.js} +1 -1
  43. package/dist/assets/{calendar-days-DXHDRm_4.js → calendar-days-Bs4Es4HT.js} +1 -1
  44. package/dist/assets/channel-1lG6Uyuf.js +1 -0
  45. package/dist/assets/{chunk-2Q5K7J3B-0rIrumUG.js → chunk-2Q5K7J3B-BXaLRTO9.js} +1 -1
  46. package/dist/assets/{chunk-5VM5RSS4-w9e6iKvs.js → chunk-5VM5RSS4-Cie3uAC2.js} +1 -1
  47. package/dist/assets/{chunk-7TKQ45FW-c4KxVKkf.js → chunk-7TKQ45FW-Bw5vFHu9.js} +1 -1
  48. package/dist/assets/{chunk-AW2ZBBNX-DmaBcuOP.js → chunk-AW2ZBBNX-CkxRWzwT.js} +1 -1
  49. package/dist/assets/{chunk-FVRAUYC3-BLzfqLHR.js → chunk-FVRAUYC3-7FMS_2yS.js} +2 -2
  50. package/dist/assets/{chunk-HTAEGDNF-BN48XtBm.js → chunk-HTAEGDNF-DQ_kiZYj.js} +1 -1
  51. package/dist/assets/{chunk-JWPE2WC7-D2NACa6i.js → chunk-JWPE2WC7-DD4AjX_J.js} +1 -1
  52. package/dist/assets/{chunk-KQW6MTUR-CbAfnxxG.js → chunk-KQW6MTUR-DAs7Un1F.js} +1 -1
  53. package/dist/assets/{chunk-XXDRQBXY-Bwg-DPP_.js → chunk-XXDRQBXY-qEAG8aNp.js} +1 -1
  54. package/dist/assets/{classDiagram-v2-K4WV4PDN-Dh3ohoZp.js → classDiagram-v2-K4WV4PDN-SFDos9_W.js} +1 -1
  55. package/dist/assets/{coins-Bv8-qll4.js → coins-ni7p9MMO.js} +1 -1
  56. package/dist/assets/{core-DeJAGBXR.js → core-CMus8yJ9.js} +1 -1
  57. package/dist/assets/{cose-bilkent-JH36ORCC-DCHadn0L.js → cose-bilkent-JH36ORCC-BIvnumq9.js} +1 -1
  58. package/dist/assets/{cynefin-EF2NZ3EQ-ClpPPxUg.js → cynefin-EF2NZ3EQ-DEyZ973A.js} +1 -1
  59. package/dist/assets/{cynefinDiagram-3GCD6N5R-DZ5l7nkL.js → cynefinDiagram-3GCD6N5R-DIH2LA6q.js} +1 -1
  60. package/dist/assets/{dagre-W4DXFKR2-BagTvT62.js → dagre-W4DXFKR2-doItNion.js} +1 -1
  61. package/dist/assets/{diagram-2UJZ2QOL-DuByootb.js → diagram-2UJZ2QOL-DKfcZ3Fe.js} +1 -1
  62. package/dist/assets/{diagram-OVF4WLC6-BQ8Cvr0D.js → diagram-OVF4WLC6-BRnKWMs5.js} +1 -1
  63. package/dist/assets/{diagram-PFPMY2P6-D_pklaf9.js → diagram-PFPMY2P6-C3aLszxc.js} +1 -1
  64. package/dist/assets/{diagram-UMYRVEAY-1BuHq7tR.js → diagram-UMYRVEAY-DbMKM9Kw.js} +1 -1
  65. package/dist/assets/{diagram-YEKJPTXX-CK0cPx8n.js → diagram-YEKJPTXX-BqrhdTTX.js} +1 -1
  66. package/dist/assets/{diagram-ZIFT7M5P-DVzPho45.js → diagram-ZIFT7M5P-CYnUFm0_.js} +1 -1
  67. package/dist/assets/{ebnfDiagram-VR2GEFS7-D8UO_5sE.js → ebnfDiagram-VR2GEFS7-CnuOWBxY.js} +1 -1
  68. package/dist/assets/{elk-IJKZMXRS-BRigfR5V.js → elk-IJKZMXRS-BmtbqAAs.js} +1 -1
  69. package/dist/assets/{erDiagram-O2IAPWRE-CXHPobWj.js → erDiagram-O2IAPWRE-CL6oNmZD.js} +1 -1
  70. package/dist/assets/{file-up-CQezfdJk.js → file-up-DT7t5NXT.js} +1 -1
  71. package/dist/assets/{flowDiagram-OXPTDLAJ-CqKD6jh8.js → flowDiagram-OXPTDLAJ-CE9hfMoK.js} +1 -1
  72. package/dist/assets/{ganttDiagram-R7TSEDQI-C9FV3gsA.js → ganttDiagram-R7TSEDQI-DRU_A2cB.js} +1 -1
  73. package/dist/assets/{gitGraphDiagram-XJZIOB7I-CRu4dnr_.js → gitGraphDiagram-XJZIOB7I-BPkWYlKZ.js} +1 -1
  74. package/dist/assets/{import-name-dEG0japD.js → import-name-DutQXK90.js} +1 -1
  75. package/dist/assets/{index-COnd2OMZ.js → index-C6OVbzeZ.js} +2 -2
  76. package/dist/assets/{index-B4HW_T-k.js → index-CiYcp06m.js} +1 -1
  77. package/dist/assets/{infoDiagram-5W2HQ5XZ-CCRjb2CQ.js → infoDiagram-5W2HQ5XZ-C4SBhDtT.js} +1 -1
  78. package/dist/assets/{ishikawaDiagram-K3B6WC7H-TaNPsGZS.js → ishikawaDiagram-K3B6WC7H-3TlhKRsV.js} +1 -1
  79. package/dist/assets/{journeyDiagram-COXZFDF6-Dz7YIIc2.js → journeyDiagram-COXZFDF6-K9dyCbpb.js} +1 -1
  80. package/dist/assets/{kanban-definition-P3RFRI5V-Cg69Fpp8.js → kanban-definition-P3RFRI5V-Df8i0B0H.js} +1 -1
  81. package/dist/assets/{mcp-servers-Ih6dI7Ga.js → mcp-servers-xuxWmmEV.js} +1 -1
  82. package/dist/assets/{mermaid.core-DiG3LoLz.js → mermaid.core-Dj_sBsXx.js} +4 -4
  83. package/dist/assets/{mindmap-definition-LZFPQGKD-nxP4f0fq.js → mindmap-definition-LZFPQGKD-CuBxqBw1.js} +1 -1
  84. package/dist/assets/{minimize-B_k_fmmo.js → minimize-CuDlETLH.js} +1 -1
  85. package/dist/assets/{monitor-BfmnPU-I.js → monitor-DqFADU6d.js} +1 -1
  86. package/dist/assets/{new-bot-CQm0FPc5.js → new-bot-DoJRfwOC.js} +1 -1
  87. package/dist/assets/{org-library-C2li0IRL.js → org-library-B7nhJQxt.js} +1 -1
  88. package/dist/assets/{package-format-B3whu9DW.js → package-format-FvzPr9Xd.js} +1 -1
  89. package/dist/assets/{pegDiagram-BGZESJAR-gPjpGcNH.js → pegDiagram-BGZESJAR-BDWRqrLr.js} +1 -1
  90. package/dist/assets/{pieDiagram-CAPJLFHJ-Cgh25Z4_.js → pieDiagram-CAPJLFHJ-BByTBeRn.js} +1 -1
  91. package/dist/assets/{quadrantDiagram-GDMTTRAM-DD9luyqA.js → quadrantDiagram-GDMTTRAM-D0SI7j3Y.js} +1 -1
  92. package/dist/assets/{rail-B1mj4zlo.js → rail-BEOIK49V.js} +1 -1
  93. package/dist/assets/{railroadDiagram-SM67HX2B-CS_TYko7.js → railroadDiagram-SM67HX2B-Dz3tUGbx.js} +1 -1
  94. package/dist/assets/{remote-desktop-panel-DIllbP7i.js → remote-desktop-panel-DgZionIJ.js} +1 -1
  95. package/dist/assets/{requirementDiagram-X7JNWC4A-BePHia6E.js → requirementDiagram-X7JNWC4A-DrwqW7OV.js} +1 -1
  96. package/dist/assets/{sankeyDiagram-UM26HJRW-24ExCXGS.js → sankeyDiagram-UM26HJRW-95WqcM7l.js} +1 -1
  97. package/dist/assets/{sequenceDiagram-ZO4K6R2Y-Dtmx27I5.js → sequenceDiagram-ZO4K6R2Y-1zoh2EeE.js} +1 -1
  98. package/dist/assets/{sizeCapture-INFHLROL-2LvmoT1T.js → sizeCapture-INFHLROL-IzBNvZN8.js} +1 -1
  99. package/dist/assets/{stateDiagram-v2-TBUQTH76-moL2SIo9.js → stateDiagram-v2-TBUQTH76-CXdapkBV.js} +1 -1
  100. package/dist/assets/styles-CSArda2J.css +1 -0
  101. package/dist/assets/{swimlanes-N4OXWK64-D95Pk8qh.js → swimlanes-N4OXWK64-QK7_rFPB.js} +1 -1
  102. package/dist/assets/swimlanesDiagram-K3J5GTZL-B4Zj1xuq.js +8 -0
  103. package/dist/assets/{team-share-0GXKeixY.js → team-share-NJEAQGUX.js} +1 -1
  104. package/dist/assets/{timeline-definition-YOQAKGHF-HM-J25k2.js → timeline-definition-YOQAKGHF-MhSu-mD6.js} +1 -1
  105. package/dist/assets/{usecaseDiagram-VIAY4XPW-C6q6bCzJ.js → usecaseDiagram-VIAY4XPW-DdZIcDLN.js} +1 -1
  106. package/dist/assets/{vennDiagram-BLWOH2XV-hf0dh2yT.js → vennDiagram-BLWOH2XV-DDMi9wJz.js} +1 -1
  107. package/dist/assets/{wardleyDiagram-YMQ3BMBF-CpT_eRn9.js → wardleyDiagram-YMQ3BMBF-DrZpbJKY.js} +1 -1
  108. package/dist/assets/{xychartDiagram-TAQBALBS-D6g1V9DG.js → xychartDiagram-TAQBALBS-__UkZCKS.js} +1 -1
  109. package/dist/index.html +3 -3
  110. package/dist/rail.html +5 -5
  111. package/dist-server/index.js +307 -289
  112. package/dist-server/openmausbot.js +17 -16
  113. package/dist-server/package.json +1 -1
  114. package/dist-server/pair-cli.js +17 -16
  115. package/dist-server/server/index.js +2 -0
  116. package/dist-server/server/limix-skill-hub.js +264 -0
  117. package/dist-server/server/routes/bot-skills.js +11 -0
  118. package/package.json +1 -1
  119. package/dist/assets/SettingsModal-BpJhi2nc.js +0 -11
  120. package/dist/assets/channel-BStWWrSt.js +0 -1
  121. package/dist/assets/styles-bUbK33f1.css +0 -1
  122. package/dist/assets/swimlanesDiagram-K3J5GTZL-DByi9G3i.js +0 -8
  123. /package/dist/assets/{styles-6RbfQIY7.js → styles-Btbbe7r3.js} +0 -0
@@ -8655,22 +8655,23 @@ email:email3,message:message5},message5},requestCode=rawEmail=>serialize(async()
8655
8655
  async()=>{if(!configured)throw new Error(FRIENDLY_MESSAGES.control_plane_unavailable);requestedAt=now();let email3=normalizeAccountEmail(rawEmail);phase={status:"connecting",email:email3};let verified;try{await requireHealthyControlPlane(),verified=await obtain(email3)}catch(error62){let message5=failAction(error62,{email:email3,signedOut:!0});throw new Error(message5)}let previous2=storedAccount(credentials()),refreshingPendingCleanup=!!(previous2&&previous2.userId===verified.user.id&&companionAccountCleanupPending(
8656
8656
  credentials())),authenticatedPersisted=!1;try{previous2&&previous2.userId!==verified.user.id&&await cleanupCurrentAccount();let existingIdentity=ownString(credentials(),COMPANION_CLIENT_INSTANCE_FIELD),clientInstanceId=UUID.test(existingIdentity)?existingIdentity:newClientInstanceId?.();if(!UUID.test(clientInstanceId??""))throw new Error("A stable computer identity could not be created");return await updateCredentials(document3=>withAuthenticatedAccount(document3,{...verified,clientInstanceId,preserveCleanupPending:refreshingPendingCleanup})),
8657
8657
  authenticatedPersisted=!0,refreshingPendingCleanup?(await cleanupCurrentAccount({markPending:!1}),settledState()):await provision(verified)}catch(error62){return authenticatedPersisted||await client.signOut(verified.accountToken).catch(()=>{}),failAction(error62,{email:authenticatedPersisted?verified.user.email:previous2?.email??email3,expiredSessionIsSignedOut:authenticatedPersisted}),authenticatedPersisted&&scheduleAutoRetry(error62),settledState()}}),verifyCode=(rawEmail,rawCode)=>signInFlow(
8658
- rawEmail,email3=>client.verifyOTP(email3,rawCode)),connectLimix=credential=>signInFlow(credential.email,async()=>({accountToken:credential.bearer,user:{id:credential.userId,email:credential.email}})),retryWork=async()=>{if(!configured)return settledState();let account2=storedAccount(credentials());if(!account2)return phase=null,settledState();if(companionAccountCleanupPending(credentials())){phase={status:"connecting",email:account2.email};try{await requireHealthyControlPlane(),await cleanupCurrentAccount(
8659
- {markPending:!1})}catch(error62){failAction(error62,{email:account2.email,expiredSessionIsSignedOut:!0})}return settledState()}phase={status:"connecting",email:account2.email};try{return await requireHealthyControlPlane(),await provision({accountToken:account2.accountToken,user:{id:account2.userId,email:account2.email}})}catch(error62){return failAction(error62,{email:account2.email,expiredSessionIsSignedOut:!0}),scheduleAutoRetry(error62),settledState()}},retry=()=>serialize(async()=>(requestedAt=
8660
- now(),retryWork())),reconcileIfEndpointGone=async({onGone=retryWork}={})=>{let document3=credentials(),account2=storedAccount(document3),access2=managedCompanionTunnelAccess(document3);if(!account2||!access2||!account2.installationCredential||companionAccountCleanupPending(document3)||typeof client.getEndpoint!="function")return null;lastEndpointCheck=now();let endpoint;try{endpoint=await client.getEndpoint(account2.installationCredential)}catch(error62){return error62 instanceof ControlPlaneError&&
8661
- error62.status===401?onGone():null}return endpoint&&endpoint.url===access2.endpoint&&!ENDPOINT_NEEDS_RECONCILE.has(endpoint.status)?null:onGone()},endpointCheckDue=()=>lastEndpointCheck===null||now()-lastEndpointCheck>=endpointCheckIntervalMs/2,recoveryDue=({atLaunch=!1}={})=>{if(!autoRecover||!companionIsOn())return!1;if(atLaunch)return!0;if(autoRetryTimer!==null)return!1;let connection=managedConnectionState?.()??{};return WATCHDOG_CHECK_STATES.has(connection.status)&&endpointCheckDue()},requestedRecently=()=>requestedAt!==
8662
- null&&now()-requestedAt<REQUEST_WINDOW_MS,savedAddress=document3=>normalizeManagedCompanionEndpoint(document3[MANAGED_COMPANION_ENDPOINT_FIELD]),phonesUnused=!1,currentNeed=async()=>{let answer=await Promise.resolve().then(()=>managedEndpointNeeded()).catch(()=>null);phonesUnused=answer===MANAGED_ENDPOINT_PHONES_UNUSED;let need=phonesUnused?!1:typeof answer=="boolean"?answer:null;return need===!1&&relay.capable()?!1:requestedRecently()?(phonesUnused=!1,!0):need},releaseScheduledAt=()=>{let at2=credentials()[MANAGED_COMPANION_RELEASE_AT_FIELD];
8663
- return Number.isSafeInteger(at2)&&at2>0?at2:null},cancelRelease=async()=>{releaseScheduledAt()!==null&&await updateCredentials(document3=>{let next={...document3};return delete next[MANAGED_COMPANION_RELEASE_AT_FIELD],next})},releaseAddress=async()=>{let need=await currentNeed(),pause2=phonesUnused;if(need!==!1)return need&&await cancelRelease(),settledState();let document3=credentials(),account2=storedAccount(document3);if(!account2?.installationCredential||!managedCompanionTunnelAccess(document3)||
8664
- companionAccountCleanupPending(document3))return settledState();await stopManagedEndpoint();let removalPending=!1;try{await client.deleteEndpoint(account2.installationCredential)}catch(error62){if(removalPending=error62 instanceof ControlPlaneError&&error62.code==="endpoint_cleanup_pending",!(removalPending||error62 instanceof ControlPlaneError&&error62.status===401))return writeLog(error62 instanceof ControlPlaneError?`companion account: could not give the secure address back code=${error62.code}\
8665
- status=${error62.status||"none"} ref=${error62.requestId||"none"}`:"companion account: could not give the secure address back (not a control-plane answer)"),await activatePersistedEndpoint(),settledState()}return await updateCredentials(current=>({...withoutManagedCompanionTunnelAccess(current),...removalPending?{[COMPANION_ENDPOINT_RELEASE_PENDING_FIELD]:!0}:{},...pause2?{[COMPANION_ENDPOINT_PAUSED_FIELD]:!0}:{}})),resetAutoRetry(),phase=null,writeLog(pause2?"companion account: gave the secure\
8666
- address back; no paired phone was used in 30 days":"companion account: gave the secure address back; no device needs it"),settledState()},reviewAddress=async({atLaunch=!1}={})=>{let document3=credentials();if(!storedAccount(document3)||companionAccountCleanupPending(document3))return null;if(!managedCompanionTunnelAccess(document3)){let waiting2=document3[COMPANION_TUNNEL_WAITING_FIELD]===!0;return!savedAddress(document3)&&!waiting2||autoRetryTimer!==null?null:await currentNeed()===!0?retryWork():
8667
- null}let need=await currentNeed();if(need!==!1)return need&&await cancelRelease(),need===null&&atLaunch?null:recoveryDue({atLaunch})?reconcileIfEndpointGone():null;let releaseAt=releaseScheduledAt();if(releaseAt!==null&&now()>=releaseAt)return releaseAddress();let connection=managedConnectionState?.()??{};if(companionIsOn()&&CONNECTOR_FAILING_STATES.has(connection.status)&&(atLaunch||endpointCheckDue())){let released=await reconcileIfEndpointGone({onGone:releaseAddress});if(released)return released}
8668
- if(releaseAt===null||releaseAt-now()>releaseGraceMs+releaseJitterMs){let delay3=releaseGraceMs+Math.round(random()*releaseJitterMs);await updateCredentials(current=>managedCompanionTunnelAccess(current)?{...current,[MANAGED_COMPANION_RELEASE_AT_FIELD]:now()+delay3}:current),writeLog(`companion account: no device needs the secure address; giving it back in ${Math.round(delay3/6e4)} min`)}return null},retryIfWanted=async()=>requestedRecently()||savedAddress(credentials())&&await currentNeed()!==!1?
8669
- retryWork():(resetAutoRetry(),phase?.status==="error"&&(phase=null),writeLog("companion account: stopped retrying; no device needs a secure address"),settledState());retryFromTimer=()=>serialize(onDemand?retryIfWanted:retryWork);let prepare=()=>serialize(async()=>{let document3=credentials();if(!configured||!companionIsOn()||!storedAccount(document3)||companionAccountCleanupPending(document3))return settledState();if(requestedAt=now(),!managedCompanionTunnelAccess(document3))return retryWork();await cancelRelease();
8670
- let connection=managedConnectionState?.()??{};if(["stopped","unconfigured"].includes(connection.status))await activatePersistedEndpoint();else if(CONNECTOR_FAILING_STATES.has(connection.status)){let repaired=await reconcileIfEndpointGone();if(repaired)return repaired}return settledState()}),watchdogTick=()=>{if(watchdogTimer=null,!disposed&&(watchdogTimer=setTimer(watchdogTick,endpointCheckIntervalMs),phase?.status!=="signed-out")){if(onDemand){serialize(()=>reviewAddress()).catch(()=>{});return}
8671
- recoveryDue()&&serialize(()=>reconcileIfEndpointGone()).catch(()=>{})}},startWatchdog=()=>{!(autoRecover||onDemand)||disposed||!configured||watchdogTimer!==null||(watchdogTimer=setTimer(watchdogTick,firstEndpointCheckMs))};return Object.freeze({state:async()=>(await probeControlPlane(),settledState()),requestCode,verifyCode,connectLimix,retry,prepare,signOut:()=>serialize(async()=>{if(resetAutoRetry(),!storedAccount(credentials()))return await clearAfterCleanup(),settledState();let email3=storedAccount(
8672
- credentials())?.email;phase={status:"connecting",email:email3};try{return await cleanupCurrentAccount(),healthy=!0,lastHealthCheck=now(),settledState()}catch(error62){return failAction(error62,{email:email3}),settledState()}}),restore:()=>serialize(async()=>{if(!configured||(startWatchdog(),!await probeControlPlane({force:!0})))return settledState();if(companionAccountCleanupPending(credentials()))return retryWork();if(await ensureClientIdentity(),onDemand){let reviewed=await reviewAddress({atLaunch:!0});
8673
- return reviewed||(phase=null,settledState())}let account2=storedAccount(credentials());if(account2&&!managedCompanionTunnelAccess(credentials()))return retryWork();if(autoRecover&&account2&&companionIsOn()){let reconciled=await reconcileIfEndpointGone();if(reconciled)return reconciled}return phase=null,settledState()}),dispose:()=>{disposed=!0,cancelAutoRetry(),watchdogTimer!==null&&clearTimer(watchdogTimer),watchdogTimer=null}})}var SIGNIN_CODE_TTL_MS=10*6e4;init_user_errors();function parseAllowList(value){return(value??"").split(/[,\s]+/).map(entry=>entry.trim().toLowerCase()).filter(Boolean)}var NOT_WELCOME={ok:!1,status:403,...userError("serverError.email.notAllowed")};init_decision_log();init_redact2();init_usage_ledger();import{appendFile as appendFile2,mkdir as mkdir2}from"node:fs/promises";import{basename as basename2,join as join2}from"node:path";var DIR2="admin-activity";var PRUNE_EVERY_MS2=60*6e4,writeQueues2=new Map,lastPrune2=new Map;function adminActivityFileFor(dataDir,at2){return join2(dataDir,DIR2,`${monthKey(at2)}.ndjson`)}async function writeRow(dataDir,row){let at2=new Date(row.at);await mkdir2(join2(dataDir,DIR2),{recursive:!0,mode:448}),await appendFile2(adminActivityFileFor(dataDir,at2),JSON.stringify(row)+`
8658
+ rawEmail,email3=>client.verifyOTP(email3,rawCode)),connectLimix=credential=>signInFlow(credential.email,async()=>({accountToken:credential.bearer,user:{id:credential.userId,email:credential.email}})),forgetForeign=()=>serialize(async()=>{let document3=credentials(),account2=storedAccount(document3);if(!account2)return!1;let access2=managedCompanionTunnelAccess(document3),host2="";try{host2=access2?.endpoint?new URL(access2.endpoint).hostname:""}catch{host2=""}return account2.userId.startsWith("l\
8659
+ imix:")&&(!host2||host2.endsWith(".limixmedia.com"))?!1:(await clearAfterCleanup(),!0)}),retryWork=async()=>{if(!configured)return settledState();let account2=storedAccount(credentials());if(!account2)return phase=null,settledState();if(companionAccountCleanupPending(credentials())){phase={status:"connecting",email:account2.email};try{await requireHealthyControlPlane(),await cleanupCurrentAccount({markPending:!1})}catch(error62){failAction(error62,{email:account2.email,expiredSessionIsSignedOut:!0})}
8660
+ return settledState()}phase={status:"connecting",email:account2.email};try{return await requireHealthyControlPlane(),await provision({accountToken:account2.accountToken,user:{id:account2.userId,email:account2.email}})}catch(error62){return failAction(error62,{email:account2.email,expiredSessionIsSignedOut:!0}),scheduleAutoRetry(error62),settledState()}},retry=()=>serialize(async()=>(requestedAt=now(),retryWork())),reconcileIfEndpointGone=async({onGone=retryWork}={})=>{let document3=credentials(),
8661
+ account2=storedAccount(document3),access2=managedCompanionTunnelAccess(document3);if(!account2||!access2||!account2.installationCredential||companionAccountCleanupPending(document3)||typeof client.getEndpoint!="function")return null;lastEndpointCheck=now();let endpoint;try{endpoint=await client.getEndpoint(account2.installationCredential)}catch(error62){return error62 instanceof ControlPlaneError&&error62.status===401?onGone():null}return endpoint&&endpoint.url===access2.endpoint&&!ENDPOINT_NEEDS_RECONCILE.
8662
+ has(endpoint.status)?null:onGone()},endpointCheckDue=()=>lastEndpointCheck===null||now()-lastEndpointCheck>=endpointCheckIntervalMs/2,recoveryDue=({atLaunch=!1}={})=>{if(!autoRecover||!companionIsOn())return!1;if(atLaunch)return!0;if(autoRetryTimer!==null)return!1;let connection=managedConnectionState?.()??{};return WATCHDOG_CHECK_STATES.has(connection.status)&&endpointCheckDue()},requestedRecently=()=>requestedAt!==null&&now()-requestedAt<REQUEST_WINDOW_MS,savedAddress=document3=>normalizeManagedCompanionEndpoint(
8663
+ document3[MANAGED_COMPANION_ENDPOINT_FIELD]),phonesUnused=!1,currentNeed=async()=>{let answer=await Promise.resolve().then(()=>managedEndpointNeeded()).catch(()=>null);phonesUnused=answer===MANAGED_ENDPOINT_PHONES_UNUSED;let need=phonesUnused?!1:typeof answer=="boolean"?answer:null;return need===!1&&relay.capable()?!1:requestedRecently()?(phonesUnused=!1,!0):need},releaseScheduledAt=()=>{let at2=credentials()[MANAGED_COMPANION_RELEASE_AT_FIELD];return Number.isSafeInteger(at2)&&at2>0?at2:null},cancelRelease=async()=>{
8664
+ releaseScheduledAt()!==null&&await updateCredentials(document3=>{let next={...document3};return delete next[MANAGED_COMPANION_RELEASE_AT_FIELD],next})},releaseAddress=async()=>{let need=await currentNeed(),pause2=phonesUnused;if(need!==!1)return need&&await cancelRelease(),settledState();let document3=credentials(),account2=storedAccount(document3);if(!account2?.installationCredential||!managedCompanionTunnelAccess(document3)||companionAccountCleanupPending(document3))return settledState();await stopManagedEndpoint();
8665
+ let removalPending=!1;try{await client.deleteEndpoint(account2.installationCredential)}catch(error62){if(removalPending=error62 instanceof ControlPlaneError&&error62.code==="endpoint_cleanup_pending",!(removalPending||error62 instanceof ControlPlaneError&&error62.status===401))return writeLog(error62 instanceof ControlPlaneError?`companion account: could not give the secure address back code=${error62.code} status=${error62.status||"none"} ref=${error62.requestId||"none"}`:"companion account: co\
8666
+ uld not give the secure address back (not a control-plane answer)"),await activatePersistedEndpoint(),settledState()}return await updateCredentials(current=>({...withoutManagedCompanionTunnelAccess(current),...removalPending?{[COMPANION_ENDPOINT_RELEASE_PENDING_FIELD]:!0}:{},...pause2?{[COMPANION_ENDPOINT_PAUSED_FIELD]:!0}:{}})),resetAutoRetry(),phase=null,writeLog(pause2?"companion account: gave the secure address back; no paired phone was used in 30 days":"companion account: gave the secure ad\
8667
+ dress back; no device needs it"),settledState()},reviewAddress=async({atLaunch=!1}={})=>{let document3=credentials();if(!storedAccount(document3)||companionAccountCleanupPending(document3))return null;if(!managedCompanionTunnelAccess(document3)){let waiting2=document3[COMPANION_TUNNEL_WAITING_FIELD]===!0;return!savedAddress(document3)&&!waiting2||autoRetryTimer!==null?null:await currentNeed()===!0?retryWork():null}let need=await currentNeed();if(need!==!1)return need&&await cancelRelease(),need===
8668
+ null&&atLaunch?null:recoveryDue({atLaunch})?reconcileIfEndpointGone():null;let releaseAt=releaseScheduledAt();if(releaseAt!==null&&now()>=releaseAt)return releaseAddress();let connection=managedConnectionState?.()??{};if(companionIsOn()&&CONNECTOR_FAILING_STATES.has(connection.status)&&(atLaunch||endpointCheckDue())){let released=await reconcileIfEndpointGone({onGone:releaseAddress});if(released)return released}if(releaseAt===null||releaseAt-now()>releaseGraceMs+releaseJitterMs){let delay3=releaseGraceMs+
8669
+ Math.round(random()*releaseJitterMs);await updateCredentials(current=>managedCompanionTunnelAccess(current)?{...current,[MANAGED_COMPANION_RELEASE_AT_FIELD]:now()+delay3}:current),writeLog(`companion account: no device needs the secure address; giving it back in ${Math.round(delay3/6e4)} min`)}return null},retryIfWanted=async()=>requestedRecently()||savedAddress(credentials())&&await currentNeed()!==!1?retryWork():(resetAutoRetry(),phase?.status==="error"&&(phase=null),writeLog("companion accoun\
8670
+ t: stopped retrying; no device needs a secure address"),settledState());retryFromTimer=()=>serialize(onDemand?retryIfWanted:retryWork);let prepare=()=>serialize(async()=>{let document3=credentials();if(!configured||!companionIsOn()||!storedAccount(document3)||companionAccountCleanupPending(document3))return settledState();if(requestedAt=now(),!managedCompanionTunnelAccess(document3))return retryWork();await cancelRelease();let connection=managedConnectionState?.()??{};if(["stopped","unconfigured"].
8671
+ includes(connection.status))await activatePersistedEndpoint();else if(CONNECTOR_FAILING_STATES.has(connection.status)){let repaired=await reconcileIfEndpointGone();if(repaired)return repaired}return settledState()}),watchdogTick=()=>{if(watchdogTimer=null,!disposed&&(watchdogTimer=setTimer(watchdogTick,endpointCheckIntervalMs),phase?.status!=="signed-out")){if(onDemand){serialize(()=>reviewAddress()).catch(()=>{});return}recoveryDue()&&serialize(()=>reconcileIfEndpointGone()).catch(()=>{})}},startWatchdog=()=>{
8672
+ !(autoRecover||onDemand)||disposed||!configured||watchdogTimer!==null||(watchdogTimer=setTimer(watchdogTick,firstEndpointCheckMs))};return Object.freeze({state:async()=>(await probeControlPlane(),settledState()),requestCode,verifyCode,connectLimix,forgetForeign,retry,prepare,signOut:()=>serialize(async()=>{if(resetAutoRetry(),!storedAccount(credentials()))return await clearAfterCleanup(),settledState();let email3=storedAccount(credentials())?.email;phase={status:"connecting",email:email3};try{return await cleanupCurrentAccount(),
8673
+ healthy=!0,lastHealthCheck=now(),settledState()}catch(error62){return failAction(error62,{email:email3}),settledState()}}),restore:()=>serialize(async()=>{if(!configured||(startWatchdog(),!await probeControlPlane({force:!0})))return settledState();if(companionAccountCleanupPending(credentials()))return retryWork();if(await ensureClientIdentity(),onDemand){let reviewed=await reviewAddress({atLaunch:!0});return reviewed||(phase=null,settledState())}let account2=storedAccount(credentials());if(account2&&
8674
+ !managedCompanionTunnelAccess(credentials()))return retryWork();if(autoRecover&&account2&&companionIsOn()){let reconciled=await reconcileIfEndpointGone();if(reconciled)return reconciled}return phase=null,settledState()}),dispose:()=>{disposed=!0,cancelAutoRetry(),watchdogTimer!==null&&clearTimer(watchdogTimer),watchdogTimer=null}})}var SIGNIN_CODE_TTL_MS=10*6e4;init_user_errors();function parseAllowList(value){return(value??"").split(/[,\s]+/).map(entry=>entry.trim().toLowerCase()).filter(Boolean)}var NOT_WELCOME={ok:!1,status:403,...userError("serverError.email.notAllowed")};init_decision_log();init_redact2();init_usage_ledger();import{appendFile as appendFile2,mkdir as mkdir2}from"node:fs/promises";import{basename as basename2,join as join2}from"node:path";var DIR2="admin-activity";var PRUNE_EVERY_MS2=60*6e4,writeQueues2=new Map,lastPrune2=new Map;function adminActivityFileFor(dataDir,at2){return join2(dataDir,DIR2,`${monthKey(at2)}.ndjson`)}async function writeRow(dataDir,row){let at2=new Date(row.at);await mkdir2(join2(dataDir,DIR2),{recursive:!0,mode:448}),await appendFile2(adminActivityFileFor(dataDir,at2),JSON.stringify(row)+`
8674
8675
  `,{mode:384}),at2.getTime()-(lastPrune2.get(dataDir)??0)>=PRUNE_EVERY_MS2&&(lastPrune2.set(dataDir,at2.getTime()),await pruneMonthFiles(join2(dataDir,DIR2),boundRetentionDays(),at2))}function appendAdminAction(dataDir,row){let record12={...row,at:row.at??new Date().toISOString(),...row.before?{before:auditValues(row.before)}:{},...row.after?{after:auditValues(row.after)}:{}},queued=(writeQueues2.get(dataDir)??Promise.resolve()).then(()=>writeRow(dataDir,record12)).catch(()=>{});writeQueues2.set(
8675
8676
  dataDir,queued),queued.finally(()=>{writeQueues2.get(dataDir)===queued&&writeQueues2.delete(dataDir)})}async function flushAdminActivity(dataDir){await writeQueues2.get(dataDir)}var SECRET_PARTS=["token","secret","password","passwd","passphrase","credential","authorization","cookie","bearer","apikey","api_key"],OPAQUE_MAP=/^(?:headers|env|environment)$/i,MAX_VALUE_CHARS=2e3,HIDDEN="[hidden]";function isSecretName2(name){let lower=name.toLowerCase();return lower.endsWith("tokens")?!1:SECRET_PARTS.some(part=>lower.includes(part))?!0:/(?:^|[_.-])keys?$/.test(lower)||/[a-z]Keys?$/.test(name)}function isSecretParameter(name){
8676
8677
  if(name==="-k"||name==="-K")return!0;let bare=name.replace(/^-+/,"");return isSecretName2(bare)||/^(?:auth|sig|signature|pass|pwd|code)$/i.test(bare)}function keyLikeSegment(segment){return(segment.match(/[A-Za-z0-9]{16,}/g)??[]).some(run6=>/\d/.test(run6)&&/[A-Za-z]/.test(run6))}function maskUrlSecrets(text13){return text13.replace(/\b([a-z][\w+.-]*:\/\/)([^\s/?#]*)([^\s?#]*)([^\s]*)/gi,(_all,scheme,authority2,path3,rest)=>{let at2=authority2.lastIndexOf("@"),host2=at2>=0?`${HIDDEN}@${authority2.
@@ -1 +1 @@
1
- {"type":"module","version":"0.3.12"}
1
+ {"type":"module","version":"0.3.15"}
@@ -8654,22 +8654,23 @@ email:email3,message:message5},message5},requestCode=rawEmail=>serialize(async()
8654
8654
  async()=>{if(!configured)throw new Error(FRIENDLY_MESSAGES.control_plane_unavailable);requestedAt=now();let email3=normalizeAccountEmail(rawEmail);phase={status:"connecting",email:email3};let verified;try{await requireHealthyControlPlane(),verified=await obtain(email3)}catch(error62){let message5=failAction(error62,{email:email3,signedOut:!0});throw new Error(message5)}let previous2=storedAccount(credentials()),refreshingPendingCleanup=!!(previous2&&previous2.userId===verified.user.id&&companionAccountCleanupPending(
8655
8655
  credentials())),authenticatedPersisted=!1;try{previous2&&previous2.userId!==verified.user.id&&await cleanupCurrentAccount();let existingIdentity=ownString(credentials(),COMPANION_CLIENT_INSTANCE_FIELD),clientInstanceId=UUID.test(existingIdentity)?existingIdentity:newClientInstanceId?.();if(!UUID.test(clientInstanceId??""))throw new Error("A stable computer identity could not be created");return await updateCredentials(document3=>withAuthenticatedAccount(document3,{...verified,clientInstanceId,preserveCleanupPending:refreshingPendingCleanup})),
8656
8656
  authenticatedPersisted=!0,refreshingPendingCleanup?(await cleanupCurrentAccount({markPending:!1}),settledState()):await provision(verified)}catch(error62){return authenticatedPersisted||await client.signOut(verified.accountToken).catch(()=>{}),failAction(error62,{email:authenticatedPersisted?verified.user.email:previous2?.email??email3,expiredSessionIsSignedOut:authenticatedPersisted}),authenticatedPersisted&&scheduleAutoRetry(error62),settledState()}}),verifyCode=(rawEmail,rawCode)=>signInFlow(
8657
- rawEmail,email3=>client.verifyOTP(email3,rawCode)),connectLimix=credential=>signInFlow(credential.email,async()=>({accountToken:credential.bearer,user:{id:credential.userId,email:credential.email}})),retryWork=async()=>{if(!configured)return settledState();let account2=storedAccount(credentials());if(!account2)return phase=null,settledState();if(companionAccountCleanupPending(credentials())){phase={status:"connecting",email:account2.email};try{await requireHealthyControlPlane(),await cleanupCurrentAccount(
8658
- {markPending:!1})}catch(error62){failAction(error62,{email:account2.email,expiredSessionIsSignedOut:!0})}return settledState()}phase={status:"connecting",email:account2.email};try{return await requireHealthyControlPlane(),await provision({accountToken:account2.accountToken,user:{id:account2.userId,email:account2.email}})}catch(error62){return failAction(error62,{email:account2.email,expiredSessionIsSignedOut:!0}),scheduleAutoRetry(error62),settledState()}},retry=()=>serialize(async()=>(requestedAt=
8659
- now(),retryWork())),reconcileIfEndpointGone=async({onGone=retryWork}={})=>{let document3=credentials(),account2=storedAccount(document3),access2=managedCompanionTunnelAccess(document3);if(!account2||!access2||!account2.installationCredential||companionAccountCleanupPending(document3)||typeof client.getEndpoint!="function")return null;lastEndpointCheck=now();let endpoint;try{endpoint=await client.getEndpoint(account2.installationCredential)}catch(error62){return error62 instanceof ControlPlaneError&&
8660
- error62.status===401?onGone():null}return endpoint&&endpoint.url===access2.endpoint&&!ENDPOINT_NEEDS_RECONCILE.has(endpoint.status)?null:onGone()},endpointCheckDue=()=>lastEndpointCheck===null||now()-lastEndpointCheck>=endpointCheckIntervalMs/2,recoveryDue=({atLaunch=!1}={})=>{if(!autoRecover||!companionIsOn())return!1;if(atLaunch)return!0;if(autoRetryTimer!==null)return!1;let connection=managedConnectionState?.()??{};return WATCHDOG_CHECK_STATES.has(connection.status)&&endpointCheckDue()},requestedRecently=()=>requestedAt!==
8661
- null&&now()-requestedAt<REQUEST_WINDOW_MS,savedAddress=document3=>normalizeManagedCompanionEndpoint(document3[MANAGED_COMPANION_ENDPOINT_FIELD]),phonesUnused=!1,currentNeed=async()=>{let answer=await Promise.resolve().then(()=>managedEndpointNeeded()).catch(()=>null);phonesUnused=answer===MANAGED_ENDPOINT_PHONES_UNUSED;let need=phonesUnused?!1:typeof answer=="boolean"?answer:null;return need===!1&&relay.capable()?!1:requestedRecently()?(phonesUnused=!1,!0):need},releaseScheduledAt=()=>{let at2=credentials()[MANAGED_COMPANION_RELEASE_AT_FIELD];
8662
- return Number.isSafeInteger(at2)&&at2>0?at2:null},cancelRelease=async()=>{releaseScheduledAt()!==null&&await updateCredentials(document3=>{let next={...document3};return delete next[MANAGED_COMPANION_RELEASE_AT_FIELD],next})},releaseAddress=async()=>{let need=await currentNeed(),pause2=phonesUnused;if(need!==!1)return need&&await cancelRelease(),settledState();let document3=credentials(),account2=storedAccount(document3);if(!account2?.installationCredential||!managedCompanionTunnelAccess(document3)||
8663
- companionAccountCleanupPending(document3))return settledState();await stopManagedEndpoint();let removalPending=!1;try{await client.deleteEndpoint(account2.installationCredential)}catch(error62){if(removalPending=error62 instanceof ControlPlaneError&&error62.code==="endpoint_cleanup_pending",!(removalPending||error62 instanceof ControlPlaneError&&error62.status===401))return writeLog(error62 instanceof ControlPlaneError?`companion account: could not give the secure address back code=${error62.code}\
8664
- status=${error62.status||"none"} ref=${error62.requestId||"none"}`:"companion account: could not give the secure address back (not a control-plane answer)"),await activatePersistedEndpoint(),settledState()}return await updateCredentials(current=>({...withoutManagedCompanionTunnelAccess(current),...removalPending?{[COMPANION_ENDPOINT_RELEASE_PENDING_FIELD]:!0}:{},...pause2?{[COMPANION_ENDPOINT_PAUSED_FIELD]:!0}:{}})),resetAutoRetry(),phase=null,writeLog(pause2?"companion account: gave the secure\
8665
- address back; no paired phone was used in 30 days":"companion account: gave the secure address back; no device needs it"),settledState()},reviewAddress=async({atLaunch=!1}={})=>{let document3=credentials();if(!storedAccount(document3)||companionAccountCleanupPending(document3))return null;if(!managedCompanionTunnelAccess(document3)){let waiting2=document3[COMPANION_TUNNEL_WAITING_FIELD]===!0;return!savedAddress(document3)&&!waiting2||autoRetryTimer!==null?null:await currentNeed()===!0?retryWork():
8666
- null}let need=await currentNeed();if(need!==!1)return need&&await cancelRelease(),need===null&&atLaunch?null:recoveryDue({atLaunch})?reconcileIfEndpointGone():null;let releaseAt=releaseScheduledAt();if(releaseAt!==null&&now()>=releaseAt)return releaseAddress();let connection=managedConnectionState?.()??{};if(companionIsOn()&&CONNECTOR_FAILING_STATES.has(connection.status)&&(atLaunch||endpointCheckDue())){let released=await reconcileIfEndpointGone({onGone:releaseAddress});if(released)return released}
8667
- if(releaseAt===null||releaseAt-now()>releaseGraceMs+releaseJitterMs){let delay3=releaseGraceMs+Math.round(random()*releaseJitterMs);await updateCredentials(current=>managedCompanionTunnelAccess(current)?{...current,[MANAGED_COMPANION_RELEASE_AT_FIELD]:now()+delay3}:current),writeLog(`companion account: no device needs the secure address; giving it back in ${Math.round(delay3/6e4)} min`)}return null},retryIfWanted=async()=>requestedRecently()||savedAddress(credentials())&&await currentNeed()!==!1?
8668
- retryWork():(resetAutoRetry(),phase?.status==="error"&&(phase=null),writeLog("companion account: stopped retrying; no device needs a secure address"),settledState());retryFromTimer=()=>serialize(onDemand?retryIfWanted:retryWork);let prepare=()=>serialize(async()=>{let document3=credentials();if(!configured||!companionIsOn()||!storedAccount(document3)||companionAccountCleanupPending(document3))return settledState();if(requestedAt=now(),!managedCompanionTunnelAccess(document3))return retryWork();await cancelRelease();
8669
- let connection=managedConnectionState?.()??{};if(["stopped","unconfigured"].includes(connection.status))await activatePersistedEndpoint();else if(CONNECTOR_FAILING_STATES.has(connection.status)){let repaired=await reconcileIfEndpointGone();if(repaired)return repaired}return settledState()}),watchdogTick=()=>{if(watchdogTimer=null,!disposed&&(watchdogTimer=setTimer(watchdogTick,endpointCheckIntervalMs),phase?.status!=="signed-out")){if(onDemand){serialize(()=>reviewAddress()).catch(()=>{});return}
8670
- recoveryDue()&&serialize(()=>reconcileIfEndpointGone()).catch(()=>{})}},startWatchdog=()=>{!(autoRecover||onDemand)||disposed||!configured||watchdogTimer!==null||(watchdogTimer=setTimer(watchdogTick,firstEndpointCheckMs))};return Object.freeze({state:async()=>(await probeControlPlane(),settledState()),requestCode,verifyCode,connectLimix,retry,prepare,signOut:()=>serialize(async()=>{if(resetAutoRetry(),!storedAccount(credentials()))return await clearAfterCleanup(),settledState();let email3=storedAccount(
8671
- credentials())?.email;phase={status:"connecting",email:email3};try{return await cleanupCurrentAccount(),healthy=!0,lastHealthCheck=now(),settledState()}catch(error62){return failAction(error62,{email:email3}),settledState()}}),restore:()=>serialize(async()=>{if(!configured||(startWatchdog(),!await probeControlPlane({force:!0})))return settledState();if(companionAccountCleanupPending(credentials()))return retryWork();if(await ensureClientIdentity(),onDemand){let reviewed=await reviewAddress({atLaunch:!0});
8672
- return reviewed||(phase=null,settledState())}let account2=storedAccount(credentials());if(account2&&!managedCompanionTunnelAccess(credentials()))return retryWork();if(autoRecover&&account2&&companionIsOn()){let reconciled=await reconcileIfEndpointGone();if(reconciled)return reconciled}return phase=null,settledState()}),dispose:()=>{disposed=!0,cancelAutoRetry(),watchdogTimer!==null&&clearTimer(watchdogTimer),watchdogTimer=null}})}var SIGNIN_CODE_TTL_MS=10*6e4;init_user_errors();function parseAllowList(value){return(value??"").split(/[,\s]+/).map(entry=>entry.trim().toLowerCase()).filter(Boolean)}var NOT_WELCOME={ok:!1,status:403,...userError("serverError.email.notAllowed")};init_decision_log();init_redact2();init_usage_ledger();import{appendFile as appendFile2,mkdir as mkdir2}from"node:fs/promises";import{basename as basename2,join as join2}from"node:path";var DIR2="admin-activity";var PRUNE_EVERY_MS2=60*6e4,writeQueues2=new Map,lastPrune2=new Map;function adminActivityFileFor(dataDir,at2){return join2(dataDir,DIR2,`${monthKey(at2)}.ndjson`)}async function writeRow(dataDir,row){let at2=new Date(row.at);await mkdir2(join2(dataDir,DIR2),{recursive:!0,mode:448}),await appendFile2(adminActivityFileFor(dataDir,at2),JSON.stringify(row)+`
8657
+ rawEmail,email3=>client.verifyOTP(email3,rawCode)),connectLimix=credential=>signInFlow(credential.email,async()=>({accountToken:credential.bearer,user:{id:credential.userId,email:credential.email}})),forgetForeign=()=>serialize(async()=>{let document3=credentials(),account2=storedAccount(document3);if(!account2)return!1;let access2=managedCompanionTunnelAccess(document3),host2="";try{host2=access2?.endpoint?new URL(access2.endpoint).hostname:""}catch{host2=""}return account2.userId.startsWith("l\
8658
+ imix:")&&(!host2||host2.endsWith(".limixmedia.com"))?!1:(await clearAfterCleanup(),!0)}),retryWork=async()=>{if(!configured)return settledState();let account2=storedAccount(credentials());if(!account2)return phase=null,settledState();if(companionAccountCleanupPending(credentials())){phase={status:"connecting",email:account2.email};try{await requireHealthyControlPlane(),await cleanupCurrentAccount({markPending:!1})}catch(error62){failAction(error62,{email:account2.email,expiredSessionIsSignedOut:!0})}
8659
+ return settledState()}phase={status:"connecting",email:account2.email};try{return await requireHealthyControlPlane(),await provision({accountToken:account2.accountToken,user:{id:account2.userId,email:account2.email}})}catch(error62){return failAction(error62,{email:account2.email,expiredSessionIsSignedOut:!0}),scheduleAutoRetry(error62),settledState()}},retry=()=>serialize(async()=>(requestedAt=now(),retryWork())),reconcileIfEndpointGone=async({onGone=retryWork}={})=>{let document3=credentials(),
8660
+ account2=storedAccount(document3),access2=managedCompanionTunnelAccess(document3);if(!account2||!access2||!account2.installationCredential||companionAccountCleanupPending(document3)||typeof client.getEndpoint!="function")return null;lastEndpointCheck=now();let endpoint;try{endpoint=await client.getEndpoint(account2.installationCredential)}catch(error62){return error62 instanceof ControlPlaneError&&error62.status===401?onGone():null}return endpoint&&endpoint.url===access2.endpoint&&!ENDPOINT_NEEDS_RECONCILE.
8661
+ has(endpoint.status)?null:onGone()},endpointCheckDue=()=>lastEndpointCheck===null||now()-lastEndpointCheck>=endpointCheckIntervalMs/2,recoveryDue=({atLaunch=!1}={})=>{if(!autoRecover||!companionIsOn())return!1;if(atLaunch)return!0;if(autoRetryTimer!==null)return!1;let connection=managedConnectionState?.()??{};return WATCHDOG_CHECK_STATES.has(connection.status)&&endpointCheckDue()},requestedRecently=()=>requestedAt!==null&&now()-requestedAt<REQUEST_WINDOW_MS,savedAddress=document3=>normalizeManagedCompanionEndpoint(
8662
+ document3[MANAGED_COMPANION_ENDPOINT_FIELD]),phonesUnused=!1,currentNeed=async()=>{let answer=await Promise.resolve().then(()=>managedEndpointNeeded()).catch(()=>null);phonesUnused=answer===MANAGED_ENDPOINT_PHONES_UNUSED;let need=phonesUnused?!1:typeof answer=="boolean"?answer:null;return need===!1&&relay.capable()?!1:requestedRecently()?(phonesUnused=!1,!0):need},releaseScheduledAt=()=>{let at2=credentials()[MANAGED_COMPANION_RELEASE_AT_FIELD];return Number.isSafeInteger(at2)&&at2>0?at2:null},cancelRelease=async()=>{
8663
+ releaseScheduledAt()!==null&&await updateCredentials(document3=>{let next={...document3};return delete next[MANAGED_COMPANION_RELEASE_AT_FIELD],next})},releaseAddress=async()=>{let need=await currentNeed(),pause2=phonesUnused;if(need!==!1)return need&&await cancelRelease(),settledState();let document3=credentials(),account2=storedAccount(document3);if(!account2?.installationCredential||!managedCompanionTunnelAccess(document3)||companionAccountCleanupPending(document3))return settledState();await stopManagedEndpoint();
8664
+ let removalPending=!1;try{await client.deleteEndpoint(account2.installationCredential)}catch(error62){if(removalPending=error62 instanceof ControlPlaneError&&error62.code==="endpoint_cleanup_pending",!(removalPending||error62 instanceof ControlPlaneError&&error62.status===401))return writeLog(error62 instanceof ControlPlaneError?`companion account: could not give the secure address back code=${error62.code} status=${error62.status||"none"} ref=${error62.requestId||"none"}`:"companion account: co\
8665
+ uld not give the secure address back (not a control-plane answer)"),await activatePersistedEndpoint(),settledState()}return await updateCredentials(current=>({...withoutManagedCompanionTunnelAccess(current),...removalPending?{[COMPANION_ENDPOINT_RELEASE_PENDING_FIELD]:!0}:{},...pause2?{[COMPANION_ENDPOINT_PAUSED_FIELD]:!0}:{}})),resetAutoRetry(),phase=null,writeLog(pause2?"companion account: gave the secure address back; no paired phone was used in 30 days":"companion account: gave the secure ad\
8666
+ dress back; no device needs it"),settledState()},reviewAddress=async({atLaunch=!1}={})=>{let document3=credentials();if(!storedAccount(document3)||companionAccountCleanupPending(document3))return null;if(!managedCompanionTunnelAccess(document3)){let waiting2=document3[COMPANION_TUNNEL_WAITING_FIELD]===!0;return!savedAddress(document3)&&!waiting2||autoRetryTimer!==null?null:await currentNeed()===!0?retryWork():null}let need=await currentNeed();if(need!==!1)return need&&await cancelRelease(),need===
8667
+ null&&atLaunch?null:recoveryDue({atLaunch})?reconcileIfEndpointGone():null;let releaseAt=releaseScheduledAt();if(releaseAt!==null&&now()>=releaseAt)return releaseAddress();let connection=managedConnectionState?.()??{};if(companionIsOn()&&CONNECTOR_FAILING_STATES.has(connection.status)&&(atLaunch||endpointCheckDue())){let released=await reconcileIfEndpointGone({onGone:releaseAddress});if(released)return released}if(releaseAt===null||releaseAt-now()>releaseGraceMs+releaseJitterMs){let delay3=releaseGraceMs+
8668
+ Math.round(random()*releaseJitterMs);await updateCredentials(current=>managedCompanionTunnelAccess(current)?{...current,[MANAGED_COMPANION_RELEASE_AT_FIELD]:now()+delay3}:current),writeLog(`companion account: no device needs the secure address; giving it back in ${Math.round(delay3/6e4)} min`)}return null},retryIfWanted=async()=>requestedRecently()||savedAddress(credentials())&&await currentNeed()!==!1?retryWork():(resetAutoRetry(),phase?.status==="error"&&(phase=null),writeLog("companion accoun\
8669
+ t: stopped retrying; no device needs a secure address"),settledState());retryFromTimer=()=>serialize(onDemand?retryIfWanted:retryWork);let prepare=()=>serialize(async()=>{let document3=credentials();if(!configured||!companionIsOn()||!storedAccount(document3)||companionAccountCleanupPending(document3))return settledState();if(requestedAt=now(),!managedCompanionTunnelAccess(document3))return retryWork();await cancelRelease();let connection=managedConnectionState?.()??{};if(["stopped","unconfigured"].
8670
+ includes(connection.status))await activatePersistedEndpoint();else if(CONNECTOR_FAILING_STATES.has(connection.status)){let repaired=await reconcileIfEndpointGone();if(repaired)return repaired}return settledState()}),watchdogTick=()=>{if(watchdogTimer=null,!disposed&&(watchdogTimer=setTimer(watchdogTick,endpointCheckIntervalMs),phase?.status!=="signed-out")){if(onDemand){serialize(()=>reviewAddress()).catch(()=>{});return}recoveryDue()&&serialize(()=>reconcileIfEndpointGone()).catch(()=>{})}},startWatchdog=()=>{
8671
+ !(autoRecover||onDemand)||disposed||!configured||watchdogTimer!==null||(watchdogTimer=setTimer(watchdogTick,firstEndpointCheckMs))};return Object.freeze({state:async()=>(await probeControlPlane(),settledState()),requestCode,verifyCode,connectLimix,forgetForeign,retry,prepare,signOut:()=>serialize(async()=>{if(resetAutoRetry(),!storedAccount(credentials()))return await clearAfterCleanup(),settledState();let email3=storedAccount(credentials())?.email;phase={status:"connecting",email:email3};try{return await cleanupCurrentAccount(),
8672
+ healthy=!0,lastHealthCheck=now(),settledState()}catch(error62){return failAction(error62,{email:email3}),settledState()}}),restore:()=>serialize(async()=>{if(!configured||(startWatchdog(),!await probeControlPlane({force:!0})))return settledState();if(companionAccountCleanupPending(credentials()))return retryWork();if(await ensureClientIdentity(),onDemand){let reviewed=await reviewAddress({atLaunch:!0});return reviewed||(phase=null,settledState())}let account2=storedAccount(credentials());if(account2&&
8673
+ !managedCompanionTunnelAccess(credentials()))return retryWork();if(autoRecover&&account2&&companionIsOn()){let reconciled=await reconcileIfEndpointGone();if(reconciled)return reconciled}return phase=null,settledState()}),dispose:()=>{disposed=!0,cancelAutoRetry(),watchdogTimer!==null&&clearTimer(watchdogTimer),watchdogTimer=null}})}var SIGNIN_CODE_TTL_MS=10*6e4;init_user_errors();function parseAllowList(value){return(value??"").split(/[,\s]+/).map(entry=>entry.trim().toLowerCase()).filter(Boolean)}var NOT_WELCOME={ok:!1,status:403,...userError("serverError.email.notAllowed")};init_decision_log();init_redact2();init_usage_ledger();import{appendFile as appendFile2,mkdir as mkdir2}from"node:fs/promises";import{basename as basename2,join as join2}from"node:path";var DIR2="admin-activity";var PRUNE_EVERY_MS2=60*6e4,writeQueues2=new Map,lastPrune2=new Map;function adminActivityFileFor(dataDir,at2){return join2(dataDir,DIR2,`${monthKey(at2)}.ndjson`)}async function writeRow(dataDir,row){let at2=new Date(row.at);await mkdir2(join2(dataDir,DIR2),{recursive:!0,mode:448}),await appendFile2(adminActivityFileFor(dataDir,at2),JSON.stringify(row)+`
8673
8674
  `,{mode:384}),at2.getTime()-(lastPrune2.get(dataDir)??0)>=PRUNE_EVERY_MS2&&(lastPrune2.set(dataDir,at2.getTime()),await pruneMonthFiles(join2(dataDir,DIR2),boundRetentionDays(),at2))}function appendAdminAction(dataDir,row){let record12={...row,at:row.at??new Date().toISOString(),...row.before?{before:auditValues(row.before)}:{},...row.after?{after:auditValues(row.after)}:{}},queued=(writeQueues2.get(dataDir)??Promise.resolve()).then(()=>writeRow(dataDir,record12)).catch(()=>{});writeQueues2.set(
8674
8675
  dataDir,queued),queued.finally(()=>{writeQueues2.get(dataDir)===queued&&writeQueues2.delete(dataDir)})}async function flushAdminActivity(dataDir){await writeQueues2.get(dataDir)}var SECRET_PARTS=["token","secret","password","passwd","passphrase","credential","authorization","cookie","bearer","apikey","api_key"],OPAQUE_MAP=/^(?:headers|env|environment)$/i,MAX_VALUE_CHARS=2e3,HIDDEN="[hidden]";function isSecretName2(name){let lower=name.toLowerCase();return lower.endsWith("tokens")?!1:SECRET_PARTS.some(part=>lower.includes(part))?!0:/(?:^|[_.-])keys?$/.test(lower)||/[a-z]Keys?$/.test(name)}function isSecretParameter(name){
8675
8676
  if(name==="-k"||name==="-K")return!0;let bare=name.replace(/^-+/,"");return isSecretName2(bare)||/^(?:auth|sig|signature|pass|pwd|code)$/i.test(bare)}function keyLikeSegment(segment){return(segment.match(/[A-Za-z0-9]{16,}/g)??[]).some(run6=>/\d/.test(run6)&&/[A-Za-z]/.test(run6))}function maskUrlSecrets(text13){return text13.replace(/\b([a-z][\w+.-]*:\/\/)([^\s/?#]*)([^\s?#]*)([^\s]*)/gi,(_all,scheme,authority2,path3,rest)=>{let at2=authority2.lastIndexOf("@"),host2=at2>=0?`${HIDDEN}@${authority2.
@@ -511,6 +511,7 @@ import { createBotPatchRoutes } from "./routes/bot-patch.js";
511
511
  import { createBotOrderRoutes } from "./routes/bot-order.js";
512
512
  import { createBotLifecycleRoutes } from "./routes/bot-lifecycle.js";
513
513
  import { createBotSkillsRoutes } from "./routes/bot-skills.js";
514
+ import { startHubAutoSync } from "./limix-skill-hub.js";
514
515
  import { createSkillShareRoutes } from "./routes/skill-share.js";
515
516
  import { createTeamContextRoutes } from "./routes/team-context.js";
516
517
  import { createAppSuggestions } from "./host/app-suggestions.js";
@@ -12385,6 +12386,7 @@ ROUTES.push(createBotLifecycleRoutes({
12385
12386
  activeGroupTurnForBot, cancelGroupTurnOperations, runningTurnInstance, closeOpenApprovals, deleteBotWithLifecycle,
12386
12387
  }));
12387
12388
  ROUTES.push(createBotSkillsRoutes({ store, cfg, listBotSkills, readBotSkillFile, stagedSkillListing }));
12389
+ startHubAutoSync();
12388
12390
  ROUTES.push(createEmailRoutes({ service: emailService }));
12389
12391
  ROUTES.push(phones.routes);
12390
12392
  ROUTES.push(createSkillShareRoutes({ enabled: () => skillsHost.mode() !== "off", botExists: (id) => Boolean(store.bot(id)),
@@ -0,0 +1,264 @@
1
+ // Limix Skill Hub client: keeps a cached copy of the hub catalog, installs a skill only after every file matches the
2
+ // SHA-256 the catalog pins, and never installs scripts, binaries or MCP files unless the owner allows tools for that
3
+ // one skill. Installed skills land DISABLED; the owner turns them on. Auto Sync (catalog refresh) is on by default,
4
+ // Auto Install is off by default.
5
+ import { createHash } from "node:crypto";
6
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { isSkillName } from "../shared/skill-md.js";
9
+ import { writeFileAtomic } from "./atomic.js";
10
+ import { DATA_DIR } from "./config.js";
11
+ import { installLibrarySkill, readSkillLibraryIndex, retireLibrarySkill, setLibrarySkillReviewState } from "./skill-library.js";
12
+ export const HUB_REPO = "leomieo/limix-skill-hub";
13
+ const HUB_BRANCH = "main";
14
+ const SYNC_MIN_GAP_MS = 20 * 60 * 60 * 1000;
15
+ const FILE_MAX_BYTES = 1024 * 1024;
16
+ const DEFAULT_SETTINGS = { autoSync: true, autoInstall: false, lastSync: null, lastError: null, etag: null };
17
+ const hubDir = () => join(DATA_DIR, "skill-hub");
18
+ const settingsFile = () => join(hubDir(), "settings.json");
19
+ const catalogFile = () => join(hubDir(), "catalog.json");
20
+ function readJson(file, fallback) {
21
+ try {
22
+ return JSON.parse(readFileSync(file, "utf8"));
23
+ }
24
+ catch {
25
+ return fallback;
26
+ }
27
+ }
28
+ export function hubSettings() {
29
+ return { ...DEFAULT_SETTINGS, ...readJson(settingsFile(), {}) };
30
+ }
31
+ function saveSettings(next) {
32
+ mkdirSync(hubDir(), { recursive: true, mode: 0o700 });
33
+ writeFileAtomic(settingsFile(), JSON.stringify(next, null, 2), { mode: 0o600 });
34
+ }
35
+ export function hubCatalog() {
36
+ const body = readJson(catalogFile(), {});
37
+ return Array.isArray(body.skills) ? body.skills : [];
38
+ }
39
+ const rawUrl = (path) => `https://raw.githubusercontent.com/${HUB_REPO}/${HUB_BRANCH}/${path}`;
40
+ async function fetchText(url, headers = {}) {
41
+ return fetch(url, { signal: AbortSignal.timeout(30_000), headers: { "User-Agent": "limix-bot", ...headers } });
42
+ }
43
+ let syncing = null;
44
+ /** Refreshes the cached catalog (ETag, so an unchanged hub costs one tiny request). */
45
+ export function syncHub() {
46
+ if (syncing)
47
+ return syncing;
48
+ syncing = (async () => {
49
+ const settings = hubSettings();
50
+ try {
51
+ const response = await fetchText(rawUrl("registry/catalog.json"), settings.etag && existsSync(catalogFile()) ? { "If-None-Match": settings.etag } : {});
52
+ if (response.status === 304) {
53
+ saveSettings({ ...settings, lastSync: new Date().toISOString(), lastError: null });
54
+ return { changed: false, count: hubCatalog().length };
55
+ }
56
+ if (!response.ok)
57
+ throw new Error(`Hub trả về ${response.status}`);
58
+ const text = await response.text();
59
+ const parsed = JSON.parse(text);
60
+ if (!Array.isArray(parsed.skills))
61
+ throw new Error("Danh mục Hub không đúng định dạng");
62
+ mkdirSync(hubDir(), { recursive: true, mode: 0o700 });
63
+ writeFileAtomic(catalogFile(), text, { mode: 0o600 });
64
+ saveSettings({ ...settings, etag: response.headers.get("etag"), lastSync: new Date().toISOString(), lastError: null });
65
+ if (settings.autoInstall)
66
+ await autoInstallEligible();
67
+ return { changed: true, count: parsed.skills.length };
68
+ }
69
+ catch (cause) {
70
+ const message = cause instanceof Error ? cause.message : String(cause);
71
+ saveSettings({ ...settings, lastError: message });
72
+ return { error: message };
73
+ }
74
+ finally {
75
+ syncing = null;
76
+ }
77
+ })();
78
+ return syncing;
79
+ }
80
+ const SCRIPT = /\.(py|js|mjs|cjs|ts|sh|bash|zsh|ps1|bat|cmd|rb|pl|php|go|rs|exe|dll|so|dylib|bin|jar|class|scr|msi|com|apk|dex|node|wasm)$/i;
81
+ const MCP_FILE = /(^|\/)(\.mcp\.json|mcp\.json|mcp-servers?\.json|claude_desktop_config\.json)$/i;
82
+ const isToolFile = (path) => SCRIPT.test(path) || MCP_FILE.test(path);
83
+ const sha = (data) => createHash("sha256").update(data).digest("hex");
84
+ export function hubSkillBlocked(entry) {
85
+ if (entry.area === "quarantine" || entry.security_status === "quarantined")
86
+ return "Skill nằm trong khu cách ly vì có mã nguy hiểm.";
87
+ if (entry.approval_status === "quarantined" || entry.approval_status === "rejected")
88
+ return "Skill bị từ chối.";
89
+ return null;
90
+ }
91
+ /** installed state of every catalog entry that came from the hub */
92
+ function installedFromHub() {
93
+ const out = new Map();
94
+ for (const entry of Object.values(readSkillLibraryIndex())) {
95
+ const match = /^limix-hub:([a-z0-9-]+)@([0-9a-f]{64})/.exec(entry.source);
96
+ if (match)
97
+ out.set(match[1], { checksum: match[2], sha256: entry.sha256, reviewState: entry.reviewState });
98
+ }
99
+ return out;
100
+ }
101
+ export function hubSkills() {
102
+ const installed = installedFromHub();
103
+ const library = readSkillLibraryIndex();
104
+ return hubCatalog().map((entry) => {
105
+ const have = installed.get(entry.id);
106
+ return {
107
+ ...entry,
108
+ installed: Boolean(have),
109
+ // A same-named skill the owner imported by hand is not the hub's: never touched by sync.
110
+ enabled: have?.reviewState === "approved",
111
+ updateAvailable: Boolean(have && have.checksum !== entry.checksum),
112
+ blocked: hubSkillBlocked(entry) ?? (!have && library[entry.id] ? "Thư viện đã có kỹ năng cùng tên không đến từ Hub." : null),
113
+ };
114
+ });
115
+ }
116
+ export async function installHubSkill(id, options = {}) {
117
+ if (!isSkillName(id))
118
+ return { error: "Tên kỹ năng không hợp lệ" };
119
+ const entry = hubCatalog().find((e) => e.id === id);
120
+ if (!entry)
121
+ return { error: "Không thấy kỹ năng này trong danh mục Hub. Hãy đồng bộ lại." };
122
+ const blocked = hubSkillBlocked(entry);
123
+ if (blocked)
124
+ return { error: blocked };
125
+ const installed = installedFromHub().get(id);
126
+ const existing = readSkillLibraryIndex()[id];
127
+ if (existing && !installed)
128
+ return { error: "Thư viện đã có kỹ năng cùng tên không đến từ Hub." };
129
+ const wanted = Object.keys(entry.file_hashes).filter((path) => path !== "SKILL.md" && (options.allowTools || !isToolFile(path)));
130
+ const skipped = Object.keys(entry.file_hashes).filter((path) => path !== "SKILL.md" && !wanted.includes(path));
131
+ const files = [];
132
+ let skillMd = "";
133
+ for (const path of ["SKILL.md", ...wanted]) {
134
+ const expected = entry.file_hashes[path];
135
+ if (!expected)
136
+ return { error: `Danh mục thiếu mã kiểm tra cho ${path}` };
137
+ let response;
138
+ try {
139
+ response = await fetchText(rawUrl(`skills/${id}/${path}`));
140
+ }
141
+ catch (cause) {
142
+ return { error: `Không tải được ${path}: ${cause instanceof Error ? cause.message : String(cause)}` };
143
+ }
144
+ if (!response.ok)
145
+ return { error: `Không tải được ${path} (${response.status})` };
146
+ const bytes = new Uint8Array(await response.arrayBuffer());
147
+ if (bytes.byteLength > FILE_MAX_BYTES)
148
+ return { error: `${path} quá lớn` };
149
+ // Pinned content: a file the hub no longer serves as catalogued is refused, never installed.
150
+ if (sha(bytes) !== expected)
151
+ return { error: `${path} không khớp mã SHA-256 trong danh mục — đã dừng cài` };
152
+ if (path === "SKILL.md")
153
+ skillMd = new TextDecoder().decode(bytes);
154
+ else
155
+ files.push({ path, content: bytes });
156
+ }
157
+ if (installed) {
158
+ const retired = retireLibrarySkill(id, installed.sha256);
159
+ if ("error" in retired)
160
+ return { error: retired.error };
161
+ if ("stale" in retired)
162
+ return { error: "Kỹ năng đã bị sửa tay sau khi cài, không cập nhật đè." };
163
+ }
164
+ const result = installLibrarySkill({
165
+ name: id,
166
+ instructions: skillMd,
167
+ source: `limix-hub:${id}@${entry.checksum} (${entry.source.repository ?? "?"}@${(entry.source.commit ?? "").slice(0, 7)})`,
168
+ ...(entry.license ? { license: entry.license } : {}),
169
+ ...(entry.compatibility.text ? { compatibility: entry.compatibility.text } : {}),
170
+ ...(entry.tags.length ? { tags: entry.tags } : {}),
171
+ warnings: entry.notes,
172
+ skippedFiles: skipped,
173
+ reviewState: "disabled",
174
+ ...(files.length ? { files } : {}),
175
+ });
176
+ if ("error" in result)
177
+ return { error: result.error };
178
+ return { ok: true, skipped };
179
+ }
180
+ export function setHubSkillEnabled(id, enabled) {
181
+ if (!installedFromHub().has(id))
182
+ return { error: "Kỹ năng này chưa cài từ Hub" };
183
+ const entry = hubCatalog().find((e) => e.id === id);
184
+ if (enabled && entry && hubSkillBlocked(entry))
185
+ return { error: hubSkillBlocked(entry) };
186
+ const result = setLibrarySkillReviewState(id, enabled ? "approved" : "disabled");
187
+ return "error" in result ? { error: result.error } : { ok: true };
188
+ }
189
+ export function removeHubSkill(id) {
190
+ const installed = installedFromHub().get(id);
191
+ if (!installed)
192
+ return { error: "Kỹ năng này chưa cài từ Hub" };
193
+ const result = retireLibrarySkill(id, installed.sha256);
194
+ if ("error" in result)
195
+ return { error: result.error };
196
+ if ("stale" in result)
197
+ return { error: "Kỹ năng đã bị sửa tay sau khi cài, không gỡ tự động." };
198
+ return { ok: true }; // the bytes stay in the library archive, so a removal can be undone by hand
199
+ }
200
+ /** Auto Install (off by default): only excellent/recommended, approved, instructions-only skills; max 5 per sync. */
201
+ async function autoInstallEligible() {
202
+ let budget = 5;
203
+ for (const skill of hubSkills()) {
204
+ if (budget <= 0)
205
+ return;
206
+ const eligible = !skill.blocked && skill.kind === "instructions-only" && skill.approval_status === "approved" && (skill.grade === "excellent" || skill.grade === "recommended");
207
+ if (!eligible || (skill.installed && !skill.updateAvailable))
208
+ continue;
209
+ budget -= 1;
210
+ const done = await installHubSkill(skill.id);
211
+ if ("ok" in done && skill.updateAvailable && skill.enabled)
212
+ setHubSkillEnabled(skill.id, true);
213
+ }
214
+ }
215
+ export async function handleLimixHub(method, path, body) {
216
+ const record = (body && typeof body === "object" ? body : {});
217
+ if (method === "GET" && path === "/api/limix/hub") {
218
+ return { status: 200, body: { repo: HUB_REPO, settings: hubSettings(), skills: hubSkills() } };
219
+ }
220
+ if (method === "POST" && path === "/api/limix/hub/sync") {
221
+ const result = await syncHub();
222
+ return "error" in result ? { status: 502, body: result } : { status: 200, body: result };
223
+ }
224
+ if (method === "POST" && path === "/api/limix/hub/settings") {
225
+ const current = hubSettings();
226
+ saveSettings({
227
+ ...current,
228
+ autoSync: typeof record.autoSync === "boolean" ? record.autoSync : current.autoSync,
229
+ autoInstall: typeof record.autoInstall === "boolean" ? record.autoInstall : current.autoInstall,
230
+ });
231
+ return { status: 200, body: { settings: hubSettings() } };
232
+ }
233
+ const id = typeof record.id === "string" ? record.id : "";
234
+ if (method === "POST" && (path === "/api/limix/hub/install" || path === "/api/limix/hub/update")) {
235
+ const result = await installHubSkill(id, { allowTools: record.allowTools === true });
236
+ return "error" in result ? { status: 422, body: result } : { status: 200, body: result };
237
+ }
238
+ if (method === "POST" && path === "/api/limix/hub/enable") {
239
+ const result = setHubSkillEnabled(id, record.enabled === true);
240
+ return "error" in result ? { status: 422, body: result } : { status: 200, body: result };
241
+ }
242
+ if (method === "POST" && path === "/api/limix/hub/remove") {
243
+ const result = removeHubSkill(id);
244
+ return "error" in result ? { status: 422, body: result } : { status: 200, body: result };
245
+ }
246
+ return null;
247
+ }
248
+ let started = false;
249
+ /** Auto Sync: once soon after start, then checked every 30 minutes; it only runs when 20 h have passed. */
250
+ export function startHubAutoSync() {
251
+ if (started)
252
+ return;
253
+ started = true;
254
+ const tick = () => {
255
+ const settings = hubSettings();
256
+ if (!settings.autoSync)
257
+ return;
258
+ if (settings.lastSync && Date.now() - Date.parse(settings.lastSync) < SYNC_MIN_GAP_MS)
259
+ return;
260
+ void syncHub();
261
+ };
262
+ setTimeout(tick, 60_000).unref();
263
+ setInterval(tick, 30 * 60 * 1000).unref();
264
+ }
@@ -4,6 +4,8 @@ import { setNativeSkillEnabled } from "../native-skill-choices.js";
4
4
  import { botSkillTemplateSchema } from "../new-bot-defaults.js";
5
5
  import { fetchSkillFromSource } from "../skill-fetch.js";
6
6
  import { readLocalSkills } from "../limix-skill-folder.js";
7
+ import { loadBundledSkills } from "../skill-library.js";
8
+ import { handleLimixHub } from "../limix-skill-hub.js";
7
9
  import { skillImportSummary } from "../skill-folder.js";
8
10
  import { installLibrarySkill, librarySkillOptOuts, listLibrarySkills, readLibrarySkillFile, readSkillLibraryIndex, setLibrarySkillEveryBot, setLibrarySkillOptOut, setLibrarySkillReviewState, skillsLibraryEnabled, unassignLibrarySkill } from "../skill-library.js";
9
11
  import { installSkill, listBotSkillsWithOrigin, listStagedSkillWrites, parseSkillMd, removeSkill, scanSkillText, setSkillEnabled, } from "../skills.js";
@@ -12,6 +14,15 @@ export function createBotSkillsRoutes(deps) {
12
14
  const { store, cfg, listBotSkills, readBotSkillFile, stagedSkillListing } = deps;
13
15
  return async ({ req, res, url, path, method, json, readBody }) => {
14
16
  let m = null;
17
+ if (path.startsWith("/api/limix/hub")) {
18
+ const hub = await handleLimixHub(method, path, method === "POST" ? await readBody(req) : undefined);
19
+ if (hub)
20
+ return json(res, hub.status, hub.body);
21
+ }
22
+ if (method === "GET" && path === "/api/limix/bundled-skills") {
23
+ const skills = loadBundledSkills().map(({ manifest }) => ({ id: manifest.id, name: manifest.name, description: manifest.description, defaultEnabled: manifest.defaultEnabled, triggerTerms: manifest.triggerTerms }));
24
+ return json(res, 200, { skills });
25
+ }
15
26
  // Imports land DISABLED: a person enables one after reading SKILL.md and its scan warnings.
16
27
  m = path.match(/^\/api\/bots\/([\w-]+)\/skill-template$/);
17
28
  if (m && method === "POST") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "limixbot",
3
- "version": "0.3.12",
3
+ "version": "0.3.15",
4
4
  "description": "Run the Limix Bot server anywhere and pair your devices to it (Leox ৹ Limix AI)",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",