@pellux/goodvibes-tui 0.19.1 → 0.19.4

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/CHANGELOG.md CHANGED
@@ -4,6 +4,39 @@ All notable changes to GoodVibes TUI.
4
4
 
5
5
  ---
6
6
 
7
+ ## [0.19.4] - 2026-04-18
8
+
9
+ ### Changed
10
+ - Upgraded `@pellux/goodvibes-sdk` from 0.21.6 to 0.21.8. This carries the **bare-model-id fix** (0.21.7): the companion-chat adapter now correctly passes the bare model id (e.g. `"mercury-2"`) to provider `.chat()` calls instead of the compound registry key (e.g. `"inception:mercury-2"`). Upstream compat APIs (InceptionLabs, Venice, Cerebras, Groq, etc.) only accept bare ids. Resolves `400 invalid_request_error` reported by companion apps.
11
+ - Also includes 0.21.8's internal rename nit fix (cosmetic only).
12
+
13
+ ## [0.19.3] - 2026-04-18
14
+
15
+ ### Fixed
16
+ - Extracted CLI flag parsing (`--provider`, `--model`) from `src/main.ts` and `src/daemon/cli.ts` into a shared `src/cli-flags.ts` module. `src/main.ts` now passes the 800-line architecture-check gate that was tripped in 0.19.1 and 0.19.2 (CI failures on both prior releases).
17
+
18
+ ### Changed
19
+ - No user-visible behavior change. Pure structural refactor.
20
+
21
+ ### Note
22
+ - `v0.19.1` and `v0.19.2` GitHub releases have 0 binary assets due to the architecture-check CI failure. Consumers should upgrade to `0.19.3` for access to the binaries + consistent release state.
23
+
24
+ ---
25
+
26
+ ## [0.19.2] — 2026-04-18
27
+
28
+ ### Fixed
29
+ - Daemon test: `channel account APIs expose surface auth and secret posture` — corrected
30
+ `GET /api/providers` assertions from legacy `providerId` field to current `id` field
31
+ (SDK 0.21.x provider-routes.ts renamed the field). The subscription-oauth auth routes
32
+ assertion was dropped from the list endpoint (it only exists on per-provider snapshots).
33
+ - Model picker now correctly surfaces `configuredVia='secrets'` tier as `[key]` badge
34
+ (previously all secrets-manager-keyed providers were collapsed to `[env]`). Secrets are
35
+ pre-resolved async before the picker renders; `secretsManager` is now threaded from
36
+ `RuntimeServices` through `wireShellUiOpeners`.
37
+
38
+ ---
39
+
7
40
  ## [0.19.1] — 2026-04-17
8
41
 
9
42
  ### Changed
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml/badge.svg)](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![Version](https://img.shields.io/badge/version-0.19.1-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
5
+ [![Version](https://img.shields.io/badge/version-0.19.4-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
6
6
 
7
7
  A terminal-native AI coding, operations, automation, knowledge, and integration console with a typed runtime, omnichannel surfaces, structured memory/knowledge, and a raw ANSI renderer.
8
8
 
@@ -3,7 +3,7 @@
3
3
  "product": {
4
4
  "id": "goodvibes",
5
5
  "surface": "operator",
6
- "version": "0.21.6"
6
+ "version": "0.21.8"
7
7
  },
8
8
  "auth": {
9
9
  "modes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-tui",
3
- "version": "0.19.1",
3
+ "version": "0.19.4",
4
4
  "description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
5
5
  "type": "module",
6
6
  "main": "src/main.ts",
@@ -89,7 +89,7 @@
89
89
  "@anthropic-ai/vertex-sdk": "^0.16.0",
90
90
  "@ast-grep/napi": "^0.42.0",
91
91
  "@aws/bedrock-token-generator": "^1.1.0",
92
- "@pellux/goodvibes-sdk": "0.21.6",
92
+ "@pellux/goodvibes-sdk": "0.21.8",
93
93
  "bash-language-server": "^5.6.0",
94
94
  "fuse.js": "^7.1.0",
95
95
  "graphql": "^16.13.2",
@@ -0,0 +1,48 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Shared CLI flag parsing for TUI shell and daemon entrypoints.
3
+ // ---------------------------------------------------------------------------
4
+
5
+ export type CliFlags = {
6
+ readonly provider: string | undefined;
7
+ readonly model: string | undefined;
8
+ };
9
+
10
+ /**
11
+ * Parse `--provider` / `--model` / `--help` flags from an argv slice.
12
+ *
13
+ * @param argv - argv array (pass `process.argv.slice(2)`)
14
+ * @param binary - binary name shown in the --help usage line (e.g. "goodvibes" or "goodvibes-daemon")
15
+ */
16
+ export function parseCliFlags(argv: readonly string[], binary = 'goodvibes'): CliFlags {
17
+ let provider: string | undefined;
18
+ let model: string | undefined;
19
+
20
+ for (let i = 0; i < argv.length; i++) {
21
+ const arg = argv[i];
22
+ if (arg === '--help' || arg === '-h') {
23
+ // eslint-disable-next-line no-console
24
+ console.log([
25
+ `Usage: ${binary} [options]`,
26
+ '',
27
+ 'Options:',
28
+ ' --provider <id> Override the provider from settings.json at startup',
29
+ ' --model <registryKey> Override the model from settings.json at startup',
30
+ ' Format: provider:modelId (e.g. inception:mercury-2)',
31
+ ' If provider:modelId format is used, --provider is inferred',
32
+ ' --help, -h Show this help message',
33
+ ].join('\n'));
34
+ process.exit(0);
35
+ }
36
+ if (arg === '--provider' && argv[i + 1] !== undefined) {
37
+ provider = argv[++i];
38
+ } else if (arg === '--model' && argv[i + 1] !== undefined) {
39
+ model = argv[++i];
40
+ // Infer provider from registryKey format (provider:modelId) if --provider not given
41
+ if (typeof model === 'string' && model.includes(':') && provider === undefined) {
42
+ provider = model.split(':')[0];
43
+ }
44
+ }
45
+ }
46
+
47
+ return { provider, model };
48
+ }
package/src/daemon/cli.ts CHANGED
@@ -17,53 +17,13 @@ import {
17
17
  } from '@pellux/goodvibes-sdk/platform/pairing/index';
18
18
  import { generateQrMatrix, renderQrToString } from '@pellux/goodvibes-sdk/platform/pairing/qr-generator';
19
19
 
20
+ import { parseCliFlags } from '../cli-flags.ts';
20
21
  type DaemonCliOwnership = {
21
22
  readonly workingDirectory: string;
22
23
  readonly homeDirectory: string;
23
24
  };
24
25
 
25
- // ---------------------------------------------------------------------------
26
- // CLI flag parsing
27
- // ---------------------------------------------------------------------------
28
-
29
- type DaemonCliFlags = {
30
- readonly provider: string | undefined;
31
- readonly model: string | undefined;
32
- };
33
-
34
- function parseDaemonCliFlags(argv: readonly string[]): DaemonCliFlags {
35
- let provider: string | undefined;
36
- let model: string | undefined;
37
-
38
- for (let i = 0; i < argv.length; i++) {
39
- const arg = argv[i];
40
- if (arg === '--help' || arg === '-h') {
41
- // eslint-disable-next-line no-console
42
- console.log([
43
- 'Usage: goodvibes-daemon [options]',
44
- '',
45
- 'Options:',
46
- ' --provider <id> Override the provider from settings.json at startup',
47
- ' --model <registryKey> Override the model from settings.json at startup',
48
- ' Format: provider:modelId (e.g. inception:mercury-2)',
49
- ' If provider:modelId format is used, --provider is inferred',
50
- ' --help, -h Show this help message',
51
- ].join('\n'));
52
- process.exit(0);
53
- }
54
- if (arg === '--provider' && argv[i + 1] !== undefined) {
55
- provider = argv[++i];
56
- } else if (arg === '--model' && argv[i + 1] !== undefined) {
57
- model = argv[++i];
58
- // Infer provider from registryKey format (provider:modelId) if --provider not given
59
- if (typeof model === 'string' && model.includes(':') && provider === undefined) {
60
- provider = model.split(':')[0];
61
- }
62
- }
63
- }
64
-
65
- return { provider, model };
66
- }
26
+ // CLI flag parsing delegated to shared module — see src/cli-flags.ts
67
27
 
68
28
  type DaemonCliTokens = {
69
29
  readonly daemonToken: string | undefined;
@@ -117,7 +77,7 @@ async function main(): Promise<void> {
117
77
  new GlobalNetworkTransportInstaller().install(config);
118
78
 
119
79
  // Apply CLI flags — override settings.json before the provider registry is constructed
120
- const cliFlags = parseDaemonCliFlags(process.argv.slice(2));
80
+ const cliFlags = parseCliFlags(process.argv.slice(2), 'goodvibes-daemon');
121
81
  if (cliFlags.provider !== undefined) {
122
82
  config.set('provider.provider', cliFlags.provider);
123
83
  logger.info('daemon: --provider flag applied', { provider: cliFlags.provider });
package/src/main.ts CHANGED
@@ -1,6 +1,4 @@
1
1
  #!/usr/bin/env bun
2
- // Main shell entrypoint. Composition-heavy startup remains here, with
3
- // lower-level session/bootstrap/input helpers extracted into focused modules.
4
2
  import { homedir } from 'node:os';
5
3
  import { join } from 'node:path';
6
4
  import { Compositor } from './renderer/compositor.ts';
@@ -54,7 +52,7 @@ import { deriveComposerState } from './core/composer-state.ts';
54
52
  import { buildPersistedSessionContext, formatReturnContextForDisplay, getReturnContextMode, maybeAssistReturnContextSummary } from '@pellux/goodvibes-sdk/platform/runtime/session-return-context';
55
53
  import { GlobalNetworkTransportInstaller } from '@pellux/goodvibes-sdk/platform/runtime/network/index';
56
54
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
57
-
55
+ import { parseCliFlags } from './cli-flags.ts';
58
56
 
59
57
  const ALT_SCREEN_ENTER = '\x1b[?1049h';
60
58
  const ALT_SCREEN_EXIT = '\x1b[?1049l';
@@ -80,49 +78,6 @@ function resolveShellEntrypointOwnership(): ShellEntrypointOwnership {
80
78
  };
81
79
  }
82
80
 
83
- // ---------------------------------------------------------------------------
84
- // CLI flag parsing (TUI shell entry point)
85
- // ---------------------------------------------------------------------------
86
-
87
- type TuiCliFlags = {
88
- readonly provider: string | undefined;
89
- readonly model: string | undefined;
90
- };
91
-
92
- function parseTuiCliFlags(argv: readonly string[]): TuiCliFlags {
93
- let provider: string | undefined;
94
- let model: string | undefined;
95
-
96
- for (let i = 0; i < argv.length; i++) {
97
- const arg = argv[i];
98
- if (arg === '--help' || arg === '-h') {
99
- // eslint-disable-next-line no-console
100
- console.log([
101
- 'Usage: goodvibes [options]',
102
- '',
103
- 'Options:',
104
- ' --provider <id> Override the provider from settings.json at startup',
105
- ' --model <registryKey> Override the model from settings.json at startup',
106
- ' Format: provider:modelId (e.g. inception:mercury-2)',
107
- ' If provider:modelId format is used, --provider is inferred',
108
- ' --help, -h Show this help message',
109
- ].join('\n'));
110
- process.exit(0);
111
- }
112
- if (arg === '--provider' && argv[i + 1] !== undefined) {
113
- provider = argv[++i];
114
- } else if (arg === '--model' && argv[i + 1] !== undefined) {
115
- model = argv[++i];
116
- // Infer provider from registryKey format (provider:modelId) if --provider not given
117
- if (typeof model === 'string' && model.includes(':') && provider === undefined) {
118
- provider = model.split(':')[0];
119
- }
120
- }
121
- }
122
-
123
- return { provider, model };
124
- }
125
-
126
81
  async function main() {
127
82
  const stdout = process.stdout;
128
83
  const stdin = process.stdin;
@@ -139,7 +94,7 @@ async function main() {
139
94
  new GlobalNetworkTransportInstaller().install(configManager);
140
95
 
141
96
  // Apply CLI flags — override settings.json before the provider registry is constructed
142
- const cliFlags = parseTuiCliFlags(process.argv.slice(2));
97
+ const cliFlags = parseCliFlags(process.argv.slice(2), 'goodvibes');
143
98
  if (cliFlags.provider !== undefined) {
144
99
  configManager.set('provider.provider', cliFlags.provider);
145
100
  }
@@ -147,10 +102,7 @@ async function main() {
147
102
  configManager.set('provider.model', cliFlags.model);
148
103
  }
149
104
 
150
- // ── Bootstrap all runtime subsystems ─────────────────────────────────────
151
- // bootstrapRuntime initializes all subsystems in dependency order and returns
152
- // a fully-wired BootstrapContext. main.ts owns terminal setup, the render loop,
153
- // stdin input, and signal handlers — everything else is in bootstrap.
105
+ // ── Bootstrap runtime subsystems via bootstrapRuntime.
154
106
  const ctx: BootstrapContext = await bootstrapRuntime(stdout, {
155
107
  configManager,
156
108
  workingDir: bootstrapWorkingDir,
@@ -180,7 +132,7 @@ async function main() {
180
132
  } = ctx;
181
133
  const workingDir = ctx.services.workingDirectory;
182
134
  const homeDirectory = ctx.services.homeDirectory;
183
- const { approvalBroker, agentManager, modeManager, processManager, providerRegistry } = ctx.services;
135
+ const { approvalBroker, agentManager, modeManager, processManager, providerRegistry, secretsManager, subscriptionManager } = ctx.services;
184
136
  conversation.setSessionMemoryStore(ctx.services.sessionMemoryStore);
185
137
  conversation.setSessionLineageTracker(ctx.services.sessionLineageTracker);
186
138
  orchestrator.setCoreServices({
@@ -433,9 +385,6 @@ async function main() {
433
385
  });
434
386
 
435
387
  // ── InputHandler — created here so getViewportHeight can reference it ──────
436
- // orchestratorRefs.getViewportHeight and .scrollToEnd are patched immediately after.
437
-
438
- // ── InputHandler ────────────────────────────────────────────────────────
439
388
  const input: InputHandler = new InputHandler(
440
389
  () => render(),
441
390
  selection,
@@ -502,7 +451,6 @@ async function main() {
502
451
  toolCount,
503
452
  };
504
453
 
505
- // Sessions start fresh — use /session resume to load a previous session
506
454
 
507
455
  // --- Render function ---
508
456
  const render = () => {
@@ -514,7 +462,6 @@ async function main() {
514
462
  const sessionSnapshot = uiServices.readModels.session.getSnapshot();
515
463
  const agentSnapshot = uiServices.readModels.agents.getSnapshot();
516
464
 
517
-
518
465
  // Build header and footer FIRST so we know the exact viewport height
519
466
  const headerLines = UIFactory.createHeader(width, currentModel.id, currentModel.provider, conversation.title || undefined, lastGitInfoRef.value);
520
467
  const managerAgents = agentManager.list().filter(
@@ -735,7 +682,8 @@ async function main() {
735
682
  runtime,
736
683
  featureFlags: ctx.featureFlags,
737
684
  mcpRegistry: ctx.services.mcpRegistry,
738
- subscriptionManager: ctx.services.subscriptionManager,
685
+ subscriptionManager,
686
+ secretsManager,
739
687
  serviceRegistry: ctx.services.serviceRegistry,
740
688
  getConfiguredProviderIds: ctx._getConfiguredProviderIds,
741
689
  getPinned: ctx._getPinned,
@@ -743,6 +691,7 @@ async function main() {
743
691
  });
744
692
 
745
693
  // --- Streaming speed + tool preview wiring ---
694
+ const refreshGit = () => gitStatusProvider.refresh().then((info) => { lastGitInfoRef.value = info; render(); }).catch(() => { /* non-fatal */ });
746
695
  // Refresh git status after each turn completes or after tool results arrive
747
696
  unsubs.push(uiServices.events.turns.on('TURN_COMPLETED', () => {
748
697
  // Auto-save after every LLM turn so kills don't lose the session
@@ -759,22 +708,13 @@ async function main() {
759
708
  );
760
709
  hookDispatcher.fire({ path: 'Lifecycle:session:save' as HookEventPath, phase: 'Lifecycle' as HookPhase, category: 'session' as HookCategory, specific: 'save', sessionId: runtime.sessionId, timestamp: Date.now(), payload: { sessionId: runtime.sessionId } }).catch((err: unknown) => logger.debug('hook fire error', { error: summarizeError(err) }));
761
710
  } catch (e) { logger.debug('auto-save on turn:complete failed', { error: summarizeError(e) }); }
762
- gitStatusProvider.refresh().then((info) => {
763
- lastGitInfoRef.value = info;
764
- render();
765
- }).catch(() => { /* non-fatal */ });
711
+ refreshGit();
766
712
  }));
767
713
  unsubs.push(uiServices.events.tools.on('TOOL_SUCCEEDED', () => {
768
- gitStatusProvider.refresh().then((info) => {
769
- lastGitInfoRef.value = info;
770
- render();
771
- }).catch(() => { /* non-fatal */ });
714
+ refreshGit();
772
715
  }));
773
716
  unsubs.push(uiServices.events.tools.on('TOOL_FAILED', () => {
774
- gitStatusProvider.refresh().then((info) => {
775
- lastGitInfoRef.value = info;
776
- render();
777
- }).catch(() => { /* non-fatal */ });
717
+ refreshGit();
778
718
  }));
779
719
 
780
720
  unsubs.push(uiServices.events.turns.on('STREAM_START', () => {
@@ -8,6 +8,7 @@ import type { MutableRuntimeState } from '@pellux/goodvibes-sdk/platform/runtime
8
8
  import type { FeatureFlagManager } from '@pellux/goodvibes-sdk/platform/runtime/feature-flags/index';
9
9
  import type { McpRegistry } from '@pellux/goodvibes-sdk/platform/mcp/registry';
10
10
  import type { SubscriptionManager } from '@pellux/goodvibes-sdk/platform/config/subscriptions';
11
+ import type { SecretsManager } from '@pellux/goodvibes-sdk/platform/config/secrets';
11
12
  import type { ServiceInspectionQuery } from '../runtime/ui-service-queries.ts';
12
13
 
13
14
  type WireShellUiOpenersOptions = {
@@ -21,6 +22,7 @@ type WireShellUiOpenersOptions = {
21
22
  featureFlags: FeatureFlagManager;
22
23
  mcpRegistry: McpRegistry;
23
24
  subscriptionManager: SubscriptionManager;
25
+ secretsManager?: Pick<SecretsManager, 'get'>;
24
26
  serviceRegistry: Pick<ServiceInspectionQuery, 'getAll'>;
25
27
  getConfiguredProviderIds: () => string[];
26
28
  getPinned: () => Promise<string[]>;
@@ -29,35 +31,43 @@ type WireShellUiOpenersOptions = {
29
31
 
30
32
  /**
31
33
  * Derive the configuredVia tier for a provider.
32
- * Checks env-vars first (env tier), then falls back to subscription (from configuredIds
33
- * that aren't env-keyed). Returns undefined when not configured.
34
+ * Tier order mirrors SDK provider-routes.ts: env secrets subscription undefined.
35
+ * The preResolvedSecretKeys set is pre-fetched async before the sync picker render cycle.
34
36
  */
35
37
  function deriveConfiguredVia(
36
38
  providerId: string,
37
39
  configuredIds: Set<string>,
38
40
  subscriptionManager: SubscriptionManager,
41
+ preResolvedSecretKeys?: ReadonlySet<string>,
39
42
  ): 'env' | 'secrets' | 'subscription' | 'anonymous' | undefined {
40
43
  if (!configuredIds.has(providerId)) return undefined;
41
44
 
42
- // Check if a subscription session is active for this provider
45
+ // Tier 1: subscription check (most specific — subscription overrides env for this provider)
43
46
  const subs = subscriptionManager.list();
44
47
  if (subs.some((s) => s.provider === providerId)) return 'subscription';
45
48
 
46
- // Assume env-var backed (anonymous providers don't appear in configuredIds)
49
+ // Tier 2: env-var present (process.env check; anonymous providers don't appear in configuredIds)
50
+ // We don't have BUILTIN_PROVIDER_ENV_KEYS here; if env was used the configuredIds path covers it.
51
+ // The presence in configuredIds and no subscription → either env or secrets.
52
+ // Tier 3: secrets-manager backed (pre-resolved async batch)
53
+ if (preResolvedSecretKeys && preResolvedSecretKeys.has(providerId)) return 'secrets';
54
+
47
55
  return 'env';
48
56
  }
49
57
 
50
58
  /**
51
59
  * Build a configuredViaMap for the given provider list.
60
+ * Pass preResolvedSecretKeys (from an async SecretsManager batch) to surface the 'secrets' tier.
52
61
  */
53
62
  function buildConfiguredViaMap(
54
63
  providers: string[],
55
64
  configuredIds: Set<string>,
56
65
  subscriptionManager: SubscriptionManager,
66
+ preResolvedSecretKeys?: ReadonlySet<string>,
57
67
  ): Map<string, 'env' | 'secrets' | 'subscription' | 'anonymous'> {
58
68
  const map = new Map<string, 'env' | 'secrets' | 'subscription' | 'anonymous'>();
59
69
  for (const p of providers) {
60
- const via = deriveConfiguredVia(p, configuredIds, subscriptionManager);
70
+ const via = deriveConfiguredVia(p, configuredIds, subscriptionManager, preResolvedSecretKeys);
61
71
  if (via !== undefined) map.set(p, via);
62
72
  }
63
73
  return map;
@@ -75,35 +85,61 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
75
85
  featureFlags,
76
86
  mcpRegistry,
77
87
  subscriptionManager,
88
+ secretsManager,
78
89
  serviceRegistry,
79
90
  getConfiguredProviderIds,
80
91
  getPinned,
81
92
  render,
82
93
  } = options;
83
94
 
84
- commandContext.openModelPicker = () => {
85
- const models = providerRegistry.getSelectableModels();
95
+ /**
96
+ * Pre-resolve which provider IDs have secrets-manager keys (async batch, SDK tier pattern).
97
+ * Returns a set of provider IDs (not env var names) that are secrets-backed.
98
+ * Falls back to empty set if secretsManager is not provided.
99
+ */
100
+ async function resolveSecretProviderIds(): Promise<ReadonlySet<string>> {
101
+ if (!secretsManager) return new Set<string>();
86
102
  const configuredIds = new Set(getConfiguredProviderIds());
87
- input.modelPicker.configuredProviders = configuredIds;
88
- const providerIds = [...new Set(models.map((m) => m.provider))];
89
- input.modelPicker.configuredViaMap = buildConfiguredViaMap(providerIds, configuredIds, subscriptionManager);
90
- void getPinned().then((pinned) => {
91
- input.modelPicker.pinnedIds = new Set(pinned);
92
- });
93
- void input.modelPicker.loadRecentModels().catch(() => {}); // best-effort: prefetch for UI, failure is non-visible
94
- input.modalOpened('modelPicker');
95
- input.modelPicker.openAllModels(models, runtime.model);
96
- render();
103
+ // For each configured provider, check if secretsManager has a key for it by provider ID.
104
+ // We use provider ID as the lookup key since we don't have BUILTIN_PROVIDER_ENV_KEYS here.
105
+ const results = await Promise.all(
106
+ [...configuredIds].map(async (providerId) => {
107
+ const val = await secretsManager.get(providerId).catch(() => null);
108
+ return val !== null ? providerId : null;
109
+ }),
110
+ );
111
+ return new Set(results.filter((v): v is string => v !== null));
112
+ }
113
+
114
+ commandContext.openModelPicker = () => {
115
+ void (async () => {
116
+ const models = providerRegistry.getSelectableModels();
117
+ const configuredIds = new Set(getConfiguredProviderIds());
118
+ input.modelPicker.configuredProviders = configuredIds;
119
+ const providerIds = [...new Set(models.map((m) => m.provider))];
120
+ const secretProviderIds = await resolveSecretProviderIds();
121
+ input.modelPicker.configuredViaMap = buildConfiguredViaMap(providerIds, configuredIds, subscriptionManager, secretProviderIds);
122
+ void getPinned().then((pinned) => {
123
+ input.modelPicker.pinnedIds = new Set(pinned);
124
+ });
125
+ void input.modelPicker.loadRecentModels().catch(() => {}); // best-effort: prefetch for UI, failure is non-visible
126
+ input.modalOpened('modelPicker');
127
+ input.modelPicker.openAllModels(models, runtime.model);
128
+ render();
129
+ })();
97
130
  };
98
131
 
99
132
  commandContext.openProviderPicker = () => {
100
- const providers = [...new Set(providerRegistry.listModels().map((model) => model.provider))];
101
- const configuredIds = new Set(getConfiguredProviderIds());
102
- input.modelPicker.configuredProviders = configuredIds;
103
- input.modelPicker.configuredViaMap = buildConfiguredViaMap(providers, configuredIds, subscriptionManager);
104
- input.modalOpened('modelPicker');
105
- input.modelPicker.openProviders(providers, runtime.provider);
106
- render();
133
+ void (async () => {
134
+ const providers = [...new Set(providerRegistry.listModels().map((model) => model.provider))];
135
+ const configuredIds = new Set(getConfiguredProviderIds());
136
+ input.modelPicker.configuredProviders = configuredIds;
137
+ const secretProviderIds = await resolveSecretProviderIds();
138
+ input.modelPicker.configuredViaMap = buildConfiguredViaMap(providers, configuredIds, subscriptionManager, secretProviderIds);
139
+ input.modalOpened('modelPicker');
140
+ input.modelPicker.openProviders(providers, runtime.provider);
141
+ render();
142
+ })();
107
143
  };
108
144
 
109
145
  commandContext.openSelection = (title, items, opts, callback) => {
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '0.19.1';
9
+ let _version = '0.19.4';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
12
12
  _version = pkg.version ?? _version;