[object Object]

← 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

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 →