@sellable/install 0.1.0
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 +21 -0
- package/bin/sellable-install.mjs +264 -0
- package/package.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Sellable Installer
|
|
2
|
+
|
|
3
|
+
Installs Sellable MCP for Claude Code and Codex.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx -y @sellable/install --host all
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The installer uses package stdio MCP by default:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx -y @sellable/mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Get a Sellable API token from:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
https://app.sellable.dev/settings
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then pass it with `--token` or `SELLABLE_TOKEN`.
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_API_URL = "https://app.sellable.dev";
|
|
8
|
+
const DEFAULT_SERVER_PACKAGE =
|
|
9
|
+
process.env.SELLABLE_MCP_PACKAGE || "@sellable/mcp";
|
|
10
|
+
|
|
11
|
+
function usage() {
|
|
12
|
+
return `Sellable agent installer
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
sellable-install [options]
|
|
16
|
+
npx -y @sellable/install -- [options]
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--host <host> claude, codex, or all. Default: all
|
|
20
|
+
--server <mode> package, local, or hosted. Default: package
|
|
21
|
+
--token <token> Sellable API token. Also reads SELLABLE_TOKEN.
|
|
22
|
+
--workspace-id <id> Sellable workspace id. Also reads SELLABLE_WORKSPACE_ID.
|
|
23
|
+
--api-url <url> Sellable API URL. Default: ${DEFAULT_API_URL}
|
|
24
|
+
--mcp-package <pkg> MCP server package. Default: ${DEFAULT_SERVER_PACKAGE}
|
|
25
|
+
--local-command <cmd> Local MCP command for --server local.
|
|
26
|
+
--hosted-url <url> Hosted MCP URL for --server hosted.
|
|
27
|
+
--dry-run Print actions without writing or running host commands.
|
|
28
|
+
--verify-only Verify installed host config where possible.
|
|
29
|
+
--help Show help.
|
|
30
|
+
|
|
31
|
+
Auth:
|
|
32
|
+
Sign up or log in at ${DEFAULT_API_URL}/settings, create an API token, then
|
|
33
|
+
pass it with --token or SELLABLE_TOKEN.
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseArgs(argv) {
|
|
38
|
+
const opts = {
|
|
39
|
+
host: process.env.SELLABLE_INSTALL_HOST || "all",
|
|
40
|
+
server: process.env.SELLABLE_INSTALL_SERVER || "package",
|
|
41
|
+
token: process.env.SELLABLE_TOKEN || "",
|
|
42
|
+
workspaceId: process.env.SELLABLE_WORKSPACE_ID || "",
|
|
43
|
+
apiUrl: process.env.SELLABLE_API_URL || DEFAULT_API_URL,
|
|
44
|
+
mcpPackage: DEFAULT_SERVER_PACKAGE,
|
|
45
|
+
localCommand: process.env.SELLABLE_MCP_LOCAL_COMMAND || "",
|
|
46
|
+
hostedUrl: process.env.SELLABLE_MCP_HOSTED_URL || "",
|
|
47
|
+
dryRun: false,
|
|
48
|
+
verifyOnly: false,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
52
|
+
const arg = argv[i];
|
|
53
|
+
const next = () => {
|
|
54
|
+
const value = argv[i + 1];
|
|
55
|
+
if (!value || value.startsWith("--")) {
|
|
56
|
+
throw new Error(`Missing value for ${arg}`);
|
|
57
|
+
}
|
|
58
|
+
i += 1;
|
|
59
|
+
return value;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (arg === "--help" || arg === "-h") {
|
|
63
|
+
opts.help = true;
|
|
64
|
+
} else if (arg === "--host") {
|
|
65
|
+
opts.host = next();
|
|
66
|
+
} else if (arg === "--server") {
|
|
67
|
+
opts.server = next();
|
|
68
|
+
} else if (arg === "--token") {
|
|
69
|
+
opts.token = next();
|
|
70
|
+
} else if (arg === "--workspace-id") {
|
|
71
|
+
opts.workspaceId = next();
|
|
72
|
+
} else if (arg === "--api-url") {
|
|
73
|
+
opts.apiUrl = next();
|
|
74
|
+
} else if (arg === "--mcp-package") {
|
|
75
|
+
opts.mcpPackage = next();
|
|
76
|
+
} else if (arg === "--local-command") {
|
|
77
|
+
opts.localCommand = next();
|
|
78
|
+
} else if (arg === "--hosted-url") {
|
|
79
|
+
opts.hostedUrl = next();
|
|
80
|
+
} else if (arg === "--dry-run") {
|
|
81
|
+
opts.dryRun = true;
|
|
82
|
+
} else if (arg === "--verify-only") {
|
|
83
|
+
opts.verifyOnly = true;
|
|
84
|
+
} else {
|
|
85
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!["claude", "codex", "all"].includes(opts.host)) {
|
|
90
|
+
throw new Error("--host must be claude, codex, or all");
|
|
91
|
+
}
|
|
92
|
+
if (!["package", "local", "hosted"].includes(opts.server)) {
|
|
93
|
+
throw new Error("--server must be package, local, or hosted");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return opts;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function redact(value) {
|
|
100
|
+
if (!value) return "";
|
|
101
|
+
if (value.length <= 10) return "[redacted]";
|
|
102
|
+
return `${value.slice(0, 6)}...[redacted]...${value.slice(-4)}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function run(command, args, opts = {}) {
|
|
106
|
+
const rendered = [command, ...args].join(" ");
|
|
107
|
+
console.log(`+ ${rendered.replace(opts.token || "__NO_TOKEN__", "[redacted-token]")}`);
|
|
108
|
+
if (opts.dryRun) return { status: 0, stdout: "", stderr: "" };
|
|
109
|
+
const result = spawnSync(command, args, {
|
|
110
|
+
encoding: "utf8",
|
|
111
|
+
stdio: "pipe",
|
|
112
|
+
});
|
|
113
|
+
if (result.status !== 0 && !opts.allowFail) {
|
|
114
|
+
const stderr = (result.stderr || "").trim();
|
|
115
|
+
throw new Error(`${command} failed${stderr ? `: ${stderr}` : ""}`);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function commandExists(command) {
|
|
121
|
+
const result = spawnSync("sh", ["-lc", `command -v ${command}`], {
|
|
122
|
+
encoding: "utf8",
|
|
123
|
+
});
|
|
124
|
+
return result.status === 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function writeJson(path, data, opts) {
|
|
128
|
+
const redacted = JSON.stringify({ ...data, token: redact(data.token) }, null, 2);
|
|
129
|
+
console.log(`Writing ${path}: ${redacted}`);
|
|
130
|
+
if (opts.dryRun) return;
|
|
131
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
132
|
+
writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`, { mode: 0o600 });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function readExisting(path) {
|
|
136
|
+
if (!existsSync(path)) return null;
|
|
137
|
+
try {
|
|
138
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function writeAuth(opts) {
|
|
145
|
+
if (!opts.token || !opts.workspaceId) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Missing Sellable token/workspace id. Create a token at ${opts.apiUrl}/settings, then rerun with --token and --workspace-id or SELLABLE_TOKEN and SELLABLE_WORKSPACE_ID.`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const config = {
|
|
152
|
+
token: opts.token,
|
|
153
|
+
activeWorkspaceId: opts.workspaceId,
|
|
154
|
+
apiUrl: opts.apiUrl,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const sellablePath = join(homedir(), ".sellable", "config.json");
|
|
158
|
+
const claudePath = join(homedir(), ".claude", "sellable.json");
|
|
159
|
+
const codexPath = join(homedir(), ".codex", "sellable.json");
|
|
160
|
+
|
|
161
|
+
writeJson(sellablePath, config, opts);
|
|
162
|
+
if (opts.host === "claude" || opts.host === "all") writeJson(claudePath, config, opts);
|
|
163
|
+
if (opts.host === "codex" || opts.host === "all") writeJson(codexPath, config, opts);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function mcpCommand(opts) {
|
|
167
|
+
if (opts.server === "package") {
|
|
168
|
+
return ["npx", ["-y", opts.mcpPackage]];
|
|
169
|
+
}
|
|
170
|
+
if (opts.server === "local") {
|
|
171
|
+
if (!opts.localCommand) {
|
|
172
|
+
throw new Error("--server local requires --local-command");
|
|
173
|
+
}
|
|
174
|
+
return ["sh", ["-lc", opts.localCommand]];
|
|
175
|
+
}
|
|
176
|
+
if (!opts.hostedUrl) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
"--server hosted requires --hosted-url. Hosted MCP is not the default until Sellable ships a hosted MCP endpoint."
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return ["hosted", [opts.hostedUrl]];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function installClaude(opts) {
|
|
185
|
+
if (!commandExists("claude")) {
|
|
186
|
+
throw new Error("Missing Claude CLI. Install/login to Claude Code, then rerun this installer.");
|
|
187
|
+
}
|
|
188
|
+
if (opts.server === "hosted") {
|
|
189
|
+
run("claude", ["mcp", "add", "--transport", "http", "sellable", opts.hostedUrl], opts);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const [command, args] = mcpCommand(opts);
|
|
193
|
+
run("claude", ["mcp", "remove", "sellable"], {
|
|
194
|
+
...opts,
|
|
195
|
+
dryRun: opts.dryRun,
|
|
196
|
+
allowFail: true,
|
|
197
|
+
});
|
|
198
|
+
run("claude", ["mcp", "add", "--transport", "stdio", "sellable", "--", command, ...args], opts);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function installCodex(opts) {
|
|
202
|
+
if (!commandExists("codex")) {
|
|
203
|
+
throw new Error("Missing Codex CLI. Install/login to Codex, then rerun this installer.");
|
|
204
|
+
}
|
|
205
|
+
if (opts.server === "hosted") {
|
|
206
|
+
run("codex", ["mcp", "add", "sellable", "--url", opts.hostedUrl], opts);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const [command, args] = mcpCommand(opts);
|
|
210
|
+
run("codex", ["mcp", "remove", "sellable"], {
|
|
211
|
+
...opts,
|
|
212
|
+
dryRun: opts.dryRun,
|
|
213
|
+
allowFail: true,
|
|
214
|
+
});
|
|
215
|
+
run("codex", ["mcp", "add", "sellable", "--", command, ...args], opts);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function verify(opts) {
|
|
219
|
+
const authPath = join(homedir(), ".sellable", "config.json");
|
|
220
|
+
const auth = readExisting(authPath);
|
|
221
|
+
if (!auth?.token || !auth?.activeWorkspaceId) {
|
|
222
|
+
throw new Error(`Sellable auth config missing or incomplete: ${authPath}`);
|
|
223
|
+
}
|
|
224
|
+
console.log(`Sellable auth config present: ${authPath}`);
|
|
225
|
+
if (opts.host === "claude" || opts.host === "all") {
|
|
226
|
+
console.log(commandExists("claude") ? "Claude CLI present" : "Claude CLI missing");
|
|
227
|
+
}
|
|
228
|
+
if (opts.host === "codex" || opts.host === "all") {
|
|
229
|
+
console.log(commandExists("codex") ? "Codex CLI present" : "Codex CLI missing");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function main() {
|
|
234
|
+
try {
|
|
235
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
236
|
+
if (opts.help) {
|
|
237
|
+
console.log(usage());
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
console.log("Sellable installer");
|
|
242
|
+
console.log(`- host: ${opts.host}`);
|
|
243
|
+
console.log(`- server: ${opts.server}`);
|
|
244
|
+
console.log(`- api: ${opts.apiUrl}`);
|
|
245
|
+
console.log(`- token: ${opts.token ? redact(opts.token) : "(missing)"}`);
|
|
246
|
+
|
|
247
|
+
if (!opts.verifyOnly) {
|
|
248
|
+
writeAuth(opts);
|
|
249
|
+
if (opts.host === "claude" || opts.host === "all") installClaude(opts);
|
|
250
|
+
if (opts.host === "codex" || opts.host === "all") installCodex(opts);
|
|
251
|
+
}
|
|
252
|
+
if (opts.dryRun) {
|
|
253
|
+
console.log("Dry run complete; verification skipped because no files were written.");
|
|
254
|
+
} else {
|
|
255
|
+
verify(opts);
|
|
256
|
+
}
|
|
257
|
+
console.log("Sellable install complete.");
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
260
|
+
process.exitCode = 1;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sellable/install",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "One-command installer for Sellable MCP in Claude Code and Codex",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sellable": "bin/sellable-install.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"sellable",
|
|
15
|
+
"mcp",
|
|
16
|
+
"claude-code",
|
|
17
|
+
"codex",
|
|
18
|
+
"linkedin",
|
|
19
|
+
"outbound"
|
|
20
|
+
],
|
|
21
|
+
"author": "Sellable",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
}
|
|
26
|
+
}
|