@sellable/mcp 0.1.531 → 0.1.532
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 +1 -0
- package/dist/auth.js +23 -4
- package/dist/index-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/tools/auth.d.ts +3 -0
- package/dist/tools/auth.js +19 -5
- package/dist/tools/bootstrap.js +1 -1
- package/dist/tools/campaigns.d.ts +2 -2
- package/dist/tools/campaigns.js +17 -5
- package/dist/tools/model-quality.d.ts +2 -1
- package/dist/tools/model-quality.js +21 -1
- package/dist/tools/refill-sends-evergreen.d.ts +28 -0
- package/dist/tools/refill-sends-evergreen.js +47 -0
- package/package.json +3 -2
- package/skills/research/config.json +9 -0
package/dist/auth.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export type SkillState = {
|
|
|
38
38
|
* - "sellable-dev.json" - local development
|
|
39
39
|
*/
|
|
40
40
|
export declare function setConfigFile(fileName: string): void;
|
|
41
|
+
export declare function getResolvedConfigsDir(): string | null;
|
|
41
42
|
export declare function getConfigPath(): string;
|
|
42
43
|
export declare function getConfig(): SellableConfig;
|
|
43
44
|
export declare function updateActiveWorkspace(params: {
|
package/dist/auth.js
CHANGED
|
@@ -19,9 +19,9 @@ export function setConfigFile(fileName) {
|
|
|
19
19
|
}
|
|
20
20
|
function getConfigPathCandidates() {
|
|
21
21
|
const candidates = [];
|
|
22
|
-
const explicitConfigPath =
|
|
22
|
+
const explicitConfigPath = getExplicitConfigPath();
|
|
23
23
|
if (explicitConfigPath) {
|
|
24
|
-
candidates.push(
|
|
24
|
+
candidates.push(explicitConfigPath);
|
|
25
25
|
}
|
|
26
26
|
if (configFileName === "sellable.json") {
|
|
27
27
|
candidates.push(path.join(os.homedir(), ".sellable", "config.json"));
|
|
@@ -34,12 +34,31 @@ function getConfigPathCandidates() {
|
|
|
34
34
|
candidates.push(path.join(os.homedir(), ".claude", configFileName));
|
|
35
35
|
return Array.from(new Set(candidates));
|
|
36
36
|
}
|
|
37
|
+
function getExplicitConfigPath() {
|
|
38
|
+
const explicitConfigPath = process.env.SELLABLE_CONFIG_PATH?.trim();
|
|
39
|
+
return explicitConfigPath ? path.resolve(explicitConfigPath) : null;
|
|
40
|
+
}
|
|
41
|
+
export function getResolvedConfigsDir() {
|
|
42
|
+
const explicitConfigsDir = process.env.SELLABLE_CONFIGS_DIR?.trim();
|
|
43
|
+
if (explicitConfigsDir) {
|
|
44
|
+
return path.resolve(explicitConfigsDir);
|
|
45
|
+
}
|
|
46
|
+
const explicitConfigPath = getExplicitConfigPath();
|
|
47
|
+
if (explicitConfigPath) {
|
|
48
|
+
return path.join(path.dirname(explicitConfigPath), "configs");
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
37
52
|
function renderConfigPathOrder(candidates) {
|
|
38
53
|
return candidates
|
|
39
54
|
.map((candidate, idx) => `${idx + 1}. ${candidate}`)
|
|
40
55
|
.join("\n");
|
|
41
56
|
}
|
|
42
57
|
export function getConfigPath() {
|
|
58
|
+
const explicitConfigPath = getExplicitConfigPath();
|
|
59
|
+
if (explicitConfigPath) {
|
|
60
|
+
return explicitConfigPath;
|
|
61
|
+
}
|
|
43
62
|
const candidates = getConfigPathCandidates();
|
|
44
63
|
for (const candidate of candidates) {
|
|
45
64
|
if (fs.existsSync(candidate)) {
|
|
@@ -49,9 +68,9 @@ export function getConfigPath() {
|
|
|
49
68
|
return candidates[0];
|
|
50
69
|
}
|
|
51
70
|
function getConfigWritePath() {
|
|
52
|
-
const explicitConfigPath =
|
|
71
|
+
const explicitConfigPath = getExplicitConfigPath();
|
|
53
72
|
if (explicitConfigPath) {
|
|
54
|
-
return
|
|
73
|
+
return explicitConfigPath;
|
|
55
74
|
}
|
|
56
75
|
if (configFileName === "sellable.json") {
|
|
57
76
|
return path.join(os.homedir(), ".sellable", "config.json");
|
package/dist/index-dev.js
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/tools/auth.d.ts
CHANGED
|
@@ -2,10 +2,13 @@ import { type SellableUpdateStatus } from "../update-check.js";
|
|
|
2
2
|
export type AuthStatus = {
|
|
3
3
|
ok: boolean;
|
|
4
4
|
configPath: string;
|
|
5
|
+
configExists: boolean;
|
|
6
|
+
configsDir: string | null;
|
|
5
7
|
activeEnvName: string | null;
|
|
6
8
|
apiUrl: string | null;
|
|
7
9
|
activeWorkspaceId: string | null;
|
|
8
10
|
activeWorkspaceName: string | null;
|
|
11
|
+
tokenPresent: boolean;
|
|
9
12
|
tokenPrefix: string | null;
|
|
10
13
|
workspacesCount: number | null;
|
|
11
14
|
checkedAt: string;
|
package/dist/tools/auth.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
1
2
|
import { getApi, SellableApiError } from "../api.js";
|
|
2
|
-
import { getConfig, getConfigPath } from "../auth.js";
|
|
3
|
+
import { getConfig, getConfigPath, getResolvedConfigsDir } from "../auth.js";
|
|
3
4
|
import { checkForUpdates } from "../update-check.js";
|
|
4
5
|
function maskToken(token) {
|
|
5
6
|
if (token.length <= 10)
|
|
@@ -33,15 +34,20 @@ export const authToolDefinitions = [
|
|
|
33
34
|
];
|
|
34
35
|
export async function getAuthStatus() {
|
|
35
36
|
const configPath = getConfigPath();
|
|
37
|
+
const configsDir = getResolvedConfigsDir();
|
|
36
38
|
const checkedAt = new Date().toISOString();
|
|
37
39
|
const update = await getUpdateStatus();
|
|
40
|
+
let tokenPresent = false;
|
|
38
41
|
const base = {
|
|
39
42
|
ok: false,
|
|
40
43
|
configPath,
|
|
44
|
+
configExists: fs.existsSync(configPath),
|
|
45
|
+
configsDir,
|
|
41
46
|
activeEnvName: null,
|
|
42
47
|
apiUrl: null,
|
|
43
48
|
activeWorkspaceId: null,
|
|
44
49
|
activeWorkspaceName: null,
|
|
50
|
+
tokenPresent: false,
|
|
45
51
|
tokenPrefix: null,
|
|
46
52
|
workspacesCount: null,
|
|
47
53
|
_userNotice: appendUpdateNotice(null, update),
|
|
@@ -50,6 +56,7 @@ export async function getAuthStatus() {
|
|
|
50
56
|
};
|
|
51
57
|
try {
|
|
52
58
|
const config = getConfig();
|
|
59
|
+
tokenPresent = Boolean(config.token);
|
|
53
60
|
base.activeEnvName = config.activeEnvName || null;
|
|
54
61
|
const api = getApi();
|
|
55
62
|
const { workspaces } = await api.get("/api/v3/workspaces");
|
|
@@ -61,6 +68,7 @@ export async function getAuthStatus() {
|
|
|
61
68
|
return {
|
|
62
69
|
...base,
|
|
63
70
|
apiUrl: config.apiUrl || null,
|
|
71
|
+
tokenPresent,
|
|
64
72
|
tokenPrefix: maskToken(config.token),
|
|
65
73
|
workspacesCount: workspaces.length,
|
|
66
74
|
error: {
|
|
@@ -77,6 +85,7 @@ export async function getAuthStatus() {
|
|
|
77
85
|
return {
|
|
78
86
|
...base,
|
|
79
87
|
apiUrl: config.apiUrl || null,
|
|
88
|
+
tokenPresent,
|
|
80
89
|
tokenPrefix: maskToken(config.token),
|
|
81
90
|
workspacesCount: workspaces.length,
|
|
82
91
|
error: {
|
|
@@ -97,10 +106,13 @@ export async function getAuthStatus() {
|
|
|
97
106
|
return {
|
|
98
107
|
ok: true,
|
|
99
108
|
configPath,
|
|
109
|
+
configExists: fs.existsSync(configPath),
|
|
110
|
+
configsDir,
|
|
100
111
|
activeEnvName: envLabel,
|
|
101
112
|
apiUrl: config.apiUrl || null,
|
|
102
113
|
activeWorkspaceId,
|
|
103
114
|
activeWorkspaceName: workspaceName,
|
|
115
|
+
tokenPresent,
|
|
104
116
|
tokenPrefix: maskToken(config.token),
|
|
105
117
|
workspacesCount: workspaces.length,
|
|
106
118
|
_userNotice: appendUpdateNotice(notice, update),
|
|
@@ -124,7 +136,7 @@ export async function getAuthStatus() {
|
|
|
124
136
|
" 3. Click it, come back here, and we'll keep going\\n\\n" +
|
|
125
137
|
"What email should I use?` " +
|
|
126
138
|
"2) Wait for the user to type their email in normal chat (do NOT use AskUserQuestion / request_user_input). " +
|
|
127
|
-
"3) Call `mcp__sellable__start_cli_login({ email })` with that email. " +
|
|
139
|
+
"3) Call `mcp__sellable__start_cli_login({ email })` with that email in Claude Code/Codex, or `mcp_sellable_start_cli_login({ email })` in Hermes. " +
|
|
128
140
|
"4) On `ok: true`, say verbatim (substituting the email exactly as typed):\\n" +
|
|
129
141
|
"`Magic link sent to {email}.\\n\\n" +
|
|
130
142
|
"─────────────────────────────────────────────\\n" +
|
|
@@ -135,14 +147,15 @@ export async function getAuthStatus() {
|
|
|
135
147
|
" 3. Come back here when you're done\\n\\n" +
|
|
136
148
|
"I'll be waiting right here.\\n\\n" +
|
|
137
149
|
" (If your team already uses Sellable, ask an admin to invite you into their shared workspace instead — that gets you straight in.)` " +
|
|
138
|
-
"5) Call `mcp__sellable__wait_for_cli_login({ sessionId })` using the sessionId returned by start_cli_login. " +
|
|
139
|
-
"6) If the result is `error.type === 'tool_timeout_guard'`, IMMEDIATELY re-call wait_for_cli_login with the SAME sessionId — do not narrate, do not call start_cli_login again. Loop until you get a different result. " +
|
|
140
|
-
|
|
150
|
+
"5) Call `mcp__sellable__wait_for_cli_login({ sessionId })` in Claude Code/Codex, or `mcp_sellable_wait_for_cli_login({ sessionId })` in Hermes, using the sessionId returned by start_cli_login. " +
|
|
151
|
+
"6) If the result is `error.type === 'tool_timeout_guard'`, IMMEDIATELY re-call wait_for_cli_login with the SAME sessionId — do not narrate, do not call start_cli_login again. In Hermes, re-call `mcp_sellable_wait_for_cli_login({ sessionId })`. Loop until you get a different result. " +
|
|
152
|
+
`7) On \`ok: true\`, the user is signed in and the resolved Sellable config file has been written at ${configPath}. Branch on \`isReturningUser\` and use \`activeWorkspaceName\` when present, otherwise \`activeWorkspaceId\`, as \`{workspaceLabel}\`: ` +
|
|
141
153
|
"if true, say `You're in {workspaceLabel}.\\n\\nExcited to help you launch your LinkedIn outbound campaign. We're at setup: first I'll use your LinkedIn profile to understand the company, then I'll draft the campaign brief, help choose where to find buyers, review messages, and wait for final launch approval.\\n\\nWhat's your LinkedIn profile URL or handle?`; " +
|
|
142
154
|
"if false, say `You're set up in {workspaceLabel}.\\n\\nExcited to help you launch your LinkedIn outbound campaign. We're at setup: first I'll use your LinkedIn profile to understand the company, then I'll draft the campaign brief, help choose where to find buyers, review messages, and wait for final launch approval.\\n\\nWhat's your LinkedIn profile URL or handle?`";
|
|
143
155
|
if (error instanceof SellableApiError && error.isAuthError) {
|
|
144
156
|
return {
|
|
145
157
|
...base,
|
|
158
|
+
tokenPresent,
|
|
146
159
|
error: {
|
|
147
160
|
type: "auth",
|
|
148
161
|
status: error.status,
|
|
@@ -162,6 +175,7 @@ export async function getAuthStatus() {
|
|
|
162
175
|
: `Fix the configuration in ${configPath}, then retry get_auth_status.`;
|
|
163
176
|
return {
|
|
164
177
|
...base,
|
|
178
|
+
tokenPresent,
|
|
165
179
|
error: {
|
|
166
180
|
type: isConfigError ? "config" : "api",
|
|
167
181
|
message,
|
package/dist/tools/bootstrap.js
CHANGED
|
@@ -307,7 +307,7 @@ export async function bootstrapCreateCampaign(input = {}) {
|
|
|
307
307
|
? resumeDetected
|
|
308
308
|
? `Bootstrap complete.${workspaceNotice}${modelNotice} Resume from campaign state and navigation diagnostics first; treat local draft artifacts as debug-only evidence. 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.`
|
|
309
309
|
: flowVersion === "v2"
|
|
310
|
-
? `Bootstrap complete.${workspaceNotice}${modelNotice} Load the compact create-campaign-v2 entry prompt once with get_subskill_prompt({ subskillName: "create-campaign-v2" });
|
|
310
|
+
? `Bootstrap complete.${workspaceNotice}${modelNotice} Load the compact create-campaign-v2 entry prompt once with get_subskill_prompt({ subskillName: "create-campaign-v2" }); Hermes users should start this flow with /sellable-create-campaign. Load flow/reference assets lazily only when that stage needs them. Preserve the pre-intake sequence: confirm auth/workspace status, ask only for the LinkedIn profile URL or handle, normalize handles to a full profile URL, require that profile identity before continuing, run lightweight profile/company lookup, then ask the target, offer, credibility, and prospect-source setup questions. Do not call list_senders or sender discovery during setup; sender availability belongs only to Settings after message approval. Then write the campaign brief, call create_campaign once to mint the watchable shell, surface the returned watch link once before brief approval, and hand off to lead finding without repeating the link.`
|
|
311
311
|
: `Bootstrap complete.${workspaceNotice}${modelNotice} 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.`
|
|
312
312
|
: "Bootstrap incomplete. Resolve blockingErrors and rerun bootstrap_create_campaign before provider/search/import tools.";
|
|
313
313
|
// Strip prompt body from createCampaignSubskill — it's loaded via the host
|
|
@@ -9,9 +9,9 @@ declare const LEAD_SOURCE_PROVIDERS: {
|
|
|
9
9
|
};
|
|
10
10
|
type LeadSourceProvider = (typeof LEAD_SOURCE_PROVIDERS)[keyof typeof LEAD_SOURCE_PROVIDERS];
|
|
11
11
|
export declare function buildWatchUrl(config: Pick<ReturnType<typeof getConfig>, "apiUrl" | "token" | "activeWorkspaceId" | "workspaceId">, path: string): string;
|
|
12
|
-
export type CampaignBuilderWatchMode = "claude" | "codex";
|
|
12
|
+
export type CampaignBuilderWatchMode = "claude" | "codex" | "hermes";
|
|
13
13
|
export declare function getCampaignBuilderWatchModeParam(): CampaignBuilderWatchMode;
|
|
14
|
-
export declare function getCampaignBuilderWatchModeDriverLabel(mode?: CampaignBuilderWatchMode): "Claude Code" | "Codex";
|
|
14
|
+
export declare function getCampaignBuilderWatchModeDriverLabel(mode?: CampaignBuilderWatchMode): "Claude Code" | "Codex" | "Hermes";
|
|
15
15
|
export declare function buildCampaignWatchHandoffMarkdown(watchUrl: string, mode?: CampaignBuilderWatchMode): string;
|
|
16
16
|
export interface Campaign {
|
|
17
17
|
id: string;
|
package/dist/tools/campaigns.js
CHANGED
|
@@ -124,23 +124,35 @@ export function buildWatchUrl(config, path) {
|
|
|
124
124
|
}
|
|
125
125
|
return url.toString();
|
|
126
126
|
}
|
|
127
|
+
const CAMPAIGN_BUILDER_AGENT_WATCH_MODES = new Set([
|
|
128
|
+
"claude",
|
|
129
|
+
"codex",
|
|
130
|
+
"hermes",
|
|
131
|
+
]);
|
|
127
132
|
export function getCampaignBuilderWatchModeParam() {
|
|
128
133
|
const explicit = process.env.SELLABLE_WATCH_MODE_DRIVER?.trim().toLowerCase();
|
|
129
|
-
if (explicit
|
|
134
|
+
if (CAMPAIGN_BUILDER_AGENT_WATCH_MODES.has(explicit)) {
|
|
130
135
|
return explicit;
|
|
136
|
+
}
|
|
131
137
|
return process.env.CODEX_HOME ? "codex" : "claude";
|
|
132
138
|
}
|
|
133
139
|
function getCampaignBuilderWatchModeFromUrl(watchUrl) {
|
|
134
140
|
try {
|
|
135
141
|
const mode = new URL(watchUrl).searchParams.get("mode");
|
|
136
|
-
return mode
|
|
142
|
+
return CAMPAIGN_BUILDER_AGENT_WATCH_MODES.has(mode)
|
|
143
|
+
? mode
|
|
144
|
+
: null;
|
|
137
145
|
}
|
|
138
146
|
catch {
|
|
139
147
|
return null;
|
|
140
148
|
}
|
|
141
149
|
}
|
|
142
150
|
export function getCampaignBuilderWatchModeDriverLabel(mode = getCampaignBuilderWatchModeParam()) {
|
|
143
|
-
|
|
151
|
+
if (mode === "codex")
|
|
152
|
+
return "Codex";
|
|
153
|
+
if (mode === "hermes")
|
|
154
|
+
return "Hermes";
|
|
155
|
+
return "Claude Code";
|
|
144
156
|
}
|
|
145
157
|
export function buildCampaignWatchHandoffMarkdown(watchUrl, mode = getCampaignBuilderWatchModeFromUrl(watchUrl) ?? getCampaignBuilderWatchModeParam()) {
|
|
146
158
|
const driverLabel = getCampaignBuilderWatchModeDriverLabel(mode);
|
|
@@ -161,7 +173,7 @@ function isValidBriefHandoffWatchUrl(watchUrl, campaignId) {
|
|
|
161
173
|
const url = new URL(watchUrl);
|
|
162
174
|
const mode = url.searchParams.get("mode");
|
|
163
175
|
return (url.pathname === `/campaign-builder/${campaignId}` &&
|
|
164
|
-
(mode
|
|
176
|
+
CAMPAIGN_BUILDER_AGENT_WATCH_MODES.has(mode) &&
|
|
165
177
|
Boolean(url.searchParams.get("workspaceId")) &&
|
|
166
178
|
Boolean(url.searchParams.get("token")));
|
|
167
179
|
}
|
|
@@ -173,7 +185,7 @@ function assertBriefHandoffWatchUrl(watchUrl, campaignId) {
|
|
|
173
185
|
if (isValidBriefHandoffWatchUrl(watchUrl, campaignId))
|
|
174
186
|
return;
|
|
175
187
|
throw new Error("create_campaign produced an invalid watchUrl for the brief approval handoff. " +
|
|
176
|
-
"Recover a fresh direct /campaign-builder/{campaignId}?mode={claude|codex}&workspaceId=...&token=... URL " +
|
|
188
|
+
"Recover a fresh direct /campaign-builder/{campaignId}?mode={claude|codex|hermes}&workspaceId=...&token=... URL " +
|
|
177
189
|
"with create_campaign({ campaignId }) or get_campaign before asking for approval.");
|
|
178
190
|
}
|
|
179
191
|
export const campaignToolDefinitions = [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type CampaignModelHost = "claude" | "codex" | "unknown";
|
|
1
|
+
export type CampaignModelHost = "claude" | "codex" | "hermes" | "unknown";
|
|
2
2
|
export type CampaignModelQualityInput = {
|
|
3
3
|
host?: string | null;
|
|
4
4
|
model?: string | null;
|
|
@@ -33,6 +33,7 @@ export type CampaignModelQualityConfig = {
|
|
|
33
33
|
hosts: {
|
|
34
34
|
claude: CampaignModelQualityHostConfig;
|
|
35
35
|
codex: CampaignModelQualityHostConfig;
|
|
36
|
+
hermes: CampaignModelQualityHostConfig;
|
|
36
37
|
};
|
|
37
38
|
warningCopy: {
|
|
38
39
|
ok: string;
|
|
@@ -30,6 +30,20 @@ const DEFAULT_MODEL_QUALITY_CONFIG = {
|
|
|
30
30
|
],
|
|
31
31
|
recommendedReasoningEffort: "xhigh",
|
|
32
32
|
},
|
|
33
|
+
hermes: {
|
|
34
|
+
label: "Hermes",
|
|
35
|
+
minimumModel: "GPT 5.5",
|
|
36
|
+
familyKeywords: ["gpt"],
|
|
37
|
+
minimumVersion: "5.5",
|
|
38
|
+
minimumReasoningEffort: "xhigh",
|
|
39
|
+
acceptedReasoningEfforts: [
|
|
40
|
+
"extra high",
|
|
41
|
+
"extra-high",
|
|
42
|
+
"xhigh",
|
|
43
|
+
"extra_high",
|
|
44
|
+
],
|
|
45
|
+
recommendedReasoningEffort: "xhigh",
|
|
46
|
+
},
|
|
33
47
|
},
|
|
34
48
|
warningCopy: {
|
|
35
49
|
ok: "Active host model metadata meets the configured campaign floor: {currentSettings}.",
|
|
@@ -41,6 +55,8 @@ const TRUSTED_METADATA_SOURCE_KEYWORDS = [
|
|
|
41
55
|
"codex_turn_metadata",
|
|
42
56
|
"claude_runtime_metadata",
|
|
43
57
|
"claude_session_context",
|
|
58
|
+
"hermes_runtime_metadata",
|
|
59
|
+
"hermes_session_context",
|
|
44
60
|
"active_turn_metadata",
|
|
45
61
|
"user_confirmed",
|
|
46
62
|
];
|
|
@@ -49,6 +65,9 @@ const normalize = (value) => String(value ?? "")
|
|
|
49
65
|
.toLowerCase();
|
|
50
66
|
const normalizeHost = (host) => {
|
|
51
67
|
const normalized = normalize(host);
|
|
68
|
+
if (normalized.includes("hermes")) {
|
|
69
|
+
return "hermes";
|
|
70
|
+
}
|
|
52
71
|
if (normalized.includes("claude") ||
|
|
53
72
|
normalized.includes("opus") ||
|
|
54
73
|
normalized.includes("sonnet") ||
|
|
@@ -114,6 +133,7 @@ function findHostConfig(host, model, config) {
|
|
|
114
133
|
? [
|
|
115
134
|
["claude", config.hosts.claude],
|
|
116
135
|
["codex", config.hosts.codex],
|
|
136
|
+
["hermes", config.hosts.hermes],
|
|
117
137
|
]
|
|
118
138
|
: [[host, config.hosts[host]]];
|
|
119
139
|
return candidates.find(([, hostConfig]) => modelMeetsMinimum(model, hostConfig, {
|
|
@@ -130,7 +150,7 @@ export function evaluateCampaignModelQuality(input = {}) {
|
|
|
130
150
|
const model = input.model?.trim() || null;
|
|
131
151
|
const reasoningEffort = input.reasoningEffort?.trim() || null;
|
|
132
152
|
const metadataSource = input.metadataSource?.trim() || null;
|
|
133
|
-
const recommendationHost = host === "claude"
|
|
153
|
+
const recommendationHost = host === "claude" || host === "hermes" ? host : "codex";
|
|
134
154
|
const recommendedHostConfig = config.hosts[recommendationHost];
|
|
135
155
|
const minimumSummary = getCampaignModelMinimumSummary(config);
|
|
136
156
|
const currentSettings = [
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
type RefillSendsEvergreenInput = {
|
|
2
|
+
workspaceId?: string;
|
|
3
|
+
};
|
|
4
|
+
export declare const refillSendsEvergreenToolDefinitions: {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: string;
|
|
9
|
+
properties: {
|
|
10
|
+
workspaceId: {
|
|
11
|
+
type: string;
|
|
12
|
+
description: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
required: string[];
|
|
16
|
+
additionalProperties: boolean;
|
|
17
|
+
};
|
|
18
|
+
}[];
|
|
19
|
+
export declare function refillSendsEvergreenCommand(input: RefillSendsEvergreenInput): {
|
|
20
|
+
readOnly: boolean;
|
|
21
|
+
workspaceId: string | null;
|
|
22
|
+
firstOperationalSteps: string[];
|
|
23
|
+
approvalContract: string;
|
|
24
|
+
forbiddenActions: string[];
|
|
25
|
+
fillWindow: string;
|
|
26
|
+
hostExamples: string[];
|
|
27
|
+
};
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const refillSendsEvergreenToolDefinitions = [
|
|
2
|
+
{
|
|
3
|
+
name: "refill_sends_evergreen",
|
|
4
|
+
description: "Read-only Phase 85 evergreen refill command contract. It performs no mutations and only tells the operator to call get_evergreen_refill_plan for a dry-run packet and journal.",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: {
|
|
8
|
+
workspaceId: {
|
|
9
|
+
type: "string",
|
|
10
|
+
description: "Explicit request-scoped workspace id.",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
required: ["workspaceId"],
|
|
14
|
+
additionalProperties: false,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
export function refillSendsEvergreenCommand(input) {
|
|
19
|
+
return {
|
|
20
|
+
readOnly: true,
|
|
21
|
+
workspaceId: input.workspaceId ?? null,
|
|
22
|
+
firstOperationalSteps: [
|
|
23
|
+
"Call get_evergreen_refill_plan with the explicit workspaceId.",
|
|
24
|
+
"Read the returned packet, globalActionQueue, per-sender plans, and itinerary before taking any action.",
|
|
25
|
+
"Review the dry-run journal file path returned by get_evergreen_refill_plan.",
|
|
26
|
+
"Phase 85 is PLAN-ONLY; execution arrives in Phase 86.",
|
|
27
|
+
],
|
|
28
|
+
approvalContract: "Nothing is approved or executable in Phase 85. The evergreen command is read-only; Phase 86 introduces execution approval.",
|
|
29
|
+
forbiddenActions: [
|
|
30
|
+
"Do not schedule sends.",
|
|
31
|
+
"Do not send messages.",
|
|
32
|
+
"Do not approve messages.",
|
|
33
|
+
"Do not prepare messages.",
|
|
34
|
+
"Do not start or launch campaigns.",
|
|
35
|
+
"Do not create campaigns.",
|
|
36
|
+
"Do not switch providers or source families.",
|
|
37
|
+
"Do not lower paid InMail thresholds.",
|
|
38
|
+
"Do not refresh paid InMail credits.",
|
|
39
|
+
"Do not write scheduler fields.",
|
|
40
|
+
],
|
|
41
|
+
fillWindow: "Use only the target window and caps returned by get_evergreen_refill_plan.",
|
|
42
|
+
hostExamples: [
|
|
43
|
+
"refill_sends_evergreen({ workspaceId })",
|
|
44
|
+
"get_evergreen_refill_plan({ workspaceId })",
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sellable/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.532",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Sellable MCP server for Claude Code and
|
|
5
|
+
"description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"mcp": "dist/index.js",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"mcp",
|
|
21
21
|
"claude-code",
|
|
22
22
|
"codex",
|
|
23
|
+
"hermes",
|
|
23
24
|
"sellable",
|
|
24
25
|
"linkedin",
|
|
25
26
|
"outreach"
|