mirror of
https://github.com/tvytlx/ai-agent-deep-dive.git
synced 2026-04-05 00:24:50 +08:00
Add extracted source directory and README navigation
This commit is contained in:
705
extracted-source/node_modules/@anthropic-ai/mcpb/dist/cli/init.js
generated
vendored
Normal file
705
extracted-source/node_modules/@anthropic-ai/mcpb/dist/cli/init.js
generated
vendored
Normal file
@@ -0,0 +1,705 @@
|
||||
import { confirm, input, select } from "@inquirer/prompts";
|
||||
import { existsSync, readFileSync, writeFileSync } from "fs";
|
||||
import { basename, join, resolve } from "path";
|
||||
import { CURRENT_MANIFEST_VERSION } from "../schemas.js";
|
||||
export function readPackageJson(dirPath) {
|
||||
const packageJsonPath = join(dirPath, "package.json");
|
||||
if (existsSync(packageJsonPath)) {
|
||||
try {
|
||||
return JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
||||
}
|
||||
catch (e) {
|
||||
// Ignore package.json parsing errors
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
export function getDefaultAuthorName(packageData) {
|
||||
if (typeof packageData.author === "string") {
|
||||
return packageData.author;
|
||||
}
|
||||
return packageData.author?.name || "";
|
||||
}
|
||||
export function getDefaultAuthorEmail(packageData) {
|
||||
if (typeof packageData.author === "object") {
|
||||
return packageData.author?.email || "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
export function getDefaultAuthorUrl(packageData) {
|
||||
if (typeof packageData.author === "object") {
|
||||
return packageData.author?.url || "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
export function getDefaultRepositoryUrl(packageData) {
|
||||
if (typeof packageData.repository === "string") {
|
||||
return packageData.repository;
|
||||
}
|
||||
return packageData.repository?.url || "";
|
||||
}
|
||||
export function getDefaultBasicInfo(packageData, resolvedPath) {
|
||||
const name = packageData.name || basename(resolvedPath);
|
||||
const authorName = getDefaultAuthorName(packageData) || "Unknown Author";
|
||||
const displayName = name;
|
||||
const version = packageData.version || "1.0.0";
|
||||
const description = packageData.description || "A MCPB bundle";
|
||||
return { name, authorName, displayName, version, description };
|
||||
}
|
||||
export function getDefaultAuthorInfo(packageData) {
|
||||
return {
|
||||
authorEmail: getDefaultAuthorEmail(packageData),
|
||||
authorUrl: getDefaultAuthorUrl(packageData),
|
||||
};
|
||||
}
|
||||
export function getDefaultServerConfig(packageData) {
|
||||
const serverType = "node";
|
||||
const entryPoint = getDefaultEntryPoint(serverType, packageData);
|
||||
const mcp_config = createMcpConfig(serverType, entryPoint);
|
||||
return { serverType, entryPoint, mcp_config };
|
||||
}
|
||||
export function getDefaultOptionalFields(packageData) {
|
||||
return {
|
||||
keywords: "",
|
||||
license: packageData.license || "MIT",
|
||||
repository: undefined,
|
||||
};
|
||||
}
|
||||
export function createMcpConfig(serverType, entryPoint) {
|
||||
switch (serverType) {
|
||||
case "node":
|
||||
return {
|
||||
command: "node",
|
||||
args: ["${__dirname}/" + entryPoint],
|
||||
env: {},
|
||||
};
|
||||
case "python":
|
||||
return {
|
||||
command: "python",
|
||||
args: ["${__dirname}/" + entryPoint],
|
||||
env: {
|
||||
PYTHONPATH: "${__dirname}/server/lib",
|
||||
},
|
||||
};
|
||||
case "binary":
|
||||
return {
|
||||
command: "${__dirname}/" + entryPoint,
|
||||
args: [],
|
||||
env: {},
|
||||
};
|
||||
}
|
||||
}
|
||||
export function getDefaultEntryPoint(serverType, packageData) {
|
||||
switch (serverType) {
|
||||
case "node":
|
||||
return packageData?.main || "server/index.js";
|
||||
case "python":
|
||||
return "server/main.py";
|
||||
case "binary":
|
||||
return "server/my-server";
|
||||
}
|
||||
}
|
||||
export async function promptBasicInfo(packageData, resolvedPath) {
|
||||
const defaultName = packageData.name || basename(resolvedPath);
|
||||
const name = await input({
|
||||
message: "Extension name:",
|
||||
default: defaultName,
|
||||
validate: (value) => value.trim().length > 0 || "Name is required",
|
||||
});
|
||||
const authorName = await input({
|
||||
message: "Author name:",
|
||||
default: getDefaultAuthorName(packageData),
|
||||
validate: (value) => value.trim().length > 0 || "Author name is required",
|
||||
});
|
||||
const displayName = await input({
|
||||
message: "Display name (optional):",
|
||||
default: name,
|
||||
});
|
||||
const version = await input({
|
||||
message: "Version:",
|
||||
default: packageData.version || "1.0.0",
|
||||
validate: (value) => {
|
||||
if (!value.trim())
|
||||
return "Version is required";
|
||||
if (!/^\d+\.\d+\.\d+/.test(value)) {
|
||||
return "Version must follow semantic versioning (e.g., 1.0.0)";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
});
|
||||
const description = await input({
|
||||
message: "Description:",
|
||||
default: packageData.description || "",
|
||||
validate: (value) => value.trim().length > 0 || "Description is required",
|
||||
});
|
||||
return { name, authorName, displayName, version, description };
|
||||
}
|
||||
export async function promptAuthorInfo(packageData) {
|
||||
const authorEmail = await input({
|
||||
message: "Author email (optional):",
|
||||
default: getDefaultAuthorEmail(packageData),
|
||||
});
|
||||
const authorUrl = await input({
|
||||
message: "Author URL (optional):",
|
||||
default: getDefaultAuthorUrl(packageData),
|
||||
});
|
||||
return { authorEmail, authorUrl };
|
||||
}
|
||||
export async function promptServerConfig(packageData) {
|
||||
const serverType = (await select({
|
||||
message: "Server type:",
|
||||
choices: [
|
||||
{ name: "Node.js", value: "node" },
|
||||
{ name: "Python", value: "python" },
|
||||
{ name: "Binary", value: "binary" },
|
||||
],
|
||||
default: "node",
|
||||
}));
|
||||
const entryPoint = await input({
|
||||
message: "Entry point:",
|
||||
default: getDefaultEntryPoint(serverType, packageData),
|
||||
});
|
||||
const mcp_config = createMcpConfig(serverType, entryPoint);
|
||||
return { serverType, entryPoint, mcp_config };
|
||||
}
|
||||
export async function promptTools() {
|
||||
const addTools = await confirm({
|
||||
message: "Does your MCP Server provide tools you want to advertise (optional)?",
|
||||
default: true,
|
||||
});
|
||||
const tools = [];
|
||||
let toolsGenerated = false;
|
||||
if (addTools) {
|
||||
let addMore = true;
|
||||
while (addMore) {
|
||||
const toolName = await input({
|
||||
message: "Tool name:",
|
||||
validate: (value) => value.trim().length > 0 || "Tool name is required",
|
||||
});
|
||||
const toolDescription = await input({
|
||||
message: "Tool description (optional):",
|
||||
});
|
||||
tools.push({
|
||||
name: toolName,
|
||||
...(toolDescription ? { description: toolDescription } : {}),
|
||||
});
|
||||
addMore = await confirm({
|
||||
message: "Add another tool?",
|
||||
default: false,
|
||||
});
|
||||
}
|
||||
// Ask about generated tools
|
||||
toolsGenerated = await confirm({
|
||||
message: "Does your server generate additional tools at runtime?",
|
||||
default: false,
|
||||
});
|
||||
}
|
||||
return { tools, toolsGenerated };
|
||||
}
|
||||
export async function promptPrompts() {
|
||||
const addPrompts = await confirm({
|
||||
message: "Does your MCP Server provide prompts you want to advertise (optional)?",
|
||||
default: false,
|
||||
});
|
||||
const prompts = [];
|
||||
let promptsGenerated = false;
|
||||
if (addPrompts) {
|
||||
let addMore = true;
|
||||
while (addMore) {
|
||||
const promptName = await input({
|
||||
message: "Prompt name:",
|
||||
validate: (value) => value.trim().length > 0 || "Prompt name is required",
|
||||
});
|
||||
const promptDescription = await input({
|
||||
message: "Prompt description (optional):",
|
||||
});
|
||||
// Ask about arguments
|
||||
const hasArguments = await confirm({
|
||||
message: "Does this prompt have arguments?",
|
||||
default: false,
|
||||
});
|
||||
const argumentNames = [];
|
||||
if (hasArguments) {
|
||||
let addMoreArgs = true;
|
||||
while (addMoreArgs) {
|
||||
const argName = await input({
|
||||
message: "Argument name:",
|
||||
validate: (value) => {
|
||||
if (!value.trim())
|
||||
return "Argument name is required";
|
||||
if (argumentNames.includes(value)) {
|
||||
return "Argument names must be unique";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
});
|
||||
argumentNames.push(argName);
|
||||
addMoreArgs = await confirm({
|
||||
message: "Add another argument?",
|
||||
default: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Prompt for the text template
|
||||
const promptText = await input({
|
||||
message: hasArguments
|
||||
? `Prompt text (use \${arguments.name} for arguments: ${argumentNames.join(", ")}):`
|
||||
: "Prompt text:",
|
||||
validate: (value) => value.trim().length > 0 || "Prompt text is required",
|
||||
});
|
||||
prompts.push({
|
||||
name: promptName,
|
||||
...(promptDescription ? { description: promptDescription } : {}),
|
||||
...(argumentNames.length > 0 ? { arguments: argumentNames } : {}),
|
||||
text: promptText,
|
||||
});
|
||||
addMore = await confirm({
|
||||
message: "Add another prompt?",
|
||||
default: false,
|
||||
});
|
||||
}
|
||||
// Ask about generated prompts
|
||||
promptsGenerated = await confirm({
|
||||
message: "Does your server generate additional prompts at runtime?",
|
||||
default: false,
|
||||
});
|
||||
}
|
||||
return { prompts, promptsGenerated };
|
||||
}
|
||||
export async function promptOptionalFields(packageData) {
|
||||
const keywords = await input({
|
||||
message: "Keywords (comma-separated, optional):",
|
||||
default: "",
|
||||
});
|
||||
const license = await input({
|
||||
message: "License:",
|
||||
default: packageData.license || "MIT",
|
||||
});
|
||||
const addRepository = await confirm({
|
||||
message: "Add repository information?",
|
||||
default: !!packageData.repository,
|
||||
});
|
||||
let repository;
|
||||
if (addRepository) {
|
||||
const repoUrl = await input({
|
||||
message: "Repository URL:",
|
||||
default: getDefaultRepositoryUrl(packageData),
|
||||
});
|
||||
if (repoUrl) {
|
||||
repository = {
|
||||
type: "git",
|
||||
url: repoUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
return { keywords, license, repository };
|
||||
}
|
||||
export async function promptLongDescription(description) {
|
||||
const hasLongDescription = await confirm({
|
||||
message: "Add a detailed long description?",
|
||||
default: false,
|
||||
});
|
||||
if (hasLongDescription) {
|
||||
const longDescription = await input({
|
||||
message: "Long description (supports basic markdown):",
|
||||
default: description,
|
||||
});
|
||||
return longDescription;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
export async function promptUrls() {
|
||||
const homepage = await input({
|
||||
message: "Homepage URL (optional):",
|
||||
validate: (value) => {
|
||||
if (!value.trim())
|
||||
return true;
|
||||
try {
|
||||
new URL(value);
|
||||
return true;
|
||||
}
|
||||
catch {
|
||||
return "Must be a valid URL (e.g., https://example.com)";
|
||||
}
|
||||
},
|
||||
});
|
||||
const documentation = await input({
|
||||
message: "Documentation URL (optional):",
|
||||
validate: (value) => {
|
||||
if (!value.trim())
|
||||
return true;
|
||||
try {
|
||||
new URL(value);
|
||||
return true;
|
||||
}
|
||||
catch {
|
||||
return "Must be a valid URL";
|
||||
}
|
||||
},
|
||||
});
|
||||
const support = await input({
|
||||
message: "Support URL (optional):",
|
||||
validate: (value) => {
|
||||
if (!value.trim())
|
||||
return true;
|
||||
try {
|
||||
new URL(value);
|
||||
return true;
|
||||
}
|
||||
catch {
|
||||
return "Must be a valid URL";
|
||||
}
|
||||
},
|
||||
});
|
||||
return { homepage, documentation, support };
|
||||
}
|
||||
export async function promptVisualAssets() {
|
||||
const icon = await input({
|
||||
message: "Icon file path (optional, relative to manifest):",
|
||||
validate: (value) => {
|
||||
if (!value.trim())
|
||||
return true;
|
||||
if (value.includes(".."))
|
||||
return "Relative paths cannot include '..'";
|
||||
return true;
|
||||
},
|
||||
});
|
||||
const addScreenshots = await confirm({
|
||||
message: "Add screenshots?",
|
||||
default: false,
|
||||
});
|
||||
const screenshots = [];
|
||||
if (addScreenshots) {
|
||||
let addMore = true;
|
||||
while (addMore) {
|
||||
const screenshot = await input({
|
||||
message: "Screenshot file path (relative to manifest):",
|
||||
validate: (value) => {
|
||||
if (!value.trim())
|
||||
return "Screenshot path is required";
|
||||
if (value.includes(".."))
|
||||
return "Relative paths cannot include '..'";
|
||||
return true;
|
||||
},
|
||||
});
|
||||
screenshots.push(screenshot);
|
||||
addMore = await confirm({
|
||||
message: "Add another screenshot?",
|
||||
default: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
return { icon, screenshots };
|
||||
}
|
||||
export async function promptCompatibility(serverType) {
|
||||
const addCompatibility = await confirm({
|
||||
message: "Add compatibility constraints?",
|
||||
default: false,
|
||||
});
|
||||
if (!addCompatibility) {
|
||||
return undefined;
|
||||
}
|
||||
const addPlatforms = await confirm({
|
||||
message: "Specify supported platforms?",
|
||||
default: false,
|
||||
});
|
||||
let platforms;
|
||||
if (addPlatforms) {
|
||||
const selectedPlatforms = [];
|
||||
const supportsDarwin = await confirm({
|
||||
message: "Support macOS (darwin)?",
|
||||
default: true,
|
||||
});
|
||||
if (supportsDarwin)
|
||||
selectedPlatforms.push("darwin");
|
||||
const supportsWin32 = await confirm({
|
||||
message: "Support Windows (win32)?",
|
||||
default: true,
|
||||
});
|
||||
if (supportsWin32)
|
||||
selectedPlatforms.push("win32");
|
||||
const supportsLinux = await confirm({
|
||||
message: "Support Linux?",
|
||||
default: true,
|
||||
});
|
||||
if (supportsLinux)
|
||||
selectedPlatforms.push("linux");
|
||||
platforms = selectedPlatforms.length > 0 ? selectedPlatforms : undefined;
|
||||
}
|
||||
let runtimes;
|
||||
if (serverType !== "binary") {
|
||||
const addRuntimes = await confirm({
|
||||
message: "Specify runtime version constraints?",
|
||||
default: false,
|
||||
});
|
||||
if (addRuntimes) {
|
||||
if (serverType === "python") {
|
||||
const pythonVersion = await input({
|
||||
message: "Python version constraint (e.g., >=3.8,<4.0):",
|
||||
validate: (value) => value.trim().length > 0 || "Python version constraint is required",
|
||||
});
|
||||
runtimes = { python: pythonVersion };
|
||||
}
|
||||
else if (serverType === "node") {
|
||||
const nodeVersion = await input({
|
||||
message: "Node.js version constraint (e.g., >=16.0.0):",
|
||||
validate: (value) => value.trim().length > 0 || "Node.js version constraint is required",
|
||||
});
|
||||
runtimes = { node: nodeVersion };
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
...(platforms ? { platforms } : {}),
|
||||
...(runtimes ? { runtimes } : {}),
|
||||
};
|
||||
}
|
||||
export async function promptUserConfig() {
|
||||
const addUserConfig = await confirm({
|
||||
message: "Add user-configurable options?",
|
||||
default: false,
|
||||
});
|
||||
if (!addUserConfig) {
|
||||
return {};
|
||||
}
|
||||
const userConfig = {};
|
||||
let addMore = true;
|
||||
while (addMore) {
|
||||
const optionKey = await input({
|
||||
message: "Configuration option key (unique identifier):",
|
||||
validate: (value) => {
|
||||
if (!value.trim())
|
||||
return "Key is required";
|
||||
if (userConfig[value])
|
||||
return "Key must be unique";
|
||||
return true;
|
||||
},
|
||||
});
|
||||
const optionType = (await select({
|
||||
message: "Option type:",
|
||||
choices: [
|
||||
{ name: "String", value: "string" },
|
||||
{ name: "Number", value: "number" },
|
||||
{ name: "Boolean", value: "boolean" },
|
||||
{ name: "Directory", value: "directory" },
|
||||
{ name: "File", value: "file" },
|
||||
],
|
||||
}));
|
||||
const optionTitle = await input({
|
||||
message: "Option title (human-readable name):",
|
||||
validate: (value) => value.trim().length > 0 || "Title is required",
|
||||
});
|
||||
const optionDescription = await input({
|
||||
message: "Option description:",
|
||||
validate: (value) => value.trim().length > 0 || "Description is required",
|
||||
});
|
||||
const optionRequired = await confirm({
|
||||
message: "Is this option required?",
|
||||
default: false,
|
||||
});
|
||||
const optionSensitive = await confirm({
|
||||
message: "Is this option sensitive (like a password)?",
|
||||
default: false,
|
||||
});
|
||||
// Build the option object
|
||||
const option = {
|
||||
type: optionType,
|
||||
title: optionTitle,
|
||||
description: optionDescription,
|
||||
required: optionRequired,
|
||||
sensitive: optionSensitive,
|
||||
};
|
||||
// Add default value if not required
|
||||
if (!optionRequired) {
|
||||
let defaultValue;
|
||||
if (optionType === "boolean") {
|
||||
defaultValue = await confirm({
|
||||
message: "Default value:",
|
||||
default: false,
|
||||
});
|
||||
}
|
||||
else if (optionType === "number") {
|
||||
const defaultStr = await input({
|
||||
message: "Default value (number):",
|
||||
validate: (value) => {
|
||||
if (!value.trim())
|
||||
return true;
|
||||
return !isNaN(Number(value)) || "Must be a valid number";
|
||||
},
|
||||
});
|
||||
defaultValue = defaultStr ? Number(defaultStr) : undefined;
|
||||
}
|
||||
else {
|
||||
defaultValue = await input({
|
||||
message: "Default value (optional):",
|
||||
});
|
||||
}
|
||||
if (defaultValue !== undefined && defaultValue !== "") {
|
||||
option.default = defaultValue;
|
||||
}
|
||||
}
|
||||
// Add constraints for number types
|
||||
if (optionType === "number") {
|
||||
const addConstraints = await confirm({
|
||||
message: "Add min/max constraints?",
|
||||
default: false,
|
||||
});
|
||||
if (addConstraints) {
|
||||
const min = await input({
|
||||
message: "Minimum value (optional):",
|
||||
validate: (value) => {
|
||||
if (!value.trim())
|
||||
return true;
|
||||
return !isNaN(Number(value)) || "Must be a valid number";
|
||||
},
|
||||
});
|
||||
const max = await input({
|
||||
message: "Maximum value (optional):",
|
||||
validate: (value) => {
|
||||
if (!value.trim())
|
||||
return true;
|
||||
return !isNaN(Number(value)) || "Must be a valid number";
|
||||
},
|
||||
});
|
||||
if (min)
|
||||
option.min = Number(min);
|
||||
if (max)
|
||||
option.max = Number(max);
|
||||
}
|
||||
}
|
||||
userConfig[optionKey] = option;
|
||||
addMore = await confirm({
|
||||
message: "Add another configuration option?",
|
||||
default: false,
|
||||
});
|
||||
}
|
||||
return userConfig;
|
||||
}
|
||||
export function buildManifest(basicInfo, longDescription, authorInfo, urls, visualAssets, serverConfig, tools, toolsGenerated, prompts, promptsGenerated, compatibility, userConfig, optionalFields) {
|
||||
const { name, displayName, version, description, authorName } = basicInfo;
|
||||
const { authorEmail, authorUrl } = authorInfo;
|
||||
const { serverType, entryPoint, mcp_config } = serverConfig;
|
||||
const { keywords, license, repository } = optionalFields;
|
||||
return {
|
||||
manifest_version: CURRENT_MANIFEST_VERSION,
|
||||
name,
|
||||
...(displayName && displayName !== name
|
||||
? { display_name: displayName }
|
||||
: {}),
|
||||
version,
|
||||
description,
|
||||
...(longDescription ? { long_description: longDescription } : {}),
|
||||
author: {
|
||||
name: authorName,
|
||||
...(authorEmail ? { email: authorEmail } : {}),
|
||||
...(authorUrl ? { url: authorUrl } : {}),
|
||||
},
|
||||
...(urls.homepage ? { homepage: urls.homepage } : {}),
|
||||
...(urls.documentation ? { documentation: urls.documentation } : {}),
|
||||
...(urls.support ? { support: urls.support } : {}),
|
||||
...(visualAssets.icon ? { icon: visualAssets.icon } : {}),
|
||||
...(visualAssets.screenshots.length > 0
|
||||
? { screenshots: visualAssets.screenshots }
|
||||
: {}),
|
||||
server: {
|
||||
type: serverType,
|
||||
entry_point: entryPoint,
|
||||
mcp_config,
|
||||
},
|
||||
...(tools.length > 0 ? { tools } : {}),
|
||||
...(toolsGenerated ? { tools_generated: true } : {}),
|
||||
...(prompts.length > 0 ? { prompts } : {}),
|
||||
...(promptsGenerated ? { prompts_generated: true } : {}),
|
||||
...(compatibility ? { compatibility } : {}),
|
||||
...(Object.keys(userConfig).length > 0 ? { user_config: userConfig } : {}),
|
||||
...(keywords
|
||||
? {
|
||||
keywords: keywords
|
||||
.split(",")
|
||||
.map((k) => k.trim())
|
||||
.filter((k) => k),
|
||||
}
|
||||
: {}),
|
||||
...(license ? { license } : {}),
|
||||
...(repository ? { repository } : {}),
|
||||
};
|
||||
}
|
||||
export function printNextSteps() {
|
||||
console.log("\nNext steps:");
|
||||
console.log(`1. Ensure all your production dependencies are in this directory`);
|
||||
console.log(`2. Run 'mcpb pack' to create your .mcpb file`);
|
||||
}
|
||||
export async function initExtension(targetPath = process.cwd(), nonInteractive = false) {
|
||||
const resolvedPath = resolve(targetPath);
|
||||
const manifestPath = join(resolvedPath, "manifest.json");
|
||||
if (existsSync(manifestPath)) {
|
||||
if (nonInteractive) {
|
||||
console.log("manifest.json already exists. Use --force to overwrite in non-interactive mode.");
|
||||
return false;
|
||||
}
|
||||
const overwrite = await confirm({
|
||||
message: "manifest.json already exists. Overwrite?",
|
||||
default: false,
|
||||
});
|
||||
if (!overwrite) {
|
||||
console.log("Cancelled");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!nonInteractive) {
|
||||
console.log("This utility will help you create a manifest.json file for your MCPB bundle.");
|
||||
console.log("Press ^C at any time to quit.\n");
|
||||
}
|
||||
else {
|
||||
console.log("Creating manifest.json with default values...");
|
||||
}
|
||||
try {
|
||||
const packageData = readPackageJson(resolvedPath);
|
||||
// Prompt for all information or use defaults
|
||||
const basicInfo = nonInteractive
|
||||
? getDefaultBasicInfo(packageData, resolvedPath)
|
||||
: await promptBasicInfo(packageData, resolvedPath);
|
||||
const longDescription = nonInteractive
|
||||
? undefined
|
||||
: await promptLongDescription(basicInfo.description);
|
||||
const authorInfo = nonInteractive
|
||||
? getDefaultAuthorInfo(packageData)
|
||||
: await promptAuthorInfo(packageData);
|
||||
const urls = nonInteractive
|
||||
? { homepage: "", documentation: "", support: "" }
|
||||
: await promptUrls();
|
||||
const visualAssets = nonInteractive
|
||||
? { icon: "", screenshots: [] }
|
||||
: await promptVisualAssets();
|
||||
const serverConfig = nonInteractive
|
||||
? getDefaultServerConfig(packageData)
|
||||
: await promptServerConfig(packageData);
|
||||
const toolsData = nonInteractive
|
||||
? { tools: [], toolsGenerated: false }
|
||||
: await promptTools();
|
||||
const promptsData = nonInteractive
|
||||
? { prompts: [], promptsGenerated: false }
|
||||
: await promptPrompts();
|
||||
const compatibility = nonInteractive
|
||||
? undefined
|
||||
: await promptCompatibility(serverConfig.serverType);
|
||||
const userConfig = nonInteractive ? {} : await promptUserConfig();
|
||||
const optionalFields = nonInteractive
|
||||
? getDefaultOptionalFields(packageData)
|
||||
: await promptOptionalFields(packageData);
|
||||
// Build manifest
|
||||
const manifest = buildManifest(basicInfo, longDescription, authorInfo, urls, visualAssets, serverConfig, toolsData.tools, toolsData.toolsGenerated, promptsData.prompts, promptsData.promptsGenerated, compatibility, userConfig, optionalFields);
|
||||
// Write manifest
|
||||
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
||||
console.log(`\nCreated manifest.json at ${manifestPath}`);
|
||||
printNextSteps();
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error && error.message.includes("User force closed")) {
|
||||
console.log("\nCancelled");
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
200
extracted-source/node_modules/@anthropic-ai/mcpb/dist/cli/pack.js
generated
vendored
Normal file
200
extracted-source/node_modules/@anthropic-ai/mcpb/dist/cli/pack.js
generated
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
import { confirm } from "@inquirer/prompts";
|
||||
import { createHash } from "crypto";
|
||||
import { zipSync } from "fflate";
|
||||
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync, } from "fs";
|
||||
import { basename, join, relative, resolve, sep } from "path";
|
||||
import { getAllFilesWithCount, readMcpbIgnorePatterns } from "../node/files.js";
|
||||
import { validateManifest } from "../node/validate.js";
|
||||
import { CURRENT_MANIFEST_VERSION, McpbManifestSchema } from "../schemas.js";
|
||||
import { getLogger } from "../shared/log.js";
|
||||
import { initExtension } from "./init.js";
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes < 1024) {
|
||||
return `${bytes}B`;
|
||||
}
|
||||
else if (bytes < 1024 * 1024) {
|
||||
return `${(bytes / 1024).toFixed(1)}kB`;
|
||||
}
|
||||
else {
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
||||
}
|
||||
}
|
||||
function sanitizeNameForFilename(name) {
|
||||
// Replace spaces with hyphens
|
||||
// Remove or replace characters that are problematic in filenames
|
||||
return name
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "-") // Replace spaces with hyphens
|
||||
.replace(/[^a-z0-9-_.]/g, "") // Keep only alphanumeric, hyphens, underscores, and dots
|
||||
.replace(/-+/g, "-") // Replace multiple hyphens with single hyphen
|
||||
.replace(/^-+|-+$/g, "") // Remove leading/trailing hyphens
|
||||
.substring(0, 100); // Limit length to 100 characters
|
||||
}
|
||||
export async function packExtension({ extensionPath, outputPath, silent, }) {
|
||||
const resolvedPath = resolve(extensionPath);
|
||||
const logger = getLogger({ silent });
|
||||
// Check if directory exists
|
||||
if (!existsSync(resolvedPath) || !statSync(resolvedPath).isDirectory()) {
|
||||
logger.error(`ERROR: Directory not found: ${extensionPath}`);
|
||||
return false;
|
||||
}
|
||||
// Check if manifest exists
|
||||
const manifestPath = join(resolvedPath, "manifest.json");
|
||||
if (!existsSync(manifestPath)) {
|
||||
logger.log(`No manifest.json found in ${extensionPath}`);
|
||||
const shouldInit = await confirm({
|
||||
message: "Would you like to create a manifest.json file?",
|
||||
default: true,
|
||||
});
|
||||
if (shouldInit) {
|
||||
const success = await initExtension(extensionPath);
|
||||
if (!success) {
|
||||
logger.error("ERROR: Failed to create manifest");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.error("ERROR: Cannot pack extension without manifest.json");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Validate manifest first
|
||||
logger.log("Validating manifest...");
|
||||
if (!validateManifest(manifestPath)) {
|
||||
logger.error("ERROR: Cannot pack extension with invalid manifest");
|
||||
return false;
|
||||
}
|
||||
// Read and parse manifest
|
||||
let manifest;
|
||||
try {
|
||||
const manifestContent = readFileSync(manifestPath, "utf-8");
|
||||
const manifestData = JSON.parse(manifestContent);
|
||||
manifest = McpbManifestSchema.parse(manifestData);
|
||||
}
|
||||
catch (error) {
|
||||
logger.error("ERROR: Failed to parse manifest.json");
|
||||
if (error instanceof Error) {
|
||||
logger.error(` ${error.message}`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const manifestVersion = manifest.manifest_version || manifest.dxt_version;
|
||||
if (manifestVersion !== CURRENT_MANIFEST_VERSION) {
|
||||
logger.error(`ERROR: Manifest version mismatch. Expected "${CURRENT_MANIFEST_VERSION}", found "${manifestVersion}"`);
|
||||
logger.error(` Please update the manifest_version in your manifest.json to "${CURRENT_MANIFEST_VERSION}"`);
|
||||
return false;
|
||||
}
|
||||
// Determine output path
|
||||
const extensionName = basename(resolvedPath);
|
||||
const finalOutputPath = outputPath
|
||||
? resolve(outputPath)
|
||||
: resolve(`${extensionName}.mcpb`);
|
||||
// Ensure output directory exists
|
||||
const outputDir = join(finalOutputPath, "..");
|
||||
mkdirSync(outputDir, { recursive: true });
|
||||
try {
|
||||
// Read .mcpbignore patterns if present
|
||||
const mcpbIgnorePatterns = readMcpbIgnorePatterns(resolvedPath);
|
||||
// Get all files in the extension directory
|
||||
const { files, ignoredCount } = getAllFilesWithCount(resolvedPath, resolvedPath, {}, mcpbIgnorePatterns);
|
||||
// Print package header
|
||||
logger.log(`\n📦 ${manifest.name}@${manifest.version}`);
|
||||
// Print file list
|
||||
logger.log("Archive Contents");
|
||||
const fileEntries = Object.entries(files);
|
||||
let totalUnpackedSize = 0;
|
||||
// Sort files for consistent output
|
||||
fileEntries.sort(([a], [b]) => a.localeCompare(b));
|
||||
// Group files by directory for deep nesting
|
||||
const directoryGroups = new Map();
|
||||
const shallowFiles = [];
|
||||
for (const [filePath, fileData] of fileEntries) {
|
||||
const relPath = relative(resolvedPath, filePath);
|
||||
const content = fileData.data;
|
||||
const size = typeof content === "string"
|
||||
? Buffer.byteLength(content, "utf8")
|
||||
: content.length;
|
||||
totalUnpackedSize += size;
|
||||
// Check if file is deeply nested (3+ levels)
|
||||
const parts = relPath.split(sep);
|
||||
if (parts.length > 3) {
|
||||
// Group by the first 3 directory levels
|
||||
const groupKey = parts.slice(0, 3).join("/");
|
||||
if (!directoryGroups.has(groupKey)) {
|
||||
directoryGroups.set(groupKey, { files: [], totalSize: 0 });
|
||||
}
|
||||
const group = directoryGroups.get(groupKey);
|
||||
group.files.push(relPath);
|
||||
group.totalSize += size;
|
||||
}
|
||||
else {
|
||||
shallowFiles.push({ path: relPath, size });
|
||||
}
|
||||
}
|
||||
// Print shallow files first
|
||||
for (const { path, size } of shallowFiles) {
|
||||
logger.log(`${formatFileSize(size).padStart(8)} ${path}`);
|
||||
}
|
||||
// Print grouped directories
|
||||
for (const [dir, { files, totalSize }] of directoryGroups) {
|
||||
if (files.length === 1) {
|
||||
// If only one file in the group, print it normally
|
||||
const filePath = files[0];
|
||||
const fileSize = totalSize;
|
||||
logger.log(`${formatFileSize(fileSize).padStart(8)} ${filePath}`);
|
||||
}
|
||||
else {
|
||||
// Print directory summary
|
||||
logger.log(`${formatFileSize(totalSize).padStart(8)} ${dir}/ [and ${files.length} more files]`);
|
||||
}
|
||||
}
|
||||
// Create zip with preserved file permissions
|
||||
const zipFiles = {};
|
||||
const isUnix = process.platform !== "win32";
|
||||
for (const [filePath, fileData] of Object.entries(files)) {
|
||||
if (isUnix) {
|
||||
// Set external file attributes to preserve Unix permissions
|
||||
// The mode needs to be shifted to the upper 16 bits for ZIP format
|
||||
zipFiles[filePath] = [
|
||||
fileData.data,
|
||||
{ os: 3, attrs: (fileData.mode & 0o777) << 16 },
|
||||
];
|
||||
}
|
||||
else {
|
||||
// On Windows, use default ZIP attributes (no Unix permissions)
|
||||
zipFiles[filePath] = fileData.data;
|
||||
}
|
||||
}
|
||||
const zipData = zipSync(zipFiles, {
|
||||
level: 9, // Maximum compression
|
||||
mtime: new Date(),
|
||||
});
|
||||
// Write zip file
|
||||
writeFileSync(finalOutputPath, zipData);
|
||||
// Calculate SHA sum
|
||||
const shasum = createHash("sha1").update(zipData).digest("hex");
|
||||
// Print archive details
|
||||
const sanitizedName = sanitizeNameForFilename(manifest.name);
|
||||
const archiveName = `${sanitizedName}-${manifest.version}.mcpb`;
|
||||
logger.log("\nArchive Details");
|
||||
logger.log(`name: ${manifest.name}`);
|
||||
logger.log(`version: ${manifest.version}`);
|
||||
logger.log(`filename: ${archiveName}`);
|
||||
logger.log(`package size: ${formatFileSize(zipData.length)}`);
|
||||
logger.log(`unpacked size: ${formatFileSize(totalUnpackedSize)}`);
|
||||
logger.log(`shasum: ${shasum}`);
|
||||
logger.log(`total files: ${fileEntries.length}`);
|
||||
logger.log(`ignored (.mcpbignore) files: ${ignoredCount}`);
|
||||
logger.log(`\nOutput: ${finalOutputPath}`);
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
logger.error(`ERROR: Archive error: ${error.message}`);
|
||||
}
|
||||
else {
|
||||
logger.error("ERROR: Unknown archive error occurred");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
101
extracted-source/node_modules/@anthropic-ai/mcpb/dist/cli/unpack.js
generated
vendored
Normal file
101
extracted-source/node_modules/@anthropic-ai/mcpb/dist/cli/unpack.js
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
import { unzipSync } from "fflate";
|
||||
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "fs";
|
||||
import { join, resolve, sep } from "path";
|
||||
import { extractSignatureBlock } from "../node/sign.js";
|
||||
import { getLogger } from "../shared/log.js";
|
||||
export async function unpackExtension({ mcpbPath, outputDir, silent, }) {
|
||||
const logger = getLogger({ silent });
|
||||
const resolvedMcpbPath = resolve(mcpbPath);
|
||||
if (!existsSync(resolvedMcpbPath)) {
|
||||
logger.error(`ERROR: MCPB file not found: ${mcpbPath}`);
|
||||
return false;
|
||||
}
|
||||
const finalOutputDir = outputDir ? resolve(outputDir) : process.cwd();
|
||||
if (!existsSync(finalOutputDir)) {
|
||||
mkdirSync(finalOutputDir, { recursive: true });
|
||||
}
|
||||
try {
|
||||
const fileContent = readFileSync(resolvedMcpbPath);
|
||||
const { originalContent } = extractSignatureBlock(fileContent);
|
||||
// Parse file attributes from ZIP central directory
|
||||
const fileAttributes = new Map();
|
||||
const isUnix = process.platform !== "win32";
|
||||
if (isUnix) {
|
||||
// Parse ZIP central directory to extract file attributes
|
||||
const zipBuffer = originalContent;
|
||||
// Find end of central directory record
|
||||
let eocdOffset = -1;
|
||||
for (let i = zipBuffer.length - 22; i >= 0; i--) {
|
||||
if (zipBuffer.readUInt32LE(i) === 0x06054b50) {
|
||||
eocdOffset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (eocdOffset !== -1) {
|
||||
const centralDirOffset = zipBuffer.readUInt32LE(eocdOffset + 16);
|
||||
const centralDirEntries = zipBuffer.readUInt16LE(eocdOffset + 8);
|
||||
let offset = centralDirOffset;
|
||||
for (let i = 0; i < centralDirEntries; i++) {
|
||||
if (zipBuffer.readUInt32LE(offset) === 0x02014b50) {
|
||||
const externalAttrs = zipBuffer.readUInt32LE(offset + 38);
|
||||
const filenameLength = zipBuffer.readUInt16LE(offset + 28);
|
||||
const filename = zipBuffer.toString("utf8", offset + 46, offset + 46 + filenameLength);
|
||||
// Extract Unix permissions from external attributes (upper 16 bits)
|
||||
const mode = (externalAttrs >> 16) & 0o777;
|
||||
if (mode > 0) {
|
||||
fileAttributes.set(filename, mode);
|
||||
}
|
||||
const extraFieldLength = zipBuffer.readUInt16LE(offset + 30);
|
||||
const commentLength = zipBuffer.readUInt16LE(offset + 32);
|
||||
offset += 46 + filenameLength + extraFieldLength + commentLength;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const decompressed = unzipSync(originalContent);
|
||||
for (const relativePath in decompressed) {
|
||||
if (Object.prototype.hasOwnProperty.call(decompressed, relativePath)) {
|
||||
const data = decompressed[relativePath];
|
||||
const fullPath = join(finalOutputDir, relativePath);
|
||||
// Prevent zip slip attacks by validating the resolved path
|
||||
const normalizedPath = resolve(fullPath);
|
||||
const normalizedOutputDir = resolve(finalOutputDir);
|
||||
if (!normalizedPath.startsWith(normalizedOutputDir + sep) &&
|
||||
normalizedPath !== normalizedOutputDir) {
|
||||
throw new Error(`Path traversal attempt detected: ${relativePath}`);
|
||||
}
|
||||
const dir = join(fullPath, "..");
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
writeFileSync(fullPath, data);
|
||||
// Restore Unix file permissions if available
|
||||
if (isUnix && fileAttributes.has(relativePath)) {
|
||||
try {
|
||||
const mode = fileAttributes.get(relativePath);
|
||||
if (mode !== undefined) {
|
||||
chmodSync(fullPath, mode);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// Silently ignore permission errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.log(`Extension unpacked successfully to ${finalOutputDir}`);
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
logger.error(`ERROR: Failed to unpack extension: ${error.message}`);
|
||||
}
|
||||
else {
|
||||
logger.error("ERROR: An unknown error occurred during unpacking.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user