npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

atlas-bot-sdk

v0.1.1

Published

Chat bots for Atlas: a plain Matrix account, an outbound-only /sync loop, the auto-responder guard kit, and the platform LLM. Typed ESM, zero runtime dependencies.

Readme

Atlas Bot SDK

Chat bots for Atlas. A bot is an Atlas bot account plus an outbound-only message stream: no webhook, no public URL, no endpoint registration, no Atlas source code. Membership is the routing table, the chat history is the queue, and the default chat UI is the whole interface. One BotFather token is the entire configuration.

Ships as compiled ESM with full type declarations; zero runtime dependencies, Node 18+. npm test builds and runs the suite (node:test).

The pieces

| File | What it is | |---|---| | src/bot.ts | createAtlasBot — the runtime: message stream, command/message routing, history, every sender, think() | | src/wire.ts | The app-visible wire contracts: metadata, choices widget, receipts, media, commands | | src/guards.ts | The guard kit (🤖 marker loop-breaker, stale guard, dedupe, cooldown, consecutive cap) — on by default | | src/llm.ts | The platform LLM client + the model→tool→model loop | | src/matrix.ts | The minimal chat-API client (messages, media upload/download, membership) + the provisioning surface BotFather uses | | src/config.ts | Baked-in production endpoints — why a token is the only config (env vars still override for dev) | | src/botfather.ts | BotFather — chat-based bot provisioning on Atlas accounts | | src/yaml-lite.ts | Dependency-free YAML subset for bot config files | | examples/doctor-finder/ | The worked example: LLM triage on deterministic rails → dispatch with tap-to-claim → case groups |

A minimal bot

import { createAtlasBot } from "atlas-bot-sdk";

const bot = createAtlasBot({
    accessToken: process.env.ATLAS_BOT_TOKEN,          // from BotFather — the whole config
    instructions: "You are a helpful assistant for the Acme team."
});

bot.tools.register("lookup_order", {
    description: "Look up an order by id",
    parameters: { type: "object", properties: { id: { type: "string" } } },
    handler: ({ id }) => myDatabase.orders.get(id)     // your server, your code
});

bot.onCommand("start", (ctx) => ctx.sendText("Hi! Ask me about your order."));
bot.onMessage(async (ctx) => {
    const turn = await ctx.think();                    // platform LLM + your tools
    if (turn.reply) await ctx.sendText(turn.reply);
});

bot.start();

Deploy anywhere with outbound HTTPS — Railway, a VPS, your laptop. Downtime loses nothing: the chat history queues events, the stream resumes from the persisted cursor, and the stale guard keeps a restarted bot from answering old mail.

What a bot can do

Everything below exists on the bot (bot.sendText(chatId, …)) and, bound to the current chat, on the handler context (ctx.sendText(…)):

  • MessagessendText; every send also pushes a lock-screen notification to the chat's humans (notifications: false opts out).
  • Choice buttonssendChoices(chatId, prompt, options): tappable chips in the app, a numbered list on older clients; the tap comes back as an ordinary reply (ctx.choice carries the value when the app marks it).
  • Live messages — sends return an eventId; editMessage(chatId, eventId, text, {choices, disabled}) edits the same bubble in place. Freeze buttons once the moment passes (claimed by someone else, passed, canceled) — per option or whole-widget disabled: true; frozen chips stay visible but stop responding.
  • Menus — nested choice arrays make button grids (rows of rows, max 4); silent: true options fire an invisible callback instead of a visible reply (ctx.isCallback) — answer by editing the menu message in place.
  • Reply keyboardssendKeyboard(chatId, prompt, options, {oneTime}) puts preset answers in a strip above the user's input bar until used, removed (removeKeyboard), or dismissed.
  • Relay & identitycopyMessage(chatId, event) / ctx.copyTo relays text or media (same content url, no re-upload); ctx.senderName gives the sender's display name. Markdown renders in bot messages.
  • Media, both directionssendImage / sendVideo / sendVoice / sendDocument / sendContact (builds the vCard) / sendLocation; inbound attachments arrive as ctx.attachment with a download().
  • Read ticks — automatic: the SDK marks handled messages read (receipts: false opts out); markRead(chatId) is the explicit form.
  • Chats & memberscreateGroup(chatId, title, members) makes a bot-owned group everyone's app auto-joins (THE pattern when people need to talk or call — bot chats are text-only), invite, auto-join of incoming invites, setRoomName (renders as a system row), dmRoom(userId) to message anyone. Winding down: kickUser(chatId, userId, reason?) removes a member (needs room power — guaranteed in groups the bot created) and leaveRoom(chatId) exits the bot itself. Remaining members keep the chat read-only with its history — Doctor Finder does both when a case closes.
  • Notificationsnotify(chatId, preview) pushes explicitly.
  • App commands — the user's Atlas executes a curated command on the bot's behalf and echoes the outcome back. The typed surface is bot.app(chatId) / ctx.app: messaging as the user (sendAsUser, listMessages, messageAction), chats (createChat, listChats, openChat), search & recall (searchMessages, searchChat, recallAbout, retrieveFile), memory (storeMemory, forgetAbout, tagging), to-dos, buckets, contacts (lookupContact), calls (startCall, endCall, muteCall, setCallCamera), notifications, and device routes (deviceContext, dialPhone, openUrl, openMaps, composeMail, composeSms). Every app.* method WAITS for the device's result — reads return their payload in reply.result.result, creates return the new item's id in reply.result.createdId. runCommand(chatId, command, params, {timeoutMs}) is the general awaiting form; sendCommand stays fire-and-forget. Legacy sugar (addTodo, createBucket, scheduleNotification, cancelNotification) remains. Params are sanitized on-device per command; bulk wipes, self-configuration, and confirm-gated destruction are off-catalog by design.
  • The platform LLMctx.think() (works with zero config; the token is the model access), ctx.think({extraHistory, tools}) for per-call tools, bot.tools.register for bot-wide ones, llm: false to opt out.

BotFather

Create and manage bots in chat, with the full command set (/newbot, /mybots, /setname, /setdescription, /setabouttext, /setuserpic, /setcommands, /token, /revoke, /setprivacy, /setjoingroups, /deletebot, /cancel). Interactive prompts (choosing a bot, Enable/Disable, delete confirmation) are buttons.

/newbot walks name → username (must end in bot), provisions the account, and hands back the access token. /setprivacy, /setjoingroups, and /setcommands are written to the bot's platform-stored settings, where the SDK reads them at startup — settings apply without the developer redeploying.

BotFather itself is run by the platform operator; the required credentials are documented in examples/run-botfather.js, not here.

The LLM

ctx.think() calls the platform model, authenticated with the bot's token — the developer never holds a provider key, and bot tokens authenticate at the platform directly. The platform enforces the base-rules preamble server-side (identify as a bot; never claim untraced actions; never impersonate), so the rules aren't strippable.

Guard rails (on by default)

Sends carry the 🤖 marker and marked inbound is ignored (no bot⇄auto-responder ping-pong); catch-up events older than 5 minutes are dropped; event ids are deduped; per-chat cooldown 1s; after 8 unanswered sends in 30 minutes the bot goes quiet in that chat until a human speaks. Guards gate every sender — media and app commands included. Disabling any of these is a deliberate opt-out via guardOptions.