SHIPWRIGHTS WANTED

Change how AI agents work. In TypeScript.

Write a plugin once, and anyone can install it in one click, seeing exactly what it can reach first.

A plugin is a small folder: a readme, an index.ts, and optional commands, skills and companion assets. You assign handlers to a few globals. We bundle it, work out what it does and what access it needs straight from your code, and run every call in its own sandbox. No manifest to maintain, no permission list to write, no server to run.

A WHOLE PLUGIN

A complete plugin, start to finish.

This one adds your own guidance to every agent's instructions. It needs no permissions.

index.ts

// Adds your own guidance to every agent's instructions, on every turn.
const GUIDANCE = [
  "",
  "## House style",
  "- Lead with the answer, then the detail.",
  "- Use plain language, and explain any technical term the first time you use it.",
  "- If you're not sure, say so, and say what would settle it.",
].join("\n");

globalThis.middleware.systemPrompt = async (context) => {
  // context.systemPrompt is the agent's full prompt, already composed.
  // Return the version the model should see.
  return context.systemPrompt + GUIDANCE;
};

That's the plugin. Upload it and every agent on the account that installs it follows the new guidance on its next run. Swap in your own rules: a tone of voice, a checklist, the way your team writes.

More real samples

Ask for an API key or any setting; we store it securely for you. A new web-search engine takes a dozen lines.

// Ask the user for an API key and any other setting. Secrets are stored encrypted
// and handed to your code only when it runs. Here: a new web-search engine.
globalThis.manifest = {
  egress: ["api.example-search.com"],
  settings: [
    { key: "apiKey", title: "Search API key", type: "secret", required: true },
    { key: "safeSearch", title: "Safe search", type: "bool" },
  ],
};

globalThis.web.search = async (input) => {
  const apiKey = await host.settings.getSecret("apiKey");
  const safe = host.settings.get("safeSearch") ? "on" : "off";
  const response = await host.fetch({
    url: `https://api.example-search.com/search?q=${encodeURIComponent(input.query)}&safe=${safe}`,
    headers: { Authorization: `Bearer ${apiKey}` },
  });
  const data = JSON.parse(response.body);
  return { results: data.items.map((i) => ({ title: i.title, url: i.link, snippet: i.summary })) };
};

Hook into events such as a new chat starting, and walk people through setup.

// Hook into events to get every user set up. When a chat starts and the plugin
// isn't configured yet, the agent opens with a pointer to what's missing.
globalThis.events.sessionStarted = async () => {
  if (!host.settings.get("teamName")) {
    await host.session.message.post({
      text: "Welcome aboard! Add your team name in this plugin's settings and I'll tailor every answer to it.",
    });
  }
};

Plugins get full control: a security plugin can stop a turn that contains data it shouldn't.

// Middleware gets full control over each step. This security plugin stops a
// message that carries a key or a card number before any agent sees it.
const SENSITIVE = /\b(sk-[A-Za-z0-9_-]{20,}|\d{4}[ -]?\d{4}[ -]?\d{4}[ -]?\d{4})\b/;

globalThis.middleware.userMessageInbound = async (context, tools) => {
  if (SENSITIVE.test(context.text)) {
    return tools.cancel("Held: this message looks like it contains a secret key or a card number.");
  }
  return context.text;
};

THE TOOL CHEST

The plugin SDK at a glance.

Assign a handler, get exactly that. Eight registries, one host object, 14 kinds of capability.

tools.<name>

A description, a JSON schema and a handler. Your tool sits next to the 45 built-ins.

middleware.*

17 hooks, from systemPrompt to interAgentMessage, sshCommandSending and cronJobFiring. Return a new value, or cancel.

events.*

Six lifecycle events: installed, updated, uninstalled; chat started, cleared, deleted. Delivered at least once.

compaction.summarize

Your own summariser for long conversations.

web.search / web.fetch

Your own search and page-reading backend.

modelProvider.*

A new chat model, decision model, voice, or speech-to-text provider.

channel.* / connection.*

A chat app by webhook, or over a socket we hold open for you with heartbeats, filters and reconnects handled.

commands/, skills/

Markdown slash-commands and multi-file skills.

companion-characters/, companion-models/

Characters and looks (avatars with their own motions). Data only.

host.*

fetch (to the hosts you declared), settings and secrets, crypto, the chat session, crew agents, models, workspace files, audio, the agent's own model, decisions, SSH (inside SSH hooks) and socket sends.

WHY THIS YARD

Build once. Run safely everywhere.

No permission list to write.

We read your host.* calls and derive the permissions. Users see them before they install.

Ship a whole crew, not just a function.

Create agents, arrange them under a lead, pick their models, and put them back if one goes missing.

Hold a socket without a server.

Discord-style gateways work from a sandbox that runs for seconds, because we keep the connection open.

Use the agent's own model.

Call it from your plugin without ever handling anyone's API key.

We play by the same rules.

Our official plugins use the same SDK and the same publish pipeline you do.

Fail-safe by design.

If your middleware throws or stalls, it's skipped and the agent carries on.

LAUNCHING

Publish your plugin.

Draft, test, publish, list.

  1. 1

    Write

    In the in-browser editor, or locally with tsc against the SDK types.

  2. 2

    Upload

    Upload a zip. We bundle it, read what it registers, and derive its permissions. Typos in your manifest fail loudly.

  3. 3

    Test a draft

    Run your draft on your own account before anyone else sees it.

  4. 4

    Publish a version

    Each version you publish is numbered and never changes. Pull one any time.

  5. 5

    List it

    Private until you list it in Browse.

Users on the latest version get your updates automatically. A version that asks for more access goes out as soon as each user approves it.

$ zip -r house-style.zip . -x tsconfig.json

$ curl -X POST $BASE/plugins -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"slug":"house-style","displayName":"House Style"}'

$ curl -X POST $BASE/plugins/$ID/versions/zip -H "Authorization: Bearer $TOKEN" \
    -F file=@house-style.zip -F semVer=1.0.0
  → house-style@1.0.0 published (immutable)

$ curl -X POST $BASE/plugins/$ID/listing -H "Authorization: Bearer $TOKEN"

Relative imports only (no npm packages). Bundle up to 2 MB. Tools get 30 seconds and 128 MB per call; middleware 5 seconds.

Build the next best practice.

Sign up free, open the plugin editor, and publish your first version today.