Token导航 LogoToken导航TokenDH.com
前端设计需要联网github未标认证来源可访问clear审计通过

harmonyos-app和谐应用程序

Agent Skill

用于辅助前端页面、组件、样式和交互逻辑的开发与维护。它适合让 Agent 生成或审查 React、Next.js、Vue、Tailwind、CSS 等相关代码,整理组件结构,或定位布局和性能问题。使用时需要结合项目现有设计系统、路由和构建方式,避免只生成孤立片段;涉及页面改动时,应配合本地预览和构建检查确认视觉效果。

总安装

6,656

周安装

283

GitHub Stars

28

下载量

2,332
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

3

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:harmonyos-app(和谐应用程序)
来源仓库:https://github.com/majiayu000/claude-arsenal
仓库路径:skills/harmonyos-app
安装命令:
npx skills add https://github.com/majiayu000/claude-arsenal --skill harmonyos-app
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/majiayu000/claude-arsenal --skill harmonyos-app

简介

用于辅助 HarmonyOS 应用前端页面、组件和样式开发。

  • 适合在 Codex、Claude、Cursor、Gemini CLI 中生成或审查相关代码,整理组件结构。
  • 通过 GitHub 安装,需结合项目现有设计系统和构建方式使用。
  • 涉及页面改动时,应配合本地预览和构建检查确认视觉效果。
  • 避免只生成孤立片段,需与整体项目结构保持一致。

SKILL.md

HarmonyOS Application Development

Core Principles

  • ArkTS First — Use ArkTS with strict type safety, no any or dynamic types
  • Declarative UI — Build UI with ArkUI's declarative components and state management
  • Stage Model — Use modern Stage model (UIAbility), not legacy FA model
  • Distributed by Design — Leverage cross-device capabilities from the start
  • Atomic Services — Consider atomic services and cards for lightweight experiences
  • One-time Development — Design for multi-device adaptation (phone, tablet, watch, TV)

Hard Rules (Must Follow)

These rules are mandatory. Violating them means the skill is not working correctly.

No Dynamic Types

ArkTS prohibits dynamic typing. Never use any, type assertions, or dynamic property access.

// ❌ FORBIDDEN: Dynamic types
let data: any = fetchData();
let obj: object = {};
obj['dynamicKey'] = value;  // Dynamic property access
(someVar as SomeType).method();  // Type assertion

// ✅ REQUIRED: Strict typing
interface UserData {
  id: string;
  name: string;
}
let data: UserData = fetchData();

// Use Record for dynamic keys
let obj: Record<string, string> = {};
obj['key'] = value;  // OK with Record type

No Direct State Mutation

Never mutate @State/@Prop variables directly in nested objects. Use immutable updates.

// ❌ FORBIDDEN: Direct mutation
@State user: User = { name: 'John', age: 25 };

updateAge() {
  this.user.age = 26;  // UI won't update!
}

// ✅ REQUIRED: Immutable update
updateAge() {
  this.user = { ...this.user, age: 26 };  // Creates new object, triggers UI update
}

// For arrays
@State items: string[] = ['a', 'b'];

// ❌ FORBIDDEN
this.items.push('c');  // UI won't update

// ✅ REQUIRED
this.items = [...this.items, 'c'];

Stage Model Only

Always use Stage model (UIAbility). Never use deprecated FA model (PageAbility).

// ❌ FORBIDDEN: FA Model (deprecated)
// config.json with "pages" array
export default {
  onCreate() { ... }  // PageAbility lifecycle
}

// ✅ REQUIRED: Stage Model
// module.json5 with abilities configuration
import { UIAbility } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // Modern Stage model lifecycle
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index');
  }
}

Component Reusability

Extract reusable UI into @Component. No inline complex UI in build() methods.

// ❌ FORBIDDEN: Monolithic build method
@Entry
@Component
struct MainPage {
  build() {
    Column() {
      // 200+ lines of inline UI...
      Row() {
        Image($r('app.media.avatar'))
        Column() {
          Text(this.user.name)
          Text(this.user.email)
        }
      }
      // More inline UI...
    }
  }
}

// ✅ REQUIRED: Extract components
@Component
struct UserCard {
  @Prop user: User;

  build() {
    Row() {
      Image($r('app.media.avatar'))
      Column() {
        Text(this.user.name)
        Text(this.user.email)
      }
    }
  }
}

@Entry
@Component
struct MainPage {
  @State user: User = { name: 'John', email: 'john@example.com' };

  build() {
    Column() {
      UserCard({ user: this.user })
    }
  }
}

Quick Reference

When to Use What

ScenarioPatternExample
Component-local state@StateCounter, form inputs
Parent-to-child data@PropRead-only child data
Two-way binding@LinkShared mutable state
Cross-component state@Provide/@ConsumeTheme, user context
Persistent statePersistentStorageUser preferences
App-wide stateAppStorageGlobal state
Complex state logic@Observed/@ObjectLinkNested object updates

State Decorator Selection

@State        → Component owns the state, triggers re-render on change
@Prop         → Parent passes value, child gets copy (one-way)
@Link         → Parent passes reference, child can modify (two-way)
@Provide      → Ancestor provides value to all descendants
@Consume      → Descendant consumes value from ancestor
@StorageLink  → Syncs with AppStorage, two-way binding
@StorageProp  → Syncs with AppStorage, one-way binding
@Observed     → Class decorator for observable objects
@ObjectLink   → Links to @Observed object in parent

Project Structure

Recommended Architecture

MyApp/
├── entry/                          # Main entry module
│   ├── src/main/
│   │   ├── ets/
│   │   │   ├── entryability/       # UIAbility definitions
│   │   │   │   └── EntryAbility.ets
│   │   │   ├── pages/              # Page components
│   │   │   │   ├── Index.ets
│   │   │   │   └── Detail.ets
│   │   │   ├── components/         # Reusable UI components
│   │   │   │   ├── common/         # Common components
│   │   │   │   └── business/       # Business-specific components
│   │   │   ├── viewmodel/          # ViewModels (MVVM)
│   │   │   ├── model/              # Data models
│   │   │   ├── service/            # Business logic services
│   │   │   ├── repository/         # Data access layer
│   │   │   ├── utils/              # Utility functions
│   │   │   └── constants/          # Constants and configs
│   │   ├── resources/              # Resources (strings, images)
│   │   └── module.json5            # Module configuration
│   └── build-profile.json5
├── common/                         # Shared library module
│   └── src/main/ets/
├── features/                       # Feature modules
│   ├── feature_home/
│   └── feature_profile/
└── build-profile.json5             # Project configuration

Layer Separation

┌─────────────────────────────────────┐
│           UI Layer (Pages)          │  ArkUI Components
├─────────────────────────────────────┤
│         ViewModel Layer             │  State management, UI logic
├─────────────────────────────────────┤
│         Service Layer               │  Business logic
├─────────────────────────────────────┤
│        Repository Layer             │  Data access abstraction
├─────────────────────────────────────┤
│    Data Sources (Local/Remote)      │  Preferences, RDB, Network
└─────────────────────────────────────┘

ArkUI Component Patterns

Basic Component Structure

import { router } from '@kit.ArkUI';

@Component
export struct ProductCard {
  // Props from parent
  @Prop product: Product;
  @Prop onAddToCart: (product: Product) => void;

  // Local state
  @State isExpanded: boolean = false;

  // Computed values (use getters)
  get formattedPrice(): string {
    return `¥${this.product.price.toFixed(2)}`;
  }

  // Lifecycle
  aboutToAppear(): void {
    console.info('ProductCard appearing');
  }

  aboutToDisappear(): void {
    console.info('ProductCard disappearing');
  }

  // Event handlers
  private handleTap(): void {
    router.pushUrl({ url: 'pages/ProductDetail', params: { id: this.product.id } });
  }

  private handleAddToCart(): void {
    this.onAddToCart(this.product);
  }

  // UI builder
  build() {
    Column() {
      Image(this.product.imageUrl)
        .width('100%')
        .aspectRatio(1)
        .objectFit(ImageFit.Cover)

      Text(this.product.name)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)

      Text(this.formattedPrice)
        .fontSize(14)
        .fontColor('#FF6B00')

      Button('Add to Cart')
        .onClick(() => this.handleAddToCart())
    }
    .padding(12)
    .backgroundColor(Color.White)
    .borderRadius(8)
    .onClick(() => this.handleTap())
  }
}

List with LazyForEach

import { BasicDataSource } from '../utils/BasicDataSource';

class ProductDataSource extends BasicDataSource<Product> {
  private products: Product[] = [];

  totalCount(): number {
    return this.products.length;
  }

  getData(index: number): Product {
    return this.products[index];
  }

  addData(product: Product): void {
    this.products.push(product);
    this.notifyDataAdd(this.products.length - 1);
  }

  updateData(index: number, product: Product): void {
    this.products[index] = product;
    this.notifyDataChange(index);
  }
}

@Component
struct ProductList {
  private dataSource: ProductDataSource = new ProductDataSource();

  build() {
    List() {
      LazyForEach(this.dataSource, (product: Product, index: number) => {
        ListItem() {
          ProductCard({ product: product })
        }
      }, (product: Product) => product.id)  // Key generator
    }
    .lanes(2)  // Grid with 2 columns
    .cachedCount(4)  // Cache 4 items for smooth scrolling
  }
}

Custom Dialog

@CustomDialog
struct ConfirmDialog {
  controller: CustomDialogController;
  title: string = 'Confirm';
  message: string = '';
  onConfirm: () => void = () => {};

  build() {
    Column() {
      Text(this.title)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 16 })

      Text(this.message)
        .fontSize(16)
        .margin({ bottom: 24 })

      Row() {
        Button('Cancel')
          .onClick(() => this.controller.close())
          .backgroundColor(Color.Gray)
          .margin({ right: 16 })

        Button('Confirm')
          .onClick(() => {
            this.onConfirm();
            this.controller.close();
          })
      }
    }
    .padding(24)
  }
}

// Usage
@Entry
@Component
struct MainPage {
  dialogController: CustomDialogController = new CustomDialogController({
    builder: ConfirmDialog({
      title: 'Delete Item',
      message: 'Are you sure you want to delete this item?',
      onConfirm: () => this.deleteItem()
    }),
    autoCancel: true
  });

  private deleteItem(): void {
    // Delete logic
  }

  build() {
    Button('Delete')
      .onClick(() => this.dialogController.open())
  }
}

State Management Patterns

ViewModel Pattern

// viewmodel/ProductViewModel.ets
import { Product } from '../model/Product';
import { ProductRepository } from '../repository/ProductRepository';

@Observed
export class ProductViewModel {
  products: Product[] = [];
  isLoading: boolean = false;
  errorMessage: string = '';

  private repository: ProductRepository = new ProductRepository();

  async loadProducts(): Promise<void> {
    this.isLoading = true;
    this.errorMessage = '';

    try {
      this.products = await this.repository.getProducts();
    } catch (error) {
      this.errorMessage = `Failed to load: ${error.message}`;
    } finally {
      this.isLoading = false;
    }
  }

  async addProduct(product: Product): Promise<void> {
    const created = await this.repository.createProduct(product);
    this.products = [...this.products, created];
  }
}

// pages/ProductPage.ets
@Entry
@Component
struct ProductPage {
  @State viewModel: ProductViewModel = new ProductViewModel();

  aboutToAppear(): void {
    this.viewModel.loadProducts();
  }

  build() {
    Column() {
      if (this.viewModel.isLoading) {
        LoadingProgress()
      } else if (this.viewModel.errorMessage) {
        Text(this.viewModel.errorMessage)
          .fontColor(Color.Red)
      } else {
        ForEach(this.viewModel.products, (product: Product) => {
          ProductCard({ product: product })
        }, (product: Product) => product.id)
      }
    }
  }
}

AppStorage for Global State

// Initialize in EntryAbility
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // Initialize global state
    AppStorage.setOrCreate('isLoggedIn', false);
    AppStorage.setOrCreate('currentUser', null);
    AppStorage.setOrCreate('theme', 'light');
  }
}

// Access in components
@Entry
@Component
struct ProfilePage {
  @StorageLink('isLoggedIn') isLoggedIn: boolean = false;
  @StorageLink('currentUser') currentUser: User | null = null;
  @StorageProp('theme') theme: string = 'light';  // Read-only

  build() {
    Column() {
      if (this.isLoggedIn && this.currentUser) {
        Text(`Welcome, ${this.currentUser.name}`)
      } else {
        Button('Login')
          .onClick(() => {
            // After login
            this.isLoggedIn = true;
            this.currentUser = { id: '1', name: 'John' };
          })
      }
    }
  }
}

PersistentStorage for Preferences

// Initialize persistent storage
PersistentStorage.persistProp('userSettings', {
  notifications: true,
  darkMode: false,
  language: 'zh-CN'
});

@Entry
@Component
struct SettingsPage {
  @StorageLink('userSettings') settings: UserSettings = {
    notifications: true,
    darkMode: false,
    language: 'zh-CN'
  };

  build() {
    Column() {
      Toggle({ type: ToggleType.Switch, isOn: this.settings.notifications })
        .onChange((isOn: boolean) => {
          this.settings = { ...this.settings, notifications: isOn };
        })

      Toggle({ type: ToggleType.Switch, isOn: this.settings.darkMode })
        .onChange((isOn: boolean) => {
          this.settings = { ...this.settings, darkMode: isOn };
        })
    }
  }
}

Navigation Patterns

Router Navigation

import { router } from '@kit.ArkUI';

// Navigate to page
router.pushUrl({
  url: 'pages/Detail',
  params: { productId: '123' }
});

// Navigate with result
router.pushUrl({
  url: 'pages/SelectAddress'
}).then(() => {
  // Navigation complete
});

// Get params in target page
@Entry
@Component
struct DetailPage {
  @State productId: string = '';

  aboutToAppear(): void {
    const params = router.getParams() as Record<string, string>;
    this.productId = params?.productId ?? '';
  }
}

// Go back
router.back();

// Replace current page
router.replaceUrl({ url: 'pages/Home' });

// Clear stack and navigate
router.clear();
router.pushUrl({ url: 'pages/Login' });

Navigation Component (Recommended for HarmonyOS NEXT)

@Entry
@Component
struct MainPage {
  @Provide('navPathStack') navPathStack: NavPathStack = new NavPathStack();

  @Builder
  pageBuilder(name: string, params: object) {
    if (name === 'detail') {
      DetailPage({ params: params as DetailParams })
    } else if (name === 'settings') {
      SettingsPage()
    }
  }

  build() {
    Navigation(this.navPathStack) {
      Column() {
        Button('Go to Detail')
          .onClick(() => {
            this.navPathStack.pushPath({ name: 'detail', param: { id: '123' } });
          })
      }
    }
    .navDestination(this.pageBuilder)
    .title('Home')
  }
}

@Component
struct DetailPage {
  @Consume('navPathStack') navPathStack: NavPathStack;
  params: DetailParams = { id: '' };

  build() {
    NavDestination() {
      Column() {
        Text(`Product ID: ${this.params.id}`)
        Button('Back')
          .onClick(() => this.navPathStack.pop())
      }
    }
    .title('Detail')
  }
}

Network Requests

HTTP Client

import { http } from '@kit.NetworkKit';

interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

class HttpClient {
  private baseUrl: string = 'https://api.example.com';

  async get<T>(path: string): Promise<T> {
    const httpRequest = http.createHttp();

    try {
      const response = await httpRequest.request(
        `${this.baseUrl}${path}`,
        {
          method: http.RequestMethod.GET,
          header: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${await this.getToken()}`
          },
          expectDataType: http.HttpDataType.OBJECT
        }
      );

      if (response.responseCode === 200) {
        const result = response.result as ApiResponse<T>;
        if (result.code === 0) {
          return result.data;
        }
        throw new Error(result.message);
      }
      throw new Error(`HTTP ${response.responseCode}`);
    } finally {
      httpRequest.destroy();
    }
  }

  async post<T, R>(path: string, data: T): Promise<R> {
    const httpRequest = http.createHttp();

    try {
      const response = await httpRequest.request(
        `${this.baseUrl}${path}`,
        {
          method: http.RequestMethod.POST,
          header: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${await this.getToken()}`
          },
          extraData: JSON.stringify(data),
          expectDataType: http.HttpDataType.OBJECT
        }
      );

      const result = response.result as ApiResponse<R>;
      return result.data;
    } finally {
      httpRequest.destroy();
    }
  }

  private async getToken(): Promise<string> {
    return AppStorage.get('authToken') ?? '';
  }
}

export const httpClient = new HttpClient();

Distributed Capabilities

Cross-Device Data Sync

import { distributedKVStore } from '@kit.ArkData';

class DistributedStore {
  private kvManager: distributedKVStore.KVManager | null = null;
  private kvStore: distributedKVStore.SingleKVStore | null = null;

  async init(context: Context): Promise<void> {
    const config: distributedKVStore.KVManagerConfig = {
      context: context,
      bundleName: 'com.example.myapp'
    };

    this.kvManager = distributedKVStore.createKVManager(config);

    const options: distributedKVStore.Options = {
      createIfMissing: true,
      encrypt: false,
      backup: false,
      autoSync: true,  // Auto sync across devices
      kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
      securityLevel: distributedKVStore.SecurityLevel.S1
    };

    this.kvStore = await this.kvManager.getKVStore('myStore', options);
  }

  async put(key: string, value: string): Promise<void> {
    await this.kvStore?.put(key, value);
  }

  async get(key: string): Promise<string | null> {
    try {
      return await this.kvStore?.get(key) as string;
    } catch {
      return null;
    }
  }

  // Subscribe to changes from other devices
  subscribe(callback: (key: string, value: string) => void): void {
    this.kvStore?.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL,
      (data: distributedKVStore.ChangeNotification) => {
        for (const entry of data.insertEntries) {
          callback(entry.key, entry.value.value as string);
        }
        for (const entry of data.updateEntries) {
          callback(entry.key, entry.value.value as string);
        }
      }
    );
  }
}

Device Discovery and Connection

import { distributedDeviceManager } from '@kit.DistributedServiceKit';

class DeviceManager {
  private deviceManager: distributedDeviceManager.DeviceManager | null = null;

  async init(context: Context): Promise<void> {
    this.deviceManager = distributedDeviceManager.createDeviceManager(
      context.applicationInfo.name
    );
  }

  getAvailableDevices(): distributedDeviceManager.DeviceBasicInfo[] {
    return this.deviceManager?.getAvailableDeviceListSync() ?? [];
  }

  startDiscovery(): void {
    const filter: distributedDeviceManager.DiscoveryFilter = {
      discoverMode: distributedDeviceManager.DiscoverMode.DISCOVER_MODE_PASSIVE
    };

    this.deviceManager?.startDiscovering(filter);

    this.deviceManager?.on('discoverSuccess', (data) => {
      console.info(`Found device: ${data.device.deviceName}`);
    });
  }

  stopDiscovery(): void {
    this.deviceManager?.stopDiscovering();
  }
}

Multi-Device Adaptation

Responsive Layout

import { BreakpointSystem, BreakPointType } from '../utils/BreakpointSystem';

@Entry
@Component
struct AdaptivePage {
  @StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm';

  build() {
    GridRow({
      columns: { sm: 4, md: 8, lg: 12 },
      gutter: { x: 12, y: 12 }
    }) {
      GridCol({ span: { sm: 4, md: 4, lg: 3 } }) {
        // Sidebar - full width on phone, 1/3 on tablet, 1/4 on desktop
        Sidebar()
      }

      GridCol({ span: { sm: 4, md: 4, lg: 9 } }) {
        // Content - full width on phone, 2/3 on tablet, 3/4 on desktop
        MainContent()
      }
    }
  }
}

// Breakpoint system
export class BreakpointSystem {
  private static readonly BREAKPOINTS: Record<string, number> = {
    'sm': 320,   // Phone
    'md': 600,   // Foldable/Tablet
    'lg': 840    // Desktop/TV
  };

  static register(context: UIContext): void {
    context.getMediaQuery().matchMediaSync('(width >= 840vp)').on('change', (result) => {
      AppStorage.setOrCreate('currentBreakpoint', result.matches ? 'lg' : 'md');
    });

    context.getMediaQuery().matchMediaSync('(width >= 600vp)').on('change', (result) => {
      if (!result.matches) {
        AppStorage.setOrCreate('currentBreakpoint', 'sm');
      }
    });
  }
}

Testing

Unit Testing

import { describe, it, expect, beforeEach } from '@ohos/hypium';
import { ProductViewModel } from '../viewmodel/ProductViewModel';

export default function ProductViewModelTest() {
  describe('ProductViewModel', () => {
    let viewModel: ProductViewModel;

    beforeEach(() => {
      viewModel = new ProductViewModel();
    });

    it('should load products successfully', async () => {
      await viewModel.loadProducts();

      expect(viewModel.products.length).assertLarger(0);
      expect(viewModel.isLoading).assertFalse();
      expect(viewModel.errorMessage).assertEqual('');
    });

    it('should add product to list', async () => {
      const initialCount = viewModel.products.length;
      const newProduct: Product = { id: 'test', name: 'Test Product', price: 99 };

      await viewModel.addProduct(newProduct);

      expect(viewModel.products.length).assertEqual(initialCount + 1);
    });
  });
}

UI Testing

import { describe, it, expect } from '@ohos/hypium';
import { Driver, ON } from '@ohos.UiTest';

export default function ProductPageUITest() {
  describe('ProductPage UI', () => {
    it('should display product list', async () => {
      const driver = Driver.create();
      await driver.delayMs(1000);

      // Find and verify list exists
      const list = await driver.findComponent(ON.type('List'));
      expect(list).not().assertNull();

      // Verify list items
      const items = await driver.findComponents(ON.type('ListItem'));
      expect(items.length).assertLarger(0);
    });

    it('should navigate to detail on tap', async () => {
      const driver = Driver.create();

      // Find first product card
      const card = await driver.findComponent(ON.type('ProductCard'));
      await card.click();

      await driver.delayMs(500);

      // Verify navigation to detail page
      const detailTitle = await driver.findComponent(ON.text('Product Detail'));
      expect(detailTitle).not().assertNull();
    });
  });
}

Checklist

## Project Setup
- [ ] Stage model used (not FA model)
- [ ] module.json5 properly configured
- [ ] Permissions declared in module.json5
- [ ] Resource files organized (strings, images)

## Code Quality
- [ ] No `any` types in codebase
- [ ] All state decorated with proper decorators
- [ ] No direct mutation of @State objects
- [ ] Components extracted for reusability
- [ ] Lifecycle methods used appropriately

## UI/UX
- [ ] LazyForEach used for long lists
- [ ] Loading states implemented
- [ ] Error handling with user feedback
- [ ] Multi-device layouts with GridRow/GridCol
- [ ] Accessibility attributes added

## State Management
- [ ] Clear state ownership (component vs global)
- [ ] @Observed/@ObjectLink for nested objects
- [ ] PersistentStorage for user preferences
- [ ] AppStorage for app-wide state

## Performance
- [ ] Images optimized and cached
- [ ] Unnecessary re-renders avoided
- [ ] Network requests with proper error handling
- [ ] Background tasks properly managed

## Testing
- [ ] Unit tests for ViewModels
- [ ] UI tests for critical flows
- [ ] Edge cases covered

See Also

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

04

需要参考平台分布和安装热度时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Claude Code

30.09%
按下载量换算702

Cursor

22.39%
按下载量换算522

OpenCode

17.51%
按下载量换算408

trae

14.6%
按下载量换算340

Gemini CLI

8.3%
按下载量换算194

Codex

3.67%
按下载量换算86

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

来源信息

继续浏览同类 Skills