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.
Files changed (222) hide show
  1. package/README.md +58 -131
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +488 -394
  4. package/dist/cli.js.map +1 -1
  5. package/dist/frontend/assets/{advancedFormat-tiBAHNGo.js → advancedFormat-CsxROTkH.js} +1 -1
  6. package/dist/frontend/assets/{ar-SA-G6X2FPQ2-DVxiWV5Z.js → ar-SA-G6X2FPQ2-Bu8X6mOi.js} +1 -1
  7. package/dist/frontend/assets/arc-B9VNLSzj.js +1 -0
  8. package/dist/frontend/assets/{az-AZ-76LH7QW2-Z_2BqDo-.js → az-AZ-76LH7QW2-DpvzY31_.js} +1 -1
  9. package/dist/frontend/assets/{bg-BG-XCXSNQG7-DrxGJZOG.js → bg-BG-XCXSNQG7-DCtZnLyo.js} +1 -1
  10. package/dist/frontend/assets/{blockDiagram-38ab4fdb-CDnkTFfP.js → blockDiagram-38ab4fdb-4cfDkUsw.js} +1 -1
  11. package/dist/frontend/assets/{blockDiagram-68f4deed-Bb0W6mxN.js → blockDiagram-68f4deed-CsgWL6uC.js} +1 -1
  12. package/dist/frontend/assets/{bn-BD-2XOGV67Q-BkGJ-w8k.js → bn-BD-2XOGV67Q-B-IdLNKT.js} +1 -1
  13. package/dist/frontend/assets/{c4Diagram-15b5d702-ByRV0tka.js → c4Diagram-15b5d702-7BAqo4qT.js} +1 -1
  14. package/dist/frontend/assets/{c4Diagram-3d4e48cf-QSVnzYYK.js → c4Diagram-3d4e48cf-CDW-O-vg.js} +1 -1
  15. package/dist/frontend/assets/{ca-ES-6MX7JW3Y-BBJvOPE1.js → ca-ES-6MX7JW3Y-DM8olClc.js} +1 -1
  16. package/dist/frontend/assets/channel-jdNM4v-F.js +1 -0
  17. package/dist/frontend/assets/{classDiagram-70f12bd4-DDFBNGUt.js → classDiagram-70f12bd4-D2U4i4yC.js} +1 -1
  18. package/dist/frontend/assets/{classDiagram-d40c83e7-cgC3s7Nz.js → classDiagram-d40c83e7-DtBHRJc9.js} +1 -1
  19. package/dist/frontend/assets/classDiagram-v2-d5a6b087-Drykcxns.js +2 -0
  20. package/dist/frontend/assets/classDiagram-v2-f2320105-D8ruMBA6.js +2 -0
  21. package/dist/frontend/assets/clone-DFO_zGRO.js +1 -0
  22. package/dist/frontend/assets/{createText-2e5e7dd3-CqY00v-4.js → createText-2e5e7dd3-1KPUhDpM.js} +1 -1
  23. package/dist/frontend/assets/{createText-d213de94-D59sBfqq.js → createText-d213de94-C6EhGbUN.js} +4 -4
  24. package/dist/frontend/assets/{cs-CZ-2BRQDIVT-CK09MAsg.js → cs-CZ-2BRQDIVT-DgjTgz6K.js} +1 -1
  25. package/dist/frontend/assets/{cytoscape-cose-bilkent-CKy_tuGZ.js → cytoscape-cose-bilkent-BnIHl72R.js} +1 -1
  26. package/dist/frontend/assets/{da-DK-5WZEPLOC-V4hdLKuT.js → da-DK-5WZEPLOC-uEs3xGKF.js} +1 -1
  27. package/dist/frontend/assets/{de-DE-XR44H4JA-Dw8qkP9g.js → de-DE-XR44H4JA-CBglLw42.js} +1 -1
  28. package/dist/frontend/assets/{edges-332bd1c7-D-u0KMZB.js → edges-332bd1c7-cWCBwcrg.js} +1 -1
  29. package/dist/frontend/assets/{edges-e0da2a9e-B3UFqLqY.js → edges-e0da2a9e-DKeDUJ_F.js} +1 -1
  30. package/dist/frontend/assets/{el-GR-BZB4AONW-BUdBQbYg.js → el-GR-BZB4AONW-B-_eub00.js} +1 -1
  31. package/dist/frontend/assets/{elk.bundled-BZA3Xcoo.js → elk.bundled-Ch9TeAu4.js} +1 -1
  32. package/dist/frontend/assets/{erDiagram-880f2ed8-CNxm7e1x.js → erDiagram-880f2ed8-CtDf_58r.js} +4 -4
  33. package/dist/frontend/assets/{erDiagram-9861fffd-C2u_ziAM.js → erDiagram-9861fffd-CAlUeljk.js} +1 -1
  34. package/dist/frontend/assets/{es-ES-U4NZUMDT-DS0FAZqX.js → es-ES-U4NZUMDT-BIN3bSVD.js} +1 -1
  35. package/dist/frontend/assets/{eu-ES-A7QVB2H4-D-D5uidc.js → eu-ES-A7QVB2H4-TyQet3-N.js} +1 -1
  36. package/dist/frontend/assets/{fa-IR-HGAKTJCU-mAW_2-jg.js → fa-IR-HGAKTJCU-DVmjjr57.js} +1 -1
  37. package/dist/frontend/assets/{fi-FI-Z5N7JZ37-B9mW6nxS.js → fi-FI-Z5N7JZ37-ZW9kXLa8.js} +1 -1
  38. package/dist/frontend/assets/{flowDb-7c981674-6JB2gd0I.js → flowDb-7c981674-B5ppx8EN.js} +1 -1
  39. package/dist/frontend/assets/{flowDb-956e92f1-BbpkWRi7.js → flowDb-956e92f1-Dqi00Wq-.js} +1 -1
  40. package/dist/frontend/assets/{flowDiagram-66a62f08-DrhrQuXc.js → flowDiagram-66a62f08-v3Liu0m_.js} +1 -1
  41. package/dist/frontend/assets/{flowDiagram-cbd28bf7-BmNY7bvJ.js → flowDiagram-cbd28bf7-DJ_YHRwa.js} +3 -3
  42. package/dist/frontend/assets/flowDiagram-v2-96b9c2cf-6vlgZ0Bg.js +1 -0
  43. package/dist/frontend/assets/flowDiagram-v2-ffc7f31a-FmnI78Vm.js +1 -0
  44. package/dist/frontend/assets/{flowchart-elk-definition-36e2d292-4G1YiIlk.js → flowchart-elk-definition-36e2d292-BleYul6b.js} +2 -2
  45. package/dist/frontend/assets/{flowchart-elk-definition-4a651766-Cm94SrrQ.js → flowchart-elk-definition-4a651766-D6lLh4Ih.js} +1 -1
  46. package/dist/frontend/assets/{fr-FR-RHASNOE6-BXnmUxTA.js → fr-FR-RHASNOE6-CxLR66tH.js} +1 -1
  47. package/dist/frontend/assets/{ganttDiagram-04f9e578-X0BweAnG.js → ganttDiagram-04f9e578-DJSOyr8B.js} +1 -1
  48. package/dist/frontend/assets/{ganttDiagram-c361ad54-D3vxZ5MQ.js → ganttDiagram-c361ad54-CJmbCLvp.js} +1 -1
  49. package/dist/frontend/assets/{gitGraphDiagram-21fc4d3e-wznrpRWn.js → gitGraphDiagram-21fc4d3e-DiRq_FJc.js} +6 -6
  50. package/dist/frontend/assets/{gitGraphDiagram-72cf32ee-B9zCS3Pw.js → gitGraphDiagram-72cf32ee-MfvDzoAb.js} +1 -1
  51. package/dist/frontend/assets/{gl-ES-HMX3MZ6V-CrUx-ab6.js → gl-ES-HMX3MZ6V-CxWj2itK.js} +1 -1
  52. package/dist/frontend/assets/{graph-BQZ6SIVr.js → graph-CT_r46y9.js} +1 -1
  53. package/dist/frontend/assets/{graph-Cda8xSTx.js → graph-DJt_Vezo.js} +1 -1
  54. package/dist/frontend/assets/{he-IL-6SHJWFNN-D33eaw6-.js → he-IL-6SHJWFNN-5v2WFGO3.js} +1 -1
  55. package/dist/frontend/assets/{hi-IN-IWLTKZ5I-DEnStF-g.js → hi-IN-IWLTKZ5I-BJGbvgRu.js} +1 -1
  56. package/dist/frontend/assets/{hu-HU-A5ZG7DT2-BjH2w8Sn.js → hu-HU-A5ZG7DT2-REQT2EcE.js} +1 -1
  57. package/dist/frontend/assets/{id-ID-SAP4L64H-Bc6R-JEz.js → id-ID-SAP4L64H-aZWQYWdg.js} +1 -1
  58. package/dist/frontend/assets/{index-3862675e-CpYfS3Db.js → index-3862675e-BgrvgbsM.js} +1 -1
  59. package/dist/frontend/assets/{index-6079d271-Y2bgyRpa.js → index-6079d271-Cp8jFHTC.js} +1 -1
  60. package/dist/frontend/assets/{index-D7eYnAQp.js → index-DBHHeSXy.js} +4 -4
  61. package/dist/frontend/assets/index-GTq6y6g6.js +349 -0
  62. package/dist/frontend/assets/{infoDiagram-4a4f5b27-Bm3Oq-Rq.js → infoDiagram-4a4f5b27-C1ZJJtWv.js} +3 -3
  63. package/dist/frontend/assets/{infoDiagram-f8f76790-oNVitBKM.js → infoDiagram-f8f76790-GeS0JKxo.js} +1 -1
  64. package/dist/frontend/assets/{it-IT-JPQ66NNP-BcxQABF2.js → it-IT-JPQ66NNP-EyeA2Ty1.js} +1 -1
  65. package/dist/frontend/assets/{ja-JP-DBVTYXUO-D1zIM-nt.js → ja-JP-DBVTYXUO-BLcE45D9.js} +1 -1
  66. package/dist/frontend/assets/{journeyDiagram-29694f62-CamPk_3P.js → journeyDiagram-29694f62-4Z_lpK4g.js} +5 -5
  67. package/dist/frontend/assets/{journeyDiagram-49397b02-D6w3tLtf.js → journeyDiagram-49397b02-ChwU3Zo6.js} +1 -1
  68. package/dist/frontend/assets/{kaa-6HZHGXH3-CoILysVg.js → kaa-6HZHGXH3-CkpTcX9X.js} +1 -1
  69. package/dist/frontend/assets/{kab-KAB-ZGHBKWFO-B9ZbybBt.js → kab-KAB-ZGHBKWFO-CChq0y6o.js} +1 -1
  70. package/dist/frontend/assets/{kk-KZ-P5N5QNE5-Dxi4GIQ7.js → kk-KZ-P5N5QNE5-CwtoTY4z.js} +1 -1
  71. package/dist/frontend/assets/{km-KH-HSX4SM5Z-DaJ7EtO2.js → km-KH-HSX4SM5Z-D26sdohj.js} +1 -1
  72. package/dist/frontend/assets/{ko-KR-MTYHY66A-DG57Ah12.js → ko-KR-MTYHY66A-DvnDcJQA.js} +1 -1
  73. package/dist/frontend/assets/{ku-TR-6OUDTVRD-DnDf9E8a.js → ku-TR-6OUDTVRD-qdHbLgKg.js} +1 -1
  74. package/dist/frontend/assets/{layout-Drr-66M9.js → layout-C-wUeNvb.js} +1 -1
  75. package/dist/frontend/assets/{layout-BsWfhUAF.js → layout-YRLNN4au.js} +1 -1
  76. package/dist/frontend/assets/{line-DwQObOhP.js → line-BEgMClKG.js} +1 -1
  77. package/dist/frontend/assets/{linear-C36MjJps.js → linear-DDHpSdYw.js} +1 -1
  78. package/dist/frontend/assets/{lt-LT-XHIRWOB4-D_zqS0yg.js → lt-LT-XHIRWOB4-DT4LYe_d.js} +1 -1
  79. package/dist/frontend/assets/{lv-LV-5QDEKY6T-owIsIh1b.js → lv-LV-5QDEKY6T-Dbc3ivwT.js} +1 -1
  80. package/dist/frontend/assets/{mindmap-definition-ac74a2e8-Cf9JbrBJ.js → mindmap-definition-ac74a2e8-CY2RhWUN.js} +6 -6
  81. package/dist/frontend/assets/{mindmap-definition-fc14e90a-BpsNCNCp.js → mindmap-definition-fc14e90a-VT4JDi6N.js} +1 -1
  82. package/dist/frontend/assets/{mr-IN-CRQNXWMA-CS9XUj74.js → mr-IN-CRQNXWMA-D75p1fVc.js} +1 -1
  83. package/dist/frontend/assets/{my-MM-5M5IBNSE-BKB060k9.js → my-MM-5M5IBNSE-DnEoBXyE.js} +1 -1
  84. package/dist/frontend/assets/{nb-NO-T6EIAALU-CkmLUrvm.js → nb-NO-T6EIAALU-BL4ThJxl.js} +1 -1
  85. package/dist/frontend/assets/{nl-NL-IS3SIHDZ-DdiLyJoz.js → nl-NL-IS3SIHDZ-u18nUblY.js} +1 -1
  86. package/dist/frontend/assets/{nn-NO-6E72VCQL-Yevp6f_Y.js → nn-NO-6E72VCQL-B9LJ0oXc.js} +1 -1
  87. package/dist/frontend/assets/{oc-FR-POXYY2M6-D_M6GHup.js → oc-FR-POXYY2M6-BKG5kIcO.js} +1 -1
  88. package/dist/frontend/assets/{pa-IN-N4M65BXN-1vuACN9n.js → pa-IN-N4M65BXN-CmpnjbkL.js} +1 -1
  89. package/dist/frontend/assets/{pica-DDDx0iqx.js → pica-BcpcM0v1.js} +1 -1
  90. package/dist/frontend/assets/{pie-CFIpuQMO.js → pie-Swzl5NrI.js} +1 -1
  91. package/dist/frontend/assets/{pieDiagram-421022e6-DO7e_Qgw.js → pieDiagram-421022e6-ndz96BLe.js} +5 -5
  92. package/dist/frontend/assets/{pieDiagram-8a3498a8-BEoqChXo.js → pieDiagram-8a3498a8-D5rcV68O.js} +1 -1
  93. package/dist/frontend/assets/{pl-PL-T2D74RX3-Cc6Q5nSh.js → pl-PL-T2D74RX3-CrR66Rgj.js} +1 -1
  94. package/dist/frontend/assets/{pt-BR-5N22H2LF-3eEiAax3.js → pt-BR-5N22H2LF-Crv7jZiH.js} +1 -1
  95. package/dist/frontend/assets/{pt-PT-UZXXM6DQ-CgnF0Le7.js → pt-PT-UZXXM6DQ-Bkaa5ESR.js} +1 -1
  96. package/dist/frontend/assets/{quadrantDiagram-0957ecba-C1Eo-ZUY.js → quadrantDiagram-0957ecba-DOdyxeTP.js} +3 -3
  97. package/dist/frontend/assets/{quadrantDiagram-120e2f19-Bdfsl_O7.js → quadrantDiagram-120e2f19-beQD7R1C.js} +1 -1
  98. package/dist/frontend/assets/relay-BIOL2uw2.js +1 -0
  99. package/dist/frontend/assets/{requirementDiagram-23d650b8-DiOK6QW3.js → requirementDiagram-23d650b8-B2i-OB97.js} +1 -1
  100. package/dist/frontend/assets/{requirementDiagram-deff3bca-BDJ-7T4h.js → requirementDiagram-deff3bca-beS-SvfY.js} +1 -1
  101. package/dist/frontend/assets/{ro-RO-JPDTUUEW-CbJ3IB7a.js → ro-RO-JPDTUUEW-DsrnQm7K.js} +1 -1
  102. package/dist/frontend/assets/{ru-RU-B4JR7IUQ-PQGmeGG4.js → ru-RU-B4JR7IUQ-DrnGGdUs.js} +1 -1
  103. package/dist/frontend/assets/{sankeyDiagram-04a897e0-DV25bgN2.js → sankeyDiagram-04a897e0-CVad45bN.js} +1 -1
  104. package/dist/frontend/assets/{sankeyDiagram-23345273-szSb_Sei.js → sankeyDiagram-23345273-ghWBXXF2.js} +1 -1
  105. package/dist/frontend/assets/{selectAll-DsrPvhqV.js → selectAll-XplaO988.js} +1 -1
  106. package/dist/frontend/assets/{sequenceDiagram-17ac3bff-Dg88bSlc.js → sequenceDiagram-17ac3bff-DBI-EdWx.js} +1 -1
  107. package/dist/frontend/assets/{sequenceDiagram-704730f1-DRIjLCxx.js → sequenceDiagram-704730f1-BnJ__mBt.js} +1 -1
  108. package/dist/frontend/assets/{si-LK-N5RQ5JYF-BtIIudm3.js → si-LK-N5RQ5JYF-BxAwuRM5.js} +1 -1
  109. package/dist/frontend/assets/{sk-SK-C5VTKIMK-CIwXticU.js → sk-SK-C5VTKIMK-CnDyiLRf.js} +1 -1
  110. package/dist/frontend/assets/{sl-SI-NN7IZMDC-CXEdg57y.js → sl-SI-NN7IZMDC-BXHXIdFA.js} +1 -1
  111. package/dist/frontend/assets/{stateDiagram-587899a1-BwRwgRo6.js → stateDiagram-587899a1-D3Ahxsgc.js} +1 -1
  112. package/dist/frontend/assets/{stateDiagram-9c5f0230-C8n96rER.js → stateDiagram-9c5f0230-8XbLWvC0.js} +1 -1
  113. package/dist/frontend/assets/{stateDiagram-v2-51a3dcff-B7e5E4J7.js → stateDiagram-v2-51a3dcff-Cqf-MUGD.js} +1 -1
  114. package/dist/frontend/assets/{stateDiagram-v2-d93cdb3a-B18iZQLw.js → stateDiagram-v2-d93cdb3a-DjGrud9D.js} +1 -1
  115. package/dist/frontend/assets/{styles-2ab5d517-CfWubwio.js → styles-2ab5d517-B4qdHOgu.js} +3 -3
  116. package/dist/frontend/assets/{styles-5f03d8d2-C-_n19AB.js → styles-5f03d8d2-DOD2v5V8.js} +3 -3
  117. package/dist/frontend/assets/{styles-6aaf32cf-Dh-DHkVP.js → styles-6aaf32cf-bP3XXcmf.js} +1 -1
  118. package/dist/frontend/assets/{styles-9a916d00-Boy0hUEG.js → styles-9a916d00-QLeuL_Dy.js} +1 -1
  119. package/dist/frontend/assets/{styles-c10674c1-BfxiK3Ns.js → styles-c10674c1-Lpvh1u4f.js} +1 -1
  120. package/dist/frontend/assets/{styles-edf9a4b0-T7mKViRF.js → styles-edf9a4b0-D4W9aPm7.js} +1 -1
  121. package/dist/frontend/assets/subset-shared.chunk.js +9 -9
  122. package/dist/frontend/assets/subset-worker.chunk.js +1 -1
  123. package/dist/frontend/assets/{sv-SE-XGPEYMSR-dVrUAqld.js → sv-SE-XGPEYMSR-YrCQQBuo.js} +1 -1
  124. package/dist/frontend/assets/{svgDrawCommon-08f97a94-fsWk6SQc.js → svgDrawCommon-08f97a94-CwNv3PjS.js} +1 -1
  125. package/dist/frontend/assets/{svgDrawCommon-3ba9043b-BJ8X3qS-.js → svgDrawCommon-3ba9043b-DZbT0Bqu.js} +1 -1
  126. package/dist/frontend/assets/{ta-IN-2NMHFXQM-CtVVZfIT.js → ta-IN-2NMHFXQM-qR82aBSW.js} +1 -1
  127. package/dist/frontend/assets/{th-TH-HPSO5L25-Do90CyDN.js → th-TH-HPSO5L25-C65V7hE5.js} +1 -1
  128. package/dist/frontend/assets/{timeline-definition-7e6b55e7-D_iHA485.js → timeline-definition-7e6b55e7-GkPy3QeG.js} +3 -3
  129. package/dist/frontend/assets/{timeline-definition-85554ec2-nhT8QMsO.js → timeline-definition-85554ec2-CCdkBaC0.js} +3 -3
  130. package/dist/frontend/assets/{tr-TR-DEFEU3FU-MfR-IQke.js → tr-TR-DEFEU3FU-CFzbwAKV.js} +1 -1
  131. package/dist/frontend/assets/{uk-UA-QMV73CPH-CPet8B1O.js → uk-UA-QMV73CPH-ePt5WVz3.js} +1 -1
  132. package/dist/frontend/assets/{union-BgYwhB7T.js → union-CVZobYBh.js} +1 -1
  133. package/dist/frontend/assets/{vi-VN-M7AON7JQ-3HD06T_8.js → vi-VN-M7AON7JQ-CaPKykgJ.js} +1 -1
  134. package/dist/frontend/assets/{xychartDiagram-b6496bcd-D5vSaFT2.js → xychartDiagram-b6496bcd-CV5WRc81.js} +4 -4
  135. package/dist/frontend/assets/{xychartDiagram-e933f94c-DLIHXsUi.js → xychartDiagram-e933f94c-BH-ySdQf.js} +1 -1
  136. package/dist/frontend/assets/{zh-CN-LNUGB5OW-t2RyWPw2.js → zh-CN-LNUGB5OW-CK8AEj8o.js} +1 -1
  137. package/dist/frontend/assets/{zh-HK-E62DVLB3-Bj22hc7e.js → zh-HK-E62DVLB3-CUINiFgH.js} +1 -1
  138. package/dist/frontend/assets/{zh-TW-RAJ6MFWO-CBRhqhU1.js → zh-TW-RAJ6MFWO-BEBY7iJR.js} +1 -1
  139. package/dist/frontend/assets/zipObject-W8SLG3F1.js +1 -0
  140. package/dist/frontend/index.html +167 -235
  141. package/dist/{index.d.ts → mcp.d.ts} +1 -1
  142. package/dist/mcp.d.ts.map +1 -0
  143. package/dist/mcp.js +1183 -0
  144. package/dist/mcp.js.map +1 -0
  145. package/package.json +19 -34
  146. package/skills/excalidraw-skill/SKILL.md +42 -55
  147. package/skills/excalidraw-skill/references/cheatsheet.md +23 -56
  148. package/skills/excalidraw-skill/scripts/clear-canvas.cjs +9 -32
  149. package/skills/excalidraw-skill/scripts/create-element.cjs +18 -61
  150. package/skills/excalidraw-skill/scripts/delete-element.cjs +12 -42
  151. package/skills/excalidraw-skill/scripts/export-elements.cjs +17 -44
  152. package/skills/excalidraw-skill/scripts/gh-helper.cjs +58 -0
  153. package/skills/excalidraw-skill/scripts/healthcheck.cjs +8 -29
  154. package/skills/excalidraw-skill/scripts/import-elements.cjs +20 -70
  155. package/skills/excalidraw-skill/scripts/update-element.cjs +19 -63
  156. package/tsconfig.json +3 -2
  157. package/vite.config.js +3 -10
  158. package/dist/frontend/assets/arc-Cc3ncJ1n.js +0 -1
  159. package/dist/frontend/assets/channel-BQSIb3i3.js +0 -1
  160. package/dist/frontend/assets/classDiagram-v2-d5a6b087-BgB8AG2e.js +0 -2
  161. package/dist/frontend/assets/classDiagram-v2-f2320105-Dtywkpeg.js +0 -2
  162. package/dist/frontend/assets/clone-BoZziNL9.js +0 -1
  163. package/dist/frontend/assets/flowDiagram-v2-96b9c2cf-Z3H2d3Wj.js +0 -1
  164. package/dist/frontend/assets/flowDiagram-v2-ffc7f31a-CIQOMghC.js +0 -1
  165. package/dist/frontend/assets/index-DIz4WrSB.js +0 -349
  166. package/dist/frontend/assets/zipObject-DMfHW2o8.js +0 -1
  167. package/dist/index.d.ts.map +0 -1
  168. package/dist/index.js +0 -2279
  169. package/dist/index.js.map +0 -1
  170. package/dist/remote.d.ts +0 -5
  171. package/dist/remote.d.ts.map +0 -1
  172. package/dist/remote.js +0 -129
  173. package/dist/remote.js.map +0 -1
  174. package/dist/server.d.ts +0 -4
  175. package/dist/server.d.ts.map +0 -1
  176. package/dist/server.js +0 -1208
  177. package/dist/server.js.map +0 -1
  178. package/dist/target.d.ts +0 -38
  179. package/dist/target.d.ts.map +0 -1
  180. package/dist/target.js +0 -225
  181. package/dist/target.js.map +0 -1
  182. package/dist/types.d.ts +0 -228
  183. package/dist/types.d.ts.map +0 -1
  184. package/dist/types.js +0 -45
  185. package/dist/types.js.map +0 -1
  186. package/dist/utils/auth.d.ts +0 -14
  187. package/dist/utils/auth.d.ts.map +0 -1
  188. package/dist/utils/auth.js +0 -67
  189. package/dist/utils/auth.js.map +0 -1
  190. package/dist/utils/github.d.ts +0 -24
  191. package/dist/utils/github.d.ts.map +0 -1
  192. package/dist/utils/github.js +0 -132
  193. package/dist/utils/github.js.map +0 -1
  194. package/dist/utils/logger.d.ts +0 -4
  195. package/dist/utils/logger.d.ts.map +0 -1
  196. package/dist/utils/logger.js +0 -25
  197. package/dist/utils/logger.js.map +0 -1
  198. package/dist/utils/r2store.d.ts +0 -12
  199. package/dist/utils/r2store.d.ts.map +0 -1
  200. package/dist/utils/r2store.js +0 -75
  201. package/dist/utils/r2store.js.map +0 -1
  202. package/frontend/drop.png +0 -0
  203. package/frontend/index.html +0 -339
  204. package/frontend/src/App.tsx +0 -1091
  205. package/frontend/src/main.tsx +0 -15
  206. package/frontend/src/utils/ghSync.ts +0 -234
  207. package/frontend/src/utils/mermaidConverter.ts +0 -43
  208. package/frontend/src/utils/webmcp.ts +0 -265
  209. package/scripts/check-local-bind.mjs +0 -164
  210. package/scripts/postinstall.mjs +0 -23
  211. package/scripts/publish-pages.sh +0 -39
  212. package/scripts/seed-r2.mjs +0 -9
  213. package/src/cli.ts +0 -528
  214. package/src/index.ts +0 -2538
  215. package/src/remote.ts +0 -92
  216. package/src/server.ts +0 -1349
  217. package/src/target.ts +0 -198
  218. package/src/types.ts +0 -336
  219. package/src/utils/auth.ts +0 -61
  220. package/src/utils/github.ts +0 -121
  221. package/src/utils/logger.ts +0 -35
  222. 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 SERVER_ENTRY = path.join(__dirname, 'server.js');
13
- const MCP_ENTRY = path.join(__dirname, 'index.js');
14
- const DEFAULT_START_PORT = 3030;
15
- const PORT_SCAN_SIZE = 200;
16
- function isPortFree(port, host = '127.0.0.1', timeoutMs = 300) {
17
- return new Promise((resolve) => {
18
- let settled = false;
19
- const socket = createConnection({ host, port });
20
- const done = (free) => {
21
- if (settled)
22
- return;
23
- settled = true;
24
- socket.destroy();
25
- resolve(free);
26
- };
27
- socket.setTimeout(timeoutMs);
28
- socket.once('connect', () => done(false)); // something listens → taken
29
- socket.once('timeout', () => done(true));
30
- socket.once('error', () => done(true)); // ECONNREFUSED → free
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 findFreePort(preferred, start = DEFAULT_START_PORT) {
34
- if (preferred && preferred > 0 && preferred < 65536 && (await isPortFree(preferred))) {
35
- return preferred;
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
- for (let port = start; port < start + PORT_SCAN_SIZE; port++) {
38
- if (await isPortFree(port))
39
- return port;
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
- for (let i = 0; i < 50; i++) {
42
- const port = 32000 + Math.floor(Math.random() * 2000);
43
- if (await isPortFree(port))
44
- return port;
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('Could not find a free port for the excalidrop canvas server.');
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 findProjectRoot(cwd) {
49
- let dir = path.resolve(cwd);
50
- const home = process.env.HOME || process.env.USERPROFILE || '/';
51
- while (true) {
52
- if (fs.existsSync(path.join(dir, 'package.json')) || fs.existsSync(path.join(dir, '.git'))) {
53
- return dir;
54
- }
55
- const parent = path.dirname(dir);
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 findDropConfig(cwd) {
62
- let dir = path.resolve(cwd);
63
- const root = path.parse(dir).root;
64
- while (true) {
65
- const file = path.join(dir, CONFIG_NAME);
66
- if (fs.existsSync(file)) {
67
- try {
68
- const raw = JSON.parse(fs.readFileSync(file, 'utf8'));
69
- if (typeof raw.port === 'number') {
70
- return {
71
- dir,
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
- if (dir === root)
83
- return null;
84
- dir = path.dirname(dir);
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 portFromExpressUrl(url) {
88
- if (!url)
89
- return undefined;
166
+ async function isPrivateRepo(repo) {
90
167
  try {
91
- const port = Number(new URL(url).port);
92
- return Number.isFinite(port) && port > 0 ? port : undefined;
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 undefined;
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 agentCommands() {
121
- return [
122
- {
123
- name: 'Claude Code (project scope)',
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 commandExists(cmd) {
167
- const probe = spawnSync(process.platform === 'win32' ? 'where' : 'command', ['-v', cmd], {
168
- stdio: 'ignore',
169
- shell: process.platform === 'win32',
170
- });
171
- return probe.status === 0;
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
- async function cmdInit(args) {
186
- const portFlag = args.find((a) => a.startsWith('--port='));
187
- const preferred = portFlag ? Number(portFlag.split('=')[1]) : undefined;
188
- const noPrompt = args.includes('--no-prompt') || args.includes('-y');
189
- const root = findProjectRoot(process.cwd());
190
- const slug = detectSlug();
191
- const port = await findFreePort(preferred);
192
- const config = { port, canvasUrl: `http://127.0.0.1:${port}`, ...(slug ? { remote: slug } : {}) };
193
- fs.writeFileSync(path.join(root, CONFIG_NAME), JSON.stringify(config, null, 2) + '\n');
194
- console.log(`\nexcalidrop: canvas port ${port} saved to ${path.join(root, CONFIG_NAME)}`);
195
- if (slug)
196
- console.log(`excalidrop: remote canvas preselected: ${slug} (agent uses it automatically)`);
197
- const scenePath = path.join(root, 'canvas.excalidraw');
198
- if (!fs.existsSync(scenePath)) {
199
- fs.writeFileSync(scenePath, JSON.stringify({ type: 'excalidraw', version: 2, source: 'excalidrop', elements: [], appState: { viewBackgroundColor: '#ffffff', gridSize: null }, files: {} }, null, 2) + '\n');
200
- console.log(`excalidrop: empty scene written to ${scenePath}`);
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(`excalidrop: existing ${MCP_JSON} is not valid JSON, leaving it untouched.`);
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: ['excalidrop', 'mcp'] };
215
- if (Object.keys(mcp).length > 0) {
216
- fs.writeFileSync(mcpPath, JSON.stringify(mcp, null, 2) + '\n');
217
- console.log(`excalidrop: MCP server entry written to ${mcpPath}`);
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
- printAgentCommands();
223
- return;
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
- if (slug) {
226
- if (!ghAuthed()) {
227
- console.log('GitHub: not logged in. Run `gh auth login` first (2FA via GitHub), then re-run init.\n');
228
- }
229
- else {
230
- const pub = await prompt(`Publish an empty canvas to ${slug} (creates gh-pages + enables Pages)? [Y/n] `);
231
- if (!pub || /^(y|yes)$/i.test(pub)) {
232
- const r = spawnSync('bash', [path.join(__dirname, '../scripts/publish-pages.sh')], {
233
- stdio: 'inherit', env: { ...process.env, REPO_SLUG: slug },
234
- });
235
- if (r.status === 0) {
236
- const [owner, repo] = slug.split('/');
237
- console.log(`\nCanvas live at https://${owner}.github.io/${repo}/`);
238
- const appSlug = process.env.EXCALIDROP_APP_SLUG || 'excalidrop';
239
- console.log(`Install the app once per repo: https://github.com/apps/${appSlug}/installations/new`);
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
- const answer = await prompt('Install the excalidrop MCP server into your AI agent now? [Y/n] ');
245
- if (answer && !/^(y|yes)$/i.test(answer)) {
246
- printAgentCommands();
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
- let installed = false;
250
- if (commandExists('claude')) {
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 printAgentCommands() {
271
- console.log('Add excalidrop to your AI agent (same pattern as chrome-devtools-mcp):\n');
272
- for (const c of agentCommands()) {
273
- console.log(`## ${c.name}\n${c.command}\n`);
409
+ async function runTui() {
410
+ let clack;
411
+ try {
412
+ clack = await import('@clack/prompts');
274
413
  }
275
- }
276
- async function cmdUp(args) {
277
- const portFlag = args.find((a) => a.startsWith('--port='));
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
- else if (existing && (await isPortFree(existing.config.port))) {
286
- port = existing.config.port;
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
- else {
289
- port = await findFreePort(existing?.config.port ?? DEFAULT_START_PORT);
290
- }
291
- fs.writeFileSync(path.join(existing?.dir ?? root, CONFIG_NAME), JSON.stringify({ port, canvasUrl: `http://127.0.0.1:${port}` }, null, 2) + '\n');
292
- const host = process.env.HOST || '127.0.0.1';
293
- console.log(`excalidrop: starting canvas on http://${host === '0.0.0.0' ? '127.0.0.1' : host}:${port}`);
294
- const child = spawn(process.execPath, [SERVER_ENTRY], {
295
- stdio: 'inherit',
296
- env: { ...process.env, PORT: String(port), HOST: host },
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
- child.on('exit', (code) => process.exit(code ?? 0));
299
- }
300
- function cmdMcp() {
301
- const port = resolveSavedPort(process.cwd());
302
- const canvasUrl = process.env.EXPRESS_SERVER_URL || `http://127.0.0.1:${port}`;
303
- const child = spawn(process.execPath, [MCP_ENTRY], {
304
- stdio: 'inherit',
305
- env: { ...process.env, EXPRESS_SERVER_URL: canvasUrl },
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
- child.on('exit', (code) => process.exit(code ?? 0));
308
- }
309
- async function cmdStatus() {
310
- const found = findDropConfig(process.cwd());
311
- const port = found?.config.port ?? resolveSavedPort(process.cwd());
312
- const url = process.env.EXPRESS_SERVER_URL || `http://127.0.0.1:${port}`;
313
- console.log(`config: ${found ? path.join(found.dir, CONFIG_NAME) : '(none — using defaults)'}`);
314
- console.log(`canvas: ${url}`);
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
- const res = await fetch(`${url}/health`);
317
- console.log(`health: ${res.ok ? 'ok' : `HTTP ${res.status}`} ${res.ok ? '' : await res.text()}`);
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
- console.log('excalidrop setup — works on any repo you own or can access\n');
345
- // 1. gh auth (the only prerequisite; 2FA enforced by GitHub itself)
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 slug = (() => {
352
- try {
353
- const url = spawnSync('git', ['config', '--get', 'remote.origin.url'], { encoding: 'utf8' }).stdout.trim().replace(/\.git$/, '');
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
- catch {
358
- return '';
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 cfgFile = path.join(process.cwd(), CONFIG_NAME);
374
- const cfg = fs.existsSync(cfgFile) ? JSON.parse(fs.readFileSync(cfgFile, 'utf8')) : {};
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
- // Dynamic import to avoid loading MCP deps in CLI path
413
- const mod = await import('./target.js');
414
- console.log('Starting GitHub device login…');
415
- const dev = await mod.deviceStart(clientId);
416
- console.log(`\nOpen ${dev.verification_uri} and enter code: ${dev.user_code}\n`);
417
- const repoId = undefined; // unrestricted; pass --repo owner/repo? (future: parse argv)
418
- const token = await mod.devicePoll(clientId, dev.device_code, dev.interval || 5, 300, repoId);
419
- console.log('Logged in — token stored in ~/.config/excalidrop/gh_token');
420
- void token;
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 — drop an Excalidraw canvas + MCP server into any project
528
+ console.log(`excalidrop — remote GitHub-backed canvas (no local server)
424
529
 
425
530
  Usage:
426
- npx excalidrop init [--port=N] [--no-prompt] pick a free port, write .excalidrop.json + .mcp.json
427
- npx excalidrop up [--port=N] start this project's canvas server
428
- npx excalidrop setup one-command setup on any repo (gh auth + publish + guide)
429
- npx excalidrop login GitHub device-flow login from the terminal
430
- npx excalidrop pages GitHub-truth + gh-pages publish guide
431
- npx excalidrop remote run remote MCP-v2 (GitHub-backed, multi-project)
432
- npx excalidrop publish publish viewer straight to gh-pages (no Actions)
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
- Install:
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 'init':
447
- await cmdInit(rest);
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 'publish': {
471
- const child = spawn('bash', [path.join(__dirname, '../scripts/publish-pages.sh')], { stdio: 'inherit', env: process.env });
472
- child.on('exit', (code) => process.exit(code ?? 0));
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
- printAgentCommands();
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
- if (cmd && cmd !== '--help' && cmd !== '-h')
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