@sellable/install 0.1.2 → 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 CHANGED
@@ -30,4 +30,7 @@ Auth is stored once at:
30
30
 
31
31
  Claude Code and Codex are configured to launch the same packaged MCP server.
32
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
+
33
36
  If only one host is installed, `--host all` installs the available host and tells you how to add the other one later.
@@ -1,6 +1,6 @@
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
6
  import { stdin as input, stdout as output } from "node:process";
@@ -9,6 +9,7 @@ import { createInterface } from "node:readline/promises";
9
9
  const DEFAULT_API_URL = "https://app.sellable.dev";
10
10
  const DEFAULT_SERVER_PACKAGE =
11
11
  process.env.SELLABLE_MCP_PACKAGE || "@sellable/mcp";
12
+ const CODEX_PLUGIN_VERSION = "0.1.3";
12
13
 
13
14
  function usage() {
14
15
  return `Sellable agent installer
@@ -225,6 +226,198 @@ function readExisting(path) {
225
226
  }
226
227
  }
227
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
+
228
421
  function writeAuth(opts) {
229
422
  if (!opts.token || !opts.workspaceId) {
230
423
  throw new Error(
@@ -299,6 +492,7 @@ function installCodex(opts) {
299
492
  }
300
493
  if (opts.server === "hosted") {
301
494
  run("codex", ["mcp", "add", "sellable", "--url", opts.hostedUrl], opts);
495
+ installCodexDesktopPlugin(opts);
302
496
  return true;
303
497
  }
304
498
  const [command, args] = mcpCommand(opts);
@@ -308,6 +502,7 @@ function installCodex(opts) {
308
502
  allowFail: true,
309
503
  });
310
504
  run("codex", ["mcp", "add", "sellable", "--", command, ...args], opts);
505
+ installCodexDesktopPlugin(opts);
311
506
  return true;
312
507
  }
313
508
 
@@ -322,6 +517,17 @@ function verify(opts) {
322
517
  }
323
518
  if (opts.host === "codex" || opts.host === "all") {
324
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");
325
531
  }
326
532
  }
327
533
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code and Codex",
6
6
  "bin": {