thrust-cli 1.0.10 → 1.0.12
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/mcps/ThrustMCPBridge.cs +226 -0
- package/package.json +1 -3
- package/utils/config.js +13 -3
- package/utils/daemon.js +468 -91
- package/utils/daemon2.js +321 -0
package/frontend/index.html
CHANGED
|
@@ -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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
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;
|
|
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-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
.
|
|
68
|
-
|
|
69
|
-
.
|
|
70
|
-
.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.
|
|
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
|
-
.
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
.
|
|
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-
|
|
180
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
<div class="
|
|
213
|
-
<div class="
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
<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
|
-
|
|
219
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
<
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
243
|
-
|
|
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
|
-
<
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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=">
|
|
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;
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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, '"');
|
|
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
|
-
|
|
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
|
-
|
|
299
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
addLog(
|
|
313
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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').
|
|
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').
|
|
558
|
+
document.getElementById('btn-unlink').classList.add('hidden');
|
|
357
559
|
}
|
|
358
|
-
} catch (e) { addLog('Error fetching
|
|
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
|
|
565
|
+
addLog('Stopping watcher...');
|
|
364
566
|
const response = await fetch('/api/unlink', { method: 'POST' });
|
|
365
|
-
|
|
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
|
|
382
|
-
|
|
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
|
-
|
|
391
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
|
467
|
-
|
|
468
|
-
|
|
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();
|
|
485
|
-
addLog('Dashboard UI initialized.');
|
|
621
|
+
loadDirectory();
|
|
622
|
+
connectLocalWebSocket();
|
|
486
623
|
});
|
|
487
624
|
</script>
|
|
488
625
|
</body>
|