@sellable/mcp 0.1.237 → 0.1.239

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.
@@ -19,6 +19,7 @@ export interface TrackedPerson {
19
19
  }
20
20
  export interface EngageMemory {
21
21
  styleGuide?: EngageStyleGuide;
22
+ postWritingRules?: EngageStyleGuide;
22
23
  provenSearches: ProvenSearch[];
23
24
  trackedPeople: TrackedPerson[];
24
25
  identity?: IdentityMemoryChunk;
@@ -116,8 +116,18 @@ function buildMarkdownTable(headers, rows) {
116
116
  // ── Style guide ────────────────────────────────────────────────────────────
117
117
  const FLAT_STYLE_CORE_PATH = "writing/styleguide-core.md";
118
118
  const STYLE_COMMENTS_PATH = "writing/comments.md";
119
+ const STYLE_POSTS_PATH = "writing/posts.md";
119
120
  const LEGACY_AUDIENCE_ICP_PATH = "audience/icp.md";
120
121
  const LEGACY_FAQS_CORE_PATH = "faqs/core.md";
122
+ function readPostWritingRules() {
123
+ const markdown = readFile(STYLE_POSTS_PATH);
124
+ if (!markdown)
125
+ return undefined;
126
+ return {
127
+ markdown,
128
+ updatedAt: latestUpdatedAt([STYLE_POSTS_PATH]),
129
+ };
130
+ }
121
131
  function readStyleGuide(senderId, identityMemory) {
122
132
  const corePath = senderPath(senderId, "styleguide-core.md", FLAT_STYLE_CORE_PATH);
123
133
  const core = readFile(corePath);
@@ -200,7 +210,8 @@ function hasCoreIdentityMemory(identityMemory) {
200
210
  identityMemory.decisionRules ||
201
211
  identityMemory.changeLog ||
202
212
  identityMemory.transcripts?.index ||
203
- identityMemory.references?.index);
213
+ identityMemory.references?.index ||
214
+ (identityMemory.references?.childIndexes?.length ?? 0) > 0);
204
215
  }
205
216
  function appendCoreSection(parts, sourcePaths, title, chunk) {
206
217
  if (!chunk)
@@ -209,9 +220,12 @@ function appendCoreSection(parts, sourcePaths, title, chunk) {
209
220
  sourcePaths.push(chunk.source.relativePath);
210
221
  }
211
222
  function appendCoreDirectoryIndex(parts, sourcePaths, title, directory) {
212
- if (!directory?.index)
223
+ if (!directory)
213
224
  return;
214
225
  appendCoreSection(parts, sourcePaths, title, directory.index);
226
+ for (const childIndex of directory.childIndexes ?? []) {
227
+ appendCoreSection(parts, sourcePaths, title, childIndex);
228
+ }
215
229
  }
216
230
  function appendCompatibilitySection(parts, sourcePaths, section) {
217
231
  parts.push(`## ${section.title} (${section.relativePath})\n\n${section.markdown.trim()}`);
@@ -229,11 +243,13 @@ function latestUpdatedAt(relativePaths) {
229
243
  export function getEngageMemory(senderId) {
230
244
  const identityMemory = readIdentityMemory({ connectedSenderId: senderId });
231
245
  const styleGuide = readStyleGuide(senderId, identityMemory);
246
+ const postWritingRules = readPostWritingRules();
232
247
  const provenSearches = readProvenSearches(senderId);
233
248
  const trackedPeople = readTrackedPeople(senderId);
234
249
  return {
235
250
  memory: {
236
251
  styleGuide,
252
+ postWritingRules,
237
253
  provenSearches,
238
254
  trackedPeople,
239
255
  ...identityMemory,
@@ -23,6 +23,7 @@ export interface IdentityMemoryChunk {
23
23
  export interface IdentityMemoryDirectory {
24
24
  source: IdentityMemorySource;
25
25
  index?: IdentityMemoryChunk;
26
+ childIndexes?: IdentityMemoryChunk[];
26
27
  }
27
28
  export interface IdentityMemory {
28
29
  identity?: IdentityMemoryChunk;
@@ -81,5 +81,32 @@ function readDirectory(configsDir, relativePath) {
81
81
  if (index) {
82
82
  directory.index = index;
83
83
  }
84
+ const childIndexes = readChildIndexes(configsDir, relativePath);
85
+ if (childIndexes.length > 0) {
86
+ directory.childIndexes = childIndexes;
87
+ }
84
88
  return directory;
85
89
  }
90
+ function readChildIndexes(configsDir, relativePath) {
91
+ const root = path.join(configsDir, relativePath);
92
+ const indexes = [];
93
+ function walk(currentFullPath, currentRelativePath) {
94
+ for (const entry of fs.readdirSync(currentFullPath).sort()) {
95
+ const entryFullPath = path.join(currentFullPath, entry);
96
+ const entryRelativePath = `${currentRelativePath}/${entry}`;
97
+ const stat = fs.statSync(entryFullPath);
98
+ if (stat.isDirectory()) {
99
+ walk(entryFullPath, entryRelativePath);
100
+ }
101
+ else if (stat.isFile() &&
102
+ entry === "INDEX.md" &&
103
+ entryRelativePath !== `${relativePath}/INDEX.md`) {
104
+ const chunk = readMarkdownChunk(configsDir, entryRelativePath);
105
+ if (chunk)
106
+ indexes.push(chunk);
107
+ }
108
+ }
109
+ }
110
+ walk(root, relativePath);
111
+ return indexes;
112
+ }
@@ -58,6 +58,7 @@ export declare const contentPostToolDefinitions: ({
58
58
  keywords?: undefined;
59
59
  sourcePosts?: undefined;
60
60
  selectedPatterns?: undefined;
61
+ previewBudget?: undefined;
61
62
  notes?: undefined;
62
63
  createdAt?: undefined;
63
64
  draftId?: undefined;
@@ -130,6 +131,11 @@ export declare const contentPostToolDefinitions: ({
130
131
  type: string;
131
132
  };
132
133
  };
134
+ previewBudget: {
135
+ type: string;
136
+ description: string;
137
+ additionalProperties: boolean;
138
+ };
133
139
  notes: {
134
140
  type: string;
135
141
  };
@@ -192,6 +198,7 @@ export declare const contentPostToolDefinitions: ({
192
198
  keywords?: undefined;
193
199
  sourcePosts?: undefined;
194
200
  selectedPatterns?: undefined;
201
+ previewBudget?: undefined;
195
202
  notes?: undefined;
196
203
  };
197
204
  required: string[];
@@ -245,6 +252,7 @@ export declare function saveHookResearchTool(input: {
245
252
  keywords?: string[];
246
253
  sourcePosts?: Array<Record<string, unknown>>;
247
254
  selectedPatterns?: string[];
255
+ previewBudget?: Record<string, unknown>;
248
256
  notes?: string;
249
257
  createdAt?: string;
250
258
  }): {
@@ -66,6 +66,11 @@ export const contentPostToolDefinitions = [
66
66
  type: "array",
67
67
  items: { type: "string" },
68
68
  },
69
+ previewBudget: {
70
+ type: "object",
71
+ description: "Optional structured LinkedIn preview-budget summary for studied hooks and selected patterns.",
72
+ additionalProperties: true,
73
+ },
69
74
  notes: { type: "string" },
70
75
  createdAt: { type: "string" },
71
76
  },
@@ -202,11 +207,14 @@ export function capturePostIdeaTool(input) {
202
207
  const markdown = buildMarkdown(metadata, [
203
208
  ["Distilled Brief", input.distilledBrief || ""],
204
209
  ["Raw Source", rawBlock(input.rawSource)],
205
- ["Source Metadata", jsonBlock(stripUndefined({
210
+ [
211
+ "Source Metadata",
212
+ jsonBlock(stripUndefined({
206
213
  sourceType: input.sourceType,
207
214
  sourceUrl: input.sourceUrl,
208
215
  capturedAt: now,
209
- }))],
216
+ })),
217
+ ],
210
218
  ]);
211
219
  writeArtifact(relativePath, markdown);
212
220
  return {
@@ -246,6 +254,7 @@ export function saveHookResearchTool(input) {
246
254
  const markdown = buildMarkdown(metadata, [
247
255
  ["Keywords", listBlock(input.keywords ?? [])],
248
256
  ["Selected Patterns", listBlock(input.selectedPatterns ?? [])],
257
+ ["Preview Budget", jsonBlock(input.previewBudget ?? {})],
249
258
  ["Source Posts", jsonBlock(input.sourcePosts ?? [])],
250
259
  ["Notes", input.notes || ""],
251
260
  ]);
@@ -332,18 +341,24 @@ export function markPostPublishedTool(input) {
332
341
  };
333
342
  const markdown = buildMarkdown(metadata, [
334
343
  ["Final Text", input.finalText || ""],
335
- ["Publish Metadata", jsonBlock(stripUndefined({
344
+ [
345
+ "Publish Metadata",
346
+ jsonBlock(stripUndefined({
336
347
  draftId: safeDraftId,
337
348
  publishUrl: input.publishUrl,
338
349
  activityId: activityId || undefined,
339
350
  publishedAt,
340
- }))],
341
- ["Future Metrics", jsonBlock({
351
+ })),
352
+ ],
353
+ [
354
+ "Future Metrics",
355
+ jsonBlock({
342
356
  impressions: null,
343
357
  reactions: null,
344
358
  comments: null,
345
359
  snapshots: [],
346
- })],
360
+ }),
361
+ ],
347
362
  ]);
348
363
  writeArtifact(relativePath, markdown);
349
364
  return {
@@ -483,7 +498,8 @@ function validateRelativePath(relativePath) {
483
498
  }
484
499
  function isPathInside(candidate, root) {
485
500
  const relative = path.relative(root, candidate);
486
- return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
501
+ return (relative === "" ||
502
+ (!relative.startsWith("..") && !path.isAbsolute(relative)));
487
503
  }
488
504
  function normalizeArtifactId(id, label) {
489
505
  requireString(id, label);
@@ -2,7 +2,7 @@ import { copySenderConfig, getEngageMemory, migrateFlatConfigs, recordProvenSear
2
2
  export const engageMemoryToolDefinitions = [
3
3
  {
4
4
  name: "get_engage_memory",
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.",
5
+ description: "Load backward-compatible engage memory from ~/.sellable/configs/: style guide, post writing rules, 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: {
@@ -1214,6 +1214,7 @@ export declare const allTools: ({
1214
1214
  keywords?: undefined;
1215
1215
  sourcePosts?: undefined;
1216
1216
  selectedPatterns?: undefined;
1217
+ previewBudget?: undefined;
1217
1218
  notes?: undefined;
1218
1219
  createdAt?: undefined;
1219
1220
  draftId?: undefined;
@@ -1279,6 +1280,7 @@ export declare const allTools: ({
1279
1280
  keywords?: undefined;
1280
1281
  sourcePosts?: undefined;
1281
1282
  selectedPatterns?: undefined;
1283
+ previewBudget?: undefined;
1282
1284
  notes?: undefined;
1283
1285
  };
1284
1286
  required: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.237",
3
+ "version": "0.1.239",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -11,6 +11,7 @@
11
11
  },
12
12
  "scripts": {
13
13
  "build": "tsc",
14
+ "prepack": "npm run build",
14
15
  "start": "node dist/index.js",
15
16
  "start:dev": "node dist/index-dev.js",
16
17
  "dev": "ts-node src/index.ts"
@@ -107,8 +107,8 @@ This is based on what I found. If your site or LinkedIn is stale, tell me what
107
107
  to update before I build the lead list.
108
108
  ```
109
109
 
110
- Ask: "Do you agree with this researched campaign direction?" The early brief
111
- must not include quoted first-message copy. If a message-shape hint is useful,
110
+ Ask: "Does this brief look right, and how should Sellable continue?" The early
111
+ brief must not include quoted first-message copy. If a message-shape hint is useful,
112
112
  keep it as non-final bullets and say: "This is not the message yet; I will write
113
113
  real examples after we find and filter leads."
114
114
 
@@ -120,8 +120,9 @@ watch-link handoff, then ask approve/revise.
120
120
  ## YOLO Mode
121
121
 
122
122
  Enable YOLO when the user asks for yolo/autopilot, passes `--yolo` or
123
- `mode=yolo`, selects `Approve + YOLO autopilot setup`, or asks you to use best
124
- guesses. If identity is missing, ask only for the LinkedIn profile URL; never
123
+ `mode=yolo`, selects `Approve brief + activate YOLO mode (Recommended)`, or asks
124
+ you to use best guesses. If identity is missing, ask only for the LinkedIn
125
+ profile URL; never
125
126
  continue from a website/domain alone. Infer buyer, offer/CTA, proof, source,
126
127
  filters, and message direction from evidence plus user directions; newest wins.
127
128
  Set `interactionMode: "autonomous"` once `campaignId` exists. Auto-select
@@ -364,9 +364,10 @@ wants to "sell first." Say "sender and company" for the person/company context,
364
364
  and ask what we should "pitch to prospects first" for the offer/CTA choice.
365
365
 
366
366
  At the brief approval gate, do not add a negative list of future steps that are
367
- not approved. Ask approve/revise/Approve + YOLO autopilot setup in plain
368
- language. The YOLO option approves the brief, continues with best guesses, and
369
- still stops before final launch.
367
+ not approved. Ask whether the brief looks right and how Sellable should continue:
368
+ activate YOLO mode, build the campaign with AI step by step, or revise the
369
+ brief. The YOLO option approves the brief, auto-decides the remaining setup
370
+ choices with best guesses, and still stops before final launch.
370
371
 
371
372
  At message review, if the user asks for edits, write the revised template and
372
373
  examples before asking approval again. Route the feedback back to Message