partite-ai/write-particle-typescript

Crie, edite ou estenda uma Partícula Partite em TypeScript ou JavaScript usando Particlefile.ts ou Particlefile.js. Use para manipuladores de ferramentas, integrações de API, credenciais e capacidades de tempo de execução. Para Partículas Python, use a habilidade de linguagem correspondente quando disponível.

O que é write-particle-typescript?

Esta habilidade fornece um guia completo para desenvolvedores criarem uma 'partícula' (particle) no ecossistema Partite, usando TypeScript ou JavaScript. Uma partícula é um programa de arquivo único que expõe ferramentas (operações nomeadas com entrada JSON-Schema) para um runtime de partículas. O documento cobre a estrutura de arquivos (Particlefile.ts ou Particlefile.js), a declaração da partícula via export default (incluindo nome, descrição, versão, capacidades, credenciais e ferramentas), APIs de runtime disponíveis (como fetch para HTTP, acesso a sistema de arquivos, armazenamento chave-valor, credenciais e renovação OAuth), métodos de credenciais (OAuth2, API Key, Basic, chave de assinatura e raw), dependências npm, regras críticas (como não fazer chamadas de host no escopo do módulo) e o fluxo de trabalho com comandos CLI como particle build, particle run e particle link. É útil para desenvolvedores que desejam criar integrações de API, manipuladores de ferramentas para agentes de IA (como Claude Code) ou extensões modulares para o runtime Partite, suportando TypeScript (com verificação de tipos) e JavaScript (sem tipos).

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

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Escrever uma partícula TypeScript/JavaScript

Uma partícula é um programa de arquivo único que expõe uma ou mais ferramentas (operações nomeadas com entrada JSON-Schema) para um runtime de partículas. Esta habilidade fornece tudo que você precisa para produzir uma que compile e execute.

1. Estrutura de arquivos

Um arquivo fonte na raiz do projeto, nomeado exatamente:

  • Particlefile.ts — preferido (com verificação de tipos completa),
  • Particlefile.js — funciona, mas sem tipos.

Nada mais é necessário. Dependências npm são declaradas inline com instruções import padrão; a compilação as resolve. Sem package.json, sem tsconfig.json, sem node_modules para gerenciar.

2. A exportação padrão — a API completa

A exportação padrão do arquivo é a declaração da partícula. Forma:

export default {
  name: "nome-em-kebab-case",        // obrigatório, kebab-case
  description: "...",              // obrigatório, uma linha
  version: "0.1.0",                // obrigatório, semver

  capabilities: {
    // Lista de permissões HTTP de saída. Hosts não listados aqui são negados
    // no limite da rede. Omita `http` inteiramente se a partícula não fizer
    // requisições de saída.
    http: { allowedHosts: ["api.exemplo.com"] },

    // Acesso a diretórios do host, desligado por padrão. Veja "Sistema de arquivos" abaixo.
    // filesystem: { mounts: { ... }, temp: { ... } },
  },

  // Opcional. Declarado por nome; o usuário escolhe um método na configuração.
  credentials: {
    exemplo: {
      hosts: ["api.exemplo.com"],   // vincula a substituição a estes
      required: true,
      methods: {
        // veja "Métodos de credenciais" abaixo
      },
    },
  },

  // Obrigatório. Mapa de nome-da-ferramenta → { description, inputSchema, handler }.
  tools: {
    fazer_coisa: {
      description: "O que esta ferramenta faz — uma linha, escrita para um LLM.",
      inputSchema: {
        type: "object",
        properties: {
          nome: { type: "string", description: "..." },
        },
        required: ["nome"],
      },
      handler: async ({ nome }: { nome: string }) => {
        return { resultado: `olá ${nome}` };
      },
    },
  },

  // Opcional. Retorna vivacidade/prontidão. Chamado por `particle ping`.
  ping: async () => ({ status: "ok" as const, message: "vivo" }),
};

Ferramentas

  • description: escrita para um chamador LLM — curta, orientada à ação.
  • inputSchema: JSON Schema Draft 2020-12, com raiz em objeto. O host valida argumentos contra ele antes de chamar seu handler, então você pode confiar na forma da entrada dentro do handler. Use required para marcar campos obrigatórios; forneça default para opcionais.
  • handler: síncrono ou assíncrono. Retorne qualquer valor serializável em JSON. Lance um erro para sinalizar um erro ao chamador — a mensagem lançada é o que eles veem.

Ping

Opcional, mas recomendado. Forma de retorno:

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

3. APIs de runtime disponíveis para handlers

Estas são as únicas coisas não padrão da biblioteca que você pode importar. Cada uma é um módulo do host fornecido pelo runtime — elas são fornecidas como pacotes npm apenas de tipo, a implementação é injetada na execução.

HTTP de saída

Use o fetch() global. É a API Fetch Web padrão, roteada através do host para que a lista de permissões e a substituição de credenciais se apliquem.

const res = await fetch("https://api.exemplo.com/coisas", {
  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 não em capabilities.http.allowedHosts são negados. fetch simples não aplica credenciais — use credentials.fetcher para isso.

Sistema de arquivos

Desligado por padrão — um handler vê apenas seu próprio pacote. Declare capabilities.filesystem para obter acesso a diretórios do host, então leia e escreva com APIs node:fs comuns (o runtime as roteia através de wasi:filesystem). Nenhum módulo de capacidade para importar.

capabilities: {
  filesystem: {
    // Diretórios do host que o *usuário* mapeia para caminhos reais — persistentemente
    // com `particle mount <particle> <nome> <caminho-host>`, ou por execução
    // com `--mount <nome>=<caminho-host>`.
    mounts: {
      dados: {
        description: "Onde os relatórios são escritos",  // mostrado no prompt de instalação
        path: "/mnt/dados",                          // caminho absoluto dentro do sandbox
        access: "readwrite",                        // "readonly" | "readwrite"
        required: true,                             // recusa executar até mapeado (padrão false)
      },
    },
    // Espaço temporário que o host provisiona fresco a cada execução e limpa na
    // saída. Sempre leitura-escrita; o usuário nunca mapeia estes.
    temp: {
      trabalho: { description: "Espaço temporário", path: "/tmp/trabalho", maxSize: "10MB" },
    },
  },
},
import { readFile, writeFile } from "node:fs/promises";

const input = await readFile("/mnt/dados/entrada.json", "utf8");
await writeFile("/mnt/dados/saida.json", JSON.stringify(resultado));
  • O handler abre o path declarado diretamente; o nome do mount nunca aparece dentro do sandbox.
  • Escrever em um mount readonly, ou ler/escrever qualquer caminho fora de um mount declarado, lança um erro.
  • Um mount required que o usuário nunca mapeia falha em tempo de execução, não na compilação/instalação.
  • maxSize é uma contagem de bytes com um sufixo opcional KB/MB/GB ("10MB"); escritas além disso falham.

Credenciais

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

// fetcher(nome) retorna uma função em forma de fetch que aplica automaticamente
// a credencial no local configurado pelo host (cabeçalho, parâmetro de consulta,
// etc.) antes da requisição sair do sandbox.
const fetcher = await credentials.fetcher("exemplo");
const res = await fetcher("https://api.exemplo.com/me");

// Descubra qual método o usuário configurou na instalação ("oauth", "pat", ...).
const metodo = credentials.getConfiguredMethod("exemplo"); // string | null

// Apenas para credenciais declaradas com type: "raw".
const valor = await credentials.getRaw("alguma-cred-raw");

credentials.fetcher funciona para credenciais basic, oauth2 e apikey. Para signing-key, use signing.sign. Para raw, use getRaw.

Armazenamento chave/valor

Escopo por partícula. Apenas strings — codifique binário em base64 se necessário. Declare-o sob capabilities.kv — sem a declaração, toda chamada lança um erro de não-declarado (nenhuma aprovação do usuário é necessária, apenas a declaração):

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 chaves = await kv.list("prefixo:");    // string[]

Atualização OAuth

Apenas se você tiver uma credencial oauth2. O fetcher atualiza automaticamente tokens expirados; chame isso apenas quando um upstream rejeitar um token que você pensava ainda ser válido.

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

Assinatura

Apenas para credenciais signing-key. O material da chave nunca entra no JS.

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

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

4. Métodos de credenciais

Cada credencial tem um mapa methods. O usuário escolhe um na configuração. Uma partícula comumente oferece múltiplos métodos para o mesmo provedor.

oauth2

oauth: {
  type: "oauth2",
  description: "Entrar via OAuth",
  flows: ["authorization-code", "device-code"],   // escolha qualquer um destes três
  scopes: ["read", "write"],
  authorizationUrl: "https://provedor/oauth/autorizar",
  tokenUrl:         "https://provedor/oauth/token",
  deviceAuthUrl:    "https://provedor/oauth/dispositivo/codigo",  // obrigatório para "device-code"
},

Strings de fluxo válidas: "authorization-code", "authorization-code-pkce", "device-code".

apikey

pat: {
  type: "apikey",
  description: "Usar um token de acesso pessoal",
  location: { kind: "auth-scheme", scheme: "Bearer" },
},

location.kind é um de:

  • "header" + name: <name>: <key>
  • "auth-scheme" + scheme: Authorization: <scheme> <key> (ex.: Bearer)
  • "query-param" + name: anexado como ?<name>=<key>

Omita location inteiramente para deixar o importador solicitar ao usuário.

basic

basic_auth: { type: "basic", description: "Nome de usuário + senha" },

Substitui como Authorization: Basic <base64>.

signing-key

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

Válidos: "hmac-sha256", "hmac-sha512". Use via signing.sign.

raw

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

Use via credentials.getRaw(nome). Escolha isso apenas quando você genuinamente precisar dos bytes brutos — os outros tipos são mais seguros porque o valor nunca entra no seu código.

5. Dependências npm

Importe qualquer pacote npm puro-ESM diretamente:

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

A compilação faz uma varredura de importações e as resolve automaticamente — sem package.json necessário. Restrições:

  • O pacote deve ser puro JS/TS (sem ligações nativas, sem APIs apenas Node).
  • Compilações compatíveis com navegador funcionam melhor.
  • Evite fs, child_process, etc. — eles não existem no runtime.

6. Regras críticas

  1. Nenhuma chamada de host no escopo do módulo. Chamar fetch, kv.get, credentials.fetcher, etc. no nível superior do seu arquivo falhará no tempo de compilação (a fase de introspecção executa seu módulo sob armadilhas de armazenamento). Faça toda chamada de host dentro de um handler ou ping.

    // ✗ Falhará a compilação
    const fetcher = await credentials.fetcher("github");
    export default { tools: { ... } };
    
    // ✓ Dentro do handler
    export default {
      tools: {
        ir: { handler: async (args) => {
          const fetcher = await credentials.fetcher("github");
          // ...
        }},
      },
    };
    
  2. Declare todo host que você acessar. Uma requisição a um host ausente de capabilities.http.allowedHosts é negada. O erro aparece como uma exceção lançada no handler.

  3. credentials.<nome>.hosts deve ser um subconjunto da lista de permissões HTTP. A compilação rejeita credenciais vinculadas a hosts que a partícula não pode alcançar.

  4. name deve estar em kebab-case (minúsculas, hífens). A chave do registro é (name, version).

  5. version deve ser semver válido. 0.1.0, 1.2.3-rc.1, etc.

  6. Handlers de ferramenta devem retornar valores serializáveis em JSON. Retornar undefined é aceitável (torna-se null). Retornar uma instância de classe, Date, BigInt ou Map falhará ao serializar.

7. Fluxo de trabalho

Após escrever Particlefile.ts:

particle build                          # verificação de tipos, empacotamento, introspecção, registro
particle ping <nome>                    # verifique ping (se definido)
particle run  <nome>                    # liste ferramentas
particle run  <nome> <ferramenta> --help      # mostre flags da ferramenta
particle run  <nome> <ferramenta> --foo=bar   # invoque-a

particle build guia o usuário pela configuração de credenciais interativamente na primeira vez. Se a compilação falhar, o erro nomeia a fase (import-scan, typecheck, bundle, manifest-extract) e o problema específico.

Se a partícula declarar montagens de sistema de arquivos, mapeie-as antes de executar (ou passe --mount nome=caminho em particle run para uma execução única):

particle mount <nome> <nome-da-montagem> <caminho-host>   # salve um mapeamento persistente
particle mount <nome>                            # liste montagens + mapeamentos atuais

Para expor a partícula como um executável independente (útil para habilidades Claude Code e uso em shell), particle link <nome> ./<nome> escreve um lançador que encaminha seus argumentos para particle run <nome>.

8. Exemplo prático

Uma partícula mínima expondo uma ferramenta de consulta de repositório GitHub:

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

export default {
  name: "github-lookup",
  description: "Consultar metadados de repositório GitHub.",
  version: "0.1.0",

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

  credentials: {
    github: {
      hosts: ["api.github.com"],
      required: true,
      methods: {
        pat: {
          type: "apikey",
          description: "Token de acesso pessoal GitHub",
          location: { kind: "auth-scheme", scheme: "Bearer" },
        },
      },
    },
  },

  tools: {
    get_repo: {
      description: "Buscar metadados para um repositório GitHub.",
      inputSchema: {
        type: "object",
        properties: {
          owner: { type: "string", description: "Login do proprietário." },
          repo:  { type: "string", description: "Nome do repositório." },
        },
        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. Referência rápida

NecessidadeImport / chamada
Requisição HTTPawait fetch(url, init)
HTTP autenticadoconst f = await credentials.fetcher(nome); await f(url, init)
Ler/escrever um dir montadonode:fs/promises no path declarado (precisa de capabilities.filesystem)
Estado por partículakv.get / set / delete / list
Forçar atualização OAuthoauth.refresh(nome)
Assinar / verificar HMACsigning.sign(nome, dados) / signing.verify(nome, dados, sig)
Descobrir método escolhido na instalaçãocredentials.getConfiguredMethod(nome)
Bytes de credencial rawcredentials.getRaw(nome)

Importações sempre vêm dos pacotes @partite-ai/particle-<area> — não invente outros nomes.

Fonte e licença

Adaptado de ...

Individual skills in this repo

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

Habilidades Relacionadas