@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 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`.
@@ -1,15 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
- import { dirname, join } from "node:path";
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.6";
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(`+ ${rendered.replace(opts.token || "__NO_TOKEN__", "[redacted-token]")}`);
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("Sellable needs one API token to connect Claude/Codex to your workspace.");
190
- console.log(`Open ${opts.apiUrl}/settings, create a token, then paste the values below.`);
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({ ...data, token: redact(data.token) }, null, 2);
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(tablePattern, (_, prefix) => `${prefix}${normalizedBlock}`);
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(marketplaceRoot, ".agents", "plugins", "marketplace.json");
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(join(pluginRoot, ".mcp.json"), `${JSON.stringify(mcp, null, 2)}\n`, opts);
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(join(pluginCache, ".mcp.json"), `${JSON.stringify(mcp, null, 2)}\n`, opts);
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) ? readFileSync(configPath, "utf8") : "";
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(`+ disable stale [plugins."sellable@sellable-local"] in ${configPath}`);
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(`Leaving existing Sellable auth config unchanged: ${authPath()}`);
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 = "Claude CLI not found. Install/login to Claude Code, then rerun: sellable --host claude";
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("claude", ["mcp", "add", "--transport", "http", "sellable", opts.hostedUrl], opts);
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("claude", ["mcp", "add", "--transport", "stdio", "sellable", "--", command, ...args], opts);
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 = "Codex CLI not found. Install/login to Codex, then rerun: sellable --host codex";
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(`Sellable auth config missing or incomplete: ${authPath()}`);
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(commandExists("claude") ? "Claude CLI present" : "Claude CLI missing");
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(commandExists("codex") ? "Codex CLI present" : "Codex CLI missing");
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(existsSync(pluginPath) ? "Codex Desktop plugin present" : "Codex Desktop plugin missing");
766
- console.log(existsSync(skillPath) ? "Codex skill bundle present" : "Codex skill bundle missing");
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(`1. Fully quit and reopen ${installedHosts.join(" and ")} so MCP tools reload.`);
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("3. If tools do not appear, run: sellable --verify-only --host all");
918
+ console.log(
919
+ "3. If tools do not appear, run: sellable --verify-only --host all"
920
+ );
777
921
  } else {
778
- console.log("1. Install Claude Code or Codex, then rerun this installer for that host.");
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("Dry run complete; verification skipped because no files were written.");
955
+ console.log(
956
+ "Dry run complete; verification skipped because no files were written."
957
+ );
810
958
  } else {
811
959
  verify(opts);
812
960
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code and Codex",
6
6
  "bin": {