@sellable/mcp 0.1.30 → 0.1.32
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/dist/auth.d.ts +25 -0
- package/dist/auth.js +30 -0
- package/dist/server.js +10 -4
- package/dist/tools/campaigns.d.ts +10 -97
- package/dist/tools/campaigns.js +26 -150
- package/dist/tools/cli-login.d.ts +80 -0
- package/dist/tools/cli-login.js +207 -0
- package/dist/tools/flow-preflight.d.ts +3 -16
- package/dist/tools/flow-preflight.js +6 -25
- package/dist/tools/navigation.js +2 -2
- package/dist/tools/one-off.d.ts +15 -2
- package/dist/tools/one-off.js +23 -6
- package/dist/tools/prompts.d.ts +2 -2
- package/dist/tools/prompts.js +2 -2
- package/dist/tools/readiness.d.ts +1 -0
- package/dist/tools/readiness.js +4 -2
- package/dist/tools/sequencer.d.ts +7 -0
- package/dist/tools/sequencer.js +8 -2
- package/package.json +1 -1
- package/skills/building-gtm-tables/SKILL.md +1 -0
- package/skills/create-campaign/SKILL.md +370 -238
- package/skills/create-campaign-v2/core/flow.v2.json +0 -2
- package/skills/create-post/SKILL.md +1 -1
- package/skills/engage/SKILL.md +1 -1
- package/skills/interview/SKILL.md +1 -1
- package/skills/research-sender/SKILL.md +78 -36
- package/skills/workflow-sequences/SKILL.md +1 -1
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { writeNewConfig } from "../auth.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sellable CLI login tools.
|
|
4
|
+
*
|
|
5
|
+
* Two-phase magic-link handoff used by the create-campaign skill when a
|
|
6
|
+
* brand-new user has no `~/.sellable/config.json` yet:
|
|
7
|
+
*
|
|
8
|
+
* 1. start_cli_login({ email })
|
|
9
|
+
* → POSTs to /api/v3/cli-session, server emails a magic link.
|
|
10
|
+
*
|
|
11
|
+
* 2. wait_for_cli_login({ sessionId })
|
|
12
|
+
* → Polls /api/v3/cli-session/[id]/poll every 2s until the user clicks
|
|
13
|
+
* the link, completes browser auth, and the server transitions the
|
|
14
|
+
* session to status="ready". Persists the resulting token via
|
|
15
|
+
* writeNewConfig().
|
|
16
|
+
*
|
|
17
|
+
* Constants:
|
|
18
|
+
* POLL_INTERVAL_MS — 2s between polls
|
|
19
|
+
* DEFAULT_TIMEOUT_MS — 5min outer session budget (matches CliSession TTL)
|
|
20
|
+
* TOOL_CALL_TIMEOUT_GUARD — 55s, mirrors mcp/sellable/src/tools/readiness.ts:31.
|
|
21
|
+
* MCP hosts (esp. Claude Code) cap individual tool
|
|
22
|
+
* calls at ~60s; without this guard the host kills
|
|
23
|
+
* the loop before our outer 5min budget elapses.
|
|
24
|
+
* Agent re-invokes wait_for_cli_login with the SAME
|
|
25
|
+
* sessionId on tool_timeout_guard until the outer
|
|
26
|
+
* budget runs out or sign-in completes.
|
|
27
|
+
*
|
|
28
|
+
* Security:
|
|
29
|
+
* No tool here logs `confirmUrl`, the plaintext token, or `sessionId`.
|
|
30
|
+
* See Phase 116 RESEARCH.md Pitfall 6.
|
|
31
|
+
*/
|
|
32
|
+
const POLL_INTERVAL_MS = 2_000;
|
|
33
|
+
const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
34
|
+
const TOOL_CALL_TIMEOUT_GUARD_MS = 55_000;
|
|
35
|
+
const MAX_NETWORK_RETRIES = 3;
|
|
36
|
+
// Exposed for tests so the 55s guard doesn't cause real-time waits.
|
|
37
|
+
export const __TEST_INTERNALS__ = {
|
|
38
|
+
POLL_INTERVAL_MS,
|
|
39
|
+
DEFAULT_TIMEOUT_MS,
|
|
40
|
+
TOOL_CALL_TIMEOUT_GUARD_MS,
|
|
41
|
+
MAX_NETWORK_RETRIES,
|
|
42
|
+
};
|
|
43
|
+
function errorPayload(type, message, guidance) {
|
|
44
|
+
return { ok: false, error: { type, message, guidance } };
|
|
45
|
+
}
|
|
46
|
+
function sleep(ms) {
|
|
47
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve API base URL for the un-authed handoff. Matches the resolution chain
|
|
51
|
+
* documented in Plan 09 UAT step 1: env override > production default. We do
|
|
52
|
+
* NOT auto-read a partial ~/.sellable/config.json here — that's an
|
|
53
|
+
* over-engineering trap for a single edge case fully addressed by an env-var
|
|
54
|
+
* doc.
|
|
55
|
+
*/
|
|
56
|
+
function resolveApiUrl() {
|
|
57
|
+
return (process.env.SELLABLE_API_URL ??
|
|
58
|
+
process.env.SELLABLE_BASE_URL ??
|
|
59
|
+
"https://app.sellable.dev");
|
|
60
|
+
}
|
|
61
|
+
export const startCliLoginToolDef = {
|
|
62
|
+
name: "start_cli_login",
|
|
63
|
+
description: "Start the CLI login handoff. Triggers a magic-link email to the supplied email address. " +
|
|
64
|
+
"Returns the confirm URL the user will be redirected to after clicking the link. " +
|
|
65
|
+
"Pair with wait_for_cli_login to block until the user finishes browser sign-in.",
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: "object",
|
|
68
|
+
properties: {
|
|
69
|
+
email: {
|
|
70
|
+
type: "string",
|
|
71
|
+
description: "User's email — magic link is sent here.",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
required: ["email"],
|
|
75
|
+
additionalProperties: false,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
export const waitForCliLoginToolDef = {
|
|
79
|
+
name: "wait_for_cli_login",
|
|
80
|
+
description: "Poll the CLI login session every 2s until the user finishes browser sign-in or 5 minutes elapse. " +
|
|
81
|
+
"On success, writes ~/.sellable/config.json so subsequent MCP calls are authenticated. " +
|
|
82
|
+
"Re-call with the SAME sessionId on tool_timeout_guard — do NOT call start_cli_login again.",
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: "object",
|
|
85
|
+
properties: {
|
|
86
|
+
sessionId: {
|
|
87
|
+
type: "string",
|
|
88
|
+
description: "Session ID from start_cli_login.",
|
|
89
|
+
},
|
|
90
|
+
timeoutMs: {
|
|
91
|
+
type: "number",
|
|
92
|
+
description: "Outer session budget (ms). Default 300000 (5min). Clamped at 5min.",
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
required: ["sessionId"],
|
|
96
|
+
additionalProperties: false,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
export async function handleStartCliLogin(args) {
|
|
100
|
+
const apiUrl = resolveApiUrl();
|
|
101
|
+
let res;
|
|
102
|
+
try {
|
|
103
|
+
res = await fetch(`${apiUrl}/api/v3/cli-session`, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: { "Content-Type": "application/json" },
|
|
106
|
+
body: JSON.stringify({ email: args.email }),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
111
|
+
return errorPayload("network_error", "Could not reach Sellable to start CLI login.", `Check your internet connection, then retry. Underlying error: ${message.slice(0, 200)}`);
|
|
112
|
+
}
|
|
113
|
+
if (res.status === 429) {
|
|
114
|
+
return errorPayload("rate_limited", "Too many login attempts in the last minute. Wait 60s and try again.", "Magic-link signups are rate-limited per IP to 10 per minute. This is normal during testing.");
|
|
115
|
+
}
|
|
116
|
+
if (!res.ok) {
|
|
117
|
+
let detail = "";
|
|
118
|
+
try {
|
|
119
|
+
detail = await res.text();
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// ignore
|
|
123
|
+
}
|
|
124
|
+
return errorPayload("send_failed", "Could not start CLI login.", detail.slice(0, 500) ||
|
|
125
|
+
`Server returned ${res.status}. Try again or contact support.`);
|
|
126
|
+
}
|
|
127
|
+
const body = (await res.json());
|
|
128
|
+
// SECURITY: do NOT log body.confirmUrl, body.sessionId, or any token.
|
|
129
|
+
return {
|
|
130
|
+
ok: true,
|
|
131
|
+
sessionId: body.sessionId,
|
|
132
|
+
confirmUrl: body.confirmUrl,
|
|
133
|
+
expiresInSec: body.expiresInSec,
|
|
134
|
+
humanMessage: `Magic link sent to ${args.email}. Click it from your inbox — I'll wait. (If your team already has a Sellable workspace, ask an admin to invite you instead — that gets you straight into their data.)`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export async function handleWaitForCliLogin(args) {
|
|
138
|
+
const timeoutMs = Math.min(args.timeoutMs ?? DEFAULT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS);
|
|
139
|
+
const toolGuardMs = args.toolGuardMs ?? TOOL_CALL_TIMEOUT_GUARD_MS;
|
|
140
|
+
const pollIntervalMs = args.pollIntervalMs ?? POLL_INTERVAL_MS;
|
|
141
|
+
const apiUrl = resolveApiUrl();
|
|
142
|
+
const startedAt = Date.now();
|
|
143
|
+
const work = (async () => {
|
|
144
|
+
let consecutiveNetworkFailures = 0;
|
|
145
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
146
|
+
let res;
|
|
147
|
+
try {
|
|
148
|
+
res = await fetch(`${apiUrl}/api/v3/cli-session/${args.sessionId}/poll`);
|
|
149
|
+
consecutiveNetworkFailures = 0;
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
consecutiveNetworkFailures += 1;
|
|
153
|
+
if (consecutiveNetworkFailures >= MAX_NETWORK_RETRIES) {
|
|
154
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
155
|
+
return errorPayload("network_error", "Lost connection to Sellable while waiting for sign-in.", `Tried ${MAX_NETWORK_RETRIES} times. Check your internet and retry. Underlying error: ${message.slice(0, 200)}`);
|
|
156
|
+
}
|
|
157
|
+
await sleep(pollIntervalMs);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (res.status === 410) {
|
|
161
|
+
return errorPayload("expired", "That magic link expired.", "Run /sellable:create-campaign again to retry.");
|
|
162
|
+
}
|
|
163
|
+
if (!res.ok) {
|
|
164
|
+
// Transient — back off and retry
|
|
165
|
+
await sleep(pollIntervalMs);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
let body;
|
|
169
|
+
try {
|
|
170
|
+
body = (await res.json());
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
await sleep(pollIntervalMs);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (body.status === "ready") {
|
|
177
|
+
const { configPath } = writeNewConfig({
|
|
178
|
+
token: body.token,
|
|
179
|
+
activeWorkspaceId: body.activeWorkspaceId,
|
|
180
|
+
activeWorkspaceName: body.activeWorkspaceName,
|
|
181
|
+
apiUrl: body.apiUrl,
|
|
182
|
+
});
|
|
183
|
+
return {
|
|
184
|
+
ok: true,
|
|
185
|
+
activeWorkspaceId: body.activeWorkspaceId,
|
|
186
|
+
...(body.activeWorkspaceName
|
|
187
|
+
? { activeWorkspaceName: body.activeWorkspaceName }
|
|
188
|
+
: {}),
|
|
189
|
+
configPath,
|
|
190
|
+
humanMessage: "Signed in. Continuing.",
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (body.status === "claimed") {
|
|
194
|
+
return errorPayload("already_consumed", "Login already completed in another window.", "Your terminal config has been written; try the next step.");
|
|
195
|
+
}
|
|
196
|
+
// status === 'pending' — keep polling
|
|
197
|
+
await sleep(pollIntervalMs);
|
|
198
|
+
}
|
|
199
|
+
return errorPayload("timeout", "Login timed out after 5 minutes.", "Run /sellable:create-campaign again, or paste the manual fallback shown in the browser.");
|
|
200
|
+
})();
|
|
201
|
+
const guard = new Promise((resolve) => {
|
|
202
|
+
setTimeout(() => {
|
|
203
|
+
resolve(errorPayload("tool_timeout_guard", "Tool call hit the 55s host-cap guard.", "Re-call wait_for_cli_login with the SAME sessionId — the underlying CliSession is still pending; this does NOT create a new session."));
|
|
204
|
+
}, toolGuardMs).unref?.();
|
|
205
|
+
});
|
|
206
|
+
return Promise.race([work, guard]);
|
|
207
|
+
}
|
|
@@ -13,7 +13,7 @@ type SenderEnrichmentSnapshot = {
|
|
|
13
13
|
hasCaseStudies: boolean;
|
|
14
14
|
hasProof: boolean;
|
|
15
15
|
};
|
|
16
|
-
type SenderResearchDepth = "minimal-verification" | "deep-proof";
|
|
16
|
+
type SenderResearchDepth = "minimal-verification" | "deep-proof" | "parallel-batch";
|
|
17
17
|
export declare function markCreateCampaignPromptLoaded(): CreateCampaignPromptPreflightState;
|
|
18
18
|
export declare function markResearchPromptLoaded(mode?: ResearchPromptMode, subskillName?: string): ResearchPromptPreflightState;
|
|
19
19
|
export declare function markSenderEnrichmentObserved(input: {
|
|
@@ -48,21 +48,8 @@ export declare function assertResearchPromptLoaded(mode?: ResearchPromptMode | "
|
|
|
48
48
|
};
|
|
49
49
|
export declare function assertNetNewCreateCampaignResearchReady(): {
|
|
50
50
|
readonly ok: true;
|
|
51
|
-
readonly
|
|
52
|
-
readonly
|
|
53
|
-
readonly hasProof: boolean;
|
|
54
|
-
readonly enrichmentLooksComplete: boolean;
|
|
55
|
-
readonly usedResearchPrompt: boolean;
|
|
56
|
-
readonly researchPromptMode: ResearchPromptMode;
|
|
57
|
-
readonly researchPromptSubskillName: string;
|
|
58
|
-
readonly researchCompletion: {
|
|
59
|
-
readonly depth: SenderResearchDepth;
|
|
60
|
-
readonly proofItemsFound: number;
|
|
61
|
-
readonly caseStudyItemsFound: number;
|
|
62
|
-
readonly credibilitySignalsFound: number;
|
|
63
|
-
readonly completedAt: string;
|
|
64
|
-
};
|
|
65
|
-
readonly needsResearch: true;
|
|
51
|
+
readonly senderResearchPromptLoadedAt: string;
|
|
52
|
+
readonly senderResearchCompletedAt: string;
|
|
66
53
|
};
|
|
67
54
|
export declare function resetFlowPreflightState(): void;
|
|
68
55
|
export {};
|
|
@@ -93,40 +93,21 @@ export function assertResearchPromptLoaded(mode = "any") {
|
|
|
93
93
|
throw new Error("FLOW_PRECONDITION: research preload missing. Call get_subskill_prompt with the matching research wrapper (research-sender or research-prospect), then run research before continuing.");
|
|
94
94
|
}
|
|
95
95
|
export function assertNetNewCreateCampaignResearchReady() {
|
|
96
|
-
if (!senderEnrichmentSnapshot) {
|
|
97
|
-
throw new Error("FLOW_PRECONDITION: net-new create_campaign requires sender enrichment context. Call enrich_sender first.");
|
|
98
|
-
}
|
|
99
|
-
const enrichmentLooksComplete = senderEnrichmentSnapshot.enrichmentStatus === "complete" &&
|
|
100
|
-
senderEnrichmentSnapshot.hasCaseStudies &&
|
|
101
|
-
senderEnrichmentSnapshot.hasProof;
|
|
102
96
|
const senderResearchPromptState = researchPromptStates.sender;
|
|
103
97
|
if (!senderResearchPromptState) {
|
|
104
|
-
throw new Error('FLOW_PRECONDITION: net-new create_campaign requires sender research
|
|
98
|
+
throw new Error('FLOW_PRECONDITION: net-new create_campaign requires sender research preload. Call get_subskill_prompt({ subskillName: "research-sender" }) and run sender research before create_campaign.');
|
|
105
99
|
}
|
|
106
100
|
if (!senderResearchCompletionState) {
|
|
107
|
-
throw new Error("FLOW_PRECONDITION: completed sender research is required before create_campaign. After running research-sender, call complete_sender_research({ depth, proofItemsFound, caseStudyItemsFound, credibilitySignalsFound, notes? }).");
|
|
101
|
+
throw new Error("FLOW_PRECONDITION: completed sender research is required before create_campaign. After running research-sender (parallel batch of fetch_linkedin_profile + fetch_company + WebSearches), call complete_sender_research({ depth, proofItemsFound, caseStudyItemsFound, credibilitySignalsFound, notes? }).");
|
|
108
102
|
}
|
|
109
103
|
if (new Date(senderResearchCompletionState.completedAt).getTime() <
|
|
110
|
-
new Date(
|
|
111
|
-
throw new Error("FLOW_PRECONDITION: sender research completion is
|
|
104
|
+
new Date(senderResearchPromptState.loadedAt).getTime()) {
|
|
105
|
+
throw new Error("FLOW_PRECONDITION: sender research completion is older than the latest research-sender prompt load. Re-run sender research and call complete_sender_research again.");
|
|
112
106
|
}
|
|
113
107
|
return {
|
|
114
108
|
ok: true,
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
hasProof: senderEnrichmentSnapshot.hasProof,
|
|
118
|
-
enrichmentLooksComplete,
|
|
119
|
-
usedResearchPrompt: Boolean(senderResearchPromptState),
|
|
120
|
-
researchPromptMode: senderResearchPromptState?.mode ?? null,
|
|
121
|
-
researchPromptSubskillName: senderResearchPromptState?.subskillName ?? null,
|
|
122
|
-
researchCompletion: {
|
|
123
|
-
depth: senderResearchCompletionState.depth,
|
|
124
|
-
proofItemsFound: senderResearchCompletionState.proofItemsFound,
|
|
125
|
-
caseStudyItemsFound: senderResearchCompletionState.caseStudyItemsFound,
|
|
126
|
-
credibilitySignalsFound: senderResearchCompletionState.credibilitySignalsFound,
|
|
127
|
-
completedAt: senderResearchCompletionState.completedAt,
|
|
128
|
-
},
|
|
129
|
-
needsResearch: true,
|
|
109
|
+
senderResearchPromptLoadedAt: senderResearchPromptState.loadedAt,
|
|
110
|
+
senderResearchCompletedAt: senderResearchCompletionState.completedAt,
|
|
130
111
|
};
|
|
131
112
|
}
|
|
132
113
|
export function resetFlowPreflightState() {
|
package/dist/tools/navigation.js
CHANGED
|
@@ -138,8 +138,8 @@ function checkCampaignCreated(campaign) {
|
|
|
138
138
|
const missing = [];
|
|
139
139
|
if (!campaign.id)
|
|
140
140
|
missing.push("campaignId");
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
// Phase 114: clientProspectId is optional. A persisted campaign without
|
|
142
|
+
// clientProspectId is a valid Phase-114 net-new campaign.
|
|
143
143
|
if (!getBriefContent(campaign.campaignBrief).trim()) {
|
|
144
144
|
missing.push("campaignBrief.content");
|
|
145
145
|
}
|
package/dist/tools/one-off.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export type CreateOnDemandTableInput = {
|
|
2
2
|
name: string;
|
|
3
3
|
senderIds?: string[];
|
|
4
|
-
clientProspectId
|
|
4
|
+
clientProspectId?: string;
|
|
5
|
+
senderLinkedinUrl?: string;
|
|
5
6
|
offerPositioning?: Record<string, unknown>;
|
|
6
7
|
campaignBrief?: unknown;
|
|
7
8
|
};
|
|
@@ -26,7 +27,8 @@ export type InitOnDemandSequenceInput = {
|
|
|
26
27
|
};
|
|
27
28
|
export type CreateOnDemandCampaignInput = {
|
|
28
29
|
name: string;
|
|
29
|
-
clientProspectId
|
|
30
|
+
clientProspectId?: string;
|
|
31
|
+
senderLinkedinUrl?: string;
|
|
30
32
|
senderIds: string[];
|
|
31
33
|
campaignBrief?: unknown;
|
|
32
34
|
offerPositioning?: Record<string, unknown>;
|
|
@@ -54,6 +56,10 @@ export declare const onDemandToolDefinitions: ({
|
|
|
54
56
|
type: string;
|
|
55
57
|
description: string;
|
|
56
58
|
};
|
|
59
|
+
senderLinkedinUrl: {
|
|
60
|
+
type: string;
|
|
61
|
+
description: string;
|
|
62
|
+
};
|
|
57
63
|
offerPositioning: {
|
|
58
64
|
type: string;
|
|
59
65
|
description: string;
|
|
@@ -84,6 +90,10 @@ export declare const onDemandToolDefinitions: ({
|
|
|
84
90
|
type: string;
|
|
85
91
|
description: string;
|
|
86
92
|
};
|
|
93
|
+
senderLinkedinUrl: {
|
|
94
|
+
type: string;
|
|
95
|
+
description: string;
|
|
96
|
+
};
|
|
87
97
|
senderIds: {
|
|
88
98
|
type: string;
|
|
89
99
|
items: {
|
|
@@ -155,6 +165,7 @@ export declare const onDemandToolDefinitions: ({
|
|
|
155
165
|
name?: undefined;
|
|
156
166
|
senderIds?: undefined;
|
|
157
167
|
clientProspectId?: undefined;
|
|
168
|
+
senderLinkedinUrl?: undefined;
|
|
158
169
|
offerPositioning?: undefined;
|
|
159
170
|
campaignBrief?: undefined;
|
|
160
171
|
sequenceTemplate?: undefined;
|
|
@@ -176,6 +187,7 @@ export declare const onDemandToolDefinitions: ({
|
|
|
176
187
|
name?: undefined;
|
|
177
188
|
senderIds?: undefined;
|
|
178
189
|
clientProspectId?: undefined;
|
|
190
|
+
senderLinkedinUrl?: undefined;
|
|
179
191
|
offerPositioning?: undefined;
|
|
180
192
|
campaignBrief?: undefined;
|
|
181
193
|
sequenceTemplate?: undefined;
|
|
@@ -207,6 +219,7 @@ export declare const onDemandToolDefinitions: ({
|
|
|
207
219
|
name?: undefined;
|
|
208
220
|
senderIds?: undefined;
|
|
209
221
|
clientProspectId?: undefined;
|
|
222
|
+
senderLinkedinUrl?: undefined;
|
|
210
223
|
offerPositioning?: undefined;
|
|
211
224
|
campaignBrief?: undefined;
|
|
212
225
|
sequenceTemplate?: undefined;
|
package/dist/tools/one-off.js
CHANGED
|
@@ -38,7 +38,8 @@ const SIMPLE_LINEAR_TEMPLATE = {
|
|
|
38
38
|
export const onDemandToolDefinitions = [
|
|
39
39
|
{
|
|
40
40
|
name: "create_on_demand_table",
|
|
41
|
-
description: "Create an on-demand campaign table backed by a lightweight CampaignOffer. Returns tableId + campaignOfferId
|
|
41
|
+
description: "Create an on-demand campaign table backed by a lightweight CampaignOffer. Returns tableId + campaignOfferId.\n\n" +
|
|
42
|
+
"INPUTS: Pass EITHER clientProspectId (existing prospect ID) OR senderLinkedinUrl (sender's LinkedIn profile URL). One is required.",
|
|
42
43
|
inputSchema: {
|
|
43
44
|
type: "object",
|
|
44
45
|
properties: {
|
|
@@ -53,7 +54,11 @@ export const onDemandToolDefinitions = [
|
|
|
53
54
|
},
|
|
54
55
|
clientProspectId: {
|
|
55
56
|
type: "string",
|
|
56
|
-
description: "
|
|
57
|
+
description: "Optional. Pass this OR senderLinkedinUrl.",
|
|
58
|
+
},
|
|
59
|
+
senderLinkedinUrl: {
|
|
60
|
+
type: "string",
|
|
61
|
+
description: "Optional. Sender's LinkedIn profile URL — used to lazy-resolve the prospect at send-time when clientProspectId is not yet available.",
|
|
57
62
|
},
|
|
58
63
|
offerPositioning: {
|
|
59
64
|
type: "object",
|
|
@@ -63,12 +68,13 @@ export const onDemandToolDefinitions = [
|
|
|
63
68
|
description: "Optional campaign brief content (markdown or JSON)",
|
|
64
69
|
},
|
|
65
70
|
},
|
|
66
|
-
required: ["name"
|
|
71
|
+
required: ["name"],
|
|
67
72
|
},
|
|
68
73
|
},
|
|
69
74
|
{
|
|
70
75
|
name: "create_on_demand_campaign",
|
|
71
|
-
description: "Create a lightweight CampaignOffer + workflow table, set senders, and initialize sequence columns. Auto-selects template by sender tiers unless provided
|
|
76
|
+
description: "Create a lightweight CampaignOffer + workflow table, set senders, and initialize sequence columns. Auto-selects template by sender tiers unless provided.\n\n" +
|
|
77
|
+
"INPUTS: Pass EITHER clientProspectId (existing prospect ID) OR senderLinkedinUrl (sender's LinkedIn profile URL). One is required.",
|
|
72
78
|
inputSchema: {
|
|
73
79
|
type: "object",
|
|
74
80
|
properties: {
|
|
@@ -78,7 +84,11 @@ export const onDemandToolDefinitions = [
|
|
|
78
84
|
},
|
|
79
85
|
clientProspectId: {
|
|
80
86
|
type: "string",
|
|
81
|
-
description: "
|
|
87
|
+
description: "Optional. Pass this OR senderLinkedinUrl.",
|
|
88
|
+
},
|
|
89
|
+
senderLinkedinUrl: {
|
|
90
|
+
type: "string",
|
|
91
|
+
description: "Optional. Sender's LinkedIn profile URL — used to lazy-resolve the prospect at send-time when clientProspectId is not yet available.",
|
|
82
92
|
},
|
|
83
93
|
senderIds: {
|
|
84
94
|
type: "array",
|
|
@@ -101,7 +111,7 @@ export const onDemandToolDefinitions = [
|
|
|
101
111
|
description: "Set true to overwrite existing sequence columns",
|
|
102
112
|
},
|
|
103
113
|
},
|
|
104
|
-
required: ["name", "
|
|
114
|
+
required: ["name", "senderIds"],
|
|
105
115
|
},
|
|
106
116
|
},
|
|
107
117
|
{
|
|
@@ -180,6 +190,9 @@ export const onDemandToolDefinitions = [
|
|
|
180
190
|
},
|
|
181
191
|
];
|
|
182
192
|
export async function createOnDemandTable(input) {
|
|
193
|
+
if (!input.clientProspectId && !input.senderLinkedinUrl) {
|
|
194
|
+
throw new Error("VALIDATION_ERROR: create_on_demand_table requires either clientProspectId (existing prospect ID) or senderLinkedinUrl (sender's LinkedIn profile URL). Pass one.");
|
|
195
|
+
}
|
|
183
196
|
const api = getApi();
|
|
184
197
|
return api.post("/api/v3/on-demand-campaigns", input);
|
|
185
198
|
}
|
|
@@ -221,6 +234,9 @@ async function initCampaignSequence(input) {
|
|
|
221
234
|
return api.put(`/api/v3/campaigns/${input.campaignOfferId}/sequence`, payload);
|
|
222
235
|
}
|
|
223
236
|
export async function createOnDemandCampaign(input) {
|
|
237
|
+
if (!input.clientProspectId && !input.senderLinkedinUrl) {
|
|
238
|
+
throw new Error("VALIDATION_ERROR: create_on_demand_campaign requires either clientProspectId (existing prospect ID) or senderLinkedinUrl (sender's LinkedIn profile URL). Pass one.");
|
|
239
|
+
}
|
|
224
240
|
if (!Array.isArray(input.senderIds) || input.senderIds.length === 0) {
|
|
225
241
|
throw new Error("senderIds must be a non-empty array");
|
|
226
242
|
}
|
|
@@ -228,6 +244,7 @@ export async function createOnDemandCampaign(input) {
|
|
|
228
244
|
name: input.name,
|
|
229
245
|
senderIds: input.senderIds,
|
|
230
246
|
clientProspectId: input.clientProspectId,
|
|
247
|
+
senderLinkedinUrl: input.senderLinkedinUrl,
|
|
231
248
|
offerPositioning: input.offerPositioning,
|
|
232
249
|
campaignBrief: input.campaignBrief,
|
|
233
250
|
});
|
package/dist/tools/prompts.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export interface SearchSubskillPromptsResponse {
|
|
|
41
41
|
}>;
|
|
42
42
|
}
|
|
43
43
|
export interface CompleteSenderResearchInput {
|
|
44
|
-
depth?: "minimal-verification" | "deep-proof";
|
|
44
|
+
depth?: "minimal-verification" | "deep-proof" | "parallel-batch";
|
|
45
45
|
proofItemsFound?: number;
|
|
46
46
|
caseStudyItemsFound?: number;
|
|
47
47
|
credibilitySignalsFound?: number;
|
|
@@ -213,7 +213,7 @@ export declare function listSubskillPrompts(limit?: number, includePublic?: bool
|
|
|
213
213
|
export declare function getSubskillPrompt(subskillName: string, offset?: number, limit?: number): SubskillPromptResponse;
|
|
214
214
|
export declare function completeSenderResearch(input?: CompleteSenderResearchInput): {
|
|
215
215
|
readonly completedAt: string;
|
|
216
|
-
readonly depth: "minimal-verification" | "deep-proof";
|
|
216
|
+
readonly depth: "minimal-verification" | "deep-proof" | "parallel-batch";
|
|
217
217
|
readonly proofItemsFound: number;
|
|
218
218
|
readonly caseStudyItemsFound: number;
|
|
219
219
|
readonly credibilitySignalsFound: number;
|
package/dist/tools/prompts.js
CHANGED
|
@@ -97,8 +97,8 @@ export const promptToolDefinitions = [
|
|
|
97
97
|
properties: {
|
|
98
98
|
depth: {
|
|
99
99
|
type: "string",
|
|
100
|
-
enum: ["minimal-verification", "deep-proof"],
|
|
101
|
-
description: "Research depth used: minimal
|
|
100
|
+
enum: ["minimal-verification", "deep-proof", "parallel-batch"],
|
|
101
|
+
description: "Research depth used: minimal-verification (proofDigest already had strong signals), deep-proof (extra search agents spawned), or parallel-batch (Phase 114+ default — single 5-call parallel batch with fetch_linkedin_profile + fetch_company + 3x WebSearch).",
|
|
102
102
|
},
|
|
103
103
|
proofItemsFound: {
|
|
104
104
|
type: "number",
|
|
@@ -20,6 +20,7 @@ export declare function resolveWaitTimeout(timeoutMs?: number): {
|
|
|
20
20
|
effectiveTimeoutMs: number;
|
|
21
21
|
guardApplied: boolean;
|
|
22
22
|
};
|
|
23
|
+
export declare function hasNonWaitableMissing(missing: string[]): boolean;
|
|
23
24
|
export declare const readinessToolDefinitions: ({
|
|
24
25
|
name: string;
|
|
25
26
|
description: string;
|
package/dist/tools/readiness.js
CHANGED
|
@@ -32,9 +32,11 @@ export function resolveWaitTimeout(timeoutMs) {
|
|
|
32
32
|
guardApplied: requestedTimeoutMs > effectiveTimeoutMs,
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
-
function hasNonWaitableMissing(missing) {
|
|
35
|
+
export function hasNonWaitableMissing(missing) {
|
|
36
|
+
// Phase 114: clientProspectId is no longer non-waitable. Net-new campaigns
|
|
37
|
+
// can be created with senderLinkedinUrl instead, with downstream resolution
|
|
38
|
+
// happening at send-time.
|
|
36
39
|
const nonWaitable = new Set([
|
|
37
|
-
"clientProspectId",
|
|
38
40
|
"campaignBrief.content",
|
|
39
41
|
"leadSourceType",
|
|
40
42
|
'leadSourceType="new"',
|
|
@@ -2,6 +2,7 @@ export type CreateWorkflowTableInput = {
|
|
|
2
2
|
name: string;
|
|
3
3
|
senderId: string;
|
|
4
4
|
clientProspectId?: string;
|
|
5
|
+
senderLinkedinUrl?: string;
|
|
5
6
|
sequenceActions?: string[];
|
|
6
7
|
};
|
|
7
8
|
export type CreateWorkflowTableResponse = {
|
|
@@ -58,6 +59,10 @@ export declare const sequencerToolDefinitions: ({
|
|
|
58
59
|
type: string;
|
|
59
60
|
description: string;
|
|
60
61
|
};
|
|
62
|
+
senderLinkedinUrl: {
|
|
63
|
+
type: string;
|
|
64
|
+
description: string;
|
|
65
|
+
};
|
|
61
66
|
sequenceActions: {
|
|
62
67
|
type: string;
|
|
63
68
|
items: {
|
|
@@ -93,6 +98,7 @@ export declare const sequencerToolDefinitions: ({
|
|
|
93
98
|
name?: undefined;
|
|
94
99
|
senderId?: undefined;
|
|
95
100
|
clientProspectId?: undefined;
|
|
101
|
+
senderLinkedinUrl?: undefined;
|
|
96
102
|
sequenceActions?: undefined;
|
|
97
103
|
campaignId?: undefined;
|
|
98
104
|
};
|
|
@@ -115,6 +121,7 @@ export declare const sequencerToolDefinitions: ({
|
|
|
115
121
|
name?: undefined;
|
|
116
122
|
senderId?: undefined;
|
|
117
123
|
clientProspectId?: undefined;
|
|
124
|
+
senderLinkedinUrl?: undefined;
|
|
118
125
|
sequenceActions?: undefined;
|
|
119
126
|
tableId?: undefined;
|
|
120
127
|
template?: undefined;
|
package/dist/tools/sequencer.js
CHANGED
|
@@ -7,7 +7,8 @@ export const sequencerToolDefinitions = [
|
|
|
7
7
|
"All tables include LinkedIn URL, Name, and Approved. " +
|
|
8
8
|
"Additional columns depend on the planned actions: INVITE/DM -> Message, " +
|
|
9
9
|
"INMAIL_* -> Subject + Message, COMMENT -> Post URL + Comment + Reaction Type. " +
|
|
10
|
-
"Campaign brief, rubrics, positioning, and prospect linkage remain optional follow-on metadata
|
|
10
|
+
"Campaign brief, rubrics, positioning, and prospect linkage remain optional follow-on metadata.\n\n" +
|
|
11
|
+
"INPUTS: Pass EITHER clientProspectId OR senderLinkedinUrl. Both optional — if neither is supplied, the table is created without prospect linkage.",
|
|
11
12
|
inputSchema: {
|
|
12
13
|
type: "object",
|
|
13
14
|
properties: {
|
|
@@ -21,7 +22,11 @@ export const sequencerToolDefinitions = [
|
|
|
21
22
|
},
|
|
22
23
|
clientProspectId: {
|
|
23
24
|
type: "string",
|
|
24
|
-
description: "Optional
|
|
25
|
+
description: "Optional. EnrichedProspect ID. Pass this OR senderLinkedinUrl.",
|
|
26
|
+
},
|
|
27
|
+
senderLinkedinUrl: {
|
|
28
|
+
type: "string",
|
|
29
|
+
description: "Optional. Sender's LinkedIn profile URL — used to lazy-resolve the prospect at send-time when clientProspectId is not yet available.",
|
|
25
30
|
},
|
|
26
31
|
sequenceActions: {
|
|
27
32
|
type: "array",
|
|
@@ -87,6 +92,7 @@ export async function createWorkflowTable(input) {
|
|
|
87
92
|
name: input.name,
|
|
88
93
|
senderId: input.senderId,
|
|
89
94
|
clientProspectId: input.clientProspectId,
|
|
95
|
+
senderLinkedinUrl: input.senderLinkedinUrl,
|
|
90
96
|
...(input.sequenceActions
|
|
91
97
|
? { sequenceActions: input.sequenceActions }
|
|
92
98
|
: {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: building-gtm-tables
|
|
3
3
|
description: Turn a GTM idea into a fully configured, verified workflow table. Use this skill whenever the user asks to "build a table that..." / "set up an enrichment + scoring pipeline" / "add columns that run end-to-end against my rows." Blueprints the whole table in memory, validates composition, commits atomically in topological order, then runs single-row verify + a user-approved 2-5 row batch before declaring done. Do NOT use this skill for pure messaging or for editing an already-committed table one column at a time.
|
|
4
|
+
visibility: internal
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# Building GTM Tables
|