@pellux/goodvibes-tui 0.19.1 → 0.19.3
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 +27 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/cli-flags.ts +48 -0
- package/src/daemon/cli.ts +3 -43
- package/src/main.ts +10 -70
- package/src/shell/ui-openers.ts +60 -24
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,33 @@ All notable changes to GoodVibes TUI.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [0.19.3] - 2026-04-18
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- 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).
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- No user-visible behavior change. Pure structural refactor.
|
|
14
|
+
|
|
15
|
+
### Note
|
|
16
|
+
- `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.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [0.19.2] — 2026-04-18
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Daemon test: `channel account APIs expose surface auth and secret posture` — corrected
|
|
24
|
+
`GET /api/providers` assertions from legacy `providerId` field to current `id` field
|
|
25
|
+
(SDK 0.21.x provider-routes.ts renamed the field). The subscription-oauth auth routes
|
|
26
|
+
assertion was dropped from the list endpoint (it only exists on per-provider snapshots).
|
|
27
|
+
- Model picker now correctly surfaces `configuredVia='secrets'` tier as `[key]` badge
|
|
28
|
+
(previously all secrets-manager-keyed providers were collapsed to `[env]`). Secrets are
|
|
29
|
+
pre-resolved async before the picker renders; `secretsManager` is now threaded from
|
|
30
|
+
`RuntimeServices` through `wireShellUiOpeners`.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
7
34
|
## [0.19.1] — 2026-04-17
|
|
8
35
|
|
|
9
36
|
### Changed
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](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
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-tui",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.3",
|
|
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",
|
package/src/cli-flags.ts
ADDED
|
@@ -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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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', () => {
|
package/src/shell/ui-openers.ts
CHANGED
|
@@ -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
|
-
*
|
|
33
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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.
|
|
9
|
+
let _version = '0.19.3';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
|
|
12
12
|
_version = pkg.version ?? _version;
|