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.
@@ -19,245 +19,228 @@
19
19
  --danger: #EF4444;
20
20
  --warning: #F59E0B;
21
21
  --sync: #3B82F6;
22
+ --mcp: #06B6D4;
22
23
  }
23
24
 
24
- body {
25
- background-color: var(--bg-main);
26
- color: var(--text-main);
27
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
28
- margin: 0;
29
- padding: 1.5rem;
30
- }
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; }
26
+
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
+ .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;}
29
+
30
+ .profile-wrapper { position: relative; display: none; }
31
+ .profile-btn { width: 36px; height: 36px; border-radius: 50%; background: linear-gradient(to top right, #7C3AED, #2563EB); color: white; font-weight: bold; font-size: 14px; display: flex; justify-content: center; align-items: center; cursor: pointer; border: 2px solid var(--bg-main); box-shadow: 0 4px 12px rgba(0,0,0,0.5); transition: transform 0.2s; }
32
+ .profile-btn:hover { transform: scale(1.05); }
31
33
 
32
- .container { max-width: 800px; margin: auto; }
34
+ .profile-dropdown { position: absolute; right: 0; top: 48px; background-color: var(--bg-panel); border: 1px solid var(--border-color); border-radius: 12px; width: 240px; box-shadow: 0 10px 30px rgba(0,0,0,0.8); display: none; flex-direction: column; overflow: hidden; z-index: 101; }
35
+ .dropdown-header { padding: 1rem; border-bottom: 1px solid var(--border-color); background-color: rgba(0,0,0,0.2); }
36
+ .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; }
37
+ .dropdown-header strong { display: block; font-size: 0.9rem; color: white; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
38
+ .dropdown-body { padding: 0.5rem; }
39
+ .dropdown-item { width: 100%; text-align: left; background: transparent; border: none; padding: 0.75rem 1rem; color: var(--danger); font-size: 0.85rem; font-weight: 600; border-radius: 8px; cursor: pointer; transition: background 0.2s; }
40
+ .dropdown-item:hover { background-color: rgba(239, 68, 68, 0.1); }
33
41
 
34
- h1 { color: var(--primary); margin-top: 0; font-size: 1.8rem; }
42
+ .container { max-width: 800px; margin: 2rem auto; padding: 0 1.5rem; }
35
43
  h2 { font-size: 1.1rem; margin-bottom: 1rem; color: var(--text-main); border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem; }
36
44
 
37
- .status-box {
38
- background-color: var(--bg-panel);
39
- border: 1px solid var(--border-color);
40
- padding: 1rem;
41
- border-radius: 8px;
42
- margin-bottom: 2rem;
43
- }
45
+ .auth-box, .status-box { background-color: var(--bg-panel); border: 1px solid var(--border-color); padding: 1.5rem; border-radius: 12px; margin-bottom: 2rem; }
44
46
 
45
- .status-item { display: flex; align-items: center; margin-bottom: 0.75rem; font-weight: 500; }
46
- .status-item:last-child { margin-bottom: 0; justify-content: space-between; }
47
-
47
+ .status-item { display: flex; align-items: center; margin-bottom: 0.75rem; font-weight: 500; justify-content: space-between; }
48
+ .status-item:last-child { margin-bottom: 0; }
48
49
  .status-content { display: flex; align-items: center; word-break: break-all; padding-right: 1rem; }
49
-
50
50
  .status-dot { width: 12px; height: 12px; border-radius: 50%; margin-right: 12px; flex-shrink: 0; }
51
51
  .dot-red { background-color: var(--danger); box-shadow: 0 0 8px rgba(239, 68, 68, 0.4); }
52
52
  .dot-green { background-color: var(--success); box-shadow: 0 0 8px rgba(16, 185, 129, 0.4); }
53
53
  .dot-yellow { background-color: var(--warning); box-shadow: 0 0 8px rgba(245, 158, 11, 0.4); }
54
54
 
55
- .btn-unlink {
56
- background-color: transparent;
57
- color: var(--danger);
58
- border: 1px solid var(--danger);
59
- padding: 0.4rem 0.8rem;
60
- border-radius: 6px;
61
- font-size: 0.85rem;
62
- cursor: pointer;
63
- transition: all 0.2s;
64
- display: none;
65
- white-space: nowrap;
66
- }
67
- .btn-unlink:hover { background-color: var(--danger); color: white; }
68
-
69
- .input-group { margin-bottom: 1.5rem; }
70
- .input-group label { display: block; margin-bottom: 0.5rem; font-size: 0.9rem; color: var(--text-muted); }
71
- input[type="text"] {
72
- width: 100%;
73
- background-color: var(--bg-hover);
74
- color: var(--text-main);
75
- border: 1px solid var(--border-color);
76
- padding: 0.8rem;
77
- border-radius: 6px;
78
- font-size: 1rem;
79
- box-sizing: border-box;
80
- }
81
- input[type="text"]:focus { outline: none; border-color: var(--primary); }
82
-
83
- .explorer-container {
84
- background-color: var(--bg-explorer);
85
- border: 1px solid var(--border-color);
86
- border-radius: 8px;
87
- display: flex;
88
- flex-direction: column;
89
- overflow: hidden;
90
- margin-bottom: 2rem;
91
- }
92
-
93
- .explorer-header {
94
- background-color: var(--bg-panel);
95
- padding: 1rem;
96
- border-bottom: 1px solid var(--border-color);
97
- font-family: monospace;
98
- font-size: 0.85rem;
99
- color: var(--primary);
100
- word-break: break-all;
101
- }
102
-
103
- .explorer-body {
104
- height: 300px;
105
- overflow-y: auto;
106
- }
107
-
108
- .folder-item {
109
- display: flex;
110
- justify-content: space-between;
111
- align-items: center;
112
- padding: 0.6rem 1rem;
113
- border-bottom: 1px solid #1A1A24;
114
- transition: background-color 0.2s;
115
- }
55
+ .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; }
56
+ .btn-outline-danger:hover { background-color: var(--danger); color: white; }
57
+
58
+ .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;}
59
+ .btn-primary:hover { background-color: var(--primary-hover); }
60
+
61
+ .btn-override { background: linear-gradient(135deg, var(--warning), #D97706); color: #fff; border: none; padding: 0.5rem 1rem; border-radius: 6px; font-weight: bold; cursor: pointer; font-size: 0.85rem; display: flex; align-items: center; gap: 6px; transition: transform 0.2s; }
62
+ .btn-override:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3); }
63
+
64
+ .directive-card { background-color: var(--bg-panel); border: 1px solid var(--border-color); border-radius: 12px; margin-bottom: 2rem; overflow: hidden; }
65
+ .directive-header { padding: 1.2rem; border-bottom: 1px solid var(--border-color); display: flex; align-items: center; justify-content: space-between; }
66
+ .directive-header h3 { margin: 0; font-size: 1.1rem; color: var(--text-main); }
67
+ .directive-icon { background: rgba(139, 92, 246, 0.1); color: var(--primary); padding: 8px; border-radius: 8px; display: flex; align-items: center; justify-content: center; }
68
+
69
+ .directive-body { padding: 1.5rem; }
70
+ .target-container { background-color: var(--bg-explorer); border: 1px solid var(--border-color); border-radius: 8px; padding: 1rem; margin-bottom: 1.5rem;}
71
+ .target-label { font-size: 0.75rem; text-transform: uppercase; color: var(--text-muted); letter-spacing: 1px; font-weight: bold; margin-bottom: 10px; display: block; }
72
+ .pills-row { display: flex; flex-wrap: wrap; gap: 8px; }
73
+ .target-pill { font-family: monospace; font-size: 0.85rem; padding: 6px 12px; border-radius: 6px; border: 1px solid; display: inline-flex; align-items: center; gap: 6px;}
74
+ .pill-create { color: var(--success); background-color: rgba(16, 185, 129, 0.1); border-color: rgba(16, 185, 129, 0.3); }
75
+ .pill-modify { color: var(--warning); background-color: rgba(245, 158, 11, 0.1); border-color: rgba(245, 158, 11, 0.3); }
76
+
77
+ .directive-text { font-size: 1rem; line-height: 1.6; color: var(--text-muted); }
78
+ .directive-text strong { color: var(--text-main); font-weight: 600; }
79
+
80
+ .checklist-container { background-color: var(--bg-panel); border-top: 1px solid var(--border-color); padding: 1.5rem; }
81
+ .task-item { display: flex; align-items: flex-start; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--border-color); }
82
+ .task-item:last-child { border-bottom: none; padding-bottom: 0; }
83
+ .task-checkbox { margin-top: 4px; width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary); }
84
+ .task-text { font-size: 0.95rem; color: var(--text-main); transition: color 0.3s; }
85
+ .task-done { text-decoration: line-through; color: var(--text-muted); }
86
+
87
+ .input-group { margin-bottom: 1.5rem; display: flex; gap: 10px; flex-direction: column; }
88
+ .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); }
91
+
92
+ .projects-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }
93
+ .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; }
94
+ .project-card:hover { border-color: var(--primary); background-color: #1A1A24; transform: translateY(-2px); }
95
+ .project-card.selected { border-color: var(--success); background-color: rgba(16, 185, 129, 0.1); }
96
+ .pc-title { font-weight: bold; font-size: 1.1rem; margin-bottom: 0.5rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: white;}
97
+ .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;}
98
+ .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;}
99
+
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; }
116
104
  .folder-item:hover { background-color: var(--bg-hover); }
117
-
118
- .folder-info {
119
- display: flex;
120
- align-items: center;
121
- flex-grow: 1;
122
- overflow: hidden;
123
- }
105
+ .folder-info { display: flex; align-items: center; flex-grow: 1; overflow: hidden; }
124
106
  .folder-icon { margin-right: 12px; font-size: 1.2rem; flex-shrink: 0; }
125
107
  .folder-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 0.95rem; }
126
-
127
- .folder-actions {
128
- display: flex;
129
- gap: 0.5rem;
130
- flex-shrink: 0;
131
- margin-left: 10px;
132
- }
133
-
134
- .btn-small {
135
- padding: 0.4rem 0.8rem;
136
- border-radius: 4px;
137
- border: none;
138
- font-size: 0.85rem;
139
- font-weight: bold;
140
- cursor: pointer;
141
- transition: background-color 0.2s;
142
- }
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; }
143
110
  .btn-open { background-color: #2D2D3D; color: #F3F4F6; }
144
111
  .btn-open:hover { background-color: #3F3F5A; }
145
-
146
112
  .btn-watch { background-color: var(--primary); color: white; }
147
113
  .btn-watch:hover { background-color: var(--primary-hover); }
148
-
149
- .explorer-footer {
150
- background-color: var(--bg-panel);
151
- padding: 0.75rem 1rem;
152
- border-top: 1px solid var(--border-color);
153
- display: flex;
154
- justify-content: space-between;
155
- align-items: center;
156
- }
157
-
158
- /* Log & Terminal Box */
159
- .terminal-container {
160
- background-color: var(--bg-panel);
161
- border: 1px solid var(--border-color);
162
- border-radius: 8px;
163
- display: flex;
164
- flex-direction: column;
165
- overflow: hidden;
166
- }
167
-
168
- .log-box {
169
- height: 250px;
170
- overflow-y: auto;
171
- padding: 1rem;
172
- font-family: monospace;
173
- font-size: 0.85rem;
174
- color: var(--text-muted);
175
- }
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
+ /* MCP LIST UI */
117
+ .mcp-list { margin-bottom: 1.5rem; }
118
+ .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
+ .mcp-item span { font-weight: bold; color: var(--mcp); }
120
+ .mcp-item small { color: var(--text-muted); font-family: monospace; margin-left: 10px; }
121
+ .mcp-add-form { display: flex; gap: 10px; margin-bottom: 2rem; }
122
+ .mcp-add-form input { margin: 0; }
123
+
124
+ .terminal-container { background-color: var(--bg-panel); border: 1px solid var(--border-color); border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; }
125
+ .log-box { height: 200px; overflow-y: auto; padding: 1rem; font-family: monospace; font-size: 0.85rem; color: var(--text-muted); }
176
126
  .log-box p { margin: 0 0 0.5rem 0; line-height: 1.4; word-break: break-all; }
177
127
  .log-time { color: var(--primary); margin-right: 8px; flex-shrink: 0; }
178
-
179
- .prompt-bar {
180
- display: flex;
181
- border-top: 1px solid var(--border-color);
182
- background-color: #12121A;
183
- padding: 0.5rem;
184
- }
185
- .prompt-input {
186
- flex-grow: 1;
187
- background: transparent;
188
- border: none;
189
- color: white;
190
- padding: 0.5rem;
191
- font-family: monospace;
192
- font-size: 0.9rem;
193
- outline: none;
194
- }
195
- .prompt-btn {
196
- background-color: var(--primary);
197
- color: white;
198
- border: none;
199
- border-radius: 4px;
200
- padding: 0.5rem 1rem;
201
- cursor: pointer;
202
- font-weight: bold;
203
- }
128
+ .prompt-bar { display: flex; border-top: 1px solid var(--border-color); background-color: #12121A; padding: 0.5rem; }
129
+ .prompt-input { flex-grow: 1; background: transparent; border: none; color: white; padding: 0.5rem; font-family: monospace; font-size: 0.9rem; outline: none; }
130
+ .prompt-btn { background-color: var(--primary); color: white; border: none; border-radius: 4px; padding: 0.5rem 1rem; cursor: pointer; font-weight: bold; }
204
131
  .prompt-btn:hover { background-color: var(--primary-hover); }
205
132
 
133
+ .hidden { display: none !important; }
206
134
  </style>
207
135
  </head>
208
136
  <body>
209
- <div class="container">
210
- <h1>Thrust Local Agent</h1>
211
-
212
- <div class="status-box">
213
- <div class="status-item">
214
- <div class="status-content">
215
- <div id="ws-dot" class="status-dot dot-red"></div>
216
- <span id="ws-status">Disconnected from Cloud Gateway</span>
137
+
138
+ <nav class="navbar">
139
+ <div class="nav-logo">⚡ THRUST</div>
140
+ <div class="profile-wrapper" id="profile-wrapper">
141
+ <div class="profile-btn" id="profile-btn" onclick="toggleDropdown()">?</div>
142
+ <div class="profile-dropdown" id="profile-dropdown">
143
+ <div class="dropdown-header">
144
+ <span>Local Agent</span>
145
+ <strong id="auth-email-display">Authenticated</strong>
217
146
  </div>
218
- </div>
219
- <div class="status-item">
220
- <div class="status-content">
221
- <div id="proj-dot" class="status-dot dot-yellow"></div>
222
- <span id="proj-status">No active project linked</span>
147
+ <div class="dropdown-body">
148
+ <button class="dropdown-item" onclick="logout()">Unlink Account</button>
223
149
  </div>
224
- <button id="btn-unlink" class="btn-unlink" onclick="unlinkProject()">Stop Watching</button>
225
150
  </div>
226
151
  </div>
152
+ </nav>
227
153
 
228
- <h2>1. Link to Cloud Project</h2>
229
- <div class="input-group">
230
- <label for="lead-id">Lead ID (From your Web Dashboard)</label>
231
- <input id="lead-id" type="text" placeholder="e.g. lead_12345abcde">
154
+ <div class="container">
155
+ <!-- AUTH SECTION -->
156
+ <div id="auth-panel" class="auth-box">
157
+ <h2>Account Connection</h2>
158
+ <div id="unauth-view">
159
+ <p style="font-size: 0.9rem; color: var(--text-muted); margin-bottom: 1rem;">Authenticate with your official Thrust account to link this device.</p>
160
+ <button class="btn-primary" onclick="openRemoteAuth()">🚀 Login with Thrust Web</button>
161
+ </div>
162
+ <div id="auth-view" class="hidden">
163
+ <div class="status-item">
164
+ <div class="status-content">
165
+ <div class="status-dot dot-green"></div>
166
+ <span>Agent is online and linked to Cloud.</span>
167
+ </div>
168
+ </div>
169
+ </div>
232
170
  </div>
233
171
 
234
- <h2>2. Select Local Folder to Watch</h2>
235
-
236
- <div class="explorer-container">
237
- <div class="explorer-header">
238
- <span style="color: var(--text-muted);">Current Path:</span>
239
- <strong id="current-path">Loading...</strong>
172
+ <!-- MAIN WORKSPACE SECTION -->
173
+ <div id="workspace-panel" class="hidden">
174
+ <div class="status-box">
175
+ <div class="status-item">
176
+ <div class="status-content">
177
+ <div id="proj-dot" class="status-dot dot-yellow"></div>
178
+ <span id="proj-status">No active project linked</span>
179
+ </div>
180
+ <button id="btn-unlink" class="btn-outline-danger hidden" onclick="unlinkProject()">Stop Watching</button>
181
+ </div>
240
182
  </div>
241
-
242
- <div id="directory-list" class="explorer-body">
243
- <!-- Folders injected here -->
183
+
184
+ <!-- DIRECTIVE PANEL -->
185
+ <div id="directive-panel" class="directive-card hidden">
186
+ <div class="directive-header">
187
+ <div style="display: flex; align-items: center; gap: 12px;">
188
+ <div class="directive-icon">💻</div>
189
+ <h3 id="dir-title">Current Thrust Directive</h3>
190
+ </div>
191
+ <button class="btn-override" onclick="requestNewThrust()">⚡ Request Next Thrust</button>
192
+ </div>
193
+ <div class="directive-body">
194
+ <div id="dir-targets" class="target-container hidden">
195
+ <span class="target-label">Target Files</span>
196
+ <div id="dir-pills" class="pills-row"></div>
197
+ </div>
198
+ <div id="dir-content" class="directive-text"></div>
199
+ </div>
200
+ <div class="checklist-container">
201
+ <span class="target-label" style="margin-bottom:15px;">AI Mission Checklist</span>
202
+ <div id="dir-tasks"></div>
203
+ </div>
244
204
  </div>
245
-
246
- <div class="explorer-footer">
247
- <span style="font-size: 0.85rem; color: var(--text-muted);">Want to watch the folder you are currently inside?</span>
248
- <button class="btn-small btn-watch" onclick="submitWatch(currentExplorerPath, currentFolderName)" id="select-current-btn">
249
- Watch Current
250
- </button>
205
+
206
+ <h2>1. Select Cloud Project</h2>
207
+ <div id="cloud-projects-loading" style="text-align: center; padding: 2rem; color: var(--text-muted); font-family: monospace;">Fetching remote projects...</div>
208
+ <div id="cloud-projects-grid" class="projects-grid"></div>
209
+
210
+ <div class="input-group">
211
+ <label for="lead-id">Target Lead ID</label>
212
+ <input id="lead-id" type="text" placeholder="Click a project above or paste an ID...">
213
+ </div>
214
+
215
+ <h2>2. Select Local Folder to Watch</h2>
216
+ <div class="explorer-container">
217
+ <div class="explorer-header">
218
+ <span style="color: var(--text-muted);">Current Path:</span>
219
+ <strong id="current-path">Loading...</strong>
220
+ </div>
221
+ <div id="directory-list" class="explorer-body"></div>
222
+ <div class="explorer-footer">
223
+ <span style="font-size: 0.85rem; color: var(--text-muted);">Watch the folder you are currently inside?</span>
224
+ <button class="btn-small btn-watch" onclick="submitWatch(currentExplorerPath, currentFolderName)" id="select-current-btn">Watch Current</button>
225
+ </div>
251
226
  </div>
227
+
228
+ <!-- EXTERNAL MCP INTEGRATIONS -->
229
+ <h2>3. External Integrations (MCP Clients)</h2>
230
+ <p style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 1rem;">Connect external tools (Figma, Unity, Cursor) by providing their local HTTP MCP URL. The Agent will poll them automatically.</p>
231
+ <div id="mcp-list" class="mcp-list"></div>
232
+ <form class="mcp-add-form" onsubmit="addMCPServer(event)">
233
+ <input type="text" id="mcp-name" placeholder="Tool Name (e.g., Unity)" required style="flex: 1;">
234
+ <input type="url" id="mcp-url" placeholder="http://localhost:8081/mcp" required style="flex: 2;">
235
+ <button type="submit" class="btn-primary" style="width: auto;">+ Link Tool</button>
236
+ </form>
252
237
  </div>
253
238
 
254
239
  <h2>Agent Terminal</h2>
255
240
  <div class="terminal-container">
256
241
  <div id="log" class="log-box"></div>
257
-
258
- <!-- NEW: Two-Way WebSocket Input -->
259
242
  <form id="prompt-form" class="prompt-bar" onsubmit="sendPrompt(event)">
260
- <input type="text" id="prompt-input" class="prompt-input" placeholder="> Send a command to the Local Daemon..." autocomplete="off">
243
+ <input type="text" id="prompt-input" class="prompt-input" placeholder="> Force an AI override prompt..." autocomplete="off">
261
244
  <button type="submit" class="prompt-btn">Send</button>
262
245
  </form>
263
246
  </div>
@@ -267,129 +250,336 @@
267
250
  const logBox = document.getElementById('log');
268
251
  let currentExplorerPath = "";
269
252
  let currentFolderName = "";
270
- let localWs = null; // Native WebSocket Client
253
+ let localWs = null;
254
+ let currentPage = 1;
271
255
 
272
256
  function addLog(message, color = "var(--text-muted)") {
273
257
  const p = document.createElement('p');
274
258
  p.style.display = 'flex';
275
-
276
- const timeSpan = document.createElement('span');
277
- timeSpan.className = 'log-time';
278
- timeSpan.textContent = `[${new Date().toLocaleTimeString()}]`;
279
-
280
- const msgSpan = document.createElement('span');
281
- msgSpan.style.color = color;
282
- msgSpan.textContent = message;
283
-
284
- p.appendChild(timeSpan);
285
- p.appendChild(msgSpan);
286
-
259
+ p.innerHTML = `<span class="log-time">[${new Date().toLocaleTimeString()}]</span> <span style="color: ${color}">${message}</span>`;
287
260
  logBox.appendChild(p);
288
261
  logBox.scrollTop = logBox.scrollHeight;
289
262
  }
290
263
 
291
- // --- Native Two-Way WebSocket Connection ---
292
- function connectLocalWebSocket() {
293
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
294
- const wsUrl = `${protocol}//${window.location.host}`;
264
+ function toggleDropdown() {
265
+ const dropdown = document.getElementById('profile-dropdown');
266
+ dropdown.style.display = dropdown.style.display === 'flex' ? 'none' : 'flex';
267
+ }
268
+
269
+ window.onclick = function(event) {
270
+ if (!event.target.matches('.profile-btn')) {
271
+ const dropdowns = document.getElementsByClassName("profile-dropdown");
272
+ for (let i = 0; i < dropdowns.length; i++) {
273
+ if (dropdowns[i].style.display === 'flex') dropdowns[i].style.display = 'none';
274
+ }
275
+ }
276
+ }
277
+
278
+ function openRemoteAuth() {
279
+ const localPort = window.location.port || '8765';
280
+ const authUrl = `https://thrust.web.app/auth?localPort=${localPort}`;
281
+ addLog("Opening Official Thrust Authentication...", "var(--warning)");
282
+ window.open(authUrl, 'ThrustAuth', 'width=500,height=700');
283
+ }
284
+
285
+ async function logout() {
286
+ try {
287
+ await fetch('/api/auth/logout', { method: 'POST' });
288
+ window.location.reload();
289
+ } catch(e) {}
290
+ }
291
+
292
+ async function completeTask(taskId, taskTitle, checkboxElem) {
293
+ checkboxElem.disabled = true;
294
+ const textSpan = checkboxElem.nextElementSibling;
295
+ textSpan.classList.add('task-done');
296
+
297
+ try {
298
+ const res = await fetch('/api/tasks/complete', {
299
+ method: 'POST',
300
+ headers: { 'Content-Type': 'application/json' },
301
+ body: JSON.stringify({ taskId, taskTitle })
302
+ });
303
+
304
+ if (res.ok) {
305
+ addLog(`✅ Task marked complete: ${taskTitle}`, 'var(--success)');
306
+ } else {
307
+ throw new Error("API Failed");
308
+ }
309
+ } catch(e) {
310
+ checkboxElem.disabled = false;
311
+ textSpan.classList.remove('task-done');
312
+ addLog(`❌ Failed to sync task completion.`, 'var(--danger)');
313
+ }
314
+ }
315
+
316
+ function requestNewThrust() {
317
+ 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
+
319
+ if (localWs && localWs.readyState === 1) {
320
+ localWs.send(JSON.stringify({ type: 'frontend_prompt', payload: overridePrompt }));
321
+ addLog(`⚡ Override triggered. Waking up AI Director...`, 'var(--warning)');
322
+ } else {
323
+ addLog(`❌ Cannot request Thrust: Agent Offline.`, 'var(--danger)');
324
+ }
325
+ }
326
+
327
+ async function fetchDirective() {
328
+ try {
329
+ const res = await fetch('/api/thrusts/active');
330
+ const data = await res.json();
331
+
332
+ if (data && !data.error && data.length > 0) {
333
+ const thrust = data[0];
334
+ document.getElementById('directive-panel').classList.remove('hidden');
335
+ document.getElementById('dir-title').innerText = thrust.title || "Current Directive";
336
+
337
+ let rawMd = thrust.markdown_content || "";
338
+ const targetBox = document.getElementById('dir-targets');
339
+ const pillBox = document.getElementById('dir-pills');
340
+ pillBox.innerHTML = '';
341
+
342
+ const createRegex = /CREATE:\s*\(\+@([^\)]+)\)/g;
343
+ const modifyRegex = /MODIFY:\s*\(\~@([^\)]+)\)/g;
344
+
345
+ let hasTargets = false;
346
+ let match;
347
+
348
+ while ((match = createRegex.exec(rawMd)) !== null) {
349
+ hasTargets = true;
350
+ pillBox.innerHTML += `<span class="target-pill pill-create">📄 Create: ${match[1]}</span>`;
351
+ }
352
+ while ((match = modifyRegex.exec(rawMd)) !== null) {
353
+ hasTargets = true;
354
+ pillBox.innerHTML += `<span class="target-pill pill-modify">✍️ Modify: ${match[1]}</span>`;
355
+ }
356
+
357
+ if (hasTargets) {
358
+ targetBox.classList.remove('hidden');
359
+ rawMd = rawMd.replace(createRegex, '').replace(modifyRegex, '');
360
+ } else {
361
+ targetBox.classList.add('hidden');
362
+ }
363
+
364
+ rawMd = rawMd.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
365
+ document.getElementById('dir-content').innerHTML = rawMd.trim().replace(/\n/g, '<br>');
366
+
367
+ const tasksBox = document.getElementById('dir-tasks');
368
+ tasksBox.innerHTML = '';
369
+ if (thrust.thrust_tasks && thrust.thrust_tasks.length > 0) {
370
+ thrust.thrust_tasks.forEach(task => {
371
+ const isDone = task.is_completed;
372
+ const safeTitle = task.title.replace(/'/g, "\\'").replace(/"/g, '&quot;');
373
+
374
+ tasksBox.innerHTML += `
375
+ <div class="task-item">
376
+ <input type="checkbox" class="task-checkbox"
377
+ ${isDone ? 'checked disabled' : `onchange="completeTask('${task.id}', '${safeTitle}', this)"`}>
378
+ <span class="task-text ${isDone ? 'task-done' : ''}">${task.title}</span>
379
+ </div>
380
+ `;
381
+ });
382
+ } else {
383
+ tasksBox.innerHTML = '<span class="task-text" style="color:var(--text-muted)">No specific tasks for this session.</span>';
384
+ }
385
+ } else {
386
+ document.getElementById('directive-panel').classList.add('hidden');
387
+ }
388
+ } catch (e) {
389
+ console.log("No active directive found.");
390
+ }
391
+ }
392
+
393
+ async function fetchCloudProjects() {
394
+ const grid = document.getElementById('cloud-projects-grid');
395
+ const loader = document.getElementById('cloud-projects-loading');
396
+
397
+ grid.innerHTML = '';
398
+ loader.style.display = 'block';
399
+
400
+ try {
401
+ const res = await fetch(`/api/cloud-projects?page=${currentPage}&limit=6`);
402
+ const data = await res.json();
403
+ loader.style.display = 'none';
404
+
405
+ if (data.projects && data.projects.length > 0) {
406
+ data.projects.forEach(proj => {
407
+ const card = document.createElement('div');
408
+ card.className = 'project-card';
409
+ const currentLeadInput = document.getElementById('lead-id').value;
410
+ if(currentLeadInput === proj.id) card.classList.add('selected');
411
+
412
+ card.onclick = () => selectCloudProject(proj.id, card);
413
+
414
+ card.innerHTML = `
415
+ <div class="pc-title">${proj.name}</div>
416
+ <div class="pc-desc">${proj.description || 'No description provided.'}</div>
417
+ <div class="pc-meta">
418
+ <span>${proj.status}</span>
419
+ <span>${new Date(proj.created_at).toLocaleDateString()}</span>
420
+ </div>
421
+ `;
422
+ grid.appendChild(card);
423
+ });
424
+ } else {
425
+ grid.innerHTML = `<div style="grid-column: 1/-1; color: var(--text-muted); text-align: center; padding: 2rem;">No projects found on your cloud account.</div>`;
426
+ }
427
+ } catch (e) {
428
+ loader.innerText = "Error fetching cloud projects.";
429
+ }
430
+ }
431
+
432
+ function selectCloudProject(id, cardElement) {
433
+ document.getElementById('lead-id').value = id;
434
+ document.querySelectorAll('.project-card').forEach(c => c.classList.remove('selected'));
435
+ cardElement.classList.add('selected');
436
+ addLog(`Selected Cloud Project ID: ${id}`);
437
+ document.getElementById('current-path').scrollIntoView({ behavior: 'smooth', block: 'center' });
438
+ }
439
+
440
+ /* MCP CLIENT MANAGEMENT */
441
+ async function fetchMCPServers() {
442
+ try {
443
+ const res = await fetch('/api/mcp/servers');
444
+ const servers = await res.json();
445
+ const list = document.getElementById('mcp-list');
446
+ list.innerHTML = '';
447
+
448
+ if (servers.length === 0) {
449
+ list.innerHTML = `<div style="color: var(--text-muted); font-size: 0.85rem;">No external integrations linked.</div>`;
450
+ return;
451
+ }
452
+
453
+ servers.forEach((s, index) => {
454
+ list.innerHTML += `
455
+ <div class="mcp-item">
456
+ <div><span>🔗 ${s.name}</span> <small>${s.url}</small></div>
457
+ <button class="btn-outline-danger" onclick="removeMCPServer(${index})">Remove</button>
458
+ </div>
459
+ `;
460
+ });
461
+ } catch (e) { console.error("Failed to load MCP servers"); }
462
+ }
463
+
464
+ async function addMCPServer(e) {
465
+ e.preventDefault();
466
+ const name = document.getElementById('mcp-name').value;
467
+ const url = document.getElementById('mcp-url').value;
295
468
 
296
- localWs = new WebSocket(wsUrl);
469
+ try {
470
+ const res = await fetch('/api/mcp/servers', {
471
+ method: 'POST',
472
+ headers: { 'Content-Type': 'application/json' },
473
+ body: JSON.stringify({ name, url })
474
+ });
475
+ if (res.ok) {
476
+ document.getElementById('mcp-name').value = '';
477
+ document.getElementById('mcp-url').value = '';
478
+ fetchMCPServers();
479
+ addLog(`🔗 Linked External MCP: ${name}`, 'var(--mcp)');
480
+ }
481
+ } catch (err) { addLog('❌ Failed to link MCP.', 'var(--danger)'); }
482
+ }
297
483
 
298
- localWs.onopen = () => {
299
- addLog('🔌 Local WebSocket pipe established.');
300
- };
484
+ async function removeMCPServer(index) {
485
+ try {
486
+ await fetch(`/api/mcp/servers/${index}`, { method: 'DELETE' });
487
+ fetchMCPServers();
488
+ } catch (err) {}
489
+ }
490
+
491
+ function connectLocalWebSocket() {
492
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
493
+ localWs = new WebSocket(`${protocol}//${window.location.host}`);
494
+ localWs.onopen = () => addLog('🔌 Local Stream connected.');
301
495
 
302
496
  localWs.onmessage = (event) => {
303
497
  const data = JSON.parse(event.data);
304
-
305
- if (data.type === 'watch') {
306
- addLog(`👀 ${data.message}`, 'var(--success)');
307
- } else if (data.type === 'sync') {
308
- addLog(data.message, 'var(--sync)');
309
- } else if (data.type === 'ai') {
310
- addLog(data.message, 'var(--warning)');
311
- } else if (data.type === 'error') {
312
- addLog(`❌ ${data.message}`, 'var(--danger)');
313
- } else {
314
- addLog(data.message);
498
+ if (data.type === 'success' && data.message.includes('Authentication Successful')) fetchStatus();
499
+ else if (data.type === 'watch') addLog(`👀 ${data.message}`, 'var(--success)');
500
+ else if (data.type === 'sync') addLog(data.message, 'var(--sync)');
501
+ else if (data.type === 'ai') addLog(data.message, 'var(--warning)');
502
+ else if (data.type === 'mcp') addLog(`${data.message}`, 'var(--mcp)');
503
+ else if (data.type === 'success') addLog(data.message, 'var(--success)');
504
+ else if (data.type === 'error') addLog(`❌ ${data.message}`, 'var(--danger)');
505
+ else if (data.type === 'reload') {
506
+ addLog('🔄 AI updated Directive. Fetching...', 'var(--primary)');
507
+ fetchDirective();
315
508
  }
509
+ else addLog(data.message);
316
510
  };
317
-
318
- localWs.onclose = () => {
319
- addLog('⚠️ Local pipe disconnected. Retrying in 3s...', 'var(--danger)');
320
- setTimeout(connectLocalWebSocket, 3000);
321
- };
511
+ localWs.onclose = () => { setTimeout(connectLocalWebSocket, 3000); };
322
512
  }
323
513
 
324
- // Send data to Local Daemon via WS
325
514
  function sendPrompt(event) {
326
515
  event.preventDefault();
327
516
  const input = document.getElementById('prompt-input');
328
517
  const message = input.value.trim();
329
-
330
- if (!message || !localWs || localWs.readyState !== WebSocket.OPEN) return;
331
-
332
- addLog(`> You: ${message}`, '#FFFFFF'); // Show user input in white
333
-
334
- localWs.send(JSON.stringify({
335
- type: 'frontend_prompt',
336
- payload: message
337
- }));
338
-
339
- input.value = ''; // Clear input
518
+ if (!message || !localWs || localWs.readyState !== 1) return;
519
+ addLog(`> You: ${message}`, '#FFFFFF');
520
+ localWs.send(JSON.stringify({ type: 'frontend_prompt', payload: message }));
521
+ input.value = '';
340
522
  }
341
523
 
342
524
  async function fetchStatus() {
343
525
  try {
344
526
  const response = await fetch('/api/status');
345
527
  const data = await response.json();
346
-
528
+
529
+ if (data.auth && data.auth.token) {
530
+ document.getElementById('unauth-view').classList.add('hidden');
531
+ document.getElementById('auth-view').classList.remove('hidden');
532
+ document.getElementById('workspace-panel').classList.remove('hidden');
533
+
534
+ const userEmail = data.auth.email || "Linked User";
535
+ document.getElementById('profile-wrapper').style.display = 'block';
536
+ document.getElementById('auth-email-display').innerText = userEmail;
537
+ document.getElementById('profile-btn').innerText = userEmail.charAt(0).toUpperCase();
538
+
539
+ fetchCloudProjects();
540
+ fetchDirective();
541
+ fetchMCPServers(); // Load the MCP client list
542
+ } else {
543
+ document.getElementById('unauth-view').classList.remove('hidden');
544
+ document.getElementById('auth-view').classList.add('hidden');
545
+ document.getElementById('workspace-panel').classList.add('hidden');
546
+ document.getElementById('directive-panel').classList.add('hidden');
547
+ document.getElementById('profile-wrapper').style.display = 'none';
548
+ }
549
+
347
550
  if (data.activeProject) {
348
551
  document.getElementById('proj-dot').className = 'status-dot dot-green';
349
552
  document.getElementById('proj-status').textContent = `Watching: ${data.activeProject.path}`;
350
- document.getElementById('btn-unlink').style.display = 'block';
351
- document.getElementById('lead-id').value = data.activeProject.id;
352
- addLog(`Loaded config for: ${data.activeProject.id}`);
553
+ document.getElementById('btn-unlink').classList.remove('hidden');
554
+ document.getElementById('lead-id').value = data.activeProject.id;
353
555
  } else {
354
556
  document.getElementById('proj-dot').className = 'status-dot dot-yellow';
355
557
  document.getElementById('proj-status').textContent = `No active project linked`;
356
- document.getElementById('btn-unlink').style.display = 'none';
558
+ document.getElementById('btn-unlink').classList.add('hidden');
357
559
  }
358
- } catch (e) { addLog('Error fetching daemon status.'); }
560
+ } catch (e) { addLog('Error fetching status.', 'var(--danger)'); }
359
561
  }
360
562
 
361
563
  async function unlinkProject() {
362
564
  try {
363
- addLog('Stopping watcher and unlinking project...');
565
+ addLog('Stopping watcher...');
364
566
  const response = await fetch('/api/unlink', { method: 'POST' });
365
- const data = await response.json();
366
-
367
- if (data.success) {
368
- document.getElementById('proj-dot').className = 'status-dot dot-yellow';
369
- document.getElementById('proj-status').textContent = `No active project linked`;
370
- document.getElementById('btn-unlink').style.display = 'none';
567
+ if ((await response.json()).success) {
371
568
  document.getElementById('lead-id').value = '';
569
+ fetchStatus();
372
570
  }
373
- } catch (e) {
374
- addLog('❌ Failed to unlink project.', 'var(--danger)');
375
- }
571
+ } catch (e) {}
376
572
  }
377
573
 
378
574
  async function loadDirectory(targetPath = "") {
379
575
  try {
380
576
  const url = targetPath ? `/api/explore?path=${encodeURIComponent(targetPath)}` : '/api/explore';
381
- const response = await fetch(url);
382
- const data = await response.json();
383
-
384
- if (data.error) {
385
- addLog(`Explorer Error: ${data.error}`, 'var(--danger)');
386
- return;
387
- }
577
+ const data = await (await fetch(url)).json();
578
+ if (data.error) return addLog(`Explorer Error: ${data.error}`, 'var(--danger)');
388
579
 
389
580
  currentExplorerPath = data.currentPath;
390
- const pathParts = data.currentPath.split(/[/\\]/);
391
- currentFolderName = pathParts[pathParts.length - 1] || "Root";
392
-
581
+ currentFolderName = data.currentPath.split(/[/\\]/).pop() || "Root";
582
+
393
583
  document.getElementById('current-path').textContent = data.currentPath;
394
584
  document.getElementById('select-current-btn').textContent = `Watch '${currentFolderName}'`;
395
585
 
@@ -397,92 +587,39 @@
397
587
  dirList.innerHTML = '';
398
588
 
399
589
  if (data.parentPath) {
400
- const upRow = document.createElement('div');
401
- upRow.className = 'folder-item';
402
-
403
- const info = document.createElement('div');
404
- info.className = 'folder-info';
405
- info.innerHTML = `<span class="folder-icon">⬆️</span> <span class="folder-text"><strong>.. (Go Up)</strong></span>`;
406
-
407
- const actions = document.createElement('div');
408
- actions.className = 'folder-actions';
409
-
410
- const openBtn = document.createElement('button');
411
- openBtn.className = 'btn-small btn-open';
412
- // openBtn.textContent = 'Open';
413
- openBtn.textContent = 'Go back';
414
- openBtn.onclick = () => loadDirectory(data.parentPath);
415
-
416
- actions.appendChild(openBtn);
417
- upRow.appendChild(info);
418
- upRow.appendChild(actions);
419
- dirList.appendChild(upRow);
590
+ dirList.innerHTML += `<div class="folder-item">
591
+ <div class="folder-info"><span class="folder-icon">⬆️</span><span class="folder-text"><strong>.. (Go Up)</strong></span></div>
592
+ <div class="folder-actions"><button class="btn-small btn-open" onclick="loadDirectory('${data.parentPath.replace(/\\/g, '\\\\')}')">Open</button></div>
593
+ </div>`;
420
594
  }
421
595
 
422
596
  data.directories.forEach(dir => {
423
- const fullPath = `${data.currentPath}/${dir}`;
424
- const div = document.createElement('div');
425
- div.className = 'folder-item';
426
-
427
- const info = document.createElement('div');
428
- info.className = 'folder-info';
429
- info.innerHTML = `<span class="folder-icon">📁</span> <span class="folder-text">${dir}</span>`;
430
-
431
- const actions = document.createElement('div');
432
- actions.className = 'folder-actions';
433
-
434
- const openBtn = document.createElement('button');
435
- openBtn.className = 'btn-small btn-open';
436
- openBtn.textContent = 'Open 📂';
437
- openBtn.onclick = () => loadDirectory(fullPath);
438
-
439
- const watchBtn = document.createElement('button');
440
- watchBtn.className = 'btn-small btn-watch';
441
- watchBtn.textContent = 'Watch 👁️';
442
- watchBtn.onclick = () => submitWatch(fullPath, dir);
443
-
444
- actions.appendChild(openBtn);
445
- actions.appendChild(watchBtn);
446
-
447
- div.appendChild(info);
448
- div.appendChild(actions);
449
- dirList.appendChild(div);
597
+ const fullPath = `${data.currentPath}/${dir}`.replace(/\\/g, '\\\\');
598
+ dirList.innerHTML += `<div class="folder-item">
599
+ <div class="folder-info"><span class="folder-icon">📁</span><span class="folder-text">${dir}</span></div>
600
+ <div class="folder-actions">
601
+ <button class="btn-small btn-open" onclick="loadDirectory('${fullPath}')">Open 📂</button>
602
+ <button class="btn-small btn-watch" onclick="submitWatch('${fullPath}', '${dir}')">Watch 👁️</button>
603
+ </div>
604
+ </div>`;
450
605
  });
451
- } catch (e) {
452
- addLog('Failed to load directory from server.', 'var(--danger)');
453
- }
606
+ } catch (e) {}
454
607
  }
455
608
 
456
609
  async function submitWatch(absolutePath, folderName) {
457
610
  const leadId = document.getElementById('lead-id').value.trim();
458
-
459
- if (!leadId) {
460
- addLog('❌ Error: You must enter a Lead ID in Step 1.', 'var(--danger)');
461
- document.getElementById('lead-id').focus();
462
- return;
463
- }
611
+ if (!leadId) { document.getElementById('lead-id').focus(); return addLog('❌ Error: Select a Cloud Project first', 'var(--danger)'); }
464
612
 
465
613
  try {
466
- const response = await fetch('/api/link', {
467
- method: 'POST',
468
- headers: { 'Content-Type': 'application/json' },
469
- body: JSON.stringify({ leadId, folderPath: absolutePath })
470
- });
471
- const data = await response.json();
472
-
473
- if (data.success) {
474
- fetchStatus();
475
- } else {
476
- addLog(`❌ Error: ${data.error}`, 'var(--danger)');
477
- }
478
- } catch (e) { addLog('❌ API call failed. Is the daemon running?', 'var(--danger)'); }
614
+ 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) fetchStatus();
616
+ } catch (e) {}
479
617
  }
480
618
 
481
619
  document.addEventListener('DOMContentLoaded', () => {
482
620
  fetchStatus();
483
- loadDirectory();
484
- connectLocalWebSocket(); // Start Two-Way WebSockets
485
- addLog('Dashboard UI initialized.');
621
+ loadDirectory();
622
+ connectLocalWebSocket();
486
623
  });
487
624
  </script>
488
625
  </body>