@pellux/goodvibes-tui 2.0.1 → 2.0.2

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,17 @@ All notable changes to GoodVibes TUI.
4
4
 
5
5
  ---
6
6
 
7
+ ## [2.0.2] - 2026-08-01
8
+
9
+ ### Changes
10
+
11
+ - Fixed: importing settings that include `display.themeMode` no longer prints
12
+ an "unknown key" warning. The key is now declared in the platform
13
+ configuration schema (SDK 2.0.1) — `auto` probes the terminal background
14
+ once at startup, `dark` and `light` force a fixed appearance — and the
15
+ settings screen's row for it comes from that schema like every other
16
+ `display.*` key, instead of a local stand-in descriptor.
17
+
7
18
  ## [2.0.1] - 2026-08-01
8
19
 
9
20
  ### Changes
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-2.0.1-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
5
+ [![Version](https://img.shields.io/badge/version-2.0.2-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
6
6
 
7
7
  GoodVibes is a terminal console for coding and operations work with an AI model. You run `goodvibes` in a project directory and get a full-screen terminal app where you talk to a model that can read and edit your files, run shell commands, search the web, and hand work off to background agents — asking your permission before anything that writes or executes. It talks to many model providers (OpenAI, Anthropic, Gemini, Bedrock, Copilot, OpenRouter and other OpenAI-compatible gateways, plus local servers like Ollama and LM Studio that it finds on startup), keeps its settings, sessions, and secrets on your own machine, and shows you the token count and running cost of every turn. Alongside the conversation, panels turn background work — running agents, git state, diffs, tokens, cost — into live control rooms.
8
8
 
@@ -3,7 +3,7 @@
3
3
  "product": {
4
4
  "id": "goodvibes",
5
5
  "surface": "operator",
6
- "version": "2.0.0"
6
+ "version": "2.0.1"
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": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Terminal-native GoodVibes client for coding, operations, knowledge and channel workflows — a pure client of the GoodVibes daemon.",
5
5
  "type": "module",
6
6
  "main": "src/main.ts",
@@ -95,11 +95,11 @@
95
95
  "@anthropic-ai/bedrock-sdk": "^0.29.1",
96
96
  "@anthropic-ai/sdk": "^0.92.0",
97
97
  "@ast-grep/napi": "^0.42.0",
98
- "@pellux/goodvibes-sdk": "2.0.0",
99
- "@pellux/goodvibes-terminal-shell": "2.0.0",
98
+ "@pellux/goodvibes-sdk": "2.0.1",
99
+ "@pellux/goodvibes-terminal-shell": "2.0.1",
100
100
  "bash-language-server": "^5.6.0",
101
101
  "fuse.js": "^7.1.0",
102
- "@pellux/goodvibes-daemon": "1.28.1",
102
+ "@pellux/goodvibes-daemon": "1.28.3",
103
103
  "graphql": "^16.13.2",
104
104
  "jszip": "^3.10.1",
105
105
  "node-edge-tts": "^1.2.10",
@@ -124,10 +124,10 @@
124
124
  "typescript": "^5.9.3"
125
125
  },
126
126
  "optionalDependencies": {
127
- "@pellux/goodvibes-tui-linux-x64": "2.0.1",
128
- "@pellux/goodvibes-tui-linux-arm64": "2.0.1",
129
- "@pellux/goodvibes-tui-darwin-x64": "2.0.1",
130
- "@pellux/goodvibes-tui-darwin-arm64": "2.0.1"
127
+ "@pellux/goodvibes-tui-linux-x64": "2.0.2",
128
+ "@pellux/goodvibes-tui-linux-arm64": "2.0.2",
129
+ "@pellux/goodvibes-tui-darwin-x64": "2.0.2",
130
+ "@pellux/goodvibes-tui-darwin-arm64": "2.0.2"
131
131
  },
132
132
  "trustedDependencies": [],
133
133
  "overrides": {
@@ -26,15 +26,15 @@
26
26
  * implicit card the way this app's `/payments card` flow models it, so these
27
27
  * four fields are synthetic sub-keys under the SDK's real `payments`
28
28
  * section — the same established pattern this codebase already uses for
29
- * `behavior.notifyAfterSeconds`, `display.themeMode`, etc.: a key one level
30
- * under an EXISTING section that CONFIG_SCHEMA has no scalar entry for.
29
+ * `behavior.notifyAfterSeconds`, `storage.codeIndexEnabled`, etc.: a key one
30
+ * level under an EXISTING section that CONFIG_SCHEMA has no scalar entry for.
31
31
  *
32
32
  * That one-level-under-an-existing-section shape matters mechanically: the
33
33
  * real ConfigManager's dotted-path resolver only throws "Invalid config path"
34
34
  * when an INTERMEDIATE segment is missing (e.g. `payments.card.number`, where
35
35
  * `card` is not itself a section) — it does not throw for a final leaf that
36
36
  * the schema has not declared (`payments.cardNumber`), the same tolerance
37
- * `display.themeMode` relies on under `display`. So these four keys are named
37
+ * `storage.codeIndexEnabled` relies on under `storage`. So these four keys are named
38
38
  * FLAT (`payments.cardNumber`, not `payments.card.number`) specifically so
39
39
  * `ConfigManager.get/setDynamic` accepts them — verified against the real
40
40
  * ConfigManager (not the fake store this module used before this round): a
@@ -79,7 +79,7 @@ export const PAYMENTS_CVV_HANDLING_CONFIG_KEY = 'payments.cvvHandling' as Config
79
79
  function readStringField(configManager: PaymentsConfigReader, key: ConfigKey): string {
80
80
  // Defensive try/catch, same rationale as worktree-setup-config.ts's
81
81
  // readWorktreeSetupList and this codebase's other one-level-under-a-real-
82
- // section synthetic reads (behavior.notifyAfterSeconds, display.themeMode): the real
82
+ // section synthetic reads (behavior.notifyAfterSeconds, storage.codeIndexEnabled): the real
83
83
  // ConfigManager never throws for these particular keys (verified — see
84
84
  // header comment), but degrading to empty rather than crashing the whole
85
85
  // settings modal is the same posture every synthetic setting here takes
@@ -36,13 +36,6 @@ import {
36
36
  isExecEnvScrubAllowlistConfigKey,
37
37
  readExecEnvScrubAllowlist,
38
38
  } from './exec-env-scrub-config.ts';
39
- import {
40
- THEME_MODE_CONFIG_KEY,
41
- THEME_MODE_VALUES,
42
- THEME_MODE_DEFAULT,
43
- THEME_MODE_DESCRIPTION,
44
- coerceThemeModeSetting,
45
- } from '../renderer/theme-mode-config.ts';
46
39
  import {
47
40
  SETTINGS_CATEGORIES,
48
41
  type FlagEntry,
@@ -145,12 +138,6 @@ export function buildSettingGroups(
145
138
  if (rawCat === 'cluster' && groups.has('network')) groups.get('network')!.push(entry);
146
139
  }
147
140
 
148
- // Synthetic display.themeMode enum (auto|dark|light) — TUI-local, see theme-mode-config.ts.
149
- const displayEntries = groups.get('display');
150
- if (displayEntries && !displayEntries.some((e) => e.setting.key === (THEME_MODE_CONFIG_KEY as ConfigKey))) {
151
- displayEntries.push(buildThemeModeSyntheticEntry(configManager));
152
- }
153
-
154
141
  const uiEntries = groups.get('ui');
155
142
  if (uiEntries) {
156
143
  const uiPriority: Record<string, number> = {
@@ -283,34 +270,6 @@ export function buildSettingGroups(
283
270
  return groups;
284
271
  }
285
272
 
286
- // ---------------------------------------------------------------------------
287
- // display.themeMode synthetic setting (light theme)
288
- // ---------------------------------------------------------------------------
289
-
290
- /**
291
- * The synthetic ConfigSetting descriptor for display.themeMode. TUI-local (not
292
- * in the SDK ConfigKey union); the key is cast to ConfigKey as the other
293
- * synthetic settings do. Stored under the existing `display` section so
294
- * ConfigManager.setDynamic/get round-trip it with no SDK change.
295
- */
296
- export const THEME_MODE_SYNTHETIC_SETTING: ConfigSetting = {
297
- key: THEME_MODE_CONFIG_KEY as ConfigKey,
298
- type: 'enum',
299
- default: THEME_MODE_DEFAULT,
300
- enumValues: [...THEME_MODE_VALUES],
301
- description: THEME_MODE_DESCRIPTION,
302
- };
303
-
304
- /** Build the synthetic SettingEntry for display.themeMode. */
305
- export function buildThemeModeSyntheticEntry(configManager: Pick<ConfigManager, 'get'>): SettingEntry {
306
- const currentValue = coerceThemeModeSetting(configManager.get(THEME_MODE_CONFIG_KEY as ConfigKey));
307
- return {
308
- setting: THEME_MODE_SYNTHETIC_SETTING,
309
- currentValue,
310
- isDefault: currentValue === THEME_MODE_DEFAULT,
311
- };
312
- }
313
-
314
273
  // ---------------------------------------------------------------------------
315
274
  // behavior.notifyAfterSeconds synthetic setting
316
275
  // ---------------------------------------------------------------------------
@@ -623,11 +582,9 @@ export function refreshEntryValues(
623
582
  }
624
583
  const raw = configManager.get(entry.setting.key as ConfigKey);
625
584
  // Synthetic entries that have no SDK schema key return undefined from
626
- // configManager. Normalize using the same logic used at construction
627
- // time so isDefault stays accurate.
628
- entry.currentValue = entry.setting.key === (THEME_MODE_CONFIG_KEY as ConfigKey)
629
- ? coerceThemeModeSetting(raw)
630
- : raw;
585
+ // configManager; the schema-driven entries above already normalize on
586
+ // their own read paths, so the raw value is used as-is here.
587
+ entry.currentValue = raw;
631
588
  entry.isDefault = deepEqual(entry.currentValue, entry.setting.default);
632
589
  }
633
590
  }
@@ -670,10 +627,7 @@ export function updateEntryForKey(
670
627
  continue;
671
628
  }
672
629
  const raw = configManager.get(key);
673
- // Synthetic entries: normalize using the same fallback logic as construction.
674
- entry.currentValue = key === (THEME_MODE_CONFIG_KEY as ConfigKey)
675
- ? coerceThemeModeSetting(raw)
676
- : raw;
630
+ entry.currentValue = raw;
677
631
  entry.isDefault = deepEqual(entry.currentValue, entry.setting.default);
678
632
  }
679
633
  }
@@ -9,7 +9,7 @@
9
9
  * settings modal even though they fully control what runs when a fresh
10
10
  * isolated worktree is provisioned (setup commands, then untracked-file
11
11
  * carry-over). Same rationale as the other synthetic settings in
12
- * settings-modal-data.ts (behavior.notifyAfterSeconds, display.themeMode, etc).
12
+ * settings-modal-data.ts (behavior.notifyAfterSeconds, storage.codeIndexEnabled, etc).
13
13
  *
14
14
  * Both keys hold a JSON array of strings on disk. The settings modal's
15
15
  * inline editor is a single-line text field, so — matching the SDK's own
@@ -2,15 +2,17 @@
2
2
  * theme-mode-config — the appearance/theme-mode preference, TUI-side.
3
3
  *
4
4
  * The preference lives at the config key `display.themeMode` (auto | dark |
5
- * light, default auto). It is stored under the existing SDK `display` section
6
- * rather than a new `appearance` section on purpose: the SDK ConfigManager's
7
- * resolvePath() throws for any dot-path whose top-level section is absent from
8
- * DEFAULT_CONFIG, and adding an `appearance` section would require an SDK schema
9
- * change (out of scope for this TUI-only work order). Storing under `display`
10
- * reuses the same proven "TUI-local synthetic setting" pattern the codebase
11
- * already uses for behavior.notifyAfterSeconds / storage.codeIndexEnabled:
12
- * ConfigManager.setDynamic writes it to settings.json and get() reads it back,
13
- * with zero SDK changes.
5
+ * light, default auto), stored under the existing SDK `display` section
6
+ * alongside `display.theme` (the color palette — a separate, independent
7
+ * concept: theme picks the palette, themeMode picks light/dark appearance).
8
+ *
9
+ * `display.themeMode` is a real CONFIG_SCHEMA entry (SDK 2.0.0+); this module
10
+ * no longer carries a TUI-local synthetic descriptor for it. The settings
11
+ * modal renders the schema's own row like every other key, and this module
12
+ * keeps the read-side helpers (coerceThemeModeSetting,
13
+ * resolveConfiguredThemeMode) that the renderer's runtime paths (theme.ts,
14
+ * terminal-bg-probe.ts, ui-openers.ts) use to turn the resolved value into a
15
+ * ThemeMode ('dark' | 'light') for actual rendering.
14
16
  *
15
17
  * This module is deliberately free of any terminal/probe state so both the
16
18
  * settings-modal data layer (input) and the probe (renderer) can import it
@@ -21,27 +23,15 @@ import type { ThemeModeSetting } from './theme.ts';
21
23
  import type { ConfigKey, ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
22
24
 
23
25
  /** Config key backing the appearance/theme-mode preference (see module doc). */
24
- export const THEME_MODE_CONFIG_KEY = 'display.themeMode';
25
-
26
- /** The three valid preference values, in settings-cycle order. */
27
- export const THEME_MODE_VALUES: readonly ThemeModeSetting[] = ['auto', 'dark', 'light'];
28
-
29
- /** Default when unset: probe the terminal background on startup. */
30
- export const THEME_MODE_DEFAULT: ThemeModeSetting = 'auto';
26
+ export const THEME_MODE_CONFIG_KEY: ConfigKey = 'display.themeMode';
31
27
 
32
28
  /**
33
- * Human-facing description for the settings-modal entry. States the honest
34
- * timing contract: forced modes apply on next full paint immediately; `auto`
35
- * only re-probes at startup, so switching TO auto takes effect next launch.
29
+ * Default when unset: probe the terminal background on startup. Matches the
30
+ * CONFIG_SCHEMA default for display.themeMode (module-private: nothing
31
+ * outside this file needs the default value directly — callers read the
32
+ * resolved mode via coerceThemeModeSetting/resolveConfiguredThemeMode).
36
33
  */
37
- export const THEME_MODE_DESCRIPTION =
38
- 'Terminal background theme. auto probes the terminal background colour once at '
39
- + 'startup (OSC 11) and picks light or dark; dark/light force a fixed theme. '
40
- + 'Forced modes take effect immediately; auto is only evaluated at startup, so '
41
- + 'selecting auto takes effect on the next launch. Unreadable/unsupported '
42
- + 'terminals fall back to dark. Scope (honest): transcript markdown, modal '
43
- + 'accents, and the header/footer/thinking chrome all flip with this setting; '
44
- + 'only the background colour itself follows your terminal.';
34
+ const THEME_MODE_DEFAULT: ThemeModeSetting = 'auto';
45
35
 
46
36
  /** Narrow an unknown config value to a valid ThemeModeSetting, else the default. */
47
37
  export function coerceThemeModeSetting(raw: unknown): ThemeModeSetting {
@@ -49,17 +39,15 @@ export function coerceThemeModeSetting(raw: unknown): ThemeModeSetting {
49
39
  }
50
40
 
51
41
  /**
52
- * Read the configured theme-mode preference. Reads the TUI-local key via a cast
53
- * (it is not in the SDK ConfigKey union). Safe: the `display` section exists in
54
- * DEFAULT_CONFIG, so get() returns undefined for the absent field rather than
55
- * throwing, and we coerce undefined → 'auto'.
42
+ * Read the configured theme-mode preference. Safe: the `display` section
43
+ * exists in DEFAULT_CONFIG, so get() returns undefined for an unset field
44
+ * rather than throwing, and we coerce undefined → 'auto'.
56
45
  */
57
46
  export function resolveConfiguredThemeMode(
58
47
  configManager: Pick<ConfigManager, 'get'>,
59
48
  ): ThemeModeSetting {
60
49
  try {
61
- // Cast key: display.themeMode is TUI-local, not in the SDK ConfigKey union.
62
- return coerceThemeModeSetting(configManager.get(THEME_MODE_CONFIG_KEY as ConfigKey));
50
+ return coerceThemeModeSetting(configManager.get(THEME_MODE_CONFIG_KEY));
63
51
  } catch {
64
52
  // Defensive: any unexpected resolvePath/section error → honest default.
65
53
  return THEME_MODE_DEFAULT;
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 = '2.0.1';
9
+ let _version = '2.0.2';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
12
12
  // Only trust a version read from OUR OWN package.json. A Bun single-file