@sellable/install
Version:
One-command installer for Sellable MCP in Claude Code, Codex, and Hermes
239 lines (229 loc) • 6.79 kB
JavaScript
const SAFE_ID = /^[A-Za-z0-9_.:-]{1,256}$/;
const SHA256 = /^[a-f0-9]{64}$/;
const RUNTIME_BINDING_V1_KEYS = Object.freeze([
"schemaVersion",
"workspaceId",
"agentId",
"runtimeId",
"profileId",
"revisionId",
"installationId",
"teamId",
"slackGeneration",
"routeGeneration",
"membershipGeneration",
"serviceCredentialGeneration",
"modelCredentialGeneration",
"runtimeGeneration",
"selectedChannelIds",
"memberAllowlist",
]);
export const ADMIN_RUNTIME_BINDING_V3_KEYS = Object.freeze([
"schemaVersion",
"runtimeMode",
"workspaceId",
"agentId",
"runtimeId",
"profileId",
"serviceCredentialGeneration",
"runtimeGeneration",
]);
export const RUNTIME_BINDING_V2_KEYS = Object.freeze([
"schemaVersion",
"runtimeMode",
"workspaceId",
"agentId",
"runtimeId",
"profileId",
"revisionId",
"installationId",
"teamId",
"botUserId",
"slackAppGeneration",
"slackGeneration",
"routeGeneration",
"membershipGeneration",
"serviceCredentialGeneration",
"modelCredentialGeneration",
"modelActivationId",
"modelBundleGeneration",
"modelPolicyHash",
"apiServerKeyGeneration",
"apiServerKeyFingerprint",
"runtimeGeneration",
"selectedChannelIds",
"memberAllowlist",
"accessPolicyHash",
"runtimeDesiredHash",
]);
export const FIXED_TURN_BINDING_KEYS = Object.freeze([
"workspaceId",
"agentId",
"runtimeId",
"profileId",
"revisionId",
"installationId",
"teamId",
"slackGeneration",
"routeGeneration",
"membershipGeneration",
"serviceCredentialGeneration",
"modelCredentialGeneration",
"runtimeGeneration",
]);
function exactKeys(value, keys) {
return Boolean(
value &&
typeof value === "object" &&
!Array.isArray(value) &&
Object.keys(value).sort().join("\0") === [...keys].sort().join("\0")
);
}
function validScope(values, { allowEmpty = false } = {}) {
return Boolean(
Array.isArray(values) &&
(allowEmpty || values.length >= 1) &&
values.length <= 256 &&
values.every(
(value) => typeof value === "string" && SAFE_ID.test(value)
) &&
new Set(values).size === values.length
);
}
export function validateMcpRuntimeBinding(binding) {
if (
exactKeys(binding, ADMIN_RUNTIME_BINDING_V3_KEYS) &&
binding.schemaVersion === "sellable-agent-mcp-runtime-binding/v3"
) {
for (const key of [
"workspaceId",
"agentId",
"runtimeId",
"profileId",
]) {
if (typeof binding[key] !== "string" || !SAFE_ID.test(binding[key])) {
return false;
}
}
return Boolean(
binding.runtimeMode === "native_admin" &&
Number.isSafeInteger(binding.serviceCredentialGeneration) &&
binding.serviceCredentialGeneration > 0 &&
Number.isSafeInteger(binding.runtimeGeneration) &&
binding.runtimeGeneration > 0
);
}
if (
exactKeys(binding, RUNTIME_BINDING_V2_KEYS) &&
binding.schemaVersion === "sellable-agent-mcp-runtime-binding/v2"
) {
for (const key of [
"workspaceId",
"agentId",
"runtimeId",
"profileId",
"revisionId",
"installationId",
"teamId",
"botUserId",
]) {
if (typeof binding[key] !== "string" || !SAFE_ID.test(binding[key])) {
return false;
}
}
for (const key of [
"slackAppGeneration",
"slackGeneration",
"routeGeneration",
"membershipGeneration",
"serviceCredentialGeneration",
"modelCredentialGeneration",
"modelBundleGeneration",
"apiServerKeyGeneration",
"runtimeGeneration",
]) {
if (!Number.isSafeInteger(binding[key]) || binding[key] < 1) return false;
}
return Boolean(
binding.runtimeMode === "native_slack_customer" &&
typeof binding.modelActivationId === "string" &&
SAFE_ID.test(binding.modelActivationId) &&
SHA256.test(binding.modelPolicyHash ?? "") &&
SHA256.test(binding.accessPolicyHash ?? "") &&
SHA256.test(binding.runtimeDesiredHash ?? "") &&
typeof binding.apiServerKeyFingerprint === "string" &&
validScope(binding.selectedChannelIds) &&
validScope(binding.memberAllowlist, { allowEmpty: true })
);
}
if (
!exactKeys(binding, RUNTIME_BINDING_V1_KEYS) ||
binding.schemaVersion !== "sellable-agent-mcp-runtime-binding/v1"
) {
return false;
}
for (const key of FIXED_TURN_BINDING_KEYS) {
if (key.endsWith("Generation")) {
if (!Number.isSafeInteger(binding[key]) || binding[key] < 1) return false;
} else if (
typeof binding[key] !== "string" ||
!SAFE_ID.test(binding[key])
) {
return false;
}
}
return (
validScope(binding.selectedChannelIds) &&
validScope(binding.memberAllowlist)
);
}
export function createMcpAdminRuntimeBindingV3(input) {
const binding = {
schemaVersion: "sellable-agent-mcp-runtime-binding/v3",
runtimeMode: "native_admin",
workspaceId: input.workspaceId,
agentId: input.agentId,
runtimeId: input.runtimeId,
profileId: input.profileId,
serviceCredentialGeneration: input.serviceCredentialGeneration,
runtimeGeneration: input.runtimeGeneration,
};
if (!validateMcpRuntimeBinding(binding)) {
throw new Error("mcp_admin_runtime_binding_v3_invalid");
}
return binding;
}
export function createMcpRuntimeBindingV2(desired) {
const binding = {
schemaVersion: "sellable-agent-mcp-runtime-binding/v2",
runtimeMode: desired.runtimeMode,
workspaceId: desired.workspaceId,
agentId: desired.agentId,
runtimeId: desired.runtimeId,
profileId: desired.profileId,
revisionId: desired.revisionId,
installationId: desired.installationId,
teamId: desired.teamId,
botUserId: desired.botUserId,
slackAppGeneration: desired.slackAppGeneration,
slackGeneration: desired.slackGeneration,
routeGeneration: desired.routeGeneration,
membershipGeneration: desired.membershipGeneration,
serviceCredentialGeneration: desired.serviceCredentialGeneration,
modelCredentialGeneration: desired.modelCredentialGeneration,
modelActivationId: desired.modelActivationId,
modelBundleGeneration: desired.modelBundleGeneration,
modelPolicyHash: desired.modelPolicyHash,
apiServerKeyGeneration: desired.apiServerKeyGeneration,
apiServerKeyFingerprint: desired.apiServerKeyFingerprint,
runtimeGeneration: desired.runtimeGeneration,
selectedChannelIds: [...desired.selectedChannelIds],
memberAllowlist: [...desired.memberAllowlist],
accessPolicyHash: desired.accessPolicyHash,
runtimeDesiredHash: desired.runtimeDesiredHash,
};
if (!validateMcpRuntimeBinding(binding)) {
throw new Error("mcp_runtime_binding_v2_invalid");
}
return binding;
}