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

sowork

v0.4.0

Published

Official TypeScript client for the SoWork API.

Readme

SoWork

The official TypeScript client for the SoWork API.

SoWork is a virtual office for remote teams. This package lets you bring SoWork into your own workflows and automations — read and update presence, look up teammates, trigger in-office actions, send chat messages, and subscribe to webhooks.

Installation

npm install sowork

Usage

Authenticate with an API key generated in your SoWork settings, then call the API:

import { SoWork } from 'sowork';

const client = new SoWork('sw_your_key');

// Read the authenticated user
const me = await client.me.getMe();

// Update your presence in the office
await client.me.updatePresence({
  textStatus: 'Coding',
  availability: 'busy',
});

The client is organized into namespaces that mirror the API: me, users, office, chat, meetings, insights, and subscriptions. See the full reference for every method and its parameters.

Streaming events

Subscriptions deliver events three ways: webhook push to a URL, cursor polling, and live streaming. A subscription created without a url is inbox-only — matched events accumulate in a durable server-side log (72-hour retention) that you consume directly. Nothing to host, no tunnel.

// Create an inbox-only subscription (no url) for the events you care about
const { data: sub } = await client.subscriptions.create({
  events: ['me.presence_updated', 'me.chat_mentioned'],
});

// Stream it. Auto-reconnect with backoff, resume, goodbye handling, an idle
// watchdog, and duplicate suppression are all built in.
await client.streamEvents({
  subscriptionId: sub.id,
  cursor: readCursorFromDisk(), // undefined on first run
  onEvent: async (event, logId) => {
    console.log(event.type, event.data);
    writeCursorToDisk(logId); // the resume cursor
  },
});

Persist logId after each event and pass it back as cursor when your process restarts — the durable log means you catch up losslessly on everything you missed while down.

Routing for centralized apps: every event envelope carries officeId (the office of the subscription it was delivered for) and — when the subscription belongs to an app installation — installationId. An app serving several workspaces should route on event.installationId to pick which installation's credentials to act with (it's the same value you pass as installation_id when minting a token). On webhook deliveries both fields sit inside the signed body, so a verified delivery's routing context can be trusted directly.

Prefer request/response? client.events.listEvents({ subscriptionId, after }) pages the same log with a cursor. And raw HTTP works too: the stream is standard Server-Sent Events on GET /v1/events/stream (resume via the Last-Event-ID header), the poll is GET /v1/events.

For a complete worked example — a bot that connects, streams, replies, and survives restarts — see examples/echo-bot. For the shape a real product takes — webhook delivery, many installations in one process, and an LLM answering from live workspace data — see examples/office-assistant.

Command-line interface

The package also ships a sowork command. Install it globally and authenticate once:

npm install -g sowork
sowork login   # paste an API key generated in your SoWork settings

Then drive the API straight from your shell:

sowork status "Coding"             # set your status text
sowork status --availability busy  # set your availability
sowork users                       # list teammates

Channel and DM listings return top-level messages only, so threads and the files hanging off them need their own commands:

sowork chat messages <channelId>          # top-level messages (replyCount marks threads)
sowork chat replies <messageId>           # the thread: root message + its replies
sowork chat attachment <messageId> <idx>  # short-lived signed URL for one attachment
sowork chat send <channelId> "text" --parent-id <rootId>   # reply inside a thread
sowork chat dm <userId> "text" --with <userId> <userId>    # group DM (up to 8)

Ended meetings and their notes, transcripts, chat and recordings live in the library:

sowork meetings library list
sowork meetings library search "onboarding" --kinds note transcript
sowork meetings library get <digestId> --include notes --include transcript
sowork insights working-hours --from 2026-08-01 --to 2026-08-07

Commands mirror the SDK namespaces (sowork users, sowork chat, sowork meetings, sowork insights, sowork webhooks) - each with subcommands, plus short top-level aliases for the common ones. Run sowork --help or sowork <command> --help to explore.

The CLI takes its API key from --api-key, then the SOWORK_API_KEY environment variable, then the credentials saved by sowork login (in ~/.config/sowork/config.json). Add --json to any command for machine-readable output.

Authenticating as an app

SoWork apps authenticate with their installation credentials instead of an API key. Pass them to the constructor and the client handles the whole token lifecycle for you — it mints short-lived access tokens, caches them, refreshes ahead of expiry, and retries once if a token stops verifying. Your code never sees a token:

import { SoWork } from 'sowork';

const client = new SoWork({
  app: {
    clientId: process.env.SOWORK_CLIENT_ID,
    clientSecret: process.env.SOWORK_CLIENT_SECRET,
    installationId: process.env.SOWORK_INSTALLATION_ID,
  },
});

// Who am I installed as?
const { app, installation, group } = await client.app.getApp();

// Post as the app (thread replies via parentId; the Idempotency-Key
// header makes retried deliveries collapse to one message)
await client.chat.sendChannelMessage(
  channelId,
  { text: 'Hello from my app!' },
  { headers: { 'Idempotency-Key': eventId } },
);

Every resource method works identically across both credential types - the server resolves the principal from the bearer token.

Self-hosted apps bootstrap from a single-use setup token minted in SoWork's "connect your agent" flow: SoWork.exchangeSetupToken('sw_app_setup_…') returns the installation credentials once — persist them like a password, then construct the client with them.

Verifying webhooks

Incoming webhook deliveries are signed. Verify the signature before trusting a payload:

import { verifyWebhookSignature, WebhookSignatureError } from 'sowork';

try {
  verifyWebhookSignature({
    secret: process.env.SOWORK_WEBHOOK_SECRET,
    signatureHeader: request.headers['sowork-signature'],
    rawBody, // the raw, unparsed request body string
  });
  // signature is valid — handle the event
} catch (err) {
  if (err instanceof WebhookSignatureError) {
    // reject the request (e.g. respond 401)
  }
  throw err;
}

Documentation

Full API reference: https://api.sowork.com/public/documentation

License

MIT © Sophya, Inc.