← 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})`));