verbalcoding 0.2.9 → 0.2.11
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/app-node/cli_install.test.mjs +12 -0
- package/app-node/install_config.mjs +4 -1
- package/package.json +1 -1
- package/scripts/cli.mjs +9 -0
- package/scripts/doctor.mjs +1 -1
- package/scripts/install.mjs +94 -0
|
@@ -30,7 +30,10 @@ test('CLI includes npm-friendly setup and start commands', () => {
|
|
|
30
30
|
const cli = fs.readFileSync(path.join(ROOT, 'scripts', 'cli.mjs'), 'utf8');
|
|
31
31
|
|
|
32
32
|
assert.match(cli, /vc setup \[--yes\]/);
|
|
33
|
+
assert.match(cli, /vc setup token \[bot-token\]/);
|
|
34
|
+
assert.match(cli, /vc setup channels \[voice-channel/);
|
|
33
35
|
assert.match(cli, /command === 'setup'/);
|
|
36
|
+
assert.match(cli, /install\.mjs'\), \.\.\.argv\.slice\(1\)/);
|
|
34
37
|
assert.match(cli, /VERBALCODING_SKIP_CLI_LINK/);
|
|
35
38
|
assert.match(cli, /command === 'start'/);
|
|
36
39
|
assert.match(cli, /run\.sh/);
|
|
@@ -53,8 +56,15 @@ test('npm setup supports non-interactive --yes mode', () => {
|
|
|
53
56
|
const config = fs.readFileSync(path.join(ROOT, 'app-node', 'install_config.mjs'), 'utf8');
|
|
54
57
|
|
|
55
58
|
assert.match(installer, /args\.includes\('--yes'\)/);
|
|
59
|
+
assert.match(installer, /configureDiscordToken/);
|
|
60
|
+
assert.match(installer, /configureAutoJoinChannels/);
|
|
61
|
+
assert.match(installer, /DISCORD_BOT_TOKEN: token/);
|
|
62
|
+
assert.match(installer, /AUTO_JOIN_VOICE_CHANNELS: channels/);
|
|
63
|
+
assert.match(installer, /vc setup token/);
|
|
64
|
+
assert.match(installer, /vc setup channels/);
|
|
56
65
|
assert.match(installer, /normalizeInstallAnswers\(process\.env\)/);
|
|
57
66
|
assert.match(config, /vc start/);
|
|
67
|
+
assert.match(config, /vc setup channels/);
|
|
58
68
|
assert.doesNotMatch(config, /npm install -g \.\s+#/);
|
|
59
69
|
});
|
|
60
70
|
|
|
@@ -86,6 +96,7 @@ test('doctor auto-bootstraps fixable prerequisites by default', () => {
|
|
|
86
96
|
assert.match(doctor, /NousResearch\/hermes-agent\/main\/scripts\/install\.sh/);
|
|
87
97
|
assert.match(doctor, /VERBALCODING_DOCTOR_INSTALL_HERMES/);
|
|
88
98
|
assert.match(doctor, /Discord bot setup:/);
|
|
99
|
+
assert.match(doctor, /vc setup token/);
|
|
89
100
|
assert.match(doctor, /discord\.com\/developers\/applications/);
|
|
90
101
|
assert.match(cli, /doctor\.mjs'\), \.\.\.argv\.slice\(1\)/);
|
|
91
102
|
});
|
|
@@ -99,6 +110,7 @@ test('setup summary guides Discord app creation and records client id', () => {
|
|
|
99
110
|
assert.match(config, /Discord app setup:/);
|
|
100
111
|
assert.match(config, /https:\/\/discord\.com\/developers\/applications/);
|
|
101
112
|
assert.match(config, /vc bot invite <client-id>/);
|
|
113
|
+
assert.match(config, /vc setup token/);
|
|
102
114
|
assert.match(config, /buildDiscordBotInviteUrl\(\{ clientId: values\.DISCORD_CLIENT_ID \}\)/);
|
|
103
115
|
});
|
|
104
116
|
|
|
@@ -252,12 +252,15 @@ export function renderInstallSummary(values = {}) {
|
|
|
252
252
|
'Discord app setup:',
|
|
253
253
|
' 1. Create an app: https://discord.com/developers/applications',
|
|
254
254
|
' 2. Bot tab: Add Bot, enable Message Content Intent, copy/reset the token.',
|
|
255
|
-
' 3.
|
|
255
|
+
' 3. Register the token with `vc setup token` (or `vc setup token <token>`).',
|
|
256
256
|
inviteUrl ? ` 4. Invite URL: ${inviteUrl}` : ' 4. Invite URL: vc bot invite <client-id>',
|
|
257
257
|
' 5. Make sure the bot can read/send text and connect/speak in voice.',
|
|
258
|
+
'You may skip the token now and run `vc setup token` anytime later.',
|
|
258
259
|
'',
|
|
259
260
|
'Next commands:',
|
|
260
261
|
' vc doctor',
|
|
262
|
+
' vc setup token # register/update Discord bot token when ready',
|
|
263
|
+
' vc setup channels # set auto-join voice channel names',
|
|
261
264
|
' vc start',
|
|
262
265
|
'',
|
|
263
266
|
'Legacy project-local equivalents still work:',
|
package/package.json
CHANGED
package/scripts/cli.mjs
CHANGED
|
@@ -31,6 +31,8 @@ function usage() {
|
|
|
31
31
|
|
|
32
32
|
Usage:
|
|
33
33
|
vc setup [--yes] [--no-wizard] [--skip-system] [--skip-model] [--skip-edge-tts]
|
|
34
|
+
vc setup token [bot-token] [--client-id <client-id>]
|
|
35
|
+
vc setup channels [voice-channel[,voice-channel...]]
|
|
34
36
|
vc start
|
|
35
37
|
vc status
|
|
36
38
|
vc language <ko|en|auto>
|
|
@@ -48,6 +50,8 @@ Usage:
|
|
|
48
50
|
Examples:
|
|
49
51
|
npx verbalcoding setup --yes
|
|
50
52
|
vc setup --yes
|
|
53
|
+
vc setup token
|
|
54
|
+
vc setup channels "General,Team Voice"
|
|
51
55
|
vc start
|
|
52
56
|
vc language en
|
|
53
57
|
vc language ko
|
|
@@ -275,6 +279,11 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
275
279
|
}
|
|
276
280
|
if (command === 'setup' || command === 'install') {
|
|
277
281
|
const { spawnSync } = await import('node:child_process');
|
|
282
|
+
if (['token', 'discord', 'bot-token', 'channels', 'channel', 'voice'].includes(argv[1])) {
|
|
283
|
+
const result = spawnSync(process.execPath, [path.join(ROOT, 'scripts', 'install.mjs'), ...argv.slice(1)], { stdio: 'inherit', cwd: ROOT });
|
|
284
|
+
process.exitCode = result.status ?? 1;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
278
287
|
const script = path.join(ROOT, 'scripts', 'install.sh');
|
|
279
288
|
const result = spawnSync('bash', [script, ...argv.slice(1)], {
|
|
280
289
|
stdio: 'inherit',
|
package/scripts/doctor.mjs
CHANGED
|
@@ -141,7 +141,7 @@ function discordSetupGuidance(env) {
|
|
|
141
141
|
'Discord bot setup:',
|
|
142
142
|
' 1. Open https://discord.com/developers/applications and create an application.',
|
|
143
143
|
' 2. Bot tab: Add Bot, enable Message Content Intent, then Reset/Copy Token.',
|
|
144
|
-
` 3.
|
|
144
|
+
` 3. Register it with \`vc setup token\` (or \`vc setup token <token>\`).`,
|
|
145
145
|
' 4. OAuth2 tab: copy the Application/Client ID.',
|
|
146
146
|
clientId
|
|
147
147
|
? ` 5. Invite the bot: vc bot invite ${clientId}`
|
package/scripts/install.mjs
CHANGED
|
@@ -15,8 +15,102 @@ async function ask(question, fallback = '', options = {}) {
|
|
|
15
15
|
return answer || fallback;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function quoteEnv(value) {
|
|
19
|
+
return JSON.stringify(String(value ?? ''));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function upsertEnvFile(file, updates) {
|
|
23
|
+
const existing = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
const lines = existing.split(/\r?\n/).map(raw => {
|
|
26
|
+
const line = raw.trim();
|
|
27
|
+
if (!line || line.startsWith('#') || !line.includes('=')) return raw;
|
|
28
|
+
const idx = line.indexOf('=');
|
|
29
|
+
const key = line.slice(0, idx).trim().replace(/^export\s+/, '');
|
|
30
|
+
if (!(key in updates)) return raw;
|
|
31
|
+
seen.add(key);
|
|
32
|
+
return `${key}=${quoteEnv(updates[key])}`;
|
|
33
|
+
});
|
|
34
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
35
|
+
if (!seen.has(key)) lines.push(`${key}=${quoteEnv(value)}`);
|
|
36
|
+
}
|
|
37
|
+
const text = `${lines.filter((line, index, arr) => line !== '' || index < arr.length - 1).join('\n')}\n`;
|
|
38
|
+
fs.writeFileSync(file, text, { mode: 0o600 });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function argValue(args, name) {
|
|
42
|
+
const idx = args.indexOf(name);
|
|
43
|
+
if (idx < 0) return '';
|
|
44
|
+
const value = args[idx + 1] || '';
|
|
45
|
+
return value.startsWith('--') ? '' : value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function configureDiscordToken(args) {
|
|
49
|
+
const envPath = path.join(ROOT, '.env');
|
|
50
|
+
const tokenArg = args.find((arg, idx) => idx > 0 && !arg.startsWith('--')) || argValue(args, '--token');
|
|
51
|
+
const clientIdArg = argValue(args, '--client-id') || argValue(args, '--application-id');
|
|
52
|
+
let token = tokenArg || process.env.DISCORD_BOT_TOKEN || '';
|
|
53
|
+
let clientId = clientIdArg || process.env.DISCORD_CLIENT_ID || '';
|
|
54
|
+
if (!token) {
|
|
55
|
+
globalThis.__rl = readline.createInterface({ input, output });
|
|
56
|
+
try {
|
|
57
|
+
console.log('Discord bot token setup');
|
|
58
|
+
console.log('Create/copy the token at https://discord.com/developers/applications → your app → Bot.');
|
|
59
|
+
token = await ask('Discord bot token (DISCORD_BOT_TOKEN)', '', { fallbackLabel: '' });
|
|
60
|
+
clientId = await ask('Discord application/client ID for invite URL (optional)', clientId, { fallbackLabel: clientId ? 'keep existing' : 'skip' });
|
|
61
|
+
} finally {
|
|
62
|
+
globalThis.__rl.close();
|
|
63
|
+
globalThis.__rl = null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!token) {
|
|
67
|
+
console.error('No Discord bot token provided. Nothing changed.');
|
|
68
|
+
process.exitCode = 2;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const updates = { DISCORD_BOT_TOKEN: token };
|
|
72
|
+
if (clientId) updates.DISCORD_CLIENT_ID = clientId;
|
|
73
|
+
upsertEnvFile(envPath, updates);
|
|
74
|
+
console.log(`Updated ${envPath}`);
|
|
75
|
+
console.log('Discord bot token saved. Run `vc doctor` to verify. You can update it anytime with `vc setup token`.');
|
|
76
|
+
if (clientId) console.log(`Invite URL: vc bot invite ${clientId}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function configureAutoJoinChannels(args) {
|
|
80
|
+
const envPath = path.join(ROOT, '.env');
|
|
81
|
+
let channels = args.find((arg, idx) => idx > 0 && !arg.startsWith('--')) || argValue(args, '--channels') || argValue(args, '--voice');
|
|
82
|
+
if (!channels) {
|
|
83
|
+
globalThis.__rl = readline.createInterface({ input, output });
|
|
84
|
+
try {
|
|
85
|
+
console.log('Discord auto-join voice channel setup');
|
|
86
|
+
console.log('Enter one or more voice channel names, comma-separated. Example: General,Team Voice,일반');
|
|
87
|
+
channels = await ask('Auto-join voice channel names', process.env.AUTO_JOIN_VOICE_CHANNELS || 'General,general');
|
|
88
|
+
} finally {
|
|
89
|
+
globalThis.__rl.close();
|
|
90
|
+
globalThis.__rl = null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!channels) {
|
|
94
|
+
console.error('No voice channel names provided. Nothing changed.');
|
|
95
|
+
process.exitCode = 2;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
upsertEnvFile(envPath, { AUTO_JOIN_VOICE_CHANNELS: channels });
|
|
99
|
+
console.log(`Updated ${envPath}`);
|
|
100
|
+
console.log(`Auto-join voice channels: ${channels}`);
|
|
101
|
+
console.log('Restart the bridge for this to take effect. You can update it anytime with `vc setup channels`.');
|
|
102
|
+
}
|
|
103
|
+
|
|
18
104
|
async function main() {
|
|
19
105
|
const args = process.argv.slice(2);
|
|
106
|
+
if (args[0] === 'token' || args[0] === 'discord' || args[0] === 'bot-token') {
|
|
107
|
+
await configureDiscordToken(args);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (args[0] === 'channels' || args[0] === 'channel' || args[0] === 'voice') {
|
|
111
|
+
await configureAutoJoinChannels(args);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
20
114
|
const yes = args.includes('--yes') || args.includes('-y');
|
|
21
115
|
if (args[0] === 'instance' || args.includes('--instance')) {
|
|
22
116
|
const { spawnSync } = await import('node:child_process');
|