@alint-js/cli 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,446 @@
1
+ # `alint`
2
+
3
+ ![Demo](https://raw.githubusercontent.com/moeru-ai/alint/main/docs/assets/demo.gif)
4
+
5
+ `alint` is an [`eslint`](https://eslint.org/) 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.
6
+
7
+ 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.
8
+
9
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
10
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
11
+ ## Table of Contents
12
+
13
+ - [Installation and Usage](#installation-and-usage)
14
+ - [Concepts](#concepts)
15
+ - [Packages](#packages)
16
+ - [Documentation Automation](#documentation-automation)
17
+ - [Development](#development)
18
+ - [Status](#status)
19
+ - [License](#license)
20
+
21
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
22
+
23
+ ## Installation and Usage
24
+
25
+ ### Prerequisites
26
+
27
+ `alint` is published as ESM packages and is intended for modern Node.js projects. Install Node.js and a package manager such as npm or pnpm before using the CLI.
28
+
29
+ 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.
30
+
31
+ ### Install the CLI
32
+
33
+ Install globally if you want an `alint` command available everywhere:
34
+
35
+ ```bash
36
+ npm install -g @alint-js/cli
37
+ pnpm add -g @alint-js/cli
38
+ ```
39
+
40
+ Or install it in a project and run it through your package manager:
41
+
42
+ ```bash
43
+ npm install -D @alint-js/cli @alint-js/core
44
+ npx alint src
45
+ ```
46
+
47
+ ```bash
48
+ pnpm add -D @alint-js/cli @alint-js/core
49
+ pnpm exec alint src
50
+ ```
51
+
52
+ ### Configure a Model Provider
53
+
54
+ 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.
55
+
56
+ Without `--local`, setup writes the global config at `~/.config/alint/config.toml`. With `--local`, it writes `.alint/config.toml` in the current project.
57
+
58
+ <details>
59
+ <summary>Ollama</summary>
60
+
61
+ ```bash
62
+ alint setup -N \
63
+ --provider-endpoint http://localhost:11434/v1 \
64
+ --provider-model qwen:8b
65
+ ```
66
+
67
+ </details>
68
+
69
+ Write provider setup into the current project when a repository should carry its own model mapping:
70
+
71
+ ```bash
72
+ alint setup -N \
73
+ --local \
74
+ --provider-endpoint http://localhost:11434/v1 \
75
+ --provider-model qwen:8b
76
+ ```
77
+
78
+ <details>
79
+ <summary>LM Studio</summary>
80
+
81
+ ```bash
82
+ alint setup -N \
83
+ --provider-endpoint http://localhost:1234/v1 \
84
+ --provider-model qwen:8b
85
+ ```
86
+
87
+ </details>
88
+
89
+ <details>
90
+ <summary>OpenRouter</summary>
91
+
92
+ ```bash
93
+ export OPENROUTER_API_KEY="sk-..."
94
+
95
+ alint setup -N \
96
+ --provider-endpoint https://openrouter.ai/api/v1 \
97
+ --provider-header "Authorization=Bearer $OPENROUTER_API_KEY" \
98
+ --provider-header "HTTP-Referer=http://localhost" \
99
+ --provider-header "X-OpenRouter-Title=alint" \
100
+ --provider-model openrouter:fusion
101
+ ```
102
+
103
+ </details>
104
+
105
+ <details>
106
+ <summary>OpenAI</summary>
107
+
108
+ ```bash
109
+ export OPENAI_API_KEY="sk-..."
110
+
111
+ alint setup -N \
112
+ --provider-endpoint https://api.openai.com/v1 \
113
+ --provider-header "Authorization=Bearer $OPENAI_API_KEY" \
114
+ --provider-model gpt-5.4-mini
115
+ ```
116
+
117
+ </details>
118
+
119
+ ### Run alint
120
+
121
+ Run the CLI against files or directories:
122
+
123
+ ```bash
124
+ alint src
125
+ alint demo.ts
126
+ alint --format json demo.ts
127
+ ```
128
+
129
+ Override the matched model for a one-off run:
130
+
131
+ ```bash
132
+ alint --model qwen:8b demo.ts
133
+ ```
134
+
135
+ Ask model-backed rules to write diagnostics in a specific language:
136
+
137
+ ```bash
138
+ alint --lang zh-CN src
139
+ ```
140
+
141
+ `alint` returns exit code `1` when diagnostics are reported and `0` when the run is clean.
142
+
143
+ ### Inspect Configuration and Output
144
+
145
+ Useful CLI commands:
146
+
147
+ ```bash
148
+ alint config inspect src/index.ts
149
+ alint config providers list
150
+ alint config models list
151
+ alint config models probe
152
+ ```
153
+
154
+ Save machine-readable output and inspect it later without rerunning model calls:
155
+
156
+ ```bash
157
+ alint --format json src > alint-output.json
158
+ alint output inspect alint-output.json
159
+ ```
160
+
161
+ ## Concepts
162
+
163
+ `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.
164
+
165
+ ### `alint` User side
166
+
167
+ #### Configurations
168
+
169
+ 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.
170
+
171
+ The priorities follow:
172
+
173
+ ```text
174
+ ~/.config/alint/config.toml < .alint/config.toml < alint.config.ts < environment and CLI overrides
175
+ ```
176
+
177
+ - `~/.config/alint/config.toml` stores user-level provider setup.
178
+ - `.alint/config.toml` stores optional project-local provider setup.
179
+ - `alint.config.ts` stores project lint config, plugins, files, ignores, and rule settings.
180
+ - Environment variables and CLI flags are the highest-priority overrides.
181
+
182
+ Use setup TOML for machine or project provider definitions:
183
+
184
+ ```toml
185
+ version = 1
186
+
187
+ [[providers]]
188
+ id = "http://localhost:11434/v1"
189
+ type = "openai-compatible"
190
+ endpoint = "http://localhost:11434/v1"
191
+
192
+ [[providers.models]]
193
+ id = "qwen:8b"
194
+ name = "qwen:8b"
195
+ size = "small"
196
+ capabilities = [ "tool-call" ]
197
+ ```
198
+
199
+ 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.
200
+
201
+ You can also use `--local` to write the config in the current project:
202
+
203
+ ```bash
204
+ alint setup -N \
205
+ --local \
206
+ --provider-endpoint http://localhost:11434/v1 \
207
+ --provider-model qwen:8b
208
+ ```
209
+
210
+ - Without `--local`, `alint` writes the global config under `~/.config/alint/config.toml`.
211
+ - `--local` writes `.alint/config.toml` in the current project.
212
+ - You can inspect configs using the `alint config` command group.
213
+
214
+ #### Using Rules & Plugins
215
+
216
+ Similar to `eslint`, use `alint.config.ts` for files, ignores, plugins, and rules:
217
+
218
+ ```ts
219
+ import { defineConfig } from '@alint-js/core'
220
+ import { examplePlugin } from '@alint-js/plugin-example'
221
+
222
+ export default defineConfig([
223
+ {
224
+ files: ['**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}'],
225
+ ignore: {
226
+ // Reads applicable nested .gitignore files when selecting lint targets.
227
+ // This is powered by gitignore-fs.
228
+ gitignore: true,
229
+ },
230
+ plugins: {
231
+ example: examplePlugin,
232
+ },
233
+ rules: {
234
+ // The `example` prefix is the local alias configured above.
235
+ 'example/inline-miniature-normalizer': 'warn',
236
+ 'example/no-redundant-jsdoc': 'warn',
237
+ 'example/no-trivial-wrapper-stack': 'warn',
238
+ },
239
+ },
240
+ ])
241
+ ```
242
+
243
+ Rule severities follow the familiar lint convention:
244
+
245
+ - `"off"` or `0` disables a rule.
246
+ - `"warn"` or `1` reports a warning.
247
+ - `"error"` or `2` reports an error.
248
+
249
+ Flat configs can analyze non-JavaScript files by selecting `text/plain`:
250
+
251
+ ```ts
252
+ import docsPlugin from '@your-alint-config/docs-rules'
253
+
254
+ import { defineConfig } from '@alint-js/core'
255
+
256
+ export default defineConfig([
257
+ {
258
+ files: ['docs/**/*.md', '**/*.txt'],
259
+ language: 'text/plain',
260
+ plugins: {
261
+ docs: docsPlugin,
262
+ },
263
+ rules: {
264
+ 'docs/review-copy': 'warn',
265
+ },
266
+ },
267
+ ])
268
+ ```
269
+
270
+ #### Cache and Stats
271
+
272
+ `alint` caches rule target results by default in `.alintcache` to avoid repeating LLM calls for unchanged source targets.
273
+
274
+ > [!NOTE]
275
+ > `.alintcache` should not be committed to Git. Add it to `.gitignore` before running repeated local analysis.
276
+
277
+ ```bash
278
+ echo ".alintcache" >> .gitignore
279
+ ```
280
+
281
+ Disable cache for a single run:
282
+
283
+ ```bash
284
+ alint --no-cache src
285
+ ```
286
+
287
+ 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.
288
+
289
+ ### Rule Developer side
290
+
291
+ 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.
292
+
293
+ 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.
294
+
295
+ ```ts
296
+ const model = await ctx.model({
297
+ capabilities: ['tool-call'],
298
+ size: 'small',
299
+ })
300
+ ```
301
+
302
+ You can also call `ctx.model()` without arguments when a rule does not need a specific size or capability.
303
+
304
+ #### About agent
305
+
306
+ 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.
307
+
308
+ 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:
309
+
310
+ ```ts
311
+ import { createApeiraAdapter } from '@alint-js/agent-apeira'
312
+ import { createAgentExamplePlugin } from '@alint-js/plugin-example-agent'
313
+
314
+ export default [
315
+ {
316
+ agent: createApeiraAdapter(),
317
+ extends: ['agent-example/recommended'],
318
+ plugins: {
319
+ 'agent-example': createAgentExamplePlugin(),
320
+ },
321
+ },
322
+ ]
323
+ ```
324
+
325
+ Current adapter packages include:
326
+
327
+ - `@alint-js/agent-apeira` for Apeira on the xsai stack.
328
+ - `@alint-js/agent-pi` for Pi.
329
+
330
+ 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.
331
+
332
+ #### BYOA, any agent works
333
+
334
+ 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.
335
+
336
+ 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.
337
+
338
+ #### Example rule
339
+
340
+ Rules are ordinary JavaScript objects built with the public DSL from `@alint-js/core`. A rule receives source targets and reports diagnostics.
341
+
342
+ ```ts
343
+ import { defineRule } from '@alint-js/core'
344
+
345
+ export const checkFunctionRule = defineRule({
346
+ create: ctx => ({
347
+ async onTarget(target) {
348
+ if (target.kind !== 'function') {
349
+ return
350
+ }
351
+
352
+ const model = await ctx.model({ capabilities: ['tool-call'], size: 'small' })
353
+
354
+ ctx.report({
355
+ filePath: target.file.path,
356
+ loc: target.loc,
357
+ message: `checked ${target.name} with ${model.id}`,
358
+ })
359
+ },
360
+ }),
361
+ })
362
+ ```
363
+
364
+ Package rules as plugins:
365
+
366
+ ```ts
367
+ import { definePlugin } from '@alint-js/core'
368
+
369
+ import { checkFunctionRule } from './rules/check-function'
370
+
371
+ export default definePlugin({
372
+ configs: {
373
+ recommended: [
374
+ {
375
+ rules: {
376
+ 'my-plugin/check-function': 'warn',
377
+ },
378
+ },
379
+ ],
380
+ },
381
+ rules: {
382
+ 'check-function': checkFunctionRule,
383
+ },
384
+ })
385
+ ```
386
+
387
+ Rule authors can opt out of caching when a rule depends on external state:
388
+
389
+ ```ts
390
+ defineRule({
391
+ cache: false,
392
+ create: ctx => ({
393
+ onTarget(target) {
394
+ // Always reruns.
395
+ },
396
+ }),
397
+ })
398
+ ```
399
+
400
+ ## Packages
401
+
402
+ | Package | Purpose |
403
+ | --- | --- |
404
+ | [`@alint-js/cli`](https://github.com/moeru-ai/alint/tree/main/packages/cli) | CLI entrypoint, setup commands, reporters, output inspection, and stats commands. |
405
+ | [`@alint-js/config`](https://github.com/moeru-ai/alint/tree/main/packages/config) | Config loading, setup TOML parsing, config paths, and ignore defaults. |
406
+ | [`@alint-js/core`](https://github.com/moeru-ai/alint/tree/main/packages/core) | Public DSL, run engine, source runtime, model resolution, diagnostics, cache, and agent contracts. |
407
+ | [`@alint-js/agent-apeira`](https://github.com/moeru-ai/alint/tree/main/packages/agent-apeira) | Apeira-backed `AgentAdapter`. |
408
+ | [`@alint-js/agent-pi`](https://github.com/moeru-ai/alint/tree/main/packages/agent-pi) | Pi-backed `AgentAdapter`. |
409
+ | [`@alint-js/plugin-example`](https://github.com/moeru-ai/alint/tree/main/packages/plugin-example) | Example TypeScript/JavaScript model-backed rules. |
410
+ | [`@alint-js/plugin-example-agent`](https://github.com/moeru-ai/alint/tree/main/packages/plugin-example-agent) | Example plugin for framework-agnostic agentic rules. |
411
+ | [`@alint-js/plugin-example-go`](https://github.com/moeru-ai/alint/tree/main/packages/plugin-example-go) | Example semantic Go review plugin using `text/plain`. |
412
+
413
+ ## Documentation Automation
414
+
415
+ The table of contents above is generated with `doctoc` so README navigation is not hand-written. Run this after changing headings:
416
+
417
+ ```bash
418
+ pnpm docs:update
419
+ ```
420
+
421
+ `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.
422
+
423
+ ## Development
424
+
425
+ This repository is a pnpm workspace.
426
+
427
+ ```bash
428
+ pnpm install
429
+ pnpm -F @alint-js/cli build
430
+ pnpm -F @alint-js/core exec vitest run
431
+ ```
432
+
433
+ Before sending changes, run:
434
+
435
+ ```bash
436
+ pnpm typecheck
437
+ pnpm lint
438
+ ```
439
+
440
+ ## Status
441
+
442
+ `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.
443
+
444
+ ## License
445
+
446
+ MIT
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as executeCli } from "../cli-D4YC9Jnb.mjs";
2
+ import { t as executeCli } from "../cli-DmIOO6F8.mjs";
3
3
  import process from "node:process";
4
4
  //#region src/bin/index.ts
5
5
  executeCli(process.argv, {
@@ -13,6 +13,9 @@ import { relative as relative$1 } from "node:path";
13
13
  import Gitignore from "gitignore-fs";
14
14
  import { Minimatch, minimatch } from "minimatch";
15
15
  import { errorMessageFrom as errorMessageFrom$1 } from "@moeru/std";
16
+ //#region package.json
17
+ var version = "0.0.12";
18
+ //#endregion
16
19
  //#region src/cli/commands/command.ts
17
20
  function defineCommand(node) {
18
21
  return node;
@@ -2082,6 +2085,10 @@ const commandTree = [
2082
2085
  //#endregion
2083
2086
  //#region src/cli/cli.ts
2084
2087
  async function executeCli(argv, io) {
2088
+ if (argv.includes("--version") || argv.includes("-v")) {
2089
+ io.stdout.write(`${version}\n`);
2090
+ return 0;
2091
+ }
2085
2092
  const cli = cac("alint");
2086
2093
  const setupNoInteractive = argv.includes("-N") || argv.includes("--no-interactive");
2087
2094
  const globalOptions = { outputLanguage: parseStringOption(argv, ["--lang", "-l"]) };
@@ -2090,7 +2097,7 @@ async function executeCli(argv, io) {
2090
2097
  pendingResult = result;
2091
2098
  return result;
2092
2099
  };
2093
- cli.option("--no-cache", "Disable cache for this run").option("--cache-location <path>", "Path to the alint cache file or directory").option("--config <path>", "Path to alint config file").option("--file-concurrency <count>", "Number of files to lint concurrently").option("--format <format>", "Reporter format", { default: "stylish" }).option("--model <model>", "Force a model override").option("-l, --lang <language>", "Ask model-backed rules to write diagnostics in this language").option("--progress", "Show run progress").option("--rule-concurrency <count>", "Number of rules to run concurrently within a file").option("--no-stats", "Do not record run stats for this run").option("--timeout-ms <ms>", "Rule execution timeout in milliseconds").help();
2100
+ cli.option("--no-cache", "Disable cache for this run").option("--cache-location <path>", "Path to the alint cache file or directory").option("--config <path>", "Path to alint config file").option("--file-concurrency <count>", "Number of files to lint concurrently").option("--format <format>", "Reporter format", { default: "stylish" }).option("--model <model>", "Force a model override").option("-l, --lang <language>", "Ask model-backed rules to write diagnostics in this language").option("--progress", "Show run progress").option("--rule-concurrency <count>", "Number of rules to run concurrently within a file").option("--no-stats", "Do not record run stats for this run").option("--timeout-ms <ms>", "Rule execution timeout in milliseconds").version(version).help();
2094
2101
  registerCommandTree(cli, commandTree, {
2095
2102
  globalOptions,
2096
2103
  interceptConsoleOutput,
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { i as formatJson, n as formatDiagnostics, r as formatStylish, t as executeCli } from "./cli-D4YC9Jnb.mjs";
1
+ import { i as formatJson, n as formatDiagnostics, r as formatStylish, t as executeCli } from "./cli-DmIOO6F8.mjs";
2
2
  export { executeCli, formatDiagnostics, formatJson, formatStylish };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@alint-js/cli",
3
3
  "type": "module",
4
- "version": "0.0.10",
4
+ "version": "0.0.12",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.mts",
@@ -25,11 +25,15 @@
25
25
  "pathe": "^2.0.3",
26
26
  "table": "^6.9.0",
27
27
  "tinyrainbow": "^3.1.0",
28
- "@alint-js/core": "0.0.10",
29
- "@alint-js/config": "0.0.10"
28
+ "@alint-js/config": "0.0.12",
29
+ "@alint-js/core": "0.0.12"
30
30
  },
31
31
  "devDependencies": {
32
+ "@pnpm/find-workspace-dir": "^1000.1.5",
32
33
  "@types/node": "^26.0.1",
34
+ "local-pkg": "^1.2.1",
35
+ "mlly": "^1.8.2",
36
+ "tinyexec": "^1.2.4",
33
37
  "tsdown": "^0.22.3",
34
38
  "typescript": "^6.0.3",
35
39
  "vitest": "^4.1.9"
@@ -37,6 +41,7 @@
37
41
  "scripts": {
38
42
  "alint": "node ./dist/bin/index.mjs",
39
43
  "build": "tsdown",
44
+ "build:binary": "tsdown --config tsdown.binary.config.ts --config-loader unrun",
40
45
  "typecheck": "tsc -p tsconfig.json --noEmit",
41
46
  "test": "vitest run --config vitest.config.ts"
42
47
  }