@sellable/mcp 0.1.136 → 0.1.138
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/post-find-leads-filter-scout.md +19 -25
- package/agents/post-find-leads-message-scout.md +21 -0
- package/agents/registry.json +9 -16
- package/agents/source-scout-linkedin-engagement.md +2 -2
- package/dist/engage-memory.d.ts +13 -0
- package/dist/engage-memory.js +129 -34
- package/dist/identity-compiler.d.ts +119 -0
- package/dist/identity-compiler.js +344 -0
- package/dist/identity-memory.d.ts +49 -0
- package/dist/identity-memory.js +89 -0
- package/dist/index-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/tools/bootstrap.js +1 -1
- package/dist/tools/engage-memory.js +2 -2
- package/dist/tools/navigation.js +3 -0
- package/dist/tools/prompts.d.ts +1 -4
- package/dist/tools/prompts.js +3 -3
- package/package.json +1 -1
- package/skills/craft-message/SKILL.md +13 -4
- package/skills/create-campaign/references/brief-template.md +2 -1
- package/skills/create-campaign-v2/SKILL.md +150 -1342
- package/skills/create-campaign-v2/core/flow.v2.json +471 -1970
- package/skills/create-post/SKILL.md +54 -9
- package/skills/engage/SKILL.md +37 -15
- package/skills/engage/core/README.md +18 -12
- package/skills/generate-messages/SKILL.md +15 -5
- package/skills/interview/SKILL.md +183 -89
- package/skills/interview/references/anti-ai-audit.md +101 -0
- package/skills/interview/references/compiler-schema.md +94 -0
- package/skills/interview/references/legacy-linkedin-interview.md +118 -0
- package/skills/interview/references/question-bank.md +123 -0
- package/skills/interview/references/reference-curation.md +68 -0
- package/skills/interview/references/voice-capture-method.md +62 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { CORE_MEMORY_PATHS, resolveIdentityConfigsDir, } from "./identity-memory.js";
|
|
4
|
+
export function applyIdentityCompilerProposal(proposal) {
|
|
5
|
+
const configsDir = proposal.configsDir ?? resolveIdentityConfigsDir();
|
|
6
|
+
const changed = new Set();
|
|
7
|
+
for (const section of proposal.generatedSections ?? []) {
|
|
8
|
+
if (upsertGeneratedSection(configsDir, section.relativePath, section.key, section.markdown)) {
|
|
9
|
+
changed.add(section.relativePath);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
upsertBlockEntries(configsDir, CORE_MEMORY_PATHS.proofLedger, "Proof Ledger", proposal.proofEntries, (entry) => stableKey("proof", [entry.key, entry.claim, entry.source]).toLowerCase(), (entry) => renderBlock(`Claim: ${entry.claim.trim()}`, {
|
|
13
|
+
Source: entry.source,
|
|
14
|
+
Confidence: entry.confidence,
|
|
15
|
+
"Allowed use": entry.allowedUse,
|
|
16
|
+
Privacy: entry.privacy,
|
|
17
|
+
Notes: entry.notes,
|
|
18
|
+
}), changed);
|
|
19
|
+
upsertBlockEntries(configsDir, CORE_MEMORY_PATHS.winsLedger, "Wins Ledger", proposal.winsEntries, (entry) => stableKey("win", [entry.key, entry.title, entry.source]), (entry) => renderBlock(`Win: ${entry.title.trim()}`, {
|
|
20
|
+
"Proof type": entry.proofType,
|
|
21
|
+
Outcome: entry.outcome,
|
|
22
|
+
Source: entry.source,
|
|
23
|
+
Permission: entry.permission,
|
|
24
|
+
Confidence: entry.confidence,
|
|
25
|
+
"Public/private safety": entry.publicSafety,
|
|
26
|
+
"Allowed use": entry.allowedUse,
|
|
27
|
+
Notes: entry.notes,
|
|
28
|
+
}), changed);
|
|
29
|
+
upsertBlockEntries(configsDir, CORE_MEMORY_PATHS.storyBank, "Story Bank", proposal.storyEntries, (entry) => stableKey("story", [entry.key, entry.title, entry.source]), (entry) => renderBlock(`Story: ${entry.title.trim()}`, {
|
|
30
|
+
Setup: entry.setup,
|
|
31
|
+
Moment: entry.moment,
|
|
32
|
+
Lesson: entry.lesson,
|
|
33
|
+
Source: entry.source,
|
|
34
|
+
Contexts: entry.contexts,
|
|
35
|
+
Privacy: entry.privacy,
|
|
36
|
+
Notes: entry.notes,
|
|
37
|
+
}), changed);
|
|
38
|
+
upsertBlockEntries(configsDir, CORE_MEMORY_PATHS.answerBank, "Answer Bank", proposal.answerEntries, (entry) => stableKey("answer", [
|
|
39
|
+
entry.key,
|
|
40
|
+
entry.question,
|
|
41
|
+
entry.topicTags,
|
|
42
|
+
entry.source,
|
|
43
|
+
]), (entry) => renderBlock(`Question: ${entry.question.trim()}`, {
|
|
44
|
+
Answer: entry.answer,
|
|
45
|
+
Topics: entry.topicTags,
|
|
46
|
+
Source: entry.source,
|
|
47
|
+
"Use contexts": entry.useContexts,
|
|
48
|
+
Confidence: entry.confidence,
|
|
49
|
+
Privacy: entry.privacy,
|
|
50
|
+
"Derived memory links": entry.derivedMemoryLinks,
|
|
51
|
+
}), changed);
|
|
52
|
+
upsertBlockEntries(configsDir, CORE_MEMORY_PATHS.decisionRules, "Decision Rules", proposal.decisionRules, (entry) => stableKey("rule", [entry.key, entry.title, entry.source]), (entry) => renderBlock(`Rule: ${entry.title.trim()}`, {
|
|
53
|
+
Rule: entry.rule,
|
|
54
|
+
Source: entry.source,
|
|
55
|
+
}), changed);
|
|
56
|
+
upsertBlockEntries(configsDir, CORE_MEMORY_PATHS.antiAiStyle, "Anti-AI Writing Style", proposal.antiAiBans, (entry) => stableKey("anti-ai", [entry.key, entry.pattern, entry.context]), (entry) => renderBlock(`Banned Pattern: ${entry.pattern.trim()}`, {
|
|
57
|
+
Rule: entry.rule,
|
|
58
|
+
Context: entry.context,
|
|
59
|
+
Source: entry.source,
|
|
60
|
+
}), changed);
|
|
61
|
+
upsertBlockEntries(configsDir, CORE_MEMORY_PATHS.changeLog, "Change Log", proposal.changeLogEntries, (entry) => stableKey("change", [entry.key, entry.date, entry.changed, entry.source]), (entry) => renderBlock(`Change: ${entry.date} - ${entry.changed.trim()}`, {
|
|
62
|
+
Source: entry.source,
|
|
63
|
+
"New rule": entry.newRule,
|
|
64
|
+
}), changed);
|
|
65
|
+
if (upsertTableRows(configsDir, "core/transcripts/INDEX.md", "Transcript Index", [
|
|
66
|
+
"Title",
|
|
67
|
+
"Source",
|
|
68
|
+
"Date",
|
|
69
|
+
"Speakers",
|
|
70
|
+
"Target Speaker",
|
|
71
|
+
"Topics",
|
|
72
|
+
"Context Mode",
|
|
73
|
+
"Sample Type",
|
|
74
|
+
"Sensitivity",
|
|
75
|
+
"Key",
|
|
76
|
+
], (proposal.transcriptIndexRows ?? []).map((row) => {
|
|
77
|
+
const key = stableKey("transcript", [
|
|
78
|
+
row.key,
|
|
79
|
+
row.sourcePath,
|
|
80
|
+
row.date,
|
|
81
|
+
row.targetSpeaker,
|
|
82
|
+
row.topics,
|
|
83
|
+
row.title,
|
|
84
|
+
]);
|
|
85
|
+
return {
|
|
86
|
+
key,
|
|
87
|
+
row: {
|
|
88
|
+
Title: row.title,
|
|
89
|
+
Source: row.sourcePath,
|
|
90
|
+
Date: row.date ?? "",
|
|
91
|
+
Speakers: row.speakers ?? "",
|
|
92
|
+
"Target Speaker": row.targetSpeaker ?? "",
|
|
93
|
+
Topics: row.topics ?? "",
|
|
94
|
+
"Context Mode": row.contextMode ?? "",
|
|
95
|
+
"Sample Type": row.sampleType ?? "",
|
|
96
|
+
Sensitivity: row.sensitivity ?? "",
|
|
97
|
+
Key: key,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}))) {
|
|
101
|
+
changed.add("core/transcripts/INDEX.md");
|
|
102
|
+
}
|
|
103
|
+
for (const topic of proposal.topicTranscripts ?? []) {
|
|
104
|
+
const topicSlug = normalizeSlug(topic.topicSlug);
|
|
105
|
+
const relativePath = `core/transcripts/topics/${topicSlug}.md`;
|
|
106
|
+
const key = stableKey("topic", [
|
|
107
|
+
topic.key,
|
|
108
|
+
topicSlug,
|
|
109
|
+
topic.sourcePath,
|
|
110
|
+
topic.targetSpeaker,
|
|
111
|
+
topic.title,
|
|
112
|
+
]);
|
|
113
|
+
const block = renderBlock(`Transcript: ${topic.title.trim()}`, {
|
|
114
|
+
Source: topic.sourcePath,
|
|
115
|
+
"Target speaker": topic.targetSpeaker,
|
|
116
|
+
Excerpt: topic.excerpt,
|
|
117
|
+
});
|
|
118
|
+
if (upsertGeneratedSection(configsDir, relativePath, key, block, "Topic Transcript")) {
|
|
119
|
+
changed.add(relativePath);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const referenceRowsByPath = new Map();
|
|
123
|
+
for (const reference of proposal.referenceRows ?? []) {
|
|
124
|
+
const relativePath = reference.indexPath ??
|
|
125
|
+
`core/references/${normalizeSlug(reference.category ?? "misc")}/INDEX.md`;
|
|
126
|
+
const key = stableKey("reference", [
|
|
127
|
+
reference.key,
|
|
128
|
+
reference.source,
|
|
129
|
+
reference.title,
|
|
130
|
+
reference.copiedPath,
|
|
131
|
+
]);
|
|
132
|
+
const rows = referenceRowsByPath.get(relativePath) ?? [];
|
|
133
|
+
rows.push({
|
|
134
|
+
key,
|
|
135
|
+
row: {
|
|
136
|
+
Title: reference.title,
|
|
137
|
+
"Source Path/URL": reference.source,
|
|
138
|
+
"Copied Path": reference.copiedPath ?? "indexed only",
|
|
139
|
+
Tags: reference.tags ?? "",
|
|
140
|
+
"Why It Matters": reference.whyItMatters ?? "",
|
|
141
|
+
"Usage Contexts": reference.usageContexts ?? "",
|
|
142
|
+
"Last Reviewed": reference.lastReviewed ?? "",
|
|
143
|
+
Key: key,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
referenceRowsByPath.set(relativePath, rows);
|
|
147
|
+
}
|
|
148
|
+
for (const [relativePath, rows] of referenceRowsByPath) {
|
|
149
|
+
if (upsertTableRows(configsDir, relativePath, "Reference Index", [
|
|
150
|
+
"Title",
|
|
151
|
+
"Source Path/URL",
|
|
152
|
+
"Copied Path",
|
|
153
|
+
"Tags",
|
|
154
|
+
"Why It Matters",
|
|
155
|
+
"Usage Contexts",
|
|
156
|
+
"Last Reviewed",
|
|
157
|
+
"Key",
|
|
158
|
+
], rows)) {
|
|
159
|
+
changed.add(relativePath);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
configsDir,
|
|
164
|
+
filesChanged: [...changed].sort(),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function upsertBlockEntries(configsDir, relativePath, title, entries, keyForEntry, markdownForEntry, changed) {
|
|
168
|
+
if (!entries?.length)
|
|
169
|
+
return;
|
|
170
|
+
const seen = new Set();
|
|
171
|
+
let fileChanged = false;
|
|
172
|
+
for (const entry of entries) {
|
|
173
|
+
const key = keyForEntry(entry);
|
|
174
|
+
if (seen.has(key))
|
|
175
|
+
continue;
|
|
176
|
+
seen.add(key);
|
|
177
|
+
fileChanged =
|
|
178
|
+
upsertGeneratedSection(configsDir, relativePath, key, markdownForEntry(entry), title) || fileChanged;
|
|
179
|
+
}
|
|
180
|
+
if (fileChanged) {
|
|
181
|
+
changed.add(relativePath);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function upsertGeneratedSection(configsDir, relativePath, key, markdown, defaultTitle) {
|
|
185
|
+
const cleanKey = key.trim();
|
|
186
|
+
const filePath = resolveConfigPath(configsDir, relativePath);
|
|
187
|
+
const existing = readMarkdown(filePath);
|
|
188
|
+
const base = existing || (defaultTitle ? `# ${defaultTitle}\n` : "");
|
|
189
|
+
const block = generatedBlock(cleanKey, markdown);
|
|
190
|
+
const blockPattern = new RegExp(`${escapeRegExp(startMarker(cleanKey))}\\n[\\s\\S]*?\\n${escapeRegExp(END_MARKER)}`, "m");
|
|
191
|
+
const next = blockPattern.test(base)
|
|
192
|
+
? base.replace(blockPattern, block)
|
|
193
|
+
: appendMarkdown(base, block);
|
|
194
|
+
return writeMarkdownIfChanged(filePath, next);
|
|
195
|
+
}
|
|
196
|
+
function upsertTableRows(configsDir, relativePath, title, headers, rows) {
|
|
197
|
+
if (rows.length === 0)
|
|
198
|
+
return false;
|
|
199
|
+
const filePath = resolveConfigPath(configsDir, relativePath);
|
|
200
|
+
const existing = readMarkdown(filePath) || `# ${title}\n`;
|
|
201
|
+
const table = parseFirstMarkdownTable(existing);
|
|
202
|
+
const existingRows = table ? table.rows : [];
|
|
203
|
+
const rowByKey = new Map();
|
|
204
|
+
for (const row of existingRows) {
|
|
205
|
+
const key = row.Key ||
|
|
206
|
+
stableKey("row", headers.map((header) => row[header] ?? ""));
|
|
207
|
+
rowByKey.set(key, row);
|
|
208
|
+
}
|
|
209
|
+
const seenInputKeys = new Set();
|
|
210
|
+
for (const { key, row } of rows) {
|
|
211
|
+
if (seenInputKeys.has(key))
|
|
212
|
+
continue;
|
|
213
|
+
seenInputKeys.add(key);
|
|
214
|
+
rowByKey.set(key, row);
|
|
215
|
+
}
|
|
216
|
+
const nextTable = buildMarkdownTable(headers, [...rowByKey.values()]);
|
|
217
|
+
const next = table
|
|
218
|
+
? `${existing.slice(0, table.start)}${nextTable}${existing.slice(table.end)}`
|
|
219
|
+
: appendMarkdown(existing, nextTable);
|
|
220
|
+
return writeMarkdownIfChanged(filePath, next);
|
|
221
|
+
}
|
|
222
|
+
const END_MARKER = "<!-- sellable:generated:end -->";
|
|
223
|
+
function startMarker(key) {
|
|
224
|
+
return `<!-- sellable:generated:start key="${key}" -->`;
|
|
225
|
+
}
|
|
226
|
+
function generatedBlock(key, markdown) {
|
|
227
|
+
return `${startMarker(key)}\n${markdown.trim()}\n${END_MARKER}`;
|
|
228
|
+
}
|
|
229
|
+
function renderBlock(title, fields) {
|
|
230
|
+
const lines = [`## ${title.trim()}`];
|
|
231
|
+
for (const [label, value] of Object.entries(fields)) {
|
|
232
|
+
const cleanValue = value?.trim();
|
|
233
|
+
if (!cleanValue)
|
|
234
|
+
continue;
|
|
235
|
+
lines.push(`- ${label}: ${cleanValue}`);
|
|
236
|
+
}
|
|
237
|
+
return lines.join("\n");
|
|
238
|
+
}
|
|
239
|
+
function parseFirstMarkdownTable(markdown) {
|
|
240
|
+
const lines = markdown.split(/\n/);
|
|
241
|
+
let offset = 0;
|
|
242
|
+
for (let index = 0; index < lines.length - 1; index++) {
|
|
243
|
+
const line = lines[index];
|
|
244
|
+
const next = lines[index + 1];
|
|
245
|
+
if (isTableLine(line) && /^\|[\s\-:|]+\|$/.test(next.trim())) {
|
|
246
|
+
const headers = splitTableLine(line);
|
|
247
|
+
const rows = [];
|
|
248
|
+
let tableEndLine = index + 2;
|
|
249
|
+
for (; tableEndLine < lines.length; tableEndLine++) {
|
|
250
|
+
const rowLine = lines[tableEndLine];
|
|
251
|
+
if (!isTableLine(rowLine))
|
|
252
|
+
break;
|
|
253
|
+
const cells = splitTableLine(rowLine);
|
|
254
|
+
const row = {};
|
|
255
|
+
headers.forEach((header, headerIndex) => {
|
|
256
|
+
row[header] = cells[headerIndex] ?? "";
|
|
257
|
+
});
|
|
258
|
+
rows.push(row);
|
|
259
|
+
}
|
|
260
|
+
const start = offset;
|
|
261
|
+
const end = lines.slice(0, tableEndLine).join("\n").length;
|
|
262
|
+
return { start, end, headers, rows };
|
|
263
|
+
}
|
|
264
|
+
offset += line.length + 1;
|
|
265
|
+
}
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
function buildMarkdownTable(headers, rows) {
|
|
269
|
+
const header = `| ${headers.join(" | ")} |`;
|
|
270
|
+
const separator = `| ${headers.map(() => "---").join(" | ")} |`;
|
|
271
|
+
const body = rows.map((row) => `| ${headers.map((headerName) => tableCell(row[headerName])).join(" | ")} |`);
|
|
272
|
+
return [header, separator, ...body].join("\n");
|
|
273
|
+
}
|
|
274
|
+
function isTableLine(line) {
|
|
275
|
+
const trimmed = line.trim();
|
|
276
|
+
return trimmed.startsWith("|") && trimmed.endsWith("|");
|
|
277
|
+
}
|
|
278
|
+
function splitTableLine(line) {
|
|
279
|
+
return line
|
|
280
|
+
.trim()
|
|
281
|
+
.slice(1, -1)
|
|
282
|
+
.split("|")
|
|
283
|
+
.map((cell) => cell.trim());
|
|
284
|
+
}
|
|
285
|
+
function tableCell(value) {
|
|
286
|
+
return (value ?? "").replace(/\r?\n/g, " ").replace(/\|/g, "\\|").trim();
|
|
287
|
+
}
|
|
288
|
+
function readMarkdown(filePath) {
|
|
289
|
+
if (!fs.existsSync(filePath))
|
|
290
|
+
return "";
|
|
291
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
292
|
+
}
|
|
293
|
+
function writeMarkdownIfChanged(filePath, markdown) {
|
|
294
|
+
const next = `${markdown.trimEnd()}\n`;
|
|
295
|
+
if (readMarkdown(filePath) === next)
|
|
296
|
+
return false;
|
|
297
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
298
|
+
fs.writeFileSync(filePath, next);
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
function appendMarkdown(existing, addition) {
|
|
302
|
+
const trimmedExisting = existing.trimEnd();
|
|
303
|
+
if (!trimmedExisting)
|
|
304
|
+
return addition;
|
|
305
|
+
return `${trimmedExisting}\n\n${addition}`;
|
|
306
|
+
}
|
|
307
|
+
function resolveConfigPath(configsDir, relativePath) {
|
|
308
|
+
const cleanRelativePath = relativePath.replace(/\\/g, "/").trim();
|
|
309
|
+
if (cleanRelativePath === "" ||
|
|
310
|
+
cleanRelativePath.startsWith("/") ||
|
|
311
|
+
cleanRelativePath.split("/").includes("..")) {
|
|
312
|
+
throw new Error(`Invalid identity compiler path: ${relativePath}`);
|
|
313
|
+
}
|
|
314
|
+
const root = path.resolve(configsDir);
|
|
315
|
+
const resolved = path.resolve(root, cleanRelativePath);
|
|
316
|
+
const relative = path.relative(root, resolved);
|
|
317
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
318
|
+
throw new Error(`Invalid identity compiler path: ${relativePath}`);
|
|
319
|
+
}
|
|
320
|
+
return resolved;
|
|
321
|
+
}
|
|
322
|
+
function stableKey(prefix, parts) {
|
|
323
|
+
const normalizedParts = parts
|
|
324
|
+
.filter((part) => Boolean(part?.trim()))
|
|
325
|
+
.map(normalizeKeyPart)
|
|
326
|
+
.filter(Boolean);
|
|
327
|
+
return `${normalizeKeyPart(prefix)}:${normalizedParts.join(":") || "entry"}`;
|
|
328
|
+
}
|
|
329
|
+
function normalizeSlug(value) {
|
|
330
|
+
return normalizeKeyPart(value) || "misc";
|
|
331
|
+
}
|
|
332
|
+
function normalizeKeyPart(value) {
|
|
333
|
+
return value
|
|
334
|
+
.normalize("NFKD")
|
|
335
|
+
.toLowerCase()
|
|
336
|
+
.replace(/['"]/g, "")
|
|
337
|
+
.replace(/https?:\/\//g, "")
|
|
338
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
339
|
+
.replace(/^-+|-+$/g, "")
|
|
340
|
+
.slice(0, 120);
|
|
341
|
+
}
|
|
342
|
+
function escapeRegExp(value) {
|
|
343
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
344
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export declare const CORE_MEMORY_PATHS: {
|
|
2
|
+
readonly identity: "core/about-me.md";
|
|
3
|
+
readonly company: "core/my-company.md";
|
|
4
|
+
readonly antiAiStyle: "core/anti-ai-writing-style.md";
|
|
5
|
+
readonly proofLedger: "core/proof-ledger.md";
|
|
6
|
+
readonly winsLedger: "core/wins-ledger.md";
|
|
7
|
+
readonly storyBank: "core/story-bank.md";
|
|
8
|
+
readonly answerBank: "core/answer-bank.md";
|
|
9
|
+
readonly contextModes: "core/context-modes.md";
|
|
10
|
+
readonly decisionRules: "core/decision-rules.md";
|
|
11
|
+
readonly changeLog: "core/change-log.md";
|
|
12
|
+
readonly transcripts: "core/transcripts";
|
|
13
|
+
readonly references: "core/references";
|
|
14
|
+
};
|
|
15
|
+
export interface IdentityMemorySource {
|
|
16
|
+
relativePath: string;
|
|
17
|
+
exists: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface IdentityMemoryChunk {
|
|
20
|
+
markdown: string;
|
|
21
|
+
source: IdentityMemorySource;
|
|
22
|
+
}
|
|
23
|
+
export interface IdentityMemoryDirectory {
|
|
24
|
+
source: IdentityMemorySource;
|
|
25
|
+
index?: IdentityMemoryChunk;
|
|
26
|
+
}
|
|
27
|
+
export interface IdentityMemory {
|
|
28
|
+
identity?: IdentityMemoryChunk;
|
|
29
|
+
company?: IdentityMemoryChunk;
|
|
30
|
+
antiAiStyle?: IdentityMemoryChunk;
|
|
31
|
+
proofLedger?: IdentityMemoryChunk;
|
|
32
|
+
winsLedger?: IdentityMemoryChunk;
|
|
33
|
+
storyBank?: IdentityMemoryChunk;
|
|
34
|
+
answerBank?: IdentityMemoryChunk;
|
|
35
|
+
contextModes?: IdentityMemoryChunk;
|
|
36
|
+
decisionRules?: IdentityMemoryChunk;
|
|
37
|
+
changeLog?: IdentityMemoryChunk;
|
|
38
|
+
transcripts?: IdentityMemoryDirectory;
|
|
39
|
+
references?: IdentityMemoryDirectory;
|
|
40
|
+
}
|
|
41
|
+
export interface ReadIdentityMemoryOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Core identity/company memory is workspace-level in v1. Connected sender
|
|
44
|
+
* IDs remain engage compatibility overlays, not separate identity profiles.
|
|
45
|
+
*/
|
|
46
|
+
connectedSenderId?: string;
|
|
47
|
+
}
|
|
48
|
+
export declare function resolveIdentityConfigsDir(): string;
|
|
49
|
+
export declare function readIdentityMemory(_options?: ReadIdentityMemoryOptions): IdentityMemory;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as os from "os";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
export const CORE_MEMORY_PATHS = {
|
|
5
|
+
identity: "core/about-me.md",
|
|
6
|
+
company: "core/my-company.md",
|
|
7
|
+
antiAiStyle: "core/anti-ai-writing-style.md",
|
|
8
|
+
proofLedger: "core/proof-ledger.md",
|
|
9
|
+
winsLedger: "core/wins-ledger.md",
|
|
10
|
+
storyBank: "core/story-bank.md",
|
|
11
|
+
answerBank: "core/answer-bank.md",
|
|
12
|
+
contextModes: "core/context-modes.md",
|
|
13
|
+
decisionRules: "core/decision-rules.md",
|
|
14
|
+
changeLog: "core/change-log.md",
|
|
15
|
+
transcripts: "core/transcripts",
|
|
16
|
+
references: "core/references",
|
|
17
|
+
};
|
|
18
|
+
export function resolveIdentityConfigsDir() {
|
|
19
|
+
const candidates = [];
|
|
20
|
+
if (process.env.SELLABLE_CONFIGS_DIR) {
|
|
21
|
+
candidates.push(path.resolve(process.env.SELLABLE_CONFIGS_DIR));
|
|
22
|
+
}
|
|
23
|
+
if (process.argv[1]) {
|
|
24
|
+
candidates.push(path.resolve(path.dirname(process.argv[1]), "../../.sellable/configs"));
|
|
25
|
+
}
|
|
26
|
+
candidates.push(path.resolve(process.cwd(), ".sellable/configs"));
|
|
27
|
+
candidates.push(path.resolve(os.homedir(), ".sellable/configs"));
|
|
28
|
+
for (const candidate of candidates) {
|
|
29
|
+
if (fs.existsSync(candidate)) {
|
|
30
|
+
return candidate;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return candidates[candidates.length - 1];
|
|
34
|
+
}
|
|
35
|
+
export function readIdentityMemory(_options = {}) {
|
|
36
|
+
const configsDir = resolveIdentityConfigsDir();
|
|
37
|
+
const memory = {};
|
|
38
|
+
const fields = [
|
|
39
|
+
{ key: "identity", relativePath: CORE_MEMORY_PATHS.identity },
|
|
40
|
+
{ key: "company", relativePath: CORE_MEMORY_PATHS.company },
|
|
41
|
+
{ key: "antiAiStyle", relativePath: CORE_MEMORY_PATHS.antiAiStyle },
|
|
42
|
+
{ key: "proofLedger", relativePath: CORE_MEMORY_PATHS.proofLedger },
|
|
43
|
+
{ key: "winsLedger", relativePath: CORE_MEMORY_PATHS.winsLedger },
|
|
44
|
+
{ key: "storyBank", relativePath: CORE_MEMORY_PATHS.storyBank },
|
|
45
|
+
{ key: "answerBank", relativePath: CORE_MEMORY_PATHS.answerBank },
|
|
46
|
+
{ key: "contextModes", relativePath: CORE_MEMORY_PATHS.contextModes },
|
|
47
|
+
{ key: "decisionRules", relativePath: CORE_MEMORY_PATHS.decisionRules },
|
|
48
|
+
{ key: "changeLog", relativePath: CORE_MEMORY_PATHS.changeLog },
|
|
49
|
+
];
|
|
50
|
+
for (const field of fields) {
|
|
51
|
+
const chunk = readMarkdownChunk(configsDir, field.relativePath);
|
|
52
|
+
if (chunk) {
|
|
53
|
+
memory[field.key] = chunk;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const transcripts = readDirectory(configsDir, CORE_MEMORY_PATHS.transcripts);
|
|
57
|
+
if (transcripts) {
|
|
58
|
+
memory.transcripts = transcripts;
|
|
59
|
+
}
|
|
60
|
+
const references = readDirectory(configsDir, CORE_MEMORY_PATHS.references);
|
|
61
|
+
if (references) {
|
|
62
|
+
memory.references = references;
|
|
63
|
+
}
|
|
64
|
+
return memory;
|
|
65
|
+
}
|
|
66
|
+
function readMarkdownChunk(configsDir, relativePath) {
|
|
67
|
+
const fullPath = path.join(configsDir, relativePath);
|
|
68
|
+
if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
markdown: fs.readFileSync(fullPath, "utf-8"),
|
|
73
|
+
source: { relativePath, exists: true },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function readDirectory(configsDir, relativePath) {
|
|
77
|
+
const fullPath = path.join(configsDir, relativePath);
|
|
78
|
+
if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isDirectory()) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
const directory = {
|
|
82
|
+
source: { relativePath, exists: true },
|
|
83
|
+
};
|
|
84
|
+
const index = readMarkdownChunk(configsDir, `${relativePath}/INDEX.md`);
|
|
85
|
+
if (index) {
|
|
86
|
+
directory.index = index;
|
|
87
|
+
}
|
|
88
|
+
return directory;
|
|
89
|
+
}
|
package/dist/index-dev.js
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/tools/bootstrap.js
CHANGED
|
@@ -270,7 +270,7 @@ export async function bootstrapCreateCampaign(input = {}) {
|
|
|
270
270
|
? resumeDetected
|
|
271
271
|
? `Bootstrap complete.${workspaceNotice} Resume from campaign state and navigation diagnostics first; treat local draft artifacts as debug-only evidence. Then load ${createCampaignSubskill?.name ?? "create-campaign"} instructions with get_subskill_prompt({ subskillName: "${createCampaignSubskill?.name ?? "create-campaign"}" }); if the response has hasMore=true, continue with nextOffset until hasMore=false.`
|
|
272
272
|
: flowVersion === "v2"
|
|
273
|
-
? `Bootstrap complete.${workspaceNotice} Load create-campaign-v2
|
|
273
|
+
? `Bootstrap complete.${workspaceNotice} Load the compact create-campaign-v2 entry prompt once with get_subskill_prompt({ subskillName: "create-campaign-v2" }); load flow/reference assets lazily only when that stage needs them. Preserve the pre-intake sequence: confirm auth/workspace status, resolve campaign identity, and run lightweight profile/company lookup. Do not call list_senders or sender discovery during setup; sender availability belongs only to Settings after message approval. Then run the short setup/intake, write the campaign brief, call create_campaign once to mint the watchable shell, surface the returned watch link, and hand off to lead finding.`
|
|
274
274
|
: `Bootstrap complete.${workspaceNotice} Load ${createCampaignSubskill?.name ?? "create-campaign"} instructions with get_subskill_prompt({ subskillName: "${createCampaignSubskill?.name ?? "create-campaign"}" }); if the response has hasMore=true, continue with nextOffset until hasMore=false. Follow that flow before calling create_campaign.`
|
|
275
275
|
: "Bootstrap incomplete. Resolve blockingErrors and rerun bootstrap_create_campaign before provider/search/import tools.";
|
|
276
276
|
// Strip prompt body from createCampaignSubskill — it's loaded via the host
|
|
@@ -2,13 +2,13 @@ import { copySenderConfig, getEngageMemory, migrateFlatConfigs, recordProvenSear
|
|
|
2
2
|
export const engageMemoryToolDefinitions = [
|
|
3
3
|
{
|
|
4
4
|
name: "get_engage_memory",
|
|
5
|
-
description: "Load engage memory from .sellable/configs/: style guide, proven search keywords,
|
|
5
|
+
description: "Load backward-compatible engage memory from .sellable/configs/: style guide, proven search keywords, tracked people, plus optional core identity/company memory. All data lives in readable markdown files the user can also edit directly. When senderId is provided, reads compatibility overrides from senders/{senderId}/ with flat-path fallback.",
|
|
6
6
|
inputSchema: {
|
|
7
7
|
type: "object",
|
|
8
8
|
properties: {
|
|
9
9
|
senderId: {
|
|
10
10
|
type: "string",
|
|
11
|
-
description: "Optional sender ID. When provided, reads per-sender configs from senders/{senderId}/. Falls back to flat configs
|
|
11
|
+
description: "Optional sender ID. When provided, reads per-sender compatibility configs from senders/{senderId}/. Falls back to flat configs for missing sender files.",
|
|
12
12
|
},
|
|
13
13
|
},
|
|
14
14
|
required: [],
|
package/dist/tools/navigation.js
CHANGED
|
@@ -243,6 +243,9 @@ function checkMessages(campaign) {
|
|
|
243
243
|
if (!hasApprovedMessageTemplate(campaign)) {
|
|
244
244
|
missing.push("approvedMessageTemplate");
|
|
245
245
|
}
|
|
246
|
+
if (campaign.currentStep === "auto-execute-messaging") {
|
|
247
|
+
missing.push("generatedMessageReview");
|
|
248
|
+
}
|
|
246
249
|
return { stepId: "messages", missing };
|
|
247
250
|
}
|
|
248
251
|
function checkSettings(campaign) {
|
package/dist/tools/prompts.d.ts
CHANGED
|
@@ -99,9 +99,7 @@ export interface SourceScoutRegistryResponse {
|
|
|
99
99
|
export interface PostFindLeadsScoutRegistryResponse {
|
|
100
100
|
version: number;
|
|
101
101
|
trigger: "find_leads_source_approved";
|
|
102
|
-
requiredArtifacts?: string[];
|
|
103
102
|
requiredInputs?: string[];
|
|
104
|
-
optionalDebugArtifacts?: string[];
|
|
105
103
|
scouts: Array<{
|
|
106
104
|
id: string;
|
|
107
105
|
name: string;
|
|
@@ -125,11 +123,9 @@ export interface PostFindLeadsScoutRegistryResponse {
|
|
|
125
123
|
}>;
|
|
126
124
|
joinGate: {
|
|
127
125
|
afterAllComplete: boolean;
|
|
128
|
-
requiredArtifacts?: string[];
|
|
129
126
|
requiredState?: string[];
|
|
130
127
|
noFilterRequiredState?: string[];
|
|
131
128
|
skipFilterRule?: string;
|
|
132
|
-
optionalDebugArtifacts?: string[];
|
|
133
129
|
show: string[];
|
|
134
130
|
nextStage: string;
|
|
135
131
|
};
|
|
@@ -140,6 +136,7 @@ export interface PostFindLeadsScoutRegistryResponse {
|
|
|
140
136
|
runtimeProofRequiredFields: string[];
|
|
141
137
|
basisFields: string[];
|
|
142
138
|
compactOutputFields: string[];
|
|
139
|
+
reusePolicy?: string;
|
|
143
140
|
};
|
|
144
141
|
usage: {
|
|
145
142
|
codex: string;
|
package/dist/tools/prompts.js
CHANGED
|
@@ -263,7 +263,6 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
263
263
|
"imported review-batch rows from selectedLeadList with row ids/hash",
|
|
264
264
|
"filter choice and filter/rubric basis when present",
|
|
265
265
|
],
|
|
266
|
-
optionalDebugArtifacts: ["brief.md", "lead-review.md", "lead-sample.json"],
|
|
267
266
|
scouts: scouts.map((agent) => ({
|
|
268
267
|
id: String(agent.id || ""),
|
|
269
268
|
name: String(agent.name || ""),
|
|
@@ -294,7 +293,6 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
294
293
|
requiredState: ["leadScoringRubrics", "messageDraftRecommendation"],
|
|
295
294
|
noFilterRequiredState: ["messageDraftRecommendation"],
|
|
296
295
|
skipFilterRule: "leadScoringRubrics may be absent only when the user explicitly skips filters with enableICPFilters=false; messageDraftRecommendation is still required before template review.",
|
|
297
|
-
optionalDebugArtifacts: ["lead-filter.md", "message-validation.md"],
|
|
298
296
|
show: ["readable_filters_with_reasons", "sample_message_for_approval"],
|
|
299
297
|
nextStage: "message-review",
|
|
300
298
|
},
|
|
@@ -332,16 +330,18 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
332
330
|
"renderedSample",
|
|
333
331
|
"concerns",
|
|
334
332
|
"status",
|
|
333
|
+
"basisStatus",
|
|
335
334
|
"basisToken",
|
|
336
335
|
"outputAt",
|
|
337
336
|
"outputHash",
|
|
338
337
|
"error or retry detail",
|
|
339
338
|
],
|
|
339
|
+
reusePolicy: "The first completed Message Draft Builder recommendation remains the default review candidate. Later Lead Fit Builder, Filter Leads, enrichment, or rubric completion may make an enriched rewrite available, but does not automatically retry or replace the initial draft unless campaign/brief/source/list/table/review-batch identity mismatches or the initial output failed.",
|
|
340
340
|
},
|
|
341
341
|
usage: {
|
|
342
342
|
codex: "After the user approves or auto-confirms the lead source, spawn both returned scout `name` values in one assistant turn only when the current Codex host exposes those custom agents.",
|
|
343
343
|
claude: "After lead source approval, invoke both returned Task/Agent subagents in one assistant message only when the current Claude session lists those agents, so filter-leads and message generation run concurrently.",
|
|
344
|
-
parentThreadRule: "Named agents are optional acceleration. If they are absent, do not customer-surface install status; the main thread still orchestrates filter and message branches from CampaignOffer state, selected source state, workflowTableId, and imported review-batch rows.
|
|
344
|
+
parentThreadRule: "Named agents are optional acceleration. If they are absent, do not customer-surface install status; the main thread still orchestrates filter and message branches from CampaignOffer state, selected source state, workflowTableId, and imported review-batch rows. Local markdown/json files are not normal-path inputs. The message branch must load the full generate-messages prompt, must read live campaign/review-batch state through scoped MCP/product tools, must reject mismatched selectedLeadListId/workflowTableId/campaign/workspace input, and may use the create-campaign-v2 message-review safety gate as a supplemental approval check. Join before message review. Do not automatically rerun Message Draft Builder after filters/enrichment finish; show the initial draft by default and offer an enriched rewrite only with explicit user opt-in.",
|
|
345
345
|
},
|
|
346
346
|
};
|
|
347
347
|
}
|
package/package.json
CHANGED
|
@@ -78,7 +78,7 @@ Parallel execution:
|
|
|
78
78
|
|
|
79
79
|
Config tools (built-in, free):
|
|
80
80
|
|
|
81
|
-
- `Read` - Load
|
|
81
|
+
- `Read` - Load core identity/company memory, proof/wins ledgers, anti-AI audit, outbound rules, and cross-skill insights from `.sellable/`
|
|
82
82
|
|
|
83
83
|
Research tools (free - user's Claude tokens):
|
|
84
84
|
|
|
@@ -111,8 +111,14 @@ Parallel batch (6 calls):
|
|
|
111
111
|
├── get_rows(rowId) → prospect data
|
|
112
112
|
├── get_message_prompt() → REPLY framework
|
|
113
113
|
├── get_subskill_prompt({ subskillName: "research-prospect" }) → research protocol
|
|
114
|
+
├── Read .sellable/configs/core/about-me.md → core identity memory
|
|
115
|
+
├── Read .sellable/configs/core/my-company.md → company truth
|
|
116
|
+
├── Read .sellable/configs/core/context-modes.md → outbound context mode
|
|
117
|
+
├── Read .sellable/configs/core/proof-ledger.md → safe proof claims
|
|
118
|
+
├── Read .sellable/configs/core/wins-ledger.md → social proof and wins
|
|
119
|
+
├── Read .sellable/configs/core/anti-ai-writing-style.md → anti-AI audit
|
|
114
120
|
├── Read .sellable/configs/writing/outbound.md → outbound rules
|
|
115
|
-
├── Read .sellable/configs/writing/styleguide-core.md → sender voice
|
|
121
|
+
├── Read .sellable/configs/writing/styleguide-core.md → legacy sender voice fallback
|
|
116
122
|
└── Read .sellable/insights/cross-skill.md → cross-skill learnings
|
|
117
123
|
```
|
|
118
124
|
|
|
@@ -161,6 +167,8 @@ Extract the following from the REPLY framework, configs, and research into a sin
|
|
|
161
167
|
- Core thesis: {1 line from styleguide-core.md}
|
|
162
168
|
- Signature phrases: {3-5 key phrases from styleguide-core.md}
|
|
163
169
|
- Offer: {1 line from campaign brief}
|
|
170
|
+
- Core memory: {1-2 lines from core/about-me.md, my-company.md, and context-modes.md}
|
|
171
|
+
- Safe proof: {best usable proof from proof-ledger.md or campaign brief}
|
|
164
172
|
|
|
165
173
|
### Message Rules
|
|
166
174
|
- Structure: observation about THEM → why it's relevant → soft ask
|
|
@@ -271,7 +279,7 @@ After all 10 angle agents return, **you (the Opus orchestrator) act as the final
|
|
|
271
279
|
- Does the bridge prove the sender has a unique perspective? The message should make the prospect think "this person actually gets it" — not "this person read my LinkedIn headline."
|
|
272
280
|
- Is there at least one sentence that would make the prospect stop scrolling? Something surprising, specific, or contrarian.
|
|
273
281
|
- Kill any line that reads like a template. If you've seen it in a cold outreach before, rewrite it.
|
|
274
|
-
5. **Voice-check** — does it sound like the sender? Use
|
|
282
|
+
5. **Voice-check** — does it sound like the sender? Use core identity/context modes first, then legacy signature phrases from `styleguide-core.md` as fallback/source material.
|
|
275
283
|
6. **Full 12-gate check using the REPLY framework** — this is the only place the full framework is applied. All gates must pass on the combined message. The angle agents drafted creatively; you enforce compliance.
|
|
276
284
|
7. **Polish** — one final pass for flow, tone, and length
|
|
277
285
|
|
|
@@ -379,7 +387,7 @@ After saving (and optionally approving), return control for:
|
|
|
379
387
|
|
|
380
388
|
## Critical Rules
|
|
381
389
|
|
|
382
|
-
1. **Load
|
|
390
|
+
1. **Load core memory first** — read `.sellable/configs/core/about-me.md`, `my-company.md`, `context-modes.md`, `proof-ledger.md`, `wins-ledger.md`, and `anti-ai-writing-style.md` before crafting. Then read `.sellable/configs/writing/outbound.md` and legacy `styleguide-core.md` as fallback/source material. The message must sound like the sender and stay inside safe proof.
|
|
383
391
|
2. **Load the framework first** — always call `get_message_prompt()` before crafting
|
|
384
392
|
3. **Always fan out 10 angles** — never skip the angle exploration step. More angles = better final message. This is the core differentiator.
|
|
385
393
|
4. **Launch agents in ONE message, no `run_in_background`** — all 10 angle agents (and all 6 research agents) must be launched as regular Task calls in a single message. They run concurrently and all results return together. Never use `run_in_background: true` — it causes notification spam.
|
|
@@ -392,6 +400,7 @@ After saving (and optionally approving), return control for:
|
|
|
392
400
|
11. **Research deeply** — this is the value (5+ min per message). Compute time is the quality knob.
|
|
393
401
|
12. Credits awareness — note when using LinkedIn tools
|
|
394
402
|
13. **Use cross-skill insights** — if `insights/cross-skill.md` shows what hooks/angles resonate, use those patterns in the angle exploration
|
|
403
|
+
14. **Anti-AI/proof final gate** — before showing a message, check `anti-ai-writing-style.md`, `proof-ledger.md`, `wins-ledger.md`, and `context-modes.md`; remove unsupported proof, stale wins, fake-depth reframes, and context-inappropriate voice.
|
|
395
404
|
|
|
396
405
|
## Developer Note
|
|
397
406
|
|