@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.
- package/bin/sellable-install.mjs +281 -165
- package/package.json +1 -1
package/bin/sellable-install.mjs
CHANGED
|
@@ -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 {
|
|
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.
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
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
|
-
|
|
1165
|
-
|
|
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
|
-
|
|
1170
|
-
|
|
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
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
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
|
-
|
|
1184
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
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
|
-
|
|
1326
|
+
|
|
1297
1327
|
if (opts.host === "claude" || opts.host === "all") {
|
|
1298
|
-
|
|
1299
|
-
commandExists("claude")
|
|
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
|
-
|
|
1304
|
-
commandExists("codex")
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1342
|
-
|
|
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
|
-
|
|
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.
|
|
1402
|
+
` 1. Quit and reopen ${installedHosts.join(" and ")} so MCP tools reload.`
|
|
1355
1403
|
);
|
|
1356
|
-
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?`);
|
|
1357
1423
|
console.log(
|
|
1358
|
-
|
|
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
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
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)"}`);
|
|
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
|
|
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))
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1405
|
-
if (!opts.verifyOnly && !opts.dryRun)
|
|
1516
|
+
|
|
1517
|
+
if (!opts.verifyOnly && !opts.dryRun) {
|
|
1518
|
+
printNextSteps(installedHosts);
|
|
1519
|
+
}
|
|
1406
1520
|
} catch (error) {
|
|
1407
|
-
console.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
|
}
|