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

@alint-js/cli

v0.6.0

Published

Agentic code analysis CLI for model-backed lint rules

Readme

alint

npm version npm downloads bundle License JSDocs

Demo

alint is an eslint inspired agentic code analysis tool for vibe-coded code that needs another look. It runs model-backed rules against source files, reports diagnostics in a familiar lint format, and lets rule authors use plain model calls or swappable tool-using agents when a rule needs deeper context.

While alint is inspired by eslint, we expect the concept that alint brings to the table to be a new paradigm for code analysis. It should not be limited to just JavaScript/TypeScript. You can extend this to other languages, non-code artifacts, generated files, or any content a plugin knows how to review.

Table of Contents

Installation and Usage

Prerequisites

alint is written in TypeScript and published as ESM packages for JavaScript and TypeScript projects. Release builds also include standalone binaries, so teams working in Python, Go, Rust, and other non-Node.js projects can run the CLI without setting up Node.js, npm, or pnpm.

You also need at least one OpenAI-compatible model provider. Local providers such as Ollama and LM Studio work well for repeated lint runs because they keep token cost predictable.

Install the CLI

Download a standalone binary from the GitHub release assets if you want to use alint without a Node.js toolchain.

Install globally if you want an alint command available everywhere:

npm install -g @alint-js/cli
pnpm add -g @alint-js/cli

Or install it in a project and run it through your package manager:

npm install -D @alint-js/cli
npx alint src
pnpm add -D @alint-js/cli
pnpm exec alint src

Configure a Model Provider

Use alint setup to write provider configuration. The -N flag is short for --no-interactive, so setup can run in scripts or through coding agents without opening an interactive TUI.

Without --local, setup writes the global config at ~/.config/alint/config.toml. With --local, it writes .alint/config.toml in the current project.

Provider management commands use the same scope policy: writes target the global config by default, and --local selects the current project's config. Update an existing provider or edit individual fields with:

alint config providers update --provider openrouter
alint config providers set --provider openrouter endpoint https://openrouter.ai/api/v1
alint config providers set --provider openrouter headers.Authorization "Bearer $TOKEN"
alint config providers unset --provider openrouter headers.Authorization

providers update probes the provider and adds newly reported models. It is additive by default and does not automatically remove a configured model merely because the remote provider no longer reports it. In the interactive TUI, deselecting a configured model removes it when you confirm the update. Use the destructive models prune command when you intend to remove configured models absent from provider responses.

Provider inspection, command output, and failure reports show header names only; they never print header values. Header values supplied as command arguments may remain in shell history. Use an environment variable such as $TOKEN, not a literal credential.

alint setup -N \
  --provider-endpoint http://localhost:11434/v1 \
  --provider-model qwen:8b

Write provider setup into the current project when a repository should carry its own model mapping:

alint setup -N \
  --local \
  --provider-endpoint http://localhost:11434/v1 \
  --provider-model qwen:8b
alint setup -N \
  --provider-endpoint http://localhost:1234/v1 \
  --provider-model qwen:8b
export OPENROUTER_API_KEY="sk-..."

alint setup -N \
  --provider-endpoint https://openrouter.ai/api/v1 \
  --provider-header "Authorization=Bearer $OPENROUTER_API_KEY" \
  --provider-header "HTTP-Referer=http://localhost" \
  --provider-header "X-OpenRouter-Title=alint" \
  --provider-model openrouter:fusion
export OPENAI_API_KEY="sk-..."

alint setup -N \
  --provider-endpoint https://api.openai.com/v1 \
  --provider-header "Authorization=Bearer $OPENAI_API_KEY" \
  --provider-model gpt-5.4-mini

Run alint

Run the CLI against files or directories:

alint src
alint demo.ts
alint --format json demo.ts

--dirty

Use --dirty without file arguments to lint only existing files that differ from HEAD:

alint --dirty

This includes staged, unstaged, and untracked files from the Git repository root. Ignored and deleted files are excluded. Registered submodule worktrees are excluded. A clean repository exits successfully without producing lint output.

--model

Override the matched model for a one-off run:

alint --model qwen:8b demo.ts

--lang

When the project-local setup does not configure any models, model calls without a rule-level or call-level selector use the default alias from the global setup. Rule selectors continue to use normal model matching. Configuring at least one model in .alint/config.toml restores project-first matching for unselected calls. An explicit --model override always takes precedence.

Ask model-backed rules to write diagnostics in a specific language:

alint --lang zh-CN src

alint returns exit code 0 when diagnostics contain no errors, including warning-only runs. It returns 1 when at least one error diagnostic is reported and 2 when the command cannot complete because of a configuration, input, or runtime failure. alint output inspect uses the same exit-code behavior for saved results.

Inspect Configuration and Output

Useful CLI commands:

alint config inspect src/index.ts
alint config providers list
alint config providers show openrouter
alint config models list
alint config models show ollama/qwen
alint config models probe --endpoint http://localhost:11434/v1
alint config models rm qwen --provider ollama
alint config models prune --provider ollama -N --yes

When a model ID exists under multiple providers, qualify it as <provider>/<model-id> or pass --provider <provider-id>. Configuration mutations write globally unless --local selects the current project's setup config.

models rm removes one exact configured model. models prune probes provider model endpoints and destructively removes configured IDs that are no longer reported. Interactive prune asks for confirmation; scripts must pass -N --yes.

Save machine-readable output and inspect it later without rerunning model calls:

alint --format json src > alint-output.json
alint output inspect alint-output.json

Editor Integration

Work in progress. alint lsp currently publishes cached diagnostics when an editor opens a workspace, and nothing more. Saving a file does not refresh it, changing alint.config.ts does not reload it, and the run commands below are advertised but not yet implemented. Restart the server to pick up either kind of change.

alint lsp runs alint as a language server over stdin and stdout.

It is cache-first. The server reads diagnostics that earlier runs already stored and never calls a model on its own, so opening a workspace costs nothing. A cold cache therefore shows nothing — run alint once to populate it.

Point any LSP editor at the command. In Neovim:

vim.lsp.config.alint = {
  cmd = { 'alint', 'lsp' },
  root_markers = { 'alint.config.ts' },
}

The server declares two commands, alint.runFile and alint.runWorkspace, which will start runs that call models and spend tokens. Both return MethodNotFound today.

A VS Code extension lives in apps/vscode. It starts the server and shows the diagnostics; it is not published to the marketplace yet. To run it from a checkout:

pnpm -F @alint-js/vscode build
code --extensionDevelopmentPath=apps/vscode /path/to/your/project

It resolves the alint executable from the alint.path setting, then node_modules/.bin/alint in the workspace folder, then PATH. The workspace install wins so that the server and the alint you run in a terminal share one cache.

Codex stop-gate plugin (optional)

Install

The Codex plugin adds a Stop hook that runs alint --dirty before the agent ends a turn.

Run these commands to install the plugin from the repository's default branch. These commands do not select the latest release:

codex plugin marketplace add moeru-ai/alint \
  --sparse .agents/plugins \
  --sparse plugins/codex-plugin-alint
codex plugin add alint@alint

To install from a release, use its Git tag:

codex plugin marketplace add moeru-ai/alint \
  --ref vX.Y.Z \
  --sparse .agents/plugins \
  --sparse plugins/codex-plugin-alint
codex plugin add alint@alint

Replace vX.Y.Z with the required release tag. Review and trust the Stop hook when Codex asks. The plugin stays inactive until a repository enables Stop Gate.

Configure

The codex plugin explicitly requires per-repo enable. Use this command to enable for current repository:

  • .toml: run alint config integrations stop-gate enable
  • .js/.ts:
export default defineConfig([
  // ...
  {
    integrations: {
      stopGate: {
        enabled: true,
        // target: 'dirty-files' | 'all'
        // timeoutMs: 900000
      },
    },
  },
  // ...
])

Concepts

alint keeps the familiar lint shape: select targets, apply named rules, report diagnostics, and return an exit code that CI can understand. The difference is that a rule can reach its judgment through model calls or a tool-using agent when syntax-only checks are not enough.

alint User side

Configurations

alint uses two configuration systems with separate schemas and responsibilities:

| Configuration | Priority (high → low) | Content | | --- |--------------------------------------------------------------------------------------|----------------------------------------------------------------------| | Setup | Environment and CLI overrides > .alint/config.toml > ~/.config/alint/config.toml | Providers, models, and runner defaults. | | Lint | Environment and CLI overrides > alint.config.* | Files, ignores, plugins, rules, and other project-level lint policy. |

Use setup TOML for machine or project provider definitions:

version = 1

[[providers]]
id = "http://localhost:11434/v1"
type = "openai-compatible"
endpoint = "http://localhost:11434/v1"

[[providers.models]]
id = "qwen:8b"
name = "qwen:8b"
size = "small"
capabilities = [ "tool-call" ]

[providers.models.default_params]
thinking = { type = "disabled" }

default_params is merged into every chat request for that model. Use it for provider-specific request fields that are not covered by the rest of the model entry.

The CLI can also launch an ACP coding-agent command and expose it to rules as an ordinary model. Interactive alint setup provides presets for Claude Code, Codex, Gemini CLI, Kimi Code CLI, and OpenCode:

version = 1

[[providers]]
id = "acp"

[[providers.models]]
id = "codex"
name = "Codex ACP"
driver = "acp"
aliases = [ "default" ]
capabilities = [ "tool-call" ]
command = "codex-acp"
args = []
cwd = "."

Then run alint --model acp/codex src. The checked-in alint.config.ts remains lint policy; process commands come only from global setup or .alint/config.toml. The command inherits the environment that launched alint; optional model env entries are literal overrides and should not contain committed credentials. Each concurrent request starts one ACP process, and request tools require MCP-over-HTTP support from the agent.

alint structured output forces a tool call (tool_choice). Some models, such as DeepSeek V4, reject that combination while thinking/reasoning is enabled. For those models, set thinking = { type = "disabled" } as above so structured output can run.

Note that -N stands for --no-interactive, which means this is not an interactive setup. TUI is not required, so you can ask Codex or Claude Code to run this command for you.

You can also use --local to write the config in the current project:

alint setup -N \
  --local \
  --provider-endpoint http://localhost:11434/v1 \
  --provider-model qwen:8b
  • Without --local, alint writes the global config under ~/.config/alint/config.toml.
  • --local writes .alint/config.toml in the current project.
  • You can inspect configs using the alint config command group.
Codex Stop Gate

Configure the optional Codex Stop Gate integration through the same project config system:

alint config integrations stop-gate enable
alint config integrations stop-gate show
alint config integrations stop-gate set --target all --timeout-ms 1800000
alint config integrations stop-gate disable

Stop Gate is disabled by default and runs only when the repository explicitly sets integrations.stopGate.enabled = true. The defaults after activation are target = "dirty-files" and timeoutMs = 900000; the maximum timeout is 86100000 (23 hours 55 minutes), leaving five minutes inside the plugin's 24-hour Codex hook limit for startup and persistence. The writer persists only non-default overrides and only extends the existing TOML write path; it does not extend the config writer to other formats. Read the plugins/codex-plugin-alint documentation for complete runtime behavior.

Using Rules & Plugins

Similar to eslint, use alint.config.ts for files, ignores, plugins, and rules:

import { defineConfig } from '@alint-js/cli'
import { examplePlugin } from '@alint-js/plugin-example'

export default defineConfig([
  {
    files: ['**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}'],
    ignore: {
      // Reads applicable nested .gitignore files when selecting lint targets.
      // This is powered by gitignore-fs.
      gitignore: true,
    },
    plugins: {
      example: examplePlugin,
    },
    rules: {
      // The `example` prefix is the local alias configured above.
      'example/inline-miniature-normalizer': 'warn',
      'example/no-redundant-jsdoc': 'warn',
      'example/no-trivial-wrapper-stack': 'warn',
    },
  },
])

Executable and static configs

alint supports executable configs (.js, .ts, .mjs, .cjs, .mts, and .cts) and data-only static configs (.toml, .yaml, .yml, .json, .jsonc, and .json5).

  • Executable configs export a flat config array and can import plugin definitions directly.
  • Static configs use a config.group array and identify plugin sources with strings. They are useful for Python, Rust, Go, and other repositories that do not want to author a JavaScript config file or install a Node.js package manager just to run alint.

For example, a TOML static config can install an exact remote package or a local plugin directory:

[[config.group]]

[config.group.plugins]
plugin_1 = "@scope/[email protected]" # package
plugin_2 = "./plugins/relative-plugin-examplex" # relative directory
plugin_3 = "/opt/alint/plugins/absolute-plugin" # absolute directory

Relative plugin paths are resolved from the directory containing the config file, including when --config points to a nested config. On Windows, use TOML literal strings to preserve native backslashes:

[[config.group]]

[config.group.plugins]
native = 'C:\alint\plugins\native-plugin'

Use alint plugin install after adding or changing plugin sources.

  • Package: @scope/[email protected] downloads that exact version into .alint/plugins/store, verifies its integrity, and loads its root export.
  • Local: ./plugins/my-plugin or an absolute directory loads the current package root export in place. alint checks that the package and entry exist and that the entry stays inside the package directory. It does not build the plugin or install the plugin's dependencies.

Declarative local rule plugins

For simple prompt-backed rules, a local plugin directory can omit package.json and contain one or more rule.alint.* files:

# rules/architecture/semantic/rule.alint.toml
name = "semantic-boundary"
builtInAgent = "basic-structured"
instruction = """
Find semantic boundary problems in the reviewed source.
Report concrete findings with line numbers and short suggestions.
"""
includeFiles = [ "src/**/*.py" ]
excludeFiles = [
  "**/*_test.py",
  "**/vendor/**",
]

Reference the directory through the same static plugins table:

[[config.group]]
files = [ "src/**/*.py" ]
language = "plaintext"

[config.group.plugins]
arch = "./rules/architecture"

[config.group.rules]
"arch/semantic-boundary" = "warn"

name becomes the local rule id. builtInAgent can be basic-structured for a prompt-only structured-output rule or basic-coding-agent for a small built-in agent with filesystem tools. includeFiles and excludeFiles define where diagnostics may be reported; they do not limit what basic-coding-agent can inspect.

Run the install command again after changing a source string, moving a local directory, or changing its symlink target. Changes inside the same local directory are loaded by the next CLI process without reinstalling.

Local plugins execute as trusted Node.js code. Directory containment checks validate the installed source; they are not a sandbox.

Plugin lockfile

alint plugin install writes .alint/plugins/lock.json. Package entries lock the downloaded package and integrity metadata. Local entries lock the physical directory identity while loading its current contents at runtime.

Rule severities follow the familiar lint convention:

  • "off" or 0 disables a rule.
  • "warn" or 1 reports a warning.
  • "error" or 2 reports an error.

Flat configs can analyze non-JavaScript files by selecting plaintext. Language ids follow VS Code's language identifiers, so a plugin that registers a language should use the same id:

import docsPlugin from '@your-alint-config/docs-rules'

import { defineConfig } from '@alint-js/cli'

export default defineConfig([
  {
    files: ['docs/**/*.md', '**/*.txt'],
    language: 'plaintext',
    plugins: {
      docs: docsPlugin,
    },
    rules: {
      'docs/review-copy': 'warn',
    },
  },
])

Cache and Stats

alint caches rule target results by default in .alintcache to avoid repeating LLM calls for unchanged source targets.

After each cacheable rule job completes, alint writes a cache checkpoint before releasing that job's scheduler slot. Each checkpoint atomically replaces the complete cache file, so an interrupted run can reuse every result that had already become durable. Cache hits, skipped jobs, failed jobs, and rules that opt out of caching do not add checkpoint writes. Any checkpoint or final cache write error causes the run to fail.

[!NOTE] .alintcache should not be committed to Git. Add it to .gitignore before running repeated local analysis.

echo ".alintcache" >> .gitignore

Disable cache for a single run:

alint --no-cache src

Run stats are recorded by default. Use --no-stats to skip recording for a run, and use the stats command group to inspect saved usage over time.

Rule Developer side

To reduce the token cost during analysis while allowing rule authors to specify model size and capabilities, alint allows rule authors to Request & Match models with Capability Selector and Size Selector, instead of hardening the entire alint run to use a single model for all rules.

In other words, you could set up your DeepSeek, OpenAI, Ollama, or other OpenAI-compatible model on your machine and let rule authors request a model with tool-call capability and small size. alint will match the best configured model for them.

const model = await ctx.model({
  capabilities: ['tool-call'],
  size: 'small',
})

You can also call ctx.model() without arguments when a rule does not need a specific size or capability.

About agent

In alint, we don't limit you to any specific agent SDK. You can use an exported client from a framework such as Eve, Strands, Pi, Claude Code SDK, Codex SDK, or another tool-using runtime to implement your own agent to analyze code.

Agentic rules use ctx.agent when they need a multi-step tool loop, such as reading related files before reporting findings. The rule stays framework-agnostic, and the user chooses the adapter:

import { createApeiraAdapter } from '@alint-js/agent-apeira'
import { createAgentExamplePlugin } from '@alint-js/plugin-example-agent'

export default [
  {
    agent: createApeiraAdapter(),
    extends: ['agent-example/recommended'],
    plugins: {
      'agent-example': createAgentExamplePlugin(),
    },
  },
]

Current adapter packages include:

  • @alint-js/agent-apeira for Apeira on the xsai stack.
  • @alint-js/agent-pi for Pi.

However, be careful with token cost. alint is designed to be a code analysis tool, where rapid and repeated calls to the model are expected. Local models, cheap small models, and cache-friendly prompts are usually better defaults than routing every rule target through a large hosted model.

BYOA, any agent works

Bring Your Own Agent means alint does not try to own the agent harness. A rule can depend on the @alint-js/core agent contract, while the actual runtime can be Apeira, Pi, your own in-house agent, or a small function that calls the tools you need.

If an adapter is missing, you can implement the missing function or package your own plugin to replace it. The important part is that the rule reports diagnostics back through alint; how the agent reads files, calls tools, plans steps, or talks to a model is intentionally left to the adapter or plugin author.

Example rule

Rules are ordinary JavaScript objects built with the public DSL from @alint-js/core. A rule receives source targets and reports diagnostics.

import { defineRule } from '@alint-js/core'

export const checkFunctionRule = defineRule({
  create: ctx => ({
    async onTargetFunction(target) {
      const model = await ctx.model({ capabilities: ['tool-call'], size: 'small' })

      ctx.report({
        filePath: target.file.path,
        loc: target.loc,
        message: `checked ${target.name} with ${model.id}`,
      })
    },
  }),
})

Package rules as plugins:

import { definePlugin } from '@alint-js/core'

import { checkFunctionRule } from './rules/check-function'

export default definePlugin({
  configs: {
    recommended: [
      {
        rules: {
          'my-plugin/check-function': 'warn',
        },
      },
    ],
  },
  rules: {
    'check-function': checkFunctionRule,
  },
})

Rule authors can opt out of caching when a rule depends on external state:

defineRule({
  cache: false,
  create: ctx => ({
    onTargetFile(target) {
      // Always reruns.
    },
  }),
})

Packages

| Package | Purpose | | --- | --- | | @alint-js/cli | CLI entrypoint, user-facing config facade, setup commands, reporters, output inspection, and stats commands. | | @alint-js/config | Lower-level config loading, setup TOML parsing, config paths, and ignore defaults for tools. | | @alint-js/core | SDK and run engine for plugins, rules, source runtime, model resolution, diagnostics, cache, and agent contracts. | | @alint-js/agent-apeira | Apeira-backed AgentAdapter. | | @alint-js/agent-pi | Pi-backed AgentAdapter. | | @alint-js/languages | First-party language support beyond core's built-in JavaScript and TypeScript: Go, Python, and Rust. | | @alint-js/plugin-example | Example TypeScript/JavaScript model-backed rules. | | @alint-js/plugin-example-agent | Example plugin for framework-agnostic agentic rules. | | @alint-js/plugin-example-go | Example semantic Go review plugin using plaintext. |

Documentation Automation

The table of contents above is generated with doctoc so README navigation is not hand-written. Run this after changing headings:

pnpm docs:update

docs:update refreshes the root README TOC and then copies the root README to packages/cli/README.md, keeping the npm README for @alint-js/cli in sync with the project overview.

Development

This repository is a pnpm workspace.

pnpm install
pnpm -F @alint-js/cli build
pnpm -F @alint-js/core exec vitest run

Before sending changes, run:

pnpm typecheck
pnpm lint

Status

alint is early and APIs may change. The core direction is stable: lint-style diagnostics, model-backed rules, flat configs, provider setup, and optional agent adapters.

License

MIT