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:
Trung Dang 2025-05-02 23:16:26 +07:00 committed by GitHub
parent c0638798d8
commit 563b327007
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
85 changed files with 379 additions and 2578 deletions

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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))
})
})

View file

@ -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}

View file

@ -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...

View file

@ -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) {

View file

@ -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)

View file

@ -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] │
├─────────────────────────────────────────────────────────┤

View file

@ -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)

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -19,7 +19,6 @@
"src/exports/**",
"src/schemas/ipc.ts",
"src/extension.ts",
"marketplace-template/**",
"scripts/**"
],
"workspaces": {

View file

@ -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

View file

@ -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"]

View file

@ -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()

View file

@ -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"]

View file

@ -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"]

View file

@ -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

View file

@ -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/"

View file

@ -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"

View file

@ -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")
})

View file

@ -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/"

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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()

View file

@ -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"]

View file

@ -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"]

View file

@ -1,5 +0,0 @@
name: "データプラットフォームパッケージ"
description: "ロール、サーバー、ストレージシステムを含む完全なデータプラットフォームソリューション"
type: "package"
version: "1.0.0"
tags: ["データプラットフォーム", "エンタープライズ", "データエンジニアリング", "完全ソリューション"]

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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,
})

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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": "अन्य",

View file

@ -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",

View file

@ -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": "その他",

View file

@ -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": "기타",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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": "其他",

View file

@ -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": "其他",

View file

@ -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,

View file

@ -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) {

View file

@ -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)
}
/**

View file

@ -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", () => {

View file

@ -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")
})
})

View file

@ -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()

View file

@ -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()

View file

@ -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":

View file

@ -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"
}

View file

@ -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() {

View file

@ -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}

View file

@ -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>

View file

@ -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",

View file

@ -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"

View file

@ -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":

View file

@ -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()

View file

@ -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

View file

@ -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", () => {

View file

@ -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}}"

View file

@ -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}}"

View file

@ -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}}"

View file

@ -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}}"

View file

@ -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}}"

View file

@ -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}}"

View file

@ -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}}"

View file

@ -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}}"

View file

@ -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}}"

View file

@ -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}}"

View file

@ -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}}"

View file

@ -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}}"

View file

@ -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}}"

View file

@ -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}}"

View file

@ -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}}"

View file

@ -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",