mirror of
https://github.com/xx254/linkedin_skills.git
synced 2026-06-10 23:44:56 +03:00
128 lines
4.2 KiB
JavaScript
128 lines
4.2 KiB
JavaScript
#!/usr/bin/env node
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const { classifyProspectReply } = require("./lib/reply-classify");
|
|
const { readJson, writeJson, ensureDir, formatDateKey } = require("./lib/io");
|
|
const { ensureWeekMetrics } = require("./lib/week-metrics");
|
|
|
|
const ROOT = path.resolve(__dirname, "..");
|
|
|
|
function getArgValue(flag) {
|
|
const index = process.argv.findIndex((a) => a === flag);
|
|
if (index === -1 || index === process.argv.length - 1) {
|
|
return "";
|
|
}
|
|
return process.argv[index + 1];
|
|
}
|
|
|
|
const leadKeyArg = getArgValue("--lead-key");
|
|
const messageArg = getArgValue("--message");
|
|
const messageFile = getArgValue("--message-file");
|
|
|
|
if (!leadKeyArg) {
|
|
console.error("Usage: node scripts/run-reply-handler.js --lead-key <key> (--message \"...\" | --message-file path)");
|
|
process.exit(1);
|
|
}
|
|
|
|
let prospectText = messageArg;
|
|
if (messageFile) {
|
|
const p = path.resolve(process.cwd(), messageFile);
|
|
if (!fs.existsSync(p)) {
|
|
console.error(`Message file not found: ${p}`);
|
|
process.exit(1);
|
|
}
|
|
prospectText = fs.readFileSync(p, "utf8");
|
|
}
|
|
|
|
if (!prospectText || !String(prospectText).trim()) {
|
|
console.error("Prospect message is empty. Use --message or --message-file.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const state = readJson(path.join(ROOT, "state", "linkedin-system-state.json"), null);
|
|
if (!state) {
|
|
console.error("Missing state. Run: node scripts/bootstrap-system.js");
|
|
process.exit(1);
|
|
}
|
|
|
|
ensureWeekMetrics(state);
|
|
|
|
const leadMeta = state.leadRegistry && state.leadRegistry[leadKeyArg];
|
|
const firstName =
|
|
leadMeta && leadMeta.name ? leadMeta.name.split(" ")[0].replace(/[(),]/g, "") : "";
|
|
|
|
const { type: replyType, confidence } = classifyProspectReply(prospectText);
|
|
|
|
function buildDraft(rt, first) {
|
|
const prefix = first ? `${first}, ` : "";
|
|
switch (rt) {
|
|
case "interested":
|
|
return `${prefix}Thanks for writing back. What would be most useful to clarify first about how you're thinking about this?`;
|
|
case "hesitant":
|
|
return `${prefix}Makes sense. What would need to be true for this to feel worth a short look?`;
|
|
case "competitor_mention":
|
|
return `${prefix}Got it. Is that setup covering the outcome you care about here, or is there still a gap?`;
|
|
case "positive_but_vague":
|
|
return `${prefix}Happy to go deeper. Want to do a 15-minute pass with a clear agenda so it stays useful?`;
|
|
case "not_interested":
|
|
return `${prefix}Thanks for letting me know. I'll step back here. If anything changes, you're welcome to reach out.`;
|
|
case "question":
|
|
return `${prefix}Good question. Here's the direct answer: [fill in from your offer]. If you want, we can also sanity-check fit on a quick call.`;
|
|
case "irrelevant":
|
|
default:
|
|
return `${prefix}Thanks for the note. To stay useful: are you still open to a quick exchange on [topic from your thread]?`;
|
|
}
|
|
}
|
|
|
|
const draft = buildDraft(replyType, firstName).trim();
|
|
const now = new Date().toISOString();
|
|
const dateKey = formatDateKey();
|
|
const outDir = path.join(ROOT, "output", `replies_${dateKey}`);
|
|
ensureDir(outDir);
|
|
|
|
const safeKey = leadKeyArg.replace(/[^a-z0-9_|-]/gi, "_").slice(0, 120);
|
|
const artifactPath = path.join(outDir, `reply_${safeKey}_${dateKey}.json`);
|
|
|
|
const artifact = {
|
|
lead_key: leadKeyArg,
|
|
generated_at: now,
|
|
prospect_message_excerpt: String(prospectText).slice(0, 2000),
|
|
reply_type: replyType,
|
|
classification_confidence: confidence,
|
|
draft,
|
|
status: "reply_drafted",
|
|
note:
|
|
"Template draft from run-reply-handler.js. Refine in the agent using context/brand-voice.md."
|
|
};
|
|
|
|
writeJson(artifactPath, artifact);
|
|
|
|
state.lastReplyRunAt = now;
|
|
state.lastSuccessfulRunAt = now;
|
|
state.weeklyMetrics.reply_drafts_count = (state.weeklyMetrics.reply_drafts_count || 0) + 1;
|
|
|
|
state.outreachRegistry = state.outreachRegistry || {};
|
|
const prev = state.outreachRegistry[leadKeyArg] || {};
|
|
state.outreachRegistry[leadKeyArg] = {
|
|
...prev,
|
|
lastReplyDraftAt: now,
|
|
lastReplyType: replyType,
|
|
lastDraftStatus: "reply_drafted",
|
|
lastProspectSnippet: String(prospectText).slice(0, 280)
|
|
};
|
|
|
|
writeJson(path.join(ROOT, "state", "linkedin-system-state.json"), state);
|
|
|
|
console.log(
|
|
JSON.stringify(
|
|
{
|
|
output: artifactPath,
|
|
lead_key: leadKeyArg,
|
|
reply_type: replyType,
|
|
draft_chars: draft.length
|
|
},
|
|
null,
|
|
2
|
|
)
|
|
);
|