@sellable/install 0.1.216 → 0.1.218
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 +269 -25
- package/package.json +1 -1
package/bin/sellable-install.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { homedir } from "node:os";
|
|
14
14
|
import { dirname, join, relative } from "node:path";
|
|
15
15
|
import { stdout as output } from "node:process";
|
|
16
|
+
import { createInterface } from "node:readline/promises";
|
|
16
17
|
import { fileURLToPath } from "node:url";
|
|
17
18
|
import {
|
|
18
19
|
REQUIRED_SELLABLE_MCP_TOOLS,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
const DEFAULT_API_URL = "https://app.sellable.dev";
|
|
23
24
|
const DEFAULT_SERVER_PACKAGE =
|
|
24
25
|
process.env.SELLABLE_MCP_PACKAGE || "@sellable/mcp@latest";
|
|
26
|
+
const CODEX_INSTALL_URL = "https://chatgpt.com/codex/install";
|
|
25
27
|
|
|
26
28
|
function getInstallVersion() {
|
|
27
29
|
try {
|
|
@@ -175,6 +177,8 @@ Options:
|
|
|
175
177
|
--hosted-url <url> Hosted MCP URL for --server hosted.
|
|
176
178
|
--verbose Print every file write and shell command.
|
|
177
179
|
--dry-run Print actions without writing or running host commands.
|
|
180
|
+
--install-codex-cli Install Codex CLI without prompting if it is missing or blocked.
|
|
181
|
+
--no-install-codex-cli Do not prompt to install Codex CLI; install Desktop config only when possible.
|
|
178
182
|
--verify-only Verify installed host config where possible.
|
|
179
183
|
--json Print machine-readable verification JSON.
|
|
180
184
|
--artifact <path> Write verification JSON to a file.
|
|
@@ -247,6 +251,7 @@ function parseArgs(argv) {
|
|
|
247
251
|
json: false,
|
|
248
252
|
artifactPath: process.env.SELLABLE_VERIFY_ARTIFACT || "",
|
|
249
253
|
verbose: false,
|
|
254
|
+
codexCliInstall: codexCliInstallPreference(),
|
|
250
255
|
};
|
|
251
256
|
|
|
252
257
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -280,6 +285,10 @@ function parseArgs(argv) {
|
|
|
280
285
|
opts.hostedUrl = next();
|
|
281
286
|
} else if (arg === "--dry-run") {
|
|
282
287
|
opts.dryRun = true;
|
|
288
|
+
} else if (arg === "--install-codex-cli") {
|
|
289
|
+
opts.codexCliInstall = "install";
|
|
290
|
+
} else if (arg === "--no-install-codex-cli") {
|
|
291
|
+
opts.codexCliInstall = "skip";
|
|
283
292
|
} else if (arg === "--verify-only") {
|
|
284
293
|
opts.verifyOnly = true;
|
|
285
294
|
} else if (arg === "--json") {
|
|
@@ -303,6 +312,14 @@ function parseArgs(argv) {
|
|
|
303
312
|
return opts;
|
|
304
313
|
}
|
|
305
314
|
|
|
315
|
+
function codexCliInstallPreference() {
|
|
316
|
+
const raw = process.env.SELLABLE_INSTALL_CODEX_CLI || "";
|
|
317
|
+
const value = raw.trim().toLowerCase();
|
|
318
|
+
if (["1", "true", "yes", "y", "install"].includes(value)) return "install";
|
|
319
|
+
if (["0", "false", "no", "n", "skip"].includes(value)) return "skip";
|
|
320
|
+
return "ask";
|
|
321
|
+
}
|
|
322
|
+
|
|
306
323
|
function redact(value) {
|
|
307
324
|
if (!value) return "";
|
|
308
325
|
if (value.length <= 10) return "[redacted]";
|
|
@@ -360,6 +377,175 @@ function commandExistsDetails(command, platform = process.platform) {
|
|
|
360
377
|
};
|
|
361
378
|
}
|
|
362
379
|
|
|
380
|
+
function summarizeSpawnFailure(result) {
|
|
381
|
+
const errorMessage = result.error?.message || "";
|
|
382
|
+
const stderr = (result.stderr || "").trim();
|
|
383
|
+
const stdout = (result.stdout || "").trim();
|
|
384
|
+
return (
|
|
385
|
+
errorMessage ||
|
|
386
|
+
stderr ||
|
|
387
|
+
stdout ||
|
|
388
|
+
(Number.isInteger(result.status) ? `exit ${result.status}` : "unknown failure")
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function codexCliStatus(opts = {}) {
|
|
393
|
+
const details = commandExistsDetails("codex");
|
|
394
|
+
if (!details.exists) {
|
|
395
|
+
return {
|
|
396
|
+
exists: false,
|
|
397
|
+
usable: false,
|
|
398
|
+
reason: "Codex CLI not found on PATH",
|
|
399
|
+
details,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
if (opts.dryRun) {
|
|
403
|
+
return {
|
|
404
|
+
exists: true,
|
|
405
|
+
usable: true,
|
|
406
|
+
reason: "dry-run",
|
|
407
|
+
details,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const result = spawnSync("codex", ["mcp", "--help"], {
|
|
412
|
+
encoding: "utf8",
|
|
413
|
+
stdio: "pipe",
|
|
414
|
+
timeout: 10000,
|
|
415
|
+
});
|
|
416
|
+
if (result.status === 0) {
|
|
417
|
+
return {
|
|
418
|
+
exists: true,
|
|
419
|
+
usable: true,
|
|
420
|
+
reason: "Codex CLI responded to `codex mcp --help`",
|
|
421
|
+
details,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
exists: true,
|
|
427
|
+
usable: false,
|
|
428
|
+
reason: `Codex CLI found but not usable: ${summarizeSpawnFailure(result)}`,
|
|
429
|
+
details,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function codexDesktopLikelyInstalled(status = null) {
|
|
434
|
+
if (process.env.SELLABLE_INSTALL_ASSUME_CODEX_DESKTOP === "1") return true;
|
|
435
|
+
const home = codexHome();
|
|
436
|
+
const probes = [
|
|
437
|
+
status?.details?.stdout || "",
|
|
438
|
+
process.env.PATH || "",
|
|
439
|
+
].join("\n");
|
|
440
|
+
|
|
441
|
+
if (/WindowsApps[\\/]+codex\.exe/i.test(probes)) return true;
|
|
442
|
+
if (existsSync(join(home, "config.toml"))) return true;
|
|
443
|
+
if (existsSync(join(home, "plugins", ".plugin-appserver"))) return true;
|
|
444
|
+
if (existsSync(join(home, "plugins", "cache"))) return true;
|
|
445
|
+
if (process.platform === "darwin" && existsSync("/Applications/Codex.app")) {
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function isNonInteractiveInstall() {
|
|
452
|
+
return (
|
|
453
|
+
process.env.CI === "true" ||
|
|
454
|
+
process.env.SELLABLE_NON_INTERACTIVE === "1" ||
|
|
455
|
+
process.env.SELLABLE_NON_INTERACTIVE === "true" ||
|
|
456
|
+
process.env.SELLABLE_NON_INTERACTIVE === "yes"
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
async function askYesNo(question, defaultAnswer = false) {
|
|
461
|
+
if (!process.stdin.isTTY || !output.isTTY || isNonInteractiveInstall()) {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
const suffix = defaultAnswer ? " [Y/n] " : " [y/N] ";
|
|
465
|
+
const rl = createInterface({
|
|
466
|
+
input: process.stdin,
|
|
467
|
+
output: process.stdout,
|
|
468
|
+
});
|
|
469
|
+
try {
|
|
470
|
+
const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase();
|
|
471
|
+
if (!answer) return defaultAnswer;
|
|
472
|
+
return ["y", "yes"].includes(answer);
|
|
473
|
+
} finally {
|
|
474
|
+
rl.close();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function codexInstallCommand(platform = process.platform) {
|
|
479
|
+
const override = process.env.SELLABLE_CODEX_INSTALL_COMMAND;
|
|
480
|
+
if (override) {
|
|
481
|
+
return isWindowsPlatform(platform)
|
|
482
|
+
? ["cmd.exe", ["/d", "/s", "/c", override]]
|
|
483
|
+
: ["sh", ["-c", override]];
|
|
484
|
+
}
|
|
485
|
+
if (isWindowsPlatform(platform)) {
|
|
486
|
+
return [
|
|
487
|
+
"powershell.exe",
|
|
488
|
+
[
|
|
489
|
+
"-NoProfile",
|
|
490
|
+
"-ExecutionPolicy",
|
|
491
|
+
"Bypass",
|
|
492
|
+
"-Command",
|
|
493
|
+
`$env:CODEX_NON_INTERACTIVE='1'; irm ${CODEX_INSTALL_URL}.ps1 | iex`,
|
|
494
|
+
],
|
|
495
|
+
];
|
|
496
|
+
}
|
|
497
|
+
return [
|
|
498
|
+
"sh",
|
|
499
|
+
["-c", `curl -fsSL ${CODEX_INSTALL_URL}.sh | CODEX_NON_INTERACTIVE=1 sh`],
|
|
500
|
+
];
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function codexManualInstallCommand(platform = process.platform) {
|
|
504
|
+
return isWindowsPlatform(platform)
|
|
505
|
+
? `$env:CODEX_NON_INTERACTIVE=1; irm ${CODEX_INSTALL_URL}.ps1 | iex`
|
|
506
|
+
: `curl -fsSL ${CODEX_INSTALL_URL}.sh | sh`;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async function maybeInstallCodexCli(status, opts) {
|
|
510
|
+
if (opts.dryRun) return false;
|
|
511
|
+
if (opts.codexCliInstall === "skip") {
|
|
512
|
+
logWarn(`${status.reason}. Skipping Codex CLI install by request.`);
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
let shouldInstall = opts.codexCliInstall === "install";
|
|
517
|
+
if (!shouldInstall) {
|
|
518
|
+
const answer = await askYesNo(
|
|
519
|
+
`${status.reason}. Install the Codex CLI now?`,
|
|
520
|
+
false
|
|
521
|
+
);
|
|
522
|
+
if (answer === null) {
|
|
523
|
+
logWarn(
|
|
524
|
+
`${status.reason}. Non-interactive shell; skipping Codex CLI install. Re-run with --install-codex-cli to install it.`
|
|
525
|
+
);
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
shouldInstall = answer;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (!shouldInstall) {
|
|
532
|
+
logWarn(`${status.reason}. Continuing with Codex Desktop plugin setup only.`);
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const [command, args] = codexInstallCommand();
|
|
537
|
+
logStep("Installing Codex CLI using the official Codex installer...");
|
|
538
|
+
try {
|
|
539
|
+
run(command, args, opts);
|
|
540
|
+
return true;
|
|
541
|
+
} catch (err) {
|
|
542
|
+
logWarn(
|
|
543
|
+
`Codex CLI install failed: ${err instanceof Error ? err.message : String(err)}`
|
|
544
|
+
);
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
363
549
|
function shellQuote(value) {
|
|
364
550
|
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
365
551
|
}
|
|
@@ -2455,6 +2641,27 @@ function writeAuth(opts) {
|
|
|
2455
2641
|
return { written: true, reused: false };
|
|
2456
2642
|
}
|
|
2457
2643
|
|
|
2644
|
+
function resolveWindowsNpmCommand() {
|
|
2645
|
+
const explicit =
|
|
2646
|
+
process.env.SELLABLE_INSTALL_NPM_CMD_COMMAND ||
|
|
2647
|
+
process.env.SELLABLE_INSTALL_NPM_COMMAND;
|
|
2648
|
+
if (explicit) return explicit;
|
|
2649
|
+
|
|
2650
|
+
const nodeBin = process.env.SELLABLE_INSTALL_NODE_BIN || "";
|
|
2651
|
+
for (const name of ["npm.cmd", "npm"]) {
|
|
2652
|
+
const candidate = nodeBin ? join(nodeBin, name) : "";
|
|
2653
|
+
if (candidate && existsSync(candidate)) return candidate;
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
if (isWindowsPlatform()) {
|
|
2657
|
+
const details = commandExistsDetails("npm.cmd");
|
|
2658
|
+
const first = details.stdout.split(/\r?\n/).find((line) => line.trim());
|
|
2659
|
+
if (details.exists && first) return first.trim();
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
return packageManagerCommand("win32");
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2458
2665
|
function installSelfShim(opts) {
|
|
2459
2666
|
const installDir =
|
|
2460
2667
|
process.env.SELLABLE_INSTALL_DIR || join(homedir(), ".local", "bin");
|
|
@@ -2475,12 +2682,11 @@ exec ${shellQuote(npmCommand)} exec --yes --package ${shellQuote(INSTALL_PACKAGE
|
|
|
2475
2682
|
isWindowsPlatform() || process.env.SELLABLE_INSTALL_WRITE_CMD_SHIM === "1";
|
|
2476
2683
|
if (shouldWriteCmdShim) {
|
|
2477
2684
|
const cmdPath = `${binPath}.cmd`;
|
|
2478
|
-
const npmCmdCommand =
|
|
2479
|
-
process.env.SELLABLE_INSTALL_NPM_CMD_COMMAND ||
|
|
2480
|
-
process.env.SELLABLE_INSTALL_NPM_COMMAND ||
|
|
2481
|
-
packageManagerCommand("win32");
|
|
2685
|
+
const npmCmdCommand = resolveWindowsNpmCommand();
|
|
2482
2686
|
const cmdPathPrefix = nodeBin ? `set "PATH=${nodeBin};%PATH%"\r\n` : "";
|
|
2483
|
-
const cmdShim =
|
|
2687
|
+
const cmdShim =
|
|
2688
|
+
`@echo off\r\n${cmdPathPrefix}` +
|
|
2689
|
+
`call "${npmCmdCommand}" exec --yes --package ${INSTALL_PACKAGE_SPEC} -- sellable %*\r\n`;
|
|
2484
2690
|
writeFile(cmdPath, cmdShim, opts, 0o755);
|
|
2485
2691
|
}
|
|
2486
2692
|
}
|
|
@@ -2820,32 +3026,70 @@ function patchClaudeAlwaysLoad(opts) {
|
|
|
2820
3026
|
}
|
|
2821
3027
|
}
|
|
2822
3028
|
|
|
2823
|
-
function
|
|
2824
|
-
|
|
3029
|
+
function registerCodexCliMcp(opts) {
|
|
3030
|
+
try {
|
|
3031
|
+
if (opts.server !== "hosted") {
|
|
3032
|
+
run("codex", ["mcp", "remove", "sellable"], {
|
|
3033
|
+
...opts,
|
|
3034
|
+
dryRun: opts.dryRun,
|
|
3035
|
+
allowFail: true,
|
|
3036
|
+
});
|
|
3037
|
+
}
|
|
3038
|
+
run("codex", codexMcpAddArgs(opts), opts);
|
|
3039
|
+
return true;
|
|
3040
|
+
} catch (err) {
|
|
3041
|
+
logWarn(
|
|
3042
|
+
`Codex CLI MCP registration skipped: ${err instanceof Error ? err.message : String(err)}`
|
|
3043
|
+
);
|
|
3044
|
+
return false;
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
async function installCodex(opts) {
|
|
3049
|
+
let cliStatus = codexCliStatus(opts);
|
|
3050
|
+
let desktopLikely = codexDesktopLikelyInstalled(cliStatus);
|
|
3051
|
+
let attemptedCliInstall = false;
|
|
3052
|
+
|
|
3053
|
+
if (!opts.dryRun && !cliStatus.usable) {
|
|
3054
|
+
attemptedCliInstall = await maybeInstallCodexCli(cliStatus, opts);
|
|
3055
|
+
if (attemptedCliInstall) {
|
|
3056
|
+
cliStatus = codexCliStatus(opts);
|
|
3057
|
+
desktopLikely = codexDesktopLikelyInstalled(cliStatus) || desktopLikely;
|
|
3058
|
+
if (!cliStatus.usable) {
|
|
3059
|
+
logWarn(
|
|
3060
|
+
`${cliStatus.reason}. Continuing with Codex Desktop plugin setup where possible.`
|
|
3061
|
+
);
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
|
|
3066
|
+
const shouldInstallDesktop =
|
|
3067
|
+
opts.host === "codex" ||
|
|
3068
|
+
cliStatus.usable ||
|
|
3069
|
+
desktopLikely ||
|
|
3070
|
+
attemptedCliInstall;
|
|
3071
|
+
if (!shouldInstallDesktop) {
|
|
2825
3072
|
const message =
|
|
2826
|
-
"Codex CLI not found. Install/login to Codex, then rerun: sellable --host codex";
|
|
3073
|
+
"Codex CLI not found and Codex Desktop was not detected. Install/login to Codex, then rerun: sellable --host codex";
|
|
2827
3074
|
if (opts.host === "all") {
|
|
2828
3075
|
logWarn(`Skipping Codex: ${message}`);
|
|
2829
3076
|
return { installed: false };
|
|
2830
3077
|
}
|
|
2831
3078
|
throw new Error(message);
|
|
2832
3079
|
}
|
|
3080
|
+
|
|
2833
3081
|
if (!opts.dryRun) {
|
|
2834
3082
|
mkdirSync(codexHome(), { recursive: true, mode: 0o700 });
|
|
2835
3083
|
}
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
3084
|
+
|
|
3085
|
+
const cliRegistered = cliStatus.usable ? registerCodexCliMcp(opts) : false;
|
|
3086
|
+
if (!cliRegistered && !opts.dryRun) {
|
|
3087
|
+
logWarn(
|
|
3088
|
+
"Codex CLI MCP registration did not run; writing Codex Desktop plugin and config directly."
|
|
3089
|
+
);
|
|
2840
3090
|
}
|
|
2841
|
-
run("codex", ["mcp", "remove", "sellable"], {
|
|
2842
|
-
...opts,
|
|
2843
|
-
dryRun: opts.dryRun,
|
|
2844
|
-
allowFail: true,
|
|
2845
|
-
});
|
|
2846
|
-
run("codex", codexMcpAddArgs(opts), opts);
|
|
2847
3091
|
const info = installCodexDesktopPlugin(opts);
|
|
2848
|
-
return { installed: true, ...info };
|
|
3092
|
+
return { installed: true, cliRegistered, ...info };
|
|
2849
3093
|
}
|
|
2850
3094
|
|
|
2851
3095
|
function requiredCheck(ok, label) {
|
|
@@ -2996,11 +3240,11 @@ async function verify(opts) {
|
|
|
2996
3240
|
);
|
|
2997
3241
|
}
|
|
2998
3242
|
if (opts.host === "codex" || opts.host === "all") {
|
|
2999
|
-
const
|
|
3243
|
+
const codexStatus = codexCliStatus(opts);
|
|
3000
3244
|
checks.push(
|
|
3001
3245
|
warningCheck(
|
|
3002
|
-
|
|
3003
|
-
|
|
3246
|
+
codexStatus.usable,
|
|
3247
|
+
codexStatus.usable ? "Codex CLI usable" : codexStatus.reason
|
|
3004
3248
|
)
|
|
3005
3249
|
);
|
|
3006
3250
|
const pluginPath = join(
|
|
@@ -3358,8 +3602,8 @@ function printNextSteps(installedHosts, authReused) {
|
|
|
3358
3602
|
console.log("");
|
|
3359
3603
|
printInstallAgentBox(
|
|
3360
3604
|
"Install Codex",
|
|
3361
|
-
|
|
3362
|
-
"https://
|
|
3605
|
+
codexManualInstallCommand(),
|
|
3606
|
+
"https://developers.openai.com/codex/quickstart"
|
|
3363
3607
|
);
|
|
3364
3608
|
console.log("");
|
|
3365
3609
|
console.log("");
|
|
@@ -3805,7 +4049,7 @@ async function main() {
|
|
|
3805
4049
|
}
|
|
3806
4050
|
}
|
|
3807
4051
|
if (opts.host === "codex" || opts.host === "all") {
|
|
3808
|
-
const result = installCodex(opts);
|
|
4052
|
+
const result = await installCodex(opts);
|
|
3809
4053
|
if (result.installed) {
|
|
3810
4054
|
installedHosts.push("Codex");
|
|
3811
4055
|
}
|