agentgate 0.3.2 → 0.5.0
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/README.md +118 -4
- package/package.json +3 -2
- package/public/mobile.css +178 -0
- package/public/style.css +129 -0
- package/src/index.js +304 -51
- package/src/lib/agentNotifier.js +82 -1
- package/src/lib/db.js +868 -9
- package/src/lib/socketManager.js +73 -0
- package/src/routes/agents.js +117 -3
- package/src/routes/linkedin.js +30 -10
- package/src/routes/memento.js +106 -0
- package/src/routes/queue.js +238 -4
- package/src/routes/services.js +87 -0
- package/src/routes/ui/access.js +290 -0
- package/src/routes/ui/auth.js +149 -0
- package/src/routes/ui/calendar.js +65 -14
- package/src/routes/ui/fitbit.js +63 -14
- package/src/routes/ui/home.js +313 -0
- package/src/routes/ui/index.js +52 -35
- package/src/routes/ui/keys.js +852 -0
- package/src/routes/ui/linkedin.js +75 -19
- package/src/routes/ui/mastodon.js +70 -18
- package/src/routes/ui/mementos.js +363 -0
- package/src/routes/ui/messages.js +588 -0
- package/src/routes/ui/queue.js +599 -0
- package/src/routes/ui/reddit.js +63 -14
- package/src/routes/ui/services.js +46 -0
- package/src/routes/ui/settings.js +59 -0
- package/src/routes/ui/shared.js +269 -0
- package/src/routes/ui/youtube.js +63 -14
- package/src/routes/ui-new.js +196 -0
- package/src/routes/webhooks.js +247 -0
- package/src/routes/ui.js +0 -1901
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { listMementos, getMementoById, deleteMemento, getMementoCounts, listApiKeys, getPendingQueueCount, listPendingMessages, getMessagingMode } from '../../lib/db.js';
|
|
3
|
+
import {
|
|
4
|
+
htmlHead,
|
|
5
|
+
simpleNavHeader,
|
|
6
|
+
socketScript,
|
|
7
|
+
localizeScript,
|
|
8
|
+
escapeHtml,
|
|
9
|
+
formatDate,
|
|
10
|
+
renderAvatar
|
|
11
|
+
} from './shared.js';
|
|
12
|
+
|
|
13
|
+
const router = Router();
|
|
14
|
+
|
|
15
|
+
// GET /ui/mementos - Admin mementos list
|
|
16
|
+
router.get('/', (req, res) => {
|
|
17
|
+
const { agent, keyword, limit = '50', offset = '0' } = req.query;
|
|
18
|
+
const parsedLimit = Math.min(parseInt(limit, 10) || 50, 100);
|
|
19
|
+
const parsedOffset = parseInt(offset, 10) || 0;
|
|
20
|
+
|
|
21
|
+
const mementos = listMementos({
|
|
22
|
+
agentId: agent || undefined,
|
|
23
|
+
keyword: keyword || undefined,
|
|
24
|
+
limit: parsedLimit,
|
|
25
|
+
offset: parsedOffset
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Get all agents for filter dropdown
|
|
29
|
+
const agents = listApiKeys().map(k => k.name).sort();
|
|
30
|
+
|
|
31
|
+
// Get stats for dashboard
|
|
32
|
+
const counts = getMementoCounts();
|
|
33
|
+
|
|
34
|
+
// Get nav counts
|
|
35
|
+
const pendingQueueCount = getPendingQueueCount();
|
|
36
|
+
const messagingMode = getMessagingMode();
|
|
37
|
+
const pendingMessagesCount = messagingMode !== 'off' ? listPendingMessages().length : 0;
|
|
38
|
+
|
|
39
|
+
const html = `${htmlHead('Mementos', { includeSocket: true })}
|
|
40
|
+
<body>
|
|
41
|
+
<div class="container">
|
|
42
|
+
${simpleNavHeader({ pendingQueueCount, pendingMessagesCount, messagingMode })}
|
|
43
|
+
|
|
44
|
+
<h2 style="margin-bottom: 16px;">🧠 Agent Mementos</h2>
|
|
45
|
+
|
|
46
|
+
<!-- Stats Bar -->
|
|
47
|
+
<div style="display: flex; gap: 16px; margin-bottom: 20px; flex-wrap: wrap;">
|
|
48
|
+
<div class="stat-card">
|
|
49
|
+
<div class="stat-value">${counts.total || 0}</div>
|
|
50
|
+
<div class="stat-label">Total</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="stat-card">
|
|
53
|
+
<div class="stat-value">${counts.byAgent?.length || agents.length}</div>
|
|
54
|
+
<div class="stat-label">Agents</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="stat-card">
|
|
57
|
+
<div class="stat-value">${counts.last24h || 0}</div>
|
|
58
|
+
<div class="stat-label">Last 24h</div>
|
|
59
|
+
</div>
|
|
60
|
+
<div style="margin-left: auto;">
|
|
61
|
+
<a href="/ui/mementos/export${agent || keyword ? `?agent=${encodeURIComponent(agent || '')}&keyword=${encodeURIComponent(keyword || '')}` : ''}" class="btn btn-secondary" style="display: inline-flex; align-items: center; gap: 6px;">
|
|
62
|
+
📥 Export JSON
|
|
63
|
+
</a>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<!-- Filters -->
|
|
68
|
+
<form method="GET" action="/ui/mementos" style="display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; align-items: center;">
|
|
69
|
+
<select name="agent" style="padding: 8px 12px; background: #1e1e1e; border: 1px solid #333; border-radius: 4px; color: #e0e0e0;">
|
|
70
|
+
<option value="">All Agents</option>
|
|
71
|
+
${agents.map(a => `<option value="${escapeHtml(a)}" ${agent === a ? 'selected' : ''}>${escapeHtml(a)}</option>`).join('')}
|
|
72
|
+
</select>
|
|
73
|
+
<input type="text" name="keyword" placeholder="Filter by keyword..." value="${escapeHtml(keyword || '')}"
|
|
74
|
+
style="padding: 8px 12px; background: #1e1e1e; border: 1px solid #333; border-radius: 4px; color: #e0e0e0; width: 200px;">
|
|
75
|
+
<button type="submit" class="btn btn-primary">Filter</button>
|
|
76
|
+
${agent || keyword ? '<a href="/ui/mementos" class="btn btn-secondary">Clear</a>' : ''}
|
|
77
|
+
</form>
|
|
78
|
+
|
|
79
|
+
<!-- Results -->
|
|
80
|
+
<div class="card">
|
|
81
|
+
${mementos.length === 0 ? `
|
|
82
|
+
<div style="text-align: center; padding: 40px; color: #6b7280;">
|
|
83
|
+
<div style="font-size: 48px; margin-bottom: 16px;">🧠</div>
|
|
84
|
+
<p>No mementos found${agent || keyword ? ' matching your filters' : ''}.</p>
|
|
85
|
+
<p style="font-size: 12px; margin-top: 8px;">Agents can store mementos via POST /api/agents/memento</p>
|
|
86
|
+
</div>
|
|
87
|
+
` : `
|
|
88
|
+
<table style="width: 100%;">
|
|
89
|
+
<thead>
|
|
90
|
+
<tr>
|
|
91
|
+
<th style="width: 40px;">ID</th>
|
|
92
|
+
<th style="width: 120px;">Agent</th>
|
|
93
|
+
<th style="width: 150px;">Keywords</th>
|
|
94
|
+
<th>Preview</th>
|
|
95
|
+
<th style="width: 140px;">Created</th>
|
|
96
|
+
<th style="width: 60px;"></th>
|
|
97
|
+
</tr>
|
|
98
|
+
</thead>
|
|
99
|
+
<tbody>
|
|
100
|
+
${mementos.map(m => `
|
|
101
|
+
<tr>
|
|
102
|
+
<td style="font-family: monospace; color: #6b7280;">${m.id}</td>
|
|
103
|
+
<td>
|
|
104
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
105
|
+
${renderAvatar(m.agent_id, { size: 24 })}
|
|
106
|
+
<span style="font-size: 13px;">${escapeHtml(m.agent_id)}</span>
|
|
107
|
+
</div>
|
|
108
|
+
</td>
|
|
109
|
+
<td>
|
|
110
|
+
<div style="display: flex; flex-wrap: wrap; gap: 4px;">
|
|
111
|
+
${m.keywords.slice(0, 5).map(k => `<span class="tag">${escapeHtml(k)}</span>`).join('')}
|
|
112
|
+
${m.keywords.length > 5 ? `<span class="tag" style="opacity: 0.6;">+${m.keywords.length - 5}</span>` : ''}
|
|
113
|
+
</div>
|
|
114
|
+
</td>
|
|
115
|
+
<td style="font-size: 13px; color: #9ca3af; max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
|
116
|
+
${escapeHtml(m.preview)}
|
|
117
|
+
</td>
|
|
118
|
+
<td style="font-size: 12px;">${formatDate(m.created_at)}</td>
|
|
119
|
+
<td style="white-space: nowrap;">
|
|
120
|
+
<a href="/ui/mementos/${m.id}" class="btn btn-secondary" style="padding: 4px 8px; font-size: 12px;">View</a>
|
|
121
|
+
<button onclick="deleteMemento(${m.id})" class="btn btn-danger" style="padding: 4px 8px; font-size: 12px; margin-left: 4px;">×</button>
|
|
122
|
+
</td>
|
|
123
|
+
</tr>
|
|
124
|
+
`).join('')}
|
|
125
|
+
</tbody>
|
|
126
|
+
</table>
|
|
127
|
+
|
|
128
|
+
<!-- Pagination -->
|
|
129
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 16px; padding-top: 16px; border-top: 1px solid #333;">
|
|
130
|
+
<span style="color: #6b7280; font-size: 13px;">
|
|
131
|
+
Showing ${parsedOffset + 1}-${parsedOffset + mementos.length} mementos
|
|
132
|
+
</span>
|
|
133
|
+
<div style="display: flex; gap: 8px;">
|
|
134
|
+
${parsedOffset > 0 ? `
|
|
135
|
+
<a href="/ui/mementos?${new URLSearchParams({ ...(agent && { agent }), ...(keyword && { keyword }), limit: parsedLimit, offset: Math.max(0, parsedOffset - parsedLimit) })}" class="btn btn-secondary">← Previous</a>
|
|
136
|
+
` : ''}
|
|
137
|
+
${mementos.length === parsedLimit ? `
|
|
138
|
+
<a href="/ui/mementos?${new URLSearchParams({ ...(agent && { agent }), ...(keyword && { keyword }), limit: parsedLimit, offset: parsedOffset + parsedLimit })}" class="btn btn-secondary">Next →</a>
|
|
139
|
+
` : ''}
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
`}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
${socketScript()}
|
|
146
|
+
${localizeScript()}
|
|
147
|
+
<style>
|
|
148
|
+
.tag {
|
|
149
|
+
background: rgba(99, 102, 241, 0.2);
|
|
150
|
+
color: #a5b4fc;
|
|
151
|
+
padding: 2px 6px;
|
|
152
|
+
border-radius: 4px;
|
|
153
|
+
font-size: 11px;
|
|
154
|
+
}
|
|
155
|
+
.stat-card {
|
|
156
|
+
background: rgba(99, 102, 241, 0.1);
|
|
157
|
+
border: 1px solid rgba(99, 102, 241, 0.2);
|
|
158
|
+
border-radius: 8px;
|
|
159
|
+
padding: 12px 20px;
|
|
160
|
+
text-align: center;
|
|
161
|
+
}
|
|
162
|
+
.stat-value {
|
|
163
|
+
font-size: 24px;
|
|
164
|
+
font-weight: 700;
|
|
165
|
+
color: #818cf8;
|
|
166
|
+
}
|
|
167
|
+
.stat-label {
|
|
168
|
+
font-size: 11px;
|
|
169
|
+
color: #9ca3af;
|
|
170
|
+
text-transform: uppercase;
|
|
171
|
+
letter-spacing: 0.05em;
|
|
172
|
+
}
|
|
173
|
+
.btn-danger {
|
|
174
|
+
background: rgba(239, 68, 68, 0.1);
|
|
175
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
176
|
+
color: #f87171;
|
|
177
|
+
}
|
|
178
|
+
.btn-danger:hover {
|
|
179
|
+
background: rgba(239, 68, 68, 0.2);
|
|
180
|
+
}
|
|
181
|
+
</style>
|
|
182
|
+
<script>
|
|
183
|
+
async function deleteMemento(id) {
|
|
184
|
+
if (!confirm('Delete this memento? This cannot be undone.')) return;
|
|
185
|
+
try {
|
|
186
|
+
const res = await fetch('/ui/mementos/' + id + '/delete', {
|
|
187
|
+
method: 'POST',
|
|
188
|
+
headers: { 'Accept': 'application/json' }
|
|
189
|
+
});
|
|
190
|
+
const data = await res.json();
|
|
191
|
+
if (data.success) {
|
|
192
|
+
window.location.reload();
|
|
193
|
+
} else {
|
|
194
|
+
alert(data.error || 'Failed to delete');
|
|
195
|
+
}
|
|
196
|
+
} catch (err) {
|
|
197
|
+
alert('Error: ' + err.message);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
</script>
|
|
201
|
+
</body>
|
|
202
|
+
</html>`;
|
|
203
|
+
|
|
204
|
+
res.send(html);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// GET /ui/mementos/export - Export mementos as JSON
|
|
208
|
+
router.get('/export', (req, res) => {
|
|
209
|
+
const { agent, keyword } = req.query;
|
|
210
|
+
const mementos = listMementos({
|
|
211
|
+
agentId: agent || undefined,
|
|
212
|
+
keyword: keyword || undefined,
|
|
213
|
+
limit: 10000
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
res.setHeader('Content-Type', 'application/json');
|
|
217
|
+
res.setHeader('Content-Disposition', 'attachment; filename="mementos-export.json"');
|
|
218
|
+
res.json(mementos);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// POST /ui/mementos/:id/delete - Delete a memento
|
|
222
|
+
router.post('/:id/delete', (req, res) => {
|
|
223
|
+
const { id } = req.params;
|
|
224
|
+
const wantsJson = req.headers.accept?.includes('application/json');
|
|
225
|
+
|
|
226
|
+
const memento = getMementoById(parseInt(id, 10));
|
|
227
|
+
if (!memento) {
|
|
228
|
+
return wantsJson
|
|
229
|
+
? res.status(404).json({ error: 'Memento not found' })
|
|
230
|
+
: res.status(404).send('Memento not found');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
deleteMemento(parseInt(id, 10));
|
|
234
|
+
|
|
235
|
+
if (wantsJson) {
|
|
236
|
+
return res.json({ success: true });
|
|
237
|
+
}
|
|
238
|
+
res.redirect('/ui/mementos');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// GET /ui/mementos/:id - View single memento
|
|
242
|
+
router.get('/:id', (req, res) => {
|
|
243
|
+
const { id } = req.params;
|
|
244
|
+
const memento = getMementoById(parseInt(id, 10));
|
|
245
|
+
|
|
246
|
+
if (!memento) {
|
|
247
|
+
return res.status(404).send(`${htmlHead('Memento Not Found')}
|
|
248
|
+
<body>
|
|
249
|
+
<div class="container">
|
|
250
|
+
<h1>Memento Not Found</h1>
|
|
251
|
+
<p>No memento with ID ${escapeHtml(id)} exists.</p>
|
|
252
|
+
<a href="/ui/mementos" class="btn btn-primary">← Back to Mementos</a>
|
|
253
|
+
</div>
|
|
254
|
+
</body>
|
|
255
|
+
</html>`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Get nav counts
|
|
259
|
+
const pendingQueueCount = getPendingQueueCount();
|
|
260
|
+
const messagingMode = getMessagingMode();
|
|
261
|
+
const pendingMessagesCount = messagingMode !== 'off' ? listPendingMessages().length : 0;
|
|
262
|
+
|
|
263
|
+
const html = `${htmlHead(`Memento #${memento.id}`, { includeSocket: true })}
|
|
264
|
+
<body>
|
|
265
|
+
<div class="container">
|
|
266
|
+
${simpleNavHeader({ pendingQueueCount, pendingMessagesCount, messagingMode })}
|
|
267
|
+
|
|
268
|
+
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px;">
|
|
269
|
+
<a href="/ui/mementos" class="btn btn-secondary">← Back</a>
|
|
270
|
+
<h2 style="margin: 0; flex: 1;">Memento #${memento.id}</h2>
|
|
271
|
+
<button onclick="deleteMemento(${memento.id})" class="btn btn-danger">Delete</button>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
<div class="card" style="margin-bottom: 16px;">
|
|
275
|
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 16px;">
|
|
276
|
+
<div>
|
|
277
|
+
<div style="color: #6b7280; font-size: 12px; margin-bottom: 4px;">Agent</div>
|
|
278
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
279
|
+
${renderAvatar(memento.agent_id, { size: 28 })}
|
|
280
|
+
<span>${escapeHtml(memento.agent_id)}</span>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
<div>
|
|
284
|
+
<div style="color: #6b7280; font-size: 12px; margin-bottom: 4px;">Created</div>
|
|
285
|
+
<div>${formatDate(memento.created_at)}</div>
|
|
286
|
+
</div>
|
|
287
|
+
${memento.model ? `
|
|
288
|
+
<div>
|
|
289
|
+
<div style="color: #6b7280; font-size: 12px; margin-bottom: 4px;">Model</div>
|
|
290
|
+
<div style="font-family: monospace; font-size: 13px;">${escapeHtml(memento.model)}</div>
|
|
291
|
+
</div>
|
|
292
|
+
` : ''}
|
|
293
|
+
${memento.role ? `
|
|
294
|
+
<div>
|
|
295
|
+
<div style="color: #6b7280; font-size: 12px; margin-bottom: 4px;">Role</div>
|
|
296
|
+
<div>${escapeHtml(memento.role)}</div>
|
|
297
|
+
</div>
|
|
298
|
+
` : ''}
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
<div style="margin-bottom: 16px;">
|
|
302
|
+
<div style="color: #6b7280; font-size: 12px; margin-bottom: 8px;">Keywords</div>
|
|
303
|
+
<div style="display: flex; flex-wrap: wrap; gap: 6px;">
|
|
304
|
+
${memento.keywords.map(k => `
|
|
305
|
+
<a href="/ui/mementos?keyword=${encodeURIComponent(k)}" class="tag" style="text-decoration: none;">${escapeHtml(k)}</a>
|
|
306
|
+
`).join('')}
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<div>
|
|
311
|
+
<div style="color: #6b7280; font-size: 12px; margin-bottom: 8px;">Content</div>
|
|
312
|
+
<pre style="background: #0d0d0d; padding: 16px; border-radius: 8px; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word; font-size: 13px; line-height: 1.5;">${escapeHtml(memento.content)}</pre>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
${socketScript()}
|
|
317
|
+
${localizeScript()}
|
|
318
|
+
<style>
|
|
319
|
+
.tag {
|
|
320
|
+
background: rgba(99, 102, 241, 0.2);
|
|
321
|
+
color: #a5b4fc;
|
|
322
|
+
padding: 4px 10px;
|
|
323
|
+
border-radius: 4px;
|
|
324
|
+
font-size: 12px;
|
|
325
|
+
}
|
|
326
|
+
.tag:hover {
|
|
327
|
+
background: rgba(99, 102, 241, 0.3);
|
|
328
|
+
}
|
|
329
|
+
.btn-danger {
|
|
330
|
+
background: rgba(239, 68, 68, 0.1);
|
|
331
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
332
|
+
color: #f87171;
|
|
333
|
+
}
|
|
334
|
+
.btn-danger:hover {
|
|
335
|
+
background: rgba(239, 68, 68, 0.2);
|
|
336
|
+
}
|
|
337
|
+
</style>
|
|
338
|
+
<script>
|
|
339
|
+
async function deleteMemento(id) {
|
|
340
|
+
if (!confirm('Delete this memento? This cannot be undone.')) return;
|
|
341
|
+
try {
|
|
342
|
+
const res = await fetch('/ui/mementos/' + id + '/delete', {
|
|
343
|
+
method: 'POST',
|
|
344
|
+
headers: { 'Accept': 'application/json' }
|
|
345
|
+
});
|
|
346
|
+
const data = await res.json();
|
|
347
|
+
if (data.success) {
|
|
348
|
+
window.location.href = '/ui/mementos';
|
|
349
|
+
} else {
|
|
350
|
+
alert(data.error || 'Failed to delete');
|
|
351
|
+
}
|
|
352
|
+
} catch (err) {
|
|
353
|
+
alert('Error: ' + err.message);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
</script>
|
|
357
|
+
</body>
|
|
358
|
+
</html>`;
|
|
359
|
+
|
|
360
|
+
res.send(html);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
export default router;
|