@axiomatic-labs/claudeflow 2.13.33 → 2.13.35
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 +97 -6
- package/package.json +1 -1
package/lib/panel.js
CHANGED
|
@@ -385,6 +385,7 @@ function listLogEvents(cwd, { limit = 200, before = null, type = 'all' } = {}) {
|
|
|
385
385
|
if (type !== 'all') {
|
|
386
386
|
if (type === 'enforcement' && !r.type.startsWith('enforcement')) continue;
|
|
387
387
|
if (type === 'reminder' && !r.type.startsWith('reminder')) continue;
|
|
388
|
+
if (type === 'blocked' && r.type !== 'enforcement-blocked') continue;
|
|
388
389
|
}
|
|
389
390
|
// Strip large `content` from list view; clients fetch it via /api/logs/:id
|
|
390
391
|
// Old sidecars (pre-token-estimate) lack `contentTokens`. Backfill on
|
|
@@ -408,6 +409,8 @@ function listLogEvents(cwd, { limit = 200, before = null, type = 'all' } = {}) {
|
|
|
408
409
|
capturedFromStdout: r.capturedFromStdout === true,
|
|
409
410
|
stdoutTruncated: r.stdoutTruncated === true,
|
|
410
411
|
parseError: r.parseError || null,
|
|
412
|
+
decision: r.decision || null,
|
|
413
|
+
reason: r.reason || null,
|
|
411
414
|
});
|
|
412
415
|
}
|
|
413
416
|
return { entries, total, thresholds };
|
|
@@ -422,6 +425,29 @@ function readLogEvent(cwd, id) {
|
|
|
422
425
|
}
|
|
423
426
|
}
|
|
424
427
|
|
|
428
|
+
// Destructive: removes every sidecar in .claude/tmp/log-events/ and truncates
|
|
429
|
+
// .claude/tmp/hooks.log. Returns counts so the UI can confirm the wipe.
|
|
430
|
+
function clearLogs(cwd) {
|
|
431
|
+
const dir = logEventsDir(cwd);
|
|
432
|
+
let removed = 0;
|
|
433
|
+
try {
|
|
434
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.json'));
|
|
435
|
+
for (const f of files) {
|
|
436
|
+
try { fs.unlinkSync(path.join(dir, f)); removed++; } catch {}
|
|
437
|
+
}
|
|
438
|
+
} catch {}
|
|
439
|
+
// Truncate the human-readable log too so the user sees a fresh tail -f.
|
|
440
|
+
let logTruncated = false;
|
|
441
|
+
const logPath = path.join(cwd, '.claude', 'tmp', 'hooks.log');
|
|
442
|
+
try {
|
|
443
|
+
if (fs.existsSync(logPath)) {
|
|
444
|
+
fs.writeFileSync(logPath, '');
|
|
445
|
+
logTruncated = true;
|
|
446
|
+
}
|
|
447
|
+
} catch {}
|
|
448
|
+
return { removed, logTruncated };
|
|
449
|
+
}
|
|
450
|
+
|
|
425
451
|
function getLogsInfo(cwd) {
|
|
426
452
|
const dir = logEventsDir(cwd);
|
|
427
453
|
const thresholds = tokenThresholds();
|
|
@@ -429,11 +455,16 @@ function getLogsInfo(cwd) {
|
|
|
429
455
|
let mostRecent = null;
|
|
430
456
|
let latestReminderTokens = null;
|
|
431
457
|
let latestReminderStatus = null;
|
|
458
|
+
let recentBlockedCount = 0;
|
|
432
459
|
try {
|
|
433
460
|
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.json')).sort();
|
|
434
461
|
total = files.length;
|
|
435
|
-
// Walk from newest
|
|
436
|
-
|
|
462
|
+
// Walk from newest. Count blocks within the last 50 events for the
|
|
463
|
+
// sidebar severity. Latest-reminder lookup short-circuits when both
|
|
464
|
+
// mostRecent + latestReminderTokens are populated AND we've scanned
|
|
465
|
+
// enough for the block tally.
|
|
466
|
+
const scanCap = Math.min(50, files.length);
|
|
467
|
+
for (let i = files.length - 1; i >= files.length - scanCap; i--) {
|
|
437
468
|
try {
|
|
438
469
|
const r = JSON.parse(fs.readFileSync(path.join(dir, files[i]), 'utf8'));
|
|
439
470
|
if (mostRecent === null) mostRecent = r.timestamp;
|
|
@@ -444,6 +475,7 @@ function getLogsInfo(cwd) {
|
|
|
444
475
|
latestReminderTokens = tokens;
|
|
445
476
|
latestReminderStatus = classifyTokens(tokens, thresholds);
|
|
446
477
|
}
|
|
478
|
+
if (r.type === 'enforcement-blocked') recentBlockedCount++;
|
|
447
479
|
} catch {}
|
|
448
480
|
}
|
|
449
481
|
} catch {}
|
|
@@ -454,6 +486,7 @@ function getLogsInfo(cwd) {
|
|
|
454
486
|
thresholds,
|
|
455
487
|
latestReminderTokens,
|
|
456
488
|
latestReminderStatus,
|
|
489
|
+
recentBlockedCount,
|
|
457
490
|
};
|
|
458
491
|
}
|
|
459
492
|
|
|
@@ -593,6 +626,10 @@ details[open] summary { padding-bottom: 8px; border-bottom: 1px solid var(--bord
|
|
|
593
626
|
.logs-controls { display: flex; gap: 12px; align-items: center; margin-bottom: 14px; }
|
|
594
627
|
.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; }
|
|
595
628
|
.logs-controls button:hover { border-color: var(--accent); cursor: pointer; }
|
|
629
|
+
.logs-clear-btn { margin-left: auto; color: var(--err); border-color: rgba(248,81,73,0.4); }
|
|
630
|
+
.logs-clear-btn:hover { border-color: var(--err); background: rgba(248,81,73,0.08); }
|
|
631
|
+
.logs-clear-btn:disabled { opacity: 0.4; cursor: not-allowed; color: var(--muted); border-color: var(--border); }
|
|
632
|
+
.logs-clear-btn:disabled:hover { border-color: var(--border); background: var(--panel); }
|
|
596
633
|
.logs-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
597
634
|
.logs-table th, .logs-table td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); vertical-align: top; }
|
|
598
635
|
.logs-table th { font-weight: 500; color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
@@ -611,6 +648,10 @@ details[open] summary { padding-bottom: 8px; border-bottom: 1px solid var(--bord
|
|
|
611
648
|
.logs-status { font-family: var(--mono); font-size: 12px; white-space: nowrap; }
|
|
612
649
|
.logs-status.injected { color: #58a6ff; }
|
|
613
650
|
.logs-status.silenced { color: var(--err); font-weight: 600; }
|
|
651
|
+
.logs-status.blocked { color: var(--err); font-weight: 700; }
|
|
652
|
+
.logs-table tr.blocked .logs-handler { color: var(--err); }
|
|
653
|
+
.logs-table tr.blocked td:first-child { border-left: 3px solid var(--err) !important; }
|
|
654
|
+
.logs-reason-preview { font-size: 11px; color: var(--err); margin-top: 2px; max-width: 480px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
614
655
|
.token-badge { display: inline-block; padding: 1px 7px; border-radius: 999px; font-size: 10px; font-family: var(--mono); margin-left: 6px; vertical-align: middle; }
|
|
615
656
|
.token-badge.heavy { background: rgba(210,153,34,0.18); color: var(--warn); }
|
|
616
657
|
.token-badge.high { background: rgba(248,81,73,0.20); color: var(--err); }
|
|
@@ -765,6 +806,7 @@ function severityFor(id) {
|
|
|
765
806
|
case 'reminders': return s.reminders && s.reminders.disabledCount > 0 ? 'info' : 'ok';
|
|
766
807
|
case 'logs': {
|
|
767
808
|
if (!s.logs) return 'info';
|
|
809
|
+
if (s.logs.recentBlockedCount > 0) return 'err';
|
|
768
810
|
if (s.logs.latestReminderStatus === 'high') return 'err';
|
|
769
811
|
if (s.logs.latestReminderStatus === 'heavy') return 'warn';
|
|
770
812
|
return s.logs.total > 0 ? 'ok' : 'info';
|
|
@@ -1001,6 +1043,17 @@ async function viewLogDetail(id) {
|
|
|
1001
1043
|
: '<span class="badge warn" title="No content was captured — the hook may have crashed before writing or returned null.">⚠ no stdout</span>');
|
|
1002
1044
|
html += '<h4 style="margin-top:14px">Provenance ' + provBadge + (ev.stdoutTruncated ? ' <span class="badge warn">⚠ truncated</span>' : '') + '</h4>';
|
|
1003
1045
|
}
|
|
1046
|
+
// Enforcement-blocked detail: show decision + full reason verbatim.
|
|
1047
|
+
if (ev.type === 'enforcement-blocked') {
|
|
1048
|
+
html += '<h4 style="margin-top:14px">Decision: <span class="badge err">' + escapeHtml(ev.decision || 'block').toUpperCase() + '</span></h4>';
|
|
1049
|
+
if (ev.reason) {
|
|
1050
|
+
html += '<h4 style="margin-top:14px">Reason</h4>';
|
|
1051
|
+
html += '<pre style="border-color: rgba(248,81,73,0.5)">' + escapeHtml(ev.reason) + '</pre>';
|
|
1052
|
+
}
|
|
1053
|
+
if (ev.capturedStdout) {
|
|
1054
|
+
html += '<details style="margin-top:14px"><summary>Raw stdout (' + fmtBytes(ev.stdoutBytes || 0) + ')</summary><pre>' + escapeHtml(ev.capturedStdout) + '</pre></details>';
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1004
1057
|
if (typeof ev.contentBytes === 'number' && ev.contentBytes > 0) {
|
|
1005
1058
|
const tokens = typeof ev.contentTokens === 'number' ? ev.contentTokens : Math.round(ev.contentBytes / 4);
|
|
1006
1059
|
const th = (state.logs && state.logs.thresholds) || { heavy: 8000, high: 20000 };
|
|
@@ -1065,11 +1118,14 @@ function renderLogs() {
|
|
|
1065
1118
|
const rows = logsState.entries.map((e) => {
|
|
1066
1119
|
const isReminder = e.type.startsWith('reminder');
|
|
1067
1120
|
const isSilenced = e.type === 'enforcement-silenced';
|
|
1121
|
+
const isBlocked = e.type === 'enforcement-blocked';
|
|
1068
1122
|
const badge = isReminder
|
|
1069
1123
|
? '<span class="badge info" style="background:rgba(88,166,255,0.18); color:#58a6ff">REMINDER</span>'
|
|
1070
1124
|
: '<span class="badge err">ENFORCEMENT</span>';
|
|
1071
1125
|
const statusCell = isSilenced
|
|
1072
1126
|
? '<span class="logs-status silenced" title="Master Enforcement OFF silenced this hook — it did NOT run. Claude Code received no output for this event.">✗ SILENCED</span>'
|
|
1127
|
+
: isBlocked
|
|
1128
|
+
? '<span class="logs-status blocked" title="Hook ran and BLOCKED this action. Claude Code rejected the tool call. Click View for the full reason.">⛔ BLOCKED</span>'
|
|
1073
1129
|
: '<span class="logs-status injected" title="Hook ran and emitted additionalContext to Claude Code">✓ injected</span>';
|
|
1074
1130
|
const tokenStatus = e.tokenStatus || 'none';
|
|
1075
1131
|
const tokenBadge = tokenStatus === 'high'
|
|
@@ -1083,13 +1139,18 @@ function renderLogs() {
|
|
|
1083
1139
|
const bytesStr = e.contentBytes ? fmtBytes(e.contentBytes) : '';
|
|
1084
1140
|
const tokensTitle = e.contentBytes
|
|
1085
1141
|
? \`≈\${e.contentTokens} tokens (chars/4 heuristic) · \${fmtBytes(e.contentBytes)} raw · status: \${tokenStatus}\`
|
|
1086
|
-
:
|
|
1087
|
-
|
|
1142
|
+
: isBlocked
|
|
1143
|
+
? 'Hook BLOCKED the action. Click View for full reason.'
|
|
1144
|
+
: 'No content (hook was silenced — never ran)';
|
|
1145
|
+
const rowCls = \`token-\${tokenStatus}\${isSilenced ? ' silenced' : ''}\${isBlocked ? ' blocked' : ''}\`;
|
|
1146
|
+
const reasonPreview = isBlocked && e.reason
|
|
1147
|
+
? \`<div class="logs-reason-preview" title="\${escapeHtml(e.reason)}">\${escapeHtml(e.reason.split('\\n')[0].slice(0, 120))}</div>\`
|
|
1148
|
+
: '';
|
|
1088
1149
|
return \`<tr class="\${rowCls}">
|
|
1089
1150
|
<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>
|
|
1090
1151
|
<td>\${badge}</td>
|
|
1091
1152
|
<td>\${statusCell}</td>
|
|
1092
|
-
<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>
|
|
1153
|
+
<td class="logs-handler"><span class="muted">\${escapeHtml(e.event)}</span><br>\${escapeHtml(e.handler)}\${e.matcher ? \` <span class="muted">(\${escapeHtml(e.matcher)})</span>\` : ''}\${reasonPreview}</td>
|
|
1093
1154
|
<td class="logs-size" title="\${escapeHtml(tokensTitle)}">\${tokensStr}\${tokenBadge}\${bytesStr ? \`<br><span class="muted" style="font-size:11px">\${bytesStr}</span>\` : ''}</td>
|
|
1094
1155
|
<td><button class="logs-view" data-log-id="\${escapeHtml(e.id)}">View</button></td>
|
|
1095
1156
|
</tr>\`;
|
|
@@ -1109,10 +1170,12 @@ function renderLogs() {
|
|
|
1109
1170
|
<select id="logs-filter">
|
|
1110
1171
|
<option value="all" \${logsState.type === 'all' ? 'selected' : ''}>All</option>
|
|
1111
1172
|
<option value="reminder" \${logsState.type === 'reminder' ? 'selected' : ''}>Reminders only</option>
|
|
1112
|
-
<option value="enforcement" \${logsState.type === 'enforcement' ? 'selected' : ''}>Enforcement
|
|
1173
|
+
<option value="enforcement" \${logsState.type === 'enforcement' ? 'selected' : ''}>Enforcement (silenced + blocked)</option>
|
|
1174
|
+
<option value="blocked" \${logsState.type === 'blocked' ? 'selected' : ''}>Blocked actions only</option>
|
|
1113
1175
|
</select>
|
|
1114
1176
|
</label>
|
|
1115
1177
|
<button id="logs-reload">Reload</button>
|
|
1178
|
+
<button id="logs-clear" class="logs-clear-btn" \${state.logs && state.logs.total ? '' : 'disabled'} title="Delete every sidecar in .claude/tmp/log-events/ and truncate .claude/tmp/hooks.log. Cannot be undone.">Clear all</button>
|
|
1116
1179
|
</div>
|
|
1117
1180
|
\${empty || \`<table class="logs-table">
|
|
1118
1181
|
<thead><tr><th>Time</th><th>Type</th><th title="Did the hook actually run? ✓ injected = hook ran and emitted to Claude Code · ✗ SILENCED = master Enforcement OFF blocked the hook from running.">Status</th><th>Handler</th><th title="Approximate token count (chars/4 heuristic, ±10–15% vs Claude's real tokenizer). Heavy ≥\${thresholds.heavy.toLocaleString()} → yellow border. High ≥\${thresholds.high.toLocaleString()} → red border. Anthropic does not publish official thresholds for per-turn additionalContext.">≈ Tokens</th><th></th></tr></thead>
|
|
@@ -1237,8 +1300,31 @@ document.addEventListener('click', (e) => {
|
|
|
1237
1300
|
if (t.id === 'logs-load-more') {
|
|
1238
1301
|
return loadLogsPage({ append: true });
|
|
1239
1302
|
}
|
|
1303
|
+
if (t.id === 'logs-clear') {
|
|
1304
|
+
const total = (state.logs && state.logs.total) || 0;
|
|
1305
|
+
if (!total) return;
|
|
1306
|
+
if (!confirm('Delete ' + total + ' captured log event(s) and truncate .claude/tmp/hooks.log?\\n\\nThis cannot be undone. (Future events will be captured normally.)')) return;
|
|
1307
|
+
return clearLogsAction();
|
|
1308
|
+
}
|
|
1240
1309
|
});
|
|
1241
1310
|
|
|
1311
|
+
async function clearLogsAction() {
|
|
1312
|
+
try {
|
|
1313
|
+
const r = await fetch('/api/logs', { method: 'DELETE' });
|
|
1314
|
+
const result = await r.json();
|
|
1315
|
+
if (!r.ok) {
|
|
1316
|
+
showToast('Clear failed: ' + (result.error || r.status), 'err');
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
showToast('Cleared ' + result.removed + ' event(s)' + (result.logTruncated ? ' + truncated hooks.log' : ''), 'ok');
|
|
1320
|
+
logsState.entries = [];
|
|
1321
|
+
logsState.allLoaded = false;
|
|
1322
|
+
await refresh();
|
|
1323
|
+
} catch (e) {
|
|
1324
|
+
showToast('Network error: ' + e.message, 'err');
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1242
1328
|
document.addEventListener('change', (e) => {
|
|
1243
1329
|
const t = e.target;
|
|
1244
1330
|
if (!t || !t.classList) return;
|
|
@@ -1380,6 +1466,11 @@ function handler(cwd) {
|
|
|
1380
1466
|
return send(status, JSON.stringify(result), 'application/json');
|
|
1381
1467
|
}
|
|
1382
1468
|
|
|
1469
|
+
if (req.method === 'DELETE' && route === '/api/logs') {
|
|
1470
|
+
const result = clearLogs(cwd);
|
|
1471
|
+
return send(200, JSON.stringify(result), 'application/json');
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1383
1474
|
if (req.method === 'POST' && route === '/api/enforcement/toggle') {
|
|
1384
1475
|
const raw = await readRequestBody(req);
|
|
1385
1476
|
let payload;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.35",
|
|
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"
|