thrust-cli 1.0.15 → 1.0.17
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/build/AuthPage.jsx +0 -0
- package/build/asset-manifest.json +13 -0
- package/build/favicon.ico +0 -0
- package/build/index.html +1 -0
- package/build/logo192.png +0 -0
- package/build/logo512.png +0 -0
- package/build/manifest.json +25 -0
- package/build/robots.txt +3 -0
- package/build/static/css/main.ebf0e7c6.css +4 -0
- package/build/static/css/main.ebf0e7c6.css.map +1 -0
- package/build/static/js/main.80b95b75.js +3 -0
- package/build/static/js/main.80b95b75.js.LICENSE.txt +56 -0
- package/build/static/js/main.80b95b75.js.map +1 -0
- package/build/thrust_black.jpg +0 -0
- package/build/thrust_equal_black.png +0 -0
- package/build/thrust_equal_white.png +0 -0
- package/build/thrust_tracing_black.png +0 -0
- package/build/thrust_tracing_white.png +0 -0
- package/build/thrust_white.jpg +0 -0
- package/frontend/index.html +232 -37
- package/mcps/projectMcpServer.js +34 -24
- package/package.json +1 -1
- package/utils/daemon.js +56 -21
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');
|
|
@@ -127,7 +127,7 @@ export async function startDaemon(preferredPort) {
|
|
|
127
127
|
app.use(cors(corsOptionsDelegate));
|
|
128
128
|
app.use(express.json());
|
|
129
129
|
|
|
130
|
-
const frontendPath = path.join(__dirname, '..', '
|
|
130
|
+
const frontendPath = path.join(__dirname, '..', 'build');
|
|
131
131
|
|
|
132
132
|
// ==========================================
|
|
133
133
|
// MCP SERVER ENDPOINTS (FEED-IN PUSH)
|
|
@@ -138,12 +138,16 @@ export async function startDaemon(preferredPort) {
|
|
|
138
138
|
const token = config.auth?.token;
|
|
139
139
|
const projectId = config.activeLeadId;
|
|
140
140
|
|
|
141
|
+
const prd = req.query.prd || 'true';
|
|
142
|
+
const thrust = req.query.thrust || 'true';
|
|
143
|
+
const timeline = req.query.timeline || 'true';
|
|
144
|
+
|
|
141
145
|
if (!token || !projectId) {
|
|
142
146
|
return res.status(401).json({ error: "Thrust agent not linked or authenticated." });
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
try {
|
|
146
|
-
const response = await fetch(`${API_URL}/api/projects/${projectId}/
|
|
150
|
+
const response = await fetch(`${API_URL}/api/projects/${projectId}/mcp-context?prd=${prd}&thrust=${thrust}&timeline=${timeline}`, {
|
|
147
151
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
148
152
|
});
|
|
149
153
|
const data = await response.json();
|
|
@@ -151,10 +155,11 @@ export async function startDaemon(preferredPort) {
|
|
|
151
155
|
res.json({
|
|
152
156
|
projectId,
|
|
153
157
|
projectPath: config.leads[projectId].path,
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
prd: data.prd,
|
|
159
|
+
activeThrust: data.thrust,
|
|
160
|
+
timeline: data.timeline
|
|
156
161
|
});
|
|
157
|
-
broadcastLocalLog('mcp', `🔗 [Context Sync]
|
|
162
|
+
broadcastLocalLog('mcp', `🔗 [Context Sync] AI Client requested project state.`);
|
|
158
163
|
} catch (e) {
|
|
159
164
|
res.status(502).json({ error: "Failed to fetch context." });
|
|
160
165
|
}
|
|
@@ -193,7 +198,7 @@ export async function startDaemon(preferredPort) {
|
|
|
193
198
|
saveConfig(config);
|
|
194
199
|
|
|
195
200
|
pollExternalMCPServers();
|
|
196
|
-
fetchInitialMCPContext({ name, url, type: 'http' });
|
|
201
|
+
fetchInitialMCPContext({ name, url, type: 'http' });
|
|
197
202
|
res.json({ success: true });
|
|
198
203
|
});
|
|
199
204
|
|
|
@@ -299,7 +304,8 @@ export async function startDaemon(preferredPort) {
|
|
|
299
304
|
}
|
|
300
305
|
});
|
|
301
306
|
|
|
302
|
-
|
|
307
|
+
|
|
308
|
+
app.post('/api/tasks/complete', async (req, res) => {
|
|
303
309
|
const { taskId, taskTitle } = req.body;
|
|
304
310
|
const config = getConfig();
|
|
305
311
|
const token = config.auth?.token;
|
|
@@ -320,6 +326,10 @@ export async function startDaemon(preferredPort) {
|
|
|
320
326
|
const data = await response.json();
|
|
321
327
|
if (!response.ok) throw new Error(data.error);
|
|
322
328
|
|
|
329
|
+
// 👉 NEW: Force the buffer to have content so syncContext doesn't abort!
|
|
330
|
+
// This tells the AI exactly what you just checked off manually.
|
|
331
|
+
fileActivityBuffer += `\n[${new Date().toLocaleTimeString()}] [USER ACTION] Manually marked task as complete: "${taskTitle}"\n`;
|
|
332
|
+
|
|
323
333
|
if (config.leads[projectId]?.path) {
|
|
324
334
|
if (inactivityTimer) clearTimeout(inactivityTimer);
|
|
325
335
|
syncContext(config.leads[projectId].path);
|
|
@@ -528,13 +538,11 @@ async function startWatching(projectPath) {
|
|
|
528
538
|
|
|
529
539
|
fileActivityBuffer = "";
|
|
530
540
|
|
|
531
|
-
// Fetch initial MCP states immediately upon watching a project
|
|
532
541
|
const config = getConfig();
|
|
533
542
|
if (config.mcpServers && config.mcpServers.length > 0) {
|
|
534
543
|
config.mcpServers.forEach(server => fetchInitialMCPContext(server));
|
|
535
544
|
}
|
|
536
545
|
|
|
537
|
-
// Start the active polling loop for external services
|
|
538
546
|
if (mcpPollTimer) clearInterval(mcpPollTimer);
|
|
539
547
|
mcpPollTimer = setInterval(pollExternalMCPServers, MCP_POLL_INTERVAL_MS);
|
|
540
548
|
|
|
@@ -542,11 +550,37 @@ async function startWatching(projectPath) {
|
|
|
542
550
|
ignored:[
|
|
543
551
|
/(^|[\/\\])\../,
|
|
544
552
|
'**/node_modules/**', '**/dist/**', '**/build/**',
|
|
545
|
-
// Ignore noisy Unity cache & build folders
|
|
546
553
|
'**/Library/**', '**/Temp/**', '**/Logs/**', '**/obj/**', '**/ProjectSettings/**'
|
|
547
554
|
],
|
|
548
555
|
persistent: true,
|
|
549
|
-
ignoreInitial: true
|
|
556
|
+
ignoreInitial: true,
|
|
557
|
+
ignorePermissionErrors: true // NEW: Ignores restricted Android/Termux subfolders safely
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// NEW: FATAL ERROR HANDLER
|
|
561
|
+
// Catches EACCES constraints before Node.js crashes and forces a UI unlink
|
|
562
|
+
currentWatcher.on('error', async (error) => {
|
|
563
|
+
const isPermissionError = error.code === 'EACCES' || error.code === 'EPERM';
|
|
564
|
+
const msg = isPermissionError
|
|
565
|
+
? `Permission Denied: Cannot watch folder. Access Denied by OS.`
|
|
566
|
+
: `Watcher Error: ${error.message}`;
|
|
567
|
+
|
|
568
|
+
broadcastLocalLog('error', msg);
|
|
569
|
+
|
|
570
|
+
// Force unlink the bad folder
|
|
571
|
+
const cfg = getConfig();
|
|
572
|
+
cfg.activeLeadId = null;
|
|
573
|
+
saveConfig(cfg);
|
|
574
|
+
|
|
575
|
+
if (inactivityTimer) { clearTimeout(inactivityTimer); inactivityTimer = null; }
|
|
576
|
+
if (mcpPollTimer) { clearInterval(mcpPollTimer); mcpPollTimer = null; }
|
|
577
|
+
if (currentWatcher) {
|
|
578
|
+
await currentWatcher.close().catch(() => {});
|
|
579
|
+
currentWatcher = null;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Signal the frontend to drop the user back to the setup wizard
|
|
583
|
+
broadcastLocalLog('force_unlink', 'Folder access denied. Project unlinked safely.');
|
|
550
584
|
});
|
|
551
585
|
|
|
552
586
|
currentWatcher.on('all', (event, filePath) => {
|
|
@@ -556,7 +590,9 @@ async function startWatching(projectPath) {
|
|
|
556
590
|
|
|
557
591
|
triggerDebouncedSync();
|
|
558
592
|
});
|
|
559
|
-
} catch (err) {
|
|
593
|
+
} catch (err) {
|
|
594
|
+
broadcastLocalLog('error', `Failed to initialize watcher: ${err.message}`);
|
|
595
|
+
}
|
|
560
596
|
}
|
|
561
597
|
|
|
562
598
|
function connectWebSocket() {
|
|
@@ -581,12 +617,11 @@ function connectWebSocket() {
|
|
|
581
617
|
globalWs.on('message', async (data) => {
|
|
582
618
|
try {
|
|
583
619
|
const msg = JSON.parse(data.toString());
|
|
584
|
-
|
|
585
|
-
// NEW: Handle dynamic MCP Queries pushed from the AI Director
|
|
620
|
+
|
|
586
621
|
if (msg.type === 'mcp_query' && msg.payload) {
|
|
587
622
|
const { serverName, toolName, targetArg } = msg.payload;
|
|
588
623
|
const targetServer = getConfig().mcpServers?.find(s => s.name.toLowerCase() === serverName.toLowerCase());
|
|
589
|
-
|
|
624
|
+
|
|
590
625
|
if (targetServer) {
|
|
591
626
|
broadcastLocalLog('mcp', `🤖 AI requested data from ${serverName} (${toolName})...`);
|
|
592
627
|
try {
|
|
@@ -605,7 +640,7 @@ function connectWebSocket() {
|
|
|
605
640
|
}
|
|
606
641
|
}
|
|
607
642
|
}
|
|
608
|
-
|
|
643
|
+
|
|
609
644
|
if (msg.type === 'toast' || msg.type === 'response') {
|
|
610
645
|
broadcastLocalLog('ai', `🔔 [AI]: ${msg.message || msg.text}`);
|
|
611
646
|
}
|