@sellable/install 0.1.39 → 0.1.41
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 +232 -50
- package/package.json +1 -1
package/bin/sellable-install.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { spawnSync } from "node:child_process";
|
|
|
3
3
|
import {
|
|
4
4
|
existsSync,
|
|
5
5
|
mkdirSync,
|
|
6
|
+
readdirSync,
|
|
6
7
|
readFileSync,
|
|
7
8
|
rmSync,
|
|
8
9
|
symlinkSync,
|
|
@@ -73,6 +74,7 @@ const C = {
|
|
|
73
74
|
grey: useColor ? "\x1b[90m" : "",
|
|
74
75
|
bold: useColor ? "\x1b[1m" : "",
|
|
75
76
|
magenta: useColor ? "\x1b[35m" : "",
|
|
77
|
+
brand: useColor ? "\x1b[96m" : "",
|
|
76
78
|
};
|
|
77
79
|
|
|
78
80
|
let VERBOSE = false;
|
|
@@ -105,11 +107,11 @@ function printBanner() {
|
|
|
105
107
|
" ╚══════╝ ╚══════╝ ╚══════╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚══════╝",
|
|
106
108
|
];
|
|
107
109
|
console.log("");
|
|
108
|
-
for (const line of lines) console.log(`${C.
|
|
110
|
+
for (const line of lines) console.log(`${C.brand}${line}${C.reset}`);
|
|
109
111
|
console.log("");
|
|
110
112
|
} else {
|
|
111
113
|
console.log("");
|
|
112
|
-
console.log(`${C.bold}${C.
|
|
114
|
+
console.log(`${C.bold}${C.brand}SELLABLE${C.reset}`);
|
|
113
115
|
console.log("");
|
|
114
116
|
}
|
|
115
117
|
}
|
|
@@ -1382,14 +1384,48 @@ function verify(opts) {
|
|
|
1382
1384
|
}
|
|
1383
1385
|
}
|
|
1384
1386
|
|
|
1385
|
-
function
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1387
|
+
function visibleLen(s) {
|
|
1388
|
+
// Strip ANSI escape sequences for visible-width calculation.
|
|
1389
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
function printAgentBox(title, terminalCmd, agentCmd) {
|
|
1393
|
+
// Inner width between │ ... │ — i.e. the printable area excluding the borders.
|
|
1394
|
+
const W = 58;
|
|
1395
|
+
const agentName = title.replace("Using ", "").replace("?", "");
|
|
1396
|
+
|
|
1397
|
+
const line = (text) => {
|
|
1398
|
+
const pad = Math.max(0, W - visibleLen(text));
|
|
1399
|
+
return ` │ ${text}${" ".repeat(pad)} │`;
|
|
1400
|
+
};
|
|
1401
|
+
const blank = () => ` │${" ".repeat(W + 2)}│`;
|
|
1402
|
+
|
|
1403
|
+
const titleColored = `${C.bold}${title}${C.reset}`;
|
|
1404
|
+
const titleVis = visibleLen(titleColored);
|
|
1405
|
+
const top = ` ┌─ ${titleColored} ${"─".repeat(Math.max(0, W - titleVis - 1))}┐`;
|
|
1406
|
+
const bot = ` └${"─".repeat(W + 2)}┘`;
|
|
1407
|
+
|
|
1408
|
+
console.log(top);
|
|
1409
|
+
console.log(blank());
|
|
1410
|
+
console.log(line(` ${C.bold}STEP 1${C.reset} In your terminal, run:`));
|
|
1411
|
+
console.log(blank());
|
|
1412
|
+
console.log(line(` ${C.cyan}${terminalCmd}${C.reset}`));
|
|
1413
|
+
console.log(blank());
|
|
1414
|
+
console.log(line(` ${C.bold}STEP 2${C.reset} Then in ${agentName}, run:`));
|
|
1415
|
+
console.log(blank());
|
|
1416
|
+
console.log(line(` ${C.cyan}${agentCmd}${C.reset}`));
|
|
1417
|
+
console.log(blank());
|
|
1418
|
+
console.log(bot);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
function printNextSteps(installedHosts, authReused) {
|
|
1390
1422
|
console.log("");
|
|
1391
1423
|
|
|
1392
1424
|
if (installedHosts.length === 0) {
|
|
1425
|
+
printDivider();
|
|
1426
|
+
console.log(` ${C.bold}Installed.${C.reset}`);
|
|
1427
|
+
printDivider();
|
|
1428
|
+
console.log("");
|
|
1393
1429
|
console.log(
|
|
1394
1430
|
` ${C.yellow}!${C.reset} Neither Claude Code nor Codex was found on this machine.`
|
|
1395
1431
|
);
|
|
@@ -1410,50 +1446,207 @@ function printNextSteps(installedHosts) {
|
|
|
1410
1446
|
const hasClaude = installedHosts.includes("Claude Code");
|
|
1411
1447
|
const hasCodex = installedHosts.includes("Codex");
|
|
1412
1448
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
console.log(
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1449
|
+
// Header
|
|
1450
|
+
if (authReused) {
|
|
1451
|
+
console.log(` ${C.bold}Installed. Welcome back!${C.reset}`);
|
|
1452
|
+
} else {
|
|
1453
|
+
console.log(` ${C.bold}Installed.${C.reset}`);
|
|
1454
|
+
}
|
|
1455
|
+
console.log(
|
|
1456
|
+
` ${C.green}✓${C.reset} MCP servers registered (${installedHosts.join(" + ")})`
|
|
1457
|
+
);
|
|
1458
|
+
if (hasCodex) {
|
|
1459
|
+
console.log(` ${C.green}✓${C.reset} Codex Desktop plugin installed`);
|
|
1460
|
+
console.log(` ${C.green}✓${C.reset} Skills installed`);
|
|
1461
|
+
}
|
|
1462
|
+
if (authReused) {
|
|
1419
1463
|
console.log(
|
|
1420
|
-
`
|
|
1464
|
+
` ${C.green}✓${C.reset} Existing session detected — no need to sign in again`
|
|
1421
1465
|
);
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1466
|
+
}
|
|
1467
|
+
console.log("");
|
|
1468
|
+
console.log("");
|
|
1469
|
+
console.log(` ${"═".repeat(63)}`);
|
|
1470
|
+
console.log(` ${C.bold}To launch your first campaign:${C.reset}`);
|
|
1471
|
+
console.log(` ${"═".repeat(63)}`);
|
|
1472
|
+
console.log("");
|
|
1473
|
+
console.log("");
|
|
1474
|
+
|
|
1475
|
+
if (hasClaude) {
|
|
1476
|
+
printAgentBox("Using Claude Code?", "claude", "/sellable:create-campaign");
|
|
1424
1477
|
console.log("");
|
|
1425
|
-
console.log(` ${C.cyan}/sellable:create-campaign${C.reset}`);
|
|
1426
|
-
} else {
|
|
1427
|
-
console.log(` ${C.bold}Open Codex and run:${C.reset}`);
|
|
1428
1478
|
console.log("");
|
|
1429
|
-
|
|
1479
|
+
}
|
|
1480
|
+
if (hasCodex) {
|
|
1481
|
+
printAgentBox("Using Codex?", "codex", "$sellable:create-campaign");
|
|
1482
|
+
console.log("");
|
|
1430
1483
|
}
|
|
1431
1484
|
|
|
1432
1485
|
console.log("");
|
|
1486
|
+
console.log(` ${"─".repeat(63)}`);
|
|
1487
|
+
console.log(` ${C.grey}Need help?${C.reset}`);
|
|
1433
1488
|
console.log(
|
|
1434
|
-
`
|
|
1435
|
-
);
|
|
1436
|
-
|
|
1437
|
-
console.log("");
|
|
1438
|
-
printDivider();
|
|
1439
|
-
console.log("");
|
|
1440
|
-
console.log(` ${C.grey}Need help?${C.reset}`);
|
|
1441
|
-
console.log(
|
|
1442
|
-
` Slack: ${C.cyan}https://join.slack.com/t/ditttoai/shared_invite/zt-3wvs86yau-csKZGP3iGXO3oEiAUmtH9A${C.reset}`
|
|
1489
|
+
` Slack ${C.cyan}https://join.slack.com/t/ditttoai/shared_invite/zt-3wvs86yau-csKZGP3iGXO3oEiAUmtH9A${C.reset}`
|
|
1443
1490
|
);
|
|
1444
|
-
console.log(`
|
|
1491
|
+
console.log(` Email ${C.cyan}admin@dittto.ai${C.reset}`);
|
|
1445
1492
|
console.log(
|
|
1446
|
-
`
|
|
1493
|
+
` LinkedIn ${C.cyan}https://www.linkedin.com/in/csreyes92/${C.reset} ${C.grey}(DM Christian)${C.reset}`
|
|
1447
1494
|
);
|
|
1448
1495
|
console.log("");
|
|
1449
1496
|
const installV = getInstallVersion();
|
|
1450
1497
|
const mcpV = getMcpVersion();
|
|
1451
1498
|
console.log(
|
|
1452
|
-
`
|
|
1499
|
+
` ${C.grey}@sellable/install v${installV} · @sellable/mcp v${mcpV} · Codex plugin v${CODEX_PLUGIN_VERSION}${C.reset}`
|
|
1453
1500
|
);
|
|
1454
1501
|
console.log("");
|
|
1455
1502
|
}
|
|
1456
1503
|
|
|
1504
|
+
function removeTomlSection(content, header) {
|
|
1505
|
+
// Removes a [section] header and its body, up to (but not including) the next [...] header or EOF.
|
|
1506
|
+
// header is the literal string in brackets, e.g. `mcp_servers.sellable` or `plugins."sellable@sellable"`.
|
|
1507
|
+
const pattern = new RegExp(
|
|
1508
|
+
`\\n*\\[${escapeRegExp(header)}\\][^\\n]*\\n[\\s\\S]*?(?=\\n\\[[^\\n]+\\]|$)`
|
|
1509
|
+
);
|
|
1510
|
+
return content.replace(pattern, "");
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
function runUninstall() {
|
|
1514
|
+
console.log("");
|
|
1515
|
+
console.log(` ${C.bold}Uninstalling Sellable…${C.reset}`);
|
|
1516
|
+
console.log("");
|
|
1517
|
+
|
|
1518
|
+
const removed = [];
|
|
1519
|
+
const preserved = [];
|
|
1520
|
+
const skipped = [];
|
|
1521
|
+
|
|
1522
|
+
// 1) Surgical removal from Codex config.toml
|
|
1523
|
+
const codexConfigPath = join(codexHome(), "config.toml");
|
|
1524
|
+
if (existsSync(codexConfigPath)) {
|
|
1525
|
+
try {
|
|
1526
|
+
const before = readFileSync(codexConfigPath, "utf8");
|
|
1527
|
+
const ts = new Date()
|
|
1528
|
+
.toISOString()
|
|
1529
|
+
.replace(/[-:]/g, "")
|
|
1530
|
+
.replace(/\..+/, "");
|
|
1531
|
+
const backupPath = `${codexConfigPath}.bak-sellable-uninstall-${ts}`;
|
|
1532
|
+
writeFileSync(backupPath, before, { mode: 0o600 });
|
|
1533
|
+
|
|
1534
|
+
let after = before;
|
|
1535
|
+
after = removeTomlSection(after, "mcp_servers.sellable");
|
|
1536
|
+
after = removeTomlSection(after, "marketplaces.sellable");
|
|
1537
|
+
after = removeTomlSection(after, 'plugins."sellable@sellable"');
|
|
1538
|
+
after = removeTomlSection(after, 'plugins."sellable@sellable-local"');
|
|
1539
|
+
// Collapse 3+ blank lines that the removals may leave behind.
|
|
1540
|
+
after = after.replace(/\n{3,}/g, "\n\n");
|
|
1541
|
+
if (after !== before) {
|
|
1542
|
+
writeFileSync(codexConfigPath, after, { mode: 0o600 });
|
|
1543
|
+
removed.push(
|
|
1544
|
+
`Codex config (sellable sections removed in ${codexConfigPath})`
|
|
1545
|
+
);
|
|
1546
|
+
removed.push(` backup: ${backupPath}`);
|
|
1547
|
+
} else {
|
|
1548
|
+
skipped.push(`Codex config had no sellable sections`);
|
|
1549
|
+
}
|
|
1550
|
+
} catch (err) {
|
|
1551
|
+
console.log(
|
|
1552
|
+
` ${C.yellow}!${C.reset} Could not edit Codex config: ${err instanceof Error ? err.message : String(err)}`
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
} else {
|
|
1556
|
+
skipped.push(`Codex config not found at ${codexConfigPath}`);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// 2) Claude Code MCP removal
|
|
1560
|
+
if (commandExists("claude")) {
|
|
1561
|
+
const r = spawnSync("claude", ["mcp", "remove", "sellable"], {
|
|
1562
|
+
encoding: "utf8",
|
|
1563
|
+
});
|
|
1564
|
+
if (r.status === 0) {
|
|
1565
|
+
removed.push(`Claude Code MCP (sellable)`);
|
|
1566
|
+
} else {
|
|
1567
|
+
skipped.push(`Claude Code MCP (none registered or already removed)`);
|
|
1568
|
+
}
|
|
1569
|
+
} else {
|
|
1570
|
+
skipped.push(`Claude Code CLI not found`);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// 3) Surgical removal of Sellable-installed artifacts inside ~/.sellable/
|
|
1574
|
+
const sellableDir = join(homedir(), ".sellable");
|
|
1575
|
+
if (existsSync(sellableDir)) {
|
|
1576
|
+
const tracked = [
|
|
1577
|
+
"config.json",
|
|
1578
|
+
"update-check.json",
|
|
1579
|
+
"codex-marketplace",
|
|
1580
|
+
"create-campaign-v2",
|
|
1581
|
+
];
|
|
1582
|
+
for (const name of tracked) {
|
|
1583
|
+
const p = join(sellableDir, name);
|
|
1584
|
+
if (existsSync(p)) {
|
|
1585
|
+
try {
|
|
1586
|
+
rmSync(p, { recursive: true, force: true });
|
|
1587
|
+
removed.push(`${p}`);
|
|
1588
|
+
} catch (err) {
|
|
1589
|
+
console.log(
|
|
1590
|
+
` ${C.yellow}!${C.reset} Could not remove ${p}: ${err instanceof Error ? err.message : String(err)}`
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
// Inventory anything left — these are user-authored and we leave them.
|
|
1596
|
+
let leftovers = [];
|
|
1597
|
+
try {
|
|
1598
|
+
leftovers = readdirSync(sellableDir);
|
|
1599
|
+
} catch {
|
|
1600
|
+
// ignore
|
|
1601
|
+
}
|
|
1602
|
+
if (leftovers.length > 0) {
|
|
1603
|
+
for (const name of leftovers) {
|
|
1604
|
+
preserved.push(`${join(sellableDir, name)}`);
|
|
1605
|
+
}
|
|
1606
|
+
} else {
|
|
1607
|
+
try {
|
|
1608
|
+
rmSync(sellableDir, { recursive: false, force: false });
|
|
1609
|
+
removed.push(`${sellableDir} (empty)`);
|
|
1610
|
+
} catch {
|
|
1611
|
+
// ignore
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
} else {
|
|
1615
|
+
skipped.push(`~/.sellable not present`);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
// 4) Self-shim
|
|
1619
|
+
const shimPath = join(homedir(), ".local", "bin", "sellable");
|
|
1620
|
+
if (existsSync(shimPath)) {
|
|
1621
|
+
try {
|
|
1622
|
+
rmSync(shimPath, { force: true });
|
|
1623
|
+
removed.push(`${shimPath}`);
|
|
1624
|
+
} catch {
|
|
1625
|
+
// ignore
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// Print receipt
|
|
1630
|
+
for (const r of removed) {
|
|
1631
|
+
console.log(` ${C.green}✓${C.reset} Removed ${r}`);
|
|
1632
|
+
}
|
|
1633
|
+
for (const p of preserved) {
|
|
1634
|
+
console.log(
|
|
1635
|
+
` ${C.cyan}•${C.reset} Preserved ${p} ${C.grey}(user-authored)${C.reset}`
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1638
|
+
for (const s of skipped) {
|
|
1639
|
+
console.log(` ${C.grey}·${C.reset} Skipped ${s}`);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
console.log("");
|
|
1643
|
+
console.log(` ${C.bold}Uninstalled.${C.reset}`);
|
|
1644
|
+
console.log("");
|
|
1645
|
+
console.log(` Reinstall anytime:`);
|
|
1646
|
+
console.log(` ${C.cyan}npx -y @sellable/install@latest${C.reset}`);
|
|
1647
|
+
console.log("");
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1457
1650
|
async function main() {
|
|
1458
1651
|
try {
|
|
1459
1652
|
// Phase 116: auth set <token> subcommand — manual-paste fallback when CLI poll fails.
|
|
@@ -1498,6 +1691,10 @@ async function main() {
|
|
|
1498
1691
|
console.log(` Run /sellable:create-campaign in your agent to continue.`);
|
|
1499
1692
|
process.exit(0);
|
|
1500
1693
|
}
|
|
1694
|
+
if (rawArgs[0] === "uninstall") {
|
|
1695
|
+
runUninstall();
|
|
1696
|
+
process.exit(0);
|
|
1697
|
+
}
|
|
1501
1698
|
const opts = parseArgs(process.argv.slice(2));
|
|
1502
1699
|
if (opts.help) {
|
|
1503
1700
|
console.log(usage());
|
|
@@ -1527,19 +1724,12 @@ async function main() {
|
|
|
1527
1724
|
}
|
|
1528
1725
|
|
|
1529
1726
|
const installedHosts = [];
|
|
1727
|
+
let authReused = false;
|
|
1530
1728
|
if (!opts.verifyOnly) {
|
|
1531
1729
|
await loadAuthIfPresent(opts);
|
|
1532
1730
|
const authResult = writeAuth(opts);
|
|
1731
|
+
authReused = Boolean(authResult.reused);
|
|
1533
1732
|
installSelfShim(opts);
|
|
1534
|
-
if (authResult.reused) {
|
|
1535
|
-
logMilestone("Auth ready");
|
|
1536
|
-
} else if (authResult.written) {
|
|
1537
|
-
logMilestone(`Authenticated (workspace: ${opts.workspaceId})`);
|
|
1538
|
-
} else {
|
|
1539
|
-
logMilestone(
|
|
1540
|
-
"Sellable infrastructure ready — sign in happens on first /sellable:create-campaign"
|
|
1541
|
-
);
|
|
1542
|
-
}
|
|
1543
1733
|
|
|
1544
1734
|
if (opts.host === "claude" || opts.host === "all") {
|
|
1545
1735
|
if (installClaude(opts)) {
|
|
@@ -1552,14 +1742,6 @@ async function main() {
|
|
|
1552
1742
|
installedHosts.push("Codex");
|
|
1553
1743
|
}
|
|
1554
1744
|
}
|
|
1555
|
-
|
|
1556
|
-
if (installedHosts.length > 0) {
|
|
1557
|
-
logMilestone(`MCP servers registered (${installedHosts.join(" + ")})`);
|
|
1558
|
-
if (installedHosts.includes("Codex")) {
|
|
1559
|
-
logMilestone("Codex Desktop plugin installed");
|
|
1560
|
-
logMilestone("Skills installed");
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
1745
|
}
|
|
1564
1746
|
|
|
1565
1747
|
if (opts.dryRun) {
|
|
@@ -1570,7 +1752,7 @@ async function main() {
|
|
|
1570
1752
|
}
|
|
1571
1753
|
|
|
1572
1754
|
if (!opts.verifyOnly) {
|
|
1573
|
-
printNextSteps(installedHosts);
|
|
1755
|
+
printNextSteps(installedHosts, authReused);
|
|
1574
1756
|
}
|
|
1575
1757
|
} catch (error) {
|
|
1576
1758
|
console.error(
|