@sellable/install 0.1.1 → 0.1.2

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 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` or `SELLABLE_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,5 @@ 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
+ If only one host is installed, `--host all` installs the available host and tells you how to add the other one later.
@@ -3,6 +3,8 @@ import { spawnSync } from "node:child_process";
3
3
  import { existsSync, mkdirSync, readFileSync, 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 =
@@ -30,7 +32,8 @@ Options:
30
32
 
31
33
  Auth:
32
34
  Sign up or log in at ${DEFAULT_API_URL}/settings, create an API token, then
33
- pass it with --token or SELLABLE_TOKEN.
35
+ pass it with --token or SELLABLE_TOKEN. In an interactive terminal, the
36
+ installer will prompt for missing auth values.
34
37
  `;
35
38
  }
36
39
 
@@ -124,6 +127,87 @@ function commandExists(command) {
124
127
  return result.status === 0;
125
128
  }
126
129
 
130
+ function isInteractive() {
131
+ return Boolean(input.isTTY && output.isTTY);
132
+ }
133
+
134
+ function authPath() {
135
+ return join(homedir(), ".sellable", "config.json");
136
+ }
137
+
138
+ function normalizeStoredAuth(raw) {
139
+ if (raw?.activeEnv && raw?.environments) {
140
+ const env = raw.environments[raw.activeEnv];
141
+ if (!env) return null;
142
+ return {
143
+ token: env.token,
144
+ workspaceId: env.activeWorkspaceId || env.workspaceId,
145
+ apiUrl: env.apiUrl,
146
+ };
147
+ }
148
+
149
+ return {
150
+ token: raw?.token,
151
+ workspaceId: raw?.activeWorkspaceId || raw?.workspaceId,
152
+ apiUrl: raw?.apiUrl,
153
+ };
154
+ }
155
+
156
+ function readStoredAuth() {
157
+ const raw = readExisting(authPath());
158
+ return raw ? normalizeStoredAuth(raw) : null;
159
+ }
160
+
161
+ async function promptForMissingAuth(opts) {
162
+ if (opts.token && opts.workspaceId) return opts;
163
+
164
+ const stored = readStoredAuth();
165
+ if (stored?.token && stored?.workspaceId) {
166
+ opts.token ||= stored.token;
167
+ opts.workspaceId ||= stored.workspaceId;
168
+ opts.apiUrl = opts.apiUrl || stored.apiUrl || DEFAULT_API_URL;
169
+ opts.authFromExistingConfig = true;
170
+ console.log(`Using existing Sellable auth config: ${authPath()}`);
171
+ return opts;
172
+ }
173
+
174
+ if (!isInteractive()) {
175
+ throw new Error(
176
+ [
177
+ "Missing Sellable token/workspace id.",
178
+ `Create a token at ${opts.apiUrl}/settings, then rerun:`,
179
+ " npx -y @sellable/install --host all --token <token> --workspace-id <workspace_id>",
180
+ "",
181
+ "You can also use SELLABLE_TOKEN and SELLABLE_WORKSPACE_ID.",
182
+ ].join("\n")
183
+ );
184
+ }
185
+
186
+ console.log("");
187
+ console.log("Sellable needs one API token to connect Claude/Codex to your workspace.");
188
+ console.log(`Open ${opts.apiUrl}/settings, create a token, then paste the values below.`);
189
+ console.log(`Auth will be stored once at ${authPath()}.`);
190
+ console.log("");
191
+
192
+ const rl = createInterface({ input, output });
193
+ try {
194
+ if (!opts.token) {
195
+ opts.token = (await rl.question("Sellable API token: ")).trim();
196
+ }
197
+ if (!opts.workspaceId) {
198
+ opts.workspaceId = (await rl.question("Sellable workspace id: ")).trim();
199
+ }
200
+ } finally {
201
+ rl.close();
202
+ }
203
+
204
+ if (!opts.token || !opts.workspaceId) {
205
+ throw new Error("Sellable token and workspace id are both required.");
206
+ }
207
+
208
+ return opts;
209
+ }
210
+
127
211
  function writeJson(path, data, opts) {
128
212
  const redacted = JSON.stringify({ ...data, token: redact(data.token) }, null, 2);
129
213
  console.log(`Writing ${path}: ${redacted}`);
@@ -154,7 +238,12 @@ function writeAuth(opts) {
154
238
  apiUrl: opts.apiUrl,
155
239
  };
156
240
 
157
- const sellablePath = join(homedir(), ".sellable", "config.json");
241
+ if (opts.authFromExistingConfig) {
242
+ console.log(`Leaving existing Sellable auth config unchanged: ${authPath()}`);
243
+ return;
244
+ }
245
+
246
+ const sellablePath = authPath();
158
247
  writeJson(sellablePath, config, opts);
159
248
  }
160
249
 
@@ -178,11 +267,16 @@ function mcpCommand(opts) {
178
267
 
179
268
  function installClaude(opts) {
180
269
  if (!commandExists("claude")) {
181
- throw new Error("Missing Claude CLI. Install/login to Claude Code, then rerun this installer.");
270
+ const message = "Claude CLI not found. Install/login to Claude Code, then rerun: sellable --host claude";
271
+ if (opts.host === "all") {
272
+ console.log(`Skipping Claude Code: ${message}`);
273
+ return false;
274
+ }
275
+ throw new Error(message);
182
276
  }
183
277
  if (opts.server === "hosted") {
184
278
  run("claude", ["mcp", "add", "--transport", "http", "sellable", opts.hostedUrl], opts);
185
- return;
279
+ return true;
186
280
  }
187
281
  const [command, args] = mcpCommand(opts);
188
282
  run("claude", ["mcp", "remove", "sellable"], {
@@ -191,15 +285,21 @@ function installClaude(opts) {
191
285
  allowFail: true,
192
286
  });
193
287
  run("claude", ["mcp", "add", "--transport", "stdio", "sellable", "--", command, ...args], opts);
288
+ return true;
194
289
  }
195
290
 
196
291
  function installCodex(opts) {
197
292
  if (!commandExists("codex")) {
198
- throw new Error("Missing Codex CLI. Install/login to Codex, then rerun this installer.");
293
+ const message = "Codex CLI not found. Install/login to Codex, then rerun: sellable --host codex";
294
+ if (opts.host === "all") {
295
+ console.log(`Skipping Codex: ${message}`);
296
+ return false;
297
+ }
298
+ throw new Error(message);
199
299
  }
200
300
  if (opts.server === "hosted") {
201
301
  run("codex", ["mcp", "add", "sellable", "--url", opts.hostedUrl], opts);
202
- return;
302
+ return true;
203
303
  }
204
304
  const [command, args] = mcpCommand(opts);
205
305
  run("codex", ["mcp", "remove", "sellable"], {
@@ -208,15 +308,15 @@ function installCodex(opts) {
208
308
  allowFail: true,
209
309
  });
210
310
  run("codex", ["mcp", "add", "sellable", "--", command, ...args], opts);
311
+ return true;
211
312
  }
212
313
 
213
314
  function verify(opts) {
214
- const authPath = join(homedir(), ".sellable", "config.json");
215
- const auth = readExisting(authPath);
216
- if (!auth?.token || !auth?.activeWorkspaceId) {
217
- throw new Error(`Sellable auth config missing or incomplete: ${authPath}`);
315
+ const stored = readStoredAuth();
316
+ if (!stored?.token || !stored?.workspaceId) {
317
+ throw new Error(`Sellable auth config missing or incomplete: ${authPath()}`);
218
318
  }
219
- console.log(`Sellable auth config present: ${authPath}`);
319
+ console.log(`Sellable auth config present: ${authPath()}`);
220
320
  if (opts.host === "claude" || opts.host === "all") {
221
321
  console.log(commandExists("claude") ? "Claude CLI present" : "Claude CLI missing");
222
322
  }
@@ -225,7 +325,20 @@ function verify(opts) {
225
325
  }
226
326
  }
227
327
 
228
- function main() {
328
+ function printNextSteps(installedHosts) {
329
+ console.log("");
330
+ console.log("Next steps:");
331
+ if (installedHosts.length > 0) {
332
+ console.log(`1. Fully quit and reopen ${installedHosts.join(" and ")} so MCP tools reload.`);
333
+ console.log("2. Start a new thread and choose Sellable Create Campaign.");
334
+ console.log("3. If tools do not appear, run: sellable --verify-only --host all");
335
+ } else {
336
+ console.log("1. Install Claude Code or Codex, then rerun this installer for that host.");
337
+ console.log("2. Verify auth later with: sellable --verify-only --host all");
338
+ }
339
+ }
340
+
341
+ async function main() {
229
342
  try {
230
343
  const opts = parseArgs(process.argv.slice(2));
231
344
  if (opts.help) {
@@ -239,10 +352,16 @@ function main() {
239
352
  console.log(`- api: ${opts.apiUrl}`);
240
353
  console.log(`- token: ${opts.token ? redact(opts.token) : "(missing)"}`);
241
354
 
355
+ const installedHosts = [];
242
356
  if (!opts.verifyOnly) {
357
+ await promptForMissingAuth(opts);
243
358
  writeAuth(opts);
244
- if (opts.host === "claude" || opts.host === "all") installClaude(opts);
245
- if (opts.host === "codex" || opts.host === "all") installCodex(opts);
359
+ if (opts.host === "claude" || opts.host === "all") {
360
+ if (installClaude(opts)) installedHosts.push("Claude Code");
361
+ }
362
+ if (opts.host === "codex" || opts.host === "all") {
363
+ if (installCodex(opts)) installedHosts.push("Codex");
364
+ }
246
365
  }
247
366
  if (opts.dryRun) {
248
367
  console.log("Dry run complete; verification skipped because no files were written.");
@@ -250,6 +369,7 @@ function main() {
250
369
  verify(opts);
251
370
  }
252
371
  console.log("Sellable install complete.");
372
+ if (!opts.verifyOnly && !opts.dryRun) printNextSteps(installedHosts);
253
373
  } catch (error) {
254
374
  console.error(error instanceof Error ? error.message : String(error));
255
375
  process.exitCode = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code and Codex",
6
6
  "bin": {