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,308 @@
1
+ // VibeFast CLI Worker - Phase 5
2
+ // Handles authentication, device management, and recipe delivery
3
+
4
+ // --- Utility: Hash function ---
5
+ async function sha256(str) {
6
+ const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(str));
7
+ return Array.from(new Uint8Array(buf))
8
+ .map((b) => b.toString(16).padStart(2, "0"))
9
+ .join("");
10
+ }
11
+
12
+ // --- Utility: JSON Response Helper ---
13
+ function json(data, status = 200) {
14
+ return new Response(JSON.stringify(data, null, 2), {
15
+ headers: {
16
+ "Content-Type": "application/json",
17
+ "Access-Control-Allow-Origin": "*",
18
+ },
19
+ status,
20
+ });
21
+ }
22
+
23
+ // --- Rate Limiting (simple in-memory, use KV for production) ---
24
+ const rateLimits = new Map();
25
+
26
+ function checkRateLimit(tokenHash) {
27
+ const now = Date.now();
28
+ const key = tokenHash;
29
+ const limit = rateLimits.get(key) || { count: 0, resetAt: now + 60000 };
30
+
31
+ if (now > limit.resetAt) {
32
+ limit.count = 0;
33
+ limit.resetAt = now + 60000;
34
+ }
35
+
36
+ if (limit.count >= 30) {
37
+ return false; // Rate limited
38
+ }
39
+
40
+ limit.count++;
41
+ rateLimits.set(key, limit);
42
+ return true;
43
+ }
44
+
45
+ // --- Router ---
46
+ async function handleRequest(request, env) {
47
+ const url = new URL(request.url);
48
+ const path = url.pathname;
49
+
50
+ console.log(`[Worker] ${request.method} ${path}`);
51
+
52
+ // CORS preflight
53
+ if (request.method === "OPTIONS") {
54
+ return new Response(null, {
55
+ headers: {
56
+ "Access-Control-Allow-Origin": "*",
57
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
58
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
59
+ },
60
+ });
61
+ }
62
+
63
+ // Route: POST /api/recipe/fetch
64
+ if (path === "/api/recipe/fetch" && request.method === "POST") {
65
+ return handleRecipeFetch(request, env);
66
+ }
67
+
68
+ // Route: GET /api/recipes/list
69
+ if (path === "/api/recipes/list" && request.method === "GET") {
70
+ return handleRecipesList(request, env);
71
+ }
72
+
73
+ // Route: GET /api/devices/list
74
+ if (path === "/api/devices/list" && request.method === "GET") {
75
+ return handleDevicesList(request, env);
76
+ }
77
+
78
+ // Route: POST /api/devices/deactivate
79
+ if (path === "/api/devices/deactivate" && request.method === "POST") {
80
+ return handleDeviceDeactivate(request, env);
81
+ }
82
+
83
+ return json({ error: "Not found" }, 404);
84
+ }
85
+
86
+ // --- Handler: Recipe Fetch ---
87
+ async function handleRecipeFetch(request, env) {
88
+ try {
89
+ const body = await request.json();
90
+ const { token, device, feature, target, starter } = body;
91
+
92
+ // Validate required fields
93
+ if (!token || !device?.id || !feature || !target) {
94
+ return json({ error: "Missing required fields" }, 400);
95
+ }
96
+
97
+ // Validate token
98
+ const tokenHash = await sha256(token);
99
+ const kvKey = `lic:${tokenHash}`;
100
+ const license = await env.LICENSES.get(kvKey, { type: "json" });
101
+
102
+ if (!license || license.status !== "active") {
103
+ return json({
104
+ ok: false,
105
+ error: "Invalid or inactive token",
106
+ message: "Your license token is invalid or has been revoked."
107
+ }, 403);
108
+ }
109
+
110
+ // Check rate limit
111
+ if (!checkRateLimit(tokenHash)) {
112
+ return json({
113
+ ok: false,
114
+ error: "Rate limit exceeded",
115
+ message: "Too many requests. Please wait a minute and try again."
116
+ }, 429);
117
+ }
118
+
119
+ // Enforce device slots
120
+ if (!license.devices.includes(device.id)) {
121
+ if (license.devices.length >= (license.max_devices || 2)) {
122
+ return json({
123
+ ok: false,
124
+ error: "Device limit reached",
125
+ message: `Maximum ${license.max_devices || 2} devices allowed. Use 'vf devices --deactivate <id>' to free a slot.`
126
+ }, 403);
127
+ }
128
+
129
+ // Add new device
130
+ license.devices.push(device.id);
131
+ license.lastSeen = new Date().toISOString();
132
+ await env.LICENSES.put(kvKey, JSON.stringify(license));
133
+ console.log(`[Worker] Added device ${device.id} to license`);
134
+ }
135
+
136
+ // Construct recipe filename
137
+ const recipeFile = `${feature}@latest.zip`;
138
+
139
+ // Check if recipe exists in R2
140
+ const object = await env.VIBEFAST_RECIPES.head(recipeFile);
141
+ if (!object) {
142
+ return json({
143
+ ok: false,
144
+ error: "Recipe not found",
145
+ message: `Feature '${feature}' for target '${target}' not found.`
146
+ }, 404);
147
+ }
148
+
149
+ // Get the recipe zip from R2
150
+ const objectData = await env.VIBEFAST_RECIPES.get(recipeFile);
151
+ if (!objectData) {
152
+ return json({
153
+ ok: false,
154
+ error: "Recipe not found",
155
+ message: `Feature '${feature}' could not be retrieved.`
156
+ }, 404);
157
+ }
158
+
159
+ // Generate watermark
160
+ const watermark = `${tokenHash.slice(0, 8)}-${device.id.slice(0, 8)}`;
161
+
162
+ // Return the zip file directly as a blob URL
163
+ const arrayBuffer = await objectData.arrayBuffer();
164
+ const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
165
+
166
+ return json({
167
+ ok: true,
168
+ zipData: base64,
169
+ expiresIn: 180,
170
+ watermark,
171
+ });
172
+
173
+ } catch (err) {
174
+ console.error("[Worker] Error in handleRecipeFetch:", err);
175
+ return json({
176
+ ok: false,
177
+ error: "Internal server error",
178
+ message: err.message
179
+ }, 500);
180
+ }
181
+ }
182
+
183
+ // --- Handler: List Recipes ---
184
+ async function handleRecipesList(request, env) {
185
+ try {
186
+ const authHeader = request.headers.get("Authorization");
187
+ if (!authHeader?.startsWith("Bearer ")) {
188
+ return json({ error: "Missing authorization" }, 401);
189
+ }
190
+
191
+ const token = authHeader.slice(7);
192
+ const tokenHash = await sha256(token);
193
+ const kvKey = `lic:${tokenHash}`;
194
+ const license = await env.LICENSES.get(kvKey, { type: "json" });
195
+
196
+ if (!license || license.status !== "active") {
197
+ return json({ error: "Invalid token" }, 403);
198
+ }
199
+
200
+ // List all recipes from R2
201
+ const list = await env.VIBEFAST_RECIPES.list();
202
+
203
+ // For now, use simple heuristic: if name contains 'native' or 'web', use that
204
+ // Otherwise default to 'native' for backward compatibility
205
+ const recipes = list.objects.map(obj => {
206
+ const name = obj.key.replace(/@.*\.zip$/, '');
207
+ let target = 'native'; // default
208
+
209
+ if (name.includes('-native')) {
210
+ target = 'native';
211
+ } else if (name.includes('-web')) {
212
+ target = 'web';
213
+ }
214
+ // For names without suffix, assume native
215
+
216
+ return {
217
+ name,
218
+ target,
219
+ description: `${name} feature`,
220
+ version: 'latest',
221
+ };
222
+ });
223
+
224
+ return json({ recipes });
225
+
226
+ } catch (err) {
227
+ console.error("[Worker] Error in handleRecipesList:", err);
228
+ return json({ error: "Internal server error" }, 500);
229
+ }
230
+ }
231
+
232
+ // --- Handler: List Devices ---
233
+ async function handleDevicesList(request, env) {
234
+ try {
235
+ const authHeader = request.headers.get("Authorization");
236
+ if (!authHeader?.startsWith("Bearer ")) {
237
+ return json({ error: "Missing authorization" }, 401);
238
+ }
239
+
240
+ const token = authHeader.slice(7);
241
+ const tokenHash = await sha256(token);
242
+ const kvKey = `lic:${tokenHash}`;
243
+ const license = await env.LICENSES.get(kvKey, { type: "json" });
244
+
245
+ if (!license || license.status !== "active") {
246
+ return json({ error: "Invalid token" }, 403);
247
+ }
248
+
249
+ const devices = license.devices.map(id => ({
250
+ id,
251
+ os: 'unknown',
252
+ arch: 'unknown',
253
+ lastSeen: license.lastSeen || 'unknown',
254
+ }));
255
+
256
+ return json({
257
+ devices,
258
+ maxDevices: license.max_devices || 2,
259
+ });
260
+
261
+ } catch (err) {
262
+ console.error("[Worker] Error in handleDevicesList:", err);
263
+ return json({ error: "Internal server error" }, 500);
264
+ }
265
+ }
266
+
267
+ // --- Handler: Deactivate Device ---
268
+ async function handleDeviceDeactivate(request, env) {
269
+ try {
270
+ const authHeader = request.headers.get("Authorization");
271
+ if (!authHeader?.startsWith("Bearer ")) {
272
+ return json({ error: "Missing authorization" }, 401);
273
+ }
274
+
275
+ const token = authHeader.slice(7);
276
+ const body = await request.json();
277
+ const { deviceId } = body;
278
+
279
+ if (!deviceId) {
280
+ return json({ error: "Missing deviceId" }, 400);
281
+ }
282
+
283
+ const tokenHash = await sha256(token);
284
+ const kvKey = `lic:${tokenHash}`;
285
+ const license = await env.LICENSES.get(kvKey, { type: "json" });
286
+
287
+ if (!license || license.status !== "active") {
288
+ return json({ error: "Invalid token" }, 403);
289
+ }
290
+
291
+ // Remove device
292
+ license.devices = license.devices.filter(id => id !== deviceId);
293
+ await env.LICENSES.put(kvKey, JSON.stringify(license));
294
+
295
+ return json({ ok: true, message: "Device deactivated" });
296
+
297
+ } catch (err) {
298
+ console.error("[Worker] Error in handleDeviceDeactivate:", err);
299
+ return json({ error: "Internal server error" }, 500);
300
+ }
301
+ }
302
+
303
+ // --- Worker Entrypoint ---
304
+ export default {
305
+ async fetch(request, env) {
306
+ return handleRequest(request, env);
307
+ },
308
+ };
@@ -0,0 +1,13 @@
1
+ name = "vibefast-cli-worker"
2
+ main = "worker.js"
3
+ compatibility_date = "2024-01-01"
4
+
5
+ # KV Namespace for license storage
6
+ [[kv_namespaces]]
7
+ binding = "LICENSES"
8
+ id = "64907821e2634080acce34618d2f3d4c"
9
+
10
+ # R2 Bucket for recipe zips
11
+ [[r2_buckets]]
12
+ binding = "VIBEFAST_RECIPES"
13
+ bucket_name = "vibefast-recipes" # Replace with your R2 bucket name
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const addCommand: Command;
3
+ //# sourceMappingURL=add.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsBpC,eAAO,MAAM,UAAU,SAgKnB,CAAC"}
@@ -0,0 +1,149 @@
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
+ export const addCommand = new Command('add')
14
+ .description('Add a VibeFast feature to your project')
15
+ .argument('<feature>', 'Feature name to install')
16
+ .option('--target <target>', 'Target platform (native or web)', 'native')
17
+ .option('--dry-run', 'Preview changes without applying')
18
+ .option('--force', 'Overwrite existing files')
19
+ .action(async (feature, options) => {
20
+ try {
21
+ const paths = getPaths();
22
+ const target = options.target;
23
+ // Validate setup
24
+ const config = await validateSignature(paths.signatureFile);
25
+ validateTarget(target, config.targets);
26
+ // Check auth
27
+ const token = await getToken();
28
+ if (!token) {
29
+ log.error('Not logged in. Run "vf login --token <TOKEN>" first');
30
+ process.exit(1);
31
+ }
32
+ // Check if already installed
33
+ const existing = await getEntry(paths.journalFile, feature, target);
34
+ if (existing && !options.force) {
35
+ log.warn(`${feature} is already installed for ${target}`);
36
+ log.info('Use --force to reinstall');
37
+ return;
38
+ }
39
+ if (options.dryRun) {
40
+ log.info('[DRY RUN] No changes will be made');
41
+ }
42
+ // Fetch recipe
43
+ log.info(`Fetching ${feature} for ${target}...`);
44
+ const device = await getDeviceInfo();
45
+ const response = await fetchRecipe({
46
+ token,
47
+ device,
48
+ feature,
49
+ target,
50
+ starter: { name: config.name, version: config.version },
51
+ });
52
+ if (!response.ok || (!response.signedUrl && !response.zipData)) {
53
+ log.error(`Failed to fetch recipe: ${response.error || 'Unknown error'}`);
54
+ if (response.message) {
55
+ log.plain(` ${response.message}`);
56
+ }
57
+ process.exit(1);
58
+ }
59
+ // Download and extract
60
+ log.info('Downloading recipe...');
61
+ const zipPath = response.zipData
62
+ ? await downloadZip(response.zipData, true)
63
+ : await downloadZip(response.signedUrl);
64
+ const extractDir = zipPath.replace('.zip', '');
65
+ const extractRoot = resolve(extractDir);
66
+ const repoRoot = resolve(paths.cwd);
67
+ await extractZipSafe(zipPath, extractDir);
68
+ // Read manifest
69
+ const manifestPath = join(extractDir, 'recipe.json');
70
+ const manifestContent = await readFileContent(manifestPath);
71
+ const manifest = JSON.parse(manifestContent);
72
+ if (manifest.target !== target) {
73
+ throw new Error(`Recipe target mismatch: expected ${target}, got ${manifest.target}`);
74
+ }
75
+ log.info(`Installing ${manifest.name} v${manifest.version}...`);
76
+ // Copy files
77
+ const copiedFiles = [];
78
+ for (const copySpec of manifest.copy) {
79
+ const srcPath = ensureWithinBase(extractRoot, resolve(extractRoot, copySpec.from), `Recipe file ${copySpec.from}`);
80
+ const destPath = ensureWithinBase(repoRoot, resolve(repoRoot, copySpec.to), `Destination ${copySpec.to}`);
81
+ log.info(`Copying ${copySpec.from} → ${copySpec.to}`);
82
+ const files = await copyTree(srcPath, destPath, {
83
+ dryRun: options.dryRun,
84
+ force: options.force,
85
+ });
86
+ copiedFiles.push(...files);
87
+ }
88
+ // Add watermark if provided
89
+ if (response.watermark && !options.dryRun) {
90
+ const { writeFileContent, readFileContent } = await import('../core/fsx.js');
91
+ for (const file of copiedFiles) {
92
+ if (file.endsWith('.ts') || file.endsWith('.tsx') || file.endsWith('.js') || file.endsWith('.jsx')) {
93
+ const content = await readFileContent(file);
94
+ const watermarked = `// vibefast license: ${response.watermark}\n${content}`;
95
+ await writeFileContent(file, watermarked, { force: true });
96
+ }
97
+ }
98
+ }
99
+ // Insert nav link
100
+ let navInserted = false;
101
+ let navHref;
102
+ let navLabel;
103
+ if (manifest.nav) {
104
+ log.info('Adding navigation link...');
105
+ const navFile = target === 'native' ? paths.nativeNavFile : paths.webNavFile;
106
+ const insertFn = target === 'native' ? insertNavLinkNative : insertNavLinkWeb;
107
+ navHref = manifest.nav.href;
108
+ navLabel = manifest.nav.label;
109
+ navInserted = await insertFn(navFile, manifest.nav, { dryRun: options.dryRun });
110
+ if (navInserted) {
111
+ log.success('Navigation link added');
112
+ }
113
+ else {
114
+ log.info('Navigation link already exists');
115
+ }
116
+ }
117
+ // Update journal
118
+ if (!options.dryRun) {
119
+ await addEntry(paths.journalFile, {
120
+ feature: manifest.name,
121
+ target: manifest.target,
122
+ files: copiedFiles,
123
+ insertedNav: navInserted,
124
+ navHref,
125
+ navLabel,
126
+ ts: Date.now(),
127
+ });
128
+ }
129
+ log.success(`${manifest.name} installed successfully!`);
130
+ log.info(`Files added: ${copiedFiles.length}`);
131
+ if (options.dryRun) {
132
+ log.warn('This was a dry run. Run without --dry-run to apply changes.');
133
+ }
134
+ else {
135
+ log.info('Next steps:');
136
+ log.plain(` 1. Review the changes in your repo`);
137
+ log.plain(` 2. Run your dev server to test`);
138
+ log.plain(` 3. Navigate to the new feature`);
139
+ }
140
+ }
141
+ catch (error) {
142
+ log.error(`Installation failed: ${error.message}`);
143
+ if (error.stack) {
144
+ log.plain(error.stack);
145
+ }
146
+ process.exit(1);
147
+ }
148
+ });
149
+ //# sourceMappingURL=add.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add.js","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const devicesCommand: Command;
3
+ //# sourceMappingURL=devices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devices.d.ts","sourceRoot":"","sources":["../../src/commands/devices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,eAAO,MAAM,cAAc,SAgCvB,CAAC"}
@@ -0,0 +1,35 @@
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
+ export const devicesCommand = new Command('devices')
6
+ .description('Manage your device activations')
7
+ .option('--deactivate <id>', 'Deactivate a device by ID')
8
+ .action(async (options) => {
9
+ try {
10
+ const token = await getToken();
11
+ if (!token) {
12
+ log.error('Not logged in. Run "vf login --token <TOKEN>" first');
13
+ process.exit(1);
14
+ }
15
+ if (options.deactivate) {
16
+ await deactivateDevice(token, options.deactivate);
17
+ log.success(`Device ${options.deactivate} deactivated`);
18
+ return;
19
+ }
20
+ const response = await listDevices(token);
21
+ if (!response.devices || response.devices.length === 0) {
22
+ log.info('No active devices');
23
+ return;
24
+ }
25
+ log.info(`Active devices (${response.devices.length}/${response.maxDevices}):`);
26
+ response.devices.forEach((device) => {
27
+ log.plain(` • ${device.id} (${device.os}/${device.arch}) - ${device.lastSeen}`);
28
+ });
29
+ }
30
+ catch (error) {
31
+ log.error(`Failed: ${error.message}`);
32
+ process.exit(1);
33
+ }
34
+ });
35
+ //# sourceMappingURL=devices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devices.js","sourceRoot":"","sources":["../../src/commands/devices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEhE,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,OAAO,CAAC,SAAS,CAAC;KACjD,WAAW,CAAC,gCAAgC,CAAC;KAC7C,MAAM,CAAC,mBAAmB,EAAE,2BAA2B,CAAC;KACxD,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAC;YACjE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YACvB,MAAM,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;YAClD,GAAG,CAAC,OAAO,CAAC,UAAU,OAAO,CAAC,UAAU,cAAc,CAAC,CAAC;YACxD,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,CAAC;QAE1C,IAAI,CAAC,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvD,GAAG,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;YAC9B,OAAO;QACT,CAAC;QAED,GAAG,CAAC,IAAI,CAAC,mBAAmB,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,IAAI,CAAC,CAAC;QAChF,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAW,EAAE,EAAE;YACvC,GAAG,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QACnF,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const doctorCommand: Command;
3
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,eAAO,MAAM,aAAa,SA2DtB,CAAC"}
@@ -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
+ export const doctorCommand = new Command('doctor')
8
+ .description('Check your VibeFast setup')
9
+ .action(async () => {
10
+ try {
11
+ const paths = getPaths();
12
+ let hasErrors = false;
13
+ // Check authentication
14
+ log.info('Checking authentication...');
15
+ const token = await getToken();
16
+ if (!token) {
17
+ log.error('Not logged in');
18
+ log.plain(' Run: vf login --token <YOUR_TOKEN>');
19
+ hasErrors = true;
20
+ }
21
+ else {
22
+ log.success('Authenticated');
23
+ }
24
+ // Check signature file
25
+ log.info('Checking repository signature...');
26
+ try {
27
+ const config = await validateSignature(paths.signatureFile);
28
+ log.success(`Found VibeFast repo (v${config.version})`);
29
+ log.info(`Available targets: ${config.targets.join(', ')}`);
30
+ }
31
+ catch (error) {
32
+ log.error(error.message);
33
+ hasErrors = true;
34
+ }
35
+ // Check nav markers
36
+ log.info('Checking navigation markers...');
37
+ const nativeExists = await exists(paths.nativeNavFile);
38
+ if (nativeExists) {
39
+ log.success('Native nav file found');
40
+ }
41
+ else {
42
+ log.warn('Native nav file not found');
43
+ log.plain(` Expected: ${paths.nativeNavFile}`);
44
+ }
45
+ const webExists = await exists(paths.webNavFile);
46
+ if (webExists) {
47
+ log.success('Web nav file found');
48
+ }
49
+ else {
50
+ log.warn('Web nav file not found');
51
+ log.plain(` Expected: ${paths.webNavFile}`);
52
+ }
53
+ if (hasErrors) {
54
+ log.error('Setup incomplete');
55
+ process.exit(1);
56
+ }
57
+ else {
58
+ log.success('All checks passed! Ready to add features.');
59
+ log.info('Run "vf list" to see available features');
60
+ }
61
+ }
62
+ catch (error) {
63
+ log.error(`Doctor check failed: ${error.message}`);
64
+ process.exit(1);
65
+ }
66
+ });
67
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const listCommand: Command;
3
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,eAAO,MAAM,WAAW,SAuCpB,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { Command } from 'commander';
2
+ import { log } from '../core/log.js';
3
+ import { getToken } from '../core/auth.js';
4
+ import { listRecipes } from '../core/http.js';
5
+ export const listCommand = new Command('list')
6
+ .description('List available VibeFast features')
7
+ .action(async () => {
8
+ try {
9
+ const token = await getToken();
10
+ if (!token) {
11
+ log.error('Not logged in. Run "vf login --token <TOKEN>" first');
12
+ process.exit(1);
13
+ }
14
+ const response = await listRecipes(token);
15
+ if (!response.recipes || response.recipes.length === 0) {
16
+ log.info('No recipes available');
17
+ return;
18
+ }
19
+ const native = response.recipes.filter((r) => r.target === 'native');
20
+ const web = response.recipes.filter((r) => r.target === 'web');
21
+ if (native.length > 0) {
22
+ log.info('Native (Expo) features:');
23
+ native.forEach((recipe) => {
24
+ log.plain(` • ${recipe.name} - ${recipe.description}`);
25
+ });
26
+ }
27
+ if (web.length > 0) {
28
+ log.info('\nWeb (Next.js) features:');
29
+ web.forEach((recipe) => {
30
+ log.plain(` • ${recipe.name} - ${recipe.description}`);
31
+ });
32
+ }
33
+ log.plain('\nInstall with: vf add <feature-name>');
34
+ }
35
+ catch (error) {
36
+ log.error(`Failed to list recipes: ${error.message}`);
37
+ process.exit(1);
38
+ }
39
+ });
40
+ //# sourceMappingURL=list.js.map