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

@emergentic/client

v0.1.2

Published

TypeScript client for Emergentic simulations and agent chat.

Readme

@emergentic/client

TypeScript client for creating, attaching to, and chatting with Emergentic simulations from non-Python apps.

Use this package from a trusted server context. Do not expose an Emergentic service API key in browser JavaScript.

API documentation: https://emergentic.ai/api-docs

Install

pnpm add @emergentic/client

For local handoff before publishing:

pnpm add file:../path/to/packages/emergentic-client

Next.js Route Handler Example

import { EmergenticClient } from "@emergentic/client";

const emergentic = new EmergenticClient({
  baseUrl: process.env.EMERGENTIC_API_URL!,
  token: process.env.EMERGENTIC_API_KEY!,
});

export async function POST(req: Request) {
  const { message } = await req.json();

  const session = await emergentic.createAndAttachChat({
    worldId: Number(process.env.EMERGENTIC_WORLD_ID!),
    name: "Landing chat",
    agentIds: [Number(process.env.EMERGENTIC_AGENT_ID!)],
    start: true,
  });

  const stopHeartbeat = session.startHeartbeat();
  try {
    await session.sendMessage(message);
    const logs = await session.getLogs({ limit: 20 });

    return Response.json({
      simulationId: session.simulation.id,
      sessionId: session.sessionId,
      logs,
    });
  } finally {
    stopHeartbeat();
    await session.stop();
  }
}

session.getLogs() uses the public REST API and returns log metadata only: ids, action/agent/location ids, timestamps, and is_player. Use realtime callbacks such as session.onConversationMessage() for live message payloads. REST log and agent list calls default to 20 records per page and cap limit at 100; use afterId for cursor pagination.

Reuse an Existing Simulation

const session = await emergentic.attachSimulation({
  simulationId: 123,
  agentIds: [45],
  start: true,
});
const stopHeartbeat = session.startHeartbeat();

try {
  await session.sendMessage("Hi, can you introduce yourself?");

  await session.addAgents([46]);
  await session.sendMessage({
    content: "Can you weigh in too?",
    targetAgentIds: [46],
  });
} finally {
  stopHeartbeat();
}

In chat mode, the simulation's selected_agent_ids are the default roster. Use session.setAgents(), session.addAgents(), or session.removeAgents() to change that roster. Use targetAgentIds on one message to target a subset without changing the roster.

REST Resources

const world = await emergentic.createWorld({
  name: "Harbor District",
  width: 1600,
  height: 1200,
});

const location = await emergentic.createLocation(world.id, {
  name: "Studio Lobby",
  description: "A public lobby where agents meet guests.",
});

const mira = await emergentic.createAgent(world.id, {
  name: "Mira",
  backstory: "A producer coordinating the studio opening.",
  current_location_id: location.id,
});

const sol = await emergentic.createAgent(world.id, {
  name: "Sol",
  backstory: "A designer reviewing the guest experience.",
  current_location_id: location.id,
});

const plot = await emergentic.createPlot(world.id, {
  name: "Opening Schedule",
  narrative_mode: "schedule",
  schedule_state: {
    entries: [
      {
        title: "Lobby walkthrough",
        start_time: "2026-05-16T18:00:00Z",
        end_time: "2026-05-16T18:30:00Z",
        location_id: location.id,
        description: "Prepare the lobby walkthrough.",
      },
    ],
  },
});

const simulation = await emergentic.createSimulationRecord(world.id, {
  name: "Harbor District walkthrough",
  plot_id: plot.id,
  mode: "chat",
  agent_ids: [mira.id, sol.id],
});

const questionJob = await emergentic.questionAgentPair({
  question: "Where do these agents disagree?",
  agent_a_id: mira.id,
  agent_b_id: sol.id,
  simulation_id: simulation.id,
});

const recommendations = await emergentic.getAgentRecommendations(world.id, {
  simulationId: simulation.id,
  agentName: "Mira",
  limit: 5,
});

const analysis = await emergentic.getAgentAnalysisSummary(
  simulation.id,
  mira.id,
  {
    topLimit: 5,
    timelineLimit: 12,
    recommendationLimit: 3,
  },
);

if (recommendations.status === "missing") {
  await emergentic.refreshAgentRecommendations(world.id, {
    simulation_id: simulation.id,
    top_k: 5,
  });
}

async function waitForQuestionJob(jobId: number) {
  for (;;) {
    const job = await emergentic.getQuestionJob(jobId);
    if (job.status === "succeeded") return job.result;
    if (job.status === "failed") {
      throw new Error(job.error_message ?? "Question failed.");
    }
    await new Promise((resolve) => setTimeout(resolve, 2500));
  }
}

const answer = await waitForQuestionJob(questionJob.job_id);

End A Simulation

const simulation = await emergentic.startSimulation(session.simulation.id);
console.log(simulation.status);

const stopHeartbeat = session.startHeartbeat();
try {
  await session.sendMessage("Wrap up the scene.");
} finally {
  stopHeartbeat();
  await session.stop();
}

Realtime Updates

const unsubscribe = session.onConversationMessage((message) => {
  console.log(message.entry);
});

await session.sendMessage("What should I explore first?");

Call unsubscribe() when the request or UI lifecycle ends.

Rate Limits

Public REST endpoints allow 120 requests per minute per API key or client IP. Question submission endpoints additionally allow 30 requests per minute because they enqueue LLM-backed jobs. Polling question jobs counts against the general REST limit, not the question submission limit. Rate-limited requests return 429 with a Retry-After header.

API Docs Smoke Test

EMERGENTIC_API_URL=http://localhost:8000 \
EMERGENTIC_API_KEY=... \
npm --prefix packages/emergentic-client run smoke:api-docs

Use --base-url https://emergentic.ai and --api-key ... to target prod. The default smoke mode is mostly read-only and skips LLM-backed question jobs. Use --include-questions to queue and poll question jobs. Use --mode full --include-realtime when you want create/update fixture coverage and Socket.IO lifecycle coverage before a load test. Full mode can leave created world, agent, location, action, and simulation records.