@sellable/install 0.1.27 → 0.1.30

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.
@@ -5,17 +5,17 @@ import {
5
5
  mkdirSync,
6
6
  readFileSync,
7
7
  rmSync,
8
+ symlinkSync,
8
9
  writeFileSync,
9
10
  } from "node:fs";
10
11
  import { homedir } from "node:os";
11
- import { dirname, join } from "node:path";
12
- import { stdin as input, stdout as output } from "node:process";
13
- import { createInterface } from "node:readline/promises";
12
+ import { dirname, join, relative } from "node:path";
13
+ import { stdout as output } from "node:process";
14
14
 
15
15
  const DEFAULT_API_URL = "https://app.sellable.dev";
16
16
  const DEFAULT_SERVER_PACKAGE =
17
17
  process.env.SELLABLE_MCP_PACKAGE || "@sellable/mcp@latest";
18
- const CODEX_PLUGIN_VERSION = "0.1.26";
18
+ const CODEX_PLUGIN_VERSION = "0.1.27";
19
19
  const CODEX_PLUGIN_COMPAT_VERSIONS = [
20
20
  "0.1.8",
21
21
  "0.1.9",
@@ -35,10 +35,67 @@ const CODEX_PLUGIN_COMPAT_VERSIONS = [
35
35
  "0.1.23",
36
36
  "0.1.24",
37
37
  "0.1.25",
38
+ "0.1.26",
38
39
  ];
39
40
  const INSTALL_PACKAGE_SPEC =
40
41
  process.env.SELLABLE_INSTALL_PACKAGE_SPEC || "@sellable/install@latest";
41
42
 
43
+ const useColor = Boolean(output.isTTY) && process.env.NO_COLOR === undefined;
44
+ const C = {
45
+ reset: useColor ? "\x1b[0m" : "",
46
+ green: useColor ? "\x1b[32m" : "",
47
+ cyan: useColor ? "\x1b[36m" : "",
48
+ yellow: useColor ? "\x1b[33m" : "",
49
+ grey: useColor ? "\x1b[90m" : "",
50
+ bold: useColor ? "\x1b[1m" : "",
51
+ magenta: useColor ? "\x1b[35m" : "",
52
+ };
53
+
54
+ let VERBOSE = false;
55
+
56
+ function logVerbose(line) {
57
+ if (VERBOSE) console.log(line);
58
+ }
59
+
60
+ function logMilestone(line) {
61
+ console.log(` ${C.green}✓${C.reset} ${line}`);
62
+ }
63
+
64
+ function logWarn(line) {
65
+ console.log(` ${C.yellow}!${C.reset} ${line}`);
66
+ }
67
+
68
+ function logStep(line) {
69
+ console.log(` ${C.grey}${line}${C.reset}`);
70
+ }
71
+
72
+ function printBanner() {
73
+ const cols = output.columns || 80;
74
+ if (output.isTTY && cols >= 70) {
75
+ const lines = [
76
+ " ███████╗ ███████╗ ██╗ ██╗ █████╗ ██████╗ ██╗ ███████╗",
77
+ " ██╔════╝ ██╔════╝ ██║ ██║ ██╔══██╗ ██╔══██╗ ██║ ██╔════╝",
78
+ " ███████╗ █████╗ ██║ ██║ ███████║ ██████╔╝ ██║ █████╗ ",
79
+ " ╚════██║ ██╔══╝ ██║ ██║ ██╔══██║ ██╔══██╗ ██║ ██╔══╝ ",
80
+ " ███████║ ███████╗ ███████╗ ███████╗██║ ██║ ██████╔╝ ███████╗ ███████╗",
81
+ " ╚══════╝ ╚══════╝ ╚══════╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚══════╝",
82
+ ];
83
+ console.log("");
84
+ for (const line of lines) console.log(`${C.magenta}${line}${C.reset}`);
85
+ console.log("");
86
+ } else {
87
+ console.log("");
88
+ console.log(`${C.bold}${C.magenta}SELLABLE${C.reset}`);
89
+ console.log("");
90
+ }
91
+ }
92
+
93
+ function printDivider() {
94
+ console.log(
95
+ `${C.cyan}────────────────────────────────────────────────────────────${C.reset}`
96
+ );
97
+ }
98
+
42
99
  function usage() {
43
100
  return `Sellable agent installer
44
101
 
@@ -55,14 +112,20 @@ Options:
55
112
  --mcp-package <pkg> MCP server package. Default: ${DEFAULT_SERVER_PACKAGE}
56
113
  --local-command <cmd> Local MCP command for --server local.
57
114
  --hosted-url <url> Hosted MCP URL for --server hosted.
115
+ --verbose Print every file write and shell command.
58
116
  --dry-run Print actions without writing or running host commands.
59
117
  --verify-only Verify installed host config where possible.
60
118
  --help Show help.
61
119
 
62
120
  Auth:
63
- Sign up or log in at ${DEFAULT_API_URL}/settings, create an API token, then
64
- pass it with --token or SELLABLE_TOKEN. In an interactive terminal, the
65
- installer will prompt for missing auth values.
121
+ Install is auth-free by default. Sign in happens on the first run of
122
+ /sellable:create-campaign in Claude Code or Codex, where the agent walks
123
+ you through signup or sign-in and stores credentials in
124
+ ~/.sellable/config.json.
125
+
126
+ For CI/scripted installs, pass --token + --workspace-id or set
127
+ SELLABLE_TOKEN + SELLABLE_WORKSPACE_ID and the installer will write the
128
+ config file directly without prompting.
66
129
  `;
67
130
  }
68
131
 
@@ -78,6 +141,7 @@ function parseArgs(argv) {
78
141
  hostedUrl: process.env.SELLABLE_MCP_HOSTED_URL || "",
79
142
  dryRun: false,
80
143
  verifyOnly: false,
144
+ verbose: false,
81
145
  };
82
146
 
83
147
  for (let i = 0; i < argv.length; i += 1) {
@@ -113,6 +177,8 @@ function parseArgs(argv) {
113
177
  opts.dryRun = true;
114
178
  } else if (arg === "--verify-only") {
115
179
  opts.verifyOnly = true;
180
+ } else if (arg === "--verbose") {
181
+ opts.verbose = true;
116
182
  } else {
117
183
  throw new Error(`Unknown option: ${arg}`);
118
184
  }
@@ -136,13 +202,13 @@ function redact(value) {
136
202
 
137
203
  function run(command, args, opts = {}) {
138
204
  const rendered = [command, ...args].join(" ");
139
- console.log(
140
- `+ ${rendered.replace(opts.token || "__NO_TOKEN__", "[redacted-token]")}`
205
+ logVerbose(
206
+ `${C.grey}+ ${rendered.replace(opts.token || "__NO_TOKEN__", "[redacted-token]")}${C.reset}`
141
207
  );
142
208
  if (opts.dryRun) return { status: 0, stdout: "", stderr: "" };
143
209
  const result = spawnSync(command, args, {
144
210
  encoding: "utf8",
145
- stdio: "pipe",
211
+ stdio: VERBOSE ? "inherit" : "pipe",
146
212
  });
147
213
  if (result.status !== 0 && !opts.allowFail) {
148
214
  const stderr = (result.stderr || "").trim();
@@ -158,10 +224,6 @@ function commandExists(command) {
158
224
  return result.status === 0;
159
225
  }
160
226
 
161
- function isInteractive() {
162
- return Boolean(input.isTTY && output.isTTY);
163
- }
164
-
165
227
  function authPath() {
166
228
  return join(homedir(), ".sellable", "config.json");
167
229
  }
@@ -189,7 +251,7 @@ function readStoredAuth() {
189
251
  return raw ? normalizeStoredAuth(raw) : null;
190
252
  }
191
253
 
192
- async function promptForMissingAuth(opts) {
254
+ async function loadAuthIfPresent(opts) {
193
255
  if (opts.token && opts.workspaceId) return opts;
194
256
 
195
257
  const stored = readStoredAuth();
@@ -198,58 +260,19 @@ async function promptForMissingAuth(opts) {
198
260
  opts.workspaceId ||= stored.workspaceId;
199
261
  opts.apiUrl = opts.apiUrl || stored.apiUrl || DEFAULT_API_URL;
200
262
  opts.authFromExistingConfig = true;
201
- console.log(`Using existing Sellable auth config: ${authPath()}`);
202
- return opts;
203
- }
204
-
205
- if (!isInteractive()) {
206
- throw new Error(
207
- [
208
- "Missing Sellable token/workspace id.",
209
- `Create a token at ${opts.apiUrl}/settings, then rerun:`,
210
- ` npx -y ${INSTALL_PACKAGE_SPEC} --host all --token <token> --workspace-id <workspace_id>`,
211
- "",
212
- "You can also use SELLABLE_TOKEN and SELLABLE_WORKSPACE_ID.",
213
- ].join("\n")
214
- );
215
- }
216
-
217
- console.log("");
218
- console.log(
219
- "Sellable needs one API token to connect Claude/Codex to your workspace."
220
- );
221
- console.log(
222
- `Open ${opts.apiUrl}/settings, create a token, then paste the values below.`
223
- );
224
- console.log(`Auth will be stored once at ${authPath()}.`);
225
- console.log("");
226
-
227
- const rl = createInterface({ input, output });
228
- try {
229
- if (!opts.token) {
230
- opts.token = (await rl.question("Sellable API token: ")).trim();
231
- }
232
- if (!opts.workspaceId) {
233
- opts.workspaceId = (await rl.question("Sellable workspace id: ")).trim();
234
- }
235
- } finally {
236
- rl.close();
237
- }
238
-
239
- if (!opts.token || !opts.workspaceId) {
240
- throw new Error("Sellable token and workspace id are both required.");
241
263
  }
242
-
243
264
  return opts;
244
265
  }
245
266
 
246
267
  function writeJson(path, data, opts) {
247
- const redacted = JSON.stringify(
248
- { ...data, token: redact(data.token) },
249
- null,
250
- 2
251
- );
252
- console.log(`Writing ${path}: ${redacted}`);
268
+ if (VERBOSE) {
269
+ const redacted = JSON.stringify(
270
+ { ...data, token: redact(data.token) },
271
+ null,
272
+ 2
273
+ );
274
+ logVerbose(`${C.grey}Writing ${path}: ${redacted}${C.reset}`);
275
+ }
253
276
  if (opts.dryRun) return;
254
277
  mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
255
278
  writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`, { mode: 0o600 });
@@ -316,12 +339,25 @@ function upsertTomlBoolean(content, tableName, key, value) {
316
339
  }
317
340
 
318
341
  function writeFile(path, content, opts, mode = 0o644) {
319
- console.log(`Writing ${path}`);
342
+ logVerbose(`${C.grey}Writing ${path}${C.reset}`);
320
343
  if (opts.dryRun) return;
321
344
  mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
322
345
  writeFileSync(path, content, { mode });
323
346
  }
324
347
 
348
+ function ensureSymlink(target, linkPath, opts) {
349
+ logVerbose(`${C.grey}Linking ${linkPath} -> ${target}${C.reset}`);
350
+ if (opts.dryRun) return;
351
+ mkdirSync(dirname(linkPath), { recursive: true, mode: 0o700 });
352
+ try {
353
+ rmSync(linkPath, { recursive: true, force: true });
354
+ } catch {
355
+ // ignore
356
+ }
357
+ const rel = relative(dirname(linkPath), target);
358
+ symlinkSync(rel, linkPath, "dir");
359
+ }
360
+
325
361
  function codexPluginManifest(opts) {
326
362
  return {
327
363
  name: "sellable",
@@ -1061,13 +1097,7 @@ function installCodexDesktopPlugin(opts) {
1061
1097
  );
1062
1098
  const pluginRoot = join(marketplaceRoot, "plugins", "sellable");
1063
1099
  const cacheRoot = join(home, "plugins", "cache", "sellable", "sellable");
1064
- const pluginCache = join(cacheRoot, CODEX_PLUGIN_VERSION);
1065
- const pluginCacheVersions = [
1066
- CODEX_PLUGIN_VERSION,
1067
- ...CODEX_PLUGIN_COMPAT_VERSIONS.filter(
1068
- (version) => version !== CODEX_PLUGIN_VERSION
1069
- ),
1070
- ];
1100
+ const canonicalCachePath = join(cacheRoot, CODEX_PLUGIN_VERSION);
1071
1101
 
1072
1102
  const marketplace = {
1073
1103
  name: "sellable",
@@ -1109,23 +1139,24 @@ function installCodexDesktopPlugin(opts) {
1109
1139
  if (!opts.dryRun) {
1110
1140
  rmSync(cacheRoot, { recursive: true, force: true });
1111
1141
  }
1112
- for (const version of pluginCacheVersions) {
1113
- const cachePath = join(cacheRoot, version);
1114
- writeFile(
1115
- join(cachePath, ".codex-plugin", "plugin.json"),
1116
- `${JSON.stringify(
1117
- { ...manifest, version: CODEX_PLUGIN_VERSION },
1118
- null,
1119
- 2
1120
- )}\n`,
1121
- opts
1122
- );
1123
- writeFile(
1124
- join(cachePath, ".mcp.json"),
1125
- `${JSON.stringify(mcp, null, 2)}\n`,
1126
- opts
1127
- );
1128
- writeCodexPluginSkills(cachePath, opts);
1142
+
1143
+ // Write canonical version once
1144
+ writeFile(
1145
+ join(canonicalCachePath, ".codex-plugin", "plugin.json"),
1146
+ `${JSON.stringify({ ...manifest, version: CODEX_PLUGIN_VERSION }, null, 2)}\n`,
1147
+ opts
1148
+ );
1149
+ writeFile(
1150
+ join(canonicalCachePath, ".mcp.json"),
1151
+ `${JSON.stringify(mcp, null, 2)}\n`,
1152
+ opts
1153
+ );
1154
+ writeCodexPluginSkills(canonicalCachePath, opts);
1155
+
1156
+ // Symlink older compat versions to canonical (one ln per version, no file copy)
1157
+ for (const version of CODEX_PLUGIN_COMPAT_VERSIONS) {
1158
+ if (version === CODEX_PLUGIN_VERSION) continue;
1159
+ ensureSymlink(canonicalCachePath, join(cacheRoot, version), opts);
1129
1160
  }
1130
1161
 
1131
1162
  if (!opts.dryRun) {
@@ -1161,28 +1192,31 @@ enabled = false`
1161
1192
  );
1162
1193
  writeFileSync(configPath, `${content.trimEnd()}\n`, { mode: 0o600 });
1163
1194
  } else {
1164
- console.log(`+ upsert [marketplaces.sellable] in ${configPath}`);
1165
- console.log(`+ enable [plugins."sellable@sellable"] in ${configPath}`);
1166
- console.log(
1167
- `+ disable stale [plugins."sellable@sellable-local"] in ${configPath}`
1195
+ logVerbose(
1196
+ `${C.grey}+ upsert [marketplaces.sellable] in ${configPath}${C.reset}`
1168
1197
  );
1169
- console.log(
1170
- `+ enable [features].default_mode_request_user_input in ${configPath}`
1198
+ logVerbose(
1199
+ `${C.grey}+ enable [plugins."sellable@sellable"] in ${configPath}${C.reset}`
1200
+ );
1201
+ logVerbose(
1202
+ `${C.grey}+ enable [features].default_mode_request_user_input in ${configPath}${C.reset}`
1171
1203
  );
1172
1204
  }
1173
1205
 
1174
- console.log("Codex Desktop plugin installed:");
1175
- console.log(`- marketplace: ${marketplaceRoot}`);
1176
- console.log(`- plugin: sellable@sellable`);
1177
- console.log(`- cache: ${pluginCache}`);
1178
- console.log(`- compat: ${CODEX_PLUGIN_COMPAT_VERSIONS.join(", ")}`);
1206
+ return {
1207
+ marketplaceRoot,
1208
+ canonicalCachePath,
1209
+ compatCount: CODEX_PLUGIN_COMPAT_VERSIONS.length,
1210
+ };
1179
1211
  }
1180
1212
 
1181
1213
  function writeAuth(opts) {
1182
1214
  if (!opts.token || !opts.workspaceId) {
1183
- throw new Error(
1184
- `Missing Sellable token/workspace id. Create a token at ${opts.apiUrl}/settings, then rerun with --token and --workspace-id or SELLABLE_TOKEN and SELLABLE_WORKSPACE_ID.`
1185
- );
1215
+ return { written: false, reused: false };
1216
+ }
1217
+
1218
+ if (opts.authFromExistingConfig) {
1219
+ return { written: false, reused: true };
1186
1220
  }
1187
1221
 
1188
1222
  const config = {
@@ -1190,16 +1224,8 @@ function writeAuth(opts) {
1190
1224
  activeWorkspaceId: opts.workspaceId,
1191
1225
  apiUrl: opts.apiUrl,
1192
1226
  };
1193
-
1194
- if (opts.authFromExistingConfig) {
1195
- console.log(
1196
- `Leaving existing Sellable auth config unchanged: ${authPath()}`
1197
- );
1198
- return;
1199
- }
1200
-
1201
- const sellablePath = authPath();
1202
- writeJson(sellablePath, config, opts);
1227
+ writeJson(authPath(), config, opts);
1228
+ return { written: true, reused: false };
1203
1229
  }
1204
1230
 
1205
1231
  function installSelfShim(opts) {
@@ -1233,7 +1259,7 @@ function installClaude(opts) {
1233
1259
  const message =
1234
1260
  "Claude CLI not found. Install/login to Claude Code, then rerun: sellable --host claude";
1235
1261
  if (opts.host === "all") {
1236
- console.log(`Skipping Claude Code: ${message}`);
1262
+ logWarn(`Skipping Claude Code: ${message}`);
1237
1263
  return false;
1238
1264
  }
1239
1265
  throw new Error(message);
@@ -1265,15 +1291,15 @@ function installCodex(opts) {
1265
1291
  const message =
1266
1292
  "Codex CLI not found. Install/login to Codex, then rerun: sellable --host codex";
1267
1293
  if (opts.host === "all") {
1268
- console.log(`Skipping Codex: ${message}`);
1269
- return false;
1294
+ logWarn(`Skipping Codex: ${message}`);
1295
+ return { installed: false };
1270
1296
  }
1271
1297
  throw new Error(message);
1272
1298
  }
1273
1299
  if (opts.server === "hosted") {
1274
1300
  run("codex", ["mcp", "add", "sellable", "--url", opts.hostedUrl], opts);
1275
- installCodexDesktopPlugin(opts);
1276
- return true;
1301
+ const info = installCodexDesktopPlugin(opts);
1302
+ return { installed: true, ...info };
1277
1303
  }
1278
1304
  const [command, args] = mcpCommand(opts);
1279
1305
  run("codex", ["mcp", "remove", "sellable"], {
@@ -1282,27 +1308,35 @@ function installCodex(opts) {
1282
1308
  allowFail: true,
1283
1309
  });
1284
1310
  run("codex", ["mcp", "add", "sellable", "--", command, ...args], opts);
1285
- installCodexDesktopPlugin(opts);
1286
- return true;
1311
+ const info = installCodexDesktopPlugin(opts);
1312
+ return { installed: true, ...info };
1287
1313
  }
1288
1314
 
1289
1315
  function verify(opts) {
1290
1316
  const stored = readStoredAuth();
1291
- if (!stored?.token || !stored?.workspaceId) {
1292
- throw new Error(
1293
- `Sellable auth config missing or incomplete: ${authPath()}`
1294
- );
1317
+ const checks = [];
1318
+ if (stored?.token && stored?.workspaceId) {
1319
+ checks.push({ ok: true, label: `Auth config: ${authPath()}` });
1320
+ } else {
1321
+ checks.push({
1322
+ ok: true,
1323
+ label: `Auth: not yet signed in — sign in on first run of /sellable:create-campaign`,
1324
+ });
1295
1325
  }
1296
- console.log(`Sellable auth config present: ${authPath()}`);
1326
+
1297
1327
  if (opts.host === "claude" || opts.host === "all") {
1298
- console.log(
1299
- commandExists("claude") ? "Claude CLI present" : "Claude CLI missing"
1300
- );
1328
+ checks.push({
1329
+ ok: commandExists("claude"),
1330
+ label: commandExists("claude")
1331
+ ? "Claude CLI present"
1332
+ : "Claude CLI missing",
1333
+ });
1301
1334
  }
1302
1335
  if (opts.host === "codex" || opts.host === "all") {
1303
- console.log(
1304
- commandExists("codex") ? "Codex CLI present" : "Codex CLI missing"
1305
- );
1336
+ checks.push({
1337
+ ok: commandExists("codex"),
1338
+ label: commandExists("codex") ? "Codex CLI present" : "Codex CLI missing",
1339
+ });
1306
1340
  const pluginPath = join(
1307
1341
  codexHome(),
1308
1342
  "plugins",
@@ -1324,45 +1358,81 @@ function verify(opts) {
1324
1358
  "sellable-create-campaign",
1325
1359
  "SKILL.md"
1326
1360
  );
1327
- console.log(
1328
- existsSync(pluginPath)
1361
+ checks.push({
1362
+ ok: existsSync(pluginPath),
1363
+ label: existsSync(pluginPath)
1329
1364
  ? "Codex Desktop plugin present"
1330
- : "Codex Desktop plugin missing"
1331
- );
1332
- console.log(
1333
- existsSync(skillPath)
1365
+ : "Codex Desktop plugin missing",
1366
+ });
1367
+ checks.push({
1368
+ ok: existsSync(skillPath),
1369
+ label: existsSync(skillPath)
1334
1370
  ? "Codex skill bundle present"
1335
- : "Codex skill bundle missing"
1336
- );
1371
+ : "Codex skill bundle missing",
1372
+ });
1337
1373
  const configPath = join(codexHome(), "config.toml");
1338
1374
  const configContent = existsSync(configPath)
1339
1375
  ? readFileSync(configPath, "utf8")
1340
1376
  : "";
1341
- console.log(
1342
- configContent.includes("default_mode_request_user_input = true")
1343
- ? "Codex Default-mode request_user_input enabled"
1344
- : "Codex Default-mode request_user_input missing"
1377
+ const hasFlag = configContent.includes(
1378
+ "default_mode_request_user_input = true"
1345
1379
  );
1380
+ checks.push({
1381
+ ok: hasFlag,
1382
+ label: hasFlag
1383
+ ? "Codex Default-mode request_user_input enabled"
1384
+ : "Codex Default-mode request_user_input missing",
1385
+ });
1386
+ }
1387
+
1388
+ for (const c of checks) {
1389
+ if (c.ok) logMilestone(c.label);
1390
+ else logWarn(c.label);
1346
1391
  }
1347
1392
  }
1348
1393
 
1349
1394
  function printNextSteps(installedHosts) {
1350
1395
  console.log("");
1351
- console.log("Next steps:");
1396
+ printDivider();
1397
+ console.log(` ${C.bold}Next steps${C.reset}`);
1398
+ printDivider();
1399
+ console.log("");
1352
1400
  if (installedHosts.length > 0) {
1353
1401
  console.log(
1354
- `1. Fully quit and reopen ${installedHosts.join(" and ")} so MCP tools reload.`
1402
+ ` 1. Quit and reopen ${installedHosts.join(" and ")} so MCP tools reload.`
1355
1403
  );
1356
- console.log("2. Start a new thread and choose Sellable Create Campaign.");
1404
+ console.log(" 2. Start a new thread and run:");
1405
+ if (installedHosts.includes("Claude Code")) {
1406
+ console.log(
1407
+ ` ${C.cyan}/sellable:create-campaign${C.reset} ${C.grey}(Claude Code)${C.reset}`
1408
+ );
1409
+ }
1410
+ if (installedHosts.includes("Codex")) {
1411
+ console.log(
1412
+ ` ${C.cyan}$sellable:create-campaign${C.reset} ${C.grey}(Codex)${C.reset}`
1413
+ );
1414
+ }
1415
+ console.log(
1416
+ ` ${C.grey}Sellable will walk you through signup or sign-in on first use.${C.reset}`
1417
+ );
1418
+ console.log("");
1419
+ console.log(
1420
+ ` Verify any time: ${C.cyan}sellable --verify-only --host all${C.reset}`
1421
+ );
1422
+ console.log(` Need help?`);
1357
1423
  console.log(
1358
- "3. If tools do not appear, run: sellable --verify-only --host all"
1424
+ ` Slack: ${C.cyan}https://join.slack.com/t/ditttoai/shared_invite/zt-3wvs86yau-csKZGP3iGXO3oEiAUmtH9A${C.reset}`
1359
1425
  );
1426
+ console.log(` Email: ${C.cyan}admin@dittto.ai${C.reset}`);
1360
1427
  } else {
1361
1428
  console.log(
1362
- "1. Install Claude Code or Codex, then rerun this installer for that host."
1429
+ " 1. Install Claude Code or Codex, then rerun this installer for that host."
1430
+ );
1431
+ console.log(
1432
+ ` 2. Verify auth later with: ${C.cyan}sellable --verify-only --host all${C.reset}`
1363
1433
  );
1364
- console.log("2. Verify auth later with: sellable --verify-only --host all");
1365
1434
  }
1435
+ console.log("");
1366
1436
  }
1367
1437
 
1368
1438
  async function main() {
@@ -1373,38 +1443,84 @@ async function main() {
1373
1443
  return;
1374
1444
  }
1375
1445
 
1376
- console.log("Sellable installer");
1377
- console.log(`- host: ${opts.host}`);
1378
- console.log(`- server: ${opts.server}`);
1379
- if (opts.server === "package") {
1380
- console.log(`- mcp package: ${opts.mcpPackage}`);
1446
+ VERBOSE = Boolean(opts.verbose);
1447
+
1448
+ if (!opts.verifyOnly) {
1449
+ printBanner();
1450
+ console.log(
1451
+ ` ${C.bold}Connecting Sellable to Claude Code and Codex…${C.reset}`
1452
+ );
1453
+ console.log("");
1454
+ } else {
1455
+ printBanner();
1456
+ console.log(` ${C.bold}Verifying Sellable install…${C.reset}`);
1457
+ console.log("");
1458
+ }
1459
+
1460
+ if (VERBOSE) {
1461
+ logStep(`host: ${opts.host}`);
1462
+ logStep(`server: ${opts.server}`);
1463
+ if (opts.server === "package") logStep(`mcp package: ${opts.mcpPackage}`);
1464
+ logStep(`api: ${opts.apiUrl}`);
1465
+ logStep(`token: ${opts.token ? redact(opts.token) : "(missing)"}`);
1381
1466
  }
1382
- console.log(`- api: ${opts.apiUrl}`);
1383
- console.log(`- token: ${opts.token ? redact(opts.token) : "(missing)"}`);
1384
1467
 
1385
1468
  const installedHosts = [];
1386
1469
  if (!opts.verifyOnly) {
1387
- await promptForMissingAuth(opts);
1388
- writeAuth(opts);
1470
+ await loadAuthIfPresent(opts);
1471
+ const authResult = writeAuth(opts);
1389
1472
  installSelfShim(opts);
1473
+ if (authResult.reused) {
1474
+ logMilestone(`Using existing auth config: ${authPath()}`);
1475
+ } else if (authResult.written) {
1476
+ logMilestone(`Authenticated (workspace: ${opts.workspaceId})`);
1477
+ } else {
1478
+ logMilestone(
1479
+ "Sellable infrastructure ready — sign in happens on first /sellable:create-campaign"
1480
+ );
1481
+ }
1482
+
1390
1483
  if (opts.host === "claude" || opts.host === "all") {
1391
- if (installClaude(opts)) installedHosts.push("Claude Code");
1484
+ if (installClaude(opts)) {
1485
+ installedHosts.push("Claude Code");
1486
+ logMilestone("Claude Code MCP server registered");
1487
+ }
1392
1488
  }
1393
1489
  if (opts.host === "codex" || opts.host === "all") {
1394
- if (installCodex(opts)) installedHosts.push("Codex");
1490
+ const result = installCodex(opts);
1491
+ if (result.installed) {
1492
+ installedHosts.push("Codex");
1493
+ logMilestone("Codex MCP server registered");
1494
+ logMilestone(
1495
+ `Codex Desktop plugin installed (sellable@sellable v${CODEX_PLUGIN_VERSION})`
1496
+ );
1497
+ logMilestone(
1498
+ "Skill bundle: create-campaign, engage, interview, workflow-sequences"
1499
+ );
1500
+ if (result.compatCount) {
1501
+ logMilestone(
1502
+ `Backwards-compat cache linked (${result.compatCount} versions → symlinked, no copy)`
1503
+ );
1504
+ }
1505
+ logMilestone("Codex Default-mode request_user_input enabled");
1506
+ }
1395
1507
  }
1396
1508
  }
1509
+
1397
1510
  if (opts.dryRun) {
1398
- console.log(
1399
- "Dry run complete; verification skipped because no files were written."
1400
- );
1401
- } else {
1511
+ console.log("");
1512
+ logStep("Dry run complete; no files were written.");
1513
+ } else if (opts.verifyOnly) {
1402
1514
  verify(opts);
1403
1515
  }
1404
- console.log("Sellable install complete.");
1405
- if (!opts.verifyOnly && !opts.dryRun) printNextSteps(installedHosts);
1516
+
1517
+ if (!opts.verifyOnly && !opts.dryRun) {
1518
+ printNextSteps(installedHosts);
1519
+ }
1406
1520
  } catch (error) {
1407
- console.error(error instanceof Error ? error.message : String(error));
1521
+ console.error(
1522
+ `${C.yellow}✗${C.reset} ${error instanceof Error ? error.message : String(error)}`
1523
+ );
1408
1524
  process.exitCode = 1;
1409
1525
  }
1410
1526
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.27",
3
+ "version": "0.1.30",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code and Codex",
6
6
  "bin": {