orcommit 1.2.21 → 1.2.23

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
@@ -1,181 +1,217 @@
1
1
  # ORCommit
2
2
 
3
- ### AI-powered Git commits with security, standards, and full control
3
+ ### AI-powered git commit messages — secure, conventional, multi-provider
4
4
 
5
5
  <p align="center">
6
6
  <img src="https://unpkg.com/orcommit@latest/preview.png" alt="ORCommit Banner" width="600" />
7
7
  </p>
8
8
 
9
- > Generate **accurate, conventional, and secure** git commit messages using **OpenAI, Claude, OpenRouter, or local models (Ollama)**.
10
-
11
9
  ```bash
12
10
  git add .
13
11
  orc commit
14
12
  ```
15
13
 
16
- Conventional Commits
17
- ✔ Secret scanning (Gitleaks)
18
- ✔ Cloud & local AI
19
- ✔ Zero-config to start
14
+ ORCommit reads your staged diff and writes a clean, [Conventional Commit](https://www.conventionalcommits.org/) message for you — using OpenRouter, OpenAI, a local model (Ollama), or any OpenAI-compatible API. It scans for secrets before committing, so you don't leak keys into git history.
20
15
 
21
16
  <p align="center">
22
17
  <a href="https://badge.fury.io/js/orcommit"><img src="https://badge.fury.io/js/orcommit.svg" alt="npm version"></a>
23
- <a href="https://github.com/ellerbrock/typescript-badges/"><img src="https://badges.frapsoft.com/typescript/code/typescript.svg?v=101" alt="TypeScript"></a>
24
18
  <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
25
19
  </p>
26
20
 
27
21
  ---
28
22
 
29
- ## TL;DR
23
+ ## Install
30
24
 
31
- **ORCommit** is a production-grade CLI that:
25
+ ```bash
26
+ npm install -g orcommit
27
+ ```
32
28
 
33
- * analyzes your staged git diff
34
- * generates a high-quality commit message via LLMs
35
- * enforces Conventional Commits
36
- * blocks secrets and dependency folders **before** commit
37
- * works with both **cloud and local** AI models
29
+ > **Never use `sudo npm install -g`.** A root-owned install breaks every later
30
+ > update with `EACCES`. If the install asks for elevated permissions, fix your
31
+ > npm prefix once (no sudo ever again):
32
+ >
33
+ > ```bash
34
+ > mkdir -p ~/.npm-global
35
+ > npm config set prefix ~/.npm-global
36
+ > echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.zshrc
37
+ > source ~/.zshrc
38
+ > ```
38
39
 
39
- If you care about **clean history, security, and standards** this tool is for you.
40
+ Update with `npm install -g orcommit@latest`. ORCommit tells you when a new
41
+ version exists — it never auto-installs and never asks for sudo.
40
42
 
41
43
  ---
42
44
 
43
- ## Key Features
45
+ ## Setup (60 seconds)
44
46
 
45
- ### 🤖 AI Providers
47
+ Pick a provider and give it a key.
46
48
 
47
- * OpenRouter (200+ models — Gemini, Claude, GPT, and more)
48
- * OpenAI (GPT‑4o, GPT‑4o‑mini)
49
- * Local models via **Ollama** (offline & private)
49
+ **OpenRouter** (recommended — 200+ models, one key):
50
50
 
51
- Sensible defaults out of the box: `google/gemini-2.5-flash-lite` on OpenRouter
52
- (cheap, fast, great structured output) and `gpt-4o-mini` on OpenAI.
51
+ ```bash
52
+ orc config set openrouter YOUR_API_KEY
53
+ ```
53
54
 
54
- ### 🧠 Smart Commit Generation
55
+ **OpenAI:**
55
56
 
56
- * **Schema-constrained output** — the model is forced to return valid structured
57
- JSON (json_schema / constrained decoding), so responses don't need brittle parsing
58
- * **Grounded in your diff** — messages describe only what the diff actually shows,
59
- no invented or boilerplate changes
60
- * Token-aware diff chunking (large repos supported)
61
- * Interactive regeneration with feedback
62
- * Custom prompts & project context
63
- * Conventional Commits by default
57
+ ```bash
58
+ orc config set openai YOUR_API_KEY
59
+ ```
60
+
61
+ Then commit:
62
+
63
+ ```bash
64
+ git add .
65
+ orc commit
66
+ ```
64
67
 
65
- ### 🔐 Security by Default
68
+ That's it. **Whichever provider you configure last becomes the active one** —
69
+ configure a key (or a custom provider), and it's immediately used. You normally
70
+ keep just one active provider and never think about it.
66
71
 
67
- * Secret scanning via **Gitleaks** (100+ patterns)
68
- * Blocks API keys, tokens, private keys
69
- * Prevents committing `node_modules/`, `vendor/`, etc.
70
- * Secure API key storage (600 permissions)
72
+ ---
71
73
 
72
- ### ⚙️ Git-Native Workflow
74
+ ## How it works
73
75
 
74
- * Breaking change detection
75
- * Optional push after commit
76
- * Git hooks support
76
+ When you run `orc commit`, ORCommit:
77
77
 
78
- ### Fast & Reliable
78
+ 1. **Reads** your staged diff (`git add` first — it only looks at staged changes).
79
+ 2. **Scans** it for secrets with Gitleaks. If it finds an API key, token, or
80
+ private key, it **stops** — nothing is committed.
81
+ 3. **Sends** the diff to your AI provider, which returns a structured commit
82
+ message (schema-constrained JSON, so no brittle text parsing).
83
+ 4. **Shows** you the message. You confirm, regenerate with feedback, or edit it.
84
+ 5. **Commits** (and optionally pushes).
79
85
 
80
- * Per-repository memory + disk cache (no cross-project message bleed)
81
- * Parallel API calls
82
- * Strict TypeScript + comprehensive tests
86
+ Large diffs are chunked automatically, and messages are cached per-repository so
87
+ the same diff isn't re-billed.
83
88
 
84
89
  ---
85
90
 
86
- ## 🚀 Quick Start
91
+ ## Everyday commands
87
92
 
88
93
  ```bash
89
- npm install -g orcommit
90
- orc config set openrouter YOUR_API_KEY
91
-
92
- git add .
93
- orc commit
94
+ orc commit # interactive — review before committing
95
+ orc commit -y # auto-confirm, skip the prompt
96
+ orc commit -p openai # use a specific provider for this commit
97
+ orc commit --dry-run # generate the message, don't commit
98
+ orc commit --context "..." # give the AI extra context
99
+ orc commit --emoji # gitmoji style
100
+ orc commit --breaking # mark as a breaking change
94
101
  ```
95
102
 
96
- That’s it.
103
+ | Command | What it does |
104
+ |---|---|
105
+ | `orc commit` | Generate and create a commit |
106
+ | `orc config` | Manage providers and settings |
107
+ | `orc test [provider]` | Check a provider's connection works |
108
+ | `orc doctor` | Diagnose install / PATH / update problems |
109
+ | `orc cache` | Manage the commit-message cache |
97
110
 
98
- > **Don't use `sudo npm install -g`.** A root-owned global install creates files
99
- > that break every later (non-sudo) update with `EACCES`. If `npm install -g`
100
- > asks for elevated permissions, your npm prefix is system-owned — fix it once
101
- > with a user-owned prefix (no sudo ever again):
102
- >
103
- > ```bash
104
- > mkdir -p ~/.npm-global
105
- > npm config set prefix ~/.npm-global
106
- > echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.zshrc
107
- > source ~/.zshrc
108
- > ```
111
+ Full flag list: `orc commit --help`.
112
+
113
+ ---
114
+
115
+ ## Managing providers
109
116
 
110
- ### Updating
117
+ See everything that's configured:
111
118
 
112
119
  ```bash
113
- npm install -g orcommit@latest
120
+ orc config get # all providers + the current default
121
+ orc config path # where the config file lives
114
122
  ```
115
123
 
116
- orc also tells you when a newer version is available. It never auto-installs and
117
- never asks for sudo.
124
+ Set or change a key / model on an existing provider:
118
125
 
119
- ### Troubleshooting
126
+ ```bash
127
+ orc config set <provider> <api-key>
128
+ orc config model <provider> <model>
129
+ ```
120
130
 
121
- If `orc` reports the wrong version, won't update, or you suspect duplicate
122
- installs, run the built-in diagnostic — it inspects your npm prefix, every `orc`
123
- on your `PATH`, and the installed-vs-latest version, then prints exact fixes:
131
+ Add **any OpenAI-compatible endpoint** as a custom provider:
124
132
 
125
133
  ```bash
126
- orc doctor
134
+ orc config provider <name> \
135
+ --base-url <url> \
136
+ --key <api-key> \
137
+ --model <model> \
138
+ --auth-header <header> # optional — default: Authorization
139
+ --auth-scheme <scheme> # optional — default: Bearer (pass "" to send the key raw)
127
140
  ```
128
141
 
129
- ---
142
+ > Custom providers **must** set `--model`. ORCommit only ships default models for
143
+ > the built-in `openrouter` and `openai` providers — it can't guess a third
144
+ > party's catalog.
130
145
 
131
- ## 🛠 Common Commands
146
+ Use a provider for one commit, or remove it:
132
147
 
133
148
  ```bash
134
- orc commit # interactive commit
135
- orc commit --yes # auto-confirm
136
- orc commit --context "..." # extra context
137
- orc commit --emoji # gitmoji
138
- orc commit --breaking # breaking change
139
- orc commit --dry-run # preview only
140
- orc doctor # diagnose install / PATH / update issues
149
+ orc commit -p <name>
150
+ orc config remove-provider <name> # can't remove the current default — switch first
141
151
  ```
142
152
 
143
- 👉 [Full CLI reference](https://github.com/markolofsen/openrouter-commit/blob/main/docs/cli.md)
153
+ ### Switching the active provider
144
154
 
145
- ---
155
+ Configuring a provider already makes it active, so usually there's nothing to
156
+ do. To switch back to an already-configured provider (e.g. your default ran out
157
+ of credit and you want to use another one you'd set up earlier):
146
158
 
147
- ## 🔐 Security Highlights
159
+ ```bash
160
+ orc config default <name>
161
+ ```
148
162
 
149
- ORCommit includes **mandatory security checks**:
163
+ Or pass `-p <name>` on a single commit without changing the active provider.
150
164
 
151
- * 🔍 Secret scanning via **Gitleaks**
152
- * 🚫 Blocks API keys, tokens, private keys
153
- * 🚫 Prevents committing dependency folders
165
+ Removing the active provider automatically falls back to another configured one
166
+ you never end up with the default pointing at a provider that's gone.
154
167
 
155
- These checks run **before** commit creation and cannot be bypassed accidentally.
168
+ ---
156
169
 
157
- 👉 [Security details](https://github.com/markolofsen/openrouter-commit/blob/main/docs/security.md)
170
+ ## Example: a custom provider (cmdop)
158
171
 
159
- ---
172
+ [cmdop](https://cmdop.com)'s router is OpenAI-compatible but authenticates with
173
+ an `X-API-Key` header (not `Authorization: Bearer`) and uses model aliases like
174
+ `@cheap` / `@fast` / `@balanced` / `@smart`:
160
175
 
161
- ## 💡 Who Is ORCommit For?
176
+ ```bash
177
+ orc config provider cmdop \
178
+ --base-url https://router.cmdop.com/v1 \
179
+ --key YOUR_CMDOP_API_KEY \
180
+ --model @fast \
181
+ --auth-header X-API-Key
162
182
 
163
- * **Teams** — enforce commit standards automatically
164
- * **Open Source** — keep contribution quality high
165
- * **Enterprise** — prevent leaks and ensure compliance
183
+ orc commit -p cmdop
184
+ ```
185
+
186
+ This adds an entry to `~/.config/orcommit.json`:
187
+
188
+ ```json
189
+ {
190
+ "providers": {
191
+ "cmdop": {
192
+ "baseUrl": "https://router.cmdop.com/v1",
193
+ "apiKey": "YOUR_CMDOP_API_KEY",
194
+ "model": "@fast",
195
+ "authHeader": "X-API-Key"
196
+ }
197
+ }
198
+ }
199
+ ```
200
+
201
+ Because `authHeader` isn't `Authorization`, the key is sent **raw** (no `Bearer`
202
+ prefix). For endpoints that use bearer auth with a non-standard scheme, use
203
+ `--auth-scheme` instead.
166
204
 
167
205
  ---
168
206
 
169
- ## ⚙️ Configuration
207
+ ## Configuration
170
208
 
171
- Config is stored at `~/.config/orcommit.json` (permissions `600`).
209
+ Config lives at `~/.config/orcommit.json` (permissions `600`). A minimal file:
172
210
 
173
211
  ```json
174
212
  {
175
213
  "providers": {
176
- "openrouter": {
177
- "model": "google/gemini-2.5-flash-lite"
178
- }
214
+ "openrouter": { "model": "google/gemini-2.5-flash-lite" }
179
215
  },
180
216
  "preferences": {
181
217
  "defaultProvider": "openrouter",
@@ -186,47 +222,42 @@ Config is stored at `~/.config/orcommit.json` (permissions `600`).
186
222
  ```
187
223
 
188
224
  > A low `temperature` (default `0.3`) keeps messages grounded in the actual diff
189
- > and avoids drifting into generic, memorized phrasings.
225
+ > instead of drifting into generic phrasing.
190
226
 
191
- Environment variables are also supported:
227
+ API keys can also come from the environment:
192
228
 
193
229
  ```bash
194
230
  export OPENROUTER_API_KEY="your-key"
195
231
  export OPENAI_API_KEY="your-key"
196
232
  ```
197
233
 
198
- ---
199
-
200
- ## 📚 Documentation
201
-
202
- * [CLI Reference](https://github.com/markolofsen/openrouter-commit/blob/main/docs/cli.md)
203
- * [Security Model](https://github.com/markolofsen/openrouter-commit/blob/main/docs/security.md)
204
- * [Architecture](https://github.com/markolofsen/openrouter-commit/blob/main/docs/architecture.md)
205
- * [Advanced Usage](https://github.com/markolofsen/openrouter-commit/blob/main/docs/advanced.md)
234
+ Defaults out of the box: `google/gemini-2.5-flash-lite` on OpenRouter (cheap,
235
+ fast, good structured output) and `gpt-4o-mini` on OpenAI.
206
236
 
207
237
  ---
208
238
 
209
- ## 🤝 Contributing
239
+ ## Troubleshooting
210
240
 
211
- 1. Fork the repository
212
- 2. Create a feature branch
213
- 3. Add tests
214
- 4. Submit a pull request
241
+ | Problem | Fix |
242
+ |---|---|
243
+ | `402 Insufficient credits` | Your provider is out of credit. Top up, switch with `orc config default <name>`, or commit once via `-p <name>`. |
244
+ | Wrong version / won't update / duplicate installs | Run `orc doctor`. |
245
+ | Provider not responding | Run `orc test <provider>` to check the connection. |
215
246
 
216
247
  ---
217
248
 
218
- ## 🏢 About the Maintainers
249
+ ## Documentation
219
250
 
220
- ORCommit is built and maintained by **[Reforms.ai](https://reforms.ai)** — a team specializing in AI-powered developer tools.
221
-
222
- Commercial support, consulting, and custom AI integrations are available.
251
+ * [CLI Reference](https://github.com/markolofsen/openrouter-commit/blob/main/docs/cli.md)
252
+ * [Security Model](https://github.com/markolofsen/openrouter-commit/blob/main/docs/security.md)
253
+ * [Architecture](https://github.com/markolofsen/openrouter-commit/blob/main/docs/architecture.md)
254
+ * [Advanced Usage](https://github.com/markolofsen/openrouter-commit/blob/main/docs/advanced.md)
223
255
 
224
256
  ---
225
257
 
226
- ## 📄 License
258
+ ## About
227
259
 
228
- MIT License see [LICENSE](LICENSE).
229
-
230
- ---
260
+ ORCommit is built and maintained by **[Reforms.ai](https://reforms.ai)**.
261
+ Commercial support and custom AI integrations are available.
231
262
 
232
- Built with ❤️ using TypeScript and modern AI tooling.
263
+ MIT License see [LICENSE](LICENSE).
package/dist/cli.d.ts CHANGED
@@ -25,6 +25,18 @@ declare class CliApplication {
25
25
  * Handle model configuration command
26
26
  */
27
27
  private handleConfigModel;
28
+ /**
29
+ * Handle full custom-provider configuration in one call
30
+ */
31
+ private handleConfigProvider;
32
+ /**
33
+ * Handle removing a configured provider
34
+ */
35
+ private handleConfigRemoveProvider;
36
+ /**
37
+ * Handle setting the active (default) provider
38
+ */
39
+ private handleConfigDefault;
28
40
  /**
29
41
  * Handle custom prompt configuration
30
42
  */
@@ -61,12 +73,15 @@ declare class CliApplication {
61
73
  * Validate commit type
62
74
  */
63
75
  private validateCommitType;
76
+ private static readonly PROVIDER_NAME_RE;
64
77
  /**
65
- * Validate provider
78
+ * Validate provider name (commander option parser). Accepts any
79
+ * syntactically valid provider identifier.
66
80
  */
67
81
  private validateProvider;
68
82
  /**
69
- * Check if provider is valid
83
+ * Check whether a provider name is syntactically valid (not whether it is
84
+ * one of the built-ins — any registered provider is allowed).
70
85
  */
71
86
  private isValidProvider;
72
87
  /**
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAiBA;;GAEG;AACH,cAAM,cAAc;IAClB,OAAO,CAAC,OAAO,CAAU;;IAQzB;;OAEG;IACH,OAAO,CAAC,aAAa;IAoJrB;;OAEG;YACW,mBAAmB;IAwBjC;;OAEG;YACW,eAAe;IAgB7B;;OAEG;YACW,eAAe;IA0C7B;;OAEG;YACW,iBAAiB;IAgB/B;;OAEG;YACW,kBAAkB;IAkBhC;;OAEG;YACW,iBAAiB;IA+B/B;;OAEG;YACW,mBAAmB;IAYjC;;OAEG;YACW,eAAe;IAQ7B;;OAEG;YACW,gBAAgB;IAmB9B;;OAEG;YACW,gBAAgB;IAc9B;;OAEG;YACW,kBAAkB;IAchC;;OAEG;YACW,eAAe;IAgB7B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAa1B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAOxB;;OAEG;IACH,OAAO,CAAC,eAAe;IAIvB;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAqC3B;AASD,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAiBA;;GAEG;AACH,cAAM,cAAc;IAClB,OAAO,CAAC,OAAO,CAAU;;IAQzB;;OAEG;IACH,OAAO,CAAC,aAAa;IA8KrB;;OAEG;YACW,mBAAmB;IAwBjC;;OAEG;YACW,eAAe;IAgB7B;;OAEG;YACW,eAAe;IAmD7B;;OAEG;YACW,iBAAiB;IAgB/B;;OAEG;YACW,oBAAoB;IA0BlC;;OAEG;YACW,0BAA0B;IAgBxC;;OAEG;YACW,mBAAmB;IAgBjC;;OAEG;YACW,kBAAkB;IAkBhC;;OAEG;YACW,iBAAiB;IA+B/B;;OAEG;YACW,mBAAmB;IAYjC;;OAEG;YACW,eAAe;IAQ7B;;OAEG;YACW,gBAAgB;IAmB9B;;OAEG;YACW,gBAAgB;IAc9B;;OAEG;YACW,kBAAkB;IAchC;;OAEG;YACW,eAAe;IAgB7B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAgB1B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAsB;IAE9D;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAOxB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAIvB;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAqC3B;AASD,OAAO,EAAE,cAAc,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -40,7 +40,7 @@ class CliApplication {
40
40
  // NOTE: no -v short flag here — -v is reserved program-wide for --version.
41
41
  .option('--verbose', 'Enable verbose logging', false)
42
42
  .option('-w, --watch', 'Watch for changes and auto-generate commits', false)
43
- .option('-p, --provider <provider>', 'Specify AI provider (openrouter|openai)', this.validateProvider)
43
+ .option('-p, --provider <provider>', 'Specify AI provider (any configured provider)', this.validateProvider)
44
44
  // Extended formatting options
45
45
  .option('--emoji', 'Include emoji in commit message', false)
46
46
  .option('--one-line', 'Generate single-line commit message', false)
@@ -69,7 +69,7 @@ class CliApplication {
69
69
  .description('Manage configuration settings');
70
70
  configCmd
71
71
  .command('set <provider> <key>')
72
- .description('Set API key for provider (openrouter|openai)')
72
+ .description('Set API key for a provider (creates it if new)')
73
73
  .action(async (provider, key) => {
74
74
  await this.handleConfigSet(provider, key);
75
75
  });
@@ -85,6 +85,29 @@ class CliApplication {
85
85
  .action(async (provider, model) => {
86
86
  await this.handleConfigModel(provider, model);
87
87
  });
88
+ configCmd
89
+ .command('provider <name>')
90
+ .description('Configure a custom provider (baseUrl, key, model, auth header)')
91
+ .option('--base-url <url>', 'API base URL (e.g. https://router.cmdop.com/v1)')
92
+ .option('--key <key>', 'API key')
93
+ .option('--model <model>', 'Default model (e.g. @fast)')
94
+ .option('--auth-header <header>', "Auth header name (default 'Authorization'; use 'X-API-Key' for cmdop)")
95
+ .option('--auth-scheme <scheme>', "Scheme prefix for Authorization header (default 'Bearer'; pass empty to send raw)")
96
+ .action(async (name, opts) => {
97
+ await this.handleConfigProvider(name, opts);
98
+ });
99
+ configCmd
100
+ .command('remove-provider <name>')
101
+ .description('Remove a configured provider')
102
+ .action(async (name) => {
103
+ await this.handleConfigRemoveProvider(name);
104
+ });
105
+ configCmd
106
+ .command('default <name>')
107
+ .description('Set the active (default) provider')
108
+ .action(async (name) => {
109
+ await this.handleConfigDefault(name);
110
+ });
88
111
  configCmd
89
112
  .command('prompt [text]')
90
113
  .description('Set or clear custom system prompt (omit text to clear)')
@@ -182,7 +205,7 @@ class CliApplication {
182
205
  async handleConfigSet(provider, key) {
183
206
  try {
184
207
  if (!this.isValidProvider(provider)) {
185
- logger.error(`Invalid provider: ${provider}. Must be 'openrouter' or 'openai'`);
208
+ logger.error(`Invalid provider name: ${provider}`);
186
209
  process.exit(1);
187
210
  }
188
211
  await configManager.setApiKey(provider, key);
@@ -201,32 +224,38 @@ class CliApplication {
201
224
  const config = await configManager.load();
202
225
  if (provider) {
203
226
  if (!this.isValidProvider(provider)) {
204
- logger.error(`Invalid provider: ${provider}. Must be 'openrouter' or 'openai'`);
227
+ logger.error(`Invalid provider name: ${provider}`);
205
228
  process.exit(1);
206
229
  }
207
- const providerKey = provider;
208
- const maskedKey = await configManager.getMaskedApiKey(providerKey);
209
- const model = config.providers[providerKey].model || 'default';
230
+ const maskedKey = await configManager.getMaskedApiKey(provider);
231
+ const providerConfig = config.providers[provider];
232
+ const model = providerConfig?.model || 'default';
210
233
  logger.table({
211
234
  Provider: provider,
212
235
  'API Key': maskedKey,
213
236
  Model: model,
237
+ 'Base URL': providerConfig?.baseUrl || 'default',
238
+ 'Auth Header': providerConfig?.authHeader || 'Authorization',
214
239
  });
215
240
  }
216
241
  else {
217
- // Show all configuration
218
- const openrouterKey = await configManager.getMaskedApiKey('openrouter');
219
- const openaiKey = await configManager.getMaskedApiKey('openai');
220
- logger.table({
242
+ // Show all configuration — iterate over every configured provider.
243
+ const providers = await configManager.listProviders();
244
+ const table = {
221
245
  'Default Provider': config.preferences.defaultProvider,
222
- 'OpenRouter API Key': openrouterKey,
223
- 'OpenAI API Key': openaiKey,
224
- 'Max Tokens': config.preferences.maxTokens,
225
- 'Temperature': config.preferences.temperature,
226
- 'Auto Confirm': config.preferences.autoConfirm,
227
- 'Language': config.preferences.language,
228
- 'Format': config.preferences.commitFormat,
229
- });
246
+ };
247
+ for (const name of providers) {
248
+ const maskedKey = await configManager.getMaskedApiKey(name);
249
+ const model = config.providers[name]?.model || 'default';
250
+ table[`${name} API Key`] = maskedKey;
251
+ table[`${name} Model`] = model;
252
+ }
253
+ table['Max Tokens'] = config.preferences.maxTokens;
254
+ table['Temperature'] = config.preferences.temperature;
255
+ table['Auto Confirm'] = config.preferences.autoConfirm;
256
+ table['Language'] = config.preferences.language;
257
+ table['Format'] = config.preferences.commitFormat;
258
+ logger.table(table);
230
259
  }
231
260
  }
232
261
  catch (error) {
@@ -240,7 +269,7 @@ class CliApplication {
240
269
  async handleConfigModel(provider, model) {
241
270
  try {
242
271
  if (!this.isValidProvider(provider)) {
243
- logger.error(`Invalid provider: ${provider}. Must be 'openrouter' or 'openai'`);
272
+ logger.error(`Invalid provider name: ${provider}`);
244
273
  process.exit(1);
245
274
  }
246
275
  await configManager.setModel(provider, model);
@@ -251,6 +280,63 @@ class CliApplication {
251
280
  process.exit(1);
252
281
  }
253
282
  }
283
+ /**
284
+ * Handle full custom-provider configuration in one call
285
+ */
286
+ async handleConfigProvider(name, opts) {
287
+ try {
288
+ if (!this.isValidProvider(name)) {
289
+ logger.error(`Invalid provider name: ${name}`);
290
+ process.exit(1);
291
+ }
292
+ await configManager.setProvider(name, {
293
+ baseUrl: opts.baseUrl,
294
+ apiKey: opts.key,
295
+ model: opts.model,
296
+ authHeader: opts.authHeader,
297
+ authScheme: opts.authScheme,
298
+ });
299
+ logger.success(`Provider '${name}' configured`);
300
+ }
301
+ catch (error) {
302
+ logger.error('Failed to configure provider', error);
303
+ process.exit(1);
304
+ }
305
+ }
306
+ /**
307
+ * Handle removing a configured provider
308
+ */
309
+ async handleConfigRemoveProvider(name) {
310
+ try {
311
+ if (!this.isValidProvider(name)) {
312
+ logger.error(`Invalid provider name: ${name}`);
313
+ process.exit(1);
314
+ }
315
+ await configManager.removeProvider(name);
316
+ logger.success(`Provider '${name}' removed`);
317
+ }
318
+ catch (error) {
319
+ logger.error('Failed to remove provider', error);
320
+ process.exit(1);
321
+ }
322
+ }
323
+ /**
324
+ * Handle setting the active (default) provider
325
+ */
326
+ async handleConfigDefault(name) {
327
+ try {
328
+ if (!this.isValidProvider(name)) {
329
+ logger.error(`Invalid provider name: ${name}`);
330
+ process.exit(1);
331
+ }
332
+ await configManager.setDefaultProvider(name);
333
+ logger.success(`Active provider set to '${name}'`);
334
+ }
335
+ catch (error) {
336
+ logger.error('Failed to set default provider', error);
337
+ process.exit(1);
338
+ }
339
+ }
254
340
  /**
255
341
  * Handle custom prompt configuration
256
342
  */
@@ -281,7 +367,7 @@ class CliApplication {
281
367
  const config = await configManager.load();
282
368
  const testProvider = provider || config.preferences.defaultProvider;
283
369
  if (!this.isValidProvider(testProvider)) {
284
- logger.error(`Invalid provider: ${testProvider}. Must be 'openrouter' or 'openai'`);
370
+ logger.error(`Invalid provider name: ${testProvider}`);
285
371
  process.exit(1);
286
372
  }
287
373
  const progress = logger.startProgress(`Testing ${testProvider} connection...`);
@@ -406,20 +492,26 @@ class CliApplication {
406
492
  }
407
493
  return value;
408
494
  }
495
+ // Syntactically valid provider name: non-empty, alphanumerics, dashes,
496
+ // underscores. Provider is now an open dictionary, so this is a soft-format
497
+ // check, not a whitelist of two literals.
498
+ static PROVIDER_NAME_RE = /^[a-zA-Z0-9_-]+$/;
409
499
  /**
410
- * Validate provider
500
+ * Validate provider name (commander option parser). Accepts any
501
+ * syntactically valid provider identifier.
411
502
  */
412
503
  validateProvider(value) {
413
- if (value !== 'openrouter' && value !== 'openai') {
414
- throw new Error(`Invalid provider: ${value}. Must be 'openrouter' or 'openai'`);
504
+ if (!value || !CliApplication.PROVIDER_NAME_RE.test(value)) {
505
+ throw new Error(`Invalid provider name: ${value}`);
415
506
  }
416
507
  return value;
417
508
  }
418
509
  /**
419
- * Check if provider is valid
510
+ * Check whether a provider name is syntactically valid (not whether it is
511
+ * one of the built-ins — any registered provider is allowed).
420
512
  */
421
513
  isValidProvider(provider) {
422
- return provider === 'openrouter' || provider === 'openai';
514
+ return Boolean(provider) && CliApplication.PROVIDER_NAME_RE.test(provider);
423
515
  }
424
516
  /**
425
517
  * Run the CLI application