zidane 9.1.1 → 9.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-Di9WvMDF.js → acp-Cl4gCZN6.js} +7 -7
- package/dist/{acp-Di9WvMDF.js.map → acp-Cl4gCZN6.js.map} +1 -1
- package/dist/acp-cli.js +5 -5
- package/dist/acp.d.ts +2 -2
- package/dist/acp.js +1 -1
- package/dist/{agent-DJhpTS-B.js → agent-Dh_XZ7fj.js} +74 -19
- package/dist/agent-Dh_XZ7fj.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/{auth-store-port-DFgf22oo.js → auth-store-port-DBl2JUC6.js} +2 -2
- package/dist/{auth-store-port-DFgf22oo.js.map → auth-store-port-DBl2JUC6.js.map} +1 -1
- package/dist/{auto-update-restart-BFB7Ek87.js → auto-update-restart-CF_kjcJd.js} +277 -41
- package/dist/auto-update-restart-CF_kjcJd.js.map +1 -0
- package/dist/{browser-BcoCCB_n.js → browser-BkdeKnxO.js} +56 -2
- package/dist/browser-BkdeKnxO.js.map +1 -0
- package/dist/{browser-entry-ypi8CyE-.d.ts → browser-entry-BCwruqNa.d.ts} +10 -10
- package/dist/{browser-entry-ypi8CyE-.d.ts.map → browser-entry-BCwruqNa.d.ts.map} +1 -1
- package/dist/channels.d.ts +5 -5
- package/dist/chat/browser.d.ts +4 -4
- package/dist/chat/browser.js +6 -6
- package/dist/chat/pure.d.ts +5 -5
- package/dist/chat/pure.js +8 -8
- package/dist/chat.d.ts +5 -5
- package/dist/chat.js +55 -55
- package/dist/chat.js.map +1 -1
- package/dist/{cli-shared-CabruH8Z.js → cli-shared-DIV9c5Nr.js} +4 -4
- package/dist/{cli-shared-CabruH8Z.js.map → cli-shared-DIV9c5Nr.js.map} +1 -1
- package/dist/cli-style-DBEm6CyT.js +105 -0
- package/dist/cli-style-DBEm6CyT.js.map +1 -0
- package/dist/{client-core-CIixwznM.d.ts → client-core-C_4m_oL6.d.ts} +4 -4
- package/dist/{client-core-CIixwznM.d.ts.map → client-core-C_4m_oL6.d.ts.map} +1 -1
- package/dist/{client-CCaIXWue.d.ts → client-ygTASGis.d.ts} +2 -2
- package/dist/{client-CCaIXWue.d.ts.map → client-ygTASGis.d.ts.map} +1 -1
- package/dist/codemode-worker.js +8 -5
- package/dist/codemode-worker.js.map +1 -1
- package/dist/codemode.d.ts +16 -0
- package/dist/codemode.d.ts.map +1 -1
- package/dist/codemode.js +2 -2
- package/dist/{connection-loop-QmWL-fJf.d.ts → connection-loop-D_pRONCr.d.ts} +4 -4
- package/dist/{connection-loop-QmWL-fJf.d.ts.map → connection-loop-D_pRONCr.d.ts.map} +1 -1
- package/dist/contexts/daytona.d.ts +3 -3
- package/dist/contexts/docker.d.ts +1 -1
- package/dist/contexts/e2b.d.ts +2 -2
- package/dist/contexts/sandbox.d.ts +1 -1
- package/dist/{contexts-C72b0mMa.js → contexts-DLZTSRHU.js} +17 -106
- package/dist/contexts-DLZTSRHU.js.map +1 -0
- package/dist/contexts.d.ts +4 -4
- package/dist/contexts.js +2 -2
- package/dist/{credentials-wwETW8rI.js → credentials-D71duF64.js} +2 -2
- package/dist/{credentials-wwETW8rI.js.map → credentials-D71duF64.js.map} +1 -1
- package/dist/{cwd-picker-CcekODCt.js → cwd-picker-BsHA6HKv.js} +4 -4
- package/dist/{cwd-picker-CcekODCt.js.map → cwd-picker-BsHA6HKv.js.map} +1 -1
- package/dist/daemon-cli.js +4 -3
- package/dist/daemon-cli.js.map +1 -1
- package/dist/{decisions-CXmK0nz9.js → decisions-hsw5XS2g.js} +3 -1
- package/dist/decisions-hsw5XS2g.js.map +1 -0
- package/dist/{discovery-DycXFxdA.js → discovery-DD4wPWKS.js} +4 -4
- package/dist/{discovery-DycXFxdA.js.map → discovery-DD4wPWKS.js.map} +1 -1
- package/dist/durable/sqlite.d.ts +2 -2
- package/dist/durable.d.ts +1 -1
- package/dist/{engine-VQ86XGdG.js → engine-BDmtgr2O.js} +6 -6
- package/dist/{engine-VQ86XGdG.js.map → engine-BDmtgr2O.js.map} +1 -1
- package/dist/{eval-Bod0I1W_.js → eval-CfGUDAkN.js} +3 -3
- package/dist/{eval-Bod0I1W_.js.map → eval-CfGUDAkN.js.map} +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +1 -1
- package/dist/{executor-C7RQrkEq.js → executor-EWOhLyd9.js} +4 -4
- package/dist/{executor-C7RQrkEq.js.map → executor-EWOhLyd9.js.map} +1 -1
- package/dist/{extension-mark-BqKdEZwF.js → extension-mark-_rhkDWL5.js} +9 -6
- package/dist/extension-mark-_rhkDWL5.js.map +1 -0
- package/dist/extensions/testing.d.ts +1 -1
- package/dist/extensions/testing.js +2 -2
- package/dist/{extensions-BCx4QuGB.js → extensions-DklXPjiq.js} +8 -8
- package/dist/{extensions-BCx4QuGB.js.map → extensions-DklXPjiq.js.map} +1 -1
- package/dist/{extensions-client-D8tEAcMw.d.ts → extensions-client-D4Q2XRKV.d.ts} +31 -5
- package/dist/extensions-client-D4Q2XRKV.d.ts.map +1 -0
- package/dist/extensions.d.ts +1 -1
- package/dist/extensions.js +6 -6
- package/dist/{fold-rules-DvLK8pbf.d.ts → fold-rules-1DpDwG9h.d.ts} +2 -2
- package/dist/{fold-rules-DvLK8pbf.d.ts.map → fold-rules-1DpDwG9h.d.ts.map} +1 -1
- package/dist/{google-Crva32sk.js → google-BtMW9KzL.js} +2 -2
- package/dist/{google-Crva32sk.js.map → google-BtMW9KzL.js.map} +1 -1
- package/dist/{headless-CCm-jLLT.js → headless-CQm19BL0.js} +4 -4
- package/dist/{headless-CCm-jLLT.js.map → headless-CQm19BL0.js.map} +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -1
- package/dist/history-worker.js +1 -1
- package/dist/{host-BHMs9jsN.js → host-DEoEzQal.js} +13 -4
- package/dist/host-DEoEzQal.js.map +1 -0
- package/dist/{host-scoped-settings-CrbcqOkP.js → host-scoped-settings-WBYaCork.js} +2 -2
- package/dist/{host-scoped-settings-CrbcqOkP.js.map → host-scoped-settings-WBYaCork.js.map} +1 -1
- package/dist/{host-signals-Jsjd_-4g.js → host-signals-91zyko_i.js} +5 -5
- package/dist/{host-signals-Jsjd_-4g.js.map → host-signals-91zyko_i.js.map} +1 -1
- package/dist/{host-wO_6GW0y.js → host-uP3t08w8.js} +276 -103
- package/dist/host-uP3t08w8.js.map +1 -0
- package/dist/{host-version-CNAr9pov.js → host-version-_RHlAB29.js} +2 -2
- package/dist/{host-version-CNAr9pov.js.map → host-version-_RHlAB29.js.map} +1 -1
- package/dist/{index-C6nnKu32.d.ts → index-B0PLo9jn.d.ts} +4 -4
- package/dist/{index-C6nnKu32.d.ts.map → index-B0PLo9jn.d.ts.map} +1 -1
- package/dist/{index-Djnc8Peh.d.ts → index-De4x-s76.d.ts} +14 -14
- package/dist/{index-Djnc8Peh.d.ts.map → index-De4x-s76.d.ts.map} +1 -1
- package/dist/{index-CGbOWa7y.d.ts → index-WlE93Q6d.d.ts} +411 -3
- package/dist/index-WlE93Q6d.d.ts.map +1 -0
- package/dist/{index-CCf_WeTQ.d.ts → index-f-XWIoYo.d.ts} +2 -2
- package/dist/{index-CCf_WeTQ.d.ts.map → index-f-XWIoYo.d.ts.map} +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +17 -17
- package/dist/ipc/browser-types.d.ts +304 -7
- package/dist/ipc/browser-types.d.ts.map +1 -1
- package/dist/ipc/browser.d.ts +4 -4
- package/dist/ipc/browser.js +2 -2
- package/dist/ipc/remote.d.ts +6 -6
- package/dist/ipc/remote.js +3 -3
- package/dist/{ipc-BzWhIybr.js → ipc-CatnsVEd.js} +4 -4
- package/dist/{ipc-BzWhIybr.js.map → ipc-CatnsVEd.js.map} +1 -1
- package/dist/ipc.d.ts +10 -10
- package/dist/ipc.js +8 -8
- package/dist/{json-Dj-YATkZ.js → json-BjClr43H.js} +2 -20
- package/dist/json-BjClr43H.js.map +1 -0
- package/dist/{links-CJG_rFvf.js → links-a8C9eby4.js} +9 -9
- package/dist/{links-CJG_rFvf.js.map → links-a8C9eby4.js.map} +1 -1
- package/dist/{loaded-entry-DTURJo_R.js → loaded-entry-D6jzt5HQ.js} +2 -2
- package/dist/{loaded-entry-DTURJo_R.js.map → loaded-entry-D6jzt5HQ.js.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/{media-type-BtA0cbfI.d.ts → media-type-ZSmAGHvg.d.ts} +188 -7
- package/dist/media-type-ZSmAGHvg.d.ts.map +1 -0
- package/dist/{memory-DuVGrzbl.js → memory-CJy52DNi.js} +132 -31
- package/dist/memory-CJy52DNi.js.map +1 -0
- package/dist/model-test-CAtJ76kM.js.map +1 -1
- package/dist/{move-session-kylExJ5H.js → move-session-D7ILECs-.js} +4 -4
- package/dist/{move-session-kylExJ5H.js.map → move-session-D7ILECs-.js.map} +1 -1
- package/dist/{nested-call-record-DlzPeTB7.js → nested-call-record-OayszZBQ.js} +4 -3
- package/dist/nested-call-record-OayszZBQ.js.map +1 -0
- package/dist/output/stream-json.d.ts +1 -1
- package/dist/output/terminal.d.ts +1 -1
- package/dist/{path-display-COVV71Yu.js → path-display-CAWz-sMq.js} +3 -3
- package/dist/{path-display-COVV71Yu.js.map → path-display-CAWz-sMq.js.map} +1 -1
- package/dist/plugins.d.ts +1 -1
- package/dist/{prelude-CVKoUzV8.js → prelude-BwiSR1uA.js} +16 -5
- package/dist/prelude-BwiSR1uA.js.map +1 -0
- package/dist/{presets-CBt6rFfS.js → presets-CMY43nUP.js} +3 -3
- package/dist/{presets-CBt6rFfS.js.map → presets-CMY43nUP.js.map} +1 -1
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -1
- package/dist/{protocol-CEVqBO-q.d.ts → protocol-C2lqKuMd.d.ts} +50 -7
- package/dist/protocol-C2lqKuMd.d.ts.map +1 -0
- package/dist/protocol-DOGyHgLQ.js.map +1 -1
- package/dist/protocol-NSllElLw.js.map +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/google.d.ts +1 -1
- package/dist/providers/google.js +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/neuralwatt.d.ts +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/{providers-Z0TaBp1O.js → providers-zVRxiJDv.js} +5 -5
- package/dist/providers-zVRxiJDv.js.map +1 -0
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +1 -1
- package/dist/{readonly-db-D7Z-k0pQ.js → readonly-db-DrbT8tX6.js} +2 -2
- package/dist/{readonly-db-D7Z-k0pQ.js.map → readonly-db-DrbT8tX6.js.map} +1 -1
- package/dist/{registry-BKjeBnoy.js → registry-CBoYDIND.js} +2 -2
- package/dist/{registry-BKjeBnoy.js.map → registry-CBoYDIND.js.map} +1 -1
- package/dist/{registry-B6Ww76iW.js → registry-CQOU5jvF.js} +2 -2
- package/dist/{registry-B6Ww76iW.js.map → registry-CQOU5jvF.js.map} +1 -1
- package/dist/{registry-B9INJwJR.js → registry-YUUqbYGr.js} +324 -39
- package/dist/registry-YUUqbYGr.js.map +1 -0
- package/dist/{remote-hosts-BMWoHCWO.js → remote-hosts-CT8Q95dR.js} +4 -4
- package/dist/{remote-hosts-BMWoHCWO.js.map → remote-hosts-CT8Q95dR.js.map} +1 -1
- package/dist/{repo-url-D4Dr8NfZ.js → repo-url-DLjjuVk_.js} +169 -2
- package/dist/repo-url-DLjjuVk_.js.map +1 -0
- package/dist/restate.d.ts +2 -2
- package/dist/{sandbox-BwiPGKF7.d.ts → sandbox-B4OW6oNx.d.ts} +2 -2
- package/dist/{sandbox-BwiPGKF7.d.ts.map → sandbox-B4OW6oNx.d.ts.map} +1 -1
- package/dist/script-progress-CrZ507Qq.js +900 -0
- package/dist/script-progress-CrZ507Qq.js.map +1 -0
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/{setting-BQBSyUq0.js → setting-_jh2OtiL.js} +6 -6
- package/dist/{setting-BQBSyUq0.js.map → setting-_jh2OtiL.js.map} +1 -1
- package/dist/{settings-catalog-4IU2i_jG.js → settings-catalog-DEZlXP51.js} +14 -2
- package/dist/settings-catalog-DEZlXP51.js.map +1 -0
- package/dist/{settings-keys-iI_Y-7MC.js → settings-keys-BT0_oKjO.js} +3 -3
- package/dist/{settings-keys-iI_Y-7MC.js.map → settings-keys-BT0_oKjO.js.map} +1 -1
- package/dist/{skills-CFXRItJ6.js → skills-CEbosUlM.js} +2 -2
- package/dist/{skills-CFXRItJ6.js.map → skills-CEbosUlM.js.map} +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +1 -1
- package/dist/{source-Bl1Z501r.js → source-3ZnEPOSa.js} +3 -2
- package/dist/source-3ZnEPOSa.js.map +1 -0
- package/dist/{sqlite-runtime-BZ2L4RaG.d.ts → sqlite-runtime-CS_LBaAN.d.ts} +2 -2
- package/dist/sqlite-runtime-CS_LBaAN.d.ts.map +1 -0
- package/dist/{src-BY_ND2xn.js → src-BmTXDta8.js} +2 -2
- package/dist/{src-BY_ND2xn.js.map → src-BmTXDta8.js.map} +1 -1
- package/dist/{store-C6gI7FfZ.js → store-ByCDaKgJ.js} +14 -3
- package/dist/store-ByCDaKgJ.js.map +1 -0
- package/dist/{system-freeze-m5o1sGIM.js → system-freeze-DxJ-lRhu.js} +3 -3
- package/dist/{system-freeze-m5o1sGIM.js.map → system-freeze-DxJ-lRhu.js.map} +1 -1
- package/dist/{todo-edits-CbukbjyV.js → todo-edits-Dyj32ds6.js} +4 -3
- package/dist/todo-edits-Dyj32ds6.js.map +1 -0
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/{tools-CVTGhggj.js → tools-BsrGQ8K4.js} +2 -2
- package/dist/{tools-CVTGhggj.js.map → tools-BsrGQ8K4.js.map} +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +3 -3
- package/dist/{transcript-anchors-CiSi_UBn.js → transcript-anchors-DMjvyUjk.js} +3 -3
- package/dist/{transcript-anchors-CiSi_UBn.js.map → transcript-anchors-DMjvyUjk.js.map} +1 -1
- package/dist/{transcript-reconcile-jbSBjLq2.js → transcript-reconcile-DpQO85wl.js} +3 -3
- package/dist/{transcript-reconcile-jbSBjLq2.js.map → transcript-reconcile-DpQO85wl.js.map} +1 -1
- package/dist/tui.d.ts +9 -9
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +289 -38
- package/dist/tui.js.map +1 -1
- package/dist/{types-bDQagoiL.d.ts → types-2HrgolQz.d.ts} +20 -8
- package/dist/types-2HrgolQz.d.ts.map +1 -0
- package/dist/types-4q1bFG53.js +122 -0
- package/dist/types-4q1bFG53.js.map +1 -0
- package/dist/{types-DnRd9_cA.d.ts → types-SXqk6hLD.d.ts} +3 -3
- package/dist/{types-DnRd9_cA.d.ts.map → types-SXqk6hLD.d.ts.map} +1 -1
- package/dist/types-myxheQkw.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/{usage-summary-BfcXjiSM.js → usage-summary-BM7B3JdI.js} +2 -2
- package/dist/{usage-summary-BfcXjiSM.js.map → usage-summary-BM7B3JdI.js.map} +1 -1
- package/dist/{version-DqqqvHDP.js → version-DwQI12WA.js} +2 -2
- package/dist/{version-DqqqvHDP.js.map → version-DwQI12WA.js.map} +1 -1
- package/dist/{wait-task-BX4NcOOm.js → wait-task-BiI5UaZQ.js} +13 -3
- package/dist/wait-task-BiI5UaZQ.js.map +1 -0
- package/dist/{wire-CLm2rMK0.js → wire-Dxwxb7QQ.js} +2 -2
- package/dist/{wire-CLm2rMK0.js.map → wire-Dxwxb7QQ.js.map} +1 -1
- package/dist/{write-file-0E6TUJ9A.js → write-file-Dquu7MzB.js} +5 -3
- package/dist/write-file-Dquu7MzB.js.map +1 -0
- package/dist/{ws-DfGGYQ4W.js → ws-CA3o2nie.js} +4 -4
- package/dist/{ws-DfGGYQ4W.js.map → ws-CA3o2nie.js.map} +1 -1
- package/dist/{ws-client-CFMbhsNq.js → ws-client-C0iPAAzc.js} +2 -2
- package/dist/{ws-client-CFMbhsNq.js.map → ws-client-C0iPAAzc.js.map} +1 -1
- package/docs/CHAT.md +55 -0
- package/docs/CODEMODE.md +37 -0
- package/docs/EXTENSIONS.md +8 -6
- package/docs/RELEASE-NOTES-NEXT.md +48 -1
- package/docs/RELEASE.md +9 -1
- package/package.json +2 -3
- package/dist/agent-DJhpTS-B.js.map +0 -1
- package/dist/auto-update-restart-BFB7Ek87.js.map +0 -1
- package/dist/browser-BcoCCB_n.js.map +0 -1
- package/dist/contexts-C72b0mMa.js.map +0 -1
- package/dist/decisions-CXmK0nz9.js.map +0 -1
- package/dist/extension-mark-BqKdEZwF.js.map +0 -1
- package/dist/extensions-client-D8tEAcMw.d.ts.map +0 -1
- package/dist/host-BHMs9jsN.js.map +0 -1
- package/dist/host-wO_6GW0y.js.map +0 -1
- package/dist/index-CGbOWa7y.d.ts.map +0 -1
- package/dist/json-Dj-YATkZ.js.map +0 -1
- package/dist/media-type-BtA0cbfI.d.ts.map +0 -1
- package/dist/memory-DuVGrzbl.js.map +0 -1
- package/dist/nested-call-events-BYk8XvGX.js +0 -182
- package/dist/nested-call-events-BYk8XvGX.js.map +0 -1
- package/dist/nested-call-record-DlzPeTB7.js.map +0 -1
- package/dist/prelude-CVKoUzV8.js.map +0 -1
- package/dist/protocol-CEVqBO-q.d.ts.map +0 -1
- package/dist/providers-Z0TaBp1O.js.map +0 -1
- package/dist/registry-B9INJwJR.js.map +0 -1
- package/dist/repo-url-D4Dr8NfZ.js.map +0 -1
- package/dist/settings-catalog-4IU2i_jG.js.map +0 -1
- package/dist/source-Bl1Z501r.js.map +0 -1
- package/dist/sqlite-runtime-BZ2L4RaG.d.ts.map +0 -1
- package/dist/store-C6gI7FfZ.js.map +0 -1
- package/dist/todo-edits-CbukbjyV.js.map +0 -1
- package/dist/types-DTMEhKJO.js +0 -14
- package/dist/types-DTMEhKJO.js.map +0 -1
- package/dist/types-bDQagoiL.d.ts.map +0 -1
- package/dist/wait-task-BX4NcOOm.js.map +0 -1
- package/dist/write-file-0E6TUJ9A.js.map +0 -1
- package/scripts/eval.ts +0 -179
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-keys-iI_Y-7MC.js","names":[],"sources":["../src/ipc/daemon/lifecycle.ts","../src/ipc/transports/client-core.ts","../src/chat/sync/hash.ts","../src/chat/sync/settings-keys.ts"],"sourcesContent":["/**\n * Daemon lifecycle: socket-path resolution, liveness probing, stale-socket\n * cleanup, and spawn-on-demand.\n *\n * The socket lives in a dedicated daemon subdir of the resolved state dir\n * (`~/.zidane/daemon/daemon.sock` by default, honoring the same\n * `ZIDANE_STORAGE_DIR` / `ZIDANE_PREFIX` env knobs every other surface\n * uses), overridable via `ZIDANE_DAEMON_SOCKET` for parallel installs and\n * tests.\n */\nimport { spawn } from 'node:child_process'\nimport { existsSync, readdirSync, statSync } from 'node:fs'\nimport { connect } from 'node:net'\nimport { basename, dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { resolveStorageDirs } from '../../chat/xdg'\n\n/**\n * Build fingerprint for the handshake. Running from source it is the newest\n * mtime under the `src/` tree at import time. Bundled, it is the mtime of this\n * module's own file. Client and daemon load the same files from disk, so a\n * daemon spawned before a rebuild (or a source edit in dev) reports a\n * different id than a freshly-started client, and the client drains +\n * respawns it. This closes the \"same protocol version, different code\"\n * gap that a hand-bumped version constant can't: during development the\n * daemon process is frozen at spawn while the tree keeps moving.\n *\n * Compiled binaries (`/$bunfs/…` virtual paths) can't be stat'ed and\n * report `'unknown'`; the check is skipped when EITHER side is unknown,\n * so released builds rely on `IPC_PROTOCOL_VERSION` alone — correct,\n * since their code only changes when the version does.\n */\nexport const IPC_BUILD_ID: string = (() => {\n try {\n const selfPath = fileURLToPath(import.meta.url)\n /*\n * Dev (running from source): fingerprint the whole source tree, not\n * just this file — a change to server.ts / engine.ts / runtime code\n * with no edit to lifecycle.ts must still retire an old daemon. Walk\n * `src/` and take the newest mtime. When the tree isn't present\n * (bundled dist, compiled binary) we fall back to this module's own\n * file mtime; in a bundle every ipc source collapses into one chunk, so\n * that single mtime already moves on any ipc change.\n */\n /*\n * Root validation is load-bearing: from a built bundle this module\n * lives flat in `dist/`, so the two-levels-up guess would be the\n * PACKAGE root — walking it would synchronously stat the whole repo\n * (node_modules, .git) at import time and fingerprint unrelated\n * churn. Only walk when the candidate directory demonstrably IS the\n * ipc source tree; a bundle falls back to this chunk's own mtime,\n * which already moves on every rebuild.\n */\n const ipcRoot = dirname(dirname(selfPath))\n if (!existsSync(join(ipcRoot, 'protocol.ts')))\n return String(Math.floor(statSync(selfPath).mtimeMs))\n\n /*\n * Dev scope: walk the whole `src/` tree, not just `src/ipc` — daemon\n * behavior also lives in agent.ts, loop.ts, and chat/** (store,\n * replay, discovery), and an edit there must retire a running dev\n * daemon just as surely as an ipc edit (the spawn-marker incident\n * lived in chat/store.ts). A few hundred stats — milliseconds.\n */\n const srcRoot = dirname(ipcRoot)\n let newest = 0\n const walk = (dir: string): void => {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name)\n if (entry.isDirectory())\n walk(full)\n else\n newest = Math.max(newest, statSync(full).mtimeMs)\n }\n }\n try {\n walk(srcRoot)\n }\n catch {\n newest = statSync(selfPath).mtimeMs\n }\n return String(Math.floor(newest))\n }\n catch {\n return 'unknown'\n }\n})()\n\n/**\n * Socket file name. Dev builds (`ZIDANE_DEV=1`, set by every `dev` script\n * in the repo) get their OWN socket so a checkout's TUI, GUI and `zidane\n * daemon` pair up among themselves and never attach to — or get refused by\n * — the installed daemon. Without this, a protocol bump in the working\n * tree turned the dev GUI's dot red against the stable daemon, and a dev\n * daemon could drain the one the installed TUI relies on.\n * @param env - Environment to read `ZIDANE_DEV` from.\n * @returns `dev.sock` for a dev build, else `daemon.sock`.\n */\nexport function daemonSocketName(env: NodeJS.ProcessEnv = process.env): 'daemon.sock' | 'dev.sock' {\n return env.ZIDANE_DEV && env.ZIDANE_DEV !== '0' ? 'dev.sock' : 'daemon.sock'\n}\n\n/**\n * Path of the daemon socket this process uses.\n * The `ZIDANE_DAEMON_SOCKET` environment variable wins. Otherwise it is `<stateDir>/daemon/<socket name>`, see {@link daemonSocketName}.\n * @returns The socket path.\n */\nexport function defaultSocketPath(): string {\n const explicit = process.env.ZIDANE_DAEMON_SOCKET\n if (explicit)\n return explicit\n /*\n * The socket is transient runtime state, so it lands under the resolved\n * state dir — XDG-aware on XDG installs, `<storageDir>/<prefix>` in\n * legacy mode. Uses the same resolver every other surface reads through,\n * honoring `ZIDANE_STORAGE_DIR` / `ZIDANE_PREFIX`.\n *\n * DEDICATED `daemon/` subdir: the state dir is shared with\n * `state.json` / logs, so the daemon can't chmod it 0700 without\n * affecting unrelated files. The server hardens this subdir instead;\n * client and server must agree on this path.\n */\n const dirs = resolveStorageDirs({\n ...(process.env.ZIDANE_PREFIX !== undefined ? { prefix: process.env.ZIDANE_PREFIX } : {}),\n })\n return join(dirs.stateDir, 'daemon', daemonSocketName())\n}\n\n/**\n * Legacy socket location (`<stateDir>/daemon.sock`) for the given socket path,\n * or null when the path isn't the default `<stateDir>/daemon/daemon.sock`\n * layout (explicit `ZIDANE_DAEMON_SOCKET` users never had a migration to run).\n * A daemon still serving the old path would be ORPHANED by the move — new clients\n * never probe it, and a service-style `--idle-exit 0` daemon would hold\n * agents and run-guard markers against the same sessions.db forever —\n * so daemon startup probes this path and drains any survivor.\n */\nexport function legacySocketPath(socketPath: string): string | null {\n const dir = dirname(socketPath)\n if (basename(socketPath) !== 'daemon.sock' || basename(dir) !== 'daemon')\n return null\n return join(dirname(dir), 'daemon.sock')\n}\n\n/**\n * Spawn command that re-enters the CURRENT zidane program as `… daemon`.\n *\n * Three launch shapes:\n * - compiled Bun binary — `Bun.main` is the virtual `/$bunfs/…` entry the\n * binary self-inserts as argv[1], so the spawn must NOT repeat it\n * (verified: repeating it makes the child parse the junk path as its\n * subcommand and boot the full TUI instead of the daemon).\n * - dev (`bun run cli.ts`) — the script path must ride along.\n * - Node hosts (no `Bun` global) — fall back to the published bin.\n */\nexport function selfDaemonSpawnCommand(): string[] {\n const bunMain = (globalThis as { Bun?: { main?: string } }).Bun?.main\n // Compiled binary: the virtual entry parses its own `daemon` subcommand.\n if (bunMain?.startsWith('/$bunfs'))\n return [process.execPath, 'daemon']\n // Dev-from-source: only re-execute `Bun.main` when it's actually a\n // zidane CLI entry (`cli.ts`/`cli.js`). For any other embedder (library\n // consumer, script) re-running their entry with a `daemon` argv would\n // launch an arbitrary program — fall back to the published bin instead.\n if (bunMain && /(?:^|[/\\\\])cli\\.[cm]?[jt]s$/.test(bunMain))\n return [process.execPath, bunMain, 'daemon']\n return ['zidane-daemon']\n}\n\n/**\n * True when something accepts connections on `socketPath`. Distinguishes a\n * live daemon from a stale socket file left by a crashed one.\n * @param socketPath - Path of the socket to try.\n * @param timeoutMs - How long to wait for the connection, in milliseconds.\n */\nexport function probeSocket(socketPath: string, timeoutMs = 1000): Promise<boolean> {\n return new Promise((resolve) => {\n /*\n * Skip the connect entirely unless the path is an actual socket file.\n * A stale REGULAR file (crashed daemon) or a missing path both mean\n * \"nothing listening\" — and connecting to them can surface a noisy\n * async ENOENT/ENOTSOCK that some runtimes treat as uncaught.\n */\n try {\n if (!existsSync(socketPath) || !statSync(socketPath).isSocket()) {\n resolve(false)\n return\n }\n }\n catch {\n resolve(false)\n return\n }\n\n let socket: ReturnType<typeof connect>\n try {\n socket = connect(socketPath)\n }\n catch {\n resolve(false)\n return\n }\n const done = (alive: boolean): void => {\n socket.destroy()\n resolve(alive)\n }\n socket.once('connect', () => done(true))\n socket.once('error', () => done(false))\n socket.setTimeout(timeoutMs, () => done(false))\n })\n}\n\n/** Options for {@link ensureDaemonRunning}. */\nexport interface EnsureDaemonOptions {\n /**\n * Socket the daemon listens on.\n * @default defaultSocketPath()\n */\n socketPath?: string\n /**\n * Command used to spawn the daemon when nothing is listening. Defaults to\n * `['zidane-daemon']` (the published bin). The child is detached and\n * unref'd — it outlives the spawning client by design.\n */\n spawnCommand?: string[]\n /** Total time to wait for the daemon to accept connections. Default 8000 ms. */\n timeoutMs?: number\n}\n\n/** Daemon starts in flight, by socket path, so concurrent callers share one. */\nconst pendingStarts = new Map<string, Promise<string>>()\n\n/**\n * Makes sure a daemon is listening on the socket, spawning one when nothing is. Concurrent calls for the same socket share one start.\n * Rejects when the spawn fails or the daemon does not accept connections within `timeoutMs`.\n * @param options - Socket, spawn command and timeout.\n * @returns The socket path once a connection succeeds.\n */\nexport function ensureDaemonRunning(options: EnsureDaemonOptions = {}): Promise<string> {\n const socketPath = options.socketPath ?? defaultSocketPath()\n const pending = pendingStarts.get(socketPath)\n if (pending)\n return pending\n const start = startDaemon({ ...options, socketPath }).finally(() => pendingStarts.delete(socketPath))\n pendingStarts.set(socketPath, start)\n return start\n}\n\nasync function startDaemon(options: EnsureDaemonOptions & { socketPath: string }): Promise<string> {\n const socketPath = options.socketPath\n if (await probeSocket(socketPath))\n return socketPath\n\n const [cmd, ...args] = options.spawnCommand ?? ['zidane-daemon']\n const child = spawn(cmd!, args, {\n detached: true,\n stdio: 'ignore',\n env: { ...process.env, ZIDANE_DAEMON_SOCKET: socketPath },\n })\n /*\n * A missing binary (e.g. `zidane-daemon` not on PATH) emits an async\n * `error` event; without a listener Node rethrows it as an uncaught\n * exception and crashes the caller. Capture it and surface a clean\n * rejection through the poll loop below.\n */\n let spawnError: Error | undefined\n child.once('error', (err: Error) => {\n spawnError = err\n })\n child.unref()\n\n const deadline = Date.now() + (options.timeoutMs ?? 8000)\n for (;;) {\n if (spawnError !== undefined)\n throw new Error(`Failed to spawn zidane daemon (${cmd}): ${spawnError.message}`)\n if (await probeSocket(socketPath))\n return socketPath\n if (Date.now() > deadline)\n throw new Error(`zidane daemon did not come up on ${socketPath} within ${options.timeoutMs ?? 8000}ms.`)\n await new Promise(r => setTimeout(r, 150))\n }\n}\n","/**\n * The client side every wire transport shares (the unix socket in\n * `../client.ts`, the WebSocket in `./ws-client.ts`): the `ipc.hello`\n * handshake with its version fences, typed requests with deadlines,\n * notifications, and signal fan-out. A transport supplies a connected\n * JSON-RPC {@link ClientChannel}; this turns it into an {@link IpcClient}.\n */\nimport type { AcpPeer, JsonRpcMessage } from '../../acp/types'\nimport type { HelloResult, HostSignal, IpcMethodName, IpcParams, IpcResult, SignalMeta } from '../protocol'\nimport { JsonRpcConnectionClosedError, JsonRpcRemoteError } from '../../acp/json-rpc'\nimport { compareVersions, ZIDANE_VERSION } from '../../version'\nimport { IPC_BUILD_ID } from '../daemon/lifecycle'\nimport { IPC_MIN_PROTOCOL_VERSION, IPC_PROTOCOL_VERSION, IpcErrorCode, PING_METHOD, SIGNAL_SEQ_FIELD } from '../protocol'\n\n/** A connected host client, shared by every transport. Requests are checked against {@link IpcMethods}. */\nexport interface IpcClient {\n /** The host's answer to the `ipc.hello` handshake. */\n hello: HelloResult\n /**\n * Calls a host method and resolves with its result. Rejects with a `JsonRpcRemoteError` when the host answers with an error, or with `JsonRpcConnectionClosedError` when the connection closes first.\n * A request not answered within its deadline (30 s, longer for slow methods) rejects and drops the connection. `machine.call` has no deadline.\n */\n request: <M extends IpcMethodName>(method: M, params: IpcParams<M>) => Promise<IpcResult<M>>\n /**\n * Fire-and-forget notification — same method table as `request`, but no\n * id, no response, no pending-promise bookkeeping. The hot path for\n * high-frequency writes (per-keystroke draft sync). Daemons that don't\n * know the method ignore it silently.\n */\n notify: <M extends IpcMethodName>(method: M, params: IpcParams<M>) => void\n /** Host signals, with their number in their session when they have one (`SignalMeta`). */\n onSignal: (cb: SignalListener) => () => void\n /** Closes the connection. Pending requests reject. */\n close: () => void\n}\n\nexport type SignalListener = (signal: HostSignal, meta?: SignalMeta) => void\n\n/** Thrown when the host refuses the handshake because its protocol, build or code version does not match the client's. */\nexport class IpcVersionMismatchError extends Error {\n /**\n * @param message - Description of the mismatch.\n * @param daemon - The host's handshake data, when it sent any.\n */\n constructor(message: string, readonly daemon?: HelloResult) {\n super(message)\n this.name = 'IpcVersionMismatchError'\n }\n}\n\n/** A connected JSON-RPC link to a host, as a transport hands it over. */\nexport interface ClientChannel {\n rpc: Pick<AcpPeer, 'sendRequest' | 'sendNotification'>\n /** The caller's `close()`: quiet (no `onClose`), fails what is in flight. Idempotent. */\n close: () => void\n /** Drop a wedged connection: like the host going away, so the transport's `onClose` fires. */\n abort: () => void\n /** Where the host is, for error messages (a socket path, a URL). */\n where: string\n}\n\nexport interface ClientCoreOptions {\n appVersion?: string\n /**\n * When the host refuses the handshake on a version / build mismatch:\n * `always` asks it to drain so a matching one can be spawned (session\n * controllers on the local daemon); `stale-only` only when the host is\n * provably older (passive local clients); `never` leaves it running (a\n * remote server's lifetime is not the client's to end).\n */\n drain: 'always' | 'stale-only' | 'never'\n /**\n * Send this build's fingerprint, so a host from an older build of the same\n * protocol refuses us. Off sends the `'unknown'` sentinel: clients that\n * can't act on a build mismatch (no drain, no respawn) opt out of that fence.\n */\n buildFence: boolean\n /**\n * Ping the host (`$/ping`) this often once attached; two pings in a row\n * left unanswered at the next tick abort the connection (the transport's\n * `onClose` fires, its owner reconnects). Off when absent or `0`: a\n * local socket learns of a dead host from the kernel.\n */\n heartbeatMs?: number\n}\n\n/** Missed heartbeats after which the host is considered gone. */\nexport const HEARTBEAT_MISSES = 2\n\n/** Starts the client heartbeat on `channel`; returns its stop. Stops itself once the channel closes. */\nfunction startHeartbeat(channel: ClientChannel, intervalMs: number): () => void {\n let awaiting = false\n let missed = 0\n const timer = setInterval(() => {\n if (awaiting && ++missed >= HEARTBEAT_MISSES) {\n clearInterval(timer)\n channel.abort()\n return\n }\n awaiting = true\n channel.rpc.sendRequest(PING_METHOD, {}).then(\n () => {\n awaiting = false\n missed = 0\n },\n (err: unknown) => {\n if (err instanceof JsonRpcConnectionClosedError) {\n clearInterval(timer)\n return\n }\n // Any answer, an error included (a host predating `$/ping`), proves it alive.\n awaiting = false\n missed = 0\n },\n )\n }, intervalMs)\n timer.unref?.()\n return () => clearInterval(timer)\n}\n\n/** The signal listeners of one client, and the JSON-RPC notification handler that feeds them. */\nexport function createSignalListeners() {\n const listeners = new Set<SignalListener>()\n return {\n listeners,\n onNotification(message: JsonRpcMessage & { method: string }): void {\n if (message.method !== 'ipc.signal')\n return\n const params = (message as { params?: HostSignal & { [SIGNAL_SEQ_FIELD]?: unknown } }).params\n if (!params)\n return\n const { [SIGNAL_SEQ_FIELD]: seq, ...signal } = params\n const meta = typeof seq === 'number' ? { seq } : undefined\n for (const cb of listeners)\n cb(signal as HostSignal, meta)\n },\n }\n}\n\nfunction hostRelease(hello: HelloResult): string | undefined {\n for (const value of [hello.codeVersion, hello.appVersion]) {\n if (typeof value !== 'string')\n continue\n const match = /^(?:zidane-gui\\/)?(v?\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?)$/.exec(value.trim())\n if (match)\n return match[1]\n }\n return undefined\n}\n\nconst HELLO_TIMEOUT_MS = 5000\nconst LONG_REQUEST_TIMEOUT_MS = 10 * 60 * 1000\n/**\n * RPCs that legitimately run long: full compaction, index-backed cross-session search, a clone, a folder search,\n * and extension code (an RPC method, a command, a UI callback, a sign-in that waits for the user).\n */\nconst LONG_REQUEST_METHODS = new Set<string>(['sessions.compact', 'history.searchAll', 'history.searchSession', 'git.clone', 'fs.searchDirs', 'rpc.call', 'extensions.command.run', 'extensions.ui.invoke', 'extensions.integrations.start', 'extensions.ui.list', 'extensions.entries'])\n\n/**\n * A refused handshake. Asks the host to drain when `drain` allows it, closes\n * the channel, and returns the error to throw.\n */\nasync function refusedHello(channel: ClientChannel, err: JsonRpcRemoteError, drain: ClientCoreOptions['drain']): Promise<IpcVersionMismatchError> {\n /*\n * Ask the stale daemon to drain so the caller can respawn a matching\n * one. AWAIT the reply (bounded) before tearing the connection down —\n * closing discards unflushed writes, so a fire-and-forget\n * `daemon.shutdown` could be dropped, leaving the stale daemon alive\n * and defeating `connectOrSpawnDaemon`'s retry.\n */\n const daemon = (err.data as { daemon?: HelloResult } | undefined)?.daemon\n // A network server's lifetime is never a client's to end, whatever `drain` says.\n if (daemon?.hostKind === 'server') {\n channel.close()\n return new IpcVersionMismatchError(serverMismatchMessage(daemon, channel.where) ?? err.message, daemon)\n }\n /*\n * Passive clients (`stale-only`) never retire a daemon on a protocol /\n * build mismatch — it may be NEWER than them. A daemon that is provably\n * OLDER — an older release, or the same release speaking an older\n * protocol (a dev build from before a protocol bump) — is the case where\n * draining is always safe: nothing newer can be running on it, and\n * whoever attaches next spawns a current one. Without this a passive\n * client (the GUI) would sit in-process forever behind a stale daemon\n * nobody restarts: every retry is refused.\n */\n const daemonIsOlderRelease\n = typeof daemon?.codeVersion === 'string' && compareVersions(daemon.codeVersion, ZIDANE_VERSION) < 0\n const release = daemon && hostRelease(daemon)\n const daemonIsNewerRelease = release !== undefined && compareVersions(release, ZIDANE_VERSION) > 0\n const daemonSpeaksOlderProtocol = typeof daemon?.protocolVersion === 'number' && daemon.protocolVersion < IPC_PROTOCOL_VERSION\n const daemonIsStale = daemonIsOlderRelease || daemonSpeaksOlderProtocol\n if (drain !== 'never' && !daemonIsNewerRelease && (drain === 'always' || daemonIsStale)) {\n // `whenIdle`: a daemon with live runs defers its exit instead of\n // aborting them (older daemons ignore the flag and drain now).\n await Promise.race([\n channel.rpc.sendRequest('daemon.shutdown', { whenIdle: true }).catch(() => undefined),\n new Promise(resolve => setTimeout(resolve, 1500)),\n ])\n }\n channel.close()\n return new IpcVersionMismatchError(err.message, daemon)\n}\n\n/**\n * A server's protocol refusal, from this client's side: both versions and which side to upgrade.\n * `null` when the server's range (`minProtocolVersion`…`protocolVersion`) overlaps this client's.\n */\nfunction serverMismatchMessage(server: HelloResult, where: string): string | null {\n if (typeof server.protocolVersion !== 'number')\n return null\n const serverMin = typeof server.minProtocolVersion === 'number' ? server.minProtocolVersion : server.protocolVersion\n if (serverMin <= IPC_PROTOCOL_VERSION && server.protocolVersion >= IPC_MIN_PROTOCOL_VERSION)\n return null\n const upgrade = server.protocolVersion < IPC_PROTOCOL_VERSION ? 'server' : 'client'\n return `Protocol version mismatch with ${where}: the server speaks v${server.protocolVersion}, this client v${IPC_PROTOCOL_VERSION}. Upgrade the ${upgrade}.`\n}\n\nasync function handshake(channel: ClientChannel, options: ClientCoreOptions): Promise<HelloResult> {\n /*\n * Bound the handshake: a wedged daemon (accepting connections but never\n * reading — e.g. SIGSTOP'd) would otherwise hang this promise forever.\n * On timeout the channel is closed and the connect rejects.\n * `Promise.race` keeps the losing request promise handled, so the\n * connection-closed rejection that follows `close()` can't surface as an\n * unhandled rejection.\n */\n let helloTimer: ReturnType<typeof setTimeout> | undefined\n try {\n return (await Promise.race([\n channel.rpc.sendRequest('ipc.hello', {\n protocolVersion: IPC_PROTOCOL_VERSION,\n minProtocolVersion: IPC_MIN_PROTOCOL_VERSION,\n appVersion: options.appVersion ?? 'dev',\n pid: process.pid,\n // Build fingerprint: a daemon started from an OLDER build of this\n // same protocol version rejects us, gets drained, and respawns —\n // dev daemons cannot run stale code silently. Clients that\n // cannot act on a build mismatch send the 'unknown' sentinel\n // instead (see `buildFence`): fencing them would just lock them\n // out until someone else retires the host. They stay fenced by\n // protocol version and degrade via `optional()` like any client of\n // an older daemon.\n buildId: options.buildFence ? IPC_BUILD_ID : 'unknown',\n // Always sent, passive clients included: a stale-RELEASE daemon is\n // safe for anyone to retire (see `refusedHello`).\n codeVersion: ZIDANE_VERSION,\n }),\n new Promise<never>((_, reject) => {\n helloTimer = setTimeout(\n () => reject(new Error(`Daemon handshake timed out after ${HELLO_TIMEOUT_MS}ms on ${channel.where}.`)),\n HELLO_TIMEOUT_MS,\n )\n }),\n ])) as HelloResult\n }\n catch (err) {\n if (err instanceof JsonRpcRemoteError && err.code === IpcErrorCode.VersionMismatch)\n throw await refusedHello(channel, err, options.drain)\n channel.close()\n throw err\n }\n finally {\n clearTimeout(helloTimer)\n }\n}\n\n/** Handshake over `channel`, then the typed client around it. On failure the channel is closed. */\nexport async function openIpcClient(\n channel: ClientChannel,\n listeners: Set<SignalListener>,\n options: ClientCoreOptions,\n): Promise<IpcClient> {\n const hello = await handshake(channel, options)\n\n const release = hostRelease(hello)\n // A `retiring` daemon is fence-aware and already exits once idle; staying\n // attached until then keeps its live runs reachable from this window. A\n // network server is never retired for a client: an older one is attached\n // to, and this client degrades (`optional()`) like an older client does.\n if (!hello.retiring && hello.hostKind !== 'server' && release !== undefined && compareVersions(release, ZIDANE_VERSION) < 0) {\n // Pre-fence daemons accept hello without checking codeVersion. Disconnect\n // only: shutting down a successfully attached daemon can abort active runs.\n channel.close()\n throw new IpcVersionMismatchError(\n `Daemon release ${release} is older than client ${ZIDANE_VERSION}. Restart the daemon when active runs have finished.`,\n hello,\n )\n }\n\n // Env override is a test seam (process-level e2e exercises the real\n // deadline without waiting 30 s); undocumented, not a user setting.\n const requestTimeoutMs = Number(process.env.ZIDANE_IPC_REQUEST_TIMEOUT_MS) || 30_000\n\n const stopHeartbeat = options.heartbeatMs ? startHeartbeat(channel, options.heartbeatMs) : () => undefined\n\n return {\n hello,\n notify: (method, params) => {\n try {\n channel.rpc.sendNotification(method, params)\n }\n catch {\n // fire-and-forget by contract — a dying connection surfaces via onClose\n }\n },\n request: async (method, params) => {\n /*\n * Per-request deadline: close rejection covers a DEAD host, but a\n * wedged one (alive, not reading — the same class the server-side\n * backpressure bound handles for peers) would hang callers forever.\n * On timeout the connection is aborted (NOT `close()` — the `onClose`\n * path must fire so reconnect logic takes over) and every pending\n * request rejects via the close sweep. Known long-running RPCs get a\n * generous budget; tearing the connection down mid-`sessions.compact`\n * would abort real work, so those bounds are deliberately loose.\n */\n // A machine call lasts as long as its command (`docs/MACHINES.md`); a dead host is the heartbeat's to catch.\n if (method === 'machine.call')\n return channel.rpc.sendRequest(method, params) as Promise<IpcResult<typeof method>>\n const timeoutMs = LONG_REQUEST_METHODS.has(method) ? LONG_REQUEST_TIMEOUT_MS : requestTimeoutMs\n let timer: ReturnType<typeof setTimeout> | undefined\n try {\n return (await Promise.race([\n channel.rpc.sendRequest(method, params),\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n reject(new Error(`Daemon request ${method} timed out after ${timeoutMs}ms, closing the connection.`))\n channel.abort()\n }, timeoutMs)\n timer.unref?.()\n }),\n ])) as IpcResult<typeof method>\n }\n finally {\n clearTimeout(timer)\n }\n },\n onSignal: (cb) => {\n listeners.add(cb)\n return () => listeners.delete(cb)\n },\n close: () => {\n stopHeartbeat()\n channel.close()\n },\n }\n}\n","/** Stable hashes of sync values: equal values (whatever their key order) hash the same, on both sides. */\nimport { createHash } from 'node:crypto'\n\nexport const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/** JSON with sorted keys (undefined members dropped). */\nexport function stableStringify(value: unknown): string {\n if (Array.isArray(value))\n return `[${value.map(stableStringify).join(',')}]`\n if (isRecord(value))\n return `{${Object.keys(value).sort().filter(k => value[k] !== undefined).map(k => `${JSON.stringify(k)}:${stableStringify(value[k])}`).join(',')}}`\n return JSON.stringify(value) ?? 'undefined'\n}\n\nexport function hashValue(value: unknown): string {\n return createHash('sha256').update(stableStringify(value)).digest('hex')\n}\n","/** Which settings the `settings` sync category carries (`./types`). */\nimport type { Settings } from '../types'\nimport { HOST_ENABLED_LISTS, isWindowOnlySetting } from '../host-scoped-settings'\n\n/**\n * Host-scoped settings that still don't sync: they describe this computer\n * (its environments, its daemon, remote hosts on it) or would turn off what\n * the server has that this computer doesn't (the enabled allowlists, its OS\n * sandbox). Every `sandbox*` key, present and future, is the server's own:\n * a client turning it off there would be a sandbox escape.\n */\nconst NOT_SYNCED_SETTINGS: ReadonlySet<string> = new Set<string>(['environments', 'useDaemon', 'remoteHosts', ...HOST_ENABLED_LISTS])\n\n// The same legacy `safeMode` mapping as the live settings wire (`settings.get` / `settings.set`).\nexport { settingsFromWire, settingsToWire } from '../host-scoped-settings'\n\n/** A setting the `settings` category carries: host-scoped (never window-only) and not machine-specific. */\nexport function isSyncableSetting(key: keyof Settings | string): boolean {\n return !isWindowOnlySetting(key) && !NOT_SYNCED_SETTINGS.has(key) && !key.startsWith('sandbox')\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"settings-keys-BT0_oKjO.js","names":[],"sources":["../src/ipc/daemon/lifecycle.ts","../src/ipc/transports/client-core.ts","../src/chat/sync/hash.ts","../src/chat/sync/settings-keys.ts"],"sourcesContent":["/**\n * Daemon lifecycle: socket-path resolution, liveness probing, stale-socket\n * cleanup, and spawn-on-demand.\n *\n * The socket lives in a dedicated daemon subdir of the resolved state dir\n * (`~/.zidane/daemon/daemon.sock` by default, honoring the same\n * `ZIDANE_STORAGE_DIR` / `ZIDANE_PREFIX` env knobs every other surface\n * uses), overridable via `ZIDANE_DAEMON_SOCKET` for parallel installs and\n * tests.\n */\nimport { spawn } from 'node:child_process'\nimport { existsSync, readdirSync, statSync } from 'node:fs'\nimport { connect } from 'node:net'\nimport { basename, dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { resolveStorageDirs } from '../../chat/xdg'\n\n/**\n * Build fingerprint for the handshake. Running from source it is the newest\n * mtime under the `src/` tree at import time. Bundled, it is the mtime of this\n * module's own file. Client and daemon load the same files from disk, so a\n * daemon spawned before a rebuild (or a source edit in dev) reports a\n * different id than a freshly-started client, and the client drains +\n * respawns it. This closes the \"same protocol version, different code\"\n * gap that a hand-bumped version constant can't: during development the\n * daemon process is frozen at spawn while the tree keeps moving.\n *\n * Compiled binaries (`/$bunfs/…` virtual paths) can't be stat'ed and\n * report `'unknown'`; the check is skipped when EITHER side is unknown,\n * so released builds rely on `IPC_PROTOCOL_VERSION` alone — correct,\n * since their code only changes when the version does.\n */\nexport const IPC_BUILD_ID: string = (() => {\n try {\n const selfPath = fileURLToPath(import.meta.url)\n /*\n * Dev (running from source): fingerprint the whole source tree, not\n * just this file — a change to server.ts / engine.ts / runtime code\n * with no edit to lifecycle.ts must still retire an old daemon. Walk\n * `src/` and take the newest mtime. When the tree isn't present\n * (bundled dist, compiled binary) we fall back to this module's own\n * file mtime; in a bundle every ipc source collapses into one chunk, so\n * that single mtime already moves on any ipc change.\n */\n /*\n * Root validation is load-bearing: from a built bundle this module\n * lives flat in `dist/`, so the two-levels-up guess would be the\n * PACKAGE root — walking it would synchronously stat the whole repo\n * (node_modules, .git) at import time and fingerprint unrelated\n * churn. Only walk when the candidate directory demonstrably IS the\n * ipc source tree; a bundle falls back to this chunk's own mtime,\n * which already moves on every rebuild.\n */\n const ipcRoot = dirname(dirname(selfPath))\n if (!existsSync(join(ipcRoot, 'protocol.ts')))\n return String(Math.floor(statSync(selfPath).mtimeMs))\n\n /*\n * Dev scope: walk the whole `src/` tree, not just `src/ipc` — daemon\n * behavior also lives in agent.ts, loop.ts, and chat/** (store,\n * replay, discovery), and an edit there must retire a running dev\n * daemon just as surely as an ipc edit (the spawn-marker incident\n * lived in chat/store.ts). A few hundred stats — milliseconds.\n */\n const srcRoot = dirname(ipcRoot)\n let newest = 0\n const walk = (dir: string): void => {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name)\n if (entry.isDirectory())\n walk(full)\n else\n newest = Math.max(newest, statSync(full).mtimeMs)\n }\n }\n try {\n walk(srcRoot)\n }\n catch {\n newest = statSync(selfPath).mtimeMs\n }\n return String(Math.floor(newest))\n }\n catch {\n return 'unknown'\n }\n})()\n\n/**\n * Socket file name. Dev builds (`ZIDANE_DEV=1`, set by every `dev` script\n * in the repo) get their OWN socket so a checkout's TUI, GUI and `zidane\n * daemon` pair up among themselves and never attach to — or get refused by\n * — the installed daemon. Without this, a protocol bump in the working\n * tree turned the dev GUI's dot red against the stable daemon, and a dev\n * daemon could drain the one the installed TUI relies on.\n * @param env - Environment to read `ZIDANE_DEV` from.\n * @returns `dev.sock` for a dev build, else `daemon.sock`.\n */\nexport function daemonSocketName(env: NodeJS.ProcessEnv = process.env): 'daemon.sock' | 'dev.sock' {\n return env.ZIDANE_DEV && env.ZIDANE_DEV !== '0' ? 'dev.sock' : 'daemon.sock'\n}\n\n/**\n * Path of the daemon socket this process uses.\n * The `ZIDANE_DAEMON_SOCKET` environment variable wins. Otherwise it is `<stateDir>/daemon/<socket name>`, see {@link daemonSocketName}.\n * @returns The socket path.\n */\nexport function defaultSocketPath(): string {\n const explicit = process.env.ZIDANE_DAEMON_SOCKET\n if (explicit)\n return explicit\n /*\n * The socket is transient runtime state, so it lands under the resolved\n * state dir — XDG-aware on XDG installs, `<storageDir>/<prefix>` in\n * legacy mode. Uses the same resolver every other surface reads through,\n * honoring `ZIDANE_STORAGE_DIR` / `ZIDANE_PREFIX`.\n *\n * DEDICATED `daemon/` subdir: the state dir is shared with\n * `state.json` / logs, so the daemon can't chmod it 0700 without\n * affecting unrelated files. The server hardens this subdir instead;\n * client and server must agree on this path.\n */\n const dirs = resolveStorageDirs({\n ...(process.env.ZIDANE_PREFIX !== undefined ? { prefix: process.env.ZIDANE_PREFIX } : {}),\n })\n return join(dirs.stateDir, 'daemon', daemonSocketName())\n}\n\n/**\n * Legacy socket location (`<stateDir>/daemon.sock`) for the given socket path,\n * or null when the path isn't the default `<stateDir>/daemon/daemon.sock`\n * layout (explicit `ZIDANE_DAEMON_SOCKET` users never had a migration to run).\n * A daemon still serving the old path would be ORPHANED by the move — new clients\n * never probe it, and a service-style `--idle-exit 0` daemon would hold\n * agents and run-guard markers against the same sessions.db forever —\n * so daemon startup probes this path and drains any survivor.\n */\nexport function legacySocketPath(socketPath: string): string | null {\n const dir = dirname(socketPath)\n if (basename(socketPath) !== 'daemon.sock' || basename(dir) !== 'daemon')\n return null\n return join(dirname(dir), 'daemon.sock')\n}\n\n/**\n * Spawn command that re-enters the CURRENT zidane program as `… daemon`.\n *\n * Three launch shapes:\n * - compiled Bun binary — `Bun.main` is the virtual `/$bunfs/…` entry the\n * binary self-inserts as argv[1], so the spawn must NOT repeat it\n * (verified: repeating it makes the child parse the junk path as its\n * subcommand and boot the full TUI instead of the daemon).\n * - dev (`bun run cli.ts`) — the script path must ride along.\n * - Node hosts (no `Bun` global) — fall back to the published bin.\n */\nexport function selfDaemonSpawnCommand(): string[] {\n const bunMain = (globalThis as { Bun?: { main?: string } }).Bun?.main\n // Compiled binary: the virtual entry parses its own `daemon` subcommand.\n if (bunMain?.startsWith('/$bunfs'))\n return [process.execPath, 'daemon']\n // Dev-from-source: only re-execute `Bun.main` when it's actually a\n // zidane CLI entry (`cli.ts`/`cli.js`). For any other embedder (library\n // consumer, script) re-running their entry with a `daemon` argv would\n // launch an arbitrary program — fall back to the published bin instead.\n if (bunMain && /(?:^|[/\\\\])cli\\.[cm]?[jt]s$/.test(bunMain))\n return [process.execPath, bunMain, 'daemon']\n return ['zidane-daemon']\n}\n\n/**\n * True when something accepts connections on `socketPath`. Distinguishes a\n * live daemon from a stale socket file left by a crashed one.\n * @param socketPath - Path of the socket to try.\n * @param timeoutMs - How long to wait for the connection, in milliseconds.\n */\nexport function probeSocket(socketPath: string, timeoutMs = 1000): Promise<boolean> {\n return new Promise((resolve) => {\n /*\n * Skip the connect entirely unless the path is an actual socket file.\n * A stale REGULAR file (crashed daemon) or a missing path both mean\n * \"nothing listening\" — and connecting to them can surface a noisy\n * async ENOENT/ENOTSOCK that some runtimes treat as uncaught.\n */\n try {\n if (!existsSync(socketPath) || !statSync(socketPath).isSocket()) {\n resolve(false)\n return\n }\n }\n catch {\n resolve(false)\n return\n }\n\n let socket: ReturnType<typeof connect>\n try {\n socket = connect(socketPath)\n }\n catch {\n resolve(false)\n return\n }\n const done = (alive: boolean): void => {\n socket.destroy()\n resolve(alive)\n }\n socket.once('connect', () => done(true))\n socket.once('error', () => done(false))\n socket.setTimeout(timeoutMs, () => done(false))\n })\n}\n\n/** Options for {@link ensureDaemonRunning}. */\nexport interface EnsureDaemonOptions {\n /**\n * Socket the daemon listens on.\n * @default defaultSocketPath()\n */\n socketPath?: string\n /**\n * Command used to spawn the daemon when nothing is listening. Defaults to\n * `['zidane-daemon']` (the published bin). The child is detached and\n * unref'd — it outlives the spawning client by design.\n */\n spawnCommand?: string[]\n /** Total time to wait for the daemon to accept connections. Default 8000 ms. */\n timeoutMs?: number\n}\n\n/** Daemon starts in flight, by socket path, so concurrent callers share one. */\nconst pendingStarts = new Map<string, Promise<string>>()\n\n/**\n * Makes sure a daemon is listening on the socket, spawning one when nothing is. Concurrent calls for the same socket share one start.\n * Rejects when the spawn fails or the daemon does not accept connections within `timeoutMs`.\n * @param options - Socket, spawn command and timeout.\n * @returns The socket path once a connection succeeds.\n */\nexport function ensureDaemonRunning(options: EnsureDaemonOptions = {}): Promise<string> {\n const socketPath = options.socketPath ?? defaultSocketPath()\n const pending = pendingStarts.get(socketPath)\n if (pending)\n return pending\n const start = startDaemon({ ...options, socketPath }).finally(() => pendingStarts.delete(socketPath))\n pendingStarts.set(socketPath, start)\n return start\n}\n\nasync function startDaemon(options: EnsureDaemonOptions & { socketPath: string }): Promise<string> {\n const socketPath = options.socketPath\n if (await probeSocket(socketPath))\n return socketPath\n\n const [cmd, ...args] = options.spawnCommand ?? ['zidane-daemon']\n const child = spawn(cmd!, args, {\n detached: true,\n stdio: 'ignore',\n env: { ...process.env, ZIDANE_DAEMON_SOCKET: socketPath },\n })\n /*\n * A missing binary (e.g. `zidane-daemon` not on PATH) emits an async\n * `error` event; without a listener Node rethrows it as an uncaught\n * exception and crashes the caller. Capture it and surface a clean\n * rejection through the poll loop below.\n */\n let spawnError: Error | undefined\n child.once('error', (err: Error) => {\n spawnError = err\n })\n child.unref()\n\n const deadline = Date.now() + (options.timeoutMs ?? 8000)\n for (;;) {\n if (spawnError !== undefined)\n throw new Error(`Failed to spawn zidane daemon (${cmd}): ${spawnError.message}`)\n if (await probeSocket(socketPath))\n return socketPath\n if (Date.now() > deadline)\n throw new Error(`zidane daemon did not come up on ${socketPath} within ${options.timeoutMs ?? 8000}ms.`)\n await new Promise(r => setTimeout(r, 150))\n }\n}\n","/**\n * The client side every wire transport shares (the unix socket in\n * `../client.ts`, the WebSocket in `./ws-client.ts`): the `ipc.hello`\n * handshake with its version fences, typed requests with deadlines,\n * notifications, and signal fan-out. A transport supplies a connected\n * JSON-RPC {@link ClientChannel}; this turns it into an {@link IpcClient}.\n */\nimport type { AcpPeer, JsonRpcMessage } from '../../acp/types'\nimport type { HelloResult, HostSignal, IpcMethodName, IpcParams, IpcResult, SignalMeta } from '../protocol'\nimport { JsonRpcConnectionClosedError, JsonRpcRemoteError } from '../../acp/json-rpc'\nimport { compareVersions, ZIDANE_VERSION } from '../../version'\nimport { IPC_BUILD_ID } from '../daemon/lifecycle'\nimport { IPC_MIN_PROTOCOL_VERSION, IPC_PROTOCOL_VERSION, IpcErrorCode, PING_METHOD, SIGNAL_SEQ_FIELD } from '../protocol'\n\n/** A connected host client, shared by every transport. Requests are checked against {@link IpcMethods}. */\nexport interface IpcClient {\n /** The host's answer to the `ipc.hello` handshake. */\n hello: HelloResult\n /**\n * Calls a host method and resolves with its result. Rejects with a `JsonRpcRemoteError` when the host answers with an error, or with `JsonRpcConnectionClosedError` when the connection closes first.\n * A request not answered within its deadline (30 s, longer for slow methods) rejects and drops the connection. `machine.call` has no deadline.\n */\n request: <M extends IpcMethodName>(method: M, params: IpcParams<M>) => Promise<IpcResult<M>>\n /**\n * Fire-and-forget notification — same method table as `request`, but no\n * id, no response, no pending-promise bookkeeping. The hot path for\n * high-frequency writes (per-keystroke draft sync). Daemons that don't\n * know the method ignore it silently.\n */\n notify: <M extends IpcMethodName>(method: M, params: IpcParams<M>) => void\n /** Host signals, with their number in their session when they have one (`SignalMeta`). */\n onSignal: (cb: SignalListener) => () => void\n /** Closes the connection. Pending requests reject. */\n close: () => void\n}\n\nexport type SignalListener = (signal: HostSignal, meta?: SignalMeta) => void\n\n/** Thrown when the host refuses the handshake because its protocol, build or code version does not match the client's. */\nexport class IpcVersionMismatchError extends Error {\n /**\n * @param message - Description of the mismatch.\n * @param daemon - The host's handshake data, when it sent any.\n */\n constructor(message: string, readonly daemon?: HelloResult) {\n super(message)\n this.name = 'IpcVersionMismatchError'\n }\n}\n\n/** A connected JSON-RPC link to a host, as a transport hands it over. */\nexport interface ClientChannel {\n rpc: Pick<AcpPeer, 'sendRequest' | 'sendNotification'>\n /** The caller's `close()`: quiet (no `onClose`), fails what is in flight. Idempotent. */\n close: () => void\n /** Drop a wedged connection: like the host going away, so the transport's `onClose` fires. */\n abort: () => void\n /** Where the host is, for error messages (a socket path, a URL). */\n where: string\n}\n\nexport interface ClientCoreOptions {\n appVersion?: string\n /**\n * When the host refuses the handshake on a version / build mismatch:\n * `always` asks it to drain so a matching one can be spawned (session\n * controllers on the local daemon); `stale-only` only when the host is\n * provably older (passive local clients); `never` leaves it running (a\n * remote server's lifetime is not the client's to end).\n */\n drain: 'always' | 'stale-only' | 'never'\n /**\n * Send this build's fingerprint, so a host from an older build of the same\n * protocol refuses us. Off sends the `'unknown'` sentinel: clients that\n * can't act on a build mismatch (no drain, no respawn) opt out of that fence.\n */\n buildFence: boolean\n /**\n * Ping the host (`$/ping`) this often once attached; two pings in a row\n * left unanswered at the next tick abort the connection (the transport's\n * `onClose` fires, its owner reconnects). Off when absent or `0`: a\n * local socket learns of a dead host from the kernel.\n */\n heartbeatMs?: number\n}\n\n/** Missed heartbeats after which the host is considered gone. */\nexport const HEARTBEAT_MISSES = 2\n\n/** Starts the client heartbeat on `channel`; returns its stop. Stops itself once the channel closes. */\nfunction startHeartbeat(channel: ClientChannel, intervalMs: number): () => void {\n let awaiting = false\n let missed = 0\n const timer = setInterval(() => {\n if (awaiting && ++missed >= HEARTBEAT_MISSES) {\n clearInterval(timer)\n channel.abort()\n return\n }\n awaiting = true\n channel.rpc.sendRequest(PING_METHOD, {}).then(\n () => {\n awaiting = false\n missed = 0\n },\n (err: unknown) => {\n if (err instanceof JsonRpcConnectionClosedError) {\n clearInterval(timer)\n return\n }\n // Any answer, an error included (a host predating `$/ping`), proves it alive.\n awaiting = false\n missed = 0\n },\n )\n }, intervalMs)\n timer.unref?.()\n return () => clearInterval(timer)\n}\n\n/** The signal listeners of one client, and the JSON-RPC notification handler that feeds them. */\nexport function createSignalListeners() {\n const listeners = new Set<SignalListener>()\n return {\n listeners,\n onNotification(message: JsonRpcMessage & { method: string }): void {\n if (message.method !== 'ipc.signal')\n return\n const params = (message as { params?: HostSignal & { [SIGNAL_SEQ_FIELD]?: unknown } }).params\n if (!params)\n return\n const { [SIGNAL_SEQ_FIELD]: seq, ...signal } = params\n const meta = typeof seq === 'number' ? { seq } : undefined\n for (const cb of listeners)\n cb(signal as HostSignal, meta)\n },\n }\n}\n\nfunction hostRelease(hello: HelloResult): string | undefined {\n for (const value of [hello.codeVersion, hello.appVersion]) {\n if (typeof value !== 'string')\n continue\n const match = /^(?:zidane-gui\\/)?(v?\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?)$/.exec(value.trim())\n if (match)\n return match[1]\n }\n return undefined\n}\n\nconst HELLO_TIMEOUT_MS = 5000\nconst LONG_REQUEST_TIMEOUT_MS = 10 * 60 * 1000\n/**\n * RPCs that legitimately run long: full compaction, index-backed cross-session search, a clone, a folder search,\n * and extension code (an RPC method, a command, a UI callback, a sign-in that waits for the user).\n */\nconst LONG_REQUEST_METHODS = new Set<string>(['sessions.compact', 'history.searchAll', 'history.searchSession', 'git.clone', 'fs.searchDirs', 'rpc.call', 'extensions.command.run', 'extensions.ui.invoke', 'extensions.integrations.start', 'extensions.ui.list', 'extensions.entries'])\n\n/**\n * A refused handshake. Asks the host to drain when `drain` allows it, closes\n * the channel, and returns the error to throw.\n */\nasync function refusedHello(channel: ClientChannel, err: JsonRpcRemoteError, drain: ClientCoreOptions['drain']): Promise<IpcVersionMismatchError> {\n /*\n * Ask the stale daemon to drain so the caller can respawn a matching\n * one. AWAIT the reply (bounded) before tearing the connection down —\n * closing discards unflushed writes, so a fire-and-forget\n * `daemon.shutdown` could be dropped, leaving the stale daemon alive\n * and defeating `connectOrSpawnDaemon`'s retry.\n */\n const daemon = (err.data as { daemon?: HelloResult } | undefined)?.daemon\n // A network server's lifetime is never a client's to end, whatever `drain` says.\n if (daemon?.hostKind === 'server') {\n channel.close()\n return new IpcVersionMismatchError(serverMismatchMessage(daemon, channel.where) ?? err.message, daemon)\n }\n /*\n * Passive clients (`stale-only`) never retire a daemon on a protocol /\n * build mismatch — it may be NEWER than them. A daemon that is provably\n * OLDER — an older release, or the same release speaking an older\n * protocol (a dev build from before a protocol bump) — is the case where\n * draining is always safe: nothing newer can be running on it, and\n * whoever attaches next spawns a current one. Without this a passive\n * client (the GUI) would sit in-process forever behind a stale daemon\n * nobody restarts: every retry is refused.\n */\n const daemonIsOlderRelease\n = typeof daemon?.codeVersion === 'string' && compareVersions(daemon.codeVersion, ZIDANE_VERSION) < 0\n const release = daemon && hostRelease(daemon)\n const daemonIsNewerRelease = release !== undefined && compareVersions(release, ZIDANE_VERSION) > 0\n const daemonSpeaksOlderProtocol = typeof daemon?.protocolVersion === 'number' && daemon.protocolVersion < IPC_PROTOCOL_VERSION\n const daemonIsStale = daemonIsOlderRelease || daemonSpeaksOlderProtocol\n if (drain !== 'never' && !daemonIsNewerRelease && (drain === 'always' || daemonIsStale)) {\n // `whenIdle`: a daemon with live runs defers its exit instead of\n // aborting them (older daemons ignore the flag and drain now).\n await Promise.race([\n channel.rpc.sendRequest('daemon.shutdown', { whenIdle: true }).catch(() => undefined),\n new Promise(resolve => setTimeout(resolve, 1500)),\n ])\n }\n channel.close()\n return new IpcVersionMismatchError(err.message, daemon)\n}\n\n/**\n * A server's protocol refusal, from this client's side: both versions and which side to upgrade.\n * `null` when the server's range (`minProtocolVersion`…`protocolVersion`) overlaps this client's.\n */\nfunction serverMismatchMessage(server: HelloResult, where: string): string | null {\n if (typeof server.protocolVersion !== 'number')\n return null\n const serverMin = typeof server.minProtocolVersion === 'number' ? server.minProtocolVersion : server.protocolVersion\n if (serverMin <= IPC_PROTOCOL_VERSION && server.protocolVersion >= IPC_MIN_PROTOCOL_VERSION)\n return null\n const upgrade = server.protocolVersion < IPC_PROTOCOL_VERSION ? 'server' : 'client'\n return `Protocol version mismatch with ${where}: the server speaks v${server.protocolVersion}, this client v${IPC_PROTOCOL_VERSION}. Upgrade the ${upgrade}.`\n}\n\nasync function handshake(channel: ClientChannel, options: ClientCoreOptions): Promise<HelloResult> {\n /*\n * Bound the handshake: a wedged daemon (accepting connections but never\n * reading — e.g. SIGSTOP'd) would otherwise hang this promise forever.\n * On timeout the channel is closed and the connect rejects.\n * `Promise.race` keeps the losing request promise handled, so the\n * connection-closed rejection that follows `close()` can't surface as an\n * unhandled rejection.\n */\n let helloTimer: ReturnType<typeof setTimeout> | undefined\n try {\n return (await Promise.race([\n channel.rpc.sendRequest('ipc.hello', {\n protocolVersion: IPC_PROTOCOL_VERSION,\n minProtocolVersion: IPC_MIN_PROTOCOL_VERSION,\n appVersion: options.appVersion ?? 'dev',\n pid: process.pid,\n // Build fingerprint: a daemon started from an OLDER build of this\n // same protocol version rejects us, gets drained, and respawns —\n // dev daemons cannot run stale code silently. Clients that\n // cannot act on a build mismatch send the 'unknown' sentinel\n // instead (see `buildFence`): fencing them would just lock them\n // out until someone else retires the host. They stay fenced by\n // protocol version and degrade via `optional()` like any client of\n // an older daemon.\n buildId: options.buildFence ? IPC_BUILD_ID : 'unknown',\n // Always sent, passive clients included: a stale-RELEASE daemon is\n // safe for anyone to retire (see `refusedHello`).\n codeVersion: ZIDANE_VERSION,\n }),\n new Promise<never>((_, reject) => {\n helloTimer = setTimeout(\n () => reject(new Error(`Daemon handshake timed out after ${HELLO_TIMEOUT_MS}ms on ${channel.where}.`)),\n HELLO_TIMEOUT_MS,\n )\n }),\n ])) as HelloResult\n }\n catch (err) {\n if (err instanceof JsonRpcRemoteError && err.code === IpcErrorCode.VersionMismatch)\n throw await refusedHello(channel, err, options.drain)\n channel.close()\n throw err\n }\n finally {\n clearTimeout(helloTimer)\n }\n}\n\n/** Handshake over `channel`, then the typed client around it. On failure the channel is closed. */\nexport async function openIpcClient(\n channel: ClientChannel,\n listeners: Set<SignalListener>,\n options: ClientCoreOptions,\n): Promise<IpcClient> {\n const hello = await handshake(channel, options)\n\n const release = hostRelease(hello)\n // A `retiring` daemon is fence-aware and already exits once idle; staying\n // attached until then keeps its live runs reachable from this window. A\n // network server is never retired for a client: an older one is attached\n // to, and this client degrades (`optional()`) like an older client does.\n if (!hello.retiring && hello.hostKind !== 'server' && release !== undefined && compareVersions(release, ZIDANE_VERSION) < 0) {\n // Pre-fence daemons accept hello without checking codeVersion. Disconnect\n // only: shutting down a successfully attached daemon can abort active runs.\n channel.close()\n throw new IpcVersionMismatchError(\n `Daemon release ${release} is older than client ${ZIDANE_VERSION}. Restart the daemon when active runs have finished.`,\n hello,\n )\n }\n\n // Env override is a test seam (process-level e2e exercises the real\n // deadline without waiting 30 s); undocumented, not a user setting.\n const requestTimeoutMs = Number(process.env.ZIDANE_IPC_REQUEST_TIMEOUT_MS) || 30_000\n\n const stopHeartbeat = options.heartbeatMs ? startHeartbeat(channel, options.heartbeatMs) : () => undefined\n\n return {\n hello,\n notify: (method, params) => {\n try {\n channel.rpc.sendNotification(method, params)\n }\n catch {\n // fire-and-forget by contract — a dying connection surfaces via onClose\n }\n },\n request: async (method, params) => {\n /*\n * Per-request deadline: close rejection covers a DEAD host, but a\n * wedged one (alive, not reading — the same class the server-side\n * backpressure bound handles for peers) would hang callers forever.\n * On timeout the connection is aborted (NOT `close()` — the `onClose`\n * path must fire so reconnect logic takes over) and every pending\n * request rejects via the close sweep. Known long-running RPCs get a\n * generous budget; tearing the connection down mid-`sessions.compact`\n * would abort real work, so those bounds are deliberately loose.\n */\n // A machine call lasts as long as its command (`docs/MACHINES.md`); a dead host is the heartbeat's to catch.\n if (method === 'machine.call')\n return channel.rpc.sendRequest(method, params) as Promise<IpcResult<typeof method>>\n const timeoutMs = LONG_REQUEST_METHODS.has(method) ? LONG_REQUEST_TIMEOUT_MS : requestTimeoutMs\n let timer: ReturnType<typeof setTimeout> | undefined\n try {\n return (await Promise.race([\n channel.rpc.sendRequest(method, params),\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n reject(new Error(`Daemon request ${method} timed out after ${timeoutMs}ms, closing the connection.`))\n channel.abort()\n }, timeoutMs)\n timer.unref?.()\n }),\n ])) as IpcResult<typeof method>\n }\n finally {\n clearTimeout(timer)\n }\n },\n onSignal: (cb) => {\n listeners.add(cb)\n return () => listeners.delete(cb)\n },\n close: () => {\n stopHeartbeat()\n channel.close()\n },\n }\n}\n","/** Stable hashes of sync values: equal values (whatever their key order) hash the same, on both sides. */\nimport { createHash } from 'node:crypto'\n\nexport const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/** JSON with sorted keys (undefined members dropped). */\nexport function stableStringify(value: unknown): string {\n if (Array.isArray(value))\n return `[${value.map(stableStringify).join(',')}]`\n if (isRecord(value))\n return `{${Object.keys(value).sort().filter(k => value[k] !== undefined).map(k => `${JSON.stringify(k)}:${stableStringify(value[k])}`).join(',')}}`\n return JSON.stringify(value) ?? 'undefined'\n}\n\nexport function hashValue(value: unknown): string {\n return createHash('sha256').update(stableStringify(value)).digest('hex')\n}\n","/** Which settings the `settings` sync category carries (`./types`). */\nimport type { Settings } from '../types'\nimport { HOST_ENABLED_LISTS, isWindowOnlySetting } from '../host-scoped-settings'\n\n/**\n * Host-scoped settings that still don't sync: they describe this computer\n * (its environments, its daemon, remote hosts on it) or would turn off what\n * the server has that this computer doesn't (the enabled allowlists, its OS\n * sandbox). Every `sandbox*` key, present and future, is the server's own:\n * a client turning it off there would be a sandbox escape.\n */\nconst NOT_SYNCED_SETTINGS: ReadonlySet<string> = new Set<string>(['environments', 'useDaemon', 'remoteHosts', ...HOST_ENABLED_LISTS])\n\n// The same legacy `safeMode` mapping as the live settings wire (`settings.get` / `settings.set`).\nexport { settingsFromWire, settingsToWire } from '../host-scoped-settings'\n\n/** A setting the `settings` category carries: host-scoped (never window-only) and not machine-specific. */\nexport function isSyncableSetting(key: keyof Settings | string): boolean {\n return !isWindowOnlySetting(key) && !NOT_SYNCED_SETTINGS.has(key) && !key.startsWith('sandbox')\n}\n"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { p as errorMessage } from "./errors-C3z7pmOx.js";
|
|
|
2
2
|
import { c as validateSkillForWrite, l as validateSkillName } from "./interpolate-DD1y8j36.js";
|
|
3
3
|
import { f as parseSkillFile, u as parseFrontmatter } from "./allowed-tools-Da9RyNfj.js";
|
|
4
4
|
import { t as projectUserPaths } from "./project-user-paths-Chsi83mp.js";
|
|
5
|
-
import { a as fetchInstallSource, i as defaultWhich, l as resolveSubdirRoot, n as abortableInstallIo, o as lastLine, r as defaultExec, s as parseInstallSource, t as InstallSourceError } from "./source-
|
|
5
|
+
import { a as fetchInstallSource, i as defaultWhich, l as resolveSubdirRoot, n as abortableInstallIo, o as lastLine, r as defaultExec, s as parseInstallSource, t as InstallSourceError } from "./source-3ZnEPOSa.js";
|
|
6
6
|
import { i as skillCandidatesFromPaths, r as sanitizeSkillName } from "./weights-CqSDHJ4U.js";
|
|
7
7
|
import { n as githubAvatarUrl, r as githubPreviewImageUrl } from "./sources-h464u_hH.js";
|
|
8
8
|
import { basename, join, resolve } from "node:path";
|
|
@@ -760,4 +760,4 @@ function defineSkill(config) {
|
|
|
760
760
|
//#endregion
|
|
761
761
|
export { SKILL_PROVENANCE_FILE as a, listSkillCandidates as c, clearSkillSourceCache as i, parseSkillInstallSpec as l, SkillBrowseError as n, SkillInstallError as o, browseSkillSource as r, installSkillFromSpec as s, defineSkill as t, readSkillProvenance as u };
|
|
762
762
|
|
|
763
|
-
//# sourceMappingURL=skills-
|
|
763
|
+
//# sourceMappingURL=skills-CEbosUlM.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills-CFXRItJ6.js","names":["sanitizeName"],"sources":["../src/skills/install.ts","../src/skills/browse.ts","../src/skills/index.ts"],"sourcesContent":["/**\n * Skill installation — fetch a skill (or a collection of skills) from\n * GitHub / npm / skills.sh and land it under the zidane skills root,\n * where the existing discovery scan picks it up.\n *\n * Built on the shared install layer (`src/install/source.ts`), so the\n * spec grammar, transports, download caps, and subdir containment are\n * byte-identical with the extensions installer. What differs:\n *\n * - **Validation never executes code.** A skill is data: candidates\n * are validated by parsing `SKILL.md` (lenient parser, same one\n * discovery uses) — unlike extensions, whose validation must import\n * the module. Installing a hostile skill can plant instructions,\n * never run a process.\n * - **skills.sh interop, two-way.** The spec grammar accepts pasted\n * `https://skills.sh/<owner>/<repo>[/<skill>]` catalog URLs (mapped\n * to the GitHub repo + a skill selector — skills.sh uses GitHub as\n * its registry). And when the `skills` CLI is on PATH,\n * {@link installSkillFromSpec} delegates to it (`skills add … -y`)\n * so installs land in the user's cross-agent `.agents/skills/`\n * layout and feed the skills.sh lockfile/leaderboard; without the\n * CLI the native fetch path installs the same repos directly.\n * - **Target roots**: `~/.zidane/skills/<name>` (user) or\n * `<gitRoot>/.zidane/skills/<name>` (project) — both already on\n * discovery's default scan paths.\n *\n * Same hardening as extensions: staged `mkdtemp` fetch, physical\n * symlink containment for `:subdir`, folder-safe names, overwrite with\n * backup/rollback, and a provenance file (`.zidane-install.json`) that\n * records the spec so it can be replayed (see `readSkillProvenance`).\n */\n\nimport type { InstallIo, InstallScope, InstallSource } from '../install/source'\nimport { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { basename, join, resolve } from 'node:path'\nimport { projectUserPaths } from '../chat/project-user-paths'\nimport { errorMessage } from '../errors'\nimport { defaultExec, defaultWhich, fetchInstallSource, InstallSourceError, lastLine, parseInstallSource, resolveSubdirRoot } from '../install/source'\nimport { sanitizeSkillName as sanitizeName, SKILL_CANDIDATE_MAX_DEPTH } from './candidates'\nimport { parseSkillFile } from './discovery'\nimport { validateSkillName } from './validate'\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/** Machine-readable reason for a {@link SkillInstallError}. */\nexport type SkillInstallErrorCode\n = | 'invalid-source'\n | 'fetch-failed'\n | 'no-skill'\n | 'multiple-candidates'\n | 'validate-failed'\n | 'exists'\n | 'cli-failed'\n | 'io-failed'\n\n/** Error thrown when a skill install fails. `code` tells why. */\nexport class SkillInstallError extends InstallSourceError {\n /** Reason for the failure. See {@link SkillInstallErrorCode}. */\n declare readonly code: SkillInstallErrorCode\n /**\n * Frontmatter metadata per candidate, parallel to `candidates` — set on\n * the selector-less `multiple-candidates` error so host pickers can show\n * descriptions without re-fetching the source. Best-effort: candidates\n * whose SKILL.md fails to parse appear with no description.\n */\n readonly details?: readonly SkillCandidateDetail[]\n /**\n * Creates the error.\n *\n * @param code - Reason for the failure.\n * @param message - Human-readable description.\n * @param candidates - Root-relative paths of the skills found, set on `multiple-candidates`.\n * @param details - Metadata per candidate, parallel to `candidates`.\n */\n constructor(code: SkillInstallErrorCode, message: string, candidates?: readonly string[], details?: readonly SkillCandidateDetail[]) {\n super(code, message, candidates)\n this.name = 'SkillInstallError'\n if (details)\n this.details = details\n }\n}\n\n/** One pickable candidate surfaced to host UIs — see {@link SkillInstallError.details}. */\nexport interface SkillCandidateDetail {\n /** Root-relative path — the unambiguous selector (`skills` option). */\n rel: string\n /** Frontmatter description, when the candidate's SKILL.md parses. */\n description?: string\n}\n\nfunction rethrowAsSkillError(err: unknown): never {\n if (err instanceof InstallSourceError && !(err instanceof SkillInstallError))\n throw new SkillInstallError(err.code as SkillInstallErrorCode, err.message, err.candidates)\n throw err\n}\n\n// ---------------------------------------------------------------------------\n// Spec parsing (shared grammar + the skills.sh catalog URL form)\n// ---------------------------------------------------------------------------\n\nconst SKILLS_SH_URL_RE = /^(?:https?:\\/\\/)?(?:www\\.)?skills\\.sh\\/([\\w.-]+)\\/([\\w.-]+)(?:\\/([\\w.-]+))?\\/?$/\n\n/**\n * The skills CLI's deep shorthand, `owner/repo/path/to/folder` (as in\n * skill packs: `cursor/plugins/pstack/skills/unslop`): a GitHub repo with\n * the rest as its subfolder. The shared grammar spells that `owner/repo:path`.\n */\nconst DEEP_SHORTHAND_RE = /^([\\w.-]+)\\/([\\w.-]+)\\/([\\w.-]+(?:\\/[\\w.-]+)*)\\/?$/\n\n/**\n * Pasted install commands, as printed by skill READMEs and skills.sh\n * pages — `npx skills add owner/repo`, `bunx skills add …`,\n * `pnpm dlx skills add … --skill x`, or a bare `skills add …`. The\n * runner + `add` verb are stripped and the remaining tokens re-enter\n * the normal grammar, so users can copy a whole command into the\n * install input verbatim.\n */\nconst SKILLS_CLI_COMMAND_RE = /^(?:(?:npx|bunx|yarn\\s+dlx|pnpm\\s+dlx)\\s+)?skills(?:@\\S+)?\\s+add\\s+(\\S.*)$/i\n\n/** A parsed skill install source, ready for the install pipeline. */\nexport interface SkillInstallSpec {\n /** Where the skill comes from: a GitHub repository, an npm package or another supported source. */\n source: InstallSource\n /**\n * Skill name selector for multi-skill sources. From a skills.sh URL's\n * third segment or the caller's explicit pick after a\n * `multiple-candidates` error.\n */\n skill?: string\n /**\n * The spec string to hand the `skills` CLI (its `add` argument).\n * skills.sh URLs pass through verbatim — the CLI understands them —\n * while parsed shorthand re-serializes to `owner/repo`.\n */\n cliSpec: string\n}\n\n/**\n * Parse a raw skill source. Accepts everything the shared grammar does\n * (`owner/repo[#ref][:subdir]`, GitHub URLs, `pkg@version`, …) plus\n * pasted skills.sh catalog URLs — `skills.sh/vercel-labs/agent-skills/\n * react-best-practices` maps to the backing GitHub repo with the third\n * segment as the skill selector.\n * @param raw - Source text: shorthand, a URL, a skills.sh URL or a pasted `skills add` command.\n * @returns The parsed source, optional skill selector and CLI spec. Throws {@link SkillInstallError} with `invalid-source` when the text cannot be parsed.\n */\nexport function parseSkillInstallSpec(raw: string): SkillInstallSpec {\n const spec = raw.trim()\n const cliCommand = SKILLS_CLI_COMMAND_RE.exec(spec)\n if (cliCommand)\n return parseCliAddCommand(cliCommand[1] as string)\n const skillsSh = SKILLS_SH_URL_RE.exec(spec)\n if (skillsSh) {\n return {\n source: { kind: 'github', owner: skillsSh[1] as string, repo: skillsSh[2] as string },\n ...(skillsSh[3] ? { skill: skillsSh[3] } : {}),\n cliSpec: `${skillsSh[1]}/${skillsSh[2]}`,\n }\n }\n const deep = /^(?:www\\.)?github\\.com\\//i.test(spec) ? null : DEEP_SHORTHAND_RE.exec(spec)\n if (deep && (deep[3] as string).split('/').every(segment => segment !== '.' && segment !== '..'))\n return { source: { kind: 'github', owner: deep[1] as string, repo: deep[2] as string, subdir: deep[3] as string }, cliSpec: spec }\n try {\n const source = parseInstallSource(spec)\n return { source, cliSpec: spec }\n }\n catch (err) {\n rethrowAsSkillError(err)\n }\n}\n\n/**\n * Parse the argument tail of a pasted `skills add …` command. Honors\n * `--skill <name>` / `--skill=<name>` (their per-skill selector — an\n * explicit selector in the URL/shorthand form wins over it), ignores\n * flags that are the dialog's job here (`-y`, `-g`, `--global`, …),\n * and routes the first non-flag token back through\n * {@link parseSkillInstallSpec} so URLs and shorthands inside the\n * command parse identically to a bare paste.\n */\nfunction parseCliAddCommand(rest: string): SkillInstallSpec {\n const tokens = rest.split(/\\s+/).filter(t => t.length > 0)\n let sourceToken: string | undefined\n let skill: string | undefined\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i] as string\n if (token === '--skill' || token === '-s') {\n skill = tokens[i + 1]\n i += 1\n continue\n }\n const inline = /^--skill=(.+)$/.exec(token)\n if (inline) {\n skill = inline[1] as string\n continue\n }\n if (token.startsWith('-'))\n continue\n if (sourceToken === undefined)\n sourceToken = token\n }\n if (sourceToken === undefined)\n throw new SkillInstallError('invalid-source', 'The pasted command has no source. Expected “skills add <owner/repo>”.')\n const parsed = parseSkillInstallSpec(sourceToken)\n return { ...parsed, ...(skill !== undefined && parsed.skill === undefined ? { skill } : {}) }\n}\n\n// ---------------------------------------------------------------------------\n// Candidate enumeration + validation (data-only — no code execution)\n// ---------------------------------------------------------------------------\n\n/** One installable skill found in a fetched source tree. */\nexport interface SkillInstallCandidate {\n /** Directory name candidate (validated against SKILL.md frontmatter later). */\n name: string\n /** Absolute path of the skill directory in the fetched tree. */\n dir: string\n /**\n * Root-relative path (`engineering/tdd`) — the unambiguous selector\n * when the same skill NAME exists in several category directories.\n */\n rel: string\n}\n\n/**\n * How deep the candidate walk descends. Real-world collections nest\n * skills under category directories (`skills/engineering/tdd/SKILL.md`\n * in mattpocock/skills; `.github/skills/…` elsewhere), which a one-level\n * scan would miss. Four levels covers the layouts seen so far while\n * keeping the walk bounded on the staged tree.\n */\nconst CANDIDATE_WALK_MAX_DEPTH = SKILL_CANDIDATE_MAX_DEPTH\n\n/**\n * List installable skills inside a fetched tree.\n *\n * A skill is ANY directory containing a `SKILL.md` — found by a\n * bounded recursive walk (depth {@link CANDIDATE_WALK_MAX_DEPTH}),\n * skipping dot-dirs and `node_modules`, and never descending INTO a\n * skill (its `references/` / `scripts/` subtree ships with it, it\n * doesn't nest further candidates). A root-level `SKILL.md` means\n * \"repo IS the skill\" and short-circuits, named from the fallback.\n *\n * Candidates carry their root-relative path (`rel`) so same-named\n * skills in different categories stay individually addressable.\n * Sorted by `rel`.\n * @param dir - Root of the fetched source tree.\n * @param fallbackName - Name used when the root itself holds a `SKILL.md`.\n */\nexport function listSkillCandidates(dir: string, fallbackName: string): SkillInstallCandidate[] {\n if (existsSync(join(dir, 'SKILL.md')))\n return [{ name: sanitizeName(fallbackName), dir, rel: '.' }]\n const out: SkillInstallCandidate[] = []\n const walk = (current: string, rel: string, depth: number): void => {\n if (depth > CANDIDATE_WALK_MAX_DEPTH)\n return\n for (const entry of readdirSync(current, { withFileTypes: true })) {\n if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules')\n continue\n const childDir = join(current, entry.name)\n const childRel = rel.length === 0 ? entry.name : `${rel}/${entry.name}`\n if (existsSync(join(childDir, 'SKILL.md')))\n out.push({ name: entry.name, dir: childDir, rel: childRel })\n else\n walk(childDir, childRel, depth + 1)\n }\n }\n walk(dir, '', 1)\n return out.sort((a, b) => a.rel.localeCompare(b.rel))\n}\n\n/**\n * Frontmatter details for a host picker, parallel to the candidate list.\n * Parsing is best-effort per candidate — a malformed SKILL.md yields a\n * detail row without a description rather than failing the enumeration.\n */\nasync function candidateDetails(candidates: readonly SkillInstallCandidate[]): Promise<SkillCandidateDetail[]> {\n return Promise.all(candidates.map(async (c) => {\n const parsed = await parseSkillFile(join(c.dir, 'SKILL.md')).catch(() => null)\n const description = parsed?.description.trim()\n return { rel: c.rel, ...(description ? { description } : {}) }\n }))\n}\n\n/** Human-readable candidate list for error messages — capped, rel-qualified when ambiguous. */\nfunction describeCandidates(candidates: readonly SkillInstallCandidate[]): string {\n const nameCounts = new Map<string, number>()\n for (const c of candidates)\n nameCounts.set(c.name, (nameCounts.get(c.name) ?? 0) + 1)\n const labels = candidates.map(c => ((nameCounts.get(c.name) ?? 0) > 1 ? c.rel : c.name))\n const shown = labels.slice(0, 20)\n const more = labels.length - shown.length\n return shown.join(', ') + (more > 0 ? `, … and ${more} more` : '')\n}\n\n/**\n * Validate a staged candidate by PARSING its SKILL.md — the same lenient\n * parser discovery runs, so \"installs cleanly\" and \"discovers cleanly\"\n * are the same predicate. Returns the frontmatter name (which wins over\n * the directory-derived candidate name for the target folder).\n */\nasync function validateSkillCandidate(candidate: SkillInstallCandidate): Promise<{ name: string }> {\n const parsed = await parseSkillFile(join(candidate.dir, 'SKILL.md'))\n if (!parsed)\n throw new SkillInstallError('validate-failed', `${candidate.dir}/SKILL.md is not a valid skill (missing name or description in frontmatter).`)\n const name = parsed.name\n if (!validateSkillName(name))\n throw new SkillInstallError('validate-failed', `Skill name \"${name}\" is invalid (lowercase alphanumerics + single hyphens, max 64 chars).`)\n return { name }\n}\n\n// ---------------------------------------------------------------------------\n// Install\n// ---------------------------------------------------------------------------\n\n/** Options for {@link installSkillFromSpec}. */\nexport interface SkillInstallOptions {\n /** Raw source spec — see {@link parseSkillInstallSpec} for the grammar. */\n source: string\n /** Install into the `project` or the `user` skills root. */\n scope: InstallScope\n /** Pick one skill from a multi-skill source (names from the `multiple-candidates` error). */\n skill?: string\n /**\n * Install EVERY skill a multi-skill source contains (the affirmative\n * answer to the host's \"import all?\" prompt after a\n * `multiple-candidates` error). Native engine only — already-installed\n * names are skipped (reported via `SkillInstallResult.skipped`) rather\n * than failing the batch, unless `overwrite` is also set. Overrides a\n * selector embedded in the source string; ignored when an explicit\n * `skill` option is present.\n */\n all?: boolean\n /**\n * Install a picked SUBSET of a multi-skill source — the host's checkbox\n * picker over the `multiple-candidates` list. Entries match a skill's\n * name or its root-relative path (same rules as `skill`); duplicates\n * collapse. Batch semantics mirror `all` (skip existing, pre-validate,\n * native engine). Takes precedence over `all` and over a selector\n * embedded in the source string; ignored when an explicit `skill`\n * option is present.\n */\n skills?: readonly string[]\n /** Overwrite an existing install of the same name. */\n overwrite?: boolean\n /** Working directory that locates the project scope. Default: the process working directory. */\n cwd?: string\n /** Home directory that locates the user scope. Default: the current user home. */\n home?: string\n /** Storage prefix (default `.zidane`) — mirrors `ResolvedConfig.prefix`. */\n prefix?: string\n /** Overrides for `fetch`, command execution and `which`. Used by tests and sandboxed hosts. */\n io?: InstallIo\n /** Called with a short status message as the install proceeds. */\n onProgress?: (message: string) => void\n /**\n * Prefer the skills.sh CLI when `skills` is on PATH (default true).\n * The CLI owns scope + layout decisions (`.agents/skills/`, their\n * lockfile); the native path is the fallback and the forced path in\n * tests. Set false to always install natively.\n */\n preferSkillsCli?: boolean\n}\n\n/** Outcome of {@link installSkillFromSpec}. */\nexport interface SkillInstallResult {\n /** Final skill name(s) installed. The CLI path reports the spec, not names. */\n installed: readonly string[]\n /** Names skipped during an `all` import because they were already installed. */\n skipped?: readonly string[]\n /** Candidate paths excluded from an `all` import because they failed validation. */\n failed?: readonly string[]\n /** Absolute target directory (native path only — the CLI owns its layout). */\n dir?: string\n /** `skills-cli` when delegated to the skills.sh CLI, else `native`. */\n engine: 'skills-cli' | 'native'\n /**\n * Where the skill came from, for example `github:owner/repo@main` or `npm:pkg@1.2.3`. Set on the native path.\n */\n provenance?: string\n}\n\n/**\n * Install a skill (or one skill out of a multi-skill source).\n *\n * Engine selection: targeted installs (an explicit skill selector) go\n * through the skills.sh CLI when present on PATH (unless\n * `preferSkillsCli: false`) — keeping their cross-agent layout and\n * lockfile — else the native fetch pipeline. Selector-less installs are\n * ALWAYS native: only native enumeration can tell single- from\n * multi-skill sources, and the host's \"import all?\" prompt rides on the\n * `multiple-candidates` error — delegating them to `skills add … -y`\n * would let the CLI's own multi-skill behavior silently decide instead.\n * `all: true` batches are native for the same reason (skip-existing +\n * per-skill provenance are our semantics).\n * @param opts - Source, scope and install options. See {@link SkillInstallOptions}.\n */\nexport async function installSkillFromSpec(opts: SkillInstallOptions): Promise<SkillInstallResult> {\n const io = opts.io ?? {}\n const which = io.which ?? defaultWhich\n const spec = parseSkillInstallSpec(opts.source)\n\n // Batch modes (`all` / `skills` subset) are always native — see JSDoc.\n // They also override a selector derived from the SOURCE string (a\n // skills.sh URL's third segment, a pasted `--skill`): the host's picker\n // re-runs with the same source after an ambiguous-selector error, and\n // keeping the spec selector would just re-throw the same error in a\n // loop. An explicit `opts.skill` still wins over everything.\n const batchMode = opts.all === true || (opts.skills?.length ?? 0) > 0\n const skillSelector = opts.skill ?? (batchMode ? undefined : spec.skill)\n if (!batchMode && skillSelector !== undefined && (opts.preferSkillsCli ?? true) && which('skills'))\n return await installViaSkillsCli(spec, skillSelector, opts)\n\n return await installNatively(spec, skillSelector, opts)\n}\n\n/**\n * Delegate to the skills.sh CLI. Non-interactive by construction: `-y`\n * skips confirmation prompts, `-g` maps our `user` scope to their\n * global scope (project scope is their default, keyed off cwd). The CLI\n * decides the on-disk layout (`.agents/skills/`), which zidane's\n * discovery already scans.\n */\nasync function installViaSkillsCli(\n spec: SkillInstallSpec,\n skillSelector: string | undefined,\n opts: SkillInstallOptions,\n): Promise<SkillInstallResult> {\n const exec = opts.io?.exec ?? defaultExec\n const args = ['add', spec.cliSpec, '-y']\n if (skillSelector)\n args.push('--skill', skillSelector)\n if (opts.scope === 'user')\n args.push('-g')\n opts.onProgress?.(`skills add ${spec.cliSpec}${skillSelector ? ` --skill ${skillSelector}` : ''}…`)\n const res = await exec('skills', args, { cwd: opts.cwd ?? process.cwd() })\n if (!res.ok)\n throw new SkillInstallError('cli-failed', `skills.sh CLI failed: ${lastLine(res.stderr) || lastLine(res.stdout) || 'unknown error'}`)\n return {\n installed: [skillSelector ?? spec.cliSpec],\n engine: 'skills-cli',\n }\n}\n\nasync function installNatively(\n spec: SkillInstallSpec,\n skillSelector: string | undefined,\n opts: SkillInstallOptions,\n): Promise<SkillInstallResult> {\n const io = opts.io ?? {}\n const source = spec.source\n const stage = mkdtempSync(join(tmpdir(), 'zidane-skill-install-'))\n try {\n const fetchedInto = join(stage, 'fetched')\n mkdirSync(fetchedInto, { recursive: true })\n let provenance: string\n try {\n provenance = (await fetchInstallSource(source, fetchedInto, io, opts.onProgress)).provenance\n }\n catch (err) {\n rethrowAsSkillError(err)\n }\n\n // `:subdir` re-roots the search — same physical containment as\n // extensions (hostile symlinks must not walk out of the stage).\n let candidateRoot = fetchedInto\n let fallbackName = source.kind === 'github' ? source.repo : source.pkg\n if (source.kind === 'github' && source.subdir) {\n const rooted = resolveSubdirRoot(fetchedInto, source.subdir)\n if (!rooted.ok) {\n if (rooted.reason === 'missing')\n throw new SkillInstallError('no-skill', `\"${source.subdir}\" is not a directory in ${source.owner}/${source.repo}.`)\n throw new SkillInstallError('invalid-source', `Subdirectory \"${source.subdir}\" escapes the repository root${rooted.reason === 'escape-symlink' ? ' through a symlink' : ''}.`)\n }\n candidateRoot = rooted.dir\n fallbackName = basename(resolve(fetchedInto, source.subdir))\n }\n\n const candidates = listSkillCandidates(candidateRoot, fallbackName)\n if (candidates.length === 0)\n throw new SkillInstallError('no-skill', `No skill found in ${opts.source}: no SKILL.md in the tree (searched ${CANDIDATE_WALK_MAX_DEPTH} levels deep).`)\n let picked: SkillInstallCandidate[]\n if (!skillSelector && opts.skills && opts.skills.length > 0) {\n // Host-picked subset (checkbox picker over the `multiple-candidates`\n // list). Each entry resolves with the same name-or-rel-path rules as\n // the single selector; duplicate picks collapse to one install.\n const byRel = new Map<string, SkillInstallCandidate>()\n for (const sel of opts.skills) {\n const matches = candidates.filter(c => c.name === sel || c.rel === sel)\n if (matches.length === 0)\n throw new SkillInstallError('no-skill', `No skill named \"${sel}\" in ${opts.source}. Available: ${describeCandidates(candidates)}`, candidates.map(c => c.rel))\n if (matches.length > 1)\n throw new SkillInstallError('multiple-candidates', `\"${sel}\" matches several skills in ${opts.source}. Pick a path: ${matches.map(c => c.rel).join(', ')}`, matches.map(c => c.rel))\n const match = matches[0] as SkillInstallCandidate\n byRel.set(match.rel, match)\n }\n picked = [...byRel.values()]\n }\n else if (skillSelector) {\n // Selector matches the skill name OR its root-relative path — the\n // path form disambiguates same-named skills in different category\n // directories (`engineering/tdd` vs `personal/tdd`).\n const matches = candidates.filter(c => c.name === skillSelector || c.rel === skillSelector)\n if (matches.length === 0)\n throw new SkillInstallError('no-skill', `No skill named \"${skillSelector}\" in ${opts.source}. Available: ${describeCandidates(candidates)}`, candidates.map(c => c.rel))\n if (matches.length > 1)\n throw new SkillInstallError('multiple-candidates', `\"${skillSelector}\" matches several skills in ${opts.source}. Pick a path: ${matches.map(c => c.rel).join(', ')}`, matches.map(c => c.rel))\n picked = [matches[0] as SkillInstallCandidate]\n }\n else if (candidates.length > 1 && !opts.all) {\n throw new SkillInstallError(\n 'multiple-candidates',\n `${opts.source} contains ${candidates.length} skills. Pick one: ${describeCandidates(candidates)}`,\n candidates.map(c => c.rel),\n await candidateDetails(candidates),\n )\n }\n else {\n picked = [...candidates]\n }\n\n const root = scopeRoot(opts)\n const installed: string[] = []\n const skipped: string[] = []\n const failed: string[] = []\n let lastDir: string | undefined\n const batch = picked.length > 1\n\n // Validate the whole batch BEFORE installing anything — one malformed\n // candidate must not abort a half-applied import. Batch mode collects\n // failures (reported via `SkillInstallResult.failed`) and installs the\n // rest; a targeted single install keeps the hard throw.\n const resolved: Array<{ candidate: SkillInstallCandidate, name: string }> = []\n // Intra-batch name conflicts: two candidates whose frontmatter\n // validates to the same name (`engineering/tdd` + `personal/tdd`)\n // would otherwise install the first and mislabel the second as\n // \"already installed\" (or, with overwrite, silently last-wins).\n // First occurrence wins; later ones are reported as conflicts.\n const nameToRel = new Map<string, string>()\n for (const candidate of picked) {\n try {\n // Frontmatter name wins over the directory-derived candidate name —\n // mirrors \"target folder is named from the VALIDATED definition\".\n const { name } = await validateSkillCandidate(candidate)\n const conflict = nameToRel.get(name)\n if (conflict !== undefined) {\n // Only reachable in batch mode — a single pick can't conflict.\n failed.push(candidate.rel)\n opts.onProgress?.(`skipped \"${candidate.rel}\": the name \"${name}\" conflicts with \"${conflict}\"`)\n continue\n }\n nameToRel.set(name, candidate.rel)\n resolved.push({ candidate, name })\n }\n catch (err) {\n if (!batch)\n throw err\n failed.push(candidate.rel)\n opts.onProgress?.(`skipped \"${candidate.rel}\": ${errorMessage(err)}`)\n }\n }\n\n for (const { candidate, name } of resolved) {\n const targetDir = join(root, name)\n const backupDir = `${targetDir}.zidane-backup`\n if (existsSync(targetDir) && !opts.overwrite) {\n // Batch imports skip collisions (already-installed skills are the\n // common case when re-importing a repo); a targeted single install\n // keeps the hard error so the overwrite decision stays explicit.\n if (batch) {\n skipped.push(name)\n opts.onProgress?.(`skipped \"${name}\": already installed`)\n continue\n }\n throw new SkillInstallError('exists', `Skill \"${name}\" is already installed at ${targetDir}. Overwrite to replace it.`)\n }\n\n mkdirSync(root, { recursive: true })\n const hadPrevious = existsSync(targetDir)\n if (hadPrevious) {\n rmSync(backupDir, { recursive: true, force: true })\n renameSync(targetDir, backupDir)\n }\n try {\n // `dereference` materializes in-tree symlinked files so the install\n // has no dangling links once the stage is deleted.\n cpSync(candidate.dir, targetDir, { recursive: true, dereference: true })\n writeProvenance(targetDir, { spec: opts.source, ...(skillSelector ? { skill: skillSelector } : {}), scope: opts.scope, provenance, installedAt: Date.now() })\n }\n catch (err) {\n rmSync(targetDir, { recursive: true, force: true })\n if (hadPrevious)\n renameSync(backupDir, targetDir)\n throw new SkillInstallError('io-failed', `Writing ${targetDir} failed: ${errorMessage(err)}`)\n }\n rmSync(backupDir, { recursive: true, force: true })\n installed.push(name)\n lastDir = targetDir\n if (batch)\n opts.onProgress?.(`installed \"${name}\" (${installed.length}/${resolved.length})`)\n }\n\n if (installed.length === 0 && skipped.length > 0)\n throw new SkillInstallError('exists', `All ${skipped.length} installable skills from ${opts.source} are already installed: ${skipped.join(', ')}. Overwrite to replace them.`)\n if (installed.length === 0 && failed.length > 0)\n throw new SkillInstallError('validate-failed', `No skill from ${opts.source} could be installed. Invalid: ${failed.join(', ')}.`)\n\n return {\n installed,\n ...(skipped.length > 0 ? { skipped } : {}),\n ...(failed.length > 0 ? { failed } : {}),\n ...(lastDir !== undefined ? { dir: lastDir } : {}),\n engine: 'native',\n provenance,\n }\n }\n finally {\n rmSync(stage, { recursive: true, force: true })\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Provenance sidecar file written into each natively installed skill directory. It records the spec so the install can be replayed. */\nexport const SKILL_PROVENANCE_FILE = '.zidane-install.json'\n\ninterface SkillProvenance {\n spec: string\n skill?: string\n scope: InstallScope\n provenance: string\n installedAt: number\n}\n\nfunction writeProvenance(dir: string, data: SkillProvenance): void {\n writeFileSync(join(dir, SKILL_PROVENANCE_FILE), `${JSON.stringify(data, null, 2)}\\n`)\n}\n\n/**\n * Read a skill directory's install provenance, `null` for hand-authored skills.\n * @param dir - Skill directory.\n */\nexport function readSkillProvenance(dir: string): (SkillProvenance & { spec: string }) | null {\n try {\n const raw = JSON.parse(readFileSync(join(dir, SKILL_PROVENANCE_FILE), 'utf8')) as Partial<SkillProvenance>\n return typeof raw.spec === 'string' && raw.spec.length > 0 ? (raw as SkillProvenance) : null\n }\n catch {\n return null\n }\n}\n\n/**\n * Native-install target root: the `<prefix>/skills` flavor of the\n * project/user convention (`~/.zidane/skills`, `<gitRoot>/.zidane/skills`).\n * The `.agents/skills` siblings stay read-only for us — they belong to\n * the cross-agent ecosystem (and the skills.sh CLI writes there).\n */\nfunction scopeRoot(opts: SkillInstallOptions): string {\n const roots = projectUserPaths({\n subPath: 'skills',\n ...(opts.cwd ? { cwd: opts.cwd } : {}),\n ...(opts.home ? { home: opts.home } : {}),\n ...(opts.prefix ? { prefix: opts.prefix } : {}),\n })\n const root = roots.find(r => r.source === opts.scope && r.path.includes(`.${(opts.prefix ?? '.zidane').replace(/^\\./, '')}`))\n if (!root)\n throw new SkillInstallError('io-failed', `No ${opts.scope} skills root resolved.`)\n return root.path\n}\n","/**\n * Lists the skills of a source before an install, so an app can show them\n * and let the user pick. It takes everything the installer takes\n * (`parseSkillInstallSpec`): `owner/repo`, GitHub URLs with\n * `/tree/<ref>/<path>`, `owner/repo/path`, skills.sh URLs, pasted\n * `npx skills add …` commands and npm packages.\n *\n * - GitHub: two API calls (the repository and its file tree), then the\n * head of each SKILL.md from raw.githubusercontent, eight at a time.\n * The candidate rules are the installer's (`skillCandidatesFromPaths`),\n * so every listed skill installs. A token raises GitHub's limit from\n * 60 to 5,000 calls an hour.\n * - A pack: a repository with no SKILL.md whose README links SKILL.md\n * files in other repositories (antfu/skills-pack). Each link is a skill.\n * - npm, or a GitHub API error (a rate limit, no network to the API):\n * the installer's own download, then the same folder walk.\n *\n * Nothing runs: a skill is data. Listings stay cached in memory for ten\n * minutes. Each skill carries the spec that installs it\n * (`installSkillFromSpec` with `skills: [rel]`).\n */\nimport type { InstallIo, InstallSource } from '../install/source'\nimport type { SkillSourceInfo, SkillSourceListing, SkillSourceSkill } from './sources'\nimport { createHash } from 'node:crypto'\nimport { mkdirSync, mkdtempSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { basename, join } from 'node:path'\nimport { errorMessage } from '../errors'\nimport { abortableInstallIo, fetchInstallSource, resolveSubdirRoot } from '../install/source'\nimport { skillCandidatesFromPaths } from './candidates'\nimport { parseFrontmatter, parseSkillFile } from './discovery'\nimport { listSkillCandidates, parseSkillInstallSpec } from './install'\nimport { githubAvatarUrl, githubPreviewImageUrl } from './sources'\n\n/**\n * Why a listing failed.\n *\n * - `invalid-source`: the text is not a source.\n * - `not-found`: no such repository, folder or package, or no access to it.\n * - `rate-limited`: GitHub refused more calls without a token, and the download failed too.\n * - `fetch-failed`: the network or the download failed.\n */\nexport type SkillBrowseErrorCode = 'invalid-source' | 'not-found' | 'rate-limited' | 'fetch-failed'\n\n/** A listing that failed, with the reason in `code`. */\nexport class SkillBrowseError extends Error {\n /** Why it failed. */\n readonly code: SkillBrowseErrorCode\n /**\n * @param code - Why the listing failed.\n * @param message - What failed, in a sentence a person can read.\n */\n constructor(code: SkillBrowseErrorCode, message: string) {\n super(message)\n this.name = 'SkillBrowseError'\n this.code = code\n }\n}\n\n/** Options of {@link browseSkillSource}. */\nexport interface BrowseSkillSourceOptions {\n /** A GitHub token: 5,000 API calls an hour instead of 60, and private repositories. */\n token?: string\n /** Stops the listing. */\n signal?: AbortSignal\n /** Overrides for `fetch`, command execution and `which`. Used by tests. */\n io?: InstallIo\n /** Ignore the cached listing and list again. */\n fresh?: boolean\n}\n\nconst API = 'https://api.github.com'\nconst RAW = 'https://raw.githubusercontent.com'\nconst CACHE_TTL_MS = 10 * 60_000\nconst CACHE_MAX = 24\n/** More skills than any real source holds: a cap, so a hostile tree cannot flood the list. */\nconst MAX_SKILLS = 300\n/** The bytes read from each SKILL.md: its frontmatter fits. */\nconst HEAD_BYTES = 8192\nconst CONCURRENCY = 8\nconst README_RE = /^readme(?:\\.md|\\.markdown)?$/i\n/** A link to a SKILL.md on GitHub: `github.com/<owner>/<repo>/blob/<ref>/<folder>/SKILL.md`. */\nconst SKILL_LINK_RE = /https?:\\/\\/github\\.com\\/([\\w.-]+)\\/([\\w.-]+)\\/blob\\/([\\w.-]+)\\/(?:([\\w./-]+?)\\/)?SKILL\\.md/g\n\nconst cache = new Map<string, { at: number, listing: SkillSourceListing }>()\n\n/** Forgets every cached listing. */\nexport function clearSkillSourceCache(): void {\n cache.clear()\n}\n\nfunction remember(key: string, listing: SkillSourceListing): void {\n cache.delete(key)\n cache.set(key, { at: Date.now(), listing })\n while (cache.size > CACHE_MAX)\n cache.delete(cache.keys().next().value as string)\n}\n\nasync function mapLimit<T, R>(items: readonly T[], limit: number, fn: (item: T) => Promise<R>): Promise<R[]> {\n const out: R[] = Array.from({ length: items.length })\n let next = 0\n const worker = async (): Promise<void> => {\n while (next < items.length) {\n const i = next++\n out[i] = await fn(items[i] as T)\n }\n }\n await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker))\n return out\n}\n\n/** The spec that installs from a GitHub folder: `owner/repo[#ref][:folder]`. */\nfunction githubSpec(owner: string, repo: string, ref: string | undefined, folder: string): string {\n return `${owner}/${repo}${ref && ref !== 'HEAD' ? `#${ref}` : ''}${folder ? `:${folder}` : ''}`\n}\n\nfunction text(value: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\ninterface Fetcher {\n fetch: typeof fetch\n token: string | undefined\n}\n\nasync function call(fetcher: Fetcher, url: string, init: RequestInit = {}): Promise<Response> {\n try {\n return await fetcher.fetch(url, init)\n }\n catch (err) {\n throw new SkillBrowseError('fetch-failed', `The connection to ${new URL(url).host} failed. ${errorMessage(err)}`)\n }\n}\n\nasync function githubJson<T>(fetcher: Fetcher, path: string, what: string): Promise<T> {\n const res = await call(fetcher, `${API}${path}`, {\n headers: {\n 'Accept': 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n ...(fetcher.token ? { Authorization: `Bearer ${fetcher.token}` } : {}),\n },\n })\n if (res.status === 404)\n throw new SkillBrowseError('not-found', `GitHub has no ${what}, or this computer has no access to it.`)\n if (res.status === 429 || (res.status === 403 && res.headers.get('x-ratelimit-remaining') === '0'))\n throw new SkillBrowseError('rate-limited', 'GitHub limits calls without a token. Sign in with gh auth login, or try again later.')\n if (!res.ok)\n throw new SkillBrowseError('fetch-failed', `GitHub answered ${res.status} for ${what}.`)\n return await res.json() as T\n}\n\n/** The name and description of a SKILL.md on GitHub, from its first bytes. */\nasync function skillHead(fetcher: Fetcher, owner: string, repo: string, ref: string, folder: string): Promise<{ name: string, description: string }> {\n const url = `${RAW}/${owner}/${repo}/${ref}/${folder ? `${folder}/` : ''}SKILL.md`\n const auth: Record<string, string> = fetcher.token ? { Authorization: `token ${fetcher.token}` } : {}\n try {\n // Identity: a byte range of a gzip stream does not decompress.\n let res = await call(fetcher, url, { headers: { 'Range': `bytes=0-${HEAD_BYTES - 1}`, 'Accept-Encoding': 'identity', ...auth } })\n let content = res.ok ? await res.text() : ''\n // A frontmatter longer than the head: read the whole file.\n if (res.status === 206 && !/^\\s*---[\\s\\S]*?\\n---/.test(content)) {\n res = await call(fetcher, url, { headers: auth })\n content = res.ok ? await res.text() : ''\n }\n const { frontmatter } = parseFrontmatter(content)\n return { name: text(frontmatter.name), description: text(frontmatter.description) }\n }\n catch {\n // One unreadable SKILL.md costs its description, not the listing.\n return { name: '', description: '' }\n }\n}\n\ninterface GithubRepo {\n description: string | null\n stargazers_count: number\n default_branch: string\n html_url: string\n}\n\ninterface GithubTree {\n tree: { path: string, type: string }[]\n truncated: boolean\n}\n\nasync function browseGithub(input: string, source: Extract<InstallSource, { kind: 'github' }>, fetcher: Fetcher): Promise<SkillSourceListing> {\n const { owner, repo } = source\n const repoInfo = await githubJson<GithubRepo>(fetcher, `/repos/${owner}/${repo}`, `repository ${owner}/${repo}`)\n const ref = source.ref ?? repoInfo.default_branch\n const tree = await githubJson<GithubTree>(fetcher, `/repos/${owner}/${repo}/git/trees/${encodeURIComponent(ref)}?recursive=1`, `branch ${ref} in ${owner}/${repo}`)\n const root = source.subdir ?? ''\n if (root && !tree.tree.some(entry => entry.type === 'tree' && entry.path === root))\n throw new SkillBrowseError('not-found', `\"${root}\" is not a folder in ${owner}/${repo}.`)\n const files = tree.tree.filter(entry => entry.type === 'blob').map(entry => entry.path)\n const info: SkillSourceInfo = {\n kind: 'github',\n label: root ? `${owner}/${repo}/${root}` : `${owner}/${repo}`,\n owner,\n repo,\n ref,\n ...(root ? { path: root } : {}),\n url: root ? `https://github.com/${owner}/${repo}/tree/${ref}/${root}` : repoInfo.html_url,\n ...(repoInfo.description ? { description: repoInfo.description } : {}),\n stars: repoInfo.stargazers_count,\n avatarUrl: githubAvatarUrl(owner),\n imageUrl: githubPreviewImageUrl(owner, repo),\n }\n const candidates = skillCandidatesFromPaths(files, root, root ? basename(root) : repo).slice(0, MAX_SKILLS)\n if (candidates.length === 0 && !root) {\n const readme = files.find(file => README_RE.test(file))\n const pack = readme ? await packSkills(fetcher, owner, repo, ref, readme) : []\n if (pack.length > 0)\n return { input, source: { ...info, collection: true }, skills: pack, ...(tree.truncated ? { truncated: true } : {}) }\n }\n const spec = githubSpec(owner, repo, source.ref, root)\n const skills = await mapLimit(candidates, CONCURRENCY, async (candidate): Promise<SkillSourceSkill> => {\n const head = await skillHead(fetcher, owner, repo, ref, candidate.dir)\n return {\n name: head.name || candidate.name,\n description: head.description,\n path: candidate.rel,\n url: `https://github.com/${owner}/${repo}/blob/${ref}/${candidate.dir ? `${candidate.dir}/` : ''}SKILL.md`,\n install: { source: spec, rel: candidate.rel },\n }\n })\n return { input, source: info, skills, ...(tree.truncated ? { truncated: true } : {}) }\n}\n\n/** The skills a pack's README links to, in README order. */\nasync function packSkills(fetcher: Fetcher, owner: string, repo: string, ref: string, readme: string): Promise<SkillSourceSkill[]> {\n const res = await call(fetcher, `${RAW}/${owner}/${repo}/${ref}/${readme}`, fetcher.token ? { headers: { Authorization: `token ${fetcher.token}` } } : {})\n if (!res.ok)\n return []\n const links = new Map<string, { owner: string, repo: string, ref: string, folder: string }>()\n for (const match of (await res.text()).matchAll(SKILL_LINK_RE)) {\n const link = { owner: match[1] as string, repo: match[2] as string, ref: match[3] as string, folder: match[4] ?? '' }\n if (link.folder.split('/').includes('..'))\n continue\n links.set(`${link.owner}/${link.repo}/${link.folder}`, link)\n }\n return mapLimit([...links.values()].slice(0, MAX_SKILLS), CONCURRENCY, async (link): Promise<SkillSourceSkill> => {\n const head = await skillHead(fetcher, link.owner, link.repo, link.ref, link.folder)\n const segments = link.folder ? link.folder.split('/') : []\n const name = segments[segments.length - 1] ?? link.repo\n return {\n name: head.name || name,\n description: head.description,\n path: link.folder || '.',\n url: `https://github.com/${link.owner}/${link.repo}/blob/${link.ref}/${link.folder ? `${link.folder}/` : ''}SKILL.md`,\n from: `${link.owner}/${link.repo}`,\n // Rooted at the skill's parent folder: one download serves the skills that sit side by side.\n install: { source: githubSpec(link.owner, link.repo, link.ref, segments.slice(0, -1).join('/')), rel: segments.length > 0 ? name : '.' },\n }\n })\n}\n\n/** The listing of a downloaded source: npm packages, and GitHub when its API fails. */\nasync function browseByDownload(input: string, source: InstallSource, io: InstallIo): Promise<SkillSourceListing> {\n const stage = mkdtempSync(join(tmpdir(), 'zidane-skill-browse-'))\n try {\n const dest = join(stage, 'source')\n mkdirSync(dest)\n try {\n await fetchInstallSource(source, dest, io)\n }\n catch (err) {\n throw new SkillBrowseError('fetch-failed', errorMessage(err))\n }\n let root = dest\n let fallbackName = source.kind === 'github' ? source.repo : source.pkg\n if (source.kind === 'github' && source.subdir) {\n const rooted = resolveSubdirRoot(dest, source.subdir)\n if (!rooted.ok)\n throw new SkillBrowseError('not-found', `\"${source.subdir}\" is not a folder in ${source.owner}/${source.repo}.`)\n root = rooted.dir\n fallbackName = basename(source.subdir)\n }\n const spec = source.kind === 'github'\n ? githubSpec(source.owner, source.repo, source.ref, source.subdir ?? '')\n : `${source.pkg}${source.version ? `@${source.version}` : ''}`\n const skills = await mapLimit(listSkillCandidates(root, fallbackName).slice(0, MAX_SKILLS), CONCURRENCY, async (candidate): Promise<SkillSourceSkill> => {\n const parsed = await parseSkillFile(join(candidate.dir, 'SKILL.md')).catch(() => null)\n return { name: parsed?.name || candidate.name, description: parsed?.description.trim() ?? '', path: candidate.rel, install: { source: spec, rel: candidate.rel } }\n })\n const info: SkillSourceInfo = source.kind === 'github'\n ? {\n kind: 'github',\n label: source.subdir ? `${source.owner}/${source.repo}/${source.subdir}` : `${source.owner}/${source.repo}`,\n owner: source.owner,\n repo: source.repo,\n ...(source.ref ? { ref: source.ref } : {}),\n ...(source.subdir ? { path: source.subdir } : {}),\n url: `https://github.com/${source.owner}/${source.repo}`,\n avatarUrl: githubAvatarUrl(source.owner),\n imageUrl: githubPreviewImageUrl(source.owner, source.repo),\n }\n : { kind: 'npm', label: source.pkg, url: `https://www.npmjs.com/package/${source.pkg}` }\n return { input, source: info, skills }\n }\n finally {\n rmSync(stage, { recursive: true, force: true })\n }\n}\n\n/**\n * Lists the skills of a source, with what installs each one.\n *\n * @param input - A source, as the installer takes it: `owner/repo`, a GitHub URL, `owner/repo/path`, a skills.sh URL, a `skills add` command or an npm package.\n * @param opts - A GitHub token, a signal, and test overrides.\n * @returns The listing. A source without skills gives an empty list, not an error.\n * @example\n * ```ts\n * import { browseSkillSource, installSkillFromSpec } from 'zidane/skills'\n *\n * const listing = await browseSkillSource('https://github.com/cursor/plugins/tree/HEAD/pstack/skills')\n * const tdd = listing.skills.find(skill => skill.name === 'tdd')!\n * await installSkillFromSpec({ source: tdd.install.source, skills: [tdd.install.rel], scope: 'user' })\n * ```\n */\nexport async function browseSkillSource(input: string, opts: BrowseSkillSourceOptions = {}): Promise<SkillSourceListing> {\n const raw = input.trim()\n let spec: ReturnType<typeof parseSkillInstallSpec>\n try {\n spec = parseSkillInstallSpec(raw)\n }\n catch (err) {\n throw new SkillBrowseError('invalid-source', errorMessage(err))\n }\n // A token can see private repositories: its listing is cached for that token alone.\n const key = `${raw}\\u0000${opts.token ? createHash('sha256').update(opts.token).digest('hex') : ''}`\n const hit = opts.fresh ? undefined : cache.get(key)\n if (hit && Date.now() - hit.at < CACHE_TTL_MS)\n return hit.listing\n const io = opts.signal ? abortableInstallIo(opts.io, opts.signal) : (opts.io ?? {})\n const fetcher: Fetcher = { fetch: io.fetch ?? fetch, token: opts.token }\n const { source } = spec\n let listing: SkillSourceListing\n if (source.kind === 'github') {\n try {\n listing = await browseGithub(raw, source, fetcher)\n }\n catch (err) {\n // A rate limit or an unreachable API: the download needs neither.\n if (!(err instanceof SkillBrowseError) || (err.code !== 'rate-limited' && err.code !== 'fetch-failed'))\n throw err\n listing = await browseByDownload(raw, source, io).catch((fallback: unknown) => {\n throw err.code === 'rate-limited' ? err : fallback\n })\n }\n }\n else {\n listing = await browseByDownload(raw, source, io)\n }\n if (spec.skill)\n listing = { ...listing, preselect: [spec.skill] }\n remember(key, listing)\n return listing\n}\n","import type { SkillConfig } from './types'\nimport { validateSkillForWrite } from './validate'\n\nexport {\n type ActivationVia,\n type ActiveSkill,\n createSkillActivationState,\n type DeactivationReason,\n type SkillActivationState,\n type SkillActivationStateOptions,\n} from './activation'\nexport {\n IMPLICITLY_ALLOWED_SKILL_TOOLS,\n installAllowedToolsGate,\n} from './allowed-tools'\nexport { browseSkillSource, type BrowseSkillSourceOptions, clearSkillSourceCache, SkillBrowseError, type SkillBrowseErrorCode } from './browse'\nexport { sanitizeSkillName, SKILL_CANDIDATE_MAX_DEPTH, skillCandidatesFromPaths, type SkillPathCandidate } from './candidates'\nexport { buildCatalog, skillCatalogEntry } from './catalog'\nexport {\n discoverSkills,\n getDefaultScanPaths,\n inferSource,\n parseFrontmatter,\n parseSkillContent,\n parseSkillFile,\n} from './discovery'\nexport type { SourcedScanPath } from './discovery'\nexport {\n installSkillFromSpec,\n listSkillCandidates,\n parseSkillInstallSpec,\n readSkillProvenance,\n SKILL_PROVENANCE_FILE,\n SkillInstallError,\n} from './install'\nexport type {\n SkillCandidateDetail,\n SkillInstallCandidate,\n SkillInstallErrorCode,\n SkillInstallOptions,\n SkillInstallResult,\n SkillInstallSpec,\n} from './install'\nexport { interpolateShellCommands, stripShellInterpolations } from './interpolate'\nexport type { InterpolateShellCommandsOptions } from './interpolate'\nexport { resolveSkills } from './resolve'\nexport {\n githubAvatarUrl,\n githubPreviewImageUrl,\n groupSkillPicks,\n RECOMMENDED_SKILL_SOURCES,\n type RecommendedSkillSource,\n skillCategory,\n type SkillInstallTarget,\n type SkillPick,\n type SkillPickGroup,\n skillPickKey,\n skillPickOf,\n type SkillSourceInfo,\n type SkillSourceListing,\n type SkillSourceSkill,\n} from './sources'\nexport type {\n ShellInterpolationApproval,\n SkillConfig,\n SkillDiagnostic,\n SkillResource,\n SkillsConfig,\n SkillSource,\n} from './types'\nexport {\n isToolAllowedByUnion,\n matchesAllowedTool,\n parseAllowedToolPattern,\n validateResourcePath,\n validateResourcePathReal,\n validateSkillForWrite,\n validateSkillName,\n} from './validate'\nexport type { SkillValidationIssue, SkillValidationResult } from './validate'\nexport { skillTokenWeights, type SkillTokenWeights } from './weights'\nexport { writeSkillsToDisk, writeSkillToDisk } from './writer'\n\n// ---------------------------------------------------------------------------\n// defineSkill helper\n// ---------------------------------------------------------------------------\n\n/**\n * Define an inline skill configuration.\n *\n * Strict-validates at author-time — throws if the config would be rejected by\n * `writeSkillToDisk` or the spec. Source defaults to `'inline'`.\n * @param config - Skill fields. `source` defaults to `'inline'`.\n * @example\n * ```ts\n * import { defineSkill } from 'zidane/skills'\n *\n * const skill = defineSkill({\n * name: 'release-notes',\n * description: 'Drafts release notes from merged pull requests.',\n * instructions: 'List merged PRs since the last tag and group them by area.',\n * })\n * ```\n */\nexport function defineSkill(config: Omit<SkillConfig, 'source'> & { source?: SkillConfig['source'] }): SkillConfig {\n const normalized: SkillConfig = {\n ...config,\n source: config.source ?? 'inline',\n }\n const result = validateSkillForWrite(normalized)\n if (!result.valid) {\n const summary = result.errors.map(e => ` - [${e.code}] ${e.message}`).join('\\n')\n throw new Error(`Invalid skill \"${normalized.name}\":\\n${summary}`)\n }\n return normalized\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"skills-CEbosUlM.js","names":["sanitizeName"],"sources":["../src/skills/install.ts","../src/skills/browse.ts","../src/skills/index.ts"],"sourcesContent":["/**\n * Skill installation — fetch a skill (or a collection of skills) from\n * GitHub / npm / skills.sh and land it under the zidane skills root,\n * where the existing discovery scan picks it up.\n *\n * Built on the shared install layer (`src/install/source.ts`), so the\n * spec grammar, transports, download caps, and subdir containment are\n * byte-identical with the extensions installer. What differs:\n *\n * - **Validation never executes code.** A skill is data: candidates\n * are validated by parsing `SKILL.md` (lenient parser, same one\n * discovery uses) — unlike extensions, whose validation must import\n * the module. Installing a hostile skill can plant instructions,\n * never run a process.\n * - **skills.sh interop, two-way.** The spec grammar accepts pasted\n * `https://skills.sh/<owner>/<repo>[/<skill>]` catalog URLs (mapped\n * to the GitHub repo + a skill selector — skills.sh uses GitHub as\n * its registry). And when the `skills` CLI is on PATH,\n * {@link installSkillFromSpec} delegates to it (`skills add … -y`)\n * so installs land in the user's cross-agent `.agents/skills/`\n * layout and feed the skills.sh lockfile/leaderboard; without the\n * CLI the native fetch path installs the same repos directly.\n * - **Target roots**: `~/.zidane/skills/<name>` (user) or\n * `<gitRoot>/.zidane/skills/<name>` (project) — both already on\n * discovery's default scan paths.\n *\n * Same hardening as extensions: staged `mkdtemp` fetch, physical\n * symlink containment for `:subdir`, folder-safe names, overwrite with\n * backup/rollback, and a provenance file (`.zidane-install.json`) that\n * records the spec so it can be replayed (see `readSkillProvenance`).\n */\n\nimport type { InstallIo, InstallScope, InstallSource } from '../install/source'\nimport { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { basename, join, resolve } from 'node:path'\nimport { projectUserPaths } from '../chat/project-user-paths'\nimport { errorMessage } from '../errors'\nimport { defaultExec, defaultWhich, fetchInstallSource, InstallSourceError, lastLine, parseInstallSource, resolveSubdirRoot } from '../install/source'\nimport { sanitizeSkillName as sanitizeName, SKILL_CANDIDATE_MAX_DEPTH } from './candidates'\nimport { parseSkillFile } from './discovery'\nimport { validateSkillName } from './validate'\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/** Machine-readable reason for a {@link SkillInstallError}. */\nexport type SkillInstallErrorCode\n = | 'invalid-source'\n | 'fetch-failed'\n | 'no-skill'\n | 'multiple-candidates'\n | 'validate-failed'\n | 'exists'\n | 'cli-failed'\n | 'io-failed'\n\n/** Error thrown when a skill install fails. `code` tells why. */\nexport class SkillInstallError extends InstallSourceError {\n /** Reason for the failure. See {@link SkillInstallErrorCode}. */\n declare readonly code: SkillInstallErrorCode\n /**\n * Frontmatter metadata per candidate, parallel to `candidates` — set on\n * the selector-less `multiple-candidates` error so host pickers can show\n * descriptions without re-fetching the source. Best-effort: candidates\n * whose SKILL.md fails to parse appear with no description.\n */\n readonly details?: readonly SkillCandidateDetail[]\n /**\n * Creates the error.\n *\n * @param code - Reason for the failure.\n * @param message - Human-readable description.\n * @param candidates - Root-relative paths of the skills found, set on `multiple-candidates`.\n * @param details - Metadata per candidate, parallel to `candidates`.\n */\n constructor(code: SkillInstallErrorCode, message: string, candidates?: readonly string[], details?: readonly SkillCandidateDetail[]) {\n super(code, message, candidates)\n this.name = 'SkillInstallError'\n if (details)\n this.details = details\n }\n}\n\n/** One pickable candidate surfaced to host UIs — see {@link SkillInstallError.details}. */\nexport interface SkillCandidateDetail {\n /** Root-relative path — the unambiguous selector (`skills` option). */\n rel: string\n /** Frontmatter description, when the candidate's SKILL.md parses. */\n description?: string\n}\n\nfunction rethrowAsSkillError(err: unknown): never {\n if (err instanceof InstallSourceError && !(err instanceof SkillInstallError))\n throw new SkillInstallError(err.code as SkillInstallErrorCode, err.message, err.candidates)\n throw err\n}\n\n// ---------------------------------------------------------------------------\n// Spec parsing (shared grammar + the skills.sh catalog URL form)\n// ---------------------------------------------------------------------------\n\nconst SKILLS_SH_URL_RE = /^(?:https?:\\/\\/)?(?:www\\.)?skills\\.sh\\/([\\w.-]+)\\/([\\w.-]+)(?:\\/([\\w.-]+))?\\/?$/\n\n/**\n * The skills CLI's deep shorthand, `owner/repo/path/to/folder` (as in\n * skill packs: `cursor/plugins/pstack/skills/unslop`): a GitHub repo with\n * the rest as its subfolder. The shared grammar spells that `owner/repo:path`.\n */\nconst DEEP_SHORTHAND_RE = /^([\\w.-]+)\\/([\\w.-]+)\\/([\\w.-]+(?:\\/[\\w.-]+)*)\\/?$/\n\n/**\n * Pasted install commands, as printed by skill READMEs and skills.sh\n * pages — `npx skills add owner/repo`, `bunx skills add …`,\n * `pnpm dlx skills add … --skill x`, or a bare `skills add …`. The\n * runner + `add` verb are stripped and the remaining tokens re-enter\n * the normal grammar, so users can copy a whole command into the\n * install input verbatim.\n */\nconst SKILLS_CLI_COMMAND_RE = /^(?:(?:npx|bunx|yarn\\s+dlx|pnpm\\s+dlx)\\s+)?skills(?:@\\S+)?\\s+add\\s+(\\S.*)$/i\n\n/** A parsed skill install source, ready for the install pipeline. */\nexport interface SkillInstallSpec {\n /** Where the skill comes from: a GitHub repository, an npm package or another supported source. */\n source: InstallSource\n /**\n * Skill name selector for multi-skill sources. From a skills.sh URL's\n * third segment or the caller's explicit pick after a\n * `multiple-candidates` error.\n */\n skill?: string\n /**\n * The spec string to hand the `skills` CLI (its `add` argument).\n * skills.sh URLs pass through verbatim — the CLI understands them —\n * while parsed shorthand re-serializes to `owner/repo`.\n */\n cliSpec: string\n}\n\n/**\n * Parse a raw skill source. Accepts everything the shared grammar does\n * (`owner/repo[#ref][:subdir]`, GitHub URLs, `pkg@version`, …) plus\n * pasted skills.sh catalog URLs — `skills.sh/vercel-labs/agent-skills/\n * react-best-practices` maps to the backing GitHub repo with the third\n * segment as the skill selector.\n * @param raw - Source text: shorthand, a URL, a skills.sh URL or a pasted `skills add` command.\n * @returns The parsed source, optional skill selector and CLI spec. Throws {@link SkillInstallError} with `invalid-source` when the text cannot be parsed.\n */\nexport function parseSkillInstallSpec(raw: string): SkillInstallSpec {\n const spec = raw.trim()\n const cliCommand = SKILLS_CLI_COMMAND_RE.exec(spec)\n if (cliCommand)\n return parseCliAddCommand(cliCommand[1] as string)\n const skillsSh = SKILLS_SH_URL_RE.exec(spec)\n if (skillsSh) {\n return {\n source: { kind: 'github', owner: skillsSh[1] as string, repo: skillsSh[2] as string },\n ...(skillsSh[3] ? { skill: skillsSh[3] } : {}),\n cliSpec: `${skillsSh[1]}/${skillsSh[2]}`,\n }\n }\n const deep = /^(?:www\\.)?github\\.com\\//i.test(spec) ? null : DEEP_SHORTHAND_RE.exec(spec)\n if (deep && (deep[3] as string).split('/').every(segment => segment !== '.' && segment !== '..'))\n return { source: { kind: 'github', owner: deep[1] as string, repo: deep[2] as string, subdir: deep[3] as string }, cliSpec: spec }\n try {\n const source = parseInstallSource(spec)\n return { source, cliSpec: spec }\n }\n catch (err) {\n rethrowAsSkillError(err)\n }\n}\n\n/**\n * Parse the argument tail of a pasted `skills add …` command. Honors\n * `--skill <name>` / `--skill=<name>` (their per-skill selector — an\n * explicit selector in the URL/shorthand form wins over it), ignores\n * flags that are the dialog's job here (`-y`, `-g`, `--global`, …),\n * and routes the first non-flag token back through\n * {@link parseSkillInstallSpec} so URLs and shorthands inside the\n * command parse identically to a bare paste.\n */\nfunction parseCliAddCommand(rest: string): SkillInstallSpec {\n const tokens = rest.split(/\\s+/).filter(t => t.length > 0)\n let sourceToken: string | undefined\n let skill: string | undefined\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i] as string\n if (token === '--skill' || token === '-s') {\n skill = tokens[i + 1]\n i += 1\n continue\n }\n const inline = /^--skill=(.+)$/.exec(token)\n if (inline) {\n skill = inline[1] as string\n continue\n }\n if (token.startsWith('-'))\n continue\n if (sourceToken === undefined)\n sourceToken = token\n }\n if (sourceToken === undefined)\n throw new SkillInstallError('invalid-source', 'The pasted command has no source. Expected “skills add <owner/repo>”.')\n const parsed = parseSkillInstallSpec(sourceToken)\n return { ...parsed, ...(skill !== undefined && parsed.skill === undefined ? { skill } : {}) }\n}\n\n// ---------------------------------------------------------------------------\n// Candidate enumeration + validation (data-only — no code execution)\n// ---------------------------------------------------------------------------\n\n/** One installable skill found in a fetched source tree. */\nexport interface SkillInstallCandidate {\n /** Directory name candidate (validated against SKILL.md frontmatter later). */\n name: string\n /** Absolute path of the skill directory in the fetched tree. */\n dir: string\n /**\n * Root-relative path (`engineering/tdd`) — the unambiguous selector\n * when the same skill NAME exists in several category directories.\n */\n rel: string\n}\n\n/**\n * How deep the candidate walk descends. Real-world collections nest\n * skills under category directories (`skills/engineering/tdd/SKILL.md`\n * in mattpocock/skills; `.github/skills/…` elsewhere), which a one-level\n * scan would miss. Four levels covers the layouts seen so far while\n * keeping the walk bounded on the staged tree.\n */\nconst CANDIDATE_WALK_MAX_DEPTH = SKILL_CANDIDATE_MAX_DEPTH\n\n/**\n * List installable skills inside a fetched tree.\n *\n * A skill is ANY directory containing a `SKILL.md` — found by a\n * bounded recursive walk (depth {@link CANDIDATE_WALK_MAX_DEPTH}),\n * skipping dot-dirs and `node_modules`, and never descending INTO a\n * skill (its `references/` / `scripts/` subtree ships with it, it\n * doesn't nest further candidates). A root-level `SKILL.md` means\n * \"repo IS the skill\" and short-circuits, named from the fallback.\n *\n * Candidates carry their root-relative path (`rel`) so same-named\n * skills in different categories stay individually addressable.\n * Sorted by `rel`.\n * @param dir - Root of the fetched source tree.\n * @param fallbackName - Name used when the root itself holds a `SKILL.md`.\n */\nexport function listSkillCandidates(dir: string, fallbackName: string): SkillInstallCandidate[] {\n if (existsSync(join(dir, 'SKILL.md')))\n return [{ name: sanitizeName(fallbackName), dir, rel: '.' }]\n const out: SkillInstallCandidate[] = []\n const walk = (current: string, rel: string, depth: number): void => {\n if (depth > CANDIDATE_WALK_MAX_DEPTH)\n return\n for (const entry of readdirSync(current, { withFileTypes: true })) {\n if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules')\n continue\n const childDir = join(current, entry.name)\n const childRel = rel.length === 0 ? entry.name : `${rel}/${entry.name}`\n if (existsSync(join(childDir, 'SKILL.md')))\n out.push({ name: entry.name, dir: childDir, rel: childRel })\n else\n walk(childDir, childRel, depth + 1)\n }\n }\n walk(dir, '', 1)\n return out.sort((a, b) => a.rel.localeCompare(b.rel))\n}\n\n/**\n * Frontmatter details for a host picker, parallel to the candidate list.\n * Parsing is best-effort per candidate — a malformed SKILL.md yields a\n * detail row without a description rather than failing the enumeration.\n */\nasync function candidateDetails(candidates: readonly SkillInstallCandidate[]): Promise<SkillCandidateDetail[]> {\n return Promise.all(candidates.map(async (c) => {\n const parsed = await parseSkillFile(join(c.dir, 'SKILL.md')).catch(() => null)\n const description = parsed?.description.trim()\n return { rel: c.rel, ...(description ? { description } : {}) }\n }))\n}\n\n/** Human-readable candidate list for error messages — capped, rel-qualified when ambiguous. */\nfunction describeCandidates(candidates: readonly SkillInstallCandidate[]): string {\n const nameCounts = new Map<string, number>()\n for (const c of candidates)\n nameCounts.set(c.name, (nameCounts.get(c.name) ?? 0) + 1)\n const labels = candidates.map(c => ((nameCounts.get(c.name) ?? 0) > 1 ? c.rel : c.name))\n const shown = labels.slice(0, 20)\n const more = labels.length - shown.length\n return shown.join(', ') + (more > 0 ? `, … and ${more} more` : '')\n}\n\n/**\n * Validate a staged candidate by PARSING its SKILL.md — the same lenient\n * parser discovery runs, so \"installs cleanly\" and \"discovers cleanly\"\n * are the same predicate. Returns the frontmatter name (which wins over\n * the directory-derived candidate name for the target folder).\n */\nasync function validateSkillCandidate(candidate: SkillInstallCandidate): Promise<{ name: string }> {\n const parsed = await parseSkillFile(join(candidate.dir, 'SKILL.md'))\n if (!parsed)\n throw new SkillInstallError('validate-failed', `${candidate.dir}/SKILL.md is not a valid skill (missing name or description in frontmatter).`)\n const name = parsed.name\n if (!validateSkillName(name))\n throw new SkillInstallError('validate-failed', `Skill name \"${name}\" is invalid (lowercase alphanumerics + single hyphens, max 64 chars).`)\n return { name }\n}\n\n// ---------------------------------------------------------------------------\n// Install\n// ---------------------------------------------------------------------------\n\n/** Options for {@link installSkillFromSpec}. */\nexport interface SkillInstallOptions {\n /** Raw source spec — see {@link parseSkillInstallSpec} for the grammar. */\n source: string\n /** Install into the `project` or the `user` skills root. */\n scope: InstallScope\n /** Pick one skill from a multi-skill source (names from the `multiple-candidates` error). */\n skill?: string\n /**\n * Install EVERY skill a multi-skill source contains (the affirmative\n * answer to the host's \"import all?\" prompt after a\n * `multiple-candidates` error). Native engine only — already-installed\n * names are skipped (reported via `SkillInstallResult.skipped`) rather\n * than failing the batch, unless `overwrite` is also set. Overrides a\n * selector embedded in the source string; ignored when an explicit\n * `skill` option is present.\n */\n all?: boolean\n /**\n * Install a picked SUBSET of a multi-skill source — the host's checkbox\n * picker over the `multiple-candidates` list. Entries match a skill's\n * name or its root-relative path (same rules as `skill`); duplicates\n * collapse. Batch semantics mirror `all` (skip existing, pre-validate,\n * native engine). Takes precedence over `all` and over a selector\n * embedded in the source string; ignored when an explicit `skill`\n * option is present.\n */\n skills?: readonly string[]\n /** Overwrite an existing install of the same name. */\n overwrite?: boolean\n /** Working directory that locates the project scope. Default: the process working directory. */\n cwd?: string\n /** Home directory that locates the user scope. Default: the current user home. */\n home?: string\n /** Storage prefix (default `.zidane`) — mirrors `ResolvedConfig.prefix`. */\n prefix?: string\n /** Overrides for `fetch`, command execution and `which`. Used by tests and sandboxed hosts. */\n io?: InstallIo\n /** Called with a short status message as the install proceeds. */\n onProgress?: (message: string) => void\n /**\n * Prefer the skills.sh CLI when `skills` is on PATH (default true).\n * The CLI owns scope + layout decisions (`.agents/skills/`, their\n * lockfile); the native path is the fallback and the forced path in\n * tests. Set false to always install natively.\n */\n preferSkillsCli?: boolean\n}\n\n/** Outcome of {@link installSkillFromSpec}. */\nexport interface SkillInstallResult {\n /** Final skill name(s) installed. The CLI path reports the spec, not names. */\n installed: readonly string[]\n /** Names skipped during an `all` import because they were already installed. */\n skipped?: readonly string[]\n /** Candidate paths excluded from an `all` import because they failed validation. */\n failed?: readonly string[]\n /** Absolute target directory (native path only — the CLI owns its layout). */\n dir?: string\n /** `skills-cli` when delegated to the skills.sh CLI, else `native`. */\n engine: 'skills-cli' | 'native'\n /**\n * Where the skill came from, for example `github:owner/repo@main` or `npm:pkg@1.2.3`. Set on the native path.\n */\n provenance?: string\n}\n\n/**\n * Install a skill (or one skill out of a multi-skill source).\n *\n * Engine selection: targeted installs (an explicit skill selector) go\n * through the skills.sh CLI when present on PATH (unless\n * `preferSkillsCli: false`) — keeping their cross-agent layout and\n * lockfile — else the native fetch pipeline. Selector-less installs are\n * ALWAYS native: only native enumeration can tell single- from\n * multi-skill sources, and the host's \"import all?\" prompt rides on the\n * `multiple-candidates` error — delegating them to `skills add … -y`\n * would let the CLI's own multi-skill behavior silently decide instead.\n * `all: true` batches are native for the same reason (skip-existing +\n * per-skill provenance are our semantics).\n * @param opts - Source, scope and install options. See {@link SkillInstallOptions}.\n */\nexport async function installSkillFromSpec(opts: SkillInstallOptions): Promise<SkillInstallResult> {\n const io = opts.io ?? {}\n const which = io.which ?? defaultWhich\n const spec = parseSkillInstallSpec(opts.source)\n\n // Batch modes (`all` / `skills` subset) are always native — see JSDoc.\n // They also override a selector derived from the SOURCE string (a\n // skills.sh URL's third segment, a pasted `--skill`): the host's picker\n // re-runs with the same source after an ambiguous-selector error, and\n // keeping the spec selector would just re-throw the same error in a\n // loop. An explicit `opts.skill` still wins over everything.\n const batchMode = opts.all === true || (opts.skills?.length ?? 0) > 0\n const skillSelector = opts.skill ?? (batchMode ? undefined : spec.skill)\n if (!batchMode && skillSelector !== undefined && (opts.preferSkillsCli ?? true) && which('skills'))\n return await installViaSkillsCli(spec, skillSelector, opts)\n\n return await installNatively(spec, skillSelector, opts)\n}\n\n/**\n * Delegate to the skills.sh CLI. Non-interactive by construction: `-y`\n * skips confirmation prompts, `-g` maps our `user` scope to their\n * global scope (project scope is their default, keyed off cwd). The CLI\n * decides the on-disk layout (`.agents/skills/`), which zidane's\n * discovery already scans.\n */\nasync function installViaSkillsCli(\n spec: SkillInstallSpec,\n skillSelector: string | undefined,\n opts: SkillInstallOptions,\n): Promise<SkillInstallResult> {\n const exec = opts.io?.exec ?? defaultExec\n const args = ['add', spec.cliSpec, '-y']\n if (skillSelector)\n args.push('--skill', skillSelector)\n if (opts.scope === 'user')\n args.push('-g')\n opts.onProgress?.(`skills add ${spec.cliSpec}${skillSelector ? ` --skill ${skillSelector}` : ''}…`)\n const res = await exec('skills', args, { cwd: opts.cwd ?? process.cwd() })\n if (!res.ok)\n throw new SkillInstallError('cli-failed', `skills.sh CLI failed: ${lastLine(res.stderr) || lastLine(res.stdout) || 'unknown error'}`)\n return {\n installed: [skillSelector ?? spec.cliSpec],\n engine: 'skills-cli',\n }\n}\n\nasync function installNatively(\n spec: SkillInstallSpec,\n skillSelector: string | undefined,\n opts: SkillInstallOptions,\n): Promise<SkillInstallResult> {\n const io = opts.io ?? {}\n const source = spec.source\n const stage = mkdtempSync(join(tmpdir(), 'zidane-skill-install-'))\n try {\n const fetchedInto = join(stage, 'fetched')\n mkdirSync(fetchedInto, { recursive: true })\n let provenance: string\n try {\n provenance = (await fetchInstallSource(source, fetchedInto, io, opts.onProgress)).provenance\n }\n catch (err) {\n rethrowAsSkillError(err)\n }\n\n // `:subdir` re-roots the search — same physical containment as\n // extensions (hostile symlinks must not walk out of the stage).\n let candidateRoot = fetchedInto\n let fallbackName = source.kind === 'github' ? source.repo : source.pkg\n if (source.kind === 'github' && source.subdir) {\n const rooted = resolveSubdirRoot(fetchedInto, source.subdir)\n if (!rooted.ok) {\n if (rooted.reason === 'missing')\n throw new SkillInstallError('no-skill', `\"${source.subdir}\" is not a directory in ${source.owner}/${source.repo}.`)\n throw new SkillInstallError('invalid-source', `Subdirectory \"${source.subdir}\" escapes the repository root${rooted.reason === 'escape-symlink' ? ' through a symlink' : ''}.`)\n }\n candidateRoot = rooted.dir\n fallbackName = basename(resolve(fetchedInto, source.subdir))\n }\n\n const candidates = listSkillCandidates(candidateRoot, fallbackName)\n if (candidates.length === 0)\n throw new SkillInstallError('no-skill', `No skill found in ${opts.source}: no SKILL.md in the tree (searched ${CANDIDATE_WALK_MAX_DEPTH} levels deep).`)\n let picked: SkillInstallCandidate[]\n if (!skillSelector && opts.skills && opts.skills.length > 0) {\n // Host-picked subset (checkbox picker over the `multiple-candidates`\n // list). Each entry resolves with the same name-or-rel-path rules as\n // the single selector; duplicate picks collapse to one install.\n const byRel = new Map<string, SkillInstallCandidate>()\n for (const sel of opts.skills) {\n const matches = candidates.filter(c => c.name === sel || c.rel === sel)\n if (matches.length === 0)\n throw new SkillInstallError('no-skill', `No skill named \"${sel}\" in ${opts.source}. Available: ${describeCandidates(candidates)}`, candidates.map(c => c.rel))\n if (matches.length > 1)\n throw new SkillInstallError('multiple-candidates', `\"${sel}\" matches several skills in ${opts.source}. Pick a path: ${matches.map(c => c.rel).join(', ')}`, matches.map(c => c.rel))\n const match = matches[0] as SkillInstallCandidate\n byRel.set(match.rel, match)\n }\n picked = [...byRel.values()]\n }\n else if (skillSelector) {\n // Selector matches the skill name OR its root-relative path — the\n // path form disambiguates same-named skills in different category\n // directories (`engineering/tdd` vs `personal/tdd`).\n const matches = candidates.filter(c => c.name === skillSelector || c.rel === skillSelector)\n if (matches.length === 0)\n throw new SkillInstallError('no-skill', `No skill named \"${skillSelector}\" in ${opts.source}. Available: ${describeCandidates(candidates)}`, candidates.map(c => c.rel))\n if (matches.length > 1)\n throw new SkillInstallError('multiple-candidates', `\"${skillSelector}\" matches several skills in ${opts.source}. Pick a path: ${matches.map(c => c.rel).join(', ')}`, matches.map(c => c.rel))\n picked = [matches[0] as SkillInstallCandidate]\n }\n else if (candidates.length > 1 && !opts.all) {\n throw new SkillInstallError(\n 'multiple-candidates',\n `${opts.source} contains ${candidates.length} skills. Pick one: ${describeCandidates(candidates)}`,\n candidates.map(c => c.rel),\n await candidateDetails(candidates),\n )\n }\n else {\n picked = [...candidates]\n }\n\n const root = scopeRoot(opts)\n const installed: string[] = []\n const skipped: string[] = []\n const failed: string[] = []\n let lastDir: string | undefined\n const batch = picked.length > 1\n\n // Validate the whole batch BEFORE installing anything — one malformed\n // candidate must not abort a half-applied import. Batch mode collects\n // failures (reported via `SkillInstallResult.failed`) and installs the\n // rest; a targeted single install keeps the hard throw.\n const resolved: Array<{ candidate: SkillInstallCandidate, name: string }> = []\n // Intra-batch name conflicts: two candidates whose frontmatter\n // validates to the same name (`engineering/tdd` + `personal/tdd`)\n // would otherwise install the first and mislabel the second as\n // \"already installed\" (or, with overwrite, silently last-wins).\n // First occurrence wins; later ones are reported as conflicts.\n const nameToRel = new Map<string, string>()\n for (const candidate of picked) {\n try {\n // Frontmatter name wins over the directory-derived candidate name —\n // mirrors \"target folder is named from the VALIDATED definition\".\n const { name } = await validateSkillCandidate(candidate)\n const conflict = nameToRel.get(name)\n if (conflict !== undefined) {\n // Only reachable in batch mode — a single pick can't conflict.\n failed.push(candidate.rel)\n opts.onProgress?.(`skipped \"${candidate.rel}\": the name \"${name}\" conflicts with \"${conflict}\"`)\n continue\n }\n nameToRel.set(name, candidate.rel)\n resolved.push({ candidate, name })\n }\n catch (err) {\n if (!batch)\n throw err\n failed.push(candidate.rel)\n opts.onProgress?.(`skipped \"${candidate.rel}\": ${errorMessage(err)}`)\n }\n }\n\n for (const { candidate, name } of resolved) {\n const targetDir = join(root, name)\n const backupDir = `${targetDir}.zidane-backup`\n if (existsSync(targetDir) && !opts.overwrite) {\n // Batch imports skip collisions (already-installed skills are the\n // common case when re-importing a repo); a targeted single install\n // keeps the hard error so the overwrite decision stays explicit.\n if (batch) {\n skipped.push(name)\n opts.onProgress?.(`skipped \"${name}\": already installed`)\n continue\n }\n throw new SkillInstallError('exists', `Skill \"${name}\" is already installed at ${targetDir}. Overwrite to replace it.`)\n }\n\n mkdirSync(root, { recursive: true })\n const hadPrevious = existsSync(targetDir)\n if (hadPrevious) {\n rmSync(backupDir, { recursive: true, force: true })\n renameSync(targetDir, backupDir)\n }\n try {\n // `dereference` materializes in-tree symlinked files so the install\n // has no dangling links once the stage is deleted.\n cpSync(candidate.dir, targetDir, { recursive: true, dereference: true })\n writeProvenance(targetDir, { spec: opts.source, ...(skillSelector ? { skill: skillSelector } : {}), scope: opts.scope, provenance, installedAt: Date.now() })\n }\n catch (err) {\n rmSync(targetDir, { recursive: true, force: true })\n if (hadPrevious)\n renameSync(backupDir, targetDir)\n throw new SkillInstallError('io-failed', `Writing ${targetDir} failed: ${errorMessage(err)}`)\n }\n rmSync(backupDir, { recursive: true, force: true })\n installed.push(name)\n lastDir = targetDir\n if (batch)\n opts.onProgress?.(`installed \"${name}\" (${installed.length}/${resolved.length})`)\n }\n\n if (installed.length === 0 && skipped.length > 0)\n throw new SkillInstallError('exists', `All ${skipped.length} installable skills from ${opts.source} are already installed: ${skipped.join(', ')}. Overwrite to replace them.`)\n if (installed.length === 0 && failed.length > 0)\n throw new SkillInstallError('validate-failed', `No skill from ${opts.source} could be installed. Invalid: ${failed.join(', ')}.`)\n\n return {\n installed,\n ...(skipped.length > 0 ? { skipped } : {}),\n ...(failed.length > 0 ? { failed } : {}),\n ...(lastDir !== undefined ? { dir: lastDir } : {}),\n engine: 'native',\n provenance,\n }\n }\n finally {\n rmSync(stage, { recursive: true, force: true })\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Provenance sidecar file written into each natively installed skill directory. It records the spec so the install can be replayed. */\nexport const SKILL_PROVENANCE_FILE = '.zidane-install.json'\n\ninterface SkillProvenance {\n spec: string\n skill?: string\n scope: InstallScope\n provenance: string\n installedAt: number\n}\n\nfunction writeProvenance(dir: string, data: SkillProvenance): void {\n writeFileSync(join(dir, SKILL_PROVENANCE_FILE), `${JSON.stringify(data, null, 2)}\\n`)\n}\n\n/**\n * Read a skill directory's install provenance, `null` for hand-authored skills.\n * @param dir - Skill directory.\n */\nexport function readSkillProvenance(dir: string): (SkillProvenance & { spec: string }) | null {\n try {\n const raw = JSON.parse(readFileSync(join(dir, SKILL_PROVENANCE_FILE), 'utf8')) as Partial<SkillProvenance>\n return typeof raw.spec === 'string' && raw.spec.length > 0 ? (raw as SkillProvenance) : null\n }\n catch {\n return null\n }\n}\n\n/**\n * Native-install target root: the `<prefix>/skills` flavor of the\n * project/user convention (`~/.zidane/skills`, `<gitRoot>/.zidane/skills`).\n * The `.agents/skills` siblings stay read-only for us — they belong to\n * the cross-agent ecosystem (and the skills.sh CLI writes there).\n */\nfunction scopeRoot(opts: SkillInstallOptions): string {\n const roots = projectUserPaths({\n subPath: 'skills',\n ...(opts.cwd ? { cwd: opts.cwd } : {}),\n ...(opts.home ? { home: opts.home } : {}),\n ...(opts.prefix ? { prefix: opts.prefix } : {}),\n })\n const root = roots.find(r => r.source === opts.scope && r.path.includes(`.${(opts.prefix ?? '.zidane').replace(/^\\./, '')}`))\n if (!root)\n throw new SkillInstallError('io-failed', `No ${opts.scope} skills root resolved.`)\n return root.path\n}\n","/**\n * Lists the skills of a source before an install, so an app can show them\n * and let the user pick. It takes everything the installer takes\n * (`parseSkillInstallSpec`): `owner/repo`, GitHub URLs with\n * `/tree/<ref>/<path>`, `owner/repo/path`, skills.sh URLs, pasted\n * `npx skills add …` commands and npm packages.\n *\n * - GitHub: two API calls (the repository and its file tree), then the\n * head of each SKILL.md from raw.githubusercontent, eight at a time.\n * The candidate rules are the installer's (`skillCandidatesFromPaths`),\n * so every listed skill installs. A token raises GitHub's limit from\n * 60 to 5,000 calls an hour.\n * - A pack: a repository with no SKILL.md whose README links SKILL.md\n * files in other repositories (antfu/skills-pack). Each link is a skill.\n * - npm, or a GitHub API error (a rate limit, no network to the API):\n * the installer's own download, then the same folder walk.\n *\n * Nothing runs: a skill is data. Listings stay cached in memory for ten\n * minutes. Each skill carries the spec that installs it\n * (`installSkillFromSpec` with `skills: [rel]`).\n */\nimport type { InstallIo, InstallSource } from '../install/source'\nimport type { SkillSourceInfo, SkillSourceListing, SkillSourceSkill } from './sources'\nimport { createHash } from 'node:crypto'\nimport { mkdirSync, mkdtempSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { basename, join } from 'node:path'\nimport { errorMessage } from '../errors'\nimport { abortableInstallIo, fetchInstallSource, resolveSubdirRoot } from '../install/source'\nimport { skillCandidatesFromPaths } from './candidates'\nimport { parseFrontmatter, parseSkillFile } from './discovery'\nimport { listSkillCandidates, parseSkillInstallSpec } from './install'\nimport { githubAvatarUrl, githubPreviewImageUrl } from './sources'\n\n/**\n * Why a listing failed.\n *\n * - `invalid-source`: the text is not a source.\n * - `not-found`: no such repository, folder or package, or no access to it.\n * - `rate-limited`: GitHub refused more calls without a token, and the download failed too.\n * - `fetch-failed`: the network or the download failed.\n */\nexport type SkillBrowseErrorCode = 'invalid-source' | 'not-found' | 'rate-limited' | 'fetch-failed'\n\n/** A listing that failed, with the reason in `code`. */\nexport class SkillBrowseError extends Error {\n /** Why it failed. */\n readonly code: SkillBrowseErrorCode\n /**\n * @param code - Why the listing failed.\n * @param message - What failed, in a sentence a person can read.\n */\n constructor(code: SkillBrowseErrorCode, message: string) {\n super(message)\n this.name = 'SkillBrowseError'\n this.code = code\n }\n}\n\n/** Options of {@link browseSkillSource}. */\nexport interface BrowseSkillSourceOptions {\n /** A GitHub token: 5,000 API calls an hour instead of 60, and private repositories. */\n token?: string\n /** Stops the listing. */\n signal?: AbortSignal\n /** Overrides for `fetch`, command execution and `which`. Used by tests. */\n io?: InstallIo\n /** Ignore the cached listing and list again. */\n fresh?: boolean\n}\n\nconst API = 'https://api.github.com'\nconst RAW = 'https://raw.githubusercontent.com'\nconst CACHE_TTL_MS = 10 * 60_000\nconst CACHE_MAX = 24\n/** More skills than any real source holds: a cap, so a hostile tree cannot flood the list. */\nconst MAX_SKILLS = 300\n/** The bytes read from each SKILL.md: its frontmatter fits. */\nconst HEAD_BYTES = 8192\nconst CONCURRENCY = 8\nconst README_RE = /^readme(?:\\.md|\\.markdown)?$/i\n/** A link to a SKILL.md on GitHub: `github.com/<owner>/<repo>/blob/<ref>/<folder>/SKILL.md`. */\nconst SKILL_LINK_RE = /https?:\\/\\/github\\.com\\/([\\w.-]+)\\/([\\w.-]+)\\/blob\\/([\\w.-]+)\\/(?:([\\w./-]+?)\\/)?SKILL\\.md/g\n\nconst cache = new Map<string, { at: number, listing: SkillSourceListing }>()\n\n/** Forgets every cached listing. */\nexport function clearSkillSourceCache(): void {\n cache.clear()\n}\n\nfunction remember(key: string, listing: SkillSourceListing): void {\n cache.delete(key)\n cache.set(key, { at: Date.now(), listing })\n while (cache.size > CACHE_MAX)\n cache.delete(cache.keys().next().value as string)\n}\n\nasync function mapLimit<T, R>(items: readonly T[], limit: number, fn: (item: T) => Promise<R>): Promise<R[]> {\n const out: R[] = Array.from({ length: items.length })\n let next = 0\n const worker = async (): Promise<void> => {\n while (next < items.length) {\n const i = next++\n out[i] = await fn(items[i] as T)\n }\n }\n await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker))\n return out\n}\n\n/** The spec that installs from a GitHub folder: `owner/repo[#ref][:folder]`. */\nfunction githubSpec(owner: string, repo: string, ref: string | undefined, folder: string): string {\n return `${owner}/${repo}${ref && ref !== 'HEAD' ? `#${ref}` : ''}${folder ? `:${folder}` : ''}`\n}\n\nfunction text(value: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\ninterface Fetcher {\n fetch: typeof fetch\n token: string | undefined\n}\n\nasync function call(fetcher: Fetcher, url: string, init: RequestInit = {}): Promise<Response> {\n try {\n return await fetcher.fetch(url, init)\n }\n catch (err) {\n throw new SkillBrowseError('fetch-failed', `The connection to ${new URL(url).host} failed. ${errorMessage(err)}`)\n }\n}\n\nasync function githubJson<T>(fetcher: Fetcher, path: string, what: string): Promise<T> {\n const res = await call(fetcher, `${API}${path}`, {\n headers: {\n 'Accept': 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n ...(fetcher.token ? { Authorization: `Bearer ${fetcher.token}` } : {}),\n },\n })\n if (res.status === 404)\n throw new SkillBrowseError('not-found', `GitHub has no ${what}, or this computer has no access to it.`)\n if (res.status === 429 || (res.status === 403 && res.headers.get('x-ratelimit-remaining') === '0'))\n throw new SkillBrowseError('rate-limited', 'GitHub limits calls without a token. Sign in with gh auth login, or try again later.')\n if (!res.ok)\n throw new SkillBrowseError('fetch-failed', `GitHub answered ${res.status} for ${what}.`)\n return await res.json() as T\n}\n\n/** The name and description of a SKILL.md on GitHub, from its first bytes. */\nasync function skillHead(fetcher: Fetcher, owner: string, repo: string, ref: string, folder: string): Promise<{ name: string, description: string }> {\n const url = `${RAW}/${owner}/${repo}/${ref}/${folder ? `${folder}/` : ''}SKILL.md`\n const auth: Record<string, string> = fetcher.token ? { Authorization: `token ${fetcher.token}` } : {}\n try {\n // Identity: a byte range of a gzip stream does not decompress.\n let res = await call(fetcher, url, { headers: { 'Range': `bytes=0-${HEAD_BYTES - 1}`, 'Accept-Encoding': 'identity', ...auth } })\n let content = res.ok ? await res.text() : ''\n // A frontmatter longer than the head: read the whole file.\n if (res.status === 206 && !/^\\s*---[\\s\\S]*?\\n---/.test(content)) {\n res = await call(fetcher, url, { headers: auth })\n content = res.ok ? await res.text() : ''\n }\n const { frontmatter } = parseFrontmatter(content)\n return { name: text(frontmatter.name), description: text(frontmatter.description) }\n }\n catch {\n // One unreadable SKILL.md costs its description, not the listing.\n return { name: '', description: '' }\n }\n}\n\ninterface GithubRepo {\n description: string | null\n stargazers_count: number\n default_branch: string\n html_url: string\n}\n\ninterface GithubTree {\n tree: { path: string, type: string }[]\n truncated: boolean\n}\n\nasync function browseGithub(input: string, source: Extract<InstallSource, { kind: 'github' }>, fetcher: Fetcher): Promise<SkillSourceListing> {\n const { owner, repo } = source\n const repoInfo = await githubJson<GithubRepo>(fetcher, `/repos/${owner}/${repo}`, `repository ${owner}/${repo}`)\n const ref = source.ref ?? repoInfo.default_branch\n const tree = await githubJson<GithubTree>(fetcher, `/repos/${owner}/${repo}/git/trees/${encodeURIComponent(ref)}?recursive=1`, `branch ${ref} in ${owner}/${repo}`)\n const root = source.subdir ?? ''\n if (root && !tree.tree.some(entry => entry.type === 'tree' && entry.path === root))\n throw new SkillBrowseError('not-found', `\"${root}\" is not a folder in ${owner}/${repo}.`)\n const files = tree.tree.filter(entry => entry.type === 'blob').map(entry => entry.path)\n const info: SkillSourceInfo = {\n kind: 'github',\n label: root ? `${owner}/${repo}/${root}` : `${owner}/${repo}`,\n owner,\n repo,\n ref,\n ...(root ? { path: root } : {}),\n url: root ? `https://github.com/${owner}/${repo}/tree/${ref}/${root}` : repoInfo.html_url,\n ...(repoInfo.description ? { description: repoInfo.description } : {}),\n stars: repoInfo.stargazers_count,\n avatarUrl: githubAvatarUrl(owner),\n imageUrl: githubPreviewImageUrl(owner, repo),\n }\n const candidates = skillCandidatesFromPaths(files, root, root ? basename(root) : repo).slice(0, MAX_SKILLS)\n if (candidates.length === 0 && !root) {\n const readme = files.find(file => README_RE.test(file))\n const pack = readme ? await packSkills(fetcher, owner, repo, ref, readme) : []\n if (pack.length > 0)\n return { input, source: { ...info, collection: true }, skills: pack, ...(tree.truncated ? { truncated: true } : {}) }\n }\n const spec = githubSpec(owner, repo, source.ref, root)\n const skills = await mapLimit(candidates, CONCURRENCY, async (candidate): Promise<SkillSourceSkill> => {\n const head = await skillHead(fetcher, owner, repo, ref, candidate.dir)\n return {\n name: head.name || candidate.name,\n description: head.description,\n path: candidate.rel,\n url: `https://github.com/${owner}/${repo}/blob/${ref}/${candidate.dir ? `${candidate.dir}/` : ''}SKILL.md`,\n install: { source: spec, rel: candidate.rel },\n }\n })\n return { input, source: info, skills, ...(tree.truncated ? { truncated: true } : {}) }\n}\n\n/** The skills a pack's README links to, in README order. */\nasync function packSkills(fetcher: Fetcher, owner: string, repo: string, ref: string, readme: string): Promise<SkillSourceSkill[]> {\n const res = await call(fetcher, `${RAW}/${owner}/${repo}/${ref}/${readme}`, fetcher.token ? { headers: { Authorization: `token ${fetcher.token}` } } : {})\n if (!res.ok)\n return []\n const links = new Map<string, { owner: string, repo: string, ref: string, folder: string }>()\n for (const match of (await res.text()).matchAll(SKILL_LINK_RE)) {\n const link = { owner: match[1] as string, repo: match[2] as string, ref: match[3] as string, folder: match[4] ?? '' }\n if (link.folder.split('/').includes('..'))\n continue\n links.set(`${link.owner}/${link.repo}/${link.folder}`, link)\n }\n return mapLimit([...links.values()].slice(0, MAX_SKILLS), CONCURRENCY, async (link): Promise<SkillSourceSkill> => {\n const head = await skillHead(fetcher, link.owner, link.repo, link.ref, link.folder)\n const segments = link.folder ? link.folder.split('/') : []\n const name = segments[segments.length - 1] ?? link.repo\n return {\n name: head.name || name,\n description: head.description,\n path: link.folder || '.',\n url: `https://github.com/${link.owner}/${link.repo}/blob/${link.ref}/${link.folder ? `${link.folder}/` : ''}SKILL.md`,\n from: `${link.owner}/${link.repo}`,\n // Rooted at the skill's parent folder: one download serves the skills that sit side by side.\n install: { source: githubSpec(link.owner, link.repo, link.ref, segments.slice(0, -1).join('/')), rel: segments.length > 0 ? name : '.' },\n }\n })\n}\n\n/** The listing of a downloaded source: npm packages, and GitHub when its API fails. */\nasync function browseByDownload(input: string, source: InstallSource, io: InstallIo): Promise<SkillSourceListing> {\n const stage = mkdtempSync(join(tmpdir(), 'zidane-skill-browse-'))\n try {\n const dest = join(stage, 'source')\n mkdirSync(dest)\n try {\n await fetchInstallSource(source, dest, io)\n }\n catch (err) {\n throw new SkillBrowseError('fetch-failed', errorMessage(err))\n }\n let root = dest\n let fallbackName = source.kind === 'github' ? source.repo : source.pkg\n if (source.kind === 'github' && source.subdir) {\n const rooted = resolveSubdirRoot(dest, source.subdir)\n if (!rooted.ok)\n throw new SkillBrowseError('not-found', `\"${source.subdir}\" is not a folder in ${source.owner}/${source.repo}.`)\n root = rooted.dir\n fallbackName = basename(source.subdir)\n }\n const spec = source.kind === 'github'\n ? githubSpec(source.owner, source.repo, source.ref, source.subdir ?? '')\n : `${source.pkg}${source.version ? `@${source.version}` : ''}`\n const skills = await mapLimit(listSkillCandidates(root, fallbackName).slice(0, MAX_SKILLS), CONCURRENCY, async (candidate): Promise<SkillSourceSkill> => {\n const parsed = await parseSkillFile(join(candidate.dir, 'SKILL.md')).catch(() => null)\n return { name: parsed?.name || candidate.name, description: parsed?.description.trim() ?? '', path: candidate.rel, install: { source: spec, rel: candidate.rel } }\n })\n const info: SkillSourceInfo = source.kind === 'github'\n ? {\n kind: 'github',\n label: source.subdir ? `${source.owner}/${source.repo}/${source.subdir}` : `${source.owner}/${source.repo}`,\n owner: source.owner,\n repo: source.repo,\n ...(source.ref ? { ref: source.ref } : {}),\n ...(source.subdir ? { path: source.subdir } : {}),\n url: `https://github.com/${source.owner}/${source.repo}`,\n avatarUrl: githubAvatarUrl(source.owner),\n imageUrl: githubPreviewImageUrl(source.owner, source.repo),\n }\n : { kind: 'npm', label: source.pkg, url: `https://www.npmjs.com/package/${source.pkg}` }\n return { input, source: info, skills }\n }\n finally {\n rmSync(stage, { recursive: true, force: true })\n }\n}\n\n/**\n * Lists the skills of a source, with what installs each one.\n *\n * @param input - A source, as the installer takes it: `owner/repo`, a GitHub URL, `owner/repo/path`, a skills.sh URL, a `skills add` command or an npm package.\n * @param opts - A GitHub token, a signal, and test overrides.\n * @returns The listing. A source without skills gives an empty list, not an error.\n * @example\n * ```ts\n * import { browseSkillSource, installSkillFromSpec } from 'zidane/skills'\n *\n * const listing = await browseSkillSource('https://github.com/cursor/plugins/tree/HEAD/pstack/skills')\n * const tdd = listing.skills.find(skill => skill.name === 'tdd')!\n * await installSkillFromSpec({ source: tdd.install.source, skills: [tdd.install.rel], scope: 'user' })\n * ```\n */\nexport async function browseSkillSource(input: string, opts: BrowseSkillSourceOptions = {}): Promise<SkillSourceListing> {\n const raw = input.trim()\n let spec: ReturnType<typeof parseSkillInstallSpec>\n try {\n spec = parseSkillInstallSpec(raw)\n }\n catch (err) {\n throw new SkillBrowseError('invalid-source', errorMessage(err))\n }\n // A token can see private repositories: its listing is cached for that token alone.\n const key = `${raw}\\u0000${opts.token ? createHash('sha256').update(opts.token).digest('hex') : ''}`\n const hit = opts.fresh ? undefined : cache.get(key)\n if (hit && Date.now() - hit.at < CACHE_TTL_MS)\n return hit.listing\n const io = opts.signal ? abortableInstallIo(opts.io, opts.signal) : (opts.io ?? {})\n const fetcher: Fetcher = { fetch: io.fetch ?? fetch, token: opts.token }\n const { source } = spec\n let listing: SkillSourceListing\n if (source.kind === 'github') {\n try {\n listing = await browseGithub(raw, source, fetcher)\n }\n catch (err) {\n // A rate limit or an unreachable API: the download needs neither.\n if (!(err instanceof SkillBrowseError) || (err.code !== 'rate-limited' && err.code !== 'fetch-failed'))\n throw err\n listing = await browseByDownload(raw, source, io).catch((fallback: unknown) => {\n throw err.code === 'rate-limited' ? err : fallback\n })\n }\n }\n else {\n listing = await browseByDownload(raw, source, io)\n }\n if (spec.skill)\n listing = { ...listing, preselect: [spec.skill] }\n remember(key, listing)\n return listing\n}\n","import type { SkillConfig } from './types'\nimport { validateSkillForWrite } from './validate'\n\nexport {\n type ActivationVia,\n type ActiveSkill,\n createSkillActivationState,\n type DeactivationReason,\n type SkillActivationState,\n type SkillActivationStateOptions,\n} from './activation'\nexport {\n IMPLICITLY_ALLOWED_SKILL_TOOLS,\n installAllowedToolsGate,\n} from './allowed-tools'\nexport { browseSkillSource, type BrowseSkillSourceOptions, clearSkillSourceCache, SkillBrowseError, type SkillBrowseErrorCode } from './browse'\nexport { sanitizeSkillName, SKILL_CANDIDATE_MAX_DEPTH, skillCandidatesFromPaths, type SkillPathCandidate } from './candidates'\nexport { buildCatalog, skillCatalogEntry } from './catalog'\nexport {\n discoverSkills,\n getDefaultScanPaths,\n inferSource,\n parseFrontmatter,\n parseSkillContent,\n parseSkillFile,\n} from './discovery'\nexport type { SourcedScanPath } from './discovery'\nexport {\n installSkillFromSpec,\n listSkillCandidates,\n parseSkillInstallSpec,\n readSkillProvenance,\n SKILL_PROVENANCE_FILE,\n SkillInstallError,\n} from './install'\nexport type {\n SkillCandidateDetail,\n SkillInstallCandidate,\n SkillInstallErrorCode,\n SkillInstallOptions,\n SkillInstallResult,\n SkillInstallSpec,\n} from './install'\nexport { interpolateShellCommands, stripShellInterpolations } from './interpolate'\nexport type { InterpolateShellCommandsOptions } from './interpolate'\nexport { resolveSkills } from './resolve'\nexport {\n githubAvatarUrl,\n githubPreviewImageUrl,\n groupSkillPicks,\n RECOMMENDED_SKILL_SOURCES,\n type RecommendedSkillSource,\n skillCategory,\n type SkillInstallTarget,\n type SkillPick,\n type SkillPickGroup,\n skillPickKey,\n skillPickOf,\n type SkillSourceInfo,\n type SkillSourceListing,\n type SkillSourceSkill,\n} from './sources'\nexport type {\n ShellInterpolationApproval,\n SkillConfig,\n SkillDiagnostic,\n SkillResource,\n SkillsConfig,\n SkillSource,\n} from './types'\nexport {\n isToolAllowedByUnion,\n matchesAllowedTool,\n parseAllowedToolPattern,\n validateResourcePath,\n validateResourcePathReal,\n validateSkillForWrite,\n validateSkillName,\n} from './validate'\nexport type { SkillValidationIssue, SkillValidationResult } from './validate'\nexport { skillTokenWeights, type SkillTokenWeights } from './weights'\nexport { writeSkillsToDisk, writeSkillToDisk } from './writer'\n\n// ---------------------------------------------------------------------------\n// defineSkill helper\n// ---------------------------------------------------------------------------\n\n/**\n * Define an inline skill configuration.\n *\n * Strict-validates at author-time — throws if the config would be rejected by\n * `writeSkillToDisk` or the spec. Source defaults to `'inline'`.\n * @param config - Skill fields. `source` defaults to `'inline'`.\n * @example\n * ```ts\n * import { defineSkill } from 'zidane/skills'\n *\n * const skill = defineSkill({\n * name: 'release-notes',\n * description: 'Drafts release notes from merged pull requests.',\n * instructions: 'List merged PRs since the last tag and group them by area.',\n * })\n * ```\n */\nexport function defineSkill(config: Omit<SkillConfig, 'source'> & { source?: SkillConfig['source'] }): SkillConfig {\n const normalized: SkillConfig = {\n ...config,\n source: config.source ?? 'inline',\n }\n const result = validateSkillForWrite(normalized)\n if (!result.valid) {\n const summary = result.errors.map(e => ` - [${e.code}] ${e.message}`).join('\\n')\n throw new Error(`Invalid skill \"${normalized.name}\":\\n${summary}`)\n }\n return normalized\n}\n"],"mappings":"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"}
|
package/dist/skills.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { $
|
|
1
|
+
import { $D as parseSkillInstallSpec, AD as SkillValidationIssue, AF as SkillConfig, AO as skillCategory, BD as InterpolateShellCommandsOptions, CO as SkillPickGroup, DD as writeSkillsToDisk, DO as githubAvatarUrl, ED as writeSkillToDisk, EO as SkillSourceSkill, FD as validateResourcePath, FO as installAllowedToolsGate, GD as SkillInstallCandidate, HD as stripShellInterpolations, ID as validateResourcePathReal, JD as SkillInstallOptions, KD as SkillInstallError, LD as validateSkillForWrite, MD as isToolAllowedByUnion, MF as SkillResource, MO as skillPickOf, ND as matchesAllowedTool, NF as SkillSource, OD as SkillTokenWeights, OO as githubPreviewImageUrl, PD as parseAllowedToolPattern, PF as SkillsConfig, PO as IMPLICITLY_ALLOWED_SKILL_TOOLS, QD as listSkillCandidates, RD as validateSkillName, SO as SkillPick, TD as defineSkill, TO as SkillSourceListing, UD as SKILL_PROVENANCE_FILE, VD as interpolateShellCommands, WD as SkillCandidateDetail, XD as SkillInstallSpec, YD as SkillInstallResult, ZD as installSkillFromSpec, _A as createSkillActivationState, _O as browseSkillSource, aO as parseFrontmatter, bO as RecommendedSkillSource, cO as buildCatalog, dO as SkillPathCandidate, eO as readSkillProvenance, fA as ActivationVia, fO as sanitizeSkillName, gA as SkillActivationStateOptions, gO as SkillBrowseErrorCode, hA as SkillActivationState, hO as SkillBrowseError, iO as inferSource, jD as SkillValidationResult, jF as SkillDiagnostic, jO as skillPickKey, kD as skillTokenWeights, kF as ShellInterpolationApproval, kO as groupSkillPicks, lO as skillCatalogEntry, mA as DeactivationReason, mO as BrowseSkillSourceOptions, nO as discoverSkills, oO as parseSkillContent, pA as ActiveSkill, pO as skillCandidatesFromPaths, qD as SkillInstallErrorCode, rO as getDefaultScanPaths, sO as parseSkillFile, tO as SourcedScanPath, uO as SKILL_CANDIDATE_MAX_DEPTH, vO as clearSkillSourceCache, wO as SkillSourceInfo, xO as SkillInstallTarget, yO as RECOMMENDED_SKILL_SOURCES, zD as resolveSkills } from "./index-WlE93Q6d.js";
|
|
2
2
|
export { type ActivationVia, type ActiveSkill, type BrowseSkillSourceOptions, type DeactivationReason, IMPLICITLY_ALLOWED_SKILL_TOOLS, type InterpolateShellCommandsOptions, RECOMMENDED_SKILL_SOURCES, type RecommendedSkillSource, SKILL_CANDIDATE_MAX_DEPTH, SKILL_PROVENANCE_FILE, type ShellInterpolationApproval, type SkillActivationState, type SkillActivationStateOptions, SkillBrowseError, type SkillBrowseErrorCode, type SkillCandidateDetail, type SkillConfig, type SkillDiagnostic, type SkillInstallCandidate, SkillInstallError, type SkillInstallErrorCode, type SkillInstallOptions, type SkillInstallResult, type SkillInstallSpec, type SkillInstallTarget, type SkillPathCandidate, type SkillPick, type SkillPickGroup, type SkillResource, type SkillSource, type SkillSourceInfo, type SkillSourceListing, type SkillSourceSkill, type SkillTokenWeights, type SkillValidationIssue, type SkillValidationResult, type SkillsConfig, type SourcedScanPath, browseSkillSource, buildCatalog, clearSkillSourceCache, createSkillActivationState, defineSkill, discoverSkills, getDefaultScanPaths, githubAvatarUrl, githubPreviewImageUrl, groupSkillPicks, inferSource, installAllowedToolsGate, installSkillFromSpec, interpolateShellCommands, isToolAllowedByUnion, listSkillCandidates, matchesAllowedTool, parseAllowedToolPattern, parseFrontmatter, parseSkillContent, parseSkillFile, parseSkillInstallSpec, readSkillProvenance, resolveSkills, sanitizeSkillName, skillCandidatesFromPaths, skillCatalogEntry, skillCategory, skillPickKey, skillPickOf, skillTokenWeights, stripShellInterpolations, validateResourcePath, validateResourcePathReal, validateSkillForWrite, validateSkillName, writeSkillToDisk, writeSkillsToDisk };
|
package/dist/skills.js
CHANGED
|
@@ -2,6 +2,6 @@ import { n as skillCatalogEntry, t as buildCatalog } from "./catalog-DWcFhfO3.js
|
|
|
2
2
|
import { a as parseAllowedToolPattern, c as validateSkillForWrite, i as matchesAllowedTool, l as validateSkillName, n as stripShellInterpolations, o as validateResourcePath, r as isToolAllowedByUnion, s as validateResourcePathReal, t as interpolateShellCommands } from "./interpolate-DD1y8j36.js";
|
|
3
3
|
import { a as writeSkillToDisk, c as getDefaultScanPaths, d as parseSkillContent, f as parseSkillFile, i as resolveSkills, l as inferSource, n as installAllowedToolsGate, o as writeSkillsToDisk, r as createSkillActivationState, s as discoverSkills, t as IMPLICITLY_ALLOWED_SKILL_TOOLS, u as parseFrontmatter } from "./allowed-tools-Da9RyNfj.js";
|
|
4
4
|
import { i as skillCandidatesFromPaths, n as SKILL_CANDIDATE_MAX_DEPTH, r as sanitizeSkillName, t as skillTokenWeights } from "./weights-CqSDHJ4U.js";
|
|
5
|
-
import { a as SKILL_PROVENANCE_FILE, c as listSkillCandidates, i as clearSkillSourceCache, l as parseSkillInstallSpec, n as SkillBrowseError, o as SkillInstallError, r as browseSkillSource, s as installSkillFromSpec, t as defineSkill, u as readSkillProvenance } from "./skills-
|
|
5
|
+
import { a as SKILL_PROVENANCE_FILE, c as listSkillCandidates, i as clearSkillSourceCache, l as parseSkillInstallSpec, n as SkillBrowseError, o as SkillInstallError, r as browseSkillSource, s as installSkillFromSpec, t as defineSkill, u as readSkillProvenance } from "./skills-CEbosUlM.js";
|
|
6
6
|
import { a as skillCategory, i as groupSkillPicks, n as githubAvatarUrl, o as skillPickKey, r as githubPreviewImageUrl, s as skillPickOf, t as RECOMMENDED_SKILL_SOURCES } from "./sources-h464u_hH.js";
|
|
7
7
|
export { IMPLICITLY_ALLOWED_SKILL_TOOLS, RECOMMENDED_SKILL_SOURCES, SKILL_CANDIDATE_MAX_DEPTH, SKILL_PROVENANCE_FILE, SkillBrowseError, SkillInstallError, browseSkillSource, buildCatalog, clearSkillSourceCache, createSkillActivationState, defineSkill, discoverSkills, getDefaultScanPaths, githubAvatarUrl, githubPreviewImageUrl, groupSkillPicks, inferSource, installAllowedToolsGate, installSkillFromSpec, interpolateShellCommands, isToolAllowedByUnion, listSkillCandidates, matchesAllowedTool, parseAllowedToolPattern, parseFrontmatter, parseSkillContent, parseSkillFile, parseSkillInstallSpec, readSkillProvenance, resolveSkills, sanitizeSkillName, skillCandidatesFromPaths, skillCatalogEntry, skillCategory, skillPickKey, skillPickOf, skillTokenWeights, stripShellInterpolations, validateResourcePath, validateResourcePathReal, validateSkillForWrite, validateSkillName, writeSkillToDisk, writeSkillsToDisk };
|