chainlesschain 0.162.149 → 0.162.150
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/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-BJif14yR.js → AIOps-CBLvrjeG.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CXek6gJY.js → ActionButton-DYv2GtE0.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BgQhdAJi.js → Analytics-CL6PZ-Ww.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DTYl5NtR.js → AppLayout-xuyHt4uA.js} +3 -3
- package/src/assets/web-panel/assets/{Audit-BzATQAeR.js → Audit-DOvSVycq.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BzdPEQhL.js → Backup-DMhfTswr.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BM0KVh8E.js → BaseInput-CQxDdm63.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-D7fHXHkz.js → Chat-BEeNhK2a.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-Wm34RR-b.js → ChatBubbleRenderer-8nul8eTq.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-CAiSQ9vO.js → Checkbox-CL04O-ER.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Df40CrEe.js → Codegen-C50o-n7x.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DIT2fNFU.js → Col-CH-bDkzs.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CSTIbW2k.js → Community-DqAIMvSe.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DtqXZZUi.js → Compact-C43fo_my.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CUtzw6o7.js → Compliance-BERnMrdP.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CezmlnmU.js → Cowork-Baw3w1gp.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-P4BITarg.js → Cron-BEqAHF1-.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BRRiYwvq.js → Crosschain-CvnmKwK4.js} +1 -1
- package/src/assets/web-panel/assets/{DID-L5ijB9i4.js → DID-iMPxgVdt.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CFxro4ob.js → Dashboard--4SJX3YR.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BmAFCQ71.js → Dropdown-BgLV6xgz.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DJBCEuon.js → EmailListRenderer-Bvb8oUD8.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-z4oLq6eT.js → FamilyGuardDashboard-DpXPjJB5.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DsVCpHMF.js → Federation-BbFfJ1Xi.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DP3bP5th.js → FormItemContext-DeGq1B-q.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BAXP2Gc6.js → GenericCardRenderer-CNW7s9zM.js} +1 -1
- package/src/assets/web-panel/assets/{Git-I3g_bAw9.js → Git-L1PjW-lT.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-jHS5-ioS.js → Governance-PK-X58to.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CPL7sfx9.js → Inference-BePWEH-d.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Jf236h4C.js → KnowledgeGraph-LF_sBvdL.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Cm2tcSKg.js → Logs-BKkfk9hy.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CZK82rhi.js → Marketplace-BNMqUDla.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CZCeji7a.js → McpTools-w4WPiyz2.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-3l2KVS9k.js → Memory-DUKMVGNi.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-VYdpoLh6.js → MobileBridge-BWhiEyTJ.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CiB9cmm1.js → MobileProjects-Ocf5JEkX.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-wCFZbBuL.js → Mtc-D2B6MMhU.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-1a3THIyG.js → MtcAudit-DdYjCq1O.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DMzLB2hG.js → Multisig-Cb1hfuUZ.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-D9wZbf6l.js → NLProgramming-CTeImGp7.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-hrFe2ZM-.js → Notes-BjYNc7eQ.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-CHGX5Jwm.js → NotificationSettings-Dn_NtRXQ.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-DVYYIizh.js +1 -0
- package/src/assets/web-panel/assets/{Organization-By7FkhtY.js → Organization-Be2Kmr5j.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow--khGSzJn.js → Overflow-Cu_-qRlR.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CAG9dH35.js → P2P-DD2HoGzE.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-B3SQZmK9.js → PdhVaultBrowser-BRI7DVth.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-D35ec6JA.js → Permissions-C9Srcs2M.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-CxBF7hW_.js → PersonalDataHub-CUqcMgo1.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-s6EtOO1s.js → Pipeline-HBrQGLXt.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BbJYmWmO.js → Privacy-D-B0vjwh.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-VGQq1jMT.js → ProjectInit-eTeLIu9e.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-B1ew3Teh.js → ProjectSettings-DrRqCsC6.js} +2 -2
- package/src/assets/web-panel/assets/Projects-1G9DNOhI.js +1 -0
- package/src/assets/web-panel/assets/{Providers-mrO47FIK.js → Providers-RkTZzpxP.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-IJF2mHyw.js → QrScannerModal-BSCUgsgC.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CU0wN_Z0.js → QuickAsk-CHgyM3Bz.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DhN37R6G.js → Recommend-0lry4OZq.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-D_xOX_hu.js → RemoteSession-Cn45UroZ.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-D_Ssv7uH.js → Reputation-Q-Zjb707.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Kx5dKxX7.js → Row-BdM70oda.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BlGwqZkY.js → RssFeed-DmOAKKap.js} +2 -2
- package/src/assets/web-panel/assets/{Search-hLFQzxpb.js → Search-Dwavbm07.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DMlvsVt4.js → Security-DukXFCnk.js} +3 -3
- package/src/assets/web-panel/assets/{Services-Bd0Qsj2m.js → Services-BIlRnITu.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DFclq9X3.js → Skeleton-YYVQdhhQ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BNtln2qY.js → Skills-B8_iYHz2.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Dch5H19G.js → Sla-BdVrhT31.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-2UfQcU1g.js → SpeechSettings-CD3ggRx7.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CD9YQr4i.js → SyncSettings-Bp02VZFH.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BNu-7MzI.js → Tasks-DEVmCEsr.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-D1n9CaIR.js → Templates-B6czWc4N.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DenL3iBW.js → Tenant-BCJLv17r.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-ZzU0Io1Y.js → Terminal-CD132d2Y.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-D87IfukO.js → TimelineRenderer-ZB-CvkZl.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BkiZc8E3.js → Tokens-BnSY0S-s.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BNRSytGN.js → Trigger-VZw9Oy2w.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Bwbd4X2m.js → Trust-pZq7Hjd2.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B8O2bs0o.js → UkeySign-DnQaqk4q.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C2fL8zmH.js → VideoEditing-BEUfmvJV.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BORYDdE1.js → Wallet-CoZKMXJ2.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C0V5S2FM.js → WebAuthn-CBOGVx1I.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-2dR0fcHL.js → WorkflowEditor-huSKn7TT.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BmuAFAn8.js → chat-A_3w6FBO.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CN3smMcK.js → colors-D1Bem9Oy.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DCnxb4kW.js → compact-item-CJYJj0oO.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-vlR43pHn.js → createContext-BkbFiKT4.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-WDr3_M_N.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D04tHYFd.js → hasIn-CKvUafVY.js} +1 -1
- package/src/assets/web-panel/assets/{index-0DnaZGkC.js → index-AjrSPnvv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BudvKQfG.js → index-B8i0lViZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BeGDEXFs.js → index-B9Svn0zc.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5LZVZ0L.js → index-BHdHSX06.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTPEZTk1.js → index-BHdailyo.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIpY0qM5.js → index-BJwlEnYo.js} +1 -1
- package/src/assets/web-panel/assets/{index-CsAsCPJ6.js → index-BOoAQjJz.js} +1 -1
- package/src/assets/web-panel/assets/{index-ljuBiQW9.js → index-BSgWxAOG.js} +1 -1
- package/src/assets/web-panel/assets/{index-hpvvtAZ3.js → index-BdutMsne.js} +1 -1
- package/src/assets/web-panel/assets/{index-_FIkN3jd.js → index-BiDWxzbn.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-6NO5il.js → index-By5a0T_W.js} +1 -1
- package/src/assets/web-panel/assets/{index-B78xL3s2.js → index-C-WIY-FQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-ClhAHDK3.js → index-C37f6iey.js} +1 -1
- package/src/assets/web-panel/assets/{index-ToUh2b1-.js → index-C9bWmTcO.js} +1 -1
- package/src/assets/web-panel/assets/{index-D28DeAAB.js → index-CBaosWRO.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8vwp4qp.js → index-CBlBVJ_m.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqhASEL4.js → index-CD62EEGo.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDwLgQY9.js → index-CUj-l7GM.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbVsQBOH.js → index-CVS8Ymuq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cr-A0FYJ.js → index-ChkXUj3f.js} +1 -1
- package/src/assets/web-panel/assets/{index-D65_XseV.js → index-Cj0OZ-37.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXRg8GFQ.js → index-Co3OFL0t.js} +1 -1
- package/src/assets/web-panel/assets/{index-430S68MN.js → index-Crp8CbRg.js} +1 -1
- package/src/assets/web-panel/assets/{index-CngCC8hC.js → index-D22zNXiS.js} +1 -1
- package/src/assets/web-panel/assets/{index-HhNjnCfe.js → index-DTs_D1OL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CP9m9Ggr.js → index-DfgCdFrN.js} +3 -3
- package/src/assets/web-panel/assets/{index-jzR7Ujuw.js → index-DjYrecNb.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTOCnIQ7.js → index-DpW9cf3e.js} +1 -1
- package/src/assets/web-panel/assets/index-Dwix3p4g.js +1 -0
- package/src/assets/web-panel/assets/{index-D_n8_vfI.js → index-F--HuPG1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BasvsWDM.js → index-Fuk6t_3K.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnXebZLL.js → index-JEAGMii9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYAtmqiv.js → index-PF-mpv8K.js} +1 -1
- package/src/assets/web-panel/assets/index-SHaoZ0CA.js +1 -0
- package/src/assets/web-panel/assets/{index-DTWg-18U.js → index-b-sbVdjQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5Sxb1sE.js → index-pyvMVX8r.js} +1 -1
- package/src/assets/web-panel/assets/{index-TslMTwNy.js → index-qNTtOVi_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTtRscUa.js → index-tgO8iza6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbADHwhr.js → index-ttDQVhZy.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C3wUb4je.js → initDefaultProps-DtrnfsZH.js} +1 -1
- package/src/assets/web-panel/assets/{motion-D-jYFUNA.js → motion-D4zqSaX3.js} +1 -1
- package/src/assets/web-panel/assets/{move-tqb8nwJ2.js → move-CGFGOUQj.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DSOjMJMz.js → mtc-parser-DH2HvkJl.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CJ7VimIW.js → omit-jJ2at5HX.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-qXiG1iT3.js → pickAttrs-CRyMT1Fv.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CkGBcy1Z.js → placementArrow-DZ_CX0Pt.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-D3WvTlLO.js → responsiveObserve-wruHk-h5.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BEE2ftge.js → slide-BGgx8q67.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CsfBpxiG.js → statusUtils-Do5M7gKm.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-xPrIiJPI.js → styleChecker-CRa-GZpX.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-C5Yd-SCR.js → useFlexGapSupport-BxPcq6pj.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-D_MDS8HI.js → useFs-v5O2ZcJm.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BqJh-usA.js → usePersonalDataHub--z2FU0Px.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-chm_p-gz.js → vnode-1emidVKd.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-3-YDNz0Z.js → zoom-CcJaRyHs.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/a2a.js +19 -4
- package/src/commands/activitypub.js +20 -3
- package/src/commands/agent.js +27 -5
- package/src/commands/audit.js +40 -24
- package/src/commands/codeintel.js +68 -0
- package/src/commands/collab.js +15 -2
- package/src/commands/compliance.js +5 -0
- package/src/commands/config.js +4 -1
- package/src/commands/dao.js +86 -14
- package/src/commands/dev.js +2 -0
- package/src/commands/did.js +7 -1
- package/src/commands/dlp.js +23 -1
- package/src/commands/economy.js +29 -3
- package/src/commands/eval.js +7 -0
- package/src/commands/evomap.js +26 -0
- package/src/commands/governance.js +17 -3
- package/src/commands/hardening.js +18 -0
- package/src/commands/incentive.js +5 -0
- package/src/commands/init.js +46 -2
- package/src/commands/kg.js +4 -0
- package/src/commands/loop.js +6 -1
- package/src/commands/lowcode.js +15 -2
- package/src/commands/marketplace.js +40 -6
- package/src/commands/matrix.js +20 -1
- package/src/commands/memory.js +4 -1
- package/src/commands/mtc.js +7 -1
- package/src/commands/nostr.js +31 -4
- package/src/commands/note.js +7 -4
- package/src/commands/plugin.js +125 -5
- package/src/commands/pqc.js +5 -0
- package/src/commands/reputation.js +10 -1
- package/src/commands/scim.js +6 -0
- package/src/commands/session.js +32 -10
- package/src/commands/siem.js +11 -0
- package/src/commands/sla.js +18 -3
- package/src/commands/social.js +38 -5
- package/src/commands/sso.js +10 -2
- package/src/commands/stress.js +23 -4
- package/src/commands/team.js +9 -2
- package/src/commands/tech.js +2 -0
- package/src/commands/tenant.js +10 -1
- package/src/commands/terraform.js +6 -0
- package/src/commands/update.js +1 -1
- package/src/commands/zkp.js +20 -0
- package/src/gateways/ws/message-dispatcher.js +5 -2
- package/src/gateways/ws/remote-session-protocol.js +103 -42
- package/src/harness/jsonl-session-store.js +12 -5
- package/src/harness/plugin-manager.js +16 -4
- package/src/harness/remote-command-ledger.js +46 -15
- package/src/harness/worktree-isolator.js +16 -6
- package/src/lib/__tests__/logger.test.js +5 -2
- package/src/lib/a2a-protocol.js +25 -1
- package/src/lib/activitypub-bridge.js +61 -0
- package/src/lib/agent-core.js +2 -0
- package/src/lib/agent-economy.js +262 -29
- package/src/lib/agent-network.js +7 -1
- package/src/lib/agent-team/team-runner.js +25 -9
- package/src/lib/agent-team/team-worktree.js +36 -4
- package/src/lib/async-hook-supervisor.cjs +102 -16
- package/src/lib/audit-logger.js +7 -6
- package/src/lib/autonomous-developer.js +60 -0
- package/src/lib/chat-core.js +17 -4
- package/src/lib/collaboration-governance.js +56 -0
- package/src/lib/community-governance.js +68 -0
- package/src/lib/compliance-manager.js +63 -0
- package/src/lib/cross-chain.js +5 -0
- package/src/lib/dao-governance.js +151 -2
- package/src/lib/did-manager.js +23 -10
- package/src/lib/dlp-engine.js +70 -0
- package/src/lib/eval/runner.js +89 -1
- package/src/lib/eval/tasks.js +170 -0
- package/src/lib/eval/trend.js +16 -1
- package/src/lib/evolution-system.js +92 -0
- package/src/lib/evomap-federation.js +55 -0
- package/src/lib/evomap-governance.js +94 -0
- package/src/lib/fatal-handler.js +17 -1
- package/src/lib/hardening-manager.js +73 -0
- package/src/lib/hierarchical-memory.js +14 -8
- package/src/lib/knowledge-exporter.js +8 -2
- package/src/lib/knowledge-graph.js +79 -0
- package/src/lib/llm-providers.js +23 -14
- package/src/lib/logger.js +4 -1
- package/src/lib/lsp/__tests__/benchmark.test.js +88 -0
- package/src/lib/lsp/__tests__/lsp-client.test.js +11 -0
- package/src/lib/lsp/__tests__/lsp-manager.test.js +69 -0
- package/src/lib/lsp/benchmark.js +257 -0
- package/src/lib/lsp/lsp-client.js +30 -9
- package/src/lib/lsp/lsp-manager.js +54 -2
- package/src/lib/matrix-bridge.js +84 -0
- package/src/lib/memory-manager.js +5 -3
- package/src/lib/nostr-bridge.js +53 -0
- package/src/lib/permission-engine.js +22 -4
- package/src/lib/plugin-runtime/__tests__/remote-source.test.js +256 -0
- package/src/lib/plugin-runtime/install.js +9 -1
- package/src/lib/plugin-runtime/policy.js +9 -1
- package/src/lib/plugin-runtime/remote-source.js +240 -0
- package/src/lib/pqc-manager.js +64 -0
- package/src/lib/reputation-optimizer.js +101 -0
- package/src/lib/sandbox-egress-proxy.js +159 -59
- package/src/lib/sandbox-fs-policy.js +112 -0
- package/src/lib/sandbox-network-policy.js +18 -1
- package/src/lib/sandbox-v2.js +14 -14
- package/src/lib/scim-manager.js +36 -0
- package/src/lib/session-manager.js +5 -2
- package/src/lib/settings-hooks.cjs +1 -0
- package/src/lib/siem-exporter.js +45 -0
- package/src/lib/skill-loader.js +20 -2
- package/src/lib/skill-marketplace.js +83 -0
- package/src/lib/sla-manager.js +88 -0
- package/src/lib/social-manager.js +74 -0
- package/src/lib/stress-tester.js +65 -0
- package/src/lib/tech-learning-engine.js +75 -0
- package/src/lib/tenant-saas.js +107 -0
- package/src/lib/terraform-manager.js +67 -0
- package/src/lib/token-incentive.js +180 -29
- package/src/lib/wallet-manager.js +81 -35
- package/src/lib/zkp-engine.js +87 -0
- package/src/repl/agent-repl.js +17 -2
- package/src/runtime/__tests__/auto-pin.test.js +104 -0
- package/src/runtime/agent-core.js +241 -68
- package/src/runtime/auto-pin.js +117 -0
- package/src/runtime/headless-runner.js +35 -0
- package/src/assets/web-panel/assets/OrderTableRenderer-CRIFE2Tj.js +0 -1
- package/src/assets/web-panel/assets/Projects-CGcNIs_g.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DxwmsL4j.js +0 -1
- package/src/assets/web-panel/assets/index-Cg2ldRfU.js +0 -1
- package/src/assets/web-panel/assets/index-Dwzo-dtJ.js +0 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import fs from "fs";
|
|
9
9
|
import path from "path";
|
|
10
|
-
import { likePrefix } from "./sql-like.js";
|
|
10
|
+
import { likePrefix, escapeLike } from "./sql-like.js";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Atomically write a UTF-8 file: a crash mid-write must not truncate MEMORY.md
|
|
@@ -98,12 +98,14 @@ export function searchMemory(db, query, options = {}) {
|
|
|
98
98
|
if (!query || !query.trim()) return [];
|
|
99
99
|
|
|
100
100
|
const limit = Math.max(1, parseInt(options.limit) || 20);
|
|
101
|
-
|
|
101
|
+
// Escape LIKE wildcards so a query containing % / _ is matched literally
|
|
102
|
+
// rather than as a wildcard (`search "%"` would otherwise match every entry).
|
|
103
|
+
const pattern = `%${escapeLike(query)}%`;
|
|
102
104
|
|
|
103
105
|
return db
|
|
104
106
|
.prepare(
|
|
105
107
|
`SELECT * FROM memory_entries
|
|
106
|
-
WHERE content LIKE ?
|
|
108
|
+
WHERE content LIKE ? ESCAPE '\\'
|
|
107
109
|
ORDER BY importance DESC, created_at DESC
|
|
108
110
|
LIMIT ?`,
|
|
109
111
|
)
|
package/src/lib/nostr-bridge.js
CHANGED
|
@@ -59,6 +59,59 @@ export function ensureNostrTables(db) {
|
|
|
59
59
|
`);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Rehydrate the module-level _relays/_events Maps from the persisted
|
|
64
|
+
* nostr_relays/nostr_events tables. The `cc` CLI runs every command in a
|
|
65
|
+
* fresh process, so without this the Maps are empty on read and a relay/event
|
|
66
|
+
* added in a prior invocation is invisible (`cc nostr relays`/`events` show
|
|
67
|
+
* nothing; `dm-decrypt` can't find the DM). Call after ensureNostrTables(db).
|
|
68
|
+
* (_keypairs/_didMappings are intentionally ephemeral — no DB table.)
|
|
69
|
+
*/
|
|
70
|
+
export function loadFromDb(db) {
|
|
71
|
+
if (!db) return 0;
|
|
72
|
+
_relays.clear();
|
|
73
|
+
_events.clear();
|
|
74
|
+
|
|
75
|
+
const relayRows = db.prepare(`SELECT * FROM nostr_relays`).all();
|
|
76
|
+
for (const row of relayRows) {
|
|
77
|
+
_relays.set(row.id, {
|
|
78
|
+
id: row.id,
|
|
79
|
+
url: row.url,
|
|
80
|
+
status: row.status,
|
|
81
|
+
lastConnected: row.last_connected ?? null,
|
|
82
|
+
eventCount: row.event_count ?? 0,
|
|
83
|
+
readEnabled: !!row.read_enabled,
|
|
84
|
+
writeEnabled: !!row.write_enabled,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const eventRows = db.prepare(`SELECT * FROM nostr_events`).all();
|
|
89
|
+
for (const row of eventRows) {
|
|
90
|
+
let tags = [];
|
|
91
|
+
try {
|
|
92
|
+
tags = row.tags ? JSON.parse(row.tags) : [];
|
|
93
|
+
} catch {
|
|
94
|
+
tags = [];
|
|
95
|
+
}
|
|
96
|
+
_events.set(row.id, {
|
|
97
|
+
id: row.id,
|
|
98
|
+
pubkey: row.pubkey,
|
|
99
|
+
kind: row.kind,
|
|
100
|
+
content: row.content,
|
|
101
|
+
tags,
|
|
102
|
+
sig: row.sig ?? "",
|
|
103
|
+
// write side stores createdAt as unix seconds; the column is an ISO
|
|
104
|
+
// string — convert back so the two process paths agree.
|
|
105
|
+
createdAt: row.created_at
|
|
106
|
+
? Math.floor(new Date(row.created_at).getTime() / 1000)
|
|
107
|
+
: null,
|
|
108
|
+
relayUrl: row.relay_url ?? null,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return _relays.size + _events.size;
|
|
113
|
+
}
|
|
114
|
+
|
|
62
115
|
/* ── Relay Management ─────────────────────────────────────── */
|
|
63
116
|
|
|
64
117
|
export function listRelays() {
|
|
@@ -275,6 +275,24 @@ export function revokePermission(db, userDid, permission) {
|
|
|
275
275
|
return result.changes > 0;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Is a grant still active given its stored `expires_at`?
|
|
280
|
+
*
|
|
281
|
+
* The previous check compared `expires_at > now` as ISO STRINGS. That is
|
|
282
|
+
* fail-open on unvalidated input: an unpadded date ("2026-7-5") or any
|
|
283
|
+
* non-numeric-leading garbage ("next year") sorts lexicographically AFTER a
|
|
284
|
+
* canonical `now` ('7'/'n' > '0'/'2'), so an already-expired or malformed
|
|
285
|
+
* time-limited grant silently became a PERMANENT grant. Compare numerically and
|
|
286
|
+
* treat an unparseable expiry as expired (fail closed) so a typo can never widen
|
|
287
|
+
* access. A null/empty `expires_at` means no expiry.
|
|
288
|
+
*/
|
|
289
|
+
function _grantActive(expiresAt, nowMs) {
|
|
290
|
+
if (!expiresAt) return true;
|
|
291
|
+
const t = Date.parse(expiresAt);
|
|
292
|
+
if (Number.isNaN(t)) return false;
|
|
293
|
+
return t > nowMs;
|
|
294
|
+
}
|
|
295
|
+
|
|
278
296
|
/**
|
|
279
297
|
* Get all roles and direct permissions for a user.
|
|
280
298
|
*/
|
|
@@ -287,15 +305,15 @@ export function getUserPermissions(db, userDid) {
|
|
|
287
305
|
.all(userDid);
|
|
288
306
|
|
|
289
307
|
// Filter out expired grants
|
|
290
|
-
const
|
|
291
|
-
const grants = allGrants.filter((g) =>
|
|
308
|
+
const nowMs = Date.now();
|
|
309
|
+
const grants = allGrants.filter((g) => _grantActive(g.expires_at, nowMs));
|
|
292
310
|
|
|
293
311
|
// Get direct permissions (not expired)
|
|
294
312
|
const allDirectPerms = db
|
|
295
313
|
.prepare("SELECT * FROM rbac_direct_permissions WHERE user_did = ?")
|
|
296
314
|
.all(userDid);
|
|
297
|
-
const directPerms = allDirectPerms.filter(
|
|
298
|
-
(d
|
|
315
|
+
const directPerms = allDirectPerms.filter((d) =>
|
|
316
|
+
_grantActive(d.expires_at, nowMs),
|
|
299
317
|
);
|
|
300
318
|
|
|
301
319
|
// Collect all permissions by looking up each role
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* remote-source tests — resolving a plugin from a hosted registry/manifest URL,
|
|
3
|
+
* with private-token auth and an offline cache. `fetch` and fs are injected via
|
|
4
|
+
* `_deps` so nothing hits the network or the real disk.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
8
|
+
import {
|
|
9
|
+
isRemoteSource,
|
|
10
|
+
validateRegistry,
|
|
11
|
+
resolvePluginEntry,
|
|
12
|
+
listRegistryPlugins,
|
|
13
|
+
resolveRegistryToken,
|
|
14
|
+
fetchRegistry,
|
|
15
|
+
resolveRemoteSource,
|
|
16
|
+
registryCachePath,
|
|
17
|
+
_deps,
|
|
18
|
+
} from "../remote-source.js";
|
|
19
|
+
|
|
20
|
+
const REGISTRY = {
|
|
21
|
+
name: "acme",
|
|
22
|
+
plugins: [
|
|
23
|
+
{
|
|
24
|
+
name: "toml-tools",
|
|
25
|
+
source: "https://github.com/x/toml.git",
|
|
26
|
+
ref: "v1.0.0",
|
|
27
|
+
version: "1.0.0",
|
|
28
|
+
description: "TOML LSP",
|
|
29
|
+
sha256: "abc123",
|
|
30
|
+
},
|
|
31
|
+
{ name: "py-helpers", source: "owner/py-helpers" },
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** A fake in-memory fs + fetch wired into _deps. */
|
|
36
|
+
function install(fakes = {}) {
|
|
37
|
+
const files = new Map(fakes.files || []);
|
|
38
|
+
_deps.existsSync = (p) => files.has(String(p));
|
|
39
|
+
_deps.readFileSync = (p) => {
|
|
40
|
+
if (!files.has(String(p))) throw new Error("ENOENT");
|
|
41
|
+
return files.get(String(p));
|
|
42
|
+
};
|
|
43
|
+
_deps.writeFileSync = (p, text) => files.set(String(p), text);
|
|
44
|
+
_deps.mkdirSync = () => {};
|
|
45
|
+
if (fakes.fetch) _deps.fetch = fakes.fetch;
|
|
46
|
+
return files;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Build a fetch that returns JSON once, then throws (to test caching). */
|
|
50
|
+
function okThenOffline(body) {
|
|
51
|
+
let calls = 0;
|
|
52
|
+
return vi.fn(async () => {
|
|
53
|
+
calls++;
|
|
54
|
+
if (calls === 1) {
|
|
55
|
+
return { ok: true, status: 200, text: async () => JSON.stringify(body) };
|
|
56
|
+
}
|
|
57
|
+
throw new Error("ECONNREFUSED");
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let orig;
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
orig = { ..._deps };
|
|
64
|
+
delete process.env.CC_PLUGIN_REGISTRY_TOKEN;
|
|
65
|
+
});
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
Object.assign(_deps, orig);
|
|
68
|
+
vi.restoreAllMocks();
|
|
69
|
+
delete process.env.CC_PLUGIN_REGISTRY_TOKEN;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("isRemoteSource", () => {
|
|
73
|
+
it("recognizes http(s) .json URLs", () => {
|
|
74
|
+
expect(isRemoteSource("https://x.com/registry.json")).toBe(true);
|
|
75
|
+
expect(isRemoteSource("http://x.com/a/b.json?ref=1")).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
it("rejects local dirs, git URLs, and owner/repo", () => {
|
|
78
|
+
expect(isRemoteSource("./plugins/foo")).toBe(false);
|
|
79
|
+
expect(isRemoteSource("https://github.com/x/foo.git")).toBe(false);
|
|
80
|
+
expect(isRemoteSource("owner/repo")).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("validateRegistry", () => {
|
|
85
|
+
it("accepts a multi-plugin index", () => {
|
|
86
|
+
expect(validateRegistry(REGISTRY).plugins).toHaveLength(2);
|
|
87
|
+
});
|
|
88
|
+
it("wraps a single-plugin manifest into a one-entry registry", () => {
|
|
89
|
+
const r = validateRegistry({
|
|
90
|
+
name: "solo",
|
|
91
|
+
source: "owner/solo",
|
|
92
|
+
ref: "v2",
|
|
93
|
+
});
|
|
94
|
+
expect(r.plugins).toEqual([
|
|
95
|
+
{ name: "solo", source: "owner/solo", ref: "v2" },
|
|
96
|
+
]);
|
|
97
|
+
});
|
|
98
|
+
it("rejects garbage and entries missing name/source", () => {
|
|
99
|
+
expect(() => validateRegistry(null)).toThrow(/not a JSON object/);
|
|
100
|
+
expect(() => validateRegistry({ foo: 1 })).toThrow(
|
|
101
|
+
/plugins.*array.*source/,
|
|
102
|
+
);
|
|
103
|
+
expect(() => validateRegistry({ plugins: [{ name: "x" }] })).toThrow(
|
|
104
|
+
/missing name\/source/,
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("resolvePluginEntry", () => {
|
|
110
|
+
it("selects by name", () => {
|
|
111
|
+
expect(resolvePluginEntry(REGISTRY, "py-helpers").source).toBe(
|
|
112
|
+
"owner/py-helpers",
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
it("auto-selects the sole plugin when name omitted", () => {
|
|
116
|
+
const solo = { plugins: [REGISTRY.plugins[0]] };
|
|
117
|
+
expect(resolvePluginEntry(solo).name).toBe("toml-tools");
|
|
118
|
+
});
|
|
119
|
+
it("requires a name when the registry has many", () => {
|
|
120
|
+
expect(() => resolvePluginEntry(REGISTRY)).toThrow(/pick one with --name/);
|
|
121
|
+
});
|
|
122
|
+
it("lists options when the name is unknown", () => {
|
|
123
|
+
expect(() => resolvePluginEntry(REGISTRY, "nope")).toThrow(
|
|
124
|
+
/not found.*toml-tools, py-helpers/s,
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("resolveRegistryToken", () => {
|
|
130
|
+
it("prefers an explicit token, then env, then per-host config", () => {
|
|
131
|
+
expect(
|
|
132
|
+
resolveRegistryToken("https://h/r.json", { token: "explicit" }),
|
|
133
|
+
).toBe("explicit");
|
|
134
|
+
process.env.CC_PLUGIN_REGISTRY_TOKEN = "envtok";
|
|
135
|
+
expect(resolveRegistryToken("https://h/r.json", {})).toBe("envtok");
|
|
136
|
+
delete process.env.CC_PLUGIN_REGISTRY_TOKEN;
|
|
137
|
+
const config = { plugins: { registryTokens: { "priv.co": "cfgtok" } } };
|
|
138
|
+
expect(resolveRegistryToken("https://priv.co/r.json", { config })).toBe(
|
|
139
|
+
"cfgtok",
|
|
140
|
+
);
|
|
141
|
+
expect(
|
|
142
|
+
resolveRegistryToken("https://other.co/r.json", { config }),
|
|
143
|
+
).toBeNull();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe("fetchRegistry", () => {
|
|
148
|
+
it("fetches, validates, and caches; sends a bearer token when given", async () => {
|
|
149
|
+
const fetch = vi.fn(async () => ({
|
|
150
|
+
ok: true,
|
|
151
|
+
status: 200,
|
|
152
|
+
text: async () => JSON.stringify(REGISTRY),
|
|
153
|
+
}));
|
|
154
|
+
install({ fetch });
|
|
155
|
+
const { registry, fromCache } = await fetchRegistry("https://h/r.json", {
|
|
156
|
+
token: "t",
|
|
157
|
+
cacheDir: "/cache",
|
|
158
|
+
});
|
|
159
|
+
expect(fromCache).toBe(false);
|
|
160
|
+
expect(registry.plugins).toHaveLength(2);
|
|
161
|
+
const [, init] = fetch.mock.calls[0];
|
|
162
|
+
expect(init.headers.Authorization).toBe("Bearer t");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("falls back to the offline cache when the network fails", async () => {
|
|
166
|
+
const fetch = okThenOffline(REGISTRY);
|
|
167
|
+
install({ fetch });
|
|
168
|
+
// 1st call populates the cache
|
|
169
|
+
await fetchRegistry("https://h/r.json", { cacheDir: "/cache" });
|
|
170
|
+
// 2nd call: network throws → served from cache
|
|
171
|
+
const { registry, fromCache } = await fetchRegistry("https://h/r.json", {
|
|
172
|
+
cacheDir: "/cache",
|
|
173
|
+
});
|
|
174
|
+
expect(fromCache).toBe(true);
|
|
175
|
+
expect(registry.plugins).toHaveLength(2);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("throws on HTTP error with no cache", async () => {
|
|
179
|
+
const fetch = vi.fn(async () => ({
|
|
180
|
+
ok: false,
|
|
181
|
+
status: 404,
|
|
182
|
+
statusText: "Not Found",
|
|
183
|
+
text: async () => "",
|
|
184
|
+
}));
|
|
185
|
+
install({ fetch });
|
|
186
|
+
await expect(
|
|
187
|
+
fetchRegistry("https://h/missing.json", { cacheDir: "/cache" }),
|
|
188
|
+
).rejects.toThrow(/HTTP 404/);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("throws when offline and nothing is cached", async () => {
|
|
192
|
+
const fetch = vi.fn(async () => {
|
|
193
|
+
throw new Error("ENOTFOUND");
|
|
194
|
+
});
|
|
195
|
+
install({ fetch });
|
|
196
|
+
await expect(
|
|
197
|
+
fetchRegistry("https://h/r.json", { cacheDir: "/cache" }),
|
|
198
|
+
).rejects.toThrow(/no offline cache/);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe("resolveRemoteSource", () => {
|
|
203
|
+
it("resolves a named entry to a git source string with a #ref and carries sha256", async () => {
|
|
204
|
+
const fetch = vi.fn(async () => ({
|
|
205
|
+
ok: true,
|
|
206
|
+
status: 200,
|
|
207
|
+
text: async () => JSON.stringify(REGISTRY),
|
|
208
|
+
}));
|
|
209
|
+
install({ fetch });
|
|
210
|
+
const r = await resolveRemoteSource("https://h/r.json", {
|
|
211
|
+
name: "toml-tools",
|
|
212
|
+
cacheDir: "/cache",
|
|
213
|
+
});
|
|
214
|
+
expect(r.source).toBe("https://github.com/x/toml.git#v1.0.0");
|
|
215
|
+
expect(r.sha256).toBe("abc123");
|
|
216
|
+
expect(r.fromCache).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("omits the #ref when the entry has none", async () => {
|
|
220
|
+
const fetch = vi.fn(async () => ({
|
|
221
|
+
ok: true,
|
|
222
|
+
status: 200,
|
|
223
|
+
text: async () => JSON.stringify(REGISTRY),
|
|
224
|
+
}));
|
|
225
|
+
install({ fetch });
|
|
226
|
+
const r = await resolveRemoteSource("https://h/r.json", {
|
|
227
|
+
name: "py-helpers",
|
|
228
|
+
cacheDir: "/cache",
|
|
229
|
+
});
|
|
230
|
+
expect(r.source).toBe("owner/py-helpers");
|
|
231
|
+
expect(r.ref).toBeNull();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe("listRegistryPlugins", () => {
|
|
236
|
+
it("flattens entries for display", () => {
|
|
237
|
+
const rows = listRegistryPlugins(REGISTRY);
|
|
238
|
+
expect(rows.map((r) => r.name)).toEqual(["toml-tools", "py-helpers"]);
|
|
239
|
+
expect(rows[0]).toMatchObject({
|
|
240
|
+
version: "1.0.0",
|
|
241
|
+
ref: "v1.0.0",
|
|
242
|
+
description: "TOML LSP",
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("registryCachePath", () => {
|
|
248
|
+
it("is stable and content-addressed by URL", () => {
|
|
249
|
+
const a = registryCachePath("https://h/r.json", "/cache");
|
|
250
|
+
const b = registryCachePath("https://h/r.json", "/cache");
|
|
251
|
+
const c = registryCachePath("https://h/OTHER.json", "/cache");
|
|
252
|
+
expect(a).toBe(b);
|
|
253
|
+
expect(a).not.toBe(c);
|
|
254
|
+
expect(a.endsWith(".json")).toBe(true);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
@@ -320,6 +320,14 @@ export function uninstall(name, opts = {}) {
|
|
|
320
320
|
`${name}@${opts.version} is not installed at ${scope} scope`,
|
|
321
321
|
);
|
|
322
322
|
}
|
|
323
|
+
// Capture the active version BEFORE removing, so we only repoint `.active`
|
|
324
|
+
// when the version we removed WAS the active one. Removing a NON-active
|
|
325
|
+
// version (e.g. cleaning up a newer version after rolling back to an older
|
|
326
|
+
// pinned one) must not silently change which version is active — the old
|
|
327
|
+
// code always rewrote `.active` to the newest remaining, so uninstalling an
|
|
328
|
+
// unrelated version could bump the user's pinned choice.
|
|
329
|
+
const removedWasActive =
|
|
330
|
+
getActiveVersion(name, { scope, cwd }) === opts.version;
|
|
323
331
|
_deps.rmSync(dir, { recursive: true, force: true });
|
|
324
332
|
removed.push(opts.version);
|
|
325
333
|
// Repoint .active to the newest remaining version, or clear it.
|
|
@@ -333,7 +341,7 @@ export function uninstall(name, opts = {}) {
|
|
|
333
341
|
recursive: true,
|
|
334
342
|
force: true,
|
|
335
343
|
});
|
|
336
|
-
} else if (_deps.existsSync(activeFile)) {
|
|
344
|
+
} else if (removedWasActive && _deps.existsSync(activeFile)) {
|
|
337
345
|
_deps.writeFileSync(activeFile, remaining[0], "utf8");
|
|
338
346
|
}
|
|
339
347
|
return { removed };
|
|
@@ -43,7 +43,15 @@ export function loadManagedPluginPolicy(opts = {}) {
|
|
|
43
43
|
const key = String(
|
|
44
44
|
opts.managedSettingsFile || env.CC_MANAGED_SETTINGS || "<default>",
|
|
45
45
|
);
|
|
46
|
-
if (_cache.has(key))
|
|
46
|
+
if (_cache.has(key)) {
|
|
47
|
+
const cached = _cache.get(key);
|
|
48
|
+
// A cached failure sentinel must re-throw on EVERY call — otherwise the
|
|
49
|
+
// first call fails closed but later callers get the truthy {__invalid}
|
|
50
|
+
// object, treat it as an empty policy, and silently bypass the org's
|
|
51
|
+
// deny/allow/requireSignedPlugins enforcement (fail-open).
|
|
52
|
+
if (cached && cached.__invalid) throw cached.__invalid;
|
|
53
|
+
return cached;
|
|
54
|
+
}
|
|
47
55
|
let settings = null;
|
|
48
56
|
try {
|
|
49
57
|
const loaded = _deps.loadManagedSettings({
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote plugin sources — install from a hosted registry / manifest URL, not
|
|
3
|
+
* just a local dir or git URL (Phase 3 "远程 manifest" + "私有仓认证" + "离线
|
|
4
|
+
* seed cache" work items).
|
|
5
|
+
*
|
|
6
|
+
* A remote source is an `https?://…/*.json` document, in one of two shapes:
|
|
7
|
+
*
|
|
8
|
+
* 1. Registry index — many plugins, selected by name:
|
|
9
|
+
* { "name": "acme-registry",
|
|
10
|
+
* "plugins": [
|
|
11
|
+
* { "name": "toml-tools", "source": "https://github.com/x/toml.git",
|
|
12
|
+
* "ref": "v1.0.0", "version": "1.0.0", "description": "…", "sha256": "…" },
|
|
13
|
+
* { "name": "py-helpers", "source": "owner/py-helpers" }
|
|
14
|
+
* ] }
|
|
15
|
+
*
|
|
16
|
+
* 2. Single-plugin manifest — one plugin, no selection needed:
|
|
17
|
+
* { "name": "toml-tools", "source": "https://github.com/x/toml.git", "ref": "v1.0.0" }
|
|
18
|
+
*
|
|
19
|
+
* The remote layer is an INDIRECTION: it resolves a name → a git source string
|
|
20
|
+
* that the existing installer (`installFromSource`) already knows how to clone.
|
|
21
|
+
* This keeps the sync install core untouched and avoids bundling a tarball
|
|
22
|
+
* extractor. Downloaded registry JSON is cached content-addressed so browsing /
|
|
23
|
+
* installing works OFFLINE, and a bearer token can gate a PRIVATE registry.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import fs from "fs";
|
|
27
|
+
import os from "os";
|
|
28
|
+
import path from "path";
|
|
29
|
+
import crypto from "crypto";
|
|
30
|
+
import { getElectronUserDataDir } from "../paths.js";
|
|
31
|
+
|
|
32
|
+
export const _deps = {
|
|
33
|
+
// Node 22+ global fetch; injectable for tests.
|
|
34
|
+
fetch: (...args) => globalThis.fetch(...args),
|
|
35
|
+
readFileSync: fs.readFileSync,
|
|
36
|
+
writeFileSync: fs.writeFileSync,
|
|
37
|
+
existsSync: fs.existsSync,
|
|
38
|
+
mkdirSync: fs.mkdirSync,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
42
|
+
|
|
43
|
+
/** True when `raw` looks like a remote registry/manifest URL (http(s) + .json). */
|
|
44
|
+
export function isRemoteSource(raw) {
|
|
45
|
+
const s = String(raw || "").trim();
|
|
46
|
+
return /^https?:\/\//i.test(s) && /\.json(\?.*)?(#.*)?$/i.test(s);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Where a fetched registry is cached (content-addressed by its URL). */
|
|
50
|
+
export function registryCachePath(url, cacheDir) {
|
|
51
|
+
const dir =
|
|
52
|
+
cacheDir || path.join(getElectronUserDataDir(), "plugin-registry-cache");
|
|
53
|
+
const hash = crypto
|
|
54
|
+
.createHash("sha256")
|
|
55
|
+
.update(String(url))
|
|
56
|
+
.digest("hex")
|
|
57
|
+
.slice(0, 32);
|
|
58
|
+
return path.join(dir, `${hash}.json`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the bearer token for a registry host from (in order): explicit
|
|
63
|
+
* `opts.token`, `CC_PLUGIN_REGISTRY_TOKEN` env, or a per-host map in config
|
|
64
|
+
* (`config.plugins.registryTokens[host]`). Returns null if none — a public
|
|
65
|
+
* registry needs no token.
|
|
66
|
+
*/
|
|
67
|
+
export function resolveRegistryToken(url, { token, config } = {}) {
|
|
68
|
+
if (token) return token;
|
|
69
|
+
if (process.env.CC_PLUGIN_REGISTRY_TOKEN)
|
|
70
|
+
return process.env.CC_PLUGIN_REGISTRY_TOKEN;
|
|
71
|
+
try {
|
|
72
|
+
const host = new URL(url).host;
|
|
73
|
+
const map = config?.plugins?.registryTokens;
|
|
74
|
+
if (map && map[host]) return map[host];
|
|
75
|
+
} catch {
|
|
76
|
+
/* bad URL — handled by fetch */
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* GET a registry/manifest URL as JSON, with optional bearer auth. On success the
|
|
83
|
+
* body is written to the offline cache; on a network/HTTP failure we fall back
|
|
84
|
+
* to that cache (so a previously-seen registry still installs offline). Throws
|
|
85
|
+
* only when neither the network NOR the cache can produce a valid document.
|
|
86
|
+
*
|
|
87
|
+
* @returns {Promise<{ registry: object, fromCache: boolean }>}
|
|
88
|
+
*/
|
|
89
|
+
export async function fetchRegistry(url, opts = {}) {
|
|
90
|
+
const {
|
|
91
|
+
token,
|
|
92
|
+
cacheDir,
|
|
93
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
94
|
+
allowCache = true,
|
|
95
|
+
} = opts;
|
|
96
|
+
const cachePath = registryCachePath(url, cacheDir);
|
|
97
|
+
const headers = { Accept: "application/json" };
|
|
98
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
99
|
+
|
|
100
|
+
let networkErr = null;
|
|
101
|
+
try {
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
104
|
+
let res;
|
|
105
|
+
try {
|
|
106
|
+
res = await _deps.fetch(url, {
|
|
107
|
+
headers,
|
|
108
|
+
redirect: "follow",
|
|
109
|
+
signal: controller.signal,
|
|
110
|
+
});
|
|
111
|
+
} finally {
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
}
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`registry fetch failed: HTTP ${res.status} ${res.statusText || ""}`.trim(),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const text = await res.text();
|
|
120
|
+
const registry = validateRegistry(JSON.parse(text), url);
|
|
121
|
+
if (allowCache) writeCache(cachePath, text);
|
|
122
|
+
return { registry, fromCache: false };
|
|
123
|
+
} catch (err) {
|
|
124
|
+
networkErr = err;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Network failed — try the offline cache.
|
|
128
|
+
if (allowCache && _deps.existsSync(cachePath)) {
|
|
129
|
+
try {
|
|
130
|
+
const text = _deps.readFileSync(cachePath, "utf8");
|
|
131
|
+
const registry = validateRegistry(JSON.parse(text), url);
|
|
132
|
+
return { registry, fromCache: true };
|
|
133
|
+
} catch {
|
|
134
|
+
/* cache unreadable — fall through to throw the network error */
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
throw new Error(
|
|
138
|
+
`could not fetch registry ${url}: ${networkErr ? networkErr.message : "unknown error"}` +
|
|
139
|
+
(allowCache ? " (no offline cache available)" : ""),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function writeCache(cachePath, text) {
|
|
144
|
+
try {
|
|
145
|
+
_deps.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
146
|
+
_deps.writeFileSync(cachePath, text, "utf8");
|
|
147
|
+
} catch {
|
|
148
|
+
/* caching is best-effort; a read-only cache dir must not break install */
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Validate + normalize a fetched document into a registry object. Throws on garbage. */
|
|
153
|
+
export function validateRegistry(doc, url = "") {
|
|
154
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
155
|
+
throw new Error(`registry at ${url} is not a JSON object`);
|
|
156
|
+
}
|
|
157
|
+
if (Array.isArray(doc.plugins)) {
|
|
158
|
+
for (const p of doc.plugins) {
|
|
159
|
+
if (!p || typeof p !== "object" || !p.name || !p.source) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`registry at ${url} has a plugin entry missing name/source`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return doc;
|
|
166
|
+
}
|
|
167
|
+
// Single-plugin manifest.
|
|
168
|
+
if (doc.source) {
|
|
169
|
+
return { plugins: [doc] };
|
|
170
|
+
}
|
|
171
|
+
throw new Error(
|
|
172
|
+
`registry at ${url} must have a "plugins" array or a top-level "source"`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** List installable entries from a registry (for `cc plugin search`). */
|
|
177
|
+
export function listRegistryPlugins(registry) {
|
|
178
|
+
return (registry.plugins || []).map((p) => ({
|
|
179
|
+
name: p.name,
|
|
180
|
+
version: p.version || null,
|
|
181
|
+
source: p.source,
|
|
182
|
+
ref: p.ref || null,
|
|
183
|
+
description: p.description || "",
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Pick one plugin entry from a registry. When the registry has exactly one
|
|
189
|
+
* plugin, `name` is optional; otherwise it must match. Returns the entry, or
|
|
190
|
+
* throws with a helpful list.
|
|
191
|
+
*/
|
|
192
|
+
export function resolvePluginEntry(registry, name) {
|
|
193
|
+
const plugins = registry.plugins || [];
|
|
194
|
+
if (plugins.length === 0) throw new Error("registry has no plugins");
|
|
195
|
+
if (!name) {
|
|
196
|
+
if (plugins.length === 1) return plugins[0];
|
|
197
|
+
throw new Error(
|
|
198
|
+
`registry has ${plugins.length} plugins — pick one with --name <plugin>:\n ` +
|
|
199
|
+
plugins.map((p) => p.name).join(", "),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const entry = plugins.find((p) => p.name === name);
|
|
203
|
+
if (!entry) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`plugin "${name}" not found in registry. Available:\n ` +
|
|
206
|
+
plugins.map((p) => p.name).join(", "),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return entry;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Full resolution: fetch the registry (with auth + offline cache), select the
|
|
214
|
+
* entry, and return the git source string the existing installer consumes plus
|
|
215
|
+
* carry-through metadata (ref pins the checkout; sha256 becomes an install
|
|
216
|
+
* integrity check).
|
|
217
|
+
*
|
|
218
|
+
* @returns {Promise<{ source: string, ref: string|null, sha256: string|null,
|
|
219
|
+
* entry: object, fromCache: boolean }>}
|
|
220
|
+
*/
|
|
221
|
+
export async function resolveRemoteSource(url, opts = {}) {
|
|
222
|
+
const token = resolveRegistryToken(url, opts);
|
|
223
|
+
const { registry, fromCache } = await fetchRegistry(url, { ...opts, token });
|
|
224
|
+
const entry = resolvePluginEntry(registry, opts.name);
|
|
225
|
+
// Carry a `#ref` on the source string so installFromSource pins the checkout,
|
|
226
|
+
// matching its existing `owner/repo#ref` / `url#ref` convention.
|
|
227
|
+
const source = entry.ref ? `${entry.source}#${entry.ref}` : entry.source;
|
|
228
|
+
return {
|
|
229
|
+
source,
|
|
230
|
+
ref: entry.ref || null,
|
|
231
|
+
sha256: entry.sha256 || null,
|
|
232
|
+
entry,
|
|
233
|
+
fromCache,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Best-effort local temp dir helper (kept here so tests can stub fs deps). */
|
|
238
|
+
export function _tmpRoot() {
|
|
239
|
+
return os.tmpdir();
|
|
240
|
+
}
|