← 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
M scripts/ingest-linkedin-exports.mjs
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 →