Skip to content

Commit

Permalink
Refactor package management and update dependencies
Browse files Browse the repository at this point in the history
Fixes #22

- Restructured command handling in `src/index.ts` for improved clarity and maintainability.
- Removed unused files: `src/installed.ts`, `src/list.ts`, and `src/uninstall.ts`.
- Updated `package.json` and `package-lock.json` to include new dependencies: `jest`, `ts-jest`, and `@types/jest`.
- Modified `tsconfig.json` to include types for `node` and `jest`.
- Enhanced package resolution logic in `src/utils/package-management.ts` to utilize a new `ConfigManager`.
- Cleaned up type definitions by consolidating them in `src/types/package.js`.
- Improved error handling and user prompts throughout the package management process.
  • Loading branch information
michaellatman committed Dec 6, 2024
1 parent 11ac798 commit 10edf25
Show file tree
Hide file tree
Showing 21 changed files with 4,502 additions and 1,022 deletions.
File renamed without changes.
22 changes: 22 additions & 0 deletions jest.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
const config = {
preset: 'ts-jest/presets/default-esm',
testEnvironment: 'node',
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
useESM: true,
},
],
},
testMatch: ['**/__tests__/**/*.test.ts'],
testPathIgnorePatterns: ['/node_modules/', '/loaders/'],
setupFilesAfterEnv: ['<rootDir>/test/setup.ts'],
};

export default config;
4,239 changes: 3,805 additions & 434 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
"version:minor": "npm version minor",
"version:major": "npm version major",
"publish:npm": "npm run build && npm publish --access public",
"pr-check": "node src/scripts/pr-check.js"
"pr-check": "node src/scripts/pr-check.js",
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --config jest.config.mjs --watch",
"test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --config jest.config.mjs --coverage",
"prepare": "npm run build"
},
"bin": {
"mcp-get": "dist/index.js"
Expand All @@ -36,7 +39,10 @@
"devDependencies": {
"@types/inquirer": "^8.2.4",
"@types/inquirer-autocomplete-prompt": "^3.0.3",
"@types/jest": "^29.5.14",
"@types/node": "^14.0.0",
"jest": "^29.7.0",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.1"
},
"files": [
Expand Down
71 changes: 71 additions & 0 deletions src/commands/install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Package } from '../types/package.js';
import { installPackage as installPkg } from '../utils/package-management.js';
import inquirer from 'inquirer';
import chalk from 'chalk';
import { resolvePackages } from '../utils/package-resolver.js';

async function promptForRuntime(): Promise<'node' | 'python'> {
const { runtime } = await inquirer.prompt<{ runtime: 'node' | 'python' }>([
{
type: 'list',
name: 'runtime',
message: 'What runtime does this package use?',
choices: [
{ name: 'Node.js', value: 'node' },
{ name: 'Python', value: 'python' }
]
}
]);
return runtime;
}

function createUnknownPackage(packageName: string, runtime: 'node' | 'python'): Package {
return {
name: packageName,
description: 'Unverified package',
runtime,
vendor: '',
sourceUrl: '',
homepage: '',
license: ''
};
}

export async function installPackage(pkg: Package): Promise<void> {
return installPkg(pkg);
}

export async function install(packageName: string): Promise<void> {
const packages = resolvePackages();
const pkg = packages.find(p => p.name === packageName);

if (!pkg) {
console.warn(chalk.yellow(`Package ${packageName} not found in the curated list.`));

const { proceedWithInstall } = await inquirer.prompt<{ proceedWithInstall: boolean }>([
{
type: 'confirm',
name: 'proceedWithInstall',
message: `Would you like to try installing ${packageName} anyway? This package hasn't been verified.`,
default: false
}
]);

if (proceedWithInstall) {
console.log(chalk.cyan(`Proceeding with installation of ${packageName}...`));

// Prompt for runtime for unverified packages
const runtime = await promptForRuntime();

// Create a basic package object for unverified packages
const unknownPkg = createUnknownPackage(packageName, runtime);
await installPkg(unknownPkg);
} else {
console.log('Installation cancelled.');
process.exit(1);
}
return;
}

await installPkg(pkg);
}
40 changes: 40 additions & 0 deletions src/commands/installed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import inquirer from 'inquirer';
import chalk from 'chalk';
import { displayPackageDetailsWithActions } from '../utils/display.js';
import { resolvePackages } from '../utils/package-resolver.js';
import { ResolvedPackage } from '../types/package.js';
import AutocompletePrompt from 'inquirer-autocomplete-prompt';
import { createPackagePrompt, printPackageListHeader } from '../utils/ui.js';
import { handlePackageAction } from '../utils/package-actions.js';

inquirer.registerPrompt('autocomplete', AutocompletePrompt);

export async function listInstalledPackages(): Promise<void> {
// Get all packages with their resolved status
const allPackages = resolvePackages();

// Filter for only installed packages
const installedPackages = allPackages.filter(pkg => pkg.isInstalled);

if (installedPackages.length === 0) {
console.log(chalk.yellow('\nNo MCP servers are currently installed.'));
return;
}

printPackageListHeader(installedPackages.length, 'installed');

const prompt = createPackagePrompt(installedPackages, {
message: 'Search and select a package:'
});
const answer = await inquirer.prompt<{ selectedPackage: ResolvedPackage }>([prompt]);

if (!answer.selectedPackage) {
return;
}

const action = await displayPackageDetailsWithActions(answer.selectedPackage);
await handlePackageAction(answer.selectedPackage, action, {
onUninstall: () => listInstalledPackages(),
onBack: listInstalledPackages
});
}
34 changes: 34 additions & 0 deletions src/commands/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import chalk from 'chalk';
import inquirer from 'inquirer';
import { displayPackageDetailsWithActions } from '../utils/display.js';
import { resolvePackages } from '../utils/package-resolver.js';
import { ResolvedPackage } from '../types/package.js';
import AutocompletePrompt from 'inquirer-autocomplete-prompt';
import { createPackagePrompt, printPackageListHeader } from '../utils/ui.js';
import { handlePackageAction } from '../utils/package-actions.js';

// Register the autocomplete prompt
inquirer.registerPrompt('autocomplete', AutocompletePrompt);

export async function list() {
try {
const packages = resolvePackages();
printPackageListHeader(packages.length);

const prompt = createPackagePrompt(packages, { showInstallStatus: true });
const answer = await inquirer.prompt<{ selectedPackage: ResolvedPackage }>([prompt]);

if (!answer.selectedPackage) {
return;
}

const action = await displayPackageDetailsWithActions(answer.selectedPackage);
await handlePackageAction(answer.selectedPackage, action, {
onBack: list
});
} catch (error) {
console.error(chalk.red('Error loading package list:'));
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
process.exit(1);
}
}
51 changes: 51 additions & 0 deletions src/commands/uninstall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import chalk from 'chalk';
import inquirer from 'inquirer';
import { resolvePackage } from '../utils/package-resolver.js';
import { uninstallPackage } from '../utils/package-management.js';

export async function uninstall(packageName?: string): Promise<void> {
console.error("!");
try {
// If no package name provided, show error
if (!packageName) {
console.error(chalk.red('Error: Package name is required'));
console.log('Usage: mcp-get uninstall <package-name>');
process.exit(1);
}

// Resolve the package
const pkg = resolvePackage(packageName);
if (!pkg) {
console.log(chalk.yellow(`Package ${packageName} not found.`));
return;
}

if (!pkg.isInstalled) {
console.log(chalk.yellow(`Package ${packageName} is not installed.`));
return;
}

// Confirm uninstallation
const { confirmUninstall } = await inquirer.prompt<{ confirmUninstall: boolean }>([{
type: 'confirm',
name: 'confirmUninstall',
message: `Are you sure you want to uninstall ${packageName}?`,
default: false
}]);

if (!confirmUninstall) {
console.log('Uninstallation cancelled.');
return;
}

// Perform uninstallation
await uninstallPackage(packageName);
console.log(chalk.green(`\nSuccessfully uninstalled ${packageName}`));
console.log(chalk.yellow('\nNote: Please restart Claude for the changes to take effect.'));

} catch (error) {
console.error(chalk.red('Failed to uninstall package:'));
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
process.exit(1);
}
}
102 changes: 29 additions & 73 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,38 @@
#!/usr/bin/env node

import { install } from './install.js';
import { list } from './list.js';
import inquirer from 'inquirer';
import autocomplete from 'inquirer-autocomplete-prompt';
import { listInstalledPackages } from './installed.js';
import { uninstall } from './uninstall.js';
import { updatePackage } from './auto-update.js';
import chalk from 'chalk';
import { list } from './commands/list.js';
import { install } from './commands/install.js';
import { uninstall } from './commands/uninstall.js';
import { listInstalledPackages } from './commands/installed.js';

inquirer.registerPrompt('autocomplete', autocomplete);

const args = process.argv.slice(2);
const command = args[0];

const skipUpdateCommands = ['update', 'version', 'help'];

function displayHelp() {
console.log(chalk.bold.cyan('\nMCP-Get - Model Context Protocol Package Manager\n'));
console.log('Usage: mcp-get <command>\n');
console.log('Commands:');
console.log(' install <package> Install a package');
console.log(' uninstall <package> Uninstall a package');
console.log(' ls, list List available packages');
console.log(' installed List installed packages');
console.log(' update Update mcp-get to the latest version');
console.log(' help Display this help message\n');
console.log('Examples:');
console.log(' mcp-get install @modelcontextprotocol/server-brave-search');
console.log(' mcp-get ls');
console.log(' mcp-get installed\n');
console.log('For more information, visit: https://modelcontextprotocol.io\n');
}
const command = process.argv[2];
const packageName = process.argv[3];

async function main() {
try {
if (!skipUpdateCommands.includes(command)) {
await updatePackage();
}

switch (command) {
case undefined:
case 'help':
displayHelp();
break;
case 'install':
const packageName = args[1];
if (!packageName) {
console.error('Please provide a package name.');
process.exit(1);
}
await install(packageName);
break;
case 'uninstall':
const pkgToUninstall = args[1];
if (!pkgToUninstall) {
console.error('Please provide a package name to uninstall.');
process.exit(1);
}
uninstall(pkgToUninstall);
break;
case 'ls':
case 'list':
list();
break;
case 'installed':
listInstalledPackages();
break;
case 'update':
await updatePackage();
break;
default:
console.error(`Unknown command: ${command}`);
switch (command) {
case 'list':
await list();
break;
case 'install':
if (!packageName) {
console.error('Please provide a package name to install');
process.exit(1);
}
} catch (error) {
console.error('Error:', error);
process.exit(1);
}
await install(packageName);
break;
case 'uninstall':
await uninstall(packageName);
break;
case 'installed':
await listInstalledPackages();
break;
default:
console.log('Available commands:');
console.log(' list List all available packages');
console.log(' install <package> Install a package');
console.log(' uninstall [package] Uninstall a package');
console.log(' installed List installed packages');
process.exit(1);
}
}

Expand Down
Loading

0 comments on commit 10edf25

Please sign in to comment.