agentvibes 5.0.0 → 5.1.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/.claude/audio/tracks/Drifting Down the Hall.mp3 +0 -0
- package/.claude/audio/tracks/Late Night Hip Hop Groove.mp3 +0 -0
- package/.claude/audio/tracks/Midnight Charleston Stomp.mp3 +0 -0
- package/.claude/config/audio-effects.cfg +1 -1
- package/.claude/hooks/play-tts.sh +10 -3
- package/.claude/hooks-windows/play-tts.ps1 +8 -1
- package/README.md +22 -2
- package/RELEASE_NOTES.md +76 -0
- package/package.json +1 -1
- package/src/console/tabs/agents-tab.js +65 -62
- package/src/console/tabs/music-tab.js +49 -19
- package/src/console/tabs/settings-tab.js +39 -37
- package/src/console/tabs/setup-tab.js +346 -86
- package/src/console/tabs/voices-tab.js +152 -29
- package/src/console/widgets/format-utils.js +92 -89
- package/src/console/widgets/track-picker.js +325 -322
- package/src/installer.js +8 -8
- package/src/services/llm-provider-service.js +79 -0
|
@@ -23,13 +23,13 @@ import { SUPPORTED_LANGUAGES, t } from '../../i18n/strings.js';
|
|
|
23
23
|
import {
|
|
24
24
|
PROVIDERS,
|
|
25
25
|
checkClaudeInstalled, checkCopilotInstalled, checkCodexInstalled,
|
|
26
|
-
installClaudeMcp, removeClaudeMcp,
|
|
26
|
+
installClaudeMcp, removeClaudeMcp, uninstallClaude,
|
|
27
27
|
installCopilotMcp, removeCopilotMcp,
|
|
28
28
|
installCopilotInstructions, removeCopilotInstructions,
|
|
29
29
|
installCodexMcp, removeCodexMcp,
|
|
30
30
|
installCodexInstructions, installCodexHooks,
|
|
31
31
|
removeCodexInstructions, removeCodexHooks,
|
|
32
|
-
loadLlmConfigSync, saveLlmConfigSync,
|
|
32
|
+
loadLlmConfigSync, saveLlmConfigSync, resolveCfgPath,
|
|
33
33
|
} from '../../services/llm-provider-service.js';
|
|
34
34
|
import {
|
|
35
35
|
getAvailableEngines, getEngineStatuses, checkEngineInstalled,
|
|
@@ -38,7 +38,7 @@ import { openReverbPicker, REVERB_PRESETS } from '../widgets/reverb-picker.js';
|
|
|
38
38
|
import { openTrackPicker, openVolumeInput } from '../widgets/track-picker.js';
|
|
39
39
|
import { formatTrackName } from '../widgets/format-utils.js';
|
|
40
40
|
import { destroyList } from '../widgets/destroy-list.js';
|
|
41
|
-
import { scanInstalledVoices, getVoiceMeta, PIPER_VOICES_DIR, SAMPLE_PHRASES, parseMultiSpeaker } from './voices-tab.js';
|
|
41
|
+
import { scanInstalledVoices, getVoiceMeta, genderIconTag, PIPER_VOICES_DIR, SAMPLE_PHRASES, parseMultiSpeaker, getFavorites, toggleFavorite } from './voices-tab.js';
|
|
42
42
|
import { attachBtnBlink } from './agents-tab.js';
|
|
43
43
|
import { buildAudioEnv, detectWavPlayer } from '../audio-env.js';
|
|
44
44
|
import { spawn } from 'node:child_process';
|
|
@@ -201,7 +201,9 @@ export function createSetupTab(screen, services) {
|
|
|
201
201
|
// Wizard state
|
|
202
202
|
|
|
203
203
|
let _screen = 0;
|
|
204
|
-
let _lastScreen = -
|
|
204
|
+
let _lastScreen = -2;
|
|
205
|
+
let _pendingGlobalCfg = null; // Set when global config detected on first run
|
|
206
|
+
let _globalChoiceIdx = 0; // 0 = Load Global, 1 = Start Fresh
|
|
205
207
|
const _getLang = () => languageService?.getLang() ?? 'en';
|
|
206
208
|
const _tl = (key) => languageService?.t(key) ?? t('en', key);
|
|
207
209
|
let _langIdx = 0;
|
|
@@ -458,10 +460,10 @@ export function createSetupTab(screen, services) {
|
|
|
458
460
|
// Info box for provider details
|
|
459
461
|
const infoBox = blessed.box({
|
|
460
462
|
parent: box,
|
|
461
|
-
top:
|
|
462
|
-
left:
|
|
463
|
-
width: '
|
|
464
|
-
bottom:
|
|
463
|
+
top: 0,
|
|
464
|
+
left: 0,
|
|
465
|
+
width: '100%',
|
|
466
|
+
bottom: 0,
|
|
465
467
|
hidden: true,
|
|
466
468
|
scrollable: true,
|
|
467
469
|
alwaysScroll: true,
|
|
@@ -469,6 +471,7 @@ export function createSetupTab(screen, services) {
|
|
|
469
471
|
keys: true,
|
|
470
472
|
vi: true,
|
|
471
473
|
mouse: true,
|
|
474
|
+
valign: 'top',
|
|
472
475
|
scrollbar: { ch: '|', style: { fg: 'cyan' } },
|
|
473
476
|
style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
|
|
474
477
|
});
|
|
@@ -650,9 +653,9 @@ export function createSetupTab(screen, services) {
|
|
|
650
653
|
|
|
651
654
|
async function handleProviderRemove(provider) {
|
|
652
655
|
if (provider.id === 'claude-code') {
|
|
653
|
-
await
|
|
656
|
+
const result = await uninstallClaude(targetDir);
|
|
654
657
|
await refreshInstalledState();
|
|
655
|
-
showRemoveInfo('claude-code');
|
|
658
|
+
showRemoveInfo('claude-code', result.removed || []);
|
|
656
659
|
return;
|
|
657
660
|
}
|
|
658
661
|
|
|
@@ -694,10 +697,20 @@ export function createSetupTab(screen, services) {
|
|
|
694
697
|
let _closed = false;
|
|
695
698
|
navigationService?.openModal();
|
|
696
699
|
|
|
700
|
+
const defaultPretext = {
|
|
701
|
+
'claude-code': 'Claude Code here',
|
|
702
|
+
'copilot': 'Copilot here',
|
|
703
|
+
'codex': 'Codex here',
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
// Read global defaults for display
|
|
707
|
+
const globalEngine = providerService?.getActiveProvider?.() || 'piper';
|
|
708
|
+
const globalVoice = providerService?.getActiveVoiceId?.() || 'none';
|
|
709
|
+
|
|
697
710
|
const draft = {
|
|
698
711
|
ttsEngine: config.ttsEngine || '',
|
|
699
712
|
voice: config.voice || '',
|
|
700
|
-
pretext: config.pretext || '',
|
|
713
|
+
pretext: config.pretext || defaultPretext[llmKey] || '',
|
|
701
714
|
reverbPreset: config.effects || 'off',
|
|
702
715
|
bgTrack: config.bgTrack || '',
|
|
703
716
|
bgVolume: config.bgVolume || '0.15',
|
|
@@ -708,7 +721,7 @@ export function createSetupTab(screen, services) {
|
|
|
708
721
|
top: 'center',
|
|
709
722
|
left: 'center',
|
|
710
723
|
width: 72,
|
|
711
|
-
height:
|
|
724
|
+
height: 22,
|
|
712
725
|
border: { type: 'line' },
|
|
713
726
|
tags: true,
|
|
714
727
|
label: ` {bold}{cyan-fg} ${provider.name} -- Audio Config {/cyan-fg}{/bold} `,
|
|
@@ -722,8 +735,8 @@ export function createSetupTab(screen, services) {
|
|
|
722
735
|
|
|
723
736
|
// Field definitions
|
|
724
737
|
const FIELDS = [
|
|
725
|
-
{ key: 'ttsEngine', label: 'TTS Engine', getValue: () => draft.ttsEngine ||
|
|
726
|
-
{ key: 'voice', label: 'Voice', getValue: () => draft.voice ||
|
|
738
|
+
{ key: 'ttsEngine', label: 'TTS Engine', getValue: () => draft.ttsEngine || `(global: ${globalEngine})` },
|
|
739
|
+
{ key: 'voice', label: 'Voice', getValue: () => draft.voice || `(global: ${globalVoice})` },
|
|
727
740
|
{ key: 'pretext', label: 'Pretext', getValue: () => draft.pretext || '(none)' },
|
|
728
741
|
{ key: 'reverb', label: 'Reverb', getValue: () => {
|
|
729
742
|
const p = REVERB_PRESETS.find(r => r.value === draft.reverbPreset);
|
|
@@ -770,7 +783,7 @@ export function createSetupTab(screen, services) {
|
|
|
770
783
|
left: 2,
|
|
771
784
|
right: 2,
|
|
772
785
|
tags: true,
|
|
773
|
-
content: '{white-fg}[Up/Down] Navigate [Enter] Edit [Tab]
|
|
786
|
+
content: '{white-fg}[Up/Down] Navigate [Enter] Edit [Tab] Buttons [Esc] Close{/white-fg}',
|
|
774
787
|
style: { bg: COLORS.contentBg },
|
|
775
788
|
});
|
|
776
789
|
|
|
@@ -797,40 +810,139 @@ export function createSetupTab(screen, services) {
|
|
|
797
810
|
return btn;
|
|
798
811
|
}
|
|
799
812
|
|
|
800
|
-
|
|
813
|
+
// Preview status line
|
|
814
|
+
const previewLine = blessed.text({
|
|
815
|
+
parent: modal,
|
|
816
|
+
bottom: 1,
|
|
817
|
+
left: 2,
|
|
818
|
+
right: 2,
|
|
819
|
+
tags: true,
|
|
820
|
+
content: '',
|
|
821
|
+
style: { bg: COLORS.contentBg },
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
let _previewModalProc = null;
|
|
825
|
+
function _killPreview() {
|
|
826
|
+
if (_previewModalProc) {
|
|
827
|
+
try { _previewModalProc.kill(); } catch {}
|
|
828
|
+
_previewModalProc = null;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function _playPreview() {
|
|
833
|
+
_killPreview();
|
|
834
|
+
previewLine.setContent('{cyan-fg}♪ Previewing...{/cyan-fg}');
|
|
835
|
+
screen.render();
|
|
836
|
+
|
|
837
|
+
// Save first so play-tts picks up current settings
|
|
838
|
+
_autoSave(true);
|
|
839
|
+
|
|
840
|
+
// Temporarily enable background music for preview if a track is configured
|
|
841
|
+
const hasBgTrack = !!draft.bgTrack;
|
|
842
|
+
let _bgRestore = null;
|
|
843
|
+
if (hasBgTrack) {
|
|
844
|
+
const avCfgPath = path.join(targetDir, '.agentvibes', 'config.json');
|
|
845
|
+
try {
|
|
846
|
+
const raw = fs.readFileSync(avCfgPath, 'utf8');
|
|
847
|
+
const cfg = JSON.parse(raw);
|
|
848
|
+
if (cfg.backgroundMusic && !cfg.backgroundMusic.enabled) {
|
|
849
|
+
cfg.backgroundMusic.enabled = true;
|
|
850
|
+
fs.writeFileSync(avCfgPath, JSON.stringify(cfg, null, 2) + '\n');
|
|
851
|
+
_bgRestore = () => { cfg.backgroundMusic.enabled = false; fs.writeFileSync(avCfgPath, JSON.stringify(cfg, null, 2) + '\n'); };
|
|
852
|
+
}
|
|
853
|
+
} catch {
|
|
854
|
+
// No .agentvibes/config.json — use legacy txt fallback
|
|
855
|
+
const bgEnabledFile = path.join(targetDir, '.claude', 'config', 'background-music-enabled.txt');
|
|
856
|
+
let bgWas = false;
|
|
857
|
+
try { bgWas = fs.readFileSync(bgEnabledFile, 'utf8').trim() === 'true'; } catch {}
|
|
858
|
+
if (!bgWas) {
|
|
859
|
+
try { fs.writeFileSync(bgEnabledFile, 'true', 'utf8'); } catch {}
|
|
860
|
+
_bgRestore = () => { try { fs.writeFileSync(bgEnabledFile, 'false', 'utf8'); } catch {} };
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const hooksSubdir = process.platform === 'win32' ? 'hooks-windows' : 'hooks';
|
|
866
|
+
const isWin = process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
|
|
867
|
+
// Don't include pretext — play-tts already prepends it from the config
|
|
868
|
+
const sampleText = 'Here is a preview of your audio settings.';
|
|
869
|
+
|
|
870
|
+
let cmd, args;
|
|
871
|
+
if (isWin) {
|
|
872
|
+
const script = path.join(targetDir, '.claude', hooksSubdir, 'play-tts.ps1');
|
|
873
|
+
cmd = 'powershell';
|
|
874
|
+
args = ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', script, sampleText, '', '-llm', llmKey];
|
|
875
|
+
} else {
|
|
876
|
+
const script = path.join(targetDir, '.claude', hooksSubdir, 'play-tts.sh');
|
|
877
|
+
cmd = 'bash';
|
|
878
|
+
args = [script, sampleText, '', '--llm', llmKey];
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const proc = spawn(cmd, args, {
|
|
882
|
+
stdio: 'ignore',
|
|
883
|
+
windowsHide: true,
|
|
884
|
+
env: { ...process.env, CLAUDE_PROJECT_DIR: targetDir },
|
|
885
|
+
});
|
|
886
|
+
_previewModalProc = proc;
|
|
887
|
+
|
|
888
|
+
const _restoreBg = () => { if (_bgRestore) _bgRestore(); };
|
|
889
|
+
|
|
890
|
+
proc.on('exit', () => {
|
|
891
|
+
_previewModalProc = null;
|
|
892
|
+
_restoreBg();
|
|
893
|
+
if (!_closed) { previewLine.setContent(''); screen.render(); }
|
|
894
|
+
});
|
|
895
|
+
proc.on('error', () => {
|
|
896
|
+
_previewModalProc = null;
|
|
897
|
+
_restoreBg();
|
|
898
|
+
if (!_closed) { previewLine.setContent('{red-fg}Preview failed{/red-fg}'); screen.render(); }
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Auto-save: persist draft to config immediately on any change
|
|
903
|
+
function _autoSave(silent) {
|
|
904
|
+
// Infer engine from voice — voice picker only shows Piper voices,
|
|
905
|
+
// so if a voice is set but no engine chosen, default to piper
|
|
906
|
+
const engine = draft.ttsEngine || (draft.voice ? 'piper' : '');
|
|
801
907
|
saveLlmConfigSync(llmKey, {
|
|
802
908
|
voice: draft.voice,
|
|
803
909
|
pretext: draft.pretext,
|
|
804
910
|
effects: draft.reverbPreset === 'off' ? '' : draft.reverbPreset,
|
|
805
911
|
bgTrack: draft.bgTrack,
|
|
806
912
|
bgVolume: draft.bgVolume,
|
|
807
|
-
ttsEngine:
|
|
913
|
+
ttsEngine: engine,
|
|
808
914
|
sourcePath: config.sourcePath,
|
|
809
915
|
}, targetDir);
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
916
|
+
if (!silent) {
|
|
917
|
+
const cfgPath = config.sourcePath || resolveCfgPath(targetDir);
|
|
918
|
+
_showSavedToast('Settings', cfgPath);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
813
921
|
|
|
814
|
-
const
|
|
922
|
+
const previewBtn = _modalBtn('Preview', 4, _playPreview);
|
|
923
|
+
|
|
924
|
+
const resetBtn = _modalBtn('Reset', 18, () => {
|
|
815
925
|
draft.ttsEngine = '';
|
|
816
926
|
draft.voice = '';
|
|
817
|
-
draft.pretext = '';
|
|
927
|
+
draft.pretext = defaultPretext[llmKey] || '';
|
|
818
928
|
draft.reverbPreset = 'off';
|
|
819
929
|
draft.bgTrack = '';
|
|
820
930
|
draft.bgVolume = '0.15';
|
|
931
|
+
_autoSave();
|
|
821
932
|
fieldList.setItems(_fieldItems());
|
|
822
933
|
fieldList.focus();
|
|
823
934
|
screen.render();
|
|
824
935
|
});
|
|
825
936
|
|
|
826
|
-
const
|
|
937
|
+
const closeBtn = _modalBtn('Close', 30, _closeModal);
|
|
827
938
|
|
|
828
|
-
const allBtns = [
|
|
939
|
+
const allBtns = [previewBtn, resetBtn, closeBtn];
|
|
829
940
|
const btnBlink = attachBtnBlink(allBtns, screen);
|
|
830
941
|
|
|
831
942
|
function _closeModal() {
|
|
832
943
|
if (_closed) return;
|
|
833
944
|
_closed = true;
|
|
945
|
+
_killPreview();
|
|
834
946
|
btnBlink.cleanup();
|
|
835
947
|
navigationService?.closeModal();
|
|
836
948
|
destroyList(modal, screen);
|
|
@@ -845,6 +957,7 @@ export function createSetupTab(screen, services) {
|
|
|
845
957
|
if (!field) return;
|
|
846
958
|
|
|
847
959
|
const _refreshField = () => {
|
|
960
|
+
_autoSave();
|
|
848
961
|
fieldList.setItems(_fieldItems());
|
|
849
962
|
fieldList.select(idx);
|
|
850
963
|
fieldList.focus();
|
|
@@ -1021,7 +1134,6 @@ export function createSetupTab(screen, services) {
|
|
|
1021
1134
|
navigationService?.openModal();
|
|
1022
1135
|
|
|
1023
1136
|
let _allVoices = [];
|
|
1024
|
-
let _filterText = '';
|
|
1025
1137
|
let _previewProc = null;
|
|
1026
1138
|
let _previewVoiceId = null;
|
|
1027
1139
|
let _vpClosed = false;
|
|
@@ -1060,28 +1172,17 @@ export function createSetupTab(screen, services) {
|
|
|
1060
1172
|
});
|
|
1061
1173
|
vpModal.setFront();
|
|
1062
1174
|
|
|
1063
|
-
// Search
|
|
1064
|
-
blessed.text({
|
|
1065
|
-
parent: vpModal, top: 1, left: 2,
|
|
1066
|
-
content: 'Search:', style: { fg: COLORS.labelFg, bg: COLORS.contentBg },
|
|
1067
|
-
});
|
|
1068
|
-
const vpSearch = blessed.textbox({
|
|
1069
|
-
parent: vpModal, top: 1, left: 11, width: 40, height: 1,
|
|
1070
|
-
inputOnFocus: true, keys: true,
|
|
1071
|
-
style: { fg: COLORS.valueFg, bg: 'blue', focus: { bg: 'cyan' } },
|
|
1072
|
-
});
|
|
1073
|
-
|
|
1074
1175
|
// Column header
|
|
1075
|
-
const COL_N =
|
|
1076
|
-
const COL_G =
|
|
1176
|
+
const COL_N = 30;
|
|
1177
|
+
const COL_G = 4;
|
|
1077
1178
|
blessed.text({
|
|
1078
|
-
parent: vpModal, top:
|
|
1079
|
-
content: `{cyan-fg}${'Name'.padEnd(COL_N)}
|
|
1179
|
+
parent: vpModal, top: 1, left: 6, tags: true,
|
|
1180
|
+
content: `{cyan-fg}${'Name'.padEnd(COL_N)}{/cyan-fg}{magenta-fg}♀{/magenta-fg}/{bright-cyan-fg}♂{/bright-cyan-fg} {cyan-fg}Provider{/cyan-fg}`,
|
|
1080
1181
|
style: { bg: COLORS.contentBg },
|
|
1081
1182
|
});
|
|
1082
1183
|
|
|
1083
1184
|
const vpList = blessed.list({
|
|
1084
|
-
parent: vpModal, top:
|
|
1185
|
+
parent: vpModal, top: 2, left: 2, right: 2, bottom: 5,
|
|
1085
1186
|
keys: true, vi: true, mouse: true,
|
|
1086
1187
|
border: { type: 'line' },
|
|
1087
1188
|
scrollbar: { ch: '|', style: { fg: 'cyan' } },
|
|
@@ -1101,26 +1202,24 @@ export function createSetupTab(screen, services) {
|
|
|
1101
1202
|
|
|
1102
1203
|
blessed.text({
|
|
1103
1204
|
parent: vpModal, bottom: 2, left: 2, right: 2, tags: true,
|
|
1104
|
-
content: '{white-fg}[
|
|
1205
|
+
content: '{white-fg}[↑↓] Nav [PgUp/PgDn] Page [Home/End] [a-z] Jump [Enter] Select [Space] Preview [*] Fav [Esc] Cancel{/white-fg}',
|
|
1105
1206
|
style: { bg: COLORS.contentBg },
|
|
1106
1207
|
});
|
|
1107
1208
|
|
|
1108
|
-
function _getFiltered() {
|
|
1109
|
-
if (!_filterText) return _allVoices;
|
|
1110
|
-
const f = _filterText.toLowerCase();
|
|
1111
|
-
return _allVoices.filter(v => v.toLowerCase().includes(f));
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
1209
|
function _buildVoiceItems(voices) {
|
|
1210
|
+
const favs = getFavorites(configService);
|
|
1115
1211
|
return voices.map(v => {
|
|
1116
1212
|
const isActive = v === draft.voice;
|
|
1117
1213
|
const isPrev = v === _previewVoiceId;
|
|
1214
|
+
const isFav = favs.includes(v);
|
|
1118
1215
|
const dot = isPrev ? '♪' : (isActive ? '●' : ' ');
|
|
1216
|
+
const star = isFav ? '{yellow-fg}★{/yellow-fg}' : ' ';
|
|
1119
1217
|
const meta = getVoiceMeta(v);
|
|
1120
1218
|
const name = meta.displayName.length > COL_N
|
|
1121
1219
|
? meta.displayName.slice(0, COL_N - 1) + '…'
|
|
1122
1220
|
: meta.displayName.padEnd(COL_N);
|
|
1123
|
-
|
|
1221
|
+
// genderIconTag has invisible color tags — pad with literal spaces (1 visible char + 3 spaces = 4)
|
|
1222
|
+
return ` ${dot}${star} ${name}${genderIconTag(meta.gender)} ${meta.provider}`;
|
|
1124
1223
|
});
|
|
1125
1224
|
}
|
|
1126
1225
|
|
|
@@ -1129,8 +1228,10 @@ export function createSetupTab(screen, services) {
|
|
|
1129
1228
|
const savedIdx = vpList.selected ?? 0;
|
|
1130
1229
|
const savedScroll = vpList.childBase ?? 0;
|
|
1131
1230
|
_allVoices = scanInstalledVoices();
|
|
1132
|
-
|
|
1133
|
-
|
|
1231
|
+
// Sort by display name so the first-letter quick jump is intuitive
|
|
1232
|
+
_allVoices.sort((a, b) => getVoiceMeta(a).displayName.localeCompare(
|
|
1233
|
+
getVoiceMeta(b).displayName, undefined, { sensitivity: 'base' }));
|
|
1234
|
+
const items = _buildVoiceItems(_allVoices);
|
|
1134
1235
|
vpList.setItems(items.length > 0 ? items : [' (no voices found)']);
|
|
1135
1236
|
vpList.select(Math.min(savedIdx, items.length - 1));
|
|
1136
1237
|
vpList.childBase = Math.min(savedScroll, Math.max(0, items.length - (vpList.height - 2)));
|
|
@@ -1198,25 +1299,45 @@ export function createSetupTab(screen, services) {
|
|
|
1198
1299
|
piper.on('error', () => { _previewProc = null; _previewVoiceId = null; });
|
|
1199
1300
|
}
|
|
1200
1301
|
|
|
1201
|
-
vpSearch.on('keypress', () => {
|
|
1202
|
-
setTimeout(() => { _filterText = vpSearch.getValue().trim(); _refreshVP(); }, 0);
|
|
1203
|
-
});
|
|
1204
|
-
vpSearch.key(['escape'], () => { vpList.focus(); screen.render(); });
|
|
1205
|
-
vpList.key(['/'], () => { vpSearch.clearValue(); vpSearch.focus(); screen.render(); });
|
|
1206
1302
|
vpList.key(['enter'], () => {
|
|
1207
|
-
const
|
|
1208
|
-
const sel = filtered[vpList.selected];
|
|
1303
|
+
const sel = _allVoices[vpList.selected];
|
|
1209
1304
|
if (sel) { draft.voice = sel; _closeVP(); }
|
|
1210
1305
|
});
|
|
1211
1306
|
vpList.key(['space'], () => {
|
|
1212
|
-
const
|
|
1213
|
-
const sel = filtered[vpList.selected];
|
|
1307
|
+
const sel = _allVoices[vpList.selected];
|
|
1214
1308
|
if (sel) _previewVoice(sel);
|
|
1215
1309
|
});
|
|
1310
|
+
vpList.key(['*'], () => {
|
|
1311
|
+
const sel = _allVoices[vpList.selected];
|
|
1312
|
+
if (sel) { toggleFavorite(configService, sel); _refreshVP(); }
|
|
1313
|
+
});
|
|
1216
1314
|
vpList.key(['escape', 'q'], _closeVP);
|
|
1217
1315
|
|
|
1316
|
+
// PageUp / PageDown / Home / End navigation
|
|
1317
|
+
const _pageSize = () => Math.max(1, (vpList.height ?? 10) - 2);
|
|
1318
|
+
vpList.key(['pageup'], () => { vpList.up(_pageSize()); screen.render(); });
|
|
1319
|
+
vpList.key(['pagedown'], () => { vpList.down(_pageSize()); screen.render(); });
|
|
1320
|
+
vpList.key(['home'], () => { vpList.select(0); screen.render(); });
|
|
1321
|
+
vpList.key(['end'], () => { vpList.select(Math.max(0, _allVoices.length - 1)); screen.render(); });
|
|
1322
|
+
|
|
1323
|
+
// First-letter quick jump: typing 'a' jumps to the first voice starting
|
|
1324
|
+
// with A. Block keys reserved by the list widget (vi nav, cancel) so
|
|
1325
|
+
// they don't get swallowed: q (cancel), j/k/g/h/l (vi navigation).
|
|
1326
|
+
const _vpJumpBlocked = new Set(['j', 'k', 'g', 'h', 'l', 'q']);
|
|
1327
|
+
vpList.on('keypress', (ch, key) => {
|
|
1328
|
+
if (!ch || key?.ctrl || key?.meta) return;
|
|
1329
|
+
if (!/^[a-zA-Z]$/.test(ch)) return;
|
|
1330
|
+
const target = ch.toLowerCase();
|
|
1331
|
+
if (_vpJumpBlocked.has(target)) return;
|
|
1332
|
+
const idx = _allVoices.findIndex(v => {
|
|
1333
|
+
const name = getVoiceMeta(v).displayName.toLowerCase();
|
|
1334
|
+
return name.startsWith(target);
|
|
1335
|
+
});
|
|
1336
|
+
if (idx >= 0) { vpList.select(idx); screen.render(); }
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1218
1339
|
_refreshVP();
|
|
1219
|
-
const activeIdx =
|
|
1340
|
+
const activeIdx = _allVoices.indexOf(draft.voice);
|
|
1220
1341
|
if (activeIdx >= 0) vpList.select(activeIdx);
|
|
1221
1342
|
vpList.focus();
|
|
1222
1343
|
screen.render();
|
|
@@ -1272,16 +1393,19 @@ export function createSetupTab(screen, services) {
|
|
|
1272
1393
|
|
|
1273
1394
|
// ── Saved toast ───────────────────────────────────────────────────────────
|
|
1274
1395
|
|
|
1275
|
-
function _showSavedToast(name) {
|
|
1396
|
+
function _showSavedToast(name, filePath) {
|
|
1397
|
+
const lines = [`{center}{green-fg}{bold}${name} saved!{/bold}{/green-fg}{/center}`];
|
|
1398
|
+
if (filePath) lines.push(`{center}{white-fg}${filePath}{/white-fg}{/center}`);
|
|
1399
|
+
const w = filePath ? Math.min(Math.max(filePath.length + 6, 30), 70) : 30;
|
|
1276
1400
|
const toast = blessed.box({
|
|
1277
1401
|
parent: screen,
|
|
1278
1402
|
top: 'center',
|
|
1279
1403
|
left: 'center',
|
|
1280
|
-
width:
|
|
1281
|
-
height: 3,
|
|
1404
|
+
width: w,
|
|
1405
|
+
height: filePath ? 4 : 3,
|
|
1282
1406
|
border: { type: 'line' },
|
|
1283
1407
|
tags: true,
|
|
1284
|
-
content:
|
|
1408
|
+
content: lines.join('\n'),
|
|
1285
1409
|
style: { bg: COLORS.contentBg, border: { fg: 'green' } },
|
|
1286
1410
|
});
|
|
1287
1411
|
toast.setFront();
|
|
@@ -1354,10 +1478,11 @@ export function createSetupTab(screen, services) {
|
|
|
1354
1478
|
lines.push('');
|
|
1355
1479
|
lines.push(' {yellow-fg}4.{/yellow-fg} {bold}.claude/config/{/bold} (personality, verbosity, voice settings)');
|
|
1356
1480
|
lines.push('');
|
|
1357
|
-
lines.push('{white-fg}Press {bold}Escape{/bold} to return to the provider list.{/white-fg}');
|
|
1481
|
+
lines.push('{white-fg}Press {bold}Enter{/bold} or {bold}Escape{/bold} to return to the provider list.{/white-fg}');
|
|
1358
1482
|
|
|
1359
1483
|
infoBox.setContent(lines.join('\n'));
|
|
1360
1484
|
infoBox.show();
|
|
1485
|
+
infoBox.setFront();
|
|
1361
1486
|
infoBox.focus();
|
|
1362
1487
|
infoBox.scrollTo(0);
|
|
1363
1488
|
screen.render();
|
|
@@ -1382,10 +1507,11 @@ export function createSetupTab(screen, services) {
|
|
|
1382
1507
|
lines.push(' {yellow-fg}1.{/yellow-fg} {bold}.vscode/mcp.json{/bold}');
|
|
1383
1508
|
lines.push(' {yellow-fg}2.{/yellow-fg} {bold}.github/copilot-instructions.md{/bold}');
|
|
1384
1509
|
lines.push('');
|
|
1385
|
-
lines.push('{white-fg}Press {bold}Escape{/bold} to return to the provider list.{/white-fg}');
|
|
1510
|
+
lines.push('{white-fg}Press {bold}Enter{/bold} or {bold}Escape{/bold} to return to the provider list.{/white-fg}');
|
|
1386
1511
|
|
|
1387
1512
|
infoBox.setContent(lines.join('\n'));
|
|
1388
1513
|
infoBox.show();
|
|
1514
|
+
infoBox.setFront();
|
|
1389
1515
|
infoBox.focus();
|
|
1390
1516
|
infoBox.scrollTo(0);
|
|
1391
1517
|
screen.render();
|
|
@@ -1412,25 +1538,35 @@ export function createSetupTab(screen, services) {
|
|
|
1412
1538
|
lines.push(' {yellow-fg}3.{/yellow-fg} {bold}AGENTS.md{/bold}');
|
|
1413
1539
|
lines.push(' {yellow-fg}4.{/yellow-fg} {bold}.codex/hooks/{/bold}');
|
|
1414
1540
|
lines.push('');
|
|
1415
|
-
lines.push('{white-fg}Press {bold}Escape{/bold} to return to the provider list.{/white-fg}');
|
|
1541
|
+
lines.push('{white-fg}Press {bold}Enter{/bold} or {bold}Escape{/bold} to return to the provider list.{/white-fg}');
|
|
1416
1542
|
|
|
1417
1543
|
infoBox.setContent(lines.join('\n'));
|
|
1418
1544
|
infoBox.show();
|
|
1545
|
+
infoBox.setFront();
|
|
1419
1546
|
infoBox.focus();
|
|
1420
1547
|
infoBox.scrollTo(0);
|
|
1421
1548
|
screen.render();
|
|
1422
1549
|
}
|
|
1423
1550
|
|
|
1424
|
-
function showRemoveInfo(providerId) {
|
|
1551
|
+
function showRemoveInfo(providerId, removedItems) {
|
|
1425
1552
|
providerView = 'info';
|
|
1426
1553
|
hideAllProviderRows();
|
|
1427
1554
|
contentBox.hide();
|
|
1428
1555
|
|
|
1429
1556
|
const lines = [];
|
|
1430
1557
|
if (providerId === 'claude-code') {
|
|
1431
|
-
lines.push('{bold}{cyan-fg}
|
|
1558
|
+
lines.push('{bold}{cyan-fg}Claude Code -- Uninstalled{/cyan-fg}{/bold}');
|
|
1559
|
+
lines.push('');
|
|
1560
|
+
lines.push('{green-fg}AgentVibes fully removed from this project!{/green-fg}');
|
|
1432
1561
|
lines.push('');
|
|
1433
|
-
|
|
1562
|
+
if (removedItems && removedItems.length > 0) {
|
|
1563
|
+
lines.push('{bold}{cyan-fg}Removed:{/cyan-fg}{/bold}');
|
|
1564
|
+
for (const item of removedItems) {
|
|
1565
|
+
lines.push(` {yellow-fg}•{/yellow-fg} ${item}`);
|
|
1566
|
+
}
|
|
1567
|
+
lines.push('');
|
|
1568
|
+
}
|
|
1569
|
+
lines.push('{white-fg}Re-install anytime with the Install button.{/white-fg}');
|
|
1434
1570
|
} else if (providerId === 'github-copilot') {
|
|
1435
1571
|
lines.push('{bold}{cyan-fg}GitHub Copilot -- Removed{/cyan-fg}{/bold}');
|
|
1436
1572
|
lines.push('');
|
|
@@ -1441,10 +1577,11 @@ export function createSetupTab(screen, services) {
|
|
|
1441
1577
|
lines.push('{green-fg}Successfully removed!{/green-fg}');
|
|
1442
1578
|
}
|
|
1443
1579
|
lines.push('');
|
|
1444
|
-
lines.push('{white-fg}Press {bold}Escape{/bold} to return to the provider list.{/white-fg}');
|
|
1580
|
+
lines.push('{white-fg}Press {bold}Enter{/bold} or {bold}Escape{/bold} to return to the provider list.{/white-fg}');
|
|
1445
1581
|
|
|
1446
1582
|
infoBox.setContent(lines.join('\n'));
|
|
1447
1583
|
infoBox.show();
|
|
1584
|
+
infoBox.setFront();
|
|
1448
1585
|
infoBox.focus();
|
|
1449
1586
|
infoBox.scrollTo(0);
|
|
1450
1587
|
screen.render();
|
|
@@ -1460,7 +1597,7 @@ export function createSetupTab(screen, services) {
|
|
|
1460
1597
|
screen.render();
|
|
1461
1598
|
}
|
|
1462
1599
|
|
|
1463
|
-
infoBox.key(['escape'], () => {
|
|
1600
|
+
infoBox.key(['escape', 'enter'], () => {
|
|
1464
1601
|
showProviderListView();
|
|
1465
1602
|
});
|
|
1466
1603
|
|
|
@@ -1597,8 +1734,41 @@ export function createSetupTab(screen, services) {
|
|
|
1597
1734
|
}
|
|
1598
1735
|
|
|
1599
1736
|
function _renderScreen3() {
|
|
1600
|
-
// Mark setup as completed
|
|
1601
|
-
|
|
1737
|
+
// Mark setup as completed — write to targetDir in case configService
|
|
1738
|
+
// has a different projectRoot (e.g. npm link resolves differently).
|
|
1739
|
+
// Each step is wrapped individually so a partial failure (e.g. corrupt
|
|
1740
|
+
// local config file) does not block the others — and errors are logged
|
|
1741
|
+
// to stderr so the user can see why setup keeps re-running.
|
|
1742
|
+
try { configService.set('setupCompleted', true); }
|
|
1743
|
+
catch (e) { console.error('setupCompleted (project): ' + e.message); }
|
|
1744
|
+
try { configService.setGlobal?.('setupCompleted', true); }
|
|
1745
|
+
catch (e) { console.error('setupCompleted (global): ' + e.message); }
|
|
1746
|
+
|
|
1747
|
+
try {
|
|
1748
|
+
const localCfgDir = path.join(targetDir, '.agentvibes');
|
|
1749
|
+
const localCfgPath = path.join(localCfgDir, 'config.json');
|
|
1750
|
+
if (!fs.existsSync(localCfgPath)) {
|
|
1751
|
+
fs.mkdirSync(localCfgDir, { recursive: true });
|
|
1752
|
+
fs.writeFileSync(localCfgPath, JSON.stringify({ setupCompleted: true }, null, 2));
|
|
1753
|
+
} else {
|
|
1754
|
+
let existing = {};
|
|
1755
|
+
try {
|
|
1756
|
+
existing = JSON.parse(fs.readFileSync(localCfgPath, 'utf8'));
|
|
1757
|
+
} catch (e) {
|
|
1758
|
+
// Corrupt JSON — back up the old file and start fresh so the user
|
|
1759
|
+
// doesn't get stuck in an endless setup loop.
|
|
1760
|
+
console.error(`setupCompleted: ${localCfgPath} is corrupt (${e.message}); rewriting`);
|
|
1761
|
+
try { fs.renameSync(localCfgPath, localCfgPath + '.bak'); } catch {}
|
|
1762
|
+
existing = {};
|
|
1763
|
+
}
|
|
1764
|
+
if (!existing.setupCompleted) {
|
|
1765
|
+
existing.setupCompleted = true;
|
|
1766
|
+
fs.writeFileSync(localCfgPath, JSON.stringify(existing, null, 2));
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
} catch (e) {
|
|
1770
|
+
console.error('setupCompleted (local file): ' + e.message);
|
|
1771
|
+
}
|
|
1602
1772
|
|
|
1603
1773
|
// Show provider rows instead of contentBox
|
|
1604
1774
|
contentBox.hide();
|
|
@@ -1659,6 +1829,7 @@ export function createSetupTab(screen, services) {
|
|
|
1659
1829
|
setTimeout(() => {
|
|
1660
1830
|
if (_screen !== targetScreen) return;
|
|
1661
1831
|
switch (_screen) {
|
|
1832
|
+
case -1: _renderScreenGlobal(); break;
|
|
1662
1833
|
case 0: _renderScreen0(); break;
|
|
1663
1834
|
case 1: _renderScreen1(); break;
|
|
1664
1835
|
case 2: _renderScreen2(); break;
|
|
@@ -1668,6 +1839,7 @@ export function createSetupTab(screen, services) {
|
|
|
1668
1839
|
return;
|
|
1669
1840
|
}
|
|
1670
1841
|
switch (_screen) {
|
|
1842
|
+
case -1: _renderScreenGlobal(); break;
|
|
1671
1843
|
case 0: _renderScreen0(); break;
|
|
1672
1844
|
case 1: _renderScreen1(); break;
|
|
1673
1845
|
case 2: _renderScreen2(); break;
|
|
@@ -1680,7 +1852,20 @@ export function createSetupTab(screen, services) {
|
|
|
1680
1852
|
// =========================================================================
|
|
1681
1853
|
|
|
1682
1854
|
screen.key(['enter'], () => {
|
|
1683
|
-
if (box.hidden || _checking) return;
|
|
1855
|
+
if (box.hidden || _checking || navigationService?.isModalOpen()) return;
|
|
1856
|
+
if (_screen === -1) {
|
|
1857
|
+
// Global config choice screen
|
|
1858
|
+
if (_globalChoiceIdx === 0) {
|
|
1859
|
+
try { configService.saveAllToLocal(_pendingGlobalCfg); } catch {}
|
|
1860
|
+
_screen = 3;
|
|
1861
|
+
} else {
|
|
1862
|
+
_screen = 0;
|
|
1863
|
+
_langIdx = 0;
|
|
1864
|
+
}
|
|
1865
|
+
_pendingGlobalCfg = null;
|
|
1866
|
+
_showCurrentScreen();
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1684
1869
|
if (_screen === 0) {
|
|
1685
1870
|
if (languageService) languageService.setLang(SUPPORTED_LANGUAGES[_langIdx].value);
|
|
1686
1871
|
_screen = 1;
|
|
@@ -1693,7 +1878,11 @@ export function createSetupTab(screen, services) {
|
|
|
1693
1878
|
});
|
|
1694
1879
|
|
|
1695
1880
|
screen.key(['escape'], () => {
|
|
1696
|
-
if (box.hidden || _checking) return;
|
|
1881
|
+
if (box.hidden || _checking || navigationService?.isModalOpen()) return;
|
|
1882
|
+
if (_screen === -1) {
|
|
1883
|
+
setTimeout(() => navigationService?.switchTab('settings'), 0);
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1697
1886
|
if (_screen === 3 && providerView === 'info') {
|
|
1698
1887
|
showProviderListView();
|
|
1699
1888
|
return;
|
|
@@ -1707,7 +1896,12 @@ export function createSetupTab(screen, services) {
|
|
|
1707
1896
|
});
|
|
1708
1897
|
|
|
1709
1898
|
screen.key(['up'], () => {
|
|
1710
|
-
if (box.hidden) return;
|
|
1899
|
+
if (box.hidden || navigationService?.isModalOpen()) return;
|
|
1900
|
+
if (_screen === -1) {
|
|
1901
|
+
_globalChoiceIdx = 0;
|
|
1902
|
+
_renderScreenGlobal();
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1711
1905
|
if (_screen === 0) {
|
|
1712
1906
|
_langIdx = Math.max(0, _langIdx - 1);
|
|
1713
1907
|
_renderScreen0();
|
|
@@ -1716,7 +1910,8 @@ export function createSetupTab(screen, services) {
|
|
|
1716
1910
|
});
|
|
1717
1911
|
|
|
1718
1912
|
screen.key(['left'], () => {
|
|
1719
|
-
if (box.hidden || _checking) return;
|
|
1913
|
+
if (box.hidden || _checking || navigationService?.isModalOpen()) return;
|
|
1914
|
+
if (_screen === -1) return;
|
|
1720
1915
|
if (_screen === 3) return; // Left handled by button nav
|
|
1721
1916
|
if (_screen > 0) {
|
|
1722
1917
|
_screen--;
|
|
@@ -1725,7 +1920,8 @@ export function createSetupTab(screen, services) {
|
|
|
1725
1920
|
});
|
|
1726
1921
|
|
|
1727
1922
|
screen.key(['right'], () => {
|
|
1728
|
-
if (box.hidden || _checking) return;
|
|
1923
|
+
if (box.hidden || _checking || navigationService?.isModalOpen()) return;
|
|
1924
|
+
if (_screen === -1) return;
|
|
1729
1925
|
if (_screen === 0) {
|
|
1730
1926
|
if (languageService) languageService.setLang(SUPPORTED_LANGUAGES[_langIdx].value);
|
|
1731
1927
|
_screen = 1;
|
|
@@ -1738,7 +1934,12 @@ export function createSetupTab(screen, services) {
|
|
|
1738
1934
|
});
|
|
1739
1935
|
|
|
1740
1936
|
screen.key(['down'], () => {
|
|
1741
|
-
if (box.hidden) return;
|
|
1937
|
+
if (box.hidden || navigationService?.isModalOpen()) return;
|
|
1938
|
+
if (_screen === -1) {
|
|
1939
|
+
_globalChoiceIdx = 1;
|
|
1940
|
+
_renderScreenGlobal();
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1742
1943
|
if (_screen === 0) {
|
|
1743
1944
|
_langIdx = Math.min(SUPPORTED_LANGUAGES.length - 1, _langIdx + 1);
|
|
1744
1945
|
_renderScreen0();
|
|
@@ -1746,6 +1947,49 @@ export function createSetupTab(screen, services) {
|
|
|
1746
1947
|
}
|
|
1747
1948
|
});
|
|
1748
1949
|
|
|
1950
|
+
// =========================================================================
|
|
1951
|
+
// Screen -1: Global Config Detection (pre-wizard)
|
|
1952
|
+
// =========================================================================
|
|
1953
|
+
|
|
1954
|
+
function _renderScreenGlobal() {
|
|
1955
|
+
const cfg = _pendingGlobalCfg || {};
|
|
1956
|
+
const cfgPath = configService?.getGlobalConfigPath?.() || '~/.agentvibes/config.json';
|
|
1957
|
+
|
|
1958
|
+
// Build settings preview
|
|
1959
|
+
const voice = cfg.voice || '(default)';
|
|
1960
|
+
const lang = cfg.language || 'en';
|
|
1961
|
+
const ttsEngine = cfg.ttsEngine || '(auto)';
|
|
1962
|
+
const verbosity = cfg.verbosity || 'high';
|
|
1963
|
+
const personality = cfg.personality || 'none';
|
|
1964
|
+
|
|
1965
|
+
const sel0 = _globalChoiceIdx === 0;
|
|
1966
|
+
const sel1 = _globalChoiceIdx === 1;
|
|
1967
|
+
const hi = '{magenta-bg}{white-fg}';
|
|
1968
|
+
const lo = '{/white-fg}{/magenta-bg}';
|
|
1969
|
+
|
|
1970
|
+
const lines = [
|
|
1971
|
+
_HDR('', 'Global Settings Found'),
|
|
1972
|
+
'',
|
|
1973
|
+
` {white-fg}Location:{/white-fg} {yellow-fg}${cfgPath}{/yellow-fg}`,
|
|
1974
|
+
'',
|
|
1975
|
+
` {cyan-fg}Voice:{/cyan-fg} {yellow-fg}${voice}{/yellow-fg}`,
|
|
1976
|
+
` {cyan-fg}Language:{/cyan-fg} {yellow-fg}${lang}{/yellow-fg}`,
|
|
1977
|
+
` {cyan-fg}TTS Engine:{/cyan-fg} {yellow-fg}${ttsEngine}{/yellow-fg}`,
|
|
1978
|
+
` {cyan-fg}Verbosity:{/cyan-fg} {yellow-fg}${verbosity}{/yellow-fg}`,
|
|
1979
|
+
` {cyan-fg}Personality:{/cyan-fg}{yellow-fg} ${personality}{/yellow-fg}`,
|
|
1980
|
+
'',
|
|
1981
|
+
' {white-fg}What would you like to do for this project?{/white-fg}',
|
|
1982
|
+
'',
|
|
1983
|
+
` ${sel0 ? hi : ''}> Load Global Settings${sel0 ? lo : ''} {white-fg}— use these settings for this project{/white-fg}`,
|
|
1984
|
+
` ${sel1 ? hi : ''}> Start Fresh${sel1 ? lo : ''} {white-fg}— run the full setup wizard from scratch{/white-fg}`,
|
|
1985
|
+
'',
|
|
1986
|
+
];
|
|
1987
|
+
contentBox.setContent(_c(lines));
|
|
1988
|
+
hintLine.setContent(' [Up/Down] Choose | [Enter] Select');
|
|
1989
|
+
box.focus();
|
|
1990
|
+
screen.render();
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1749
1993
|
// =========================================================================
|
|
1750
1994
|
// Tab Component Contract
|
|
1751
1995
|
// =========================================================================
|
|
@@ -1754,16 +1998,32 @@ export function createSetupTab(screen, services) {
|
|
|
1754
1998
|
box,
|
|
1755
1999
|
|
|
1756
2000
|
show() {
|
|
1757
|
-
|
|
1758
|
-
|
|
2001
|
+
_lastScreen = -1;
|
|
2002
|
+
providerView = 'list';
|
|
2003
|
+
box.show();
|
|
2004
|
+
|
|
2005
|
+
// Detect if AgentVibes is already installed in the target directory
|
|
2006
|
+
// (e.g. user ran install, closed TUI, came back)
|
|
2007
|
+
const alreadyInstalled = fs.existsSync(path.join(targetDir, '.claude', 'commands', 'agent-vibes'));
|
|
2008
|
+
|
|
2009
|
+
// Check: no local config but global exists with setupCompleted
|
|
2010
|
+
const hasLocal = configService?.hasLocalConfig?.();
|
|
2011
|
+
const globalCfg = configService?.getGlobalConfig?.() ?? {};
|
|
2012
|
+
if (!alreadyInstalled && !hasLocal && globalCfg.setupCompleted) {
|
|
2013
|
+
_pendingGlobalCfg = globalCfg;
|
|
2014
|
+
_screen = -1;
|
|
2015
|
+
_showCurrentScreen();
|
|
2016
|
+
screen.render();
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
// If already installed or not first run, skip directly to Screen 3 (providers)
|
|
2021
|
+
if (alreadyInstalled || !_isFirstRun()) {
|
|
1759
2022
|
_screen = 3;
|
|
1760
2023
|
} else {
|
|
1761
2024
|
_screen = 0;
|
|
1762
2025
|
_langIdx = 0;
|
|
1763
2026
|
}
|
|
1764
|
-
_lastScreen = -1;
|
|
1765
|
-
providerView = 'list';
|
|
1766
|
-
box.show();
|
|
1767
2027
|
_showCurrentScreen();
|
|
1768
2028
|
screen.render();
|
|
1769
2029
|
},
|