← back to Stack Map Viewer
server.js
663 lines
#!/usr/bin/env node
// Stack Interconnection Map — live web viewer
// Basic-auth admin/DW2024! · live-scans ~/.claude, ~/Library/LaunchAgents, yolo-queue, ~/Projects
const http = require('http');
const { execSync } = require('child_process');
const fs = require('fs');
const os = require('os');
const path = require('path');
const HOME = os.homedir();
const SKILLS = path.join(HOME, '.claude/skills');
const AGENTS = path.join(HOME, '.claude/agents');
const PROJECTS = path.join(HOME, 'Projects');
const LA = path.join(HOME, 'Library/LaunchAgents');
const PENDING = path.join(HOME, '.claude/yolo-queue/pending-approval');
const LEDGER = path.join(HOME, '.claude/yolo-queue/executed-reversible/ledger.jsonl');
const TICKETS = path.join(HOME, '.claude/tickets/events.jsonl');
const AUTH = 'Basic ' + Buffer.from('admin:DW2024!').toString('base64');
const sh = (c) => { try { return execSync(c, { encoding: 'utf8', timeout: 15000 }); } catch { return ''; } };
const lsdirs = (p, re) => { try { return fs.readdirSync(p, { withFileTypes: true }).filter(d => d.isDirectory() && !d.name.startsWith('.') && (!re || re.test(d.name))).map(d => d.name); } catch { return []; } };
const bucket = (names, re) => names.filter(n => re.test(n)).sort();
// ── Folder taxonomy (name-regex buckets) ────────────────────────────────────
// Each entry: [drill-kind, display label, regex]. Drill-kind must be unique.
// Adding a row here creates a live folder + drill-down with zero other edits.
const SKILL_CATS = [
['sk_canary', 'canaries / monitors', /canary/],
['sk_scraper', 'vendor scraper-managers', /scraper-manager$|-scraper$/],
['sk_dots', 'terminal dots / color', /dot|^color$|^flash$|green|orange|purple|yellow|pink|lightblue/],
['sk_video', 'hyperframes / video', /hyperframes|video|reel|avatar|heygen|clip|caption|motion|film|slideshow/],
['sk_social', 'social / marketing', /instagram|tiktok|linkedin|facebook|pinterest|youtube|social|marketing|^dw-x|kartiseira|threads|promo/],
['sk_pricing', 'pricing / cost', /price|cost|-map-|margin|kravet|discount|pricing/],
['sk_settlement', 'settlement gate', /settlement/],
['sk_storefront', 'storefront / site UI', /site|storefront|landing|hero|grid|viewer|rotator|bento|flipbook|gallery|page-flip|kv-list|modal|nav|scroll/],
['sk_research', 'research / web', /research|web-|websearch|last30|deep|exa|knowledge|scout|competitor|peer-survey/],
['sk_orchestration','loops / orchestration', /yolo|loop|officer|dtd|contrarian|council|plannator|ralph|masterdot|ticketmaster|agent-team|coordinat|abramstasks|iterm/],
['sk_shopify', 'shopify / catalog', /shopify|catalog|sku|metafield|gmc|merchant|activator|inventory|vendor-|reconcile/],
['sk_imagedesign', 'image / design', /image|design|room|mockup|logo|seam|edges|luxe|four-horsemen|graphic|interior|elements|fix-|regen/],
['sk_health', 'health / fleet guards', /health|rollup|fleet|drift|watchdog|sentinel|uptime|-guard|liveness/],
['sk_email', 'email / comms', /email|george|mailer|gmail|purelymail|comms|newsletter|inbox|reply/],
['sk_infra', 'infra / deploy', /domain|dns|cloudflare|nginx|kamatera|deploy|firewall|secret|backup|disk|cron|pm2|egress/],
];
const AGENT_CATS = [
['ag_officers', 'cabinet officers (vp-*)', /^vp-/],
['ag_vendor', 'DW commerce / vendor', /^dw-(?!x|instagram|tiktok|facebook|linkedin|pinterest|youtube|websites|promo|marketing)|scraper|vendor|shopify|catalog|greenland|fentucci|momentum|wallquest|google-merchant|showroom|fineartamerica|sister-parish/],
['ag_marketing', 'marketing / social', /marketing|instagram|tiktok|linkedin|facebook|pinterest|youtube|dw-x|dw-promo|dw-websites|front-page|graphic|ui-ux|seo|reels|logo/],
['ag_engineering', 'engineering', /engineer|review|architect|debug|error|test|security|database|backend|frontend|fullstack|python|typescript|javascript|mobile|ios|performance|code|prompt|mcp|api-doc|refactor/],
['ag_directory', 'directories', /directory|doctor|lawyer|la-research|adsense|domain|professional/],
['ag_research', 'research / data', /research|web-researcher|search|technical|data-|product-strategist|analyst|scientist|seo-analy|task-decomp|context-manager/],
['ag_special', 'special projects', /wallpapersback|apartment|seam|consulting|abramsego|bounce|wpb|garcia|elevator|avatar/],
['ag_ops', 'ops / approval / fleet', /approval|cncp|operations|fleet|kamatera|pm2|masterdot|contrarian|deployment|devops|statusline/],
];
const PROJECT_CATS = [
['pj_dw', 'DW / designerwallcoverings',/^dw-|designerwallcoverings|-internal$|shopify|catalog|vendor|filemaker/i],
['pj_scraper', 'scrapers / crawlers', /scraper|crawl|refresh|-catalog|reprice|onboard|exec$/i],
['pj_website', 'websites / storefronts', /wallpaper|wallcovering|grasscloth|flocked|novasuede|silk|linen|cork|storefront|microsite|\.com|website|site$/i],
['pj_canary', 'canaries / monitors', /canary|monitor|watchdog|guard|health|sentinel/i],
['pj_viewer', 'viewers / dashboards', /viewer|dashboard|-board|map$|panel|console|admin/i],
['pj_directory', 'directories / real-estate',/directory|lawyer|doctor|calbar|animals|ventura|crcp|realestate|homesonspec|commercialreal|lacounty/i],
['pj_media', 'media / video / photo', /video|reel|hyperframes|photo|image|render|comfy|design|mockup/i],
['pj_infra', 'infra / agents / tools', /agent|ticket|secret|domain|george|gmail|nas|cluster|exo|mcp|cncp|tool/i],
];
let CACHE = null, CACHE_TS = 0;
function scan() {
if (CACHE && Date.now() - CACHE_TS < 20000) return CACHE;
const skillDirs = lsdirs(SKILLS);
const canaries = bucket(skillDirs, /canary/);
const scrapers = bucket(skillDirs, /scraper-manager$/);
const dots = bucket(skillDirs, /dot|^color$|green|orange|purple|yellow|pink|lightblue/);
const video = bucket(skillDirs, /hyperframes|video/);
const health = (sh(`ls ${SKILLS}/*/data/latest.json 2>/dev/null | wc -l`).trim() || '0');
const agentFiles = (() => { try { return fs.readdirSync(AGENTS).filter(f => f.endsWith('.md')); } catch { return []; } })();
const agentNames = agentFiles.map(f => f.replace('.md', ''));
const officers = agentNames.filter(n => n.startsWith('vp-')).sort();
const workers = agentNames.filter(n => !n.startsWith('vp-')).sort();
const projectDirs = lsdirs(PROJECTS).sort();
const projectRepos = projectDirs.filter(d => { try { return fs.existsSync(path.join(PROJECTS, d, '.git')); } catch { return false; } });
const cronLoaded = (sh(`launchctl list 2>/dev/null | grep -c com.steve`).trim() || '0');
const cronPlists = (() => { try { return fs.readdirSync(LA).filter(f => /^com\.steve\..*\.plist$/.test(f)); } catch { return []; } })();
let cronToSkill = 0;
for (const f of cronPlists) { const t = sh(`grep -oE '/skills/[^/< ]+' '${path.join(LA, f)}' 2>/dev/null | head -1`).trim(); if (t) cronToSkill++; }
let parked = [], p3 = 0, p24 = 0, p7d = 0; const now = Date.now();
try { parked = fs.readdirSync(PENDING).filter(f => f.endsWith('.md')); } catch {}
for (const f of parked) { try { const m = fs.statSync(path.join(PENDING, f)).mtimeMs; const h = (now - m) / 3.6e6; if (h >= 3) p3++; if (h >= 24) p24++; if (h >= 168) p7d++; } catch {} }
const ledger = (sh(`wc -l < ${LEDGER} 2>/dev/null`).trim() || '0');
let verdict = 'UNKNOWN';
try { const j = JSON.parse(fs.readFileSync(path.join(SKILLS, 'fleet-health-rollup/data/latest.json'), 'utf8')); verdict = j.verdict || j.status || 'UNKNOWN'; } catch {}
// Build category lists + a live "folders" descriptor array grouped for the UI.
const _lists = {
canaries, scrapers, dots, video,
officers, workers,
sk_all: skillDirs.slice().sort(),
ag_all: agentNames.slice().sort(),
ag_workers: workers,
pj_all: projectDirs,
pj_repos: projectRepos.slice().sort(),
parked: parked.slice().sort(),
cronPlists: cronPlists.slice().sort(),
};
const folders = [];
const addCats = (group, names, cats) => {
for (const [kind, label, re] of cats) {
const items = bucket(names, re);
_lists[kind] = items;
if (items.length) folders.push({ group, kind, label, n: items.length });
}
};
addCats('SKILLS', skillDirs, SKILL_CATS);
addCats('AGENTS', agentNames, AGENT_CATS);
addCats('PROJECTS', projectDirs, PROJECT_CATS);
CACHE = {
ts: new Date().toISOString(),
skills: skillDirs.length, canaries: canaries.length, scrapers: scrapers.length,
dots: dots.length, video: video.length, health: +health,
agents: agentFiles.length, officers: officers.length, workers: workers.length,
projects: projectDirs.length, projectRepos: projectRepos.length,
cronLoaded: +cronLoaded, cronPlists: cronPlists.length, cronToSkill,
parked: parked.length, p3, p24, p7d, ledger: +ledger, verdict,
folders,
_lists,
};
CACHE_TS = Date.now();
return CACHE;
}
// ── Recursive drill graph ────────────────────────────────────────────────────
// Generic typed-children endpoint: /api/drill?type=<t>&id=<id>
// Every function returns { title, breadcrumb, children:[{type,id,label,meta}] }.
// Children whose type has a handler are drillable; leaf types (kv/file-leaf) just
// return empty children → the UI shows "(no deeper detail)" gracefully.
const CABINET = path.join(PROJECTS, 'agent-cabinet/cabinet.yaml');
const readText = (p) => { try { return fs.readFileSync(p, 'utf8'); } catch { return ''; } };
const existsP = (p) => { try { return fs.existsSync(p); } catch { return false; } };
const isDirP = (p) => { try { return fs.statSync(p).isDirectory(); } catch { return false; } };
const mtimeP = (p) => { try { return fs.statSync(p).mtimeMs; } catch { return 0; } };
const sizeP = (p) => { try { return fs.statSync(p).size; } catch { return 0; } };
const fmtSize = (n) => n < 1024 ? n + 'B' : n < 1048576 ? (n / 1024).toFixed(1) + 'K' : (n / 1048576).toFixed(1) + 'M';
const shq = (s) => "'" + String(s).replace(/'/g, "'\\''") + "'"; // single-quote for shell
const lines = (s) => s.split('\n').map(x => x.trim()).filter(Boolean);
const base = (p) => path.basename(p);
const DRILL = new Map();
function dcache(key, fn) {
const hit = DRILL.get(key);
if (hit && Date.now() - hit.ts < 15000) return hit.v;
const v = fn();
DRILL.set(key, { ts: Date.now(), v });
return v;
}
function heartbeatKids(dir) {
const hb = path.join(dir, 'data/latest.json');
if (!existsP(hb)) return [];
const out = [{ type: 'kv', id: hb, label: '♥ heartbeat: data/latest.json', meta: new Date(mtimeP(hb)).toLocaleString() }];
try {
const j = JSON.parse(readText(hb));
for (const k of Object.keys(j)) {
const v = j[k];
if (v === null || typeof v === 'object') continue;
out.push({ type: 'kv', id: dir + '#' + k, label: k, meta: String(v) });
}
} catch { out.push({ type: 'kv', id: hb + '#err', label: '(unparseable latest.json)', meta: '' }); }
return out;
}
// ── skill "5W" info block: description + what/why/where/who/when ─────────────
// Robust to BOTH SKILL.md shapes: YAML frontmatter (name:/description:) AND the
// bare "# Heading + prose" style. Always returns a filled card — never blank.
const firstSentence = (s) => { const m = String(s || '').match(/^[\s\S]*?[.!?](\s|$)/); return (m ? m[0] : String(s || '')).trim(); };
const sentences = (s) => String(s || '').split(/(?<=[.!?])\s+/).map(x => x.trim()).filter(Boolean);
function parseSkillMd(id) {
const dir = path.join(SKILLS, id);
const md = readText(path.join(dir, 'SKILL.md'));
let name = id, desc = '', body = md;
const fm = md.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
if (fm) {
body = fm[2] || '';
const nm = fm[1].match(/^name:\s*(.+)$/m); if (nm) name = nm[1].trim();
const dm = fm[1].match(/^description:\s*([\s\S]*?)(?:\n[a-zA-Z_-]+:\s|\n*$)/m); if (dm) desc = dm[1].replace(/\s+/g, ' ').trim();
}
if (!desc) { // no frontmatter description — take the first real prose paragraph, stopping at any structural line
const h = body.match(/^#\s+(.+)$/m); if (h) name = h[1].replace(/[*#]/g, '').trim();
const isStruct = (l) => { const t = l.trim(); return t === '' || /^[#>`|]/.test(t) || /^\*\*[^*]+\*\*\s*:/.test(t) || /^(?:CREATE|SELECT|INSERT|UPDATE|DELETE|ALTER|\{|\}|<|\d+[.)])/i.test(t); };
const buf = []; let started = false;
for (const l of body.split('\n')) {
if (!started) { if (isStruct(l) || l.trim().length < 25) continue; started = true; buf.push(l.trim()); }
else { if (isStruct(l)) break; buf.push(l.trim()); if (buf.length >= 4) break; }
}
desc = buf.join(' ').replace(/\*\*/g, '').replace(/\s+/g, ' ').trim();
}
return { dir, name, desc, body };
}
// last time a vendor scraper actually RAN. Scrape signals = a crawl/insert/scrape ts.
// crawled_at + created_at COUNT: a new row's created_at means a scraper genuinely found a
// new SKU (the overnight new-products refresh inserts rows without bumping last_scraped,
// e.g. anna-french +22 rows Aug-31), so treating created_at as activity is correct.
// ONLY updated_at is demoted — it moves on ANY bulk edit of EXISTING rows (the Apr-1
// gemini-tagger migration touched 152 tables), so it alone is the "db-touch" fallback.
const SCRAPE_COLS = ['last_scraped', 'scraped_at', 'crawled_at', 'specs_scraped_at', 'imported_at', 'price_updated_at', 'created_at'];
const FALLBACK_COLS = ['updated_at'];
const TS_COLS = [...SCRAPE_COLS, ...FALLBACK_COLS];
const gexpr = (cols) => cols.length ? (cols.length > 1 ? 'greatest(' + cols.map(c => c + '::timestamptz').join(',') + ')' : cols[0] + '::timestamptz') : 'null::timestamptz';
const qtbl = (t) => '"' + String(t).replace(/"/g, '') + '"'; // quote table names (digit-leading like w1838 are fine, but be safe)
// ── Authoritative skill→table resolution ────────────────────────────────────
// vendor_registry.catalog_table is the source of truth; SKILL.md-named tables can be
// wrong/stale (e.g. 1838's SKILL.md names 1838_catalog, which doesn't exist — the real
// table is w1838_catalog). Resolution: SKILL.md table IF it exists, else registry match.
let _existTbls = null, _existTs = 0;
function existingCatalogTables() {
if (_existTbls && Date.now() - _existTs < 60000) return _existTbls;
const rows = lines(sh(`psql -h /tmp -d dw_unified -tAc "select table_name from information_schema.tables where table_schema='public' and table_name ~ '_(catalog|colorways?)$' and table_name !~ '(bak|backup|prebackfill|_old|_tmp|_temp|_copy)'" 2>/dev/null`));
_existTbls = new Set(rows); _existTs = Date.now();
return _existTbls;
}
let _reg = null, _regTs = 0;
function vendorRegMap() {
if (_reg && Date.now() - _regTs < 60000) return _reg;
const out = [];
for (const l of lines(sh(`psql -h /tmp -d dw_unified -tAF'|' -c "select lower(vendor_code), lower(coalesce(vendor_name,'')), catalog_table from vendor_registry where catalog_table is not null" 2>/dev/null`))) {
const [code, name, tbl] = l.split('|'); if (!tbl) continue;
out.push({ tbl, keys: [code, name, tbl.replace(/_catalog$/, '')].map(s => (s || '').replace(/[^a-z0-9]/g, '')).filter(Boolean) });
}
_reg = out; _regTs = Date.now();
return _reg;
}
const normSkill = (s) => s.replace(/-scraper-manager$|-colorway-scraper$|-catalog-scraper$|-colorway$|-scraper$/, '').replace(/[^a-z0-9]/g, '');
function resolveVendorTable(skillId, smCandidate) {
const exist = existingCatalogTables();
if (smCandidate && exist.has(smCandidate)) return smCandidate; // SKILL.md table, verified to exist
const k = normSkill(skillId), reg = vendorRegMap();
let m = reg.find(r => r.keys.includes(k) && exist.has(r.tbl)); // exact key
if (!m) { const c = reg.filter(r => r.keys.some(x => x.length >= 5 && (x.startsWith(k) || k.startsWith(x))) && exist.has(r.tbl)); if (c.length === 1) m = c[0]; } // unambiguous prefix only
if (m) return m.tbl;
return (smCandidate && exist.has(smCandidate)) ? smCandidate : null; // never return a non-existent table
}
function lastRunFromParts(sLast, fLast, rows, sd, smin) {
const last = sLast || fLast; if (!last) return null;
const source = sLast ? 'scrape' : (fLast ? 'db-touch' : '');
const days = Math.floor((Date.now() - Date.parse(last.replace(' ', 'T'))) / 86400000);
const age = isNaN(days) ? '' : days === 0 ? 'today' : days === 1 ? '1 day ago' : days + ' days ago';
const n = (sd === '' || sd == null) ? 0 : +sd; // distinct scrape-dates recorded in the table
const cadence = n >= 2 ? 'cadence' : n === 1 ? 'once' : 'never'; // cadence=has re-scraped · once=onboard-only · never=no true scrape
return { last, rows: +rows, days: isNaN(days) ? null : days, age, source, scrapeDates: n, firstScrape: smin || '', cadence };
}
function tableLastRun(tbl) {
if (!/^[a-z0-9_]+$/.test(tbl || '')) return null;
const q1 = `select column_name from information_schema.columns where table_name='${tbl}' and column_name in (${TS_COLS.map(c => `'${c}'`).join(',')})`;
const cols = lines(sh(`psql -h /tmp -d dw_unified -tAc ${shq(q1)} 2>/dev/null`));
if (!cols.length) return null;
const sc = cols.filter(c => SCRAPE_COLS.includes(c)), fc = cols.filter(c => FALLBACK_COLS.includes(c));
const q2 = `select coalesce(to_char(max(${gexpr(sc)}),'YYYY-MM-DD HH24:MI'),'') || '|' || coalesce(to_char(max(${gexpr(fc)}),'YYYY-MM-DD HH24:MI'),'') || '|' || count(*) || '|' || count(distinct (${gexpr(sc)})::date) || '|' || coalesce(to_char(min(${gexpr(sc)}),'YYYY-MM-DD'),'') from ${qtbl(tbl)}`;
const out = sh(`psql -h /tmp -d dw_unified -tAc ${shq(q2)} 2>/dev/null`).trim();
if (!out) return null;
const [sLast, fLast, rows, sd, smin] = out.split('|');
const lr = lastRunFromParts(sLast, fLast, rows, sd, smin);
return lr ? { ...lr, tbl } : null;
}
// ── "date last run" for agents/officers/skills/projects ──────────────────────
// HARD RULE (per CLAUDE.md TK-11431): a last-run must come from a TRUE execution
// signal, never a fabricated one. A skill-DIR mtime moves when the skill is EDITED,
// not when it RUNS — so it's a db-touch lie and is never used here. When no genuine
// run signal exists we say so explicitly ("never recorded") rather than invent a date.
const ageOf = (iso) => { const t = Date.parse(iso); if (isNaN(t)) return ''; const d = Math.floor((Date.now() - t) / 86400000); return d <= 0 ? 'today' : d === 1 ? '1 day ago' : d + ' days ago'; };
const fmtWhen = (iso) => { const d = new Date(iso); return isNaN(d.getTime()) ? String(iso) : d.toLocaleString(); };
// newest matching line in an append-only, chronological JSONL where "agent":"<name>"
// (both events.jsonl and ledger.jsonl are append-ordered, so the LAST match is newest)
function newestAgentEntry(file, name) {
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(name)) return null; // names are filenames — guard grep -F against newlines/meta
const line = sh(`grep -F ${shq('"agent":"' + name + '"')} ${shq(file)} 2>/dev/null | tail -1`).trim();
if (!line) return null;
try { const j = JSON.parse(line); return j.ts ? { ts: j.ts, ticket: j.ticket || j.id || '', what: j.action || j.title || j.text || j.type || '' } : null; }
catch { return null; }
}
// last time an agent/officer actually RAN = newest ticket-event OR reversible-ledger entry under its name
function agentLastRun(name) {
const cands = [];
const ev = newestAgentEntry(TICKETS, name); if (ev) cands.push({ src: 'ticket', ...ev });
const lg = newestAgentEntry(LEDGER, name); if (lg) cands.push({ src: 'ledger', ...lg });
if (!cands.length) return 'never recorded — no ticket or ledger activity logged under this agent';
cands.sort((a, b) => Date.parse(b.ts) - Date.parse(a.ts));
const c = cands[0];
const snip = String(c.what || '').replace(/\s+/g, ' ').slice(0, 80);
return fmtWhen(c.ts) + ' (' + ageOf(c.ts) + ') · via ' + c.src + (c.ticket ? ' ' + c.ticket : '') + (snip ? ' — ' + snip : '');
}
function agentMdDesc(id) {
const md = readText(path.join(AGENTS, id + '.md'));
const fm = md.match(/^---\n([\s\S]*?)\n---/);
if (fm) { const dm = fm[1].match(/^description:\s*([\s\S]*?)(?:\n[a-zA-Z_-]+:\s|\n*$)/m); if (dm) return dm[1].replace(/\s+/g, ' ').trim(); }
return '';
}
function agentInfo(id) {
const desc = agentMdDesc(id);
return {
description: desc || '(' + (id.startsWith('vp-') ? 'cabinet officer' : 'worker agent') + ' ' + id + ')',
who: id.startsWith('vp-') ? 'cabinet officer (vp-*) — routes + signs off gated work' : 'worker agent — owns & supervises, recruits subagents',
when: 'on-demand (invoked via Agent tool / officer routing)',
lastRun: agentLastRun(id),
};
}
function skillInfo(id) {
const { dir, name, desc, body } = parseSkillMd(id);
const hay = desc + ' \n' + body.slice(0, 4000);
// WHERE — vendor domains, staging tables, SKU prefix, target systems, dir
const domains = [...new Set([...hay.matchAll(/\b([a-z0-9][a-z0-9-]*\.(?:com|co\.uk|fr|net|org|io|us|design))\b/gi)].map(m => m[1].toLowerCase()))]
.filter(d => !/\.(?:js|md|json|sh|mjs|py)$/.test(d) && !/^com\.steve/.test(d)).slice(0, 4);
const tables = [...new Set([...hay.matchAll(/\b([a-z0-9][a-z0-9_]*_(?:catalog|colorways|pricing|registry))\b/g)].map(m => m[1]))].slice(0, 4);
const prefix = (hay.match(/\bDW[A-Z]{2,4}-/) || [])[0] || '';
const systems = ['Shopify', 'FileMaker', 'dw_unified', 'Merchant Center', 'Postgres', 'Browserbase'].filter(s => new RegExp(s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i').test(hay));
let scripts = [];
try { scripts = fs.readdirSync(dir).filter(f => /\.(?:mjs|js|py|sh)$/.test(f)).slice(0, 6); } catch {}
const whereBits = [];
if (domains.length) whereBits.push('source ' + domains.join(', '));
if (tables.length) whereBits.push('tables ' + tables.join(', '));
if (prefix) whereBits.push('SKU ' + prefix);
if (systems.length) whereBits.push('writes ' + systems.join(' · '));
if (scripts.length) whereBits.push('scripts ' + scripts.join(', '));
whereBits.push('~/.claude/skills/' + id);
// WHO — referenced-by (cron plists · agents · sibling skills)
const refP = lines(sh(`grep -lF ${shq(id)} ${LA}/com.steve.*.plist 2>/dev/null`)).map(base);
const refA = lines(sh(`grep -lF ${shq(id)} ${AGENTS}/*.md 2>/dev/null`)).map(f => base(f).replace(/\.md$/, ''));
const refS = lines(sh(`grep -rlF ${shq(id)} ${SKILLS}/*/SKILL.md 2>/dev/null`)).map(f => base(path.dirname(f))).filter(n => n !== id);
const whoBits = [];
if (refA.length) whoBits.push(refA.length + ' agent' + (refA.length > 1 ? 's' : '') + ' (' + refA.slice(0, 4).join(', ') + (refA.length > 4 ? '…' : '') + ')');
if (refP.length) whoBits.push(refP.length + ' cron plist' + (refP.length > 1 ? 's' : ''));
if (refS.length) whoBits.push(refS.length + ' skill' + (refS.length > 1 ? 's' : ''));
// WHEN — cadence from a driving plist, else on-demand
let when = 'on-demand (invoked via Skill tool)';
if (refP.length) {
const xml = readText(path.join(LA, refP[0]));
const iv = xml.match(/<key>StartInterval<\/key>\s*<integer>(\d+)/);
const cal = /StartCalendarInterval/.test(xml);
if (iv) { const s = +iv[1]; when = s >= 3600 ? 'every ' + (s / 3600) + 'h (cron)' : s >= 60 ? 'every ' + (s / 60) + 'm (cron)' : 'every ' + s + 's (cron)'; }
else if (cal) { const hrs = [...xml.matchAll(/<key>Hour<\/key>\s*<integer>(\d+)/g)].map(x => x[1]); when = 'daily' + (hrs.length ? ' @ ' + hrs.join(',') + 'h' : '') + ' (cron)'; }
else when = 'cron (' + refP[0].replace(/\.plist$/, '') + ')';
}
// WHY — pull rationale/trigger sentences from the description
const why = sentences(desc).filter(s => /\b(use when|use proactively|born|catch|guard|because|so that|must never|never customer-facing|enforce|ensure|prevent|keeps?|refuses?|private label)\b/i.test(s)).slice(0, 2).join(' ')
|| sentences(desc).slice(1, 3).find(s => s.length > 25) || firstSentence(desc) || '—';
// WHAT — one-line summary
const what = firstSentence(desc) || desc || '—';
// LAST RUN — newest scrape timestamp in the vendor's staging table (scrapers only)
const vendorTbl = resolveVendorTable(id, tables.find(t => /_catalog$/.test(t)) || tables[0]);
const lr = vendorTbl ? tableLastRun(vendorTbl) : null;
const hist = lr ? (lr.cadence === 'cadence' ? ' · ' + lr.scrapeDates + ' scrapes since ' + lr.firstScrape + ' (cadence lapsed)' : lr.cadence === 'once' ? ' · ⚠ scraped ONCE at onboarding, never refreshed' : ' · ⚠ no true scrape ever (db-touch only)') : '';
let lastRun;
if (lr) {
lastRun = lr.last + (lr.age ? ' (' + lr.age + ')' : '') + ' · ' + Number(lr.rows).toLocaleString() + ' rows · ' + lr.tbl + hist;
} else {
// no vendor table → next-strongest TRUE run signal, else say so honestly (never the dir mtime)
const hb = path.join(dir, 'data/latest.json');
if (existsP(hb)) {
// A file mtime is a db-touch lie (git pull / rsync -a / tar -x all rewrite it without the skill running).
// Prefer a TRUE timestamp field written INSIDE the heartbeat; fall back to mtime ONLY when none exists,
// and label that fallback as unverified so it is never mistaken for a real run signal. (Cody TK-12015; [[liveness-artifact-must-follow-the-side-effect]])
let ts = '', hbKey = '';
try {
const j = JSON.parse(readText(hb));
for (const k of ['ts', 'timestamp', 'generated_at', 'generatedAt', 'checked_at', 'checkedAt', 'run_at', 'runAt', 'last_run', 'lastRun', 'ranAt', 'asof', 'date', 'completed_at']) {
if (j && j[k] != null && !isNaN(Date.parse(String(j[k])))) { ts = new Date(j[k]).toISOString(); hbKey = k; break; }
}
} catch {}
if (ts) { lastRun = 'heartbeat ' + fmtWhen(ts) + ' (' + ageOf(ts) + ') · from "' + hbKey + '" in data/latest.json'; }
else { const m = new Date(mtimeP(hb)).toISOString(); lastRun = 'data/latest.json present · file mtime ' + fmtWhen(m) + ' (' + ageOf(m) + ') · ⚠ mtime-only, UNVERIFIED — no timestamp field inside the heartbeat (a git/rsync touch would move this)'; }
}
else if (refP.length) { lastRun = 'driven by cron ' + refP[0].replace(/\.plist$/, '') + ' — no per-run timestamp recorded'; }
else { lastRun = 'on-demand — no local run record kept'; }
}
return {
description: desc || '(no description found in SKILL.md)',
what, why,
who: whoBits.length ? whoBits.join(' · ') : 'nothing references it (pure on-demand)',
where: whereBits.join(' · '),
when, lastRun, name,
};
}
function catChildType(kind) {
if (kind === 'canaries' || kind === 'sk_canary') return 'canary';
if (kind === 'officers' || kind === 'ag_officers') return 'officer';
if (kind === 'cronPlists') return 'plist';
if (kind === 'parked') return 'parked';
if (kind.startsWith('ag_') || kind === 'workers' || kind === 'ag_workers') return 'agent';
if (kind.startsWith('pj_') || kind === 'projectRepos') return 'project';
return 'skill'; // sk_*, scrapers, dots, video, sk_all + default
}
const DRILLERS = {
cat(id) {
const s = scan();
const items = (s._lists[id] || []).slice().sort();
const ct = catChildType(id);
return {
title: 'folder · ' + id + ' (' + items.length + ')',
breadcrumb: id,
children: items.map(it => ({ type: ct, id: it, label: it })),
};
},
skill(id) {
const dir = path.join(SKILLS, id);
if (!existsP(dir)) return { title: 'skill · ' + id, breadcrumb: id, children: [{ type: 'kv', id, label: '(no such skill dir)', meta: '' }] };
const children = [{ type: 'refby', id, label: '⇦ referenced-by (plists · agents · skills)', meta: 'who names this skill' }];
children.push(...heartbeatKids(dir));
let entries = [];
try { entries = fs.readdirSync(dir).filter(f => !f.startsWith('.')).sort(); } catch {}
for (const e of entries) {
const abs = path.join(dir, e);
children.push({ type: 'file', id: abs, label: e + (isDirP(abs) ? '/' : ''), meta: isDirP(abs) ? 'dir' : fmtSize(sizeP(abs)) });
}
return { title: 'skill · ' + id, breadcrumb: id, info: skillInfo(id), children };
},
refby(id) {
const children = [];
const seen = new Set();
const gp = sh(`grep -lF ${shq(id)} ${LA}/com.steve.*.plist 2>/dev/null`);
for (const f of lines(gp)) { const b = base(f); if (!seen.has('p' + b)) { seen.add('p' + b); children.push({ type: 'plist', id: b, label: b, meta: 'launchd' }); } }
const ga = sh(`grep -lF ${shq(id)} ${AGENTS}/*.md 2>/dev/null`);
for (const f of lines(ga)) { const b = base(f).replace(/\.md$/, ''); if (!seen.has('a' + b)) { seen.add('a' + b); children.push({ type: 'agent', id: b, label: b, meta: 'agent' }); } }
const gs = sh(`grep -rlF ${shq(id)} ${SKILLS}/*/SKILL.md 2>/dev/null`);
for (const f of lines(gs)) { const nm = base(path.dirname(f)); if (nm !== id && !seen.has('s' + nm)) { seen.add('s' + nm); children.push({ type: 'skill', id: nm, label: nm, meta: 'skill' }); } }
return { title: 'referenced-by · ' + id + ' (' + children.length + ')', breadcrumb: 'refby/' + id, children };
},
file(id) {
if (isDirP(id)) {
let entries = [];
try { entries = fs.readdirSync(id).filter(f => !f.startsWith('.')).sort(); } catch {}
return {
title: 'dir · ' + base(id), breadcrumb: id,
children: entries.map(e => { const abs = path.join(id, e); return { type: 'file', id: abs, label: e + (isDirP(abs) ? '/' : ''), meta: isDirP(abs) ? 'dir' : fmtSize(sizeP(abs)) }; }),
};
}
const txt = readText(id);
const nl = txt ? txt.split('\n').length : 0;
const children = [{ type: 'kv', id: id + '#size', label: 'size', meta: fmtSize(sizeP(id)) }, { type: 'kv', id: id + '#lines', label: 'lines', meta: String(nl) }, { type: 'kv', id: id + '#mtime', label: 'modified', meta: new Date(mtimeP(id)).toLocaleString() }];
txt.split('\n').slice(0, 12).forEach((ln, i) => { if (ln.trim()) children.push({ type: 'kv', id: id + '#L' + i, label: String(i + 1).padStart(3), meta: ln.slice(0, 120) }); });
return { title: 'file · ' + base(id), breadcrumb: id, children };
},
plist(id) {
if (!/\.plist$/.test(id)) id += '.plist';
const p = path.join(LA, id);
const xml = readText(p);
const children = [];
const m = xml.match(/\/skills\/([^\/<"' ]+)/);
if (m) children.push({ type: 'skill', id: m[1], label: 'skill: ' + m[1], meta: 'invokes' });
const scripts = [...xml.matchAll(/<string>([^<]*\.(?:sh|mjs|js|py))<\/string>/g)].map(x => x[1]);
for (const s of scripts) children.push({ type: 'file', id: s, label: '▶ ' + base(s), meta: s });
const iv = xml.match(/<key>StartInterval<\/key>\s*<integer>(\d+)/);
if (iv) children.push({ type: 'kv', id: id + '#iv', label: 'StartInterval', meta: iv[1] + 's' });
if (/StartCalendarInterval/.test(xml)) {
const hrs = [...xml.matchAll(/<key>Hour<\/key>\s*<integer>(\d+)/g)].map(x => x[1]).join(', ');
children.push({ type: 'kv', id: id + '#cal', label: 'StartCalendarInterval', meta: hrs ? 'hours ' + hrs : 'calendar' });
}
const label = id.replace(/\.plist$/, '');
const ll = sh(`launchctl list 2>/dev/null | grep -F ${shq(label)}`).trim();
if (ll) { const parts = ll.split(/\s+/); children.push({ type: 'kv', id: id + '#pid', label: 'launchctl pid', meta: parts[0] }); children.push({ type: 'kv', id: id + '#exit', label: 'last exit status', meta: parts[1] }); }
else children.push({ type: 'kv', id: id + '#unloaded', label: 'launchctl', meta: 'not loaded' });
return { title: 'plist · ' + label, breadcrumb: 'plist/' + label, children };
},
officer(id) {
const children = [];
const seen = new Set();
// parse cabinet.yaml block for this vp
const y = readText(CABINET).split('\n');
let inBlock = false;
for (const raw of y) {
const t = raw.trim();
const vpM = raw.match(/^\s*-\s*vp:\s*(\S+)/);
if (vpM) { inBlock = vpM[1] === id; continue; }
if (!inBlock) continue;
const sub = t.match(/^-?\s*subagent:\s*(\S+)/);
const sk = t.match(/^-?\s*skill:\s*(\S+)/);
if (sub && !seen.has('a' + sub[1])) { seen.add('a' + sub[1]); children.push({ type: 'agent', id: sub[1], label: sub[1], meta: 'subagent' }); }
if (sk && !seen.has('s' + sk[1])) { seen.add('s' + sk[1]); children.push({ type: 'skill', id: sk[1], label: sk[1], meta: 'skill' }); }
}
// officer .md "Delegates to" line
const md = readText(path.join(AGENTS, id + '.md'));
const del = md.match(/Delegates to ([^.]+)\./i);
if (del) for (const nm of del[1].split(/,|and/).map(x => x.trim().replace(/\s+subagents?$/, '')).filter(Boolean)) {
if (existsP(path.join(AGENTS, nm + '.md')) && !seen.has('a' + nm)) { seen.add('a' + nm); children.push({ type: 'agent', id: nm, label: nm, meta: 'delegate' }); }
}
return { title: 'officer · ' + id + ' (' + children.length + ')', breadcrumb: 'officer/' + id, info: agentInfo(id), children };
},
agent(id) {
const md = readText(path.join(AGENTS, id + '.md'));
if (!md) return { title: 'agent · ' + id, breadcrumb: id, info: { description: '(no agent .md found — may be a skill or renamed)', lastRun: agentLastRun(id) }, children: [{ type: 'kv', id, label: '(no such agent .md)', meta: '' }] };
const children = [];
const tools = md.match(/^tools:\s*(.+)$/m);
if (tools) children.push({ type: 'kv', id: id + '#tools', label: 'tools', meta: tools[1].trim() });
const names = new Set([...md.matchAll(/skills\/([a-z0-9][a-z0-9-]+)/gi)].map(x => x[1]));
for (const nm of names) if (existsP(path.join(SKILLS, nm))) children.push({ type: 'skill', id: nm, label: 'skill: ' + nm, meta: 'invokes' });
if (id.startsWith('vp-')) { const off = DRILLERS.officer(id); for (const c of off.children) children.push(c); }
return { title: 'agent · ' + id + ' (' + children.length + ')', breadcrumb: 'agent/' + id, info: agentInfo(id), children };
},
canary(id) {
const dir = path.join(SKILLS, id);
const children = heartbeatKids(dir);
const gp = sh(`grep -lF ${shq(id)} ${LA}/com.steve.*.plist 2>/dev/null`);
for (const f of lines(gp)) children.push({ type: 'plist', id: base(f), label: '⏰ ' + base(f), meta: 'cron driver' });
if (!children.length) children.push({ type: 'kv', id, label: '(no heartbeat / no cron found)', meta: '' });
return { title: 'canary · ' + id, breadcrumb: 'canary/' + id, info: skillInfo(id), children };
},
project(id) {
const dir = path.join(PROJECTS, id);
const children = [];
const isRepo = existsP(path.join(dir, '.git'));
children.push({ type: 'kv', id: id + '#git', label: 'git repo', meta: isRepo ? 'yes' : 'no' });
// LAST RUN — for a project the true "last activity" signal is the newest git commit (never dir mtime)
let headSubj = '', lastRun;
if (isRepo) {
const ci = sh(`cd ${shq(dir)} && git log -1 --format='%cI' 2>/dev/null`).trim();
headSubj = sh(`cd ${shq(dir)} && git log -1 --format='%h %s' 2>/dev/null`).trim();
if (headSubj) children.push({ type: 'kv', id: id + '#head', label: 'HEAD', meta: headSubj });
lastRun = ci ? 'git commit ' + fmtWhen(ci) + ' (' + ageOf(ci) + ')' + (headSubj ? ' · ' + headSubj : '') : 'git repo — no commits yet';
} else { lastRun = 'not a git repo — no commit history'; }
let entries = [];
try { entries = fs.readdirSync(dir).filter(f => !f.startsWith('.') && f !== 'node_modules').sort().slice(0, 60); } catch {}
for (const e of entries) { const abs = path.join(dir, e); children.push({ type: 'file', id: abs, label: e + (isDirP(abs) ? '/' : ''), meta: isDirP(abs) ? 'dir' : fmtSize(sizeP(abs)) }); }
return { title: 'project · ' + id, breadcrumb: 'project/' + id, info: { description: headSubj || ('project ' + id), who: '~/Projects/' + id, lastRun }, children };
},
parked(id) {
const p = path.join(PENDING, id);
const txt = readText(p);
const children = [{ type: 'kv', id: id + '#age', label: 'modified', meta: new Date(mtimeP(p)).toLocaleString() }];
txt.split('\n').slice(0, 40).forEach((ln, i) => { if (ln.trim()) children.push({ type: 'kv', id: id + '#L' + i, label: String(i + 1).padStart(3), meta: ln.slice(0, 140) }); });
if (children.length === 1) children.push({ type: 'kv', id, label: '(empty memo)', meta: '' });
return { title: 'parked · ' + id, breadcrumb: 'parked/' + id, children };
},
kv(id) { return { title: id, breadcrumb: id, children: [] }; },
};
// ── Scraper staleness ranking: every scraper → newest timestamp in its staging table ──
// Skill→table resolved from SKILL.md (no DB); then just 2 batched psql calls for all tables.
const prettyTbl = (t) => t.replace(/_catalog$|_colorways?$/, '').replace(/_/g, ' ');
const prettySkill = (s) => s.replace(/-scraper-manager$|-scraper$/, '');
// ── Scraper healthcheck status (verified / broken + failure reason) ─────────
const STATUS_JSON = path.join(PROJECTS, 'Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL/data/scraper-audit/scraper-status.json');
const nkey = (s) => (s || '').toLowerCase().replace(/-scraper-manager$|-scraper$|_catalog$|_colorways?$/, '').replace(/[^a-z0-9]/g, '');
let _hc = null, _hcTs = 0;
function healthMap() {
if (_hc && Date.now() - _hcTs < 60000) return _hc;
const out = [];
try {
const S = (JSON.parse(readText(STATUS_JSON)).scrapers) || {};
for (const k of Object.keys(S)) { const e = S[k] || {}; out.push([k.toLowerCase().replace(/[^a-z0-9]/g, ''), { status: e.status || '', note: (e.notes || e.lastError || e.error || '').toString().replace(/^healthcheck[^—-]*[—-]\s*/, '').replace(/\s+/g, ' ').trim().slice(0, 140) }]); }
} catch {}
_hc = out; _hcTs = Date.now(); return _hc;
}
function healthOf(keys) {
const hm = healthMap(); const ks = keys.filter(Boolean);
const fz = (k, x) => k === x || (k.length >= 5 && x.length >= 5 && (x.startsWith(k) || k.startsWith(x) || x.includes(k) || k.includes(x)));
for (const [hk, v] of hm) if (ks.some(k => fz(k, hk))) return v;
return null;
}
// freshness stats for a set of tables, CHUNKED so 200+ tables stay under the 15s shell timeout
function statsForTables(tbls) {
const stats = {}; const CH = 60;
for (let i = 0; i < tbls.length; i += CH) {
const chunk = tbls.slice(i, i + CH);
const q1 = `select table_name, string_agg(column_name, ',') from information_schema.columns where table_name in (${chunk.map(t => `'${t}'`).join(',')}) and column_name in (${TS_COLS.map(c => `'${c}'`).join(',')}) group by table_name`;
const colsByTbl = {};
for (const r of lines(sh(`psql -h /tmp -d dw_unified -tAF'|' -c ${shq(q1)} 2>/dev/null`))) { const [t, cs] = r.split('|'); if (t && cs) colsByTbl[t] = cs.split(','); }
const parts = [];
for (const t of chunk) {
const cs = colsByTbl[t]; if (!cs || !cs.length) continue;
const sc = cs.filter(c => SCRAPE_COLS.includes(c)), fc = cs.filter(c => FALLBACK_COLS.includes(c));
parts.push(`select '${t}' t, coalesce(to_char(max(${gexpr(sc)}),'YYYY-MM-DD HH24:MI'),'') s, coalesce(to_char(max(${gexpr(fc)}),'YYYY-MM-DD HH24:MI'),'') f, count(*) n, count(distinct (${gexpr(sc)})::date) sd, coalesce(to_char(min(${gexpr(sc)}),'YYYY-MM-DD'),'') smin from ${qtbl(t)}`);
}
if (parts.length) for (const r of lines(sh(`psql -h /tmp -d dw_unified -tAF'|' -c ${shq(parts.join(' union all '))} 2>/dev/null`))) {
const [t, s, f, n, sd, smin] = r.split('|'); stats[t] = lastRunFromParts(s, f, n, sd, smin);
}
}
return stats;
}
function scraperStaleness() {
return dcache('scraper-staleness', () => {
const exist = existingCatalogTables(); // every catalog/colorway table (the full vendor universe)
const nameByTbl = {}; // authoritative vendor name per table
for (const l of lines(sh(`psql -h /tmp -d dw_unified -tAF'|' -c "select catalog_table, coalesce(nullif(vendor_name,''),vendor_code) from vendor_registry where catalog_table is not null" 2>/dev/null`))) { const [t, nm] = l.split('|'); if (t && nm && !nameByTbl[t]) nameByTbl[t] = nm; }
const entries = {}; // keyed by table
for (const t of exist) entries[t] = { tbl: t, name: nameByTbl[t] || prettyTbl(t), skill: null };
// attach a dedicated skill (for click-to-card) where one maps to the table
const skillOnly = [];
for (const s of bucket(lsdirs(SKILLS), /scraper-manager$|-scraper$/)) {
const { body, desc } = parseSkillMd(s);
const m = (desc + ' ' + body.slice(0, 4000)).match(/\b([a-z0-9][a-z0-9_]*_(?:catalog|colorways))\b/);
const tbl = resolveVendorTable(s, m ? m[1] : null);
if (tbl && entries[tbl]) entries[tbl].skill = entries[tbl].skill || s;
else if (tbl) entries[tbl] = { tbl, name: nameByTbl[tbl] || prettyTbl(tbl), skill: s };
else skillOnly.push({ name: prettySkill(s), skill: s }); // non-vendor scraper skill (feed-first, fineartamerica…)
}
const stats = statsForTables(Object.keys(entries));
const mk = (e, st) => { const hv = healthOf([nkey(e.tbl), nkey(e.skill), nkey(e.name)]); return { name: e.name, skill: e.skill || '', tbl: e.tbl || '', last: st ? st.last : '', rows: st ? st.rows : null, days: st && st.days != null ? st.days : null, source: st ? st.source : '', cadence: st ? st.cadence : '', scrapeDates: st ? st.scrapeDates : 0, firstScrape: st ? st.firstScrape : '', health: hv ? (hv.status || '') : '', healthNote: hv ? hv.note : '' }; };
const rows = Object.values(entries).map(e => mk(e, stats[e.tbl]));
for (const so of skillOnly) rows.push(mk(so, null));
rows.sort((a, b) => a.days == null && b.days == null ? a.name.localeCompare(b.name) : a.days == null ? 1 : b.days == null ? -1 : b.days - a.days);
const stale30 = rows.filter(r => r.days != null && r.days > 30).length;
const nodata = rows.filter(r => r.days == null).length;
const onceOnly = rows.filter(r => r.cadence === 'once').length;
const neverScraped = rows.filter(r => r.cadence === 'never').length;
const withSkill = rows.filter(r => r.skill).length;
const broken = rows.filter(r => r.health === 'broken').length;
return { ts: new Date().toISOString(), count: rows.length, stale30, nodata, onceOnly, neverScraped, withSkill, broken, rows };
});
}
function drill(type, id) {
const fn = DRILLERS[type];
if (!fn) return { title: type + ' · ' + id, breadcrumb: type + '/' + id, children: [], error: 'unknown type ' + type };
return fn(id);
}
const PAGE = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');
const server = http.createServer((req, res) => {
if (req.headers.authorization !== AUTH) {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="stack-map"' }); return res.end('auth required');
}
const u = new URL(req.url, 'http://x');
if (u.pathname === '/api/stats') {
const s = scan();
const { _lists, ...pub } = s; // don't ship every list on every poll; drill fetches lists on demand
res.writeHead(200, { 'content-type': 'application/json' }); return res.end(JSON.stringify(pub));
}
if (u.pathname === '/api/list') {
const kind = u.searchParams.get('kind'); const s = scan();
res.writeHead(200, { 'content-type': 'application/json' });
return res.end(JSON.stringify({ kind, items: (s._lists[kind] || []).slice().sort() }));
}
if (u.pathname === '/api/scrapers') {
res.writeHead(200, { 'content-type': 'application/json' });
return res.end(JSON.stringify(scraperStaleness()));
}
if (u.pathname === '/api/drill') {
const type = u.searchParams.get('type') || '';
const id = u.searchParams.get('id') || '';
let out;
try { out = dcache(type + '|' + id, () => drill(type, id)); }
catch (e) { out = { title: type + ' · ' + id, breadcrumb: type + '/' + id, children: [], error: String(e && e.message || e) }; }
res.writeHead(200, { 'content-type': 'application/json' });
return res.end(JSON.stringify(out));
}
res.writeHead(200, { 'content-type': 'text/html' }); res.end(PAGE);
});
const PORT = process.env.PORT || 9768;
server.listen(PORT, () => console.log(`stack-map viewer → http://127.0.0.1:${PORT} (admin/DW2024!)`));