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

@sheetgrid/agent

v0.1.0-alpha.1

Published

SheetGrid — framework-agnostic controller and LLM tool descriptors for agentic apps

Downloads

28

Readme

@sheetgrid/agent

Framework-agnostic controller and LLM tool descriptors for driving SheetGrid from an agent.

Pair with @sheetgrid/react or @sheetgrid/vue to make a grid that your app's AI agent can read, mutate, and undo — with typed operations, structured error returns, and paste/multi-cell atomicity out of the box.

| BYOK panel (6 providers) | Vue equivalent | |---|---| | | |

Try it live in 60 secondspnpm dev:demo (React) or pnpm dev:demo-vue (Vue) from the monorepo. Both demos ship a BYOK panel above the chat with 6 providers (mock / Anthropic / OpenAI / OpenAI-compatible / Gemini / Vercel). Paste a key, pick a model, talk to your grid.

Table of contents

How SheetGrid compares

Agentic capabilities are new territory for data grids. Here's the honest state as of alpha:

| | SheetGrid | AG Grid | TanStack Table | Handsontable | |---|---|---|---|---| | Typed controller API for external programmatic driving | ✅ built-in (GridController) | ❌ (imperative Grid API, not designed for agents) | ❌ (headless — you own the state) | ❌ | | Structured OpResult returns (no exceptions across boundary) | ✅ | ❌ | ❌ | ❌ | | Reversible Command / History model with undo / redo / snapshot | ✅ | Partial (undo via Enterprise) | ❌ | Partial (via plugin) | | Ready-made LLM tool descriptors (26 tools, JSON Schema, SDK-agnostic) | ✅ (describeGridTools) | ❌ | ❌ | ❌ | | Drop-in chat UI with tool loop (<AgentChat> / useAgent) | ✅ | ❌ | ❌ | ❌ | | Per-column + per-op authorization for agent writes | ✅ (agentWritable, authorize) | ❌ | N/A (you own state) | ❌ | | Bundle impact if unused | 0 (opt-in package) | — | 0 | — | | Framework support | React + Vue + Nuxt | React / Angular / Vue / vanilla | React / Vue / Solid / Svelte / Qwik | React / Vue / Angular | | License | MIT | MIT + Enterprise ($) | MIT | MIT + Commercial ($) |

Where SheetGrid focuses: making the grid a first-class agent target. Where the others focus: enterprise-grade grid features (server-side row model, aggregation, pivot tables) that SheetGrid doesn't ship yet. Pick based on what dominates your project.

Install

pnpm add @sheetgrid/agent
# Plus your framework binding:
pnpm add @sheetgrid/react   # or @sheetgrid/vue@next

Peer: @sheetgrid/core >= 0.3.0 (installed transitively).

Quickstart — React

import { Grid, useGridController } from "@sheetgrid/react";
import { describeGridTools } from "@sheetgrid/agent";

function App() {
  const controller = useGridController({
    // Optional: lock the whole grid.
    readOnly: false,
    // Optional: dynamic authorization.
    authorize: (op) => {
      if (op.type === "grid.delete_row" && !userIsAdmin) return "admins only";
      return true;
    },
  });

  // Get the tool descriptors to hand to your LLM SDK.
  const tools = describeGridTools(controller);

  // Anthropic example:
  // await anthropic.messages.create({ model, tools, messages });

  return (
    <Grid
      controller={controller}
      rows={[
        { id: "r1", name: "Ada", age: 36 },
        { id: "r2", name: "Grace", age: 40 },
      ]}
      columns={[
        { id: "name", header: "Name" },
        { id: "age", header: "Age", type: "number" },
      ]}
    />
  );
}

Quickstart — Vue

<script setup lang="ts">
import { SheetGrid, useGridController } from "@sheetgrid/vue";
import { describeGridTools } from "@sheetgrid/agent";

const controller = useGridController();
const tools = describeGridTools(controller);
</script>

<template>
  <SheetGrid :controller="controller" :rows="rows" :columns="columns" />
</template>

Controller API — reads

controller.getSchema();
controller.getData({ rowIds?, columnIds?, range?, includeFormulaSources? });
controller.getCell(rowId, columnId);
controller.queryRows(whereClause);
controller.getSelection();
controller.describe();  // human-readable summary for prompt context

Controller API — writes

Every write returns { ok: true } | { ok: false, code, message, details? }.

controller.setCell(rowId, columnId, value);
controller.setCells(patches);   // partial-success report

controller.addRow(values, { at?, id? });
controller.updateRow(rowId, patch);
controller.deleteRow(rowId);
controller.moveRow(rowId, toIndex);

controller.addColumn(def, { at? });
controller.updateColumn(columnId, patch);
controller.deleteColumn(columnId);
controller.moveColumn(columnId, toIndex);

controller.setSort([{ columnId, direction }]);
controller.clearSort();
controller.setFilter({ column, op, value });
controller.select({ rowId, columnId });

controller.setFormula(rowId, columnId, source);
controller.clearFormula(rowId, columnId);

Transactions

await controller.batch(async (tx) => {
  tx.setCell("r1", "amount", 100);
  tx.setCell("r1", "tax", 15);
  // Throw to roll back. One undo reverses the whole batch.
});

History and snapshots

controller.undo();
controller.redo();
controller.canUndo();

const snap = controller.snapshot();
controller.restore(snap);   // itself undoable

Safety layers

Three composable levers (all optional):

useGridController({
  readOnly: true,                                       // grid-wide lock
  authorize: (op) => op.type !== "grid.delete_row"      // dynamic policy
    ? true
    : "deletes require admin",
});

Per-column: mark { agentWritable: false } on any column def to lock it.

Events

controller.on("cell.changed", (e) => console.log(e));
controller.on("*", (e) => log(e));   // all events

Every event carries a source tag: { kind: 'agent' | 'user' | 'system', ... } so multi-actor sessions stay coherent.

LLM tool descriptors

describeGridTools(controller) returns SDK-agnostic descriptors:

{
  name: 'grid_set_cell',
  description: 'Write a single cell...',
  input_schema: { /* JSON Schema */ },
  execute(input) => Promise<OpResult>;
}

Convert for your SDK

Anthropic (matches directly):

const tools = describeGridTools(controller).map((t) => ({
  name: t.name,
  description: t.description,
  input_schema: t.input_schema,
}));

OpenAI (rename input_schemaparameters):

const tools = describeGridTools(controller).map((t) => ({
  type: "function",
  function: {
    name: t.name,
    description: t.description,
    parameters: t.input_schema,
  },
}));

Vercel AI SDK:

import { tool } from "ai";

const tools = Object.fromEntries(
  describeGridTools(controller).map((t) => [
    t.name,
    tool({
      description: t.description,
      parameters: /* your schema converter */,
      execute: async (args) => (await t.execute(args)),
    }),
  ]),
);

<AgentChat> and useAgent — drop-in chat UI

For a full chat experience, use the higher-level useAgent hook (React + Vue) or <AgentChat> component. Both are built on createAgentLoop, are framework-native, and ship zero LLM SDK dependencies.

React — <AgentChat>

import { Grid, useGridController, AgentChat } from "@sheetgrid/react";
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({ apiKey: import.meta.env.VITE_ANTHROPIC_KEY });

function App() {
  const controller = useGridController();
  return (
    <>
      <Grid controller={controller} rows={rows} columns={columns} />
      <AgentChat
        controller={controller}
        send={async ({ messages, tools, systemPrompt, signal }) => {
          const res = await client.messages.create(
            {
              model: "claude-opus-4-7",
              max_tokens: 1024,
              system: systemPrompt,
              tools: tools.map((t) => ({
                name: t.name,
                description: t.description,
                input_schema: t.input_schema,
              })),
              messages: messages.map((m) =>
                m.role === "user"
                  ? { role: "user", content: m.content }
                  : m.role === "assistant"
                    ? { role: "assistant", content: m.content }
                    : { role: "user", content: m.content.map((c) => ({ type: "tool_result", tool_use_id: c.tool_use_id, content: JSON.stringify(c.output) })) }
              ),
            },
            { signal },
          );
          return res;
        }}
      />
    </>
  );
}

That's the full integration. Anthropic's response shape matches SendOutput directly.

Vue — <AgentChat>

<script setup lang="ts">
import { SheetGrid, useGridController, AgentChat } from "@sheetgrid/vue";
const controller = useGridController();
async function send(input) { /* same as React */ }
</script>

<template>
  <AgentChat :controller="controller" :send="send" />
</template>

Headless — useAgent

If <AgentChat> doesn't fit your UI, use the hook directly:

const { messages, thinking, send, error } = useAgent(controller, { send: myLLMCall });
// render however you want

Adapters for other SDKs

  • OpenAI: rename tool.input_schemaparameters; map response tool_callscontent blocks (~10 lines).
  • Vercel AI SDK: pass tools to generateText; walk the returned toolCalls (~15 lines).

The loop engine only cares that send returns { content: [{ type: 'text' | 'tool_use' }], stop_reason }. Adapt on your side.

Interception hooks

<AgentChat
  controller={controller}
  send={myLLMCall}
  onBeforeTool={(call) => call.name !== "grid_delete_row" || confirm("Really delete?")}
  onAfterTool={(call, result) => console.log(call.name, result)}
  onError={(err) => showToast(err.message)}
/>

Return false from onBeforeTool to deny — the loop feeds the deny back to the model as a tool_result so it can adapt.

Customization

<AgentChat> accepts className, style, renderMessage, renderInput, renderToolTrace, renderError, renderThinking (Vue uses named slots with the same names). Beyond that, drop the component and use useAgent directly.

Status

Alpha. API surface is not stable until 0.1.0. Feedback via GitHub issues welcome.