orquesta-agent 0.2.0 → 0.2.1
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/dist/ui/public/app.js +411 -0
- package/dist/ui/public/index.html +144 -0
- package/dist/ui/public/style.css +471 -0
- package/package.json +2 -2
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
// Orquesta Agent Manager Frontend
|
|
2
|
+
|
|
3
|
+
const API_BASE = '/api'
|
|
4
|
+
let agents = []
|
|
5
|
+
let systemInfo = null
|
|
6
|
+
let editingAgentId = null
|
|
7
|
+
let logsAgentId = null
|
|
8
|
+
let logsInterval = null
|
|
9
|
+
|
|
10
|
+
// Load system info
|
|
11
|
+
async function loadSystemInfo() {
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch(`${API_BASE}/system`)
|
|
14
|
+
systemInfo = await res.json()
|
|
15
|
+
|
|
16
|
+
// Update UI
|
|
17
|
+
document.getElementById('version').textContent = `v${systemInfo.version}`
|
|
18
|
+
document.getElementById('platform').textContent = systemInfo.platform
|
|
19
|
+
|
|
20
|
+
// Claude CLI status
|
|
21
|
+
const claudeStatus = document.getElementById('claude-status')
|
|
22
|
+
if (systemInfo.hasClaudeCli) {
|
|
23
|
+
if (systemInfo.claudeAuth?.authenticated) {
|
|
24
|
+
claudeStatus.innerHTML = `<span class="badge success">✓ ${systemInfo.claudeAuth.method}</span>`
|
|
25
|
+
} else {
|
|
26
|
+
claudeStatus.innerHTML = `<span class="badge warning">⚠ Not authenticated</span>`
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
claudeStatus.innerHTML = `<span class="badge danger">✗ Not installed</span>`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Orquesta CLI status
|
|
33
|
+
const orquestaStatus = document.getElementById('orquesta-status')
|
|
34
|
+
if (systemInfo.hasOrquestaCli) {
|
|
35
|
+
orquestaStatus.innerHTML = `<span class="badge success">✓ Installed</span>`
|
|
36
|
+
} else {
|
|
37
|
+
orquestaStatus.innerHTML = `<span class="badge danger">✗ Not installed</span>`
|
|
38
|
+
}
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error('Failed to load system info:', err)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Load agents
|
|
45
|
+
async function loadAgents() {
|
|
46
|
+
try {
|
|
47
|
+
const res = await fetch(`${API_BASE}/agents`)
|
|
48
|
+
agents = await res.json()
|
|
49
|
+
renderAgents()
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error('Failed to load agents:', err)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Render agents list
|
|
56
|
+
function renderAgents() {
|
|
57
|
+
const container = document.getElementById('agents-container')
|
|
58
|
+
const emptyState = document.getElementById('empty-state')
|
|
59
|
+
|
|
60
|
+
if (agents.length === 0) {
|
|
61
|
+
emptyState.style.display = 'block'
|
|
62
|
+
container.innerHTML = ''
|
|
63
|
+
container.appendChild(emptyState)
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
emptyState.style.display = 'none'
|
|
68
|
+
container.innerHTML = agents.map(agent => `
|
|
69
|
+
<div class="agent-card">
|
|
70
|
+
<div class="agent-header">
|
|
71
|
+
<div class="agent-title">
|
|
72
|
+
<span class="status-dot ${agent.status}"></span>
|
|
73
|
+
<h3>${escapeHtml(agent.name)}</h3>
|
|
74
|
+
</div>
|
|
75
|
+
<span class="badge ${agent.status === 'running' ? 'success' : agent.status === 'error' ? 'danger' : ''}">${agent.status.toUpperCase()}</span>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="agent-info">
|
|
79
|
+
<div class="info-row">
|
|
80
|
+
<span class="label">Working Dir:</span>
|
|
81
|
+
<span class="value">${escapeHtml(agent.workingDir)}</span>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="info-row">
|
|
84
|
+
<span class="label">CLI:</span>
|
|
85
|
+
<span class="value">${agent.cliPreference}</span>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="info-row">
|
|
88
|
+
<span class="label">Permission:</span>
|
|
89
|
+
<span class="value">${agent.permissionMode}</span>
|
|
90
|
+
</div>
|
|
91
|
+
${agent.pid ? `
|
|
92
|
+
<div class="info-row">
|
|
93
|
+
<span class="label">PID:</span>
|
|
94
|
+
<span class="value">${agent.pid}</span>
|
|
95
|
+
</div>
|
|
96
|
+
` : ''}
|
|
97
|
+
${agent.uptime ? `
|
|
98
|
+
<div class="info-row">
|
|
99
|
+
<span class="label">Uptime:</span>
|
|
100
|
+
<span class="value">${formatUptime(agent.uptime)}</span>
|
|
101
|
+
</div>
|
|
102
|
+
` : ''}
|
|
103
|
+
${agent.error ? `
|
|
104
|
+
<div class="info-row">
|
|
105
|
+
<span class="label">Error:</span>
|
|
106
|
+
<span class="value" style="color: #c62828;">${escapeHtml(agent.error)}</span>
|
|
107
|
+
</div>
|
|
108
|
+
` : ''}
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div class="agent-actions">
|
|
112
|
+
${agent.status === 'running' ? `
|
|
113
|
+
<button class="btn btn-sm btn-secondary" onclick="stopAgent('${agent.id}')">
|
|
114
|
+
<span class="icon">⏹</span> Stop
|
|
115
|
+
</button>
|
|
116
|
+
<button class="btn btn-sm btn-secondary" onclick="restartAgent('${agent.id}')">
|
|
117
|
+
<span class="icon">🔄</span> Restart
|
|
118
|
+
</button>
|
|
119
|
+
` : `
|
|
120
|
+
<button class="btn btn-sm btn-success" onclick="startAgent('${agent.id}')">
|
|
121
|
+
<span class="icon">▶️</span> Start
|
|
122
|
+
</button>
|
|
123
|
+
`}
|
|
124
|
+
<button class="btn btn-sm btn-secondary" onclick="viewLogs('${agent.id}', '${escapeHtml(agent.name)}')">
|
|
125
|
+
<span class="icon">📄</span> Logs
|
|
126
|
+
</button>
|
|
127
|
+
<button class="btn btn-sm btn-secondary" onclick="editAgent('${agent.id}')">
|
|
128
|
+
<span class="icon">✏️</span> Edit
|
|
129
|
+
</button>
|
|
130
|
+
<button class="btn btn-sm btn-danger" onclick="deleteAgent('${agent.id}', '${escapeHtml(agent.name)}')">
|
|
131
|
+
<span class="icon">🗑️</span> Delete
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
`).join('')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Start agent
|
|
139
|
+
async function startAgent(id) {
|
|
140
|
+
try {
|
|
141
|
+
const res = await fetch(`${API_BASE}/agents/${id}/start`, { method: 'POST' })
|
|
142
|
+
const data = await res.json()
|
|
143
|
+
|
|
144
|
+
if (!res.ok) {
|
|
145
|
+
alert(`Failed to start agent: ${data.error}`)
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
setTimeout(loadAgents, 1000)
|
|
150
|
+
} catch (err) {
|
|
151
|
+
alert(`Failed to start agent: ${err.message}`)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Stop agent
|
|
156
|
+
async function stopAgent(id) {
|
|
157
|
+
try {
|
|
158
|
+
const res = await fetch(`${API_BASE}/agents/${id}/stop`, { method: 'POST' })
|
|
159
|
+
const data = await res.json()
|
|
160
|
+
|
|
161
|
+
if (!res.ok) {
|
|
162
|
+
alert(`Failed to stop agent: ${data.error}`)
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
setTimeout(loadAgents, 1000)
|
|
167
|
+
} catch (err) {
|
|
168
|
+
alert(`Failed to stop agent: ${err.message}`)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Restart agent
|
|
173
|
+
async function restartAgent(id) {
|
|
174
|
+
try {
|
|
175
|
+
const res = await fetch(`${API_BASE}/agents/${id}/restart`, { method: 'POST' })
|
|
176
|
+
const data = await res.json()
|
|
177
|
+
|
|
178
|
+
if (!res.ok) {
|
|
179
|
+
alert(`Failed to restart agent: ${data.error}`)
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
setTimeout(loadAgents, 2000)
|
|
184
|
+
} catch (err) {
|
|
185
|
+
alert(`Failed to restart agent: ${err.message}`)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Delete agent
|
|
190
|
+
async function deleteAgent(id, name) {
|
|
191
|
+
if (!confirm(`Are you sure you want to delete agent "${name}"? This will stop the agent and remove all configuration.`)) {
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const res = await fetch(`${API_BASE}/agents/${id}`, { method: 'DELETE' })
|
|
197
|
+
const data = await res.json()
|
|
198
|
+
|
|
199
|
+
if (!res.ok) {
|
|
200
|
+
alert(`Failed to delete agent: ${data.error}`)
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
loadAgents()
|
|
205
|
+
} catch (err) {
|
|
206
|
+
alert(`Failed to delete agent: ${err.message}`)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Open add agent modal
|
|
211
|
+
function openAddAgentModal() {
|
|
212
|
+
editingAgentId = null
|
|
213
|
+
document.getElementById('modal-title').textContent = 'Add New Agent'
|
|
214
|
+
document.getElementById('agent-form').reset()
|
|
215
|
+
document.getElementById('agent-modal').classList.add('active')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Open edit agent modal
|
|
219
|
+
function editAgent(id) {
|
|
220
|
+
const agent = agents.find(a => a.id === id)
|
|
221
|
+
if (!agent) return
|
|
222
|
+
|
|
223
|
+
editingAgentId = id
|
|
224
|
+
document.getElementById('modal-title').textContent = 'Edit Agent'
|
|
225
|
+
document.getElementById('agent-name').value = agent.name
|
|
226
|
+
document.getElementById('agent-working-dir').value = agent.workingDir
|
|
227
|
+
document.getElementById('agent-token').value = agent.token
|
|
228
|
+
document.getElementById('agent-cli-preference').value = agent.cliPreference
|
|
229
|
+
document.getElementById('agent-permission-mode').value = agent.permissionMode
|
|
230
|
+
document.getElementById('agent-auto-start').checked = agent.autoStart
|
|
231
|
+
document.getElementById('agent-auto-pull').checked = agent.autoPull
|
|
232
|
+
document.getElementById('agent-modal').classList.add('active')
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Close agent modal
|
|
236
|
+
function closeAgentModal() {
|
|
237
|
+
document.getElementById('agent-modal').classList.remove('active')
|
|
238
|
+
editingAgentId = null
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Save agent
|
|
242
|
+
async function saveAgent(event) {
|
|
243
|
+
event.preventDefault()
|
|
244
|
+
|
|
245
|
+
const formData = {
|
|
246
|
+
name: document.getElementById('agent-name').value,
|
|
247
|
+
workingDir: document.getElementById('agent-working-dir').value,
|
|
248
|
+
token: document.getElementById('agent-token').value,
|
|
249
|
+
cliPreference: document.getElementById('agent-cli-preference').value,
|
|
250
|
+
permissionMode: document.getElementById('agent-permission-mode').value,
|
|
251
|
+
autoStart: document.getElementById('agent-auto-start').checked,
|
|
252
|
+
autoPull: document.getElementById('agent-auto-pull').checked,
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
let res
|
|
257
|
+
if (editingAgentId) {
|
|
258
|
+
// Update existing agent
|
|
259
|
+
res = await fetch(`${API_BASE}/agents/${editingAgentId}`, {
|
|
260
|
+
method: 'PUT',
|
|
261
|
+
headers: { 'Content-Type': 'application/json' },
|
|
262
|
+
body: JSON.stringify(formData),
|
|
263
|
+
})
|
|
264
|
+
} else {
|
|
265
|
+
// Create new agent
|
|
266
|
+
res = await fetch(`${API_BASE}/agents`, {
|
|
267
|
+
method: 'POST',
|
|
268
|
+
headers: { 'Content-Type': 'application/json' },
|
|
269
|
+
body: JSON.stringify(formData),
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const data = await res.json()
|
|
274
|
+
|
|
275
|
+
if (!res.ok) {
|
|
276
|
+
alert(`Failed to save agent: ${data.error}`)
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
closeAgentModal()
|
|
281
|
+
loadAgents()
|
|
282
|
+
} catch (err) {
|
|
283
|
+
alert(`Failed to save agent: ${err.message}`)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// View logs
|
|
288
|
+
function viewLogs(id, name) {
|
|
289
|
+
logsAgentId = id
|
|
290
|
+
document.getElementById('logs-title').textContent = `Logs: ${name}`
|
|
291
|
+
document.getElementById('logs-modal').classList.add('active')
|
|
292
|
+
loadLogs()
|
|
293
|
+
|
|
294
|
+
// Auto-refresh logs every 2 seconds
|
|
295
|
+
logsInterval = setInterval(() => {
|
|
296
|
+
if (document.getElementById('logs-autoscroll').checked) {
|
|
297
|
+
loadLogs()
|
|
298
|
+
}
|
|
299
|
+
}, 2000)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Load logs
|
|
303
|
+
async function loadLogs() {
|
|
304
|
+
if (!logsAgentId) return
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
const res = await fetch(`${API_BASE}/agents/${logsAgentId}/logs?lines=200`)
|
|
308
|
+
const data = await res.json()
|
|
309
|
+
|
|
310
|
+
const logsContent = document.getElementById('logs-content')
|
|
311
|
+
logsContent.textContent = data.logs.join('\n')
|
|
312
|
+
|
|
313
|
+
// Auto-scroll to bottom if enabled
|
|
314
|
+
if (document.getElementById('logs-autoscroll').checked) {
|
|
315
|
+
logsContent.scrollTop = logsContent.scrollHeight
|
|
316
|
+
}
|
|
317
|
+
} catch (err) {
|
|
318
|
+
console.error('Failed to load logs:', err)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Clear logs
|
|
323
|
+
async function clearLogs() {
|
|
324
|
+
if (!logsAgentId) return
|
|
325
|
+
|
|
326
|
+
if (!confirm('Are you sure you want to clear all logs for this agent?')) {
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
const res = await fetch(`${API_BASE}/agents/${logsAgentId}/logs`, { method: 'DELETE' })
|
|
332
|
+
const data = await res.json()
|
|
333
|
+
|
|
334
|
+
if (!res.ok) {
|
|
335
|
+
alert(`Failed to clear logs: ${data.error}`)
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
loadLogs()
|
|
340
|
+
} catch (err) {
|
|
341
|
+
alert(`Failed to clear logs: ${err.message}`)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Close logs modal
|
|
346
|
+
function closeLogsModal() {
|
|
347
|
+
document.getElementById('logs-modal').classList.remove('active')
|
|
348
|
+
if (logsInterval) {
|
|
349
|
+
clearInterval(logsInterval)
|
|
350
|
+
logsInterval = null
|
|
351
|
+
}
|
|
352
|
+
logsAgentId = null
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Utility: Escape HTML
|
|
356
|
+
function escapeHtml(text) {
|
|
357
|
+
const div = document.createElement('div')
|
|
358
|
+
div.textContent = text
|
|
359
|
+
return div.innerHTML
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Utility: Format uptime
|
|
363
|
+
function formatUptime(ms) {
|
|
364
|
+
const seconds = Math.floor(ms / 1000)
|
|
365
|
+
const minutes = Math.floor(seconds / 60)
|
|
366
|
+
const hours = Math.floor(minutes / 60)
|
|
367
|
+
const days = Math.floor(hours / 24)
|
|
368
|
+
|
|
369
|
+
if (days > 0) return `${days}d ${hours % 24}h`
|
|
370
|
+
if (hours > 0) return `${hours}h ${minutes % 60}m`
|
|
371
|
+
if (minutes > 0) return `${minutes}m ${seconds % 60}s`
|
|
372
|
+
return `${seconds}s`
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Event listeners
|
|
376
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
377
|
+
loadSystemInfo()
|
|
378
|
+
loadAgents()
|
|
379
|
+
|
|
380
|
+
// Refresh button
|
|
381
|
+
document.getElementById('refresh-btn').addEventListener('click', () => {
|
|
382
|
+
document.getElementById('refresh-btn').querySelector('.icon').classList.add('loading')
|
|
383
|
+
Promise.all([loadSystemInfo(), loadAgents()]).then(() => {
|
|
384
|
+
document.getElementById('refresh-btn').querySelector('.icon').classList.remove('loading')
|
|
385
|
+
})
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
// Add agent button
|
|
389
|
+
document.getElementById('add-agent-btn').addEventListener('click', openAddAgentModal)
|
|
390
|
+
|
|
391
|
+
// Agent form submit
|
|
392
|
+
document.getElementById('agent-form').addEventListener('submit', saveAgent)
|
|
393
|
+
|
|
394
|
+
// Logs controls
|
|
395
|
+
document.getElementById('logs-refresh').addEventListener('click', loadLogs)
|
|
396
|
+
document.getElementById('logs-clear').addEventListener('click', clearLogs)
|
|
397
|
+
|
|
398
|
+
// Auto-refresh agents every 5 seconds
|
|
399
|
+
setInterval(loadAgents, 5000)
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
// Close modals on background click
|
|
403
|
+
window.addEventListener('click', (e) => {
|
|
404
|
+
if (e.target.classList.contains('modal')) {
|
|
405
|
+
e.target.classList.remove('active')
|
|
406
|
+
if (e.target.id === 'logs-modal' && logsInterval) {
|
|
407
|
+
clearInterval(logsInterval)
|
|
408
|
+
logsInterval = null
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
})
|
|
@@ -0,0 +1,144 @@
|
|
|
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>Orquesta Agent Manager</title>
|
|
7
|
+
<link rel="stylesheet" href="style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="container">
|
|
11
|
+
<!-- Header -->
|
|
12
|
+
<header class="header">
|
|
13
|
+
<div class="header-content">
|
|
14
|
+
<h1>🎼 Orquesta Agent Manager</h1>
|
|
15
|
+
<div class="header-info">
|
|
16
|
+
<span id="version" class="badge">v0.0.0</span>
|
|
17
|
+
<button id="refresh-btn" class="btn btn-secondary">
|
|
18
|
+
<span class="icon">🔄</span> Refresh
|
|
19
|
+
</button>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</header>
|
|
23
|
+
|
|
24
|
+
<!-- System Info -->
|
|
25
|
+
<div id="system-info" class="system-info">
|
|
26
|
+
<div class="system-item">
|
|
27
|
+
<span class="label">Platform:</span>
|
|
28
|
+
<span id="platform">-</span>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="system-item">
|
|
31
|
+
<span class="label">Claude CLI:</span>
|
|
32
|
+
<span id="claude-status">-</span>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="system-item">
|
|
35
|
+
<span class="label">Orquesta CLI:</span>
|
|
36
|
+
<span id="orquesta-status">-</span>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<!-- Actions -->
|
|
41
|
+
<div class="actions">
|
|
42
|
+
<button id="add-agent-btn" class="btn btn-primary">
|
|
43
|
+
<span class="icon">➕</span> Add New Agent
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<!-- Agents List -->
|
|
48
|
+
<div id="agents-container" class="agents-container">
|
|
49
|
+
<div id="empty-state" class="empty-state">
|
|
50
|
+
<div class="empty-icon">📦</div>
|
|
51
|
+
<h3>No agents configured</h3>
|
|
52
|
+
<p>Click "Add New Agent" to get started</p>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<!-- Modal: Add/Edit Agent -->
|
|
57
|
+
<div id="agent-modal" class="modal">
|
|
58
|
+
<div class="modal-content">
|
|
59
|
+
<div class="modal-header">
|
|
60
|
+
<h2 id="modal-title">Configure Agent</h2>
|
|
61
|
+
<button class="modal-close" onclick="closeAgentModal()">×</button>
|
|
62
|
+
</div>
|
|
63
|
+
<form id="agent-form" class="modal-body">
|
|
64
|
+
<div class="form-group">
|
|
65
|
+
<label for="agent-name">Agent Name *</label>
|
|
66
|
+
<input type="text" id="agent-name" required placeholder="my-project">
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div class="form-group">
|
|
70
|
+
<label for="agent-working-dir">Working Directory *</label>
|
|
71
|
+
<input type="text" id="agent-working-dir" required placeholder="/path/to/project">
|
|
72
|
+
<small>The folder where your project code lives</small>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div class="form-group">
|
|
76
|
+
<label for="agent-token">Token *</label>
|
|
77
|
+
<input type="password" id="agent-token" required placeholder="oat_...">
|
|
78
|
+
<small>Get your token from Orquesta dashboard</small>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div class="form-group">
|
|
82
|
+
<label for="agent-cli-preference">CLI Preference</label>
|
|
83
|
+
<select id="agent-cli-preference">
|
|
84
|
+
<option value="auto">Auto (prefer Orquesta CLI)</option>
|
|
85
|
+
<option value="orquesta">Orquesta CLI (force local LLM)</option>
|
|
86
|
+
<option value="claude">Claude CLI (force Anthropic API)</option>
|
|
87
|
+
</select>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div class="form-group">
|
|
91
|
+
<label for="agent-permission-mode">Permission Mode</label>
|
|
92
|
+
<select id="agent-permission-mode">
|
|
93
|
+
<option value="auto">Auto (no approval needed)</option>
|
|
94
|
+
<option value="supervised">Supervised (require approval)</option>
|
|
95
|
+
</select>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div class="form-group">
|
|
99
|
+
<label class="checkbox-label">
|
|
100
|
+
<input type="checkbox" id="agent-auto-start">
|
|
101
|
+
<span>Auto-start on boot (systemd/launchd)</span>
|
|
102
|
+
</label>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div class="form-group">
|
|
106
|
+
<label class="checkbox-label">
|
|
107
|
+
<input type="checkbox" id="agent-auto-pull" checked>
|
|
108
|
+
<span>Auto-pull git before prompts</span>
|
|
109
|
+
</label>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div class="form-actions">
|
|
113
|
+
<button type="button" class="btn btn-secondary" onclick="closeAgentModal()">Cancel</button>
|
|
114
|
+
<button type="submit" class="btn btn-primary">Save Agent</button>
|
|
115
|
+
</div>
|
|
116
|
+
</form>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<!-- Modal: Logs Viewer -->
|
|
121
|
+
<div id="logs-modal" class="modal">
|
|
122
|
+
<div class="modal-content modal-large">
|
|
123
|
+
<div class="modal-header">
|
|
124
|
+
<h2 id="logs-title">Agent Logs</h2>
|
|
125
|
+
<button class="modal-close" onclick="closeLogsModal()">×</button>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="modal-body">
|
|
128
|
+
<div class="logs-controls">
|
|
129
|
+
<button id="logs-refresh" class="btn btn-secondary btn-sm">🔄 Refresh</button>
|
|
130
|
+
<button id="logs-clear" class="btn btn-secondary btn-sm">🗑️ Clear Logs</button>
|
|
131
|
+
<label class="checkbox-label">
|
|
132
|
+
<input type="checkbox" id="logs-autoscroll" checked>
|
|
133
|
+
<span>Auto-scroll</span>
|
|
134
|
+
</label>
|
|
135
|
+
</div>
|
|
136
|
+
<pre id="logs-content" class="logs-content"></pre>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<script src="app.js"></script>
|
|
143
|
+
</body>
|
|
144
|
+
</html>
|
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
/* Reset and base styles */
|
|
2
|
+
* {
|
|
3
|
+
margin: 0;
|
|
4
|
+
padding: 0;
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
body {
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
|
|
10
|
+
background: #f5f7fa;
|
|
11
|
+
color: #2c3e50;
|
|
12
|
+
line-height: 1.6;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.container {
|
|
16
|
+
max-width: 1200px;
|
|
17
|
+
margin: 0 auto;
|
|
18
|
+
padding: 20px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Header */
|
|
22
|
+
.header {
|
|
23
|
+
background: white;
|
|
24
|
+
border-radius: 12px;
|
|
25
|
+
padding: 24px;
|
|
26
|
+
margin-bottom: 24px;
|
|
27
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.header-content {
|
|
31
|
+
display: flex;
|
|
32
|
+
justify-content: space-between;
|
|
33
|
+
align-items: center;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.header h1 {
|
|
37
|
+
font-size: 28px;
|
|
38
|
+
color: #2c3e50;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.header-info {
|
|
42
|
+
display: flex;
|
|
43
|
+
gap: 12px;
|
|
44
|
+
align-items: center;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* System Info */
|
|
48
|
+
.system-info {
|
|
49
|
+
background: #f8f9fa;
|
|
50
|
+
border-radius: 8px;
|
|
51
|
+
padding: 16px;
|
|
52
|
+
margin-bottom: 24px;
|
|
53
|
+
display: flex;
|
|
54
|
+
gap: 24px;
|
|
55
|
+
flex-wrap: wrap;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.system-item {
|
|
59
|
+
display: flex;
|
|
60
|
+
gap: 8px;
|
|
61
|
+
font-size: 14px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.system-item .label {
|
|
65
|
+
font-weight: 600;
|
|
66
|
+
color: #6c757d;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Badge */
|
|
70
|
+
.badge {
|
|
71
|
+
display: inline-block;
|
|
72
|
+
padding: 4px 12px;
|
|
73
|
+
border-radius: 12px;
|
|
74
|
+
font-size: 12px;
|
|
75
|
+
font-weight: 600;
|
|
76
|
+
background: #e3f2fd;
|
|
77
|
+
color: #1976d2;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.badge.success {
|
|
81
|
+
background: #e8f5e9;
|
|
82
|
+
color: #2e7d32;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.badge.danger {
|
|
86
|
+
background: #ffebee;
|
|
87
|
+
color: #c62828;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.badge.warning {
|
|
91
|
+
background: #fff3e0;
|
|
92
|
+
color: #ef6c00;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Buttons */
|
|
96
|
+
.btn {
|
|
97
|
+
display: inline-flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
gap: 6px;
|
|
100
|
+
padding: 10px 20px;
|
|
101
|
+
border: none;
|
|
102
|
+
border-radius: 8px;
|
|
103
|
+
font-size: 14px;
|
|
104
|
+
font-weight: 500;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
transition: all 0.2s;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.btn:hover {
|
|
110
|
+
transform: translateY(-1px);
|
|
111
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.btn-primary {
|
|
115
|
+
background: #1976d2;
|
|
116
|
+
color: white;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.btn-primary:hover {
|
|
120
|
+
background: #1565c0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.btn-secondary {
|
|
124
|
+
background: #e0e0e0;
|
|
125
|
+
color: #424242;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.btn-secondary:hover {
|
|
129
|
+
background: #bdbdbd;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.btn-success {
|
|
133
|
+
background: #2e7d32;
|
|
134
|
+
color: white;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.btn-success:hover {
|
|
138
|
+
background: #1b5e20;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.btn-danger {
|
|
142
|
+
background: #c62828;
|
|
143
|
+
color: white;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.btn-danger:hover {
|
|
147
|
+
background: #b71c1c;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.btn-sm {
|
|
151
|
+
padding: 6px 12px;
|
|
152
|
+
font-size: 12px;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.btn .icon {
|
|
156
|
+
font-size: 16px;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* Actions */
|
|
160
|
+
.actions {
|
|
161
|
+
margin-bottom: 24px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Empty State */
|
|
165
|
+
.empty-state {
|
|
166
|
+
text-align: center;
|
|
167
|
+
padding: 60px 20px;
|
|
168
|
+
background: white;
|
|
169
|
+
border-radius: 12px;
|
|
170
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.empty-icon {
|
|
174
|
+
font-size: 64px;
|
|
175
|
+
margin-bottom: 16px;
|
|
176
|
+
opacity: 0.5;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.empty-state h3 {
|
|
180
|
+
font-size: 20px;
|
|
181
|
+
margin-bottom: 8px;
|
|
182
|
+
color: #2c3e50;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.empty-state p {
|
|
186
|
+
color: #6c757d;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* Agents Container */
|
|
190
|
+
.agents-container {
|
|
191
|
+
display: grid;
|
|
192
|
+
gap: 16px;
|
|
193
|
+
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* Agent Card */
|
|
197
|
+
.agent-card {
|
|
198
|
+
background: white;
|
|
199
|
+
border-radius: 12px;
|
|
200
|
+
padding: 20px;
|
|
201
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
|
202
|
+
transition: all 0.2s;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.agent-card:hover {
|
|
206
|
+
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.agent-header {
|
|
210
|
+
display: flex;
|
|
211
|
+
justify-content: space-between;
|
|
212
|
+
align-items: flex-start;
|
|
213
|
+
margin-bottom: 16px;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.agent-title {
|
|
217
|
+
display: flex;
|
|
218
|
+
align-items: center;
|
|
219
|
+
gap: 10px;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.agent-title h3 {
|
|
223
|
+
font-size: 18px;
|
|
224
|
+
color: #2c3e50;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.status-dot {
|
|
228
|
+
width: 12px;
|
|
229
|
+
height: 12px;
|
|
230
|
+
border-radius: 50%;
|
|
231
|
+
display: inline-block;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.status-dot.running {
|
|
235
|
+
background: #2e7d32;
|
|
236
|
+
box-shadow: 0 0 8px #2e7d32;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.status-dot.stopped {
|
|
240
|
+
background: #9e9e9e;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.status-dot.error {
|
|
244
|
+
background: #c62828;
|
|
245
|
+
box-shadow: 0 0 8px #c62828;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.agent-info {
|
|
249
|
+
margin-bottom: 16px;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.info-row {
|
|
253
|
+
display: flex;
|
|
254
|
+
gap: 8px;
|
|
255
|
+
margin-bottom: 8px;
|
|
256
|
+
font-size: 13px;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.info-row .label {
|
|
260
|
+
font-weight: 600;
|
|
261
|
+
color: #6c757d;
|
|
262
|
+
min-width: 100px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.info-row .value {
|
|
266
|
+
color: #2c3e50;
|
|
267
|
+
word-break: break-all;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.agent-actions {
|
|
271
|
+
display: flex;
|
|
272
|
+
gap: 8px;
|
|
273
|
+
flex-wrap: wrap;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Modal */
|
|
277
|
+
.modal {
|
|
278
|
+
display: none;
|
|
279
|
+
position: fixed;
|
|
280
|
+
top: 0;
|
|
281
|
+
left: 0;
|
|
282
|
+
width: 100%;
|
|
283
|
+
height: 100%;
|
|
284
|
+
background: rgba(0,0,0,0.5);
|
|
285
|
+
z-index: 1000;
|
|
286
|
+
align-items: center;
|
|
287
|
+
justify-content: center;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.modal.active {
|
|
291
|
+
display: flex;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.modal-content {
|
|
295
|
+
background: white;
|
|
296
|
+
border-radius: 12px;
|
|
297
|
+
width: 90%;
|
|
298
|
+
max-width: 600px;
|
|
299
|
+
max-height: 90vh;
|
|
300
|
+
overflow-y: auto;
|
|
301
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.modal-large {
|
|
305
|
+
max-width: 900px;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.modal-header {
|
|
309
|
+
display: flex;
|
|
310
|
+
justify-content: space-between;
|
|
311
|
+
align-items: center;
|
|
312
|
+
padding: 20px 24px;
|
|
313
|
+
border-bottom: 1px solid #e0e0e0;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.modal-header h2 {
|
|
317
|
+
font-size: 20px;
|
|
318
|
+
color: #2c3e50;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.modal-close {
|
|
322
|
+
background: none;
|
|
323
|
+
border: none;
|
|
324
|
+
font-size: 28px;
|
|
325
|
+
color: #9e9e9e;
|
|
326
|
+
cursor: pointer;
|
|
327
|
+
line-height: 1;
|
|
328
|
+
padding: 0;
|
|
329
|
+
width: 32px;
|
|
330
|
+
height: 32px;
|
|
331
|
+
display: flex;
|
|
332
|
+
align-items: center;
|
|
333
|
+
justify-content: center;
|
|
334
|
+
border-radius: 4px;
|
|
335
|
+
transition: all 0.2s;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.modal-close:hover {
|
|
339
|
+
background: #f5f5f5;
|
|
340
|
+
color: #424242;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.modal-body {
|
|
344
|
+
padding: 24px;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/* Form */
|
|
348
|
+
.form-group {
|
|
349
|
+
margin-bottom: 20px;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.form-group label {
|
|
353
|
+
display: block;
|
|
354
|
+
margin-bottom: 6px;
|
|
355
|
+
font-weight: 600;
|
|
356
|
+
color: #2c3e50;
|
|
357
|
+
font-size: 14px;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.form-group input[type="text"],
|
|
361
|
+
.form-group input[type="password"],
|
|
362
|
+
.form-group select {
|
|
363
|
+
width: 100%;
|
|
364
|
+
padding: 10px 12px;
|
|
365
|
+
border: 1px solid #e0e0e0;
|
|
366
|
+
border-radius: 6px;
|
|
367
|
+
font-size: 14px;
|
|
368
|
+
font-family: inherit;
|
|
369
|
+
transition: all 0.2s;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.form-group input:focus,
|
|
373
|
+
.form-group select:focus {
|
|
374
|
+
outline: none;
|
|
375
|
+
border-color: #1976d2;
|
|
376
|
+
box-shadow: 0 0 0 3px rgba(25,118,210,0.1);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.form-group small {
|
|
380
|
+
display: block;
|
|
381
|
+
margin-top: 4px;
|
|
382
|
+
font-size: 12px;
|
|
383
|
+
color: #6c757d;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.checkbox-label {
|
|
387
|
+
display: flex;
|
|
388
|
+
align-items: center;
|
|
389
|
+
gap: 8px;
|
|
390
|
+
cursor: pointer;
|
|
391
|
+
font-weight: normal;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.checkbox-label input[type="checkbox"] {
|
|
395
|
+
width: 18px;
|
|
396
|
+
height: 18px;
|
|
397
|
+
cursor: pointer;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.form-actions {
|
|
401
|
+
display: flex;
|
|
402
|
+
gap: 12px;
|
|
403
|
+
justify-content: flex-end;
|
|
404
|
+
margin-top: 24px;
|
|
405
|
+
padding-top: 24px;
|
|
406
|
+
border-top: 1px solid #e0e0e0;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/* Logs */
|
|
410
|
+
.logs-controls {
|
|
411
|
+
display: flex;
|
|
412
|
+
gap: 12px;
|
|
413
|
+
margin-bottom: 16px;
|
|
414
|
+
align-items: center;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.logs-content {
|
|
418
|
+
background: #1e1e1e;
|
|
419
|
+
color: #d4d4d4;
|
|
420
|
+
padding: 16px;
|
|
421
|
+
border-radius: 6px;
|
|
422
|
+
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
|
423
|
+
font-size: 13px;
|
|
424
|
+
line-height: 1.5;
|
|
425
|
+
max-height: 500px;
|
|
426
|
+
overflow-y: auto;
|
|
427
|
+
white-space: pre-wrap;
|
|
428
|
+
word-wrap: break-word;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.logs-content:empty::before {
|
|
432
|
+
content: 'No logs available';
|
|
433
|
+
color: #6c757d;
|
|
434
|
+
font-style: italic;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/* Responsive */
|
|
438
|
+
@media (max-width: 768px) {
|
|
439
|
+
.agents-container {
|
|
440
|
+
grid-template-columns: 1fr;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.header-content {
|
|
444
|
+
flex-direction: column;
|
|
445
|
+
gap: 16px;
|
|
446
|
+
align-items: flex-start;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.system-info {
|
|
450
|
+
flex-direction: column;
|
|
451
|
+
gap: 8px;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.agent-actions {
|
|
455
|
+
flex-direction: column;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.agent-actions .btn {
|
|
459
|
+
width: 100%;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/* Loading spinner */
|
|
464
|
+
@keyframes spin {
|
|
465
|
+
to { transform: rotate(360deg); }
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.loading {
|
|
469
|
+
animation: spin 1s linear infinite;
|
|
470
|
+
display: inline-block;
|
|
471
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orquesta-agent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Local agent for Orquesta - connects your VM to the Orquesta dashboard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"orquesta-agent": "./dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"build": "tsc",
|
|
11
|
+
"build": "tsc && cp -r src/ui/public dist/ui/",
|
|
12
12
|
"dev": "tsc --watch",
|
|
13
13
|
"start": "node dist/index.js",
|
|
14
14
|
"prepublishOnly": "npm run build",
|