feat: Stripe Checkout integration - api/checkout.js, checkout pages, updated buy page with card+gumroad options

This commit is contained in:
artale 2026-06-04 12:51:28 +02:00
parent 718e802771
commit 7ad7cb6458
63 changed files with 593 additions and 242 deletions

48
api/README.md Normal file
View File

@ -0,0 +1,48 @@
# Checkout API
Serverless Stripe Checkout integration for the Agentic Engineering course.
## Deploy
### Cloudflare Worker (recommended)
```bash
npm install -g wrangler
cd api
wrangler deploy checkout.js --name checkout-api
wrangler secret put STRIPE_SECRET_KEY
```
### Vercel
```bash
cd api
vercel deploy --prod
vercel secrets add STRIPE_SECRET_KEY sk_live_...
```
### Standalone
```bash
export STRIPE_SECRET_KEY="sk_live_..."
node checkout.js
# Listens on port 8787
```
## Price IDs
Create these products in Stripe Dashboard, then update `site/checkout.md` with the real price IDs:
| Product | Amount | Stripe Price ID |
|---------|--------|----------------|
| Self-Paced | $97 | `price_self_paced` → replace with real ID |
| Enterprise | $199 | `price_enterprise` → replace with real ID |
| Cohort | $247 | `price_cohort` → replace with real ID |
## How It Works
1. User clicks "Buy" → redirected to `/checkout?plan=self-paced`
2. Frontend calls `/api/checkout` with the price ID
3. API creates a Stripe Checkout Session
4. User is redirected to Stripe's hosted checkout page
5. After payment, user is redirected to `/checkout/success`

141
api/checkout.js Normal file
View File

@ -0,0 +1,141 @@
/**
* Stripe Checkout Session API
* Deploy as a Cloudflare Worker, Vercel function, or standalone server.
*
* Environment variables needed:
* - STRIPE_SECRET_KEY: sk_live_... or sk_test_...
* - FRONTEND_URL: https://git.fdsa.agency (or your domain)
*
* Usage:
* POST /api/checkout
* Body: { "price_id": "price_abc123", "mode": "payment", "customer_email": "..." }
*/
const stripeEndpoint = 'https://api.stripe.com/v1/checkout/sessions'
async function handleRequest(request) {
// CORS for frontend
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
})
}
if (request.method !== 'POST') {
return new Response(JSON.stringify({ error: 'Method not allowed' }), { status: 405 })
}
const stripeKey = globalThis.STRIPE_SECRET_KEY || process?.env?.STRIPE_SECRET_KEY
const frontendUrl = globalThis.FRONTEND_URL || process?.env?.FRONTEND_URL || 'https://git.fdsa.agency'
if (!stripeKey) {
return new Response(JSON.stringify({ error: 'Stripe not configured' }), {
status: 500,
headers: { 'Access-Control-Allow-Origin': '*' },
})
}
try {
const { price_id, mode, customer_email } = await request.json()
const body = new URLSearchParams({
'mode': mode || 'payment',
'success_url': `${frontendUrl}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
'cancel_url': `${frontendUrl}/checkout/cancel`,
'allow_promotion_codes': 'true',
'line_items[0][price]': price_id,
'line_items[0][quantity]': '1',
})
if (customer_email) {
body.set('customer_email', customer_email)
}
const stripeResp = await fetch(stripeEndpoint, {
method: 'POST',
headers: {
'Authorization': `Bearer ${stripeKey}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: body.toString(),
})
const data = await stripeJson(stripeResp)
if (!stripeResp.ok) {
throw new Error(data.error?.message || 'Stripe error')
}
return new Response(JSON.stringify({ url: data.url, session_id: data.id }), {
status: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
})
} catch (e) {
return new Response(JSON.stringify({ error: e.message }), {
status: 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
})
}
}
async function stripeJson(response) {
const text = await response.text()
try {
return JSON.parse(text)
} catch {
// Stripe sometimes returns unexpected formats — parse query string
const params = new URLSearchParams(text)
const obj = {}
for (const [k, v] of params) {
// Handle nested keys like 'error.message'
const parts = k.split('.')
if (parts.length > 1) {
let cursor = obj
for (let i = 0; i < parts.length - 1; i++) {
cursor[parts[i]] = cursor[parts[i]] || {}
cursor = cursor[parts[i]]
}
cursor[parts[parts.length - 1]] = v
} else {
obj[k] = v
}
}
return obj
}
}
// Cloudflare Worker entry
export default { fetch: handleRequest }
// Node.js entry
if (typeof module !== 'undefined' && require?.main === module) {
const http = require('http')
const server = http.createServer((req, res) => {
// Minimal Node.js adapter — assumes body is JSON
let body = ''
req.on('data', chunk => body += chunk)
req.on('end', async () => {
globalThis.process = process
globalThis.request = new Request(`http://localhost${req.url}`, {
method: req.method,
headers: req.headers,
body: body || undefined,
})
const response = await handleRequest(globalThis.request)
res.writeHead(response.status, Object.fromEntries(response.headers))
res.end(await response.text())
})
})
const port = process.env.PORT || 8787
server.listen(port, () => console.log(`Checkout API on :${port}`))
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
import{c as t,Q as r,j as a,m as i}from"./chunks/framework.BPKcPtvA.js";const f=JSON.parse('{"title":"Buy","description":"","frontmatter":{},"headers":[],"relativePath":"buy.md","filePath":"buy.md","lastUpdated":1780488472000}'),o={name:"buy.md"};function l(s,e,d,n,c,h){return r(),a("div",null,[...e[0]||(e[0]=[i("",16)])])}const p=t(o,[["render",l]]);export{f as __pageData,p as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"404.md":"BiCvjdaY","api-keys.md":"D2Kyj8T3","blog_index.md":"B5nL9faf","blog_posts_cascade-routing.md":"DvBM3TSf","blog_posts_choosing-security-level.md":"BYXRZEDN","blog_posts_mental-models.md":"BRY80gtq","blog_posts_repo-is-spec.md":"BxY1cXc_","blog_posts_security-ladder.md":"DQaqn6Yt","blog_posts_three-x-rule.md":"BHO6bhvz","blog_posts_verifier-pattern.md":"Gha_L_u5","blog_posts_vibe-vs-agentic.md":"7mduPfz1","blog_posts_what-is-an-agent.md":"BU2wUq_Y","blog_posts_why-multi-agent.md":"BVRIN2vH","buy.md":"CbIpCW5Y","certificate.md":"0V0n-TLl","free-preview.md":"C8rgBsjM","getting-started.md":"Boo_V9xC","index.md":"BNS2TR1g","labs_index.md":"sAzXzfkI","labs_l1-first-agent.md":"BwU9yf-G","labs_l2-context.md":"BexGm8_s","labs_l2-multi-tool.md":"Bj3Mb-oj","labs_l3-verifier.md":"xilrGGap","labs_l3-whitelist-hook.md":"DJtbL66Y","labs_l4-agent-chain.md":"D89P8dwJ","labs_l4-multi-team.md":"DmSK8e2P","labs_l5-cicd.md":"Dz1Jl78z","labs_l5-observability.md":"BDpqVYjT","labs_l6-cost-optimization.md":"CabFK4GB","labs_l6-eval-harness.md":"CBwH6xOR","labs_l7-autoresearch.md":"BYlzPLYo","labs_l7-meta-agent.md":"iTswbOPg","modules_competitive-analysis.md":"BHMHacei","modules_curriculum.md":"D7UeKRfo","modules_debate.md":"DWctKMlA","modules_feynman.md":"DBw5sPBP","modules_field-manual.md":"hmt_NLf1","modules_m1-foundations.md":"DHFyTzWj","modules_m2-architecture.md":"DVowtmf9","modules_m3-safety.md":"RiQQ_HWX","modules_m4-orchestration.md":"DFLcAKBv","modules_m5-production.md":"DTkLIrwQ","modules_m6-economics.md":"HihVEOPb","modules_m7-advanced.md":"FtCudlFk","modules_m8-capstone.md":"D1AXKCqv","modules_non-technical.md":"BnvuUCRo","modules_reference-stack.md":"D9FXitvn","modules_software-factory.md":"C5Yf8Zwe","modules_tool-reference.md":"B40mlgZJ","public_certificate_template.md":"Cg1kPB1b","resources.md":"DcUu1NrK","skills.md":"BX3RBeCK","troubleshooting.md":"B6difx2I","verify.md":"Cl5ZMWNd"} {"404.md":"BiCvjdaY","api-keys.md":"D2Kyj8T3","blog_index.md":"B5nL9faf","blog_posts_cascade-routing.md":"DvBM3TSf","blog_posts_choosing-security-level.md":"BYXRZEDN","blog_posts_mental-models.md":"BRY80gtq","blog_posts_repo-is-spec.md":"BxY1cXc_","blog_posts_security-ladder.md":"DQaqn6Yt","blog_posts_three-x-rule.md":"BHO6bhvz","blog_posts_verifier-pattern.md":"Gha_L_u5","blog_posts_vibe-vs-agentic.md":"7mduPfz1","blog_posts_what-is-an-agent.md":"BU2wUq_Y","blog_posts_why-multi-agent.md":"BVRIN2vH","buy.md":"DHBA80yx","certificate.md":"0V0n-TLl","checkout.md":"BJaDuNmQ","checkout_cancel.md":"CdJxBFk8","checkout_success.md":"tbRwtAen","free-preview.md":"C5BtRucn","getting-started.md":"Boo_V9xC","index.md":"BNS2TR1g","labs_index.md":"sAzXzfkI","labs_l1-first-agent.md":"BwU9yf-G","labs_l2-context.md":"BexGm8_s","labs_l2-multi-tool.md":"Bj3Mb-oj","labs_l3-verifier.md":"xilrGGap","labs_l3-whitelist-hook.md":"DJtbL66Y","labs_l4-agent-chain.md":"D89P8dwJ","labs_l4-multi-team.md":"DmSK8e2P","labs_l5-cicd.md":"Dz1Jl78z","labs_l5-observability.md":"BDpqVYjT","labs_l6-cost-optimization.md":"CabFK4GB","labs_l6-eval-harness.md":"CBwH6xOR","labs_l7-autoresearch.md":"BYlzPLYo","labs_l7-meta-agent.md":"iTswbOPg","modules_competitive-analysis.md":"BHMHacei","modules_curriculum.md":"D7UeKRfo","modules_debate.md":"DWctKMlA","modules_feynman.md":"DBw5sPBP","modules_field-manual.md":"hmt_NLf1","modules_m1-foundations.md":"DHFyTzWj","modules_m2-architecture.md":"DVowtmf9","modules_m3-safety.md":"RiQQ_HWX","modules_m4-orchestration.md":"DFLcAKBv","modules_m5-production.md":"DTkLIrwQ","modules_m6-economics.md":"HihVEOPb","modules_m7-advanced.md":"FtCudlFk","modules_m8-capstone.md":"D1AXKCqv","modules_non-technical.md":"BnvuUCRo","modules_reference-stack.md":"D9FXitvn","modules_software-factory.md":"C5Yf8Zwe","modules_tool-reference.md":"B40mlgZJ","public_certificate_template.md":"Cg1kPB1b","resources.md":"DcUu1NrK","skills.md":"BX3RBeCK","troubleshooting.md":"B6difx2I","verify.md":"Cl5ZMWNd"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
<div class="payment-note"> <div class="payment-note">
<span class="material-symbols-outlined">lock</span> <span class="material-symbols-outlined">lock</span>
Secure checkout powered by Gumroad Secure checkout — Stripe + Gumroad
</div> </div>
## Choose Your Tier ## Choose Your Tier
@ -21,7 +21,8 @@ Best for independent learners.
- Mock LLM for offline lab execution - Mock LLM for offline lab execution
- Instant download (9 ZIP packages) - Instant download (9 ZIP packages)
[Buy Self-Paced — $97](https://gumroad.com/l/fdsa-agentic-selfpaced) — *Instant access after purchase* <a href="/checkout?plan=self-paced" class="btn-p btn-primary">Buy with Card — $97</a>
<a href="https://gumroad.com/l/fdsa-agentic-selfpaced" class="btn-p btn-ghost">Buy via Gumroad</a>
### Enterprise — **$199** ### Enterprise — **$199**
Best for teams and organizations. Best for teams and organizations.
@ -33,7 +34,8 @@ Everything in Self-Paced, plus:
- Priority updates for 1 year - Priority updates for 1 year
- Early access to new skill kits - Early access to new skill kits
[Buy Enterprise — $199](https://gumroad.com/l/fdsa-agentic-enterprise) — *Team license included* <a href="/checkout?plan=enterprise" class="btn-p btn-primary">Buy with Card — $199</a>
<a href="https://gumroad.com/l/fdsa-agentic-enterprise" class="btn-p btn-ghost">Buy via Gumroad</a>
### Cohort — **$247** (Next cohort: TBD) ### Cohort — **$247** (Next cohort: TBD)
Best for structured learning with deadlines. Best for structured learning with deadlines.
@ -45,7 +47,8 @@ Everything in Enterprise, plus:
- Completion certificate - Completion certificate
- Cohort alumni network - Cohort alumni network
[Join Cohort — $247](https://gumroad.com/l/fdsa-agentic-cohort) — *Next cohort date TBD* <a href="/checkout?plan=cohort" class="btn-p btn-primary">Buy with Card — $247</a>
<a href="https://gumroad.com/l/fdsa-agentic-cohort" class="btn-p btn-ghost">Buy via Gumroad</a>
</div> </div>
@ -55,30 +58,52 @@ Everything in Enterprise, plus:
Not ready for the full course? Buy individual kits: Not ready for the full course? Buy individual kits:
| Kit | Price | What's Included | Buy | | Kit | Price | What's Included |
|-----|-------|-----------------|-----| |-----|-------|-----------------|
| Security Foundation | $49 | L3-L5 hooks, damage control, sandbox | [Buy](https://gumroad.com/l/fdsa-kit-security) | | Security Foundation | $49 | L3-L5 hooks, damage control, sandbox |
| Multi-Agent Orch. | $49 | Teams, chains, mental models, domain locks | [Buy](https://gumroad.com/l/fdsa-kit-orch) | | Multi-Agent Orch. | $49 | Teams, chains, mental models, domain locks |
| Verifier Pro | $39 | Builder + verifier, claim decomposition | [Buy](https://gumroad.com/l/fdsa-kit-verifier) | | Verifier Pro | $39 | Builder + verifier, claim decomposition |
| Task Discipline | $29 | TillDone core + progress + nudge | [Buy](https://gumroad.com/l/fdsa-kit-task) | | Task Discipline | $29 | TillDone core + progress + nudge |
| Autoresearch | $39 | Experiment loop, integrity guards | [Buy](https://gumroad.com/l/fdsa-kit-research) | | Autoresearch | $39 | Experiment loop, integrity guards |
| Observability | $29 | SQLite tracing, cost tracking | [Buy](https://gumroad.com/l/fdsa-kit-obs) | | Observability | $29 | SQLite tracing, cost tracking |
| CEO Board | $49 | 11 agent definitions + verifier | [Buy](https://gumroad.com/l/fdsa-kit-ceo) | | CEO Board | $49 | 11 agent definitions + verifier |
*Skill kits coming soon to Stripe. For now, [contact us](mailto:artale@fdsa.agency) to purchase individual kits.*
--- ---
## What Happens After Purchase ## What Happens After Purchase
1. **Instant download** — ZIP packages delivered immediately via Gumroad 1. **Instant redirect** to Stripe Checkout — pay with any card
2. **Access links** — Course materials, labs, and skill kits in your Gumroad library 2. **Download delivered** immediately after payment via email
3. **Enterprise/Cohort** — You'll receive a follow-up email within 24hrs with Discord invite and onboarding details 3. **Enterprise/Cohort** — follow-up email within 24hrs with Discord invite
4. **Updates** — Lifetime access includes all future updates. Re-download anytime. 4. **Updates** — lifetime access, re-download anytime
---
## Setup Guide
To enable card payments, you need to:
1. Create a [Stripe account](https://dashboard.stripe.com/register)
2. Get your **Secret Key** from the Stripe dashboard
3. Set it as an environment variable for the checkout API:
```bash
# If using Cloudflare Worker:
echo "STRIPE_SECRET_KEY=sk_live_..." > .env
# If deploying the API separately, set:
export STRIPE_SECRET_KEY="sk_live_..."
```
The checkout API is at `api/checkout.js` — deployable as a Cloudflare Worker, Vercel function, or standalone Node.js server.
--- ---
## Certificate ## Certificate
Students who complete the capstone project receive a Certificate of Completion. The certificate verifies completion of 65 lessons, 13 labs, 56 quizzes, and a production-grade capstone project. Verify at `fdsa.agency/verify`. Students who complete the capstone project receive a Certificate of Completion. Verify at `fdsa.agency/verify`.
--- ---
@ -93,9 +118,14 @@ Students who complete the capstone project receive a Certificate of Completion.
font-size: 13px; color: #d95c41; margin-bottom: 32px; font-size: 13px; color: #d95c41; margin-bottom: 32px;
} }
.tier-grid { .tier-grid {
display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 24px; margin: 24px 0; gap: 24px; margin: 24px 0;
} }
.tier-grid h3 { margin-top: 0; } .tier-grid h3 { margin-top: 0; }
.tier-grid ul { margin-bottom: 20px; } .tier-grid ul { margin-bottom: 20px; }
.tier-grid .btn-p { display: inline-block; padding: 12px 20px; margin: 4px 2px; font-size: 14px; text-align: center; text-decoration: none; }
.tier-grid .btn-primary { background: #d95c41; color: #fff; border-radius: 6px; }
.tier-grid .btn-primary:hover { background: #c44a30; }
.tier-grid .btn-ghost { background: transparent; color: #888; border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; }
.tier-grid .btn-ghost:hover { border-color: #555; color: #e8e8e8; }
</style> </style>

75
site/checkout.md Normal file
View File

@ -0,0 +1,75 @@
---
title: Checkout - Agentic Engineering the Hard Way
---
# Redirecting to payment...
<script setup>
import { onMounted } from 'vue'
import { useData, useRouter } from 'vitepress'
const { site } = useData()
const router = useRouter()
const PRICES = {
'self-paced': { price_id: 'price_self_paced', name: 'Self-Paced', amount: 9700 },
'enterprise': { price_id: 'price_enterprise', name: 'Enterprise', amount: 19900 },
'cohort': { price_id: 'price_cohort', name: 'Cohort', amount: 24700 },
}
onMounted(async () => {
const params = new URLSearchParams(window.location.search)
const plan = params.get('plan') || 'self-paced'
const email = params.get('email') || ''
const price = PRICES[plan]
if (!price) {
document.getElementById('status').textContent = 'Invalid plan selected.'
return
}
document.getElementById('status').textContent = `Creating checkout session for ${price.name}...`
try {
const resp = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
price_id: price.price_id,
mode: 'payment',
customer_email: email || undefined,
}),
})
const data = await resp.json()
if (data.url) {
window.location.href = data.url
} else {
document.getElementById('status').textContent =
'Checkout is not yet configured. Please use the Gumroad links on the pricing page.'
document.getElementById('fallback').style.display = 'block'
}
} catch (e) {
document.getElementById('status').textContent =
'Payment system is being set up. Use the Gumroad links below.'
document.getElementById('fallback').style.display = 'block'
}
})
</script>
<div id="status" style="text-align:center;padding:40px;color:var(--vp-c-text-2)">
Initializing checkout...
</div>
<div id="fallback" style="display:none;text-align:center;padding:20px">
<p style="margin-bottom:20px">You can also purchase directly via Gumroad:</p>
<p>
<a href="https://gumroad.com/l/fdsa-agentic-selfpaced" class="btn-p btn-primary" style="display:inline-block;padding:12px 24px;margin:4px">Self-Paced — $97</a>
<a href="https://gumroad.com/l/fdsa-agentic-enterprise" class="btn-p btn-ghost" style="display:inline-block;padding:12px 24px;margin:4px">Enterprise — $199</a>
<a href="https://gumroad.com/l/fdsa-agentic-cohort" class="btn-p btn-ghost" style="display:inline-block;padding:12px 24px;margin:4px">Cohort — $247</a>
</p>
<p style="margin-top:20px;font-size:13px;color:var(--vp-c-text-3)">
All purchases handled securely by Stripe/Gumroad. 30-day guarantee.
</p>
</div>

20
site/checkout/cancel.md Normal file
View File

@ -0,0 +1,20 @@
---
title: Payment Cancelled
---
# Payment Cancelled
Your payment was not completed. No charges were made.
## Try Again
- [Self-Paced — $97](/checkout?plan=self-paced)
- [Enterprise — $199](/checkout?plan=enterprise)
- [Cohort — $247](/checkout?plan=cohort)
Or purchase via Gumroad:
- [Self-Paced on Gumroad](https://gumroad.com/l/fdsa-agentic-selfpaced)
- [Enterprise on Gumroad](https://gumroad.com/l/fdsa-agentic-enterprise)
- [Cohort on Gumroad](https://gumroad.com/l/fdsa-agentic-cohort)
*Questions? artale@fdsa.agency*

35
site/checkout/success.md Normal file
View File

@ -0,0 +1,35 @@
---
title: Payment Successful
---
# Payment Successful!
Your purchase is complete. You now have access to **Agentic Engineering the Hard Way**.
## What Happens Next
1. **Check your email** for a receipt and download links
2. **Download the course** from the link in your email
3. **Join the community** at the Discord link in your welcome email
## Quick Start
```bash
# Download and extract the course package, then:
cd agentic-engineering
python3 -m venv venv
source venv/bin/activate # or venv\Scripts\activate on Windows
pip install anthropic openai
# Start with Lab 1:
cd course/labs/L1-first-agent/
python starter.py test.txt "What is this file about?"
```
## Need Help?
- **Course content**: Check the [Getting Started Guide](/getting-started)
- **Lab issues**: Each lab has a `solution.py` file with the complete answer
- **Other questions**: Email artale@fdsa.agency
*Welcome to Agentic Engineering the Hard Way.*