Agent Skills by ALSEL
Anthropic ClaudeLLM・AI開発⭐ リポ 0品質スコア 50/100

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
リポジトリ
majiayu000/claude-arsenal
ライセンス
MIT
最終更新
不明

Source: https://github.com/majiayu000/claude-arsenal / ライセンス: MIT

関連スキル

OpenAILLM・AI開発⭐ リポ 6,054

agent-browser

AI エージェント向けのブラウザ自動化 CLI です。ウェブサイトとの対話が必要な場合に使用します。ページ遷移、フォーム入力、ボタンクリック、スクリーンショット取得、データ抽出、ウェブアプリのテスト、ブラウザ操作の自動化など、あらゆるブラウザタスクに対応できます。「ウェブサイトを開く」「フォームに記入する」「ボタンをクリックする」「スクリーンショットを取得する」「ページからデータを抽出する」「このウェブアプリをテストする」「サイトにログインする」「ブラウザ操作を自動化する」といった要求や、プログラマティックなウェブ操作が必要なタスクで起動します。

by JimmyLv
汎用LLM・AI開発⭐ リポ 1,982

anyskill

AnySkill — あなたのプライベート・スキルクラウド。GitHubを基盤としたリポジトリからエージェントスキルを管理、同期、動的にロードできます。自然言語でクラウドスキルを検索し、オンデマンドでプロンプトを自動ロード、カスタムスキルのアップロードと共有、スキルバンドルの一括インストールが可能です。OpenClaw、Antigravity、Claude Code、Cursorに対応しています。

by LeoYeAI
汎用LLM・AI開発⭐ リポ 1,982

engram

AIエージェント向けの永続的なメモリシステムです。バグ修正、意思決定、発見、設定変更の後はmem_saveを使用してください。ユーザーが「覚えている」「記憶している」と言及した場合、または以前のセッションと重複する作業を開始する際はmem_searchを使用します。セッション終了前にmem_session_summaryを使用して、コンテキストを保持してください。

by LeoYeAI
汎用LLM・AI開発⭐ リポ 21,584

skyvern

AI駆動のブラウザ自動化により、任意のウェブサイトを自動化できます。フォーム入力、データ抽出、ファイルダウンロード、ログイン、複数ステップのワークフロー実行など、ユーザーがウェブサイトと連携する必要があるときに使用します。Skyvernは、LLMとコンピュータビジョンを活用して、未知のサイトも自動操作可能です。Python SDK、TypeScript SDK、REST API、MCPサーバー、またはCLIを通じて統合できます。

by Skyvern-AI
汎用LLM・AI開発⭐ リポ 1,149

pinchbench

PinchBenchベンチマークを実行して、OpenClawエージェントの実世界タスクにおけるパフォーマンスを評価できます。モデルの機能テスト、モデル間の比較、ベンチマーク結果のリーダーボード提出、またはOpenClawのセットアップがカレンダー、メール、リサーチ、コーディング、複数ステップのワークフローにどの程度対応しているかを確認する際に使用します。

by pinchbench
汎用LLM・AI開発⭐ リポ 4,693

openui

OpenUIとOpenUI Langを使用してジェネレーティブUIアプリを構築できます。これらはLLM生成インターフェースのためのトークン効率的なオープン標準です。OpenUI、@openuidev、ジェネレーティブUI、LLMからのストリーミングUI、AI向けコンポーネントライブラリ、またはjson-render/A2UIの置き換えについて述べる際に使用します。スキャフォルディング、defineComponent、システムプロンプト、Renderer、およびOpenUI Lang出力のデバッグに対応しています。

by thesysdev
本サイトは GitHub 上で公開されているオープンソースの SKILL.md ファイルをクロール・インデックス化したものです。 各スキルの著作権は原作者に帰属します。掲載に問題がある場合は info@alsel.co.jp または /takedown フォームよりご連絡ください。
原作者: majiayu000 · majiayu000/claude-arsenal · ライセンス: MIT