← back to build-pages
api: /api/fleet/{agents,skills} — cross-project aggregated facet tallies + 4 smoke
232a25310fc87fc794d0d5efaa8e8d21dc8e268f · 2026-05-13 17:00:24 -0700 · SteveStudio2
Files touched
M server.jsM test/smoke.js
Diff
commit 232a25310fc87fc794d0d5efaa8e8d21dc8e268f
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Wed May 13 17:00:24 2026 -0700
api: /api/fleet/{agents,skills} — cross-project aggregated facet tallies + 4 smoke
---
server.js | 26 ++++++++++++++++++++++++++
test/smoke.js | 7 +++++++
2 files changed, 33 insertions(+)
diff --git a/server.js b/server.js
index f19f3f8..7fb8da8 100644
--- a/server.js
+++ b/server.js
@@ -575,6 +575,30 @@ app.get('/p/:slug/c/:hash', async (req, res, next) => {
} catch (e) { next(e); }
});
+// /api/fleet/skills + /api/fleet/agents — aggregated facet counts across every
+// project. Merges per-project tallies into a fleet-wide ranking so the user
+// can see "the secrets skill is used 17 times across the fleet" without
+// fetching 4 endpoints and summing client-side.
+for (const facet of ['agents', 'skills']) {
+ app.get(`/api/fleet/${facet}`, async (_req, res, next) => {
+ try {
+ const tallies = new Map();
+ for (const [slug, p] of Object.entries(PROJECTS)) {
+ const bundle = await getProjectBundle(slug, p).catch(() => null);
+ if (!bundle) continue;
+ for (const f of bundle.facets[facet]) {
+ tallies.set(f.name, (tallies.get(f.name) || 0) + f.count);
+ }
+ }
+ const merged = [...tallies.entries()]
+ .map(([name, count]) => ({ name, count }))
+ .sort((a, b) => b.count - a.count);
+ res.setHeader('Cache-Control', 'public, max-age=60, stale-while-revalidate=120');
+ res.json({ [facet]: merged });
+ } catch (e) { next(e); }
+ });
+}
+
// /api/recent — most recent N commits across the entire fleet, merged + sorted
// by date desc. Powers a "today across the fleet" rail. Shares the bundle
// cache so it's free after the first warm.
@@ -799,6 +823,8 @@ app.get('/api', (_req, res) => {
'GET /api/projects/:slug/ideas': 'Raw ideas — commit bodies ≥120 chars (design-rationale + scaffolds)',
'GET /api/search': 'Cross-project commit search — ?q=<term>&limit=N, round-robin + by_project breakdown',
'GET /api/recent': 'Most recent N commits across the whole fleet — date-sorted, ?limit=20',
+ 'GET /api/fleet/agents': 'Fleet-wide agent tallies merged across every project',
+ 'GET /api/fleet/skills': 'Fleet-wide skill tallies merged across every project',
'GET /p/:slug': 'Project journal HTML page (?q= deep-link supported)',
'GET /p/:slug/c/:hash': 'Commit detail page — full diff + body',
'GET /p/:slug/feed.atom': 'Atom 1.0 feed — up to 50 recent commits, autodiscovered on project page',
diff --git a/test/smoke.js b/test/smoke.js
index ae2ec1e..ec363cc 100644
--- a/test/smoke.js
+++ b/test/smoke.js
@@ -203,6 +203,13 @@ function check(name, cond, hint = '') {
check('recent: results array', /"results":\[/.test(r.body));
check('recent: hash field', /"hash":"[a-f0-9]{4,}/.test(r.body));
+ // 13b2. fleet-wide facet endpoints (agents + skills)
+ for (const f of ['agents', 'skills']) {
+ r = await fetch(`/api/fleet/${f}`);
+ check(`fleet/${f}: 200`, r.status === 200);
+ check(`fleet/${f}: field`, new RegExp(`"${f}":\\[`).test(r.body));
+ }
+
// 13c. per-project facet endpoints (agents / skills / ideas)
for (const facet of ['agents', 'skills', 'ideas']) {
r = await fetch(`/api/projects/butlr/${facet}`);
← 8fd3a31 home: auto-refresh 'Recent across the fleet' rail every 60s
·
back to build-pages
·
home: top skills + agents rail (fleet-wide tallies surfaced 83d2712 →