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

@topolo/sdk

v0.23.22

Published

Typed client SDK for the Topolo platform. Used by TopoloCli, TopoloMCP, and third-party agents.

Readme

@topolo/sdk

Typed client SDK for the Topolo platform. Consumed by @topolo/cli and @topolo/mcp, and available to any first- or third-party code that needs to call Topolo APIs on a user's behalf.

Use @topolo/login for Login with Topolo OAuth/OIDC provider integration, token exchange, userinfo, ID-token verification, and local account linking. Use @topolo/sdk after login, when the app already has a Topolo API credential and needs to call Topolo APIs.

This package is the single place where cross-org isolation is enforced on the client side.

Install

npm install @topolo/sdk

Usage

import { createTopolo } from '@topolo/sdk';

const topolo = createTopolo({
  credential: { kind: 'api_key', apiKey: process.env.TOPOLO_API_KEY! },
  agent: {
    clientName: 'my-integration',
    clientVersion: '1.0.0',
    agentName: 'my-backend-service',
  },
});

const me = await topolo.identity.whoami();
console.log(me.organization?.slug);

const actions = await topolo.client.listActions({ service: 'mail' });
const sendMessage = actions.find((action) => action.name === 'messages.send');
if (sendMessage) {
  await topolo.client.callAction(sendMessage.actionId, {
    mailboxId: 'primary',
    subject: 'Hello',
    text: '...',
  }, { confirm: true });
}

Design invariants

No orgId parameter, anywhere

No public method, tool definition, or CLI command in the Topolo agent surface accepts an orgId input. Every request derives the org from the credential — a JWT orgId claim for access tokens, or the organization_id bound to an API key at issuance.

OAuth context is credential-bound: refresh rotation preserves the token's personal/org selection and never re-resolves it from the user's mutable web preference. Separate CLI/MCP credentials can therefore remain in different contexts concurrently.

Saved credential exchange concurrency

The Node saved-OAuth refresh helpers serialize exchanges among cooperating SDK participants using a private acquisition marker, then re-read the store under the lock, so concurrent processes exchange a grant once. Renewals in one process share one exchange. A waiting caller stops after 15 seconds (TOPOLO_CONFIG_LOCK_TIMEOUT_MS), and every Auth exchange made under the lock is bounded to 10 seconds. The lock never removes live, legacy or unknown custody; captured markers stay in private quarantine before cleanup.

A rotated refresh token is written atomically (private temporary file and rename, re-applied if an older unlocked writer changed the file meanwhile) to every slot that still holds the token just sent; the rotated-away token's one-way fingerprint is recorded in retiredRefreshTokens and is never sent again (Auth revokes the grant family on reuse). writeConfig takes the same lock and keeps the stored successor whenever a caller's stale copy holds a retired token.

A grant Auth refuses (invalid_grant or expired_token) is never sent again, by any process: the context's credential is removed, the context is marked in profiles.<env>.reauthorization, and one device authorization per environment

  • context is started and shared by every process until it is approved or expires. Calls then throw TopoloReauthRequiredError (reauth_required) carrying the verification URL, user code and the manual topolo auth login command (never the device code). The pending approval is polled at most once per Auth interval across processes, honouring slow_down; once approved, the new grant is stored atomically and the next call resumes. awaitReauthorization() waits interactively; an approval for a different organization is stored under its own context and reported.

A 429 from Auth during renewal is a retryable TopoloRateLimitError (rate_limited, retryAfter) that keeps the saved grant. Service 429s from TopoloClient.request are also TopoloRateLimitError; idempotent GET/HEAD reads wait and retry at most twice when the wait is 10 seconds or less.

Owned custody is released when the process exits or receives SIGINT, SIGTERM or SIGHUP. A waiter takes over a lock whose single owner marker is at least 2 seconds old and names a PID that no longer exists on this host (ESRCH), under the recovery fence, keeping the dead marker and a nonsecret receipt in a private .credential-reclaim-* quarantine. Legacy/empty/unknown locks and conflict quarantine are retained for separately supported recovery. A waiter or transport timeout is not evidence that an owner's remote exchange had no effects. Writers outside the lock (older SDKs, arbitrary same-user filesystem writes) remain outside this guarantee.

This is enforced at every layer:

  1. @topolo/sdk — no typed method takes an orgId arg.
  2. @topolo/cli — no command takes an --org flag.
  3. @topolo/mcp — no tool schema has an orgId field.
  4. Each Topolo backend app re-derives orgId from the credential on every request and binds it into SQL WHERE clauses.

Write-action gating

TopoloClient rejects POST/PUT/PATCH/DELETE by default unless the call passes { confirm: true }. This prevents an agent from accidentally issuing writes during casual exploration. Disable with requireConfirmForWrites: false only for trusted surfaces.

Audit headers

Every request sends:

  • X-Topolo-Client: <clientName>/<clientVersion>
  • X-Topolo-Agent: <agentName> (if set)
  • X-Topolo-Request-Id: <uuid>

Combined with the credential identity, this makes every platform API call traceable to a specific agent invocation.

Actor provenance

ActorEnvelope is the versioned authority and provenance contract for work performed by humans, Topolo-managed agents, external harness installations, services, and declared browser automation. It always records the accountable subject and actual performer independently and binds them to one organization, task/run lineage, channel, assurance level, action, resource, and canonical input hash.

import {
  parseActorEnvelope,
  sha256CanonicalValue,
  type ActorEnvelope,
} from '@topolo/sdk';

const actionInput = { designId: 'design_123', decision: 'approve' };
const actorEnvelope: ActorEnvelope = parseActorEnvelope({
  schemaVersion: 1,
  organizationId: 'org_acme',
  accountableSubject: {
    principalType: 'human',
    principalId: 'usr_ashley',
    displayName: 'Ashley Lane',
  },
  performer: {
    principalType: 'external_agent',
    principalId: 'installation_codex_macbook',
    installationId: 'installation_codex_macbook',
    provider: 'openai',
    model: 'gpt-5',
  },
  delegation: {
    taskId: 'task_123',
    runId: 'run_456',
    lineage: [
      { principalType: 'human', principalId: 'usr_ashley' },
      { principalType: 'external_agent', principalId: 'installation_codex_macbook' },
    ],
  },
  channel: 'mcp',
  assurance: { level: 'workload_identity', methods: ['oauth_client'] },
  action: {
    requestId: 'req_789',
    actionId: 'app_topolo_design.designs.approve',
    resourceType: 'design',
    resourceId: 'design_123',
    inputHash: await sha256CanonicalValue(actionInput),
  },
  issuedAt: new Date().toISOString(),
});

await topolo.client.callAction(
  actorEnvelope.action.actionId,
  actionInput,
  {
    actorEnvelope,
    resource: { resourceType: 'design', resourceId: 'design_123' },
    confirm: true,
  },
);

The parser rejects unknown fields, unsupported principal types, partial resource bindings, malformed hashes, and non-human performers without a delegation chain. External agents require an installation and provider; managed agents require an agent instance; browser automation requires both an installation and a browser-session identity. X-Topolo-Agent remains useful telemetry but is never trusted as provenance or approval authority. callAction and readAction validate the envelope against the exact action, input and resource before sending its canonical base64url representation in X-Topolo-Actor-Envelope; the envelope request ID also becomes the transport request ID.

Use projectActorAuditEvent when persisting or rendering immutable audit events. The role remains explicit and the friendly label is derived without rewriting either identity:

const executedBy = projectActorAuditEvent(actorEnvelope, 'executed_by');
// executedBy.label === 'Ashley Lane via codex'

The frozen projection retains the accountable subject, actual performer, delegation, channel, assurance, resource, request, action and canonical input hash for consistent application queries and exports.

Modules

| Surface | Methods | | ------------ | ------------------------------------- | | identity | whoami() | | client | listServices(), listActions(), getAction(), callAction() | | app modules | compatibility helpers for stable app-specific contracts |

Agents should use the action catalog for app-specific operations so new services and actions become usable without an SDK, CLI, or MCP release.

Application directory (live registry)

The platform scales to millions of constantly-changing applications, so the SDK ships NO compiled application map that decides behavior. The live credential-scoped registry is the source of truth:

  • TopoloClient.searchServices() / getService() enumerate and resolve applications for the current credential.
  • listApplicationDirectory(client) walks the registry to exhaustion and returns friendly ids derived from live slugs (topolo-notify → notify).
  • resolveApplicationDirectoryEntry(client, application) resolves one app by id, slug, or name — including apps registered after this package was built.
  • friendlyAppId(entry) derives the display id from a live catalog entry.

DEFAULT_APP_URLS contains only the auth and developers bootstrap seeds — the endpoints required before the registry can be consulted. If live discovery is unavailable, directory APIs fail closed with application_catalog_unavailable; the public SDK never substitutes compiled first-party metadata.

Action catalog

TopoloClient.listActions() reads TopoloAuth's live action catalog for the current credential. Applications publish their action definitions through Topolo Developers, and Auth returns only actions whose existing service permission is granted to the credential. Actions with input-dependent policy use authorizationMode: runtime; they remain typed and discoverable while the target service evaluates the supplied organization, application, and resource against current RBAC on every call. The action entry includes the service, HTTP method/path, authorization mode, required permission when static, input/output schemas, read-only and destructive hints, and the stable MCP/agent tool name.

TopoloClient.callAction(actionRef, input, options) resolves an action by actionId, name, or toolName, maps path parameters and query/body values from input, and routes the request through the same auth, audit-header, and write-confirmation flow as request().

TopoloClient.uploadAction(actionRef, bytes, input, options) requires an agentContract.upload profile. inline-base64 declares the content, MIME-type, and file-name input fields; the SDK validates and base64-encodes the file before calling the action. url-session declares the initiation fields, response path for the returned URL, upload method, and optional completion action mapping; the SDK initiates, uploads the raw bytes, and completes the session in one call. Both profiles declare a maximum byte length and accepted MIME patterns, and use the same authorization and confirmation path as callAction(). TopoloClient.uploadActionBase64() provides the same contract for MCP and other transports that already hold canonical base64. completionInput supplies any additional fields required by a declared URL-session completion action.

Native dashboard widgets

Every marketplace application populates Topolo One through its own GET /api/widget endpoint. The SDK owns the complete, versioned payload contract; Topolo One never assumes how many applications or widgets a credential can discover.

import {
  createTopoloWidgetResponse,
  defineTopoloStatsWidget,
} from '@topolo/sdk';

return Response.json(createTopoloWidgetResponse({
  appId: '<runtime-app-id>',
  serviceName: 'Example',
  widgets: [
    defineTopoloStatsWidget({
      id: 'example.open-items',
      version: '1.0.0',
      appId: '<runtime-app-id>',
      serviceName: 'Example',
      lastUpdated: new Date().toISOString(),
      presentation: {
        title: 'Open items',
        contexts: ['workspace'],
        supportedSizes: ['compact', 'medium'],
        defaultSize: 'compact',
      },
      resource: {
        resourceType: 'workspace',
        requiredPermissions: ['app_example.items:read'],
      },
      state: { status: 'ready', observedAt: new Date().toISOString() },
      refresh: {
        intervalSeconds: 60,
        timeoutSeconds: 5,
        maxAgeSeconds: 60,
        staleWhileRevalidateSeconds: 300,
      },
      configuration: {
        allowMultipleInstances: true,
        fields: [],
        defaults: {},
      },
      drilldowns: [],
      controls: [],
      localization: { locale: 'en', titleKey: 'widgets.openItems.title' },
      accessibility: { label: 'Open items' },
      metrics: [{
        key: 'open_items',
        label: 'Open items',
        value: 3,
        unit: 'count',
        semantic: 'gauge',
        aggregation: 'last',
        period: {
          start: '2026-08-29T00:00:00.000Z',
          end: '2026-08-30T00:00:00.000Z',
          timezone: 'UTC',
        },
        dimensions: [],
      }],
    }),
  ],
}));

Successful responses must contain at least one complete widget. Represent legitimate no-data with the widget's explicit empty state; an empty widget array is a conformance failure. Actions use published actionId bindings with their confirmation, effects, verification, and recovery contract. Use validateTopoloWidgetResponse() in endpoint and marketplace conformance tests to prevent app-local payload drift.

Escape hatch

await topolo.client.request({
  service: 'crm',
  method: 'GET',
  path: '/api/some/untyped/endpoint',
});

Still routes through auth + audit headers. Mutating methods still require confirm: true.

Errors

| Class | Thrown for | | ------------------------ | ---------------------------------------- | | TopoloAuthError | 401 responses, missing credentials | | TopoloPermissionError | 403 responses (includes .required) | | TopoloHttpError | Other non-2xx responses | | TopoloSdkError | Base class |

Service URL overrides

Defaults target production. Self-hosted installations may override a service:

createTopolo({
  /* ... */
  serviceUrls: {
    mail: 'https://mail.example.com',
  },
});

Or via env: TOPOLO_SERVICE_URL_MAIL=... (falls through resolveServiceUrl).

Validate published action contracts

enrichActionContract marks examples that need prior resource discovery with requiresDiscovery: true and omits their input. Do not execute such an example as an empty request. Discover the required IDs through the published read actions, then validate and plan the concrete input.

import { validateTopoloActionContract, actionContractCompleteness } from '@topolo/sdk';

const validation = validateTopoloActionContract(action, representativeHandlerResponses);
// validation.errors identifies invalid examples, output shapes and resource paths.
// validation.requiresDiscovery distinguishes prerequisites from runnable examples.
const completeness = actionContractCompleteness(action, representativeHandlerResponses);

Declared examples are checked against inputSchema. Representative responses are checked against outputSchema, then the declared agentContract.resource.idPath (e.g. $.data.id, $.data.items[].id, or $[].id) must resolve to nonempty string identifiers. Empty collections are valid. Unsupported paths and missing or invalid identifiers fail closed. Without representative responses, validation checks example inputs and resource metadata syntax only; it does not prove handler output. Authoritative response schemas and resource paths belong in the application contract.

When a response contains no resource identifier, declare that explicitly:

resource: {
  kind: 'workspace-archive',
  identifierSource: 'none',
  explanation: 'The archive handler returns an empty response; verify through the workspace read action.',
}

This declaration requires a nonempty explanation and prohibits idPath and versionPath. Representative responses still must match outputSchema, including the SDK's empty-string representation of an empty HTTP response. Actions returning identifiers may declare identifierSource: 'response' and must prove their rooted identifier path against representative responses.

Shared action input validation supports uri, email, date-time and uuid formats and JSON Schema if/then/else assertions, including boolean branches. Conditional output requirements are also checked when proving handler responses. UUIDs use the case-insensitive hexadecimal 8-4-4-4-12 representation defined in RFC 9562; application-specific version constraints remain authoritative on the server. Unknown formats fail closed rather than being ignored.

Action-contract enrichment preserves an explicit outputSchema: { type: 'null' } and its declared output: null example. A no-body action such as Sign workspace archival must not acquire a derived object response merely because it exposes no response fields. This metadata preservation does not execute the action or prove that a caller may archive a workspace; its published runtime contract remains authoritative.

Returned action credentials, including each item of backupCodes and recoveryCodes arrays, use opaque references when a protected action secret store is configured. Without a store they are redacted. Pass a reference unchanged into the next published approved action; do not extract the code into logs or messages. Recovery-code protection does not provide a human recovery-code delivery interface or prove that a recipient received and secured the codes.

Operator action audiences

Action discovery preserves principalAccess, allowedServices and accessPolicy. The explicit platform_admin_or_service and platform_super_admin_or_service variants require runtime authorization and a non-empty declared service list. Existing platform-only variants remain human-only.

accessPolicy.human can bind discovery and execution to an operator organization and an explicit delegated permission union, with an optional verified platform-authority alternative. accessPolicy.service.scopeAnyOf declares the accepted service scopes; caller allow-lists and resource/subject checks remain required. The shared validator and evaluator fail closed for malformed policies. Publication digests include these constraints.

Nonsecret configuration-lock diagnostics and reviewed recovery

The Node subpath exports inspectConfigLock() and reconcileConfigLock(). Diagnostics resolve the current invocation's config path and read only lock metadata and at most one bounded private owner marker. They never read config contents or contact Auth. A private empty directory is UNKNOWN; its age and an absent PID do not establish a dead owner or a completed token exchange.

reconcileConfigLock({ expectedGeneration, review }) supports one explicitly reviewed, quiescent empty legacy initialization namespace. The review uses kind: 'quiescent-empty-quarantine', the exact resolved configPath and diagnostic generation, a 64-character quiescenceEvidenceSha256, an owner reviewedBy identifier, and an expiresAt no more than 15 minutes ahead. The config owner/CLI must obtain existing repair authority and record current writer quiescence, including legacy SDK, MCP and bare writers, before calling. The SDK validates the review's scope/freshness; it does not authenticate a reviewer or independently prove that all external writers have stopped. This is an operator-controlled local filesystem capability, not an authority or credential grant. The request must remain unchanged through execution.

The operation acquires an exclusive recovery fence, revalidates the exact private UID/mode/directory generation, captures the whole empty directory into a private quarantine, and checks captured identity/emptiness again. The original directory and a nonsecret receipt remain preserved. A successful QUARANTINED_UNKNOWN_EMPTY result retains the original UNKNOWN classification; it does not prove owner death or settle any earlier remote OAuth effect. UNRESOLVED_CAPTURE retains the quarantine and fence and requires owner resolution; it is not successful recovery. Preguard/fence acquisition conflicts throw state_conflict; a closure conflict after capture returns UNRESOLVED_CAPTURE with the private receipt paths and fence retained. No recursive deletion, force, credential update or renewal occurs. There is no automatic restore over a newer namespace: portable Node directory rename is not a no-replace operation or an OS compare-and-swap. Rollback needs a separately qualified exact owner action; unresolved capture must remain fenced and preserved until that action is admitted.

Corrected renewal initializers honor the recovery fence before and after allocation and during owned checks. These checks protect cooperating adopted SDK participants. Older running SDKs, bare writeConfig, arbitrary same-user writers and non-adopted consumers remain outside that guarantee and require explicit quiescence. Never use this API to clear a marked/live renewal, ambiguous nonempty lock, symlink, permissive/wrong-owner namespace, or an unreviewed current default lock. Installation and runtime adoption plus the exact live recovery scope remain separate from this source capability.

Review inputs must contain exactly the six own primitive string data fields listed above; extra fields, accessors and coercible objects are refused without invoking their getters or coercion. Receipts persist an immutable snapshot of those validated fields, so later caller mutation cannot add private evidence.

Required outcome.json persistence completes while the exclusion fence is still held. It records CAPTURE_VERIFIED_PENDING_FENCE_CLOSURE with fenceClosureVerified: false; this prepared receipt never claims a successful close. An outcome open/write/fsync failure returns UNRESOLVED_CAPTURE and retains the actual fence and quarantined beforeimage. Only the subsequent successful API result plus the caller's native read-back proves fence closure. No required outcome IO occurs after the fence is removed.