← back to Marketing Command Center

scripts/build-dw-accounts.js

68 lines

#!/usr/bin/env node
/*
 * build-dw-accounts.js — regenerate data/dw-ig-accounts.json, the CANONICAL list
 * of every DW-owned Instagram account, from Norma's postable-accounts registry.
 *
 * Why this exists: the Command Center's panels each used to derive their own
 * account list (the IG-Activity dropdown from post handles only, follow-counts
 * live from Norma, etc.), so an account with no posts/data silently vanished from
 * a surface. This script promotes ONE source of truth that every social/account
 * panel reads via GET /api/dw-accounts.
 *
 * Source (overridable via env):
 *   NORMA_ACCOUNTS  (default ~/Projects/Norma/agents/instagram-agent/accounts.json)
 *
 * Runs on Mac2 (where Norma lives). The OUT file is committed so it ships to
 * Kamatera, where Norma is unreachable — same reason the IG-delete flow degrades
 * on prod. If the source is missing, the existing committed JSON is preserved.
 * Idempotent; safe to run repeatedly.
 *
 *   npm run build:dwaccounts
 */
const fs = require('fs');
const os = require('os');
const path = require('path');

const HOME = os.homedir();
const SRC = process.env.NORMA_ACCOUNTS ||
  path.join(HOME, 'Projects', 'Norma', 'agents', 'instagram-agent', 'accounts.json');
const OUT = path.join(__dirname, '..', 'data', 'dw-ig-accounts.json');

const prev = fs.existsSync(OUT) ? JSON.parse(fs.readFileSync(OUT, 'utf8')) : { accounts: [] };

if (!fs.existsSync(SRC)) {
  console.log(`• Norma source missing (${SRC}) — preserving existing ${(prev.accounts || []).length} accounts`);
  process.exit(0);
}

const reg = JSON.parse(fs.readFileSync(SRC, 'utf8'));
const src = reg.accounts || {};

// Normalize to a flat, sorted list. `name` is the human display name (page_name),
// falling back to the handle. Keep ig_user_id so panels that publish/track by the
// Graph id can join without re-reading Norma.
const accounts = Object.values(src)
  .filter(a => a && a.handle)
  .map(a => ({
    handle: String(a.handle),
    name: String(a.page_name || a.handle),
    ig_user_id: a.ig_user_id || null,
    // An account is postable when it has an ig_user_id (wired to the Meta Graph API).
    // Accounts without ig_user_id are staged but not yet API-connected.
    postable: !!(a.ig_user_id),
  }))
  .sort((x, y) => x.handle.localeCompare(y.handle));

const out = {
  source: 'Norma/agents/instagram-agent/accounts.json',
  generated_at: new Date().toISOString(),
  count: accounts.length,
  accounts,
};

const tmp = OUT + '.tmp';
fs.writeFileSync(tmp, JSON.stringify(out, null, 2));
fs.renameSync(tmp, OUT);
console.log(`wrote ${path.relative(process.cwd(), OUT)} — ${accounts.length} DW-owned accounts`);
accounts.forEach(a => console.log(`   @${a.handle}  (${a.name})`));