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

@fluidtalk/sdk

v1.0.3

Published

Official TypeScript/JavaScript client for the FluidTalk Handshake API. Drop FluidTalk's AI persona replies into your own chatbot backend without rebuilding the API client.

Downloads

534

Readme

@fluidtalk/sdk

Official TypeScript/JavaScript client for the FluidTalk Handshake API. Drop FluidTalk's AI persona replies into your own chatbot backend without rebuilding the HTTP/auth/multipart layer.

You keep your own orchestration (when to open a chat, when to follow up, where to send messages); this client just gives you the persona's reply in one call.

npm install @fluidtalk/sdk

⚠️ Server-side only. Use this from your backend. Never put your ft_sk_ key in frontend, browser, or mobile-client code — it would ship in your bundle and leak. Your server calls FluidTalk; your client calls your server.

import { FluidTalk } from "@fluidtalk/sdk";

const ft = new FluidTalk({
  apiKey: process.env.FT_API_KEY!,        // ft_sk_... (scoped to one persona + engine)
  ownUsername: "@my_account",             // optional default for tracking / dedup
  // baseUrl: "https://api-talk.fluidvip.com" (default)
});

// A lead messaged you → get the persona's reply and relay it on your platform
const res = await ft.inboundChat({ target: "@john_doe", message: "hey, saw your post!" });
for (const bubble of res.replies) await myChat.send("@john_doe", bubble);
if (res.shouldSendPhoto && res.photoUrl) await myChat.sendImage("@john_doe", res.photoUrl);

Concepts → methods

A conversation can start four ways. The builder names map to these methods (the API wire value is handled for you):

| Builder name | When | Method | | :-- | :-- | :-- | | Inbound Chat | the lead messages first (or an ongoing chat) | ft.inboundChat({ target, message }) | | AI Opener | your persona messages first | ft.aiOpener({ target }) | | Event Trigger | a platform event fires | ft.eventTrigger({ target, eventType, context }) | | Follow-up | re-engage a quiet lead | ft.followUp({ target }) | | — | host a local image for context | ft.uploadImage({ path }) |

A conversation is keyed by (persona, target) — reuse the same target for the same lead and the engine keeps phase/state across turns. The persona + engine are fixed by your API key.

An image (profile screenshot / context photo) is accepted by inboundChat, aiOpener, and eventTriggernot followUp.

Examples

// AI opener to a new lead (optionally with a profile screenshot for better targeting)
const opener = await ft.aiOpener({ target: "@lead", image: { path: "./profile.jpg" } });
await myChat.send("@lead", opener.message);

// Platform event (eventType must match an Event Trigger configured in your engine)
const ev = await ft.eventTrigger({ target: "@lead", eventType: "story_reaction", context: { reaction: "🔥" } });

// Re-engage a ghost (needs an existing conversation)
const fu = await ft.followUp({ target: "@lead" });

// Attach an image the lead sent — from a local file path...
const { url } = await ft.uploadImage({ path: "./incoming.jpg" });
const r = await ft.inboundChat({ target: "@lead", message: "what do you think?", image: { url } });

// ...or from bytes (e.g. a browser upload relayed to your backend, or serverless)
const up = await ft.uploadImage({ data: bytes, filename: "lead.jpg" });
// you can also pass raw bytes straight to a message: image: { data: bytes, filename: "lead.jpg" }

Results

  • inboundChat{ reply, replies[], shouldSendPhoto, photoUrl, sessionId, ignored, ignoreReason }
  • aiOpener / eventTrigger / followUp{ sessionId, message, ignored, ignoreReason, continued, continuedReason }
  • uploadImage{ url }

Always send replies as separate bubbles; when shouldSendPhoto is true, also send photoUrl.

Behaviour to handle

  • continued: true — the lead already had an active conversation, so the action continued it instead of starting fresh. For an opener (aiOpener), message is then ""send nothing. continuedReason tells you why (e.g. "conversation_already_active"). For eventTrigger, the event is folded into the ongoing chat and message is the reply.
  • ignored: true — duplicate lead (already owned by another account of the persona). Send nothing.
  • Sealed conversation. When a conversation is finished, the next inboundChat throws FluidTalkError with status: 400 ("This session is DONE") — stop messaging that lead.
import { FluidTalk, FluidTalkError } from "@fluidtalk/sdk";
try {
  const res = await ft.inboundChat({ target, message });
  if (res.ignored) return; // skip duplicate
  // ... send res.replies
} catch (e) {
  if (e instanceof FluidTalkError) {
    if (e.status === 401) { /* missing or invalid API key */ }
    else if (e.status === 402) { /* insufficient credits to start a new conversation — see your plan */ }
    else if (e.status === 400) { /* conversation already done */ }
    else if (e.status === 404) { /* no conversation yet (follow-up before any contact) */ }
  }
}

API keys are action-only: send messages and upload images, but never change personas/engines/billing (that stays in the dashboard).