Developer Guide
A plugin is a single JavaScript file that gives your assistant a new tool it can call — a custom action, integration, or workflow. This guide covers exactly what a valid plugin needs, a copy-paste template, and how to load it.
The idea
One .js file that exports a tool. When the file is valid, Vyrgent registers it automatically and the assistant can call it whenever a request matches what the tool does. Plugins live in your ~/.vyrgent/plugins/ folder and hot-reload — save the file and it updates live, no restart.
Under the hood, a plugin is a CommonJS module: it sets module.exports to an object with four required fields.
The contract
Vyrgent validates each file on load. If any of the four required fields is missing, the plugin is rejected with a clear error and won't appear as a tool.
| Field | Type | What it does |
|---|---|---|
| name required | string | Unique tool ID in snake_case — letters, numbers, underscores, no spaces (e.g. currency_convert). |
| description required | string | Plain-language summary of what the tool does and when to use it. The assistant reads this to decide whether to call your plugin — so write it carefully. |
| input_schema required | object | A JSON Schema describing the arguments your tool accepts. Always type: "object" with properties and a required list. |
| handler required | async function | The code that runs. Receives { input, config, services } and returns a string (shown in chat). Throw an Error to report failure. |
| version optional | string | Your version tag, e.g. "1.0.0". Handy for tracking changes. |
| configKeys optional | string[] | Names of saved settings your plugin reads (like an API key). Their values arrive on config. |
| timeout optional | number | Max run time in milliseconds for long jobs (default is a few minutes). |
Copy this
A complete, working plugin. Save it as e.g. currency_convert.js, change the four fields to fit your tool, and load it.
// A Vyrgent plugin is a CommonJS module: set module.exports to an object.
module.exports = {
// 1) name — unique, snake_case, no spaces. This is the tool's ID.
name: "currency_convert",
// 2) description — the assistant reads THIS to decide when to call the
// tool. Say plainly what it does and when to use it.
description: "Convert an amount from one currency to another (e.g. 100 USD to EUR). Use when the user asks to convert money between currencies.",
// Optional metadata.
version: "1.0.0",
configKeys: [], // e.g. ["exchangeApiKey"] to read a saved key
timeout: 30000, // 30s (optional)
// 3) input_schema — JSON Schema for the arguments. Give every property a
// clear "description"; the assistant fills them from the user's request.
input_schema: {
type: "object",
properties: {
amount: { type: "number", description: "How much to convert." },
from: { type: "string", description: "Source currency code, e.g. USD." },
to: { type: "string", description: "Target currency code, e.g. EUR." },
},
required: ["amount", "from", "to"],
},
// 4) handler — runs when the tool is called. Return a STRING; the text
// is what the assistant sees and relays. Throw an Error to fail.
async handler({ input, config, services }) {
const { amount, from, to } = input;
// Validate inputs and return a clear message on bad data.
if (!amount || !from || !to) return "Please provide amount, from, and to.";
// Do the work. Fetch is available; so are Node built-ins.
const res = await fetch(`https://api.exchangerate.host/convert?from=${from}&to=${to}&amount=${amount}`);
if (!res.ok) throw new Error(`Exchange API error (HTTP ${res.status}).`);
const data = await res.json();
// Return a concise, human-readable string.
return `${amount} ${from} = ${data.result.toFixed(2)} ${to}`;
},
};
async, and it returns a string. Load it and the assistant can convert currencies on request.Inside the handler
Every handler is called with one object: { input, config, services }.
The arguments the assistant filled in, matching your input_schema. Read them as input.amount, input.from, etc.
The app's saved settings. Anything you listed in configKeys is here — e.g. config.exchangeApiKey. Never hard-code secrets; read them from config.
Built-in helpers so you don't reinvent common actions. Call them with await.
throw new Error("what went wrong"); the assistant will report it and stop, rather than pretend it worked.Load it
.js file. (Or copy it straight into ~/.vyrgent/plugins/.)name or description.Do it well
throw new Error(...) with the real reason. Don't silently return fake success.configKeys. Read API keys from config, never paste them into the file.execSync/spawnSync for anything slow — a blocking child process freezes the whole app. Use the async forms with await.If it won't load
Vyrgent will refuse a file and tell you exactly what's wrong. The four messages map straight to the required fields:
| Error | Fix |
|---|---|
missing name | Add a non-empty name string. |
missing description | Add a non-empty description string. |
missing input_schema | Add an input_schema object (use { type:"object", properties:{}, required:[] } if the tool takes no arguments). |
missing handler function | Add a handler that is a function. |
must export an object | Make sure you set module.exports = { … }. |
description (and use words the user would say), then try again — matching is driven by the tool's name and description.