@sellable/install 0.1.28 → 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.
- package/bin/sellable-install.mjs +279 -164
- package/package.json +1 -1
package/bin/sellable-install.mjs
CHANGED
|
@@ -5,12 +5,12 @@ 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 {
|
|
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 =
|
|
@@ -40,6 +40,62 @@ const CODEX_PLUGIN_COMPAT_VERSIONS = [
|
|
|
40
40
|
const INSTALL_PACKAGE_SPEC =
|
|
41
41
|
process.env.SELLABLE_INSTALL_PACKAGE_SPEC || "@sellable/install@latest";
|
|
42
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
|
+
|
|
43
99
|
function usage() {
|
|
44
100
|
return `Sellable agent installer
|
|
45
101
|
|
|
@@ -56,14 +112,20 @@ Options:
|
|
|
56
112
|
--mcp-package <pkg> MCP server package. Default: ${DEFAULT_SERVER_PACKAGE}
|
|
57
113
|
--local-command <cmd> Local MCP command for --server local.
|
|
58
114
|
--hosted-url <url> Hosted MCP URL for --server hosted.
|
|
115
|
+
--verbose Print every file write and shell command.
|
|
59
116
|
--dry-run Print actions without writing or running host commands.
|
|
60
117
|
--verify-only Verify installed host config where possible.
|
|
61
118
|
--help Show help.
|
|
62
119
|
|
|
63
120
|
Auth:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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.
|
|
67
129
|
`;
|
|
68
130
|
}
|
|
69
131
|
|
|
@@ -79,6 +141,7 @@ function parseArgs(argv) {
|
|
|
79
141
|
hostedUrl: process.env.SELLABLE_MCP_HOSTED_URL || "",
|
|
80
142
|
dryRun: false,
|
|
81
143
|
verifyOnly: false,
|
|
144
|
+
verbose: false,
|
|
82
145
|
};
|
|
83
146
|
|
|
84
147
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -114,6 +177,8 @@ function parseArgs(argv) {
|
|
|
114
177
|
opts.dryRun = true;
|
|
115
178
|
} else if (arg === "--verify-only") {
|
|
116
179
|
opts.verifyOnly = true;
|
|
180
|
+
} else if (arg === "--verbose") {
|
|
181
|
+
opts.verbose = true;
|
|
117
182
|
} else {
|
|
118
183
|
throw new Error(`Unknown option: ${arg}`);
|
|
119
184
|
}
|
|
@@ -137,13 +202,13 @@ function redact(value) {
|
|
|
137
202
|
|
|
138
203
|
function run(command, args, opts = {}) {
|
|
139
204
|
const rendered = [command, ...args].join(" ");
|
|
140
|
-
|
|
141
|
-
|
|
205
|
+
logVerbose(
|
|
206
|
+
`${C.grey}+ ${rendered.replace(opts.token || "__NO_TOKEN__", "[redacted-token]")}${C.reset}`
|
|
142
207
|
);
|
|
143
208
|
if (opts.dryRun) return { status: 0, stdout: "", stderr: "" };
|
|
144
209
|
const result = spawnSync(command, args, {
|
|
145
210
|
encoding: "utf8",
|
|
146
|
-
stdio: "pipe",
|
|
211
|
+
stdio: VERBOSE ? "inherit" : "pipe",
|
|
147
212
|
});
|
|
148
213
|
if (result.status !== 0 && !opts.allowFail) {
|
|
149
214
|
const stderr = (result.stderr || "").trim();
|
|
@@ -159,10 +224,6 @@ function commandExists(command) {
|
|
|
159
224
|
return result.status === 0;
|
|
160
225
|
}
|
|
161
226
|
|
|
162
|
-
function isInteractive() {
|
|
163
|
-
return Boolean(input.isTTY && output.isTTY);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
227
|
function authPath() {
|
|
167
228
|
return join(homedir(), ".sellable", "config.json");
|
|
168
229
|
}
|
|
@@ -190,7 +251,7 @@ function readStoredAuth() {
|
|
|
190
251
|
return raw ? normalizeStoredAuth(raw) : null;
|
|
191
252
|
}
|
|
192
253
|
|
|
193
|
-
async function
|
|
254
|
+
async function loadAuthIfPresent(opts) {
|
|
194
255
|
if (opts.token && opts.workspaceId) return opts;
|
|
195
256
|
|
|
196
257
|
const stored = readStoredAuth();
|
|
@@ -199,58 +260,19 @@ async function promptForMissingAuth(opts) {
|
|
|
199
260
|
opts.workspaceId ||= stored.workspaceId;
|
|
200
261
|
opts.apiUrl = opts.apiUrl || stored.apiUrl || DEFAULT_API_URL;
|
|
201
262
|
opts.authFromExistingConfig = true;
|
|
202
|
-
console.log(`Using existing Sellable auth config: ${authPath()}`);
|
|
203
|
-
return opts;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (!isInteractive()) {
|
|
207
|
-
throw new Error(
|
|
208
|
-
[
|
|
209
|
-
"Missing Sellable token/workspace id.",
|
|
210
|
-
`Create a token at ${opts.apiUrl}/settings, then rerun:`,
|
|
211
|
-
` npx -y ${INSTALL_PACKAGE_SPEC} --host all --token <token> --workspace-id <workspace_id>`,
|
|
212
|
-
"",
|
|
213
|
-
"You can also use SELLABLE_TOKEN and SELLABLE_WORKSPACE_ID.",
|
|
214
|
-
].join("\n")
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
console.log("");
|
|
219
|
-
console.log(
|
|
220
|
-
"Sellable needs one API token to connect Claude/Codex to your workspace."
|
|
221
|
-
);
|
|
222
|
-
console.log(
|
|
223
|
-
`Open ${opts.apiUrl}/settings, create a token, then paste the values below.`
|
|
224
|
-
);
|
|
225
|
-
console.log(`Auth will be stored once at ${authPath()}.`);
|
|
226
|
-
console.log("");
|
|
227
|
-
|
|
228
|
-
const rl = createInterface({ input, output });
|
|
229
|
-
try {
|
|
230
|
-
if (!opts.token) {
|
|
231
|
-
opts.token = (await rl.question("Sellable API token: ")).trim();
|
|
232
|
-
}
|
|
233
|
-
if (!opts.workspaceId) {
|
|
234
|
-
opts.workspaceId = (await rl.question("Sellable workspace id: ")).trim();
|
|
235
|
-
}
|
|
236
|
-
} finally {
|
|
237
|
-
rl.close();
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (!opts.token || !opts.workspaceId) {
|
|
241
|
-
throw new Error("Sellable token and workspace id are both required.");
|
|
242
263
|
}
|
|
243
|
-
|
|
244
264
|
return opts;
|
|
245
265
|
}
|
|
246
266
|
|
|
247
267
|
function writeJson(path, data, opts) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
+
}
|
|
254
276
|
if (opts.dryRun) return;
|
|
255
277
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
256
278
|
writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`, { mode: 0o600 });
|
|
@@ -317,12 +339,25 @@ function upsertTomlBoolean(content, tableName, key, value) {
|
|
|
317
339
|
}
|
|
318
340
|
|
|
319
341
|
function writeFile(path, content, opts, mode = 0o644) {
|
|
320
|
-
|
|
342
|
+
logVerbose(`${C.grey}Writing ${path}${C.reset}`);
|
|
321
343
|
if (opts.dryRun) return;
|
|
322
344
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
323
345
|
writeFileSync(path, content, { mode });
|
|
324
346
|
}
|
|
325
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
|
+
|
|
326
361
|
function codexPluginManifest(opts) {
|
|
327
362
|
return {
|
|
328
363
|
name: "sellable",
|
|
@@ -1062,13 +1097,7 @@ function installCodexDesktopPlugin(opts) {
|
|
|
1062
1097
|
);
|
|
1063
1098
|
const pluginRoot = join(marketplaceRoot, "plugins", "sellable");
|
|
1064
1099
|
const cacheRoot = join(home, "plugins", "cache", "sellable", "sellable");
|
|
1065
|
-
const
|
|
1066
|
-
const pluginCacheVersions = [
|
|
1067
|
-
CODEX_PLUGIN_VERSION,
|
|
1068
|
-
...CODEX_PLUGIN_COMPAT_VERSIONS.filter(
|
|
1069
|
-
(version) => version !== CODEX_PLUGIN_VERSION
|
|
1070
|
-
),
|
|
1071
|
-
];
|
|
1100
|
+
const canonicalCachePath = join(cacheRoot, CODEX_PLUGIN_VERSION);
|
|
1072
1101
|
|
|
1073
1102
|
const marketplace = {
|
|
1074
1103
|
name: "sellable",
|
|
@@ -1110,23 +1139,24 @@ function installCodexDesktopPlugin(opts) {
|
|
|
1110
1139
|
if (!opts.dryRun) {
|
|
1111
1140
|
rmSync(cacheRoot, { recursive: true, force: true });
|
|
1112
1141
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
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);
|
|
1130
1160
|
}
|
|
1131
1161
|
|
|
1132
1162
|
if (!opts.dryRun) {
|
|
@@ -1162,28 +1192,31 @@ enabled = false`
|
|
|
1162
1192
|
);
|
|
1163
1193
|
writeFileSync(configPath, `${content.trimEnd()}\n`, { mode: 0o600 });
|
|
1164
1194
|
} else {
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
console.log(
|
|
1168
|
-
`+ disable stale [plugins."sellable@sellable-local"] in ${configPath}`
|
|
1195
|
+
logVerbose(
|
|
1196
|
+
`${C.grey}+ upsert [marketplaces.sellable] in ${configPath}${C.reset}`
|
|
1169
1197
|
);
|
|
1170
|
-
|
|
1171
|
-
|
|
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}`
|
|
1172
1203
|
);
|
|
1173
1204
|
}
|
|
1174
1205
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1206
|
+
return {
|
|
1207
|
+
marketplaceRoot,
|
|
1208
|
+
canonicalCachePath,
|
|
1209
|
+
compatCount: CODEX_PLUGIN_COMPAT_VERSIONS.length,
|
|
1210
|
+
};
|
|
1180
1211
|
}
|
|
1181
1212
|
|
|
1182
1213
|
function writeAuth(opts) {
|
|
1183
1214
|
if (!opts.token || !opts.workspaceId) {
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1215
|
+
return { written: false, reused: false };
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
if (opts.authFromExistingConfig) {
|
|
1219
|
+
return { written: false, reused: true };
|
|
1187
1220
|
}
|
|
1188
1221
|
|
|
1189
1222
|
const config = {
|
|
@@ -1191,16 +1224,8 @@ function writeAuth(opts) {
|
|
|
1191
1224
|
activeWorkspaceId: opts.workspaceId,
|
|
1192
1225
|
apiUrl: opts.apiUrl,
|
|
1193
1226
|
};
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
console.log(
|
|
1197
|
-
`Leaving existing Sellable auth config unchanged: ${authPath()}`
|
|
1198
|
-
);
|
|
1199
|
-
return;
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
const sellablePath = authPath();
|
|
1203
|
-
writeJson(sellablePath, config, opts);
|
|
1227
|
+
writeJson(authPath(), config, opts);
|
|
1228
|
+
return { written: true, reused: false };
|
|
1204
1229
|
}
|
|
1205
1230
|
|
|
1206
1231
|
function installSelfShim(opts) {
|
|
@@ -1234,7 +1259,7 @@ function installClaude(opts) {
|
|
|
1234
1259
|
const message =
|
|
1235
1260
|
"Claude CLI not found. Install/login to Claude Code, then rerun: sellable --host claude";
|
|
1236
1261
|
if (opts.host === "all") {
|
|
1237
|
-
|
|
1262
|
+
logWarn(`Skipping Claude Code: ${message}`);
|
|
1238
1263
|
return false;
|
|
1239
1264
|
}
|
|
1240
1265
|
throw new Error(message);
|
|
@@ -1266,15 +1291,15 @@ function installCodex(opts) {
|
|
|
1266
1291
|
const message =
|
|
1267
1292
|
"Codex CLI not found. Install/login to Codex, then rerun: sellable --host codex";
|
|
1268
1293
|
if (opts.host === "all") {
|
|
1269
|
-
|
|
1270
|
-
return false;
|
|
1294
|
+
logWarn(`Skipping Codex: ${message}`);
|
|
1295
|
+
return { installed: false };
|
|
1271
1296
|
}
|
|
1272
1297
|
throw new Error(message);
|
|
1273
1298
|
}
|
|
1274
1299
|
if (opts.server === "hosted") {
|
|
1275
1300
|
run("codex", ["mcp", "add", "sellable", "--url", opts.hostedUrl], opts);
|
|
1276
|
-
installCodexDesktopPlugin(opts);
|
|
1277
|
-
return true;
|
|
1301
|
+
const info = installCodexDesktopPlugin(opts);
|
|
1302
|
+
return { installed: true, ...info };
|
|
1278
1303
|
}
|
|
1279
1304
|
const [command, args] = mcpCommand(opts);
|
|
1280
1305
|
run("codex", ["mcp", "remove", "sellable"], {
|
|
@@ -1283,27 +1308,35 @@ function installCodex(opts) {
|
|
|
1283
1308
|
allowFail: true,
|
|
1284
1309
|
});
|
|
1285
1310
|
run("codex", ["mcp", "add", "sellable", "--", command, ...args], opts);
|
|
1286
|
-
installCodexDesktopPlugin(opts);
|
|
1287
|
-
return true;
|
|
1311
|
+
const info = installCodexDesktopPlugin(opts);
|
|
1312
|
+
return { installed: true, ...info };
|
|
1288
1313
|
}
|
|
1289
1314
|
|
|
1290
1315
|
function verify(opts) {
|
|
1291
1316
|
const stored = readStoredAuth();
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
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
|
+
});
|
|
1296
1325
|
}
|
|
1297
|
-
|
|
1326
|
+
|
|
1298
1327
|
if (opts.host === "claude" || opts.host === "all") {
|
|
1299
|
-
|
|
1300
|
-
commandExists("claude")
|
|
1301
|
-
|
|
1328
|
+
checks.push({
|
|
1329
|
+
ok: commandExists("claude"),
|
|
1330
|
+
label: commandExists("claude")
|
|
1331
|
+
? "Claude CLI present"
|
|
1332
|
+
: "Claude CLI missing",
|
|
1333
|
+
});
|
|
1302
1334
|
}
|
|
1303
1335
|
if (opts.host === "codex" || opts.host === "all") {
|
|
1304
|
-
|
|
1305
|
-
commandExists("codex")
|
|
1306
|
-
|
|
1336
|
+
checks.push({
|
|
1337
|
+
ok: commandExists("codex"),
|
|
1338
|
+
label: commandExists("codex") ? "Codex CLI present" : "Codex CLI missing",
|
|
1339
|
+
});
|
|
1307
1340
|
const pluginPath = join(
|
|
1308
1341
|
codexHome(),
|
|
1309
1342
|
"plugins",
|
|
@@ -1325,45 +1358,81 @@ function verify(opts) {
|
|
|
1325
1358
|
"sellable-create-campaign",
|
|
1326
1359
|
"SKILL.md"
|
|
1327
1360
|
);
|
|
1328
|
-
|
|
1329
|
-
existsSync(pluginPath)
|
|
1361
|
+
checks.push({
|
|
1362
|
+
ok: existsSync(pluginPath),
|
|
1363
|
+
label: existsSync(pluginPath)
|
|
1330
1364
|
? "Codex Desktop plugin present"
|
|
1331
|
-
: "Codex Desktop plugin missing"
|
|
1332
|
-
);
|
|
1333
|
-
|
|
1334
|
-
existsSync(skillPath)
|
|
1365
|
+
: "Codex Desktop plugin missing",
|
|
1366
|
+
});
|
|
1367
|
+
checks.push({
|
|
1368
|
+
ok: existsSync(skillPath),
|
|
1369
|
+
label: existsSync(skillPath)
|
|
1335
1370
|
? "Codex skill bundle present"
|
|
1336
|
-
: "Codex skill bundle missing"
|
|
1337
|
-
);
|
|
1371
|
+
: "Codex skill bundle missing",
|
|
1372
|
+
});
|
|
1338
1373
|
const configPath = join(codexHome(), "config.toml");
|
|
1339
1374
|
const configContent = existsSync(configPath)
|
|
1340
1375
|
? readFileSync(configPath, "utf8")
|
|
1341
1376
|
: "";
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
? "Codex Default-mode request_user_input enabled"
|
|
1345
|
-
: "Codex Default-mode request_user_input missing"
|
|
1377
|
+
const hasFlag = configContent.includes(
|
|
1378
|
+
"default_mode_request_user_input = true"
|
|
1346
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);
|
|
1347
1391
|
}
|
|
1348
1392
|
}
|
|
1349
1393
|
|
|
1350
1394
|
function printNextSteps(installedHosts) {
|
|
1351
1395
|
console.log("");
|
|
1352
|
-
|
|
1396
|
+
printDivider();
|
|
1397
|
+
console.log(` ${C.bold}Next steps${C.reset}`);
|
|
1398
|
+
printDivider();
|
|
1399
|
+
console.log("");
|
|
1353
1400
|
if (installedHosts.length > 0) {
|
|
1354
1401
|
console.log(
|
|
1355
|
-
`1.
|
|
1402
|
+
` 1. Quit and reopen ${installedHosts.join(" and ")} so MCP tools reload.`
|
|
1356
1403
|
);
|
|
1357
|
-
console.log("2. Start a new thread and
|
|
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?`);
|
|
1358
1423
|
console.log(
|
|
1359
|
-
|
|
1424
|
+
` Slack: ${C.cyan}https://join.slack.com/t/ditttoai/shared_invite/zt-3wvs86yau-csKZGP3iGXO3oEiAUmtH9A${C.reset}`
|
|
1360
1425
|
);
|
|
1426
|
+
console.log(` Email: ${C.cyan}admin@dittto.ai${C.reset}`);
|
|
1361
1427
|
} else {
|
|
1362
1428
|
console.log(
|
|
1363
|
-
"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}`
|
|
1364
1433
|
);
|
|
1365
|
-
console.log("2. Verify auth later with: sellable --verify-only --host all");
|
|
1366
1434
|
}
|
|
1435
|
+
console.log("");
|
|
1367
1436
|
}
|
|
1368
1437
|
|
|
1369
1438
|
async function main() {
|
|
@@ -1374,38 +1443,84 @@ async function main() {
|
|
|
1374
1443
|
return;
|
|
1375
1444
|
}
|
|
1376
1445
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
console.log(
|
|
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)"}`);
|
|
1382
1466
|
}
|
|
1383
|
-
console.log(`- api: ${opts.apiUrl}`);
|
|
1384
|
-
console.log(`- token: ${opts.token ? redact(opts.token) : "(missing)"}`);
|
|
1385
1467
|
|
|
1386
1468
|
const installedHosts = [];
|
|
1387
1469
|
if (!opts.verifyOnly) {
|
|
1388
|
-
await
|
|
1389
|
-
writeAuth(opts);
|
|
1470
|
+
await loadAuthIfPresent(opts);
|
|
1471
|
+
const authResult = writeAuth(opts);
|
|
1390
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
|
+
|
|
1391
1483
|
if (opts.host === "claude" || opts.host === "all") {
|
|
1392
|
-
if (installClaude(opts))
|
|
1484
|
+
if (installClaude(opts)) {
|
|
1485
|
+
installedHosts.push("Claude Code");
|
|
1486
|
+
logMilestone("Claude Code MCP server registered");
|
|
1487
|
+
}
|
|
1393
1488
|
}
|
|
1394
1489
|
if (opts.host === "codex" || opts.host === "all") {
|
|
1395
|
-
|
|
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
|
+
}
|
|
1396
1507
|
}
|
|
1397
1508
|
}
|
|
1509
|
+
|
|
1398
1510
|
if (opts.dryRun) {
|
|
1399
|
-
console.log(
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
} else {
|
|
1511
|
+
console.log("");
|
|
1512
|
+
logStep("Dry run complete; no files were written.");
|
|
1513
|
+
} else if (opts.verifyOnly) {
|
|
1403
1514
|
verify(opts);
|
|
1404
1515
|
}
|
|
1405
|
-
|
|
1406
|
-
if (!opts.verifyOnly && !opts.dryRun)
|
|
1516
|
+
|
|
1517
|
+
if (!opts.verifyOnly && !opts.dryRun) {
|
|
1518
|
+
printNextSteps(installedHosts);
|
|
1519
|
+
}
|
|
1407
1520
|
} catch (error) {
|
|
1408
|
-
console.error(
|
|
1521
|
+
console.error(
|
|
1522
|
+
`${C.yellow}✗${C.reset} ${error instanceof Error ? error.message : String(error)}`
|
|
1523
|
+
);
|
|
1409
1524
|
process.exitCode = 1;
|
|
1410
1525
|
}
|
|
1411
1526
|
}
|