@alint-js/cli 0.0.21 → 0.0.22

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 CHANGED
@@ -274,6 +274,41 @@ Use `alint plugin install` after adding or changing plugin sources.
274
274
  - **Package:** `@scope/alint-plugin-example1@1.2.3` downloads that exact version into `.alint/plugins/store`, verifies its integrity, and loads its root export.
275
275
  - **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.
276
276
 
277
+ #### Declarative local rule plugins
278
+
279
+ For simple prompt-backed rules, a local plugin directory can omit `package.json` and contain one or more `rule.alint.*` files:
280
+
281
+ ```toml
282
+ # rules/architecture/semantic/rule.alint.toml
283
+ name = "semantic-boundary"
284
+ builtInAgent = "basic-structured"
285
+ instruction = """
286
+ Find semantic boundary problems in the reviewed source.
287
+ Report concrete findings with line numbers and short suggestions.
288
+ """
289
+ includeFiles = [ "src/**/*.py" ]
290
+ excludeFiles = [
291
+ "**/*_test.py",
292
+ "**/vendor/**",
293
+ ]
294
+ ```
295
+
296
+ Reference the directory through the same static `plugins` table:
297
+
298
+ ```toml
299
+ [[config.group]]
300
+ files = [ "src/**/*.py" ]
301
+ language = "text/plain"
302
+
303
+ [config.group.plugins]
304
+ arch = "./rules/architecture"
305
+
306
+ [config.group.rules]
307
+ "arch/semantic-boundary" = "warn"
308
+ ```
309
+
310
+ `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.
311
+
277
312
  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.
278
313
 
279
314
  Local plugins execute as trusted Node.js code. Directory containment checks validate the installed source; they are not a sandbox.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as executeCli } from "../cli-Dey9TSc8.mjs";
2
+ import { t as executeCli } from "../cli-CmLooFry.mjs";
3
3
  import process from "node:process";
4
4
  //#region src/bin/index.ts
5
5
  executeCli(process.argv, {
@@ -14,7 +14,7 @@ import Gitignore from "gitignore-fs";
14
14
  import { Minimatch, minimatch } from "minimatch";
15
15
  import { errorMessageFrom as errorMessageFrom$1 } from "@moeru/std";
16
16
  //#region package.json
17
- var version = "0.0.21";
17
+ var version = "0.0.22";
18
18
  //#endregion
19
19
  //#region src/cli/commands/command.ts
20
20
  function defineCommand(node) {
@@ -200,6 +200,7 @@ function shouldSkipOptionValue(arg) {
200
200
  "--rule-concurrency",
201
201
  "--since",
202
202
  "--timeout-ms",
203
+ "-c",
203
204
  "-f",
204
205
  "-l"
205
206
  ].includes(arg);
@@ -258,7 +259,8 @@ function createProviderId(endpoint, existingIds) {
258
259
  let base = "provider";
259
260
  try {
260
261
  const url = new URL(endpoint);
261
- base = findProviderSetupSourceByEndpoint(url)?.defaultProviderId ?? (url.hostname.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "").toLowerCase() || "provider");
262
+ const source = findProviderSetupSourceByEndpoint(url);
263
+ base = normalizeProviderIdBase(source?.defaultProviderId ?? source?.label ?? url.hostname);
262
264
  } catch {
263
265
  base = "provider";
264
266
  }
@@ -362,6 +364,9 @@ function formatTable(rows) {
362
364
  drawHorizontalLine: () => false
363
365
  });
364
366
  }
367
+ function normalizeProviderIdBase(value) {
368
+ return value.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "").toLowerCase() || "provider";
369
+ }
365
370
  //#endregion
366
371
  //#region src/cli/commands/config/setup-config.ts
367
372
  async function loadMergedSetupConfig(io) {
@@ -1715,6 +1720,53 @@ const plugin = defineCommand({
1715
1720
  //#region src/cli/commands/setup/interactive.ts
1716
1721
  const nonTtyMessage = "interactive setup requires a TTY. Use -N/--no-interactive with --provider-id and --provider-endpoint.\n";
1717
1722
  const backValue = "__alint_back__";
1723
+ function applyDefaultAlias(config, target) {
1724
+ return {
1725
+ ...config,
1726
+ providers: config.providers.map((provider) => ({
1727
+ ...provider,
1728
+ headers: provider.headers === void 0 ? void 0 : { ...provider.headers },
1729
+ models: provider.models.map((model) => {
1730
+ const aliasesWithoutDefault = (model.aliases ?? []).filter((alias) => alias !== "default");
1731
+ const aliases = provider.id === target.providerId && model.id === target.modelId ? [...aliasesWithoutDefault, "default"] : aliasesWithoutDefault;
1732
+ return {
1733
+ ...model,
1734
+ aliases: aliases.length > 0 ? aliases : void 0,
1735
+ capabilities: model.capabilities === void 0 ? void 0 : [...model.capabilities],
1736
+ defaultParams: model.defaultParams === void 0 ? void 0 : { ...model.defaultParams }
1737
+ };
1738
+ })
1739
+ }))
1740
+ };
1741
+ }
1742
+ function createDefaultModelCandidates(config, newProviderId, newModelIds) {
1743
+ const candidates = [];
1744
+ const seen = /* @__PURE__ */ new Set();
1745
+ const newModelIdSet = new Set(newModelIds);
1746
+ const allModels = config.providers.flatMap((provider) => provider.models.map((model) => ({
1747
+ isCurrentDefault: (model.aliases ?? []).includes("default"),
1748
+ isNew: provider.id === newProviderId && newModelIdSet.has(model.id),
1749
+ model,
1750
+ provider,
1751
+ value: createDefaultModelCandidateValue(provider.id, model.id)
1752
+ })));
1753
+ const addCandidate = (candidate) => {
1754
+ if (candidate === void 0 || seen.has(candidate.value)) return;
1755
+ seen.add(candidate.value);
1756
+ candidates.push({
1757
+ isCurrentDefault: candidate.isCurrentDefault,
1758
+ isNew: candidate.isNew,
1759
+ label: `${candidate.provider.id} / ${candidate.model.id}`,
1760
+ modelId: candidate.model.id,
1761
+ providerId: candidate.provider.id,
1762
+ value: candidate.value
1763
+ });
1764
+ };
1765
+ addCandidate(allModels.find((candidate) => candidate.isCurrentDefault));
1766
+ for (const modelId of newModelIds) addCandidate(allModels.find((candidate) => candidate.provider.id === newProviderId && candidate.model.id === modelId));
1767
+ for (const candidate of allModels) addCandidate(candidate);
1768
+ return candidates;
1769
+ }
1718
1770
  function formatProbeModelsFailure(endpoint, error) {
1719
1771
  const hint = endpoint.startsWith("https://localhost:11434") ? " Ollama usually uses http://localhost:11434/v1." : "";
1720
1772
  return `Could not probe models: ${errorMessageFrom(error)}.${hint}`;
@@ -1786,9 +1838,8 @@ async function runInteractiveSetup(io) {
1786
1838
  if (step === "providerId") {
1787
1839
  const existingConfig = await loadSetupConfig(getConfigPath(io, draft.scope ?? "global"));
1788
1840
  const providerId = await prompts.text({
1789
- defaultValue: draft.providerId ?? createProviderId(draft.endpoint ?? "", new Set(existingConfig.providers.map((provider) => provider.id))),
1790
- message: "Provider id",
1791
- placeholder: "Type .. to go back",
1841
+ initialValue: draft.providerId ?? createProviderId(draft.endpoint ?? "", new Set(existingConfig.providers.map((provider) => provider.id))),
1842
+ message: "Provider id (type .. to go back)",
1792
1843
  validate: (value) => isBackInput(value ?? "") || (value ?? "").trim().length > 0 ? void 0 : "Provider id is required."
1793
1844
  });
1794
1845
  if (prompts.isCancel(providerId)) return cancelPrompt();
@@ -1803,9 +1854,9 @@ async function runInteractiveSetup(io) {
1803
1854
  }
1804
1855
  if (step === "headers") {
1805
1856
  const headerInput = await prompts.text({
1806
- defaultValue: draft.headerInput ?? "",
1807
- message: "Headers",
1808
- placeholder: "Authorization=Bearer token, X-Test=true; type .. to go back",
1857
+ initialValue: draft.headerInput,
1858
+ message: "Headers (leave empty to skip; type .. to go back)",
1859
+ placeholder: "Authorization=Bearer token, X-Test=true",
1809
1860
  validate: (value) => {
1810
1861
  if (isBackInput(value ?? "")) return;
1811
1862
  try {
@@ -1841,26 +1892,69 @@ async function runInteractiveSetup(io) {
1841
1892
  continue;
1842
1893
  }
1843
1894
  if (step === "defaultAlias") {
1844
- const addDefaultAlias = await prompts.select({
1895
+ const defaultAliasAction = await prompts.select({
1845
1896
  message: `Add alias "default" to ${draft.selectedModels?.[0]}?`,
1846
- options: withBackOption([{
1847
- label: "Yes",
1848
- value: "yes"
1849
- }, {
1850
- label: "No",
1851
- value: "no"
1852
- }])
1897
+ options: withBackOption([
1898
+ {
1899
+ label: "Yes",
1900
+ value: "yes"
1901
+ },
1902
+ {
1903
+ label: "No",
1904
+ value: "no"
1905
+ },
1906
+ {
1907
+ label: "Select another",
1908
+ value: "selectAnother"
1909
+ }
1910
+ ])
1853
1911
  });
1854
- if (prompts.isCancel(addDefaultAlias)) return cancelPrompt();
1855
- if (addDefaultAlias === backValue) {
1912
+ if (prompts.isCancel(defaultAliasAction)) return cancelPrompt();
1913
+ if (defaultAliasAction === backValue) {
1856
1914
  step = "models";
1857
1915
  continue;
1858
1916
  }
1859
- draft.addDefaultAlias = addDefaultAlias === "yes";
1917
+ if (defaultAliasAction === "selectAnother") {
1918
+ step = "selectDefaultModel";
1919
+ continue;
1920
+ }
1921
+ draft.defaultAliasTarget = defaultAliasAction === "yes" ? {
1922
+ modelId: draft.selectedModels?.[0] ?? "",
1923
+ providerId: (draft.providerId ?? "").trim()
1924
+ } : void 0;
1925
+ step = "confirm";
1926
+ continue;
1927
+ }
1928
+ if (step === "selectDefaultModel") {
1929
+ const existingConfig = await loadSetupConfig(getConfigPath(io, draft.scope ?? "global"));
1930
+ const draftProvider = createProviderConfig((draft.providerId ?? "").trim(), (draft.endpoint ?? "").trim(), draft.headers, draft.selectedModels ?? []);
1931
+ const candidates = createDefaultModelCandidates(mergeSetupConfigs(existingConfig, {
1932
+ providers: [draftProvider],
1933
+ version: 1
1934
+ }), draftProvider.id, draft.selectedModels ?? []);
1935
+ const defaultModel = await prompts.select({
1936
+ message: "Select default model",
1937
+ options: withBackOption(candidates.map((candidate) => ({
1938
+ hint: candidate.isCurrentDefault ? "current default" : candidate.isNew ? "new" : void 0,
1939
+ label: candidate.label,
1940
+ value: candidate.value
1941
+ })))
1942
+ });
1943
+ if (prompts.isCancel(defaultModel)) return cancelPrompt();
1944
+ if (defaultModel === backValue) {
1945
+ step = "defaultAlias";
1946
+ continue;
1947
+ }
1948
+ const candidate = candidates.find((item) => item.value === defaultModel);
1949
+ if (candidate === void 0) return cancelPrompt();
1950
+ draft.defaultAliasTarget = {
1951
+ modelId: candidate.modelId,
1952
+ providerId: candidate.providerId
1953
+ };
1860
1954
  step = "confirm";
1861
1955
  continue;
1862
1956
  }
1863
- const nextProvider = createProviderConfig((draft.providerId ?? "").trim(), (draft.endpoint ?? "").trim(), draft.headers, draft.selectedModels ?? [], draft.addDefaultAlias ?? true);
1957
+ const nextProvider = createProviderConfig((draft.providerId ?? "").trim(), (draft.endpoint ?? "").trim(), draft.headers, draft.selectedModels ?? []);
1864
1958
  const confirmed = await prompts.select({
1865
1959
  message: [
1866
1960
  `Write ${draft.scope} setup config?`,
@@ -1883,10 +1977,11 @@ async function runInteractiveSetup(io) {
1883
1977
  }
1884
1978
  if (confirmed === "no") return cancelPrompt();
1885
1979
  const configPath = getConfigPath(io, draft.scope ?? "global");
1886
- await writeSetupConfig(configPath, mergeSetupConfigs(await loadSetupConfig(configPath), {
1980
+ const mergedConfig = mergeSetupConfigs(await loadSetupConfig(configPath), {
1887
1981
  providers: [nextProvider],
1888
1982
  version: 1
1889
- }));
1983
+ });
1984
+ await writeSetupConfig(configPath, draft.defaultAliasTarget === void 0 ? mergedConfig : applyDefaultAlias(mergedConfig, draft.defaultAliasTarget));
1890
1985
  prompts.outro(`Wrote ${configPath}`);
1891
1986
  return 0;
1892
1987
  }
@@ -1897,13 +1992,15 @@ function withBackOption(options) {
1897
1992
  value: backValue
1898
1993
  }];
1899
1994
  }
1900
- function createProviderConfig(providerId, endpoint, headers, modelIds, addDefaultAlias) {
1995
+ function createDefaultModelCandidateValue(providerId, modelId) {
1996
+ return `${providerId}\u0000${modelId}`;
1997
+ }
1998
+ function createProviderConfig(providerId, endpoint, headers, modelIds) {
1901
1999
  return {
1902
2000
  endpoint,
1903
2001
  headers,
1904
2002
  id: providerId,
1905
- models: modelIds.map((modelId, index) => ({
1906
- aliases: index === 0 && addDefaultAlias ? ["default"] : void 0,
2003
+ models: modelIds.map((modelId) => ({
1907
2004
  id: modelId,
1908
2005
  name: modelId
1909
2006
  })),
@@ -1927,9 +2024,9 @@ async function probeModelsWithSpinner(prompts, endpoint, headers) {
1927
2024
  }
1928
2025
  async function promptEndpoint(prompts, source) {
1929
2026
  return prompts.text({
1930
- defaultValue: source.defaultEndpoint,
1931
- message: "Provider endpoint",
1932
- placeholder: `${source.defaultEndpoint ?? "https://example.test/v1"}; type .. to go back`,
2027
+ initialValue: source.defaultEndpoint,
2028
+ message: "Provider endpoint (type .. to go back)",
2029
+ placeholder: source.defaultEndpoint ?? "https://example.test/v1",
1933
2030
  validate: (value) => isBackInput(value ?? "") || (value ?? "").trim().length > 0 ? void 0 : "Provider endpoint is required."
1934
2031
  });
1935
2032
  }
@@ -2163,7 +2260,7 @@ async function executeCli(argv, io) {
2163
2260
  pendingResult = result;
2164
2261
  return result;
2165
2262
  };
2166
- 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();
2263
+ cli.option("--no-cache", "Disable cache for this run").option("--cache-location <path>", "Path to the alint cache file or directory").option("-c, --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();
2167
2264
  registerCommandTree(cli, commandTree, {
2168
2265
  globalOptions,
2169
2266
  interceptConsoleOutput,
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { i as formatJson, n as formatDiagnostics, r as formatStylish, t as executeCli } from "./cli-Dey9TSc8.mjs";
1
+ import { i as formatJson, n as formatDiagnostics, r as formatStylish, t as executeCli } from "./cli-CmLooFry.mjs";
2
2
  import { ignorePatternsAIAgents, ignorePatternsBuildOutputs, ignorePatternsCaches, ignorePatternsCommon, ignorePatternsEslintDefaults, ignorePatternsGenerated } from "@alint-js/config";
3
3
  import { defineConfig } from "@alint-js/core";
4
4
  export { defineConfig, executeCli, formatDiagnostics, formatJson, formatStylish, ignorePatternsAIAgents, ignorePatternsBuildOutputs, ignorePatternsCaches, ignorePatternsCommon, ignorePatternsEslintDefaults, ignorePatternsGenerated };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@alint-js/cli",
3
3
  "type": "module",
4
- "version": "0.0.21",
4
+ "version": "0.0.22",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.mts",
@@ -25,18 +25,14 @@
25
25
  "pathe": "^2.0.3",
26
26
  "table": "^6.9.0",
27
27
  "tinyrainbow": "^3.1.0",
28
- "@alint-js/config": "0.0.21",
29
- "@alint-js/core": "0.0.21"
28
+ "@alint-js/config": "0.0.22",
29
+ "@alint-js/core": "0.0.22"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@pnpm/find-workspace-dir": "^1000.1.5",
33
- "@types/node": "^26.0.1",
34
33
  "local-pkg": "^1.2.1",
35
34
  "mlly": "^1.8.2",
36
- "tinyexec": "^1.2.4",
37
- "tsdown": "^0.22.3",
38
- "typescript": "^6.0.3",
39
- "vitest": "^4.1.9"
35
+ "tinyexec": "^1.2.4"
40
36
  },
41
37
  "scripts": {
42
38
  "alint": "node ./dist/bin/index.mjs",