verbalcoding 0.2.6 → 0.2.8
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/README.md +12 -22
- package/app-node/cli_install.test.mjs +15 -0
- package/docs/FRESH_INSTALL.md +8 -2
- package/docs/assets/figures/verbalcoding-flow.svg +45 -30
- package/docs/i18n/CONFIGURATION.es.md +239 -0
- package/docs/i18n/CONFIGURATION.fr.md +239 -0
- package/docs/i18n/CONFIGURATION.ja.md +239 -0
- package/docs/i18n/CONFIGURATION.ko.md +66 -74
- package/docs/i18n/CONFIGURATION.ru.md +239 -0
- package/docs/i18n/CONFIGURATION.zh.md +239 -0
- package/docs/i18n/FRESH_INSTALL.es.md +207 -0
- package/docs/i18n/FRESH_INSTALL.fr.md +207 -0
- package/docs/i18n/FRESH_INSTALL.ja.md +207 -0
- package/docs/i18n/FRESH_INSTALL.ko.md +60 -54
- package/docs/i18n/FRESH_INSTALL.ru.md +207 -0
- package/docs/i18n/FRESH_INSTALL.zh.md +207 -0
- package/docs/i18n/MULTI_INSTANCE.es.md +180 -0
- package/docs/i18n/MULTI_INSTANCE.fr.md +180 -0
- package/docs/i18n/MULTI_INSTANCE.ja.md +179 -0
- package/docs/i18n/MULTI_INSTANCE.ko.md +46 -46
- package/docs/i18n/MULTI_INSTANCE.ru.md +179 -0
- package/docs/i18n/MULTI_INSTANCE.zh.md +179 -0
- package/docs/i18n/README.es.md +83 -55
- package/docs/i18n/README.fr.md +85 -57
- package/docs/i18n/README.ja.md +83 -55
- package/docs/i18n/README.ko.md +47 -56
- package/docs/i18n/README.ru.md +86 -58
- package/docs/i18n/README.zh.md +83 -56
- package/docs/i18n/RELEASE.es.md +74 -0
- package/docs/i18n/RELEASE.fr.md +74 -0
- package/docs/i18n/RELEASE.ja.md +74 -0
- package/docs/i18n/RELEASE.ko.md +38 -36
- package/docs/i18n/RELEASE.ru.md +74 -0
- package/docs/i18n/RELEASE.zh.md +74 -0
- package/docs/i18n/USAGE.es.md +161 -0
- package/docs/i18n/USAGE.fr.md +161 -0
- package/docs/i18n/USAGE.ja.md +161 -0
- package/docs/i18n/USAGE.ko.md +61 -72
- package/docs/i18n/USAGE.ru.md +161 -0
- package/docs/i18n/USAGE.zh.md +161 -0
- package/package.json +1 -1
- package/scripts/bootstrap_prereqs.sh +15 -3
- package/scripts/cli.mjs +1 -1
- package/scripts/doctor.mjs +114 -8
package/scripts/doctor.mjs
CHANGED
|
@@ -7,6 +7,9 @@ import { checkInstanceConfigs, formatInstanceDoctor } from '../app-node/instance
|
|
|
7
7
|
import { autoRestartVoiceBotEnabled } from '../app-node/restart_policy.mjs';
|
|
8
8
|
|
|
9
9
|
const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const autoFixEnabled = !args.includes('--no-fix') && !['0', 'false', 'no', 'off'].includes(String(process.env.VERBALCODING_DOCTOR_AUTO_FIX || '1').toLowerCase());
|
|
12
|
+
let autoFixAttempted = false;
|
|
10
13
|
|
|
11
14
|
function readEnvFile(file) {
|
|
12
15
|
try {
|
|
@@ -25,14 +28,56 @@ function mergeEnv() {
|
|
|
25
28
|
};
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
function quoteEnv(value) {
|
|
32
|
+
return JSON.stringify(String(value ?? ''));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function upsertEnvFile(file, updates) {
|
|
36
|
+
const existing = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
const lines = existing.split(/\r?\n/).map(raw => {
|
|
39
|
+
const line = raw.trim();
|
|
40
|
+
if (!line || line.startsWith('#') || !line.includes('=')) return raw;
|
|
41
|
+
const idx = line.indexOf('=');
|
|
42
|
+
const key = line.slice(0, idx).trim().replace(/^export\s+/, '');
|
|
43
|
+
if (!(key in updates)) return raw;
|
|
44
|
+
seen.add(key);
|
|
45
|
+
return `${key}=${quoteEnv(updates[key])}`;
|
|
46
|
+
});
|
|
47
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
48
|
+
if (!seen.has(key)) lines.push(`${key}=${quoteEnv(value)}`);
|
|
49
|
+
}
|
|
50
|
+
const text = `${lines.filter((line, index, arr) => line !== '' || index < arr.length - 1).join('\n')}\n`;
|
|
51
|
+
fs.writeFileSync(file, text, { mode: 0o600 });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isExecutable(file) {
|
|
55
|
+
try {
|
|
56
|
+
fs.accessSync(file, fs.constants.X_OK);
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
28
63
|
function commandExists(command) {
|
|
29
64
|
const result = spawnSync('bash', ['-lc', `command -v ${JSON.stringify(command)}`], {
|
|
30
65
|
cwd: ROOT,
|
|
31
66
|
encoding: 'utf8',
|
|
67
|
+
env: { ...process.env, PATH: `${path.join(ROOT, '.local', 'bin')}:${process.env.PATH || ''}` },
|
|
32
68
|
});
|
|
33
69
|
return result.status === 0 ? result.stdout.trim() : '';
|
|
34
70
|
}
|
|
35
71
|
|
|
72
|
+
function resolveCommand(command, fallbackPaths = []) {
|
|
73
|
+
const found = commandExists(command);
|
|
74
|
+
if (found) return found;
|
|
75
|
+
for (const candidate of fallbackPaths) {
|
|
76
|
+
if (isExecutable(candidate)) return candidate;
|
|
77
|
+
}
|
|
78
|
+
return '';
|
|
79
|
+
}
|
|
80
|
+
|
|
36
81
|
function check(label, ok, detail = '') {
|
|
37
82
|
const mark = ok ? '✓' : '✗';
|
|
38
83
|
console.log(`${mark} ${label}${detail ? ` — ${detail}` : ''}`);
|
|
@@ -43,7 +88,60 @@ function note(label, detail = '') {
|
|
|
43
88
|
console.log(`• ${label}${detail ? ` — ${detail}` : ''}`);
|
|
44
89
|
}
|
|
45
90
|
|
|
46
|
-
|
|
91
|
+
function fixablePrerequisites(env) {
|
|
92
|
+
const ttsBackend = (env.TTS_BACKEND || 'edge').toLowerCase();
|
|
93
|
+
const missing = [];
|
|
94
|
+
if (!commandExists('ffmpeg')) missing.push('ffmpeg');
|
|
95
|
+
if (!resolveCommand(env.WHISPER_CPP_BIN || 'whisper-cli', [path.join(ROOT, '.local', 'bin', 'whisper-cli')])) missing.push('whisper-cli');
|
|
96
|
+
const modelPath = path.resolve(ROOT, env.WHISPER_CPP_MODEL || 'models/ggml-small-q5_1.bin');
|
|
97
|
+
if (!fs.existsSync(modelPath)) missing.push('whisper.cpp model');
|
|
98
|
+
if (ttsBackend === 'edge') {
|
|
99
|
+
const edgeCommand = env.EDGE_TTS_COMMAND || env.TTS_EDGE_COMMAND || 'edge-tts';
|
|
100
|
+
if (!resolveCommand(edgeCommand, [path.join(ROOT, '.venv-tts', 'bin', 'edge-tts')])) missing.push('edge-tts');
|
|
101
|
+
}
|
|
102
|
+
return missing;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function persistDiscoveredLocalHelpers(env) {
|
|
106
|
+
const updates = {};
|
|
107
|
+
const localWhisper = path.join(ROOT, '.local', 'bin', 'whisper-cli');
|
|
108
|
+
if (!commandExists(env.WHISPER_CPP_BIN || 'whisper-cli') && isExecutable(localWhisper)) {
|
|
109
|
+
updates.WHISPER_CPP_BIN = localWhisper;
|
|
110
|
+
}
|
|
111
|
+
const ttsBackend = (env.TTS_BACKEND || 'edge').toLowerCase();
|
|
112
|
+
const localEdge = path.join(ROOT, '.venv-tts', 'bin', 'edge-tts');
|
|
113
|
+
const edgeCommand = env.EDGE_TTS_COMMAND || env.TTS_EDGE_COMMAND || 'edge-tts';
|
|
114
|
+
if (ttsBackend === 'edge' && !commandExists(edgeCommand) && isExecutable(localEdge)) {
|
|
115
|
+
updates.EDGE_TTS_COMMAND = localEdge;
|
|
116
|
+
}
|
|
117
|
+
if (Object.keys(updates).length > 0) {
|
|
118
|
+
upsertEnvFile(path.join(ROOT, '.env'), updates);
|
|
119
|
+
return updates;
|
|
120
|
+
}
|
|
121
|
+
return {};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let env = mergeEnv();
|
|
125
|
+
const missingBeforeFix = fixablePrerequisites(env);
|
|
126
|
+
if (autoFixEnabled && missingBeforeFix.length > 0) {
|
|
127
|
+
console.log(`VerbalCoding doctor: missing ${missingBeforeFix.join(', ')}; running automatic prerequisite bootstrap...`);
|
|
128
|
+
const result = spawnSync('bash', [path.join(ROOT, 'scripts', 'bootstrap_prereqs.sh'), '--yes'], {
|
|
129
|
+
cwd: ROOT,
|
|
130
|
+
stdio: 'inherit',
|
|
131
|
+
env: { ...process.env, VERBALCODING_SKIP_CLI_LINK: process.env.VERBALCODING_SKIP_CLI_LINK || '1' },
|
|
132
|
+
});
|
|
133
|
+
autoFixAttempted = true;
|
|
134
|
+
const persisted = persistDiscoveredLocalHelpers(mergeEnv());
|
|
135
|
+
if (Object.keys(persisted).length > 0) {
|
|
136
|
+
console.log(`Doctor recorded local helper paths in .env: ${Object.keys(persisted).join(', ')}`);
|
|
137
|
+
}
|
|
138
|
+
if (result.status !== 0) {
|
|
139
|
+
console.log(`Doctor bootstrap exited with status ${result.status}. Continuing with checks.`);
|
|
140
|
+
}
|
|
141
|
+
console.log('');
|
|
142
|
+
env = mergeEnv();
|
|
143
|
+
}
|
|
144
|
+
|
|
47
145
|
const backend = (env.AGENT_BACKEND || 'hermes').toLowerCase();
|
|
48
146
|
const ttsBackend = (env.TTS_BACKEND || 'edge').toLowerCase();
|
|
49
147
|
let ok = true;
|
|
@@ -52,12 +150,18 @@ console.log('VerbalCoding doctor');
|
|
|
52
150
|
console.log(`Project: ${ROOT}`);
|
|
53
151
|
console.log(`Backend: ${backend}`);
|
|
54
152
|
console.log(`TTS backend: ${ttsBackend}`);
|
|
153
|
+
if (!autoFixEnabled) note('Automatic prerequisite bootstrap', 'off');
|
|
154
|
+
if (autoFixAttempted) note('Automatic prerequisite bootstrap', 'attempted');
|
|
55
155
|
console.log('');
|
|
56
156
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
157
|
+
const nodeCommand = commandExists('node');
|
|
158
|
+
const npmCommand = commandExists('npm');
|
|
159
|
+
const ffmpegCommand = commandExists('ffmpeg');
|
|
160
|
+
const whisperCommand = resolveCommand(env.WHISPER_CPP_BIN || 'whisper-cli', [path.join(ROOT, '.local', 'bin', 'whisper-cli')]);
|
|
161
|
+
ok = check('Node.js', nodeCommand, nodeCommand || 'missing') && ok;
|
|
162
|
+
ok = check('npm', npmCommand, npmCommand || 'missing') && ok;
|
|
163
|
+
ok = check('ffmpeg', ffmpegCommand, ffmpegCommand || 'missing') && ok;
|
|
164
|
+
ok = check('whisper-cli', whisperCommand, whisperCommand || 'missing') && ok;
|
|
61
165
|
|
|
62
166
|
const modelPath = path.resolve(ROOT, env.WHISPER_CPP_MODEL || 'models/ggml-small-q5_1.bin');
|
|
63
167
|
ok = check('whisper.cpp model', fs.existsSync(modelPath), path.relative(ROOT, modelPath)) && ok;
|
|
@@ -77,7 +181,8 @@ if (!['edge', 'openvoice', 'speechswift', 'supertonic'].includes(ttsBackend)) {
|
|
|
77
181
|
}
|
|
78
182
|
if (ttsBackend === 'edge') {
|
|
79
183
|
const edgeCommand = env.EDGE_TTS_COMMAND || env.TTS_EDGE_COMMAND || 'edge-tts';
|
|
80
|
-
|
|
184
|
+
const edgeFound = resolveCommand(edgeCommand, [path.join(ROOT, '.venv-tts', 'bin', 'edge-tts')]);
|
|
185
|
+
ok = check('edge-tts', edgeFound, edgeFound || 'missing') && ok;
|
|
81
186
|
} else if (ttsBackend === 'openvoice') {
|
|
82
187
|
ok = check('Python for OpenVoice', commandExists('python3'), commandExists('python3') || 'missing') && ok;
|
|
83
188
|
const openvoiceDir = path.resolve(ROOT, env.OPENVOICE_DIR || './vendor/OpenVoice');
|
|
@@ -127,8 +232,9 @@ ok = instanceResult.errors.length === 0 && ok;
|
|
|
127
232
|
|
|
128
233
|
console.log('');
|
|
129
234
|
if (ok) {
|
|
130
|
-
console.log('Doctor passed. Run
|
|
235
|
+
console.log('Doctor passed. Run vc start to start VerbalCoding.');
|
|
131
236
|
} else {
|
|
132
|
-
|
|
237
|
+
const suffix = autoFixEnabled ? 'Fix the remaining ✗ items, then rerun vc doctor.' : 'Fix the ✗ items, then rerun vc doctor.';
|
|
238
|
+
console.log(`Doctor found missing prerequisites. ${suffix}`);
|
|
133
239
|
process.exitCode = 1;
|
|
134
240
|
}
|