[object Object]

← back to Marketing Command Center

ingest-checker: re-export staleness reminder to steve-personal via George (env/keychain auth, no hardcoded secret)

6c4b6cf73943d7a5c14bb5fde182352416caf25b · 2026-08-31 14:41:08 -0700 · Steve Abrams

Files touched

Diff

commit 6c4b6cf73943d7a5c14bb5fde182352416caf25b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Aug 31 14:41:08 2026 -0700

    ingest-checker: re-export staleness reminder to steve-personal via George (env/keychain auth, no hardcoded secret)
---
 scripts/ingest-linkedin-exports.mjs | 40 +++++++++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)

diff --git a/scripts/ingest-linkedin-exports.mjs b/scripts/ingest-linkedin-exports.mjs
index 2a4064d..ae4e76b 100644
--- a/scripts/ingest-linkedin-exports.mjs
+++ b/scripts/ingest-linkedin-exports.mjs
@@ -52,5 +52,41 @@ if (conns && conns.mtime > (state.connsMtime || 0)) {
   state.connsMtime = conns.mtime; did = true;
 } else log('connections: up to date');
 
-fs.writeFileSync(STATE, JSON.stringify({ ...state, lastCheck: new Date().toISOString() }, null, 2));
-log(did ? 'INGESTED new export(s).' : 'nothing new.');
+// ── Staleness reminder — email steve-personal when the LinkedIn data needs a fresh
+// export (LinkedIn has no follows/connections API, so re-export is manual). Fires when
+// the newest-ingested export is older than STALE_DAYS, re-nagging at most every 7 days.
+const STALE_DAYS = 30, RENAG_DAYS = 7, DAY = 864e5;
+const newestIngest = Math.max(state.followsMtime || 0, state.connsMtime || 0);
+const ageDays = newestIngest ? (Date.now() - newestIngest) / DAY : 999;
+const lastRemind = state.reminderSentAt ? new Date(state.reminderSentAt).getTime() : 0;
+// George Basic-Auth — env first, then macOS Keychain (dw-agents/admin). Never hardcoded.
+function georgeAuth() {
+  if (process.env.GEORGE_BASIC_AUTH) return process.env.GEORGE_BASIC_AUTH;
+  try { const pw = execFileSync('security', ['find-generic-password', '-s', 'dw-agents', '-a', 'admin', '-w'], { encoding: 'utf8' }).trim();
+    if (pw) return Buffer.from('admin:' + pw).toString('base64'); } catch {}
+  return null;
+}
+if (!did && ageDays >= STALE_DAYS && (Date.now() - lastRemind) >= RENAG_DAYS * DAY) {
+  const AUTH = georgeAuth();
+  if (!AUTH) { log('reminder skipped: no George auth (set GEORGE_BASIC_AUTH or dw-agents keychain)'); }
+  else {
+  const body = `Your LinkedIn data feeding the DW Marketing Command Center is ${Math.round(ageDays)} days old.\n\n`
+    + `LinkedIn has no API for your follows/connections, so a fresh pull is a manual export:\n`
+    + `  1. LinkedIn → Settings → Data Privacy → Get a copy of your data → tick "Connections" + "Company Follows"\n`
+    + `  2. Request archive (ready in ~minutes), download the .zip, unzip on your Mac's Desktop/Downloads.\n`
+    + `That's it — the command center auto-ingests it within 6h (contacts + follows refresh, no other step).\n`;
+  try {
+    const r = execFileSync('curl', ['-s', '--max-time', '20',
+      '-X', 'POST', 'http://127.0.0.1:9850/api/send', '-H', 'content-type: application/json',
+      '-H', `authorization: Basic ${AUTH}`,
+      '--data', JSON.stringify({ account: 'steve-office', from: 'steve@designerwallcoverings.com',
+        to: 'steveabramsdesigns@gmail.com', subject: 'DW Marketing: time to re-export your LinkedIn data',
+        body, no_source_tag: false })], { encoding: 'utf8' });
+    log('reminder emailed steve-personal:', r.slice(0, 120));
+    state.reminderSentAt = new Date().toISOString();
+  } catch (e) { log('reminder email FAILED (non-fatal):', e.message); }
+  }
+}
+
+fs.writeFileSync(STATE, JSON.stringify({ ...state, lastCheck: new Date().toISOString(), dataAgeDays: Math.round(ageDays) }, null, 2));
+log(did ? 'INGESTED new export(s).' : `nothing new (data ${Math.round(ageDays)}d old).`);

← 793ca1d auto-data-snapshot: 2026-08-31T14:28:57 (1 data files) — pub  ·  back to Marketing Command Center  ·  auto-data-snapshot: 2026-08-31T15:07:00 (2 data files) — dat 2534ddf →