@sellable/install 0.1.2 → 0.1.4
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 +4 -0
- package/bin/sellable-install.mjs +464 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,4 +30,8 @@ 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`, includes the Sellable skill entrypoints, and
|
|
35
|
+
enables it in `~/.codex/config.toml`.
|
|
36
|
+
|
|
33
37
|
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,8 @@ 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.4";
|
|
13
|
+
const INSTALL_PACKAGE_SPEC = `@sellable/install@${CODEX_PLUGIN_VERSION}`;
|
|
12
14
|
|
|
13
15
|
function usage() {
|
|
14
16
|
return `Sellable agent installer
|
|
@@ -176,7 +178,7 @@ async function promptForMissingAuth(opts) {
|
|
|
176
178
|
[
|
|
177
179
|
"Missing Sellable token/workspace id.",
|
|
178
180
|
`Create a token at ${opts.apiUrl}/settings, then rerun:`,
|
|
179
|
-
|
|
181
|
+
` npx -y ${INSTALL_PACKAGE_SPEC} --host all --token <token> --workspace-id <workspace_id>`,
|
|
180
182
|
"",
|
|
181
183
|
"You can also use SELLABLE_TOKEN and SELLABLE_WORKSPACE_ID.",
|
|
182
184
|
].join("\n")
|
|
@@ -225,6 +227,441 @@ function readExisting(path) {
|
|
|
225
227
|
}
|
|
226
228
|
}
|
|
227
229
|
|
|
230
|
+
function codexHome() {
|
|
231
|
+
return process.env.CODEX_HOME?.trim() || join(homedir(), ".codex");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function quoteToml(value) {
|
|
235
|
+
return `"${String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function escapeRegExp(value) {
|
|
239
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function upsertTomlTable(content, tableName, block) {
|
|
243
|
+
const tablePattern = new RegExp(
|
|
244
|
+
`(^|\\n)\\[${escapeRegExp(tableName)}\\]\\n[\\s\\S]*?(?=\\n\\[[^\\n]+\\]|$)`
|
|
245
|
+
);
|
|
246
|
+
const normalizedBlock = block.trimEnd();
|
|
247
|
+
|
|
248
|
+
if (tablePattern.test(content)) {
|
|
249
|
+
return content.replace(tablePattern, (_, prefix) => `${prefix}${normalizedBlock}`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return `${content.trimEnd()}\n\n${normalizedBlock}\n`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function writeFile(path, content, opts, mode = 0o644) {
|
|
256
|
+
console.log(`Writing ${path}`);
|
|
257
|
+
if (opts.dryRun) return;
|
|
258
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
259
|
+
writeFileSync(path, content, { mode });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function codexPluginManifest(opts) {
|
|
263
|
+
return {
|
|
264
|
+
name: "sellable",
|
|
265
|
+
version: CODEX_PLUGIN_VERSION,
|
|
266
|
+
description:
|
|
267
|
+
"Sellable MCP tools for campaign creation, engagement, interviews, and workflow sequencing.",
|
|
268
|
+
author: {
|
|
269
|
+
name: "Sellable",
|
|
270
|
+
url: "https://sellable.dev/",
|
|
271
|
+
},
|
|
272
|
+
homepage: "https://sellable.dev/",
|
|
273
|
+
repository: "https://github.com/csreyes/sellable",
|
|
274
|
+
license: "UNLICENSED",
|
|
275
|
+
keywords: ["sellable", "linkedin", "outbound", "campaigns", "mcp"],
|
|
276
|
+
mcpServers: "./.mcp.json",
|
|
277
|
+
interface: {
|
|
278
|
+
displayName: "Sellable",
|
|
279
|
+
shortDescription: "Sellable MCP tools for outbound campaign workflows",
|
|
280
|
+
longDescription:
|
|
281
|
+
"Loads the Sellable MCP server into Codex Desktop so Sellable skills can create campaigns, find leads, draft messages, and manage approval-gated launch workflows.",
|
|
282
|
+
developerName: "Sellable",
|
|
283
|
+
category: "Productivity",
|
|
284
|
+
capabilities: ["Interactive", "Write"],
|
|
285
|
+
websiteURL: "https://sellable.dev/",
|
|
286
|
+
privacyPolicyURL: "https://sellable.dev/privacy",
|
|
287
|
+
termsOfServiceURL: "https://sellable.dev/terms",
|
|
288
|
+
defaultPrompt: [
|
|
289
|
+
"Create a Sellable campaign",
|
|
290
|
+
"Find LinkedIn leads",
|
|
291
|
+
"Build an outbound sequence",
|
|
292
|
+
],
|
|
293
|
+
brandColor: "#8B5CF6",
|
|
294
|
+
screenshots: [],
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function codexPluginMcp(opts) {
|
|
300
|
+
if (opts.server === "hosted") {
|
|
301
|
+
return {
|
|
302
|
+
mcpServers: {
|
|
303
|
+
sellable: {
|
|
304
|
+
type: "http",
|
|
305
|
+
url: opts.hostedUrl,
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (opts.server === "local") {
|
|
312
|
+
const [command, args] = mcpCommand(opts);
|
|
313
|
+
return {
|
|
314
|
+
mcpServers: {
|
|
315
|
+
sellable: {
|
|
316
|
+
type: "stdio",
|
|
317
|
+
command,
|
|
318
|
+
args,
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
mcpServers: {
|
|
326
|
+
sellable: {
|
|
327
|
+
type: "stdio",
|
|
328
|
+
command: "npx",
|
|
329
|
+
args: ["-y", opts.mcpPackage],
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const CREATE_CAMPAIGN_ALLOWED_TOOLS = [
|
|
336
|
+
"mcp__sellable__get_auth_status",
|
|
337
|
+
"mcp__sellable__bootstrap_create_campaign",
|
|
338
|
+
"mcp__sellable__get_subskill_prompt",
|
|
339
|
+
"mcp__sellable__search_subskill_prompts",
|
|
340
|
+
"mcp__sellable__get_provider_prompt",
|
|
341
|
+
"mcp__sellable__get_message_prompt",
|
|
342
|
+
"mcp__sellable__get_active_workspace",
|
|
343
|
+
"mcp__sellable__list_senders",
|
|
344
|
+
"mcp__sellable__get_sender",
|
|
345
|
+
"mcp__sellable__enrich_sender",
|
|
346
|
+
"mcp__sellable__complete_sender_research",
|
|
347
|
+
"mcp__sellable__fetch_linkedin_profile",
|
|
348
|
+
"mcp__sellable__fetch_linkedin_posts",
|
|
349
|
+
"mcp__sellable__get_linkedin_profile",
|
|
350
|
+
"mcp__sellable__fetch_company",
|
|
351
|
+
"mcp__sellable__fetch_company_posts",
|
|
352
|
+
"mcp__sellable__lookup_sales_nav_filter",
|
|
353
|
+
"mcp__sellable__search_sales_nav",
|
|
354
|
+
"mcp__sellable__search_prospeo",
|
|
355
|
+
"mcp__sellable__search_signals",
|
|
356
|
+
"mcp__sellable__fetch_post_engagers",
|
|
357
|
+
"mcp__sellable__enrich_with_prospeo",
|
|
358
|
+
"mcp__sellable__bulk_enrich_with_prospeo",
|
|
359
|
+
"mcp__sellable__save_domain_filters",
|
|
360
|
+
"mcp__sellable__add_rubric_item",
|
|
361
|
+
"mcp__sellable__upsert_rubric",
|
|
362
|
+
"mcp__sellable__set_headline_icp_criteria",
|
|
363
|
+
"mcp__sellable__check_rubric",
|
|
364
|
+
"mcp__sellable__create_campaign",
|
|
365
|
+
"mcp__sellable__save_rubrics",
|
|
366
|
+
"mcp__sellable__wait_for_rubric_results",
|
|
367
|
+
"mcp__sellable__update_campaign_brief",
|
|
368
|
+
"mcp__sellable__update_campaign",
|
|
369
|
+
"mcp__sellable__get_campaign",
|
|
370
|
+
"mcp__sellable__get_campaign_context",
|
|
371
|
+
"mcp__sellable__get_campaign_framework",
|
|
372
|
+
"mcp__sellable__get_campaign_navigation_state",
|
|
373
|
+
"mcp__sellable__confirm_lead_list",
|
|
374
|
+
"mcp__sellable__import_leads",
|
|
375
|
+
"mcp__sellable__wait_for_lead_list_ready",
|
|
376
|
+
"mcp__sellable__wait_for_campaign_table_ready",
|
|
377
|
+
"mcp__sellable__get_rows",
|
|
378
|
+
"mcp__sellable__get_rows_minimal",
|
|
379
|
+
"mcp__sellable__get_table_rows",
|
|
380
|
+
"mcp__sellable__load_csv_linkedin_leads",
|
|
381
|
+
"mcp__sellable__load_csv_domains",
|
|
382
|
+
"mcp__sellable__queue_cells",
|
|
383
|
+
"mcp__sellable__generate_messages",
|
|
384
|
+
"mcp__sellable__get_campaign_messages_preview",
|
|
385
|
+
"mcp__sellable__attach_sequence",
|
|
386
|
+
"mcp__sellable__attach_recommended_sequence",
|
|
387
|
+
"mcp__sellable__start_campaign",
|
|
388
|
+
];
|
|
389
|
+
|
|
390
|
+
function allowedToolsYaml(tools) {
|
|
391
|
+
return tools.map((tool) => ` - ${tool}`).join("\n");
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function codexSkillOpenAiYaml(displayName, description) {
|
|
395
|
+
return `interface:
|
|
396
|
+
display_name: "${displayName.replaceAll('"', '\\"')}"
|
|
397
|
+
short_description: "${description.replaceAll('"', '\\"')}"
|
|
398
|
+
|
|
399
|
+
dependencies:
|
|
400
|
+
tools:
|
|
401
|
+
- type: "mcp"
|
|
402
|
+
value: "sellable"
|
|
403
|
+
description: "Sellable MCP server"
|
|
404
|
+
`;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function createCampaignSkillMd() {
|
|
408
|
+
return `---
|
|
409
|
+
name: sellable:create-campaign
|
|
410
|
+
description: Create a Sellable campaign through the approval-gated workflow.
|
|
411
|
+
allowed-tools:
|
|
412
|
+
${allowedToolsYaml(CREATE_CAMPAIGN_ALLOWED_TOOLS)}
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
# Sellable Create Campaign
|
|
416
|
+
|
|
417
|
+
Use this as the customer-facing entrypoint for Sellable campaign creation.
|
|
418
|
+
|
|
419
|
+
## Bootstrap
|
|
420
|
+
|
|
421
|
+
MCP tool access is required. First load the bootstrap tools in one tool-discovery
|
|
422
|
+
pass:
|
|
423
|
+
|
|
424
|
+
- \`ToolSearch("select:mcp__sellable__get_auth_status")\`
|
|
425
|
+
- \`ToolSearch("select:mcp__sellable__bootstrap_create_campaign")\`
|
|
426
|
+
- \`ToolSearch("select:mcp__sellable__get_subskill_prompt")\`
|
|
427
|
+
- \`ToolSearch("select:mcp__sellable__search_subskill_prompts")\`
|
|
428
|
+
|
|
429
|
+
Do not stop before attempting this ToolSearch preload. If the
|
|
430
|
+
\`mcp__sellable__*\` tools are still not exposed after ToolSearch, stop and say
|
|
431
|
+
this is a Codex install/reload problem, not a campaign problem. Tell the user to
|
|
432
|
+
run \`npx -y ${INSTALL_PACKAGE_SPEC} --host all\` so the packaged MCP server,
|
|
433
|
+
Codex Desktop plugin, and Sellable skill bundle are installed. If they want a
|
|
434
|
+
CLI verification, tell them to run \`sellable --verify-only --host all\`. After
|
|
435
|
+
that, they must fully quit and reopen Codex Desktop before starting a new
|
|
436
|
+
thread. Do not use \`scripts/mcp/sellable-tool-call.mjs\`, \`npm run\`,
|
|
437
|
+
\`node\`, or any local harness as a fallback for this interactive skill.
|
|
438
|
+
|
|
439
|
+
1. Call \`mcp__sellable__get_auth_status({})\`.
|
|
440
|
+
2. If auth is not OK, stop and show the returned guidance.
|
|
441
|
+
3. Detect optional campaign id in the user request (\`cmp_...\`).
|
|
442
|
+
4. If no campaign id is provided, stay in fresh-create mode and do not call campaign discovery/resume helpers to find one.
|
|
443
|
+
- Do not call \`mcp__sellable__get_campaigns\`.
|
|
444
|
+
- Do not call \`mcp__sellable__get_campaign\` to hunt for IDs.
|
|
445
|
+
- Do not call \`mcp__sellable__create_campaign({ campaignId: ... })\` unless the user supplied that id.
|
|
446
|
+
5. Call \`mcp__sellable__bootstrap_create_campaign({ flowVersion: "v2", campaignId? })\`.
|
|
447
|
+
6. If \`safeToProceed !== true\`, stop and show \`blockingErrors\` + \`nextStep\`.
|
|
448
|
+
|
|
449
|
+
## Execute Workflow
|
|
450
|
+
|
|
451
|
+
1. Load canonical prompt via
|
|
452
|
+
\`mcp__sellable__get_subskill_prompt({ subskillName: "create-campaign-v2" })\`.
|
|
453
|
+
2. Follow that prompt exactly.
|
|
454
|
+
3. For message generation, load the full \`generate-messages\` prompt in the
|
|
455
|
+
same run with chunked
|
|
456
|
+
\`mcp__sellable__get_subskill_prompt({ subskillName: "generate-messages", offset, limit })\`
|
|
457
|
+
calls until \`hasMore\` is false. Do not synthesize
|
|
458
|
+
\`message-validation.md\` from the brief, lead review, or general knowledge.
|
|
459
|
+
4. Treat message quality as the gate before minting. Do not create a campaign,
|
|
460
|
+
show a commit gate, or mint anything until \`message-validation.md\` proves
|
|
461
|
+
the full generate-messages workflow ran and \`message-review.md\` recommends
|
|
462
|
+
\`approve-message\` against the gold-standard rules.
|
|
463
|
+
5. Do not create or mutate the live campaign until the approval gate returns
|
|
464
|
+
\`approve\`.
|
|
465
|
+
6. Do not ask the user to run another command.
|
|
466
|
+
|
|
467
|
+
## Fallback
|
|
468
|
+
|
|
469
|
+
If subskill lookup fails, use
|
|
470
|
+
\`mcp__sellable__search_subskill_prompts({ query: "create-campaign-v2" })\`,
|
|
471
|
+
then retry \`get_subskill_prompt\`.
|
|
472
|
+
`;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function genericSellableSkillMd({ name, title, description }) {
|
|
476
|
+
return `---
|
|
477
|
+
name: sellable:${name}
|
|
478
|
+
description: ${description}
|
|
479
|
+
allowed-tools:
|
|
480
|
+
- mcp__sellable__get_auth_status
|
|
481
|
+
- mcp__sellable__get_subskill_prompt
|
|
482
|
+
- mcp__sellable__search_subskill_prompts
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
# ${title}
|
|
486
|
+
|
|
487
|
+
Use this as the customer-facing entrypoint for the Sellable \`${name}\` workflow.
|
|
488
|
+
|
|
489
|
+
## Bootstrap
|
|
490
|
+
|
|
491
|
+
MCP tool access is required. Do not inspect repo files, run shell commands, use
|
|
492
|
+
\`npm\`, \`node\`, local harness scripts, or read local prompt files to emulate
|
|
493
|
+
this workflow.
|
|
494
|
+
|
|
495
|
+
Before giving up, try to load the required tools with ToolSearch:
|
|
496
|
+
|
|
497
|
+
- \`ToolSearch("select:mcp__sellable__get_auth_status")\`
|
|
498
|
+
- \`ToolSearch("select:mcp__sellable__get_subskill_prompt")\`
|
|
499
|
+
- \`ToolSearch("select:mcp__sellable__search_subskill_prompts")\`
|
|
500
|
+
|
|
501
|
+
If the \`mcp__sellable__*\` tools are still not exposed after ToolSearch, stop
|
|
502
|
+
and say this is a Codex install/reload problem. Tell the user to run
|
|
503
|
+
\`npx -y ${INSTALL_PACKAGE_SPEC} --host all\`, fully quit and reopen Codex
|
|
504
|
+
Desktop, then start a new thread.
|
|
505
|
+
|
|
506
|
+
1. Call \`mcp__sellable__get_auth_status({})\`.
|
|
507
|
+
2. If auth is not OK, stop and show the returned guidance.
|
|
508
|
+
|
|
509
|
+
## Execute Workflow
|
|
510
|
+
|
|
511
|
+
1. Load canonical prompt via \`mcp__sellable__get_subskill_prompt({ subskillName: "${name}" })\`.
|
|
512
|
+
2. Follow that prompt exactly.
|
|
513
|
+
|
|
514
|
+
## MCP Prompt Fallback
|
|
515
|
+
|
|
516
|
+
If subskill lookup fails, use \`mcp__sellable__search_subskill_prompts({ query: "${name}" })\`, then retry \`get_subskill_prompt\`.
|
|
517
|
+
`;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function codexPluginSkills() {
|
|
521
|
+
return [
|
|
522
|
+
{
|
|
523
|
+
dir: "sellable-create-campaign",
|
|
524
|
+
displayName: "Sellable Create Campaign",
|
|
525
|
+
description: "Create a Sellable campaign with approval gates",
|
|
526
|
+
skillMd: createCampaignSkillMd(),
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
dir: "sellable-engage",
|
|
530
|
+
displayName: "Sellable Engage",
|
|
531
|
+
description: "Discover posts and draft comments for a comment campaign",
|
|
532
|
+
skillMd: genericSellableSkillMd({
|
|
533
|
+
name: "engage",
|
|
534
|
+
title: "Sellable Engage",
|
|
535
|
+
description:
|
|
536
|
+
"Find high-signal LinkedIn posts and draft thoughtful engagement comments, then load approved comments into the sender's comment campaign.",
|
|
537
|
+
}),
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
dir: "sellable-interview",
|
|
541
|
+
displayName: "Sellable Interview",
|
|
542
|
+
description: "Build a commenting style guide and ICP profile",
|
|
543
|
+
skillMd: genericSellableSkillMd({
|
|
544
|
+
name: "interview",
|
|
545
|
+
title: "Sellable Interview",
|
|
546
|
+
description:
|
|
547
|
+
"Voice-first interview to build your commenting style guide and ICP profile.",
|
|
548
|
+
}),
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
dir: "sellable-workflow-sequences",
|
|
552
|
+
displayName: "Sellable Workflow Sequences",
|
|
553
|
+
description: "Create workflow tables and attach outbound sequences",
|
|
554
|
+
skillMd: genericSellableSkillMd({
|
|
555
|
+
name: "workflow-sequences",
|
|
556
|
+
title: "Sellable Workflow Sequences",
|
|
557
|
+
description:
|
|
558
|
+
"Create workflow tables via Sellable MCP, attach outbound sequence templates, and avoid manual sequence-column runCondition mistakes.",
|
|
559
|
+
}),
|
|
560
|
+
},
|
|
561
|
+
];
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function writeCodexPluginSkills(pluginRoot, opts) {
|
|
565
|
+
for (const skill of codexPluginSkills()) {
|
|
566
|
+
const skillRoot = join(pluginRoot, "skills", skill.dir);
|
|
567
|
+
writeFile(join(skillRoot, "SKILL.md"), skill.skillMd, opts);
|
|
568
|
+
writeFile(
|
|
569
|
+
join(skillRoot, "agents", "openai.yaml"),
|
|
570
|
+
codexSkillOpenAiYaml(skill.displayName, skill.description),
|
|
571
|
+
opts
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function installCodexDesktopPlugin(opts) {
|
|
577
|
+
const home = codexHome();
|
|
578
|
+
const configPath = join(home, "config.toml");
|
|
579
|
+
const marketplaceRoot = join(homedir(), ".sellable", "codex-marketplace");
|
|
580
|
+
const marketplacePath = join(marketplaceRoot, ".agents", "plugins", "marketplace.json");
|
|
581
|
+
const pluginRoot = join(marketplaceRoot, "plugins", "sellable");
|
|
582
|
+
const cacheRoot = join(home, "plugins", "cache", "sellable", "sellable");
|
|
583
|
+
const pluginCache = join(cacheRoot, CODEX_PLUGIN_VERSION);
|
|
584
|
+
|
|
585
|
+
const marketplace = {
|
|
586
|
+
name: "sellable",
|
|
587
|
+
interface: {
|
|
588
|
+
displayName: "Sellable",
|
|
589
|
+
},
|
|
590
|
+
plugins: [
|
|
591
|
+
{
|
|
592
|
+
name: "sellable",
|
|
593
|
+
source: {
|
|
594
|
+
source: "local",
|
|
595
|
+
path: "./plugins/sellable",
|
|
596
|
+
},
|
|
597
|
+
policy: {
|
|
598
|
+
installation: "AVAILABLE",
|
|
599
|
+
authentication: "ON_INSTALL",
|
|
600
|
+
},
|
|
601
|
+
category: "Productivity",
|
|
602
|
+
},
|
|
603
|
+
],
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const manifest = codexPluginManifest(opts);
|
|
607
|
+
const mcp = codexPluginMcp(opts);
|
|
608
|
+
|
|
609
|
+
writeFile(marketplacePath, `${JSON.stringify(marketplace, null, 2)}\n`, opts);
|
|
610
|
+
writeFile(
|
|
611
|
+
join(pluginRoot, ".codex-plugin", "plugin.json"),
|
|
612
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
613
|
+
opts
|
|
614
|
+
);
|
|
615
|
+
writeFile(join(pluginRoot, ".mcp.json"), `${JSON.stringify(mcp, null, 2)}\n`, opts);
|
|
616
|
+
writeCodexPluginSkills(pluginRoot, opts);
|
|
617
|
+
|
|
618
|
+
if (!opts.dryRun) {
|
|
619
|
+
rmSync(cacheRoot, { recursive: true, force: true });
|
|
620
|
+
}
|
|
621
|
+
writeFile(
|
|
622
|
+
join(pluginCache, ".codex-plugin", "plugin.json"),
|
|
623
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
624
|
+
opts
|
|
625
|
+
);
|
|
626
|
+
writeFile(join(pluginCache, ".mcp.json"), `${JSON.stringify(mcp, null, 2)}\n`, opts);
|
|
627
|
+
writeCodexPluginSkills(pluginCache, opts);
|
|
628
|
+
|
|
629
|
+
if (!opts.dryRun) {
|
|
630
|
+
mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
631
|
+
let content = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
632
|
+
content = upsertTomlTable(
|
|
633
|
+
content,
|
|
634
|
+
"marketplaces.sellable",
|
|
635
|
+
`[marketplaces.sellable]
|
|
636
|
+
source_type = "local"
|
|
637
|
+
source = ${quoteToml(marketplaceRoot)}
|
|
638
|
+
last_updated = ${quoteToml(new Date().toISOString())}`
|
|
639
|
+
);
|
|
640
|
+
content = upsertTomlTable(
|
|
641
|
+
content,
|
|
642
|
+
'plugins."sellable@sellable"',
|
|
643
|
+
`[plugins."sellable@sellable"]
|
|
644
|
+
enabled = true`
|
|
645
|
+
);
|
|
646
|
+
content = upsertTomlTable(
|
|
647
|
+
content,
|
|
648
|
+
'plugins."sellable@sellable-local"',
|
|
649
|
+
`[plugins."sellable@sellable-local"]
|
|
650
|
+
enabled = false`
|
|
651
|
+
);
|
|
652
|
+
writeFileSync(configPath, `${content.trimEnd()}\n`, { mode: 0o600 });
|
|
653
|
+
} else {
|
|
654
|
+
console.log(`+ upsert [marketplaces.sellable] in ${configPath}`);
|
|
655
|
+
console.log(`+ enable [plugins."sellable@sellable"] in ${configPath}`);
|
|
656
|
+
console.log(`+ disable stale [plugins."sellable@sellable-local"] in ${configPath}`);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
console.log("Codex Desktop plugin installed:");
|
|
660
|
+
console.log(`- marketplace: ${marketplaceRoot}`);
|
|
661
|
+
console.log(`- plugin: sellable@sellable`);
|
|
662
|
+
console.log(`- cache: ${pluginCache}`);
|
|
663
|
+
}
|
|
664
|
+
|
|
228
665
|
function writeAuth(opts) {
|
|
229
666
|
if (!opts.token || !opts.workspaceId) {
|
|
230
667
|
throw new Error(
|
|
@@ -299,6 +736,7 @@ function installCodex(opts) {
|
|
|
299
736
|
}
|
|
300
737
|
if (opts.server === "hosted") {
|
|
301
738
|
run("codex", ["mcp", "add", "sellable", "--url", opts.hostedUrl], opts);
|
|
739
|
+
installCodexDesktopPlugin(opts);
|
|
302
740
|
return true;
|
|
303
741
|
}
|
|
304
742
|
const [command, args] = mcpCommand(opts);
|
|
@@ -308,6 +746,7 @@ function installCodex(opts) {
|
|
|
308
746
|
allowFail: true,
|
|
309
747
|
});
|
|
310
748
|
run("codex", ["mcp", "add", "sellable", "--", command, ...args], opts);
|
|
749
|
+
installCodexDesktopPlugin(opts);
|
|
311
750
|
return true;
|
|
312
751
|
}
|
|
313
752
|
|
|
@@ -322,6 +761,29 @@ function verify(opts) {
|
|
|
322
761
|
}
|
|
323
762
|
if (opts.host === "codex" || opts.host === "all") {
|
|
324
763
|
console.log(commandExists("codex") ? "Codex CLI present" : "Codex CLI missing");
|
|
764
|
+
const pluginPath = join(
|
|
765
|
+
codexHome(),
|
|
766
|
+
"plugins",
|
|
767
|
+
"cache",
|
|
768
|
+
"sellable",
|
|
769
|
+
"sellable",
|
|
770
|
+
CODEX_PLUGIN_VERSION,
|
|
771
|
+
".codex-plugin",
|
|
772
|
+
"plugin.json"
|
|
773
|
+
);
|
|
774
|
+
const skillPath = join(
|
|
775
|
+
codexHome(),
|
|
776
|
+
"plugins",
|
|
777
|
+
"cache",
|
|
778
|
+
"sellable",
|
|
779
|
+
"sellable",
|
|
780
|
+
CODEX_PLUGIN_VERSION,
|
|
781
|
+
"skills",
|
|
782
|
+
"sellable-create-campaign",
|
|
783
|
+
"SKILL.md"
|
|
784
|
+
);
|
|
785
|
+
console.log(existsSync(pluginPath) ? "Codex Desktop plugin present" : "Codex Desktop plugin missing");
|
|
786
|
+
console.log(existsSync(skillPath) ? "Codex skill bundle present" : "Codex skill bundle missing");
|
|
325
787
|
}
|
|
326
788
|
}
|
|
327
789
|
|