@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.
@@ -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: packageRunnerCommand(),
686
- args: ["-y", opts.mcpPackage],
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
  );
@@ -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
- export async function verifySellableMcpRuntime({
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.212",
3
+ "version": "0.1.213",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code and Codex",
6
6
  "bin": {