A full-featured TypeScript library for parsing, validating, and running AIML (Artificial Intelligence Markup Language) bots on both Node.js and in the browser.

Features


Installation

npm install aiml.js

npm scripts

Command Description
npm run build Compile library + CLI to dist/
npm test Run Jest test suite
npm run test:coverage Tests with coverage report
npm run docs Generate Markdown API docs in docs/
npm run typecheck TypeScript type check (no emit)
npm run build:docs Rebuild static docs site in gh-pages/
npm run validate -- <path> Run aiml-validate on a file or directory
npm run serve -- [args] Run aiml-serve (ChatGPT-compatible API)
npm run chat:freeaiml Interactive terminal chat with Free-AIML bot
npm run chat:rosie Interactive terminal chat with Rosie AIML 2.0 bot
npm run app Start the web chat app at http://localhost:3000

aiml-validate CLI

Validate AIML files from the command line.

Run without installing (npx)

npx aiml.js aiml-validate mybot.aiml
npx aiml.js aiml-validate -r ./knowledge-base

Install globally

npm install -g aiml.js
aiml-validate --help

Usage

aiml-validate [options] <file|directory> [...]

Options:
  -r, --recursive    Recurse into subdirectories
  -s, --stats        Show per-file category count and parse time
  -q, --quiet        Only print errors (suppress warnings and stats)
  --json             Output results as JSON (useful for CI pipelines)
  -v, --version      Show version
  -h, --help         Show help

Examples

# Validate a single file
aiml-validate greetings.aiml

# Validate all .aiml files in a folder
aiml-validate ./alice/

# Recursive scan with per-file stats
aiml-validate -r --stats ./knowledge-base/

# CI-friendly JSON output โ€” exits 1 on errors
aiml-validate --json -r ./aiml/ > report.json

Sample output

โœ“ greetings.aiml  (42 categories, 8ms)
โœ“ personality.aiml (318 categories, 22ms)
โœ— broken.aiml
  โœ— error   <category> missing <template> [category]

Results: 3 files checked
  360 categories loaded
  2 valid
  1 with errors (1 total)

aiml-serve CLI

Expose any AIML bot as a ChatGPT-compatible REST API with Swagger UI โ€” use it as a drop-in for the OpenAI API.

Run without installing (npx)

npx aiml.js aiml-serve ./alice/
npx aiml.js aiml-serve --rosie --freeaiml --port 8080

Install globally

npm install -g aiml.js
aiml-serve --help

Usage

aiml-serve [options] [file|directory ...]

Options:
  -p, --port <n>       Port (default: 8080)
  -m, --model <name>   Model name in API responses (default: aiml-bot-1)
  --api-key <key>      Require Bearer token in Authorization header
  --v2                 Force AIML 2.0 parser
  --rosie              Load bundled Rosie bot
  --freeaiml           Load bundled Free-AIML bot
  -h, --help           Show help

Endpoints

Method Path Description
GET / API info (categories, model, links)
GET /v1/models List models
POST /v1/chat/completions Chat โ€” ChatGPT-compatible
GET /docs Swagger UI (dark themed)
GET /openapi.json OpenAPI 3.0 spec

Example request

curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "aiml-bot-1",
    "messages": [{ "role": "user", "content": "hello" }],
    "user": "session-42"
  }'

The user field is used as the session ID โ€” the bot remembers predicates (names, topics, etc.) across requests with the same value.

Streaming

curl http://localhost:8080/v1/chat/completions \
  -d '{"messages":[{"role":"user","content":"hello"}],"stream":true}' \
  -H "Content-Type: application/json"

Use with the OpenAI SDK

import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'http://localhost:8080/v1',
  apiKey: 'none',
});

const res = await client.chat.completions.create({
  model: 'aiml-bot-1',
  messages: [{ role: 'user', content: 'my name is Alice' }],
  user: 'session-1',
});
console.log(res.choices[0].message.content); // "Nice to meet you, Alice!"

Opt-in features

Two template tags are disabled by default for security. Enable them explicitly for AIML files you trust.

`` โ€” shell execution (Node.js only)

const bot = new AIML1Bot({ enableSystem: true });
<category>
  <pattern>WHAT TIME IS IT</pattern>
  <template>The time is <system>date +%H:%M:%S</system>.</template>
</category>

<category>
  <pattern>HOW MANY LINES IN *</pattern>
  <template><system>wc -l "<star/>"</system></template>
</category>

When enableSystem is false (default), <system> tags produce empty output rather than throwing.

`` โ€” inline JS via `new Function()`

const bot = new AIML1Bot({ enableJavaScript: true });
<category>
  <pattern>REVERSE *</pattern>
  <template><javascript>
    return '<star/>'.split('').reverse().join('');
  </javascript></template>
</category>

<category>
  <pattern>CALCULATE *</pattern>
  <template><javascript>
    var e = '<star/>';
    if (/^[0-9+\-*\/.() ]+$/.test(e))
      return String(Function('return (' + e + ')')());
    return 'Invalid expression.';
  </javascript></template>
</category>

Backslash tip: When embedding AIML in a JS/TS template literal, write \\s (double backslash) for regex \s โ€” a bare \s gets silently collapsed to s by the template-literal parser. This is a JS string-escaping concern, not an aiml.js issue.

`` โ€” silent logging via subclass

<gossip> fires handleGossip(text) without producing any output. Override it by subclassing:

class LoggingBot extends AIML1Bot {
  protected handleGossip(text: string): void {
    console.log('[gossip]', text.trim());
  }
}
<category>
  <pattern>LOG *</pattern>
  <template><gossip>User said: <star/></gossip>Noted!</template>
</category>

See examples/06-opt-in-features.ts for a full runnable demo.


Web Chat App

A local Express.js web app with a ChatGPT-like interface that combines the Rosie AIML 2.0 bot and the Free-AIML collection (credits: Pandora Bots) into a single bot named Alice.

npm run app
# โ†’ http://localhost:3000

Features:


Quick start

Node.js

import { AIML1Bot } from 'aiml.js';

const bot = new AIML1Bot({ properties: { name: 'Alice' } });
await bot.loadDirectory('./aiml');          // load all *.aiml files

const { response, sessionId } = await bot.talk('hello');
console.log(response); // "Hi there!"

// Continue the same conversation with the session ID
const r2 = await bot.talk('my name is Bob', sessionId);
const r3 = await bot.talk('what is my name', sessionId);
console.log(r3.response); // "Your name is Bob."

Browser (with a bundler)

import { AIML1Bot } from 'aiml.js';

const bot = new AIML1Bot();

// From a <input type="file"> element
const [file] = inputEl.files;
await bot.loadFile(file);

// Or from a pre-loaded string
await bot.loadFile({ name: 'greetings.aiml', content: xmlString });

const { response } = await bot.talk('hello');

AIML 1.0

import { AIML1Bot } from 'aiml.js';

const bot = new AIML1Bot({
  properties: { name: 'Alice', version: '1.0' },
  defaultTopic: 'general',
});

// All-in-one directory loader (finds bot.properties, normal.txt, sets/, *.aiml)
await bot.loadDataDirectory('./alice');

// Or load each piece individually
await bot.loadPropertiesFile('./alice/bot.properties');
await bot.loadSubstitutionFile('normal', './alice/normal.txt');
await bot.loadSetFile('color', './alice/sets/colors.txt');
await bot.loadDirectory('./alice/aiml');

Supported AIML 1.0 tags

<star>, <srai>, <sr>, <set>, <get>, <bot>, <think>, <condition> (all 3 forms), <random>, <uppercase>, <lowercase>, <formal>, <sentence>, <person>, <person2>, <gender>, <input>, <that>, <thatstar>, <topicstar>, <learn>, <gossip>, <system> (opt-in), <javascript> (opt-in), <date>, <version>, <size>, <id>, <br>.


AIML 2.0

import { AIML2Bot } from 'aiml.js';

const bot = new AIML2Bot({
  properties: { name: 'Rosie' },
  maps:  { capitals: { france: 'Paris', japan: 'Tokyo' } },
  sets:  { color: ['red', 'green', 'blue'] },
  sraixHandler: async (service, input) => fetchExternalService(service, input),
  maxRecursionDepth: 200,
});

// All-in-one loader (properties.json, substitutions/, sets/, maps/, *.aiml)
await bot.loadDataDirectory('./rosie');

Additional AIML 2.0 features

Tag / feature Description
# wildcard Zero or more words (highest priority)
^ wildcard Zero or more words (lower than *)
<sraix> Call external services
<map name="..."> / <map><name>...</name> Named map lookups (both attribute and element forms)
<normalize> / <denormalize> Substitution transforms
<explode> Space-separate every character
<first> / <rest> First / remaining words
<request> / <response> History access
<learn> / <learnf> Teach the bot at runtime
<loop/> Loop a <condition> branch
<addtriple> / <deletetriple> Manage RDF-style facts
<select> / <uniq> Query the fact store
<condition> with <var> element AIML 2.0 child-element condition form
Sets / maps in JSON ["item"] / {"key":"value"}

Session management

// Conversations are isolated by session ID
const r1 = await bot.talk('my name is Alice', 'user-42');
const r2 = await bot.talk('what is my name', 'user-42');
// r2.response โ†’ "Your name is Alice."

// Unset predicates return "unknown" (Pandorabots convention)
// Conditions in AIML check value="unknown" for unset predicates

// Serialise โ€” persist to DB, file, or localStorage
const json = bot.serializeSession('user-42');

// Restore in a different process / after restart
const bot2 = new AIML1Bot(/* same options */);
await bot2.loadDirectory('./aiml');
const id = bot2.loadSerializedSession(json);

// Save / restore all sessions at once
const allJson = bot.serializeAllSessions();
bot2.loadAllSerializedSessions(allJson);

Loading data (text and JSON)

All loaders auto-detect format.

Properties

bot.loadProperties('name:Alice\nversion:1.0');          // text
bot.loadProperties({ name: 'Alice', version: '1.0' });  // object
bot.loadProperties('{"name":"Alice"}');                  // JSON
// Pandorabots array-of-arrays format also supported:
bot.loadProperties('[["name","Alice"],["version","1.0"]]');

Substitutions

bot.loadSubstitutions('normal', "can't : cannot\nwon't : will not");   // text
bot.loadSubstitutions('normal', '[{"find":"cant","replace":"cannot"}]'); // JSON array
bot.loadSubstitutions('person', '[["I am","he or she is"]]');            // array-of-arrays

Sets

bot.loadSet('color', ['red', 'green', 'blue']);  // array
bot.loadSet('animal', 'cat\ndog\nbird');          // text
bot.loadSet('fruit', '["apple","banana"]');       // JSON
bot.loadSet('name', '[["Alice"],["Bob"]]');        // Pandorabots array-of-arrays

Maps

bot.loadMap('capitals', { france: 'Paris', japan: 'Tokyo' });  // object
bot.loadMap('size', 'small : 1\nmedium : 2');                  // text
bot.loadMap('scores', '{"alice":"100"}');                       // JSON
bot.loadMap('nation2capital', '[["France","Paris"]]');          // array-of-arrays

Programmatic categories

bot.addCategory('HELLO', 'Hi there!');
bot.addCategory('DO YOU LIKE *', 'I love <star/>!', { topic: 'ANIMALS' });
bot.addCategory('ARE YOU *', '<srai>HELLO</srai>', { that: 'HI THERE' });

Validation

import { validateAIML } from 'aiml.js';

const result = validateAIML(xmlString, 'mybot.aiml');
if (!result.valid) {
  for (const err of result.errors) {
    console.error(`${err.file}: ${err.message}`);
  }
}

Pattern priority

Priority AIML 1.0 AIML 2.0
Highest _ wildcard # wildcard
High Exact words _ wildcard
Medium - Exact words / sets
Low - ^ wildcard
Lowest * wildcard * wildcard

Priority applies independently to input, <that>, and <topic> patterns. Categories without a <topic> element default to topic="*" (match any topic).


API reference

Generate the full Markdown docs locally:

npm run docs
# โ†’ docs/

Examples

File Description
examples/01-basic-aiml1.ts Basic AIML 1.0 โ€” predicates, srai, topics, random, serialisation
examples/02-aiml2-features.ts AIML 2.0 โ€” wildcards, maps, triples, sraix, learn
examples/03-session-management.ts Multi-user sessions and serialisation
examples/04-validation-and-parser.ts Validation and low-level parser / PatternMatcher API
examples/05-browser-usage.ts Browser patterns (fetch, File API, localStorage)
examples/06-opt-in-features.ts <system>, <javascript>, <gossip> opt-in tags

Run an example:

npx tsx examples/01-basic-aiml1.ts

License

MIT