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