@sellable/install 0.1.68 → 0.1.70
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 +5 -1
- package/agents/linkedin-engagement-scout.md +41 -0
- package/agents/prospeo-contact-scout.md +43 -0
- package/agents/registry.json +108 -0
- package/agents/sales-nav-scout.md +44 -0
- package/bin/sellable-install.mjs +220 -0
- package/package.json +2 -1
- package/skill-templates/create-campaign.md +11 -4
package/README.md
CHANGED
|
@@ -33,7 +33,11 @@ Auth is stored once at:
|
|
|
33
33
|
~/.sellable/config.json
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
Claude Code and Codex are configured to launch the same packaged MCP server.
|
|
36
|
+
Claude Code and Codex are configured to launch the same packaged MCP server. The
|
|
37
|
+
installer also writes Sellable source-scout agents for both hosts from the
|
|
38
|
+
packaged `agents/` registry: Claude Code gets `lead-explorer-*` subagents, and
|
|
39
|
+
Codex gets `linkedin_engagement_scout`, `sales_nav_scout`, and
|
|
40
|
+
`prospeo_contact_scout`.
|
|
37
41
|
|
|
38
42
|
## Names
|
|
39
43
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
You are the LinkedIn Engagement Scout for Sellable find-leads.
|
|
2
|
+
|
|
3
|
+
Your job is to test whether active LinkedIn posts and engagers can produce a warm first-send list for the campaign. Work only on this source lane. Do not import leads, create campaigns, write campaign artifacts, draft messages, ask the user questions, or make the final source decision.
|
|
4
|
+
|
|
5
|
+
Required first step:
|
|
6
|
+
|
|
7
|
+
- Load the canonical provider prompt before searching: `get_provider_prompt({ provider: "signal-discovery", confirmed: true })`.
|
|
8
|
+
|
|
9
|
+
Use the inherited Sellable MCP tools when available:
|
|
10
|
+
|
|
11
|
+
- `search_signals` to find recent post lanes.
|
|
12
|
+
- `fetch_post_engagers` to sample engagers from selected posts.
|
|
13
|
+
|
|
14
|
+
Process:
|
|
15
|
+
|
|
16
|
+
1. Read the campaign brief, kickoff doc, or lane prompt supplied by the parent.
|
|
17
|
+
2. Search 3-5 keyword/topic lanes, favoring fresh posts from the last 7-14 days.
|
|
18
|
+
3. Select 3-5 promising posts when available.
|
|
19
|
+
4. Fetch or sample engagers for selected posts and score rough ICP fit from visible headline/display-name cues only. Do not enrich people during viability estimation.
|
|
20
|
+
5. Estimate usable prospects per selected post from sampled pass rate. If the sample is good but volume is low, say how many more similar posts should be added or scraped.
|
|
21
|
+
6. Return false positives and dead ends explicitly.
|
|
22
|
+
|
|
23
|
+
Return a concise structured result with:
|
|
24
|
+
|
|
25
|
+
- `source_lane`
|
|
26
|
+
- `provider_prompt_loaded`
|
|
27
|
+
- `keyword_lanes` with timeframe, raw posts found, finalist posts reviewed
|
|
28
|
+
- `selected_posts` with URL/title, author/topic, age, engager count, sampled engagers, good fits as n/N, estimated usable prospects per post, use/discard
|
|
29
|
+
- `sample_leads`, if any
|
|
30
|
+
- `estimated_good_fit_range`
|
|
31
|
+
- `expected_reply_rate_range`, directional if inferred
|
|
32
|
+
- `false_positive_patterns`
|
|
33
|
+
- `recommendation`
|
|
34
|
+
- `confidence`
|
|
35
|
+
|
|
36
|
+
Evidence standards:
|
|
37
|
+
|
|
38
|
+
- Do not trust raw post volume without inspecting finalist post quality.
|
|
39
|
+
- Prefer sample-based pass rates over intuition.
|
|
40
|
+
- If `fetch_post_engagers` is unavailable or fails, report that explicitly and mark the estimate lower-confidence.
|
|
41
|
+
- Keep LinkedIn Engagement viable when selected posts can produce roughly 150+ ICP-fit warm prospects before final filtering, even if Sales Nav is more scalable.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
You are the Prospeo Contact Scout for Sellable find-leads.
|
|
2
|
+
|
|
3
|
+
Your job is to test whether Prospeo can produce verified-contact scale for the campaign through account/domain targeting or broad persona expansion. Work only on this source lane. Do not import leads, create campaigns, write campaign artifacts, draft messages, ask the user questions, or make the final source decision.
|
|
4
|
+
|
|
5
|
+
Required first step:
|
|
6
|
+
|
|
7
|
+
- Load the canonical provider prompt before searching: `get_provider_prompt({ provider: "prospeo", confirmed: true })`.
|
|
8
|
+
|
|
9
|
+
Use the inherited Sellable MCP tools when available:
|
|
10
|
+
|
|
11
|
+
- `load_csv_domains` when the parent supplies a CSV on disk and no `domainFilterId` exists.
|
|
12
|
+
- `save_domain_filters` when the parent supplies pasted/raw include or exclude domains and no `domainFilterId` exists.
|
|
13
|
+
- `search_prospeo` for campaignless people previews.
|
|
14
|
+
|
|
15
|
+
Process:
|
|
16
|
+
|
|
17
|
+
1. Read the campaign brief, source intake, kickoff doc, or lane prompt supplied by the parent.
|
|
18
|
+
2. Identify whether this is domain/account targeting or broad persona expansion.
|
|
19
|
+
3. For domain targeting, use or create the standalone `domainFilterId` before searching; never pass raw domains directly into `search_prospeo`.
|
|
20
|
+
4. Run the narrowest useful Prospeo people preview and 1-2 refinements if quality or scale is unclear.
|
|
21
|
+
5. Call out that Prospeo gives contact/account coverage but usually weaker LinkedIn intent than LinkedIn Engagement or Sales Nav activity slices.
|
|
22
|
+
|
|
23
|
+
Return a concise structured result with:
|
|
24
|
+
|
|
25
|
+
- `source_lane`
|
|
26
|
+
- `provider_prompt_loaded`
|
|
27
|
+
- `mode`
|
|
28
|
+
- `domain_filter_or_account_inputs`
|
|
29
|
+
- `exact_search_recipe`
|
|
30
|
+
- `raw_result_count`
|
|
31
|
+
- `sampled_people` and good fits as n/N
|
|
32
|
+
- `estimated_good_fit_range_after_cleanup`
|
|
33
|
+
- `expected_reply_rate_range`, directional if inferred
|
|
34
|
+
- `sample_leads`
|
|
35
|
+
- `false_positive_patterns`
|
|
36
|
+
- `recommendation`
|
|
37
|
+
- `confidence`
|
|
38
|
+
|
|
39
|
+
Evidence standards:
|
|
40
|
+
|
|
41
|
+
- Never pass raw domains, company website arrays, or company-name arrays into `search_prospeo`.
|
|
42
|
+
- If the user supplied company names rather than domains, report that domain resolution is required before this lane can run safely.
|
|
43
|
+
- Treat Prospeo as an account/contact coverage lane, not as proof of fresh LinkedIn intent.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"agents": [
|
|
4
|
+
{
|
|
5
|
+
"id": "linkedin-engagement-scout",
|
|
6
|
+
"promptFile": "linkedin-engagement-scout.md",
|
|
7
|
+
"displayName": "LinkedIn Engagement Scout",
|
|
8
|
+
"codex": {
|
|
9
|
+
"name": "linkedin_engagement_scout",
|
|
10
|
+
"filename": "linkedin-engagement-scout.toml",
|
|
11
|
+
"description": "Sellable lead-source scout for LinkedIn post engagement and active conversation signals.",
|
|
12
|
+
"modelReasoningEffort": "medium",
|
|
13
|
+
"sandboxMode": "read-only",
|
|
14
|
+
"nicknameCandidates": [
|
|
15
|
+
"LinkedIn Engagement Scout",
|
|
16
|
+
"Post Scout",
|
|
17
|
+
"Engager Scout"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"claude": {
|
|
21
|
+
"name": "lead-explorer-signals",
|
|
22
|
+
"filename": "lead-explorer-signals.md",
|
|
23
|
+
"description": "Use proactively as a background Sellable source scout when find-leads or create-campaign needs LinkedIn post engagement, Signals, or active conversation evidence.",
|
|
24
|
+
"model": "inherit",
|
|
25
|
+
"background": true,
|
|
26
|
+
"maxTurns": 8,
|
|
27
|
+
"color": "blue",
|
|
28
|
+
"tools": [
|
|
29
|
+
"Read",
|
|
30
|
+
"Grep",
|
|
31
|
+
"Glob",
|
|
32
|
+
"mcp__sellable__get_provider_prompt",
|
|
33
|
+
"mcp__sellable__search_signals",
|
|
34
|
+
"mcp__sellable__fetch_post_engagers"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "sales-nav-scout",
|
|
40
|
+
"promptFile": "sales-nav-scout.md",
|
|
41
|
+
"displayName": "Sales Nav Scout",
|
|
42
|
+
"codex": {
|
|
43
|
+
"name": "sales_nav_scout",
|
|
44
|
+
"filename": "sales-nav-scout.toml",
|
|
45
|
+
"description": "Sellable lead-source scout for Sales Navigator role, company, and activity filters.",
|
|
46
|
+
"modelReasoningEffort": "medium",
|
|
47
|
+
"sandboxMode": "read-only",
|
|
48
|
+
"nicknameCandidates": [
|
|
49
|
+
"Sales Nav Scout",
|
|
50
|
+
"Role Filter Scout",
|
|
51
|
+
"Activity Scout"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"claude": {
|
|
55
|
+
"name": "lead-explorer-sales-nav",
|
|
56
|
+
"filename": "lead-explorer-sales-nav.md",
|
|
57
|
+
"description": "Use proactively as a background Sellable source scout when find-leads or create-campaign needs Sales Navigator title, company, geography, or activity-filter evidence.",
|
|
58
|
+
"model": "inherit",
|
|
59
|
+
"background": true,
|
|
60
|
+
"maxTurns": 8,
|
|
61
|
+
"color": "cyan",
|
|
62
|
+
"tools": [
|
|
63
|
+
"Read",
|
|
64
|
+
"Grep",
|
|
65
|
+
"Glob",
|
|
66
|
+
"mcp__sellable__get_provider_prompt",
|
|
67
|
+
"mcp__sellable__lookup_sales_nav_filter",
|
|
68
|
+
"mcp__sellable__search_sales_nav"
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": "prospeo-contact-scout",
|
|
74
|
+
"promptFile": "prospeo-contact-scout.md",
|
|
75
|
+
"displayName": "Prospeo Contact Scout",
|
|
76
|
+
"codex": {
|
|
77
|
+
"name": "prospeo_contact_scout",
|
|
78
|
+
"filename": "prospeo-contact-scout.toml",
|
|
79
|
+
"description": "Sellable lead-source scout for Prospeo account/domain and broad contact expansion.",
|
|
80
|
+
"modelReasoningEffort": "medium",
|
|
81
|
+
"sandboxMode": "read-only",
|
|
82
|
+
"nicknameCandidates": [
|
|
83
|
+
"Prospeo Contact Scout",
|
|
84
|
+
"Domain Scout",
|
|
85
|
+
"Contact Scout"
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
"claude": {
|
|
89
|
+
"name": "lead-explorer-prospeo",
|
|
90
|
+
"filename": "lead-explorer-prospeo.md",
|
|
91
|
+
"description": "Use proactively as a background Sellable source scout when find-leads or create-campaign needs Prospeo account, domain-list, CSV-domain, or verified-contact evidence.",
|
|
92
|
+
"model": "inherit",
|
|
93
|
+
"background": true,
|
|
94
|
+
"maxTurns": 8,
|
|
95
|
+
"color": "green",
|
|
96
|
+
"tools": [
|
|
97
|
+
"Read",
|
|
98
|
+
"Grep",
|
|
99
|
+
"Glob",
|
|
100
|
+
"mcp__sellable__get_provider_prompt",
|
|
101
|
+
"mcp__sellable__load_csv_domains",
|
|
102
|
+
"mcp__sellable__save_domain_filters",
|
|
103
|
+
"mcp__sellable__search_prospeo"
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
You are the Sales Nav Scout for Sellable find-leads.
|
|
2
|
+
|
|
3
|
+
Your job is to test whether Sales Navigator filters can produce a scalable, high-fit lead pool for the campaign. Work only on this source lane. Do not import leads, create campaigns, write campaign artifacts, draft messages, ask the user questions, or make the final source decision.
|
|
4
|
+
|
|
5
|
+
Required first step:
|
|
6
|
+
|
|
7
|
+
- Load the canonical provider prompt before searching: `get_provider_prompt({ provider: "sales-nav", confirmed: true })`.
|
|
8
|
+
|
|
9
|
+
Use the inherited Sellable MCP tools when available:
|
|
10
|
+
|
|
11
|
+
- `lookup_sales_nav_filter` before any dynamic Sales Nav filter.
|
|
12
|
+
- `search_sales_nav` for campaignless preview searches.
|
|
13
|
+
|
|
14
|
+
Process:
|
|
15
|
+
|
|
16
|
+
1. Read the campaign brief, kickoff doc, or lane prompt supplied by the parent.
|
|
17
|
+
2. Preserve target role names with `CURRENT_TITLE` lookups; do not rely on seniority alone when the brief names concrete roles.
|
|
18
|
+
3. When `lookup_sales_nav_filter` returns multiple title options, choose the closest semantic title match instead of the first result.
|
|
19
|
+
4. Build a broad-but-reasonable baseline from role/title, geography, company size, industry/account context, and recent LinkedIn activity when relevant.
|
|
20
|
+
5. Run the baseline plus 1-2 refinements if the first pass is noisy or under-scaled.
|
|
21
|
+
6. Verify filters actually applied: returned search URL contains filters, first-page rows match the intended lane, and result count does not look like an unfiltered pool.
|
|
22
|
+
|
|
23
|
+
Return a concise structured result with:
|
|
24
|
+
|
|
25
|
+
- `source_lane`
|
|
26
|
+
- `provider_prompt_loaded`
|
|
27
|
+
- `exact_filter_recipe`
|
|
28
|
+
- `lookup_ids_used`
|
|
29
|
+
- `raw_result_count`
|
|
30
|
+
- `sampled_people` and good fits as n/N
|
|
31
|
+
- `estimated_good_fit_range_after_cleanup`
|
|
32
|
+
- `expected_acceptance_rate_range`, directional if inferred
|
|
33
|
+
- `expected_reply_rate_range`, directional if inferred
|
|
34
|
+
- `sample_leads`
|
|
35
|
+
- `false_positive_patterns`
|
|
36
|
+
- `recommendation`
|
|
37
|
+
- `confidence`
|
|
38
|
+
|
|
39
|
+
Evidence standards:
|
|
40
|
+
|
|
41
|
+
- Optimize for a useful prospect pool, not max volume at any cost.
|
|
42
|
+
- Bias toward `POSTED_ON_LINKEDIN` for reply-likelihood when the pool still has enough scale.
|
|
43
|
+
- Do not hand-wave missing filter IDs.
|
|
44
|
+
- If Sales Nav returns a giant unfiltered pool, discard that result and retry with valid filters before recommending it.
|
package/bin/sellable-install.mjs
CHANGED
|
@@ -1089,6 +1089,111 @@ function codexPluginSkills() {
|
|
|
1089
1089
|
];
|
|
1090
1090
|
}
|
|
1091
1091
|
|
|
1092
|
+
function installerPackageRoot() {
|
|
1093
|
+
return join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
function agentTemplateRoot() {
|
|
1097
|
+
const packagedRoot = join(installerPackageRoot(), "agents");
|
|
1098
|
+
if (existsSync(join(packagedRoot, "registry.json"))) {
|
|
1099
|
+
return packagedRoot;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// Dev fallback for running the installer directly from the repo before
|
|
1103
|
+
// sync-skill-templates has copied the package templates.
|
|
1104
|
+
const repoRoot = join(installerPackageRoot(), "..", "..");
|
|
1105
|
+
const canonicalRoot = join(repoRoot, "mcp", "sellable", "agents");
|
|
1106
|
+
if (existsSync(join(canonicalRoot, "registry.json"))) {
|
|
1107
|
+
return canonicalRoot;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
throw new Error(
|
|
1111
|
+
`Sellable source scout agent templates missing. Expected ${join(
|
|
1112
|
+
packagedRoot,
|
|
1113
|
+
"registry.json"
|
|
1114
|
+
)}`
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
function loadCanonicalAgents() {
|
|
1119
|
+
const root = agentTemplateRoot();
|
|
1120
|
+
const registry = JSON.parse(readFileSync(join(root, "registry.json"), "utf8"));
|
|
1121
|
+
if (!Array.isArray(registry.agents)) {
|
|
1122
|
+
throw new Error("Sellable agent registry is missing agents array.");
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
return registry.agents.map((agent) => {
|
|
1126
|
+
const promptPath = join(root, agent.promptFile || "");
|
|
1127
|
+
if (!agent.id || !agent.promptFile || !existsSync(promptPath)) {
|
|
1128
|
+
throw new Error(`Invalid Sellable agent registry entry: ${agent.id || "unknown"}`);
|
|
1129
|
+
}
|
|
1130
|
+
if (!agent.codex?.name || !agent.codex?.filename) {
|
|
1131
|
+
throw new Error(`Sellable agent ${agent.id} is missing Codex metadata.`);
|
|
1132
|
+
}
|
|
1133
|
+
if (!agent.claude?.name || !agent.claude?.filename) {
|
|
1134
|
+
throw new Error(`Sellable agent ${agent.id} is missing Claude metadata.`);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
return {
|
|
1138
|
+
...agent,
|
|
1139
|
+
prompt: readFileSync(promptPath, "utf8").trimEnd(),
|
|
1140
|
+
};
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function tomlArray(values) {
|
|
1145
|
+
return `[${values.map((value) => quoteToml(value)).join(", ")}]`;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function tomlMultilineString(value) {
|
|
1149
|
+
const escaped = String(value).replace(/\\/g, "\\\\").replace(/"""/g, '\\"""');
|
|
1150
|
+
return `"""\n${escaped}\n"""`;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function generateCodexAgentToml(agent) {
|
|
1154
|
+
const codex = agent.codex;
|
|
1155
|
+
return `name = ${quoteToml(codex.name)}
|
|
1156
|
+
description = ${quoteToml(codex.description)}
|
|
1157
|
+
model_reasoning_effort = ${quoteToml(codex.modelReasoningEffort || "medium")}
|
|
1158
|
+
sandbox_mode = ${quoteToml(codex.sandboxMode || "read-only")}
|
|
1159
|
+
nickname_candidates = ${tomlArray(codex.nicknameCandidates || [agent.displayName])}
|
|
1160
|
+
developer_instructions = ${tomlMultilineString(agent.prompt)}
|
|
1161
|
+
`;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function codexCustomAgents() {
|
|
1165
|
+
return loadCanonicalAgents().map((agent) => ({
|
|
1166
|
+
name: agent.codex.name,
|
|
1167
|
+
filename: agent.codex.filename,
|
|
1168
|
+
content: generateCodexAgentToml(agent),
|
|
1169
|
+
}));
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
function generateClaudeAgentMd(agent) {
|
|
1173
|
+
const claude = agent.claude;
|
|
1174
|
+
return `---
|
|
1175
|
+
name: ${claude.name}
|
|
1176
|
+
description: ${JSON.stringify(claude.description)}
|
|
1177
|
+
tools: ${(claude.tools || []).join(", ")}
|
|
1178
|
+
model: ${claude.model || "inherit"}
|
|
1179
|
+
background: ${claude.background === true ? "true" : "false"}
|
|
1180
|
+
maxTurns: ${Number.isFinite(claude.maxTurns) ? claude.maxTurns : 8}
|
|
1181
|
+
color: ${claude.color || "blue"}
|
|
1182
|
+
---
|
|
1183
|
+
|
|
1184
|
+
${agent.prompt}
|
|
1185
|
+
`;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
function claudeCustomAgents() {
|
|
1189
|
+
return loadCanonicalAgents().map((agent) => ({
|
|
1190
|
+
name: agent.claude.name,
|
|
1191
|
+
filename: agent.claude.filename,
|
|
1192
|
+
tools: agent.claude.tools || [],
|
|
1193
|
+
content: generateClaudeAgentMd(agent),
|
|
1194
|
+
}));
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1092
1197
|
function writeCodexPluginSkills(pluginRoot, opts) {
|
|
1093
1198
|
for (const skill of codexPluginSkills()) {
|
|
1094
1199
|
const skillRoot = join(pluginRoot, "skills", skill.dir);
|
|
@@ -1104,6 +1209,23 @@ function writeCodexPluginSkills(pluginRoot, opts) {
|
|
|
1104
1209
|
}
|
|
1105
1210
|
}
|
|
1106
1211
|
|
|
1212
|
+
function writeCodexCustomAgents(home, opts) {
|
|
1213
|
+
for (const agent of codexCustomAgents()) {
|
|
1214
|
+
writeFile(join(home, "agents", agent.filename), agent.content, opts);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
function claudeHome() {
|
|
1219
|
+
return join(homedir(), ".claude");
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
function writeClaudeCustomAgents(opts) {
|
|
1223
|
+
const home = claudeHome();
|
|
1224
|
+
for (const agent of claudeCustomAgents()) {
|
|
1225
|
+
writeFile(join(home, "agents", agent.filename), agent.content, opts);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1107
1229
|
function installCodexDesktopPlugin(opts) {
|
|
1108
1230
|
const home = codexHome();
|
|
1109
1231
|
const configPath = join(home, "config.toml");
|
|
@@ -1154,6 +1276,7 @@ function installCodexDesktopPlugin(opts) {
|
|
|
1154
1276
|
opts
|
|
1155
1277
|
);
|
|
1156
1278
|
writeCodexPluginSkills(pluginRoot, opts);
|
|
1279
|
+
writeCodexCustomAgents(home, opts);
|
|
1157
1280
|
|
|
1158
1281
|
if (!opts.dryRun) {
|
|
1159
1282
|
rmSync(cacheRoot, { recursive: true, force: true });
|
|
@@ -1209,6 +1332,21 @@ enabled = false`
|
|
|
1209
1332
|
"default_mode_request_user_input",
|
|
1210
1333
|
true
|
|
1211
1334
|
);
|
|
1335
|
+
content = upsertTomlTable(
|
|
1336
|
+
content,
|
|
1337
|
+
"agents",
|
|
1338
|
+
`[agents]
|
|
1339
|
+
max_threads = 6
|
|
1340
|
+
max_depth = 1`
|
|
1341
|
+
);
|
|
1342
|
+
for (const agent of codexCustomAgents()) {
|
|
1343
|
+
content = upsertTomlTable(
|
|
1344
|
+
content,
|
|
1345
|
+
`agents.${agent.name}`,
|
|
1346
|
+
`[agents.${agent.name}]
|
|
1347
|
+
config_file = ${quoteToml(join(home, "agents", agent.filename))}`
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1212
1350
|
writeFileSync(configPath, `${content.trimEnd()}\n`, { mode: 0o600 });
|
|
1213
1351
|
} else {
|
|
1214
1352
|
logVerbose(
|
|
@@ -1220,6 +1358,8 @@ enabled = false`
|
|
|
1220
1358
|
logVerbose(
|
|
1221
1359
|
`${C.grey}+ enable [features].default_mode_request_user_input in ${configPath}${C.reset}`
|
|
1222
1360
|
);
|
|
1361
|
+
logVerbose(`${C.grey}+ write Codex custom scout agents in ${home}/agents${C.reset}`);
|
|
1362
|
+
logVerbose(`${C.grey}+ register Codex source scout agents in ${configPath}${C.reset}`);
|
|
1223
1363
|
}
|
|
1224
1364
|
|
|
1225
1365
|
return {
|
|
@@ -1283,12 +1423,14 @@ function installClaude(opts) {
|
|
|
1283
1423
|
}
|
|
1284
1424
|
throw new Error(message);
|
|
1285
1425
|
}
|
|
1426
|
+
writeClaudeCustomAgents(opts);
|
|
1286
1427
|
if (opts.server === "hosted") {
|
|
1287
1428
|
run(
|
|
1288
1429
|
"claude",
|
|
1289
1430
|
["mcp", "add", "--transport", "http", "sellable", opts.hostedUrl],
|
|
1290
1431
|
opts
|
|
1291
1432
|
);
|
|
1433
|
+
patchClaudeAlwaysLoad(opts);
|
|
1292
1434
|
return true;
|
|
1293
1435
|
}
|
|
1294
1436
|
const [command, args] = mcpCommand(opts);
|
|
@@ -1403,6 +1545,30 @@ function verify(opts) {
|
|
|
1403
1545
|
? "Claude CLI present"
|
|
1404
1546
|
: "Claude CLI missing",
|
|
1405
1547
|
});
|
|
1548
|
+
const claudeAgentPaths = claudeCustomAgents().map((agent) =>
|
|
1549
|
+
join(claudeHome(), "agents", agent.filename)
|
|
1550
|
+
);
|
|
1551
|
+
const hasClaudeScouts = claudeAgentPaths.every((agentPath) =>
|
|
1552
|
+
existsSync(agentPath)
|
|
1553
|
+
);
|
|
1554
|
+
checks.push({
|
|
1555
|
+
ok: hasClaudeScouts,
|
|
1556
|
+
label: hasClaudeScouts
|
|
1557
|
+
? "Claude custom scout agents present"
|
|
1558
|
+
: "Claude custom scout agents missing",
|
|
1559
|
+
});
|
|
1560
|
+
const claudeAgentsHaveTools = claudeCustomAgents().every((agent) => {
|
|
1561
|
+
const agentPath = join(claudeHome(), "agents", agent.filename);
|
|
1562
|
+
if (!existsSync(agentPath)) return false;
|
|
1563
|
+
const content = readFileSync(agentPath, "utf8");
|
|
1564
|
+
return agent.tools.every((tool) => content.includes(tool));
|
|
1565
|
+
});
|
|
1566
|
+
checks.push({
|
|
1567
|
+
ok: claudeAgentsHaveTools,
|
|
1568
|
+
label: claudeAgentsHaveTools
|
|
1569
|
+
? "Claude scout MCP tool allowlists present"
|
|
1570
|
+
: "Claude scout MCP tool allowlists missing",
|
|
1571
|
+
});
|
|
1406
1572
|
}
|
|
1407
1573
|
if (opts.host === "codex" || opts.host === "all") {
|
|
1408
1574
|
checks.push({
|
|
@@ -1442,10 +1608,31 @@ function verify(opts) {
|
|
|
1442
1608
|
? "Codex skill bundle present"
|
|
1443
1609
|
: "Codex skill bundle missing",
|
|
1444
1610
|
});
|
|
1611
|
+
const codexAgentPaths = codexCustomAgents().map((agent) =>
|
|
1612
|
+
join(codexHome(), "agents", agent.filename)
|
|
1613
|
+
);
|
|
1614
|
+
const hasCodexScouts = codexAgentPaths.every((agentPath) =>
|
|
1615
|
+
existsSync(agentPath)
|
|
1616
|
+
);
|
|
1617
|
+
checks.push({
|
|
1618
|
+
ok: hasCodexScouts,
|
|
1619
|
+
label: hasCodexScouts
|
|
1620
|
+
? "Codex custom scout agents present"
|
|
1621
|
+
: "Codex custom scout agents missing",
|
|
1622
|
+
});
|
|
1445
1623
|
const configPath = join(codexHome(), "config.toml");
|
|
1446
1624
|
const configContent = existsSync(configPath)
|
|
1447
1625
|
? readFileSync(configPath, "utf8")
|
|
1448
1626
|
: "";
|
|
1627
|
+
const hasCodexAgentRegistrations = codexCustomAgents().every((agent) =>
|
|
1628
|
+
configContent.includes(`[agents.${agent.name}]`)
|
|
1629
|
+
);
|
|
1630
|
+
checks.push({
|
|
1631
|
+
ok: hasCodexAgentRegistrations,
|
|
1632
|
+
label: hasCodexAgentRegistrations
|
|
1633
|
+
? "Codex custom scout agents registered"
|
|
1634
|
+
: "Codex custom scout agents unregistered",
|
|
1635
|
+
});
|
|
1449
1636
|
const hasFlag = configContent.includes(
|
|
1450
1637
|
"default_mode_request_user_input = true"
|
|
1451
1638
|
);
|
|
@@ -1592,6 +1779,10 @@ function printNextSteps(installedHosts, authReused) {
|
|
|
1592
1779
|
if (hasCodex) {
|
|
1593
1780
|
console.log(` ${C.green}✓${C.reset} Codex Desktop plugin installed`);
|
|
1594
1781
|
console.log(` ${C.green}✓${C.reset} Skills installed`);
|
|
1782
|
+
console.log(` ${C.green}✓${C.reset} Codex source scout agents installed`);
|
|
1783
|
+
}
|
|
1784
|
+
if (hasClaude) {
|
|
1785
|
+
console.log(` ${C.green}✓${C.reset} Claude source scout agents installed`);
|
|
1595
1786
|
}
|
|
1596
1787
|
if (authReused) {
|
|
1597
1788
|
console.log(
|
|
@@ -1670,6 +1861,9 @@ function runUninstall() {
|
|
|
1670
1861
|
after = removeTomlSection(after, "marketplaces.sellable");
|
|
1671
1862
|
after = removeTomlSection(after, 'plugins."sellable@sellable"');
|
|
1672
1863
|
after = removeTomlSection(after, 'plugins."sellable@sellable-local"');
|
|
1864
|
+
for (const agent of codexCustomAgents()) {
|
|
1865
|
+
after = removeTomlSection(after, `agents.${agent.name}`);
|
|
1866
|
+
}
|
|
1673
1867
|
// Collapse 3+ blank lines that the removals may leave behind.
|
|
1674
1868
|
after = after.replace(/\n{3,}/g, "\n\n");
|
|
1675
1869
|
if (after !== before) {
|
|
@@ -1690,6 +1884,19 @@ function runUninstall() {
|
|
|
1690
1884
|
skipped.push(`Codex config not found at ${codexConfigPath}`);
|
|
1691
1885
|
}
|
|
1692
1886
|
|
|
1887
|
+
for (const agent of codexCustomAgents()) {
|
|
1888
|
+
const agentPath = join(codexHome(), "agents", agent.filename);
|
|
1889
|
+
if (!existsSync(agentPath)) continue;
|
|
1890
|
+
try {
|
|
1891
|
+
rmSync(agentPath, { force: true });
|
|
1892
|
+
removed.push(`${agentPath}`);
|
|
1893
|
+
} catch (err) {
|
|
1894
|
+
console.log(
|
|
1895
|
+
` ${C.yellow}!${C.reset} Could not remove ${agentPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
1896
|
+
);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1693
1900
|
// 2) Claude Code MCP removal
|
|
1694
1901
|
if (commandExists("claude")) {
|
|
1695
1902
|
const r = spawnSync("claude", ["mcp", "remove", "sellable"], {
|
|
@@ -1704,6 +1911,19 @@ function runUninstall() {
|
|
|
1704
1911
|
skipped.push(`Claude Code CLI not found`);
|
|
1705
1912
|
}
|
|
1706
1913
|
|
|
1914
|
+
for (const agent of claudeCustomAgents()) {
|
|
1915
|
+
const agentPath = join(claudeHome(), "agents", agent.filename);
|
|
1916
|
+
if (!existsSync(agentPath)) continue;
|
|
1917
|
+
try {
|
|
1918
|
+
rmSync(agentPath, { force: true });
|
|
1919
|
+
removed.push(`${agentPath}`);
|
|
1920
|
+
} catch (err) {
|
|
1921
|
+
console.log(
|
|
1922
|
+
` ${C.yellow}!${C.reset} Could not remove ${agentPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1707
1927
|
// 3) Surgical removal of Sellable-installed artifacts inside ~/.sellable/
|
|
1708
1928
|
const sellableDir = join(homedir(), ".sellable");
|
|
1709
1929
|
if (existsSync(sellableDir)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sellable/install",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.70",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "One-command installer for Sellable MCP in Claude Code and Codex",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"bin",
|
|
15
|
+
"agents",
|
|
15
16
|
"skill-templates",
|
|
16
17
|
"README.md"
|
|
17
18
|
],
|
|
@@ -99,10 +99,17 @@ denominator, and sample basis.
|
|
|
99
99
|
|
|
100
100
|
When the user has not supplied a source and multiple source angles are viable,
|
|
101
101
|
scout those angles as independent branches when the host can actually do it:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
LinkedIn Engagement / active post engagers (internal `signal-discovery`
|
|
103
|
+
provider prompt), Sales Nav / title + company filters, and Prospeo Contact /
|
|
104
|
+
domains only when relevant. In Codex, explicitly spawn the named custom scouts
|
|
105
|
+
`linkedin_engagement_scout`, `sales_nav_scout`, and `prospeo_contact_scout` for
|
|
106
|
+
the credible lanes; Codex does not infer subagent fan-out from generic source
|
|
107
|
+
comparison wording. In Claude Code, invoke the generated `lead-explorer-*`
|
|
108
|
+
Task/Agent subagents for all credible lanes in one assistant message; the
|
|
109
|
+
installer writes them from the same canonical Sellable agent registry with
|
|
110
|
+
explicit Sellable MCP tool allowlists. If the host runs them sequentially, do not
|
|
111
|
+
claim they ran in parallel. In chat, call the downstream copy stage `message generation`;
|
|
112
|
+
`message-validation.md` is only an internal proof artifact.
|
|
106
113
|
|
|
107
114
|
Use rendered Markdown for user review surfaces, not fenced code blocks. Keep
|
|
108
115
|
lines short, use indexed section labels and bullets, and translate internal
|