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

@cycgraph/tools

v1.6.0

Published

Curated, plug-in tools for @cycgraph/orchestrator: SSRF-guarded web access and pure data utilities, built on defineTool.

Readme

@cycgraph/tools

License: Apache 2.0 Node.js

Tool library for @cycgraph/orchestrator. These are optional dependencies that provide tools that can be used by agents.

Install

npm install @cycgraph/tools

Subpath imports keep dependencies scoped:

import { webFetchTool, httpRequestTool, webSearchTool } from '@cycgraph/tools/web';
import { calculatorTool, jsonTransformTool, currentTimeTool } from '@cycgraph/tools/data';
import { memorySearchTool } from '@cycgraph/tools/memory';
import { sandboxedJsTool } from '@cycgraph/tools/sandbox';
import { workspaceTools } from '@cycgraph/tools/workspace';
import { deliveryNodes, publishConfigFromEnv } from '@cycgraph/tools/git';

Quick start

import { GraphRunner } from '@cycgraph/orchestrator';
import { webFetchTool } from '@cycgraph/tools/web';
import { calculatorTool } from '@cycgraph/tools/data';

const runner = new GraphRunner(graph, state, {
  tools: [webFetchTool(), calculatorTool()],
});

Agents declare the tools by name:

tools: ['web_fetch', 'calculator']

The tools

Web Search

import { webSearchTool } from '@cycgraph/tools/web';

const search = webSearchTool({
  provider: 'brave',
  apiKey: process.env.BRAVE_API_KEY!,
  maxResults: 10,
  timeoutMs: 15_000,
});

The model calls it with { query }.

Provider-pluggable search (Brave, Tavily) with normalized results. The API key stays config-side — it is never part of the model-facing schema.

Web Fetch

import { webFetchTool } from '@cycgraph/tools/web';

const fetch = webFetchTool({
  allowedHosts: ['example.com'],
  maxResponseBytes: 1024 * 1024,
  extract: 'markdown',
  timeoutMs: 15_000,
  userAgent: 'My Agent',
});

The model calls it with { url } — which URL to fetch is the model's decision; which hosts it may reach is yours.

GET a public URL and return its body as text.

SSRF-protected: private/loopback/link-local hosts are rejected in any IP encoding, DNS-resolved addresses are re-checked (rebinding), and redirects are followed manually so each hop is validated. Max 5 redirects.

HTML to Markdown

import { htmlToMarkdownTool } from '@cycgraph/tools/web';

const markdown = htmlToMarkdownTool({
  maxInputBytes: 5 * 1024 * 1024,
  timeoutMs: 10_000,
});

The model calls it with { html, mode?, baseUrl? } — mode is 'markdown' (default) or 'text', and baseUrl resolves relative links.

HTTP Request

import { httpRequestTool } from '@cycgraph/tools/web';

const request = httpRequestTool({
  allowedHosts: ['api.example.com'],
  allowedMethods: ['GET', 'POST'],
  defaultHeaders: { authorization: `Bearer ${process.env.API_TOKEN}` },
  maxResponseBytes: 1024 * 1024,
  timeoutMs: 15_000,
});

The model calls it with { url, method?, headers?, body? }. allowedHosts is required and non-empty — this tool exists for a fixed set of APIs, and defaultHeaders merge over the model's headers so credentials stay config-side.

Current Time

import { currentTimeTool } from '@cycgraph/tools/data';

const now = currentTimeTool({
  timezone: 'America/New_York',
});

Calculator

import { calculatorTool } from '@cycgraph/tools/data';

const calc = calculatorTool();

The model calls it with { expression, variables? } — e.g. { expression: 'x + y * z', variables: { x: 1, y: 2, z: 3 } }.

JSON Transform

import { jsonTransformTool } from '@cycgraph/tools/data';

const transform = jsonTransformTool();

The model calls it with { data, path?, pick? } — e.g. { data: { orders: [{ id: 1, total: 100 }] }, path: 'orders[0].total' }. data accepts a JSON value or a JSON-encoded string.

Extract and reshape JSON. Resolve a dot/bracket path and optionally pick a key subset. Accepts a JSON value or a JSON string.

CSV Parse

import { csvParseTool } from '@cycgraph/tools/data';

const csv = csvParseTool({ maxRows: 1000 });

The model calls it with { csv, delimiter?, hasHeader? } — e.g. { csv: 'id,name\n1,Alice\n2,Bob', hasHeader: true }.

Stats

import { statsTool } from '@cycgraph/tools/data';

const stats = statsTool();

The model calls it with { values } — an array of finite numbers.

Descriptive statistics: count, sum, mean, median, min, max, sample stdDev, and interpolated p25/p75/p95.

Text Extract

import { textExtractTool } from '@cycgraph/tools/data';

const extract = textExtractTool({
  regexTimeoutMs: 2000,
  maxMatches: 100,
});

The model calls it with { text, pattern, flags? } — e.g. { text: 'order_123 total $100', pattern: 'order_(\\d+)' } (no surrounding slashes; escape backslashes when authoring the pattern in a string literal).

Regex extraction with a structural ReDoS guard: the pattern runs in a worker thread terminated at the deadline (a promise race can't interrupt synchronous backtracking), plus pattern/input/match caps. Returns matches with indexes, positional groups, and named groups.

Memory Search

import { memorySearchTool } from '@cycgraph/tools/memory';
import { InMemoryMemoryStore, InMemoryMemoryIndex } from '@cycgraph/memory';

const search = memorySearchTool({
  store: new InMemoryMemoryStore(),
  index: new InMemoryMemoryIndex(),
  scopeTags: ['customer-123'],
  maxResults: 10,
});

The model calls it with { query?, entityIds?, tags?, limit? } — e.g. { tags: ['order'] }. Free-text query needs the embed hook, and is rejected without one.

Agent-initiated retrieval over the @cycgraph/memory temporal knowledge graph: search by tags, seed entity ids (subgraph expansion), or free text via an embed hook. scopeTags namespace-restrict results regardless of what the model searched; fact ids come back for caller-side outcome attribution. Requires @cycgraph/memory (optional peer dependency, loaded only via this subpath).

Sandboxed JS

import { sandboxedJsTool } from '@cycgraph/tools/sandbox';

const sandbox = sandboxedJsTool({
  deadlineMs: 2000,
  memoryLimitBytes: 64 * 1024 * 1024,
  maxResultBytes: 1024 * 1024,
});

The model calls it with { code, input? } — e.g. { code: 'input.x + input.y * 2', input: { x: 1, y: 2 } }. The last expression is the result; there is no return at the top level.

Evaluate agent-authored JavaScript against workflow data and return a JSON result. Two nested boundaries: QuickJS-in-WASM (no fs/network/timers/modules; only a string-only console.log bridge) inside a worker_threads worker terminated at the deadline. Synchronous; last expression is the result; optional JSON input global. Defaults 2s deadline / 64 MiB / 1 MiB result cap. Carries the QuickJS WASM engine, so it lives behind its own subpath.

Workspace: Search, Read File, Edit File, Create File, Diagnostics

import { workspaceTools } from '@cycgraph/tools/workspace';

const runner = new GraphRunner(graph, state, {
  tools: workspaceTools('/path/to/disposable/clone'),
});

Or individually, when a surface needs its own limits:

import { searchTool, readFileTool, editFileTool } from '@cycgraph/tools/workspace';

const tools = [
  searchTool({ root, maxHits: 20 }),
  readFileTool({ root, maxFileBytes: 256 * 1024 }),
  editFileTool({ root }),
];

The model calls search with { query }, read_file with { path, offset?, limit? }, edit_file with { path, find, replace }, and create_file with { path, contents } — paths relative to the workspace root.

The file-access surface for a code-editing agent, and deliberately no more than that. Every path resolves through a jail that refuses anything outside the root, so the workspace should be a disposable clone — never a live checkout, never the host. search skips dependency and build directories and caps its hits. read_file is line-windowed: a large file comes back in slices with a marker saying how to read on, because sized tool results are the difference between editing a two-hundred line file and a three-thousand line one. edit_file requires its find text to appear exactly once and otherwise changes nothing, telling the model to bring more context — an agent must react to ambiguity, never have the tool guess where an edit half-fits. create_file brings a new file into existence and only that: a path that already exists is refused rather than overwritten, so the read-before-edit invariant cannot be routed around by clobbering a file nobody read. Reads and searches are taint-tracked (taints: true): workspace contents are someone's repository, not the engine's. Branching, verifying, and committing are procedures that belong to the caller, not the model.

The workspaceTools(root) bundle is search, read_file, edit_file, and create_file over one root, and it arms read-before-edit as harness discipline: a shared session records a content hash at every read, and edit_file refuses a file that was never read or that changed since — the agent is told to read again, never left editing a stale picture. A successful edit or create records the new content, so iterating on one file needs no re-read. Wire it yourself with createWorkspaceSession() when composing individual factories.

diagnosticsTool({ cwd, command, args }) closes the feedback loop: the model calls it with {} and gets back { clean, output } from a caller-configured check (typecheck, build, tests) — the agent chooses nothing, it can only ask the question the caller wired, which is what keeps a command-running tool inside the no-host-execution mandate. An editing agent that can see its own breakage iterates; one that cannot fails verification blind.

Each tool's Zod schema is exported beside its factory (searchParameters, readFileParameters, editFileParameters, createFileParameters, diagnosticsParameters), so a transport serving these tools remotely, an MCP server for instance, never restates the parameters.

Git Delivery Helpers

import { deliveryNodes, publishConfigFromEnv } from '@cycgraph/tools/git';

const nodes = deliveryNodes({
  repoRoot: '/path/to/repo',
  workspaceAt: '/tmp/clone',
  branch: 'chore/maintenance',
  title: 'chore: automated maintenance',
  detailFrom: 'change_summary',
  config: publishConfigFromEnv(),
});

The other half of the workspace surface: the procedures the caller runs around the jailed clone. Nothing here is a model-facing tool, deliberately — a model edits, the harness delivers. deliveryNodes builds the clone, commit, and publish nodes of a maintenance workflow (commit to a branch under a configured identity, push, open a PR whose body follows the repository's template); the caller wires them into its graph and owns every edge. Lower-level pieces (cloneToBranch, commit, publishBranch, commentOnPr, createIssue, …) are exported for workflows that compose their own tail.

Development

npm test --workspace=packages/tools
npm run build --workspace=packages/tools
npm run lint --workspace=packages/tools

Related

Contributing

Issues and PRs welcome on GitHub. New tools need tests, a taint declaration, and a maintenance owner — see CONTRIBUTING.md.

License

Apache 2.0.