thrust-cli 1.0.9 → 1.0.11
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 +480 -343
- package/frontend/index2.html +468 -0
- package/package.json +1 -3
- package/utils/config.js +13 -3
- package/utils/daemon.js +416 -107
- package/utils/daemon2.js +321 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Thrust Local Dashboard</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg-main: #050508;
|
|
10
|
+
--bg-panel: #0F0F16;
|
|
11
|
+
--bg-explorer: #0A0A0F;
|
|
12
|
+
--bg-hover: #16161E;
|
|
13
|
+
--border-color: #22222E;
|
|
14
|
+
--text-main: #F3F4F6;
|
|
15
|
+
--text-muted: #9CA3AF;
|
|
16
|
+
--primary: #8B5CF6;
|
|
17
|
+
--primary-hover: #7C3AED;
|
|
18
|
+
--success: #10B981;
|
|
19
|
+
--danger: #EF4444;
|
|
20
|
+
--warning: #F59E0B;
|
|
21
|
+
--sync: #3B82F6;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
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
|
+
|
|
26
|
+
/* --- TOP NAV BAR --- */
|
|
27
|
+
.navbar {
|
|
28
|
+
background-color: rgba(15, 15, 22, 0.8); backdrop-filter: blur(10px); border-bottom: 1px solid var(--border-color);
|
|
29
|
+
padding: 1rem 2rem; display: flex; justify-content: space-between; align-items: center; position: sticky; top: 0; z-index: 100;
|
|
30
|
+
}
|
|
31
|
+
.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;}
|
|
32
|
+
|
|
33
|
+
.profile-wrapper { position: relative; display: none; }
|
|
34
|
+
.profile-btn {
|
|
35
|
+
width: 36px; height: 36px; border-radius: 50%;
|
|
36
|
+
background: linear-gradient(to top right, #7C3AED, #2563EB);
|
|
37
|
+
color: white; font-weight: bold; font-size: 14px;
|
|
38
|
+
display: flex; justify-content: center; align-items: center;
|
|
39
|
+
cursor: pointer; border: 2px solid var(--bg-main);
|
|
40
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.5); transition: transform 0.2s;
|
|
41
|
+
}
|
|
42
|
+
.profile-btn:hover { transform: scale(1.05); }
|
|
43
|
+
|
|
44
|
+
.profile-dropdown {
|
|
45
|
+
position: absolute; right: 0; top: 48px;
|
|
46
|
+
background-color: var(--bg-panel); border: 1px solid var(--border-color);
|
|
47
|
+
border-radius: 12px; width: 240px; box-shadow: 0 10px 30px rgba(0,0,0,0.8);
|
|
48
|
+
display: none; flex-direction: column; overflow: hidden; z-index: 101;
|
|
49
|
+
}
|
|
50
|
+
.dropdown-header { padding: 1rem; border-bottom: 1px solid var(--border-color); background-color: rgba(0,0,0,0.2); }
|
|
51
|
+
.dropdown-header span { display: block; font-size: 0.7rem; font-weight: bold; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 4px; }
|
|
52
|
+
.dropdown-header strong { display: block; font-size: 0.9rem; color: white; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
53
|
+
.dropdown-body { padding: 0.5rem; }
|
|
54
|
+
.dropdown-item {
|
|
55
|
+
width: 100%; text-align: left; background: transparent; border: none;
|
|
56
|
+
padding: 0.75rem 1rem; color: var(--danger); font-size: 0.85rem; font-weight: 600;
|
|
57
|
+
border-radius: 8px; cursor: pointer; transition: background 0.2s;
|
|
58
|
+
}
|
|
59
|
+
.dropdown-item:hover { background-color: rgba(239, 68, 68, 0.1); }
|
|
60
|
+
|
|
61
|
+
.container { max-width: 800px; margin: 2rem auto; padding: 0 1.5rem; }
|
|
62
|
+
h2 { font-size: 1.1rem; margin-bottom: 1rem; color: var(--text-main); border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem; }
|
|
63
|
+
.auth-box, .status-box { background-color: var(--bg-panel); border: 1px solid var(--border-color); padding: 1.5rem; border-radius: 12px; margin-bottom: 2rem; }
|
|
64
|
+
|
|
65
|
+
.status-item { display: flex; align-items: center; margin-bottom: 0.75rem; font-weight: 500; justify-content: space-between; }
|
|
66
|
+
.status-item:last-child { margin-bottom: 0; }
|
|
67
|
+
.status-content { display: flex; align-items: center; word-break: break-all; padding-right: 1rem; }
|
|
68
|
+
.status-dot { width: 12px; height: 12px; border-radius: 50%; margin-right: 12px; flex-shrink: 0; }
|
|
69
|
+
.dot-red { background-color: var(--danger); box-shadow: 0 0 8px rgba(239, 68, 68, 0.4); }
|
|
70
|
+
.dot-green { background-color: var(--success); box-shadow: 0 0 8px rgba(16, 185, 129, 0.4); }
|
|
71
|
+
.dot-yellow { background-color: var(--warning); box-shadow: 0 0 8px rgba(245, 158, 11, 0.4); }
|
|
72
|
+
|
|
73
|
+
.btn-outline-danger { background-color: transparent; color: var(--danger); border: 1px solid var(--danger); padding: 0.4rem 0.8rem; border-radius: 6px; font-size: 0.85rem; cursor: pointer; transition: all 0.2s; white-space: nowrap; }
|
|
74
|
+
.btn-outline-danger:hover { background-color: var(--danger); color: white; }
|
|
75
|
+
|
|
76
|
+
.btn-primary { background-color: var(--primary); color: white; border: none; padding: 0.8rem 1.5rem; border-radius: 8px; font-size: 1rem; font-weight: bold; cursor: pointer; display: flex; align-items: center; justify-content: center; width: 100%; transition: background-color 0.2s;}
|
|
77
|
+
.btn-primary:hover { background-color: var(--primary-hover); }
|
|
78
|
+
|
|
79
|
+
.input-group { margin-bottom: 1.5rem; display: flex; gap: 10px; flex-direction: column; }
|
|
80
|
+
.input-group label { font-size: 0.85rem; font-weight: bold; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1px; }
|
|
81
|
+
input[type="text"] { 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; }
|
|
82
|
+
input[type="text"]:focus { outline: none; border-color: var(--primary); }
|
|
83
|
+
|
|
84
|
+
/* CLOUD PROJECTS GRID */
|
|
85
|
+
.projects-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }
|
|
86
|
+
.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; }
|
|
87
|
+
.project-card:hover { border-color: var(--primary); background-color: #1A1A24; transform: translateY(-2px); }
|
|
88
|
+
.project-card.selected { border-color: var(--success); background-color: rgba(16, 185, 129, 0.1); }
|
|
89
|
+
.pc-title { font-weight: bold; font-size: 1.1rem; margin-bottom: 0.5rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: white;}
|
|
90
|
+
.pc-desc { font-size: 0.85rem; color: var(--text-muted); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 1rem; flex-grow: 1;}
|
|
91
|
+
.pc-meta { display: flex; justify-content: space-between; font-size: 0.75rem; color: #555; font-family: monospace; border-top: 1px solid var(--border-color); padding-top: 0.75rem;}
|
|
92
|
+
|
|
93
|
+
.pagination { display: flex; justify-content: center; align-items: center; gap: 1rem; margin-bottom: 2rem; }
|
|
94
|
+
.page-btn { background: var(--bg-panel); border: 1px solid var(--border-color); color: var(--text-main); padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; font-weight: bold; }
|
|
95
|
+
.page-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
96
|
+
.page-btn:not(:disabled):hover { background: var(--bg-hover); }
|
|
97
|
+
.page-info { font-size: 0.85rem; color: var(--text-muted); font-family: monospace; }
|
|
98
|
+
|
|
99
|
+
/* File Explorer */
|
|
100
|
+
.explorer-container { background-color: var(--bg-explorer); border: 1px solid var(--border-color); border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; margin-bottom: 2rem; }
|
|
101
|
+
.explorer-header { background-color: var(--bg-panel); padding: 1rem; border-bottom: 1px solid var(--border-color); font-family: monospace; font-size: 0.85rem; color: var(--primary); word-break: break-all; }
|
|
102
|
+
.explorer-body { height: 250px; overflow-y: auto; }
|
|
103
|
+
.folder-item { display: flex; justify-content: space-between; align-items: center; padding: 0.6rem 1rem; border-bottom: 1px solid #1A1A24; transition: background-color 0.2s; }
|
|
104
|
+
.folder-item:hover { background-color: var(--bg-hover); }
|
|
105
|
+
.folder-info { display: flex; align-items: center; flex-grow: 1; overflow: hidden; }
|
|
106
|
+
.folder-icon { margin-right: 12px; font-size: 1.2rem; flex-shrink: 0; }
|
|
107
|
+
.folder-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 0.95rem; }
|
|
108
|
+
.folder-actions { display: flex; gap: 0.5rem; flex-shrink: 0; margin-left: 10px; }
|
|
109
|
+
.btn-small { padding: 0.4rem 0.8rem; border-radius: 4px; border: none; font-size: 0.85rem; font-weight: bold; cursor: pointer; }
|
|
110
|
+
.btn-open { background-color: #2D2D3D; color: #F3F4F6; }
|
|
111
|
+
.btn-open:hover { background-color: #3F3F5A; }
|
|
112
|
+
.btn-watch { background-color: var(--primary); color: white; }
|
|
113
|
+
.btn-watch:hover { background-color: var(--primary-hover); }
|
|
114
|
+
.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
|
+
|
|
116
|
+
/* Terminal */
|
|
117
|
+
.terminal-container { background-color: var(--bg-panel); border: 1px solid var(--border-color); border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; }
|
|
118
|
+
.log-box { height: 200px; overflow-y: auto; padding: 1rem; font-family: monospace; font-size: 0.85rem; color: var(--text-muted); }
|
|
119
|
+
.log-box p { margin: 0 0 0.5rem 0; line-height: 1.4; word-break: break-all; }
|
|
120
|
+
.log-time { color: var(--primary); margin-right: 8px; flex-shrink: 0; }
|
|
121
|
+
.prompt-bar { display: flex; border-top: 1px solid var(--border-color); background-color: #12121A; padding: 0.5rem; }
|
|
122
|
+
.prompt-input { flex-grow: 1; background: transparent; border: none; color: white; padding: 0.5rem; font-family: monospace; font-size: 0.9rem; outline: none; }
|
|
123
|
+
.prompt-btn { background-color: var(--primary); color: white; border: none; border-radius: 4px; padding: 0.5rem 1rem; cursor: pointer; font-weight: bold; }
|
|
124
|
+
.prompt-btn:hover { background-color: var(--primary-hover); }
|
|
125
|
+
|
|
126
|
+
.hidden { display: none !important; }
|
|
127
|
+
</style>
|
|
128
|
+
</head>
|
|
129
|
+
<body>
|
|
130
|
+
|
|
131
|
+
<nav class="navbar">
|
|
132
|
+
<div class="nav-logo">⚡ THRUST</div>
|
|
133
|
+
<div class="profile-wrapper" id="profile-wrapper">
|
|
134
|
+
<div class="profile-btn" id="profile-btn" onclick="toggleDropdown()">?</div>
|
|
135
|
+
<div class="profile-dropdown" id="profile-dropdown">
|
|
136
|
+
<div class="dropdown-header">
|
|
137
|
+
<span>Local Agent</span>
|
|
138
|
+
<strong id="auth-email-display">Authenticated</strong>
|
|
139
|
+
</div>
|
|
140
|
+
<div class="dropdown-body">
|
|
141
|
+
<button class="dropdown-item" onclick="logout()">Unlink Account</button>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</nav>
|
|
146
|
+
|
|
147
|
+
<div class="container">
|
|
148
|
+
<!-- AUTH SECTION -->
|
|
149
|
+
<div id="auth-panel" class="auth-box">
|
|
150
|
+
<h2>Account Connection</h2>
|
|
151
|
+
<div id="unauth-view">
|
|
152
|
+
<p style="font-size: 0.9rem; color: var(--text-muted); margin-bottom: 1rem;">
|
|
153
|
+
Authenticate with your official Thrust account to link this device.
|
|
154
|
+
</p>
|
|
155
|
+
<button class="btn-primary" onclick="openRemoteAuth()">🚀 Login with Thrust Web</button>
|
|
156
|
+
</div>
|
|
157
|
+
<div id="auth-view" class="hidden">
|
|
158
|
+
<div class="status-item">
|
|
159
|
+
<div class="status-content">
|
|
160
|
+
<div class="status-dot dot-green"></div>
|
|
161
|
+
<span>Agent is online and linked to Cloud.</span>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<!-- MAIN WORKSPACE SECTION -->
|
|
168
|
+
<div id="workspace-panel" class="hidden">
|
|
169
|
+
<div class="status-box">
|
|
170
|
+
<div class="status-item">
|
|
171
|
+
<div class="status-content">
|
|
172
|
+
<div id="proj-dot" class="status-dot dot-yellow"></div>
|
|
173
|
+
<span id="proj-status">No active project linked</span>
|
|
174
|
+
</div>
|
|
175
|
+
<button id="btn-unlink" class="btn-outline-danger hidden" onclick="unlinkProject()">Stop Watching</button>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<h2>1. Select Cloud Project</h2>
|
|
180
|
+
<div id="cloud-projects-loading" style="text-align: center; padding: 2rem; color: var(--text-muted); font-family: monospace;">Fetching remote projects...</div>
|
|
181
|
+
<div id="cloud-projects-grid" class="projects-grid"></div>
|
|
182
|
+
|
|
183
|
+
<div id="pagination-controls" class="pagination hidden">
|
|
184
|
+
<button id="btn-prev" class="page-btn" onclick="changePage(-1)">Prev</button>
|
|
185
|
+
<span id="page-indicator" class="page-info">Page 1 of 1</span>
|
|
186
|
+
<button id="btn-next" class="page-btn" onclick="changePage(1)">Next</button>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div class="input-group">
|
|
190
|
+
<label for="lead-id">Target Lead ID</label>
|
|
191
|
+
<input id="lead-id" type="text" placeholder="Click a project above or paste an ID...">
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<h2>2. Select Local Folder to Watch</h2>
|
|
195
|
+
<div class="explorer-container">
|
|
196
|
+
<div class="explorer-header">
|
|
197
|
+
<span style="color: var(--text-muted);">Current Path:</span>
|
|
198
|
+
<strong id="current-path">Loading...</strong>
|
|
199
|
+
</div>
|
|
200
|
+
<div id="directory-list" class="explorer-body"></div>
|
|
201
|
+
<div class="explorer-footer">
|
|
202
|
+
<span style="font-size: 0.85rem; color: var(--text-muted);">Watch the folder you are currently inside?</span>
|
|
203
|
+
<button class="btn-small btn-watch" onclick="submitWatch(currentExplorerPath, currentFolderName)" id="select-current-btn">Watch Current</button>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<h2>Agent Terminal</h2>
|
|
209
|
+
<div class="terminal-container">
|
|
210
|
+
<div id="log" class="log-box"></div>
|
|
211
|
+
<form id="prompt-form" class="prompt-bar" onsubmit="sendPrompt(event)">
|
|
212
|
+
<input type="text" id="prompt-input" class="prompt-input" placeholder="> Message AI Director..." autocomplete="off">
|
|
213
|
+
<button type="submit" class="prompt-btn">Send</button>
|
|
214
|
+
</form>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<script>
|
|
219
|
+
const logBox = document.getElementById('log');
|
|
220
|
+
let currentExplorerPath = "";
|
|
221
|
+
let currentFolderName = "";
|
|
222
|
+
let localWs = null;
|
|
223
|
+
|
|
224
|
+
let currentPage = 1;
|
|
225
|
+
let totalPages = 1;
|
|
226
|
+
let currentProjects = [];
|
|
227
|
+
|
|
228
|
+
function addLog(message, color = "var(--text-muted)") {
|
|
229
|
+
const p = document.createElement('p');
|
|
230
|
+
p.style.display = 'flex';
|
|
231
|
+
const timeSpan = document.createElement('span');
|
|
232
|
+
timeSpan.className = 'log-time';
|
|
233
|
+
timeSpan.textContent = `[${new Date().toLocaleTimeString()}]`;
|
|
234
|
+
const msgSpan = document.createElement('span');
|
|
235
|
+
msgSpan.style.color = color;
|
|
236
|
+
msgSpan.textContent = message;
|
|
237
|
+
p.appendChild(timeSpan);
|
|
238
|
+
p.appendChild(msgSpan);
|
|
239
|
+
logBox.appendChild(p);
|
|
240
|
+
logBox.scrollTop = logBox.scrollHeight;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function toggleDropdown() {
|
|
244
|
+
const dropdown = document.getElementById('profile-dropdown');
|
|
245
|
+
dropdown.style.display = dropdown.style.display === 'flex' ? 'none' : 'flex';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
window.onclick = function(event) {
|
|
249
|
+
if (!event.target.matches('.profile-btn')) {
|
|
250
|
+
const dropdowns = document.getElementsByClassName("profile-dropdown");
|
|
251
|
+
for (let i = 0; i < dropdowns.length; i++) {
|
|
252
|
+
let openDropdown = dropdowns[i];
|
|
253
|
+
if (openDropdown.style.display === 'flex') openDropdown.style.display = 'none';
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function openRemoteAuth() {
|
|
259
|
+
const localPort = window.location.port || '8765';
|
|
260
|
+
const authUrl = `https://thrust.web.app/auth?localPort=${localPort}`;
|
|
261
|
+
addLog("Opening Official Thrust Authentication...", "var(--warning)");
|
|
262
|
+
window.open(authUrl, 'ThrustAuth', 'width=500,height=700');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function logout() {
|
|
266
|
+
try {
|
|
267
|
+
await fetch('/api/auth/logout', { method: 'POST' });
|
|
268
|
+
window.location.reload();
|
|
269
|
+
} catch(e) {}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function fetchCloudProjects() {
|
|
273
|
+
const grid = document.getElementById('cloud-projects-grid');
|
|
274
|
+
const loader = document.getElementById('cloud-projects-loading');
|
|
275
|
+
const controls = document.getElementById('pagination-controls');
|
|
276
|
+
|
|
277
|
+
grid.innerHTML = '';
|
|
278
|
+
loader.style.display = 'block';
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
const res = await fetch(`/api/cloud-projects?page=${currentPage}&limit=6`);
|
|
282
|
+
const data = await res.json();
|
|
283
|
+
loader.style.display = 'none';
|
|
284
|
+
|
|
285
|
+
if (data.projects && data.projects.length > 0) {
|
|
286
|
+
currentProjects = data.projects;
|
|
287
|
+
totalPages = data.totalPages;
|
|
288
|
+
|
|
289
|
+
data.projects.forEach(proj => {
|
|
290
|
+
const card = document.createElement('div');
|
|
291
|
+
card.className = 'project-card';
|
|
292
|
+
const currentLeadInput = document.getElementById('lead-id').value;
|
|
293
|
+
if(currentLeadInput === proj.id) card.classList.add('selected');
|
|
294
|
+
|
|
295
|
+
card.onclick = () => selectCloudProject(proj.id, card);
|
|
296
|
+
|
|
297
|
+
card.innerHTML = `
|
|
298
|
+
<div class="pc-title">${proj.name}</div>
|
|
299
|
+
<div class="pc-desc">${proj.description || 'No description provided.'}</div>
|
|
300
|
+
<div class="pc-meta">
|
|
301
|
+
<span>${proj.status}</span>
|
|
302
|
+
<span>${new Date(proj.created_at).toLocaleDateString()}</span>
|
|
303
|
+
</div>
|
|
304
|
+
`;
|
|
305
|
+
grid.appendChild(card);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
if (totalPages > 1) {
|
|
309
|
+
controls.classList.remove('hidden');
|
|
310
|
+
document.getElementById('page-indicator').innerText = `Page ${currentPage} of ${totalPages}`;
|
|
311
|
+
document.getElementById('btn-prev').disabled = currentPage === 1;
|
|
312
|
+
document.getElementById('btn-next').disabled = currentPage === totalPages;
|
|
313
|
+
} else {
|
|
314
|
+
controls.classList.add('hidden');
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
grid.innerHTML = `<div style="grid-column: 1/-1; color: var(--text-muted); text-align: center; padding: 2rem;">No projects found on your cloud account. Create one in the Web Dashboard.</div>`;
|
|
318
|
+
controls.classList.add('hidden');
|
|
319
|
+
}
|
|
320
|
+
} catch (e) {
|
|
321
|
+
loader.innerText = "Error fetching cloud projects.";
|
|
322
|
+
addLog("Failed to fetch cloud projects.", "var(--danger)");
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function changePage(delta) {
|
|
327
|
+
currentPage += delta;
|
|
328
|
+
fetchCloudProjects();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function selectCloudProject(id, cardElement) {
|
|
332
|
+
document.getElementById('lead-id').value = id;
|
|
333
|
+
document.querySelectorAll('.project-card').forEach(c => c.classList.remove('selected'));
|
|
334
|
+
cardElement.classList.add('selected');
|
|
335
|
+
addLog(`Selected Cloud Project ID: ${id}`);
|
|
336
|
+
document.getElementById('current-path').scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function connectLocalWebSocket() {
|
|
340
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
341
|
+
localWs = new WebSocket(`${protocol}//${window.location.host}`);
|
|
342
|
+
localWs.onopen = () => addLog('🔌 Local Stream connected.');
|
|
343
|
+
|
|
344
|
+
localWs.onmessage = (event) => {
|
|
345
|
+
const data = JSON.parse(event.data);
|
|
346
|
+
if (data.type === 'success' && data.message.includes('Authentication Successful')) fetchStatus();
|
|
347
|
+
if (data.type === 'watch') addLog(`👀 ${data.message}`, 'var(--success)');
|
|
348
|
+
else if (data.type === 'sync') addLog(data.message, 'var(--sync)');
|
|
349
|
+
else if (data.type === 'ai') addLog(data.message, 'var(--warning)');
|
|
350
|
+
else if (data.type === 'success') addLog(data.message, 'var(--success)');
|
|
351
|
+
else if (data.type === 'error') addLog(`❌ ${data.message}`, 'var(--danger)');
|
|
352
|
+
else addLog(data.message);
|
|
353
|
+
};
|
|
354
|
+
localWs.onclose = () => { setTimeout(connectLocalWebSocket, 3000); };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function sendPrompt(event) {
|
|
358
|
+
event.preventDefault();
|
|
359
|
+
const input = document.getElementById('prompt-input');
|
|
360
|
+
const message = input.value.trim();
|
|
361
|
+
if (!message || !localWs || localWs.readyState !== WebSocket.OPEN) return;
|
|
362
|
+
addLog(`> You: ${message}`, '#FFFFFF');
|
|
363
|
+
localWs.send(JSON.stringify({ type: 'frontend_prompt', payload: message }));
|
|
364
|
+
input.value = '';
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function fetchStatus() {
|
|
368
|
+
try {
|
|
369
|
+
const response = await fetch('/api/status');
|
|
370
|
+
const data = await response.json();
|
|
371
|
+
|
|
372
|
+
if (data.auth && data.auth.token) {
|
|
373
|
+
document.getElementById('unauth-view').classList.add('hidden');
|
|
374
|
+
document.getElementById('auth-view').classList.remove('hidden');
|
|
375
|
+
document.getElementById('workspace-panel').classList.remove('hidden');
|
|
376
|
+
|
|
377
|
+
// --- NEW: INJECT EMAIL INTO UI ---
|
|
378
|
+
const userEmail = data.auth.email || "Linked User";
|
|
379
|
+
document.getElementById('profile-wrapper').style.display = 'block';
|
|
380
|
+
document.getElementById('auth-email-display').innerText = userEmail;
|
|
381
|
+
document.getElementById('profile-btn').innerText = userEmail.charAt(0).toUpperCase();
|
|
382
|
+
|
|
383
|
+
fetchCloudProjects();
|
|
384
|
+
} else {
|
|
385
|
+
document.getElementById('unauth-view').classList.remove('hidden');
|
|
386
|
+
document.getElementById('auth-view').classList.add('hidden');
|
|
387
|
+
document.getElementById('workspace-panel').classList.add('hidden');
|
|
388
|
+
document.getElementById('profile-wrapper').style.display = 'none';
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (data.activeProject) {
|
|
392
|
+
document.getElementById('proj-dot').className = 'status-dot dot-green';
|
|
393
|
+
document.getElementById('proj-status').textContent = `Watching: ${data.activeProject.path}`;
|
|
394
|
+
document.getElementById('btn-unlink').classList.remove('hidden');
|
|
395
|
+
document.getElementById('lead-id').value = data.activeProject.id;
|
|
396
|
+
} else {
|
|
397
|
+
document.getElementById('proj-dot').className = 'status-dot dot-yellow';
|
|
398
|
+
document.getElementById('proj-status').textContent = `No active project linked`;
|
|
399
|
+
document.getElementById('btn-unlink').classList.add('hidden');
|
|
400
|
+
}
|
|
401
|
+
} catch (e) { addLog('Error fetching status.', 'var(--danger)'); }
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function unlinkProject() {
|
|
405
|
+
try {
|
|
406
|
+
addLog('Stopping watcher...');
|
|
407
|
+
const response = await fetch('/api/unlink', { method: 'POST' });
|
|
408
|
+
if ((await response.json()).success) {
|
|
409
|
+
document.getElementById('lead-id').value = '';
|
|
410
|
+
fetchStatus();
|
|
411
|
+
}
|
|
412
|
+
} catch (e) {}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function loadDirectory(targetPath = "") {
|
|
416
|
+
try {
|
|
417
|
+
const url = targetPath ? `/api/explore?path=${encodeURIComponent(targetPath)}` : '/api/explore';
|
|
418
|
+
const data = await (await fetch(url)).json();
|
|
419
|
+
if (data.error) return addLog(`Explorer Error: ${data.error}`, 'var(--danger)');
|
|
420
|
+
|
|
421
|
+
currentExplorerPath = data.currentPath;
|
|
422
|
+
currentFolderName = data.currentPath.split(/[/\\]/).pop() || "Root";
|
|
423
|
+
|
|
424
|
+
document.getElementById('current-path').textContent = data.currentPath;
|
|
425
|
+
document.getElementById('select-current-btn').textContent = `Watch '${currentFolderName}'`;
|
|
426
|
+
|
|
427
|
+
const dirList = document.getElementById('directory-list');
|
|
428
|
+
dirList.innerHTML = '';
|
|
429
|
+
|
|
430
|
+
if (data.parentPath) {
|
|
431
|
+
dirList.innerHTML += `<div class="folder-item">
|
|
432
|
+
<div class="folder-info"><span class="folder-icon">⬆️</span><span class="folder-text"><strong>.. (Go Up)</strong></span></div>
|
|
433
|
+
<div class="folder-actions"><button class="btn-small btn-open" onclick="loadDirectory('${data.parentPath.replace(/\\/g, '\\\\')}')">Open</button></div>
|
|
434
|
+
</div>`;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
data.directories.forEach(dir => {
|
|
438
|
+
const fullPath = `${data.currentPath}/${dir}`.replace(/\\/g, '\\\\');
|
|
439
|
+
dirList.innerHTML += `<div class="folder-item">
|
|
440
|
+
<div class="folder-info"><span class="folder-icon">📁</span><span class="folder-text">${dir}</span></div>
|
|
441
|
+
<div class="folder-actions">
|
|
442
|
+
<button class="btn-small btn-open" onclick="loadDirectory('${fullPath}')">Open 📂</button>
|
|
443
|
+
<button class="btn-small btn-watch" onclick="submitWatch('${fullPath}', '${dir}')">Watch 👁️</button>
|
|
444
|
+
</div>
|
|
445
|
+
</div>`;
|
|
446
|
+
});
|
|
447
|
+
} catch (e) {}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function submitWatch(absolutePath, folderName) {
|
|
451
|
+
const leadId = document.getElementById('lead-id').value.trim();
|
|
452
|
+
if (!leadId) { document.getElementById('lead-id').focus(); return addLog('❌ Error: Select a Cloud Project first', 'var(--danger)'); }
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
|
|
456
|
+
const res = await fetch('/api/link', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ leadId, folderPath: absolutePath }) });
|
|
457
|
+
if ((await res.json()).success) fetchStatus();
|
|
458
|
+
} catch (e) {}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
462
|
+
fetchStatus();
|
|
463
|
+
loadDirectory();
|
|
464
|
+
connectLocalWebSocket();
|
|
465
|
+
});
|
|
466
|
+
</script>
|
|
467
|
+
</body>
|
|
468
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thrust-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "The local agent for Thrust AI Director",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://thrust.web.app",
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
],
|
|
20
20
|
"author": "Thrust",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"auto-launch": "^5.0.6",
|
|
23
22
|
"axios": "^1.13.2",
|
|
24
23
|
"chokidar": "^3.6.0",
|
|
25
24
|
"commander": "^12.0.0",
|
|
@@ -27,7 +26,6 @@
|
|
|
27
26
|
"express": "5.2.1",
|
|
28
27
|
"inquirer": "^9.3.8",
|
|
29
28
|
"inquirer-file-tree-selection-prompt": "^2.0.5",
|
|
30
|
-
"jsonwebtoken": "^9.0.2",
|
|
31
29
|
"open": "11.0.0",
|
|
32
30
|
"semver": "^7.6.0",
|
|
33
31
|
"simple-git": "^3.22.0",
|
package/utils/config.js
CHANGED
|
@@ -6,9 +6,13 @@ const CONFIG_DIR = path.join(os.homedir(), '.thrust');
|
|
|
6
6
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
7
7
|
|
|
8
8
|
const DEFAULT_CONFIG = {
|
|
9
|
-
|
|
9
|
+
auth: {
|
|
10
|
+
token: null,
|
|
11
|
+
user: null
|
|
12
|
+
},
|
|
10
13
|
activeLeadId: null,
|
|
11
|
-
leads: {}
|
|
14
|
+
leads: {},
|
|
15
|
+
mcpServers: [] // NEW: Stores external MCP servers we poll (Feed-In Client)
|
|
12
16
|
};
|
|
13
17
|
|
|
14
18
|
export function initConfig() {
|
|
@@ -28,7 +32,13 @@ export function getConfig() {
|
|
|
28
32
|
initConfig();
|
|
29
33
|
try {
|
|
30
34
|
const data = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
31
|
-
|
|
35
|
+
const parsed = JSON.parse(data);
|
|
36
|
+
|
|
37
|
+
// Ensure legacy configs get the new objects injected safely
|
|
38
|
+
if (!parsed.auth) parsed.auth = { token: null, user: null };
|
|
39
|
+
if (!parsed.mcpServers) parsed.mcpServers = [];
|
|
40
|
+
|
|
41
|
+
return parsed;
|
|
32
42
|
} catch (error) {
|
|
33
43
|
console.error('⚠️ [Config Error] Corrupted config file. Returning defaults.', error.message);
|
|
34
44
|
return { ...DEFAULT_CONFIG };
|