@sellable/install 0.1.212 → 0.1.213
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 +98 -6
- package/lib/runtime-verify.mjs +92 -1
- package/package.json +1 -1
package/bin/sellable-install.mjs
CHANGED
|
@@ -330,10 +330,6 @@ function isWindowsPlatform(platform = process.platform) {
|
|
|
330
330
|
return platform === "win32";
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
-
function packageRunnerCommand(platform = process.platform) {
|
|
334
|
-
return isWindowsPlatform(platform) ? "npx.cmd" : "npx";
|
|
335
|
-
}
|
|
336
|
-
|
|
337
333
|
function packageManagerCommand(platform = process.platform) {
|
|
338
334
|
return isWindowsPlatform(platform) ? "npm.cmd" : "npm";
|
|
339
335
|
}
|
|
@@ -551,6 +547,14 @@ function upsertTomlTable(content, tableName, block) {
|
|
|
551
547
|
return `${content.trimEnd()}\n\n${normalizedBlock}\n`;
|
|
552
548
|
}
|
|
553
549
|
|
|
550
|
+
function readTomlTable(content, tableName) {
|
|
551
|
+
const tablePattern = new RegExp(
|
|
552
|
+
`(^|\\n)\\[${escapeRegExp(tableName)}\\]\\n([\\s\\S]*?)(?=\\n\\[[^\\n]+\\]|$)`
|
|
553
|
+
);
|
|
554
|
+
const match = content.match(tablePattern);
|
|
555
|
+
return match ? match[2] : "";
|
|
556
|
+
}
|
|
557
|
+
|
|
554
558
|
function upsertTomlBoolean(content, tableName, key, value) {
|
|
555
559
|
const line = `${key} = ${value ? "true" : "false"}`;
|
|
556
560
|
const tablePattern = new RegExp(
|
|
@@ -678,12 +682,13 @@ function codexPluginMcp(opts) {
|
|
|
678
682
|
};
|
|
679
683
|
}
|
|
680
684
|
|
|
685
|
+
const [command, args] = mcpCommand(opts);
|
|
681
686
|
return {
|
|
682
687
|
mcpServers: {
|
|
683
688
|
sellable: {
|
|
684
689
|
type: "stdio",
|
|
685
|
-
command
|
|
686
|
-
args
|
|
690
|
+
command,
|
|
691
|
+
args,
|
|
687
692
|
env: {
|
|
688
693
|
SELLABLE_WATCH_MODE_DRIVER: "codex",
|
|
689
694
|
},
|
|
@@ -2386,6 +2391,7 @@ enabled = false`
|
|
|
2386
2391
|
"default_mode_request_user_input",
|
|
2387
2392
|
true
|
|
2388
2393
|
);
|
|
2394
|
+
content = upsertCodexMcpServerConfig(content, opts);
|
|
2389
2395
|
content = upsertTomlTable(
|
|
2390
2396
|
content,
|
|
2391
2397
|
"agents",
|
|
@@ -2544,6 +2550,68 @@ function codexMcpAddArgs(opts) {
|
|
|
2544
2550
|
];
|
|
2545
2551
|
}
|
|
2546
2552
|
|
|
2553
|
+
function upsertCodexMcpServerConfig(content, opts) {
|
|
2554
|
+
if (opts.server === "hosted") {
|
|
2555
|
+
content = upsertTomlTable(
|
|
2556
|
+
content,
|
|
2557
|
+
"mcp_servers.sellable",
|
|
2558
|
+
`[mcp_servers.sellable]
|
|
2559
|
+
url = ${quoteToml(withHostedWatchModeDriver(opts.hostedUrl, "codex"))}`
|
|
2560
|
+
);
|
|
2561
|
+
content = removeTomlSection(content, "mcp_servers.sellable.env");
|
|
2562
|
+
return content;
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
const [command, args] = mcpCommand(opts);
|
|
2566
|
+
content = upsertTomlTable(
|
|
2567
|
+
content,
|
|
2568
|
+
"mcp_servers.sellable",
|
|
2569
|
+
`[mcp_servers.sellable]
|
|
2570
|
+
command = ${quoteToml(command)}
|
|
2571
|
+
args = ${tomlArray(args)}`
|
|
2572
|
+
);
|
|
2573
|
+
content = upsertTomlTable(
|
|
2574
|
+
content,
|
|
2575
|
+
"mcp_servers.sellable.env",
|
|
2576
|
+
`[mcp_servers.sellable.env]
|
|
2577
|
+
SELLABLE_WATCH_MODE_DRIVER = "codex"`
|
|
2578
|
+
);
|
|
2579
|
+
return content;
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
function codexMcpServerMatches(content, opts) {
|
|
2583
|
+
const server = readTomlTable(content, "mcp_servers.sellable");
|
|
2584
|
+
if (!server) return false;
|
|
2585
|
+
if (opts.server === "hosted") {
|
|
2586
|
+
return (
|
|
2587
|
+
server.includes(
|
|
2588
|
+
`url = ${quoteToml(withHostedWatchModeDriver(opts.hostedUrl, "codex"))}`
|
|
2589
|
+
) &&
|
|
2590
|
+
!/\bcommand\s*=/.test(server) &&
|
|
2591
|
+
!/\bargs\s*=/.test(server)
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
const [command, args] = mcpCommand(opts);
|
|
2596
|
+
const env = readTomlTable(content, "mcp_servers.sellable.env");
|
|
2597
|
+
return (
|
|
2598
|
+
server.includes(`command = ${quoteToml(command)}`) &&
|
|
2599
|
+
server.includes(`args = ${tomlArray(args)}`) &&
|
|
2600
|
+
env.includes('SELLABLE_WATCH_MODE_DRIVER = "codex"')
|
|
2601
|
+
);
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
function codexPluginMcpServerMatches(content, opts) {
|
|
2605
|
+
if (!content) return false;
|
|
2606
|
+
try {
|
|
2607
|
+
const actual = JSON.parse(content)?.mcpServers?.sellable;
|
|
2608
|
+
const expected = codexPluginMcp(opts).mcpServers.sellable;
|
|
2609
|
+
return JSON.stringify(actual) === JSON.stringify(expected);
|
|
2610
|
+
} catch {
|
|
2611
|
+
return false;
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2547
2615
|
function installClaude(opts) {
|
|
2548
2616
|
if (!commandExists("claude")) {
|
|
2549
2617
|
const message =
|
|
@@ -3027,6 +3095,30 @@ async function verify(opts) {
|
|
|
3027
3095
|
: "Codex Desktop plugin watch mode driver missing"
|
|
3028
3096
|
)
|
|
3029
3097
|
);
|
|
3098
|
+
const codexMcpEntriesCanonical = codexMcpServerMatches(
|
|
3099
|
+
configContent,
|
|
3100
|
+
opts
|
|
3101
|
+
);
|
|
3102
|
+
checks.push(
|
|
3103
|
+
warningCheck(
|
|
3104
|
+
codexMcpEntriesCanonical,
|
|
3105
|
+
codexMcpEntriesCanonical
|
|
3106
|
+
? "Codex CLI Sellable MCP entry canonical"
|
|
3107
|
+
: "Codex CLI Sellable MCP entry stale"
|
|
3108
|
+
)
|
|
3109
|
+
);
|
|
3110
|
+
const codexPluginMcpEntryCanonical = codexPluginMcpServerMatches(
|
|
3111
|
+
pluginMcpContent,
|
|
3112
|
+
opts
|
|
3113
|
+
);
|
|
3114
|
+
checks.push(
|
|
3115
|
+
warningCheck(
|
|
3116
|
+
codexPluginMcpEntryCanonical,
|
|
3117
|
+
codexPluginMcpEntryCanonical
|
|
3118
|
+
? "Codex Desktop plugin Sellable MCP entry canonical"
|
|
3119
|
+
: "Codex Desktop plugin Sellable MCP entry stale"
|
|
3120
|
+
)
|
|
3121
|
+
);
|
|
3030
3122
|
const hasCodexAgentRegistrations = codexCustomAgents().every((agent) =>
|
|
3031
3123
|
configContent.includes(`[agents.${agent.name}]`)
|
|
3032
3124
|
);
|
package/lib/runtime-verify.mjs
CHANGED
|
@@ -3,6 +3,8 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
|
3
3
|
|
|
4
4
|
const MAX_STDERR_LINES = 80;
|
|
5
5
|
const DEFAULT_VERIFY_TIMEOUT_MS = 10_000;
|
|
6
|
+
const DEFAULT_VERIFY_ATTEMPTS = 2;
|
|
7
|
+
const DEFAULT_VERIFY_RETRY_DELAY_MS = 750;
|
|
6
8
|
|
|
7
9
|
export const REQUIRED_SELLABLE_MCP_TOOLS = [
|
|
8
10
|
"get_auth_status",
|
|
@@ -125,6 +127,15 @@ export function missingRequiredTools(tools, requiredTools) {
|
|
|
125
127
|
return requiredTools.filter((name) => !available.has(name));
|
|
126
128
|
}
|
|
127
129
|
|
|
130
|
+
function sleep(ms) {
|
|
131
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function numberFromEnv(name, fallback) {
|
|
135
|
+
const parsed = Number(process.env[name] || "");
|
|
136
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
137
|
+
}
|
|
138
|
+
|
|
128
139
|
function textFromToolResult(result) {
|
|
129
140
|
return (result?.content || [])
|
|
130
141
|
.filter((part) => part?.type === "text" && typeof part.text === "string")
|
|
@@ -291,7 +302,39 @@ async function verifyCreateCampaignSmoke({
|
|
|
291
302
|
};
|
|
292
303
|
}
|
|
293
304
|
|
|
294
|
-
|
|
305
|
+
function summarizeAttempt(result, attempt) {
|
|
306
|
+
return {
|
|
307
|
+
attempt,
|
|
308
|
+
ok: result.ok === true,
|
|
309
|
+
availableToolCount: result.availableTools?.length || 0,
|
|
310
|
+
missingToolCount: result.missingTools?.length || 0,
|
|
311
|
+
error: result.error || null,
|
|
312
|
+
createCampaignSmoke: result.createCampaignSmoke
|
|
313
|
+
? {
|
|
314
|
+
ok: result.createCampaignSmoke.ok === true,
|
|
315
|
+
error: result.createCampaignSmoke.error || null,
|
|
316
|
+
missingTools: result.createCampaignSmoke.missingTools || [],
|
|
317
|
+
calls: (result.createCampaignSmoke.calls || []).map((call) => ({
|
|
318
|
+
name: call.name,
|
|
319
|
+
ok: call.ok === true,
|
|
320
|
+
})),
|
|
321
|
+
}
|
|
322
|
+
: null,
|
|
323
|
+
stderrTail: result.stderrTail || [],
|
|
324
|
+
startedAt: result.startedAt,
|
|
325
|
+
completedAt: result.completedAt,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function shouldRetryRuntimeVerification(result, requiredTools) {
|
|
330
|
+
if (result.ok || result.skipped) return false;
|
|
331
|
+
if (result.error) return true;
|
|
332
|
+
const availableToolCount = result.availableTools?.length || 0;
|
|
333
|
+
const missingToolCount = result.missingTools?.length || 0;
|
|
334
|
+
return availableToolCount === 0 || missingToolCount === requiredTools.length;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function verifySellableMcpRuntimeOnce({
|
|
295
338
|
command,
|
|
296
339
|
args = [],
|
|
297
340
|
cwd = process.cwd(),
|
|
@@ -389,3 +432,51 @@ export async function verifySellableMcpRuntime({
|
|
|
389
432
|
completedAt: new Date().toISOString(),
|
|
390
433
|
};
|
|
391
434
|
}
|
|
435
|
+
|
|
436
|
+
export async function verifySellableMcpRuntime({
|
|
437
|
+
command,
|
|
438
|
+
args = [],
|
|
439
|
+
cwd = process.cwd(),
|
|
440
|
+
env = process.env,
|
|
441
|
+
requiredTools = REQUIRED_SELLABLE_MCP_TOOLS,
|
|
442
|
+
timeoutMs = Number(process.env.SELLABLE_VERIFY_TIMEOUT_MS || "") ||
|
|
443
|
+
DEFAULT_VERIFY_TIMEOUT_MS,
|
|
444
|
+
attempts = numberFromEnv("SELLABLE_VERIFY_ATTEMPTS", DEFAULT_VERIFY_ATTEMPTS),
|
|
445
|
+
retryDelayMs = numberFromEnv(
|
|
446
|
+
"SELLABLE_VERIFY_RETRY_DELAY_MS",
|
|
447
|
+
DEFAULT_VERIFY_RETRY_DELAY_MS
|
|
448
|
+
),
|
|
449
|
+
}) {
|
|
450
|
+
const attemptSummaries = [];
|
|
451
|
+
let lastResult = null;
|
|
452
|
+
|
|
453
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
454
|
+
const result = await verifySellableMcpRuntimeOnce({
|
|
455
|
+
command,
|
|
456
|
+
args,
|
|
457
|
+
cwd,
|
|
458
|
+
env,
|
|
459
|
+
requiredTools,
|
|
460
|
+
timeoutMs,
|
|
461
|
+
});
|
|
462
|
+
lastResult = result;
|
|
463
|
+
attemptSummaries.push(summarizeAttempt(result, attempt));
|
|
464
|
+
|
|
465
|
+
if (
|
|
466
|
+
attempt >= attempts ||
|
|
467
|
+
!shouldRetryRuntimeVerification(result, requiredTools)
|
|
468
|
+
) {
|
|
469
|
+
return {
|
|
470
|
+
...result,
|
|
471
|
+
attempts: attemptSummaries,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
await sleep(retryDelayMs);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
...lastResult,
|
|
480
|
+
attempts: attemptSummaries,
|
|
481
|
+
};
|
|
482
|
+
}
|