thrust-cli 1.0.20 → 1.0.22

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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -3
  3. package/build/asset-manifest.json +3 -3
  4. package/build/index.html +1 -1
  5. package/build/static/js/{main.675da974.js → main.f7cc89bf.js} +3 -3
  6. package/build/static/js/main.f7cc89bf.js.map +1 -0
  7. package/package.json +4 -2
  8. package/utils/daemon.js +5 -1
  9. package/AuthPage.jsx +0 -0
  10. package/asset-manifest.json +0 -13
  11. package/build/static/css/main.3f5f9134.css +0 -4
  12. package/build/static/css/main.3f5f9134.css.map +0 -1
  13. package/build/static/css/main.ecc04ee5.css +0 -4
  14. package/build/static/css/main.ecc04ee5.css.map +0 -1
  15. package/build/static/js/main.5b9e596b.js +0 -3
  16. package/build/static/js/main.5b9e596b.js.map +0 -1
  17. package/build/static/js/main.631499e7.js +0 -3
  18. package/build/static/js/main.631499e7.js.LICENSE.txt +0 -56
  19. package/build/static/js/main.631499e7.js.map +0 -1
  20. package/build/static/js/main.675da974.js.LICENSE.txt +0 -56
  21. package/build/static/js/main.675da974.js.map +0 -1
  22. package/build/static/js/main.728d8aba.js +0 -3
  23. package/build/static/js/main.728d8aba.js.LICENSE.txt +0 -56
  24. package/build/static/js/main.728d8aba.js.map +0 -1
  25. package/favicon.ico +0 -0
  26. package/frontend/index.html +0 -821
  27. package/frontend/index2.html +0 -468
  28. package/index.html +0 -1
  29. package/logo192.png +0 -0
  30. package/logo512.png +0 -0
  31. package/manifest.json +0 -25
  32. package/robots.txt +0 -3
  33. package/static/css/main.66c99de4.css +0 -4
  34. package/static/css/main.66c99de4.css.map +0 -1
  35. package/static/js/main.920336e3.js +0 -3
  36. package/static/js/main.920336e3.js.LICENSE.txt +0 -56
  37. package/static/js/main.920336e3.js.map +0 -1
  38. package/thrust_black.jpg +0 -0
  39. package/thrust_equal_black.png +0 -0
  40. package/thrust_equal_white.png +0 -0
  41. package/thrust_tracing_black.png +0 -0
  42. package/thrust_tracing_white.png +0 -0
  43. package/thrust_white.jpg +0 -0
  44. /package/build/static/js/{main.5b9e596b.js.LICENSE.txt → main.f7cc89bf.js.LICENSE.txt} +0 -0
@@ -1,821 +0,0 @@
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
- --mcp: #06B6D4;
23
- }
24
-
25
- body { background-color: var(--bg-main); color: var(--text-main); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; overflow-x: hidden; }
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); }
33
-
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); }
41
-
42
- .container { max-width: 800px; margin: 2rem auto; padding: 0 1.5rem; }
43
- h2 { font-size: 1.1rem; margin-bottom: 1rem; color: var(--text-main); border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem; }
44
-
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; }
46
-
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; }
49
- .status-content { display: flex; align-items: center; word-break: break-all; padding-right: 1rem; }
50
- .status-dot { width: 12px; height: 12px; border-radius: 50%; margin-right: 12px; flex-shrink: 0; }
51
- .dot-red { background-color: var(--danger); box-shadow: 0 0 8px rgba(239, 68, 68, 0.4); }
52
- .dot-green { background-color: var(--success); box-shadow: 0 0 8px rgba(16, 185, 129, 0.4); }
53
- .dot-yellow { background-color: var(--warning); box-shadow: 0 0 8px rgba(245, 158, 11, 0.4); }
54
-
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
-
82
- .task-item-wrapper { padding: 12px 0; border-bottom: 1px solid var(--border-color); }
83
- .task-item-wrapper:last-child { border-bottom: none; padding-bottom: 0; }
84
- .task-item { display: flex; align-items: flex-start; gap: 12px; }
85
- .task-checkbox { margin-top: 4px; width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary); }
86
- .task-text { font-size: 0.95rem; color: var(--text-main); transition: color 0.3s; }
87
- .task-done { text-decoration: line-through; color: var(--text-muted); }
88
-
89
- /* AI QUICK COMMENTS UI */
90
- .quick-comments { display: flex; gap: 8px; flex-wrap: wrap; }
91
- .quick-badge { background: rgba(255,255,255,0.05); border: 1px solid var(--border-color); color: var(--text-muted); font-size: 0.75rem; padding: 4px 10px; border-radius: 12px; cursor: pointer; transition: all 0.2s; font-family: inherit; font-weight: 500; }
92
- .quick-badge:hover { background: rgba(139, 92, 246, 0.1); border-color: var(--primary); color: var(--primary); }
93
- .task-input { flex: 1; background: var(--bg-hover); border: 1px solid var(--border-color); color: white; padding: 6px 12px; border-radius: 6px; font-size: 0.85rem; outline: none; }
94
- .task-input:focus { border-color: var(--primary); }
95
-
96
- .input-group { margin-bottom: 1.5rem; display: flex; gap: 10px; flex-direction: column; }
97
- .input-group label { font-size: 0.85rem; font-weight: bold; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1px; }
98
- input[type="text"], input[type="url"], input[type="password"] { flex-grow: 1; background-color: var(--bg-hover); color: var(--text-main); border: 1px solid var(--border-color); padding: 0.8rem; border-radius: 6px; font-size: 1rem; box-sizing: border-box; }
99
- input[type="text"]:focus, input[type="url"]:focus, input[type="password"]:focus { outline: none; border-color: var(--primary); }
100
-
101
- .projects-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }
102
- .project-card { background-color: var(--bg-hover); border: 1px solid var(--border-color); border-radius: 12px; padding: 1rem; cursor: pointer; transition: all 0.2s; display: flex; flex-direction: column; }
103
- .project-card:hover { border-color: var(--primary); background-color: #1A1A24; transform: translateY(-2px); }
104
- .project-card.selected { border-color: var(--success); background-color: rgba(16, 185, 129, 0.1); }
105
- .pc-title { font-weight: bold; font-size: 1.1rem; margin-bottom: 0.5rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: white;}
106
- .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;}
107
- .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;}
108
-
109
- .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; }
110
- .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; }
111
- .explorer-body { height: 250px; overflow-y: auto; }
112
- .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; }
113
- .folder-item:hover { background-color: var(--bg-hover); }
114
- .folder-info { display: flex; align-items: center; flex-grow: 1; overflow: hidden; }
115
- .folder-icon { margin-right: 12px; font-size: 1.2rem; flex-shrink: 0; }
116
- .folder-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 0.95rem; }
117
- .folder-actions { display: flex; gap: 0.5rem; flex-shrink: 0; margin-left: 10px; }
118
- .btn-small { padding: 0.4rem 0.8rem; border-radius: 4px; border: none; font-size: 0.85rem; font-weight: bold; cursor: pointer; }
119
- .btn-open { background-color: #2D2D3D; color: #F3F4F6; }
120
- .btn-open:hover { background-color: #3F3F5A; }
121
- .btn-watch { background-color: var(--primary); color: white; }
122
- .btn-watch:hover { background-color: var(--primary-hover); }
123
- .explorer-footer { background-color: var(--bg-panel); padding: 0.75rem 1rem; border-top: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; }
124
-
125
- /* MCP LIST & TABS UI */
126
- .mcp-list { margin-bottom: 1.5rem; }
127
- .mcp-item { display: flex; justify-content: space-between; align-items: center; background-color: var(--bg-hover); border: 1px solid var(--border-color); padding: 0.8rem 1rem; border-radius: 8px; margin-bottom: 0.5rem; }
128
- .mcp-item span { font-weight: bold; color: var(--mcp); }
129
- .mcp-item small { color: var(--text-muted); font-family: monospace; margin-left: 10px; }
130
-
131
- .mcp-tabs { display: flex; gap: 8px; margin-bottom: 12px; }
132
- .mcp-tab { background: transparent; border: 1px solid var(--border-color); color: var(--text-muted); padding: 8px 16px; border-radius: 8px; cursor: pointer; font-size: 0.85rem; font-weight: bold; transition: all 0.2s; }
133
- .mcp-tab:hover { background: rgba(255,255,255,0.05); }
134
- .mcp-tab.active { background: rgba(139, 92, 246, 0.1); color: var(--primary); border-color: var(--primary); }
135
-
136
- .mcp-add-form { display: flex; gap: 10px; margin-bottom: 2rem; }
137
- .mcp-add-form input { margin: 0; }
138
-
139
- .terminal-container { background-color: var(--bg-panel); border: 1px solid var(--border-color); border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; margin-bottom: 40px; }
140
- .log-box { height: 200px; overflow-y: auto; padding: 1rem; font-family: monospace; font-size: 0.85rem; color: var(--text-muted); }
141
- .log-box p { margin: 0 0 0.5rem 0; line-height: 1.4; word-break: break-all; }
142
- .log-time { color: var(--primary); margin-right: 8px; flex-shrink: 0; }
143
- .prompt-bar { display: flex; border-top: 1px solid var(--border-color); background-color: #12121A; padding: 0.5rem; }
144
- .prompt-input { flex-grow: 1; background: transparent; border: none; color: white; padding: 0.5rem; font-family: monospace; font-size: 0.9rem; outline: none; }
145
- .prompt-btn { background-color: var(--primary); color: white; border: none; border-radius: 4px; padding: 0.5rem 1rem; cursor: pointer; font-weight: bold; }
146
- .prompt-btn:hover { background-color: var(--primary-hover); }
147
-
148
- .hidden { display: none !important; }
149
-
150
- /* TOAST NOTIFICATIONS */
151
- #toast-container { position: fixed; bottom: 24px; right: 24px; z-index: 9999; display: flex; flex-direction: column; gap: 12px; }
152
- .toast { padding: 14px 20px; border-radius: 10px; color: white; font-weight: 500; font-size: 0.95rem; box-shadow: 0 10px 30px rgba(0,0,0,0.5); transform: translateX(120%); transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); display: flex; align-items: center; gap: 12px; min-width: 250px; max-width: 350px; word-break: break-word; }
153
- .toast.show { transform: translateX(0); }
154
- .toast-info { background: var(--primary); border: 1px solid var(--primary-hover); }
155
- .toast-success { background: var(--success); border: 1px solid #059669; }
156
- .toast-error { background: var(--danger); border: 1px solid #DC2626; }
157
- .toast-ai { background: #1A1A24; border: 1px solid var(--primary); }
158
- </style>
159
- </head>
160
- <body>
161
-
162
- <!-- TOAST NOTIFICATION MOUNT -->
163
- <div id="toast-container"></div>
164
-
165
- <nav class="navbar">
166
- <div class="nav-logo">⚡ THRUST</div>
167
- <div class="profile-wrapper" id="profile-wrapper">
168
- <div class="profile-btn" id="profile-btn" onclick="toggleDropdown()">?</div>
169
- <div class="profile-dropdown" id="profile-dropdown">
170
- <div class="dropdown-header">
171
- <span>Local Agent</span>
172
- <strong id="auth-email-display">Authenticated</strong>
173
- </div>
174
- <div class="dropdown-body">
175
- <button class="dropdown-item" onclick="logout()">Unlink Account</button>
176
- </div>
177
- </div>
178
- </div>
179
- </nav>
180
-
181
- <div class="container">
182
- <!-- AUTH SECTION -->
183
- <div id="auth-panel" class="auth-box">
184
- <h2>Account Connection</h2>
185
- <div id="unauth-view">
186
- <p style="font-size: 0.9rem; color: var(--text-muted); margin-bottom: 1rem;">Authenticate with your official Thrust account to link this device.</p>
187
- <button class="btn-primary" onclick="openRemoteAuth()">🚀 Login with Thrust Web</button>
188
- </div>
189
- <div id="auth-view" class="hidden">
190
- <div class="status-item">
191
- <div class="status-content">
192
- <div class="status-dot dot-green"></div>
193
- <span>Agent is online and linked to Cloud.</span>
194
- </div>
195
- </div>
196
- </div>
197
- </div>
198
-
199
- <!-- MAIN WORKSPACE SECTION -->
200
- <div id="workspace-panel" class="hidden">
201
- <div class="status-box">
202
- <div class="status-item">
203
- <div class="status-content">
204
- <div id="proj-dot" class="status-dot dot-yellow"></div>
205
- <span id="proj-status">No active project linked</span>
206
- </div>
207
- <button id="btn-unlink" class="btn-outline-danger hidden" onclick="unlinkProject()">Stop Watching</button>
208
- </div>
209
- </div>
210
-
211
- <!-- DIRECTIVE PANEL -->
212
- <div id="directive-panel" class="directive-card hidden">
213
- <div class="directive-header">
214
- <div style="display: flex; align-items: center; gap: 12px;">
215
- <div class="directive-icon">💻</div>
216
- <h3 id="dir-title">Current Thrust Directive</h3>
217
- </div>
218
- <button class="btn-override" onclick="requestNewThrust()">⚡ Request Next Thrust</button>
219
- </div>
220
- <div class="directive-body">
221
- <div id="dir-targets" class="target-container hidden">
222
- <span class="target-label">Target Files</span>
223
- <div id="dir-pills" class="pills-row"></div>
224
- </div>
225
- <div id="dir-content" class="directive-text"></div>
226
- </div>
227
- <div class="checklist-container">
228
- <span class="target-label" style="margin-bottom:15px;">AI Mission Checklist</span>
229
- <div id="dir-tasks"></div>
230
- </div>
231
- </div>
232
-
233
- <h2>1. Select Cloud Project</h2>
234
- <div id="cloud-projects-loading" style="text-align: center; padding: 2rem; color: var(--text-muted); font-family: monospace;">Fetching remote projects...</div>
235
- <div id="cloud-projects-grid" class="projects-grid"></div>
236
-
237
- <div class="input-group">
238
- <label for="lead-id">Target Lead ID</label>
239
- <input id="lead-id" type="text" placeholder="Click a project above or paste an ID...">
240
- </div>
241
-
242
- <h2>2. Select Local Folder to Watch</h2>
243
- <div class="explorer-container">
244
- <div class="explorer-header">
245
- <span style="color: var(--text-muted);">Current Path:</span>
246
- <strong id="current-path">Loading...</strong>
247
- </div>
248
- <div id="directory-list" class="explorer-body"></div>
249
- <div class="explorer-footer">
250
- <span style="font-size: 0.85rem; color: var(--text-muted);">Watch the folder you are currently inside?</span>
251
- <button class="btn-small btn-watch" onclick="submitWatch(currentExplorerPath, currentFolderName)" id="select-current-btn">Watch Current</button>
252
- </div>
253
- </div>
254
-
255
- <!-- EXTERNAL MCP INTEGRATIONS -->
256
- <h2>3. External Integrations (MCP Clients)</h2>
257
- <p style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 1rem;">Connect external tools like Figma, GitHub, or local MCPs. The Agent will poll them to maintain context momentum.</p>
258
- <div id="mcp-list" class="mcp-list"></div>
259
-
260
- <div class="mcp-tabs">
261
- <button type="button" class="mcp-tab active" onclick="setMcpTab('local', this)">Standard MCP</button>
262
- <button type="button" class="mcp-tab" onclick="setMcpTab('figma', this)">Figma Bridge</button>
263
- <button type="button" class="mcp-tab" onclick="setMcpTab('github', this)">GitHub Bridge</button>
264
- </div>
265
-
266
- <form class="mcp-add-form" onsubmit="addMCPServer(event)" id="mcp-form">
267
- <!-- Rendered Dynamically -->
268
- </form>
269
- </div>
270
-
271
- <h2>Agent Terminal</h2>
272
- <div class="terminal-container">
273
- <div id="log" class="log-box"></div>
274
- <form id="prompt-form" class="prompt-bar" onsubmit="sendPrompt(event)">
275
- <input type="text" id="prompt-input" class="prompt-input" placeholder="> Force an AI override prompt..." autocomplete="off">
276
- <button type="submit" class="prompt-btn">Send</button>
277
- </form>
278
- </div>
279
- </div>
280
-
281
- <script>
282
- const logBox = document.getElementById('log');
283
- let currentExplorerPath = "";
284
- let currentFolderName = "";
285
- let localWs = null;
286
- let currentPage = 1;
287
- let currentMcpType = 'local';
288
-
289
- // --- AUDIO ENGINE ---
290
- function playNotificationSound() {
291
- try {
292
- const ctx = new (window.AudioContext || window.webkitAudioContext)();
293
- const osc = ctx.createOscillator();
294
- const gain = ctx.createGain();
295
- osc.connect(gain);
296
- gain.connect(ctx.destination);
297
- osc.type = 'sine';
298
- osc.frequency.setValueAtTime(880, ctx.currentTime);
299
- osc.frequency.exponentialRampToValueAtTime(1760, ctx.currentTime + 0.1);
300
- gain.gain.setValueAtTime(0.05, ctx.currentTime);
301
- gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.3);
302
- osc.start();
303
- osc.stop(ctx.currentTime + 0.3);
304
- } catch (e) {} // Failsafe for auto-play policy blocks
305
- }
306
-
307
- // --- TOAST UI ---
308
- function showToast(message, type = "info") {
309
- playNotificationSound();
310
- const container = document.getElementById('toast-container');
311
- const toast = document.createElement('div');
312
- toast.className = `toast toast-${type}`;
313
-
314
- let icon = "💡";
315
- if(type === 'success') icon = "✅";
316
- if(type === 'error') icon = "❌";
317
- if(type === 'ai') icon = "🤖";
318
-
319
- toast.innerHTML = `<span style="font-size: 1.2rem;">${icon}</span> <span>${message}</span>`;
320
- container.appendChild(toast);
321
-
322
- setTimeout(() => toast.classList.add('show'), 10);
323
-
324
- setTimeout(() => {
325
- toast.classList.remove('show');
326
- setTimeout(() => toast.remove(), 300);
327
- }, 5000);
328
- }
329
-
330
- function addLog(message, color = "var(--text-muted)") {
331
- const p = document.createElement('p');
332
- p.style.display = 'flex';
333
- p.innerHTML = `<span class="log-time">[${new Date().toLocaleTimeString()}]</span> <span style="color: ${color}">${message}</span>`;
334
- logBox.appendChild(p);
335
- logBox.scrollTop = logBox.scrollHeight;
336
- }
337
-
338
- function toggleDropdown() {
339
- const dropdown = document.getElementById('profile-dropdown');
340
- dropdown.style.display = dropdown.style.display === 'flex' ? 'none' : 'flex';
341
- }
342
-
343
- window.onclick = function(event) {
344
- if (!event.target.matches('.profile-btn')) {
345
- const dropdowns = document.getElementsByClassName("profile-dropdown");
346
- for (let i = 0; i < dropdowns.length; i++) {
347
- if (dropdowns[i].style.display === 'flex') dropdowns[i].style.display = 'none';
348
- }
349
- }
350
- }
351
-
352
- function openRemoteAuth() {
353
- const localPort = window.location.port || '8765';
354
- const authUrl = `https://thrust.web.app/auth?localPort=${localPort}`;
355
- addLog("Opening Official Thrust Authentication...", "var(--warning)");
356
- window.open(authUrl, 'ThrustAuth', 'width=500,height=700');
357
- }
358
-
359
- async function logout() {
360
- try {
361
- await fetch('/api/auth/logout', { method: 'POST' });
362
- window.location.reload();
363
- } catch(e) {}
364
- }
365
-
366
- // --- UPDATED COMPLETION LOGIC FOR QUICK REPLIES ---
367
- async function completeTask(taskId, taskTitle, checkboxElem, comment = null) {
368
- if (!checkboxElem) checkboxElem = document.getElementById(`cb-${taskId}`);
369
- checkboxElem.disabled = true;
370
- checkboxElem.checked = true;
371
-
372
- const textSpan = checkboxElem.nextElementSibling;
373
- if(textSpan) textSpan.classList.add('task-done');
374
-
375
- // Hide the quick comment UI once completed
376
- const qcContainer = document.getElementById(`qc-${taskId}`);
377
- if (qcContainer) qcContainer.style.display = 'none';
378
-
379
- // Extract comment from the input box if a badge wasn't explicitly clicked
380
- let finalComment = comment;
381
- if (finalComment === null || finalComment === undefined) {
382
- const inputElem = document.getElementById(`input-${taskId}`);
383
- if (inputElem && inputElem.value.trim() !== '') {
384
- finalComment = inputElem.value.trim();
385
- }
386
- }
387
-
388
- // Append comment for the AI timeline if it exists
389
- const finalTitle = finalComment ? `${taskTitle} (Response: ${finalComment})` : taskTitle;
390
-
391
- try {
392
- const res = await fetch('/api/tasks/complete', {
393
- method: 'POST',
394
- headers: { 'Content-Type': 'application/json' },
395
- body: JSON.stringify({ taskId, taskTitle: finalTitle })
396
- });
397
-
398
- if (res.ok) {
399
- addLog(`✅ Task marked complete: ${finalTitle}`, 'var(--success)');
400
- } else {
401
- throw new Error("API Failed");
402
- }
403
- } catch(e) {
404
- checkboxElem.disabled = false;
405
- checkboxElem.checked = false;
406
- if(textSpan) textSpan.classList.remove('task-done');
407
- if (qcContainer) qcContainer.style.display = 'block';
408
- addLog(`❌ Failed to sync task completion.`, 'var(--danger)');
409
- showToast("Failed to sync task", "error");
410
- }
411
- }
412
-
413
- function requestNewThrust() {
414
- const overridePrompt = `[SYSTEM OVERRIDE]: The user has requested the next Thrust. Evaluate the current Timeline, PRD, and recent Git diffs. Check if the previous Thrust tasks are completed. Generate a new <thrust_create> with the next 1-4 immediate steps to keep momentum going.`;
415
-
416
- if (localWs && localWs.readyState === 1) {
417
- localWs.send(JSON.stringify({ type: 'frontend_prompt', payload: overridePrompt }));
418
- addLog(`⚡ Override triggered. Waking up AI Director...`, 'var(--warning)');
419
- } else {
420
- addLog(`❌ Cannot request Thrust: Agent Offline.`, 'var(--danger)');
421
- }
422
- }
423
-
424
- async function fetchDirective() {
425
- try {
426
- const res = await fetch('/api/thrusts/active');
427
- const data = await res.json();
428
-
429
- if (data && !data.error && data.length > 0) {
430
- const thrust = data[0];
431
- document.getElementById('directive-panel').classList.remove('hidden');
432
- document.getElementById('dir-title').innerText = thrust.title || "Current Directive";
433
-
434
- let rawMd = thrust.markdown_content || "";
435
- const targetBox = document.getElementById('dir-targets');
436
- const pillBox = document.getElementById('dir-pills');
437
- pillBox.innerHTML = '';
438
-
439
- const createRegex = /CREATE:\s*\(\+@([^\)]+)\)/g;
440
- const modifyRegex = /MODIFY:\s*\(\~@([^\)]+)\)/g;
441
-
442
- let hasTargets = false;
443
- let match;
444
-
445
- while ((match = createRegex.exec(rawMd)) !== null) {
446
- hasTargets = true;
447
- pillBox.innerHTML += `<span class="target-pill pill-create">📄 Create: ${match[1]}</span>`;
448
- }
449
- while ((match = modifyRegex.exec(rawMd)) !== null) {
450
- hasTargets = true;
451
- pillBox.innerHTML += `<span class="target-pill pill-modify">✍️ Modify: ${match[1]}</span>`;
452
- }
453
-
454
- if (hasTargets) {
455
- targetBox.classList.remove('hidden');
456
- rawMd = rawMd.replace(createRegex, '').replace(modifyRegex, '');
457
- } else {
458
- targetBox.classList.add('hidden');
459
- }
460
-
461
- rawMd = rawMd.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
462
- document.getElementById('dir-content').innerHTML = rawMd.trim().replace(/\n/g, '<br>');
463
-
464
- const tasksBox = document.getElementById('dir-tasks');
465
- tasksBox.innerHTML = '';
466
-
467
- if (thrust.thrust_tasks && thrust.thrust_tasks.length > 0) {
468
- thrust.thrust_tasks.forEach(task => {
469
- const isDone = task.is_completed || task.status === 'done';
470
- const safeTitle = task.title.replace(/'/g, "\\'").replace(/"/g, '&quot;');
471
-
472
- let commentsHtml = '';
473
- if (!isDone) {
474
- // NEW: Dynamically parse the options/placeholder generated by the AI
475
- const hasOptions = task.options && task.options.length > 0;
476
- const placeholder = task.placeholder || "Add a note or comment...";
477
-
478
- if (hasOptions) {
479
- let badgesHtml = task.options.map(qc => {
480
- const safeQc = qc.replace(/'/g, "\\'");
481
- return `<button type="button" class="quick-badge" onclick="document.getElementById('input-${task.id}').value = '${safeQc}'">${qc}</button>`;
482
- }).join('');
483
-
484
- commentsHtml = `
485
- <div id="qc-${task.id}" style="margin-top: 8px; margin-left: 28px;">
486
- <div class="quick-comments" style="margin-bottom: 8px;">
487
- ${badgesHtml}
488
- </div>
489
- <div style="display: flex; gap: 8px; align-items: center;">
490
- <input type="text" id="input-${task.id}" placeholder="${placeholder}" class="task-input">
491
- <button class="btn-small btn-watch" onclick="completeTask('${task.id}', '${safeTitle}', document.getElementById('cb-${task.id}'), document.getElementById('input-${task.id}').value)">Complete</button>
492
- </div>
493
- </div>
494
- `;
495
- }
496
- }
497
-
498
- tasksBox.innerHTML += `
499
- <div class="task-item-wrapper">
500
- <div class="task-item">
501
- <input type="checkbox" id="cb-${task.id}" class="task-checkbox"
502
- ${isDone ? 'checked disabled' : `onchange="completeTask('${task.id}', '${safeTitle}', this)"`}>
503
- <span class="task-text ${isDone ? 'task-done' : ''}">${task.title}</span>
504
- </div>
505
- ${commentsHtml}
506
- </div>
507
- `;
508
- });
509
- } else {
510
- tasksBox.innerHTML = '<span class="task-text" style="color:var(--text-muted)">No specific tasks for this session.</span>';
511
- }
512
- } else {
513
- document.getElementById('directive-panel').classList.add('hidden');
514
- }
515
- } catch (e) {
516
- console.log("No active directive found.");
517
- }
518
- }
519
-
520
- async function fetchCloudProjects() {
521
- const grid = document.getElementById('cloud-projects-grid');
522
- const loader = document.getElementById('cloud-projects-loading');
523
-
524
- grid.innerHTML = '';
525
- loader.style.display = 'block';
526
-
527
- try {
528
- const res = await fetch(`/api/cloud-projects?page=${currentPage}&limit=6`);
529
- const data = await res.json();
530
- loader.style.display = 'none';
531
-
532
- if (data.projects && data.projects.length > 0) {
533
- data.projects.forEach(proj => {
534
- const card = document.createElement('div');
535
- card.className = 'project-card';
536
- const currentLeadInput = document.getElementById('lead-id').value;
537
- if(currentLeadInput === proj.id) card.classList.add('selected');
538
-
539
- card.onclick = () => selectCloudProject(proj.id, card);
540
-
541
- card.innerHTML = `
542
- <div class="pc-title">${proj.name}</div>
543
- <div class="pc-desc">${proj.description || 'No description provided.'}</div>
544
- <div class="pc-meta">
545
- <span>${proj.status}</span>
546
- <span>${new Date(proj.created_at).toLocaleDateString()}</span>
547
- </div>
548
- `;
549
- grid.appendChild(card);
550
- });
551
- } else {
552
- 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>`;
553
- }
554
- } catch (e) {
555
- loader.innerText = "Error fetching cloud projects.";
556
- }
557
- }
558
-
559
- function selectCloudProject(id, cardElement) {
560
- document.getElementById('lead-id').value = id;
561
- document.querySelectorAll('.project-card').forEach(c => c.classList.remove('selected'));
562
- cardElement.classList.add('selected');
563
- addLog(`Selected Cloud Project ID: ${id}`);
564
- document.getElementById('current-path').scrollIntoView({ behavior: 'smooth', block: 'center' });
565
- }
566
-
567
- /* DYNAMIC MCP CLIENT MANAGEMENT */
568
- function setMcpTab(type, btn) {
569
- document.querySelectorAll('.mcp-tab').forEach(b => b.classList.remove('active'));
570
- btn.classList.add('active');
571
- currentMcpType = type;
572
- const form = document.getElementById('mcp-form');
573
-
574
- if (type === 'local') {
575
- form.innerHTML = `
576
- <input type="text" id="mcp-name" placeholder="Tool Name (e.g., Unity, Cursor)" required style="flex: 1;">
577
- <input type="url" id="mcp-url" placeholder="http://localhost:8081/mcp" required style="flex: 2;">
578
- <button type="submit" class="btn-primary" style="width: auto;">+ Link Tool</button>
579
- `;
580
- } else if (type === 'figma') {
581
- form.innerHTML = `
582
- <input type="text" id="mcp-file" placeholder="Figma File Key" required style="flex: 1;">
583
- <input type="password" id="mcp-token" placeholder="Figma Access Token" required style="flex: 2;">
584
- <button type="submit" class="btn-primary" style="width: auto;">+ Link Figma</button>
585
- `;
586
- } else if (type === 'github') {
587
- form.innerHTML = `
588
- <input type="text" id="mcp-repo" placeholder="Repo (owner/repo)" required style="flex: 1;">
589
- <input type="password" id="mcp-token" placeholder="GitHub PAT Token" required style="flex: 2;">
590
- <button type="submit" class="btn-primary" style="width: auto;">+ Link GitHub</button>
591
- `;
592
- }
593
- }
594
-
595
- async function fetchMCPServers() {
596
- try {
597
- const res = await fetch('/api/mcp/servers');
598
- const servers = await res.json();
599
- const list = document.getElementById('mcp-list');
600
- list.innerHTML = '';
601
-
602
- if (servers.length === 0) {
603
- list.innerHTML = `<div style="color: var(--text-muted); font-size: 0.85rem;">No external integrations linked.</div>`;
604
- return;
605
- }
606
-
607
- servers.forEach((s, index) => {
608
- list.innerHTML += `
609
- <div class="mcp-item">
610
- <div><span>🔗 ${s.name}</span> <small>${s.url.substring(0, 40)}${s.url.length > 40 ? '...' : ''}</small></div>
611
- <button class="btn-outline-danger" onclick="removeMCPServer(${index})">Remove</button>
612
- </div>
613
- `;
614
- });
615
- } catch (e) { console.error("Failed to load MCP servers"); }
616
- }
617
-
618
- async function addMCPServer(e) {
619
- e.preventDefault();
620
- let name = "";
621
- let url = "";
622
- const hostBase = `${window.location.protocol}//${window.location.host}`;
623
-
624
- if (currentMcpType === 'local') {
625
- name = document.getElementById('mcp-name').value;
626
- url = document.getElementById('mcp-url').value;
627
- } else if (currentMcpType === 'figma') {
628
- const fileKey = document.getElementById('mcp-file').value;
629
- const token = document.getElementById('mcp-token').value;
630
- name = "Figma";
631
- url = `${hostBase}/api/bridge/figma?fileKey=${encodeURIComponent(fileKey)}&token=${encodeURIComponent(token)}`;
632
- } else if (currentMcpType === 'github') {
633
- const repo = document.getElementById('mcp-repo').value;
634
- const token = document.getElementById('mcp-token').value;
635
- name = "GitHub";
636
- url = `${hostBase}/api/bridge/github?repo=${encodeURIComponent(repo)}&token=${encodeURIComponent(token)}`;
637
- }
638
-
639
- try {
640
- const res = await fetch('/api/mcp/servers', {
641
- method: 'POST',
642
- headers: { 'Content-Type': 'application/json' },
643
- body: JSON.stringify({ name, url })
644
- });
645
- if (res.ok) {
646
- // Reset inputs
647
- if(document.getElementById('mcp-name')) document.getElementById('mcp-name').value = '';
648
- if(document.getElementById('mcp-url')) document.getElementById('mcp-url').value = '';
649
- if(document.getElementById('mcp-file')) document.getElementById('mcp-file').value = '';
650
- if(document.getElementById('mcp-repo')) document.getElementById('mcp-repo').value = '';
651
- if(document.getElementById('mcp-token')) document.getElementById('mcp-token').value = '';
652
-
653
- fetchMCPServers();
654
- addLog(`🔗 Linked External MCP: ${name}`, 'var(--mcp)');
655
- showToast(`Successfully linked ${name} bridge!`, 'success');
656
- }
657
- } catch (err) {
658
- addLog('❌ Failed to link MCP.', 'var(--danger)');
659
- showToast('Failed to link integration.', 'error');
660
- }
661
- }
662
-
663
- async function removeMCPServer(index) {
664
- try {
665
- await fetch(`/api/mcp/servers/${index}`, { method: 'DELETE' });
666
- fetchMCPServers();
667
- showToast("Integration removed", "info");
668
- } catch (err) {}
669
- }
670
-
671
- function connectLocalWebSocket() {
672
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
673
- localWs = new WebSocket(`${protocol}//${window.location.host}`);
674
- localWs.onopen = () => addLog('🔌 Local Stream connected.');
675
-
676
- localWs.onmessage = (event) => {
677
- const data = JSON.parse(event.data);
678
-
679
- if (data.type === 'success' && data.message.includes('Authentication Successful')) fetchStatus();
680
- else if (data.type === 'watch') addLog(`👀 ${data.message}`, 'var(--success)');
681
- else if (data.type === 'sync') addLog(data.message, 'var(--sync)');
682
- else if (data.type === 'mcp') addLog(`${data.message}`, 'var(--mcp)');
683
- else if (data.type === 'success') {
684
- addLog(data.message, 'var(--success)');
685
- }
686
- else if (data.type === 'error') {
687
- addLog(`❌ ${data.message}`, 'var(--danger)');
688
- showToast(data.message, 'error');
689
- }
690
- else if (data.type === 'ai') {
691
- addLog(data.message, 'var(--warning)');
692
- // Clean AI Prefix for the Toast
693
- let cleanMsg = data.message.replace('🔔 [AI]: ', '');
694
- showToast(cleanMsg, 'ai');
695
- }
696
- else if (data.type === 'reload') {
697
- addLog('🔄 AI updated Directive. Fetching...', 'var(--primary)');
698
- fetchDirective();
699
- }
700
- else addLog(data.message);
701
- };
702
- localWs.onclose = () => { setTimeout(connectLocalWebSocket, 3000); };
703
- }
704
-
705
- function sendPrompt(event) {
706
- event.preventDefault();
707
- const input = document.getElementById('prompt-input');
708
- const message = input.value.trim();
709
- if (!message || !localWs || localWs.readyState !== 1) return;
710
- addLog(`> You: ${message}`, '#FFFFFF');
711
- localWs.send(JSON.stringify({ type: 'frontend_prompt', payload: message }));
712
- input.value = '';
713
- }
714
-
715
- async function fetchStatus() {
716
- try {
717
- const response = await fetch('/api/status');
718
- const data = await response.json();
719
-
720
- if (data.auth && data.auth.token) {
721
- document.getElementById('unauth-view').classList.add('hidden');
722
- document.getElementById('auth-view').classList.remove('hidden');
723
- document.getElementById('workspace-panel').classList.remove('hidden');
724
-
725
- const userEmail = data.auth.email || "Linked User";
726
- document.getElementById('profile-wrapper').style.display = 'block';
727
- document.getElementById('auth-email-display').innerText = userEmail;
728
- document.getElementById('profile-btn').innerText = userEmail.charAt(0).toUpperCase();
729
-
730
- fetchCloudProjects();
731
- fetchDirective();
732
- fetchMCPServers();
733
- } else {
734
- document.getElementById('unauth-view').classList.remove('hidden');
735
- document.getElementById('auth-view').classList.add('hidden');
736
- document.getElementById('workspace-panel').classList.add('hidden');
737
- document.getElementById('directive-panel').classList.add('hidden');
738
- document.getElementById('profile-wrapper').style.display = 'none';
739
- }
740
-
741
- if (data.activeProject) {
742
- document.getElementById('proj-dot').className = 'status-dot dot-green';
743
- document.getElementById('proj-status').textContent = `Watching: ${data.activeProject.path}`;
744
- document.getElementById('btn-unlink').classList.remove('hidden');
745
- document.getElementById('lead-id').value = data.activeProject.id;
746
- } else {
747
- document.getElementById('proj-dot').className = 'status-dot dot-yellow';
748
- document.getElementById('proj-status').textContent = `No active project linked`;
749
- document.getElementById('btn-unlink').classList.add('hidden');
750
- }
751
- } catch (e) { addLog('Error fetching status.', 'var(--danger)'); }
752
- }
753
-
754
- async function unlinkProject() {
755
- try {
756
- addLog('Stopping watcher...');
757
- const response = await fetch('/api/unlink', { method: 'POST' });
758
- if ((await response.json()).success) {
759
- document.getElementById('lead-id').value = '';
760
- fetchStatus();
761
- }
762
- } catch (e) {}
763
- }
764
-
765
- async function loadDirectory(targetPath = "") {
766
- try {
767
- const url = targetPath ? `/api/explore?path=${encodeURIComponent(targetPath)}` : '/api/explore';
768
- const data = await (await fetch(url)).json();
769
- if (data.error) return addLog(`Explorer Error: ${data.error}`, 'var(--danger)');
770
-
771
- currentExplorerPath = data.currentPath;
772
- currentFolderName = data.currentPath.split(/[/\\]/).pop() || "Root";
773
-
774
- document.getElementById('current-path').textContent = data.currentPath;
775
- document.getElementById('select-current-btn').textContent = `Watch '${currentFolderName}'`;
776
-
777
- const dirList = document.getElementById('directory-list');
778
- dirList.innerHTML = '';
779
-
780
- if (data.parentPath) {
781
- dirList.innerHTML += `<div class="folder-item">
782
- <div class="folder-info"><span class="folder-icon">⬆️</span><span class="folder-text"><strong>.. (Go Up)</strong></span></div>
783
- <div class="folder-actions"><button class="btn-small btn-open" onclick="loadDirectory('${data.parentPath.replace(/\\/g, '\\\\')}')">Open</button></div>
784
- </div>`;
785
- }
786
-
787
- data.directories.forEach(dir => {
788
- const fullPath = `${data.currentPath}/${dir}`.replace(/\\/g, '\\\\');
789
- dirList.innerHTML += `<div class="folder-item">
790
- <div class="folder-info"><span class="folder-icon">📁</span><span class="folder-text">${dir}</span></div>
791
- <div class="folder-actions">
792
- <button class="btn-small btn-open" onclick="loadDirectory('${fullPath}')">Open 📂</button>
793
- <button class="btn-small btn-watch" onclick="submitWatch('${fullPath}', '${dir}')">Watch 👁️</button>
794
- </div>
795
- </div>`;
796
- });
797
- } catch (e) {}
798
- }
799
-
800
- async function submitWatch(absolutePath, folderName) {
801
- const leadId = document.getElementById('lead-id').value.trim();
802
- if (!leadId) { document.getElementById('lead-id').focus(); return addLog('❌ Error: Select a Cloud Project first', 'var(--danger)'); }
803
-
804
- try {
805
- const res = await fetch('/api/link', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ leadId, folderPath: absolutePath }) });
806
- if ((await res.json()).success) {
807
- fetchStatus();
808
- showToast(`Started watching ${folderName}`, "success");
809
- }
810
- } catch (e) {}
811
- }
812
-
813
- document.addEventListener('DOMContentLoaded', () => {
814
- fetchStatus();
815
- loadDirectory();
816
- connectLocalWebSocket();
817
- setMcpTab('local', document.querySelector('.mcp-tab.active'));
818
- });
819
- </script>
820
- </body>
821
- </html>