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

gace-sdk

v0.4.1

Published

SDK for building gace AI plugins — zero config, instant hot-reload

Readme

gace-sdk

Build AI-powered tools that run in a sandboxed environment. Write TypeScript, get a standalone bundle.

What This Is

gace-sdk lets you write tool functions in TypeScript, decorate them with @Tool or @BrowserTool, and the CLI handles everything else — scanning, bundling, and packaging into self-contained JS files that run in a sandboxed environment.

The goal: You write business logic. The SDK handles the infrastructure.

Quick Start

pnpm create gace-plugin my-plugin
cd my-plugin
pnpm install
pnpm dev

Writing Server Tools

Server tools run on the backend in a QuickJS sandbox. They can make HTTP requests and process data.

// src/index.ts
import { Tool } from "gace-sdk";
import type { GaceSDK } from "gace-sdk";
import { z } from "zod";

@Tool({
  name: "weather",
  description: "Get current weather for a city",
  args: z.object({
    city: z.string().describe("City name"),
  }),
  permissions: { http: ["wttr.in"] },
})
export async function getWeather(sdk: GaceSDK) {
  const res = await sdk.http.get(
    `https://wttr.in/${encodeURIComponent(sdk.args.city)}?format=j1`
  );
  const data = res.json();
  return `${data.current_condition[0].temp_C}°C`;
}

Each exported function decorated with @Tool becomes a standalone tool:

  • name — globally unique identifier (also used as filename)
  • description — shown to the AI when selecting tools
  • args — Zod schema, automatically converted to JSON Schema in the manifest
  • permissions — declares which URLs the tool can access

GaceSDK Object

Your function receives an sdk object:

| Property | Description | |----------|-------------| | sdk.args | Parsed arguments (typed from your Zod schema) | | sdk.http.get(url, options?) | HTTP GET, returns response with .json() and .text() | | sdk.http.post(url, body?, options?) | HTTP POST | | sdk.http.put(url, body?, options?) | HTTP PUT | | sdk.http.patch(url, body?, options?) | HTTP PATCH | | sdk.http.delete(url, options?) | HTTP DELETE |

All HTTP methods accept an optional options object with headers.

Writing Browser Tools

Browser tools run in the user's browser via the Gace extension. They can interact with web pages — click elements, read content, manage tabs.

import { BrowserTool } from "gace-sdk";
import type { BrowserSDK } from "gace-sdk";
import { z } from "zod";

@BrowserTool({
  name: "get_page_title",
  description: "Get the title of the current page in a tab",
  permissions: { document: true, tabs: true, url: "*.example.com" },
  args: z.object({
    tabId: z.number().describe("The tab to read from"),
  }),
})
export async function getPageTitle(sdk: BrowserSDK) {
  const tab = sdk.tabs.getById(sdk.args.tabId);
  const doc: any = tab.document;
  const title = await doc.querySelector("title");
  return await title.textContent;
}

BrowserSDK Object

| Property | Description | |----------|-------------| | sdk.args | Parsed arguments (typed from your Zod schema) | | sdk.tabs.open(url) | Open a new tab, returns { tab } | | sdk.tabs.getById(id) | Get a tab by ID (has .document for DOM access) | | sdk.tabs.list() | List all available tabs | | sdk.time.sleep(duration) | Wait for a duration (ms number or string like "2s") |

Browser Permissions

| Permission | Description | |------------|-------------| | document | Access the DOM of matching pages | | tabs | Tab management (open, close, list) | | url | URL pattern this tool operates on (e.g. "reddit.com/*") |

Note: DOM interactions are async because they're proxied from the sandbox to the browser extension host. Use await when accessing DOM properties and methods.

Config

Create a gace.config.ts in your project root:

import { defineConfig } from "gace-sdk";

export default defineConfig({
  name: "my-plugin",
  displayName: "My Plugin",
  tagline: "What this plugin does",
  version: "1.0.0",
  icon: "./icon.png",  // normalised to 512x512 WebP at build
});

All fields are included in manifest.json and used by the backend for display.

CLI

gace dev

Starts development mode:

  1. Scans your entry file for @Tool and @BrowserTool exports
  2. Bundles each tool
  3. Connects to the dev server via WebSocket
  4. Watches for file changes and hot-reloads

gace build

Produces a dist/ folder ready for publishing:

  • manifest.json — plugin metadata + tool definitions with JSON Schema
  • <tool_name>.js — one self-contained bundle per tool
  • icon.webp — icon file (if configured)

Options

| Flag | Description | Default | |------|-------------|---------| | --root <dir> | Project root directory | . | | --entry <file> | Entry file path | src/index.ts |

Build Output

dist/
  manifest.json       ← Plugin + tool definitions
  weather.js          ← Standalone bundle (all imports resolved)
  hacker_news.js      ← Each tool is independent
  icon.webp           ← If configured

Each .js bundle is completely self-contained — all imports (including zod) are inlined. The bundle can be executed in an isolated sandbox with no external dependencies.

How It Works

  1. Preprocessor extracts @Tool({...}) / @BrowserTool({...}) from your source (string-level, before any AST parsing)
  2. Babel strips TypeScript types
  3. esbuild resolves and bundles all imports into a self-contained IIFE
  4. Zod schemas are converted to JSON Schema for the manifest
  5. The host runtime (@gace/sandbox) provides the sdk object at execution time

The @Tool and @BrowserTool decorators are no-ops at runtime — all metadata extraction happens at build time.