@axiomatic-labs/claudeflow 2.13.28 → 2.13.29
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/lib/panel.js +284 -0
- package/package.json +1 -1
package/lib/panel.js
CHANGED
|
@@ -309,6 +309,91 @@ function getRemindersInfo(cwd) {
|
|
|
309
309
|
};
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
// ─── log events (Logs tab) ───────────────────────────────────────
|
|
313
|
+
//
|
|
314
|
+
// Sidecar files live in `.claude/tmp/log-events/<id>.json`. Each is one
|
|
315
|
+
// event (enforcement-silenced or reminder-injected) with metadata and,
|
|
316
|
+
// for reminders, the full content that was injected at fire time. The
|
|
317
|
+
// panel reads them via:
|
|
318
|
+
// GET /api/logs?limit=&type=&before= → metadata-only paged list
|
|
319
|
+
// GET /api/logs/:id → full record incl. content
|
|
320
|
+
function logEventsDir(cwd) {
|
|
321
|
+
return path.join(cwd, '.claude', 'tmp', 'log-events');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function isValidLogId(id) {
|
|
325
|
+
return typeof id === 'string' && /^[A-Za-z0-9_-]+$/.test(id) && id.length <= 64;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function listLogEvents(cwd, { limit = 200, before = null, type = 'all' } = {}) {
|
|
329
|
+
const dir = logEventsDir(cwd);
|
|
330
|
+
let files;
|
|
331
|
+
try {
|
|
332
|
+
files = fs.readdirSync(dir).filter((f) => f.endsWith('.json'));
|
|
333
|
+
} catch {
|
|
334
|
+
return { entries: [], total: 0 };
|
|
335
|
+
}
|
|
336
|
+
files.sort().reverse(); // filenames begin with sortable timestamp; newest first
|
|
337
|
+
const total = files.length;
|
|
338
|
+
|
|
339
|
+
let beforeIdx = 0;
|
|
340
|
+
if (before) {
|
|
341
|
+
const beforeFile = `${before}.json`;
|
|
342
|
+
const idx = files.indexOf(beforeFile);
|
|
343
|
+
if (idx >= 0) beforeIdx = idx + 1;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const entries = [];
|
|
347
|
+
for (let i = beforeIdx; i < files.length && entries.length < limit; i++) {
|
|
348
|
+
let r;
|
|
349
|
+
try { r = JSON.parse(fs.readFileSync(path.join(dir, files[i]), 'utf8')); }
|
|
350
|
+
catch { continue; }
|
|
351
|
+
if (type !== 'all') {
|
|
352
|
+
if (type === 'enforcement' && !r.type.startsWith('enforcement')) continue;
|
|
353
|
+
if (type === 'reminder' && !r.type.startsWith('reminder')) continue;
|
|
354
|
+
}
|
|
355
|
+
// Strip large `content` from list view; clients fetch it via /api/logs/:id
|
|
356
|
+
entries.push({
|
|
357
|
+
id: r.id,
|
|
358
|
+
timestamp: r.timestamp,
|
|
359
|
+
type: r.type,
|
|
360
|
+
event: r.event,
|
|
361
|
+
matcher: r.matcher || '',
|
|
362
|
+
handler: r.handler,
|
|
363
|
+
summary: r.summary,
|
|
364
|
+
contentBytes: r.contentBytes || 0,
|
|
365
|
+
activeReminderIds: r.activeReminderIds || null,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
return { entries, total };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function readLogEvent(cwd, id) {
|
|
372
|
+
if (!isValidLogId(id)) return null;
|
|
373
|
+
try {
|
|
374
|
+
return JSON.parse(fs.readFileSync(path.join(logEventsDir(cwd), `${id}.json`), 'utf8'));
|
|
375
|
+
} catch {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function getLogsInfo(cwd) {
|
|
381
|
+
const dir = logEventsDir(cwd);
|
|
382
|
+
let total = 0;
|
|
383
|
+
let mostRecent = null;
|
|
384
|
+
try {
|
|
385
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.json')).sort();
|
|
386
|
+
total = files.length;
|
|
387
|
+
if (total > 0) {
|
|
388
|
+
try {
|
|
389
|
+
const r = JSON.parse(fs.readFileSync(path.join(dir, files[files.length - 1]), 'utf8'));
|
|
390
|
+
mostRecent = r.timestamp;
|
|
391
|
+
} catch {}
|
|
392
|
+
}
|
|
393
|
+
} catch {}
|
|
394
|
+
return { available: true, total, mostRecent };
|
|
395
|
+
}
|
|
396
|
+
|
|
312
397
|
function getDoctorInfo(cwd) {
|
|
313
398
|
const checks = [checkCdpPortMismatch(cwd), checkStaleLockfiles(cwd)];
|
|
314
399
|
const issues = checks.filter((c) => c.severity !== 'ok' && c.severity !== 'info');
|
|
@@ -331,6 +416,7 @@ function collectStatus(cwd) {
|
|
|
331
416
|
activeRun: getActiveRunInfo(cwd),
|
|
332
417
|
reminders: getRemindersInfo(cwd),
|
|
333
418
|
enforcement: getEnforcementInfo(cwd),
|
|
419
|
+
logs: getLogsInfo(cwd),
|
|
334
420
|
doctor: getDoctorInfo(cwd),
|
|
335
421
|
};
|
|
336
422
|
}
|
|
@@ -441,6 +527,26 @@ details[open] summary { padding-bottom: 8px; border-bottom: 1px solid var(--bord
|
|
|
441
527
|
.reminder-row .reminder-desc { font-size: 12px; margin-left: 22px; margin-top: 2px; }
|
|
442
528
|
.reminder-row .reminder-id { font-size: 11px; margin-left: 22px; margin-top: 2px; }
|
|
443
529
|
.reminder-row code { background: var(--panel-2); padding: 1px 5px; border-radius: 3px; font-size: 11px; }
|
|
530
|
+
.logs-controls { display: flex; gap: 12px; align-items: center; margin-bottom: 14px; }
|
|
531
|
+
.logs-controls select, .logs-controls button { background: var(--panel); border: 1px solid var(--border); color: var(--fg); padding: 6px 10px; border-radius: 6px; font: inherit; }
|
|
532
|
+
.logs-controls button:hover { border-color: var(--accent); cursor: pointer; }
|
|
533
|
+
.logs-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
534
|
+
.logs-table th, .logs-table td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); vertical-align: top; }
|
|
535
|
+
.logs-table th { font-weight: 500; color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
536
|
+
.logs-table tr:hover td { background: rgba(124, 58, 237, 0.05); }
|
|
537
|
+
.logs-time { font-family: var(--mono); font-size: 12px; white-space: nowrap; }
|
|
538
|
+
.logs-handler { font-family: var(--mono); font-size: 12px; }
|
|
539
|
+
.logs-size { font-family: var(--mono); font-size: 12px; color: var(--muted); white-space: nowrap; }
|
|
540
|
+
.logs-view { background: var(--panel-2); border: 1px solid var(--border); color: var(--fg); padding: 4px 12px; border-radius: 5px; font: inherit; font-size: 12px; cursor: pointer; }
|
|
541
|
+
.logs-view:hover { border-color: var(--accent); }
|
|
542
|
+
#log-drawer { display: none; position: fixed; top: 0; right: 0; bottom: 0; width: 60%; max-width: 920px; background: var(--panel); border-left: 1px solid var(--border); box-shadow: -8px 0 24px rgba(0,0,0,0.4); z-index: 100; overflow-y: auto; }
|
|
543
|
+
#log-drawer .drawer-head { display: flex; justify-content: space-between; align-items: center; padding: 14px 22px; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--panel); }
|
|
544
|
+
#log-drawer .drawer-head .title { font-weight: 600; }
|
|
545
|
+
#log-drawer .drawer-head button { background: none; border: 1px solid var(--border); color: var(--fg); padding: 5px 10px; border-radius: 5px; cursor: pointer; }
|
|
546
|
+
#log-drawer .drawer-body { padding: 18px 22px; }
|
|
547
|
+
#log-drawer h3 { margin: 0 0 12px; font-size: 15px; }
|
|
548
|
+
#log-drawer h4 { margin: 0 0 6px; font-size: 13px; color: var(--muted); }
|
|
549
|
+
#log-drawer pre { max-height: none; }
|
|
444
550
|
.toast { position: fixed; bottom: 18px; right: 18px; background: var(--panel); border: 1px solid var(--border); padding: 10px 14px; border-radius: 6px; font-size: 13px; box-shadow: 0 4px 12px rgba(0,0,0,0.4); }
|
|
445
551
|
.toast.err { border-color: var(--err); }
|
|
446
552
|
.toast.ok { border-color: var(--ok); }
|
|
@@ -466,12 +572,20 @@ details[open] summary { padding-bottom: 8px; border-bottom: 1px solid var(--bord
|
|
|
466
572
|
<nav id="nav"></nav>
|
|
467
573
|
<section id="content"><p class="muted">Loading…</p></section>
|
|
468
574
|
</main>
|
|
575
|
+
<aside id="log-drawer">
|
|
576
|
+
<div class="drawer-head">
|
|
577
|
+
<span class="title">Log event details</span>
|
|
578
|
+
<button id="log-drawer-close">Close ✕</button>
|
|
579
|
+
</div>
|
|
580
|
+
<div class="drawer-body" id="log-drawer-body"></div>
|
|
581
|
+
</aside>
|
|
469
582
|
<script>
|
|
470
583
|
const SECTIONS = [
|
|
471
584
|
{ id: 'overview', label: 'Overview' },
|
|
472
585
|
{ id: 'claudeMd', label: 'CLAUDE.md' },
|
|
473
586
|
{ id: 'hooks', label: 'Hooks' },
|
|
474
587
|
{ id: 'reminders', label: 'Reminders' },
|
|
588
|
+
{ id: 'logs', label: 'Logs' },
|
|
475
589
|
{ id: 'mcp', label: 'MCP & observer' },
|
|
476
590
|
{ id: 'setupContext', label: 'Setup context' },
|
|
477
591
|
{ id: 'activeRun', label: 'Active run' },
|
|
@@ -542,6 +656,8 @@ async function refresh() {
|
|
|
542
656
|
restoreOpenDetails(open);
|
|
543
657
|
// If preview was visible/open, ensure it stays loaded after re-render.
|
|
544
658
|
if (open.has('claude-md-preview')) loadClaudeMdPreview();
|
|
659
|
+
// Refresh logs entries silently on the Logs tab so new events appear.
|
|
660
|
+
if (active === 'logs') loadLogsPage();
|
|
545
661
|
}
|
|
546
662
|
|
|
547
663
|
function renderHeader() {
|
|
@@ -571,6 +687,7 @@ function severityFor(id) {
|
|
|
571
687
|
case 'claudeMd': return !s.claudeMd.exists ? 'err' : (s.claudeMd.optOut ? 'info' : 'ok');
|
|
572
688
|
case 'hooks': return s.hooks.warnings > 0 ? 'warn' : 'ok';
|
|
573
689
|
case 'reminders': return s.reminders && s.reminders.disabledCount > 0 ? 'info' : 'ok';
|
|
690
|
+
case 'logs': return s.logs && s.logs.total > 0 ? 'ok' : 'info';
|
|
574
691
|
case 'mcp': return !s.mcp.configFound ? 'info' : (s.mcp.playwright.match && s.mcp.staleLockfiles.length === 0 ? 'ok' : 'warn');
|
|
575
692
|
case 'setupContext': return !s.setupContext.exists ? 'err' : (s.setupContext.toolingComplete ? 'ok' : 'warn');
|
|
576
693
|
case 'activeRun': return s.activeRun.active ? 'info' : 'info';
|
|
@@ -615,6 +732,7 @@ function renderContent() {
|
|
|
615
732
|
claudeMd: renderClaudeMd,
|
|
616
733
|
hooks: renderHooks,
|
|
617
734
|
reminders: renderReminders,
|
|
735
|
+
logs: renderLogs,
|
|
618
736
|
mcp: renderMcp,
|
|
619
737
|
setupContext: renderSetup,
|
|
620
738
|
activeRun: renderRun,
|
|
@@ -642,6 +760,9 @@ function renderOverview() {
|
|
|
642
760
|
const ef = s.enforcement && s.enforcement.available
|
|
643
761
|
? (s.enforcement.on ? \`ON (\${s.enforcement.count} blocking hooks)\` : \`OFF — \${s.enforcement.count} blocking hooks silenced\`)
|
|
644
762
|
: 'unavailable';
|
|
763
|
+
const lg = s.logs && s.logs.available
|
|
764
|
+
? \`\${s.logs.total} captured\${s.logs.mostRecent ? ' · last ' + fmtRelativeTime(s.logs.mostRecent) : ''}\`
|
|
765
|
+
: 'no events';
|
|
645
766
|
return \`<h2>Overview</h2>
|
|
646
767
|
<p class="sub">Snapshot at \${new Date(s.timestamp).toLocaleTimeString()}</p>
|
|
647
768
|
<div class="card">
|
|
@@ -653,6 +774,7 @@ function renderOverview() {
|
|
|
653
774
|
\${row('Active run', ar, { kind: 'info', text: s.activeRun.active ? '·' : '·' })}
|
|
654
775
|
\${row('Reminders', rm, { kind: s.reminders && s.reminders.available ? (s.reminders.disabledCount ? 'info' : 'ok') : 'err', text: s.reminders && s.reminders.disabledCount ? '·' : '✓' })}
|
|
655
776
|
\${row('Enforcement', ef, { kind: s.enforcement && s.enforcement.available ? (s.enforcement.on ? 'ok' : 'err') : 'err', text: s.enforcement && s.enforcement.available ? (s.enforcement.on ? '✓' : '✗') : '·' })}
|
|
777
|
+
\${row('Logs', lg, { kind: s.logs && s.logs.total > 0 ? 'ok' : 'info', text: '·' })}
|
|
656
778
|
\${row('Doctor', dc, { kind: s.doctor.issueCount === 0 ? 'ok' : 'warn', text: s.doctor.issueCount === 0 ? '✓' : '!' })}
|
|
657
779
|
</div>\`;
|
|
658
780
|
}
|
|
@@ -730,6 +852,132 @@ function renderReminders() {
|
|
|
730
852
|
\${sections}\`;
|
|
731
853
|
}
|
|
732
854
|
|
|
855
|
+
// Logs tab — paginated table backed by /api/logs. Lazy-loaded (we don't
|
|
856
|
+
// inline 500 rows into the initial render). The "View" button fetches
|
|
857
|
+
// the full sidecar from /api/logs/:id and renders it in the drawer.
|
|
858
|
+
let logsState = { entries: [], type: 'all', loading: false, allLoaded: false };
|
|
859
|
+
|
|
860
|
+
async function loadLogsPage({ append = false } = {}) {
|
|
861
|
+
const params = new URLSearchParams({ limit: '100', type: logsState.type });
|
|
862
|
+
if (append && logsState.entries.length > 0) {
|
|
863
|
+
params.set('before', logsState.entries[logsState.entries.length - 1].id);
|
|
864
|
+
}
|
|
865
|
+
logsState.loading = true;
|
|
866
|
+
try {
|
|
867
|
+
const r = await fetch('/api/logs?' + params.toString());
|
|
868
|
+
const j = await r.json();
|
|
869
|
+
if (append) logsState.entries = logsState.entries.concat(j.entries);
|
|
870
|
+
else logsState.entries = j.entries;
|
|
871
|
+
logsState.allLoaded = j.entries.length < 100;
|
|
872
|
+
} catch (e) {
|
|
873
|
+
showToast('Logs fetch failed: ' + e.message, 'err');
|
|
874
|
+
} finally {
|
|
875
|
+
logsState.loading = false;
|
|
876
|
+
if (active === 'logs') renderContent();
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
async function viewLogDetail(id) {
|
|
881
|
+
const drawer = document.getElementById('log-drawer');
|
|
882
|
+
const body = document.getElementById('log-drawer-body');
|
|
883
|
+
drawer.style.display = 'block';
|
|
884
|
+
body.innerHTML = '<p class="muted">Loading…</p>';
|
|
885
|
+
try {
|
|
886
|
+
const r = await fetch('/api/logs/' + encodeURIComponent(id));
|
|
887
|
+
if (!r.ok) { body.innerHTML = '<p class="error">Event not found.</p>'; return; }
|
|
888
|
+
const ev = await r.json();
|
|
889
|
+
const meta = [
|
|
890
|
+
['ID', ev.id],
|
|
891
|
+
['Timestamp (UTC)', ev.timestamp],
|
|
892
|
+
['Local time', new Date(ev.timestamp).toLocaleString()],
|
|
893
|
+
['Type', ev.type],
|
|
894
|
+
['Event', ev.event],
|
|
895
|
+
['Matcher', ev.matcher || '(any)'],
|
|
896
|
+
['Handler', ev.handler],
|
|
897
|
+
];
|
|
898
|
+
let html = '<h3>' + escapeHtml(ev.summary || ev.type) + '</h3>';
|
|
899
|
+
html += '<div class="card">' + meta.map(([k, v]) => row(k, v)).join('') + '</div>';
|
|
900
|
+
if (Array.isArray(ev.activeReminderIds)) {
|
|
901
|
+
html += '<h4 style="margin-top:14px">Active reminders at fire time</h4>';
|
|
902
|
+
html += '<p class="sub">' + ev.activeReminderIds.map(escapeHtml).join(', ') + '</p>';
|
|
903
|
+
}
|
|
904
|
+
if (typeof ev.contentBytes === 'number') {
|
|
905
|
+
html += '<h4 style="margin-top:14px">Injected content (' + fmtBytes(ev.contentBytes) + ')</h4>';
|
|
906
|
+
}
|
|
907
|
+
if (typeof ev.content === 'string' && ev.content.length > 0) {
|
|
908
|
+
html += '<pre>' + escapeHtml(ev.content) + '</pre>';
|
|
909
|
+
} else if (ev.type === 'enforcement-silenced') {
|
|
910
|
+
html += '<p class="muted">No content — the hook was silenced before running.</p>';
|
|
911
|
+
} else {
|
|
912
|
+
html += '<p class="muted">No captured content.</p>';
|
|
913
|
+
}
|
|
914
|
+
body.innerHTML = html;
|
|
915
|
+
} catch (e) {
|
|
916
|
+
body.innerHTML = '<p class="error">Error: ' + escapeHtml(e.message) + '</p>';
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
function closeLogDrawer() {
|
|
921
|
+
document.getElementById('log-drawer').style.display = 'none';
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function fmtRelativeTime(iso) {
|
|
925
|
+
const ms = Date.now() - new Date(iso).getTime();
|
|
926
|
+
if (ms < 1000) return 'just now';
|
|
927
|
+
if (ms < 60000) return Math.floor(ms / 1000) + 's ago';
|
|
928
|
+
if (ms < 3600000) return Math.floor(ms / 60000) + 'm ago';
|
|
929
|
+
if (ms < 86400000) return Math.floor(ms / 3600000) + 'h ago';
|
|
930
|
+
return Math.floor(ms / 86400000) + 'd ago';
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function fmtLocalTime(iso) {
|
|
934
|
+
const d = new Date(iso);
|
|
935
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
936
|
+
return \`\${d.getFullYear()}-\${pad(d.getMonth() + 1)}-\${pad(d.getDate())} \${pad(d.getHours())}:\${pad(d.getMinutes())}:\${pad(d.getSeconds())}\`;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function renderLogs() {
|
|
940
|
+
// Trigger initial load (or filter change reload) on first render.
|
|
941
|
+
if (logsState.entries.length === 0 && !logsState.loading) {
|
|
942
|
+
loadLogsPage();
|
|
943
|
+
}
|
|
944
|
+
const totalLabel = state.logs && state.logs.total != null ? \`\${state.logs.total} total events\` : '';
|
|
945
|
+
const rows = logsState.entries.map((e) => {
|
|
946
|
+
const isReminder = e.type.startsWith('reminder');
|
|
947
|
+
const badge = isReminder
|
|
948
|
+
? '<span class="badge info" style="background:rgba(88,166,255,0.18); color:#58a6ff">REMINDER</span>'
|
|
949
|
+
: '<span class="badge err">ENFORCEMENT</span>';
|
|
950
|
+
const sizeStr = e.contentBytes ? fmtBytes(e.contentBytes) : '—';
|
|
951
|
+
return \`<tr>
|
|
952
|
+
<td class="logs-time"><span title="\${escapeHtml(e.timestamp)}">\${escapeHtml(fmtLocalTime(e.timestamp))}</span><br><span class="muted" style="font-size:11px">\${fmtRelativeTime(e.timestamp)}</span></td>
|
|
953
|
+
<td>\${badge}</td>
|
|
954
|
+
<td class="logs-handler"><span class="muted">\${escapeHtml(e.event)}</span><br>\${escapeHtml(e.handler)}\${e.matcher ? \` <span class="muted">(\${escapeHtml(e.matcher)})</span>\` : ''}</td>
|
|
955
|
+
<td class="logs-size">\${sizeStr}</td>
|
|
956
|
+
<td><button class="logs-view" data-log-id="\${escapeHtml(e.id)}">View</button></td>
|
|
957
|
+
</tr>\`;
|
|
958
|
+
}).join('');
|
|
959
|
+
const empty = logsState.entries.length === 0
|
|
960
|
+
? '<p class="muted" style="padding:24px 0">No events captured yet. Open a Claude Code session, send a prompt — reminders + any silenced enforcement hooks will appear here.</p>'
|
|
961
|
+
: '';
|
|
962
|
+
return \`<h2>Logs</h2>
|
|
963
|
+
<p class="sub">Real-time trace of every reminder injection and enforcement-silenced hook. \${totalLabel} · sidecars in <code>.claude/tmp/log-events/</code> (capped at 2000, oldest evicted).</p>
|
|
964
|
+
<div class="logs-controls">
|
|
965
|
+
<label>Filter:
|
|
966
|
+
<select id="logs-filter">
|
|
967
|
+
<option value="all" \${logsState.type === 'all' ? 'selected' : ''}>All</option>
|
|
968
|
+
<option value="reminder" \${logsState.type === 'reminder' ? 'selected' : ''}>Reminders only</option>
|
|
969
|
+
<option value="enforcement" \${logsState.type === 'enforcement' ? 'selected' : ''}>Enforcement-silenced only</option>
|
|
970
|
+
</select>
|
|
971
|
+
</label>
|
|
972
|
+
<button id="logs-reload">Reload</button>
|
|
973
|
+
</div>
|
|
974
|
+
\${empty || \`<table class="logs-table">
|
|
975
|
+
<thead><tr><th>Time</th><th>Type</th><th>Handler</th><th>Size</th><th></th></tr></thead>
|
|
976
|
+
<tbody>\${rows}</tbody>
|
|
977
|
+
</table>\`}
|
|
978
|
+
\${logsState.allLoaded || logsState.entries.length === 0 ? '' : '<div style="margin-top:12px"><button id="logs-load-more">Load older</button></div>'}\`;
|
|
979
|
+
}
|
|
980
|
+
|
|
733
981
|
function renderMcp() {
|
|
734
982
|
const m = state.mcp;
|
|
735
983
|
if (!m.configFound) return '<h2>MCP & observer</h2><p class="muted">.mcp.json not found</p>';
|
|
@@ -831,9 +1079,33 @@ async function toggleReminder(id, disable) {
|
|
|
831
1079
|
await refresh();
|
|
832
1080
|
}
|
|
833
1081
|
|
|
1082
|
+
document.addEventListener('click', (e) => {
|
|
1083
|
+
const t = e.target;
|
|
1084
|
+
if (!t) return;
|
|
1085
|
+
if (t.id === 'log-drawer-close') return closeLogDrawer();
|
|
1086
|
+
if (t.classList && t.classList.contains('logs-view') && t.dataset.logId) {
|
|
1087
|
+
return viewLogDetail(t.dataset.logId);
|
|
1088
|
+
}
|
|
1089
|
+
if (t.id === 'logs-reload') {
|
|
1090
|
+
logsState.entries = [];
|
|
1091
|
+
logsState.allLoaded = false;
|
|
1092
|
+
return loadLogsPage();
|
|
1093
|
+
}
|
|
1094
|
+
if (t.id === 'logs-load-more') {
|
|
1095
|
+
return loadLogsPage({ append: true });
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
|
|
834
1099
|
document.addEventListener('change', (e) => {
|
|
835
1100
|
const t = e.target;
|
|
836
1101
|
if (!t || !t.classList) return;
|
|
1102
|
+
if (t.id === 'logs-filter') {
|
|
1103
|
+
logsState.type = t.value;
|
|
1104
|
+
logsState.entries = [];
|
|
1105
|
+
logsState.allLoaded = false;
|
|
1106
|
+
loadLogsPage();
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
837
1109
|
if (t.classList.contains('hook-toggle')) {
|
|
838
1110
|
const event = t.dataset.event;
|
|
839
1111
|
const matcher = t.dataset.matcher;
|
|
@@ -936,6 +1208,18 @@ function handler(cwd) {
|
|
|
936
1208
|
if (route === '/' || route === '/index.html') return send(200, renderHtml(), 'text/html; charset=utf-8');
|
|
937
1209
|
if (route === '/api/status') return send(200, JSON.stringify(collectStatus(cwd)), 'application/json');
|
|
938
1210
|
if (route === '/api/claude-md') return send(200, readClaudeMdSafe(cwd), 'text/plain; charset=utf-8');
|
|
1211
|
+
if (route === '/api/logs') {
|
|
1212
|
+
const limit = Math.min(parseInt(url.searchParams.get('limit'), 10) || 100, 500);
|
|
1213
|
+
const before = url.searchParams.get('before') || null;
|
|
1214
|
+
const type = url.searchParams.get('type') || 'all';
|
|
1215
|
+
return send(200, JSON.stringify(listLogEvents(cwd, { limit, before, type })), 'application/json');
|
|
1216
|
+
}
|
|
1217
|
+
if (route.startsWith('/api/logs/')) {
|
|
1218
|
+
const id = route.slice('/api/logs/'.length);
|
|
1219
|
+
const ev = readLogEvent(cwd, id);
|
|
1220
|
+
if (!ev) return send(404, JSON.stringify({ error: 'not found' }), 'application/json');
|
|
1221
|
+
return send(200, JSON.stringify(ev), 'application/json');
|
|
1222
|
+
}
|
|
939
1223
|
if (route === '/healthz') return send(200, 'ok', 'text/plain');
|
|
940
1224
|
}
|
|
941
1225
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.29",
|
|
4
4
|
"description": "Claudeflow — AI-powered development toolkit for Claude Code. Skills, agents, hooks, and quality gates that ship production apps.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claudeflow": "./bin/cli.js"
|