partite-ai/write-particle-typescript

Créer, modifier ou étendre une particule Partite en TypeScript ou JavaScript à l'aide de Particlefile.ts ou Particlefile.js. Utilisé pour les gestionnaires d'outils, les intégrations d'API, les identifiants et les capacités d'exécution. Pour les particules Python, utilisez la compétence linguistique correspondante lorsqu'elle est disponible.

Qu'est-ce que write-particle-typescript ?

Cette compétence guide les développeurs dans la création de particules Partite en TypeScript ou JavaScript : des programmes mono-fichier exposant des outils, des identifiants et des capacités d'exécution. Le fichier `Particlefile.ts` (ou `.js`) contient une exportation par défaut avec un schéma structuré incluant le nom, la description, la version, les capacités (HTTP, système de fichiers, stockage clé-valeur), les identifiants (OAuth, clé API, basique, clé de signature, brut) et une carte d'outils avec des schémas JSON et des gestionnaires asynchrones. Les dépendances npm sont résolues automatiquement sans nécessiter de `package.json`. La compétence prend en charge les plates-formes d'exécution Partite, telles que Claude Code ou d'autres environnements agentiques. Elle insiste sur des règles critiques : ne pas appeler les API hôtes au niveau du module, déclarer tous les hôtes autorisés, respecter les formats kebab-case et semver. Un exemple concret montre une particule de recherche de dépôt GitHub. Enfin, elle fournit un guide de référence rapide pour les appels courants (fetch, identifiants, système de fichiers, stockage clé-valeur).

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

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Écrire une particule TypeScript/JavaScript

Une particule est un programme mono-fichier qui expose un ou plusieurs outils (opérations nommées avec une entrée JSON-Schema) à un environnement d'exécution Partite. Cette compétence vous dit tout ce que vous devez savoir pour en produire une qui se compile et s'exécute.

1. Structure du fichier

Un fichier source à la racine du projet, nommé exactement :

  • Particlefile.ts — préféré (vérification de type complète),
  • Particlefile.js — fonctionne, mais sans types.

Rien d'autre n'est requis. Les dépendances npm sont déclarées en ligne avec les instructions import standard ; la construction les résout. Pas de package.json, pas de tsconfig.json, pas de node_modules à gérer.

2. L'exportation par défaut — toute l'API

L'exportation par défaut du fichier est la déclaration de la particule. Forme :

export default {
  name: "kebab-case-name",        // obligatoire, kebab-case
  description: "...",              // obligatoire, une ligne
  version: "0.1.0",                // obligatoire, semver

  capabilities: {
    // Liste blanche HTTP sortante. Les hôtes non listés ici sont refusés
    // à la limite du réseau. Omettez `http` entièrement si la particule
    // n'effectue aucune requête sortante.
    http: { allowedHosts: ["api.example.com"] },

    // Accès au répertoire hôte, désactivé par défaut. Voir "Système de fichiers" ci-dessous.
    // filesystem: { mounts: { ... }, temp: { ... } },
  },

  // Optionnel. Déclaré par nom ; l'utilisateur choisit une méthode lors de la configuration.
  credentials: {
    example: {
      hosts: ["api.example.com"],   // lier la substitution à ces hôtes
      required: true,
      methods: {
        // voir "Méthodes d'identifiants" ci-dessous
      },
    },
  },

  // Obligatoire. Mappage nom-outil → { description, inputSchema, handler }.
  tools: {
    do_thing: {
      description: "Ce que fait cet outil — une ligne, écrite pour un LLM.",
      inputSchema: {
        type: "object",
        properties: {
          name: { type: "string", description: "..." },
        },
        required: ["name"],
      },
      handler: async ({ name }: { name: string }) => {
        return { result: `hello ${name}` };
      },
    },
  },

  // Optionnel. Renvoie la vivacité/la disponibilité. Appelé par `particle ping`.
  ping: async () => ({ status: "ok" as const, message: "alive" }),
};

Outils

  • description : écrite pour un appelant LLM — courte, orientée action.
  • inputSchema : JSON Schema Draft 2020-12, à racine objet. L'hôte valide les arguments par rapport à celui-ci avant d'appeler votre gestionnaire, vous pouvez donc faire confiance à la forme de l'entrée à l'intérieur du gestionnaire. Utilisez required pour marquer les champs obligatoires ; fournissez default pour les optionnels.
  • handler : synchrone ou asynchrone. Renvoie toute valeur sérialisable en JSON. Lancez une erreur pour la signaler à l'appelant — le message lancé est ce qu'il voit.

Ping

Optionnel mais recommandé. Forme de retour :

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

3. API d'exécution disponibles pour les gestionnaires

Ce sont les seules choses non-stdlib que vous pouvez importer. Chacune est un module hôte fourni par l'environnement d'exécution — ils sont livrés en tant que packages npm de types uniquement, l'implémentation est injectée lors de l'exécution.

HTTP sortant

Utilisez la fonction globale fetch(). Il s'agit de l'API Web Fetch standard, acheminée via l'hôte afin que la liste blanche et la substitution d'identifiants s'appliquent.

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

Les hôtes absents de capabilities.http.allowedHosts sont refusés. fetch simple n'applique pas les identifiants — utilisez credentials.fetcher pour cela.

Système de fichiers

Désactivé par défaut — un gestionnaire ne voit que son propre bundle. Déclarez capabilities.filesystem pour accéder au répertoire hôte, puis lisez et écrivez avec les API standard node:fs (l'environnement d'exécution les achemine via wasi:filesystem). Aucun module de capacité à importer.

capabilities: {
  filesystem: {
    // Répertoires hôtes que *l'utilisateur* mappe vers des chemins réels — de manière
    // persistante avec `particle mount <particle> <name> <host-path>`, ou par exécution
    // avec `--mount <name>=<host-path>`.
    mounts: {
      data: {
        description: "Où les rapports sont écrits",  // affiché dans l'invite d'installation
        path: "/mnt/data",                          // chemin absolu à l'intérieur du bac à sable
        access: "readwrite",                        // "readonly" | "readwrite"
        required: true,                             // refuse de s'exécuter tant que non mappé (false par défaut)
      },
    },
    // Espace temporaire que l'hôte provisionne à chaque exécution et efface à la
    // sortie. Toujours en lecture-écriture ; l'utilisateur ne les mappe jamais.
    temp: {
      work: { description: "Espace temporaire", 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));
  • Le gestionnaire ouvre le path déclaré directement ; le nom du mount n'apparaît jamais à l'intérieur du bac à sable.
  • Écrire sur un mount readonly, ou lire/écrire tout chemin en dehors d'un mount déclaré, lève une exception.
  • Un mount required que l'utilisateur ne mappe jamais échoue au moment de l'exécution, pas de la construction/installation.
  • maxSize est un nombre d'octets avec un suffixe KB/MB/GB optionnel ("10MB") ; les écritures au-delà échouent.

Identifiants

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

// fetcher(name) renvoie une fonction de type fetch qui applique
// automatiquement l'identifiant à l'emplacement configuré par l'hôte
// (en-tête, paramètre de requête, etc.) avant que la requête ne quitte
// le bac à sable.
const fetcher = await credentials.fetcher("example");
const res = await fetcher("https://api.example.com/me");

// Découvrez quelle méthode l'utilisateur a configurée lors de l'installation ("oauth", "pat", ...).
const method = credentials.getConfiguredMethod("example"); // string | null

// Uniquement pour les identifiants déclarés avec type: "raw".
const value = await credentials.getRaw("some-raw-cred");

credentials.fetcher fonctionne pour les identifiants basic, oauth2, et apikey. Pour signing-key, utilisez signing.sign. Pour raw, utilisez getRaw.

Stockage clé/valeur

Limitié à chaque particule. Chaînes uniquement — encodez en base64 le binaire si nécessaire. Déclarez-le sous capabilities.kv — sans la déclaration, chaque appel lève une erreur not-declared (aucune approbation utilisateur n'est nécessaire, juste la déclaration) :

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[]

Rafraîchissement OAuth

Uniquement si vous avez un identifiant oauth2. Le fetcher rafraîchit automatiquement les jetons expirés ; appelez-le uniquement lorsqu'un système distant rejette un jeton que vous pensiez encore valide.

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

Signature

Uniquement pour les identifiants signing-key. Le matériel de clé n'entre jamais dans le 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. Méthodes d'identifiants

Chaque identifiant a un dictionnaire methods. L'utilisateur en choisit une lors de l'installation. Une particule propose souvent plusieurs méthodes pour le même fournisseur.

oauth2

oauth: {
  type: "oauth2",
  description: "Se connecter via OAuth",
  flows: ["authorization-code", "device-code"],   // choisissez l'un de ces trois
  scopes: ["read", "write"],
  authorizationUrl: "https://provider/oauth/authorize",
  tokenUrl:         "https://provider/oauth/token",
  deviceAuthUrl:    "https://provider/oauth/device/code",  // obligatoire pour "device-code"
},

Chaînes de flux valides : "authorization-code", "authorization-code-pkce", "device-code".

apikey

pat: {
  type: "apikey",
  description: "Utiliser un jeton d'accès personnel",
  location: { kind: "auth-scheme", scheme: "Bearer" },
},

location.kind est l'un de :

  • "header" + name : <name>: <key>
  • "auth-scheme" + scheme : Authorization: <scheme> <key> (par exemple Bearer)
  • "query-param" + name : ajouté comme ?<name>=<key>

Omettez location entièrement pour laisser l'importateur demander l'emplacement à l'utilisateur.

basic

basic_auth: { type: "basic", description: "Nom d'utilisateur + mot de passe" },

Se substitue par Authorization: Basic <base64>.

signing-key

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

Valide : "hmac-sha256", "hmac-sha512". Utilisez via signing.sign.

raw

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

Utilisez via credentials.getRaw(name). Choisissez ceci uniquement lorsque vous avez réellement besoin des octets bruts — les autres types sont plus sûrs car la valeur n'entre jamais dans votre code.

5. Dépendances npm

Importez tout package ESM pur directement :

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

La construction effectue une analyse des importations et les résout automatiquement — aucun package.json nécessaire. Contraintes :

  • Le package doit être purement JS/TS (pas de liaisons natives, pas d'API Node uniquement).
  • Les builds compatibles navigateur fonctionnent le mieux.
  • Évitez fs, child_process, etc. — ils n'existent pas dans l'environnement d'exécution.

6. Règles critiques

  1. Aucun appel hôte au niveau du module. Appeler fetch, kv.get, credentials.fetcher, etc. au niveau supérieur de votre fichier échouera au moment de la construction (la phase d'introspection exécute votre module sous des magasins de pièges). Effectuez chaque appel hôte à l'intérieur d'un handler ou ping.

    // ✗ Échouera la construction
    const fetcher = await credentials.fetcher("github");
    export default { tools: { ... } };
    
    // ✓ À l'intérieur du gestionnaire
    export default {
      tools: {
        go: { handler: async (args) => {
          const fetcher = await credentials.fetcher("github");
          // ...
        }},
      },
    };
    
  2. Déclarez chaque hôte que vous fetch. Une requête vers un hôte absent de capabilities.http.allowedHosts est refusée. L'erreur se manifeste par une exception levée dans le gestionnaire.

  3. credentials.<name>.hosts doit être un sous-ensemble de la liste blanche HTTP. La construction rejette les identifiants liés à des hôtes que la particule ne peut pas atteindre.

  4. name doit être en kebab-case (minuscules, traits d'union). La clé du registre est (name, version).

  5. version doit être un semver valide. 0.1.0, 1.2.3-rc.1, etc.

  6. Les gestionnaires d'outils doivent renvoyer des valeurs sérialisables en JSON. Renvoyer undefined est acceptable (devient null). Renvoyer une instance de classe, Date, BigInt ou Map échouera à la sérialisation.

7. Flux de travail

Après avoir écrit Particlefile.ts :

particle build                          # vérifier les types, empaqueter, introspecter, enregistrer
particle ping <name>                    # vérifier le ping (si défini)
particle run  <name>                    # lister les outils
particle run  <name> <tool> --help      # afficher les drapeaux de l'outil
particle run  <name> <tool> --foo=bar   # l'invoquer

particle build guide l'utilisateur à travers la configuration des identifiants de manière interactive la première fois. Si la construction échoue, l'erreur nomme la phase (import-scan, typecheck, bundle, manifest-extract) et le problème spécifique.

Si la particule déclare des montages de système de fichiers, mappez-les avant l'exécution (ou passez --mount name=path sur particle run pour un usage unique) :

particle mount <name> <mount-name> <host-path>   # enregistrer un mappage persistant
particle mount <name>                            # lister les montages + les mappages actuels

Pour exposer la particule en tant qu'exécutable autonome (pratique pour les compétences Claude Code et l'utilisation en shell), particle link <name> ./<name> écrit un lanceur qui transmet ses arguments à particle run <name>.

8. Exemple concret

Une particule minimale exposant un outil de recherche de dépôt GitHub :

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

export default {
  name: "github-lookup",
  description: "Rechercher les métadonnées d'un dépôt GitHub.",
  version: "0.1.0",

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

  credentials: {
    github: {
      hosts: ["api.github.com"],
      required: true,
      methods: {
        pat: {
          type: "apikey",
          description: "Jeton d'accès personnel GitHub",
          location: { kind: "auth-scheme", scheme: "Bearer" },
        },
      },
    },
  },

  tools: {
    get_repo: {
      description: "Récupérer les métadonnées d'un dépôt GitHub.",
      inputSchema: {
        type: "object",
        properties: {
          owner: { type: "string", description: "Identifiant du propriétaire." },
          repo:  { type: "string", description: "Nom du dépôt." },
        },
        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. Référence rapide

BesoinImportation / appel
Requête HTTPawait fetch(url, init)
HTTP authentifiéconst f = await credentials.fetcher(name); await f(url, init)
Lire/écrire un répertoire monténode:fs/promises sur le path déclaré (nécessite capabilities.filesystem)
État par particulekv.get / set / delete / list
Forcer le rafraîchissement OAuthoauth.refresh(name)
Signature / vérification HMACsigning.sign(name, data) / signing.verify(name, data, sig)
Découvrir la méthode choisie à la configurationcredentials.getConfiguredMethod(name)
Octets bruts de l'identifiantcredentials.getRaw(name)

Les importations proviennent toujours des packages @partite-ai/particle-<area> — n'inventez pas d'autres noms.

Source et licence

Adapté de ...

[abrégé pour la traduction]

Individual skills in this repo

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

Skills associés