@smithery/cli 0.0.3
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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/auto-update.js +33 -0
- package/dist/commands/get.js +25 -0
- package/dist/commands/install.js +70 -0
- package/dist/commands/installed.js +31 -0
- package/dist/commands/list.js +45 -0
- package/dist/commands/uninstall.js +47 -0
- package/dist/extractors/modelcontextprotocol-extractor.js +209 -0
- package/dist/helpers/index.js +94 -0
- package/dist/index.js +44 -0
- package/dist/install.js +79 -0
- package/dist/types/index.js +1 -0
- package/dist/types/package.js +1 -0
- package/dist/types.js +1 -0
- package/dist/utils/config-manager.js +113 -0
- package/dist/utils/config.js +73 -0
- package/dist/utils/display.js +38 -0
- package/dist/utils/package-actions.js +52 -0
- package/dist/utils/package-management.js +197 -0
- package/dist/utils/package-resolver.js +143 -0
- package/dist/utils/runtime-utils.js +37 -0
- package/dist/utils/ui.js +46 -0
- package/package.json +66 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { ConfigManager } from "./config-manager.js";
|
|
2
|
+
// import path from 'path';
|
|
3
|
+
// import fs from 'fs';
|
|
4
|
+
// import { dirname } from 'path';
|
|
5
|
+
// import { fileURLToPath } from 'url';
|
|
6
|
+
export function isPackageInstalled(packageName) {
|
|
7
|
+
return ConfigManager.isPackageInstalled(packageName);
|
|
8
|
+
}
|
|
9
|
+
export async function resolvePackages() {
|
|
10
|
+
try {
|
|
11
|
+
// Fetch all packages from registry
|
|
12
|
+
const response = await fetch("https://registry.smithery.ai/packages");
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
throw new Error(`Failed to fetch packages: ${response.statusText} (${response.status})`);
|
|
15
|
+
}
|
|
16
|
+
// Add text validation before JSON parsing
|
|
17
|
+
const text = await response.text();
|
|
18
|
+
if (!text) {
|
|
19
|
+
throw new Error("Empty response received from registry");
|
|
20
|
+
}
|
|
21
|
+
let packages;
|
|
22
|
+
try {
|
|
23
|
+
packages = JSON.parse(text);
|
|
24
|
+
}
|
|
25
|
+
catch (parseError) {
|
|
26
|
+
const errorMessage = parseError instanceof Error
|
|
27
|
+
? parseError.message
|
|
28
|
+
: "Unknown parsing error";
|
|
29
|
+
throw new Error(`Invalid JSON response from registry: ${errorMessage}`);
|
|
30
|
+
}
|
|
31
|
+
const config = ConfigManager.readConfig();
|
|
32
|
+
const installedServers = config.mcpServers || {};
|
|
33
|
+
const installedIds = Object.keys(installedServers);
|
|
34
|
+
// Create a map using IDs
|
|
35
|
+
const packageMap = new Map();
|
|
36
|
+
for (const pkg of packages) {
|
|
37
|
+
packageMap.set(pkg.id, pkg);
|
|
38
|
+
}
|
|
39
|
+
const resolvedPackages = new Map();
|
|
40
|
+
// Add packages from registry
|
|
41
|
+
for (const pkg of packages) {
|
|
42
|
+
resolvedPackages.set(pkg.id, {
|
|
43
|
+
id: pkg.id,
|
|
44
|
+
name: pkg.name,
|
|
45
|
+
description: pkg.description,
|
|
46
|
+
vendor: pkg.vendor,
|
|
47
|
+
sourceUrl: pkg.sourceUrl,
|
|
48
|
+
license: pkg.license,
|
|
49
|
+
connections: pkg.connections,
|
|
50
|
+
homepage: pkg.homepage,
|
|
51
|
+
// runtime: 'node',
|
|
52
|
+
isInstalled: false,
|
|
53
|
+
isVerified: true,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
// Process installed packages
|
|
57
|
+
for (const id of installedIds) {
|
|
58
|
+
const installedServer = installedServers[id];
|
|
59
|
+
const existingPkg = packageMap.get(id);
|
|
60
|
+
if (existingPkg) {
|
|
61
|
+
resolvedPackages.set(id, {
|
|
62
|
+
id: existingPkg.id,
|
|
63
|
+
name: existingPkg.name,
|
|
64
|
+
description: existingPkg.description,
|
|
65
|
+
vendor: existingPkg.vendor,
|
|
66
|
+
sourceUrl: existingPkg.sourceUrl,
|
|
67
|
+
license: existingPkg.license,
|
|
68
|
+
connections: existingPkg.connections,
|
|
69
|
+
homepage: existingPkg.homepage,
|
|
70
|
+
// runtime: installedServer?.runtime || 'node',
|
|
71
|
+
isInstalled: true,
|
|
72
|
+
isVerified: true,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
resolvedPackages.set(id, {
|
|
77
|
+
...installedServer,
|
|
78
|
+
id,
|
|
79
|
+
name: id,
|
|
80
|
+
description: "Local package",
|
|
81
|
+
vendor: "Local",
|
|
82
|
+
sourceUrl: "",
|
|
83
|
+
license: "",
|
|
84
|
+
connections: [],
|
|
85
|
+
homepage: "",
|
|
86
|
+
// runtime: installedServer?.runtime || 'node',
|
|
87
|
+
isInstalled: true,
|
|
88
|
+
isVerified: false,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return Array.from(resolvedPackages.values());
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
throw new Error(`Failed to resolve packages: ${error instanceof Error ? error.message : String(error)}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export async function resolvePackage(packageId) {
|
|
99
|
+
try {
|
|
100
|
+
// Replace file reading with registry fetch
|
|
101
|
+
const response = await fetch(`https://registry.smithery.ai/${packageId}`);
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
// Check if it's an installed package
|
|
104
|
+
const config = ConfigManager.readConfig();
|
|
105
|
+
const installedServer = config.mcpServers?.[packageId];
|
|
106
|
+
if (installedServer) {
|
|
107
|
+
return {
|
|
108
|
+
id: packageId,
|
|
109
|
+
name: packageId,
|
|
110
|
+
description: "Local package",
|
|
111
|
+
vendor: "Local",
|
|
112
|
+
sourceUrl: "",
|
|
113
|
+
license: "",
|
|
114
|
+
connections: [],
|
|
115
|
+
homepage: "",
|
|
116
|
+
// runtime: installedServer.runtime || 'node',
|
|
117
|
+
isInstalled: true,
|
|
118
|
+
isVerified: false,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const registryPackage = await response.json();
|
|
124
|
+
const resolvedPackage = {
|
|
125
|
+
id: registryPackage.id,
|
|
126
|
+
name: registryPackage.name,
|
|
127
|
+
description: registryPackage.description,
|
|
128
|
+
vendor: registryPackage.vendor,
|
|
129
|
+
sourceUrl: registryPackage.sourceUrl,
|
|
130
|
+
license: registryPackage.license,
|
|
131
|
+
connections: registryPackage.connections,
|
|
132
|
+
homepage: registryPackage.homepage,
|
|
133
|
+
// runtime: 'node',
|
|
134
|
+
isInstalled: isPackageInstalled(registryPackage.id),
|
|
135
|
+
isVerified: true,
|
|
136
|
+
};
|
|
137
|
+
return resolvedPackage;
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
console.error("Error resolving package:", error);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
export async function checkUVInstalled() {
|
|
6
|
+
try {
|
|
7
|
+
await execAsync("uvx --version");
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function promptForUVInstall(inquirerInstance) {
|
|
15
|
+
const { shouldInstall } = await inquirerInstance.prompt([
|
|
16
|
+
{
|
|
17
|
+
type: "confirm",
|
|
18
|
+
name: "shouldInstall",
|
|
19
|
+
message: "UV package manager is required for Python MCP servers. Would you like to install it?",
|
|
20
|
+
default: true,
|
|
21
|
+
},
|
|
22
|
+
]);
|
|
23
|
+
if (!shouldInstall) {
|
|
24
|
+
console.warn(chalk.yellow("UV installation was declined. You can install it manually from https://astral.sh/uv"));
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
console.log("Installing uv package manager...");
|
|
28
|
+
try {
|
|
29
|
+
await execAsync("curl -LsSf https://astral.sh/uv/install.sh | sh");
|
|
30
|
+
console.log(chalk.green("✓ UV installed successfully"));
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.warn(chalk.yellow("Failed to install UV. You can install it manually from https://astral.sh/uv"));
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
package/dist/utils/ui.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import fuzzy from "fuzzy";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
export function formatPackageChoice(pkg, showInstallStatus = false) {
|
|
5
|
+
const prefix = showInstallStatus ? (pkg.isInstalled ? "✓ " : " ") : "";
|
|
6
|
+
return {
|
|
7
|
+
name: `${prefix}${pkg.name.padEnd(showInstallStatus ? 22 : 24)} │ ${pkg.description.length > 47
|
|
8
|
+
? `${pkg.description.slice(0, 44)}...`
|
|
9
|
+
: pkg.description.padEnd(49)} │ ${pkg.vendor.padEnd(19)} │ ${pkg.license.padEnd(14)}`,
|
|
10
|
+
value: pkg,
|
|
11
|
+
short: pkg.name,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function createPackagePrompt(packages, options = {}) {
|
|
15
|
+
const choices = packages.map((pkg) => formatPackageChoice(pkg, options.showInstallStatus));
|
|
16
|
+
return {
|
|
17
|
+
type: "autocomplete",
|
|
18
|
+
name: "selectedPackage",
|
|
19
|
+
message: options.message || "Search and select a package:",
|
|
20
|
+
source: async (_answersSoFar, input) => {
|
|
21
|
+
if (!input)
|
|
22
|
+
return choices;
|
|
23
|
+
return fuzzy
|
|
24
|
+
.filter(input.toLowerCase(), choices, {
|
|
25
|
+
extract: (choice) => `${choice.value.name} ${choice.value.description} ${choice.value.vendor}`.toLowerCase(),
|
|
26
|
+
})
|
|
27
|
+
.map((result) => result.original);
|
|
28
|
+
},
|
|
29
|
+
pageSize: 10,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export async function confirmUninstall(packageName) {
|
|
33
|
+
const { confirmUninstall } = await inquirer.prompt([
|
|
34
|
+
{
|
|
35
|
+
type: "confirm",
|
|
36
|
+
name: "confirmUninstall",
|
|
37
|
+
message: `Are you sure you want to uninstall ${packageName}?`,
|
|
38
|
+
default: false,
|
|
39
|
+
},
|
|
40
|
+
]);
|
|
41
|
+
return confirmUninstall;
|
|
42
|
+
}
|
|
43
|
+
export function printPackageListHeader(count, type = "all") {
|
|
44
|
+
console.log(chalk.bold.cyan(`\n📦 ${type === "installed" ? "Installed Packages" : "Available Packages"}`));
|
|
45
|
+
console.log(chalk.gray(`Found ${count} ${type === "installed" ? "installed " : ""}packages\n`));
|
|
46
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@smithery/cli",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"homepage": "https://smithery.ai/",
|
|
5
|
+
"description": "A NPX command to install and list Model Context Protocols",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"test:list": "node --loader ts-node/esm src/index.ts list",
|
|
11
|
+
"test:install": "node --loader ts-node/esm src/index.ts install",
|
|
12
|
+
"test:installed": "node --loader ts-node/esm src/index.ts installed",
|
|
13
|
+
"extract": "node --loader ts-node/esm src/extractors/modelcontextprotocol-extractor.ts",
|
|
14
|
+
"test:uninstall": "node --loader ts-node/esm src/index.ts uninstall",
|
|
15
|
+
"version:patch": "npm version patch",
|
|
16
|
+
"version:minor": "npm version minor",
|
|
17
|
+
"version:major": "npm version major",
|
|
18
|
+
"publish:npm": "npm run build && npm publish --access public",
|
|
19
|
+
"pr-check": "node src/scripts/pr-check.js",
|
|
20
|
+
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --config jest.config.mjs --watch",
|
|
21
|
+
"test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --config jest.config.mjs --coverage",
|
|
22
|
+
"prepare": "npm run build",
|
|
23
|
+
"lint": "npx @biomejs/biome lint --write",
|
|
24
|
+
"format": "npx @biomejs/biome format --write"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"@smithery/cli": "dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@iarna/toml": "^2.2.5",
|
|
31
|
+
"@types/iarna__toml": "^2.0.5",
|
|
32
|
+
"chalk": "^4.1.2",
|
|
33
|
+
"cli-table3": "^0.6.5",
|
|
34
|
+
"dotenv": "^16.4.5",
|
|
35
|
+
"fuzzy": "^0.1.3",
|
|
36
|
+
"inquirer": "^8.2.4",
|
|
37
|
+
"inquirer-autocomplete-prompt": "^2.0.0",
|
|
38
|
+
"open": "^10.1.0",
|
|
39
|
+
"string-width": "^4.2.3",
|
|
40
|
+
"typescript": "^4.0.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/inquirer": "^8.2.4",
|
|
44
|
+
"@types/inquirer-autocomplete-prompt": "^3.0.3",
|
|
45
|
+
"@types/jest": "^29.5.14",
|
|
46
|
+
"@types/node": "^14.0.0",
|
|
47
|
+
"jest": "^29.7.0",
|
|
48
|
+
"ts-jest": "^29.2.5",
|
|
49
|
+
"ts-node": "^10.9.1",
|
|
50
|
+
"tsx": "^4.19.2"
|
|
51
|
+
},
|
|
52
|
+
"files": [
|
|
53
|
+
"dist",
|
|
54
|
+
"README.md",
|
|
55
|
+
"package.json",
|
|
56
|
+
"packages",
|
|
57
|
+
"LICENSE"
|
|
58
|
+
],
|
|
59
|
+
"type": "module",
|
|
60
|
+
"exports": {
|
|
61
|
+
".": {
|
|
62
|
+
"import": "./dist/index.js"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"license": "MIT"
|
|
66
|
+
}
|