chainlesschain 0.164.0 → 0.165.2
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/README.md +248 -2
- package/package.json +7 -4
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-D2MEn7fI.js → AIOps-lS1R0Cza.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-lCW_4Gf0.js → ActionButton-BmlgBPsj.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Bs7i-nsS.js → Analytics-CNKczv1S.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CK7B6S_j.js → AppLayout-BuZGJARQ.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-C8ILjpyo.js → Artifacts-Dm73Sl-O.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bb9cNaWc.js → Audit-BGMW7A5e.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-sC9ooXIf.js → BackgroundAgents-BQ6M54JR.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-D-DrsplT.js → Backup-DBzGP0bR.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-uCNZpwSK.js → BaseInput-D1mLCoOV.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CsDK1uE9.js → Chat-BUfcN7p8.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-OqSDt50w.js → ChatBubbleRenderer-CY5m8ZHg.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-JhTXM1-K.js → Checkbox-Du0Yg5Pi.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BbzZLnyD.js → Codegen-B2RXziHW.js} +1 -1
- package/src/assets/web-panel/assets/{Col-D4VW4m4j.js → Col-DWhekOrR.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BXrYawxV.js → Community-HQhrkon4.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DCihGy1u.js → Compact-izHNb4uz.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DlyZ-lk8.js → Compliance-BDwLy41-.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DvGZrhbO.js → Cowork-DFJhLa7K.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DUncZi8h.js → Cron-DYY9RG7-.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-C_hLuUcm.js → Crosschain-rr7tJTtv.js} +1 -1
- package/src/assets/web-panel/assets/{DID-b6YSUqxr.js → DID-D2sfHRLi.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-uvM15Cfb.js → Dashboard-q_eGdhha.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-D1-Nd4qY.js → Dropdown-Cufrh2Bz.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-8ApvL6Zc.js → EmailListRenderer-B-kdv10f.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-LYJt8811.js → FamilyGuardDashboard-D21XjgfG.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BbI3HGpv.js → Federation-CAwRTLZH.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DSoEKuFu.js → FormItemContext-vx3daNDF.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-B0S9OWd5.js → GenericCardRenderer-CRWY2wU4.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DIs9Zhf3.js → Git-Q6SwauyD.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-jzZXrxcl.js → Governance-BJ1q4IQZ.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-JUPjf2ZI.js → Inference-CSWjROgD.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DxnfJvlP.js → KnowledgeGraph-fzuQsWuY.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-iXiz8BEE.js → Logs-DVF8Mnns.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Y3Xch312.js → Marketplace-BQP7LOA4.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D7kGGbO7.js → McpTools-BNlJCmNc.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-Do_hVxzh.js → Memory-DLLg5WQ4.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CtrR0Tas.js → MobileBridge-CR2skNAF.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-BxVavObl.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-qo5QCTGd.js → Mtc-B17pgqMI.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-cq5mnjYj.js → MtcAudit-4UmV5cIL.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Bq0MAQHw.js → Multisig-DoMEXEcU.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DdWE6Vi2.js → NLProgramming-h9DMXFX4.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BxMBFcIp.js → Notes-Cihn-xX8.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-RTrAcbUe.js → NotificationSettings-BE6ZGa3R.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DJbpGqzT.js → OrderTableRenderer-CnwojoSI.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-SQzIPdqC.js → Organization-B2vpwmjb.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-02yECh22.js → Overflow-C4z28R0-.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BNg4pNCM.js → P2P-BVlFkwhW.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BxPJZsD3.js → PdhVaultBrowser-BXbEdL5B.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CLnDnPAz.js → Permissions-C28xs4lp.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-C5m0AjGf.js → PersonalDataHub-Dgl_rBjM.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-VwOf5Dhv.js → Pipeline-3QUIhrKI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CC0W2hZd.js → Privacy-8Gh33OZ5.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Dz893Bob.js → ProjectInit-BNmg4tZi.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CxQkvl0k.js → ProjectSettings-vgMfeCf9.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BhtAVilc.js +1 -0
- package/src/assets/web-panel/assets/{Providers-DeB9IkxK.js → Providers-CXoOa8eX.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-OCeoaswn.js → QrScannerModal-BhfTF0oS.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DenotaAj.js → QuickAsk-C746HEAj.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cies_jjI.js → Recommend-BSej8Eo0.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-f3gmnlG3.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-CW0mvxgZ.js → Reputation-CyvybpkN.js} +1 -1
- package/src/assets/web-panel/assets/{Row-SEd32qZU.js → Row-DwV2PeYn.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BSwnJ3Lm.js → RssFeed-11GrzjZy.js} +3 -3
- package/src/assets/web-panel/assets/{Search-xe2m8bkC.js → Search-BjKXkrAY.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Mq2F9Nro.js → Security-eWiFb9Yk.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BKGw2V3P.js → Services-CdCsMAtt.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DW95-Rxf.js → Skeleton-4Q2UTKQ5.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CHgChCv_.js → Skills-v4rtYrRj.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-tKh6k3R7.js → Sla-CRhsFvd_.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-D0Fw8fVL.js → SyncSettings-D16zonl7.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-20x1GB3D.js → Tasks-Xxozz0i_.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DJ-lts-a.js → Templates-CXJpPtCT.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BFdGpbs7.js → Tenant-XRV0L1ZF.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-Xpx61vHL.js → Terminal-CYuO1i_w.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-C6UIvyZQ.js → TimelineRenderer-BXqitWsu.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Dellq08f.js → Tokens-BvPrDHIy.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-VKw8d0J-.js → Trigger-DhCrSTf5.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DNftdKT5.js → Trust-DmsuZD6K.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DX9VAKJI.js → UkeySign-D_yrts5C.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B2h3jO8q.js → VideoEditing-B01ihPrm.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CjGkof7f.js → Wallet-DpUjKa-N.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-2Zfcu55P.js → WebAuthn-CL07yON6.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-B9eZ8UxZ.js → WorkflowEditor-CLMflnRr.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CQNfD-YO.js → chat-DtUqe8h_.js} +1 -1
- package/src/assets/web-panel/assets/{colors-D4usyFT2.js → colors-DPKrgVfy.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-s4TpjvjX.js → compact-item-BTt2GtOm.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BRCSvYxj.js → createContext-D_VeUc1J.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-AdkUbkV-.js → hasIn-1kndiylj.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIoG5yYf.js → index-2eyAXE3d.js} +1 -1
- package/src/assets/web-panel/assets/{index-nXyk8GZ9.js → index-4bna2_SR.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2V-eKfK.js → index-8nz_fmlj.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_XBHopw.js → index-B9KvcS7R.js} +1 -1
- package/src/assets/web-panel/assets/{index-s_buFMEG.js → index-BAXfRKfG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZT36ZYq.js → index-BHpBMWCh.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMcimU-2.js → index-BaZAbw9A.js} +1 -1
- package/src/assets/web-panel/assets/{index-DzRjHxEv.js → index-BepLZwgB.js} +1 -1
- package/src/assets/web-panel/assets/{index--00U6vXZ.js → index-Bhudc_Ez.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7Y8Aluc.js → index-BlDNVgBt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtobShro.js → index-By5n8up9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cvyt-zhm.js → index-CHG-KcS4.js} +1 -1
- package/src/assets/web-panel/assets/index-CPcM-eqo.js +1 -0
- package/src/assets/web-panel/assets/{index-MMN9V6JF.js → index-CX81qSM4.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bad4VNvz.js → index-CauoBQkH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCsM_Ybx.js → index-CcO04eDn.js} +1 -1
- package/src/assets/web-panel/assets/{index-DARsAyUp.js → index-CtF7EN49.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5KAQFPQ.js → index-CzjkKUmR.js} +1 -1
- package/src/assets/web-panel/assets/{index-RfMr79MW.js → index-D95OFht7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdCB70SS.js → index-DFIZ_1Sf.js} +1 -1
- package/src/assets/web-panel/assets/{index-CYdoQsa1.js → index-DG2MZQ3f.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPhkFZ1I.js → index-DJ8vXGOa.js} +1 -1
- package/src/assets/web-panel/assets/{index-_IRVsV9X.js → index-Dbbymxpj.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVKfQ0Ux.js → index-DdkplL9R.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkwiSLhU.js → index-DfGl8CqG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJVEOa4i.js → index-DfKBRdSS.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1mJtE6q.js → index-DsJCDUJG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgiXR_-_.js → index-Dwqmtk65.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJCFe2Qf.js → index-DxdTdBix.js} +1 -1
- package/src/assets/web-panel/assets/{index-C484EJiS.js → index-FzdwcSy2.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4JJCfQ2.js → index-GsoUQ2M7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQhAVU5t.js → index-brCInrzq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKC1t3Ic.js → index-dVc-CXY4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQ3DXBtD.js → index-fk2Rjacw.js} +3 -3
- package/src/assets/web-panel/assets/{index-Cduu2Hhr.js → index-l1BETrDb.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bw2MEnUk.js → index-m31Ka8hZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2GjI2oe.js → index-ocf06EVQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJdQrZth.js → index-sDoMHTNA.js} +1 -1
- package/src/assets/web-panel/assets/index-xeSyVoRe.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-Blmpk7HL.js → initDefaultProps-CvyYJd7z.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DPBJMv1n.js → motion-DYZkhUdz.js} +1 -1
- package/src/assets/web-panel/assets/{move-ButdzCM7.js → move-slnJRVT1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-cyRC_QCe.js → mtc-parser-DVc0CKmy.js} +1 -1
- package/src/assets/web-panel/assets/{omit-1epSV7hz.js → omit-BCXAtHCq.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DuPY8xJs.js → pickAttrs-DBsemz_K.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-6XZqarhe.js → placementArrow-BRtkJ-nv.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B40pXBWM.js → responsiveObserve-Cebq7EZn.js} +1 -1
- package/src/assets/web-panel/assets/{slide-jbZ9Wu4n.js → slide-DT7qvId2.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BKQQcVy4.js → statusUtils-pQiITLkG.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-NiKfWjUT.js → styleChecker-c-19BVKQ.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-bQUCvPeb.js → useFlexGapSupport-DsS5-0bE.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CUGji7ph.js → useFs-CxGiMe5n.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-4veyN2N2.js → usePersonalDataHub-D9btVID5.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-1-nLug-Z.js → vnode-CUYFsFEN.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-dhCeRDMo.js → zoom-CXmn_35j.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/cowork.js +559 -62
- package/src/commands/plugin.js +819 -64
- package/src/commands/session-location.js +318 -7
- package/src/constants.js +2 -2
- package/src/data/changelog.json +28 -2
- package/src/gateways/ws/action-protocol.js +293 -19
- package/src/gateways/ws/message-dispatcher.js +30 -0
- package/src/gateways/ws/remote-session-protocol.js +542 -35
- package/src/gateways/ws/ws-approval-gate.js +5 -0
- package/src/gateways/ws/ws-server.js +462 -38
- package/src/harness/jsonl-session-store.js +638 -2
- package/src/harness/prompt-compressor.js +4 -0
- package/src/harness/remote-session-audit.js +1 -0
- package/src/harness/remote-session-crypto.js +750 -50
- package/src/harness/remote-session-registry.js +795 -27
- package/src/lib/auto-mode-config.js +98 -3
- package/src/lib/background-agent-keeper-client.js +29 -0
- package/src/lib/background-agent-keeper-protocol.js +158 -10
- package/src/lib/background-agent-supervisor.js +870 -48
- package/src/lib/background-session-transport.js +23 -6
- package/src/lib/background-turn-bootstrap-protocol.js +64 -0
- package/src/lib/cowork-task-runner.js +70 -0
- package/src/lib/cowork-workflow.js +887 -62
- package/src/lib/delivery-production-adapter.js +38 -2
- package/src/lib/doctor-checkup.js +34 -10
- package/src/lib/dynamic-workflow-draft.js +535 -0
- package/src/lib/dynamic-workflow-facade.js +1105 -17
- package/src/lib/dynamic-workflow-runtime.js +2974 -0
- package/src/lib/execution-location-contract.js +111 -7
- package/src/lib/execution-location-runtime.js +3 -1
- package/src/lib/execution-location-target.js +910 -0
- package/src/lib/llm-providers.js +1 -0
- package/src/lib/mcp-call-ledger-store.js +35 -0
- package/src/lib/mcp-call-ledger.js +30 -0
- package/src/lib/mcp-stdio-package-materialization.js +1 -1
- package/src/lib/plugin-runtime/__tests__/remote-source.test.js +34 -16
- package/src/lib/plugin-runtime/install.js +4956 -657
- package/src/lib/plugin-runtime/manifest.js +3 -1
- package/src/lib/plugin-runtime/marketplace-artifact-readback.js +671 -31
- package/src/lib/plugin-runtime/marketplace-catalog.js +68 -2
- package/src/lib/plugin-runtime/marketplace-impact.js +208 -15
- package/src/lib/plugin-runtime/marketplace-network.js +317 -0
- package/src/lib/plugin-runtime/marketplace-pac-worker.js +15 -0
- package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +25 -2
- package/src/lib/plugin-runtime/marketplace-source-cache.js +359 -0
- package/src/lib/plugin-runtime/policy.js +21 -6
- package/src/lib/plugin-runtime/publisher-trust.js +332 -0
- package/src/lib/plugin-runtime/remote-source.js +323 -27
- package/src/lib/plugin-runtime/scopes.js +317 -41
- package/src/lib/plugin-runtime/transaction-journal.js +806 -0
- package/src/lib/public-ide-capability-manifest.js +1 -1
- package/src/lib/remote-approval-bridge.js +745 -108
- package/src/lib/remote-control.js +2 -0
- package/src/lib/remote-membership-coordinator.js +612 -46
- package/src/lib/remote-membership-host-store.js +59 -10
- package/src/lib/runtime-provenance-ledger.js +171 -46
- package/src/lib/shell-approval.js +169 -6
- package/src/lib/sub-agent-context.js +268 -2
- package/src/lib/task-model-selector.js +1 -1
- package/src/lib/with-file-lock.js +167 -42
- package/src/repl/agent-repl.js +70 -29
- package/src/repl/remote-approval.js +29 -7
- package/src/runtime/agent-core.js +1695 -194
- package/src/runtime/headless-runner.js +22 -5
- package/src/runtime/headless-stream.js +19 -3
- package/src/workers/background-agent-keeper.js +279 -58
- package/src/workers/background-agent-worker.js +7 -0
- package/src/workers/background-turn-bootstrap.js +29 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-GmB6DOr6.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +0 -1
- package/src/assets/web-panel/assets/Projects-DDAjkUkT.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-Dxzl5n2T.js +0 -5
- package/src/assets/web-panel/assets/SpeechSettings-D6UL3dA5.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +0 -1
- package/src/assets/web-panel/assets/index-C61cnl3K.js +0 -1
- package/src/assets/web-panel/assets/index-D4XFHxBr.js +0 -1
package/src/commands/plugin.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import chalk from "chalk";
|
|
7
|
+
import fs from "node:fs";
|
|
7
8
|
import { logger } from "../lib/logger.js";
|
|
8
9
|
import {
|
|
9
10
|
enforcePluginPolicy,
|
|
@@ -27,6 +28,11 @@ import {
|
|
|
27
28
|
removePluginSkills,
|
|
28
29
|
getPluginSkills,
|
|
29
30
|
} from "../harness/plugin-manager.js";
|
|
31
|
+
import {
|
|
32
|
+
PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA,
|
|
33
|
+
PLUGIN_MARKETPLACE_PAYLOAD_SBOM_SCHEMA,
|
|
34
|
+
isMarketplacePayloadSbomFormat,
|
|
35
|
+
} from "../lib/plugin-runtime/marketplace-artifact-readback.js";
|
|
30
36
|
|
|
31
37
|
function collectRepeatableOption(value, previous = []) {
|
|
32
38
|
return [...previous, value];
|
|
@@ -37,6 +43,71 @@ function normalizeRegistryUrls(value) {
|
|
|
37
43
|
return value ? [value] : [];
|
|
38
44
|
}
|
|
39
45
|
|
|
46
|
+
function normalizeRegistryDigestPins(value, registryUrls) {
|
|
47
|
+
const pins = new Map();
|
|
48
|
+
const allowed = new Set(registryUrls);
|
|
49
|
+
for (const raw of Array.isArray(value) ? value : value ? [value] : []) {
|
|
50
|
+
const separator = String(raw).lastIndexOf("=");
|
|
51
|
+
const url = separator > 0 ? String(raw).slice(0, separator) : "";
|
|
52
|
+
const digest = separator > 0 ? String(raw).slice(separator + 1) : "";
|
|
53
|
+
if (!allowed.has(url)) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`registry digest pin must name an exact requested registry URL: ${url || raw}`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (!/^[a-f0-9]{64}$/.test(digest)) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`registry digest pin for ${url} must be 64 lowercase hexadecimal characters`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
if (pins.has(url) && pins.get(url) !== digest) {
|
|
64
|
+
throw new Error(`conflicting registry digest pins for ${url}`);
|
|
65
|
+
}
|
|
66
|
+
pins.set(url, digest);
|
|
67
|
+
}
|
|
68
|
+
return pins;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function registryNetworkOptions(options = {}) {
|
|
72
|
+
return {
|
|
73
|
+
proxyUrl:
|
|
74
|
+
options.proxy || process.env.CC_PLUGIN_REGISTRY_PROXY || undefined,
|
|
75
|
+
pacFile:
|
|
76
|
+
options.pacFile || process.env.CC_PLUGIN_REGISTRY_PAC_FILE || undefined,
|
|
77
|
+
caFile:
|
|
78
|
+
options.caFile || process.env.CC_PLUGIN_REGISTRY_CA_FILE || undefined,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function assertOfflinePluginSourceLocal(source, ref = null) {
|
|
83
|
+
if (
|
|
84
|
+
ref ||
|
|
85
|
+
typeof source !== "string" ||
|
|
86
|
+
!fs.existsSync(source) ||
|
|
87
|
+
!fs.lstatSync(source).isDirectory()
|
|
88
|
+
) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
"offline install without --registry requires an existing local plugin directory; remote Git caching is available only for registry candidates with manifest and semantic payload anchors",
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function readBoundedPluginJson(file, label) {
|
|
96
|
+
const stat = fs.lstatSync(file);
|
|
97
|
+
if (
|
|
98
|
+
stat.isSymbolicLink() ||
|
|
99
|
+
!stat.isFile() ||
|
|
100
|
+
stat.nlink !== 1 ||
|
|
101
|
+
stat.size <= 0 ||
|
|
102
|
+
stat.size > 256 * 1024
|
|
103
|
+
) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`${label} must be a single-link regular file under 256 KiB`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
109
|
+
}
|
|
110
|
+
|
|
40
111
|
async function loadOptionalPluginConfig() {
|
|
41
112
|
try {
|
|
42
113
|
const cm = await import("../lib/config-manager.js");
|
|
@@ -49,7 +120,14 @@ async function loadOptionalPluginConfig() {
|
|
|
49
120
|
async function fetchRegistryRemoteArtifacts(
|
|
50
121
|
registryUrl,
|
|
51
122
|
entry,
|
|
52
|
-
{
|
|
123
|
+
{
|
|
124
|
+
token,
|
|
125
|
+
allowInsecure = false,
|
|
126
|
+
offline = false,
|
|
127
|
+
proxyUrl,
|
|
128
|
+
pacFile,
|
|
129
|
+
caFile,
|
|
130
|
+
} = {},
|
|
53
131
|
) {
|
|
54
132
|
if (!registryUrl || !entry || typeof entry !== "object") return null;
|
|
55
133
|
const rawSignature =
|
|
@@ -98,6 +176,10 @@ async function fetchRegistryRemoteArtifacts(
|
|
|
98
176
|
registryUrl,
|
|
99
177
|
token: resolveRegistryToken(registryUrl, { token, config }),
|
|
100
178
|
allowInsecure,
|
|
179
|
+
offline,
|
|
180
|
+
proxyUrl,
|
|
181
|
+
pacFile,
|
|
182
|
+
caFile,
|
|
101
183
|
signature,
|
|
102
184
|
sbom,
|
|
103
185
|
});
|
|
@@ -114,6 +196,32 @@ function cleanupRegistryRemoteArtifacts(remoteArtifacts) {
|
|
|
114
196
|
}
|
|
115
197
|
}
|
|
116
198
|
|
|
199
|
+
function remoteSbomComparisonAuthorityMatches(
|
|
200
|
+
catalogAuthority,
|
|
201
|
+
remoteArtifactEvidence,
|
|
202
|
+
expectedSbom = null,
|
|
203
|
+
) {
|
|
204
|
+
const sbom = remoteArtifactEvidence?.sbom;
|
|
205
|
+
const semanticFormat = sbom?.format || expectedSbom?.format;
|
|
206
|
+
if (!isMarketplacePayloadSbomFormat(semanticFormat)) return true;
|
|
207
|
+
if (!sbom) {
|
|
208
|
+
const comparisonRequired =
|
|
209
|
+
semanticFormat === PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA ||
|
|
210
|
+
(semanticFormat === PLUGIN_MARKETPLACE_PAYLOAD_SBOM_SCHEMA &&
|
|
211
|
+
Boolean(expectedSbom?.url && expectedSbom?.documentSha256));
|
|
212
|
+
return !comparisonRequired;
|
|
213
|
+
}
|
|
214
|
+
const comparison = catalogAuthority?.remoteSbomPayloadComparison;
|
|
215
|
+
return (
|
|
216
|
+
comparison?.status === "matched" &&
|
|
217
|
+
comparison.format === sbom.format &&
|
|
218
|
+
comparison.remoteArtifactEvidenceDigest ===
|
|
219
|
+
remoteArtifactEvidence.evidenceDigest &&
|
|
220
|
+
comparison.documentSha256 === sbom.documentSha256 &&
|
|
221
|
+
comparison.remotePayload?.digest === comparison.installedPayload?.digest
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
117
225
|
function registryManifestDigestConflict(registrySha256, optionSha256) {
|
|
118
226
|
if (!registrySha256 || !optionSha256) return false;
|
|
119
227
|
return (
|
|
@@ -139,7 +247,16 @@ async function buildRegistrySetSelection(
|
|
|
139
247
|
registryUrls,
|
|
140
248
|
name,
|
|
141
249
|
cwd,
|
|
142
|
-
{
|
|
250
|
+
{
|
|
251
|
+
token,
|
|
252
|
+
allowInsecure = false,
|
|
253
|
+
strict = false,
|
|
254
|
+
offline = false,
|
|
255
|
+
registryDigestPins = new Map(),
|
|
256
|
+
proxyUrl,
|
|
257
|
+
pacFile,
|
|
258
|
+
caFile,
|
|
259
|
+
} = {},
|
|
143
260
|
) {
|
|
144
261
|
const { fetchRegistry, resolveRegistryToken } =
|
|
145
262
|
await import("../lib/plugin-runtime/remote-source.js");
|
|
@@ -165,6 +282,11 @@ async function buildRegistrySetSelection(
|
|
|
165
282
|
const resolved = await fetchRegistry(url, {
|
|
166
283
|
token: resolvedToken,
|
|
167
284
|
allowInsecure,
|
|
285
|
+
offline,
|
|
286
|
+
expectedSha256: registryDigestPins.get(url),
|
|
287
|
+
proxyUrl,
|
|
288
|
+
pacFile,
|
|
289
|
+
caFile,
|
|
168
290
|
});
|
|
169
291
|
return { url, ...resolved };
|
|
170
292
|
} catch (error) {
|
|
@@ -216,6 +338,8 @@ async function buildRegistrySetSelection(
|
|
|
216
338
|
sha256: entry.sha256 || null,
|
|
217
339
|
entry,
|
|
218
340
|
fromCache: source.fromCache === true,
|
|
341
|
+
documentSha256: source.documentSha256 || null,
|
|
342
|
+
networkAuthority: source.networkAuthority || null,
|
|
219
343
|
},
|
|
220
344
|
};
|
|
221
345
|
}
|
|
@@ -229,6 +353,8 @@ async function buildRegistryInstallPreflight(url, resolved, cwd) {
|
|
|
229
353
|
registryUrl: url,
|
|
230
354
|
entry: resolved.entry,
|
|
231
355
|
fromCache: resolved.fromCache === true,
|
|
356
|
+
registryDocumentSha256: resolved.documentSha256,
|
|
357
|
+
registryNetworkAuthority: resolved.networkAuthority,
|
|
232
358
|
installed,
|
|
233
359
|
hostVersion: VERSION,
|
|
234
360
|
}).preflight;
|
|
@@ -245,7 +371,16 @@ function catalogAuthorityFromPreflight(
|
|
|
245
371
|
candidateDigest: preflight.candidateDigest,
|
|
246
372
|
governanceStatus: preflight.governance.status,
|
|
247
373
|
registryStatus: preflight.registry.status,
|
|
374
|
+
...(preflight.registry.documentSha256
|
|
375
|
+
? { registryDocumentSha256: preflight.registry.documentSha256 }
|
|
376
|
+
: {}),
|
|
377
|
+
...(preflight.registry.networkAuthority
|
|
378
|
+
? { registryNetworkAuthority: preflight.registry.networkAuthority }
|
|
379
|
+
: {}),
|
|
248
380
|
versionAuthority: preflight.versionAuthority,
|
|
381
|
+
...(preflight.publisherIdentity
|
|
382
|
+
? { publisherDeclaration: preflight.publisherIdentity }
|
|
383
|
+
: {}),
|
|
249
384
|
artifactExpectations: {
|
|
250
385
|
manifest: {
|
|
251
386
|
status: preflight.integrity.digest.status,
|
|
@@ -323,9 +458,12 @@ async function installedCatalogAuthorityMatches(
|
|
|
323
458
|
if (!preflight) return null;
|
|
324
459
|
try {
|
|
325
460
|
const { listInstalled } = await import("../lib/plugin-runtime/install.js");
|
|
326
|
-
const installed = listInstalled({
|
|
327
|
-
|
|
328
|
-
|
|
461
|
+
const installed = listInstalled({
|
|
462
|
+
cwd,
|
|
463
|
+
scopes: [scope],
|
|
464
|
+
allowRetainedInstall: true,
|
|
465
|
+
}).find((row) => row.name === name && row.scope === scope);
|
|
466
|
+
if (installed?.runtimeBlocked === true) return false;
|
|
329
467
|
const authority = installed?.source?.catalogAuthority;
|
|
330
468
|
return (
|
|
331
469
|
authority?.catalogDigest === preflight.catalogDigest &&
|
|
@@ -337,6 +475,11 @@ async function installedCatalogAuthorityMatches(
|
|
|
337
475
|
(!remoteArtifactEvidence ||
|
|
338
476
|
authority?.remoteArtifactEvidence?.evidenceDigest ===
|
|
339
477
|
remoteArtifactEvidence.evidenceDigest) &&
|
|
478
|
+
remoteSbomComparisonAuthorityMatches(
|
|
479
|
+
authority,
|
|
480
|
+
remoteArtifactEvidence,
|
|
481
|
+
preflight.integrity?.sbom,
|
|
482
|
+
) &&
|
|
340
483
|
authority?.preflightStatus === "allowed"
|
|
341
484
|
);
|
|
342
485
|
} catch {
|
|
@@ -345,12 +488,42 @@ async function installedCatalogAuthorityMatches(
|
|
|
345
488
|
}
|
|
346
489
|
|
|
347
490
|
async function buildInstalledMarketplaceSnapshot(name, scope, cwd) {
|
|
348
|
-
const { listInstalled } =
|
|
491
|
+
const { listInstalled, readSourceMetadataStrict } =
|
|
492
|
+
await import("../lib/plugin-runtime/install.js");
|
|
349
493
|
const { discoverPlugins } = await import("../lib/plugin-runtime/scopes.js");
|
|
350
494
|
const row = listInstalled({ cwd, scopes: [scope] }).find(
|
|
351
495
|
(candidate) => candidate.name === name && candidate.scope === scope,
|
|
352
496
|
);
|
|
353
497
|
if (!row) return null;
|
|
498
|
+
if (row.runtimeBlocked === true) {
|
|
499
|
+
throw new Error(
|
|
500
|
+
`INSTALLED_PLUGIN_RUNTIME_BLOCKED (${row.activePointer?.status || "unknown"})`,
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
const installedSource = readSourceMetadataStrict(row.dir, {
|
|
504
|
+
required: true,
|
|
505
|
+
requireRegistryAuthority: true,
|
|
506
|
+
});
|
|
507
|
+
const installedAuthority = installedSource.catalogAuthority;
|
|
508
|
+
const installedExpectedSbom =
|
|
509
|
+
installedAuthority?.artifactExpectations?.sbom || null;
|
|
510
|
+
const installedSemanticComparisonRequired =
|
|
511
|
+
installedExpectedSbom?.format ===
|
|
512
|
+
PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA ||
|
|
513
|
+
(installedExpectedSbom?.format === PLUGIN_MARKETPLACE_PAYLOAD_SBOM_SCHEMA &&
|
|
514
|
+
Boolean(
|
|
515
|
+
installedExpectedSbom.url && installedExpectedSbom.documentSha256,
|
|
516
|
+
));
|
|
517
|
+
if (
|
|
518
|
+
installedSemanticComparisonRequired &&
|
|
519
|
+
!remoteSbomComparisonAuthorityMatches(
|
|
520
|
+
installedAuthority,
|
|
521
|
+
installedAuthority?.remoteArtifactEvidence,
|
|
522
|
+
installedExpectedSbom,
|
|
523
|
+
)
|
|
524
|
+
) {
|
|
525
|
+
throw new Error("INSTALLED_SEMANTIC_SBOM_EVIDENCE_INVALID");
|
|
526
|
+
}
|
|
354
527
|
const discovered = discoverPlugins({
|
|
355
528
|
cwd,
|
|
356
529
|
scopes: [scope],
|
|
@@ -359,6 +532,7 @@ async function buildInstalledMarketplaceSnapshot(name, scope, cwd) {
|
|
|
359
532
|
}).find((candidate) => candidate.name === name && candidate.scope === scope);
|
|
360
533
|
let dependencies = {};
|
|
361
534
|
let payloadSbom = null;
|
|
535
|
+
let semanticPayloadFormat = null;
|
|
362
536
|
if (discovered?.manifest?.manifestPath) {
|
|
363
537
|
try {
|
|
364
538
|
const fs = await import("node:fs");
|
|
@@ -381,25 +555,54 @@ async function buildInstalledMarketplaceSnapshot(name, scope, cwd) {
|
|
|
381
555
|
}
|
|
382
556
|
}
|
|
383
557
|
if (row.dir) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
558
|
+
const {
|
|
559
|
+
assertSafeInstalledPluginStructure,
|
|
560
|
+
buildMarketplacePayloadSbom,
|
|
561
|
+
isMarketplacePayloadSbomFormat,
|
|
562
|
+
validateRemoteSbomPayloadComparison,
|
|
563
|
+
} = await import("../lib/plugin-runtime/marketplace-artifact-readback.js");
|
|
564
|
+
const recordedFormat =
|
|
565
|
+
installedSource?.catalogAuthority?.remoteSbomPayloadComparison?.format;
|
|
566
|
+
semanticPayloadFormat = isMarketplacePayloadSbomFormat(recordedFormat)
|
|
567
|
+
? recordedFormat
|
|
568
|
+
: null;
|
|
569
|
+
if (semanticPayloadFormat) {
|
|
570
|
+
assertSafeInstalledPluginStructure(row.dir);
|
|
571
|
+
payloadSbom = buildMarketplacePayloadSbom(row.dir, {
|
|
572
|
+
schemaVersion: semanticPayloadFormat,
|
|
573
|
+
});
|
|
574
|
+
const comparison = validateRemoteSbomPayloadComparison(
|
|
575
|
+
installedAuthority?.remoteSbomPayloadComparison,
|
|
576
|
+
{
|
|
577
|
+
remoteArtifactEvidence: installedAuthority?.remoteArtifactEvidence,
|
|
578
|
+
expectedPayloadSha256: installedExpectedSbom?.payloadSha256,
|
|
579
|
+
currentPayloadSbom: payloadSbom,
|
|
580
|
+
},
|
|
581
|
+
);
|
|
582
|
+
if (!comparison.valid || !comparison.currentPayloadMatches) {
|
|
583
|
+
throw new Error("INSTALLED_SEMANTIC_SBOM_EVIDENCE_INVALID");
|
|
584
|
+
}
|
|
585
|
+
} else {
|
|
586
|
+
try {
|
|
587
|
+
payloadSbom = buildMarketplacePayloadSbom(row.dir);
|
|
588
|
+
} catch {
|
|
589
|
+
payloadSbom = null;
|
|
590
|
+
}
|
|
390
591
|
}
|
|
391
592
|
}
|
|
392
593
|
return {
|
|
393
594
|
name: row.name,
|
|
394
595
|
version: row.version,
|
|
395
596
|
scope: row.scope,
|
|
396
|
-
|
|
597
|
+
enabled: row.enabled !== false,
|
|
598
|
+
source: installedSource,
|
|
397
599
|
integrity: {
|
|
398
600
|
...row.integrity,
|
|
399
601
|
sbom: {
|
|
400
602
|
...row.integrity?.sbom,
|
|
401
603
|
payloadSha256: payloadSbom?.digest || null,
|
|
402
604
|
payloadSchema: payloadSbom?.schemaVersion || null,
|
|
605
|
+
semanticPayloadFormat,
|
|
403
606
|
},
|
|
404
607
|
},
|
|
405
608
|
license: {
|
|
@@ -413,8 +616,29 @@ async function buildInstalledMarketplaceSnapshot(name, scope, cwd) {
|
|
|
413
616
|
async function buildRegistryUpdateImpact(preflight, name, scope, cwd) {
|
|
414
617
|
const { buildPluginMarketplaceUpdateImpact } =
|
|
415
618
|
await import("../lib/plugin-runtime/marketplace-impact.js");
|
|
416
|
-
const
|
|
417
|
-
|
|
619
|
+
const { listInstalledAllScopes } =
|
|
620
|
+
await import("../lib/plugin-runtime/install.js");
|
|
621
|
+
const inventory = listInstalledAllScopes({ cwd }).filter(
|
|
622
|
+
(entry) => entry.name === name,
|
|
623
|
+
);
|
|
624
|
+
const installedScopes = [];
|
|
625
|
+
for (const entry of inventory) {
|
|
626
|
+
installedScopes.push(
|
|
627
|
+
await buildInstalledMarketplaceSnapshot(name, entry.scope, cwd),
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
const effectiveScope = inventory.find(
|
|
631
|
+
(entry) => entry.effectiveAuthority === true,
|
|
632
|
+
)?.scope;
|
|
633
|
+
const installed = effectiveScope
|
|
634
|
+
? installedScopes.find((entry) => entry?.scope === effectiveScope) || null
|
|
635
|
+
: null;
|
|
636
|
+
return buildPluginMarketplaceUpdateImpact({
|
|
637
|
+
preflight,
|
|
638
|
+
installed,
|
|
639
|
+
installedScopes,
|
|
640
|
+
targetScope: scope,
|
|
641
|
+
});
|
|
418
642
|
}
|
|
419
643
|
|
|
420
644
|
/**
|
|
@@ -437,9 +661,11 @@ async function resolvePluginCapabilityNotice(
|
|
|
437
661
|
const { describeCapabilities } =
|
|
438
662
|
await import("../lib/plugin-runtime/capabilities.js");
|
|
439
663
|
const consent = await import("../lib/plugin-runtime/capability-consent.js");
|
|
440
|
-
const installed = discoverPlugins({
|
|
441
|
-
|
|
442
|
-
|
|
664
|
+
const installed = discoverPlugins({
|
|
665
|
+
cwd,
|
|
666
|
+
skipPolicy: true,
|
|
667
|
+
allowRetainedInstall: true,
|
|
668
|
+
}).find((p) => p.name === name && p.scope === scope);
|
|
443
669
|
if (!installed) {
|
|
444
670
|
if (strict) {
|
|
445
671
|
throw new Error(`${name} is not discoverable at ${scope} scope`);
|
|
@@ -532,9 +758,11 @@ async function grantInstalledPluginCapabilities(name, scope, cwd) {
|
|
|
532
758
|
try {
|
|
533
759
|
const { discoverPlugins } = await import("../lib/plugin-runtime/scopes.js");
|
|
534
760
|
const consent = await import("../lib/plugin-runtime/capability-consent.js");
|
|
535
|
-
const installed = discoverPlugins({
|
|
536
|
-
|
|
537
|
-
|
|
761
|
+
const installed = discoverPlugins({
|
|
762
|
+
cwd,
|
|
763
|
+
skipPolicy: true,
|
|
764
|
+
allowRetainedInstall: true,
|
|
765
|
+
}).find((p) => p.name === name && p.scope === scope);
|
|
538
766
|
if (!installed) return false;
|
|
539
767
|
const declared = installed.manifest?.capabilities;
|
|
540
768
|
if (!declared || consent.capabilitiesAreEmpty(declared)) return false;
|
|
@@ -810,6 +1038,10 @@ export function registerPluginCommand(program) {
|
|
|
810
1038
|
"--scope <scope>",
|
|
811
1039
|
"Enable the unified-runtime install at this scope (user|project|local)",
|
|
812
1040
|
)
|
|
1041
|
+
.option(
|
|
1042
|
+
"--allow-source-switch",
|
|
1043
|
+
"Approve changing the effective source authority across scopes",
|
|
1044
|
+
)
|
|
813
1045
|
.option("--json", "Output unified-runtime result as JSON")
|
|
814
1046
|
.action(async (name, options) => {
|
|
815
1047
|
try {
|
|
@@ -819,6 +1051,7 @@ export function registerPluginCommand(program) {
|
|
|
819
1051
|
const result = setPluginEnabled(name, true, {
|
|
820
1052
|
scope: options.scope,
|
|
821
1053
|
cwd: process.cwd(),
|
|
1054
|
+
allowSourceSwitch: options.allowSourceSwitch === true,
|
|
822
1055
|
});
|
|
823
1056
|
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
824
1057
|
else {
|
|
@@ -862,6 +1095,10 @@ export function registerPluginCommand(program) {
|
|
|
862
1095
|
"--scope <scope>",
|
|
863
1096
|
"Disable the unified-runtime install at this scope (user|project|local)",
|
|
864
1097
|
)
|
|
1098
|
+
.option(
|
|
1099
|
+
"--allow-source-switch",
|
|
1100
|
+
"Approve the lower-scope source authority exposed by disabling",
|
|
1101
|
+
)
|
|
865
1102
|
.option("--json", "Output unified-runtime result as JSON")
|
|
866
1103
|
.action(async (name, options) => {
|
|
867
1104
|
try {
|
|
@@ -871,6 +1108,7 @@ export function registerPluginCommand(program) {
|
|
|
871
1108
|
const result = setPluginEnabled(name, false, {
|
|
872
1109
|
scope: options.scope,
|
|
873
1110
|
cwd: process.cwd(),
|
|
1111
|
+
allowSourceSwitch: options.allowSourceSwitch === true,
|
|
874
1112
|
});
|
|
875
1113
|
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
876
1114
|
else {
|
|
@@ -1273,6 +1511,14 @@ export function registerPluginCommand(program) {
|
|
|
1273
1511
|
)
|
|
1274
1512
|
.option("--scope <scope>", "Install scope (user|project|local)", "user")
|
|
1275
1513
|
.option("--force", "Reinstall over an existing immutable version")
|
|
1514
|
+
.option(
|
|
1515
|
+
"--allow-source-switch",
|
|
1516
|
+
"Explicitly approve changing effective source authority across scopes",
|
|
1517
|
+
)
|
|
1518
|
+
.option(
|
|
1519
|
+
"--allow-downgrade",
|
|
1520
|
+
"Explicitly approve a version downgrade across scoped installs",
|
|
1521
|
+
)
|
|
1276
1522
|
.option("--sha256 <hex>", "Expected SHA-256 of the manifest file")
|
|
1277
1523
|
.option("--signature <path>", "Detached Ed25519 signature of the manifest")
|
|
1278
1524
|
.option("--public-key <path>", "Public key for signature verification")
|
|
@@ -1291,6 +1537,28 @@ export function registerPluginCommand(program) {
|
|
|
1291
1537
|
"--allow-insecure-registry",
|
|
1292
1538
|
"Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
|
|
1293
1539
|
)
|
|
1540
|
+
.option(
|
|
1541
|
+
"--offline",
|
|
1542
|
+
"Use verified registry/artifact/source caches or an existing local plugin source",
|
|
1543
|
+
)
|
|
1544
|
+
.option(
|
|
1545
|
+
"--registry-digest <url=sha256>",
|
|
1546
|
+
"Pin an exact immutable registry document (repeatable)",
|
|
1547
|
+
collectRepeatableOption,
|
|
1548
|
+
[],
|
|
1549
|
+
)
|
|
1550
|
+
.option(
|
|
1551
|
+
"--proxy <url>",
|
|
1552
|
+
"Explicit HTTP(S) registry proxy (or CC_PLUGIN_REGISTRY_PROXY)",
|
|
1553
|
+
)
|
|
1554
|
+
.option(
|
|
1555
|
+
"--pac-file <path>",
|
|
1556
|
+
"Bounded local PAC file (or CC_PLUGIN_REGISTRY_PAC_FILE)",
|
|
1557
|
+
)
|
|
1558
|
+
.option(
|
|
1559
|
+
"--ca-file <path>",
|
|
1560
|
+
"Custom registry CA bundle (or CC_PLUGIN_REGISTRY_CA_FILE)",
|
|
1561
|
+
)
|
|
1294
1562
|
.option(
|
|
1295
1563
|
"--expected-selection-digest <sha256>",
|
|
1296
1564
|
"Require the exact digest from a prior multi-registry `cc plugin select` review",
|
|
@@ -1311,13 +1579,34 @@ export function registerPluginCommand(program) {
|
|
|
1311
1579
|
let sourceMetadata = null;
|
|
1312
1580
|
let expectedIdentity = null;
|
|
1313
1581
|
let marketplacePreflight = null;
|
|
1582
|
+
let marketplaceImpact = null;
|
|
1314
1583
|
let remoteArtifactRequest = null;
|
|
1315
1584
|
let remoteArtifacts = null;
|
|
1316
1585
|
const registryUrls = normalizeRegistryUrls(options.registry);
|
|
1317
1586
|
const { isRemoteSource, resolveRemoteSource } =
|
|
1318
1587
|
await import("../lib/plugin-runtime/remote-source.js");
|
|
1588
|
+
if (
|
|
1589
|
+
options.offline === true &&
|
|
1590
|
+
registryUrls.length === 0 &&
|
|
1591
|
+
!isRemoteSource(source)
|
|
1592
|
+
) {
|
|
1593
|
+
try {
|
|
1594
|
+
assertOfflinePluginSourceLocal(source);
|
|
1595
|
+
} catch (error) {
|
|
1596
|
+
logger.error(`Offline source validation failed: ${error.message}`);
|
|
1597
|
+
process.exitCode = 1;
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1319
1601
|
if (registryUrls.length || isRemoteSource(source)) {
|
|
1320
1602
|
try {
|
|
1603
|
+
const requestedRegistryUrls = registryUrls.length
|
|
1604
|
+
? registryUrls
|
|
1605
|
+
: [source];
|
|
1606
|
+
const registryDigestPins = normalizeRegistryDigestPins(
|
|
1607
|
+
options.registryDigest,
|
|
1608
|
+
requestedRegistryUrls,
|
|
1609
|
+
);
|
|
1321
1610
|
let resolved;
|
|
1322
1611
|
let url;
|
|
1323
1612
|
let name;
|
|
@@ -1330,6 +1619,9 @@ export function registerPluginCommand(program) {
|
|
|
1330
1619
|
{
|
|
1331
1620
|
token: options.token,
|
|
1332
1621
|
allowInsecure: options.allowInsecureRegistry === true,
|
|
1622
|
+
offline: options.offline === true,
|
|
1623
|
+
registryDigestPins,
|
|
1624
|
+
...registryNetworkOptions(options),
|
|
1333
1625
|
},
|
|
1334
1626
|
);
|
|
1335
1627
|
resolved = selected.resolved;
|
|
@@ -1349,6 +1641,9 @@ export function registerPluginCommand(program) {
|
|
|
1349
1641
|
token: options.token,
|
|
1350
1642
|
config,
|
|
1351
1643
|
allowInsecure: options.allowInsecureRegistry === true,
|
|
1644
|
+
offline: options.offline === true,
|
|
1645
|
+
expectedSha256: registryDigestPins.get(url),
|
|
1646
|
+
...registryNetworkOptions(options),
|
|
1352
1647
|
});
|
|
1353
1648
|
marketplacePreflight = await buildRegistryInstallPreflight(
|
|
1354
1649
|
url,
|
|
@@ -1372,6 +1667,47 @@ export function registerPluginCommand(program) {
|
|
|
1372
1667
|
options.expectedSelectionDigest,
|
|
1373
1668
|
marketplacePreflight,
|
|
1374
1669
|
);
|
|
1670
|
+
marketplaceImpact = await buildRegistryUpdateImpact(
|
|
1671
|
+
marketplacePreflight,
|
|
1672
|
+
resolved.entry.name,
|
|
1673
|
+
options.scope,
|
|
1674
|
+
process.cwd(),
|
|
1675
|
+
);
|
|
1676
|
+
if (
|
|
1677
|
+
marketplaceImpact.blockers.some(
|
|
1678
|
+
(blocker) => blocker.code === "SEMANTIC_SBOM_BINDING_DOWNGRADE",
|
|
1679
|
+
)
|
|
1680
|
+
) {
|
|
1681
|
+
throw new Error(
|
|
1682
|
+
"registry candidate preflight blocked: SEMANTIC_SBOM_BINDING_DOWNGRADE",
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
if (
|
|
1686
|
+
marketplaceImpact.blockers.some(
|
|
1687
|
+
(blocker) =>
|
|
1688
|
+
blocker.code === "REGISTRY_VERSION_REQUIRED_FOR_UPDATE",
|
|
1689
|
+
)
|
|
1690
|
+
) {
|
|
1691
|
+
throw new Error(
|
|
1692
|
+
"registry candidate preflight blocked: REGISTRY_VERSION_REQUIRED_FOR_UPDATE",
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
if (
|
|
1696
|
+
marketplaceImpact.changes.source.requiresApproval &&
|
|
1697
|
+
options.allowSourceSwitch !== true
|
|
1698
|
+
) {
|
|
1699
|
+
throw new Error(
|
|
1700
|
+
`registry candidate preflight blocked: SOURCE_SWITCH_APPROVAL_REQUIRED (${marketplaceImpact.changes.source.kind}); pass --allow-source-switch after reviewing cc plugin impact`,
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
if (
|
|
1704
|
+
marketplaceImpact.changes.version.kind === "downgrade" &&
|
|
1705
|
+
options.allowDowngrade !== true
|
|
1706
|
+
) {
|
|
1707
|
+
throw new Error(
|
|
1708
|
+
"registry candidate preflight blocked: VERSION_DOWNGRADE_APPROVAL_REQUIRED; pass --allow-downgrade after reviewing cc plugin impact",
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1375
1711
|
expectedIdentity = {
|
|
1376
1712
|
name:
|
|
1377
1713
|
typeof resolved.entry.name === "string"
|
|
@@ -1399,8 +1735,10 @@ export function registerPluginCommand(program) {
|
|
|
1399
1735
|
resolved.source.slice(resolved.source.indexOf("#") + 1)) ||
|
|
1400
1736
|
null,
|
|
1401
1737
|
offline: resolved.fromCache === true,
|
|
1402
|
-
catalogAuthority:
|
|
1403
|
-
|
|
1738
|
+
catalogAuthority: catalogAuthorityFromPreflight(
|
|
1739
|
+
marketplacePreflight,
|
|
1740
|
+
marketplaceImpact,
|
|
1741
|
+
),
|
|
1404
1742
|
};
|
|
1405
1743
|
if (resolved.fromCache) {
|
|
1406
1744
|
logger.warn(
|
|
@@ -1435,6 +1773,8 @@ export function registerPluginCommand(program) {
|
|
|
1435
1773
|
{
|
|
1436
1774
|
token: options.token,
|
|
1437
1775
|
allowInsecure: options.allowInsecureRegistry === true,
|
|
1776
|
+
offline: options.offline === true,
|
|
1777
|
+
...registryNetworkOptions(options),
|
|
1438
1778
|
},
|
|
1439
1779
|
);
|
|
1440
1780
|
if (
|
|
@@ -1448,7 +1788,7 @@ export function registerPluginCommand(program) {
|
|
|
1448
1788
|
if (remoteArtifacts?.authority && sourceMetadata?.catalogAuthority) {
|
|
1449
1789
|
sourceMetadata.catalogAuthority = catalogAuthorityFromPreflight(
|
|
1450
1790
|
marketplacePreflight,
|
|
1451
|
-
|
|
1791
|
+
marketplaceImpact,
|
|
1452
1792
|
remoteArtifacts.authority,
|
|
1453
1793
|
);
|
|
1454
1794
|
}
|
|
@@ -1489,8 +1829,9 @@ export function registerPluginCommand(program) {
|
|
|
1489
1829
|
}
|
|
1490
1830
|
: null;
|
|
1491
1831
|
let res = null;
|
|
1492
|
-
let
|
|
1493
|
-
let
|
|
1832
|
+
let lifecycleTransactionFinalized = false;
|
|
1833
|
+
let lifecycleCleanupPending = false;
|
|
1834
|
+
let lifecycleCleanupPath = null;
|
|
1494
1835
|
try {
|
|
1495
1836
|
res = installFromSource(installSource, {
|
|
1496
1837
|
scope: options.scope,
|
|
@@ -1501,7 +1842,12 @@ export function registerPluginCommand(program) {
|
|
|
1501
1842
|
expectedIdentity,
|
|
1502
1843
|
managedPolicy: managed,
|
|
1503
1844
|
policySource: sourceMetadata?.registry || source,
|
|
1504
|
-
|
|
1845
|
+
remoteSbomBytes: remoteArtifacts?.sbomBytes || null,
|
|
1846
|
+
offline: options.offline === true,
|
|
1847
|
+
transactional: true,
|
|
1848
|
+
enforceUpdateApprovals: true,
|
|
1849
|
+
allowSourceSwitch: options.allowSourceSwitch === true,
|
|
1850
|
+
allowDowngrade: options.allowDowngrade === true,
|
|
1505
1851
|
});
|
|
1506
1852
|
const marketplaceAuthorityPersisted = marketplacePreflight
|
|
1507
1853
|
? res.sourceMetadata?.catalogAuthority?.catalogDigest ===
|
|
@@ -1513,9 +1859,17 @@ export function registerPluginCommand(program) {
|
|
|
1513
1859
|
(!marketplacePreflight.selectionDigest ||
|
|
1514
1860
|
res.sourceMetadata?.catalogAuthority?.selectionDigest ===
|
|
1515
1861
|
marketplacePreflight.selectionDigest) &&
|
|
1862
|
+
res.sourceMetadata?.catalogAuthority?.updateImpactDigest ===
|
|
1863
|
+
marketplaceImpact?.impactDigest &&
|
|
1516
1864
|
(!remoteArtifacts?.authority ||
|
|
1517
1865
|
res.sourceMetadata?.catalogAuthority?.remoteArtifactEvidence
|
|
1518
|
-
?.evidenceDigest ===
|
|
1866
|
+
?.evidenceDigest ===
|
|
1867
|
+
remoteArtifacts.authority.evidenceDigest) &&
|
|
1868
|
+
remoteSbomComparisonAuthorityMatches(
|
|
1869
|
+
res.sourceMetadata?.catalogAuthority,
|
|
1870
|
+
remoteArtifacts?.authority,
|
|
1871
|
+
marketplacePreflight.integrity?.sbom,
|
|
1872
|
+
)
|
|
1519
1873
|
: null;
|
|
1520
1874
|
if (marketplacePreflight && !marketplaceAuthorityPersisted) {
|
|
1521
1875
|
throw new Error(
|
|
@@ -1538,23 +1892,26 @@ export function registerPluginCommand(program) {
|
|
|
1538
1892
|
res.scope,
|
|
1539
1893
|
process.cwd(),
|
|
1540
1894
|
);
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
);
|
|
1547
|
-
}
|
|
1548
|
-
marketplaceCleanupPending = finalization.cleanupPending === true;
|
|
1549
|
-
marketplaceTransactionFinalized = true;
|
|
1895
|
+
const finalization = finalizePluginUpdate(res);
|
|
1896
|
+
if (!finalization.finalized) {
|
|
1897
|
+
throw new Error(
|
|
1898
|
+
"plugin install transaction could not be finalized",
|
|
1899
|
+
);
|
|
1550
1900
|
}
|
|
1901
|
+
lifecycleCleanupPending = finalization.cleanupPending === true;
|
|
1902
|
+
lifecycleCleanupPath = finalization.cleanupPath || null;
|
|
1903
|
+
lifecycleTransactionFinalized = true;
|
|
1551
1904
|
console.log(
|
|
1552
1905
|
JSON.stringify(
|
|
1553
1906
|
{
|
|
1554
1907
|
...res,
|
|
1555
1908
|
marketplacePreflight,
|
|
1909
|
+
marketplaceImpact,
|
|
1556
1910
|
marketplaceAuthorityPersisted,
|
|
1557
|
-
marketplaceCleanupPending,
|
|
1911
|
+
marketplaceCleanupPending: lifecycleCleanupPending,
|
|
1912
|
+
cleanupPending:
|
|
1913
|
+
res.cleanupPending === true || lifecycleCleanupPending,
|
|
1914
|
+
cleanupPath: res.cleanupPath || lifecycleCleanupPath,
|
|
1558
1915
|
capabilities: capNotice,
|
|
1559
1916
|
capabilitiesGranted,
|
|
1560
1917
|
},
|
|
@@ -1573,16 +1930,15 @@ export function registerPluginCommand(program) {
|
|
|
1573
1930
|
interactive: Boolean(process.stdin.isTTY),
|
|
1574
1931
|
},
|
|
1575
1932
|
);
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
);
|
|
1582
|
-
}
|
|
1583
|
-
marketplaceCleanupPending = finalization.cleanupPending === true;
|
|
1584
|
-
marketplaceTransactionFinalized = true;
|
|
1933
|
+
const finalization = finalizePluginUpdate(res);
|
|
1934
|
+
if (!finalization.finalized) {
|
|
1935
|
+
throw new Error(
|
|
1936
|
+
"plugin install transaction could not be finalized",
|
|
1937
|
+
);
|
|
1585
1938
|
}
|
|
1939
|
+
lifecycleCleanupPending = finalization.cleanupPending === true;
|
|
1940
|
+
lifecycleCleanupPath = finalization.cleanupPath || null;
|
|
1941
|
+
lifecycleTransactionFinalized = true;
|
|
1586
1942
|
logger.success(
|
|
1587
1943
|
`Installed ${res.name} v${res.version} (${res.scope} scope)` +
|
|
1588
1944
|
(res.signatureVerified ? chalk.green(" ✔ signed") : ""),
|
|
@@ -1602,18 +1958,26 @@ export function registerPluginCommand(program) {
|
|
|
1602
1958
|
);
|
|
1603
1959
|
}
|
|
1604
1960
|
}
|
|
1961
|
+
if (res.cleanupPending === true || lifecycleCleanupPending) {
|
|
1962
|
+
logger.warn(
|
|
1963
|
+
`Install committed; inert cleanup remains at ${res.cleanupPath || lifecycleCleanupPath || "an internal cleanup directory"} and will be retried on the next mutation`,
|
|
1964
|
+
);
|
|
1965
|
+
}
|
|
1605
1966
|
for (const w of res.warnings || [])
|
|
1606
1967
|
logger.log(chalk.yellow(` ⚠ ${w}`));
|
|
1607
1968
|
}
|
|
1608
1969
|
} catch (err) {
|
|
1609
1970
|
let recoveryNote = "";
|
|
1610
|
-
if (res &&
|
|
1971
|
+
if (res && !lifecycleTransactionFinalized) {
|
|
1611
1972
|
try {
|
|
1612
1973
|
const recovery = rollbackPluginUpdate(res);
|
|
1613
1974
|
if (recovery.rolledBack) {
|
|
1614
1975
|
recoveryNote = recovery.version
|
|
1615
1976
|
? `; restored v${recovery.version}`
|
|
1616
1977
|
: "; removed the rejected install";
|
|
1978
|
+
if (recovery.cleanupPending) {
|
|
1979
|
+
recoveryNote += `; inert cleanup retained at ${recovery.cleanupPath || "an internal cleanup directory"}`;
|
|
1980
|
+
}
|
|
1617
1981
|
}
|
|
1618
1982
|
} catch (recoveryError) {
|
|
1619
1983
|
recoveryNote = `; automatic recovery failed: ${recoveryError.message}`;
|
|
@@ -1638,6 +2002,16 @@ export function registerPluginCommand(program) {
|
|
|
1638
2002
|
"--allow-insecure-registry",
|
|
1639
2003
|
"Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
|
|
1640
2004
|
)
|
|
2005
|
+
.option("--offline", "Use only the verified immutable registry cache")
|
|
2006
|
+
.option(
|
|
2007
|
+
"--registry-digest <url=sha256>",
|
|
2008
|
+
"Pin the exact immutable registry document",
|
|
2009
|
+
collectRepeatableOption,
|
|
2010
|
+
[],
|
|
2011
|
+
)
|
|
2012
|
+
.option("--proxy <url>", "Explicit HTTP(S) registry proxy")
|
|
2013
|
+
.option("--pac-file <path>", "Bounded local PAC file")
|
|
2014
|
+
.option("--ca-file <path>", "Custom registry CA bundle")
|
|
1641
2015
|
.option("--json", "Output as JSON")
|
|
1642
2016
|
.action(async (query, options) => {
|
|
1643
2017
|
const { fetchRegistry, listRegistryPlugins, resolveRegistryToken } =
|
|
@@ -1650,14 +2024,24 @@ export function registerPluginCommand(program) {
|
|
|
1650
2024
|
config = null;
|
|
1651
2025
|
}
|
|
1652
2026
|
try {
|
|
2027
|
+
const registryDigestPins = normalizeRegistryDigestPins(
|
|
2028
|
+
options.registryDigest,
|
|
2029
|
+
[options.registry],
|
|
2030
|
+
);
|
|
1653
2031
|
const token = resolveRegistryToken(options.registry, {
|
|
1654
2032
|
token: options.token,
|
|
1655
2033
|
config,
|
|
1656
2034
|
});
|
|
1657
|
-
const { registry, fromCache } = await fetchRegistry(
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
2035
|
+
const { registry, fromCache, documentSha256 } = await fetchRegistry(
|
|
2036
|
+
options.registry,
|
|
2037
|
+
{
|
|
2038
|
+
token,
|
|
2039
|
+
allowInsecure: options.allowInsecureRegistry === true,
|
|
2040
|
+
offline: options.offline === true,
|
|
2041
|
+
expectedSha256: registryDigestPins.get(options.registry),
|
|
2042
|
+
...registryNetworkOptions(options),
|
|
2043
|
+
},
|
|
2044
|
+
);
|
|
1661
2045
|
let rows = listRegistryPlugins(registry);
|
|
1662
2046
|
if (query) {
|
|
1663
2047
|
const q = query.toLowerCase();
|
|
@@ -1668,7 +2052,13 @@ export function registerPluginCommand(program) {
|
|
|
1668
2052
|
);
|
|
1669
2053
|
}
|
|
1670
2054
|
if (options.json) {
|
|
1671
|
-
console.log(
|
|
2055
|
+
console.log(
|
|
2056
|
+
JSON.stringify(
|
|
2057
|
+
{ fromCache, documentSha256, plugins: rows },
|
|
2058
|
+
null,
|
|
2059
|
+
2,
|
|
2060
|
+
),
|
|
2061
|
+
);
|
|
1672
2062
|
return;
|
|
1673
2063
|
}
|
|
1674
2064
|
if (fromCache) {
|
|
@@ -1683,6 +2073,7 @@ export function registerPluginCommand(program) {
|
|
|
1683
2073
|
return;
|
|
1684
2074
|
}
|
|
1685
2075
|
logger.log(chalk.bold(`Plugins (${rows.length}):`));
|
|
2076
|
+
logger.log(chalk.gray(`Registry SHA-256: ${documentSha256}`));
|
|
1686
2077
|
for (const r of rows) {
|
|
1687
2078
|
const ver = r.version ? chalk.gray(` v${r.version}`) : "";
|
|
1688
2079
|
logger.log(
|
|
@@ -1719,6 +2110,16 @@ export function registerPluginCommand(program) {
|
|
|
1719
2110
|
"--allow-insecure-registry",
|
|
1720
2111
|
"Allow plain-HTTP registry URLs (MITM risk — trusted networks only)",
|
|
1721
2112
|
)
|
|
2113
|
+
.option("--offline", "Use only verified immutable registry caches")
|
|
2114
|
+
.option(
|
|
2115
|
+
"--registry-digest <url=sha256>",
|
|
2116
|
+
"Pin exact immutable registry documents (repeatable)",
|
|
2117
|
+
collectRepeatableOption,
|
|
2118
|
+
[],
|
|
2119
|
+
)
|
|
2120
|
+
.option("--proxy <url>", "Explicit HTTP(S) registry proxy")
|
|
2121
|
+
.option("--pac-file <path>", "Bounded local PAC file")
|
|
2122
|
+
.option("--ca-file <path>", "Custom registry CA bundle")
|
|
1722
2123
|
.option(
|
|
1723
2124
|
"--strict",
|
|
1724
2125
|
"Block candidates missing digest, signature, SBOM, license, or capabilities",
|
|
@@ -1745,6 +2146,17 @@ export function registerPluginCommand(program) {
|
|
|
1745
2146
|
process.exitCode = 1;
|
|
1746
2147
|
return;
|
|
1747
2148
|
}
|
|
2149
|
+
let registryDigestPins;
|
|
2150
|
+
try {
|
|
2151
|
+
registryDigestPins = normalizeRegistryDigestPins(
|
|
2152
|
+
options.registryDigest,
|
|
2153
|
+
registryUrls,
|
|
2154
|
+
);
|
|
2155
|
+
} catch (error) {
|
|
2156
|
+
logger.error(`Marketplace catalog failed: ${error.message}`);
|
|
2157
|
+
process.exitCode = 1;
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
1748
2160
|
const { discoverPlugins } =
|
|
1749
2161
|
await import("../lib/plugin-runtime/scopes.js");
|
|
1750
2162
|
const { VERSION } = await import("../constants.js");
|
|
@@ -1766,6 +2178,9 @@ export function registerPluginCommand(program) {
|
|
|
1766
2178
|
const resolved = await fetchRegistry(url, {
|
|
1767
2179
|
token,
|
|
1768
2180
|
allowInsecure: options.allowInsecureRegistry === true,
|
|
2181
|
+
offline: options.offline === true,
|
|
2182
|
+
expectedSha256: registryDigestPins.get(url),
|
|
2183
|
+
...registryNetworkOptions(options),
|
|
1769
2184
|
});
|
|
1770
2185
|
return { url, ...resolved };
|
|
1771
2186
|
} catch (error) {
|
|
@@ -1904,6 +2319,16 @@ export function registerPluginCommand(program) {
|
|
|
1904
2319
|
"--allow-insecure-registry",
|
|
1905
2320
|
"Allow plain-HTTP registry URLs (MITM risk — trusted networks only)",
|
|
1906
2321
|
)
|
|
2322
|
+
.option("--offline", "Use only verified immutable registry caches")
|
|
2323
|
+
.option(
|
|
2324
|
+
"--registry-digest <url=sha256>",
|
|
2325
|
+
"Pin exact immutable registry documents (repeatable)",
|
|
2326
|
+
collectRepeatableOption,
|
|
2327
|
+
[],
|
|
2328
|
+
)
|
|
2329
|
+
.option("--proxy <url>", "Explicit HTTP(S) registry proxy")
|
|
2330
|
+
.option("--pac-file <path>", "Bounded local PAC file")
|
|
2331
|
+
.option("--ca-file <path>", "Custom registry CA bundle")
|
|
1907
2332
|
.option(
|
|
1908
2333
|
"--strict",
|
|
1909
2334
|
"Block candidates missing digest, signature, SBOM, license, or capabilities",
|
|
@@ -1911,14 +2336,22 @@ export function registerPluginCommand(program) {
|
|
|
1911
2336
|
.option("--json", "Output the versioned candidate selection as JSON")
|
|
1912
2337
|
.action(async (name, options) => {
|
|
1913
2338
|
try {
|
|
2339
|
+
const registryUrls = normalizeRegistryUrls(options.registry);
|
|
2340
|
+
const registryDigestPins = normalizeRegistryDigestPins(
|
|
2341
|
+
options.registryDigest,
|
|
2342
|
+
registryUrls,
|
|
2343
|
+
);
|
|
1914
2344
|
const result = await buildRegistrySetSelection(
|
|
1915
|
-
|
|
2345
|
+
registryUrls,
|
|
1916
2346
|
name,
|
|
1917
2347
|
process.cwd(),
|
|
1918
2348
|
{
|
|
1919
2349
|
token: options.token,
|
|
1920
2350
|
allowInsecure: options.allowInsecureRegistry === true,
|
|
1921
2351
|
strict: options.strict === true,
|
|
2352
|
+
offline: options.offline === true,
|
|
2353
|
+
registryDigestPins,
|
|
2354
|
+
...registryNetworkOptions(options),
|
|
1922
2355
|
},
|
|
1923
2356
|
);
|
|
1924
2357
|
const { selection } = result;
|
|
@@ -1980,10 +2413,24 @@ export function registerPluginCommand(program) {
|
|
|
1980
2413
|
"--allow-insecure-registry",
|
|
1981
2414
|
"Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
|
|
1982
2415
|
)
|
|
2416
|
+
.option("--offline", "Use only verified immutable registry caches")
|
|
2417
|
+
.option(
|
|
2418
|
+
"--registry-digest <url=sha256>",
|
|
2419
|
+
"Pin exact immutable registry documents (repeatable)",
|
|
2420
|
+
collectRepeatableOption,
|
|
2421
|
+
[],
|
|
2422
|
+
)
|
|
2423
|
+
.option("--proxy <url>", "Explicit HTTP(S) registry proxy")
|
|
2424
|
+
.option("--pac-file <path>", "Bounded local PAC file")
|
|
2425
|
+
.option("--ca-file <path>", "Custom registry CA bundle")
|
|
1983
2426
|
.option("--json", "Output the versioned update-impact projection as JSON")
|
|
1984
2427
|
.action(async (name, options) => {
|
|
1985
2428
|
try {
|
|
1986
2429
|
const registryUrls = normalizeRegistryUrls(options.registry);
|
|
2430
|
+
const registryDigestPins = normalizeRegistryDigestPins(
|
|
2431
|
+
options.registryDigest,
|
|
2432
|
+
registryUrls,
|
|
2433
|
+
);
|
|
1987
2434
|
const selected = await buildRegistrySetSelection(
|
|
1988
2435
|
registryUrls,
|
|
1989
2436
|
name,
|
|
@@ -1991,6 +2438,9 @@ export function registerPluginCommand(program) {
|
|
|
1991
2438
|
{
|
|
1992
2439
|
token: options.token,
|
|
1993
2440
|
allowInsecure: options.allowInsecureRegistry === true,
|
|
2441
|
+
offline: options.offline === true,
|
|
2442
|
+
registryDigestPins,
|
|
2443
|
+
...registryNetworkOptions(options),
|
|
1994
2444
|
},
|
|
1995
2445
|
);
|
|
1996
2446
|
if (!selected.preflight) {
|
|
@@ -2019,6 +2469,12 @@ export function registerPluginCommand(program) {
|
|
|
2019
2469
|
logger.log(
|
|
2020
2470
|
` source: ${impact.changes.source.kind}${impact.changes.source.requiresApproval ? " (explicit approval required)" : ""}`,
|
|
2021
2471
|
);
|
|
2472
|
+
if (impact.changes.scopeAuthority) {
|
|
2473
|
+
const authority = impact.changes.scopeAuthority;
|
|
2474
|
+
logger.log(
|
|
2475
|
+
` scope authority: ${authority.effectiveFrom || "none"} → ${authority.effectiveTo || "none"}${authority.changed ? " (effective change)" : ""}`,
|
|
2476
|
+
);
|
|
2477
|
+
}
|
|
2022
2478
|
logger.log(
|
|
2023
2479
|
` license: ${impact.changes.license.from || "unknown"} → ${impact.changes.license.to || "unknown"}`,
|
|
2024
2480
|
);
|
|
@@ -2060,11 +2516,17 @@ export function registerPluginCommand(program) {
|
|
|
2060
2516
|
plugin
|
|
2061
2517
|
.command("installed")
|
|
2062
2518
|
.description("List plugins installed in the unified runtime (scope dirs)")
|
|
2519
|
+
.option(
|
|
2520
|
+
"--all-scopes",
|
|
2521
|
+
"Show every physical scoped install, including shadowed and disabled rows",
|
|
2522
|
+
)
|
|
2063
2523
|
.option("--json", "Output as JSON")
|
|
2064
2524
|
.action(async (options) => {
|
|
2065
|
-
const { listInstalled } =
|
|
2525
|
+
const { listInstalled, listInstalledAllScopes } =
|
|
2066
2526
|
await import("../lib/plugin-runtime/install.js");
|
|
2067
|
-
const rows =
|
|
2527
|
+
const rows = options.allScopes
|
|
2528
|
+
? listInstalledAllScopes({ cwd: process.cwd() })
|
|
2529
|
+
: listInstalled({ cwd: process.cwd() });
|
|
2068
2530
|
if (options.json) {
|
|
2069
2531
|
console.log(JSON.stringify(rows, null, 2));
|
|
2070
2532
|
return;
|
|
@@ -2077,6 +2539,17 @@ export function registerPluginCommand(program) {
|
|
|
2077
2539
|
await import("../lib/plugin-runtime/trust.js");
|
|
2078
2540
|
logger.log(chalk.bold(`Installed plugins (${rows.length}):`));
|
|
2079
2541
|
for (const r of rows) {
|
|
2542
|
+
if (r.runtimeBlocked === true) {
|
|
2543
|
+
const status = r.activePointer?.status || "unknown";
|
|
2544
|
+
const inspectionVersion = r.activePointer?.inspectionVersion;
|
|
2545
|
+
const inspection = inspectionVersion
|
|
2546
|
+
? `, inspect=v${inspectionVersion}`
|
|
2547
|
+
: "";
|
|
2548
|
+
logger.log(
|
|
2549
|
+
` ${chalk.red("!")} ${chalk.cyan(r.name)} ${chalk.gray(`[${r.scope}]`)} ${chalk.red(`active=blocked (${status})`)}${inspection}`,
|
|
2550
|
+
);
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2080
2553
|
const ok = r.ok ? chalk.green("✔") : chalk.red("✖");
|
|
2081
2554
|
const trust = isPluginTrusted(r)
|
|
2082
2555
|
? chalk.green("trusted")
|
|
@@ -2100,8 +2573,172 @@ export function registerPluginCommand(program) {
|
|
|
2100
2573
|
}
|
|
2101
2574
|
});
|
|
2102
2575
|
|
|
2576
|
+
// plugin transaction/recover — inspect or resolve an abandoned durable
|
|
2577
|
+
// lifecycle owner. Ordinary mutations never reclaim this authority.
|
|
2578
|
+
plugin
|
|
2579
|
+
.command("transaction <name>")
|
|
2580
|
+
.description("Inspect a durable cross-process plugin lifecycle transaction")
|
|
2581
|
+
.option("--scope <scope>", "Installed plugin scope", "user")
|
|
2582
|
+
.option("--json", "Output the redacted transaction authority as JSON")
|
|
2583
|
+
.action(async (name, options) => {
|
|
2584
|
+
try {
|
|
2585
|
+
const { inspectPluginTransaction } =
|
|
2586
|
+
await import("../lib/plugin-runtime/install.js");
|
|
2587
|
+
const transaction = inspectPluginTransaction(name, {
|
|
2588
|
+
scope: options.scope,
|
|
2589
|
+
cwd: process.cwd(),
|
|
2590
|
+
});
|
|
2591
|
+
if (!transaction) {
|
|
2592
|
+
throw new Error(
|
|
2593
|
+
`no retained transaction exists for ${name} at ${options.scope} scope`,
|
|
2594
|
+
);
|
|
2595
|
+
}
|
|
2596
|
+
if (options.json) {
|
|
2597
|
+
console.log(JSON.stringify(transaction, null, 2));
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2600
|
+
logger.log(
|
|
2601
|
+
`${name} [${options.scope}] ${transaction.operation}/${transaction.phase} r${transaction.revision}`,
|
|
2602
|
+
);
|
|
2603
|
+
logger.log(
|
|
2604
|
+
` owner: pid=${transaction.owner.pid} host=${transaction.owner.hostname} alive=${transaction.owner.alive == null ? "unverifiable" : transaction.owner.alive}`,
|
|
2605
|
+
);
|
|
2606
|
+
logger.log(` journal digest: ${transaction.journalDigest}`);
|
|
2607
|
+
} catch (error) {
|
|
2608
|
+
logger.error(`Transaction inspection failed: ${error.message}`);
|
|
2609
|
+
process.exitCode = 1;
|
|
2610
|
+
}
|
|
2611
|
+
});
|
|
2612
|
+
|
|
2613
|
+
plugin
|
|
2614
|
+
.command("recover <name>")
|
|
2615
|
+
.description("Recover a durable plugin transaction after its owner died")
|
|
2616
|
+
.option("--scope <scope>", "Installed plugin scope", "user")
|
|
2617
|
+
.option(
|
|
2618
|
+
"--action <action>",
|
|
2619
|
+
"Recovery action (rollback|finalize|abort)",
|
|
2620
|
+
"rollback",
|
|
2621
|
+
)
|
|
2622
|
+
.option(
|
|
2623
|
+
"--force-owner",
|
|
2624
|
+
"Explicitly override a live or cross-host owner after external adjudication",
|
|
2625
|
+
)
|
|
2626
|
+
.option("--json", "Output the recovery result as JSON")
|
|
2627
|
+
.action(async (name, options) => {
|
|
2628
|
+
try {
|
|
2629
|
+
const { recoverPluginTransaction } =
|
|
2630
|
+
await import("../lib/plugin-runtime/install.js");
|
|
2631
|
+
const recovery = recoverPluginTransaction(name, {
|
|
2632
|
+
scope: options.scope,
|
|
2633
|
+
cwd: process.cwd(),
|
|
2634
|
+
action: options.action,
|
|
2635
|
+
forceOwner: options.forceOwner === true,
|
|
2636
|
+
});
|
|
2637
|
+
if (options.json) {
|
|
2638
|
+
console.log(JSON.stringify(recovery, null, 2));
|
|
2639
|
+
return;
|
|
2640
|
+
}
|
|
2641
|
+
logger.success(
|
|
2642
|
+
`${name} transaction ${recovery.action}: ${recovery.recovered ? "recovered" : "recovery still required"}`,
|
|
2643
|
+
);
|
|
2644
|
+
if (recovery.cleanupPending) {
|
|
2645
|
+
logger.warn(`Cleanup remains at ${recovery.cleanupPath}`);
|
|
2646
|
+
}
|
|
2647
|
+
} catch (error) {
|
|
2648
|
+
logger.error(`Transaction recovery failed: ${error.message}`);
|
|
2649
|
+
process.exitCode = 1;
|
|
2650
|
+
}
|
|
2651
|
+
});
|
|
2652
|
+
|
|
2653
|
+
plugin
|
|
2654
|
+
.command("provenance-plan <name>")
|
|
2655
|
+
.description(
|
|
2656
|
+
"Build exact payload-bound authority bytes for a signed legacy provenance migration",
|
|
2657
|
+
)
|
|
2658
|
+
.requiredOption("--version <version>", "Installed plugin version")
|
|
2659
|
+
.requiredOption(
|
|
2660
|
+
"--metadata <file>",
|
|
2661
|
+
"JSON source metadata to bind (local, git, or complete registry authority)",
|
|
2662
|
+
)
|
|
2663
|
+
.option("--scope <scope>", "Installed plugin scope", "user")
|
|
2664
|
+
.option(
|
|
2665
|
+
"--issued-at <timestamp>",
|
|
2666
|
+
"Canonical ISO-8601 UTC signing time (defaults to now)",
|
|
2667
|
+
)
|
|
2668
|
+
.action(async (name, options) => {
|
|
2669
|
+
try {
|
|
2670
|
+
const { planPluginProvenanceMigration } =
|
|
2671
|
+
await import("../lib/plugin-runtime/install.js");
|
|
2672
|
+
const plan = planPluginProvenanceMigration(name, {
|
|
2673
|
+
scope: options.scope,
|
|
2674
|
+
cwd: process.cwd(),
|
|
2675
|
+
version: options.version,
|
|
2676
|
+
issuedAt: options.issuedAt,
|
|
2677
|
+
sourceMetadata: readBoundedPluginJson(
|
|
2678
|
+
options.metadata,
|
|
2679
|
+
"plugin source metadata",
|
|
2680
|
+
),
|
|
2681
|
+
});
|
|
2682
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
2683
|
+
} catch (error) {
|
|
2684
|
+
logger.error(`Provenance migration plan failed: ${error.message}`);
|
|
2685
|
+
process.exitCode = 1;
|
|
2686
|
+
}
|
|
2687
|
+
});
|
|
2688
|
+
|
|
2689
|
+
plugin
|
|
2690
|
+
.command("provenance-migrate <name>")
|
|
2691
|
+
.description(
|
|
2692
|
+
"Apply a signed, payload-bound provenance record to one legacy install",
|
|
2693
|
+
)
|
|
2694
|
+
.requiredOption("--version <version>", "Installed plugin version")
|
|
2695
|
+
.requiredOption(
|
|
2696
|
+
"--attestation <file>",
|
|
2697
|
+
"Signed attestation JSON containing authority, publicKeyPem, and signatureBase64",
|
|
2698
|
+
)
|
|
2699
|
+
.requiredOption(
|
|
2700
|
+
"--expected-signer-sha256 <hex>",
|
|
2701
|
+
"Exact trusted signer SPKI SHA-256 fingerprint",
|
|
2702
|
+
)
|
|
2703
|
+
.option("--scope <scope>", "Installed plugin scope", "user")
|
|
2704
|
+
.option("--yes", "Confirm the provenance mutation")
|
|
2705
|
+
.option("--json", "Output the migration result as JSON")
|
|
2706
|
+
.action(async (name, options) => {
|
|
2707
|
+
try {
|
|
2708
|
+
if (options.yes !== true) {
|
|
2709
|
+
throw new Error(
|
|
2710
|
+
"refusing provenance mutation without --yes after reviewing the signed plan",
|
|
2711
|
+
);
|
|
2712
|
+
}
|
|
2713
|
+
const { migratePluginProvenance } =
|
|
2714
|
+
await import("../lib/plugin-runtime/install.js");
|
|
2715
|
+
const result = migratePluginProvenance(name, {
|
|
2716
|
+
scope: options.scope,
|
|
2717
|
+
cwd: process.cwd(),
|
|
2718
|
+
version: options.version,
|
|
2719
|
+
attestation: readBoundedPluginJson(
|
|
2720
|
+
options.attestation,
|
|
2721
|
+
"plugin provenance attestation",
|
|
2722
|
+
),
|
|
2723
|
+
expectedSignerSha256: options.expectedSignerSha256,
|
|
2724
|
+
});
|
|
2725
|
+
if (options.json) {
|
|
2726
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
logger.success(
|
|
2730
|
+
`${name}@${result.version} provenance migrated at ${result.scope} scope`,
|
|
2731
|
+
);
|
|
2732
|
+
logger.log(` authority: ${result.authorityDigest}`);
|
|
2733
|
+
logger.log(` signer: ${result.signerPublicKeySha256}`);
|
|
2734
|
+
} catch (error) {
|
|
2735
|
+
logger.error(`Provenance migration failed: ${error.message}`);
|
|
2736
|
+
process.exitCode = 1;
|
|
2737
|
+
}
|
|
2738
|
+
});
|
|
2739
|
+
|
|
2103
2740
|
// plugin evidence — compare persisted registry artifact expectations with
|
|
2104
|
-
// actual bytes in the active
|
|
2741
|
+
// actual bytes in the active on-disk install. Remote artifacts are not
|
|
2105
2742
|
// re-fetched here. The signature record is bound to the locally re-verified
|
|
2106
2743
|
// lock; the external SBOM digest remains an install-time record because its
|
|
2107
2744
|
// document bytes are not retained.
|
|
@@ -2118,7 +2755,7 @@ export function registerPluginCommand(program) {
|
|
|
2118
2755
|
.option("--json", "Output the versioned artifact readback as JSON")
|
|
2119
2756
|
.action(async (name, options) => {
|
|
2120
2757
|
try {
|
|
2121
|
-
const { listInstalled } =
|
|
2758
|
+
const { listInstalled, readSourceMetadataStrict } =
|
|
2122
2759
|
await import("../lib/plugin-runtime/install.js");
|
|
2123
2760
|
const installed = listInstalled({
|
|
2124
2761
|
cwd: process.cwd(),
|
|
@@ -2130,12 +2767,21 @@ export function registerPluginCommand(program) {
|
|
|
2130
2767
|
if (!installed) {
|
|
2131
2768
|
throw new Error(`${name} is not installed at ${options.scope} scope`);
|
|
2132
2769
|
}
|
|
2770
|
+
if (installed.runtimeBlocked === true) {
|
|
2771
|
+
throw new Error(
|
|
2772
|
+
`${name} runtime is blocked by active pointer status ${installed.activePointer?.status || "unknown"}; repair it with plugin use before reading active evidence`,
|
|
2773
|
+
);
|
|
2774
|
+
}
|
|
2775
|
+
const source = readSourceMetadataStrict(installed.dir, {
|
|
2776
|
+
required: true,
|
|
2777
|
+
requireRegistryAuthority: true,
|
|
2778
|
+
});
|
|
2133
2779
|
const { buildPluginMarketplaceArtifactReadback } =
|
|
2134
2780
|
await import("../lib/plugin-runtime/marketplace-artifact-readback.js");
|
|
2135
2781
|
const evidence = buildPluginMarketplaceArtifactReadback({
|
|
2136
2782
|
root: installed.dir,
|
|
2137
2783
|
scope: installed.scope,
|
|
2138
|
-
source
|
|
2784
|
+
source,
|
|
2139
2785
|
});
|
|
2140
2786
|
if (options.json) {
|
|
2141
2787
|
console.log(JSON.stringify(evidence, null, 2));
|
|
@@ -2174,14 +2820,14 @@ export function registerPluginCommand(program) {
|
|
|
2174
2820
|
}`
|
|
2175
2821
|
: null,
|
|
2176
2822
|
evidence.claims.remoteSbomFetched
|
|
2177
|
-
? "the SBOM digest is an install-time record
|
|
2823
|
+
? "the remote SBOM document digest is an install-time record; its document bytes were not retained or re-hashed, while the installed payload inventory above was freshly computed"
|
|
2178
2824
|
: null,
|
|
2179
2825
|
].filter(Boolean);
|
|
2180
2826
|
logger.log(
|
|
2181
2827
|
chalk.gray(
|
|
2182
2828
|
remoteKinds.length
|
|
2183
|
-
? ` Install-time remote ${remoteKinds.join("/")} evidence is present: ${remoteDetail.join("; ")}. Remote documents were not re-fetched; publisher identity
|
|
2184
|
-
:
|
|
2829
|
+
? ` Install-time remote ${remoteKinds.join("/")} evidence is present: ${remoteDetail.join("; ")}. Remote documents were not re-fetched; publisher identity is ${evidence.claims.registryPublisherIdentityVerified ? "verified by the current managed organization trust root" : "unverified"}.`
|
|
2830
|
+
: ` No valid install-time remote signature/SBOM evidence is available; publisher identity is ${evidence.claims.registryPublisherIdentityVerified ? "verified by the current managed organization trust root" : "unverified"}.`,
|
|
2185
2831
|
),
|
|
2186
2832
|
);
|
|
2187
2833
|
}
|
|
@@ -2574,13 +3220,29 @@ export function registerPluginCommand(program) {
|
|
|
2574
3220
|
.description(`Uninstall a runtime plugin from a scope (${SCOPES})`)
|
|
2575
3221
|
.option("--scope <scope>", "Scope to remove from", "user")
|
|
2576
3222
|
.option("--version <version>", "Remove only this version (default: all)")
|
|
3223
|
+
.option(
|
|
3224
|
+
"--allow-source-switch",
|
|
3225
|
+
"Approve a different-source fallback when removing the active version",
|
|
3226
|
+
)
|
|
2577
3227
|
.action(async (name, options) => {
|
|
2578
|
-
const { uninstall } =
|
|
3228
|
+
const { listInstalled, uninstall } =
|
|
3229
|
+
await import("../lib/plugin-runtime/install.js");
|
|
2579
3230
|
try {
|
|
3231
|
+
const blockedRow = listInstalled({
|
|
3232
|
+
cwd: process.cwd(),
|
|
3233
|
+
scopes: [options.scope],
|
|
3234
|
+
}).find(
|
|
3235
|
+
(row) =>
|
|
3236
|
+
row.name === name &&
|
|
3237
|
+
row.scope === options.scope &&
|
|
3238
|
+
row.runtimeBlocked === true,
|
|
3239
|
+
);
|
|
2580
3240
|
const res = uninstall(name, {
|
|
2581
3241
|
scope: options.scope,
|
|
2582
3242
|
cwd: process.cwd(),
|
|
2583
3243
|
version: options.version,
|
|
3244
|
+
allowSourceSwitch: options.allowSourceSwitch === true,
|
|
3245
|
+
allowBlockedIdentity: Boolean(blockedRow),
|
|
2584
3246
|
});
|
|
2585
3247
|
logger.success(
|
|
2586
3248
|
`Uninstalled ${name} (${res.removed.join(", ") || "nothing"}) from ${options.scope} scope`,
|
|
@@ -2617,6 +3279,28 @@ export function registerPluginCommand(program) {
|
|
|
2617
3279
|
"--allow-insecure-registry",
|
|
2618
3280
|
"Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
|
|
2619
3281
|
)
|
|
3282
|
+
.option(
|
|
3283
|
+
"--offline",
|
|
3284
|
+
"Use verified registry/artifact/source caches or an existing local plugin source",
|
|
3285
|
+
)
|
|
3286
|
+
.option(
|
|
3287
|
+
"--registry-digest <url=sha256>",
|
|
3288
|
+
"Pin an exact immutable registry document (repeatable)",
|
|
3289
|
+
collectRepeatableOption,
|
|
3290
|
+
[],
|
|
3291
|
+
)
|
|
3292
|
+
.option(
|
|
3293
|
+
"--proxy <url>",
|
|
3294
|
+
"Explicit HTTP(S) registry proxy (or CC_PLUGIN_REGISTRY_PROXY)",
|
|
3295
|
+
)
|
|
3296
|
+
.option(
|
|
3297
|
+
"--pac-file <path>",
|
|
3298
|
+
"Bounded local PAC file (or CC_PLUGIN_REGISTRY_PAC_FILE)",
|
|
3299
|
+
)
|
|
3300
|
+
.option(
|
|
3301
|
+
"--ca-file <path>",
|
|
3302
|
+
"Custom registry CA bundle (or CC_PLUGIN_REGISTRY_CA_FILE)",
|
|
3303
|
+
)
|
|
2620
3304
|
.option(
|
|
2621
3305
|
"--allow-source-switch",
|
|
2622
3306
|
"Explicitly approve changing registry/source authority",
|
|
@@ -2649,8 +3333,28 @@ export function registerPluginCommand(program) {
|
|
|
2649
3333
|
const registryUrls = normalizeRegistryUrls(options.registry);
|
|
2650
3334
|
const { isRemoteSource, resolveRemoteSource } =
|
|
2651
3335
|
await import("../lib/plugin-runtime/remote-source.js");
|
|
3336
|
+
if (
|
|
3337
|
+
options.offline === true &&
|
|
3338
|
+
registryUrls.length === 0 &&
|
|
3339
|
+
!isRemoteSource(source)
|
|
3340
|
+
) {
|
|
3341
|
+
try {
|
|
3342
|
+
assertOfflinePluginSourceLocal(source);
|
|
3343
|
+
} catch (error) {
|
|
3344
|
+
logger.error(`Offline source validation failed: ${error.message}`);
|
|
3345
|
+
process.exitCode = 1;
|
|
3346
|
+
return;
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
2652
3349
|
if (registryUrls.length || isRemoteSource(source)) {
|
|
2653
3350
|
try {
|
|
3351
|
+
const requestedRegistryUrls = registryUrls.length
|
|
3352
|
+
? registryUrls
|
|
3353
|
+
: [source];
|
|
3354
|
+
const registryDigestPins = normalizeRegistryDigestPins(
|
|
3355
|
+
options.registryDigest,
|
|
3356
|
+
requestedRegistryUrls,
|
|
3357
|
+
);
|
|
2654
3358
|
let resolved;
|
|
2655
3359
|
let url;
|
|
2656
3360
|
let name;
|
|
@@ -2663,6 +3367,9 @@ export function registerPluginCommand(program) {
|
|
|
2663
3367
|
{
|
|
2664
3368
|
token: options.token,
|
|
2665
3369
|
allowInsecure: options.allowInsecureRegistry === true,
|
|
3370
|
+
offline: options.offline === true,
|
|
3371
|
+
registryDigestPins,
|
|
3372
|
+
...registryNetworkOptions(options),
|
|
2666
3373
|
},
|
|
2667
3374
|
);
|
|
2668
3375
|
resolved = selected.resolved;
|
|
@@ -2682,6 +3389,9 @@ export function registerPluginCommand(program) {
|
|
|
2682
3389
|
token: options.token,
|
|
2683
3390
|
config,
|
|
2684
3391
|
allowInsecure: options.allowInsecureRegistry === true,
|
|
3392
|
+
offline: options.offline === true,
|
|
3393
|
+
expectedSha256: registryDigestPins.get(url),
|
|
3394
|
+
...registryNetworkOptions(options),
|
|
2685
3395
|
});
|
|
2686
3396
|
marketplacePreflight = await buildRegistryInstallPreflight(
|
|
2687
3397
|
url,
|
|
@@ -2711,6 +3421,25 @@ export function registerPluginCommand(program) {
|
|
|
2711
3421
|
options.scope,
|
|
2712
3422
|
process.cwd(),
|
|
2713
3423
|
);
|
|
3424
|
+
if (
|
|
3425
|
+
marketplaceImpact.blockers.some(
|
|
3426
|
+
(blocker) => blocker.code === "SEMANTIC_SBOM_BINDING_DOWNGRADE",
|
|
3427
|
+
)
|
|
3428
|
+
) {
|
|
3429
|
+
throw new Error(
|
|
3430
|
+
"registry candidate preflight blocked: SEMANTIC_SBOM_BINDING_DOWNGRADE",
|
|
3431
|
+
);
|
|
3432
|
+
}
|
|
3433
|
+
if (
|
|
3434
|
+
marketplaceImpact.blockers.some(
|
|
3435
|
+
(blocker) =>
|
|
3436
|
+
blocker.code === "REGISTRY_VERSION_REQUIRED_FOR_UPDATE",
|
|
3437
|
+
)
|
|
3438
|
+
) {
|
|
3439
|
+
throw new Error(
|
|
3440
|
+
"registry candidate preflight blocked: REGISTRY_VERSION_REQUIRED_FOR_UPDATE",
|
|
3441
|
+
);
|
|
3442
|
+
}
|
|
2714
3443
|
if (
|
|
2715
3444
|
marketplaceImpact.changes.source.requiresApproval &&
|
|
2716
3445
|
options.allowSourceSwitch !== true
|
|
@@ -2808,6 +3537,8 @@ export function registerPluginCommand(program) {
|
|
|
2808
3537
|
{
|
|
2809
3538
|
token: options.token,
|
|
2810
3539
|
allowInsecure: options.allowInsecureRegistry === true,
|
|
3540
|
+
offline: options.offline === true,
|
|
3541
|
+
...registryNetworkOptions(options),
|
|
2811
3542
|
},
|
|
2812
3543
|
);
|
|
2813
3544
|
if (
|
|
@@ -2873,7 +3604,12 @@ export function registerPluginCommand(program) {
|
|
|
2873
3604
|
expectedIdentity,
|
|
2874
3605
|
managedPolicy: managed,
|
|
2875
3606
|
policySource: sourceMetadata?.registry || source,
|
|
3607
|
+
remoteSbomBytes: remoteArtifacts?.sbomBytes || null,
|
|
3608
|
+
offline: options.offline === true,
|
|
2876
3609
|
transactional: true,
|
|
3610
|
+
enforceUpdateApprovals: true,
|
|
3611
|
+
allowSourceSwitch: options.allowSourceSwitch === true,
|
|
3612
|
+
allowDowngrade: options.allowDowngrade === true,
|
|
2877
3613
|
});
|
|
2878
3614
|
marketplaceAuthorityPersisted = await installedCatalogAuthorityMatches(
|
|
2879
3615
|
res.name,
|
|
@@ -2937,6 +3673,7 @@ export function registerPluginCommand(program) {
|
|
|
2937
3673
|
let activationStatus = changed ? "activated" : "unchanged";
|
|
2938
3674
|
let rollbackVersion = null;
|
|
2939
3675
|
let cleanupPending = false;
|
|
3676
|
+
let cleanupPath = null;
|
|
2940
3677
|
if (rollbackReason) {
|
|
2941
3678
|
const recovery = rollbackPluginUpdate(res);
|
|
2942
3679
|
if (!recovery.rolledBack) {
|
|
@@ -2944,6 +3681,8 @@ export function registerPluginCommand(program) {
|
|
|
2944
3681
|
}
|
|
2945
3682
|
activationStatus = "rolled_back";
|
|
2946
3683
|
rollbackVersion = recovery.version;
|
|
3684
|
+
cleanupPending = recovery.cleanupPending === true;
|
|
3685
|
+
cleanupPath = recovery.cleanupPath || null;
|
|
2947
3686
|
marketplaceAuthorityPersisted =
|
|
2948
3687
|
await installedCatalogAuthorityMatches(
|
|
2949
3688
|
res.name,
|
|
@@ -2954,7 +3693,9 @@ export function registerPluginCommand(program) {
|
|
|
2954
3693
|
remoteArtifacts?.authority || null,
|
|
2955
3694
|
);
|
|
2956
3695
|
} else {
|
|
2957
|
-
|
|
3696
|
+
const finalization = finalizePluginUpdate(res);
|
|
3697
|
+
cleanupPending = finalization.cleanupPending === true;
|
|
3698
|
+
cleanupPath = finalization.cleanupPath || null;
|
|
2958
3699
|
}
|
|
2959
3700
|
|
|
2960
3701
|
if (options.json) {
|
|
@@ -2972,6 +3713,7 @@ export function registerPluginCommand(program) {
|
|
|
2972
3713
|
rollbackVersion,
|
|
2973
3714
|
rollbackReason,
|
|
2974
3715
|
cleanupPending,
|
|
3716
|
+
cleanupPath,
|
|
2975
3717
|
},
|
|
2976
3718
|
null,
|
|
2977
3719
|
2,
|
|
@@ -3018,6 +3760,11 @@ export function registerPluginCommand(program) {
|
|
|
3018
3760
|
`${res.name} is already up to date at v${res.version} (use --force to reinstall)`,
|
|
3019
3761
|
);
|
|
3020
3762
|
}
|
|
3763
|
+
if (cleanupPending) {
|
|
3764
|
+
logger.warn(
|
|
3765
|
+
`Plugin state committed; inert cleanup remains at ${cleanupPath || "an internal cleanup directory"} and will be retried on the next mutation`,
|
|
3766
|
+
);
|
|
3767
|
+
}
|
|
3021
3768
|
} catch (err) {
|
|
3022
3769
|
let recoveryNote = "";
|
|
3023
3770
|
if (res) {
|
|
@@ -3027,6 +3774,9 @@ export function registerPluginCommand(program) {
|
|
|
3027
3774
|
recoveryNote = recovery.version
|
|
3028
3775
|
? `; restored v${recovery.version}`
|
|
3029
3776
|
: "; removed the rejected install";
|
|
3777
|
+
if (recovery.cleanupPending) {
|
|
3778
|
+
recoveryNote += `; inert cleanup retained at ${recovery.cleanupPath || "an internal cleanup directory"}`;
|
|
3779
|
+
}
|
|
3030
3780
|
}
|
|
3031
3781
|
} catch (recoveryError) {
|
|
3032
3782
|
recoveryNote = `; automatic recovery failed: ${recoveryError.message}`;
|
|
@@ -3044,6 +3794,10 @@ export function registerPluginCommand(program) {
|
|
|
3044
3794
|
.command("use <name> <version>")
|
|
3045
3795
|
.description("Pin a plugin's active version (rollback or switch)")
|
|
3046
3796
|
.option("--scope <scope>", "Scope", "user")
|
|
3797
|
+
.option(
|
|
3798
|
+
"--allow-source-switch",
|
|
3799
|
+
"Approve activating an installed version from a different source",
|
|
3800
|
+
)
|
|
3047
3801
|
.action(async (name, version, options) => {
|
|
3048
3802
|
const { setActiveVersion } =
|
|
3049
3803
|
await import("../lib/plugin-runtime/install.js");
|
|
@@ -3051,6 +3805,7 @@ export function registerPluginCommand(program) {
|
|
|
3051
3805
|
setActiveVersion(name, version, {
|
|
3052
3806
|
scope: options.scope,
|
|
3053
3807
|
cwd: process.cwd(),
|
|
3808
|
+
allowSourceSwitch: options.allowSourceSwitch === true,
|
|
3054
3809
|
});
|
|
3055
3810
|
logger.success(
|
|
3056
3811
|
`${name} active version → v${version} (${options.scope} scope)`,
|