@hifullmoon/aicommit 1.5.1 → 2.0.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.
@@ -1,8 +1,8 @@
1
1
  # Distribution / 分发
2
2
 
3
- AICommit 通过 npm 与 Homebrew 发布。Homebrew formula 安装同一个 npm tarball,并用 SHA-256 固定其内容。
3
+ AICommit 仅通过 npm 发布。
4
4
 
5
- AICommit is distributed through npm and Homebrew. The Homebrew formula installs the same npm tarball and pins it by SHA-256.
5
+ AICommit is distributed exclusively through npm.
6
6
 
7
7
  ## npm
8
8
 
@@ -26,36 +26,10 @@ The release workflow uses npm Trusted Publishing without a long-lived `NPM_TOKEN
26
26
  ```bash
27
27
  workdir=$(mktemp -d)
28
28
  cd "$workdir"
29
- npm install --package-lock-only @hifullmoon/aicommit@1.5.1
29
+ npm install --package-lock-only @hifullmoon/aicommit@2.0.0
30
30
  npm audit signatures
31
31
  ```
32
32
 
33
- ## Homebrew
34
-
35
- ```bash
36
- # install / 安装
37
- brew tap hi-fullmoon/aicommit https://github.com/hi-fullmoon/AICommit.git
38
- brew install hi-fullmoon/aicommit/aicommit
39
-
40
- # upgrade / 升级
41
- brew update
42
- brew upgrade hi-fullmoon/aicommit/aicommit
43
-
44
- # uninstall / 卸载
45
- brew uninstall aicommit
46
- brew untap hi-fullmoon/aicommit
47
- ```
48
-
49
- Formula 依赖 Homebrew 的 Node package,使用 Homebrew 标准 npm 安装参数,并测试 `--version`、`--help` 与无凭据配置校验。每次 npm 发布完成后,release workflow 会从公开 registry 再执行一次真实安装测试。
50
-
51
- The formula depends on Homebrew's Node package, uses Homebrew's standard npm installation arguments, and tests `--version`, `--help`, and credential-free configuration validation. After every npm publish, the release workflow performs a real install from the public registry.
52
-
53
- ## Integrity / 完整性
54
-
55
- `Formula/aicommit.rb` 中的 `sha256` 必须与对应 npm tarball 一致。维护者发布前通过 `scripts/release-assets.mjs` 生成 formula,工作流发布前会再次比较生成结果与已提交文件。
56
-
57
- The `sha256` in `Formula/aicommit.rb` must match the corresponding npm tarball. Maintainers generate the formula with `scripts/release-assets.mjs`, and the release workflow compares it again before publishing.
58
-
59
33
  ## Rollback / 回滚
60
34
 
61
35
  npm 用户可以立即固定上一可用版本:
@@ -64,13 +38,4 @@ npm 用户可以立即固定上一可用版本:
64
38
  npm install --global @hifullmoon/aicommit@<last-good-version>
65
39
  ```
66
40
 
67
- Homebrew 用户可从旧 tag 取出 formula 并本地安装:
68
-
69
- ```bash
70
- git clone https://github.com/hi-fullmoon/AICommit.git /tmp/aicommit-rollback
71
- git -C /tmp/aicommit-rollback show v1.4.0:Formula/aicommit.rb > /tmp/aicommit.rb
72
- brew uninstall aicommit
73
- brew install --formula /tmp/aicommit.rb
74
- ```
75
-
76
41
  已发布的 npm version 不应覆盖或复用。维护者应 deprecate 有问题的版本、恢复正确的 dist-tag,并发布修复 patch。完整流程见 [`RELEASING.md`](../RELEASING.md)。
package/docs/privacy.md CHANGED
@@ -6,14 +6,14 @@ AICommit 是本地 CLI,不是托管中转服务。仓库数据从本地进程
6
6
 
7
7
  ## Trust boundaries / 信任边界
8
8
 
9
- | Boundary / 边界 | Data visible there / 可见数据 | Default control / 默认控制 |
10
- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
11
- | Main CLI process / CLI 主进程 | Selected Git metadata/diff, effective config, resolved provider credential / 选定 Git 元数据与 diff、有效配置、provider 凭据 | User config owns connection fields; project config is filtered / 连接字段归用户配置,项目配置受过滤 |
12
- | Git subprocess / Git 子进程 | Requested status, diff, index/tree operations / 请求的 status、diff、index/tree 操作 | Explicit arguments, temporary indexes, fingerprints and recovery checkpoints / 显式参数、临时 index、指纹与恢复 checkpoint |
13
- | Provider endpoint / Provider 端点 | Prompt, protected selected diff/context, model options, Bearer credential / prompt、保护后的选定 diff/context、模型选项、Bearer 凭据 | Remote HTTPS except loopback; preview/protection before send; bounded context / 远端仅 HTTPS;发送前预览/保护;上下文有预算 |
14
- | Extension child / 扩展子进程 | Only the input for its declared interface / 仅声明接口所需输入 | Sanitized environment, no resolved credential, Node permission model, timeout/output bounds / 清理环境、不传凭据、Node 权限模型、超时/输出限制 |
15
- | Local metrics file / 本地指标文件 | Duration, token totals, bounded result, edited/rewrite flags / 延迟、token 总量、结果类别、编辑/重写标记 | Local-only, owner permissions, retention cap, disable/clear controls / 仅本地、owner 权限、保留上限、可关闭/清除 |
16
- | GitHub/npm/Homebrew during installation / 安装期 GitHub/npm/Homebrew | Package/formula download metadata / 包与 formula 下载元数据 | Signed tag, Sigstore/GitHub provenance, npm provenance, SHA-256 formula pin / 签名 tag、构建证明、npm provenance、SHA-256 固定 |
9
+ | Boundary / 边界 | Data visible there / 可见数据 | Default control / 默认控制 |
10
+ | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | Main CLI process / CLI 主进程 | Selected Git metadata/diff, effective config, resolved provider credential / 选定 Git 元数据与 diff、有效配置、provider 凭据 | User config owns connection fields; project config is filtered / 连接字段归用户配置,项目配置受过滤 |
12
+ | Git subprocess / Git 子进程 | Requested status, diff, index/tree operations / 请求的 status、diff、index/tree 操作 | Explicit arguments, temporary indexes, fingerprints and recovery checkpoints / 显式参数、临时 index、指纹与恢复 checkpoint |
13
+ | Provider endpoint / Provider 端点 | Prompt, protected selected diff/context, model options, Bearer credential / prompt、保护后的选定 diff/context、模型选项、Bearer 凭据 | Remote HTTPS except loopback; preview/protection before send; bounded context / 远端仅 HTTPS;发送前预览/保护;上下文有预算 |
14
+ | Extension child / 扩展子进程 | Only the input for its declared interface / 仅声明接口所需输入 | Sanitized environment, no resolved credential, Node permission model, timeout/output bounds / 清理环境、不传凭据、Node 权限模型、超时/输出限制 |
15
+ | Local metrics file / 本地指标文件 | Duration, token totals, bounded result, edited/rewrite flags / 延迟、token 总量、结果类别、编辑/重写标记 | Local-only, owner permissions, retention cap, disable/clear controls / 仅本地、owner 权限、保留上限、可关闭/清除 |
16
+ | GitHub/npm during installation / 安装期 GitHub/npm | Package download metadata / package 下载元数据 | Version tag and npm Trusted Publishing provenance / 版本 tag 与 npm Trusted Publishing provenance |
17
17
 
18
18
  ## Provider request contents / Provider 请求内容
19
19
 
@@ -1,8 +1,8 @@
1
1
  # Provider presets / Provider 预设
2
2
 
3
- Provider presets are versioned setup data, not request code. The stable adapters in `src/providers.js` own request/response behavior; `presets/provider-presets.json` only supplies a provider ID, display label, adapter ID, secure endpoint, default model, and optional bounded `extraBody` defaults. Adding another OpenAI-compatible service with the `custom` adapter does not change Git, interaction, or request orchestration code.
3
+ Provider presets are versioned setup data, not request code. The stable adapters in `src/providers.js` own request/response behavior; `presets/provider-presets.json` supplies a provider ID, display label, adapter ID, secure endpoint, a named model map, and the default model name. Each model supplies its API `modelId` and may include a label or bounded `extraBody` defaults. Adding another OpenAI-compatible service with the `custom` adapter does not change Git, interaction, or request orchestration code.
4
4
 
5
- Provider preset 是带版本的 setup 数据,不是请求代码。`src/providers.js` 中的稳定 adapter 负责请求/响应行为;`presets/provider-presets.json` 只提供 provider ID、显示名称、adapter ID、安全 endpoint、默认模型以及可选且有界的 `extraBody` 默认值。使用 `custom` adapter 新增另一个 OpenAI-compatible 服务时,不需要修改 Git、交互或请求编排代码。
5
+ Provider preset 是带版本的 setup 数据,不是请求代码。`src/providers.js` 中的稳定 adapter 负责请求/响应行为;`presets/provider-presets.json` 提供 provider ID、显示名称、adapter ID、安全 endpoint、命名模型表和默认模型名。每个模型提供 API `modelId`,也可提供显示名称或有界的 `extraBody` 默认值。使用 `custom` adapter 新增另一个 OpenAI-compatible 服务时,不需要修改 Git、交互或请求编排代码。
6
6
 
7
7
  ## Compatibility contract / 兼容契约
8
8
 
@@ -13,11 +13,11 @@ Every manifest declares:
13
13
  ```json
14
14
  {
15
15
  "kind": "aicommit-provider-presets",
16
- "schemaVersion": 1,
17
- "version": "1.0.0",
16
+ "schemaVersion": 2,
17
+ "version": "2.0.0",
18
18
  "compatibility": {
19
- "coreMinimum": "1.4.0",
20
- "coreMaximumExclusive": "2.0.0",
19
+ "coreMinimum": "1.5.1",
20
+ "coreMaximumExclusive": "3.0.0",
21
21
  "adapterContract": 1
22
22
  }
23
23
  }
@@ -27,13 +27,15 @@ Every manifest declares:
27
27
  - The core range is inclusive at `coreMinimum` and exclusive at `coreMaximumExclusive`.
28
28
  - Core prerelease versions and build metadata follow SemVer; build metadata is ignored for precedence.
29
29
  - `adapterContract` declares the request-adapter interface expected by every entry.
30
- - The runtime rejects unknown fields, duplicate/reserved IDs, unsupported adapters, remote HTTP, credentials, top-level `model`/`messages` overrides, oversized data, incompatible versions, and symlinked files.
30
+ - Every provider declares a non-empty `models` map and a `defaultModel` that references one entry. Model names are setup aliases; `modelId` is sent to the API.
31
+ - The runtime rejects unknown fields, duplicate/reserved IDs, unsupported adapters, remote HTTP, credentials, model-level `model`/`messages` overrides, oversized data, incompatible versions, and symlinked files.
31
32
 
32
33
  - `version` 标识可独立替换的 preset 数据版本。
33
34
  - core 范围包含 `coreMinimum`,不包含 `coreMaximumExclusive`。
34
35
  - Core 的预发布版本与构建元数据遵循 SemVer;构建元数据不参与优先级比较。
35
36
  - `adapterContract` 声明每个条目所依赖的请求 adapter 接口。
36
- - 运行时拒绝未知字段、重复/保留 ID、不支持的 adapter、远程 HTTP、凭据、顶层 `model`/`messages` 覆盖、超限数据、不兼容版本和符号链接文件。
37
+ - 每个 Provider 都声明非空 `models`,并通过 `defaultModel` 引用其中一项。模型名是 setup 使用的别名,`modelId` 才会发送给 API。
38
+ - 运行时拒绝未知字段、重复/保留 ID、不支持的 adapter、远程 HTTP、凭据、模型级 `model`/`messages` 覆盖、超限数据、不兼容版本和符号链接文件。
37
39
 
38
40
  The published JSON Schema is [`schemas/aicommit-provider-presets.schema.json`](../schemas/aicommit-provider-presets.schema.json). Runtime validation is authoritative and adds semantic/security checks that JSON Schema alone cannot express.
39
41
 
@@ -91,7 +93,18 @@ Add one entry to a copied manifest, bump `version`, validate, and install it:
91
93
  "label": "Acme Compatible",
92
94
  "adapter": "custom",
93
95
  "apiUrl": "https://api.acme.example/v1/chat/completions",
94
- "modelId": "acme-chat"
96
+ "defaultModel": "fast",
97
+ "models": {
98
+ "fast": {
99
+ "label": "Acme Chat",
100
+ "modelId": "acme-chat"
101
+ },
102
+ "quality": {
103
+ "label": "Acme Reasoner",
104
+ "modelId": "acme-reasoner",
105
+ "extraBody": { "reasoning": true }
106
+ }
107
+ }
95
108
  }
96
109
  ```
97
110
 
@@ -29,7 +29,6 @@ JSON 模式保证 stdout 只有一个机器对象,诊断进入 stderr。`error
29
29
  | Preset install/rollback fails / preset 安装或回滚失败 | `config` / `2` | Schema/core range/adapter contract mismatch or no valid backup / schema、核心范围、adapter contract 不匹配或无备份 | `aicommit preset validate --file=...`; `aicommit preset show`; follow the compatibility guide |
30
30
  | Extension requires Node 20+ / 扩展要求 Node 20+ | `config` / `2` | Node 18 intentionally has no executable-extension fallback / Node 18 有意不降级运行扩展 | Upgrade Node for extensions or remove user manifest entries; core CLI still supports Node 18 |
31
31
  | Extension timeout or permission error / 扩展超时或权限错误 | warning or `response_format` | Extension exceeded its limit or tried denied filesystem/process access / 扩展超时或访问被拒资源 | Review the extension, keep it single-file, raise `extensions.timeoutMs` cautiously; validator failures are fail-closed |
32
- | `brew install` checksum mismatch / Homebrew checksum 不一致 | Homebrew failure | Formula and registry tarball differ, stale tap, or tampered download / formula 与 tarball 不同、tap 过旧或下载被篡改 | `brew update`; compare `Formula/aicommit.rb` with the npm tarball; do not use `--force` to bypass integrity |
33
32
  | npm provenance is absent or invalid / npm provenance 缺失或失败 | npm audit failure | Old npm CLI, non-trusted release, or wrong version / npm 过旧、非可信发布或版本错误 | Upgrade npm; run `npm audit signatures`; install only a version linked to the official workflow |
34
33
 
35
34
  If a failure remains, capture `aicommit doctor --output=json`, Node/Git versions, the error category, and redacted config sources. Never attach a diff, commit message, config file, API key, reasoning trace, extension source containing secrets, or credential-helper output to a public issue.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hifullmoon/aicommit",
3
- "version": "1.5.1",
3
+ "version": "2.0.0",
4
4
  "description": "Safe, local-first AI commit message generator for Git workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,9 +29,8 @@
29
29
  "coverage": "c8 node --test --test-concurrency=1",
30
30
  "eval": "node eval/run.mjs",
31
31
  "test:package": "node scripts/package-smoke.mjs",
32
- "test:homebrew": "node scripts/homebrew-smoke.mjs",
33
32
  "docs:terminal-demo": "node scripts/readme-terminal-demo.mjs",
34
- "release:assets": "node scripts/release-assets.mjs",
33
+ "release:version": "node scripts/update-version.mjs",
35
34
  "release:npm:check": "node scripts/verify-release.mjs && npm run ci && npm run test:package && npm pack --dry-run",
36
35
  "ci": "npm run lint && npm run format:check && npm run eval && npm run coverage"
37
36
  },
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "kind": "aicommit-provider-presets",
3
- "schemaVersion": 1,
4
- "version": "1.1.0",
3
+ "schemaVersion": 2,
4
+ "version": "2.0.0",
5
5
  "compatibility": {
6
- "coreMinimum": "1.4.0",
7
- "coreMaximumExclusive": "2.0.0",
6
+ "coreMinimum": "1.5.1",
7
+ "coreMaximumExclusive": "3.0.0",
8
8
  "adapterContract": 1
9
9
  },
10
10
  "providers": [
@@ -13,33 +13,55 @@
13
13
  "label": "OpenAI",
14
14
  "adapter": "openai",
15
15
  "apiUrl": "https://api.openai.com/v1/chat/completions",
16
- "modelId": "gpt-4o"
16
+ "defaultModel": "gpt-4o",
17
+ "models": {
18
+ "gpt-4o": {
19
+ "label": "GPT-4o",
20
+ "modelId": "gpt-4o"
21
+ }
22
+ }
17
23
  },
18
24
  {
19
25
  "id": "deepseek",
20
26
  "label": "DeepSeek",
21
27
  "adapter": "deepseek",
22
28
  "apiUrl": "https://api.deepseek.com/v1/chat/completions",
23
- "modelId": "deepseek-v4-flash"
29
+ "defaultModel": "chat",
30
+ "models": {
31
+ "chat": {
32
+ "label": "DeepSeek Chat",
33
+ "modelId": "deepseek-v4-flash"
34
+ }
35
+ }
24
36
  },
25
37
  {
26
38
  "id": "openrouter",
27
39
  "label": "OpenRouter",
28
40
  "adapter": "openrouter",
29
41
  "apiUrl": "https://openrouter.ai/api/v1/chat/completions",
30
- "modelId": "openai/gpt-4o-mini"
42
+ "defaultModel": "default",
43
+ "models": {
44
+ "default": {
45
+ "modelId": "openai/gpt-4o-mini"
46
+ }
47
+ }
31
48
  },
32
49
  {
33
50
  "id": "minimax",
34
51
  "label": "MiniMax",
35
52
  "adapter": "minimax",
36
53
  "apiUrl": "https://api.minimaxi.com/v1/chat/completions",
37
- "modelId": "MiniMax-M3",
38
- "extraBody": {
39
- "thinking": {
40
- "type": "disabled"
41
- },
42
- "reasoning_split": true
54
+ "defaultModel": "default",
55
+ "models": {
56
+ "default": {
57
+ "modelId": "MiniMax-M3",
58
+ "extraBody": {
59
+ "thinking": {
60
+ "type": "disabled"
61
+ },
62
+ "reasoning_split": true
63
+ }
64
+ }
43
65
  }
44
66
  },
45
67
  {
@@ -47,14 +69,25 @@
47
69
  "label": "Kimi Code",
48
70
  "adapter": "custom",
49
71
  "apiUrl": "https://api.kimi.com/coding/v1/chat/completions",
50
- "modelId": "kimi-for-coding"
72
+ "defaultModel": "default",
73
+ "models": {
74
+ "default": {
75
+ "modelId": "kimi-for-coding"
76
+ }
77
+ }
51
78
  },
52
79
  {
53
80
  "id": "ollama",
54
81
  "label": "Ollama (local)",
55
82
  "adapter": "ollama",
56
83
  "apiUrl": "http://127.0.0.1:11434/api/chat",
57
- "modelId": "qwen3:8b"
84
+ "defaultModel": "qwen",
85
+ "models": {
86
+ "qwen": {
87
+ "label": "Qwen 3 8B",
88
+ "modelId": "qwen3:8b"
89
+ }
90
+ }
58
91
  }
59
92
  ]
60
93
  }
@@ -7,7 +7,7 @@
7
7
  "required": ["kind", "schemaVersion", "version", "compatibility", "providers"],
8
8
  "properties": {
9
9
  "kind": { "const": "aicommit-provider-presets" },
10
- "schemaVersion": { "const": 1 },
10
+ "schemaVersion": { "const": 2 },
11
11
  "version": {
12
12
  "type": "string",
13
13
  "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
@@ -35,7 +35,7 @@
35
35
  "items": {
36
36
  "type": "object",
37
37
  "additionalProperties": false,
38
- "required": ["id", "label", "adapter", "apiUrl", "modelId"],
38
+ "required": ["id", "label", "adapter", "apiUrl", "defaultModel", "models"],
39
39
  "properties": {
40
40
  "id": {
41
41
  "type": "string",
@@ -47,8 +47,28 @@
47
47
  "enum": ["openai", "openrouter", "deepseek", "minimax", "ollama", "custom"]
48
48
  },
49
49
  "apiUrl": { "type": "string", "minLength": 1, "maxLength": 2048 },
50
- "modelId": { "type": "string", "minLength": 1, "maxLength": 256 },
51
- "extraBody": { "type": "object" }
50
+ "defaultModel": {
51
+ "type": "string",
52
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
53
+ },
54
+ "models": {
55
+ "type": "object",
56
+ "minProperties": 1,
57
+ "maxProperties": 100,
58
+ "propertyNames": {
59
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
60
+ },
61
+ "additionalProperties": {
62
+ "type": "object",
63
+ "additionalProperties": false,
64
+ "required": ["modelId"],
65
+ "properties": {
66
+ "label": { "type": "string", "minLength": 1, "maxLength": 80 },
67
+ "modelId": { "type": "string", "minLength": 1, "maxLength": 256 },
68
+ "extraBody": { "type": "object" }
69
+ }
70
+ }
71
+ }
52
72
  }
53
73
  }
54
74
  }
package/src/cli.js CHANGED
@@ -45,6 +45,7 @@ function showHelp() {
45
45
  -v, --version Show version number
46
46
  -l, --lang=<zh|en> Commit message language (default: zh)
47
47
  -p, --provider=<name> Use the named provider from config "providers"
48
+ -m, --model=<name> Use a named model from the selected provider
48
49
  --split-hunks Experimental same-file hunk planning (default: off)
49
50
  --scope=<scope> Scope for "split|split plan": staged, all
50
51
  --file=<path> Split-plan artifact or commit-message file
@@ -72,7 +73,7 @@ function showHelp() {
72
73
  aicommit stats Show local acceptance, quality, latency, and token trends
73
74
  aicommit Commit changes in current directory (Chinese)
74
75
  aicommit --lang=en Generate English commit message
75
- aicommit -p deepseek Switch to the "deepseek" provider from config
76
+ aicommit -p deepseek -m chat Select a configured provider and model
76
77
  aicommit split Choose staged/all scope, then plan logical commits
77
78
  aicommit split --scope=staged Split only the reviewed index snapshot
78
79
  aicommit split --scope=all --yes Split the complete working tree non-interactively
@@ -109,6 +110,7 @@ function parsedDefaults(overrides = {}) {
109
110
  targetPath: null,
110
111
  cliLang: null,
111
112
  cliProvider: null,
113
+ cliModel: null,
112
114
  cliReasoning: null,
113
115
  output: 'text',
114
116
  debug: false,
@@ -228,6 +230,7 @@ export function parseArgs(args = process.argv.slice(2)) {
228
230
  let targetPath = null;
229
231
  let cliLang = null;
230
232
  let cliProvider = null;
233
+ let cliModel = null;
231
234
  let cliReasoning = null;
232
235
  let output = 'text';
233
236
  let debug = false;
@@ -387,6 +390,23 @@ export function parseArgs(args = process.argv.slice(2)) {
387
390
  continue;
388
391
  }
389
392
 
393
+ if (arg === '-m' || arg === '--model') {
394
+ cliModel = takeValue(args, i, arg, 'name');
395
+ i++;
396
+ continue;
397
+ }
398
+
399
+ if (arg.startsWith('--model=')) {
400
+ cliModel = arg.slice('--model='.length);
401
+ if (!cliModel) throw fail(ERROR_CATEGORIES.CONFIG, 'Missing value for --model.');
402
+ continue;
403
+ }
404
+
405
+ if (arg.startsWith('-m') && arg.length > 2) {
406
+ cliModel = arg.slice(2);
407
+ continue;
408
+ }
409
+
390
410
  if (!arg.startsWith('-')) {
391
411
  if (targetPath) {
392
412
  throw fail(
@@ -439,7 +459,7 @@ export function parseArgs(args = process.argv.slice(2)) {
439
459
  'split apply reads its scope from the plan; do not pass --scope or --split-hunks.',
440
460
  );
441
461
  }
442
- if (cliProvider || cliLang || cliReasoning || dryRun) {
462
+ if (cliProvider || cliModel || cliLang || cliReasoning || dryRun) {
443
463
  throw fail(
444
464
  ERROR_CATEGORIES.CONFIG,
445
465
  'split apply accepts only --file, --yes, --output, and --debug.',
@@ -451,6 +471,7 @@ export function parseArgs(args = process.argv.slice(2)) {
451
471
  split ||
452
472
  splitHunks ||
453
473
  cliProvider ||
474
+ cliModel ||
454
475
  cliLang ||
455
476
  cliReasoning ||
456
477
  dryRun
@@ -484,15 +505,23 @@ export function parseArgs(args = process.argv.slice(2)) {
484
505
  ) {
485
506
  throw fail(
486
507
  ERROR_CATEGORIES.CONFIG,
487
- 'config accepts only an optional path, --provider, --output, and --debug.',
508
+ 'config accepts only an optional path, --provider, --model, --output, and --debug.',
488
509
  );
489
510
  }
490
- if (configAction === 'path' && cliProvider) {
491
- throw fail(ERROR_CATEGORIES.CONFIG, 'config path does not accept --provider.');
511
+ if (configAction === 'path' && (cliProvider || cliModel)) {
512
+ throw fail(ERROR_CATEGORIES.CONFIG, 'config path does not accept --provider or --model.');
492
513
  }
493
514
  if (
494
515
  policyAction &&
495
- (cliProvider || cliLang || cliReasoning || split || splitHunks || splitCommand || dryRun || yes)
516
+ (cliProvider ||
517
+ cliModel ||
518
+ cliLang ||
519
+ cliReasoning ||
520
+ split ||
521
+ splitHunks ||
522
+ splitCommand ||
523
+ dryRun ||
524
+ yes)
496
525
  ) {
497
526
  throw fail(
498
527
  ERROR_CATEGORIES.CONFIG,
@@ -509,6 +538,7 @@ export function parseArgs(args = process.argv.slice(2)) {
509
538
  presetAction &&
510
539
  (targetPath ||
511
540
  cliProvider ||
541
+ cliModel ||
512
542
  cliLang ||
513
543
  cliReasoning ||
514
544
  split ||
@@ -534,7 +564,7 @@ export function parseArgs(args = process.argv.slice(2)) {
534
564
  ) {
535
565
  throw fail(
536
566
  ERROR_CATEGORIES.CONFIG,
537
- 'doctor accepts only --provider, --output, and --debug options.',
567
+ 'doctor accepts only --provider, --model, --output, and --debug options.',
538
568
  );
539
569
  }
540
570
 
@@ -542,6 +572,7 @@ export function parseArgs(args = process.argv.slice(2)) {
542
572
  targetPath,
543
573
  cliLang,
544
574
  cliProvider,
575
+ cliModel,
545
576
  cliReasoning,
546
577
  output,
547
578
  debug,
package/src/completion.js CHANGED
@@ -11,6 +11,7 @@ const TOP_LEVEL = [
11
11
  '--version',
12
12
  '--lang',
13
13
  '--provider',
14
+ '--model',
14
15
  '--split-hunks',
15
16
  '--scope',
16
17
  '--reasoning',
@@ -30,7 +31,7 @@ _aicommit() {
30
31
  command="\${COMP_WORDS[1]}"
31
32
 
32
33
  case "$command" in
33
- config) COMPREPLY=( $(compgen -W "show validate path --provider --output --debug" -- "$cur") ); return ;;
34
+ config) COMPREPLY=( $(compgen -W "show validate path --provider --model --output --debug" -- "$cur") ); return ;;
34
35
  policy) COMPREPLY=( $(compgen -W "template check --file --range --output --debug" -- "$cur") ); return ;;
35
36
  preset) COMPREPLY=( $(compgen -W "show validate path install rollback --file --output --debug" -- "$cur") ); return ;;
36
37
  completion) COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") ); return ;;
@@ -66,6 +67,7 @@ _aicommit() {
66
67
  '--version[show version]'
67
68
  '--lang=[commit language]:language:(zh en)'
68
69
  '--provider=[provider name]:provider:'
70
+ '--model=[model name]:model:'
69
71
  '--split-hunks[experimental same-file hunk splitting]'
70
72
  '--scope=[split scope]:scope:(staged all)'
71
73
  '--file=[split plan or commit-message file]:path:_files'
@@ -115,6 +117,7 @@ complete -c aicommit -s h -l help -d 'Show help'
115
117
  complete -c aicommit -s v -l version -d 'Show version'
116
118
  complete -c aicommit -s l -l lang -x -a 'zh en' -d 'Commit language'
117
119
  complete -c aicommit -s p -l provider -x -d 'Provider name'
120
+ complete -c aicommit -s m -l model -x -d 'Model name'
118
121
  complete -c aicommit -l split-hunks -d 'Experimental same-file hunk splitting'
119
122
  complete -c aicommit -l scope -x -a 'staged all' -d 'Split plan scope'
120
123
  complete -c aicommit -l file -r -d 'Split plan file'
@@ -28,7 +28,10 @@ function printPaths(paths) {
28
28
  );
29
29
  }
30
30
 
31
- export async function runConfigCommand(action, { provider = null, machineOutput = false } = {}) {
31
+ export async function runConfigCommand(
32
+ action,
33
+ { provider = null, model = null, machineOutput = false } = {},
34
+ ) {
32
35
  const projectRoot = getProjectRoot();
33
36
  const paths = await inspectConfigPaths(projectRoot);
34
37
  if (action === 'path') {
@@ -36,18 +39,17 @@ export async function runConfigCommand(action, { provider = null, machineOutput
36
39
  return { exitReason: 'config_path', data: { projectRoot, paths } };
37
40
  }
38
41
 
39
- const loaded = await loadConfig(provider, { resolveCredentials: false });
42
+ const loaded = await loadConfig(provider, { model, resolveCredentials: false });
40
43
  const data = {
41
44
  projectRoot: loaded.projectRoot,
42
45
  sources: loaded.loaded,
43
46
  provider: loaded.providerName,
47
+ model: loaded.modelName,
44
48
  paths,
45
49
  };
46
50
  if (action === 'validate') {
47
51
  if (!machineOutput) {
48
- console.log(
49
- `Configuration valid${loaded.providerName ? ` for provider "${loaded.providerName}"` : ''}.`,
50
- );
52
+ console.log(`Configuration valid for ${loaded.providerName}/${loaded.modelName}.`);
51
53
  printPaths(paths);
52
54
  }
53
55
  return { exitReason: 'config_valid', data };