@sellable/install 0.1.208 → 0.1.210
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/bin/sellable-install.mjs +376 -29
- package/package.json +1 -1
- package/skill-templates/create-campaign.md +13 -5
package/bin/sellable-install.mjs
CHANGED
|
@@ -136,13 +136,22 @@ function usage() {
|
|
|
136
136
|
return `Sellable agent installer
|
|
137
137
|
|
|
138
138
|
Usage:
|
|
139
|
+
curl -fsSL "https://app.sellable.dev/api/v2/cli/install" | sh
|
|
139
140
|
sellable create
|
|
141
|
+
sellable prefs set prompt-sharing true|false
|
|
142
|
+
sellable setup claude-permissions --allow-common-setup
|
|
140
143
|
sellable-install [options]
|
|
144
|
+
|
|
145
|
+
Advanced fallback:
|
|
141
146
|
npx -y @sellable/install@latest -- [options]
|
|
142
147
|
|
|
143
148
|
Commands:
|
|
144
149
|
create Show how to launch public Sellable workflows.
|
|
145
150
|
auth set <token> Save a Sellable API token for first-run auth.
|
|
151
|
+
prefs set prompt-sharing true|false
|
|
152
|
+
Save the ask-first prompt-sharing preference.
|
|
153
|
+
setup claude-permissions --allow-common-setup
|
|
154
|
+
Add common Sellable Bash permissions to Claude settings.
|
|
146
155
|
uninstall Remove Sellable host config and installed artifacts.
|
|
147
156
|
|
|
148
157
|
Options:
|
|
@@ -427,6 +436,83 @@ function readExisting(path) {
|
|
|
427
436
|
}
|
|
428
437
|
}
|
|
429
438
|
|
|
439
|
+
function sellableHostEnvPath() {
|
|
440
|
+
return join(homedir(), ".local", "sellable", "app-sellable-dev", ".env");
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function writePromptSharingPreference(value, opts = { dryRun: false }) {
|
|
444
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
445
|
+
if (normalized !== "true" && normalized !== "false") {
|
|
446
|
+
throw new Error("prompt-sharing must be true or false");
|
|
447
|
+
}
|
|
448
|
+
const envPath = sellableHostEnvPath();
|
|
449
|
+
const key = "SELLABLE_SHARE_SESSION_USER_PROMPTS";
|
|
450
|
+
const line = `${key}=${normalized}`;
|
|
451
|
+
const existing = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
|
|
452
|
+
const lines = existing
|
|
453
|
+
.split(/\r?\n/)
|
|
454
|
+
.filter((existingLine) => existingLine.trim() !== "");
|
|
455
|
+
let replaced = false;
|
|
456
|
+
const nextLines = lines.map((existingLine) => {
|
|
457
|
+
if (existingLine.startsWith(`${key}=`)) {
|
|
458
|
+
replaced = true;
|
|
459
|
+
return line;
|
|
460
|
+
}
|
|
461
|
+
return existingLine;
|
|
462
|
+
});
|
|
463
|
+
if (!replaced) nextLines.push(line);
|
|
464
|
+
|
|
465
|
+
if (!opts.dryRun) {
|
|
466
|
+
mkdirSync(dirname(envPath), { recursive: true, mode: 0o700 });
|
|
467
|
+
writeFileSync(envPath, `${nextLines.join("\n")}\n`, { mode: 0o600 });
|
|
468
|
+
}
|
|
469
|
+
return envPath;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const CLAUDE_COMMON_SETUP_ALLOW = [
|
|
473
|
+
"Bash(sellable *)",
|
|
474
|
+
"Bash(export PATH=$HOME/.local/bin:$PATH && sellable *)",
|
|
475
|
+
];
|
|
476
|
+
|
|
477
|
+
function claudeSettingsPath() {
|
|
478
|
+
return join(homedir(), ".claude", "settings.json");
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function allowClaudeCommonSetup(opts = { dryRun: false }) {
|
|
482
|
+
const settingsPath = claudeSettingsPath();
|
|
483
|
+
let settings = {};
|
|
484
|
+
if (existsSync(settingsPath)) {
|
|
485
|
+
try {
|
|
486
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
487
|
+
} catch (err) {
|
|
488
|
+
throw new Error(
|
|
489
|
+
`Could not parse ${settingsPath}: ${
|
|
490
|
+
err instanceof Error ? err.message : String(err)
|
|
491
|
+
}`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) {
|
|
496
|
+
settings = {};
|
|
497
|
+
}
|
|
498
|
+
const permissions =
|
|
499
|
+
settings.permissions &&
|
|
500
|
+
typeof settings.permissions === "object" &&
|
|
501
|
+
!Array.isArray(settings.permissions)
|
|
502
|
+
? settings.permissions
|
|
503
|
+
: {};
|
|
504
|
+
const allow = Array.isArray(permissions.allow) ? permissions.allow : [];
|
|
505
|
+
const nextAllow = [...allow];
|
|
506
|
+
for (const entry of CLAUDE_COMMON_SETUP_ALLOW) {
|
|
507
|
+
if (!nextAllow.includes(entry)) nextAllow.push(entry);
|
|
508
|
+
}
|
|
509
|
+
settings.permissions = { ...permissions, allow: nextAllow };
|
|
510
|
+
if (!opts.dryRun) {
|
|
511
|
+
writeJson(settingsPath, settings, opts);
|
|
512
|
+
}
|
|
513
|
+
return settingsPath;
|
|
514
|
+
}
|
|
515
|
+
|
|
430
516
|
function codexHome() {
|
|
431
517
|
return process.env.CODEX_HOME?.trim() || join(homedir(), ".codex");
|
|
432
518
|
}
|
|
@@ -728,11 +814,17 @@ person/company this campaign is for, then I’ll turn that into a campaign brief
|
|
|
728
814
|
before anything is created.
|
|
729
815
|
\`\`\`
|
|
730
816
|
|
|
731
|
-
Exception: if \`bootstrap_create_campaign.modelQuality.status === "warn"
|
|
732
|
-
|
|
817
|
+
Exception: if \`bootstrap_create_campaign.modelQuality.status === "warn"\` and
|
|
818
|
+
\`bootstrap_create_campaign.modelQuality.metadataStale !== true\`, the first
|
|
819
|
+
visible campaign message must be the model-quality warning from
|
|
733
820
|
\`modelQuality.message\`. Ask the user to switch to the configured minimum model
|
|
734
821
|
or explicitly continue anyway before identity setup, research, lead filtering,
|
|
735
|
-
message generation, or launch review.
|
|
822
|
+
message generation, or launch review. If \`metadataStale === true\`, continue
|
|
823
|
+
normally and do not ask the user to switch.
|
|
824
|
+
|
|
825
|
+
If \`bootstrap_create_campaign.modelQuality.metadataStale === true\`, continue
|
|
826
|
+
normally. Do not ask the user to switch models; this is an accepted Codex host
|
|
827
|
+
metadata mismatch.
|
|
736
828
|
|
|
737
829
|
If a linked/local skill file is stale or missing, silently use the installed
|
|
738
830
|
\`sellable@sellable\` plugin copy. Do not tell the user about the stale link,
|
|
@@ -1174,8 +1266,10 @@ updates.
|
|
|
1174
1266
|
5. Call \`mcp__sellable__bootstrap_create_campaign({ flowVersion: "v2", campaignId?, host?, model?, reasoningEffort? })\`.
|
|
1175
1267
|
Pass the current host, model, and reasoning when the host exposes them.
|
|
1176
1268
|
6. If \`safeToProceed !== true\`, stop and show \`blockingErrors\` + \`nextStep\`.
|
|
1177
|
-
7. If \`modelQuality.status === "warn"
|
|
1178
|
-
setup/research and wait for the user
|
|
1269
|
+
7. If \`modelQuality.status === "warn"\` and \`modelQuality.metadataStale !== true\`,
|
|
1270
|
+
show \`modelQuality.message\` before any setup/research and wait for the user
|
|
1271
|
+
to switch or explicitly continue. If \`metadataStale === true\`, continue
|
|
1272
|
+
normally and do not tell the user to switch.
|
|
1179
1273
|
|
|
1180
1274
|
## Execute Workflow
|
|
1181
1275
|
|
|
@@ -1868,12 +1962,7 @@ function legacyClaudeCustomAgents() {
|
|
|
1868
1962
|
}
|
|
1869
1963
|
|
|
1870
1964
|
function legacyClaudeCommands() {
|
|
1871
|
-
return [
|
|
1872
|
-
{
|
|
1873
|
-
name: "sellable-create-campaign",
|
|
1874
|
-
filename: join("sellable", "create-campaign.md"),
|
|
1875
|
-
},
|
|
1876
|
-
];
|
|
1965
|
+
return [];
|
|
1877
1966
|
}
|
|
1878
1967
|
|
|
1879
1968
|
function tomlArray(values) {
|
|
@@ -1930,6 +2019,72 @@ function claudeCustomAgents() {
|
|
|
1930
2019
|
}));
|
|
1931
2020
|
}
|
|
1932
2021
|
|
|
2022
|
+
function stripFrontmatter(markdown) {
|
|
2023
|
+
return String(markdown).replace(/^---\n[\s\S]*?\n---\n?/, "").trimStart();
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
function claudeCommandMd(command) {
|
|
2027
|
+
const allowedTools =
|
|
2028
|
+
command.allowedTools && command.allowedTools.length > 0
|
|
2029
|
+
? `allowed-tools:\n${allowedToolsYaml(command.allowedTools)}\n`
|
|
2030
|
+
: "";
|
|
2031
|
+
return `---
|
|
2032
|
+
name: sellable:${command.name}
|
|
2033
|
+
description: ${yamlString(command.description)}
|
|
2034
|
+
argument-hint: ${yamlString(command.argumentHint || "[instructions]")}
|
|
2035
|
+
${allowedTools}---
|
|
2036
|
+
|
|
2037
|
+
# Sellable ${command.title}
|
|
2038
|
+
|
|
2039
|
+
This Claude Code command is the customer-facing entrypoint for
|
|
2040
|
+
\`/sellable:${command.name}\`.
|
|
2041
|
+
|
|
2042
|
+
${stripFrontmatter(command.skillMd)}
|
|
2043
|
+
`;
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
function claudeCommands() {
|
|
2047
|
+
return [
|
|
2048
|
+
{
|
|
2049
|
+
name: "create-campaign",
|
|
2050
|
+
title: "Create Campaign",
|
|
2051
|
+
filename: join("sellable", "create-campaign.md"),
|
|
2052
|
+
description: "Create a Sellable campaign through the approval-gated workflow.",
|
|
2053
|
+
argumentHint: "[campaign goal, company, or offer]",
|
|
2054
|
+
skillMd: createCampaignSkillMd(),
|
|
2055
|
+
allowedTools: ["AskUserQuestion", "Task", ...CREATE_CAMPAIGN_ALLOWED_TOOLS],
|
|
2056
|
+
},
|
|
2057
|
+
{
|
|
2058
|
+
name: "foundation",
|
|
2059
|
+
title: "Foundation",
|
|
2060
|
+
filename: join("sellable", "foundation.md"),
|
|
2061
|
+
description: FOUNDATION_SKILL_DESCRIPTION,
|
|
2062
|
+
argumentHint: "[founder/company context]",
|
|
2063
|
+
skillMd: foundationSkillMd(),
|
|
2064
|
+
},
|
|
2065
|
+
{
|
|
2066
|
+
name: "content",
|
|
2067
|
+
title: "Content",
|
|
2068
|
+
filename: join("sellable", "content.md"),
|
|
2069
|
+
description:
|
|
2070
|
+
"Add transcripts and rough ideas, cluster themes, and hand post requests to create-post.",
|
|
2071
|
+
argumentHint: "[idea, transcript, or content goal]",
|
|
2072
|
+
skillMd: contentSkillMd(),
|
|
2073
|
+
},
|
|
2074
|
+
{
|
|
2075
|
+
name: "create-post",
|
|
2076
|
+
title: "Create Post",
|
|
2077
|
+
filename: join("sellable", "create-post.md"),
|
|
2078
|
+
description: "Capture ideas and draft LinkedIn posts in the user's voice.",
|
|
2079
|
+
argumentHint: "[post idea or source material]",
|
|
2080
|
+
skillMd: createPostSkillMd(),
|
|
2081
|
+
},
|
|
2082
|
+
].map((command) => ({
|
|
2083
|
+
...command,
|
|
2084
|
+
content: claudeCommandMd(command),
|
|
2085
|
+
}));
|
|
2086
|
+
}
|
|
2087
|
+
|
|
1933
2088
|
function writeCodexPluginSkills(pluginRoot, opts) {
|
|
1934
2089
|
const skills = codexPluginSkills();
|
|
1935
2090
|
const currentSkillDirs = new Set(skills.map((skill) => skill.dir));
|
|
@@ -1982,6 +2137,7 @@ function writeClaudeCustomAgents(opts) {
|
|
|
1982
2137
|
for (const agent of claudeCustomAgents()) {
|
|
1983
2138
|
writeFile(join(home, "agents", agent.filename), agent.content, opts);
|
|
1984
2139
|
}
|
|
2140
|
+
writeClaudeCommands(opts);
|
|
1985
2141
|
for (const agent of legacyClaudeCustomAgents()) {
|
|
1986
2142
|
const legacyPath = join(home, "agents", agent.filename);
|
|
1987
2143
|
logVerbose(
|
|
@@ -1992,6 +2148,13 @@ function writeClaudeCustomAgents(opts) {
|
|
|
1992
2148
|
removeLegacyClaudeCommands(home, opts);
|
|
1993
2149
|
}
|
|
1994
2150
|
|
|
2151
|
+
function writeClaudeCommands(opts) {
|
|
2152
|
+
const home = claudeHome();
|
|
2153
|
+
for (const command of claudeCommands()) {
|
|
2154
|
+
writeFile(join(home, "commands", command.filename), command.content, opts);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
|
|
1995
2158
|
function removeLegacyClaudeCommands(home, opts) {
|
|
1996
2159
|
for (const command of legacyClaudeCommands()) {
|
|
1997
2160
|
const commandPath = join(home, "commands", command.filename);
|
|
@@ -2277,12 +2440,15 @@ function installClaude(opts) {
|
|
|
2277
2440
|
throw new Error(message);
|
|
2278
2441
|
}
|
|
2279
2442
|
writeClaudeCustomAgents(opts);
|
|
2443
|
+
removeClaudeMcp(opts);
|
|
2280
2444
|
if (opts.server === "hosted") {
|
|
2281
2445
|
run(
|
|
2282
2446
|
"claude",
|
|
2283
2447
|
[
|
|
2284
2448
|
"mcp",
|
|
2285
2449
|
"add",
|
|
2450
|
+
"--scope",
|
|
2451
|
+
"user",
|
|
2286
2452
|
"--transport",
|
|
2287
2453
|
"http",
|
|
2288
2454
|
"sellable",
|
|
@@ -2294,14 +2460,20 @@ function installClaude(opts) {
|
|
|
2294
2460
|
return true;
|
|
2295
2461
|
}
|
|
2296
2462
|
const [command, args] = mcpCommand(opts);
|
|
2297
|
-
run("claude", ["mcp", "remove", "sellable"], {
|
|
2298
|
-
...opts,
|
|
2299
|
-
dryRun: opts.dryRun,
|
|
2300
|
-
allowFail: true,
|
|
2301
|
-
});
|
|
2302
2463
|
run(
|
|
2303
2464
|
"claude",
|
|
2304
|
-
[
|
|
2465
|
+
[
|
|
2466
|
+
"mcp",
|
|
2467
|
+
"add",
|
|
2468
|
+
"--scope",
|
|
2469
|
+
"user",
|
|
2470
|
+
"--transport",
|
|
2471
|
+
"stdio",
|
|
2472
|
+
"sellable",
|
|
2473
|
+
"--",
|
|
2474
|
+
command,
|
|
2475
|
+
...args,
|
|
2476
|
+
],
|
|
2305
2477
|
opts
|
|
2306
2478
|
);
|
|
2307
2479
|
// Patch ~/.claude.json to add `alwaysLoad: true` so Claude Code v2.1.121+
|
|
@@ -2312,6 +2484,63 @@ function installClaude(opts) {
|
|
|
2312
2484
|
return true;
|
|
2313
2485
|
}
|
|
2314
2486
|
|
|
2487
|
+
function removeClaudeMcp(opts) {
|
|
2488
|
+
for (const scope of ["local", "project", "user"]) {
|
|
2489
|
+
run("claude", ["mcp", "remove", "--scope", scope, "sellable"], {
|
|
2490
|
+
...opts,
|
|
2491
|
+
dryRun: opts.dryRun,
|
|
2492
|
+
allowFail: true,
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
function canonicalClaudeMcpServer(opts) {
|
|
2498
|
+
if (opts.server === "hosted") {
|
|
2499
|
+
return {
|
|
2500
|
+
type: "http",
|
|
2501
|
+
url: withHostedWatchModeDriver(opts.hostedUrl, "claude"),
|
|
2502
|
+
alwaysLoad: true,
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
const [command, args] = mcpCommand(opts);
|
|
2507
|
+
return {
|
|
2508
|
+
type: "stdio",
|
|
2509
|
+
command,
|
|
2510
|
+
args,
|
|
2511
|
+
env: { SELLABLE_WATCH_MODE_DRIVER: "claude" },
|
|
2512
|
+
alwaysLoad: true,
|
|
2513
|
+
};
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
function collectClaudeSellableServers(config) {
|
|
2517
|
+
const servers = [];
|
|
2518
|
+
if (config?.mcpServers?.sellable) {
|
|
2519
|
+
servers.push(config.mcpServers.sellable);
|
|
2520
|
+
}
|
|
2521
|
+
for (const projectPath of Object.keys(config?.projects || {})) {
|
|
2522
|
+
const server = config.projects[projectPath]?.mcpServers?.sellable;
|
|
2523
|
+
if (server) {
|
|
2524
|
+
servers.push(server);
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
return servers;
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
function claudeMcpServerMatches(server, canonical) {
|
|
2531
|
+
if (!server || typeof server !== "object") return false;
|
|
2532
|
+
if (server.alwaysLoad !== true) return false;
|
|
2533
|
+
if (canonical.type === "http") {
|
|
2534
|
+
return server.type === canonical.type && server.url === canonical.url;
|
|
2535
|
+
}
|
|
2536
|
+
return (
|
|
2537
|
+
server.type === canonical.type &&
|
|
2538
|
+
server.command === canonical.command &&
|
|
2539
|
+
JSON.stringify(server.args || []) === JSON.stringify(canonical.args) &&
|
|
2540
|
+
server.env?.SELLABLE_WATCH_MODE_DRIVER === "claude"
|
|
2541
|
+
);
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2315
2544
|
function patchClaudeAlwaysLoad(opts) {
|
|
2316
2545
|
if (opts.dryRun) {
|
|
2317
2546
|
logVerbose(
|
|
@@ -2329,18 +2558,44 @@ function patchClaudeAlwaysLoad(opts) {
|
|
|
2329
2558
|
try {
|
|
2330
2559
|
const raw = readFileSync(claudeJsonPath, "utf8");
|
|
2331
2560
|
const config = JSON.parse(raw);
|
|
2561
|
+
const canonical = canonicalClaudeMcpServer(opts);
|
|
2332
2562
|
let touched = false;
|
|
2333
2563
|
const patchSellableServer = (server) => {
|
|
2334
2564
|
if (!server || typeof server !== "object") return;
|
|
2335
|
-
if (server
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
server.
|
|
2343
|
-
|
|
2565
|
+
if (opts.server === "hosted") {
|
|
2566
|
+
for (const staleKey of ["command", "args", "env"]) {
|
|
2567
|
+
if (staleKey in server) {
|
|
2568
|
+
delete server[staleKey];
|
|
2569
|
+
touched = true;
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
if (server.type !== canonical.type) {
|
|
2573
|
+
server.type = canonical.type;
|
|
2574
|
+
touched = true;
|
|
2575
|
+
}
|
|
2576
|
+
if (server.url !== canonical.url) {
|
|
2577
|
+
server.url = canonical.url;
|
|
2578
|
+
touched = true;
|
|
2579
|
+
}
|
|
2580
|
+
} else {
|
|
2581
|
+
for (const staleKey of ["url"]) {
|
|
2582
|
+
if (staleKey in server) {
|
|
2583
|
+
delete server[staleKey];
|
|
2584
|
+
touched = true;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
if (server.type !== canonical.type) {
|
|
2588
|
+
server.type = canonical.type;
|
|
2589
|
+
touched = true;
|
|
2590
|
+
}
|
|
2591
|
+
if (server.command !== canonical.command) {
|
|
2592
|
+
server.command = canonical.command;
|
|
2593
|
+
touched = true;
|
|
2594
|
+
}
|
|
2595
|
+
if (JSON.stringify(server.args || []) !== JSON.stringify(canonical.args)) {
|
|
2596
|
+
server.args = [...canonical.args];
|
|
2597
|
+
touched = true;
|
|
2598
|
+
}
|
|
2344
2599
|
const existingDriver = server.env?.SELLABLE_WATCH_MODE_DRIVER;
|
|
2345
2600
|
server.env = {
|
|
2346
2601
|
...(server.env && typeof server.env === "object" ? server.env : {}),
|
|
@@ -2350,12 +2605,16 @@ function patchClaudeAlwaysLoad(opts) {
|
|
|
2350
2605
|
touched = true;
|
|
2351
2606
|
}
|
|
2352
2607
|
}
|
|
2608
|
+
if (server.alwaysLoad !== true) {
|
|
2609
|
+
server.alwaysLoad = true;
|
|
2610
|
+
touched = true;
|
|
2611
|
+
}
|
|
2353
2612
|
};
|
|
2354
|
-
// Top-level mcpServers.sellable (
|
|
2613
|
+
// Top-level mcpServers.sellable (user scope)
|
|
2355
2614
|
if (config.mcpServers?.sellable) {
|
|
2356
2615
|
patchSellableServer(config.mcpServers.sellable);
|
|
2357
2616
|
}
|
|
2358
|
-
// Per-project mcpServers.sellable (
|
|
2617
|
+
// Per-project mcpServers.sellable (old local/project installs)
|
|
2359
2618
|
for (const projectPath of Object.keys(config.projects || {})) {
|
|
2360
2619
|
const projServers = config.projects[projectPath]?.mcpServers;
|
|
2361
2620
|
if (projServers?.sellable) {
|
|
@@ -2469,6 +2728,34 @@ async function verify(opts) {
|
|
|
2469
2728
|
: "Claude custom agent MCP tool allowlists missing"
|
|
2470
2729
|
)
|
|
2471
2730
|
);
|
|
2731
|
+
const claudeCommandPaths = claudeCommands().map((command) =>
|
|
2732
|
+
join(claudeHome(), "commands", command.filename)
|
|
2733
|
+
);
|
|
2734
|
+
const hasClaudeCommands = claudeCommandPaths.every((commandPath) =>
|
|
2735
|
+
existsSync(commandPath)
|
|
2736
|
+
);
|
|
2737
|
+
checks.push(
|
|
2738
|
+
warningCheck(
|
|
2739
|
+
hasClaudeCommands,
|
|
2740
|
+
hasClaudeCommands
|
|
2741
|
+
? "Claude slash commands present"
|
|
2742
|
+
: "Claude slash commands missing"
|
|
2743
|
+
)
|
|
2744
|
+
);
|
|
2745
|
+
const claudeCommandsHaveCurrentNames = claudeCommands().every((command) => {
|
|
2746
|
+
const commandPath = join(claudeHome(), "commands", command.filename);
|
|
2747
|
+
if (!existsSync(commandPath)) return false;
|
|
2748
|
+
const content = readFileSync(commandPath, "utf8");
|
|
2749
|
+
return content.includes(`name: sellable:${command.name}`);
|
|
2750
|
+
});
|
|
2751
|
+
checks.push(
|
|
2752
|
+
warningCheck(
|
|
2753
|
+
claudeCommandsHaveCurrentNames,
|
|
2754
|
+
claudeCommandsHaveCurrentNames
|
|
2755
|
+
? "Claude slash command names current"
|
|
2756
|
+
: "Claude slash command names stale"
|
|
2757
|
+
)
|
|
2758
|
+
);
|
|
2472
2759
|
const legacyClaudePaths = [
|
|
2473
2760
|
...legacyClaudeCustomAgents().map((agent) =>
|
|
2474
2761
|
join(claudeHome(), "agents", agent.filename)
|
|
@@ -2504,6 +2791,25 @@ async function verify(opts) {
|
|
|
2504
2791
|
: "Claude watch mode driver missing"
|
|
2505
2792
|
)
|
|
2506
2793
|
);
|
|
2794
|
+
let claudeMcpEntriesCanonical = false;
|
|
2795
|
+
try {
|
|
2796
|
+
const config = claudeConfigContent ? JSON.parse(claudeConfigContent) : {};
|
|
2797
|
+
const servers = collectClaudeSellableServers(config);
|
|
2798
|
+
const canonical = canonicalClaudeMcpServer(opts);
|
|
2799
|
+
claudeMcpEntriesCanonical =
|
|
2800
|
+
servers.length > 0 &&
|
|
2801
|
+
servers.every((server) => claudeMcpServerMatches(server, canonical));
|
|
2802
|
+
} catch {
|
|
2803
|
+
claudeMcpEntriesCanonical = false;
|
|
2804
|
+
}
|
|
2805
|
+
checks.push(
|
|
2806
|
+
warningCheck(
|
|
2807
|
+
claudeMcpEntriesCanonical,
|
|
2808
|
+
claudeMcpEntriesCanonical
|
|
2809
|
+
? "Claude Sellable MCP entries canonical"
|
|
2810
|
+
: "Claude Sellable MCP entries stale"
|
|
2811
|
+
)
|
|
2812
|
+
);
|
|
2507
2813
|
}
|
|
2508
2814
|
if (opts.host === "codex" || opts.host === "all") {
|
|
2509
2815
|
const hasCodexCli = commandExists("codex");
|
|
@@ -2886,7 +3192,9 @@ function printNextSteps(installedHosts, authReused) {
|
|
|
2886
3192
|
console.log(` ${C.green}✓${C.reset} Codex custom agents installed`);
|
|
2887
3193
|
}
|
|
2888
3194
|
if (hasClaude) {
|
|
2889
|
-
console.log(
|
|
3195
|
+
console.log(
|
|
3196
|
+
` ${C.green}✓${C.reset} Claude slash commands and custom agents installed`
|
|
3197
|
+
);
|
|
2890
3198
|
}
|
|
2891
3199
|
if (authReused) {
|
|
2892
3200
|
console.log(
|
|
@@ -3200,6 +3508,45 @@ async function main() {
|
|
|
3200
3508
|
console.log(` Codex: $sellable:create-post`);
|
|
3201
3509
|
process.exit(0);
|
|
3202
3510
|
}
|
|
3511
|
+
if (rawArgs[0] === "prefs") {
|
|
3512
|
+
const dryRun = rawArgs.includes("--dry-run");
|
|
3513
|
+
const args = rawArgs.filter((arg) => arg !== "--dry-run");
|
|
3514
|
+
if (
|
|
3515
|
+
args[1] !== "set" ||
|
|
3516
|
+
args[2] !== "prompt-sharing" ||
|
|
3517
|
+
(args[3] !== "true" && args[3] !== "false") ||
|
|
3518
|
+
args.length !== 4
|
|
3519
|
+
) {
|
|
3520
|
+
console.error(
|
|
3521
|
+
"Usage: sellable prefs set prompt-sharing true|false [--dry-run]"
|
|
3522
|
+
);
|
|
3523
|
+
process.exit(2);
|
|
3524
|
+
}
|
|
3525
|
+
const envPath = writePromptSharingPreference(args[3], { dryRun });
|
|
3526
|
+
console.log(
|
|
3527
|
+
`✓ Sellable prompt sharing preference set to ${args[3]} at ${envPath}`
|
|
3528
|
+
);
|
|
3529
|
+
process.exit(0);
|
|
3530
|
+
}
|
|
3531
|
+
if (rawArgs[0] === "setup") {
|
|
3532
|
+
const dryRun = rawArgs.includes("--dry-run");
|
|
3533
|
+
const args = rawArgs.filter((arg) => arg !== "--dry-run");
|
|
3534
|
+
if (args[1] !== "claude-permissions") {
|
|
3535
|
+
console.error(
|
|
3536
|
+
`Unknown setup subcommand: ${args[1] ?? "(none)"}\nKnown: claude-permissions`
|
|
3537
|
+
);
|
|
3538
|
+
process.exit(2);
|
|
3539
|
+
}
|
|
3540
|
+
if (!args.includes("--allow-common-setup")) {
|
|
3541
|
+
console.error(
|
|
3542
|
+
"Usage: sellable setup claude-permissions --allow-common-setup [--dry-run]"
|
|
3543
|
+
);
|
|
3544
|
+
process.exit(2);
|
|
3545
|
+
}
|
|
3546
|
+
const settingsPath = allowClaudeCommonSetup({ dryRun });
|
|
3547
|
+
console.log(`✓ Claude Sellable permissions updated at ${settingsPath}`);
|
|
3548
|
+
process.exit(0);
|
|
3549
|
+
}
|
|
3203
3550
|
if (rawArgs[0] === "uninstall") {
|
|
3204
3551
|
runUninstall();
|
|
3205
3552
|
process.exit(0);
|
package/package.json
CHANGED
|
@@ -179,11 +179,17 @@ person/company this campaign is for, then I’ll turn that into a campaign brief
|
|
|
179
179
|
before we move into lead sourcing.
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
-
Exception: if `bootstrap_create_campaign.modelQuality.status === "warn"
|
|
183
|
-
|
|
182
|
+
Exception: if `bootstrap_create_campaign.modelQuality.status === "warn"` and
|
|
183
|
+
`bootstrap_create_campaign.modelQuality.metadataStale !== true`, the first
|
|
184
|
+
visible campaign message must be the model-quality warning from
|
|
184
185
|
`modelQuality.message`. Ask the user to switch to the configured minimum model
|
|
185
186
|
or explicitly continue anyway before identity setup, research, lead filtering,
|
|
186
|
-
message generation, or launch review.
|
|
187
|
+
message generation, or launch review. If `metadataStale === true`, continue
|
|
188
|
+
normally and do not ask the user to switch.
|
|
189
|
+
|
|
190
|
+
If `bootstrap_create_campaign.modelQuality.metadataStale === true`, continue
|
|
191
|
+
normally. Do not ask the user to switch models; this is an accepted Codex host
|
|
192
|
+
metadata mismatch.
|
|
187
193
|
|
|
188
194
|
If a linked/local skill file is stale or missing, silently use the installed
|
|
189
195
|
`sellable@sellable` plugin copy. Do not tell the user about the stale link,
|
|
@@ -935,8 +941,10 @@ updates.
|
|
|
935
941
|
6. Call `mcp__sellable__bootstrap_create_campaign({ flowVersion: "v2", campaignId?, host?, model?, reasoningEffort? })`.
|
|
936
942
|
Pass the current host, model, and reasoning when the host exposes them.
|
|
937
943
|
7. If `safeToProceed !== true`, stop and show `blockingErrors` + `nextStep`.
|
|
938
|
-
8. If `modelQuality.status === "warn"
|
|
939
|
-
setup/research and wait for the user
|
|
944
|
+
8. If `modelQuality.status === "warn"` and `modelQuality.metadataStale !== true`,
|
|
945
|
+
show `modelQuality.message` before any setup/research and wait for the user
|
|
946
|
+
to switch or explicitly continue. If `metadataStale === true`, continue
|
|
947
|
+
normally and do not tell the user to switch.
|
|
940
948
|
|
|
941
949
|
## Execute Workflow
|
|
942
950
|
|