@alint-js/cli 0.0.20 → 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-CGvIT3NA.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.20";
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);
@@ -226,13 +227,40 @@ async function runConfigInspectCommand(file, configPath, io) {
226
227
  }
227
228
  //#endregion
228
229
  //#region src/cli/provider-registry.ts
230
+ const providerSetupSources = [
231
+ {
232
+ defaultEndpoint: "http://127.0.0.1:8317/v1",
233
+ defaultProviderId: "CLIProxyAPI",
234
+ label: "CLIProxyAPI",
235
+ probeModels: true,
236
+ value: "cliProxyApi"
237
+ },
238
+ {
239
+ label: "Custom OpenAI-compatible provider",
240
+ probeModels: true,
241
+ value: "custom"
242
+ },
243
+ {
244
+ defaultEndpoint: "http://localhost:11434/v1",
245
+ label: "Ollama",
246
+ probeModels: true,
247
+ value: "ollama"
248
+ },
249
+ {
250
+ label: "Manual model entry",
251
+ probeModels: false,
252
+ value: "manual"
253
+ }
254
+ ];
229
255
  function buildModelsUrl(endpoint) {
230
256
  return new URL("models", endpoint.endsWith("/") ? endpoint : `${endpoint}/`).toString();
231
257
  }
232
258
  function createProviderId(endpoint, existingIds) {
233
259
  let base = "provider";
234
260
  try {
235
- base = new URL(endpoint).hostname.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "").toLowerCase() || "provider";
261
+ const url = new URL(endpoint);
262
+ const source = findProviderSetupSourceByEndpoint(url);
263
+ base = normalizeProviderIdBase(source?.defaultProviderId ?? source?.label ?? url.hostname);
236
264
  } catch {
237
265
  base = "provider";
238
266
  }
@@ -245,6 +273,13 @@ function createProviderId(endpoint, existingIds) {
245
273
  function findModel(config, request) {
246
274
  return flattenModels(config).find(({ model }) => model.id === request || model.name === request || (model.aliases ?? []).includes(request));
247
275
  }
276
+ function findProviderSetupSource(value) {
277
+ return providerSetupSources.find((source) => source.value === value);
278
+ }
279
+ function findProviderSetupSourceByEndpoint(endpoint) {
280
+ const normalizedEndpoint = endpoint instanceof URL ? endpoint.toString() : new URL(endpoint).toString();
281
+ return providerSetupSources.find((source) => source.defaultEndpoint !== void 0 && new URL(source.defaultEndpoint).toString() === normalizedEndpoint);
282
+ }
248
283
  function flattenModels(config) {
249
284
  return config.providers.flatMap((provider) => provider.models.map((model) => ({
250
285
  model,
@@ -329,6 +364,9 @@ function formatTable(rows) {
329
364
  drawHorizontalLine: () => false
330
365
  });
331
366
  }
367
+ function normalizeProviderIdBase(value) {
368
+ return value.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "").toLowerCase() || "provider";
369
+ }
332
370
  //#endregion
333
371
  //#region src/cli/commands/config/setup-config.ts
334
372
  async function loadMergedSetupConfig(io) {
@@ -1682,6 +1720,53 @@ const plugin = defineCommand({
1682
1720
  //#region src/cli/commands/setup/interactive.ts
1683
1721
  const nonTtyMessage = "interactive setup requires a TTY. Use -N/--no-interactive with --provider-id and --provider-endpoint.\n";
1684
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
+ }
1685
1770
  function formatProbeModelsFailure(endpoint, error) {
1686
1771
  const hint = endpoint.startsWith("https://localhost:11434") ? " Ollama usually uses http://localhost:11434/v1." : "";
1687
1772
  return `Could not probe models: ${errorMessageFrom(error)}.${hint}`;
@@ -1722,20 +1807,10 @@ async function runInteractiveSetup(io) {
1722
1807
  if (step === "source") {
1723
1808
  const source = await prompts.select({
1724
1809
  message: "Choose provider setup mode.",
1725
- options: withBackOption([
1726
- {
1727
- label: "Custom OpenAI-compatible provider",
1728
- value: "custom"
1729
- },
1730
- {
1731
- label: "Ollama",
1732
- value: "ollama"
1733
- },
1734
- {
1735
- label: "Manual model entry",
1736
- value: "manual"
1737
- }
1738
- ])
1810
+ options: withBackOption(providerSetupSources.map(({ label, value }) => ({
1811
+ label,
1812
+ value
1813
+ })))
1739
1814
  });
1740
1815
  if (prompts.isCancel(source)) return cancelPrompt();
1741
1816
  if (source === backValue) {
@@ -1747,7 +1822,9 @@ async function runInteractiveSetup(io) {
1747
1822
  continue;
1748
1823
  }
1749
1824
  if (step === "endpoint") {
1750
- const endpoint = await promptEndpoint(prompts, draft.source ?? "custom");
1825
+ const setupSource = findProviderSetupSource(draft.source ?? "custom");
1826
+ if (setupSource === void 0) return cancelPrompt();
1827
+ const endpoint = await promptEndpoint(prompts, setupSource);
1751
1828
  if (prompts.isCancel(endpoint)) return cancelPrompt();
1752
1829
  if (typeof endpoint !== "string") return cancelPrompt();
1753
1830
  if (isBackInput(endpoint)) {
@@ -1761,9 +1838,8 @@ async function runInteractiveSetup(io) {
1761
1838
  if (step === "providerId") {
1762
1839
  const existingConfig = await loadSetupConfig(getConfigPath(io, draft.scope ?? "global"));
1763
1840
  const providerId = await prompts.text({
1764
- defaultValue: draft.providerId ?? createProviderId(draft.endpoint ?? "", new Set(existingConfig.providers.map((provider) => provider.id))),
1765
- message: "Provider id",
1766
- 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)",
1767
1843
  validate: (value) => isBackInput(value ?? "") || (value ?? "").trim().length > 0 ? void 0 : "Provider id is required."
1768
1844
  });
1769
1845
  if (prompts.isCancel(providerId)) return cancelPrompt();
@@ -1778,9 +1854,9 @@ async function runInteractiveSetup(io) {
1778
1854
  }
1779
1855
  if (step === "headers") {
1780
1856
  const headerInput = await prompts.text({
1781
- defaultValue: draft.headerInput ?? "",
1782
- message: "Headers",
1783
- 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",
1784
1860
  validate: (value) => {
1785
1861
  if (isBackInput(value ?? "")) return;
1786
1862
  try {
@@ -1799,7 +1875,7 @@ async function runInteractiveSetup(io) {
1799
1875
  }
1800
1876
  draft.headerInput = headerInput;
1801
1877
  draft.headers = parseHeaderList(splitHeaderInput(headerInput));
1802
- draft.discoveredModels = draft.source === "manual" ? [] : await probeModelsWithSpinner(prompts, draft.endpoint ?? "", draft.headers);
1878
+ draft.discoveredModels = findProviderSetupSource(draft.source ?? "custom")?.probeModels === false ? [] : await probeModelsWithSpinner(prompts, draft.endpoint ?? "", draft.headers);
1803
1879
  step = "models";
1804
1880
  continue;
1805
1881
  }
@@ -1816,26 +1892,69 @@ async function runInteractiveSetup(io) {
1816
1892
  continue;
1817
1893
  }
1818
1894
  if (step === "defaultAlias") {
1819
- const addDefaultAlias = await prompts.select({
1895
+ const defaultAliasAction = await prompts.select({
1820
1896
  message: `Add alias "default" to ${draft.selectedModels?.[0]}?`,
1821
- options: withBackOption([{
1822
- label: "Yes",
1823
- value: "yes"
1824
- }, {
1825
- label: "No",
1826
- value: "no"
1827
- }])
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
+ ])
1828
1911
  });
1829
- if (prompts.isCancel(addDefaultAlias)) return cancelPrompt();
1830
- if (addDefaultAlias === backValue) {
1912
+ if (prompts.isCancel(defaultAliasAction)) return cancelPrompt();
1913
+ if (defaultAliasAction === backValue) {
1831
1914
  step = "models";
1832
1915
  continue;
1833
1916
  }
1834
- 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
+ };
1835
1954
  step = "confirm";
1836
1955
  continue;
1837
1956
  }
1838
- 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 ?? []);
1839
1958
  const confirmed = await prompts.select({
1840
1959
  message: [
1841
1960
  `Write ${draft.scope} setup config?`,
@@ -1858,10 +1977,11 @@ async function runInteractiveSetup(io) {
1858
1977
  }
1859
1978
  if (confirmed === "no") return cancelPrompt();
1860
1979
  const configPath = getConfigPath(io, draft.scope ?? "global");
1861
- await writeSetupConfig(configPath, mergeSetupConfigs(await loadSetupConfig(configPath), {
1980
+ const mergedConfig = mergeSetupConfigs(await loadSetupConfig(configPath), {
1862
1981
  providers: [nextProvider],
1863
1982
  version: 1
1864
- }));
1983
+ });
1984
+ await writeSetupConfig(configPath, draft.defaultAliasTarget === void 0 ? mergedConfig : applyDefaultAlias(mergedConfig, draft.defaultAliasTarget));
1865
1985
  prompts.outro(`Wrote ${configPath}`);
1866
1986
  return 0;
1867
1987
  }
@@ -1872,13 +1992,15 @@ function withBackOption(options) {
1872
1992
  value: backValue
1873
1993
  }];
1874
1994
  }
1875
- 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) {
1876
1999
  return {
1877
2000
  endpoint,
1878
2001
  headers,
1879
2002
  id: providerId,
1880
- models: modelIds.map((modelId, index) => ({
1881
- aliases: index === 0 && addDefaultAlias ? ["default"] : void 0,
2003
+ models: modelIds.map((modelId) => ({
1882
2004
  id: modelId,
1883
2005
  name: modelId
1884
2006
  })),
@@ -1902,9 +2024,9 @@ async function probeModelsWithSpinner(prompts, endpoint, headers) {
1902
2024
  }
1903
2025
  async function promptEndpoint(prompts, source) {
1904
2026
  return prompts.text({
1905
- defaultValue: source === "ollama" ? "http://localhost:11434/v1" : void 0,
1906
- message: "Provider endpoint",
1907
- placeholder: source === "ollama" ? "http://localhost:11434/v1; type .. to go back" : "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",
1908
2030
  validate: (value) => isBackInput(value ?? "") || (value ?? "").trim().length > 0 ? void 0 : "Provider endpoint is required."
1909
2031
  });
1910
2032
  }
@@ -2138,7 +2260,7 @@ async function executeCli(argv, io) {
2138
2260
  pendingResult = result;
2139
2261
  return result;
2140
2262
  };
2141
- 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();
2142
2264
  registerCommandTree(cli, commandTree, {
2143
2265
  globalOptions,
2144
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-CGvIT3NA.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.20",
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/core": "0.0.20",
29
- "@alint-js/config": "0.0.20"
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",