Communityコーディング&開発github.com

partite-ai/write-particle-typescript

Partite Particle(TypeScript または JavaScript)を作成、編集、拡張するためのスキル。ツールハンドラ、API連携、認証情報、実行時機能に使用します。Python Particleの場合は、対応する言語スキルを使用してください。

write-particle-typescript とは?

このスキルは、Partite プラットフォーム向けに TypeScript または JavaScript で書かれた Particle(単一ファイルのプログラム)を作成するための完全なガイドを提供します。Particle は、JSON-Schema 入力を持つ名前付き操作(ツール)を公開し、Particle ランタイム内で実行されます。Particlefile.ts または Particlefile.js という特定のファイル構造、デフォルトエクスポートの形式、ツール、ping ハンドラ、そして capabilities 宣言(HTTP 許可リスト、ファイルシステムアクセス、キーバリューストアなど)について詳しく説明します。また、Outbound HTTP、ファイルシステム(node:fs 経由)、認証情報(OAuth2、API キー、ベーシック認証など)、キーバリューストア、OAuth リフレッシュ、署名などのランタイム API もカバーしています。このスキルは、Claude Code、Cursor、Codex などの AI エージェントで Particle を構築・実行するための実践的な知識を提供し、npm 依存関係のインライン管理、`particle build`、`particle run`、`particle link` などのワークフローのベストプラクティスも含まれています。

対応✓Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/partite-ai/partite-skills/tree/main/skills/write-particle-typescript

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

TypeScript/JavaScript particle を書く

Particle は、Particle ランタイムに対してツール(JSON-Schema 入力を持つ名前付き操作)を公開する単一ファイルのプログラムです。このスキルは、ビルドと実行が可能な Particle を生成するために必要なすべてを説明します。

1. ファイルレイアウト

プロジェクトルートに、以下の正確な名前でソースファイルを1つ配置します:

  • Particlefile.ts — 推奨(完全な型チェック)
  • Particlefile.js — 動作しますが、型はありません。

他に必要なものはありません。npm 依存関係は、標準の import 文でインライン宣言されます。ビルドがそれらを解決します。package.json、tsconfig.json、node_modules を管理する必要はありません。

2. デフォルトエクスポート — 全体のAPI

ファイルのデフォルトエクスポートが particle 宣言です。形式:

export default {
  name: "kebab-case-name",        // 必須、kebab-case
  description: "...",              // 必須、1行
  version: "0.1.0",                // 必須、semver

  capabilities: {
    // アウトバウンドHTTP許可リスト。ここにリストされていないホストへのリクエストはネットワーク境界で拒否されます。
    // particle がアウトバウンドリクエストを行わない場合は、`http` を省略してください。
    http: { allowedHosts: ["api.example.com"] },

    // ホストディレクトリへのアクセス。デフォルトではオフです。以下の「ファイルシステム」を参照してください。
    // filesystem: { mounts: { ... }, temp: { ... } },
  },

  // オプション。名前ごとに宣言します。ユーザーはセットアップ時にメソッドを選択します。
  credentials: {
    example: {
      hosts: ["api.example.com"],   // これらのホストにサブスティテューションをバインド
      required: true,
      methods: {
        // 以下の「資格情報メソッド」を参照
      },
    },
  },

  // 必須。ツール名 → { description, inputSchema, handler } のマップ。
  tools: {
    do_thing: {
      description: "このツールが行うこと — LLM向けに書かれた1行の説明。",
      inputSchema: {
        type: "object",
        properties: {
          name: { type: "string", description: "..." },
        },
        required: ["name"],
      },
      handler: async ({ name }: { name: string }) => {
        return { result: `hello ${name}` };
      },
    },
  },

  // オプション。liveness/readiness を返します。`particle ping` によって呼び出されます。
  ping: async () => ({ status: "ok" as const, message: "alive" }),
};

Tools

  • description: LLMの呼び出し元向けに書かれます — 短く、アクション指向。
  • inputSchema: JSON Schema Draft 2020-12、オブジェクトをルートとします。ホストは、ハンドラを呼び出す前に引数を検証するため、ハンドラ内で入力の形状を信頼できます。required を使用して必須フィールドをマークし、オプションのフィールドには default を指定します。
  • handler: 同期または非同期。JSON シリアライズ可能な任意の値を返します。呼び出し元にエラーを通知するには throw します — スローされたメッセージが表示されます。

Ping

オプションですが推奨されます。戻り値の形式:

{ status: "ok" | "degraded" | "unhealthy",
  message?: string,
  details?: string }

3. ハンドラで利用可能なランタイムAPI

インポートできるのは、これらのみが非標準ライブラリのものです。それぞれはランタイムによって提供されるホストモジュールです — 型のみのnpmパッケージとして提供され、実装は実行時に注入されます。

アウトバウンドHTTP

グローバル fetch() を使用します。これは標準の Web Fetch API で、ホストを経由してルーティングされるため、許可リストと資格情報のサブスティテューションが適用されます。

const res = await fetch("https://api.example.com/things", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ ... }),
});
if (!res.ok) throw new Error(`upstream ${res.status}`);
const data = await res.json();

capabilities.http.allowedHosts にないホストは拒否されます。通常の fetch は資格情報を適用しません — そのためには credentials.fetcher を使用してください。

ファイルシステム

デフォルトではオフ — ハンドラは自身のバンドルのみを参照します。capabilities.filesystem を宣言してホストディレクトリへのアクセスを取得し、通常の node:fs API を使用して読み書きします(ランタイムはこれらを wasi:filesystem 経由でルーティングします)。インポートする機能モジュールはありません。

capabilities: {
  filesystem: {
    // *ユーザー*が実際のパスにマッピングするホストディレクトリ — `particle mount <particle> <name> <host-path>` で永続的に、
    // または `--mount <name>=<host-path>` で実行ごとにマッピングします。
    mounts: {
      data: {
        description: "レポートの書き込み先",  // インストールプロンプトに表示されます
        path: "/mnt/data",                          // サンドボックス内の絶対パス
        access: "readwrite",                        // "readonly" | "readwrite"
        required: true,                             // マッピングされるまで実行を拒否(デフォルト false)
      },
    },
    // ホストが実行ごとに新しくプロビジョニングし、終了時にクリアするスクラッチスペース。
    // 常に読み書き可能。ユーザーがマッピングすることはありません。
    temp: {
      work: { description: "スクラッチスペース", path: "/tmp/work", maxSize: "10MB" },
    },
  },
},
import { readFile, writeFile } from "node:fs/promises";

const input = await readFile("/mnt/data/in.json", "utf8");
await writeFile("/mnt/data/out.json", JSON.stringify(result));
  • ハンドラは宣言された path を直接開きます。マウント名がサンドボックス内に現れることはありません。
  • readonly マウントへの書き込み、または宣言されたマウント外のパスの読み取り/書き込みは、エラーをスローします。
  • ユーザーがマッピングしなかった required マウントは、ビルド/インストール時ではなく実行時に失敗します。
  • maxSize は、オプションの KB/MB/GB サフィックス("10MB")を持つバイト数です。これを超える書き込みは失敗します。

資格情報

import { credentials } from "@partite-ai/particle-credentials";

// fetcher(name) は、リクエストがサンドボックスから送出される前に、ホストが設定した場所(ヘッダー、クエリパラメータなど)に資格情報を自動適用する fetch 形式の関数を返します。
const fetcher = await credentials.fetcher("example");
const res = await fetcher("https://api.example.com/me");

// ユーザーがセットアップ時に設定したメソッドを検出します("oauth", "pat", ...)。
const method = credentials.getConfiguredMethod("example"); // string | null

// `type: "raw"` で宣言された資格情報のみ。
const value = await credentials.getRaw("some-raw-cred");

credentials.fetcher は、basic、oauth2、apikey 資格情報で動作します。signing-key の場合は signing.sign を、raw の場合は getRaw を使用します。

キーバリューストア

Particleごとにスコープされます。文字列のみ — バイナリの場合は必要に応じて base64 エンコードしてください。capabilities.kv の下に宣言します — 宣言がない場合、すべての呼び出しは not-declared エラーをスローします(ユーザーの承認は不要で、宣言のみが必要です):

capabilities: { kv: { enabled: true } },
import { kv } from "@partite-ai/particle-kv";

await kv.set("cursor", "abc123");
const v = await kv.get("cursor");        // string | null
await kv.delete("cursor");
const keys = await kv.list("prefix:");    // string[]

OAuth リフレッシュ

oauth2 資格情報がある場合のみ。フェッチャーは期限切れのトークンを自動的にリフレッシュしますが、まだ有効だと思っていたトークンが上流で拒否された場合にのみこれを呼び出してください。

import { oauth } from "@partite-ai/particle-oauth";
await oauth.refresh("example");

署名

signing-key 資格情報のみ。鍵の素材は JS に入りません。

import { signing } from "@partite-ai/particle-signing";

const sig = await signing.sign("webhook-key", new TextEncoder().encode(payload));
const ok  = await signing.verify("webhook-key", data, sig);

4. 資格情報メソッド

各資格情報には methods マップがあります。ユーザーはセットアップ時に1つを選択します。Particle は通常、同じプロバイダに対して複数のメソッドを提供します。

oauth2

oauth: {
  type: "oauth2",
  description: "OAuth でサインイン",
  flows: ["authorization-code", "device-code"],   // この3つから任意のものを選択
  scopes: ["read", "write"],
  authorizationUrl: "https://provider/oauth/authorize",
  tokenUrl:         "https://provider/oauth/token",
  deviceAuthUrl:    "https://provider/oauth/device/code",  // "device-code" の場合必須
},

有効なフロー文字列: "authorization-code"、"authorization-code-pkce"、"device-code"。

apikey

pat: {
  type: "apikey",
  description: "パーソナルアクセストークンを使用",
  location: { kind: "auth-scheme", scheme: "Bearer" },
},

location.kind は以下のいずれか:

  • "header" + name: <name>: <key>
  • "auth-scheme" + scheme: Authorization: <scheme> <key> (例: Bearer)
  • "query-param" + name: ?<name>=<key> として追加

location を完全に省略すると、インポーターがユーザーに入力を促します。

basic

basic_auth: { type: "basic", description: "ユーザー名 + パスワード" },

Authorization: Basic <base64> としてサブスティテュートされます。

signing-key

webhook: { type: "signing-key", description: "...", algorithm: "hmac-sha256" },

有効: "hmac-sha256"、"hmac-sha512"。signing.sign を使用します。

raw

api_key_blob: { type: "raw", description: "..." },

credentials.getRaw(name) を使用します。本当に生のバイトが必要な場合にのみこれを選択してください — 他の型は値がコードに入らないため、より安全です。

5. npm 依存関係

任意の pure-ESM npm パッケージを直接インポートします:

import { z } from "zod";
import * as YAML from "yaml";

ビルドはインポートスキャンを行い、自動的に解決します — package.json は不要です。制約:

  • パッケージは pure-JS/TS である必要があります(ネイティブバインディングや Node 専用APIは不可)。
  • ブラウザ互換のビルドが最適です。
  • fs、child_process などは避けてください — これらはランタイムに存在しません。

6. 重要なルール

  1. モジュールスコープでのホスト呼び出し禁止。 ファイルのトップレベルで fetch、kv.get、credentials.fetcher などを呼び出すと、ビルド時に失敗します(イントロスペクトフェーズでは、トラップストアの下でモジュールが実行されます)。すべてのホスト呼び出しは handler または ping 内で行ってください。

    // ✗ ビルドに失敗します
    const fetcher = await credentials.fetcher("github");
    export default { tools: { ... } };
    
    // ✓ ハンドラ内で
    export default {
      tools: {
        go: { handler: async (args) => {
          const fetcher = await credentials.fetcher("github");
          // ...
        }},
      },
    };
    
  2. フェッチするすべてのホストを宣言してください。 capabilities.http.allowedHosts にないホストへのリクエストは拒否されます。エラーはハンドラ内でスローされた例外として発生します。

  3. credentials.<name>.hosts は HTTP 許可リストのサブセットである必要があります。 ビルドは、particle が到達できないホストにバインドされた資格情報を拒否します。

  4. name は kebab-case(小文字、ハイフン)でなければなりません。 レジストリキーは (name, version) です。

  5. version は有効な semver でなければなりません。 0.1.0、1.2.3-rc.1 など。

  6. ツールハンドラは JSON シリアライズ可能な値を返す必要があります。 undefined を返すのは問題ありません(null になります)。クラスインスタンス、Date、BigInt、Map を返すとシリアライズに失敗します。

7. ワークフロー

Particlefile.ts を作成した後:

particle build                          # 型チェック、バンドル、イントロスペクト、登録
particle ping <name>                    # ping を確認(定義されている場合)
particle run  <name>                    # ツール一覧
particle run  <name> <tool> --help      # ツールのフラグを表示
particle run  <name> <tool> --foo=bar   # 実行

particle build は、初回に対話的に資格情報設定をユーザーに案内します。ビルドが失敗した場合、エラーはフェーズ(import-scan、typecheck、bundle、manifest-extract)と具体的な問題を示します。

particle がファイルシステムマウントを宣言している場合、実行前にマッピングするか、particle run で --mount name=path を渡します(一時的な場合):

particle mount <name> <mount-name> <host-path>   # 永続的なマッピングを保存
particle mount <name>                            # マウントと現在のマッピングを一覧表示

particle をスタンドアロン実行可能ファイルとして公開するには(Claude Code スキルやシェルでの使用に便利)、particle link <name> ./<name> が引数を particle run <name> に転送するランチャーを書き込みます。

8. 実例

GitHub リポジトリ検索ツールを公開する最小限の particle:

import { credentials } from "@partite-ai/particle-credentials";

export default {
  name: "github-lookup",
  description: "GitHub リポジトリのメタデータを検索します。",
  version: "0.1.0",

  capabilities: { http: { allowedHosts: ["api.github.com"] } },

  credentials: {
    github: {
      hosts: ["api.github.com"],
      required: true,
      methods: {
        pat: {
          type: "apikey",
          description: "GitHub パーソナルアクセストークン",
          location: { kind: "auth-scheme", scheme: "Bearer" },
        },
      },
    },
  },

  tools: {
    get_repo: {
      description: "GitHub リポジトリのメタデータを取得します。",
      inputSchema: {
        type: "object",
        properties: {
          owner: { type: "string", description: "オーナーのログイン名。" },
          repo:  { type: "string", description: "リポジトリ名。" },
        },
        required: ["owner", "repo"],
      },
      handler: async ({ owner, repo }: { owner: string; repo: string }) => {
        const fetcher = await credentials.fetcher("github");
        const res = await fetcher(`https://api.github.com/repos/${owner}/${repo}`, {
          headers: { Accept: "application/vnd.github+json" },
        });
        if (!res.ok) throw new Error(`GitHub ${res.status}: ${await res.text()}`);
        const r = (await res.json()) as Record<string, unknown>;
        return {
          full_name: r.full_name,
          stars:     r.stargazers_count,
          url:       r.html_url,
        };
      },
    },
  },
};

9. クイックリファレンス

必要なものインポート / 呼び出し
HTTP リクエストawait fetch(url, init)
認証付きHTTPconst f = await credentials.fetcher(name); await f(url, init)
マウントされたディレクトリの読み書き宣言された path 上の node:fs/promises(capabilities.filesystem が必要)
Particleごとの状態kv.get / set / delete / list
OAuth 強制リフレッシュoauth.refresh(name)
HMAC 署名 / 検証signing.sign(name, data) / signing.verify(name, data, sig)
セットアップ時に選択されたメソッドを検出credentials.getConfiguredMethod(name)
生の資格情報バイトcredentials.getRaw(name)

インポートは常に @partite-ai/particle-<area> パッケージから行います — 他の名前は発明しないでください。

出典とライセンス

(ライセンス情報は原文に記載されていますが、翻訳対象外として省略します)

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

関連スキル