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.
- package/FINAL-STATUS.md +144 -0
- package/HOW-IT-WORKS.md +559 -0
- package/PLAN.md +453 -0
- package/README.md +129 -0
- package/RECIPES-READY.md +172 -0
- package/STATUS.md +199 -0
- package/SUCCESS.md +259 -0
- package/TESTING-CHECKLIST.md +450 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/64907821e2634080acce34618d2f3d4c/blobs/11f2769953c717e188062bc644da97c1fd1e4d6d0813a226ce7567dba759afab0000019a736fb8d4 +1 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/0b03767237c0408301af51ca35d4b09470cbc479c7e5f23cc9de774749d23c59.sqlite +0 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/0b03767237c0408301af51ca35d4b09470cbc479c7e5f23cc9de774749d23c59.sqlite-shm +0 -0
- package/cloudflare-worker/.wrangler/state/v3/kv/miniflare-KVNamespaceObject/0b03767237c0408301af51ca35d4b09470cbc479c7e5f23cc9de774749d23c59.sqlite-wal +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/miniflare-R2BucketObject/d1cc388a1a0ef44dd5669fd1a165d168b61362136c8b5fa50aefd96c72688e54.sqlite +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/miniflare-R2BucketObject/d1cc388a1a0ef44dd5669fd1a165d168b61362136c8b5fa50aefd96c72688e54.sqlite-shm +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/miniflare-R2BucketObject/d1cc388a1a0ef44dd5669fd1a165d168b61362136c8b5fa50aefd96c72688e54.sqlite-wal +0 -0
- package/cloudflare-worker/.wrangler/state/v3/r2/vibefast-recipes/blobs/620e8cf7c35d9806da25dee237e1d7e8b2432bd98f755b60e2c7f08a48d2c7b90000019a73736484 +0 -0
- package/cloudflare-worker/MIGRATION.md +160 -0
- package/cloudflare-worker/QUICKSTART.md +200 -0
- package/cloudflare-worker/README.md +242 -0
- package/cloudflare-worker/generate-token.js +32 -0
- package/cloudflare-worker/mini-native@latest.zip +0 -0
- package/cloudflare-worker/setup.sh +143 -0
- package/cloudflare-worker/test-recipe/apps/native/src/app/mini/index.tsx +15 -0
- package/cloudflare-worker/test-recipe/recipe.json +16 -0
- package/cloudflare-worker/worker.js +308 -0
- package/cloudflare-worker/wrangler.toml +13 -0
- package/dist/commands/add.d.ts +3 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +149 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/devices.d.ts +3 -0
- package/dist/commands/devices.d.ts.map +1 -0
- package/dist/commands/devices.js +35 -0
- package/dist/commands/devices.js.map +1 -0
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +67 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +40 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +23 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +3 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +16 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/remove.d.ts +3 -0
- package/dist/commands/remove.d.ts.map +1 -0
- package/dist/commands/remove.js +67 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/core/__tests__/journal.test.d.ts +2 -0
- package/dist/core/__tests__/journal.test.d.ts.map +1 -0
- package/dist/core/__tests__/journal.test.js +101 -0
- package/dist/core/__tests__/journal.test.js.map +1 -0
- package/dist/core/__tests__/validate.test.d.ts +2 -0
- package/dist/core/__tests__/validate.test.d.ts.map +1 -0
- package/dist/core/__tests__/validate.test.js +53 -0
- package/dist/core/__tests__/validate.test.js.map +1 -0
- package/dist/core/archive.d.ts +2 -0
- package/dist/core/archive.d.ts.map +1 -0
- package/dist/core/archive.js +59 -0
- package/dist/core/archive.js.map +1 -0
- package/dist/core/auth.d.ts +15 -0
- package/dist/core/auth.d.ts.map +1 -0
- package/dist/core/auth.js +76 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/codemod.d.ts +20 -0
- package/dist/core/codemod.d.ts.map +1 -0
- package/dist/core/codemod.js +150 -0
- package/dist/core/codemod.js.map +1 -0
- package/dist/core/fsx.d.ts +12 -0
- package/dist/core/fsx.d.ts.map +1 -0
- package/dist/core/fsx.js +70 -0
- package/dist/core/fsx.js.map +1 -0
- package/dist/core/http.d.ts +30 -0
- package/dist/core/http.d.ts.map +1 -0
- package/dist/core/http.js +95 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/journal.d.ts +18 -0
- package/dist/core/journal.d.ts.map +1 -0
- package/dist/core/journal.js +34 -0
- package/dist/core/journal.js.map +1 -0
- package/dist/core/log.d.ts +8 -0
- package/dist/core/log.d.ts.map +1 -0
- package/dist/core/log.js +9 -0
- package/dist/core/log.js.map +1 -0
- package/dist/core/pathGuard.d.ts +3 -0
- package/dist/core/pathGuard.d.ts.map +1 -0
- package/dist/core/pathGuard.js +18 -0
- package/dist/core/pathGuard.js.map +1 -0
- package/dist/core/paths.d.ts +11 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +22 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/validate.d.ts +8 -0
- package/dist/core/validate.d.ts.map +1 -0
- package/dist/core/validate.js +27 -0
- package/dist/core/validate.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/docs/decisions.md +55 -0
- package/package.json +39 -0
- package/recipes/audio-recorder/apps/native/src/app/audio-recorder/index.tsx +5 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-player.tsx +301 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-recorder.tsx +373 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/audio-waveform.tsx +270 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/index.ts +4 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/components/recording-list.tsx +89 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-player-demo.tsx +66 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-recorder-cloud.tsx +68 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/audio-recorder-interview.tsx +102 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/basic.tsx +27 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/index.ts +5 -0
- package/recipes/audio-recorder/apps/native/src/features/audio-recorder/demo/with-recording-list-demo.tsx +82 -0
- package/recipes/audio-recorder/recipe.json +22 -0
- package/recipes/audio-recorder@latest.zip +0 -0
- package/recipes/charts/apps/native/src/app/charts/index.tsx +3 -0
- package/recipes/charts/apps/native/src/features/charts/README.md +185 -0
- package/recipes/charts/apps/native/src/features/charts/app/preview.tsx +223 -0
- package/recipes/charts/apps/native/src/features/charts/components/area-chart.tsx +40 -0
- package/recipes/charts/apps/native/src/features/charts/components/bar-chart.tsx +143 -0
- package/recipes/charts/apps/native/src/features/charts/components/candlestick-chart.tsx +196 -0
- package/recipes/charts/apps/native/src/features/charts/components/chart-card.tsx +65 -0
- package/recipes/charts/apps/native/src/features/charts/components/column-chart.tsx +143 -0
- package/recipes/charts/apps/native/src/features/charts/components/doughnut-chart.tsx +246 -0
- package/recipes/charts/apps/native/src/features/charts/components/index.ts +10 -0
- package/recipes/charts/apps/native/src/features/charts/components/line-chart.tsx +308 -0
- package/recipes/charts/apps/native/src/features/charts/components/radar-chart.tsx +180 -0
- package/recipes/charts/apps/native/src/features/charts/components/radial-bar-chart.tsx +188 -0
- package/recipes/charts/apps/native/src/features/charts/components/stacked-area-chart.tsx +265 -0
- package/recipes/charts/apps/native/src/features/charts/components/stacked-bar-chart.tsx +322 -0
- package/recipes/charts/apps/native/src/features/charts/data/mock-data.ts +183 -0
- package/recipes/charts/apps/native/src/features/charts/types/index.ts +66 -0
- package/recipes/charts/recipe.json +22 -0
- package/recipes/charts@latest.zip +0 -0
- package/recipes/chatbot/apps/native/src/app/chatbot/index.tsx +1 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/app/index.tsx +302 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-header-buttons.tsx +59 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-input-bar.tsx +469 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-markdown.tsx +575 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-message-bubble.tsx +246 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/chat-settings-modal.tsx +161 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/image-preview-list.tsx +115 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/code-block.tsx +165 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/index.ts +10 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/markdown/table-renderer.tsx +129 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/message-error-boundary.tsx +78 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/message-list.tsx +173 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/model-selector.tsx +283 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/report-content-modal.tsx +188 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/components/suggested-messages.tsx +67 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/constants/models.ts +20 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/constants/report-reasons.ts +9 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-attachment-cache.ts +143 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chat-config.ts +664 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chat-handlers.ts +359 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-chatbot-settings.ts +89 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-conversation.ts +79 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-image-picker.ts +122 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-keyboard-coordinator.ts +161 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/hooks/use-smart-scroll-manager.ts +207 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/index.ts +86 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/models.ts +162 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/providers.ts +62 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/models/types.ts +40 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/services/file-uploader.ts +238 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/services/message-handler-service.ts +180 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/types/index.ts +60 -0
- package/recipes/chatbot/apps/native/src/features/chatbot/utils/chat-telemetry.ts +91 -0
- package/recipes/chatbot/recipe.json +22 -0
- package/recipes/chatbot@latest.zip +0 -0
- package/recipes/image-generator/apps/native/src/app/image-generator/gallery.tsx +3 -0
- package/recipes/image-generator/apps/native/src/app/image-generator/index.tsx +3 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/_layout.tsx +25 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/gallery.tsx +217 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/app/index.tsx +237 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/gallery-image.tsx +26 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-detail-modal.tsx +215 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-model-selector.tsx +210 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/components/image-placeholder.tsx +26 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-gallery.ts +71 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator-settings.ts +152 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/hooks/use-image-generator.ts +93 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/models/models.ts +66 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/services/image-gallery-service.ts +98 -0
- package/recipes/image-generator/apps/native/src/features/image-generator/services/image-save-service.ts +121 -0
- package/recipes/image-generator/recipe.json +22 -0
- package/recipes/image-generator@latest.zip +0 -0
- package/recipes/quiz/apps/native/src/app/quiz/index.tsx +47 -0
- package/recipes/quiz/apps/native/src/features/quiz/components/question.tsx +67 -0
- package/recipes/quiz/apps/native/src/features/quiz/config.ts +11 -0
- package/recipes/quiz/apps/native/src/features/quiz/index.tsx +133 -0
- package/recipes/quiz/recipe.json +22 -0
- package/recipes/quiz@latest.zip +0 -0
- package/recipes/tracker-app/apps/native/src/app/tracker-app/index.tsx +1 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/app/index.tsx +108 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/animated-number.tsx +102 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/calorie-card.tsx +66 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/circular-progress.tsx +97 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/floating-add-button.tsx +27 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/macro-card.tsx +80 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/promo-banner.tsx +98 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/recently-logged.tsx +64 -0
- package/recipes/tracker-app/apps/native/src/features/tracker-app/components/week-calendar.tsx +68 -0
- package/recipes/tracker-app/recipe.json +22 -0
- package/recipes/tracker-app@latest.zip +0 -0
- package/recipes/upload-all.sh +32 -0
- package/recipes/voice-bot/apps/native/src/app/voice-bot/index.tsx +27 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/README.md +185 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/conversation-status.tsx +76 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/index.ts +4 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/message-input.tsx +98 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/voice-bot-screen.tsx +173 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/components/voice-controls.tsx +73 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/index.ts +3 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/services/index.ts +1 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/services/use-voice-bot.ts +161 -0
- package/recipes/voice-bot/apps/native/src/features/voice-bot/types.ts +29 -0
- package/recipes/voice-bot/recipe.json +22 -0
- package/recipes/voice-bot@latest.zip +0 -0
- package/scripts/create-recipes.mjs +189 -0
- package/src/commands/add.ts +183 -0
- package/src/commands/devices.ts +38 -0
- package/src/commands/doctor.ts +67 -0
- package/src/commands/list.ts +45 -0
- package/src/commands/login.ts +24 -0
- package/src/commands/logout.ts +15 -0
- package/src/commands/remove.ts +78 -0
- package/src/core/__tests__/journal.test.ts +119 -0
- package/src/core/__tests__/validate.test.ts +64 -0
- package/src/core/archive.ts +69 -0
- package/src/core/auth.ts +103 -0
- package/src/core/codemod.ts +211 -0
- package/src/core/fsx.ts +80 -0
- package/src/core/http.ts +136 -0
- package/src/core/journal.ts +64 -0
- package/src/core/log.ts +9 -0
- package/src/core/pathGuard.ts +22 -0
- package/src/core/paths.ts +33 -0
- package/src/core/validate.ts +44 -0
- package/src/index.ts +27 -0
- package/test-critical-cases.mjs +258 -0
- package/tsconfig.json +21 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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
|