@sellable/mcp 0.1.528 → 0.1.529

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.
@@ -124,7 +124,7 @@ export async function getAuthStatus() {
124
124
  " 3. Click it, come back here, and we'll keep going\\n\\n" +
125
125
  "What email should I use?` " +
126
126
  "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. " +
127
+ "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
128
  "4) On `ok: true`, say verbatim (substituting the email exactly as typed):\\n" +
129
129
  "`Magic link sent to {email}.\\n\\n" +
130
130
  "─────────────────────────────────────────────\\n" +
@@ -135,8 +135,8 @@ export async function getAuthStatus() {
135
135
  " 3. Come back here when you're done\\n\\n" +
136
136
  "I'll be waiting right here.\\n\\n" +
137
137
  " (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. " +
138
+ "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. " +
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. In Hermes, re-call `mcp_sellable_wait_for_cli_login({ sessionId })`. Loop until you get a different result. " +
140
140
  "7) On `ok: true`, the user is signed in and `~/.sellable/config.json` has been written. Branch on `isReturningUser` and use `activeWorkspaceName` when present, otherwise `activeWorkspaceId`, as `{workspaceLabel}`: " +
141
141
  "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
142
  "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?`";
@@ -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" }); 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.`
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;
@@ -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 === "claude" || explicit === "codex")
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 === "claude" || mode === "codex" ? mode : null;
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
- return mode === "codex" ? "Codex" : "Claude Code";
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 === "claude" || mode === "codex") &&
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" ? "claude" : "codex";
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 = [
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.528",
3
+ "version": "0.1.529",
4
4
  "type": "module",
5
- "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
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"