@sellable/install 0.1.1 → 0.1.3
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/README.md +8 -1
- package/bin/sellable-install.mjs +341 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@ Installs Sellable MCP for Claude Code and Codex.
|
|
|
6
6
|
npx -y @sellable/install --host all
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+
If you do not pass a token, the installer prompts you for one and shows where to get it.
|
|
10
|
+
|
|
9
11
|
The installer uses package stdio MCP by default:
|
|
10
12
|
|
|
11
13
|
```bash
|
|
@@ -18,7 +20,7 @@ Get a Sellable API token from:
|
|
|
18
20
|
https://app.sellable.dev/settings
|
|
19
21
|
```
|
|
20
22
|
|
|
21
|
-
Then pass it with `--token`
|
|
23
|
+
Then paste it into the installer, or pass it with `--token` / `SELLABLE_TOKEN`.
|
|
22
24
|
|
|
23
25
|
Auth is stored once at:
|
|
24
26
|
|
|
@@ -27,3 +29,8 @@ Auth is stored once at:
|
|
|
27
29
|
```
|
|
28
30
|
|
|
29
31
|
Claude Code and Codex are configured to launch the same packaged MCP server.
|
|
32
|
+
|
|
33
|
+
For Codex Desktop, the installer also writes a local Sellable plugin bundle into
|
|
34
|
+
`~/.sellable/codex-marketplace` and enables it in `~/.codex/config.toml`.
|
|
35
|
+
|
|
36
|
+
If only one host is installed, `--host all` installs the available host and tells you how to add the other one later.
|
package/bin/sellable-install.mjs
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
7
|
+
import { createInterface } from "node:readline/promises";
|
|
6
8
|
|
|
7
9
|
const DEFAULT_API_URL = "https://app.sellable.dev";
|
|
8
10
|
const DEFAULT_SERVER_PACKAGE =
|
|
9
11
|
process.env.SELLABLE_MCP_PACKAGE || "@sellable/mcp";
|
|
12
|
+
const CODEX_PLUGIN_VERSION = "0.1.3";
|
|
10
13
|
|
|
11
14
|
function usage() {
|
|
12
15
|
return `Sellable agent installer
|
|
@@ -30,7 +33,8 @@ Options:
|
|
|
30
33
|
|
|
31
34
|
Auth:
|
|
32
35
|
Sign up or log in at ${DEFAULT_API_URL}/settings, create an API token, then
|
|
33
|
-
pass it with --token or SELLABLE_TOKEN.
|
|
36
|
+
pass it with --token or SELLABLE_TOKEN. In an interactive terminal, the
|
|
37
|
+
installer will prompt for missing auth values.
|
|
34
38
|
`;
|
|
35
39
|
}
|
|
36
40
|
|
|
@@ -124,6 +128,87 @@ function commandExists(command) {
|
|
|
124
128
|
return result.status === 0;
|
|
125
129
|
}
|
|
126
130
|
|
|
131
|
+
function isInteractive() {
|
|
132
|
+
return Boolean(input.isTTY && output.isTTY);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function authPath() {
|
|
136
|
+
return join(homedir(), ".sellable", "config.json");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function normalizeStoredAuth(raw) {
|
|
140
|
+
if (raw?.activeEnv && raw?.environments) {
|
|
141
|
+
const env = raw.environments[raw.activeEnv];
|
|
142
|
+
if (!env) return null;
|
|
143
|
+
return {
|
|
144
|
+
token: env.token,
|
|
145
|
+
workspaceId: env.activeWorkspaceId || env.workspaceId,
|
|
146
|
+
apiUrl: env.apiUrl,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
token: raw?.token,
|
|
152
|
+
workspaceId: raw?.activeWorkspaceId || raw?.workspaceId,
|
|
153
|
+
apiUrl: raw?.apiUrl,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function readStoredAuth() {
|
|
158
|
+
const raw = readExisting(authPath());
|
|
159
|
+
return raw ? normalizeStoredAuth(raw) : null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function promptForMissingAuth(opts) {
|
|
163
|
+
if (opts.token && opts.workspaceId) return opts;
|
|
164
|
+
|
|
165
|
+
const stored = readStoredAuth();
|
|
166
|
+
if (stored?.token && stored?.workspaceId) {
|
|
167
|
+
opts.token ||= stored.token;
|
|
168
|
+
opts.workspaceId ||= stored.workspaceId;
|
|
169
|
+
opts.apiUrl = opts.apiUrl || stored.apiUrl || DEFAULT_API_URL;
|
|
170
|
+
opts.authFromExistingConfig = true;
|
|
171
|
+
console.log(`Using existing Sellable auth config: ${authPath()}`);
|
|
172
|
+
return opts;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!isInteractive()) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
[
|
|
178
|
+
"Missing Sellable token/workspace id.",
|
|
179
|
+
`Create a token at ${opts.apiUrl}/settings, then rerun:`,
|
|
180
|
+
" npx -y @sellable/install --host all --token <token> --workspace-id <workspace_id>",
|
|
181
|
+
"",
|
|
182
|
+
"You can also use SELLABLE_TOKEN and SELLABLE_WORKSPACE_ID.",
|
|
183
|
+
].join("\n")
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log("");
|
|
188
|
+
console.log("Sellable needs one API token to connect Claude/Codex to your workspace.");
|
|
189
|
+
console.log(`Open ${opts.apiUrl}/settings, create a token, then paste the values below.`);
|
|
190
|
+
console.log(`Auth will be stored once at ${authPath()}.`);
|
|
191
|
+
console.log("");
|
|
192
|
+
|
|
193
|
+
const rl = createInterface({ input, output });
|
|
194
|
+
try {
|
|
195
|
+
if (!opts.token) {
|
|
196
|
+
opts.token = (await rl.question("Sellable API token: ")).trim();
|
|
197
|
+
}
|
|
198
|
+
if (!opts.workspaceId) {
|
|
199
|
+
opts.workspaceId = (await rl.question("Sellable workspace id: ")).trim();
|
|
200
|
+
}
|
|
201
|
+
} finally {
|
|
202
|
+
rl.close();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!opts.token || !opts.workspaceId) {
|
|
206
|
+
throw new Error("Sellable token and workspace id are both required.");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return opts;
|
|
210
|
+
}
|
|
211
|
+
|
|
127
212
|
function writeJson(path, data, opts) {
|
|
128
213
|
const redacted = JSON.stringify({ ...data, token: redact(data.token) }, null, 2);
|
|
129
214
|
console.log(`Writing ${path}: ${redacted}`);
|
|
@@ -141,6 +226,198 @@ function readExisting(path) {
|
|
|
141
226
|
}
|
|
142
227
|
}
|
|
143
228
|
|
|
229
|
+
function codexHome() {
|
|
230
|
+
return process.env.CODEX_HOME?.trim() || join(homedir(), ".codex");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function quoteToml(value) {
|
|
234
|
+
return `"${String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function escapeRegExp(value) {
|
|
238
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function upsertTomlTable(content, tableName, block) {
|
|
242
|
+
const tablePattern = new RegExp(
|
|
243
|
+
`(^|\\n)\\[${escapeRegExp(tableName)}\\]\\n[\\s\\S]*?(?=\\n\\[[^\\n]+\\]|$)`
|
|
244
|
+
);
|
|
245
|
+
const normalizedBlock = block.trimEnd();
|
|
246
|
+
|
|
247
|
+
if (tablePattern.test(content)) {
|
|
248
|
+
return content.replace(tablePattern, (_, prefix) => `${prefix}${normalizedBlock}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return `${content.trimEnd()}\n\n${normalizedBlock}\n`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function writeFile(path, content, opts, mode = 0o644) {
|
|
255
|
+
console.log(`Writing ${path}`);
|
|
256
|
+
if (opts.dryRun) return;
|
|
257
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
258
|
+
writeFileSync(path, content, { mode });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function codexPluginManifest(opts) {
|
|
262
|
+
return {
|
|
263
|
+
name: "sellable",
|
|
264
|
+
version: CODEX_PLUGIN_VERSION,
|
|
265
|
+
description:
|
|
266
|
+
"Sellable MCP tools for campaign creation, engagement, interviews, and workflow sequencing.",
|
|
267
|
+
author: {
|
|
268
|
+
name: "Sellable",
|
|
269
|
+
url: "https://sellable.dev/",
|
|
270
|
+
},
|
|
271
|
+
homepage: "https://sellable.dev/",
|
|
272
|
+
repository: "https://github.com/csreyes/sellable",
|
|
273
|
+
license: "UNLICENSED",
|
|
274
|
+
keywords: ["sellable", "linkedin", "outbound", "campaigns", "mcp"],
|
|
275
|
+
mcpServers: "./.mcp.json",
|
|
276
|
+
interface: {
|
|
277
|
+
displayName: "Sellable",
|
|
278
|
+
shortDescription: "Sellable MCP tools for outbound campaign workflows",
|
|
279
|
+
longDescription:
|
|
280
|
+
"Loads the Sellable MCP server into Codex Desktop so Sellable skills can create campaigns, find leads, draft messages, and manage approval-gated launch workflows.",
|
|
281
|
+
developerName: "Sellable",
|
|
282
|
+
category: "Productivity",
|
|
283
|
+
capabilities: ["Interactive", "Write"],
|
|
284
|
+
websiteURL: "https://sellable.dev/",
|
|
285
|
+
privacyPolicyURL: "https://sellable.dev/privacy",
|
|
286
|
+
termsOfServiceURL: "https://sellable.dev/terms",
|
|
287
|
+
defaultPrompt: [
|
|
288
|
+
"Create a Sellable campaign",
|
|
289
|
+
"Find LinkedIn leads",
|
|
290
|
+
"Build an outbound sequence",
|
|
291
|
+
],
|
|
292
|
+
brandColor: "#8B5CF6",
|
|
293
|
+
screenshots: [],
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function codexPluginMcp(opts) {
|
|
299
|
+
if (opts.server === "hosted") {
|
|
300
|
+
return {
|
|
301
|
+
mcpServers: {
|
|
302
|
+
sellable: {
|
|
303
|
+
type: "http",
|
|
304
|
+
url: opts.hostedUrl,
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (opts.server === "local") {
|
|
311
|
+
const [command, args] = mcpCommand(opts);
|
|
312
|
+
return {
|
|
313
|
+
mcpServers: {
|
|
314
|
+
sellable: {
|
|
315
|
+
type: "stdio",
|
|
316
|
+
command,
|
|
317
|
+
args,
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
mcpServers: {
|
|
325
|
+
sellable: {
|
|
326
|
+
type: "stdio",
|
|
327
|
+
command: "npx",
|
|
328
|
+
args: ["-y", opts.mcpPackage],
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function installCodexDesktopPlugin(opts) {
|
|
335
|
+
const home = codexHome();
|
|
336
|
+
const configPath = join(home, "config.toml");
|
|
337
|
+
const marketplaceRoot = join(homedir(), ".sellable", "codex-marketplace");
|
|
338
|
+
const marketplacePath = join(marketplaceRoot, ".agents", "plugins", "marketplace.json");
|
|
339
|
+
const pluginRoot = join(marketplaceRoot, "plugins", "sellable");
|
|
340
|
+
const cacheRoot = join(home, "plugins", "cache", "sellable", "sellable");
|
|
341
|
+
const pluginCache = join(cacheRoot, CODEX_PLUGIN_VERSION);
|
|
342
|
+
|
|
343
|
+
const marketplace = {
|
|
344
|
+
name: "sellable",
|
|
345
|
+
interface: {
|
|
346
|
+
displayName: "Sellable",
|
|
347
|
+
},
|
|
348
|
+
plugins: [
|
|
349
|
+
{
|
|
350
|
+
name: "sellable",
|
|
351
|
+
source: {
|
|
352
|
+
source: "local",
|
|
353
|
+
path: "./plugins/sellable",
|
|
354
|
+
},
|
|
355
|
+
policy: {
|
|
356
|
+
installation: "AVAILABLE",
|
|
357
|
+
authentication: "ON_INSTALL",
|
|
358
|
+
},
|
|
359
|
+
category: "Productivity",
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const manifest = codexPluginManifest(opts);
|
|
365
|
+
const mcp = codexPluginMcp(opts);
|
|
366
|
+
|
|
367
|
+
writeFile(marketplacePath, `${JSON.stringify(marketplace, null, 2)}\n`, opts);
|
|
368
|
+
writeFile(
|
|
369
|
+
join(pluginRoot, ".codex-plugin", "plugin.json"),
|
|
370
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
371
|
+
opts
|
|
372
|
+
);
|
|
373
|
+
writeFile(join(pluginRoot, ".mcp.json"), `${JSON.stringify(mcp, null, 2)}\n`, opts);
|
|
374
|
+
|
|
375
|
+
if (!opts.dryRun) {
|
|
376
|
+
rmSync(cacheRoot, { recursive: true, force: true });
|
|
377
|
+
}
|
|
378
|
+
writeFile(
|
|
379
|
+
join(pluginCache, ".codex-plugin", "plugin.json"),
|
|
380
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
381
|
+
opts
|
|
382
|
+
);
|
|
383
|
+
writeFile(join(pluginCache, ".mcp.json"), `${JSON.stringify(mcp, null, 2)}\n`, opts);
|
|
384
|
+
|
|
385
|
+
if (!opts.dryRun) {
|
|
386
|
+
mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
387
|
+
let content = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
388
|
+
content = upsertTomlTable(
|
|
389
|
+
content,
|
|
390
|
+
"marketplaces.sellable",
|
|
391
|
+
`[marketplaces.sellable]
|
|
392
|
+
source_type = "local"
|
|
393
|
+
source = ${quoteToml(marketplaceRoot)}
|
|
394
|
+
last_updated = ${quoteToml(new Date().toISOString())}`
|
|
395
|
+
);
|
|
396
|
+
content = upsertTomlTable(
|
|
397
|
+
content,
|
|
398
|
+
'plugins."sellable@sellable"',
|
|
399
|
+
`[plugins."sellable@sellable"]
|
|
400
|
+
enabled = true`
|
|
401
|
+
);
|
|
402
|
+
content = upsertTomlTable(
|
|
403
|
+
content,
|
|
404
|
+
'plugins."sellable@sellable-local"',
|
|
405
|
+
`[plugins."sellable@sellable-local"]
|
|
406
|
+
enabled = false`
|
|
407
|
+
);
|
|
408
|
+
writeFileSync(configPath, `${content.trimEnd()}\n`, { mode: 0o600 });
|
|
409
|
+
} else {
|
|
410
|
+
console.log(`+ upsert [marketplaces.sellable] in ${configPath}`);
|
|
411
|
+
console.log(`+ enable [plugins."sellable@sellable"] in ${configPath}`);
|
|
412
|
+
console.log(`+ disable stale [plugins."sellable@sellable-local"] in ${configPath}`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
console.log("Codex Desktop plugin installed:");
|
|
416
|
+
console.log(`- marketplace: ${marketplaceRoot}`);
|
|
417
|
+
console.log(`- plugin: sellable@sellable`);
|
|
418
|
+
console.log(`- cache: ${pluginCache}`);
|
|
419
|
+
}
|
|
420
|
+
|
|
144
421
|
function writeAuth(opts) {
|
|
145
422
|
if (!opts.token || !opts.workspaceId) {
|
|
146
423
|
throw new Error(
|
|
@@ -154,7 +431,12 @@ function writeAuth(opts) {
|
|
|
154
431
|
apiUrl: opts.apiUrl,
|
|
155
432
|
};
|
|
156
433
|
|
|
157
|
-
|
|
434
|
+
if (opts.authFromExistingConfig) {
|
|
435
|
+
console.log(`Leaving existing Sellable auth config unchanged: ${authPath()}`);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const sellablePath = authPath();
|
|
158
440
|
writeJson(sellablePath, config, opts);
|
|
159
441
|
}
|
|
160
442
|
|
|
@@ -178,11 +460,16 @@ function mcpCommand(opts) {
|
|
|
178
460
|
|
|
179
461
|
function installClaude(opts) {
|
|
180
462
|
if (!commandExists("claude")) {
|
|
181
|
-
|
|
463
|
+
const message = "Claude CLI not found. Install/login to Claude Code, then rerun: sellable --host claude";
|
|
464
|
+
if (opts.host === "all") {
|
|
465
|
+
console.log(`Skipping Claude Code: ${message}`);
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
throw new Error(message);
|
|
182
469
|
}
|
|
183
470
|
if (opts.server === "hosted") {
|
|
184
471
|
run("claude", ["mcp", "add", "--transport", "http", "sellable", opts.hostedUrl], opts);
|
|
185
|
-
return;
|
|
472
|
+
return true;
|
|
186
473
|
}
|
|
187
474
|
const [command, args] = mcpCommand(opts);
|
|
188
475
|
run("claude", ["mcp", "remove", "sellable"], {
|
|
@@ -191,15 +478,22 @@ function installClaude(opts) {
|
|
|
191
478
|
allowFail: true,
|
|
192
479
|
});
|
|
193
480
|
run("claude", ["mcp", "add", "--transport", "stdio", "sellable", "--", command, ...args], opts);
|
|
481
|
+
return true;
|
|
194
482
|
}
|
|
195
483
|
|
|
196
484
|
function installCodex(opts) {
|
|
197
485
|
if (!commandExists("codex")) {
|
|
198
|
-
|
|
486
|
+
const message = "Codex CLI not found. Install/login to Codex, then rerun: sellable --host codex";
|
|
487
|
+
if (opts.host === "all") {
|
|
488
|
+
console.log(`Skipping Codex: ${message}`);
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
throw new Error(message);
|
|
199
492
|
}
|
|
200
493
|
if (opts.server === "hosted") {
|
|
201
494
|
run("codex", ["mcp", "add", "sellable", "--url", opts.hostedUrl], opts);
|
|
202
|
-
|
|
495
|
+
installCodexDesktopPlugin(opts);
|
|
496
|
+
return true;
|
|
203
497
|
}
|
|
204
498
|
const [command, args] = mcpCommand(opts);
|
|
205
499
|
run("codex", ["mcp", "remove", "sellable"], {
|
|
@@ -208,24 +502,49 @@ function installCodex(opts) {
|
|
|
208
502
|
allowFail: true,
|
|
209
503
|
});
|
|
210
504
|
run("codex", ["mcp", "add", "sellable", "--", command, ...args], opts);
|
|
505
|
+
installCodexDesktopPlugin(opts);
|
|
506
|
+
return true;
|
|
211
507
|
}
|
|
212
508
|
|
|
213
509
|
function verify(opts) {
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
throw new Error(`Sellable auth config missing or incomplete: ${authPath}`);
|
|
510
|
+
const stored = readStoredAuth();
|
|
511
|
+
if (!stored?.token || !stored?.workspaceId) {
|
|
512
|
+
throw new Error(`Sellable auth config missing or incomplete: ${authPath()}`);
|
|
218
513
|
}
|
|
219
|
-
console.log(`Sellable auth config present: ${authPath}`);
|
|
514
|
+
console.log(`Sellable auth config present: ${authPath()}`);
|
|
220
515
|
if (opts.host === "claude" || opts.host === "all") {
|
|
221
516
|
console.log(commandExists("claude") ? "Claude CLI present" : "Claude CLI missing");
|
|
222
517
|
}
|
|
223
518
|
if (opts.host === "codex" || opts.host === "all") {
|
|
224
519
|
console.log(commandExists("codex") ? "Codex CLI present" : "Codex CLI missing");
|
|
520
|
+
const pluginPath = join(
|
|
521
|
+
codexHome(),
|
|
522
|
+
"plugins",
|
|
523
|
+
"cache",
|
|
524
|
+
"sellable",
|
|
525
|
+
"sellable",
|
|
526
|
+
CODEX_PLUGIN_VERSION,
|
|
527
|
+
".codex-plugin",
|
|
528
|
+
"plugin.json"
|
|
529
|
+
);
|
|
530
|
+
console.log(existsSync(pluginPath) ? "Codex Desktop plugin present" : "Codex Desktop plugin missing");
|
|
225
531
|
}
|
|
226
532
|
}
|
|
227
533
|
|
|
228
|
-
function
|
|
534
|
+
function printNextSteps(installedHosts) {
|
|
535
|
+
console.log("");
|
|
536
|
+
console.log("Next steps:");
|
|
537
|
+
if (installedHosts.length > 0) {
|
|
538
|
+
console.log(`1. Fully quit and reopen ${installedHosts.join(" and ")} so MCP tools reload.`);
|
|
539
|
+
console.log("2. Start a new thread and choose Sellable Create Campaign.");
|
|
540
|
+
console.log("3. If tools do not appear, run: sellable --verify-only --host all");
|
|
541
|
+
} else {
|
|
542
|
+
console.log("1. Install Claude Code or Codex, then rerun this installer for that host.");
|
|
543
|
+
console.log("2. Verify auth later with: sellable --verify-only --host all");
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function main() {
|
|
229
548
|
try {
|
|
230
549
|
const opts = parseArgs(process.argv.slice(2));
|
|
231
550
|
if (opts.help) {
|
|
@@ -239,10 +558,16 @@ function main() {
|
|
|
239
558
|
console.log(`- api: ${opts.apiUrl}`);
|
|
240
559
|
console.log(`- token: ${opts.token ? redact(opts.token) : "(missing)"}`);
|
|
241
560
|
|
|
561
|
+
const installedHosts = [];
|
|
242
562
|
if (!opts.verifyOnly) {
|
|
563
|
+
await promptForMissingAuth(opts);
|
|
243
564
|
writeAuth(opts);
|
|
244
|
-
if (opts.host === "claude" || opts.host === "all")
|
|
245
|
-
|
|
565
|
+
if (opts.host === "claude" || opts.host === "all") {
|
|
566
|
+
if (installClaude(opts)) installedHosts.push("Claude Code");
|
|
567
|
+
}
|
|
568
|
+
if (opts.host === "codex" || opts.host === "all") {
|
|
569
|
+
if (installCodex(opts)) installedHosts.push("Codex");
|
|
570
|
+
}
|
|
246
571
|
}
|
|
247
572
|
if (opts.dryRun) {
|
|
248
573
|
console.log("Dry run complete; verification skipped because no files were written.");
|
|
@@ -250,6 +575,7 @@ function main() {
|
|
|
250
575
|
verify(opts);
|
|
251
576
|
}
|
|
252
577
|
console.log("Sellable install complete.");
|
|
578
|
+
if (!opts.verifyOnly && !opts.dryRun) printNextSteps(installedHosts);
|
|
253
579
|
} catch (error) {
|
|
254
580
|
console.error(error instanceof Error ? error.message : String(error));
|
|
255
581
|
process.exitCode = 1;
|