vibefast-cli 0.1.1

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 (250) hide show
  1. package/FINAL-STATUS.md +144 -0
  2. package/HOW-IT-WORKS.md +559 -0
  3. package/PLAN.md +453 -0
  4. package/README.md +129 -0
  5. package/RECIPES-READY.md +172 -0
  6. package/STATUS.md +199 -0
  7. package/SUCCESS.md +259 -0
  8. package/TESTING-CHECKLIST.md +450 -0
  9. package/cloudflare-worker/.wrangler/state/v3/kv/64907821e2634080acce34618d2f3d4c/blobs/11f2769953c717e188062bc644da97c1fd1e4d6d0813a226ce7567dba759afab0000019a736fb8d4 +1 -0
  10. package/cloudflare-worker/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/0b03767237c0408301af51ca35d4b09470cbc479c7e5f23cc9de774749d23c59.sqlite +0 -0
  11. package/cloudflare-worker/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/0b03767237c0408301af51ca35d4b09470cbc479c7e5f23cc9de774749d23c59.sqlite-shm +0 -0
  12. package/cloudflare-worker/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/0b03767237c0408301af51ca35d4b09470cbc479c7e5f23cc9de774749d23c59.sqlite-wal +0 -0
  13. package/cloudflare-worker/.wrangler/state/v3/r2/miniflare-R2BucketObject/d1cc388a1a0ef44dd5669fd1a165d168b61362136c8b5fa50aefd96c72688e54.sqlite +0 -0
  14. package/cloudflare-worker/.wrangler/state/v3/r2/miniflare-R2BucketObject/d1cc388a1a0ef44dd5669fd1a165d168b61362136c8b5fa50aefd96c72688e54.sqlite-shm +0 -0
  15. package/cloudflare-worker/.wrangler/state/v3/r2/miniflare-R2BucketObject/d1cc388a1a0ef44dd5669fd1a165d168b61362136c8b5fa50aefd96c72688e54.sqlite-wal +0 -0
  16. package/cloudflare-worker/.wrangler/state/v3/r2/vibefast-recipes/blobs/620e8cf7c35d9806da25dee237e1d7e8b2432bd98f755b60e2c7f08a48d2c7b90000019a73736484 +0 -0
  17. package/cloudflare-worker/MIGRATION.md +160 -0
  18. package/cloudflare-worker/QUICKSTART.md +200 -0
  19. package/cloudflare-worker/README.md +242 -0
  20. package/cloudflare-worker/generate-token.js +32 -0
  21. package/cloudflare-worker/mini-native@latest.zip +0 -0
  22. package/cloudflare-worker/setup.sh +143 -0
  23. package/cloudflare-worker/test-recipe/apps/native/src/app/mini/index.tsx +15 -0
  24. package/cloudflare-worker/test-recipe/recipe.json +16 -0
  25. package/cloudflare-worker/worker.js +308 -0
  26. package/cloudflare-worker/wrangler.toml +13 -0
  27. package/dist/commands/add.d.ts +3 -0
  28. package/dist/commands/add.d.ts.map +1 -0
  29. package/dist/commands/add.js +149 -0
  30. package/dist/commands/add.js.map +1 -0
  31. package/dist/commands/devices.d.ts +3 -0
  32. package/dist/commands/devices.d.ts.map +1 -0
  33. package/dist/commands/devices.js +35 -0
  34. package/dist/commands/devices.js.map +1 -0
  35. package/dist/commands/doctor.d.ts +3 -0
  36. package/dist/commands/doctor.d.ts.map +1 -0
  37. package/dist/commands/doctor.js +67 -0
  38. package/dist/commands/doctor.js.map +1 -0
  39. package/dist/commands/list.d.ts +3 -0
  40. package/dist/commands/list.d.ts.map +1 -0
  41. package/dist/commands/list.js +40 -0
  42. package/dist/commands/list.js.map +1 -0
  43. package/dist/commands/login.d.ts +3 -0
  44. package/dist/commands/login.d.ts.map +1 -0
  45. package/dist/commands/login.js +23 -0
  46. package/dist/commands/login.js.map +1 -0
  47. package/dist/commands/logout.d.ts +3 -0
  48. package/dist/commands/logout.d.ts.map +1 -0
  49. package/dist/commands/logout.js +16 -0
  50. package/dist/commands/logout.js.map +1 -0
  51. package/dist/commands/remove.d.ts +3 -0
  52. package/dist/commands/remove.d.ts.map +1 -0
  53. package/dist/commands/remove.js +67 -0
  54. package/dist/commands/remove.js.map +1 -0
  55. package/dist/core/__tests__/journal.test.d.ts +2 -0
  56. package/dist/core/__tests__/journal.test.d.ts.map +1 -0
  57. package/dist/core/__tests__/journal.test.js +101 -0
  58. package/dist/core/__tests__/journal.test.js.map +1 -0
  59. package/dist/core/__tests__/validate.test.d.ts +2 -0
  60. package/dist/core/__tests__/validate.test.d.ts.map +1 -0
  61. package/dist/core/__tests__/validate.test.js +53 -0
  62. package/dist/core/__tests__/validate.test.js.map +1 -0
  63. package/dist/core/archive.d.ts +2 -0
  64. package/dist/core/archive.d.ts.map +1 -0
  65. package/dist/core/archive.js +59 -0
  66. package/dist/core/archive.js.map +1 -0
  67. package/dist/core/auth.d.ts +15 -0
  68. package/dist/core/auth.d.ts.map +1 -0
  69. package/dist/core/auth.js +76 -0
  70. package/dist/core/auth.js.map +1 -0
  71. package/dist/core/codemod.d.ts +20 -0
  72. package/dist/core/codemod.d.ts.map +1 -0
  73. package/dist/core/codemod.js +150 -0
  74. package/dist/core/codemod.js.map +1 -0
  75. package/dist/core/fsx.d.ts +12 -0
  76. package/dist/core/fsx.d.ts.map +1 -0
  77. package/dist/core/fsx.js +70 -0
  78. package/dist/core/fsx.js.map +1 -0
  79. package/dist/core/http.d.ts +30 -0
  80. package/dist/core/http.d.ts.map +1 -0
  81. package/dist/core/http.js +95 -0
  82. package/dist/core/http.js.map +1 -0
  83. package/dist/core/journal.d.ts +18 -0
  84. package/dist/core/journal.d.ts.map +1 -0
  85. package/dist/core/journal.js +34 -0
  86. package/dist/core/journal.js.map +1 -0
  87. package/dist/core/log.d.ts +8 -0
  88. package/dist/core/log.d.ts.map +1 -0
  89. package/dist/core/log.js +9 -0
  90. package/dist/core/log.js.map +1 -0
  91. package/dist/core/pathGuard.d.ts +3 -0
  92. package/dist/core/pathGuard.d.ts.map +1 -0
  93. package/dist/core/pathGuard.js +18 -0
  94. package/dist/core/pathGuard.js.map +1 -0
  95. package/dist/core/paths.d.ts +11 -0
  96. package/dist/core/paths.d.ts.map +1 -0
  97. package/dist/core/paths.js +22 -0
  98. package/dist/core/paths.js.map +1 -0
  99. package/dist/core/validate.d.ts +8 -0
  100. package/dist/core/validate.d.ts.map +1 -0
  101. package/dist/core/validate.js +27 -0
  102. package/dist/core/validate.js.map +1 -0
  103. package/dist/index.d.ts +3 -0
  104. package/dist/index.d.ts.map +1 -0
  105. package/dist/index.js +23 -0
  106. package/dist/index.js.map +1 -0
  107. package/docs/decisions.md +55 -0
  108. package/package.json +39 -0
  109. package/recipes/audio-recorder/apps/native/src/app/audio-recorder/index.tsx +5 -0
  110. package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-player.tsx +301 -0
  111. package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +373 -0
  112. package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-waveform.tsx +270 -0
  113. package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/index.ts +4 -0
  114. package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/recording-list.tsx +89 -0
  115. package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-player-demo.tsx +66 -0
  116. package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-recorder-cloud.tsx +68 -0
  117. package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-recorder-interview.tsx +102 -0
  118. package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/basic.tsx +27 -0
  119. package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/index.ts +5 -0
  120. package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +82 -0
  121. package/recipes/audio-recorder/recipe.json +22 -0
  122. package/recipes/audio-recorder@latest.zip +0 -0
  123. package/recipes/charts/apps/native/src/app/charts/index.tsx +3 -0
  124. package/recipes/charts/apps/native/src/features/charts/README.md +185 -0
  125. package/recipes/charts/apps/native/src/features/charts/app/preview.tsx +223 -0
  126. package/recipes/charts/apps/native/src/features/charts/components/area-chart.tsx +40 -0
  127. package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +143 -0
  128. package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +196 -0
  129. package/recipes/charts/apps/native/src/features/charts/components/chart-card.tsx +65 -0
  130. package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +143 -0
  131. package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +246 -0
  132. package/recipes/charts/apps/native/src/features/charts/components/index.ts +10 -0
  133. package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +308 -0
  134. package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +180 -0
  135. package/recipes/charts/apps/native/src/features/charts/components/radial-bar-chart.tsx +188 -0
  136. package/recipes/charts/apps/native/src/features/charts/components/stacked-area-chart.tsx +265 -0
  137. package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +322 -0
  138. package/recipes/charts/apps/native/src/features/charts/data/mock-data.ts +183 -0
  139. package/recipes/charts/apps/native/src/features/charts/types/index.ts +66 -0
  140. package/recipes/charts/recipe.json +22 -0
  141. package/recipes/charts@latest.zip +0 -0
  142. package/recipes/chatbot/apps/native/src/app/chatbot/index.tsx +1 -0
  143. package/recipes/chatbot/apps/native/src/features/chatbot/app/index.tsx +302 -0
  144. package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-header-buttons.tsx +59 -0
  145. package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-input-bar.tsx +469 -0
  146. package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +575 -0
  147. package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-message-bubble.tsx +246 -0
  148. package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-settings-modal.tsx +161 -0
  149. package/recipes/chatbot/apps/native/src/features/chatbot/components/image-preview-list.tsx +115 -0
  150. package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +165 -0
  151. package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/index.ts +10 -0
  152. package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/table-renderer.tsx +129 -0
  153. package/recipes/chatbot/apps/native/src/features/chatbot/components/message-error-boundary.tsx +78 -0
  154. package/recipes/chatbot/apps/native/src/features/chatbot/components/message-list.tsx +173 -0
  155. package/recipes/chatbot/apps/native/src/features/chatbot/components/model-selector.tsx +283 -0
  156. package/recipes/chatbot/apps/native/src/features/chatbot/components/report-content-modal.tsx +188 -0
  157. package/recipes/chatbot/apps/native/src/features/chatbot/components/suggested-messages.tsx +67 -0
  158. package/recipes/chatbot/apps/native/src/features/chatbot/constants/models.ts +20 -0
  159. package/recipes/chatbot/apps/native/src/features/chatbot/constants/report-reasons.ts +9 -0
  160. package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-attachment-cache.ts +143 -0
  161. package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chat-config.ts +664 -0
  162. package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chat-handlers.ts +359 -0
  163. package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chatbot-settings.ts +89 -0
  164. package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-conversation.ts +79 -0
  165. package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-image-picker.ts +122 -0
  166. package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-keyboard-coordinator.ts +161 -0
  167. package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-smart-scroll-manager.ts +207 -0
  168. package/recipes/chatbot/apps/native/src/features/chatbot/models/index.ts +86 -0
  169. package/recipes/chatbot/apps/native/src/features/chatbot/models/models.ts +162 -0
  170. package/recipes/chatbot/apps/native/src/features/chatbot/models/providers.ts +62 -0
  171. package/recipes/chatbot/apps/native/src/features/chatbot/models/types.ts +40 -0
  172. package/recipes/chatbot/apps/native/src/features/chatbot/services/file-uploader.ts +238 -0
  173. package/recipes/chatbot/apps/native/src/features/chatbot/services/message-handler-service.ts +180 -0
  174. package/recipes/chatbot/apps/native/src/features/chatbot/types/index.ts +60 -0
  175. package/recipes/chatbot/apps/native/src/features/chatbot/utils/chat-telemetry.ts +91 -0
  176. package/recipes/chatbot/recipe.json +22 -0
  177. package/recipes/chatbot@latest.zip +0 -0
  178. package/recipes/image-generator/apps/native/src/app/image-generator/gallery.tsx +3 -0
  179. package/recipes/image-generator/apps/native/src/app/image-generator/index.tsx +3 -0
  180. package/recipes/image-generator/apps/native/src/features/image-generator/app/_layout.tsx +25 -0
  181. package/recipes/image-generator/apps/native/src/features/image-generator/app/gallery.tsx +217 -0
  182. package/recipes/image-generator/apps/native/src/features/image-generator/app/index.tsx +237 -0
  183. package/recipes/image-generator/apps/native/src/features/image-generator/components/gallery-image.tsx +26 -0
  184. package/recipes/image-generator/apps/native/src/features/image-generator/components/image-detail-modal.tsx +215 -0
  185. package/recipes/image-generator/apps/native/src/features/image-generator/components/image-model-selector.tsx +210 -0
  186. package/recipes/image-generator/apps/native/src/features/image-generator/components/image-placeholder.tsx +26 -0
  187. package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-gallery.ts +71 -0
  188. package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator-settings.ts +152 -0
  189. package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator.ts +93 -0
  190. package/recipes/image-generator/apps/native/src/features/image-generator/models/models.ts +66 -0
  191. package/recipes/image-generator/apps/native/src/features/image-generator/services/image-gallery-service.ts +98 -0
  192. package/recipes/image-generator/apps/native/src/features/image-generator/services/image-save-service.ts +121 -0
  193. package/recipes/image-generator/recipe.json +22 -0
  194. package/recipes/image-generator@latest.zip +0 -0
  195. package/recipes/quiz/apps/native/src/app/quiz/index.tsx +47 -0
  196. package/recipes/quiz/apps/native/src/features/quiz/components/question.tsx +67 -0
  197. package/recipes/quiz/apps/native/src/features/quiz/config.ts +11 -0
  198. package/recipes/quiz/apps/native/src/features/quiz/index.tsx +133 -0
  199. package/recipes/quiz/recipe.json +22 -0
  200. package/recipes/quiz@latest.zip +0 -0
  201. package/recipes/tracker-app/apps/native/src/app/tracker-app/index.tsx +1 -0
  202. package/recipes/tracker-app/apps/native/src/features/tracker-app/app/index.tsx +108 -0
  203. package/recipes/tracker-app/apps/native/src/features/tracker-app/components/animated-number.tsx +102 -0
  204. package/recipes/tracker-app/apps/native/src/features/tracker-app/components/calorie-card.tsx +66 -0
  205. package/recipes/tracker-app/apps/native/src/features/tracker-app/components/circular-progress.tsx +97 -0
  206. package/recipes/tracker-app/apps/native/src/features/tracker-app/components/floating-add-button.tsx +27 -0
  207. package/recipes/tracker-app/apps/native/src/features/tracker-app/components/macro-card.tsx +80 -0
  208. package/recipes/tracker-app/apps/native/src/features/tracker-app/components/promo-banner.tsx +98 -0
  209. package/recipes/tracker-app/apps/native/src/features/tracker-app/components/recently-logged.tsx +64 -0
  210. package/recipes/tracker-app/apps/native/src/features/tracker-app/components/week-calendar.tsx +68 -0
  211. package/recipes/tracker-app/recipe.json +22 -0
  212. package/recipes/tracker-app@latest.zip +0 -0
  213. package/recipes/upload-all.sh +32 -0
  214. package/recipes/voice-bot/apps/native/src/app/voice-bot/index.tsx +27 -0
  215. package/recipes/voice-bot/apps/native/src/features/voice-bot/README.md +185 -0
  216. package/recipes/voice-bot/apps/native/src/features/voice-bot/components/conversation-status.tsx +76 -0
  217. package/recipes/voice-bot/apps/native/src/features/voice-bot/components/index.ts +4 -0
  218. package/recipes/voice-bot/apps/native/src/features/voice-bot/components/message-input.tsx +98 -0
  219. package/recipes/voice-bot/apps/native/src/features/voice-bot/components/voice-bot-screen.tsx +173 -0
  220. package/recipes/voice-bot/apps/native/src/features/voice-bot/components/voice-controls.tsx +73 -0
  221. package/recipes/voice-bot/apps/native/src/features/voice-bot/index.ts +3 -0
  222. package/recipes/voice-bot/apps/native/src/features/voice-bot/services/index.ts +1 -0
  223. package/recipes/voice-bot/apps/native/src/features/voice-bot/services/use-voice-bot.ts +161 -0
  224. package/recipes/voice-bot/apps/native/src/features/voice-bot/types.ts +29 -0
  225. package/recipes/voice-bot/recipe.json +22 -0
  226. package/recipes/voice-bot@latest.zip +0 -0
  227. package/scripts/create-recipes.mjs +189 -0
  228. package/src/commands/add.ts +183 -0
  229. package/src/commands/devices.ts +38 -0
  230. package/src/commands/doctor.ts +67 -0
  231. package/src/commands/list.ts +45 -0
  232. package/src/commands/login.ts +24 -0
  233. package/src/commands/logout.ts +15 -0
  234. package/src/commands/remove.ts +78 -0
  235. package/src/core/__tests__/journal.test.ts +119 -0
  236. package/src/core/__tests__/validate.test.ts +64 -0
  237. package/src/core/archive.ts +69 -0
  238. package/src/core/auth.ts +103 -0
  239. package/src/core/codemod.ts +211 -0
  240. package/src/core/fsx.ts +80 -0
  241. package/src/core/http.ts +136 -0
  242. package/src/core/journal.ts +64 -0
  243. package/src/core/log.ts +9 -0
  244. package/src/core/pathGuard.ts +22 -0
  245. package/src/core/paths.ts +33 -0
  246. package/src/core/validate.ts +44 -0
  247. package/src/index.ts +27 -0
  248. package/test-critical-cases.mjs +258 -0
  249. package/tsconfig.json +21 -0
  250. package/vitest.config.mts +12 -0
@@ -0,0 +1,161 @@
1
+ import { useConversation } from '@elevenlabs/react-native';
2
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
+
4
+ import { generateConvexHttpApiUrl } from '@/core/utils/convex-http-api';
5
+
6
+ import type {
7
+ ConversationState,
8
+ VoiceBotCallbacks,
9
+ VoiceBotConfig,
10
+ } from '../types';
11
+
12
+ export function useVoiceBot(
13
+ config: VoiceBotConfig,
14
+ callbacks?: VoiceBotCallbacks,
15
+ ) {
16
+ const [isStarting, setIsStarting] = useState(false);
17
+ const [textInput, setTextInput] = useState('');
18
+ const tokenFetchUrl = useMemo(() => {
19
+ try {
20
+ return generateConvexHttpApiUrl('/voice/conversation-token');
21
+ } catch (error) {
22
+ console.error(
23
+ '[useVoiceBot] Failed to compute ElevenLabs token fetch URL',
24
+ error,
25
+ );
26
+ return undefined;
27
+ }
28
+ }, []);
29
+
30
+ const conversation = useConversation({
31
+ tokenFetchUrl,
32
+ onConnect: ({ conversationId }: { conversationId: string }) => {
33
+ console.log('✅ Connected to conversation', conversationId);
34
+ callbacks?.onConnect?.(conversationId);
35
+ },
36
+ onDisconnect: (details: string) => {
37
+ console.log('❌ Disconnected from conversation', details);
38
+ callbacks?.onDisconnect?.(details);
39
+ },
40
+ onError: (message: string, context?: Record<string, unknown>) => {
41
+ console.error('❌ Conversation error:', message, context);
42
+ callbacks?.onError?.(message, context);
43
+ },
44
+ onMessage: ({ message, source }) => {
45
+ console.log(`💬 Message from ${source}:`, message);
46
+ callbacks?.onMessage?.({
47
+ id: Date.now().toString(),
48
+ content: message.toString(),
49
+ role: source,
50
+ timestamp: new Date(),
51
+ });
52
+ },
53
+ onModeChange: ({ mode }: { mode: 'speaking' | 'listening' }) => {
54
+ console.log(`🔊 Mode: ${mode}`);
55
+ callbacks?.onModeChange?.(mode);
56
+ },
57
+ onStatusChange: ({ status }) => {
58
+ console.log(`📡 Status: ${status}`);
59
+ callbacks?.onStatusChange?.(status);
60
+ },
61
+ onCanSendFeedbackChange: ({ canSendFeedback }) => {
62
+ console.log(`🔊 Can send feedback: ${canSendFeedback}`);
63
+ },
64
+ });
65
+
66
+ const state: ConversationState = {
67
+ status: conversation.status,
68
+ conversationId: conversation.getId(),
69
+ isSpeaking: conversation.isSpeaking,
70
+ canSendFeedback: conversation.canSendFeedback,
71
+ isStarting,
72
+ };
73
+
74
+ const startConversation = async () => {
75
+ if (isStarting) return;
76
+ setIsStarting(true);
77
+ try {
78
+ await conversation.startSession({
79
+ agentId: config.agentId,
80
+ });
81
+ } catch (error) {
82
+ console.error('Failed to start conversation:', error);
83
+ callbacks?.onError?.('Failed to start conversation', { error });
84
+ } finally {
85
+ setIsStarting(false);
86
+ }
87
+ };
88
+
89
+ const endConversation = async () => {
90
+ try {
91
+ // Idempotent: only end if actually connected
92
+ if (conversation.status === 'connected') {
93
+ await conversation.endSession();
94
+ }
95
+ } catch (error) {
96
+ console.error('Failed to end conversation:', error);
97
+ callbacks?.onError?.('Failed to end conversation', { error });
98
+ }
99
+ };
100
+
101
+ const sendMessage = (message: string) => {
102
+ if (message.trim()) {
103
+ conversation.sendUserMessage(message.trim());
104
+ }
105
+ };
106
+
107
+ const sendContextualUpdate = (context: string) => {
108
+ if (context.trim()) {
109
+ conversation.sendContextualUpdate(context.trim());
110
+ }
111
+ };
112
+
113
+ const sendFeedback = (isPositive: boolean) => {
114
+ conversation.sendFeedback(isPositive);
115
+ };
116
+
117
+ const sendUserActivity = () => {
118
+ conversation.sendUserActivity();
119
+ };
120
+
121
+ // ---- Cleanup only on UNMOUNT (avoid running on every conversation object change)
122
+
123
+ // Keep latest refs so unmount cleanup has fresh values
124
+ const convRef = useRef(conversation);
125
+ const statusRef = useRef(conversation.status);
126
+ useEffect(() => {
127
+ convRef.current = conversation;
128
+ statusRef.current = conversation.status;
129
+ }, [conversation, conversation.status]);
130
+
131
+ // Guard for React dev StrictMode double-invoke
132
+ const cleanedUpRef = useRef(false);
133
+
134
+ useEffect(() => {
135
+ return () => {
136
+ if (cleanedUpRef.current) return;
137
+ cleanedUpRef.current = true;
138
+
139
+ if (statusRef.current === 'connected') {
140
+ console.log('🧹 Auto-ending conversation due to screen exit');
141
+ convRef.current.endSession().catch((error: unknown) => {
142
+ console.error('Failed to auto-end conversation:', error);
143
+ });
144
+ }
145
+ };
146
+ }, []);
147
+
148
+ return {
149
+ state,
150
+ actions: {
151
+ startConversation,
152
+ endConversation,
153
+ sendMessage,
154
+ sendContextualUpdate,
155
+ sendFeedback,
156
+ sendUserActivity,
157
+ },
158
+ textInput,
159
+ setTextInput,
160
+ };
161
+ }
@@ -0,0 +1,29 @@
1
+ import type { ConversationStatus, Role } from '@elevenlabs/react-native';
2
+
3
+ export interface VoiceBotConfig {
4
+ agentId: string;
5
+ }
6
+
7
+ export interface ConversationState {
8
+ status: ConversationStatus;
9
+ conversationId: string | null;
10
+ isSpeaking: boolean;
11
+ canSendFeedback: boolean;
12
+ isStarting: boolean;
13
+ }
14
+
15
+ export interface VoiceBotMessage {
16
+ id: string;
17
+ content: string;
18
+ role: Role;
19
+ timestamp: Date;
20
+ }
21
+
22
+ export interface VoiceBotCallbacks {
23
+ onConnect?: (conversationId: string) => void;
24
+ onDisconnect?: (details: string) => void;
25
+ onError?: (message: string, context?: Record<string, unknown>) => void;
26
+ onMessage?: (message: VoiceBotMessage) => void;
27
+ onModeChange?: (mode: 'speaking' | 'listening') => void;
28
+ onStatusChange?: (status: ConversationStatus) => void;
29
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "voice-bot",
3
+ "version": "1.0.0",
4
+ "description": "Real-time voice conversations with AI",
5
+ "copy": [
6
+ {
7
+ "from": "apps/native/src/app/voice-bot",
8
+ "to": "apps/native/src/app/(root)/(protected)/voice-bot"
9
+ },
10
+ {
11
+ "from": "apps/native/src/features/voice-bot",
12
+ "to": "apps/native/src/features/voice-bot"
13
+ }
14
+ ],
15
+ "nav": {
16
+ "href": "/(root)/(protected)/voice-bot",
17
+ "label": "Voice Assistant",
18
+ "icon": "🗣️",
19
+ "color": "#14B8A6"
20
+ },
21
+ "target": "native"
22
+ }
Binary file
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readdir, stat, mkdir, writeFile, cp } from 'fs/promises';
4
+ import { join, dirname } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { exec } from 'child_process';
7
+ import { promisify } from 'util';
8
+
9
+ const execAsync = promisify(exec);
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+
12
+ // Feature definitions
13
+ const features = [
14
+ {
15
+ name: 'charts',
16
+ label: 'Charts',
17
+ icon: '📊',
18
+ description: 'Beautiful data visualization with charts',
19
+ color: '#6366F1',
20
+ },
21
+ {
22
+ name: 'chatbot',
23
+ label: 'AI Chatbot',
24
+ icon: '💬',
25
+ description: 'AI-powered chat assistant',
26
+ color: '#F43F5E',
27
+ },
28
+ {
29
+ name: 'voice-bot',
30
+ label: 'Voice Assistant',
31
+ icon: '🗣️',
32
+ description: 'Real-time voice conversations with AI',
33
+ color: '#14B8A6',
34
+ },
35
+ {
36
+ name: 'image-generator',
37
+ label: 'Image Generator',
38
+ icon: '🎨',
39
+ description: 'AI-powered image generation',
40
+ color: '#A855F7',
41
+ },
42
+ {
43
+ name: 'audio-recorder',
44
+ label: 'Voice Notes',
45
+ icon: '🎤',
46
+ description: 'Record and manage voice notes',
47
+ color: '#EF4444',
48
+ },
49
+ {
50
+ name: 'quiz',
51
+ label: 'Quiz',
52
+ icon: '📱',
53
+ description: 'Interactive quiz feature',
54
+ color: '#F97316',
55
+ },
56
+ {
57
+ name: 'tracker-app',
58
+ label: 'Tracker',
59
+ icon: '🍎',
60
+ description: 'Track habits and activities',
61
+ color: '#06B6D4',
62
+ },
63
+ ];
64
+
65
+ const monorepoPath = join(__dirname, '../../vibefast-monorepo');
66
+ const recipesDir = join(__dirname, '../recipes');
67
+
68
+ async function exists(path) {
69
+ try {
70
+ await stat(path);
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ async function createRecipe(feature) {
78
+ console.log(`\n📦 Creating recipe for ${feature.name}...`);
79
+
80
+ const recipeDir = join(recipesDir, feature.name);
81
+ await mkdir(recipeDir, { recursive: true });
82
+
83
+ // Paths to copy
84
+ const appPath = join(monorepoPath, 'apps/native/src/app/(root)/(protected)', feature.name);
85
+ const featurePath = join(monorepoPath, 'apps/native/src/features', feature.name);
86
+
87
+ const copySpecs = [];
88
+
89
+ // Check if app route exists
90
+ if (await exists(appPath)) {
91
+ const destAppPath = join(recipeDir, 'apps/native/src/app', feature.name);
92
+ await mkdir(dirname(destAppPath), { recursive: true });
93
+ await cp(appPath, destAppPath, { recursive: true });
94
+ copySpecs.push({
95
+ from: `apps/native/src/app/${feature.name}`,
96
+ to: `apps/native/src/app/(root)/(protected)/${feature.name}`,
97
+ });
98
+ console.log(` ✓ Copied app route`);
99
+ }
100
+
101
+ // Check if feature components exist
102
+ if (await exists(featurePath)) {
103
+ const destFeaturePath = join(recipeDir, 'apps/native/src/features', feature.name);
104
+ await mkdir(dirname(destFeaturePath), { recursive: true });
105
+ await cp(featurePath, destFeaturePath, { recursive: true });
106
+ copySpecs.push({
107
+ from: `apps/native/src/features/${feature.name}`,
108
+ to: `apps/native/src/features/${feature.name}`,
109
+ });
110
+ console.log(` ✓ Copied feature components`);
111
+ }
112
+
113
+ if (copySpecs.length === 0) {
114
+ console.log(` ⚠️ No files found for ${feature.name}, skipping...`);
115
+ return null;
116
+ }
117
+
118
+ // Create recipe.json
119
+ const manifest = {
120
+ name: feature.name,
121
+ version: '1.0.0',
122
+ description: feature.description,
123
+ copy: copySpecs,
124
+ nav: {
125
+ href: `/(root)/(protected)/${feature.name}`,
126
+ label: feature.label,
127
+ icon: feature.icon,
128
+ color: feature.color,
129
+ },
130
+ target: 'native',
131
+ };
132
+
133
+ await writeFile(
134
+ join(recipeDir, 'recipe.json'),
135
+ JSON.stringify(manifest, null, 2)
136
+ );
137
+ console.log(` ✓ Created recipe.json`);
138
+
139
+ // Create zip
140
+ const zipName = `${feature.name}@latest.zip`;
141
+ const zipPath = join(recipesDir, zipName);
142
+
143
+ await execAsync(`cd "${recipeDir}" && zip -r "../${zipName}" .`);
144
+ console.log(` ✓ Created ${zipName}`);
145
+
146
+ return zipPath;
147
+ }
148
+
149
+ async function main() {
150
+ console.log('🚀 VibeFast Recipe Generator\n');
151
+ console.log(`Monorepo: ${monorepoPath}`);
152
+ console.log(`Output: ${recipesDir}\n`);
153
+
154
+ await mkdir(recipesDir, { recursive: true });
155
+
156
+ const created = [];
157
+
158
+ for (const feature of features) {
159
+ try {
160
+ const zipPath = await createRecipe(feature);
161
+ if (zipPath) {
162
+ created.push({ name: feature.name, zipPath });
163
+ }
164
+ } catch (err) {
165
+ console.error(` ✗ Error creating ${feature.name}:`, err.message);
166
+ }
167
+ }
168
+
169
+ console.log('\n✅ Recipe creation complete!\n');
170
+ console.log('Created recipes:');
171
+ created.forEach(({ name, zipPath }) => {
172
+ console.log(` • ${name}@latest.zip`);
173
+ });
174
+
175
+ console.log('\n📤 Upload to R2 with:');
176
+ console.log('cd ../recipes');
177
+ created.forEach(({ name }) => {
178
+ console.log(`wrangler r2 object put vibefast-recipes/${name}@latest.zip --file=${name}@latest.zip --remote`);
179
+ });
180
+
181
+ console.log('\n🧪 Test with:');
182
+ console.log('cd ../../vibefast-monorepo');
183
+ console.log('export VIBEFAST_WORKER_URL=https://vibefast-cli-worker.mzafar611.workers.dev');
184
+ created.forEach(({ name }) => {
185
+ console.log(`vf add ${name}`);
186
+ });
187
+ }
188
+
189
+ main().catch(console.error);
@@ -0,0 +1,183 @@
1
+ import { Command } from 'commander';
2
+ import { log } from '../core/log.js';
3
+ import { getPaths } from '../core/paths.js';
4
+ import { validateSignature, validateTarget } from '../core/validate.js';
5
+ import { getToken, getDeviceInfo } from '../core/auth.js';
6
+ import { fetchRecipe, downloadZip } from '../core/http.js';
7
+ import { addEntry, getEntry } from '../core/journal.js';
8
+ import { copyTree, readFileContent } from '../core/fsx.js';
9
+ import { insertNavLinkNative, insertNavLinkWeb } from '../core/codemod.js';
10
+ import { join, resolve } from 'path';
11
+ import { ensureWithinBase } from '../core/pathGuard.js';
12
+ import { extractZipSafe } from '../core/archive.js';
13
+
14
+ interface RecipeManifest {
15
+ name: string;
16
+ version: string;
17
+ description: string;
18
+ copy: Array<{ from: string; to: string }>;
19
+ nav: { href: string; label: string };
20
+ target: 'native' | 'web';
21
+ }
22
+
23
+ export const addCommand = new Command('add')
24
+ .description('Add a VibeFast feature to your project')
25
+ .argument('<feature>', 'Feature name to install')
26
+ .option('--target <target>', 'Target platform (native or web)', 'native')
27
+ .option('--dry-run', 'Preview changes without applying')
28
+ .option('--force', 'Overwrite existing files')
29
+ .action(async (feature: string, options) => {
30
+ try {
31
+ const paths = getPaths();
32
+ const target = options.target as 'native' | 'web';
33
+
34
+ // Validate setup
35
+ const config = await validateSignature(paths.signatureFile);
36
+ validateTarget(target, config.targets);
37
+
38
+ // Check auth
39
+ const token = await getToken();
40
+ if (!token) {
41
+ log.error('Not logged in. Run "vf login --token <TOKEN>" first');
42
+ process.exit(1);
43
+ }
44
+
45
+ // Check if already installed
46
+ const existing = await getEntry(paths.journalFile, feature, target);
47
+ if (existing && !options.force) {
48
+ log.warn(`${feature} is already installed for ${target}`);
49
+ log.info('Use --force to reinstall');
50
+ return;
51
+ }
52
+
53
+ if (options.dryRun) {
54
+ log.info('[DRY RUN] No changes will be made');
55
+ }
56
+
57
+ // Fetch recipe
58
+ log.info(`Fetching ${feature} for ${target}...`);
59
+ const device = await getDeviceInfo();
60
+ const response = await fetchRecipe({
61
+ token,
62
+ device,
63
+ feature,
64
+ target,
65
+ starter: { name: config.name, version: config.version },
66
+ });
67
+
68
+ if (!response.ok || (!response.signedUrl && !response.zipData)) {
69
+ log.error(`Failed to fetch recipe: ${response.error || 'Unknown error'}`);
70
+ if (response.message) {
71
+ log.plain(` ${response.message}`);
72
+ }
73
+ process.exit(1);
74
+ }
75
+
76
+ // Download and extract
77
+ log.info('Downloading recipe...');
78
+ const zipPath = response.zipData
79
+ ? await downloadZip(response.zipData, true)
80
+ : await downloadZip(response.signedUrl!);
81
+
82
+ const extractDir = zipPath.replace('.zip', '');
83
+ const extractRoot = resolve(extractDir);
84
+ const repoRoot = resolve(paths.cwd);
85
+ await extractZipSafe(zipPath, extractDir);
86
+
87
+ // Read manifest
88
+ const manifestPath = join(extractDir, 'recipe.json');
89
+ const manifestContent = await readFileContent(manifestPath);
90
+ const manifest: RecipeManifest = JSON.parse(manifestContent);
91
+ if (manifest.target !== target) {
92
+ throw new Error(
93
+ `Recipe target mismatch: expected ${target}, got ${manifest.target}`
94
+ );
95
+ }
96
+
97
+ log.info(`Installing ${manifest.name} v${manifest.version}...`);
98
+
99
+ // Copy files
100
+ const copiedFiles: string[] = [];
101
+ for (const copySpec of manifest.copy) {
102
+ const srcPath = ensureWithinBase(
103
+ extractRoot,
104
+ resolve(extractRoot, copySpec.from),
105
+ `Recipe file ${copySpec.from}`
106
+ );
107
+ const destPath = ensureWithinBase(
108
+ repoRoot,
109
+ resolve(repoRoot, copySpec.to),
110
+ `Destination ${copySpec.to}`
111
+ );
112
+
113
+ log.info(`Copying ${copySpec.from} → ${copySpec.to}`);
114
+ const files = await copyTree(srcPath, destPath, {
115
+ dryRun: options.dryRun,
116
+ force: options.force,
117
+ });
118
+ copiedFiles.push(...files);
119
+ }
120
+
121
+ // Add watermark if provided
122
+ if (response.watermark && !options.dryRun) {
123
+ const { writeFileContent, readFileContent } = await import('../core/fsx.js');
124
+ for (const file of copiedFiles) {
125
+ if (file.endsWith('.ts') || file.endsWith('.tsx') || file.endsWith('.js') || file.endsWith('.jsx')) {
126
+ const content = await readFileContent(file);
127
+ const watermarked = `// vibefast license: ${response.watermark}\n${content}`;
128
+ await writeFileContent(file, watermarked, { force: true });
129
+ }
130
+ }
131
+ }
132
+
133
+ // Insert nav link
134
+ let navInserted = false;
135
+ let navHref: string | undefined;
136
+ let navLabel: string | undefined;
137
+ if (manifest.nav) {
138
+ log.info('Adding navigation link...');
139
+ const navFile = target === 'native' ? paths.nativeNavFile : paths.webNavFile;
140
+ const insertFn = target === 'native' ? insertNavLinkNative : insertNavLinkWeb;
141
+ navHref = manifest.nav.href;
142
+ navLabel = manifest.nav.label;
143
+
144
+ navInserted = await insertFn(navFile, manifest.nav, { dryRun: options.dryRun });
145
+ if (navInserted) {
146
+ log.success('Navigation link added');
147
+ } else {
148
+ log.info('Navigation link already exists');
149
+ }
150
+ }
151
+
152
+ // Update journal
153
+ if (!options.dryRun) {
154
+ await addEntry(paths.journalFile, {
155
+ feature: manifest.name,
156
+ target: manifest.target,
157
+ files: copiedFiles,
158
+ insertedNav: navInserted,
159
+ navHref,
160
+ navLabel,
161
+ ts: Date.now(),
162
+ });
163
+ }
164
+
165
+ log.success(`${manifest.name} installed successfully!`);
166
+ log.info(`Files added: ${copiedFiles.length}`);
167
+
168
+ if (options.dryRun) {
169
+ log.warn('This was a dry run. Run without --dry-run to apply changes.');
170
+ } else {
171
+ log.info('Next steps:');
172
+ log.plain(` 1. Review the changes in your repo`);
173
+ log.plain(` 2. Run your dev server to test`);
174
+ log.plain(` 3. Navigate to the new feature`);
175
+ }
176
+ } catch (error: any) {
177
+ log.error(`Installation failed: ${error.message}`);
178
+ if (error.stack) {
179
+ log.plain(error.stack);
180
+ }
181
+ process.exit(1);
182
+ }
183
+ });
@@ -0,0 +1,38 @@
1
+ import { Command } from 'commander';
2
+ import { log } from '../core/log.js';
3
+ import { getToken } from '../core/auth.js';
4
+ import { listDevices, deactivateDevice } from '../core/http.js';
5
+
6
+ export const devicesCommand = new Command('devices')
7
+ .description('Manage your device activations')
8
+ .option('--deactivate <id>', 'Deactivate a device by ID')
9
+ .action(async (options) => {
10
+ try {
11
+ const token = await getToken();
12
+ if (!token) {
13
+ log.error('Not logged in. Run "vf login --token <TOKEN>" first');
14
+ process.exit(1);
15
+ }
16
+
17
+ if (options.deactivate) {
18
+ await deactivateDevice(token, options.deactivate);
19
+ log.success(`Device ${options.deactivate} deactivated`);
20
+ return;
21
+ }
22
+
23
+ const response = await listDevices(token);
24
+
25
+ if (!response.devices || response.devices.length === 0) {
26
+ log.info('No active devices');
27
+ return;
28
+ }
29
+
30
+ log.info(`Active devices (${response.devices.length}/${response.maxDevices}):`);
31
+ response.devices.forEach((device: any) => {
32
+ log.plain(` • ${device.id} (${device.os}/${device.arch}) - ${device.lastSeen}`);
33
+ });
34
+ } catch (error: any) {
35
+ log.error(`Failed: ${error.message}`);
36
+ process.exit(1);
37
+ }
38
+ });
@@ -0,0 +1,67 @@
1
+ import { Command } from 'commander';
2
+ import { log } from '../core/log.js';
3
+ import { getPaths } from '../core/paths.js';
4
+ import { validateSignature } from '../core/validate.js';
5
+ import { exists } from '../core/fsx.js';
6
+ import { getToken } from '../core/auth.js';
7
+
8
+ export const doctorCommand = new Command('doctor')
9
+ .description('Check your VibeFast setup')
10
+ .action(async () => {
11
+ try {
12
+ const paths = getPaths();
13
+ let hasErrors = false;
14
+
15
+ // Check authentication
16
+ log.info('Checking authentication...');
17
+ const token = await getToken();
18
+ if (!token) {
19
+ log.error('Not logged in');
20
+ log.plain(' Run: vf login --token <YOUR_TOKEN>');
21
+ hasErrors = true;
22
+ } else {
23
+ log.success('Authenticated');
24
+ }
25
+
26
+ // Check signature file
27
+ log.info('Checking repository signature...');
28
+ try {
29
+ const config = await validateSignature(paths.signatureFile);
30
+ log.success(`Found VibeFast repo (v${config.version})`);
31
+ log.info(`Available targets: ${config.targets.join(', ')}`);
32
+ } catch (error: any) {
33
+ log.error(error.message);
34
+ hasErrors = true;
35
+ }
36
+
37
+ // Check nav markers
38
+ log.info('Checking navigation markers...');
39
+
40
+ const nativeExists = await exists(paths.nativeNavFile);
41
+ if (nativeExists) {
42
+ log.success('Native nav file found');
43
+ } else {
44
+ log.warn('Native nav file not found');
45
+ log.plain(` Expected: ${paths.nativeNavFile}`);
46
+ }
47
+
48
+ const webExists = await exists(paths.webNavFile);
49
+ if (webExists) {
50
+ log.success('Web nav file found');
51
+ } else {
52
+ log.warn('Web nav file not found');
53
+ log.plain(` Expected: ${paths.webNavFile}`);
54
+ }
55
+
56
+ if (hasErrors) {
57
+ log.error('Setup incomplete');
58
+ process.exit(1);
59
+ } else {
60
+ log.success('All checks passed! Ready to add features.');
61
+ log.info('Run "vf list" to see available features');
62
+ }
63
+ } catch (error: any) {
64
+ log.error(`Doctor check failed: ${error.message}`);
65
+ process.exit(1);
66
+ }
67
+ });