lazyclaw 3.99.14 → 3.99.15

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/cli.mjs CHANGED
@@ -1731,12 +1731,19 @@ async function _pickProviderInteractive() {
1731
1731
  while (!family) {
1732
1732
  const familyItems = Object.entries(families)
1733
1733
  .filter(([, b]) => b.members.length > 0)
1734
- .map(([id, b]) => ({
1735
- id,
1736
- label: b.label,
1737
- desc: `${b.desc} · ${b.members.join(' / ')}`,
1738
- tag: b.tag,
1739
- }));
1734
+ .map(([id, b]) => {
1735
+ // Show member count + a few names instead of the full list — the
1736
+ // API-key family alone now has 12 vendors and joining all of them
1737
+ // produced an unreadable line.
1738
+ const preview = b.members.slice(0, 3).join(' / ');
1739
+ const more = b.members.length > 3 ? ` … (+${b.members.length - 3} more)` : '';
1740
+ return {
1741
+ id,
1742
+ label: b.label,
1743
+ desc: `${b.desc} · ${preview}${more}`,
1744
+ tag: b.tag,
1745
+ };
1746
+ });
1740
1747
  const picked = await _arrowMenu({
1741
1748
  title: 'LazyClaw setup — Step 1 of 3: pick how you want to auth',
1742
1749
  subtitle: 'API: bring your own key · CLI/Local: use what\'s already on this machine · Mock: offline test',
@@ -1752,14 +1759,21 @@ async function _pickProviderInteractive() {
1752
1759
  const memberNames = families[family.id].members;
1753
1760
  const provItems = memberNames.map((name) => {
1754
1761
  const meta = info[name] || {};
1755
- const models = (meta.suggestedModels || []).slice(0, 4).join(' · ') || '(default)';
1756
1762
  const isCustom = !!meta.custom;
1763
+ const isBuiltinCompat = !!meta.builtinOpenAICompat;
1764
+ // Step-2 desc used to preview four suggested model ids per provider.
1765
+ // That made the row read like "gemini · models: gemini-2.5-pro ·
1766
+ // gemini-2.5-flash · gemini-2.0-flash · gemini-2.0-flash-thinking-exp",
1767
+ // which is too dense and partly redundant — step 3 already shows the
1768
+ // full curated list. Keep the row to a vendor label + endpoint hint.
1769
+ let desc = '';
1770
+ if (isCustom) desc = `custom · ${meta.baseUrl || ''}`;
1771
+ else if (isBuiltinCompat) desc = meta.label || meta.baseUrl || '';
1772
+ else if (meta.label && meta.label !== name) desc = meta.label;
1757
1773
  return {
1758
1774
  id: name,
1759
1775
  label: name,
1760
- desc: isCustom
1761
- ? `custom · ${meta.baseUrl || ''}`
1762
- : `models: ${models}`,
1776
+ desc,
1763
1777
  tag: isCustom
1764
1778
  ? '\x1b[38;5;213m[custom]\x1b[0m'
1765
1779
  : (meta.requiresApiKey ? '\x1b[38;5;245m[api key]\x1b[0m' : '\x1b[38;5;208m[no key]\x1b[0m'),
@@ -1970,7 +1984,7 @@ async function _addCustomProviderInteractive() {
1970
1984
  process.stdout.write(dim(' · Groq https://api.groq.com/openai/v1') + '\n');
1971
1985
  process.stdout.write(dim(' · vLLM / LM Studio http://localhost:8000/v1') + '\n\n');
1972
1986
 
1973
- const { validateCustomProviderName, registerCustomProviders, fetchOpenAICompatModels } = _registryMod;
1987
+ const { validateCustomProviderName, registerCustomProviders, fetchOpenAICompatModels, isBuiltinOpenAICompatName } = _registryMod;
1974
1988
  let name;
1975
1989
  while (true) {
1976
1990
  const raw = (await _quickPrompt(` ${bold('name')} ${dim('(short id, e.g. "nim", "openrouter"):')} `)).trim();
@@ -1978,8 +1992,24 @@ async function _addCustomProviderInteractive() {
1978
1992
  process.stdout.write(dim(' cancelled — back to the picker.\n'));
1979
1993
  return null;
1980
1994
  }
1981
- try { name = validateCustomProviderName(raw); break; }
1982
- catch (e) { process.stdout.write(` \x1b[33m${e.message}\x1b[0m — try again.\n`); }
1995
+ try { name = validateCustomProviderName(raw); }
1996
+ catch (e) {
1997
+ process.stdout.write(` \x1b[33m${e.message}\x1b[0m — try again.\n`);
1998
+ continue;
1999
+ }
2000
+ // OpenAI-compat builtins (nim / openrouter / groq / …) can be overridden
2001
+ // by a custom entry of the same name — both go through
2002
+ // makeOpenAICompatProvider, so the wire format is identical and the
2003
+ // user is just pointing the same alias at a different URL/key. Surface
2004
+ // the override so it isn't a silent surprise.
2005
+ if (typeof isBuiltinOpenAICompatName === 'function' && isBuiltinOpenAICompatName(name)) {
2006
+ process.stdout.write(
2007
+ ` \x1b[2mNote: "${name}" is a built-in OpenAI-compatible provider; ` +
2008
+ `your custom entry will override the built-in baseUrl/api-key for this install. ` +
2009
+ `Remove with: lazyclaw providers remove ${name}\x1b[0m\n`
2010
+ );
2011
+ }
2012
+ break;
1983
2013
  }
1984
2014
  const baseUrlRaw = (await _quickPrompt(` ${bold('baseUrl')} ${dim('(must end in /v1, no trailing slash needed):')} `)).trim();
1985
2015
  if (!baseUrlRaw) { process.stdout.write(dim(' cancelled — baseUrl is required.\n')); return null; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lazyclaw",
3
- "version": "3.99.14",
3
+ "version": "3.99.15",
4
4
  "description": "Lazy, elegant terminal CLI for chatting with Claude / OpenAI / Gemini / Ollama and orchestrating multi-step LLM workflows. Banner-on-launch, slash-command ghost autocomplete, persistent sessions, local HTTP gateway.",
5
5
  "keywords": [
6
6
  "claude",
@@ -413,15 +413,27 @@ export function parseProviderModel(s) {
413
413
  * @param {string|undefined|null} key
414
414
  * @returns {string}
415
415
  */
416
- // Reserved provider names — built-in providers and meta-keywords the picker
417
- // uses internally. Custom registrations must not collide with these.
416
+ // Reserved provider names — names whose factory is bespoke (not the
417
+ // generic OpenAI-compat one) so a custom registration of the same name
418
+ // would silently break the wire format. The OpenAI-compat builtins are
419
+ // deliberately NOT listed: a user can register `nim` / `openrouter` /
420
+ // etc. as a custom entry to override the baseUrl / api-key / headers,
421
+ // because both the built-in and the custom go through
422
+ // `makeOpenAICompatProvider` — overriding is well-defined.
418
423
  const RESERVED_PROVIDER_NAMES = new Set([
419
424
  'mock', 'claude-cli', 'anthropic', 'openai', 'gemini', 'ollama',
420
- // OpenAI-compatible builtins (kept in lockstep with OPENAI_COMPAT_BUILTINS).
421
- ...Object.keys(OPENAI_COMPAT_BUILTINS),
422
425
  '__add_custom__', '__custom_model__', '__fetch_models__',
423
426
  ]);
424
427
 
428
+ /**
429
+ * Whether the supplied name belongs to one of the OpenAI-compatible
430
+ * builtins. Used by the custom-add interactive flow so it can warn the
431
+ * user that their custom entry will shadow the built-in registration.
432
+ */
433
+ export function isBuiltinOpenAICompatName(name) {
434
+ return Object.prototype.hasOwnProperty.call(OPENAI_COMPAT_BUILTINS, String(name || '').trim().toLowerCase());
435
+ }
436
+
425
437
  /**
426
438
  * Validate a custom provider name. Allowed: lowercase alnum + dash + dot.
427
439
  * Returns the trimmed name on success; throws on collision / bad format.