Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ A simple, automatically updated site providing the latest download links for the

**Live Site:** [downloadcursor.app](https://downloadcursor.app)

### JSON API (LLM-friendly)

- **Latest release (all platforms):** `https://downloadcursor.app/api/latest-download`
- **Latest for a specific platform:** `https://downloadcursor.app/api/latest-download?platform=<key>`

Supported platform keys include: `win32-x64-user`, `win32-x64-system`, `win32-arm64-user`, `win32-arm64-system`, `darwin-universal`, `darwin-arm64`, `darwin-x64`, `linux-x64`, `linux-arm64`. Aliases accepted: `windows`, `windows-user`, `windows-system`, `mac`, `macos`, `macos-arm64`, `macos-x64`, `linux`.

Example response (platform-filtered):

```json
{
"version": "<version>",
"date": "<date>",
"platform": "win32-x64-user",
"url": "https://downloads.cursor.com/.../CursorUserSetup-x64-<version>.exe",
"sizeBytes": <sizeBytes>,
"sha256": "<sha256>"
}
```

For the full machine-readable history, you can also use `https://downloadcursor.app/version-history.json`.

![GitHub stars](https://img.shields.io/github/stars/accesstechnology-mike/cursor-downloads?style=social)
![Last commit](https://img.shields.io/github/last-commit/accesstechnology-mike/cursor-downloads)
![Update workflow](https://img.shields.io/github/actions/workflow/status/accesstechnology-mike/cursor-downloads/update.yml?branch=main)
Expand Down
114 changes: 114 additions & 0 deletions api/latest-download.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
const fs = require("fs");
const path = require("path");

async function readVersionHistory(req) {
const candidatePaths = [
path.join(process.cwd(), "version-history.json"),
path.join(__dirname, "..", "version-history.json"),
];

for (const historyPath of candidatePaths) {
try {
if (fs.existsSync(historyPath)) {
const raw = fs.readFileSync(historyPath, "utf-8");
return JSON.parse(raw);
}
} catch {}
}

// Fallback to HTTP fetch from the same host if local file not available
try {
const base = req?.headers?.host
? `https://${req.headers.host}`
: "https://downloadcursor.app";
const res = await fetch(new URL("/version-history.json", base));
if (res.ok) {
return await res.json();
}
} catch {}

throw new Error("version-history.json not found");
}

function getLatestVersion(history) {
if (!history || !Array.isArray(history.versions) || history.versions.length === 0) {
return null;
}
return history.versions[0];
}

function normalizePlatformParam(param) {
if (!param) return null;
const p = String(param).toLowerCase();
// Accept canonical keys exactly as in version-history.json
// Also accept a few friendly aliases commonly used by users/tools
const aliases = {
windows: "win32-x64-user",
"windows-user": "win32-x64-user",
"windows-system": "win32-x64-system",
mac: "darwin-universal",
macos: "darwin-universal",
"macos-arm64": "darwin-arm64",
"macos-x64": "darwin-x64",
linux: "linux-x64",
"linux-x64": "linux-x64",
"linux-arm64": "linux-arm64",
};
return aliases[p] || param; // if user passed canonical key, keep as-is
}

module.exports = async (req, res) => {
if (req.method !== "GET") {
return res.status(405).json({ error: "Method not allowed" });
}

res.setHeader("Content-Type", "application/json; charset=utf-8");
res.setHeader("Cache-Control", "no-store, must-revalidate");

try {
const history = await readVersionHistory(req);
const latest = getLatestVersion(history);

if (!latest) {
return res.status(500).json({ error: "No version data available" });
}

const urlParams = new URL(req.url, `http://${req.headers.host}`);
const platformParam = normalizePlatformParam(urlParams.searchParams.get("platform"));

// If a platform is specified, return a minimal, LLM-friendly JSON for that platform
if (platformParam) {
const url = latest.platforms?.[platformParam];
const details = latest.platformDetails?.[platformParam];

if (!url) {
const available = Object.keys(latest.platforms || {});
return res.status(404).json({
error: "Unknown or unavailable platform",
platform: platformParam,
availablePlatforms: available,
});
}

return res.status(200).json({
version: latest.version,
date: latest.date,
platform: platformParam,
url,
sizeBytes: details?.sizeBytes ?? null,
sha256: details?.sha256 ?? null,
});
}

// Otherwise return concise metadata for the latest release
return res.status(200).json({
version: latest.version,
date: latest.date,
platforms: latest.platforms || {},
platformDetails: latest.platformDetails || {},
});
} catch (err) {
console.error("/api/latest-download error:", err);
return res.status(500).json({ error: "Internal server error" });
}
};
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2693,6 +2693,9 @@ <h2 class="section-title">
>GitHub</a
>.
</div>
<div style="margin-top: 0.5rem; color: var(--text-secondary); font-size: 0.9rem;">
JSON API: <code>/api/latest-download</code> (optionally <code>?platform=&lt;key&gt;</code>)
</div>
</div>
<div
style="
Expand Down
22 changes: 22 additions & 0 deletions scripts/update-links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,28 @@ A simple, automatically updated site providing the latest download links for the

**Live Site:** [${liveSiteUrl.replace("https://", "")}](${liveSiteUrl})

### JSON API (LLM-friendly)

- **Latest release (all platforms):** \`${liveSiteUrl}/api/latest-download\`
- **Latest for a specific platform:** \`${liveSiteUrl}/api/latest-download?platform=<key>\`

Supported platform keys include: \`win32-x64-user\`, \`win32-x64-system\`, \`win32-arm64-user\`, \`win32-arm64-system\`, \`darwin-universal\`, \`darwin-arm64\`, \`darwin-x64\`, \`linux-x64\`, \`linux-arm64\`. Aliases accepted: \`windows\`, \`windows-user\`, \`windows-system\`, \`mac\`, \`macos\`, \`macos-arm64\`, \`macos-x64\`, \`linux\`.

Example response (platform-filtered):

\`\`\`json
{
"version": "${latestEntry.version}",
"date": "${latestEntry.date}",
"platform": "win32-x64-user",
"url": "${platforms["win32-x64-user"] || "https://downloads.cursor.com/.../CursorUserSetup-x64.exe"}",
"sizeBytes": ${latestDetails["win32-x64-user"]?.sizeBytes ?? 0},
"sha256": "${latestDetails["win32-x64-user"]?.sha256 || "<sha256>"}"
}
\`\`\`

For the full machine-readable history, you can also use \`${liveSiteUrl}/version-history.json\`.

![GitHub stars](https://img.shields.io/github/stars/accesstechnology-mike/cursor-downloads?style=social)
![Last commit](https://img.shields.io/github/last-commit/accesstechnology-mike/cursor-downloads)
![Update workflow](https://img.shields.io/github/actions/workflow/status/accesstechnology-mike/cursor-downloads/update.yml?branch=main)
Expand Down
8 changes: 8 additions & 0 deletions vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@
"Cache-Control": "no-store, must-revalidate"
}
},
{
"src": "/api/latest-download",
"dest": "/api/latest-download.js",
"headers": {
"Content-Type": "application/json",
"Cache-Control": "no-store, must-revalidate"
}
},
{
"src": "/(.*\\.(js|css|json|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot))",
"dest": "/$1"
Expand Down