upfynai-code 2.4.0 → 2.5.0
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/client/dist/assets/AppContent-CRld2UWX.js +513 -0
- package/client/dist/assets/CanvasPanel-CB4sweQq.js +34 -0
- package/client/dist/assets/CanvasPanel-WhZulBJw.css +1 -0
- package/client/dist/assets/DashboardPanel-BXaA-b9z.js +1 -0
- package/client/dist/assets/LoginModal-BwkvjfPR.js +19 -0
- package/client/dist/assets/{Onboarding-CtIoXiTp.js → Onboarding-2A_5fPxy.js} +1 -1
- package/client/dist/assets/{SetupForm-B4p8im5O.js → SetupForm-CH5EA5W0.js} +1 -1
- package/client/dist/assets/WorkflowsPanel-CO5g5yGG.js +1 -0
- package/client/dist/assets/{ar-SA-G6X2FPQ2-2gfmdvHk.js → ar-SA-G6X2FPQ2-DoJuo98H.js} +2 -2
- package/client/dist/assets/{arc-DCZSHhoJ.js → arc-B0wBaTeh.js} +1 -1
- package/client/dist/assets/az-AZ-76LH7QW2-xdrt1Z13.js +1 -0
- package/client/dist/assets/{bg-BG-XCXSNQG7-D6__XtOK.js → bg-BG-XCXSNQG7-D8NAiF6Y.js} +2 -2
- package/client/dist/assets/{blockDiagram-38ab4fdb-Cfbaeyp6.js → blockDiagram-38ab4fdb-DSnyKzK4.js} +2 -2
- package/client/dist/assets/{bn-BD-2XOGV67Q-DHNJw3OG.js → bn-BD-2XOGV67Q-B0qWv8_J.js} +2 -2
- package/client/dist/assets/{c4Diagram-3d4e48cf-BBCnjOTy.js → c4Diagram-3d4e48cf-DoZJ13XA.js} +2 -2
- package/client/dist/assets/{ca-ES-6MX7JW3Y-r5g4o3zQ.js → ca-ES-6MX7JW3Y-RgLhfbZZ.js} +3 -3
- package/client/dist/assets/channel-BmO6nY0W.js +1 -0
- package/client/dist/assets/classDiagram-70f12bd4-GNyDrRCk.js +2 -0
- package/client/dist/assets/classDiagram-v2-f2320105-CxdGhHm2.js +2 -0
- package/client/dist/assets/clone-xuHMqFoD.js +1 -0
- package/client/dist/assets/{createText-2e5e7dd3-B8jCDmF_.js → createText-2e5e7dd3-DiPywQOa.js} +1 -1
- package/client/dist/assets/{cs-CZ-2BRQDIVT-p08jRLRC.js → cs-CZ-2BRQDIVT-BAjmnuoC.js} +2 -2
- package/client/dist/assets/{da-DK-5WZEPLOC-CnhOImFf.js → da-DK-5WZEPLOC-JxKVGt8o.js} +2 -2
- package/client/dist/assets/{de-DE-XR44H4JA-BunSXZ-Y.js → de-DE-XR44H4JA-CrnRlt4z.js} +2 -2
- package/client/dist/assets/{edges-e0da2a9e-CGBBhG8k.js → edges-e0da2a9e-DDsXzXLJ.js} +1 -1
- package/client/dist/assets/{el-GR-BZB4AONW-D4wv1oIz.js → el-GR-BZB4AONW-DQd8iogq.js} +2 -2
- package/client/dist/assets/{erDiagram-9861fffd-CYaF3q1I.js → erDiagram-9861fffd-CBiCC4rl.js} +2 -2
- package/client/dist/assets/{es-ES-U4NZUMDT-CGeTKXgd.js → es-ES-U4NZUMDT-vvUblc5i.js} +2 -2
- package/client/dist/assets/{eu-ES-A7QVB2H4-Cayx1TxR.js → eu-ES-A7QVB2H4-De4NNCc1.js} +2 -2
- package/client/dist/assets/{fa-IR-HGAKTJCU-CmUg8pmw.js → fa-IR-HGAKTJCU-DFBXqIqq.js} +2 -2
- package/client/dist/assets/{fi-FI-Z5N7JZ37-xvHcPhsU.js → fi-FI-Z5N7JZ37-DV9zESPg.js} +2 -2
- package/client/dist/assets/{flowDb-956e92f1-C-_LFz70.js → flowDb-956e92f1-BhdSHbdO.js} +1 -1
- package/client/dist/assets/{flowDiagram-66a62f08-C1sHdSjn.js → flowDiagram-66a62f08-M-fp1_Ie.js} +2 -2
- package/client/dist/assets/flowDiagram-v2-96b9c2cf-C5eiN8Pg.js +1 -0
- package/client/dist/assets/{flowchart-elk-definition-4a651766-CNGfpudb.js → flowchart-elk-definition-4a651766-Bp0SonQx.js} +2 -2
- package/client/dist/assets/{fr-FR-RHASNOE6-DBoHEcNj.js → fr-FR-RHASNOE6-CKTMXuGk.js} +2 -2
- package/client/dist/assets/ganttDiagram-c361ad54-iA737GUS.js +257 -0
- package/client/dist/assets/{gitGraphDiagram-72cf32ee-DojCDvlS.js → gitGraphDiagram-72cf32ee-BX-wj-PV.js} +2 -2
- package/client/dist/assets/{gl-ES-HMX3MZ6V-p6hrn2cN.js → gl-ES-HMX3MZ6V-Cdiqq4jY.js} +2 -2
- package/client/dist/assets/{graph-DXM7lcy1.js → graph-Rxkx3sEa.js} +1 -1
- package/client/dist/assets/{he-IL-6SHJWFNN-y2jEX6-0.js → he-IL-6SHJWFNN-gYmR5_KT.js} +2 -2
- package/client/dist/assets/{hi-IN-IWLTKZ5I-99pNfyWr.js → hi-IN-IWLTKZ5I-pyqK94AR.js} +2 -2
- package/client/dist/assets/{hu-HU-A5ZG7DT2-hygceGMS.js → hu-HU-A5ZG7DT2-DpacJgJy.js} +2 -2
- package/client/dist/assets/{id-ID-SAP4L64H-CyIqi1hv.js → id-ID-SAP4L64H-CAvIX-mj.js} +2 -2
- package/client/dist/assets/{index-3862675e-4idOQN2N.js → index-3862675e-BX3Fpn6V.js} +1 -1
- package/client/dist/assets/{index-BHZfFT_V.js → index-BBlwbHq_.js} +4 -4
- package/client/dist/assets/{index-BGmwbRlb.js → index-ClfzLIqY.js} +6 -6
- package/client/dist/assets/index-Td4UdtLF.css +1 -0
- package/client/dist/assets/{infoDiagram-f8f76790-CFLrHqtc.js → infoDiagram-f8f76790-Ckv8imiv.js} +2 -2
- package/client/dist/assets/{it-IT-JPQ66NNP-DzVvVdQI.js → it-IT-JPQ66NNP-BtpNRSce.js} +2 -2
- package/client/dist/assets/{ja-JP-DBVTYXUO-BI4fPexV.js → ja-JP-DBVTYXUO-CwJRyY6M.js} +2 -2
- package/client/dist/assets/{journeyDiagram-49397b02-C3CFDo8z.js → journeyDiagram-49397b02-DWWZssji.js} +2 -2
- package/client/dist/assets/kaa-6HZHGXH3-DIWQEb4A.js +1 -0
- package/client/dist/assets/{kab-KAB-ZGHBKWFO-DBI_ri48.js → kab-KAB-ZGHBKWFO-DjGbqhUg.js} +2 -2
- package/client/dist/assets/kk-KZ-P5N5QNE5-B_VzJdWf.js +1 -0
- package/client/dist/assets/{km-KH-HSX4SM5Z-DOMFSres.js → km-KH-HSX4SM5Z-DUD5mi0o.js} +2 -2
- package/client/dist/assets/{ko-KR-MTYHY66A-tb08hXzd.js → ko-KR-MTYHY66A--sDB10db.js} +3 -3
- package/client/dist/assets/{ku-TR-6OUDTVRD-DlIQCCY4.js → ku-TR-6OUDTVRD-CKvKrkcX.js} +2 -2
- package/client/dist/assets/{layout-B_11mCXA.js → layout-CkB7sSeq.js} +1 -1
- package/client/dist/assets/{line-B-qmK_vI.js → line-DC7MA9qY.js} +1 -1
- package/client/dist/assets/{linear-Ph6uuYcX.js → linear-C1lBBthf.js} +1 -1
- package/client/dist/assets/{lt-LT-XHIRWOB4--qWy24_Z.js → lt-LT-XHIRWOB4-MSZf7xYG.js} +2 -2
- package/client/dist/assets/{lv-LV-5QDEKY6T-Bnd_1GDb.js → lv-LV-5QDEKY6T-C-gvvmBB.js} +2 -2
- package/client/dist/assets/{mindmap-definition-fc14e90a-Do79tIc0.js → mindmap-definition-fc14e90a-B3O7hztq.js} +2 -2
- package/client/dist/assets/{mr-IN-CRQNXWMA-BsV6HaD9.js → mr-IN-CRQNXWMA-XHtBUWQH.js} +2 -2
- package/client/dist/assets/my-MM-5M5IBNSE-D9eD2edL.js +1 -0
- package/client/dist/assets/{nb-NO-T6EIAALU-Cvf9FdSF.js → nb-NO-T6EIAALU-BlImC6gp.js} +3 -3
- package/client/dist/assets/{nl-NL-IS3SIHDZ-DA1yqpXw.js → nl-NL-IS3SIHDZ-CPFhnaSP.js} +2 -2
- package/client/dist/assets/{nn-NO-6E72VCQL-89lm3vku.js → nn-NO-6E72VCQL-BMvoJSKQ.js} +2 -2
- package/client/dist/assets/{oc-FR-POXYY2M6-BsrjTJQh.js → oc-FR-POXYY2M6-Buye63LS.js} +2 -2
- package/client/dist/assets/{pa-IN-N4M65BXN-CczefYaj.js → pa-IN-N4M65BXN-D9uQ3niy.js} +2 -2
- package/client/dist/assets/{percentages-BXMCSKIN-Be6p9phi.js → percentages-BXMCSKIN-BzXIakGM.js} +7 -7
- package/client/dist/assets/{pieDiagram-8a3498a8-CfblQHdm.js → pieDiagram-8a3498a8-BU38mzx-.js} +3 -3
- package/client/dist/assets/{pl-PL-T2D74RX3-DdhH-zcK.js → pl-PL-T2D74RX3-BqM4xdcg.js} +2 -2
- package/client/dist/assets/{pt-BR-5N22H2LF-gpwlheL6.js → pt-BR-5N22H2LF-rAjrxGyI.js} +2 -2
- package/client/dist/assets/{pt-PT-UZXXM6DQ-Cs87vICi.js → pt-PT-UZXXM6DQ-DXsqcwLt.js} +2 -2
- package/client/dist/assets/{quadrantDiagram-120e2f19-CRMSamSP.js → quadrantDiagram-120e2f19-HhK4H1WU.js} +2 -2
- package/client/dist/assets/{requirementDiagram-deff3bca-D3LBN016.js → requirementDiagram-deff3bca-aDrcyj-A.js} +2 -2
- package/client/dist/assets/{ro-RO-JPDTUUEW-CWTSJ1Dt.js → ro-RO-JPDTUUEW-D_F9UKer.js} +2 -2
- package/client/dist/assets/{ru-RU-B4JR7IUQ-Bq7aN2ep.js → ru-RU-B4JR7IUQ-MirqN29p.js} +2 -2
- package/client/dist/assets/sankeyDiagram-04a897e0-C6ij7qbQ.js +8 -0
- package/client/dist/assets/{sequenceDiagram-704730f1-BRYXVDGX.js → sequenceDiagram-704730f1-C0EKO3th.js} +2 -2
- package/client/dist/assets/si-LK-N5RQ5JYF-DyZC3mkC.js +1 -0
- package/client/dist/assets/{sk-SK-C5VTKIMK-ByjKQzUb.js → sk-SK-C5VTKIMK-D-ksz-WY.js} +2 -2
- package/client/dist/assets/{sl-SI-NN7IZMDC-B8WCyMBU.js → sl-SI-NN7IZMDC-CknuYoQ1.js} +2 -2
- package/client/dist/assets/stateDiagram-587899a1-CYoq2VjL.js +1 -0
- package/client/dist/assets/stateDiagram-v2-d93cdb3a-C5lbp5px.js +1 -0
- package/client/dist/assets/{styles-6aaf32cf-Dr-lfIOW.js → styles-6aaf32cf-Dkfsk8gt.js} +1 -1
- package/client/dist/assets/{styles-9a916d00-DS4wRpL7.js → styles-9a916d00-CMYqtcEN.js} +1 -1
- package/client/dist/assets/{styles-c10674c1-nKRF6NrH.js → styles-c10674c1-Bp-5OlRU.js} +1 -1
- package/client/dist/assets/{subset-shared.chunk-KT79s7KG.js → subset-shared.chunk-kfIB1Zam.js} +3 -3
- package/client/dist/assets/subset-worker.chunk-DwQBgc4z.js +1 -0
- package/client/dist/assets/{sv-SE-XGPEYMSR-BiIPUVbv.js → sv-SE-XGPEYMSR-DwN13se1.js} +2 -2
- package/client/dist/assets/{svgDrawCommon-08f97a94-C3uP9PYr.js → svgDrawCommon-08f97a94-CEgCMqs4.js} +1 -1
- package/client/dist/assets/{ta-IN-2NMHFXQM-Cidadso2.js → ta-IN-2NMHFXQM-ejDfFhwa.js} +2 -2
- package/client/dist/assets/th-TH-HPSO5L25-Bqc90ZNn.js +2 -0
- package/client/dist/assets/{timeline-definition-85554ec2-BSsLsIgF.js → timeline-definition-85554ec2-BmGdKqG0.js} +2 -2
- package/client/dist/assets/{tr-TR-DEFEU3FU-DaFcI-KL.js → tr-TR-DEFEU3FU-CJvlPbcW.js} +2 -2
- package/client/dist/assets/{uk-UA-QMV73CPH-DkBW36St.js → uk-UA-QMV73CPH-D26-cbWL.js} +3 -3
- package/client/dist/assets/vendor-codemirror-D_s0aGBu.js +35 -0
- package/client/dist/assets/{vendor-icons-Dh9m_Ydt.js → vendor-icons-aNdOvTr_.js} +159 -119
- package/client/dist/assets/{vi-VN-M7AON7JQ-KrtfxOzl.js → vi-VN-M7AON7JQ-MbqIIwYM.js} +2 -2
- package/client/dist/assets/{xychartDiagram-e933f94c-CgNgZ4pp.js → xychartDiagram-e933f94c-gfcTauxU.js} +2 -2
- package/client/dist/assets/{zh-CN-LNUGB5OW-BQu12RoD.js → zh-CN-LNUGB5OW-BZSmhUdL.js} +3 -3
- package/client/dist/assets/zh-HK-E62DVLB3-BJqejpiX.js +1 -0
- package/client/dist/assets/{zh-TW-RAJ6MFWO-ffJWgVxn.js → zh-TW-RAJ6MFWO-BBXtV-Uz.js} +2 -2
- package/client/dist/index.html +3 -3
- package/package.json +5 -2
- package/server/cli.js +64 -5
- package/server/constants/config.js +29 -3
- package/server/database/auth.db +0 -0
- package/server/database/db.js +203 -1
- package/server/index.js +348 -48
- package/server/mcp-server.js +2 -1
- package/server/middleware/auth.js +20 -9
- package/server/projects.js +95 -202
- package/server/relay-client.js +205 -11
- package/server/routes/auth.js +6 -0
- package/server/routes/commands.js +1 -1
- package/server/routes/dashboard.js +52 -0
- package/server/routes/projects.js +38 -35
- package/server/routes/voice.js +198 -0
- package/server/routes/webhooks.js +166 -0
- package/server/routes/workflows.js +118 -0
- package/server/services/whisperService.js +84 -0
- package/server/services/workflowScheduler.js +186 -0
- package/client/dist/assets/AppContent-DTZ2FbvM.js +0 -513
- package/client/dist/assets/CanvasPanel-DlTW6Jh6.js +0 -6
- package/client/dist/assets/CanvasPanel-q4HEqNtV.css +0 -1
- package/client/dist/assets/LoginModal-CWoFm0au.js +0 -19
- package/client/dist/assets/az-AZ-76LH7QW2-CDdeucRZ.js +0 -1
- package/client/dist/assets/channel-O3ovC0x9.js +0 -1
- package/client/dist/assets/classDiagram-70f12bd4-D0lhAcxU.js +0 -2
- package/client/dist/assets/classDiagram-v2-f2320105-BuwUsF3F.js +0 -2
- package/client/dist/assets/clone-BG9u7vLi.js +0 -1
- package/client/dist/assets/flowDiagram-v2-96b9c2cf-Cd0Iascd.js +0 -1
- package/client/dist/assets/ganttDiagram-c361ad54-B8HJQqjt.js +0 -257
- package/client/dist/assets/index-B8wwD_Xo.css +0 -1
- package/client/dist/assets/kaa-6HZHGXH3-fwOleoQB.js +0 -1
- package/client/dist/assets/kk-KZ-P5N5QNE5-zpl7uvyF.js +0 -1
- package/client/dist/assets/my-MM-5M5IBNSE-kZQURVIi.js +0 -1
- package/client/dist/assets/sankeyDiagram-04a897e0-CsFqOQZN.js +0 -8
- package/client/dist/assets/si-LK-N5RQ5JYF-BBjcNYQh.js +0 -1
- package/client/dist/assets/stateDiagram-587899a1-BHoy9LtD.js +0 -1
- package/client/dist/assets/stateDiagram-v2-d93cdb3a-BvMUA6bS.js +0 -1
- package/client/dist/assets/subset-worker.chunk-BMx1eyv3.js +0 -1
- package/client/dist/assets/th-TH-HPSO5L25-CFNnJwSv.js +0 -2
- package/client/dist/assets/vendor-codemirror-langs-BH1ZcKHY.js +0 -20
- package/client/dist/assets/vendor-codemirror-rix45NST.js +0 -16
- package/client/dist/assets/zh-HK-E62DVLB3-zx9CvERq.js +0 -1
package/server/projects.js
CHANGED
|
@@ -387,8 +387,8 @@ async function extractProjectDirectory(projectName) {
|
|
|
387
387
|
}
|
|
388
388
|
|
|
389
389
|
async function getProjects(progressCallback = null) {
|
|
390
|
-
// Wrap with a
|
|
391
|
-
const timeoutMs =
|
|
390
|
+
// Wrap with a timeout to prevent hanging on slow filesystems
|
|
391
|
+
const timeoutMs = 15000;
|
|
392
392
|
const result = await Promise.race([
|
|
393
393
|
_getProjectsImpl(progressCallback),
|
|
394
394
|
new Promise((_, reject) => setTimeout(() => reject(new Error('Projects scan timed out')), timeoutMs))
|
|
@@ -403,220 +403,103 @@ async function _getProjectsImpl(progressCallback = null) {
|
|
|
403
403
|
const claudeDir = path.join(os.homedir(), '.claude', 'projects');
|
|
404
404
|
const config = await loadProjectConfig();
|
|
405
405
|
const projects = [];
|
|
406
|
-
const existingProjects = new Set();
|
|
407
406
|
const codexSessionsIndexRef = { sessionsByProject: null };
|
|
408
|
-
|
|
407
|
+
|
|
408
|
+
// Only load projects that were explicitly added by the user (manuallyAdded).
|
|
409
|
+
// No auto-scanning of ~/.claude/projects/ — the user adds projects via the UI.
|
|
410
|
+
const manualEntries = Object.entries(config).filter(([, cfg]) => cfg.manuallyAdded);
|
|
411
|
+
const totalProjects = manualEntries.length;
|
|
409
412
|
let processedProjects = 0;
|
|
410
|
-
let directories = [];
|
|
411
413
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
await fs.access(claudeDir);
|
|
415
|
-
|
|
416
|
-
// First, get existing Claude projects from the file system
|
|
417
|
-
const entries = await fs.readdir(claudeDir, { withFileTypes: true });
|
|
418
|
-
directories = entries.filter(e => e.isDirectory());
|
|
419
|
-
|
|
420
|
-
// Build set of existing project names for later
|
|
421
|
-
directories.forEach(e => existingProjects.add(e.name));
|
|
422
|
-
|
|
423
|
-
// Count manual projects not already in directories
|
|
424
|
-
const manualProjectsCount = Object.entries(config)
|
|
425
|
-
.filter(([name, cfg]) => cfg.manuallyAdded && !existingProjects.has(name))
|
|
426
|
-
.length;
|
|
427
|
-
|
|
428
|
-
totalProjects = directories.length + manualProjectsCount;
|
|
429
|
-
|
|
430
|
-
for (const entry of directories) {
|
|
431
|
-
processedProjects++;
|
|
432
|
-
|
|
433
|
-
// Emit progress
|
|
434
|
-
if (progressCallback) {
|
|
435
|
-
progressCallback({
|
|
436
|
-
phase: 'loading',
|
|
437
|
-
current: processedProjects,
|
|
438
|
-
total: totalProjects,
|
|
439
|
-
currentProject: entry.name
|
|
440
|
-
});
|
|
441
|
-
}
|
|
414
|
+
for (const [projectName, projectConfig] of manualEntries) {
|
|
415
|
+
processedProjects++;
|
|
442
416
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
const project = {
|
|
452
|
-
name: entry.name,
|
|
453
|
-
path: actualProjectDir,
|
|
454
|
-
displayName: customName || autoDisplayName,
|
|
455
|
-
fullPath: fullPath,
|
|
456
|
-
isCustomName: !!customName,
|
|
457
|
-
sessions: [],
|
|
458
|
-
sessionMeta: {
|
|
459
|
-
hasMore: false,
|
|
460
|
-
total: 0
|
|
461
|
-
}
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
// Try to get sessions for this project (just first 5 for performance)
|
|
465
|
-
try {
|
|
466
|
-
const sessionResult = await getSessions(entry.name, 5, 0);
|
|
467
|
-
project.sessions = sessionResult.sessions || [];
|
|
468
|
-
project.sessionMeta = {
|
|
469
|
-
hasMore: sessionResult.hasMore,
|
|
470
|
-
total: sessionResult.total
|
|
471
|
-
};
|
|
472
|
-
} catch (e) {
|
|
473
|
-
console.warn(`Could not load sessions for project ${entry.name}:`, e.message);
|
|
474
|
-
project.sessionMeta = {
|
|
475
|
-
hasMore: false,
|
|
476
|
-
total: 0
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Also fetch Cursor sessions for this project
|
|
481
|
-
try {
|
|
482
|
-
project.cursorSessions = await getCursorSessions(actualProjectDir);
|
|
483
|
-
} catch (e) {
|
|
484
|
-
console.warn(`Could not load Cursor sessions for project ${entry.name}:`, e.message);
|
|
485
|
-
project.cursorSessions = [];
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Also fetch Codex sessions for this project
|
|
489
|
-
try {
|
|
490
|
-
project.codexSessions = await getCodexSessions(actualProjectDir, {
|
|
491
|
-
indexRef: codexSessionsIndexRef,
|
|
492
|
-
});
|
|
493
|
-
} catch (e) {
|
|
494
|
-
console.warn(`Could not load Codex sessions for project ${entry.name}:`, e.message);
|
|
495
|
-
project.codexSessions = [];
|
|
496
|
-
}
|
|
417
|
+
if (progressCallback) {
|
|
418
|
+
progressCallback({
|
|
419
|
+
phase: 'loading',
|
|
420
|
+
current: processedProjects,
|
|
421
|
+
total: totalProjects,
|
|
422
|
+
currentProject: projectName
|
|
423
|
+
});
|
|
424
|
+
}
|
|
497
425
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
const taskMasterResult = await detectTaskMasterFolder(actualProjectDir);
|
|
501
|
-
project.taskmaster = {
|
|
502
|
-
hasTaskmaster: taskMasterResult.hasTaskmaster,
|
|
503
|
-
hasEssentialFiles: taskMasterResult.hasEssentialFiles,
|
|
504
|
-
metadata: taskMasterResult.metadata,
|
|
505
|
-
status: taskMasterResult.hasTaskmaster && taskMasterResult.hasEssentialFiles ? 'configured' : 'not-configured'
|
|
506
|
-
};
|
|
507
|
-
} catch (e) {
|
|
508
|
-
console.warn(`Could not detect TaskMaster for project ${entry.name}:`, e.message);
|
|
509
|
-
project.taskmaster = {
|
|
510
|
-
hasTaskmaster: false,
|
|
511
|
-
hasEssentialFiles: false,
|
|
512
|
-
metadata: null,
|
|
513
|
-
status: 'error'
|
|
514
|
-
};
|
|
515
|
-
}
|
|
426
|
+
// Use the original path if available, otherwise extract from potential sessions
|
|
427
|
+
let actualProjectDir = projectConfig.originalPath;
|
|
516
428
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}
|
|
524
|
-
// Calculate total for manual projects only (no directories exist)
|
|
525
|
-
totalProjects = Object.entries(config)
|
|
526
|
-
.filter(([name, cfg]) => cfg.manuallyAdded)
|
|
527
|
-
.length;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// Add manually configured projects that don't exist as folders yet
|
|
531
|
-
for (const [projectName, projectConfig] of Object.entries(config)) {
|
|
532
|
-
if (!existingProjects.has(projectName) && projectConfig.manuallyAdded) {
|
|
533
|
-
processedProjects++;
|
|
534
|
-
|
|
535
|
-
// Emit progress for manual projects
|
|
536
|
-
if (progressCallback) {
|
|
537
|
-
progressCallback({
|
|
538
|
-
phase: 'loading',
|
|
539
|
-
current: processedProjects,
|
|
540
|
-
total: totalProjects,
|
|
541
|
-
currentProject: projectName
|
|
542
|
-
});
|
|
429
|
+
if (!actualProjectDir) {
|
|
430
|
+
try {
|
|
431
|
+
actualProjectDir = await extractProjectDirectory(projectName);
|
|
432
|
+
} catch (error) {
|
|
433
|
+
// Fall back to decoded project name
|
|
434
|
+
actualProjectDir = projectName.replace(/-/g, '/');
|
|
543
435
|
}
|
|
436
|
+
}
|
|
544
437
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
total: 0
|
|
568
|
-
},
|
|
569
|
-
cursorSessions: [],
|
|
570
|
-
codexSessions: []
|
|
438
|
+
const project = {
|
|
439
|
+
name: projectName,
|
|
440
|
+
path: actualProjectDir,
|
|
441
|
+
displayName: projectConfig.displayName || await generateDisplayName(projectName, actualProjectDir),
|
|
442
|
+
fullPath: actualProjectDir,
|
|
443
|
+
isCustomName: !!projectConfig.displayName,
|
|
444
|
+
isManuallyAdded: true,
|
|
445
|
+
sessions: [],
|
|
446
|
+
sessionMeta: { hasMore: false, total: 0 },
|
|
447
|
+
cursorSessions: [],
|
|
448
|
+
codexSessions: []
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// Check if a Claude project folder exists for this project (for session history)
|
|
452
|
+
const projectDir = path.join(claudeDir, projectName);
|
|
453
|
+
try {
|
|
454
|
+
await fs.access(projectDir);
|
|
455
|
+
const sessionResult = await getSessions(projectName, 5, 0);
|
|
456
|
+
project.sessions = sessionResult.sessions || [];
|
|
457
|
+
project.sessionMeta = {
|
|
458
|
+
hasMore: sessionResult.hasMore,
|
|
459
|
+
total: sessionResult.total
|
|
571
460
|
};
|
|
461
|
+
} catch (e) {
|
|
462
|
+
// No Claude sessions — that's fine
|
|
463
|
+
}
|
|
572
464
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
465
|
+
// Fetch Cursor sessions
|
|
466
|
+
try {
|
|
467
|
+
project.cursorSessions = await getCursorSessions(actualProjectDir);
|
|
468
|
+
} catch (e) {
|
|
469
|
+
// No Cursor sessions
|
|
470
|
+
}
|
|
579
471
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
472
|
+
// Fetch Codex sessions
|
|
473
|
+
try {
|
|
474
|
+
project.codexSessions = await getCodexSessions(actualProjectDir, {
|
|
475
|
+
indexRef: codexSessionsIndexRef,
|
|
476
|
+
});
|
|
477
|
+
} catch (e) {
|
|
478
|
+
// No Codex sessions
|
|
479
|
+
}
|
|
588
480
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
}
|
|
606
|
-
console.warn(`TaskMaster detection failed for manual project ${projectName}:`, error.message);
|
|
607
|
-
project.taskmaster = {
|
|
608
|
-
status: 'error',
|
|
609
|
-
hasTaskmaster: false,
|
|
610
|
-
hasEssentialFiles: false,
|
|
611
|
-
error: error.message
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
projects.push(project);
|
|
481
|
+
// TaskMaster detection
|
|
482
|
+
try {
|
|
483
|
+
const taskMasterResult = await detectTaskMasterFolder(actualProjectDir);
|
|
484
|
+
project.taskmaster = {
|
|
485
|
+
status: taskMasterResult.hasTaskmaster && taskMasterResult.hasEssentialFiles
|
|
486
|
+
? 'configured' : 'not-configured',
|
|
487
|
+
hasTaskmaster: taskMasterResult.hasTaskmaster,
|
|
488
|
+
hasEssentialFiles: taskMasterResult.hasEssentialFiles,
|
|
489
|
+
metadata: taskMasterResult.metadata
|
|
490
|
+
};
|
|
491
|
+
} catch (error) {
|
|
492
|
+
project.taskmaster = {
|
|
493
|
+
status: 'error',
|
|
494
|
+
hasTaskmaster: false,
|
|
495
|
+
hasEssentialFiles: false,
|
|
496
|
+
error: error.message
|
|
497
|
+
};
|
|
616
498
|
}
|
|
499
|
+
|
|
500
|
+
projects.push(project);
|
|
617
501
|
}
|
|
618
502
|
|
|
619
|
-
// Emit completion after all projects (including manual) are processed
|
|
620
503
|
if (progressCallback) {
|
|
621
504
|
progressCallback({
|
|
622
505
|
phase: 'complete',
|
|
@@ -1225,7 +1108,17 @@ async function addProjectManually(projectPath, displayName = null) {
|
|
|
1225
1108
|
const projectDir = path.join(os.homedir(), '.claude', 'projects', projectName);
|
|
1226
1109
|
|
|
1227
1110
|
if (config[projectName]) {
|
|
1228
|
-
|
|
1111
|
+
// Project already exists — return it instead of erroring
|
|
1112
|
+
return {
|
|
1113
|
+
name: projectName,
|
|
1114
|
+
path: absolutePath,
|
|
1115
|
+
fullPath: absolutePath,
|
|
1116
|
+
displayName: config[projectName].displayName || await generateDisplayName(projectName, absolutePath),
|
|
1117
|
+
isManuallyAdded: true,
|
|
1118
|
+
alreadyExists: true,
|
|
1119
|
+
sessions: [],
|
|
1120
|
+
cursorSessions: []
|
|
1121
|
+
};
|
|
1229
1122
|
}
|
|
1230
1123
|
|
|
1231
1124
|
// Allow adding projects even if the directory exists - this enables tracking
|
package/server/relay-client.js
CHANGED
|
@@ -14,7 +14,7 @@ import WebSocket from 'ws';
|
|
|
14
14
|
import os from 'os';
|
|
15
15
|
import fs from 'fs';
|
|
16
16
|
import path from 'path';
|
|
17
|
-
import { spawn } from 'child_process';
|
|
17
|
+
import { spawn, execSync } from 'child_process';
|
|
18
18
|
import { promises as fsPromises } from 'fs';
|
|
19
19
|
import crypto from 'crypto';
|
|
20
20
|
import {
|
|
@@ -137,6 +137,100 @@ async function handleRelayCommand(data, ws) {
|
|
|
137
137
|
break;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
case 'codex-query': {
|
|
141
|
+
const { command, options } = data;
|
|
142
|
+
logRelayEvent('>', `Codex query: ${command?.slice(0, 60)}...`, 'cyan');
|
|
143
|
+
|
|
144
|
+
const codexArgs = ['--quiet'];
|
|
145
|
+
if (options?.projectPath || options?.cwd) {
|
|
146
|
+
codexArgs.push('--cwd', options.projectPath || options.cwd);
|
|
147
|
+
}
|
|
148
|
+
if (options?.model) codexArgs.push('--model', options.model);
|
|
149
|
+
|
|
150
|
+
const codexProc = spawn('codex', [...codexArgs, command || ''], {
|
|
151
|
+
shell: true,
|
|
152
|
+
cwd: options?.projectPath || options?.cwd || os.homedir(),
|
|
153
|
+
env: process.env,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
codexProc.stdout.on('data', (chunk) => {
|
|
157
|
+
ws.send(JSON.stringify({
|
|
158
|
+
type: 'relay-stream',
|
|
159
|
+
requestId,
|
|
160
|
+
data: { type: 'codex-response', content: chunk.toString() }
|
|
161
|
+
}));
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
codexProc.stderr.on('data', (chunk) => {
|
|
165
|
+
ws.send(JSON.stringify({
|
|
166
|
+
type: 'relay-stream',
|
|
167
|
+
requestId,
|
|
168
|
+
data: { type: 'codex-error', content: chunk.toString() }
|
|
169
|
+
}));
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
codexProc.on('close', (code) => {
|
|
173
|
+
ws.send(JSON.stringify({
|
|
174
|
+
type: 'relay-complete',
|
|
175
|
+
requestId,
|
|
176
|
+
exitCode: code
|
|
177
|
+
}));
|
|
178
|
+
});
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
case 'cursor-query': {
|
|
183
|
+
const { command, options } = data;
|
|
184
|
+
logRelayEvent('>', `Cursor query: ${command?.slice(0, 60)}...`, 'cyan');
|
|
185
|
+
|
|
186
|
+
const cursorArgs = [];
|
|
187
|
+
if (options?.projectPath || options?.cwd) {
|
|
188
|
+
cursorArgs.push('--cwd', options.projectPath || options.cwd);
|
|
189
|
+
}
|
|
190
|
+
if (options?.model) cursorArgs.push('--model', options.model);
|
|
191
|
+
|
|
192
|
+
const cursorProc = spawn('cursor-agent', [...cursorArgs, command || ''], {
|
|
193
|
+
shell: true,
|
|
194
|
+
cwd: options?.projectPath || options?.cwd || os.homedir(),
|
|
195
|
+
env: process.env,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
cursorProc.stdout.on('data', (chunk) => {
|
|
199
|
+
ws.send(JSON.stringify({
|
|
200
|
+
type: 'relay-stream',
|
|
201
|
+
requestId,
|
|
202
|
+
data: { type: 'cursor-response', content: chunk.toString() }
|
|
203
|
+
}));
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
cursorProc.stderr.on('data', (chunk) => {
|
|
207
|
+
ws.send(JSON.stringify({
|
|
208
|
+
type: 'relay-stream',
|
|
209
|
+
requestId,
|
|
210
|
+
data: { type: 'cursor-error', content: chunk.toString() }
|
|
211
|
+
}));
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
cursorProc.on('close', (code) => {
|
|
215
|
+
ws.send(JSON.stringify({
|
|
216
|
+
type: 'relay-complete',
|
|
217
|
+
requestId,
|
|
218
|
+
exitCode: code
|
|
219
|
+
}));
|
|
220
|
+
});
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
case 'detect-agents': {
|
|
225
|
+
const agents = detectInstalledAgents();
|
|
226
|
+
ws.send(JSON.stringify({
|
|
227
|
+
type: 'relay-response',
|
|
228
|
+
requestId,
|
|
229
|
+
data: { agents }
|
|
230
|
+
}));
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
|
|
140
234
|
case 'shell-command': {
|
|
141
235
|
const { command: cmd, cwd } = data;
|
|
142
236
|
// Block dangerous shell patterns
|
|
@@ -228,6 +322,39 @@ async function buildFileTree(dirPath, maxDepth, currentDepth = 0) {
|
|
|
228
322
|
}
|
|
229
323
|
}
|
|
230
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Detect which AI CLI agents are installed on this machine
|
|
327
|
+
* Returns an object with agent names and their availability
|
|
328
|
+
*/
|
|
329
|
+
function detectInstalledAgents() {
|
|
330
|
+
const isWindows = process.platform === 'win32';
|
|
331
|
+
const whichCmd = isWindows ? 'where' : 'which';
|
|
332
|
+
|
|
333
|
+
const agents = [
|
|
334
|
+
{ name: 'claude', binary: 'claude', label: 'Claude Code' },
|
|
335
|
+
{ name: 'codex', binary: 'codex', label: 'OpenAI Codex' },
|
|
336
|
+
{ name: 'cursor', binary: 'cursor-agent', label: 'Cursor Agent' },
|
|
337
|
+
];
|
|
338
|
+
|
|
339
|
+
const detected = {};
|
|
340
|
+
for (const agent of agents) {
|
|
341
|
+
try {
|
|
342
|
+
const result = execSync(`${whichCmd} ${agent.binary}`, { stdio: 'pipe', timeout: 5000 }).toString().trim();
|
|
343
|
+
detected[agent.name] = {
|
|
344
|
+
installed: true,
|
|
345
|
+
path: result.split('\n')[0].trim(),
|
|
346
|
+
label: agent.label,
|
|
347
|
+
};
|
|
348
|
+
} catch {
|
|
349
|
+
detected[agent.name] = {
|
|
350
|
+
installed: false,
|
|
351
|
+
label: agent.label,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return detected;
|
|
356
|
+
}
|
|
357
|
+
|
|
231
358
|
/**
|
|
232
359
|
* Create WebSocket connection with optional API key in handshake
|
|
233
360
|
*/
|
|
@@ -239,6 +366,8 @@ function createRelayConnection(wsUrl, config = {}) {
|
|
|
239
366
|
}
|
|
240
367
|
headers['x-upfyn-version'] = VERSION;
|
|
241
368
|
headers['x-upfyn-machine'] = os.hostname();
|
|
369
|
+
headers['x-upfyn-platform'] = process.platform;
|
|
370
|
+
headers['x-upfyn-cwd'] = process.cwd();
|
|
242
371
|
|
|
243
372
|
return new WebSocket(wsUrl, { headers });
|
|
244
373
|
}
|
|
@@ -284,11 +413,15 @@ export async function connectToServer(options = {}) {
|
|
|
284
413
|
let reconnectAttempts = 0;
|
|
285
414
|
const MAX_RECONNECT = 10;
|
|
286
415
|
|
|
416
|
+
let lastPongTime = Date.now();
|
|
417
|
+
|
|
287
418
|
function connect() {
|
|
288
419
|
const ws = createRelayConnection(wsUrl, config);
|
|
420
|
+
lastPongTime = Date.now();
|
|
289
421
|
|
|
290
422
|
ws.on('open', () => {
|
|
291
423
|
reconnectAttempts = 0;
|
|
424
|
+
lastPongTime = Date.now();
|
|
292
425
|
// Don't stop spinner yet — wait for relay-connected message
|
|
293
426
|
});
|
|
294
427
|
|
|
@@ -302,6 +435,34 @@ export async function connectToServer(options = {}) {
|
|
|
302
435
|
const nameMatch = data.message?.match(/Connected as (.+?)\./);
|
|
303
436
|
const username = nameMatch ? nameMatch[1] : 'Unknown';
|
|
304
437
|
showConnectionBanner(username, serverUrl);
|
|
438
|
+
|
|
439
|
+
// Detect and report installed agents
|
|
440
|
+
const agents = detectInstalledAgents();
|
|
441
|
+
const installed = Object.entries(agents)
|
|
442
|
+
.filter(([, info]) => info.installed)
|
|
443
|
+
.map(([name, info]) => info.label);
|
|
444
|
+
const missing = Object.entries(agents)
|
|
445
|
+
.filter(([, info]) => !info.installed)
|
|
446
|
+
.map(([name, info]) => info.label);
|
|
447
|
+
|
|
448
|
+
if (installed.length > 0) {
|
|
449
|
+
logRelayEvent('+', `Agents found: ${installed.join(', ')}`, 'green');
|
|
450
|
+
}
|
|
451
|
+
if (missing.length > 0) {
|
|
452
|
+
logRelayEvent('~', `Not found: ${missing.join(', ')} (install to enable)`, 'yellow');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Send agent capabilities to server
|
|
456
|
+
ws.send(JSON.stringify({
|
|
457
|
+
type: 'agent-capabilities',
|
|
458
|
+
agents,
|
|
459
|
+
machine: {
|
|
460
|
+
hostname: os.hostname(),
|
|
461
|
+
platform: process.platform,
|
|
462
|
+
cwd: process.cwd(),
|
|
463
|
+
}
|
|
464
|
+
}));
|
|
465
|
+
|
|
305
466
|
logRelayEvent('*', 'Relay active -- waiting for commands...', 'green');
|
|
306
467
|
return;
|
|
307
468
|
}
|
|
@@ -311,7 +472,14 @@ export async function connectToServer(options = {}) {
|
|
|
311
472
|
return;
|
|
312
473
|
}
|
|
313
474
|
|
|
314
|
-
if (data.type === 'pong')
|
|
475
|
+
if (data.type === 'pong' || data.type === 'server-ping') {
|
|
476
|
+
lastPongTime = Date.now();
|
|
477
|
+
// Reply to server-ping so server knows we're alive
|
|
478
|
+
if (data.type === 'server-ping') {
|
|
479
|
+
ws.send(JSON.stringify({ type: 'ping' }));
|
|
480
|
+
}
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
315
483
|
|
|
316
484
|
if (data.type === 'error') {
|
|
317
485
|
spinner.fail(`Server error: ${data.error}`);
|
|
@@ -346,14 +514,24 @@ export async function connectToServer(options = {}) {
|
|
|
346
514
|
// close handler will trigger reconnect
|
|
347
515
|
});
|
|
348
516
|
|
|
349
|
-
// Heartbeat every 30 seconds
|
|
517
|
+
// Heartbeat every 30 seconds with pong timeout detection
|
|
350
518
|
const heartbeat = setInterval(() => {
|
|
351
|
-
if (ws.readyState
|
|
352
|
-
ws.send(JSON.stringify({ type: 'ping' }));
|
|
353
|
-
} else {
|
|
519
|
+
if (ws.readyState !== 1) {
|
|
354
520
|
clearInterval(heartbeat);
|
|
521
|
+
return;
|
|
355
522
|
}
|
|
523
|
+
// If no pong received in 75s, consider connection dead
|
|
524
|
+
if (Date.now() - lastPongTime > 75000) {
|
|
525
|
+
clearInterval(heartbeat);
|
|
526
|
+
logRelayEvent('!', 'No heartbeat response — connection stale, reconnecting...', 'yellow');
|
|
527
|
+
ws.terminate();
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
ws.send(JSON.stringify({ type: 'ping' }));
|
|
356
531
|
}, 30000);
|
|
532
|
+
|
|
533
|
+
ws.on('close', () => clearInterval(heartbeat));
|
|
534
|
+
ws.on('error', () => clearInterval(heartbeat));
|
|
357
535
|
}
|
|
358
536
|
|
|
359
537
|
connect();
|
|
@@ -381,13 +559,22 @@ export function connectToServerBackground(options = {}) {
|
|
|
381
559
|
|
|
382
560
|
let reconnectAttempts = 0;
|
|
383
561
|
const MAX_RECONNECT = 5;
|
|
562
|
+
let lastPongTime = Date.now();
|
|
384
563
|
|
|
385
564
|
function connect() {
|
|
386
565
|
const ws = createRelayConnection(wsUrl, config);
|
|
566
|
+
lastPongTime = Date.now();
|
|
387
567
|
|
|
388
568
|
ws.on('message', (rawMessage) => {
|
|
389
569
|
try {
|
|
390
570
|
const data = JSON.parse(rawMessage);
|
|
571
|
+
if (data.type === 'pong' || data.type === 'server-ping') {
|
|
572
|
+
lastPongTime = Date.now();
|
|
573
|
+
if (data.type === 'server-ping') {
|
|
574
|
+
ws.send(JSON.stringify({ type: 'ping' }));
|
|
575
|
+
}
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
391
578
|
if (data.type === 'relay-command') {
|
|
392
579
|
handleRelayCommand(data, ws);
|
|
393
580
|
}
|
|
@@ -396,9 +583,11 @@ export function connectToServerBackground(options = {}) {
|
|
|
396
583
|
|
|
397
584
|
ws.on('open', () => {
|
|
398
585
|
reconnectAttempts = 0;
|
|
586
|
+
lastPongTime = Date.now();
|
|
399
587
|
});
|
|
400
588
|
|
|
401
589
|
ws.on('close', (code) => {
|
|
590
|
+
clearInterval(heartbeat);
|
|
402
591
|
if (code === 1000) return;
|
|
403
592
|
reconnectAttempts++;
|
|
404
593
|
if (reconnectAttempts <= MAX_RECONNECT) {
|
|
@@ -408,16 +597,21 @@ export function connectToServerBackground(options = {}) {
|
|
|
408
597
|
});
|
|
409
598
|
|
|
410
599
|
ws.on('error', () => {
|
|
411
|
-
|
|
600
|
+
clearInterval(heartbeat);
|
|
412
601
|
});
|
|
413
602
|
|
|
414
|
-
// Heartbeat
|
|
603
|
+
// Heartbeat with pong timeout
|
|
415
604
|
const heartbeat = setInterval(() => {
|
|
416
|
-
if (ws.readyState
|
|
417
|
-
|
|
418
|
-
|
|
605
|
+
if (ws.readyState !== 1) {
|
|
606
|
+
clearInterval(heartbeat);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
if (Date.now() - lastPongTime > 75000) {
|
|
419
610
|
clearInterval(heartbeat);
|
|
611
|
+
ws.terminate();
|
|
612
|
+
return;
|
|
420
613
|
}
|
|
614
|
+
ws.send(JSON.stringify({ type: 'ping' }));
|
|
421
615
|
}, 30000);
|
|
422
616
|
}
|
|
423
617
|
|
package/server/routes/auth.js
CHANGED
|
@@ -220,6 +220,12 @@ router.get('/user', authenticateToken, async (req, res) => {
|
|
|
220
220
|
}
|
|
221
221
|
});
|
|
222
222
|
|
|
223
|
+
// Get a fresh JWT for the current session (used by frontend to pass to iframe)
|
|
224
|
+
router.get('/token', authenticateToken, (req, res) => {
|
|
225
|
+
const token = generateToken(req.user);
|
|
226
|
+
res.json({ token });
|
|
227
|
+
});
|
|
228
|
+
|
|
223
229
|
// Get user's tokens — relay token (for Connect + MCP) and API key
|
|
224
230
|
router.get('/connect-token', authenticateToken, async (req, res) => {
|
|
225
231
|
try {
|
|
@@ -293,7 +293,7 @@ Custom commands can be created in:
|
|
|
293
293
|
// Read version from package.json
|
|
294
294
|
const packageJsonPath = path.join(path.dirname(__dirname), '..', 'package.json');
|
|
295
295
|
let version = 'unknown';
|
|
296
|
-
let packageName = '
|
|
296
|
+
let packageName = 'upfynai-code';
|
|
297
297
|
|
|
298
298
|
try {
|
|
299
299
|
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|