thrust-cli 1.0.14 → 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/README.md +1 -0
- package/frontend/index.html +232 -37
- package/index.js +35 -11
- package/mcps/ExternalBridge.js +144 -0
- package/mcps/ThrustMCPBridge.cs +78 -24
- package/mcps/core.js +19 -0
- package/mcps/projectMcpServer.js +165 -0
- package/package.json +3 -1
- package/utils/daemon.js +90 -22
package/README.md
CHANGED
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/index.js
CHANGED
|
@@ -8,10 +8,14 @@ import { fileURLToPath } from 'url';
|
|
|
8
8
|
import { startDaemon } from './utils/daemon.js';
|
|
9
9
|
import { getConfig, saveConfig } from './utils/config.js';
|
|
10
10
|
|
|
11
|
+
// --- MCP Imports ---
|
|
12
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
|
+
import { server } from "./mcps/projectMcpServer.js";
|
|
14
|
+
|
|
11
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
16
|
const __dirname = path.dirname(__filename);
|
|
13
17
|
|
|
14
|
-
let packageJson = { version: "1.0.
|
|
18
|
+
let packageJson = { version: "1.0.15" };
|
|
15
19
|
try {
|
|
16
20
|
packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
|
|
17
21
|
} catch (e) {
|
|
@@ -29,7 +33,7 @@ program
|
|
|
29
33
|
console.log('🚀 Booting Thrust Agent...');
|
|
30
34
|
|
|
31
35
|
await setupNativeStartup();
|
|
32
|
-
|
|
36
|
+
|
|
33
37
|
startDaemon(parseInt(options.port, 10));
|
|
34
38
|
});
|
|
35
39
|
|
|
@@ -52,10 +56,30 @@ program
|
|
|
52
56
|
config.activeLeadId = leadId;
|
|
53
57
|
|
|
54
58
|
saveConfig(config);
|
|
55
|
-
console.log(`✅ Successfully linked Lead
|
|
59
|
+
console.log(`✅ Successfully linked Lead[${leadId}] to ${absolutePath}`);
|
|
56
60
|
console.log(`Type 'thrust' to begin tracking.`);
|
|
57
61
|
});
|
|
58
62
|
|
|
63
|
+
// --- NEW MCP COMMAND ---
|
|
64
|
+
program
|
|
65
|
+
.command('mcp')
|
|
66
|
+
.description('Start the Thrust MCP Server over Stdio for AI clients (Claude, VS Code, Cursor)')
|
|
67
|
+
.action(async () => {
|
|
68
|
+
try {
|
|
69
|
+
// IMPORTANT: We must use console.error here.
|
|
70
|
+
// console.log prints to stdout, which corrupts the JSON-RPC stream the AI expects.
|
|
71
|
+
console.error('Starting Thrust MCP Server over Stdio...');
|
|
72
|
+
|
|
73
|
+
const transport = new StdioServerTransport();
|
|
74
|
+
await server.connect(transport);
|
|
75
|
+
|
|
76
|
+
console.error('Thrust MCP Server is ready and listening.');
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('MCP Server crashed:', error);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
59
83
|
program.parse(process.argv);
|
|
60
84
|
|
|
61
85
|
// --- NATIVE, CROSS-PLATFORM OS STARTUP LOGIC ---
|
|
@@ -71,7 +95,7 @@ async function setupNativeStartup() {
|
|
|
71
95
|
try {
|
|
72
96
|
const startupFolder = path.join(os.homedir(), 'AppData', 'Roaming', 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
|
|
73
97
|
const batFilePath = path.join(startupFolder, 'thrust-agent.bat');
|
|
74
|
-
|
|
98
|
+
|
|
75
99
|
// Clean batch script that simply calls the global `thrust` command
|
|
76
100
|
const batContent = `@echo off\ntitle Thrust Local Agent\necho Starting Thrust...\ntimeout /t 2 /nobreak > NUL\nthrust\npause`;
|
|
77
101
|
|
|
@@ -81,14 +105,14 @@ async function setupNativeStartup() {
|
|
|
81
105
|
} catch (err) {
|
|
82
106
|
console.log(`⚠️ Failed to create Windows startup script: ${err.message}`);
|
|
83
107
|
}
|
|
84
|
-
}
|
|
85
|
-
|
|
108
|
+
}
|
|
109
|
+
|
|
86
110
|
// 3. macOS - Requires absolute paths due to empty launchd $PATH
|
|
87
111
|
else if (process.platform === 'darwin') {
|
|
88
112
|
try {
|
|
89
113
|
const launchAgentsDir = path.join(os.homedir(), 'Library', 'LaunchAgents');
|
|
90
114
|
const plistPath = path.join(launchAgentsDir, 'com.thrust.agent.plist');
|
|
91
|
-
|
|
115
|
+
|
|
92
116
|
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
93
117
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
94
118
|
<plist version="1.0">
|
|
@@ -109,7 +133,7 @@ async function setupNativeStartup() {
|
|
|
109
133
|
|
|
110
134
|
if (!fs.existsSync(launchAgentsDir)) fs.mkdirSync(launchAgentsDir, { recursive: true });
|
|
111
135
|
fs.writeFileSync(plistPath, plistContent, 'utf8');
|
|
112
|
-
|
|
136
|
+
|
|
113
137
|
import('child_process').then(({ exec }) => {
|
|
114
138
|
exec(`launchctl load -w "${plistPath}"`, () => {});
|
|
115
139
|
});
|
|
@@ -117,8 +141,8 @@ async function setupNativeStartup() {
|
|
|
117
141
|
} catch (err) {
|
|
118
142
|
console.log(`⚠️ Failed to create macOS LaunchAgent: ${err.message}`);
|
|
119
143
|
}
|
|
120
|
-
}
|
|
121
|
-
|
|
144
|
+
}
|
|
145
|
+
|
|
122
146
|
// 4. Linux - Requires absolute paths for FreeDesktop compliance
|
|
123
147
|
else if (process.platform === 'linux') {
|
|
124
148
|
try {
|
|
@@ -129,7 +153,7 @@ async function setupNativeStartup() {
|
|
|
129
153
|
|
|
130
154
|
const autostartDir = path.join(os.homedir(), '.config', 'autostart');
|
|
131
155
|
const desktopPath = path.join(autostartDir, 'thrust-agent.desktop');
|
|
132
|
-
|
|
156
|
+
|
|
133
157
|
const desktopContent = `[Desktop Entry]
|
|
134
158
|
Type=Application
|
|
135
159
|
Exec="${process.execPath}" "${__filename}"
|