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

@useatlas/twenty

v0.0.6

Published

Atlas Twenty CRM action plugin (upsertPerson, createNote, sales-form normalization)

Readme

@useatlas/twenty

Atlas action plugin for Twenty CRM. Ships two agent-callable actions:

  • upsertTwentyPerson — upsert a Person by email with sticky first/last source attribution.
  • stampStripeCustomerId — stamp atlasStripeCustomerId on a Person matching email (CONVERSION source). Use from a Stripe webhook handler.

Required Twenty setup

Before deploy, three required custom fields must be created on the Person object in Twenty. All three are hard requirements — if any is missing at boot, the Atlas SaaS wiring (/ee/src/saas-crm/) flips SaasCrm.available to false and every downstream demo / signup / conversion dispatch becomes a no-op (no dead outbox rows). The startup verification logs the exact missing-field list and create-instructions.

| Field name | Type | Required? | Purpose | | ----------------------- | ------- | --------- | ----------------------------------------------------------------------------------------------------- | | atlasFirstSource | Text | Yes | Sticky first-touch attribution — set once on a Person, never overwritten thereafter (e.g. DEMO). | | atlasLastSource | Text | Yes | Most recent touch — overwritten on every dispatch (e.g. DEMO, SIGNUP, SALES_FORM, CONVERSION). | | atlasStripeCustomerId | Text | Yes | Stripe customer.id of a paying customer. Stamped by stampStripeCustomerId on conversion. | | atlasIp | Text | No | Client IP on demo gate submissions. Optional — absence is non-fatal (the field is written if present, silently skipped otherwise). |

Create the required fields under Settings → Data Model → Person → + Add Field in the Twenty UI. Verification runs once at boot via the Twenty metadata GraphQL endpoint (/metadata).

Install (self-hoster)

Self-hosted operators have two ways to configure per-workspace Twenty credentials. Both are workspace-scoped — credentials live in twenty_integrations (or in your atlas.config.ts plugin block). The TWENTY_API_KEY environment variable is NOT consulted by any plugin install (see "What about TWENTY_API_KEY?" below).

Option 1: Admin UI (recommended)

Navigate to Admin → Integrations → Twenty in your Atlas deployment and submit:

  • Base URL — your Twenty instance hostname (e.g. https://crm.example.com). Required, no default — the form will NOT auto-fill https://crm.useatlas.dev (that's Atlas's own Twenty).
  • API key — bearer token from Twenty → Settings → API & Webhooks.

The key is encrypted at rest in the twenty_integrations table (AES-256-GCM via Atlas's F-41 selective-field encryption). Deleting the row makes subsequent plugin actions fail with an actionable TwentyCredentialError until the row is restored — there is no env fallback.

Option 2: atlas.config.ts

bun add @useatlas/twenty
import { defineConfig } from "@atlas/api/lib/config";
import { twentyPlugin } from "@useatlas/twenty";

export default defineConfig({
  plugins: [
    twentyPlugin({
      // Operator-supplied secret threaded into the plugin config. Name
      // the env var after the workspace so multi-workspace deployments
      // stay readable. Atlas does NOT read TWENTY_API_KEY here — that
      // var is platform-only (see below).
      apiKey: process.env.MY_TWENTY_API_KEY!,
      baseUrl: "https://crm.example.com", // required — point at your own Twenty
    }),
  ],
});

The plugin exposes two agent tools:

  • upsertTwentyPerson — takes { email, eventSource, firstName?, lastName?, atlasIp? } and upserts the Person by emails.primaryEmail. The first/last source rule is enforced inside TwentyClient.upsertPerson — callers pass a single eventSource.
  • stampStripeCustomerId — takes { email, stripeCustomerId } and stamps atlasStripeCustomerId on the matching Person (creates a new Person with atlasFirstSource = "CONVERSION" if none exists). Self-hosters with their own Stripe + Twenty wiring can call this from their own webhook handler.

What about TWENTY_API_KEY?

TWENTY_API_KEY (and optional TWENTY_BASE_URL) are platform-only: they configure Atlas's own SaaS lead-capture pipeline (ee/src/saas-crm/), which POSTs demo / sales-form / signup events into Atlas's CRM at crm.useatlas.dev.

No plugin install — customer workspace, or Atlas's own team workspace on app.useatlas.dev — reads from these env vars, even as a fallback. This split (#2850) prevents two leak scenarios structurally:

  • A customer install with a missing apiKey cannot silently route writes through Atlas's operator key.
  • A future change in ee/src/saas-crm/ cannot accidentally read a customer workspace's twenty_integrations row.

The scripts/check-twenty-resolver-imports.sh CI gate enforces that only ee/src/saas-crm/ may import resolveOperatorCredentials from this package.

Atlas SaaS wiring

Atlas SaaS (app.useatlas.dev) does NOT register this plugin via atlas.config.ts. Instead, /ee/src/saas-crm/ consumes TwentyClient directly through the SaasCrm Effect Tag with operator credentials resolved from TWENTY_API_KEY env. Self-hosters that want their workspaces to fire upsertTwentyPerson / stampStripeCustomerId from their own webhook handlers wire the actions themselves via this plugin's action API.

Atlas's own team workspace on app.useatlas.dev, when it needs Twenty as an action layer, installs the plugin via Admin → Integrations → Twenty just like any other workspace.

The SaaS-side conversion stamping (SaasCrm.stampConversion) fires only on actual payment, not at trial start. All paid plans ship with a 14-day freeTrial, so onSubscriptionComplete fires with subscription.status === "trialing" at checkout completion — stamping then would overcount unpaid trials as paid conversions in funnel queries. The two real "paid" signals, both wired in packages/api/src/lib/auth/server.ts:

  1. onSubscriptionComplete — when subscription.status === "active" (a paid plan without a trial, or a trial that completed instantly).
  2. onSubscriptionUpdate — when the underlying Stripe event is customer.subscription.updated and previous_attributes.status === "trialing" with current status === "active" (i.e. the customer just paid their first post-trial invoice).

Each path retrieves the Stripe customer's email and enqueues a stamp-conversion row into crm_outbox for durable dispatch by the scheduler-backed flusher.

Config

| Field | Type | Default | Description | | ----------- | ---------- | ------- | ---------------------------------------------------------- | | apiKey | string | — | Bearer key from Twenty Settings → API & Webhooks (secret). | | baseUrl | string | — | Required Twenty REST base URL (e.g. https://crm.example.com). | | timeoutMs | number? | 10000 | Per-request timeout. |

apiKey is marked secret: true for F-41 selective-field encryption when configured via the admin UI.

API endpoints used

| Operation | Method | URL | | -------------------- | ------ | ---------------------------------------------------------------- | | Find Person by email | GET | /rest/people?filter[emails.primaryEmail][eq]=<email>&limit=1 | | Create Person | POST | /rest/people | | Update Person | PATCH | /rest/people/<id> | | Metadata probe | POST | /metadata (GraphQL — REST has no per-object fields endpoint) |

Custom fields live INLINE on the Person record on both read and write — not under a customFields wrapper. Verified against Twenty's REST docs (companies PATCH writes annualRecurringRevenue as a top-level field).

Testing rules — IMPORTANT

Do NOT run integration tests against crm.useatlas.dev from CI. The Atlas company Twenty instance carries real prospect data — accidental writes during a test loop would pollute the lead funnel. All __tests__/ here use mocked fetch responses. The repo's bun run test:others target invokes only the fixture-based suites; there is no live-integration target by design.

Manual smoke-tests against the SaaS Twenty instance happen before each release of the SaaS-side wiring (/ee/src/saas-crm/) — not automated.

Reference