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 devWriting 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 toolsargs— Zod schema, automatically converted to JSON Schema in the manifestpermissions— 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:
- Scans your entry file for
@Tooland@BrowserToolexports - Bundles each tool
- Connects to the dev server via WebSocket
- 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 toolicon.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 configuredEach .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
- Preprocessor extracts
@Tool({...})/@BrowserTool({...})from your source (string-level, before any AST parsing) - Babel strips TypeScript types
- esbuild resolves and bundles all imports into a self-contained IIFE
- Zod schemas are converted to JSON Schema for the manifest
- The host runtime (
@gace/sandbox) provides thesdkobject at execution time
The @Tool and @BrowserTool decorators are no-ops at runtime — all metadata extraction happens at build time.
