chainlesschain 0.163.8 → 0.165.1
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 +7 -4
- package/scripts/macos-mcp-launcher-build.mjs +317 -0
- package/scripts/macos-mcp-launcher-postinstall.sh +54 -0
- package/scripts/macos-mcp-launcher-preinstall.sh +61 -0
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-CavM7W3k.js → AIOps-CYxFDrcm.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CUvZGabQ.js → ActionButton-CPBtdiU2.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DeBnWnbe.js → Analytics-Dayq7Dp7.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DbI5kxvd.js → AppLayout-6U7bPb2s.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-D93bigKm.js → Artifacts-D2Iqg_3D.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CBNvfUZa.js → Audit-CdoOB7WV.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CJLyh_YU.js → BackgroundAgents-DrnAVB8w.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-D_0yoehD.js → Backup-CesMi5cy.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-TJxmbObW.js → BaseInput-DaX9ptax.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CE0dto1q.js → Chat-OrwOPxDS.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZ5-yYiD.js → ChatBubbleRenderer-CvPZFRxM.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-Ck1RVIVo.js → Checkbox-DiHbcysC.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-qpoyf6DK.js → Codegen-CbgCkaBJ.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DUyPWjIE.js → Col-Du5hgrh4.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D_S1BemS.js → Community-DsPOBW-p.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BjxeK-Gc.js → Compact-BwHIFkNC.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Dd3CEJsu.js → Compliance-BvrDQSgd.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-B4QG7-ad.js → Cowork-gwAE6arx.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-BtZnids5.js → Cron-DW-0l4uF.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CHPfCBFD.js → Crosschain-j-NsLesB.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DT8DCEOK.js → DID-CibVEBOI.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-COQab5_U.js → Dashboard-W4z9vSfe.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BY1PsQuU.js → Dropdown-DAcrkKxG.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CsuAuaq3.js → EmailListRenderer-6GXah-89.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BqpinD-j.js → FamilyGuardDashboard-TKDCq8Ku.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D0gkfQFv.js → Federation-BXBLlcNm.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BqgE-axK.js → FormItemContext-BoJJBkG3.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-D0hJ2KvC.js → GenericCardRenderer-okjsl46v.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CmMB0uPY.js → Git-Rvs7i6nn.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BnVEI0Dk.js → Governance-CHSUxXXS.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-YTAMihRP.js → Inference-V3BlfxsB.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-0ax7VHop.js → KnowledgeGraph-C_NlD4aR.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-B9TRcokL.js → Logs-Nn4LNxhA.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-SVAgNnkt.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-B6CSuD_H.js → Marketplace-DEmjFcg-.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CjMXvOVU.js → McpTools-DlopXipI.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CXRF71U3.js → Memory-BG3ynX60.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DQgNp_oS.js → MobileBridge-DeRzWOfT.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DlPwcCbq.js → MobileProjects-Tyd3o2A0.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-6hDTRPe1.js → Mtc-BoPbOv1C.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BlVHU1DS.js → MtcAudit-un-rTHp7.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DP9pzEbV.js → Multisig-Ds0oveEB.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Djorh8EV.js → NLProgramming-D09JI-yY.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-oLZ-JkUg.js → Notes-DTRhiKJn.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-Cg288VH3.js → NotificationSettings-BAnnNCim.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Yr-1fSYi.js +1 -0
- package/src/assets/web-panel/assets/{Organization-CBdo-bHs.js → Organization-S9EAolpj.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-qZdnVumx.js → Overflow-CqcLKqJ8.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-D4kNfGNQ.js → P2P-Dw0F9EcU.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DnlMemM7.js → PdhVaultBrowser-BDRwUevN.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-DHucHFBJ.js → Permissions-D3_c3nKB.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BovlOIPR.js → PersonalDataHub-CxdMV98x.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-Deru3CQY.js → Pipeline-CgjNQFLI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bq5oTTbU.js → Privacy-BHghIIht.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BfzxVWAz.js → ProjectInit-De6pqaKM.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CKC6XBtH.js → ProjectSettings-Bg4ZkNYQ.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CP8vPvzs.js +1 -0
- package/src/assets/web-panel/assets/{Providers-C_jx4TWX.js → Providers-CUnHbNf-.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BdIHeVGC.js → QrScannerModal-DA2f3Wts.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BwZwAcPD.js → QuickAsk-C1FeHDqp.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BNTt0L-m.js → Recommend-B5guaUMN.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-PDADSds3.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-D6VpG32B.js → Reputation-eTlRhgvK.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D_74OvQQ.js → Row-D7AqSGY0.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BTr0a-BQ.js → RssFeed-JiuLm4Ja.js} +3 -3
- package/src/assets/web-panel/assets/{Search-C3MCFoNU.js → Search-CwruahW3.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CqW9aJ6s.js → Security-CdhqaR0b.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Cw5GNaHF.js → Services-CU0ZIlcT.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DZps5qeY.js → Skeleton-CtduPfAb.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C6NQ1znT.js → Skills-CROXm6AY.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DFP8G2Sh.js → Sla-DiZiABFD.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-ESt6MB6v.js → SpeechSettings-B5pLcXUw.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BHnN98aM.js → SyncSettings-DBX6J1Oe.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-NiSCaOf-.js → Tasks-BLyRJd3-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DsKFhW2C.js → Templates-BKa5qzmk.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CQ0E0ze5.js → Tenant-DnD3a4M6.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-D_rBu_by.js → Terminal-DEitQeTb.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-D3Vm4Po4.js → TimelineRenderer-CjHggTDq.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DNTxRH6q.js → Tokens-CsGT7sfl.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Dgprz1ms.js → Trigger-yr4rFDrW.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B2rUnVBD.js → Trust-CLvu1Sjp.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DEGQNc6_.js → UkeySign-B0m7yUP8.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-jlsAx3Eh.js → VideoEditing-C0VvP4lc.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-8jQih1ij.js → Wallet-kllBAG1M.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-XLeO6xIB.js → WebAuthn-C54UQNLx.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-De_dgDAG.js → WorkflowEditor-B7y8qSvU.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CI19R_Sk.js → chat-DRzTh8CJ.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DsYTOYN3.js → colors-BSMwSnCx.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CM3-t_Qr.js → compact-item-BTg602H5.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BjxlBRZv.js → createContext-B6EGsdvp.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-RDaWX_4i.js → hasIn-DDKqJ8RD.js} +1 -1
- package/src/assets/web-panel/assets/{index-nAH87mQP.js → index-323Hhxxt.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIUnNOjG.js → index-BICn3Qh1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyIWssPC.js → index-BMuGd2--.js} +1 -1
- package/src/assets/web-panel/assets/{index-DX7tuWVJ.js → index-BRqt4fO-.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cd3euP_4.js → index-BVTAl5N2.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cpqm1qfU.js → index-BsR7Y2NG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DcPFB8qJ.js → index-Bx044qDj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cd9OJhHG.js → index-C1GFfuqN.js} +1 -1
- package/src/assets/web-panel/assets/index-C3QX5tOl.js +1 -0
- package/src/assets/web-panel/assets/{index-B-Z8eFUJ.js → index-C66NpWy5.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlT2VAEK.js → index-CDTvsX8t.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dsij_KsN.js → index-CGNklbP6.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4gkzxkw.js → index-CHfLYwcW.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6sm5-Xq.js → index-CNxg61YP.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0yx-jtQ.js → index-CQlHFN_g.js} +1 -1
- package/src/assets/web-panel/assets/{index-CjTrRt13.js → index-CUEU48o1.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7WBt_4T.js → index-CWGax2QW.js} +1 -1
- package/src/assets/web-panel/assets/{index-IeiL9RKc.js → index-C_JzIXUq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnHH7bnD.js → index-Cf23wVMs.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKJr-TZp.js → index-CmAInUkX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiECJi-Q.js → index-CmAubjDi.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCnZyIMA.js → index-Co7oM1qr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Drlnwr8F.js → index-CwPUZbMI.js} +1 -1
- package/src/assets/web-panel/assets/{index-9R6BxAr0.js → index-D1naNj4U.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGqqJfL0.js → index-D5CsBShd.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bi46Xu4u.js → index-DBT3YeQ9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DXXtGMUJ.js → index-DLdrHhML.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0PMlbMs.js → index-Dc77VR3H.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0zcy54x.js → index-Dfv2lKu3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BN6UPD2E.js → index-DiW0sDkz.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dxge-xWl.js → index-DpTHIRfZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyOGN6a5.js → index-DqFXGzzC.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9O9YyWE.js → index-XFe-1twF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHNYPO0b.js → index-p2HYn89m.js} +3 -3
- package/src/assets/web-panel/assets/{index-CVQMeiwH.js → index-pqfux8vZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-pPL66J85.js → index-rjzk9z1Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-DoQA0-52.js → index-th2ihDc0.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5REnKt8.js → index-u2YVld9W.js} +1 -1
- package/src/assets/web-panel/assets/index-xYybvEic.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-BTL88of4.js → initDefaultProps-C3Djh9fa.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BUB2pXwB.js → motion-BzqFmbSe.js} +1 -1
- package/src/assets/web-panel/assets/{move-egCkkOMm.js → move-DJo_FFaW.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DAWhZC8c.js → mtc-parser-_tlTiBvk.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CvOxe_6d.js → omit-CB2ZyxUg.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DZUSLZ5Z.js → pickAttrs-DZwIFD4u.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BERxGNMY.js → placementArrow-DxZY0dgk.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DZuDfZ9n.js → responsiveObserve-CzzGt7GL.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DO756sgI.js → slide-BaIK52yz.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BRQlIz28.js → statusUtils-3-FHu9_M.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DCSfdybl.js → styleChecker-BkRT68fK.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-LD8dWVve.js → useFlexGapSupport-DHYwKS-s.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BX9uW2LK.js → useFs-DVVUhV8p.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BPSlPuHU.js → usePersonalDataHub-CkrFKAzQ.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BmOILC9V.js → vnode-BuBLFz0c.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-1iKowKGg.js → zoom-kpm6LtXU.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/commands/cowork.js +418 -41
- package/src/commands/permissions.js +240 -16
- package/src/commands/plugin.js +1293 -56
- package/src/commands/session-location.js +276 -0
- package/src/commands/session.js +2 -0
- package/src/data/changelog.json +28 -2
- package/src/gateways/ws/action-protocol.js +316 -17
- package/src/gateways/ws/message-dispatcher.js +30 -0
- package/src/gateways/ws/remote-session-protocol.js +565 -35
- package/src/gateways/ws/ws-agent-handler.js +12 -0
- package/src/gateways/ws/ws-approval-gate.js +5 -0
- package/src/gateways/ws/ws-server.js +510 -50
- package/src/harness/jsonl-session-store.js +341 -24
- package/src/harness/mcp-client.js +8 -1
- package/src/harness/remote-session-audit.js +2 -0
- package/src/harness/remote-session-crypto.js +929 -21
- package/src/harness/remote-session-registry.js +945 -24
- package/src/harness/session-list-index.js +233 -14
- package/src/lib/agent-authority.js +6 -0
- package/src/lib/auto-mode-config.js +98 -3
- package/src/lib/background-agent-keeper-client.js +248 -0
- package/src/lib/background-agent-keeper-protocol.js +291 -0
- package/src/lib/background-agent-supervisor.js +1357 -85
- package/src/lib/background-interaction-resolver.js +12 -0
- package/src/lib/background-session-transport.js +23 -6
- package/src/lib/background-turn-bootstrap-protocol.js +190 -0
- package/src/lib/cowork-workflow.js +1188 -76
- package/src/lib/delivery-production-adapter.js +38 -2
- package/src/lib/dynamic-workflow-facade.js +1736 -0
- package/src/lib/execution-location-contract.js +551 -0
- package/src/lib/execution-location-runtime.js +130 -0
- package/src/lib/ide-context.js +94 -6
- package/src/lib/mcp-stdio-package-materialization.js +166 -14
- package/src/lib/packer/pkg-config-generator.js +2 -0
- package/src/lib/permission-authority.js +77 -0
- package/src/lib/permission-side-effect-center.js +495 -0
- package/src/lib/plugin-runtime/install.js +514 -13
- package/src/lib/plugin-runtime/marketplace-artifact-readback.js +900 -0
- package/src/lib/plugin-runtime/marketplace-catalog.js +1181 -0
- package/src/lib/plugin-runtime/marketplace-impact.js +408 -0
- package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1567 -0
- package/src/lib/plugin-security.js +60 -3
- package/src/lib/process-execution-broker/index.js +1095 -57
- package/src/lib/process-execution-broker/linux-bwrap-descriptor-launch.js +375 -0
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +90 -16
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +159 -18
- package/src/lib/process-execution-broker/macos-mcp-launcher-contract.js +278 -0
- package/src/lib/process-execution-broker/macos-mcp-launcher-protocol.json +23 -0
- package/src/lib/process-execution-broker/macos-mcp-launcher.c +1497 -0
- package/src/lib/process-execution-broker/mcp-fd-entry-bootstrap.js +37 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +2330 -279
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +50 -4
- package/src/lib/public-ide-capability-manifest.js +3 -0
- package/src/lib/remote-approval-bridge.js +777 -34
- package/src/lib/remote-control.js +2 -0
- package/src/lib/remote-membership-authority.js +632 -0
- package/src/lib/remote-membership-coordinator.js +2973 -0
- package/src/lib/remote-membership-host-store.js +1799 -0
- package/src/lib/runtime-provenance-ledger.js +171 -46
- package/src/lib/scoped-permission-store.js +369 -0
- package/src/lib/session-anti-rollback-anchor.js +262 -5
- package/src/lib/shell-approval.js +169 -6
- package/src/lib/sub-agent-context.js +4 -0
- package/src/lib/with-file-lock.js +73 -38
- package/src/lib/workflow-definition-contract.js +149 -0
- package/src/repl/agent-repl.js +111 -36
- package/src/repl/chat-repl.js +2 -0
- package/src/repl/clipboard-image.js +735 -16
- package/src/repl/ide-status.js +1 -1
- package/src/repl/prompt-interactions.js +59 -6
- package/src/repl/remote-approval.js +29 -7
- package/src/runtime/agent-core.js +1182 -155
- package/src/runtime/headless-runner.js +52 -30
- package/src/runtime/headless-stream.js +51 -28
- package/src/workers/background-agent-keeper.js +689 -0
- package/src/workers/background-agent-worker.js +365 -13
- package/src/workers/background-turn-bootstrap.js +170 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-yySN5YNM.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Bb1O3Ofr.js +0 -1
- package/src/assets/web-panel/assets/Projects-CMolGhae.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-Biyn2-Ro.js +0 -5
- package/src/assets/web-panel/assets/devWarning-S1ld2LwU.js +0 -1
- package/src/assets/web-panel/assets/index-Drz6yq0m.js +0 -1
- package/src/assets/web-panel/assets/index-uWiUs7S_.js +0 -1
package/src/commands/plugin.js
CHANGED
|
@@ -28,6 +28,395 @@ import {
|
|
|
28
28
|
getPluginSkills,
|
|
29
29
|
} from "../harness/plugin-manager.js";
|
|
30
30
|
|
|
31
|
+
function collectRepeatableOption(value, previous = []) {
|
|
32
|
+
return [...previous, value];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeRegistryUrls(value) {
|
|
36
|
+
if (Array.isArray(value)) return value.filter(Boolean);
|
|
37
|
+
return value ? [value] : [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function loadOptionalPluginConfig() {
|
|
41
|
+
try {
|
|
42
|
+
const cm = await import("../lib/config-manager.js");
|
|
43
|
+
return cm.loadConfig();
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function fetchRegistryRemoteArtifacts(
|
|
50
|
+
registryUrl,
|
|
51
|
+
entry,
|
|
52
|
+
{ token, allowInsecure = false } = {},
|
|
53
|
+
) {
|
|
54
|
+
if (!registryUrl || !entry || typeof entry !== "object") return null;
|
|
55
|
+
const rawSignature =
|
|
56
|
+
entry.signature && typeof entry.signature === "object"
|
|
57
|
+
? entry.signature
|
|
58
|
+
: null;
|
|
59
|
+
const signatureUrl = rawSignature?.url;
|
|
60
|
+
const publicKeyUrl =
|
|
61
|
+
rawSignature?.publicKeyUrl ?? rawSignature?.publicKey?.url;
|
|
62
|
+
const publicKeySha256 = rawSignature?.publicKeySha256;
|
|
63
|
+
const signature =
|
|
64
|
+
signatureUrl && publicKeyUrl && publicKeySha256
|
|
65
|
+
? {
|
|
66
|
+
algorithm: rawSignature.algorithm,
|
|
67
|
+
url: signatureUrl,
|
|
68
|
+
sha256:
|
|
69
|
+
rawSignature.documentSha256 ??
|
|
70
|
+
rawSignature.sha256 ??
|
|
71
|
+
rawSignature.digest,
|
|
72
|
+
publicKeyUrl,
|
|
73
|
+
publicKeySha256,
|
|
74
|
+
publicKeyDocumentSha256:
|
|
75
|
+
rawSignature.publicKeyDocumentSha256 ??
|
|
76
|
+
rawSignature.publicKey?.documentSha256 ??
|
|
77
|
+
rawSignature.publicKey?.sha256,
|
|
78
|
+
}
|
|
79
|
+
: null;
|
|
80
|
+
const rawSbom =
|
|
81
|
+
entry.sbom && typeof entry.sbom === "object" ? entry.sbom : null;
|
|
82
|
+
const sbomDocumentSha256 = rawSbom?.documentSha256 ?? rawSbom?.documentDigest;
|
|
83
|
+
const sbom =
|
|
84
|
+
rawSbom?.url && sbomDocumentSha256
|
|
85
|
+
? {
|
|
86
|
+
format: rawSbom.format,
|
|
87
|
+
url: rawSbom.url,
|
|
88
|
+
digest: sbomDocumentSha256,
|
|
89
|
+
}
|
|
90
|
+
: null;
|
|
91
|
+
if (!signature && !sbom) return null;
|
|
92
|
+
const config = await loadOptionalPluginConfig();
|
|
93
|
+
const { resolveRegistryToken } =
|
|
94
|
+
await import("../lib/plugin-runtime/remote-source.js");
|
|
95
|
+
const { fetchPluginMarketplaceRemoteArtifacts } =
|
|
96
|
+
await import("../lib/plugin-runtime/marketplace-remote-artifacts.js");
|
|
97
|
+
return fetchPluginMarketplaceRemoteArtifacts({
|
|
98
|
+
registryUrl,
|
|
99
|
+
token: resolveRegistryToken(registryUrl, { token, config }),
|
|
100
|
+
allowInsecure,
|
|
101
|
+
signature,
|
|
102
|
+
sbom,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function cleanupRegistryRemoteArtifacts(remoteArtifacts) {
|
|
107
|
+
if (!remoteArtifacts?.cleanup) return;
|
|
108
|
+
try {
|
|
109
|
+
remoteArtifacts.cleanup();
|
|
110
|
+
} catch {
|
|
111
|
+
logger.warn(
|
|
112
|
+
"Verified marketplace artifact temporary files could not be removed; cleanup can be retried safely.",
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function registryManifestDigestConflict(registrySha256, optionSha256) {
|
|
118
|
+
if (!registrySha256 || !optionSha256) return false;
|
|
119
|
+
return (
|
|
120
|
+
String(registrySha256).trim().toLowerCase() !==
|
|
121
|
+
String(optionSha256).trim().toLowerCase()
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function discoverInstalledPluginVersions(cwd) {
|
|
126
|
+
const { discoverPlugins } = await import("../lib/plugin-runtime/scopes.js");
|
|
127
|
+
const installed = {};
|
|
128
|
+
try {
|
|
129
|
+
for (const row of discoverPlugins({ cwd, skipPolicy: true })) {
|
|
130
|
+
installed[row.name] = row.version;
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
/* an empty inventory conservatively leaves declared dependencies missing */
|
|
134
|
+
}
|
|
135
|
+
return installed;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function buildRegistrySetSelection(
|
|
139
|
+
registryUrls,
|
|
140
|
+
name,
|
|
141
|
+
cwd,
|
|
142
|
+
{ token, allowInsecure = false, strict = false } = {},
|
|
143
|
+
) {
|
|
144
|
+
const { fetchRegistry, resolveRegistryToken } =
|
|
145
|
+
await import("../lib/plugin-runtime/remote-source.js");
|
|
146
|
+
const {
|
|
147
|
+
buildPluginMarketplaceCatalog,
|
|
148
|
+
buildPluginMarketplaceCandidateSelection,
|
|
149
|
+
buildPluginMarketplaceInstallPreflightFromSelection,
|
|
150
|
+
MAX_MARKETPLACE_CATALOG_SOURCES,
|
|
151
|
+
} = await import("../lib/plugin-runtime/marketplace-catalog.js");
|
|
152
|
+
if (registryUrls.length === 0) {
|
|
153
|
+
throw new Error("at least one registry source is required");
|
|
154
|
+
}
|
|
155
|
+
if (registryUrls.length > MAX_MARKETPLACE_CATALOG_SOURCES) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`at most ${MAX_MARKETPLACE_CATALOG_SOURCES} registry sources are allowed`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
const config = await loadOptionalPluginConfig();
|
|
161
|
+
const sources = await Promise.all(
|
|
162
|
+
registryUrls.map(async (url) => {
|
|
163
|
+
try {
|
|
164
|
+
const resolvedToken = resolveRegistryToken(url, { token, config });
|
|
165
|
+
const resolved = await fetchRegistry(url, {
|
|
166
|
+
token: resolvedToken,
|
|
167
|
+
allowInsecure,
|
|
168
|
+
});
|
|
169
|
+
return { url, ...resolved };
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return {
|
|
172
|
+
url,
|
|
173
|
+
error: {
|
|
174
|
+
code: "REGISTRY_FETCH_FAILED",
|
|
175
|
+
message: error.message,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}),
|
|
180
|
+
);
|
|
181
|
+
const { VERSION } = await import("../constants.js");
|
|
182
|
+
const catalog = buildPluginMarketplaceCatalog({
|
|
183
|
+
sources,
|
|
184
|
+
installed: await discoverInstalledPluginVersions(cwd),
|
|
185
|
+
hostVersion: VERSION,
|
|
186
|
+
strict,
|
|
187
|
+
});
|
|
188
|
+
const selection = buildPluginMarketplaceCandidateSelection({
|
|
189
|
+
catalog,
|
|
190
|
+
name,
|
|
191
|
+
});
|
|
192
|
+
if (!selection.selected) {
|
|
193
|
+
return { catalog, selection, preflight: null, resolved: null };
|
|
194
|
+
}
|
|
195
|
+
const preflight = buildPluginMarketplaceInstallPreflightFromSelection({
|
|
196
|
+
catalog,
|
|
197
|
+
selection,
|
|
198
|
+
}).preflight;
|
|
199
|
+
const sourceIndex = selection.selected.registry.priority;
|
|
200
|
+
const entryIndex = selection.selected.registry.entryIndex;
|
|
201
|
+
const source = sources[sourceIndex];
|
|
202
|
+
const entry = source?.registry?.plugins?.[entryIndex];
|
|
203
|
+
if (!entry || entry.name !== selection.selected.name) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
"selected registry entry no longer matches catalog authority",
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
catalog,
|
|
210
|
+
selection,
|
|
211
|
+
preflight,
|
|
212
|
+
resolved: {
|
|
213
|
+
registryUrl: registryUrls[sourceIndex],
|
|
214
|
+
source: entry.ref ? `${entry.source}#${entry.ref}` : entry.source,
|
|
215
|
+
ref: entry.ref || null,
|
|
216
|
+
sha256: entry.sha256 || null,
|
|
217
|
+
entry,
|
|
218
|
+
fromCache: source.fromCache === true,
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function buildRegistryInstallPreflight(url, resolved, cwd) {
|
|
224
|
+
const { VERSION } = await import("../constants.js");
|
|
225
|
+
const { buildPluginMarketplaceInstallPreflight } =
|
|
226
|
+
await import("../lib/plugin-runtime/marketplace-catalog.js");
|
|
227
|
+
const installed = await discoverInstalledPluginVersions(cwd);
|
|
228
|
+
return buildPluginMarketplaceInstallPreflight({
|
|
229
|
+
registryUrl: url,
|
|
230
|
+
entry: resolved.entry,
|
|
231
|
+
fromCache: resolved.fromCache === true,
|
|
232
|
+
installed,
|
|
233
|
+
hostVersion: VERSION,
|
|
234
|
+
}).preflight;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function catalogAuthorityFromPreflight(
|
|
238
|
+
preflight,
|
|
239
|
+
impact = null,
|
|
240
|
+
remoteArtifactEvidence = null,
|
|
241
|
+
) {
|
|
242
|
+
return {
|
|
243
|
+
catalogDigest: preflight.catalogDigest,
|
|
244
|
+
candidateId: preflight.candidateId,
|
|
245
|
+
candidateDigest: preflight.candidateDigest,
|
|
246
|
+
governanceStatus: preflight.governance.status,
|
|
247
|
+
registryStatus: preflight.registry.status,
|
|
248
|
+
versionAuthority: preflight.versionAuthority,
|
|
249
|
+
artifactExpectations: {
|
|
250
|
+
manifest: {
|
|
251
|
+
status: preflight.integrity.digest.status,
|
|
252
|
+
sha256: preflight.integrity.digest.value,
|
|
253
|
+
},
|
|
254
|
+
signature: {
|
|
255
|
+
status: preflight.integrity.signature.status,
|
|
256
|
+
algorithm: preflight.integrity.signature.algorithm,
|
|
257
|
+
publicKeySha256: preflight.integrity.signature.publicKeySha256,
|
|
258
|
+
url: preflight.integrity.signature.url,
|
|
259
|
+
publicKeyUrl: preflight.integrity.signature.publicKeyUrl,
|
|
260
|
+
documentSha256: preflight.integrity.signature.documentSha256,
|
|
261
|
+
publicKeyDocumentSha256:
|
|
262
|
+
preflight.integrity.signature.publicKeyDocumentSha256,
|
|
263
|
+
},
|
|
264
|
+
sbom: {
|
|
265
|
+
status: preflight.integrity.sbom.status,
|
|
266
|
+
format: preflight.integrity.sbom.format,
|
|
267
|
+
sha256: preflight.integrity.sbom.payloadSha256,
|
|
268
|
+
payloadSha256: preflight.integrity.sbom.payloadSha256,
|
|
269
|
+
documentSha256: preflight.integrity.sbom.documentSha256,
|
|
270
|
+
url: preflight.integrity.sbom.url,
|
|
271
|
+
},
|
|
272
|
+
license: {
|
|
273
|
+
status: preflight.license.status,
|
|
274
|
+
expression: preflight.license.expression,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
...(preflight.selectionDigest
|
|
278
|
+
? {
|
|
279
|
+
selectionDigest: preflight.selectionDigest,
|
|
280
|
+
selectionSourceCount: preflight.selectionSourceCount,
|
|
281
|
+
}
|
|
282
|
+
: {}),
|
|
283
|
+
...(impact ? { updateImpactDigest: impact.impactDigest } : {}),
|
|
284
|
+
...(remoteArtifactEvidence ? { remoteArtifactEvidence } : {}),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function marketplacePreflightBlockerMessage(preflight) {
|
|
289
|
+
return preflight.blockers
|
|
290
|
+
.map((blocker) =>
|
|
291
|
+
blocker.detail ? `${blocker.code} (${blocker.detail})` : blocker.code,
|
|
292
|
+
)
|
|
293
|
+
.join(", ");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function assertExpectedSelectionDigest(expectedDigest, preflight) {
|
|
297
|
+
if (!expectedDigest) return;
|
|
298
|
+
if (!/^[a-f0-9]{64}$/.test(expectedDigest)) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
"registry candidate preflight blocked: INVALID_EXPECTED_SELECTION_DIGEST",
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
if (!preflight?.selectionDigest) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
"registry candidate preflight blocked: EXPECTED_SELECTION_DIGEST_REQUIRES_REGISTRY_SET",
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
if (expectedDigest !== preflight.selectionDigest) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`registry candidate preflight blocked: MARKETPLACE_SELECTION_DIGEST_MISMATCH (expected ${expectedDigest}, actual ${preflight.selectionDigest})`,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function installedCatalogAuthorityMatches(
|
|
316
|
+
name,
|
|
317
|
+
scope,
|
|
318
|
+
cwd,
|
|
319
|
+
preflight,
|
|
320
|
+
impact = null,
|
|
321
|
+
remoteArtifactEvidence = null,
|
|
322
|
+
) {
|
|
323
|
+
if (!preflight) return null;
|
|
324
|
+
try {
|
|
325
|
+
const { listInstalled } = await import("../lib/plugin-runtime/install.js");
|
|
326
|
+
const installed = listInstalled({ cwd, scopes: [scope] }).find(
|
|
327
|
+
(row) => row.name === name && row.scope === scope,
|
|
328
|
+
);
|
|
329
|
+
const authority = installed?.source?.catalogAuthority;
|
|
330
|
+
return (
|
|
331
|
+
authority?.catalogDigest === preflight.catalogDigest &&
|
|
332
|
+
authority?.candidateId === preflight.candidateId &&
|
|
333
|
+
authority?.candidateDigest === preflight.candidateDigest &&
|
|
334
|
+
(!preflight.selectionDigest ||
|
|
335
|
+
authority?.selectionDigest === preflight.selectionDigest) &&
|
|
336
|
+
(!impact || authority?.updateImpactDigest === impact.impactDigest) &&
|
|
337
|
+
(!remoteArtifactEvidence ||
|
|
338
|
+
authority?.remoteArtifactEvidence?.evidenceDigest ===
|
|
339
|
+
remoteArtifactEvidence.evidenceDigest) &&
|
|
340
|
+
authority?.preflightStatus === "allowed"
|
|
341
|
+
);
|
|
342
|
+
} catch {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async function buildInstalledMarketplaceSnapshot(name, scope, cwd) {
|
|
348
|
+
const { listInstalled } = await import("../lib/plugin-runtime/install.js");
|
|
349
|
+
const { discoverPlugins } = await import("../lib/plugin-runtime/scopes.js");
|
|
350
|
+
const row = listInstalled({ cwd, scopes: [scope] }).find(
|
|
351
|
+
(candidate) => candidate.name === name && candidate.scope === scope,
|
|
352
|
+
);
|
|
353
|
+
if (!row) return null;
|
|
354
|
+
const discovered = discoverPlugins({
|
|
355
|
+
cwd,
|
|
356
|
+
scopes: [scope],
|
|
357
|
+
skipPolicy: true,
|
|
358
|
+
includeDisabled: true,
|
|
359
|
+
}).find((candidate) => candidate.name === name && candidate.scope === scope);
|
|
360
|
+
let dependencies = {};
|
|
361
|
+
let payloadSbom = null;
|
|
362
|
+
if (discovered?.manifest?.manifestPath) {
|
|
363
|
+
try {
|
|
364
|
+
const fs = await import("node:fs");
|
|
365
|
+
const raw = JSON.parse(
|
|
366
|
+
fs.readFileSync(discovered.manifest.manifestPath, "utf8"),
|
|
367
|
+
);
|
|
368
|
+
if (
|
|
369
|
+
raw.dependencies &&
|
|
370
|
+
typeof raw.dependencies === "object" &&
|
|
371
|
+
!Array.isArray(raw.dependencies)
|
|
372
|
+
) {
|
|
373
|
+
dependencies = Object.fromEntries(
|
|
374
|
+
Object.entries(raw.dependencies).filter(
|
|
375
|
+
([dependency]) => dependency !== "host" && dependency !== "cc",
|
|
376
|
+
),
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
} catch {
|
|
380
|
+
dependencies = {};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (row.dir) {
|
|
384
|
+
try {
|
|
385
|
+
const { buildMarketplacePayloadSbom } =
|
|
386
|
+
await import("../lib/plugin-runtime/marketplace-artifact-readback.js");
|
|
387
|
+
payloadSbom = buildMarketplacePayloadSbom(row.dir);
|
|
388
|
+
} catch {
|
|
389
|
+
payloadSbom = null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
name: row.name,
|
|
394
|
+
version: row.version,
|
|
395
|
+
scope: row.scope,
|
|
396
|
+
source: row.source,
|
|
397
|
+
integrity: {
|
|
398
|
+
...row.integrity,
|
|
399
|
+
sbom: {
|
|
400
|
+
...row.integrity?.sbom,
|
|
401
|
+
payloadSha256: payloadSbom?.digest || null,
|
|
402
|
+
payloadSchema: payloadSbom?.schemaVersion || null,
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
license: {
|
|
406
|
+
expression: discovered?.manifest?.metadata?.license || null,
|
|
407
|
+
},
|
|
408
|
+
capabilities: discovered?.manifest?.capabilities || null,
|
|
409
|
+
dependencies,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async function buildRegistryUpdateImpact(preflight, name, scope, cwd) {
|
|
414
|
+
const { buildPluginMarketplaceUpdateImpact } =
|
|
415
|
+
await import("../lib/plugin-runtime/marketplace-impact.js");
|
|
416
|
+
const installed = await buildInstalledMarketplaceSnapshot(name, scope, cwd);
|
|
417
|
+
return buildPluginMarketplaceUpdateImpact({ preflight, installed });
|
|
418
|
+
}
|
|
419
|
+
|
|
31
420
|
/**
|
|
32
421
|
* After an install/upgrade, resolve the just-installed plugin's DECLARED
|
|
33
422
|
* capabilities and its current consent status, so `cc plugin add`/`upgrade`
|
|
@@ -889,7 +1278,9 @@ export function registerPluginCommand(program) {
|
|
|
889
1278
|
.option("--public-key <path>", "Public key for signature verification")
|
|
890
1279
|
.option(
|
|
891
1280
|
"--registry <url>",
|
|
892
|
-
"
|
|
1281
|
+
"Registry URL used to resolve <source> as a plugin name (repeatable)",
|
|
1282
|
+
collectRepeatableOption,
|
|
1283
|
+
[],
|
|
893
1284
|
)
|
|
894
1285
|
.option(
|
|
895
1286
|
"--name <plugin>",
|
|
@@ -900,13 +1291,17 @@ export function registerPluginCommand(program) {
|
|
|
900
1291
|
"--allow-insecure-registry",
|
|
901
1292
|
"Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
|
|
902
1293
|
)
|
|
1294
|
+
.option(
|
|
1295
|
+
"--expected-selection-digest <sha256>",
|
|
1296
|
+
"Require the exact digest from a prior multi-registry `cc plugin select` review",
|
|
1297
|
+
)
|
|
903
1298
|
.option(
|
|
904
1299
|
"--grant-capabilities",
|
|
905
1300
|
"Grant the plugin's declared capabilities at install time (no separate consent step)",
|
|
906
1301
|
)
|
|
907
1302
|
.option("--json", "Output as JSON")
|
|
908
1303
|
.action(async (source, options) => {
|
|
909
|
-
const { installFromSource } =
|
|
1304
|
+
const { installFromSource, finalizePluginUpdate, rollbackPluginUpdate } =
|
|
910
1305
|
await import("../lib/plugin-runtime/install.js");
|
|
911
1306
|
|
|
912
1307
|
// Remote resolution: a registry/manifest URL (or --registry <url> with a
|
|
@@ -914,29 +1309,84 @@ export function registerPluginCommand(program) {
|
|
|
914
1309
|
let installSource = source;
|
|
915
1310
|
let integritySha = null;
|
|
916
1311
|
let sourceMetadata = null;
|
|
1312
|
+
let expectedIdentity = null;
|
|
1313
|
+
let marketplacePreflight = null;
|
|
1314
|
+
let remoteArtifactRequest = null;
|
|
1315
|
+
let remoteArtifacts = null;
|
|
1316
|
+
const registryUrls = normalizeRegistryUrls(options.registry);
|
|
917
1317
|
const { isRemoteSource, resolveRemoteSource } =
|
|
918
1318
|
await import("../lib/plugin-runtime/remote-source.js");
|
|
919
|
-
if (
|
|
920
|
-
const url = options.registry || source;
|
|
921
|
-
// With --registry the positional arg is the plugin NAME; otherwise the
|
|
922
|
-
// URL is positional and --name selects.
|
|
923
|
-
const name = options.registry ? source : options.name;
|
|
924
|
-
let config = null;
|
|
925
|
-
try {
|
|
926
|
-
({ loadConfig: config } = await import("../lib/config-manager.js"));
|
|
927
|
-
config = config();
|
|
928
|
-
} catch {
|
|
929
|
-
config = null; // config is optional — token can come from --token/env
|
|
930
|
-
}
|
|
1319
|
+
if (registryUrls.length || isRemoteSource(source)) {
|
|
931
1320
|
try {
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1321
|
+
let resolved;
|
|
1322
|
+
let url;
|
|
1323
|
+
let name;
|
|
1324
|
+
if (registryUrls.length) {
|
|
1325
|
+
name = source;
|
|
1326
|
+
const selected = await buildRegistrySetSelection(
|
|
1327
|
+
registryUrls,
|
|
1328
|
+
name,
|
|
1329
|
+
process.cwd(),
|
|
1330
|
+
{
|
|
1331
|
+
token: options.token,
|
|
1332
|
+
allowInsecure: options.allowInsecureRegistry === true,
|
|
1333
|
+
},
|
|
1334
|
+
);
|
|
1335
|
+
resolved = selected.resolved;
|
|
1336
|
+
marketplacePreflight = selected.preflight;
|
|
1337
|
+
url = resolved?.registryUrl;
|
|
1338
|
+
if (!marketplacePreflight || !resolved) {
|
|
1339
|
+
throw new Error(
|
|
1340
|
+
`registry candidate preflight blocked: ${selected.selection.blockers.map((blocker) => blocker.code).join(", ") || "PLUGIN_NOT_FOUND"}`,
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
} else {
|
|
1344
|
+
url = source;
|
|
1345
|
+
name = options.name;
|
|
1346
|
+
const config = await loadOptionalPluginConfig();
|
|
1347
|
+
resolved = await resolveRemoteSource(url, {
|
|
1348
|
+
name,
|
|
1349
|
+
token: options.token,
|
|
1350
|
+
config,
|
|
1351
|
+
allowInsecure: options.allowInsecureRegistry === true,
|
|
1352
|
+
});
|
|
1353
|
+
marketplacePreflight = await buildRegistryInstallPreflight(
|
|
1354
|
+
url,
|
|
1355
|
+
resolved,
|
|
1356
|
+
process.cwd(),
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
938
1359
|
installSource = resolved.source;
|
|
939
1360
|
integritySha = resolved.sha256;
|
|
1361
|
+
if (registryManifestDigestConflict(integritySha, options.sha256)) {
|
|
1362
|
+
throw new Error(
|
|
1363
|
+
"registry artifact verification failed: --sha256 does not match the registry-declared manifest SHA-256",
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
if (marketplacePreflight.status !== "allowed") {
|
|
1367
|
+
throw new Error(
|
|
1368
|
+
`registry candidate preflight blocked: ${marketplacePreflightBlockerMessage(marketplacePreflight)}`,
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
assertExpectedSelectionDigest(
|
|
1372
|
+
options.expectedSelectionDigest,
|
|
1373
|
+
marketplacePreflight,
|
|
1374
|
+
);
|
|
1375
|
+
expectedIdentity = {
|
|
1376
|
+
name:
|
|
1377
|
+
typeof resolved.entry.name === "string"
|
|
1378
|
+
? resolved.entry.name
|
|
1379
|
+
: null,
|
|
1380
|
+
version:
|
|
1381
|
+
marketplacePreflight.versionAuthority ===
|
|
1382
|
+
"registry-declared-unverified"
|
|
1383
|
+
? marketplacePreflight.registryVersion
|
|
1384
|
+
: null,
|
|
1385
|
+
};
|
|
1386
|
+
remoteArtifactRequest = {
|
|
1387
|
+
registryUrl: url,
|
|
1388
|
+
entry: resolved.entry,
|
|
1389
|
+
};
|
|
940
1390
|
sourceMetadata = {
|
|
941
1391
|
type: "registry",
|
|
942
1392
|
source: url,
|
|
@@ -949,6 +1399,8 @@ export function registerPluginCommand(program) {
|
|
|
949
1399
|
resolved.source.slice(resolved.source.indexOf("#") + 1)) ||
|
|
950
1400
|
null,
|
|
951
1401
|
offline: resolved.fromCache === true,
|
|
1402
|
+
catalogAuthority:
|
|
1403
|
+
catalogAuthorityFromPreflight(marketplacePreflight),
|
|
952
1404
|
};
|
|
953
1405
|
if (resolved.fromCache) {
|
|
954
1406
|
logger.warn(
|
|
@@ -975,32 +1427,101 @@ export function registerPluginCommand(program) {
|
|
|
975
1427
|
const requiresManagedSignature =
|
|
976
1428
|
managed?.requireSignedPlugins === true ||
|
|
977
1429
|
managed?.requireSignedPlugins === "require";
|
|
1430
|
+
try {
|
|
1431
|
+
if (remoteArtifactRequest) {
|
|
1432
|
+
remoteArtifacts = await fetchRegistryRemoteArtifacts(
|
|
1433
|
+
remoteArtifactRequest.registryUrl,
|
|
1434
|
+
remoteArtifactRequest.entry,
|
|
1435
|
+
{
|
|
1436
|
+
token: options.token,
|
|
1437
|
+
allowInsecure: options.allowInsecureRegistry === true,
|
|
1438
|
+
},
|
|
1439
|
+
);
|
|
1440
|
+
if (
|
|
1441
|
+
remoteArtifacts?.signatureFile &&
|
|
1442
|
+
(options.signature || options.publicKey)
|
|
1443
|
+
) {
|
|
1444
|
+
throw new Error(
|
|
1445
|
+
"registry remote signature artifacts cannot be combined with --signature/--public-key",
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1448
|
+
if (remoteArtifacts?.authority && sourceMetadata?.catalogAuthority) {
|
|
1449
|
+
sourceMetadata.catalogAuthority = catalogAuthorityFromPreflight(
|
|
1450
|
+
marketplacePreflight,
|
|
1451
|
+
null,
|
|
1452
|
+
remoteArtifacts.authority,
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
} catch (err) {
|
|
1457
|
+
cleanupRegistryRemoteArtifacts(remoteArtifacts);
|
|
1458
|
+
logger.error(`Registry artifact verification failed: ${err.message}`);
|
|
1459
|
+
process.exitCode = 1;
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
978
1462
|
const signature =
|
|
979
1463
|
options.sha256 ||
|
|
980
1464
|
options.signature ||
|
|
981
1465
|
options.publicKey ||
|
|
1466
|
+
remoteArtifacts?.signatureFile ||
|
|
1467
|
+
remoteArtifacts?.publicKeyFile ||
|
|
982
1468
|
integritySha ||
|
|
983
1469
|
requiresManagedSignature
|
|
984
1470
|
? {
|
|
985
1471
|
sha256: options.sha256 || integritySha,
|
|
986
|
-
signatureFile:
|
|
987
|
-
|
|
1472
|
+
signatureFile:
|
|
1473
|
+
remoteArtifacts?.signatureFile || options.signature,
|
|
1474
|
+
publicKeyFile:
|
|
1475
|
+
remoteArtifacts?.publicKeyFile || options.publicKey,
|
|
1476
|
+
expectedSignatureSha256:
|
|
1477
|
+
remoteArtifacts?.authority?.signature?.signatureSha256 || null,
|
|
1478
|
+
expectedPublicKeyDocumentSha256:
|
|
1479
|
+
remoteArtifacts?.authority?.signature?.publicKey
|
|
1480
|
+
?.documentSha256 || null,
|
|
1481
|
+
expectedPublicKeySha256:
|
|
1482
|
+
remoteArtifacts?.authority?.signature?.publicKey?.spkiSha256 ||
|
|
1483
|
+
null,
|
|
988
1484
|
requireSignature:
|
|
989
|
-
Boolean(options.signature) ||
|
|
1485
|
+
Boolean(remoteArtifacts?.signatureFile || options.signature) ||
|
|
1486
|
+
requiresManagedSignature,
|
|
990
1487
|
trustedKeySha256: managed?.trustedPluginKeySha256 || null,
|
|
991
1488
|
requireTrustedKey: requiresManagedSignature,
|
|
992
1489
|
}
|
|
993
1490
|
: null;
|
|
1491
|
+
let res = null;
|
|
1492
|
+
let marketplaceTransactionFinalized = false;
|
|
1493
|
+
let marketplaceCleanupPending = false;
|
|
994
1494
|
try {
|
|
995
|
-
|
|
1495
|
+
res = installFromSource(installSource, {
|
|
996
1496
|
scope: options.scope,
|
|
997
1497
|
cwd: process.cwd(),
|
|
998
1498
|
force: options.force === true,
|
|
999
1499
|
signature,
|
|
1000
1500
|
sourceMetadata,
|
|
1501
|
+
expectedIdentity,
|
|
1001
1502
|
managedPolicy: managed,
|
|
1002
|
-
policySource:
|
|
1503
|
+
policySource: sourceMetadata?.registry || source,
|
|
1504
|
+
transactional: Boolean(marketplacePreflight),
|
|
1003
1505
|
});
|
|
1506
|
+
const marketplaceAuthorityPersisted = marketplacePreflight
|
|
1507
|
+
? res.sourceMetadata?.catalogAuthority?.catalogDigest ===
|
|
1508
|
+
marketplacePreflight.catalogDigest &&
|
|
1509
|
+
res.sourceMetadata?.catalogAuthority?.candidateId ===
|
|
1510
|
+
marketplacePreflight.candidateId &&
|
|
1511
|
+
res.sourceMetadata?.catalogAuthority?.candidateDigest ===
|
|
1512
|
+
marketplacePreflight.candidateDigest &&
|
|
1513
|
+
(!marketplacePreflight.selectionDigest ||
|
|
1514
|
+
res.sourceMetadata?.catalogAuthority?.selectionDigest ===
|
|
1515
|
+
marketplacePreflight.selectionDigest) &&
|
|
1516
|
+
(!remoteArtifacts?.authority ||
|
|
1517
|
+
res.sourceMetadata?.catalogAuthority?.remoteArtifactEvidence
|
|
1518
|
+
?.evidenceDigest === remoteArtifacts.authority.evidenceDigest)
|
|
1519
|
+
: null;
|
|
1520
|
+
if (marketplacePreflight && !marketplaceAuthorityPersisted) {
|
|
1521
|
+
throw new Error(
|
|
1522
|
+
"marketplace install rejected because catalog and remote artifact authority was not persisted",
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1004
1525
|
const capNotice = await resolvePluginCapabilityNotice(
|
|
1005
1526
|
res.name,
|
|
1006
1527
|
res.scope,
|
|
@@ -1017,21 +1538,31 @@ export function registerPluginCommand(program) {
|
|
|
1017
1538
|
res.scope,
|
|
1018
1539
|
process.cwd(),
|
|
1019
1540
|
);
|
|
1541
|
+
if (marketplacePreflight) {
|
|
1542
|
+
const finalization = finalizePluginUpdate(res);
|
|
1543
|
+
if (!finalization.finalized) {
|
|
1544
|
+
throw new Error(
|
|
1545
|
+
"marketplace install transaction could not be finalized",
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
marketplaceCleanupPending = finalization.cleanupPending === true;
|
|
1549
|
+
marketplaceTransactionFinalized = true;
|
|
1550
|
+
}
|
|
1020
1551
|
console.log(
|
|
1021
1552
|
JSON.stringify(
|
|
1022
|
-
{
|
|
1553
|
+
{
|
|
1554
|
+
...res,
|
|
1555
|
+
marketplacePreflight,
|
|
1556
|
+
marketplaceAuthorityPersisted,
|
|
1557
|
+
marketplaceCleanupPending,
|
|
1558
|
+
capabilities: capNotice,
|
|
1559
|
+
capabilitiesGranted,
|
|
1560
|
+
},
|
|
1023
1561
|
null,
|
|
1024
1562
|
2,
|
|
1025
1563
|
),
|
|
1026
1564
|
);
|
|
1027
1565
|
} else {
|
|
1028
|
-
logger.success(
|
|
1029
|
-
`Installed ${res.name} v${res.version} (${res.scope} scope)` +
|
|
1030
|
-
(res.signatureVerified ? chalk.green(" ✔ signed") : ""),
|
|
1031
|
-
);
|
|
1032
|
-
logger.log(chalk.gray(` → ${res.dir}`));
|
|
1033
|
-
for (const w of res.warnings || [])
|
|
1034
|
-
logger.log(chalk.yellow(` ⚠ ${w}`));
|
|
1035
1566
|
await applyCapabilityConsentGate(
|
|
1036
1567
|
res.name,
|
|
1037
1568
|
res.scope,
|
|
@@ -1042,10 +1573,56 @@ export function registerPluginCommand(program) {
|
|
|
1042
1573
|
interactive: Boolean(process.stdin.isTTY),
|
|
1043
1574
|
},
|
|
1044
1575
|
);
|
|
1576
|
+
if (marketplacePreflight) {
|
|
1577
|
+
const finalization = finalizePluginUpdate(res);
|
|
1578
|
+
if (!finalization.finalized) {
|
|
1579
|
+
throw new Error(
|
|
1580
|
+
"marketplace install transaction could not be finalized",
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
marketplaceCleanupPending = finalization.cleanupPending === true;
|
|
1584
|
+
marketplaceTransactionFinalized = true;
|
|
1585
|
+
}
|
|
1586
|
+
logger.success(
|
|
1587
|
+
`Installed ${res.name} v${res.version} (${res.scope} scope)` +
|
|
1588
|
+
(res.signatureVerified ? chalk.green(" ✔ signed") : ""),
|
|
1589
|
+
);
|
|
1590
|
+
logger.log(chalk.gray(` → ${res.dir}`));
|
|
1591
|
+
if (marketplacePreflight) {
|
|
1592
|
+
logger.log(
|
|
1593
|
+
chalk.gray(
|
|
1594
|
+
` catalog: ${marketplacePreflight.catalogDigest} (${marketplacePreflight.governance.status}; authority persisted)`,
|
|
1595
|
+
),
|
|
1596
|
+
);
|
|
1597
|
+
if (marketplacePreflight.governance.missing.length) {
|
|
1598
|
+
logger.log(
|
|
1599
|
+
chalk.yellow(
|
|
1600
|
+
` ⚠ registry governance metadata missing: ${marketplacePreflight.governance.missing.join(", ")}`,
|
|
1601
|
+
),
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
for (const w of res.warnings || [])
|
|
1606
|
+
logger.log(chalk.yellow(` ⚠ ${w}`));
|
|
1045
1607
|
}
|
|
1046
1608
|
} catch (err) {
|
|
1047
|
-
|
|
1609
|
+
let recoveryNote = "";
|
|
1610
|
+
if (res && marketplacePreflight && !marketplaceTransactionFinalized) {
|
|
1611
|
+
try {
|
|
1612
|
+
const recovery = rollbackPluginUpdate(res);
|
|
1613
|
+
if (recovery.rolledBack) {
|
|
1614
|
+
recoveryNote = recovery.version
|
|
1615
|
+
? `; restored v${recovery.version}`
|
|
1616
|
+
: "; removed the rejected install";
|
|
1617
|
+
}
|
|
1618
|
+
} catch (recoveryError) {
|
|
1619
|
+
recoveryNote = `; automatic recovery failed: ${recoveryError.message}`;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
logger.error(`Install failed: ${err.message}${recoveryNote}`);
|
|
1048
1623
|
process.exitCode = 1;
|
|
1624
|
+
} finally {
|
|
1625
|
+
cleanupRegistryRemoteArtifacts(remoteArtifacts);
|
|
1049
1626
|
}
|
|
1050
1627
|
});
|
|
1051
1628
|
|
|
@@ -1123,6 +1700,362 @@ export function registerPluginCommand(program) {
|
|
|
1123
1700
|
}
|
|
1124
1701
|
});
|
|
1125
1702
|
|
|
1703
|
+
// plugin catalog — merge one or more registries into a bounded, versioned
|
|
1704
|
+
// governance projection. Registry assertions remain explicitly unverified;
|
|
1705
|
+
// signature/SBOM verification still happens only after bytes are fetched.
|
|
1706
|
+
plugin
|
|
1707
|
+
.command("catalog [query]")
|
|
1708
|
+
.description(
|
|
1709
|
+
"Review digest, signature, SBOM, license, capability, dependency, and health metadata across registries",
|
|
1710
|
+
)
|
|
1711
|
+
.option(
|
|
1712
|
+
"--registry <url>",
|
|
1713
|
+
"Registry URL to include (repeatable; earlier sources have higher priority)",
|
|
1714
|
+
collectRepeatableOption,
|
|
1715
|
+
[],
|
|
1716
|
+
)
|
|
1717
|
+
.option("--token <token>", "Bearer token for private registries")
|
|
1718
|
+
.option(
|
|
1719
|
+
"--allow-insecure-registry",
|
|
1720
|
+
"Allow plain-HTTP registry URLs (MITM risk — trusted networks only)",
|
|
1721
|
+
)
|
|
1722
|
+
.option(
|
|
1723
|
+
"--strict",
|
|
1724
|
+
"Block candidates missing digest, signature, SBOM, license, or capabilities",
|
|
1725
|
+
)
|
|
1726
|
+
.option("--json", "Output the versioned catalog projection as JSON")
|
|
1727
|
+
.action(async (query, options) => {
|
|
1728
|
+
const registryUrls = Array.isArray(options.registry)
|
|
1729
|
+
? options.registry
|
|
1730
|
+
: [];
|
|
1731
|
+
if (registryUrls.length === 0) {
|
|
1732
|
+
logger.error("At least one --registry <url> is required");
|
|
1733
|
+
process.exitCode = 1;
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
const { fetchRegistry, resolveRegistryToken } =
|
|
1738
|
+
await import("../lib/plugin-runtime/remote-source.js");
|
|
1739
|
+
const { buildPluginMarketplaceCatalog, MAX_MARKETPLACE_CATALOG_SOURCES } =
|
|
1740
|
+
await import("../lib/plugin-runtime/marketplace-catalog.js");
|
|
1741
|
+
if (registryUrls.length > MAX_MARKETPLACE_CATALOG_SOURCES) {
|
|
1742
|
+
logger.error(
|
|
1743
|
+
`At most ${MAX_MARKETPLACE_CATALOG_SOURCES} --registry sources are allowed`,
|
|
1744
|
+
);
|
|
1745
|
+
process.exitCode = 1;
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
const { discoverPlugins } =
|
|
1749
|
+
await import("../lib/plugin-runtime/scopes.js");
|
|
1750
|
+
const { VERSION } = await import("../constants.js");
|
|
1751
|
+
let config = null;
|
|
1752
|
+
try {
|
|
1753
|
+
const cm = await import("../lib/config-manager.js");
|
|
1754
|
+
config = cm.loadConfig();
|
|
1755
|
+
} catch {
|
|
1756
|
+
config = null;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
const sources = await Promise.all(
|
|
1760
|
+
registryUrls.map(async (url) => {
|
|
1761
|
+
try {
|
|
1762
|
+
const token = resolveRegistryToken(url, {
|
|
1763
|
+
token: options.token,
|
|
1764
|
+
config,
|
|
1765
|
+
});
|
|
1766
|
+
const resolved = await fetchRegistry(url, {
|
|
1767
|
+
token,
|
|
1768
|
+
allowInsecure: options.allowInsecureRegistry === true,
|
|
1769
|
+
});
|
|
1770
|
+
return { url, ...resolved };
|
|
1771
|
+
} catch (error) {
|
|
1772
|
+
return {
|
|
1773
|
+
url,
|
|
1774
|
+
error: {
|
|
1775
|
+
code: "REGISTRY_FETCH_FAILED",
|
|
1776
|
+
message: error.message,
|
|
1777
|
+
},
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
}),
|
|
1781
|
+
);
|
|
1782
|
+
|
|
1783
|
+
const installed = {};
|
|
1784
|
+
try {
|
|
1785
|
+
for (const pluginRow of discoverPlugins({
|
|
1786
|
+
cwd: process.cwd(),
|
|
1787
|
+
skipPolicy: true,
|
|
1788
|
+
})) {
|
|
1789
|
+
installed[pluginRow.name] = pluginRow.version;
|
|
1790
|
+
}
|
|
1791
|
+
} catch {
|
|
1792
|
+
/* dependency projection stays conservative with an empty inventory */
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
const catalog = buildPluginMarketplaceCatalog({
|
|
1796
|
+
sources,
|
|
1797
|
+
installed,
|
|
1798
|
+
hostVersion: VERSION,
|
|
1799
|
+
query,
|
|
1800
|
+
strict: options.strict === true,
|
|
1801
|
+
});
|
|
1802
|
+
if (options.json) {
|
|
1803
|
+
console.log(JSON.stringify(catalog, null, 2));
|
|
1804
|
+
} else {
|
|
1805
|
+
logger.log(
|
|
1806
|
+
chalk.bold(
|
|
1807
|
+
`Marketplace candidates (${catalog.summary.candidateCount}) — ${catalog.mode}`,
|
|
1808
|
+
),
|
|
1809
|
+
);
|
|
1810
|
+
for (const source of catalog.sources) {
|
|
1811
|
+
const mark =
|
|
1812
|
+
source.status === "online"
|
|
1813
|
+
? chalk.green("online")
|
|
1814
|
+
: source.status === "cached"
|
|
1815
|
+
? chalk.yellow("cached")
|
|
1816
|
+
: chalk.red("unavailable");
|
|
1817
|
+
logger.log(
|
|
1818
|
+
` source ${chalk.cyan(source.sourceId)} [${mark}] ${source.url}`,
|
|
1819
|
+
);
|
|
1820
|
+
if (source.error) {
|
|
1821
|
+
logger.log(
|
|
1822
|
+
chalk.red(` ${source.error.code}: ${source.error.message}`),
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
for (const candidate of catalog.candidates) {
|
|
1827
|
+
const allowed =
|
|
1828
|
+
candidate.installability.status === "allowed"
|
|
1829
|
+
? chalk.green("allowed")
|
|
1830
|
+
: chalk.red("blocked");
|
|
1831
|
+
logger.log(
|
|
1832
|
+
`\n ${chalk.cyan(candidate.name)} v${candidate.version || "?"} [${allowed}]`,
|
|
1833
|
+
);
|
|
1834
|
+
logger.log(
|
|
1835
|
+
` source: ${candidate.registry.sourceId} → ${candidate.package.source || "missing"}${candidate.package.ref ? `#${candidate.package.ref}` : ""}`,
|
|
1836
|
+
);
|
|
1837
|
+
logger.log(
|
|
1838
|
+
` digest (${candidate.integrity.digest.subject}): ${candidate.integrity.digest.status}${candidate.integrity.digest.value ? ` sha256:${candidate.integrity.digest.value}` : ""}`,
|
|
1839
|
+
);
|
|
1840
|
+
logger.log(
|
|
1841
|
+
` signature: ${candidate.integrity.signature.status} (${candidate.integrity.signature.verification})`,
|
|
1842
|
+
);
|
|
1843
|
+
logger.log(
|
|
1844
|
+
` SBOM: ${candidate.integrity.sbom.status} (${candidate.integrity.sbom.verification})`,
|
|
1845
|
+
);
|
|
1846
|
+
logger.log(
|
|
1847
|
+
` license: ${candidate.license.expression || candidate.license.status}`,
|
|
1848
|
+
);
|
|
1849
|
+
logger.log(
|
|
1850
|
+
` capabilities: ${candidate.capabilities.declared ? candidate.capabilities.summary.join("; ") || "declared none" : "missing"}`,
|
|
1851
|
+
);
|
|
1852
|
+
logger.log(
|
|
1853
|
+
` compatibility: ${candidate.compatibility.status}${candidate.compatibility.range ? ` (${candidate.compatibility.range})` : ""}; dependencies: ${candidate.dependencies.status}; health: ${candidate.health.status}`,
|
|
1854
|
+
);
|
|
1855
|
+
if (candidate.installability.blockers.length) {
|
|
1856
|
+
logger.log(
|
|
1857
|
+
chalk.red(
|
|
1858
|
+
` blockers: ${candidate.installability.blockers.map((item) => item.code).join(", ")}`,
|
|
1859
|
+
),
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
1862
|
+
if (candidate.governance.missing.length) {
|
|
1863
|
+
logger.log(
|
|
1864
|
+
chalk.yellow(
|
|
1865
|
+
` missing governance metadata: ${candidate.governance.missing.join(", ")}`,
|
|
1866
|
+
),
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
logger.log(
|
|
1871
|
+
chalk.gray(
|
|
1872
|
+
`\nCatalog ${catalog.catalogDigest}; registry metadata is unverified until install/load verification.`,
|
|
1873
|
+
),
|
|
1874
|
+
);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
if (catalog.summary.availableSourceCount === 0) {
|
|
1878
|
+
process.exitCode = 1;
|
|
1879
|
+
} else if (
|
|
1880
|
+
options.strict &&
|
|
1881
|
+
(catalog.summary.unavailableSourceCount > 0 ||
|
|
1882
|
+
catalog.summary.blockedCandidateCount > 0 ||
|
|
1883
|
+
catalog.summary.incompleteCandidateCount > 0)
|
|
1884
|
+
) {
|
|
1885
|
+
process.exitCode = 2;
|
|
1886
|
+
}
|
|
1887
|
+
});
|
|
1888
|
+
|
|
1889
|
+
// plugin select — deterministically select one exact plugin across a
|
|
1890
|
+
// requested registry set and expose the stable pre-clone authority digest.
|
|
1891
|
+
plugin
|
|
1892
|
+
.command("select <name>")
|
|
1893
|
+
.description(
|
|
1894
|
+
"Select the highest-ranked install candidate across an exact registry set",
|
|
1895
|
+
)
|
|
1896
|
+
.option(
|
|
1897
|
+
"--registry <url>",
|
|
1898
|
+
"Registry URL to include (repeatable; earlier sources win version ties)",
|
|
1899
|
+
collectRepeatableOption,
|
|
1900
|
+
[],
|
|
1901
|
+
)
|
|
1902
|
+
.option("--token <token>", "Bearer token for private registries")
|
|
1903
|
+
.option(
|
|
1904
|
+
"--allow-insecure-registry",
|
|
1905
|
+
"Allow plain-HTTP registry URLs (MITM risk — trusted networks only)",
|
|
1906
|
+
)
|
|
1907
|
+
.option(
|
|
1908
|
+
"--strict",
|
|
1909
|
+
"Block candidates missing digest, signature, SBOM, license, or capabilities",
|
|
1910
|
+
)
|
|
1911
|
+
.option("--json", "Output the versioned candidate selection as JSON")
|
|
1912
|
+
.action(async (name, options) => {
|
|
1913
|
+
try {
|
|
1914
|
+
const result = await buildRegistrySetSelection(
|
|
1915
|
+
normalizeRegistryUrls(options.registry),
|
|
1916
|
+
name,
|
|
1917
|
+
process.cwd(),
|
|
1918
|
+
{
|
|
1919
|
+
token: options.token,
|
|
1920
|
+
allowInsecure: options.allowInsecureRegistry === true,
|
|
1921
|
+
strict: options.strict === true,
|
|
1922
|
+
},
|
|
1923
|
+
);
|
|
1924
|
+
const { selection } = result;
|
|
1925
|
+
if (options.json) {
|
|
1926
|
+
console.log(JSON.stringify(selection, null, 2));
|
|
1927
|
+
} else {
|
|
1928
|
+
logger.log(
|
|
1929
|
+
chalk.bold(
|
|
1930
|
+
`${name}: ${selection.status} across ${selection.sourceCount} registries`,
|
|
1931
|
+
),
|
|
1932
|
+
);
|
|
1933
|
+
if (selection.selected) {
|
|
1934
|
+
logger.log(
|
|
1935
|
+
` selected: v${selection.selected.version || "manifest-deferred"} from ${selection.selected.registry.sourceId}`,
|
|
1936
|
+
);
|
|
1937
|
+
logger.log(
|
|
1938
|
+
` package: ${selection.selected.package.source || "missing"}${selection.selected.package.ref ? `#${selection.selected.package.ref}` : ""}`,
|
|
1939
|
+
);
|
|
1940
|
+
}
|
|
1941
|
+
if (selection.blockers.length) {
|
|
1942
|
+
logger.log(
|
|
1943
|
+
chalk.red(
|
|
1944
|
+
` blockers: ${selection.blockers.map((blocker) => blocker.code).join(", ")}`,
|
|
1945
|
+
),
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
logger.log(
|
|
1949
|
+
chalk.gray(` selection digest: ${selection.selectionDigest}`),
|
|
1950
|
+
);
|
|
1951
|
+
logger.log(
|
|
1952
|
+
chalk.gray(
|
|
1953
|
+
" Registry metadata is unverified; no candidate bytes were fetched or executed.",
|
|
1954
|
+
),
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
if (selection.status === "blocked") process.exitCode = 2;
|
|
1958
|
+
} catch (error) {
|
|
1959
|
+
logger.error(`Marketplace selection failed: ${error.message}`);
|
|
1960
|
+
process.exitCode = 1;
|
|
1961
|
+
}
|
|
1962
|
+
});
|
|
1963
|
+
|
|
1964
|
+
// plugin impact — compare one registry candidate with the active immutable
|
|
1965
|
+
// install before upgrade. This never clones or executes candidate bytes.
|
|
1966
|
+
plugin
|
|
1967
|
+
.command("impact <name>")
|
|
1968
|
+
.description(
|
|
1969
|
+
"Preview version, source, integrity, license, capability, and dependency impact before a registry upgrade",
|
|
1970
|
+
)
|
|
1971
|
+
.option(
|
|
1972
|
+
"--registry <url>",
|
|
1973
|
+
"Registry URL containing the candidate (repeatable)",
|
|
1974
|
+
collectRepeatableOption,
|
|
1975
|
+
[],
|
|
1976
|
+
)
|
|
1977
|
+
.option("--scope <scope>", "Installed plugin scope", "user")
|
|
1978
|
+
.option("--token <token>", "Bearer token for a private registry")
|
|
1979
|
+
.option(
|
|
1980
|
+
"--allow-insecure-registry",
|
|
1981
|
+
"Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
|
|
1982
|
+
)
|
|
1983
|
+
.option("--json", "Output the versioned update-impact projection as JSON")
|
|
1984
|
+
.action(async (name, options) => {
|
|
1985
|
+
try {
|
|
1986
|
+
const registryUrls = normalizeRegistryUrls(options.registry);
|
|
1987
|
+
const selected = await buildRegistrySetSelection(
|
|
1988
|
+
registryUrls,
|
|
1989
|
+
name,
|
|
1990
|
+
process.cwd(),
|
|
1991
|
+
{
|
|
1992
|
+
token: options.token,
|
|
1993
|
+
allowInsecure: options.allowInsecureRegistry === true,
|
|
1994
|
+
},
|
|
1995
|
+
);
|
|
1996
|
+
if (!selected.preflight) {
|
|
1997
|
+
throw new Error(
|
|
1998
|
+
`registry candidate preflight blocked: ${selected.selection.blockers.map((blocker) => blocker.code).join(", ") || "PLUGIN_NOT_FOUND"}`,
|
|
1999
|
+
);
|
|
2000
|
+
}
|
|
2001
|
+
const preflight = selected.preflight;
|
|
2002
|
+
const impact = await buildRegistryUpdateImpact(
|
|
2003
|
+
preflight,
|
|
2004
|
+
name,
|
|
2005
|
+
options.scope,
|
|
2006
|
+
process.cwd(),
|
|
2007
|
+
);
|
|
2008
|
+
if (options.json) {
|
|
2009
|
+
console.log(JSON.stringify(impact, null, 2));
|
|
2010
|
+
} else {
|
|
2011
|
+
logger.log(
|
|
2012
|
+
chalk.bold(
|
|
2013
|
+
`${name}: ${impact.status} (${impact.changeCount} material changes)`,
|
|
2014
|
+
),
|
|
2015
|
+
);
|
|
2016
|
+
logger.log(
|
|
2017
|
+
` version: ${impact.changes.version.from || "not installed"} → ${impact.changes.version.to || "manifest-deferred"} [${impact.changes.version.kind}]`,
|
|
2018
|
+
);
|
|
2019
|
+
logger.log(
|
|
2020
|
+
` source: ${impact.changes.source.kind}${impact.changes.source.requiresApproval ? " (explicit approval required)" : ""}`,
|
|
2021
|
+
);
|
|
2022
|
+
logger.log(
|
|
2023
|
+
` license: ${impact.changes.license.from || "unknown"} → ${impact.changes.license.to || "unknown"}`,
|
|
2024
|
+
);
|
|
2025
|
+
logger.log(
|
|
2026
|
+
` capabilities: +${impact.changes.capabilities.added.join(", ") || "none"}; -${impact.changes.capabilities.removed.join(", ") || "none"}`,
|
|
2027
|
+
);
|
|
2028
|
+
logger.log(
|
|
2029
|
+
` dependencies: +${impact.changes.dependencies.added.length} / -${impact.changes.dependencies.removed.length} / changed ${impact.changes.dependencies.changed.length}`,
|
|
2030
|
+
);
|
|
2031
|
+
if (impact.blockers.length) {
|
|
2032
|
+
logger.log(
|
|
2033
|
+
chalk.red(
|
|
2034
|
+
` blockers: ${impact.blockers.map((blocker) => blocker.code).join(", ")}`,
|
|
2035
|
+
),
|
|
2036
|
+
);
|
|
2037
|
+
}
|
|
2038
|
+
if (impact.requiredApprovals.length) {
|
|
2039
|
+
logger.log(
|
|
2040
|
+
chalk.yellow(
|
|
2041
|
+
` approvals: ${impact.requiredApprovals.map((approval) => approval.code).join(", ")}`,
|
|
2042
|
+
),
|
|
2043
|
+
);
|
|
2044
|
+
}
|
|
2045
|
+
logger.log(chalk.gray(` impact digest: ${impact.impactDigest}`));
|
|
2046
|
+
logger.log(
|
|
2047
|
+
chalk.gray(
|
|
2048
|
+
" Candidate registry metadata is unverified; no candidate bytes were fetched or executed.",
|
|
2049
|
+
),
|
|
2050
|
+
);
|
|
2051
|
+
}
|
|
2052
|
+
if (impact.status === "blocked") process.exitCode = 2;
|
|
2053
|
+
} catch (error) {
|
|
2054
|
+
logger.error(`Marketplace impact failed: ${error.message}`);
|
|
2055
|
+
process.exitCode = 1;
|
|
2056
|
+
}
|
|
2057
|
+
});
|
|
2058
|
+
|
|
1126
2059
|
// plugin installed — list runtime-installed plugins across scopes
|
|
1127
2060
|
plugin
|
|
1128
2061
|
.command("installed")
|
|
@@ -1167,6 +2100,103 @@ export function registerPluginCommand(program) {
|
|
|
1167
2100
|
}
|
|
1168
2101
|
});
|
|
1169
2102
|
|
|
2103
|
+
// plugin evidence — compare persisted registry artifact expectations with
|
|
2104
|
+
// actual bytes in the active immutable install. Remote artifacts are not
|
|
2105
|
+
// re-fetched here. The signature record is bound to the locally re-verified
|
|
2106
|
+
// lock; the external SBOM digest remains an install-time record because its
|
|
2107
|
+
// document bytes are not retained.
|
|
2108
|
+
plugin
|
|
2109
|
+
.command("evidence <name>")
|
|
2110
|
+
.description(
|
|
2111
|
+
"Read back installed manifest, license, signature, and payload-SBOM evidence",
|
|
2112
|
+
)
|
|
2113
|
+
.option("--scope <scope>", "Installed plugin scope", "user")
|
|
2114
|
+
.option(
|
|
2115
|
+
"--strict",
|
|
2116
|
+
"Return a non-zero status when any artifact expectation is partial",
|
|
2117
|
+
)
|
|
2118
|
+
.option("--json", "Output the versioned artifact readback as JSON")
|
|
2119
|
+
.action(async (name, options) => {
|
|
2120
|
+
try {
|
|
2121
|
+
const { listInstalled } =
|
|
2122
|
+
await import("../lib/plugin-runtime/install.js");
|
|
2123
|
+
const installed = listInstalled({
|
|
2124
|
+
cwd: process.cwd(),
|
|
2125
|
+
scopes: [options.scope],
|
|
2126
|
+
}).find(
|
|
2127
|
+
(candidate) =>
|
|
2128
|
+
candidate.name === name && candidate.scope === options.scope,
|
|
2129
|
+
);
|
|
2130
|
+
if (!installed) {
|
|
2131
|
+
throw new Error(`${name} is not installed at ${options.scope} scope`);
|
|
2132
|
+
}
|
|
2133
|
+
const { buildPluginMarketplaceArtifactReadback } =
|
|
2134
|
+
await import("../lib/plugin-runtime/marketplace-artifact-readback.js");
|
|
2135
|
+
const evidence = buildPluginMarketplaceArtifactReadback({
|
|
2136
|
+
root: installed.dir,
|
|
2137
|
+
scope: installed.scope,
|
|
2138
|
+
source: installed.source,
|
|
2139
|
+
});
|
|
2140
|
+
if (options.json) {
|
|
2141
|
+
console.log(JSON.stringify(evidence, null, 2));
|
|
2142
|
+
} else {
|
|
2143
|
+
logger.log(
|
|
2144
|
+
chalk.bold(
|
|
2145
|
+
`${name}: marketplace artifact evidence ${evidence.status}`,
|
|
2146
|
+
),
|
|
2147
|
+
);
|
|
2148
|
+
for (const [kind, comparison] of Object.entries(
|
|
2149
|
+
evidence.comparisons,
|
|
2150
|
+
)) {
|
|
2151
|
+
logger.log(
|
|
2152
|
+
` ${kind}: ${comparison.status}${comparison.comparable ? " (exact comparison)" : ""}`,
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2155
|
+
logger.log(
|
|
2156
|
+
` signature: ${evidence.actual.signature.verified ? "cryptographically verified" : evidence.actual.signature.reason}`,
|
|
2157
|
+
);
|
|
2158
|
+
logger.log(
|
|
2159
|
+
` payload SBOM: ${evidence.actual.payloadSbom.digest} (${evidence.actual.payloadSbom.fileCount} files)`,
|
|
2160
|
+
);
|
|
2161
|
+
logger.log(
|
|
2162
|
+
chalk.gray(` evidence digest: ${evidence.evidenceDigest}`),
|
|
2163
|
+
);
|
|
2164
|
+
const remoteKinds = [
|
|
2165
|
+
evidence.claims.remoteSignatureFetched ? "signature" : null,
|
|
2166
|
+
evidence.claims.remoteSbomFetched ? "SBOM" : null,
|
|
2167
|
+
].filter(Boolean);
|
|
2168
|
+
const remoteDetail = [
|
|
2169
|
+
evidence.claims.remoteSignatureFetched
|
|
2170
|
+
? `signature bytes/key ${
|
|
2171
|
+
evidence.claims.remoteSignatureBoundToInstalledLock
|
|
2172
|
+
? "are bound to the current installed lock"
|
|
2173
|
+
: "are not fully bound to the current installed lock"
|
|
2174
|
+
}`
|
|
2175
|
+
: null,
|
|
2176
|
+
evidence.claims.remoteSbomFetched
|
|
2177
|
+
? "the SBOM digest is an install-time record and was not re-hashed here"
|
|
2178
|
+
: null,
|
|
2179
|
+
].filter(Boolean);
|
|
2180
|
+
logger.log(
|
|
2181
|
+
chalk.gray(
|
|
2182
|
+
remoteKinds.length
|
|
2183
|
+
? ` Install-time remote ${remoteKinds.join("/")} evidence is present: ${remoteDetail.join("; ")}. Remote documents were not re-fetched; publisher identity remains unverified.`
|
|
2184
|
+
: " No valid install-time remote signature/SBOM evidence is available; publisher identity remains unverified.",
|
|
2185
|
+
),
|
|
2186
|
+
);
|
|
2187
|
+
}
|
|
2188
|
+
if (
|
|
2189
|
+
evidence.status === "failed" ||
|
|
2190
|
+
(options.strict && evidence.status !== "matched")
|
|
2191
|
+
) {
|
|
2192
|
+
process.exitCode = 2;
|
|
2193
|
+
}
|
|
2194
|
+
} catch (error) {
|
|
2195
|
+
logger.error(`Marketplace artifact evidence failed: ${error.message}`);
|
|
2196
|
+
process.exitCode = 1;
|
|
2197
|
+
}
|
|
2198
|
+
});
|
|
2199
|
+
|
|
1170
2200
|
// plugin trust <name> — allow a plugin's code-bearing components to run
|
|
1171
2201
|
plugin
|
|
1172
2202
|
.command("trust <name>")
|
|
@@ -1574,7 +2604,9 @@ export function registerPluginCommand(program) {
|
|
|
1574
2604
|
.option("--public-key <path>", "Public key for signature verification")
|
|
1575
2605
|
.option(
|
|
1576
2606
|
"--registry <url>",
|
|
1577
|
-
"
|
|
2607
|
+
"Registry URL used to resolve <source> as a plugin name (repeatable)",
|
|
2608
|
+
collectRepeatableOption,
|
|
2609
|
+
[],
|
|
1578
2610
|
)
|
|
1579
2611
|
.option(
|
|
1580
2612
|
"--name <plugin>",
|
|
@@ -1585,6 +2617,19 @@ export function registerPluginCommand(program) {
|
|
|
1585
2617
|
"--allow-insecure-registry",
|
|
1586
2618
|
"Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
|
|
1587
2619
|
)
|
|
2620
|
+
.option(
|
|
2621
|
+
"--allow-source-switch",
|
|
2622
|
+
"Explicitly approve changing registry/source authority",
|
|
2623
|
+
)
|
|
2624
|
+
.option("--allow-downgrade", "Explicitly approve a version downgrade")
|
|
2625
|
+
.option(
|
|
2626
|
+
"--expected-impact-digest <sha256>",
|
|
2627
|
+
"Require the exact digest from a prior `cc plugin impact` review",
|
|
2628
|
+
)
|
|
2629
|
+
.option(
|
|
2630
|
+
"--expected-selection-digest <sha256>",
|
|
2631
|
+
"Require the exact digest from a prior multi-registry `cc plugin select` review",
|
|
2632
|
+
)
|
|
1588
2633
|
.option(
|
|
1589
2634
|
"--grant-capabilities",
|
|
1590
2635
|
"Grant any newly declared capabilities during the upgrade (no separate consent step)",
|
|
@@ -1596,27 +2641,123 @@ export function registerPluginCommand(program) {
|
|
|
1596
2641
|
let installSource = source;
|
|
1597
2642
|
let integritySha = null;
|
|
1598
2643
|
let sourceMetadata = null;
|
|
2644
|
+
let expectedIdentity = null;
|
|
2645
|
+
let marketplacePreflight = null;
|
|
2646
|
+
let marketplaceImpact = null;
|
|
2647
|
+
let remoteArtifactRequest = null;
|
|
2648
|
+
let remoteArtifacts = null;
|
|
2649
|
+
const registryUrls = normalizeRegistryUrls(options.registry);
|
|
1599
2650
|
const { isRemoteSource, resolveRemoteSource } =
|
|
1600
2651
|
await import("../lib/plugin-runtime/remote-source.js");
|
|
1601
|
-
if (
|
|
1602
|
-
const url = options.registry || source;
|
|
1603
|
-
const name = options.registry ? source : options.name;
|
|
1604
|
-
let config = null;
|
|
1605
|
-
try {
|
|
1606
|
-
const cm = await import("../lib/config-manager.js");
|
|
1607
|
-
config = cm.loadConfig();
|
|
1608
|
-
} catch {
|
|
1609
|
-
config = null;
|
|
1610
|
-
}
|
|
2652
|
+
if (registryUrls.length || isRemoteSource(source)) {
|
|
1611
2653
|
try {
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
2654
|
+
let resolved;
|
|
2655
|
+
let url;
|
|
2656
|
+
let name;
|
|
2657
|
+
if (registryUrls.length) {
|
|
2658
|
+
name = source;
|
|
2659
|
+
const selected = await buildRegistrySetSelection(
|
|
2660
|
+
registryUrls,
|
|
2661
|
+
name,
|
|
2662
|
+
process.cwd(),
|
|
2663
|
+
{
|
|
2664
|
+
token: options.token,
|
|
2665
|
+
allowInsecure: options.allowInsecureRegistry === true,
|
|
2666
|
+
},
|
|
2667
|
+
);
|
|
2668
|
+
resolved = selected.resolved;
|
|
2669
|
+
marketplacePreflight = selected.preflight;
|
|
2670
|
+
url = resolved?.registryUrl;
|
|
2671
|
+
if (!marketplacePreflight || !resolved) {
|
|
2672
|
+
throw new Error(
|
|
2673
|
+
`registry candidate preflight blocked: ${selected.selection.blockers.map((blocker) => blocker.code).join(", ") || "PLUGIN_NOT_FOUND"}`,
|
|
2674
|
+
);
|
|
2675
|
+
}
|
|
2676
|
+
} else {
|
|
2677
|
+
url = source;
|
|
2678
|
+
name = options.name;
|
|
2679
|
+
const config = await loadOptionalPluginConfig();
|
|
2680
|
+
resolved = await resolveRemoteSource(url, {
|
|
2681
|
+
name,
|
|
2682
|
+
token: options.token,
|
|
2683
|
+
config,
|
|
2684
|
+
allowInsecure: options.allowInsecureRegistry === true,
|
|
2685
|
+
});
|
|
2686
|
+
marketplacePreflight = await buildRegistryInstallPreflight(
|
|
2687
|
+
url,
|
|
2688
|
+
resolved,
|
|
2689
|
+
process.cwd(),
|
|
2690
|
+
);
|
|
2691
|
+
}
|
|
1618
2692
|
installSource = resolved.source;
|
|
1619
2693
|
integritySha = resolved.sha256;
|
|
2694
|
+
if (registryManifestDigestConflict(integritySha, options.sha256)) {
|
|
2695
|
+
throw new Error(
|
|
2696
|
+
"registry artifact verification failed: --sha256 does not match the registry-declared manifest SHA-256",
|
|
2697
|
+
);
|
|
2698
|
+
}
|
|
2699
|
+
if (marketplacePreflight.status !== "allowed") {
|
|
2700
|
+
throw new Error(
|
|
2701
|
+
`registry candidate preflight blocked: ${marketplacePreflightBlockerMessage(marketplacePreflight)}`,
|
|
2702
|
+
);
|
|
2703
|
+
}
|
|
2704
|
+
assertExpectedSelectionDigest(
|
|
2705
|
+
options.expectedSelectionDigest,
|
|
2706
|
+
marketplacePreflight,
|
|
2707
|
+
);
|
|
2708
|
+
marketplaceImpact = await buildRegistryUpdateImpact(
|
|
2709
|
+
marketplacePreflight,
|
|
2710
|
+
resolved.entry.name,
|
|
2711
|
+
options.scope,
|
|
2712
|
+
process.cwd(),
|
|
2713
|
+
);
|
|
2714
|
+
if (
|
|
2715
|
+
marketplaceImpact.changes.source.requiresApproval &&
|
|
2716
|
+
options.allowSourceSwitch !== true
|
|
2717
|
+
) {
|
|
2718
|
+
throw new Error(
|
|
2719
|
+
`registry candidate preflight blocked: SOURCE_SWITCH_APPROVAL_REQUIRED (${marketplaceImpact.changes.source.kind}); pass --allow-source-switch after reviewing cc plugin impact`,
|
|
2720
|
+
);
|
|
2721
|
+
}
|
|
2722
|
+
if (
|
|
2723
|
+
marketplaceImpact.changes.version.kind === "downgrade" &&
|
|
2724
|
+
options.allowDowngrade !== true
|
|
2725
|
+
) {
|
|
2726
|
+
throw new Error(
|
|
2727
|
+
"registry candidate preflight blocked: VERSION_DOWNGRADE_APPROVAL_REQUIRED; pass --allow-downgrade after reviewing cc plugin impact",
|
|
2728
|
+
);
|
|
2729
|
+
}
|
|
2730
|
+
if (
|
|
2731
|
+
options.expectedImpactDigest &&
|
|
2732
|
+
!/^[a-f0-9]{64}$/.test(options.expectedImpactDigest)
|
|
2733
|
+
) {
|
|
2734
|
+
throw new Error(
|
|
2735
|
+
"registry candidate preflight blocked: INVALID_EXPECTED_IMPACT_DIGEST",
|
|
2736
|
+
);
|
|
2737
|
+
}
|
|
2738
|
+
if (
|
|
2739
|
+
options.expectedImpactDigest &&
|
|
2740
|
+
options.expectedImpactDigest !== marketplaceImpact.impactDigest
|
|
2741
|
+
) {
|
|
2742
|
+
throw new Error(
|
|
2743
|
+
`registry candidate preflight blocked: UPDATE_IMPACT_DIGEST_MISMATCH (expected ${options.expectedImpactDigest}, actual ${marketplaceImpact.impactDigest})`,
|
|
2744
|
+
);
|
|
2745
|
+
}
|
|
2746
|
+
expectedIdentity = {
|
|
2747
|
+
name:
|
|
2748
|
+
typeof resolved.entry.name === "string"
|
|
2749
|
+
? resolved.entry.name
|
|
2750
|
+
: null,
|
|
2751
|
+
version:
|
|
2752
|
+
marketplacePreflight.versionAuthority ===
|
|
2753
|
+
"registry-declared-unverified"
|
|
2754
|
+
? marketplacePreflight.registryVersion
|
|
2755
|
+
: null,
|
|
2756
|
+
};
|
|
2757
|
+
remoteArtifactRequest = {
|
|
2758
|
+
registryUrl: url,
|
|
2759
|
+
entry: resolved.entry,
|
|
2760
|
+
};
|
|
1620
2761
|
sourceMetadata = {
|
|
1621
2762
|
type: "registry",
|
|
1622
2763
|
source: url,
|
|
@@ -1629,6 +2770,10 @@ export function registerPluginCommand(program) {
|
|
|
1629
2770
|
resolved.source.slice(resolved.source.indexOf("#") + 1)) ||
|
|
1630
2771
|
null,
|
|
1631
2772
|
offline: resolved.fromCache === true,
|
|
2773
|
+
catalogAuthority: catalogAuthorityFromPreflight(
|
|
2774
|
+
marketplacePreflight,
|
|
2775
|
+
marketplaceImpact,
|
|
2776
|
+
),
|
|
1632
2777
|
};
|
|
1633
2778
|
if (resolved.fromCache && !options.json) {
|
|
1634
2779
|
logger.warn(
|
|
@@ -1655,34 +2800,94 @@ export function registerPluginCommand(program) {
|
|
|
1655
2800
|
const requiresManagedSignature =
|
|
1656
2801
|
managed?.requireSignedPlugins === true ||
|
|
1657
2802
|
managed?.requireSignedPlugins === "require";
|
|
2803
|
+
try {
|
|
2804
|
+
if (remoteArtifactRequest) {
|
|
2805
|
+
remoteArtifacts = await fetchRegistryRemoteArtifacts(
|
|
2806
|
+
remoteArtifactRequest.registryUrl,
|
|
2807
|
+
remoteArtifactRequest.entry,
|
|
2808
|
+
{
|
|
2809
|
+
token: options.token,
|
|
2810
|
+
allowInsecure: options.allowInsecureRegistry === true,
|
|
2811
|
+
},
|
|
2812
|
+
);
|
|
2813
|
+
if (
|
|
2814
|
+
remoteArtifacts?.signatureFile &&
|
|
2815
|
+
(options.signature || options.publicKey)
|
|
2816
|
+
) {
|
|
2817
|
+
throw new Error(
|
|
2818
|
+
"registry remote signature artifacts cannot be combined with --signature/--public-key",
|
|
2819
|
+
);
|
|
2820
|
+
}
|
|
2821
|
+
if (remoteArtifacts?.authority && sourceMetadata?.catalogAuthority) {
|
|
2822
|
+
sourceMetadata.catalogAuthority = catalogAuthorityFromPreflight(
|
|
2823
|
+
marketplacePreflight,
|
|
2824
|
+
marketplaceImpact,
|
|
2825
|
+
remoteArtifacts.authority,
|
|
2826
|
+
);
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
} catch (err) {
|
|
2830
|
+
cleanupRegistryRemoteArtifacts(remoteArtifacts);
|
|
2831
|
+
logger.error(`Registry artifact verification failed: ${err.message}`);
|
|
2832
|
+
process.exitCode = 1;
|
|
2833
|
+
return;
|
|
2834
|
+
}
|
|
1658
2835
|
const signature =
|
|
1659
2836
|
options.sha256 ||
|
|
1660
2837
|
options.signature ||
|
|
1661
2838
|
options.publicKey ||
|
|
2839
|
+
remoteArtifacts?.signatureFile ||
|
|
2840
|
+
remoteArtifacts?.publicKeyFile ||
|
|
1662
2841
|
integritySha ||
|
|
1663
2842
|
requiresManagedSignature
|
|
1664
2843
|
? {
|
|
1665
2844
|
sha256: options.sha256 || integritySha,
|
|
1666
|
-
signatureFile:
|
|
1667
|
-
|
|
2845
|
+
signatureFile:
|
|
2846
|
+
remoteArtifacts?.signatureFile || options.signature,
|
|
2847
|
+
publicKeyFile:
|
|
2848
|
+
remoteArtifacts?.publicKeyFile || options.publicKey,
|
|
2849
|
+
expectedSignatureSha256:
|
|
2850
|
+
remoteArtifacts?.authority?.signature?.signatureSha256 || null,
|
|
2851
|
+
expectedPublicKeyDocumentSha256:
|
|
2852
|
+
remoteArtifacts?.authority?.signature?.publicKey
|
|
2853
|
+
?.documentSha256 || null,
|
|
2854
|
+
expectedPublicKeySha256:
|
|
2855
|
+
remoteArtifacts?.authority?.signature?.publicKey?.spkiSha256 ||
|
|
2856
|
+
null,
|
|
1668
2857
|
requireSignature:
|
|
1669
|
-
Boolean(options.signature) ||
|
|
2858
|
+
Boolean(remoteArtifacts?.signatureFile || options.signature) ||
|
|
2859
|
+
requiresManagedSignature,
|
|
1670
2860
|
trustedKeySha256: managed?.trustedPluginKeySha256 || null,
|
|
1671
2861
|
requireTrustedKey: requiresManagedSignature,
|
|
1672
2862
|
}
|
|
1673
2863
|
: null;
|
|
1674
2864
|
let res = null;
|
|
2865
|
+
let marketplaceAuthorityPersisted = null;
|
|
1675
2866
|
try {
|
|
1676
2867
|
res = updatePlugin(installSource, {
|
|
1677
2868
|
scope: options.scope,
|
|
1678
2869
|
cwd: process.cwd(),
|
|
1679
|
-
force: options.force,
|
|
2870
|
+
force: options.force === true || Boolean(remoteArtifacts?.authority),
|
|
1680
2871
|
signature,
|
|
1681
2872
|
sourceMetadata,
|
|
2873
|
+
expectedIdentity,
|
|
1682
2874
|
managedPolicy: managed,
|
|
1683
|
-
policySource:
|
|
2875
|
+
policySource: sourceMetadata?.registry || source,
|
|
1684
2876
|
transactional: true,
|
|
1685
2877
|
});
|
|
2878
|
+
marketplaceAuthorityPersisted = await installedCatalogAuthorityMatches(
|
|
2879
|
+
res.name,
|
|
2880
|
+
options.scope,
|
|
2881
|
+
process.cwd(),
|
|
2882
|
+
marketplacePreflight,
|
|
2883
|
+
marketplaceImpact,
|
|
2884
|
+
remoteArtifacts?.authority || null,
|
|
2885
|
+
);
|
|
2886
|
+
if (marketplacePreflight && !marketplaceAuthorityPersisted) {
|
|
2887
|
+
throw new Error(
|
|
2888
|
+
"registry candidate matched existing immutable bytes without the exact catalog authority; retry with --force to verify and persist this source",
|
|
2889
|
+
);
|
|
2890
|
+
}
|
|
1686
2891
|
const upgradeNotice = await resolvePluginCapabilityNotice(
|
|
1687
2892
|
res.name,
|
|
1688
2893
|
options.scope,
|
|
@@ -1739,6 +2944,15 @@ export function registerPluginCommand(program) {
|
|
|
1739
2944
|
}
|
|
1740
2945
|
activationStatus = "rolled_back";
|
|
1741
2946
|
rollbackVersion = recovery.version;
|
|
2947
|
+
marketplaceAuthorityPersisted =
|
|
2948
|
+
await installedCatalogAuthorityMatches(
|
|
2949
|
+
res.name,
|
|
2950
|
+
options.scope,
|
|
2951
|
+
process.cwd(),
|
|
2952
|
+
marketplacePreflight,
|
|
2953
|
+
marketplaceImpact,
|
|
2954
|
+
remoteArtifacts?.authority || null,
|
|
2955
|
+
);
|
|
1742
2956
|
} else {
|
|
1743
2957
|
cleanupPending = finalizePluginUpdate(res).cleanupPending === true;
|
|
1744
2958
|
}
|
|
@@ -1749,6 +2963,9 @@ export function registerPluginCommand(program) {
|
|
|
1749
2963
|
{
|
|
1750
2964
|
...res,
|
|
1751
2965
|
scope: options.scope,
|
|
2966
|
+
marketplacePreflight,
|
|
2967
|
+
marketplaceImpact,
|
|
2968
|
+
marketplaceAuthorityPersisted,
|
|
1752
2969
|
capabilities: upgradeNotice,
|
|
1753
2970
|
capabilitiesGranted,
|
|
1754
2971
|
activationStatus,
|
|
@@ -1763,6 +2980,24 @@ export function registerPluginCommand(program) {
|
|
|
1763
2980
|
return;
|
|
1764
2981
|
}
|
|
1765
2982
|
|
|
2983
|
+
if (marketplacePreflight) {
|
|
2984
|
+
logger.log(
|
|
2985
|
+
chalk.gray(
|
|
2986
|
+
` catalog: ${marketplacePreflight.catalogDigest} (${marketplacePreflight.governance.status}; ${marketplaceAuthorityPersisted ? "authority active" : "activation rolled back"})`,
|
|
2987
|
+
),
|
|
2988
|
+
);
|
|
2989
|
+
if (marketplacePreflight.governance.missing.length) {
|
|
2990
|
+
logger.log(
|
|
2991
|
+
chalk.yellow(
|
|
2992
|
+
` ⚠ registry governance metadata missing: ${marketplacePreflight.governance.missing.join(", ")}`,
|
|
2993
|
+
),
|
|
2994
|
+
);
|
|
2995
|
+
}
|
|
2996
|
+
logger.log(
|
|
2997
|
+
chalk.gray(` reviewed impact: ${marketplaceImpact.impactDigest}`),
|
|
2998
|
+
);
|
|
2999
|
+
}
|
|
3000
|
+
|
|
1766
3001
|
if (activationStatus === "rolled_back") {
|
|
1767
3002
|
logger.warn(
|
|
1768
3003
|
`Upgrade not activated: ${rollbackReason.replaceAll("_", " ")}; ` +
|
|
@@ -1799,6 +3034,8 @@ export function registerPluginCommand(program) {
|
|
|
1799
3034
|
}
|
|
1800
3035
|
logger.error(`Upgrade failed: ${err.message}${recoveryNote}`);
|
|
1801
3036
|
process.exitCode = 1;
|
|
3037
|
+
} finally {
|
|
3038
|
+
cleanupRegistryRemoteArtifacts(remoteArtifacts);
|
|
1802
3039
|
}
|
|
1803
3040
|
});
|
|
1804
3041
|
|