harmonyos-app
HarmonyOSアプリケーション開発の専門スキルです。ArkTS、ArkUI、Stageモデル、分散機能を活用したHarmonyOSアプリの構築時に使用してください。HarmonyOS NEXT(API 12以降)のベストプラクティスに対応しています。
description の原文を見る
HarmonyOS application development expert. Use when building HarmonyOS apps with ArkTS, ArkUI, Stage model, and distributed capabilities. Covers HarmonyOS NEXT (API 12+) best practices.
SKILL.md 本文
HarmonyOS アプリケーション開発
コア原則
- ArkTS First — 厳密な型安全性を備えた ArkTS を使用し、
anyや動的型を使わない - 宣言的 UI — ArkUI の宣言的コンポーネントと状態管理で UI を構築
- Stage モデル — 古い FA モデルではなく、最新の Stage モデル(UIAbility)を使用
- 分散設計 — 設計段階からクロスデバイス機能を活用
- アトミック サービス — 軽量な体験のためにアトミック サービスとカードを検討
- ワンタイム開発 — マルチデバイス対応(スマートフォン、タブレット、スマートウォッチ、TV)を意識した設計
厳守ルール(必ず従うこと)
これらのルールは必須です。違反すると、このスキルは正常に機能しません。
動的型の禁止
ArkTS は動的型付けを禁止します。any、型アサーション、または動的プロパティアクセスを使用しないでください。
// ❌ 禁止: 動的型
let data: any = fetchData();
let obj: object = {};
obj['dynamicKey'] = value; // 動的プロパティアクセス
(someVar as SomeType).method(); // 型アサーション
// ✅ 必須: 厳密な型付け
interface UserData {
id: string;
name: string;
}
let data: UserData = fetchData();
// 動的キーには Record を使用
let obj: Record<string, string> = {};
obj['key'] = value; // Record 型で OK
直接的な状態変更の禁止
ネストされたオブジェクト内の @State/@Prop 変数を直接変更しないでください。イミュータブルな更新を使用してください。
// ❌ 禁止: 直接変更
@State user: User = { name: 'John', age: 25 };
updateAge() {
this.user.age = 26; // UI は更新されません!
}
// ✅ 必須: イミュータブルな更新
updateAge() {
this.user = { ...this.user, age: 26 }; // 新しいオブジェクトを作成し、UI 更新をトリガー
}
// 配列の場合
@State items: string[] = ['a', 'b'];
// ❌ 禁止
this.items.push('c'); // UI は更新されません
// ✅ 必須
this.items = [...this.items, 'c'];
Stage モデルのみ
常に Stage モデル(UIAbility)を使用してください。非推奨の FA モデル(PageAbility)を使用しないでください。
// ❌ 禁止: FA モデル(非推奨)
// config.json with "pages" array
export default {
onCreate() { ... } // PageAbility ライフサイクル
}
// ✅ 必須: Stage モデル
// module.json5 with abilities configuration
import { UIAbility } from '@kit.AbilityKit';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 最新の Stage モデルライフサイクル
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index');
}
}
コンポーネント再利用性
再利用可能な UI を @Component として抽出してください。build() メソッド内に複雑な UI をインラインで書かないでください。
// ❌ 禁止: 巨大な build メソッド
@Entry
@Component
struct MainPage {
build() {
Column() {
// 200+ 行のインライン UI...
Row() {
Image($r('app.media.avatar'))
Column() {
Text(this.user.name)
Text(this.user.email)
}
}
// さらにインライン UI...
}
}
}
// ✅ 必須: コンポーネントを抽出
@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 })
}
}
}
クイック リファレンス
何をいつ使うか
| シナリオ | パターン | 例 |
|---|---|---|
| コンポーネントローカル状態 | @State | カウンター、フォーム入力 |
| 親から子へのデータ | @Prop | 読み取り専用の子データ |
| 双方向バインディング | @Link | 共有可変状態 |
| クロスコンポーネント状態 | @Provide/@Consume | テーマ、ユーザーコンテキスト |
| 永続的な状態 | PersistentStorage | ユーザー設定 |
| アプリ全体の状態 | AppStorage | グローバル状態 |
| 複雑な状態ロジック | @Observed/@ObjectLink | ネストされたオブジェクト更新 |
状態デコレータ選択
@State → コンポーネントが状態を所有し、変更時に再レンダリングをトリガー
@Prop → 親が値を渡し、子はコピーを取得(一方向)
@Link → 親が参照を渡し、子は変更可能(双方向)
@Provide → 祖先がすべての子孫に値を提供
@Consume → 子孫が祖先から値を消費
@StorageLink → AppStorage と同期、双方向バインディング
@StorageProp → AppStorage と同期、一方向バインディング
@Observed → 監視可能なオブジェクトのクラスデコレータ
@ObjectLink → 親の @Observed オブジェクトにリンク
プロジェクト構造
推奨アーキテクチャ
MyApp/
├── entry/ # メイン entry モジュール
│ ├── src/main/
│ │ ├── ets/
│ │ │ ├── entryability/ # UIAbility 定義
│ │ │ │ └── EntryAbility.ets
│ │ │ ├── pages/ # ページコンポーネント
│ │ │ │ ├── Index.ets
│ │ │ │ └── Detail.ets
│ │ │ ├── components/ # 再利用可能な UI コンポーネント
│ │ │ │ ├── common/ # 共通コンポーネント
│ │ │ │ └── business/ # ビジネス固有コンポーネント
│ │ │ ├── viewmodel/ # ViewModel(MVVM)
│ │ │ ├── model/ # データモデル
│ │ │ ├── service/ # ビジネスロジックサービス
│ │ │ ├── repository/ # データアクセス層
│ │ │ ├── utils/ # ユーティリティ関数
│ │ │ └── constants/ # 定数と設定
│ │ ├── resources/ # リソース(文字列、画像)
│ │ └── module.json5 # モジュール設定
│ └── build-profile.json5
├── common/ # 共有ライブラリモジュール
│ └── src/main/ets/
├── features/ # フィーチャーモジュール
│ ├── feature_home/
│ └── feature_profile/
└── build-profile.json5 # プロジェクト設定
レイヤー分離
┌─────────────────────────────────────┐
│ UI レイヤー(ページ) │ ArkUI コンポーネント
├─────────────────────────────────────┤
│ ViewModel レイヤー │ 状態管理、UI ロジック
├─────────────────────────────────────┤
│ Service レイヤー │ ビジネスロジック
├─────────────────────────────────────┤
│ Repository レイヤー │ データアクセス抽象化
├─────────────────────────────────────┤
│ データソース(ローカル/リモート) │ Preferences、RDB、ネットワーク
└─────────────────────────────────────┘
ArkUI コンポーネント パターン
基本的なコンポーネント構造
import { router } from '@kit.ArkUI';
@Component
export struct ProductCard {
// 親からの Props
@Prop product: Product;
@Prop onAddToCart: (product: Product) => void;
// ローカル状態
@State isExpanded: boolean = false;
// 計算値(ゲッターを使用)
get formattedPrice(): string {
return `¥${this.product.price.toFixed(2)}`;
}
// ライフサイクル
aboutToAppear(): void {
console.info('ProductCard appearing');
}
aboutToDisappear(): void {
console.info('ProductCard disappearing');
}
// イベントハンドラ
private handleTap(): void {
router.pushUrl({ url: 'pages/ProductDetail', params: { id: this.product.id } });
}
private handleAddToCart(): void {
this.onAddToCart(this.product);
}
// UI ビルダー
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())
}
}
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) // キー生成関数
}
.lanes(2) // 2 列のグリッド
.cachedCount(4) // スムーズなスクロール用に 4 項目をキャッシュ
}
}
カスタム ダイアログ
@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)
}
}
// 使用方法
@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 {
// 削除ロジック
}
build() {
Button('Delete')
.onClick(() => this.dialogController.open())
}
}
状態管理パターン
ViewModel パターン
// 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
// EntryAbility で初期化
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// グローバル状態を初期化
AppStorage.setOrCreate('isLoggedIn', false);
AppStorage.setOrCreate('currentUser', null);
AppStorage.setOrCreate('theme', 'light');
}
}
// コンポーネント内でアクセス
@Entry
@Component
struct ProfilePage {
@StorageLink('isLoggedIn') isLoggedIn: boolean = false;
@StorageLink('currentUser') currentUser: User | null = null;
@StorageProp('theme') theme: string = 'light'; // 読み取り専用
build() {
Column() {
if (this.isLoggedIn && this.currentUser) {
Text(`Welcome, ${this.currentUser.name}`)
} else {
Button('Login')
.onClick(() => {
// ログイン後
this.isLoggedIn = true;
this.currentUser = { id: '1', name: 'John' };
})
}
}
}
}
ユーザー設定用 PersistentStorage
// 永続ストレージを初期化
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 };
})
}
}
}
ナビゲーション パターン
ルーター ナビゲーション
import { router } from '@kit.ArkUI';
// ページに移動
router.pushUrl({
url: 'pages/Detail',
params: { productId: '123' }
});
// 結果と共に移動
router.pushUrl({
url: 'pages/SelectAddress'
}).then(() => {
// ナビゲーション完了
});
// ターゲットページでパラメーターを取得
@Entry
@Component
struct DetailPage {
@State productId: string = '';
aboutToAppear(): void {
const params = router.getParams() as Record<string, string>;
this.productId = params?.productId ?? '';
}
}
// 戻る
router.back();
// 現在のページを置き換え
router.replaceUrl({ url: 'pages/Home' });
// スタックをクリアして移動
router.clear();
router.pushUrl({ url: 'pages/Login' });
Navigation コンポーネント(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')
}
}
ネットワーク リクエスト
HTTP クライアント
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();
分散機能
クロスデバイス データ同期
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, // デバイス間で自動同期
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(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);
}
}
);
}
}
デバイス検出と接続
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();
}
}
マルチデバイス適応
レスポンシブ レイアウト
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 } }) {
// サイドバー - 電話では全幅、タブレットでは 1/3、デスクトップでは 1/4
Sidebar()
}
GridCol({ span: { sm: 4, md: 4, lg: 9 } }) {
// コンテンツ - 電話では全幅、タブレットでは 2/3、デスクトップでは 3/4
MainContent()
}
}
}
}
// ブレークポイント システム
export class BreakpointSystem {
private static readonly BREAKPOINTS: Record<string, number> = {
'sm': 320, // スマートフォン
'md': 600, // 折り畳み式/タブレット
'lg': 840 // デスクトップ/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');
}
});
}
}
テスト
ユニット テスト
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 テスト
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);
// リスト存在を検出して確認
const list = await driver.findComponent(ON.type('List'));
expect(list).not().assertNull();
// リストアイテムを確認
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();
// 最初の製品カードを検出
const card = await driver.findComponent(ON.type('ProductCard'));
await card.click();
await driver.delayMs(500);
// 詳細ページへのナビゲーションを確認
const detailTitle = await driver.findComponent(ON.text('Product Detail'));
expect(detailTitle).not().assertNull();
});
});
}
チェックリスト
## プロジェクト セットアップ
- [ ] Stage モデルを使用(FA モデルではない)
- [ ] module.json5 が正しく設定されている
- [ ] module.json5 に権限が宣言されている
- [ ] リソースファイルが整理されている(文字列、画像)
## コード品質
- [ ] コードベースに `any` 型がない
- [ ] すべての状態が適切なデコレータで装飾されている
- [ ] @State オブジェクトの直接変更がない
- [ ] 再利用性のためにコンポーネントが抽出されている
- [ ] ライフサイクルメソッドが適切に使用されている
## UI/UX
- [ ] 長いリストに LazyForEach を使用
- [ ] ロード状態が実装されている
- [ ] エラーハンドリングとユーザーフィードバック
- [ ] GridRow/GridCol を使用したマルチデバイス レイアウト
- [ ] アクセシビリティ属性が追加されている
## 状態管理
- [ ] 明確な状態の所有(コンポーネント vs グローバル)
- [ ] ネストされたオブジェクトに @Observed/@ObjectLink を使用
- [ ] ユーザー設定に PersistentStorage を使用
- [ ] アプリ全体の状態に AppStorage を使用
## パフォーマンス
- [ ] 画像が最適化されてキャッシュされている
- [ ] 不要な再レンダリングを回避
- [ ] 適切なエラーハンドリングを使用したネットワーク リクエスト
- [ ] バックグラウンドタスクが適切に管理されている
## テスト
- [ ] ViewModel のユニット テスト
- [ ] 重要なフローの UI テスト
- [ ] エッジケースがカバーされている
参考資料
reference/arkts.md— ArkTS 言語ガイドと制限reference/arkui.md— ArkUI コンポーネントとスタイリングreference/stage-model.md— Stage モデル アーキテクチャreference/distributed.md— 分散機能ガイドtemplates/project-structure.md— プロジェクト テンプレート
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- majiayu000
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/majiayu000/claude-arsenal / ライセンス: MIT
関連スキル
agent-browser
AI エージェント向けのブラウザ自動化 CLI です。ウェブサイトとの対話が必要な場合に使用します。ページ遷移、フォーム入力、ボタンクリック、スクリーンショット取得、データ抽出、ウェブアプリのテスト、ブラウザ操作の自動化など、あらゆるブラウザタスクに対応できます。「ウェブサイトを開く」「フォームに記入する」「ボタンをクリックする」「スクリーンショットを取得する」「ページからデータを抽出する」「このウェブアプリをテストする」「サイトにログインする」「ブラウザ操作を自動化する」といった要求や、プログラマティックなウェブ操作が必要なタスクで起動します。
anyskill
AnySkill — あなたのプライベート・スキルクラウド。GitHubを基盤としたリポジトリからエージェントスキルを管理、同期、動的にロードできます。自然言語でクラウドスキルを検索し、オンデマンドでプロンプトを自動ロード、カスタムスキルのアップロードと共有、スキルバンドルの一括インストールが可能です。OpenClaw、Antigravity、Claude Code、Cursorに対応しています。
engram
AIエージェント向けの永続的なメモリシステムです。バグ修正、意思決定、発見、設定変更の後はmem_saveを使用してください。ユーザーが「覚えている」「記憶している」と言及した場合、または以前のセッションと重複する作業を開始する際はmem_searchを使用します。セッション終了前にmem_session_summaryを使用して、コンテキストを保持してください。
skyvern
AI駆動のブラウザ自動化により、任意のウェブサイトを自動化できます。フォーム入力、データ抽出、ファイルダウンロード、ログイン、複数ステップのワークフロー実行など、ユーザーがウェブサイトと連携する必要があるときに使用します。Skyvernは、LLMとコンピュータビジョンを活用して、未知のサイトも自動操作可能です。Python SDK、TypeScript SDK、REST API、MCPサーバー、またはCLIを通じて統合できます。
pinchbench
PinchBenchベンチマークを実行して、OpenClawエージェントの実世界タスクにおけるパフォーマンスを評価できます。モデルの機能テスト、モデル間の比較、ベンチマーク結果のリーダーボード提出、またはOpenClawのセットアップがカレンダー、メール、リサーチ、コーディング、複数ステップのワークフローにどの程度対応しているかを確認する際に使用します。
openui
OpenUIとOpenUI Langを使用してジェネレーティブUIアプリを構築できます。これらはLLM生成インターフェースのためのトークン効率的なオープン標準です。OpenUI、@openuidev、ジェネレーティブUI、LLMからのストリーミングUI、AI向けコンポーネントライブラリ、またはjson-render/A2UIの置き換えについて述べる際に使用します。スキャフォルディング、defineComponent、システムプロンプト、Renderer、およびOpenUI Lang出力のデバッグに対応しています。