wogiflow 1.0.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.
Files changed (221) hide show
  1. package/.workflow/agents/reviewer.md +81 -0
  2. package/.workflow/agents/security.md +94 -0
  3. package/.workflow/agents/story-writer.md +58 -0
  4. package/.workflow/bridges/base-bridge.js +395 -0
  5. package/.workflow/bridges/claude-bridge.js +434 -0
  6. package/.workflow/bridges/index.js +130 -0
  7. package/.workflow/lib/assumption-detector.js +481 -0
  8. package/.workflow/lib/config-substitution.js +371 -0
  9. package/.workflow/lib/failure-categories.js +478 -0
  10. package/.workflow/state/app-map.md.template +15 -0
  11. package/.workflow/state/architecture.md.template +24 -0
  12. package/.workflow/state/component-index.json.template +5 -0
  13. package/.workflow/state/decisions.md.template +15 -0
  14. package/.workflow/state/feedback-patterns.md.template +9 -0
  15. package/.workflow/state/knowledge-sync.json.template +6 -0
  16. package/.workflow/state/progress.md.template +14 -0
  17. package/.workflow/state/ready.json.template +7 -0
  18. package/.workflow/state/request-log.md.template +14 -0
  19. package/.workflow/state/session-state.json.template +11 -0
  20. package/.workflow/state/stack.md.template +33 -0
  21. package/.workflow/state/testing.md.template +36 -0
  22. package/.workflow/templates/claude-md.hbs +257 -0
  23. package/.workflow/templates/correction-report.md +67 -0
  24. package/.workflow/templates/gemini-md.hbs +52 -0
  25. package/README.md +1802 -0
  26. package/bin/flow +205 -0
  27. package/lib/index.js +33 -0
  28. package/lib/installer.js +467 -0
  29. package/lib/release-channel.js +269 -0
  30. package/lib/skill-registry.js +526 -0
  31. package/lib/upgrader.js +401 -0
  32. package/lib/utils.js +305 -0
  33. package/package.json +64 -0
  34. package/scripts/flow +985 -0
  35. package/scripts/flow-adaptive-learning.js +1259 -0
  36. package/scripts/flow-aggregate.js +488 -0
  37. package/scripts/flow-archive +133 -0
  38. package/scripts/flow-auto-context.js +1015 -0
  39. package/scripts/flow-auto-learn.js +615 -0
  40. package/scripts/flow-bridge.js +223 -0
  41. package/scripts/flow-browser-suggest.js +316 -0
  42. package/scripts/flow-bug.js +247 -0
  43. package/scripts/flow-cascade.js +711 -0
  44. package/scripts/flow-changelog +85 -0
  45. package/scripts/flow-checkpoint.js +483 -0
  46. package/scripts/flow-cli.js +403 -0
  47. package/scripts/flow-code-intelligence.js +760 -0
  48. package/scripts/flow-complexity.js +502 -0
  49. package/scripts/flow-config-set.js +152 -0
  50. package/scripts/flow-constants.js +157 -0
  51. package/scripts/flow-context +152 -0
  52. package/scripts/flow-context-init.js +482 -0
  53. package/scripts/flow-context-monitor.js +384 -0
  54. package/scripts/flow-context-scoring.js +886 -0
  55. package/scripts/flow-correct.js +458 -0
  56. package/scripts/flow-damage-control.js +985 -0
  57. package/scripts/flow-deps +101 -0
  58. package/scripts/flow-diff.js +700 -0
  59. package/scripts/flow-done +151 -0
  60. package/scripts/flow-done.js +489 -0
  61. package/scripts/flow-durable-session.js +1541 -0
  62. package/scripts/flow-entropy-monitor.js +345 -0
  63. package/scripts/flow-export-profile +349 -0
  64. package/scripts/flow-export-scanner.js +1046 -0
  65. package/scripts/flow-figma-confirm.js +400 -0
  66. package/scripts/flow-figma-extract.js +496 -0
  67. package/scripts/flow-figma-generate.js +683 -0
  68. package/scripts/flow-figma-index.js +909 -0
  69. package/scripts/flow-figma-match.js +617 -0
  70. package/scripts/flow-figma-mcp-server.js +518 -0
  71. package/scripts/flow-figma-pipeline.js +414 -0
  72. package/scripts/flow-file-ops.js +301 -0
  73. package/scripts/flow-gate-confidence.js +825 -0
  74. package/scripts/flow-guided-edit.js +659 -0
  75. package/scripts/flow-health +185 -0
  76. package/scripts/flow-health.js +413 -0
  77. package/scripts/flow-hooks.js +556 -0
  78. package/scripts/flow-http-client.js +249 -0
  79. package/scripts/flow-hybrid-detect.js +167 -0
  80. package/scripts/flow-hybrid-interactive.js +591 -0
  81. package/scripts/flow-hybrid-test.js +152 -0
  82. package/scripts/flow-import-profile +439 -0
  83. package/scripts/flow-init +253 -0
  84. package/scripts/flow-instruction-richness.js +827 -0
  85. package/scripts/flow-jira-integration.js +579 -0
  86. package/scripts/flow-knowledge-router.js +522 -0
  87. package/scripts/flow-knowledge-sync.js +589 -0
  88. package/scripts/flow-linear-integration.js +631 -0
  89. package/scripts/flow-links.js +774 -0
  90. package/scripts/flow-log-manager.js +559 -0
  91. package/scripts/flow-loop-enforcer.js +1246 -0
  92. package/scripts/flow-loop-retry-learning.js +630 -0
  93. package/scripts/flow-lsp.js +923 -0
  94. package/scripts/flow-map-index +348 -0
  95. package/scripts/flow-map-sync +201 -0
  96. package/scripts/flow-memory-blocks.js +668 -0
  97. package/scripts/flow-memory-compactor.js +350 -0
  98. package/scripts/flow-memory-db.js +1110 -0
  99. package/scripts/flow-memory-sync.js +484 -0
  100. package/scripts/flow-metrics.js +353 -0
  101. package/scripts/flow-migrate-ids.js +370 -0
  102. package/scripts/flow-model-adapter.js +802 -0
  103. package/scripts/flow-model-router.js +884 -0
  104. package/scripts/flow-models.js +1231 -0
  105. package/scripts/flow-morning.js +517 -0
  106. package/scripts/flow-multi-approach.js +660 -0
  107. package/scripts/flow-new-feature +86 -0
  108. package/scripts/flow-onboard +1042 -0
  109. package/scripts/flow-orchestrate-llm.js +459 -0
  110. package/scripts/flow-orchestrate.js +3592 -0
  111. package/scripts/flow-output.js +123 -0
  112. package/scripts/flow-parallel-detector.js +399 -0
  113. package/scripts/flow-parallel-dispatch.js +987 -0
  114. package/scripts/flow-parallel.js +428 -0
  115. package/scripts/flow-pattern-enforcer.js +600 -0
  116. package/scripts/flow-prd-manager.js +282 -0
  117. package/scripts/flow-progress.js +323 -0
  118. package/scripts/flow-project-analyzer.js +975 -0
  119. package/scripts/flow-prompt-composer.js +487 -0
  120. package/scripts/flow-providers.js +1381 -0
  121. package/scripts/flow-queue.js +308 -0
  122. package/scripts/flow-ready +82 -0
  123. package/scripts/flow-ready.js +189 -0
  124. package/scripts/flow-regression.js +396 -0
  125. package/scripts/flow-response-parser.js +450 -0
  126. package/scripts/flow-resume.js +284 -0
  127. package/scripts/flow-rules-sync.js +439 -0
  128. package/scripts/flow-run-trace.js +718 -0
  129. package/scripts/flow-safety.js +587 -0
  130. package/scripts/flow-search +104 -0
  131. package/scripts/flow-security.js +481 -0
  132. package/scripts/flow-session-end +106 -0
  133. package/scripts/flow-session-end.js +437 -0
  134. package/scripts/flow-session-state.js +671 -0
  135. package/scripts/flow-setup-hooks +216 -0
  136. package/scripts/flow-setup-hooks.js +377 -0
  137. package/scripts/flow-skill-create.js +329 -0
  138. package/scripts/flow-skill-creator.js +572 -0
  139. package/scripts/flow-skill-generator.js +1046 -0
  140. package/scripts/flow-skill-learn.js +880 -0
  141. package/scripts/flow-skill-matcher.js +578 -0
  142. package/scripts/flow-spec-generator.js +820 -0
  143. package/scripts/flow-stack-wizard.js +895 -0
  144. package/scripts/flow-standup +162 -0
  145. package/scripts/flow-start +74 -0
  146. package/scripts/flow-start.js +235 -0
  147. package/scripts/flow-status +110 -0
  148. package/scripts/flow-status.js +301 -0
  149. package/scripts/flow-step-browser.js +83 -0
  150. package/scripts/flow-step-changelog.js +217 -0
  151. package/scripts/flow-step-comments.js +306 -0
  152. package/scripts/flow-step-complexity.js +234 -0
  153. package/scripts/flow-step-coverage.js +218 -0
  154. package/scripts/flow-step-knowledge.js +193 -0
  155. package/scripts/flow-step-pr-tests.js +364 -0
  156. package/scripts/flow-step-regression.js +89 -0
  157. package/scripts/flow-step-review.js +516 -0
  158. package/scripts/flow-step-security.js +162 -0
  159. package/scripts/flow-step-silent-failures.js +290 -0
  160. package/scripts/flow-step-simplifier.js +346 -0
  161. package/scripts/flow-story +105 -0
  162. package/scripts/flow-story.js +500 -0
  163. package/scripts/flow-suspend.js +252 -0
  164. package/scripts/flow-sync-daemon.js +654 -0
  165. package/scripts/flow-task-analyzer.js +606 -0
  166. package/scripts/flow-team-dashboard.js +748 -0
  167. package/scripts/flow-team-sync.js +752 -0
  168. package/scripts/flow-team.js +977 -0
  169. package/scripts/flow-tech-options.js +528 -0
  170. package/scripts/flow-templates.js +812 -0
  171. package/scripts/flow-tiered-learning.js +728 -0
  172. package/scripts/flow-trace +204 -0
  173. package/scripts/flow-transcript-chunking.js +1106 -0
  174. package/scripts/flow-transcript-digest.js +7918 -0
  175. package/scripts/flow-transcript-language.js +465 -0
  176. package/scripts/flow-transcript-parsing.js +1085 -0
  177. package/scripts/flow-transcript-stories.js +2194 -0
  178. package/scripts/flow-update-map +224 -0
  179. package/scripts/flow-utils.js +2242 -0
  180. package/scripts/flow-verification.js +644 -0
  181. package/scripts/flow-verify.js +1177 -0
  182. package/scripts/flow-voice-input.js +638 -0
  183. package/scripts/flow-watch +168 -0
  184. package/scripts/flow-workflow-steps.js +521 -0
  185. package/scripts/flow-workflow.js +1029 -0
  186. package/scripts/flow-worktree.js +489 -0
  187. package/scripts/hooks/adapters/base-adapter.js +102 -0
  188. package/scripts/hooks/adapters/claude-code.js +359 -0
  189. package/scripts/hooks/adapters/index.js +79 -0
  190. package/scripts/hooks/core/component-check.js +341 -0
  191. package/scripts/hooks/core/index.js +35 -0
  192. package/scripts/hooks/core/loop-check.js +241 -0
  193. package/scripts/hooks/core/session-context.js +294 -0
  194. package/scripts/hooks/core/task-gate.js +177 -0
  195. package/scripts/hooks/core/validation.js +230 -0
  196. package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
  197. package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
  198. package/scripts/hooks/entry/claude-code/session-end.js +87 -0
  199. package/scripts/hooks/entry/claude-code/session-start.js +46 -0
  200. package/scripts/hooks/entry/claude-code/stop.js +43 -0
  201. package/scripts/postinstall.js +139 -0
  202. package/templates/browser-test-flow.json +56 -0
  203. package/templates/bug-report.md +43 -0
  204. package/templates/component-detail.md +42 -0
  205. package/templates/component.stories.tsx +49 -0
  206. package/templates/context/constraints.md +83 -0
  207. package/templates/context/conventions.md +177 -0
  208. package/templates/context/stack.md +60 -0
  209. package/templates/correction-report.md +90 -0
  210. package/templates/feature-proposal.md +35 -0
  211. package/templates/hybrid/_base.md +254 -0
  212. package/templates/hybrid/_patterns.md +45 -0
  213. package/templates/hybrid/create-component.md +127 -0
  214. package/templates/hybrid/create-file.md +56 -0
  215. package/templates/hybrid/create-hook.md +145 -0
  216. package/templates/hybrid/create-service.md +70 -0
  217. package/templates/hybrid/fix-bug.md +33 -0
  218. package/templates/hybrid/modify-file.md +55 -0
  219. package/templates/story.md +68 -0
  220. package/templates/task.json +56 -0
  221. package/templates/trace.md +69 -0
@@ -0,0 +1,638 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Voice Input
5
+ *
6
+ * Voice-to-transcript support with multiple provider options:
7
+ * - Local: Whisper.cpp (no API key required)
8
+ * - Cloud: OpenAI Whisper API
9
+ * - Cloud: Groq (free tier available)
10
+ *
11
+ * Usage:
12
+ * flow voice-input # Record and transcribe
13
+ * flow voice-input --duration 30 # Record for 30 seconds
14
+ * flow voice-input --provider openai # Use specific provider
15
+ * flow voice-input --to-story # Create story from transcript
16
+ * flow voice-input setup # Interactive setup
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const { spawn, execSync } = require('child_process');
22
+ const { HttpClient } = require('./flow-http-client');
23
+ const readline = require('readline');
24
+ const { getConfig, getProjectRoot, colors: c } = require('./flow-utils');
25
+
26
+ const PROJECT_ROOT = getProjectRoot();
27
+ const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
28
+ const CONFIG_PATH = path.join(WORKFLOW_DIR, 'config.json');
29
+
30
+ // ============================================================
31
+ // Voice Provider Types
32
+ // ============================================================
33
+
34
+ const VOICE_PROVIDERS = {
35
+ LOCAL: 'local',
36
+ OPENAI: 'openai',
37
+ GROQ: 'groq'
38
+ };
39
+
40
+ const PROVIDER_INFO = {
41
+ local: {
42
+ name: 'Local (Whisper.cpp)',
43
+ description: 'Run transcription locally - no API key required',
44
+ requiresKey: false,
45
+ binaryName: 'whisper'
46
+ },
47
+ openai: {
48
+ name: 'OpenAI Whisper',
49
+ description: 'Cloud transcription via OpenAI API',
50
+ requiresKey: true,
51
+ endpoint: 'https://api.openai.com/v1/audio/transcriptions',
52
+ model: 'whisper-1'
53
+ },
54
+ groq: {
55
+ name: 'Groq',
56
+ description: 'Fast cloud transcription - free tier available',
57
+ requiresKey: true,
58
+ endpoint: 'https://api.groq.com/openai/v1/audio/transcriptions',
59
+ model: 'whisper-large-v3'
60
+ }
61
+ };
62
+
63
+ // ============================================================
64
+ // Configuration
65
+ // ============================================================
66
+
67
+ /**
68
+ * Get voice configuration from config.json
69
+ */
70
+ function getVoiceConfig() {
71
+ const config = getConfig();
72
+ return config.voice || {
73
+ enabled: false,
74
+ provider: null,
75
+ openaiApiKey: null,
76
+ groqApiKey: null,
77
+ localModelPath: null,
78
+ defaultDuration: 30,
79
+ sampleRate: 16000,
80
+ channels: 1
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Check if voice input is enabled
86
+ */
87
+ function isVoiceEnabled() {
88
+ const config = getVoiceConfig();
89
+ return config.enabled === true && config.provider !== null;
90
+ }
91
+
92
+ /**
93
+ * Get the active provider
94
+ */
95
+ function getActiveProvider() {
96
+ const config = getVoiceConfig();
97
+ return config.provider;
98
+ }
99
+
100
+ // ============================================================
101
+ // Audio Recording
102
+ // ============================================================
103
+
104
+ /**
105
+ * Check if recording dependencies are available
106
+ */
107
+ function checkRecordingDependencies() {
108
+ const issues = [];
109
+
110
+ // Check for sox (required for most recorders)
111
+ try {
112
+ execSync('which sox', { stdio: 'pipe' });
113
+ } catch {
114
+ issues.push('sox not found - install with: brew install sox');
115
+ }
116
+
117
+ // Check for rec (part of sox)
118
+ try {
119
+ execSync('which rec', { stdio: 'pipe' });
120
+ } catch {
121
+ issues.push('rec not found - install with: brew install sox');
122
+ }
123
+
124
+ return issues;
125
+ }
126
+
127
+ /**
128
+ * Record audio from microphone
129
+ * Returns path to recorded WAV file
130
+ */
131
+ async function recordAudio(durationSeconds = 30, options = {}) {
132
+ const {
133
+ sampleRate = 16000,
134
+ channels = 1,
135
+ showProgress = true
136
+ } = options;
137
+
138
+ const tempFile = path.join('/tmp', `wogi-voice-${Date.now()}.wav`);
139
+
140
+ return new Promise((resolve, reject) => {
141
+ if (showProgress) {
142
+ console.log(`${c.cyan}Recording for ${durationSeconds} seconds...${c.reset}`);
143
+ console.log(`${c.dim}Press Ctrl+C to stop early${c.reset}\n`);
144
+ }
145
+
146
+ // Use sox's rec command for cross-platform recording
147
+ const rec = spawn('rec', [
148
+ '-r', String(sampleRate),
149
+ '-c', String(channels),
150
+ '-b', '16',
151
+ tempFile,
152
+ 'trim', '0', String(durationSeconds)
153
+ ], {
154
+ stdio: showProgress ? ['inherit', 'inherit', 'inherit'] : 'pipe'
155
+ });
156
+
157
+ rec.on('close', (code) => {
158
+ if (code === 0 && fs.existsSync(tempFile)) {
159
+ resolve(tempFile);
160
+ } else {
161
+ reject(new Error(`Recording failed with code ${code}`));
162
+ }
163
+ });
164
+
165
+ rec.on('error', (err) => {
166
+ reject(new Error(`Recording error: ${err.message}`));
167
+ });
168
+ });
169
+ }
170
+
171
+ // ============================================================
172
+ // Transcription Providers
173
+ // ============================================================
174
+
175
+ /**
176
+ * Transcribe with local Whisper.cpp
177
+ */
178
+ async function transcribeLocal(audioPath, options = {}) {
179
+ const config = getVoiceConfig();
180
+ const modelPath = config.localModelPath || 'base.en';
181
+
182
+ try {
183
+ // Check if whisper is available
184
+ execSync('which whisper', { stdio: 'pipe' });
185
+ } catch {
186
+ throw new Error(
187
+ 'Local Whisper not found. Install with:\n' +
188
+ ' brew install openai-whisper\n' +
189
+ 'Or download whisper.cpp from: https://github.com/ggerganov/whisper.cpp'
190
+ );
191
+ }
192
+
193
+ return new Promise((resolve, reject) => {
194
+ const whisper = spawn('whisper', [
195
+ audioPath,
196
+ '--model', modelPath,
197
+ '--output_format', 'txt',
198
+ '--output_dir', '/tmp'
199
+ ]);
200
+
201
+ let stderr = '';
202
+ whisper.stderr.on('data', (data) => {
203
+ stderr += data.toString();
204
+ });
205
+
206
+ whisper.on('close', (code) => {
207
+ if (code === 0) {
208
+ // Read the output file
209
+ const outputPath = audioPath.replace('.wav', '.txt');
210
+ if (fs.existsSync(outputPath)) {
211
+ const text = fs.readFileSync(outputPath, 'utf-8').trim();
212
+ fs.unlinkSync(outputPath); // Cleanup
213
+ resolve({ text, provider: 'local', model: modelPath });
214
+ } else {
215
+ reject(new Error('Whisper output file not found'));
216
+ }
217
+ } else {
218
+ reject(new Error(`Whisper failed: ${stderr}`));
219
+ }
220
+ });
221
+ });
222
+ }
223
+
224
+ /**
225
+ * Transcribe with OpenAI Whisper API using shared HttpClient
226
+ */
227
+ async function transcribeOpenAI(audioPath, options = {}) {
228
+ const config = getVoiceConfig();
229
+ const apiKey = config.openaiApiKey || process.env.OPENAI_API_KEY;
230
+
231
+ if (!apiKey) {
232
+ throw new Error(
233
+ 'OpenAI API key not found. Set it in config.json:\n' +
234
+ ' "voice": { "openaiApiKey": "sk-..." }\n' +
235
+ 'Or set OPENAI_API_KEY environment variable'
236
+ );
237
+ }
238
+
239
+ const audioData = fs.readFileSync(audioPath);
240
+ const client = new HttpClient('https://api.openai.com', {
241
+ headers: { 'Authorization': `Bearer ${apiKey}` },
242
+ timeout: 60000
243
+ });
244
+
245
+ const response = await client.postMultipart('/v1/audio/transcriptions', [
246
+ { name: 'file', value: audioData, filename: 'audio.wav', contentType: 'audio/wav' },
247
+ { name: 'model', value: 'whisper-1' }
248
+ ]);
249
+
250
+ if (response.data?.error) {
251
+ throw new Error(response.data.error.message);
252
+ }
253
+
254
+ return { text: response.data.text, provider: 'openai', model: 'whisper-1' };
255
+ }
256
+
257
+ /**
258
+ * Transcribe with Groq API using shared HttpClient
259
+ */
260
+ async function transcribeGroq(audioPath, options = {}) {
261
+ const config = getVoiceConfig();
262
+ const apiKey = config.groqApiKey || process.env.GROQ_API_KEY;
263
+
264
+ if (!apiKey) {
265
+ throw new Error(
266
+ 'Groq API key not found. Set it in config.json:\n' +
267
+ ' "voice": { "groqApiKey": "gsk_..." }\n' +
268
+ 'Or set GROQ_API_KEY environment variable\n\n' +
269
+ 'Get a free key at: https://console.groq.com'
270
+ );
271
+ }
272
+
273
+ const audioData = fs.readFileSync(audioPath);
274
+ const client = new HttpClient('https://api.groq.com', {
275
+ headers: { 'Authorization': `Bearer ${apiKey}` },
276
+ timeout: 60000
277
+ });
278
+
279
+ const response = await client.postMultipart('/openai/v1/audio/transcriptions', [
280
+ { name: 'file', value: audioData, filename: 'audio.wav', contentType: 'audio/wav' },
281
+ { name: 'model', value: 'whisper-large-v3' }
282
+ ]);
283
+
284
+ if (response.data?.error) {
285
+ throw new Error(response.data.error.message);
286
+ }
287
+
288
+ return { text: response.data.text, provider: 'groq', model: 'whisper-large-v3' };
289
+ }
290
+
291
+ /**
292
+ * Main transcription function - routes to appropriate provider
293
+ */
294
+ async function transcribe(audioPath, providerOverride = null) {
295
+ const config = getVoiceConfig();
296
+ const provider = providerOverride || config.provider || 'openai';
297
+
298
+ switch (provider) {
299
+ case 'local':
300
+ return transcribeLocal(audioPath);
301
+ case 'openai':
302
+ return transcribeOpenAI(audioPath);
303
+ case 'groq':
304
+ return transcribeGroq(audioPath);
305
+ default:
306
+ throw new Error(`Unknown provider: ${provider}`);
307
+ }
308
+ }
309
+
310
+ // ============================================================
311
+ // Interactive Setup
312
+ // ============================================================
313
+
314
+ /**
315
+ * Interactive setup wizard for voice input
316
+ */
317
+ async function runSetup() {
318
+ const rl = readline.createInterface({
319
+ input: process.stdin,
320
+ output: process.stdout
321
+ });
322
+
323
+ const question = (q) => new Promise(resolve => rl.question(q, resolve));
324
+
325
+ console.log(`\n${c.cyan}=== Wogi Flow Voice Input Setup ===${c.reset}\n`);
326
+
327
+ // Check recording dependencies
328
+ const issues = checkRecordingDependencies();
329
+ if (issues.length > 0) {
330
+ console.log(`${c.yellow}Recording dependencies missing:${c.reset}`);
331
+ issues.forEach(i => console.log(` - ${i}`));
332
+ console.log('');
333
+ }
334
+
335
+ // Enable voice input
336
+ const enable = await question(`Enable voice input? (y/n): `);
337
+ if (enable.toLowerCase() !== 'y') {
338
+ console.log(`${c.dim}Voice input disabled.${c.reset}`);
339
+ rl.close();
340
+ return;
341
+ }
342
+
343
+ // Choose provider
344
+ console.log(`\n${c.cyan}Choose transcription provider:${c.reset}`);
345
+ console.log(' 1. Local (Whisper.cpp) - No API key, works offline');
346
+ console.log(' 2. OpenAI - Best accuracy, requires API key');
347
+ console.log(' 3. Groq - Fast, free tier available');
348
+
349
+ const providerChoice = await question(`\nSelect provider (1-3): `);
350
+ let provider, apiKey = null;
351
+
352
+ switch (providerChoice) {
353
+ case '1':
354
+ provider = 'local';
355
+ console.log(`\n${c.green}Local provider selected.${c.reset}`);
356
+ console.log(`${c.dim}Ensure whisper is installed: brew install openai-whisper${c.reset}`);
357
+ break;
358
+ case '2':
359
+ provider = 'openai';
360
+ apiKey = await question(`\nEnter OpenAI API key (sk-...): `);
361
+ if (!apiKey.startsWith('sk-')) {
362
+ console.log(`${c.yellow}Warning: Key doesn't look like an OpenAI key${c.reset}`);
363
+ }
364
+ break;
365
+ case '3':
366
+ provider = 'groq';
367
+ apiKey = await question(`\nEnter Groq API key (gsk_...): `);
368
+ if (!apiKey.startsWith('gsk_')) {
369
+ console.log(`${c.yellow}Warning: Key doesn't look like a Groq key${c.reset}`);
370
+ }
371
+ break;
372
+ default:
373
+ console.log(`${c.red}Invalid choice${c.reset}`);
374
+ rl.close();
375
+ return;
376
+ }
377
+
378
+ // Save configuration
379
+ const config = getConfig();
380
+ config.voice = {
381
+ enabled: true,
382
+ provider,
383
+ openaiApiKey: provider === 'openai' ? apiKey : (config.voice?.openaiApiKey || null),
384
+ groqApiKey: provider === 'groq' ? apiKey : (config.voice?.groqApiKey || null),
385
+ localModelPath: config.voice?.localModelPath || 'base.en',
386
+ defaultDuration: 30,
387
+ sampleRate: 16000,
388
+ channels: 1
389
+ };
390
+
391
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
392
+
393
+ console.log(`\n${c.green}Voice input configured!${c.reset}`);
394
+ console.log(`Provider: ${PROVIDER_INFO[provider].name}`);
395
+ console.log(`\nTest with: ${c.cyan}./scripts/flow voice-input${c.reset}`);
396
+
397
+ rl.close();
398
+ }
399
+
400
+ // ============================================================
401
+ // Main CLI Handler
402
+ // ============================================================
403
+
404
+ async function main() {
405
+ const args = process.argv.slice(2);
406
+ const command = args[0];
407
+
408
+ // Parse options
409
+ const options = {
410
+ duration: 30,
411
+ provider: null,
412
+ toStory: false,
413
+ output: null
414
+ };
415
+
416
+ for (let i = 0; i < args.length; i++) {
417
+ switch (args[i]) {
418
+ case '--duration':
419
+ case '-d':
420
+ options.duration = parseInt(args[++i]) || 30;
421
+ break;
422
+ case '--provider':
423
+ case '-p':
424
+ options.provider = args[++i];
425
+ break;
426
+ case '--to-story':
427
+ options.toStory = true;
428
+ break;
429
+ case '--output':
430
+ case '-o':
431
+ options.output = args[++i];
432
+ break;
433
+ }
434
+ }
435
+
436
+ // Handle commands
437
+ switch (command) {
438
+ case 'setup':
439
+ await runSetup();
440
+ break;
441
+
442
+ case 'status':
443
+ showStatus();
444
+ break;
445
+
446
+ case 'test':
447
+ await testVoiceInput();
448
+ break;
449
+
450
+ case undefined:
451
+ case 'record':
452
+ await recordAndTranscribe(options);
453
+ break;
454
+
455
+ default:
456
+ showHelp();
457
+ }
458
+ }
459
+
460
+ /**
461
+ * Show voice input status
462
+ */
463
+ function showStatus() {
464
+ const config = getVoiceConfig();
465
+
466
+ console.log(`\n${c.cyan}Voice Input Status${c.reset}\n`);
467
+ console.log(`Enabled: ${config.enabled ? c.green + 'Yes' : c.red + 'No'}${c.reset}`);
468
+
469
+ if (config.enabled) {
470
+ const providerInfo = PROVIDER_INFO[config.provider] || {};
471
+ console.log(`Provider: ${providerInfo.name || config.provider}`);
472
+ console.log(`Default duration: ${config.defaultDuration}s`);
473
+
474
+ if (config.provider === 'openai') {
475
+ console.log(`API Key: ${config.openaiApiKey ? c.green + 'Configured' : c.red + 'Missing'}${c.reset}`);
476
+ } else if (config.provider === 'groq') {
477
+ console.log(`API Key: ${config.groqApiKey ? c.green + 'Configured' : c.red + 'Missing'}${c.reset}`);
478
+ }
479
+ }
480
+
481
+ // Check dependencies
482
+ const issues = checkRecordingDependencies();
483
+ if (issues.length > 0) {
484
+ console.log(`\n${c.yellow}Missing dependencies:${c.reset}`);
485
+ issues.forEach(i => console.log(` - ${i}`));
486
+ } else {
487
+ console.log(`\n${c.green}Recording dependencies OK${c.reset}`);
488
+ }
489
+ }
490
+
491
+ /**
492
+ * Test voice input with a short recording
493
+ */
494
+ async function testVoiceInput() {
495
+ const config = getVoiceConfig();
496
+
497
+ if (!config.enabled) {
498
+ console.log(`${c.yellow}Voice input not enabled. Run: ./scripts/flow voice-input setup${c.reset}`);
499
+ return;
500
+ }
501
+
502
+ console.log(`${c.cyan}Testing voice input...${c.reset}`);
503
+ console.log(`Provider: ${PROVIDER_INFO[config.provider]?.name || config.provider}\n`);
504
+
505
+ try {
506
+ console.log('Recording 5 seconds of audio...\n');
507
+ const audioPath = await recordAudio(5);
508
+
509
+ console.log(`\n${c.cyan}Transcribing...${c.reset}`);
510
+ const result = await transcribe(audioPath);
511
+
512
+ console.log(`\n${c.green}Success!${c.reset}`);
513
+ console.log(`Transcript: "${result.text}"`);
514
+ console.log(`Provider: ${result.provider}, Model: ${result.model}`);
515
+
516
+ // Cleanup
517
+ if (fs.existsSync(audioPath)) {
518
+ fs.unlinkSync(audioPath);
519
+ }
520
+ } catch (error) {
521
+ console.error(`${c.red}Test failed: ${error.message}${c.reset}`);
522
+ }
523
+ }
524
+
525
+ /**
526
+ * Main record and transcribe flow
527
+ */
528
+ async function recordAndTranscribe(options) {
529
+ const config = getVoiceConfig();
530
+
531
+ if (!config.enabled) {
532
+ console.log(`${c.yellow}Voice input not enabled.${c.reset}`);
533
+ console.log(`Run: ${c.cyan}./scripts/flow voice-input setup${c.reset}`);
534
+ return;
535
+ }
536
+
537
+ try {
538
+ // Record
539
+ const duration = options.duration || config.defaultDuration || 30;
540
+ const audioPath = await recordAudio(duration);
541
+
542
+ // Transcribe
543
+ console.log(`\n${c.cyan}Transcribing...${c.reset}`);
544
+ const result = await transcribe(audioPath, options.provider);
545
+
546
+ console.log(`\n${c.green}Transcript:${c.reset}`);
547
+ console.log(result.text);
548
+
549
+ // Output to file if requested
550
+ if (options.output) {
551
+ fs.writeFileSync(options.output, result.text);
552
+ console.log(`\n${c.dim}Saved to: ${options.output}${c.reset}`);
553
+ }
554
+
555
+ // Create story if requested
556
+ if (options.toStory) {
557
+ console.log(`\n${c.cyan}Creating story from transcript...${c.reset}`);
558
+ // This would integrate with flow-story.js
559
+ console.log(`${c.dim}[Integration with story creation would go here]${c.reset}`);
560
+ }
561
+
562
+ // Cleanup
563
+ if (fs.existsSync(audioPath)) {
564
+ fs.unlinkSync(audioPath);
565
+ }
566
+
567
+ // Return result for programmatic use
568
+ return result;
569
+ } catch (error) {
570
+ console.error(`${c.red}Error: ${error.message}${c.reset}`);
571
+ process.exit(1);
572
+ }
573
+ }
574
+
575
+ /**
576
+ * Show help
577
+ */
578
+ function showHelp() {
579
+ console.log(`
580
+ ${c.cyan}Wogi Flow - Voice Input${c.reset}
581
+
582
+ ${c.bold}Usage:${c.reset}
583
+ flow voice-input [command] [options]
584
+
585
+ ${c.bold}Commands:${c.reset}
586
+ setup Interactive setup wizard
587
+ status Show voice input configuration
588
+ test Test with a 5-second recording
589
+ record Record and transcribe (default)
590
+
591
+ ${c.bold}Options:${c.reset}
592
+ --duration, -d Recording duration in seconds (default: 30)
593
+ --provider, -p Override provider (local, openai, groq)
594
+ --to-story Create a story from the transcript
595
+ --output, -o Save transcript to file
596
+
597
+ ${c.bold}Examples:${c.reset}
598
+ flow voice-input setup # Configure voice input
599
+ flow voice-input # Record and transcribe
600
+ flow voice-input -d 60 # Record for 60 seconds
601
+ flow voice-input -p groq # Use Groq provider
602
+ flow voice-input --to-story # Create story from voice
603
+
604
+ ${c.bold}Providers:${c.reset}
605
+ local - Whisper.cpp (no API key, works offline)
606
+ openai - OpenAI Whisper API (best accuracy)
607
+ groq - Groq API (fast, free tier available)
608
+ `);
609
+ }
610
+
611
+ // ============================================================
612
+ // Exports
613
+ // ============================================================
614
+
615
+ module.exports = {
616
+ getVoiceConfig,
617
+ isVoiceEnabled,
618
+ getActiveProvider,
619
+ recordAudio,
620
+ transcribe,
621
+ transcribeLocal,
622
+ transcribeOpenAI,
623
+ transcribeGroq,
624
+ checkRecordingDependencies,
625
+ VOICE_PROVIDERS,
626
+ PROVIDER_INFO
627
+ };
628
+
629
+ // ============================================================
630
+ // Run CLI
631
+ // ============================================================
632
+
633
+ if (require.main === module) {
634
+ main().catch(err => {
635
+ console.error(`${c.red}Error: ${err.message}${c.reset}`);
636
+ process.exit(1);
637
+ });
638
+ }