CommunityCodierung & Entwicklunggithub.com

partite-ai/write-particle-typescript

Erstellen, Bearbeiten oder Erweitern eines Partite-Partikels in TypeScript oder JavaScript unter Verwendung von Particlefile.ts oder Particlefile.js. Für Tool-Handler, API-Integrationen, Anmeldeinformationen und Laufzeitfähigkeiten. Für Python-Partikel verwenden Sie bitte die entsprechende Sprachfähigkeit, falls verfügbar.

Was ist write-particle-typescript?

Diese Fähigkeit beschreibt vollständig, wie ein einzelnes TypeScript- oder JavaScript-Partikel für die Partite-Laufzeitumgebung geschrieben wird. Ein Partikel ist ein eigenständiges Programm, das eine oder mehrere **Tools** (benannte Operationen mit JSON-Schema-Eingabe) in einer einzigen `Particlefile.ts`- oder `Particlefile.js`-Datei deklariert. Die Fähigkeit behandelt alles von der Dateistruktur und dem Standardexport (der den Namen, die Beschreibung, die Version, die Fähigkeiten, die Anmeldeinformationen und die Tool-Definitionen des Partikels enthält) bis hin zu Ausgabe-Handler-APIs (HTTP, Dateisystem, Schlüssel/Wert-Speicher, OAuth-Refresh und Signierung). Sie erklärt, wie npm-Abhängigkeiten deklariert werden, wie Anmeldeinformationen (OAuth2, API-Schlüssel, Basic, Signier-Schlüssel, Raw) konfiguriert werden und wie die kritischen Regeln für die Laufzeitkompatibilität eingehalten werden (z. B. keine Host-Aufrufe auf Modulebene, alle abgerufenen Hosts deklarieren). Der Workflow umfasst das Erstellen, Pingen, Ausführen und Verknüpfen von Partikeln, um sie als eigenständige ausführbare Dateien für Claude Code oder die Shell-Nutzung verfügbar zu machen. Sie richtet sich an Entwickler, die Partikeln zum Einbinden von Tool-Handlern, API-Integrationen oder erweiterten Laufzeitfunktionen unter Verwendung von TypeScript erstellen.

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

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Schreiben eines TypeScript/JavaScript-Partikels

Ein Partikel ist ein einzeiliges Programm, das ein oder mehrere Tools (benannte Operationen mit einer JSON-Schema-Eingabe) für eine Partikel-Laufzeitumgebung bereitstellt. Diese Fähigkeit enthält alles, was Sie benötigen, um ein Partikel zu erstellen, das gebaut und ausgeführt wird.

1. Dateiaufbau

Eine Quelldatei im Projektstammverzeichnis, die genau wie folgt benannt ist:

  • Particlefile.ts — bevorzugt (vollständige Typprüfung),
  • Particlefile.js — funktioniert, aber ohne Typen.

Es ist nichts weiter erforderlich. npm-Abhängigkeiten werden inline mit standardmäßigen import-Anweisungen deklariert; die Build-Umgebung löst sie auf. Keine package.json, kein tsconfig.json, kein node_modules zu verwalten.

2. Der Standardexport — die gesamte API

Der Standardexport der Datei ist die Partikeldeklaration. Form:

export default {
  name: "kebab-case-name",        // erforderlich, kebab-case
  description: "...",              // erforderlich, eine Zeile
  version: "0.1.0",                // erforderlich, semver

  capabilities: {
    // Ausgehende HTTP-Whitelist. Hier nicht aufgeführte Hosts werden
    // an der Netzwerkgrenze abgelehnt. Lassen Sie `http` vollständig weg, wenn das Partikel
    // keine ausgehenden Anfragen stellt.
    http: { allowedHosts: ["api.example.com"] },

    // Host-Verzeichniszugriff, standardmäßig deaktiviert. Siehe "Dateisystem" weiter unten.
    // filesystem: { mounts: { ... }, temp: { ... } },
  },

  // Optional. Wird pro Name deklariert; der Benutzer wählt bei der Einrichtung eine Methode aus.
  credentials: {
    example: {
      hosts: ["api.example.com"],   // Substitution an diese binden
      required: true,
      methods: {
        // siehe "Anmeldeinformationsmethoden" weiter unten
      },
    },
  },

  // Erforderlich. Zuordnung von Tool-Name → { description, inputSchema, handler }.
  tools: {
    do_thing: {
      description: "Was dieses Tool tut — eine Zeile, geschrieben für ein LLM.",
      inputSchema: {
        type: "object",
        properties: {
          name: { type: "string", description: "..." },
        },
        required: ["name"],
      },
      handler: async ({ name }: { name: string }) => {
        return { result: `hello ${name}` };
      },
    },
  },

  // Optional. Gibt Lebendigkeit/Bereitschaft zurück. Wird von `particle ping` aufgerufen.
  ping: async () => ({ status: "ok" as const, message: "alive" }),
};

Tools

  • description: geschrieben für einen LLM-Aufrufer — kurz, handlungsorientiert.
  • inputSchema: JSON Schema Draft 2020-12, objektbasiert. Der Host validiert Argumente dagegen, bevor Ihr Handler aufgerufen wird, sodass Sie innerhalb des Handlers der Eingabeform vertrauen können. Verwenden Sie required, um obligatorische Felder zu kennzeichnen; geben Sie default für optionale an.
  • handler: synchron oder asynchron. Geben Sie einen beliebigen JSON-serialisierbaren Wert zurück. Werfen Sie einen Fehler, um ihn dem Aufrufer anzuzeigen — die geworfene Nachricht wird angezeigt.

Ping

Optional, aber empfohlen. Rückgabeform:

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

3. Laufzeit-APIs, die Handlern zur Verfügung stehen

Dies sind die einzigen Nicht-Stdlib-Dinge, die Sie importieren können. Jedes ist ein Host-Modul, das von der Laufzeitumgebung bereitgestellt wird — sie werden als reine Typ-npm-Pakete ausgeliefert, die Implementierung wird zur Ausführungszeit injiziert.

Ausgehendes HTTP

Verwenden Sie das globale fetch(). Es ist die standardmäßige Web-Fetch-API, die über den Host geleitet wird, sodass die Whitelist und die Anmeldeinformationssubstitution angewendet werden.

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

Hosts, die nicht in capabilities.http.allowedHosts enthalten sind, werden abgelehnt. Ein einfaches fetch wendet keine Anmeldeinformationen an — verwenden Sie dafür credentials.fetcher.

Dateisystem

Standardmäßig deaktiviert — ein Handler sieht nur sein eigenes Bundle. Deklarieren Sie capabilities.filesystem, um Host-Verzeichniszugriff zu erhalten, und lesen und schreiben Sie dann mit den üblichen node:fs-APIs (die Laufzeitumgebung leitet sie durch wasi:filesystem). Kein Fähigkeitsmodul zum Importieren.

capabilities: {
  filesystem: {
    // Host-Verzeichnisse, die der *Benutzer* auf reale Pfade abbildet — dauerhaft
    // mit `particle mount <particle> <name> <host-path>`, oder pro Ausführung
    // mit `--mount <name>=<host-path>`.
    mounts: {
      data: {
        description: "Wohin Berichte geschrieben werden",  // im Installationsprompt angezeigt
        path: "/mnt/data",                          // absoluter Pfad innerhalb der Sandbox
        access: "readwrite",                        // "readonly" | "readwrite"
        required: true,                             // Ausführung ablehnen, bis zugeordnet (Standard false)
      },
    },
    // Scratch-Speicher, den der Host bei jeder Ausführung frisch bereitstellt und beim
    // Beenden löscht. Immer lesend-schreibend; der Benutzer ordnet diese nie zu.
    temp: {
      work: { description: "Scratch-Speicher", 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));
  • Der Handler öffnet den deklarierten path direkt; der Mount-Name erscheint nie innerhalb der Sandbox.
  • Das Schreiben in einen readonly-Mount oder das Lesen/Schreiben eines beliebigen Pfades außerhalb eines deklarierten Mounts führt zu einem Fehler.
  • Ein required-Mount, den der Benutzer nie zuordnet, schlägt zur Laufzeit fehl, nicht beim Bau/Installation.
  • maxSize ist eine Byteanzahl mit einem optionalen KB/MB/GB-Suffix ("10MB"); Schreibvorgänge, die darüber hinausgehen, schlagen fehl.

Anmeldeinformationen

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

// fetcher(name) gibt eine fetch-artige Funktion zurück, die automatisch die
// Anmeldeinformationen an der host-konfigurierten Stelle (Header, Query-
// Parameter usw.) anwendet, bevor die Anfrage die Sandbox verlässt.
const fetcher = await credentials.fetcher("example");
const res = await fetcher("https://api.example.com/me");

// Ermitteln, welche Methode der Benutzer bei der Einrichtung konfiguriert hat ("oauth", "pat", ...).
const method = credentials.getConfiguredMethod("example"); // string | null

// Nur für Anmeldeinformationen, die mit type: "raw" deklariert wurden.
const value = await credentials.getRaw("some-raw-cred");

credentials.fetcher funktioniert für basic, oauth2- und apikey-Anmeldeinformationen. Für signing-key verwenden Sie signing.sign. Für raw verwenden Sie getRaw.

Schlüssel/Wert-Speicher

Pro Partikel begrenzt. Nur Zeichenfolgen — base64-kodieren Sie Binärdaten bei Bedarf. Deklarieren Sie es unter capabilities.kv — ohne die Deklaration schlägt jeder Aufruf mit einem not-declared-Fehler fehl (keine Benutzerfreigabe erforderlich, nur die Deklaration):

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-Aktualisierung

Nur, wenn Sie eine oauth2-Anmeldeinformation haben. Der Fetcher aktualisiert abgelaufene Token automatisch; rufen Sie dies nur auf, wenn ein vorgeschalteter Dienst ein Token ablehnt, von dem Sie dachten, es sei noch gültig.

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

Signierung

Nur für signing-key-Anmeldeinformationen. Das Schlüsselmaterial gelangt nie in 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. Anmeldeinformationsmethoden

Jede Anmeldeinformation hat eine methods-Zuordnung. Der Benutzer wählt bei der Einrichtung eine aus. Ein Partikel bietet häufig mehrere Methoden für denselben Anbieter an.

oauth2

oauth: {
  type: "oauth2",
  description: "Über OAuth anmelden",
  flows: ["authorization-code", "device-code"],   // eine dieser drei auswählen
  scopes: ["read", "write"],
  authorizationUrl: "https://provider/oauth/authorize",
  tokenUrl:         "https://provider/oauth/token",
  deviceAuthUrl:    "https://provider/oauth/device/code",  // erforderlich für "device-code"
},

Gültige Flow-Strings: "authorization-code", "authorization-code-pkce", "device-code".

apikey

pat: {
  type: "apikey",
  description: "Ein persönliches Zugriffstoken verwenden",
  location: { kind: "auth-scheme", scheme: "Bearer" },
},

location.kind ist einer von:

  • "header" + name: <name>: <key>
  • "auth-scheme" + scheme: Authorization: <scheme> <key> (z. B. Bearer)
  • "query-param" + name: angehängt als ?<name>=<key>

Lassen Sie location vollständig weg, um den Importeur aufzufordern, den Benutzer danach zu fragen.

basic

basic_auth: { type: "basic", description: "Benutzername + Passwort" },

Ersetzt als Authorization: Basic <base64>.

signing-key

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

Gültig: "hmac-sha256", "hmac-sha512". Verwendung über signing.sign.

raw

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

Verwendung über credentials.getRaw(name). Wählen Sie dies nur, wenn Sie wirklich die Rohdatenbytes benötigen — die anderen Typen sind sicherer, da der Wert nie in Ihren Code gelangt.

5. npm-Abhängigkeiten

Importieren Sie jedes reine ESM-npm-Paket direkt:

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

Die Build-Umgebung führt einen Import-Scan durch und löst sie automatisch auf — keine package.json erforderlich. Einschränkungen:

  • Das Paket muss reines JS/TS sein (keine nativen Bindungen, keine Node-Only-APIs).
  • Browser-kompatible Builds funktionieren am besten.
  • Vermeiden Sie fs, child_process usw. — sie existieren in der Laufzeitumgebung nicht.

6. Kritische Regeln

  1. Keine Host-Aufrufe auf Modulebene. Das Aufrufen von fetch, kv.get, credentials.fetcher usw. auf der obersten Ebene Ihrer Datei schlägt zur Build-Zeit fehl (die Introspect-Phase führt Ihr Modul unter Trap-Stores aus). Führen Sie alle Host-Aufrufe innerhalb eines handler oder ping durch.

    // ✗ Wird den Build fehlschlagen lassen
    const fetcher = await credentials.fetcher("github");
    export default { tools: { ... } };
    
    // ✓ Innerhalb des Handlers
    export default {
      tools: {
        go: { handler: async (args) => {
          const fetcher = await credentials.fetcher("github");
          // ...
        }},
      },
    };
    
  2. Deklarieren Sie jeden Host, den Sie abrufen. Eine Anfrage an einen Host, der nicht in capabilities.http.allowedHosts enthalten ist, wird abgelehnt. Der Fehler wird als geworfene Ausnahme im Handler sichtbar.

  3. credentials.<name>.hosts muss eine Teilmenge der HTTP-Whitelist sein. Der Build lehnt Anmeldeinformationen ab, die an Hosts gebunden sind, die das Partikel nicht erreichen kann.

  4. name muss kebab-case sein (Kleinschreibung, Bindestriche). Der Registrierungsschlüssel ist (name, version).

  5. version muss gültiges semver sein. 0.1.0, 1.2.3-rc.1 usw.

  6. Tool-Handler müssen JSON-serialisierbare Werte zurückgeben. Die Rückgabe von undefined ist in Ordnung (wird zu null). Die Rückgabe einer Klasseninstanz, eines Date, BigInt oder Map schlägt bei der Serialisierung fehl.

7. Workflow

Nach dem Schreiben von Particlefile.ts:

particle build                          # Typprüfung, Bündelung, Introspection, Registrierung
particle ping <name>                    # Ping überprüfen (falls definiert)
particle run  <name>                    # Tools auflisten
particle run  <name> <tool> --help      # Tool-Flags anzeigen
particle run  <name> <tool> --foo=bar   # Ausführen

particle build führt den Benutzer beim ersten Mal interaktiv durch die Einrichtung der Anmeldeinformationen. Wenn der Build fehlschlägt, gibt der Fehler die Phase (import-scan, typecheck, bundle, manifest-extract) und das spezifische Problem an.

Wenn das Partikel Dateisystem-Mounts deklariert, ordnen Sie sie vor der Ausführung zu (oder übergeben Sie --mount name=path an particle run für einen einmaligen Durchlauf):

particle mount <name> <mount-name> <host-path>   # dauerhafte Zuordnung speichern
particle mount <name>                            # Mounts + aktuelle Zuordnungen auflisten

Um das Partikel als eigenständige ausführbare Datei bereitzustellen (praktisch für Claude Code-Fähigkeiten und Shell-Nutzung), schreibt particle link <name> ./<name> einen Launcher, der seine Argumente an particle run <name> weiterleitet.

8. Ausgearbeitetes Beispiel

Ein minimales Partikel, das ein GitHub-Repo-Lookup-Tool bereitstellt:

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

export default {
  name: "github-lookup",
  description: "GitHub-Repository-Metadaten nachschlagen.",
  version: "0.1.0",

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

  credentials: {
    github: {
      hosts: ["api.github.com"],
      required: true,
      methods: {
        pat: {
          type: "apikey",
          description: "GitHub persönliches Zugriffstoken",
          location: { kind: "auth-scheme", scheme: "Bearer" },
        },
      },
    },
  },

  tools: {
    get_repo: {
      description: "Metadaten für ein GitHub-Repository abrufen.",
      inputSchema: {
        type: "object",
        properties: {
          owner: { type: "string", description: "Besitzer-Login." },
          repo:  { type: "string", description: "Repository-Name." },
        },
        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. Kurzreferenz

BedarfImport / Aufruf
HTTP-Anfrageawait fetch(url, init)
Authentifiziertes HTTPconst f = await credentials.fetcher(name); await f(url, init)
Lesen/Schreiben eines gemounteten Verzeichnissesnode:fs/promises auf dem deklarierten path (benötigt capabilities.filesystem)
Pro-Partikel-Zustandkv.get / set / delete / list
OAuth-Aktualisierung erzwingenoauth.refresh(name)
HMAC-Signierung/-Verifizierungsigning.sign(name, data) / signing.verify(name, data, sig)
Bei der Einrichtung ausgewählte Methode ermittelncredentials.getConfiguredMethod(name)
Rohe Anmeldeinformationsbytescredentials.getRaw(name)

Imports kommen immer aus den @partite-ai/particle-<area>-Paketen — erfinden Sie keine anderen Namen.

Quelle und Lizenz

Adaptiert von ...

[truncated for translation]

Individual skills in this repo

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

Verwandte Skills