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

@linqapp/chat-sdk-adapter

v0.1.0

Published

Linq adapter for Chat SDK — build chatbots that talk over iMessage and SMS

Readme

@linqapp/chat-sdk-adapter

Linq adapter for Chat SDK (chat). Build agentic chatbots that talk over iMessage and SMS through Linq, using the same handler code you'd write for Slack, Telegram, or WhatsApp.

Install

npm install @linqapp/chat-sdk-adapter chat

Quick start

import { createLinqAdapter } from "@linqapp/chat-sdk-adapter";
import { Chat } from "chat";

const chat = new Chat({
  userName: "mybot",
  adapters: {
    linq: createLinqAdapter({
      apiKey: process.env.LINQ_API_KEY!,
      signingSecret: process.env.LINQ_WEBHOOK_SECRET!,
    }),
  },
});

chat.onDirectMessage(async (thread, message) => {
  await thread.subscribe();
  await thread.post(`you said: ${message.text}`);
});

chat.onReaction(["thumbs_up"], async (event) => {
  await event.thread.post("appreciate the tapback 🫡");
});

Then route Linq webhooks to the adapter from any framework with fetch-style handlers:

// e.g. a Nitro/Next.js/Hono POST route
export default async (request: Request) => {
  return chat.webhooks.linq(request);
};

Point a Linq webhook subscription at that route and subscribe to at least:

  • message.received
  • reaction.added
  • reaction.removed

Other event types are acknowledged with a 200 and ignored.

Configuration

| Option | Required | Description | | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ | | apiKey | yes | Linq API key used for all outbound API calls. | | signingSecret | yes | Webhook signing secret. Requests are verified with HMAC-SHA256 over {timestamp}.{raw_body}, with replay-window checks. | | baseURL | no | Override the Linq API base URL (e.g. sandbox). |

Supported features

| Feature | Status | | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | Inbound text messages | ✅ | | Outbound text messages | ✅ | | Group chats | ✅ reply to existing groups received via webhook | | Inbound media (images, audio, files) | ✅ parsed as attachments with downloadable data | | Outbound media / file sending | ✅ attachments and files on a message become media parts | | Inbound reactions (tapbacks + custom emoji) | ✅ dispatch to onReaction() | | Outbound reactions (add/remove) | ✅ | | Edit message | ✅ text, first part only | | Fetch message / history / thread | ✅ | | Typing indicators | ✅ DMs only (Linq rejects typing in groups) | | Webhook signature verification + replay protection | ✅ | | Streaming | ⚠️ buffered — recipients see one final message | | Sticker reactions | ❌ skipped (no Chat SDK equivalent) | | Delete message | ❌ Linq cannot unsend on the recipient's device | | openDM() / creating chats | ❌ Linq creates chats with an initial message, which doesn't match Chat SDK semantics — the adapter only replies to existing chats | | Modals, cards, slash commands | ❌ no Linq equivalent — cards render as fallback text |

Thread IDs

Thread IDs are stable and always take the form linq:{chatId}, regardless of whether the thread was first seen via webhook or API. Group vs DM identity is tracked internally from webhook payloads and chats.retrieve() calls; legacy linq:{chatId}:group / linq:{chatId}:dm IDs from older versions still decode.

Attachments

Attach media by putting attachments or files on a message:

await thread.post({
  markdown: "here's the report 📎",
  attachments: [{ type: "file", url: "https://example.com/report.pdf", mimeType: "application/pdf" }],
});

// or send raw bytes
await thread.post({
  markdown: "fresh render",
  files: [{ filename: "render.png", mimeType: "image/png", data: pngBuffer }],
});

How each attachment is delivered:

  • Public HTTPS URL, ≤ 10MB — sent by reference; Linq downloads it on send. No upload round-trip, so forwarding inbound Linq media (already on cdn.linqapp.com) is free.
  • Raw bytes, non-HTTPS URLs, or files > 10MB — uploaded via POST /v3/attachments (up to 100MB) and sent by attachment_id.

A message can be media-only (no text). Inbound attachments expose fetchData() to download, and survive queue serialization via rehydrateAttachment (Linq CDN URLs don't expire). Audio is sent as a downloadable file attachment — the dedicated iMessage voice-memo bubble endpoint isn't wired up yet.

Reactions

Standard iMessage tapbacks map to normalized Chat SDK emoji in both directions:

| Linq tapback | Chat SDK emoji | | ------------ | -------------- | | like | thumbs_up | | dislike | thumbs_down | | love | heart | | laugh | laugh | | emphasize | exclamation | | question | question |

Custom emoji reactions pass through the default emoji resolver (e.g. 👍thumbs_up), falling back to the raw emoji for anything unmapped.

Development

pnpm install
pnpm test        # vitest
pnpm typecheck
pnpm build

A full example app (Nitro server wiring Linq, Telegram, and WhatsApp adapters into one bot) lives in apps/api in this repo.

Live smoke test

smoke-live.mjs drives this adapter against the real Linq API so you can validate a sandbox in one command. Run pnpm build first (it imports ./dist).

Get a sandbox number with the Linq CLI: linq signup --phone <your cell>, then grab the token from ~/.linq/config.json.

# outbound: bootstrap a chat and send text + two images (one by URL, one pre-uploaded)
LINQ_API_KEY=<token> LINQ_FROM=<sandbox number> LINQ_TEST_TO=<your cell> \
  node smoke-live.mjs send

# inbound: receive real webhooks (text + reactions), optionally echo-reply
LINQ_API_KEY=<token> LINQ_SIGNING_SECRET=<webhook secret> LINQ_ECHO=1 \
  node smoke-live.mjs serve
# then tunnel it (cloudflared/ngrok) and register the URL as a Linq webhook subscription

| Env | Mode | Purpose | | --- | ---- | ------- | | LINQ_API_KEY | both | Linq API token | | LINQ_FROM / LINQ_TEST_TO | send | sender (sandbox) number / your phone — or set LINQ_TEST_CHAT_ID to reuse a chat | | LINQ_SIGNING_SECRET | serve | webhook signing secret (from the subscription) | | LINQ_BASE_URL | both | override API base URL (optional) | | LINQ_ECHO=1 | serve | reply to inbound messages so you get a round-trip on the device |

License

Apache-2.0