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

@argon-sdk/core

v1.2.0

Published

SDK for building Argon Chat bots

Readme

@argon-sdk/core

SDK for building Argon Chat bots.

Install

bun add @argon-sdk/core

Quick start

import { Bot, Intent, command, name, describe, message } from '@argon-sdk/core'

const bot = new Bot(process.env.BOT_TOKEN, {
  intents: Intent.Messages | Intent.Commands,
})

const hello = command(name('hello'), describe('Say hi')).run(async (ctx) => {
  await ctx.reply(`hi, ${ctx.user.displayName}`)
})

bot.commands(hello)

bot.on(message.create, async (ctx) => {
  await ctx.reply(`echo: ${ctx.text}`)
})

await bot.start()

Two API styles

Every builder in the SDK supports two usage styles. Use whichever fits better.

Token-based

Compact, declarative, great for simple definitions:

import { command, name, describe, option, string } from '@argon-sdk/core'

const greet = command(
  name('greet'),
  describe('Greet a user'),
  option(string('Who to greet'), name('target'), describe('Username')),
).run(async (ctx, opts) => {
  await ctx.reply(`Hello, ${opts.target}!`)
})

Fluent

Chainable methods, good when you need full control:

import { CommandBuilder } from '@argon-sdk/core'

const greet = new CommandBuilder()
  .setName('greet')
  .setDescription('Greet a user')
  .addOption((o) => o.string().setName('target').setDescription('Who to greet'))
  .run(async (ctx, opts) => {
    await ctx.reply(`Hello, ${opts.target}!`)
  })

Commands

Options are fully typed -- opts.target is string, opts.count is number, etc.

import { command, name, describe, option, string, integer } from '@argon-sdk/core'

const roll = command(
  name('roll'),
  describe('Roll dice'),
  option(integer('Number of sides').choices({ d6: 6, d20: 20 }), name('sides')),
).run(async (ctx, opts) => {
  const result = Math.floor(Math.random() * opts.sides) + 1
  await ctx.reply(`Rolled: ${result}`)
})

bot.commands(roll, greet)

Events

Two ways to subscribe -- filter objects or string event names:

import { message, member, modal, reaction, button } from '@argon-sdk/core'

// Filter objects -- composable, type-safe
bot.on(message.create, async (ctx) => {
  await ctx.reply(`echo: ${ctx.text}`)
})

bot.on(member.join, async (ctx) => {
  console.log('new member:', ctx.userId)
})

bot.on(modal.submit, async (ctx) => {
  console.log('modal data:', ctx.values)
})

bot.on(button.interaction, async (ctx) => {
  console.log('button clicked:', ctx.id)
})

bot.on(reaction.add, async (ctx) => {
  console.log('reaction:', ctx.emoji)
})

// String event names -- simpler, still typed
bot.on('messageCreate', async (ctx) => {
  await ctx.reply(`echo: ${ctx.text}`)
})

bot.on('memberJoin', async (ctx) => {
  console.log('new member:', ctx.userId)
})

bot.on('modalSubmit', async (ctx) => {
  console.log('modal data:', ctx.values)
})

Bot lifecycle

import { botLifecycle } from '@argon-sdk/core'

bot.on(botLifecycle.installing, async (ctx) => {
  console.log('installed into space', ctx.spaceId)
})

bot.on(botLifecycle.entitlementsUpdated, async (ctx) => {
  console.log(
    `space ${ctx.spaceId}: granted=${ctx.grantedEntitlements} required=${ctx.requiredEntitlements}`,
  )
})

botLifecycle.entitlementsUpdated fires whenever the space's granted entitlements drift from what the bot requires — handy for surfacing a re-authorize prompt. Always delivered, no intent needed.

Voice & calls

Voice channel join/leave events are unprivileged (Intent.Voice). Call lifecycle events (Intent.Calls) are verified-bot only.

import { voice, call } from '@argon-sdk/core'

bot.on(voice.join, async (ctx) => {
  console.log(`${ctx.displayName} joined voice ${ctx.channelId}`)
})

bot.on(voice.leave, async (ctx) => {
  console.log(`${ctx.displayName} left voice ${ctx.channelId}`)
})

bot.on(call.incoming, async (ctx) => {
  await ctx.accept() // or ctx.reject('busy')
})

bot.on(call.ended, async (ctx) => {
  console.log('call ended:', ctx.callId)
})

The audio data plane (LiveKit room, Opus codec) is not part of @argon-sdk/core — it's handled by the separate @argon-sdk/voice plugin (analogous to @discordjs/voice).

Low-level API endpoints are accessible via bot.api.voice, bot.api.calls, bot.api.voiceEgress if you need them directly.

Controls

Buttons

Token-based -- label via name():

import { row, button, name } from '@argon-sdk/core'

await ctx.reply('Pick one', {
  controls: [
    row(
      button.callback(name('Confirm')).id('confirm').on(async (ctx) => {
        await ctx.reply('Confirmed!')
      }),
      button.link(name('Docs')).href('https://argon.gl/docs'),
    ),
  ],
})

Fluent:

import { row, ButtonBuilder } from '@argon-sdk/core'

await ctx.reply('Pick one', {
  controls: [
    row(
      new ButtonBuilder('callback').label('Confirm').id('confirm').on(async (ctx) => {
        await ctx.reply('Confirmed!')
      }),
      new ButtonBuilder('link').label('Docs').href('https://argon.gl/docs'),
    ),
  ],
})

Selects

Token-based -- placeholder via name():

import { row, select, name } from '@argon-sdk/core'

const roleSelect = select.string(name('Pick a role'))
  .id('role')
  .options([
    { label: 'Admin', value: 'admin' },
    { label: 'Member', value: 'member' },
  ])
  .on(async (ctx) => {
    await ctx.reply(`Selected: ${ctx.values.join(', ')}`)
  })

Fluent -- one SelectBuilder for all types:

import { SelectBuilder } from '@argon-sdk/core'

const roleSelect = new SelectBuilder('stringSelect')
  .id('role')
  .placeholder('Pick a role')
  .options([
    { label: 'Admin', value: 'admin' },
    { label: 'Member', value: 'member' },
  ])
  .on(async (ctx) => {
    await ctx.reply(`Selected: ${ctx.values.join(', ')}`)
  })

const userPicker = new SelectBuilder('userSelect')
  .id('target')
  .placeholder('Pick a user')
  .on(async (ctx) => {
    await ctx.reply(`Picked: ${ctx.values[0]}`)
  })

Other select types: select.user(), select.archetype(), select.channel().

await ctx.reply('Choose:', { controls: [row(roleSelect)] })

Modals

Token-based

import { modal, textInput, checkboxInput } from '@argon-sdk/core'

const feedback = modal(
  'feedback',
  'Send feedback',
  textInput('message', 'Your message').style('paragraph').required(),
  checkboxInput('anonymous', 'Send anonymously'),
)

await ctx.showModal(feedback)

Fluent

import { ModalBuilder } from '@argon-sdk/core'

const feedback = new ModalBuilder()
  .setId('feedback')
  .setTitle('Send feedback')
  .addControl((c) => c.textInput().setId('message').setLabel('Your message').setStyle('paragraph').setRequired())
  .addControl((c) => c.checkbox().setId('anonymous').setLabel('Send anonymously'))
  .build()

await ctx.showModal(feedback)

Rich text

Token-based

import { richText, bold, plain, italic, url } from '@argon-sdk/core'

const msg = richText(
  bold('Welcome'),
  plain(' to the server! Read the '),
  url('https://argon.gl/rules', 'rules'),
  plain('.'),
)

await ctx.reply(msg.text, { entities: msg.entities })

Fluent

import { richText } from '@argon-sdk/core'

const msg = richText()
  .bold('Welcome')
  .plain(' to the server! Read the ')
  .url('https://argon.gl/rules', 'rules')
  .plain('.')
  .build()

await ctx.reply(msg.text, { entities: msg.entities })

Middleware

bot.use(async (ctx, next) => {
  const start = Date.now()
  await next()
  console.log(`${ctx.event} took ${Date.now() - start}ms`)
})

Writing a plugin

Plugins inject a service that becomes available on every context. Use definePlugin to keep the literal name and value type — the Bot<TDecorators> generic accumulates them automatically. No declare module augmentation needed in consumer code.

// my-plugin/src/index.ts
import { definePlugin } from '@argon-sdk/core'

export const myPlugin = (config: Config) =>
  definePlugin('myService', () => buildService(config))
// → Plugin<'myService', MyService>

Consumer:

const bot = new Bot(token).plugin(myPlugin(cfg))

bot.on(message.create, async (ctx) => {
  ctx.myService.doThing() // typed automatically — no .d.ts
})

For nested handlers (button/select callbacks), construct controls via the bot-bound factories so decorators thread through:

bot.on(message.create, async (ctx) => {
  await ctx.reply('Pick:', {
    controls: [
      bot.row(
        bot.button.callback(name('Confirm'))
          .id('confirm')
          .on(async (innerCtx) => {
            innerCtx.myService.log('clicked') // typed
          }),
      ),
    ],
  })
})

The standalone button.callback(...) / command(...) / row(...) exports still work — they default to Bot<{}>, so nested handlers won't see plugin types.

License

MIT