verbalcoding 0.2.9 → 0.2.10

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.
@@ -30,7 +30,9 @@ 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\]/);
33
34
  assert.match(cli, /command === 'setup'/);
35
+ assert.match(cli, /install\.mjs'\), \.\.\.argv\.slice\(1\)/);
34
36
  assert.match(cli, /VERBALCODING_SKIP_CLI_LINK/);
35
37
  assert.match(cli, /command === 'start'/);
36
38
  assert.match(cli, /run\.sh/);
@@ -53,6 +55,9 @@ test('npm setup supports non-interactive --yes mode', () => {
53
55
  const config = fs.readFileSync(path.join(ROOT, 'app-node', 'install_config.mjs'), 'utf8');
54
56
 
55
57
  assert.match(installer, /args\.includes\('--yes'\)/);
58
+ assert.match(installer, /configureDiscordToken/);
59
+ assert.match(installer, /DISCORD_BOT_TOKEN: token/);
60
+ assert.match(installer, /vc setup token/);
56
61
  assert.match(installer, /normalizeInstallAnswers\(process\.env\)/);
57
62
  assert.match(config, /vc start/);
58
63
  assert.doesNotMatch(config, /npm install -g \.\s+#/);
@@ -86,6 +91,7 @@ test('doctor auto-bootstraps fixable prerequisites by default', () => {
86
91
  assert.match(doctor, /NousResearch\/hermes-agent\/main\/scripts\/install\.sh/);
87
92
  assert.match(doctor, /VERBALCODING_DOCTOR_INSTALL_HERMES/);
88
93
  assert.match(doctor, /Discord bot setup:/);
94
+ assert.match(doctor, /vc setup token/);
89
95
  assert.match(doctor, /discord\.com\/developers\/applications/);
90
96
  assert.match(cli, /doctor\.mjs'\), \.\.\.argv\.slice\(1\)/);
91
97
  });
@@ -99,6 +105,7 @@ test('setup summary guides Discord app creation and records client id', () => {
99
105
  assert.match(config, /Discord app setup:/);
100
106
  assert.match(config, /https:\/\/discord\.com\/developers\/applications/);
101
107
  assert.match(config, /vc bot invite <client-id>/);
108
+ assert.match(config, /vc setup token/);
102
109
  assert.match(config, /buildDiscordBotInviteUrl\(\{ clientId: values\.DISCORD_CLIENT_ID \}\)/);
103
110
  });
104
111
 
@@ -252,9 +252,10 @@ 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. Put the token in .env as DISCORD_BOT_TOKEN="...".',
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',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "verbalcoding",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "Discord voice bridge for CLI coding agents.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/scripts/cli.mjs CHANGED
@@ -31,6 +31,7 @@ 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>]
34
35
  vc start
35
36
  vc status
36
37
  vc language <ko|en|auto>
@@ -48,6 +49,7 @@ Usage:
48
49
  Examples:
49
50
  npx verbalcoding setup --yes
50
51
  vc setup --yes
52
+ vc setup token
51
53
  vc start
52
54
  vc language en
53
55
  vc language ko
@@ -275,6 +277,11 @@ async function main(argv = process.argv.slice(2)) {
275
277
  }
276
278
  if (command === 'setup' || command === 'install') {
277
279
  const { spawnSync } = await import('node:child_process');
280
+ if (argv[1] === 'token' || argv[1] === 'discord' || argv[1] === 'bot-token') {
281
+ const result = spawnSync(process.execPath, [path.join(ROOT, 'scripts', 'install.mjs'), ...argv.slice(1)], { stdio: 'inherit', cwd: ROOT });
282
+ process.exitCode = result.status ?? 1;
283
+ return;
284
+ }
278
285
  const script = path.join(ROOT, 'scripts', 'install.sh');
279
286
  const result = spawnSync('bash', [script, ...argv.slice(1)], {
280
287
  stdio: 'inherit',
@@ -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. Save it in ${path.join(ROOT, '.env')} as DISCORD_BOT_TOKEN="...".`,
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}`
@@ -15,8 +15,73 @@ 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
+
18
79
  async function main() {
19
80
  const args = process.argv.slice(2);
81
+ if (args[0] === 'token' || args[0] === 'discord' || args[0] === 'bot-token') {
82
+ await configureDiscordToken(args);
83
+ return;
84
+ }
20
85
  const yes = args.includes('--yes') || args.includes('-y');
21
86
  if (args[0] === 'instance' || args.includes('--instance')) {
22
87
  const { spawnSync } = await import('node:child_process');