#!/usr/bin/env node const fs = require("fs"); const path = require("path"); const readline = require("readline"); const { readJson, writeJson, ensureDir } = require("./lib/io"); const ROOT = path.resolve(__dirname, ".."); const settingsPath = path.join(ROOT, "state", "linkedin-settings.json"); const statePath = path.join(ROOT, "state", "linkedin-system-state.json"); const defaultSettingsPath = path.join(ROOT, "config", "default-settings.json"); const contextDir = path.join(ROOT, "context"); const aboutMePath = path.join(contextDir, "about-me.md"); const brandVoicePath = path.join(contextDir, "brand-voice.md"); const defaultSettings = readJson(defaultSettingsPath, {}); const currentSettings = readJson(settingsPath, null); const currentState = readJson(statePath, null); if (!currentSettings) { writeJson(settingsPath, defaultSettings); } if (!currentState) { writeJson(statePath, { version: "1.0.0", firstRunCompleted: false, lastSuccessfulRunAt: null, lastLeadFilterRunAt: null, lastDraftRunAt: null, lastReplyRunAt: null, leadRegistry: {}, outreachRegistry: {}, weeklyMetrics: { weekKey: null, leads_processed: 0, qualified_count: 0, drafted_count: 0, reply_drafts_count: 0, replied_count: 0, rejected_count: 0, score3_conversion_count: 0 } }); } ensureDir(path.join(ROOT, "output")); ensureDir(path.join(ROOT, "reports")); ensureDir(contextDir); if (!fs.existsSync(aboutMePath)) { fs.writeFileSync( aboutMePath, "# ICP Profile\n\n## Target Industries\n- Software Development\n- Marketing Services\n\n## Target Seniority\n- Manager\n- Director\n\n## Target Geographies\n- United States\n\n## Offer Summary\nDescribe your offer here.\n", "utf8" ); } if (!fs.existsSync(brandVoicePath)) { fs.writeFileSync( brandVoicePath, "# Brand Voice Profile\n\n## Language\nDefault: English\n\n## Outreach Goal\nStart relevant conversations with qualified leads.\n\n## Formatting Rules\n- Keep first-touch message concise.\n- Max one question.\n- No generic opener.\n", "utf8" ); } function showSampleLeads() { const samplePath = path.join(ROOT, "sample_warm_leads.example.csv"); if (!fs.existsSync(samplePath)) return; const lines = fs.readFileSync(samplePath, "utf8").trim().split("\n"); const rows = lines.slice(1).filter(l => l.trim()); // skip header const preview = rows.slice(0, 5); const remaining = rows.length - preview.length; const pad = (s, n) => String(s).slice(0, n).padEnd(n); console.log("\nHere's a sample of warm leads with signals:\n"); console.log("| # | Name | Title | Company | Signal |"); console.log("|---|------|-------|---------|--------|"); preview.forEach((line, i) => { const cols = line.split(","); const name = `${cols[0].trim()} ${cols[1].trim()}`; const title = cols[2].trim(); const company = cols[4].trim(); const signal = cols[9].trim().replace(/^"|"$/g, "").slice(0, 60); console.log(`| ${i + 1} | ${name} | ${title} | ${company} | ${signal} |`); }); if (remaining > 0) { console.log(`| ... | and ${remaining} more | | | |`); } console.log(""); } const result = { ok: true, settingsInitialized: !currentSettings, stateInitialized: !currentState, root: ROOT }; // Telemetry opt-in — ask once on first setup const freshState = readJson(statePath, {}); if (!freshState.telemetryPrompted) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const ask = (q) => new Promise((resolve) => rl.question(q, resolve)); (async () => { console.log("\n──────────────────────────────────────────"); console.log("Help us improve these skills for your industry."); console.log("Community mode shares anonymous usage data — which skills you run,"); console.log("how many leads processed, signal types that worked."); console.log("No names, no message content, no file paths. Ever."); console.log("Change anytime: set \"telemetry\": \"off\" in state/linkedin-settings.json"); console.log("──────────────────────────────────────────"); const a1 = await ask("\nA) Sure, happy to help improve this\nB) No thanks\n> "); let telemetryLevel; if (a1.trim().toUpperCase() === "A") { telemetryLevel = "community"; } else { const a2 = await ask( "\nHow about anonymous mode? We just learn that someone ran the pipeline —\n" + "no ID, no details. Just a counter that helps us know the tool is useful.\n\n" + "A) Anonymous is fine\nB) No thanks, fully off\n> " ); telemetryLevel = a2.trim().toUpperCase() === "A" ? "anonymous" : "off"; } rl.close(); // Save telemetry choice to settings const settings = readJson(settingsPath, {}); if (!settings.settings) settings.settings = {}; settings.settings.telemetry = telemetryLevel; writeJson(settingsPath, settings); // Mark telemetry as prompted in state const state = readJson(statePath, {}); state.telemetryPrompted = true; writeJson(statePath, state); result.telemetry = telemetryLevel; showSampleLeads(); console.log("\n" + JSON.stringify(result, null, 2)); })(); } else { showSampleLeads(); console.log(JSON.stringify(result, null, 2)); }