excalidrop 0.1.2 → 0.1.4
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/dist/cli.d.ts +0 -21
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -46
- package/dist/cli.js.map +1 -1
- package/dist/frontend/assets/{advancedFormat-bKUmsig-.js → advancedFormat-tiBAHNGo.js} +1 -1
- package/dist/frontend/assets/{ar-SA-G6X2FPQ2-C7K_UR3P.js → ar-SA-G6X2FPQ2-DVxiWV5Z.js} +1 -1
- package/dist/frontend/assets/{arc-BI_FJWE7.js → arc-Cc3ncJ1n.js} +1 -1
- package/dist/frontend/assets/{az-AZ-76LH7QW2-PkhuRuSr.js → az-AZ-76LH7QW2-Z_2BqDo-.js} +1 -1
- package/dist/frontend/assets/{bg-BG-XCXSNQG7-CgLRSYTl.js → bg-BG-XCXSNQG7-DrxGJZOG.js} +1 -1
- package/dist/frontend/assets/{blockDiagram-38ab4fdb-BFdBRYSn.js → blockDiagram-38ab4fdb-CDnkTFfP.js} +1 -1
- package/dist/frontend/assets/{blockDiagram-68f4deed-Ynbqr7bQ.js → blockDiagram-68f4deed-Bb0W6mxN.js} +1 -1
- package/dist/frontend/assets/{bn-BD-2XOGV67Q-CEvkU0mz.js → bn-BD-2XOGV67Q-BkGJ-w8k.js} +1 -1
- package/dist/frontend/assets/{c4Diagram-15b5d702-Dm7F1Pn_.js → c4Diagram-15b5d702-ByRV0tka.js} +1 -1
- package/dist/frontend/assets/{c4Diagram-3d4e48cf-COkK-e67.js → c4Diagram-3d4e48cf-QSVnzYYK.js} +1 -1
- package/dist/frontend/assets/{ca-ES-6MX7JW3Y-DzP6q9bE.js → ca-ES-6MX7JW3Y-BBJvOPE1.js} +1 -1
- package/dist/frontend/assets/channel-BQSIb3i3.js +1 -0
- package/dist/frontend/assets/{classDiagram-70f12bd4-CRzulwvH.js → classDiagram-70f12bd4-DDFBNGUt.js} +1 -1
- package/dist/frontend/assets/{classDiagram-d40c83e7-DNbA-n-a.js → classDiagram-d40c83e7-cgC3s7Nz.js} +1 -1
- package/dist/frontend/assets/{classDiagram-v2-d5a6b087-l-GglywH.js → classDiagram-v2-d5a6b087-BgB8AG2e.js} +1 -1
- package/dist/frontend/assets/{classDiagram-v2-f2320105-B_TY1zoc.js → classDiagram-v2-f2320105-Dtywkpeg.js} +1 -1
- package/dist/frontend/assets/clone-BoZziNL9.js +1 -0
- package/dist/frontend/assets/{createText-2e5e7dd3-DB_k52Pn.js → createText-2e5e7dd3-CqY00v-4.js} +1 -1
- package/dist/frontend/assets/{createText-d213de94-D-G49giw.js → createText-d213de94-D59sBfqq.js} +1 -1
- package/dist/frontend/assets/{cs-CZ-2BRQDIVT-Bf4TOzpR.js → cs-CZ-2BRQDIVT-CK09MAsg.js} +1 -1
- package/dist/frontend/assets/{cytoscape-cose-bilkent-DQYypj0P.js → cytoscape-cose-bilkent-CKy_tuGZ.js} +1 -1
- package/dist/frontend/assets/{da-DK-5WZEPLOC-jO-5kyXb.js → da-DK-5WZEPLOC-V4hdLKuT.js} +1 -1
- package/dist/frontend/assets/{de-DE-XR44H4JA-BkNXWOVh.js → de-DE-XR44H4JA-Dw8qkP9g.js} +1 -1
- package/dist/frontend/assets/drop-BFhuMYSw.png +0 -0
- package/dist/frontend/assets/{edges-332bd1c7-CVvMtkTa.js → edges-332bd1c7-D-u0KMZB.js} +1 -1
- package/dist/frontend/assets/{edges-e0da2a9e-DQBAvC8-.js → edges-e0da2a9e-B3UFqLqY.js} +1 -1
- package/dist/frontend/assets/{el-GR-BZB4AONW-Dv7Y9hMk.js → el-GR-BZB4AONW-BUdBQbYg.js} +1 -1
- package/dist/frontend/assets/{elk.bundled-YVS3PsMH.js → elk.bundled-BZA3Xcoo.js} +1 -1
- package/dist/frontend/assets/{erDiagram-880f2ed8-BjBeR38f.js → erDiagram-880f2ed8-CNxm7e1x.js} +1 -1
- package/dist/frontend/assets/{erDiagram-9861fffd-B2Bi3R45.js → erDiagram-9861fffd-C2u_ziAM.js} +1 -1
- package/dist/frontend/assets/{es-ES-U4NZUMDT-CWhs0SQY.js → es-ES-U4NZUMDT-DS0FAZqX.js} +1 -1
- package/dist/frontend/assets/{eu-ES-A7QVB2H4-BfkFwc3e.js → eu-ES-A7QVB2H4-D-D5uidc.js} +1 -1
- package/dist/frontend/assets/{fa-IR-HGAKTJCU-BgWzSj46.js → fa-IR-HGAKTJCU-mAW_2-jg.js} +1 -1
- package/dist/frontend/assets/{fi-FI-Z5N7JZ37-CUyvkum-.js → fi-FI-Z5N7JZ37-B9mW6nxS.js} +1 -1
- package/dist/frontend/assets/{flowDb-7c981674-BSZyyVBf.js → flowDb-7c981674-6JB2gd0I.js} +1 -1
- package/dist/frontend/assets/{flowDb-956e92f1-BC3-CLkF.js → flowDb-956e92f1-BbpkWRi7.js} +1 -1
- package/dist/frontend/assets/{flowDiagram-66a62f08-H57DAvmM.js → flowDiagram-66a62f08-DrhrQuXc.js} +1 -1
- package/dist/frontend/assets/{flowDiagram-cbd28bf7-Bw4Z2nBD.js → flowDiagram-cbd28bf7-BmNY7bvJ.js} +1 -1
- package/dist/frontend/assets/flowDiagram-v2-96b9c2cf-Z3H2d3Wj.js +1 -0
- package/dist/frontend/assets/flowDiagram-v2-ffc7f31a-CIQOMghC.js +1 -0
- package/dist/frontend/assets/{flowchart-elk-definition-36e2d292-1kZNdf3X.js → flowchart-elk-definition-36e2d292-4G1YiIlk.js} +1 -1
- package/dist/frontend/assets/{flowchart-elk-definition-4a651766-DDhG2Esi.js → flowchart-elk-definition-4a651766-Cm94SrrQ.js} +1 -1
- package/dist/frontend/assets/{fr-FR-RHASNOE6-7xzoRwbd.js → fr-FR-RHASNOE6-BXnmUxTA.js} +1 -1
- package/dist/frontend/assets/{ganttDiagram-04f9e578-CtUSsXCz.js → ganttDiagram-04f9e578-X0BweAnG.js} +1 -1
- package/dist/frontend/assets/{ganttDiagram-c361ad54-K42AklFY.js → ganttDiagram-c361ad54-D3vxZ5MQ.js} +1 -1
- package/dist/frontend/assets/{gitGraphDiagram-21fc4d3e-uW6XA6Y6.js → gitGraphDiagram-21fc4d3e-wznrpRWn.js} +1 -1
- package/dist/frontend/assets/{gitGraphDiagram-72cf32ee-CCNbhnBg.js → gitGraphDiagram-72cf32ee-B9zCS3Pw.js} +1 -1
- package/dist/frontend/assets/{gl-ES-HMX3MZ6V-CXuDDwNP.js → gl-ES-HMX3MZ6V-CrUx-ab6.js} +1 -1
- package/dist/frontend/assets/{graph-CYOmcSYN.js → graph-BQZ6SIVr.js} +1 -1
- package/dist/frontend/assets/{graph-o42dOfI7.js → graph-Cda8xSTx.js} +1 -1
- package/dist/frontend/assets/{he-IL-6SHJWFNN-O9dzh9EM.js → he-IL-6SHJWFNN-D33eaw6-.js} +1 -1
- package/dist/frontend/assets/{hi-IN-IWLTKZ5I-DlrOXhWs.js → hi-IN-IWLTKZ5I-DEnStF-g.js} +1 -1
- package/dist/frontend/assets/{hu-HU-A5ZG7DT2-C3eNg-uI.js → hu-HU-A5ZG7DT2-BjH2w8Sn.js} +1 -1
- package/dist/frontend/assets/{id-ID-SAP4L64H-6iM4pI5c.js → id-ID-SAP4L64H-Bc6R-JEz.js} +1 -1
- package/dist/frontend/assets/{index-3862675e-DrvLhypC.js → index-3862675e-CpYfS3Db.js} +1 -1
- package/dist/frontend/assets/{index-6079d271-BgVTRqg2.js → index-6079d271-Y2bgyRpa.js} +1 -1
- package/dist/frontend/assets/{index-HkUXslJy.js → index-D7eYnAQp.js} +4 -4
- package/dist/frontend/assets/{index-D-cr7jpJ.js → index-DIz4WrSB.js} +72 -72
- package/dist/frontend/assets/{infoDiagram-4a4f5b27-ZxmAcaW1.js → infoDiagram-4a4f5b27-Bm3Oq-Rq.js} +1 -1
- package/dist/frontend/assets/{infoDiagram-f8f76790-Bn-PhiFG.js → infoDiagram-f8f76790-oNVitBKM.js} +1 -1
- package/dist/frontend/assets/{it-IT-JPQ66NNP-B26NQzGp.js → it-IT-JPQ66NNP-BcxQABF2.js} +1 -1
- package/dist/frontend/assets/{ja-JP-DBVTYXUO-CB0fvnYJ.js → ja-JP-DBVTYXUO-D1zIM-nt.js} +1 -1
- package/dist/frontend/assets/{journeyDiagram-29694f62-ggDKs06f.js → journeyDiagram-29694f62-CamPk_3P.js} +1 -1
- package/dist/frontend/assets/{journeyDiagram-49397b02-pdDS-n-z.js → journeyDiagram-49397b02-D6w3tLtf.js} +1 -1
- package/dist/frontend/assets/{kaa-6HZHGXH3-YNdBdcwJ.js → kaa-6HZHGXH3-CoILysVg.js} +1 -1
- package/dist/frontend/assets/{kab-KAB-ZGHBKWFO-D86pwZO0.js → kab-KAB-ZGHBKWFO-B9ZbybBt.js} +1 -1
- package/dist/frontend/assets/{kk-KZ-P5N5QNE5-CpG5oheM.js → kk-KZ-P5N5QNE5-Dxi4GIQ7.js} +1 -1
- package/dist/frontend/assets/{km-KH-HSX4SM5Z-DYOZ3Azw.js → km-KH-HSX4SM5Z-DaJ7EtO2.js} +1 -1
- package/dist/frontend/assets/{ko-KR-MTYHY66A-BHorm6lC.js → ko-KR-MTYHY66A-DG57Ah12.js} +1 -1
- package/dist/frontend/assets/{ku-TR-6OUDTVRD-CM9RgEHv.js → ku-TR-6OUDTVRD-DnDf9E8a.js} +1 -1
- package/dist/frontend/assets/{layout-DW9Qf-At.js → layout-BsWfhUAF.js} +1 -1
- package/dist/frontend/assets/{layout-CCtkxrAf.js → layout-Drr-66M9.js} +1 -1
- package/dist/frontend/assets/{line-DvZfgg9P.js → line-DwQObOhP.js} +1 -1
- package/dist/frontend/assets/{linear-FwFrGRfa.js → linear-C36MjJps.js} +1 -1
- package/dist/frontend/assets/{lt-LT-XHIRWOB4-DvriKXBj.js → lt-LT-XHIRWOB4-D_zqS0yg.js} +1 -1
- package/dist/frontend/assets/{lv-LV-5QDEKY6T-DzQpwk87.js → lv-LV-5QDEKY6T-owIsIh1b.js} +1 -1
- package/dist/frontend/assets/{mindmap-definition-ac74a2e8-DsRo8f2w.js → mindmap-definition-ac74a2e8-Cf9JbrBJ.js} +1 -1
- package/dist/frontend/assets/{mindmap-definition-fc14e90a-dQ9reluI.js → mindmap-definition-fc14e90a-BpsNCNCp.js} +1 -1
- package/dist/frontend/assets/{mr-IN-CRQNXWMA-C7RzxxP5.js → mr-IN-CRQNXWMA-CS9XUj74.js} +1 -1
- package/dist/frontend/assets/{my-MM-5M5IBNSE-B-RRuy29.js → my-MM-5M5IBNSE-BKB060k9.js} +1 -1
- package/dist/frontend/assets/{nb-NO-T6EIAALU-B47x16Ik.js → nb-NO-T6EIAALU-CkmLUrvm.js} +1 -1
- package/dist/frontend/assets/{nl-NL-IS3SIHDZ-BxAKoQne.js → nl-NL-IS3SIHDZ-DdiLyJoz.js} +1 -1
- package/dist/frontend/assets/{nn-NO-6E72VCQL-dfpbEDSZ.js → nn-NO-6E72VCQL-Yevp6f_Y.js} +1 -1
- package/dist/frontend/assets/{oc-FR-POXYY2M6-gv6geBjV.js → oc-FR-POXYY2M6-D_M6GHup.js} +1 -1
- package/dist/frontend/assets/{pa-IN-N4M65BXN-CY30-qH7.js → pa-IN-N4M65BXN-1vuACN9n.js} +1 -1
- package/dist/frontend/assets/{pica-DWYGHLQV.js → pica-DDDx0iqx.js} +1 -1
- package/dist/frontend/assets/{pie-CDiWh1rj.js → pie-CFIpuQMO.js} +1 -1
- package/dist/frontend/assets/{pieDiagram-421022e6-Bgy-duyf.js → pieDiagram-421022e6-DO7e_Qgw.js} +1 -1
- package/dist/frontend/assets/{pieDiagram-8a3498a8-DH7FsbPK.js → pieDiagram-8a3498a8-BEoqChXo.js} +1 -1
- package/dist/frontend/assets/{pl-PL-T2D74RX3-TrHugsIA.js → pl-PL-T2D74RX3-Cc6Q5nSh.js} +1 -1
- package/dist/frontend/assets/{pt-BR-5N22H2LF-CJxdFp9c.js → pt-BR-5N22H2LF-3eEiAax3.js} +1 -1
- package/dist/frontend/assets/{pt-PT-UZXXM6DQ-CB-1q6YJ.js → pt-PT-UZXXM6DQ-CgnF0Le7.js} +1 -1
- package/dist/frontend/assets/{quadrantDiagram-0957ecba-DcwYI1oi.js → quadrantDiagram-0957ecba-C1Eo-ZUY.js} +1 -1
- package/dist/frontend/assets/{quadrantDiagram-120e2f19-CvHU5a9v.js → quadrantDiagram-120e2f19-Bdfsl_O7.js} +1 -1
- package/dist/frontend/assets/{requirementDiagram-23d650b8-tdagN8HP.js → requirementDiagram-23d650b8-DiOK6QW3.js} +1 -1
- package/dist/frontend/assets/{requirementDiagram-deff3bca-BMGM-fSI.js → requirementDiagram-deff3bca-BDJ-7T4h.js} +1 -1
- package/dist/frontend/assets/{ro-RO-JPDTUUEW-DQq9DBOd.js → ro-RO-JPDTUUEW-CbJ3IB7a.js} +1 -1
- package/dist/frontend/assets/{ru-RU-B4JR7IUQ-CWX32rfh.js → ru-RU-B4JR7IUQ-PQGmeGG4.js} +1 -1
- package/dist/frontend/assets/{sankeyDiagram-04a897e0-3i6zWpY2.js → sankeyDiagram-04a897e0-DV25bgN2.js} +1 -1
- package/dist/frontend/assets/{sankeyDiagram-23345273-ncj1Ol7S.js → sankeyDiagram-23345273-szSb_Sei.js} +1 -1
- package/dist/frontend/assets/{selectAll-DJwesbwT.js → selectAll-DsrPvhqV.js} +1 -1
- package/dist/frontend/assets/{sequenceDiagram-17ac3bff-ChyKKRZA.js → sequenceDiagram-17ac3bff-Dg88bSlc.js} +1 -1
- package/dist/frontend/assets/{sequenceDiagram-704730f1-D57_wP9k.js → sequenceDiagram-704730f1-DRIjLCxx.js} +1 -1
- package/dist/frontend/assets/{si-LK-N5RQ5JYF-Lkc5ZxyG.js → si-LK-N5RQ5JYF-BtIIudm3.js} +1 -1
- package/dist/frontend/assets/{sk-SK-C5VTKIMK-DyvYXjqZ.js → sk-SK-C5VTKIMK-CIwXticU.js} +1 -1
- package/dist/frontend/assets/{sl-SI-NN7IZMDC-D_MvP9Rh.js → sl-SI-NN7IZMDC-CXEdg57y.js} +1 -1
- package/dist/frontend/assets/{stateDiagram-587899a1-OjFTnFLS.js → stateDiagram-587899a1-BwRwgRo6.js} +1 -1
- package/dist/frontend/assets/{stateDiagram-9c5f0230-BzU36S5C.js → stateDiagram-9c5f0230-C8n96rER.js} +1 -1
- package/dist/frontend/assets/{stateDiagram-v2-51a3dcff-CfX1qo7U.js → stateDiagram-v2-51a3dcff-B7e5E4J7.js} +1 -1
- package/dist/frontend/assets/{stateDiagram-v2-d93cdb3a-BH7Ud4sU.js → stateDiagram-v2-d93cdb3a-B18iZQLw.js} +1 -1
- package/dist/frontend/assets/{styles-2ab5d517-CA8x9Nh5.js → styles-2ab5d517-CfWubwio.js} +1 -1
- package/dist/frontend/assets/{styles-5f03d8d2-BnO2pLhV.js → styles-5f03d8d2-C-_n19AB.js} +1 -1
- package/dist/frontend/assets/{styles-6aaf32cf-COnot1ea.js → styles-6aaf32cf-Dh-DHkVP.js} +1 -1
- package/dist/frontend/assets/{styles-9a916d00-C3QkBgTF.js → styles-9a916d00-Boy0hUEG.js} +1 -1
- package/dist/frontend/assets/{styles-c10674c1-CPAG2_rr.js → styles-c10674c1-BfxiK3Ns.js} +1 -1
- package/dist/frontend/assets/{styles-edf9a4b0-B8Q9fyu0.js → styles-edf9a4b0-T7mKViRF.js} +1 -1
- package/dist/frontend/assets/subset-shared.chunk.js +1 -1
- package/dist/frontend/assets/subset-worker.chunk.js +1 -1
- package/dist/frontend/assets/{sv-SE-XGPEYMSR-t6voGQSI.js → sv-SE-XGPEYMSR-dVrUAqld.js} +1 -1
- package/dist/frontend/assets/{svgDrawCommon-08f97a94-CdT21LWf.js → svgDrawCommon-08f97a94-fsWk6SQc.js} +1 -1
- package/dist/frontend/assets/{svgDrawCommon-3ba9043b-D1MU7jAE.js → svgDrawCommon-3ba9043b-BJ8X3qS-.js} +1 -1
- package/dist/frontend/assets/{ta-IN-2NMHFXQM-7VmfDDyV.js → ta-IN-2NMHFXQM-CtVVZfIT.js} +1 -1
- package/dist/frontend/assets/{th-TH-HPSO5L25-B5rLEY9P.js → th-TH-HPSO5L25-Do90CyDN.js} +1 -1
- package/dist/frontend/assets/{timeline-definition-7e6b55e7-BIcL44O_.js → timeline-definition-7e6b55e7-D_iHA485.js} +1 -1
- package/dist/frontend/assets/{timeline-definition-85554ec2-TgrYYwpU.js → timeline-definition-85554ec2-nhT8QMsO.js} +1 -1
- package/dist/frontend/assets/{tr-TR-DEFEU3FU-0CgOHXTn.js → tr-TR-DEFEU3FU-MfR-IQke.js} +1 -1
- package/dist/frontend/assets/{uk-UA-QMV73CPH-CzSFsIsx.js → uk-UA-QMV73CPH-CPet8B1O.js} +1 -1
- package/dist/frontend/assets/{union-CnQAJu0l.js → union-BgYwhB7T.js} +1 -1
- package/dist/frontend/assets/{vi-VN-M7AON7JQ-Bhx3rYU9.js → vi-VN-M7AON7JQ-3HD06T_8.js} +1 -1
- package/dist/frontend/assets/{xychartDiagram-b6496bcd-BMKDop6v.js → xychartDiagram-b6496bcd-D5vSaFT2.js} +1 -1
- package/dist/frontend/assets/{xychartDiagram-e933f94c-DAWithrN.js → xychartDiagram-e933f94c-DLIHXsUi.js} +1 -1
- package/dist/frontend/assets/{zh-CN-LNUGB5OW-Br52_wH4.js → zh-CN-LNUGB5OW-t2RyWPw2.js} +1 -1
- package/dist/frontend/assets/{zh-HK-E62DVLB3-Cj6mnIQ9.js → zh-HK-E62DVLB3-Bj22hc7e.js} +1 -1
- package/dist/frontend/assets/{zh-TW-RAJ6MFWO-yLEsVBbC.js → zh-TW-RAJ6MFWO-CBRhqhU1.js} +1 -1
- package/dist/frontend/assets/{zipObject-BeXj5PVd.js → zipObject-DMfHW2o8.js} +1 -1
- package/dist/frontend/index.html +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -28
- package/dist/index.js.map +1 -1
- package/dist/server.js +0 -9
- package/dist/server.js.map +1 -1
- package/dist/target.d.ts +0 -3
- package/dist/target.d.ts.map +1 -1
- package/dist/target.js +1 -20
- package/dist/target.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -8
- package/dist/types.js.map +1 -1
- package/dist/utils/auth.d.ts +0 -1
- package/dist/utils/auth.d.ts.map +1 -1
- package/dist/utils/auth.js +0 -1
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/github.d.ts +7 -9
- package/dist/utils/github.d.ts.map +1 -1
- package/dist/utils/github.js +57 -36
- package/dist/utils/github.js.map +1 -1
- package/dist/utils/logger.js +2 -2
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/r2store.d.ts +0 -7
- package/dist/utils/r2store.d.ts.map +1 -1
- package/dist/utils/r2store.js.map +1 -1
- package/frontend/drop.png +0 -0
- package/frontend/index.html +1 -0
- package/frontend/src/utils/ghSync.ts +99 -36
- package/frontend/src/utils/mermaidConverter.ts +2 -8
- package/frontend/src/utils/webmcp.ts +7 -27
- package/package.json +2 -1
- package/src/cli.ts +16 -47
- package/src/index.ts +11 -30
- package/src/server.ts +9 -9
- package/src/target.ts +10 -20
- package/src/types.ts +16 -18
- package/src/utils/auth.ts +1 -1
- package/src/utils/github.ts +61 -33
- package/src/utils/logger.ts +2 -2
- package/src/utils/r2store.ts +1 -7
- package/vite.config.js +0 -2
- package/dist/frontend/assets/channel-BqCiG5n3.js +0 -1
- package/dist/frontend/assets/clone-B6Jqcrku.js +0 -1
- package/dist/frontend/assets/flowDiagram-v2-96b9c2cf-CBjbm_oB.js +0 -1
- package/dist/frontend/assets/flowDiagram-v2-ffc7f31a-CoQufPp2.js +0 -1
package/src/cli.ts
CHANGED
|
@@ -1,25 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* excalidrop — per-project Excalidraw canvas + MCP server setup.
|
|
4
|
-
*
|
|
5
|
-
* Modelled on the `chrome-devtools-mcp` install pattern (`npx -y <pkg>` +
|
|
6
|
-
* `mcpServers` JSON), with one addition: per-project dynamic port selection
|
|
7
|
-
* so multiple checkouts can run their own canvas concurrently.
|
|
8
|
-
*
|
|
9
|
-
* How the dynamic port works:
|
|
10
|
-
* - `excalidrop init` picks a free port (default scan from 3030) and writes
|
|
11
|
-
* it to `.excalidrop.json` in the project root.
|
|
12
|
-
* - `excalidrop mcp` (what the AI agent runs via stdio) resolves that same
|
|
13
|
-
* file by walking up from cwd, so each project transparently gets its own
|
|
14
|
-
* canvas URL with nothing hardcoded in the agent config.
|
|
15
|
-
*
|
|
16
|
-
* Commands:
|
|
17
|
-
* init pick a port, write .excalidrop.json + .mcp.json, offer agent install
|
|
18
|
-
* up start the canvas server on this project's port
|
|
19
|
-
* mcp run the MCP stdio server pointed at this project's canvas (for agents)
|
|
20
|
-
* status show resolved port + canvas health
|
|
21
|
-
* add print / run AI-agent install commands
|
|
22
|
-
*/
|
|
23
2
|
|
|
24
3
|
import { spawn, spawnSync } from 'node:child_process';
|
|
25
4
|
import { createConnection } from 'node:net';
|
|
@@ -41,11 +20,11 @@ const PORT_SCAN_SIZE = 200;
|
|
|
41
20
|
interface DropConfig {
|
|
42
21
|
port: number;
|
|
43
22
|
canvasUrl: string;
|
|
44
|
-
|
|
23
|
+
|
|
45
24
|
remote?: string;
|
|
46
25
|
}
|
|
47
26
|
|
|
48
|
-
|
|
27
|
+
|
|
49
28
|
|
|
50
29
|
function isPortFree(port: number, host = '127.0.0.1', timeoutMs = 300): Promise<boolean> {
|
|
51
30
|
return new Promise((resolve) => {
|
|
@@ -71,7 +50,6 @@ async function findFreePort(preferred?: number, start = DEFAULT_START_PORT): Pro
|
|
|
71
50
|
for (let port = start; port < start + PORT_SCAN_SIZE; port++) {
|
|
72
51
|
if (await isPortFree(port)) return port;
|
|
73
52
|
}
|
|
74
|
-
// Fallback: random high port
|
|
75
53
|
for (let i = 0; i < 50; i++) {
|
|
76
54
|
const port = 32000 + Math.floor(Math.random() * 2000);
|
|
77
55
|
if (await isPortFree(port)) return port;
|
|
@@ -79,7 +57,7 @@ async function findFreePort(preferred?: number, start = DEFAULT_START_PORT): Pro
|
|
|
79
57
|
throw new Error('Could not find a free port for the excalidrop canvas server.');
|
|
80
58
|
}
|
|
81
59
|
|
|
82
|
-
|
|
60
|
+
|
|
83
61
|
|
|
84
62
|
function findProjectRoot(cwd: string): string {
|
|
85
63
|
let dir = path.resolve(cwd);
|
|
@@ -112,7 +90,7 @@ function findDropConfig(cwd: string): { dir: string; config: DropConfig } | null
|
|
|
112
90
|
};
|
|
113
91
|
}
|
|
114
92
|
} catch {
|
|
115
|
-
|
|
93
|
+
|
|
116
94
|
}
|
|
117
95
|
}
|
|
118
96
|
if (dir === root) return null;
|
|
@@ -130,7 +108,7 @@ function portFromExpressUrl(url: string | undefined): number | undefined {
|
|
|
130
108
|
}
|
|
131
109
|
}
|
|
132
110
|
|
|
133
|
-
|
|
111
|
+
|
|
134
112
|
function resolveSavedPort(cwd: string): number {
|
|
135
113
|
const envPort = Number(process.env.EXCALIDROP_PORT);
|
|
136
114
|
if (Number.isFinite(envPort) && envPort > 0) return envPort;
|
|
@@ -141,11 +119,10 @@ function resolveSavedPort(cwd: string): number {
|
|
|
141
119
|
return DEFAULT_START_PORT;
|
|
142
120
|
}
|
|
143
121
|
|
|
144
|
-
|
|
122
|
+
|
|
145
123
|
|
|
146
124
|
function mcpServerJson(): Record<string, unknown> {
|
|
147
|
-
|
|
148
|
-
// so the same snippet works for every project, concurrently.
|
|
125
|
+
|
|
149
126
|
return {
|
|
150
127
|
mcpServers: {
|
|
151
128
|
excalidrop: {
|
|
@@ -216,7 +193,7 @@ function commandExists(cmd: string): boolean {
|
|
|
216
193
|
return probe.status === 0;
|
|
217
194
|
}
|
|
218
195
|
|
|
219
|
-
|
|
196
|
+
|
|
220
197
|
|
|
221
198
|
function detectSlug(): string {
|
|
222
199
|
try {
|
|
@@ -237,17 +214,12 @@ async function cmdInit(args: string[]): Promise<void> {
|
|
|
237
214
|
const slug = detectSlug();
|
|
238
215
|
|
|
239
216
|
const port = await findFreePort(preferred);
|
|
240
|
-
// Preselect this repo as the remote canvas: the MCP auto-loads it, so the
|
|
241
|
-
// agent never needs switch_remote for its home project (chrome-devtools-mcp
|
|
242
|
-
// wrapper pattern: in-memory default + persisted startup target — ours
|
|
243
|
-
// persists in .excalidrop.json so it survives restarts too).
|
|
244
217
|
const config: DropConfig = { port, canvasUrl: `http://127.0.0.1:${port}`, ...(slug ? { remote: slug } : {}) };
|
|
245
218
|
fs.writeFileSync(path.join(root, CONFIG_NAME), JSON.stringify(config, null, 2) + '\n');
|
|
246
219
|
console.log(`\nexcalidrop: canvas port ${port} saved to ${path.join(root, CONFIG_NAME)}`);
|
|
247
220
|
if (slug) console.log(`excalidrop: remote canvas preselected: ${slug} (agent uses it automatically)`);
|
|
248
221
|
|
|
249
|
-
|
|
250
|
-
// that export_scene/import_scene round-trip through.
|
|
222
|
+
|
|
251
223
|
const scenePath = path.join(root, 'canvas.excalidraw');
|
|
252
224
|
if (!fs.existsSync(scenePath)) {
|
|
253
225
|
fs.writeFileSync(
|
|
@@ -257,7 +229,7 @@ async function cmdInit(args: string[]): Promise<void> {
|
|
|
257
229
|
console.log(`excalidrop: empty scene written to ${scenePath}`);
|
|
258
230
|
}
|
|
259
231
|
|
|
260
|
-
|
|
232
|
+
|
|
261
233
|
const mcpPath = path.join(root, MCP_JSON);
|
|
262
234
|
let mcp: Record<string, any> = {};
|
|
263
235
|
if (fs.existsSync(mcpPath)) {
|
|
@@ -282,7 +254,7 @@ async function cmdInit(args: string[]): Promise<void> {
|
|
|
282
254
|
return;
|
|
283
255
|
}
|
|
284
256
|
|
|
285
|
-
|
|
257
|
+
|
|
286
258
|
if (slug) {
|
|
287
259
|
if (!ghAuthed()) {
|
|
288
260
|
console.log('GitHub: not logged in. Run `gh auth login` first (2FA via GitHub), then re-run init.\n');
|
|
@@ -308,7 +280,7 @@ async function cmdInit(args: string[]): Promise<void> {
|
|
|
308
280
|
return;
|
|
309
281
|
}
|
|
310
282
|
|
|
311
|
-
|
|
283
|
+
|
|
312
284
|
let installed = false;
|
|
313
285
|
if (commandExists('claude')) {
|
|
314
286
|
const scopeAnswer = await prompt('Claude Code scope? [project/user] (default: project) ');
|
|
@@ -343,8 +315,7 @@ async function cmdUp(args: string[]): Promise<void> {
|
|
|
343
315
|
const root = findProjectRoot(process.cwd());
|
|
344
316
|
const existing = findDropConfig(process.cwd());
|
|
345
317
|
|
|
346
|
-
|
|
347
|
-
// otherwise claim a new one so concurrent projects never collide.
|
|
318
|
+
|
|
348
319
|
let port: number;
|
|
349
320
|
if (preferred) {
|
|
350
321
|
port = await findFreePort(preferred);
|
|
@@ -368,7 +339,7 @@ async function cmdUp(args: string[]): Promise<void> {
|
|
|
368
339
|
}
|
|
369
340
|
|
|
370
341
|
function cmdMcp(): void {
|
|
371
|
-
|
|
342
|
+
|
|
372
343
|
const port = resolveSavedPort(process.cwd());
|
|
373
344
|
const canvasUrl = process.env.EXPRESS_SERVER_URL || `http://127.0.0.1:${port}`;
|
|
374
345
|
const child = spawn(process.execPath, [MCP_ENTRY], {
|
|
@@ -409,8 +380,7 @@ function cmdRemote(): void {
|
|
|
409
380
|
child.on('exit', (code) => process.exit(code ?? 0));
|
|
410
381
|
}
|
|
411
382
|
|
|
412
|
-
|
|
413
|
-
async function cmdSetup(args: string[] = []): Promise<void> {
|
|
383
|
+
async function cmdSetup(args: string[]): Promise<void> {
|
|
414
384
|
if (args.includes('--help') || args.includes('-h')) {
|
|
415
385
|
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.');
|
|
416
386
|
return;
|
|
@@ -467,7 +437,6 @@ async function cmdSetup(args: string[] = []): Promise<void> {
|
|
|
467
437
|
console.log('\nLogin on the page: pill → device code → github.com/login/device (2FA via GitHub).');
|
|
468
438
|
}
|
|
469
439
|
|
|
470
|
-
/** Terminal GitHub login via device flow (same flow the agent's github_login uses). */
|
|
471
440
|
async function cmdLogin(): Promise<void> {
|
|
472
441
|
const { APP_CLIENT_ID } = await import('./target.js');
|
|
473
442
|
const clientId = process.env.GITHUB_OAUTH_CLIENT_ID || APP_CLIENT_ID;
|
|
@@ -510,7 +479,7 @@ Each project gets its own port (scanned free from ${DEFAULT_START_PORT}), so sev
|
|
|
510
479
|
checkouts can run concurrently. Agent config needs no hardcoded port.`);
|
|
511
480
|
}
|
|
512
481
|
|
|
513
|
-
|
|
482
|
+
|
|
514
483
|
|
|
515
484
|
async function main(): Promise<void> {
|
|
516
485
|
const [cmd, ...rest] = process.argv.slice(2);
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
process.env.NODE_DISABLE_COLORS = '1';
|
|
5
5
|
process.env.NO_COLOR = '1';
|
|
6
6
|
|
|
@@ -30,10 +30,10 @@ import {
|
|
|
30
30
|
} from './types.js';
|
|
31
31
|
import fetch from 'node-fetch';
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
dotenv.config();
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
const ALLOWED_EXPORT_DIR = process.env.EXCALIDRAW_EXPORT_DIR || process.cwd();
|
|
38
38
|
|
|
39
39
|
function sanitizeFilePath(filePath: string): string {
|
|
@@ -48,14 +48,14 @@ function sanitizeFilePath(filePath: string): string {
|
|
|
48
48
|
return resolved;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
let ACTIVE_CANVAS_URL = process.env.EXPRESS_SERVER_URL || 'http://127.0.0.1:3000';
|
|
53
53
|
function activeCanvasUrl(): string {
|
|
54
54
|
return process.env.EXPRESS_SERVER_URL || ACTIVE_CANVAS_URL;
|
|
55
55
|
}
|
|
56
|
-
const ENABLE_CANVAS_SYNC = process.env.ENABLE_CANVAS_SYNC !== 'false';
|
|
56
|
+
const ENABLE_CANVAS_SYNC = process.env.ENABLE_CANVAS_SYNC !== 'false';
|
|
57
|
+
|
|
57
58
|
|
|
58
|
-
// API Response types
|
|
59
59
|
interface ApiResponse {
|
|
60
60
|
success: boolean;
|
|
61
61
|
element?: ServerElement;
|
|
@@ -70,7 +70,7 @@ interface SyncResponse {
|
|
|
70
70
|
elements?: ServerElement[];
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
async function syncToCanvas(operation: string, data: any): Promise<SyncResponse | null> {
|
|
75
75
|
const tgt = await import('./target.js');
|
|
76
76
|
if (tgt.isRemote()) {
|
|
@@ -150,31 +150,26 @@ async function syncToCanvas(operation: string, data: any): Promise<SyncResponse
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
// Helper to sync element creation to canvas
|
|
154
153
|
async function createElementOnCanvas(elementData: ServerElement): Promise<ServerElement | null> {
|
|
155
154
|
const result = await syncToCanvas('create', elementData);
|
|
156
155
|
return result?.element || elementData;
|
|
157
156
|
}
|
|
158
157
|
|
|
159
|
-
// Helper to sync element update to canvas
|
|
160
158
|
async function updateElementOnCanvas(elementData: Partial<ServerElement> & { id: string }): Promise<ServerElement | null> {
|
|
161
159
|
const result = await syncToCanvas('update', elementData);
|
|
162
160
|
return result?.element || null;
|
|
163
161
|
}
|
|
164
162
|
|
|
165
|
-
// Helper to sync element deletion to canvas
|
|
166
163
|
async function deleteElementOnCanvas(elementId: string): Promise<any> {
|
|
167
164
|
const result = await syncToCanvas('delete', { id: elementId });
|
|
168
165
|
return result;
|
|
169
166
|
}
|
|
170
167
|
|
|
171
|
-
// Helper to sync batch creation to canvas
|
|
172
168
|
async function batchCreateElementsOnCanvas(elementsData: ServerElement[]): Promise<ServerElement[] | null> {
|
|
173
169
|
const result = await syncToCanvas('batch_create', elementsData);
|
|
174
170
|
return result?.elements || elementsData;
|
|
175
171
|
}
|
|
176
172
|
|
|
177
|
-
// Helper to fetch element from canvas
|
|
178
173
|
async function getElementFromCanvas(elementId: string): Promise<ServerElement | null> {
|
|
179
174
|
const tgt = await import('./target.js');
|
|
180
175
|
if (tgt.isRemote()) {
|
|
@@ -199,7 +194,6 @@ async function getElementFromCanvas(elementId: string): Promise<ServerElement |
|
|
|
199
194
|
}
|
|
200
195
|
}
|
|
201
196
|
|
|
202
|
-
// In-memory storage for scene state
|
|
203
197
|
interface SceneState {
|
|
204
198
|
theme: string;
|
|
205
199
|
viewport: { x: number; y: number; zoom: number };
|
|
@@ -214,12 +208,10 @@ const sceneState: SceneState = {
|
|
|
214
208
|
groups: new Map()
|
|
215
209
|
};
|
|
216
210
|
|
|
217
|
-
// Points schema: accept both {x, y} objects and [x, y] tuples
|
|
218
211
|
const PointObjectSchema = z.object({ x: z.number(), y: z.number() });
|
|
219
212
|
const PointTupleSchema = z.tuple([z.number(), z.number()]);
|
|
220
213
|
const PointSchema = z.union([PointObjectSchema, PointTupleSchema]);
|
|
221
214
|
|
|
222
|
-
// Normalize points to [x, y] tuple format that Excalidraw expects
|
|
223
215
|
function normalizePoints(points: Array<{ x: number; y: number } | [number, number]>): [number, number][] {
|
|
224
216
|
return points.map(p => {
|
|
225
217
|
if (Array.isArray(p)) return p as [number, number];
|
|
@@ -227,7 +219,6 @@ function normalizePoints(points: Array<{ x: number; y: number } | [number, numbe
|
|
|
227
219
|
});
|
|
228
220
|
}
|
|
229
221
|
|
|
230
|
-
// Schema definitions using zod
|
|
231
222
|
const ElementSchema = z.object({
|
|
232
223
|
id: z.string().optional(),
|
|
233
224
|
type: z.enum(Object.values(EXCALIDRAW_ELEMENT_TYPES) as [ExcalidrawElementType, ...ExcalidrawElementType[]]),
|
|
@@ -293,7 +284,6 @@ const ResourceSchema = z.object({
|
|
|
293
284
|
resource: z.enum(['scene', 'library', 'theme', 'elements'])
|
|
294
285
|
});
|
|
295
286
|
|
|
296
|
-
// Diagram design guide — injected into LLM context via read_diagram_guide tool
|
|
297
287
|
const DIAGRAM_DESIGN_GUIDE = `# Excalidraw Diagram Design Guide
|
|
298
288
|
|
|
299
289
|
## Color Palette
|
|
@@ -386,7 +376,7 @@ const DIAGRAM_DESIGN_GUIDE = `# Excalidraw Diagram Design Guide
|
|
|
386
376
|
5. **Refinement** — align, distribute, adjust spacing, screenshot to verify
|
|
387
377
|
`;
|
|
388
378
|
|
|
389
|
-
|
|
379
|
+
|
|
390
380
|
const tools: Tool[] = [
|
|
391
381
|
{
|
|
392
382
|
name: 'create_element',
|
|
@@ -910,7 +900,7 @@ const tools: Tool[] = [
|
|
|
910
900
|
}
|
|
911
901
|
];
|
|
912
902
|
|
|
913
|
-
|
|
903
|
+
|
|
914
904
|
const server = new Server(
|
|
915
905
|
{
|
|
916
906
|
name: "mcp-excalidraw-server",
|
|
@@ -927,15 +917,13 @@ const server = new Server(
|
|
|
927
917
|
}
|
|
928
918
|
);
|
|
929
919
|
|
|
930
|
-
|
|
920
|
+
|
|
931
921
|
function convertTextToLabel(element: ServerElement): ServerElement {
|
|
932
922
|
const { text, ...rest } = element;
|
|
933
923
|
if (text) {
|
|
934
|
-
// For standalone text elements, keep text as direct property
|
|
935
924
|
if (element.type === 'text') {
|
|
936
|
-
return element;
|
|
925
|
+
return element;
|
|
937
926
|
}
|
|
938
|
-
// For other elements (rectangle, ellipse, diamond), convert to label format
|
|
939
927
|
return {
|
|
940
928
|
...rest,
|
|
941
929
|
label: { text }
|
|
@@ -944,11 +932,8 @@ function convertTextToLabel(element: ServerElement): ServerElement {
|
|
|
944
932
|
return element;
|
|
945
933
|
}
|
|
946
934
|
|
|
947
|
-
// Set up request handler for tool calls
|
|
948
935
|
let toolQueue: Promise<void> = Promise.resolve();
|
|
949
936
|
|
|
950
|
-
// Serialized: concurrent tool calls (e.g. switch+create+commit in one block)
|
|
951
|
-
// must not interleave against the remote scene store.
|
|
952
937
|
async function handleToolCall(request: CallToolRequest) {
|
|
953
938
|
try {
|
|
954
939
|
const { name, arguments: args } = request.params;
|
|
@@ -965,7 +950,6 @@ async function handleToolCall(request: CallToolRequest) {
|
|
|
965
950
|
id,
|
|
966
951
|
...elementProps,
|
|
967
952
|
points: elementProps.points ? normalizePoints(elementProps.points) : undefined,
|
|
968
|
-
// Convert binding IDs to Excalidraw's start/end format
|
|
969
953
|
...(startElementId ? { start: { id: startElementId } } : {}),
|
|
970
954
|
...(endElementId ? { end: { id: endElementId } } : {}),
|
|
971
955
|
createdAt: new Date().toISOString(),
|
|
@@ -973,17 +957,14 @@ async function handleToolCall(request: CallToolRequest) {
|
|
|
973
957
|
version: 1
|
|
974
958
|
};
|
|
975
959
|
|
|
976
|
-
// Normalize fontFamily from string names to numeric values
|
|
977
960
|
if (element.fontFamily !== undefined) {
|
|
978
961
|
element.fontFamily = normalizeFontFamily(element.fontFamily);
|
|
979
962
|
}
|
|
980
963
|
|
|
981
|
-
// For bound arrows without explicit points, set a default
|
|
982
964
|
if ((startElementId || endElementId) && !elementProps.points) {
|
|
983
965
|
(element as any).points = [[0, 0], [100, 0]];
|
|
984
966
|
}
|
|
985
967
|
|
|
986
|
-
// Convert text to label format for Excalidraw
|
|
987
968
|
const excalidrawElement = convertTextToLabel(element);
|
|
988
969
|
|
|
989
970
|
// Create element directly on HTTP server (no local storage)
|
package/src/server.ts
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
import { z } from 'zod';
|
|
32
32
|
import WebSocket from 'ws';
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
dotenv.config();
|
|
36
36
|
|
|
37
37
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -41,12 +41,12 @@ const app: Express = express();
|
|
|
41
41
|
const server = createServer(app);
|
|
42
42
|
const wss = new WebSocketServer({ server });
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
app.use(cors());
|
|
46
46
|
app.use(express.json({ limit: '10mb' }));
|
|
47
47
|
app.use(authOptional);
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
const SCENE_KEY = `${process.env.GITHUB_REPOSITORY?.replace('/', '__') || process.env.SCENE_ID || 'default'}.json`;
|
|
51
51
|
let r2Cfg = getR2Config();
|
|
52
52
|
let persistTimer: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -69,7 +69,7 @@ function schedulePersist(): void {
|
|
|
69
69
|
}
|
|
70
70
|
void loadFromR2();
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
app.get('/api/auth/login', (req, res) => {
|
|
74
74
|
const cid = process.env.GITHUB_CLIENT_ID;
|
|
75
75
|
if (!cid) return res.status(500).json({ success: false, error: 'GITHUB_CLIENT_ID not configured' });
|
|
@@ -93,7 +93,7 @@ app.get('/api/auth/callback', async (req, res) => {
|
|
|
93
93
|
});
|
|
94
94
|
app.get('/api/auth/me', (req, res) => res.json({ user: (req as any).user || null, publicRead: String(process.env.PUBLIC_READ || 'false') }));
|
|
95
95
|
app.post('/api/auth/logout', (_req, res) => { res.setHeader('Set-Cookie', 'excalidrop_token=; Path=/; Max-Age=0'); res.json({ success: true }); });
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
const OPEN_WRITE_PATHS = new Set(['/api/export/image/result', '/api/viewport/result']);
|
|
98
98
|
app.use('/api/', (req, res, next) => {
|
|
99
99
|
if (req.path.startsWith('/auth')) return next();
|
|
@@ -106,17 +106,17 @@ app.use('/api/', (req, res, next) => {
|
|
|
106
106
|
});
|
|
107
107
|
const persist = () => schedulePersist();
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
|
|
110
110
|
const staticDir = path.join(__dirname, '../dist');
|
|
111
111
|
app.use(express.static(staticDir));
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
app.use(express.static(path.join(__dirname, '../dist/frontend')));
|
|
114
|
-
|
|
114
|
+
|
|
115
115
|
app.use('/assets/fonts', express.static(
|
|
116
116
|
path.join(__dirname, '../node_modules/@excalidraw/excalidraw/dist/prod/fonts')
|
|
117
117
|
));
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
|
|
120
120
|
const clients = new Set<WebSocket>();
|
|
121
121
|
|
|
122
122
|
// Broadcast to all connected clients
|
package/src/target.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Agents switch with the switch_remote tool; writes commit straight to GitHub.
|
|
3
|
-
* Token resolution (no user setup): GITHUB_TOKEN env → `gh auth token` →
|
|
4
|
-
* ~/.config/excalidrop/gh_token (written by github_login device flow).
|
|
5
|
-
*/
|
|
1
|
+
|
|
6
2
|
import { execSync } from 'node:child_process';
|
|
7
3
|
import fs from 'node:fs';
|
|
8
4
|
import os from 'node:os';
|
|
@@ -14,11 +10,11 @@ export type Target = { kind: 'local' } | { kind: 'remote'; repo: string };
|
|
|
14
10
|
let active: Target = { kind: 'local' };
|
|
15
11
|
export const getTarget = (): Target => active;
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
|
|
18
14
|
export const APP_CLIENT_ID = process.env.GITHUB_OAUTH_CLIENT_ID || 'Iv23liuS2fx3QOEIoDmx';
|
|
19
15
|
|
|
20
16
|
const CONFIG_NAME = '.excalidrop.json';
|
|
21
|
-
|
|
17
|
+
|
|
22
18
|
function findConfigDir(): string | null {
|
|
23
19
|
let dir = process.cwd();
|
|
24
20
|
const root = path.parse(dir).root;
|
|
@@ -31,7 +27,7 @@ function findConfigDir(): string | null {
|
|
|
31
27
|
dir = path.dirname(dir);
|
|
32
28
|
}
|
|
33
29
|
}
|
|
34
|
-
|
|
30
|
+
|
|
35
31
|
function persistTarget(repo: string | null): void {
|
|
36
32
|
const dir = findConfigDir();
|
|
37
33
|
if (!dir) return;
|
|
@@ -42,7 +38,7 @@ function persistTarget(repo: string | null): void {
|
|
|
42
38
|
fs.writeFileSync(file, JSON.stringify(raw, null, 2) + '\n');
|
|
43
39
|
} catch (e) { logger.warn('could not persist remote target: ' + (e as Error).message); }
|
|
44
40
|
}
|
|
45
|
-
|
|
41
|
+
|
|
46
42
|
export function loadSavedTarget(): string | null {
|
|
47
43
|
const dir = findConfigDir();
|
|
48
44
|
if (!dir) return null;
|
|
@@ -70,7 +66,7 @@ export function storeToken(t: string): void {
|
|
|
70
66
|
fs.writeFileSync(TOKEN_FILE, t + '\n', { mode: 0o600 });
|
|
71
67
|
}
|
|
72
68
|
|
|
73
|
-
|
|
69
|
+
|
|
74
70
|
export function parseRepo(input: string): string {
|
|
75
71
|
const s = input.trim().replace(/\/$/, '');
|
|
76
72
|
const m = s.match(/^https?:\/\/([a-z0-9-]+)\.github\.io\/([a-z0-9_.-]+)/i);
|
|
@@ -79,7 +75,7 @@ export function parseRepo(input: string): string {
|
|
|
79
75
|
throw new Error(`Cannot parse repo from "${input}" — use owner/repo or a *.github.io/<repo> URL`);
|
|
80
76
|
}
|
|
81
77
|
|
|
82
|
-
|
|
78
|
+
|
|
83
79
|
const scenes = new Map<string, { elements: Map<string, ServerElement>; sha: string | null; dirty: boolean }>();
|
|
84
80
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
85
81
|
const SCENE_PATH = process.env.SCENE_PATH || 'canvas.excalidraw';
|
|
@@ -133,22 +129,20 @@ export async function commitNow(message?: string): Promise<{ sha: string; count:
|
|
|
133
129
|
const st = current();
|
|
134
130
|
const elements = Array.from(st.elements.values());
|
|
135
131
|
const doc = { type: 'excalidraw', version: 2, source: 'excalidrop', elements };
|
|
136
|
-
// Shared putFile handles: sha optimistic concurrency (409) + bootstrapping
|
|
137
|
-
// main on empty repos (forks off gh-pages) — same path as the remote server.
|
|
138
132
|
const gh = await import('./utils/github.js');
|
|
139
133
|
const newSha = await gh.putFile(repo, SCENE_PATH, doc, message || `excalidrop: update ${elements.length} elements`, 'main', st.sha || undefined);
|
|
140
134
|
st.sha = newSha;
|
|
141
135
|
st.dirty = false;
|
|
142
|
-
// mirror to gh-pages viewer copy
|
|
143
136
|
try {
|
|
144
137
|
const cur = await gh.getFile(repo, SCENE_PATH, 'gh-pages').catch(() => null);
|
|
145
138
|
await gh.putFile(repo, SCENE_PATH, doc, 'excalidrop: sync viewer', 'gh-pages', cur?.sha);
|
|
139
|
+
await gh.updateRepoMetadata(repo, { homepage: `https://${repo.replace('/', '.github.io/')}/` });
|
|
146
140
|
} catch (e) { logger.warn('viewer mirror failed: ' + (e as Error).message); }
|
|
147
141
|
logger.info(`Committed ${elements.length} elements to ${repo}`);
|
|
148
142
|
return { sha: st.sha as string, count: elements.length };
|
|
149
143
|
}
|
|
150
144
|
|
|
151
|
-
|
|
145
|
+
|
|
152
146
|
export function rCreate(el: ServerElement): ServerElement {
|
|
153
147
|
const st = current();
|
|
154
148
|
const full = { ...el, id: el.id || generateId() };
|
|
@@ -172,11 +166,7 @@ export function rList(type?: string): ServerElement[] {
|
|
|
172
166
|
export function rClear(): number { const st = current(); const n = st.elements.size; st.elements.clear(); st.dirty = true; scheduleCommit(); return n; }
|
|
173
167
|
export const isRemote = (): boolean => active.kind === 'remote';
|
|
174
168
|
|
|
175
|
-
|
|
176
|
-
// NOTE: deliberate device-flow (not web flow): static hosting has nowhere to
|
|
177
|
-
// keep a client_secret, and device flow is the only secretless path per docs.
|
|
178
|
-
// App user-tokens carry no scopes — permissions = user ∩ app. Optional
|
|
179
|
-
// repository_id restricts the token to a single repo (least privilege).
|
|
169
|
+
|
|
180
170
|
export async function deviceStart(clientId: string): Promise<{ user_code: string; verification_uri: string; device_code: string; interval: number }> {
|
|
181
171
|
const r = await fetch('https://github.com/login/device/code', {
|
|
182
172
|
method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
package/src/types.ts
CHANGED
|
@@ -108,7 +108,7 @@ export interface ExcalidrawBinding {
|
|
|
108
108
|
|
|
109
109
|
export type ExcalidrawElementType = 'rectangle' | 'ellipse' | 'diamond' | 'arrow' | 'text' | 'line' | 'freedraw' | 'image';
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
export const EXCALIDRAW_ELEMENT_TYPES: Record<string, ExcalidrawElementType> = {
|
|
113
113
|
RECTANGLE: 'rectangle',
|
|
114
114
|
ELLIPSE: 'ellipse',
|
|
@@ -120,7 +120,7 @@ export const EXCALIDRAW_ELEMENT_TYPES: Record<string, ExcalidrawElementType> = {
|
|
|
120
120
|
IMAGE: 'image'
|
|
121
121
|
} as const;
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
|
|
124
124
|
export interface ServerElement extends Omit<ExcalidrawElementBase, 'id'> {
|
|
125
125
|
id: string;
|
|
126
126
|
type: ExcalidrawElementType;
|
|
@@ -138,12 +138,12 @@ export interface ServerElement extends Omit<ExcalidrawElementBase, 'id'> {
|
|
|
138
138
|
text: string;
|
|
139
139
|
};
|
|
140
140
|
points?: any;
|
|
141
|
-
|
|
141
|
+
|
|
142
142
|
start?: { id: string };
|
|
143
143
|
end?: { id: string };
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
|
|
147
147
|
export interface ApiResponse<T = any> {
|
|
148
148
|
success: boolean;
|
|
149
149
|
data?: T;
|
|
@@ -167,7 +167,7 @@ export interface SyncResponse extends ApiResponse {
|
|
|
167
167
|
afterCount: number;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
|
|
171
171
|
export interface WebSocketMessage {
|
|
172
172
|
type: WebSocketMessageType;
|
|
173
173
|
[key: string]: any;
|
|
@@ -226,7 +226,7 @@ export interface MermaidConvertMessage extends WebSocketMessage {
|
|
|
226
226
|
timestamp: string;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
|
|
230
230
|
export interface MermaidConfig {
|
|
231
231
|
startOnLoad?: boolean;
|
|
232
232
|
flowchart?: {
|
|
@@ -250,13 +250,13 @@ export interface MermaidConversionResponse extends ApiResponse {
|
|
|
250
250
|
count: number;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
|
|
253
|
+
|
|
254
254
|
export interface CanvasClearedMessage extends WebSocketMessage {
|
|
255
255
|
type: 'canvas_cleared';
|
|
256
256
|
timestamp: string;
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
|
|
259
|
+
|
|
260
260
|
export interface ExportImageRequestMessage extends WebSocketMessage {
|
|
261
261
|
type: 'export_image_request';
|
|
262
262
|
requestId: string;
|
|
@@ -264,7 +264,7 @@ export interface ExportImageRequestMessage extends WebSocketMessage {
|
|
|
264
264
|
background?: boolean;
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
|
|
268
268
|
export interface SetViewportMessage extends WebSocketMessage {
|
|
269
269
|
type: 'set_viewport';
|
|
270
270
|
requestId: string;
|
|
@@ -275,20 +275,20 @@ export interface SetViewportMessage extends WebSocketMessage {
|
|
|
275
275
|
offsetY?: number;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
|
|
278
|
+
|
|
279
279
|
export interface Snapshot {
|
|
280
280
|
name: string;
|
|
281
281
|
elements: ServerElement[];
|
|
282
282
|
createdAt: string;
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
|
|
285
|
+
|
|
286
286
|
export const elements = new Map<string, ServerElement>();
|
|
287
287
|
|
|
288
|
-
|
|
288
|
+
|
|
289
289
|
export const snapshots = new Map<string, Snapshot>();
|
|
290
290
|
|
|
291
|
-
|
|
291
|
+
|
|
292
292
|
export interface ExcalidrawFile {
|
|
293
293
|
id: string;
|
|
294
294
|
dataURL: string;
|
|
@@ -297,7 +297,7 @@ export interface ExcalidrawFile {
|
|
|
297
297
|
}
|
|
298
298
|
export const files = new Map<string, ExcalidrawFile>();
|
|
299
299
|
|
|
300
|
-
|
|
300
|
+
|
|
301
301
|
export function validateElement(element: Partial<ServerElement>): element is ServerElement {
|
|
302
302
|
const requiredFields: (keyof ServerElement)[] = ['type', 'x', 'y'];
|
|
303
303
|
const hasRequiredFields = requiredFields.every(field => field in element);
|
|
@@ -313,14 +313,12 @@ export function validateElement(element: Partial<ServerElement>): element is Ser
|
|
|
313
313
|
return true;
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
|
|
316
|
+
|
|
317
317
|
export function generateId(): string {
|
|
318
318
|
return Date.now().toString(36) + Math.random().toString(36).substring(2);
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
|
|
322
|
-
// Excalidraw uses: 1 = Virgil (handwritten), 2 = Helvetica (sans-serif), 3 = Cascadia (monospace)
|
|
323
|
-
// 5 = Excalifont, 6 = Nunito, 7 = Lilita One, 8 = Comic Shanns
|
|
321
|
+
|
|
324
322
|
export function normalizeFontFamily(fontFamily: string | number | undefined): number | undefined {
|
|
325
323
|
if (fontFamily === undefined) return undefined;
|
|
326
324
|
if (typeof fontFamily === 'number') return fontFamily;
|
package/src/utils/auth.ts
CHANGED
|
@@ -41,7 +41,7 @@ export function authOptional(req: Request, _res: Response, next: NextFunction) {
|
|
|
41
41
|
next();
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
export function requireWriteAuth(req: Request, res: Response, next: NextFunction) {
|
|
46
46
|
if (!process.env.GITHUB_CLIENT_ID) return next(); // local dev: open
|
|
47
47
|
const tok = getToken(req);
|