vibefast-cli 0.4.0 → 0.5.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 (89) hide show
  1. package/FEATURE-DEPENDENCY-SPEC.md +338 -0
  2. package/dist/__tests__/integration.test.d.ts +2 -0
  3. package/dist/__tests__/integration.test.d.ts.map +1 -0
  4. package/dist/__tests__/integration.test.js +219 -0
  5. package/dist/__tests__/integration.test.js.map +1 -0
  6. package/dist/__tests__/recipes.test.d.ts +2 -0
  7. package/dist/__tests__/recipes.test.d.ts.map +1 -0
  8. package/dist/__tests__/recipes.test.js +143 -0
  9. package/dist/__tests__/recipes.test.js.map +1 -0
  10. package/dist/commands/__tests__/init.test.d.ts +2 -0
  11. package/dist/commands/__tests__/init.test.d.ts.map +1 -0
  12. package/dist/commands/__tests__/init.test.js +95 -0
  13. package/dist/commands/__tests__/init.test.js.map +1 -0
  14. package/dist/commands/__tests__/platform.test.d.ts +2 -0
  15. package/dist/commands/__tests__/platform.test.d.ts.map +1 -0
  16. package/dist/commands/__tests__/platform.test.js +123 -0
  17. package/dist/commands/__tests__/platform.test.js.map +1 -0
  18. package/dist/commands/add.d.ts.map +1 -1
  19. package/dist/commands/add.js +4 -5
  20. package/dist/commands/add.js.map +1 -1
  21. package/dist/commands/init.d.ts.map +1 -1
  22. package/dist/commands/init.js +12 -12
  23. package/dist/commands/init.js.map +1 -1
  24. package/dist/commands/platform.d.ts +3 -0
  25. package/dist/commands/platform.d.ts.map +1 -0
  26. package/dist/commands/platform.js +245 -0
  27. package/dist/commands/platform.js.map +1 -0
  28. package/dist/core/journal.d.ts.map +1 -1
  29. package/dist/core/journal.js +36 -19
  30. package/dist/core/journal.js.map +1 -1
  31. package/dist/core/recipes.d.ts.map +1 -1
  32. package/dist/core/recipes.js +8 -39
  33. package/dist/core/recipes.js.map +1 -1
  34. package/dist/index.js +2 -0
  35. package/dist/index.js.map +1 -1
  36. package/package.json +1 -1
  37. package/recipes/ios-widget/recipe.json +78 -0
  38. package/recipes/ios-widget/targets/widget/AppIntent.swift +46 -0
  39. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@1x.png +0 -0
  40. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@2x.png +0 -0
  41. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-20x20@3x.png +0 -0
  42. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@1x.png +0 -0
  43. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@2x.png +0 -0
  44. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-29x29@3x.png +0 -0
  45. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@1x.png +0 -0
  46. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@2x.png +0 -0
  47. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-40x40@3x.png +0 -0
  48. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@2x.png +0 -0
  49. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-60x60@3x.png +0 -0
  50. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@1x.png +0 -0
  51. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-76x76@2x.png +0 -0
  52. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/App-Icon-83.5x83.5@2x.png +0 -0
  53. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
  54. package/recipes/ios-widget/targets/widget/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png +0 -0
  55. package/recipes/ios-widget/targets/widget/CalorieTrackerWidget.swift +424 -0
  56. package/recipes/ios-widget/targets/widget/HabitTrackerWidget.swift +305 -0
  57. package/recipes/ios-widget/targets/widget/Info.plist +11 -0
  58. package/recipes/ios-widget/targets/widget/WidgetLiveActivity.swift +75 -0
  59. package/recipes/ios-widget/targets/widget/expo-target.config.js +10 -0
  60. package/recipes/ios-widget/targets/widget/generated.entitlements +5 -0
  61. package/recipes/ios-widget/targets/widget/index.swift +18 -0
  62. package/recipes/ios-widget/targets/widget/widgets.swift +96 -0
  63. package/recipes/ios-widget@latest.zip +0 -0
  64. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/index.tsx +74 -0
  65. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/local.tsx +25 -0
  66. package/recipes/payments/apps/native/src/app/(root)/(protected)/paywall/remote.tsx +23 -0
  67. package/recipes/payments/apps/native/src/features/payments/README.md +200 -0
  68. package/recipes/payments/apps/native/src/features/payments/app/local-paywall.tsx +194 -0
  69. package/recipes/payments/apps/native/src/features/payments/app/remote-paywall.tsx +79 -0
  70. package/recipes/payments/apps/native/src/features/payments/components/payment-initializer.tsx +95 -0
  71. package/recipes/payments/apps/native/src/features/payments/components/paywall-error-state.tsx +60 -0
  72. package/recipes/payments/apps/native/src/features/payments/components/paywall-local-mode.tsx +116 -0
  73. package/recipes/payments/apps/native/src/features/payments/components/paywall-product-card.tsx +133 -0
  74. package/recipes/payments/apps/native/src/features/payments/components/paywall-remote-mode.tsx +146 -0
  75. package/recipes/payments/apps/native/src/features/payments/hooks/use-entitlement.ts +63 -0
  76. package/recipes/payments/apps/native/src/features/payments/index.ts +8 -0
  77. package/recipes/payments/apps/native/src/features/payments/services/revenuecat-adapter.ts +407 -0
  78. package/recipes/payments/recipe.json +58 -0
  79. package/recipes/payments@latest.zip +0 -0
  80. package/src/__tests__/integration.test.ts +249 -0
  81. package/src/__tests__/recipes.test.ts +168 -0
  82. package/src/commands/__tests__/init.test.ts +112 -0
  83. package/src/commands/__tests__/platform.test.ts +141 -0
  84. package/src/commands/add.ts +4 -5
  85. package/src/commands/init.ts +14 -15
  86. package/src/commands/platform.ts +309 -0
  87. package/src/core/journal.ts +42 -25
  88. package/src/core/recipes.ts +8 -40
  89. package/src/index.ts +2 -0
@@ -0,0 +1,424 @@
1
+ import SwiftUI
2
+ import WidgetKit
3
+ import AppIntents
4
+
5
+ // MARK: - Data Models
6
+ struct MacroData {
7
+ var protein: (current: Int, target: Int)
8
+ var carbs: (current: Int, target: Int)
9
+ var fat: (current: Int, target: Int)
10
+ }
11
+
12
+ @available(iOSApplicationExtension 17.0, *)
13
+ struct CalorieEntry: TimelineEntry {
14
+ let date: Date
15
+ let currentCalories: Int
16
+ let targetCalories: Int
17
+ let caloriesLeft: Int
18
+ let progress: Int
19
+ let macros: MacroData
20
+ }
21
+
22
+ // MARK: - Timeline Provider
23
+ @available(iOSApplicationExtension 17.0, *)
24
+ struct CalorieProvider: AppIntentTimelineProvider {
25
+ typealias Entry = CalorieEntry
26
+ typealias Intent = CalorieWidgetConfigurationIntent
27
+
28
+ private let sharedDefaults = UserDefaults(suiteName: "group.com.vibefast.shared")
29
+
30
+ func placeholder(in context: Context) -> CalorieEntry {
31
+ CalorieEntry(
32
+ date: Date(),
33
+ currentCalories: 1450,
34
+ targetCalories: 2000,
35
+ caloriesLeft: 550,
36
+ progress: 73,
37
+ macros: getHardcodedMacros()
38
+ )
39
+ }
40
+
41
+ func snapshot(for configuration: CalorieWidgetConfigurationIntent, in context: Context) async -> CalorieEntry {
42
+ return loadNutritionData() ?? getPlaceholderEntry()
43
+ }
44
+
45
+ func timeline(for configuration: CalorieWidgetConfigurationIntent, in context: Context) async -> Timeline<CalorieEntry> {
46
+ let currentDate = Date()
47
+ let entry = loadNutritionData() ?? getPlaceholderEntry()
48
+
49
+ // Refresh every 30 minutes for better data freshness
50
+ let nextUpdate = Calendar.current.date(byAdding: .minute, value: 30, to: currentDate)!
51
+ return Timeline(entries: [entry], policy: .after(nextUpdate))
52
+ }
53
+
54
+ private func loadNutritionData() -> CalorieEntry? {
55
+ guard let sharedDefaults = sharedDefaults,
56
+ let data = sharedDefaults.data(forKey: "widget_nutrition_summary"),
57
+ let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
58
+ return nil
59
+ }
60
+
61
+ let currentCalories = json["currentCalories"] as? Int ?? 0
62
+ let targetCalories = json["targetCalories"] as? Int ?? 2000
63
+ let caloriesLeft = json["caloriesLeft"] as? Int ?? targetCalories
64
+ let progress = json["progress"] as? Int ?? 0
65
+
66
+ var macros = getHardcodedMacros()
67
+ if let macrosData = json["macros"] as? [String: Any] {
68
+ if let protein = macrosData["protein"] as? [String: Int] {
69
+ macros.protein.current = protein["current"] ?? 0
70
+ macros.protein.target = protein["target"] ?? 150
71
+ }
72
+ if let carbs = macrosData["carbs"] as? [String: Int] {
73
+ macros.carbs.current = carbs["current"] ?? 0
74
+ macros.carbs.target = carbs["target"] ?? 250
75
+ }
76
+ if let fat = macrosData["fat"] as? [String: Int] {
77
+ macros.fat.current = fat["current"] ?? 0
78
+ macros.fat.target = fat["target"] ?? 67
79
+ }
80
+ }
81
+
82
+ return CalorieEntry(
83
+ date: Date(),
84
+ currentCalories: currentCalories,
85
+ targetCalories: targetCalories,
86
+ caloriesLeft: caloriesLeft,
87
+ progress: progress,
88
+ macros: macros
89
+ )
90
+ }
91
+
92
+ private func getPlaceholderEntry() -> CalorieEntry {
93
+ return CalorieEntry(
94
+ date: Date(),
95
+ currentCalories: 1450,
96
+ targetCalories: 2000,
97
+ caloriesLeft: 550,
98
+ progress: 73,
99
+ macros: getHardcodedMacros()
100
+ )
101
+ }
102
+
103
+ private func getHardcodedMacros() -> MacroData {
104
+ return MacroData(
105
+ protein: (current: 95, target: 150),
106
+ carbs: (current: 180, target: 250),
107
+ fat: (current: 52, target: 67)
108
+ )
109
+ }
110
+ }
111
+
112
+ // MARK: - Widget Views
113
+ @available(iOSApplicationExtension 17.0, *)
114
+ struct CalorieTrackerWidgetEntryView: View {
115
+ var entry: CalorieProvider.Entry
116
+ @Environment(\.widgetFamily) var family
117
+
118
+ var body: some View {
119
+ switch family {
120
+ case .systemSmall:
121
+ SmallCalorieView(entry: entry)
122
+ case .systemMedium:
123
+ MediumCalorieView(entry: entry)
124
+ case .systemLarge:
125
+ LargeCalorieView(entry: entry)
126
+ default:
127
+ MediumCalorieView(entry: entry)
128
+ }
129
+ }
130
+ }
131
+
132
+ @available(iOSApplicationExtension 17.0, *)
133
+ struct SmallCalorieView: View {
134
+ let entry: CalorieEntry
135
+
136
+ var body: some View {
137
+ VStack(spacing: 8) {
138
+ HStack {
139
+ Text("Calories")
140
+ .font(.system(size: 14, weight: .bold))
141
+ .foregroundColor(.primary)
142
+ Spacer()
143
+ Text("🍎")
144
+ .font(.system(size: 16))
145
+ }
146
+
147
+ Spacer()
148
+
149
+ ZStack {
150
+ Circle()
151
+ .stroke(Color.gray.opacity(0.3), lineWidth: 6)
152
+ .frame(width: 80, height: 80)
153
+
154
+ Circle()
155
+ .trim(from: 0, to: CGFloat(entry.progress) / 100)
156
+ .stroke(Color.blue, style: StrokeStyle(lineWidth: 6, lineCap: .round))
157
+ .frame(width: 80, height: 80)
158
+ .rotationEffect(.degrees(-90))
159
+
160
+ VStack(spacing: 2) {
161
+ Text("\(entry.caloriesLeft)")
162
+ .font(.system(size: 18, weight: .bold))
163
+ .foregroundColor(.primary)
164
+ Text("left")
165
+ .font(.system(size: 10))
166
+ .foregroundColor(.secondary)
167
+ }
168
+ }
169
+
170
+ Spacer()
171
+ }
172
+ .padding(12)
173
+ .containerBackground(Color.clear, for: .widget)
174
+ }
175
+ }
176
+
177
+ @available(iOSApplicationExtension 17.0, *)
178
+ struct MediumCalorieView: View {
179
+ let entry: CalorieEntry
180
+
181
+ var body: some View {
182
+ HStack(spacing: 12) {
183
+ // Left side - Circular progress
184
+ VStack(alignment: .leading, spacing: 8) {
185
+ HStack {
186
+ Text("Calorie")
187
+ .font(.system(size: 14, weight: .bold))
188
+ .foregroundColor(.primary)
189
+ .lineLimit(1)
190
+ Spacer()
191
+ Text("🍎")
192
+ .font(.system(size: 16))
193
+ }
194
+
195
+ Spacer()
196
+
197
+ ZStack {
198
+ Circle()
199
+ .stroke(Color.gray.opacity(0.3), lineWidth: 6)
200
+ .frame(width: 80, height: 80)
201
+
202
+ Circle()
203
+ .trim(from: 0, to: CGFloat(entry.progress) / 100)
204
+ .stroke(Color.blue, style: StrokeStyle(lineWidth: 6, lineCap: .round))
205
+ .frame(width: 80, height: 80)
206
+ .rotationEffect(.degrees(-90))
207
+
208
+ VStack(spacing: 1) {
209
+ Text("\(entry.caloriesLeft)")
210
+ .font(.system(size: 16, weight: .bold))
211
+ .foregroundColor(.primary)
212
+ Text("left")
213
+ .font(.system(size: 8))
214
+ .foregroundColor(.secondary)
215
+ }
216
+ }
217
+
218
+ Spacer()
219
+ }
220
+
221
+ // Right side - Macros
222
+ VStack(spacing: 6) {
223
+ MacroProgressBar(
224
+ label: "Protein",
225
+ current: entry.macros.protein.current,
226
+ target: entry.macros.protein.target,
227
+ color: .red
228
+ )
229
+ MacroProgressBar(
230
+ label: "Carbs",
231
+ current: entry.macros.carbs.current,
232
+ target: entry.macros.carbs.target,
233
+ color: .orange
234
+ )
235
+ MacroProgressBar(
236
+ label: "Fat",
237
+ current: entry.macros.fat.current,
238
+ target: entry.macros.fat.target,
239
+ color: .blue
240
+ )
241
+ }
242
+ }
243
+ .padding(14)
244
+ .containerBackground(Color.clear, for: .widget)
245
+ }
246
+ }
247
+
248
+ @available(iOSApplicationExtension 17.0, *)
249
+ struct LargeCalorieView: View {
250
+ let entry: CalorieEntry
251
+
252
+ var body: some View {
253
+ VStack(alignment: .leading, spacing: 14) {
254
+ HStack {
255
+ Text("Calorie Tracker")
256
+ .font(.system(size: 16, weight: .bold))
257
+ .foregroundColor(.primary)
258
+ Spacer()
259
+ Text("🍎")
260
+ .font(.system(size: 18))
261
+ }
262
+
263
+ HStack(spacing: 16) {
264
+ ZStack {
265
+ Circle()
266
+ .stroke(Color.gray.opacity(0.3), lineWidth: 8)
267
+ .frame(width: 90, height: 90)
268
+
269
+ Circle()
270
+ .trim(from: 0, to: CGFloat(entry.progress) / 100)
271
+ .stroke(Color.blue, style: StrokeStyle(lineWidth: 8, lineCap: .round))
272
+ .frame(width: 90, height: 90)
273
+ .rotationEffect(.degrees(-90))
274
+
275
+ VStack(spacing: 1) {
276
+ Text("\(entry.caloriesLeft)")
277
+ .font(.system(size: 18, weight: .bold))
278
+ .foregroundColor(.primary)
279
+ Text("left")
280
+ .font(.system(size: 9))
281
+ .foregroundColor(.secondary)
282
+ }
283
+ }
284
+
285
+ VStack(spacing: 8) {
286
+ MacroProgressBar(
287
+ label: "Protein",
288
+ current: entry.macros.protein.current,
289
+ target: entry.macros.protein.target,
290
+ color: .red
291
+ )
292
+ MacroProgressBar(
293
+ label: "Carbs",
294
+ current: entry.macros.carbs.current,
295
+ target: entry.macros.carbs.target,
296
+ color: .orange
297
+ )
298
+ MacroProgressBar(
299
+ label: "Fat",
300
+ current: entry.macros.fat.current,
301
+ target: entry.macros.fat.target,
302
+ color: .blue
303
+ )
304
+ }
305
+ }
306
+
307
+ Spacer()
308
+
309
+ VStack(spacing: 6) {
310
+ HStack {
311
+ Text("Today's intake")
312
+ .font(.system(size: 10))
313
+ .foregroundColor(.secondary)
314
+ Spacer()
315
+ Text("\(entry.currentCalories) / \(entry.targetCalories) cal")
316
+ .font(.system(size: 10, weight: .semibold))
317
+ .foregroundColor(.primary)
318
+ }
319
+
320
+ HStack {
321
+ Text("Progress")
322
+ .font(.system(size: 10))
323
+ .foregroundColor(.secondary)
324
+ Spacer()
325
+ Text("\(entry.progress)%")
326
+ .font(.system(size: 10, weight: .semibold))
327
+ .foregroundColor(.blue)
328
+ }
329
+
330
+ // Quick action button for large widget
331
+ HStack(spacing: 8) {
332
+ Link(destination: URL(string: "ezcaltracker://(root)/(protected)/add-meal?source=widget&mode=quick_entry")!) {
333
+ HStack(spacing: 4) {
334
+ Image(systemName: "plus.circle.fill")
335
+ .font(.system(size: 12))
336
+ .foregroundColor(.blue)
337
+ Text("Log Food")
338
+ .font(.system(size: 11, weight: .medium))
339
+ .foregroundColor(.blue)
340
+ }
341
+ .padding(.horizontal, 8)
342
+ .padding(.vertical, 4)
343
+ .background(Color.blue.opacity(0.1))
344
+ .cornerRadius(6)
345
+ }
346
+
347
+ Spacer()
348
+
349
+ Link(destination: URL(string: "ezcaltracker://(root)/(protected)/analysis/food-recognition?source=widget")!) {
350
+ HStack(spacing: 4) {
351
+ Image(systemName: "camera.fill")
352
+ .font(.system(size: 12))
353
+ .foregroundColor(.green)
354
+ Text("Scan")
355
+ .font(.system(size: 11, weight: .medium))
356
+ .foregroundColor(.green)
357
+ }
358
+ .padding(.horizontal, 8)
359
+ .padding(.vertical, 4)
360
+ .background(Color.green.opacity(0.1))
361
+ .cornerRadius(6)
362
+ }
363
+ }
364
+ }
365
+ }
366
+ .padding(18)
367
+ .containerBackground(Color.clear, for: .widget)
368
+ }
369
+ }
370
+
371
+ @available(iOSApplicationExtension 17.0, *)
372
+ struct MacroProgressBar: View {
373
+ let label: String
374
+ let current: Int
375
+ let target: Int
376
+ let color: Color
377
+
378
+ private var progress: Double {
379
+ Double(current) / Double(target)
380
+ }
381
+
382
+ var body: some View {
383
+ VStack(alignment: .leading, spacing: 4) {
384
+ HStack {
385
+ Text(label)
386
+ .font(.system(size: 10, weight: .medium))
387
+ .foregroundColor(.primary)
388
+ Spacer()
389
+ Text("\(current)g / \(target)g")
390
+ .font(.system(size: 9))
391
+ .foregroundColor(.secondary)
392
+ }
393
+
394
+ GeometryReader { geometry in
395
+ ZStack(alignment: .leading) {
396
+ RoundedRectangle(cornerRadius: 2)
397
+ .fill(Color.gray.opacity(0.3))
398
+ .frame(height: 6)
399
+
400
+ RoundedRectangle(cornerRadius: 2)
401
+ .fill(color)
402
+ .frame(width: geometry.size.width * min(progress, 1.0), height: 6)
403
+ }
404
+ }
405
+ .frame(height: 6)
406
+ }
407
+ }
408
+ }
409
+
410
+ // MARK: - Widget Configuration
411
+ @available(iOSApplicationExtension 17.0, *)
412
+ struct CalorieTrackerWidget: Widget {
413
+ let kind: String = "CalorieTrackerWidget"
414
+
415
+ var body: some WidgetConfiguration {
416
+ AppIntentConfiguration(kind: kind, intent: CalorieWidgetConfigurationIntent.self, provider: CalorieProvider()) { entry in
417
+ CalorieTrackerWidgetEntryView(entry: entry)
418
+ .widgetURL(URL(string: "ezcaltracker://(root)/(protected)/tracker-app?source=widget"))
419
+ }
420
+ .configurationDisplayName("Calorie Tracker")
421
+ .description("Track your daily calorie intake and macros.")
422
+ .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
423
+ }
424
+ }