@botconnector/bccli 0.5.4 → 0.5.5

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/branding.mjs CHANGED
@@ -7,6 +7,7 @@ import { fileURLToPath } from 'node:url'
7
7
  import { homedir } from 'node:os'
8
8
  import { BCCLI_PROVIDER_COMMAND } from './provider-command.mjs'
9
9
  import { BCCLI_PROVIDER_DIALOG } from './provider-dialog.mjs'
10
+ import { BCCLI_MODEL_DIALOG } from './model-dialog.mjs'
10
11
 
11
12
  const VERSION = '0.26.3'
12
13
  const SOURCE_HASHES = Object.freeze({
@@ -32,7 +33,7 @@ const SOURCE_HASHES = Object.freeze({
32
33
  'chunks/chunk-B3ZFUGTC.js': 'f3f57aafbaeab56ca58f5d3173751d30e5fef815aeaf8dc9b46e6ea9e85eb528',
33
34
  'chunks/chunk-GGVWG4SI.js': '945c7f4e9b3697003c165b7bc5d007c9b87140d1fad7be084d7b253d8d2b476c',
34
35
  })
35
- const BRAND_REVISION = 'bccli-20261031-opentui-provider-fallback-v17'
36
+ const BRAND_REVISION = 'bccli-20261031-model-picker-search-v18'
36
37
  // Deliberately not Qwen's blue→purple→rose gradient. BotConnector-facing
37
38
  // coding CLI uses a distinct teal→green→amber palette in dark/light themes.
38
39
  export const BCCLI_COLORS = Object.freeze(['#00AFA0', '#22C55E', '#EAB308'])
@@ -67,9 +68,9 @@ function replaceOnce(text, oldValue, newValue, label) {
67
68
  // Embedded slash commands are part of the upstream renderer bundle. BCCLI's
68
69
  // Gateway owns authentication and release management, so those commands must
69
70
  // not open arbitrary upstream provider settings or invoke the upstream npm /
70
- // standalone auto-updater. /model intentionally stays upstream-owned: the
71
- // interactive picker is fed by the gateway catalog that BCCLI writes into the
72
- // session's modelProviders (see dialog-models.mjs).
71
+ // standalone auto-updater. /model keeps the upstream command, but its primary
72
+ // picker is BCCLI's searchable dialog (see model-dialog.mjs); the auxiliary
73
+ // pickers stay upstream-owned.
73
74
  export function guardUpstreamProviderCommands(text) {
74
75
  const replacements = [
75
76
  {
@@ -198,6 +199,23 @@ function renameProductLanguage(text) {
198
199
  return out
199
200
  }
200
201
 
202
+ // The upstream Select Model list cannot be filtered and the gateway catalog is
203
+ // long, so the primary picker becomes BCCLI's searchable one. The auxiliary
204
+ // pickers (fast / vision / voice / compaction / image / advisor) keep the
205
+ // upstream implementation, which owns their eligibility and persistence rules;
206
+ // the BCCLI component delegates to it for those modes.
207
+ export function patchModelDialog(text) {
208
+ const renamed = replaceExactly(text,
209
+ 'function ModelDialog(',
210
+ 'function UpstreamModelDialog(',
211
+ 1,
212
+ 'model picker upstream rename')
213
+ return replaceOnce(renamed,
214
+ 'function UpstreamModelDialog(',
215
+ BCCLI_MODEL_DIALOG + '\nfunction UpstreamModelDialog(',
216
+ 'model picker BCCLI search dialog')
217
+ }
218
+
201
219
  export function patchUiChunk(file, text) {
202
220
  if (file === 'chunks/chunk-Y6HHV4LC.js') {
203
221
  // Keep both context formats available and prioritize the vendor-neutral
@@ -298,6 +316,7 @@ export function patchUiChunk(file, text) {
298
316
  throw Error('BCCLI arrow-key provider dialog locator changed upstream.')
299
317
  }
300
318
  if (start >= 0) result = result.slice(0,start) + BCCLI_PROVIDER_DIALOG + result.slice(end)
319
+ result = patchModelDialog(result)
301
320
  return renameProductLanguage(result)
302
321
  }
303
322
  if (file === 'chunks/start-opentui-ui-Q34XCEMN.js') {
package/dialog-models.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  // The upstream /model picker lists models from settings.modelProviders. BCCLI's
2
2
  // engine can only reach the parent-owned loopback proxy, so publish the live
3
3
  // gateway catalog into the per-provider QWEN_HOME as one OpenAI-compatible
4
- // provider. The picker itself stays upstream-owned (arrow keys, search,
5
- // --global/--project and one-off prompts); BCCLI only supplies the eligible
6
- // model set and reads back the persisted selection.
4
+ // provider. BCCLI only supplies the eligible model set and reads back the
5
+ // persisted selection: the primary picker is BCCLI's own searchable dialog
6
+ // (see model-dialog.mjs), while the auxiliary pickers stay upstream-owned.
7
7
  import { lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
8
8
  import { randomBytes } from 'node:crypto'
9
9
  import { join } from 'node:path'
@@ -0,0 +1,130 @@
1
+ // BCCLI-owned model picker, injected into the locked upstream `ModelDialog`
2
+ // slot. The upstream dialog renders one flat numbered list with no way to type
3
+ // a name, while the gateway catalog is long; this replacement adds the same
4
+ // search row BCCLI's provider picker already ships.
5
+ //
6
+ // Only the primary model goes through this picker. The auxiliary dialogs
7
+ // (fast / vision / voice / compaction / image / advisor) keep the upstream
8
+ // implementation, because they own per-mode eligibility and persistence rules
9
+ // that this picker must not re-implement.
10
+ //
11
+ // Runtime identifiers are the aliases the surrounding engine chunk already
12
+ // uses for this region: import_react31 for hooks, import_jsx_runtime36 for jsx.
13
+ export const BCCLI_MODEL_DIALOG = String.raw`function BccliModelPicker(props){
14
+ const config=useConfig();
15
+ const [models,setModels]=(0,import_react31.useState)([]);
16
+ const [search,setSearch]=(0,import_react31.useState)("");
17
+ const [editing,setEditing]=(0,import_react31.useState)(false);
18
+ const [cursor,setCursor]=(0,import_react31.useState)(0);
19
+ const [message,setMessage]=(0,import_react31.useState)("");
20
+ const [busy,setBusy]=(0,import_react31.useState)(false);
21
+ const [active,setActive]=(0,import_react31.useState)(config.getModel?.()||"");
22
+ const base=process.env.OPENAI_BASE_URL||"";
23
+ const token=process.env.OPENAI_API_KEY||"";
24
+ const request=async(path,options={})=>{
25
+ if(!/^http:\/\/127\.0\.0\.1:[0-9]{1,5}\/v1$/.test(base)||!token) throw Error("Local provider session unavailable");
26
+ const result=await fetch(base+path,{method:options.method||"GET",
27
+ headers:{Authorization:"Bearer "+token,Accept:"application/json",...(options.body?{"Content-Type":"application/json"}:{})},
28
+ ...(options.body?{body:JSON.stringify(options.body)}:{}),
29
+ redirect:"error",signal:AbortSignal.timeout(12000)});
30
+ const json=await result.json().catch(()=>null);
31
+ if(!result.ok)throw Error(json?.error||"Provider request failed");
32
+ return json;
33
+ };
34
+ (0,import_react31.useEffect)(()=>{
35
+ let mounted=true;
36
+ request("/models").then(value=>{
37
+ if(!mounted)return;
38
+ const ids=[...new Set((Array.isArray(value?.data)?value.data:[]).map(x=>x?.id).filter(x=>typeof x==="string"))].sort();
39
+ setModels(ids);
40
+ setActive(config.getModel?.()||"");
41
+ }).catch(()=>{if(mounted)setMessage("Could not read the model catalog from the local BCCLI gateway.")});
42
+ return()=>{mounted=false};
43
+ },[]);
44
+ const needle=search.trim().toLowerCase();
45
+ const filtered=needle?models.filter(x=>x.toLowerCase().includes(needle)):models;
46
+ const count=filtered.length;
47
+ const at=count?Math.min(cursor,count-1):0;
48
+ const searchRow="🔎 Search model: "+(search||"(all)")+(editing?" █":" [Enter to search]");
49
+ // Switching keeps the loopback proxy: setModel refreshes the live content
50
+ // generator, then the gateway records the choice for the next launch. If the
51
+ // record fails, the previous model is restored so the dialog never lies.
52
+ const choose=async(id)=>{
53
+ if(busy)return;
54
+ setBusy(true);setMessage("");
55
+ const previous=config.getModel?.();
56
+ try{
57
+ await config.setModel(id,{reason:"bccli-gateway-model-picker"});
58
+ if(config.getModel?.()!==id)throw Error("Model switch did not take effect");
59
+ try{
60
+ const providers=await request("/bccli/providers");
61
+ await request("/bccli/provider/switch",{method:"POST",
62
+ body:{action:"user-selected-provider",provider:providers.active,model:id}});
63
+ }catch{
64
+ if(previous&&previous!==id)await config.setModel(previous,{reason:"bccli-gateway-model-picker-rollback"}).catch(()=>{});
65
+ throw Error("Model selection was not saved");
66
+ }
67
+ setActive(id);
68
+ props.onClose?.();
69
+ }catch(error){
70
+ setMessage(String(error?.message||"Model selection failed"));
71
+ }finally{setBusy(false)}
72
+ };
73
+ useKeypress(key=>{
74
+ if(busy)return;
75
+ if(key.name==="escape"){
76
+ if(editing){setEditing(false);return}
77
+ if(search){setSearch("");setCursor(0);return}
78
+ props.onClose?.();return;
79
+ }
80
+ if(key.ctrl&&key.name==="f"||key.sequence==="/"&&!key.ctrl&&!key.meta){
81
+ setEditing(true);return;
82
+ }
83
+ if(key.name==="up"||key.ctrl&&key.name==="p"){setEditing(false);setCursor(Math.max(0,at-1));return}
84
+ if(key.name==="down"||key.ctrl&&key.name==="n"){setEditing(false);setCursor(Math.min(Math.max(0,count-1),at+1));return}
85
+ if(key.name==="backspace"||key.name==="delete"){
86
+ if(editing){setSearch(s=>s.slice(0,-1));setCursor(0)}
87
+ return;
88
+ }
89
+ if(key.ctrl&&key.name==="u"){setSearch("");setCursor(0);setEditing(true);return}
90
+ if(key.name==="return"){
91
+ const picked=filtered[at];
92
+ if(picked)void choose(picked);
93
+ return;
94
+ }
95
+ if(!key.ctrl&&!key.meta){
96
+ const incoming=String(key.sequence||"");
97
+ if(incoming&&incoming.length<=128&&/^[a-zA-Z0-9._:/+ -]+$/.test(incoming)){
98
+ setSearch(s=>(s+incoming).slice(0,64));setEditing(true);setCursor(0);
99
+ }
100
+ }
101
+ },{isActive:true});
102
+ const start=Math.max(0,Math.min(at-4,Math.max(0,count-10)));
103
+ const visible=filtered.slice(start,start+10);
104
+ const title="BCCLI | Select model"+(props.persistScope==="workspace"?" (this project)":props.persistScope==="user"?" (global)":"");
105
+ return (0,import_jsx_runtime36.jsxs)(Box_default,{borderStyle:"round",borderColor:theme?.border?.default,
106
+ flexDirection:"column",padding:1,width:"100%",children:[
107
+ (0,import_jsx_runtime36.jsx)(Text,{bold:true,children:title}),
108
+ (0,import_jsx_runtime36.jsx)(Text,{color:theme.text.secondary,children:models.length?
109
+ "Search models ("+count+"/"+models.length+") · type to filter · Esc clears the search, then closes":
110
+ "Loading the provider model catalog..."}),
111
+ (0,import_jsx_runtime36.jsx)(Text,{color:editing?theme.text.link:theme.text.secondary,children:searchRow}),
112
+ ...(visible.length?visible.map((label,index)=>{
113
+ const absolute=start+index,focused=absolute===at;
114
+ return (0,import_jsx_runtime36.jsx)(Box_default,{
115
+ children:(0,import_jsx_runtime36.jsx)(Text,{bold:focused,color:focused?theme.text.link:theme.text.primary,
116
+ children:(focused?"❯ ":" ")+label+(label===active?" [ACTIVE]":"")})},String(absolute))
117
+ }):[(0,import_jsx_runtime36.jsx)(Text,{color:theme.text.secondary,
118
+ children:models.length?"No models match the search. Backspace to adjust.":(busy?"Switching model...":"Loading...")},"empty")]),
119
+ (0,import_jsx_runtime36.jsx)(Text,{color:theme.text.secondary,
120
+ children:(count?(Math.min(start+1,count)+"-"+(start+visible.length)+" of "+count+" | "):"")+
121
+ "↑↓ navigate · Enter select · / search · Ctrl+U clear · Backspace edit · Esc close"}),
122
+ message?(0,import_jsx_runtime36.jsx)(Text,{color:theme.status.error,children:message}):null,
123
+ (0,import_jsx_runtime36.jsx)(Text,{color:theme.text.secondary,children:"Active model: "+(active||"(unknown)")})
124
+ ]})
125
+ }
126
+ function ModelDialog(props){
127
+ const auxiliary=props.isFastModelMode||props.isAdvisorModelMode||props.isVoiceModelMode||props.isVisionModelMode||props.isCompactionModelMode||props.isImageModelMode;
128
+ if(auxiliary)return (0,import_jsx_runtime36.jsx)(UpstreamModelDialog,props);
129
+ return (0,import_jsx_runtime36.jsx)(BccliModelPicker,props);
130
+ }`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botconnector/bccli",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "BCCLI | Full coding engine and legacy SDK with verified Apache-2.0 bundled runtime",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,7 +24,7 @@
24
24
  "test": "node --test test/*.test.mjs",
25
25
  "test:security": "node --test test/security-e2e.test.mjs test/proxy-mcp-e2e.test.mjs test/trusted-mcp.test.mjs test/proxy.test.mjs",
26
26
  "test:integration": "node --test test-integration/*.test.mjs",
27
- "build": "node --check cli.mjs && node --check core.mjs && node --check proxy.mjs && node --check gateway.mjs && node --check branding.mjs && node --check credentials.mjs && node --check dialog-models.mjs && node --check providers.mjs && node --check provider-cli.mjs && node --check startup-menu.mjs",
27
+ "build": "node --check cli.mjs && node --check core.mjs && node --check proxy.mjs && node --check gateway.mjs && node --check branding.mjs && node --check model-dialog.mjs && node --check credentials.mjs && node --check dialog-models.mjs && node --check providers.mjs && node --check provider-cli.mjs && node --check startup-menu.mjs",
28
28
  "rebuild": "npm run build && npm test && npm pack --ignore-scripts"
29
29
  },
30
30
  "license": "Apache-2.0",
@@ -44,6 +44,7 @@
44
44
  "branding.mjs",
45
45
  "credentials.mjs",
46
46
  "dialog-models.mjs",
47
+ "model-dialog.mjs",
47
48
  "providers.mjs",
48
49
  "provider-cli.mjs",
49
50
  "startup-menu.mjs",
@@ -81,8 +82,8 @@
81
82
  },
82
83
  "bccliRuntimeSource": {
83
84
  "repository": "https://github.com/farijarifriyanto-debug/bccli-runtime",
84
- "commit": "999fe7d6179954f45e9492c55a8384cd93a51737",
85
- "release": "bccli-v0.5.4",
86
- "originalSha256": "52702af547846044b9aa36d0f7be33816d3fd26cdcb05f8bb3bdd30b3d290da3"
85
+ "commit": "5caed31a1a12a6d6257b815b3f5073f077b12716",
86
+ "release": "bccli-v0.5.5",
87
+ "originalSha256": "1fab7322f7e201aac20371c372828cfcea73acefdb2ad187d3b3143b885384a9"
87
88
  }
88
89
  }