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

taliqraph

v0.1.6

Published

Run workflows made of AI agents, scripts and approval gates, defined as plain files. A CLI, a programmatic API, and a SKILL.md that teaches your own agent the format.

Readme

taliqraph

Run workflows made of AI agents, scripts and approval gates, defined as plain files. From a terminal, or from your own code.

A workflow is a folder you can read, diff and commit: a workflow.yaml with the agents and scripts it uses beside it. The runner loads it, checks it, runs it in the folder you point it at, and hands back the result with every event it produced. It keeps no database, no home folder and no background state.

npm i -g taliqraph
taliqraph ./my-workflow --input question="what does this folder do"

Node 22 or newer. The engine is your own install and sign-in: Claude Code, which the runner drives as the anthropic engine.

Contents

Try it

Three files, no model call, so it costs nothing:

mkdir -p probe/scripts/inspect && cd probe

workflow.yaml

name: probe
title: Probe
description: Lists the folder, then asks what to do with the files it found.
steps:
  - id: look
    script: inspect
    output: listing

  - id: decide
    gate: select
    show: [listing]
    list: listing.files
    choices:
      - { id: keep, label: Looks right, needs: selection }
      - { id: stop, label: Stop here, needs: none }

  - id: done
    finish: run
    with:
      picked: $decide.selected
      exit: $decide.choice
      _summary: $listing.summary

scripts/inspect/script.yaml

name: inspect
title: Inspect the folder
description: Lists what is in the workspace and reports it.
run: node run.mjs
timeout_minutes: 1
report:
  summary: 3 files in /path
  files: [a.txt]

scripts/inspect/run.mjs

import { readdirSync, writeFileSync } from 'node:fs';

const files = readdirSync(process.cwd());
writeFileSync(
  process.env.TQ_REPORT_FILE,
  JSON.stringify({ summary: `${files.length} files in ${process.cwd()}`, files }),
);

Then:

taliqraph lint .          # the same checks a run makes before it starts
taliqraph . --cwd ~/some-folder

The gate opens in the terminal with one checkbox per file. Arrows move, Space ticks, Enter picks, d dismisses an entry, Ctrl+C stops the run. Add -p to answer everything automatically.

What a workflow looks like

A package is a folder. Only workflow.yaml is required; everything else is what that workflow uses.

my-workflow/
  workflow.yaml            the pipeline
  agents/
    reviewer.agent.md      a model with a prompt, a toolset and a report shape
  scripts/
    post-review/
      script.yaml          name, inputs, report shape, timeout
      run.mjs              the code
  standards/
    typescript.md          house rules an agent is told to follow
  skills/
    changelog.md           reusable instructions an agent can pull in
  workflows/
    sub-flow/              a nested package, run by a `workflow:` step
      workflow.yaml

Every step writes its result under its own id, and under output: when it names one. Later steps read them as references: reviewer, reviewer.findings, inputs.question, $listing.summary inside a with: block.

A fork's results survive it: every branch of a parallel: runs, so a later step reads any branch's output, and a for_each collects its runs into <step>.results. A condition's branches are the exception - one of them did not happen, so nothing after the fork can read them.

Every reference is checked before a run. A name nothing produces is an error; a field outside the producer's report skeleton is a warning, because it arrives as "(not available)" rather than failing. That check only works where a skeleton exists, which is the reason to declare one on every agent and script.

Steps

One key per step decides what it is.

| Step | Key | What it does | | ------------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | Agent | agent: <name> | Runs an agent from agents/. input: lists what it is given, output: names its report | | Script | script: <name> | Runs a script from scripts/, or an inline shell command. input: fills its parameters in order, with: adds named values | | Gate | gate: approve \| choice \| select | Pauses for a person. See Gates | | Sub-workflow | workflow: <name> | Runs a nested package under workflows/ | | Condition | if: <ref> | Compares a reference and takes then: or else: | | Loop | while: <ref> | Jumps back to goto: while the comparison holds, up to max_loops | | Fan-out | for_each: <ref> | Runs one agent (do:) per entry of a list, all at the same time, capped by max_items | | Parallel | parallel: | Runs several branches at the same time | | Jump | goto: <step id> | Jumps to another step, up to max_loops times | | Finish | finish: run | Ends the run successfully. input: and with: become the run's output | | Fail | fail: <message> | Ends the run as failed; the value is the message |

Conditions and loops compare with equals, not_equals, gte, lte or in:

- id: route
  if: review.blocking
  equals: true
  then:
    - id: fix
      agent: engineer
      input: [review]
  else: post # a step id, or a list of steps

- id: retry
  while: tests.failed
  equals: true
  goto: fix
  max_loops: 3

- id: score
  for_each: review.findings
  as: finding
  max_items: 20
  do:
    agent: scorer
    input: [finding]

Inputs, secrets and environment

inputs:
  question: { type: prompt, description: What do you want to know? }
  depth: { type: choice, options: [quick, deep], default: quick }
  retries: { type: number, required: false, default: 2 }
secrets: [GH_TOKEN, JIRA_TOKEN?]
env: [HTTP_PROXY]

Input types are text, prompt, number, boolean and choice. Steps read them as inputs.<name>. A plain value instead of a spec is shorthand: question: '' is required text. An input's name is lowercase letters, digits and underscores, starting with a letter - test_hash, not testHash.

Secrets are names, never values. A ? marks one as optional. They come from --secret NAME=value or the process environment, and reach only the steps that list them under secrets:. A run refuses to start when a required one is missing. Values are redacted from the event log.

env: is a pass-through: host variables a step may see, for proxies and the like.

Gates

A gate says on its first line what it asks for.

- id: sign-off
  gate: approve # show these, answer yes or no, with an optional note
  show: [plan, diff]
  editable: true # the shown text can be edited before deciding

- id: what-next
  gate: choice # pick one of the exits
  show: [review]
  choices:
    - { id: post, label: Post the review, default: true }
    - { id: nothing, label: Do nothing }

- id: triage
  gate: select # tick entries from a list a step produced
  show: [review]
  list: review.findings # must resolve to an array
  choices:
    - { id: post, label: Post the ticked ones, needs: selection }
    - { id: nothing, label: Do nothing, needs: none }

needs: selection keeps an exit unavailable until something is ticked. default: true marks the exit a headless run takes.

What a gate hands to later steps:

| Reference | On which gate | Value | | ------------------------------------- | -------------------- | -------------------------------------------------------- | | <gate>.approved | all | true or false | | <gate>.note | all | the note typed, empty when none | | <gate>.rejections | all | how many times it was sent back | | <gate>.choice | choice, select | the exit that was picked | | <gate>.selected, <gate>.dismissed | select | the entries ticked, and those dropped with their reasons | | <gate>.edited | any editable: gate | the edited text |

Agents

An agent is a markdown file: frontmatter for the model and its permissions, body for the prompt.

---
name: reviewer
description: Reviews a diff for real bugs.
engine: anthropic
model: sonnet-5
effort: high
tools:
  read: always # always | off
  write: workspace # workspace | anywhere | off
  commands: allowlist # allowlist | sandbox | off
  allowlist: [git diff, npm test]
  network: off # off | allowlist
  mcp: [github] # MCP servers this agent may use
skills: [changelog]
---

You review a diff and report real bugs only.

## Report

```json
{ "findings": [{ "id": "f1", "short": "null deref", "severity": "high" }] }
```

The report block is the shape the agent must end with. The runner reads it, warns when declared fields are missing, and later steps read its fields as reviewer.findings.

Scripts

A script is a folder with a manifest and code. The manifest declares its name, how to run it, what it takes and the shape it reports.

name: post-review
title: Post a review
description: Posts the ticked findings to the pull request.
run: node run.mjs
inputs: [pr, findings]
timeout_minutes: 5
report:
  posted: 3
  url: https://example.com/pull/1

The contract, for both script definitions and inline commands:

| Given to the script | What it holds | | ------------------------------- | --------------------------------------------------------------------------------------------------- | | $TQ_INPUTS | a JSON file: { inputs: {...}, args: [...in the step's order], <parameter>: value }, also on stdin | | TQ_ARG_<n>, TQ_INPUT_<NAME> | the same values as environment variables, strings raw, everything else JSON | | $TQ_WORKSPACE | the folder the run works in, which is also the working directory | | $TQ_SCRIPT_DIR | the script's own folder, for files that ship with it | | $TQ_REPORT_FILE | write the report here; it wins over anything printed |

The report is the file at $TQ_REPORT_FILE, else the last JSON line on stdout - a script that does both gets the file, and stdout is read only when the file is missing, empty or not valid JSON. Anything else is kept as { output: "<text>" } and the step says so, so print JSON.stringify(...) when later steps read fields. A definition's run is spawned without a shell, so quoting behaves the same everywhere; inline commands go through the shell.

Standards and skills

A standard is house rules an agent is told to follow, a markdown file with name and optional applies_to globs. A skill is a reusable instruction file in the SKILL.md shape, name and description in frontmatter, pulled in by an agent's skills: list. Both are plain files under standards/ and skills/.

The authoring skill

The package ships one: skill/SKILL.md, a reference to this format written for a model to read. Hand it to whatever agent you want writing workflows.

# Claude Code, for every project on this machine
mkdir -p ~/.claude/skills/taliqraph-workflows
cp "$(npm root -g)/taliqraph/skill/SKILL.md" ~/.claude/skills/taliqraph-workflows/

# or into a package, for that package's own agents
mkdir -p my-workflow/skills/taliqraph-workflows
cp "$(npm root -g)/taliqraph/skill/SKILL.md" my-workflow/skills/taliqraph-workflows/

The CLI

taliqraph <workflow> [options]     # run it; the workflow is a folder or a .tqh file
taliqraph lint [target]            # the checks a run makes, without running
taliqraph pack <folder>            # a package as one .tqh file
taliqraph unpack <file>            # a .tqh file back into a folder
taliqraph login                    # the engine's own sign-in, in this terminal
taliqraph doctor                   # engines, sign-ins, node

The binary is taliqraph, and tq is the same thing.

| Run option | What it does | | ---------------------------- | ------------------------------------------------------------------------ | | -i, --input <name=value> | one declared input, repeat per input | | --inputs <json\|file> | every input at once, as a JSON object or a path to one | | -p, --print | headless: gates answer themselves, over budget exits 4 | | --secret <NAME=value> | a secret for this run only, never stored | | --env <NAME=value> | a host variable for the workflow's env: pass-through | | --mcp-servers <file> | MCP servers the agents may use, a JSON array of { name, kind, ... } | | --cwd <folder> | the folder to work in, default the current one | | --output-format <fmt> | text, json, or stream-json for one event per line | | --verbose | agent prose and tool calls in the feed | | --no-stream | no live typing: the agent's prose arrives finished, not as it is written | | --json, --no-color, -q | machine-readable output, plain output, results only |

Interactive by default: a gate, a question or a permission ask opens a menu in the terminal. Without a terminal, the first pause fails the run and points at -p.

| Exit code | Meaning | | --------- | ----------------------------------------------------------------------------- | | 0 | the run finished | | 1 | the run failed | | 2 | the workflow is wrong: usage, a lint problem, or a package that will not load | | 3 | stopped at a gate | | 4 | over budget | | 130 | interrupted |

An MCP servers file looks like this:

[
  { "name": "docs", "kind": "stdio", "command": "mcp-docs", "args": ["--root", "."] },
  {
    "name": "api",
    "kind": "http",
    "url": "https://example.com/mcp",
    "headers": { "Authorization": "Bearer …" }
  }
]

From code

import { runWorkflow } from 'taliqraph';

const result = await runWorkflow({
  workflow: './my-workflow',
  inputs: { question: 'what does this folder do' },
  cwd: '/path/to/folder',
  secrets: { GH_TOKEN: process.env.GH_TOKEN },
  onEvent: (event) => console.log(event.type, event.at),
  onGate: async (gate) => {
    if (gate.kind === 'select') {
      return { approved: true, choice: 'post', selected: gate.items?.map((item) => item.key) };
    }
    return { approved: true, note: 'looks fine' };
  },
});

console.log(result.status, result.summary);
console.log(result.metrics.costUsd, result.metrics.tokens.total);

| Option | Default | What it is | | ------------ | -------------------- | ------------------------------------------------------------------------------------- | | workflow | required | a package folder or a .tqh file | | inputs | {} | values for the inputs the workflow declares | | secrets | the environment | values for the secrets it declares | | env | the environment | host variables for its env: pass-through | | cwd | the process cwd | the folder the run works in | | isolated | false | true when cwd is a copy you made, so agents are told to stay inside it | | hooks | none | { afterStep, afterTask } shell commands run in the workspace | | engines | the Anthropic engine | the engine registry to use | | mcpServers | none | resolved MCP servers the agents may name | | streamText | true | agents stream their prose as agent-text-partial; false sends only the finished text | | onEvent | none | every event as it happens, timestamped | | onGate | none | answers every pause | | headless | false | answer every pause automatically, the way -p does | | signal | none | aborting cancels the run | | resumeFrom | none | the events of an earlier run, to continue it |

The result:

{
  status: 'done' | 'failed' | 'cancelled',
  output?: Record<string, unknown>,   // what the finish step handed out, `_summary` included
  summary?: string,
  message?: string,                   // why it failed or stopped
  workspace: string,                  // the folder it worked in
  metrics: { costUsd, tokens, durationMs, steps: [...] },
  events: TimedRunEvent[],            // the whole log; feed it back as `resumeFrom`
  exitCode: number,
}

Events are the whole record of the run, each stamped with at:

| Event | What it carries | | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | run-started | the workflow's name and the inputs the run was given | | step-started | the step id and kind, the references it was handed, and for an agent its name, model and effort | | agent-text, agent-tool-call, agent-tool-result | what the agent said, every tool call with its input, and whether it failed | | step-usage | tokens in and out, and the cost, per step | | step-completed | the step's report, plus any declared fields it left out | | step-failed, step-skipped | why a step failed or did not run | | gate-opened | what the gate showed, the entries to tick, the exits offered | | gate-selection-changed, gate-resolved | every tick and dismissal, then the answer with its note and choice | | condition-evaluated, loop-back | which way a condition went and why the run jumped back | | hook-ran | a hook command and its exit code | | run-completed | the run's output, and every step's report under outputs | | run-failed, run-cancelled, run-interrupted, run-resumed | how the run ended, or why it stopped |

Secret values never appear in the log; they are redacted from every event. Feed result.events back as resumeFrom to continue a run where it stopped.

A workflow that can pause needs onGate or headless. Without either, runWorkflow throws before anything runs, so a server never hangs on a gate nobody can answer:

import { GateHandlerRequired, SecretsMissing, WorkflowInvalid, lintPackage } from 'taliqraph';

try {
  await runWorkflow({ workflow: './my-workflow' });
} catch (error) {
  if (error instanceof GateHandlerRequired) {
    // error.stepIds: the steps that would pause
  }
  if (error instanceof WorkflowInvalid) {
    // error.problems: what the linter refused
  }
  if (error instanceof SecretsMissing) {
    // error.names: what it needs
  }
}

const { lint, missingMcp, problems } = await lintPackage('./my-workflow');

taliqraph/definitions is the definition model on its own, with no Node in it: parse, serialize and lint workflows, agents, scripts, standards and skills, and read or write .tqh files. It runs in a browser.

import { parseWorkflowDefinition, lintWorkflow, buildPackageTqh } from 'taliqraph/definitions';

Single-file packages

pack writes a whole package as one YAML file, .tqh, with every file inline. unpack writes it back out. A .tqh file runs and lints like a folder, so a workflow travels as one attachment. Packing refuses content that looks like a token.

taliqraph pack ./my-workflow --file my-workflow.tqh
taliqraph my-workflow.tqh -p --input question="what changed"

Development

corepack enable
pnpm install
pnpm cli --help
pnpm verify        # typecheck, lint, tests, build

Contributions are welcome. See CONTRIBUTING.md.

License

Apache License 2.0. See LICENSE.

Taliqraph runs workflows with engines you install and sign in to yourself. The anthropic engine uses Anthropic's Claude Agent SDK, which carries Anthropic's own terms.