mulmoclaude 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -38
- package/bin/mulmoclaude.js +1 -2
- package/client/assets/JsonEditor-CPJfn76E.css +1 -0
- package/client/assets/JsonEditor-CoWGJG3Y.js +10 -0
- package/client/assets/abnfDiagram-VRR7QNED-6nNByj6v-By7GJ3xW.js +1 -0
- package/client/assets/abnfDiagram-VRR7QNED-BrQlVixL.js +1 -0
- package/client/assets/arc-BXutyUAX.js +1 -0
- package/client/assets/arc-zT0wd74V-DulGJNnb.js +1 -0
- package/client/assets/architecture-TIHT7OUA-CYMWc3UT-C7HoFzWN.js +1 -0
- package/client/assets/architecture-TIHT7OUA-aml8u-G9.js +1 -0
- package/client/assets/architectureDiagram-ZJ3FMSHR-Bgnyaj_i-CCpEsCHN.js +36 -0
- package/client/assets/architectureDiagram-ZJ3FMSHR-DJTFpPjB.js +36 -0
- package/client/assets/array-BifhSqXX.js +1 -0
- package/client/assets/array-CApNbSU9-BifhSqXX.js +1 -0
- package/client/assets/blockDiagram-677ZJIJ3-DQ35o5E4-BXS11D3J.js +132 -0
- package/client/assets/blockDiagram-677ZJIJ3-aYvCODxv.js +132 -0
- package/client/assets/c4Diagram-LMCZKHZV-Bau6RMBb.js +10 -0
- package/client/assets/c4Diagram-LMCZKHZV-ClWZeiWo-DXGnOOUf.js +10 -0
- package/client/assets/channel-D9VSi_QV.js +1 -0
- package/client/assets/channel-Di5rtkx0-C3gZcytF.js +1 -0
- package/client/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/client/assets/chunk-2Q5K7J3B-DfKpwyl9-DTxwf5E9.js +1 -0
- package/client/assets/chunk-32BRIVSS-CCt9wtYd-Cwa_IP35.js +1 -0
- package/client/assets/chunk-32BRIVSS-CxzHeys5.js +1 -0
- package/client/assets/chunk-52WLFC77-BtNjbTdw.js +10 -0
- package/client/assets/chunk-52WLFC77-FbBbR4uI-wsNC5d-f.js +10 -0
- package/client/assets/chunk-5VM5RSS4-BYnnUiN6-C4rOnu9A.js +15 -0
- package/client/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/client/assets/chunk-7BUUIJ7U-Bb538aSH.js +1 -0
- package/client/assets/chunk-7BUUIJ7U-hh8aCuGX-mGEpPzQy.js +1 -0
- package/client/assets/chunk-C7G6YPKG-BE8ehsnc.js +1 -0
- package/client/assets/chunk-C7G6YPKG-C87hlS9c-BitbJpTa.js +1 -0
- package/client/assets/{chunk-D8eiyYIV-BY16KEZc.js → chunk-D8eiyYIV-BeeEsA1B.js} +1 -1
- package/client/assets/chunk-EX3LRPZG-Bhgskzyy.js +231 -0
- package/client/assets/chunk-EX3LRPZG-BqGqMXLN-Cl2Eo1_u.js +231 -0
- package/client/assets/chunk-FWX5IMBZ-BkbSAAuW-C2r4AuAO.js +2 -0
- package/client/assets/chunk-FWX5IMBZ-D9sskZb_.js +2 -0
- package/client/assets/chunk-HOUHSVGY-CBCXk7rb.js +1 -0
- package/client/assets/chunk-HOUHSVGY-Cxu0eDlh-owg_DcY0.js +1 -0
- package/client/assets/chunk-ICXQ74PX-DTqPpDQ9.js +2 -0
- package/client/assets/chunk-ICXQ74PX-hiraF_Xj-CdaEwVin.js +2 -0
- package/client/assets/chunk-JWPE2WC7-BQ3zXr2k-DSd3Ct95.js +1 -0
- package/client/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/client/assets/chunk-KEIR6QF5-DNzq6p3w.js +161 -0
- package/client/assets/chunk-MOJQB5TN-CqxshQHA-Cn_EpDyZ.js +88 -0
- package/client/assets/chunk-MOJQB5TN-D1s_zUGz.js +88 -0
- package/client/assets/chunk-OGEWGWER-CjCr7ceX-Dfk5-70y.js +1 -0
- package/client/assets/chunk-OGEWGWER-DzfhxoQd.js +1 -0
- package/client/assets/chunk-PUDLZKDR-BZlebNhn.js +156 -0
- package/client/assets/chunk-PUDLZKDR-Dx6M-vz1-CLyVurOz.js +156 -0
- package/client/assets/chunk-Q4XR5HBZ-BcJb7mwA.js +70 -0
- package/client/assets/chunk-Q4XR5HBZ-CZd-9lTB-qPEYoRNc.js +11 -0
- package/client/assets/chunk-RYQCIY6F-B_IgMG7T.js +1 -0
- package/client/assets/chunk-RYQCIY6F-YkbemkCt-CX4fncTQ.js +1 -0
- package/client/assets/chunk-V7JOEXUC-Buce04o6.js +206 -0
- package/client/assets/chunk-V7JOEXUC-rI0xlC_O-C607vJXy.js +206 -0
- package/client/assets/chunk-VAUOI2AC-CxhkzXN0.js +1 -0
- package/client/assets/chunk-VAUOI2AC-DrcykVNK-CSROJWdE.js +1 -0
- package/client/assets/chunk-VR4S4FIN-D5afNrsS.js +1 -0
- package/client/assets/chunk-VR4S4FIN-O6iF8Yvf-CLIwrgqU.js +1 -0
- package/client/assets/chunk-WYO6CB5R-B83L_z6I-Cs8mg9If.js +127 -0
- package/client/assets/chunk-WYO6CB5R-BXTIdTMw.js +125 -0
- package/client/assets/chunk-XXDRQBXY-DFXH_lWn-B5HjCp2c.js +1 -0
- package/client/assets/chunk-XXDRQBXY-DXMD6ofA.js +1 -0
- package/client/assets/chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js +1 -0
- package/client/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/client/assets/chunk-ZGVPDNZ5-BpFv9JSP-CHYySMjV.js +62 -0
- package/client/assets/chunk-ZGVPDNZ5-CYusI0J_.js +62 -0
- package/client/assets/chunk-ZIRB5QZD-Biy6ZNFG-D1maAiXb.js +32 -0
- package/client/assets/chunk-ZIRB5QZD-C6fEPe3t.js +32 -0
- package/client/assets/classDiagram-OUVF2IWQ-BgAZMSbT-BgxEE1D3.js +1 -0
- package/client/assets/classDiagram-OUVF2IWQ-CBD3Eidl.js +1 -0
- package/client/assets/classDiagram-v2-EOCWNBFH-CBD3Eidl.js +1 -0
- package/client/assets/classDiagram-v2-EOCWNBFH-DxHTyui1-BgxEE1D3.js +1 -0
- package/client/assets/cose-bilkent-JH36ORCC-CuSL2tA8-aMIqISfj.js +1 -0
- package/client/assets/cose-bilkent-JH36ORCC-WAJDBXv1.js +1 -0
- package/client/assets/cynefin-VYW2F7L2-DH2qkJKw.js +1 -0
- package/client/assets/cynefin-VYW2F7L2-DqA3n9nY-BNWhIK_n.js +1 -0
- package/client/assets/cynefinDiagram-TSTJHNR4-CUvPV9MV.js +62 -0
- package/client/assets/cynefinDiagram-TSTJHNR4-x0-0sQ15-Cq1gFcFu.js +62 -0
- package/client/assets/cytoscape.esm-CzdwbRaj-Djp6vQyU.js +321 -0
- package/client/assets/cytoscape.esm-Djp6vQyU.js +321 -0
- package/client/assets/dagre-CXRCoUWR.js +1 -0
- package/client/assets/dagre-VKFMJZFB-CQdfl-bx-CzZZuace.js +4 -0
- package/client/assets/dagre-VKFMJZFB-C_9IS7mB.js +4 -0
- package/client/assets/dagre-wczQIDso-CfevN9RO.js +1 -0
- package/client/assets/defaultLocale-C8Fc0cco.js +1 -0
- package/client/assets/defaultLocale-DUNguUWs-BbDo_yJX.js +1 -0
- package/client/assets/diagram-FQU43EPY-BOSB6VUb-isPdQ8WX.js +3 -0
- package/client/assets/diagram-FQU43EPY-BP8N00-b.js +3 -0
- package/client/assets/diagram-G47NLZAW-DLXrcXsN-BIqj7RKy.js +24 -0
- package/client/assets/diagram-G47NLZAW-ulE1JlWG.js +24 -0
- package/client/assets/diagram-NH7WQ7WH-BMQp1rkF-D6-fOq_v.js +24 -0
- package/client/assets/diagram-NH7WQ7WH-D_IXrL3i.js +24 -0
- package/client/assets/diagram-OA4YK3LP-C-pC6Eyu.js +30 -0
- package/client/assets/diagram-OA4YK3LP-D1wQ0vUj-BNi6QLCK.js +30 -0
- package/client/assets/diagram-WEI45ONY-Di9m35i-.js +41 -0
- package/client/assets/diagram-WEI45ONY-RR0DpF8R-BS3nrb3w.js +41 -0
- package/client/assets/dist-BQBs5pjy-BmtZ7Oc2.js +1 -0
- package/client/assets/dist-CQ3HaWOk.js +1 -0
- package/client/assets/ebnfDiagram-CCIWWBDH-Cbwnim2x.js +1 -0
- package/client/assets/ebnfDiagram-CCIWWBDH-M123uVJ8-9-dq55nQ.js +1 -0
- package/client/assets/erDiagram-Q63AITRT-BWx_-PXG-CWGfG4z5.js +85 -0
- package/client/assets/erDiagram-Q63AITRT-CMbtO3Sm.js +85 -0
- package/client/assets/eventmodeling-45OFAUF4--32SIpkL.js +1 -0
- package/client/assets/eventmodeling-45OFAUF4-_BVSjAXf-DQdL0Icr.js +1 -0
- package/client/assets/flowDiagram-23GEKE2U-BeOc_anm-CuVzKmmU.js +1 -0
- package/client/assets/flowDiagram-23GEKE2U-Dofa_qxG.js +1 -0
- package/client/assets/ganttDiagram-NO4QXBWP-BOoJ1eTw-TWoNmSvq.js +292 -0
- package/client/assets/ganttDiagram-NO4QXBWP-BgoAVKuc.js +292 -0
- package/client/assets/gitGraph-TEB2WS4Q-CH12KLTN-Bz4frAhV.js +1 -0
- package/client/assets/gitGraph-TEB2WS4Q-DIMvNvqt.js +1 -0
- package/client/assets/gitGraphDiagram-IHSO6WYX-B2CJhk_G-ClOKkjxw.js +106 -0
- package/client/assets/gitGraphDiagram-IHSO6WYX-Dmb6KnPz.js +106 -0
- package/client/assets/graphlib-B8gBHxth.js +1 -0
- package/client/assets/graphlib-hY-1btwe-DQjxxcnr.js +1 -0
- package/client/assets/{html2canvas-CDGcmOD3-CKJ6vKPo.js → html2canvas-CDGcmOD3-DRL9pFVl.js} +2 -2
- package/client/assets/{index-9lmYSaus.js → index-Dc0R-HW5.js} +129 -190
- package/client/assets/index-Dxo1Zdd-.css +2 -0
- package/client/assets/{index.es-DqtpmBm8-DFXjJgCa.js → index.es-DqtpmBm8-EQk3NgR8.js} +1 -1
- package/client/assets/info-DKCQHKI2-Cbw3mbiK-GsDZz9IO.js +1 -0
- package/client/assets/info-DKCQHKI2-ViCqobGo.js +1 -0
- package/client/assets/infoDiagram-FWYZ7A6U-HKV7LIG-.js +2 -0
- package/client/assets/infoDiagram-FWYZ7A6U-Mp1X3pBP-ATJv87Ur.js +2 -0
- package/client/assets/init-D6jRqBbL.js +1 -0
- package/client/assets/init-DEsX3bhM-D6jRqBbL.js +1 -0
- package/client/assets/ishikawaDiagram-FXEZZL3T-BNG7tkJu-CGCeOIlJ.js +70 -0
- package/client/assets/ishikawaDiagram-FXEZZL3T-CVDUj46f.js +70 -0
- package/client/assets/journeyDiagram-5HDEW3XC-BA-ESGLP.js +139 -0
- package/client/assets/journeyDiagram-5HDEW3XC-Dbp_hY9X-Bx-IoCe9.js +139 -0
- package/client/assets/kanban-definition-HUTT4EX6-Bqaet01L.js +89 -0
- package/client/assets/kanban-definition-HUTT4EX6-DSTc5u3q-C0JGckt1.js +89 -0
- package/client/assets/katex-CddkPoXu.js +257 -0
- package/client/assets/katex-M0IxphGf-CddkPoXu.js +257 -0
- package/client/assets/lib-4Tgx7rAy.js +114 -0
- package/client/assets/line-B1wBwzrY-f5wxpqoF.js +1 -0
- package/client/assets/line-CkyHfW7d.js +1 -0
- package/client/assets/linear-B9fuEF4c.js +1 -0
- package/client/assets/linear-lrGinF5_-CqNBync7.js +1 -0
- package/client/assets/map-CgrwEyH7-DWQFomlZ.js +1 -0
- package/client/assets/map-DsCK-0Cs.js +1 -0
- package/client/assets/marked.esm-CIU5FDdN.js +64 -0
- package/client/assets/marp-CSq0PPfK.js +3448 -0
- package/client/assets/material-symbols-outlined-DdRFxZLh.woff2 +0 -0
- package/client/assets/mermaid-parser.core-DC7NPJ_M-Ca6XzwfM.js +166 -0
- package/client/assets/mermaid-parser.core-SAwSf4_o.js +7 -0
- package/client/assets/mermaid.core-BBEQfkdJ.js +11 -0
- package/client/assets/mermaid.core-DZM3Ha-E-BxOo-RYG.js +11 -0
- package/client/assets/mindmap-definition-LN4V7U3C-B4BN0efL.js +96 -0
- package/client/assets/mindmap-definition-LN4V7U3C-DYtgcMsY-DynyLTNv.js +96 -0
- package/client/assets/ordinal-B9_Llu10-CU85fhaB.js +1 -0
- package/client/assets/ordinal-CopWnP7w.js +1 -0
- package/client/assets/packet-7NZHBO7P-CY4XRk9N.js +1 -0
- package/client/assets/packet-7NZHBO7P-lwb58iYx--vqSg65J.js +1 -0
- package/client/assets/path-BWPyau1x.js +1 -0
- package/client/assets/path-COivZ1Gw-C5riojK2.js +1 -0
- package/client/assets/pegDiagram-2B236MQR-BOKErjUu.js +1 -0
- package/client/assets/pegDiagram-2B236MQR-C43eIpKM-Bi_5Sxjt.js +1 -0
- package/client/assets/pie-RZYD4A2V-6BHB7bAx.js +1 -0
- package/client/assets/pie-RZYD4A2V-B1UWb4Gu-Dmn2hLWH.js +1 -0
- package/client/assets/pieDiagram-ENE6RG2P-BkTqgJyR-Bcdm19kR.js +39 -0
- package/client/assets/pieDiagram-ENE6RG2P-DtNjvz-U.js +39 -0
- package/client/assets/preload-helper-CZgWQFsJ.js +1 -0
- package/client/assets/purify.es-DY32g7DN.js +3 -0
- package/client/assets/quadrantDiagram-ABIIQ3AL-BQmAJL2v.js +7 -0
- package/client/assets/quadrantDiagram-ABIIQ3AL-Bm1Zjm45-B3orqx_f.js +7 -0
- package/client/assets/radar-I7S5WNFK-7CKcb_l--CqKdnR0v.js +1 -0
- package/client/assets/radar-I7S5WNFK-R7Jhjfdt.js +1 -0
- package/client/assets/railroad-3IZDKUUU-BepSPyHI.js +1 -0
- package/client/assets/railroad-3IZDKUUU-gCySKdnW-CKU26HTP.js +1 -0
- package/client/assets/railroad-abnf-AHOZXSZD-BlsHsp3c.js +1 -0
- package/client/assets/railroad-abnf-AHOZXSZD-BophH4r--CGkGHq54.js +1 -0
- package/client/assets/railroad-ebnf-EBAXGLYW-AZNjl_Zu-B7bn_PF3.js +1 -0
- package/client/assets/railroad-ebnf-EBAXGLYW-B4fMcGEH.js +1 -0
- package/client/assets/railroad-peg-LSFZ7HO6-B1ZrjJIu.js +1 -0
- package/client/assets/railroad-peg-LSFZ7HO6-BSiEEyeb-CP_B-N5g.js +1 -0
- package/client/assets/railroadDiagram-RFXS5EU6-8cz0Iby3.js +1 -0
- package/client/assets/railroadDiagram-RFXS5EU6-BkfbdeAs-Di6jH5I6.js +1 -0
- package/client/assets/requirementDiagram-TGXJPOKE-CrGTTjYg-DmIC7Shp.js +84 -0
- package/client/assets/requirementDiagram-TGXJPOKE-Dlqoe9TY.js +84 -0
- package/client/assets/rough.esm-CSKSodPl.js +1 -0
- package/client/assets/rough.esm-wVmwlXu7-CSKSodPl.js +1 -0
- package/client/assets/runtime-protocol-vue-WG3JLsjz.js +1 -0
- package/client/assets/runtime-vue-lHsY5zMj.js +1 -0
- package/client/assets/sankeyDiagram-HTMAVEWB-BkTKgu8Q.js +40 -0
- package/client/assets/sankeyDiagram-HTMAVEWB-rWXPf03Z-CjV9pCSJ.js +40 -0
- package/client/assets/{schemas-D_RbFtuQ.js → schemas-DGvl73AE.js} +3 -3
- package/client/assets/sequenceDiagram-DBY2YBRQ-BpuXUcFy.js +162 -0
- package/client/assets/sequenceDiagram-DBY2YBRQ-nkJYWO2m-BqS_sTCH.js +162 -0
- package/client/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +1 -0
- package/client/assets/sizeCapture-X5ZJPWSS-F5SadAuT-VHIb4_NU.js +1 -0
- package/client/assets/src-Brzfja-q-DNc4Or4z.js +1 -0
- package/client/assets/src-Cqa3Jy1j.js +1 -0
- package/client/assets/stateDiagram-2N3HPSRC-CdlGXuo7.js +1 -0
- package/client/assets/stateDiagram-2N3HPSRC-T4-clK8b-DQRiB4va.js +1 -0
- package/client/assets/stateDiagram-v2-6OUMAXLB-BN8Jr3ZM.js +1 -0
- package/client/assets/stateDiagram-v2-6OUMAXLB-DIp7nhRd-DbrOhUn_.js +1 -0
- package/client/assets/swimlanes-5IMT3BWC-HmQNEntu-CNBgZdur.js +2 -0
- package/client/assets/swimlanes-5IMT3BWC-iEJg5gki.js +2 -0
- package/client/assets/swimlanesDiagram-G3AALYLV-BeoZhwg7-DpqgeNRA.js +8 -0
- package/client/assets/swimlanesDiagram-G3AALYLV-o-sS6aEP.js +8 -0
- package/client/assets/timeline-definition-FHXFAJF6-Bjh-7QmL.js +120 -0
- package/client/assets/timeline-definition-FHXFAJF6-CNc9jSTP-BKNSymgY.js +120 -0
- package/client/assets/treeView-QDETBFTQ-D3cqRl6k.js +1 -0
- package/client/assets/treeView-QDETBFTQ-nIQcG1h9-O_ri9HxD.js +1 -0
- package/client/assets/treemap-6X3UGDF4-BnsvC8yL-Cfsty1e3.js +1 -0
- package/client/assets/treemap-6X3UGDF4-DGkZcvDG.js +1 -0
- package/client/assets/v4-B29ayslu.js +1 -0
- package/client/assets/vennDiagram-L72KCM5P-Cz3dbe7b.js +34 -0
- package/client/assets/vennDiagram-L72KCM5P-Dr3pTJ_0-C38xwcVo.js +34 -0
- package/client/assets/vue-C6d2VveO.js +1 -0
- package/client/assets/vue-i18n-CL-ejuNu.js +3 -0
- package/client/assets/vue.runtime.esm-bundler-BeoTfMNc.js +4 -0
- package/client/assets/wardley-OPB4EBWU-8Odxkx6V-CCDIVuBs.js +1 -0
- package/client/assets/wardley-OPB4EBWU-CZsCDvKk.js +1 -0
- package/client/assets/wardleyDiagram-EHGQE667-DSFc7ZZa-mlWXRiWx.js +78 -0
- package/client/assets/wardleyDiagram-EHGQE667-DiyoyO_X.js +78 -0
- package/client/assets/xychartDiagram-FW5EYKEG-BP0Nn4Pp-DRRiStGZ.js +7 -0
- package/client/assets/xychartDiagram-FW5EYKEG-DhY1_UGM.js +7 -0
- package/client/index.html +16 -12
- package/package.json +17 -19
- package/server/agent/backend/types.ts +1 -1
- package/server/agent/backgroundSessions.ts +43 -0
- package/server/agent/config.ts +3 -2
- package/server/agent/mcp-tools/manageCollection.ts +4 -4
- package/server/agent/sandboxMounts.ts +1 -1
- package/server/agent/stream.ts +1 -3
- package/server/api/auth/viewToken.ts +1 -1
- package/server/api/bridge/sessionRole.ts +47 -0
- package/server/api/routes/agent.ts +79 -2
- package/server/api/routes/collections.ts +60 -7
- package/server/api/routes/collectionsRegistry.ts +113 -0
- package/server/api/routes/dashboard.ts +47 -0
- package/server/api/routes/feeds.ts +2 -2
- package/server/api/routes/files.ts +1 -2
- package/server/api/routes/pdf.ts +48 -9
- package/server/api/routes/plugins.ts +2 -2
- package/server/api/routes/remoteHost.ts +55 -0
- package/server/api/routes/sessions.ts +20 -11
- package/server/api/routes/share.ts +105 -0
- package/server/api/routes/transcribe.ts +1 -1
- package/server/api/routes/wiki/history.ts +2 -2
- package/server/api/routes/wiki.ts +29 -177
- package/server/api/sandboxStatus.ts +1 -1
- package/server/build/dispatcher.mjs +25 -14928
- package/server/events/collection-change.ts +4 -4
- package/server/events/file-change.ts +2 -2
- package/server/events/scheduler-adapter.ts +3 -3
- package/server/events/task-manager/index.ts +3 -3
- package/server/index.ts +53 -17
- package/server/notifier/engine.ts +3 -3
- package/server/notifier/types.ts +2 -2
- package/server/plugins/html-builtin.ts +15 -1
- package/server/prompts/system/system.md +4 -0
- package/server/remoteHost/auth.ts +29 -0
- package/server/remoteHost/commandChannel.ts +56 -0
- package/server/remoteHost/firebase.ts +16 -0
- package/server/remoteHost/handlers/collectionPage.ts +29 -0
- package/server/remoteHost/handlers/getCollection.ts +32 -0
- package/server/remoteHost/handlers/getFeed.ts +35 -0
- package/server/remoteHost/handlers/index.ts +19 -0
- package/server/remoteHost/handlers/listCollections.ts +29 -0
- package/server/remoteHost/handlers/listFeeds.ts +37 -0
- package/server/remoteHost/handlers/listShortcuts.ts +24 -0
- package/server/remoteHost/handlers/startChat.ts +78 -0
- package/server/remoteHost/hostRunner.ts +122 -0
- package/server/remoteHost/index.ts +117 -0
- package/server/system/config.ts +1 -1
- package/server/system/credentials.ts +2 -3
- package/server/system/docker.ts +9 -8
- package/server/system/env.ts +8 -0
- package/server/system/whisper/index.ts +4 -4
- package/server/utils/claudeConfigPath.ts +57 -0
- package/server/utils/files/dashboard-io.ts +87 -0
- package/server/utils/files/session-io.ts +11 -0
- package/server/utils/markdown/frontmatter.ts +8 -4
- package/server/utils/regex.ts +4 -3
- package/server/utils/share/packHtml.ts +108 -0
- package/server/utils/share/rewriteAssets.ts +178 -0
- package/server/workspace/chat-index/paths.ts +8 -2
- package/server/workspace/chat-index/summarizer.ts +23 -15
- package/server/workspace/collections/configure.ts +5 -3
- package/server/workspace/collections/index.ts +8 -6
- package/server/workspace/collections/notifications.ts +3 -3
- package/server/workspace/collections/types.ts +2 -2
- package/server/workspace/collections/watcher.ts +2 -2
- package/server/workspace/feeds/configure.ts +24 -0
- package/server/workspace/hooks/handlers/skillBridge.ts +2 -2
- package/server/workspace/hooks/handlers/wikiSnapshot.ts +1 -1
- package/server/workspace/paths.ts +8 -1
- package/server/workspace/skills/catalog.ts +1 -3
- package/server/workspace/skills/external/catalog.ts +1 -3
- package/server/workspace/skills/paths.ts +4 -3
- package/server/workspace/skills/writer.ts +1 -4
- package/server/workspace/skills-preset.ts +3 -3
- package/server/workspace/wiki-pages/io.ts +2 -2
- package/server/workspace/workspace.ts +2 -2
- package/src/App.vue +41 -18
- package/src/components/ChatInput.vue +1 -1
- package/src/components/DashboardView.vue +372 -0
- package/src/components/FileContentRenderer.vue +32 -7
- package/src/components/JsonEditor.vue +1 -1
- package/src/components/PluginLauncher.vue +122 -79
- package/src/components/RemoteHostControl.vue +159 -0
- package/src/components/SessionCountBadges.vue +32 -0
- package/src/components/SessionHeaderControls.vue +1 -8
- package/src/components/SessionHistoryPanel.vue +2 -0
- package/src/components/SessionHistoryToggleButton.vue +1 -15
- package/src/components/SidebarHeader.vue +2 -0
- package/src/components/collectionTypes.ts +2 -2
- package/src/composables/accountingHost.ts +24 -0
- package/src/composables/collections/uiHost.ts +17 -2
- package/src/composables/useDashboard.ts +191 -0
- package/src/composables/useMarkdownZip.ts +51 -0
- package/src/composables/useNotifications.ts +1 -4
- package/src/composables/useSharePack.ts +60 -0
- package/src/composables/useTranslatedQueries.ts +15 -95
- package/src/composables/useTranslatedStrings.ts +68 -0
- package/src/composables/useVoiceInput.ts +3 -3
- package/src/config/apiRoutes.ts +57 -9
- package/src/config/createFilePolicy.ts +1 -1
- package/src/config/firebase.ts +12 -0
- package/src/config/firebaseConfig.ts +18 -0
- package/src/config/historyFilters.ts +7 -0
- package/src/config/pubsubChannels.ts +7 -27
- package/src/config/roles.ts +12 -10
- package/src/config/workspacePaths.ts +10 -0
- package/src/index.css +1 -1
- package/src/lang/de.ts +39 -220
- package/src/lang/en.ts +39 -191
- package/src/lang/es.ts +39 -218
- package/src/lang/fr.ts +39 -220
- package/src/lang/ja.ts +39 -216
- package/src/lang/ko.ts +39 -216
- package/src/lang/pt-BR.ts +39 -217
- package/src/lang/zh.ts +39 -215
- package/src/main.ts +3 -0
- package/src/plugins/accounting/definition.ts +12 -8
- package/src/plugins/accounting/index.ts +7 -4
- package/src/plugins/accounting/meta.ts +23 -60
- package/src/plugins/manageSkills/View.vue +9 -1
- package/src/plugins/photoLocations/View.vue +2 -2
- package/src/plugins/presentCollection/definition.ts +2 -2
- package/src/plugins/presentCollection/plugin.ts +2 -2
- package/src/plugins/presentCollection/types.ts +2 -2
- package/src/plugins/presentHtml/definition.ts +1 -1
- package/src/plugins/presentMulmoScript/View.vue +2 -2
- package/src/plugins/presentMulmoScript/helpers.ts +1 -1
- package/src/plugins/presentSVG/View.vue +2 -2
- package/src/plugins/scheduler/TasksTab.vue +1 -2
- package/src/plugins/skill/View.vue +7 -3
- package/src/plugins/spreadsheet/View.vue +3 -3
- package/src/plugins/spreadsheet/engine/functions/logical.ts +8 -1
- package/src/plugins/textResponse/View.vue +43 -16
- package/src/plugins/wiki/View.vue +29 -2
- package/src/plugins/wiki/components/WikiGraphView.vue +1 -1
- package/src/plugins/wiki/components/WikiPageBody.vue +4 -1
- package/src/plugins/wiki/helpers.ts +13 -81
- package/src/plugins/wiki/index.ts +1 -1
- package/src/router/guards.ts +1 -1
- package/src/router/index.ts +12 -0
- package/src/router/pageRoutes.ts +2 -0
- package/src/types/dashboard.ts +40 -0
- package/src/types/session.ts +3 -0
- package/src/utils/blobDownload.ts +19 -0
- package/src/utils/collections/presentSeed.ts +1 -1
- package/src/utils/html/customViewSrcdoc.ts +31 -3
- package/src/utils/html/previewCsp.ts +1 -1
- package/src/utils/id.ts +1 -1
- package/src/utils/image/htmlSrcAttrs.ts +1 -1
- package/src/utils/markdown/frontmatter.ts +12 -7
- package/src/utils/markdown/highlight.ts +22 -0
- package/src/utils/markdown/marpCustomSize.ts +2 -2
- package/src/utils/markdown/mermaidExtension.ts +56 -0
- package/src/utils/markdown/mermaidRender.ts +147 -0
- package/src/utils/markdown/setup.ts +17 -4
- package/src/utils/markdown/useMermaid.ts +33 -0
- package/src/utils/session/longRunning.ts +33 -0
- package/src/utils/session/mergeSessions.ts +1 -1
- package/client/assets/JsonEditor-Di5xGeZY.css +0 -1
- package/client/assets/JsonEditor-o5--tPQH.js +0 -10
- package/client/assets/index-tOu5ArRZ.css +0 -2
- package/client/assets/lib-D6Xy0IFc.js +0 -114
- package/client/assets/marp-D6GXA-EB.js +0 -3452
- package/client/assets/material-symbols-outlined-DtIK7AQn.woff2 +0 -0
- package/client/assets/runtime-protocol-vue-pU0Mw7Zm.js +0 -1
- package/client/assets/runtime-vue-fFYhnNg3.js +0 -1
- package/client/assets/vue-UDIWDtr8.js +0 -1
- package/client/assets/vue-i18n-CQbxVmNs.js +0 -3
- package/client/assets/vue.runtime.esm-bundler-BTyIdNAI.js +0 -4
- package/server/accounting/accountNormalize.ts +0 -32
- package/server/accounting/defaultAccounts.ts +0 -87
- package/server/accounting/eventPublisher.ts +0 -52
- package/server/accounting/journal.ts +0 -252
- package/server/accounting/openingBalances.ts +0 -114
- package/server/accounting/report.ts +0 -237
- package/server/accounting/service.ts +0 -718
- package/server/accounting/snapshotCache.ts +0 -334
- package/server/accounting/timeSeries.ts +0 -265
- package/server/accounting/types.ts +0 -148
- package/server/api/routes/accounting.ts +0 -373
- package/server/api/routes/wiki/frontmatter.ts +0 -34
- package/server/api/routes/wiki/pageIndex.ts +0 -53
- package/server/system/logs/aaa +0 -737
- package/server/system/logs/bb +0 -446
- package/server/utils/files/accounting-io.ts +0 -294
- package/server/workspace/feeds/engine.ts +0 -143
- package/server/workspace/feeds/fetch/httpClient.ts +0 -127
- package/server/workspace/feeds/fetch/rssParser.ts +0 -117
- package/server/workspace/feeds/index.ts +0 -8
- package/server/workspace/feeds/ingestTypes.ts +0 -62
- package/server/workspace/feeds/pathResolver.ts +0 -74
- package/server/workspace/feeds/paths.ts +0 -30
- package/server/workspace/feeds/projectItem.ts +0 -92
- package/server/workspace/feeds/registry.ts +0 -43
- package/server/workspace/feeds/retrievers/httpJson.ts +0 -19
- package/server/workspace/feeds/retrievers/index.ts +0 -27
- package/server/workspace/feeds/retrievers/registerAll.ts +0 -5
- package/server/workspace/feeds/retrievers/rss.ts +0 -24
- package/server/workspace/feeds/state.ts +0 -55
- package/src/composables/useAccountingChannel.ts +0 -58
- package/src/lib/wiki-page/graph.ts +0 -108
- package/src/lib/wiki-page/index-parse.ts +0 -221
- package/src/lib/wiki-page/link.ts +0 -62
- package/src/lib/wiki-page/lint.ts +0 -105
- package/src/lib/wiki-page/paths.ts +0 -35
- package/src/lib/wiki-page/slug.ts +0 -54
- package/src/plugins/accounting/Preview.vue +0 -103
- package/src/plugins/accounting/View.vue +0 -633
- package/src/plugins/accounting/actions.ts +0 -34
- package/src/plugins/accounting/api.ts +0 -301
- package/src/plugins/accounting/components/AccountEditor.vue +0 -250
- package/src/plugins/accounting/components/AccountRow.vue +0 -50
- package/src/plugins/accounting/components/AccountsList.vue +0 -102
- package/src/plugins/accounting/components/AccountsModal.vue +0 -301
- package/src/plugins/accounting/components/BalanceSheet.vue +0 -186
- package/src/plugins/accounting/components/BookSettings.vue +0 -284
- package/src/plugins/accounting/components/BookSwitcher.vue +0 -78
- package/src/plugins/accounting/components/DateRangePicker.vue +0 -140
- package/src/plugins/accounting/components/JournalEntryForm.vue +0 -505
- package/src/plugins/accounting/components/JournalList.vue +0 -554
- package/src/plugins/accounting/components/Ledger.vue +0 -206
- package/src/plugins/accounting/components/NewBookForm.vue +0 -211
- package/src/plugins/accounting/components/OpeningBalancesForm.vue +0 -272
- package/src/plugins/accounting/components/ProfitLoss.vue +0 -160
- package/src/plugins/accounting/components/accountDraft.ts +0 -13
- package/src/plugins/accounting/components/accountNumbering.ts +0 -103
- package/src/plugins/accounting/components/accountValidation.ts +0 -75
- package/src/plugins/accounting/components/useLatestRequest.ts +0 -44
- package/src/plugins/accounting/countries.ts +0 -158
- package/src/plugins/accounting/currencies.ts +0 -77
- package/src/plugins/accounting/dates.ts +0 -51
- package/src/plugins/accounting/fiscalYear.ts +0 -136
- package/src/plugins/accounting/timeSeriesEnums.ts +0 -16
- package/src/plugins/wiki/route.ts +0 -137
- /package/client/assets/{_plugin-vue_export-helper-B67ILkmu.js → _plugin-vue_export-helper-BDNMzG2s.js} +0 -0
- /package/client/assets/{purify.es-B27wDFIb-51iYcXuK.js → purify.es-B27wDFIb-Bu4Grnl0.js} +0 -0
- /package/client/assets/{typeof-DBp4T-Ny-z2wCIsir.js → typeof-DBp4T-Ny-B5XbjTb1.js} +0 -0
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// Public surface of the Feeds module. Routes + the scheduler import
|
|
2
|
-
// from here.
|
|
3
|
-
|
|
4
|
-
export { listFeeds, removeFeed } from "./registry.js";
|
|
5
|
-
export { refreshOne, refreshDue, type RefreshResult } from "./engine.js";
|
|
6
|
-
export { feedsRoot, feedDir, feedStatePath, FEEDS_DIR } from "./paths.js";
|
|
7
|
-
export { readFeedState, type FeedState } from "./state.js";
|
|
8
|
-
export { INGEST_KINDS, FEED_SCHEDULES, isFeedSchedule, type IngestSpec, type IngestKind, type FeedSchedule } from "./ingestTypes.js";
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// Declarative retrieval config for the "Feeds" mechanism. A Feed is a
|
|
2
|
-
// CollectionSchema plus this `ingest` block, registered as data (NOT as
|
|
3
|
-
// a skill) under `<workspace>/feeds/<slug>/schema.json`. The host's
|
|
4
|
-
// retrieval engine reads it to periodically refill the collection's
|
|
5
|
-
// records via the shared collections io layer.
|
|
6
|
-
//
|
|
7
|
-
// The ingest vocab (INGEST_KINDS / FEED_SCHEDULES + their literal-union types)
|
|
8
|
-
// now lives in @mulmoclaude/collection-plugin alongside the schema contract, so
|
|
9
|
-
// the package's schema validator can enforce it. Re-exported here so the feeds
|
|
10
|
-
// engine's existing importers resolve them unchanged.
|
|
11
|
-
import { type CollectionIngest, INGEST_KINDS, FEED_SCHEDULES, type IngestKind, type FeedSchedule } from "@mulmoclaude/collection-plugin";
|
|
12
|
-
//
|
|
13
|
-
// Declarative-only for now; the `kind` enum reserves room for future
|
|
14
|
-
// "code" (LLM-generated transform) and "prompt" (LLM-performed fetch)
|
|
15
|
-
// retrievers without reshaping the engine.
|
|
16
|
-
|
|
17
|
-
export { INGEST_KINDS, FEED_SCHEDULES, type IngestKind, type FeedSchedule };
|
|
18
|
-
|
|
19
|
-
const FEED_SCHEDULE_SET: ReadonlySet<string> = new Set(FEED_SCHEDULES);
|
|
20
|
-
|
|
21
|
-
export function isFeedSchedule(value: unknown): value is FeedSchedule {
|
|
22
|
-
return typeof value === "string" && FEED_SCHEDULE_SET.has(value);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Default cap on stored records per feed when `ingest.maxItems` is
|
|
26
|
-
* omitted. Keeps high-volume feeds (news / podcasts) bounded. */
|
|
27
|
-
export const DEFAULT_FEED_MAX_ITEMS = 100;
|
|
28
|
-
|
|
29
|
-
/** Declarative field map: target collection field name → source path
|
|
30
|
-
* into the raw item (dot/bracket path, e.g. `"title"` or
|
|
31
|
-
* `"data.name"`). */
|
|
32
|
-
export type IngestFieldMap = Record<string, string>;
|
|
33
|
-
|
|
34
|
-
/** The `ingest` block carried on a Feed's `CollectionSchema`. The canonical
|
|
35
|
-
* schema (in @mulmoclaude/collection-plugin) only promises the minimal
|
|
36
|
-
* `CollectionIngest` (kind/url/schedule as plain strings); this feeds-only
|
|
37
|
-
* subtype narrows those + adds the retrieval fields the engine needs. */
|
|
38
|
-
export interface IngestSpec extends CollectionIngest {
|
|
39
|
-
/** Which retriever handles this feed. */
|
|
40
|
-
kind: IngestKind;
|
|
41
|
-
/** Endpoint to fetch (http/https). */
|
|
42
|
-
url: string;
|
|
43
|
-
/** Refresh cadence. */
|
|
44
|
-
schedule: FeedSchedule;
|
|
45
|
-
/** `http-json` only: dot/bracket path to the array of items in the
|
|
46
|
-
* response (e.g. `"hourly[]"` or `"data.results[]"`). Ignored for
|
|
47
|
-
* `rss`/`atom`, which yield items natively. */
|
|
48
|
-
itemsAt?: string;
|
|
49
|
-
/** target field → source path. Projects each raw item into a record
|
|
50
|
-
* whose keys match the schema's `fields`. */
|
|
51
|
-
map: IngestFieldMap;
|
|
52
|
-
/** Optional source path used to derive the primaryKey value when the
|
|
53
|
-
* mapped record's primaryKey is empty (e.g. `"feedId"`). Falls back
|
|
54
|
-
* to a content hash of the record. */
|
|
55
|
-
idFrom?: string;
|
|
56
|
-
/** Cap on stored records. After each fetch the feed keeps only the
|
|
57
|
-
* newest `maxItems` (ordered by the schema's first `date` field) and
|
|
58
|
-
* deletes the rest. Defaults to {@link DEFAULT_FEED_MAX_ITEMS} when
|
|
59
|
-
* omitted; `0` disables the cap (keep everything). Pruning is skipped
|
|
60
|
-
* when the schema has no `date` field to order by. */
|
|
61
|
-
maxItems?: number;
|
|
62
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
// Tiny dot/bracket path resolver used by the declarative `ingest.map`
|
|
2
|
-
// and `ingest.itemsAt`. Pure, no I/O, exhaustively unit-testable.
|
|
3
|
-
//
|
|
4
|
-
// Supported syntax:
|
|
5
|
-
// "title" → root.title
|
|
6
|
-
// "data.name" → root.data.name
|
|
7
|
-
// "results[0].id" → root.results[0].id
|
|
8
|
-
// "hourly[]" → root.hourly (trailing [] = "the array
|
|
9
|
-
// here"; the marker is a no-op for value reads)
|
|
10
|
-
// "data.results[]" → root.data.results
|
|
11
|
-
//
|
|
12
|
-
// Any miss (wrong type, out-of-range index, absent key) yields
|
|
13
|
-
// `undefined` rather than throwing — declarative configs fail soft.
|
|
14
|
-
|
|
15
|
-
interface KeyToken {
|
|
16
|
-
kind: "key";
|
|
17
|
-
key: string;
|
|
18
|
-
}
|
|
19
|
-
interface IndexToken {
|
|
20
|
-
kind: "index";
|
|
21
|
-
index: number;
|
|
22
|
-
}
|
|
23
|
-
type PathToken = KeyToken | IndexToken;
|
|
24
|
-
|
|
25
|
-
const BRACKET_RE = /\[(\d*)\]/g;
|
|
26
|
-
|
|
27
|
-
/** Split one dot-segment (`results[0]`, `hourly[]`, `name`) into tokens.
|
|
28
|
-
* An empty `[]` is an array-identity marker and emits no token — the
|
|
29
|
-
* array value is read by the preceding key. */
|
|
30
|
-
function parseSegment(segment: string, tokens: PathToken[]): void {
|
|
31
|
-
const bracketStart = segment.indexOf("[");
|
|
32
|
-
const name = bracketStart === -1 ? segment : segment.slice(0, bracketStart);
|
|
33
|
-
if (name.length > 0) tokens.push({ kind: "key", key: name });
|
|
34
|
-
if (bracketStart === -1) return;
|
|
35
|
-
for (const match of segment.slice(bracketStart).matchAll(BRACKET_RE)) {
|
|
36
|
-
const [, inner] = match;
|
|
37
|
-
if (inner.length > 0) tokens.push({ kind: "index", index: Number(inner) });
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function tokenize(path: string): PathToken[] {
|
|
42
|
-
const tokens: PathToken[] = [];
|
|
43
|
-
for (const segment of path.split(".")) {
|
|
44
|
-
if (segment.length > 0) parseSegment(segment, tokens);
|
|
45
|
-
}
|
|
46
|
-
return tokens;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function step(current: unknown, token: PathToken): unknown {
|
|
50
|
-
if (current === null || current === undefined) return undefined;
|
|
51
|
-
if (token.kind === "key") {
|
|
52
|
-
if (typeof current !== "object" || Array.isArray(current)) return undefined;
|
|
53
|
-
return (current as Record<string, unknown>)[token.key];
|
|
54
|
-
}
|
|
55
|
-
return Array.isArray(current) ? current[token.index] : undefined;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Resolve a dot/bracket path against `root`, or `undefined` on any miss. */
|
|
59
|
-
export function getByPath(root: unknown, path: string): unknown {
|
|
60
|
-
let current: unknown = root;
|
|
61
|
-
for (const token of tokenize(path)) {
|
|
62
|
-
current = step(current, token);
|
|
63
|
-
}
|
|
64
|
-
return current;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Locate the array of raw items for a fetch. With `itemsAt` set, walk
|
|
68
|
-
* to it; without it, the response itself must be the array. Non-arrays
|
|
69
|
-
* yield `[]` so a malformed response is a no-op, not a crash. */
|
|
70
|
-
export function getItemsArray(root: unknown, itemsAt: string | undefined): unknown[] {
|
|
71
|
-
if (!itemsAt) return Array.isArray(root) ? root : [];
|
|
72
|
-
const value = getByPath(root, itemsAt);
|
|
73
|
-
return Array.isArray(value) ? value : [];
|
|
74
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
// Path helpers for the non-skill Feeds registry. Each feed lives at
|
|
2
|
-
// `<workspace>/feeds/<slug>/schema.json` (a CollectionSchema + `ingest`
|
|
3
|
-
// block) with its retrieval state alongside at
|
|
4
|
-
// `<workspace>/feeds/<slug>/_state.json`. Records land wherever the
|
|
5
|
-
// schema's `dataPath` points (validated by `resolveDataDir`), exactly
|
|
6
|
-
// like every other collection.
|
|
7
|
-
//
|
|
8
|
-
// Slugs reaching these helpers must already have passed `safeSlugName`
|
|
9
|
-
// (from `../collections/paths.js`) — these joins do not re-sanitize.
|
|
10
|
-
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
import { workspacePath } from "../workspace.js";
|
|
13
|
-
|
|
14
|
-
export const FEEDS_DIR = "feeds";
|
|
15
|
-
export const FEED_STATE_FILE = "_state.json";
|
|
16
|
-
|
|
17
|
-
/** Absolute path to the feeds registry root for a workspace. */
|
|
18
|
-
export function feedsRoot(workspaceRoot: string = workspacePath): string {
|
|
19
|
-
return path.join(workspaceRoot, FEEDS_DIR);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Absolute path to one feed's directory (`<root>/<slug>/`). */
|
|
23
|
-
export function feedDir(slug: string, workspaceRoot: string = workspacePath): string {
|
|
24
|
-
return path.join(feedsRoot(workspaceRoot), slug);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Absolute path to one feed's retrieval-state file. */
|
|
28
|
-
export function feedStatePath(slug: string, workspaceRoot: string = workspacePath): string {
|
|
29
|
-
return path.join(feedsRoot(workspaceRoot), slug, FEED_STATE_FILE);
|
|
30
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
// Project one raw retrieved item (a parsed RSS entry, or a JSON object)
|
|
2
|
-
// into a CollectionItem whose keys match the schema's fields, using the
|
|
3
|
-
// declarative `ingest.map`. Also derives the record's id.
|
|
4
|
-
//
|
|
5
|
-
// Id rule: a collection record's filename IS its primaryKey value, and
|
|
6
|
-
// that value must be a safe slug. Feed-native keys (RSS guids/URLs,
|
|
7
|
-
// ISO datetimes) usually are NOT slug-safe, so we slugify the natural
|
|
8
|
-
// key into a deterministic, stable id — same natural key → same id, so
|
|
9
|
-
// re-fetches upsert in place. The natural key comes from the mapped
|
|
10
|
-
// primaryKey value, then `ingest.idFrom`, then a content hash.
|
|
11
|
-
|
|
12
|
-
import { createHash } from "node:crypto";
|
|
13
|
-
import { getByPath } from "./pathResolver.js";
|
|
14
|
-
import type { CollectionItem, CollectionSchema } from "../collections/index.js";
|
|
15
|
-
import type { IngestSpec } from "./ingestTypes.js";
|
|
16
|
-
|
|
17
|
-
function asKeyString(value: unknown): string | null {
|
|
18
|
-
if (typeof value === "string" && value.trim().length > 0) return value.trim();
|
|
19
|
-
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Collapse an XML element object that carries body text alongside
|
|
24
|
-
* attributes (`{ "#text": "v", "@_x": "..." }`) or CDATA to its text.
|
|
25
|
-
* Generic — the host knows no field names, only this fast-xml-parser
|
|
26
|
-
* shape — so a tag like `<guid isPermaLink="false">id</guid>` maps to
|
|
27
|
-
* its text without the caller needing to write `.#text`. */
|
|
28
|
-
function unwrapTextNode(value: unknown): unknown {
|
|
29
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
30
|
-
const obj = value as Record<string, unknown>;
|
|
31
|
-
if (typeof obj["#text"] === "string") return obj["#text"];
|
|
32
|
-
if (typeof obj["#cdata"] === "string") return obj["#cdata"];
|
|
33
|
-
}
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Normalize a mapped value generically — driven by the SCHEMA's declared
|
|
38
|
-
* field type, never by the source field name. Today: unwrap XML text
|
|
39
|
-
* nodes, and coerce anything mapped into a `date` field to a `YYYY-MM-DD`
|
|
40
|
-
* civil date (the collection `date` type + calendar are day-granularity
|
|
41
|
-
* and parse strictly — a full RFC-3339 timestamp would be rejected). */
|
|
42
|
-
function normalizeValue(value: unknown, fieldType: string | undefined): unknown {
|
|
43
|
-
const unwrapped = unwrapTextNode(value);
|
|
44
|
-
if (fieldType === "date" && typeof unwrapped === "string") {
|
|
45
|
-
const millis = Date.parse(unwrapped);
|
|
46
|
-
if (Number.isFinite(millis)) return new Date(millis).toISOString().slice(0, 10);
|
|
47
|
-
}
|
|
48
|
-
return unwrapped;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Slugify a natural key into a stable, filename-safe id. Short, safe
|
|
52
|
-
* keys pass through (lowercased); long or unsafe keys collapse to a
|
|
53
|
-
* hash so the filename stays bounded and valid. */
|
|
54
|
-
function toSafeId(natural: string): string {
|
|
55
|
-
const collapsed = natural
|
|
56
|
-
.trim()
|
|
57
|
-
.toLowerCase()
|
|
58
|
-
.replace(/[^a-z0-9]+/g, "-");
|
|
59
|
-
// eslint-disable-next-line sonarjs/slow-regex -- anchored hyphen trim, linear, no catastrophic backtracking
|
|
60
|
-
const slug = collapsed.replace(/^-+|-+$/g, "");
|
|
61
|
-
if (slug.length > 0 && slug.length <= 80) return slug;
|
|
62
|
-
const hash = createHash("sha256")
|
|
63
|
-
.update(natural || "item", "utf-8")
|
|
64
|
-
.digest("hex")
|
|
65
|
-
.slice(0, 16);
|
|
66
|
-
return slug.length > 80 ? `${slug.slice(0, 60)}-${hash}` : `feed-${hash}`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function naturalKey(record: CollectionItem, rawItem: unknown, ingest: IngestSpec, schema: CollectionSchema): string {
|
|
70
|
-
const fromMapped = asKeyString(record[schema.primaryKey]);
|
|
71
|
-
if (fromMapped) return fromMapped;
|
|
72
|
-
if (ingest.idFrom) {
|
|
73
|
-
const fromId = asKeyString(unwrapTextNode(getByPath(rawItem, ingest.idFrom)));
|
|
74
|
-
if (fromId) return fromId;
|
|
75
|
-
}
|
|
76
|
-
return JSON.stringify(record);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Build a record from a raw fetched item (a parsed RSS/Atom element or a
|
|
80
|
-
* JSON object) using `ingest.map`. Each source path is resolved against
|
|
81
|
-
* the raw item and normalized per the target field's declared type. The
|
|
82
|
-
* returned record's primaryKey is set to the derived safe id (so it
|
|
83
|
-
* doubles as the filename). */
|
|
84
|
-
export function projectRecord(rawItem: unknown, ingest: IngestSpec, schema: CollectionSchema): CollectionItem {
|
|
85
|
-
const record: CollectionItem = {};
|
|
86
|
-
for (const [targetField, sourcePath] of Object.entries(ingest.map)) {
|
|
87
|
-
const value = normalizeValue(getByPath(rawItem, sourcePath), schema.fields?.[targetField]?.type);
|
|
88
|
-
if (value !== undefined) record[targetField] = value;
|
|
89
|
-
}
|
|
90
|
-
record[schema.primaryKey] = toSafeId(naturalKey(record, rawItem, ingest, schema));
|
|
91
|
-
return record;
|
|
92
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// List the registered data-source feeds. Feeds are CREATED / REMOVED by
|
|
2
|
-
// the agent writing / deleting `feeds/<slug>/schema.json` directly (see
|
|
3
|
-
// config/helps/feeds.md) — the host only discovers + retrieves them.
|
|
4
|
-
// icon / dataPath defaults for agent-authored feed schemas are applied in
|
|
5
|
-
// `collections/discovery.ts` (source === "feed").
|
|
6
|
-
|
|
7
|
-
import { rm } from "node:fs/promises";
|
|
8
|
-
import { workspacePath } from "../workspace.js";
|
|
9
|
-
import { log } from "../../system/logger/index.js";
|
|
10
|
-
import { discoverCollections, type LoadedCollection } from "../collections/index.js";
|
|
11
|
-
import { resolveDataDir, safeSlugName } from "@mulmoclaude/collection-plugin/server";
|
|
12
|
-
import { feedDir } from "./paths.js";
|
|
13
|
-
|
|
14
|
-
/** Every registered feed, as a discovered collection (carrying its
|
|
15
|
-
* validated schema, `ingest`, and resolved `dataDir`). */
|
|
16
|
-
export async function listFeeds(workspaceRoot: string = workspacePath): Promise<LoadedCollection[]> {
|
|
17
|
-
const all = await discoverCollections({ workspaceRoot });
|
|
18
|
-
return all.filter((collection) => collection.source === "feed");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Delete a feed entirely: its records AND its `feeds/<slug>/` directory
|
|
22
|
-
* (schema + state). Idempotent. Host-side only (backs the UI delete
|
|
23
|
-
* button); the agent removes a feed by deleting both directories itself.
|
|
24
|
-
*
|
|
25
|
-
* The records dir is derived from the SLUG (`data/feeds/<slug>`), never
|
|
26
|
-
* from the schema's `dataPath` — feeds are forced into that namespace at
|
|
27
|
-
* discovery, so a malformed/hostile `dataPath` can't redirect this delete
|
|
28
|
-
* at another app's data (e.g. `data/wiki`). `resolveDataDir` also rejects
|
|
29
|
-
* any path that escapes the workspace. */
|
|
30
|
-
export async function removeFeed(workspaceRoot: string, slug: string): Promise<boolean> {
|
|
31
|
-
const safe = safeSlugName(slug);
|
|
32
|
-
if (safe === null) return false;
|
|
33
|
-
const recordsDir = resolveDataDir(`data/feeds/${safe}`, workspaceRoot);
|
|
34
|
-
try {
|
|
35
|
-
if (recordsDir) await rm(recordsDir, { recursive: true, force: true });
|
|
36
|
-
await rm(feedDir(safe, workspaceRoot), { recursive: true, force: true });
|
|
37
|
-
log.info("feeds", "feed + records removed", { slug: safe });
|
|
38
|
-
return true;
|
|
39
|
-
} catch (error) {
|
|
40
|
-
log.warn("feeds", "feed remove failed", { slug: safe, error: String(error) });
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// Generic JSON-API retriever. Fetches JSON, walks `ingest.itemsAt` to
|
|
2
|
-
// the array of raw items, and projects each through `ingest.map` (whose
|
|
3
|
-
// source paths are dot/bracket paths into each raw item).
|
|
4
|
-
|
|
5
|
-
import { fetchJson } from "../fetch/httpClient.js";
|
|
6
|
-
import { getItemsArray } from "../pathResolver.js";
|
|
7
|
-
import { projectRecord } from "../projectItem.js";
|
|
8
|
-
import { registerRetriever, type RetrieveFn } from "./index.js";
|
|
9
|
-
|
|
10
|
-
const retrieveHttpJson: RetrieveFn = async (ingest, schema) => {
|
|
11
|
-
const json = await fetchJson(ingest.url);
|
|
12
|
-
const rawItems = getItemsArray(json, ingest.itemsAt);
|
|
13
|
-
const items = rawItems.map((raw) => projectRecord(raw, ingest, schema));
|
|
14
|
-
return { items, cursor: {} };
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
registerRetriever("http-json", retrieveHttpJson);
|
|
18
|
-
|
|
19
|
-
export { retrieveHttpJson };
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// Pluggable retriever registry. Each `ingest.kind` maps to one
|
|
2
|
-
// RetrieveFn that fetches the endpoint and returns projected records.
|
|
3
|
-
// Side-effect registration keeps the engine decoupled from the kinds.
|
|
4
|
-
// New kinds (`code`, `prompt`) register here without touching the engine.
|
|
5
|
-
|
|
6
|
-
import type { CollectionItem, CollectionSchema } from "../../collections/index.js";
|
|
7
|
-
import type { IngestSpec } from "../ingestTypes.js";
|
|
8
|
-
import type { FeedState } from "../state.js";
|
|
9
|
-
|
|
10
|
-
export interface RetrieveResult {
|
|
11
|
-
/** Projected records, keyed by primaryKey (the engine upserts them). */
|
|
12
|
-
items: CollectionItem[];
|
|
13
|
-
/** Updated retriever cursor to persist (incremental fetches). */
|
|
14
|
-
cursor: Record<string, string>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type RetrieveFn = (ingest: IngestSpec, schema: CollectionSchema, state: FeedState) => Promise<RetrieveResult>;
|
|
18
|
-
|
|
19
|
-
const registry = new Map<string, RetrieveFn>();
|
|
20
|
-
|
|
21
|
-
export function registerRetriever(kind: string, retriever: RetrieveFn): void {
|
|
22
|
-
registry.set(kind, retriever);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function getRetriever(kind: string): RetrieveFn | undefined {
|
|
26
|
-
return registry.get(kind);
|
|
27
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// RSS / Atom retriever. Fetches the feed, parses it, and projects each
|
|
2
|
-
// item's RAW parsed XML element through `ingest.map`. The map's source
|
|
3
|
-
// paths are the item's own tags/attributes (e.g. `title`, `pubDate`,
|
|
4
|
-
// `enclosure.@_url`, `itunes:duration`) — the host hard-codes no field
|
|
5
|
-
// list; the caller inspects the feed and maps what it carries.
|
|
6
|
-
|
|
7
|
-
import { fetchText } from "../fetch/httpClient.js";
|
|
8
|
-
import { parseFeed } from "../fetch/rssParser.js";
|
|
9
|
-
import { projectRecord } from "../projectItem.js";
|
|
10
|
-
import { registerRetriever, type RetrieveFn } from "./index.js";
|
|
11
|
-
|
|
12
|
-
const retrieveRss: RetrieveFn = async (ingest, schema) => {
|
|
13
|
-
const body = await fetchText(ingest.url);
|
|
14
|
-
const feed = parseFeed(body);
|
|
15
|
-
if (!feed) return { items: [], cursor: {} };
|
|
16
|
-
const items = feed.items.map((item) => projectRecord(item.raw, ingest, schema));
|
|
17
|
-
return { items, cursor: {} };
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
// Atom shares the same parser + projection path.
|
|
21
|
-
registerRetriever("rss", retrieveRss);
|
|
22
|
-
registerRetriever("atom", retrieveRss);
|
|
23
|
-
|
|
24
|
-
export { retrieveRss };
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// Per-feed retrieval state — when we last fetched, the retriever's
|
|
2
|
-
// cursor (for incremental fetches), and a consecutive-failure counter.
|
|
3
|
-
// NOT committed to git; lives at `<workspace>/feeds/<slug>/_state.json`.
|
|
4
|
-
// Deliberately minimal: the legacy `sources` tree carries richer backoff
|
|
5
|
-
// state, but the Feeds engine starts simple and grows on real need.
|
|
6
|
-
|
|
7
|
-
import { mkdir, readFile } from "node:fs/promises";
|
|
8
|
-
import { writeFileAtomic } from "../../utils/files/atomic.js";
|
|
9
|
-
import { log } from "../../system/logger/index.js";
|
|
10
|
-
import { feedDir, feedStatePath } from "./paths.js";
|
|
11
|
-
|
|
12
|
-
export interface FeedState {
|
|
13
|
-
slug: string;
|
|
14
|
-
/** ISO timestamp of the last successful fetch, or null if never. */
|
|
15
|
-
lastFetchedAt: string | null;
|
|
16
|
-
/** Free-form retriever cursor (e.g. last-seen id / etag). */
|
|
17
|
-
cursor: Record<string, string>;
|
|
18
|
-
/** Consecutive failed fetches; reset to 0 on success. */
|
|
19
|
-
consecutiveFailures: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function defaultFeedState(slug: string): FeedState {
|
|
23
|
-
return { slug, lastFetchedAt: null, cursor: {}, consecutiveFailures: 0 };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function normalizeState(slug: string, parsed: Partial<FeedState>): FeedState {
|
|
27
|
-
const base = defaultFeedState(slug);
|
|
28
|
-
const cursor = parsed.cursor && typeof parsed.cursor === "object" ? (parsed.cursor as Record<string, string>) : base.cursor;
|
|
29
|
-
return {
|
|
30
|
-
slug,
|
|
31
|
-
lastFetchedAt: typeof parsed.lastFetchedAt === "string" ? parsed.lastFetchedAt : base.lastFetchedAt,
|
|
32
|
-
cursor,
|
|
33
|
-
consecutiveFailures: typeof parsed.consecutiveFailures === "number" ? parsed.consecutiveFailures : base.consecutiveFailures,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Read a feed's state, tolerating a missing file (first run → default). */
|
|
38
|
-
export async function readFeedState(workspaceRoot: string, slug: string): Promise<FeedState> {
|
|
39
|
-
try {
|
|
40
|
-
const raw = await readFile(feedStatePath(slug, workspaceRoot), "utf-8");
|
|
41
|
-
return normalizeState(slug, JSON.parse(raw) as Partial<FeedState>);
|
|
42
|
-
} catch (err) {
|
|
43
|
-
const error = err as { code?: string };
|
|
44
|
-
if (error.code !== "ENOENT") {
|
|
45
|
-
log.warn("feeds", "failed to read feed state, using default", { slug, error: String(err) });
|
|
46
|
-
}
|
|
47
|
-
return defaultFeedState(slug);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Persist a feed's state atomically (creating the feed dir if needed). */
|
|
52
|
-
export async function writeFeedState(workspaceRoot: string, slug: string, state: FeedState): Promise<void> {
|
|
53
|
-
await mkdir(feedDir(slug, workspaceRoot), { recursive: true });
|
|
54
|
-
await writeFileAtomic(feedStatePath(slug, workspaceRoot), `${JSON.stringify(state, null, 2)}\n`);
|
|
55
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// Subscribe to per-book accounting events.
|
|
2
|
-
//
|
|
3
|
-
// Returns a `version` ref that bumps every time the server publishes a
|
|
4
|
-
// change for the given bookId — addEntries, voidEntry,
|
|
5
|
-
// setOpeningBalances, upsertAccount, snapshot rebuild completion. View
|
|
6
|
-
// components watch `version` to drive `refetch` calls.
|
|
7
|
-
//
|
|
8
|
-
// `bookId` is reactive: switching the active book in BookSwitcher
|
|
9
|
-
// flips it; the composable unsubscribes from the old channel and
|
|
10
|
-
// subscribes to the new one.
|
|
11
|
-
//
|
|
12
|
-
// `onPayload` is an optional fine-grained hook for callers that want to
|
|
13
|
-
// inspect the event kind (e.g. show a "rebuilding…" indicator on
|
|
14
|
-
// `kind: "snapshots-rebuilding"`).
|
|
15
|
-
|
|
16
|
-
import { ref, watch, onUnmounted, type Ref } from "vue";
|
|
17
|
-
import { usePubSub } from "./usePubSub";
|
|
18
|
-
import { accountingBookChannel, PUBSUB_CHANNELS, type AccountingBookChannelPayload } from "../config/pubsubChannels";
|
|
19
|
-
|
|
20
|
-
export interface UseAccountingChannelReturn {
|
|
21
|
-
/** Bumps on every accountingBookChannel event for the current
|
|
22
|
-
* bookId. Resets to 0 when bookId changes. */
|
|
23
|
-
version: Ref<number>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function useAccountingChannel(bookId: Ref<string | null>, onPayload?: (payload: AccountingBookChannelPayload) => void): UseAccountingChannelReturn {
|
|
27
|
-
const version = ref(0);
|
|
28
|
-
const { subscribe } = usePubSub();
|
|
29
|
-
let unsubscribe: (() => void) | null = null;
|
|
30
|
-
|
|
31
|
-
function bind(nextBookId: string | null): void {
|
|
32
|
-
unsubscribe?.();
|
|
33
|
-
unsubscribe = null;
|
|
34
|
-
version.value = 0;
|
|
35
|
-
if (!nextBookId) return;
|
|
36
|
-
unsubscribe = subscribe(accountingBookChannel(nextBookId), (data) => {
|
|
37
|
-
const event = data as AccountingBookChannelPayload;
|
|
38
|
-
version.value += 1;
|
|
39
|
-
onPayload?.(event);
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
watch(bookId, bind, { immediate: true });
|
|
44
|
-
onUnmounted(() => {
|
|
45
|
-
unsubscribe?.();
|
|
46
|
-
unsubscribe = null;
|
|
47
|
-
});
|
|
48
|
-
return { version };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Subscribe to "the list of books changed" events. Use in
|
|
52
|
-
* BookSwitcher.vue to refetch the dropdown contents when a sibling
|
|
53
|
-
* tab adds / deletes a book. */
|
|
54
|
-
export function useAccountingBooksChannel(onChange: () => void): void {
|
|
55
|
-
const { subscribe } = usePubSub();
|
|
56
|
-
const unsubscribe = subscribe(PUBSUB_CHANNELS.accountingBooks, onChange);
|
|
57
|
-
onUnmounted(() => unsubscribe());
|
|
58
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
// Pure builder for the wiki page→page link graph. Used by:
|
|
2
|
-
// - server/api/routes/wiki.ts — the `graph` action endpoint
|
|
3
|
-
// - src/plugins/wiki/View.vue — backlinks ("linked references") +
|
|
4
|
-
// the Graph tab
|
|
5
|
-
//
|
|
6
|
-
// All functions are pure string / collection ops; no `node:*` imports,
|
|
7
|
-
// so the frontend bundle can import them directly (same discipline as
|
|
8
|
-
// the sibling `link.ts` / `lint.ts` / `index-parse.ts` modules).
|
|
9
|
-
//
|
|
10
|
-
// NOTE: this is page→page link structure, distinct from the
|
|
11
|
-
// `server/workspace/wiki-backlinks/` module, which appends *session*
|
|
12
|
-
// backlinks (a page → the chat that edited it, #109). Different concept
|
|
13
|
-
// — do not conflate.
|
|
14
|
-
|
|
15
|
-
import { WIKI_LINK_PATTERN, parseWikiLink } from "./link.js";
|
|
16
|
-
import { wikiSlugify } from "./slug.js";
|
|
17
|
-
import type { WikiPageEntry } from "./index-parse.js";
|
|
18
|
-
|
|
19
|
-
export interface WikiGraphNode {
|
|
20
|
-
slug: string;
|
|
21
|
-
title: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface WikiGraphEdge {
|
|
25
|
-
from: string;
|
|
26
|
-
to: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface WikiGraph {
|
|
30
|
-
nodes: WikiGraphNode[];
|
|
31
|
-
edges: WikiGraphEdge[];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** One page's raw body plus its canonical (file-derived) slug. */
|
|
35
|
-
export interface WikiPageContent {
|
|
36
|
-
slug: string;
|
|
37
|
-
content: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Resolve a raw `[[link]]` target to an existing page slug, or null.
|
|
41
|
-
* Mirrors the route resolver's strategy: slugify first, then fall
|
|
42
|
-
* back to matching an index entry title so non-ASCII targets like
|
|
43
|
-
* `[[さくらインターネット]]` resolve to their ASCII file slug. */
|
|
44
|
-
export function resolveLinkTarget(target: string, fileSlugs: ReadonlySet<string>, slugByTitle: ReadonlyMap<string, string>): string | null {
|
|
45
|
-
const slug = wikiSlugify(target);
|
|
46
|
-
if (slug.length > 0 && fileSlugs.has(slug)) return slug;
|
|
47
|
-
const byTitle = slugByTitle.get(target.trim());
|
|
48
|
-
if (byTitle !== undefined && fileSlugs.has(byTitle)) return byTitle;
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Resolved, deduped outgoing slugs for one page body. Self-links and
|
|
53
|
-
* links to non-existent pages are dropped (the latter are already a
|
|
54
|
-
* lint "broken link", not a graph edge). */
|
|
55
|
-
export function pageOutgoingSlugs(fromSlug: string, content: string, fileSlugs: ReadonlySet<string>, slugByTitle: ReadonlyMap<string, string>): string[] {
|
|
56
|
-
const out = new Set<string>();
|
|
57
|
-
for (const match of content.matchAll(WIKI_LINK_PATTERN)) {
|
|
58
|
-
const { target } = parseWikiLink(match[1]);
|
|
59
|
-
const resolved = resolveLinkTarget(target, fileSlugs, slugByTitle);
|
|
60
|
-
if (resolved !== null && resolved !== fromSlug) out.add(resolved);
|
|
61
|
-
}
|
|
62
|
-
return [...out];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function buildTitleMaps(entries: readonly WikiPageEntry[]): { titleBySlug: Map<string, string>; slugByTitle: Map<string, string> } {
|
|
66
|
-
const titleBySlug = new Map<string, string>();
|
|
67
|
-
const slugByTitle = new Map<string, string>();
|
|
68
|
-
for (const entry of entries) {
|
|
69
|
-
if (!titleBySlug.has(entry.slug)) titleBySlug.set(entry.slug, entry.title);
|
|
70
|
-
if (entry.title.length > 0 && !slugByTitle.has(entry.title)) slugByTitle.set(entry.title, entry.slug);
|
|
71
|
-
}
|
|
72
|
-
return { titleBySlug, slugByTitle };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Build the full page→page graph. Nodes are the existing page files
|
|
76
|
-
* (titled from index.md, falling back to the slug for un-indexed
|
|
77
|
-
* pages); edges are the resolved `[[links]]`, deduped per (from,to). */
|
|
78
|
-
export function buildWikiGraph(pages: readonly WikiPageContent[], entries: readonly WikiPageEntry[]): WikiGraph {
|
|
79
|
-
const fileSlugs = new Set(pages.map((page) => page.slug));
|
|
80
|
-
const { titleBySlug, slugByTitle } = buildTitleMaps(entries);
|
|
81
|
-
const nodes: WikiGraphNode[] = pages.map((page) => ({ slug: page.slug, title: titleBySlug.get(page.slug) ?? page.slug }));
|
|
82
|
-
const edges: WikiGraphEdge[] = [];
|
|
83
|
-
const seen = new Set<string>();
|
|
84
|
-
for (const page of pages) {
|
|
85
|
-
for (const toSlug of pageOutgoingSlugs(page.slug, page.content, fileSlugs, slugByTitle)) {
|
|
86
|
-
// Newline cannot appear in a wiki slug, so it is a safe pair key.
|
|
87
|
-
const key = [page.slug, toSlug].join("\n");
|
|
88
|
-
if (seen.has(key)) continue;
|
|
89
|
-
seen.add(key);
|
|
90
|
-
edges.push({ from: page.slug, to: toSlug });
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return { nodes, edges };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Pages that link TO `slug` (incoming edges), deduped, as nodes. */
|
|
97
|
-
export function incomingLinks(graph: WikiGraph, slug: string): WikiGraphNode[] {
|
|
98
|
-
const nodeBySlug = new Map(graph.nodes.map((node) => [node.slug, node]));
|
|
99
|
-
const result: WikiGraphNode[] = [];
|
|
100
|
-
const seen = new Set<string>();
|
|
101
|
-
for (const edge of graph.edges) {
|
|
102
|
-
if (edge.to !== slug || seen.has(edge.from)) continue;
|
|
103
|
-
seen.add(edge.from);
|
|
104
|
-
const node = nodeBySlug.get(edge.from);
|
|
105
|
-
if (node) result.push(node);
|
|
106
|
-
}
|
|
107
|
-
return result;
|
|
108
|
-
}
|