String-based command framework for Stoat

$build[ the bot framework that reads like the messages it sends ]

stoatly.js turns Stoat bot commands into short, readable $function strings.

$ npm install stoatly.js
# general
command code message someone sends in #general

Instant setup in three commands.

Every command is a name and a code string. Hit "Try it live" to load any of them into the console above.

client.command({ name: "ping", code: "Pong! $ping ms", });
client.command({ name: "say", aliases: ["echo"], code: "$onlyIf[$argsCount>0;You need to give me something to say!]$sendMessage[$args]", });
client.command({ name: "score", code: ` $if[$args[0]==add; $addVar[score;1;$authorID]You're now at $getVar[score;0;$authorID] points!; Your score is $getVar[score;0;$authorID] ] `, });

Install

One package. stoat.js comes along as a dependency, pinned to a known-good version.

$ npm install stoatly.js
stoat.js ships as a dependency of stoatly.js, currently pinned to ^7.3.6. You don't need to install it yourself, though client.client gives you the raw instance if you ever need it directly.
  • Node.js v22.15.0+ - required by stoat.js itself. Deno v2.2+ also works.
  • ES modules only - stoatly.js ships as "type": "module". Use import, not require().
  • CommonJS project? Switch your own package.json to "type": "module", or load stoatly.js with a dynamic import("stoatly.js").

Every built-in function

Grouped the way you'll reach for them: reading context, taking action, branching logic, storing state, and general-purpose utilities. Search or filter by category.

No functions match that search.

Bring your own functions

Anything the built-ins don't cover, you add yourself, same $name[args] syntax, your own logic.

client.addFunction({ name: "double", execute: (args) => String(Number(args[0]) * 2), });

Need control over when your arguments get evaluated, the way $if only evaluates the branch it takes? Set lazy: true and read the raw argument nodes yourself:

execute: (rawArgNodes, ctx, evalNodes) => { // evalNodes(node) when you decide to }

Nothing is hidden from you

stoatly.js wraps stoat.js, it doesn't replace it. Drop down to raw events whenever a $function isn't enough.

client.on("messageCreate", (message) => { // full access to the underlying stoat.js Message object });

client.client is the underlying stoat.js Client instance. Its users, channels, and messages are reactive under the hood, read straight from it and it stays in sync.

Bring your Bot Online

Get your bot token, start building your bot.

import { StoatlyClient } from "stoatly.js"; const client = new StoatlyClient({ prefix: "!" }); client.command({ name: "ping", code: "Pong! $ping ms", }); client.command({ name: "say", aliases: ["echo"], code: "$onlyIf[$argsCount>0;You need to give me something to say!]$sendMessage[$args]", }); client.command({ name: "score", code: ` $if[$args[0]==add; $addVar[score;1;$authorID]You're now at $getVar[score;0;$authorID] points!; Your score is $getVar[score;0;$authorID] ] `, }); client.login("YOUR_BOT_TOKEN");

Development

See CONTRIBUTING.md in the repo for how to add a new $function.

npm install
npm test
npm run lint