@sellable/install 0.1.326 → 0.1.328
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 +54 -16
- package/lib/codex-plugin-selection.mjs +93 -4
- package/package.json +1 -1
package/bin/sellable-install.mjs
CHANGED
|
@@ -48,7 +48,7 @@ function getInstallVersion() {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const CODEX_PLUGIN_VERSION = "0.1.
|
|
51
|
+
const CODEX_PLUGIN_VERSION = "0.1.58";
|
|
52
52
|
const CODEX_PLUGIN_COMPAT_VERSIONS = [
|
|
53
53
|
"0.1.8",
|
|
54
54
|
"0.1.9",
|
|
@@ -2064,7 +2064,41 @@ installPackageVersion: ${getInstallVersion()}
|
|
|
2064
2064
|
codexPluginVersion: ${CODEX_PLUGIN_VERSION}
|
|
2065
2065
|
<!-- REFILL_CONTRACT_GENERATED:END -->`;
|
|
2066
2066
|
|
|
2067
|
-
function
|
|
2067
|
+
function refillReleaseIdentityBlock(host, opts = {}) {
|
|
2068
|
+
const match = /^@sellable\/mcp@(.+)$/.exec(opts.mcpPackage || "");
|
|
2069
|
+
const mcpVersion = match?.[1] || String(opts.mcpPackage || "unknown");
|
|
2070
|
+
const pluginVersion = host === "codex" ? CODEX_PLUGIN_VERSION : "not-applicable";
|
|
2071
|
+
const cacheIdentity =
|
|
2072
|
+
host === "codex"
|
|
2073
|
+
? `codex-plugin:${pluginVersion}|mcp:${mcpVersion}`
|
|
2074
|
+
: `${host}-host|mcp:${mcpVersion}`;
|
|
2075
|
+
return `## Installed release identity
|
|
2076
|
+
|
|
2077
|
+
expectedMcpPackageVersion: ${mcpVersion}
|
|
2078
|
+
expectedInstallPackageVersion: ${getInstallVersion()}
|
|
2079
|
+
expectedCodexPluginVersion: ${pluginVersion}
|
|
2080
|
+
expectedPluginCacheIdentity: ${cacheIdentity}
|
|
2081
|
+
|
|
2082
|
+
Immediately after \`get_auth_status\`, require
|
|
2083
|
+
\`update.mcp.currentVersion\` to equal \`${mcpVersion}\`. If it is missing or
|
|
2084
|
+
different, stop with \`installed_mcp_identity_mismatch\` before any product mutation.
|
|
2085
|
+
Do not call \`get_refill_target_plan\`, \`refill_sends\`, or another mutating
|
|
2086
|
+
workflow tool. Explain that Codex is still using a stale Sellable MCP runtime,
|
|
2087
|
+
then fully quit and reopen Codex Desktop and start a new thread.`;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
function stampRefillReleaseIdentity(markdown, host, opts) {
|
|
2091
|
+
const marker = markdown.includes("## Bootstrap and execute")
|
|
2092
|
+
? "## Bootstrap and execute"
|
|
2093
|
+
: "## Bootstrap";
|
|
2094
|
+
if (!markdown.includes(marker)) return markdown;
|
|
2095
|
+
return markdown.replace(
|
|
2096
|
+
marker,
|
|
2097
|
+
`${refillReleaseIdentityBlock(host, opts)}\n\n${marker}`
|
|
2098
|
+
);
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
function refillSendsSkillMd(host = "shared", opts = {}) {
|
|
2068
2102
|
try {
|
|
2069
2103
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
2070
2104
|
const templatePath = join(
|
|
@@ -2078,9 +2112,13 @@ function refillSendsSkillMd(host = "shared") {
|
|
|
2078
2112
|
existsSync(templatePath)
|
|
2079
2113
|
) {
|
|
2080
2114
|
return stampInstalledHost(
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2115
|
+
stampRefillReleaseIdentity(
|
|
2116
|
+
addOrReplaceAllowedTools(
|
|
2117
|
+
readFileSync(templatePath, "utf8"),
|
|
2118
|
+
REFILL_SENDS_ALLOWED_TOOLS
|
|
2119
|
+
),
|
|
2120
|
+
host,
|
|
2121
|
+
opts
|
|
2084
2122
|
),
|
|
2085
2123
|
host,
|
|
2086
2124
|
"refill-sends"
|
|
@@ -2089,7 +2127,7 @@ function refillSendsSkillMd(host = "shared") {
|
|
|
2089
2127
|
} catch {
|
|
2090
2128
|
// fall through to hardcoded fallback below
|
|
2091
2129
|
}
|
|
2092
|
-
return stampInstalledHost(`---
|
|
2130
|
+
return stampInstalledHost(stampRefillReleaseIdentity(`---
|
|
2093
2131
|
name: refill-sends
|
|
2094
2132
|
description: Refill Sellable sender sends through the approval-gated refill workflow.
|
|
2095
2133
|
visibility: public
|
|
@@ -2156,7 +2194,7 @@ Desktop, then start a new thread.
|
|
|
2156
2194
|
If exact subskill lookup fails, use
|
|
2157
2195
|
\`mcp__sellable__search_subskill_prompts({ query: "refill-sends", includePublic: true, includeInternal: true })\`,
|
|
2158
2196
|
then retry \`get_subskill_prompt\`.
|
|
2159
|
-
`, host, "refill-sends");
|
|
2197
|
+
`, host, opts), host, "refill-sends");
|
|
2160
2198
|
}
|
|
2161
2199
|
|
|
2162
2200
|
function refreshSenderEngagementSkillMd(host = "shared") {
|
|
@@ -2668,7 +2706,7 @@ will change or after they approve the settings update.
|
|
|
2668
2706
|
`;
|
|
2669
2707
|
}
|
|
2670
2708
|
|
|
2671
|
-
function codexPluginSkills() {
|
|
2709
|
+
function codexPluginSkills(opts = {}) {
|
|
2672
2710
|
return [
|
|
2673
2711
|
{
|
|
2674
2712
|
dir: "sellable-create-campaign",
|
|
@@ -2717,7 +2755,7 @@ function codexPluginSkills() {
|
|
|
2717
2755
|
dir: "sellable-refill-sends",
|
|
2718
2756
|
displayName: "Sellable Refill Sends",
|
|
2719
2757
|
description: "Refill sender sends with approval-gated campaign selection",
|
|
2720
|
-
skillMd: refillSendsSkillMd("codex"),
|
|
2758
|
+
skillMd: refillSendsSkillMd("codex", opts),
|
|
2721
2759
|
},
|
|
2722
2760
|
];
|
|
2723
2761
|
}
|
|
@@ -2908,7 +2946,7 @@ then retry \`get_subskill_prompt\`.
|
|
|
2908
2946
|
);
|
|
2909
2947
|
}
|
|
2910
2948
|
|
|
2911
|
-
function hermesSkillDefinitions() {
|
|
2949
|
+
function hermesSkillDefinitions(opts = {}) {
|
|
2912
2950
|
return [
|
|
2913
2951
|
{
|
|
2914
2952
|
commandName: "create-campaign",
|
|
@@ -2965,7 +3003,7 @@ function hermesSkillDefinitions() {
|
|
|
2965
3003
|
commandName: "refill-sends",
|
|
2966
3004
|
dir: hermesCommandName("refill-sends"),
|
|
2967
3005
|
skillMd: normalizeHermesSkillMarkdown(
|
|
2968
|
-
refillSendsSkillMd("hermes"),
|
|
3006
|
+
refillSendsSkillMd("hermes", opts),
|
|
2969
3007
|
"refill-sends"
|
|
2970
3008
|
),
|
|
2971
3009
|
},
|
|
@@ -3158,7 +3196,7 @@ ${stripFrontmatter(command.skillMd)}
|
|
|
3158
3196
|
`;
|
|
3159
3197
|
}
|
|
3160
3198
|
|
|
3161
|
-
function claudeCommands() {
|
|
3199
|
+
function claudeCommands(opts = {}) {
|
|
3162
3200
|
return [
|
|
3163
3201
|
{
|
|
3164
3202
|
name: "create-campaign",
|
|
@@ -3214,7 +3252,7 @@ function claudeCommands() {
|
|
|
3214
3252
|
description:
|
|
3215
3253
|
"Refill Sellable sender sends through the approval-gated refill workflow.",
|
|
3216
3254
|
argumentHint: "[--yolo] [--sender name-or-id ...]",
|
|
3217
|
-
skillMd: refillSendsSkillMd("claude"),
|
|
3255
|
+
skillMd: refillSendsSkillMd("claude", opts),
|
|
3218
3256
|
allowedTools: ["AskUserQuestion", ...REFILL_SENDS_ALLOWED_TOOLS],
|
|
3219
3257
|
},
|
|
3220
3258
|
{
|
|
@@ -3239,7 +3277,7 @@ function claudeCommands() {
|
|
|
3239
3277
|
}
|
|
3240
3278
|
|
|
3241
3279
|
function writeCodexPluginSkills(pluginRoot, opts) {
|
|
3242
|
-
const skills = codexPluginSkills();
|
|
3280
|
+
const skills = codexPluginSkills(opts);
|
|
3243
3281
|
const currentSkillDirs = new Set(skills.map((skill) => skill.dir));
|
|
3244
3282
|
const skillsRoot = join(pluginRoot, "skills");
|
|
3245
3283
|
|
|
@@ -3303,7 +3341,7 @@ function writeClaudeCustomAgents(opts) {
|
|
|
3303
3341
|
|
|
3304
3342
|
function writeClaudeCommands(opts) {
|
|
3305
3343
|
const home = claudeHome();
|
|
3306
|
-
for (const command of claudeCommands()) {
|
|
3344
|
+
for (const command of claudeCommands(opts)) {
|
|
3307
3345
|
writeFile(join(home, "commands", command.filename), command.content, opts);
|
|
3308
3346
|
}
|
|
3309
3347
|
}
|
|
@@ -3815,7 +3853,7 @@ function writeHermesMcpServer(opts) {
|
|
|
3815
3853
|
|
|
3816
3854
|
function installHermesSkills(opts) {
|
|
3817
3855
|
const root = hermesSkillsRoot(opts);
|
|
3818
|
-
for (const skill of hermesSkillDefinitions()) {
|
|
3856
|
+
for (const skill of hermesSkillDefinitions(opts)) {
|
|
3819
3857
|
const skillRoot = join(root, skill.dir);
|
|
3820
3858
|
writeFile(join(skillRoot, "SKILL.md"), skill.skillMd, opts);
|
|
3821
3859
|
if (skill.soulMd) {
|
|
@@ -86,6 +86,54 @@ function treeHash(root) {
|
|
|
86
86
|
return canonicalDigest(treeRecords(root));
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
function quoteToml(value) {
|
|
90
|
+
return JSON.stringify(String(value));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function removeTomlTable(content, header) {
|
|
94
|
+
const lines = content.split("\n");
|
|
95
|
+
const target = `[${header}]`;
|
|
96
|
+
const kept = [];
|
|
97
|
+
let skipping = false;
|
|
98
|
+
for (const line of lines) {
|
|
99
|
+
const trimmed = line.trim();
|
|
100
|
+
if (/^\[[^\]]+\]$/.test(trimmed)) {
|
|
101
|
+
skipping = trimmed === target;
|
|
102
|
+
if (skipping) continue;
|
|
103
|
+
}
|
|
104
|
+
if (!skipping) kept.push(line);
|
|
105
|
+
}
|
|
106
|
+
return kept.join("\n");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function candidateCodexConfig(content, candidatePath) {
|
|
110
|
+
const mcpPath = join(candidatePath, ".mcp.json");
|
|
111
|
+
if (!existsSync(mcpPath)) throw new Error("codex_candidate_mcp_manifest_missing");
|
|
112
|
+
const manifest = JSON.parse(readFileSync(mcpPath, "utf8"));
|
|
113
|
+
const server = manifest?.mcpServers?.sellable;
|
|
114
|
+
if (
|
|
115
|
+
server?.type !== "stdio" ||
|
|
116
|
+
typeof server.command !== "string" ||
|
|
117
|
+
!Array.isArray(server.args) ||
|
|
118
|
+
server.args.some((value) => typeof value !== "string") ||
|
|
119
|
+
!server.env ||
|
|
120
|
+
typeof server.env !== "object" ||
|
|
121
|
+
Object.values(server.env).some((value) => typeof value !== "string")
|
|
122
|
+
) {
|
|
123
|
+
throw new Error("codex_candidate_mcp_manifest_invalid");
|
|
124
|
+
}
|
|
125
|
+
let next = removeTomlTable(content, "mcp_servers.sellable.env");
|
|
126
|
+
next = removeTomlTable(next, "mcp_servers.sellable").trimEnd();
|
|
127
|
+
const args = `[${server.args.map(quoteToml).join(", ")}]`;
|
|
128
|
+
const env = Object.entries(server.env)
|
|
129
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
130
|
+
.map(([key, value]) => `${key} = ${quoteToml(value)}`)
|
|
131
|
+
.join("\n");
|
|
132
|
+
return `${next}\n\n[mcp_servers.sellable]\ncommand = ${quoteToml(
|
|
133
|
+
server.command
|
|
134
|
+
)}\nargs = ${args}\n\n[mcp_servers.sellable.env]\n${env}\n`;
|
|
135
|
+
}
|
|
136
|
+
|
|
89
137
|
function pluginVersion(pluginRoot) {
|
|
90
138
|
const manifestPath = join(pluginRoot, ".codex-plugin", "plugin.json");
|
|
91
139
|
if (!existsSync(manifestPath)) throw new Error("codex_selection_plugin_manifest_missing");
|
|
@@ -240,6 +288,20 @@ function atomicReplaceTree(source, target) {
|
|
|
240
288
|
}
|
|
241
289
|
}
|
|
242
290
|
|
|
291
|
+
function atomicReplaceFile(content, target) {
|
|
292
|
+
const parent = dirname(target);
|
|
293
|
+
mkdirSync(parent, { recursive: true });
|
|
294
|
+
const staging = join(parent, `.${basename(target)}.phase98-next-${process.pid}`);
|
|
295
|
+
const mode = existsSync(target) ? lstatSync(target).mode & 0o777 : 0o600;
|
|
296
|
+
writeFileSync(staging, content, { mode });
|
|
297
|
+
try {
|
|
298
|
+
renameSync(staging, target);
|
|
299
|
+
} catch (error) {
|
|
300
|
+
rmSync(staging, { force: true });
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
243
305
|
export function activateCodexCandidateSelection(input) {
|
|
244
306
|
const {
|
|
245
307
|
paths,
|
|
@@ -271,10 +333,30 @@ export function activateCodexCandidateSelection(input) {
|
|
|
271
333
|
if (!existsSync(candidatePath) || treeHash(candidatePath) !== expectedCandidateHash) {
|
|
272
334
|
throw new Error("codex_selection_candidate_cache_mismatch");
|
|
273
335
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
336
|
+
const candidateConfig = candidateCodexConfig(
|
|
337
|
+
readFileSync(paths.configPath, "utf8"),
|
|
338
|
+
candidatePath
|
|
339
|
+
);
|
|
340
|
+
const configBackup = snapshot.items.find((item) => item.name === "config");
|
|
341
|
+
const pluginBackup = snapshot.items.find((item) => item.name === "pluginRoot");
|
|
342
|
+
if (!configBackup || !pluginBackup) {
|
|
343
|
+
throw new Error("codex_selection_snapshot_restore_items_missing");
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
atomicReplaceFile(candidateConfig, paths.configPath);
|
|
347
|
+
atomicReplaceTree(candidatePath, paths.pluginRoot);
|
|
348
|
+
if (
|
|
349
|
+
pluginVersion(paths.pluginRoot) !== candidateVersion ||
|
|
350
|
+
treeHash(paths.pluginRoot) !== expectedCandidateHash
|
|
351
|
+
) {
|
|
352
|
+
throw new Error("codex_selection_candidate_activation_mismatch");
|
|
353
|
+
}
|
|
354
|
+
} catch (error) {
|
|
355
|
+
copyExact(configBackup.backupPath, configBackup.sourcePath);
|
|
356
|
+
copyExact(pluginBackup.backupPath, pluginBackup.sourcePath);
|
|
357
|
+
throw error;
|
|
277
358
|
}
|
|
359
|
+
const candidateConfigHash = treeHash(paths.configPath);
|
|
278
360
|
const activation = {
|
|
279
361
|
schemaVersion: CODEX_PLUGIN_SELECTION_SCHEMA_VERSION,
|
|
280
362
|
approvalId,
|
|
@@ -285,7 +367,8 @@ export function activateCodexCandidateSelection(input) {
|
|
|
285
367
|
stableVersion: snapshot.pluginVersion,
|
|
286
368
|
candidateVersion,
|
|
287
369
|
candidateHash: expectedCandidateHash,
|
|
288
|
-
|
|
370
|
+
candidateConfigHash,
|
|
371
|
+
changedPaths: [paths.pluginRoot, paths.configPath],
|
|
289
372
|
};
|
|
290
373
|
const activationPath = join(dirname(snapshotManifestPath), "activation.json");
|
|
291
374
|
writeFileSync(activationPath, `${JSON.stringify(activation, null, 2)}\n`, { mode: 0o600 });
|
|
@@ -310,6 +393,12 @@ export function restoreCodexPluginSelection(input) {
|
|
|
310
393
|
activation.snapshotHash !== expectedSnapshotHash ||
|
|
311
394
|
activation.candidateHash !== treeHash(paths.pluginRoot)
|
|
312
395
|
) throw new Error("codex_selection_active_candidate_drift");
|
|
396
|
+
if (
|
|
397
|
+
!activation.candidateConfigHash ||
|
|
398
|
+
activation.candidateConfigHash !== treeHash(paths.configPath)
|
|
399
|
+
) {
|
|
400
|
+
throw new Error("codex_selection_active_config_drift");
|
|
401
|
+
}
|
|
313
402
|
if (!recoveryReason) {
|
|
314
403
|
if (!taskEvidence?.taskId || taskEvidence.resolvedCandidateHash !== activation.candidateHash) {
|
|
315
404
|
throw new Error("codex_selection_task_identity_mismatch");
|