@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 +3 -0
- package/bin/sellable-install.mjs +207 -1
- package/package.json +1 -1
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.
|
package/bin/sellable-install.mjs
CHANGED
|
@@ -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
|
|