upfynai-code 2.5.1 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -88
- package/bin/cli.js +63 -0
- package/package.json +48 -112
- package/src/auth.js +115 -0
- package/src/config.js +33 -0
- package/src/connect.js +314 -0
- package/src/launch.js +54 -0
- package/src/mcp.js +57 -0
- package/src/server.js +54 -0
- package/client/dist/api-docs.html +0 -879
- package/client/dist/assets/AppContent-C0CyP3g5.js +0 -513
- package/client/dist/assets/CanvasPanel-0u9QR7U-.js +0 -34
- package/client/dist/assets/CanvasPanel-WhZulBJw.css +0 -1
- package/client/dist/assets/DashboardPanel-Dgqw1yZk.js +0 -1
- package/client/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/client/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/client/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/client/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/client/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/client/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/client/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/client/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/client/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/client/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/client/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/client/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/client/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/client/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/client/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/client/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/client/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/client/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/client/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/client/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/client/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/client/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/client/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/client/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/client/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/client/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/client/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/client/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/client/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/client/dist/assets/LoginModal-CZDEzqjK.js +0 -19
- package/client/dist/assets/MarkdownPreview-CYdvwJaV.js +0 -1
- package/client/dist/assets/Onboarding-DR6NZ4Vz.js +0 -1
- package/client/dist/assets/SetupForm-D49gtWY4.js +0 -1
- package/client/dist/assets/Tableau10-B-NsZVaP.js +0 -1
- package/client/dist/assets/WorkflowsPanel-CqlbEJA_.js +0 -1
- package/client/dist/assets/_commonjs-dynamic-modules-TDtrdbi3.js +0 -1
- package/client/dist/assets/ar-SA-G6X2FPQ2-BWqa1yBH.js +0 -10
- package/client/dist/assets/arc-BegSKqEW.js +0 -1
- package/client/dist/assets/array-BKyUJesY.js +0 -1
- package/client/dist/assets/az-AZ-76LH7QW2-DrVlbZDP.js +0 -1
- package/client/dist/assets/bg-BG-XCXSNQG7-DdunjBgT.js +0 -5
- package/client/dist/assets/blockDiagram-38ab4fdb-BKMbwGHu.js +0 -118
- package/client/dist/assets/bn-BD-2XOGV67Q-_7DtmvwO.js +0 -5
- package/client/dist/assets/c4Diagram-3d4e48cf-hJuiHhSn.js +0 -10
- package/client/dist/assets/ca-ES-6MX7JW3Y-BFIrmojG.js +0 -8
- package/client/dist/assets/channel-Bur-rRTp.js +0 -1
- package/client/dist/assets/classDiagram-70f12bd4-BjiAf9cM.js +0 -2
- package/client/dist/assets/classDiagram-v2-f2320105-pwBewejc.js +0 -2
- package/client/dist/assets/clone-BtqXeoBJ.js +0 -1
- package/client/dist/assets/createText-2e5e7dd3-Dq_acOWe.js +0 -5
- package/client/dist/assets/cs-CZ-2BRQDIVT-B-x4F6TJ.js +0 -11
- package/client/dist/assets/da-DK-5WZEPLOC-Btlc8Dgn.js +0 -5
- package/client/dist/assets/de-DE-XR44H4JA-BVu3ZIoD.js +0 -8
- package/client/dist/assets/directory-open-01563666-DWU9wJ6I.js +0 -1
- package/client/dist/assets/directory-open-4ed118d0-CunoC1EB.js +0 -1
- package/client/dist/assets/edges-e0da2a9e-DH0wVTXR.js +0 -4
- package/client/dist/assets/el-GR-BZB4AONW-h2ll8_ZC.js +0 -10
- package/client/dist/assets/erDiagram-9861fffd-BYezLIR7.js +0 -51
- package/client/dist/assets/es-ES-U4NZUMDT-Cveiulwt.js +0 -9
- package/client/dist/assets/eu-ES-A7QVB2H4-DQluL2PY.js +0 -11
- package/client/dist/assets/fa-IR-HGAKTJCU-BJtcMBSv.js +0 -8
- package/client/dist/assets/fi-FI-Z5N7JZ37-D8NfbVXV.js +0 -6
- package/client/dist/assets/file-open-002ab408-DIuFHtCF.js +0 -1
- package/client/dist/assets/file-open-7c801643-684qeFg4.js +0 -1
- package/client/dist/assets/file-save-3189631c-C1wFhQhH.js +0 -1
- package/client/dist/assets/file-save-745eba88-Bb9F9Kg7.js +0 -1
- package/client/dist/assets/flowDb-956e92f1-scnUykhM.js +0 -10
- package/client/dist/assets/flowDiagram-66a62f08-jVyWsfyU.js +0 -4
- package/client/dist/assets/flowDiagram-v2-96b9c2cf-N6xgi25h.js +0 -1
- package/client/dist/assets/flowchart-elk-definition-4a651766-gKGX3HqR.js +0 -139
- package/client/dist/assets/fr-FR-RHASNOE6-vdj42kC6.js +0 -9
- package/client/dist/assets/ganttDiagram-c361ad54-C2CiWFUP.js +0 -257
- package/client/dist/assets/gitGraphDiagram-72cf32ee-C59Yz2LK.js +0 -70
- package/client/dist/assets/gl-ES-HMX3MZ6V-DQo0TzoP.js +0 -10
- package/client/dist/assets/graph-Dx_H43Kv.js +0 -1
- package/client/dist/assets/he-IL-6SHJWFNN-DKXK5e33.js +0 -10
- package/client/dist/assets/hi-IN-IWLTKZ5I-C2Qgqc0R.js +0 -4
- package/client/dist/assets/hu-HU-A5ZG7DT2-Ss-6vX0m.js +0 -7
- package/client/dist/assets/id-ID-SAP4L64H-D7Wsg1S2.js +0 -10
- package/client/dist/assets/image-blob-reduce.esm-D6s-rqMO.js +0 -7
- package/client/dist/assets/index-3862675e-u8Nv7hHC.js +0 -1
- package/client/dist/assets/index-BVowJdZF.js +0 -97
- package/client/dist/assets/index-ce18TYkg.js +0 -27
- package/client/dist/assets/index-kQoJx-bc.css +0 -1
- package/client/dist/assets/infoDiagram-f8f76790-LmoJYsxo.js +0 -7
- package/client/dist/assets/init-Gi6I4Gst.js +0 -1
- package/client/dist/assets/it-IT-JPQ66NNP-CAPTVl7M.js +0 -11
- package/client/dist/assets/ja-JP-DBVTYXUO-eNVPawR2.js +0 -8
- package/client/dist/assets/journeyDiagram-49397b02-BaJqehpR.js +0 -139
- package/client/dist/assets/kaa-6HZHGXH3-tpuNkKhS.js +0 -1
- package/client/dist/assets/kab-KAB-ZGHBKWFO-Dp83kx4x.js +0 -8
- package/client/dist/assets/kk-KZ-P5N5QNE5-B9IlC6YN.js +0 -1
- package/client/dist/assets/km-KH-HSX4SM5Z-B_KMYaMj.js +0 -11
- package/client/dist/assets/ko-KR-MTYHY66A-yebnUNdb.js +0 -9
- package/client/dist/assets/ku-TR-6OUDTVRD-BR6fh6-5.js +0 -9
- package/client/dist/assets/layout-DLl5Jwcl.js +0 -1
- package/client/dist/assets/line-FpB7omSK.js +0 -1
- package/client/dist/assets/linear-CkXqUFJ8.js +0 -1
- package/client/dist/assets/lt-LT-XHIRWOB4-SutZSWtR.js +0 -3
- package/client/dist/assets/lv-LV-5QDEKY6T-DuAxdcZL.js +0 -7
- package/client/dist/assets/mindmap-definition-fc14e90a-DyxXOExh.js +0 -425
- package/client/dist/assets/mr-IN-CRQNXWMA-DqDUWM_8.js +0 -13
- package/client/dist/assets/my-MM-5M5IBNSE-C40kMFMR.js +0 -1
- package/client/dist/assets/nb-NO-T6EIAALU-DVij32Ju.js +0 -10
- package/client/dist/assets/nl-NL-IS3SIHDZ-rT84mDYq.js +0 -8
- package/client/dist/assets/nn-NO-6E72VCQL-BBZXBW8V.js +0 -8
- package/client/dist/assets/oc-FR-POXYY2M6-DzjOugOf.js +0 -8
- package/client/dist/assets/ordinal-Cboi1Yqb.js +0 -1
- package/client/dist/assets/pa-IN-N4M65BXN-DD1iU8_F.js +0 -4
- package/client/dist/assets/path-CbwjOpE9.js +0 -1
- package/client/dist/assets/pdf-CE_K4jFx.js +0 -12
- package/client/dist/assets/pdf.worker-BA9kU3Pw.mjs +0 -61080
- package/client/dist/assets/percentages-BXMCSKIN-WVlHS4wx.js +0 -207
- package/client/dist/assets/pica-CQIY57Tf.js +0 -7
- package/client/dist/assets/pieDiagram-8a3498a8-Dd_85qBH.js +0 -35
- package/client/dist/assets/pl-PL-T2D74RX3-ukVXa48G.js +0 -9
- package/client/dist/assets/pt-BR-5N22H2LF-BibawarT.js +0 -9
- package/client/dist/assets/pt-PT-UZXXM6DQ-So3i9l9w.js +0 -9
- package/client/dist/assets/quadrantDiagram-120e2f19-C4dFVDEx.js +0 -7
- package/client/dist/assets/requirementDiagram-deff3bca-DrTO7yFl.js +0 -52
- package/client/dist/assets/ro-RO-JPDTUUEW-DY0Xq_Hd.js +0 -11
- package/client/dist/assets/roundRect-0PYZxl1G.js +0 -1
- package/client/dist/assets/ru-RU-B4JR7IUQ-B7u_Zvkd.js +0 -9
- package/client/dist/assets/sankeyDiagram-04a897e0-D24gfzuS.js +0 -8
- package/client/dist/assets/sequenceDiagram-704730f1-Dgji2XLQ.js +0 -122
- package/client/dist/assets/si-LK-N5RQ5JYF-OejsLzQ_.js +0 -1
- package/client/dist/assets/sk-SK-C5VTKIMK-_vy2Bt-M.js +0 -6
- package/client/dist/assets/sl-SI-NN7IZMDC-DKOl_u2M.js +0 -6
- package/client/dist/assets/stateDiagram-587899a1-CJ8eBaiU.js +0 -1
- package/client/dist/assets/stateDiagram-v2-d93cdb3a-C5K3l-Nt.js +0 -1
- package/client/dist/assets/styles-6aaf32cf-DAKE0jbx.js +0 -207
- package/client/dist/assets/styles-9a916d00-LFAJCgEy.js +0 -160
- package/client/dist/assets/styles-c10674c1-CllKO8NG.js +0 -116
- package/client/dist/assets/subset-shared.chunk-Uy-J87FQ.js +0 -84
- package/client/dist/assets/subset-worker.chunk-dvgDvqt9.js +0 -1
- package/client/dist/assets/sv-SE-XGPEYMSR-CDCB2ZV5.js +0 -10
- package/client/dist/assets/svgDrawCommon-08f97a94-CObOzbFQ.js +0 -1
- package/client/dist/assets/ta-IN-2NMHFXQM-DHUNdO69.js +0 -9
- package/client/dist/assets/th-TH-HPSO5L25-zI2hnBq3.js +0 -2
- package/client/dist/assets/timeline-definition-85554ec2-C2XHRmxK.js +0 -61
- package/client/dist/assets/tr-TR-DEFEU3FU-l-6Hu4-D.js +0 -7
- package/client/dist/assets/uk-UA-QMV73CPH-CqSOwrl7.js +0 -6
- package/client/dist/assets/vendor-codemirror-D_s0aGBu.js +0 -35
- package/client/dist/assets/vendor-i18n-DCFGyhQR.js +0 -1
- package/client/dist/assets/vendor-icons-Lb69KSFJ.js +0 -646
- package/client/dist/assets/vendor-markdown-BXEi_H3G.js +0 -298
- package/client/dist/assets/vendor-react-9mUTKBHH.js +0 -67
- package/client/dist/assets/vendor-syntax-DnmwQQJF.js +0 -16
- package/client/dist/assets/vendor-xterm-CZq1hqo1.js +0 -66
- package/client/dist/assets/vendor-xterm-qxJ8_QYu.css +0 -32
- package/client/dist/assets/vi-VN-M7AON7JQ-CUL8-mBZ.js +0 -5
- package/client/dist/assets/xychartDiagram-e933f94c-1fmf6slj.js +0 -7
- package/client/dist/assets/zh-CN-LNUGB5OW-CB5y5VVU.js +0 -10
- package/client/dist/assets/zh-HK-E62DVLB3-BHcrrEeJ.js +0 -1
- package/client/dist/assets/zh-TW-RAJ6MFWO-DoDUdkaJ.js +0 -9
- package/client/dist/clear-cache.html +0 -85
- package/client/dist/convert-icons.md +0 -53
- package/client/dist/favicon.png +0 -0
- package/client/dist/favicon.svg +0 -9
- package/client/dist/generate-icons.js +0 -49
- package/client/dist/icons/claude-ai-icon.svg +0 -1
- package/client/dist/icons/codex-white.svg +0 -3
- package/client/dist/icons/codex.svg +0 -3
- package/client/dist/icons/cursor-white.svg +0 -12
- package/client/dist/icons/cursor.svg +0 -1
- package/client/dist/icons/icon-128x128.png +0 -0
- package/client/dist/icons/icon-128x128.svg +0 -12
- package/client/dist/icons/icon-144x144.png +0 -0
- package/client/dist/icons/icon-144x144.svg +0 -12
- package/client/dist/icons/icon-152x152.png +0 -0
- package/client/dist/icons/icon-152x152.svg +0 -12
- package/client/dist/icons/icon-192x192.png +0 -0
- package/client/dist/icons/icon-192x192.svg +0 -12
- package/client/dist/icons/icon-384x384.png +0 -0
- package/client/dist/icons/icon-384x384.svg +0 -12
- package/client/dist/icons/icon-512x512.png +0 -0
- package/client/dist/icons/icon-512x512.svg +0 -12
- package/client/dist/icons/icon-72x72.png +0 -0
- package/client/dist/icons/icon-72x72.svg +0 -12
- package/client/dist/icons/icon-96x96.png +0 -0
- package/client/dist/icons/icon-96x96.svg +0 -12
- package/client/dist/icons/icon-template.svg +0 -12
- package/client/dist/index.html +0 -128
- package/client/dist/logo-128.png +0 -0
- package/client/dist/logo-256.png +0 -0
- package/client/dist/logo-32.png +0 -0
- package/client/dist/logo-512.png +0 -0
- package/client/dist/logo-64.png +0 -0
- package/client/dist/logo.svg +0 -17
- package/client/dist/manifest.json +0 -61
- package/client/dist/mcp-docs.html +0 -119
- package/client/dist/screenshots/cli-selection.png +0 -0
- package/client/dist/screenshots/desktop-main.png +0 -0
- package/client/dist/screenshots/mobile-chat.png +0 -0
- package/client/dist/screenshots/tools-modal.png +0 -0
- package/client/dist/sw.js +0 -19
- package/commands/upfynai-connect.md +0 -59
- package/commands/upfynai-disconnect.md +0 -31
- package/commands/upfynai-doctor.md +0 -99
- package/commands/upfynai-export.md +0 -49
- package/commands/upfynai-local.md +0 -82
- package/commands/upfynai-status.md +0 -75
- package/commands/upfynai-stop.md +0 -49
- package/commands/upfynai-uninstall.md +0 -58
- package/commands/upfynai.md +0 -69
- package/scripts/build-client.js +0 -17
- package/scripts/fix-node-pty.js +0 -67
- package/scripts/install-commands.js +0 -78
- package/server/claude-sdk.js +0 -714
- package/server/cli-ui.js +0 -785
- package/server/cli.js +0 -596
- package/server/constants/config.js +0 -31
- package/server/cursor-cli.js +0 -270
- package/server/database/auth.db +0 -0
- package/server/database/db.js +0 -808
- package/server/database/init.sql +0 -70
- package/server/index.js +0 -2621
- package/server/load-env.js +0 -26
- package/server/mcp-server.js +0 -621
- package/server/middleware/auth.js +0 -173
- package/server/openai-codex.js +0 -403
- package/server/openrouter.js +0 -137
- package/server/projects.js +0 -1742
- package/server/relay-client.js +0 -619
- package/server/routes/agent.js +0 -1266
- package/server/routes/auth.js +0 -263
- package/server/routes/cli-auth.js +0 -263
- package/server/routes/codex.js +0 -344
- package/server/routes/commands.js +0 -601
- package/server/routes/cursor.js +0 -808
- package/server/routes/dashboard.js +0 -52
- package/server/routes/git.js +0 -1165
- package/server/routes/mcp-utils.js +0 -48
- package/server/routes/mcp.js +0 -552
- package/server/routes/payments.js +0 -172
- package/server/routes/projects.js +0 -552
- package/server/routes/settings.js +0 -269
- package/server/routes/taskmaster.js +0 -1964
- package/server/routes/user.js +0 -106
- package/server/routes/voice.js +0 -198
- package/server/routes/webhooks.js +0 -166
- package/server/routes/workflows.js +0 -118
- package/server/services/whisperService.js +0 -84
- package/server/services/workflowScheduler.js +0 -186
- package/server/utils/commandParser.js +0 -303
- package/server/utils/gitConfig.js +0 -24
- package/server/utils/mcp-detector.js +0 -198
- package/server/utils/taskmaster-websocket.js +0 -129
- package/shared/modelConstants.js +0 -96
package/src/connect.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import WebSocket from 'ws';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import { promises as fsPromises } from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { readConfig, writeConfig } from './config.js';
|
|
8
|
+
import { getToken, validateToken } from './auth.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Execute a shell command and return stdout
|
|
12
|
+
*/
|
|
13
|
+
function execCommand(cmd, args, options = {}) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const proc = spawn(cmd, args, {
|
|
16
|
+
shell: true,
|
|
17
|
+
cwd: options.cwd || os.homedir(),
|
|
18
|
+
env: { ...process.env, ...options.env },
|
|
19
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
let stdout = '';
|
|
23
|
+
let stderr = '';
|
|
24
|
+
proc.stdout.on('data', (d) => { stdout += d; });
|
|
25
|
+
proc.stderr.on('data', (d) => { stderr += d; });
|
|
26
|
+
|
|
27
|
+
const timeout = setTimeout(() => {
|
|
28
|
+
proc.kill();
|
|
29
|
+
reject(new Error('Command timed out'));
|
|
30
|
+
}, options.timeout || 30000);
|
|
31
|
+
|
|
32
|
+
proc.on('close', (code) => {
|
|
33
|
+
clearTimeout(timeout);
|
|
34
|
+
if (code === 0) resolve(stdout);
|
|
35
|
+
else reject(new Error(stderr || `Exit code ${code}`));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
proc.on('error', (err) => {
|
|
39
|
+
clearTimeout(timeout);
|
|
40
|
+
reject(err);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Build a file tree for a directory
|
|
47
|
+
*/
|
|
48
|
+
async function buildFileTree(dirPath, maxDepth, currentDepth = 0) {
|
|
49
|
+
if (currentDepth >= maxDepth) return [];
|
|
50
|
+
try {
|
|
51
|
+
const entries = await fsPromises.readdir(dirPath, { withFileTypes: true });
|
|
52
|
+
const items = [];
|
|
53
|
+
for (const entry of entries.slice(0, 100)) {
|
|
54
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
55
|
+
const item = { name: entry.name, type: entry.isDirectory() ? 'directory' : 'file' };
|
|
56
|
+
if (entry.isDirectory() && currentDepth < maxDepth - 1) {
|
|
57
|
+
item.children = await buildFileTree(path.join(dirPath, entry.name), maxDepth, currentDepth + 1);
|
|
58
|
+
}
|
|
59
|
+
items.push(item);
|
|
60
|
+
}
|
|
61
|
+
return items;
|
|
62
|
+
} catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Handle incoming relay commands from the server
|
|
69
|
+
*/
|
|
70
|
+
async function handleRelayCommand(data, ws) {
|
|
71
|
+
const { requestId, action } = data;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
switch (action) {
|
|
75
|
+
case 'claude-query': {
|
|
76
|
+
const { command, options } = data;
|
|
77
|
+
console.log(chalk.cyan(' [relay] Processing Claude query...'));
|
|
78
|
+
|
|
79
|
+
const args = ['--print'];
|
|
80
|
+
if (options?.projectPath) args.push('--cwd', options.projectPath);
|
|
81
|
+
if (options?.sessionId) args.push('--continue', options.sessionId);
|
|
82
|
+
|
|
83
|
+
const proc = spawn('claude', [...args, command || ''], {
|
|
84
|
+
shell: true,
|
|
85
|
+
cwd: options?.projectPath || os.homedir(),
|
|
86
|
+
env: process.env,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
proc.stdout.on('data', (chunk) => {
|
|
90
|
+
ws.send(JSON.stringify({
|
|
91
|
+
type: 'relay-stream',
|
|
92
|
+
requestId,
|
|
93
|
+
data: { type: 'claude-response', content: chunk.toString() },
|
|
94
|
+
}));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
proc.stderr.on('data', (chunk) => {
|
|
98
|
+
ws.send(JSON.stringify({
|
|
99
|
+
type: 'relay-stream',
|
|
100
|
+
requestId,
|
|
101
|
+
data: { type: 'claude-error', content: chunk.toString() },
|
|
102
|
+
}));
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
proc.on('close', (code) => {
|
|
106
|
+
ws.send(JSON.stringify({
|
|
107
|
+
type: 'relay-complete',
|
|
108
|
+
requestId,
|
|
109
|
+
exitCode: code,
|
|
110
|
+
}));
|
|
111
|
+
});
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
case 'shell-command': {
|
|
116
|
+
const { command: cmd, cwd } = data;
|
|
117
|
+
if (!cmd || typeof cmd !== 'string') throw new Error('Invalid command');
|
|
118
|
+
const cmdLower = cmd.toLowerCase();
|
|
119
|
+
const dangerous = [
|
|
120
|
+
'rm -rf /', 'mkfs', 'dd if=', ':(){', 'fork bomb', '> /dev/sd',
|
|
121
|
+
'format c:', 'format d:', 'format e:', 'del /s /q c:\\',
|
|
122
|
+
'rd /s /q c:\\', 'reg delete', 'bcdedit',
|
|
123
|
+
];
|
|
124
|
+
if (dangerous.some(d => cmdLower.includes(d.toLowerCase()))) throw new Error('Command blocked for safety');
|
|
125
|
+
console.log(chalk.dim(' [relay] Executing shell command...'));
|
|
126
|
+
const result = await execCommand(cmd, [], { cwd: cwd || process.cwd(), timeout: 60000 });
|
|
127
|
+
ws.send(JSON.stringify({ type: 'relay-response', requestId, data: { stdout: result } }));
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
case 'file-read': {
|
|
132
|
+
const { filePath } = data;
|
|
133
|
+
if (!filePath || typeof filePath !== 'string') throw new Error('Invalid file path');
|
|
134
|
+
const normalizedPath = path.resolve(filePath);
|
|
135
|
+
const normLower = normalizedPath.toLowerCase().replace(/\\/g, '/');
|
|
136
|
+
const blockedRead = ['/etc/shadow', '/etc/passwd', '.ssh/id_rsa', '.ssh/id_ed25519', '/.env'];
|
|
137
|
+
if (blockedRead.some(b => normLower.includes(b))) throw new Error('Access denied');
|
|
138
|
+
const content = await fsPromises.readFile(normalizedPath, 'utf8');
|
|
139
|
+
ws.send(JSON.stringify({ type: 'relay-response', requestId, data: { content } }));
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
case 'file-write': {
|
|
144
|
+
const { filePath: fp, content: fileContent } = data;
|
|
145
|
+
if (!fp || typeof fp !== 'string') throw new Error('Invalid file path');
|
|
146
|
+
const normalizedFp = path.resolve(fp);
|
|
147
|
+
const fpLower = normalizedFp.toLowerCase().replace(/\\/g, '/');
|
|
148
|
+
const blockedWrite = [
|
|
149
|
+
'/etc/', '/usr/bin/', '/usr/sbin/',
|
|
150
|
+
'/windows/system32', '/windows/syswow64', '/program files',
|
|
151
|
+
'.ssh/', '/.env',
|
|
152
|
+
];
|
|
153
|
+
if (blockedWrite.some(d => fpLower.includes(d))) throw new Error('Access denied');
|
|
154
|
+
const parentDir = path.dirname(normalizedFp);
|
|
155
|
+
await fsPromises.mkdir(parentDir, { recursive: true }).catch(() => {});
|
|
156
|
+
await fsPromises.writeFile(normalizedFp, fileContent, 'utf8');
|
|
157
|
+
ws.send(JSON.stringify({ type: 'relay-response', requestId, data: { success: true } }));
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
case 'file-tree': {
|
|
162
|
+
const { dirPath, depth = 3 } = data;
|
|
163
|
+
const resolvedDir = dirPath ? path.resolve(dirPath) : process.cwd();
|
|
164
|
+
const tree = await buildFileTree(resolvedDir, depth);
|
|
165
|
+
ws.send(JSON.stringify({ type: 'relay-response', requestId, data: { tree } }));
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case 'git-operation': {
|
|
170
|
+
const { gitCommand, cwd: gitCwd } = data;
|
|
171
|
+
console.log(chalk.dim(' [relay] Running git operation...'));
|
|
172
|
+
const resolvedGitCwd = gitCwd ? path.resolve(gitCwd) : process.cwd();
|
|
173
|
+
const result = await execCommand('git', [gitCommand], { cwd: resolvedGitCwd });
|
|
174
|
+
ws.send(JSON.stringify({ type: 'relay-response', requestId, data: { stdout: result } }));
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
default:
|
|
179
|
+
ws.send(JSON.stringify({
|
|
180
|
+
type: 'relay-response',
|
|
181
|
+
requestId,
|
|
182
|
+
error: `Unknown action: ${action}`,
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
} catch (err) {
|
|
186
|
+
ws.send(JSON.stringify({
|
|
187
|
+
type: 'relay-response',
|
|
188
|
+
requestId,
|
|
189
|
+
error: err.message,
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Connect to the remote server via WebSocket relay.
|
|
196
|
+
* Bridges local Claude Code, shell, filesystem, and git to the web UI.
|
|
197
|
+
*/
|
|
198
|
+
export async function connect(options = {}) {
|
|
199
|
+
const config = readConfig();
|
|
200
|
+
const serverUrl = options.server || config.serverUrl;
|
|
201
|
+
let relayKey = options.key;
|
|
202
|
+
|
|
203
|
+
// If no key provided, fetch one using the auth token
|
|
204
|
+
if (!relayKey) {
|
|
205
|
+
const token = getToken();
|
|
206
|
+
if (!token) {
|
|
207
|
+
console.log(chalk.yellow('\n No account found. Run `upfyn-code login` first.\n'));
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(chalk.dim('\n Validating session...'));
|
|
212
|
+
const user = await validateToken();
|
|
213
|
+
if (!user) {
|
|
214
|
+
console.log(chalk.yellow(' Session expired. Run `upfyn-code login` to re-authenticate.\n'));
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Fetch a relay token from the API
|
|
219
|
+
try {
|
|
220
|
+
const res = await fetch(`${serverUrl}/api/auth/connect-token`, {
|
|
221
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
222
|
+
});
|
|
223
|
+
if (!res.ok) throw new Error('Failed to get connect token');
|
|
224
|
+
const data = await res.json();
|
|
225
|
+
relayKey = data.token;
|
|
226
|
+
} catch (err) {
|
|
227
|
+
console.log(chalk.red('\n Could not get connection token. Check your network and try again.\n'));
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Save for future use
|
|
233
|
+
writeConfig({ relayKey });
|
|
234
|
+
|
|
235
|
+
const wsUrl = serverUrl.replace(/^http/, 'ws') + '/relay?token=' + encodeURIComponent(relayKey);
|
|
236
|
+
|
|
237
|
+
console.log(chalk.bold('\n Upfyn-Code Relay Client\n'));
|
|
238
|
+
console.log(` Server: ${chalk.cyan(serverUrl)}`);
|
|
239
|
+
console.log(` Machine: ${chalk.dim(os.hostname())}`);
|
|
240
|
+
console.log(` User: ${chalk.dim(os.userInfo().username)}\n`);
|
|
241
|
+
|
|
242
|
+
let reconnectAttempts = 0;
|
|
243
|
+
const MAX_RECONNECT = 10;
|
|
244
|
+
|
|
245
|
+
function doConnect() {
|
|
246
|
+
const ws = new WebSocket(wsUrl);
|
|
247
|
+
|
|
248
|
+
ws.on('open', () => {
|
|
249
|
+
reconnectAttempts = 0;
|
|
250
|
+
console.log(chalk.green(' Connected! Your local machine is now bridged to the server.'));
|
|
251
|
+
console.log(chalk.dim(' Claude Code is the AI brain. Press Ctrl+C to disconnect.\n'));
|
|
252
|
+
|
|
253
|
+
const heartbeat = setInterval(() => {
|
|
254
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
255
|
+
ws.send(JSON.stringify({ type: 'ping' }));
|
|
256
|
+
}
|
|
257
|
+
}, 30000);
|
|
258
|
+
|
|
259
|
+
ws.on('close', () => clearInterval(heartbeat));
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
ws.on('message', (rawMessage) => {
|
|
263
|
+
try {
|
|
264
|
+
const data = JSON.parse(rawMessage);
|
|
265
|
+
|
|
266
|
+
if (data.type === 'relay-connected') {
|
|
267
|
+
console.log(chalk.green(` ${data.message}`));
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (data.type === 'relay-command') {
|
|
271
|
+
handleRelayCommand(data, ws);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (data.type === 'pong') return;
|
|
275
|
+
if (data.type === 'error') {
|
|
276
|
+
console.error(chalk.red(` Server error: ${data.error}`));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
} catch (e) {
|
|
280
|
+
// message parse error
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
ws.on('close', (code) => {
|
|
285
|
+
if (code === 1000) {
|
|
286
|
+
console.log(chalk.dim(' Disconnected.'));
|
|
287
|
+
process.exit(0);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
reconnectAttempts++;
|
|
291
|
+
if (reconnectAttempts <= MAX_RECONNECT) {
|
|
292
|
+
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
|
|
293
|
+
console.log(chalk.dim(` Connection lost. Reconnecting in ${delay / 1000}s... (${reconnectAttempts}/${MAX_RECONNECT})`));
|
|
294
|
+
setTimeout(doConnect, delay);
|
|
295
|
+
} else {
|
|
296
|
+
console.error(chalk.red(' Max reconnection attempts reached. Exiting.'));
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
ws.on('error', (err) => {
|
|
302
|
+
if (err.code === 'ECONNREFUSED') {
|
|
303
|
+
console.error(chalk.red(` Cannot reach ${serverUrl}. Is the server running?`));
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
doConnect();
|
|
309
|
+
|
|
310
|
+
process.on('SIGINT', () => {
|
|
311
|
+
console.log(chalk.dim('\n Disconnecting...'));
|
|
312
|
+
process.exit(0);
|
|
313
|
+
});
|
|
314
|
+
}
|
package/src/launch.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import open from 'open';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { readConfig } from './config.js';
|
|
4
|
+
import { getToken, validateToken } from './auth.js';
|
|
5
|
+
import { startServer } from './server.js';
|
|
6
|
+
|
|
7
|
+
export async function openHosted() {
|
|
8
|
+
const token = getToken();
|
|
9
|
+
|
|
10
|
+
if (!token) {
|
|
11
|
+
console.log(chalk.yellow('\n No account found. Run `upfyn-code login` first.\n'));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
console.log(chalk.dim('\n Validating session...'));
|
|
16
|
+
const user = await validateToken();
|
|
17
|
+
|
|
18
|
+
if (!user) {
|
|
19
|
+
console.log(chalk.yellow(' Session expired. Run `upfyn-code login` to re-authenticate.\n'));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const config = readConfig();
|
|
24
|
+
const url = `${config.serverUrl}?token=${encodeURIComponent(token)}`;
|
|
25
|
+
|
|
26
|
+
const name = user.first_name || user.username;
|
|
27
|
+
console.log(chalk.green(` Welcome back, ${chalk.bold(name)}!`));
|
|
28
|
+
console.log(chalk.dim(' Opening Upfyn-Code in your browser...\n'));
|
|
29
|
+
|
|
30
|
+
await open(url);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function startLocal() {
|
|
34
|
+
const token = getToken();
|
|
35
|
+
|
|
36
|
+
if (!token) {
|
|
37
|
+
console.log(chalk.yellow('\n No account found. Run `upfyn-code login` first.\n'));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(chalk.dim('\n Validating session...'));
|
|
42
|
+
const user = await validateToken();
|
|
43
|
+
|
|
44
|
+
if (!user) {
|
|
45
|
+
console.log(chalk.yellow(' Session expired. Run `upfyn-code login` to re-authenticate.\n'));
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const config = readConfig();
|
|
50
|
+
const name = user.first_name || user.username;
|
|
51
|
+
console.log(chalk.green(` Welcome back, ${chalk.bold(name)}!`));
|
|
52
|
+
|
|
53
|
+
await startServer(config.localPort, config.serverUrl, token);
|
|
54
|
+
}
|
package/src/mcp.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { readConfig } from './config.js';
|
|
3
|
+
import { getToken, validateToken } from './auth.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Print MCP configuration for Claude / Cursor / other AI tools.
|
|
7
|
+
* Optionally accepts --server and --key overrides.
|
|
8
|
+
*/
|
|
9
|
+
export async function mcp(options = {}) {
|
|
10
|
+
const config = readConfig();
|
|
11
|
+
const serverUrl = options.server || config.serverUrl;
|
|
12
|
+
let relayKey = options.key;
|
|
13
|
+
|
|
14
|
+
// If no key provided, fetch one using auth token
|
|
15
|
+
if (!relayKey) {
|
|
16
|
+
const token = getToken();
|
|
17
|
+
if (!token) {
|
|
18
|
+
console.log(chalk.yellow('\n No account found. Run `upfyn-code login` first.\n'));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const user = await validateToken();
|
|
23
|
+
if (!user) {
|
|
24
|
+
console.log(chalk.yellow('\n Session expired. Run `upfyn-code login` to re-authenticate.\n'));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const res = await fetch(`${serverUrl}/api/auth/connect-token`, {
|
|
30
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok) throw new Error('Failed to get connect token');
|
|
33
|
+
const data = await res.json();
|
|
34
|
+
relayKey = data.token;
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.log(chalk.red('\n Could not get connection token. Check your network and try again.\n'));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const mcpConfig = {
|
|
42
|
+
mcpServers: {
|
|
43
|
+
'upfyn-code': {
|
|
44
|
+
url: `${serverUrl}/mcp`,
|
|
45
|
+
headers: {
|
|
46
|
+
Authorization: `Bearer ${relayKey}`,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
console.log(chalk.bold('\n Upfyn-Code — MCP Integration\n'));
|
|
53
|
+
console.log(chalk.dim(' Add this to your Claude / Cursor MCP settings:\n'));
|
|
54
|
+
console.log(chalk.white(JSON.stringify(mcpConfig, null, 2)));
|
|
55
|
+
console.log('');
|
|
56
|
+
console.log(chalk.dim(' The JSON above contains your MCP URL and authorization header.\n'));
|
|
57
|
+
}
|
package/src/server.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { fileURLToPath } from 'url';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import express from 'express';
|
|
5
|
+
import { createProxyMiddleware } from 'http-proxy-middleware';
|
|
6
|
+
import open from 'open';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const DIST_DIR = join(__dirname, '..', 'dist');
|
|
11
|
+
|
|
12
|
+
export async function startServer(port, serverUrl, token) {
|
|
13
|
+
if (!existsSync(DIST_DIR)) {
|
|
14
|
+
console.log(chalk.red('\n Error: No built frontend found at dist/.'));
|
|
15
|
+
console.log(chalk.dim(' Run the build-frontend script first, or use the default mode (no --local flag).\n'));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const app = express();
|
|
20
|
+
|
|
21
|
+
// Proxy API calls to the remote Vercel backend
|
|
22
|
+
app.use('/api', createProxyMiddleware({
|
|
23
|
+
target: serverUrl,
|
|
24
|
+
changeOrigin: true,
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${token}`,
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
// Serve the built React frontend
|
|
31
|
+
app.use(express.static(DIST_DIR));
|
|
32
|
+
|
|
33
|
+
// SPA fallback — serve index.html for all non-API, non-static routes
|
|
34
|
+
app.get('*', (req, res) => {
|
|
35
|
+
res.sendFile(join(DIST_DIR, 'index.html'));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
const server = app.listen(port, async () => {
|
|
40
|
+
const url = `http://localhost:${port}?token=${encodeURIComponent(token)}`;
|
|
41
|
+
console.log(chalk.green(`\n Local server running at ${chalk.bold(`http://localhost:${port}`)}`));
|
|
42
|
+
console.log(chalk.dim(' API proxy active. Press Ctrl+C to stop.\n'));
|
|
43
|
+
await open(url);
|
|
44
|
+
resolve(server);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const shutdown = () => {
|
|
48
|
+
console.log(chalk.dim('\n Shutting down...'));
|
|
49
|
+
server.close(() => process.exit(0));
|
|
50
|
+
};
|
|
51
|
+
process.on('SIGINT', shutdown);
|
|
52
|
+
process.on('SIGTERM', shutdown);
|
|
53
|
+
});
|
|
54
|
+
}
|