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

@devicai/sdk

v0.1.1

Published

Official Devic SDK for TypeScript and JavaScript

Readme

@devicai/sdk

The official Devic SDK for TypeScript and JavaScript. Runs on your server.

npm install @devicai/sdk
import { Devic } from '@devicai/sdk';

const devic = new Devic({ apiKey: process.env.DEVIC_API_KEY! });

const reply = await devic.assistants.chat('support-bot', 'where is my order?');

The one idea

devic.* speaks for your workspace. devic.auth(tenantId) speaks for one of your customers inside it.

// As the workspace: what an operator configures.
await devic.assistants.list();
await devic.toolServers.create({ … });
await devic.projects.list();

// On behalf of a customer: what an end user does.
const acme = devic.auth('acme', 'user-7');
await acme.assistants.chat('support-bot', 'where is my order?');
await acme.assistants.conversations.list('support-bot');
await acme.integrations.list('support-bot');
await acme.usage.get();

Everything through a scope carries that customer's identity, so it cannot be left off one call by accident — which is the failure with no symptom: the message goes through, the answer looks right, and the conversation is filed under your workspace instead of under your customer.

And what a customer must not do is simply not reachable from there. There is no acme.toolServers, no acme.projects, no acme.documents.

Tenant sessions — for the browser

Your page needs a credential. Sending it your API key makes the tenant a claim: the key is readable by anyone who opens the network tab, so anyone can say they are any of your customers.

Mint a session instead. From your server, taking the identity from your own login and never from the request body:

app.post('/api/devic-session', requireLogin, async (req, res) => {
  const session = await devic
    .auth(req.user.organisationId, req.user.id)
    .session();

  res.json(session);        // { token, expiresAt, … }
});

Then in your page, with @devicai/ui:

<DevicProvider
  getTenantSession={async () => {
    const r = await fetch('/api/devic-session', { credentials: 'include' });
    return r.json();
  }}
  onSessionExpired={() => location.assign('/login')}
>
  <ChatDrawer assistantId="support-bot" />
</DevicProvider>

A session lasts an hour by default, is confined to what an end user does, and dies with the API key that minted it. It cannot create assistants, read your costs, or reach another customer — whatever the page asks for.

Without a renewal endpoint. You do not have to expose one. Mint the session inside your own login, give it a lifetime matching your session, and put it wherever your page can read it:

const { token } = await devic.auth(user.org, user.id).session({
  ttlSeconds: 8 * 3600,          // up to 12 h
});
res.cookie('devic_session', token, { sameSite: 'lax' });

Simpler, and the only thing it trades is the window if a token is stolen — the same one your own session cookie already accepts. Do set onSessionExpired: there is nothing to renew from, so without it the widget stops answering at the exact moment the user's login has expired too.

Making it compulsory

All of the above is a convention until the key is unable to do anything else. In the Devic console, an API key has an identity mode:

| Mode | What the key can do | |---|---| | open (default) | Anything it is allowed, for whichever tenant it declares beside itself. | | signed | Mint tenant sessions, and nothing else. Every other /api/v1 call with the key alone answers 401. |

Put the SDK's key in signed and the mistake stops being possible: nobody can paste that key into a page and reach a customer's data with it, because the only thing it can do is ask for a token that pins the customer.

const devic = new Devic({ apiKey: process.env.DEVIC_API_KEY! });

await devic.auth('acme', 'user-7').session();   // the one thing it can do
await devic.assistants.list();                  // 401 — and that is the point

Which means a signed key is for exactly this: minting sessions in front of a browser. Anything else your server does — provisioning assistants, reading costs, running agents — needs a second key left on open. Two keys, two jobs.

A session cannot mint another session, so nothing that reaches the page can widen itself back.

What is here

| | | |---|---| | devic.assistants | assistants, chatting, conversations, feedback | | devic.agents | agents, runs, approvals, costs | | devic.toolServers | tool servers and their tools | | devic.projects | projects, their runs and their costs | | devic.documents | knowledge documents, versions, folders | | devic.skills | the skill catalogue, install and uninstall | | devic.integrations | the app catalogue and the workspace's connected accounts | | devic.triggers | starting agents and assistants from app events | | devic.tenantSessions | minting tokens that prove which customer is calling |

And on devic.auth(tenantId, subtenantId?):

| | | |---|---| | .assistants / .agents | the same, with the customer filled in | | .integrations | the apps that customer connected for themselves | | .usage | their limits and what they have consumed | | .session() | the credential for their browser |

Anything not wrapped yet is reachable on devic.client, which is the HTTP client underneath.

Errors

Every failure is a DevicApiError with the status code and the API's own message:

import { DevicApiError } from '@devicai/sdk';

try {
  await devic.auth('acme').assistants.chat('support-bot', 'hola');
} catch (e) {
  if (e instanceof DevicApiError && e.statusCode === 429) {
    // the tenant is over its limit
  }
  throw e;
}

Configuration

new Devic({
  apiKey: process.env.DEVIC_API_KEY!,
  baseUrl: 'https://api.devic.ai',   // default
  source: 'sdk',                     // how the API files this traffic
});

source only matters if you are building a tool on top of this package and want its usage counted apart from your own.

The two remaining options are for callers whose credential is not a static API key but a token that expires — an internal service passing a user's access token, for instance:

new Devic({
  apiKey: accessToken,
  refreshToken: () => renew(),          // called after a 401, then retried once
  shouldRefreshProactively: () => isExpired(accessToken),
});

Licence

MIT