thrust-cli 1.0.15 → 1.0.16
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/frontend/index.html +232 -37
- package/mcps/projectMcpServer.js +34 -24
- package/package.json +1 -1
- package/utils/daemon.js +20 -13
package/frontend/index.html
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
--mcp: #06B6D4;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
body { background-color: var(--bg-main); color: var(--text-main); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; }
|
|
25
|
+
body { background-color: var(--bg-main); color: var(--text-main); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; overflow-x: hidden; }
|
|
26
26
|
|
|
27
27
|
.navbar { background-color: rgba(15, 15, 22, 0.8); backdrop-filter: blur(10px); border-bottom: 1px solid var(--border-color); padding: 1rem 2rem; display: flex; justify-content: space-between; align-items: center; position: sticky; top: 0; z-index: 100; }
|
|
28
28
|
.nav-logo { font-size: 1.25rem; font-weight: 900; color: var(--primary); font-style: italic; letter-spacing: -0.5px; display: flex; align-items: center; gap: 8px;}
|
|
@@ -78,16 +78,25 @@
|
|
|
78
78
|
.directive-text strong { color: var(--text-main); font-weight: 600; }
|
|
79
79
|
|
|
80
80
|
.checklist-container { background-color: var(--bg-panel); border-top: 1px solid var(--border-color); padding: 1.5rem; }
|
|
81
|
-
|
|
82
|
-
.task-item
|
|
81
|
+
|
|
82
|
+
.task-item-wrapper { padding: 12px 0; border-bottom: 1px solid var(--border-color); }
|
|
83
|
+
.task-item-wrapper:last-child { border-bottom: none; padding-bottom: 0; }
|
|
84
|
+
.task-item { display: flex; align-items: flex-start; gap: 12px; }
|
|
83
85
|
.task-checkbox { margin-top: 4px; width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary); }
|
|
84
86
|
.task-text { font-size: 0.95rem; color: var(--text-main); transition: color 0.3s; }
|
|
85
87
|
.task-done { text-decoration: line-through; color: var(--text-muted); }
|
|
86
88
|
|
|
89
|
+
/* AI QUICK COMMENTS UI */
|
|
90
|
+
.quick-comments { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
91
|
+
.quick-badge { background: rgba(255,255,255,0.05); border: 1px solid var(--border-color); color: var(--text-muted); font-size: 0.75rem; padding: 4px 10px; border-radius: 12px; cursor: pointer; transition: all 0.2s; font-family: inherit; font-weight: 500; }
|
|
92
|
+
.quick-badge:hover { background: rgba(139, 92, 246, 0.1); border-color: var(--primary); color: var(--primary); }
|
|
93
|
+
.task-input { flex: 1; background: var(--bg-hover); border: 1px solid var(--border-color); color: white; padding: 6px 12px; border-radius: 6px; font-size: 0.85rem; outline: none; }
|
|
94
|
+
.task-input:focus { border-color: var(--primary); }
|
|
95
|
+
|
|
87
96
|
.input-group { margin-bottom: 1.5rem; display: flex; gap: 10px; flex-direction: column; }
|
|
88
97
|
.input-group label { font-size: 0.85rem; font-weight: bold; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1px; }
|
|
89
|
-
input[type="text"], input[type="url"] { flex-grow: 1; background-color: var(--bg-hover); color: var(--text-main); border: 1px solid var(--border-color); padding: 0.8rem; border-radius: 6px; font-size: 1rem; box-sizing: border-box; }
|
|
90
|
-
input[type="text"]:focus, input[type="url"]:focus { outline: none; border-color: var(--primary); }
|
|
98
|
+
input[type="text"], input[type="url"], input[type="password"] { flex-grow: 1; background-color: var(--bg-hover); color: var(--text-main); border: 1px solid var(--border-color); padding: 0.8rem; border-radius: 6px; font-size: 1rem; box-sizing: border-box; }
|
|
99
|
+
input[type="text"]:focus, input[type="url"]:focus, input[type="password"]:focus { outline: none; border-color: var(--primary); }
|
|
91
100
|
|
|
92
101
|
.projects-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }
|
|
93
102
|
.project-card { background-color: var(--bg-hover); border: 1px solid var(--border-color); border-radius: 12px; padding: 1rem; cursor: pointer; transition: all 0.2s; display: flex; flex-direction: column; }
|
|
@@ -113,15 +122,21 @@
|
|
|
113
122
|
.btn-watch:hover { background-color: var(--primary-hover); }
|
|
114
123
|
.explorer-footer { background-color: var(--bg-panel); padding: 0.75rem 1rem; border-top: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; }
|
|
115
124
|
|
|
116
|
-
/* MCP LIST UI */
|
|
125
|
+
/* MCP LIST & TABS UI */
|
|
117
126
|
.mcp-list { margin-bottom: 1.5rem; }
|
|
118
127
|
.mcp-item { display: flex; justify-content: space-between; align-items: center; background-color: var(--bg-hover); border: 1px solid var(--border-color); padding: 0.8rem 1rem; border-radius: 8px; margin-bottom: 0.5rem; }
|
|
119
128
|
.mcp-item span { font-weight: bold; color: var(--mcp); }
|
|
120
129
|
.mcp-item small { color: var(--text-muted); font-family: monospace; margin-left: 10px; }
|
|
130
|
+
|
|
131
|
+
.mcp-tabs { display: flex; gap: 8px; margin-bottom: 12px; }
|
|
132
|
+
.mcp-tab { background: transparent; border: 1px solid var(--border-color); color: var(--text-muted); padding: 8px 16px; border-radius: 8px; cursor: pointer; font-size: 0.85rem; font-weight: bold; transition: all 0.2s; }
|
|
133
|
+
.mcp-tab:hover { background: rgba(255,255,255,0.05); }
|
|
134
|
+
.mcp-tab.active { background: rgba(139, 92, 246, 0.1); color: var(--primary); border-color: var(--primary); }
|
|
135
|
+
|
|
121
136
|
.mcp-add-form { display: flex; gap: 10px; margin-bottom: 2rem; }
|
|
122
137
|
.mcp-add-form input { margin: 0; }
|
|
123
138
|
|
|
124
|
-
.terminal-container { background-color: var(--bg-panel); border: 1px solid var(--border-color); border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; }
|
|
139
|
+
.terminal-container { background-color: var(--bg-panel); border: 1px solid var(--border-color); border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; margin-bottom: 40px; }
|
|
125
140
|
.log-box { height: 200px; overflow-y: auto; padding: 1rem; font-family: monospace; font-size: 0.85rem; color: var(--text-muted); }
|
|
126
141
|
.log-box p { margin: 0 0 0.5rem 0; line-height: 1.4; word-break: break-all; }
|
|
127
142
|
.log-time { color: var(--primary); margin-right: 8px; flex-shrink: 0; }
|
|
@@ -131,10 +146,22 @@
|
|
|
131
146
|
.prompt-btn:hover { background-color: var(--primary-hover); }
|
|
132
147
|
|
|
133
148
|
.hidden { display: none !important; }
|
|
149
|
+
|
|
150
|
+
/* TOAST NOTIFICATIONS */
|
|
151
|
+
#toast-container { position: fixed; bottom: 24px; right: 24px; z-index: 9999; display: flex; flex-direction: column; gap: 12px; }
|
|
152
|
+
.toast { padding: 14px 20px; border-radius: 10px; color: white; font-weight: 500; font-size: 0.95rem; box-shadow: 0 10px 30px rgba(0,0,0,0.5); transform: translateX(120%); transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); display: flex; align-items: center; gap: 12px; min-width: 250px; max-width: 350px; word-break: break-word; }
|
|
153
|
+
.toast.show { transform: translateX(0); }
|
|
154
|
+
.toast-info { background: var(--primary); border: 1px solid var(--primary-hover); }
|
|
155
|
+
.toast-success { background: var(--success); border: 1px solid #059669; }
|
|
156
|
+
.toast-error { background: var(--danger); border: 1px solid #DC2626; }
|
|
157
|
+
.toast-ai { background: #1A1A24; border: 1px solid var(--primary); }
|
|
134
158
|
</style>
|
|
135
159
|
</head>
|
|
136
160
|
<body>
|
|
137
161
|
|
|
162
|
+
<!-- TOAST NOTIFICATION MOUNT -->
|
|
163
|
+
<div id="toast-container"></div>
|
|
164
|
+
|
|
138
165
|
<nav class="navbar">
|
|
139
166
|
<div class="nav-logo">⚡ THRUST</div>
|
|
140
167
|
<div class="profile-wrapper" id="profile-wrapper">
|
|
@@ -227,12 +254,17 @@
|
|
|
227
254
|
|
|
228
255
|
<!-- EXTERNAL MCP INTEGRATIONS -->
|
|
229
256
|
<h2>3. External Integrations (MCP Clients)</h2>
|
|
230
|
-
<p style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 1rem;">Connect external tools
|
|
257
|
+
<p style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 1rem;">Connect external tools like Figma, GitHub, or local MCPs. The Agent will poll them to maintain context momentum.</p>
|
|
231
258
|
<div id="mcp-list" class="mcp-list"></div>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
<
|
|
235
|
-
<button type="
|
|
259
|
+
|
|
260
|
+
<div class="mcp-tabs">
|
|
261
|
+
<button type="button" class="mcp-tab active" onclick="setMcpTab('local', this)">Standard MCP</button>
|
|
262
|
+
<button type="button" class="mcp-tab" onclick="setMcpTab('figma', this)">Figma Bridge</button>
|
|
263
|
+
<button type="button" class="mcp-tab" onclick="setMcpTab('github', this)">GitHub Bridge</button>
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<form class="mcp-add-form" onsubmit="addMCPServer(event)" id="mcp-form">
|
|
267
|
+
<!-- Rendered Dynamically -->
|
|
236
268
|
</form>
|
|
237
269
|
</div>
|
|
238
270
|
|
|
@@ -252,6 +284,48 @@
|
|
|
252
284
|
let currentFolderName = "";
|
|
253
285
|
let localWs = null;
|
|
254
286
|
let currentPage = 1;
|
|
287
|
+
let currentMcpType = 'local';
|
|
288
|
+
|
|
289
|
+
// --- AUDIO ENGINE ---
|
|
290
|
+
function playNotificationSound() {
|
|
291
|
+
try {
|
|
292
|
+
const ctx = new (window.AudioContext || window.webkitAudioContext)();
|
|
293
|
+
const osc = ctx.createOscillator();
|
|
294
|
+
const gain = ctx.createGain();
|
|
295
|
+
osc.connect(gain);
|
|
296
|
+
gain.connect(ctx.destination);
|
|
297
|
+
osc.type = 'sine';
|
|
298
|
+
osc.frequency.setValueAtTime(880, ctx.currentTime);
|
|
299
|
+
osc.frequency.exponentialRampToValueAtTime(1760, ctx.currentTime + 0.1);
|
|
300
|
+
gain.gain.setValueAtTime(0.05, ctx.currentTime);
|
|
301
|
+
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.3);
|
|
302
|
+
osc.start();
|
|
303
|
+
osc.stop(ctx.currentTime + 0.3);
|
|
304
|
+
} catch (e) {} // Failsafe for auto-play policy blocks
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// --- TOAST UI ---
|
|
308
|
+
function showToast(message, type = "info") {
|
|
309
|
+
playNotificationSound();
|
|
310
|
+
const container = document.getElementById('toast-container');
|
|
311
|
+
const toast = document.createElement('div');
|
|
312
|
+
toast.className = `toast toast-${type}`;
|
|
313
|
+
|
|
314
|
+
let icon = "💡";
|
|
315
|
+
if(type === 'success') icon = "✅";
|
|
316
|
+
if(type === 'error') icon = "❌";
|
|
317
|
+
if(type === 'ai') icon = "🤖";
|
|
318
|
+
|
|
319
|
+
toast.innerHTML = `<span style="font-size: 1.2rem;">${icon}</span> <span>${message}</span>`;
|
|
320
|
+
container.appendChild(toast);
|
|
321
|
+
|
|
322
|
+
setTimeout(() => toast.classList.add('show'), 10);
|
|
323
|
+
|
|
324
|
+
setTimeout(() => {
|
|
325
|
+
toast.classList.remove('show');
|
|
326
|
+
setTimeout(() => toast.remove(), 300);
|
|
327
|
+
}, 5000);
|
|
328
|
+
}
|
|
255
329
|
|
|
256
330
|
function addLog(message, color = "var(--text-muted)") {
|
|
257
331
|
const p = document.createElement('p');
|
|
@@ -289,34 +363,57 @@
|
|
|
289
363
|
} catch(e) {}
|
|
290
364
|
}
|
|
291
365
|
|
|
292
|
-
|
|
366
|
+
// --- UPDATED COMPLETION LOGIC FOR QUICK REPLIES ---
|
|
367
|
+
async function completeTask(taskId, taskTitle, checkboxElem, comment = null) {
|
|
368
|
+
if (!checkboxElem) checkboxElem = document.getElementById(`cb-${taskId}`);
|
|
293
369
|
checkboxElem.disabled = true;
|
|
370
|
+
checkboxElem.checked = true;
|
|
371
|
+
|
|
294
372
|
const textSpan = checkboxElem.nextElementSibling;
|
|
295
|
-
textSpan.classList.add('task-done');
|
|
373
|
+
if(textSpan) textSpan.classList.add('task-done');
|
|
374
|
+
|
|
375
|
+
// Hide the quick comment UI once completed
|
|
376
|
+
const qcContainer = document.getElementById(`qc-${taskId}`);
|
|
377
|
+
if (qcContainer) qcContainer.style.display = 'none';
|
|
378
|
+
|
|
379
|
+
// Extract comment from the input box if a badge wasn't explicitly clicked
|
|
380
|
+
let finalComment = comment;
|
|
381
|
+
if (finalComment === null || finalComment === undefined) {
|
|
382
|
+
const inputElem = document.getElementById(`input-${taskId}`);
|
|
383
|
+
if (inputElem && inputElem.value.trim() !== '') {
|
|
384
|
+
finalComment = inputElem.value.trim();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Append comment for the AI timeline if it exists
|
|
389
|
+
const finalTitle = finalComment ? `${taskTitle} (Response: ${finalComment})` : taskTitle;
|
|
296
390
|
|
|
297
391
|
try {
|
|
298
392
|
const res = await fetch('/api/tasks/complete', {
|
|
299
393
|
method: 'POST',
|
|
300
394
|
headers: { 'Content-Type': 'application/json' },
|
|
301
|
-
body: JSON.stringify({ taskId, taskTitle })
|
|
395
|
+
body: JSON.stringify({ taskId, taskTitle: finalTitle })
|
|
302
396
|
});
|
|
303
397
|
|
|
304
398
|
if (res.ok) {
|
|
305
|
-
addLog(`✅ Task marked complete: ${
|
|
399
|
+
addLog(`✅ Task marked complete: ${finalTitle}`, 'var(--success)');
|
|
306
400
|
} else {
|
|
307
401
|
throw new Error("API Failed");
|
|
308
402
|
}
|
|
309
403
|
} catch(e) {
|
|
310
404
|
checkboxElem.disabled = false;
|
|
311
|
-
|
|
405
|
+
checkboxElem.checked = false;
|
|
406
|
+
if(textSpan) textSpan.classList.remove('task-done');
|
|
407
|
+
if (qcContainer) qcContainer.style.display = 'block';
|
|
312
408
|
addLog(`❌ Failed to sync task completion.`, 'var(--danger)');
|
|
409
|
+
showToast("Failed to sync task", "error");
|
|
313
410
|
}
|
|
314
411
|
}
|
|
315
412
|
|
|
316
413
|
function requestNewThrust() {
|
|
317
414
|
const overridePrompt = `[SYSTEM OVERRIDE]: The user has requested the next Thrust. Evaluate the current Timeline, PRD, and recent Git diffs. Check if the previous Thrust tasks are completed. Generate a new <thrust_create> with the next 1-4 immediate steps to keep momentum going.`;
|
|
318
415
|
|
|
319
|
-
if (localWs && localWs.readyState === 1) {
|
|
416
|
+
if (localWs && localWs.readyState === 1) {
|
|
320
417
|
localWs.send(JSON.stringify({ type: 'frontend_prompt', payload: overridePrompt }));
|
|
321
418
|
addLog(`⚡ Override triggered. Waking up AI Director...`, 'var(--warning)');
|
|
322
419
|
} else {
|
|
@@ -366,16 +463,46 @@
|
|
|
366
463
|
|
|
367
464
|
const tasksBox = document.getElementById('dir-tasks');
|
|
368
465
|
tasksBox.innerHTML = '';
|
|
466
|
+
|
|
369
467
|
if (thrust.thrust_tasks && thrust.thrust_tasks.length > 0) {
|
|
370
468
|
thrust.thrust_tasks.forEach(task => {
|
|
371
|
-
const isDone = task.is_completed;
|
|
469
|
+
const isDone = task.is_completed || task.status === 'done';
|
|
372
470
|
const safeTitle = task.title.replace(/'/g, "\\'").replace(/"/g, '"');
|
|
373
471
|
|
|
472
|
+
let commentsHtml = '';
|
|
473
|
+
if (!isDone) {
|
|
474
|
+
// NEW: Dynamically parse the options/placeholder generated by the AI
|
|
475
|
+
const hasOptions = task.options && task.options.length > 0;
|
|
476
|
+
const placeholder = task.placeholder || "Add a note or comment...";
|
|
477
|
+
|
|
478
|
+
if (hasOptions) {
|
|
479
|
+
let badgesHtml = task.options.map(qc => {
|
|
480
|
+
const safeQc = qc.replace(/'/g, "\\'");
|
|
481
|
+
return `<button type="button" class="quick-badge" onclick="document.getElementById('input-${task.id}').value = '${safeQc}'">${qc}</button>`;
|
|
482
|
+
}).join('');
|
|
483
|
+
|
|
484
|
+
commentsHtml = `
|
|
485
|
+
<div id="qc-${task.id}" style="margin-top: 8px; margin-left: 28px;">
|
|
486
|
+
<div class="quick-comments" style="margin-bottom: 8px;">
|
|
487
|
+
${badgesHtml}
|
|
488
|
+
</div>
|
|
489
|
+
<div style="display: flex; gap: 8px; align-items: center;">
|
|
490
|
+
<input type="text" id="input-${task.id}" placeholder="${placeholder}" class="task-input">
|
|
491
|
+
<button class="btn-small btn-watch" onclick="completeTask('${task.id}', '${safeTitle}', document.getElementById('cb-${task.id}'), document.getElementById('input-${task.id}').value)">Complete</button>
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
`;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
374
498
|
tasksBox.innerHTML += `
|
|
375
|
-
<div class="task-item">
|
|
376
|
-
<
|
|
377
|
-
|
|
378
|
-
|
|
499
|
+
<div class="task-item-wrapper">
|
|
500
|
+
<div class="task-item">
|
|
501
|
+
<input type="checkbox" id="cb-${task.id}" class="task-checkbox"
|
|
502
|
+
${isDone ? 'checked disabled' : `onchange="completeTask('${task.id}', '${safeTitle}', this)"`}>
|
|
503
|
+
<span class="task-text ${isDone ? 'task-done' : ''}">${task.title}</span>
|
|
504
|
+
</div>
|
|
505
|
+
${commentsHtml}
|
|
379
506
|
</div>
|
|
380
507
|
`;
|
|
381
508
|
});
|
|
@@ -437,14 +564,41 @@
|
|
|
437
564
|
document.getElementById('current-path').scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
438
565
|
}
|
|
439
566
|
|
|
440
|
-
/* MCP CLIENT MANAGEMENT */
|
|
567
|
+
/* DYNAMIC MCP CLIENT MANAGEMENT */
|
|
568
|
+
function setMcpTab(type, btn) {
|
|
569
|
+
document.querySelectorAll('.mcp-tab').forEach(b => b.classList.remove('active'));
|
|
570
|
+
btn.classList.add('active');
|
|
571
|
+
currentMcpType = type;
|
|
572
|
+
const form = document.getElementById('mcp-form');
|
|
573
|
+
|
|
574
|
+
if (type === 'local') {
|
|
575
|
+
form.innerHTML = `
|
|
576
|
+
<input type="text" id="mcp-name" placeholder="Tool Name (e.g., Unity, Cursor)" required style="flex: 1;">
|
|
577
|
+
<input type="url" id="mcp-url" placeholder="http://localhost:8081/mcp" required style="flex: 2;">
|
|
578
|
+
<button type="submit" class="btn-primary" style="width: auto;">+ Link Tool</button>
|
|
579
|
+
`;
|
|
580
|
+
} else if (type === 'figma') {
|
|
581
|
+
form.innerHTML = `
|
|
582
|
+
<input type="text" id="mcp-file" placeholder="Figma File Key" required style="flex: 1;">
|
|
583
|
+
<input type="password" id="mcp-token" placeholder="Figma Access Token" required style="flex: 2;">
|
|
584
|
+
<button type="submit" class="btn-primary" style="width: auto;">+ Link Figma</button>
|
|
585
|
+
`;
|
|
586
|
+
} else if (type === 'github') {
|
|
587
|
+
form.innerHTML = `
|
|
588
|
+
<input type="text" id="mcp-repo" placeholder="Repo (owner/repo)" required style="flex: 1;">
|
|
589
|
+
<input type="password" id="mcp-token" placeholder="GitHub PAT Token" required style="flex: 2;">
|
|
590
|
+
<button type="submit" class="btn-primary" style="width: auto;">+ Link GitHub</button>
|
|
591
|
+
`;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
441
595
|
async function fetchMCPServers() {
|
|
442
596
|
try {
|
|
443
597
|
const res = await fetch('/api/mcp/servers');
|
|
444
598
|
const servers = await res.json();
|
|
445
599
|
const list = document.getElementById('mcp-list');
|
|
446
600
|
list.innerHTML = '';
|
|
447
|
-
|
|
601
|
+
|
|
448
602
|
if (servers.length === 0) {
|
|
449
603
|
list.innerHTML = `<div style="color: var(--text-muted); font-size: 0.85rem;">No external integrations linked.</div>`;
|
|
450
604
|
return;
|
|
@@ -453,7 +607,7 @@
|
|
|
453
607
|
servers.forEach((s, index) => {
|
|
454
608
|
list.innerHTML += `
|
|
455
609
|
<div class="mcp-item">
|
|
456
|
-
<div><span>🔗 ${s.name}</span> <small>${s.url}</small></div>
|
|
610
|
+
<div><span>🔗 ${s.name}</span> <small>${s.url.substring(0, 40)}${s.url.length > 40 ? '...' : ''}</small></div>
|
|
457
611
|
<button class="btn-outline-danger" onclick="removeMCPServer(${index})">Remove</button>
|
|
458
612
|
</div>
|
|
459
613
|
`;
|
|
@@ -463,9 +617,25 @@
|
|
|
463
617
|
|
|
464
618
|
async function addMCPServer(e) {
|
|
465
619
|
e.preventDefault();
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
620
|
+
let name = "";
|
|
621
|
+
let url = "";
|
|
622
|
+
const hostBase = `${window.location.protocol}//${window.location.host}`;
|
|
623
|
+
|
|
624
|
+
if (currentMcpType === 'local') {
|
|
625
|
+
name = document.getElementById('mcp-name').value;
|
|
626
|
+
url = document.getElementById('mcp-url').value;
|
|
627
|
+
} else if (currentMcpType === 'figma') {
|
|
628
|
+
const fileKey = document.getElementById('mcp-file').value;
|
|
629
|
+
const token = document.getElementById('mcp-token').value;
|
|
630
|
+
name = "Figma";
|
|
631
|
+
url = `${hostBase}/api/bridge/figma?fileKey=${encodeURIComponent(fileKey)}&token=${encodeURIComponent(token)}`;
|
|
632
|
+
} else if (currentMcpType === 'github') {
|
|
633
|
+
const repo = document.getElementById('mcp-repo').value;
|
|
634
|
+
const token = document.getElementById('mcp-token').value;
|
|
635
|
+
name = "GitHub";
|
|
636
|
+
url = `${hostBase}/api/bridge/github?repo=${encodeURIComponent(repo)}&token=${encodeURIComponent(token)}`;
|
|
637
|
+
}
|
|
638
|
+
|
|
469
639
|
try {
|
|
470
640
|
const res = await fetch('/api/mcp/servers', {
|
|
471
641
|
method: 'POST',
|
|
@@ -473,18 +643,28 @@
|
|
|
473
643
|
body: JSON.stringify({ name, url })
|
|
474
644
|
});
|
|
475
645
|
if (res.ok) {
|
|
476
|
-
|
|
477
|
-
document.getElementById('mcp-
|
|
646
|
+
// Reset inputs
|
|
647
|
+
if(document.getElementById('mcp-name')) document.getElementById('mcp-name').value = '';
|
|
648
|
+
if(document.getElementById('mcp-url')) document.getElementById('mcp-url').value = '';
|
|
649
|
+
if(document.getElementById('mcp-file')) document.getElementById('mcp-file').value = '';
|
|
650
|
+
if(document.getElementById('mcp-repo')) document.getElementById('mcp-repo').value = '';
|
|
651
|
+
if(document.getElementById('mcp-token')) document.getElementById('mcp-token').value = '';
|
|
652
|
+
|
|
478
653
|
fetchMCPServers();
|
|
479
654
|
addLog(`🔗 Linked External MCP: ${name}`, 'var(--mcp)');
|
|
655
|
+
showToast(`Successfully linked ${name} bridge!`, 'success');
|
|
480
656
|
}
|
|
481
|
-
} catch (err) {
|
|
657
|
+
} catch (err) {
|
|
658
|
+
addLog('❌ Failed to link MCP.', 'var(--danger)');
|
|
659
|
+
showToast('Failed to link integration.', 'error');
|
|
660
|
+
}
|
|
482
661
|
}
|
|
483
662
|
|
|
484
663
|
async function removeMCPServer(index) {
|
|
485
664
|
try {
|
|
486
665
|
await fetch(`/api/mcp/servers/${index}`, { method: 'DELETE' });
|
|
487
666
|
fetchMCPServers();
|
|
667
|
+
showToast("Integration removed", "info");
|
|
488
668
|
} catch (err) {}
|
|
489
669
|
}
|
|
490
670
|
|
|
@@ -495,13 +675,24 @@
|
|
|
495
675
|
|
|
496
676
|
localWs.onmessage = (event) => {
|
|
497
677
|
const data = JSON.parse(event.data);
|
|
678
|
+
|
|
498
679
|
if (data.type === 'success' && data.message.includes('Authentication Successful')) fetchStatus();
|
|
499
680
|
else if (data.type === 'watch') addLog(`👀 ${data.message}`, 'var(--success)');
|
|
500
681
|
else if (data.type === 'sync') addLog(data.message, 'var(--sync)');
|
|
501
|
-
else if (data.type === 'ai') addLog(data.message, 'var(--warning)');
|
|
502
682
|
else if (data.type === 'mcp') addLog(`${data.message}`, 'var(--mcp)');
|
|
503
|
-
else if (data.type === 'success')
|
|
504
|
-
|
|
683
|
+
else if (data.type === 'success') {
|
|
684
|
+
addLog(data.message, 'var(--success)');
|
|
685
|
+
}
|
|
686
|
+
else if (data.type === 'error') {
|
|
687
|
+
addLog(`❌ ${data.message}`, 'var(--danger)');
|
|
688
|
+
showToast(data.message, 'error');
|
|
689
|
+
}
|
|
690
|
+
else if (data.type === 'ai') {
|
|
691
|
+
addLog(data.message, 'var(--warning)');
|
|
692
|
+
// Clean AI Prefix for the Toast
|
|
693
|
+
let cleanMsg = data.message.replace('🔔 [AI]: ', '');
|
|
694
|
+
showToast(cleanMsg, 'ai');
|
|
695
|
+
}
|
|
505
696
|
else if (data.type === 'reload') {
|
|
506
697
|
addLog('🔄 AI updated Directive. Fetching...', 'var(--primary)');
|
|
507
698
|
fetchDirective();
|
|
@@ -538,7 +729,7 @@
|
|
|
538
729
|
|
|
539
730
|
fetchCloudProjects();
|
|
540
731
|
fetchDirective();
|
|
541
|
-
fetchMCPServers();
|
|
732
|
+
fetchMCPServers();
|
|
542
733
|
} else {
|
|
543
734
|
document.getElementById('unauth-view').classList.remove('hidden');
|
|
544
735
|
document.getElementById('auth-view').classList.add('hidden');
|
|
@@ -612,7 +803,10 @@
|
|
|
612
803
|
|
|
613
804
|
try {
|
|
614
805
|
const res = await fetch('/api/link', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ leadId, folderPath: absolutePath }) });
|
|
615
|
-
if ((await res.json()).success)
|
|
806
|
+
if ((await res.json()).success) {
|
|
807
|
+
fetchStatus();
|
|
808
|
+
showToast(`Started watching ${folderName}`, "success");
|
|
809
|
+
}
|
|
616
810
|
} catch (e) {}
|
|
617
811
|
}
|
|
618
812
|
|
|
@@ -620,6 +814,7 @@
|
|
|
620
814
|
fetchStatus();
|
|
621
815
|
loadDirectory();
|
|
622
816
|
connectLocalWebSocket();
|
|
817
|
+
setMcpTab('local', document.querySelector('.mcp-tab.active'));
|
|
623
818
|
});
|
|
624
819
|
</script>
|
|
625
820
|
</body>
|
package/mcps/projectMcpServer.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
-
import {
|
|
3
|
-
CallToolRequestSchema,
|
|
4
|
-
ListToolsRequestSchema
|
|
2
|
+
import {
|
|
3
|
+
CallToolRequestSchema,
|
|
4
|
+
ListToolsRequestSchema
|
|
5
5
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
6
6
|
|
|
7
7
|
// Thrust daemon default port
|
|
8
8
|
const PORT = process.env.THRUST_PORT || 8765;
|
|
9
|
-
|
|
10
|
-
const DAEMON_URL = `http://127.0.0.1:${PORT}`;
|
|
9
|
+
const DAEMON_URL = `http://127.0.0.1:${PORT}`;
|
|
11
10
|
|
|
12
11
|
// --- Initialize the MCP Server ---
|
|
13
12
|
const server = new Server(
|
|
@@ -26,10 +25,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
26
25
|
tools:[
|
|
27
26
|
{
|
|
28
27
|
name: "get_current_active_project",
|
|
29
|
-
description: "DEFAULT tool to fetch the globally active project context. SYSTEM INSTRUCTION TO AI: You are connected to Thrust Local Agent. Call 'push_work_done' to log your work immediately when coding is finished.",
|
|
28
|
+
description: "DEFAULT tool to fetch the globally active project context. You can optionally filter what data you want to ingest. SYSTEM INSTRUCTION TO AI: You are connected to Thrust Local Agent. Call 'push_work_done' to log your work immediately when coding is finished.",
|
|
30
29
|
inputSchema: {
|
|
31
30
|
type: "object",
|
|
32
|
-
properties: {
|
|
31
|
+
properties: {
|
|
32
|
+
includePrd: { type: "boolean", description: "Include the Project Requirements Document (Default: true)" },
|
|
33
|
+
includeThrust: { type: "boolean", description: "Include the currently active Thrust and tasks (Default: true)" },
|
|
34
|
+
includeTimeline: { type: "boolean", description: "Include the last 20 timeline events (Default: true)" }
|
|
35
|
+
}
|
|
33
36
|
},
|
|
34
37
|
},
|
|
35
38
|
{
|
|
@@ -38,15 +41,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
38
41
|
inputSchema: {
|
|
39
42
|
type: "object",
|
|
40
43
|
properties: {
|
|
41
|
-
agentName: {
|
|
44
|
+
agentName: {
|
|
42
45
|
type: "string",
|
|
43
46
|
description: "Your AI identifier (e.g., 'Claude-VSCode', 'Cursor-Agent')"
|
|
44
47
|
},
|
|
45
|
-
workDescription: {
|
|
48
|
+
workDescription: {
|
|
46
49
|
type: "string",
|
|
47
50
|
description: "A concise summary of the code or work you just completed."
|
|
48
51
|
},
|
|
49
|
-
artifacts: {
|
|
52
|
+
artifacts: {
|
|
50
53
|
type: "string",
|
|
51
54
|
description: "Optional list of files modified, functions created, or scripts run."
|
|
52
55
|
}
|
|
@@ -65,25 +68,32 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
65
68
|
try {
|
|
66
69
|
// 1. Fetching Context
|
|
67
70
|
if (name === "get_current_active_project") {
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
// Default all to true if not specified
|
|
72
|
+
const includePrd = args.includePrd !== false;
|
|
73
|
+
const includeThrust = args.includeThrust !== false;
|
|
74
|
+
const includeTimeline = args.includeTimeline !== false;
|
|
75
|
+
|
|
76
|
+
const response = await fetch(`${DAEMON_URL}/api/mcp/context?prd=${includePrd}&thrust=${includeThrust}&timeline=${includeTimeline}`);
|
|
77
|
+
|
|
70
78
|
if (!response.ok) {
|
|
71
79
|
const errorData = await response.json().catch(() => ({}));
|
|
72
80
|
throw new Error(errorData.error || "Thrust daemon returned an error. Ensure you are authenticated in the UI.");
|
|
73
81
|
}
|
|
74
|
-
|
|
82
|
+
|
|
75
83
|
const data = await response.json();
|
|
76
84
|
|
|
77
85
|
return {
|
|
78
86
|
content:[
|
|
79
|
-
{
|
|
80
|
-
type: "text",
|
|
87
|
+
{
|
|
88
|
+
type: "text",
|
|
81
89
|
text: JSON.stringify({
|
|
82
90
|
projectId: data.projectId,
|
|
83
91
|
projectPath: data.projectPath,
|
|
92
|
+
prd: data.prd || "Not requested.",
|
|
84
93
|
activeDirective: data.activeThrust || "No active cloud directive found. Proceed with standard file modifications.",
|
|
94
|
+
timeline: data.timeline || "Not requested.",
|
|
85
95
|
systemInstruction: "You are editing files in the projectPath or something related to the project. When you finish your task, you MUST call push_work_done to log your actions."
|
|
86
|
-
}, null, 2)
|
|
96
|
+
}, null, 2)
|
|
87
97
|
}
|
|
88
98
|
]
|
|
89
99
|
};
|
|
@@ -114,14 +124,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
114
124
|
const errorData = await response.json().catch(() => ({}));
|
|
115
125
|
throw new Error(errorData.error || "Failed to push work to Thrust daemon.");
|
|
116
126
|
}
|
|
117
|
-
|
|
127
|
+
|
|
118
128
|
const data = await response.json();
|
|
119
129
|
|
|
120
130
|
return {
|
|
121
131
|
content:[
|
|
122
|
-
{
|
|
123
|
-
type: "text",
|
|
124
|
-
text: `SUCCESS: ${data.message} Work logged by ${agentName}. Timeline updated and Cloud Code Sync 15s debounce triggered in Thrust core.`
|
|
132
|
+
{
|
|
133
|
+
type: "text",
|
|
134
|
+
text: `SUCCESS: ${data.message} Work logged by ${agentName}. Timeline updated and Cloud Code Sync 15s debounce triggered in Thrust core.`
|
|
125
135
|
}
|
|
126
136
|
]
|
|
127
137
|
};
|
|
@@ -133,14 +143,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
133
143
|
if (error.cause?.code === 'ECONNREFUSED' || error.message.includes('fetch failed')) {
|
|
134
144
|
return {
|
|
135
145
|
content:[
|
|
136
|
-
{
|
|
137
|
-
type: "text",
|
|
138
|
-
text: `ERROR: The Thrust main daemon is not reachable at ${DAEMON_URL}. Please ensure you have run 'thrust' in your terminal and linked a project.`
|
|
146
|
+
{
|
|
147
|
+
type: "text",
|
|
148
|
+
text: `ERROR: The Thrust main daemon is not reachable at ${DAEMON_URL}. Please ensure you have run 'thrust' in your terminal and linked a project.`
|
|
139
149
|
}
|
|
140
150
|
]
|
|
141
151
|
};
|
|
142
152
|
}
|
|
143
|
-
|
|
153
|
+
|
|
144
154
|
return {
|
|
145
155
|
content:[
|
|
146
156
|
{
|
package/package.json
CHANGED
package/utils/daemon.js
CHANGED
|
@@ -21,8 +21,8 @@ const API_URL = GATEWAY_URL.replace('ws://', 'http://').replace('wss://', 'https
|
|
|
21
21
|
const AUTH_PROXY_URL = "https://everydaycats-thrust-auth-server.hf.space";
|
|
22
22
|
|
|
23
23
|
// --- DEBOUNCE & POLLING TIMERS ---
|
|
24
|
-
const INACTIVITY_DELAY_MS = 15 * 1000;
|
|
25
|
-
const MCP_POLL_INTERVAL_MS = 18 * 1000;
|
|
24
|
+
const INACTIVITY_DELAY_MS = 15 * 1000;
|
|
25
|
+
const MCP_POLL_INTERVAL_MS = 18 * 1000;
|
|
26
26
|
|
|
27
27
|
let currentWatcher = null;
|
|
28
28
|
let inactivityTimer = null;
|
|
@@ -89,10 +89,10 @@ async function fetchInitialMCPContext(server) {
|
|
|
89
89
|
const data = await res.json();
|
|
90
90
|
if (data.result && data.result.content && data.result.content.length > 0) {
|
|
91
91
|
const stateText = data.result.content[0].text;
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
fileActivityBuffer += `\n[${new Date().toLocaleTimeString()}][MCP BOOT STATE] Source: ${server.name} | Current Context:\n${stateText}\n`;
|
|
94
94
|
broadcastLocalLog('mcp', `📥 Fetched initial project state from ${server.name}`);
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
triggerDebouncedSync();
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -106,7 +106,7 @@ export async function startDaemon(preferredPort) {
|
|
|
106
106
|
|
|
107
107
|
const app = express();
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
attachExternalBridges(app);
|
|
110
110
|
|
|
111
111
|
const corsOptionsDelegate = (req, callback) => {
|
|
112
112
|
const origin = req.header('Origin');
|
|
@@ -138,12 +138,18 @@ export async function startDaemon(preferredPort) {
|
|
|
138
138
|
const token = config.auth?.token;
|
|
139
139
|
const projectId = config.activeLeadId;
|
|
140
140
|
|
|
141
|
+
// Extract optional parameters, default to true
|
|
142
|
+
const prd = req.query.prd || 'true';
|
|
143
|
+
const thrust = req.query.thrust || 'true';
|
|
144
|
+
const timeline = req.query.timeline || 'true';
|
|
145
|
+
|
|
141
146
|
if (!token || !projectId) {
|
|
142
147
|
return res.status(401).json({ error: "Thrust agent not linked or authenticated." });
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
try {
|
|
146
|
-
|
|
151
|
+
// Forward parameters to new combined gateway endpoint
|
|
152
|
+
const response = await fetch(`${API_URL}/api/projects/${projectId}/mcp-context?prd=${prd}&thrust=${thrust}&timeline=${timeline}`, {
|
|
147
153
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
148
154
|
});
|
|
149
155
|
const data = await response.json();
|
|
@@ -151,10 +157,11 @@ export async function startDaemon(preferredPort) {
|
|
|
151
157
|
res.json({
|
|
152
158
|
projectId,
|
|
153
159
|
projectPath: config.leads[projectId].path,
|
|
154
|
-
|
|
155
|
-
|
|
160
|
+
prd: data.prd,
|
|
161
|
+
activeThrust: data.thrust,
|
|
162
|
+
timeline: data.timeline
|
|
156
163
|
});
|
|
157
|
-
broadcastLocalLog('mcp', `🔗 [Context Sync]
|
|
164
|
+
broadcastLocalLog('mcp', `🔗 [Context Sync] AI Client requested project state.`);
|
|
158
165
|
} catch (e) {
|
|
159
166
|
res.status(502).json({ error: "Failed to fetch context." });
|
|
160
167
|
}
|
|
@@ -193,7 +200,7 @@ export async function startDaemon(preferredPort) {
|
|
|
193
200
|
saveConfig(config);
|
|
194
201
|
|
|
195
202
|
pollExternalMCPServers();
|
|
196
|
-
fetchInitialMCPContext({ name, url, type: 'http' });
|
|
203
|
+
fetchInitialMCPContext({ name, url, type: 'http' });
|
|
197
204
|
res.json({ success: true });
|
|
198
205
|
});
|
|
199
206
|
|
|
@@ -581,12 +588,12 @@ function connectWebSocket() {
|
|
|
581
588
|
globalWs.on('message', async (data) => {
|
|
582
589
|
try {
|
|
583
590
|
const msg = JSON.parse(data.toString());
|
|
584
|
-
|
|
591
|
+
|
|
585
592
|
// NEW: Handle dynamic MCP Queries pushed from the AI Director
|
|
586
593
|
if (msg.type === 'mcp_query' && msg.payload) {
|
|
587
594
|
const { serverName, toolName, targetArg } = msg.payload;
|
|
588
595
|
const targetServer = getConfig().mcpServers?.find(s => s.name.toLowerCase() === serverName.toLowerCase());
|
|
589
|
-
|
|
596
|
+
|
|
590
597
|
if (targetServer) {
|
|
591
598
|
broadcastLocalLog('mcp', `🤖 AI requested data from ${serverName} (${toolName})...`);
|
|
592
599
|
try {
|
|
@@ -605,7 +612,7 @@ function connectWebSocket() {
|
|
|
605
612
|
}
|
|
606
613
|
}
|
|
607
614
|
}
|
|
608
|
-
|
|
615
|
+
|
|
609
616
|
if (msg.type === 'toast' || msg.type === 'response') {
|
|
610
617
|
broadcastLocalLog('ai', `🔔 [AI]: ${msg.message || msg.text}`);
|
|
611
618
|
}
|