← back to Abrams

scripts/counters.js

123 lines

#!/usr/bin/env node
// counters.js — probe each Abrams sub-property for headline numbers.
// Read-only. Times out fast. Writes data/counters.json for the panel hero strip.

const fs = require('fs');
const path = require('path');
const http = require('http');
const { execSync } = require('child_process');

const ROOT = path.resolve(__dirname, '..');
const HOME = process.env.HOME;
const OUT = path.join(ROOT, 'data', 'counters.json');
const LOG = path.join(ROOT, 'data', 'build-log.jsonl');

const log = (type, payload) => fs.appendFileSync(LOG, JSON.stringify({ ts: new Date().toISOString(), type, ...payload }) + '\n');

function fetchJson(url, timeoutMs = 1500) {
  return new Promise(resolve => {
    const req = http.get(url, { timeout: timeoutMs }, r => {
      let data = '';
      r.on('data', c => data += c);
      r.on('end', () => { try { resolve(JSON.parse(data)); } catch { resolve(null); } });
    });
    req.on('error', () => resolve(null));
    req.on('timeout', () => { req.destroy(); resolve(null); });
  });
}

function fileCount(dir, ext) {
  try { return parseInt(execSync(`find "${dir}" -type f -name "*.${ext}" 2>/dev/null | wc -l`, { encoding: 'utf8' }).trim(), 10) || 0; } catch { return 0; }
}

async function main() {
  const counters = [];

  // ── Abrams Panel itself ────────────────────────────────────────────
  const projHere = fs.existsSync(path.join(ROOT, 'data', 'projects.json'))
    ? JSON.parse(fs.readFileSync(path.join(ROOT, 'data', 'projects.json'), 'utf8'))
    : { projects: [] };
  counters.push({ key: 'projects', label: 'Projects tracked', value: projHere.count || projHere.projects?.length || 0, url: '/#projects' });

  const ideasHere = fs.existsSync(path.join(ROOT, 'data', 'ideas.json'))
    ? JSON.parse(fs.readFileSync(path.join(ROOT, 'data', 'ideas.json'), 'utf8'))
    : { ideas: [] };
  counters.push({ key: 'swots', label: 'SWOT analyses', value: (ideasHere.ideas || []).length, url: '/ideas' });

  const leadCount = fs.existsSync(path.join(ROOT, 'data', 'leads.jsonl'))
    ? fs.readFileSync(path.join(ROOT, 'data', 'leads.jsonl'), 'utf8').split('\n').filter(Boolean).length
    : 0;
  counters.push({ key: 'leads', label: 'Leads captured', value: leadCount, url: '#lead' });

  // ── AbramsOS (household-rights OS, port 9931) ──────────────────────
  const aos = await fetchJson('http://127.0.0.1:9931/api/stats').catch(() => null);
  if (aos && typeof aos.warranty_count === 'number') {
    counters.push({ key: 'warranties', label: 'Warranties tracked', value: aos.warranty_count, url: 'http://127.0.0.1:9931/' });
  } else {
    // fallback: count items in AbramsOS data dir
    const aosData = path.join(HOME, 'Projects/AbramsOS/data');
    if (fs.existsSync(aosData)) {
      const items = fileCount(aosData, 'json');
      counters.push({ key: 'aos_records', label: 'AbramsOS records', value: items, url: 'http://127.0.0.1:9931/' });
    }
  }

  // ── mp3.agentabrams.com (tracks) ───────────────────────────────────
  const mp3Data = path.join(HOME, 'Projects/mp3-agentabrams');
  if (fs.existsSync(mp3Data)) {
    let tracks = 0;
    try {
      const meta = fs.readFileSync(path.join(mp3Data, 'data/tracks.json'), 'utf8');
      tracks = JSON.parse(meta).length || 0;
    } catch {
      tracks = fileCount(mp3Data, 'json');
    }
    counters.push({ key: 'tracks', label: 'MP3s indexed', value: tracks, url: 'http://127.0.0.1:9931/' });
  }

  // ── Abrams Report (stories) ────────────────────────────────────────
  const reportData = path.join(HOME, 'Projects/abrams-report/data');
  if (fs.existsSync(reportData)) {
    let stories = 0;
    try {
      const f = fs.readFileSync(path.join(reportData, 'stories.json'), 'utf8');
      stories = JSON.parse(f).length || 0;
    } catch {
      stories = fileCount(reportData, 'json');
    }
    counters.push({ key: 'stories', label: 'Stories curated', value: stories, url: 'http://report.agentabrams.com/' });
  }

  // ── Butlr (calls) ──────────────────────────────────────────────────
  const butlrData = path.join(HOME, 'Projects/butlr');
  if (fs.existsSync(butlrData)) {
    try {
      const f = fs.readFileSync(path.join(butlrData, 'data/calls.json'), 'utf8');
      const calls = JSON.parse(f);
      counters.push({ key: 'calls', label: 'Butlr calls placed', value: Array.isArray(calls) ? calls.length : 0, url: 'http://butlr.agentabrams.com/' });
    } catch {}
  }

  // ── lawyers.agentabrams.com (attorneys) ────────────────────────────
  const lawyersData = path.join(HOME, 'Projects/lawyer-directory-builder');
  if (fs.existsSync(lawyersData)) {
    try {
      const out = execSync(`wc -l "${path.join(lawyersData, 'data/lawyers.csv')}" 2>/dev/null | awk '{print $1}'`, { encoding: 'utf8' }).trim();
      counters.push({ key: 'lawyers', label: 'Attorneys indexed', value: parseInt(out, 10) || 0, url: 'https://lawyers.agentabrams.com' });
    } catch {}
  }

  // ── git-commits across portfolio ───────────────────────────────────
  try {
    const total = projHere.projects.reduce((a, p) => a + (p.commit_count || 0), 0);
    counters.push({ key: 'commits', label: 'Commits across portfolio', value: total, url: null });
  } catch {}

  const out = { generated_at: new Date().toISOString(), counters };
  fs.writeFileSync(OUT, JSON.stringify(out, null, 2));
  log('counters', { count: counters.length });
  console.log(`counters: ${counters.length} metrics → ${OUT}`);
}

main().catch(e => { console.error(e); process.exit(1); });