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.
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.
Download a standalone binary from the GitHub release assets when 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/cliOr install it in a project and run it through your package manager:
npm install -D @alint-js/cli
npx alint srcpnpm add -D @alint-js/cli
pnpm exec alint srcUse 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.Authorizationproviders 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.
Ollama
alint setup -N \
--provider-endpoint http://localhost:11434/v1 \
--provider-model qwen:8bWrite 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:8bLM Studio
alint setup -N \
--provider-endpoint http://localhost:1234/v1 \
--provider-model qwen:8bOpenRouter
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:fusionOpenAI
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-miniRun the CLI against files or directories:
alint src
alint demo.ts
alint --format json demo.tsOverride the matched model for a one-off run:
alint --model qwen:8b demo.tsAsk model-backed rules to write diagnostics in a specific language:
alint --lang zh-CN srcalint 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.
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 --yesWhen 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.jsonalint 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.
In order to provision models and LLMs for alint while keeping it clean for contributors of your project without requiring them to set up their own LLMs, alint offers layers of configuration covering Project Local, Global, project config, and environment or CLI overrides.
The priorities follow:
~/.config/alint/config.toml < .alint/config.toml < alint.config.* < environment and CLI overrides
~/.config/alint/config.tomlstores user-level provider setup..alint/config.tomlstores optional project-local provider setup.alint.config.*stores project lint config, plugins, files, ignores, and rule settings.- Environment variables and CLI flags are the highest-priority overrides.
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" ]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,alintwrites the global config under~/.config/alint/config.toml. --localwrites.alint/config.tomlin the current project.- You can inspect configs using the
alint configcommand group.
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',
},
},
])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.grouparray 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 runalint.
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/alint-plugin-example1@1.2.3" # package
plugin_2 = "./plugins/relative-plugin-examplex" # relative directory
plugin_3 = "/opt/alint/plugins/absolute-plugin" # absolute directoryRelative 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/alint-plugin-example1@1.2.3downloads that exact version into.alint/plugins/store, verifies its integrity, and loads its root export. - Local:
./plugins/my-pluginor an absolute directory loads the current package root export in place.alintchecks 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.
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.
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"or0disables a rule."warn"or1reports a warning."error"or2reports 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',
},
},
])alint caches rule target results by default in .alintcache to avoid repeating LLM calls for unchanged source targets.
Note
.alintcache should not be committed to Git. Add it to .gitignore before running repeated local analysis.
echo ".alintcache" >> .gitignoreDisable cache for a single run:
alint --no-cache srcRun 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.
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.
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-apeirafor Apeira on the xsai stack.@alint-js/agent-pifor 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.
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.
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.
},
}),
})| 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/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. |
The table of contents above is generated with doctoc so README navigation is not hand-written. Run this after changing headings:
pnpm docs:updatedocs: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.
This repository is a pnpm workspace.
pnpm install
pnpm -F @alint-js/cli build
pnpm -F @alint-js/core exec vitest runBefore sending changes, run:
pnpm typecheck
pnpm lintalint 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.
MIT