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

@davepi/agent

v0.1.0

Published

Chat agent that ships pre-wired against a dAvePi backend. HTTP /chat + Slack channels, OpenAI + Anthropic providers, service-account or per-user OAuth-link auth. Consumes the dAvePi MCP server, so ACL/scope/tenant isolation are enforced server-side and th

Downloads

192

Readme

@davepi/agent

A chat agent that ships pre-wired against a dAvePi backend. The agent connects to davepi's built-in MCP server, so every schema's CRUD + relations + aggregations + audit + file ops are available as tools out of the box — and tenant isolation and ACL are enforced server-side, not in the prompt.

What you get

  • HTTP /chat endpoint with Server-Sent Events streaming.
  • Slack bot via @slack/bolt (app mention + DM). Render tools translate to Block Kit tables and QuickChart images.
  • Telegram / WhatsApp / Embeddable widget templates in lib/channels/templates/ — stubs with a one-screen recipe for filling in.
  • OpenAI + Anthropic providers via the Vercel AI SDK. Switch via LLM_PROVIDER.
  • Two auth modes:
    • service — one JWT (or X-Client-Id) for the whole bot. Right for an anonymous storefront widget where every visitor sees the same role-scoped slice.
    • per-user — each channel user maps to a real davepi user via an OAuth-style link flow. Refresh tokens stored locally; access tokens minted on demand and cached.
  • Tool router for backends with too many schemas: above the configured limit (default 40), the agent first picks a resource, then loads that resource's tools.
  • Structured render tools (render_table, render_chart) so the model can ask for a visualization without each channel reinventing layout.

ACL boundary — design rule

The JWT (or X-Client-Id) is the access boundary. The agent never re-implements ACL checks and never tries to constrain results via prompt text. If you want a service-account bot to only see "published" rows, declare a schema.acl.scope[role] filter on the davepi side — the MCP server applies it on every read and the agent never sees the filter itself. The wrong pattern (and the one to avoid) is a broad service token plus "only show user X's data" in the prompt; that's a confused-deputy bug waiting to happen.

Quick start

# In your davepi project, install the agent as a dev tool:
npm install @davepi/agent

# Minimal env:
export DAVEPI_URL=http://localhost:5050
export ANTHROPIC_API_KEY=sk-ant-...
export DAVEPI_BEARER=<long-lived-jwt-issued-by-/login>
# OR for anonymous reads via an apiClient role:
# export DAVEPI_CLIENT_ID=<client-id>

# Start the agent:
npx davepi-agent
# HTTP /chat is now listening on :5060

# Talk to it:
curl -N -X POST http://localhost:5060/chat \
  -H 'content-type: application/json' \
  -d '{"message":"What products do we have?"}'

Configuration (env)

Required:

| Variable | Purpose | | ----------------- | ------------------------------------------------- | | DAVEPI_URL | Base URL of the davepi backend | | LLM_PROVIDER | anthropic (default) or openai | | ANTHROPIC_API_KEY / OPENAI_API_KEY | Provider key |

Service auth (default) — set one of:

| Variable | Purpose | | ------------------ | ------------------------------------------------ | | DAVEPI_BEARER | Long-lived JWT for a davepi user | | DAVEPI_CLIENT_ID | Public client id for anonymous reads |

Per-user auth:

| Variable | Purpose | | -------------------------- | ----------------------------------------------------------------------------- | | AGENT_AUTH_MODE=per-user | Switch on per-user mode | | AGENT_LINK_BASE_URL | Public base URL of the agent itself (where GET /link/:nonce is served) | | AGENT_SESSION_SECRET | Required for HTTP per-user mode — HMAC key for the signed session cookie | | AGENT_COOKIE_SECURE | true (default) — emits Secure on the session cookie. Set false for HTTP-only dev | | STORE_URL | Where to persist refresh tokens. file:./davepi-agent-store.json (default) or memory: |

Persona & memory (optional):

| Variable | Purpose | | ----------------------------------- | ------------------------------------------------------------------------------------------------ | | AGENT_KEY | Which agent this process is (e.g. support). Selects the agentPersona / agentMemory rows used as prompt slots. Unset → built-in default prompt and no persisted snapshot | | AGENT_PERSONA_CACHE_TTL_SECONDS | Per-process cache TTL for the persona lookup (default 60). Set 0 to fetch on every turn (strict immediacy) | | AGENT_PERSIST_CONVERSATIONS | Persist history + the frozen prompt snapshot to davepi's conversation schema (default true). false keeps the channel-managed in-memory round-trip only | | AGENT_SESSION_IDLE_SECONDS | Idle gap after which a returning user is a new session and the snapshot is re-frozen, picking up memory/profile writes from the prior session (default 1800) | | LLM_PROMPT_CACHING | Anthropic prompt caching on the frozen snapshot prefix (default true, Anthropic provider only). false to disable |

Memory & the frozen snapshot

Once AGENT_KEY is set the agent reads a per-tenant memory row (agentMemory, slow-changing facts about the account) and a per-end-user profile row (customerProfile, preferences/notes — shared across the tenant's agents) and folds them into the system prompt alongside the persona. These five slots are snapshotted once at session start and frozen for the whole conversation, so the prefix stays byte-stable and Anthropic prompt caching keeps hitting. The agent self-authors memory/profiles through the schema-generated MCP tools (e.g. update_agentMemory); because the snapshot is frozen, a write takes effect on the next session, never mid-conversation.

Live vs. remembered. Treat memory/profile/persona as slow-changing background that may be slightly stale — never as live system state. For anything that changes (order status, ticket state, inventory, balances), the agent calls a tool to read it fresh. Snapshotted text shapes tone and recall; it is never an access-control or live-data mechanism (the JWT / client id remains the boundary).

Linking flow (per-user mode): on first contact from an unlinked user the agent returns a one-time link URL (<agent>/link/<nonce>). The user opens it, signs in via a small HTML form, and the agent calls davepi's POST /login server-side to obtain the refresh token. The refresh token never crosses the browser. For HTTP-channel users, link completion sets an HMAC-signed davepi_agent_session cookie (HttpOnly, SameSite=Lax) that /chat reads on every subsequent request. /chat ignores any caller-supplied channelUserId — the cookie is the trust boundary.

Channels:

| Variable | Purpose | | ----------------------- | ------------------------------------------------------ | | AGENT_HTTP_ENABLED | true (default) / false | | AGENT_HTTP_PORT | HTTP port (default 5060) | | AGENT_CORS_ORIGINS | Comma-separated allowlist | | SLACK_BOT_TOKEN | Enables the Slack channel when set | | SLACK_SIGNING_SECRET | Required when Slack is enabled (HTTP mode) | | SLACK_APP_TOKEN | App-level token for socket mode | | SLACK_SOCKET_MODE | true to use socket mode | | SLACK_PORT | Slack HTTP port (default 5061) |

Tools:

| Variable | Purpose | | ------------------------- | ---------------------------------------------------- | | AGENT_TOOL_LIMIT | Above this many tools, switch to routed mode (40) | | AGENT_INCLUDE_RENDER | Inject render_table + render_chart tools (true) |

Programmatic use

const { startAgent, runTurn, createAgent } = require('@davepi/agent');

// Start with all configured channels:
await startAgent();

// Or build the pieces and drive runTurn() yourself:
const { config, model, mcpClient, auth } = await createAgent({
  llm: { provider: 'openai', model: 'gpt-4o-mini' },
});
const out = await runTurn({
  config, model, mcpClient, auth,
  channelCtx: { channel: 'my-channel', channelUserId: 'user-123' },
  history: [],
  userMessage: 'Show me last week\'s orders as a chart',
  onEvent: console.log,
});

Slack setup checklist

  1. Create a Slack app at https://api.slack.com/apps.
  2. OAuth & Permissions scopes: app_mentions:read, chat:write, im:history, im:write, users:read.
  3. Event Subscriptions: enable; subscribe to app_mention and message.im.
  4. Install the app to your workspace; copy the Bot User OAuth Token to SLACK_BOT_TOKEN and the Signing Secret to SLACK_SIGNING_SECRET.
  5. For local dev without a public URL, set SLACK_SOCKET_MODE=true and provide SLACK_APP_TOKEN.
  6. Start the agent; @-mention it in a channel or DM it.

Proactive / scheduled agents (cron + attached skills)

Agents don't have to wait to be spoken to. Pair @davepi/agent with davepi-plugin-cron to run a fresh agent on a schedule that follows a named, approved skill (a governed runbook) and posts its output to Slack — follow-ups, SLA digests, end-of-day summaries.

const { createAgent } = require('@davepi/agent');
const cron = require('davepi-plugin-cron');

const agent = await createAgent({ agent: { key: 'support' } });

cron.register('daily-sla-digest', {
  schedule: '0 9 * * 1-5', // 9am on weekdays
  handler: agent.scheduledSkill({
    skill: 'Daily SLA digest',  // name of an *approved* skill for this agentKey
    slackChannel: 'C0123456789', // channel id to post into
    // prompt: 'optional override of the default autonomous preamble',
    // threadTs: 'optional thread to post into',
  }),
});

Each tick:

  1. Loads the named skill through the agent's own MCP identity, filtered to status: 'approved' — a draft/deprecated runbook is never fired.
  2. Runs a fresh runTurn (empty history, no end-user) with the persona loaded and the skill's body inlined as the task. Live data is fetched with tools, not assumed from the snapshot.
  3. Posts the reply (plus any render_table / render_chart output) to Slack.

Tenant scoping is inherited, not re-implemented: the agent's service auth owns exactly one tenant's data, so the skill lookup and the run are tenant-scoped server-side like every other read. For a multi-tenant deployment, register one job per tenant agent (each built with its own auth) or pass an explicit channelCtx.

Service auth is the default and the expectation. A scheduled run has no end-user, so the default cron context has no channelUserId. Per-user auth resolves the agent's identity from the end-user, so a per-user agent is rejected at registration unless you pass an explicit channelCtx with a channelUserId (advanced: a job that acts as one specific linked user).

SLACK_BOT_TOKEN must be set (the poster reuses the bundled @slack/web-api client); the full Slack channel doesn't have to be enabled. A run that produces no output skips the post. Cancellation is cooperative: the cron lease's AbortSignal is threaded into the run — forwarded to MCP tool calls and the model stream — so if the lease is lost mid-run (another node took over), in-flight tool calls and generation stop instead of continuing to write, and nothing is posted (no double-post). Use createScheduledHandler({ agent, ... }) directly if you'd rather not go through agent.scheduledSkill.

Channels not yet shipped

lib/channels/templates/telegram.js, whatsapp.js, and widget.js are checked-in stubs with the recipe for filling them in. They mirror the http.js / slack.js shape — channelCtx, runTurn, render-event translation — so contributing a real one is one file plus deps.

License

ISC