@sellable/mcp 0.1.307 → 0.1.309
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 +5 -1
- package/dist/generated/column-schema-manifest.js +1 -1
- package/dist/index-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/tools/bootstrap.js +7 -5
- package/dist/tools/campaigns.d.ts +1 -1
- package/dist/tools/model-quality.d.ts +24 -0
- package/dist/tools/model-quality.js +165 -25
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +16 -1
- package/skills/create-campaign/core/model-quality.json +28 -0
- package/skills/create-campaign-v2/core/flow.v2.json +1 -1
- package/skills/create-campaign-v2/references/approval-gate-framing.md +1 -1
- package/skills/create-campaign-v2-tail/SKILL.md +32 -18
- package/skills/generate-messages/SKILL.md +9 -4
|
@@ -1,9 +1,57 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { resolveSkillsDir } from "../skills.js";
|
|
4
|
+
const DEFAULT_MODEL_QUALITY_CONFIG = {
|
|
5
|
+
version: 1,
|
|
6
|
+
hosts: {
|
|
7
|
+
claude: {
|
|
8
|
+
label: "Claude Code",
|
|
9
|
+
minimumModel: "Claude Opus 4.8",
|
|
10
|
+
familyKeywords: ["opus"],
|
|
11
|
+
minimumVersion: "4.8",
|
|
12
|
+
minimumReasoningEffort: "high",
|
|
13
|
+
acceptedReasoningEfforts: [
|
|
14
|
+
"high",
|
|
15
|
+
"extra high",
|
|
16
|
+
"extra-high",
|
|
17
|
+
"xhigh",
|
|
18
|
+
"extra_high",
|
|
19
|
+
],
|
|
20
|
+
recommendedReasoningEffort: "high or better",
|
|
21
|
+
},
|
|
22
|
+
codex: {
|
|
23
|
+
label: "Codex",
|
|
24
|
+
minimumModel: "GPT 5.5",
|
|
25
|
+
familyKeywords: ["gpt"],
|
|
26
|
+
minimumVersion: "5.5",
|
|
27
|
+
minimumReasoningEffort: "high",
|
|
28
|
+
acceptedReasoningEfforts: [
|
|
29
|
+
"high",
|
|
30
|
+
"extra high",
|
|
31
|
+
"extra-high",
|
|
32
|
+
"xhigh",
|
|
33
|
+
"extra_high",
|
|
34
|
+
],
|
|
35
|
+
recommendedReasoningEffort: "high or better",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
warningCopy: {
|
|
39
|
+
ok: "Campaign model settings meet the configured minimum: {minimumSummary}.",
|
|
40
|
+
unknownSettings: "Model settings were not provided by the host. Best campaigns need at least {minimumSummary}. Confirm the user is on one of those settings, or ask them to switch before continuing.",
|
|
41
|
+
belowMinimum: "Best campaigns need at least {minimumSummary}. Current settings look below that: {currentSettings}. Please switch before continuing, or explicitly say to continue anyway.",
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
let cachedConfig = null;
|
|
1
45
|
const normalize = (value) => String(value ?? "")
|
|
2
46
|
.trim()
|
|
3
47
|
.toLowerCase();
|
|
4
48
|
const normalizeHost = (host) => {
|
|
5
49
|
const normalized = normalize(host);
|
|
6
|
-
if (normalized.includes("claude") ||
|
|
50
|
+
if (normalized.includes("claude") ||
|
|
51
|
+
normalized.includes("clod") ||
|
|
52
|
+
normalized.includes("opus") ||
|
|
53
|
+
normalized.includes("sonnet") ||
|
|
54
|
+
normalized.includes("haiku")) {
|
|
7
55
|
return "claude";
|
|
8
56
|
}
|
|
9
57
|
if (normalized.includes("codex") ||
|
|
@@ -13,20 +61,101 @@ const normalizeHost = (host) => {
|
|
|
13
61
|
}
|
|
14
62
|
return "unknown";
|
|
15
63
|
};
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
64
|
+
const normalizeReasoning = (reasoning) => normalize(reasoning).replace(/[_\s-]+/g, "");
|
|
65
|
+
const compareVersion = (candidate, minimum) => {
|
|
66
|
+
const candidateParts = candidate.split(".").map((part) => Number(part));
|
|
67
|
+
const minimumParts = minimum.split(".").map((part) => Number(part));
|
|
68
|
+
const length = Math.max(candidateParts.length, minimumParts.length);
|
|
69
|
+
for (let index = 0; index < length; index += 1) {
|
|
70
|
+
const candidatePart = candidateParts[index] ?? 0;
|
|
71
|
+
const minimumPart = minimumParts[index] ?? 0;
|
|
72
|
+
if (candidatePart > minimumPart)
|
|
73
|
+
return 1;
|
|
74
|
+
if (candidatePart < minimumPart)
|
|
75
|
+
return -1;
|
|
76
|
+
}
|
|
77
|
+
return 0;
|
|
23
78
|
};
|
|
79
|
+
const extractModelVersions = (model) => Array.from(normalize(model).matchAll(/\b(\d+(?:\.\d+){0,2})\b/g)).map((match) => match[1]);
|
|
80
|
+
function mergeConfig(rawConfig) {
|
|
81
|
+
return {
|
|
82
|
+
...DEFAULT_MODEL_QUALITY_CONFIG,
|
|
83
|
+
...rawConfig,
|
|
84
|
+
hosts: {
|
|
85
|
+
claude: {
|
|
86
|
+
...DEFAULT_MODEL_QUALITY_CONFIG.hosts.claude,
|
|
87
|
+
...(rawConfig.hosts?.claude || {}),
|
|
88
|
+
},
|
|
89
|
+
codex: {
|
|
90
|
+
...DEFAULT_MODEL_QUALITY_CONFIG.hosts.codex,
|
|
91
|
+
...(rawConfig.hosts?.codex || {}),
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
warningCopy: {
|
|
95
|
+
...DEFAULT_MODEL_QUALITY_CONFIG.warningCopy,
|
|
96
|
+
...(rawConfig.warningCopy || {}),
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function getCampaignModelQualityConfig() {
|
|
101
|
+
if (cachedConfig)
|
|
102
|
+
return cachedConfig;
|
|
103
|
+
const configPath = join(resolveSkillsDir(), "create-campaign", "core", "model-quality.json");
|
|
104
|
+
if (!existsSync(configPath)) {
|
|
105
|
+
cachedConfig = DEFAULT_MODEL_QUALITY_CONFIG;
|
|
106
|
+
return cachedConfig;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
cachedConfig = mergeConfig(JSON.parse(readFileSync(configPath, "utf-8")));
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
cachedConfig = DEFAULT_MODEL_QUALITY_CONFIG;
|
|
113
|
+
}
|
|
114
|
+
return cachedConfig;
|
|
115
|
+
}
|
|
116
|
+
export function getCampaignModelMinimumSummary(config = getCampaignModelQualityConfig()) {
|
|
117
|
+
return `${config.hosts.claude.minimumModel} with ${config.hosts.claude.minimumReasoningEffort} reasoning or ${config.hosts.codex.minimumModel} with ${config.hosts.codex.minimumReasoningEffort} reasoning`;
|
|
118
|
+
}
|
|
119
|
+
function formatCopy(template, values) {
|
|
120
|
+
return Object.entries(values).reduce((text, [key, value]) => text.replaceAll(`{${key}}`, value), template);
|
|
121
|
+
}
|
|
122
|
+
function acceptsReasoning(reasoning, hostConfig) {
|
|
123
|
+
const normalized = normalizeReasoning(reasoning);
|
|
124
|
+
return hostConfig.acceptedReasoningEfforts
|
|
125
|
+
.map((value) => normalizeReasoning(value))
|
|
126
|
+
.includes(normalized);
|
|
127
|
+
}
|
|
128
|
+
function modelMeetsMinimum(model, hostConfig) {
|
|
129
|
+
const normalizedModel = normalize(model).replace(/[_-]+/g, " ");
|
|
130
|
+
if (!normalizedModel)
|
|
131
|
+
return false;
|
|
132
|
+
const familyMatches = hostConfig.familyKeywords.every((keyword) => normalizedModel.includes(normalize(keyword)));
|
|
133
|
+
if (!familyMatches)
|
|
134
|
+
return false;
|
|
135
|
+
const versions = extractModelVersions(normalizedModel);
|
|
136
|
+
if (!hostConfig.minimumVersion)
|
|
137
|
+
return true;
|
|
138
|
+
return versions.some((version) => compareVersion(version, hostConfig.minimumVersion) >= 0);
|
|
139
|
+
}
|
|
140
|
+
function findAcceptedHostConfig(host, model, config) {
|
|
141
|
+
const candidates = host === "unknown"
|
|
142
|
+
? [
|
|
143
|
+
["claude", config.hosts.claude],
|
|
144
|
+
["codex", config.hosts.codex],
|
|
145
|
+
]
|
|
146
|
+
: [[host, config.hosts[host]]];
|
|
147
|
+
return candidates.find(([, hostConfig]) => modelMeetsMinimum(model, hostConfig));
|
|
148
|
+
}
|
|
24
149
|
export function evaluateCampaignModelQuality(input = {}) {
|
|
25
|
-
const
|
|
150
|
+
const config = getCampaignModelQualityConfig();
|
|
151
|
+
const host = normalizeHost([input.host, input.model].filter(Boolean).join(" "));
|
|
26
152
|
const model = input.model?.trim() || null;
|
|
27
153
|
const reasoningEffort = input.reasoningEffort?.trim() || null;
|
|
28
|
-
const
|
|
29
|
-
const
|
|
154
|
+
const recommendationHost = host === "claude" ? "claude" : "codex";
|
|
155
|
+
const recommendedHostConfig = config.hosts[recommendationHost];
|
|
156
|
+
const minimumSummary = getCampaignModelMinimumSummary(config);
|
|
157
|
+
const recommendedModel = recommendedHostConfig.minimumModel;
|
|
158
|
+
const recommendedReasoningEffort = recommendedHostConfig.recommendedReasoningEffort;
|
|
30
159
|
if (!model && !reasoningEffort) {
|
|
31
160
|
return {
|
|
32
161
|
status: "unknown",
|
|
@@ -35,30 +164,37 @@ export function evaluateCampaignModelQuality(input = {}) {
|
|
|
35
164
|
reasoningEffort,
|
|
36
165
|
recommendedModel,
|
|
37
166
|
recommendedReasoningEffort,
|
|
167
|
+
minimumSummary,
|
|
38
168
|
confirmationRequired: true,
|
|
39
|
-
message:
|
|
169
|
+
message: formatCopy(config.warningCopy.unknownSettings, {
|
|
170
|
+
minimumSummary,
|
|
171
|
+
currentSettings: "unknown",
|
|
172
|
+
}),
|
|
40
173
|
};
|
|
41
174
|
}
|
|
42
|
-
const
|
|
43
|
-
const ok =
|
|
44
|
-
? normalizedModel.includes("opus") &&
|
|
45
|
-
normalizedModel.includes("4.8") &&
|
|
46
|
-
isHighClaudeReasoning(reasoningEffort)
|
|
47
|
-
: normalizedModel.includes("gpt") &&
|
|
48
|
-
normalizedModel.includes("5.5") &&
|
|
49
|
-
isExtraHighCodexReasoning(reasoningEffort);
|
|
175
|
+
const acceptedHostConfig = findAcceptedHostConfig(host, model, config);
|
|
176
|
+
const ok = Boolean(acceptedHostConfig && acceptsReasoning(reasoningEffort, acceptedHostConfig[1]));
|
|
50
177
|
if (ok) {
|
|
51
178
|
return {
|
|
52
179
|
status: "ok",
|
|
53
|
-
host,
|
|
180
|
+
host: acceptedHostConfig?.[0] ?? host,
|
|
54
181
|
model,
|
|
55
182
|
reasoningEffort,
|
|
56
|
-
recommendedModel,
|
|
57
|
-
recommendedReasoningEffort
|
|
183
|
+
recommendedModel: acceptedHostConfig?.[1].minimumModel ?? recommendedModel,
|
|
184
|
+
recommendedReasoningEffort: acceptedHostConfig?.[1].recommendedReasoningEffort ??
|
|
185
|
+
recommendedReasoningEffort,
|
|
186
|
+
minimumSummary,
|
|
58
187
|
confirmationRequired: false,
|
|
59
|
-
message:
|
|
188
|
+
message: formatCopy(config.warningCopy.ok, {
|
|
189
|
+
minimumSummary,
|
|
190
|
+
currentSettings: "current settings",
|
|
191
|
+
}),
|
|
60
192
|
};
|
|
61
193
|
}
|
|
194
|
+
const currentSettings = [
|
|
195
|
+
model ? `model "${model}"` : "unknown model",
|
|
196
|
+
reasoningEffort ? `reasoning "${reasoningEffort}"` : "unknown reasoning",
|
|
197
|
+
].join(", ");
|
|
62
198
|
return {
|
|
63
199
|
status: "warn",
|
|
64
200
|
host,
|
|
@@ -66,7 +202,11 @@ export function evaluateCampaignModelQuality(input = {}) {
|
|
|
66
202
|
reasoningEffort,
|
|
67
203
|
recommendedModel,
|
|
68
204
|
recommendedReasoningEffort,
|
|
205
|
+
minimumSummary,
|
|
69
206
|
confirmationRequired: true,
|
|
70
|
-
message:
|
|
207
|
+
message: formatCopy(config.warningCopy.belowMinimum, {
|
|
208
|
+
minimumSummary,
|
|
209
|
+
currentSettings,
|
|
210
|
+
}),
|
|
71
211
|
};
|
|
72
212
|
}
|
package/package.json
CHANGED
|
@@ -114,6 +114,12 @@ sends", use `approvalMode:"approve"` to approve exactly the bounded X-message
|
|
|
114
114
|
cohort during preparation, then continue through sender, sequence, and final
|
|
115
115
|
launch greenlight; the launch path must verify that bounded cohort and must not
|
|
116
116
|
broad approve-all.
|
|
117
|
+
When approving reviewed draft rows in the campaign table, resolve the actual
|
|
118
|
+
visible `Approved` cells with `select_campaign_cells({ columnRole: "approved",
|
|
119
|
+
rowSelector: { type: "rowIds", rowIds } })` and `update_cell` those returned
|
|
120
|
+
cell IDs. Do not rely on `approveCellId` from `get_rows` unless it matches the
|
|
121
|
+
semantic selector result; it may be a row-level helper and leave the UI checkbox
|
|
122
|
+
unchecked.
|
|
117
123
|
Treat `campaignId` as `CampaignOffer.id`. Low level selector/queue tools are
|
|
118
124
|
diagnostics and recovery only for this lane. If the user asks to stop
|
|
119
125
|
preparation, the target is wrong, or status shows the wrong campaign/table, call
|
|
@@ -168,6 +174,12 @@ person/company this campaign is for, then I’ll turn that into a campaign brief
|
|
|
168
174
|
before we move into lead sourcing.
|
|
169
175
|
```
|
|
170
176
|
|
|
177
|
+
Exception: if `bootstrap_create_campaign.modelQuality.status === "warn"`,
|
|
178
|
+
the first visible campaign message must be the model-quality warning from
|
|
179
|
+
`modelQuality.message`. Ask the user to switch to the configured minimum model
|
|
180
|
+
or explicitly continue anyway before identity setup, research, lead filtering,
|
|
181
|
+
message generation, or launch review.
|
|
182
|
+
|
|
171
183
|
If a linked/local skill file is stale or missing, silently use the installed
|
|
172
184
|
`sellable@sellable` plugin copy. Do not tell the user about the stale link,
|
|
173
185
|
the old version, or the replacement path.
|
|
@@ -912,8 +924,11 @@ updates.
|
|
|
912
924
|
- Do not call `mcp__sellable__get_campaigns`.
|
|
913
925
|
- Do not call `mcp__sellable__get_campaign` to hunt for IDs.
|
|
914
926
|
- Do not call `mcp__sellable__create_campaign({ campaignId: ... })` unless the user supplied that id.
|
|
915
|
-
6. Call `mcp__sellable__bootstrap_create_campaign({ flowVersion: "v2", campaignId? })`.
|
|
927
|
+
6. Call `mcp__sellable__bootstrap_create_campaign({ flowVersion: "v2", campaignId?, host?, model?, reasoningEffort? })`.
|
|
928
|
+
Pass the current host, model, and reasoning when the host exposes them.
|
|
916
929
|
7. If `safeToProceed !== true`, stop and show `blockingErrors` + `nextStep`.
|
|
930
|
+
8. If `modelQuality.status === "warn"`, show `modelQuality.message` before any
|
|
931
|
+
setup/research and wait for the user to switch or explicitly continue.
|
|
917
932
|
|
|
918
933
|
## Execute Workflow
|
|
919
934
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"hosts": {
|
|
4
|
+
"claude": {
|
|
5
|
+
"label": "Claude Code",
|
|
6
|
+
"minimumModel": "Claude Opus 4.8",
|
|
7
|
+
"familyKeywords": ["opus"],
|
|
8
|
+
"minimumVersion": "4.8",
|
|
9
|
+
"minimumReasoningEffort": "high",
|
|
10
|
+
"acceptedReasoningEfforts": ["high", "extra high", "extra-high", "xhigh", "extra_high"],
|
|
11
|
+
"recommendedReasoningEffort": "high or better"
|
|
12
|
+
},
|
|
13
|
+
"codex": {
|
|
14
|
+
"label": "Codex",
|
|
15
|
+
"minimumModel": "GPT 5.5",
|
|
16
|
+
"familyKeywords": ["gpt"],
|
|
17
|
+
"minimumVersion": "5.5",
|
|
18
|
+
"minimumReasoningEffort": "high",
|
|
19
|
+
"acceptedReasoningEfforts": ["high", "extra high", "extra-high", "xhigh", "extra_high"],
|
|
20
|
+
"recommendedReasoningEffort": "high or better"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"warningCopy": {
|
|
24
|
+
"ok": "Campaign model settings meet the configured minimum: {minimumSummary}.",
|
|
25
|
+
"unknownSettings": "Model settings were not provided by the host. Best campaigns need at least {minimumSummary}. Confirm the user is on one of those settings, or ask them to switch before continuing.",
|
|
26
|
+
"belowMinimum": "Best campaigns need at least {minimumSummary}. Current settings look below that: {currentSettings}. Please switch before continuing, or explicitly say to continue anyway."
|
|
27
|
+
}
|
|
28
|
+
}
|