@alint-js/cli 0.0.6 → 0.0.8

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as executeCli } from "../cli-C4PnKXoK.mjs";
2
+ import { t as executeCli } from "../cli-D7cUTZnE.mjs";
3
3
  import process from "node:process";
4
4
  //#region src/bin/index.ts
5
5
  executeCli(process.argv, {
@@ -6,18 +6,20 @@ import { AlintRunError, hasDiscoveryFilePatterns, matchesDiscoveryFile, normaliz
6
6
  import { relative, resolve } from "pathe";
7
7
  import { getBorderCharacters, table } from "table";
8
8
  import { errorMessageFrom } from "@moeru/std/error";
9
- import { readdir, stat } from "node:fs/promises";
9
+ import { readFile, readdir, stat } from "node:fs/promises";
10
10
  import c, { createColors } from "tinyrainbow";
11
11
  import cliSpinners from "cli-spinners";
12
12
  import { relative as relative$1 } from "node:path";
13
13
  import Gitignore from "gitignore-fs";
14
14
  import { minimatch } from "minimatch";
15
+ import { errorMessageFrom as errorMessageFrom$1 } from "@moeru/std";
15
16
  //#region src/cli/commands/command.ts
16
17
  function defineCommand(node) {
17
18
  return node;
18
19
  }
19
- function registerCommandTree(cli, nodes, context, setPendingResult) {
20
+ function registerCommandTree(cli, nodes, context, setPendingResult, help = {}) {
20
21
  for (const node of nodes) registerRootCommand(cli, node, context, setPendingResult);
22
+ cli.globalCommand.helpCallback = (sections) => formatCommandHelp(sections, nodes, cli.rawArgs, help);
21
23
  }
22
24
  function collectCommandOptions(node) {
23
25
  const options = /* @__PURE__ */ new Map();
@@ -36,9 +38,91 @@ function dispatchCommand(context, node, args, options, path) {
36
38
  if (!node.action) return Promise.resolve(reportUnknownCommand(context, path, args));
37
39
  return Promise.resolve(node.action(context, ...parseCommandArguments(node, args), options));
38
40
  }
41
+ function formatChildCommandHelp(parentPath, child) {
42
+ const parts = [...parentPath, child.name];
43
+ if (!child.children && child.arguments) parts.push(child.arguments);
44
+ return {
45
+ description: child.description,
46
+ pattern: parts.join(" ")
47
+ };
48
+ }
49
+ function formatCommandHelp(sections, nodes, argv, rootHelp) {
50
+ const helpPath = resolveHelpPath(nodes, argv);
51
+ const node = helpPath.at(-1)?.node;
52
+ if (!node) return insertExamplesSection(insertHelpSection(sections, rootHelp), rootHelp);
53
+ const path = helpPath.map((item) => item.node.name);
54
+ const normalizedSections = rewriteUsageSection(insertExamplesSection(insertHelpSection(sections.filter((section) => section.title !== "Options"), node), node), path, node);
55
+ if (!node.children) {
56
+ const optionSection = formatOptionsSection(node.options ?? []);
57
+ return optionSection ? [...normalizedSections, optionSection] : normalizedSections;
58
+ }
59
+ const commands = (node.children ?? []).map((child) => formatChildCommandHelp(path, child));
60
+ const longestCommand = Math.max(...commands.map((command) => command.pattern.length));
61
+ const commandSection = {
62
+ body: commands.map((command) => ` ${command.pattern.padEnd(longestCommand)} ${command.description}`).join("\n"),
63
+ title: "Commands"
64
+ };
65
+ const usageIndex = normalizedSections.findIndex((section) => section.title === "Usage");
66
+ if (usageIndex === -1) return [commandSection, ...normalizedSections];
67
+ return [
68
+ ...normalizedSections.slice(0, usageIndex + 1),
69
+ commandSection,
70
+ ...normalizedSections.slice(usageIndex + 1)
71
+ ];
72
+ }
73
+ function formatExamples(examples) {
74
+ return examples.map((example) => example.split("\n").map((line) => ` ${line}`).join("\n")).join("\n\n");
75
+ }
76
+ function formatOptionDescription(option) {
77
+ if (option.config?.default === void 0) return option.description;
78
+ return `${option.description} (default: ${option.config.default})`;
79
+ }
80
+ function formatOptionsSection(options) {
81
+ if (options.length === 0) return;
82
+ const rows = options.map((option) => ({
83
+ description: formatOptionDescription(option),
84
+ flags: option.flags
85
+ }));
86
+ const longestFlag = Math.max(...rows.map((row) => row.flags.length));
87
+ return {
88
+ body: rows.map((row) => ` ${row.flags.padEnd(longestFlag)} ${row.description}`).join("\n"),
89
+ title: "Options"
90
+ };
91
+ }
39
92
  function formatUnknownCommand(path, args) {
40
93
  return [...path, ...args].filter(Boolean).join(" ");
41
94
  }
95
+ function formatUsagePattern(path, node) {
96
+ const parts = [...path];
97
+ if (!node.children && node.arguments) parts.push(node.arguments);
98
+ return parts.join(" ");
99
+ }
100
+ function insertExamplesSection(sections, node) {
101
+ if (!node.examples?.length) return sections;
102
+ const usageIndex = sections.findIndex((section) => section.title === "Usage");
103
+ const examplesSection = {
104
+ body: formatExamples(node.examples),
105
+ title: "Examples"
106
+ };
107
+ if (usageIndex === -1) return [...sections, examplesSection];
108
+ return [
109
+ ...sections.slice(0, usageIndex),
110
+ examplesSection,
111
+ ...sections.slice(usageIndex)
112
+ ];
113
+ }
114
+ function insertHelpSection(sections, node) {
115
+ const help = node.help ?? node.description;
116
+ if (!help) return sections;
117
+ const usageIndex = sections.findIndex((section) => section.title === "Usage");
118
+ const helpSection = { body: help };
119
+ if (usageIndex === -1) return [...sections, helpSection];
120
+ return [
121
+ ...sections.slice(0, usageIndex),
122
+ helpSection,
123
+ ...sections.slice(usageIndex)
124
+ ];
125
+ }
42
126
  function parseCommandArguments(node, args) {
43
127
  if (!node.arguments) return [];
44
128
  const parts = node.arguments.split(/\s+/u).filter(Boolean);
@@ -67,12 +151,56 @@ function registerRootCommand(cli, node, context, setPendingResult) {
67
151
  });
68
152
  }
69
153
  function reportUnknownCommand(context, path, args) {
70
- context.io.stderr.write(`unknown config command: ${formatUnknownCommand(path, args)}\n`);
154
+ context.io.stderr.write(`unknown command: ${formatUnknownCommand(path, args)}\n`);
71
155
  return 2;
72
156
  }
157
+ function resolveHelpPath(nodes, argv) {
158
+ const path = [];
159
+ let currentNodes = nodes;
160
+ let skipNext = false;
161
+ for (const arg of argv.slice(2)) {
162
+ if (skipNext) {
163
+ skipNext = false;
164
+ continue;
165
+ }
166
+ if (arg.startsWith("-")) {
167
+ skipNext = shouldSkipOptionValue(arg);
168
+ continue;
169
+ }
170
+ const node = currentNodes.find((item) => item.name === arg || item.alias?.includes(arg));
171
+ if (!node) break;
172
+ path.push({ node });
173
+ currentNodes = node.children ?? [];
174
+ }
175
+ return path;
176
+ }
177
+ function rewriteUsageSection(sections, path, node) {
178
+ return sections.map((section) => section.title === "Usage" ? {
179
+ ...section,
180
+ body: ` $ alint ${formatUsagePattern(path, node)}`
181
+ } : section);
182
+ }
183
+ function shouldSkipOptionValue(arg) {
184
+ if (arg.includes("=")) return false;
185
+ return [
186
+ "--cache-location",
187
+ "--config",
188
+ "--file-concurrency",
189
+ "--format",
190
+ "--model",
191
+ "--provider-endpoint",
192
+ "--provider-header",
193
+ "--provider-id",
194
+ "--provider-model",
195
+ "--rule-concurrency",
196
+ "--timeout-ms",
197
+ "-f",
198
+ "-l"
199
+ ].includes(arg);
200
+ }
73
201
  //#endregion
74
202
  //#region src/cli/commands/config/inspect.ts
75
- const inspect$1 = defineCommand({
203
+ const inspect$2 = defineCommand({
76
204
  action: (context, file, options) => runConfigInspectCommand(file, options.config, context.io),
77
205
  arguments: "<file>",
78
206
  description: "Inspect resolved config for a file",
@@ -243,6 +371,8 @@ const models = defineCommand({
243
371
  }
244
372
  },
245
373
  description: "Probe OpenAI-compatible models",
374
+ examples: [["# Probe a provider endpoint and print available model ids", "alint config models probe --endpoint https://openrouter.ai/api/v1"].join("\n"), ["# Probe an endpoint that requires an authorization header", "alint config models probe --endpoint https://api.example.com/v1 --provider-header \"Authorization=Bearer $TOKEN\""].join("\n")],
375
+ help: ["Probe an OpenAI-compatible models endpoint before saving it in setup config.", "The command calls the provider models endpoint using the supplied endpoint and optional headers, then prints the model ids returned by that provider."].join("\n\n"),
246
376
  name: "probe",
247
377
  options: [{
248
378
  description: "Provider endpoint",
@@ -269,6 +399,7 @@ const models = defineCommand({
269
399
  })
270
400
  ],
271
401
  description: "Manage configured models",
402
+ help: ["Inspect, list, and probe model entries from alint setup configuration.", "Model entries describe the provider/model ids alint can use for model-backed rules. Probe endpoints before saving them when you want to verify what model ids a provider exposes."].join("\n\n"),
272
403
  name: "models"
273
404
  });
274
405
  //#endregion
@@ -313,7 +444,7 @@ const probe = defineCommand({
313
444
  //#region src/cli/commands/config/config.ts
314
445
  const config = defineCommand({
315
446
  children: [
316
- inspect$1,
447
+ inspect$2,
317
448
  models,
318
449
  defineCommand({
319
450
  children: [
@@ -339,6 +470,26 @@ const config = defineCommand({
339
470
  })
340
471
  ],
341
472
  description: "Manage alint configuration",
473
+ examples: [
474
+ ["# Show the effective alint config for a file", "alint config inspect src/index.ts"].join("\n"),
475
+ ["# Inspect a file using a custom config path", "alint --config alint.config.ts config inspect src/index.ts"].join("\n"),
476
+ [
477
+ "# List configured providers and models",
478
+ "alint config providers list",
479
+ "alint config models list"
480
+ ].join("\n"),
481
+ [
482
+ "# Show one configured provider or model by id",
483
+ "alint config providers show openrouter",
484
+ "alint config models show z-ai/glm-5.2"
485
+ ].join("\n"),
486
+ [
487
+ "# Probe an OpenAI-compatible endpoint before saving it",
488
+ "alint config providers probe --endpoint https://openrouter.ai/api/v1",
489
+ "alint config models probe --endpoint https://openrouter.ai/api/v1"
490
+ ].join("\n")
491
+ ],
492
+ help: ["Inspect and update alint setup/configuration state.", "Use these commands to understand the effective config for a file, inspect saved provider/model setup, and probe OpenAI-compatible endpoints before using them in model-backed rules."].join("\n\n"),
342
493
  name: "config"
343
494
  });
344
495
  //#endregion
@@ -900,7 +1051,10 @@ function resolveRunnerCacheConfig(setupCache, configCache, options) {
900
1051
  //#endregion
901
1052
  //#region src/cli/commands/lint/index.ts
902
1053
  const lint = defineCommand({
903
- action: (context, files = [], options) => runLintCommand(files, options, context.io, context.interceptConsoleOutput),
1054
+ action: (context, files = [], options) => runLintCommand(files, {
1055
+ ...options,
1056
+ outputLanguage: options.lang ?? context.globalOptions.outputLanguage
1057
+ }, context.io, context.interceptConsoleOutput),
904
1058
  alias: ["!"],
905
1059
  arguments: "[...files]",
906
1060
  default: true,
@@ -939,6 +1093,7 @@ async function runLintCommand(files, options, io, interceptConsoleOutput) {
939
1093
  cwd: io.cwd,
940
1094
  files: lintFiles,
941
1095
  modelOverride: options.model,
1096
+ outputLanguage: options.outputLanguage,
942
1097
  progress: progress?.reporter,
943
1098
  runner,
944
1099
  setupConfig
@@ -962,6 +1117,85 @@ function shouldEnableProgress(options, io) {
962
1117
  return options.format === "stylish" && io.stderr.isTTY === true;
963
1118
  }
964
1119
  //#endregion
1120
+ //#region src/cli/commands/output-inspect/inspect.ts
1121
+ const inspect$1 = defineCommand({
1122
+ action: (context, file, options) => inspectOutputFile(context.io, file, options),
1123
+ arguments: "<file>",
1124
+ description: "Inspect saved alint JSON output",
1125
+ examples: [
1126
+ ["# Pretty-print saved JSON output", "alint output inspect alint-output.json"].join("\n"),
1127
+ ["# Validate and reprint normalized JSON", "alint output inspect alint-output.json --format json"].join("\n"),
1128
+ [
1129
+ "# Save a run as JSON, then inspect it later",
1130
+ "alint --format json src > alint-output.json",
1131
+ "alint output inspect alint-output.json"
1132
+ ].join("\n")
1133
+ ],
1134
+ help: ["Read a saved alint JSON run result and render it with a reporter.", "Defaults to the human-friendly stylish reporter, which groups diagnostics by file and prints the same summary style as a normal alint run. Use `--format json` to validate the file and reprint normalized JSON."].join("\n\n"),
1135
+ name: "inspect",
1136
+ options: [{
1137
+ config: { default: "stylish" },
1138
+ description: "Reporter used to render the parsed run result. One of: stylish, json",
1139
+ flags: "-f, --format <format>"
1140
+ }]
1141
+ });
1142
+ async function inspectOutputFile(io, file, options) {
1143
+ const filePath = resolve(io.cwd, file);
1144
+ let text;
1145
+ try {
1146
+ text = await readFile(filePath, "utf8");
1147
+ } catch (error) {
1148
+ io.stderr.write(`Could not read output file "${file}": ${errorMessageFrom$1(error)}\n`);
1149
+ return 2;
1150
+ }
1151
+ let parsed;
1152
+ try {
1153
+ parsed = JSON.parse(text);
1154
+ } catch (error) {
1155
+ io.stderr.write(`Could not parse output file "${file}": ${errorMessageFrom$1(error)}\n`);
1156
+ return 2;
1157
+ }
1158
+ if (!isRunResult(parsed)) {
1159
+ io.stderr.write(`Invalid alint output "${file}": expected a run result with diagnostics and usage.\n`);
1160
+ return 2;
1161
+ }
1162
+ try {
1163
+ io.stdout.write(formatDiagnostics(options.format, parsed, { color: io.stdout.isTTY === true }));
1164
+ } catch (error) {
1165
+ io.stderr.write(`${errorMessageFrom$1(error)}\n`);
1166
+ return 2;
1167
+ }
1168
+ return parsed.diagnostics.length > 0 ? 1 : 0;
1169
+ }
1170
+ function isDiagnostic(value) {
1171
+ if (!isRecord(value)) return false;
1172
+ if (typeof value.filePath !== "string" || typeof value.message !== "string" || typeof value.ruleId !== "string" || value.severity !== "warn" && value.severity !== "error") return false;
1173
+ if (value.loc === void 0) return true;
1174
+ if (!isRecord(value.loc)) return false;
1175
+ if (value.loc.start === void 0) return true;
1176
+ if (!isRecord(value.loc.start)) return false;
1177
+ return (value.loc.start.line === void 0 || typeof value.loc.start.line === "number") && (value.loc.start.column === void 0 || typeof value.loc.start.column === "number");
1178
+ }
1179
+ function isRecord(value) {
1180
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1181
+ }
1182
+ function isRunResult(value) {
1183
+ if (!isRecord(value)) return false;
1184
+ if (!Array.isArray(value.diagnostics) || !value.diagnostics.every(isDiagnostic)) return false;
1185
+ return isUsage(value.usage);
1186
+ }
1187
+ function isUsage(value) {
1188
+ return isRecord(value) && typeof value.inputTokens === "number" && typeof value.outputTokens === "number" && Array.isArray(value.records) && typeof value.totalTokens === "number";
1189
+ }
1190
+ //#endregion
1191
+ //#region src/cli/commands/output-inspect/output.ts
1192
+ const output = defineCommand({
1193
+ children: [inspect$1],
1194
+ description: "Inspect alint output files",
1195
+ help: ["Inspect saved alint run outputs without rerunning rules or model calls.", "Use this when you already have JSON from `alint --format json` and want to render, validate, or transform that result for humans or tools."].join("\n\n"),
1196
+ name: "output"
1197
+ });
1198
+ //#endregion
965
1199
  //#region src/cli/commands/setup/interactive.ts
966
1200
  const nonTtyMessage = "interactive setup requires a TTY. Use -N/--no-interactive with --provider-id and --provider-endpoint.\n";
967
1201
  const backValue = "__alint_back__";
@@ -1225,6 +1459,12 @@ const setup = defineCommand({
1225
1459
  noInteractive: context.setupNoInteractive
1226
1460
  }, context.io),
1227
1461
  description: "Write alint provider configuration",
1462
+ examples: [
1463
+ ["# Configure a provider interactively", "alint setup"].join("\n"),
1464
+ ["# Write a project-local provider config without prompts", "alint setup --local -N --provider-id openrouter --provider-endpoint https://openrouter.ai/api/v1 --provider-model z-ai/glm-5.2"].join("\n"),
1465
+ ["# Add an authorization header for an OpenAI-compatible provider", "alint setup --local -N --provider-id openrouter --provider-endpoint https://openrouter.ai/api/v1 --provider-model z-ai/glm-5.2 --provider-header \"Authorization=Bearer $OPENROUTER_API_KEY\""].join("\n")
1466
+ ],
1467
+ help: ["Write alint provider and model configuration.", "Run without flags for interactive setup, or use `-N` with provider flags in scripts and CI. Use `--local` when the provider config should live with the current project instead of the user-level setup file."].join("\n\n"),
1228
1468
  name: "setup",
1229
1469
  options: [
1230
1470
  {
@@ -1295,6 +1535,7 @@ function toArray(value) {
1295
1535
  const commandTree = [
1296
1536
  setup,
1297
1537
  config,
1538
+ output,
1298
1539
  lint
1299
1540
  ];
1300
1541
  //#endregion
@@ -1302,17 +1543,36 @@ const commandTree = [
1302
1543
  async function executeCli(argv, io) {
1303
1544
  const cli = cac("alint");
1304
1545
  const setupNoInteractive = argv.includes("-N") || argv.includes("--no-interactive");
1546
+ const globalOptions = { outputLanguage: parseStringOption(argv, ["--lang", "-l"]) };
1305
1547
  let pendingResult;
1306
1548
  const setPendingResult = (result) => {
1307
1549
  pendingResult = result;
1308
1550
  return result;
1309
1551
  };
1310
- 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("--progress", "Show run progress").option("--rule-concurrency <count>", "Number of rules to run concurrently within a file").option("--timeout-ms <ms>", "Rule execution timeout in milliseconds").help();
1552
+ 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("--timeout-ms <ms>", "Rule execution timeout in milliseconds").help();
1311
1553
  registerCommandTree(cli, commandTree, {
1554
+ globalOptions,
1312
1555
  interceptConsoleOutput,
1313
1556
  io,
1314
1557
  setupNoInteractive
1315
- }, setPendingResult);
1558
+ }, setPendingResult, {
1559
+ examples: [
1560
+ ["# Configure a provider interactively", "alint setup"].join("\n"),
1561
+ ["# Run alint on source files with the default stylish reporter", "alint src"].join("\n"),
1562
+ [
1563
+ "# Run alint and save machine-readable JSON for later inspection",
1564
+ "alint --format json src > alint-output.json",
1565
+ "alint output inspect alint-output.json"
1566
+ ].join("\n"),
1567
+ ["# Inspect the effective config that applies to a file", "alint config inspect src/index.ts"].join("\n"),
1568
+ [
1569
+ "# List configured providers and models",
1570
+ "alint config providers list",
1571
+ "alint config models list"
1572
+ ].join("\n")
1573
+ ],
1574
+ help: ["AI-assisted linting for source files, saved run outputs, and provider/model setup.", "Start with `alint setup` to configure a model provider, run `alint <files>` to analyze files, and use `alint output inspect` to read saved JSON output without rerunning rules."].join("\n\n")
1575
+ });
1316
1576
  const restoreConsole = interceptConsoleOutput(shouldCaptureHelp(argv) ? io.stdout : io.stderr);
1317
1577
  try {
1318
1578
  cli.parse(argv);
@@ -1344,6 +1604,16 @@ function interceptConsoleOutput(stdout) {
1344
1604
  cliConsole.log = originalConsoleLog;
1345
1605
  };
1346
1606
  }
1607
+ function parseStringOption(argv, flags) {
1608
+ for (let index = 0; index < argv.length; index += 1) {
1609
+ const value = argv[index];
1610
+ for (const flag of flags) {
1611
+ const equalsPrefix = `${flag}=`;
1612
+ if (value?.startsWith(equalsPrefix)) return value.slice(equalsPrefix.length);
1613
+ if (value === flag) return argv[index + 1];
1614
+ }
1615
+ }
1616
+ }
1347
1617
  function shouldCaptureHelp(argv) {
1348
1618
  return argv.includes("--help") || argv.includes("-h");
1349
1619
  }
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-C4PnKXoK.mjs";
1
+ import { i as formatJson, n as formatDiagnostics, r as formatStylish, t as executeCli } from "./cli-D7cUTZnE.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.6",
4
+ "version": "0.0.8",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.mts",
@@ -25,8 +25,8 @@
25
25
  "pathe": "^2.0.3",
26
26
  "table": "^6.9.0",
27
27
  "tinyrainbow": "^3.1.0",
28
- "@alint-js/config": "0.0.6",
29
- "@alint-js/core": "0.0.6"
28
+ "@alint-js/core": "0.0.8",
29
+ "@alint-js/config": "0.0.8"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^26.0.1",