@sellable/install 0.1.300 → 0.1.302
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 +27 -10
- package/bin/sellable-install.mjs +659 -21
- package/package.json +5 -3
- package/skill-templates/refill-sends-evergreen.md +19 -0
- package/skill-templates/refill-sends-v2.md +156 -0
- package/skill-templates/refill-sends.md +20 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sellable Installer
|
|
2
2
|
|
|
3
|
-
Installs Sellable MCP for Claude Code and
|
|
3
|
+
Installs Sellable MCP for Claude Code, Codex, and Hermes.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
curl -fsSL "https://app.sellable.dev/api/v2/cli/install" | sh
|
|
@@ -16,7 +16,9 @@ Windows users can use PowerShell or Windows Terminal:
|
|
|
16
16
|
iwr "https://app.sellable.dev/api/v2/cli/install.ps1" | iex
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
After install, restart Codex Desktop so
|
|
19
|
+
After install, restart Claude Code, Codex Desktop, or Hermes so Sellable MCP
|
|
20
|
+
tools and skills reload. In an active Hermes session, `/reload-mcp` and
|
|
21
|
+
`/reload-skills` can reload the new config.
|
|
20
22
|
|
|
21
23
|
Verify the runtime tools with:
|
|
22
24
|
|
|
@@ -32,11 +34,11 @@ sellable create
|
|
|
32
34
|
```
|
|
33
35
|
|
|
34
36
|
Campaign creation, sender send refills, foundation memory, content
|
|
35
|
-
capture/ideation, and post drafting run inside Claude Code
|
|
36
|
-
Sellable MCP tools and approval flows are available.
|
|
37
|
+
capture/ideation, and post drafting run inside Claude Code, Codex, or Hermes,
|
|
38
|
+
where the Sellable MCP tools and approval flows are available.
|
|
37
39
|
|
|
38
40
|
Install is auth-free by default. The normal path is first-run login: launch a
|
|
39
|
-
Sellable workflow in Claude Code or
|
|
41
|
+
Sellable workflow in Claude Code, Codex, or Hermes and the agent handles Sellable
|
|
40
42
|
sign-in with a browser magic-link handoff.
|
|
41
43
|
|
|
42
44
|
The installer uses package stdio MCP by default:
|
|
@@ -45,7 +47,7 @@ The installer uses package stdio MCP by default:
|
|
|
45
47
|
npm exec --yes --package @sellable/mcp@latest -- sellable-mcp
|
|
46
48
|
```
|
|
47
49
|
|
|
48
|
-
That keeps new Claude Code/Codex MCP starts on the latest stable package. The
|
|
50
|
+
That keeps new Claude Code/Codex/Hermes MCP starts on the latest stable package. The
|
|
49
51
|
MCP server also checks npm at startup and during `get_auth_status`, caching the
|
|
50
52
|
result at `~/.sellable/update-check.json` so users are prompted to rerun the
|
|
51
53
|
latest installer only when an update is actually available.
|
|
@@ -81,7 +83,7 @@ Auth is stored once at:
|
|
|
81
83
|
~/.sellable/config.json
|
|
82
84
|
```
|
|
83
85
|
|
|
84
|
-
Claude Code and
|
|
86
|
+
Claude Code, Codex, and Hermes are configured to launch the same packaged MCP server. The
|
|
85
87
|
installer also writes Sellable agent definitions from the packaged `agents/`
|
|
86
88
|
registry, but normal create-campaign runs use only the Message Drafting
|
|
87
89
|
background agent (`post-find-leads-message-scout`). Source discovery and
|
|
@@ -92,7 +94,7 @@ source-scout agents.
|
|
|
92
94
|
|
|
93
95
|
## Names
|
|
94
96
|
|
|
95
|
-
Use the same public entrypoints
|
|
97
|
+
Use the same public entrypoints across supported hosts:
|
|
96
98
|
|
|
97
99
|
- Claude Code: `/sellable:create-campaign`
|
|
98
100
|
- Claude Code: `/sellable:create-ab-test`
|
|
@@ -110,6 +112,14 @@ Use the same public entrypoints in both hosts:
|
|
|
110
112
|
- Codex: `$sellable:create-post`
|
|
111
113
|
- Codex: `$sellable:refresh-sender-engagement`
|
|
112
114
|
- Codex: `$sellable:refill-sends`
|
|
115
|
+
- Hermes: `/sellable-create-campaign`
|
|
116
|
+
- Hermes: `/sellable-create-ab-test`
|
|
117
|
+
- Hermes: `/sellable-create-evergreen-campaigns`
|
|
118
|
+
- Hermes: `/sellable-foundation`
|
|
119
|
+
- Hermes: `/sellable-content`
|
|
120
|
+
- Hermes: `/sellable-create-post`
|
|
121
|
+
- Hermes: `/sellable-refresh-sender-engagement`
|
|
122
|
+
- Hermes: `/sellable-refill-sends`
|
|
113
123
|
- Codex Desktop plugin: `sellable@sellable`
|
|
114
124
|
- Codex visible skill: `Sellable Create Campaign`
|
|
115
125
|
- Codex visible skill: `Sellable Create A/B Test`
|
|
@@ -125,7 +135,8 @@ Use the same public entrypoints in both hosts:
|
|
|
125
135
|
|
|
126
136
|
Do not ask users to run `/sellable:create-campaign-v2`,
|
|
127
137
|
`$sellable:create-campaign-v2`, `$sellable:load-voice`, or
|
|
128
|
-
`$sellable:sellable:create-campaign`.
|
|
138
|
+
`$sellable:sellable:create-campaign`. Do not ask Hermes users to run
|
|
139
|
+
`/sellable:create-campaign`; use `/sellable-create-campaign`. `create-campaign-v2` is loaded internally
|
|
129
140
|
by the campaign skill. `foundation` is the preferred entrypoint for durable
|
|
130
141
|
founder/company memory. `content` is the preferred entrypoint for adding
|
|
131
142
|
transcripts, recurring ideas, and post seeds. `create-post` remains a supported
|
|
@@ -140,6 +151,12 @@ mode support by writing `default_mode_request_user_input = true` under
|
|
|
140
151
|
`[features]` in `~/.codex/config.toml`. `codex exec` is non-interactive, so it
|
|
141
152
|
cannot show the structured questionnaire UI.
|
|
142
153
|
|
|
154
|
+
Hermes skills use plain chat for setup and approval questions unless a
|
|
155
|
+
Hermes-native approval tool is visible in the current session. Hermes exposes
|
|
156
|
+
Sellable MCP tools as `mcp_sellable_<tool>`, for example
|
|
157
|
+
`mcp_sellable_get_auth_status`, `mcp_sellable_start_cli_login`, and
|
|
158
|
+
`mcp_sellable_wait_for_cli_login`.
|
|
159
|
+
|
|
143
160
|
For Codex Desktop, the installer also writes a local Sellable plugin bundle into
|
|
144
161
|
`~/.sellable/codex-marketplace`, includes the Sellable skill entrypoints, and
|
|
145
162
|
enables it in `~/.codex/config.toml`.
|
|
@@ -157,4 +174,4 @@ The installer writes compatibility cache aliases for recent Codex plugin
|
|
|
157
174
|
versions so stale Desktop skill links resolve to the current wrapper instead of
|
|
158
175
|
showing file/version debugging to the user.
|
|
159
176
|
|
|
160
|
-
If only one host is installed, `--host all` installs the available host and tells you how to add the
|
|
177
|
+
If only one host is installed, `--host all` installs the available host and tells you how to add the others later.
|
package/bin/sellable-install.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import { dirname, join, relative } from "node:path";
|
|
|
15
15
|
import { stdout as output } from "node:process";
|
|
16
16
|
import { createInterface } from "node:readline/promises";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
|
+
import { parseDocument } from "yaml";
|
|
18
19
|
import {
|
|
19
20
|
REQUIRED_SELLABLE_MCP_TOOLS,
|
|
20
21
|
verifySellableMcpRuntime,
|
|
@@ -170,7 +171,7 @@ Commands:
|
|
|
170
171
|
uninstall Remove Sellable host config and installed artifacts.
|
|
171
172
|
|
|
172
173
|
Options:
|
|
173
|
-
--host <host> claude, codex, or all. Default: all
|
|
174
|
+
--host <host> claude, codex, hermes, or all. Default: all
|
|
174
175
|
--server <mode> package, local, or hosted. Default: package
|
|
175
176
|
--token <token> Sellable API token. Also reads SELLABLE_TOKEN.
|
|
176
177
|
--workspace-id <id> Sellable workspace id. Also reads SELLABLE_WORKSPACE_ID.
|
|
@@ -190,8 +191,9 @@ Options:
|
|
|
190
191
|
Auth:
|
|
191
192
|
Install is auth-free by default. Sign in happens on the first run of
|
|
192
193
|
/sellable:create-campaign, /sellable:create-evergreen-campaigns,
|
|
193
|
-
/sellable:foundation, /sellable:content, /sellable:create-post,
|
|
194
|
-
/sellable:refresh-sender-engagement, or /sellable:refill-sends in Claude Code
|
|
194
|
+
/sellable:foundation, /sellable:content, /sellable:create-post,
|
|
195
|
+
/sellable:refresh-sender-engagement, or /sellable:refill-sends in Claude Code,
|
|
196
|
+
Codex, or Hermes,
|
|
195
197
|
where the agent walks you through signup or sign-in and stores credentials
|
|
196
198
|
in ~/.sellable/config.json.
|
|
197
199
|
|
|
@@ -203,7 +205,7 @@ Auth:
|
|
|
203
205
|
|
|
204
206
|
function printCreateCommandHint() {
|
|
205
207
|
printBanner();
|
|
206
|
-
console.log(` ${C.bold}Run Sellable from Claude Code or
|
|
208
|
+
console.log(` ${C.bold}Run Sellable from Claude Code, Codex, or Hermes.${C.reset}`);
|
|
207
209
|
console.log("");
|
|
208
210
|
console.log(
|
|
209
211
|
` ${C.grey}The terminal command installs and verifies Sellable. Campaign creation runs inside your agent session so the workflow can use MCP tools, live state, and approval gates.${C.reset}`
|
|
@@ -237,6 +239,18 @@ function printCreateCommandHint() {
|
|
|
237
239
|
{ label: "Refill", command: "$sellable:refill-sends" },
|
|
238
240
|
]);
|
|
239
241
|
console.log("");
|
|
242
|
+
console.log("");
|
|
243
|
+
printAgentBox("Using Hermes?", "hermes", [
|
|
244
|
+
{ label: "Campaign", command: "/sellable-create-campaign" },
|
|
245
|
+
{ label: "A/B Test", command: "/sellable-create-ab-test" },
|
|
246
|
+
{ label: "Evergreen", command: "/sellable-create-evergreen-campaigns" },
|
|
247
|
+
{ label: "Foundation", command: "/sellable-foundation" },
|
|
248
|
+
{ label: "Content", command: "/sellable-content" },
|
|
249
|
+
{ label: "Post", command: "/sellable-create-post" },
|
|
250
|
+
{ label: "Refresh", command: "/sellable-refresh-sender-engagement" },
|
|
251
|
+
{ label: "Refill", command: "/sellable-refill-sends" },
|
|
252
|
+
]);
|
|
253
|
+
console.log("");
|
|
240
254
|
console.log(` ${"─".repeat(63)}`);
|
|
241
255
|
console.log(` ${C.grey}If those commands are missing, run:${C.reset}`);
|
|
242
256
|
console.log("");
|
|
@@ -249,7 +263,10 @@ function printCreateCommandHint() {
|
|
|
249
263
|
|
|
250
264
|
function parseArgs(argv) {
|
|
251
265
|
const opts = {
|
|
252
|
-
host:
|
|
266
|
+
host:
|
|
267
|
+
process.env.SELLABLE_INSTALLER_HOST ||
|
|
268
|
+
process.env.SELLABLE_INSTALL_HOST ||
|
|
269
|
+
"all",
|
|
253
270
|
server: process.env.SELLABLE_INSTALL_SERVER || "package",
|
|
254
271
|
token: process.env.SELLABLE_TOKEN || "",
|
|
255
272
|
workspaceId: process.env.SELLABLE_WORKSPACE_ID || "",
|
|
@@ -313,8 +330,8 @@ function parseArgs(argv) {
|
|
|
313
330
|
}
|
|
314
331
|
}
|
|
315
332
|
|
|
316
|
-
if (!["claude", "codex", "all"].includes(opts.host)) {
|
|
317
|
-
throw new Error("--host must be claude, codex, or all");
|
|
333
|
+
if (!["claude", "codex", "hermes", "all"].includes(opts.host)) {
|
|
334
|
+
throw new Error("--host must be claude, codex, hermes, or all");
|
|
318
335
|
}
|
|
319
336
|
if (!["package", "local", "hosted"].includes(opts.server)) {
|
|
320
337
|
throw new Error("--server must be package, local, or hosted");
|
|
@@ -1175,6 +1192,29 @@ the Claude Code instruction for customer-facing language and host functions.
|
|
|
1175
1192
|
|
|
1176
1193
|
Do not tell Claude Code users to run \`$sellable:${commandName}\`, use
|
|
1177
1194
|
\`request_user_input\`, or restart Codex Desktop. Do not describe this run as Codex.`;
|
|
1195
|
+
}
|
|
1196
|
+
if (host === "hermes") {
|
|
1197
|
+
return `## Installed Host Contract
|
|
1198
|
+
|
|
1199
|
+
This installed skill is running in Hermes Agent. When the shared workflow body
|
|
1200
|
+
or fallback text mentions Claude Code, Codex, or Hermes for internal parity,
|
|
1201
|
+
choose the Hermes instruction for customer-facing language and host functions.
|
|
1202
|
+
|
|
1203
|
+
- Customer-facing command: \`/sellable-${commandName}\`
|
|
1204
|
+
- MCP tool naming: Hermes exposes Sellable tools as \`mcp_sellable_<tool>\`;
|
|
1205
|
+
when shared instructions show \`mcp__sellable__<tool>\`, call the matching
|
|
1206
|
+
\`mcp_sellable_<tool>\` tool instead.
|
|
1207
|
+
- Structured questions: ask plainly in chat unless a Hermes-native approval or
|
|
1208
|
+
question tool is visible in the current session.
|
|
1209
|
+
- Bootstrap host label: \`host: "Hermes"\`
|
|
1210
|
+
- Install/reload blocker label: Hermes install/reload problem
|
|
1211
|
+
- Reload instruction: restart Hermes, or run \`/reload-mcp\` in the active
|
|
1212
|
+
Hermes session after install
|
|
1213
|
+
|
|
1214
|
+
Do not tell Hermes users to run \`$sellable:${commandName}\` or
|
|
1215
|
+
\`/sellable:${commandName}\`, use \`AskUserQuestion\` or \`request_user_input\`,
|
|
1216
|
+
or restart Claude Code or Codex Desktop. Do not describe this run as Claude Code
|
|
1217
|
+
or Codex.`;
|
|
1178
1218
|
}
|
|
1179
1219
|
return "";
|
|
1180
1220
|
}
|
|
@@ -2584,6 +2624,256 @@ function codexPluginSkills() {
|
|
|
2584
2624
|
];
|
|
2585
2625
|
}
|
|
2586
2626
|
|
|
2627
|
+
function hermesCommandName(commandName) {
|
|
2628
|
+
return `sellable-${commandName}`;
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
function normalizeHermesSkillMarkdown(markdown, commandName) {
|
|
2632
|
+
const hermesName = hermesCommandName(commandName);
|
|
2633
|
+
let content = String(markdown);
|
|
2634
|
+
if (/^name:\s*.+$/m.test(content)) {
|
|
2635
|
+
content = content.replace(/^name:\s*.+$/m, `name: ${hermesName}`);
|
|
2636
|
+
} else {
|
|
2637
|
+
content = content.replace(/^---\n/m, `---\nname: ${hermesName}\n`);
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
let normalized = content
|
|
2641
|
+
.replace(/mcp__sellable__<tool>/g, "mcp_sellable_<tool>")
|
|
2642
|
+
.replace(/mcp__([A-Za-z0-9_-]+)__<tool>/g, (_, server) => {
|
|
2643
|
+
return `mcp_${String(server).replaceAll("-", "_")}_<tool>`;
|
|
2644
|
+
})
|
|
2645
|
+
.replace(/\bmcp__([A-Za-z0-9_-]+)__([A-Za-z0-9_-]+)\b/g, (_, server, tool) => {
|
|
2646
|
+
return `mcp_${String(server).replaceAll("-", "_")}_${String(tool).replaceAll("-", "_")}`;
|
|
2647
|
+
})
|
|
2648
|
+
.replace(/\bmcp__sellable__/g, "mcp_sellable_")
|
|
2649
|
+
.replace(/\$sellable:/g, "/sellable-")
|
|
2650
|
+
.replace(/\/sellable:/g, "/sellable-")
|
|
2651
|
+
.replace(/Codex install\/reload problem/g, "Hermes install/reload problem")
|
|
2652
|
+
.replace(/Claude Code install\/reload problem/g, "Hermes install/reload problem")
|
|
2653
|
+
.replace(/fully quit and reopen Codex Desktop, then start a new thread/g, "restart Hermes, or run `/reload-mcp` in the active Hermes session")
|
|
2654
|
+
.replace(/fully quit and reopen Claude Code, then start a new session/g, "restart Hermes, or run `/reload-mcp` in the active Hermes session")
|
|
2655
|
+
.replace(/Codex Desktop/g, "Hermes")
|
|
2656
|
+
.replace(/Claude Code\/Codex/g, "Hermes")
|
|
2657
|
+
.replace(/Claude Code or Codex/g, "Hermes")
|
|
2658
|
+
.replace(/Codex or Claude Code/g, "Hermes")
|
|
2659
|
+
.replace(/Claude Code and Codex/g, "Hermes")
|
|
2660
|
+
.replace(/Codex\/Claude/g, "Hermes")
|
|
2661
|
+
.replace(/quick question panel/g, "approval prompt")
|
|
2662
|
+
.replace(/request_user_input/g, "plain chat")
|
|
2663
|
+
.replace(/AskUserQuestion/g, "plain chat");
|
|
2664
|
+
|
|
2665
|
+
normalized = normalized.replace(
|
|
2666
|
+
/Do not tell Hermes users to run[\s\S]*?Do not describe this run as Claude Code\nor Codex\./g,
|
|
2667
|
+
"Do not tell Hermes users to run Codex or Claude command forms, use Codex/Claude structured-question APIs, or restart Codex Desktop or Claude Code. Do not describe this run as Claude Code or Codex."
|
|
2668
|
+
);
|
|
2669
|
+
|
|
2670
|
+
normalized = normalized.replace(
|
|
2671
|
+
/## Structured Questions\n\nUse the host-native structured question gate for intake and approval:[\s\S]*?## Host Runtime Functions/g,
|
|
2672
|
+
`## Structured Questions
|
|
2673
|
+
|
|
2674
|
+
Hermes should ask setup questions directly in chat unless the current Hermes
|
|
2675
|
+
session exposes a native approval or question tool. Use a bounded approval gate
|
|
2676
|
+
only for explicit approval decisions. For open text like LinkedIn URLs, company
|
|
2677
|
+
domains, notes, pasted context, campaign ideas, or feedback, ask in normal chat
|
|
2678
|
+
and wait for the user to paste the value.
|
|
2679
|
+
|
|
2680
|
+
Campaign setup questions are single-choice decisions. Use mutually exclusive
|
|
2681
|
+
options and route blended or custom answers through a short free-text follow-up.
|
|
2682
|
+
Customer-facing language should call this "a couple setup choices" during
|
|
2683
|
+
normal campaign progress.
|
|
2684
|
+
|
|
2685
|
+
## Host Runtime Functions`
|
|
2686
|
+
);
|
|
2687
|
+
|
|
2688
|
+
normalized = normalized.replace(
|
|
2689
|
+
/## Names To Use\n\nUse these exact public names[\s\S]*?## Structured Questions/g,
|
|
2690
|
+
`## Names To Use
|
|
2691
|
+
|
|
2692
|
+
Use these exact public names for Hermes:
|
|
2693
|
+
|
|
2694
|
+
- Hermes command: \`/${hermesName}\`
|
|
2695
|
+
- Hermes skill directory: \`skills/sellable/${hermesName}/SKILL.md\`
|
|
2696
|
+
- MCP server name: \`sellable\`
|
|
2697
|
+
- Hermes MCP tool prefix: \`mcp_sellable_\`
|
|
2698
|
+
- Internal workflow prompt: \`create-campaign-v2\`
|
|
2699
|
+
|
|
2700
|
+
Do not tell users to run internal subskill names. \`create-campaign-v2\` is only
|
|
2701
|
+
the internal subskill loaded through
|
|
2702
|
+
\`mcp_sellable_get_subskill_prompt({ subskillName: "create-campaign-v2" })\`.
|
|
2703
|
+
|
|
2704
|
+
## Structured Questions`
|
|
2705
|
+
);
|
|
2706
|
+
|
|
2707
|
+
normalized = normalized.replace(
|
|
2708
|
+
/- `ask_user`:[\s\S]*?(?=\n- `load_subprompt`:)/g,
|
|
2709
|
+
`- \`ask_user\`: ask directly in chat unless a Hermes-native approval or
|
|
2710
|
+
question tool is visible in the current session. Use this for
|
|
2711
|
+
multiple-choice intake, campaign-focus choices, source decisions, and
|
|
2712
|
+
approvals. Campaign setup questions are single-choice only; do not use
|
|
2713
|
+
multi-select or checkbox variants.`
|
|
2714
|
+
);
|
|
2715
|
+
|
|
2716
|
+
normalized = normalized.replace(
|
|
2717
|
+
/- `launch_message_drafting`:[\s\S]*?\n\nIf a required interactive/g,
|
|
2718
|
+
`- \`launch_message_drafting\`: if Hermes exposes a background-agent capability,
|
|
2719
|
+
use the compatible Message Drafting agent. Otherwise run the same message
|
|
2720
|
+
drafting branch inline in the parent session with \`statusSource:
|
|
2721
|
+
"parent-thread-fallback"\`.
|
|
2722
|
+
|
|
2723
|
+
If a required interactive`
|
|
2724
|
+
);
|
|
2725
|
+
|
|
2726
|
+
normalized = normalized.replace(
|
|
2727
|
+
/Do not silently ask (?:Codex|Hermes) intake or approval questions as plain chat[\s\S]*?Plain chat questions are only acceptable in non-interactive `codex exec`\nsmoke\/rehearsal runs because structured user input is unavailable by design\nthere\.\n\n/g,
|
|
2728
|
+
""
|
|
2729
|
+
);
|
|
2730
|
+
|
|
2731
|
+
normalized = normalized.replace(
|
|
2732
|
+
/## Active Model Metadata\n\nBefore calling `bootstrap_create_campaign`, collect[\s\S]*?Never invent the model or reasoning effort\. Never pass config defaults as active\nmetadata\. If bootstrap returns `modelQuality\.status === "unknown"`, continue\nwithout asking the user to switch models\.\n\n/g,
|
|
2733
|
+
`## Active Model Metadata
|
|
2734
|
+
|
|
2735
|
+
Before calling \`bootstrap_create_campaign\`, pass \`host: "Hermes"\`. If the
|
|
2736
|
+
current Hermes session exposes model and effort metadata for this same turn,
|
|
2737
|
+
pass those exact values with \`modelMetadataSource: "hermes_runtime_metadata"\`.
|
|
2738
|
+
If the current Hermes session context explicitly states both the exact model ID
|
|
2739
|
+
and effort/thinking level, pass them with
|
|
2740
|
+
\`modelMetadataSource: "hermes_session_context"\`. Otherwise omit \`model\`,
|
|
2741
|
+
\`reasoningEffort\`, and \`modelMetadataSource\`.
|
|
2742
|
+
|
|
2743
|
+
Never invent the model or reasoning effort. Never inspect Codex or Claude config
|
|
2744
|
+
files as proof of the active Hermes session. If bootstrap returns
|
|
2745
|
+
\`modelQuality.status === "unknown"\`, continue without asking the user to
|
|
2746
|
+
switch models.
|
|
2747
|
+
|
|
2748
|
+
`
|
|
2749
|
+
);
|
|
2750
|
+
|
|
2751
|
+
normalized = normalized.replace(/not `plain chat`/g, "not internal API names");
|
|
2752
|
+
|
|
2753
|
+
normalized = normalized
|
|
2754
|
+
.replace(/this is a Codex\ninstall\/reload problem/g, "this is a Hermes\ninstall/reload problem")
|
|
2755
|
+
.replace(/Hermes plugin/g, "Hermes skills");
|
|
2756
|
+
|
|
2757
|
+
return normalized;
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
function compactHermesEvergreenSkillMd() {
|
|
2761
|
+
return normalizeHermesSkillMarkdown(
|
|
2762
|
+
stampInstalledHost(`---
|
|
2763
|
+
name: create-evergreen-campaigns
|
|
2764
|
+
description: Create or repair evergreen campaign lanes without launching sends.
|
|
2765
|
+
visibility: public
|
|
2766
|
+
allowed-tools:
|
|
2767
|
+
${allowedToolsYaml(CREATE_EVERGREEN_CAMPAIGNS_ALLOWED_TOOLS)}
|
|
2768
|
+
---
|
|
2769
|
+
|
|
2770
|
+
# Sellable Create Evergreen Campaigns
|
|
2771
|
+
|
|
2772
|
+
Use this as the customer-facing entrypoint for the Sellable
|
|
2773
|
+
\`create-evergreen-campaigns\` workflow in Hermes.
|
|
2774
|
+
|
|
2775
|
+
## Bootstrap
|
|
2776
|
+
|
|
2777
|
+
MCP prompt and command access are required. First call
|
|
2778
|
+
\`mcp__sellable__get_auth_status({})\`. Do not inspect repo files, run shell
|
|
2779
|
+
commands, use \`npm\`, \`node\`, local harness scripts, or read local prompt
|
|
2780
|
+
files to emulate this workflow.
|
|
2781
|
+
|
|
2782
|
+
If the Sellable MCP prompt tools are unavailable, stop and say this is a Hermes
|
|
2783
|
+
install/reload problem. Tell the user to run
|
|
2784
|
+
\`curl -fsSL "https://app.sellable.dev/api/v2/cli/install" | sh\`, then restart
|
|
2785
|
+
Hermes or run \`/reload-mcp\`.
|
|
2786
|
+
|
|
2787
|
+
## Execute Workflow
|
|
2788
|
+
|
|
2789
|
+
1. Load the canonical prompt via
|
|
2790
|
+
\`mcp__sellable__get_subskill_prompt({ subskillName: "create-evergreen-campaigns" })\`.
|
|
2791
|
+
If the response has \`hasMore=true\`, continue with \`nextOffset\` until
|
|
2792
|
+
\`hasMore=false\`.
|
|
2793
|
+
2. Ask for bounded approval in normal chat before mutating if the plan requires
|
|
2794
|
+
approval. Do not call Codex app thread tools or Codex CLI workers from
|
|
2795
|
+
Hermes.
|
|
2796
|
+
3. Use \`mcp__sellable__setup_evergreen_campaigns({ mode: "plan" })\` for the
|
|
2797
|
+
plan/readback packet, then use the canonical prompt's approved mutation path
|
|
2798
|
+
and return to \`setup_evergreen_campaigns({ mode: "verify" })\` with
|
|
2799
|
+
customer-visible receipts.
|
|
2800
|
+
4. Do not launch campaigns, schedule sends, archive campaigns, delete tables,
|
|
2801
|
+
or spend paid credits.
|
|
2802
|
+
|
|
2803
|
+
## MCP Prompt Fallback
|
|
2804
|
+
|
|
2805
|
+
If exact subskill lookup fails, use
|
|
2806
|
+
\`mcp__sellable__search_subskill_prompts({ query: "create-evergreen-campaigns", includePublic: true, includeInternal: true })\`,
|
|
2807
|
+
then retry \`get_subskill_prompt\`.
|
|
2808
|
+
`, "hermes", "create-evergreen-campaigns"),
|
|
2809
|
+
"create-evergreen-campaigns"
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
function hermesSkillDefinitions() {
|
|
2814
|
+
return [
|
|
2815
|
+
{
|
|
2816
|
+
commandName: "create-campaign",
|
|
2817
|
+
dir: hermesCommandName("create-campaign"),
|
|
2818
|
+
skillMd: normalizeHermesSkillMarkdown(
|
|
2819
|
+
createCampaignSkillMd("hermes"),
|
|
2820
|
+
"create-campaign"
|
|
2821
|
+
),
|
|
2822
|
+
soulMd: createCampaignSoulMd(),
|
|
2823
|
+
},
|
|
2824
|
+
{
|
|
2825
|
+
commandName: "create-ab-test",
|
|
2826
|
+
dir: hermesCommandName("create-ab-test"),
|
|
2827
|
+
skillMd: normalizeHermesSkillMarkdown(
|
|
2828
|
+
createAbTestSkillMd("hermes"),
|
|
2829
|
+
"create-ab-test"
|
|
2830
|
+
),
|
|
2831
|
+
},
|
|
2832
|
+
{
|
|
2833
|
+
commandName: "create-evergreen-campaigns",
|
|
2834
|
+
dir: hermesCommandName("create-evergreen-campaigns"),
|
|
2835
|
+
skillMd: compactHermesEvergreenSkillMd(),
|
|
2836
|
+
},
|
|
2837
|
+
{
|
|
2838
|
+
commandName: "foundation",
|
|
2839
|
+
dir: hermesCommandName("foundation"),
|
|
2840
|
+
skillMd: normalizeHermesSkillMarkdown(
|
|
2841
|
+
foundationSkillMd("hermes"),
|
|
2842
|
+
"foundation"
|
|
2843
|
+
),
|
|
2844
|
+
},
|
|
2845
|
+
{
|
|
2846
|
+
commandName: "content",
|
|
2847
|
+
dir: hermesCommandName("content"),
|
|
2848
|
+
skillMd: normalizeHermesSkillMarkdown(contentSkillMd("hermes"), "content"),
|
|
2849
|
+
},
|
|
2850
|
+
{
|
|
2851
|
+
commandName: "create-post",
|
|
2852
|
+
dir: hermesCommandName("create-post"),
|
|
2853
|
+
skillMd: normalizeHermesSkillMarkdown(
|
|
2854
|
+
createPostSkillMd("hermes"),
|
|
2855
|
+
"create-post"
|
|
2856
|
+
),
|
|
2857
|
+
},
|
|
2858
|
+
{
|
|
2859
|
+
commandName: "refresh-sender-engagement",
|
|
2860
|
+
dir: hermesCommandName("refresh-sender-engagement"),
|
|
2861
|
+
skillMd: normalizeHermesSkillMarkdown(
|
|
2862
|
+
refreshSenderEngagementSkillMd("hermes"),
|
|
2863
|
+
"refresh-sender-engagement"
|
|
2864
|
+
),
|
|
2865
|
+
},
|
|
2866
|
+
{
|
|
2867
|
+
commandName: "refill-sends",
|
|
2868
|
+
dir: hermesCommandName("refill-sends"),
|
|
2869
|
+
skillMd: normalizeHermesSkillMarkdown(
|
|
2870
|
+
refillSendsSkillMd("hermes"),
|
|
2871
|
+
"refill-sends"
|
|
2872
|
+
),
|
|
2873
|
+
},
|
|
2874
|
+
];
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2587
2877
|
function installerPackageRoot() {
|
|
2588
2878
|
return join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
2589
2879
|
}
|
|
@@ -3152,6 +3442,7 @@ exec ${shellQuote(npmCommand)} exec --yes --package ${shellQuote(INSTALL_PACKAGE
|
|
|
3152
3442
|
const WATCH_MODE_DRIVER_ENV = {
|
|
3153
3443
|
claude: "SELLABLE_WATCH_MODE_DRIVER=claude",
|
|
3154
3444
|
codex: "SELLABLE_WATCH_MODE_DRIVER=codex",
|
|
3445
|
+
hermes: "SELLABLE_WATCH_MODE_DRIVER=hermes",
|
|
3155
3446
|
};
|
|
3156
3447
|
|
|
3157
3448
|
function withHostedWatchModeDriver(rawUrl, driver) {
|
|
@@ -3277,6 +3568,169 @@ function codexPluginMcpServerMatches(content, opts) {
|
|
|
3277
3568
|
}
|
|
3278
3569
|
}
|
|
3279
3570
|
|
|
3571
|
+
function hermesHome() {
|
|
3572
|
+
return process.env.HERMES_HOME?.trim() || join(homedir(), ".hermes");
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
function hermesConfigPath() {
|
|
3576
|
+
return join(hermesHome(), "config.yaml");
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
function hermesSkillsRoot() {
|
|
3580
|
+
return join(hermesHome(), "skills", "sellable");
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3583
|
+
function hermesLikelyInstalled() {
|
|
3584
|
+
return (
|
|
3585
|
+
Boolean(process.env.HERMES_HOME?.trim()) ||
|
|
3586
|
+
commandExists("hermes") ||
|
|
3587
|
+
existsSync(hermesConfigPath()) ||
|
|
3588
|
+
existsSync(join(hermesHome(), "skills"))
|
|
3589
|
+
);
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
function parseHermesConfig(raw, configPath = hermesConfigPath()) {
|
|
3593
|
+
const source = raw.trim() ? raw : "{}\n";
|
|
3594
|
+
const doc = parseDocument(source, { prettyErrors: false });
|
|
3595
|
+
if (doc.errors.length > 0) {
|
|
3596
|
+
throw new Error(
|
|
3597
|
+
`Invalid Hermes config YAML at ${configPath}: ${doc.errors[0].message}`
|
|
3598
|
+
);
|
|
3599
|
+
}
|
|
3600
|
+
const value = doc.toJSON() ?? {};
|
|
3601
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3602
|
+
throw new Error(
|
|
3603
|
+
`Invalid Hermes config YAML at ${configPath}: expected a mapping at the document root`
|
|
3604
|
+
);
|
|
3605
|
+
}
|
|
3606
|
+
return value;
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
function readHermesConfig() {
|
|
3610
|
+
const configPath = hermesConfigPath();
|
|
3611
|
+
const raw = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
3612
|
+
return { raw, config: parseHermesConfig(raw, configPath), configPath };
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
function hermesMcpServer(opts) {
|
|
3616
|
+
if (opts.server === "hosted") {
|
|
3617
|
+
return {
|
|
3618
|
+
url: withHostedWatchModeDriver(opts.hostedUrl, "hermes"),
|
|
3619
|
+
enabled: true,
|
|
3620
|
+
env: {
|
|
3621
|
+
SELLABLE_WATCH_MODE_DRIVER: "hermes",
|
|
3622
|
+
},
|
|
3623
|
+
};
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
const [command, args] = mcpCommand(opts);
|
|
3627
|
+
return {
|
|
3628
|
+
command,
|
|
3629
|
+
args,
|
|
3630
|
+
enabled: true,
|
|
3631
|
+
env: {
|
|
3632
|
+
SELLABLE_WATCH_MODE_DRIVER: "hermes",
|
|
3633
|
+
},
|
|
3634
|
+
};
|
|
3635
|
+
}
|
|
3636
|
+
|
|
3637
|
+
function writeHermesConfig(configPath, config, opts) {
|
|
3638
|
+
if (opts.dryRun) return;
|
|
3639
|
+
mkdirSync(dirname(configPath), { recursive: true, mode: 0o700 });
|
|
3640
|
+
const doc = parseDocument("{}\n");
|
|
3641
|
+
doc.contents = config;
|
|
3642
|
+
writeFileSync(configPath, String(doc), { mode: 0o600 });
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
function writeHermesMcpServer(opts) {
|
|
3646
|
+
const { config, configPath } = readHermesConfig();
|
|
3647
|
+
const existingMcpServers =
|
|
3648
|
+
config.mcp_servers &&
|
|
3649
|
+
typeof config.mcp_servers === "object" &&
|
|
3650
|
+
!Array.isArray(config.mcp_servers)
|
|
3651
|
+
? config.mcp_servers
|
|
3652
|
+
: {};
|
|
3653
|
+
const nextConfig = {
|
|
3654
|
+
...config,
|
|
3655
|
+
mcp_servers: {
|
|
3656
|
+
...existingMcpServers,
|
|
3657
|
+
sellable: hermesMcpServer(opts),
|
|
3658
|
+
},
|
|
3659
|
+
};
|
|
3660
|
+
writeHermesConfig(configPath, nextConfig, opts);
|
|
3661
|
+
return configPath;
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
function installHermesSkills(opts) {
|
|
3665
|
+
const root = hermesSkillsRoot();
|
|
3666
|
+
for (const skill of hermesSkillDefinitions()) {
|
|
3667
|
+
const skillRoot = join(root, skill.dir);
|
|
3668
|
+
writeFile(join(skillRoot, "SKILL.md"), skill.skillMd, opts);
|
|
3669
|
+
if (skill.soulMd) {
|
|
3670
|
+
writeFile(join(skillRoot, "SOUL.md"), skill.soulMd, opts);
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
return root;
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3676
|
+
function installHermes(opts) {
|
|
3677
|
+
// Parse config before writing skills so invalid user YAML cannot leave a
|
|
3678
|
+
// half-installed Hermes skill tree behind.
|
|
3679
|
+
readHermesConfig();
|
|
3680
|
+
let skillsRoot = null;
|
|
3681
|
+
try {
|
|
3682
|
+
skillsRoot = installHermesSkills(opts);
|
|
3683
|
+
const configPath = writeHermesMcpServer(opts);
|
|
3684
|
+
logVerbose(`${C.grey}+ Hermes config updated: ${configPath}${C.reset}`);
|
|
3685
|
+
return { installed: true, configPath, skillsRoot };
|
|
3686
|
+
} catch (err) {
|
|
3687
|
+
if (skillsRoot && !opts.dryRun) {
|
|
3688
|
+
rmSync(skillsRoot, { recursive: true, force: true });
|
|
3689
|
+
}
|
|
3690
|
+
throw err;
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
|
|
3694
|
+
function hermesMcpServerMatches(config, opts) {
|
|
3695
|
+
const server = config?.mcp_servers?.sellable;
|
|
3696
|
+
if (!server || typeof server !== "object") return false;
|
|
3697
|
+
const expected = hermesMcpServer(opts);
|
|
3698
|
+
return JSON.stringify(server) === JSON.stringify(expected);
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3701
|
+
function hermesSkillChecks() {
|
|
3702
|
+
const root = hermesSkillsRoot();
|
|
3703
|
+
const skills = hermesSkillDefinitions();
|
|
3704
|
+
const paths = skills.map((skill) => join(root, skill.dir, "SKILL.md"));
|
|
3705
|
+
const allPresent = paths.every((skillPath) => existsSync(skillPath));
|
|
3706
|
+
const contents = paths
|
|
3707
|
+
.filter((skillPath) => existsSync(skillPath))
|
|
3708
|
+
.map((skillPath) => readFileSync(skillPath, "utf8"));
|
|
3709
|
+
const commandNamesCurrent =
|
|
3710
|
+
contents.length === skills.length &&
|
|
3711
|
+
skills.every((skill, index) =>
|
|
3712
|
+
contents[index]?.includes(`name: ${hermesCommandName(skill.commandName)}`)
|
|
3713
|
+
);
|
|
3714
|
+
const slashCommandsCurrent =
|
|
3715
|
+
contents.length === skills.length &&
|
|
3716
|
+
skills.every((skill, index) =>
|
|
3717
|
+
contents[index]?.includes(`/${hermesCommandName(skill.commandName)}`)
|
|
3718
|
+
);
|
|
3719
|
+
const forbiddenSyntax = contents.some((content) =>
|
|
3720
|
+
/\$sellable:|\/sellable:|mcp__sellable__|request_user_input|AskUserQuestion/.test(
|
|
3721
|
+
content
|
|
3722
|
+
)
|
|
3723
|
+
);
|
|
3724
|
+
return {
|
|
3725
|
+
root,
|
|
3726
|
+
paths,
|
|
3727
|
+
allPresent,
|
|
3728
|
+
commandNamesCurrent,
|
|
3729
|
+
slashCommandsCurrent,
|
|
3730
|
+
forbiddenSyntax,
|
|
3731
|
+
};
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3280
3734
|
function installClaude(opts) {
|
|
3281
3735
|
if (!commandExists("claude")) {
|
|
3282
3736
|
const message =
|
|
@@ -3558,6 +4012,13 @@ function warningCheck(ok, label) {
|
|
|
3558
4012
|
return { ok, label, required: false };
|
|
3559
4013
|
}
|
|
3560
4014
|
|
|
4015
|
+
function firstRunCommandForHost(host) {
|
|
4016
|
+
if (host === "hermes") return "/sellable-create-campaign";
|
|
4017
|
+
if (host === "codex") return "$sellable:create-campaign";
|
|
4018
|
+
if (host === "claude") return "/sellable:create-campaign";
|
|
4019
|
+
return "/sellable:create-campaign, $sellable:create-campaign, or /sellable-create-campaign";
|
|
4020
|
+
}
|
|
4021
|
+
|
|
3561
4022
|
async function verify(opts) {
|
|
3562
4023
|
const startedAt = new Date().toISOString();
|
|
3563
4024
|
const stored = readStoredAuth();
|
|
@@ -3573,7 +4034,7 @@ async function verify(opts) {
|
|
|
3573
4034
|
checks.push(
|
|
3574
4035
|
warningCheck(
|
|
3575
4036
|
true,
|
|
3576
|
-
`Auth: not yet signed in — sign in on first run of
|
|
4037
|
+
`Auth: not yet signed in — sign in on first run of ${firstRunCommandForHost(opts.host)}`
|
|
3577
4038
|
)
|
|
3578
4039
|
);
|
|
3579
4040
|
}
|
|
@@ -3860,6 +4321,88 @@ async function verify(opts) {
|
|
|
3860
4321
|
)
|
|
3861
4322
|
);
|
|
3862
4323
|
}
|
|
4324
|
+
if (opts.host === "hermes" || opts.host === "all") {
|
|
4325
|
+
let hermesConfig = null;
|
|
4326
|
+
let hermesConfigError = "";
|
|
4327
|
+
try {
|
|
4328
|
+
hermesConfig = readHermesConfig().config;
|
|
4329
|
+
} catch (err) {
|
|
4330
|
+
hermesConfigError = err instanceof Error ? err.message : String(err);
|
|
4331
|
+
}
|
|
4332
|
+
const npmPresent = opts.server !== "package" || commandExists(packageManagerCommand());
|
|
4333
|
+
const skillChecks = hermesSkillChecks();
|
|
4334
|
+
const configMatches = hermesConfig
|
|
4335
|
+
? hermesMcpServerMatches(hermesConfig, opts)
|
|
4336
|
+
: false;
|
|
4337
|
+
const hasHermesDriver =
|
|
4338
|
+
hermesConfig?.mcp_servers?.sellable?.env?.SELLABLE_WATCH_MODE_DRIVER ===
|
|
4339
|
+
"hermes";
|
|
4340
|
+
|
|
4341
|
+
const check =
|
|
4342
|
+
opts.host === "hermes" ? requiredCheck : warningCheck;
|
|
4343
|
+
checks.push(
|
|
4344
|
+
check(
|
|
4345
|
+
!hermesConfigError,
|
|
4346
|
+
hermesConfigError || `Hermes config readable: ${hermesConfigPath()}`
|
|
4347
|
+
)
|
|
4348
|
+
);
|
|
4349
|
+
checks.push(
|
|
4350
|
+
check(
|
|
4351
|
+
configMatches,
|
|
4352
|
+
configMatches
|
|
4353
|
+
? "Hermes Sellable MCP entry canonical"
|
|
4354
|
+
: "Hermes Sellable MCP entry missing or stale"
|
|
4355
|
+
)
|
|
4356
|
+
);
|
|
4357
|
+
checks.push(
|
|
4358
|
+
check(
|
|
4359
|
+
hasHermesDriver,
|
|
4360
|
+
hasHermesDriver
|
|
4361
|
+
? "Hermes watch mode driver pinned to hermes"
|
|
4362
|
+
: "Hermes watch mode driver missing"
|
|
4363
|
+
)
|
|
4364
|
+
);
|
|
4365
|
+
checks.push(
|
|
4366
|
+
check(
|
|
4367
|
+
skillChecks.allPresent,
|
|
4368
|
+
skillChecks.allPresent
|
|
4369
|
+
? "Hermes Sellable skills present"
|
|
4370
|
+
: "Hermes Sellable skills missing"
|
|
4371
|
+
)
|
|
4372
|
+
);
|
|
4373
|
+
checks.push(
|
|
4374
|
+
check(
|
|
4375
|
+
skillChecks.commandNamesCurrent,
|
|
4376
|
+
skillChecks.commandNamesCurrent
|
|
4377
|
+
? "Hermes skill names current"
|
|
4378
|
+
: "Hermes skill names stale"
|
|
4379
|
+
)
|
|
4380
|
+
);
|
|
4381
|
+
checks.push(
|
|
4382
|
+
check(
|
|
4383
|
+
skillChecks.slashCommandsCurrent,
|
|
4384
|
+
skillChecks.slashCommandsCurrent
|
|
4385
|
+
? "Hermes slash command hints current"
|
|
4386
|
+
: "Hermes slash command hints stale"
|
|
4387
|
+
)
|
|
4388
|
+
);
|
|
4389
|
+
checks.push(
|
|
4390
|
+
check(
|
|
4391
|
+
!skillChecks.forbiddenSyntax,
|
|
4392
|
+
!skillChecks.forbiddenSyntax
|
|
4393
|
+
? "Hermes skills use Hermes MCP/tool syntax"
|
|
4394
|
+
: "Hermes skills contain Claude/Codex-only syntax"
|
|
4395
|
+
)
|
|
4396
|
+
);
|
|
4397
|
+
checks.push(
|
|
4398
|
+
check(
|
|
4399
|
+
npmPresent,
|
|
4400
|
+
npmPresent
|
|
4401
|
+
? "Hermes package runtime command present"
|
|
4402
|
+
: `Hermes package runtime command missing: ${packageManagerCommand()}`
|
|
4403
|
+
)
|
|
4404
|
+
);
|
|
4405
|
+
}
|
|
3863
4406
|
|
|
3864
4407
|
let runtimeMcp;
|
|
3865
4408
|
try {
|
|
@@ -4042,7 +4585,7 @@ function printNextSteps(installedHosts, authReused) {
|
|
|
4042
4585
|
` ${C.bold}Almost there — install an agent first.${C.reset}`
|
|
4043
4586
|
);
|
|
4044
4587
|
console.log(
|
|
4045
|
-
` ${C.yellow}!${C.reset} No agent CLI found (Claude Code or
|
|
4588
|
+
` ${C.yellow}!${C.reset} No agent CLI/profile found (Claude Code, Codex, or Hermes)`
|
|
4046
4589
|
);
|
|
4047
4590
|
console.log("");
|
|
4048
4591
|
console.log("");
|
|
@@ -4086,6 +4629,7 @@ function printNextSteps(installedHosts, authReused) {
|
|
|
4086
4629
|
|
|
4087
4630
|
const hasClaude = installedHosts.includes("Claude Code");
|
|
4088
4631
|
const hasCodex = installedHosts.includes("Codex");
|
|
4632
|
+
const hasHermes = installedHosts.includes("Hermes");
|
|
4089
4633
|
|
|
4090
4634
|
// Header
|
|
4091
4635
|
if (authReused) {
|
|
@@ -4101,6 +4645,11 @@ function printNextSteps(installedHosts, authReused) {
|
|
|
4101
4645
|
console.log(` ${C.green}✓${C.reset} Skills installed`);
|
|
4102
4646
|
console.log(` ${C.green}✓${C.reset} Codex custom agents installed`);
|
|
4103
4647
|
}
|
|
4648
|
+
if (hasHermes) {
|
|
4649
|
+
console.log(
|
|
4650
|
+
` ${C.green}✓${C.reset} Hermes skills and MCP config installed`
|
|
4651
|
+
);
|
|
4652
|
+
}
|
|
4104
4653
|
if (hasClaude) {
|
|
4105
4654
|
console.log(
|
|
4106
4655
|
` ${C.green}✓${C.reset} Claude slash commands and custom agents installed`
|
|
@@ -4144,6 +4693,19 @@ function printNextSteps(installedHosts, authReused) {
|
|
|
4144
4693
|
]);
|
|
4145
4694
|
console.log("");
|
|
4146
4695
|
}
|
|
4696
|
+
if (hasHermes) {
|
|
4697
|
+
printAgentBox("Using Hermes?", "hermes", [
|
|
4698
|
+
{ label: "Campaign", command: "/sellable-create-campaign" },
|
|
4699
|
+
{ label: "A/B Test", command: "/sellable-create-ab-test" },
|
|
4700
|
+
{ label: "Evergreen", command: "/sellable-create-evergreen-campaigns" },
|
|
4701
|
+
{ label: "Foundation", command: "/sellable-foundation" },
|
|
4702
|
+
{ label: "Content", command: "/sellable-content" },
|
|
4703
|
+
{ label: "Post", command: "/sellable-create-post" },
|
|
4704
|
+
{ label: "Refresh", command: "/sellable-refresh-sender-engagement" },
|
|
4705
|
+
{ label: "Refill", command: "/sellable-refill-sends" },
|
|
4706
|
+
]);
|
|
4707
|
+
console.log("");
|
|
4708
|
+
}
|
|
4147
4709
|
|
|
4148
4710
|
console.log("");
|
|
4149
4711
|
console.log(` ${"─".repeat(63)}`);
|
|
@@ -4173,7 +4735,7 @@ function removeTomlSection(content, header) {
|
|
|
4173
4735
|
return content.replace(pattern, "");
|
|
4174
4736
|
}
|
|
4175
4737
|
|
|
4176
|
-
function runUninstall() {
|
|
4738
|
+
function runUninstall(opts = { host: "all" }) {
|
|
4177
4739
|
console.log("");
|
|
4178
4740
|
console.log(` ${C.bold}Uninstalling Sellable…${C.reset}`);
|
|
4179
4741
|
console.log("");
|
|
@@ -4184,7 +4746,7 @@ function runUninstall() {
|
|
|
4184
4746
|
|
|
4185
4747
|
// 1) Surgical removal from Codex config.toml
|
|
4186
4748
|
const codexConfigPath = join(codexHome(), "config.toml");
|
|
4187
|
-
if (existsSync(codexConfigPath)) {
|
|
4749
|
+
if ((opts.host === "codex" || opts.host === "all") && existsSync(codexConfigPath)) {
|
|
4188
4750
|
try {
|
|
4189
4751
|
const before = readFileSync(codexConfigPath, "utf8");
|
|
4190
4752
|
const ts = new Date()
|
|
@@ -4221,10 +4783,11 @@ function runUninstall() {
|
|
|
4221
4783
|
` ${C.yellow}!${C.reset} Could not edit Codex config: ${err instanceof Error ? err.message : String(err)}`
|
|
4222
4784
|
);
|
|
4223
4785
|
}
|
|
4224
|
-
} else {
|
|
4786
|
+
} else if (opts.host === "codex" || opts.host === "all") {
|
|
4225
4787
|
skipped.push(`Codex config not found at ${codexConfigPath}`);
|
|
4226
4788
|
}
|
|
4227
4789
|
|
|
4790
|
+
if (opts.host === "codex" || opts.host === "all") {
|
|
4228
4791
|
for (const agent of [...codexCustomAgents(), ...legacyCodexCustomAgents()]) {
|
|
4229
4792
|
const agentPath = join(codexHome(), "agents", agent.filename);
|
|
4230
4793
|
if (!existsSync(agentPath)) continue;
|
|
@@ -4237,9 +4800,10 @@ function runUninstall() {
|
|
|
4237
4800
|
);
|
|
4238
4801
|
}
|
|
4239
4802
|
}
|
|
4803
|
+
}
|
|
4240
4804
|
|
|
4241
4805
|
// 2) Claude Code MCP removal
|
|
4242
|
-
if (commandExists("claude")) {
|
|
4806
|
+
if ((opts.host === "claude" || opts.host === "all") && commandExists("claude")) {
|
|
4243
4807
|
const r = spawnSync("claude", ["mcp", "remove", "sellable"], {
|
|
4244
4808
|
encoding: "utf8",
|
|
4245
4809
|
});
|
|
@@ -4248,10 +4812,11 @@ function runUninstall() {
|
|
|
4248
4812
|
} else {
|
|
4249
4813
|
skipped.push(`Claude Code MCP (none registered or already removed)`);
|
|
4250
4814
|
}
|
|
4251
|
-
} else {
|
|
4815
|
+
} else if (opts.host === "claude" || opts.host === "all") {
|
|
4252
4816
|
skipped.push(`Claude Code CLI not found`);
|
|
4253
4817
|
}
|
|
4254
4818
|
|
|
4819
|
+
if (opts.host === "claude" || opts.host === "all") {
|
|
4255
4820
|
for (const agent of [
|
|
4256
4821
|
...claudeCustomAgents(),
|
|
4257
4822
|
...legacyClaudeCustomAgents(),
|
|
@@ -4279,10 +4844,66 @@ function runUninstall() {
|
|
|
4279
4844
|
);
|
|
4280
4845
|
}
|
|
4281
4846
|
}
|
|
4847
|
+
}
|
|
4282
4848
|
|
|
4283
|
-
// 3)
|
|
4849
|
+
// 3) Hermes config and skill removal
|
|
4850
|
+
if (opts.host === "hermes" || opts.host === "all") {
|
|
4851
|
+
const configPath = hermesConfigPath();
|
|
4852
|
+
if (existsSync(configPath)) {
|
|
4853
|
+
try {
|
|
4854
|
+
const before = readFileSync(configPath, "utf8");
|
|
4855
|
+
const ts = new Date()
|
|
4856
|
+
.toISOString()
|
|
4857
|
+
.replace(/[-:]/g, "")
|
|
4858
|
+
.replace(/\..+/, "");
|
|
4859
|
+
const backupPath = `${configPath}.bak-sellable-uninstall-${ts}`;
|
|
4860
|
+
writeFileSync(backupPath, before, { mode: 0o600 });
|
|
4861
|
+
const config = parseHermesConfig(before, configPath);
|
|
4862
|
+
if (
|
|
4863
|
+
config.mcp_servers &&
|
|
4864
|
+
typeof config.mcp_servers === "object" &&
|
|
4865
|
+
!Array.isArray(config.mcp_servers) &&
|
|
4866
|
+
"sellable" in config.mcp_servers
|
|
4867
|
+
) {
|
|
4868
|
+
const nextMcpServers = { ...config.mcp_servers };
|
|
4869
|
+
delete nextMcpServers.sellable;
|
|
4870
|
+
writeHermesConfig(
|
|
4871
|
+
configPath,
|
|
4872
|
+
{ ...config, mcp_servers: nextMcpServers },
|
|
4873
|
+
{ dryRun: false }
|
|
4874
|
+
);
|
|
4875
|
+
removed.push(`Hermes config (sellable removed in ${configPath})`);
|
|
4876
|
+
removed.push(` backup: ${backupPath}`);
|
|
4877
|
+
} else {
|
|
4878
|
+
skipped.push(`Hermes config had no sellable MCP server`);
|
|
4879
|
+
}
|
|
4880
|
+
} catch (err) {
|
|
4881
|
+
console.log(
|
|
4882
|
+
` ${C.yellow}!${C.reset} Could not edit Hermes config: ${err instanceof Error ? err.message : String(err)}`
|
|
4883
|
+
);
|
|
4884
|
+
}
|
|
4885
|
+
} else {
|
|
4886
|
+
skipped.push(`Hermes config not found at ${configPath}`);
|
|
4887
|
+
}
|
|
4888
|
+
|
|
4889
|
+
const skillsRoot = hermesSkillsRoot();
|
|
4890
|
+
if (existsSync(skillsRoot)) {
|
|
4891
|
+
try {
|
|
4892
|
+
rmSync(skillsRoot, { recursive: true, force: true });
|
|
4893
|
+
removed.push(`${skillsRoot}`);
|
|
4894
|
+
} catch (err) {
|
|
4895
|
+
console.log(
|
|
4896
|
+
` ${C.yellow}!${C.reset} Could not remove ${skillsRoot}: ${err instanceof Error ? err.message : String(err)}`
|
|
4897
|
+
);
|
|
4898
|
+
}
|
|
4899
|
+
} else {
|
|
4900
|
+
skipped.push(`Hermes Sellable skills not found at ${skillsRoot}`);
|
|
4901
|
+
}
|
|
4902
|
+
}
|
|
4903
|
+
|
|
4904
|
+
// 4) Surgical removal of Sellable-installed artifacts inside ~/.sellable/
|
|
4284
4905
|
const sellableDir = join(homedir(), ".sellable");
|
|
4285
|
-
if (existsSync(sellableDir)) {
|
|
4906
|
+
if (opts.host === "all" && existsSync(sellableDir)) {
|
|
4286
4907
|
const tracked = [
|
|
4287
4908
|
"config.json",
|
|
4288
4909
|
"update-check.json",
|
|
@@ -4321,13 +4942,13 @@ function runUninstall() {
|
|
|
4321
4942
|
// ignore
|
|
4322
4943
|
}
|
|
4323
4944
|
}
|
|
4324
|
-
} else {
|
|
4945
|
+
} else if (opts.host === "all") {
|
|
4325
4946
|
skipped.push(`~/.sellable not present`);
|
|
4326
4947
|
}
|
|
4327
4948
|
|
|
4328
|
-
//
|
|
4949
|
+
// 5) Self-shim
|
|
4329
4950
|
const shimPath = join(homedir(), ".local", "bin", "sellable");
|
|
4330
|
-
if (existsSync(shimPath)) {
|
|
4951
|
+
if (opts.host === "all" && existsSync(shimPath)) {
|
|
4331
4952
|
try {
|
|
4332
4953
|
rmSync(shimPath, { force: true });
|
|
4333
4954
|
removed.push(`${shimPath}`);
|
|
@@ -4440,6 +5061,13 @@ async function main() {
|
|
|
4440
5061
|
console.log(` Codex: $sellable:create-post`);
|
|
4441
5062
|
console.log(` Codex: $sellable:refresh-sender-engagement`);
|
|
4442
5063
|
console.log(` Codex: $sellable:refill-sends`);
|
|
5064
|
+
console.log(` Hermes: /sellable-create-campaign`);
|
|
5065
|
+
console.log(` Hermes: /sellable-create-evergreen-campaigns`);
|
|
5066
|
+
console.log(` Hermes: /sellable-foundation`);
|
|
5067
|
+
console.log(` Hermes: /sellable-content`);
|
|
5068
|
+
console.log(` Hermes: /sellable-create-post`);
|
|
5069
|
+
console.log(` Hermes: /sellable-refresh-sender-engagement`);
|
|
5070
|
+
console.log(` Hermes: /sellable-refill-sends`);
|
|
4443
5071
|
process.exit(0);
|
|
4444
5072
|
}
|
|
4445
5073
|
if (rawArgs[0] === "prefs") {
|
|
@@ -4482,7 +5110,8 @@ async function main() {
|
|
|
4482
5110
|
process.exit(0);
|
|
4483
5111
|
}
|
|
4484
5112
|
if (rawArgs[0] === "uninstall") {
|
|
4485
|
-
|
|
5113
|
+
const opts = parseArgs(rawArgs.slice(1));
|
|
5114
|
+
runUninstall(opts);
|
|
4486
5115
|
process.exit(0);
|
|
4487
5116
|
}
|
|
4488
5117
|
if (rawArgs[0] === "create") {
|
|
@@ -4500,7 +5129,7 @@ async function main() {
|
|
|
4500
5129
|
if (!opts.verifyOnly) {
|
|
4501
5130
|
printBanner();
|
|
4502
5131
|
console.log(
|
|
4503
|
-
` ${C.bold}Connecting Sellable to Claude Code and
|
|
5132
|
+
` ${C.bold}Connecting Sellable to Claude Code, Codex, and Hermes…${C.reset}`
|
|
4504
5133
|
);
|
|
4505
5134
|
console.log("");
|
|
4506
5135
|
} else if (!opts.json) {
|
|
@@ -4536,6 +5165,15 @@ async function main() {
|
|
|
4536
5165
|
installedHosts.push("Codex");
|
|
4537
5166
|
}
|
|
4538
5167
|
}
|
|
5168
|
+
if (
|
|
5169
|
+
opts.host === "hermes" ||
|
|
5170
|
+
(opts.host === "all" && hermesLikelyInstalled())
|
|
5171
|
+
) {
|
|
5172
|
+
const result = installHermes(opts);
|
|
5173
|
+
if (result.installed) {
|
|
5174
|
+
installedHosts.push("Hermes");
|
|
5175
|
+
}
|
|
5176
|
+
}
|
|
4539
5177
|
}
|
|
4540
5178
|
|
|
4541
5179
|
if (opts.dryRun) {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sellable/install",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.302",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "One-command installer for Sellable MCP in Claude Code and
|
|
5
|
+
"description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
|
|
6
6
|
"bin": {
|
|
7
7
|
"sellable": "bin/sellable-install.mjs"
|
|
8
8
|
},
|
|
@@ -18,13 +18,15 @@
|
|
|
18
18
|
"README.md"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@modelcontextprotocol/sdk": "^1.25.2"
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
22
|
+
"yaml": "^2.8.1"
|
|
22
23
|
},
|
|
23
24
|
"keywords": [
|
|
24
25
|
"sellable",
|
|
25
26
|
"mcp",
|
|
26
27
|
"claude-code",
|
|
27
28
|
"codex",
|
|
29
|
+
"hermes",
|
|
28
30
|
"linkedin",
|
|
29
31
|
"outbound"
|
|
30
32
|
],
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: refill-sends-evergreen
|
|
3
|
+
description: Deprecated alias for refill-sends-v2.
|
|
4
|
+
visibility: public
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- mcp__sellable__get_subskill_prompt
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Refill Sends Evergreen
|
|
10
|
+
|
|
11
|
+
Superseded by refill-sends-v2. The prompt name now redirects through
|
|
12
|
+
`DEPRECATED_SUBSKILL_PROMPT_REPLACEMENTS`; do not call this deprecated prompt
|
|
13
|
+
directly.
|
|
14
|
+
|
|
15
|
+
Use `refill-sends-v2` instead:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
get_subskill_prompt({ subskillName: "refill-sends-v2" })
|
|
19
|
+
```
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: refill-sends-v2
|
|
3
|
+
description: Execute refill sends v2 through the fenced evergreen refill loop, with dry-run and resume support.
|
|
4
|
+
visibility: public
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- mcp__sellable__refill_sends_v2
|
|
7
|
+
- mcp__sellable__get_refill_plan_v2
|
|
8
|
+
- mcp__sellable__get_subskill_prompt
|
|
9
|
+
- mcp__sellable__get_subskill_asset
|
|
10
|
+
- mcp__sellable__get_auth_status
|
|
11
|
+
- mcp__sellable__get_active_workspace
|
|
12
|
+
- mcp__sellable__list_workspaces
|
|
13
|
+
- mcp__sellable__get_workspace
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Refill Sends V2
|
|
17
|
+
|
|
18
|
+
Use this command for refill requests such as "refill sends", "fill evergreen
|
|
19
|
+
senders", "load everyone up", "run Damiano", or "show a dry run for this
|
|
20
|
+
workspace".
|
|
21
|
+
|
|
22
|
+
Host command names:
|
|
23
|
+
|
|
24
|
+
- Claude Code: `/sellable:refill-sends-v2`
|
|
25
|
+
- Codex: `$sellable:refill-sends-v2`
|
|
26
|
+
|
|
27
|
+
`refill_sends_v2` is the execution surface. In real-run mode it starts or
|
|
28
|
+
resumes a fenced refill run, reads a fresh packet, executes only the packet's
|
|
29
|
+
named bounded work, verifies the result, and returns either a terminal report, a
|
|
30
|
+
blocked report, or an in-progress resume handle. In dry-run mode it stays
|
|
31
|
+
read-only and writes only a dry-run journal.
|
|
32
|
+
|
|
33
|
+
## Entry
|
|
34
|
+
|
|
35
|
+
Resolve the explicit `workspaceId` from the request, automation config, or
|
|
36
|
+
active workspace readback. Scheduled or autonomous usage must carry
|
|
37
|
+
`workspaceId` on every tool call. If the workspace is missing or ambiguous, stop
|
|
38
|
+
with `WORKSPACE_REQUIRED`; do not switch the shared active workspace.
|
|
39
|
+
|
|
40
|
+
For a real refill run:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
refill_sends_v2({ workspaceId, intent:"auto", senderIds?, approvalMode? })
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
For read-only inspection:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
refill_sends_v2({ workspaceId, dryRun:true, intent:"auto", senderIds? })
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
To resume an in-progress run, pass the handle back exactly:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
refill_sends_v2({ workspaceId, runId, fence })
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If a stale handle loses the lease, the tool reports the holder status and the
|
|
59
|
+
approximately 10 minute lockout window. Reinvoke with the current handle or wait
|
|
60
|
+
for lease expiry; never guess a fence.
|
|
61
|
+
|
|
62
|
+
The default `intent:"auto"` inspects `managed_waterfall`,
|
|
63
|
+
`dashboard_evergreen`, and `active_campaign` lane sources. Report the lane
|
|
64
|
+
source and lane chain as proof for every selected sender.
|
|
65
|
+
|
|
66
|
+
If membership blocks a workspace read, report the structured
|
|
67
|
+
`workspace_access` blocker instead of retrying auth or switching workspaces.
|
|
68
|
+
|
|
69
|
+
## Authority
|
|
70
|
+
|
|
71
|
+
The run may execute only packet-named bounded work:
|
|
72
|
+
|
|
73
|
+
- approve generated messages that are ready for review (`approve_messages`);
|
|
74
|
+
- enrich the next batch of unenriched rows (`enrich_more`);
|
|
75
|
+
- copy leads already found into the campaign table (`reconcile_source_copy`);
|
|
76
|
+
- re-run errored enrichment/message cells (`rerun_errored_cells`);
|
|
77
|
+
- find more leads via this campaign's original source (`add_leads_same_source`)
|
|
78
|
+
only for mechanical same-source continuations;
|
|
79
|
+
- start the paused campaign so ready rows can schedule (`start_campaign`) only
|
|
80
|
+
when the campaign is in the packet's pinned lane chain;
|
|
81
|
+
- refresh paid InMail credit facts during bootstrap and once per scheduler-wait
|
|
82
|
+
entry when the packet requires it.
|
|
83
|
+
|
|
84
|
+
The loop records planned -> did -> outcome before and after every foreign
|
|
85
|
+
mutation. It refuses stale packets by fingerprint and validates packet
|
|
86
|
+
coherence before dispatch. The planner owns repair decisions: if a batch needs
|
|
87
|
+
repair, the next packet names the repair rung; the loop bounds and refuses, it
|
|
88
|
+
does not improvise fixes.
|
|
89
|
+
|
|
90
|
+
Do not create campaigns, switch provider families, lower thresholds, write
|
|
91
|
+
scheduler fields, send messages, archive/delete campaigns, or mutate
|
|
92
|
+
brief/filter/message/sequence/sender config unless a separate exact approval
|
|
93
|
+
packet explicitly authorizes that work. In short: no campaign creation, no
|
|
94
|
+
provider-family switch, no threshold lowering, no scheduler writes, no
|
|
95
|
+
launch/send/archive/delete, and no brief/filter/message/sequence/sender
|
|
96
|
+
mutation.
|
|
97
|
+
|
|
98
|
+
## Continuations
|
|
99
|
+
|
|
100
|
+
Sales Nav and Prospeo source continuations that are not yolo-safe are blocked
|
|
101
|
+
as executable scoped handoffs. The report will include text shaped like:
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
$sellable:create-campaign [campaignId]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
That handoff is run in a separate thread only after explicit approval. Scope is
|
|
108
|
+
strict: add leads through this campaign's existing source, keep the same
|
|
109
|
+
saved-search identity, import mode `add` into the same source list, and do
|
|
110
|
+
source work only. It is never a new campaign, provider-family switch, brief
|
|
111
|
+
change, rubric change, message change, sequence change, or sender change.
|
|
112
|
+
|
|
113
|
+
## Verification
|
|
114
|
+
|
|
115
|
+
The loop verifies whole rows before replanning. It reattaches to an own active
|
|
116
|
+
prep job instead of re-dispatching, waits on foreign prep jobs without adopting
|
|
117
|
+
their outcomes, waits on active enrichment/import work by rereading the packet,
|
|
118
|
+
and treats capped scheduler capacity as complete.
|
|
119
|
+
|
|
120
|
+
Scheduler waits are cross-invocation. Each tool call polls briefly under the
|
|
121
|
+
host guard, heartbeats the run lease, and may return:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
{ status:"in_progress", runId, fence, gate, guidance }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Reinvoke with that handle. The approximately five minute scheduler budget is at
|
|
128
|
+
the run level, not one tool call. A window-closed or loaded-awaiting scheduler
|
|
129
|
+
report must include remaining-ready count, exact expected pickup time, and the
|
|
130
|
+
resume handle; never treat bare "awaiting scheduler" copy as a final answer.
|
|
131
|
+
|
|
132
|
+
After the first prep batch, read the conversion-verdict journal line. It is
|
|
133
|
+
rendered from packet facts: `supplyCensus`, `censusReason`, and
|
|
134
|
+
`pipelineDiagnosis.firstFailing`. If the verdict is honest rubric failure, the
|
|
135
|
+
human decision is to update the rubric or change lead source; refill does not
|
|
136
|
+
mutate those automatically.
|
|
137
|
+
|
|
138
|
+
## Results
|
|
139
|
+
|
|
140
|
+
Terminal reasons are:
|
|
141
|
+
|
|
142
|
+
- `complete`
|
|
143
|
+
- `capped_by_scheduler`
|
|
144
|
+
- `loaded_awaiting_scheduler`
|
|
145
|
+
- `lanes_exhausted`
|
|
146
|
+
- `blocked`
|
|
147
|
+
- `no_refillable_campaigns`
|
|
148
|
+
- `not_an_evergreen_workspace`
|
|
149
|
+
|
|
150
|
+
Report campaign names and sender names first, ids second. Include the terminal
|
|
151
|
+
reason or resume handle, lane source, lane chain, chosen label with token
|
|
152
|
+
secondary, plan revision, journal path, blocked continuation packets, and
|
|
153
|
+
firstFailing checklist when present.
|
|
154
|
+
|
|
155
|
+
Real-run journals live under `~/.sellable/refill/runs` by default and append an
|
|
156
|
+
index line. Dry runs include the dry marker and do not create run records.
|
|
@@ -97,8 +97,11 @@ That command helper normalizes arguments and returns the execution contract. In
|
|
|
97
97
|
non-yolo mode it does not mutate. In `--yolo`, it may execute exactly one safe
|
|
98
98
|
bounded primitive from the fresh `target.globalActionQueue[0]`, then reread and
|
|
99
99
|
return the new target plan; currently safe primitives are paid-credit refresh,
|
|
100
|
-
existing-row message preparation,
|
|
101
|
-
|
|
100
|
+
existing-row message preparation, generated-message approval, receipt-proven
|
|
101
|
+
same-source row copy, and read-only wait rereads. Same-source copy/source
|
|
102
|
+
fallback is safe only after receipt-proven exhaustion:
|
|
103
|
+
`hasMoreFrontierRows:false`, zero `approvalCandidates`, no `stuckActiveCells`,
|
|
104
|
+
and no non-terminal `approvedNotDispatched` work. It does not run unbounded approval, lower
|
|
102
105
|
paid-InMail thresholds, switch source families, create campaigns, launch, send,
|
|
103
106
|
or write scheduler rows. Continue with the workflow below for route selection,
|
|
104
107
|
state rereads, approval gating, source import, preparation, and bounded
|
|
@@ -198,7 +201,10 @@ Structured planner packet:
|
|
|
198
201
|
- `target.senderRefillPlans[]` is the canonical sender-level packet; read and
|
|
199
202
|
display it before mutation.
|
|
200
203
|
- Each sender packet includes `campaignRanking.options`, `sourcePlan`,
|
|
201
|
-
`nextActions`, and `manualAlternates`.
|
|
204
|
+
`refillReceipt`, `nextActions`, and `manualAlternates`.
|
|
205
|
+
- `refillReceipt` is the public ladder receipt. It carries the selected
|
|
206
|
+
campaign/sender/lane summary, skipped rungs, existing-row frontier proof, and
|
|
207
|
+
any absolute `wait.deadlineAt`.
|
|
202
208
|
- Preserve these coverage labels exactly: `Need to prepare`, `Goal`,
|
|
203
209
|
`Already sent`, `Scheduled`, `Ready and waiting to be scheduled`, and
|
|
204
210
|
`Still need`.
|
|
@@ -217,6 +223,13 @@ from the selected source (`selectedLeadListId`, provider, and source
|
|
|
217
223
|
fingerprint preserved), then use provider-aligned source-more. A new source or
|
|
218
224
|
provider switch changes the reply-rate baseline and is a manual alternate, not a
|
|
219
225
|
`--yolo` side effect.
|
|
226
|
+
Source/copy/fallback requires receipt-proven exhaustion of earlier rungs:
|
|
227
|
+
`existingRowFrontier.hasMoreFrontierRows:false`, zero `approvalCandidates`, no
|
|
228
|
+
fresh active prep, no `stuckActiveCells`, and no non-terminal
|
|
229
|
+
`approvedNotDispatched` rows. Treat anomalies, `stuckActiveCells`, and
|
|
230
|
+
non-terminal `approvedNotDispatched` as diagnose-and-report gates, not
|
|
231
|
+
exhaustion. Terminal `approvedNotDispatched` blockers may be reported, then the
|
|
232
|
+
ladder can proceed.
|
|
220
233
|
|
|
221
234
|
Run-local paid-credit guard: in `--yolo`, the `refill_sends` MCP command
|
|
222
235
|
automatically maintains a `refreshedPaidInmailSenderIds` set for the current
|
|
@@ -266,6 +279,10 @@ interval, until projected coverage fills the target, a concrete non-scheduler
|
|
|
266
279
|
blocker appears, or Christian explicitly asks to stop or only receive a status
|
|
267
280
|
report. Treat `awaiting_scheduler_after_ready_buffer` as an in-progress wait
|
|
268
281
|
state, not a close-out condition.
|
|
282
|
+
Wait actions are gates, not competing goals. When `wait_for_active_work` or
|
|
283
|
+
`wait_for_scheduler` includes receipt `wait.deadlineAt`, honor that absolute
|
|
284
|
+
deadline; if it is expired on this call, escalate to diagnostics with the
|
|
285
|
+
receipt evidence instead of issuing another blind wait.
|
|
269
286
|
If paid InMail credit facts are stale or missing and the first target plan
|
|
270
287
|
contains `refresh_paid_inmail_credits`, do not present that as the operator's
|
|
271
288
|
next action in `--yolo`. Do not present paid-credit refresh as the next operator
|