mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
chore: marketplace wordings, cleanup, and document refresh (#6)
* chore: reword all `PackageManager` => `Marketplace` * chore: document refresh, `mcp-server` => `mcp` * chore: correct word * chore: remove unnecessary condition * chore: remove unnecessary bits * chore: add note for failed test
This commit is contained in:
parent
c0638798d8
commit
563b327007
85 changed files with 379 additions and 2578 deletions
|
|
@ -9,10 +9,10 @@ This directory contains comprehensive documentation for the Roo Code Marketplace
|
|||
The user guide provides end-user documentation for using the Marketplace:
|
||||
|
||||
1. [Introduction to Marketplace](./user-guide/01-introduction.md) - Overview and purpose of the Marketplace
|
||||
2. [Browsing Packages](./user-guide/02-browsing-packages.md) - Understanding the interface and navigating items
|
||||
2. [Browsing Items](./user-guide/02-browsing-items.md) - Understanding the interface and navigating items
|
||||
3. [Searching and Filtering](./user-guide/03-searching-and-filtering.md) - Using search and filters to find items
|
||||
4. [Working with Package Details](./user-guide/04-working-with-details.md) - Exploring package details and subcomponents
|
||||
5. [Adding Packages](./user-guide/05-adding-packages.md) - Creating and contributing your own items and package
|
||||
4. [Working with Package Details](./user-guide/04-working-with-details.md) - Exploring package details and items
|
||||
5. [Adding Packages](./user-guide/05-adding-packages.md) - Creating and contributing your own items
|
||||
6. [Adding Custom Sources](./user-guide/06-adding-custom-sources.md) - Setting up and managing custom sources
|
||||
|
||||
### Implementation Documentation
|
||||
|
|
@ -29,10 +29,10 @@ The implementation documentation provides technical details for developers:
|
|||
The Marketplace provides the following key features:
|
||||
|
||||
- **Component Discovery**: Browse and search for items
|
||||
- **Package Management**: Add packages of items to your environment
|
||||
- **Item Management**: Add/update/remove items to your environment
|
||||
- **Custom Sources**: Add your own repositories of team or private Marketplaces
|
||||
- **Localization Support**: View items in your preferred language
|
||||
- **Filtering**: Filter components by type, search term, and tags
|
||||
- **Filtering**: Filter items by type, search term, and tags
|
||||
|
||||
## Default Marketplace Repository
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ This document provides a comprehensive overview of the Marketplace's architectur
|
|||
|
||||
## System Overview
|
||||
|
||||
The Marketplace is built on a modular architecture that separates concerns between data management, UI rendering, and user interactions. The system consists of several key components that work together to provide a seamless experience for discovering, browsing, and managing packages.
|
||||
The Marketplace is built on a modular architecture that separates concerns between data management, UI rendering, and user interactions. The system consists of several key components that work together to provide a seamless experience for discovering, browsing, and managing items.
|
||||
|
||||
### High-Level Architecture
|
||||
|
||||
|
|
@ -12,8 +12,8 @@ The Marketplace is built on a modular architecture that separates concerns betwe
|
|||
graph TD
|
||||
User[User] -->|Interacts with| UI[Marketplace UI]
|
||||
UI -->|Sends messages| MH[Message Handler]
|
||||
MH -->|Processes requests| PM[PackageManagerManager]
|
||||
PM -->|Validates sources| PSV[PackageManagerSourceValidation]
|
||||
MH -->|Processes requests| PM[MarketplaceManager]
|
||||
PM -->|Validates sources| PSV[MarketplaceSourceValidation]
|
||||
PM -->|Fetches repos| GF[GitFetcher]
|
||||
GF -->|Scans metadata| MS[MetadataScanner]
|
||||
MS -->|Reads| FS[File System / Git Repositories]
|
||||
|
|
@ -38,20 +38,20 @@ The Marketplace components interact through a well-defined message flow:
|
|||
1. **Data Loading**:
|
||||
|
||||
- GitFetcher handles repository cloning and updates
|
||||
- MetadataScanner loads package data from repositories
|
||||
- PackageManagerManager manages caching and concurrency
|
||||
- MetadataScanner loads item data from repositories
|
||||
- MarketplaceManager manages caching and concurrency
|
||||
- UI requests data through the message handler
|
||||
|
||||
2. **Filtering and Search**:
|
||||
|
||||
- UI sends filter/search criteria to the backend
|
||||
- PackageManagerManager applies filters with match info
|
||||
- MarketplaceManager applies filters with match info
|
||||
- Filtered results are returned to the UI
|
||||
- State manager handles view-level filtering
|
||||
|
||||
3. **Source Management**:
|
||||
- UI sends source management commands
|
||||
- PackageManagerManager coordinates with GitFetcher
|
||||
- MarketplaceManager coordinates with GitFetcher
|
||||
- Cache is managed with timeout protection
|
||||
- Sources are processed with concurrency control
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ graph LR
|
|||
subgraph Backend
|
||||
GF[GitFetcher]
|
||||
MS[MetadataScanner]
|
||||
PM[PackageManagerManager]
|
||||
PM[MarketplaceManager]
|
||||
MH[Message Handler]
|
||||
end
|
||||
|
||||
|
|
@ -92,16 +92,16 @@ graph LR
|
|||
|
||||
## Sequence Diagrams
|
||||
|
||||
### Package Loading Sequence
|
||||
### Item Loading Sequence
|
||||
|
||||
The following sequence diagram shows how packages are loaded from sources:
|
||||
The following sequence diagram shows how items are loaded from sources:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant UI as UI Components
|
||||
participant MH as Message Handler
|
||||
participant PM as PackageManagerManager
|
||||
participant PM as MarketplaceManager
|
||||
participant GF as GitFetcher
|
||||
participant MS as MetadataScanner
|
||||
participant FS as File System/Git
|
||||
|
|
@ -118,7 +118,7 @@ sequenceDiagram
|
|||
GF-->>PM: Return repository data
|
||||
PM-->>MH: Return initial items
|
||||
MH-->>UI: Update with items
|
||||
UI-->>User: Display packages
|
||||
UI-->>User: Display items
|
||||
```
|
||||
|
||||
### Search and Filter Sequence
|
||||
|
|
@ -131,7 +131,7 @@ sequenceDiagram
|
|||
participant UI as UI Components
|
||||
participant State as State Manager
|
||||
participant MH as Message Handler
|
||||
participant PM as PackageManagerManager
|
||||
participant PM as MarketplaceManager
|
||||
|
||||
User->>UI: Enter search term
|
||||
UI->>State: Update filters
|
||||
|
|
@ -162,20 +162,20 @@ The following class diagram shows the main classes in the Marketplace system:
|
|||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class PackageManagerManager {
|
||||
-currentItems: PackageManagerItem[]
|
||||
class MarketplaceManager {
|
||||
-currentItems: MarketplaceItem[]
|
||||
-cache: Map
|
||||
-gitFetcher: GitFetcher
|
||||
-activeSourceOperations: Set
|
||||
+getPackageManagerItems(): PackageManagerItem[]
|
||||
+filterItems(filters): PackageManagerItem[]
|
||||
+sortItems(sortBy, order): PackageManagerItem[]
|
||||
+getMarketplaceItems(): MarketplaceItem[]
|
||||
+filterItems(filters): MarketplaceItem[]
|
||||
+sortItems(sortBy, order): MarketplaceItem[]
|
||||
+refreshRepository(url): void
|
||||
-queueOperation(operation): void
|
||||
-validateSources(sources): ValidationError[]
|
||||
}
|
||||
|
||||
class PackageManagerSourceValidation {
|
||||
class MarketplaceSourceValidation {
|
||||
+validateSourceUrl(url): ValidationError[]
|
||||
+validateSourceName(name): ValidationError[]
|
||||
+validateSourceDuplicates(sources): ValidationError[]
|
||||
|
|
@ -187,7 +187,7 @@ classDiagram
|
|||
class GitFetcher {
|
||||
-cacheDir: string
|
||||
-metadataScanner: MetadataScanner
|
||||
+fetchRepository(url): PackageManagerRepository
|
||||
+fetchRepository(url): MarketplaceRepository
|
||||
-cloneOrPullRepository(url): void
|
||||
-validateRepositoryStructure(dir): void
|
||||
-parseRepositoryMetadata(dir): RepositoryMetadata
|
||||
|
|
@ -195,12 +195,12 @@ classDiagram
|
|||
|
||||
class MetadataScanner {
|
||||
-git: SimpleGit
|
||||
+scanDirectory(path): PackageManagerItem[]
|
||||
+scanDirectory(path): MarketplaceItem[]
|
||||
+parseMetadata(file): ComponentMetadata
|
||||
-buildComponentHierarchy(items): PackageManagerItem[]
|
||||
-buildComponentHierarchy(items): MarketplaceItem[]
|
||||
}
|
||||
|
||||
class PackageManagerViewStateManager {
|
||||
class MarketplaceViewStateManager {
|
||||
-state: ViewState
|
||||
-stateChangeHandlers: Set
|
||||
-fetchTimeoutId: NodeJS.Timeout
|
||||
|
|
@ -213,15 +213,15 @@ classDiagram
|
|||
-notifyStateChange(): void
|
||||
-clearFetchTimeout(): void
|
||||
-isFilterActive(): boolean
|
||||
-filterItems(items): PackageManagerItem[]
|
||||
-sortItems(items): PackageManagerItem[]
|
||||
-filterItems(items): MarketplaceItem[]
|
||||
-sortItems(items): MarketplaceItem[]
|
||||
+handleMessage(message): Promise<void>
|
||||
}
|
||||
|
||||
PackageManagerManager --> GitFetcher: uses
|
||||
PackageManagerManager --> PackageManagerSourceValidation: uses
|
||||
MarketplaceManager --> GitFetcher: uses
|
||||
MarketplaceManager --> MarketplaceSourceValidation: uses
|
||||
GitFetcher --> MetadataScanner: uses
|
||||
PackageManagerManager --> PackageManagerViewStateManager: updates
|
||||
MarketplaceManager --> MarketplaceViewStateManager: updates
|
||||
```
|
||||
|
||||
## Component Responsibilities
|
||||
|
|
@ -242,7 +242,7 @@ classDiagram
|
|||
- Builds component hierarchies
|
||||
- Handles file system operations
|
||||
|
||||
3. **PackageManagerManager**
|
||||
3. **MarketplaceManager**
|
||||
|
||||
- Manages concurrent operations
|
||||
- Handles caching with timeout protection
|
||||
|
|
@ -257,7 +257,7 @@ classDiagram
|
|||
|
||||
### Frontend Components
|
||||
|
||||
1. **PackageManagerViewStateManager**
|
||||
1. **MarketplaceViewStateManager**
|
||||
|
||||
- Manages frontend state and backend synchronization
|
||||
- Handles state transitions and message processing
|
||||
|
|
@ -267,7 +267,7 @@ classDiagram
|
|||
- Manages source modification tracking
|
||||
- Provides state change subscriptions
|
||||
|
||||
2. **PackageManagerSourceValidation**
|
||||
2. **MarketplaceSourceValidation**
|
||||
|
||||
- Validates Git repository URLs for any domain
|
||||
- Validates source names and configurations
|
||||
|
|
@ -275,12 +275,13 @@ classDiagram
|
|||
- Provides structured validation errors
|
||||
- Supports multiple Git protocols (HTTPS, SSH, Git)
|
||||
|
||||
3. **PackageManagerItemCard**
|
||||
3. **MarketplaceItemCard**
|
||||
|
||||
- Displays package information
|
||||
- Displays item information
|
||||
- Handles tag interactions
|
||||
- Manages expandable sections
|
||||
- Shows match highlights
|
||||
- Handle item actions.
|
||||
|
||||
4. **ExpandableSection**
|
||||
|
||||
|
|
@ -369,4 +370,4 @@ The Marketplace architecture is designed for extensibility:
|
|||
|
||||
---
|
||||
|
||||
**Previous**: [Adding Custom Package Sources](../user-guide/06-adding-custom-sources.md) | **Next**: [Core Components](./02-core-components.md)
|
||||
**Previous**: [Adding Custom Item Sources](../user-guide/06-adding-custom-sources.md) | **Next**: [Core Components](./02-core-components.md)
|
||||
|
|
|
|||
|
|
@ -16,46 +16,7 @@ The GitFetcher is responsible for managing Git repository operations, including
|
|||
|
||||
### Implementation Details
|
||||
|
||||
```typescript
|
||||
class GitFetcher {
|
||||
private readonly cacheDir: string
|
||||
private metadataScanner: MetadataScanner
|
||||
private git?: SimpleGit
|
||||
|
||||
/**
|
||||
* Fetch repository data
|
||||
* @param repoUrl Repository URL
|
||||
* @param forceRefresh Whether to bypass cache
|
||||
* @param sourceName Optional source name
|
||||
* @returns Repository data
|
||||
*/
|
||||
public async fetchRepository(
|
||||
repoUrl: string,
|
||||
forceRefresh = false,
|
||||
sourceName?: string,
|
||||
): Promise<PackageManagerRepository> {
|
||||
// Implementation details
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone or pull repository
|
||||
* @param repoUrl Repository URL
|
||||
* @param repoDir Repository directory
|
||||
* @param forceRefresh Whether to force refresh
|
||||
*/
|
||||
private async cloneOrPullRepository(repoUrl: string, repoDir: string, forceRefresh: boolean): Promise<void> {
|
||||
// Implementation details
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up git locks
|
||||
* @param repoDir Repository directory
|
||||
*/
|
||||
private async cleanupGitLocks(repoDir: string): Promise<void> {
|
||||
// Implementation details
|
||||
}
|
||||
}
|
||||
```
|
||||
[/src/services/marketplace/GitFetcher.ts](/src/services/marketplace/GitFetcher.ts)
|
||||
|
||||
### Key Algorithms
|
||||
|
||||
|
|
@ -85,11 +46,11 @@ The repository management process includes:
|
|||
|
||||
## MetadataScanner
|
||||
|
||||
The MetadataScanner is responsible for reading and parsing package metadata from repositories.
|
||||
The MetadataScanner is responsible for reading and parsing item metadata from repositories.
|
||||
|
||||
### Responsibilities
|
||||
|
||||
- Scanning directories for package metadata files
|
||||
- Scanning directories for item metadata files
|
||||
- Parsing YAML metadata into structured objects
|
||||
- Building component hierarchies
|
||||
- Supporting localized metadata
|
||||
|
|
@ -97,40 +58,11 @@ The MetadataScanner is responsible for reading and parsing package metadata from
|
|||
|
||||
### Implementation Details
|
||||
|
||||
```typescript
|
||||
class MetadataScanner {
|
||||
private git: SimpleGit
|
||||
private localizationOptions: LocalizationOptions
|
||||
[/src/services/marketplace/MetadataScanner.ts](/src/services/marketplace/MetadataScanner.ts)
|
||||
|
||||
/**
|
||||
* Scan directory for package metadata
|
||||
* @param directoryPath Directory to scan
|
||||
* @param baseUrl Base repository URL
|
||||
* @param sourceName Source repository name
|
||||
* @returns Array of package items
|
||||
*/
|
||||
public async scanDirectory(
|
||||
directoryPath: string,
|
||||
baseUrl?: string,
|
||||
sourceName?: string,
|
||||
): Promise<PackageManagerItem[]> {
|
||||
// Implementation details
|
||||
}
|
||||
## MarketplaceManager
|
||||
|
||||
/**
|
||||
* Parse metadata file
|
||||
* @param filePath Path to metadata file
|
||||
* @returns Parsed metadata
|
||||
*/
|
||||
private async parseMetadataFile(filePath: string): Promise<ComponentMetadata> {
|
||||
// Implementation details
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## PackageManagerManager
|
||||
|
||||
The PackageManagerManager is the central component that manages marketplace data, caching, and operations.
|
||||
The MarketplaceManager is the central component that manages marketplace data, caching, and operations.
|
||||
|
||||
### Responsibilities
|
||||
|
||||
|
|
@ -138,58 +70,11 @@ The PackageManagerManager is the central component that manages marketplace data
|
|||
- Handling repository caching
|
||||
- Coordinating with GitFetcher
|
||||
- Applying filters and sorting
|
||||
- Managing package sources
|
||||
- Managing registry sources
|
||||
|
||||
### Implementation Details
|
||||
|
||||
```typescript
|
||||
class PackageManagerManager {
|
||||
private currentItems: PackageManagerItem[] = []
|
||||
private cache: Map<string, { data: PackageManagerRepository; timestamp: number }>
|
||||
private gitFetcher: GitFetcher
|
||||
private activeSourceOperations = new Set<string>()
|
||||
private isMetadataScanActive = false
|
||||
private pendingOperations: Array<() => Promise<void>> = []
|
||||
|
||||
/**
|
||||
* Queue an operation to run when no metadata scan is active
|
||||
*/
|
||||
private async queueOperation(operation: () => Promise<void>): Promise<void> {
|
||||
// Implementation details
|
||||
}
|
||||
|
||||
/**
|
||||
* Get marketplace items from sources
|
||||
*/
|
||||
public async getPackageManagerItems(
|
||||
sources: PackageManagerSource[],
|
||||
): Promise<{ items: PackageManagerItem[]; errors?: string[] }> {
|
||||
// Implementation details
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter items based on criteria
|
||||
*/
|
||||
public filterItems(
|
||||
items: PackageManagerItem[],
|
||||
filters: { type?: ComponentType; search?: string; tags?: string[] },
|
||||
): PackageManagerItem[] {
|
||||
// Implementation details
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort items by field
|
||||
*/
|
||||
public sortItems(
|
||||
items: PackageManagerItem[],
|
||||
sortBy: keyof Pick<PackageManagerItem, "name" | "author" | "lastUpdated">,
|
||||
sortOrder: "asc" | "desc",
|
||||
sortSubcomponents: boolean = false,
|
||||
): PackageManagerItem[] {
|
||||
// Implementation details
|
||||
}
|
||||
}
|
||||
```
|
||||
[/src/services/marketplace/MarketplaceManager.ts](/src/services/marketplace/MarketplaceManager.ts)
|
||||
|
||||
### Key Algorithms
|
||||
|
||||
|
|
@ -225,7 +110,7 @@ The filtering system provides rich functionality:
|
|||
|
||||
- Filter parent items
|
||||
- Filter subcomponents
|
||||
- Handle package-specific logic
|
||||
- Handle item-specific logic
|
||||
- Track match information
|
||||
|
||||
2. **Match Information**:
|
||||
|
|
@ -234,9 +119,9 @@ The filtering system provides rich functionality:
|
|||
- Support highlighting
|
||||
- Maintain match context
|
||||
|
||||
## PackageManagerSourceValidation
|
||||
## MarketplaceValidation
|
||||
|
||||
The PackageManagerSourceValidation component handles validation of marketplace sources and their configurations.
|
||||
The MarketplaceValidation component handles validation of marketplace sources and their configurations.
|
||||
|
||||
### Responsibilities
|
||||
|
||||
|
|
@ -248,40 +133,7 @@ The PackageManagerSourceValidation component handles validation of marketplace s
|
|||
|
||||
### Implementation Details
|
||||
|
||||
```typescript
|
||||
export class PackageManagerSourceValidation {
|
||||
/**
|
||||
* Validates a marketplace source URL
|
||||
*/
|
||||
public static validateSourceUrl(url: string): ValidationError[] {
|
||||
// Implementation details
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a marketplace source name
|
||||
*/
|
||||
public static validateSourceName(name?: string): ValidationError[] {
|
||||
// Implementation details
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates sources for duplicates
|
||||
*/
|
||||
public static validateSourceDuplicates(
|
||||
sources: PackageManagerSource[],
|
||||
newSource?: PackageManagerSource,
|
||||
): ValidationError[] {
|
||||
// Implementation details
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a URL is a valid Git repository URL
|
||||
*/
|
||||
private static isValidGitRepositoryUrl(url: string): boolean {
|
||||
// Implementation details
|
||||
}
|
||||
}
|
||||
```
|
||||
[/src/shared/MarketplaceValidation.ts](/src/shared/MarketplaceValidation.ts)
|
||||
|
||||
### Key Algorithms
|
||||
|
||||
|
|
@ -309,9 +161,9 @@ The URL validation system supports:
|
|||
- Optional .git suffix
|
||||
- Subpath support
|
||||
|
||||
## PackageManagerViewStateManager
|
||||
## MarketplaceViewStateManager
|
||||
|
||||
The PackageManagerViewStateManager manages frontend state and synchronization with the backend.
|
||||
The MarketplaceViewStateManager manages frontend state and synchronization with the backend.
|
||||
|
||||
### Responsibilities
|
||||
|
||||
|
|
@ -325,42 +177,7 @@ The PackageManagerViewStateManager manages frontend state and synchronization wi
|
|||
|
||||
### Implementation Details
|
||||
|
||||
```typescript
|
||||
class PackageManagerViewStateManager {
|
||||
private state: ViewState
|
||||
private stateChangeHandlers: Set<StateChangeHandler>
|
||||
private fetchTimeoutId?: NodeJS.Timeout
|
||||
private sourcesModified: boolean
|
||||
|
||||
/**
|
||||
* Initialize state manager
|
||||
*/
|
||||
public initialize(): void {
|
||||
// Implementation details
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to state changes
|
||||
*/
|
||||
public onStateChange(handler: StateChangeHandler): () => void {
|
||||
// Implementation details
|
||||
}
|
||||
|
||||
/**
|
||||
* Process state transitions
|
||||
*/
|
||||
public async transition(transition: ViewStateTransition): Promise<void> {
|
||||
// Implementation details
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming messages
|
||||
*/
|
||||
public async handleMessage(message: any): Promise<void> {
|
||||
// Implementation details
|
||||
}
|
||||
}
|
||||
```
|
||||
[/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts](/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts)
|
||||
|
||||
## Component Integration
|
||||
|
||||
|
|
@ -370,15 +187,15 @@ The components work together through well-defined interfaces:
|
|||
|
||||
1. **Repository Operations**:
|
||||
|
||||
- PackageManagerManager validates sources with PackageManagerSourceValidation
|
||||
- PackageManagerManager coordinates with GitFetcher
|
||||
- MarketplaceManager validates sources with MarketplaceValidation
|
||||
- MarketplaceManager coordinates with GitFetcher
|
||||
- GitFetcher manages repository state
|
||||
- MetadataScanner processes repository content
|
||||
- Results flow back to PackageManagerManager
|
||||
- Results flow back to MarketplaceManager
|
||||
|
||||
2. **State Management**:
|
||||
|
||||
- PackageManagerManager maintains backend state
|
||||
- MarketplaceManager maintains backend state
|
||||
- ViewStateManager handles UI state transitions
|
||||
- ViewStateManager processes messages
|
||||
- State changes notify subscribers
|
||||
|
|
|
|||
|
|
@ -2,37 +2,32 @@
|
|||
|
||||
This document details the key data structures used in the Marketplace, including their definitions, relationships, and usage patterns.
|
||||
|
||||
## Package and Component Types
|
||||
## Item Types
|
||||
|
||||
The Marketplace uses a type system to categorize different kinds of components:
|
||||
The Marketplace uses a type system to categorize different kinds of items:
|
||||
|
||||
### ComponentType Enumeration
|
||||
### MarketplaceItemType Enumeration
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Supported component types
|
||||
*/
|
||||
export type ComponentType = "mode" | "prompt" | "package" | "mcp server"
|
||||
```
|
||||
[/src/services/marketplace/types.ts](/src/services/marketplace/types.ts)
|
||||
|
||||
These types represent the different kinds of components that can be managed by the Marketplace:
|
||||
|
||||
1. **mode**: AI assistant personalities with specialized capabilities
|
||||
2. **prompt**: Pre-configured instructions for specific tasks
|
||||
3. **package**: Collections of related components
|
||||
4. **mcp server**: Model Context Protocol servers that provide additional functionality
|
||||
3. **mcp**: Model Context Protocol servers that provide additional functionality
|
||||
4. **package**: Collections of items (multiple modes, mcps,..., like `roo-commander`)
|
||||
|
||||
## Core Data Structures
|
||||
|
||||
### PackageManagerRepository
|
||||
### MarketplaceRepository
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Represents a repository with its metadata and items
|
||||
*/
|
||||
export interface PackageManagerRepository {
|
||||
export interface MarketplaceRepository {
|
||||
metadata: RepositoryMetadata
|
||||
items: PackageManagerItem[]
|
||||
items: MarketplaceItem[]
|
||||
url: string
|
||||
defaultBranch: string
|
||||
error?: string
|
||||
|
|
@ -47,35 +42,9 @@ This interface represents a complete repository:
|
|||
- **defaultBranch**: The default Git branch (e.g., "main")
|
||||
- **error**: Optional error message if there was a problem
|
||||
|
||||
### PackageManagerItem
|
||||
### MarketplaceItem
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Represents an individual marketplace item
|
||||
*/
|
||||
export interface PackageManagerItem {
|
||||
name: string
|
||||
description: string
|
||||
type: ComponentType
|
||||
url: string
|
||||
repoUrl: string
|
||||
sourceName?: string
|
||||
author?: string
|
||||
tags?: string[]
|
||||
version?: string
|
||||
lastUpdated?: string
|
||||
sourceUrl?: string
|
||||
defaultBranch?: string
|
||||
items?: {
|
||||
type: ComponentType
|
||||
path: string
|
||||
metadata?: ComponentMetadata
|
||||
lastUpdated?: string
|
||||
matchInfo?: MatchInfo
|
||||
}[]
|
||||
matchInfo?: MatchInfo
|
||||
}
|
||||
```
|
||||
[/src/services/marketplace/types.ts](/src/services/marketplace/types.ts)
|
||||
|
||||
Key changes:
|
||||
|
||||
|
|
@ -85,21 +54,7 @@ Key changes:
|
|||
|
||||
### MatchInfo
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Information about why an item matched search/filter criteria
|
||||
*/
|
||||
export interface MatchInfo {
|
||||
matched: boolean
|
||||
matchReason?: {
|
||||
nameMatch?: boolean
|
||||
descriptionMatch?: boolean
|
||||
typeMatch?: boolean
|
||||
tagMatch?: boolean
|
||||
hasMatchingSubcomponents?: boolean
|
||||
}
|
||||
}
|
||||
```
|
||||
[/src/services/marketplace/types.ts](/src/services/marketplace/types.ts)
|
||||
|
||||
Enhanced match tracking:
|
||||
|
||||
|
|
@ -111,15 +66,7 @@ Enhanced match tracking:
|
|||
|
||||
### ValidationError
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Error type for marketplace source validation
|
||||
*/
|
||||
export interface ValidationError {
|
||||
field: string
|
||||
message: string
|
||||
}
|
||||
```
|
||||
[/src/shared/MarketplaceValidation.ts](/src/shared/MarketplaceValidation.ts)
|
||||
|
||||
Used for structured validation errors:
|
||||
|
||||
|
|
@ -128,24 +75,7 @@ Used for structured validation errors:
|
|||
|
||||
### ViewState
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* View-level state management
|
||||
*/
|
||||
interface ViewState {
|
||||
allItems: PackageManagerItem[]
|
||||
displayItems?: PackageManagerItem[]
|
||||
isFetching: boolean
|
||||
activeTab: "browse" | "sources"
|
||||
refreshingUrls: string[]
|
||||
sources: PackageManagerSource[]
|
||||
filters: Filters
|
||||
sortConfig: {
|
||||
by: "name" | "author" | "lastUpdated"
|
||||
order: "asc" | "desc"
|
||||
}
|
||||
}
|
||||
```
|
||||
[/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts](/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts)
|
||||
|
||||
Manages UI state:
|
||||
|
||||
|
|
@ -160,31 +90,7 @@ Manages UI state:
|
|||
|
||||
### ViewStateTransition
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* State transition types and payloads
|
||||
*/
|
||||
type ViewStateTransition = {
|
||||
type:
|
||||
| "FETCH_ITEMS"
|
||||
| "FETCH_COMPLETE"
|
||||
| "FETCH_ERROR"
|
||||
| "SET_ACTIVE_TAB"
|
||||
| "UPDATE_FILTERS"
|
||||
| "UPDATE_SORT"
|
||||
| "REFRESH_SOURCE"
|
||||
| "REFRESH_SOURCE_COMPLETE"
|
||||
| "UPDATE_SOURCES"
|
||||
payload?: {
|
||||
items?: PackageManagerItem[]
|
||||
tab?: "browse" | "sources"
|
||||
filters?: Partial<Filters>
|
||||
sortConfig?: Partial<SortConfig>
|
||||
url?: string
|
||||
sources?: PackageManagerSource[]
|
||||
}
|
||||
}
|
||||
```
|
||||
[/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts](/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts)
|
||||
|
||||
Defines state transitions:
|
||||
|
||||
|
|
@ -194,16 +100,7 @@ Defines state transitions:
|
|||
|
||||
### Filters
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Filter criteria
|
||||
*/
|
||||
interface Filters {
|
||||
type: string
|
||||
search: string
|
||||
tags: string[]
|
||||
}
|
||||
```
|
||||
[/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts](/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts)
|
||||
|
||||
Enhanced filtering:
|
||||
|
||||
|
|
@ -215,17 +112,7 @@ Enhanced filtering:
|
|||
|
||||
### BaseMetadata
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Base metadata interface
|
||||
*/
|
||||
export interface BaseMetadata {
|
||||
name: string
|
||||
description: string
|
||||
version: string
|
||||
tags?: string[]
|
||||
}
|
||||
```
|
||||
[/src/services/marketplace/types.ts](/src/services/marketplace/types.ts)
|
||||
|
||||
Common metadata properties:
|
||||
|
||||
|
|
@ -236,211 +123,39 @@ Common metadata properties:
|
|||
|
||||
### ComponentMetadata
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Component metadata with type
|
||||
*/
|
||||
export interface ComponentMetadata extends BaseMetadata {
|
||||
type: ComponentType
|
||||
lastUpdated?: string
|
||||
}
|
||||
```
|
||||
[/src/services/marketplace/types.ts](/src/services/marketplace/types.ts)
|
||||
|
||||
Added:
|
||||
|
||||
- **lastUpdated** field for tracking changes
|
||||
- **type** field for item component type
|
||||
|
||||
### PackageMetadata
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Package metadata with subcomponents
|
||||
*/
|
||||
export interface PackageMetadata extends ComponentMetadata {
|
||||
type: "package"
|
||||
items?: {
|
||||
type: ComponentType
|
||||
path: string
|
||||
metadata?: ComponentMetadata
|
||||
lastUpdated?: string
|
||||
}[]
|
||||
}
|
||||
```
|
||||
[/src/services/marketplace/types.ts](/src/services/marketplace/types.ts)
|
||||
|
||||
Enhanced with:
|
||||
|
||||
- Subcomponent tracking
|
||||
- Last update timestamps
|
||||
|
||||
## Source Management
|
||||
|
||||
### PackageManagerSource
|
||||
### MarketplaceSource
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Git repository source
|
||||
*/
|
||||
export interface PackageManagerSource {
|
||||
url: string
|
||||
name?: string
|
||||
enabled: boolean
|
||||
}
|
||||
```
|
||||
|
||||
Repository source configuration:
|
||||
|
||||
- **url**: Git repository URL
|
||||
- **name**: Optional display name
|
||||
- **enabled**: Source status
|
||||
|
||||
### SourceOperation
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Source operation tracking
|
||||
*/
|
||||
interface SourceOperation {
|
||||
url: string
|
||||
type: "clone" | "pull" | "refresh"
|
||||
timestamp: number
|
||||
}
|
||||
```
|
||||
|
||||
Tracks repository operations:
|
||||
|
||||
- Operation type
|
||||
- Timestamp
|
||||
- Source URL
|
||||
|
||||
## Cache Management
|
||||
|
||||
### CacheEntry
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Cache entry structure
|
||||
*/
|
||||
interface CacheEntry<T> {
|
||||
data: T
|
||||
timestamp: number
|
||||
}
|
||||
```
|
||||
|
||||
Generic cache structure:
|
||||
|
||||
- Cached data
|
||||
- Timestamp for expiry
|
||||
|
||||
### RepositoryCache
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Repository cache management
|
||||
*/
|
||||
type RepositoryCache = Map<string, CacheEntry<PackageManagerRepository>>
|
||||
```
|
||||
|
||||
Specialized for repositories:
|
||||
|
||||
- URL-based lookup
|
||||
- Timestamp-based expiry
|
||||
- Full repository data
|
||||
[/src/services/marketplace/types.ts](/src/services/marketplace/types.ts)
|
||||
|
||||
## Message Structures
|
||||
|
||||
### Input Messages
|
||||
|
||||
```typescript
|
||||
type PackageManagerMessage =
|
||||
| { type: "getItems" }
|
||||
| {
|
||||
type: "search"
|
||||
search: string
|
||||
typeFilter: string
|
||||
tagFilters: string[]
|
||||
}
|
||||
| {
|
||||
type: "addSource"
|
||||
url: string
|
||||
name?: string
|
||||
}
|
||||
| {
|
||||
type: "removeSource"
|
||||
url: string
|
||||
}
|
||||
| { type: "refreshSources" }
|
||||
```
|
||||
|
||||
### Output Messages
|
||||
|
||||
```typescript
|
||||
type PackageManagerResponse =
|
||||
| {
|
||||
type: "items"
|
||||
data: PackageManagerItem[]
|
||||
}
|
||||
| {
|
||||
type: "searchResults"
|
||||
data: PackageManagerItem[]
|
||||
filters: Filters
|
||||
}
|
||||
| {
|
||||
type: "sourceAdded" | "sourceRemoved"
|
||||
data: { success: boolean }
|
||||
}
|
||||
| {
|
||||
type: "error"
|
||||
error: string
|
||||
}
|
||||
```
|
||||
|
||||
Enhanced with:
|
||||
|
||||
- Filter state in search results
|
||||
- Operation success tracking
|
||||
- Detailed error reporting
|
||||
> TBA
|
||||
|
||||
## Data Validation
|
||||
|
||||
### Metadata Validation
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Validate component metadata
|
||||
*/
|
||||
function validateMetadata(metadata: unknown): metadata is ComponentMetadata {
|
||||
if (!isObject(metadata)) return false
|
||||
|
||||
return (
|
||||
typeof metadata.name === "string" &&
|
||||
typeof metadata.description === "string" &&
|
||||
typeof metadata.version === "string" &&
|
||||
(metadata.tags === undefined || Array.isArray(metadata.tags)) &&
|
||||
isValidComponentType(metadata.type)
|
||||
)
|
||||
}
|
||||
```
|
||||
[/src/services/marketplace/schemas.ts](/src/services/marketplace/schemas.ts)
|
||||
|
||||
### URL Validation
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Validate Git repository URL
|
||||
*/
|
||||
function isValidGitRepositoryUrl(url: string): boolean {
|
||||
// HTTPS pattern (any domain)
|
||||
const httpsPattern =
|
||||
/^https?:\/\/[a-zA-Z0-9_.-]+(\.[a-zA-Z0-9_.-]+)*\/[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+(\/.+)*(\.git)?$/
|
||||
|
||||
// SSH pattern (any domain)
|
||||
const sshPattern = /^git@[a-zA-Z0-9_.-]+(\.[a-zA-Z0-9_.-]+)*:([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)(\.git)?$/
|
||||
|
||||
// Git protocol pattern (any domain)
|
||||
const gitProtocolPattern = /^git:\/\/[a-zA-Z0-9_.-]+(\.[a-zA-Z0-9_.-]+)*\/[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+(\.git)?$/
|
||||
|
||||
return httpsPattern.test(url) || sshPattern.test(url) || gitProtocolPattern.test(url)
|
||||
}
|
||||
```
|
||||
[/src/shared/MarketplaceValidation.ts](/src/shared/MarketplaceValidation.ts)
|
||||
|
||||
Supports:
|
||||
|
||||
|
|
@ -481,15 +196,15 @@ Repository
|
|||
└── Items
|
||||
├── Package
|
||||
│ ├── Mode
|
||||
│ ├── MCP Server
|
||||
│ ├── MCP
|
||||
│ └── Prompt
|
||||
└── Standalone Components
|
||||
└── Standalone Components (Modes, MCP, Prompts)
|
||||
```
|
||||
|
||||
### State Flow
|
||||
|
||||
```
|
||||
Git Repository → Cache → PackageManager → ViewState → UI
|
||||
Git Repository → Cache → Marketplace → ViewState → UI
|
||||
```
|
||||
|
||||
### Filter Chain
|
||||
|
|
|
|||
|
|
@ -8,271 +8,25 @@ The Marketplace implements a comprehensive filtering system that handles multipl
|
|||
|
||||
### Filter Implementation
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Filter items based on criteria with match tracking
|
||||
*/
|
||||
export function filterItems(
|
||||
items: PackageManagerItem[],
|
||||
filters: {
|
||||
type?: ComponentType
|
||||
search?: string
|
||||
tags?: string[]
|
||||
},
|
||||
): PackageManagerItem[] {
|
||||
// Helper function to normalize text for case-insensitive comparison
|
||||
const normalizeText = (text: string) => text.toLowerCase().replace(/\s+/g, " ").trim()
|
||||
|
||||
// Normalize search term once
|
||||
const searchTerm = filters.search ? normalizeText(filters.search) : ""
|
||||
|
||||
// Create a deep clone of items
|
||||
const clonedItems = items.map((item) => JSON.parse(JSON.stringify(item)) as PackageManagerItem)
|
||||
|
||||
return clonedItems
|
||||
.filter((item) => {
|
||||
// Check parent item matches
|
||||
const itemMatches = {
|
||||
type: !filters.type || item.type === filters.type,
|
||||
search:
|
||||
!searchTerm ||
|
||||
containsSearchTerm(item.name, searchTerm) ||
|
||||
containsSearchTerm(item.description, searchTerm),
|
||||
tags: !filters.tags?.length || (item.tags && filters.tags.some((tag) => item.tags!.includes(tag))),
|
||||
}
|
||||
|
||||
// Check subcomponent matches
|
||||
const subcomponentMatches =
|
||||
item.items?.some((subItem) => {
|
||||
const subMatches = {
|
||||
type: !filters.type || subItem.type === filters.type,
|
||||
search:
|
||||
!searchTerm ||
|
||||
(subItem.metadata &&
|
||||
(containsSearchTerm(subItem.metadata.name, searchTerm) ||
|
||||
containsSearchTerm(subItem.metadata.description, searchTerm))),
|
||||
tags:
|
||||
!filters.tags?.length ||
|
||||
(subItem.metadata?.tags &&
|
||||
filters.tags.some((tag) => subItem.metadata!.tags!.includes(tag))),
|
||||
}
|
||||
|
||||
return (
|
||||
subMatches.type &&
|
||||
(!searchTerm || subMatches.search) &&
|
||||
(!filters.tags?.length || subMatches.tags)
|
||||
)
|
||||
}) ?? false
|
||||
|
||||
// Include item if either parent matches or has matching subcomponents
|
||||
const hasActiveFilters = filters.type || searchTerm || filters.tags?.length
|
||||
if (!hasActiveFilters) return true
|
||||
|
||||
const parentMatchesAll = itemMatches.type && itemMatches.search && itemMatches.tags
|
||||
const isPackageWithMatchingSubcomponent = item.type === "package" && subcomponentMatches
|
||||
|
||||
return parentMatchesAll || isPackageWithMatchingSubcomponent
|
||||
})
|
||||
.map((item) => addMatchInfo(item, filters))
|
||||
}
|
||||
```
|
||||
|
||||
### Match Tracking
|
||||
|
||||
The system tracks detailed match information:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Add match information to items
|
||||
*/
|
||||
function addMatchInfo(item: PackageManagerItem, filters: Filters): PackageManagerItem {
|
||||
const matchReason: Record<string, boolean> = {
|
||||
nameMatch: filters.search ? containsSearchTerm(item.name, filters.search) : true,
|
||||
descriptionMatch: filters.search ? containsSearchTerm(item.description, filters.search) : true,
|
||||
typeMatch: filters.type ? item.type === filters.type : true,
|
||||
tagMatch: filters.tags?.length ? hasMatchingTags(item.tags, filters.tags) : true,
|
||||
}
|
||||
|
||||
// Process subcomponents
|
||||
if (item.items) {
|
||||
item.items = item.items.map((subItem) => {
|
||||
const subMatches = {
|
||||
type: !filters.type || subItem.type === filters.type,
|
||||
search:
|
||||
!filters.search ||
|
||||
(subItem.metadata &&
|
||||
(containsSearchTerm(subItem.metadata.name, filters.search) ||
|
||||
containsSearchTerm(subItem.metadata.description, filters.search))),
|
||||
tags:
|
||||
!filters.tags?.length ||
|
||||
(subItem.metadata?.tags && filters.tags.some((tag) => subItem.metadata!.tags!.includes(tag))),
|
||||
}
|
||||
|
||||
subItem.matchInfo = {
|
||||
matched: subMatches.type && subMatches.search && subMatches.tags,
|
||||
matchReason: {
|
||||
typeMatch: subMatches.type,
|
||||
nameMatch: subMatches.search,
|
||||
tagMatch: subMatches.tags,
|
||||
},
|
||||
}
|
||||
|
||||
return subItem
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
matchInfo: {
|
||||
matched: Object.values(matchReason).every(Boolean),
|
||||
matchReason,
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
[/src/services/marketplace/MarketplaceManager.ts](/src/services/marketplace/MarketplaceManager.ts)
|
||||
|
||||
## Sort System
|
||||
|
||||
The Marketplace implements flexible sorting with subcomponent support:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Sort items with subcomponent support
|
||||
*/
|
||||
export function sortItems(
|
||||
items: PackageManagerItem[],
|
||||
sortBy: "name" | "lastUpdated" | "author",
|
||||
sortOrder: "asc" | "desc",
|
||||
sortSubcomponents: boolean = false,
|
||||
): PackageManagerItem[] {
|
||||
return [...items]
|
||||
.map((item) => {
|
||||
const clonedItem = { ...item }
|
||||
|
||||
if (clonedItem.items && sortSubcomponents) {
|
||||
clonedItem.items = [...clonedItem.items].sort((a, b) => {
|
||||
const aValue = getSortValue(a, sortBy)
|
||||
const bValue = getSortValue(b, sortBy)
|
||||
return compareValues(aValue, bValue, sortOrder)
|
||||
})
|
||||
}
|
||||
|
||||
return clonedItem
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const aValue = getSortValue(a, sortBy)
|
||||
const bValue = getSortValue(b, sortBy)
|
||||
return compareValues(aValue, bValue, sortOrder)
|
||||
})
|
||||
}
|
||||
```
|
||||
[/src/services/marketplace/MarketplaceManager.ts](/src/services/marketplace/MarketplaceManager.ts)
|
||||
|
||||
## State Management Integration
|
||||
|
||||
The filtering system integrates with the state management through state transitions:
|
||||
|
||||
```typescript
|
||||
export class PackageManagerViewStateManager {
|
||||
private state: ViewState
|
||||
private stateChangeHandlers: Set<StateChangeHandler>
|
||||
|
||||
/**
|
||||
* Process state transitions
|
||||
*/
|
||||
public async transition(transition: ViewStateTransition): Promise<void> {
|
||||
switch (transition.type) {
|
||||
case "UPDATE_FILTERS": {
|
||||
const { filters = {} } = transition.payload || {}
|
||||
|
||||
// Update filters while preserving existing ones
|
||||
const updatedFilters = {
|
||||
type: filters.type ?? this.state.filters.type,
|
||||
search: filters.search ?? this.state.filters.search,
|
||||
tags: filters.tags ?? this.state.filters.tags,
|
||||
}
|
||||
|
||||
// Update state
|
||||
this.state = {
|
||||
...this.state,
|
||||
filters: updatedFilters,
|
||||
}
|
||||
|
||||
// Notify subscribers
|
||||
this.notifyStateChange()
|
||||
|
||||
// Request filtered items from backend
|
||||
vscode.postMessage({
|
||||
type: "filterPackageManagerItems",
|
||||
filters: updatedFilters,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case "FETCH_COMPLETE": {
|
||||
const { items } = transition.payload as { items: PackageManagerItem[] }
|
||||
|
||||
// Update both all items and display items
|
||||
this.state = {
|
||||
...this.state,
|
||||
allItems: items,
|
||||
displayItems: items,
|
||||
isFetching: false,
|
||||
}
|
||||
|
||||
this.notifyStateChange()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to state changes
|
||||
*/
|
||||
public onStateChange(handler: StateChangeHandler): () => void {
|
||||
this.stateChangeHandlers.add(handler)
|
||||
return () => this.stateChangeHandlers.delete(handler)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
````
|
||||
[/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts](/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts)
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
### Concurrent Operation Handling
|
||||
|
||||
```typescript
|
||||
export class PackageManagerManager {
|
||||
private isMetadataScanActive = false
|
||||
private pendingOperations: Array<() => Promise<void>> = []
|
||||
|
||||
/**
|
||||
* Queue filter operations during active scans
|
||||
*/
|
||||
private async queueOperation(operation: () => Promise<void>): Promise<void> {
|
||||
if (this.isMetadataScanActive) {
|
||||
return new Promise((resolve) => {
|
||||
this.pendingOperations.push(async () => {
|
||||
await operation()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
this.isMetadataScanActive = true
|
||||
await operation()
|
||||
} finally {
|
||||
this.isMetadataScanActive = false
|
||||
|
||||
const nextOperation = this.pendingOperations.shift()
|
||||
if (nextOperation) {
|
||||
void this.queueOperation(nextOperation)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
[/src/services/marketplace/MarketplaceManager.ts](/src/services/marketplace/MarketplaceManager.ts)
|
||||
|
||||
### Filter Optimizations
|
||||
|
||||
|
|
@ -307,7 +61,7 @@ describe("Filter System", () => {
|
|||
})
|
||||
|
||||
it("should track subcomponent matches", () => {
|
||||
const result = filterItems([testPackage], { search: "test" })
|
||||
const result = filterItems([testPack], { search: "test" })
|
||||
const subItem = result[0].items![0]
|
||||
expect(subItem.matchInfo.matched).toBe(true)
|
||||
})
|
||||
|
|
@ -315,7 +69,7 @@ describe("Filter System", () => {
|
|||
|
||||
describe("Sort System", () => {
|
||||
it("should sort subcomponents", () => {
|
||||
const result = sortItems([testPackage], "name", "asc", true)
|
||||
const result = sortItems([testPack], "name", "asc", true)
|
||||
expect(result[0].items).toBeSorted((a, b) => a.metadata.name.localeCompare(b.metadata.name))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,92 +2,17 @@
|
|||
|
||||
This document details the design and implementation of the Marketplace's UI components, including their structure, styling, interactions, and accessibility features.
|
||||
|
||||
## PackageManagerView
|
||||
## MarketplaceView
|
||||
|
||||
The PackageManagerView is the main container component that manages the overall marketplace interface.
|
||||
The MarketplaceView is the main container component that manages the overall marketplace interface.
|
||||
|
||||
### Component Structure
|
||||
|
||||
```tsx
|
||||
const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||
const [state, manager] = useStateManager()
|
||||
const [tagSearch, setTagSearch] = useState("")
|
||||
const [isTagInputActive, setIsTagInputActive] = useState(false)
|
||||
|
||||
// Fetch items on mount
|
||||
useEffect(() => {
|
||||
manager.transition({ type: "FETCH_ITEMS" })
|
||||
}, [manager])
|
||||
|
||||
return (
|
||||
<Tab>
|
||||
<TabHeader>
|
||||
<div className="flex justify-between items-center">
|
||||
<h3>Marketplace</h3>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant={state.activeTab === "browse" ? "default" : "secondary"}
|
||||
onClick={() =>
|
||||
manager.transition({
|
||||
type: "SET_ACTIVE_TAB",
|
||||
payload: { tab: "browse" },
|
||||
})
|
||||
}>
|
||||
Browse
|
||||
</Button>
|
||||
<Button
|
||||
variant={state.activeTab === "sources" ? "default" : "secondary"}
|
||||
onClick={() =>
|
||||
manager.transition({
|
||||
type: "SET_ACTIVE_TAB",
|
||||
payload: { tab: "sources" },
|
||||
})
|
||||
}>
|
||||
Sources
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</TabHeader>
|
||||
|
||||
<TabContent>
|
||||
{state.activeTab === "browse" ? (
|
||||
<BrowseView
|
||||
items={state.displayItems || []}
|
||||
filters={state.filters}
|
||||
onUpdateFilters={(filters) =>
|
||||
manager.transition({
|
||||
type: "UPDATE_FILTERS",
|
||||
payload: { filters },
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<SourcesView
|
||||
sources={state.sources}
|
||||
refreshingUrls={state.refreshingUrls}
|
||||
onRefreshSource={(url) =>
|
||||
manager.transition({
|
||||
type: "REFRESH_SOURCE",
|
||||
payload: { url },
|
||||
})
|
||||
}
|
||||
onSourcesChange={(sources) =>
|
||||
manager.transition({
|
||||
type: "UPDATE_SOURCES",
|
||||
payload: { sources },
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</TabContent>
|
||||
</Tab>
|
||||
)
|
||||
}
|
||||
```
|
||||
[/webview-ui/src/components/marketplace/MarketplaceView.tsx](/webview-ui/src/components/marketplace/MarketplaceView.tsx)
|
||||
|
||||
### State Management Integration
|
||||
|
||||
The component uses the PackageManagerViewStateManager through the useStateManager hook:
|
||||
The component uses the MarketplaceViewStateManager through the useStateManager hook:
|
||||
|
||||
```tsx
|
||||
const [state, manager] = useStateManager()
|
||||
|
|
@ -101,200 +26,13 @@ Key features:
|
|||
- Manages loading states
|
||||
- Handles source validation
|
||||
|
||||
## PackageManagerItemCard
|
||||
## MarketplaceItemCard
|
||||
|
||||
The PackageManagerItemCard is the primary component for displaying package information in the UI.
|
||||
The MarketplaceItemCard is the primary component for displaying item information in the UI.
|
||||
|
||||
### Component Structure
|
||||
|
||||
```tsx
|
||||
export const PackageManagerItemCard: React.FC<PackageManagerItemCardProps> = ({
|
||||
item,
|
||||
filters,
|
||||
setFilters,
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
}) => {
|
||||
// URL validation helper
|
||||
const isValidUrl = (urlString: string): boolean => {
|
||||
try {
|
||||
new URL(urlString)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Type label and color helpers
|
||||
const getTypeLabel = (type: string) => {
|
||||
switch (type) {
|
||||
case "mode":
|
||||
return "Mode"
|
||||
case "mcp server":
|
||||
return "MCP Server"
|
||||
case "prompt":
|
||||
return "Prompt"
|
||||
case "package":
|
||||
return "Package"
|
||||
default:
|
||||
return "Other"
|
||||
}
|
||||
}
|
||||
|
||||
const getTypeColor = (type: string) => {
|
||||
switch (type) {
|
||||
case "mode":
|
||||
return "bg-blue-600"
|
||||
case "mcp server":
|
||||
return "bg-green-600"
|
||||
case "prompt":
|
||||
return "bg-purple-600"
|
||||
case "package":
|
||||
return "bg-orange-600"
|
||||
default:
|
||||
return "bg-gray-600"
|
||||
}
|
||||
}
|
||||
|
||||
// URL opening handler
|
||||
const handleOpenUrl = () => {
|
||||
const urlToOpen = item.sourceUrl && isValidUrl(item.sourceUrl) ? item.sourceUrl : item.repoUrl
|
||||
vscode.postMessage({
|
||||
type: "openExternal",
|
||||
url: urlToOpen,
|
||||
})
|
||||
}
|
||||
|
||||
// Group items by type
|
||||
const groupedItems = useMemo(() => {
|
||||
if (!item.items?.length) {
|
||||
return null
|
||||
}
|
||||
return groupItemsByType(item.items)
|
||||
}, [item.items]) as GroupedItems | null
|
||||
|
||||
return (
|
||||
<div className="border border-vscode-panel-border rounded-md p-4 bg-vscode-panel-background">
|
||||
{/* Header section with name, author, and type badge */}
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-vscode-foreground">{item.name}</h3>
|
||||
{item.author && <p className="text-sm text-vscode-descriptionForeground">{`by ${item.author}`}</p>}
|
||||
</div>
|
||||
<span className={`px-2 py-1 text-xs text-white rounded-full ${getTypeColor(item.type)}`}>
|
||||
{getTypeLabel(item.type)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="my-2 text-vscode-foreground">{item.description}</p>
|
||||
|
||||
{/* Tags section */}
|
||||
{item.tags && item.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 my-2">
|
||||
{item.tags.map((tag) => (
|
||||
<button
|
||||
key={tag}
|
||||
className={`px-2 py-1 text-xs rounded-full hover:bg-vscode-button-secondaryBackground ${
|
||||
filters.tags.includes(tag)
|
||||
? "bg-vscode-button-background text-vscode-button-foreground"
|
||||
: "bg-vscode-badge-background text-vscode-badge-foreground"
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (filters.tags.includes(tag)) {
|
||||
setFilters({
|
||||
...filters,
|
||||
tags: filters.tags.filter((t) => t !== tag),
|
||||
})
|
||||
} else {
|
||||
setFilters({
|
||||
...filters,
|
||||
tags: [...filters.tags, tag],
|
||||
})
|
||||
if (activeTab !== "browse") {
|
||||
setActiveTab("browse")
|
||||
}
|
||||
}
|
||||
}}
|
||||
title={filters.tags.includes(tag) ? `Remove tag filter: ${tag}` : `Filter by tag: ${tag}`}>
|
||||
{tag}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Footer section with metadata and action button */}
|
||||
<div className="flex justify-between items-center mt-4">
|
||||
<div className="flex items-center gap-4 text-sm text-vscode-descriptionForeground">
|
||||
{item.version && (
|
||||
<span className="flex items-center">
|
||||
<span className="codicon codicon-tag mr-1"></span>
|
||||
{item.version}
|
||||
</span>
|
||||
)}
|
||||
{item.lastUpdated && (
|
||||
<span className="flex items-center">
|
||||
<span className="codicon codicon-calendar mr-1"></span>
|
||||
{new Date(item.lastUpdated).toLocaleDateString(undefined, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button onClick={handleOpenUrl}>
|
||||
<span className="codicon codicon-link-external mr-2"></span>
|
||||
{item.sourceUrl ? "View" : item.sourceName || "Source"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Details section with subcomponents */}
|
||||
{groupedItems && (
|
||||
<ExpandableSection
|
||||
title="Component Details"
|
||||
badge={
|
||||
filters.search
|
||||
? (() => {
|
||||
const matchCount =
|
||||
item.items?.filter(
|
||||
(subItem) =>
|
||||
(subItem.metadata?.name || "")
|
||||
.toLowerCase()
|
||||
.includes(filters.search.toLowerCase()) ||
|
||||
(subItem.metadata?.description || "")
|
||||
.toLowerCase()
|
||||
.includes(filters.search.toLowerCase()),
|
||||
).length || 0
|
||||
return matchCount > 0
|
||||
? `${matchCount} match${matchCount !== 1 ? "es" : ""}`
|
||||
: undefined
|
||||
})()
|
||||
: undefined
|
||||
}
|
||||
defaultExpanded={
|
||||
!!filters.search &&
|
||||
(item.items?.some(
|
||||
(subItem) =>
|
||||
(subItem.metadata?.name || "").toLowerCase().includes(filters.search.toLowerCase()) ||
|
||||
(subItem.metadata?.description || "")
|
||||
.toLowerCase()
|
||||
.includes(filters.search.toLowerCase()),
|
||||
) ||
|
||||
false)
|
||||
}>
|
||||
<div className="space-y-4">
|
||||
{Object.entries(groupedItems).map(([type, group]) => (
|
||||
<TypeGroup key={type} type={type} items={group.items} searchTerm={filters.search} />
|
||||
))}
|
||||
</div>
|
||||
</ExpandableSection>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
[/webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx](/webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx)
|
||||
|
||||
### Design Considerations
|
||||
|
||||
|
|
@ -327,56 +65,7 @@ The ExpandableSection component provides a collapsible container for content tha
|
|||
|
||||
### Component Structure
|
||||
|
||||
```tsx
|
||||
export const ExpandableSection: React.FC<ExpandableSectionProps> = ({
|
||||
title,
|
||||
children,
|
||||
className,
|
||||
defaultExpanded = false,
|
||||
badge,
|
||||
}) => {
|
||||
const [isExpanded, setIsExpanded] = useState(defaultExpanded)
|
||||
|
||||
return (
|
||||
<div className={cn("border-t border-vscode-panel-border mt-4", className)}>
|
||||
<button
|
||||
className="w-full flex items-center justify-between py-2 text-sm text-vscode-foreground hover:text-vscode-textLink"
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
aria-expanded={isExpanded}
|
||||
aria-controls="details-content">
|
||||
<span className="font-medium flex items-center">
|
||||
<span className="codicon codicon-list-unordered mr-1"></span>
|
||||
{title}
|
||||
</span>
|
||||
<div className="flex items-center">
|
||||
{badge && (
|
||||
<span className="mr-2 text-xs bg-vscode-badge-background text-vscode-badge-foreground px-1 py-0.5 rounded">
|
||||
{badge}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"codicon",
|
||||
isExpanded ? "codicon-chevron-down" : "codicon-chevron-right",
|
||||
"transition-transform duration-200",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
id="details-content"
|
||||
className={cn(
|
||||
"overflow-hidden transition-[max-height,opacity] duration-200 ease-in-out",
|
||||
isExpanded ? "max-h-[500px] opacity-100" : "max-h-0 opacity-0",
|
||||
)}
|
||||
role="region"
|
||||
aria-labelledby="details-button">
|
||||
<div className="py-2 px-1 bg-vscode-panel-background rounded-sm">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
[/webview-ui/src/components/marketplace/components/ExpandableSection.tsx](/webview-ui/src/components/marketplace/components/ExpandableSection.tsx)
|
||||
|
||||
### Design Considerations
|
||||
|
||||
|
|
@ -409,67 +98,7 @@ The TypeGroup component displays a collection of items of the same type, with sp
|
|||
|
||||
### Component Structure
|
||||
|
||||
```tsx
|
||||
export const TypeGroup: React.FC<TypeGroupProps> = ({ type, items, className, searchTerm }) => {
|
||||
const getTypeLabel = (type: string) => {
|
||||
switch (type) {
|
||||
case "mode":
|
||||
return "Modes"
|
||||
case "mcp server":
|
||||
return "MCP Servers"
|
||||
case "prompt":
|
||||
return "Prompts"
|
||||
case "package":
|
||||
return "Packages"
|
||||
default:
|
||||
return `${type.charAt(0).toUpperCase()}${type.slice(1)}s`
|
||||
}
|
||||
}
|
||||
|
||||
if (!items?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Check if an item matches the search term
|
||||
const itemMatchesSearch = (item: { name: string; description?: string }) => {
|
||||
if (!searchTerm) return false
|
||||
const term = searchTerm.toLowerCase()
|
||||
return item.name.toLowerCase().includes(term) || (item.description || "").toLowerCase().includes(term)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("mb-4", className)}>
|
||||
<h4 className="text-sm font-medium text-vscode-foreground mb-2">{getTypeLabel(type)}</h4>
|
||||
<ol className="list-decimal list-inside space-y-1">
|
||||
{items.map((item, index) => {
|
||||
const matches = itemMatchesSearch(item)
|
||||
return (
|
||||
<li
|
||||
key={`${item.path || index}`}
|
||||
className={cn(
|
||||
"text-sm pl-1",
|
||||
matches ? "text-vscode-foreground font-medium" : "text-vscode-foreground",
|
||||
)}
|
||||
title={item.path}>
|
||||
<span className={cn("font-medium", matches ? "text-vscode-textLink" : "")}>
|
||||
{item.name}
|
||||
</span>
|
||||
{item.description && (
|
||||
<span className="text-vscode-descriptionForeground"> - {item.description}</span>
|
||||
)}
|
||||
{matches && (
|
||||
<span className="ml-2 text-xs bg-vscode-badge-background text-vscode-badge-foreground px-1 py-0.5 rounded">
|
||||
match
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ol>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
[/webview-ui/src/components/marketplace/components/TypeGroup.tsx](/webview-ui/src/components/marketplace/components/TypeGroup.tsx)
|
||||
|
||||
### Design Considerations
|
||||
|
||||
|
|
@ -498,300 +127,32 @@ export const TypeGroup: React.FC<TypeGroupProps> = ({ type, items, className, se
|
|||
|
||||
## Source Configuration Components
|
||||
|
||||
The Marketplace includes components for managing package sources.
|
||||
The Marketplace includes components for managing item sources.
|
||||
|
||||
### SourcesView
|
||||
|
||||
```tsx
|
||||
const SourcesView: React.FC<SourcesViewProps> = ({ sources, refreshingUrls, onRefreshSource, onSourcesChange }) => {
|
||||
const [newSourceUrl, setNewSourceUrl] = useState("")
|
||||
const [newSourceName, setNewSourceName] = useState("")
|
||||
const [error, setError] = useState("")
|
||||
|
||||
const handleAddSource = () => {
|
||||
// Validate source URL and name
|
||||
const errors = [
|
||||
...validateSourceUrl(newSourceUrl),
|
||||
...validateSourceName(newSourceName),
|
||||
...validateSourceDuplicates(sources, {
|
||||
url: newSourceUrl,
|
||||
name: newSourceName,
|
||||
enabled: true,
|
||||
}),
|
||||
]
|
||||
|
||||
if (errors.length > 0) {
|
||||
setError(errors[0].message)
|
||||
return
|
||||
}
|
||||
|
||||
// Add new source
|
||||
onSourcesChange([
|
||||
...sources,
|
||||
{
|
||||
url: newSourceUrl,
|
||||
name: newSourceName || undefined,
|
||||
enabled: true,
|
||||
},
|
||||
])
|
||||
|
||||
// Clear form
|
||||
setNewSourceUrl("")
|
||||
setNewSourceName("")
|
||||
setError("")
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4>Configure Marketplace Sources</h4>
|
||||
<p>Add Git repositories containing marketplace items.</p>
|
||||
|
||||
{/* Source form */}
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Git repository URL"
|
||||
value={newSourceUrl}
|
||||
onChange={(e) => setNewSourceUrl(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Display name (optional)"
|
||||
value={newSourceName}
|
||||
onChange={(e) => setNewSourceName(e.target.value)}
|
||||
/>
|
||||
{error && <p className="text-red-500">{error}</p>}
|
||||
<Button onClick={handleAddSource}>Add Source</Button>
|
||||
</div>
|
||||
|
||||
{/* Source list */}
|
||||
<div>
|
||||
{sources.map((source) => (
|
||||
<SourceItem
|
||||
key={source.url}
|
||||
source={source}
|
||||
isRefreshing={refreshingUrls.includes(source.url)}
|
||||
onRefresh={() => onRefreshSource(source.url)}
|
||||
onToggle={() => {
|
||||
const updatedSources = sources.map((s) =>
|
||||
s.url === source.url ? { ...s, enabled: !s.enabled } : s,
|
||||
)
|
||||
onSourcesChange(updatedSources)
|
||||
}}
|
||||
onRemove={() => {
|
||||
const updatedSources = sources.filter((s) => s.url !== source.url)
|
||||
onSourcesChange(updatedSources)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
[/webview-ui/src/components/marketplace/MarketplaceView.tsx](/webview-ui/src/components/marketplace/MarketplaceView.tsx)
|
||||
|
||||
## Filter Components
|
||||
|
||||
The Marketplace includes components for filtering and searching.
|
||||
|
||||
### SearchInput
|
||||
|
||||
```tsx
|
||||
const SearchInput: React.FC<{
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
}> = ({ value, onChange }) => {
|
||||
// Debounce search input to avoid excessive filtering
|
||||
const debouncedOnChange = useDebounce(onChange, 300)
|
||||
|
||||
return (
|
||||
<div className="search-container">
|
||||
<span className="codicon codicon-search"></span>
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => debouncedOnChange(e.target.value)}
|
||||
placeholder="Search packages..."
|
||||
className="search-input"
|
||||
aria-label="Search packages"
|
||||
/>
|
||||
{value && (
|
||||
<button className="clear-button" onClick={() => onChange("")} aria-label="Clear search">
|
||||
<span className="codicon codicon-close"></span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
[/webview-ui/src/components/marketplace/MarketplaceView.tsx](/webview-ui/src/components/marketplace/MarketplaceView.tsx)
|
||||
|
||||
### TypeFilterGroup
|
||||
|
||||
```tsx
|
||||
const TypeFilterGroup: React.FC<{
|
||||
selectedType: string
|
||||
onChange: (type: string) => void
|
||||
availableTypes: string[]
|
||||
}> = ({ selectedType, onChange, availableTypes }) => {
|
||||
return (
|
||||
<div className="filter-group">
|
||||
<h3 className="filter-heading">Filter by Type</h3>
|
||||
<div className="filter-options">
|
||||
<label className="filter-option">
|
||||
<input
|
||||
type="radio"
|
||||
name="type-filter"
|
||||
value=""
|
||||
checked={selectedType === ""}
|
||||
onChange={() => onChange("")}
|
||||
/>
|
||||
<span>All Types</span>
|
||||
</label>
|
||||
|
||||
{availableTypes.map((type) => (
|
||||
<label key={type} className="filter-option">
|
||||
<input
|
||||
type="radio"
|
||||
name="type-filter"
|
||||
value={type}
|
||||
checked={selectedType === type}
|
||||
onChange={() => onChange(type)}
|
||||
/>
|
||||
<span>{getTypeLabel(type)}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
[/webview-ui/src/components/marketplace/MarketplaceView.tsx](/webview-ui/src/components/marketplace/MarketplaceView.tsx)
|
||||
|
||||
### TagFilterGroup
|
||||
|
||||
```tsx
|
||||
const TagFilterGroup: React.FC<{
|
||||
selectedTags: string[]
|
||||
onChange: (tags: string[]) => void
|
||||
availableTags: string[]
|
||||
}> = ({ selectedTags, onChange, availableTags }) => {
|
||||
const toggleTag = (tag: string) => {
|
||||
if (selectedTags.includes(tag)) {
|
||||
onChange(selectedTags.filter((t) => t !== tag))
|
||||
} else {
|
||||
onChange([...selectedTags, tag])
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="filter-group">
|
||||
<h3 className="filter-heading">Filter by Tags</h3>
|
||||
<div className="tag-cloud">
|
||||
{availableTags.map((tag) => (
|
||||
<button
|
||||
key={tag}
|
||||
className={`tag ${selectedTags.includes(tag) ? "selected" : ""}`}
|
||||
onClick={() => toggleTag(tag)}
|
||||
aria-pressed={selectedTags.includes(tag)}>
|
||||
{tag}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
[/webview-ui/src/components/marketplace/MarketplaceView.tsx](/webview-ui/src/components/marketplace/MarketplaceView.tsx)
|
||||
|
||||
## Styling Approach
|
||||
|
||||
The Marketplace UI uses a combination of Tailwind CSS and VSCode theme variables for styling.
|
||||
|
||||
### VSCode Theme Integration
|
||||
|
||||
The components use VSCode theme variables to ensure they match the user's selected theme:
|
||||
|
||||
```css
|
||||
/* Example of VSCode theme variable usage */
|
||||
.package-card {
|
||||
background-color: var(--vscode-panel-background);
|
||||
border-color: var(--vscode-panel-border);
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
|
||||
.package-description {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
}
|
||||
|
||||
.package-link {
|
||||
color: var(--vscode-textLink-foreground);
|
||||
}
|
||||
|
||||
.package-link:hover {
|
||||
color: var(--vscode-textLink-activeForeground);
|
||||
}
|
||||
```
|
||||
|
||||
### Tailwind CSS Usage
|
||||
|
||||
Tailwind CSS is used for utility-based styling:
|
||||
|
||||
```tsx
|
||||
// Example of Tailwind CSS usage
|
||||
<div className="flex justify-between items-center p-4 border rounded-md">
|
||||
<h3 className="text-lg font-semibold">{item.name}</h3>
|
||||
<span className="px-2 py-1 text-xs text-white rounded-full bg-blue-600">{getTypeLabel(item.type)}</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Custom Utility Functions
|
||||
|
||||
The UI uses utility functions for class name composition:
|
||||
|
||||
```typescript
|
||||
// cn utility for conditional class names
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
```
|
||||
|
||||
## Responsive Design
|
||||
|
||||
The Marketplace UI is designed to work across different viewport sizes:
|
||||
|
||||
### Layout Adjustments
|
||||
|
||||
```tsx
|
||||
// Example of responsive layout
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{items.map((item) => (
|
||||
<PackageManagerItemCard key={item.name} item={item} />
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
### Mobile Considerations
|
||||
|
||||
For smaller screens:
|
||||
|
||||
1. **Stacked Layout**:
|
||||
|
||||
- Cards stack vertically on small screens
|
||||
- Filter panel collapses to a dropdown
|
||||
- Full-width elements for better touch targets
|
||||
|
||||
2. **Touch Optimization**:
|
||||
|
||||
- Larger touch targets for mobile users
|
||||
- Swipe gestures for common actions
|
||||
- Simplified interactions for touch devices
|
||||
|
||||
3. **Content Prioritization**:
|
||||
- Critical information shown first
|
||||
- Less important details hidden behind expandable sections
|
||||
- Reduced information density on small screens
|
||||
|
||||
## Accessibility Features
|
||||
|
||||
The Marketplace UI includes several accessibility features:
|
||||
|
|
@ -820,7 +181,7 @@ The Marketplace UI includes several accessibility features:
|
|||
|
||||
```tsx
|
||||
// Example of screen reader support
|
||||
<div role="region" aria-label="Package details" aria-expanded={isExpanded}>
|
||||
<div role="region" aria-label="Item details" aria-expanded={isExpanded}>
|
||||
<button aria-controls="details-content" aria-expanded={isExpanded} onClick={toggleExpanded}>
|
||||
{isExpanded ? "Hide details" : "Show details"}
|
||||
</button>
|
||||
|
|
@ -889,7 +250,7 @@ The Marketplace UI uses subtle animations to enhance the user experience:
|
|||
// Example of loading state animation
|
||||
<div className="loading-indicator">
|
||||
<div className="spinner animate-spin h-5 w-5 border-2 border-t-transparent rounded-full"></div>
|
||||
<span>Loading packages...</span>
|
||||
<span>Loading items...</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
|
|
@ -906,7 +267,7 @@ const ErrorDisplay: React.FC<{ error: string; retry: () => void }> = ({ error, r
|
|||
<div className="error-container p-4 border border-red-500 rounded-md bg-red-50 text-red-700">
|
||||
<div className="flex items-center">
|
||||
<span className="codicon codicon-error mr-2"></span>
|
||||
<h3 className="font-medium">Error loading packages</h3>
|
||||
<h3 className="font-medium">Error loading items</h3>
|
||||
</div>
|
||||
<p className="mt-2 mb-4">{error}</p>
|
||||
<button className="px-3 py-1 bg-red-600 text-white rounded-md hover:bg-red-700" onClick={retry}>
|
||||
|
|
@ -931,31 +292,6 @@ const EmptyState: React.FC<{ message: string }> = ({ message }) => {
|
|||
}
|
||||
```
|
||||
|
||||
### Loading States
|
||||
|
||||
```tsx
|
||||
// Example of loading state with skeleton
|
||||
const PackageCardSkeleton: React.FC = () => {
|
||||
return (
|
||||
<div className="border border-vscode-panel-border rounded-md p-4 bg-vscode-panel-background animate-pulse">
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="w-2/3">
|
||||
<div className="h-6 bg-vscode-panel-border rounded"></div>
|
||||
<div className="h-4 w-1/3 bg-vscode-panel-border rounded mt-2"></div>
|
||||
</div>
|
||||
<div className="h-6 w-16 bg-vscode-panel-border rounded-full"></div>
|
||||
</div>
|
||||
<div className="h-4 bg-vscode-panel-border rounded mt-4"></div>
|
||||
<div className="h-4 bg-vscode-panel-border rounded mt-2 w-5/6"></div>
|
||||
<div className="flex gap-2 mt-4">
|
||||
<div className="h-6 w-16 bg-vscode-panel-border rounded-full"></div>
|
||||
<div className="h-6 w-16 bg-vscode-panel-border rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Component Testing
|
||||
|
||||
The Marketplace UI components include comprehensive tests:
|
||||
|
|
@ -964,8 +300,8 @@ The Marketplace UI components include comprehensive tests:
|
|||
|
||||
```typescript
|
||||
// Example of component unit test
|
||||
describe("PackageManagerItemCard", () => {
|
||||
const mockItem: PackageManagerItem = {
|
||||
describe("MarketplaceItemCard", () => {
|
||||
const mockItem: MarketplaceItem = {
|
||||
name: "Test Package",
|
||||
description: "A test package",
|
||||
type: "package",
|
||||
|
|
@ -982,7 +318,7 @@ describe("PackageManagerItemCard", () => {
|
|||
|
||||
it("renders correctly", () => {
|
||||
render(
|
||||
<PackageManagerItemCard
|
||||
<MarketplaceItemCard
|
||||
item={mockItem}
|
||||
filters={mockFilters}
|
||||
setFilters={mockSetFilters}
|
||||
|
|
@ -998,7 +334,7 @@ describe("PackageManagerItemCard", () => {
|
|||
|
||||
it("handles tag clicks", () => {
|
||||
render(
|
||||
<PackageManagerItemCard
|
||||
<MarketplaceItemCard
|
||||
item={mockItem}
|
||||
filters={mockFilters}
|
||||
setFilters={mockSetFilters}
|
||||
|
|
@ -1024,7 +360,7 @@ describe("PackageManagerItemCard", () => {
|
|||
// Example of snapshot test
|
||||
it("matches snapshot", () => {
|
||||
const { container } = render(
|
||||
<PackageManagerItemCard
|
||||
<MarketplaceItemCard
|
||||
item={mockItem}
|
||||
filters={mockFilters}
|
||||
setFilters={mockSetFilters}
|
||||
|
|
@ -1043,7 +379,7 @@ it("matches snapshot", () => {
|
|||
// Example of accessibility test
|
||||
it("meets accessibility requirements", async () => {
|
||||
const { container } = render(
|
||||
<PackageManagerItemCard
|
||||
<MarketplaceItemCard
|
||||
item={mockItem}
|
||||
filters={mockFilters}
|
||||
setFilters={mockSetFilters}
|
||||
|
|
|
|||
|
|
@ -175,11 +175,11 @@ describe("MetadataScanner", () => {
|
|||
})
|
||||
```
|
||||
|
||||
#### PackageManagerManager Tests
|
||||
#### MarketplaceManager Tests
|
||||
|
||||
```typescript
|
||||
describe("PackageManagerManager", () => {
|
||||
let manager: PackageManagerManager
|
||||
describe("MarketplaceManager", () => {
|
||||
let manager: MarketplaceManager
|
||||
let mockContext: vscode.ExtensionContext
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -193,7 +193,7 @@ describe("PackageManagerManager", () => {
|
|||
},
|
||||
} as unknown as vscode.ExtensionContext
|
||||
|
||||
manager = new PackageManagerManager(mockContext)
|
||||
manager = new MarketplaceManager(mockContext)
|
||||
})
|
||||
|
||||
describe("filterItems", () => {
|
||||
|
|
@ -202,7 +202,7 @@ describe("PackageManagerManager", () => {
|
|||
manager["currentItems"] = [
|
||||
{ name: "Item 1", type: "mode", description: "Test item 1" },
|
||||
{ name: "Item 2", type: "package", description: "Test item 2" },
|
||||
] as PackageManagerItem[]
|
||||
] as MarketplaceItem[]
|
||||
|
||||
const result = manager.filterItems({ type: "mode" })
|
||||
|
||||
|
|
@ -215,7 +215,7 @@ describe("PackageManagerManager", () => {
|
|||
manager["currentItems"] = [
|
||||
{ name: "Alpha Item", type: "mode", description: "Test item" },
|
||||
{ name: "Beta Item", type: "package", description: "Another test" },
|
||||
] as PackageManagerItem[]
|
||||
] as MarketplaceItem[]
|
||||
|
||||
const result = manager.filterItems({ search: "alpha" })
|
||||
|
||||
|
|
@ -277,11 +277,11 @@ describe("searchUtils", () => {
|
|||
|
||||
Frontend unit tests verify the functionality of UI components:
|
||||
|
||||
#### PackageManagerItemCard Tests
|
||||
#### MarketplaceItemCard Tests
|
||||
|
||||
```typescript
|
||||
describe("PackageManagerItemCard", () => {
|
||||
const mockItem: PackageManagerItem = {
|
||||
describe("MarketplaceItemCard", () => {
|
||||
const mockItem: MarketplaceItem = {
|
||||
name: "Test Package",
|
||||
description: "A test package",
|
||||
type: "package",
|
||||
|
|
@ -302,7 +302,7 @@ describe("PackageManagerItemCard", () => {
|
|||
|
||||
it("renders correctly", () => {
|
||||
render(
|
||||
<PackageManagerItemCard
|
||||
<MarketplaceItemCard
|
||||
item={mockItem}
|
||||
filters={mockFilters}
|
||||
setFilters={mockSetFilters}
|
||||
|
|
@ -318,7 +318,7 @@ describe("PackageManagerItemCard", () => {
|
|||
|
||||
it("handles tag clicks", () => {
|
||||
render(
|
||||
<PackageManagerItemCard
|
||||
<MarketplaceItemCard
|
||||
item={mockItem}
|
||||
filters={mockFilters}
|
||||
setFilters={mockSetFilters}
|
||||
|
|
@ -421,14 +421,14 @@ Integration tests verify that different components work together correctly.
|
|||
|
||||
```typescript
|
||||
describe("Marketplace Integration", () => {
|
||||
let manager: PackageManagerManager
|
||||
let manager: MarketplaceManager
|
||||
let metadataScanner: MetadataScanner
|
||||
let templateItems: PackageManagerItem[]
|
||||
let templateItems: MarketplaceItem[]
|
||||
|
||||
beforeAll(async () => {
|
||||
// Load real data from template
|
||||
metadataScanner = new MetadataScanner()
|
||||
const templatePath = path.resolve(__dirname, "../../../../marketplace-template")
|
||||
const templatePath = path.resolve(__dirname, "marketplace-template")
|
||||
templateItems = await metadataScanner.scanDirectory(templatePath, "https://example.com")
|
||||
})
|
||||
|
||||
|
|
@ -440,7 +440,7 @@ describe("Marketplace Integration", () => {
|
|||
} as vscode.ExtensionContext
|
||||
|
||||
// Create real instances
|
||||
manager = new PackageManagerManager(context)
|
||||
manager = new MarketplaceManager(context)
|
||||
|
||||
// Set up manager with template data
|
||||
manager["currentItems"] = [...templateItems]
|
||||
|
|
@ -455,7 +455,7 @@ describe("Marketplace Integration", () => {
|
|||
tagFilters: [],
|
||||
}
|
||||
|
||||
const result = await handlePackageManagerMessages(message, manager)
|
||||
const result = await handleMarketplaceMessages(message, manager)
|
||||
|
||||
expect(result.type).toBe("searchResults")
|
||||
expect(result.data).toHaveLength(1)
|
||||
|
|
@ -470,7 +470,7 @@ describe("Marketplace Integration", () => {
|
|||
tagFilters: [],
|
||||
}
|
||||
|
||||
const result = await handlePackageManagerMessages(message, manager)
|
||||
const result = await handleMarketplaceMessages(message, manager)
|
||||
|
||||
expect(result.type).toBe("searchResults")
|
||||
expect(result.data.every((item) => item.type === "mode")).toBe(true)
|
||||
|
|
@ -488,7 +488,7 @@ describe("Marketplace Integration", () => {
|
|||
tagFilters: [],
|
||||
}
|
||||
|
||||
const result = await handlePackageManagerMessages(message, manager)
|
||||
const result = await handleMarketplaceMessages(message, manager)
|
||||
|
||||
expect(result.data.length).toBeGreaterThan(0)
|
||||
|
||||
|
|
@ -508,7 +508,7 @@ describe("Marketplace Integration", () => {
|
|||
|
||||
```typescript
|
||||
describe("Marketplace UI Integration", () => {
|
||||
const mockItems: PackageManagerItem[] = [
|
||||
const mockItems: MarketplaceItem[] = [
|
||||
{
|
||||
name: "Test Package",
|
||||
description: "A test package",
|
||||
|
|
@ -529,8 +529,8 @@ describe("Marketplace UI Integration", () => {
|
|||
]
|
||||
},
|
||||
{
|
||||
name: "Another Package",
|
||||
description: "Another test package",
|
||||
name: "Test Mode",
|
||||
description: "Another test item",
|
||||
type: "mode",
|
||||
url: "https://example.com",
|
||||
repoUrl: "https://github.com/example/repo",
|
||||
|
|
@ -544,28 +544,28 @@ describe("Marketplace UI Integration", () => {
|
|||
});
|
||||
|
||||
it("should filter items when search is entered", async () => {
|
||||
render(<PackageManagerView initialItems={mockItems} />);
|
||||
render(<MarketplaceView initialItems={mockItems} />);
|
||||
|
||||
// Both packages should be visible initially
|
||||
// Both items should be visible initially
|
||||
expect(screen.getByText("Test Package")).toBeInTheDocument();
|
||||
expect(screen.getByText("Another Package")).toBeInTheDocument();
|
||||
expect(screen.getByText("Test Mode")).toBeInTheDocument();
|
||||
|
||||
// Enter search term
|
||||
const searchInput = screen.getByPlaceholderText("Search packages...");
|
||||
const searchInput = screen.getByPlaceholderText("Search items...");
|
||||
fireEvent.change(searchInput, { target: { value: "another" } });
|
||||
|
||||
// Wait for debounce
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText("Test Package")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Another Package")).toBeInTheDocument();
|
||||
expect(screen.getByText("Test Mode")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should expand details when search matches subcomponents", async () => {
|
||||
render(<PackageManagerView initialItems={mockItems} />);
|
||||
render(<MarketplaceView initialItems={mockItems} />);
|
||||
|
||||
// Enter search term that matches a subcomponent
|
||||
const searchInput = screen.getByPlaceholderText("Search packages...");
|
||||
const searchInput = screen.getByPlaceholderText("Search items...");
|
||||
fireEvent.change(searchInput, { target: { value: "test mode" } });
|
||||
|
||||
// Wait for debounce and expansion
|
||||
|
|
@ -592,7 +592,7 @@ The Marketplace uses several approaches to manage test data:
|
|||
Mock data is used for simple unit tests:
|
||||
|
||||
```typescript
|
||||
const mockItems: PackageManagerItem[] = [
|
||||
const mockItems: MarketplaceItem[] = [
|
||||
{
|
||||
name: "Test Package",
|
||||
description: "A test package",
|
||||
|
|
@ -644,12 +644,12 @@ export const metadataFixtures = {
|
|||
},
|
||||
},
|
||||
{
|
||||
type: "mcp server",
|
||||
type: "mcp",
|
||||
path: "/test/path/server",
|
||||
metadata: {
|
||||
name: "Test Server",
|
||||
description: "A test server",
|
||||
type: "mcp server",
|
||||
type: "mcp",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -665,7 +665,7 @@ Real template data is used for integration tests:
|
|||
beforeAll(async () => {
|
||||
// Load real data from template
|
||||
metadataScanner = new MetadataScanner()
|
||||
const templatePath = path.resolve(__dirname, "../../../../marketplace-template")
|
||||
const templatePath = path.resolve(__dirname, "marketplace-template")
|
||||
templateItems = await metadataScanner.scanDirectory(templatePath, "https://example.com")
|
||||
})
|
||||
```
|
||||
|
|
@ -676,8 +676,8 @@ Generators create varied test data:
|
|||
|
||||
```typescript
|
||||
// Test data generator
|
||||
function generatePackageItems(count: number): PackageManagerItem[] {
|
||||
const types: ComponentType[] = ["mode", "mcp server", "package", "prompt"]
|
||||
function generatePackageItems(count: number): MarketplaceItem[] {
|
||||
const types: MarketplaceItemType[] = ["mode", "mcp", "package", "prompt"]
|
||||
const tags = ["test", "example", "data", "ui", "server", "client"]
|
||||
|
||||
return Array.from({ length: count }, (_, i) => {
|
||||
|
|
@ -698,8 +698,8 @@ function generatePackageItems(count: number): PackageManagerItem[] {
|
|||
})
|
||||
}
|
||||
|
||||
function generateSubcomponents(count: number): PackageManagerItem["items"] {
|
||||
const types: ComponentType[] = ["mode", "mcp server", "prompt"]
|
||||
function generateSubcomponents(count: number): MarketplaceItem["items"] {
|
||||
const types: MarketplaceItemType[] = ["mode", "mcp", "prompt"]
|
||||
|
||||
return Array.from({ length: count }, (_, i) => {
|
||||
const type = types[i % types.length]
|
||||
|
|
@ -739,12 +739,12 @@ This section outlines the test plan for the type filtering functionality in the
|
|||
- **Expected**: Only items with type "mode" are returned
|
||||
- **Verification**: Check that the returned items all have type "mode"
|
||||
|
||||
**Test: Filter by MCP Server Type**
|
||||
**Test: Filter by mcp Type**
|
||||
|
||||
- **Input**: Items with various types including "mcp server"
|
||||
- **Filter**: `{ type: "mcp server" }`
|
||||
- **Expected**: Only items with type "mcp server" are returned
|
||||
- **Verification**: Check that the returned items all have type "mcp server"
|
||||
- **Input**: Items with various types including "mcp"
|
||||
- **Filter**: `{ type: "mcp" }`
|
||||
- **Expected**: Only items with type "mcp" are returned
|
||||
- **Verification**: Check that the returned items all have type "mcp"
|
||||
|
||||
#### 2. Package with Subcomponents Tests
|
||||
|
||||
|
|
@ -778,7 +778,7 @@ This section outlines the test plan for the type filtering functionality in the
|
|||
|
||||
**Test: Type Filter and Search Term**
|
||||
|
||||
- **Input**: Various items including packages with subcomponents
|
||||
- **Input**: Various items including packages with subitems
|
||||
- **Filter**: `{ type: "mode", search: "test" }`
|
||||
- **Expected**: Only items that match both the type filter and the search term are returned
|
||||
- **Verification**:
|
||||
|
|
@ -864,7 +864,7 @@ This section outlines the test plan for the type filtering functionality in the
|
|||
**Test: Invalid Type**
|
||||
|
||||
- **Input**: Various items
|
||||
- **Filter**: `{ type: "invalid" as ComponentType }`
|
||||
- **Filter**: `{ type: "invalid" as MarketplaceItemType }`
|
||||
- **Expected**: No items are returned (since none match the invalid type)
|
||||
- **Verification**: Check that an empty array is returned
|
||||
|
||||
|
|
@ -897,9 +897,9 @@ This section outlines the test plan for the type filtering functionality in the
|
|||
|
||||
#### 2. Deep Nesting
|
||||
|
||||
**Test: Deeply Nested Packages**
|
||||
**Test: Deeply Nested Items**
|
||||
|
||||
- **Input**: Packages with deeply nested subcomponents
|
||||
- **Input**: Items with deeply nested subcomponents
|
||||
- **Filter**: Various filters
|
||||
- **Expected**: The filtering correctly handles the nested structure
|
||||
- **Verification**: Check that the results are correct for deeply nested structures
|
||||
|
|
@ -912,7 +912,7 @@ The Marketplace tests are organized by functionality rather than by file structu
|
|||
|
||||
```
|
||||
src/services/marketplace/__tests__/
|
||||
├── PackageManager.consolidated.test.ts # Combined tests
|
||||
├── Marketplace.consolidated.test.ts # Combined tests
|
||||
├── searchUtils.test.ts # Search utility tests
|
||||
└── PackageSubcomponents.test.ts # Subcomponent tests
|
||||
```
|
||||
|
|
@ -1011,7 +1011,7 @@ describe("Marketplace Integration", () => {
|
|||
|
||||
// Create fresh manager for each test
|
||||
beforeEach(() => {
|
||||
manager = new PackageManagerManager(mockContext)
|
||||
manager = new MarketplaceManager(mockContext)
|
||||
manager["currentItems"] = [...templateItems]
|
||||
})
|
||||
|
||||
|
|
@ -1114,7 +1114,7 @@ describe("Complex integration test", () => {
|
|||
// Visual debugging for UI tests
|
||||
describe("UI component test", () => {
|
||||
it("should render correctly", async () => {
|
||||
const { container } = render(<PackageManagerItemCard item={mockItem} />);
|
||||
const { container } = render(<MarketplaceItemCard item={mockItem} />);
|
||||
|
||||
// Save screenshot for visual debugging
|
||||
if (process.env.SAVE_SCREENSHOTS) {
|
||||
|
|
@ -1155,9 +1155,9 @@ describe("Search functionality", () => {
|
|||
describe("Package filtering", () => {
|
||||
/**
|
||||
* Scenario: User filters by type and search term
|
||||
* Given: A list of packages of different types
|
||||
* Given: A list of items of different types
|
||||
* When: The user selects a type filter and enters a search term
|
||||
* Then: Only packages of the selected type containing the search term should be shown
|
||||
* Then: Only items of the selected type containing the search term should be shown
|
||||
*/
|
||||
it("should combine type and search filters", () => {
|
||||
// Test implementation...
|
||||
|
|
|
|||
|
|
@ -4,19 +4,19 @@ This document provides guidance on extending the Marketplace with new features,
|
|||
|
||||
## Adding New Component Types
|
||||
|
||||
The Marketplace is designed to be extensible, allowing for the addition of new component types beyond the default ones (mode, mcp server, prompt, package).
|
||||
The Marketplace is designed to be extensible, allowing for the addition of new component types beyond the default ones (mode, mcp, prompt, package).
|
||||
|
||||
### Extending the ComponentType
|
||||
### Extending the MarketplaceItemType
|
||||
|
||||
To add a new component type:
|
||||
|
||||
1. **Update the ComponentType Type**:
|
||||
1. **Update the MarketplaceItemType Type**:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Supported component types
|
||||
*/
|
||||
export type ComponentType = "mode" | "prompt" | "package" | "mcp server" | "your-new-type"
|
||||
export type MarketplaceItemType = "mode" | "prompt" | "package" | "mcp" | "your-new-type"
|
||||
```
|
||||
|
||||
2. **Update Type Label Functions**:
|
||||
|
|
@ -26,7 +26,7 @@ const getTypeLabel = (type: string) => {
|
|||
switch (type) {
|
||||
case "mode":
|
||||
return "Mode"
|
||||
case "mcp server":
|
||||
case "mcp":
|
||||
return "MCP Server"
|
||||
case "prompt":
|
||||
return "Prompt"
|
||||
|
|
@ -47,7 +47,7 @@ const getTypeColor = (type: string) => {
|
|||
switch (type) {
|
||||
case "mode":
|
||||
return "bg-blue-600"
|
||||
case "mcp server":
|
||||
case "mcp":
|
||||
return "bg-green-600"
|
||||
case "prompt":
|
||||
return "bg-purple-600"
|
||||
|
|
@ -68,7 +68,7 @@ const getTypeGroupLabel = (type: string) => {
|
|||
switch (type) {
|
||||
case "mode":
|
||||
return "Modes"
|
||||
case "mcp server":
|
||||
case "mcp":
|
||||
return "MCP Servers"
|
||||
case "prompt":
|
||||
return "Prompts"
|
||||
|
|
@ -84,7 +84,7 @@ const getTypeGroupLabel = (type: string) => {
|
|||
|
||||
### Directory Structure for New Types
|
||||
|
||||
When adding a new component type, follow this directory structure in your package source repository:
|
||||
When adding a new component type, follow this directory structure in your source repository:
|
||||
|
||||
```
|
||||
repository-root/
|
||||
|
|
@ -134,7 +134,7 @@ const getTypeIcon = (type: string) => {
|
|||
switch (type) {
|
||||
case "mode":
|
||||
return "codicon-person"
|
||||
case "mcp server":
|
||||
case "mcp":
|
||||
return "codicon-server"
|
||||
case "prompt":
|
||||
return "codicon-comment"
|
||||
|
|
@ -225,14 +225,14 @@ interface Filters {
|
|||
|
||||
```typescript
|
||||
export function filterItems(
|
||||
items: PackageManagerItem[],
|
||||
items: MarketplaceItem[],
|
||||
filters: {
|
||||
type?: string
|
||||
search?: string
|
||||
tags?: string[]
|
||||
yourNewFilter?: string // Add your new filter
|
||||
},
|
||||
): PackageManagerItem[] {
|
||||
): MarketplaceItem[] {
|
||||
// Existing filter logic...
|
||||
|
||||
// Add your new filter logic
|
||||
|
|
@ -290,7 +290,7 @@ const [viewMode, setViewMode] = useState<ViewMode>("card")
|
|||
|
||||
```tsx
|
||||
const YourNewView: React.FC<{
|
||||
items: PackageManagerItem[]
|
||||
items: MarketplaceItem[]
|
||||
filters: Filters
|
||||
setFilters: (filters: Filters) => void
|
||||
}> = ({ items, filters, setFilters }) => {
|
||||
|
|
@ -357,7 +357,7 @@ To add custom actions for package items:
|
|||
1. **Create an Action Handler**:
|
||||
|
||||
```typescript
|
||||
const handleCustomAction = (item: PackageManagerItem) => {
|
||||
const handleCustomAction = (item: MarketplaceItem) => {
|
||||
vscode.postMessage({
|
||||
type: "customAction",
|
||||
item: item.name,
|
||||
|
|
@ -446,7 +446,7 @@ To replace or extend existing components:
|
|||
1. **Create a Custom Component**:
|
||||
|
||||
```tsx
|
||||
const CustomPackageCard: React.FC<PackageManagerItemCardProps> = (props) => {
|
||||
const CustomPackageCard: React.FC<MarketplaceItemCardProps> = (props) => {
|
||||
// Your custom implementation
|
||||
return (
|
||||
<div className="custom-package-card">
|
||||
|
|
@ -463,17 +463,17 @@ const CustomPackageCard: React.FC<PackageManagerItemCardProps> = (props) => {
|
|||
|
||||
```tsx
|
||||
interface ComponentOverrides {
|
||||
PackageCard?: React.ComponentType<PackageManagerItemCardProps>
|
||||
ExpandableSection?: React.ComponentType<ExpandableSectionProps>
|
||||
TypeGroup?: React.ComponentType<TypeGroupProps>
|
||||
PackageCard?: React.MarketplaceItemType<MarketplaceItemCardProps>
|
||||
ExpandableSection?: React.MarketplaceItemType<ExpandableSectionProps>
|
||||
TypeGroup?: React.MarketplaceItemType<TypeGroupProps>
|
||||
}
|
||||
|
||||
const PackageManagerView: React.FC<{
|
||||
initialItems: PackageManagerItem[]
|
||||
const MarketplaceView: React.FC<{
|
||||
initialItems: MarketplaceItem[]
|
||||
componentOverrides?: ComponentOverrides
|
||||
}> = ({ initialItems, componentOverrides = {} }) => {
|
||||
// Component selection logic
|
||||
const PackageCard = componentOverrides.PackageCard || PackageManagerItemCard
|
||||
const PackageCard = componentOverrides.PackageCard || MarketplaceItemCard
|
||||
|
||||
return (
|
||||
<div className="marketplace">
|
||||
|
|
@ -529,7 +529,7 @@ const CustomLayout: React.FC<{
|
|||
content={
|
||||
<div className="results-area">
|
||||
{filteredItems.map((item) => (
|
||||
<PackageManagerItemCard
|
||||
<MarketplaceItemCard
|
||||
key={item.name}
|
||||
item={item}
|
||||
filters={filters}
|
||||
|
|
@ -558,7 +558,7 @@ To add support for new source types:
|
|||
interface SourceProvider {
|
||||
type: string
|
||||
canHandle(url: string): boolean
|
||||
fetchItems(url: string): Promise<PackageManagerItem[]>
|
||||
fetchItems(url: string): Promise<MarketplaceItem[]>
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -572,7 +572,7 @@ class CustomSourceProvider implements SourceProvider {
|
|||
return url.startsWith("custom://")
|
||||
}
|
||||
|
||||
async fetchItems(url: string): Promise<PackageManagerItem[]> {
|
||||
async fetchItems(url: string): Promise<MarketplaceItem[]> {
|
||||
// Your custom implementation
|
||||
// Fetch items from your custom source
|
||||
return items
|
||||
|
|
@ -584,7 +584,7 @@ class CustomSourceProvider implements SourceProvider {
|
|||
|
||||
```typescript
|
||||
// In your extension code
|
||||
const registerSourceProviders = (marketplace: PackageManagerManager) => {
|
||||
const registerSourceProviders = (marketplace: MarketplaceManager) => {
|
||||
marketplace.registerSourceProvider(new CustomSourceProvider())
|
||||
}
|
||||
```
|
||||
|
|
@ -635,9 +635,9 @@ To add support for custom messages:
|
|||
```typescript
|
||||
// In your extension code
|
||||
const extendMessageHandler = () => {
|
||||
const originalHandler = handlePackageManagerMessages
|
||||
const originalHandler = handleMarketplaceMessages
|
||||
|
||||
return async (message: any, marketplace: PackageManagerManager) => {
|
||||
return async (message: any, marketplace: MarketplaceManager) => {
|
||||
// Handle custom messages
|
||||
if (message.type === "yourCustomMessage") {
|
||||
// Your custom message handling
|
||||
|
|
@ -685,11 +685,11 @@ class ExternalApiClient {
|
|||
this.baseUrl = baseUrl
|
||||
}
|
||||
|
||||
async fetchPackages(): Promise<PackageManagerItem[]> {
|
||||
async fetchPackages(): Promise<MarketplaceItem[]> {
|
||||
const response = await fetch(`${this.baseUrl}/packages`)
|
||||
const data = await response.json()
|
||||
|
||||
// Transform API data to PackageManagerItem format
|
||||
// Transform API data to MarketplaceItem format
|
||||
return data.map((item) => ({
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
|
|
@ -718,7 +718,7 @@ class ApiSourceProvider implements SourceProvider {
|
|||
return url.startsWith("api://")
|
||||
}
|
||||
|
||||
async fetchItems(url: string): Promise<PackageManagerItem[]> {
|
||||
async fetchItems(url: string): Promise<MarketplaceItem[]> {
|
||||
return this.apiClient.fetchPackages()
|
||||
}
|
||||
}
|
||||
|
|
@ -728,7 +728,7 @@ class ApiSourceProvider implements SourceProvider {
|
|||
|
||||
```typescript
|
||||
// In your extension code
|
||||
const registerApiProvider = (marketplace: PackageManagerManager) => {
|
||||
const registerApiProvider = (marketplace: MarketplaceManager) => {
|
||||
marketplace.registerSourceProvider(new ApiSourceProvider("https://your-api.example.com"))
|
||||
}
|
||||
```
|
||||
|
|
@ -773,7 +773,7 @@ class AuthenticatedApiClient extends ExternalApiClient {
|
|||
this.authProvider = authProvider
|
||||
}
|
||||
|
||||
async fetchPackages(): Promise<PackageManagerItem[]> {
|
||||
async fetchPackages(): Promise<MarketplaceItem[]> {
|
||||
const token = await this.authProvider.getToken()
|
||||
|
||||
if (!token) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Overview and Purpose
|
||||
|
||||
The Marketplace is a powerful feature in Roo Code that allows you to discover, browse, and utilize various components to enhance your development experience. It serves as a centralized hub for accessing:
|
||||
The Marketplace is a powerful feature in Roo Code that allows you to discover, browse, and utilize various items to enhance your development experience. It serves as a centralized hub for accessing:
|
||||
|
||||
- **Modes**: Specialized AI assistants with different capabilities
|
||||
- **MCP Servers**: Model Context Protocol servers that provide additional functionality
|
||||
|
|
@ -33,10 +33,10 @@ The Marketplace simplifies the process of extending Roo Code's capabilities by p
|
|||
- Access source repositories directly
|
||||
- Explore subcomponents organized by type
|
||||
|
||||
### Package Management
|
||||
### Item Management
|
||||
|
||||
- Add new components to your environment
|
||||
- Manage custom package sources
|
||||
- Manage custom item sources
|
||||
- Create and contribute your own packages
|
||||
|
||||
## How to Access the Marketplace
|
||||
|
|
@ -55,4 +55,4 @@ Alternatively, you can use the Command Palette:
|
|||
|
||||
---
|
||||
|
||||
**Next**: [Browsing Packages](./02-browsing-packages.md)
|
||||
**Next**: [Browsing items](./02-browsing-items.md)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ The Marketplace interface is designed to provide a clean, intuitive experience f
|
|||
│ [Browse] [Sources] │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ FILTERS │
|
||||
│ Types: □ Mode □ MCP Server □ Package □ Prompt │
|
||||
│ Types: [] Mode [] MCP Server [] Package [] Prompt │
|
||||
│ Search: [ ] │
|
||||
│ Tags: [Tag cloud] │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
|
|
@ -137,4 +137,4 @@ The Marketplace provides visual feedback about your current filters:
|
|||
|
||||
---
|
||||
|
||||
**Previous**: [Browsing Packages](./02-browsing-packages.md) | **Next**: [Working with Package Details](./04-working-with-details.md)
|
||||
**Previous**: [Browsing Items](./02-browsing-items.md) | **Next**: [Working with Package Details](./04-working-with-details.md)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Working with Package Details
|
||||
|
||||
Marketplace items often contain multiple items organized in a hierarchical structure; these items are referred to as "packages" and must have a type of package. The items organized within a package are referred to as "subcomponents" and have all the same metadata properties of regular items. This guide explains how to work with the details section of package cards to explore and understand the elements within each package.
|
||||
Marketplace items often contain multiple items organized in a hierarchical structure; these items are referred to as "Packages" and must have a type of `package`. The items organized within a package are referred to as "subitems" and have all the same metadata properties of regular items. This guide explains how to work with the details section of package cards to explore and understand the elements within each package.
|
||||
|
||||
## Expanding Package Details
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
This guide explains how to create and contribute your own packages to the Roo Code Marketplace. By following these steps, you can share your work with the community and help expand the ecosystem.
|
||||
|
||||
## Item Structure and Metadata
|
||||
## Package Structure and Metadata
|
||||
|
||||
Each item in the Marketplace requires specific metadata files and follows a consistent directory structure.
|
||||
Each package in the Marketplace requires specific metadata files and follows a consistent directory structure.
|
||||
|
||||
### Directory Structure
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ package-name/
|
|||
├── modes/ # Directory for mode components
|
||||
│ └── my-mode/
|
||||
│ └── metadata.en.yml
|
||||
├── mcp servers/ # Directory for MCP server components
|
||||
├── mcp/ # Directory for MCP server components
|
||||
│ └── my-server/
|
||||
│ └── metadata.en.yml
|
||||
└── prompts/ # Directory for prompt components
|
||||
|
|
@ -34,7 +34,7 @@ Metadata files use YAML format and must include specific fields:
|
|||
name: "My Package"
|
||||
description: "A detailed description of what this package does"
|
||||
version: "1.0.0"
|
||||
type: "package" # One of: package, mode, mcp server, prompt
|
||||
type: "package" # One of: package, mode, mcp, prompt
|
||||
tags:
|
||||
- tag1
|
||||
- tag2
|
||||
|
|
@ -50,21 +50,22 @@ authorUrl: "http://your.profile.url/" #optional
|
|||
Here's how a package might look in the actual source tree:
|
||||
|
||||
```
|
||||
Roo-Code-Packages/
|
||||
Roo-Code-Marketplace/
|
||||
├── shared-prompts/ # Shared prompts directory
|
||||
│ └── data-analysis/
|
||||
│ └── metadata.en.yml
|
||||
│
|
||||
└── data-toolkit/ # Your package directory
|
||||
├── metadata.en.yml # Package metadata
|
||||
├── metadata.fr.yml # Localized metadata
|
||||
├── README.md # Documentation
|
||||
├── modes/ # Modes directory
|
||||
│ └── data-analyst/
|
||||
│ └── metadata.en.yml
|
||||
└── mcp servers/ # MCP servers directory
|
||||
└── data-processor/
|
||||
└── metadata.en.yml
|
||||
└── packages/
|
||||
└──data-toolkit/ # Your package directory
|
||||
├── metadata.en.yml # Package metadata
|
||||
├── metadata.fr.yml # Localized metadata
|
||||
├── README.md # Documentation
|
||||
├── modes/ # Modes directory
|
||||
│ └── data-analyst/
|
||||
│ └── metadata.en.yml
|
||||
└── mcp/ # MCP servers directory
|
||||
└── data-processor/
|
||||
└── metadata.en.yml
|
||||
```
|
||||
|
||||
### Required Fields
|
||||
|
|
@ -72,9 +73,9 @@ Roo-Code-Packages/
|
|||
- **name**: A clear, descriptive name for your component
|
||||
- **description**: A detailed explanation of what your component does
|
||||
- **version**: Semantic version number (e.g., "1.0.0")
|
||||
- **type**: Component type (one of: "package", "mode", "mcp server", "prompt")
|
||||
- **type**: Component type (one of: "package", "mode", "mcp", "prompt")
|
||||
- **tags**: (Optional) Array of relevant tags for filtering
|
||||
- **items**: (Only for packages) Array of subcomponents with their type and path - when the path is not in the packages directory
|
||||
- **items**: (Only for `package`) Array of subcomponents with their type and path - when the path is not in the packages directory
|
||||
tree
|
||||
- **author**: Your name
|
||||
- **authorUrl**: A proile Url that you want people to see. GitHub profile, or linked-in profile for example
|
||||
|
|
@ -103,7 +104,7 @@ items:
|
|||
path: "../shared-prompts/data-analysis"
|
||||
|
||||
# Component from a completely different part of the repository
|
||||
- type: "mcp server"
|
||||
- type: "mcp"
|
||||
path: "../../other-category/useful-server"
|
||||
```
|
||||
|
||||
|
|
@ -238,7 +239,7 @@ items:
|
|||
# External components (outside this package directory)
|
||||
- type: "prompt"
|
||||
path: "../shared-prompts/data-cleaning"
|
||||
- type: "mcp server"
|
||||
- type: "mcp"
|
||||
path: "../../ml-tools/model-trainer"
|
||||
- type: "mode"
|
||||
path: "../visualization-tools/chart-creator-mode"
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ cd your-package-repo
|
|||
4. Create the basic repository structure:
|
||||
|
||||
```bash
|
||||
mkdir -p packages modes "mcp servers" prompts
|
||||
mkdir -p packages modes "mcps" prompts
|
||||
touch metadata.en.yml
|
||||
```
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ repository-root/
|
|||
├── modes/ # Directory for mode components
|
||||
│ └── custom-mode/
|
||||
│ └── metadata.en.yml
|
||||
├── mcp servers/ # Directory for MCP server components
|
||||
├── mcps/ # Directory for MCP server components
|
||||
│ └── custom-server/
|
||||
│ └── metadata.en.yml
|
||||
└── prompts/ # Directory for prompt components
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
"src/exports/**",
|
||||
"src/schemas/ipc.ts",
|
||||
"src/extension.ts",
|
||||
"marketplace-template/**",
|
||||
"scripts/**"
|
||||
],
|
||||
"workspaces": {
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
# Marketplace Template
|
||||
|
||||
This template provides a basic structure for creating a marketplace source repository. The structure follows the required format for Roo Code's marketplace.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
/
|
||||
├── metadata.en.yml # Required: Repository metadata
|
||||
└── mcp servers/ # Required: At least one of: mcp servers, roles, storage systems, or items
|
||||
└── example-server/
|
||||
└── metadata.en.yml
|
||||
```
|
||||
|
||||
## Required Files
|
||||
|
||||
### Root metadata.en.yml
|
||||
|
||||
```yaml
|
||||
name: "Your Repository Name"
|
||||
description: "Your repository description"
|
||||
version: "1.0.0"
|
||||
```
|
||||
|
||||
### MCP Server metadata.en.yml
|
||||
|
||||
```yaml
|
||||
name: "Your MCP Server Name"
|
||||
description: "Your MCP server description"
|
||||
type: "mcp server"
|
||||
version: "1.0.0"
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. Copy this template to create your own marketplace repository
|
||||
2. Update the metadata.en.yml with your repository information
|
||||
3. Add your MCP servers, roles, or other components
|
||||
4. Each component must have its own metadata.en.yml file with the required fields
|
||||
|
||||
## Validation Requirements
|
||||
|
||||
- The root metadata.en.yml must have name, description, and version fields
|
||||
- Version must be in semver format (e.g., 1.0.0)
|
||||
- The repository must have at least one of: mcp servers, roles, storage systems, or items directories
|
||||
- Each component must have a metadata.en.yml with the required fields including the correct type
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
name: "Data Processor"
|
||||
description: "An MCP server for processing and transforming data files with support for various formats"
|
||||
type: "mcp server"
|
||||
version: "1.0.0"
|
||||
author: "JB Brown"
|
||||
authorUrl: "https://www.linkedin.com/in/jbbrown1/"
|
||||
tags: ["data-processing", "etl", "transformation", "data-engineering"]
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
const { MCPServer } = require("@modelcontextprotocol/core")
|
||||
|
||||
class DataProcessor extends MCPServer {
|
||||
constructor() {
|
||||
super({
|
||||
name: "Data Processor",
|
||||
description: "Processes and transforms data files",
|
||||
version: "1.0.0",
|
||||
capabilities: ["file-processing", "data-transformation"],
|
||||
})
|
||||
|
||||
this.registerHandler("process-file", this.processFile.bind(this))
|
||||
this.registerHandler("transform-data", this.transformData.bind(this))
|
||||
}
|
||||
|
||||
async processFile(context, params) {
|
||||
const { filePath, options } = params
|
||||
|
||||
// Validate parameters
|
||||
if (!filePath) {
|
||||
throw new Error("File path is required")
|
||||
}
|
||||
|
||||
try {
|
||||
// Read file content
|
||||
const content = await context.readFile(filePath)
|
||||
|
||||
// Process based on file type
|
||||
const fileType = this.detectFileType(filePath)
|
||||
const processedData = await this.processContent(content, fileType, options)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: processedData,
|
||||
metadata: {
|
||||
fileType,
|
||||
processedAt: new Date().toISOString(),
|
||||
rowCount: processedData.length,
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async transformData(context, params) {
|
||||
const { data, transformations } = params
|
||||
|
||||
// Validate parameters
|
||||
if (!data || !transformations) {
|
||||
throw new Error("Data and transformations are required")
|
||||
}
|
||||
|
||||
try {
|
||||
let transformedData = data
|
||||
|
||||
// Apply each transformation in sequence
|
||||
for (const transform of transformations) {
|
||||
transformedData = await this.applyTransformation(transformedData, transform)
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: transformedData,
|
||||
metadata: {
|
||||
transformations: transformations.map((t) => t.type),
|
||||
transformedAt: new Date().toISOString(),
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
detectFileType(filePath) {
|
||||
const extension = filePath.split(".").pop().toLowerCase()
|
||||
const fileTypes = {
|
||||
csv: "CSV",
|
||||
json: "JSON",
|
||||
xml: "XML",
|
||||
xlsx: "Excel",
|
||||
parquet: "Parquet",
|
||||
}
|
||||
return fileTypes[extension] || "Unknown"
|
||||
}
|
||||
|
||||
async processContent(content, fileType, options = {}) {
|
||||
// Implementation would handle different file types
|
||||
switch (fileType) {
|
||||
case "CSV":
|
||||
return this.processCSV(content, options)
|
||||
case "JSON":
|
||||
return this.processJSON(content, options)
|
||||
case "XML":
|
||||
return this.processXML(content, options)
|
||||
default:
|
||||
throw new Error(`Unsupported file type: ${fileType}`)
|
||||
}
|
||||
}
|
||||
|
||||
async applyTransformation(data, transform) {
|
||||
// Implementation would handle different transformation types
|
||||
switch (transform.type) {
|
||||
case "filter":
|
||||
return data.filter(transform.condition)
|
||||
case "map":
|
||||
return data.map(transform.mapper)
|
||||
case "aggregate":
|
||||
return this.aggregate(data, transform.aggregation)
|
||||
default:
|
||||
throw new Error(`Unsupported transformation: ${transform.type}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods for specific file types
|
||||
processCSV(content, options) {
|
||||
// CSV processing implementation
|
||||
return []
|
||||
}
|
||||
|
||||
processJSON(content, options) {
|
||||
// JSON processing implementation
|
||||
return []
|
||||
}
|
||||
|
||||
processXML(content, options) {
|
||||
// XML processing implementation
|
||||
return []
|
||||
}
|
||||
|
||||
aggregate(data, aggregation) {
|
||||
// Aggregation implementation
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new DataProcessor()
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
name: "Data Engineering Tools"
|
||||
description: "A collection of data engineering roles and tools"
|
||||
version: "1.0.0"
|
||||
author: "JB Brown"
|
||||
authorUrl: "https://www.linkedin.com/in/jbbrown1/"
|
||||
tags: ["data-engineering", "data", "analytics"]
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
name: "Data Engineer"
|
||||
description: "A mode focused on building and maintaining data pipelines, ETL processes, and data infrastructure"
|
||||
type: "mode"
|
||||
version: "1.0.0"
|
||||
author: "JB Brown"
|
||||
authorUrl: "https://www.linkedin.com/in/jbbrown1/"
|
||||
tags: ["data-engineering", "etl", "pipelines", "infrastructure"]
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
# Data Engineer Mode
|
||||
|
||||
## Mode Description
|
||||
|
||||
As a Data Engineer, you are responsible for designing, building, and maintaining data pipelines and infrastructure. You work closely with data scientists and analysts to ensure data is accessible, reliable, and efficient.
|
||||
|
||||
## Core Responsibilities
|
||||
|
||||
- Design and implement data pipelines
|
||||
- Build ETL processes
|
||||
- Maintain data infrastructure
|
||||
- Optimize data delivery
|
||||
- Ensure data quality and reliability
|
||||
- Implement data security measures
|
||||
|
||||
## Technical Skills
|
||||
|
||||
- SQL and database management
|
||||
- ETL tools and processes
|
||||
- Data warehousing
|
||||
- Big data technologies
|
||||
- Python/Scala programming
|
||||
- Cloud platforms (AWS, GCP, Azure)
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Document all data pipelines
|
||||
2. Implement data validation checks
|
||||
3. Monitor pipeline performance
|
||||
4. Follow data security protocols
|
||||
5. Maintain data lineage
|
||||
6. Practice data governance
|
||||
|
||||
## Collaboration Guidelines
|
||||
|
||||
- Work closely with data scientists
|
||||
- Coordinate with infrastructure teams
|
||||
- Support analytics teams
|
||||
- Engage with business stakeholders
|
||||
|
||||
## Success Metrics
|
||||
|
||||
- Pipeline reliability
|
||||
- Data quality scores
|
||||
- Query performance
|
||||
- System uptime
|
||||
- Data freshness
|
||||
- Issue resolution time
|
||||
|
||||
## Tools and Technologies
|
||||
|
||||
- ETL Tools: Apache Airflow, dbt
|
||||
- Databases: PostgreSQL, MongoDB
|
||||
- Big Data: Spark, Hadoop
|
||||
- Cloud: AWS EMR, GCP Dataflow
|
||||
- Languages: Python, SQL, Scala
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
name: "Example MCP Server"
|
||||
description: "An example MCP server for testing marketplace functionality"
|
||||
type: "mcp server"
|
||||
version: "1.0.0"
|
||||
author: "JB Brown"
|
||||
authorUrl: "https://www.linkedin.com/in/jbbrown1/"
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
name: "File Analyzer MCP Server"
|
||||
description: "An MCP server that analyzes files for code quality, security issues, and performance optimizations"
|
||||
type: "mcp server"
|
||||
version: "1.0.0"
|
||||
author: "JB Brown"
|
||||
authorUrl: "https://www.linkedin.com/in/jbbrown1/"
|
||||
tags: ["file-analyzer", "code-quality", "security", "performance"]
|
||||
sourceUrl: "https://github.com/roo-team/file-analyzer-server"
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
/**
|
||||
* File Analyzer MCP Server
|
||||
*
|
||||
* This MCP server analyzes files for code quality, security issues, and performance optimizations.
|
||||
*/
|
||||
|
||||
const { createServer } = require("@modelcontextprotocol/server")
|
||||
|
||||
// Create an MCP server
|
||||
const server = createServer({
|
||||
name: "file-analyzer",
|
||||
description: "Analyzes files for code quality, security issues, and performance optimizations",
|
||||
version: "1.0.0",
|
||||
tools: [
|
||||
{
|
||||
name: "analyze_file",
|
||||
description: "Analyzes a file for code quality, security issues, and performance optimizations",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file_path: {
|
||||
type: "string",
|
||||
description: "Path to the file to analyze",
|
||||
},
|
||||
analysis_type: {
|
||||
type: "string",
|
||||
enum: ["quality", "security", "performance", "all"],
|
||||
description: "Type of analysis to perform",
|
||||
},
|
||||
},
|
||||
required: ["file_path"],
|
||||
},
|
||||
handler: async ({ file_path, analysis_type = "all" }) => {
|
||||
try {
|
||||
// In a real implementation, this would use actual code analysis tools
|
||||
// For this example, we'll just return some mock results
|
||||
|
||||
const mockResults = {
|
||||
quality: {
|
||||
issues: [
|
||||
{
|
||||
severity: "warning",
|
||||
message: "Function is too complex (cyclomatic complexity: 15)",
|
||||
line: 42,
|
||||
},
|
||||
{
|
||||
severity: "info",
|
||||
message: "Consider adding more comments to this section",
|
||||
line: 78,
|
||||
},
|
||||
],
|
||||
score: 85,
|
||||
},
|
||||
security: {
|
||||
issues: [
|
||||
{ severity: "critical", message: "Potential SQL injection vulnerability", line: 123 },
|
||||
{ severity: "warning", message: "Insecure random number generation", line: 56 },
|
||||
],
|
||||
score: 70,
|
||||
},
|
||||
performance: {
|
||||
issues: [
|
||||
{ severity: "warning", message: "Inefficient loop could be optimized", line: 92 },
|
||||
{ severity: "info", message: "Consider memoizing this function", line: 105 },
|
||||
],
|
||||
score: 90,
|
||||
},
|
||||
}
|
||||
|
||||
// Return only the requested analysis types
|
||||
if (analysis_type === "all") {
|
||||
return {
|
||||
file_path,
|
||||
results: mockResults,
|
||||
summary: "Analysis complete. Found issues in quality, security, and performance.",
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
file_path,
|
||||
results: { [analysis_type]: mockResults[analysis_type] },
|
||||
summary: `Analysis complete. Found ${mockResults[analysis_type].issues.length} issues in ${analysis_type}.`,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
error: `Failed to analyze file: ${error.message}`,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "get_file_stats",
|
||||
description: "Gets statistics about a file",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file_path: {
|
||||
type: "string",
|
||||
description: "Path to the file to get statistics for",
|
||||
},
|
||||
},
|
||||
required: ["file_path"],
|
||||
},
|
||||
handler: async ({ file_path }) => {
|
||||
try {
|
||||
// In a real implementation, this would use actual file system operations
|
||||
// For this example, we'll just return some mock results
|
||||
|
||||
return {
|
||||
file_path,
|
||||
stats: {
|
||||
lines_of_code: 250,
|
||||
comment_lines: 45,
|
||||
blank_lines: 30,
|
||||
functions: 12,
|
||||
classes: 3,
|
||||
complexity: "medium",
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
error: `Failed to get file statistics: ${error.message}`,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Start the server
|
||||
server.listen(3000, () => {
|
||||
console.log("File Analyzer MCP server is running on port 3000")
|
||||
})
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
name: "Marketplace Template"
|
||||
description: "A template repository for creating marketplace sources"
|
||||
version: "1.0.0"
|
||||
author: "JB Brown"
|
||||
authorUrl: "https://www.linkedin.com/in/jbbrown1/"
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
name: "Full-Stack Developer Mode"
|
||||
description: "A mode for a full-stack developer with expertise in web development, databases, and APIs"
|
||||
type: "mode"
|
||||
version: "1.0.0"
|
||||
author: "JB Brown"
|
||||
authorUrl: "https://www.linkedin.com/in/jbbrown1/"
|
||||
tags: ["developer", "full-stack", "web", "database", "api"]
|
||||
sourceUrl: "https://github.com/roo-team/developer-resources"
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
# Full-Stack Developer Mode
|
||||
|
||||
## Mode Description
|
||||
|
||||
You are a Full-Stack Developer with expertise in web development, databases, and APIs. You excel at building complete web applications from front-end to back-end, with a focus on creating robust, scalable, and maintainable code.
|
||||
|
||||
## Skills and Expertise
|
||||
|
||||
- **Front-End Development**: HTML, CSS, JavaScript, TypeScript, React, Vue, Angular
|
||||
- **Back-End Development**: Node.js, Python, Java, C#, Ruby
|
||||
- **Database Management**: SQL (PostgreSQL, MySQL), NoSQL (MongoDB, Redis)
|
||||
- **API Development**: REST, GraphQL, WebSockets
|
||||
- **DevOps**: Docker, Kubernetes, CI/CD pipelines
|
||||
- **Testing**: Unit testing, integration testing, end-to-end testing
|
||||
- **Version Control**: Git, GitHub, GitLab
|
||||
|
||||
## Responsibilities
|
||||
|
||||
1. Implement user interfaces based on design specifications
|
||||
2. Develop server-side logic and APIs
|
||||
3. Design and implement database schemas
|
||||
4. Integrate front-end and back-end components
|
||||
5. Optimize applications for performance and scalability
|
||||
6. Write clean, maintainable, and well-documented code
|
||||
7. Collaborate with other team members to ensure cohesive development
|
||||
|
||||
## Communication Style
|
||||
|
||||
- Clear and concise technical explanations
|
||||
- Proactive in identifying potential issues
|
||||
- Collaborative approach to problem-solving
|
||||
- Detailed documentation of code and processes
|
||||
|
||||
## Problem-Solving Approach
|
||||
|
||||
1. Understand the requirements thoroughly
|
||||
2. Break down complex problems into manageable components
|
||||
3. Research and evaluate potential solutions
|
||||
4. Implement the most appropriate solution
|
||||
5. Test thoroughly to ensure quality
|
||||
6. Document the solution and any lessons learned
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Follow coding standards and style guides
|
||||
- Write comprehensive tests
|
||||
- Use meaningful variable and function names
|
||||
- Keep functions small and focused
|
||||
- Document code and APIs
|
||||
- Regularly refactor code to improve quality
|
||||
- Stay updated with the latest technologies and best practices
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
name: "Data Validator"
|
||||
description: "An MCP server for validating data quality, schema compliance, and business rules"
|
||||
type: "mcp server"
|
||||
version: "1.0.0"
|
||||
author: "JB Brown"
|
||||
authorUrl: "https://www.linkedin.com/in/jbbrown1/"
|
||||
tags: ["validation", "data-quality", "schema", "rules-engine", "data-platform"]
|
||||
sourceUrl: "https://github.com/example/data-platform/mcp servers/data-validator"
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
const { MCPServer } = require("@modelcontextprotocol/core")
|
||||
|
||||
class DataValidator extends MCPServer {
|
||||
constructor() {
|
||||
super({
|
||||
name: "Data Validator",
|
||||
description: "Validates data quality and schema compliance",
|
||||
version: "1.0.0",
|
||||
capabilities: ["schema-validation", "data-quality", "rules-engine"],
|
||||
})
|
||||
|
||||
this.registerHandler("validate-schema", this.validateSchema.bind(this))
|
||||
this.registerHandler("validate-quality", this.validateQuality.bind(this))
|
||||
this.registerHandler("validate-rules", this.validateRules.bind(this))
|
||||
}
|
||||
|
||||
async validateSchema(context, params) {
|
||||
const { data, schema } = params
|
||||
|
||||
if (!data || !schema) {
|
||||
throw new Error("Data and schema are required")
|
||||
}
|
||||
|
||||
try {
|
||||
const validationResults = await this.performSchemaValidation(data, schema)
|
||||
return {
|
||||
success: validationResults.valid,
|
||||
errors: validationResults.errors,
|
||||
metadata: {
|
||||
schemaVersion: schema.version,
|
||||
validatedAt: new Date().toISOString(),
|
||||
recordCount: Array.isArray(data) ? data.length : 1,
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async validateQuality(context, params) {
|
||||
const { data, rules } = params
|
||||
|
||||
if (!data || !rules) {
|
||||
throw new Error("Data and quality rules are required")
|
||||
}
|
||||
|
||||
try {
|
||||
const qualityResults = await this.performQualityChecks(data, rules)
|
||||
return {
|
||||
success: qualityResults.passed,
|
||||
issues: qualityResults.issues,
|
||||
metrics: qualityResults.metrics,
|
||||
metadata: {
|
||||
rulesApplied: rules.length,
|
||||
checkedAt: new Date().toISOString(),
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async validateRules(context, params) {
|
||||
const { data, businessRules } = params
|
||||
|
||||
if (!data || !businessRules) {
|
||||
throw new Error("Data and business rules are required")
|
||||
}
|
||||
|
||||
try {
|
||||
const ruleResults = await this.evaluateBusinessRules(data, businessRules)
|
||||
return {
|
||||
success: ruleResults.passed,
|
||||
violations: ruleResults.violations,
|
||||
metadata: {
|
||||
rulesEvaluated: businessRules.length,
|
||||
evaluatedAt: new Date().toISOString(),
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async performSchemaValidation(data, schema) {
|
||||
// Implementation would validate data against the provided schema
|
||||
return {
|
||||
valid: true,
|
||||
errors: [],
|
||||
}
|
||||
}
|
||||
|
||||
async performQualityChecks(data, rules) {
|
||||
// Implementation would check data quality based on rules
|
||||
return {
|
||||
passed: true,
|
||||
issues: [],
|
||||
metrics: {
|
||||
completeness: 100,
|
||||
accuracy: 100,
|
||||
consistency: 100,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async evaluateBusinessRules(data, rules) {
|
||||
// Implementation would evaluate business rules against the data
|
||||
return {
|
||||
passed: true,
|
||||
violations: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new DataValidator()
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
name: "Data Platform Package"
|
||||
description: "A complete data platform solution including roles, servers, and storage systems"
|
||||
type: "package"
|
||||
version: "1.0.0"
|
||||
author: "JB Brown"
|
||||
authorUrl: "https://www.linkedin.com/in/jbbrown1/"
|
||||
tags: ["data-platform", "enterprise", "data-engineering", "complete-solution"]
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
name: "Paquete de Plataforma de Datos"
|
||||
description: "Una solución completa de plataforma de datos que incluye roles, servidores y sistemas de almacenamiento"
|
||||
type: "package"
|
||||
version: "1.0.0"
|
||||
tags: ["plataforma-datos", "empresarial", "ingenieria-datos", "solucion-completa"]
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
name: "データプラットフォームパッケージ"
|
||||
description: "ロール、サーバー、ストレージシステムを含む完全なデータプラットフォームソリューション"
|
||||
type: "package"
|
||||
version: "1.0.0"
|
||||
tags: ["データプラットフォーム", "エンタープライズ", "データエンジニアリング", "完全ソリューション"]
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
name: "Data Platform Administrator"
|
||||
description: "Administrative mode responsible for managing and maintaining the data platform infrastructure"
|
||||
type: "mode"
|
||||
version: "1.0.0"
|
||||
author: "JB Brown"
|
||||
authorUrl: "https://www.linkedin.com/in/jbbrown1/"
|
||||
tags: ["admin", "platform", "infrastructure", "data-platform"]
|
||||
sourceUrl: "https://github.com/example/data-platform/modes/platform-admin"
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
# Data Platform Administrator
|
||||
|
||||
## Mode Overview
|
||||
|
||||
The Data Platform Administrator is responsible for the overall management, security, and maintenance of the data platform infrastructure. This mode ensures the platform's reliability, performance, and compliance with organizational standards.
|
||||
|
||||
## Key Responsibilities
|
||||
|
||||
### Platform Management
|
||||
|
||||
- Configure and maintain data platform components
|
||||
- Monitor system performance and health
|
||||
- Implement and maintain backup/recovery procedures
|
||||
- Manage platform upgrades and patches
|
||||
- Handle capacity planning and scaling
|
||||
|
||||
### Security Administration
|
||||
|
||||
- Manage user access and permissions
|
||||
- Implement security policies and controls
|
||||
- Monitor security logs and alerts
|
||||
- Conduct security audits
|
||||
- Ensure compliance with data protection regulations
|
||||
|
||||
### Infrastructure Operations
|
||||
|
||||
- Deploy and configure platform services
|
||||
- Manage cloud resources and infrastructure
|
||||
- Optimize platform performance
|
||||
- Implement high availability solutions
|
||||
- Monitor resource utilization
|
||||
|
||||
### Team Support
|
||||
|
||||
- Provide technical guidance to platform users
|
||||
- Troubleshoot platform issues
|
||||
- Create and maintain documentation
|
||||
- Train team members on platform features
|
||||
- Collaborate with development teams
|
||||
|
||||
## Required Skills
|
||||
|
||||
### Technical Skills
|
||||
|
||||
- Cloud platform expertise (AWS/Azure/GCP)
|
||||
- Infrastructure as Code (Terraform, CloudFormation)
|
||||
- Container orchestration (Kubernetes)
|
||||
- Database administration
|
||||
- Security and compliance
|
||||
- Monitoring and logging systems
|
||||
- Automation and scripting
|
||||
|
||||
### Soft Skills
|
||||
|
||||
- Problem-solving
|
||||
- Communication
|
||||
- Team leadership
|
||||
- Project management
|
||||
- Time management
|
||||
- Documentation
|
||||
|
||||
## Tools and Technologies
|
||||
|
||||
### Infrastructure
|
||||
|
||||
- Kubernetes
|
||||
- Docker
|
||||
- Terraform
|
||||
- Git
|
||||
- CI/CD tools
|
||||
|
||||
### Monitoring
|
||||
|
||||
- Prometheus
|
||||
- Grafana
|
||||
- ELK Stack
|
||||
- CloudWatch/StackDriver
|
||||
|
||||
### Security
|
||||
|
||||
- IAM systems
|
||||
- Security scanning tools
|
||||
- Compliance frameworks
|
||||
- Encryption tools
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Infrastructure as Code
|
||||
|
||||
- Maintain all infrastructure configurations in version control
|
||||
- Use automated deployment processes
|
||||
- Document all configuration changes
|
||||
|
||||
2. Security First
|
||||
|
||||
- Follow principle of least privilege
|
||||
- Regularly review access permissions
|
||||
- Implement multi-layer security controls
|
||||
- Conduct regular security audits
|
||||
|
||||
3. Monitoring and Alerting
|
||||
|
||||
- Set up comprehensive monitoring
|
||||
- Configure meaningful alerts
|
||||
- Maintain incident response procedures
|
||||
- Regular review of metrics and logs
|
||||
|
||||
4. Documentation
|
||||
- Keep documentation up-to-date
|
||||
- Document all procedures
|
||||
- Maintain runbooks for common issues
|
||||
- Create user guides for platform features
|
||||
|
||||
## Success Metrics
|
||||
|
||||
- Platform uptime and availability
|
||||
- Security incident response time
|
||||
- User satisfaction scores
|
||||
- Resource utilization efficiency
|
||||
- Backup and recovery success rates
|
||||
- Compliance audit results
|
||||
|
||||
## Collaboration
|
||||
|
||||
- Work with development teams
|
||||
- Coordinate with security teams
|
||||
- Support data engineering teams
|
||||
- Engage with business stakeholders
|
||||
- Partner with cloud providers
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
name: Test Source URL
|
||||
description: A test package with source URL
|
||||
type: package
|
||||
version: 1.0.0
|
||||
sourceUrl: https://example.com/test-package
|
||||
tags:
|
||||
- test
|
||||
- source-url
|
||||
|
|
@ -3,7 +3,7 @@ import { ClineProvider } from "./ClineProvider"
|
|||
import { WebviewMessage } from "../../shared/WebviewMessage"
|
||||
import {
|
||||
MarketplaceManager,
|
||||
ComponentType,
|
||||
MarketplaceItemType,
|
||||
MarketplaceSource,
|
||||
validateSources,
|
||||
ValidationError,
|
||||
|
|
@ -87,7 +87,7 @@ export async function handleMarketplaceMessages(
|
|||
marketplaceManager.isFetching = false
|
||||
}
|
||||
|
||||
// The items are already stored in PackageManagerManager's currentItems
|
||||
// The items are already stored in MarketplaceManager's currentItems
|
||||
// No need to store in global state
|
||||
|
||||
// Send state to webview
|
||||
|
|
@ -100,7 +100,7 @@ export async function handleMarketplaceMessages(
|
|||
type: "state",
|
||||
text: errorMessage,
|
||||
})
|
||||
// The state will already be updated with empty items by PackageManagerManager
|
||||
// The state will already be updated with empty items by MarketplaceManager
|
||||
await provider.postStateToWebview()
|
||||
marketplaceManager.isFetching = false
|
||||
}
|
||||
|
|
@ -197,7 +197,7 @@ export async function handleMarketplaceMessages(
|
|||
try {
|
||||
// Update filtered items and post state
|
||||
marketplaceManager.updateWithFilteredItems({
|
||||
type: message.filters.type as ComponentType | undefined,
|
||||
type: message.filters.type as MarketplaceItemType | undefined,
|
||||
search: message.filters.search,
|
||||
tags: message.filters.tags,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type-group": {
|
||||
"modes": "Modes",
|
||||
"mcp-servers": "Servidors MCP",
|
||||
"mcps": "Servidors MCP",
|
||||
"prompts": "Prompts",
|
||||
"packages": "Paquets",
|
||||
"generic-type": "{{type}}s",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"item-card": {
|
||||
"type-mode": "Mode",
|
||||
"type-mcp-server": "Servidor MCP",
|
||||
"type-mcp": "Servidor MCP",
|
||||
"type-prompt": "Prompt",
|
||||
"type-package": "Paquet",
|
||||
"type-other": "Altres",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type-group": {
|
||||
"modes": "Modi",
|
||||
"mcp-servers": "MCP-Server",
|
||||
"mcps": "MCP-Server",
|
||||
"prompts": "Prompts",
|
||||
"packages": "Pakete",
|
||||
"generic-type": "{{type}}",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"item-card": {
|
||||
"type-mode": "Modus",
|
||||
"type-mcp-server": "MCP-Server",
|
||||
"type-mcp": "MCP-Server",
|
||||
"type-prompt": "Prompt",
|
||||
"type-package": "Paket",
|
||||
"type-other": "Sonstiges",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type-group": {
|
||||
"modes": "Modes",
|
||||
"mcp-servers": "MCP Servers",
|
||||
"mcps": "MCP Servers",
|
||||
"prompts": "Prompts",
|
||||
"packages": "Packages",
|
||||
"generic-type": "{{type}}s",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"item-card": {
|
||||
"type-mode": "Mode",
|
||||
"type-mcp-server": "MCP Server",
|
||||
"type-mcp": "MCP Server",
|
||||
"type-prompt": "Prompt",
|
||||
"type-package": "Package",
|
||||
"type-other": "Other",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type-group": {
|
||||
"modes": "Modos",
|
||||
"mcp-servers": "Servidores MCP",
|
||||
"mcps": "Servidores MCP",
|
||||
"prompts": "Prompts",
|
||||
"packages": "Paquetes",
|
||||
"generic-type": "{{type}}s",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"item-card": {
|
||||
"type-mode": "Modo",
|
||||
"type-mcp-server": "Servidor MCP",
|
||||
"type-mcp": "Servidor MCP",
|
||||
"type-prompt": "Prompt",
|
||||
"type-package": "Paquete",
|
||||
"type-other": "Otro",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type-group": {
|
||||
"modes": "Modes",
|
||||
"mcp-servers": "Serveurs MCP",
|
||||
"mcps": "Serveurs MCP",
|
||||
"prompts": "Prompts",
|
||||
"packages": "Paquets",
|
||||
"generic-type": "{{type}}s",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"item-card": {
|
||||
"type-mode": "Mode",
|
||||
"type-mcp-server": "Serveur MCP",
|
||||
"type-mcp": "Serveur MCP",
|
||||
"type-prompt": "Prompt",
|
||||
"type-package": "Paquet",
|
||||
"type-other": "Autre",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type-group": {
|
||||
"modes": "मोड",
|
||||
"mcp-servers": "एमसीपी सर्वर",
|
||||
"mcps": "एमसीपी सर्वर",
|
||||
"prompts": "प्रॉम्प्ट्स",
|
||||
"packages": "पैकेज",
|
||||
"generic-type": "{{type}}",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"item-card": {
|
||||
"type-mode": "मोड",
|
||||
"type-mcp-server": "एमसीपी सर्वर",
|
||||
"type-mcp": "एमसीपी सर्वर",
|
||||
"type-prompt": "प्रॉम्प्ट",
|
||||
"type-package": "पैकेज",
|
||||
"type-other": "अन्य",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type-group": {
|
||||
"modes": "Modalità",
|
||||
"mcp-servers": "Server MCP",
|
||||
"mcps": "Server MCP",
|
||||
"prompts": "Prompt",
|
||||
"packages": "Pacchetti",
|
||||
"generic-type": "{{type}}",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"item-card": {
|
||||
"type-mode": "Modalità",
|
||||
"type-mcp-server": "Server MCP",
|
||||
"type-mcp": "Server MCP",
|
||||
"type-prompt": "Prompt",
|
||||
"type-package": "Pacchetto",
|
||||
"type-other": "Altro",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type-group": {
|
||||
"modes": "モード",
|
||||
"mcp-servers": "MCPサーバー",
|
||||
"mcps": "MCPサーバー",
|
||||
"prompts": "プロンプト",
|
||||
"packages": "パッケージ",
|
||||
"generic-type": "{{type}}",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"item-card": {
|
||||
"type-mode": "モード",
|
||||
"type-mcp-server": "MCPサーバー",
|
||||
"type-mcp": "MCPサーバー",
|
||||
"type-prompt": "プロンプト",
|
||||
"type-package": "パッケージ",
|
||||
"type-other": "その他",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type-group": {
|
||||
"modes": "모드",
|
||||
"mcp-servers": "MCP 서버",
|
||||
"mcps": "MCP 서버",
|
||||
"prompts": "프롬프트",
|
||||
"packages": "패키지",
|
||||
"generic-type": "{{type}}",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"item-card": {
|
||||
"type-mode": "모드",
|
||||
"type-mcp-server": "MCP 서버",
|
||||
"type-mcp": "MCP 서버",
|
||||
"type-prompt": "프롬프트",
|
||||
"type-package": "패키지",
|
||||
"type-other": "기타",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type-group": {
|
||||
"modes": "Tryby",
|
||||
"mcp-servers": "Serwery MCP",
|
||||
"mcps": "Serwery MCP",
|
||||
"prompts": "Podpowiedzi",
|
||||
"packages": "Pakiety",
|
||||
"generic-type": "{{type}}y",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"item-card": {
|
||||
"type-mode": "Tryb",
|
||||
"type-mcp-server": "Serwer MCP",
|
||||
"type-mcp": "Serwer MCP",
|
||||
"type-prompt": "Podpowiedź",
|
||||
"type-package": "Pakiet",
|
||||
"type-other": "Inne",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type-group": {
|
||||
"modes": "Modos",
|
||||
"mcp-servers": "Servidores MCP",
|
||||
"mcps": "Servidores MCP",
|
||||
"prompts": "Prompts",
|
||||
"packages": "Pacotes",
|
||||
"generic-type": "{{type}}s",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"item-card": {
|
||||
"type-mode": "Modo",
|
||||
"type-mcp-server": "Servidor MCP",
|
||||
"type-mcp": "Servidor MCP",
|
||||
"type-prompt": "Prompt",
|
||||
"type-package": "Pacote",
|
||||
"type-other": "Outro",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type-group": {
|
||||
"modes": "Modlar",
|
||||
"mcp-servers": "MCP Sunucuları",
|
||||
"mcps": "MCP Sunucuları",
|
||||
"prompts": "Komutlar",
|
||||
"packages": "Paketler",
|
||||
"generic-type": "{{type}}lar",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"item-card": {
|
||||
"type-mode": "Mod",
|
||||
"type-mcp-server": "MCP Sunucusu",
|
||||
"type-mcp": "MCP Sunucusu",
|
||||
"type-prompt": "Komut",
|
||||
"type-package": "Paket",
|
||||
"type-other": "Diğer",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type-group": {
|
||||
"modes": "Chế độ",
|
||||
"mcp-servers": "Máy chủ MCP",
|
||||
"mcps": "Máy chủ MCP",
|
||||
"prompts": "Gợi ý",
|
||||
"packages": "Gói",
|
||||
"generic-type": "{{type}}",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"item-card": {
|
||||
"type-mode": "Chế độ",
|
||||
"type-mcp-server": "Máy chủ MCP",
|
||||
"type-mcp": "Máy chủ MCP",
|
||||
"type-prompt": "Gợi ý",
|
||||
"type-package": "Gói",
|
||||
"type-other": "Khác",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type-group": {
|
||||
"modes": "模式",
|
||||
"mcp-servers": "MCP服务器",
|
||||
"mcps": "MCP服务器",
|
||||
"prompts": "提示",
|
||||
"packages": "包",
|
||||
"generic-type": "{{type}}",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"item-card": {
|
||||
"type-mode": "模式",
|
||||
"type-mcp-server": "MCP服务器",
|
||||
"type-mcp": "MCP服务器",
|
||||
"type-prompt": "提示",
|
||||
"type-package": "包",
|
||||
"type-other": "其他",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type-group": {
|
||||
"modes": "模式",
|
||||
"mcp-servers": "MCP伺服器",
|
||||
"mcps": "MCP伺服器",
|
||||
"prompts": "提示",
|
||||
"packages": "套件",
|
||||
"generic-type": "{{type}}",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"item-card": {
|
||||
"type-mode": "模式",
|
||||
"type-mcp-server": "MCP伺服器",
|
||||
"type-mcp": "MCP伺服器",
|
||||
"type-prompt": "提示",
|
||||
"type-package": "套件",
|
||||
"type-other": "其他",
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ export class GitFetcher {
|
|||
// Get current branch using existing git instance
|
||||
const branch = (await this.git?.revparse(["--abbrev-ref", "HEAD"])) || "main"
|
||||
|
||||
const items = await this.parsePackageManagerItems(repoDir, repoUrl, sourceName || metadata.name)
|
||||
const items = await this.parseMarketplaceItems(repoDir, repoUrl, sourceName || metadata.name)
|
||||
|
||||
return {
|
||||
metadata,
|
||||
|
|
@ -294,7 +294,7 @@ export class GitFetcher {
|
|||
* @param sourceName Source repository name
|
||||
* @returns Array of marketplace items
|
||||
*/
|
||||
private async parsePackageManagerItems(
|
||||
private async parseMarketplaceItems(
|
||||
repoDir: string,
|
||||
repoUrl: string,
|
||||
sourceName: string,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
MarketplaceItem,
|
||||
MarketplaceRepository,
|
||||
MarketplaceSource,
|
||||
ComponentType,
|
||||
MarketplaceItemType,
|
||||
ComponentMetadata,
|
||||
LocalizationOptions,
|
||||
InstallMarketplaceItemOptions,
|
||||
|
|
@ -315,7 +315,7 @@ export class MarketplaceManager {
|
|||
*/
|
||||
filterItems(
|
||||
items: MarketplaceItem[],
|
||||
filters: { type?: ComponentType; search?: string; tags?: string[] },
|
||||
filters: { type?: MarketplaceItemType; search?: string; tags?: string[] },
|
||||
): MarketplaceItem[] {
|
||||
// Create cache key from filters
|
||||
const cacheKey = JSON.stringify(filters)
|
||||
|
|
@ -349,7 +349,7 @@ export class MarketplaceManager {
|
|||
*/
|
||||
private processItemBatch(
|
||||
batch: MarketplaceItem[],
|
||||
filters: { type?: ComponentType; search?: string; tags?: string[] },
|
||||
filters: { type?: MarketplaceItemType; search?: string; tags?: string[] },
|
||||
): MarketplaceItem[] {
|
||||
// Helper functions
|
||||
const normalizeText = (text: string) => text.toLowerCase().replace(/\s+/g, " ").trim()
|
||||
|
|
@ -514,7 +514,11 @@ export class MarketplaceManager {
|
|||
* @param filters The filter criteria
|
||||
* @returns Filtered items
|
||||
*/
|
||||
updateWithFilteredItems(filters: { type?: ComponentType; search?: string; tags?: string[] }): MarketplaceItem[] {
|
||||
updateWithFilteredItems(filters: {
|
||||
type?: MarketplaceItemType
|
||||
search?: string
|
||||
tags?: string[]
|
||||
}): MarketplaceItem[] {
|
||||
const filteredItems = this.filterItems(this.currentItems, filters)
|
||||
this.currentItems = filteredItems
|
||||
return filteredItems
|
||||
|
|
@ -538,7 +542,7 @@ export class MarketplaceManager {
|
|||
private getSortValue(
|
||||
item:
|
||||
| MarketplaceItem
|
||||
| { type: ComponentType; path: string; metadata?: ComponentMetadata; lastUpdated?: string },
|
||||
| { type: MarketplaceItemType; path: string; metadata?: ComponentMetadata; lastUpdated?: string },
|
||||
sortBy: keyof Pick<MarketplaceItem, "name" | "author" | "lastUpdated">,
|
||||
): string {
|
||||
if ("metadata" in item && item.metadata) {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { SimpleGit } from "simple-git"
|
|||
import { validateAnyMetadata } from "./schemas"
|
||||
import {
|
||||
ComponentMetadata,
|
||||
ComponentType,
|
||||
MarketplaceItemType,
|
||||
LocalizationOptions,
|
||||
LocalizedMetadata,
|
||||
MarketplaceItem,
|
||||
|
|
@ -76,7 +76,7 @@ export class MetadataScanner {
|
|||
const localizedMetadata = metadata ? this.getLocalizedMetadata(metadata) : null
|
||||
|
||||
if (localizedMetadata) {
|
||||
const item = await this.createPackageManagerItem(
|
||||
const item = await this.createMarketplaceItem(
|
||||
localizedMetadata,
|
||||
componentDir,
|
||||
repoUrl,
|
||||
|
|
@ -194,8 +194,8 @@ export class MetadataScanner {
|
|||
// Add type field if missing but has a parent directory indicating type
|
||||
if (!parsed.type) {
|
||||
const parentDir = path.basename(componentDir)
|
||||
if (parentDir === "mcp servers" || parentDir === "mcp-servers") {
|
||||
parsed.type = "mcp server"
|
||||
if (parentDir === "mcps") {
|
||||
parsed.type = "mcp"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -220,14 +220,14 @@ export class MetadataScanner {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a PackageManagerItem from component metadata
|
||||
* Creates a MarketplaceItem from component metadata
|
||||
* @param metadata The component metadata
|
||||
* @param componentDir The component directory
|
||||
* @param repoUrl The repository URL
|
||||
* @param sourceName Optional source repository name
|
||||
* @returns PackageManagerItem or null if invalid
|
||||
* @returns MarketplaceItem or null if invalid
|
||||
*/
|
||||
private async createPackageManagerItem(
|
||||
private async createMarketplaceItem(
|
||||
metadata: ComponentMetadata,
|
||||
componentDir: string,
|
||||
repoUrl: string,
|
||||
|
|
@ -235,7 +235,7 @@ export class MetadataScanner {
|
|||
sourceName?: string,
|
||||
): Promise<MarketplaceItem | null> {
|
||||
// Skip if no type or invalid type
|
||||
if (!metadata.type || !this.isValidComponentType(metadata.type)) {
|
||||
if (!metadata.type || !this.isValidMarketplaceItemType(metadata.type)) {
|
||||
return null
|
||||
}
|
||||
// Always use the original root directory for path calculations
|
||||
|
|
@ -393,8 +393,8 @@ export class MetadataScanner {
|
|||
* @param type The type to check
|
||||
* @returns Whether the type is valid
|
||||
*/
|
||||
private isValidComponentType(type: string): type is ComponentType {
|
||||
return ["role", "mcp server", "storage", "mode", "prompt", "package"].includes(type)
|
||||
private isValidMarketplaceItemType(type: string): type is MarketplaceItemType {
|
||||
return ["role", "mcp", "storage", "mode", "prompt", "package"].includes(type)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { MarketplaceManager } from "../MarketplaceManager"
|
||||
import { MarketplaceItem, MarketplaceSource, MarketplaceRepository, ComponentType } from "../types"
|
||||
import { MarketplaceItem, MarketplaceSource, MarketplaceRepository, MarketplaceItemType } from "../types"
|
||||
import { MetadataScanner } from "../MetadataScanner"
|
||||
import { GitFetcher } from "../GitFetcher"
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
describe("PackageManagerManager", () => {
|
||||
describe("MarketplaceManager", () => {
|
||||
describe("filterItems", () => {
|
||||
// Create a mock context with required properties
|
||||
const mockContext = {
|
||||
|
|
@ -74,7 +74,7 @@ describe("PackageManagerManager", () => {
|
|||
{
|
||||
name: "Server Item",
|
||||
description: "A server",
|
||||
type: "mcp server",
|
||||
type: "mcp",
|
||||
url: "test2",
|
||||
repoUrl: "test2",
|
||||
},
|
||||
|
|
@ -111,14 +111,6 @@ describe("PackageManagerManager", () => {
|
|||
})
|
||||
|
||||
let manager: MarketplaceManager
|
||||
let metadataScanner: MetadataScanner
|
||||
|
||||
beforeAll(async () => {
|
||||
// Load real data from the template
|
||||
const templatePath = path.resolve(__dirname, "../../../../marketplace-template")
|
||||
metadataScanner = new MetadataScanner()
|
||||
await metadataScanner.scanDirectory(templatePath, "https://example.com")
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
const context = {
|
||||
|
|
@ -130,23 +122,23 @@ describe("PackageManagerManager", () => {
|
|||
describe("Type Filter Behavior", () => {
|
||||
let typeFilterTestItems: MarketplaceItem[]
|
||||
|
||||
test("should include package with MCP server subcomponent when filtering by type 'mcp server'", () => {
|
||||
test("should include package with MCP server subcomponent when filtering by type 'mcp'", () => {
|
||||
const items: MarketplaceItem[] = [
|
||||
{
|
||||
name: "Data Platform Package",
|
||||
description: "A package containing MCP servers",
|
||||
type: "package" as ComponentType,
|
||||
type: "package" as MarketplaceItemType,
|
||||
url: "test/package",
|
||||
repoUrl: "https://example.com",
|
||||
items: [
|
||||
{
|
||||
type: "mcp server" as ComponentType,
|
||||
type: "mcp" as MarketplaceItemType,
|
||||
path: "test/server",
|
||||
metadata: {
|
||||
name: "Data Validator",
|
||||
description: "An MCP server",
|
||||
version: "1.0.0",
|
||||
type: "mcp server" as ComponentType,
|
||||
type: "mcp" as MarketplaceItemType,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -154,13 +146,13 @@ describe("PackageManagerManager", () => {
|
|||
{
|
||||
name: "Standalone Server",
|
||||
description: "A standalone MCP server",
|
||||
type: "mcp server" as ComponentType,
|
||||
type: "mcp" as MarketplaceItemType,
|
||||
url: "test/server",
|
||||
repoUrl: "https://example.com",
|
||||
},
|
||||
]
|
||||
|
||||
const filtered = manager.filterItems(items, { type: "mcp server" })
|
||||
const filtered = manager.filterItems(items, { type: "mcp" })
|
||||
expect(filtered.length).toBe(2)
|
||||
expect(filtered.map((item) => item.name)).toContain("Data Platform Package")
|
||||
expect(filtered.map((item) => item.name)).toContain("Standalone Server")
|
||||
|
|
@ -178,25 +170,25 @@ describe("PackageManagerManager", () => {
|
|||
{
|
||||
name: "Data Platform Package",
|
||||
description: "A package containing MCP servers",
|
||||
type: "package" as ComponentType,
|
||||
type: "package" as MarketplaceItemType,
|
||||
url: "test/package",
|
||||
repoUrl: "https://example.com",
|
||||
items: [
|
||||
{
|
||||
type: "mcp server" as ComponentType,
|
||||
type: "mcp" as MarketplaceItemType,
|
||||
path: "test/server",
|
||||
metadata: {
|
||||
name: "Data Validator",
|
||||
description: "An MCP server",
|
||||
version: "1.0.0",
|
||||
type: "mcp server" as ComponentType,
|
||||
type: "mcp" as MarketplaceItemType,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const filtered = manager.filterItems(items, { type: "mcp server" })
|
||||
const filtered = manager.filterItems(items, { type: "mcp" })
|
||||
expect(filtered.length).toBe(1)
|
||||
expect(filtered[0].name).toBe("Data Platform Package")
|
||||
expect(filtered[0].matchInfo?.matched).toBe(true)
|
||||
|
|
@ -225,13 +217,13 @@ describe("PackageManagerManager", () => {
|
|||
},
|
||||
},
|
||||
{
|
||||
type: "mcp server",
|
||||
type: "mcp",
|
||||
path: "test/server",
|
||||
metadata: {
|
||||
name: "Test Server",
|
||||
description: "A test server",
|
||||
version: "1.0.0",
|
||||
type: "mcp server",
|
||||
type: "mcp",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -482,12 +474,12 @@ describe("PackageManagerManager", () => {
|
|||
repoUrl: "https://example.com",
|
||||
items: [
|
||||
{
|
||||
type: "mcp server",
|
||||
path: "mcp servers/data-validator",
|
||||
type: "mcp",
|
||||
path: "mcps/data-validator",
|
||||
metadata: {
|
||||
name: "Data Validator",
|
||||
description: "An MCP server for validating data quality",
|
||||
type: "mcp server",
|
||||
type: "mcp",
|
||||
version: "1.0.0",
|
||||
},
|
||||
lastUpdated: "2025-04-13T10:00:00-07:00",
|
||||
|
|
@ -544,53 +536,6 @@ describe("PackageManagerManager", () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
// This test was skipped because it depends on the actual content of the marketplace-template
|
||||
// which may change over time
|
||||
it("should find data validator in marketplace-template", async () => {
|
||||
// Load real data from the template
|
||||
const templatePath = path.resolve(__dirname, "../../../../marketplace-template")
|
||||
const scanner = new MetadataScanner()
|
||||
const items = await scanner.scanDirectory(templatePath, "https://example.com")
|
||||
|
||||
// Test 1: Search for "data validator" (lowercase)
|
||||
const filtered1 = manager.filterItems(items, { search: "data validator" })
|
||||
|
||||
// Verify we find the Data Validator component
|
||||
expect(filtered1.length).toBeGreaterThan(0)
|
||||
|
||||
// Find the Data Validator component in the filtered results
|
||||
let foundDataValidator1 = false
|
||||
for (const item of filtered1) {
|
||||
if (item.items) {
|
||||
for (const subItem of item.items) {
|
||||
if (subItem.metadata?.name === "Data Validator") {
|
||||
foundDataValidator1 = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(foundDataValidator1).toBe(true)
|
||||
|
||||
// Test 2: Search for "DATA VALIDATOR" (uppercase)
|
||||
const filtered2 = manager.filterItems(items, { search: "DATA VALIDATOR" })
|
||||
|
||||
// Verify we find the Data Validator component
|
||||
expect(filtered2.length).toBeGreaterThan(0)
|
||||
|
||||
// Test 3: Search for "validator" (partial match)
|
||||
const filtered3 = manager.filterItems(items, { search: "validator" })
|
||||
|
||||
// Verify we find the Data Validator component
|
||||
expect(filtered3.length).toBeGreaterThan(0)
|
||||
|
||||
// Test 4: Search for "data valid" (partial match)
|
||||
const filtered4 = manager.filterItems(items, { search: "data valid" })
|
||||
|
||||
// Verify we find the Data Validator component
|
||||
expect(filtered4.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Source Attribution", () => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import { GitFetcher } from "../GitFetcher"
|
|||
import * as vscode from "vscode"
|
||||
|
||||
describe("MetadataScanner External References", () => {
|
||||
// TODO: remove this note
|
||||
// This test is expected to fail until we update the registry with the new wordings (`mcp server` => `mcp`)
|
||||
it("should find all subcomponents in Project Manager package including external references", async () => {
|
||||
// Create a GitFetcher instance using the project's mock settings directory
|
||||
const mockContext = {
|
||||
|
|
@ -25,7 +27,7 @@ describe("MetadataScanner External References", () => {
|
|||
|
||||
// Verify one is a mode and one is an MCP server
|
||||
const hasMode = projectManager?.items?.some((item) => item.type === "mode")
|
||||
const hasMcpServer = projectManager?.items?.some((item) => item.type === "mcp server")
|
||||
const hasMcpServer = projectManager?.items?.some((item) => item.type === "mcp")
|
||||
expect(hasMode).toBe(true)
|
||||
expect(hasMcpServer).toBe(true)
|
||||
|
||||
|
|
@ -34,6 +36,6 @@ describe("MetadataScanner External References", () => {
|
|||
(item) => item.metadata?.name === "Smartsheet MCP - Project Management",
|
||||
)
|
||||
expect(smartsheet).toBeDefined()
|
||||
expect(smartsheet?.type).toBe("mcp server")
|
||||
expect(smartsheet?.type).toBe("mcp")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ describe("MetadataScanner", () => {
|
|||
Buffer.from(`
|
||||
name: Test Component
|
||||
description: A test component
|
||||
type: mcp server
|
||||
type: mcp
|
||||
version: 1.0.0
|
||||
sourceUrl: https://example.com/component1
|
||||
`),
|
||||
|
|
@ -101,7 +101,7 @@ sourceUrl: https://example.com/component1
|
|||
|
||||
expect(items).toHaveLength(1)
|
||||
expect(items[0].name).toBe("Test Component")
|
||||
expect(items[0].type).toBe("mcp server")
|
||||
expect(items[0].type).toBe("mcp")
|
||||
expect(items[0].url).toBe("https://example.com/repo/tree/main/component1")
|
||||
expect(items[0].path).toBe("component1")
|
||||
expect(items[0].sourceUrl).toBe("https://example.com/component1")
|
||||
|
|
@ -139,7 +139,7 @@ sourceUrl: https://example.com/component1
|
|||
Buffer.from(`
|
||||
name: Test Component 2
|
||||
description: A test component without sourceUrl
|
||||
type: mcp server
|
||||
type: mcp
|
||||
version: 1.0.0
|
||||
`),
|
||||
)
|
||||
|
|
@ -148,7 +148,7 @@ version: 1.0.0
|
|||
|
||||
expect(items).toHaveLength(1)
|
||||
expect(items[0].name).toBe("Test Component 2")
|
||||
expect(items[0].type).toBe("mcp server")
|
||||
expect(items[0].type).toBe("mcp")
|
||||
expect(items[0].url).toBe("https://example.com/repo/tree/main/component2")
|
||||
expect(items[0].path).toBe("component2")
|
||||
expect(items[0].sourceUrl).toBeUndefined()
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ describe("Schema Validation", () => {
|
|||
name: "Test Component",
|
||||
description: "A test component",
|
||||
version: "1.0.0",
|
||||
type: "mcp server",
|
||||
type: "mcp",
|
||||
tags: ["test"],
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ describe("Schema Validation", () => {
|
|||
description: "A test package",
|
||||
version: "1.0.0",
|
||||
type: "package",
|
||||
items: [{ type: "mcp server", path: "../external/server" }],
|
||||
items: [{ type: "mcp", path: "../external/server" }],
|
||||
}
|
||||
|
||||
expect(() => validateMetadata(data, packageMetadataSchema)).not.toThrow()
|
||||
|
|
@ -81,7 +81,7 @@ describe("Schema Validation", () => {
|
|||
name: "Test Component",
|
||||
description: "A test component",
|
||||
version: "1.0.0",
|
||||
type: "mcp server",
|
||||
type: "mcp",
|
||||
}
|
||||
|
||||
expect(() => validateAnyMetadata(data)).not.toThrow()
|
||||
|
|
@ -93,7 +93,7 @@ describe("Schema Validation", () => {
|
|||
description: "A test package",
|
||||
version: "1.0.0",
|
||||
type: "package",
|
||||
items: [{ type: "mcp server", path: "../external/server" }],
|
||||
items: [{ type: "mcp", path: "../external/server" }],
|
||||
}
|
||||
|
||||
expect(() => validateAnyMetadata(data)).not.toThrow()
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const baseMetadataSchema = z.object({
|
|||
/**
|
||||
* Component type validation
|
||||
*/
|
||||
export const componentTypeSchema = z.enum(["mode", "prompt", "package", "mcp server"] as const)
|
||||
export const MarketplaceItemTypeSchema = z.enum(["mode", "prompt", "package", "mcp"] as const)
|
||||
|
||||
/**
|
||||
* Repository metadata schema
|
||||
|
|
@ -29,14 +29,14 @@ export const repositoryMetadataSchema = baseMetadataSchema
|
|||
* Component metadata schema
|
||||
*/
|
||||
export const componentMetadataSchema = baseMetadataSchema.extend({
|
||||
type: componentTypeSchema,
|
||||
type: MarketplaceItemTypeSchema,
|
||||
})
|
||||
|
||||
/**
|
||||
* External item reference schema
|
||||
*/
|
||||
export const externalItemSchema = z.object({
|
||||
type: componentTypeSchema,
|
||||
type: MarketplaceItemTypeSchema,
|
||||
path: z.string().min(1, "Path is required"),
|
||||
})
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ export function validateAnyMetadata(data: unknown) {
|
|||
case "package":
|
||||
return validateMetadata(data, packageMetadataSchema)
|
||||
case "mode":
|
||||
case "mcp server":
|
||||
case "mcp":
|
||||
case "prompt":
|
||||
case "role":
|
||||
case "storage":
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export interface MatchInfo {
|
|||
/**
|
||||
* Supported component types
|
||||
*/
|
||||
export type ComponentType = "mode" | "prompt" | "package" | "mcp server"
|
||||
export type MarketplaceItemType = "mode" | "prompt" | "package" | "mcp"
|
||||
|
||||
/**
|
||||
* Base metadata interface
|
||||
|
|
@ -41,7 +41,7 @@ export interface RepositoryMetadata extends BaseMetadata {}
|
|||
* Component metadata with type
|
||||
*/
|
||||
export interface ComponentMetadata extends BaseMetadata {
|
||||
type: ComponentType
|
||||
type: MarketplaceItemType
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -50,7 +50,7 @@ export interface ComponentMetadata extends BaseMetadata {
|
|||
export interface PackageMetadata extends ComponentMetadata {
|
||||
type: "package"
|
||||
items?: {
|
||||
type: ComponentType
|
||||
type: MarketplaceItemType
|
||||
path: string
|
||||
metadata?: ComponentMetadata
|
||||
}[]
|
||||
|
|
@ -72,7 +72,7 @@ export interface SubcomponentMetadata extends ComponentMetadata {
|
|||
export interface MarketplaceItem {
|
||||
name: string
|
||||
description: string
|
||||
type: ComponentType
|
||||
type: MarketplaceItemType
|
||||
url: string
|
||||
repoUrl: string
|
||||
sourceName?: string
|
||||
|
|
@ -87,7 +87,7 @@ export interface MarketplaceItem {
|
|||
defaultBranch?: string
|
||||
path?: string // Add path to main item
|
||||
items?: {
|
||||
type: ComponentType
|
||||
type: MarketplaceItemType
|
||||
path: string
|
||||
metadata?: ComponentMetadata
|
||||
lastUpdated?: string
|
||||
|
|
@ -134,8 +134,8 @@ export interface LocalizationOptions {
|
|||
export interface InstallMarketplaceItemOptions {
|
||||
/**
|
||||
* Specify the installation target
|
||||
*
|
||||
*
|
||||
* @default 'project'
|
||||
*/
|
||||
target?: 'global' | 'project'
|
||||
target?: "global" | "project"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -601,7 +601,7 @@ it("should detect TypeScript interfaces and HOCs", async function () {
|
|||
}
|
||||
|
||||
const withLogger = <P extends object>(
|
||||
WrappedComponent: React.ComponentType<P>
|
||||
WrappedComponent: React.MarketplaceItemType<P>
|
||||
) => {
|
||||
return class WithLogger extends React.Component<P> {
|
||||
render() {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import HistoryView from "./components/history/HistoryView"
|
|||
import SettingsView, { SettingsViewRef } from "./components/settings/SettingsView"
|
||||
import WelcomeView from "./components/welcome/WelcomeView"
|
||||
import McpView from "./components/mcp/McpView"
|
||||
import PackageManagerView from "./components/marketplace/MarketplaceView"
|
||||
import MarketplaceView from "./components/marketplace/MarketplaceView"
|
||||
import PromptsView from "./components/prompts/PromptsView"
|
||||
import { HumanRelayDialog } from "./components/human-relay/HumanRelayDialog"
|
||||
|
||||
|
|
@ -125,7 +125,7 @@ const App = () => {
|
|||
<SettingsView ref={settingsRef} onDone={() => setTab("chat")} targetSection={currentSection} />
|
||||
)}
|
||||
{tab === "marketplace" && (
|
||||
<PackageManagerView stateManager={marketplaceStateManager} onDone={() => switchTab("chat")} />
|
||||
<MarketplaceView stateManager={marketplaceStateManager} onDone={() => switchTab("chat")} />
|
||||
)}
|
||||
<ChatView
|
||||
ref={chatViewRef}
|
||||
|
|
|
|||
|
|
@ -103,9 +103,7 @@ const MarketplaceView: React.FC<MarketplaceViewProps> = ({ stateManager }) => {
|
|||
className="p-1 bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border rounded">
|
||||
<option value="">{t("marketplace:filters.type.all")}</option>
|
||||
<option value="mode">{t("marketplace:filters.type.mode")}</option>
|
||||
<option value="mcp server">
|
||||
{t("marketplace:filters.type.mcp server")}
|
||||
</option>
|
||||
<option value="mcp">{t("marketplace:filters.type.mcp")}</option>
|
||||
<option value="prompt">{t("marketplace:filters.type.prompt")}</option>
|
||||
<option value="package">{t("marketplace:filters.type.package")}</option>
|
||||
</select>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { MarketplaceViewStateManager } from "../MarketplaceViewStateManager"
|
||||
import { vscode } from "../../../utils/vscode"
|
||||
import { ComponentType, MarketplaceItem, MarketplaceSource } from "../../../../../src/services/marketplace/types"
|
||||
import { MarketplaceItemType, MarketplaceItem, MarketplaceSource } from "../../../../../src/services/marketplace/types"
|
||||
import { DEFAULT_MARKETPLACE_SOURCE } from "../../../../../src/services/marketplace/constants"
|
||||
|
||||
const createTestItem = (overrides = {}): MarketplaceItem => ({
|
||||
name: "test",
|
||||
type: "mode" as ComponentType,
|
||||
type: "mode" as MarketplaceItemType,
|
||||
description: "Test mode",
|
||||
url: "https://github.com/test/repo",
|
||||
repoUrl: "https://github.com/test/repo",
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ export const MarketplaceItemCard: React.FC<MarketplaceItemCardProps> = ({
|
|||
switch (item.type) {
|
||||
case "mode":
|
||||
return t("marketplace:filters.type.mode")
|
||||
case "mcp server":
|
||||
return t("marketplace:filters.type.mcp server")
|
||||
case "mcp":
|
||||
return t("marketplace:filters.type.mcp")
|
||||
case "prompt":
|
||||
return t("marketplace:filters.type.prompt")
|
||||
case "package":
|
||||
|
|
@ -45,7 +45,7 @@ export const MarketplaceItemCard: React.FC<MarketplaceItemCardProps> = ({
|
|||
switch (item.type) {
|
||||
case "mode":
|
||||
return "bg-blue-600"
|
||||
case "mcp server":
|
||||
case "mcp":
|
||||
return "bg-green-600"
|
||||
case "prompt":
|
||||
return "bg-purple-600"
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ export const TypeGroup: React.FC<TypeGroupProps> = ({ type, items, className })
|
|||
switch (type) {
|
||||
case "mode":
|
||||
return t("marketplace:type-group.modes")
|
||||
case "mcp server":
|
||||
return t("marketplace:type-group.mcp-servers")
|
||||
case "mcp":
|
||||
return t("marketplace:type-group.mcps")
|
||||
case "prompt":
|
||||
return t("marketplace:type-group.prompts")
|
||||
case "package":
|
||||
|
|
|
|||
|
|
@ -21,13 +21,13 @@ describe("MarketplaceItemCard", () => {
|
|||
tags: ["test", "mock"],
|
||||
items: [
|
||||
{
|
||||
type: "mcp server",
|
||||
type: "mcp",
|
||||
path: "test/path",
|
||||
metadata: {
|
||||
name: "Test Server",
|
||||
description: "A test server",
|
||||
version: "1.0.0",
|
||||
type: "mcp server",
|
||||
type: "mcp",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -166,8 +166,8 @@ describe("MarketplaceItemCard", () => {
|
|||
expect(screen.queryByText("2 components")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should not show expandable section for mcp server type", () => {
|
||||
const mcpServerItem = { ...mockItem, type: "mcp server" as const }
|
||||
it("should not show expandable section for mcp type", () => {
|
||||
const mcpServerItem = { ...mockItem, type: "mcp" as const }
|
||||
renderWithProviders(<MarketplaceItemCard {...defaultProps} item={mcpServerItem} />)
|
||||
|
||||
expect(screen.queryByText("2 components")).not.toBeInTheDocument()
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ describe("TypeGroup", () => {
|
|||
]
|
||||
|
||||
it("should render type header and items", () => {
|
||||
renderWithProviders(<TypeGroup type="mcp server" items={mockItems} />)
|
||||
renderWithProviders(<TypeGroup type="mcp" items={mockItems} />)
|
||||
|
||||
// Test using translation key with flexible text matching
|
||||
expect(screen.getByText((content, element) => element?.textContent === "MCP Servers")).toBeInTheDocument()
|
||||
|
|
@ -34,7 +34,7 @@ describe("TypeGroup", () => {
|
|||
it("should format different types correctly", () => {
|
||||
const types = [
|
||||
{ input: "mode", expected: "Modes" },
|
||||
{ input: "mcp server", expected: "MCP Servers" },
|
||||
{ input: "mcp", expected: "MCP Servers" },
|
||||
{ input: "prompt", expected: "Prompts" },
|
||||
{ input: "package", expected: "Packages" },
|
||||
{ input: "custom", expected: "Customs" }, // Uses generic-type with capitalization
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { MarketplaceItem } from "../../../../../../src/services/marketplace/type
|
|||
describe("grouping utilities", () => {
|
||||
const mockItems = [
|
||||
{
|
||||
type: "mcp server",
|
||||
type: "mcp",
|
||||
path: "servers/test-server",
|
||||
metadata: {
|
||||
name: "Test Server",
|
||||
|
|
@ -22,7 +22,7 @@ describe("grouping utilities", () => {
|
|||
},
|
||||
},
|
||||
{
|
||||
type: "mcp server",
|
||||
type: "mcp",
|
||||
path: "servers/another-server",
|
||||
metadata: {
|
||||
name: "Another Server",
|
||||
|
|
@ -37,10 +37,10 @@ describe("grouping utilities", () => {
|
|||
const result = groupItemsByType(mockItems)
|
||||
|
||||
expect(Object.keys(result)).toHaveLength(2)
|
||||
expect(result["mcp server"].items).toHaveLength(2)
|
||||
expect(result["mcp"].items).toHaveLength(2)
|
||||
expect(result["mode"].items).toHaveLength(1)
|
||||
|
||||
expect(result["mcp server"].items[0].name).toBe("Test Server")
|
||||
expect(result["mcp"].items[0].name).toBe("Test Server")
|
||||
expect(result["mode"].items[0].name).toBe("Test Mode")
|
||||
})
|
||||
|
||||
|
|
@ -52,18 +52,18 @@ describe("grouping utilities", () => {
|
|||
it("should handle items with missing metadata", () => {
|
||||
const itemsWithMissingData = [
|
||||
{
|
||||
type: "mcp server",
|
||||
type: "mcp",
|
||||
path: "test/path",
|
||||
},
|
||||
] as MarketplaceItem["items"]
|
||||
|
||||
const result = groupItemsByType(itemsWithMissingData)
|
||||
expect(result["mcp server"].items[0].name).toBe("Unnamed item")
|
||||
expect(result["mcp"].items[0].name).toBe("Unnamed item")
|
||||
})
|
||||
|
||||
it("should preserve item order within groups", () => {
|
||||
const result = groupItemsByType(mockItems)
|
||||
const servers = result["mcp server"].items
|
||||
const servers = result["mcp"].items
|
||||
|
||||
expect(servers[0].name).toBe("Test Server")
|
||||
expect(servers[1].name).toBe("Another Server")
|
||||
|
|
@ -110,7 +110,7 @@ describe("grouping utilities", () => {
|
|||
const groups = groupItemsByType(mockItems)
|
||||
const types = getUniqueTypes(groups)
|
||||
|
||||
expect(types).toEqual(["mcp server", "mode"])
|
||||
expect(types).toEqual(["mcp", "mode"])
|
||||
})
|
||||
|
||||
it("should handle empty groups", () => {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"label": "Filtrar per tipus:",
|
||||
"all": "Tots els tipus",
|
||||
"mode": "Mode",
|
||||
"mcp server": "Servidor MCP",
|
||||
"mcp": "Servidor MCP",
|
||||
"prompt": "Prompt",
|
||||
"package": "Paquet"
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"type-group": {
|
||||
"match": "Coincidència",
|
||||
"modes": "Modes",
|
||||
"mcp-servers": "Servidors MCP",
|
||||
"mcps": "Servidors MCP",
|
||||
"prompts": "Prompts",
|
||||
"packages": "Paquets",
|
||||
"generic-type": "{{type}}"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"label": "Nach Typ filtern:",
|
||||
"all": "Alle Typen",
|
||||
"mode": "Modus",
|
||||
"mcp server": "MCP-Server",
|
||||
"mcp": "MCP-Server",
|
||||
"prompt": "Prompt",
|
||||
"package": "Paket"
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"type-group": {
|
||||
"match": "Treffer",
|
||||
"modes": "Modi",
|
||||
"mcp-servers": "MCP-Server",
|
||||
"mcps": "MCP-Server",
|
||||
"prompts": "Prompts",
|
||||
"packages": "Pakete",
|
||||
"generic-type": "{{type}}"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"label": "Filter by type:",
|
||||
"all": "All types",
|
||||
"mode": "Mode",
|
||||
"mcp server": "MCP Server",
|
||||
"mcp": "MCP Server",
|
||||
"prompt": "Prompt",
|
||||
"package": "Package"
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"type-group": {
|
||||
"match": "Match",
|
||||
"modes": "Modes",
|
||||
"mcp-servers": "MCP Servers",
|
||||
"mcps": "MCP Servers",
|
||||
"prompts": "Prompts",
|
||||
"packages": "Packages",
|
||||
"generic-type": "{{type}}"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"label": "Filtrar por tipo:",
|
||||
"all": "Todos los tipos",
|
||||
"mode": "Modo",
|
||||
"mcp server": "Servidor MCP",
|
||||
"mcp": "Servidor MCP",
|
||||
"prompt": "Prompt",
|
||||
"package": "Paquete"
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"type-group": {
|
||||
"match": "Coincidencia",
|
||||
"modes": "Modos",
|
||||
"mcp-servers": "Servidores MCP",
|
||||
"mcps": "Servidores MCP",
|
||||
"prompts": "Prompts",
|
||||
"packages": "Paquetes",
|
||||
"generic-type": "{{type}}"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"label": "Filtrer par type :",
|
||||
"all": "Tous les types",
|
||||
"mode": "Mode",
|
||||
"mcp server": "Serveur MCP",
|
||||
"mcp": "Serveur MCP",
|
||||
"prompt": "Prompt",
|
||||
"package": "Paquet"
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"type-group": {
|
||||
"match": "Correspondance",
|
||||
"modes": "Modes",
|
||||
"mcp-servers": "Serveurs MCP",
|
||||
"mcps": "Serveurs MCP",
|
||||
"prompts": "Prompts",
|
||||
"packages": "Paquets",
|
||||
"generic-type": "{{type}}"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"label": "प्रकार से फ़िल्टर करें:",
|
||||
"all": "सभी प्रकार",
|
||||
"mode": "मोड",
|
||||
"mcp server": "एमसीपी सर्वर",
|
||||
"mcp": "एमसीपी सर्वर",
|
||||
"prompt": "प्रॉम्प्ट",
|
||||
"package": "पैकेज"
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"type-group": {
|
||||
"match": "मिलान",
|
||||
"modes": "मोड्स",
|
||||
"mcp-servers": "एमसीपी सर्वर",
|
||||
"mcps": "एमसीपी सर्वर",
|
||||
"prompts": "प्रॉम्प्ट्स",
|
||||
"packages": "पैकेज",
|
||||
"generic-type": "{{type}}"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"label": "Filtra per tipo:",
|
||||
"all": "Tutti i tipi",
|
||||
"mode": "Modalità",
|
||||
"mcp server": "Server MCP",
|
||||
"mcp": "Server MCP",
|
||||
"prompt": "Prompt",
|
||||
"package": "Pacchetto"
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"type-group": {
|
||||
"match": "Corrispondenza",
|
||||
"modes": "Modalità",
|
||||
"mcp-servers": "Server MCP",
|
||||
"mcps": "Server MCP",
|
||||
"prompts": "Prompt",
|
||||
"packages": "Pacchetti",
|
||||
"generic-type": "{{type}}"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"label": "タイプでフィルター:",
|
||||
"all": "すべてのタイプ",
|
||||
"mode": "モード",
|
||||
"mcp server": "MCPサーバー",
|
||||
"mcp": "MCPサーバー",
|
||||
"prompt": "プロンプト",
|
||||
"package": "パッケージ"
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"type-group": {
|
||||
"match": "一致",
|
||||
"modes": "モード",
|
||||
"mcp-servers": "MCPサーバー",
|
||||
"mcps": "MCPサーバー",
|
||||
"prompts": "プロンプト",
|
||||
"packages": "パッケージ",
|
||||
"generic-type": "{{type}}"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"label": "유형별 필터:",
|
||||
"all": "모든 유형",
|
||||
"mode": "모드",
|
||||
"mcp server": "MCP 서버",
|
||||
"mcp": "MCP 서버",
|
||||
"prompt": "프롬프트",
|
||||
"package": "패키지"
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"type-group": {
|
||||
"match": "일치",
|
||||
"modes": "모드",
|
||||
"mcp-servers": "MCP 서버",
|
||||
"mcps": "MCP 서버",
|
||||
"prompts": "프롬프트",
|
||||
"packages": "패키지",
|
||||
"generic-type": "{{type}}"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"label": "Filtruj według typu:",
|
||||
"all": "Wszystkie typy",
|
||||
"mode": "Tryb",
|
||||
"mcp server": "Serwer MCP",
|
||||
"mcp": "Serwer MCP",
|
||||
"prompt": "Prompt",
|
||||
"package": "Pakiet"
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"type-group": {
|
||||
"match": "Dopasowanie",
|
||||
"modes": "Tryby",
|
||||
"mcp-servers": "Serwery MCP",
|
||||
"mcps": "Serwery MCP",
|
||||
"prompts": "Prompty",
|
||||
"packages": "Pakiety",
|
||||
"generic-type": "{{type}}"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"label": "Filtrar por tipo:",
|
||||
"all": "Todos os tipos",
|
||||
"mode": "Modo",
|
||||
"mcp server": "Servidor MCP",
|
||||
"mcp": "Servidor MCP",
|
||||
"prompt": "Prompt",
|
||||
"package": "Pacote"
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"type-group": {
|
||||
"match": "Correspondência",
|
||||
"modes": "Modos",
|
||||
"mcp-servers": "Servidores MCP",
|
||||
"mcps": "Servidores MCP",
|
||||
"prompts": "Prompts",
|
||||
"packages": "Pacotes",
|
||||
"generic-type": "{{type}}"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"label": "Türe göre filtrele:",
|
||||
"all": "Tüm türler",
|
||||
"mode": "Mod",
|
||||
"mcp server": "MCP Sunucusu",
|
||||
"mcp": "MCP Sunucusu",
|
||||
"prompt": "Prompt",
|
||||
"package": "Paket"
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"type-group": {
|
||||
"match": "Eşleşme",
|
||||
"modes": "Modlar",
|
||||
"mcp-servers": "MCP Sunucuları",
|
||||
"mcps": "MCP Sunucuları",
|
||||
"prompts": "Promptlar",
|
||||
"packages": "Paketler",
|
||||
"generic-type": "{{type}}"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"label": "Lọc theo loại:",
|
||||
"all": "Tất cả các loại",
|
||||
"mode": "Chế độ",
|
||||
"mcp server": "Máy chủ MCP",
|
||||
"mcp": "Máy chủ MCP",
|
||||
"prompt": "Prompt",
|
||||
"package": "Gói"
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"type-group": {
|
||||
"match": "Khớp",
|
||||
"modes": "Chế độ",
|
||||
"mcp-servers": "Máy chủ MCP",
|
||||
"mcps": "Máy chủ MCP",
|
||||
"prompts": "Prompt",
|
||||
"packages": "Gói",
|
||||
"generic-type": "{{type}}"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"label": "按类型筛选:",
|
||||
"all": "所有类型",
|
||||
"mode": "模式",
|
||||
"mcp server": "MCP 服务器",
|
||||
"mcp": "MCP 服务器",
|
||||
"prompt": "提示",
|
||||
"package": "包"
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"type-group": {
|
||||
"match": "匹配",
|
||||
"modes": "模式",
|
||||
"mcp-servers": "MCP 服务器",
|
||||
"mcps": "MCP 服务器",
|
||||
"prompts": "提示",
|
||||
"packages": "包",
|
||||
"generic-type": "{{type}}"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"label": "依類型篩選:",
|
||||
"all": "所有類型",
|
||||
"mode": "模式",
|
||||
"mcp server": "MCP 伺服器",
|
||||
"mcp": "MCP 伺服器",
|
||||
"prompt": "提示",
|
||||
"package": "套件"
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"type-group": {
|
||||
"match": "符合",
|
||||
"modes": "模式",
|
||||
"mcp-servers": "MCP 伺服器",
|
||||
"mcps": "MCP 伺服器",
|
||||
"prompts": "提示",
|
||||
"packages": "套件",
|
||||
"generic-type": "{{type}}"
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ i18next.use(initReactI18next).init({
|
|||
all: "All types",
|
||||
package: "Package",
|
||||
mode: "Mode",
|
||||
"mcp server": "MCP Server",
|
||||
mcp: "MCP Server",
|
||||
prompt: "Prompt",
|
||||
},
|
||||
sort: {
|
||||
|
|
@ -74,7 +74,7 @@ i18next.use(initReactI18next).init({
|
|||
},
|
||||
},
|
||||
"type-group": {
|
||||
"mcp-servers": "MCP Servers",
|
||||
mcps: "MCP Servers",
|
||||
modes: "Modes",
|
||||
prompts: "Prompts",
|
||||
packages: "Packages",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue