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

timmy-sdk

v0.3.0

Published

Shared types and plugin interface contracts for Timmy

Readme

timmy-sdk

The plugin contract for Timmy. A Timmy plugin is a small package that exports a default TimmyPlugin — a named bundle of tools the assistant (and the models it routes to) can call. The SDK is intentionally zero-dependency and framework-free: your plugin depends only on timmy-sdk.

Quick start

import { PLUGIN_API_VERSION, type TimmyPlugin } from 'timmy-sdk'

const plugin: TimmyPlugin = {
  apiVersion: PLUGIN_API_VERSION, // required — the contract version you built against
  name: 'my-plugin', // kebab-case, see naming rules below
  version: '0.1.0',
  // Optional: declare the credential keys your tools may read (least-privilege).
  credentials: [{ key: 'api_token', label: 'API token', type: 'secret' }],
  tools: [
    {
      name: 'greet',
      description: 'Say hello to a name.',
      riskLevel: 'safe', // 'safe' | 'confirm' | 'blocked'
      parameters: {
        type: 'object',
        properties: { name: { type: 'string', description: 'who to greet' } },
        required: ['name'],
      },
      execute: async (args, ctx) => {
        // ctx = { credentials, signal, platform }
        const token = await ctx.credentials.get('api_token') // null unless declared above
        return { ok: true, data: `hello ${args.name} (on ${ctx.platform})` }
      },
    },
  ],
}

export default plugin

Build to a self-contained dist/index.js (e.g. with tsup) and install: timmy plugin install ./my-plugin or timmy plugin install github:you/my-plugin.

The contract

apiVersion (required)

Set apiVersion: PLUGIN_API_VERSION. The host accepts a supported range and skips incompatible plugins with a clear log — it never crashes. A plugin that declares no apiVersion is currently still loaded with a deprecation warning (migration runway); don't rely on that — always set it.

Naming rules (enforced at load; a violating plugin is skipped)

These exist because a tool's name is sent verbatim to cloud model providers (OpenAI, DeepSeek, Anthropic, Gemini) as a function name, and those reject anything outside ^[a-zA-Z0-9_-]{1,64}$.

  • Plugin name — kebab-case only: ^[a-z0-9-]+$ (e.g. my-plugin, omat-workflow). No uppercase, spaces, dots, or colons. The name also prefixes your credential namespace.
  • Tool name^[a-zA-Z0-9_-]+$ and must not contain __ (double underscore).
  • Model-facing name — the host exposes each of your tools to the model as <plugin>__<tool> (e.g. my-plugin__greet). The composite must fit in 64 chars; an over-long tool is dropped. Two installed plugins with the same name → the first wins, the later is skipped.

Risk levels

  • safe — runs without a prompt.
  • confirm — the user is asked to approve before it runs (surfaced inline in timmy chat).
  • blocked — never runs (declare-but-disable).

Credentials (least-privilege)

Declare keys in credentials[]. At runtime ctx.credentials.get(key) resolves only keys your plugin declared, stored in the OS keychain under "<plugin>:<key>". Undeclared keys (and another plugin's keys) always return null.

ToolContext

Every execute(args, ctx) receives:

  • ctx.credentials.get(key) — scoped credential lookup (above).
  • ctx.signal — an AbortSignal that fires when the turn is cancelled; honor it for long work.
  • ctx.platform'mac' | 'windows' | 'linux', so cross-platform plugins can branch without importing any OS package.

Tool result

Return { ok: true, data?: unknown } on success, or { ok: false, error: string } on failure. Report failure honestly — if you can't do the requested thing, return ok: false with a reason rather than a bare ok, so the assistant doesn't claim a success that didn't happen.

Resilience

One bad plugin never blocks the rest or crashes Timmy: import errors, schema violations, incompatible apiVersion, illegal names, and name collisions are each logged and skipped.