diff --git a/kits/sporting-director/.env.example b/kits/sporting-director/.env.example new file mode 100644 index 000000000..1378fce05 --- /dev/null +++ b/kits/sporting-director/.env.example @@ -0,0 +1,5 @@ +LAMATIC_API_KEY=your_lamatic_api_key_here +LAMATIC_ENDPOINT=your_lamatic_flow_endpoint_here +LAMATIC_FLOW_ID=cfaf6f71-033c-4ad2-abc6-3ae77158097f +LAMATIC_PROJECT_ID=11727d21-0009-4e68-a4f3-c891a54f1baf + diff --git a/kits/sporting-director/.gitignore b/kits/sporting-director/.gitignore new file mode 100644 index 000000000..84713a1fa --- /dev/null +++ b/kits/sporting-director/.gitignore @@ -0,0 +1,4 @@ +.lamatic/ +node_modules/ +.env* +!.env.example diff --git a/kits/sporting-director/README.md b/kits/sporting-director/README.md new file mode 100644 index 000000000..49289bacc --- /dev/null +++ b/kits/sporting-director/README.md @@ -0,0 +1,58 @@ +# sporting-director + +AI-powered decision-support assistant for football sporting directors and recruitment teams. Given a target player and buying club context, it searches for real, current information and generates a structured transfer feasibility report — helping directors quickly assess whether a target is realistic before investing research time. + +## What it does + +Instead of manually researching a player across news sites, transfer trackers, relying on scouts and rumor mills, a sporting director provides: +- Player name +- Buying club +- Available budget +- Club's positional/tactical need + +The agent searches for recent, real information about the player and returns a structured report covering: +1. **Transfer Feasibility Score** (1-10) with justification +2. **Tactical Fit Summary** — how the player suits the club's needs +3. **Personal/Cultural Fit** — rivalries, boyhood club ties, public statements, if evidenced +4. **Competing Interest** — other clubs reportedly in the race +5. **Additional Intel** — agent, valuation, form, contract terms, if available +6. **Key Risks** — contractual, financial, fitness, or behavioral +7. **Alternative Targets** — suggested if feasibility is low + +The agent only reports facts found in search results — it does not invent statistics, quotes, or rivalries. When information isn't available, it says so explicitly. + +## Setup + +1. Clone this repo and navigate to `kits/sporting-director/apps/` +2. Copy `.env.example` to `.env.local` and fill in your Lamatic API key and flow endpoint +3. Install dependencies: `npm install` +4. Run locally: `npm run dev` + +## Flow architecture + +API Request (playerName, buyingClub, budget, needs) +→ Web Search (recent news/info about the player) +→ Generate Text (structured feasibility report) +→ API Response + +See `flows/sporting-director.ts` for the full flow definition and `agent.md` for the agent contract. + +## Example + +**Input:** Player: Neymar, Buying Club: Real Madrid, Budget: €5M, Needs: high-profile signing + +**Output:** Feasibility score of 1/10 — flags his active Santos contract through Dec 2026, budget mismatch, and recent injury/fitness concerns, with a note on Barcelona rivalry history. + +## Who this is for + +This tool is best suited for **low and mid-tier clubs** with limited scouting budgets and research staff. Top-tier clubs typically have dedicated recruitment departments, proprietary scouting networks, and data providers (e.g. Wyscout, StatsBomb) that already outperform what public search can surface, for them, this is a supplementary sanity-check at best, not a primary tool. + +For resource-constrained clubs, it compresses hours of manual research (checking transfer sites, news, forums) into a single structured report, freeing up staff time for outreach and negotiation instead of information-gathering. + +## Limitations + +- Market valuation, agent representation, and negotiation data depend on what's publicly searchable — not always available +- Search results are limited to the last 30 days by default, which may miss older but relevant context +- This is a research aid, not a replacement for professional scouting or due diligence +- Reports are based on publicly available search results, which lag behind real-time information that agents, insiders, or larger clubs' networks may already have. Decision-makers should treat this as a starting point for research, not a final or time-sensitive signal — act quickly once a target is identified, since competing interest may already be ahead in negotiations +- Not a substitute for direct scouting network intelligence available to well-resourced clubs \ No newline at end of file diff --git a/kits/sporting-director/agent.md b/kits/sporting-director/agent.md new file mode 100644 index 000000000..d64096a85 --- /dev/null +++ b/kits/sporting-director/agent.md @@ -0,0 +1,22 @@ +# Sporting Director + +## Overview + +Sporting Director is a decision-support assistant for football recruitment teams. Given a target player and buying club context, it searches for real, current information about the player and generates a structured feasibility report — helping sporting directors quickly assess whether a transfer target is realistic before committing research time. + +## Flow + +1. **API Request** — accepts `playerName`, `buyingClub`, `budget`, and `needs` (club's positional/tactical need) +2. **Web Search** — searches for recent news, transfer rumors, and player information (last 30 days) +3. **Generate Text** — analyzes search results and produces a structured report: transfer feasibility score, tactical fit, personal/cultural fit, competing interest, key risks, and alternative targets +4. **API Response** — returns the report as structured JSON + +## Guardrails + +- The agent only uses facts present in search results — it does not invent statistics, quotes, or rivalries +- When information is unavailable, the report explicitly states so rather than speculating +- Output follows a concise, scannable executive-briefing style suitable for time-constrained decision-makers + +## Integration + +Call the flow's API endpoint with the four required inputs. See README.md for setup and usage details. \ No newline at end of file diff --git a/kits/sporting-director/apps/.env.example b/kits/sporting-director/apps/.env.example new file mode 100644 index 000000000..61d71b8dd --- /dev/null +++ b/kits/sporting-director/apps/.env.example @@ -0,0 +1,4 @@ +LAMATIC_API_KEY=your_lamatic_api_key_here +LAMATIC_ENDPOINT=your_lamatic_flow_endpoint_here +LAMATIC_FLOW_ID=cfaf6f71-033c-4ad2-abc6-3ae77158097f +LAMATIC_PROJECT_ID=11727d21-0009-4e68-a4f3-c891a54f1baf diff --git a/kits/sporting-director/apps/.gitignore b/kits/sporting-director/apps/.gitignore new file mode 100644 index 000000000..7b8da95f5 --- /dev/null +++ b/kits/sporting-director/apps/.gitignore @@ -0,0 +1,42 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* +!.env.example + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/kits/sporting-director/apps/README.md b/kits/sporting-director/apps/README.md new file mode 100644 index 000000000..5b3a2a062 --- /dev/null +++ b/kits/sporting-director/apps/README.md @@ -0,0 +1,38 @@ +# Sporting Director App + +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/kits/sporting-director/apps/actions/orchestrate.ts b/kits/sporting-director/apps/actions/orchestrate.ts new file mode 100644 index 000000000..2d406b523 --- /dev/null +++ b/kits/sporting-director/apps/actions/orchestrate.ts @@ -0,0 +1,67 @@ +"use server"; + +import { lamatic, FLOW_ID } from "@/lib/lamatic-client"; + +export type ScoutResult = { + report: string; +}; + +export type ScoutActionResult = + | { success: true; data: ScoutResult } + | { success: false; error: string }; + +export async function generateReport( + playerName: string, + buyingClub: string, + budget: string, + needs: string +): Promise { + + const requiredValues = [playerName, buyingClub, budget, needs]; + if ( + requiredValues.some( + (value) => typeof value !== "string" || !value.trim() + ) + ) { + return { + success: false, + error: "Player name, buying club, budget, and club needs are required" + } + } + + try { + const response = await lamatic.executeFlow(FLOW_ID, { + playerName, + buyingClub, + budget, + needs, + }); + + if (response.status !== "success") { + return { + success: false, + error: + typeof response.message === "string" && response.message + ? response.message + : "The workflow could not process this request.", + }; + } + + if (!response.result) { + return { success: false, error: "The workflow returned no result." }; + } + + const raw = response.result as { report?: string }; + if (!raw.report || !raw.report.trim()){ + return { success: false, error: "The workflow returned an empty report."}; + } + return { success: true, data: { report: raw.report }}; + + } catch { + console.error("generateReport failed while executing the Lamatic workflow."); + return { + success: false, + error: "Could not reach the Lamatic workflow. Please try again.", + }; + } +} \ No newline at end of file diff --git a/kits/sporting-director/apps/app/favicon.ico b/kits/sporting-director/apps/app/favicon.ico new file mode 100644 index 000000000..718d6fea4 Binary files /dev/null and b/kits/sporting-director/apps/app/favicon.ico differ diff --git a/kits/sporting-director/apps/app/globals.css b/kits/sporting-director/apps/app/globals.css new file mode 100644 index 000000000..1fd3f50d8 --- /dev/null +++ b/kits/sporting-director/apps/app/globals.css @@ -0,0 +1,50 @@ +@import "tailwindcss"; + +:root { + --background: #ffffff; + --foreground: #171717; + + /* Sporting Director Component Palette */ + --overlay-start: rgb(0 0 0 / 0.4); + --overlay-mid: rgb(0 0 0 / 0.5); + --overlay-end: rgb(0 0 0 / 0.7); + + --surface-bg: rgb(255 255 255 / 0.1); + --surface-border: rgb(255 255 255 / 0.2); + --surface-border-focus: rgb(255 255 255 / 0.5); + + --text-primary: #ffffff; + --text-body: rgb(255 255 255 / 0.9); + --text-secondary: rgb(255 255 255 / 0.8); + --text-muted: rgb(255 255 255 / 0.6); + --text-placeholder: rgb(255 255 255 / 0.4); + + --btn-bg: #ffffff; + --btn-bg-hover: rgb(255 255 255 / 0.9); + --btn-text: #111827; + + --error-bg: rgb(69 10 10 / 0.4); + --error-border: rgb(248 113 113 / 0.3); + --error-text: rgb(252 165 165); + --error-text-alt: rgb(254 202 202); +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +body { + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; +} \ No newline at end of file diff --git a/kits/sporting-director/apps/app/layout.tsx b/kits/sporting-director/apps/app/layout.tsx new file mode 100644 index 000000000..9755bbcc2 --- /dev/null +++ b/kits/sporting-director/apps/app/layout.tsx @@ -0,0 +1,33 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Agent Hunt", + description: "AI Sporting Director - transfer feasibility briefings", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/kits/sporting-director/apps/app/page.tsx b/kits/sporting-director/apps/app/page.tsx new file mode 100644 index 000000000..ba059b068 --- /dev/null +++ b/kits/sporting-director/apps/app/page.tsx @@ -0,0 +1,161 @@ +"use client"; + +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { generateReport } from "@/actions/orchestrate"; +import { UserRound } from "lucide-react"; + +const formSchema = z.object({ + playerName: z.string().trim().min(1, "Player name is required"), + buyingClub: z.string().trim().min(1, "Buying club is required"), + budget: z.string().trim().min(1, "Budget is required"), + needs: z.string().trim().min(1, "Club needs is required"), +}); + +type FormValues = z.infer; + +export default function Home() { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ resolver: zodResolver(formSchema) }); + + const [report, setReport] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + async function onSubmit(values: FormValues) { + setLoading(true); + setError(null); + setReport(null); + + try { + const result = await generateReport( + values.playerName, + values.buyingClub, + values.budget, + values.needs + ); + + if (!result.success) { + setError(result.error); + } else { + setReport(result.data.report); + } + } catch { + setError("Could not generate the report. Please try again."); + } finally { + setLoading(false); + } + } + + return ( +
+ {/* Dark overlay for readability */} +
+ +
+
+
+
+
+

Agent Hunt

+

Sporting Director

+
+
+ +
+
+ + + {errors.playerName && ( +

{errors.playerName.message}

+ )} +
+ +
+ + + {errors.buyingClub && ( +

{errors.buyingClub.message}

+ )} +
+ +
+ + + {errors.budget && ( +

{errors.budget.message}

+ )} +
+ +
+ +