vibefast-cli 0.1.1 → 0.1.2

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/README.md CHANGED
@@ -17,8 +17,10 @@ npx vibefast-cli <command>
17
17
  ## Quick Start
18
18
 
19
19
  1. **Login with your token:**
20
+ Use the same license key you received in your LemonSqueezy receipt:
21
+
20
22
  ```bash
21
- vf login --token YOUR_TOKEN_HERE
23
+ vf login --token YOUR_LICENSE_KEY
22
24
  ```
23
25
 
24
26
  2. **Verify your setup:**
@@ -9,6 +9,169 @@ async function sha256(str) {
9
9
  .join("");
10
10
  }
11
11
 
12
+ function parseEnvInt(value, fallback) {
13
+ const num = parseInt(value ?? "", 10);
14
+ return Number.isFinite(num) ? num : fallback;
15
+ }
16
+
17
+ function getDeviceLimitForVariant(variantId, env) {
18
+ const defaultLimit = parseEnvInt(env.LICENSE_MAX_DEVICES, 2);
19
+ const proId = parseEnvInt(env.VIBEFAST_PRO_ID, null);
20
+ const coreId = parseEnvInt(env.VIBEFAST_CORE_ID, null);
21
+ if (variantId === proId) {
22
+ return parseEnvInt(env.VIBEFAST_PRO_DEVICE_LIMIT, defaultLimit);
23
+ }
24
+ if (variantId === coreId) {
25
+ return parseEnvInt(env.VIBEFAST_CORE_DEVICE_LIMIT, defaultLimit);
26
+ }
27
+ return defaultLimit;
28
+ }
29
+
30
+ async function validateLicenseWithLemon(licenseKey, env) {
31
+ if (!env.LEMON_API_KEY) {
32
+ throw new Error("LEMON_API_KEY is not configured");
33
+ }
34
+
35
+ const storeId = parseEnvInt(env.LEMON_STORE_ID, null);
36
+ const productId = parseEnvInt(env.VIBEFAST_PRODUCT_ID, null);
37
+ const allowedVariants = [
38
+ parseEnvInt(env.VIBEFAST_PRO_ID, null),
39
+ parseEnvInt(env.VIBEFAST_CORE_ID, null),
40
+ ].filter((v) => Number.isInteger(v));
41
+
42
+ const res = await fetch("https://api.lemonsqueezy.com/v1/licenses/validate", {
43
+ method: "POST",
44
+ headers: {
45
+ Authorization: `Bearer ${env.LEMON_API_KEY}`,
46
+ "Content-Type": "application/json",
47
+ Accept: "application/json",
48
+ },
49
+ body: JSON.stringify({ license_key: licenseKey }),
50
+ });
51
+
52
+ if (!res.ok) {
53
+ const detail = await res.text().catch(() => "");
54
+ console.error(
55
+ `[Worker] LemonSqueezy validation failed: ${res.status} ${res.statusText} ${detail}`
56
+ );
57
+ return {
58
+ ok: false,
59
+ status: res.status === 401 ? 401 : 502,
60
+ message:
61
+ res.status === 401
62
+ ? "License validation is unauthorized. Check your LemonSqueezy API key."
63
+ : "Unable to validate license at this time.",
64
+ };
65
+ }
66
+
67
+ const data = await res.json();
68
+ if (!data.valid) {
69
+ return {
70
+ ok: false,
71
+ status: 401,
72
+ message: "License key is invalid.",
73
+ };
74
+ }
75
+
76
+ const keyData = data.license_key;
77
+ const meta = data.meta;
78
+
79
+ if (meta.store_id !== storeId) {
80
+ return {
81
+ ok: false,
82
+ status: 401,
83
+ message: "License does not belong to this store.",
84
+ };
85
+ }
86
+
87
+ if (meta.product_id !== productId) {
88
+ return {
89
+ ok: false,
90
+ status: 401,
91
+ message: "License is for a different product.",
92
+ };
93
+ }
94
+
95
+ if (!allowedVariants.includes(meta.variant_id)) {
96
+ return {
97
+ ok: false,
98
+ status: 401,
99
+ message: "License variant is not supported.",
100
+ };
101
+ }
102
+
103
+ if (!["inactive", "active"].includes(keyData.status)) {
104
+ return {
105
+ ok: false,
106
+ status: 401,
107
+ message: `License status '${keyData.status}' is not eligible.`,
108
+ };
109
+ }
110
+
111
+ const deviceLimit = getDeviceLimitForVariant(meta.variant_id, env);
112
+
113
+ return {
114
+ ok: true,
115
+ meta,
116
+ licenseStatus: keyData.status,
117
+ variantId: meta.variant_id,
118
+ deviceLimit,
119
+ };
120
+ }
121
+
122
+ async function authorizeLicense(token, env) {
123
+ if (!token) {
124
+ return { ok: false, status: 401, message: "Missing license token." };
125
+ }
126
+
127
+ const validation = await validateLicenseWithLemon(token, env);
128
+ if (!validation.ok) {
129
+ return validation;
130
+ }
131
+
132
+ const tokenHash = await sha256(token);
133
+ const kvKey = `lic:${tokenHash}`;
134
+ const now = new Date().toISOString();
135
+
136
+ let licenseRecord = await env.LICENSES.get(kvKey, { type: "json" });
137
+ if (!licenseRecord) {
138
+ licenseRecord = {
139
+ status: "active",
140
+ devices: [],
141
+ max_devices: validation.deviceLimit,
142
+ variant_id: validation.variantId,
143
+ lastSeen: now,
144
+ };
145
+ } else {
146
+ licenseRecord.max_devices =
147
+ licenseRecord.max_devices || validation.deviceLimit;
148
+ licenseRecord.variant_id = validation.variantId;
149
+ licenseRecord.lastSeen = now;
150
+ if (!Array.isArray(licenseRecord.devices)) {
151
+ licenseRecord.devices = [];
152
+ }
153
+ }
154
+
155
+ await env.LICENSES.put(kvKey, JSON.stringify(licenseRecord));
156
+
157
+ return {
158
+ ok: true,
159
+ tokenHash,
160
+ kvKey,
161
+ licenseRecord,
162
+ deviceLimit: validation.deviceLimit,
163
+ };
164
+ }
165
+
166
+ async function authorizeFromHeader(request, env) {
167
+ const authHeader = request.headers.get("Authorization");
168
+ if (!authHeader?.startsWith("Bearer ")) {
169
+ return { ok: false, status: 401, message: "Missing authorization header." };
170
+ }
171
+ const token = authHeader.slice(7).trim();
172
+ return authorizeLicense(token, env);
173
+ }
174
+
12
175
  // --- Utility: JSON Response Helper ---
13
176
  function json(data, status = 200) {
14
177
  return new Response(JSON.stringify(data, null, 2), {
@@ -94,19 +257,20 @@ async function handleRecipeFetch(request, env) {
94
257
  return json({ error: "Missing required fields" }, 400);
95
258
  }
96
259
 
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);
260
+ const auth = await authorizeLicense(token, env);
261
+ if (!auth.ok) {
262
+ return json(
263
+ {
264
+ ok: false,
265
+ error: "License validation failed",
266
+ message: auth.message,
267
+ },
268
+ auth.status || 403
269
+ );
108
270
  }
109
271
 
272
+ const { tokenHash, kvKey, licenseRecord } = auth;
273
+
110
274
  // Check rate limit
111
275
  if (!checkRateLimit(tokenHash)) {
112
276
  return json({
@@ -117,19 +281,19 @@ async function handleRecipeFetch(request, env) {
117
281
  }
118
282
 
119
283
  // Enforce device slots
120
- if (!license.devices.includes(device.id)) {
121
- if (license.devices.length >= (license.max_devices || 2)) {
284
+ if (!licenseRecord.devices.includes(device.id)) {
285
+ if (licenseRecord.devices.length >= (licenseRecord.max_devices || 2)) {
122
286
  return json({
123
287
  ok: false,
124
288
  error: "Device limit reached",
125
- message: `Maximum ${license.max_devices || 2} devices allowed. Use 'vf devices --deactivate <id>' to free a slot.`
289
+ message: `Maximum ${licenseRecord.max_devices || 2} devices allowed. Use 'vf devices --deactivate <id>' to free a slot.`
126
290
  }, 403);
127
291
  }
128
292
 
129
293
  // Add new device
130
- license.devices.push(device.id);
131
- license.lastSeen = new Date().toISOString();
132
- await env.LICENSES.put(kvKey, JSON.stringify(license));
294
+ licenseRecord.devices.push(device.id);
295
+ licenseRecord.lastSeen = new Date().toISOString();
296
+ await env.LICENSES.put(kvKey, JSON.stringify(licenseRecord));
133
297
  console.log(`[Worker] Added device ${device.id} to license`);
134
298
  }
135
299
 
@@ -183,18 +347,9 @@ async function handleRecipeFetch(request, env) {
183
347
  // --- Handler: List Recipes ---
184
348
  async function handleRecipesList(request, env) {
185
349
  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);
350
+ const auth = await authorizeFromHeader(request, env);
351
+ if (!auth.ok) {
352
+ return json({ error: auth.message }, auth.status || 403);
198
353
  }
199
354
 
200
355
  // List all recipes from R2
@@ -232,30 +387,21 @@ async function handleRecipesList(request, env) {
232
387
  // --- Handler: List Devices ---
233
388
  async function handleDevicesList(request, env) {
234
389
  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);
390
+ const auth = await authorizeFromHeader(request, env);
391
+ if (!auth.ok) {
392
+ return json({ error: auth.message }, auth.status || 403);
247
393
  }
248
394
 
249
- const devices = license.devices.map(id => ({
395
+ const devices = (auth.licenseRecord.devices || []).map(id => ({
250
396
  id,
251
397
  os: 'unknown',
252
398
  arch: 'unknown',
253
- lastSeen: license.lastSeen || 'unknown',
399
+ lastSeen: auth.licenseRecord.lastSeen || 'unknown',
254
400
  }));
255
401
 
256
402
  return json({
257
403
  devices,
258
- maxDevices: license.max_devices || 2,
404
+ maxDevices: auth.licenseRecord.max_devices || 2,
259
405
  });
260
406
 
261
407
  } catch (err) {
@@ -267,12 +413,11 @@ async function handleDevicesList(request, env) {
267
413
  // --- Handler: Deactivate Device ---
268
414
  async function handleDeviceDeactivate(request, env) {
269
415
  try {
270
- const authHeader = request.headers.get("Authorization");
271
- if (!authHeader?.startsWith("Bearer ")) {
272
- return json({ error: "Missing authorization" }, 401);
416
+ const auth = await authorizeFromHeader(request, env);
417
+ if (!auth.ok) {
418
+ return json({ error: auth.message }, auth.status || 403);
273
419
  }
274
420
 
275
- const token = authHeader.slice(7);
276
421
  const body = await request.json();
277
422
  const { deviceId } = body;
278
423
 
@@ -280,17 +425,10 @@ async function handleDeviceDeactivate(request, env) {
280
425
  return json({ error: "Missing deviceId" }, 400);
281
426
  }
282
427
 
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));
428
+ auth.licenseRecord.devices = (auth.licenseRecord.devices || []).filter(
429
+ id => id !== deviceId
430
+ );
431
+ await env.LICENSES.put(auth.kvKey, JSON.stringify(auth.licenseRecord));
294
432
 
295
433
  return json({ ok: true, message: "Device deactivated" });
296
434
 
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE;QACN,EAAE,EAAE,MAAM,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA0B3F;AAED,wBAAsB,WAAW,CAAC,iBAAiB,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAyB9F;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAiB7D;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAa7D;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAarF"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE;QACN,EAAE,EAAE,MAAM,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA0B3F;AAED,wBAAsB,WAAW,CAAC,iBAAiB,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAyB9F;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAwB7D;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAoB7D;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBrF"}
package/dist/core/http.js CHANGED
@@ -59,7 +59,15 @@ export async function listRecipes(token) {
59
59
  },
60
60
  });
61
61
  if (!response.ok) {
62
- throw new Error(`Failed to fetch recipes: ${response.statusText}`);
62
+ let message = response.statusText;
63
+ try {
64
+ const body = (await response.json());
65
+ message = body?.error || body?.message || message;
66
+ }
67
+ catch {
68
+ // ignore JSON parse errors
69
+ }
70
+ throw new Error(`Failed to fetch recipes: ${message}`);
63
71
  }
64
72
  return response.json();
65
73
  }
@@ -75,7 +83,15 @@ export async function listDevices(token) {
75
83
  },
76
84
  });
77
85
  if (!response.ok) {
78
- throw new Error(`Failed to fetch devices: ${response.statusText}`);
86
+ let message = response.statusText;
87
+ try {
88
+ const body = (await response.json());
89
+ message = body?.error || body?.message || message;
90
+ }
91
+ catch {
92
+ // ignore
93
+ }
94
+ throw new Error(`Failed to fetch devices: ${message}`);
79
95
  }
80
96
  return response.json();
81
97
  }
@@ -89,7 +105,15 @@ export async function deactivateDevice(token, deviceId) {
89
105
  body: JSON.stringify({ deviceId }),
90
106
  });
91
107
  if (!response.ok) {
92
- throw new Error(`Failed to deactivate device: ${response.statusText}`);
108
+ let message = response.statusText;
109
+ try {
110
+ const body = (await response.json());
111
+ message = body?.error || body?.message || message;
112
+ }
113
+ catch {
114
+ // ignore
115
+ }
116
+ throw new Error(`Failed to deactivate device: ${message}`);
93
117
  }
94
118
  }
95
119
  //# sourceMappingURL=http.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "vibefast-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "CLI for installing VibeFast features into your monorepo",
5
5
  "type": "module",
6
6
  "bin": {
7
- "vf": "./dist/index.js"
7
+ "vf": "dist/index.js"
8
8
  },
9
9
  "scripts": {
10
10
  "build": "tsc",
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Usage:
5
+ # ./scripts/sync-worker.sh
6
+ #
7
+ # This script guides you through updating worker secrets and deploying the
8
+ # Cloudflare worker. It does NOT read or store your secrets; you paste them
9
+ # interactively when prompted by wrangler.
10
+
11
+ WORKER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/cloudflare-worker"
12
+
13
+ echo "==> Switching to worker directory: ${WORKER_DIR}"
14
+ cd "${WORKER_DIR}"
15
+
16
+ echo "======== Cloudflare Worker Sync ========"
17
+ echo "You will be prompted for the following secrets:"
18
+ echo " - LEMON_API_KEY (required)"
19
+ echo " - LEMON_STORE_ID (e.g., 190529)"
20
+ echo " - VIBEFAST_PRODUCT_ID (e.g., 560488)"
21
+ echo " - VIBEFAST_PRO_ID (e.g., 871411)"
22
+ echo " - VIBEFAST_CORE_ID (e.g., 871453)"
23
+ echo " - LICENSE_MAX_DEVICES (optional, press Enter to skip)"
24
+ echo
25
+ echo "None of these values are stored in this script."
26
+ echo
27
+
28
+ read -rp "Press Enter to begin entering secrets with wrangler..." _
29
+
30
+ wrangler secret put LEMON_API_KEY
31
+ # wrangler secret put LEMON_STORE_ID
32
+ # wrangler secret put VIBEFAST_PRODUCT_ID
33
+ # wrangler secret put VIBEFAST_PRO_ID
34
+ # wrangler secret put VIBEFAST_CORE_ID
35
+
36
+ # read -rp "Set LICENSE_MAX_DEVICES? (leave blank to skip) " DEVICE_LIMIT
37
+ # if [[ -n "${DEVICE_LIMIT}" ]]; then
38
+ # echo "${DEVICE_LIMIT}" | wrangler secret put LICENSE_MAX_DEVICES
39
+ fi
40
+
41
+ echo
42
+ echo "==> Deploying worker via wrangler..."
43
+ wrangler deploy
44
+
45
+ echo "==> Done. Worker secrets updated and deployment triggered."
package/src/core/http.ts CHANGED
@@ -96,7 +96,14 @@ export async function listRecipes(token: string): Promise<any> {
96
96
  });
97
97
 
98
98
  if (!response.ok) {
99
- throw new Error(`Failed to fetch recipes: ${response.statusText}`);
99
+ let message = response.statusText;
100
+ try {
101
+ const body = (await response.json()) as { error?: string; message?: string };
102
+ message = body?.error || body?.message || message;
103
+ } catch {
104
+ // ignore JSON parse errors
105
+ }
106
+ throw new Error(`Failed to fetch recipes: ${message}`);
100
107
  }
101
108
 
102
109
  return response.json();
@@ -114,7 +121,14 @@ export async function listDevices(token: string): Promise<any> {
114
121
  });
115
122
 
116
123
  if (!response.ok) {
117
- throw new Error(`Failed to fetch devices: ${response.statusText}`);
124
+ let message = response.statusText;
125
+ try {
126
+ const body = (await response.json()) as { error?: string; message?: string };
127
+ message = body?.error || body?.message || message;
128
+ } catch {
129
+ // ignore
130
+ }
131
+ throw new Error(`Failed to fetch devices: ${message}`);
118
132
  }
119
133
 
120
134
  return response.json();
@@ -131,6 +145,13 @@ export async function deactivateDevice(token: string, deviceId: string): Promise
131
145
  });
132
146
 
133
147
  if (!response.ok) {
134
- throw new Error(`Failed to deactivate device: ${response.statusText}`);
148
+ let message = response.statusText;
149
+ try {
150
+ const body = (await response.json()) as { error?: string; message?: string };
151
+ message = body?.error || body?.message || message;
152
+ } catch {
153
+ // ignore
154
+ }
155
+ throw new Error(`Failed to deactivate device: ${message}`);
135
156
  }
136
157
  }