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

asterisk-ami-node

v1.0.0

Published

Dependency-free Asterisk Manager Interface (AMI) client for Node.js. TypeScript types, event streaming, auto-reconnect, ESM and CommonJS.

Readme

asterisk-ami-node

A small, dependency-free Asterisk Manager Interface client for Node.js.

AMI is a line protocol over TCP, so this package hand-rolls it against node:net and node:tls. Nothing else is pulled into your tree.

npm install asterisk-ami-node
  • Zero runtime dependencies
  • TypeScript types included, written in TypeScript
  • Ships both ESM and CommonJS
  • Action replies and the event stream kept apart, including during list actions
  • Optional auto-reconnect with backoff, and a keepalive that detects a dead socket
  • Refuses to send fields containing line breaks, which would otherwise inject a second action
  • Node 18 or newer

Quick start

import { AmiClient, listRows } from "asterisk-ami-node";

const ami = new AmiClient({
  host: "127.0.0.1",
  port: 5038,
  username: "admin",
  password: "secret",
});

await ami.connect();
console.log(ami.serverGreeting); // Asterisk Call Manager/7.0.3

const reply = await ami.action({ Action: "CoreShowChannels" });
for (const row of listRows(reply, "CoreShowChannel")) {
  console.log(row.Channel, row.CallerIDNum, row.Duration);
}

ami.close();

CommonJS works the same way:

const { AmiClient } = require("asterisk-ami-node");

Asterisk side setup

Add a manager account in /etc/asterisk/manager.conf:

[general]
enabled = yes
port = 5038
bindaddr = 127.0.0.1

[admin]
secret = secret
read = system,call,command,agent,user,config,dtmf,reporting,cdr,dialplan
write = system,call,command,agent,user,config,originate

Then asterisk -rx "manager reload". Keep bindaddr on localhost and reach it over a tunnel or a private network. AMI has no transport security of its own unless you turn on TLS.

Listening for events

Every event that is not part of an action reply is emitted twice: once on event, and once under its own name.

const ami = new AmiClient({ host, username, password, events: "on" });

ami.on("Newchannel", (e) => console.log("up", e.Channel));
ami.on("Hangup", (e) => console.log("down", e.Channel, e.Cause));
ami.on("event", (e) => metrics.count(e.Event));

await ami.connect();

Set events: "off" if you only want to run actions, or name specific classes such as events: "call,agent" to cut the volume down.

Rows belonging to a list action never reach these handlers. CoreShowChannel events raised by your own CoreShowChannels call are returned to the caller instead, which is usually what you wanted and is easy to get wrong by hand.

Running CLI commands

const out = await ami.command("pjsip show endpoints");
console.log(out);

command() returns the raw text Asterisk prints. Asterisk 14 and later send it back as a repeated Output header, which this client folds into one newline-joined string.

Anything passed to command() runs with full manager privileges. Never hand it unreviewed input. If you need to expose it to users, check the command against an allow list first.

Staying connected

const ami = new AmiClient({
  host,
  username,
  password,
  keepAliveMs: 30_000,
  reconnect: { delayMs: 1000, maxDelayMs: 30_000, retries: Infinity },
});

ami.on("reconnecting", (attempt, delay) => console.warn(`retry ${attempt} in ${delay}ms`));
ami.on("reconnected", () => console.log("back"));
ami.on("error", (err) => console.error(err.message));

Reconnect is off unless you ask for it. The delay doubles each attempt up to maxDelayMs. Calling close() stops it for good.

A dropped TCP connection can go unnoticed for a long time on an idle manager session, so keepAliveMs sends a Ping action on that interval and tears the socket down if it goes unanswered.

Line breaks are rejected, not stripped

AMI has no escaping. A value carrying CR or LF ends the current action early and the rest is read as a second action, so a channel name built from user input can turn a Originate into a Command.

await ami.action({ Action: "Originate", Channel: "PJSIP/1\r\nAction: Command" });
// AmiError: Refusing to send AMI field "Channel": line breaks in a value can inject a second action

Rejecting is deliberate. Silently stripping the break would send an action you did not write.

API

new AmiClient(options)

| Option | Default | Meaning | | --- | --- | --- | | host | required | Asterisk host | | port | 5038 | Manager port | | username | required | Manager account name | | password | required | Manager secret | | tls | false | Wrap the socket in TLS, needs tlsenable=yes | | rejectUnauthorized | false | Verify the certificate when tls is on | | timeoutMs | 10000 | Connect timeout, and default action timeout | | events | "on" | Value sent in the Events field at login | | keepAliveMs | off | Interval for the background Ping | | reconnect | off | true, or { retries, delayMs, maxDelayMs } |

Methods

  • connect() opens the socket and logs in. Rejects with AmiError on a bad secret.
  • action(fields, timeoutMs?) sends an action and resolves with every message carrying its ActionID, the ...Complete event included.
  • command(cli, timeoutMs?) runs a CLI command and resolves with its text output.
  • send(fields) writes an action without waiting for a reply.
  • close() disconnects and cancels any reconnect.

Properties

  • isConnected
  • serverGreeting the Asterisk Call Manager/x.y.z banner

Events

connect, close, error, reconnecting, reconnected, message (every parsed message), event (unsolicited events only), plus one channel per AMI event name.

Helpers

  • parseMessage(raw) parses one AMI message, folding repeated keys with newlines.
  • serializeAction(fields) builds an action frame, throwing on line breaks.
  • listRows(messages, eventName) keeps only the rows of a list action.
  • AmiError the error class everything here throws.

Timeouts on list actions

If a list action times out after some rows have already arrived, those rows are resolved rather than thrown away. A missing ...Complete event on a busy switch is common, and partial data beats an exception that loses the lot.

Related

  • pbx-mcp, a Model Context Protocol server that gives AI assistants a read-only window into Asterisk and FreeSWITCH. This client came out of building it.
  • freeswitch-esl-node, the same idea for the FreeSWITCH Event Socket.
  • ICTCore, the open source telephony framework behind our products.

About

Built and maintained by Tahir Almas at ICT Innovations, who have been shipping open source telephony since 2005.

Issues and pull requests are welcome. Bug reports with the actual error string are the most useful thing you can send.

License

MIT