@axiomatic-labs/claudeflow 2.13.62 → 2.13.64
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 +254 -3
- package/package.json +1 -1
package/lib/panel.js
CHANGED
|
@@ -285,6 +285,76 @@ function getEnforcementInfo(cwd) {
|
|
|
285
285
|
};
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
function getObserverInfo(cwd) {
|
|
289
|
+
// Read both the canonical state file (full snapshot) AND the summary file
|
|
290
|
+
// (which is what hooks consume — has the incident filter applied).
|
|
291
|
+
const tmpDir = path.join(cwd, '.claudeflow', 'tmp');
|
|
292
|
+
const statePath = path.join(tmpDir, 'error-observer-state.json');
|
|
293
|
+
const summaryPath = path.join(tmpDir, 'error-observer-summary.json');
|
|
294
|
+
let state;
|
|
295
|
+
let summary;
|
|
296
|
+
try { state = JSON.parse(fs.readFileSync(statePath, 'utf8')); } catch { state = null; }
|
|
297
|
+
try { summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8')); } catch { summary = null; }
|
|
298
|
+
|
|
299
|
+
// Daemon liveness reuses doctor.js's helper (same source of truth).
|
|
300
|
+
const daemon = readObserverState(cwd);
|
|
301
|
+
|
|
302
|
+
// Servers table — flatten into UI-friendly rows.
|
|
303
|
+
const servers = state && state.servers && typeof state.servers === 'object'
|
|
304
|
+
? Object.entries(state.servers).map(([label, srv]) => ({
|
|
305
|
+
label,
|
|
306
|
+
port: srv.port || null,
|
|
307
|
+
status: srv.status || 'unknown',
|
|
308
|
+
supervised: !!srv.supervised,
|
|
309
|
+
observed_external: !!srv.observed_external,
|
|
310
|
+
health_ok: srv.health_ok !== false,
|
|
311
|
+
last_ready_at: srv.last_ready_at || null,
|
|
312
|
+
last_issue_at: srv.last_issue_at || null,
|
|
313
|
+
last_exit_at: srv.last_exit_at || null,
|
|
314
|
+
last_exit_code: srv.last_exit_code,
|
|
315
|
+
last_exit_signal: srv.last_exit_signal,
|
|
316
|
+
}))
|
|
317
|
+
: [];
|
|
318
|
+
|
|
319
|
+
// Browser section.
|
|
320
|
+
const browser = state && state.browser ? {
|
|
321
|
+
last_snapshot_at: state.browser.last_snapshot_at || null,
|
|
322
|
+
latest_url: state.browser.latest_url || null,
|
|
323
|
+
latest_route: state.browser.latest_route || null,
|
|
324
|
+
latest_origin: state.browser.latest_origin || null,
|
|
325
|
+
snapshots_received: state.browser.snapshots_received || 0,
|
|
326
|
+
} : null;
|
|
327
|
+
|
|
328
|
+
// Unresolved incidents — already filtered in summary, but expose state's
|
|
329
|
+
// full list too so the UI can show recent (incl. resolved) history.
|
|
330
|
+
const unresolvedIncidents = summary && Array.isArray(summary.unresolved_incidents)
|
|
331
|
+
? summary.unresolved_incidents
|
|
332
|
+
: [];
|
|
333
|
+
const allIncidents = state && state.incidents && typeof state.incidents === 'object'
|
|
334
|
+
? Object.values(state.incidents)
|
|
335
|
+
.sort((a, b) => String(b.last_seen_at || '').localeCompare(String(a.last_seen_at || '')))
|
|
336
|
+
.slice(0, 50)
|
|
337
|
+
: [];
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
daemon,
|
|
341
|
+
summary_status: summary ? summary.status : null,
|
|
342
|
+
summary_reason: summary ? summary.reason : null,
|
|
343
|
+
summary_checked_at: summary ? summary.checked_at : null,
|
|
344
|
+
started_at: state ? state.started_at : null,
|
|
345
|
+
updated_at: state ? state.updated_at : null,
|
|
346
|
+
servers,
|
|
347
|
+
browser,
|
|
348
|
+
unresolved_incidents: unresolvedIncidents,
|
|
349
|
+
recent_incidents: allIncidents,
|
|
350
|
+
totals: summary ? {
|
|
351
|
+
unresolved: summary.unresolved_incidents_total || 0,
|
|
352
|
+
browser: summary.browser_incidents_total || 0,
|
|
353
|
+
server: summary.server_incidents_total || 0,
|
|
354
|
+
} : null,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
288
358
|
function getRunToCompletionInfo(cwd) {
|
|
289
359
|
const overrides = readOverrides(cwd);
|
|
290
360
|
return {
|
|
@@ -548,6 +618,7 @@ function collectStatus(cwd) {
|
|
|
548
618
|
reminders: getRemindersInfo(cwd),
|
|
549
619
|
enforcement: getEnforcementInfo(cwd),
|
|
550
620
|
runToCompletion: getRunToCompletionInfo(cwd),
|
|
621
|
+
observer: getObserverInfo(cwd),
|
|
551
622
|
activePlan: getActivePlanInfo(cwd),
|
|
552
623
|
logs: getLogsInfo(cwd),
|
|
553
624
|
doctor: getDoctorInfo(cwd),
|
|
@@ -748,6 +819,7 @@ const SECTIONS = [
|
|
|
748
819
|
{ id: 'hooks', label: 'Hooks' },
|
|
749
820
|
{ id: 'reminders', label: 'Reminders' },
|
|
750
821
|
{ id: 'logs', label: 'Logs' },
|
|
822
|
+
{ id: 'observer', label: 'Observer' },
|
|
751
823
|
{ id: 'activePlan', label: 'Active plan' },
|
|
752
824
|
{ id: 'mcp', label: 'MCP & observer' },
|
|
753
825
|
{ id: 'setupContext', label: 'Setup context' },
|
|
@@ -880,12 +952,19 @@ function severityFor(id) {
|
|
|
880
952
|
case 'mcp': {
|
|
881
953
|
if (!s.mcp.configFound) return 'info';
|
|
882
954
|
const playwrightOk = s.mcp.playwright.match && s.mcp.staleLockfiles.length === 0;
|
|
883
|
-
// Observer stopped while Playwright is configured = warn. Console errors
|
|
884
|
-
// and network failures from headed sessions are silently lost when the
|
|
885
|
-
// observer is down.
|
|
886
955
|
const observerOk = !s.mcp.playwright.match || (s.mcp.observer && s.mcp.observer.running);
|
|
887
956
|
return playwrightOk && observerOk ? 'ok' : 'warn';
|
|
888
957
|
}
|
|
958
|
+
case 'observer': {
|
|
959
|
+
if (!s.observer) return 'info';
|
|
960
|
+
// Daemon down → warn (not err, since the observer is supportive infra).
|
|
961
|
+
if (!s.observer.daemon || !s.observer.daemon.running) return 'warn';
|
|
962
|
+
// Any unresolved incidents → warn so the user sees the badge.
|
|
963
|
+
if (s.observer.totals && s.observer.totals.unresolved > 0) return 'warn';
|
|
964
|
+
// Any supervised server in unhealthy/exited → warn.
|
|
965
|
+
if (s.observer.servers && s.observer.servers.some((sr) => sr.status === 'unhealthy' || sr.status === 'exited')) return 'warn';
|
|
966
|
+
return 'ok';
|
|
967
|
+
}
|
|
889
968
|
case 'setupContext': return !s.setupContext.exists ? 'err' : (s.setupContext.toolingComplete ? 'ok' : 'warn');
|
|
890
969
|
case 'activeRun': return s.activeRun.active ? 'info' : 'info';
|
|
891
970
|
case 'doctor': return s.doctor.issueCount > 0 ? 'warn' : 'ok';
|
|
@@ -930,6 +1009,7 @@ function renderContent() {
|
|
|
930
1009
|
hooks: renderHooks,
|
|
931
1010
|
reminders: renderReminders,
|
|
932
1011
|
logs: renderLogs,
|
|
1012
|
+
observer: renderObserver,
|
|
933
1013
|
activePlan: renderActivePlan,
|
|
934
1014
|
mcp: renderMcp,
|
|
935
1015
|
setupContext: renderSetup,
|
|
@@ -1295,6 +1375,115 @@ function renderActivePlan() {
|
|
|
1295
1375
|
<details data-detail-id="active-plan-full"><summary>Full plan content</summary><div id="active-plan-body" class="muted">Click to load…</div></details>\`;
|
|
1296
1376
|
}
|
|
1297
1377
|
|
|
1378
|
+
function renderObserver() {
|
|
1379
|
+
const o = state.observer;
|
|
1380
|
+
if (!o) return '<h2>Observer</h2><p class="muted">No observer info available.</p>';
|
|
1381
|
+
const daemonAlive = o.daemon && o.daemon.running;
|
|
1382
|
+
const statusBadge = daemonAlive
|
|
1383
|
+
? \`<span class="badge ok">✓ running</span> <span class="muted">pid \${o.daemon.pid} • port \${o.daemon.port} • detected via \${o.daemon.source}</span>\`
|
|
1384
|
+
: \`<span class="badge warn">⚠ stopped</span> <span class="muted">no listener detected</span>\`;
|
|
1385
|
+
const restartBtn = !daemonAlive
|
|
1386
|
+
? '<button id="restart-observer" class="btn-warn" style="margin-left:14px;">Restart observer</button>'
|
|
1387
|
+
: '';
|
|
1388
|
+
const totals = o.totals || { unresolved: 0, browser: 0, server: 0 };
|
|
1389
|
+
const totalsBadge = totals.unresolved > 0
|
|
1390
|
+
? \`<span class="badge warn">\${totals.unresolved} unresolved</span> <span class="muted">(browser: \${totals.browser}, server: \${totals.server})</span>\`
|
|
1391
|
+
: '<span class="badge ok">✓ no unresolved incidents</span>';
|
|
1392
|
+
const summaryStatus = o.summary_status ? \`<span class="muted">summary status: <code>\${escapeHtml(o.summary_status)}</code></span>\` : '';
|
|
1393
|
+
|
|
1394
|
+
// Servers table
|
|
1395
|
+
const serversTable = o.servers.length === 0
|
|
1396
|
+
? '<p class="muted">No servers configured in setup-context.json.servers.</p>'
|
|
1397
|
+
: '<table class="kv-table" style="width:100%;border-collapse:collapse;font-size:13px;">' +
|
|
1398
|
+
'<thead><tr><th style="text-align:left;padding:6px 8px;">Label</th><th style="text-align:left;padding:6px 8px;">Port</th><th style="text-align:left;padding:6px 8px;">Status</th><th style="text-align:left;padding:6px 8px;">Health</th><th style="text-align:left;padding:6px 8px;">Last ready</th><th style="text-align:left;padding:6px 8px;">Last issue</th></tr></thead>' +
|
|
1399
|
+
'<tbody>' +
|
|
1400
|
+
o.servers.map((srv) => {
|
|
1401
|
+
const statusColor = srv.status === 'running' || srv.status === 'external' ? 'ok' : (srv.status === 'unhealthy' || srv.status === 'exited' ? 'err' : 'info');
|
|
1402
|
+
const healthIcon = srv.health_ok ? '<span class="badge ok">✓</span>' : '<span class="badge err">✗</span>';
|
|
1403
|
+
const exitInfo = srv.last_exit_code !== null && srv.last_exit_code !== undefined
|
|
1404
|
+
? \` <span class="muted">(exit \${srv.last_exit_code}\${srv.last_exit_signal ? '/' + srv.last_exit_signal : ''})</span>\`
|
|
1405
|
+
: '';
|
|
1406
|
+
return \`<tr style="border-top:1px solid var(--border);">
|
|
1407
|
+
<td style="padding:6px 8px;"><strong>\${escapeHtml(srv.label)}</strong></td>
|
|
1408
|
+
<td style="padding:6px 8px;"><code>\${srv.port || '?'}</code></td>
|
|
1409
|
+
<td style="padding:6px 8px;"><span class="badge \${statusColor}">\${escapeHtml(srv.status)}</span>\${exitInfo}</td>
|
|
1410
|
+
<td style="padding:6px 8px;">\${healthIcon}</td>
|
|
1411
|
+
<td style="padding:6px 8px;" class="muted">\${escapeHtml(formatRelativeTime(srv.last_ready_at))}</td>
|
|
1412
|
+
<td style="padding:6px 8px;" class="muted">\${escapeHtml(formatRelativeTime(srv.last_issue_at))}</td>
|
|
1413
|
+
</tr>\`;
|
|
1414
|
+
}).join('') +
|
|
1415
|
+
'</tbody></table>';
|
|
1416
|
+
|
|
1417
|
+
// Browser section
|
|
1418
|
+
const browserBlock = o.browser && o.browser.last_snapshot_at
|
|
1419
|
+
? \`<div class="card" style="padding:10px 14px;font-size:13px;">
|
|
1420
|
+
\${row('Latest URL', o.browser.latest_url ? '<code>' + escapeHtml(o.browser.latest_url) + '</code>' : '<span class="muted">none</span>')}
|
|
1421
|
+
\${row('Latest route', o.browser.latest_route ? '<code>' + escapeHtml(o.browser.latest_route) + '</code>' : '<span class="muted">none</span>')}
|
|
1422
|
+
\${row('Snapshots received', String(o.browser.snapshots_received))}
|
|
1423
|
+
\${row('Last snapshot', formatRelativeTime(o.browser.last_snapshot_at))}
|
|
1424
|
+
</div>\`
|
|
1425
|
+
: '<p class="muted">No browser snapshots received yet. (The observer collects when the agent navigates with mcp__claude-in-chrome__*.)</p>';
|
|
1426
|
+
|
|
1427
|
+
// Incidents feed
|
|
1428
|
+
const incidentsBlock = o.recent_incidents.length === 0
|
|
1429
|
+
? '<p class="muted">No incidents recorded.</p>'
|
|
1430
|
+
: '<div class="incidents-feed">' +
|
|
1431
|
+
o.recent_incidents.map((inc) => {
|
|
1432
|
+
const sevClass = inc.severity === 'error' || inc.severity === 'critical' ? 'err' : (inc.severity === 'warning' ? 'warn' : 'info');
|
|
1433
|
+
const resolvedBadge = inc.resolved_at
|
|
1434
|
+
? '<span class="badge ok" style="margin-left:6px;">resolved</span>'
|
|
1435
|
+
: '<span class="badge warn" style="margin-left:6px;">active</span>';
|
|
1436
|
+
const surfaceLabel = inc.source === 'server' && inc.metadata && inc.metadata.label
|
|
1437
|
+
? inc.metadata.label
|
|
1438
|
+
: inc.surface || inc.source;
|
|
1439
|
+
return \`<div class="incident-row" style="padding:10px 14px;border-top:1px solid var(--border);">
|
|
1440
|
+
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:4px;">
|
|
1441
|
+
<span class="badge \${sevClass}">\${escapeHtml(inc.severity || 'info')}</span>
|
|
1442
|
+
<span class="muted">\${escapeHtml(inc.source || 'unknown')}</span>
|
|
1443
|
+
<span class="muted">→</span>
|
|
1444
|
+
<strong>\${escapeHtml(surfaceLabel)}</strong>
|
|
1445
|
+
<span class="muted">\${escapeHtml(inc.type || '')}</span>
|
|
1446
|
+
\${resolvedBadge}
|
|
1447
|
+
<span class="muted" style="margin-left:auto;font-size:11px;">\${escapeHtml(formatRelativeTime(inc.last_seen_at))}</span>
|
|
1448
|
+
</div>
|
|
1449
|
+
<div style="font-size:12px;white-space:pre-wrap;">\${escapeHtml((inc.message || '').slice(0, 600))}\${(inc.message || '').length > 600 ? '…' : ''}</div>
|
|
1450
|
+
\${inc.metadata && inc.metadata.url ? '<div class="muted" style="font-size:11px;margin-top:4px;">at <code>' + escapeHtml(inc.metadata.url) + '</code></div>' : ''}
|
|
1451
|
+
</div>\`;
|
|
1452
|
+
}).join('') +
|
|
1453
|
+
'</div>';
|
|
1454
|
+
|
|
1455
|
+
// Clear button — only meaningful when the daemon is running AND there's
|
|
1456
|
+
// either at least one unresolved incident or some browser snapshots to wipe.
|
|
1457
|
+
const clearable = daemonAlive && (totals.unresolved > 0 || (o.browser && o.browser.snapshots_received > 0) || o.recent_incidents.length > 0);
|
|
1458
|
+
const clearBtn = clearable
|
|
1459
|
+
? '<button id="clear-observer" class="logs-clear-btn" style="margin-left:14px;" title="Resolves all active incidents, clears per-server recent_log buffers, and resets browser snapshot counter. The state file keeps incident history (marked resolved). Future events repopulate as usual.">Clear observer state</button>'
|
|
1460
|
+
: '';
|
|
1461
|
+
|
|
1462
|
+
return \`<h2>Observer</h2>
|
|
1463
|
+
<p class="sub">Daemon: \${statusBadge}\${restartBtn}\${clearBtn}</p>
|
|
1464
|
+
<p class="sub">Incidents: \${totalsBadge} \${summaryStatus}</p>
|
|
1465
|
+
<h3 style="margin-top:18px;font-size:14px;">Servers (\${o.servers.length})</h3>
|
|
1466
|
+
<div class="card" style="padding:0;">\${serversTable}</div>
|
|
1467
|
+
<h3 style="margin-top:18px;font-size:14px;">Browser channel</h3>
|
|
1468
|
+
\${browserBlock}
|
|
1469
|
+
<h3 style="margin-top:18px;font-size:14px;">Incidents (most recent \${o.recent_incidents.length})</h3>
|
|
1470
|
+
<div class="card" style="padding:0;">\${incidentsBlock}</div>\`;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
function formatRelativeTime(iso) {
|
|
1474
|
+
if (!iso) return 'never';
|
|
1475
|
+
try {
|
|
1476
|
+
const t = new Date(iso).getTime();
|
|
1477
|
+
if (Number.isNaN(t)) return 'unknown';
|
|
1478
|
+
const diff = Date.now() - t;
|
|
1479
|
+
if (diff < 0) return 'in the future';
|
|
1480
|
+
if (diff < 60_000) return Math.floor(diff / 1000) + 's ago';
|
|
1481
|
+
if (diff < 3_600_000) return Math.floor(diff / 60_000) + 'm ago';
|
|
1482
|
+
if (diff < 86_400_000) return Math.floor(diff / 3_600_000) + 'h ago';
|
|
1483
|
+
return Math.floor(diff / 86_400_000) + 'd ago';
|
|
1484
|
+
} catch { return 'unknown'; }
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1298
1487
|
function renderMcp() {
|
|
1299
1488
|
const m = state.mcp;
|
|
1300
1489
|
if (!m.configFound) return '<h2>MCP & observer</h2><p class="muted">.mcp.json not found</p>';
|
|
@@ -1425,8 +1614,33 @@ document.addEventListener('click', (e) => {
|
|
|
1425
1614
|
if (t.id === 'fix-cdp-port') {
|
|
1426
1615
|
return fixCdpPortAction(t);
|
|
1427
1616
|
}
|
|
1617
|
+
if (t.id === 'clear-observer') {
|
|
1618
|
+
return clearObserverAction(t);
|
|
1619
|
+
}
|
|
1428
1620
|
});
|
|
1429
1621
|
|
|
1622
|
+
async function clearObserverAction(btn) {
|
|
1623
|
+
if (!confirm('Resolve all active observer incidents and clear server logs?\\n\\nIncident history is preserved in state.incidents (marked resolved). Future events repopulate normally. This cannot be undone.')) return;
|
|
1624
|
+
const originalLabel = btn.textContent;
|
|
1625
|
+
btn.disabled = true;
|
|
1626
|
+
btn.textContent = 'Clearing…';
|
|
1627
|
+
try {
|
|
1628
|
+
const r = await fetch('/api/observer/clear', { method: 'POST' });
|
|
1629
|
+
const result = await r.json();
|
|
1630
|
+
if (r.ok && result.ok) {
|
|
1631
|
+
showToast('Observer state cleared at ' + (result.reset_at || 'now'), 'ok');
|
|
1632
|
+
} else {
|
|
1633
|
+
showToast('Clear failed: ' + (result.error || 'unknown'), 'err');
|
|
1634
|
+
}
|
|
1635
|
+
} catch (e) {
|
|
1636
|
+
showToast('Network error: ' + e.message, 'err');
|
|
1637
|
+
} finally {
|
|
1638
|
+
btn.disabled = false;
|
|
1639
|
+
btn.textContent = originalLabel;
|
|
1640
|
+
await refresh();
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1430
1644
|
async function fixCdpPortAction(btn) {
|
|
1431
1645
|
const originalLabel = btn.textContent;
|
|
1432
1646
|
btn.disabled = true;
|
|
@@ -1754,6 +1968,42 @@ function handler(cwd) {
|
|
|
1754
1968
|
}), 'application/json');
|
|
1755
1969
|
}
|
|
1756
1970
|
|
|
1971
|
+
if (req.method === 'POST' && route === '/api/observer/clear') {
|
|
1972
|
+
// Forwards to the observer daemon's /reset-session endpoint, which
|
|
1973
|
+
// resolves all active browser + server incidents, clears the per-server
|
|
1974
|
+
// recent_log buffers, and resets the browser snapshot counter. The
|
|
1975
|
+
// history is preserved in state.incidents (marked resolved), so this
|
|
1976
|
+
// is a soft clear: future polls won't see the old incidents as active.
|
|
1977
|
+
const observer = readObserverState(cwd);
|
|
1978
|
+
if (!observer.running) {
|
|
1979
|
+
return send(409, JSON.stringify({ ok: false, error: 'observer is not running — start it first, then clear.' }), 'application/json');
|
|
1980
|
+
}
|
|
1981
|
+
const { request: httpRequest } = require('node:http');
|
|
1982
|
+
const result = await new Promise((resolve) => {
|
|
1983
|
+
const req2 = httpRequest({
|
|
1984
|
+
hostname: '127.0.0.1',
|
|
1985
|
+
port: observer.port,
|
|
1986
|
+
path: '/reset-session',
|
|
1987
|
+
method: 'POST',
|
|
1988
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': 2 },
|
|
1989
|
+
}, (r) => {
|
|
1990
|
+
let body = '';
|
|
1991
|
+
r.on('data', (c) => { body += c; });
|
|
1992
|
+
r.on('end', () => {
|
|
1993
|
+
try { resolve({ status: r.statusCode, body: JSON.parse(body) }); }
|
|
1994
|
+
catch { resolve({ status: r.statusCode, body: { raw: body } }); }
|
|
1995
|
+
});
|
|
1996
|
+
});
|
|
1997
|
+
req2.on('error', (err) => resolve({ status: 0, body: { error: err.message } }));
|
|
1998
|
+
req2.write('{}');
|
|
1999
|
+
req2.end();
|
|
2000
|
+
});
|
|
2001
|
+
if (result.status === 200 && result.body && result.body.ok) {
|
|
2002
|
+
return send(200, JSON.stringify({ ok: true, reset_at: result.body.reset_at }), 'application/json');
|
|
2003
|
+
}
|
|
2004
|
+
return send(502, JSON.stringify({ ok: false, error: 'observer returned ' + result.status, daemon_response: result.body }), 'application/json');
|
|
2005
|
+
}
|
|
2006
|
+
|
|
1757
2007
|
if (req.method === 'POST' && route === '/api/observer/restart') {
|
|
1758
2008
|
// Re-run the SessionStart/browser-error-daemon.js hook script
|
|
1759
2009
|
// synchronously. The script calls ensureObserver() which spawns
|
|
@@ -1922,6 +2172,7 @@ module.exports = run;
|
|
|
1922
2172
|
module.exports.start = start;
|
|
1923
2173
|
module.exports.createPanelServer = handler;
|
|
1924
2174
|
module.exports.derivePanelPort = derivePanelPort;
|
|
2175
|
+
module.exports.getObserverInfo = getObserverInfo;
|
|
1925
2176
|
module.exports.collectStatus = collectStatus;
|
|
1926
2177
|
module.exports.getClaudeMdInfo = getClaudeMdInfo;
|
|
1927
2178
|
module.exports.getHooksInfo = getHooksInfo;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.64",
|
|
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"
|