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

@sitegpt/convex

v0.2.0

Published

Convex component for SiteGPT: ask your AI support chatbot from Convex functions and keep its knowledge base transactionally in sync with your Convex tables.

Downloads

337

Readme

SiteGPT Convex Component

npm version Convex Component

Add an AI support agent to your Convex app, powered by SiteGPT.

Two things this component gives you:

  1. Ask your chatbot from server functions. Call sitegpt.ask(ctx, ...) in an action and get the AI answer back, grounded in your chatbot's knowledge base. Build in-app help, ticket deflection, internal tools, or agent workflows without touching the widget.
  2. Transactional knowledge sync. Your product content already lives in Convex tables. Call sitegpt.syncDocument(ctx, ...) inside the same mutation that writes your data, and the chatbot's knowledge base follows automatically: the sync intent commits atomically with your write, a background worker pushes it to SiteGPT with retries, and deleting the row makes the bot forget it. No cron jobs, no drift.

Plus typed helpers for the rest of the SiteGPT API: knowledge ingestion (links, sitemaps, crawls, files, YouTube), conversations, messages, and leads.

Prerequisites

  • A SiteGPT account with a chatbot. No account yet? npx @sitegpt/cli onboarding start <your-website> creates a working preview chatbot without signing up.
  • A SiteGPT API token: create one in the SiteGPT dashboard under Settings, or with sitegpt tokens create.

Installation

npm install @sitegpt/convex

Mount the component and bind the token in convex/convex.config.ts:

import sitegpt from '@sitegpt/convex/convex.config.js'
import { defineApp } from 'convex/server'
import { v } from 'convex/values'

const app = defineApp({
  env: { SITEGPT_API_TOKEN: v.string() },
})

app.use(sitegpt, {
  env: { SITEGPT_API_TOKEN: app.env.SITEGPT_API_TOKEN },
})

export default app

Set the token on your deployment:

npx convex env set SITEGPT_API_TOKEN sgpt_...

Then create the client anywhere in your convex/ functions:

import { SiteGPT } from '@sitegpt/convex'
import { components } from './_generated/api'

const sitegpt = new SiteGPT(components.sitegpt, {
  defaultChatbotId: 'your-chatbot-id', // optional, saves passing it per call
})

Ask the chatbot

The SiteGPT API generates the answer synchronously, so one action call returns the reply:

import { action } from './_generated/server'
import { v } from 'convex/values'

export const askSupport = action({
  args: { question: v.string(), threadId: v.optional(v.string()) },
  handler: async (ctx, args) => {
    const result = await sitegpt.ask(ctx, {
      message: args.question,
      threadId: args.threadId, // omit to start a new conversation
    })
    return { answer: result.answer, threadId: result.threadId }
  },
})

Pass the returned threadId on the next call to continue the same conversation. Conversations show up in your SiteGPT dashboard like any other chat, so escalation, history, and analytics keep working.

Transactional knowledge sync

Keep the chatbot's knowledge in lockstep with a Convex table by syncing in the same mutation that writes it:

export const saveArticle = mutation({
  args: { slug: v.string(), title: v.string(), body: v.string() },
  handler: async (ctx, args) => {
    // ... write the article to your own table ...

    await sitegpt.syncDocument(ctx, {
      key: `articles/${args.slug}`,      // your stable identifier
      name: args.title,                  // display name in the dashboard
      content: `# ${args.title}\n\n${args.body}`,
    })
  },
})

export const deleteArticle = mutation({
  args: { slug: v.string() },
  handler: async (ctx, args) => {
    // ... delete the article from your own table ...
    await sitegpt.removeDocument(ctx, { key: `articles/${args.slug}` })
  },
})

How it works:

  • syncDocument writes a shadow row in the component's own table. Because component mutations join your mutation's transaction, the intent commits atomically with your write. If your mutation throws, nothing is recorded.
  • A scheduled worker pushes the change to SiteGPT right after commit: first push creates a knowledge document, later pushes update it in place (content is re-embedded automatically), removeDocument deletes it.
  • Unchanged content is detected by hash and skipped, so calling syncDocument on every save is free.
  • Failures retry with exponential backoff, doubling from 5s (5s, 10s, 20s, ... up to ~5 minutes between tries). After 8 attempts the row is marked failed and left alone until the next syncDocument, removeDocument, or retrySync for that key.
  • Everything reasserts state on wake-up: if content changes while a push is in flight, or a delete lands mid-create, the worker converges to the latest intent.

Observe sync state live from a query (it is subscribable like any Convex query):

export const articleSyncState = query({
  args: { slug: v.string() },
  handler: async (ctx, args) =>
    sitegpt.getSyncState(ctx, { key: `articles/${args.slug}` }),
})

Limits worth knowing:

  • One synced document holds up to 900 KB of UTF-8 content. Split bigger sources into multiple keys (that also improves retrieval).
  • Pushes run in batches of 10; a bulk import of thousands of documents drains steadily rather than instantly. Deletions are prioritized over content updates so takedowns never wait behind churn.

API surface

All API-backed methods run in actions (they call the SiteGPT REST API). Sync methods run in mutations and queries.

| Area | Methods | | --- | --- | | Chat | ask | | Knowledge sync | syncDocument, removeDocument, retrySync, getSyncState, listSyncStates | | Knowledge ingestion | addLinks, addSitemap, crawlWebsite, addYoutube, uploadFiles, setCustomText | | Knowledge documents | listDocuments, getDocument, updateDocumentContent, deleteDocument, deleteDocuments, resyncDocuments, getDocumentStats | | Conversations | listConversations, getConversation, listMessages | | Leads | listLeads, getLead | | Account | me, usage, limits, listChatbots, getChatbot, getChatbotAnalytics |

You can also call the component actions directly via ctx.runAction(components.sitegpt.knowledge.addLinks, ...) if you prefer not to use the client class.

Token scopes

Create the API token with the scopes for the methods you use:

| Methods | Scope | | --- | --- | | ask | conversations:write | | listConversations, getConversation, listMessages | conversations:read | | syncDocument, retrySync, ingestion methods, updateDocumentContent, resyncDocuments | knowledge:write | | removeDocument, deleteDocument, deleteDocuments | knowledge:delete | | listDocuments, getDocument, getDocumentStats | knowledge:read | | listLeads, getLead | leads:read | | me, usage, limits | account:read | | listChatbots, getChatbot, getChatbotAnalytics | chatbots:read |

A token used for the knowledge sync needs both knowledge:write and knowledge:delete: removeDocument (and the delete half of the sync engine) calls the document delete endpoints.

Error handling

API failures throw a ConvexError whose data carries { kind: 'SiteGptApiError', status, code, message, hint? }. The sync worker catches these itself and records them on the row (lastError), so sync never throws into your mutations after commit.

Testing

The package ships a convex-test helper:

import { convexTest } from 'convex-test'
import sitegptTest from '@sitegpt/convex/test'

const t = convexTest(schema, modules)
sitegptTest.register(t)

Stub fetch to fake the SiteGPT API; see this repo's tests/sync.test.ts for full examples, including the sync state machine.

Example

A runnable example app lives in examples/basic: an articles table mirrored into a chatbot plus an in-app ask endpoint.

FAQ

Does this store my data in Convex? Only the sync engine stores state: one shadow row per synced key. Content is held in the row until pushed, then cleared; a failed row keeps its content so retrySync can push it later. The API wrapper methods store nothing.

Can I sync multiple chatbots? Yes. Every method takes an optional chatbotId; defaultChatbotId is just a convenience.

What happens if SiteGPT is down during a sync? The intent is already durable in the shadow row, so nothing is lost: the worker retries with backoff and converges when the API is reachable again.

Which plans can use this? Any plan with API access. Rate limits follow your SiteGPT plan.

License

MIT