agent-discover 1.3.0 → 1.3.2

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/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.3.2] - 2026-04-12
9
+
10
+ ### Changed
11
+
12
+ - **Time picker redesigned** — replaced native `datetime-local` inputs in the filter bar with a popover dropdown. Default view shows preset time ranges (Last 15 min, 1h, 6h, 24h, 7d, 30d, All time). "Custom range" section at the bottom opens labeled From/To datetime pickers inside the popover. The button label updates to show the selected range (e.g. "Last 1 hour" or "2026-04-12 10:00 – 14:00").
13
+ - **Filter bar layout** — search bar is now full-width on its own row with descriptive placeholder. Server, status, and time picker sit on the second row. Title + Clear All on top row.
14
+ - **Hash-based tab routing** — active tab is persisted in the URL hash (`#servers`, `#browse`, `#logs`). Refreshing the page restores the last-viewed tab.
15
+
16
+ ## [1.3.1] - 2026-04-12
17
+
18
+ ### Added
19
+
20
+ - **Log search** — text input in the Logs tab filter bar. Searches across server name, tool name, args JSON, and response text (case-insensitive substring match).
21
+ - **Log time range filter** — "From" and "To" datetime-local pickers to narrow logs to a specific window.
22
+
23
+ ### Changed
24
+
25
+ - **Log timestamps now include date** — format changed from `HH:MM:SS` to `YYYY-MM-DD HH:MM:SS` so entries from different days are distinguishable.
26
+
8
27
  ## [1.3.0] - 2026-04-12
9
28
 
10
29
  ### Added
@@ -2,7 +2,7 @@
2
2
  "id": "agent-discover",
3
3
  "name": "Discover",
4
4
  "icon": "explore",
5
- "version": "1.3.0",
5
+ "version": "1.3.2",
6
6
  "description": "MCP server registry — browse, install, configure, monitor",
7
7
  "ui": "./dist/ui/app.js",
8
8
  "css": "./dist/ui/styles.css",
package/dist/ui/app.js CHANGED
@@ -22,7 +22,7 @@
22
22
  let openSections = {};
23
23
  let prereqs = null;
24
24
  let logEntries = [];
25
- let logFilter = { server: '', status: '' };
25
+ let logFilter = { server: '', status: '', search: '', from: '', to: '' };
26
26
 
27
27
  // -------------------------------------------------------------------------
28
28
  // WebSocket
@@ -78,26 +78,37 @@
78
78
  // Tab navigation
79
79
  // -------------------------------------------------------------------------
80
80
 
81
+ function switchTab(tab) {
82
+ currentTab = tab;
83
+ var navItems = AD._root.querySelectorAll('.nav-item');
84
+ navItems.forEach(function (n) {
85
+ n.classList.remove('active');
86
+ if (n.dataset.tab === tab) n.classList.add('active');
87
+ });
88
+ AD._root.querySelectorAll('.tab-panel').forEach(function (p) {
89
+ p.classList.remove('active');
90
+ });
91
+ AD._root.getElementById('tab-' + tab).classList.add('active');
92
+ if (tab === 'logs') renderLogs();
93
+ try {
94
+ history.replaceState(null, '', '#' + tab);
95
+ } catch (e) {
96
+ /* ignore */
97
+ }
98
+ }
99
+
81
100
  function initTabs() {
82
101
  var navItems = AD._root.querySelectorAll('.nav-item');
83
102
  navItems.forEach(function (item) {
84
103
  item.addEventListener('click', function () {
85
- var tab = this.dataset.tab;
86
- currentTab = tab;
87
-
88
- navItems.forEach(function (n) {
89
- n.classList.remove('active');
90
- });
91
- this.classList.add('active');
92
-
93
- AD._root.querySelectorAll('.tab-panel').forEach(function (p) {
94
- p.classList.remove('active');
95
- });
96
- AD._root.getElementById('tab-' + tab).classList.add('active');
97
-
98
- if (tab === 'logs') renderLogs();
104
+ switchTab(this.dataset.tab);
99
105
  });
100
106
  });
107
+
108
+ var hash = location.hash.replace('#', '');
109
+ if (hash && AD._root.getElementById('tab-' + hash)) {
110
+ switchTab(hash);
111
+ }
101
112
  }
102
113
 
103
114
  // -------------------------------------------------------------------------
@@ -1211,6 +1222,8 @@
1211
1222
  function initLogFilters() {
1212
1223
  var serverSel = AD._root.getElementById('log-filter-server');
1213
1224
  var statusSel = AD._root.getElementById('log-filter-status');
1225
+ var searchInput = AD._root.getElementById('log-filter-search');
1226
+
1214
1227
  if (serverSel)
1215
1228
  serverSel.addEventListener('change', function () {
1216
1229
  logFilter.server = this.value;
@@ -1221,6 +1234,89 @@
1221
1234
  logFilter.status = this.value;
1222
1235
  renderLogs();
1223
1236
  });
1237
+ if (searchInput)
1238
+ searchInput.addEventListener('input', function () {
1239
+ logFilter.search = this.value;
1240
+ renderLogs();
1241
+ });
1242
+
1243
+ initTimePicker();
1244
+ }
1245
+
1246
+ function initTimePicker() {
1247
+ var btn = AD._root.getElementById('log-time-btn');
1248
+ var dropdown = AD._root.getElementById('log-time-dropdown');
1249
+ var label = AD._root.getElementById('log-time-label');
1250
+ var applyBtn = AD._root.getElementById('log-time-apply');
1251
+ var fromInput = AD._root.getElementById('log-time-from');
1252
+ var toInput = AD._root.getElementById('log-time-to');
1253
+ if (!btn || !dropdown) return;
1254
+
1255
+ btn.addEventListener('click', function (e) {
1256
+ e.stopPropagation();
1257
+ dropdown.classList.toggle('open');
1258
+ });
1259
+
1260
+ document.addEventListener('click', function (e) {
1261
+ if (!dropdown.contains(e.target) && e.target !== btn && !btn.contains(e.target)) {
1262
+ dropdown.classList.remove('open');
1263
+ }
1264
+ });
1265
+
1266
+ var presetBtns = dropdown.querySelectorAll('.log-time-presets button');
1267
+ presetBtns.forEach(function (pb) {
1268
+ pb.addEventListener('click', function () {
1269
+ var minutes = parseInt(this.dataset.minutes, 10);
1270
+ presetBtns.forEach(function (b) {
1271
+ b.classList.remove('active');
1272
+ });
1273
+ this.classList.add('active');
1274
+ if (minutes === 0) {
1275
+ logFilter.from = '';
1276
+ logFilter.to = '';
1277
+ if (label) label.textContent = 'All time';
1278
+ } else {
1279
+ logFilter.from = new Date(Date.now() - minutes * 60000).toISOString();
1280
+ logFilter.to = '';
1281
+ if (label) label.textContent = this.textContent;
1282
+ }
1283
+ if (fromInput) fromInput.value = '';
1284
+ if (toInput) toInput.value = '';
1285
+ dropdown.classList.remove('open');
1286
+ renderLogs();
1287
+ });
1288
+ });
1289
+
1290
+ if (applyBtn) {
1291
+ applyBtn.addEventListener('click', function () {
1292
+ var f = fromInput ? fromInput.value : '';
1293
+ var t = toInput ? toInput.value : '';
1294
+ var fd = f ? new Date(f) : null;
1295
+ var td = t ? new Date(t) : null;
1296
+ logFilter.from = fd ? fd.toISOString() : '';
1297
+ logFilter.to = td ? td.toISOString() : '';
1298
+ presetBtns.forEach(function (b) {
1299
+ b.classList.remove('active');
1300
+ });
1301
+ function fmtShort(d) {
1302
+ return (
1303
+ d.getFullYear() +
1304
+ '-' +
1305
+ String(d.getMonth() + 1).padStart(2, '0') +
1306
+ '-' +
1307
+ String(d.getDate()).padStart(2, '0') +
1308
+ ' ' +
1309
+ String(d.getHours()).padStart(2, '0') +
1310
+ ':' +
1311
+ String(d.getMinutes()).padStart(2, '0')
1312
+ );
1313
+ }
1314
+ var rangeLabel = (fd ? fmtShort(fd) : '...') + ' \u2013 ' + (td ? fmtShort(td) : 'now');
1315
+ if (label) label.textContent = rangeLabel;
1316
+ dropdown.classList.remove('open');
1317
+ renderLogs();
1318
+ });
1319
+ }
1224
1320
  }
1225
1321
 
1226
1322
  function fetchLogs() {
@@ -1242,10 +1338,30 @@
1242
1338
  var el = AD._root.getElementById('logs-list');
1243
1339
  if (!el) return;
1244
1340
 
1341
+ var fromTs = logFilter.from ? new Date(logFilter.from).getTime() : 0;
1342
+ var toTs = logFilter.to ? new Date(logFilter.to).getTime() : Infinity;
1343
+ var q = (logFilter.search || '').toLowerCase();
1344
+
1245
1345
  var filtered = logEntries.filter(function (e) {
1246
1346
  if (logFilter.server && e.server !== logFilter.server) return false;
1247
1347
  if (logFilter.status === 'success' && !e.success) return false;
1248
1348
  if (logFilter.status === 'fail' && e.success) return false;
1349
+ if (e.timestamp) {
1350
+ var t = new Date(e.timestamp).getTime();
1351
+ if (t < fromTs || t > toTs) return false;
1352
+ }
1353
+ if (q) {
1354
+ var haystack = (
1355
+ e.server +
1356
+ ' ' +
1357
+ e.tool +
1358
+ ' ' +
1359
+ JSON.stringify(e.args) +
1360
+ ' ' +
1361
+ (e.response || '')
1362
+ ).toLowerCase();
1363
+ if (haystack.indexOf(q) === -1) return false;
1364
+ }
1249
1365
  return true;
1250
1366
  });
1251
1367
 
@@ -1259,7 +1375,22 @@
1259
1375
  var cols = 5;
1260
1376
  var rows = filtered
1261
1377
  .map(function (e) {
1262
- var ts = e.timestamp ? new Date(e.timestamp).toLocaleTimeString() : '';
1378
+ var ts = '';
1379
+ if (e.timestamp) {
1380
+ var d = new Date(e.timestamp);
1381
+ ts =
1382
+ d.getFullYear() +
1383
+ '-' +
1384
+ String(d.getMonth() + 1).padStart(2, '0') +
1385
+ '-' +
1386
+ String(d.getDate()).padStart(2, '0') +
1387
+ ' ' +
1388
+ String(d.getHours()).padStart(2, '0') +
1389
+ ':' +
1390
+ String(d.getMinutes()).padStart(2, '0') +
1391
+ ':' +
1392
+ String(d.getSeconds()).padStart(2, '0');
1393
+ }
1263
1394
  var badge = e.success
1264
1395
  ? '<span class="log-badge log-success">OK</span>'
1265
1396
  : '<span class="log-badge log-fail">FAIL</span>';
@@ -154,8 +154,23 @@
154
154
  </section>
155
155
 
156
156
  <section class="tab-panel" id="tab-logs">
157
- <div class="panel-header">
158
- <h2 class="section-title">Call Logs</h2>
157
+ <div class="log-header">
158
+ <div class="log-header-top">
159
+ <h2 class="section-title">Call Logs</h2>
160
+ <button class="btn-clear-logs" data-action="clear-logs">
161
+ <span class="material-symbols-outlined" style="font-size: 14px">delete_sweep</span>
162
+ Clear All
163
+ </button>
164
+ </div>
165
+ <div class="log-filter-search-wrap">
166
+ <span class="material-symbols-outlined" style="font-size: 16px">search</span>
167
+ <input
168
+ type="text"
169
+ id="log-filter-search"
170
+ placeholder="Search server, tool, args, response..."
171
+ autocomplete="off"
172
+ />
173
+ </div>
159
174
  <div class="log-filters">
160
175
  <select id="log-filter-server">
161
176
  <option value="">All servers</option>
@@ -165,10 +180,38 @@
165
180
  <option value="success">Success</option>
166
181
  <option value="fail">Failed</option>
167
182
  </select>
168
- <button class="btn-clear-logs" data-action="clear-logs">
169
- <span class="material-symbols-outlined" style="font-size: 14px">delete_sweep</span>
170
- Clear All
171
- </button>
183
+ <div class="log-time-picker" id="log-time-picker">
184
+ <button class="log-time-btn" id="log-time-btn">
185
+ <span class="material-symbols-outlined" style="font-size: 14px">schedule</span>
186
+ <span id="log-time-label">All time</span>
187
+ <span class="material-symbols-outlined" style="font-size: 14px">expand_more</span>
188
+ </button>
189
+ <div class="log-time-dropdown" id="log-time-dropdown">
190
+ <div class="log-time-presets">
191
+ <button data-minutes="15">Last 15 min</button>
192
+ <button data-minutes="60">Last 1 hour</button>
193
+ <button data-minutes="360">Last 6 hours</button>
194
+ <button data-minutes="1440">Last 24 hours</button>
195
+ <button data-minutes="10080">Last 7 days</button>
196
+ <button data-minutes="43200">Last 30 days</button>
197
+ <button data-minutes="0" class="active">All time</button>
198
+ </div>
199
+ <div class="log-time-custom">
200
+ <div class="log-time-custom-label">Custom range</div>
201
+ <div class="log-time-custom-fields">
202
+ <div class="log-time-field">
203
+ <label>From</label>
204
+ <input type="datetime-local" id="log-time-from" />
205
+ </div>
206
+ <div class="log-time-field">
207
+ <label>To</label>
208
+ <input type="datetime-local" id="log-time-to" />
209
+ </div>
210
+ <button class="log-time-apply" id="log-time-apply">Apply</button>
211
+ </div>
212
+ </div>
213
+ </div>
214
+ </div>
172
215
  </div>
173
216
  </div>
174
217
  <div id="logs-list" class="logs-table-wrap">
@@ -1111,6 +1111,48 @@ body {
1111
1111
  Logs tab
1112
1112
  --------------------------------------------------------------------------- */
1113
1113
 
1114
+ .log-header {
1115
+ padding: 16px 24px 12px;
1116
+ }
1117
+
1118
+ .log-header-top {
1119
+ display: flex;
1120
+ align-items: center;
1121
+ justify-content: space-between;
1122
+ margin-bottom: 10px;
1123
+ }
1124
+
1125
+ .log-filter-search-wrap {
1126
+ display: flex;
1127
+ align-items: center;
1128
+ gap: 6px;
1129
+ background: var(--bg-elevated);
1130
+ border: 1px solid var(--border);
1131
+ border-radius: 6px;
1132
+ padding: 0 10px;
1133
+ height: 36px;
1134
+ margin-bottom: 8px;
1135
+ }
1136
+
1137
+ .log-filter-search-wrap .material-symbols-outlined {
1138
+ color: var(--text-dim);
1139
+ flex-shrink: 0;
1140
+ }
1141
+
1142
+ .log-filter-search-wrap input {
1143
+ background: none;
1144
+ border: none;
1145
+ outline: none;
1146
+ font-size: 13px;
1147
+ font-family: var(--font-mono);
1148
+ color: var(--text);
1149
+ width: 100%;
1150
+ }
1151
+
1152
+ .log-filter-search-wrap input::placeholder {
1153
+ color: var(--text-dim);
1154
+ }
1155
+
1114
1156
  .log-filters {
1115
1157
  display: flex;
1116
1158
  gap: 8px;
@@ -1121,12 +1163,157 @@ body {
1121
1163
  background: var(--bg-elevated);
1122
1164
  border: 1px solid var(--border);
1123
1165
  border-radius: 6px;
1124
- padding: 5px 10px;
1166
+ padding: 6px 10px;
1167
+ font-size: 12px;
1168
+ font-family: var(--font-mono);
1169
+ color: var(--text);
1170
+ height: 32px;
1171
+ }
1172
+
1173
+ .log-time-picker {
1174
+ position: relative;
1175
+ }
1176
+
1177
+ .log-time-btn {
1178
+ display: flex;
1179
+ align-items: center;
1180
+ gap: 6px;
1181
+ background: var(--bg-elevated);
1182
+ border: 1px solid var(--border);
1183
+ border-radius: 6px;
1184
+ padding: 0 10px;
1185
+ height: 32px;
1186
+ font-size: 12px;
1187
+ font-family: var(--font-mono);
1188
+ color: var(--text);
1189
+ cursor: pointer;
1190
+ white-space: nowrap;
1191
+ }
1192
+
1193
+ .log-time-btn:hover {
1194
+ border-color: var(--accent);
1195
+ }
1196
+
1197
+ .log-time-dropdown {
1198
+ display: none;
1199
+ position: absolute;
1200
+ top: 100%;
1201
+ right: 0;
1202
+ margin-top: 4px;
1203
+ background: var(--bg-surface);
1204
+ border: 1px solid var(--border);
1205
+ border-radius: var(--radius);
1206
+ box-shadow: var(--shadow-3);
1207
+ z-index: 100;
1208
+ min-width: 220px;
1209
+ overflow: hidden;
1210
+ }
1211
+
1212
+ .log-time-dropdown.open {
1213
+ display: block;
1214
+ }
1215
+
1216
+ .log-time-presets {
1217
+ display: flex;
1218
+ flex-direction: column;
1219
+ padding: 4px;
1220
+ }
1221
+
1222
+ .log-time-presets button {
1223
+ background: none;
1224
+ border: none;
1225
+ padding: 8px 12px;
1226
+ font-size: 13px;
1227
+ font-family: var(--font-sans);
1228
+ color: var(--text);
1229
+ text-align: left;
1230
+ cursor: pointer;
1231
+ border-radius: 6px;
1232
+ }
1233
+
1234
+ .log-time-presets button:hover {
1235
+ background: var(--bg-hover);
1236
+ }
1237
+
1238
+ .log-time-presets button.active {
1239
+ background: var(--accent-dim);
1240
+ color: var(--accent);
1241
+ font-weight: 500;
1242
+ }
1243
+
1244
+ .log-time-custom {
1245
+ border-top: 1px solid var(--border);
1246
+ padding: 10px 12px;
1247
+ }
1248
+
1249
+ .log-time-custom-label {
1250
+ font-size: 10px;
1251
+ font-weight: 600;
1252
+ text-transform: uppercase;
1253
+ letter-spacing: 0.5px;
1254
+ color: var(--text-dim);
1255
+ margin-bottom: 8px;
1256
+ font-family: var(--font-sans);
1257
+ }
1258
+
1259
+ .log-time-custom-fields {
1260
+ display: flex;
1261
+ flex-direction: column;
1262
+ gap: 6px;
1263
+ }
1264
+
1265
+ .log-time-field {
1266
+ display: flex;
1267
+ align-items: center;
1268
+ gap: 8px;
1269
+ }
1270
+
1271
+ .log-time-field label {
1272
+ font-size: 11px;
1273
+ color: var(--text-muted);
1274
+ width: 34px;
1275
+ flex-shrink: 0;
1276
+ font-family: var(--font-sans);
1277
+ }
1278
+
1279
+ .log-time-field input {
1280
+ flex: 1;
1281
+ background: var(--bg-elevated);
1282
+ border: 1px solid var(--border);
1283
+ border-radius: 4px;
1284
+ padding: 5px 8px;
1125
1285
  font-size: 12px;
1126
1286
  font-family: var(--font-mono);
1127
1287
  color: var(--text);
1128
1288
  }
1129
1289
 
1290
+ .log-time-field input:focus {
1291
+ outline: none;
1292
+ border-color: var(--accent);
1293
+ }
1294
+
1295
+ .log-time-field input::-webkit-calendar-picker-indicator {
1296
+ filter: invert(0.7);
1297
+ cursor: pointer;
1298
+ }
1299
+
1300
+ .log-time-apply {
1301
+ background: var(--accent);
1302
+ color: #fff;
1303
+ border: none;
1304
+ border-radius: 4px;
1305
+ padding: 6px 12px;
1306
+ font-size: 12px;
1307
+ font-family: var(--font-sans);
1308
+ font-weight: 500;
1309
+ cursor: pointer;
1310
+ margin-top: 4px;
1311
+ }
1312
+
1313
+ .log-time-apply:hover {
1314
+ background: var(--accent-hover);
1315
+ }
1316
+
1130
1317
  .logs-table {
1131
1318
  width: 100%;
1132
1319
  border-collapse: collapse;
@@ -100,12 +100,21 @@ AD._template = function () {
100
100
  '</div>' +
101
101
  '</section>' +
102
102
  '<section class="tab-panel" id="tab-logs">' +
103
- '<div class="panel-header">' +
103
+ '<div class="log-header">' +
104
+ '<div class="log-header-top">' +
104
105
  '<h2 class="section-title">Call Logs</h2>' +
106
+ '<button class="btn-clear-logs" data-action="clear-logs"><span class="material-symbols-outlined" style="font-size:14px">delete_sweep</span> Clear All</button>' +
107
+ '</div>' +
108
+ '<div class="log-filter-search-wrap"><span class="material-symbols-outlined" style="font-size:16px">search</span><input type="text" id="log-filter-search" placeholder="Search server, tool, args, response..." autocomplete="off" /></div>' +
105
109
  '<div class="log-filters">' +
106
110
  '<select id="log-filter-server"><option value="">All servers</option></select>' +
107
111
  '<select id="log-filter-status"><option value="">All</option><option value="success">Success</option><option value="fail">Failed</option></select>' +
108
- '<button class="btn-clear-logs" data-action="clear-logs"><span class="material-symbols-outlined" style="font-size:14px">delete_sweep</span> Clear All</button>' +
112
+ '<div class="log-time-picker" id="log-time-picker">' +
113
+ '<button class="log-time-btn" id="log-time-btn"><span class="material-symbols-outlined" style="font-size:14px">schedule</span><span id="log-time-label">All time</span><span class="material-symbols-outlined" style="font-size:14px">expand_more</span></button>' +
114
+ '<div class="log-time-dropdown" id="log-time-dropdown">' +
115
+ '<div class="log-time-presets"><button data-minutes="15">Last 15 min</button><button data-minutes="60">Last 1 hour</button><button data-minutes="360">Last 6 hours</button><button data-minutes="1440">Last 24 hours</button><button data-minutes="10080">Last 7 days</button><button data-minutes="43200">Last 30 days</button><button data-minutes="0" class="active">All time</button></div>' +
116
+ '<div class="log-time-custom"><div class="log-time-custom-label">Custom range</div><div class="log-time-custom-fields"><div class="log-time-field"><label>From</label><input type="datetime-local" id="log-time-from" /></div><div class="log-time-field"><label>To</label><input type="datetime-local" id="log-time-to" /></div><button class="log-time-apply" id="log-time-apply">Apply</button></div></div>' +
117
+ '</div></div>' +
109
118
  '</div>' +
110
119
  '</div>' +
111
120
  '<div id="logs-list" class="logs-table-wrap">' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-discover",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "mcpName": "io.github.keshrath/agent-discover",
5
5
  "description": "MCP server registry and marketplace — discover, install, activate, and manage MCP tools on demand",
6
6
  "type": "module",