excalidrop 0.1.4 → 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.map +1 -1
- package/dist/cli.js +488 -394
- package/dist/cli.js.map +1 -1
- package/dist/frontend/assets/{advancedFormat-tiBAHNGo.js → advancedFormat-CsxROTkH.js} +1 -1
- package/dist/frontend/assets/{ar-SA-G6X2FPQ2-DVxiWV5Z.js → ar-SA-G6X2FPQ2-Bu8X6mOi.js} +1 -1
- package/dist/frontend/assets/arc-B9VNLSzj.js +1 -0
- package/dist/frontend/assets/{az-AZ-76LH7QW2-Z_2BqDo-.js → az-AZ-76LH7QW2-DpvzY31_.js} +1 -1
- package/dist/frontend/assets/{bg-BG-XCXSNQG7-DrxGJZOG.js → bg-BG-XCXSNQG7-DCtZnLyo.js} +1 -1
- package/dist/frontend/assets/{blockDiagram-38ab4fdb-CDnkTFfP.js → blockDiagram-38ab4fdb-4cfDkUsw.js} +1 -1
- package/dist/frontend/assets/{blockDiagram-68f4deed-Bb0W6mxN.js → blockDiagram-68f4deed-CsgWL6uC.js} +1 -1
- package/dist/frontend/assets/{bn-BD-2XOGV67Q-BkGJ-w8k.js → bn-BD-2XOGV67Q-B-IdLNKT.js} +1 -1
- package/dist/frontend/assets/{c4Diagram-15b5d702-ByRV0tka.js → c4Diagram-15b5d702-7BAqo4qT.js} +1 -1
- package/dist/frontend/assets/{c4Diagram-3d4e48cf-QSVnzYYK.js → c4Diagram-3d4e48cf-CDW-O-vg.js} +1 -1
- package/dist/frontend/assets/{ca-ES-6MX7JW3Y-BBJvOPE1.js → ca-ES-6MX7JW3Y-DM8olClc.js} +1 -1
- package/dist/frontend/assets/channel-jdNM4v-F.js +1 -0
- package/dist/frontend/assets/{classDiagram-70f12bd4-DDFBNGUt.js → classDiagram-70f12bd4-D2U4i4yC.js} +1 -1
- package/dist/frontend/assets/{classDiagram-d40c83e7-cgC3s7Nz.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-CqY00v-4.js → createText-2e5e7dd3-1KPUhDpM.js} +1 -1
- package/dist/frontend/assets/{createText-d213de94-D59sBfqq.js → createText-d213de94-C6EhGbUN.js} +4 -4
- package/dist/frontend/assets/{cs-CZ-2BRQDIVT-CK09MAsg.js → cs-CZ-2BRQDIVT-DgjTgz6K.js} +1 -1
- package/dist/frontend/assets/{cytoscape-cose-bilkent-CKy_tuGZ.js → cytoscape-cose-bilkent-BnIHl72R.js} +1 -1
- package/dist/frontend/assets/{da-DK-5WZEPLOC-V4hdLKuT.js → da-DK-5WZEPLOC-uEs3xGKF.js} +1 -1
- package/dist/frontend/assets/{de-DE-XR44H4JA-Dw8qkP9g.js → de-DE-XR44H4JA-CBglLw42.js} +1 -1
- package/dist/frontend/assets/{edges-332bd1c7-D-u0KMZB.js → edges-332bd1c7-cWCBwcrg.js} +1 -1
- package/dist/frontend/assets/{edges-e0da2a9e-B3UFqLqY.js → edges-e0da2a9e-DKeDUJ_F.js} +1 -1
- package/dist/frontend/assets/{el-GR-BZB4AONW-BUdBQbYg.js → el-GR-BZB4AONW-B-_eub00.js} +1 -1
- package/dist/frontend/assets/{elk.bundled-BZA3Xcoo.js → elk.bundled-Ch9TeAu4.js} +1 -1
- package/dist/frontend/assets/{erDiagram-880f2ed8-CNxm7e1x.js → erDiagram-880f2ed8-CtDf_58r.js} +4 -4
- package/dist/frontend/assets/{erDiagram-9861fffd-C2u_ziAM.js → erDiagram-9861fffd-CAlUeljk.js} +1 -1
- package/dist/frontend/assets/{es-ES-U4NZUMDT-DS0FAZqX.js → es-ES-U4NZUMDT-BIN3bSVD.js} +1 -1
- package/dist/frontend/assets/{eu-ES-A7QVB2H4-D-D5uidc.js → eu-ES-A7QVB2H4-TyQet3-N.js} +1 -1
- package/dist/frontend/assets/{fa-IR-HGAKTJCU-mAW_2-jg.js → fa-IR-HGAKTJCU-DVmjjr57.js} +1 -1
- package/dist/frontend/assets/{fi-FI-Z5N7JZ37-B9mW6nxS.js → fi-FI-Z5N7JZ37-ZW9kXLa8.js} +1 -1
- package/dist/frontend/assets/{flowDb-7c981674-6JB2gd0I.js → flowDb-7c981674-B5ppx8EN.js} +1 -1
- package/dist/frontend/assets/{flowDb-956e92f1-BbpkWRi7.js → flowDb-956e92f1-Dqi00Wq-.js} +1 -1
- package/dist/frontend/assets/{flowDiagram-66a62f08-DrhrQuXc.js → flowDiagram-66a62f08-v3Liu0m_.js} +1 -1
- package/dist/frontend/assets/{flowDiagram-cbd28bf7-BmNY7bvJ.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-4G1YiIlk.js → flowchart-elk-definition-36e2d292-BleYul6b.js} +2 -2
- package/dist/frontend/assets/{flowchart-elk-definition-4a651766-Cm94SrrQ.js → flowchart-elk-definition-4a651766-D6lLh4Ih.js} +1 -1
- package/dist/frontend/assets/{fr-FR-RHASNOE6-BXnmUxTA.js → fr-FR-RHASNOE6-CxLR66tH.js} +1 -1
- package/dist/frontend/assets/{ganttDiagram-04f9e578-X0BweAnG.js → ganttDiagram-04f9e578-DJSOyr8B.js} +1 -1
- package/dist/frontend/assets/{ganttDiagram-c361ad54-D3vxZ5MQ.js → ganttDiagram-c361ad54-CJmbCLvp.js} +1 -1
- package/dist/frontend/assets/{gitGraphDiagram-21fc4d3e-wznrpRWn.js → gitGraphDiagram-21fc4d3e-DiRq_FJc.js} +6 -6
- package/dist/frontend/assets/{gitGraphDiagram-72cf32ee-B9zCS3Pw.js → gitGraphDiagram-72cf32ee-MfvDzoAb.js} +1 -1
- package/dist/frontend/assets/{gl-ES-HMX3MZ6V-CrUx-ab6.js → gl-ES-HMX3MZ6V-CxWj2itK.js} +1 -1
- package/dist/frontend/assets/{graph-BQZ6SIVr.js → graph-CT_r46y9.js} +1 -1
- package/dist/frontend/assets/{graph-Cda8xSTx.js → graph-DJt_Vezo.js} +1 -1
- package/dist/frontend/assets/{he-IL-6SHJWFNN-D33eaw6-.js → he-IL-6SHJWFNN-5v2WFGO3.js} +1 -1
- package/dist/frontend/assets/{hi-IN-IWLTKZ5I-DEnStF-g.js → hi-IN-IWLTKZ5I-BJGbvgRu.js} +1 -1
- package/dist/frontend/assets/{hu-HU-A5ZG7DT2-BjH2w8Sn.js → hu-HU-A5ZG7DT2-REQT2EcE.js} +1 -1
- package/dist/frontend/assets/{id-ID-SAP4L64H-Bc6R-JEz.js → id-ID-SAP4L64H-aZWQYWdg.js} +1 -1
- package/dist/frontend/assets/{index-3862675e-CpYfS3Db.js → index-3862675e-BgrvgbsM.js} +1 -1
- package/dist/frontend/assets/{index-6079d271-Y2bgyRpa.js → index-6079d271-Cp8jFHTC.js} +1 -1
- package/dist/frontend/assets/{index-D7eYnAQp.js → index-DBHHeSXy.js} +4 -4
- package/dist/frontend/assets/index-GTq6y6g6.js +349 -0
- package/dist/frontend/assets/{infoDiagram-4a4f5b27-Bm3Oq-Rq.js → infoDiagram-4a4f5b27-C1ZJJtWv.js} +3 -3
- package/dist/frontend/assets/{infoDiagram-f8f76790-oNVitBKM.js → infoDiagram-f8f76790-GeS0JKxo.js} +1 -1
- package/dist/frontend/assets/{it-IT-JPQ66NNP-BcxQABF2.js → it-IT-JPQ66NNP-EyeA2Ty1.js} +1 -1
- package/dist/frontend/assets/{ja-JP-DBVTYXUO-D1zIM-nt.js → ja-JP-DBVTYXUO-BLcE45D9.js} +1 -1
- package/dist/frontend/assets/{journeyDiagram-29694f62-CamPk_3P.js → journeyDiagram-29694f62-4Z_lpK4g.js} +5 -5
- package/dist/frontend/assets/{journeyDiagram-49397b02-D6w3tLtf.js → journeyDiagram-49397b02-ChwU3Zo6.js} +1 -1
- package/dist/frontend/assets/{kaa-6HZHGXH3-CoILysVg.js → kaa-6HZHGXH3-CkpTcX9X.js} +1 -1
- package/dist/frontend/assets/{kab-KAB-ZGHBKWFO-B9ZbybBt.js → kab-KAB-ZGHBKWFO-CChq0y6o.js} +1 -1
- package/dist/frontend/assets/{kk-KZ-P5N5QNE5-Dxi4GIQ7.js → kk-KZ-P5N5QNE5-CwtoTY4z.js} +1 -1
- package/dist/frontend/assets/{km-KH-HSX4SM5Z-DaJ7EtO2.js → km-KH-HSX4SM5Z-D26sdohj.js} +1 -1
- package/dist/frontend/assets/{ko-KR-MTYHY66A-DG57Ah12.js → ko-KR-MTYHY66A-DvnDcJQA.js} +1 -1
- package/dist/frontend/assets/{ku-TR-6OUDTVRD-DnDf9E8a.js → ku-TR-6OUDTVRD-qdHbLgKg.js} +1 -1
- package/dist/frontend/assets/{layout-Drr-66M9.js → layout-C-wUeNvb.js} +1 -1
- package/dist/frontend/assets/{layout-BsWfhUAF.js → layout-YRLNN4au.js} +1 -1
- package/dist/frontend/assets/{line-DwQObOhP.js → line-BEgMClKG.js} +1 -1
- package/dist/frontend/assets/{linear-C36MjJps.js → linear-DDHpSdYw.js} +1 -1
- package/dist/frontend/assets/{lt-LT-XHIRWOB4-D_zqS0yg.js → lt-LT-XHIRWOB4-DT4LYe_d.js} +1 -1
- package/dist/frontend/assets/{lv-LV-5QDEKY6T-owIsIh1b.js → lv-LV-5QDEKY6T-Dbc3ivwT.js} +1 -1
- package/dist/frontend/assets/{mindmap-definition-ac74a2e8-Cf9JbrBJ.js → mindmap-definition-ac74a2e8-CY2RhWUN.js} +6 -6
- package/dist/frontend/assets/{mindmap-definition-fc14e90a-BpsNCNCp.js → mindmap-definition-fc14e90a-VT4JDi6N.js} +1 -1
- package/dist/frontend/assets/{mr-IN-CRQNXWMA-CS9XUj74.js → mr-IN-CRQNXWMA-D75p1fVc.js} +1 -1
- package/dist/frontend/assets/{my-MM-5M5IBNSE-BKB060k9.js → my-MM-5M5IBNSE-DnEoBXyE.js} +1 -1
- package/dist/frontend/assets/{nb-NO-T6EIAALU-CkmLUrvm.js → nb-NO-T6EIAALU-BL4ThJxl.js} +1 -1
- package/dist/frontend/assets/{nl-NL-IS3SIHDZ-DdiLyJoz.js → nl-NL-IS3SIHDZ-u18nUblY.js} +1 -1
- package/dist/frontend/assets/{nn-NO-6E72VCQL-Yevp6f_Y.js → nn-NO-6E72VCQL-B9LJ0oXc.js} +1 -1
- package/dist/frontend/assets/{oc-FR-POXYY2M6-D_M6GHup.js → oc-FR-POXYY2M6-BKG5kIcO.js} +1 -1
- package/dist/frontend/assets/{pa-IN-N4M65BXN-1vuACN9n.js → pa-IN-N4M65BXN-CmpnjbkL.js} +1 -1
- package/dist/frontend/assets/{pica-DDDx0iqx.js → pica-BcpcM0v1.js} +1 -1
- package/dist/frontend/assets/{pie-CFIpuQMO.js → pie-Swzl5NrI.js} +1 -1
- package/dist/frontend/assets/{pieDiagram-421022e6-DO7e_Qgw.js → pieDiagram-421022e6-ndz96BLe.js} +5 -5
- package/dist/frontend/assets/{pieDiagram-8a3498a8-BEoqChXo.js → pieDiagram-8a3498a8-D5rcV68O.js} +1 -1
- package/dist/frontend/assets/{pl-PL-T2D74RX3-Cc6Q5nSh.js → pl-PL-T2D74RX3-CrR66Rgj.js} +1 -1
- package/dist/frontend/assets/{pt-BR-5N22H2LF-3eEiAax3.js → pt-BR-5N22H2LF-Crv7jZiH.js} +1 -1
- package/dist/frontend/assets/{pt-PT-UZXXM6DQ-CgnF0Le7.js → pt-PT-UZXXM6DQ-Bkaa5ESR.js} +1 -1
- package/dist/frontend/assets/{quadrantDiagram-0957ecba-C1Eo-ZUY.js → quadrantDiagram-0957ecba-DOdyxeTP.js} +3 -3
- package/dist/frontend/assets/{quadrantDiagram-120e2f19-Bdfsl_O7.js → quadrantDiagram-120e2f19-beQD7R1C.js} +1 -1
- package/dist/frontend/assets/relay-BIOL2uw2.js +1 -0
- package/dist/frontend/assets/{requirementDiagram-23d650b8-DiOK6QW3.js → requirementDiagram-23d650b8-B2i-OB97.js} +1 -1
- package/dist/frontend/assets/{requirementDiagram-deff3bca-BDJ-7T4h.js → requirementDiagram-deff3bca-beS-SvfY.js} +1 -1
- package/dist/frontend/assets/{ro-RO-JPDTUUEW-CbJ3IB7a.js → ro-RO-JPDTUUEW-DsrnQm7K.js} +1 -1
- package/dist/frontend/assets/{ru-RU-B4JR7IUQ-PQGmeGG4.js → ru-RU-B4JR7IUQ-DrnGGdUs.js} +1 -1
- package/dist/frontend/assets/{sankeyDiagram-04a897e0-DV25bgN2.js → sankeyDiagram-04a897e0-CVad45bN.js} +1 -1
- package/dist/frontend/assets/{sankeyDiagram-23345273-szSb_Sei.js → sankeyDiagram-23345273-ghWBXXF2.js} +1 -1
- package/dist/frontend/assets/{selectAll-DsrPvhqV.js → selectAll-XplaO988.js} +1 -1
- package/dist/frontend/assets/{sequenceDiagram-17ac3bff-Dg88bSlc.js → sequenceDiagram-17ac3bff-DBI-EdWx.js} +1 -1
- package/dist/frontend/assets/{sequenceDiagram-704730f1-DRIjLCxx.js → sequenceDiagram-704730f1-BnJ__mBt.js} +1 -1
- package/dist/frontend/assets/{si-LK-N5RQ5JYF-BtIIudm3.js → si-LK-N5RQ5JYF-BxAwuRM5.js} +1 -1
- package/dist/frontend/assets/{sk-SK-C5VTKIMK-CIwXticU.js → sk-SK-C5VTKIMK-CnDyiLRf.js} +1 -1
- package/dist/frontend/assets/{sl-SI-NN7IZMDC-CXEdg57y.js → sl-SI-NN7IZMDC-BXHXIdFA.js} +1 -1
- package/dist/frontend/assets/{stateDiagram-587899a1-BwRwgRo6.js → stateDiagram-587899a1-D3Ahxsgc.js} +1 -1
- package/dist/frontend/assets/{stateDiagram-9c5f0230-C8n96rER.js → stateDiagram-9c5f0230-8XbLWvC0.js} +1 -1
- package/dist/frontend/assets/{stateDiagram-v2-51a3dcff-B7e5E4J7.js → stateDiagram-v2-51a3dcff-Cqf-MUGD.js} +1 -1
- package/dist/frontend/assets/{stateDiagram-v2-d93cdb3a-B18iZQLw.js → stateDiagram-v2-d93cdb3a-DjGrud9D.js} +1 -1
- package/dist/frontend/assets/{styles-2ab5d517-CfWubwio.js → styles-2ab5d517-B4qdHOgu.js} +3 -3
- package/dist/frontend/assets/{styles-5f03d8d2-C-_n19AB.js → styles-5f03d8d2-DOD2v5V8.js} +3 -3
- package/dist/frontend/assets/{styles-6aaf32cf-Dh-DHkVP.js → styles-6aaf32cf-bP3XXcmf.js} +1 -1
- package/dist/frontend/assets/{styles-9a916d00-Boy0hUEG.js → styles-9a916d00-QLeuL_Dy.js} +1 -1
- package/dist/frontend/assets/{styles-c10674c1-BfxiK3Ns.js → styles-c10674c1-Lpvh1u4f.js} +1 -1
- package/dist/frontend/assets/{styles-edf9a4b0-T7mKViRF.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-dVrUAqld.js → sv-SE-XGPEYMSR-YrCQQBuo.js} +1 -1
- package/dist/frontend/assets/{svgDrawCommon-08f97a94-fsWk6SQc.js → svgDrawCommon-08f97a94-CwNv3PjS.js} +1 -1
- package/dist/frontend/assets/{svgDrawCommon-3ba9043b-BJ8X3qS-.js → svgDrawCommon-3ba9043b-DZbT0Bqu.js} +1 -1
- package/dist/frontend/assets/{ta-IN-2NMHFXQM-CtVVZfIT.js → ta-IN-2NMHFXQM-qR82aBSW.js} +1 -1
- package/dist/frontend/assets/{th-TH-HPSO5L25-Do90CyDN.js → th-TH-HPSO5L25-C65V7hE5.js} +1 -1
- package/dist/frontend/assets/{timeline-definition-7e6b55e7-D_iHA485.js → timeline-definition-7e6b55e7-GkPy3QeG.js} +3 -3
- package/dist/frontend/assets/{timeline-definition-85554ec2-nhT8QMsO.js → timeline-definition-85554ec2-CCdkBaC0.js} +3 -3
- package/dist/frontend/assets/{tr-TR-DEFEU3FU-MfR-IQke.js → tr-TR-DEFEU3FU-CFzbwAKV.js} +1 -1
- package/dist/frontend/assets/{uk-UA-QMV73CPH-CPet8B1O.js → uk-UA-QMV73CPH-ePt5WVz3.js} +1 -1
- package/dist/frontend/assets/{union-BgYwhB7T.js → union-CVZobYBh.js} +1 -1
- package/dist/frontend/assets/{vi-VN-M7AON7JQ-3HD06T_8.js → vi-VN-M7AON7JQ-CaPKykgJ.js} +1 -1
- package/dist/frontend/assets/{xychartDiagram-b6496bcd-D5vSaFT2.js → xychartDiagram-b6496bcd-CV5WRc81.js} +4 -4
- package/dist/frontend/assets/{xychartDiagram-e933f94c-DLIHXsUi.js → xychartDiagram-e933f94c-BH-ySdQf.js} +1 -1
- package/dist/frontend/assets/{zh-CN-LNUGB5OW-t2RyWPw2.js → zh-CN-LNUGB5OW-CK8AEj8o.js} +1 -1
- package/dist/frontend/assets/{zh-HK-E62DVLB3-Bj22hc7e.js → zh-HK-E62DVLB3-CUINiFgH.js} +1 -1
- package/dist/frontend/assets/{zh-TW-RAJ6MFWO-CBRhqhU1.js → zh-TW-RAJ6MFWO-BEBY7iJR.js} +1 -1
- package/dist/frontend/assets/zipObject-W8SLG3F1.js +1 -0
- package/dist/frontend/index.html +167 -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 -10
- package/dist/frontend/assets/arc-Cc3ncJ1n.js +0 -1
- package/dist/frontend/assets/channel-BQSIb3i3.js +0 -1
- package/dist/frontend/assets/classDiagram-v2-d5a6b087-BgB8AG2e.js +0 -2
- package/dist/frontend/assets/classDiagram-v2-f2320105-Dtywkpeg.js +0 -2
- package/dist/frontend/assets/clone-BoZziNL9.js +0 -1
- package/dist/frontend/assets/flowDiagram-v2-96b9c2cf-Z3H2d3Wj.js +0 -1
- package/dist/frontend/assets/flowDiagram-v2-ffc7f31a-CIQOMghC.js +0 -1
- package/dist/frontend/assets/index-DIz4WrSB.js +0 -349
- package/dist/frontend/assets/zipObject-DMfHW2o8.js +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2279
- 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 -1208
- package/dist/server.js.map +0 -1
- package/dist/target.d.ts +0 -38
- package/dist/target.d.ts.map +0 -1
- package/dist/target.js +0 -225
- 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 -45
- package/dist/types.js.map +0 -1
- package/dist/utils/auth.d.ts +0 -14
- package/dist/utils/auth.d.ts.map +0 -1
- package/dist/utils/auth.js +0 -67
- package/dist/utils/auth.js.map +0 -1
- package/dist/utils/github.d.ts +0 -24
- package/dist/utils/github.d.ts.map +0 -1
- package/dist/utils/github.js +0 -132
- 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 -12
- 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/drop.png +0 -0
- package/frontend/index.html +0 -339
- package/frontend/src/App.tsx +0 -1091
- package/frontend/src/main.tsx +0 -15
- package/frontend/src/utils/ghSync.ts +0 -234
- package/frontend/src/utils/mermaidConverter.ts +0 -43
- package/frontend/src/utils/webmcp.ts +0 -265
- 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 -528
- package/src/index.ts +0 -2538
- package/src/remote.ts +0 -92
- package/src/server.ts +0 -1349
- package/src/target.ts +0 -198
- package/src/types.ts +0 -336
- package/src/utils/auth.ts +0 -61
- package/src/utils/github.ts +0 -121
- package/src/utils/logger.ts +0 -35
- package/src/utils/r2store.ts +0 -70
package/dist/cli.js
CHANGED
|
@@ -1,179 +1,205 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
-
import { createConnection } from 'node:net';
|
|
2
|
+
import { spawn, spawnSync, execSync } from 'node:child_process';
|
|
4
3
|
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import readline from 'node:readline';
|
|
7
6
|
import { fileURLToPath } from 'node:url';
|
|
8
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
8
|
const __dirname = path.dirname(__filename);
|
|
10
9
|
const CONFIG_NAME = '.excalidrop.json';
|
|
11
10
|
const MCP_JSON = '.mcp.json';
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
11
|
+
const RELAY_DEFAULT = 'https://excalidrop.wtf403.workers.dev';
|
|
12
|
+
const GH_API = 'https://api.github.com';
|
|
13
|
+
const SCENE_PATH = process.env.SCENE_PATH || 'canvas.excalidraw';
|
|
14
|
+
const CANVAS_BRANCH = process.env.CANVAS_BRANCH || 'excalidrop';
|
|
15
|
+
const PAGES_BRANCH = process.env.PAGES_BRANCH || 'excalidrop';
|
|
16
|
+
function ghToken() {
|
|
17
|
+
if (process.env.GITHUB_TOKEN)
|
|
18
|
+
return process.env.GITHUB_TOKEN;
|
|
19
|
+
try {
|
|
20
|
+
return execSync('gh auth token', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function ghHeaders() {
|
|
27
|
+
return { Authorization: `Bearer ${ghToken()}`, Accept: 'application/vnd.github+json', 'User-Agent': 'excalidrop-setup', 'Content-Type': 'application/json' };
|
|
28
|
+
}
|
|
29
|
+
async function ghGet(pathname) {
|
|
30
|
+
const r = await fetch(`${GH_API}${pathname}`, { headers: ghHeaders() });
|
|
31
|
+
if (!r.ok)
|
|
32
|
+
throw new Error(`GitHub GET ${pathname} → ${r.status}: ${(await r.text()).slice(0, 150)}`);
|
|
33
|
+
return r.json();
|
|
34
|
+
}
|
|
35
|
+
async function ghPut(pathname, body) {
|
|
36
|
+
const r = await fetch(`${GH_API}${pathname}`, { method: 'PUT', headers: ghHeaders(), body: JSON.stringify(body) });
|
|
37
|
+
if (!r.ok)
|
|
38
|
+
throw new Error(`GitHub PUT ${pathname} → ${r.status}: ${(await r.text()).slice(0, 150)}`);
|
|
39
|
+
return r.json();
|
|
40
|
+
}
|
|
41
|
+
async function ensureMainBranch(repo) {
|
|
42
|
+
const main = await fetch(`${GH_API}/repos/${repo}/git/ref/heads/main`, { headers: ghHeaders() });
|
|
43
|
+
if (main.ok)
|
|
44
|
+
return;
|
|
45
|
+
const branches = await (await fetch(`${GH_API}/repos/${repo}/branches`, { headers: ghHeaders() })).json();
|
|
46
|
+
if (branches.length !== 0)
|
|
47
|
+
return;
|
|
48
|
+
await ghPut(`/repos/${repo}/contents/README.md`, {
|
|
49
|
+
message: 'Initial commit',
|
|
50
|
+
content: Buffer.from(`# ${repo.split('/')[1]}\n\nExcalidraw canvas powered by [excalidrop](https://github.com/wtf403/excalidrop).\n`).toString('base64'),
|
|
51
|
+
branch: 'main',
|
|
31
52
|
});
|
|
32
53
|
}
|
|
33
|
-
async function
|
|
34
|
-
|
|
35
|
-
|
|
54
|
+
async function getScene(repo) {
|
|
55
|
+
const r = await fetch(`${GH_API}/repos/${repo}/contents/${SCENE_PATH}?ref=${CANVAS_BRANCH}`, { headers: ghHeaders() });
|
|
56
|
+
if (r.status === 404)
|
|
57
|
+
return { elements: [], sha: null };
|
|
58
|
+
if (!r.ok)
|
|
59
|
+
throw new Error(`scene read ${r.status}`);
|
|
60
|
+
const j = await r.json();
|
|
61
|
+
let b64 = typeof j.content === 'string' && j.content.length ? j.content : null;
|
|
62
|
+
if (!b64) {
|
|
63
|
+
const blob = await fetch(`${GH_API}/repos/${repo}/git/blobs/${j.sha}`, { headers: ghHeaders() });
|
|
64
|
+
if (blob.ok)
|
|
65
|
+
b64 = (await blob.json()).content || null;
|
|
36
66
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
67
|
+
if (!b64)
|
|
68
|
+
return { elements: [], sha: j.sha || null };
|
|
69
|
+
return { elements: (JSON.parse(Buffer.from(b64, 'base64').toString('utf8')).elements || []), sha: j.sha };
|
|
70
|
+
}
|
|
71
|
+
async function putScene(repo, elements, sha, message) {
|
|
72
|
+
const body = {
|
|
73
|
+
message,
|
|
74
|
+
content: Buffer.from(JSON.stringify({ type: 'excalidraw', version: 2, source: 'excalidrop', elements, appState: { viewBackgroundColor: '#ffffff', gridSize: null }, files: {} }, null, 2)).toString('base64'),
|
|
75
|
+
branch: CANVAS_BRANCH,
|
|
76
|
+
};
|
|
77
|
+
if (sha)
|
|
78
|
+
body.sha = sha;
|
|
79
|
+
const r = await fetch(`${GH_API}/repos/${repo}/contents/${SCENE_PATH}`, { method: 'PUT', headers: ghHeaders(), body: JSON.stringify(body) });
|
|
80
|
+
if (r.status === 404) {
|
|
81
|
+
// create the branch from default, then retry once
|
|
82
|
+
const info = await ghGet(`/repos/${repo}`);
|
|
83
|
+
const from = info.default_branch || 'main';
|
|
84
|
+
const ref = await ghGet(`/repos/${repo}/git/ref/heads/${from}`);
|
|
85
|
+
await fetch(`${GH_API}/repos/${repo}/git/refs`, { method: 'POST', headers: ghHeaders(), body: JSON.stringify({ ref: `refs/heads/${CANVAS_BRANCH}`, sha: ref.object.sha }) });
|
|
86
|
+
const retry = await fetch(`${GH_API}/repos/${repo}/contents/${SCENE_PATH}`, { method: 'PUT', headers: ghHeaders(), body: JSON.stringify(body) });
|
|
87
|
+
if (!retry.ok)
|
|
88
|
+
throw new Error(`scene write ${retry.status}`);
|
|
89
|
+
return;
|
|
40
90
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
91
|
+
if (!r.ok)
|
|
92
|
+
throw new Error(`scene write ${r.status}: ${(await r.text()).slice(0, 150)}`);
|
|
93
|
+
}
|
|
94
|
+
function viewerDir() {
|
|
95
|
+
// dist/cli.js → dist/frontend; fallback to checkout layout.
|
|
96
|
+
const cands = [path.join(__dirname, 'frontend'), path.join(process.cwd(), 'dist', 'frontend')];
|
|
97
|
+
for (const d of cands) {
|
|
98
|
+
if (fs.existsSync(path.join(d, 'index.html')) && fs.existsSync(path.join(d, 'assets')))
|
|
99
|
+
return d;
|
|
45
100
|
}
|
|
46
|
-
throw new Error('
|
|
101
|
+
throw new Error('Viewer build not found (dist/frontend). Reinstall excalidrop or run `npm run build:frontend` in the excalidrop checkout.');
|
|
47
102
|
}
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (parent === dir || dir === home)
|
|
57
|
-
return path.resolve(cwd);
|
|
58
|
-
dir = parent;
|
|
103
|
+
function listFilesRecursive(dir, base = dir) {
|
|
104
|
+
const out = [];
|
|
105
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
106
|
+
const full = path.join(dir, e.name);
|
|
107
|
+
if (e.isDirectory())
|
|
108
|
+
out.push(...listFilesRecursive(full, base));
|
|
109
|
+
else if (e.isFile())
|
|
110
|
+
out.push(path.relative(base, full));
|
|
59
111
|
}
|
|
112
|
+
return out;
|
|
60
113
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
config: {
|
|
73
|
-
port: raw.port,
|
|
74
|
-
canvasUrl: raw.canvasUrl || `http://127.0.0.1:${raw.port}`,
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
}
|
|
114
|
+
async function deployViewerPages(repo) {
|
|
115
|
+
const dir = viewerDir();
|
|
116
|
+
const files = listFilesRecursive(dir);
|
|
117
|
+
const blob = async (content) => (await (await fetch(`${GH_API}/repos/${repo}/git/blobs`, { method: 'POST', headers: ghHeaders(), body: JSON.stringify({ content, encoding: 'base64' }) })).json()).sha;
|
|
118
|
+
const tree = [];
|
|
119
|
+
const pool = 8;
|
|
120
|
+
let i = 0;
|
|
121
|
+
await Promise.all(Array.from({ length: Math.min(pool, files.length) }, async () => {
|
|
122
|
+
while (i < files.length) {
|
|
123
|
+
const rel = files[i++];
|
|
124
|
+
tree.push({ path: rel.split(path.sep).join('/'), mode: '100644', type: 'blob', sha: await blob(fs.readFileSync(path.join(dir, rel)).toString('base64')) });
|
|
81
125
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
126
|
+
}));
|
|
127
|
+
tree.push({ path: '.nojekyll', mode: '100644', type: 'blob', sha: await blob('') });
|
|
128
|
+
let baseSha = null;
|
|
129
|
+
let baseTree = null;
|
|
130
|
+
try {
|
|
131
|
+
const ref = await ghGet(`/repos/${repo}/git/ref/heads/${PAGES_BRANCH}`);
|
|
132
|
+
baseSha = ref.object.sha;
|
|
133
|
+
baseTree = (await ghGet(`/repos/${repo}/git/commits/${baseSha}`)).tree.sha;
|
|
134
|
+
}
|
|
135
|
+
catch { /* first deploy — root commit */ }
|
|
136
|
+
const newTree = await (await fetch(`${GH_API}/repos/${repo}/git/trees`, { method: 'POST', headers: ghHeaders(), body: JSON.stringify({ ...(baseTree ? { base_tree: baseTree } : {}), tree }) })).json();
|
|
137
|
+
const commit = await (await fetch(`${GH_API}/repos/${repo}/git/commits`, { method: 'POST', headers: ghHeaders(), body: JSON.stringify({ message: 'excalidrop: publish viewer', tree: newTree.sha, ...(baseSha ? { parents: [baseSha] } : { parents: [] }) }) })).json();
|
|
138
|
+
if (baseSha)
|
|
139
|
+
await fetch(`${GH_API}/repos/${repo}/git/refs/heads/${PAGES_BRANCH}`, { method: 'PATCH', headers: ghHeaders(), body: JSON.stringify({ sha: commit.sha, force: true }) });
|
|
140
|
+
else
|
|
141
|
+
await fetch(`${GH_API}/repos/${repo}/git/refs`, { method: 'POST', headers: ghHeaders(), body: JSON.stringify({ ref: `refs/heads/${PAGES_BRANCH}`, sha: commit.sha }) });
|
|
142
|
+
// API commits don't reliably queue builds — request one explicitly.
|
|
143
|
+
try {
|
|
144
|
+
const cur = await fetch(`${GH_API}/repos/${repo}/pages`, { headers: ghHeaders() });
|
|
145
|
+
if (!cur.ok || (await cur.json())?.source?.branch !== PAGES_BRANCH) {
|
|
146
|
+
const up = await fetch(`${GH_API}/repos/${repo}/pages`, { method: 'PUT', headers: ghHeaders(), body: JSON.stringify({ source: { branch: PAGES_BRANCH, path: '/' } }) });
|
|
147
|
+
if (!up.ok)
|
|
148
|
+
await fetch(`${GH_API}/repos/${repo}/pages`, { method: 'POST', headers: ghHeaders(), body: JSON.stringify({ source: { branch: PAGES_BRANCH, path: '/' } }) });
|
|
149
|
+
}
|
|
150
|
+
await fetch(`${GH_API}/repos/${repo}/pages/builds`, { method: 'POST', headers: ghHeaders() });
|
|
151
|
+
}
|
|
152
|
+
catch { /* Pages setup is best-effort */ }
|
|
153
|
+
}
|
|
154
|
+
function projectNameForRepo(repo) {
|
|
155
|
+
return `excalidrop-${repo.replace('/', '-').toLowerCase().replace(/[^a-z0-9-]/g, '-')}`.slice(0, 60);
|
|
156
|
+
}
|
|
157
|
+
function viewerUrlFor(repo, target) {
|
|
158
|
+
if (target === 'cloudflare') {
|
|
159
|
+
// Generic viewer build can't infer the repo from a pages.dev host —
|
|
160
|
+
// detectRepo() reads it from ?repo= instead.
|
|
161
|
+
return `https://${projectNameForRepo(repo)}.pages.dev/?repo=${repo}`;
|
|
85
162
|
}
|
|
163
|
+
const [owner, name] = repo.split('/');
|
|
164
|
+
return `https://${owner}.github.io/${name}/`;
|
|
86
165
|
}
|
|
87
|
-
function
|
|
88
|
-
if (!url)
|
|
89
|
-
return undefined;
|
|
166
|
+
async function isPrivateRepo(repo) {
|
|
90
167
|
try {
|
|
91
|
-
const
|
|
92
|
-
|
|
168
|
+
const r = spawnSync('gh', ['api', `repos/${repo}`, '--jq', '.private'], { encoding: 'utf8' });
|
|
169
|
+
if (r.status !== 0)
|
|
170
|
+
return null;
|
|
171
|
+
if (r.stdout.trim() === 'true')
|
|
172
|
+
return true;
|
|
173
|
+
if (r.stdout.trim() === 'false')
|
|
174
|
+
return false;
|
|
175
|
+
return null;
|
|
93
176
|
}
|
|
94
177
|
catch {
|
|
95
|
-
return
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
function resolveSavedPort(cwd) {
|
|
99
|
-
const envPort = Number(process.env.EXCALIDROP_PORT);
|
|
100
|
-
if (Number.isFinite(envPort) && envPort > 0)
|
|
101
|
-
return envPort;
|
|
102
|
-
const envUrlPort = portFromExpressUrl(process.env.EXPRESS_SERVER_URL);
|
|
103
|
-
if (envUrlPort)
|
|
104
|
-
return envUrlPort;
|
|
105
|
-
const found = findDropConfig(cwd);
|
|
106
|
-
if (found)
|
|
107
|
-
return found.config.port;
|
|
108
|
-
return DEFAULT_START_PORT;
|
|
109
|
-
}
|
|
110
|
-
function mcpServerJson() {
|
|
111
|
-
return {
|
|
112
|
-
mcpServers: {
|
|
113
|
-
excalidrop: {
|
|
114
|
-
command: 'npx',
|
|
115
|
-
args: ['excalidrop', 'mcp'],
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
};
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
119
180
|
}
|
|
120
|
-
function
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
command: 'claude mcp add excalidrop --scope project -- npx excalidrop mcp',
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
name: 'Claude Code (user scope)',
|
|
128
|
-
command: 'claude mcp add excalidraw --scope user -- npx excalidrop mcp',
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
name: 'Codex CLI',
|
|
132
|
-
command: 'codex mcp add excalidrop -- npx excalidrop mcp',
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
name: 'Gemini CLI',
|
|
136
|
-
command: 'gemini mcp add excalidrop npx excalidrop mcp',
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
name: 'Cursor (.cursor/mcp.json) / Claude Desktop / Antigravity',
|
|
140
|
-
command: JSON.stringify(mcpServerJson(), null, 2),
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: 'OpenCode (opencode.json)',
|
|
144
|
-
command: JSON.stringify({
|
|
145
|
-
$schema: 'https://opencode.ai/config.json',
|
|
146
|
-
mcp: {
|
|
147
|
-
excalidrop: {
|
|
148
|
-
type: 'local',
|
|
149
|
-
command: ['npx', 'excalidrop', 'mcp'],
|
|
150
|
-
enabled: true,
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
}, null, 2),
|
|
154
|
-
},
|
|
155
|
-
];
|
|
156
|
-
}
|
|
157
|
-
function prompt(question) {
|
|
158
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
159
|
-
return new Promise((resolve) => {
|
|
160
|
-
rl.question(question, (answer) => {
|
|
161
|
-
rl.close();
|
|
162
|
-
resolve(answer.trim());
|
|
163
|
-
});
|
|
164
|
-
});
|
|
181
|
+
async function resolveTarget(repo, explicit) {
|
|
182
|
+
if (explicit === 'pages' || explicit === 'cloudflare')
|
|
183
|
+
return explicit;
|
|
184
|
+
return (await isPrivateRepo(repo)) === true ? 'cloudflare' : 'pages';
|
|
165
185
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
});
|
|
171
|
-
|
|
186
|
+
function deployToCloudflarePages(project) {
|
|
187
|
+
// wrangler 4 doesn't auto-create Pages projects and its Workers delegation
|
|
188
|
+
// misfires on explicit asset dirs — create once, then deploy classic with --force.
|
|
189
|
+
const WRANGLER = ['-y', 'wrangler@4'];
|
|
190
|
+
const create = spawnSync('npx', [...WRANGLER, 'pages', 'project', 'create', project, '--force', '--production-branch=main'], { stdio: 'pipe', encoding: 'utf8', env: process.env });
|
|
191
|
+
const createOut = (create.stdout || '') + (create.stderr || '');
|
|
192
|
+
if (create.status !== 0 && !/already exists/i.test(createOut)) {
|
|
193
|
+
throw new Error(`wrangler pages project create failed:\n${createOut.slice(-800)}\nRun \`wrangler login\` first (or set CLOUDFLARE_API_TOKEN).`);
|
|
194
|
+
}
|
|
195
|
+
const r = spawnSync('npx', [...WRANGLER, 'pages', 'deploy', viewerDir(), '--project-name', project, '--force'], { stdio: 'inherit', env: process.env });
|
|
196
|
+
if (r.status !== 0)
|
|
197
|
+
throw new Error('wrangler pages deploy failed. Run `wrangler login` first (or set CLOUDFLARE_API_TOKEN).');
|
|
172
198
|
}
|
|
173
199
|
function detectSlug() {
|
|
174
200
|
try {
|
|
175
201
|
const url = spawnSync('git', ['config', '--get', 'remote.origin.url'], { encoding: 'utf8' }).stdout.trim().replace(/\.git$/, '');
|
|
176
|
-
return url.match(/github\.com[:/](.+)/)?.[1] || '';
|
|
202
|
+
return url.match(/github\.com[:/](.+)/)?.[1]?.toLowerCase() || '';
|
|
177
203
|
}
|
|
178
204
|
catch {
|
|
179
205
|
return '';
|
|
@@ -182,23 +208,127 @@ function detectSlug() {
|
|
|
182
208
|
function ghAuthed() {
|
|
183
209
|
return spawnSync('gh', ['auth', 'status'], { stdio: 'ignore' }).status === 0;
|
|
184
210
|
}
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
211
|
+
function commandExists(cmd) {
|
|
212
|
+
const probe = spawnSync(process.platform === 'win32' ? 'where' : 'command', ['-v', cmd], { stdio: 'ignore', shell: process.platform === 'win32' });
|
|
213
|
+
return probe.status === 0;
|
|
214
|
+
}
|
|
215
|
+
function findProjectRoot(cwd) {
|
|
216
|
+
let dir = path.resolve(cwd);
|
|
217
|
+
const home = process.env.HOME || process.env.USERPROFILE || '/';
|
|
218
|
+
while (true) {
|
|
219
|
+
if (fs.existsSync(path.join(dir, 'package.json')) || fs.existsSync(path.join(dir, '.git')))
|
|
220
|
+
return dir;
|
|
221
|
+
const parent = path.dirname(dir);
|
|
222
|
+
if (parent === dir || dir === home)
|
|
223
|
+
return path.resolve(cwd);
|
|
224
|
+
dir = parent;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function mcpArgs(repo) {
|
|
228
|
+
return ['-y', 'excalidrop@latest', 'mcp', '--repo', repo];
|
|
229
|
+
}
|
|
230
|
+
const EDITOR_IDS = ['claude-project', 'claude-user', 'codex', 'cursor', 'desktop', 'opencode'];
|
|
231
|
+
function editorCommand(id, repo) {
|
|
232
|
+
const a = mcpArgs(repo).join(' ');
|
|
233
|
+
switch (id) {
|
|
234
|
+
case 'claude-project': return `claude mcp add excalidrop --scope project -- npx ${a}`;
|
|
235
|
+
case 'claude-user': return `claude mcp add excalidrop --scope user -- npx ${a}`;
|
|
236
|
+
case 'codex': return `codex mcp add excalidrop -- npx ${a}`;
|
|
237
|
+
case 'cursor': return JSON.stringify({ mcpServers: { excalidrop: { command: 'npx', args: mcpArgs(repo) } } }, null, 2);
|
|
238
|
+
case 'desktop': return JSON.stringify({ mcpServers: { excalidrop: { command: 'npx', args: mcpArgs(repo) } } }, null, 2);
|
|
239
|
+
case 'opencode': return JSON.stringify({ $schema: 'https://opencode.ai/config.json', mcp: { excalidrop: { type: 'local', command: ['npx', ...mcpArgs(repo)], enabled: true } } }, null, 2);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function installEditor(id, repo) {
|
|
243
|
+
try {
|
|
244
|
+
if (id === 'claude-project' && commandExists('claude')) {
|
|
245
|
+
return spawnSync('claude', ['mcp', 'add', 'excalidrop', '--scope', 'project', '--', 'npx', ...mcpArgs(repo)], { stdio: 'ignore' }).status === 0;
|
|
246
|
+
}
|
|
247
|
+
if (id === 'claude-user' && commandExists('claude')) {
|
|
248
|
+
return spawnSync('claude', ['mcp', 'add', 'excalidrop', '--scope', 'user', '--', 'npx', ...mcpArgs(repo)], { stdio: 'ignore' }).status === 0;
|
|
249
|
+
}
|
|
250
|
+
if (id === 'codex' && commandExists('codex')) {
|
|
251
|
+
return spawnSync('codex', ['mcp', 'add', 'excalidrop', '--', 'npx', ...mcpArgs(repo)], { stdio: 'ignore' }).status === 0;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch { /* fall through to manual */ }
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
const EDITOR_LABELS = {
|
|
258
|
+
'claude-project': 'Claude Code (project scope)',
|
|
259
|
+
'claude-user': 'Claude Code (user scope)',
|
|
260
|
+
codex: 'Codex CLI',
|
|
261
|
+
cursor: 'Cursor (.cursor/mcp.json)',
|
|
262
|
+
desktop: 'Claude Desktop',
|
|
263
|
+
opencode: 'OpenCode (opencode.json)',
|
|
264
|
+
};
|
|
265
|
+
function printEditorCommands(ids, repo) {
|
|
266
|
+
console.log('Add excalidrop to your AI agent (no install, always latest):\n');
|
|
267
|
+
for (const id of ids)
|
|
268
|
+
console.log(`## ${EDITOR_LABELS[id]}\n${editorCommand(id, repo)}\n`);
|
|
269
|
+
}
|
|
270
|
+
function parseFlag(args, name) {
|
|
271
|
+
const eq = args.find((a) => a.startsWith(`--${name}=`));
|
|
272
|
+
if (eq)
|
|
273
|
+
return eq.split('=').slice(1).join('=');
|
|
274
|
+
const i = args.indexOf(`--${name}`);
|
|
275
|
+
if (i !== -1 && args[i + 1] && !args[i + 1].startsWith('-'))
|
|
276
|
+
return args[i + 1];
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
function parseEditorsFlag(args) {
|
|
280
|
+
const raw = parseFlag(args, 'editor');
|
|
281
|
+
if (!raw)
|
|
282
|
+
return null;
|
|
283
|
+
const ids = raw.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
284
|
+
const valid = ids.filter((s) => EDITOR_IDS.includes(s));
|
|
285
|
+
if (!valid.length) {
|
|
286
|
+
console.error(`Unknown --editor value. Use comma-separated: ${EDITOR_IDS.join(',')}`);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
return valid;
|
|
290
|
+
}
|
|
291
|
+
async function cmdSetup(args, editorOverride) {
|
|
292
|
+
const positional = args.find((a) => !a.startsWith('-') && a.includes('/'));
|
|
293
|
+
const slug = parseFlag(args, 'repo') || positional || detectSlug();
|
|
294
|
+
if (!slug || !/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/i.test(slug)) {
|
|
295
|
+
console.error('Usage: npx excalidrop setup owner/repo [--target pages|cloudflare] [--editor claude-project,codex] [--no-prompt]');
|
|
296
|
+
process.exit(1);
|
|
201
297
|
}
|
|
298
|
+
if (!ghAuthed() && !process.env.GITHUB_TOKEN) {
|
|
299
|
+
console.error('Not logged in to GitHub. Run `gh auth login` or `npx excalidrop login` first.');
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
await ghGet(`/repos/${slug}`);
|
|
304
|
+
}
|
|
305
|
+
catch (e) {
|
|
306
|
+
if (String(e.message).includes('→ 404')) {
|
|
307
|
+
console.error(`Repo "${slug}" not found (404). Check the slug for typos, or create it:\n\n gh repo create ${slug} --private # or --public\n`);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
throw e;
|
|
311
|
+
}
|
|
312
|
+
const explicitTarget = parseFlag(args, 'target');
|
|
313
|
+
const target = await resolveTarget(slug, explicitTarget);
|
|
314
|
+
console.log(`Repo: ${slug}\nHost: ${target}${explicitTarget ? ' (explicit)' : ' (auto: private→cloudflare, public→pages)'}\n`);
|
|
315
|
+
await ensureMainBranch(slug);
|
|
316
|
+
const scene = await getScene(slug).catch(() => ({ elements: [], sha: null }));
|
|
317
|
+
if (!scene.sha) {
|
|
318
|
+
await putScene(slug, [], null, 'excalidrop: init canvas');
|
|
319
|
+
console.log('Empty canvas created on branch excalidrop.');
|
|
320
|
+
}
|
|
321
|
+
let viewerUrl;
|
|
322
|
+
if (target === 'pages') {
|
|
323
|
+
await deployViewerPages(slug);
|
|
324
|
+
viewerUrl = viewerUrlFor(slug, 'pages');
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
deployToCloudflarePages(projectNameForRepo(slug));
|
|
328
|
+
viewerUrl = viewerUrlFor(slug, 'cloudflare');
|
|
329
|
+
}
|
|
330
|
+
const root = findProjectRoot(process.cwd());
|
|
331
|
+
fs.writeFileSync(path.join(root, CONFIG_NAME), JSON.stringify({ remote: slug, target, viewerUrl }, null, 2) + '\n');
|
|
202
332
|
const mcpPath = path.join(root, MCP_JSON);
|
|
203
333
|
let mcp = {};
|
|
204
334
|
if (fs.existsSync(mcpPath)) {
|
|
@@ -206,283 +336,247 @@ async function cmdInit(args) {
|
|
|
206
336
|
mcp = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
|
207
337
|
}
|
|
208
338
|
catch {
|
|
209
|
-
console.warn(`
|
|
339
|
+
console.warn(`Existing ${MCP_JSON} is not valid JSON, leaving it untouched.`);
|
|
210
340
|
}
|
|
211
341
|
}
|
|
212
342
|
if (!mcp.mcpServers || typeof mcp.mcpServers !== 'object')
|
|
213
343
|
mcp.mcpServers = {};
|
|
214
|
-
mcp.mcpServers.excalidrop = { command: 'npx', args:
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
console.log('\nStart the canvas for this project with:');
|
|
220
|
-
console.log(` npx excalidrop up # ${config.canvasUrl}\n`);
|
|
344
|
+
mcp.mcpServers.excalidrop = { command: 'npx', args: mcpArgs(slug), env: { EXCALIDROP_RELAY_URL: process.env.EXCALIDROP_RELAY_URL || RELAY_DEFAULT } };
|
|
345
|
+
fs.writeFileSync(mcpPath, JSON.stringify(mcp, null, 2) + '\n');
|
|
346
|
+
console.log(`MCP entry written to ${mcpPath}`);
|
|
347
|
+
const editors = editorOverride || parseEditorsFlag(args) || [...EDITOR_IDS];
|
|
348
|
+
const noPrompt = args.includes('--no-prompt') || args.includes('-y');
|
|
221
349
|
if (noPrompt) {
|
|
222
|
-
|
|
223
|
-
|
|
350
|
+
const installed = [];
|
|
351
|
+
const manual = [];
|
|
352
|
+
for (const id of editors)
|
|
353
|
+
(installEditor(id, slug) ? installed : manual).push(id);
|
|
354
|
+
if (installed.length)
|
|
355
|
+
console.log(`Installed into: ${installed.map((i) => EDITOR_LABELS[i]).join(', ')}`);
|
|
356
|
+
if (manual.length)
|
|
357
|
+
printEditorCommands(manual, slug);
|
|
358
|
+
return { repo: slug, viewerUrl };
|
|
224
359
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
360
|
+
return { repo: slug, viewerUrl };
|
|
361
|
+
}
|
|
362
|
+
function cmdMcp() {
|
|
363
|
+
const child = spawn(process.execPath, [path.join(__dirname, 'mcp.js'), ...process.argv.slice(3)], { stdio: 'inherit', env: process.env });
|
|
364
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
|
365
|
+
}
|
|
366
|
+
async function cmdLogin() {
|
|
367
|
+
const clientId = process.env.GITHUB_OAUTH_CLIENT_ID || 'Iv23liuS2fx3QOEIoDmx';
|
|
368
|
+
console.log('Starting GitHub device login…');
|
|
369
|
+
const dev = await (await fetch('https://github.com/login/device/code', {
|
|
370
|
+
method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
|
371
|
+
body: JSON.stringify({ client_id: clientId }),
|
|
372
|
+
})).json();
|
|
373
|
+
console.log(`\nOpen ${dev.verification_uri} and enter code: ${dev.user_code}\n`);
|
|
374
|
+
const deadline = Date.now() + 300_000;
|
|
375
|
+
while (Date.now() < deadline) {
|
|
376
|
+
await new Promise((r) => setTimeout(r, (dev.interval || 5) * 1000));
|
|
377
|
+
const j = await (await fetch('https://github.com/login/oauth/access_token', {
|
|
378
|
+
method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
|
379
|
+
body: JSON.stringify({ client_id: clientId, device_code: dev.device_code, grant_type: 'urn:ietf:params:oauth:grant-type:device_code' }),
|
|
380
|
+
})).json();
|
|
381
|
+
if (j.access_token) {
|
|
382
|
+
const dir = path.join(os.homedir(), '.config', 'excalidrop');
|
|
383
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
384
|
+
fs.writeFileSync(path.join(dir, 'gh_token'), j.access_token + '\n', { mode: 0o600 });
|
|
385
|
+
console.log('Logged in — token stored in ~/.config/excalidrop/gh_token');
|
|
386
|
+
return;
|
|
242
387
|
}
|
|
388
|
+
if (j.error && j.error !== 'authorization_pending' && j.error !== 'slow_down')
|
|
389
|
+
throw new Error(j.error_description || j.error);
|
|
243
390
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
391
|
+
throw new Error('device login timed out');
|
|
392
|
+
}
|
|
393
|
+
async function cmdStatus() {
|
|
394
|
+
const flag = process.argv.find((a) => a.startsWith('--repo='));
|
|
395
|
+
const repo = flag ? flag.split('=').slice(1).join('=') : detectSlug();
|
|
396
|
+
console.log(`repo: ${repo || '(none — run setup or pass --repo=owner/repo)'}`);
|
|
397
|
+
console.log(`auth: ${ghToken() ? 'ok (env/gh CLI/stored)' : 'missing — run `gh auth login` or `npx excalidrop login`'}`);
|
|
398
|
+
console.log(`relay: ${process.env.EXCALIDROP_RELAY_URL || RELAY_DEFAULT}`);
|
|
399
|
+
if (!repo || !ghToken())
|
|
247
400
|
return;
|
|
401
|
+
try {
|
|
402
|
+
const scene = await getScene(repo);
|
|
403
|
+
console.log(`scene: ${scene.elements.length} elements @${CANVAS_BRANCH}`);
|
|
248
404
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const scopeAnswer = await prompt('Claude Code scope? [project/user] (default: project) ');
|
|
252
|
-
const scope = /user/i.test(scopeAnswer) ? 'user' : 'project';
|
|
253
|
-
const r = spawnSync('claude', ['mcp', 'add', 'excalidrop', '--scope', scope, '--', 'npx', 'excalidrop', 'mcp'], {
|
|
254
|
-
stdio: 'inherit',
|
|
255
|
-
});
|
|
256
|
-
installed = r.status === 0;
|
|
257
|
-
}
|
|
258
|
-
if (!installed && commandExists('codex')) {
|
|
259
|
-
const useIt = await prompt('Run `codex mcp add excalidrop`? [Y/n] ');
|
|
260
|
-
if (!useIt || /^(y|yes)$/i.test(useIt)) {
|
|
261
|
-
const r = spawnSync('codex', ['mcp', 'add', 'excalidrop', '--', 'npx', 'excalidrop', 'mcp'], {
|
|
262
|
-
stdio: 'inherit',
|
|
263
|
-
});
|
|
264
|
-
installed = r.status === 0;
|
|
265
|
-
}
|
|
405
|
+
catch (e) {
|
|
406
|
+
console.log(`scene: unreachable (${e.message})`);
|
|
266
407
|
}
|
|
267
|
-
if (!installed)
|
|
268
|
-
printAgentCommands();
|
|
269
408
|
}
|
|
270
|
-
function
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
409
|
+
async function runTui() {
|
|
410
|
+
let clack;
|
|
411
|
+
try {
|
|
412
|
+
clack = await import('@clack/prompts');
|
|
274
413
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const preferred = portFlag ? Number(portFlag.split('=')[1]) : undefined;
|
|
279
|
-
const root = findProjectRoot(process.cwd());
|
|
280
|
-
const existing = findDropConfig(process.cwd());
|
|
281
|
-
let port;
|
|
282
|
-
if (preferred) {
|
|
283
|
-
port = await findFreePort(preferred);
|
|
414
|
+
catch {
|
|
415
|
+
console.error('TUI needs @clack/prompts. Run instead: npx excalidrop setup owner/repo');
|
|
416
|
+
process.exit(1);
|
|
284
417
|
}
|
|
285
|
-
|
|
286
|
-
|
|
418
|
+
clack.intro('excalidrop — remote canvas setup');
|
|
419
|
+
const detected = detectSlug();
|
|
420
|
+
const repoAnswer = await clack.text({
|
|
421
|
+
message: 'Which repo holds the canvas?',
|
|
422
|
+
placeholder: detected || 'owner/repo',
|
|
423
|
+
initialValue: detected || '',
|
|
424
|
+
validate: (v) => (/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/i.test(v.trim()) ? undefined : 'Use owner/repo'),
|
|
425
|
+
});
|
|
426
|
+
if (clack.isCancel(repoAnswer)) {
|
|
427
|
+
clack.cancel('Aborted.');
|
|
428
|
+
process.exit(0);
|
|
287
429
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
430
|
+
const slug = repoAnswer.trim().toLowerCase();
|
|
431
|
+
const priv = await isPrivateRepo(slug);
|
|
432
|
+
if (priv !== null)
|
|
433
|
+
clack.log.info(`Repo is ${priv ? 'private' : 'public'} — recommended host: ${priv ? 'Cloudflare Pages' : 'GitHub Pages'}.`);
|
|
434
|
+
if (!ghAuthed() && !process.env.GITHUB_TOKEN) {
|
|
435
|
+
const how = await clack.select({
|
|
436
|
+
message: 'GitHub auth missing. How to log in?',
|
|
437
|
+
options: [
|
|
438
|
+
{ value: 'retry', label: 'I ran `gh auth login` (re-check)' },
|
|
439
|
+
{ value: 'device', label: 'Device flow (code + browser, 2FA ok)' },
|
|
440
|
+
{ value: 'skip', label: 'Skip (I set GITHUB_TOKEN later)' },
|
|
441
|
+
],
|
|
442
|
+
});
|
|
443
|
+
if (clack.isCancel(how)) {
|
|
444
|
+
clack.cancel('Aborted.');
|
|
445
|
+
process.exit(0);
|
|
446
|
+
}
|
|
447
|
+
if (how === 'device') {
|
|
448
|
+
const s = clack.spinner();
|
|
449
|
+
s.start('Waiting for browser approval…');
|
|
450
|
+
try {
|
|
451
|
+
await cmdLogin();
|
|
452
|
+
s.stop('Logged in.');
|
|
453
|
+
}
|
|
454
|
+
catch (e) {
|
|
455
|
+
s.stop('Login failed: ' + e.message);
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
else if (how === 'retry' && !ghAuthed() && !process.env.GITHUB_TOKEN) {
|
|
460
|
+
clack.log.error('Still not logged in. Run `gh auth login` first.');
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const auto = await resolveTarget(slug);
|
|
465
|
+
const hostAnswer = await clack.select({
|
|
466
|
+
message: `Where to host the viewer? (auto: ${auto})`,
|
|
467
|
+
options: [
|
|
468
|
+
{ value: 'auto', label: `Auto — ${auto === 'pages' ? 'GitHub Pages (public repo)' : 'Cloudflare Pages (private repo)'}`, hint: 'recommended' },
|
|
469
|
+
{ value: 'pages', label: 'GitHub Pages', hint: 'public repos, zero config' },
|
|
470
|
+
{ value: 'cloudflare', label: 'Cloudflare Pages', hint: 'private repos' },
|
|
471
|
+
],
|
|
297
472
|
});
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
473
|
+
if (clack.isCancel(hostAnswer)) {
|
|
474
|
+
clack.cancel('Aborted.');
|
|
475
|
+
process.exit(0);
|
|
476
|
+
}
|
|
477
|
+
const target = (hostAnswer === 'auto' ? auto : hostAnswer);
|
|
478
|
+
const editorAnswer = await clack.multiselect({
|
|
479
|
+
message: 'Install the MCP server into which editors?',
|
|
480
|
+
options: Object.keys(EDITOR_LABELS).map((id) => ({ value: id, label: EDITOR_LABELS[id] })),
|
|
481
|
+
initialValues: ['claude-project', 'codex'],
|
|
482
|
+
required: false,
|
|
306
483
|
});
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
484
|
+
if (clack.isCancel(editorAnswer)) {
|
|
485
|
+
clack.cancel('Aborted.');
|
|
486
|
+
process.exit(0);
|
|
487
|
+
}
|
|
488
|
+
const editors = editorAnswer;
|
|
489
|
+
const s = clack.spinner();
|
|
490
|
+
s.start('Publishing viewer + wiring MCP…');
|
|
491
|
+
let viewerUrl;
|
|
315
492
|
try {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (res.ok)
|
|
319
|
-
console.log(await res.text());
|
|
320
|
-
}
|
|
321
|
-
catch (err) {
|
|
322
|
-
console.log(`health: unreachable (${err.message}) — start it with \`npx excalidrop up\``);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
async function cmdPages(_args) {
|
|
326
|
-
console.log(`excalidrop pages (GitHub as source of truth, no Actions, no setup)\n`);
|
|
327
|
-
console.log('1. Publish viewer: npx excalidrop publish');
|
|
328
|
-
console.log(' (uses `gh auth` — repo auto-detected, Pages auto-enabled.)\n');
|
|
329
|
-
console.log('2. In your agent: switch_remote { target: "<owner>.github.io/<repo>" }');
|
|
330
|
-
console.log(' Draw as usual — commits land on GitHub, viewer updates on gh-pages.\n');
|
|
331
|
-
console.log('3. Login: ask the agent to run github_login (device flow, 2FA via GitHub),');
|
|
332
|
-
console.log(' or paste a token (`gh auth token`) on the viewer login pill.\n');
|
|
333
|
-
console.log('Rule: never edit the canvas locally — the Pages site + GitHub repo are the canvas.');
|
|
334
|
-
}
|
|
335
|
-
function cmdRemote() {
|
|
336
|
-
const child = spawn(process.execPath, [path.join(__dirname, 'remote.js')], { stdio: 'inherit', env: process.env });
|
|
337
|
-
child.on('exit', (code) => process.exit(code ?? 0));
|
|
338
|
-
}
|
|
339
|
-
async function cmdSetup(args) {
|
|
340
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
341
|
-
console.log('Usage: npx excalidrop setup\n\nChecks `gh auth`, publishes the viewer to this repo\'s gh-pages\n(enabling Pages if needed), then prints the app-install link and next steps.');
|
|
342
|
-
return;
|
|
493
|
+
({ viewerUrl } = await cmdSetup(['--no-prompt', slug, `--target=${target}`], editors));
|
|
494
|
+
s.stop('Setup complete.');
|
|
343
495
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const auth = spawnSync('gh', ['auth', 'status'], { stdio: 'pipe', encoding: 'utf8' });
|
|
347
|
-
if (auth.status !== 0) {
|
|
348
|
-
console.error('Not logged in to GitHub. Run this first:\n\n gh auth login\n');
|
|
496
|
+
catch (e) {
|
|
497
|
+
s.stop('Setup failed: ' + e.message);
|
|
349
498
|
process.exit(1);
|
|
350
499
|
}
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const m = url.match(/github\.com[:/](.+)/);
|
|
355
|
-
return m?.[1] || '';
|
|
500
|
+
for (const id of editors) {
|
|
501
|
+
if (id === 'claude-project' || id === 'claude-user' || id === 'codex') {
|
|
502
|
+
clack.log.success(`${EDITOR_LABELS[id]}: installed (or already present).`);
|
|
356
503
|
}
|
|
357
|
-
|
|
358
|
-
|
|
504
|
+
else {
|
|
505
|
+
clack.log.info(`${EDITOR_LABELS[id]} config:\n${editorCommand(id, slug)}`);
|
|
359
506
|
}
|
|
360
|
-
})();
|
|
361
|
-
if (!slug) {
|
|
362
|
-
console.error('No GitHub remote detected. Run setup inside a cloned repo.\n');
|
|
363
|
-
process.exit(1);
|
|
364
507
|
}
|
|
365
|
-
console.log(`Repo: ${slug} (detected from git remote)\n`);
|
|
366
|
-
// 2. publish viewer (auto-enables Pages, auto-syncs scene)
|
|
367
|
-
await new Promise((resolve, reject) => {
|
|
368
|
-
const child = spawn('bash', [path.join(__dirname, '../scripts/publish-pages.sh')], { stdio: 'inherit', env: { ...process.env, REPO_SLUG: slug } });
|
|
369
|
-
child.on('exit', (code) => (code === 0 ? resolve() : reject(new Error(`publish failed (exit ${code})`))));
|
|
370
|
-
});
|
|
371
|
-
// 3. remember the target: agent auto-loads it, no switch_remote needed
|
|
372
508
|
try {
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
fs.writeFileSync(cfgFile, JSON.stringify({ port: cfg.port || 3030, canvasUrl: cfg.canvasUrl || 'http://127.0.0.1:3030', ...cfg, remote: slug }, null, 2) + '\n');
|
|
376
|
-
console.log(`Remote target remembered in ${CONFIG_NAME} (agent uses it automatically).`);
|
|
377
|
-
}
|
|
378
|
-
catch { /* non-fatal */ }
|
|
379
|
-
// 4. verify: Pages serves the viewer AND the scene file
|
|
380
|
-
const [sOwner, sRepo] = slug.split('/');
|
|
381
|
-
let verified = false;
|
|
382
|
-
for (let i = 0; i < 12; i++) {
|
|
383
|
-
await new Promise((r) => setTimeout(r, 10000));
|
|
384
|
-
try {
|
|
385
|
-
const res = await fetch(`https://${sOwner}.github.io/${sRepo}/canvas.excalidraw`, { signal: AbortSignal.timeout(15000) });
|
|
386
|
-
if (res.ok) {
|
|
387
|
-
verified = true;
|
|
388
|
-
break;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
catch { /* Pages build still running */ }
|
|
392
|
-
}
|
|
393
|
-
console.log(verified ? 'Verified: viewer + scene live.' : 'Note: Pages still building — check back in a minute.');
|
|
394
|
-
// 4. next steps: install the shared app + draw
|
|
395
|
-
const appSlug = process.env.EXCALIDROP_APP_SLUG || 'excalidrop';
|
|
396
|
-
console.log(`\nDone. Your canvas: https://${sOwner}.github.io/${sRepo}/\n`);
|
|
397
|
-
console.log('Two remaining clicks (one time per repo):');
|
|
398
|
-
console.log(` 1. Install the Excalidrop app on this repo:\n https://github.com/apps/${appSlug}/installations/new\n`);
|
|
399
|
-
console.log(' 2. In your agent: switch_remote { target: "<that canvas URL>" }');
|
|
400
|
-
console.log(' then draw — commits land on GitHub, the viewer updates itself.');
|
|
401
|
-
console.log('\nLogin on the page: pill → device code → github.com/login/device (2FA via GitHub).');
|
|
402
|
-
}
|
|
403
|
-
async function cmdLogin() {
|
|
404
|
-
const { APP_CLIENT_ID } = await import('./target.js');
|
|
405
|
-
const clientId = process.env.GITHUB_OAUTH_CLIENT_ID || APP_CLIENT_ID;
|
|
406
|
-
if (!clientId) {
|
|
407
|
-
console.error('No GitHub App Client ID configured yet.');
|
|
408
|
-
console.error('Set GITHUB_OAUTH_CLIENT_ID (or wait for the shared excalidrop app),');
|
|
409
|
-
console.error('or just run `gh auth login` — the MCP picks that token up automatically.');
|
|
410
|
-
process.exit(1);
|
|
509
|
+
const scene = await getScene(slug);
|
|
510
|
+
clack.log.success(`Scene readable: ${scene.elements.length} elements @${CANVAS_BRANCH}.`);
|
|
411
511
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
512
|
+
catch (e) {
|
|
513
|
+
clack.log.warn(`Scene not readable yet: ${e.message} — draw once to create it.`);
|
|
514
|
+
}
|
|
515
|
+
try {
|
|
516
|
+
const r = await fetch(viewerUrl, { signal: AbortSignal.timeout(15000) });
|
|
517
|
+
if (r.ok)
|
|
518
|
+
clack.log.success('Viewer is serving.');
|
|
519
|
+
else
|
|
520
|
+
clack.log.warn(`Viewer returned HTTP ${r.status} — Pages/Cloudflare may still be building; retry in a minute.`);
|
|
521
|
+
}
|
|
522
|
+
catch {
|
|
523
|
+
clack.log.warn('Viewer not reachable yet — hosting may still be building; retry in a minute.');
|
|
524
|
+
}
|
|
525
|
+
clack.outro(`Canvas live: ${viewerUrl}`);
|
|
421
526
|
}
|
|
422
527
|
function printHelp() {
|
|
423
|
-
console.log(`excalidrop —
|
|
528
|
+
console.log(`excalidrop — remote GitHub-backed canvas (no local server)
|
|
424
529
|
|
|
425
530
|
Usage:
|
|
426
|
-
npx excalidrop
|
|
427
|
-
npx excalidrop
|
|
428
|
-
npx
|
|
429
|
-
npx excalidrop login
|
|
430
|
-
npx excalidrop
|
|
431
|
-
npx excalidrop
|
|
432
|
-
npx excalidrop
|
|
433
|
-
npx excalidrop mcp run MCP stdio server (used by AI agents)
|
|
434
|
-
npx excalidrop status show port + canvas health
|
|
435
|
-
npx excalidrop add print AI-agent install commands
|
|
531
|
+
npx excalidrop interactive setup TUI
|
|
532
|
+
npx excalidrop setup owner/repo [--target pages|cloudflare] [--editor claude-project,codex,cursor,desktop,opencode] [--no-prompt]
|
|
533
|
+
npx -y excalidrop@latest mcp --repo owner/repo run MCP stdio server (editors use this)
|
|
534
|
+
npx excalidrop login [--repo owner/repo] GitHub device-flow login
|
|
535
|
+
npx excalidrop publish owner/repo [--target ..] redeploy viewer only
|
|
536
|
+
npx excalidrop status show repo + auth + scene
|
|
537
|
+
npx excalidrop add [owner/repo] print editor install commands
|
|
436
538
|
|
|
437
|
-
|
|
438
|
-
npm i -D excalidrop && npx excalidrop init
|
|
439
|
-
|
|
440
|
-
Each project gets its own port (scanned free from ${DEFAULT_START_PORT}), so several
|
|
441
|
-
checkouts can run concurrently. Agent config needs no hardcoded port.`);
|
|
539
|
+
No devDependency needed. Token: GITHUB_TOKEN → gh auth → ~/.config/excalidrop/gh_token.`);
|
|
442
540
|
}
|
|
443
541
|
async function main() {
|
|
444
542
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
543
|
+
if (!cmd) {
|
|
544
|
+
if (!process.stdout.isTTY) {
|
|
545
|
+
printHelp();
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
await runTui();
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
445
551
|
switch (cmd) {
|
|
446
|
-
case '
|
|
447
|
-
await
|
|
448
|
-
break;
|
|
449
|
-
case 'up':
|
|
450
|
-
await cmdUp(rest);
|
|
552
|
+
case 'setup':
|
|
553
|
+
await cmdSetup(rest);
|
|
451
554
|
break;
|
|
452
555
|
case 'mcp':
|
|
453
556
|
cmdMcp();
|
|
454
557
|
break;
|
|
455
|
-
case 'status':
|
|
456
|
-
await cmdStatus();
|
|
457
|
-
break;
|
|
458
|
-
case 'pages':
|
|
459
|
-
await cmdPages(rest);
|
|
460
|
-
break;
|
|
461
|
-
case 'remote':
|
|
462
|
-
cmdRemote();
|
|
463
|
-
break;
|
|
464
|
-
case 'setup':
|
|
465
|
-
await cmdSetup(rest);
|
|
466
|
-
break;
|
|
467
558
|
case 'login':
|
|
468
559
|
await cmdLogin();
|
|
469
560
|
break;
|
|
470
|
-
case '
|
|
471
|
-
|
|
472
|
-
|
|
561
|
+
case 'status':
|
|
562
|
+
await cmdStatus();
|
|
563
|
+
break;
|
|
564
|
+
case 'publish':
|
|
565
|
+
await cmdSetup(['--no-prompt', ...rest]);
|
|
473
566
|
break;
|
|
474
|
-
}
|
|
475
567
|
case 'add':
|
|
476
|
-
|
|
568
|
+
printEditorCommands(parseEditorsFlag(rest) || [...EDITOR_IDS], rest.find((a) => a.includes('/')) || detectSlug() || 'owner/repo');
|
|
569
|
+
break;
|
|
570
|
+
case 'init':
|
|
571
|
+
case 'up':
|
|
572
|
+
case 'remote':
|
|
573
|
+
console.error(`\`excalidrop ${cmd}\` was removed (no local canvas). Run: npx excalidrop setup owner/repo`);
|
|
574
|
+
process.exit(1);
|
|
477
575
|
break;
|
|
478
576
|
default:
|
|
479
577
|
printHelp();
|
|
480
|
-
|
|
481
|
-
process.exitCode = 1;
|
|
578
|
+
process.exitCode = 1;
|
|
482
579
|
}
|
|
483
580
|
}
|
|
484
|
-
main().catch((err) => {
|
|
485
|
-
console.error(`excalidrop: ${err.message}`);
|
|
486
|
-
process.exit(1);
|
|
487
|
-
});
|
|
581
|
+
main().catch((err) => { console.error(`excalidrop: ${err.message}`); process.exit(1); });
|
|
488
582
|
//# sourceMappingURL=cli.js.map
|