@alint-js/cli 0.3.1 → 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 +43 -11
- package/dist/bin/index.mjs +1 -1
- package/dist/{cli-CL433bER.mjs → cli-DFWnPI6m.mjs} +357 -55
- package/dist/index.mjs +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -167,6 +167,8 @@ Override the matched model for a one-off run:
|
|
|
167
167
|
alint --model qwen:8b demo.ts
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
When the project-local setup does not configure any models, model calls without a rule-level or call-level selector use the `default` alias from the global setup. Rule selectors continue to use normal model matching. Configuring at least one model in `.alint/config.toml` restores project-first matching for unselected calls. An explicit `--model` override always takes precedence.
|
|
171
|
+
|
|
170
172
|
Ask model-backed rules to write diagnostics in a specific language:
|
|
171
173
|
|
|
172
174
|
```bash
|
|
@@ -213,18 +215,12 @@ alint output inspect alint-output.json
|
|
|
213
215
|
|
|
214
216
|
#### Configurations
|
|
215
217
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
The priorities follow:
|
|
219
|
-
|
|
220
|
-
```text
|
|
221
|
-
~/.config/alint/config.toml < .alint/config.toml < alint.config.* < environment and CLI overrides
|
|
222
|
-
```
|
|
218
|
+
`alint` uses two configuration systems with separate schemas and responsibilities:
|
|
223
219
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
220
|
+
| Configuration | Priority (high → low) | Content |
|
|
221
|
+
| --- |--------------------------------------------------------------------------------------|----------------------------------------------------------------------|
|
|
222
|
+
| Setup | Environment and CLI overrides > `.alint/config.toml` > `~/.config/alint/config.toml` | Providers, models, and runner defaults. |
|
|
223
|
+
| Lint | Environment and CLI overrides > `alint.config.*` | Files, ignores, plugins, rules, and other project-level lint policy. |
|
|
228
224
|
|
|
229
225
|
Use setup TOML for machine or project provider definitions:
|
|
230
226
|
|
|
@@ -241,8 +237,44 @@ id = "qwen:8b"
|
|
|
241
237
|
name = "qwen:8b"
|
|
242
238
|
size = "small"
|
|
243
239
|
capabilities = [ "tool-call" ]
|
|
240
|
+
|
|
241
|
+
[providers.models.default_params]
|
|
242
|
+
thinking = { type = "disabled" }
|
|
244
243
|
```
|
|
245
244
|
|
|
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
|
+
|
|
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
|
+
|
|
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.
|
|
277
|
+
|
|
246
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.
|
|
247
279
|
|
|
248
280
|
You can also use `--local` to write the config in the current project:
|
package/dist/bin/index.mjs
CHANGED
|
@@ -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.
|
|
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,17 +717,105 @@ 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.";
|
|
651
806
|
return `unknown provider "${escapeLineValue(providerId)}" in ${scope} setup config.${hint}\n`;
|
|
652
807
|
}
|
|
653
808
|
async function loadMergedSetupConfig(io) {
|
|
654
|
-
const
|
|
655
|
-
const projectSetupConfigPath = getProjectSetupConfigPath(io.cwd);
|
|
656
|
-
const [globalSetupConfig, projectSetupConfig] = await Promise.all([loadSetupConfig(globalSetupConfigPath), loadSetupConfig(projectSetupConfigPath)]);
|
|
809
|
+
const { globalSetupConfig, projectSetupConfig } = await loadSetupConfigs(io);
|
|
657
810
|
return mergeSetupConfigs(globalSetupConfig, projectSetupConfig);
|
|
658
811
|
}
|
|
812
|
+
async function loadRunSetupConfig(io) {
|
|
813
|
+
const { globalSetupConfig, projectSetupConfig } = await loadSetupConfigs(io);
|
|
814
|
+
return {
|
|
815
|
+
defaultModel: projectSetupConfig.providers.some((provider) => provider.models.length > 0) ? void 0 : "default",
|
|
816
|
+
setupConfig: mergeSetupConfigs(globalSetupConfig, projectSetupConfig)
|
|
817
|
+
};
|
|
818
|
+
}
|
|
659
819
|
async function loadScopedSetupConfig(io, local) {
|
|
660
820
|
const scope = local === true ? "local" : "global";
|
|
661
821
|
const path = scope === "local" ? getProjectSetupConfigPath(io.cwd) : getGlobalSetupConfigPath(io.env ?? process.env);
|
|
@@ -665,6 +825,15 @@ async function loadScopedSetupConfig(io, local) {
|
|
|
665
825
|
scope
|
|
666
826
|
};
|
|
667
827
|
}
|
|
828
|
+
async function loadSetupConfigs(io) {
|
|
829
|
+
const globalSetupConfigPath = getGlobalSetupConfigPath(io.env ?? process.env);
|
|
830
|
+
const projectSetupConfigPath = getProjectSetupConfigPath(io.cwd);
|
|
831
|
+
const [globalSetupConfig, projectSetupConfig] = await Promise.all([loadSetupConfig(globalSetupConfigPath), loadSetupConfig(projectSetupConfigPath)]);
|
|
832
|
+
return {
|
|
833
|
+
globalSetupConfig,
|
|
834
|
+
projectSetupConfig
|
|
835
|
+
};
|
|
836
|
+
}
|
|
668
837
|
//#endregion
|
|
669
838
|
//#region src/cli/commands/config/models/benchmark-progress.ts
|
|
670
839
|
/** Keeps the benchmark preview live without mixing transient frames into the final stdout table. */
|
|
@@ -806,19 +975,21 @@ async function runListModelsCommand(context, options) {
|
|
|
806
975
|
context.io.stderr.write(`${errorMessageFrom(error) ?? "Invalid benchmark concurrency override."}\n`);
|
|
807
976
|
return 2;
|
|
808
977
|
}
|
|
978
|
+
const runtime = await startModelAdapters(config, context.io);
|
|
809
979
|
const progress = context.io.stderr.isTTY === true ? createModelBenchmarkProgressDisplay({
|
|
810
980
|
color: true,
|
|
811
|
-
config,
|
|
981
|
+
config: runtime.setupConfig,
|
|
812
982
|
output: context.io.stderr
|
|
813
983
|
}) : void 0;
|
|
814
984
|
let benchmarks;
|
|
815
985
|
try {
|
|
816
|
-
benchmarks = await benchmarkModels(
|
|
986
|
+
benchmarks = await benchmarkModels(runtime.setupConfig, {
|
|
817
987
|
onProgress: (snapshot) => progress?.update(snapshot),
|
|
818
988
|
providerConcurrency
|
|
819
989
|
});
|
|
820
990
|
} finally {
|
|
821
991
|
progress?.finish();
|
|
992
|
+
await runtime.shutdown();
|
|
822
993
|
}
|
|
823
994
|
context.io.stdout.write(formatModelBenchmarkList(config, benchmarks, { color: context.io.stdout.isTTY === true }));
|
|
824
995
|
return 0;
|
|
@@ -927,15 +1098,21 @@ async function runPruneModelsCommand(context, options, confirm = confirmPruneMod
|
|
|
927
1098
|
const plans = [];
|
|
928
1099
|
let hadFailure = false;
|
|
929
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
|
+
}
|
|
930
1107
|
let remoteModelIds;
|
|
931
1108
|
try {
|
|
932
|
-
remoteModelIds = new Set(await probeModels(
|
|
1109
|
+
remoteModelIds = new Set(await probeModels(endpoint, provider.headers));
|
|
933
1110
|
} catch (error) {
|
|
934
1111
|
hadFailure = true;
|
|
935
|
-
context.io.stderr.write(`failed to probe provider "${escapeLineValue(provider.id)}" at "${escapeLineValue(
|
|
1112
|
+
context.io.stderr.write(`failed to probe provider "${escapeLineValue(provider.id)}" at "${escapeLineValue(endpoint)}": ${classifyProbeFailure(error)}.\n`);
|
|
936
1113
|
continue;
|
|
937
1114
|
}
|
|
938
|
-
const removedModels = provider.models.filter((model) => !remoteModelIds.has(model.id));
|
|
1115
|
+
const removedModels = provider.models.filter((model) => !isAcpModel(model) && !remoteModelIds.has(model.id));
|
|
939
1116
|
const defaultModel = removedModels.find((model) => (model.aliases ?? []).includes("default"));
|
|
940
1117
|
if (defaultModel !== void 0) {
|
|
941
1118
|
hadFailure = true;
|
|
@@ -1536,7 +1713,7 @@ function createProviderEditorPrompts(prompts) {
|
|
|
1536
1713
|
async headerInput() {
|
|
1537
1714
|
return textResult(prompts, await prompts.text({
|
|
1538
1715
|
message: "Headers (leave empty to skip; type .. to go back)",
|
|
1539
|
-
placeholder: "Authorization=Bearer
|
|
1716
|
+
placeholder: "Authorization=Bearer sk-or-v1-xxxxxx, X-Test=true",
|
|
1540
1717
|
validate: (value) => {
|
|
1541
1718
|
if (isBackInput(value ?? "")) return;
|
|
1542
1719
|
try {
|
|
@@ -1655,6 +1832,7 @@ function textResult(prompts, result) {
|
|
|
1655
1832
|
*/
|
|
1656
1833
|
async function runProviderEditor(input, promptPort) {
|
|
1657
1834
|
const prompts = promptPort ?? createProviderEditorPrompts(await import("@clack/prompts"));
|
|
1835
|
+
if (input.source.kind === "acp") return runAcpProviderEditor(input, input.source, prompts);
|
|
1658
1836
|
const existingProvider = input.mode === "update" ? input.existingProvider : void 0;
|
|
1659
1837
|
const configuredHeaders = { ...existingProvider?.headers };
|
|
1660
1838
|
const initialHeaders = applyHeaderSelection(configuredHeaders, Object.keys(configuredHeaders), {}) ?? {};
|
|
@@ -1786,6 +1964,30 @@ async function runProviderEditor(input, promptPort) {
|
|
|
1786
1964
|
};
|
|
1787
1965
|
}
|
|
1788
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
|
+
}
|
|
1789
1991
|
function createProvider(input, draft) {
|
|
1790
1992
|
return {
|
|
1791
1993
|
...input.existingProvider,
|
|
@@ -1805,7 +2007,7 @@ function createSummary(input, provider, defaultAliasTarget) {
|
|
|
1805
2007
|
return [
|
|
1806
2008
|
`${input.mode === "create" ? "Create" : "Update"} provider?`,
|
|
1807
2009
|
`Provider: ${escapeLineValue(provider.id)}`,
|
|
1808
|
-
`Endpoint: ${escapeLineValue(provider.endpoint)}`,
|
|
2010
|
+
`Endpoint: ${escapeLineValue(provider.endpoint ?? "-")}`,
|
|
1809
2011
|
`Headers: ${formatSummaryValues(Object.keys(provider.headers ?? {}))}`,
|
|
1810
2012
|
`Added models: ${formatSummaryValues(additions)}`,
|
|
1811
2013
|
`Removed models: ${formatSummaryValues(removals)}`,
|
|
@@ -1818,6 +2020,87 @@ function effectiveHeaders(draft) {
|
|
|
1818
2020
|
function formatSummaryValues(values) {
|
|
1819
2021
|
return values.length > 0 ? values.map(escapeLineValue).join(", ") : "(none)";
|
|
1820
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
|
+
}
|
|
1821
2104
|
function splitInput(value) {
|
|
1822
2105
|
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
1823
2106
|
}
|
|
@@ -1871,6 +2154,11 @@ function mergeProviderHeaders(existing, replacements) {
|
|
|
1871
2154
|
return Object.keys(headers).length > 0 ? headers : void 0;
|
|
1872
2155
|
}
|
|
1873
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
|
+
}
|
|
1874
2162
|
let parsedHeaders;
|
|
1875
2163
|
try {
|
|
1876
2164
|
parsedHeaders = parseHeaderList(toArray$1(options.providerHeader));
|
|
@@ -1880,12 +2168,12 @@ async function runNonInteractiveUpdate(context, options, providerEndpoint, exist
|
|
|
1880
2168
|
}
|
|
1881
2169
|
const draft = {
|
|
1882
2170
|
...existingProvider,
|
|
1883
|
-
endpoint
|
|
2171
|
+
endpoint,
|
|
1884
2172
|
headers: mergeProviderHeaders(existingProvider.headers, parsedHeaders)
|
|
1885
2173
|
};
|
|
1886
2174
|
let remoteModelIds;
|
|
1887
2175
|
try {
|
|
1888
|
-
remoteModelIds = await probeModels(
|
|
2176
|
+
remoteModelIds = await probeModels(endpoint, draft.headers);
|
|
1889
2177
|
} catch (error) {
|
|
1890
2178
|
context.io.stderr.write(`failed to probe provider: ${errorMessageFrom(error) ?? "Unknown error."}\n`);
|
|
1891
2179
|
return 2;
|
|
@@ -1933,13 +2221,21 @@ async function runUpdateProviderCommand(context, options) {
|
|
|
1933
2221
|
context.io.stderr.write(formatUnknownProvider(providerId, scope));
|
|
1934
2222
|
return 2;
|
|
1935
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
|
+
}
|
|
1936
2229
|
if (context.setupNoInteractive) return runNonInteractiveUpdate(context, options, providerEndpoint, existingProvider, config, path);
|
|
1937
2230
|
const result = await runProviderEditor({
|
|
1938
2231
|
config,
|
|
1939
|
-
existingProvider
|
|
2232
|
+
existingProvider: {
|
|
2233
|
+
...existingProvider,
|
|
2234
|
+
endpoint
|
|
2235
|
+
},
|
|
1940
2236
|
io: context.io,
|
|
1941
2237
|
mode: "update",
|
|
1942
|
-
source: providerUpdateSource(
|
|
2238
|
+
source: providerUpdateSource(endpoint)
|
|
1943
2239
|
});
|
|
1944
2240
|
if (result.status !== "confirmed") return 0;
|
|
1945
2241
|
const replaced = replaceSetupProvider(config, result.provider);
|
|
@@ -3421,7 +3717,7 @@ function isNodeError(error) {
|
|
|
3421
3717
|
}
|
|
3422
3718
|
async function runLintCommand(files, options, io, interceptConsoleOutput) {
|
|
3423
3719
|
if (options.config) await assertConfigExists(io.cwd, options.config);
|
|
3424
|
-
const [setupConfig, config] = await Promise.all([
|
|
3720
|
+
const [{ defaultModel, setupConfig }, config] = await Promise.all([loadRunSetupConfig(io), loadAlintConfig(io.cwd, options.config)]);
|
|
3425
3721
|
let lintTargets;
|
|
3426
3722
|
try {
|
|
3427
3723
|
lintTargets = await findLintTargets({
|
|
@@ -3456,42 +3752,48 @@ async function runLintCommand(files, options, io, interceptConsoleOutput) {
|
|
|
3456
3752
|
const writeResult = (runResult) => {
|
|
3457
3753
|
io.stdout.write(formatDiagnostics(options.format, runResult, { color: io.stdout.isTTY === true }));
|
|
3458
3754
|
};
|
|
3755
|
+
const runtime = await startModelAdapters(setupConfig, io);
|
|
3459
3756
|
let result;
|
|
3460
3757
|
try {
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
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
|
+
}
|
|
3474
3789
|
restoreProgressConsole?.();
|
|
3475
3790
|
progress?.dispose();
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
}
|
|
3482
|
-
if (error instanceof AlintRunCancelledError) {
|
|
3483
|
-
await persistStats(error.result);
|
|
3484
|
-
writeResult(error.result);
|
|
3485
|
-
io.stderr.write(formatCancelledError(error, io.stderr.isTTY === true));
|
|
3486
|
-
return 2;
|
|
3487
|
-
}
|
|
3488
|
-
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();
|
|
3489
3796
|
}
|
|
3490
|
-
restoreProgressConsole?.();
|
|
3491
|
-
progress?.dispose();
|
|
3492
|
-
await persistStats(result);
|
|
3493
|
-
writeResult(result);
|
|
3494
|
-
return result.diagnostics.some((diagnostic) => diagnostic.severity === "error") ? 1 : 0;
|
|
3495
3797
|
}
|
|
3496
3798
|
function shouldEnableProgress(options, io) {
|
|
3497
3799
|
if (options.progress !== void 0) return options.progress;
|
|
@@ -3660,7 +3962,7 @@ async function runInteractiveSetup(io) {
|
|
|
3660
3962
|
label: "Global",
|
|
3661
3963
|
value: "global"
|
|
3662
3964
|
}, {
|
|
3663
|
-
label: "Local project",
|
|
3965
|
+
label: "Local project - Warning: You are about to store your credentials in the folder",
|
|
3664
3966
|
value: "local"
|
|
3665
3967
|
}]
|
|
3666
3968
|
});
|
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-
|
|
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.
|
|
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/
|
|
32
|
-
"@alint-js/core": "0.
|
|
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",
|