reeval 0.10.0 → 0.12.0
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 +64 -39
- package/THIRD_PARTY_NOTICES.md +75 -106
- package/bin/mcp-timer.cjs +224 -0
- package/dist/{adapter-DWlpOtRn.js → adapter-DigeVFWC.js} +3 -3
- package/dist/{adapter-DWlpOtRn.js.map → adapter-DigeVFWC.js.map} +1 -1
- package/dist/{agent-CaS_2Sce.js → agent-CatUEo5h.js} +21 -6
- package/dist/{agent-CaS_2Sce.js.map → agent-CatUEo5h.js.map} +1 -1
- package/dist/{assemble-B9Alds-z.js → assemble-CF063ms6.js} +19 -17
- package/dist/assemble-CF063ms6.js.map +1 -0
- package/dist/assemble-CJoD_Rzr.js +1 -0
- package/dist/bridge/index.mjs +15 -7
- package/dist/{capture-CNb9SuU5.js → capture-BiD7lVaM.js} +26 -18
- package/dist/{capture-CNb9SuU5.js.map → capture-BiD7lVaM.js.map} +1 -1
- package/dist/capture-Dp3q7K7Y.js +1 -0
- package/dist/{cli-D303Vj5N.js → cli-B5zQCR2r.js} +266 -547
- package/dist/cli-B5zQCR2r.js.map +1 -0
- package/dist/{cli-spec-BPZbQG2p.js → cli-spec-BXIBUlaK.js} +46 -18
- package/dist/cli-spec-BXIBUlaK.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/{command-BT9a37ZR.js → command-BMHXHqMT.js} +17 -14
- package/dist/command-BMHXHqMT.js.map +1 -0
- package/dist/command-C94CQNh8.js +91 -0
- package/dist/command-C94CQNh8.js.map +1 -0
- package/dist/command-CVtgyqlM.js +335 -0
- package/dist/command-CVtgyqlM.js.map +1 -0
- package/dist/{command-BLfT7vnQ.js → command-CaZgCTIw.js} +13 -17
- package/dist/command-CaZgCTIw.js.map +1 -0
- package/dist/{command-CMJtHKRa.js → command-DTgBZR0k.js} +6 -6
- package/dist/{command-CMJtHKRa.js.map → command-DTgBZR0k.js.map} +1 -1
- package/dist/{commands-1p-bIWhN.js → commands-BXzIDB3X.js} +1 -1
- package/dist/commands-CjpGBUWr.js +666 -0
- package/dist/commands-CjpGBUWr.js.map +1 -0
- package/dist/{config-DEgnUiU-.js → config-Dv5MG7Rp.js} +31 -34
- package/dist/config-Dv5MG7Rp.js.map +1 -0
- package/dist/{context-C1mxXh80.js → context-Dgn48BqT.js} +13 -2
- package/dist/context-Dgn48BqT.js.map +1 -0
- package/dist/contrasts-CcoMLC_y.js +101 -0
- package/dist/contrasts-CcoMLC_y.js.map +1 -0
- package/dist/db-BeqRiWLl.js +602 -0
- package/dist/db-BeqRiWLl.js.map +1 -0
- package/dist/db-CJDRVQal.js +2 -0
- package/dist/{dist-B8hJImYt.js → dist-CMjp3qqF.js} +81 -14
- package/dist/dist-CMjp3qqF.js.map +1 -0
- package/dist/{docker-sZrNrr82.js → docker-BbTlMmYi.js} +2 -2
- package/dist/{docker-sZrNrr82.js.map → docker-BbTlMmYi.js.map} +1 -1
- package/dist/{docker-7b_2SaH7.js → docker-CCXdbREk.js} +20 -4
- package/dist/{docker-7b_2SaH7.js.map → docker-CCXdbREk.js.map} +1 -1
- package/dist/{docker-yauK-9X8.js → docker-DC69dKxB.js} +1 -1
- package/dist/{doctor-BYNok4UO.js → doctor-CqttrZcs.js} +56 -22
- package/dist/doctor-CqttrZcs.js.map +1 -0
- package/dist/{egress-C4PZTSSe.js → egress-BfK_O9ce.js} +435 -31
- package/dist/egress-BfK_O9ce.js.map +1 -0
- package/dist/{environment-DNjRk0nU.js → environment-BBz1Yes9.js} +225 -12
- package/dist/environment-BBz1Yes9.js.map +1 -0
- package/dist/{evaluate-D4zeRGBF.js → evaluate-CXQ1o-pd.js} +539 -562
- package/dist/evaluate-CXQ1o-pd.js.map +1 -0
- package/dist/{evaluate-C1UVL21l.d.ts → evaluate-yjZKUexW.d.ts} +2 -2
- package/dist/facts-DtENlf9s.js +898 -0
- package/dist/facts-DtENlf9s.js.map +1 -0
- package/dist/{files-BXUdY8tr.js → files-B-eUpCZ1.js} +30 -7
- package/dist/files-B-eUpCZ1.js.map +1 -0
- package/dist/files-BsQbYz2i.js +65 -0
- package/dist/files-BsQbYz2i.js.map +1 -0
- package/dist/{files-DQmHE7pb.js → files-COHQLEHE.js} +6 -17
- package/dist/files-COHQLEHE.js.map +1 -0
- package/dist/files-DqCBW7i8.js +1 -0
- package/dist/files-Ha-jsQXf.js +1 -0
- package/dist/generate-Cs2TMH_k.js +1647 -0
- package/dist/generate-Cs2TMH_k.js.map +1 -0
- package/dist/{git-DfG-YJ1u.js → git-DhykwgKF.js} +2 -2
- package/dist/{git-DfG-YJ1u.js.map → git-DhykwgKF.js.map} +1 -1
- package/dist/{harness-B8QC9u0O.js → harness-BAjw1fcr.js} +3 -3
- package/dist/{harness-B8QC9u0O.js.map → harness-BAjw1fcr.js.map} +1 -1
- package/dist/harness.d.ts +1 -1
- package/dist/harness.js +1 -1
- package/dist/{header-hK-Z_Po9.js → header-kSmWatcO.js} +10 -3
- package/dist/{header-hK-Z_Po9.js.map → header-kSmWatcO.js.map} +1 -1
- package/dist/{images-0On0cztu.js → images-BmJRuTpm.js} +61 -13
- package/dist/images-BmJRuTpm.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/init-Bwc9byQr.js +543 -0
- package/dist/init-Bwc9byQr.js.map +1 -0
- package/dist/init-D7Y1ny1z.js +1 -0
- package/dist/insights-B2Xmhz9o.js +64 -0
- package/dist/insights-B2Xmhz9o.js.map +1 -0
- package/dist/{invocations-DbGfBAA_.js → invocations-CQTbP092.js} +3 -3
- package/dist/{invocations-DbGfBAA_.js.map → invocations-CQTbP092.js.map} +1 -1
- package/dist/{judge-DW_GDOmB.js → judge-Dt6KW6_O.js} +5 -3
- package/dist/judge-Dt6KW6_O.js.map +1 -0
- package/dist/judge.d.ts +7 -6
- package/dist/judge.js +8 -7
- package/dist/judge.js.map +1 -1
- package/dist/{live-jlfszAbb.js → live-DYd2QEyR.js} +2 -2
- package/dist/{live-jlfszAbb.js.map → live-DYd2QEyR.js.map} +1 -1
- package/dist/{model-h_cDtcRr.js → model-B-mel-Gv.js} +46 -20
- package/dist/model-B-mel-Gv.js.map +1 -0
- package/dist/model-BGKYVpoj.js +1 -0
- package/dist/{model-DaVCY3rb.js → model-C7tSwT7f.js} +2 -2
- package/dist/{model-DaVCY3rb.js.map → model-C7tSwT7f.js.map} +1 -1
- package/dist/{model-oqcDplJG.js → model-CWlAqmbU.js} +150 -46
- package/dist/model-CWlAqmbU.js.map +1 -0
- package/dist/model-DHY-dfoO.js +1 -0
- package/dist/{node-B3ydHo6t.js → node-D0mtjRAO.js} +3 -3
- package/dist/{node-B3ydHo6t.js.map → node-D0mtjRAO.js.map} +1 -1
- package/dist/{options-Bh2W63dK.js → options-CPUW97_7.js} +9 -12
- package/dist/options-CPUW97_7.js.map +1 -0
- package/dist/{pairs-CnInRu-G.js → pairs-DP6XOJby.js} +2 -2
- package/dist/{pairs-CnInRu-G.js.map → pairs-DP6XOJby.js.map} +1 -1
- package/dist/{patch-Zac5aIRM.js → patch-D4oluWzH.js} +2 -2
- package/dist/{patch-Zac5aIRM.js.map → patch-D4oluWzH.js.map} +1 -1
- package/dist/{plan-C9Cy27Ay.js → plan-OkwUcJQ9.js} +31 -12
- package/dist/plan-OkwUcJQ9.js.map +1 -0
- package/dist/plugins-CUWMnSNb.js +654 -0
- package/dist/plugins-CUWMnSNb.js.map +1 -0
- package/dist/{preflight-CPWpQeBI.js → preflight-B6pY89bF.js} +1 -1
- package/dist/{preflight-D7iFPKeK.js → preflight-Bjm7_ylE.js} +14 -10
- package/dist/preflight-Bjm7_ylE.js.map +1 -0
- package/dist/{prepare-doGxaAGR.js → prepare-CQmK3SBn.js} +71 -24
- package/dist/prepare-CQmK3SBn.js.map +1 -0
- package/dist/prepare-DbdgtZKe.js +2 -0
- package/dist/{provenance-DmE9uPVt.js → provenance-B3Y17oCC.js} +2 -2
- package/dist/{provenance-DmE9uPVt.js.map → provenance-B3Y17oCC.js.map} +1 -1
- package/dist/providers-CXN2RFz-.js +1 -0
- package/dist/{providers-tAOD1NQN.js → providers-eoniqK6w.js} +24 -10
- package/dist/providers-eoniqK6w.js.map +1 -0
- package/dist/query-CLRZmWBT.js +139 -0
- package/dist/query-CLRZmWBT.js.map +1 -0
- package/dist/query-CmBDYt0_.js +1 -0
- package/dist/{questions-DjAwMxZ3.js → questions-1XOulevV.js} +6 -6
- package/dist/{questions-DjAwMxZ3.js.map → questions-1XOulevV.js.map} +1 -1
- package/dist/{record-BKmMvZ6O.js → record-kk9M1hj-.js} +8 -4
- package/dist/record-kk9M1hj-.js.map +1 -0
- package/dist/remarks-1521zwfW.js +38 -0
- package/dist/remarks-1521zwfW.js.map +1 -0
- package/dist/remarks-BhAV1H4V.js +1 -0
- package/dist/remarks-DlJCqWRs.js +278 -0
- package/dist/remarks-DlJCqWRs.js.map +1 -0
- package/dist/{remote-HDRt16ew.js → remote-cE2VWfkJ.js} +3 -3
- package/dist/{remote-HDRt16ew.js.map → remote-cE2VWfkJ.js.map} +1 -1
- package/dist/resolve-C4zeccCH.js +724 -0
- package/dist/resolve-C4zeccCH.js.map +1 -0
- package/dist/resolve-D30yZYmB.js +1 -0
- package/dist/{results-DuauQI3Z.js → results-Drtt0uFl.js} +33 -7
- package/dist/results-Drtt0uFl.js.map +1 -0
- package/dist/{resume-BRfa8E_s.js → resume-BEFazUfy.js} +4 -4
- package/dist/{resume-BRfa8E_s.js.map → resume-BEFazUfy.js.map} +1 -1
- package/dist/rules-BFzQqPIm.js +317 -0
- package/dist/rules-BFzQqPIm.js.map +1 -0
- package/dist/run-BeMy92Us.js +88 -0
- package/dist/run-BeMy92Us.js.map +1 -0
- package/dist/{sandbox-D3A8oUb7.js → sandbox-CQaJj7n9.js} +2 -2
- package/dist/{sandbox-D3A8oUb7.js.map → sandbox-CQaJj7n9.js.map} +1 -1
- package/dist/sandbox.d.ts +1 -1
- package/dist/sandbox.js +1 -1
- package/dist/server-V4-2w0l8.js +1 -0
- package/dist/{server-XDleKaF_.js → server-xK0les4f.js} +8 -8
- package/dist/{server-XDleKaF_.js.map → server-xK0les4f.js.map} +1 -1
- package/dist/{shared-DYpIb1BI.js → shared-CA61BuPJ.js} +25 -3
- package/dist/shared-CA61BuPJ.js.map +1 -0
- package/dist/source-BDow_oW8.js +1 -0
- package/dist/{source-C8xumIWj.js → source-Q1hEKbCs.js} +2 -2
- package/dist/{source-C8xumIWj.js.map → source-Q1hEKbCs.js.map} +1 -1
- package/dist/src-B9L9yGKI.js +1 -0
- package/dist/{src-ofUaYbuY.js → src-Bcah6ZkT.js} +1 -1
- package/dist/{src-DvCg0NBs.js → src-BhmaorOU.js} +28 -6
- package/dist/{src-DvCg0NBs.js.map → src-BhmaorOU.js.map} +1 -1
- package/dist/{src-CF17Ug08.js → src-C0CeKiLU.js} +19 -4
- package/dist/{src-CF17Ug08.js.map → src-C0CeKiLU.js.map} +1 -1
- package/dist/src-C0xowZ3C.js +2 -0
- package/dist/src-CHIfIKUu.js +2 -0
- package/dist/src-DLtyz4w5.js +2 -0
- package/dist/src-DPIjmReZ.js +2 -0
- package/dist/{src-C_lPtowT.js → src-DXNh5mTr.js} +1 -1
- package/dist/{src-FGXhkEkH.js → src-DiLMkIr9.js} +142 -13
- package/dist/src-DiLMkIr9.js.map +1 -0
- package/dist/{src-B7treWa8.js → src-LekQrr_S.js} +324 -32
- package/dist/src-LekQrr_S.js.map +1 -0
- package/dist/src-bCnsA6Vp.js +2 -0
- package/dist/{src-u8_ixn_F.js → src-cbCb-MPy.js} +20 -4
- package/dist/src-cbCb-MPy.js.map +1 -0
- package/dist/{src-DvK0EIZx.js → src-qGoFkuJF.js} +55 -6
- package/dist/{src-DvK0EIZx.js.map → src-qGoFkuJF.js.map} +1 -1
- package/dist/{src-jzagbuqH.js → src-ynBVosfX.js} +1 -1
- package/dist/state-BQR_BCm4.js +1 -0
- package/dist/{state-C__9swoG.js → state-BmZ8QVZ8.js} +12 -12
- package/dist/{state-C__9swoG.js.map → state-BmZ8QVZ8.js.map} +1 -1
- package/dist/studio-app/assets/index-CnYJijyI.js +58 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/suite-BaHPmAGy.js +1 -0
- package/dist/suite-C8sw2pox.js +481 -0
- package/dist/suite-C8sw2pox.js.map +1 -0
- package/dist/summary-0oQNGskv.js +837 -0
- package/dist/summary-0oQNGskv.js.map +1 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +2 -2
- package/dist/{text-gEcKjAyp.js → text-CrVy5ioL.js} +18 -2
- package/dist/text-CrVy5ioL.js.map +1 -0
- package/dist/{transcript-BNNANU8A.js → transcript-BTxJpW4R.js} +2 -2
- package/dist/{transcript-BNNANU8A.js.map → transcript-BTxJpW4R.js.map} +1 -1
- package/dist/types-C5vI3g1C.js.map +1 -1
- package/dist/{types-g17bRAzt.d.ts → types-GraZtfHN.d.ts} +858 -39
- package/dist/usage-BDo2q_yu.js +1 -0
- package/dist/{command-Bge8F7Ko.js → validate-BdoXUuL_.js} +35 -383
- package/dist/validate-BdoXUuL_.js.map +1 -0
- package/dist/view/index.d.ts +20 -9
- package/dist/view/index.js +7 -6
- package/dist/views-BoUoX4_A.js +125 -0
- package/dist/views-BoUoX4_A.js.map +1 -0
- package/dist/views-noVQNCUP.js +2 -0
- package/dist/{worker-entry-yOchGYYv.js → worker-entry-D0-GErmU.js} +2 -2
- package/dist/{worker-entry-yOchGYYv.js.map → worker-entry-D0-GErmU.js.map} +1 -1
- package/dist/{workspace-D8sSgpiy.js → workspace-B90YDU_a.js} +1 -1
- package/dist/{workspace-CgyHEM0l.js → workspace-tjZFkjxC.js} +4 -4
- package/dist/{workspace-CgyHEM0l.js.map → workspace-tjZFkjxC.js.map} +1 -1
- package/package.json +10 -8
- package/skills/eval-rubric/SKILL.md +130 -0
- package/dist/agent-CURAwx_q.js +0 -334
- package/dist/agent-CURAwx_q.js.map +0 -1
- package/dist/assemble-B9Alds-z.js.map +0 -1
- package/dist/assemble-J5MukZFY.js +0 -1
- package/dist/capture-jquXZBKK.js +0 -1
- package/dist/cli-D303Vj5N.js.map +0 -1
- package/dist/cli-spec-BPZbQG2p.js.map +0 -1
- package/dist/command-BLfT7vnQ.js.map +0 -1
- package/dist/command-BT9a37ZR.js.map +0 -1
- package/dist/command-Bge8F7Ko.js.map +0 -1
- package/dist/command-BkFjijN2.js +0 -1
- package/dist/commands-BFx16WB8.js +0 -225
- package/dist/commands-BFx16WB8.js.map +0 -1
- package/dist/config-DEgnUiU-.js.map +0 -1
- package/dist/context-C1mxXh80.js.map +0 -1
- package/dist/db-CdHizc4_.js +0 -1406
- package/dist/db-CdHizc4_.js.map +0 -1
- package/dist/db-Dm1cRJJP.js +0 -2
- package/dist/dist-B8hJImYt.js.map +0 -1
- package/dist/doctor-BYNok4UO.js.map +0 -1
- package/dist/duration-CEOQww97.js +0 -26
- package/dist/duration-CEOQww97.js.map +0 -1
- package/dist/egress-C4PZTSSe.js.map +0 -1
- package/dist/environment-DNjRk0nU.js.map +0 -1
- package/dist/evaluate-D4zeRGBF.js.map +0 -1
- package/dist/family-D2b7WD10.js +0 -67
- package/dist/family-D2b7WD10.js.map +0 -1
- package/dist/files-BXUdY8tr.js.map +0 -1
- package/dist/files-BhyltlDB.js +0 -1
- package/dist/files-DQmHE7pb.js.map +0 -1
- package/dist/files-ZZPqfAo6.js +0 -1
- package/dist/images-0On0cztu.js.map +0 -1
- package/dist/init-B77Rpcqc.js +0 -360
- package/dist/init-B77Rpcqc.js.map +0 -1
- package/dist/init-BrOWin3W.js +0 -1
- package/dist/judge-DW_GDOmB.js.map +0 -1
- package/dist/model-NM3ID0uR.js +0 -1
- package/dist/model-h_cDtcRr.js.map +0 -1
- package/dist/model-nwWJvaUR.js +0 -1
- package/dist/model-oqcDplJG.js.map +0 -1
- package/dist/options-Bh2W63dK.js.map +0 -1
- package/dist/plan-C9Cy27Ay.js.map +0 -1
- package/dist/preflight-D7iFPKeK.js.map +0 -1
- package/dist/prepare-BGjePFQl.js +0 -2
- package/dist/prepare-doGxaAGR.js.map +0 -1
- package/dist/process-CSVO_jsc.js +0 -119
- package/dist/process-CSVO_jsc.js.map +0 -1
- package/dist/providers-gDMko8fl.js +0 -1
- package/dist/providers-tAOD1NQN.js.map +0 -1
- package/dist/query-Bes9Kh6Y.js +0 -91
- package/dist/query-Bes9Kh6Y.js.map +0 -1
- package/dist/query-C_G_svLU.js +0 -1
- package/dist/record-BKmMvZ6O.js.map +0 -1
- package/dist/results-DuauQI3Z.js.map +0 -1
- package/dist/server-C7sg1ndo.js +0 -1
- package/dist/shared-DYpIb1BI.js.map +0 -1
- package/dist/source-fGd0SaKP.js +0 -1
- package/dist/src-B7treWa8.js.map +0 -1
- package/dist/src-DNnG4e4R.js +0 -2
- package/dist/src-DTbrY1hi.js +0 -2
- package/dist/src-DdyJEh2v.js +0 -2
- package/dist/src-Dj9PAroc.js +0 -2
- package/dist/src-Du8H85Qq.js +0 -2
- package/dist/src-FGXhkEkH.js.map +0 -1
- package/dist/src-pJZOrYR9.js +0 -1
- package/dist/src-u8_ixn_F.js.map +0 -1
- package/dist/state-CgjQo29K.js +0 -1
- package/dist/studio-app/assets/index-DrQc2Yxd.js +0 -58
- package/dist/summary-BLAVLySU.js +0 -385
- package/dist/summary-BLAVLySU.js.map +0 -1
- package/dist/summary-DgyjUh_l.js +0 -192
- package/dist/summary-DgyjUh_l.js.map +0 -1
- package/dist/text-gEcKjAyp.js.map +0 -1
- package/dist/tools-GCyNYSc1.js +0 -249
- package/dist/tools-GCyNYSc1.js.map +0 -1
- package/dist/views-BW6oCDUQ.js +0 -2
- package/dist/views-BmQF4UCI.js +0 -225
- package/dist/views-BmQF4UCI.js.map +0 -1
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
import { t as closest } from "./shared-CA61BuPJ.js";
|
|
2
|
+
import { l as sha256 } from "./ids-Bp6ut7ZD.js";
|
|
3
|
+
import { L as hostAllowed } from "./src-LekQrr_S.js";
|
|
4
|
+
import { c as stripCredentials } from "./redact-DJ9JCShu.js";
|
|
5
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
6
|
+
import { basename, isAbsolute, join, posix, relative, sep, win32 } from "node:path";
|
|
7
|
+
import { parse } from "yaml";
|
|
8
|
+
//#region src/environment/plugins.ts
|
|
9
|
+
const PLUGIN_PARTS = [
|
|
10
|
+
"skills",
|
|
11
|
+
"agents",
|
|
12
|
+
"commands",
|
|
13
|
+
"hooks",
|
|
14
|
+
"mcp",
|
|
15
|
+
"rules"
|
|
16
|
+
];
|
|
17
|
+
/** Where each part lives in a plugin, for messages. */
|
|
18
|
+
const PART_PATHS = {
|
|
19
|
+
skills: "skills/",
|
|
20
|
+
agents: "agents/",
|
|
21
|
+
commands: "commands/",
|
|
22
|
+
hooks: "hooks/hooks.json",
|
|
23
|
+
mcp: ".mcp.json",
|
|
24
|
+
rules: "rules/"
|
|
25
|
+
};
|
|
26
|
+
/** A plugin's manifest, relative to its directory. */
|
|
27
|
+
const PLUGIN_MANIFEST = ".claude-plugin/plugin.json";
|
|
28
|
+
/** Other harnesses' plugin manifests, relative to the plugin's directory. */
|
|
29
|
+
const OTHER_MANIFESTS = [
|
|
30
|
+
"plugin.json",
|
|
31
|
+
".antigravity-plugin/plugin.json",
|
|
32
|
+
".gemini-plugin/plugin.json",
|
|
33
|
+
"gemini-extension.json",
|
|
34
|
+
".codex-plugin/plugin.json",
|
|
35
|
+
".cursor-plugin/plugin.json"
|
|
36
|
+
];
|
|
37
|
+
/** Whether a root `plugin.json` names an Agent Plugins `$schema`, which Codex needs to read it. */
|
|
38
|
+
function agentPluginsSchema(dir) {
|
|
39
|
+
try {
|
|
40
|
+
const file = join(dir, "plugin.json");
|
|
41
|
+
const schema = JSON.parse(readFileSync(file, "utf8")).$schema;
|
|
42
|
+
return typeof schema === "string" && schema.startsWith("https://agent-plugins.org/schemas/");
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const MARKETPLACE = ".claude-plugin/marketplace.json";
|
|
48
|
+
/** A plugin's name, in its manifest or a marketplace's entry. */
|
|
49
|
+
const PLUGIN_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
50
|
+
/** Manifest keys that move a part from its default place, which reeval doesn't follow. */
|
|
51
|
+
const CUSTOM_PATHS = [
|
|
52
|
+
"skills",
|
|
53
|
+
"agents",
|
|
54
|
+
"commands",
|
|
55
|
+
"hooks",
|
|
56
|
+
"outputStyles",
|
|
57
|
+
"lspServers"
|
|
58
|
+
];
|
|
59
|
+
/** Where the session copies each plugin, under the sandbox's home. */
|
|
60
|
+
const PLUGIN_DIR = "~/.reeval/plugins";
|
|
61
|
+
/** Plugin MCP servers name their own directory with this; the session substitutes it. */
|
|
62
|
+
const PLUGIN_ROOT_VAR = "CLAUDE_PLUGIN_ROOT";
|
|
63
|
+
const PLUGIN_ROOT = "${CLAUDE_PLUGIN_ROOT}";
|
|
64
|
+
/** A plugin's content that reeval can't place, in words for the plan or the attempt. */
|
|
65
|
+
var PluginProblem = class extends Error {
|
|
66
|
+
name = "PluginProblem";
|
|
67
|
+
};
|
|
68
|
+
const relativeTo = (root, path) => root ? relative(root, path).split(sep).join("/") || "." : path;
|
|
69
|
+
/** `plugin.local('plugins/review')`, as messages name a plugin before its manifest is read. */
|
|
70
|
+
function pluginCall(p, root) {
|
|
71
|
+
const arg = p.source === "local" ? relativeTo(root, p.path) : p.source === "git" ? p.repo : p.source === "npm" ? p.spec : `${p.name}@${p.marketplace.source === "git" ? npmLabel(p.marketplace.repo, root) : `npm:${npmLabel(p.marketplace.spec, root)}`}`;
|
|
72
|
+
return `plugin.${p.source}('${arg}')`;
|
|
73
|
+
}
|
|
74
|
+
const npmLabel = (spec, root) => isAbsolute(spec) ? relativeTo(root, spec) : spec;
|
|
75
|
+
/**
|
|
76
|
+
* Whether a marketplace is on this machine (a local directory, `file:` URL, tarball or folder),
|
|
77
|
+
* so its entries may name other paths on it.
|
|
78
|
+
*/
|
|
79
|
+
function localMarketplace(m) {
|
|
80
|
+
const at = m.source === "git" ? m.repo : m.spec;
|
|
81
|
+
return isAbsolute(at) || /^file:/i.test(at);
|
|
82
|
+
}
|
|
83
|
+
/** A url an entry writes that names this machine: a `file:` URL, or an absolute or relative path. */
|
|
84
|
+
const onHost = (url) => /^file:/i.test(url) || posix.isAbsolute(url) || win32.isAbsolute(url) || /^\.\.?(?:[\\/]|$)/.test(url);
|
|
85
|
+
/** A marketplace as the record writes it: `github:acme/agent-plugins#v3` or `npm:@acme/plugins@2.0.0`. */
|
|
86
|
+
function marketplaceLabel(m, root) {
|
|
87
|
+
return m.source === "git" ? `${npmLabel(m.repo, root)}${m.ref ? `#${m.ref}` : ""}` : `npm:${npmLabel(m.spec, root)}`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A plugin's source as the record writes it: `local:plugins/review`,
|
|
91
|
+
* `github:acme/agent-plugins#v1.2.0:plugins/review` or `npm:@acme/review-plugin@1.2.0`; before
|
|
92
|
+
* it's read, a marketplace entry is `lint@github:acme/agent-plugins#v3`.
|
|
93
|
+
*/
|
|
94
|
+
function pluginSourceLabel(p, root) {
|
|
95
|
+
switch (p.source) {
|
|
96
|
+
case "local": return `local:${relativeTo(root, p.path)}`;
|
|
97
|
+
case "git": return `${p.repo}${p.ref ? `#${p.ref}` : ""}${p.path ? `:${p.path}` : ""}`;
|
|
98
|
+
case "npm": return `npm:${npmLabel(p.spec, root)}${p.path ? `:${p.path}` : ""}`;
|
|
99
|
+
case "marketplace": return `${p.name}@${marketplaceLabel(p.marketplace, root)}`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Entry keys that give a plugin's parts from the marketplace, which reeval doesn't follow. */
|
|
103
|
+
const ENTRY_PARTS = [
|
|
104
|
+
"skills",
|
|
105
|
+
"agents",
|
|
106
|
+
"commands",
|
|
107
|
+
"hooks",
|
|
108
|
+
"mcpServers",
|
|
109
|
+
"lspServers",
|
|
110
|
+
"outputStyles"
|
|
111
|
+
];
|
|
112
|
+
const githubRepo = (s) => /^\w[\w.-]*\/[\w.-]+$/.test(s);
|
|
113
|
+
/**
|
|
114
|
+
* A git URL an entry may name. A marketplace is someone else's file and its URL and ref reach
|
|
115
|
+
* `git fetch` on the host, so a local path, a transport such as `ext::`, or anything that could
|
|
116
|
+
* read as an option is refused.
|
|
117
|
+
*/
|
|
118
|
+
const gitUrl = (s) => /^(?:https?|ssh|git|file):\/\/\S+$/i.test(s) || /^\w[\w.-]*@[\w.-]+:[^\s-]\S*$/.test(s);
|
|
119
|
+
const hasCredentials = (url) => {
|
|
120
|
+
if (!URL.canParse(url)) return false;
|
|
121
|
+
const u = new URL(url);
|
|
122
|
+
return u.password !== "" || u.username !== "" && /^https?:$/.test(u.protocol);
|
|
123
|
+
};
|
|
124
|
+
/** A registry package name: an entry's `package` may not be a path, URL or git spec. */
|
|
125
|
+
const NPM_NAME = /^(?:@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/i;
|
|
126
|
+
const NPM_VERSION = /^[\w.^~<>=|*+][\w.^~<>=|*+ -]*$/;
|
|
127
|
+
/** A source name Claude Code resolves under `metadata.pluginRoot`. */
|
|
128
|
+
const BARE_SOURCE = /^[A-Za-z0-9][-A-Za-z0-9._]*$/;
|
|
129
|
+
/** `metadata.pluginRoot` as Claude Code normalises it (`.` for the root), or undefined when it ignores it. */
|
|
130
|
+
function pluginRootOf(metadata) {
|
|
131
|
+
const root = isRecord(metadata) ? metadata.pluginRoot : void 0;
|
|
132
|
+
if (typeof root !== "string" || !root || root.startsWith("/") || /[\\:]/.test(root)) return;
|
|
133
|
+
const clean = root.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
134
|
+
if (clean === "" || clean === ".") return ".";
|
|
135
|
+
return clean.split("/").some((s) => s === "" || s === "." || s === "..") ? void 0 : clean;
|
|
136
|
+
}
|
|
137
|
+
/** A path in the marketplace's source, normalised, or undefined when it leaves it. */
|
|
138
|
+
function insideRoot(path) {
|
|
139
|
+
const clean = posix.normalize(path).replace(/\/+$/, "");
|
|
140
|
+
if (posix.isAbsolute(clean) || clean === ".." || clean.startsWith("../")) return void 0;
|
|
141
|
+
return clean === "." ? "" : clean;
|
|
142
|
+
}
|
|
143
|
+
/** Whether `path` in `base`, once its symlinks are followed, is outside `base`. */
|
|
144
|
+
function leavesSource(base, path) {
|
|
145
|
+
const dir = join(base, path);
|
|
146
|
+
if (!existsSync(dir)) return false;
|
|
147
|
+
const rel = relative(realpathSync(base), realpathSync(dir));
|
|
148
|
+
return isAbsolute(rel) || rel.split(sep)[0] === "..";
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Reads the marketplace at `dir` (its `.claude-plugin/marketplace.json`, in Claude Code's format)
|
|
152
|
+
* and finds the entry `name`: the marketplace's own name and where the entry's plugin is. Throws a
|
|
153
|
+
* `PluginProblem` for a source without one, an entry it doesn't list, or an entry reeval can't fetch.
|
|
154
|
+
* Only a `local` marketplace's entries may name a path on this machine.
|
|
155
|
+
*/
|
|
156
|
+
function readMarketplace(dir, call, label, name, local = false) {
|
|
157
|
+
const file = join(dir, MARKETPLACE);
|
|
158
|
+
if (!existsSync(file)) throw new PluginProblem(`${call}: ${label} has no ${MARKETPLACE}, so it isn't a Claude Code marketplace; name a plugin's own repository or package with plugin.git or plugin.npm`);
|
|
159
|
+
const raw = readJson(file, `${label}'s ${MARKETPLACE}`);
|
|
160
|
+
if (!isRecord(raw) || typeof raw.name !== "string" || !Array.isArray(raw.plugins)) throw new PluginProblem(`${call}: ${label}'s ${MARKETPLACE} needs a name and a plugins list, as Claude Code's marketplaces have`);
|
|
161
|
+
const market = raw.name;
|
|
162
|
+
const entries = raw.plugins.filter(isRecord);
|
|
163
|
+
const entry = entries.find((e) => e.name === name);
|
|
164
|
+
if (!entry) {
|
|
165
|
+
const names = entries.flatMap((e) => typeof e.name === "string" ? [e.name] : []);
|
|
166
|
+
const near = closest(name, names);
|
|
167
|
+
throw new PluginProblem(`${call}: the ${market} marketplace (${label}) has no plugin "${name}"${near ? ` (did you mean "${near}"?)` : ""}; it lists ${names.length ? names.join(", ") : "none"}`);
|
|
168
|
+
}
|
|
169
|
+
const what = `the ${market} marketplace's entry "${name}"`;
|
|
170
|
+
const custom = ENTRY_PARTS.filter((k) => entry[k] !== void 0);
|
|
171
|
+
if (custom.length) throw new PluginProblem(`${what} sets ${custom.map((k) => `"${k}"`).join(", ")}, which reeval doesn't read: keep each part in the plugin's own directory`);
|
|
172
|
+
const source = entry.source;
|
|
173
|
+
const shown = JSON.stringify(source, (_, v) => typeof v === "string" ? stripCredentials(v) : v);
|
|
174
|
+
const unfetchable = () => new PluginProblem(`${what} has source ${shown}, which reeval doesn't fetch: it takes a ./path in the marketplace, or a github, url, git-subdir or npm source`);
|
|
175
|
+
if (typeof source === "string") {
|
|
176
|
+
const root = pluginRootOf(raw.metadata);
|
|
177
|
+
const written = source === "." ? "./" : root !== void 0 && BARE_SOURCE.test(source) && !source.includes("..") ? `./${root}/${source}` : source;
|
|
178
|
+
const path = written.startsWith("./") ? insideRoot(written) : void 0;
|
|
179
|
+
if (path === void 0) throw unfetchable();
|
|
180
|
+
if (leavesSource(dir, path)) throw new PluginProblem(`${what} has source ${shown}, which leaves the marketplace through a symlink`);
|
|
181
|
+
return {
|
|
182
|
+
marketplace: market,
|
|
183
|
+
entry: {
|
|
184
|
+
source: "path",
|
|
185
|
+
path
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (!isRecord(source)) throw unfetchable();
|
|
190
|
+
const str = (k) => typeof source[k] === "string" && source[k] ? source[k] : void 0;
|
|
191
|
+
if (source.sha !== void 0 && !/^[0-9a-f]{40}$/.test(String(source.sha))) throw new PluginProblem(`${what} has sha ${JSON.stringify(source.sha)}, which isn't a full 40-character lowercase commit SHA, as Claude Code requires`);
|
|
192
|
+
if (source.ref !== void 0 && (typeof source.ref !== "string" || source.ref.startsWith("-"))) throw new PluginProblem(`${what} has ref ${JSON.stringify(source.ref)}, which isn't a branch or tag`);
|
|
193
|
+
const ref = str("sha") ?? str("ref");
|
|
194
|
+
const at = ref ? { ref } : {};
|
|
195
|
+
switch (source.source) {
|
|
196
|
+
case "github": {
|
|
197
|
+
const repo = str("repo");
|
|
198
|
+
if (!repo || !githubRepo(repo)) throw unfetchable();
|
|
199
|
+
return {
|
|
200
|
+
marketplace: market,
|
|
201
|
+
entry: {
|
|
202
|
+
source: "git",
|
|
203
|
+
url: `https://github.com/${repo}.git`,
|
|
204
|
+
repo: `github:${repo}`,
|
|
205
|
+
...at
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
case "url":
|
|
210
|
+
case "git-subdir": {
|
|
211
|
+
const written = str("url");
|
|
212
|
+
if (!written) throw unfetchable();
|
|
213
|
+
if (hasCredentials(written)) throw new PluginProblem(`${what} has a password or token in its url, which reeval would record: reeval fetches git sources with your own git credentials (a credential helper, gh auth setup-git or an SSH key), so the marketplace mustn't carry one`);
|
|
214
|
+
if (onHost(written) && !local) throw new PluginProblem(`the ${market} marketplace (${label}) names its ${name} plugin at ${stripCredentials(written)}, a path on this machine: a remote marketplace may only name remote repositories or paths inside itself`);
|
|
215
|
+
if (!githubRepo(written) && !gitUrl(written)) throw unfetchable();
|
|
216
|
+
const url = githubRepo(written) ? `https://github.com/${written}.git` : written;
|
|
217
|
+
const repo = githubRepo(written) ? `github:${written}` : written;
|
|
218
|
+
let path;
|
|
219
|
+
if (source.source === "git-subdir") {
|
|
220
|
+
path = insideRoot(str("path") ?? "");
|
|
221
|
+
if (!path) throw unfetchable();
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
marketplace: market,
|
|
225
|
+
entry: {
|
|
226
|
+
source: "git",
|
|
227
|
+
url,
|
|
228
|
+
repo,
|
|
229
|
+
...at,
|
|
230
|
+
...path ? { path } : {}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
case "npm": {
|
|
235
|
+
const pkg = str("package");
|
|
236
|
+
if (!pkg) throw unfetchable();
|
|
237
|
+
if (source.registry !== void 0) throw new PluginProblem(`${what} names its own npm registry, which reeval doesn't follow: set npm_config_registry on the host instead`);
|
|
238
|
+
if (!NPM_NAME.test(pkg)) throw new PluginProblem(`${what} names npm package ${JSON.stringify(stripCredentials(pkg))}, which isn't a registry package name: reeval packs only registry packages from a marketplace`);
|
|
239
|
+
const version = str("version");
|
|
240
|
+
if (version !== void 0 && !NPM_VERSION.test(version)) throw new PluginProblem(`${what} has npm version ${JSON.stringify(version)}, which isn't a version, range or tag such as "^2.0.0"`);
|
|
241
|
+
return {
|
|
242
|
+
marketplace: market,
|
|
243
|
+
entry: {
|
|
244
|
+
source: "npm",
|
|
245
|
+
spec: version ? `${pkg}@${version}` : pkg
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
default: throw unfetchable();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const readJson = (path, what) => {
|
|
253
|
+
try {
|
|
254
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
255
|
+
} catch (error) {
|
|
256
|
+
throw new PluginProblem(`${what} isn't valid JSON (${error.message.split("\n")[0]})`);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
260
|
+
const isDir = (path) => statSync(path, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
261
|
+
const filesIn = (dir, test) => isDir(dir) ? readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && test(e.name)).map((e) => e.name).sort() : [];
|
|
262
|
+
/** A skill's name from its `SKILL.md` frontmatter, else its directory's; `loadSkills` checks it. */
|
|
263
|
+
function skillName(dir) {
|
|
264
|
+
const text = readFileSync(join(dir, "SKILL.md"), "utf8");
|
|
265
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
|
|
266
|
+
try {
|
|
267
|
+
const front = match ? parse(match[1] ?? "") : void 0;
|
|
268
|
+
return (isRecord(front) && typeof front.name === "string" ? front.name.trim() : "") || basename(dir);
|
|
269
|
+
} catch {
|
|
270
|
+
return basename(dir);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const VAR_REF = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
274
|
+
/** `.mcp.json`'s servers as reeval's: `${VAR}` in a header is a secret template, a whole env value a secret. */
|
|
275
|
+
function mcpServers(raw, plugin, file) {
|
|
276
|
+
const map = isRecord(raw) && isRecord(raw.mcpServers) ? raw.mcpServers : raw;
|
|
277
|
+
if (!isRecord(map)) throw new PluginProblem(`the ${plugin} plugin's ${file} must map server names to servers`);
|
|
278
|
+
const out = {};
|
|
279
|
+
for (const [name, s] of Object.entries(map)) {
|
|
280
|
+
const what = `the ${plugin} plugin's MCP server "${name}"`;
|
|
281
|
+
if (!isRecord(s)) throw new PluginProblem(`${what} in ${file} must be an object`);
|
|
282
|
+
const type = s.type ?? (s.url !== void 0 ? "http" : "stdio");
|
|
283
|
+
const strings = (v, key) => {
|
|
284
|
+
if (v === void 0) return {};
|
|
285
|
+
if (!isRecord(v) || Object.values(v).some((x) => typeof x !== "string")) throw new PluginProblem(`${what}: ${key} in ${file} must map names to strings`);
|
|
286
|
+
return v;
|
|
287
|
+
};
|
|
288
|
+
if (type === "stdio") {
|
|
289
|
+
const { command, args = [] } = s;
|
|
290
|
+
if (typeof command !== "string" || !command) throw new PluginProblem(`${what} in ${file} needs a command, or a type and url`);
|
|
291
|
+
if (!Array.isArray(args) || args.some((a) => typeof a !== "string")) throw new PluginProblem(`${what}: args in ${file} must be a list of strings`);
|
|
292
|
+
const variable = [command, ...args].find((v) => v.replaceAll(PLUGIN_ROOT, "").includes("${"));
|
|
293
|
+
if (variable !== void 0) throw new PluginProblem(`${what}: ${JSON.stringify(variable)} in ${file} names a variable; reeval fills in only \${${PLUGIN_ROOT_VAR}} in a command and its args, so pass a host variable through env, e.g. "env": { "TOKEN": "\${TOKEN}" }`);
|
|
294
|
+
const env = {};
|
|
295
|
+
for (const [k, v] of Object.entries(strings(s.env, "env"))) {
|
|
296
|
+
const whole = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(v);
|
|
297
|
+
if (whole && whole[1] !== "CLAUDE_PLUGIN_ROOT") env[k] = {
|
|
298
|
+
kind: "secret",
|
|
299
|
+
from: whole[1]
|
|
300
|
+
};
|
|
301
|
+
else if (v.replaceAll("${CLAUDE_PLUGIN_ROOT}", "").includes("${")) throw new PluginProblem(`${what}: env.${k} in ${file} puts a variable inside a longer value; reeval reads a host variable only as the whole value, e.g. "\${${k}}"`);
|
|
302
|
+
else env[k] = v;
|
|
303
|
+
}
|
|
304
|
+
out[name] = {
|
|
305
|
+
kind: "mcp",
|
|
306
|
+
transport: "stdio",
|
|
307
|
+
command,
|
|
308
|
+
args,
|
|
309
|
+
env
|
|
310
|
+
};
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (type !== "http" && type !== "sse") throw new PluginProblem(`${what} in ${file} has type ${JSON.stringify(type)}; reeval takes stdio, http and sse`);
|
|
314
|
+
if (typeof s.url !== "string" || !URL.canParse(s.url) || s.url.includes("${")) throw new PluginProblem(`${what} in ${file} needs a url, written out in full`);
|
|
315
|
+
const headers = {};
|
|
316
|
+
for (const [k, v] of Object.entries(strings(s.headers, "headers"))) {
|
|
317
|
+
headers[k] = VAR_REF.test(v) ? {
|
|
318
|
+
kind: "secret-template",
|
|
319
|
+
template: v
|
|
320
|
+
} : v;
|
|
321
|
+
VAR_REF.lastIndex = 0;
|
|
322
|
+
}
|
|
323
|
+
out[name] = {
|
|
324
|
+
kind: "mcp",
|
|
325
|
+
transport: type,
|
|
326
|
+
url: s.url,
|
|
327
|
+
headers
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
return out;
|
|
331
|
+
}
|
|
332
|
+
/** The events of a `hooks/hooks.json`, the commands it runs, and its other hook types. */
|
|
333
|
+
function hookCommands(raw, plugin) {
|
|
334
|
+
const hooks = isRecord(raw) && isRecord(raw.hooks) ? raw.hooks : void 0;
|
|
335
|
+
if (!hooks) throw new PluginProblem(`the ${plugin} plugin's hooks/hooks.json needs a hooks object, e.g. { "hooks": { "SessionStart": [...] } }`);
|
|
336
|
+
const commands = [];
|
|
337
|
+
const otherTypes = /* @__PURE__ */ new Set();
|
|
338
|
+
for (const entries of Object.values(hooks)) for (const entry of Array.isArray(entries) ? entries : []) for (const hook of isRecord(entry) && Array.isArray(entry.hooks) ? entry.hooks : []) {
|
|
339
|
+
if (isRecord(hook) && typeof hook.command === "string") commands.push(hook.command);
|
|
340
|
+
const type = isRecord(hook) && typeof hook.type === "string" ? hook.type : "command";
|
|
341
|
+
if (type !== "command") otherTypes.add(type);
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
events: Object.keys(hooks).sort(),
|
|
345
|
+
commands,
|
|
346
|
+
otherTypes: [...otherTypes].sort()
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Reads the plugin at `dir`: its manifest, the parts it has and what `only` keeps. Throws a
|
|
351
|
+
* `PluginProblem` for a plugin reeval can't place as written.
|
|
352
|
+
*/
|
|
353
|
+
function readPlugin(dir, call, only) {
|
|
354
|
+
if (!isDir(dir)) throw new PluginProblem(`${call} isn't a directory`);
|
|
355
|
+
const manifestPath = join(dir, PLUGIN_MANIFEST);
|
|
356
|
+
if (!existsSync(manifestPath)) throw new PluginProblem(`${call} has no ${PLUGIN_MANIFEST}`);
|
|
357
|
+
const manifest = readJson(manifestPath, `${call}'s ${PLUGIN_MANIFEST}`);
|
|
358
|
+
if (!isRecord(manifest) || typeof manifest.name !== "string" || !PLUGIN_NAME.test(manifest.name)) throw new PluginProblem(`${call}'s ${PLUGIN_MANIFEST} needs a name in kebab-case, e.g. "name": "review" (got ${JSON.stringify(isRecord(manifest) ? manifest.name : manifest)})`);
|
|
359
|
+
const name = manifest.name;
|
|
360
|
+
const custom = CUSTOM_PATHS.filter((k) => manifest[k] !== void 0);
|
|
361
|
+
if (custom.length) throw new PluginProblem(`the ${name} plugin's manifest sets ${custom.map((k) => `"${k}"`).join(", ")}, which reeval doesn't read: keep each part in its default place (skills/, agents/, commands/, hooks/hooks.json)`);
|
|
362
|
+
const skills = isDir(join(dir, "skills")) ? readdirSync(join(dir, "skills"), { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync(join(dir, "skills", e.name, "SKILL.md"))).map((e) => ({
|
|
363
|
+
dir: e.name,
|
|
364
|
+
name: skillName(join(dir, "skills", e.name))
|
|
365
|
+
})).sort((a, b) => a.dir < b.dir ? -1 : 1) : [];
|
|
366
|
+
const md = (n) => n.endsWith(".md");
|
|
367
|
+
const agents = filesIn(join(dir, "agents"), md).map((n) => n.slice(0, -3));
|
|
368
|
+
const commands = filesIn(join(dir, "commands"), md).map((n) => n.slice(0, -3));
|
|
369
|
+
const hooksFile = join(dir, "hooks", "hooks.json");
|
|
370
|
+
const hooks = existsSync(hooksFile) ? hookCommands(readJson(hooksFile, `the ${name} plugin's hooks/hooks.json`), name) : {
|
|
371
|
+
events: [],
|
|
372
|
+
commands: [],
|
|
373
|
+
otherTypes: []
|
|
374
|
+
};
|
|
375
|
+
let mcp = {};
|
|
376
|
+
if (manifest.mcpServers !== void 0) {
|
|
377
|
+
const from = manifest.mcpServers;
|
|
378
|
+
mcp = typeof from === "string" ? mcpServers(readJson(join(dir, from), `the ${name} plugin's ${from}`), name, from) : mcpServers(from, name, PLUGIN_MANIFEST);
|
|
379
|
+
} else if (existsSync(join(dir, ".mcp.json"))) mcp = mcpServers(readJson(join(dir, ".mcp.json"), `the ${name} plugin's .mcp.json`), name, ".mcp.json");
|
|
380
|
+
const rules = filesIn(join(dir, "rules"), (n) => n.endsWith(".md") || n.endsWith(".mdc"));
|
|
381
|
+
const found = {
|
|
382
|
+
skills: skills.length > 0,
|
|
383
|
+
agents: agents.length > 0,
|
|
384
|
+
commands: commands.length > 0,
|
|
385
|
+
hooks: hooks.events.length > 0,
|
|
386
|
+
mcp: Object.keys(mcp).length > 0,
|
|
387
|
+
rules: rules.length > 0
|
|
388
|
+
};
|
|
389
|
+
const has = PLUGIN_PARTS.filter((p) => found[p]);
|
|
390
|
+
if (!has.length) throw new PluginProblem(`the ${name} plugin has nothing to place: no skills/*/SKILL.md, agents/*.md, commands/*.md, hooks/hooks.json, .mcp.json or rules/`);
|
|
391
|
+
const missing = (only ?? []).filter((p) => !found[p]);
|
|
392
|
+
if (missing.length) {
|
|
393
|
+
const list = (only ?? []).map((p) => `'${p}'`).join(", ");
|
|
394
|
+
throw new PluginProblem(missing.length === only?.length ? `the ${name} plugin has no ${missing.join(" or ")}, so only: [${list}] matches nothing (it has ${has.join(", ")})` : `the ${name} plugin has no ${missing.join(" or ")}, which only: [${list}] names (it has ${has.join(", ")})`);
|
|
395
|
+
}
|
|
396
|
+
const kept = only ? PLUGIN_PARTS.filter((p) => only.includes(p)) : has;
|
|
397
|
+
const { mcpServers: _, ...rest } = manifest;
|
|
398
|
+
return {
|
|
399
|
+
name,
|
|
400
|
+
dir,
|
|
401
|
+
call,
|
|
402
|
+
has,
|
|
403
|
+
kept,
|
|
404
|
+
skills: kept.includes("skills") ? skills : [],
|
|
405
|
+
agents: kept.includes("agents") ? agents : [],
|
|
406
|
+
commands: kept.includes("commands") ? commands : [],
|
|
407
|
+
hooks: kept.includes("hooks") ? hooks : {
|
|
408
|
+
events: [],
|
|
409
|
+
commands: [],
|
|
410
|
+
otherTypes: []
|
|
411
|
+
},
|
|
412
|
+
mcp: kept.includes("mcp") ? mcp : {},
|
|
413
|
+
rules: kept.includes("rules") ? rules : [],
|
|
414
|
+
apps: manifest.apps !== void 0 || existsSync(join(dir, ".app.json")),
|
|
415
|
+
manifests: OTHER_MANIFESTS.filter((m) => existsSync(join(dir, m))),
|
|
416
|
+
rootSchema: agentPluginsSchema(dir),
|
|
417
|
+
manifest: kept.includes("mcp") || manifest.mcpServers === void 0 ? readFileSync(manifestPath, "utf8") : `${JSON.stringify(rest, null, 2)}\n`
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
/** The paths a part keeps out of the sandbox when `only` leaves it out. */
|
|
421
|
+
const PART_FILES = {
|
|
422
|
+
skills: (p) => p.startsWith("skills/"),
|
|
423
|
+
agents: (p) => p.startsWith("agents/"),
|
|
424
|
+
commands: (p) => p.startsWith("commands/"),
|
|
425
|
+
hooks: (p) => p.startsWith("hooks/"),
|
|
426
|
+
mcp: (p) => p === ".mcp.json",
|
|
427
|
+
rules: (p) => p.startsWith("rules/")
|
|
428
|
+
};
|
|
429
|
+
/**
|
|
430
|
+
* The files to place, by path relative to the plugin's root: its tree without `.git`, without the
|
|
431
|
+
* parts `only` leaves out, and with the manifest as `readPlugin` gives it.
|
|
432
|
+
*/
|
|
433
|
+
function pluginFiles(p) {
|
|
434
|
+
const dropped = PLUGIN_PARTS.filter((part) => !p.kept.includes(part)).map((x) => PART_FILES[x]);
|
|
435
|
+
const out = [];
|
|
436
|
+
const walk = (rel) => {
|
|
437
|
+
for (const entry of readdirSync(join(p.dir, rel), { withFileTypes: true })) {
|
|
438
|
+
if (entry.name === ".git") continue;
|
|
439
|
+
const path = rel ? `${rel}/${entry.name}` : entry.name;
|
|
440
|
+
if (entry.isDirectory()) walk(path);
|
|
441
|
+
else if (entry.isFile() && path !== ".claude-plugin/plugin.json" && !dropped.some((d) => d(path))) {
|
|
442
|
+
const full = join(p.dir, path);
|
|
443
|
+
const executable = (statSync(full).mode & 64) !== 0;
|
|
444
|
+
out.push({
|
|
445
|
+
path,
|
|
446
|
+
content: readFileSync(full),
|
|
447
|
+
...executable ? { executable: true } : {}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
walk("");
|
|
453
|
+
out.push({
|
|
454
|
+
path: PLUGIN_MANIFEST,
|
|
455
|
+
content: Buffer.from(p.manifest)
|
|
456
|
+
});
|
|
457
|
+
return out.sort((a, b) => a.path < b.path ? -1 : 1);
|
|
458
|
+
}
|
|
459
|
+
/** The record's `parts`: names of what the plugin placed. */
|
|
460
|
+
const pluginParts = (p, skillNames) => ({
|
|
461
|
+
skills: skillNames,
|
|
462
|
+
agents: p.agents,
|
|
463
|
+
commands: p.commands,
|
|
464
|
+
hooks: p.hooks.events,
|
|
465
|
+
mcp: Object.keys(p.mcp).sort(),
|
|
466
|
+
rules: p.rules.length > 0
|
|
467
|
+
});
|
|
468
|
+
/** A plugin's MCP server as the environment's `mcp` names it. */
|
|
469
|
+
const pluginServerName = (plugin, server) => `${plugin}:${server}`;
|
|
470
|
+
const read = /* @__PURE__ */ new Map();
|
|
471
|
+
/**
|
|
472
|
+
* A local plugin as the plan reads it, once per process: its contents, or the problem with it.
|
|
473
|
+
* Git and npm plugins aren't fetched until an attempt, so the plan has nothing for them.
|
|
474
|
+
*/
|
|
475
|
+
function localPlugin(p, root) {
|
|
476
|
+
if (p.source !== "local") return void 0;
|
|
477
|
+
const key = JSON.stringify([
|
|
478
|
+
p.path,
|
|
479
|
+
p.only,
|
|
480
|
+
root
|
|
481
|
+
]);
|
|
482
|
+
let found = read.get(key);
|
|
483
|
+
if (!found) {
|
|
484
|
+
try {
|
|
485
|
+
found = readPlugin(p.path, pluginCall(p, root), p.only);
|
|
486
|
+
} catch (error) {
|
|
487
|
+
if (!(error instanceof PluginProblem)) throw error;
|
|
488
|
+
found = error;
|
|
489
|
+
}
|
|
490
|
+
read.set(key, found);
|
|
491
|
+
}
|
|
492
|
+
return found;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* The MCP servers the layers' local plugins bring, as `<plugin>:<server>`: the plan's egress and
|
|
496
|
+
* secrets see them as they see `mcp`. A later layer's plugin replaces an earlier one of its name.
|
|
497
|
+
*/
|
|
498
|
+
function localPluginServers(layers) {
|
|
499
|
+
const plugins = /* @__PURE__ */ new Map();
|
|
500
|
+
for (const { spec } of layers) for (const source of spec.plugins ?? []) {
|
|
501
|
+
const p = localPlugin(source);
|
|
502
|
+
if (p && !(p instanceof PluginProblem)) plugins.set(p.name, p);
|
|
503
|
+
}
|
|
504
|
+
const out = /* @__PURE__ */ new Map();
|
|
505
|
+
for (const p of plugins.values()) for (const [name, server] of Object.entries(p.mcp)) out.set(pluginServerName(p.name, name), server);
|
|
506
|
+
return out;
|
|
507
|
+
}
|
|
508
|
+
/** Why one environment can't list two plugins of one name: neither would replace the other. */
|
|
509
|
+
const samePluginTwice = (environment, name, first, second) => `environment ${environment} lists two plugins named "${name}", ${first} and ${second}: rename one, or leave one out`;
|
|
510
|
+
/** The first local plugin a layer lists twice by name, as `samePluginTwice` words it. */
|
|
511
|
+
function duplicateLocalPlugin(layers, root) {
|
|
512
|
+
for (const { name: environment, spec } of layers) {
|
|
513
|
+
const calls = /* @__PURE__ */ new Map();
|
|
514
|
+
for (const source of spec.plugins ?? []) {
|
|
515
|
+
const p = localPlugin(source, root);
|
|
516
|
+
if (!p || p instanceof PluginProblem) continue;
|
|
517
|
+
const first = calls.get(p.name);
|
|
518
|
+
if (first) return samePluginTwice(environment, p.name, first, p.call);
|
|
519
|
+
calls.set(p.name, p.call);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/** A plugin's build as commands and where they run: `cwd` relative to the source's root, or null for the plugin's directory. */
|
|
524
|
+
function buildSteps(build) {
|
|
525
|
+
if (typeof build === "string") return {
|
|
526
|
+
run: [build],
|
|
527
|
+
cwd: null
|
|
528
|
+
};
|
|
529
|
+
if (Array.isArray(build)) return {
|
|
530
|
+
run: [...build],
|
|
531
|
+
cwd: null
|
|
532
|
+
};
|
|
533
|
+
const { run, cwd } = build;
|
|
534
|
+
return {
|
|
535
|
+
run: typeof run === "string" ? [run] : [...run],
|
|
536
|
+
cwd: cwd ?? null
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
/** The layered plugins with a build: what preflight builds into the attempt's image, in order. */
|
|
540
|
+
const builtPlugins = (layers) => layeredPlugins(layers).filter((p) => p.build !== void 0);
|
|
541
|
+
/**
|
|
542
|
+
* Why a plugin's build can't run in `sandbox`, or undefined: a build runs in a Docker image
|
|
543
|
+
* layer. `name` is the plugin's, once its manifest is read; else the call names it.
|
|
544
|
+
*/
|
|
545
|
+
function buildRefusal(p, sandbox, name, root) {
|
|
546
|
+
if (p.build === void 0 || sandbox.kind === "docker") return void 0;
|
|
547
|
+
return `${name ? `plugin "${name}"` : pluginCall(p, root)} has a build, which runs in a Docker image layer: use docker()`;
|
|
548
|
+
}
|
|
549
|
+
/** ` + 2 plugin builds` in an image's label, or nothing without one. */
|
|
550
|
+
const pluginBuildsLabel = (plugins = []) => plugins.length ? ` + ${plugins.length} plugin build${plugins.length === 1 ? "" : "s"}` : "";
|
|
551
|
+
/** Names one plugin build the same way in preflight and in the attempt that places it. */
|
|
552
|
+
const pluginBuildId = (p) => sha256(JSON.stringify(p)).slice(0, 16);
|
|
553
|
+
/** The paths in a plugin's directory that hold the parts `kept` leaves out. */
|
|
554
|
+
const droppedPaths = (kept) => PLUGIN_PARTS.filter((p) => !kept.includes(p)).map((p) => p === "mcp" ? ".mcp.json" : p === "hooks" ? "hooks" : p);
|
|
555
|
+
/** The plugins the layers place, layered: a later layer's local plugin replaces one of its name. */
|
|
556
|
+
function layeredPlugins(layers) {
|
|
557
|
+
const out = [];
|
|
558
|
+
for (const { spec } of layers) for (const source of spec.plugins ?? []) {
|
|
559
|
+
const p = localPlugin(source);
|
|
560
|
+
const name = p && !(p instanceof PluginProblem) ? p.name : void 0;
|
|
561
|
+
const same = out.findIndex((o) => {
|
|
562
|
+
const q = localPlugin(o);
|
|
563
|
+
return name !== void 0 && q && !(q instanceof PluginProblem) && q.name === name;
|
|
564
|
+
});
|
|
565
|
+
if (same >= 0) out.splice(same, 1);
|
|
566
|
+
out.push(source);
|
|
567
|
+
}
|
|
568
|
+
return out;
|
|
569
|
+
}
|
|
570
|
+
/** `skill.local('skills')`, as plugin messages name the skill source a clash is with. */
|
|
571
|
+
function skillCall(s, root) {
|
|
572
|
+
switch (s.source) {
|
|
573
|
+
case "local": return `skill.local('${root ? relative(root, s.path).split(sep).join("/") || "." : s.path}')`;
|
|
574
|
+
case "git": return `skill.git('${s.repo}')`;
|
|
575
|
+
case "registry": return `skill.registry('${s.id}')`;
|
|
576
|
+
case "npm": return `skill.npm('${s.spec}')`;
|
|
577
|
+
case "inline": return `skill.inline('${s.name}')`;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* The names of the harness-placed skills the plan can read without fetching (local and inline
|
|
582
|
+
* ones), layered, with the call that places each; `loadSkills` reads them properly at the attempt.
|
|
583
|
+
*/
|
|
584
|
+
function localSkills(layers, root) {
|
|
585
|
+
const out = /* @__PURE__ */ new Map();
|
|
586
|
+
for (const { spec } of layers) for (const s of spec.skills ?? []) {
|
|
587
|
+
if (s.placement !== "harness") continue;
|
|
588
|
+
const call = skillCall(s, root);
|
|
589
|
+
if (s.source === "inline") out.set(s.name, call);
|
|
590
|
+
if (s.source !== "local" || !isDir(s.path)) continue;
|
|
591
|
+
const dirs = existsSync(join(s.path, "SKILL.md")) ? [s.path] : readdirSync(s.path, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync(join(s.path, e.name, "SKILL.md"))).map((e) => join(s.path, e.name));
|
|
592
|
+
for (const d of dirs) {
|
|
593
|
+
const name = skillName(d);
|
|
594
|
+
if (!s.only || s.only.includes(name)) out.set(name, call);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return [...out].map(([name, call]) => ({
|
|
598
|
+
name,
|
|
599
|
+
call
|
|
600
|
+
}));
|
|
601
|
+
}
|
|
602
|
+
/** The package registry a command fetches from at start, or undefined. */
|
|
603
|
+
function fetcher(command, args) {
|
|
604
|
+
const tool = basename(command);
|
|
605
|
+
if (tool === "npx" || tool === "bunx") return {
|
|
606
|
+
tool,
|
|
607
|
+
host: "registry.npmjs.org"
|
|
608
|
+
};
|
|
609
|
+
if (tool === "uvx") return {
|
|
610
|
+
tool,
|
|
611
|
+
host: "pypi.org"
|
|
612
|
+
};
|
|
613
|
+
if (tool === "pipx" && args[0] === "run") return {
|
|
614
|
+
tool: "pipx run",
|
|
615
|
+
host: "pypi.org"
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* The plugins' MCP servers and hooks that fetch a package at start (`npx`, `bunx`, `uvx`,
|
|
620
|
+
* `pipx run`): a refusal under a policy that doesn't allow the registry, else notes, since
|
|
621
|
+
* `allowed` is null under `allow-all`.
|
|
622
|
+
*/
|
|
623
|
+
function fetchesAtStart(plugins, allowed) {
|
|
624
|
+
const notes = [];
|
|
625
|
+
for (const p of plugins) {
|
|
626
|
+
const runs = [...Object.entries(p.mcp).flatMap(([name, s]) => s.transport === "stdio" ? [{
|
|
627
|
+
what: `the ${p.name} plugin's MCP server "${name}"`,
|
|
628
|
+
command: s.command,
|
|
629
|
+
args: s.args
|
|
630
|
+
}] : []), ...p.hooks.commands.map((c) => {
|
|
631
|
+
const [command = "", ...args] = c.trim().split(/\s+/);
|
|
632
|
+
return {
|
|
633
|
+
what: `the ${p.name} plugin's hook "${c}"`,
|
|
634
|
+
command,
|
|
635
|
+
args
|
|
636
|
+
};
|
|
637
|
+
})];
|
|
638
|
+
for (const { what, command, args } of runs) {
|
|
639
|
+
const f = fetcher(command, args);
|
|
640
|
+
if (!f) continue;
|
|
641
|
+
const runsIt = `${what} runs ${f.tool}, which fetches from ${f.host} at start`;
|
|
642
|
+
if (allowed === null) notes.push(`${runsIt}; add a build that installs it to keep it off the network`);
|
|
643
|
+
else if (!hostAllowed(allowed, f.host)) return {
|
|
644
|
+
refusal: `${runsIt}: add a build that installs it, or allow the host in egress`,
|
|
645
|
+
notes
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return { notes };
|
|
650
|
+
}
|
|
651
|
+
//#endregion
|
|
652
|
+
export { readPlugin as A, pluginBuildsLabel as C, pluginServerName as D, pluginParts as E, skillCall as M, pluginSourceLabel as O, pluginBuildId as S, pluginFiles as T, localMarketplace as _, PLUGIN_PARTS as a, localSkills as b, PluginProblem as c, builtPlugins as d, droppedPaths as f, leavesSource as g, layeredPlugins as h, PLUGIN_NAME as i, samePluginTwice as j, readMarketplace as k, buildRefusal as l, fetchesAtStart as m, PLUGIN_DIR as n, PLUGIN_ROOT as o, duplicateLocalPlugin as p, PLUGIN_MANIFEST as r, PLUGIN_ROOT_VAR as s, PART_PATHS as t, buildSteps as u, localPlugin as v, pluginCall as w, marketplaceLabel as x, localPluginServers as y };
|
|
653
|
+
|
|
654
|
+
//# sourceMappingURL=plugins-CUWMnSNb.js.map
|