@sellable/install 0.1.6 → 0.1.8
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 +22 -0
- package/bin/sellable-install.mjs +175 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,6 +30,28 @@ 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
|
+
## Names
|
|
34
|
+
|
|
35
|
+
Use the same public entrypoint in both hosts:
|
|
36
|
+
|
|
37
|
+
- Claude Code: `/sellable:create-campaign`
|
|
38
|
+
- Codex: `$sellable:create-campaign`
|
|
39
|
+
- Codex Desktop plugin: `sellable@sellable`
|
|
40
|
+
- Codex visible skill: `Sellable Create Campaign`
|
|
41
|
+
- Internal MCP workflow prompt: `create-campaign-v2`
|
|
42
|
+
|
|
43
|
+
Do not ask users to run `/sellable:create-campaign-v2`,
|
|
44
|
+
`$sellable:create-campaign-v2`, or `$sellable:sellable:create-campaign`.
|
|
45
|
+
`create-campaign-v2` is loaded internally by the skill.
|
|
46
|
+
|
|
47
|
+
## Structured Questions
|
|
48
|
+
|
|
49
|
+
Claude Code uses `AskUserQuestion`. Codex uses `request_user_input` when that
|
|
50
|
+
tool is exposed in an interactive session. The installer enables Codex Default
|
|
51
|
+
mode support by writing `default_mode_request_user_input = true` under
|
|
52
|
+
`[features]` in `~/.codex/config.toml`. `codex exec` is non-interactive, so it
|
|
53
|
+
cannot show the structured questionnaire UI.
|
|
54
|
+
|
|
33
55
|
For Codex Desktop, the installer also writes a local Sellable plugin bundle into
|
|
34
56
|
`~/.sellable/codex-marketplace`, includes the Sellable skill entrypoints, and
|
|
35
57
|
enables it in `~/.codex/config.toml`.
|
package/bin/sellable-install.mjs
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
5
10
|
import { homedir } from "node:os";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
6
12
|
import { stdin as input, stdout as output } from "node:process";
|
|
7
13
|
import { createInterface } from "node:readline/promises";
|
|
8
14
|
|
|
9
15
|
const DEFAULT_API_URL = "https://app.sellable.dev";
|
|
10
16
|
const DEFAULT_SERVER_PACKAGE =
|
|
11
17
|
process.env.SELLABLE_MCP_PACKAGE || "@sellable/mcp";
|
|
12
|
-
const CODEX_PLUGIN_VERSION = "0.1.
|
|
18
|
+
const CODEX_PLUGIN_VERSION = "0.1.8";
|
|
13
19
|
const INSTALL_PACKAGE_SPEC = `@sellable/install@${CODEX_PLUGIN_VERSION}`;
|
|
14
20
|
|
|
15
21
|
function usage() {
|
|
@@ -109,7 +115,9 @@ function redact(value) {
|
|
|
109
115
|
|
|
110
116
|
function run(command, args, opts = {}) {
|
|
111
117
|
const rendered = [command, ...args].join(" ");
|
|
112
|
-
console.log(
|
|
118
|
+
console.log(
|
|
119
|
+
`+ ${rendered.replace(opts.token || "__NO_TOKEN__", "[redacted-token]")}`
|
|
120
|
+
);
|
|
113
121
|
if (opts.dryRun) return { status: 0, stdout: "", stderr: "" };
|
|
114
122
|
const result = spawnSync(command, args, {
|
|
115
123
|
encoding: "utf8",
|
|
@@ -186,8 +194,12 @@ async function promptForMissingAuth(opts) {
|
|
|
186
194
|
}
|
|
187
195
|
|
|
188
196
|
console.log("");
|
|
189
|
-
console.log(
|
|
190
|
-
|
|
197
|
+
console.log(
|
|
198
|
+
"Sellable needs one API token to connect Claude/Codex to your workspace."
|
|
199
|
+
);
|
|
200
|
+
console.log(
|
|
201
|
+
`Open ${opts.apiUrl}/settings, create a token, then paste the values below.`
|
|
202
|
+
);
|
|
191
203
|
console.log(`Auth will be stored once at ${authPath()}.`);
|
|
192
204
|
console.log("");
|
|
193
205
|
|
|
@@ -211,7 +223,11 @@ async function promptForMissingAuth(opts) {
|
|
|
211
223
|
}
|
|
212
224
|
|
|
213
225
|
function writeJson(path, data, opts) {
|
|
214
|
-
const redacted = JSON.stringify(
|
|
226
|
+
const redacted = JSON.stringify(
|
|
227
|
+
{ ...data, token: redact(data.token) },
|
|
228
|
+
null,
|
|
229
|
+
2
|
|
230
|
+
);
|
|
215
231
|
console.log(`Writing ${path}: ${redacted}`);
|
|
216
232
|
if (opts.dryRun) return;
|
|
217
233
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
@@ -246,12 +262,38 @@ function upsertTomlTable(content, tableName, block) {
|
|
|
246
262
|
const normalizedBlock = block.trimEnd();
|
|
247
263
|
|
|
248
264
|
if (tablePattern.test(content)) {
|
|
249
|
-
return content.replace(
|
|
265
|
+
return content.replace(
|
|
266
|
+
tablePattern,
|
|
267
|
+
(_, prefix) => `${prefix}${normalizedBlock}`
|
|
268
|
+
);
|
|
250
269
|
}
|
|
251
270
|
|
|
252
271
|
return `${content.trimEnd()}\n\n${normalizedBlock}\n`;
|
|
253
272
|
}
|
|
254
273
|
|
|
274
|
+
function upsertTomlBoolean(content, tableName, key, value) {
|
|
275
|
+
const line = `${key} = ${value ? "true" : "false"}`;
|
|
276
|
+
const tablePattern = new RegExp(
|
|
277
|
+
`(^|\\n)(\\[${escapeRegExp(tableName)}\\]\\n)([\\s\\S]*?)(?=\\n\\[[^\\n]+\\]|$)`
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
if (!tablePattern.test(content)) {
|
|
281
|
+
return `${content.trimEnd()}\n\n[${tableName}]\n${line}\n`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return content.replace(tablePattern, (_, prefix, header, body) => {
|
|
285
|
+
const keyPattern = new RegExp(
|
|
286
|
+
`(^|\\n)\\s*${escapeRegExp(key)}\\s*=.*`,
|
|
287
|
+
"m"
|
|
288
|
+
);
|
|
289
|
+
const updatedBody = keyPattern.test(body)
|
|
290
|
+
? body.replace(keyPattern, `$1${line}`)
|
|
291
|
+
: `${body.trimEnd()}${body.trimEnd() ? "\n" : ""}${line}\n`;
|
|
292
|
+
|
|
293
|
+
return `${prefix}${header}${updatedBody.trimEnd()}`;
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
255
297
|
function writeFile(path, content, opts, mode = 0o644) {
|
|
256
298
|
console.log(`Writing ${path}`);
|
|
257
299
|
if (opts.dryRun) return;
|
|
@@ -410,6 +452,43 @@ ${allowedToolsYaml(CREATE_CAMPAIGN_ALLOWED_TOOLS)}
|
|
|
410
452
|
|
|
411
453
|
Use this as the customer-facing entrypoint for Sellable campaign creation.
|
|
412
454
|
|
|
455
|
+
## Names To Use
|
|
456
|
+
|
|
457
|
+
Use these exact public names so Claude Code and Codex do not drift:
|
|
458
|
+
|
|
459
|
+
- Claude Code command: \`/sellable:create-campaign\`
|
|
460
|
+
- Codex skill command: \`$sellable:create-campaign\`
|
|
461
|
+
- Codex Desktop plugin: \`sellable@sellable\`
|
|
462
|
+
- Codex visible skill: \`Sellable Create Campaign\`
|
|
463
|
+
- Codex skill frontmatter name: \`create-campaign\`
|
|
464
|
+
- MCP server name: \`sellable\`
|
|
465
|
+
- Internal workflow prompt: \`create-campaign-v2\`
|
|
466
|
+
|
|
467
|
+
Do not tell users to run \`/sellable:create-campaign-v2\`,
|
|
468
|
+
\`$sellable:create-campaign-v2\`, or \`$sellable:sellable:create-campaign\`.
|
|
469
|
+
\`create-campaign-v2\` is only the internal subskill loaded through
|
|
470
|
+
\`mcp__sellable__get_subskill_prompt({ subskillName: "create-campaign-v2" })\`.
|
|
471
|
+
|
|
472
|
+
## Structured Questions
|
|
473
|
+
|
|
474
|
+
Use the host-native structured question gate for intake and approval:
|
|
475
|
+
|
|
476
|
+
- Claude Code: \`AskUserQuestion\`
|
|
477
|
+
- Codex: \`request_user_input\` when exposed in an interactive session. The
|
|
478
|
+
installer enables this in Default mode with
|
|
479
|
+
\`[features].default_mode_request_user_input = true\`.
|
|
480
|
+
|
|
481
|
+
Do not silently ask Codex intake or approval questions as plain chat when
|
|
482
|
+
\`request_user_input\` is unavailable in an interactive session. Stop and tell
|
|
483
|
+
the user: \`Codex has not exposed request_user_input in this session. Can I
|
|
484
|
+
update ~/.codex/config.toml with [features].default_mode_request_user_input =
|
|
485
|
+
true? After that, fully quit and reopen Codex, then rerun
|
|
486
|
+
$sellable:create-campaign.\` If they decline, tell them to switch to
|
|
487
|
+
Plan/collaboration mode and rerun \`$sellable:create-campaign\`. Plain chat
|
|
488
|
+
questions are only acceptable in non-interactive \`codex exec\`
|
|
489
|
+
smoke/rehearsal runs because structured user input is unavailable by design
|
|
490
|
+
there.
|
|
491
|
+
|
|
413
492
|
## Bootstrap
|
|
414
493
|
|
|
415
494
|
MCP tool access is required. First call \`mcp__sellable__get_auth_status({})\`
|
|
@@ -557,7 +636,12 @@ function installCodexDesktopPlugin(opts) {
|
|
|
557
636
|
const home = codexHome();
|
|
558
637
|
const configPath = join(home, "config.toml");
|
|
559
638
|
const marketplaceRoot = join(homedir(), ".sellable", "codex-marketplace");
|
|
560
|
-
const marketplacePath = join(
|
|
639
|
+
const marketplacePath = join(
|
|
640
|
+
marketplaceRoot,
|
|
641
|
+
".agents",
|
|
642
|
+
"plugins",
|
|
643
|
+
"marketplace.json"
|
|
644
|
+
);
|
|
561
645
|
const pluginRoot = join(marketplaceRoot, "plugins", "sellable");
|
|
562
646
|
const cacheRoot = join(home, "plugins", "cache", "sellable", "sellable");
|
|
563
647
|
const pluginCache = join(cacheRoot, CODEX_PLUGIN_VERSION);
|
|
@@ -592,7 +676,11 @@ function installCodexDesktopPlugin(opts) {
|
|
|
592
676
|
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
593
677
|
opts
|
|
594
678
|
);
|
|
595
|
-
writeFile(
|
|
679
|
+
writeFile(
|
|
680
|
+
join(pluginRoot, ".mcp.json"),
|
|
681
|
+
`${JSON.stringify(mcp, null, 2)}\n`,
|
|
682
|
+
opts
|
|
683
|
+
);
|
|
596
684
|
writeCodexPluginSkills(pluginRoot, opts);
|
|
597
685
|
|
|
598
686
|
if (!opts.dryRun) {
|
|
@@ -603,12 +691,18 @@ function installCodexDesktopPlugin(opts) {
|
|
|
603
691
|
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
604
692
|
opts
|
|
605
693
|
);
|
|
606
|
-
writeFile(
|
|
694
|
+
writeFile(
|
|
695
|
+
join(pluginCache, ".mcp.json"),
|
|
696
|
+
`${JSON.stringify(mcp, null, 2)}\n`,
|
|
697
|
+
opts
|
|
698
|
+
);
|
|
607
699
|
writeCodexPluginSkills(pluginCache, opts);
|
|
608
700
|
|
|
609
701
|
if (!opts.dryRun) {
|
|
610
702
|
mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
611
|
-
let content = existsSync(configPath)
|
|
703
|
+
let content = existsSync(configPath)
|
|
704
|
+
? readFileSync(configPath, "utf8")
|
|
705
|
+
: "";
|
|
612
706
|
content = upsertTomlTable(
|
|
613
707
|
content,
|
|
614
708
|
"marketplaces.sellable",
|
|
@@ -629,11 +723,22 @@ enabled = true`
|
|
|
629
723
|
`[plugins."sellable@sellable-local"]
|
|
630
724
|
enabled = false`
|
|
631
725
|
);
|
|
726
|
+
content = upsertTomlBoolean(
|
|
727
|
+
content,
|
|
728
|
+
"features",
|
|
729
|
+
"default_mode_request_user_input",
|
|
730
|
+
true
|
|
731
|
+
);
|
|
632
732
|
writeFileSync(configPath, `${content.trimEnd()}\n`, { mode: 0o600 });
|
|
633
733
|
} else {
|
|
634
734
|
console.log(`+ upsert [marketplaces.sellable] in ${configPath}`);
|
|
635
735
|
console.log(`+ enable [plugins."sellable@sellable"] in ${configPath}`);
|
|
636
|
-
console.log(
|
|
736
|
+
console.log(
|
|
737
|
+
`+ disable stale [plugins."sellable@sellable-local"] in ${configPath}`
|
|
738
|
+
);
|
|
739
|
+
console.log(
|
|
740
|
+
`+ enable [features].default_mode_request_user_input in ${configPath}`
|
|
741
|
+
);
|
|
637
742
|
}
|
|
638
743
|
|
|
639
744
|
console.log("Codex Desktop plugin installed:");
|
|
@@ -656,7 +761,9 @@ function writeAuth(opts) {
|
|
|
656
761
|
};
|
|
657
762
|
|
|
658
763
|
if (opts.authFromExistingConfig) {
|
|
659
|
-
console.log(
|
|
764
|
+
console.log(
|
|
765
|
+
`Leaving existing Sellable auth config unchanged: ${authPath()}`
|
|
766
|
+
);
|
|
660
767
|
return;
|
|
661
768
|
}
|
|
662
769
|
|
|
@@ -684,7 +791,8 @@ function mcpCommand(opts) {
|
|
|
684
791
|
|
|
685
792
|
function installClaude(opts) {
|
|
686
793
|
if (!commandExists("claude")) {
|
|
687
|
-
const message =
|
|
794
|
+
const message =
|
|
795
|
+
"Claude CLI not found. Install/login to Claude Code, then rerun: sellable --host claude";
|
|
688
796
|
if (opts.host === "all") {
|
|
689
797
|
console.log(`Skipping Claude Code: ${message}`);
|
|
690
798
|
return false;
|
|
@@ -692,7 +800,11 @@ function installClaude(opts) {
|
|
|
692
800
|
throw new Error(message);
|
|
693
801
|
}
|
|
694
802
|
if (opts.server === "hosted") {
|
|
695
|
-
run(
|
|
803
|
+
run(
|
|
804
|
+
"claude",
|
|
805
|
+
["mcp", "add", "--transport", "http", "sellable", opts.hostedUrl],
|
|
806
|
+
opts
|
|
807
|
+
);
|
|
696
808
|
return true;
|
|
697
809
|
}
|
|
698
810
|
const [command, args] = mcpCommand(opts);
|
|
@@ -701,13 +813,18 @@ function installClaude(opts) {
|
|
|
701
813
|
dryRun: opts.dryRun,
|
|
702
814
|
allowFail: true,
|
|
703
815
|
});
|
|
704
|
-
run(
|
|
816
|
+
run(
|
|
817
|
+
"claude",
|
|
818
|
+
["mcp", "add", "--transport", "stdio", "sellable", "--", command, ...args],
|
|
819
|
+
opts
|
|
820
|
+
);
|
|
705
821
|
return true;
|
|
706
822
|
}
|
|
707
823
|
|
|
708
824
|
function installCodex(opts) {
|
|
709
825
|
if (!commandExists("codex")) {
|
|
710
|
-
const message =
|
|
826
|
+
const message =
|
|
827
|
+
"Codex CLI not found. Install/login to Codex, then rerun: sellable --host codex";
|
|
711
828
|
if (opts.host === "all") {
|
|
712
829
|
console.log(`Skipping Codex: ${message}`);
|
|
713
830
|
return false;
|
|
@@ -733,14 +850,20 @@ function installCodex(opts) {
|
|
|
733
850
|
function verify(opts) {
|
|
734
851
|
const stored = readStoredAuth();
|
|
735
852
|
if (!stored?.token || !stored?.workspaceId) {
|
|
736
|
-
throw new Error(
|
|
853
|
+
throw new Error(
|
|
854
|
+
`Sellable auth config missing or incomplete: ${authPath()}`
|
|
855
|
+
);
|
|
737
856
|
}
|
|
738
857
|
console.log(`Sellable auth config present: ${authPath()}`);
|
|
739
858
|
if (opts.host === "claude" || opts.host === "all") {
|
|
740
|
-
console.log(
|
|
859
|
+
console.log(
|
|
860
|
+
commandExists("claude") ? "Claude CLI present" : "Claude CLI missing"
|
|
861
|
+
);
|
|
741
862
|
}
|
|
742
863
|
if (opts.host === "codex" || opts.host === "all") {
|
|
743
|
-
console.log(
|
|
864
|
+
console.log(
|
|
865
|
+
commandExists("codex") ? "Codex CLI present" : "Codex CLI missing"
|
|
866
|
+
);
|
|
744
867
|
const pluginPath = join(
|
|
745
868
|
codexHome(),
|
|
746
869
|
"plugins",
|
|
@@ -762,8 +885,25 @@ function verify(opts) {
|
|
|
762
885
|
"sellable-create-campaign",
|
|
763
886
|
"SKILL.md"
|
|
764
887
|
);
|
|
765
|
-
console.log(
|
|
766
|
-
|
|
888
|
+
console.log(
|
|
889
|
+
existsSync(pluginPath)
|
|
890
|
+
? "Codex Desktop plugin present"
|
|
891
|
+
: "Codex Desktop plugin missing"
|
|
892
|
+
);
|
|
893
|
+
console.log(
|
|
894
|
+
existsSync(skillPath)
|
|
895
|
+
? "Codex skill bundle present"
|
|
896
|
+
: "Codex skill bundle missing"
|
|
897
|
+
);
|
|
898
|
+
const configPath = join(codexHome(), "config.toml");
|
|
899
|
+
const configContent = existsSync(configPath)
|
|
900
|
+
? readFileSync(configPath, "utf8")
|
|
901
|
+
: "";
|
|
902
|
+
console.log(
|
|
903
|
+
configContent.includes("default_mode_request_user_input = true")
|
|
904
|
+
? "Codex Default-mode request_user_input enabled"
|
|
905
|
+
: "Codex Default-mode request_user_input missing"
|
|
906
|
+
);
|
|
767
907
|
}
|
|
768
908
|
}
|
|
769
909
|
|
|
@@ -771,11 +911,17 @@ function printNextSteps(installedHosts) {
|
|
|
771
911
|
console.log("");
|
|
772
912
|
console.log("Next steps:");
|
|
773
913
|
if (installedHosts.length > 0) {
|
|
774
|
-
console.log(
|
|
914
|
+
console.log(
|
|
915
|
+
`1. Fully quit and reopen ${installedHosts.join(" and ")} so MCP tools reload.`
|
|
916
|
+
);
|
|
775
917
|
console.log("2. Start a new thread and choose Sellable Create Campaign.");
|
|
776
|
-
console.log(
|
|
918
|
+
console.log(
|
|
919
|
+
"3. If tools do not appear, run: sellable --verify-only --host all"
|
|
920
|
+
);
|
|
777
921
|
} else {
|
|
778
|
-
console.log(
|
|
922
|
+
console.log(
|
|
923
|
+
"1. Install Claude Code or Codex, then rerun this installer for that host."
|
|
924
|
+
);
|
|
779
925
|
console.log("2. Verify auth later with: sellable --verify-only --host all");
|
|
780
926
|
}
|
|
781
927
|
}
|
|
@@ -806,7 +952,9 @@ async function main() {
|
|
|
806
952
|
}
|
|
807
953
|
}
|
|
808
954
|
if (opts.dryRun) {
|
|
809
|
-
console.log(
|
|
955
|
+
console.log(
|
|
956
|
+
"Dry run complete; verification skipped because no files were written."
|
|
957
|
+
);
|
|
810
958
|
} else {
|
|
811
959
|
verify(opts);
|
|
812
960
|
}
|