Build a Music Explorer with Antigravity
Direct an AI agent to build a music web app using the Last.fm API.
Introduction
30 Second Summary
Everyone has that moment when a friend plays a song and you think "I need more music like this." Finding new artists that match your taste usually means scrolling through algorithm-generated playlists or asking around for recommendations.
In this project, you will direct Google Antigravity's AI agent to build a Music Explorer web app that finds similar artists and top tracks for any musician you search. Along the way, you will learn how to prompt, review, and iterate with an agentic IDE.
What You'll Build
A local web application that connects to a real music API, letting you search for any artist and instantly see related musicians and their most popular songs.
By the end of this project, you'll have:
- A working Music Explorer app with artist search, similar artists display, and top tracks listing.
- A configured Google Antigravity workspace with a custom code style rule that guides the agent's output.
- Experience reviewing an AI-generated implementation plan and providing targeted feedback to shape the final result.
- Secret Mission: Add an interactive artist web that visually maps connections between similar artists.
Are there any prerequisites?
You need a personal Gmail account to sign in to Google Antigravity. No coding experience is required. You will be directing the AI agent, not writing the code yourself.
Before We Start
Before We Start...
Install and Set Up Google Antigravity
Google Antigravity is an AI-powered development environment that treats the AI as an autonomous agent capable of planning, coding, and browsing the web on your behalf. Instead of writing code line by line, you describe what you want and the agent builds it for you.
In this step, you will install Antigravity on your Mac and configure it for a collaboration mode that gives you control over the agent's decisions. You will also install the browser extension that lets the agent interact with web pages.
In this step, get ready to:
- Download and install Google Antigravity on your Mac.
- Configure Antigravity for review-driven development.
- Install the Chrome browser extension for agent-browser integration.
Download and install Antigravity
Antigravity is a desktop application that runs locally on your machine. You will download the installer from Google's official site and walk through a setup wizard that configures how the agent behaves.
- Open Chrome and go to antigravity.google/download.
- Click Download for Apple Silicon under the macOS section.
- Once the .dmg file finishes downloading, double-click it to open the installer.
- Drag the Antigravity app into your Applications folder to complete the installation.
Have an Intel Mac?
If your Mac has an Intel processor instead of Apple Silicon, click Download for Intel instead. Not sure which one you have? Click the Apple menu in the top left, then About This Mac. If it says "Apple M1" or similar, you have Apple Silicon.
Now launch Antigravity for the first time.
- Press Cmd+Space to open Spotlight search.
- Type Antigravity and press Enter to open it.
Antigravity will launch a setup wizard with several screens. Click Next to proceed through each one. Here are the key choices to make:
- On the "Choose setup flow" screen, select Fresh start. This skips importing settings from VS Code or Cursor.
- On the "Choose an Editor theme" screen, pick any theme you like. Dark theme is a popular choice.
- On the "How do you want to use the Antigravity agent?" screen, select Review-driven development.
- On the "Configure your Editor settings" screen, accept the defaults and click Next.
What is review-driven development?
This setting controls how much freedom the agent has. In review-driven mode, the agent will ask for your approval before running terminal commands and before proceeding with generated plans. This gives you a safety net while you learn how the agent works.
You can change this setting at any time via Cmd+, (Antigravity User Settings).
Sign in and install the browser extension
Antigravity requires a personal Gmail account to authenticate. After signing in, you will trigger the browser extension installation by giving the agent its first task.
- On the "Sign in to Google" screen, click Sign in. This opens Chrome so you can log in with your personal Gmail account.
- Complete the Google sign-in flow in the browser. Once authenticated, you will be redirected back to the Antigravity application automatically.
- Accept the Terms of Use and click Next.
You should now see the Agent Manager window. This is the "mission control" for directing your AI agent.
- Click Start Conversation in the Agent Manager.
- In the text input where it says "Ask anything", type the following and press Enter:
go to antigravity.google
- The agent will analyze the task and detect that it needs browser access. When you see a prompt mentioning the browser agent setup, click Setup.
- Chrome will open to the Chrome Web Store showing the Antigravity browser extension. Click Add to Chrome to install it.
- When the confirmation popup appears, click Add extension.
Why does the agent need a browser extension?
The browser extension lets the agent interact with web pages on your behalf. It can navigate sites, click elements, take screenshots, and read page content. Later in this project, the agent will use the browser to test your Music Explorer app.
Don't see the Setup prompt?
Make sure you started a conversation in a workspace before typing the command. If you do not have a workspace open, click Open Folder first, select any folder on your Mac, and then start a new conversation.
help me set up the Antigravity browser extension
Verify everything works
After the extension is installed, the agent will complete its original task of navigating to the Antigravity website. It will ask for permission to proceed.
- Switch back to the Agent Manager window.
- When the agent asks for permission to browse, click Allow to let it navigate to the page.
- Confirm that the agent successfully loaded the page. You should see a screenshot artifact appear in the Agent Manager showing the Antigravity website.
Now verify you can switch between the two main views.
- Press Cmd+E to toggle to the Editor view.
- Confirm the Editor loads with a file explorer on the left side.
- Press Cmd+E again to switch back to the Agent Manager.
What are the two views?
The Agent Manager is your mission control for directing the agent at a high level. You give it tasks, review its plans, and provide feedback here.
The Editor is where you inspect and edit the code the agent generates. It looks and feels like VS Code with a file explorer, syntax highlighting, and a terminal.
No screenshot artifact showing?
Make sure the Chrome extension installed successfully. Check that you see the Antigravity extension icon in your Chrome toolbar. If it is missing, go to chrome://extensions and verify the extension is enabled.
help me fix my Antigravity browser extension
Your development environment is ready to go. Next up, you will get your music API key so the agent has real data to work with when it builds your app.
Get Your Music API Key
Your development environment is ready to go. Now you need a way to get real music data into your app.
Music services like Last.fm and Spotify offer APIs that let applications request artist information, similar artists, and top tracks. To use these APIs, you need an API key that identifies your app and grants access.
In this step, get ready to:
- Create an account on your chosen music service.
- Obtain your API credentials.
- Test that your credentials work with a real API call.
Choose your music API
This project supports two API paths. Pick one based on what you have.
What is an API key?
An API key is a unique string that identifies your application when it makes requests to a service. It lets the service track usage and ensure only authorized apps can access their data.
Think of it like a library card. The library (Last.fm or Spotify) gives you a card (API key) so it knows who is borrowing books (requesting data).
Last.fm (free, recommended)
Last.fm is the recommended path. It is completely free for non-commercial use and requires no paid subscription. Authentication is straightforward with a single API key added to each request URL.
Create a Last.fm account
- Open your browser and go to last.fm/join.
- Fill in a username, email address, and password.
- Check the box to agree to the Terms of Use and Privacy Policy.
- Click Sign Up.
Already have a Last.fm account?
Skip straight to the next substep. You just need to be logged in to create an API application.
Get your API key
- Go to last.fm/api/account/create.
- If prompted, log in with the account you just created.
- Enter Music Explorer in the Application name field.
- Add a short description (e.g. "A web app to explore artists and tracks").
- Click Submit.
- You will see a page showing your API key and a Shared secret. Copy the API key and save it somewhere safe (a notes app or text file).
You only need the API key
Last.fm gives you both an API key and a shared secret. For this project, you only need the API key. The shared secret is for write operations which this app does not perform.
Test your key
Before moving on, confirm the key works by making a test request directly in your browser.
- Open a new browser tab and paste this URL, replacing your-api-key-here with your actual API key:
http://ws.audioscrobbler.com/2.0/?method=artist.search&artist=radiohead&api_key=your-api-key-here&format=json
- Press Enter to load the URL.
What does this URL do?
This URL calls the Last.fm API with the artist.search method. It searches for the artist "Radiohead" and returns the results as JSON data.
The api_key parameter tells Last.fm who is making the request. The format=json parameter tells Last.fm to return data in JSON format instead of XML.
You should see a page full of JSON data containing information about Radiohead. It will look like a wall of text with curly braces and quotes.
Seeing an error instead of JSON data?
- Double-check that you replaced your-api-key-here with your actual API key (no extra spaces or missing characters).
- Make sure the entire URL is on one line with no line breaks.
- If you see a message about an invalid API key, go back to your Last.fm API account page and re-copy the key.
help me fix my Last.fm API key test
Spotify (requires Premium)
Spotify requires a Premium subscription
As of February 2026, the Spotify Web API requires the app owner to have an active Spotify Premium subscription. If you do not have Premium, use the Last.fm tab instead.
Create a Spotify app
- Go to developer.spotify.com/dashboard and log in with your Spotify account.
- Click Create an app.
- Enter Music Explorer in the App Name field.
- Enter a short description (e.g. "A web app to explore artists and tracks").
- In the Redirect URI field, enter http://127.0.0.1:3000.
- When asked which APIs you plan to use, select Web API.
- Check the Developer Terms of Service checkbox.
- Click Create.
Why http://127.0.0.1:3000?
Spotify requires a redirect URI for authentication flows. It does not allow localhost as a redirect address. You must use http://127.0.0.1:3000 instead. This is Spotify's loopback rule for development apps.
Copy your credentials
- On the app overview page, find and copy your Client ID.
- Click View client secret and copy that value too.
- Save both values somewhere safe (a notes app or text file). You will need them in a later step.
Keep your credentials private
Your Client ID and Client Secret are like a username and password for your app. Never share them publicly or commit them to a public repository.
Not seeing the Create an app button?
- Make sure you are logged into your Spotify account (the one with Premium).
- If you see a Terms of Service prompt, read and accept it first. The dashboard appears after you accept.
help me set up my Spotify developer app
Your API credentials are ready. Next up, you will open Antigravity and direct the AI agent to build your Music Explorer app using these credentials.
Create Your Workspace and Prompt the Agent
Your API key is ready. Now it's time to give your AI agent its first real task.
In this step, you'll create a dedicated workspace for your project, teach the agent how you want your code written using a workspace rule, and then craft a detailed prompt that describes the Music Explorer app.
In this step, get ready to:
- Create a music-explorer folder and open it as a workspace in Antigravity.
- Add a workspace rule that guides the agent's code style.
- Write and submit your agent prompt in Planning mode.
Create a project folder and open it in Antigravity
The agent needs a dedicated folder to work in. This folder becomes your workspace, and everything the agent creates will live inside it.
- Open Finder by clicking the Finder icon in your Dock (the blue and white smiley face).
- Navigate to your home folder by pressing Cmd+Shift+H.
- Create a new folder by pressing Cmd+Shift+N.
- Name it music-explorer and press Enter.
- Switch to Antigravity's Agent Manager. Click the workspace button in the left sidebar to open a new workspace.
- Select the music-explorer folder you just created.
- Start a new conversation by clicking the Plus button next to the workspace name in the sidebar.
What is a workspace?
A workspace in Google Antigravity is a folder that the agent treats as its project root. The agent can only see and edit files inside this folder.
Every conversation you start in this workspace shares the same context. This means your rules and project files carry over between sessions.
Don't see the workspace button?
Make sure you're in the Agent Manager view, not the Editor. Press Cmd+E to toggle between the Agent Manager and the Editor.
help me find the workspace button in Antigravity
Set up a code style rule
Antigravity lets you define rules that automatically guide the agent's behaviour across every conversation in a workspace. You'll create a rule that keeps the agent focused on simple, vanilla code.
- Switch to the Editor view by pressing Cmd+E.
- Click the ... menu at the top of the agent panel in the Editor view.
- Click Customizations.
- Click Rules.
- Click +Workspace to create a new workspace rule.
- Name the rule code-style.
- Paste the following content into the rule editor:
Use vanilla HTML, CSS, and JavaScript only. No frameworks. Keep the code simple and well-commented. Use fetch() for API calls. Store the API key in a separate config.js file.
What does this rule do?
This rule tells the agent to avoid pulling in frameworks like React or Vue. It also instructs the agent to keep your API key in a separate config.js file so you can swap it out easily.
Workspace rules are saved to the .agents/rules/ folder inside your project. Every conversation in this workspace will follow these constraints automatically.
- Save the rule by closing the Customizations panel.
Rule not saving?
Make sure you have opened the music-explorer folder as your workspace before creating the rule. Rules created with +Workspace only appear when a workspace is active.
help me troubleshoot workspace rules in Antigravity
Write your prompt and submit it
The prompt is where you describe exactly what you want the agent to build. A detailed, structured prompt produces much better results than a vague request.
- Switch back to the Agent Manager by pressing Cmd+E.
- In the conversation you started earlier, find the mode dropdown near the chat input.
- Switch from Fast to Planning mode.
Why Planning mode?
Planning mode makes the agent create a structured implementation plan before it writes any code. This gives you a chance to review, comment, and redirect before the agent commits to an approach.
Fast mode skips planning and executes immediately. That is useful for quick edits, but for building an entire app from scratch you want the agent to think first.
- Copy the prompt below (based on which API you chose in Step 2) and paste it into the agent chat input.
Last.fm (Free)
- Paste this prompt into the agent chat:
Build a Music Explorer web app. It should have a search bar where I type an artist name. When I search, it should call the Last.fm API to show: 1) The artist info (name, bio, listener count), 2) A grid of similar artists with images, 3) The top 5 tracks for that artist. Use vanilla HTML, CSS, and JavaScript. The Last.fm API base URL is http://ws.audioscrobbler.com/2.0/ and needs parameters method, artist, api_key, and format=json. The API key should be stored in config.js. Make the UI dark-themed and modern looking.
Spotify (Premium)
- Paste this prompt into the agent chat:
Build a Music Explorer web app. It should have a search bar where I type an artist name. When I search, it should call the Spotify Web API to show: 1) The artist info (name, genres, follower count, image), 2) A grid of related artists with images, 3) The top 5 tracks for that artist. Use vanilla HTML, CSS, and JavaScript. Use the Client Credentials flow to get an access token (POST to https://accounts.spotify.com/api/token with grant_type=client_credentials). Store client_id and client_secret in a config.js file. Make the UI dark-themed and modern looking.
- Press Enter or click the send button to submit your prompt.
The agent will begin processing your request. You should see it start creating a task list and an implementation plan.
What should I see?
After submitting, the agent enters a planning phase. You will see it produce task groups and an Implementation Plan artifact. This might take 30 seconds to a minute. Do not click anything yet.
Agent not responding?
Double-check that the mode dropdown says Planning (not Fast). If the agent stalls for more than two minutes, try clicking the stop button and re-sending your prompt.
my agent is not responding to my prompt in Antigravity
Your workspace is set up and the agent is planning your Music Explorer app. Next up, you'll review the agent's implementation plan and guide it toward the right approach.
Review the Plan and Provide Feedback
In the previous step, you gave the agent a detailed prompt describing your Music Explorer app. The agent has now produced an Implementation Plan for you to review before it writes any code.
This is the core skill of agentic development. You review the plan, catch anything that does not match your requirements, and provide targeted feedback. The agent iterates until the plan is right, then builds.
In this step, get ready to:
- Review the agent's Implementation Plan artifact for correctness.
- Provide feedback if the plan deviates from your requirements.
- Approve the plan and watch the agent build your app.
Read the Implementation Plan artifact
The agent produces an Implementation Plan artifact before it touches any code. This plan shows the tech stack, file structure, and approach the agent intends to use.
- In the Agent Manager, look for the Implementation Plan artifact that the agent has created.
- Click Open next to the Implementation Plan row to view its full details.
- Read through the plan carefully.
As you read, check that the plan mentions these four things:
- Separate files: index.html, style.css, app.js, and config.js.
- Correct API endpoints (Last.fm methods like artist.getinfo, artist.getsimilar, artist.gettoptracks or Spotify equivalents).
- Vanilla JavaScript with no frameworks (no React, Vue, or Angular).
- A dark-themed UI approach.
What is an Implementation Plan?
In Planning mode, the agent creates an Implementation Plan artifact before writing any code. It outlines the proposed technical approach, file structure, and architecture decisions. This gives you a chance to steer the agent before it invests time building something you did not ask for.
Add a comment if something is off
The agent sometimes drifts from your instructions. Your workspace rule said vanilla JavaScript, but the agent might still propose React or another framework. Antigravity lets you add comments directly on the plan to course-correct.
✔️ The plan looks good
The plan uses vanilla JavaScript, creates four separate files, and references the correct API endpoints.
- Click the Proceed button in the artifact header (or in the conversation) to approve the plan.
ⓧ The plan uses a framework or is missing something
If the plan mentions React, Vue, Next.js, or any other framework, you need to add a comment.
- Click directly on the section of the plan that mentions the framework.
- Type your comment: Use vanilla JavaScript only, no frameworks. and submit it.
- Click the Review button in the artifact header to submit your feedback.
- Wait for the agent to revise the plan based on your comment.
- Once the revised plan looks correct, click Proceed to approve it.
Comments work like Google Docs
You can click on any part of the Implementation Plan and leave a comment. The agent reads your comments, revises the plan, and asks for your review again.
Why review before the agent codes?
Catching a wrong tech stack at the planning stage takes one comment. Catching it after the agent has generated hundreds of lines of code means starting over. The plan-then-implement loop keeps you in control without slowing you down.
Watch the agent work
Once you click Proceed, the agent starts building. It creates files, writes code, and may even open the browser to test its own work.
- Stay in the Agent Manager view and watch the task list items get checked off as the agent completes each one.
- The agent will ask for permission before running terminal commands (because you chose review-driven development in Step 1). Click Proceed when prompted.
- Wait for the agent to finish. It produces a Walkthrough artifact when done, summarizing everything it built.
What should I see when the agent finishes?
You should see a completed task list with all items checked off. The agent also creates a Walkthrough artifact that summarizes the changes it made.
Switch to the Editor view (Cmd+E) and check the file explorer on the left. You should see four files in your music-explorer folder: index.html, style.css, app.js, and config.js.
Agent seems stuck or is not finishing?
If the agent has been working for several minutes without progress, check the conversation for any pending permission prompts. The agent may be waiting for you to approve a terminal command.
If the agent hit an error and stopped, describe the error in the chat (e.g., "You got an error creating the file. Try again.") and let it recover.
Help me troubleshoot why my Antigravity agent is stuck and not completing its task.
✔️ Awesome, I've got everything!
Great. Confirm that you can see all four files (index.html, style.css, app.js, config.js) in the Editor view file explorer.
ⓧ I'd like to double check the full code
Since the agent generated this code, your files may differ slightly in formatting or comments. The important thing is that the structure and logic match. Here is what each file should look like:
config.js:
// Replace with your actual Last.fm API key
const CONFIG = {
LASTFM_API_KEY: "your-api-key-here",
LASTFM_BASE_URL: "https://ws.audioscrobbler.com/2.0/"
};
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Music Explorer</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>Music Explorer</h1>
<div class="search-bar">
<input type="text" id="searchInput" placeholder="Search for an artist...">
<button id="searchButton">Search</button>
</div>
<div id="loading" class="loading hidden">Searching...</div>
<div id="results" class="results hidden">
<div id="artistInfo" class="artist-info"></div>
<h2>Similar Artists</h2>
<div id="similarArtists" class="similar-grid"></div>
<h2>Top Tracks</h2>
<div id="topTracks" class="tracks-list"></div>
</div>
<div id="error" class="error hidden"></div>
</div>
<script src="config.js"></script>
<script src="app.js"></script>
</body>
</html>
style.css:
/* Dark theme Music Explorer styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: #1a1a2e;
color: #eaeaea;
min-height: 100vh;
}
.container {
max-width: 900px;
margin: 0 auto;
padding: 40px 20px;
}
h1 {
text-align: center;
font-size: 2.5rem;
margin-bottom: 30px;
color: #00d4ff;
}
h2 {
margin-top: 30px;
margin-bottom: 15px;
color: #00d4ff;
font-size: 1.3rem;
}
.search-bar {
display: flex;
gap: 10px;
margin-bottom: 30px;
}
#searchInput {
flex: 1;
padding: 12px 18px;
font-size: 1rem;
border: 2px solid #333;
border-radius: 8px;
background-color: #16213e;
color: #eaeaea;
outline: none;
transition: border-color 0.3s;
}
#searchInput:focus {
border-color: #00d4ff;
}
#searchButton {
padding: 12px 24px;
font-size: 1rem;
border: none;
border-radius: 8px;
background-color: #00d4ff;
color: #1a1a2e;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s;
}
#searchButton:hover {
background-color: #00b8d4;
}
.loading {
text-align: center;
padding: 20px;
color: #00d4ff;
font-size: 1.1rem;
}
.error {
text-align: center;
padding: 20px;
color: #ff6b6b;
font-size: 1.1rem;
}
.hidden {
display: none;
}
.artist-info {
background-color: #16213e;
border-radius: 12px;
padding: 24px;
display: flex;
gap: 20px;
align-items: center;
}
.artist-info img {
width: 120px;
height: 120px;
border-radius: 50%;
object-fit: cover;
}
.artist-details h2 {
margin-top: 0;
margin-bottom: 8px;
color: #ffffff;
font-size: 1.8rem;
}
.artist-details p {
color: #aaa;
line-height: 1.5;
}
.artist-stats {
margin-top: 8px;
color: #00d4ff;
font-weight: bold;
}
.similar-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 16px;
}
.similar-card {
background-color: #16213e;
border-radius: 10px;
padding: 12px;
text-align: center;
transition: transform 0.2s, background-color 0.2s;
cursor: pointer;
}
.similar-card:hover {
transform: translateY(-4px);
background-color: #1a2744;
}
.similar-card img {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
margin-bottom: 8px;
}
.similar-card p {
font-size: 0.85rem;
font-weight: 500;
}
.tracks-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.track-item {
display: flex;
align-items: center;
gap: 12px;
background-color: #16213e;
padding: 12px 16px;
border-radius: 8px;
}
.track-number {
color: #00d4ff;
font-weight: bold;
font-size: 1.1rem;
min-width: 24px;
}
.track-name {
flex: 1;
}
.track-plays {
color: #aaa;
font-size: 0.85rem;
}
app.js:
// Music Explorer - Main application logic
// DOM element references
const searchInput = document.getElementById("searchInput");
const searchButton = document.getElementById("searchButton");
const loadingEl = document.getElementById("loading");
const resultsEl = document.getElementById("results");
const errorEl = document.getElementById("error");
const artistInfoEl = document.getElementById("artistInfo");
const similarArtistsEl = document.getElementById("similarArtists");
const topTracksEl = document.getElementById("topTracks");
// Event listeners for search
searchButton.addEventListener("click", handleSearch);
searchInput.addEventListener("keypress", function (e) {
if (e.key === "Enter") {
handleSearch();
}
});
// Main search handler
function handleSearch() {
const artist = searchInput.value.trim();
if (!artist) return;
loadingEl.classList.remove("hidden");
resultsEl.classList.add("hidden");
errorEl.classList.add("hidden");
Promise.all([
fetchArtistInfo(artist),
fetchSimilarArtists(artist),
fetchTopTracks(artist)
])
.then(function (data) {
const artistInfo = data[0];
const similarArtists = data[1];
const topTracks = data[2];
displayArtistInfo(artistInfo);
displaySimilarArtists(similarArtists);
displayTopTracks(topTracks);
loadingEl.classList.add("hidden");
resultsEl.classList.remove("hidden");
})
.catch(function (err) {
loadingEl.classList.add("hidden");
errorEl.textContent = "Error: " + err.message;
errorEl.classList.remove("hidden");
});
}
function fetchArtistInfo(artist) {
const url =
CONFIG.LASTFM_BASE_URL +
"?method=artist.getinfo&artist=" +
encodeURIComponent(artist) +
"&api_key=" +
CONFIG.LASTFM_API_KEY +
"&format=json";
return fetch(url)
.then(function (response) {
return response.json();
})
.then(function (data) {
if (data.error) {
throw new Error(data.message);
}
return data.artist;
});
}
function fetchSimilarArtists(artist) {
const url =
CONFIG.LASTFM_BASE_URL +
"?method=artist.getsimilar&artist=" +
encodeURIComponent(artist) +
"&api_key=" +
CONFIG.LASTFM_API_KEY +
"&limit=8&format=json";
return fetch(url)
.then(function (response) {
return response.json();
})
.then(function (data) {
if (data.error) {
throw new Error(data.message);
}
return data.similarartists.artist;
});
}
function fetchTopTracks(artist) {
const url =
CONFIG.LASTFM_BASE_URL +
"?method=artist.gettoptracks&artist=" +
encodeURIComponent(artist) +
"&api_key=" +
CONFIG.LASTFM_API_KEY +
"&limit=5&format=json";
return fetch(url)
.then(function (response) {
return response.json();
})
.then(function (data) {
if (data.error) {
throw new Error(data.message);
}
return data.toptracks.track;
});
}
function displayArtistInfo(artist) {
const imageUrl =
artist.image && artist.image[3]
? artist.image[3]["#text"]
: "";
const listeners = Number(artist.stats.listeners).toLocaleString();
const plays = Number(artist.stats.playcount).toLocaleString();
const bio = artist.bio
? artist.bio.summary.replace(/<a.*<\/a>/, "")
: "No bio available.";
artistInfoEl.innerHTML =
(imageUrl
? '<img src="' + imageUrl + '" alt="' + artist.name + '">'
: "") +
'<div class="artist-details">' +
"<h2>" + artist.name + "</h2>" +
'<p class="artist-stats">' +
listeners + " listeners · " + plays + " plays</p>" +
"<p>" + bio + "</p>" +
"</div>";
}
function displaySimilarArtists(artists) {
if (!artists || artists.length === 0) {
similarArtistsEl.innerHTML = "<p>No similar artists found.</p>";
return;
}
similarArtistsEl.innerHTML = artists
.map(function (artist) {
const imageUrl =
artist.image && artist.image[2]
? artist.image[2]["#text"]
: "";
return (
'<div class="similar-card" onclick="searchForArtist(\'' +
artist.name.replace(/'/g, "\\'") +
'\')">' +
(imageUrl
? '<img src="' + imageUrl + '" alt="' + artist.name + '">'
: "") +
"<p>" + artist.name + "</p>" +
"</div>"
);
})
.join("");
}
function displayTopTracks(tracks) {
if (!tracks || tracks.length === 0) {
topTracksEl.innerHTML = "<p>No top tracks found.</p>";
return;
}
topTracksEl.innerHTML = tracks
.map(function (track, index) {
const plays = Number(track.playcount).toLocaleString();
return (
'<div class="track-item">' +
'<span class="track-number">' + (index + 1) + "</span>" +
'<span class="track-name">' + track.name + "</span>" +
'<span class="track-plays">' + plays + " plays</span>" +
"</div>"
);
})
.join("");
}
function searchForArtist(name) {
searchInput.value = name;
handleSearch();
}
Your agent just built an entire web app from your description. Next up, you will plug in your real API key and test the app live.
Run and Test Your Music Explorer
Your agent just built an entire web application from a single prompt. The code is ready, but it still has one critical gap: a placeholder where your real API key needs to go.
In this step, you'll plug in your actual key, ask the agent to serve the app locally, and run your first live search to confirm everything works.
In this step, get ready to:
- Replace the placeholder API key with your real one.
- Ask the agent to start a local HTTP server.
- Test the app by searching for an artist and verifying live results.
Add your API key
The agent created a config.js file with a placeholder value. You need to swap it for the real key you saved in Step 2.
- Press Cmd+E to switch to the Editor view.
- Open config.js from the file explorer sidebar on the left.
- Find this line:
LASTFM_API_KEY: "your-api-key-here",
- Replace your-api-key-here with the actual Last.fm API key you saved earlier. Keep the surrounding quotes.
- Save the file.
Using Spotify instead?
If you chose the Spotify path in Step 2, your config.js will have fields for client_id and client_secret instead. Replace both placeholders with the credentials you copied from the Spotify Developer Dashboard.
Not sure where your API key is?
If you did not save your Last.fm API key, go back to last.fm/api/accounts while logged in. Your existing applications and their keys are listed there.
help me find my Last.fm API key
Launch the app
Your app is a set of static HTML, CSS, and JavaScript files. To run it in a browser, those files need to be served by a local HTTP server.
- Press Cmd+E to switch back to the Agent Manager.
- Start a new message and type the following prompt:
Start a local server to serve the app and open it in the browser.
- Press Enter to send the message.
What happens next?
The agent will open a terminal, start a simple HTTP server (such as Python's built-in http.server), and launch the app in Antigravity's built-in browser. Because you chose Review-driven development in Step 1, the agent may ask for permission before running the terminal command.
If it asks, click Allow to let it proceed.
- Wait for the agent to finish. You should see your Music Explorer UI appear in the browser panel with a dark background, a cyan heading, and a search bar.
Don't see the app in the browser?
If the browser panel is blank or shows an error, check the terminal output in the Agent Manager for a port number. The agent may have started the server but failed to open the browser automatically.
Tell the agent: "Open http://localhost:8000 in the browser" (adjust the port if the terminal shows a different number).
help me get the local server working in Antigravity
Test with a search
The moment of truth. Your app is live and your API key is wired up. Time to search for a real artist and see if the Last.fm data flows through.
- Click inside the search bar in the Music Explorer UI.
- Type Radiohead (or any artist you like).
- Click the Search button or press Enter.
You should see three sections populate: artist info (name, listener count, bio), a grid of similar artists with images, and a list of the top 5 tracks with play counts.
What if results look different from expected?
Last.fm's image data can be sparse for some artists. If you see missing images or placeholder circles, that is normal. The important thing is that artist names, track names, and play counts all appear.
- Try clicking one of the similar artists in the grid. The app should re-search for that artist automatically.
Something broken? Tell the agent
If a section is not loading or you see an error message, describe the problem to the agent in the Agent Manager. Be specific about what you see.
For example: "The similar artists section is empty. I can see an error in the browser console that says the API returned an error." The agent will investigate and fix the issue.
Seeing "Error: Invalid API key"?
Double-check that your key in config.js does not have extra spaces or missing quotes. The value should be wrapped in double quotes with no leading or trailing whitespace.
help me fix an invalid API key error in my Music Explorer app
✔️ Awesome, I've got everything!
Great. Make sure you have saved config.js with your real API key and that search results are displaying correctly.
ⓧ I'd like to double check the full code
Here is what config.js should look like (with your own key in place of the placeholder):
// Replace with your actual Last.fm API key
const CONFIG = {
LASTFM_API_KEY: "your-actual-api-key",
LASTFM_BASE_URL: "https://ws.audioscrobbler.com/2.0/"
};
The remaining files (index.html, style.css, app.js) were generated by the agent and should not need manual changes. If you want to verify them, open each file in the Editor and compare against the code below.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Music Explorer</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>Music Explorer</h1>
<div class="search-bar">
<input type="text" id="searchInput" placeholder="Search for an artist...">
<button id="searchButton">Search</button>
</div>
<div id="loading" class="loading hidden">Searching...</div>
<div id="results" class="results hidden">
<div id="artistInfo" class="artist-info"></div>
<h2>Similar Artists</h2>
<div id="similarArtists" class="similar-grid"></div>
<h2>Top Tracks</h2>
<div id="topTracks" class="tracks-list"></div>
</div>
<div id="error" class="error hidden"></div>
</div>
<script src="config.js"></script>
<script src="app.js"></script>
</body>
</html>
/* Dark theme Music Explorer styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: #1a1a2e;
color: #eaeaea;
min-height: 100vh;
}
.container {
max-width: 900px;
margin: 0 auto;
padding: 40px 20px;
}
h1 {
text-align: center;
font-size: 2.5rem;
margin-bottom: 30px;
color: #00d4ff;
}
h2 {
margin-top: 30px;
margin-bottom: 15px;
color: #00d4ff;
font-size: 1.3rem;
}
.search-bar {
display: flex;
gap: 10px;
margin-bottom: 30px;
}
#searchInput {
flex: 1;
padding: 12px 18px;
font-size: 1rem;
border: 2px solid #333;
border-radius: 8px;
background-color: #16213e;
color: #eaeaea;
outline: none;
transition: border-color 0.3s;
}
#searchInput:focus {
border-color: #00d4ff;
}
#searchButton {
padding: 12px 24px;
font-size: 1rem;
border: none;
border-radius: 8px;
background-color: #00d4ff;
color: #1a1a2e;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s;
}
#searchButton:hover {
background-color: #00b8d4;
}
.loading {
text-align: center;
padding: 20px;
color: #00d4ff;
font-size: 1.1rem;
}
.error {
text-align: center;
padding: 20px;
color: #ff6b6b;
font-size: 1.1rem;
}
.hidden {
display: none;
}
.artist-info {
background-color: #16213e;
border-radius: 12px;
padding: 24px;
display: flex;
gap: 20px;
align-items: center;
}
.artist-info img {
width: 120px;
height: 120px;
border-radius: 50%;
object-fit: cover;
}
.artist-details h2 {
margin-top: 0;
margin-bottom: 8px;
color: #ffffff;
font-size: 1.8rem;
}
.artist-details p {
color: #aaa;
line-height: 1.5;
}
.artist-stats {
margin-top: 8px;
color: #00d4ff;
font-weight: bold;
}
.similar-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 16px;
}
.similar-card {
background-color: #16213e;
border-radius: 10px;
padding: 12px;
text-align: center;
transition: transform 0.2s, background-color 0.2s;
cursor: pointer;
}
.similar-card:hover {
transform: translateY(-4px);
background-color: #1a2744;
}
.similar-card img {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
margin-bottom: 8px;
}
.similar-card p {
font-size: 0.85rem;
font-weight: 500;
}
.tracks-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.track-item {
display: flex;
align-items: center;
gap: 12px;
background-color: #16213e;
padding: 12px 16px;
border-radius: 8px;
}
.track-number {
color: #00d4ff;
font-weight: bold;
font-size: 1.1rem;
min-width: 24px;
}
.track-name {
flex: 1;
}
.track-plays {
color: #aaa;
font-size: 0.85rem;
}
// Music Explorer - Main application logic
// DOM element references
const searchInput = document.getElementById("searchInput");
const searchButton = document.getElementById("searchButton");
const loadingEl = document.getElementById("loading");
const resultsEl = document.getElementById("results");
const errorEl = document.getElementById("error");
const artistInfoEl = document.getElementById("artistInfo");
const similarArtistsEl = document.getElementById("similarArtists");
const topTracksEl = document.getElementById("topTracks");
// Event listeners for search
searchButton.addEventListener("click", handleSearch);
searchInput.addEventListener("keypress", function (e) {
if (e.key === "Enter") {
handleSearch();
}
});
// Main search handler
function handleSearch() {
const artist = searchInput.value.trim();
if (!artist) return;
loadingEl.classList.remove("hidden");
resultsEl.classList.add("hidden");
errorEl.classList.add("hidden");
Promise.all([
fetchArtistInfo(artist),
fetchSimilarArtists(artist),
fetchTopTracks(artist)
])
.then(function (data) {
const artistInfo = data[0];
const similarArtists = data[1];
const topTracks = data[2];
displayArtistInfo(artistInfo);
displaySimilarArtists(similarArtists);
displayTopTracks(topTracks);
loadingEl.classList.add("hidden");
resultsEl.classList.remove("hidden");
})
.catch(function (err) {
loadingEl.classList.add("hidden");
errorEl.textContent = "Error: " + err.message;
errorEl.classList.remove("hidden");
});
}
function fetchArtistInfo(artist) {
const url =
CONFIG.LASTFM_BASE_URL +
"?method=artist.getinfo&artist=" +
encodeURIComponent(artist) +
"&api_key=" +
CONFIG.LASTFM_API_KEY +
"&format=json";
return fetch(url)
.then(function (response) {
return response.json();
})
.then(function (data) {
if (data.error) {
throw new Error(data.message);
}
return data.artist;
});
}
function fetchSimilarArtists(artist) {
const url =
CONFIG.LASTFM_BASE_URL +
"?method=artist.getsimilar&artist=" +
encodeURIComponent(artist) +
"&api_key=" +
CONFIG.LASTFM_API_KEY +
"&limit=8&format=json";
return fetch(url)
.then(function (response) {
return response.json();
})
.then(function (data) {
if (data.error) {
throw new Error(data.message);
}
return data.similarartists.artist;
});
}
function fetchTopTracks(artist) {
const url =
CONFIG.LASTFM_BASE_URL +
"?method=artist.gettoptracks&artist=" +
encodeURIComponent(artist) +
"&api_key=" +
CONFIG.LASTFM_API_KEY +
"&limit=5&format=json";
return fetch(url)
.then(function (response) {
return response.json();
})
.then(function (data) {
if (data.error) {
throw new Error(data.message);
}
return data.toptracks.track;
});
}
function displayArtistInfo(artist) {
const imageUrl =
artist.image && artist.image[3]
? artist.image[3]["#text"]
: "";
const listeners = Number(artist.stats.listeners).toLocaleString();
const plays = Number(artist.stats.playcount).toLocaleString();
const bio = artist.bio
? artist.bio.summary.replace(/<a.*<\/a>/, "")
: "No bio available.";
artistInfoEl.innerHTML =
(imageUrl
? '<img src="' + imageUrl + '" alt="' + artist.name + '">'
: "") +
'<div class="artist-details">' +
"<h2>" + artist.name + "</h2>" +
'<p class="artist-stats">' +
listeners + " listeners · " + plays + " plays</p>" +
"<p>" + bio + "</p>" +
"</div>";
}
function displaySimilarArtists(artists) {
if (!artists || artists.length === 0) {
similarArtistsEl.innerHTML = "<p>No similar artists found.</p>";
return;
}
similarArtistsEl.innerHTML = artists
.map(function (artist) {
const imageUrl =
artist.image && artist.image[2]
? artist.image[2]["#text"]
: "";
return (
'<div class="similar-card" onclick="searchForArtist(\'' +
artist.name.replace(/'/g, "\\'") +
'\')">' +
(imageUrl
? '<img src="' + imageUrl + '" alt="' + artist.name + '">'
: "") +
"<p>" + artist.name + "</p>" +
"</div>"
);
})
.join("");
}
function displayTopTracks(tracks) {
if (!tracks || tracks.length === 0) {
topTracksEl.innerHTML = "<p>No top tracks found.</p>";
return;
}
topTracksEl.innerHTML = tracks
.map(function (track, index) {
const plays = Number(track.playcount).toLocaleString();
return (
'<div class="track-item">' +
'<span class="track-number">' + (index + 1) + "</span>" +
'<span class="track-name">' + track.name + "</span>" +
'<span class="track-plays">' + plays + " plays</span>" +
"</div>"
);
})
.join("");
}
function searchForArtist(name) {
searchInput.value = name;
handleSearch();
}
Your Music Explorer is alive. You just went from a blank folder to a working, API-powered web app by directing an AI agent. Next up, you can push this further with an interactive artist connection map in the Secret Mission.
Secret mission
Add an Interactive Artist Web
Your Music Explorer shows similar artists as a grid. What if you could see how artists connect to each other visually, and click through to explore the web of musical connections?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on your local machine and uses free APIs. There are no ongoing cloud costs or subscription charges from the resources you created.
Decide whether to keep your resources in place, pause Antigravity to free memory, or delete everything entirely.
Resources you used:
- music-explorer project folder (local files).
- Google Antigravity application.
- Antigravity Chrome extension.
- Spotify Developer app (if you chose Path B).
Keep everything running
No action needed. Choose this if you plan to keep experimenting with Antigravity or extending your Music Explorer app.
- Your music-explorer folder stays on your machine and takes up minimal disk space.
- Google Antigravity is free in public preview, so it costs nothing to leave installed.
- Your Last.fm API key remains active and free for non-commercial use.
Pause - I'll come back to this later
Shut down running processes to free up memory. Your files and accounts stay intact for when you return.
- If the local HTTP server is still running in the Antigravity terminal, press Ctrl+C to stop it.
- Quit Google Antigravity by pressing Cmd+Q or right-clicking the Dock icon and selecting Quit.
- Your project folder, API keys, and Chrome extension will be right where you left them.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow the steps below for each resource you want to remove.
Delete the project folder
- Open Terminal and delete the music-explorer folder by running:
rm -rf ~/music-explorer
Remove Google Antigravity
- Open Finder and navigate to Applications.
- Drag Google Antigravity to the Trash.
- Remove the Antigravity configuration directory by running:
rm -rf ~/.antigravity
Remove the Chrome extension
- Open Chrome and navigate to chrome://extensions in the address bar.
- Find the Antigravity extension and click Remove.
- Confirm by clicking Remove in the popup.
Delete your Spotify app (Path B only)
- Go to developer.spotify.com/dashboard and log in.
- Click on the name of your Music Explorer app.
- Click the red DELETE button to permanently remove the app.
Nice Work!
Nice Work!
You just built a full Music Explorer web app without writing the code yourself. You directed an AI agent like a product manager and got working software out the other end.
You've learned how to:
- Direct Google Antigravity's AI agent to build a complete web application from a natural language prompt.
- Review an AI-generated implementation plan and provide targeted feedback to steer the agent's output.
- Integrate a real music API into an agent-built app and test it with live data.
- Extended the app with an interactive artist connection map through agent collaboration.
Ready to quiz yourself?