@sellable/install 0.1.217 → 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/README.md +7 -9
- package/bin/sellable-install.mjs +290 -121
- package/package.json +1 -1
- package/skill-templates/create-campaign.md +1 -5
package/README.md
CHANGED
|
@@ -35,9 +35,8 @@ Campaign creation, foundation memory, content capture/ideation, and post
|
|
|
35
35
|
drafting run inside Claude Code or Codex, where the Sellable MCP tools and
|
|
36
36
|
approval flows are available.
|
|
37
37
|
|
|
38
|
-
Install is auth-free by default.
|
|
39
|
-
Sellable
|
|
40
|
-
sign-in with a browser magic-link handoff.
|
|
38
|
+
Install is auth-free by default. If you do not pass a token, the agent handles
|
|
39
|
+
Sellable sign-in on the first campaign run with a magic-link handoff.
|
|
41
40
|
|
|
42
41
|
The installer uses package stdio MCP by default:
|
|
43
42
|
|
|
@@ -65,15 +64,14 @@ verification in one path:
|
|
|
65
64
|
curl -fsSL "https://app.sellable.dev/api/v2/cli/install" | sh
|
|
66
65
|
```
|
|
67
66
|
|
|
68
|
-
For scripted
|
|
67
|
+
For CI/scripted installs, get a Sellable API token from:
|
|
69
68
|
|
|
70
|
-
```
|
|
71
|
-
sellable
|
|
69
|
+
```text
|
|
70
|
+
https://app.sellable.dev/settings
|
|
72
71
|
```
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
primary human setup path.
|
|
73
|
+
Then pass it with `--token` / `SELLABLE_TOKEN` plus `--workspace-id` /
|
|
74
|
+
`SELLABLE_WORKSPACE_ID`.
|
|
77
75
|
|
|
78
76
|
Auth is stored once at:
|
|
79
77
|
|
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
|
}
|
|
@@ -419,26 +605,13 @@ async function loadAuthIfPresent(opts) {
|
|
|
419
605
|
return opts;
|
|
420
606
|
}
|
|
421
607
|
|
|
422
|
-
function redactTokensForLog(value) {
|
|
423
|
-
if (Array.isArray(value)) {
|
|
424
|
-
return value.map((item) => redactTokensForLog(item));
|
|
425
|
-
}
|
|
426
|
-
if (value && typeof value === "object") {
|
|
427
|
-
return Object.fromEntries(
|
|
428
|
-
Object.entries(value).map(([key, item]) => [
|
|
429
|
-
key,
|
|
430
|
-
key.toLowerCase().includes("token") && typeof item === "string"
|
|
431
|
-
? redact(item)
|
|
432
|
-
: redactTokensForLog(item),
|
|
433
|
-
])
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
return value;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
608
|
function writeJson(path, data, opts) {
|
|
440
609
|
if (VERBOSE) {
|
|
441
|
-
const redacted = JSON.stringify(
|
|
610
|
+
const redacted = JSON.stringify(
|
|
611
|
+
{ ...data, token: redact(data.token) },
|
|
612
|
+
null,
|
|
613
|
+
2
|
|
614
|
+
);
|
|
442
615
|
logVerbose(`${C.grey}Writing ${path}: ${redacted}${C.reset}`);
|
|
443
616
|
}
|
|
444
617
|
if (opts.dryRun) return;
|
|
@@ -455,56 +628,6 @@ function readExisting(path) {
|
|
|
455
628
|
}
|
|
456
629
|
}
|
|
457
630
|
|
|
458
|
-
function mergeAuthConfig(raw, auth) {
|
|
459
|
-
const existing =
|
|
460
|
-
raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
461
|
-
const authFields = {
|
|
462
|
-
token: auth.token,
|
|
463
|
-
activeWorkspaceId: auth.activeWorkspaceId,
|
|
464
|
-
apiUrl: auth.apiUrl,
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
if (existing.activeEnv && existing.environments) {
|
|
468
|
-
const activeEnv = existing.activeEnv;
|
|
469
|
-
const envConfig = existing.environments[activeEnv];
|
|
470
|
-
if (
|
|
471
|
-
!envConfig ||
|
|
472
|
-
typeof envConfig !== "object" ||
|
|
473
|
-
Array.isArray(envConfig)
|
|
474
|
-
) {
|
|
475
|
-
throw new Error(
|
|
476
|
-
`Unknown active environment '${activeEnv}' in ${authPath()}`
|
|
477
|
-
);
|
|
478
|
-
}
|
|
479
|
-
return {
|
|
480
|
-
...existing,
|
|
481
|
-
environments: {
|
|
482
|
-
...existing.environments,
|
|
483
|
-
[activeEnv]: {
|
|
484
|
-
...envConfig,
|
|
485
|
-
...authFields,
|
|
486
|
-
},
|
|
487
|
-
},
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
return {
|
|
492
|
-
...existing,
|
|
493
|
-
...authFields,
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
function writeAuthSetConfig({ token, workspaceId, apiUrl, dryRun }) {
|
|
498
|
-
const configPath = authPath();
|
|
499
|
-
const raw = readExisting(configPath) || {};
|
|
500
|
-
const config = mergeAuthConfig(raw, {
|
|
501
|
-
token,
|
|
502
|
-
activeWorkspaceId: workspaceId || null,
|
|
503
|
-
apiUrl,
|
|
504
|
-
});
|
|
505
|
-
writeJson(configPath, config, { dryRun });
|
|
506
|
-
}
|
|
507
|
-
|
|
508
631
|
function sellableHostEnvPath() {
|
|
509
632
|
return join(homedir(), ".local", "sellable", "app-sellable-dev", ".env");
|
|
510
633
|
}
|
|
@@ -2518,6 +2641,27 @@ function writeAuth(opts) {
|
|
|
2518
2641
|
return { written: true, reused: false };
|
|
2519
2642
|
}
|
|
2520
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
|
+
|
|
2521
2665
|
function installSelfShim(opts) {
|
|
2522
2666
|
const installDir =
|
|
2523
2667
|
process.env.SELLABLE_INSTALL_DIR || join(homedir(), ".local", "bin");
|
|
@@ -2538,12 +2682,11 @@ exec ${shellQuote(npmCommand)} exec --yes --package ${shellQuote(INSTALL_PACKAGE
|
|
|
2538
2682
|
isWindowsPlatform() || process.env.SELLABLE_INSTALL_WRITE_CMD_SHIM === "1";
|
|
2539
2683
|
if (shouldWriteCmdShim) {
|
|
2540
2684
|
const cmdPath = `${binPath}.cmd`;
|
|
2541
|
-
const npmCmdCommand =
|
|
2542
|
-
process.env.SELLABLE_INSTALL_NPM_CMD_COMMAND ||
|
|
2543
|
-
process.env.SELLABLE_INSTALL_NPM_COMMAND ||
|
|
2544
|
-
packageManagerCommand("win32");
|
|
2685
|
+
const npmCmdCommand = resolveWindowsNpmCommand();
|
|
2545
2686
|
const cmdPathPrefix = nodeBin ? `set "PATH=${nodeBin};%PATH%"\r\n` : "";
|
|
2546
|
-
const cmdShim =
|
|
2687
|
+
const cmdShim =
|
|
2688
|
+
`@echo off\r\n${cmdPathPrefix}` +
|
|
2689
|
+
`call "${npmCmdCommand}" exec --yes --package ${INSTALL_PACKAGE_SPEC} -- sellable %*\r\n`;
|
|
2547
2690
|
writeFile(cmdPath, cmdShim, opts, 0o755);
|
|
2548
2691
|
}
|
|
2549
2692
|
}
|
|
@@ -2883,32 +3026,70 @@ function patchClaudeAlwaysLoad(opts) {
|
|
|
2883
3026
|
}
|
|
2884
3027
|
}
|
|
2885
3028
|
|
|
2886
|
-
function
|
|
2887
|
-
|
|
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) {
|
|
2888
3072
|
const message =
|
|
2889
|
-
"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";
|
|
2890
3074
|
if (opts.host === "all") {
|
|
2891
3075
|
logWarn(`Skipping Codex: ${message}`);
|
|
2892
3076
|
return { installed: false };
|
|
2893
3077
|
}
|
|
2894
3078
|
throw new Error(message);
|
|
2895
3079
|
}
|
|
3080
|
+
|
|
2896
3081
|
if (!opts.dryRun) {
|
|
2897
3082
|
mkdirSync(codexHome(), { recursive: true, mode: 0o700 });
|
|
2898
3083
|
}
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
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
|
+
);
|
|
2903
3090
|
}
|
|
2904
|
-
run("codex", ["mcp", "remove", "sellable"], {
|
|
2905
|
-
...opts,
|
|
2906
|
-
dryRun: opts.dryRun,
|
|
2907
|
-
allowFail: true,
|
|
2908
|
-
});
|
|
2909
|
-
run("codex", codexMcpAddArgs(opts), opts);
|
|
2910
3091
|
const info = installCodexDesktopPlugin(opts);
|
|
2911
|
-
return { installed: true, ...info };
|
|
3092
|
+
return { installed: true, cliRegistered, ...info };
|
|
2912
3093
|
}
|
|
2913
3094
|
|
|
2914
3095
|
function requiredCheck(ok, label) {
|
|
@@ -3059,11 +3240,11 @@ async function verify(opts) {
|
|
|
3059
3240
|
);
|
|
3060
3241
|
}
|
|
3061
3242
|
if (opts.host === "codex" || opts.host === "all") {
|
|
3062
|
-
const
|
|
3243
|
+
const codexStatus = codexCliStatus(opts);
|
|
3063
3244
|
checks.push(
|
|
3064
3245
|
warningCheck(
|
|
3065
|
-
|
|
3066
|
-
|
|
3246
|
+
codexStatus.usable,
|
|
3247
|
+
codexStatus.usable ? "Codex CLI usable" : codexStatus.reason
|
|
3067
3248
|
)
|
|
3068
3249
|
);
|
|
3069
3250
|
const pluginPath = join(
|
|
@@ -3421,8 +3602,8 @@ function printNextSteps(installedHosts, authReused) {
|
|
|
3421
3602
|
console.log("");
|
|
3422
3603
|
printInstallAgentBox(
|
|
3423
3604
|
"Install Codex",
|
|
3424
|
-
|
|
3425
|
-
"https://
|
|
3605
|
+
codexManualInstallCommand(),
|
|
3606
|
+
"https://developers.openai.com/codex/quickstart"
|
|
3426
3607
|
);
|
|
3427
3608
|
console.log("");
|
|
3428
3609
|
console.log("");
|
|
@@ -3728,31 +3909,18 @@ async function main() {
|
|
|
3728
3909
|
}
|
|
3729
3910
|
const token = rawArgs[2];
|
|
3730
3911
|
const authSetFlags = rawArgs.slice(3);
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
continue;
|
|
3738
|
-
}
|
|
3739
|
-
if (flag === "--workspace-id") {
|
|
3740
|
-
const value = authSetFlags[i + 1];
|
|
3741
|
-
if (!value || value.startsWith("--")) {
|
|
3742
|
-
console.error("Missing value for --workspace-id");
|
|
3743
|
-
process.exit(2);
|
|
3744
|
-
}
|
|
3745
|
-
workspaceId = value;
|
|
3746
|
-
i += 1;
|
|
3747
|
-
continue;
|
|
3748
|
-
}
|
|
3749
|
-
console.error(`Unknown auth set option: ${flag}`);
|
|
3912
|
+
const dryRun = authSetFlags.includes("--dry-run");
|
|
3913
|
+
const unknownAuthSetFlag = authSetFlags.find(
|
|
3914
|
+
(arg) => arg !== "--dry-run"
|
|
3915
|
+
);
|
|
3916
|
+
if (unknownAuthSetFlag) {
|
|
3917
|
+
console.error(`Unknown auth set option: ${unknownAuthSetFlag}`);
|
|
3750
3918
|
process.exit(2);
|
|
3751
3919
|
}
|
|
3752
3920
|
if (!token) {
|
|
3753
3921
|
console.error(
|
|
3754
|
-
"Usage: sellable auth set <token
|
|
3755
|
-
"
|
|
3922
|
+
"Usage: sellable auth set <token>\n" +
|
|
3923
|
+
"Get the token from the Sellable browser confirm page (after clicking the magic link)."
|
|
3756
3924
|
);
|
|
3757
3925
|
process.exit(2);
|
|
3758
3926
|
}
|
|
@@ -3764,22 +3932,23 @@ async function main() {
|
|
|
3764
3932
|
);
|
|
3765
3933
|
process.exit(2);
|
|
3766
3934
|
}
|
|
3767
|
-
// Bypass writeAuth()
|
|
3768
|
-
// the
|
|
3769
|
-
//
|
|
3935
|
+
// Bypass writeAuth() — its workspaceId guard early-returns on missing
|
|
3936
|
+
// workspaceId, but on the auth-set path we don't have one yet (next MCP
|
|
3937
|
+
// call hydrates it). Write the same shape directly.
|
|
3770
3938
|
// Skip installSelfShim() — by definition the user has the shim already
|
|
3771
3939
|
// (they invoked `sellable auth set` from it).
|
|
3772
3940
|
const apiUrl = process.env.SELLABLE_API_URL || DEFAULT_API_URL;
|
|
3773
|
-
|
|
3941
|
+
writeJson(
|
|
3942
|
+
authPath(),
|
|
3943
|
+
{ token, activeWorkspaceId: null, apiUrl },
|
|
3944
|
+
{ dryRun }
|
|
3945
|
+
);
|
|
3774
3946
|
if (dryRun) {
|
|
3775
3947
|
console.log(`Dry run: token would be saved to ${authPath()}`);
|
|
3776
3948
|
} else {
|
|
3777
3949
|
console.log(`✓ Token saved to ${authPath()}`);
|
|
3778
3950
|
}
|
|
3779
3951
|
console.log(` apiUrl: ${apiUrl}`);
|
|
3780
|
-
if (workspaceId) {
|
|
3781
|
-
console.log(` activeWorkspaceId: ${workspaceId}`);
|
|
3782
|
-
}
|
|
3783
3952
|
console.log(` Continue in your agent:`);
|
|
3784
3953
|
console.log(` Claude Code: /sellable:create-campaign`);
|
|
3785
3954
|
console.log(` Claude Code: /sellable:foundation`);
|
|
@@ -3880,7 +4049,7 @@ async function main() {
|
|
|
3880
4049
|
}
|
|
3881
4050
|
}
|
|
3882
4051
|
if (opts.host === "codex" || opts.host === "all") {
|
|
3883
|
-
const result = installCodex(opts);
|
|
4052
|
+
const result = await installCodex(opts);
|
|
3884
4053
|
if (result.installed) {
|
|
3885
4054
|
installedHosts.push("Codex");
|
|
3886
4055
|
}
|
package/package.json
CHANGED
|
@@ -893,11 +893,7 @@ updates.
|
|
|
893
893
|
|
|
894
894
|
1. Call `mcp__sellable__get_auth_status({})`.
|
|
895
895
|
2. If auth is not OK with `error.type === "config"` or `error.type === "auth"`,
|
|
896
|
-
the user has not signed in yet. Run
|
|
897
|
-
magic-link handoff. If a browser page or tool guidance gives the user a
|
|
898
|
-
manual fallback, it must be
|
|
899
|
-
`sellable auth set <token> --workspace-id <workspace_id>`. Do not instruct
|
|
900
|
-
the user to hand-edit JSON auth config.
|
|
896
|
+
the user has not signed in yet. Run the FTUX magic-link handoff:
|
|
901
897
|
|
|
902
898
|
a. Say to the user verbatim:
|
|
903
899
|
|