@sellable/install 0.1.38 → 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 +274 -51
- 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,
|
|
@@ -20,7 +21,9 @@ const DEFAULT_SERVER_PACKAGE =
|
|
|
20
21
|
function getInstallVersion() {
|
|
21
22
|
try {
|
|
22
23
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
23
|
-
const pkg = JSON.parse(
|
|
24
|
+
const pkg = JSON.parse(
|
|
25
|
+
readFileSync(join(here, "..", "package.json"), "utf8")
|
|
26
|
+
);
|
|
24
27
|
return pkg.version || "unknown";
|
|
25
28
|
} catch {
|
|
26
29
|
return "unknown";
|
|
@@ -1380,14 +1383,48 @@ function verify(opts) {
|
|
|
1380
1383
|
}
|
|
1381
1384
|
}
|
|
1382
1385
|
|
|
1383
|
-
function
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
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) {
|
|
1388
1421
|
console.log("");
|
|
1389
1422
|
|
|
1390
1423
|
if (installedHosts.length === 0) {
|
|
1424
|
+
printDivider();
|
|
1425
|
+
console.log(` ${C.bold}Installed.${C.reset}`);
|
|
1426
|
+
printDivider();
|
|
1427
|
+
console.log("");
|
|
1391
1428
|
console.log(
|
|
1392
1429
|
` ${C.yellow}!${C.reset} Neither Claude Code nor Codex was found on this machine.`
|
|
1393
1430
|
);
|
|
@@ -1408,52 +1445,255 @@ function printNextSteps(installedHosts) {
|
|
|
1408
1445
|
const hasClaude = installedHosts.includes("Claude Code");
|
|
1409
1446
|
const hasCodex = installedHosts.includes("Codex");
|
|
1410
1447
|
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
console.log(
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
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) {
|
|
1417
1462
|
console.log(
|
|
1418
|
-
`
|
|
1463
|
+
` ${C.green}✓${C.reset} Existing session detected — no need to sign in again`
|
|
1419
1464
|
);
|
|
1420
|
-
}
|
|
1421
|
-
|
|
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");
|
|
1422
1476
|
console.log("");
|
|
1423
|
-
console.log(` ${C.cyan}/sellable:create-campaign${C.reset}`);
|
|
1424
|
-
} else {
|
|
1425
|
-
console.log(` ${C.bold}Open Codex and run:${C.reset}`);
|
|
1426
1477
|
console.log("");
|
|
1427
|
-
|
|
1478
|
+
}
|
|
1479
|
+
if (hasCodex) {
|
|
1480
|
+
printAgentBox("Using Codex?", "codex", "$sellable:create-campaign");
|
|
1481
|
+
console.log("");
|
|
1428
1482
|
}
|
|
1429
1483
|
|
|
1430
1484
|
console.log("");
|
|
1485
|
+
console.log(` ${"─".repeat(63)}`);
|
|
1486
|
+
console.log(` ${C.grey}Need help?${C.reset}`);
|
|
1431
1487
|
console.log(
|
|
1432
|
-
`
|
|
1433
|
-
);
|
|
1434
|
-
|
|
1435
|
-
console.log("");
|
|
1436
|
-
printDivider();
|
|
1437
|
-
console.log("");
|
|
1438
|
-
console.log(` ${C.grey}Need help?${C.reset}`);
|
|
1439
|
-
console.log(
|
|
1440
|
-
` 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}`
|
|
1441
1489
|
);
|
|
1442
|
-
console.log(`
|
|
1490
|
+
console.log(` Email ${C.cyan}admin@dittto.ai${C.reset}`);
|
|
1443
1491
|
console.log(
|
|
1444
|
-
`
|
|
1492
|
+
` LinkedIn ${C.cyan}https://www.linkedin.com/in/csreyes92/${C.reset} ${C.grey}(DM Christian)${C.reset}`
|
|
1445
1493
|
);
|
|
1446
1494
|
console.log("");
|
|
1447
1495
|
const installV = getInstallVersion();
|
|
1448
1496
|
const mcpV = getMcpVersion();
|
|
1449
1497
|
console.log(
|
|
1450
|
-
`
|
|
1498
|
+
` ${C.grey}@sellable/install v${installV} · @sellable/mcp v${mcpV} · Codex plugin v${CODEX_PLUGIN_VERSION}${C.reset}`
|
|
1451
1499
|
);
|
|
1452
1500
|
console.log("");
|
|
1453
1501
|
}
|
|
1454
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
|
+
|
|
1455
1649
|
async function main() {
|
|
1456
1650
|
try {
|
|
1651
|
+
// Phase 116: auth set <token> subcommand — manual-paste fallback when CLI poll fails.
|
|
1652
|
+
// MUST be parsed BEFORE parseArgs() since parseArgs throws on non-flag args like "auth".
|
|
1653
|
+
const rawArgs = process.argv.slice(2);
|
|
1654
|
+
if (rawArgs[0] === "auth") {
|
|
1655
|
+
if (rawArgs[1] !== "set") {
|
|
1656
|
+
console.error(
|
|
1657
|
+
`Unknown auth subcommand: ${rawArgs[1] ?? "(none)"}\nKnown: set <token>`
|
|
1658
|
+
);
|
|
1659
|
+
process.exit(2);
|
|
1660
|
+
}
|
|
1661
|
+
const token = rawArgs[2];
|
|
1662
|
+
if (!token) {
|
|
1663
|
+
console.error(
|
|
1664
|
+
"Usage: sellable auth set <token>\n" +
|
|
1665
|
+
"Get the token from the Sellable browser confirm page (after clicking the magic link)."
|
|
1666
|
+
);
|
|
1667
|
+
process.exit(2);
|
|
1668
|
+
}
|
|
1669
|
+
// Verification iter 1 broadening: accept skt_(live|test|dev)_ prefixes so
|
|
1670
|
+
// non-prod tokens (existing in some environments) are not rejected.
|
|
1671
|
+
if (!/^skt_(live|test|dev)_[A-Za-z0-9_-]{20,}$/.test(token)) {
|
|
1672
|
+
console.error(
|
|
1673
|
+
`That doesn't look like a Sellable token (expected skt_live_, skt_test_, or skt_dev_ prefix). Got: ${token.slice(0, 16)}…`
|
|
1674
|
+
);
|
|
1675
|
+
process.exit(2);
|
|
1676
|
+
}
|
|
1677
|
+
// Bypass writeAuth() — its workspaceId guard early-returns on missing
|
|
1678
|
+
// workspaceId, but on the auth-set path we don't have one yet (next MCP
|
|
1679
|
+
// call hydrates it). Write the same shape directly.
|
|
1680
|
+
// Skip installSelfShim() — by definition the user has the shim already
|
|
1681
|
+
// (they invoked `sellable auth set` from it).
|
|
1682
|
+
const apiUrl = process.env.SELLABLE_API_URL || DEFAULT_API_URL;
|
|
1683
|
+
writeJson(
|
|
1684
|
+
authPath(),
|
|
1685
|
+
{ token, activeWorkspaceId: null, apiUrl },
|
|
1686
|
+
{ dryRun: false }
|
|
1687
|
+
);
|
|
1688
|
+
console.log(`✓ Token saved to ${authPath()}`);
|
|
1689
|
+
console.log(` apiUrl: ${apiUrl}`);
|
|
1690
|
+
console.log(` Run /sellable:create-campaign in your agent to continue.`);
|
|
1691
|
+
process.exit(0);
|
|
1692
|
+
}
|
|
1693
|
+
if (rawArgs[0] === "uninstall") {
|
|
1694
|
+
runUninstall();
|
|
1695
|
+
process.exit(0);
|
|
1696
|
+
}
|
|
1457
1697
|
const opts = parseArgs(process.argv.slice(2));
|
|
1458
1698
|
if (opts.help) {
|
|
1459
1699
|
console.log(usage());
|
|
@@ -1483,19 +1723,12 @@ async function main() {
|
|
|
1483
1723
|
}
|
|
1484
1724
|
|
|
1485
1725
|
const installedHosts = [];
|
|
1726
|
+
let authReused = false;
|
|
1486
1727
|
if (!opts.verifyOnly) {
|
|
1487
1728
|
await loadAuthIfPresent(opts);
|
|
1488
1729
|
const authResult = writeAuth(opts);
|
|
1730
|
+
authReused = Boolean(authResult.reused);
|
|
1489
1731
|
installSelfShim(opts);
|
|
1490
|
-
if (authResult.reused) {
|
|
1491
|
-
logMilestone("Auth ready");
|
|
1492
|
-
} else if (authResult.written) {
|
|
1493
|
-
logMilestone(`Authenticated (workspace: ${opts.workspaceId})`);
|
|
1494
|
-
} else {
|
|
1495
|
-
logMilestone(
|
|
1496
|
-
"Sellable infrastructure ready — sign in happens on first /sellable:create-campaign"
|
|
1497
|
-
);
|
|
1498
|
-
}
|
|
1499
1732
|
|
|
1500
1733
|
if (opts.host === "claude" || opts.host === "all") {
|
|
1501
1734
|
if (installClaude(opts)) {
|
|
@@ -1508,16 +1741,6 @@ async function main() {
|
|
|
1508
1741
|
installedHosts.push("Codex");
|
|
1509
1742
|
}
|
|
1510
1743
|
}
|
|
1511
|
-
|
|
1512
|
-
if (installedHosts.length > 0) {
|
|
1513
|
-
logMilestone(
|
|
1514
|
-
`MCP servers registered (${installedHosts.join(" + ")})`
|
|
1515
|
-
);
|
|
1516
|
-
if (installedHosts.includes("Codex")) {
|
|
1517
|
-
logMilestone("Codex Desktop plugin installed");
|
|
1518
|
-
logMilestone("Skills installed");
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
1744
|
}
|
|
1522
1745
|
|
|
1523
1746
|
if (opts.dryRun) {
|
|
@@ -1528,7 +1751,7 @@ async function main() {
|
|
|
1528
1751
|
}
|
|
1529
1752
|
|
|
1530
1753
|
if (!opts.verifyOnly) {
|
|
1531
|
-
printNextSteps(installedHosts);
|
|
1754
|
+
printNextSteps(installedHosts, authReused);
|
|
1532
1755
|
}
|
|
1533
1756
|
} catch (error) {
|
|
1534
1757
|
console.error(
|