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.
- package/dist/assets/{ActivityPanel-CJM61CWQ.js → ActivityPanel-CrqSA80q.js} +1 -1
- package/dist/assets/{App-DRnqqiJP.js → App-Bx6hl9Sg.js} +129 -129
- package/dist/assets/{AppEventEditor-1hupO_kL.js → AppEventEditor-DLfkpP1d.js} +1 -1
- package/dist/assets/{BotSettingsDialog-B56-kmya.js → BotSettingsDialog-rHcw4pr0.js} +1 -1
- package/dist/assets/{BrowserPanel-BXBygd1y.js → BrowserPanel-BgNsYU3s.js} +1 -1
- package/dist/assets/{BrowserProfilesManager-CzzGhZGz.js → BrowserProfilesManager-ByQtDUyM.js} +1 -1
- package/dist/assets/{CloudAddDialog-MMhMrVK3.js → CloudAddDialog-754PKKwo.js} +1 -1
- package/dist/assets/{ComputerFilesPane-Hpxjnk2e.js → ComputerFilesPane-IP5ib-4w.js} +1 -1
- package/dist/assets/{ComputerPanel-CMce64RT.js → ComputerPanel-CndWeumv.js} +2 -2
- package/dist/assets/{DataPanel-BQ7Ua1Oq.js → DataPanel-BWlfa8L7.js} +1 -1
- package/dist/assets/{DesktopViewer-BRzNtGEm.js → DesktopViewer-tgbnWJ7S.js} +1 -1
- package/dist/assets/{FilesView-DQYXl0zm.js → FilesView-PiPhchvH.js} +1 -1
- package/dist/assets/{GrokMoveDialog-Dk-pwczv.js → GrokMoveDialog-DGBkFh2_.js} +1 -1
- package/dist/assets/{InspectorPanel-CvnM0TFW.js → InspectorPanel-DMb9KlS1.js} +1 -1
- package/dist/assets/{KeyboardShortcutsModal-BQ8aAw16.js → KeyboardShortcutsModal-DcAnacT0.js} +1 -1
- package/dist/assets/{LiveDesktop-Cr7uAFoQ.js → LiveDesktop-pNuAQp7j.js} +1 -1
- package/dist/assets/{LocalVmWorkspace-BJ-i68MR.js → LocalVmWorkspace-rUDRW4sg.js} +1 -1
- package/dist/assets/{MacLocalControl-DE8MVZkU.js → MacLocalControl-D2_QV-or.js} +1 -1
- package/dist/assets/{MarkdownFilePreview-B_6azXNT.js → MarkdownFilePreview-DNNn-HhY.js} +1 -1
- package/dist/assets/{MenuMotion-BDYr4RxL.js → MenuMotion-CIhkQGNX.js} +1 -1
- package/dist/assets/{NewBotDialog-D9fUysLU.js → NewBotDialog-C1onlYty.js} +1 -1
- package/dist/assets/{PhoneAppDialog-DUv4Qx8n.js → PhoneAppDialog-DygQ7EJ1.js} +1 -1
- package/dist/assets/{PlacesRail-yZdo57pP.js → PlacesRail-DC3DtwQy.js} +1 -1
- package/dist/assets/{PluginsPanel-DYuWdWZd.js → PluginsPanel-BZv6B1JK.js} +1 -1
- package/dist/assets/{ProIntroduction-DapZkP7I.js → ProIntroduction-DeMFeFxH.js} +1 -1
- package/dist/assets/{RemoteAgentSettingsPanel-sNv3TbBd.js → RemoteAgentSettingsPanel-DyVQB35x.js} +1 -1
- package/dist/assets/{RoomPanel-DrlyOJKZ.js → RoomPanel-DEH49als.js} +1 -1
- package/dist/assets/{RoutinesPage-CIo30TVq.js → RoutinesPage-CRI83T_I.js} +1 -1
- package/dist/assets/SettingsModal-Dcdk0Qvv.js +11 -0
- package/dist/assets/{ShareTeamDialog-CH1ts7Uj.js → ShareTeamDialog-D5ML8qDY.js} +1 -1
- package/dist/assets/{SignedInSites-CisTAQW0.js → SignedInSites-Bjc69Ty-.js} +1 -1
- package/dist/assets/{TeamLibraryPanel-DvnKye05.js → TeamLibraryPanel-CVcmOn6k.js} +1 -1
- package/dist/assets/{TeamMapPage-JOiilgs0.js → TeamMapPage-C3-Cu90W.js} +1 -1
- package/dist/assets/{TriggersPage-DQ3HQ6_i.js → TriggersPage-DS2Ceion.js} +1 -1
- package/dist/assets/{VisibilitySection-DXfPp0Ql.js → VisibilitySection-DigpYJ5s.js} +1 -1
- package/dist/assets/{WebhookTrigger-BYeOyOpX.js → WebhookTrigger-BP28l-Jy.js} +1 -1
- package/dist/assets/{WorksOnGrid-8fCnUzpQ.js → WorksOnGrid-dVy5M1Fj.js} +1 -1
- package/dist/assets/{abnfDiagram-YUKMZFIV-Boj-Bkbs.js → abnfDiagram-YUKMZFIV-DE2rjoCc.js} +1 -1
- package/dist/assets/{activity-MpAxS_kw.js → activity-8Dgeq15H.js} +1 -1
- package/dist/assets/{architectureDiagram-47P4ROYG-Cl-ocqce.js → architectureDiagram-47P4ROYG-DrIn6OKQ.js} +1 -1
- package/dist/assets/{blockDiagram-E7TT5TSB-0nSQiv6g.js → blockDiagram-E7TT5TSB-BQdVPMCR.js} +1 -1
- package/dist/assets/{c4Diagram-CRA5TL53-Cx9iymkb.js → c4Diagram-CRA5TL53-DFb8MJmm.js} +1 -1
- package/dist/assets/{calendar-days-DXHDRm_4.js → calendar-days-Bs4Es4HT.js} +1 -1
- package/dist/assets/channel-1lG6Uyuf.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-0rIrumUG.js → chunk-2Q5K7J3B-BXaLRTO9.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-w9e6iKvs.js → chunk-5VM5RSS4-Cie3uAC2.js} +1 -1
- package/dist/assets/{chunk-7TKQ45FW-c4KxVKkf.js → chunk-7TKQ45FW-Bw5vFHu9.js} +1 -1
- package/dist/assets/{chunk-AW2ZBBNX-DmaBcuOP.js → chunk-AW2ZBBNX-CkxRWzwT.js} +1 -1
- package/dist/assets/{chunk-FVRAUYC3-BLzfqLHR.js → chunk-FVRAUYC3-7FMS_2yS.js} +2 -2
- package/dist/assets/{chunk-HTAEGDNF-BN48XtBm.js → chunk-HTAEGDNF-DQ_kiZYj.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-D2NACa6i.js → chunk-JWPE2WC7-DD4AjX_J.js} +1 -1
- package/dist/assets/{chunk-KQW6MTUR-CbAfnxxG.js → chunk-KQW6MTUR-DAs7Un1F.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-Bwg-DPP_.js → chunk-XXDRQBXY-qEAG8aNp.js} +1 -1
- package/dist/assets/{classDiagram-v2-K4WV4PDN-Dh3ohoZp.js → classDiagram-v2-K4WV4PDN-SFDos9_W.js} +1 -1
- package/dist/assets/{coins-Bv8-qll4.js → coins-ni7p9MMO.js} +1 -1
- package/dist/assets/{core-DeJAGBXR.js → core-CMus8yJ9.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-DCHadn0L.js → cose-bilkent-JH36ORCC-BIvnumq9.js} +1 -1
- package/dist/assets/{cynefin-EF2NZ3EQ-ClpPPxUg.js → cynefin-EF2NZ3EQ-DEyZ973A.js} +1 -1
- package/dist/assets/{cynefinDiagram-3GCD6N5R-DZ5l7nkL.js → cynefinDiagram-3GCD6N5R-DIH2LA6q.js} +1 -1
- package/dist/assets/{dagre-W4DXFKR2-BagTvT62.js → dagre-W4DXFKR2-doItNion.js} +1 -1
- package/dist/assets/{diagram-2UJZ2QOL-DuByootb.js → diagram-2UJZ2QOL-DKfcZ3Fe.js} +1 -1
- package/dist/assets/{diagram-OVF4WLC6-BQ8Cvr0D.js → diagram-OVF4WLC6-BRnKWMs5.js} +1 -1
- package/dist/assets/{diagram-PFPMY2P6-D_pklaf9.js → diagram-PFPMY2P6-C3aLszxc.js} +1 -1
- package/dist/assets/{diagram-UMYRVEAY-1BuHq7tR.js → diagram-UMYRVEAY-DbMKM9Kw.js} +1 -1
- package/dist/assets/{diagram-YEKJPTXX-CK0cPx8n.js → diagram-YEKJPTXX-BqrhdTTX.js} +1 -1
- package/dist/assets/{diagram-ZIFT7M5P-DVzPho45.js → diagram-ZIFT7M5P-CYnUFm0_.js} +1 -1
- package/dist/assets/{ebnfDiagram-VR2GEFS7-D8UO_5sE.js → ebnfDiagram-VR2GEFS7-CnuOWBxY.js} +1 -1
- package/dist/assets/{elk-IJKZMXRS-BRigfR5V.js → elk-IJKZMXRS-BmtbqAAs.js} +1 -1
- package/dist/assets/{erDiagram-O2IAPWRE-CXHPobWj.js → erDiagram-O2IAPWRE-CL6oNmZD.js} +1 -1
- package/dist/assets/{file-up-CQezfdJk.js → file-up-DT7t5NXT.js} +1 -1
- package/dist/assets/{flowDiagram-OXPTDLAJ-CqKD6jh8.js → flowDiagram-OXPTDLAJ-CE9hfMoK.js} +1 -1
- package/dist/assets/{ganttDiagram-R7TSEDQI-C9FV3gsA.js → ganttDiagram-R7TSEDQI-DRU_A2cB.js} +1 -1
- package/dist/assets/{gitGraphDiagram-XJZIOB7I-CRu4dnr_.js → gitGraphDiagram-XJZIOB7I-BPkWYlKZ.js} +1 -1
- package/dist/assets/{import-name-dEG0japD.js → import-name-DutQXK90.js} +1 -1
- package/dist/assets/{index-COnd2OMZ.js → index-C6OVbzeZ.js} +2 -2
- package/dist/assets/{index-B4HW_T-k.js → index-CiYcp06m.js} +1 -1
- package/dist/assets/{infoDiagram-5W2HQ5XZ-CCRjb2CQ.js → infoDiagram-5W2HQ5XZ-C4SBhDtT.js} +1 -1
- package/dist/assets/{ishikawaDiagram-K3B6WC7H-TaNPsGZS.js → ishikawaDiagram-K3B6WC7H-3TlhKRsV.js} +1 -1
- package/dist/assets/{journeyDiagram-COXZFDF6-Dz7YIIc2.js → journeyDiagram-COXZFDF6-K9dyCbpb.js} +1 -1
- package/dist/assets/{kanban-definition-P3RFRI5V-Cg69Fpp8.js → kanban-definition-P3RFRI5V-Df8i0B0H.js} +1 -1
- package/dist/assets/{mcp-servers-Ih6dI7Ga.js → mcp-servers-xuxWmmEV.js} +1 -1
- package/dist/assets/{mermaid.core-DiG3LoLz.js → mermaid.core-Dj_sBsXx.js} +4 -4
- package/dist/assets/{mindmap-definition-LZFPQGKD-nxP4f0fq.js → mindmap-definition-LZFPQGKD-CuBxqBw1.js} +1 -1
- package/dist/assets/{minimize-B_k_fmmo.js → minimize-CuDlETLH.js} +1 -1
- package/dist/assets/{monitor-BfmnPU-I.js → monitor-DqFADU6d.js} +1 -1
- package/dist/assets/{new-bot-CQm0FPc5.js → new-bot-DoJRfwOC.js} +1 -1
- package/dist/assets/{org-library-C2li0IRL.js → org-library-B7nhJQxt.js} +1 -1
- package/dist/assets/{package-format-B3whu9DW.js → package-format-FvzPr9Xd.js} +1 -1
- package/dist/assets/{pegDiagram-BGZESJAR-gPjpGcNH.js → pegDiagram-BGZESJAR-BDWRqrLr.js} +1 -1
- package/dist/assets/{pieDiagram-CAPJLFHJ-Cgh25Z4_.js → pieDiagram-CAPJLFHJ-BByTBeRn.js} +1 -1
- package/dist/assets/{quadrantDiagram-GDMTTRAM-DD9luyqA.js → quadrantDiagram-GDMTTRAM-D0SI7j3Y.js} +1 -1
- package/dist/assets/{rail-B1mj4zlo.js → rail-BEOIK49V.js} +1 -1
- package/dist/assets/{railroadDiagram-SM67HX2B-CS_TYko7.js → railroadDiagram-SM67HX2B-Dz3tUGbx.js} +1 -1
- package/dist/assets/{remote-desktop-panel-DIllbP7i.js → remote-desktop-panel-DgZionIJ.js} +1 -1
- package/dist/assets/{requirementDiagram-X7JNWC4A-BePHia6E.js → requirementDiagram-X7JNWC4A-DrwqW7OV.js} +1 -1
- package/dist/assets/{sankeyDiagram-UM26HJRW-24ExCXGS.js → sankeyDiagram-UM26HJRW-95WqcM7l.js} +1 -1
- package/dist/assets/{sequenceDiagram-ZO4K6R2Y-Dtmx27I5.js → sequenceDiagram-ZO4K6R2Y-1zoh2EeE.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-2LvmoT1T.js → sizeCapture-INFHLROL-IzBNvZN8.js} +1 -1
- package/dist/assets/{stateDiagram-v2-TBUQTH76-moL2SIo9.js → stateDiagram-v2-TBUQTH76-CXdapkBV.js} +1 -1
- package/dist/assets/styles-CSArda2J.css +1 -0
- package/dist/assets/{swimlanes-N4OXWK64-D95Pk8qh.js → swimlanes-N4OXWK64-QK7_rFPB.js} +1 -1
- package/dist/assets/swimlanesDiagram-K3J5GTZL-B4Zj1xuq.js +8 -0
- package/dist/assets/{team-share-0GXKeixY.js → team-share-NJEAQGUX.js} +1 -1
- package/dist/assets/{timeline-definition-YOQAKGHF-HM-J25k2.js → timeline-definition-YOQAKGHF-MhSu-mD6.js} +1 -1
- package/dist/assets/{usecaseDiagram-VIAY4XPW-C6q6bCzJ.js → usecaseDiagram-VIAY4XPW-DdZIcDLN.js} +1 -1
- package/dist/assets/{vennDiagram-BLWOH2XV-hf0dh2yT.js → vennDiagram-BLWOH2XV-DDMi9wJz.js} +1 -1
- package/dist/assets/{wardleyDiagram-YMQ3BMBF-CpT_eRn9.js → wardleyDiagram-YMQ3BMBF-DrZpbJKY.js} +1 -1
- package/dist/assets/{xychartDiagram-TAQBALBS-D6g1V9DG.js → xychartDiagram-TAQBALBS-__UkZCKS.js} +1 -1
- package/dist/index.html +3 -3
- package/dist/rail.html +5 -5
- package/dist-server/index.js +307 -289
- package/dist-server/openmausbot.js +17 -16
- package/dist-server/package.json +1 -1
- package/dist-server/pair-cli.js +17 -16
- package/dist-server/server/index.js +2 -0
- package/dist-server/server/limix-skill-hub.js +264 -0
- package/dist-server/server/routes/bot-skills.js +11 -0
- package/package.json +1 -1
- package/dist/assets/SettingsModal-BpJhi2nc.js +0 -11
- package/dist/assets/channel-BStWWrSt.js +0 -1
- package/dist/assets/styles-bUbK33f1.css +0 -1
- package/dist/assets/swimlanesDiagram-K3J5GTZL-DByi9G3i.js +0 -8
- /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}})),
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
null
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
address back
|
|
8667
|
-
|
|
8668
|
-
if(releaseAt
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
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.
|
package/dist-server/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"module","version":"0.3.
|
|
1
|
+
{"type":"module","version":"0.3.15"}
|
package/dist-server/pair-cli.js
CHANGED
|
@@ -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}})),
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
null
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
address back
|
|
8666
|
-
|
|
8667
|
-
if(releaseAt
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
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") {
|