@pellux/goodvibes-tui 0.19.0 → 0.19.1
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 +41 -4
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/daemon/cli.ts +54 -0
- package/src/input/model-picker.ts +6 -2
- package/src/main.ts +52 -0
- package/src/renderer/model-picker-overlay.ts +9 -2
- package/src/shell/ui-openers.ts +43 -2
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,14 +4,51 @@ All notable changes to GoodVibes TUI.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [0.19.1] — 2026-04-17
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Upgraded `@pellux/goodvibes-sdk` from 0.21.1 to 0.21.6 — includes secrets-tier
|
|
11
|
+
`configuredVia` discriminator, HTTP `/api/providers` + `/api/providers/current` (GET/PATCH),
|
|
12
|
+
reactive `model.changed` SSE event, `BUILTIN_LABEL_MAP` brand-accurate provider labels,
|
|
13
|
+
and clean unconfigured-provider errors instead of silent 401 pass-through.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- `--provider <id>` and `--model <registryKey>` CLI flags for daemon startup
|
|
17
|
+
(`goodvibes-daemon`) and TUI shell startup (`goodvibes`). Both override
|
|
18
|
+
`~/.goodvibes/tui/settings.json` provider/model values for the session.
|
|
19
|
+
If `--model provider:modelId` format is used, `--provider` is inferred automatically.
|
|
20
|
+
- TUI model picker now populates `configuredVia` per provider and renders it as
|
|
21
|
+
a badge (`[env]`, `[sub]`, `[anon]`) in the provider-browse view, derived from
|
|
22
|
+
env-var presence and active OAuth subscription sessions.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- **Venice-picking diagnosis**: the silent 401 Venice fallback was caused by the
|
|
26
|
+
pre-0.21.2 SDK passing unconfigured-provider requests through to the upstream API
|
|
27
|
+
unchanged. As of 0.21.2 (included in 0.21.6), `createCompanionProviderAdapter` now
|
|
28
|
+
checks `provider.isConfigured()` before `provider.chat()` and yields a structured
|
|
29
|
+
error immediately. The in-registry fallback to first-selectable-model (which may
|
|
30
|
+
resolve to venice on some installs) only triggers when `getCurrentModel()` cannot
|
|
31
|
+
find the stored `currentModelId` in the model registry — typically an old daemon
|
|
32
|
+
instance with stale config. Restarting the daemon after a settings.json change
|
|
33
|
+
resolves this; the `--model` CLI flag eliminates the need to edit settings.json
|
|
34
|
+
manually. ConfigManager reactivity (live disk-change → registry update) is out of
|
|
35
|
+
scope: the PATCH `/api/providers/current` route is the correct live-switch path
|
|
36
|
+
(calls `setCurrentModel()` + `configManager.set()` atomically).
|
|
37
|
+
|
|
38
|
+
|
|
7
39
|
## [0.19.0] — 2026-04-18
|
|
8
40
|
|
|
9
41
|
### Changed
|
|
10
42
|
- Upgraded `@pellux/goodvibes-sdk` from 0.19.6 to 0.21.1 (soak-period release).
|
|
11
|
-
TUI
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
43
|
+
TUI adaptations required:
|
|
44
|
+
1. `docs/foundation-artifacts/operator-contract.json` — regenerated to match
|
|
45
|
+
updated `buildOperatorContract()` output (`peer-contract.json`, knowledge
|
|
46
|
+
artifacts unchanged).
|
|
47
|
+
2. `scripts/perf-check.ts` — `platform/runtime/perf/index` barrel removed;
|
|
48
|
+
`createPerfMonitor()` factory removed; migrated to `new PerfMonitor()` with
|
|
49
|
+
imports split to `perf/monitor` and `perf/reporter` sub-paths.
|
|
50
|
+
3. `scripts/eval-gate.ts` — `platform/runtime/eval/index` barrel removed;
|
|
51
|
+
imports split to `eval/baseline`, `eval/format`, and `eval/scorecard`.
|
|
15
52
|
|
|
16
53
|
### Added
|
|
17
54
|
- Wave B panel migration: migrated 5 panels (knowledge, marketplace, memory,
|
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.1",
|
|
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.
|
|
92
|
+
"@pellux/goodvibes-sdk": "0.21.6",
|
|
93
93
|
"bash-language-server": "^5.6.0",
|
|
94
94
|
"fuse.js": "^7.1.0",
|
|
95
95
|
"graphql": "^16.13.2",
|
package/src/daemon/cli.ts
CHANGED
|
@@ -22,6 +22,49 @@ type DaemonCliOwnership = {
|
|
|
22
22
|
readonly homeDirectory: string;
|
|
23
23
|
};
|
|
24
24
|
|
|
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
|
+
}
|
|
67
|
+
|
|
25
68
|
type DaemonCliTokens = {
|
|
26
69
|
readonly daemonToken: string | undefined;
|
|
27
70
|
readonly httpToken: string | undefined;
|
|
@@ -72,6 +115,17 @@ async function main(): Promise<void> {
|
|
|
72
115
|
const { workingDirectory: workingDir, homeDirectory } = resolveDaemonCliOwnership();
|
|
73
116
|
const config = new ConfigManager({ workingDir, homeDir: homeDirectory, surfaceRoot: 'tui' });
|
|
74
117
|
new GlobalNetworkTransportInstaller().install(config);
|
|
118
|
+
|
|
119
|
+
// Apply CLI flags — override settings.json before the provider registry is constructed
|
|
120
|
+
const cliFlags = parseDaemonCliFlags(process.argv.slice(2));
|
|
121
|
+
if (cliFlags.provider !== undefined) {
|
|
122
|
+
config.set('provider.provider', cliFlags.provider);
|
|
123
|
+
logger.info('daemon: --provider flag applied', { provider: cliFlags.provider });
|
|
124
|
+
}
|
|
125
|
+
if (cliFlags.model !== undefined) {
|
|
126
|
+
config.set('provider.model', cliFlags.model);
|
|
127
|
+
logger.info('daemon: --model flag applied', { model: cliFlags.model });
|
|
128
|
+
}
|
|
75
129
|
const runtimeBus = new RuntimeEventBus();
|
|
76
130
|
const runtimeStore = createRuntimeStore();
|
|
77
131
|
const runtimeServices = createRuntimeServices({
|
|
@@ -99,6 +99,8 @@ export interface PickerItem {
|
|
|
99
99
|
isFree?: boolean;
|
|
100
100
|
/** True when this provider item has a configured API key. */
|
|
101
101
|
isConfigured?: boolean;
|
|
102
|
+
/** How the provider is configured — shown as a badge in provider mode. */
|
|
103
|
+
configuredVia?: 'env' | 'secrets' | 'subscription' | 'anonymous';
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
/** Provider IDs treated as "Popular" in the provider picker. */
|
|
@@ -166,6 +168,8 @@ export class ModelPickerModal {
|
|
|
166
168
|
public availableOnly = true;
|
|
167
169
|
/** Set of provider names that have a configured key (used for availableOnly filter). */
|
|
168
170
|
public configuredProviders: Set<string> = new Set();
|
|
171
|
+
/** How each provider is configured — drives badge display in provider mode. */
|
|
172
|
+
public configuredViaMap: Map<string, 'env' | 'secrets' | 'subscription' | 'anonymous'> = new Map();
|
|
169
173
|
/** IDs of pinned/favorite models — shown at top of list. */
|
|
170
174
|
public pinnedIds: Set<string> = new Set();
|
|
171
175
|
/** IDs of recently used models — shown after pinned, before the rest. */
|
|
@@ -612,13 +616,13 @@ export class ModelPickerModal {
|
|
|
612
616
|
if (filteredPopular.length > 0) {
|
|
613
617
|
providerItems.push({ id: '__header__popular', label: 'Popular', isGroupHeader: true });
|
|
614
618
|
for (const p of filteredPopular) {
|
|
615
|
-
providerItems.push({ id: p, label: p, isConfigured: this.configuredProviders.has(p) });
|
|
619
|
+
providerItems.push({ id: p, label: p, isConfigured: this.configuredProviders.has(p), configuredVia: this.configuredViaMap.get(p) });
|
|
616
620
|
}
|
|
617
621
|
}
|
|
618
622
|
if (filteredAll.length > 0) {
|
|
619
623
|
providerItems.push({ id: '__header__all', label: 'All Providers', isGroupHeader: true });
|
|
620
624
|
for (const p of filteredAll) {
|
|
621
|
-
providerItems.push({ id: p, label: p, isConfigured: this.configuredProviders.has(p) });
|
|
625
|
+
providerItems.push({ id: p, label: p, isConfigured: this.configuredProviders.has(p), configuredVia: this.configuredViaMap.get(p) });
|
|
622
626
|
}
|
|
623
627
|
}
|
|
624
628
|
|
package/src/main.ts
CHANGED
|
@@ -80,6 +80,49 @@ function resolveShellEntrypointOwnership(): ShellEntrypointOwnership {
|
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
|
|
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
|
+
|
|
83
126
|
async function main() {
|
|
84
127
|
const stdout = process.stdout;
|
|
85
128
|
const stdin = process.stdin;
|
|
@@ -95,6 +138,15 @@ async function main() {
|
|
|
95
138
|
});
|
|
96
139
|
new GlobalNetworkTransportInstaller().install(configManager);
|
|
97
140
|
|
|
141
|
+
// Apply CLI flags — override settings.json before the provider registry is constructed
|
|
142
|
+
const cliFlags = parseTuiCliFlags(process.argv.slice(2));
|
|
143
|
+
if (cliFlags.provider !== undefined) {
|
|
144
|
+
configManager.set('provider.provider', cliFlags.provider);
|
|
145
|
+
}
|
|
146
|
+
if (cliFlags.model !== undefined) {
|
|
147
|
+
configManager.set('provider.model', cliFlags.model);
|
|
148
|
+
}
|
|
149
|
+
|
|
98
150
|
// ── Bootstrap all runtime subsystems ─────────────────────────────────────
|
|
99
151
|
// bootstrapRuntime initializes all subsystems in dependency order and returns
|
|
100
152
|
// a fully-wired BootstrapContext. main.ts owns terminal setup, the render loop,
|
|
@@ -294,12 +294,19 @@ export function renderModelPickerOverlay(
|
|
|
294
294
|
const isSelected = selectableIdx === picker.selectedIndex;
|
|
295
295
|
const indicator = isSelected ? `${OVERLAY_GLYPHS.selected} ` : ' ';
|
|
296
296
|
const checkmark = item.isConfigured ? '✓ ' : ' ';
|
|
297
|
-
|
|
297
|
+
// configuredVia badge: right-aligned short label (env/sub/anon)
|
|
298
|
+
const viaBadge = item.configuredVia === 'env' ? ' [env]'
|
|
299
|
+
: item.configuredVia === 'secrets' ? ' [key]'
|
|
300
|
+
: item.configuredVia === 'subscription' ? ' [sub]'
|
|
301
|
+
: item.configuredVia === 'anonymous' ? ' [anon]'
|
|
302
|
+
: '';
|
|
303
|
+
const badgeW = viaBadge.length;
|
|
304
|
+
const labelW = contentW - 2 - 2 - badgeW; // indicator(2) + checkmark(2) + badge
|
|
298
305
|
const labelStr = item.label.length > labelW
|
|
299
306
|
? item.label.slice(0, labelW - 3) + '...'
|
|
300
307
|
: item.label.padEnd(labelW);
|
|
301
308
|
const row = createOverlayContentLine(width, layout, borderFg, isSelected ? selectedBg : DEFAULT_OVERLAY_PALETTE.bodyBg);
|
|
302
|
-
const rowText = indicator + checkmark + labelStr;
|
|
309
|
+
const rowText = indicator + checkmark + labelStr + viaBadge;
|
|
303
310
|
putRowText(row, layout.margin + 2, contentW, fitDisplay(truncateDisplay(rowText, contentW), contentW), isSelected ? titleFg : bodyFg, isSelected ? selectedBg : DEFAULT_OVERLAY_PALETTE.bodyBg, isSelected);
|
|
304
311
|
lines.push(row);
|
|
305
312
|
}
|
package/src/shell/ui-openers.ts
CHANGED
|
@@ -27,6 +27,42 @@ type WireShellUiOpenersOptions = {
|
|
|
27
27
|
render: () => void;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* 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
|
+
*/
|
|
35
|
+
function deriveConfiguredVia(
|
|
36
|
+
providerId: string,
|
|
37
|
+
configuredIds: Set<string>,
|
|
38
|
+
subscriptionManager: SubscriptionManager,
|
|
39
|
+
): 'env' | 'secrets' | 'subscription' | 'anonymous' | undefined {
|
|
40
|
+
if (!configuredIds.has(providerId)) return undefined;
|
|
41
|
+
|
|
42
|
+
// Check if a subscription session is active for this provider
|
|
43
|
+
const subs = subscriptionManager.list();
|
|
44
|
+
if (subs.some((s) => s.provider === providerId)) return 'subscription';
|
|
45
|
+
|
|
46
|
+
// Assume env-var backed (anonymous providers don't appear in configuredIds)
|
|
47
|
+
return 'env';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build a configuredViaMap for the given provider list.
|
|
52
|
+
*/
|
|
53
|
+
function buildConfiguredViaMap(
|
|
54
|
+
providers: string[],
|
|
55
|
+
configuredIds: Set<string>,
|
|
56
|
+
subscriptionManager: SubscriptionManager,
|
|
57
|
+
): Map<string, 'env' | 'secrets' | 'subscription' | 'anonymous'> {
|
|
58
|
+
const map = new Map<string, 'env' | 'secrets' | 'subscription' | 'anonymous'>();
|
|
59
|
+
for (const p of providers) {
|
|
60
|
+
const via = deriveConfiguredVia(p, configuredIds, subscriptionManager);
|
|
61
|
+
if (via !== undefined) map.set(p, via);
|
|
62
|
+
}
|
|
63
|
+
return map;
|
|
64
|
+
}
|
|
65
|
+
|
|
30
66
|
export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
31
67
|
const {
|
|
32
68
|
commandContext,
|
|
@@ -47,7 +83,10 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
47
83
|
|
|
48
84
|
commandContext.openModelPicker = () => {
|
|
49
85
|
const models = providerRegistry.getSelectableModels();
|
|
50
|
-
|
|
86
|
+
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);
|
|
51
90
|
void getPinned().then((pinned) => {
|
|
52
91
|
input.modelPicker.pinnedIds = new Set(pinned);
|
|
53
92
|
});
|
|
@@ -59,7 +98,9 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
59
98
|
|
|
60
99
|
commandContext.openProviderPicker = () => {
|
|
61
100
|
const providers = [...new Set(providerRegistry.listModels().map((model) => model.provider))];
|
|
62
|
-
|
|
101
|
+
const configuredIds = new Set(getConfiguredProviderIds());
|
|
102
|
+
input.modelPicker.configuredProviders = configuredIds;
|
|
103
|
+
input.modelPicker.configuredViaMap = buildConfiguredViaMap(providers, configuredIds, subscriptionManager);
|
|
63
104
|
input.modalOpened('modelPicker');
|
|
64
105
|
input.modelPicker.openProviders(providers, runtime.provider);
|
|
65
106
|
render();
|
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.1';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
|
|
12
12
|
_version = pkg.version ?? _version;
|