@sellable/mcp 0.1.26 → 0.1.28
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 +22 -4
- package/dist/index-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/server.js +51 -3
- package/dist/tools/auth.d.ts +2 -0
- package/dist/tools/auth.js +20 -3
- package/dist/tools/bootstrap.d.ts +1 -1
- package/dist/tools/bootstrap.js +22 -3
- package/dist/tools/leads.js +11 -3
- package/dist/tools/prompts.d.ts +12 -0
- package/dist/tools/prompts.js +36 -10
- package/dist/update-check.d.ts +25 -0
- package/dist/update-check.js +210 -0
- package/package.json +1 -1
- package/skills/create-campaign-brief/references/examples/briefs/clover.md +1 -1
- package/skills/create-campaign-brief/references/examples/briefs/galley.md +1 -1
- package/skills/create-campaign-brief/references/examples/briefs/gelee.md +1 -1
- package/skills/create-campaign-brief/references/examples/briefs/hey-digital.md +1 -1
- package/skills/create-campaign-brief/references/examples/briefs/persona.md +1 -1
- package/skills/create-campaign-brief/references/examples/briefs/revvix.md +1 -1
- package/skills/create-campaign-brief/references/examples/briefs/sellable-dev.md +1 -1
- package/skills/create-campaign-brief/references/examples/briefs/superposition.md +1 -1
- package/skills/create-campaign-brief/references/examples/briefs/superpower.md +1 -1
- package/skills/create-campaign-brief/references/examples/briefs/westpark-villas.md +1 -1
- package/skills/create-campaign-brief/references/phase75-canonical-brief-template.md +1 -1
- package/skills/create-campaign-brief/references/phase75-good-brief-and-messaging-examples.md +4 -4
- package/skills/create-campaign-v2/SKILL.md +28 -14
- package/skills/create-campaign-v2/core/flow.v2.json +2 -2
- package/skills/create-campaign-v2/references/step-13-import-leads.md +6 -2
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ command.
|
|
|
33
33
|
For Claude Code and Codex CLI:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
npx -y @sellable/install --host all --token skt_live_your_token_here --workspace-id your_workspace_id
|
|
36
|
+
npx -y @sellable/install@latest --host all --token skt_live_your_token_here --workspace-id your_workspace_id
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
Agent-readable install instructions are available at:
|
|
@@ -52,6 +52,24 @@ sh /tmp/sellable-install.sh
|
|
|
52
52
|
Phase 112 v1 uses package stdio MCP through `@sellable/mcp`. Hosted HTTP
|
|
53
53
|
MCP is a future/advanced mode until the hosted endpoint exists.
|
|
54
54
|
|
|
55
|
+
Package-mode installs launch `@sellable/mcp@latest`, so each fresh host MCP
|
|
56
|
+
start resolves the newest stable npm release. The MCP server also runs a cached
|
|
57
|
+
startup/auth update check and tells the agent to run
|
|
58
|
+
`npx -y @sellable/install@latest` when the installed runtime is behind npm.
|
|
59
|
+
|
|
60
|
+
### Publishing `@sellable/mcp`
|
|
61
|
+
|
|
62
|
+
Publish from the repo root with:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm run mcp:publish
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The script loads `.env` and `prod.env`, builds `mcp/sellable`, verifies npm auth,
|
|
69
|
+
and publishes with a temporary npm config file. Prefer `NPM_TOKEN=<npm token>` in
|
|
70
|
+
`prod.env`; `NODE_AUTH_TOKEN`, `SELLABLE_NPM_TOKEN`, and `NPM_PUBLISH_TOKEN` are
|
|
71
|
+
also supported.
|
|
72
|
+
|
|
55
73
|
### 2. Generate API Token
|
|
56
74
|
|
|
57
75
|
1. Go to https://app.sellable.dev/settings
|
|
@@ -63,7 +81,7 @@ MCP is a future/advanced mode until the hosted endpoint exists.
|
|
|
63
81
|
Install MCP server:
|
|
64
82
|
|
|
65
83
|
```bash
|
|
66
|
-
claude mcp add --transport stdio sellable -- npx -y @sellable/mcp
|
|
84
|
+
claude mcp add --transport stdio sellable -- npx -y @sellable/mcp@latest
|
|
67
85
|
```
|
|
68
86
|
|
|
69
87
|
Create auth config at `~/.sellable/config.json`:
|
|
@@ -83,14 +101,14 @@ The token is provided when you generate it. Use `list_workspaces` +
|
|
|
83
101
|
For customer/package installs, use the public installer:
|
|
84
102
|
|
|
85
103
|
```bash
|
|
86
|
-
npx -y @sellable/install@
|
|
104
|
+
npx -y @sellable/install@latest --host codex --token skt_live_your_token_here --workspace-id your_workspace_id
|
|
87
105
|
```
|
|
88
106
|
|
|
89
107
|
If you already have `~/.sellable/config.json`, rerun/verify without rewriting
|
|
90
108
|
auth:
|
|
91
109
|
|
|
92
110
|
```bash
|
|
93
|
-
npx -y @sellable/install@
|
|
111
|
+
npx -y @sellable/install@latest --host codex
|
|
94
112
|
sellable --verify-only --host codex
|
|
95
113
|
```
|
|
96
114
|
|
package/dist/index-dev.js
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
-
import { getSkillByName, listSkills
|
|
4
|
+
import { getSkillByName, listSkills } from "./skills.js";
|
|
5
5
|
import { authToolDefinitions, getAuthStatus } from "./tools/auth.js";
|
|
6
6
|
import { blueprintCommitToolDefinitions, handleAddColumn, handleCommitBlueprint, } from "./tools/blueprint-commit.js";
|
|
7
7
|
import { bootstrapCreateCampaign, bootstrapToolDefinitions, } from "./tools/bootstrap.js";
|
|
@@ -29,6 +29,7 @@ import { attachRecommendedSequence, attachSequence, createWorkflowTable, sequenc
|
|
|
29
29
|
import { listTables, tableToolDefinitions } from "./tools/tables.js";
|
|
30
30
|
import { handleVerifyTableRow, verifyRowToolDefinitions, } from "./tools/verify-row.js";
|
|
31
31
|
import { addTeammate, createWorkspace, getActiveWorkspace, listWorkspaces, setActiveWorkspace, workspaceToolDefinitions, } from "./tools/workspaces.js";
|
|
32
|
+
import { checkForUpdates, logUpdateNotice } from "./update-check.js";
|
|
32
33
|
const server = new Server({
|
|
33
34
|
name: "sellable-mcp",
|
|
34
35
|
version: "1.0.0",
|
|
@@ -67,6 +68,35 @@ const allTools = [
|
|
|
67
68
|
...blueprintCommitToolDefinitions,
|
|
68
69
|
...verifyRowToolDefinitions,
|
|
69
70
|
];
|
|
71
|
+
function parseOptionalNumber(value) {
|
|
72
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
73
|
+
return value;
|
|
74
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
75
|
+
const parsed = Number(value);
|
|
76
|
+
if (Number.isFinite(parsed))
|
|
77
|
+
return parsed;
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
function formatSubskillPromptText(result) {
|
|
82
|
+
const header = result.chunkCount && result.chunkCount > 1
|
|
83
|
+
? [
|
|
84
|
+
`# ${result.name} prompt chunk ${result.chunkIndex} of ${result.chunkCount}`,
|
|
85
|
+
"",
|
|
86
|
+
`Prompt length: ${result.promptLength} characters.`,
|
|
87
|
+
result.chunkingInstructions ?? "",
|
|
88
|
+
"",
|
|
89
|
+
].join("\n")
|
|
90
|
+
: "";
|
|
91
|
+
const footer = result.hasMore && result.nextOffset !== null
|
|
92
|
+
? [
|
|
93
|
+
"",
|
|
94
|
+
"---",
|
|
95
|
+
`Continue with offset ${result.nextOffset} and limit ${result.limit}.`,
|
|
96
|
+
].join("\n")
|
|
97
|
+
: "";
|
|
98
|
+
return `${header}${result.prompt}${footer}`;
|
|
99
|
+
}
|
|
70
100
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
71
101
|
tools: allTools,
|
|
72
102
|
}));
|
|
@@ -447,16 +477,29 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
|
447
477
|
prompts: listSkills("public").map((skill) => ({
|
|
448
478
|
name: skill.name,
|
|
449
479
|
description: skill.description,
|
|
480
|
+
arguments: [
|
|
481
|
+
{
|
|
482
|
+
name: "offset",
|
|
483
|
+
description: "Optional character offset for chunked prompt reads.",
|
|
484
|
+
required: false,
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
name: "limit",
|
|
488
|
+
description: "Optional max characters to return for chunked prompt reads.",
|
|
489
|
+
required: false,
|
|
490
|
+
},
|
|
491
|
+
],
|
|
450
492
|
})),
|
|
451
493
|
}));
|
|
452
494
|
// Get prompt content
|
|
453
495
|
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
454
496
|
const { name } = request.params;
|
|
497
|
+
const args = request.params.arguments ?? {};
|
|
455
498
|
const skill = getSkillByName(name);
|
|
456
499
|
if (!skill) {
|
|
457
500
|
throw new Error(`Unknown prompt: ${name}`);
|
|
458
501
|
}
|
|
459
|
-
const content =
|
|
502
|
+
const content = getSubskillPrompt(name, parseOptionalNumber(args.offset), parseOptionalNumber(args.limit));
|
|
460
503
|
return {
|
|
461
504
|
description: skill.description,
|
|
462
505
|
messages: [
|
|
@@ -464,7 +507,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
464
507
|
role: "user",
|
|
465
508
|
content: {
|
|
466
509
|
type: "text",
|
|
467
|
-
text: content,
|
|
510
|
+
text: formatSubskillPromptText(content),
|
|
468
511
|
},
|
|
469
512
|
},
|
|
470
513
|
],
|
|
@@ -473,6 +516,11 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
473
516
|
async function main() {
|
|
474
517
|
const startTime = Date.now();
|
|
475
518
|
console.error(`[sellable-mcp] pid=${process.pid} starting...`);
|
|
519
|
+
void checkForUpdates()
|
|
520
|
+
.then(logUpdateNotice)
|
|
521
|
+
.catch(() => {
|
|
522
|
+
// Update checks are advisory and must never block MCP startup.
|
|
523
|
+
});
|
|
476
524
|
const transport = new StdioServerTransport();
|
|
477
525
|
// Exit cleanly when parent disconnects stdin (Claude Code closed/reconnected)
|
|
478
526
|
process.stdin.on("end", () => {
|
package/dist/tools/auth.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SellableUpdateStatus } from "../update-check.js";
|
|
1
2
|
export type AuthStatus = {
|
|
2
3
|
ok: boolean;
|
|
3
4
|
configPath: string;
|
|
@@ -10,6 +11,7 @@ export type AuthStatus = {
|
|
|
10
11
|
checkedAt: string;
|
|
11
12
|
/** Short message the LLM MUST show the user after auth succeeds. */
|
|
12
13
|
_userNotice: string | null;
|
|
14
|
+
update: SellableUpdateStatus | null;
|
|
13
15
|
error: null | {
|
|
14
16
|
type: "config" | "auth" | "workspace" | "api";
|
|
15
17
|
status?: number;
|
package/dist/tools/auth.js
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
import { getApi, SellableApiError } from "../api.js";
|
|
2
2
|
import { getConfig, getConfigPath } from "../auth.js";
|
|
3
|
+
import { checkForUpdates } from "../update-check.js";
|
|
3
4
|
function maskToken(token) {
|
|
4
5
|
if (token.length <= 10)
|
|
5
6
|
return `${token.slice(0, 4)}...`;
|
|
6
7
|
return `${token.slice(0, 8)}...${token.slice(-2)}`;
|
|
7
8
|
}
|
|
9
|
+
async function getUpdateStatus() {
|
|
10
|
+
try {
|
|
11
|
+
return await checkForUpdates();
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function appendUpdateNotice(notice, update) {
|
|
18
|
+
if (!update?._userNotice)
|
|
19
|
+
return notice;
|
|
20
|
+
return notice ? `${notice}\n${update._userNotice}` : update._userNotice;
|
|
21
|
+
}
|
|
8
22
|
export const authToolDefinitions = [
|
|
9
23
|
{
|
|
10
24
|
name: "get_auth_status",
|
|
11
|
-
description: "Verify Sellable authentication early. Call this before any campaign work. Returns ok=false with clear guidance when the token is missing, revoked, or the active workspace is not set. IMPORTANT: When auth succeeds, ALWAYS tell the user the _userNotice message so they know which workspace is active.",
|
|
25
|
+
description: "Verify Sellable authentication early. Call this before any campaign work. Also performs a cached Sellable package update check. Returns ok=false with clear guidance when the token is missing, revoked, or the active workspace is not set. IMPORTANT: When auth succeeds, ALWAYS tell the user the _userNotice message so they know which workspace is active and whether an update is available.",
|
|
12
26
|
inputSchema: {
|
|
13
27
|
type: "object",
|
|
14
28
|
properties: {},
|
|
@@ -20,6 +34,7 @@ export const authToolDefinitions = [
|
|
|
20
34
|
export async function getAuthStatus() {
|
|
21
35
|
const configPath = getConfigPath();
|
|
22
36
|
const checkedAt = new Date().toISOString();
|
|
37
|
+
const update = await getUpdateStatus();
|
|
23
38
|
const base = {
|
|
24
39
|
ok: false,
|
|
25
40
|
configPath,
|
|
@@ -29,7 +44,8 @@ export async function getAuthStatus() {
|
|
|
29
44
|
activeWorkspaceName: null,
|
|
30
45
|
tokenPrefix: null,
|
|
31
46
|
workspacesCount: null,
|
|
32
|
-
_userNotice: null,
|
|
47
|
+
_userNotice: appendUpdateNotice(null, update),
|
|
48
|
+
update,
|
|
33
49
|
checkedAt,
|
|
34
50
|
};
|
|
35
51
|
try {
|
|
@@ -87,7 +103,8 @@ export async function getAuthStatus() {
|
|
|
87
103
|
activeWorkspaceName: workspaceName,
|
|
88
104
|
tokenPrefix: maskToken(config.token),
|
|
89
105
|
workspacesCount: workspaces.length,
|
|
90
|
-
_userNotice: notice,
|
|
106
|
+
_userNotice: appendUpdateNotice(notice, update),
|
|
107
|
+
update,
|
|
91
108
|
checkedAt,
|
|
92
109
|
error: null,
|
|
93
110
|
};
|
|
@@ -9,7 +9,7 @@ type BootstrapCreateCampaignInput = {
|
|
|
9
9
|
refreshContext?: boolean;
|
|
10
10
|
};
|
|
11
11
|
type BootstrapCheck = {
|
|
12
|
-
key: "auth" | "framework" | "resume_context" | "subskill_catalog" | "create_campaign_subskill";
|
|
12
|
+
key: "auth" | "update" | "framework" | "resume_context" | "subskill_catalog" | "create_campaign_subskill";
|
|
13
13
|
ok: boolean;
|
|
14
14
|
blocking: boolean;
|
|
15
15
|
detail: string;
|
package/dist/tools/bootstrap.js
CHANGED
|
@@ -10,6 +10,9 @@ function toGuidance(check, message) {
|
|
|
10
10
|
if (check === "auth") {
|
|
11
11
|
return "Run get_auth_status and fix auth/workspace setup before continuing.";
|
|
12
12
|
}
|
|
13
|
+
if (check === "update") {
|
|
14
|
+
return "Run npx -y @sellable/install@latest, then restart Claude Code/Codex so Sellable reloads.";
|
|
15
|
+
}
|
|
13
16
|
if (check === "framework") {
|
|
14
17
|
return "Fix framework files and rerun bootstrap_create_campaign.";
|
|
15
18
|
}
|
|
@@ -101,6 +104,22 @@ export async function bootstrapCreateCampaign(input = {}) {
|
|
|
101
104
|
if (!ok) {
|
|
102
105
|
pushBlockingError(blockingErrors, "auth", authCheck.status.error?.message || "Authentication check failed.");
|
|
103
106
|
}
|
|
107
|
+
const update = authCheck.status.update;
|
|
108
|
+
if (update && !update.disabled) {
|
|
109
|
+
requiredChecks.push({
|
|
110
|
+
key: "update",
|
|
111
|
+
ok: !update.blocking,
|
|
112
|
+
blocking: update.blocking,
|
|
113
|
+
detail: update.updateAvailable
|
|
114
|
+
? update._userNotice || "A newer Sellable MCP package is available."
|
|
115
|
+
: update.mcp.latestVersion
|
|
116
|
+
? `Sellable MCP is current (${update.mcp.currentVersion ?? "unknown"}).`
|
|
117
|
+
: "Sellable MCP update check completed without latest-version data.",
|
|
118
|
+
});
|
|
119
|
+
if (update.blocking) {
|
|
120
|
+
pushBlockingError(blockingErrors, "update", update._userNotice || "Sellable MCP update required.");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
104
123
|
}
|
|
105
124
|
catch (error) {
|
|
106
125
|
const message = toErrorMessage(error);
|
|
@@ -215,10 +234,10 @@ export async function bootstrapCreateCampaign(input = {}) {
|
|
|
215
234
|
: "";
|
|
216
235
|
const nextStep = safeToProceed
|
|
217
236
|
? resumeDetected
|
|
218
|
-
? `Bootstrap complete.${workspaceNotice} Resume using campaign context, then load ${createCampaignSubskill?.name ?? "create-campaign"} instructions with get_subskill_prompt({ subskillName: "${createCampaignSubskill?.name ?? "create-campaign"}" }).`
|
|
237
|
+
? `Bootstrap complete.${workspaceNotice} Resume using campaign context, 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.`
|
|
219
238
|
: flowVersion === "v2"
|
|
220
|
-
? `Bootstrap complete.${workspaceNotice} Load create-campaign-v2 instructions with get_subskill_prompt({ subskillName: "create-campaign-v2" }),
|
|
221
|
-
: `Bootstrap complete.${workspaceNotice} Load ${createCampaignSubskill?.name ?? "create-campaign"} instructions with get_subskill_prompt({ subskillName: "${createCampaignSubskill?.name ?? "create-campaign"}" })
|
|
239
|
+
? `Bootstrap complete.${workspaceNotice} Load create-campaign-v2 instructions with get_subskill_prompt({ subskillName: "create-campaign-v2" }); if the response has hasMore=true, continue with nextOffset until hasMore=false. Then start with the create-campaign-brief interview stage. Do not call create_campaign until the v2 approval gate returns approve.`
|
|
240
|
+
: `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.`
|
|
222
241
|
: "Bootstrap incomplete. Resolve blockingErrors and rerun bootstrap_create_campaign before provider/search/import tools.";
|
|
223
242
|
// Strip prompt body from createCampaignSubskill — it's loaded via the host
|
|
224
243
|
// skill prompt. Only return metadata to save context tokens.
|
package/dist/tools/leads.js
CHANGED
|
@@ -2205,10 +2205,18 @@ export async function confirmLeadList(input) {
|
|
|
2205
2205
|
...(shouldSetCurrentStep ? { currentStep: effectiveCurrentStep } : {}),
|
|
2206
2206
|
});
|
|
2207
2207
|
const campaignTableId = importResult.workflowTableId ?? importResult.campaignTableId;
|
|
2208
|
-
if
|
|
2208
|
+
// Persist currentStep if the caller asked for it. Do NOT touch
|
|
2209
|
+
// selectedLeadListId here: the campaign table id is already saved to
|
|
2210
|
+
// CampaignOffer.workflowTableId by /api/v3/campaign-builder/import-leads,
|
|
2211
|
+
// and selectedLeadListId must remain pointing at the source lead list
|
|
2212
|
+
// (config.type === "lead_list") so the UI's Continue handler — which feeds
|
|
2213
|
+
// selectedLeadListId back into /api/v3/campaign-builder/import-leads as
|
|
2214
|
+
// sourceLeadListId — can find a valid lead list. Overwriting it with the
|
|
2215
|
+
// campaign table id makes that endpoint 404 with "Lead list not found"
|
|
2216
|
+
// and silently breaks the Leads → Filter Continue button.
|
|
2217
|
+
if (shouldSetCurrentStep) {
|
|
2209
2218
|
await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
|
|
2210
|
-
|
|
2211
|
-
...(shouldSetCurrentStep ? { currentStep: effectiveCurrentStep } : {}),
|
|
2219
|
+
currentStep: effectiveCurrentStep,
|
|
2212
2220
|
});
|
|
2213
2221
|
}
|
|
2214
2222
|
return {
|
package/dist/tools/prompts.d.ts
CHANGED
|
@@ -20,6 +20,9 @@ export interface SubskillPromptResponse {
|
|
|
20
20
|
limit?: number;
|
|
21
21
|
hasMore?: boolean;
|
|
22
22
|
nextOffset?: number | null;
|
|
23
|
+
chunkIndex?: number;
|
|
24
|
+
chunkCount?: number;
|
|
25
|
+
chunkingInstructions?: string;
|
|
23
26
|
}
|
|
24
27
|
export interface ListSubskillPromptsResponse {
|
|
25
28
|
total: number;
|
|
@@ -44,6 +47,8 @@ export interface CompleteSenderResearchInput {
|
|
|
44
47
|
credibilitySignalsFound?: number;
|
|
45
48
|
notes?: string;
|
|
46
49
|
}
|
|
50
|
+
export declare const DEFAULT_SUBSKILL_PROMPT_CHUNK_CHARS = 24000;
|
|
51
|
+
export declare const MAX_SUBSKILL_PROMPT_CHUNK_CHARS = 24000;
|
|
47
52
|
export declare const promptToolDefinitions: ({
|
|
48
53
|
name: string;
|
|
49
54
|
description: string;
|
|
@@ -65,6 +70,7 @@ export declare const promptToolDefinitions: ({
|
|
|
65
70
|
required: never[];
|
|
66
71
|
additionalProperties?: undefined;
|
|
67
72
|
};
|
|
73
|
+
_meta?: undefined;
|
|
68
74
|
} | {
|
|
69
75
|
name: string;
|
|
70
76
|
description: string;
|
|
@@ -95,9 +101,13 @@ export declare const promptToolDefinitions: ({
|
|
|
95
101
|
required: never[];
|
|
96
102
|
additionalProperties: boolean;
|
|
97
103
|
};
|
|
104
|
+
_meta?: undefined;
|
|
98
105
|
} | {
|
|
99
106
|
name: string;
|
|
100
107
|
description: string;
|
|
108
|
+
_meta: {
|
|
109
|
+
"anthropic/maxResultSizeChars": number;
|
|
110
|
+
};
|
|
101
111
|
inputSchema: {
|
|
102
112
|
type: string;
|
|
103
113
|
properties: {
|
|
@@ -158,6 +168,7 @@ export declare const promptToolDefinitions: ({
|
|
|
158
168
|
required: string[];
|
|
159
169
|
additionalProperties: boolean;
|
|
160
170
|
};
|
|
171
|
+
_meta?: undefined;
|
|
161
172
|
} | {
|
|
162
173
|
name: string;
|
|
163
174
|
description: string;
|
|
@@ -195,6 +206,7 @@ export declare const promptToolDefinitions: ({
|
|
|
195
206
|
additionalProperties: boolean;
|
|
196
207
|
required?: undefined;
|
|
197
208
|
};
|
|
209
|
+
_meta?: undefined;
|
|
198
210
|
})[];
|
|
199
211
|
export declare function getMessagePrompt(): Promise<PromptResponse>;
|
|
200
212
|
export declare function listSubskillPrompts(limit?: number, includePublic?: boolean, includeInternal?: boolean): ListSubskillPromptsResponse;
|
package/dist/tools/prompts.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getApi } from "../api.js";
|
|
2
2
|
import { getSkillByName, listSkills, stripFrontmatter } from "../skills.js";
|
|
3
3
|
import { markCreateCampaignPromptLoaded, markResearchPromptLoaded, markSenderResearchCompleted, } from "./flow-preflight.js";
|
|
4
|
+
export const DEFAULT_SUBSKILL_PROMPT_CHUNK_CHARS = 24_000;
|
|
5
|
+
export const MAX_SUBSKILL_PROMPT_CHUNK_CHARS = 24_000;
|
|
4
6
|
export const promptToolDefinitions = [
|
|
5
7
|
{
|
|
6
8
|
name: "get_message_prompt",
|
|
@@ -36,7 +38,10 @@ export const promptToolDefinitions = [
|
|
|
36
38
|
},
|
|
37
39
|
{
|
|
38
40
|
name: "get_subskill_prompt",
|
|
39
|
-
description: "Load a Sellable subskill prompt by name. Internal subskills are not listed in prompts/list; use search_subskill_prompts when you are unsure of the exact name.",
|
|
41
|
+
description: "Load a Sellable subskill prompt by name. Large prompts are returned in portable chunks by default; continue with nextOffset until hasMore is false. Internal subskills are not listed in prompts/list; use search_subskill_prompts when you are unsure of the exact name.",
|
|
42
|
+
_meta: {
|
|
43
|
+
"anthropic/maxResultSizeChars": 200_000,
|
|
44
|
+
},
|
|
40
45
|
inputSchema: {
|
|
41
46
|
type: "object",
|
|
42
47
|
properties: {
|
|
@@ -50,7 +55,7 @@ export const promptToolDefinitions = [
|
|
|
50
55
|
},
|
|
51
56
|
limit: {
|
|
52
57
|
type: "number",
|
|
53
|
-
description:
|
|
58
|
+
description: `Optional max characters to return for chunked prompt reads. Defaults to ${DEFAULT_SUBSKILL_PROMPT_CHUNK_CHARS} for large prompts and is capped at ${MAX_SUBSKILL_PROMPT_CHUNK_CHARS}.`,
|
|
54
59
|
},
|
|
55
60
|
},
|
|
56
61
|
required: ["subskillName"],
|
|
@@ -141,11 +146,7 @@ export function listSubskillPrompts(limit, includePublic, includeInternal) {
|
|
|
141
146
|
})),
|
|
142
147
|
};
|
|
143
148
|
}
|
|
144
|
-
|
|
145
|
-
const skill = getSkillByName(subskillName);
|
|
146
|
-
if (!skill) {
|
|
147
|
-
throw new Error(`Unknown subskill prompt: ${subskillName}`);
|
|
148
|
-
}
|
|
149
|
+
function markSubskillPromptLoaded(subskillName) {
|
|
149
150
|
if (subskillName === "create-campaign") {
|
|
150
151
|
markCreateCampaignPromptLoaded();
|
|
151
152
|
}
|
|
@@ -158,17 +159,37 @@ export function getSubskillPrompt(subskillName, offset, limit) {
|
|
|
158
159
|
if (subskillName === "research-prospect") {
|
|
159
160
|
markResearchPromptLoaded("prospect", "research-prospect");
|
|
160
161
|
}
|
|
162
|
+
}
|
|
163
|
+
export function getSubskillPrompt(subskillName, offset, limit) {
|
|
164
|
+
const skill = getSkillByName(subskillName);
|
|
165
|
+
if (!skill) {
|
|
166
|
+
throw new Error(`Unknown subskill prompt: ${subskillName}`);
|
|
167
|
+
}
|
|
168
|
+
markSubskillPromptLoaded(subskillName);
|
|
161
169
|
const fullPrompt = stripFrontmatter(skill.content);
|
|
162
170
|
const safeOffset = Math.min(Math.max(Number.isFinite(offset) ? Math.floor(offset ?? 0) : 0, 0), fullPrompt.length);
|
|
163
|
-
const
|
|
171
|
+
const requestedLimit = Number.isFinite(limit) && limit !== undefined
|
|
164
172
|
? Math.max(Math.floor(limit), 1)
|
|
165
173
|
: null;
|
|
174
|
+
const defaultChunkLimit = fullPrompt.length > DEFAULT_SUBSKILL_PROMPT_CHUNK_CHARS
|
|
175
|
+
? DEFAULT_SUBSKILL_PROMPT_CHUNK_CHARS
|
|
176
|
+
: null;
|
|
177
|
+
const safeLimit = requestedLimit === null
|
|
178
|
+
? defaultChunkLimit
|
|
179
|
+
: Math.min(requestedLimit, MAX_SUBSKILL_PROMPT_CHUNK_CHARS);
|
|
166
180
|
const prompt = safeLimit === null
|
|
167
181
|
? fullPrompt
|
|
168
182
|
: fullPrompt.slice(safeOffset, safeOffset + safeLimit);
|
|
169
183
|
const nextOffset = safeLimit === null
|
|
170
184
|
? null
|
|
171
185
|
: Math.min(safeOffset + safeLimit, fullPrompt.length);
|
|
186
|
+
const hasMore = nextOffset !== null && nextOffset < fullPrompt.length;
|
|
187
|
+
const chunkCount = safeLimit === null
|
|
188
|
+
? 1
|
|
189
|
+
: Math.max(Math.ceil(fullPrompt.length / safeLimit), 1);
|
|
190
|
+
const chunkIndex = safeLimit === null
|
|
191
|
+
? 1
|
|
192
|
+
: Math.min(Math.floor(safeOffset / safeLimit) + 1, chunkCount);
|
|
172
193
|
return {
|
|
173
194
|
name: skill.name,
|
|
174
195
|
description: skill.description,
|
|
@@ -177,8 +198,13 @@ export function getSubskillPrompt(subskillName, offset, limit) {
|
|
|
177
198
|
promptLength: fullPrompt.length,
|
|
178
199
|
offset: safeOffset,
|
|
179
200
|
limit: safeLimit ?? undefined,
|
|
180
|
-
hasMore
|
|
181
|
-
nextOffset:
|
|
201
|
+
hasMore,
|
|
202
|
+
nextOffset: hasMore ? nextOffset : null,
|
|
203
|
+
chunkIndex,
|
|
204
|
+
chunkCount,
|
|
205
|
+
chunkingInstructions: hasMore
|
|
206
|
+
? `Continue this same prompt load with get_subskill_prompt({ subskillName: "${skill.name}", offset: ${nextOffset}, limit: ${safeLimit} }) until hasMore is false. Treat all chunks as one prompt load; do not use saved local tool-output files or machine-specific paths.`
|
|
207
|
+
: undefined,
|
|
182
208
|
};
|
|
183
209
|
}
|
|
184
210
|
export function completeSenderResearch(input) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type PackageUpdateStatus = {
|
|
2
|
+
packageName: string;
|
|
3
|
+
currentVersion: string | null;
|
|
4
|
+
latestVersion: string | null;
|
|
5
|
+
outdated: boolean;
|
|
6
|
+
error?: string;
|
|
7
|
+
};
|
|
8
|
+
export type SellableUpdateStatus = {
|
|
9
|
+
checkedAt: string;
|
|
10
|
+
cachePath: string;
|
|
11
|
+
fromCache: boolean;
|
|
12
|
+
disabled: boolean;
|
|
13
|
+
mcp: PackageUpdateStatus;
|
|
14
|
+
installer: PackageUpdateStatus;
|
|
15
|
+
updateAvailable: boolean;
|
|
16
|
+
blocking: boolean;
|
|
17
|
+
guidance: string | null;
|
|
18
|
+
_userNotice: string | null;
|
|
19
|
+
};
|
|
20
|
+
export declare function checkForUpdates(options?: {
|
|
21
|
+
force?: boolean;
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
ttlMs?: number;
|
|
24
|
+
}): Promise<SellableUpdateStatus>;
|
|
25
|
+
export declare function logUpdateNotice(status: SellableUpdateStatus): void;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as os from "os";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
const MCP_PACKAGE = "@sellable/mcp";
|
|
5
|
+
const INSTALL_PACKAGE = "@sellable/install";
|
|
6
|
+
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 1500;
|
|
8
|
+
function cachePath() {
|
|
9
|
+
return path.join(os.homedir(), ".sellable", "update-check.json");
|
|
10
|
+
}
|
|
11
|
+
function readCurrentVersion() {
|
|
12
|
+
const entrypointDir = process.argv[1]
|
|
13
|
+
? path.dirname(path.resolve(process.argv[1]))
|
|
14
|
+
: process.cwd();
|
|
15
|
+
const candidates = [
|
|
16
|
+
path.resolve(entrypointDir, "..", "package.json"),
|
|
17
|
+
path.resolve(process.cwd(), "mcp/sellable/package.json"),
|
|
18
|
+
];
|
|
19
|
+
for (const pkgPath of candidates) {
|
|
20
|
+
try {
|
|
21
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
22
|
+
if (pkg.name === MCP_PACKAGE && typeof pkg.version === "string") {
|
|
23
|
+
return pkg.version;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Keep looking; version detection is advisory only.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
function registryPackagePath(packageName) {
|
|
33
|
+
return encodeURIComponent(packageName);
|
|
34
|
+
}
|
|
35
|
+
async function fetchLatestVersion(packageName, timeoutMs) {
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(`https://registry.npmjs.org/${registryPackagePath(packageName)}/latest`, {
|
|
40
|
+
signal: controller.signal,
|
|
41
|
+
headers: {
|
|
42
|
+
Accept: "application/json",
|
|
43
|
+
"User-Agent": "sellable-mcp-update-check",
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new Error(`npm registry returned ${response.status}`);
|
|
48
|
+
}
|
|
49
|
+
const body = (await response.json());
|
|
50
|
+
if (typeof body.version !== "string" || !body.version) {
|
|
51
|
+
throw new Error("npm registry response did not include a version");
|
|
52
|
+
}
|
|
53
|
+
return body.version;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function parseVersion(version) {
|
|
60
|
+
if (!version)
|
|
61
|
+
return null;
|
|
62
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
63
|
+
if (!match)
|
|
64
|
+
return null;
|
|
65
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
66
|
+
}
|
|
67
|
+
function isVersionOutdated(currentVersion, latestVersion) {
|
|
68
|
+
const current = parseVersion(currentVersion);
|
|
69
|
+
const latest = parseVersion(latestVersion);
|
|
70
|
+
if (!current || !latest)
|
|
71
|
+
return false;
|
|
72
|
+
for (let i = 0; i < 3; i += 1) {
|
|
73
|
+
if (current[i] < latest[i])
|
|
74
|
+
return true;
|
|
75
|
+
if (current[i] > latest[i])
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
function readCache(ttlMs) {
|
|
81
|
+
const file = cachePath();
|
|
82
|
+
try {
|
|
83
|
+
const raw = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
84
|
+
if (!raw.checkedAtMs || Date.now() - raw.checkedAtMs > ttlMs)
|
|
85
|
+
return null;
|
|
86
|
+
return {
|
|
87
|
+
...raw,
|
|
88
|
+
cachePath: file,
|
|
89
|
+
fromCache: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function writeCache(status) {
|
|
97
|
+
const file = cachePath();
|
|
98
|
+
try {
|
|
99
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
100
|
+
const { fromCache: _fromCache, ...cacheableStatus } = status;
|
|
101
|
+
const cache = {
|
|
102
|
+
...cacheableStatus,
|
|
103
|
+
cachePath: file,
|
|
104
|
+
checkedAtMs: Date.now(),
|
|
105
|
+
};
|
|
106
|
+
fs.writeFileSync(file, `${JSON.stringify(cache, null, 2)}\n`, {
|
|
107
|
+
mode: 0o600,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Update checks must never break MCP startup or tool execution.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function disabledStatus(currentVersion) {
|
|
115
|
+
return {
|
|
116
|
+
checkedAt: new Date().toISOString(),
|
|
117
|
+
cachePath: cachePath(),
|
|
118
|
+
fromCache: false,
|
|
119
|
+
disabled: true,
|
|
120
|
+
mcp: {
|
|
121
|
+
packageName: MCP_PACKAGE,
|
|
122
|
+
currentVersion,
|
|
123
|
+
latestVersion: null,
|
|
124
|
+
outdated: false,
|
|
125
|
+
},
|
|
126
|
+
installer: {
|
|
127
|
+
packageName: INSTALL_PACKAGE,
|
|
128
|
+
currentVersion: null,
|
|
129
|
+
latestVersion: null,
|
|
130
|
+
outdated: false,
|
|
131
|
+
},
|
|
132
|
+
updateAvailable: false,
|
|
133
|
+
blocking: false,
|
|
134
|
+
guidance: null,
|
|
135
|
+
_userNotice: null,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function buildNotice(mcp, installer) {
|
|
139
|
+
if (!mcp.outdated)
|
|
140
|
+
return null;
|
|
141
|
+
const versions = mcp.currentVersion && mcp.latestVersion
|
|
142
|
+
? `${mcp.currentVersion} -> ${mcp.latestVersion}`
|
|
143
|
+
: "a newer version is available";
|
|
144
|
+
const installerVersion = installer.latestVersion
|
|
145
|
+
? ` The latest installer is ${installer.latestVersion}.`
|
|
146
|
+
: "";
|
|
147
|
+
return `Sellable update available: ${versions}. Run: npx -y @sellable/install@latest.${installerVersion}`;
|
|
148
|
+
}
|
|
149
|
+
export async function checkForUpdates(options) {
|
|
150
|
+
const currentVersion = readCurrentVersion();
|
|
151
|
+
const ttlMs = options?.ttlMs ??
|
|
152
|
+
(Number(process.env.SELLABLE_UPDATE_CHECK_TTL_MS || "") || DEFAULT_TTL_MS);
|
|
153
|
+
const timeoutMs = options?.timeoutMs ??
|
|
154
|
+
(Number(process.env.SELLABLE_UPDATE_CHECK_TIMEOUT_MS || "") ||
|
|
155
|
+
DEFAULT_TIMEOUT_MS);
|
|
156
|
+
if (process.env.SELLABLE_UPDATE_CHECK_DISABLED === "1") {
|
|
157
|
+
return disabledStatus(currentVersion);
|
|
158
|
+
}
|
|
159
|
+
if (!options?.force) {
|
|
160
|
+
const cached = readCache(ttlMs);
|
|
161
|
+
if (cached)
|
|
162
|
+
return cached;
|
|
163
|
+
}
|
|
164
|
+
const [mcpResult, installerResult] = await Promise.allSettled([
|
|
165
|
+
fetchLatestVersion(MCP_PACKAGE, timeoutMs),
|
|
166
|
+
fetchLatestVersion(INSTALL_PACKAGE, timeoutMs),
|
|
167
|
+
]);
|
|
168
|
+
const mcpLatest = mcpResult.status === "fulfilled" ? mcpResult.value : null;
|
|
169
|
+
const installerLatest = installerResult.status === "fulfilled" ? installerResult.value : null;
|
|
170
|
+
const mcp = {
|
|
171
|
+
packageName: MCP_PACKAGE,
|
|
172
|
+
currentVersion,
|
|
173
|
+
latestVersion: mcpLatest,
|
|
174
|
+
outdated: isVersionOutdated(currentVersion, mcpLatest),
|
|
175
|
+
...(mcpResult.status === "rejected"
|
|
176
|
+
? { error: mcpResult.reason?.message || String(mcpResult.reason) }
|
|
177
|
+
: {}),
|
|
178
|
+
};
|
|
179
|
+
const installer = {
|
|
180
|
+
packageName: INSTALL_PACKAGE,
|
|
181
|
+
currentVersion: null,
|
|
182
|
+
latestVersion: installerLatest,
|
|
183
|
+
outdated: false,
|
|
184
|
+
...(installerResult.status === "rejected"
|
|
185
|
+
? {
|
|
186
|
+
error: installerResult.reason?.message || String(installerResult.reason),
|
|
187
|
+
}
|
|
188
|
+
: {}),
|
|
189
|
+
};
|
|
190
|
+
const notice = buildNotice(mcp, installer);
|
|
191
|
+
const status = {
|
|
192
|
+
checkedAt: new Date().toISOString(),
|
|
193
|
+
cachePath: cachePath(),
|
|
194
|
+
fromCache: false,
|
|
195
|
+
disabled: false,
|
|
196
|
+
mcp,
|
|
197
|
+
installer,
|
|
198
|
+
updateAvailable: mcp.outdated,
|
|
199
|
+
blocking: false,
|
|
200
|
+
guidance: notice ? "Run npx -y @sellable/install@latest" : null,
|
|
201
|
+
_userNotice: notice,
|
|
202
|
+
};
|
|
203
|
+
writeCache(status);
|
|
204
|
+
return status;
|
|
205
|
+
}
|
|
206
|
+
export function logUpdateNotice(status) {
|
|
207
|
+
if (status._userNotice) {
|
|
208
|
+
console.error(`[sellable-mcp] ${status._userNotice}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Clover Campaign Brief + Rubric Archive
|
|
2
2
|
|
|
3
|
-
Source:
|
|
3
|
+
Source: Phase 75 all-client campaign corpus, copied into this prompt reference archive.
|
|
4
4
|
|
|
5
5
|
Campaigns captured: 4
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Galley Campaign Brief + Rubric Archive
|
|
2
2
|
|
|
3
|
-
Source:
|
|
3
|
+
Source: Phase 75 all-client campaign corpus, copied into this prompt reference archive.
|
|
4
4
|
|
|
5
5
|
Campaigns captured: 5
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Gelee Campaign Brief + Rubric Archive
|
|
2
2
|
|
|
3
|
-
Source:
|
|
3
|
+
Source: Phase 75 all-client campaign corpus, copied into this prompt reference archive.
|
|
4
4
|
|
|
5
5
|
Campaigns captured: 1
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Hey Digital Campaign Brief + Rubric Archive
|
|
2
2
|
|
|
3
|
-
Source:
|
|
3
|
+
Source: Phase 75 all-client campaign corpus, copied into this prompt reference archive.
|
|
4
4
|
|
|
5
5
|
Campaigns captured: 4
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Persona Campaign Brief + Rubric Archive
|
|
2
2
|
|
|
3
|
-
Source:
|
|
3
|
+
Source: Phase 75 all-client campaign corpus, copied into this prompt reference archive.
|
|
4
4
|
|
|
5
5
|
Campaigns captured: 3
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Revvix Campaign Brief + Rubric Archive
|
|
2
2
|
|
|
3
|
-
Source:
|
|
3
|
+
Source: Phase 75 all-client campaign corpus, copied into this prompt reference archive.
|
|
4
4
|
|
|
5
5
|
Campaigns captured: 8
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# sellable.dev Campaign Brief + Rubric Archive
|
|
2
2
|
|
|
3
|
-
Source:
|
|
3
|
+
Source: Phase 75 all-client campaign corpus, copied into this prompt reference archive.
|
|
4
4
|
|
|
5
5
|
Campaigns captured: 304
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# superposition Campaign Brief + Rubric Archive
|
|
2
2
|
|
|
3
|
-
Source:
|
|
3
|
+
Source: Phase 75 all-client campaign corpus, copied into this prompt reference archive.
|
|
4
4
|
|
|
5
5
|
Campaigns captured: 5
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Superpower Campaign Brief + Rubric Archive
|
|
2
2
|
|
|
3
|
-
Source:
|
|
3
|
+
Source: Phase 75 all-client campaign corpus, copied into this prompt reference archive.
|
|
4
4
|
|
|
5
5
|
Campaigns captured: 3
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# westpark villas Campaign Brief + Rubric Archive
|
|
2
2
|
|
|
3
|
-
Source:
|
|
3
|
+
Source: Phase 75 all-client campaign corpus, copied into this prompt reference archive.
|
|
4
4
|
|
|
5
5
|
Campaigns captured: 26
|
|
6
6
|
|
|
@@ -167,7 +167,7 @@ Add only when the campaign truly needs them:
|
|
|
167
167
|
|
|
168
168
|
Reference:
|
|
169
169
|
|
|
170
|
-
-
|
|
170
|
+
- `75-MESSAGE-VARIANT-GENERATION-FLOW.md` in the Phase 75 planning archive.
|
|
171
171
|
|
|
172
172
|
## Section Rules
|
|
173
173
|
|
package/skills/create-campaign-brief/references/phase75-good-brief-and-messaging-examples.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Phase 75 Live Brief + Messaging Example Bank
|
|
2
2
|
|
|
3
|
-
Source of truth:
|
|
3
|
+
Source of truth: Phase 75 live campaign corpus used to generate this prompt reference.
|
|
4
4
|
|
|
5
|
-
Full archive of client campaign briefs:
|
|
5
|
+
Full archive of client campaign briefs: Phase 75 all-client campaign corpus, copied into this prompt reference archive.
|
|
6
6
|
|
|
7
|
-
Direct copied archive of full campaign brief content + rubrics:
|
|
7
|
+
Direct copied archive of full campaign brief content + rubrics: `brief-rubric-archive/INDEX.md` in the Phase 75 planning archive.
|
|
8
8
|
|
|
9
9
|
This file distills the strongest live campaign briefs currently stored in production. The runtime gold set should stay narrow: active clients only, plus the one Hey Digital campaign that clearly performed and still teaches useful structure. The goal is not to preserve every brief shape forever. The goal is to preserve the patterns that consistently show up in the briefs that are actually usable, persuasive, and close to what Sellable has been shipping manually for clients.
|
|
10
10
|
|
|
@@ -20,7 +20,7 @@ Important note:
|
|
|
20
20
|
|
|
21
21
|
Runtime gold pack:
|
|
22
22
|
|
|
23
|
-
-
|
|
23
|
+
- `75-ACTIVE-RUNTIME-MESSAGE-PACK.md` in the Phase 75 planning archive.
|
|
24
24
|
|
|
25
25
|
## Winning Campaigns In Scope
|
|
26
26
|
|
|
@@ -118,6 +118,8 @@ Validated draft directory:
|
|
|
118
118
|
loading, runbooks, local skill files, skill versions, or "Sellable token".
|
|
119
119
|
"Quick question panel" is only acceptable when explaining a Codex/Claude setup
|
|
120
120
|
blocker.
|
|
121
|
+
- Before campaign mint, do not call `check_rubric`; Phase 84 only writes and
|
|
122
|
+
validates draft artifacts.
|
|
121
123
|
- Never narrate local draft housekeeping to the user. If you create directories,
|
|
122
124
|
save drafts, write artifacts, or persist intermediate state, translate it into
|
|
123
125
|
the campaign benefit: consistent brief, approved lead source, reviewed message,
|
|
@@ -394,6 +396,9 @@ should test for this campaign. Those can run in parallel and usually take
|
|
|
394
396
|
- Before the user chooses `approve`, the mutating live-campaign tools are
|
|
395
397
|
forbidden: `create_campaign`, `save_rubrics`, `import_leads`,
|
|
396
398
|
`confirm_lead_list`, `update_campaign`, `queue_cells`, and `start_campaign`.
|
|
399
|
+
- During pre-approval validation, do not call `check_rubric`; use the
|
|
400
|
+
campaignless lead-filter artifacts and only use campaign-backed scoring after
|
|
401
|
+
the approval flow allows it.
|
|
397
402
|
- Resume state is based on the presence and completeness of the chained artifacts,
|
|
398
403
|
not on inline validation blocks inside `brief.md`.
|
|
399
404
|
- Preserve the Phase 83 thesis. Do not rewrite product, ICP, offer, or message
|
|
@@ -482,7 +487,9 @@ Sellable lead lists in the main three-option first batch; support them through
|
|
|
482
487
|
custom/freeform input. If the user pastes up to 100 LinkedIn profile URLs or
|
|
483
488
|
company domains, normalize the paste into a temporary local CSV and continue
|
|
484
489
|
through the matching CSV preview path. Mixed, ambiguous, malformed, or oversized
|
|
485
|
-
pastes should ask for a real CSV file instead of guessing.
|
|
490
|
+
pastes should ask for a real CSV file instead of guessing. Uploaded CSV support
|
|
491
|
+
is larger than paste support: LinkedIn profile CSVs can contain up to 7,500
|
|
492
|
+
rows; domain CSVs can contain up to 7,500 rows but only 1,000 unique domains.
|
|
486
493
|
|
|
487
494
|
Avoid internal wording like `Which proof points should the message be allowed
|
|
488
495
|
to lean on?` because it describes the artifact, not the founder decision.
|
|
@@ -555,6 +562,10 @@ usable leads`.
|
|
|
555
562
|
- supplied LinkedIn profile CSVs call `load_csv_linkedin_leads` in preview mode
|
|
556
563
|
only before approval; do not pass `confirmed: true`, `campaignOfferId`,
|
|
557
564
|
`currentStep`, `leadListId`, `sourceLeadListId`, or provider-import params
|
|
565
|
+
during the preview. After approval, this branch is a two-stage batch path:
|
|
566
|
+
materialize the full supplied CSV into a Sellable lead-list table first, then
|
|
567
|
+
use the returned `leadListId` as `sourceLeadListId` for the bounded campaign
|
|
568
|
+
review import
|
|
558
569
|
- supplied company/domain CSVs call `load_csv_domains`; pre-approval
|
|
559
570
|
confirmation is allowed only without `campaignOfferId` or `currentStep` and
|
|
560
571
|
only to produce a standalone `domainFilterId` for campaignless
|
|
@@ -766,12 +777,13 @@ Run the `generate-messages` skill in caller-declared `DRY MODE`. Its SKILL.md
|
|
|
766
777
|
holds the full drafting contract (retrieval, proof inventory, candidates,
|
|
767
778
|
finalizer pass, voice rules, safety). This step only covers orchestration.
|
|
768
779
|
This is not optional: before any write to `message-validation.md`,
|
|
769
|
-
`message-review.md`, `approval-packet.md`, or a commit-gate question, the
|
|
770
|
-
full `generate-messages` prompt. In hosted/from-
|
|
771
|
-
chunked `get_subskill_prompt({ subskillName:
|
|
772
|
-
limit })` calls so every tool result stays small
|
|
773
|
-
harness. Start with `offset: 0
|
|
774
|
-
`offset: nextOffset`
|
|
780
|
+
`message-review.md`, `approval-packet.md`, or a commit-gate question, the
|
|
781
|
+
current run must load the full `generate-messages` prompt. In hosted/from-
|
|
782
|
+
scratch runs, load it with chunked `get_subskill_prompt({ subskillName:
|
|
783
|
+
"generate-messages", offset, limit })` calls so every tool result stays small
|
|
784
|
+
enough for the streamed harness. Start with `offset: 0`; the MCP will apply a
|
|
785
|
+
portable default limit when omitted. Keep calling with `offset: nextOffset`
|
|
786
|
+
until `hasMore` is false.
|
|
775
787
|
In Codex-hosted runs, this is a quality gate, not just provenance: if the
|
|
776
788
|
model cannot retrieve the complete prompt or cannot follow the required
|
|
777
789
|
gold-standard deliberation flow, stop at `message-review` and ask for
|
|
@@ -1648,9 +1660,9 @@ Message` column's http_request writes those cells via the cascade.
|
|
|
1648
1660
|
Load `core/auto-execute.yaml` exactly once at the start of Step 13. All
|
|
1649
1661
|
subsequent steps read the already-parsed config. Do not re-load mid-run.
|
|
1650
1662
|
Load each subskill prompt (`create-campaign-v2`, `research-sender`,
|
|
1651
|
-
`generate-messages`) at most once per run
|
|
1652
|
-
told you to load a prompt, load
|
|
1653
|
-
same prompt later.
|
|
1663
|
+
`generate-messages`) at most once per run. A multi-call chunk sequence counts
|
|
1664
|
+
as one load. If a tool result already told you to load a prompt, load all
|
|
1665
|
+
chunks once and remember; do not restart the same prompt from offset 0 later.
|
|
1654
1666
|
|
|
1655
1667
|
After every `update_campaign({ currentStep: ... })` in the tail, narrate
|
|
1656
1668
|
what changed and orient the user to what the watch link will show next —
|
|
@@ -1707,10 +1719,12 @@ atomic mint).
|
|
|
1707
1719
|
`import_leads({ campaignOfferId, targetLeadCount: importLimit })`.
|
|
1708
1720
|
If a manifest exists, branch by `sourceType`:
|
|
1709
1721
|
- `supplied-linkedin-profiles`: revalidate file metadata and confirmation
|
|
1710
|
-
token, confirm `load_csv_linkedin_leads` only after approval
|
|
1711
|
-
returned `leadListId`,
|
|
1712
|
-
`confirm_lead_list({ sourceLeadListId: leadListId,
|
|
1713
|
-
importLimit })`.
|
|
1722
|
+
token, confirm `load_csv_linkedin_leads` only after approval to batch the
|
|
1723
|
+
supplied CSV into a Sellable lead list, persist the returned `leadListId`,
|
|
1724
|
+
then call `confirm_lead_list({ sourceLeadListId: leadListId,
|
|
1725
|
+
targetLeadCount: importLimit })`. Do not call `import_leads` for this
|
|
1726
|
+
branch; the lead list is the source, and `confirm_lead_list` imports the
|
|
1727
|
+
bounded review batch into the campaign table.
|
|
1714
1728
|
- `existing-lead-list`: revalidate that the lead list still exists in the
|
|
1715
1729
|
same workspace, reuse it as `sourceLeadListId`, then call
|
|
1716
1730
|
`confirm_lead_list({ sourceLeadListId, targetLeadCount: importLimit })`.
|
|
@@ -345,7 +345,7 @@
|
|
|
345
345
|
},
|
|
346
346
|
"optionalRequiredArtifacts": ["lead-source-intake.json"],
|
|
347
347
|
"toolRules": {
|
|
348
|
-
"suppliedLinkedinProfiles": "Preview only before approval: call load_csv_linkedin_leads without confirmed, campaignOfferId, currentStep, leadListId, sourceLeadListId, or provider-import parameters; skip provider discovery.",
|
|
348
|
+
"suppliedLinkedinProfiles": "Preview only before approval: call load_csv_linkedin_leads without confirmed, campaignOfferId, currentStep, leadListId, sourceLeadListId, or provider-import parameters; skip provider discovery. After approval, batch/materialize the supplied CSV into a Sellable lead list first, then use the returned leadListId as sourceLeadListId for confirm_lead_list.",
|
|
349
349
|
"suppliedDomains": "May confirm load_csv_domains before approval only without campaignOfferId/currentStep to produce a standalone domainFilterId, then run a campaignless Prospeo sample constrained by domainFilterId.",
|
|
350
350
|
"existingLeadList": "Skip provider discovery and sample existing rows before approval; do not clone/import until after approval.",
|
|
351
351
|
"forbiddenPreApproval": [
|
|
@@ -1022,7 +1022,7 @@
|
|
|
1022
1022
|
"defaultWhenMissing": "discovered-provider-import",
|
|
1023
1023
|
"branches": {
|
|
1024
1024
|
"normal-discovery": "import_leads({ campaignOfferId, targetLeadCount: import.importLimit })",
|
|
1025
|
-
"supplied-linkedin-profiles": "confirm load_csv_linkedin_leads after approval, then confirm_lead_list({ sourceLeadListId, targetLeadCount: import.importLimit })",
|
|
1025
|
+
"supplied-linkedin-profiles": "confirm load_csv_linkedin_leads after approval to batch/materialize the CSV into a lead list, persist returned leadListId, then confirm_lead_list({ sourceLeadListId: leadListId, targetLeadCount: import.importLimit }); do not call import_leads for this branch",
|
|
1026
1026
|
"existing-lead-list": "confirm_lead_list({ sourceLeadListId: existingLeadListId, targetLeadCount: import.importLimit })",
|
|
1027
1027
|
"supplied-domains": "reuse or confirm load_csv_domains, run campaign-associated search_prospeo({ campaignOfferId, domainFilterId }), then import_leads({ campaignOfferId, targetLeadCount: import.importLimit })"
|
|
1028
1028
|
},
|
|
@@ -23,8 +23,12 @@ Supported branches:
|
|
|
23
23
|
|
|
24
24
|
- **No manifest / normal discovery** — use the existing provider import path.
|
|
25
25
|
- **Supplied LinkedIn profile CSV** — confirm `load_csv_linkedin_leads` only
|
|
26
|
-
after approval
|
|
27
|
-
`
|
|
26
|
+
after approval to batch/materialize the full uploaded CSV into a Sellable
|
|
27
|
+
lead-list table. Persist the returned `leadListId`, then import the bounded
|
|
28
|
+
review batch into the campaign table with
|
|
29
|
+
`confirm_lead_list({ sourceLeadListId: leadListId, targetLeadCount: importLimit })`.
|
|
30
|
+
Do not call `import_leads` for this branch; the materialized lead list is the
|
|
31
|
+
source.
|
|
28
32
|
Do **not** call `wait_for_lead_list_ready` for this branch; there is no
|
|
29
33
|
provider import job to wait on, and probing readiness can leave the campaign
|
|
30
34
|
in an import-failed state before `confirm_lead_list`.
|