excalidrop 0.1.3 → 0.2.1
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 +58 -131
- package/dist/cli.d.ts +0 -21
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +488 -439
- package/dist/cli.js.map +1 -1
- package/dist/frontend/assets/{advancedFormat-bKUmsig-.js → advancedFormat-CsxROTkH.js} +1 -1
- package/dist/frontend/assets/{ar-SA-G6X2FPQ2-C7K_UR3P.js → ar-SA-G6X2FPQ2-Bu8X6mOi.js} +1 -1
- package/dist/frontend/assets/arc-B9VNLSzj.js +1 -0
- package/dist/frontend/assets/{az-AZ-76LH7QW2-PkhuRuSr.js → az-AZ-76LH7QW2-DpvzY31_.js} +1 -1
- package/dist/frontend/assets/{bg-BG-XCXSNQG7-CgLRSYTl.js → bg-BG-XCXSNQG7-DCtZnLyo.js} +1 -1
- package/dist/frontend/assets/{blockDiagram-38ab4fdb-BFdBRYSn.js → blockDiagram-38ab4fdb-4cfDkUsw.js} +1 -1
- package/dist/frontend/assets/{blockDiagram-68f4deed-Ynbqr7bQ.js → blockDiagram-68f4deed-CsgWL6uC.js} +1 -1
- package/dist/frontend/assets/{bn-BD-2XOGV67Q-CEvkU0mz.js → bn-BD-2XOGV67Q-B-IdLNKT.js} +1 -1
- package/dist/frontend/assets/{c4Diagram-15b5d702-Dm7F1Pn_.js → c4Diagram-15b5d702-7BAqo4qT.js} +1 -1
- package/dist/frontend/assets/{c4Diagram-3d4e48cf-COkK-e67.js → c4Diagram-3d4e48cf-CDW-O-vg.js} +1 -1
- package/dist/frontend/assets/{ca-ES-6MX7JW3Y-DzP6q9bE.js → ca-ES-6MX7JW3Y-DM8olClc.js} +1 -1
- package/dist/frontend/assets/channel-jdNM4v-F.js +1 -0
- package/dist/frontend/assets/{classDiagram-70f12bd4-CRzulwvH.js → classDiagram-70f12bd4-D2U4i4yC.js} +1 -1
- package/dist/frontend/assets/{classDiagram-d40c83e7-DNbA-n-a.js → classDiagram-d40c83e7-DtBHRJc9.js} +1 -1
- package/dist/frontend/assets/classDiagram-v2-d5a6b087-Drykcxns.js +2 -0
- package/dist/frontend/assets/classDiagram-v2-f2320105-D8ruMBA6.js +2 -0
- package/dist/frontend/assets/clone-DFO_zGRO.js +1 -0
- package/dist/frontend/assets/{createText-2e5e7dd3-DB_k52Pn.js → createText-2e5e7dd3-1KPUhDpM.js} +1 -1
- package/dist/frontend/assets/{createText-d213de94-D-G49giw.js → createText-d213de94-C6EhGbUN.js} +4 -4
- package/dist/frontend/assets/{cs-CZ-2BRQDIVT-Bf4TOzpR.js → cs-CZ-2BRQDIVT-DgjTgz6K.js} +1 -1
- package/dist/frontend/assets/{cytoscape-cose-bilkent-DQYypj0P.js → cytoscape-cose-bilkent-BnIHl72R.js} +1 -1
- package/dist/frontend/assets/{da-DK-5WZEPLOC-jO-5kyXb.js → da-DK-5WZEPLOC-uEs3xGKF.js} +1 -1
- package/dist/frontend/assets/{de-DE-XR44H4JA-BkNXWOVh.js → de-DE-XR44H4JA-CBglLw42.js} +1 -1
- package/dist/frontend/assets/drop-BFhuMYSw.png +0 -0
- package/dist/frontend/assets/{edges-332bd1c7-CVvMtkTa.js → edges-332bd1c7-cWCBwcrg.js} +1 -1
- package/dist/frontend/assets/{edges-e0da2a9e-DQBAvC8-.js → edges-e0da2a9e-DKeDUJ_F.js} +1 -1
- package/dist/frontend/assets/{el-GR-BZB4AONW-Dv7Y9hMk.js → el-GR-BZB4AONW-B-_eub00.js} +1 -1
- package/dist/frontend/assets/{elk.bundled-YVS3PsMH.js → elk.bundled-Ch9TeAu4.js} +1 -1
- package/dist/frontend/assets/{erDiagram-880f2ed8-BjBeR38f.js → erDiagram-880f2ed8-CtDf_58r.js} +4 -4
- package/dist/frontend/assets/{erDiagram-9861fffd-B2Bi3R45.js → erDiagram-9861fffd-CAlUeljk.js} +1 -1
- package/dist/frontend/assets/{es-ES-U4NZUMDT-CWhs0SQY.js → es-ES-U4NZUMDT-BIN3bSVD.js} +1 -1
- package/dist/frontend/assets/{eu-ES-A7QVB2H4-BfkFwc3e.js → eu-ES-A7QVB2H4-TyQet3-N.js} +1 -1
- package/dist/frontend/assets/{fa-IR-HGAKTJCU-BgWzSj46.js → fa-IR-HGAKTJCU-DVmjjr57.js} +1 -1
- package/dist/frontend/assets/{fi-FI-Z5N7JZ37-CUyvkum-.js → fi-FI-Z5N7JZ37-ZW9kXLa8.js} +1 -1
- package/dist/frontend/assets/{flowDb-7c981674-BSZyyVBf.js → flowDb-7c981674-B5ppx8EN.js} +1 -1
- package/dist/frontend/assets/{flowDb-956e92f1-BC3-CLkF.js → flowDb-956e92f1-Dqi00Wq-.js} +1 -1
- package/dist/frontend/assets/{flowDiagram-66a62f08-H57DAvmM.js → flowDiagram-66a62f08-v3Liu0m_.js} +1 -1
- package/dist/frontend/assets/{flowDiagram-cbd28bf7-Bw4Z2nBD.js → flowDiagram-cbd28bf7-DJ_YHRwa.js} +3 -3
- package/dist/frontend/assets/flowDiagram-v2-96b9c2cf-6vlgZ0Bg.js +1 -0
- package/dist/frontend/assets/flowDiagram-v2-ffc7f31a-FmnI78Vm.js +1 -0
- package/dist/frontend/assets/{flowchart-elk-definition-36e2d292-1kZNdf3X.js → flowchart-elk-definition-36e2d292-BleYul6b.js} +2 -2
- package/dist/frontend/assets/{flowchart-elk-definition-4a651766-DDhG2Esi.js → flowchart-elk-definition-4a651766-D6lLh4Ih.js} +1 -1
- package/dist/frontend/assets/{fr-FR-RHASNOE6-7xzoRwbd.js → fr-FR-RHASNOE6-CxLR66tH.js} +1 -1
- package/dist/frontend/assets/{ganttDiagram-04f9e578-CtUSsXCz.js → ganttDiagram-04f9e578-DJSOyr8B.js} +1 -1
- package/dist/frontend/assets/{ganttDiagram-c361ad54-K42AklFY.js → ganttDiagram-c361ad54-CJmbCLvp.js} +1 -1
- package/dist/frontend/assets/{gitGraphDiagram-21fc4d3e-uW6XA6Y6.js → gitGraphDiagram-21fc4d3e-DiRq_FJc.js} +6 -6
- package/dist/frontend/assets/{gitGraphDiagram-72cf32ee-CCNbhnBg.js → gitGraphDiagram-72cf32ee-MfvDzoAb.js} +1 -1
- package/dist/frontend/assets/{gl-ES-HMX3MZ6V-CXuDDwNP.js → gl-ES-HMX3MZ6V-CxWj2itK.js} +1 -1
- package/dist/frontend/assets/{graph-CYOmcSYN.js → graph-CT_r46y9.js} +1 -1
- package/dist/frontend/assets/{graph-o42dOfI7.js → graph-DJt_Vezo.js} +1 -1
- package/dist/frontend/assets/{he-IL-6SHJWFNN-O9dzh9EM.js → he-IL-6SHJWFNN-5v2WFGO3.js} +1 -1
- package/dist/frontend/assets/{hi-IN-IWLTKZ5I-DlrOXhWs.js → hi-IN-IWLTKZ5I-BJGbvgRu.js} +1 -1
- package/dist/frontend/assets/{hu-HU-A5ZG7DT2-C3eNg-uI.js → hu-HU-A5ZG7DT2-REQT2EcE.js} +1 -1
- package/dist/frontend/assets/{id-ID-SAP4L64H-6iM4pI5c.js → id-ID-SAP4L64H-aZWQYWdg.js} +1 -1
- package/dist/frontend/assets/{index-3862675e-DrvLhypC.js → index-3862675e-BgrvgbsM.js} +1 -1
- package/dist/frontend/assets/{index-6079d271-BgVTRqg2.js → index-6079d271-Cp8jFHTC.js} +1 -1
- package/dist/frontend/assets/{index-HkUXslJy.js → index-DBHHeSXy.js} +4 -4
- package/dist/frontend/assets/index-GTq6y6g6.js +349 -0
- package/dist/frontend/assets/{infoDiagram-4a4f5b27-ZxmAcaW1.js → infoDiagram-4a4f5b27-C1ZJJtWv.js} +3 -3
- package/dist/frontend/assets/{infoDiagram-f8f76790-Bn-PhiFG.js → infoDiagram-f8f76790-GeS0JKxo.js} +1 -1
- package/dist/frontend/assets/{it-IT-JPQ66NNP-B26NQzGp.js → it-IT-JPQ66NNP-EyeA2Ty1.js} +1 -1
- package/dist/frontend/assets/{ja-JP-DBVTYXUO-CB0fvnYJ.js → ja-JP-DBVTYXUO-BLcE45D9.js} +1 -1
- package/dist/frontend/assets/{journeyDiagram-29694f62-ggDKs06f.js → journeyDiagram-29694f62-4Z_lpK4g.js} +5 -5
- package/dist/frontend/assets/{journeyDiagram-49397b02-pdDS-n-z.js → journeyDiagram-49397b02-ChwU3Zo6.js} +1 -1
- package/dist/frontend/assets/{kaa-6HZHGXH3-YNdBdcwJ.js → kaa-6HZHGXH3-CkpTcX9X.js} +1 -1
- package/dist/frontend/assets/{kab-KAB-ZGHBKWFO-D86pwZO0.js → kab-KAB-ZGHBKWFO-CChq0y6o.js} +1 -1
- package/dist/frontend/assets/{kk-KZ-P5N5QNE5-CpG5oheM.js → kk-KZ-P5N5QNE5-CwtoTY4z.js} +1 -1
- package/dist/frontend/assets/{km-KH-HSX4SM5Z-DYOZ3Azw.js → km-KH-HSX4SM5Z-D26sdohj.js} +1 -1
- package/dist/frontend/assets/{ko-KR-MTYHY66A-BHorm6lC.js → ko-KR-MTYHY66A-DvnDcJQA.js} +1 -1
- package/dist/frontend/assets/{ku-TR-6OUDTVRD-CM9RgEHv.js → ku-TR-6OUDTVRD-qdHbLgKg.js} +1 -1
- package/dist/frontend/assets/{layout-DW9Qf-At.js → layout-C-wUeNvb.js} +1 -1
- package/dist/frontend/assets/{layout-CCtkxrAf.js → layout-YRLNN4au.js} +1 -1
- package/dist/frontend/assets/{line-DvZfgg9P.js → line-BEgMClKG.js} +1 -1
- package/dist/frontend/assets/{linear-FwFrGRfa.js → linear-DDHpSdYw.js} +1 -1
- package/dist/frontend/assets/{lt-LT-XHIRWOB4-DvriKXBj.js → lt-LT-XHIRWOB4-DT4LYe_d.js} +1 -1
- package/dist/frontend/assets/{lv-LV-5QDEKY6T-DzQpwk87.js → lv-LV-5QDEKY6T-Dbc3ivwT.js} +1 -1
- package/dist/frontend/assets/{mindmap-definition-ac74a2e8-DsRo8f2w.js → mindmap-definition-ac74a2e8-CY2RhWUN.js} +6 -6
- package/dist/frontend/assets/{mindmap-definition-fc14e90a-dQ9reluI.js → mindmap-definition-fc14e90a-VT4JDi6N.js} +1 -1
- package/dist/frontend/assets/{mr-IN-CRQNXWMA-C7RzxxP5.js → mr-IN-CRQNXWMA-D75p1fVc.js} +1 -1
- package/dist/frontend/assets/{my-MM-5M5IBNSE-B-RRuy29.js → my-MM-5M5IBNSE-DnEoBXyE.js} +1 -1
- package/dist/frontend/assets/{nb-NO-T6EIAALU-B47x16Ik.js → nb-NO-T6EIAALU-BL4ThJxl.js} +1 -1
- package/dist/frontend/assets/{nl-NL-IS3SIHDZ-BxAKoQne.js → nl-NL-IS3SIHDZ-u18nUblY.js} +1 -1
- package/dist/frontend/assets/{nn-NO-6E72VCQL-dfpbEDSZ.js → nn-NO-6E72VCQL-B9LJ0oXc.js} +1 -1
- package/dist/frontend/assets/{oc-FR-POXYY2M6-gv6geBjV.js → oc-FR-POXYY2M6-BKG5kIcO.js} +1 -1
- package/dist/frontend/assets/{pa-IN-N4M65BXN-CY30-qH7.js → pa-IN-N4M65BXN-CmpnjbkL.js} +1 -1
- package/dist/frontend/assets/{pica-DWYGHLQV.js → pica-BcpcM0v1.js} +1 -1
- package/dist/frontend/assets/{pie-CDiWh1rj.js → pie-Swzl5NrI.js} +1 -1
- package/dist/frontend/assets/{pieDiagram-421022e6-Bgy-duyf.js → pieDiagram-421022e6-ndz96BLe.js} +5 -5
- package/dist/frontend/assets/{pieDiagram-8a3498a8-DH7FsbPK.js → pieDiagram-8a3498a8-D5rcV68O.js} +1 -1
- package/dist/frontend/assets/{pl-PL-T2D74RX3-TrHugsIA.js → pl-PL-T2D74RX3-CrR66Rgj.js} +1 -1
- package/dist/frontend/assets/{pt-BR-5N22H2LF-CJxdFp9c.js → pt-BR-5N22H2LF-Crv7jZiH.js} +1 -1
- package/dist/frontend/assets/{pt-PT-UZXXM6DQ-CB-1q6YJ.js → pt-PT-UZXXM6DQ-Bkaa5ESR.js} +1 -1
- package/dist/frontend/assets/{quadrantDiagram-0957ecba-DcwYI1oi.js → quadrantDiagram-0957ecba-DOdyxeTP.js} +3 -3
- package/dist/frontend/assets/{quadrantDiagram-120e2f19-CvHU5a9v.js → quadrantDiagram-120e2f19-beQD7R1C.js} +1 -1
- package/dist/frontend/assets/relay-BIOL2uw2.js +1 -0
- package/dist/frontend/assets/{requirementDiagram-23d650b8-tdagN8HP.js → requirementDiagram-23d650b8-B2i-OB97.js} +1 -1
- package/dist/frontend/assets/{requirementDiagram-deff3bca-BMGM-fSI.js → requirementDiagram-deff3bca-beS-SvfY.js} +1 -1
- package/dist/frontend/assets/{ro-RO-JPDTUUEW-DQq9DBOd.js → ro-RO-JPDTUUEW-DsrnQm7K.js} +1 -1
- package/dist/frontend/assets/{ru-RU-B4JR7IUQ-CWX32rfh.js → ru-RU-B4JR7IUQ-DrnGGdUs.js} +1 -1
- package/dist/frontend/assets/{sankeyDiagram-04a897e0-3i6zWpY2.js → sankeyDiagram-04a897e0-CVad45bN.js} +1 -1
- package/dist/frontend/assets/{sankeyDiagram-23345273-ncj1Ol7S.js → sankeyDiagram-23345273-ghWBXXF2.js} +1 -1
- package/dist/frontend/assets/{selectAll-DJwesbwT.js → selectAll-XplaO988.js} +1 -1
- package/dist/frontend/assets/{sequenceDiagram-17ac3bff-ChyKKRZA.js → sequenceDiagram-17ac3bff-DBI-EdWx.js} +1 -1
- package/dist/frontend/assets/{sequenceDiagram-704730f1-D57_wP9k.js → sequenceDiagram-704730f1-BnJ__mBt.js} +1 -1
- package/dist/frontend/assets/{si-LK-N5RQ5JYF-Lkc5ZxyG.js → si-LK-N5RQ5JYF-BxAwuRM5.js} +1 -1
- package/dist/frontend/assets/{sk-SK-C5VTKIMK-DyvYXjqZ.js → sk-SK-C5VTKIMK-CnDyiLRf.js} +1 -1
- package/dist/frontend/assets/{sl-SI-NN7IZMDC-D_MvP9Rh.js → sl-SI-NN7IZMDC-BXHXIdFA.js} +1 -1
- package/dist/frontend/assets/{stateDiagram-587899a1-OjFTnFLS.js → stateDiagram-587899a1-D3Ahxsgc.js} +1 -1
- package/dist/frontend/assets/{stateDiagram-9c5f0230-BzU36S5C.js → stateDiagram-9c5f0230-8XbLWvC0.js} +1 -1
- package/dist/frontend/assets/{stateDiagram-v2-51a3dcff-CfX1qo7U.js → stateDiagram-v2-51a3dcff-Cqf-MUGD.js} +1 -1
- package/dist/frontend/assets/{stateDiagram-v2-d93cdb3a-BH7Ud4sU.js → stateDiagram-v2-d93cdb3a-DjGrud9D.js} +1 -1
- package/dist/frontend/assets/{styles-2ab5d517-CA8x9Nh5.js → styles-2ab5d517-B4qdHOgu.js} +3 -3
- package/dist/frontend/assets/{styles-5f03d8d2-BnO2pLhV.js → styles-5f03d8d2-DOD2v5V8.js} +3 -3
- package/dist/frontend/assets/{styles-6aaf32cf-COnot1ea.js → styles-6aaf32cf-bP3XXcmf.js} +1 -1
- package/dist/frontend/assets/{styles-9a916d00-C3QkBgTF.js → styles-9a916d00-QLeuL_Dy.js} +1 -1
- package/dist/frontend/assets/{styles-c10674c1-CPAG2_rr.js → styles-c10674c1-Lpvh1u4f.js} +1 -1
- package/dist/frontend/assets/{styles-edf9a4b0-B8Q9fyu0.js → styles-edf9a4b0-D4W9aPm7.js} +1 -1
- package/dist/frontend/assets/subset-shared.chunk.js +9 -9
- package/dist/frontend/assets/subset-worker.chunk.js +1 -1
- package/dist/frontend/assets/{sv-SE-XGPEYMSR-t6voGQSI.js → sv-SE-XGPEYMSR-YrCQQBuo.js} +1 -1
- package/dist/frontend/assets/{svgDrawCommon-08f97a94-CdT21LWf.js → svgDrawCommon-08f97a94-CwNv3PjS.js} +1 -1
- package/dist/frontend/assets/{svgDrawCommon-3ba9043b-D1MU7jAE.js → svgDrawCommon-3ba9043b-DZbT0Bqu.js} +1 -1
- package/dist/frontend/assets/{ta-IN-2NMHFXQM-7VmfDDyV.js → ta-IN-2NMHFXQM-qR82aBSW.js} +1 -1
- package/dist/frontend/assets/{th-TH-HPSO5L25-B5rLEY9P.js → th-TH-HPSO5L25-C65V7hE5.js} +1 -1
- package/dist/frontend/assets/{timeline-definition-7e6b55e7-BIcL44O_.js → timeline-definition-7e6b55e7-GkPy3QeG.js} +3 -3
- package/dist/frontend/assets/{timeline-definition-85554ec2-TgrYYwpU.js → timeline-definition-85554ec2-CCdkBaC0.js} +3 -3
- package/dist/frontend/assets/{tr-TR-DEFEU3FU-0CgOHXTn.js → tr-TR-DEFEU3FU-CFzbwAKV.js} +1 -1
- package/dist/frontend/assets/{uk-UA-QMV73CPH-CzSFsIsx.js → uk-UA-QMV73CPH-ePt5WVz3.js} +1 -1
- package/dist/frontend/assets/{union-CnQAJu0l.js → union-CVZobYBh.js} +1 -1
- package/dist/frontend/assets/{vi-VN-M7AON7JQ-Bhx3rYU9.js → vi-VN-M7AON7JQ-CaPKykgJ.js} +1 -1
- package/dist/frontend/assets/{xychartDiagram-b6496bcd-BMKDop6v.js → xychartDiagram-b6496bcd-CV5WRc81.js} +4 -4
- package/dist/frontend/assets/{xychartDiagram-e933f94c-DAWithrN.js → xychartDiagram-e933f94c-BH-ySdQf.js} +1 -1
- package/dist/frontend/assets/{zh-CN-LNUGB5OW-Br52_wH4.js → zh-CN-LNUGB5OW-CK8AEj8o.js} +1 -1
- package/dist/frontend/assets/{zh-HK-E62DVLB3-Cj6mnIQ9.js → zh-HK-E62DVLB3-CUINiFgH.js} +1 -1
- package/dist/frontend/assets/{zh-TW-RAJ6MFWO-yLEsVBbC.js → zh-TW-RAJ6MFWO-BEBY7iJR.js} +1 -1
- package/dist/frontend/assets/zipObject-W8SLG3F1.js +1 -0
- package/dist/frontend/index.html +168 -235
- package/dist/{index.d.ts → mcp.d.ts} +1 -1
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +1183 -0
- package/dist/mcp.js.map +1 -0
- package/package.json +19 -34
- package/skills/excalidraw-skill/SKILL.md +42 -55
- package/skills/excalidraw-skill/references/cheatsheet.md +23 -56
- package/skills/excalidraw-skill/scripts/clear-canvas.cjs +9 -32
- package/skills/excalidraw-skill/scripts/create-element.cjs +18 -61
- package/skills/excalidraw-skill/scripts/delete-element.cjs +12 -42
- package/skills/excalidraw-skill/scripts/export-elements.cjs +17 -44
- package/skills/excalidraw-skill/scripts/gh-helper.cjs +58 -0
- package/skills/excalidraw-skill/scripts/healthcheck.cjs +8 -29
- package/skills/excalidraw-skill/scripts/import-elements.cjs +20 -70
- package/skills/excalidraw-skill/scripts/update-element.cjs +19 -63
- package/tsconfig.json +3 -2
- package/vite.config.js +3 -12
- package/dist/frontend/assets/arc-BI_FJWE7.js +0 -1
- package/dist/frontend/assets/channel-BqCiG5n3.js +0 -1
- package/dist/frontend/assets/classDiagram-v2-d5a6b087-l-GglywH.js +0 -2
- package/dist/frontend/assets/classDiagram-v2-f2320105-B_TY1zoc.js +0 -2
- package/dist/frontend/assets/clone-B6Jqcrku.js +0 -1
- package/dist/frontend/assets/flowDiagram-v2-96b9c2cf-CBjbm_oB.js +0 -1
- package/dist/frontend/assets/flowDiagram-v2-ffc7f31a-CoQufPp2.js +0 -1
- package/dist/frontend/assets/index-D-cr7jpJ.js +0 -349
- package/dist/frontend/assets/zipObject-BeXj5PVd.js +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2305
- package/dist/index.js.map +0 -1
- package/dist/remote.d.ts +0 -5
- package/dist/remote.d.ts.map +0 -1
- package/dist/remote.js +0 -129
- package/dist/remote.js.map +0 -1
- package/dist/server.d.ts +0 -4
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -1217
- package/dist/server.js.map +0 -1
- package/dist/target.d.ts +0 -41
- package/dist/target.d.ts.map +0 -1
- package/dist/target.js +0 -244
- package/dist/target.js.map +0 -1
- package/dist/types.d.ts +0 -228
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -53
- package/dist/types.js.map +0 -1
- package/dist/utils/auth.d.ts +0 -15
- package/dist/utils/auth.d.ts.map +0 -1
- package/dist/utils/auth.js +0 -68
- package/dist/utils/auth.js.map +0 -1
- package/dist/utils/github.d.ts +0 -26
- package/dist/utils/github.d.ts.map +0 -1
- package/dist/utils/github.js +0 -111
- package/dist/utils/github.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -4
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -25
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/r2store.d.ts +0 -19
- package/dist/utils/r2store.d.ts.map +0 -1
- package/dist/utils/r2store.js +0 -75
- package/dist/utils/r2store.js.map +0 -1
- package/frontend/index.html +0 -338
- package/frontend/src/App.tsx +0 -1091
- package/frontend/src/main.tsx +0 -15
- package/frontend/src/utils/ghSync.ts +0 -171
- package/frontend/src/utils/mermaidConverter.ts +0 -49
- package/frontend/src/utils/webmcp.ts +0 -285
- package/scripts/check-local-bind.mjs +0 -164
- package/scripts/postinstall.mjs +0 -23
- package/scripts/publish-pages.sh +0 -39
- package/scripts/seed-r2.mjs +0 -9
- package/src/cli.ts +0 -559
- package/src/index.ts +0 -2557
- package/src/remote.ts +0 -92
- package/src/server.ts +0 -1349
- package/src/target.ts +0 -208
- package/src/types.ts +0 -338
- package/src/utils/auth.ts +0 -61
- package/src/utils/github.ts +0 -93
- package/src/utils/logger.ts +0 -35
- package/src/utils/r2store.ts +0 -76
package/frontend/src/main.tsx
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import ReactDOM from 'react-dom/client'
|
|
3
|
-
import App from './App.tsx'
|
|
4
|
-
import '@excalidraw/excalidraw/index.css'
|
|
5
|
-
|
|
6
|
-
const rootElement = document.getElementById('root');
|
|
7
|
-
if (!rootElement) {
|
|
8
|
-
throw new Error('Root element not found');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
ReactDOM.createRoot(rootElement).render(
|
|
12
|
-
<React.StrictMode>
|
|
13
|
-
<App />
|
|
14
|
-
</React.StrictMode>,
|
|
15
|
-
)
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Static-viewer GitHub sync. Zero config on *.github.io:
|
|
3
|
-
* owner/repo are derived from the URL, token comes from
|
|
4
|
-
* localStorage (pasted PAT or agent handoff via #token=...).
|
|
5
|
-
* Device-flow login needs VITE_GITHUB_CLIENT_ID (one-time OAuth App setup).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface RepoRef { owner: string; repo: string; branch: string }
|
|
9
|
-
|
|
10
|
-
export function detectRepo(): RepoRef | null {
|
|
11
|
-
const explicit = (import.meta as any).env?.VITE_REPO_SLUG as string | undefined;
|
|
12
|
-
if (explicit?.includes('/')) {
|
|
13
|
-
const [owner, repo] = explicit.split('/');
|
|
14
|
-
return { owner, repo, branch: 'gh-pages' };
|
|
15
|
-
}
|
|
16
|
-
const host = window.location.hostname; // <owner>.github.io
|
|
17
|
-
const parts = window.location.pathname.split('/').filter(Boolean);
|
|
18
|
-
if (host.endsWith('.github.io') && parts.length > 0) {
|
|
19
|
-
return { owner: host.slice(0, -'.github.io'.length), repo: parts[0], branch: 'gh-pages' };
|
|
20
|
-
}
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const TOKEN_KEY = 'excalidrop_gh_token';
|
|
25
|
-
|
|
26
|
-
export function getToken(): string | null {
|
|
27
|
-
// Agent handoff: https://<site>/#token=gho_...
|
|
28
|
-
if (window.location.hash.startsWith('#token=')) {
|
|
29
|
-
const t = decodeURIComponent(window.location.hash.slice('#token='.length));
|
|
30
|
-
if (t) {
|
|
31
|
-
localStorage.setItem(TOKEN_KEY, t);
|
|
32
|
-
history.replaceState(null, '', window.location.pathname + window.location.search);
|
|
33
|
-
return t;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return localStorage.getItem(TOKEN_KEY);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function setToken(t: string | null): void {
|
|
40
|
-
if (t) localStorage.setItem(TOKEN_KEY, t);
|
|
41
|
-
else localStorage.removeItem(TOKEN_KEY);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function gh(path: string, token: string, init?: RequestInit): Promise<any> {
|
|
45
|
-
const r = await fetch(`https://api.github.com${path}`, {
|
|
46
|
-
...init,
|
|
47
|
-
headers: {
|
|
48
|
-
Authorization: `Bearer ${token}`,
|
|
49
|
-
Accept: 'application/vnd.github+json',
|
|
50
|
-
'Content-Type': 'application/json',
|
|
51
|
-
...(init?.headers || {}),
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
if (!r.ok) throw new Error(`GitHub ${r.status}: ${(await r.text()).slice(0, 200)}`);
|
|
55
|
-
return r.json();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Push scene JSON straight to the branch (viewer copy = source of truth for static mode). */
|
|
59
|
-
export async function pushScene(
|
|
60
|
-
ref: RepoRef, token: string, scene: { elements: unknown[]; files?: Record<string, unknown> },
|
|
61
|
-
): Promise<string> {
|
|
62
|
-
const path = 'canvas.excalidraw';
|
|
63
|
-
let sha: string | undefined;
|
|
64
|
-
try {
|
|
65
|
-
const cur = await gh(`/repos/${ref.owner}/${ref.repo}/contents/${path}?ref=${ref.branch}`, token);
|
|
66
|
-
sha = cur.sha;
|
|
67
|
-
} catch { /* new file */ }
|
|
68
|
-
const body: any = {
|
|
69
|
-
message: `excalidrop: autosync ${scene.elements.length} elements`,
|
|
70
|
-
content: btoa(unescape(encodeURIComponent(JSON.stringify(
|
|
71
|
-
{ type: 'excalidraw', version: 2, source: 'excalidrop', elements: scene.elements },
|
|
72
|
-
null, 2,
|
|
73
|
-
)))),
|
|
74
|
-
branch: ref.branch,
|
|
75
|
-
};
|
|
76
|
-
if (sha) body.sha = sha;
|
|
77
|
-
const out = await gh(`/repos/${ref.owner}/${ref.repo}/contents/${path}`, token, {
|
|
78
|
-
method: 'PUT',
|
|
79
|
-
body: JSON.stringify(body),
|
|
80
|
-
});
|
|
81
|
-
return out.content.sha as string;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** Load the static scene copy (no auth needed for public repos). */
|
|
85
|
-
export async function loadStaticScene(): Promise<{ elements: any[]; files?: Record<string, unknown> }> {
|
|
86
|
-
const r = await fetch('./canvas.excalidraw', { cache: 'no-cache' });
|
|
87
|
-
if (!r.ok) throw new Error(`scene fetch ${r.status}`);
|
|
88
|
-
const j = await r.json();
|
|
89
|
-
return { elements: j.elements || [], files: j.files || {} };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Device-flow login. Returns {userCode, verificationUri} — the token
|
|
93
|
-
* polling step may be CORS-blocked on static hosting, so the agent can
|
|
94
|
-
* complete it instead (github_login tool) and hand back a #token= link.
|
|
95
|
-
* NOTE: no `scope` — GitHub App user-tokens use fine-grained permissions
|
|
96
|
-
* (user ∩ app), never scopes. */
|
|
97
|
-
export async function startDeviceFlow(clientId: string): Promise<{ user_code: string; verification_uri: string }> {
|
|
98
|
-
const r = await fetch('https://github.com/login/device/code', {
|
|
99
|
-
method: 'POST',
|
|
100
|
-
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
|
101
|
-
body: JSON.stringify({ client_id: clientId }),
|
|
102
|
-
});
|
|
103
|
-
if (!r.ok) throw new Error('device flow start failed');
|
|
104
|
-
const j = await r.json();
|
|
105
|
-
return { user_code: j.user_code, verification_uri: j.verification_uri };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export type Access = 'editor' | 'viewer' | 'denied';
|
|
109
|
-
|
|
110
|
-
export type AccessDetail =
|
|
111
|
-
| 'app-not-installed' // ghu_ token, but the user has no app installation reaching this repo
|
|
112
|
-
| 'repo-not-covered' // installation(s) exist, but none of them include this repo
|
|
113
|
-
| 'no-token-permissions' // classic token with no read access to the repo
|
|
114
|
-
| null; // editor/viewer — no problem to report
|
|
115
|
-
|
|
116
|
-
/** What can this token do on this repo? Reads the user's app installations —
|
|
117
|
-
* the token only reaches repos both the user AND the app can access, so this
|
|
118
|
-
* mirrors GitHub's own enforcement (repo access == canvas access).
|
|
119
|
-
* `detail` tells the UI *why* access was denied so the login modal can
|
|
120
|
-
* point at the right fix (install the app vs. add this repo to the install). */
|
|
121
|
-
export async function checkAccess(ref: RepoRef, token: string): Promise<{ access: Access; login: string; detail: AccessDetail }> {
|
|
122
|
-
const me = await gh('/user', token);
|
|
123
|
-
const login = (me.login || '') as string;
|
|
124
|
-
const wanted = `${ref.owner}/${ref.repo}`.toLowerCase();
|
|
125
|
-
// Path 1: GitHub App user-token (ghu_) — installations list the repos it reaches.
|
|
126
|
-
// Classic PATs/OAuth tokens get 403 here ("must authenticate with an access
|
|
127
|
-
// token authorized to a GitHub App") and fall through to Path 2.
|
|
128
|
-
// per_page=100: without it a user with many repos can miss the hit (p.30 default).
|
|
129
|
-
const inst = await gh('/user/installations?per_page=100', token).catch(() => null);
|
|
130
|
-
if (inst?.installations) {
|
|
131
|
-
if ((inst.installations as unknown[]).length === 0) {
|
|
132
|
-
return { access: 'denied', login, detail: 'app-not-installed' };
|
|
133
|
-
}
|
|
134
|
-
for (const i of inst.installations || []) {
|
|
135
|
-
const repos = await gh(`/user/installations/${(i as any).id}/repositories?per_page=100`, token).catch(() => null);
|
|
136
|
-
const hit = (repos?.repositories || []).find((r: any) => r.full_name.toLowerCase() === wanted);
|
|
137
|
-
if (hit) {
|
|
138
|
-
// Token reaches the repo. Resolve read vs write via the collaborator
|
|
139
|
-
// permission endpoint; if that probe fails, start optimistic-editor —
|
|
140
|
-
// the Contents API is the real enforcement and a 403 push demotes to
|
|
141
|
-
// viewer (fail-open UI, fail-closed API).
|
|
142
|
-
const perms = await gh(`/repos/${ref.owner}/${ref.repo}/collaborators/${login}/permission`, token).catch(() => null);
|
|
143
|
-
const p = (perms?.permission || '') as string;
|
|
144
|
-
if (p === 'read' || p === 'triage') return { access: 'viewer', login, detail: null };
|
|
145
|
-
return { access: 'editor', login, detail: null };
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return { access: 'denied', login, detail: 'repo-not-covered' };
|
|
149
|
-
}
|
|
150
|
-
// Path 2: classic token — the repo endpoint echoes *this token's* permissions.
|
|
151
|
-
const repo = await gh(`/repos/${ref.owner}/${ref.repo}`, token).catch(() => null);
|
|
152
|
-
if (!repo?.permissions) return { access: 'denied', login, detail: 'no-token-permissions' };
|
|
153
|
-
if (repo.permissions.push || repo.permissions.admin || repo.permissions.maintain) {
|
|
154
|
-
return { access: 'editor', login, detail: null };
|
|
155
|
-
}
|
|
156
|
-
return { access: 'viewer', login, detail: null };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Shared GitHub App (one app, many installs — any user installs it on their
|
|
161
|
-
* own repo; the Client ID is public, not a secret). Overridable at publish
|
|
162
|
-
* time via VITE_GITHUB_CLIENT_ID / VITE_GITHUB_APP_SLUG.
|
|
163
|
-
*/
|
|
164
|
-
export const CLIENT_ID =
|
|
165
|
-
((import.meta as any).env?.VITE_GITHUB_CLIENT_ID as string | undefined) ||
|
|
166
|
-
'Iv23liuS2fx3QOEIoDmx'; // shared Excalidrop GitHub App (public ID, not a secret)
|
|
167
|
-
export const APP_SLUG =
|
|
168
|
-
((import.meta as any).env?.VITE_GITHUB_APP_SLUG as string | undefined) || 'excalidrop';
|
|
169
|
-
export const APP_INSTALL_URL = `https://github.com/apps/${APP_SLUG}/installations/new`;
|
|
170
|
-
/** True when a real Client ID is baked in (not the publish-time placeholder). */
|
|
171
|
-
export const HAS_CLIENT_ID = !!CLIENT_ID && !CLIENT_ID.includes('EXCALIDROP_APP');
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { parseMermaidToExcalidraw, MermaidConfig } from '@excalidraw/mermaid-to-excalidraw';
|
|
2
|
-
import type { ExcalidrawElement } from '@excalidraw/excalidraw/types/element/types';
|
|
3
|
-
import type { BinaryFiles } from '@excalidraw/excalidraw/types/types';
|
|
4
|
-
|
|
5
|
-
export interface MermaidConversionResult {
|
|
6
|
-
elements: readonly ExcalidrawElement[];
|
|
7
|
-
files?: BinaryFiles;
|
|
8
|
-
error?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Converts a Mermaid diagram definition to Excalidraw elements
|
|
13
|
-
* This function needs to run in the browser context as it requires DOM access
|
|
14
|
-
*/
|
|
15
|
-
export const convertMermaidToExcalidraw = async (
|
|
16
|
-
mermaidDefinition: string,
|
|
17
|
-
config?: MermaidConfig
|
|
18
|
-
): Promise<MermaidConversionResult> => {
|
|
19
|
-
try {
|
|
20
|
-
// Parse the Mermaid diagram to Excalidraw elements
|
|
21
|
-
const result = await parseMermaidToExcalidraw(mermaidDefinition, config);
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
elements: result.elements,
|
|
25
|
-
files: result.files,
|
|
26
|
-
};
|
|
27
|
-
} catch (error) {
|
|
28
|
-
console.error('Error converting Mermaid to Excalidraw:', error);
|
|
29
|
-
return {
|
|
30
|
-
elements: [],
|
|
31
|
-
error: error instanceof Error ? error.message : String(error),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Default Mermaid configuration for Excalidraw conversion
|
|
38
|
-
*/
|
|
39
|
-
export const DEFAULT_MERMAID_CONFIG: MermaidConfig = {
|
|
40
|
-
startOnLoad: false,
|
|
41
|
-
flowchart: {
|
|
42
|
-
curve: 'linear',
|
|
43
|
-
},
|
|
44
|
-
themeVariables: {
|
|
45
|
-
fontSize: '20px',
|
|
46
|
-
},
|
|
47
|
-
maxEdges: 500,
|
|
48
|
-
maxTextSize: 50000,
|
|
49
|
-
};
|
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Thin WebMCP layer for the Excalidrop canvas.
|
|
3
|
-
*
|
|
4
|
-
* Exposes the live in-tab Excalidraw scene as browser-native tools via
|
|
5
|
-
* `document.modelContext.registerTool()` (W3C WebML CG draft, Chromium 146+
|
|
6
|
-
* behind `#enable-webmcp-testing`). No-ops where the API is absent.
|
|
7
|
-
*
|
|
8
|
-
* Tools mirror the local MCP names so agents recognise them:
|
|
9
|
-
* - describe_scene (read-only)
|
|
10
|
-
* - create_elements (write)
|
|
11
|
-
* - update_element (write)
|
|
12
|
-
* - delete_element (write)
|
|
13
|
-
* - clear_canvas (consequential)
|
|
14
|
-
*
|
|
15
|
-
* The page itself is the tool registry — no HTTP/SSE/stdio involved.
|
|
16
|
-
* Auth/session comes for free: tools run inside the user's tab.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
interface MinimalCanvasApi {
|
|
20
|
-
getSceneElements(): Array<Record<string, any>>;
|
|
21
|
-
updateScene(scene: { elements: Array<Record<string, any>> }): void;
|
|
22
|
-
resetScene(): void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export type CanvasApiGetter = () => MinimalCanvasApi | null | undefined;
|
|
26
|
-
|
|
27
|
-
export interface WebMCPHandle {
|
|
28
|
-
available: boolean;
|
|
29
|
-
count: number;
|
|
30
|
-
cleanup: () => void;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface ModelContextLike {
|
|
34
|
-
registerTool(
|
|
35
|
-
tool: {
|
|
36
|
-
name: string;
|
|
37
|
-
title?: string;
|
|
38
|
-
description: string;
|
|
39
|
-
inputSchema: Record<string, unknown>;
|
|
40
|
-
annotations?: Record<string, unknown>;
|
|
41
|
-
execute: (args: any, opts?: { signal?: AbortSignal }) => unknown | Promise<unknown>;
|
|
42
|
-
},
|
|
43
|
-
options?: { signal?: AbortSignal },
|
|
44
|
-
): Promise<void>;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function getModelContext(): ModelContextLike | null {
|
|
48
|
-
try {
|
|
49
|
-
const doc = document as unknown as Record<string, unknown>;
|
|
50
|
-
const nav = navigator as unknown as Record<string, unknown>;
|
|
51
|
-
const mc =
|
|
52
|
-
(doc['modelContext'] as ModelContextLike | undefined) ??
|
|
53
|
-
(nav['modelContext'] as ModelContextLike | undefined);
|
|
54
|
-
if (mc && typeof mc.registerTool === 'function') return mc;
|
|
55
|
-
} catch {
|
|
56
|
-
/* non-DOM / restricted context */
|
|
57
|
-
}
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function newId(): string {
|
|
62
|
-
try {
|
|
63
|
-
if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
|
|
64
|
-
return (crypto as Crypto).randomUUID().slice(0, 12);
|
|
65
|
-
}
|
|
66
|
-
} catch {
|
|
67
|
-
/* fall through */
|
|
68
|
-
}
|
|
69
|
-
return `el_${Math.random().toString(36).slice(2, 10)}`;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function summarize(el: Record<string, any>): Record<string, unknown> {
|
|
73
|
-
const out: Record<string, unknown> = {
|
|
74
|
-
id: el['id'],
|
|
75
|
-
type: el['type'],
|
|
76
|
-
x: Math.round(Number(el['x'] ?? 0)),
|
|
77
|
-
y: Math.round(Number(el['y'] ?? 0)),
|
|
78
|
-
};
|
|
79
|
-
if (el['width'] !== undefined) out['width'] = el['width'];
|
|
80
|
-
if (el['height'] !== undefined) out['height'] = el['height'];
|
|
81
|
-
const text =
|
|
82
|
-
(typeof el['text'] === 'string' && el['text']) ||
|
|
83
|
-
(el['label'] as { text?: string } | undefined)?.text;
|
|
84
|
-
if (text) out['text'] = String(text).slice(0, 120);
|
|
85
|
-
if (el['locked']) out['locked'] = true;
|
|
86
|
-
return out;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const ELEMENT_SCHEMA: Record<string, unknown> = {
|
|
90
|
-
type: 'object',
|
|
91
|
-
properties: {
|
|
92
|
-
id: { type: 'string', description: 'Custom element ID (auto-generated if omitted)' },
|
|
93
|
-
type: { type: 'string', description: 'Excalidraw element type: rectangle, ellipse, diamond, text, arrow, line, freedraw, image' },
|
|
94
|
-
x: { type: 'number' },
|
|
95
|
-
y: { type: 'number' },
|
|
96
|
-
width: { type: 'number' },
|
|
97
|
-
height: { type: 'number' },
|
|
98
|
-
text: { type: 'string', description: 'Label text for shapes / content for text elements' },
|
|
99
|
-
backgroundColor: { type: 'string' },
|
|
100
|
-
strokeColor: { type: 'string' },
|
|
101
|
-
strokeWidth: { type: 'number' },
|
|
102
|
-
fontSize: { type: 'number' },
|
|
103
|
-
startElementId: { type: 'string', description: 'For arrows: bind start to this element ID' },
|
|
104
|
-
endElementId: { type: 'string', description: 'For arrows: bind end to this element ID' },
|
|
105
|
-
},
|
|
106
|
-
required: ['type', 'x', 'y'],
|
|
107
|
-
additionalProperties: true,
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Register the thin WebMCP toolset against the live canvas.
|
|
112
|
-
* Safe to call anywhere: feature-detects `document.modelContext` first.
|
|
113
|
-
* Returns a handle whose `cleanup()` unregisters all tools (AbortSignal).
|
|
114
|
-
*/
|
|
115
|
-
export function registerCanvasWebMCP(getApi: CanvasApiGetter): WebMCPHandle {
|
|
116
|
-
const noop = () => {};
|
|
117
|
-
const mc = getModelContext();
|
|
118
|
-
if (!mc) return { available: false, count: 0, cleanup: noop };
|
|
119
|
-
|
|
120
|
-
const controller = new AbortController();
|
|
121
|
-
const signal = controller.signal;
|
|
122
|
-
let count = 0;
|
|
123
|
-
|
|
124
|
-
const requireApi = (): MinimalCanvasApi => {
|
|
125
|
-
const api = getApi();
|
|
126
|
-
if (!api) throw new Error('Canvas not ready yet');
|
|
127
|
-
return api;
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const safeRegister = (tool: Parameters<ModelContextLike['registerTool']>[0]): void => {
|
|
131
|
-
try {
|
|
132
|
-
const p = mc.registerTool(tool, { signal });
|
|
133
|
-
if (p && typeof (p as Promise<void>).catch === 'function') {
|
|
134
|
-
(p as Promise<void>).catch(() => {
|
|
135
|
-
/* duplicate / invalid — ignore, keeps thin layer best-effort */
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
count += 1;
|
|
139
|
-
} catch {
|
|
140
|
-
/* API present but rejected synchronously — ignore */
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
safeRegister({
|
|
145
|
-
name: 'describe_scene',
|
|
146
|
-
title: 'Describe canvas scene',
|
|
147
|
-
description:
|
|
148
|
-
'Read-only summary of the live Excalidraw canvas: element counts by type plus id/type/position/label per element (capped at 200). Call before editing.',
|
|
149
|
-
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
150
|
-
annotations: { readOnlyHint: true },
|
|
151
|
-
execute: () => {
|
|
152
|
-
const api = requireApi();
|
|
153
|
-
const els = api.getSceneElements().filter((e) => !e['isDeleted']);
|
|
154
|
-
const byType: Record<string, number> = {};
|
|
155
|
-
for (const e of els) {
|
|
156
|
-
const t = String(e['type'] ?? 'unknown');
|
|
157
|
-
byType[t] = (byType[t] ?? 0) + 1;
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
count: els.length,
|
|
161
|
-
byType,
|
|
162
|
-
elements: els.slice(0, 200).map(summarize),
|
|
163
|
-
};
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
safeRegister({
|
|
168
|
-
name: 'create_elements',
|
|
169
|
-
title: 'Create canvas elements',
|
|
170
|
-
description:
|
|
171
|
-
'Add new elements to the live canvas in one batch. Assign custom ids to shapes so arrows can reference them via startElementId/endElementId.',
|
|
172
|
-
inputSchema: {
|
|
173
|
-
type: 'object',
|
|
174
|
-
properties: { elements: { type: 'array', items: ELEMENT_SCHEMA, minItems: 1 } },
|
|
175
|
-
required: ['elements'],
|
|
176
|
-
additionalProperties: false,
|
|
177
|
-
},
|
|
178
|
-
annotations: { destructiveHint: false },
|
|
179
|
-
execute: (args: { elements?: Array<Record<string, any>> }) => {
|
|
180
|
-
const api = requireApi();
|
|
181
|
-
const input = Array.isArray(args?.elements) ? args.elements : [];
|
|
182
|
-
if (input.length === 0) throw new Error('elements must be a non-empty array');
|
|
183
|
-
if (input.length > 200) throw new Error('max 200 elements per call');
|
|
184
|
-
const created = input.map((raw) => {
|
|
185
|
-
if (!raw || typeof raw !== 'object') throw new Error('each element must be an object');
|
|
186
|
-
if (typeof raw['type'] !== 'string') throw new Error('each element needs a type');
|
|
187
|
-
if (typeof raw['x'] !== 'number' || typeof raw['y'] !== 'number') {
|
|
188
|
-
throw new Error('each element needs numeric x and y');
|
|
189
|
-
}
|
|
190
|
-
const { startElementId, endElementId, ...rest } = raw;
|
|
191
|
-
const el: Record<string, any> = { id: newId(), ...rest };
|
|
192
|
-
if (typeof startElementId === 'string') el['start'] = { id: startElementId };
|
|
193
|
-
if (typeof endElementId === 'string') el['end'] = { id: endElementId };
|
|
194
|
-
if ((el['type'] === 'arrow' || el['type'] === 'line') && !Array.isArray(el['points'])) {
|
|
195
|
-
el['points'] = [
|
|
196
|
-
[0, 0],
|
|
197
|
-
[100, 0],
|
|
198
|
-
];
|
|
199
|
-
}
|
|
200
|
-
return el;
|
|
201
|
-
});
|
|
202
|
-
const current = api.getSceneElements();
|
|
203
|
-
api.updateScene({ elements: [...current, ...created] });
|
|
204
|
-
return { ok: true, count: created.length, ids: created.map((e) => e['id']) };
|
|
205
|
-
},
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
safeRegister({
|
|
209
|
-
name: 'update_element',
|
|
210
|
-
title: 'Update canvas element',
|
|
211
|
-
description: 'Patch position, size, colors, or text of one element by id on the live canvas.',
|
|
212
|
-
inputSchema: {
|
|
213
|
-
type: 'object',
|
|
214
|
-
properties: {
|
|
215
|
-
id: { type: 'string', description: 'Element ID from describe_scene' },
|
|
216
|
-
patch: {
|
|
217
|
-
type: 'object',
|
|
218
|
-
description: 'Fields to merge (x, y, width, height, backgroundColor, strokeColor, text, ...)',
|
|
219
|
-
},
|
|
220
|
-
},
|
|
221
|
-
required: ['id', 'patch'],
|
|
222
|
-
additionalProperties: false,
|
|
223
|
-
},
|
|
224
|
-
annotations: { destructiveHint: false },
|
|
225
|
-
execute: (args: { id?: string; patch?: Record<string, unknown> }) => {
|
|
226
|
-
const api = requireApi();
|
|
227
|
-
if (!args?.id) throw new Error('id is required');
|
|
228
|
-
if (!args?.patch || typeof args.patch !== 'object') throw new Error('patch must be an object');
|
|
229
|
-
const current = api.getSceneElements();
|
|
230
|
-
const idx = current.findIndex((e) => e['id'] === args.id);
|
|
231
|
-
if (idx === -1) throw new Error(`Element ${args.id} not found`);
|
|
232
|
-
const next = current.slice();
|
|
233
|
-
next[idx] = { ...current[idx], ...args.patch, id: args.id };
|
|
234
|
-
api.updateScene({ elements: next });
|
|
235
|
-
return { ok: true, id: args.id };
|
|
236
|
-
},
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
safeRegister({
|
|
240
|
-
name: 'delete_element',
|
|
241
|
-
title: 'Delete canvas element',
|
|
242
|
-
description: 'Remove one element by id from the live canvas.',
|
|
243
|
-
inputSchema: {
|
|
244
|
-
type: 'object',
|
|
245
|
-
properties: { id: { type: 'string' } },
|
|
246
|
-
required: ['id'],
|
|
247
|
-
additionalProperties: false,
|
|
248
|
-
},
|
|
249
|
-
annotations: { destructiveHint: true },
|
|
250
|
-
execute: (args: { id?: string }) => {
|
|
251
|
-
const api = requireApi();
|
|
252
|
-
if (!args?.id) throw new Error('id is required');
|
|
253
|
-
const current = api.getSceneElements();
|
|
254
|
-
if (!current.some((e) => e['id'] === args.id)) throw new Error(`Element ${args.id} not found`);
|
|
255
|
-
api.updateScene({ elements: current.filter((e) => e['id'] !== args.id) });
|
|
256
|
-
return { ok: true, deleted: args.id };
|
|
257
|
-
},
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
safeRegister({
|
|
261
|
-
name: 'clear_canvas',
|
|
262
|
-
title: 'Clear canvas',
|
|
263
|
-
description: 'Remove ALL elements from the live canvas. Ask the user to confirm first.',
|
|
264
|
-
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
265
|
-
annotations: { destructiveHint: true, consequentialHint: true },
|
|
266
|
-
execute: () => {
|
|
267
|
-
const api = requireApi();
|
|
268
|
-
const n = api.getSceneElements().length;
|
|
269
|
-
api.resetScene();
|
|
270
|
-
return { ok: true, cleared: n };
|
|
271
|
-
},
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
return {
|
|
275
|
-
available: true,
|
|
276
|
-
count,
|
|
277
|
-
cleanup: () => {
|
|
278
|
-
try {
|
|
279
|
-
controller.abort();
|
|
280
|
-
} catch {
|
|
281
|
-
/* ignore */
|
|
282
|
-
}
|
|
283
|
-
},
|
|
284
|
-
};
|
|
285
|
-
}
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { spawn } from 'node:child_process';
|
|
4
|
-
import { basename, dirname, join } from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
|
|
7
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
-
const __dirname = dirname(__filename);
|
|
9
|
-
const repoRoot = join(__dirname, '..');
|
|
10
|
-
const serverPath = join(repoRoot, 'dist', 'server.js');
|
|
11
|
-
const runtime = process.env.CANVAS_RUNTIME || process.execPath;
|
|
12
|
-
const runtimeName = basename(runtime).toLowerCase();
|
|
13
|
-
const runtimeArgs = runtimeName.includes('bun') ? ['run', serverPath] : [serverPath];
|
|
14
|
-
const port = Number(process.env.PORT || 32000 + Math.floor(Math.random() * 2000));
|
|
15
|
-
const startupTimeoutMs = 5000;
|
|
16
|
-
const duplicateExitTimeoutMs = 2500;
|
|
17
|
-
|
|
18
|
-
function spawnCanvas(host) {
|
|
19
|
-
const env = {
|
|
20
|
-
...process.env,
|
|
21
|
-
PORT: String(port),
|
|
22
|
-
LOG_LEVEL: 'error',
|
|
23
|
-
};
|
|
24
|
-
if (host) {
|
|
25
|
-
env.HOST = host;
|
|
26
|
-
} else {
|
|
27
|
-
delete env.HOST;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return spawn(runtime, runtimeArgs, {
|
|
31
|
-
cwd: repoRoot,
|
|
32
|
-
env,
|
|
33
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function collectOutput(child) {
|
|
38
|
-
let output = '';
|
|
39
|
-
child.stdout.on('data', chunk => {
|
|
40
|
-
output += chunk.toString();
|
|
41
|
-
});
|
|
42
|
-
child.stderr.on('data', chunk => {
|
|
43
|
-
output += chunk.toString();
|
|
44
|
-
});
|
|
45
|
-
return () => output.trim();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function endpointResponds(url, timeoutMs = 500) {
|
|
49
|
-
const controller = new AbortController();
|
|
50
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
51
|
-
try {
|
|
52
|
-
const response = await fetch(url, { signal: controller.signal });
|
|
53
|
-
return response.ok;
|
|
54
|
-
} catch {
|
|
55
|
-
return false;
|
|
56
|
-
} finally {
|
|
57
|
-
clearTimeout(timeout);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async function waitForHealth(url, timeoutMs, child, getOutput) {
|
|
62
|
-
const start = Date.now();
|
|
63
|
-
while (Date.now() - start < timeoutMs) {
|
|
64
|
-
if (child && (child.exitCode !== null || child.signalCode !== null)) {
|
|
65
|
-
const status = child.exitCode !== null ? `exit code ${child.exitCode}` : `signal ${child.signalCode}`;
|
|
66
|
-
const output = getOutput ? getOutput() : '';
|
|
67
|
-
throw new Error(`Canvas server exited before health check with ${status}.${output ? `\n${output}` : ''}`);
|
|
68
|
-
}
|
|
69
|
-
if (await endpointResponds(url)) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
73
|
-
}
|
|
74
|
-
throw new Error(`Timed out waiting for ${url}`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function waitForExit(child, timeoutMs) {
|
|
78
|
-
return new Promise(resolve => {
|
|
79
|
-
const timeout = setTimeout(() => {
|
|
80
|
-
cleanup();
|
|
81
|
-
resolve(null);
|
|
82
|
-
}, timeoutMs);
|
|
83
|
-
|
|
84
|
-
const onExit = (code, signal) => {
|
|
85
|
-
cleanup();
|
|
86
|
-
resolve({ code, signal });
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const cleanup = () => {
|
|
90
|
-
clearTimeout(timeout);
|
|
91
|
-
child.off('exit', onExit);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
child.once('exit', onExit);
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async function killChild(child) {
|
|
99
|
-
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
100
|
-
child.kill('SIGTERM');
|
|
101
|
-
const exit = await waitForExit(child, 1000);
|
|
102
|
-
if (!exit) {
|
|
103
|
-
child.kill('SIGKILL');
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
let first;
|
|
108
|
-
let second;
|
|
109
|
-
let bindAll;
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
first = spawnCanvas();
|
|
113
|
-
const firstOutput = collectOutput(first);
|
|
114
|
-
|
|
115
|
-
await waitForHealth(`http://127.0.0.1:${port}/health`, startupTimeoutMs, first, firstOutput);
|
|
116
|
-
|
|
117
|
-
if (await endpointResponds(`http://[::1]:${port}/health`)) {
|
|
118
|
-
throw new Error('Default canvas server should not listen on IPv6 loopback.');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
second = spawnCanvas();
|
|
122
|
-
const secondOutput = collectOutput(second);
|
|
123
|
-
const duplicateExit = await waitForExit(second, duplicateExitTimeoutMs);
|
|
124
|
-
|
|
125
|
-
if (!duplicateExit) {
|
|
126
|
-
throw new Error('Second canvas server stayed running on the same local port.');
|
|
127
|
-
}
|
|
128
|
-
if (duplicateExit.code === 0) {
|
|
129
|
-
throw new Error('Second canvas server exited successfully instead of failing.');
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
bindAll = spawnCanvas('::');
|
|
133
|
-
const bindAllOutput = collectOutput(bindAll);
|
|
134
|
-
const bindAllExit = await waitForExit(bindAll, duplicateExitTimeoutMs);
|
|
135
|
-
|
|
136
|
-
if (!bindAllExit) {
|
|
137
|
-
throw new Error('Canvas server with HOST=:: stayed running while a loopback server was active.');
|
|
138
|
-
}
|
|
139
|
-
if (bindAllExit.code === 0) {
|
|
140
|
-
throw new Error('Canvas server with HOST=:: exited successfully instead of failing.');
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
console.log(
|
|
144
|
-
`Local bind check passed on port ${port} using ${runtimeName}: ` +
|
|
145
|
-
'default bind is IPv4 loopback only, duplicate startup fails, and HOST=:: is guarded.'
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
await killChild(first);
|
|
149
|
-
if (process.env.DEBUG_BIND_CHECK && firstOutput()) {
|
|
150
|
-
console.error(firstOutput());
|
|
151
|
-
}
|
|
152
|
-
if (process.env.DEBUG_BIND_CHECK && secondOutput()) {
|
|
153
|
-
console.error(secondOutput());
|
|
154
|
-
}
|
|
155
|
-
if (process.env.DEBUG_BIND_CHECK && bindAllOutput()) {
|
|
156
|
-
console.error(bindAllOutput());
|
|
157
|
-
}
|
|
158
|
-
} catch (error) {
|
|
159
|
-
if (bindAll) await killChild(bindAll);
|
|
160
|
-
if (second) await killChild(second);
|
|
161
|
-
if (first) await killChild(first);
|
|
162
|
-
console.error((error instanceof Error) ? error.message : String(error));
|
|
163
|
-
process.exit(1);
|
|
164
|
-
}
|