Community编程与开发github.com

partite-ai/write-particle-typescript

使用 TypeScript 或 JavaScript 创建、编辑或扩展 Partite Particle,支持 Particlefile.ts 或 Particlefile.js。适用于工具处理器、API 集成、凭据和运行时能力。如需 Python Particle,请使用相应的语言技能(如有)。

write-particle-typescript 是什么?

该技能为开发者提供了编写 Partite Particle 的完整指南,Particle 是一个单文件程序,通过工具(具有 JSON Schema 输入的命令操作)与 Particle 运行时交互。它详细说明了如何创建 `Particlefile.ts`(首选,支持完整类型检查)或 `Particlefile.js`,包括默认导出的结构、工具定义、运行时 API(如 HTTP 请求、文件系统访问、凭据管理、键/值存储)以及 OAuth 刷新和签名等高级功能。该技能支持多种凭据方法(OAuth2、API 密钥、基本认证、签名密钥、原始凭据),并强调了关键规则,例如避免模块级主机调用。它适用于 Partite 平台,并与 Claude Code、Cursor 或 Codex 等工具兼容,提供清晰的示例和快速参考表,以便高效开发。

兼容平台✓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 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

编写 TypeScript/JavaScript 粒子

粒子是一个单文件程序,向粒子运行时暴露一个或多个工具(具有 JSON Schema 输入的命名操作)。此技能告诉您生成一个可构建和运行的程序所需的一切。

1. 文件布局

项目根目录下的一个源文件,名称必须为:

  • Particlefile.ts — 首选(完整类型检查),
  • Particlefile.js — 也可工作,但没有类型。

不需要其他任何内容。npm 依赖项通过标准的 import 语句内联声明;构建会解析它们。无需管理 package.json、tsconfig.json 或 node_modules。

2. 默认导出 — 整个 API

文件的默认导出是粒子声明。形状如下:

export default {
  name: "kebab-case-name",        // 必需,kebab 命名法
  description: "...",              // 必需,一行
  version: "0.1.0",                // 必需,语义化版本

  capabilities: {
    // 出站 HTTP 允许列表。未在此列出的主机在网络边界被拒绝。
    // 如果粒子没有出站请求,则完全省略 `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 编写。",
      inputSchema: {
        type: "object",
        properties: {
          name: { type: "string", description: "..." },
        },
        required: ["name"],
      },
      handler: async ({ name }: { name: string }) => {
        return { result: `hello ${name}` };
      },
    },
  },

  // 可选。返回存活/就绪状态。由 `particle ping` 调用。
  ping: async () => ({ status: "ok" as const, message: "alive" }),
};

工具

  • description:为 LLM 调用者编写 — 简短、面向操作。
  • inputSchema:JSON Schema Draft 2020-12,对象根。主机在调用你的处理器之前会验证参数,因此你可以在处理器内部信任输入形状。使用 required 标记必填字段;为可选字段提供 default。
  • handler:同步或异步。返回任何 JSON 可序列化的值。抛出错误以向调用者展示错误 — 他们看到的是抛出的消息。

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。

键/值存储

按粒子作用域。仅字符串 — 如果需要二进制数据,请进行 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 凭据时。fetcher 会自动刷新过期的令牌;仅当上游拒绝了你认为仍然有效的令牌时,才调用此函数。

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 映射。用户在设置时选择一个方法。一个粒子通常为同一个提供者提供多种方法。

oauth2

oauth: {
  type: "oauth2",
  description: "通过 OAuth 登录",
  flows: ["authorization-code", "device-code"],   // 选择这三个中的任意一个
  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 依赖项

直接导入任何纯 ESM npm 包:

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

构建会执行导入扫描并自动解析它们 — 无需 package.json。限制:

  • 包必须是纯 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 允许列表的子集。 构建会拒绝绑定到粒子无法访问的主机的凭据。

  4. name 必须使用 kebab 命名法(小写字母、连字符)。注册表键是 (name, version)。

  5. version 必须是有效的语义化版本。 例如 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 run 上传递 --mount name=path 以进行一次性运行):

particle mount <name> <mount-name> <host-path>   # 保存持久化映射
particle mount <name>                            # 列出挂载和当前映射

要将粒子暴露为独立的可执行文件(方便用于 Claude Code 技能和 shell 使用),particle link <name> ./<name> 会写入一个启动器,将其参数转发给 particle run <name>。

8. 工作示例

一个暴露 GitHub 仓库查找工具的最小粒子:

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)
读取/写入挂载目录node:fs/promises 作用于声明的 path(需要 capabilities.filesystem)
按粒子状态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.

相关技能