thrust-cli 1.0.16 → 1.0.18

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.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thrust-cli",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "description": "The local agent for Thrust AI Director",
5
5
  "type": "module",
6
6
  "homepage": "https://thrust.web.app",
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;
@@ -125,9 +125,9 @@ export async function startDaemon(preferredPort) {
125
125
  };
126
126
 
127
127
  app.use(cors(corsOptionsDelegate));
128
- app.use(express.json());
128
+ app.use(express.json({ limit: '50mb' }));
129
129
 
130
- const frontendPath = path.join(__dirname, '..', 'frontend');
130
+ const frontendPath = path.join(__dirname, '..', 'build');
131
131
 
132
132
  // ==========================================
133
133
  // MCP SERVER ENDPOINTS (FEED-IN PUSH)
@@ -138,7 +138,6 @@ 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
141
  const prd = req.query.prd || 'true';
143
142
  const thrust = req.query.thrust || 'true';
144
143
  const timeline = req.query.timeline || 'true';
@@ -148,7 +147,6 @@ export async function startDaemon(preferredPort) {
148
147
  }
149
148
 
150
149
  try {
151
- // Forward parameters to new combined gateway endpoint
152
150
  const response = await fetch(`${API_URL}/api/projects/${projectId}/mcp-context?prd=${prd}&thrust=${thrust}&timeline=${timeline}`, {
153
151
  headers: { 'Authorization': `Bearer ${token}` }
154
152
  });
@@ -200,7 +198,7 @@ export async function startDaemon(preferredPort) {
200
198
  saveConfig(config);
201
199
 
202
200
  pollExternalMCPServers();
203
- fetchInitialMCPContext({ name, url, type: 'http' });
201
+ fetchInitialMCPContext({ name, url, type: 'http' });
204
202
  res.json({ success: true });
205
203
  });
206
204
 
@@ -214,6 +212,52 @@ export async function startDaemon(preferredPort) {
214
212
  res.json({ success: true });
215
213
  });
216
214
 
215
+ app.get('/api/mcp/check-unity', (req, res) => {
216
+ const config = getConfig();
217
+ const projectId = config.activeLeadId;
218
+ if (!projectId || !config.leads[projectId]?.path) {
219
+ return res.status(400).json({ error: "No active project linked." });
220
+ }
221
+ const activePath = config.leads[projectId].path;
222
+
223
+ // Editor folder requirement ensures Unity treats the file as an Editor script
224
+ const expected1 = path.join(activePath, 'Assets', 'Thrust', 'Editor', 'ThrustMCPBridge.cs');
225
+ const expected2 = path.join(activePath, 'Assets', 'Editor', 'Thrust', 'ThrustMCPBridge.cs');
226
+
227
+ const exists = fs.existsSync(expected1) || fs.existsSync(expected2);
228
+ res.json({ exists });
229
+ });
230
+
231
+ app.post('/api/mcp/install-unity', async (req, res) => {
232
+ const config = getConfig();
233
+ const projectId = config.activeLeadId;
234
+ if (!projectId || !config.leads[projectId]?.path) {
235
+ return res.status(400).json({ error: "No active project linked." });
236
+ }
237
+ const activePath = config.leads[projectId].path;
238
+ const sourceScript = path.join(__dirname, '..', 'mcps', 'ThrustMCPBridge.cs');
239
+
240
+ if (!fs.existsSync(sourceScript)) {
241
+ return res.status(500).json({ error: "Source ThrustMCPBridge.cs not found in thrust-cli/mcps/" });
242
+ }
243
+
244
+ const targetDir = path.join(activePath, 'Assets', 'Thrust', 'Editor');
245
+
246
+ try {
247
+ if (!fs.existsSync(targetDir)) {
248
+ fs.mkdirSync(targetDir, { recursive: true });
249
+ }
250
+ const targetScript = path.join(targetDir, 'ThrustMCPBridge.cs');
251
+ fs.copyFileSync(sourceScript, targetScript);
252
+
253
+ broadcastLocalLog('system', `✅ Injected ThrustMCPBridge.cs into ${targetDir}`);
254
+ res.json({ success: true, path: targetScript });
255
+ } catch (e) {
256
+ broadcastLocalLog('error', `Failed to inject Unity script: ${e.message}`);
257
+ res.status(500).json({ error: e.message });
258
+ }
259
+ });
260
+
217
261
  // ==========================================
218
262
  // EXTERNAL MCP DIRECT QUERY (AI OR MANUAL)
219
263
  // ==========================================
@@ -327,6 +371,10 @@ export async function startDaemon(preferredPort) {
327
371
  const data = await response.json();
328
372
  if (!response.ok) throw new Error(data.error);
329
373
 
374
+ // 👉 NEW: Force the buffer to have content so syncContext doesn't abort!
375
+ // This tells the AI exactly what you just checked off manually.
376
+ fileActivityBuffer += `\n[${new Date().toLocaleTimeString()}] [USER ACTION] Manually marked task as complete: "${taskTitle}"\n`;
377
+
330
378
  if (config.leads[projectId]?.path) {
331
379
  if (inactivityTimer) clearTimeout(inactivityTimer);
332
380
  syncContext(config.leads[projectId].path);
@@ -455,7 +503,12 @@ export async function startDaemon(preferredPort) {
455
503
  const data = JSON.parse(message.toString());
456
504
  if (data.type === 'frontend_prompt') {
457
505
  if (globalWs && globalWs.readyState === WebSocket.OPEN) {
458
- globalWs.send(JSON.stringify({ type: 'prompt', content: data.payload, projectId: getActiveProject()?.id }));
506
+ globalWs.send(JSON.stringify({
507
+ type: 'prompt',
508
+ content: data.payload,
509
+ image: data.image,
510
+ projectId: getActiveProject()?.id
511
+ }));
459
512
  broadcastLocalLog('sync', `Prompt sent to Cloud Director...`);
460
513
  } else {
461
514
  broadcastLocalLog('error', `⚠️ Cannot send: Cloud Gateway is offline.`);
@@ -535,13 +588,11 @@ async function startWatching(projectPath) {
535
588
 
536
589
  fileActivityBuffer = "";
537
590
 
538
- // Fetch initial MCP states immediately upon watching a project
539
591
  const config = getConfig();
540
592
  if (config.mcpServers && config.mcpServers.length > 0) {
541
593
  config.mcpServers.forEach(server => fetchInitialMCPContext(server));
542
594
  }
543
595
 
544
- // Start the active polling loop for external services
545
596
  if (mcpPollTimer) clearInterval(mcpPollTimer);
546
597
  mcpPollTimer = setInterval(pollExternalMCPServers, MCP_POLL_INTERVAL_MS);
547
598
 
@@ -549,11 +600,37 @@ async function startWatching(projectPath) {
549
600
  ignored:[
550
601
  /(^|[\/\\])\../,
551
602
  '**/node_modules/**', '**/dist/**', '**/build/**',
552
- // Ignore noisy Unity cache & build folders
553
603
  '**/Library/**', '**/Temp/**', '**/Logs/**', '**/obj/**', '**/ProjectSettings/**'
554
604
  ],
555
605
  persistent: true,
556
- ignoreInitial: true
606
+ ignoreInitial: true,
607
+ ignorePermissionErrors: true // NEW: Ignores restricted Android/Termux subfolders safely
608
+ });
609
+
610
+ // NEW: FATAL ERROR HANDLER
611
+ // Catches EACCES constraints before Node.js crashes and forces a UI unlink
612
+ currentWatcher.on('error', async (error) => {
613
+ const isPermissionError = error.code === 'EACCES' || error.code === 'EPERM';
614
+ const msg = isPermissionError
615
+ ? `Permission Denied: Cannot watch folder. Access Denied by OS.`
616
+ : `Watcher Error: ${error.message}`;
617
+
618
+ broadcastLocalLog('error', msg);
619
+
620
+ // Force unlink the bad folder
621
+ const cfg = getConfig();
622
+ cfg.activeLeadId = null;
623
+ saveConfig(cfg);
624
+
625
+ if (inactivityTimer) { clearTimeout(inactivityTimer); inactivityTimer = null; }
626
+ if (mcpPollTimer) { clearInterval(mcpPollTimer); mcpPollTimer = null; }
627
+ if (currentWatcher) {
628
+ await currentWatcher.close().catch(() => {});
629
+ currentWatcher = null;
630
+ }
631
+
632
+ // Signal the frontend to drop the user back to the setup wizard
633
+ broadcastLocalLog('force_unlink', 'Folder access denied. Project unlinked safely.');
557
634
  });
558
635
 
559
636
  currentWatcher.on('all', (event, filePath) => {
@@ -563,7 +640,9 @@ async function startWatching(projectPath) {
563
640
 
564
641
  triggerDebouncedSync();
565
642
  });
566
- } catch (err) {}
643
+ } catch (err) {
644
+ broadcastLocalLog('error', `Failed to initialize watcher: ${err.message}`);
645
+ }
567
646
  }
568
647
 
569
648
  function connectWebSocket() {
@@ -589,7 +668,6 @@ function connectWebSocket() {
589
668
  try {
590
669
  const msg = JSON.parse(data.toString());
591
670
 
592
- // NEW: Handle dynamic MCP Queries pushed from the AI Director
593
671
  if (msg.type === 'mcp_query' && msg.payload) {
594
672
  const { serverName, toolName, targetArg } = msg.payload;
595
673
  const targetServer = getConfig().mcpServers?.find(s => s.name.toLowerCase() === serverName.toLowerCase());