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

@iloveagents/foundry-web-graph

v0.2.9

Published

Graph visualization for Foundry UI — a Bloom-like force-directed graph view, a source-agnostic graph payload contract, and an optional assistant-ui tool card + panel renderer.

Downloads

3,021

Readme

@iloveagents/foundry-web-graph

Interactive graph visualization for Foundry UI.

A force-directed graph view with the interactions people expect from a graph explorer — colour and caption by type, a filtering legend, hover, select, expand, search, pin, zoom-to-fit — plus an optional assistant-ui tool card and side-panel renderer so an agent can put a graph in front of the user.

Source-neutral by construction. The root export takes a plain payload and knows nothing about where the data came from. Neo4j is one adapter, in its own entry point; the chat integration is in another. Import only what you use.

pnpm add @iloveagents/foundry-web-graph

Tailwind v4 hosts: point Tailwind at this package

styles.css carries the --graph-1..8 palette and the rules Tailwind cannot express — it is not the component styling. GraphView is built from utility classes, and Tailwind v4 does not scan node_modules, so without a @source directive the graph renders with no spacing, borders or typography:

@import "tailwindcss";
@source "../node_modules/@iloveagents/foundry-web-graph/dist";

@import "@iloveagents/foundry-web-graph/styles.css";

(Adjust the relative path to your stylesheet's location. Apps scaffolded by create-foundry-ui-app --preset neo4j already have this.)

Drawing a graph

import { GraphView } from "@iloveagents/foundry-web-graph";
import "@iloveagents/foundry-web-graph/styles.css";

const data = {
  kind: "foundry.graph",
  version: 1,
  nodes: [
    { id: "1", labels: ["Person"], properties: { name: "Ada" } },
    { id: "2", labels: ["Project"], properties: { name: "Analytical Engine" } },
  ],
  edges: [{ id: "e1", source: "1", target: "2", type: "WORKED_ON" }],
} as const;

<GraphView data={data} height={480} onExpandNode={(node) => loadNeighbours(node.id)} />;

onExpandNode is a callback: the view never issues a query of its own. Feed a larger payload back through data and the layout keeps the positions of nodes that were already there.

Colours come from your app's design tokens, so light and dark mode work without configuration. The stylesheet is optional — without it the graph falls back to the host's --chart-* tokens, and then to a built-in palette.

In a chat

import { defineChatModule } from "@iloveagents/foundry-web-shell";
import {
  makeGraphToolUI,
  registerGraphPanelRenderer,
} from "@iloveagents/foundry-web-graph/assistant-ui";

const CypherToolUI = makeGraphToolUI({ toolName: "graph_read_cypher" });

export const graphModule = defineChatModule({
  name: "graph",
  useInit: registerGraphPanelRenderer,
  toolUIs: <CypherToolUI />,
});

makeGraphToolUI is a factory because assistant-ui binds a literal tool name at construction — so the card works for any tool that returns the payload, whatever it is called. A result that is not graph-shaped degrades to an ordinary tool card rather than throwing.

registerGraphPanelRenderer() teaches the tool panel to render type: "graph" content, which is what the card's Open in panel action produces. It is idempotent.

Letting the agent work in the graph

The panel can be a workspace the agent and the user share rather than a picture the agent hands over. registerGraphClientTools() adds four browser-executed tools:

| Tool | What the agent can do | | ------------------- | -------------------------------------------------------------------------------------------------------------- | | graph_get_view | Read what is on screen: counts, types, the selected node, the active search | | graph_show | Put a new graph in the panel — positions of nodes already there are kept, so a refined query reads as a change | | graph_select_node | Select and centre a node, by id or by caption | | graph_search | Highlight matches and dim the rest |

import {
  registerGraphClientTools,
  registerGraphPanelRenderer,
} from "@iloveagents/foundry-web-graph/assistant-ui";

export const graphModule = defineChatModule({
  name: "graph",
  useInit: () => {
    registerGraphPanelRenderer();
    registerGraphClientTools();
  },
  toolUIs: <CypherToolUI />,
});

The user's own clicks land in the same state, so "tell me about this node" works without either side restating which node. Read graphWorkspaceStore directly if you want to build your own UI on top of it.

From Neo4j

import { graphFromNeo4j } from "@iloveagents/foundry-web-graph/adapters/neo4j";

const payload = graphFromNeo4j(records);

Structurally typed — it does not import neo4j-driver, so a visualization package never drags a database client into your bundle. It walks arbitrary result rows and collects anything node- or relationship-shaped, so RETURN * works as well as a careful projection.

Most apps will not need this: a backend that emits the payload directly is the better arrangement. See the neo4j starter — npm create foundry-ui-app my-app -- --preset neo4j.

The payload

interface GraphPayload {
  kind: "foundry.graph";
  version: 1;
  nodes: {
    id: string;
    labels?: string[];
    caption?: string;
    properties?: Record<string, unknown>;
  }[];
  edges: {
    id: string;
    source: string;
    target: string;
    type?: string;
    properties?: Record<string, unknown>;
  }[];
  truncated?: boolean;
  stats?: { nodeCount?: number; edgeCount?: number };
  legend?: Record<string, { color?: string; captionKey?: string }>;
}

kind and version are what let a tool card look at an arbitrary result and decide whether to render it. isGraphPayload checks both strictly; coerceGraphPayload is the tolerant version that also accepts a JSON string or a { graph: … } envelope.

A payload must never carry a top-level error key — the AG-UI runner treats that as a failed tool call. Report partial results with truncated. The type declares error?: never so this fails at compile time rather than in a demo.

Large graphs

GraphView caps what it draws (1,500 nodes / 3,000 relationships by default, via limits). When a payload overflows it keeps the connected core — seeded at the best-connected node and expanded through its neighbours — rather than an arbitrary prefix, and reports what was withheld. Taking the first N instead produces a screen of unconnected dots, which reads as "there is nothing here" rather than "there is too much here".

The canvas is not readable by assistive technology, so the same graph is always available as a table (GraphTable, or forceTable on GraphView).

Peer dependencies

react, react-dom and lucide-react are required. @assistant-ui/react is optional and only needed for the /assistant-ui entry point.

License

MIT