agentvibes 4.4.1 → 4.5.0
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/.agentvibes/config.json +4 -4
- package/.claude/config/reverb-level.txt +1 -1
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks-windows/bmad-speak.ps1 +112 -0
- package/.claude/hooks-windows/play-tts-piper.ps1 +3 -4
- package/.claude/hooks-windows/play-tts-sapi.ps1 +3 -4
- package/.claude/hooks-windows/play-tts-soprano.ps1 +2 -3
- package/.claude/hooks-windows/play-tts-termux-ssh.ps1 +138 -0
- package/.claude/hooks-windows/play-tts.ps1 +14 -6
- package/.claude/hooks-windows/provider-manager.ps1 +16 -1
- package/CLAUDE.md +4 -0
- package/README.md +39 -9
- package/RELEASE_NOTES.md +39 -0
- package/bin/agent-vibes +1 -1
- package/bin/agentvibes-voice-browser.js +1 -1
- package/bin/bmad-speak.js +52 -0
- package/bin/mcp-server.js +1 -1
- package/bin/test-bmad-pr +1 -1
- package/package.json +1 -1
- package/setup-windows.ps1 +4 -4
- package/src/console/app.js +58 -11
- package/src/console/tabs/agents-tab.js +61 -65
- package/src/console/tabs/help-tab.js +107 -54
- package/src/console/tabs/install-tab.js +107 -47
- package/src/console/tabs/music-tab.js +1030 -1011
- package/src/console/tabs/placeholder-tab.js +27 -0
- package/src/console/tabs/readme-tab.js +9 -7
- package/src/console/tabs/receiver-tab.js +23 -12
- package/src/console/tabs/settings-tab.js +4001 -3783
- package/src/console/tabs/voices-tab.js +1680 -1653
- package/src/console/widgets/personality-picker.js +35 -7
- package/src/console/widgets/reverb-picker.js +9 -6
- package/src/console/widgets/track-picker.js +6 -1
- package/src/i18n/de.js +201 -0
- package/src/i18n/en.js +201 -0
- package/src/i18n/es.js +201 -0
- package/src/i18n/fr.js +201 -0
- package/src/i18n/hi.js +201 -0
- package/src/i18n/ja.js +201 -0
- package/src/i18n/ko.js +201 -0
- package/src/i18n/pt.js +201 -0
- package/src/i18n/strings.js +54 -0
- package/src/i18n/zh-CN.js +201 -0
- package/src/installer/language-screen.js +31 -0
- package/src/installer.js +79 -25
- package/src/services/language-service.js +47 -0
- package/src/utils/file-ownership-verifier.js +2 -2
- package/src/utils/provider-validator.js +9 -13
- package/.claude/hooks-windows/play-tts-windows-piper.ps1 +0 -209
- package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +0 -108
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
languageSelectTitle: "🌐 Select Language / Seleccionar Idioma / Choisir la langue / Sprache wählen / Selecionar Idioma / 言語を選択 / भाषा चुनें / 选择语言 / 언어 선택",
|
|
3
|
+
languageApplied: "语言已应用",
|
|
4
|
+
welcomeTitle: "欢迎使用 AgentVibes!",
|
|
5
|
+
installDetails: "安装详情",
|
|
6
|
+
installLocation: "安装位置",
|
|
7
|
+
packageVersion: "包版本",
|
|
8
|
+
readyToConfigure: "准备好配置 AgentVibes 了吗?",
|
|
9
|
+
installationCancelled: "安装已取消。",
|
|
10
|
+
startInstall: "开始安装",
|
|
11
|
+
installationCancelledMsg: "安装已被用户取消。",
|
|
12
|
+
installedSuccess: "AgentVibes 安装成功!",
|
|
13
|
+
installComplete: "安装完成",
|
|
14
|
+
installationFailed: "安装失败",
|
|
15
|
+
installing: "正在安装 AgentVibes...",
|
|
16
|
+
providerLabel: "提供商",
|
|
17
|
+
voiceLabel: "语音",
|
|
18
|
+
locationLabel: "位置",
|
|
19
|
+
versionLabel: "版本",
|
|
20
|
+
configurationSetup: "配置",
|
|
21
|
+
configurationIntro: "请配置您的 AgentVibes 安装。",
|
|
22
|
+
navigationHint: "使用方向键在页面之间导航。",
|
|
23
|
+
nonInteractiveDetected: "检测到非交互模式",
|
|
24
|
+
installError: "安装失败",
|
|
25
|
+
continuePrompt: "继续",
|
|
26
|
+
cancelPrompt: "取消",
|
|
27
|
+
setupWizard: "安装向导",
|
|
28
|
+
setupWizardSubtitle: "为AI助手提供个性化TTS。",
|
|
29
|
+
dependencyCheck: "依赖项检查",
|
|
30
|
+
checkingDependencies: "正在检查依赖项...",
|
|
31
|
+
depColumn: "依赖项",
|
|
32
|
+
statusColumn: "状态",
|
|
33
|
+
installed: "已安装",
|
|
34
|
+
notFound: "未找到",
|
|
35
|
+
ffmpegMissing: "未找到(背景音乐所需)",
|
|
36
|
+
ttsDetected: "已检测到TTS提供商",
|
|
37
|
+
noTtsFound: "未找到TTS提供商。请先安装Piper或Soprano。",
|
|
38
|
+
providerSelection: "提供商选择",
|
|
39
|
+
availableProviders: "可用的TTS提供商:",
|
|
40
|
+
providerAndVoice: "提供商和语音",
|
|
41
|
+
voiceChangeHint: "(安装后可在设置中更改)",
|
|
42
|
+
introText: "介绍文本",
|
|
43
|
+
introTextLabel: "介绍文本",
|
|
44
|
+
none: "无",
|
|
45
|
+
example: "示例",
|
|
46
|
+
screen1Hint: "屏幕1/5: 欢迎 | [←/→] 导航 | [Enter] 开始 | [Esc] 退出",
|
|
47
|
+
screen2Hint: "屏幕2/5: 依赖项 | [←] 返回 | [Enter] 下一步",
|
|
48
|
+
screen3Hint: "屏幕3/5: 提供商 | [←] 返回 | [↑↓] 选择 | [Enter/→] 确认",
|
|
49
|
+
screen4Hint: "屏幕4/5: 配置 | [Esc] 返回 | [E] 编辑 | [↓] 接受并安装",
|
|
50
|
+
screen5HintDone: "屏幕5/5: 完成 | [Enter] 确定 — 完成",
|
|
51
|
+
screen5HintWait: "屏幕5/5: 安装中... | 请稍候",
|
|
52
|
+
languageSettings: "语言",
|
|
53
|
+
languageSettingsSubtitle: "选择界面语言",
|
|
54
|
+
currentLanguage: "当前语言",
|
|
55
|
+
changeLanguage: "更改语言",
|
|
56
|
+
beginBtn: "▶ 开始",
|
|
57
|
+
exitBtn: "✗ 退出",
|
|
58
|
+
footerText: "[Enter] 继续/完成 [Esc] 返回/退出 [C] 控制台 [S/V/M/A/R] 标签 [Q] 退出",
|
|
59
|
+
customizationTool: "定制工具",
|
|
60
|
+
quitLabel: "[Q] 退出",
|
|
61
|
+
tabInstall: "安装",
|
|
62
|
+
tabSettings: "设置",
|
|
63
|
+
tabVoices: "语音",
|
|
64
|
+
tabMusic: "音乐",
|
|
65
|
+
tabBmad: "BMad",
|
|
66
|
+
tabReceiver: "接收器",
|
|
67
|
+
tabReadme: "说明",
|
|
68
|
+
tabHelp: "帮助",
|
|
69
|
+
subTabVoice: " [V] 语音 ",
|
|
70
|
+
subTabEffects: " [E] 效果 ",
|
|
71
|
+
subTabPersonality: " [P] 个性 ",
|
|
72
|
+
subTabOutput: " [O] 输出 ",
|
|
73
|
+
subTabLanguage: " [L] 语言 ",
|
|
74
|
+
sectionProviderVoice: " 🎤 提供商与语音 ",
|
|
75
|
+
sectionAudioEffects: " ⚡ 音频效果 ",
|
|
76
|
+
sectionBgMusic: " 🎸 背景音乐 ",
|
|
77
|
+
sectionStyle: " 🎭 风格 ",
|
|
78
|
+
sectionIntroText: " ✍️ 介绍文本 ",
|
|
79
|
+
sectionAudioDest: " 📡 音频目标 ",
|
|
80
|
+
sectionConfigStorage: " 💾 配置存储 ",
|
|
81
|
+
sectionLanguage: " 🌐 语言 ",
|
|
82
|
+
providerRowLabel: "提供商:",
|
|
83
|
+
currentVoiceLabel: "当前语音:",
|
|
84
|
+
reverbLabel: "混响:",
|
|
85
|
+
trackLabel: "曲目:",
|
|
86
|
+
volumeLabel: "音量:",
|
|
87
|
+
verbosityLabel: "详细度:",
|
|
88
|
+
personalityLabel: "个性:",
|
|
89
|
+
introTextRowLabel: "介绍文本:",
|
|
90
|
+
destinationLabel: "目标:",
|
|
91
|
+
sshAliasLabel: "SSH别名:",
|
|
92
|
+
globalLabel: "全局:",
|
|
93
|
+
localLabel: "本地:",
|
|
94
|
+
languageLabel: "语言:",
|
|
95
|
+
switchBtn: "切换",
|
|
96
|
+
changeBtn: "更改",
|
|
97
|
+
playBtn: "▶ 播放",
|
|
98
|
+
stopBtn: "■ 停止",
|
|
99
|
+
previewBtn: "▶ 预览",
|
|
100
|
+
fullPreviewBtn: "▶ 完整预览",
|
|
101
|
+
saveGloballyBtn: "全局保存",
|
|
102
|
+
saveLocallyBtn: "本地保存",
|
|
103
|
+
cancelChangesBtn: "取消更改",
|
|
104
|
+
editBtn: "编辑",
|
|
105
|
+
clearBtn: "清除",
|
|
106
|
+
applyLanguageBtn: "✓ 应用语言",
|
|
107
|
+
enabledBtn: "已启用",
|
|
108
|
+
disabledBtn: "已禁用",
|
|
109
|
+
okSaveBtn: "OK — 保存",
|
|
110
|
+
enableBtn: "启用",
|
|
111
|
+
streamingTextBtn: "仅文本流 ✓",
|
|
112
|
+
streamingPulseBtn: "Pulse音频流",
|
|
113
|
+
continueArrowBtn: "继续 →",
|
|
114
|
+
acceptInstallBtn: "✓ 接受并安装",
|
|
115
|
+
okDoneBtn: "✓ OK — 完成",
|
|
116
|
+
editInstallBtn: "编辑",
|
|
117
|
+
musicDisabledMsg: "音乐已禁用。现在启用?",
|
|
118
|
+
settingsFooter: "[↑↓] 组 [←→] 兄弟/子标签 [Enter/Space] 激活 [Tab] 切换标签 [Q] 退出",
|
|
119
|
+
voicesFooter: "[↑↓/jk] 导航 [Space] 预览 [Enter] 选择 [F] 收藏 [/] 搜索",
|
|
120
|
+
musicFooter: "[↑↓/jk] 导航 [Space] 预览 [Enter] 选择 [M] 切换 [*] 收藏 [F] 筛选 [Q] 退出",
|
|
121
|
+
helpFooter: "[↑↓/jk] 滚动 [/] 搜索 [PgUp/PgDn] 翻页 [S/V/M/A/R] 标签 [Q] 退出",
|
|
122
|
+
readmeFooter: "[↑↓/jk] 滚动 [PgUp/PgDn] 翻页 [/] 搜索 [S/V/M/A/R] 标签 [Q] 退出",
|
|
123
|
+
receiverFooter: "SSH接收器 [Q] 退出",
|
|
124
|
+
searchLabel: "搜索:",
|
|
125
|
+
settingsSavedMsg: "设置已保存",
|
|
126
|
+
changesRevertedMsg: "更改已撤销",
|
|
127
|
+
musicBuiltInHeader: "── 内置音轨 ",
|
|
128
|
+
musicStatusHeader: "── 音乐状态 ",
|
|
129
|
+
musicStatusLabel: "音乐:",
|
|
130
|
+
musicActiveTrack: "当前音轨:",
|
|
131
|
+
musicFilterLabel: "过滤:",
|
|
132
|
+
musicFilterAll: "全部",
|
|
133
|
+
musicFilterFavs: "收藏",
|
|
134
|
+
musicToggleBtn: "[切换音乐]",
|
|
135
|
+
musicAddCustomBtn: "[添加自定义音轨]",
|
|
136
|
+
voicesHeader: "── 语音 ",
|
|
137
|
+
voicesColName: "名称",
|
|
138
|
+
voicesColGender: "性别",
|
|
139
|
+
voicesColProvider: "提供者",
|
|
140
|
+
voicesInfoHeader: "── 语音信息 ",
|
|
141
|
+
voicesSwitchBtn: "[切换语音]",
|
|
142
|
+
voicesFavoriteBtn: "[★ 收藏]",
|
|
143
|
+
voicesDownloadBtn: "[下载语音]",
|
|
144
|
+
voicesRowHintInstalled: "[Space] 预览 [Enter] 选择 [*] 收藏",
|
|
145
|
+
voicesRowHintUninstalled: "[Enter] 下载并安装",
|
|
146
|
+
musicRowHint: "[Space] 播放 [Enter] 选择 [*] 收藏",
|
|
147
|
+
musicHintText: "[Space] 预览 [Enter] 设置音轨 [*] 收藏",
|
|
148
|
+
genderFemale: "女性",
|
|
149
|
+
genderMale: "男性",
|
|
150
|
+
voiceInfoVoice: "语音:",
|
|
151
|
+
voiceInfoGender: "性别:",
|
|
152
|
+
voiceInfoLanguage: "语言:",
|
|
153
|
+
voiceInfoQuality: "质量:",
|
|
154
|
+
voiceInfoProvider: "提供者:",
|
|
155
|
+
voiceInfoId: "ID:",
|
|
156
|
+
voiceInfoSpeaker: "说话者:",
|
|
157
|
+
voiceInfoModel: "模型:",
|
|
158
|
+
voiceInfoSpeakerId: "说话者ID:",
|
|
159
|
+
voiceInfoDownload: "⬇ 按[Enter]下载并安装",
|
|
160
|
+
voicePlaying: "(播放中)",
|
|
161
|
+
bmadTitle: "🧙 BMAD代理",
|
|
162
|
+
bmadWhatIsHeader: "什么是BMAD?",
|
|
163
|
+
bmadDesc: "BMad方法(Build More Architect Dreams)是一个AI驱动的开发框架模块,\n帮助您完成从构思规划到智能实现的整个软件开发过程。提供专业AI代理、\n引导式工作流程和智能规划。\n\n如果您习惯使用Claude、Cursor或GitHub Copilot等AI编程助手,\n您已经准备好开始了。",
|
|
164
|
+
bmadInstallHeader: "在您的项目中安装BMAD:",
|
|
165
|
+
bmadLearnMoreHeader: "了解更多:",
|
|
166
|
+
bmadInstalledNote: "BMAD安装后,此标签页将显示您所有的代理,并允许您独立自定义每个代理\n的语音、前置文本、混响、个性和背景音乐。",
|
|
167
|
+
receiverWhatIsTitle: "什么是SSH接收器?",
|
|
168
|
+
receiverDesc: "SSH接收器让您的远程服务器通过这台机器发声。当远程服务器上的AI助手\n需要播放TTS音频时,它通过SSH将文本发送到这台机器,由这台机器生成\n并通过本地扬声器播放音频。\n\n远程AI ──[SSH]──► 这台机器 ──[piper+sox+ffmpeg]──► 您的扬声器",
|
|
169
|
+
helpSectionGlobal: "全局快捷键",
|
|
170
|
+
helpSectionNavigation: "导航快捷键",
|
|
171
|
+
helpSectionColors: "标签颜色指南",
|
|
172
|
+
helpQuit: "退出控制台",
|
|
173
|
+
helpForceQuit: "强制退出",
|
|
174
|
+
helpSwitchSettings: "切换到设置标签",
|
|
175
|
+
helpSwitchVoices: "切换到语音标签",
|
|
176
|
+
helpSwitchMusic: "切换到音乐标签",
|
|
177
|
+
helpSwitchReadme: "切换到Readme标签",
|
|
178
|
+
helpSwitchHelp: "切换到帮助标签",
|
|
179
|
+
helpSwitchInstall: "切换到安装标签",
|
|
180
|
+
helpCloseModal: "关闭对话框 / 返回",
|
|
181
|
+
helpNavigateLists: "浏览列表",
|
|
182
|
+
helpSelectActivate: "选择 / 激活",
|
|
183
|
+
helpTogglePreview: "切换 / 预览",
|
|
184
|
+
helpNextButton: "下一个按钮",
|
|
185
|
+
helpPrevButton: "上一个按钮",
|
|
186
|
+
helpOpenSearch: "打开搜索/筛选",
|
|
187
|
+
helpToggleFavFilter: "切换收藏筛选(语音/音乐)",
|
|
188
|
+
helpToggleFav: "切换收藏(音乐标签)",
|
|
189
|
+
helpToggleMusic: "开启/关闭音乐(音乐标签)",
|
|
190
|
+
helpColorSettings: "设置标签页脚",
|
|
191
|
+
helpColorVoices: "语音标签页脚",
|
|
192
|
+
helpColorMusic: "音乐标签页脚",
|
|
193
|
+
helpColorReadme: "Readme标签页脚",
|
|
194
|
+
helpColorHelp: "帮助标签页脚",
|
|
195
|
+
helpColorInstall: "安装标签页脚",
|
|
196
|
+
helpSearchLabel: "搜索:",
|
|
197
|
+
readmeScrollMore: "↓ 向下滚动查看更多 ↓",
|
|
198
|
+
readmeNotFound: "*(当前目录中未找到README.md)*",
|
|
199
|
+
bmadFooterNobmad: "[Tab] 切换标签 [Q] 退出",
|
|
200
|
+
bmadFooterBmad: "[↑↓/jk] 导航 [Space] 预览 [Enter] 配置 [A] 自动分配 [B] 批量 [X] 重置 [Q] 退出",
|
|
201
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: src/installer/language-screen.js
|
|
3
|
+
*
|
|
4
|
+
* Language selection screen for the AgentVibes installer TUI.
|
|
5
|
+
* Presents a list of supported languages and returns the selected code.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
import { t, SUPPORTED_LANGUAGES } from '../i18n/strings.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Show the language selection screen and return the chosen language code.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} [currentLang='en'] - Currently active language code (used as default)
|
|
15
|
+
* @returns {Promise<string>} The selected language code (e.g. 'es', 'zh-CN')
|
|
16
|
+
*/
|
|
17
|
+
export async function selectLanguage(currentLang = 'en') {
|
|
18
|
+
const { lang } = await inquirer.prompt([{
|
|
19
|
+
type: 'list',
|
|
20
|
+
name: 'lang',
|
|
21
|
+
message: '🌐 Select Language / Seleccionar Idioma / Choisir la langue / Sprache wählen / Selecionar Idioma / 言語を選択 / भाषा चुनें / 选择语言 / 언어 선택',
|
|
22
|
+
default: currentLang,
|
|
23
|
+
choices: SUPPORTED_LANGUAGES
|
|
24
|
+
}]);
|
|
25
|
+
|
|
26
|
+
console.clear();
|
|
27
|
+
const chosenName = SUPPORTED_LANGUAGES.find(l => l.value === lang)?.name ?? lang;
|
|
28
|
+
console.log(`\u2713 ${t(lang, 'languageApplied')}: ${chosenName}\n`);
|
|
29
|
+
|
|
30
|
+
return lang;
|
|
31
|
+
}
|
package/src/installer.js
CHANGED
|
@@ -72,6 +72,8 @@ import {
|
|
|
72
72
|
} from './utils/provider-validator.js';
|
|
73
73
|
import { promptForCustomMusic } from './installer/music-file-input.js';
|
|
74
74
|
import { createPreviewListPrompt } from './utils/preview-list-prompt.js';
|
|
75
|
+
import { selectLanguage } from './installer/language-screen.js';
|
|
76
|
+
import { t } from './i18n/strings.js';
|
|
75
77
|
|
|
76
78
|
const __filename = fileURLToPath(import.meta.url);
|
|
77
79
|
const __dirname = path.dirname(__filename);
|
|
@@ -199,9 +201,9 @@ function supportsEmoji() {
|
|
|
199
201
|
|
|
200
202
|
const isModernTerminal = modernTerminals.some(t => term.toLowerCase().includes(t));
|
|
201
203
|
|
|
202
|
-
// Windows Terminal always supports emoji
|
|
204
|
+
// Windows Terminal always supports emoji — coerce to boolean to avoid returning WT_SESSION UUID
|
|
203
205
|
const isWindowsTerminal = process.platform === 'win32' &&
|
|
204
|
-
(process.env.WT_SESSION || process.env.WT_PROFILE_ID);
|
|
206
|
+
!!(process.env.WT_SESSION || process.env.WT_PROFILE_ID);
|
|
205
207
|
|
|
206
208
|
// macOS Terminal and iTerm2
|
|
207
209
|
const isMacOS = process.platform === 'darwin';
|
|
@@ -1115,6 +1117,9 @@ async function collectConfiguration(options = {}) {
|
|
|
1115
1117
|
}
|
|
1116
1118
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
1117
1119
|
config.piperPath = path.join(homeDir, '.claude', 'piper-voices');
|
|
1120
|
+
// AI agent / non-interactive defaults: no reverb, no background music
|
|
1121
|
+
config.reverb = 'none';
|
|
1122
|
+
config.backgroundMusic = { enabled: false, track: 'agentvibes_soft_flamenco_loop.mp3' };
|
|
1118
1123
|
return config;
|
|
1119
1124
|
}
|
|
1120
1125
|
|
|
@@ -4936,8 +4941,14 @@ async function install(options = {}) {
|
|
|
4936
4941
|
const configOffset = 0;
|
|
4937
4942
|
|
|
4938
4943
|
// Loop to allow going back to welcome screen
|
|
4944
|
+
let lang = 'en';
|
|
4939
4945
|
let userConfig = null;
|
|
4946
|
+
const isNonInteractive = options.yes || options.nonInteractive || process.env.AGENT_VIBES_NON_INTERACTIVE === '1';
|
|
4940
4947
|
while (!userConfig) {
|
|
4948
|
+
// Language selection screen — skip in non-interactive / CI mode
|
|
4949
|
+
if (!isNonInteractive) {
|
|
4950
|
+
lang = await selectLanguage(lang);
|
|
4951
|
+
}
|
|
4941
4952
|
showWelcome();
|
|
4942
4953
|
|
|
4943
4954
|
// Show release notes and recent changes after welcome banner
|
|
@@ -4970,6 +4981,7 @@ async function install(options = {}) {
|
|
|
4970
4981
|
// Returns null if user wants to go back to welcome
|
|
4971
4982
|
userConfig = await collectConfiguration({
|
|
4972
4983
|
...options,
|
|
4984
|
+
lang,
|
|
4973
4985
|
pageOffset: configOffset,
|
|
4974
4986
|
totalPages: configPages // Temporary, will show correct count later
|
|
4975
4987
|
});
|
|
@@ -4979,19 +4991,37 @@ async function install(options = {}) {
|
|
|
4979
4991
|
const piperVoicesPath = userConfig.piperPath;
|
|
4980
4992
|
const targetDir = options.directory || currentDir;
|
|
4981
4993
|
|
|
4982
|
-
//
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
name: 'startInstall',
|
|
4987
|
-
message: chalk.yellow('✅ Start Installation?'),
|
|
4988
|
-
default: true,
|
|
4989
|
-
},
|
|
4990
|
-
]);
|
|
4994
|
+
// Non-interactive mode: structured logging and piper validation before install
|
|
4995
|
+
if (options.nonInteractive || process.env.AGENT_VIBES_NON_INTERACTIVE === '1') {
|
|
4996
|
+
console.log(`[AV] Non-interactive mode detected`);
|
|
4997
|
+
console.log(`[AV] Provider: ${selectedProvider} | Platform: ${process.platform}`);
|
|
4991
4998
|
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4999
|
+
if (isPiperProvider(selectedProvider) && !isPiperInstalled()) {
|
|
5000
|
+
process.stderr.write(`[AV ERROR] Piper binaries not found.\n`);
|
|
5001
|
+
process.stderr.write(`[AV] To install Piper manually, run:\n`);
|
|
5002
|
+
process.stderr.write(`[AV] npx agentvibes --install-piper\n`);
|
|
5003
|
+
process.stderr.write(`[AV] Or visit: https://github.com/paulpreibisch/AgentVibes#-installation\n`);
|
|
5004
|
+
process.exit(1);
|
|
5005
|
+
}
|
|
5006
|
+
|
|
5007
|
+
console.log(`[AV] Installing to: ${targetDir}/.claude/`);
|
|
5008
|
+
}
|
|
5009
|
+
|
|
5010
|
+
// Confirm and start installation (skip in non-interactive / --yes mode)
|
|
5011
|
+
if (!options.yes && !options.nonInteractive && process.env.AGENT_VIBES_NON_INTERACTIVE !== '1') {
|
|
5012
|
+
const { startInstall } = await inquirer.prompt([
|
|
5013
|
+
{
|
|
5014
|
+
type: 'confirm',
|
|
5015
|
+
name: 'startInstall',
|
|
5016
|
+
message: chalk.yellow('✅ Start Installation?'),
|
|
5017
|
+
default: true,
|
|
5018
|
+
},
|
|
5019
|
+
]);
|
|
5020
|
+
|
|
5021
|
+
if (!startInstall) {
|
|
5022
|
+
console.log(chalk.red('\n❌ Installation cancelled.\n'));
|
|
5023
|
+
process.exit(0);
|
|
5024
|
+
}
|
|
4995
5025
|
}
|
|
4996
5026
|
|
|
4997
5027
|
// Silent spinner for copy functions — suppresses per-file output
|
|
@@ -5183,6 +5213,13 @@ Troubleshooting:
|
|
|
5183
5213
|
}
|
|
5184
5214
|
}
|
|
5185
5215
|
|
|
5216
|
+
// Persist language selection — validate against known codes before writing
|
|
5217
|
+
if (lang && /^[a-zA-Z]{2}(-[a-zA-Z]{2})?$/.test(lang)) {
|
|
5218
|
+
const langConfigPath = path.join(claudeDir, 'config', 'language.txt');
|
|
5219
|
+
await fs.mkdir(path.join(claudeDir, 'config'), { recursive: true });
|
|
5220
|
+
await fs.writeFile(langConfigPath, lang, { mode: 0o600 });
|
|
5221
|
+
}
|
|
5222
|
+
|
|
5186
5223
|
// Apply verbosity, personality, pretext
|
|
5187
5224
|
await fs.writeFile(path.join(claudeDir, 'tts-verbosity.txt'), userConfig.verbosity);
|
|
5188
5225
|
if (userConfig.personality && userConfig.personality !== 'none') {
|
|
@@ -5215,19 +5252,28 @@ Troubleshooting:
|
|
|
5215
5252
|
|
|
5216
5253
|
spinner.succeed(chalk.green('AgentVibes installed successfully!'));
|
|
5217
5254
|
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5255
|
+
if (options.nonInteractive || process.env.AGENT_VIBES_NON_INTERACTIVE === '1') {
|
|
5256
|
+
console.log(`[AV] Installation complete`);
|
|
5257
|
+
console.log(`[AV] Provider: ${selectedProvider} | Location: ${targetDir}/.claude/ | Version: ${VERSION}`);
|
|
5258
|
+
} else {
|
|
5259
|
+
// Clean final summary
|
|
5260
|
+
console.log('');
|
|
5261
|
+
console.log(chalk.green.bold(' ✅ Installation Complete'));
|
|
5262
|
+
console.log(chalk.gray(` Provider: ${selectedProvider}`));
|
|
5263
|
+
console.log(chalk.gray(` Location: ${targetDir}/.claude/`));
|
|
5264
|
+
console.log(chalk.gray(` Version: ${VERSION}`));
|
|
5265
|
+
console.log('');
|
|
5266
|
+
console.log(chalk.white(' Run ') + chalk.cyan('npx agentvibes') + chalk.white(' to open the console.'));
|
|
5267
|
+
console.log('');
|
|
5268
|
+
}
|
|
5227
5269
|
|
|
5228
5270
|
} catch (error) {
|
|
5229
|
-
|
|
5230
|
-
|
|
5271
|
+
if (options.nonInteractive || process.env.AGENT_VIBES_NON_INTERACTIVE === '1') {
|
|
5272
|
+
process.stderr.write(`[AV ERROR] Installation failed: ${error.message}\n`);
|
|
5273
|
+
} else {
|
|
5274
|
+
spinner.fail('Installation failed!');
|
|
5275
|
+
console.error(chalk.red('\n❌ Error:'), error.message);
|
|
5276
|
+
}
|
|
5231
5277
|
process.exit(1);
|
|
5232
5278
|
}
|
|
5233
5279
|
}
|
|
@@ -5242,7 +5288,15 @@ program
|
|
|
5242
5288
|
.description('Install AgentVibes voice commands')
|
|
5243
5289
|
.option('-d, --directory <path>', 'Installation directory (default: current directory)')
|
|
5244
5290
|
.option('-y, --yes', 'Skip confirmation prompt (auto-confirm)')
|
|
5291
|
+
.option('--non-interactive', 'Skip TUI and install with defaults (for AI agents and CI pipelines). Also triggered by AGENT_VIBES_NON_INTERACTIVE=1 env var.')
|
|
5245
5292
|
.action(async (options) => {
|
|
5293
|
+
// Merge env var trigger into options
|
|
5294
|
+
if (process.env.AGENT_VIBES_NON_INTERACTIVE === '1') {
|
|
5295
|
+
options.nonInteractive = true;
|
|
5296
|
+
}
|
|
5297
|
+
if (options.nonInteractive) {
|
|
5298
|
+
options.yes = true;
|
|
5299
|
+
}
|
|
5246
5300
|
await install(options);
|
|
5247
5301
|
});
|
|
5248
5302
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LanguageService — single source of truth for the selected UI language.
|
|
3
|
+
*
|
|
4
|
+
* Persists the selection to ~/.claude/config/language.txt so it survives
|
|
5
|
+
* process restarts. Notifies registered listeners on every change so the
|
|
6
|
+
* TUI can re-render dynamic labels immediately.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import { t, SUPPORTED_LANGUAGES } from '../i18n/strings.js';
|
|
13
|
+
|
|
14
|
+
const LANG_FILE = path.join(os.homedir(), '.claude', 'config', 'language.txt');
|
|
15
|
+
const VALID_LANGS = new Set(SUPPORTED_LANGUAGES.map(l => l.value));
|
|
16
|
+
|
|
17
|
+
export class LanguageService {
|
|
18
|
+
constructor() {
|
|
19
|
+
this._lang = this._load();
|
|
20
|
+
this._listeners = [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
_load() {
|
|
24
|
+
try {
|
|
25
|
+
const val = fs.readFileSync(LANG_FILE, 'utf8').trim();
|
|
26
|
+
return VALID_LANGS.has(val) ? val : 'en';
|
|
27
|
+
} catch {
|
|
28
|
+
return 'en';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getLang() { return this._lang; }
|
|
33
|
+
|
|
34
|
+
setLang(lang) {
|
|
35
|
+
if (!VALID_LANGS.has(lang)) return;
|
|
36
|
+
this._lang = lang;
|
|
37
|
+
try {
|
|
38
|
+
fs.mkdirSync(path.dirname(LANG_FILE), { recursive: true });
|
|
39
|
+
fs.writeFileSync(LANG_FILE, lang, { mode: 0o600 });
|
|
40
|
+
} catch { /* non-fatal */ }
|
|
41
|
+
this._listeners.forEach(fn => fn(lang));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onChange(fn) { this._listeners.push(fn); }
|
|
45
|
+
|
|
46
|
+
t(key) { return t(this._lang, key); }
|
|
47
|
+
}
|
|
@@ -250,9 +250,9 @@ function checkIsNetworkMount(filePath) {
|
|
|
250
250
|
|
|
251
251
|
if (process.platform === 'win32') {
|
|
252
252
|
// Windows: check for UNC paths (\\server\share) or mapped drives
|
|
253
|
-
//
|
|
253
|
+
// Coerce to boolean — match() returns array|null, not boolean
|
|
254
254
|
return resolvedPath.startsWith('\\\\') ||
|
|
255
|
-
resolvedPath.match(/^[A-Z]:\\[^\\]*\\netshare/i);
|
|
255
|
+
!!resolvedPath.match(/^[A-Z]:\\[^\\]*\\netshare/i);
|
|
256
256
|
} else {
|
|
257
257
|
// Unix: check for common network mount prefixes
|
|
258
258
|
// /mnt, /media, NFS mount points
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Validates TTS provider availability at installation, switch, and runtime
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { execSync } from 'node:child_process';
|
|
7
6
|
import { spawnSync } from 'node:child_process';
|
|
8
7
|
import path from 'node:path'; // For safe path operations and traversal prevention
|
|
9
8
|
import fs from 'node:fs'; // For checking file/directory existence
|
|
@@ -15,16 +14,12 @@ import os from 'node:os'; // For os.homedir() to prevent HOME injection attacks
|
|
|
15
14
|
* @returns {boolean} True if command exists in PATH
|
|
16
15
|
*/
|
|
17
16
|
function commandExistsInPath(command) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return true;
|
|
25
|
-
} catch {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
17
|
+
// SECURITY: Use spawnSync instead of execSync+shell to prevent command injection (#126)
|
|
18
|
+
const result = spawnSync('which', [command], {
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
21
|
+
});
|
|
22
|
+
return result.status === 0;
|
|
28
23
|
}
|
|
29
24
|
|
|
30
25
|
/**
|
|
@@ -180,11 +175,12 @@ export async function validateMacOSProvider() {
|
|
|
180
175
|
}
|
|
181
176
|
|
|
182
177
|
try {
|
|
183
|
-
execSync
|
|
178
|
+
// SECURITY: Use spawnSync instead of execSync+shell to prevent command injection (#126)
|
|
179
|
+
const result = spawnSync('which', ['say'], {
|
|
184
180
|
encoding: 'utf8',
|
|
185
|
-
shell: true,
|
|
186
181
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
187
182
|
});
|
|
183
|
+
if (result.status !== 0) throw new Error('say not found');
|
|
188
184
|
return { installed: true, message: 'macOS Say detected' };
|
|
189
185
|
} catch (error) {
|
|
190
186
|
// say command not found
|