verbalcoding 0.2.10 → 0.2.12

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.
Files changed (94) hide show
  1. package/.env.example +27 -1
  2. package/README.es.md +132 -0
  3. package/README.fr.md +132 -0
  4. package/README.ja.md +132 -0
  5. package/README.ko.md +132 -0
  6. package/README.md +116 -74
  7. package/README.ru.md +132 -0
  8. package/README.zh.md +131 -0
  9. package/app-node/agent_adapters.mjs +37 -5
  10. package/app-node/agent_adapters.test.mjs +13 -1
  11. package/app-node/agent_detect.mjs +73 -0
  12. package/app-node/agent_detect.test.mjs +77 -0
  13. package/app-node/cli_install.test.mjs +5 -0
  14. package/app-node/install_config.mjs +5 -0
  15. package/app-node/main.mjs +339 -4
  16. package/app-node/notify.mjs +73 -0
  17. package/app-node/notify.test.mjs +68 -0
  18. package/app-node/plan_mode.mjs +174 -0
  19. package/app-node/plan_mode.test.mjs +153 -0
  20. package/app-node/smart_progress.mjs +94 -0
  21. package/app-node/smart_progress.test.mjs +66 -0
  22. package/app-node/stream_sentencer.mjs +61 -0
  23. package/app-node/stream_sentencer.test.mjs +64 -0
  24. package/app-node/streaming_tts_queue.mjs +48 -0
  25. package/app-node/streaming_tts_queue.test.mjs +58 -0
  26. package/app-node/text_routing.mjs +20 -0
  27. package/app-node/text_routing.test.mjs +23 -1
  28. package/docs/CONFIGURATION.md +69 -96
  29. package/docs/FRESH_INSTALL.md +105 -63
  30. package/docs/HERMES_VOICE.md +65 -0
  31. package/docs/MULTI_INSTANCE.md +16 -0
  32. package/docs/README.md +49 -0
  33. package/docs/RELEASE.md +42 -19
  34. package/docs/ROADMAP.md +38 -0
  35. package/docs/TROUBLESHOOTING.md +126 -0
  36. package/docs/USAGE.md +72 -40
  37. package/docs/assets/figures/verbalcoding-flow.svg +1 -1
  38. package/docs/i18n/CONFIGURATION.es.md +25 -0
  39. package/docs/i18n/CONFIGURATION.fr.md +25 -0
  40. package/docs/i18n/CONFIGURATION.ja.md +25 -0
  41. package/docs/i18n/CONFIGURATION.ko.md +25 -0
  42. package/docs/i18n/CONFIGURATION.ru.md +25 -0
  43. package/docs/i18n/CONFIGURATION.zh.md +25 -0
  44. package/docs/i18n/FRESH_INSTALL.es.md +27 -2
  45. package/docs/i18n/FRESH_INSTALL.fr.md +27 -2
  46. package/docs/i18n/FRESH_INSTALL.ja.md +27 -2
  47. package/docs/i18n/FRESH_INSTALL.ko.md +27 -2
  48. package/docs/i18n/FRESH_INSTALL.ru.md +27 -2
  49. package/docs/i18n/FRESH_INSTALL.zh.md +27 -2
  50. package/docs/i18n/HERMES_VOICE.es.md +46 -0
  51. package/docs/i18n/HERMES_VOICE.fr.md +46 -0
  52. package/docs/i18n/HERMES_VOICE.ja.md +46 -0
  53. package/docs/i18n/HERMES_VOICE.ko.md +65 -0
  54. package/docs/i18n/HERMES_VOICE.ru.md +46 -0
  55. package/docs/i18n/HERMES_VOICE.zh.md +46 -0
  56. package/docs/i18n/MULTI_INSTANCE.es.md +25 -0
  57. package/docs/i18n/MULTI_INSTANCE.fr.md +25 -0
  58. package/docs/i18n/MULTI_INSTANCE.ja.md +25 -0
  59. package/docs/i18n/MULTI_INSTANCE.ko.md +25 -0
  60. package/docs/i18n/MULTI_INSTANCE.ru.md +25 -0
  61. package/docs/i18n/MULTI_INSTANCE.zh.md +25 -0
  62. package/docs/i18n/README.es.md +20 -134
  63. package/docs/i18n/README.fr.md +20 -134
  64. package/docs/i18n/README.ja.md +20 -134
  65. package/docs/i18n/README.ko.md +20 -133
  66. package/docs/i18n/README.ru.md +20 -134
  67. package/docs/i18n/README.zh.md +20 -133
  68. package/docs/i18n/RELEASE.es.md +26 -1
  69. package/docs/i18n/RELEASE.fr.md +26 -1
  70. package/docs/i18n/RELEASE.ja.md +26 -1
  71. package/docs/i18n/RELEASE.ko.md +26 -1
  72. package/docs/i18n/RELEASE.ru.md +26 -1
  73. package/docs/i18n/RELEASE.zh.md +26 -1
  74. package/docs/i18n/TROUBLESHOOTING.es.md +39 -0
  75. package/docs/i18n/TROUBLESHOOTING.fr.md +39 -0
  76. package/docs/i18n/TROUBLESHOOTING.ja.md +39 -0
  77. package/docs/i18n/TROUBLESHOOTING.ko.md +39 -0
  78. package/docs/i18n/TROUBLESHOOTING.ru.md +39 -0
  79. package/docs/i18n/TROUBLESHOOTING.zh.md +39 -0
  80. package/docs/i18n/USAGE.es.md +25 -0
  81. package/docs/i18n/USAGE.fr.md +25 -0
  82. package/docs/i18n/USAGE.ja.md +25 -0
  83. package/docs/i18n/USAGE.ko.md +25 -0
  84. package/docs/i18n/USAGE.ru.md +25 -0
  85. package/docs/i18n/USAGE.zh.md +25 -0
  86. package/docs/superpowers/plans/2026-05-13-phase1-streaming-pipeline.md +122 -0
  87. package/docs/superpowers/plans/2026-05-13-phase10-push-notifications.md +152 -0
  88. package/docs/superpowers/plans/2026-05-13-phase2-agent-adapters.md +242 -0
  89. package/docs/superpowers/plans/2026-05-13-phase6-smart-progress.md +172 -0
  90. package/docs/superpowers/plans/2026-05-13-phase7-voice-plan-mode.md +108 -0
  91. package/package.json +2 -1
  92. package/scripts/cli.mjs +7 -4
  93. package/scripts/doctor.mjs +11 -0
  94. package/scripts/install.mjs +44 -1
@@ -0,0 +1,108 @@
1
+ # Phase 7 — Voice Plan Mode Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development or superpowers:executing-plans.
4
+
5
+ **Goal:** Let the user say "plan it first" and the agent returns a numbered plan, narrated step-by-step; the user can say "skip step 3", "add a test for X after step 2", or "approve" to run the modified plan.
6
+
7
+ **Architecture:** Stateful plan session in `app-node/plan_mode.mjs`. The first user request is wrapped with a planning preamble in `voiceBridgePrompt`. Agent returns a numbered plan in a `PLAN_BEGIN ... PLAN_END` envelope; we parse, narrate, and listen for follow-up voice commands. Commands mutate the plan; the approval command sends the modified plan back to the agent with an "act on this plan now" preamble.
8
+
9
+ **Tech Stack:** Node 20 ESM, existing adapter + sentencer, regex grammar (no external NLP).
10
+
11
+ ---
12
+
13
+ ## Spec
14
+
15
+ ### Plan envelope
16
+
17
+ Agent returns:
18
+ ```
19
+ PLAN_BEGIN
20
+ 1. <step>
21
+ 2. <step>
22
+ PLAN_END
23
+ ```
24
+
25
+ Parser strips envelope and yields `{ id, text, status }`.
26
+
27
+ ### Voice command grammar
28
+
29
+ - enter: `plan it first` / `plan first` / `먼저 계획`
30
+ - skip: `skip step N` / `step N 건너뛰어`
31
+ - insert: `add <text> after step N` / `step N 다음에 <text> 추가`
32
+ - approve: `approve` / `go ahead` / `실행`
33
+ - cancel: `cancel` / `취소`
34
+
35
+ ### Session state
36
+
37
+ `{ active: boolean, steps: Step[], language: 'en'|'ko' }` stored in `bridge_state.mjs` per channel.
38
+
39
+ ---
40
+
41
+ ## File Structure
42
+
43
+ - Create: `app-node/plan_mode.mjs`, `app-node/plan_mode.test.mjs`.
44
+ - Modify: `app-node/agent_adapters.mjs::voiceBridgePrompt` — add `planMode` branch.
45
+ - Modify: `app-node/discord_text.mjs` and `app-node/main.mjs` — route plan-mode utterances.
46
+
47
+ ---
48
+
49
+ ## Tasks
50
+
51
+ ### Task 1: TDD — parsers
52
+
53
+ - [ ] Write tests in `app-node/plan_mode.test.mjs`:
54
+ 1. `parsePlanOutput` extracts numbered steps between markers.
55
+ 2. `parseVoiceCommand` recognises skip (en + ko).
56
+ 3. `parseVoiceCommand` recognises insert (en + ko).
57
+ 4. `parseVoiceCommand` recognises approve in both languages.
58
+ 5. `applyCommand` skip flips a step's status.
59
+ 6. `applyCommand` insert places a new step after the named one.
60
+
61
+ - [ ] Run, expect FAIL.
62
+
63
+ ### Task 2: Implement `plan_mode.mjs`
64
+
65
+ - [ ] Functions to export:
66
+ - `parsePlanOutput(text)` — regex on `/PLAN_BEGIN\s*\n([\s\S]*?)\nPLAN_END/`, then split lines matching `^\s*(\d+)\.\s*(.+)$`.
67
+ - `parseVoiceCommand(text, language)` — small regex set, returns discriminated union `{ type: 'skip'|'insert'|'approve'|'cancel'|'unknown', ...}`.
68
+ - `applyCommand(steps, cmd)` — pure reducer.
69
+ - `renderFinalPlan(steps)` — re-numbers active steps for the approval prompt.
70
+
71
+ - [ ] Run tests: PASS.
72
+ - [ ] Commit: `feat(plan-mode): parsers and reducer`.
73
+
74
+ ### Task 3: Wire `voiceBridgePrompt`
75
+
76
+ - [ ] In `agent_adapters.mjs::voiceBridgePrompt`, accept `options.planMode`. When set, append:
77
+ ```
78
+ You are in PLAN MODE. Do NOT modify any files. Reply ONLY with a plan:
79
+ PLAN_BEGIN
80
+ 1. ...
81
+ 2. ...
82
+ PLAN_END
83
+ Each step under 12 words.
84
+ ```
85
+ Korean variant included.
86
+ - [ ] Test: `voiceBridgePrompt('do X', { planMode: true })` contains `PLAN_BEGIN`.
87
+ - [ ] Commit.
88
+
89
+ ### Task 4: Bridge wiring
90
+
91
+ - [ ] In `main.mjs`:
92
+ - Detect enter phrase → call agent with `planMode: true` → parse output → store in channel state → speak each step.
93
+ - Each follow-up utterance while `state.active` → `parseVoiceCommand` → mutate state OR on approve call agent again with `renderFinalPlan(...)` as the user prompt and clear state.
94
+ - [ ] Add integration test using a fake adapter.
95
+ - [ ] Commit.
96
+
97
+ ### Task 5: Document
98
+
99
+ - [ ] Add a "Voice plan mode" section to `docs/USAGE.md` with example transcript.
100
+ - [ ] Commit.
101
+
102
+ ---
103
+
104
+ ## Self-Review
105
+
106
+ - Spec covered.
107
+ - No placeholders.
108
+ - Names consistent: `parsePlanOutput`, `parseVoiceCommand`, `applyCommand`, `renderFinalPlan`, `planMode`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "verbalcoding",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "description": "Discord voice bridge for CLI coding agents.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,6 +38,7 @@
38
38
  "run.sh",
39
39
  ".env.example",
40
40
  "README.md",
41
+ "README.*.md",
41
42
  "LICENSE"
42
43
  ],
43
44
  "scripts": {
package/scripts/cli.mjs CHANGED
@@ -32,6 +32,7 @@ function usage() {
32
32
  Usage:
33
33
  vc setup [--yes] [--no-wizard] [--skip-system] [--skip-model] [--skip-edge-tts]
34
34
  vc setup token [bot-token] [--client-id <client-id>]
35
+ vc setup channels [voice-channel[,voice-channel...]]
35
36
  vc start
36
37
  vc status
37
38
  vc language <ko|en|auto>
@@ -47,9 +48,11 @@ Usage:
47
48
  vc doctor
48
49
 
49
50
  Examples:
50
- npx verbalcoding setup --yes
51
- vc setup --yes
52
- vc setup token
51
+ npx verbalcoding setup
52
+ vc setup
53
+ vc setup --yes # automation/non-interactive starter config
54
+ vc setup token # later token update
55
+ vc setup channels "General,Team Voice"
53
56
  vc start
54
57
  vc language en
55
58
  vc language ko
@@ -277,7 +280,7 @@ async function main(argv = process.argv.slice(2)) {
277
280
  }
278
281
  if (command === 'setup' || command === 'install') {
279
282
  const { spawnSync } = await import('node:child_process');
280
- if (argv[1] === 'token' || argv[1] === 'discord' || argv[1] === 'bot-token') {
283
+ if (['token', 'discord', 'bot-token', 'channels', 'channel', 'voice'].includes(argv[1])) {
281
284
  const result = spawnSync(process.execPath, [path.join(ROOT, 'scripts', 'install.mjs'), ...argv.slice(1)], { stdio: 'inherit', cwd: ROOT });
282
285
  process.exitCode = result.status ?? 1;
283
286
  return;
@@ -5,6 +5,7 @@ import { spawnSync } from 'node:child_process';
5
5
  import { parseKeyValueEnv } from '../app-node/install_config.mjs';
6
6
  import { checkInstanceConfigs, formatInstanceDoctor } from '../app-node/instance_doctor.mjs';
7
7
  import { autoRestartVoiceBotEnabled } from '../app-node/restart_policy.mjs';
8
+ import { detectInstalledAgents, formatAgentDetectionReport } from '../app-node/agent_detect.mjs';
8
9
 
9
10
  const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
10
11
  const args = process.argv.slice(2);
@@ -210,6 +211,16 @@ if (!autoFixEnabled) note('Automatic prerequisite bootstrap', 'off');
210
211
  if (autoFixAttempted) note('Automatic prerequisite bootstrap', 'attempted');
211
212
  console.log('');
212
213
 
214
+ try {
215
+ const detection = await detectInstalledAgents(env);
216
+ console.log(formatAgentDetectionReport(detection));
217
+ const selected = detection.find(r => r.backend === backend || r.backend === backend.replace(/-/g, ''));
218
+ if (selected && !selected.present) note(`Selected backend "${backend}"`, `binary ${selected.bin} not on PATH`);
219
+ console.log('');
220
+ } catch (e) {
221
+ note('Agent backend detection', `skipped: ${e?.message || e}`);
222
+ }
223
+
213
224
  const nodeCommand = commandExists('node');
214
225
  const npmCommand = commandExists('npm');
215
226
  const ffmpegCommand = commandExists('ffmpeg');
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import readline from 'node:readline/promises';
5
5
  import { stdin as input, stdout as output } from 'node:process';
6
6
  import { buildEnvFile, normalizeInstallAnswers, renderInstallSummary, SUPPORTED_HARNESSES } from '../app-node/install_config.mjs';
7
+ import { detectInstalledAgents, pickDefaultBackend, formatAgentDetectionReport } from '../app-node/agent_detect.mjs';
7
8
 
8
9
  const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
9
10
 
@@ -76,12 +77,41 @@ async function configureDiscordToken(args) {
76
77
  if (clientId) console.log(`Invite URL: vc bot invite ${clientId}`);
77
78
  }
78
79
 
80
+ async function configureAutoJoinChannels(args) {
81
+ const envPath = path.join(ROOT, '.env');
82
+ let channels = args.find((arg, idx) => idx > 0 && !arg.startsWith('--')) || argValue(args, '--channels') || argValue(args, '--voice');
83
+ if (!channels) {
84
+ globalThis.__rl = readline.createInterface({ input, output });
85
+ try {
86
+ console.log('Discord auto-join voice channel setup');
87
+ console.log('Enter one or more voice channel names, comma-separated. Example: General,Team Voice,일반');
88
+ channels = await ask('Auto-join voice channel names', process.env.AUTO_JOIN_VOICE_CHANNELS || 'General,general');
89
+ } finally {
90
+ globalThis.__rl.close();
91
+ globalThis.__rl = null;
92
+ }
93
+ }
94
+ if (!channels) {
95
+ console.error('No voice channel names provided. Nothing changed.');
96
+ process.exitCode = 2;
97
+ return;
98
+ }
99
+ upsertEnvFile(envPath, { AUTO_JOIN_VOICE_CHANNELS: channels });
100
+ console.log(`Updated ${envPath}`);
101
+ console.log(`Auto-join voice channels: ${channels}`);
102
+ console.log('Restart the bridge for this to take effect. You can update it anytime with `vc setup channels`.');
103
+ }
104
+
79
105
  async function main() {
80
106
  const args = process.argv.slice(2);
81
107
  if (args[0] === 'token' || args[0] === 'discord' || args[0] === 'bot-token') {
82
108
  await configureDiscordToken(args);
83
109
  return;
84
110
  }
111
+ if (args[0] === 'channels' || args[0] === 'channel' || args[0] === 'voice') {
112
+ await configureAutoJoinChannels(args);
113
+ return;
114
+ }
85
115
  const yes = args.includes('--yes') || args.includes('-y');
86
116
  if (args[0] === 'instance' || args.includes('--instance')) {
87
117
  const { spawnSync } = await import('node:child_process');
@@ -109,7 +139,20 @@ async function main() {
109
139
  try {
110
140
  console.log('VerbalCoding installer');
111
141
  console.log(`Supported harnesses: ${SUPPORTED_HARNESSES.join(', ')}`);
112
- const harness = await ask('Harness/backend', 'hermes');
142
+ console.log('Discord setup: keep https://discord.com/developers/applications open.');
143
+ console.log('Create an application/bot, enable Message Content intent, then paste the bot token and application/client ID below.');
144
+ console.log('If you are not ready, press Enter to skip and run `vc setup token` / `vc setup channels` later.');
145
+ let detectionDefault = 'hermes';
146
+ try {
147
+ const detection = await detectInstalledAgents(process.env);
148
+ console.log('');
149
+ console.log(formatAgentDetectionReport(detection));
150
+ detectionDefault = pickDefaultBackend(detection, process.env.AGENT_BACKEND);
151
+ console.log('');
152
+ } catch (e) {
153
+ console.log(`(agent detection skipped: ${e?.message || e})`);
154
+ }
155
+ const harness = await ask('Harness/backend', detectionDefault);
113
156
  let agentCommand = '';
114
157
  let agentLabel = '';
115
158
  if (harness.toLowerCase() === 'custom') {