@pasko70/pibo 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex/skills/pi-agent-harness/SKILL.md +319 -0
- package/.codex/skills/pi-agent-harness/agents/openai.yaml +4 -0
- package/README.md +318 -0
- package/context/codex-base-prompt.md +144 -0
- package/context/pibo-compaction-prompt.md +100 -0
- package/context/pibo-system-prompt.md +17 -0
- package/dist/apps/chat/agent-profiles.js +26 -0
- package/dist/apps/chat/agent-store.js +336 -0
- package/dist/apps/chat/event-log.js +307 -0
- package/dist/apps/chat/read-model.js +218 -0
- package/dist/apps/chat/rooms.js +321 -0
- package/dist/apps/chat/session-metadata.js +14 -0
- package/dist/apps/chat/stream.js +122 -0
- package/dist/apps/chat/trace.js +1182 -0
- package/dist/apps/chat/web-app.js +2569 -0
- package/dist/apps/chat-ui/assets/apl-BxEMbctQ.js +1 -0
- package/dist/apps/chat-ui/assets/asciiarmor-eFjeGRuZ.js +1 -0
- package/dist/apps/chat-ui/assets/asn1-Cfx-0pt9.js +1 -0
- package/dist/apps/chat-ui/assets/asterisk-B5BHTsBr.js +1 -0
- package/dist/apps/chat-ui/assets/brainfuck-CAA2xofr.js +1 -0
- package/dist/apps/chat-ui/assets/clike-BoDCOLzN.js +1 -0
- package/dist/apps/chat-ui/assets/clojure-kVFSWX0o.js +1 -0
- package/dist/apps/chat-ui/assets/cmake-BUPAhezI.js +1 -0
- package/dist/apps/chat-ui/assets/cobol-BfgHZgup.js +1 -0
- package/dist/apps/chat-ui/assets/coffeescript-G1Pe_1AZ.js +1 -0
- package/dist/apps/chat-ui/assets/commonlisp-B3N0CjHn.js +1 -0
- package/dist/apps/chat-ui/assets/crystal-C15ndaGM.js +1 -0
- package/dist/apps/chat-ui/assets/css-BqOeyZOV.js +1 -0
- package/dist/apps/chat-ui/assets/cypher-KjIKv-wW.js +1 -0
- package/dist/apps/chat-ui/assets/d-BlT5AEC-.js +1 -0
- package/dist/apps/chat-ui/assets/diff-oF98tbqj.js +1 -0
- package/dist/apps/chat-ui/assets/dist--55mTWk3.js +1 -0
- package/dist/apps/chat-ui/assets/dist-B5rcjVCj.js +1 -0
- package/dist/apps/chat-ui/assets/dist-BPv5MdlL.js +1 -0
- package/dist/apps/chat-ui/assets/dist-BZ39rB1W.js +1 -0
- package/dist/apps/chat-ui/assets/dist-BsedZGJo.js +1 -0
- package/dist/apps/chat-ui/assets/dist-BvO6SpaM.js +9 -0
- package/dist/apps/chat-ui/assets/dist-C5YJOwhH.js +1 -0
- package/dist/apps/chat-ui/assets/dist-C5oXtFSP.js +1 -0
- package/dist/apps/chat-ui/assets/dist-CG_gRSwv.js +23 -0
- package/dist/apps/chat-ui/assets/dist-CKM5j-iy.js +1 -0
- package/dist/apps/chat-ui/assets/dist-CUp9us2q.js +1 -0
- package/dist/apps/chat-ui/assets/dist-CZ9T4Cly.js +1 -0
- package/dist/apps/chat-ui/assets/dist-D4HhiFWB.js +6 -0
- package/dist/apps/chat-ui/assets/dist-DHE-yBLH.js +1 -0
- package/dist/apps/chat-ui/assets/dist-DSKQpPaz.js +1 -0
- package/dist/apps/chat-ui/assets/dist-L1B-KWZO.js +1 -0
- package/dist/apps/chat-ui/assets/dist-SVPsM5Oi.js +13 -0
- package/dist/apps/chat-ui/assets/dist-Zqt9kNG9.js +1 -0
- package/dist/apps/chat-ui/assets/dist-_DPv0-QF.js +1 -0
- package/dist/apps/chat-ui/assets/dist-_oM7g0eh.js +11 -0
- package/dist/apps/chat-ui/assets/dist-laadbMWg.js +1 -0
- package/dist/apps/chat-ui/assets/dist-nFui3f-r.js +2 -0
- package/dist/apps/chat-ui/assets/dist-tGfufz3b.js +1 -0
- package/dist/apps/chat-ui/assets/dockerfile-CUaqzcqp.js +1 -0
- package/dist/apps/chat-ui/assets/dtd-VCA9yr61.js +1 -0
- package/dist/apps/chat-ui/assets/dylan-O-V5Dw5W.js +1 -0
- package/dist/apps/chat-ui/assets/ebnf-SPmdBkvT.js +1 -0
- package/dist/apps/chat-ui/assets/ecl-ChfCttfI.js +1 -0
- package/dist/apps/chat-ui/assets/eiffel-cB9EbjeN.js +1 -0
- package/dist/apps/chat-ui/assets/elm-BTiVflBU.js +1 -0
- package/dist/apps/chat-ui/assets/erlang-egf0TXFm.js +1 -0
- package/dist/apps/chat-ui/assets/factor-C2tAxNob.js +1 -0
- package/dist/apps/chat-ui/assets/fcl-BiCjh1tj.js +1 -0
- package/dist/apps/chat-ui/assets/forth-C4kDGW6u.js +1 -0
- package/dist/apps/chat-ui/assets/fortran-DzkLaPnd.js +1 -0
- package/dist/apps/chat-ui/assets/gas-DMOxskQF.js +1 -0
- package/dist/apps/chat-ui/assets/gherkin-BPxBpp-k.js +1 -0
- package/dist/apps/chat-ui/assets/groovy-CkEnc0ZY.js +1 -0
- package/dist/apps/chat-ui/assets/haskell-Dadpo9-w.js +1 -0
- package/dist/apps/chat-ui/assets/haxe-CDNZL9MW.js +1 -0
- package/dist/apps/chat-ui/assets/http-jy9Zxxt3.js +1 -0
- package/dist/apps/chat-ui/assets/idl-CGSB6iiy.js +1 -0
- package/dist/apps/chat-ui/assets/index-BdcqTugD.js +151 -0
- package/dist/apps/chat-ui/assets/index-Dk-opWl3.css +1 -0
- package/dist/apps/chat-ui/assets/javascript-DSlzRUd8.js +1 -0
- package/dist/apps/chat-ui/assets/julia-DTiF9v6R.js +1 -0
- package/dist/apps/chat-ui/assets/livescript-Ci8Xxna6.js +1 -0
- package/dist/apps/chat-ui/assets/lua-eVxIrt9t.js +1 -0
- package/dist/apps/chat-ui/assets/mathematica-Da3grj36.js +1 -0
- package/dist/apps/chat-ui/assets/mbox-BWBMSafM.js +1 -0
- package/dist/apps/chat-ui/assets/mirc-CtHu9S1B.js +1 -0
- package/dist/apps/chat-ui/assets/mllike-wTFlPWXS.js +1 -0
- package/dist/apps/chat-ui/assets/modelica-cKbQ0UXu.js +1 -0
- package/dist/apps/chat-ui/assets/mscgen-C5pXQCMK.js +1 -0
- package/dist/apps/chat-ui/assets/mumps-CKBHkOQq.js +1 -0
- package/dist/apps/chat-ui/assets/nginx-B00AiQel.js +1 -0
- package/dist/apps/chat-ui/assets/nsis-SMDwKltR.js +1 -0
- package/dist/apps/chat-ui/assets/ntriples-B_36EBV0.js +1 -0
- package/dist/apps/chat-ui/assets/octave-BXPjzAif.js +1 -0
- package/dist/apps/chat-ui/assets/oz-Dedpat1M.js +1 -0
- package/dist/apps/chat-ui/assets/pascal-CMcUg72K.js +1 -0
- package/dist/apps/chat-ui/assets/perl-DoVTbnzt.js +1 -0
- package/dist/apps/chat-ui/assets/pig-CULXT1YV.js +1 -0
- package/dist/apps/chat-ui/assets/powershell-Cv5Uhf6b.js +1 -0
- package/dist/apps/chat-ui/assets/properties-CQ1C2P-M.js +1 -0
- package/dist/apps/chat-ui/assets/protobuf-SRlvzrl6.js +1 -0
- package/dist/apps/chat-ui/assets/pug-Bo3wfL-f.js +1 -0
- package/dist/apps/chat-ui/assets/puppet-ClSqCGj6.js +1 -0
- package/dist/apps/chat-ui/assets/python-C2_X1GpX.js +1 -0
- package/dist/apps/chat-ui/assets/q-B_6Gdyzl.js +1 -0
- package/dist/apps/chat-ui/assets/r-BD07NGED.js +1 -0
- package/dist/apps/chat-ui/assets/rolldown-runtime-S-ySWqyJ.js +1 -0
- package/dist/apps/chat-ui/assets/rpm-CRu5Z2Jl.js +1 -0
- package/dist/apps/chat-ui/assets/ruby-dZPnOz8h.js +1 -0
- package/dist/apps/chat-ui/assets/sas-S2t8_sDI.js +1 -0
- package/dist/apps/chat-ui/assets/scheme-WcyFWLW2.js +1 -0
- package/dist/apps/chat-ui/assets/shell-DUelnYSh.js +1 -0
- package/dist/apps/chat-ui/assets/sieve-jDjlVhNh.js +1 -0
- package/dist/apps/chat-ui/assets/simple-mode-CL04yRVp.js +1 -0
- package/dist/apps/chat-ui/assets/smalltalk-ddWDYy10.js +1 -0
- package/dist/apps/chat-ui/assets/solr-C7rQhLuW.js +1 -0
- package/dist/apps/chat-ui/assets/sparql--fPCCgVd.js +1 -0
- package/dist/apps/chat-ui/assets/spreadsheet-DjthqD5G.js +1 -0
- package/dist/apps/chat-ui/assets/sql-D_dieYbr.js +1 -0
- package/dist/apps/chat-ui/assets/stex-rHcFYcA0.js +1 -0
- package/dist/apps/chat-ui/assets/stylus-DxF70RNr.js +1 -0
- package/dist/apps/chat-ui/assets/swift-T0hDoOb-.js +1 -0
- package/dist/apps/chat-ui/assets/tcl-Dlzy1mPk.js +1 -0
- package/dist/apps/chat-ui/assets/textile-Dy5rFZe5.js +1 -0
- package/dist/apps/chat-ui/assets/tiddlywiki-DQP1OymS.js +1 -0
- package/dist/apps/chat-ui/assets/tiki-0vOj3DB2.js +1 -0
- package/dist/apps/chat-ui/assets/toml-DEltN0lI.js +1 -0
- package/dist/apps/chat-ui/assets/troff-CRbBpEqY.js +1 -0
- package/dist/apps/chat-ui/assets/ttcn-M5q1nLKg.js +1 -0
- package/dist/apps/chat-ui/assets/ttcn-cfg-Rmw9NWq9.js +1 -0
- package/dist/apps/chat-ui/assets/turtle-BCDWhJK0.js +1 -0
- package/dist/apps/chat-ui/assets/vb-QQ7LGlOz.js +1 -0
- package/dist/apps/chat-ui/assets/vbscript-BiSEdUzz.js +1 -0
- package/dist/apps/chat-ui/assets/velocity-CzMg5_6w.js +1 -0
- package/dist/apps/chat-ui/assets/verilog-BJOO83gk.js +1 -0
- package/dist/apps/chat-ui/assets/vhdl-ed4zAZAA.js +1 -0
- package/dist/apps/chat-ui/assets/webidl-Ddy6hbV6.js +1 -0
- package/dist/apps/chat-ui/assets/xquery-Bw1zwhh6.js +1 -0
- package/dist/apps/chat-ui/assets/yacas-DZdTXy3s.js +1 -0
- package/dist/apps/chat-ui/assets/z80-DMG02_YV.js +1 -0
- package/dist/apps/chat-ui/index.html +15 -0
- package/dist/apps/context-files-ui/assets/apl-14BqhLYK.js +1 -0
- package/dist/apps/context-files-ui/assets/asciiarmor-WLvjqI7d.js +1 -0
- package/dist/apps/context-files-ui/assets/asn1-Bg4FbdCg.js +1 -0
- package/dist/apps/context-files-ui/assets/asterisk-B2MO-X4d.js +1 -0
- package/dist/apps/context-files-ui/assets/brainfuck-CV9kufud.js +1 -0
- package/dist/apps/context-files-ui/assets/clike-CjmcUpt6.js +1 -0
- package/dist/apps/context-files-ui/assets/clojure-De3QGfx6.js +1 -0
- package/dist/apps/context-files-ui/assets/cmake-i2VDNaRq.js +1 -0
- package/dist/apps/context-files-ui/assets/cobol-BwO6DF08.js +1 -0
- package/dist/apps/context-files-ui/assets/coffeescript-BtivDvRz.js +1 -0
- package/dist/apps/context-files-ui/assets/commonlisp-DFkEQdLP.js +1 -0
- package/dist/apps/context-files-ui/assets/crystal-CU0NkcVL.js +1 -0
- package/dist/apps/context-files-ui/assets/css-CbMZAQ1W.js +1 -0
- package/dist/apps/context-files-ui/assets/cypher-Dke5QOzv.js +1 -0
- package/dist/apps/context-files-ui/assets/d-TPqSVaHM.js +1 -0
- package/dist/apps/context-files-ui/assets/diff-B365LSoZ.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-B5PTVnox.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-B5ZAfwf5.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-BJW3917N.js +11 -0
- package/dist/apps/context-files-ui/assets/dist-BkUONE3N.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-CKQF1zEu.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-CwbH_3RN.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-CxRLRmMx.js +2 -0
- package/dist/apps/context-files-ui/assets/dist-D1KyNDg5.js +13 -0
- package/dist/apps/context-files-ui/assets/dist-DFi2YGux.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-DKoyMSNr.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-DLMjd26T.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-DQXzt2Ng.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-DotDq_kD.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-DyfSeBMu.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-MyZgX9K2.js +9 -0
- package/dist/apps/context-files-ui/assets/dist-QQuDy4KA.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-YwR9dpb-.js +1 -0
- package/dist/apps/context-files-ui/assets/dist-dTBsFYAl.js +1 -0
- package/dist/apps/context-files-ui/assets/dockerfile-CJb2sekW.js +1 -0
- package/dist/apps/context-files-ui/assets/dtd-CBMymJ7O.js +1 -0
- package/dist/apps/context-files-ui/assets/dylan-08Azy1NK.js +1 -0
- package/dist/apps/context-files-ui/assets/ebnf-Dkum_1J8.js +1 -0
- package/dist/apps/context-files-ui/assets/ecl-dhxjHL8y.js +1 -0
- package/dist/apps/context-files-ui/assets/eiffel-C446m6dI.js +1 -0
- package/dist/apps/context-files-ui/assets/elm-po1E9de0.js +1 -0
- package/dist/apps/context-files-ui/assets/erlang-BD5LkVkR.js +1 -0
- package/dist/apps/context-files-ui/assets/factor-B2EMQZkP.js +1 -0
- package/dist/apps/context-files-ui/assets/fcl-DkEOamHd.js +1 -0
- package/dist/apps/context-files-ui/assets/forth-dCg2_HVB.js +1 -0
- package/dist/apps/context-files-ui/assets/fortran-3ZpXLFh1.js +1 -0
- package/dist/apps/context-files-ui/assets/gas-DMmxLZRI.js +1 -0
- package/dist/apps/context-files-ui/assets/gherkin-DUSFg2R6.js +1 -0
- package/dist/apps/context-files-ui/assets/groovy-Cmduloe1.js +1 -0
- package/dist/apps/context-files-ui/assets/haskell-Cyr255R9.js +1 -0
- package/dist/apps/context-files-ui/assets/haxe-BJVKtprM.js +1 -0
- package/dist/apps/context-files-ui/assets/http-DszHFpc-.js +1 -0
- package/dist/apps/context-files-ui/assets/idl-Cati9Nrv.js +1 -0
- package/dist/apps/context-files-ui/assets/index-BwRDoHMC.css +1 -0
- package/dist/apps/context-files-ui/assets/index-GwvjXytt.js +202 -0
- package/dist/apps/context-files-ui/assets/javascript-C5pDVaoP.js +1 -0
- package/dist/apps/context-files-ui/assets/julia-DlqY1cTU.js +1 -0
- package/dist/apps/context-files-ui/assets/livescript-C6p8-2W1.js +1 -0
- package/dist/apps/context-files-ui/assets/lua-BxuTHaF7.js +1 -0
- package/dist/apps/context-files-ui/assets/mathematica-nDHZUMJT.js +1 -0
- package/dist/apps/context-files-ui/assets/mbox-DAaR0Hcz.js +1 -0
- package/dist/apps/context-files-ui/assets/mirc-CCA0FRfl.js +1 -0
- package/dist/apps/context-files-ui/assets/mllike-Cw3Iccsb.js +1 -0
- package/dist/apps/context-files-ui/assets/modelica-Czx80fNR.js +1 -0
- package/dist/apps/context-files-ui/assets/mscgen-BmfuTbzG.js +1 -0
- package/dist/apps/context-files-ui/assets/mumps-Ds_ZaumL.js +1 -0
- package/dist/apps/context-files-ui/assets/nginx-D9M2PQ4U.js +1 -0
- package/dist/apps/context-files-ui/assets/nsis-DOo3SJmd.js +1 -0
- package/dist/apps/context-files-ui/assets/ntriples-DLHGTvHs.js +1 -0
- package/dist/apps/context-files-ui/assets/octave-8jj96JiC.js +1 -0
- package/dist/apps/context-files-ui/assets/oz-BmMq7iC4.js +1 -0
- package/dist/apps/context-files-ui/assets/pascal-BgMr6XpT.js +1 -0
- package/dist/apps/context-files-ui/assets/perl-D6YJObi_.js +1 -0
- package/dist/apps/context-files-ui/assets/pig-nx7uchs8.js +1 -0
- package/dist/apps/context-files-ui/assets/powershell-B4IPkVxR.js +1 -0
- package/dist/apps/context-files-ui/assets/properties-CoavQdLC.js +1 -0
- package/dist/apps/context-files-ui/assets/protobuf-D5EWhcmc.js +1 -0
- package/dist/apps/context-files-ui/assets/pug-QU72BPAa.js +1 -0
- package/dist/apps/context-files-ui/assets/puppet-B_0GFuyD.js +1 -0
- package/dist/apps/context-files-ui/assets/python-Cs7sNzy_.js +1 -0
- package/dist/apps/context-files-ui/assets/q-BIB9pPtI.js +1 -0
- package/dist/apps/context-files-ui/assets/r-CN5s4f3o.js +1 -0
- package/dist/apps/context-files-ui/assets/rpm-Ct_Ck8mQ.js +1 -0
- package/dist/apps/context-files-ui/assets/ruby-BVPradRk.js +1 -0
- package/dist/apps/context-files-ui/assets/sas-DN4j74IF.js +1 -0
- package/dist/apps/context-files-ui/assets/scheme-CvNgHAnh.js +1 -0
- package/dist/apps/context-files-ui/assets/shell-BjiMLAaM.js +1 -0
- package/dist/apps/context-files-ui/assets/sieve-C2U1O5Z4.js +1 -0
- package/dist/apps/context-files-ui/assets/simple-mode-DnMbQuKW.js +1 -0
- package/dist/apps/context-files-ui/assets/smalltalk-DkYwYufV.js +1 -0
- package/dist/apps/context-files-ui/assets/solr-BopnSD-v.js +1 -0
- package/dist/apps/context-files-ui/assets/sparql-Cpg3YQfd.js +1 -0
- package/dist/apps/context-files-ui/assets/spreadsheet-kRxtVt9J.js +1 -0
- package/dist/apps/context-files-ui/assets/sql-DYI2GwJR.js +1 -0
- package/dist/apps/context-files-ui/assets/stex-BCRT8IJ3.js +1 -0
- package/dist/apps/context-files-ui/assets/stylus-CARoeVE9.js +1 -0
- package/dist/apps/context-files-ui/assets/swift-DKNaLAK6.js +1 -0
- package/dist/apps/context-files-ui/assets/tcl-CQyXkgMH.js +1 -0
- package/dist/apps/context-files-ui/assets/textile-Yg_UWn-t.js +1 -0
- package/dist/apps/context-files-ui/assets/tiddlywiki-dqVbkfX4.js +1 -0
- package/dist/apps/context-files-ui/assets/tiki-CC_ic8g0.js +1 -0
- package/dist/apps/context-files-ui/assets/toml-B7gGzVkJ.js +1 -0
- package/dist/apps/context-files-ui/assets/troff-Bq3z6oAu.js +1 -0
- package/dist/apps/context-files-ui/assets/ttcn-GvRgs2AT.js +1 -0
- package/dist/apps/context-files-ui/assets/ttcn-cfg-CGCXz7Vo.js +1 -0
- package/dist/apps/context-files-ui/assets/turtle-CwIpz2yy.js +1 -0
- package/dist/apps/context-files-ui/assets/vb-BmcHUM2C.js +1 -0
- package/dist/apps/context-files-ui/assets/vbscript-Cs9dylre.js +1 -0
- package/dist/apps/context-files-ui/assets/velocity-CBWU0VCy.js +1 -0
- package/dist/apps/context-files-ui/assets/verilog-DVCHmufx.js +1 -0
- package/dist/apps/context-files-ui/assets/vhdl-BGDk4f1d.js +1 -0
- package/dist/apps/context-files-ui/assets/webidl-egXQEWlC.js +1 -0
- package/dist/apps/context-files-ui/assets/xquery-5f0hn7cg.js +1 -0
- package/dist/apps/context-files-ui/assets/yacas-DNs9XkmQ.js +1 -0
- package/dist/apps/context-files-ui/assets/z80-B7Dfb4UO.js +1 -0
- package/dist/apps/context-files-ui/index.html +14 -0
- package/dist/auth/better-auth.js +129 -0
- package/dist/auth/types.js +14 -0
- package/dist/bin/pibo.js +3 -0
- package/dist/channels/types.js +1 -0
- package/dist/cli-errors.js +18 -0
- package/dist/cli.js +264 -0
- package/dist/config/config.js +167 -0
- package/dist/core/base-prompt.js +104 -0
- package/dist/core/codex-compat.js +44 -0
- package/dist/core/compaction-prompt.js +258 -0
- package/dist/core/events.js +1 -0
- package/dist/core/profiles.js +138 -0
- package/dist/core/routed-session.js +538 -0
- package/dist/core/runtime.js +323 -0
- package/dist/core/session-router.js +437 -0
- package/dist/core/system-prompt-template.js +56 -0
- package/dist/core/thinking.js +9 -0
- package/dist/core/workspace.js +5 -0
- package/dist/debug/events.js +92 -0
- package/dist/debug/index.js +631 -0
- package/dist/debug/session.js +200 -0
- package/dist/debug/sql.js +238 -0
- package/dist/debug/stores.js +49 -0
- package/dist/debug/trace.js +245 -0
- package/dist/env.js +32 -0
- package/dist/gateway/client.js +200 -0
- package/dist/gateway/protocol.js +46 -0
- package/dist/gateway/request.js +146 -0
- package/dist/gateway/server.js +221 -0
- package/dist/gateway/tool.js +69 -0
- package/dist/gateway/web.js +80 -0
- package/dist/index.js +23 -0
- package/dist/local/client.js +77 -0
- package/dist/local/extension.js +548 -0
- package/dist/local/tui.js +25 -0
- package/dist/mcp/agent-context.js +123 -0
- package/dist/mcp/client.js +332 -0
- package/dist/mcp/commands/call.js +146 -0
- package/dist/mcp/commands/grep.js +145 -0
- package/dist/mcp/commands/info.js +71 -0
- package/dist/mcp/commands/list.js +94 -0
- package/dist/mcp/config-command.js +217 -0
- package/dist/mcp/config.js +440 -0
- package/dist/mcp/daemon-client.js +266 -0
- package/dist/mcp/daemon.js +327 -0
- package/dist/mcp/errors.js +287 -0
- package/dist/mcp/index.js +431 -0
- package/dist/mcp/output.js +171 -0
- package/dist/mcp/python-runtime.js +131 -0
- package/dist/mcp/registry.js +116 -0
- package/dist/mcp/version.js +5 -0
- package/dist/pi-packages/cli.js +92 -0
- package/dist/pi-packages/installer.js +70 -0
- package/dist/pi-packages/metadata.js +231 -0
- package/dist/pi-packages/runtime.js +61 -0
- package/dist/pi-packages/store.js +175 -0
- package/dist/pi-packages/types.js +1 -0
- package/dist/plugins/better-auth.js +11 -0
- package/dist/plugins/builtin.js +249 -0
- package/dist/plugins/chat-web.js +11 -0
- package/dist/plugins/codex-compat.js +72 -0
- package/dist/plugins/context-files-store.js +354 -0
- package/dist/plugins/context-files.js +999 -0
- package/dist/plugins/core-tools.js +94 -0
- package/dist/plugins/example.js +67 -0
- package/dist/plugins/registry.js +368 -0
- package/dist/plugins/remote-agent.js +9 -0
- package/dist/plugins/types.js +1 -0
- package/dist/plugins/web.js +11 -0
- package/dist/reliability/store.js +759 -0
- package/dist/remote/channel.js +176 -0
- package/dist/remote/client.js +92 -0
- package/dist/remote/examples/tui-controller.js +263 -0
- package/dist/remote/protocol.js +55 -0
- package/dist/remote/session-client.js +189 -0
- package/dist/runs/registry.js +323 -0
- package/dist/runs/tools.js +185 -0
- package/dist/sessions/bindings.js +41 -0
- package/dist/sessions/sqlite-store.js +154 -0
- package/dist/sessions/store.js +125 -0
- package/dist/shared/trace-order.js +83 -0
- package/dist/subagents/tool.js +63 -0
- package/dist/tools/browser-use-cdp.js +207 -0
- package/dist/tools/browser-use-leases.js +338 -0
- package/dist/tools/browser-use-wrapper.js +338 -0
- package/dist/tools/codex-compat.js +85 -0
- package/dist/tools/desktop-env.js +90 -0
- package/dist/tools/guides.js +276 -0
- package/dist/tools/index.js +366 -0
- package/dist/tools/python-runtime.js +154 -0
- package/dist/tools/registry.js +107 -0
- package/dist/tools/web-search.js +137 -0
- package/dist/web/auth.js +10 -0
- package/dist/web/channel.js +151 -0
- package/dist/web/http.js +110 -0
- package/dist/web/types.js +1 -0
- package/package.json +74 -0
- package/src/mcp/LICENSE.mcp-cli +21 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP-CLI Configuration Types and Loader
|
|
3
|
+
*/
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
8
|
+
import { dirname, join, resolve } from 'node:path';
|
|
9
|
+
import { ErrorCode, configInvalidJsonError, configMissingFieldError, configNotFoundError, configSearchError, formatCliError, serverNotFoundError, } from './errors.js';
|
|
10
|
+
export const DEFAULT_MCP_CONFIG_FILE = 'mcp_servers.json';
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Tool Filtering
|
|
13
|
+
// ============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Simple glob pattern matcher for tool names
|
|
16
|
+
* Supports * (any characters) and ? (single character)
|
|
17
|
+
*/
|
|
18
|
+
function matchesPattern(name, pattern) {
|
|
19
|
+
// Convert glob pattern to regex
|
|
20
|
+
const regexPattern = pattern
|
|
21
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex chars
|
|
22
|
+
.replace(/\*/g, '.*') // * matches any characters
|
|
23
|
+
.replace(/\?/g, '.'); // ? matches single character
|
|
24
|
+
return new RegExp(`^${regexPattern}$`, 'i').test(name);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Check if a tool name matches any of the given patterns
|
|
28
|
+
*/
|
|
29
|
+
function matchesAnyPattern(name, patterns) {
|
|
30
|
+
return patterns.some((pattern) => matchesPattern(name, pattern));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Filter tools based on allowedTools and disabledTools configuration
|
|
34
|
+
*
|
|
35
|
+
* Rules:
|
|
36
|
+
* - If allowedTools is specified, only tools matching those patterns are available
|
|
37
|
+
* - If disabledTools is specified, tools matching those patterns are excluded
|
|
38
|
+
* - disabledTools takes precedence over allowedTools
|
|
39
|
+
*
|
|
40
|
+
* @param tools - Array of tools with name property
|
|
41
|
+
* @param config - Server config with optional allowedTools/disabledTools
|
|
42
|
+
* @returns Filtered array of tools
|
|
43
|
+
*/
|
|
44
|
+
export function filterTools(tools, config) {
|
|
45
|
+
const { allowedTools, disabledTools } = config;
|
|
46
|
+
return tools.filter((tool) => {
|
|
47
|
+
// First check if tool is in disabledTools (takes precedence)
|
|
48
|
+
if (disabledTools && disabledTools.length > 0) {
|
|
49
|
+
if (matchesAnyPattern(tool.name, disabledTools)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Then check if allowedTools is specified
|
|
54
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
55
|
+
return matchesAnyPattern(tool.name, allowedTools);
|
|
56
|
+
}
|
|
57
|
+
// No filtering specified, allow all
|
|
58
|
+
return true;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Check if a specific tool is allowed by the config
|
|
63
|
+
*
|
|
64
|
+
* @param toolName - Name of the tool to check
|
|
65
|
+
* @param config - Server config with optional allowedTools/disabledTools
|
|
66
|
+
* @returns true if tool is allowed, false otherwise
|
|
67
|
+
*/
|
|
68
|
+
export function isToolAllowed(toolName, config) {
|
|
69
|
+
const { allowedTools, disabledTools } = config;
|
|
70
|
+
// First check if tool is in disabledTools (takes precedence)
|
|
71
|
+
if (disabledTools && disabledTools.length > 0) {
|
|
72
|
+
if (matchesAnyPattern(toolName, disabledTools)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Then check if allowedTools is specified
|
|
77
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
78
|
+
return matchesAnyPattern(toolName, allowedTools);
|
|
79
|
+
}
|
|
80
|
+
// No filtering specified, allow all
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if a server config is HTTP-based
|
|
85
|
+
*/
|
|
86
|
+
export function isHttpServer(config) {
|
|
87
|
+
return 'url' in config;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if a server config is stdio-based
|
|
91
|
+
*/
|
|
92
|
+
export function isStdioServer(config) {
|
|
93
|
+
return 'command' in config;
|
|
94
|
+
}
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Environment Variables & Runtime Configuration
|
|
97
|
+
// ============================================================================
|
|
98
|
+
/**
|
|
99
|
+
* Default configuration values - centralized to avoid inline magic numbers
|
|
100
|
+
*/
|
|
101
|
+
export const DEFAULT_TIMEOUT_SECONDS = 1800; // 30 minutes
|
|
102
|
+
export const DEFAULT_TIMEOUT_MS = DEFAULT_TIMEOUT_SECONDS * 1000;
|
|
103
|
+
export const DEFAULT_CONCURRENCY = 5;
|
|
104
|
+
export const DEFAULT_MAX_RETRIES = 3;
|
|
105
|
+
export const DEFAULT_RETRY_DELAY_MS = 1000; // 1 second base delay
|
|
106
|
+
export const DEFAULT_DAEMON_TIMEOUT_SECONDS = 60; // 60 seconds idle timeout
|
|
107
|
+
export const DEFAULT_DAEMON_REQUEST_TIMEOUT_SECONDS = 60;
|
|
108
|
+
/**
|
|
109
|
+
* Debug logging utility - only logs when MCP_DEBUG is set
|
|
110
|
+
*/
|
|
111
|
+
export function debug(message) {
|
|
112
|
+
if (process.env.MCP_DEBUG) {
|
|
113
|
+
console.error(`[pibo mcp] ${message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get configured timeout in milliseconds
|
|
118
|
+
* @env MCP_TIMEOUT - timeout in seconds (default: 1800 = 30 minutes)
|
|
119
|
+
*/
|
|
120
|
+
export function getTimeoutMs() {
|
|
121
|
+
const envTimeout = process.env.MCP_TIMEOUT;
|
|
122
|
+
if (envTimeout) {
|
|
123
|
+
const seconds = Number.parseInt(envTimeout, 10);
|
|
124
|
+
if (!Number.isNaN(seconds) && seconds > 0) {
|
|
125
|
+
return seconds * 1000;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return DEFAULT_TIMEOUT_MS;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get concurrency limit for parallel server connections
|
|
132
|
+
* @env MCP_CONCURRENCY - max parallel connections (default: 5)
|
|
133
|
+
*/
|
|
134
|
+
export function getConcurrencyLimit() {
|
|
135
|
+
const envConcurrency = process.env.MCP_CONCURRENCY;
|
|
136
|
+
if (envConcurrency) {
|
|
137
|
+
const limit = Number.parseInt(envConcurrency, 10);
|
|
138
|
+
if (!Number.isNaN(limit) && limit > 0) {
|
|
139
|
+
return limit;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return DEFAULT_CONCURRENCY;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get max retry attempts for transient failures
|
|
146
|
+
* @env MCP_MAX_RETRIES - max retry attempts (default: 3, use 0 to disable retries)
|
|
147
|
+
*/
|
|
148
|
+
export function getMaxRetries() {
|
|
149
|
+
const envRetries = process.env.MCP_MAX_RETRIES;
|
|
150
|
+
if (envRetries) {
|
|
151
|
+
const retries = Number.parseInt(envRetries, 10);
|
|
152
|
+
if (!Number.isNaN(retries) && retries >= 0) {
|
|
153
|
+
return retries;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return DEFAULT_MAX_RETRIES;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get base delay for retry backoff in milliseconds
|
|
160
|
+
* @env MCP_RETRY_DELAY - base delay in milliseconds (default: 1000)
|
|
161
|
+
*/
|
|
162
|
+
export function getRetryDelayMs() {
|
|
163
|
+
const envDelay = process.env.MCP_RETRY_DELAY;
|
|
164
|
+
if (envDelay) {
|
|
165
|
+
const delay = Number.parseInt(envDelay, 10);
|
|
166
|
+
if (!Number.isNaN(delay) && delay > 0) {
|
|
167
|
+
return delay;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return DEFAULT_RETRY_DELAY_MS;
|
|
171
|
+
}
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// Daemon Configuration
|
|
174
|
+
// ============================================================================
|
|
175
|
+
/**
|
|
176
|
+
* Check if daemon mode is enabled
|
|
177
|
+
* @env MCP_NO_DAEMON - set to "1" to disable daemon, force fresh connections
|
|
178
|
+
*/
|
|
179
|
+
export function isDaemonEnabled() {
|
|
180
|
+
return process.env.MCP_NO_DAEMON !== '1';
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get daemon idle timeout in milliseconds
|
|
184
|
+
* @env MCP_DAEMON_TIMEOUT - timeout in seconds (default: 60)
|
|
185
|
+
*/
|
|
186
|
+
export function getDaemonTimeoutMs() {
|
|
187
|
+
const envTimeout = process.env.MCP_DAEMON_TIMEOUT;
|
|
188
|
+
if (envTimeout) {
|
|
189
|
+
const seconds = Number.parseInt(envTimeout, 10);
|
|
190
|
+
if (!Number.isNaN(seconds) && seconds > 0) {
|
|
191
|
+
return seconds * 1000;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return DEFAULT_DAEMON_TIMEOUT_SECONDS * 1000;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get daemon request timeout in milliseconds
|
|
198
|
+
* @env MCP_DAEMON_REQUEST_TIMEOUT - timeout in seconds (default: 60)
|
|
199
|
+
*/
|
|
200
|
+
export function getDaemonRequestTimeoutMs() {
|
|
201
|
+
const envTimeout = process.env.MCP_DAEMON_REQUEST_TIMEOUT;
|
|
202
|
+
if (envTimeout) {
|
|
203
|
+
const seconds = Number.parseInt(envTimeout, 10);
|
|
204
|
+
if (!Number.isNaN(seconds) && seconds > 0) {
|
|
205
|
+
return seconds * 1000;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return DEFAULT_DAEMON_REQUEST_TIMEOUT_SECONDS * 1000;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get the socket directory for daemon connections
|
|
212
|
+
* Uses platform-appropriate temp directory
|
|
213
|
+
*/
|
|
214
|
+
export function getSocketDir() {
|
|
215
|
+
const uid = process.getuid?.() ?? 'unknown';
|
|
216
|
+
// macOS uses /var/folders which is auto-cleaned, Linux uses /tmp
|
|
217
|
+
const base = process.platform === 'darwin' ? '/tmp' : '/tmp';
|
|
218
|
+
return join(base, `mcp-cli-${uid}`);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get socket path for a specific server
|
|
222
|
+
*/
|
|
223
|
+
export function getSocketPath(serverName) {
|
|
224
|
+
return join(getSocketDir(), `${serverName}.sock`);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get PID file path for a specific server daemon
|
|
228
|
+
*/
|
|
229
|
+
export function getPidPath(serverName) {
|
|
230
|
+
return join(getSocketDir(), `${serverName}.pid`);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Generate a hash of server config for stale detection
|
|
234
|
+
* Returns consistent hash for identical configs
|
|
235
|
+
*/
|
|
236
|
+
export function getConfigHash(config) {
|
|
237
|
+
const str = JSON.stringify(config, Object.keys(config).sort());
|
|
238
|
+
return createHash('sha256').update(str).digest('hex').slice(0, 16);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Check if strict environment variable mode is enabled
|
|
242
|
+
* @env MCP_STRICT_ENV - set to "false" to warn instead of error (default: true)
|
|
243
|
+
*/
|
|
244
|
+
function isStrictEnvMode() {
|
|
245
|
+
const value = process.env.MCP_STRICT_ENV?.toLowerCase();
|
|
246
|
+
return value !== 'false' && value !== '0';
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Substitute environment variables in a string
|
|
250
|
+
* Supports ${VAR_NAME} syntax
|
|
251
|
+
*
|
|
252
|
+
* By default (strict mode), throws an error when referenced env var is not set.
|
|
253
|
+
* Set MCP_STRICT_ENV=false to warn instead of error.
|
|
254
|
+
*/
|
|
255
|
+
function substituteEnvVars(value) {
|
|
256
|
+
const missingVars = [];
|
|
257
|
+
const result = value.replace(/\$\{([^}]+)\}/g, (match, varName) => {
|
|
258
|
+
const envValue = process.env[varName];
|
|
259
|
+
if (envValue === undefined) {
|
|
260
|
+
missingVars.push(varName);
|
|
261
|
+
return '';
|
|
262
|
+
}
|
|
263
|
+
return envValue;
|
|
264
|
+
});
|
|
265
|
+
if (missingVars.length > 0) {
|
|
266
|
+
const varList = missingVars.map((v) => `\${${v}}`).join(', ');
|
|
267
|
+
const message = `Missing environment variable${missingVars.length > 1 ? 's' : ''}: ${varList}`;
|
|
268
|
+
if (isStrictEnvMode()) {
|
|
269
|
+
throw new Error(formatCliError({
|
|
270
|
+
code: ErrorCode.CLIENT_ERROR,
|
|
271
|
+
type: 'MISSING_ENV_VAR',
|
|
272
|
+
message: message,
|
|
273
|
+
details: 'Referenced in config but not set in environment',
|
|
274
|
+
suggestion: `Set the variable(s) before running: export ${missingVars[0]}="value" or set MCP_STRICT_ENV=false to use empty values`,
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
// Non-strict mode: warn but continue
|
|
278
|
+
console.error(`[pibo mcp] Warning: ${message}`);
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Recursively substitute environment variables in an object
|
|
284
|
+
*/
|
|
285
|
+
function substituteEnvVarsInObject(obj) {
|
|
286
|
+
if (typeof obj === 'string') {
|
|
287
|
+
return substituteEnvVars(obj);
|
|
288
|
+
}
|
|
289
|
+
if (Array.isArray(obj)) {
|
|
290
|
+
return obj.map(substituteEnvVarsInObject);
|
|
291
|
+
}
|
|
292
|
+
if (obj && typeof obj === 'object') {
|
|
293
|
+
const result = {};
|
|
294
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
295
|
+
result[key] = substituteEnvVarsInObject(value);
|
|
296
|
+
}
|
|
297
|
+
return result;
|
|
298
|
+
}
|
|
299
|
+
return obj;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Get default config search paths
|
|
303
|
+
*/
|
|
304
|
+
export function getDefaultConfigPaths() {
|
|
305
|
+
const paths = [];
|
|
306
|
+
const home = homedir();
|
|
307
|
+
// Current directory
|
|
308
|
+
paths.push(resolve(DEFAULT_MCP_CONFIG_FILE));
|
|
309
|
+
// Home directory variants
|
|
310
|
+
paths.push(join(home, '.mcp_servers.json'));
|
|
311
|
+
paths.push(join(home, '.config', 'mcp', 'mcp_servers.json'));
|
|
312
|
+
return paths;
|
|
313
|
+
}
|
|
314
|
+
export function getPreferredConfigPath(explicitPath) {
|
|
315
|
+
if (explicitPath) {
|
|
316
|
+
return resolve(explicitPath);
|
|
317
|
+
}
|
|
318
|
+
if (process.env.MCP_CONFIG_PATH) {
|
|
319
|
+
return resolve(process.env.MCP_CONFIG_PATH);
|
|
320
|
+
}
|
|
321
|
+
return resolve(DEFAULT_MCP_CONFIG_FILE);
|
|
322
|
+
}
|
|
323
|
+
export function findConfigPath(explicitPath) {
|
|
324
|
+
if (explicitPath) {
|
|
325
|
+
const resolvedPath = resolve(explicitPath);
|
|
326
|
+
return existsSync(resolvedPath) ? resolvedPath : undefined;
|
|
327
|
+
}
|
|
328
|
+
if (process.env.MCP_CONFIG_PATH) {
|
|
329
|
+
const resolvedPath = resolve(process.env.MCP_CONFIG_PATH);
|
|
330
|
+
return existsSync(resolvedPath) ? resolvedPath : undefined;
|
|
331
|
+
}
|
|
332
|
+
return getDefaultConfigPaths().find((path) => existsSync(path));
|
|
333
|
+
}
|
|
334
|
+
export async function ensureConfigExists(explicitPath) {
|
|
335
|
+
const existingPath = findConfigPath(explicitPath);
|
|
336
|
+
if (existingPath) {
|
|
337
|
+
return existingPath;
|
|
338
|
+
}
|
|
339
|
+
const configPath = getPreferredConfigPath(explicitPath);
|
|
340
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
341
|
+
await writeFile(configPath, `${JSON.stringify({ mcpServers: {} }, null, 2)}\n`);
|
|
342
|
+
return configPath;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Load and parse MCP servers configuration
|
|
346
|
+
*/
|
|
347
|
+
export async function loadConfig(explicitPath) {
|
|
348
|
+
let configPath;
|
|
349
|
+
// Check explicit path from argument or environment
|
|
350
|
+
if (explicitPath) {
|
|
351
|
+
configPath = resolve(explicitPath);
|
|
352
|
+
}
|
|
353
|
+
else if (process.env.MCP_CONFIG_PATH) {
|
|
354
|
+
configPath = resolve(process.env.MCP_CONFIG_PATH);
|
|
355
|
+
}
|
|
356
|
+
// If explicit path provided, it must exist
|
|
357
|
+
if (configPath) {
|
|
358
|
+
if (!existsSync(configPath)) {
|
|
359
|
+
throw new Error(formatCliError(configNotFoundError(configPath)));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
// Search default paths
|
|
364
|
+
const searchPaths = getDefaultConfigPaths();
|
|
365
|
+
for (const path of searchPaths) {
|
|
366
|
+
if (existsSync(path)) {
|
|
367
|
+
configPath = path;
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (!configPath) {
|
|
372
|
+
throw new Error(formatCliError(configSearchError()));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// Read and parse config
|
|
376
|
+
const content = await readFile(configPath, 'utf-8');
|
|
377
|
+
let config;
|
|
378
|
+
try {
|
|
379
|
+
config = JSON.parse(content);
|
|
380
|
+
}
|
|
381
|
+
catch (e) {
|
|
382
|
+
throw new Error(formatCliError(configInvalidJsonError(configPath, e.message)));
|
|
383
|
+
}
|
|
384
|
+
// Validate structure
|
|
385
|
+
if (!config.mcpServers || typeof config.mcpServers !== 'object') {
|
|
386
|
+
throw new Error(formatCliError(configMissingFieldError(configPath)));
|
|
387
|
+
}
|
|
388
|
+
// Validate individual server configs
|
|
389
|
+
for (const [serverName, serverConfig] of Object.entries(config.mcpServers)) {
|
|
390
|
+
if (!serverConfig || typeof serverConfig !== 'object') {
|
|
391
|
+
throw new Error(formatCliError({
|
|
392
|
+
code: ErrorCode.CLIENT_ERROR,
|
|
393
|
+
type: 'CONFIG_INVALID_SERVER',
|
|
394
|
+
message: `Invalid server configuration for "${serverName}"`,
|
|
395
|
+
details: 'Server config must be an object',
|
|
396
|
+
suggestion: `Use { "command": "..." } for stdio or { "url": "..." } for HTTP`,
|
|
397
|
+
}));
|
|
398
|
+
}
|
|
399
|
+
const hasCommand = 'command' in serverConfig;
|
|
400
|
+
const hasUrl = 'url' in serverConfig;
|
|
401
|
+
if (!hasCommand && !hasUrl) {
|
|
402
|
+
throw new Error(formatCliError({
|
|
403
|
+
code: ErrorCode.CLIENT_ERROR,
|
|
404
|
+
type: 'CONFIG_INVALID_SERVER',
|
|
405
|
+
message: `Server "${serverName}" missing required field`,
|
|
406
|
+
details: `Must have either "command" (for stdio) or "url" (for HTTP)`,
|
|
407
|
+
suggestion: `Add "command": "npx ..." for local servers or "url": "https://..." for remote servers`,
|
|
408
|
+
}));
|
|
409
|
+
}
|
|
410
|
+
if (hasCommand && hasUrl) {
|
|
411
|
+
throw new Error(formatCliError({
|
|
412
|
+
code: ErrorCode.CLIENT_ERROR,
|
|
413
|
+
type: 'CONFIG_INVALID_SERVER',
|
|
414
|
+
message: `Server "${serverName}" has both "command" and "url"`,
|
|
415
|
+
details: 'A server must be either stdio (command) or HTTP (url), not both',
|
|
416
|
+
suggestion: `Remove one of "command" or "url"`,
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// Substitute environment variables
|
|
421
|
+
config = substituteEnvVarsInObject(config);
|
|
422
|
+
return config;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Get a specific server config by name
|
|
426
|
+
*/
|
|
427
|
+
export function getServerConfig(config, serverName) {
|
|
428
|
+
const server = config.mcpServers[serverName];
|
|
429
|
+
if (!server) {
|
|
430
|
+
const available = Object.keys(config.mcpServers);
|
|
431
|
+
throw new Error(formatCliError(serverNotFoundError(serverName, available)));
|
|
432
|
+
}
|
|
433
|
+
return server;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* List all server names
|
|
437
|
+
*/
|
|
438
|
+
export function listServerNames(config) {
|
|
439
|
+
return Object.keys(config.mcpServers);
|
|
440
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP-CLI Daemon Client - IPC client for communicating with daemon workers
|
|
3
|
+
*
|
|
4
|
+
* Handles spawning daemons, detecting stale connections, and forwarding requests.
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { readdir } from 'node:fs/promises';
|
|
9
|
+
import { createConnection } from 'node:net';
|
|
10
|
+
import { dirname, extname, join } from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { debug, getConfigHash, getDaemonRequestTimeoutMs, getSocketDir, getSocketPath, } from './config.js';
|
|
13
|
+
import { isProcessRunning, killProcess, readPidFile, removePidFile, removeSocketFile, } from './daemon.js';
|
|
14
|
+
/**
|
|
15
|
+
* Generate a unique request ID
|
|
16
|
+
*/
|
|
17
|
+
function generateRequestId() {
|
|
18
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Send a request to the daemon and wait for response
|
|
22
|
+
*/
|
|
23
|
+
async function sendRequest(socketPath, request) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
let settled = false;
|
|
26
|
+
let responseText = '';
|
|
27
|
+
const settle = (fn) => {
|
|
28
|
+
if (settled)
|
|
29
|
+
return;
|
|
30
|
+
settled = true;
|
|
31
|
+
clearTimeout(timeoutId);
|
|
32
|
+
fn();
|
|
33
|
+
};
|
|
34
|
+
const socket = createConnection(socketPath, () => {
|
|
35
|
+
socket.write(JSON.stringify(request));
|
|
36
|
+
});
|
|
37
|
+
socket.on('data', (data) => {
|
|
38
|
+
responseText += data.toString();
|
|
39
|
+
if (responseText.includes('\n')) {
|
|
40
|
+
settle(() => {
|
|
41
|
+
try {
|
|
42
|
+
resolve(JSON.parse(responseText.trim()));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
reject(new Error('Invalid response from daemon'));
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
socket.end();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
socket.on('end', () => {
|
|
54
|
+
if (settled)
|
|
55
|
+
return;
|
|
56
|
+
settle(() => {
|
|
57
|
+
try {
|
|
58
|
+
resolve(JSON.parse(responseText.trim()));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
reject(new Error('Invalid response from daemon'));
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
socket.on('error', (error) => {
|
|
66
|
+
settle(() => reject(error));
|
|
67
|
+
});
|
|
68
|
+
const timeoutId = setTimeout(() => {
|
|
69
|
+
socket.destroy();
|
|
70
|
+
settle(() => reject(new Error('Daemon request timeout')));
|
|
71
|
+
}, getDaemonRequestTimeoutMs());
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Check if daemon is running and has matching config
|
|
76
|
+
*/
|
|
77
|
+
function isDaemonValid(serverName, config) {
|
|
78
|
+
const socketPath = getSocketPath(serverName);
|
|
79
|
+
const pidInfo = readPidFile(serverName);
|
|
80
|
+
// No PID file = no daemon
|
|
81
|
+
if (!pidInfo) {
|
|
82
|
+
debug(`[daemon-client] No PID file for ${serverName}`);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
// Check if process is actually running
|
|
86
|
+
if (!isProcessRunning(pidInfo.pid)) {
|
|
87
|
+
debug(`[daemon-client] Process ${pidInfo.pid} not running, cleaning up`);
|
|
88
|
+
removePidFile(serverName);
|
|
89
|
+
removeSocketFile(serverName);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
// Check if config matches
|
|
93
|
+
const currentHash = getConfigHash(config);
|
|
94
|
+
if (pidInfo.configHash !== currentHash) {
|
|
95
|
+
debug(`[daemon-client] Config hash mismatch for ${serverName}, killing old daemon`);
|
|
96
|
+
killProcess(pidInfo.pid);
|
|
97
|
+
removePidFile(serverName);
|
|
98
|
+
removeSocketFile(serverName);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
// Check if socket exists
|
|
102
|
+
if (!existsSync(socketPath)) {
|
|
103
|
+
debug(`[daemon-client] Socket missing for ${serverName}, cleaning up`);
|
|
104
|
+
killProcess(pidInfo.pid);
|
|
105
|
+
removePidFile(serverName);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Spawn a new daemon process for a server
|
|
112
|
+
*/
|
|
113
|
+
async function spawnDaemon(serverName, config) {
|
|
114
|
+
debug(`[daemon-client] Spawning daemon for ${serverName}`);
|
|
115
|
+
// Find the daemon script next to this module. This is .ts under tsx and .js after build.
|
|
116
|
+
const modulePath = fileURLToPath(import.meta.url);
|
|
117
|
+
const daemonScript = join(dirname(modulePath), `daemon${extname(modulePath)}`);
|
|
118
|
+
const configJson = JSON.stringify(config);
|
|
119
|
+
// Spawn detached process
|
|
120
|
+
const proc = spawn(process.execPath, [
|
|
121
|
+
...process.execArgv,
|
|
122
|
+
daemonScript,
|
|
123
|
+
'--daemon',
|
|
124
|
+
serverName,
|
|
125
|
+
configJson,
|
|
126
|
+
], {
|
|
127
|
+
detached: true,
|
|
128
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
129
|
+
env: { ...process.env },
|
|
130
|
+
});
|
|
131
|
+
// Wait for daemon to signal readiness or fail
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
let resolved = false;
|
|
134
|
+
proc.stdout?.on('data', (data) => {
|
|
135
|
+
const text = data.toString();
|
|
136
|
+
if (text.includes('DAEMON_READY') && !resolved) {
|
|
137
|
+
resolved = true;
|
|
138
|
+
// Don't await the process, let it run in the background.
|
|
139
|
+
proc.stdout?.unref?.();
|
|
140
|
+
proc.stderr?.unref?.();
|
|
141
|
+
proc.unref();
|
|
142
|
+
resolve(true);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
// Timeout after 5 seconds (fast fallback to direct connection)
|
|
146
|
+
setTimeout(() => {
|
|
147
|
+
if (!resolved) {
|
|
148
|
+
resolved = true;
|
|
149
|
+
debug(`[daemon-client] Daemon spawn timeout for ${serverName}`);
|
|
150
|
+
resolve(false);
|
|
151
|
+
}
|
|
152
|
+
}, 5000);
|
|
153
|
+
// Check for early exit
|
|
154
|
+
proc.on('exit', (code) => {
|
|
155
|
+
if (!resolved && code !== 0) {
|
|
156
|
+
resolved = true;
|
|
157
|
+
debug(`[daemon-client] Daemon exited with code ${code}`);
|
|
158
|
+
resolve(false);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get or create a daemon connection for a server
|
|
165
|
+
* Returns null if daemon mode fails (caller should fallback to direct connection)
|
|
166
|
+
*/
|
|
167
|
+
export async function getDaemonConnection(serverName, config) {
|
|
168
|
+
const socketPath = getSocketPath(serverName);
|
|
169
|
+
// Check if valid daemon exists
|
|
170
|
+
if (!isDaemonValid(serverName, config)) {
|
|
171
|
+
// Spawn new daemon
|
|
172
|
+
const spawned = await spawnDaemon(serverName, config);
|
|
173
|
+
if (!spawned) {
|
|
174
|
+
debug(`[daemon-client] Failed to spawn daemon for ${serverName}`);
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
// Wait a bit for socket to be ready
|
|
178
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
179
|
+
}
|
|
180
|
+
// Verify socket exists
|
|
181
|
+
if (!existsSync(socketPath)) {
|
|
182
|
+
debug(`[daemon-client] Socket not found after spawn for ${serverName}`);
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
// Test connection with ping
|
|
186
|
+
try {
|
|
187
|
+
const pingResponse = await sendRequest(socketPath, {
|
|
188
|
+
id: generateRequestId(),
|
|
189
|
+
type: 'ping',
|
|
190
|
+
});
|
|
191
|
+
if (!pingResponse.success) {
|
|
192
|
+
debug(`[daemon-client] Ping failed for ${serverName}`);
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
debug(`[daemon-client] Connection test failed for ${serverName}: ${error.message}`);
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
debug(`[daemon-client] Connected to daemon for ${serverName}`);
|
|
201
|
+
// Return connection interface
|
|
202
|
+
return {
|
|
203
|
+
serverName,
|
|
204
|
+
async listTools() {
|
|
205
|
+
const response = await sendRequest(socketPath, {
|
|
206
|
+
id: generateRequestId(),
|
|
207
|
+
type: 'listTools',
|
|
208
|
+
});
|
|
209
|
+
if (!response.success) {
|
|
210
|
+
throw new Error(response.error?.message ?? 'listTools failed');
|
|
211
|
+
}
|
|
212
|
+
return response.data;
|
|
213
|
+
},
|
|
214
|
+
async callTool(toolName, args) {
|
|
215
|
+
const response = await sendRequest(socketPath, {
|
|
216
|
+
id: generateRequestId(),
|
|
217
|
+
type: 'callTool',
|
|
218
|
+
toolName,
|
|
219
|
+
args,
|
|
220
|
+
});
|
|
221
|
+
if (!response.success) {
|
|
222
|
+
throw new Error(response.error?.message ?? 'callTool failed');
|
|
223
|
+
}
|
|
224
|
+
return response.data;
|
|
225
|
+
},
|
|
226
|
+
async getInstructions() {
|
|
227
|
+
const response = await sendRequest(socketPath, {
|
|
228
|
+
id: generateRequestId(),
|
|
229
|
+
type: 'getInstructions',
|
|
230
|
+
});
|
|
231
|
+
if (!response.success) {
|
|
232
|
+
throw new Error(response.error?.message ?? 'getInstructions failed');
|
|
233
|
+
}
|
|
234
|
+
return response.data;
|
|
235
|
+
},
|
|
236
|
+
async close() {
|
|
237
|
+
// Just disconnect, don't tell daemon to close (let it idle timeout)
|
|
238
|
+
debug(`[daemon-client] Disconnecting from ${serverName} daemon`);
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Clean up any orphaned daemon processes and sockets
|
|
244
|
+
* Call this on CLI startup
|
|
245
|
+
*/
|
|
246
|
+
export async function cleanupOrphanedDaemons() {
|
|
247
|
+
const socketDir = getSocketDir();
|
|
248
|
+
if (!existsSync(socketDir)) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
const files = (await readdir(socketDir)).filter((file) => file.endsWith('.pid'));
|
|
253
|
+
for (const file of files) {
|
|
254
|
+
const serverName = file.replace('.pid', '');
|
|
255
|
+
const pidInfo = readPidFile(serverName);
|
|
256
|
+
if (pidInfo && !isProcessRunning(pidInfo.pid)) {
|
|
257
|
+
debug(`[daemon-client] Cleaning up orphaned daemon: ${serverName}`);
|
|
258
|
+
removePidFile(serverName);
|
|
259
|
+
removeSocketFile(serverName);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// Ignore errors during cleanup scan
|
|
265
|
+
}
|
|
266
|
+
}
|