@sellable/mcp 0.1.137 → 0.1.139

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/README.md CHANGED
@@ -16,14 +16,20 @@ Each message gets 5+ minutes of Claude attention with deep research - no other t
16
16
 
17
17
  ### Prompt Source Of Truth
18
18
 
19
- There is one public create-campaign entrypoint shared across hosts:
19
+ There are two public Sellable entrypoints shared across hosts:
20
20
 
21
21
  - `sellable:create-campaign`
22
+ - `sellable:interview`
22
23
 
23
- The public wrapper loads the approval-gated workflow from:
24
+ The create-campaign public wrapper loads the approval-gated workflow from:
24
25
 
25
26
  - `mcp/sellable/skills/create-campaign-v2/SKILL.md`
26
27
 
28
+ The interview public wrapper loads the core identity/company memory workflow
29
+ from:
30
+
31
+ - `mcp/sellable/skills/interview/SKILL.md`
32
+
27
33
  The older `mcp/sellable/skills/create-campaign/SKILL.md` prompt is kept as an
28
34
  internal legacy prompt for compatibility only. Do not advertise it as a public
29
35
  command.
@@ -120,8 +126,8 @@ The installer does the full local setup:
120
126
  `mcp__sellable__*` tools into skill sessions
121
127
 
122
128
  After the installer passes, fully quit and reopen Codex Desktop. Start a new
123
- thread and select `Sellable Create Campaign`, or invoke
124
- `$sellable:create-campaign`. If the app still says
129
+ thread and select `Sellable Create Campaign` / `Sellable Identity Interview`, or
130
+ invoke `$sellable:create-campaign` / `$sellable:interview`. If the app still says
125
131
  `mcp__sellable__*` tools are missing after the installer passes, check that
126
132
  `~/.codex/config.toml` contains both `[marketplaces.sellable]` and
127
133
  `[plugins."sellable@sellable"]`.
@@ -133,10 +139,14 @@ the Sellable MCP tools for Codex Desktop.
133
139
  Use these names consistently:
134
140
 
135
141
  - Claude Code command: `/sellable:create-campaign`
142
+ - Claude Code command: `/sellable:interview`
136
143
  - Codex command: `$sellable:create-campaign`
144
+ - Codex command: `$sellable:interview`
137
145
  - Codex Desktop plugin: `sellable@sellable`
138
146
  - Codex visible skill: `Sellable Create Campaign`
147
+ - Codex visible skill: `Sellable Identity Interview`
139
148
  - Codex skill frontmatter name: `create-campaign`
149
+ - Codex skill frontmatter name: `interview`
140
150
  - MCP server name: `sellable`
141
151
  - Internal workflow prompt: `create-campaign-v2`
142
152
 
@@ -332,7 +342,10 @@ Primary public entrypoint for the approval-gated campaign creation flow.
332
342
 
333
343
  ### interview
334
344
 
335
- Self-interview skill to capture voice + ICP + boundaries into `./.sellable/configs/writing/styleguide.md` and persist it into MCP memory for future sessions.
345
+ Public identity/company memory interview. Builds durable core files under
346
+ `./.sellable/configs/core/**`, raw archives under `./.sellable/interviews/**`,
347
+ and reusable answer/proof/story/transcript/reference memory for downstream
348
+ Sellable writing workflows.
336
349
 
337
350
  ## Available Tools
338
351
 
@@ -1,3 +1,4 @@
1
+ import { type IdentityMemoryChunk, type IdentityMemoryDirectory } from "./identity-memory.js";
1
2
  export interface EngageStyleGuide {
2
3
  markdown: string;
3
4
  updatedAt: string;
@@ -20,6 +21,18 @@ export interface EngageMemory {
20
21
  styleGuide?: EngageStyleGuide;
21
22
  provenSearches: ProvenSearch[];
22
23
  trackedPeople: TrackedPerson[];
24
+ identity?: IdentityMemoryChunk;
25
+ company?: IdentityMemoryChunk;
26
+ antiAiStyle?: IdentityMemoryChunk;
27
+ proofLedger?: IdentityMemoryChunk;
28
+ winsLedger?: IdentityMemoryChunk;
29
+ storyBank?: IdentityMemoryChunk;
30
+ answerBank?: IdentityMemoryChunk;
31
+ contextModes?: IdentityMemoryChunk;
32
+ decisionRules?: IdentityMemoryChunk;
33
+ changeLog?: IdentityMemoryChunk;
34
+ transcripts?: IdentityMemoryDirectory;
35
+ references?: IdentityMemoryDirectory;
23
36
  }
24
37
  /** Check if a sender has per-sender config directory. */
25
38
  export declare function senderHasConfig(senderId: string): boolean;
@@ -1,5 +1,7 @@
1
1
  import * as fs from "fs";
2
+ import * as os from "os";
2
3
  import * as path from "path";
4
+ import { readIdentityMemory, } from "./identity-memory.js";
3
5
  // ── Config directory resolution ────────────────────────────────────────────
4
6
  function resolveConfigsDir() {
5
7
  const candidates = [];
@@ -11,6 +13,7 @@ function resolveConfigsDir() {
11
13
  candidates.push(path.resolve(path.dirname(process.argv[1]), "../../.sellable/configs"));
12
14
  }
13
15
  candidates.push(path.resolve(process.cwd(), ".sellable/configs"));
16
+ candidates.push(path.resolve(os.homedir(), ".sellable/configs"));
14
17
  for (const candidate of candidates) {
15
18
  if (fs.existsSync(candidate)) {
16
19
  return candidate;
@@ -44,22 +47,21 @@ function writeFile(relPath, content) {
44
47
  fs.writeFileSync(fullPath, content);
45
48
  }
46
49
  // ── Sender-scoped path resolution ─────────────────────────────────────────
47
- /** Per-sender file paths. When senderId is given, resolves to senders/{id}/file.
48
- * Falls back to legacy flat path when senderId is omitted or sender dir doesn't exist. */
50
+ /** Per-sender reads fall back by file so partial sender dirs do not hide flat memory. */
49
51
  function senderPath(senderId, fileName, flatFallback) {
50
52
  if (senderId) {
51
- const senderDir = `senders/${senderId}`;
52
- const senderFile = `${senderDir}/${fileName}`;
53
- // If sender dir exists, always use it (even if file doesn't exist yet — it'll be created)
54
- if (fs.existsSync(path.join(getConfigsDir(), senderDir))) {
55
- return senderFile;
56
- }
57
- // If the file exists in sender dir (dir just created), use it
53
+ const senderFile = `senders/${senderId}/${fileName}`;
58
54
  if (fs.existsSync(path.join(getConfigsDir(), senderFile))) {
59
55
  return senderFile;
60
56
  }
61
57
  }
62
- // Fallback to flat path
58
+ return flatFallback;
59
+ }
60
+ /** Per-sender writes are deterministic when a sender ID is supplied. */
61
+ function senderWritePath(senderId, fileName, flatFallback) {
62
+ if (senderId) {
63
+ return `senders/${senderId}/${fileName}`;
64
+ }
63
65
  return flatFallback;
64
66
  }
65
67
  /** Check if a sender has per-sender config directory. */
@@ -119,39 +121,132 @@ function buildMarkdownTable(headers, rows) {
119
121
  // ── Style guide ────────────────────────────────────────────────────────────
120
122
  const FLAT_STYLE_CORE_PATH = "writing/styleguide-core.md";
121
123
  const STYLE_COMMENTS_PATH = "writing/comments.md";
122
- function readStyleGuide(senderId) {
124
+ const LEGACY_AUDIENCE_ICP_PATH = "audience/icp.md";
125
+ const LEGACY_FAQS_CORE_PATH = "faqs/core.md";
126
+ function readStyleGuide(senderId, identityMemory) {
123
127
  const corePath = senderPath(senderId, "styleguide-core.md", FLAT_STYLE_CORE_PATH);
124
128
  const core = readFile(corePath);
125
129
  // comments.md is always shared
126
130
  const comments = readFile(STYLE_COMMENTS_PATH);
127
- if (!core && !comments)
128
- return undefined;
131
+ if (!hasCoreIdentityMemory(identityMemory)) {
132
+ if (!core && !comments)
133
+ return undefined;
134
+ const legacyParts = [];
135
+ if (core)
136
+ legacyParts.push(core.trim());
137
+ if (comments)
138
+ legacyParts.push(comments.trim());
139
+ return {
140
+ markdown: legacyParts.join("\n\n"),
141
+ updatedAt: latestUpdatedAt([corePath, STYLE_COMMENTS_PATH]),
142
+ };
143
+ }
129
144
  const parts = [];
130
- if (core)
131
- parts.push(core.trim());
132
- if (comments)
133
- parts.push(comments.trim());
134
- const markdown = parts.join("\n\n");
135
- // Use file mtime as updatedAt
136
- const coreFullPath = path.join(getConfigsDir(), corePath);
137
- const commentsFullPath = path.join(getConfigsDir(), STYLE_COMMENTS_PATH);
138
- const coreMtime = fs.existsSync(coreFullPath)
139
- ? fs.statSync(coreFullPath).mtime
140
- : new Date(0);
141
- const commentsMtime = fs.existsSync(commentsFullPath)
142
- ? fs.statSync(commentsFullPath).mtime
143
- : new Date(0);
144
- const updatedAt = new Date(Math.max(coreMtime.getTime(), commentsMtime.getTime())).toISOString();
145
- return { markdown, updatedAt };
145
+ const sourcePaths = [];
146
+ parts.push("# Sellable Core Identity Memory");
147
+ appendCoreSection(parts, sourcePaths, "Core Identity", identityMemory.identity);
148
+ appendCoreSection(parts, sourcePaths, "Core Company", identityMemory.company);
149
+ appendCoreSection(parts, sourcePaths, "Core Anti-AI Writing Style", identityMemory.antiAiStyle);
150
+ appendCoreSection(parts, sourcePaths, "Core Proof Ledger", identityMemory.proofLedger);
151
+ appendCoreSection(parts, sourcePaths, "Core Wins Ledger", identityMemory.winsLedger);
152
+ appendCoreSection(parts, sourcePaths, "Core Story Bank", identityMemory.storyBank);
153
+ appendCoreSection(parts, sourcePaths, "Core Answer Bank", identityMemory.answerBank);
154
+ appendCoreSection(parts, sourcePaths, "Core Context Modes", identityMemory.contextModes);
155
+ appendCoreSection(parts, sourcePaths, "Core Decision Rules", identityMemory.decisionRules);
156
+ appendCoreSection(parts, sourcePaths, "Core Change Log", identityMemory.changeLog);
157
+ appendCoreDirectoryIndex(parts, sourcePaths, "Core Transcripts Index", identityMemory.transcripts);
158
+ appendCoreDirectoryIndex(parts, sourcePaths, "Core References Index", identityMemory.references);
159
+ if (core) {
160
+ appendCompatibilitySection(parts, sourcePaths, {
161
+ title: senderId
162
+ ? "Legacy Compatibility: Connected Sender Style Guide"
163
+ : "Legacy Compatibility: Style Guide",
164
+ relativePath: corePath,
165
+ markdown: core,
166
+ });
167
+ }
168
+ if (comments) {
169
+ appendCompatibilitySection(parts, sourcePaths, {
170
+ title: "Legacy Compatibility: Comment Rules",
171
+ relativePath: STYLE_COMMENTS_PATH,
172
+ markdown: comments,
173
+ });
174
+ }
175
+ const legacyAudienceIcp = readFile(LEGACY_AUDIENCE_ICP_PATH);
176
+ if (!identityMemory.company && legacyAudienceIcp) {
177
+ appendCompatibilitySection(parts, sourcePaths, {
178
+ title: "Legacy Compatibility: Audience ICP",
179
+ relativePath: LEGACY_AUDIENCE_ICP_PATH,
180
+ markdown: legacyAudienceIcp,
181
+ });
182
+ }
183
+ const legacyFaqsCore = readFile(LEGACY_FAQS_CORE_PATH);
184
+ if (!identityMemory.proofLedger && legacyFaqsCore) {
185
+ appendCompatibilitySection(parts, sourcePaths, {
186
+ title: "Legacy Compatibility: FAQ Proof",
187
+ relativePath: LEGACY_FAQS_CORE_PATH,
188
+ markdown: legacyFaqsCore,
189
+ });
190
+ }
191
+ return {
192
+ markdown: parts.join("\n\n"),
193
+ updatedAt: latestUpdatedAt(sourcePaths),
194
+ };
195
+ }
196
+ function hasCoreIdentityMemory(identityMemory) {
197
+ return Boolean(identityMemory.identity ||
198
+ identityMemory.company ||
199
+ identityMemory.antiAiStyle ||
200
+ identityMemory.proofLedger ||
201
+ identityMemory.winsLedger ||
202
+ identityMemory.storyBank ||
203
+ identityMemory.answerBank ||
204
+ identityMemory.contextModes ||
205
+ identityMemory.decisionRules ||
206
+ identityMemory.changeLog ||
207
+ identityMemory.transcripts?.index ||
208
+ identityMemory.references?.index);
209
+ }
210
+ function appendCoreSection(parts, sourcePaths, title, chunk) {
211
+ if (!chunk)
212
+ return;
213
+ parts.push(`## ${title}: ${chunk.source.relativePath}\n\n${chunk.markdown.trim()}`);
214
+ sourcePaths.push(chunk.source.relativePath);
215
+ }
216
+ function appendCoreDirectoryIndex(parts, sourcePaths, title, directory) {
217
+ if (!directory?.index)
218
+ return;
219
+ appendCoreSection(parts, sourcePaths, title, directory.index);
220
+ }
221
+ function appendCompatibilitySection(parts, sourcePaths, section) {
222
+ parts.push(`## ${section.title} (${section.relativePath})\n\n${section.markdown.trim()}`);
223
+ sourcePaths.push(section.relativePath);
224
+ }
225
+ function latestUpdatedAt(relativePaths) {
226
+ const latest = relativePaths.reduce((max, relativePath) => {
227
+ const fullPath = path.join(getConfigsDir(), relativePath);
228
+ if (!fs.existsSync(fullPath))
229
+ return max;
230
+ return Math.max(max, fs.statSync(fullPath).mtime.getTime());
231
+ }, 0);
232
+ return new Date(latest).toISOString();
146
233
  }
147
234
  export function getEngageMemory(senderId) {
148
- const styleGuide = readStyleGuide(senderId);
235
+ const identityMemory = readIdentityMemory({ connectedSenderId: senderId });
236
+ const styleGuide = readStyleGuide(senderId, identityMemory);
149
237
  const provenSearches = readProvenSearches(senderId);
150
238
  const trackedPeople = readTrackedPeople(senderId);
151
- return { memory: { styleGuide, provenSearches, trackedPeople } };
239
+ return {
240
+ memory: {
241
+ styleGuide,
242
+ provenSearches,
243
+ trackedPeople,
244
+ ...identityMemory,
245
+ },
246
+ };
152
247
  }
153
248
  export function setStyleGuide(markdown, senderId) {
154
- const corePath = senderPath(senderId, "styleguide-core.md", FLAT_STYLE_CORE_PATH);
249
+ const corePath = senderWritePath(senderId, "styleguide-core.md", FLAT_STYLE_CORE_PATH);
155
250
  writeFile(corePath, markdown + "\n");
156
251
  const updatedAt = new Date().toISOString();
157
252
  return { styleGuide: { markdown, updatedAt } };
@@ -202,7 +297,7 @@ function writeProvenSearches(searches, senderId) {
202
297
  Yielded: String(s.totalYielded),
203
298
  "Hit Rate": s.avgHitRate.toFixed(2),
204
299
  }));
205
- const filePath = senderPath(senderId, "proven-searches.md", FLAT_PROVEN_SEARCHES_PATH);
300
+ const filePath = senderWritePath(senderId, "proven-searches.md", FLAT_PROVEN_SEARCHES_PATH);
206
301
  const table = buildMarkdownTable(PROVEN_HEADERS, rows);
207
302
  writeFile(filePath, PROVEN_SEARCHES_HEADER + table + "\n");
208
303
  }
@@ -251,7 +346,7 @@ const INFLUENCER_HEADERS = [
251
346
  "Include in Discovery",
252
347
  ];
253
348
  function readTrackedPeople(senderId) {
254
- const filePath = senderPath(senderId, "influencers.md", FLAT_INFLUENCERS_PATH);
349
+ const filePath = senderWritePath(senderId, "influencers.md", FLAT_INFLUENCERS_PATH);
255
350
  const content = readFile(filePath);
256
351
  if (!content)
257
352
  return [];
@@ -0,0 +1,119 @@
1
+ export interface IdentityGeneratedSectionProposal {
2
+ relativePath: string;
3
+ key: string;
4
+ markdown: string;
5
+ }
6
+ export interface IdentityProofEntryProposal {
7
+ key?: string;
8
+ claim: string;
9
+ source?: string;
10
+ confidence?: string;
11
+ allowedUse?: string;
12
+ privacy?: string;
13
+ notes?: string;
14
+ }
15
+ export interface IdentityWinEntryProposal {
16
+ key?: string;
17
+ title: string;
18
+ proofType?: string;
19
+ outcome?: string;
20
+ source?: string;
21
+ permission?: string;
22
+ confidence?: string;
23
+ publicSafety?: string;
24
+ allowedUse?: string;
25
+ notes?: string;
26
+ }
27
+ export interface IdentityStoryEntryProposal {
28
+ key?: string;
29
+ title: string;
30
+ setup?: string;
31
+ moment?: string;
32
+ lesson?: string;
33
+ source?: string;
34
+ contexts?: string;
35
+ privacy?: string;
36
+ notes?: string;
37
+ }
38
+ export interface IdentityAnswerEntryProposal {
39
+ key?: string;
40
+ question: string;
41
+ answer: string;
42
+ topicTags?: string;
43
+ source?: string;
44
+ useContexts?: string;
45
+ confidence?: string;
46
+ privacy?: string;
47
+ derivedMemoryLinks?: string;
48
+ }
49
+ export interface IdentityRuleEntryProposal {
50
+ key?: string;
51
+ title: string;
52
+ rule: string;
53
+ source?: string;
54
+ }
55
+ export interface IdentityAntiAiBanProposal {
56
+ key?: string;
57
+ pattern: string;
58
+ rule: string;
59
+ context?: string;
60
+ source?: string;
61
+ }
62
+ export interface IdentityChangeLogEntryProposal {
63
+ key?: string;
64
+ date: string;
65
+ source?: string;
66
+ changed: string;
67
+ newRule: string;
68
+ }
69
+ export interface IdentityTranscriptIndexRowProposal {
70
+ key?: string;
71
+ title: string;
72
+ sourcePath: string;
73
+ date?: string;
74
+ speakers?: string;
75
+ targetSpeaker?: string;
76
+ topics?: string;
77
+ contextMode?: string;
78
+ sampleType?: string;
79
+ sensitivity?: string;
80
+ }
81
+ export interface IdentityTopicTranscriptProposal {
82
+ key?: string;
83
+ topicSlug: string;
84
+ title: string;
85
+ sourcePath: string;
86
+ targetSpeaker?: string;
87
+ excerpt: string;
88
+ }
89
+ export interface IdentityReferenceRowProposal {
90
+ key?: string;
91
+ category?: string;
92
+ indexPath?: string;
93
+ title: string;
94
+ source: string;
95
+ copiedPath?: string;
96
+ tags?: string;
97
+ whyItMatters?: string;
98
+ usageContexts?: string;
99
+ lastReviewed?: string;
100
+ }
101
+ export interface IdentityCompilerProposal {
102
+ configsDir?: string;
103
+ generatedSections?: IdentityGeneratedSectionProposal[];
104
+ proofEntries?: IdentityProofEntryProposal[];
105
+ winsEntries?: IdentityWinEntryProposal[];
106
+ storyEntries?: IdentityStoryEntryProposal[];
107
+ answerEntries?: IdentityAnswerEntryProposal[];
108
+ decisionRules?: IdentityRuleEntryProposal[];
109
+ antiAiBans?: IdentityAntiAiBanProposal[];
110
+ changeLogEntries?: IdentityChangeLogEntryProposal[];
111
+ transcriptIndexRows?: IdentityTranscriptIndexRowProposal[];
112
+ topicTranscripts?: IdentityTopicTranscriptProposal[];
113
+ referenceRows?: IdentityReferenceRowProposal[];
114
+ }
115
+ export interface IdentityCompilerResult {
116
+ configsDir: string;
117
+ filesChanged: string[];
118
+ }
119
+ export declare function applyIdentityCompilerProposal(proposal: IdentityCompilerProposal): IdentityCompilerResult;