@alint-js/cli 0.3.2 → 0.4.0

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
@@ -244,6 +244,35 @@ thinking = { type = "disabled" }
244
244
 
245
245
  `default_params` is merged into every chat request for that model. Use it for provider-specific request fields that are not covered by the rest of the model entry.
246
246
 
247
+ The CLI can also launch an ACP coding-agent command and expose it to rules as an ordinary model. Interactive `alint setup` provides presets for Claude Code, Codex, Gemini CLI, Kimi Code CLI, and OpenCode. Put a custom machine-specific command in the global setup config, or in `.alint/config.toml` when the repository standardizes the same command for every contributor:
248
+
249
+ ```toml
250
+ version = 1
251
+
252
+ [[providers]]
253
+ id = "acp"
254
+
255
+ [[providers.models]]
256
+ id = "codex"
257
+ name = "Codex ACP"
258
+ driver = "acp"
259
+ aliases = [ "default" ]
260
+ capabilities = [ "tool-call" ]
261
+ command = "codex-acp"
262
+ args = []
263
+ cwd = "."
264
+ ```
265
+
266
+ Then select it like any other model:
267
+
268
+ ```bash
269
+ alint --model acp/codex src
270
+ ```
271
+
272
+ `alint.config.ts` remains the shareable lint policy: it selects models, plugins, and rules but does not launch processes. The CLI merges global setup with `.alint/config.toml`, starts a loopback OpenAI-compatible adapter for the run, and shuts it down afterward. Each concurrent model request gets its own ACP process, so `--rule-concurrency` also bounds how many coding-agent processes a run can require.
273
+
274
+ The command inherits the environment that launched `alint`. Optional `[providers.models.env]` entries override individual variables, but setup TOML does not interpolate variables. Do not commit credentials into the table. Request tools require the ACP agent to support MCP over HTTP.
275
+
247
276
  `alint` structured output forces a tool call (`tool_choice`). Some models, such as DeepSeek V4, reject that combination while thinking/reasoning is enabled. For those models, set `thinking = { type = "disabled" }` as above so structured output can run.
248
277
 
249
278
  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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as executeCli } from "../cli-CP8pWPS7.mjs";
2
+ import { t as executeCli } from "../cli-DFWnPI6m.mjs";
3
3
  import process from "node:process";
4
4
  //#region src/bin/index.ts
5
5
  executeCli(process.argv, {
@@ -2,12 +2,13 @@ import process from "node:process";
2
2
  import { inspect } from "node:util";
3
3
  import { cac } from "cac";
4
4
  import { appendFile, mkdir, opendir, readFile, readdir, rm, stat } from "node:fs/promises";
5
- import { addProviderModels, getGlobalSetupConfigPath, getProjectSetupConfigPath, getStatsDir, installStaticPlugins, loadAlintConfig, loadSetupConfig, mergeSetupConfigs, pruneProviderModels, removeProviderModels, replaceSetupProvider, setProviderEndpoint, setProviderHeader, unsetProviderHeader, writeSetupConfig } from "@alint-js/config";
5
+ import { addProviderModels, getGlobalSetupConfigPath, getProjectSetupConfigPath, getStatsDir, installStaticPlugins, isAcpModel, loadAlintConfig, loadSetupConfig, mergeSetupConfigs, normalizeSetupConfig, pruneProviderModels, removeProviderModels, replaceSetupProvider, setProviderEndpoint, setProviderHeader, unsetProviderHeader, writeSetupConfig } from "@alint-js/config";
6
6
  import { AlintRunCancelledError, AlintRunError, benchmarkModels, hasDiscoveryFilePatterns, matchesDiscoveryFile, normalizeConfig, resolveConfigForDirectory, resolveConfigForFile, runAlint } from "@alint-js/core";
7
7
  import { isAbsolute, join, relative, resolve } from "pathe";
8
8
  import { errorMessageFrom } from "@moeru/std/error";
9
9
  import { getBorderCharacters, table } from "table";
10
10
  import { createColors } from "tinyrainbow";
11
+ import { createCommandModel, startGateway } from "@alint-js/model-adapter-acp";
11
12
  import cliSpinners from "cli-spinners";
12
13
  import fastStringWidth from "fast-string-width";
13
14
  import { relative as relative$1 } from "node:path";
@@ -17,7 +18,7 @@ import Gitignore from "gitignore-fs";
17
18
  import { Minimatch, minimatch } from "minimatch";
18
19
  import { errorMessageFrom as errorMessageFrom$1 } from "@moeru/std";
19
20
  //#region package.json
20
- var version = "0.3.2";
21
+ var version = "0.4.0";
21
22
  //#endregion
22
23
  //#region src/cli/output.ts
23
24
  function escapeLineValue(value) {
@@ -296,12 +297,14 @@ const providerSetupSources = [
296
297
  benchmarkConcurrency: 2,
297
298
  defaultEndpoint: "http://127.0.0.1:8317/v1",
298
299
  defaultProviderId: "CLIProxyAPI",
300
+ kind: "openai-compatible",
299
301
  label: "CLIProxyAPI",
300
302
  probeModels: true,
301
303
  value: "cliProxyApi"
302
304
  },
303
305
  {
304
306
  benchmarkConcurrency: 2,
307
+ kind: "openai-compatible",
305
308
  label: "Custom OpenAI-compatible provider",
306
309
  probeModels: true,
307
310
  value: "custom"
@@ -310,6 +313,7 @@ const providerSetupSources = [
310
313
  benchmarkConcurrency: 20,
311
314
  defaultEndpoint: "https://openrouter.ai/api/v1",
312
315
  defaultProviderId: "openrouter",
316
+ kind: "openai-compatible",
313
317
  label: "OpenRouter",
314
318
  probeModels: true,
315
319
  value: "openrouter"
@@ -318,6 +322,7 @@ const providerSetupSources = [
318
322
  benchmarkConcurrency: 2,
319
323
  defaultEndpoint: "https://api.cerebras.ai/v1",
320
324
  defaultProviderId: "cerebras",
325
+ kind: "openai-compatible",
321
326
  label: "Cerebras",
322
327
  probeModels: true,
323
328
  value: "cerebras"
@@ -326,6 +331,7 @@ const providerSetupSources = [
326
331
  benchmarkConcurrency: 2,
327
332
  defaultEndpoint: "https://api.groq.com/openai/v1",
328
333
  defaultProviderId: "groq",
334
+ kind: "openai-compatible",
329
335
  label: "Groq",
330
336
  probeModels: true,
331
337
  value: "groq"
@@ -333,15 +339,81 @@ const providerSetupSources = [
333
339
  {
334
340
  benchmarkConcurrency: 2,
335
341
  defaultEndpoint: "http://localhost:11434/v1",
342
+ kind: "openai-compatible",
336
343
  label: "Ollama",
337
344
  probeModels: true,
338
345
  value: "ollama"
339
346
  },
340
347
  {
341
348
  benchmarkConcurrency: 2,
349
+ kind: "openai-compatible",
342
350
  label: "Manual model entry",
343
351
  probeModels: false,
344
352
  value: "manual"
353
+ },
354
+ {
355
+ args: ["-y", "@agentclientprotocol/claude-agent-acp"],
356
+ command: "npx",
357
+ defaultProviderId: "claude-code",
358
+ kind: "acp",
359
+ label: "Claude Code (ACP)",
360
+ model: {
361
+ id: "claude-code",
362
+ name: "Claude Code"
363
+ },
364
+ value: "claudeCodeAcp"
365
+ },
366
+ {
367
+ args: ["-y", "@agentclientprotocol/codex-acp"],
368
+ command: "npx",
369
+ defaultProviderId: "codex",
370
+ kind: "acp",
371
+ label: "Codex (ACP)",
372
+ model: {
373
+ id: "codex",
374
+ name: "Codex"
375
+ },
376
+ value: "codexAcp"
377
+ },
378
+ {
379
+ args: [
380
+ "-y",
381
+ "@google/gemini-cli",
382
+ "--acp"
383
+ ],
384
+ command: "npx",
385
+ defaultProviderId: "gemini-cli",
386
+ kind: "acp",
387
+ label: "Gemini CLI (ACP)",
388
+ model: {
389
+ id: "gemini-cli",
390
+ name: "Gemini CLI"
391
+ },
392
+ value: "geminiCliAcp"
393
+ },
394
+ {
395
+ args: ["acp"],
396
+ command: "kimi",
397
+ defaultProviderId: "kimi-code",
398
+ kind: "acp",
399
+ label: "Kimi Code CLI (ACP)",
400
+ model: {
401
+ id: "kimi-code",
402
+ name: "Kimi Code CLI"
403
+ },
404
+ value: "kimiCliAcp"
405
+ },
406
+ {
407
+ args: ["acp"],
408
+ command: "opencode",
409
+ defaultProviderId: "opencode",
410
+ kind: "acp",
411
+ label: "OpenCode (ACP)",
412
+ model: {
413
+ id: "opencode",
414
+ name: "OpenCode"
415
+ },
416
+ value: "openCodeAcp"
345
417
  }
346
418
  ];
347
419
  function buildModelsUrl(endpoint) {
@@ -379,7 +451,7 @@ function findProviderSetupSource(value) {
379
451
  }
380
452
  function findProviderSetupSourceByEndpoint(endpoint) {
381
453
  const normalizedEndpoint = endpoint instanceof URL ? endpoint.toString() : new URL(endpoint).toString();
382
- return providerSetupSources.find((source) => source.defaultEndpoint !== void 0 && new URL(source.defaultEndpoint).toString() === normalizedEndpoint);
454
+ return providerSetupSources.find((source) => source.kind === "openai-compatible" && source.defaultEndpoint !== void 0 && new URL(source.defaultEndpoint).toString() === normalizedEndpoint);
383
455
  }
384
456
  function flattenModels(config) {
385
457
  return config.providers.flatMap((provider) => provider.models.map((model) => ({
@@ -520,16 +592,16 @@ function formatProviderList(config) {
520
592
  "models"
521
593
  ], ...config.providers.map((provider) => [
522
594
  provider.id,
523
- provider.type,
524
- provider.endpoint,
595
+ provider.type ?? "model-driven",
596
+ provider.endpoint ?? "-",
525
597
  String(provider.models.length)
526
598
  ])]);
527
599
  }
528
600
  function formatProviderShow(provider) {
529
601
  const lines = [
530
602
  `id: ${provider.id}`,
531
- `type: ${provider.type}`,
532
- `endpoint: ${provider.endpoint}`,
603
+ `type: ${provider.type ?? "model-driven"}`,
604
+ `endpoint: ${provider.endpoint ?? "-"}`,
533
605
  `models: ${provider.models.map((model) => model.id).join(", ")}`
534
606
  ];
535
607
  const headerKeys = Object.keys(provider.headers ?? {});
@@ -560,7 +632,7 @@ async function probeModels(endpoint, headers = {}) {
560
632
  return body.data.map((model) => model.id);
561
633
  }
562
634
  function resolveProviderBenchmarkConcurrency(config, overrides = []) {
563
- const concurrency = Object.fromEntries(config.providers.map((provider) => [provider.id, findProviderSetupSourceByEndpoint(provider.endpoint)?.benchmarkConcurrency ?? defaultBenchmarkConcurrency]));
635
+ const concurrency = Object.fromEntries(config.providers.map((provider) => [provider.id, provider.endpoint === void 0 ? defaultBenchmarkConcurrency : findProviderSetupSourceByEndpoint(provider.endpoint)?.benchmarkConcurrency ?? defaultBenchmarkConcurrency]));
564
636
  for (const override of overrides) {
565
637
  const separatorIndex = override.indexOf("=");
566
638
  if (separatorIndex <= 0 || separatorIndex === override.length - 1) throw new Error("Invalid benchmark concurrency override. Expected <provider-id>=<limit>.");
@@ -645,6 +717,89 @@ function stripTrailingZeros(value) {
645
717
  return String(Number(value));
646
718
  }
647
719
  //#endregion
720
+ //#region src/cli/runtime/model-adapter.ts
721
+ /**
722
+ * Normalizes file configuration and starts its model adapters.
723
+ *
724
+ * Triggering workflow:
725
+ *
726
+ * `runLintCommand | runListModelsCommand`
727
+ * -> {@link startModelAdapters}
728
+ * -> {@link createAcpAdapter}
729
+ * -> {@link normalizeSetupConfig}
730
+ * -> `AcpAdapter.provider`
731
+ * -> {@link startGateway}
732
+ *
733
+ * Upstream:
734
+ * - `runLintCommand`
735
+ * - `runListModelsCommand`
736
+ *
737
+ * Downstream:
738
+ * - {@link normalizeSetupConfig}
739
+ * - {@link startGateway}
740
+ */
741
+ async function startModelAdapters(config, io) {
742
+ const acp = createAcpAdapter(io);
743
+ try {
744
+ return {
745
+ setupConfig: await normalizeSetupConfig(config, { acp: acp.provider }),
746
+ shutdown: acp.shutdown
747
+ };
748
+ } catch (error) {
749
+ await acp.shutdown();
750
+ throw error;
751
+ }
752
+ }
753
+ /** Owns the ACP processes and loopback gateways used by one CLI operation. */
754
+ function createAcpAdapter(io) {
755
+ const shutdowns = [];
756
+ return {
757
+ provider: async (provider) => {
758
+ const gateway = await startGateway({
759
+ cwd: io.cwd,
760
+ models: provider.models.map((model) => createCommandModel({
761
+ args: model.args,
762
+ command: model.command,
763
+ cwd: resolve(io.cwd, model.cwd ?? "."),
764
+ env: {
765
+ ...io.env ?? process.env,
766
+ ...model.env
767
+ },
768
+ id: model.id,
769
+ name: model.name ?? model.id,
770
+ onStderr: createStderrWriter(io, model.id)
771
+ }))
772
+ });
773
+ shutdowns.push(gateway.shutdown);
774
+ return {
775
+ endpoint: gateway.endpoint,
776
+ type: "openai-compatible"
777
+ };
778
+ },
779
+ shutdown: async () => {
780
+ await Promise.all(shutdowns.map((shutdown) => shutdown()));
781
+ }
782
+ };
783
+ }
784
+ /**
785
+ * Creates the CLI stderr sink for non-protocol output emitted by an ACP command.
786
+ *
787
+ * Triggering workflow:
788
+ *
789
+ * {@link createCommandModel}
790
+ * -> `ChildProcess.stderr:data`
791
+ * -> {@link createStderrWriter}
792
+ *
793
+ * Upstream:
794
+ * - {@link createAcpAdapter}
795
+ *
796
+ * Downstream:
797
+ * - {@link CliIo.stderr}
798
+ */
799
+ function createStderrWriter(io, modelId) {
800
+ return (text) => io.stderr.write(`[acp:${modelId}] ${text}`);
801
+ }
802
+ //#endregion
648
803
  //#region src/cli/commands/config/setup-config.ts
649
804
  function formatUnknownProvider(providerId, scope) {
650
805
  const hint = scope === "local" ? " Remove --local to inspect global configuration." : " Add --local to inspect project-local configuration.";
@@ -820,19 +975,21 @@ async function runListModelsCommand(context, options) {
820
975
  context.io.stderr.write(`${errorMessageFrom(error) ?? "Invalid benchmark concurrency override."}\n`);
821
976
  return 2;
822
977
  }
978
+ const runtime = await startModelAdapters(config, context.io);
823
979
  const progress = context.io.stderr.isTTY === true ? createModelBenchmarkProgressDisplay({
824
980
  color: true,
825
- config,
981
+ config: runtime.setupConfig,
826
982
  output: context.io.stderr
827
983
  }) : void 0;
828
984
  let benchmarks;
829
985
  try {
830
- benchmarks = await benchmarkModels(config, {
986
+ benchmarks = await benchmarkModels(runtime.setupConfig, {
831
987
  onProgress: (snapshot) => progress?.update(snapshot),
832
988
  providerConcurrency
833
989
  });
834
990
  } finally {
835
991
  progress?.finish();
992
+ await runtime.shutdown();
836
993
  }
837
994
  context.io.stdout.write(formatModelBenchmarkList(config, benchmarks, { color: context.io.stdout.isTTY === true }));
838
995
  return 0;
@@ -941,15 +1098,21 @@ async function runPruneModelsCommand(context, options, confirm = confirmPruneMod
941
1098
  const plans = [];
942
1099
  let hadFailure = false;
943
1100
  for (const provider of providers) {
1101
+ const endpoint = provider.endpoint;
1102
+ if (endpoint === void 0) {
1103
+ hadFailure = true;
1104
+ context.io.stderr.write(`cannot prune provider "${escapeLineValue(provider.id)}" because it has no remote endpoint.\n`);
1105
+ continue;
1106
+ }
944
1107
  let remoteModelIds;
945
1108
  try {
946
- remoteModelIds = new Set(await probeModels(provider.endpoint, provider.headers));
1109
+ remoteModelIds = new Set(await probeModels(endpoint, provider.headers));
947
1110
  } catch (error) {
948
1111
  hadFailure = true;
949
- context.io.stderr.write(`failed to probe provider "${escapeLineValue(provider.id)}" at "${escapeLineValue(provider.endpoint)}": ${classifyProbeFailure(error)}.\n`);
1112
+ context.io.stderr.write(`failed to probe provider "${escapeLineValue(provider.id)}" at "${escapeLineValue(endpoint)}": ${classifyProbeFailure(error)}.\n`);
950
1113
  continue;
951
1114
  }
952
- const removedModels = provider.models.filter((model) => !remoteModelIds.has(model.id));
1115
+ const removedModels = provider.models.filter((model) => !isAcpModel(model) && !remoteModelIds.has(model.id));
953
1116
  const defaultModel = removedModels.find((model) => (model.aliases ?? []).includes("default"));
954
1117
  if (defaultModel !== void 0) {
955
1118
  hadFailure = true;
@@ -1669,6 +1832,7 @@ function textResult(prompts, result) {
1669
1832
  */
1670
1833
  async function runProviderEditor(input, promptPort) {
1671
1834
  const prompts = promptPort ?? createProviderEditorPrompts(await import("@clack/prompts"));
1835
+ if (input.source.kind === "acp") return runAcpProviderEditor(input, input.source, prompts);
1672
1836
  const existingProvider = input.mode === "update" ? input.existingProvider : void 0;
1673
1837
  const configuredHeaders = { ...existingProvider?.headers };
1674
1838
  const initialHeaders = applyHeaderSelection(configuredHeaders, Object.keys(configuredHeaders), {}) ?? {};
@@ -1800,6 +1964,30 @@ async function runProviderEditor(input, promptPort) {
1800
1964
  };
1801
1965
  }
1802
1966
  }
1967
+ function createAcpProvider(source, providerId) {
1968
+ return {
1969
+ id: providerId,
1970
+ models: [{
1971
+ ...source.args === void 0 ? {} : { args: [...source.args] },
1972
+ capabilities: ["tool-call"],
1973
+ command: source.command,
1974
+ driver: "acp",
1975
+ ...source.model
1976
+ }]
1977
+ };
1978
+ }
1979
+ function createAcpSummary(input, provider, defaultAliasTarget) {
1980
+ const model = provider.models[0];
1981
+ return [
1982
+ `${input.mode === "create" ? "Create" : "Update"} provider?`,
1983
+ `Provider: ${escapeLineValue(provider.id)}`,
1984
+ "Driver: ACP",
1985
+ `Command: ${escapeLineValue("command" in model ? model.command : "-")}`,
1986
+ `Arguments: ${"args" in model ? formatSummaryValues(model.args ?? []) : "(none)"}`,
1987
+ `Model: ${escapeLineValue(model.id)}`,
1988
+ `Default: ${defaultAliasTarget === void 0 ? "unchanged" : escapeLineValue(`${defaultAliasTarget.providerId}/${defaultAliasTarget.modelId}`)}`
1989
+ ].join("\n");
1990
+ }
1803
1991
  function createProvider(input, draft) {
1804
1992
  return {
1805
1993
  ...input.existingProvider,
@@ -1819,7 +2007,7 @@ function createSummary(input, provider, defaultAliasTarget) {
1819
2007
  return [
1820
2008
  `${input.mode === "create" ? "Create" : "Update"} provider?`,
1821
2009
  `Provider: ${escapeLineValue(provider.id)}`,
1822
- `Endpoint: ${escapeLineValue(provider.endpoint)}`,
2010
+ `Endpoint: ${escapeLineValue(provider.endpoint ?? "-")}`,
1823
2011
  `Headers: ${formatSummaryValues(Object.keys(provider.headers ?? {}))}`,
1824
2012
  `Added models: ${formatSummaryValues(additions)}`,
1825
2013
  `Removed models: ${formatSummaryValues(removals)}`,
@@ -1832,6 +2020,87 @@ function effectiveHeaders(draft) {
1832
2020
  function formatSummaryValues(values) {
1833
2021
  return values.length > 0 ? values.map(escapeLineValue).join(", ") : "(none)";
1834
2022
  }
2023
+ /**
2024
+ * Runs the preset-backed ACP branch of the provider editor.
2025
+ *
2026
+ * Triggering workflow:
2027
+ *
2028
+ * {@link runInteractiveSetup}
2029
+ * -> {@link runProviderEditor}
2030
+ * -> `provider-editor.acp-preset`
2031
+ * -> {@link runAcpProviderEditor}
2032
+ *
2033
+ * Upstream:
2034
+ * - {@link runProviderEditor}
2035
+ *
2036
+ * Downstream:
2037
+ * - {@link ProviderEditorResult} and the caller-owned setup-config write
2038
+ */
2039
+ async function runAcpProviderEditor(input, source, prompts) {
2040
+ let defaultAliasTarget;
2041
+ let providerId = input.existingProvider?.id ?? source.defaultProviderId;
2042
+ let step = "providerId";
2043
+ while (true) {
2044
+ if (step === "providerId") {
2045
+ const result = await prompts.providerId(providerId, input.mode === "create");
2046
+ if (result.status === "cancelled") return { status: "cancelled" };
2047
+ if (result.status === "back") return { status: "back" };
2048
+ providerId = result.value.trim();
2049
+ step = "defaultAction";
2050
+ continue;
2051
+ }
2052
+ const provider = createAcpProvider(source, providerId);
2053
+ if (step === "defaultAction") {
2054
+ const result = await prompts.defaultAction(source.model.id);
2055
+ if (result.status === "cancelled") return { status: "cancelled" };
2056
+ if (result.status === "back") {
2057
+ step = "providerId";
2058
+ continue;
2059
+ }
2060
+ defaultAliasTarget = result.value === "yes" ? {
2061
+ modelId: source.model.id,
2062
+ providerId
2063
+ } : void 0;
2064
+ step = result.value === "selectAnother" ? "defaultModel" : "confirm";
2065
+ continue;
2066
+ }
2067
+ if (step === "defaultModel") {
2068
+ const candidates = createDefaultModelCandidates(mergeSetupConfigs(input.config, {
2069
+ providers: [provider],
2070
+ version: 1
2071
+ }), provider.id, [source.model.id]);
2072
+ const selectedDefault = await prompts.defaultModel(candidates.map((candidate) => ({
2073
+ hint: candidate.isCurrentDefault ? "current default" : candidate.isNew ? "new" : void 0,
2074
+ label: candidate.label,
2075
+ value: candidate.value
2076
+ })));
2077
+ if (selectedDefault.status === "cancelled") return { status: "cancelled" };
2078
+ if (selectedDefault.status === "back") {
2079
+ step = "defaultAction";
2080
+ continue;
2081
+ }
2082
+ const candidate = candidates.find((item) => item.value === selectedDefault.value);
2083
+ if (candidate === void 0) return { status: "cancelled" };
2084
+ defaultAliasTarget = {
2085
+ modelId: candidate.modelId,
2086
+ providerId: candidate.providerId
2087
+ };
2088
+ step = "confirm";
2089
+ continue;
2090
+ }
2091
+ const confirmed = await prompts.confirm(createAcpSummary(input, provider, defaultAliasTarget));
2092
+ if (confirmed.status === "cancelled" || confirmed.status === "submitted" && confirmed.value === "no") return { status: "cancelled" };
2093
+ if (confirmed.status === "back") {
2094
+ step = "defaultAction";
2095
+ continue;
2096
+ }
2097
+ return {
2098
+ ...defaultAliasTarget === void 0 ? {} : { defaultAliasTarget },
2099
+ provider,
2100
+ status: "confirmed"
2101
+ };
2102
+ }
2103
+ }
1835
2104
  function splitInput(value) {
1836
2105
  return value.split(",").map((item) => item.trim()).filter(Boolean);
1837
2106
  }
@@ -1885,6 +2154,11 @@ function mergeProviderHeaders(existing, replacements) {
1885
2154
  return Object.keys(headers).length > 0 ? headers : void 0;
1886
2155
  }
1887
2156
  async function runNonInteractiveUpdate(context, options, providerEndpoint, existingProvider, config, path) {
2157
+ const endpoint = providerEndpoint ?? existingProvider.endpoint;
2158
+ if (endpoint === void 0) {
2159
+ context.io.stderr.write(`provider "${existingProvider.id}" has no remote endpoint to update.\n`);
2160
+ return 2;
2161
+ }
1888
2162
  let parsedHeaders;
1889
2163
  try {
1890
2164
  parsedHeaders = parseHeaderList(toArray$1(options.providerHeader));
@@ -1894,12 +2168,12 @@ async function runNonInteractiveUpdate(context, options, providerEndpoint, exist
1894
2168
  }
1895
2169
  const draft = {
1896
2170
  ...existingProvider,
1897
- endpoint: providerEndpoint ?? existingProvider.endpoint,
2171
+ endpoint,
1898
2172
  headers: mergeProviderHeaders(existingProvider.headers, parsedHeaders)
1899
2173
  };
1900
2174
  let remoteModelIds;
1901
2175
  try {
1902
- remoteModelIds = await probeModels(draft.endpoint, draft.headers);
2176
+ remoteModelIds = await probeModels(endpoint, draft.headers);
1903
2177
  } catch (error) {
1904
2178
  context.io.stderr.write(`failed to probe provider: ${errorMessageFrom(error) ?? "Unknown error."}\n`);
1905
2179
  return 2;
@@ -1947,13 +2221,21 @@ async function runUpdateProviderCommand(context, options) {
1947
2221
  context.io.stderr.write(formatUnknownProvider(providerId, scope));
1948
2222
  return 2;
1949
2223
  }
2224
+ const endpoint = providerEndpoint ?? existingProvider.endpoint;
2225
+ if (endpoint === void 0) {
2226
+ context.io.stderr.write(`provider "${existingProvider.id}" has no remote endpoint to update.\n`);
2227
+ return 2;
2228
+ }
1950
2229
  if (context.setupNoInteractive) return runNonInteractiveUpdate(context, options, providerEndpoint, existingProvider, config, path);
1951
2230
  const result = await runProviderEditor({
1952
2231
  config,
1953
- existingProvider,
2232
+ existingProvider: {
2233
+ ...existingProvider,
2234
+ endpoint
2235
+ },
1954
2236
  io: context.io,
1955
2237
  mode: "update",
1956
- source: providerUpdateSource(existingProvider.endpoint)
2238
+ source: providerUpdateSource(endpoint)
1957
2239
  });
1958
2240
  if (result.status !== "confirmed") return 0;
1959
2241
  const replaced = replaceSetupProvider(config, result.provider);
@@ -3470,43 +3752,48 @@ async function runLintCommand(files, options, io, interceptConsoleOutput) {
3470
3752
  const writeResult = (runResult) => {
3471
3753
  io.stdout.write(formatDiagnostics(options.format, runResult, { color: io.stdout.isTTY === true }));
3472
3754
  };
3755
+ const runtime = await startModelAdapters(setupConfig, io);
3473
3756
  let result;
3474
3757
  try {
3475
- result = await runAlint({
3476
- cacheOnly: options.cacheOnly,
3477
- config,
3478
- cwd: io.cwd,
3479
- defaultModel,
3480
- directories: lintTargets.directories,
3481
- files: lintTargets.files,
3482
- modelOverride: options.model,
3483
- outputLanguage: options.outputLanguage,
3484
- progress: mergeProgressReporters(progress?.reporter, statsCollector?.reporter),
3485
- runner,
3486
- setupConfig
3487
- });
3488
- } catch (error) {
3758
+ try {
3759
+ result = await runAlint({
3760
+ cacheOnly: options.cacheOnly,
3761
+ config,
3762
+ cwd: io.cwd,
3763
+ defaultModel,
3764
+ directories: lintTargets.directories,
3765
+ files: lintTargets.files,
3766
+ modelOverride: options.model,
3767
+ outputLanguage: options.outputLanguage,
3768
+ progress: mergeProgressReporters(progress?.reporter, statsCollector?.reporter),
3769
+ runner,
3770
+ setupConfig: runtime.setupConfig
3771
+ });
3772
+ } catch (error) {
3773
+ restoreProgressConsole?.();
3774
+ progress?.dispose();
3775
+ if (error instanceof AlintRunError) {
3776
+ await persistStats(error.result);
3777
+ writeResult(error.result);
3778
+ io.stderr.write(formatRunError(error, io.stderr.isTTY === true));
3779
+ return 2;
3780
+ }
3781
+ if (error instanceof AlintRunCancelledError) {
3782
+ await persistStats(error.result);
3783
+ writeResult(error.result);
3784
+ io.stderr.write(formatCancelledError(error, io.stderr.isTTY === true));
3785
+ return 2;
3786
+ }
3787
+ throw error;
3788
+ }
3489
3789
  restoreProgressConsole?.();
3490
3790
  progress?.dispose();
3491
- if (error instanceof AlintRunError) {
3492
- await persistStats(error.result);
3493
- writeResult(error.result);
3494
- io.stderr.write(formatRunError(error, io.stderr.isTTY === true));
3495
- return 2;
3496
- }
3497
- if (error instanceof AlintRunCancelledError) {
3498
- await persistStats(error.result);
3499
- writeResult(error.result);
3500
- io.stderr.write(formatCancelledError(error, io.stderr.isTTY === true));
3501
- return 2;
3502
- }
3503
- throw error;
3791
+ await persistStats(result);
3792
+ writeResult(result);
3793
+ return result.diagnostics.some((diagnostic) => diagnostic.severity === "error") ? 1 : 0;
3794
+ } finally {
3795
+ await runtime.shutdown();
3504
3796
  }
3505
- restoreProgressConsole?.();
3506
- progress?.dispose();
3507
- await persistStats(result);
3508
- writeResult(result);
3509
- return result.diagnostics.some((diagnostic) => diagnostic.severity === "error") ? 1 : 0;
3510
3797
  }
3511
3798
  function shouldEnableProgress(options, io) {
3512
3799
  if (options.progress !== void 0) return options.progress;
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-CP8pWPS7.mjs";
1
+ import { i as formatJson, n as formatDiagnostics, r as formatStylish, t as executeCli } from "./cli-DFWnPI6m.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.3.2",
4
+ "version": "0.4.0",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.mts",
@@ -28,8 +28,9 @@
28
28
  "pathe": "^2.0.3",
29
29
  "table": "^6.9.0",
30
30
  "tinyrainbow": "^3.1.1",
31
- "@alint-js/core": "0.3.2",
32
- "@alint-js/config": "0.3.2"
31
+ "@alint-js/model-adapter-acp": "0.4.0",
32
+ "@alint-js/core": "0.4.0",
33
+ "@alint-js/config": "0.4.0"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@pnpm/find-workspace-dir": "^1000.1.5",