Agent Native

build-sculptor-extension

Build or modify Sculptor extensions, which are runtime ES modules that add panels, overlays, widgets, home views, or settings UI to the Sculptor frontend.

by ImbueOfficial publisherNot reviewed

Identity proven by GitHub's verified organisation badge (imbue-ai)

Agents
Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode
License
MIT (permissive)
Pin
f847102pinned Oct 9, 2026
Context cost
about 3,300 tokenswhen the skill loads
GitHub stars
236on imbue-ai/sculptor
Scripts
2 scripts

What it does

“Build or modify a Sculptor extension — a runtime ESM module loaded into the Sculptor UI. Use when asked to write an extension (or "plugin") for Sculptor, add a panel, overlay, workspace widget, home view, or settings UI to Sculptor, or to iterate on an extension with `sculpt extension`. Works from any repo; this file is a self-contained reference (the Sculptor source code is optional).”
The author's description, from SKILL.md

Generated summary from the skill's own text

Build or modify Sculptor extensions, which are runtime ES modules that add panels, overlays, widgets, home views, or settings UI to the Sculptor frontend. Extensions ship as separate files loaded at startup, require only a manifest.json and an entry module with an activate function, and can be written without a build step using plain JavaScript or built with TypeScript and JSX via Vite.

When to use it

  • User asks to create or modify a Sculptor extension or plugin
  • Add a custom panel, overlay, workspace widget, or settings UI to Sculptor
  • Iterate on an extension using sculpt extension load and reload commands
  • Extend Sculptor functionality without modifying the core application

When not to

  • Building the Sculptor app itself or modifying core UI components
  • User needs to access Sculptor source code or run a dev build
  • Extensions must be published to npm or used outside Sculptor

Install

These commands install from the publisher's repository and need no account.

skills CLI

npx skills add imbue-ai/sculptor --skill build-sculptor-extension

Installs from the repository's default branch, which may have changed since the commit we checked.

GitHub CLI 2.90 or later

gh skill install imbue-ai/sculptor build-sculptor-extension@f847102ada1d6896bf6a3a5eccde996760526d9e

Installs the commit we checked (f847102).

Copy by hand

Download the folder at f847102 and put it in .claude/skills/build-sculptor-extension for Claude Code and in .agents/skills/build-sculptor-extension for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode.

Skill folder on GitHub at f847102

Copy for your agent

A prompt for any coding agent. It installs this skill from GitHub at the pinned commit, copies the files as they are, and runs nothing unless you ask.

Prompt

Install the agent skill "build-sculptor-extension" by Imbue.

Source, pinned to commit f847102ada1d6896bf6a3a5eccde996760526d9e:
https://github.com/imbue-ai/sculptor/tree/f847102ada1d6896bf6a3a5eccde996760526d9e/sculptor/sculptor-plugin/skills/build-sculptor-extension

1. Download that folder at exactly this commit. Do not use the default branch.
2. Copy every file unchanged, keeping the folder structure, into .claude/skills/build-sculptor-extension/ for Claude Code and .agents/skills/build-sculptor-extension/ for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode. Create the folders if they do not exist.
3. Do not edit the files, and do not run any script in the folder unless I ask you to.
4. When you are done, list the files you copied and where you put them.

Listing and checks: https://www.agentnative.dev/skills/imbue/build-sculptor-extension

SKILL.md at the pin

---
name: build-sculptor-extension
description: |
  Build or modify a Sculptor extension — a runtime ESM module loaded
  into the Sculptor UI. Use when asked to write an extension (or "plugin")
  for Sculptor, add a panel, overlay, workspace widget, home view, or
  settings UI to Sculptor, or to iterate on an extension with
  `sculpt extension`. Works from any repo; this file is a self-contained
  reference (the Sculptor source code is optional).
---

# Build a Sculptor extension

An extension is a runtime ES module the Sculptor frontend imports at load
time. It is **not** built into the app — it ships as its own files and is
loaded live. Everything you need is in this file plus the `sculpt` CLI; you do
**not** need the Sculptor source code or a dev build of Sculptor (see the last
section for when they help).

An extension is a directory containing `manifest.json` plus an ESM entry
module that default-exports an `activate(api)` function.

Two files ship next to this SKILL.md (resolve them against this skill's base
directory):

- `sdk.d.ts` — the **authoritative, generated** contract of
  `@sculptor/extension-sdk`: every hook, component, and registration option
  with its doc comments. Read it before writing extension code; the prose
  below only summarizes it.
- `example/` — the quick-start extension below as ready-to-load files
  (Sculptor's e2e tests load this exact directory, so it is known-good).

## Quick start (no build step)

```json
// manifest.json
{ "id": "hello", "name": "Hello", "version": "0.1.0", "entry": "main.js", "sdkVersion": "^1.0.0" }
```

```js
// main.js — hand-written ESM, imported directly by the host (no bundler)
export default function activate(api) {
  const el = document.createElement("div");
  el.textContent = "hello from an extension";
  el.style.cssText = "position:fixed;bottom:16px;right:16px;pointer-events:auto;";
  document.body.appendChild(el);
  return () => el.remove(); // disposer — REQUIRED cleanup on unload/reload
}
```

```bash
sculpt extension load <this-skill's-base-dir>/example   # package + load into the live UI
sculpt extension inspect hello                          # see what it registered
```

## Prerequisites — the two settings toggles

Both live in **Settings → Extensions** in the Sculptor UI; only the user can
flip them:

- **Extensions** (`enable_extensions`, default **on**) — master switch for
  the extension runtime.
- **Agent extension loading** (`allow_agent_extension_loading`, default
  **off**) — gates the mutating CLI ops (`load`, `reload`, `unload`,
  `remove`). Read-only ops (`list`, `inspect`, `dir`) always work.

If a mutating command fails with HTTP 403 / `agent_extension_loading_disabled`,
ask the user to enable "Agent extension loading" under Settings → Extensions,
then retry. If no window responds at all, Sculptor isn't running or extensions
are disabled.

## Dev loop — `sculpt extension`

(See the `sculpt-cli` skill for general `sculpt` usage; all commands accept
`--json` and infer the workspace from `SCULPT_WORKSPACE_ID`.)

| Command | What it does |
|---|---|
| `sculpt extension load <dir\|manifest.json\|url> [--persist]` | Package the directory (or register the URL) and load it into the live UI — run after every edit |
| `sculpt extension reload <id>` | Re-import from the extension's **current source** with cache-busting. Does NOT re-package local file edits — after editing a path-loaded extension, run `load` again; reload only helps when the source itself serves fresh files (e.g. a dev-server URL) |
| `sculpt extension inspect <id>` | One extension's live status, registrations, and config **key names** (values are never shown) |
| `sculpt extension list` | All extensions (builtin / installed / url / dev) with live status per window |
| `sculpt extension unload <id>` | Unload from the UI; files stay on disk |
| `sculpt extension remove <id>` | Unload + delete the workspace's dev install (idempotent; leaves permanent installs alone) |
| `sculpt extension dir` | Print the backend's extensions directory (e.g. `~/.sculptor/extensions`) |

`load` mechanics worth knowing:

- A local path is packaged and uploaded (dotfiles, `node_modules`, and
  `__pycache__` are skipped; 5 MB encoded limit) to a **workspace-scoped dev
  install** at `<extensions-dir>/dev/<workspace-id>/<extension-id>/`.
  Re-loading wipes and rewrites that directory.
- `--persist` installs to the top-level `<extensions-dir>/<extension-id>/`
  instead — a permanent install that survives after this workspace is gone.
  URLs are always persistent sources; `--persist` has no effect on them.
- The extension `id` must be a single safe path segment: no `/` or `\`, not
  exactly `.` or `..` (dots inside an id like `foo.bar` are fine), and not the
  reserved name `dev`.
- `load` and `reload` wait for the extension to **settle**: `load: OK` means
  it reached `status: loaded` (manifest fetched, validated, imported,
  activated). A failure at any phase (`manifest`, `validate`, `import`,
  `activate`, or the catch-all `load`) prints
  `load: FAILED` with the phase and error message and exits non-zero. Errors
  thrown *after* activation (e.g. inside a component render) surface in the
  UI's per-extension error boundary instead — check `inspect` and the UI when
  something registered but doesn't render.

Typical loop: `load` → edit → `load` again → … → `remove` (cleanup) or
`load --persist` (keep it installed). Do not reach for `reload` in this loop —
it re-imports the previously uploaded copy, so your edits won't show up.

Manual no-CLI route: drop the extension directory into
`<extensions-dir>/<extension-id>/` (path from `sculpt extension dir`) and
click the **Refresh** button in Settings → Extensions.

## manifest.json

All five fields are required non-empty strings; there are no optional fields:

| Field | Meaning |
|---|---|
| `id` | Stable identifier — used in CLI commands, registration namespaces, and the settings localStorage prefix. Safe path segment, not `dev`. |
| `name` | Display name in Settings → Extensions |
| `version` | Informational; shown in the UI, not validated |
| `entry` | ESM entry file, relative to the manifest |
| `sdkVersion` | Semver range; only the **major** must match the host's SDK major, which is `1` — use `"^1.0.0"` |

## The entry module — `activate` and disposers

```ts
type ExtensionActivate = (api: ExtensionHostApi) => void | (() => void) | Promise<void | (() => void)>;
```

- The host imports the entry and calls its **default export** with the API.
- Return a **disposer** that undoes every module-level side effect (DOM nodes,
  event listeners, timers, injected `<style>` tags). Disposers must be
  synchronous. Registrations made through `api.register*` each return their
  own undo function — compose them into the disposer you return.
- If `activate` throws or rejects, the extension lands in `error` status with
  phase `activate`, any registrations it already made are rolled back, and
  other extensions are unaffected.
- On reload the disposer runs, then the module is re-imported (cache-busted)
  and re-activated.

## The host API and SDK — read `sdk.d.ts`

The full typed contract — `ExtensionHostApi`, every registration option shape,
every hook signature, and their doc comments — is in the generated **`sdk.d.ts`
next to this file**. It is authoritative; read it rather than trusting any
summary. What it contains, in one breath:

- `api.registerPanel` / `registerSettings` / `registerOverlay` /
  `registerWorkspaceWidget` / `registerHomeView` — each returns an undo
  function; registering twice with the same `id` replaces the previous
  contribution; every component renders inside a per-extension error boundary
  and receives **no props**.
- Hooks: `useCurrentWorkspace` (selector form available), `useWorkspaces`,
  `useWorkspaceTasks`, `useNavigateToWorkspace`, `useOpenNewWorkspaceModal`,
  `useExtensionSetting`, `useExtensionSettings`, `useSetExtensionSetting`.
- Components and actions: `Markdown`, `PanelHeader`, `openExternal`.

Semantics that matter beyond the types:

- **Workspace scope**: panels and workspace widgets are mounted per-workspace,
  so workspace-scoped hooks work directly. Overlays, home views, and settings
  components are app-global — `useWorkspaceTasks` is unavailable there, and
  `useCurrentWorkspace` reflects whichever workspace the current route shows
  (or `null`).
- **Overlays** render in a `pointer-events: none` layer — interactive elements
  must set `pointer-events: auto` themselves.
- **`useExtensionSetting`** persists per-extension strings in localStorage
  (`sculptor-extension:<id>:<key>`). Values are **plaintext** — treat anything
  the user puts there (e.g. an API token) as visible to anyone with devtools
  access, and JSON-encode yourself if you need structure.
- Prefer the selector form of `useCurrentWorkspace`
  (e.g. `(w) => w?.branch ?? null`) to avoid re-rendering on unrelated
  workspace changes.

### Host-provided modules — never bundle these

The host import map provides exactly these bare specifiers; mark them as
externals in any build, and never ship your own copy (a second React instance
breaks hooks):

```
react   react/jsx-runtime   react-dom   react-dom/client
jotai   @tanstack/react-query   @radix-ui/themes   lucide-react
@sculptor/extension-sdk
```

Notes: `@tanstack/react-query` deliberately does not expose `QueryClient` /
`QueryClientProvider` — extensions share the host's query cache; namespace
your query keys with your extension id (e.g. `["my-extension", "issue", id]`).
Importing a name a module doesn't export silently yields `undefined` at
runtime, so verify in the UI rather than trusting the build.

## Two flavors: no-build vs. built

**No-build** (start here): hand-written ESM. Raw DOM (as in the quick start),
or React without JSX via `createElement`:

```js
import { createElement as h, useState } from "react";
import { useExtensionSetting } from "@sculptor/extension-sdk";

const Badge = () => {
  const [label, setLabel] = useExtensionSetting("label");
  return h("div", { style: { pointerEvents: "auto", position: "fixed", top: 8, right: 8 } },
    h("input", { value: label, onChange: (e) => setLabel(e.target.value) }));
};

export default function activate(api) {
  return api.registerOverlay({ id: "badge", component: Badge });
}
```

**Built** (when you want JSX/TypeScript/dependencies): any bundler that emits
a single ESM file with the host modules external. Minimal Vite setup:

```ts
// vite.config.ts — devDependencies: vite, @vitejs/plugin-react
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  define: { "process.env.NODE_ENV": JSON.stringify("production") },
  build: {
    lib: { entry: "src/index.tsx", formats: ["es"], fileName: () => "main.js" },
    rollupOptions: {
      external: [
        "react", "react/jsx-runtime", "react-dom", "react-dom/client",
        "jotai", "@tanstack/react-query", "@radix-ui/themes", "lucide-react",
        "@sculptor/extension-sdk",
      ],
    },
  },
});
```

The SDK is not published to npm. For TypeScript, copy the `sdk.d.ts` shipped
next to this skill into your project and alias it in `tsconfig.json`:
`"paths": { "@sculptor/extension-sdk": ["./sdk.d.ts"] }` (you'll want
`@types/react` installed for full fidelity). After building, place
`manifest.json` next to the bundle (`"entry": "main.js"`) and
`sculpt extension load <dist-dir>` — loading the output directory keeps the
upload small.

## Gotchas

- `load`/`reload` report the settled status, but errors thrown after
  activation (event handlers, component renders) don't reach the CLI — when
  something registered but misbehaves, check `inspect` and the UI's
  per-extension error boundary.
- Disposers are synchronous; clean up injected styles/listeners/timers, not
  just DOM nodes.
- Overlays: remember `pointer-events: auto` on interactive elements.
- Settings are plaintext localStorage; `inspect` reports key names only.
- Use Radix theme CSS variables (e.g. `var(--gray-12)`, `var(--accent-9)`)
  so the extension follows the host theme, and `@radix-ui/themes` components
  for native-feeling UI.
- One extension id wins per source priority (local > url > builtin); a
  shadowed duplicate loads its manifest but never activates.

## Going deeper with the Sculptor source (optional)

None of this is required — the contract above is complete — but if you have a
checkout of [github.com/imbue-ai/sculptor](https://github.com/imbue-ai/sculptor)
(or are already working in it), you can go deeper:

- **SDK source**: `sculptor/frontend/src/extensions/types.ts` (host API
  contract) and `sculptor/frontend/src/extensions/sdk/` (hooks, components,
  actions) — the `sdk.d.ts` next to this skill is generated from these
  (`just generate-extension-sdk-dts`).
- **Reference extensions**: `sculptor/frontend/public/extensions/sculpty/`
  (no-build raw DOM), `sculptor/frontend/public/extensions/pomodoro/`
  (no-build React overlay with persisted settings), and
  `sculptor/frontend/extensions/linear-issue/` (full TypeScript/Vite panel +
  settings + widget + home view).
- **Bundled extensions**: extensions shipped with the app are built by the
  host's Vite config (`sculptor/frontend/vite-plugins/bundled-extensions.ts`);
  adding an id to `COMPILED_EXTENSION_IDS` there builds
  `extensions/<id>/src/` automatically.
- **In-repo skills**: when working inside the Sculptor repo you can run a dev
  build of the app and use the `auto-qa-changes` skill to drive the UI in a
  headless browser for visual verification, instead of relying on the user's
  live instance.

From imbue-ai/sculptor at f847102, shown as plain text under its license: MIT (permissive). The license text is in LICENSE.md.

View SKILL.md on GitHub at f847102

Files

4 files, 43 KB in total. Each file has a sha256 in the pin's manifest; the first 12 characters are shown.

Skill folder on GitHub at f847102
All 50 skills from Imbue

More from Imbue

  • architect

    Generates an architecture document for a feature by reading the existing spec, analyzing the codebase deeply, and conducting guided Q&A with the user to…

    Claude Code · about 4K tokens · pinned 6 days ago

  • build

    This skill executes a multi-step implementation plan by working through task files sequentially.

    Claude Code · about 1.1K tokens · pinned 6 days ago

  • fix-bug

    Fixes bugs using test-driven development with an interactive or autonomous workflow.

    Claude Code · about 9.8K tokens · pinned 6 days ago

  • handoff

    This skill hands off ongoing work to a fresh agent, either in the same workspace or a new isolated workspace, while preserving context through a synthesized…

    Claude Code · about 1.1K tokens · pinned 6 days ago

  • help

    This skill fetches the latest Sculptor documentation from GitHub and answers user questions about how to use Sculptor's features, including workspaces…

    Claude Code · about 880 tokens · pinned 6 days ago

  • mock

    Creates interactive HTML mocks for features through guided iteration with users in exploration mode (generating 3+ variants to explore ideas) or confirmation…

    Claude Code · about 3K tokens · pinned 6 days ago

  • next-bundle-optimizer

    Audits Next.js applications to identify and reduce browser bundle size and initial-load work by analyzing routes, dependencies and client outputs.

    by VercelOfficial publisher

    Claude Code, Codex, Cursor +4 · about 2.3K tokens · pinned 6 days ago

  • next-cache-components-optimizer

    Optimize Next.js routes to display meaningful UI instantly on initial load or client navigation by encoding the optimization goal as a failing…

    by VercelOfficial publisher

    Claude Code, Codex, Cursor +4 · about 5.6K tokens · pinned 6 days ago

  • next-partial-prefetching-adoption

    Enables Partial Prefetching in Next.js 16.3+ apps and guides adoption through auditing Link prefetch behavior, preserving UI in tests or manually, and…

    by VercelOfficial publisher

    Claude Code, Codex, Cursor +4 · about 6.7K tokens · pinned 6 days ago

  • remotion-interactivity

    This skill teaches how to structure Remotion code so the Studio IDE recognizes and makes it interactive.

    by RemotionOfficial publisher

    Claude Code, Codex, Cursor +3 · about 2.4K tokens · pinned 6 days ago

  • remotion-interactivity

    This skill teaches how to structure Remotion video components to unlock Studio interactivity features like click selection, drag-and-drop, resizing, rotation…

    by RemotionOfficial publisher

    Claude Code, Codex, Cursor +3 · about 4.8K tokens · pinned 6 days ago

  • remotion-maps

    This skill enables creating map-based animations with Remotion using four different mapping libraries: Mapbox, MapLibre, MapTiler, or CesiumJS.

    by RemotionOfficial publisher

    Contains scripts

    Claude Code, Codex, Cursor +3 · about 240 tokens · pinned 6 days ago

Open roles at Imbue

No open roles at Imbue on the Agent Native jobs board right now.

Browse the jobs board

The skills listed here are third-party software, collected from public GitHub repositories as a catalog of recent skills and provided as is. Agent Native does not write or control them and is not responsible for any harm, loss or damage caused by installing or using them. Labels and checks describe what we saw at one commit and are not a guarantee. Read a skill's files before you run it. Terms of use