# Project export: nebulink

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 11.0
- Tagline: Find the right people, build real connections.
- Devpost: https://devpost.com/software/nebulink
- GitHub: https://github.com/ashleyzhang01/nebulink
- Team: 3 GitHub contributor(s) — Ashley Zhang (29 commits), “kellyhongsn” (26 commits), Sean Yang (16 commits)

## Devpost submission (written by the team)

### Inspiration

Have you ever spent hours trying to find the right person—whether it’s a hire, a mentor, or a collaborator—only to feel like the process was inefficient and hit-or-miss? It’s not just about finding talent; it’s about finding people who align with your goals, bring positive value, and fit into your long-term vision. From our experience as founders and students, we’ve seen that the best connections come from meaningful relationships. Successful hiring, mentorship, or collaboration is about building relationships over time, identifying mutual values, and offering the right opportunity at the right moment. The problem? This process is time-consuming and often reliant on referrals or alumni networks, making it hard to discover the best-fit individuals. That’s why we built this platform: to automate parts of the process while increasing its human-centered essence. We don’t just help users search for people or talent—we enable them to understand their network, foster genuine connections, and make strategic decisions with visibility into how people and companies relate to one another.

### What it does

Our platform combines real-time graph visualization with LLM-powered search and message generation to streamline relationship-building. Users connect their social platforms (e.g., LinkedIn, GitHub) to view an interactive graph of people, companies, and connections, grouped by organizations and structured by degrees of separation. The graph provides insights into the strength of connections, letting users navigate between platforms to explore how individuals and organizations are linked. With semantic search, users can input queries like: “Find a cybersecurity mentor at a startup.” “Who in my network has experience with AI and fundraising?” Behind the scenes, a multi-agent system classifies each query and triggers specialized agents to search through GitHub, LinkedIn, or other sources. Results are stored as vector embeddings in ChromaDB, ensuring quick and relevant retrieval. Users receive not just matches, but insights into who the right person is based on shared values, skills, and experiences. To facilitate outreach, the platform offers tailored message generation based on the context of the platform and the common points between them. If they don’t have a direct connection, they can request introductions through mutual contacts. Finally, data-scraping agents pull and refresh data from platforms like GitHub and LinkedIn to keep the graph updated. The agents work on a schedule or on-demand by proxy, ensuring users always have access to the latest information.

### How we built it

Our platform combines a multi-agent system, interactive graph visualization, and intelligent search and outreach capabilities to help users discover and build meaningful connections. Here’s a breakdown of the core technologies and how they work together: Multi-Agent System: Classification agents analyze user queries to determine if they target people or companies. Search agents first figure out how best identify key individuals or companies that fit user criteria. They then optimize queries with the Hyperbolic API, based on how many searches they need to get to the goal, and where they want to search. Scraping agents from Fetch.ai pull data from platforms like GitHub and LinkedIn, extracting relevant content (e.g., GitHub projects, LinkedIn profiles) to enrich the graph or store as vector embeddings in ChromaDB for more optimized search. Agents also retrieve contact information, ensuring a way to reach out to people even if the platform doesn’t offer direct messaging (e.g., finding GitHub usernames from email addresses). Message-generation agents create personalized outreach content, incorporating Hyperbolic inference APIs to ensure contextual relevance and prevent hallucinations. Interactive Graph Visualization and Retrieval: Built using Three.js for smooth rendering, with a SQL database for relational data and ChromaDB for vector-based search Each node represents a person, company, or organization, with node size reflecting the strength of the connection. Users can navigate their connections across platforms, grouped by organizations, and seamlessly click between contexts (e.g., viewing a person’s LinkedIn profile and their GitHub activity). If not logged in, the graph displays public companies without revealing sensitive data, preserving privacy.

### Challenges we ran into

Designing a Complex System and Maintaining Consistency: Building a platform with multiple interconnected agents, data sources, and visual components required meticulous planning. Dividing tasks across our team while maintaining a consistent structure and flow was challenging, especially when coordinating agents’ tasks (e.g., search, scraping, message generation) and datatypes to work together seamlessly. Smooth communication between components was essential to avoid duplication, misalignment, or performance bottlenecks. Scraping Without Accessible APIs: Platforms like LinkedIn don’t provide public APIs for many key data points, making scraping particularly challenging. We had to develop creative solutions to reliably access and extract the data while staying compliant with privacy policies. For users who don’t want to provide account credentials, we implemented proxy-based scraping techniques that work within platform limitations, providing functionality without compromising security or usability. Graph Search Optimization: Traversing large networks in real-time is computationally demanding. With thousands of nodes and connections, we focused on minimizing query complexity using advanced search techniques, such as graph pruning and embeddings-based lookups, to ensure fast and responsive performance. Data Consistency and Scraping: Pulling data from multiple sources with different structures required intelligent fetch and refresh cycles. Agents handle scheduled updates and on-demand queries to keep data current without overwhelming APIs. Accurate, Context-Aware Messaging: Balancing automation with personalization was challenging. The Hyperbolic API ensures that messages align with the search context, preventing irrelevant or incorrect messaging. Cross-Platform Integration: Creating smooth transitions between platforms like GitHub and LinkedIn required careful planning to synchronize different data structures and privacy constraints. The wifi at the venue was very slow and made scraping and LLM calls very inefficient.

### Accomplishments we're proud of

For all of us, this was our first multi-agent system relying on heavy inter-agent communication. We're of our robust multi-agent system capable of handling classification, search, scraping, and personalized outreach seamlessly. Using graph traversal and theory we learned in our classes in a real application! Developed a real-time, interactive graph that visualizes thousands of nodes, connections, and degrees of separation efficiently. Very pleasing to scroll and click around on too. Successfully integrated ChromaDB embeddings to enable fast, semantic search across diverse datasets and 2 types of dbs. Created a platform that not only identifies the right connections but also facilitates genuine relationship-building over time. While building this, we definitely gained even more insight on the benefits a tool like this can bring.

### What we learned

Real-time graph traversal at scale is challenging: Managing thousands of nodes efficiently (and our computers continuously crashing) required us to rethink our query optimization strategies, ultimately leading to embeddings-based lookups and graph pruning techniques. Consistent data management and communication about it is key: Keeping scraped data current across multiple platforms was more complex than anticipated, especially when we divided up tasks without clear communication about it and had to make changes on that later. Intelligent agents allowed us to balance data freshness with API usage constraints. The strongest models aren't always necessary. We experimented a lot and ended up going with lighter models to balance performance and latency. graph animations! (physics and math there too)

### What's next

Twitter scraping Groups of users, to see all your connections/networks together Filters for graph visualization More optimal algorithms Sentiment analysis and more user info, for message drafting to really personalize the user Searching with you.com or perplexity for additional context for message drafting

## README (from the GitHub repository)

# nebulink

<img width="1691" alt="Screenshot 2024-10-20 at 9 23 55 AM" src="https://github.com/user-attachments/assets/7c5c44d7-b526-45ec-b6bd-82bfe4319ab3">

## Inspiration
Have you ever spent hours trying to find the right person—whether it’s a hire, a mentor, or a collaborator—only to feel like the process was inefficient and hit-or-miss? It’s not just about finding talent; it’s about finding people who align with your goals, bring positive value, and fit into your long-term vision.


The problem? This process is time-consuming and often reliant on referrals or alumni networks, making it hard to discover the best-fit individuals. That’s why we built this platform: **to automate parts of the process while increasing its human-centered essence.** We don’t just help users search for people or talent—we enable them to **understand their network, foster genuine connections, and make strategic decisions** with visibility into how people and companies relate to one another.

## What it does
Our platform combines **real-time graph visualization** with **LLM-powered search and message generation** to streamline relationship-building. Users connect their social platforms (e.g., LinkedIn, GitHub) to view an interactive graph of people, companies, and connections, grouped by organizations and structured by degrees of separation. The graph provides insights into the **strength of connections**, letting users navigate between platforms to explore how individuals and organizations are linked.

With **semantic search**, users can input queries like:
- “Find a cybersecurity mentor at a startup.”
- “Who in my network has experience with AI and fundraising?”
Behind the scenes, a **multi-agent system** classifies each query and triggers specialized agents to search through GitHub, LinkedIn, or other sources. Results are stored as **vector embeddings in ChromaDB**, ensuring quick and relevant retrieval. Users receive not just matches, but insights into who the right person is based on shared values, skills, and experiences.

To facilitate outreach, the platform offers **tailored message generation** based on the context of the platform and the common points between them. If they don’t have a direct connection, they can request introductions through mutual contacts.

Finally, **data-scraping agents** pull and refresh data from platforms like GitHub and LinkedIn to keep the graph updated. The agents work on a schedule or on-demand by proxy, ensuring users always have access to the latest information.

## How we built it
Our platform combines a multi-agent system, interactive graph visualization, and intelligent search and outreach capabilities to help users discover and build meaningful connections. Here’s a breakdown of the core technologies and how they work together:

1. Multi-Agent System:
 - Classification agents analyze user queries to determine if they target people or companies.
 - Search agents first figure out how best identify key individuals or companies that fit user criteria. They then optimize queries with the **Hyperbolic** API, based on how many searches they need to get to the goal, and where they want to search.
 - Scraping agents from **Fetch.ai** pull data from platforms like GitHub and LinkedIn, extracting relevant content (e.g., GitHub projects, LinkedIn profiles) to enrich the graph or store as vector embeddings in **ChromaDB** for more optimized search. Agents also retrieve contact information, ensuring a way to reach out to people even if the platform doesn’t offer direct messaging (e.g., finding GitHub usernames from email addresses).
 - Message-generation agents create personalized outreach content, incorporating **Hyperbolic inference APIs** to ensure contextual relevance and prevent hallucinations.
2. Interactive Graph Visualization and Retrieval:
 - Built using **Three.js** for smooth rendering, with a SQL database for relational data and **ChromaDB** for vector-based search
 - Each node represents a person, company, or organization, with node size reflecting the strength of the connection.
 - Users can navigate their connections across platforms, grouped by organizations, and seamlessly click between contexts (e.g., viewing a person’s LinkedIn profile and their GitHub activity).
 - If not logged in, the graph displays public companies without revealing sensitive data, preserving privacy.

## What's next for nebulink
- Twitter scraping
- Groups of users, to see all your connections/networks together
- Filters for graph visualization
- More optimal algorithms
- Sentiment analysis and more user info, for message drafting to really personalize the user
- Searching with you.com or perplexity for additional context for message drafting


## Setup

### Backend
cd backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
uvicorn main:app --reload

If you add new packages, run `pip freeze > requirements.txt` to update the requirements.txt file.
To lint, run `chmod +x lint.sh` to make it executable for the first time, and then run `./lint.sh`.

To run the Fetch AI agent, run `python -m app.agents.scraper_agents`. They will be hosted on port 8001!

### Frontend
cd frontend
npm install
npm run dev


## Detected evidence (automated analysis)

Indexed codebase: 67 recognized source files, 196 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (87 of 87)

```
.gitattributes
.gitignore
backend/.flake8
backend/.gitignore
backend/app/__init__.py
backend/app/agents/__init__.py
backend/app/agents/classifier.py
backend/app/agents/company_query_optimizer.py
backend/app/agents/github_scraper_agent_helper.py
backend/app/agents/linkedin_scraper_agent_helper.py
backend/app/agents/other_query_optimizer.py
backend/app/agents/scraper_agents.py
backend/app/agents/search_communication.py
backend/app/api/__init__.py
backend/app/api/routes/__init__.py
backend/app/api/routes/auth.py
backend/app/api/routes/github.py
backend/app/api/routes/linkedin.py
backend/app/api/routes/message.py
backend/app/api/routes/network.py
backend/app/api/routes/search.py
backend/app/chromadb/__init__.py
backend/app/chromadb/dataclass.py
backend/app/chromadb/internal_api.py
backend/app/core/__init__.py
backend/app/core/config.py
backend/app/db/__init__.py
backend/app/db/database.py
backend/app/db/github_user_functions.py
backend/app/db/linkedin_organization_functions.py
backend/app/db/linkedin_user_functions.py
backend/app/db/repository_functions.py
backend/app/db/session.py
backend/app/db/user_functions.py
backend/app/models/__init__.py
backend/app/models/github_user.py
backend/app/models/linkedin_organization.py
backend/app/models/linkedin_user.py
backend/app/models/repository.py
backend/app/models/user.py
backend/app/schemas/__init__.py
backend/app/schemas/github_user.py
backend/app/schemas/linkedin_organization.py
backend/app/schemas/linkedin_user.py
backend/app/schemas/message.py
backend/app/schemas/repository.py
backend/app/schemas/search.py
backend/app/schemas/user.py
backend/app/utils/__init__.py
backend/app/utils/enums.py
backend/app/utils/github_scraper.py
backend/app/utils/linkedin_scraper.py
backend/app/utils/security.py
backend/lint.sh
backend/main.py
backend/mypy.ini
backend/persistent_chroma_client/chroma.sqlite3
backend/poetry.lock
backend/private_keys.json
backend/pyproject.toml
backend/requirements.txt
backend/scripts/migrate_to_singlestore.py
backend/sql_app.db
frontend/.eslintrc.json
frontend/next.config.mjs
frontend/output.log
frontend/package.json
frontend/postcss.config.mjs
frontend/public/dummy_data/dummy_links.json
frontend/public/dummy_data/dummy_nodes.json
frontend/rand.json
frontend/README.md
frontend/src/app/globals.css
frontend/src/app/layout.tsx
frontend/src/app/login/page.tsx
frontend/src/app/page.tsx
frontend/src/app/settings/page.tsx
frontend/src/app/signup/page.tsx
frontend/src/components/Navbar.tsx
frontend/src/components/NetworkGraph.tsx
frontend/src/components/OriginalGraph.tsx
frontend/src/components/PublicNetworkGraph.tsx
frontend/src/components/SearchBox.tsx
frontend/src/types.ts
frontend/tailwind.config.ts
frontend/tsconfig.json
README.md
```

### Dependencies

- backend/pyproject.toml: uagents@^0.16.2
- backend/requirements.txt: aiohappyeyeballs@==2.4.3, aiohttp@==3.10.10, aiomysql@==0.2.0, aiosignal@==1.3.1, alembic@==1.13.3, annotated-types@==0.7.0, anyio@==4.6.2.post1, apispec@==6.6.1, attrs@==24.2.0, bcrypt@==4.0.1, beautifulsoup4@==4.12.3, bech32@==1.2.0, bs4@==0.0.2, certifi@==2024.8.30, cffi@==1.17.1, charset-normalizer@==3.4.0, click@==8.1.7, cosmpy@==0.9.2, cryptography@==43.0.3, dnspython@==2.7.0, ecdsa@==0.19.0, email_validator@==2.2.0, fastapi@==0.115.2, flake8@==7.1.1, frozenlist@==1.4.1, googleapis-common-protos@==1.65.0, grpcio@==1.67.0, h11@==0.14.0, idna@==3.10, jsonschema@==4.23.0, jsonschema-specifications@==2024.10.1, Mako@==1.3.5, MarkupSafe@==3.0.2, mccabe@==0.7.0, msgpack@==1.1.0, multidict@==6.1.0, mypy@==1.12.0, mypy-extensions@==1.0.0, outcome@==1.3.0.post0, packaging@==24.1, passlib@==1.7.4, propcache@==0.2.0, protobuf@==4.25.5, pyasn1@==0.6.1, pycodestyle@==2.12.1, pycparser@==2.22, pycryptodome@==3.21.0, pydantic@==2.8.2, pydantic_core@==2.20.1, pydantic-settings@==2.6.0, pyflakes@==3.2.0, PyMySQL@==1.1.1, PySocks@==1.7.1, python-dateutil@==2.9.0.post0, python-dotenv@==1.0.1, python-jose@==3.3.0, python-multipart@==0.0.12, referencing@==0.35.1, requests@==2.32.3, rpds-py@==0.20.0, rsa@==4.9, ruff@==0.7.0, selenium@==4.25.0, six@==1.16.0, sniffio@==1.3.1, sortedcontainers@==2.4.0, soupsieve@==2.6, SQLAlchemy@==2.0.36, starlette@==0.40.0, trio@==0.27.0, trio-websocket@==0.11.1, types-pyasn1@==0.6.0.20240913, types-python-jose@==3.3.4.20240106, types-requests@==2.32.0.20241016, typing_extensions@==4.12.2, uagents@==0.16.2, urllib3@==2.2.3, uvicorn@==0.30.6, websocket-client@==1.8.0, websockets@==10.4, Werkzeug@==3.0.4, wsproto@==1.2.0, yarl@==1.15.5
- frontend/package.json: @types/node@^20, @types/react@^18, @types/react-dom@^18, @types/three@^0.169.0, 3d-force-graph@^1.73.4, axios@^1.7.7, dynamic@^4.2.2, eslint@^8, eslint-config-next@14.2.15, next@14.2.15, postcss@^8, react@^18, react-dom@^18, react-icons@^5.3.0, tailwindcss@^3.4.1, three@^0.169.0, typescript@^5

### Recent commits (newest first)

- 📚 Update README.md
- 📚 Update README.md
- Merge branch 'main' of personal-github:ashleyzhang01/calhacks
- frontend wip
- Merge branch 'main' of https://github.com/ashleyzhang01/calhacks
- close button for search results
- Merge branch 'main' of https://github.com/ashleyzhang01/calhacks
- 👻 hallucination checking with hyperbolic
- 📚 Update README.md
- add message button
- Merge branch 'main' of https://github.com/ashleyzhang01/calhacks
- search results ui
- Merge branch 'main' of personal-github:ashleyzhang01/calhacks
- update search
- search results changed
- a
- searchbox
- Merge branch 'main' of https://github.com/ashleyzhang01/calhacks
- 🗑️ add db to gitignoer
- Merge branch 'main' of https://github.com/ashleyzhang01/calhacks

## Key source files (fetched from GitHub, selected and truncated for size)

### backend/pyproject.toml

```
[tool.ruff]
exclude = [
    ".git",
    "__pycache__",
    "venv",
    ".venv",
]
per-file-ignores = { "__init__.py" = ["F401"] }
[tool.poetry]
name = "backend"
version = "0.1.0"
description = ""
authors = ["“kellyhongsn” <kellyhongsn@berkeley.edu>"]
readme = "README.md"

[tool.poetry.dependencies]
python = ">=3.11,<3.13"
uagents = "^0.16.2"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

```

### frontend/package.json

```
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "3d-force-graph": "^1.73.4",
    "axios": "^1.7.7",
    "dynamic": "^4.2.2",
    "next": "14.2.15",
    "react": "^18",
    "react-dom": "^18",
    "react-icons": "^5.3.0",
    "three": "^0.169.0"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "@types/three": "^0.169.0",
    "eslint": "^8",
    "eslint-config-next": "14.2.15",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

```

### backend/requirements.txt

```
aiohappyeyeballs==2.4.3
aiohttp==3.10.10
aiomysql==0.2.0
aiosignal==1.3.1
alembic==1.13.3
annotated-types==0.7.0
anyio==4.6.2.post1
apispec==6.6.1
attrs==24.2.0
bcrypt==4.0.1
beautifulsoup4==4.12.3
bech32==1.2.0
bs4==0.0.2
certifi==2024.8.30
cffi==1.17.1
charset-normalizer==3.4.0
click==8.1.7
cosmpy==0.9.2
cryptography==43.0.3
dnspython==2.7.0
ecdsa==0.19.0
email_validator==2.2.0
fastapi==0.115.2
flake8==7.1.1
frozenlist==1.4.1
googleapis-common-protos==1.65.0
grpcio==1.67.0
h11==0.14.0
idna==3.10
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
Mako==1.3.5
MarkupSafe==3.0.2
mccabe==0.7.0
msgpack==1.1.0
multidict==6.1.0
mypy==1.12.0
mypy-extensions==1.0.0
outcome==1.3.0.post0
packaging==24.1
passlib==1.7.4
propcache==0.2.0
protobuf==4.25.5
pyasn1==0.6.1
pycodestyle==2.12.1
pycparser==2.22
pycryptodome==3.21.0
pydantic==2.8.2
pydantic-settings==2.6.0
pydantic_core==2.20.1
pyflakes==3.2.0
PyMySQL==1.1.1
PySocks==1.7.1
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
python-jose==3.3.0
python-multipart==0.0.12
referencing==0.35.1
requests==2.32.3
rpds-py==0.20.0
rsa==4.9
ruff==0.7.0
selenium==4.25.0
six==1.16.0
sniffio==1.3.1
sortedcontainers==2.4.0
soupsieve==2.6
SQLAlchemy==2.0.36
starlette==0.40.0
trio==0.27.0
trio-websocket==0.11.1
types-pyasn1==0.6.0.20240913
types-python-jose==3.3.4.20240106
types-requests==2.32.0.20241016
typing_extensions==4.12.2
uagents==0.16.2
urllib3==2.2.3
uvicorn==0.30.6
websocket-client==1.8.0
websockets==10.4
Werkzeug==3.0.4
wsproto==1.2.0
yarl==1.15.5

```

### backend/main.py

```python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.routes import auth, github, linkedin, network
from app.db.database import create_tables
from app.core.config import settings
from app.api.routes.search import router as search_router

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(auth.router, prefix=settings.API_V1_STR)
app.include_router(github.router, prefix=settings.API_V1_STR)
app.include_router(linkedin.router, prefix=settings.API_V1_STR)
app.include_router(network.router, prefix=settings.API_V1_STR)
app.include_router(search_router)

@app.on_event("startup")
async def startup_event():
    create_tables()


@app.get("/")
def read_root():
    return {"Hello": "World"}

```

### frontend/src/app/layout.tsx

```typescript
import './globals.css'
import { Inter } from 'next/font/google'
import Navbar from '@/components/Navbar'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'Your App',
  description: 'Your app description',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Navbar />
        {children}
      </body>
    </html>
  )
}
```

### frontend/src/app/page.tsx

```typescript
"use client";
import { useState, useEffect } from 'react';
import SearchBox from '@/components/SearchBox';
import dynamic from 'next/dynamic';

const NetworkGraph = dynamic(() => import('../components/NetworkGraph'), {
  ssr: false,
  loading: () => <p>Loading...</p>
});

const PublicNetworkGraph = dynamic(() => import('../components/PublicNetworkGraph'), {
  ssr: false,
  loading: () => <p>Loading public network...</p>
});

export default function Home() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  useEffect(() => {
    const token = localStorage.getItem('token');
    setIsLoggedIn(!!token);
  }, []);

  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      {isLoggedIn ? (
        <div style={{ position: 'relative', width: '100%', height: '100%' }}>
          {/* Overlayed SearchBox */}
          <div style={{ 
            position: 'absolute', 
            top: '70px', 
            left: '50%', 
            transform: 'translateX(-50%)', 
            zIndex: 10 
          }}>
            <SearchBox />
          </div>

          {/* NetworkGraph */}
          <NetworkGraph />
        </div>
      ) : (
        <PublicNetworkGraph />
      )}
    </div>
  );
}

```

### frontend/src/app/signup/page.tsx

```typescript
'use client'

import { useState } from 'react'
import axios from 'axios'
import { useRouter } from 'next/navigation'
import Link from 'next/link'

export default function Signup() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const router = useRouter()

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    try {
      await axios.post('http://127.0.0.1:8000/api/auth/signup', {
        email,
        password,
      })
      router.push('/login')
    } catch (error) {
      console.error('Signup error:', error)
    }
  }

  return (
    <div className="flex justify-center items-center h-screen bg-black-900">
      <form onSubmit={handleSubmit} className="bg-gray-800 bg-opacity-20 shadow-md rounded px-8 pt-6 pb-8 mb-4 w-96">
        <h2 className="text-2xl font-bold mb-6 text-center text-white">Sign Up</h2>
        <div className="mb-4">
          <label className="block text-gray-300 text-sm font-bold mb-2" htmlFor="email">
            Email
          </label>
          <input
            className="shadow appearance-none border rounded w-full py-2 px-3 text-white leading-tight focus:outline-none focus:shadow-outline bg-gray-700 bg-opacity-50"
            id="email"
            type="email"
            placeholder="Email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />
        </div>
        <div className="mb-6">
          <label className="block text-gray-300 text-sm font-bold mb-2" htmlFor="password">
            Password
          </label>
          <input
            className="shadow appearance-none border rounded w-full py-2 px-3 text-white mb-3 leading-tight focus:outline-none focus:shadow-outline bg-gray-700 bg-opacity-50"
            id="password"
            type="password"
            placeholder="******************"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
        </div>
        <div className="flex items-center justify-between">
          <button
            className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full"
            type="submit"
          >
            Sign Up
          </button>
        </div>
        <div className="text-center mt-4">
          <Link href="/login" className="text-blue-400 hover:text-blue-600">
            Already have an account? Log in
          </Link>
        </div>
      </form>
    </div>
  )
}
```

### frontend/src/app/login/page.tsx

```typescript
'use client'

import { useState } from 'react'
import axios from 'axios'
import { useRouter } from 'next/navigation'
import Link from 'next/link'

export default function Login() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const router = useRouter()

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    try {
      const response = await axios.post('http://127.0.0.1:8000/api/auth/token', 
        new URLSearchParams({
          'username': email,
          'password': password,
        }),
        {
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
          }
        }
      )
      localStorage.setItem('token', response.data.access_token)
      router.push('/')
    } catch (error) {
      console.error('Login error:', error)
    }
  }

  return (
    <div className="flex justify-center items-center h-screen bg-black-900">
      <form onSubmit={handleSubmit} className="bg-gray-800 bg-opacity-20 shadow-md rounded px-8 pt-6 pb-8 mb-4 w-96">
        <h2 className="text-2xl font-bold mb-6 text-center text-white">Log In</h2>
        <div className="mb-4">
          <label className="block text-gray-300 text-sm font-bold mb-2" htmlFor="email">
            Email
          </label>
          <input
            className="shadow appearance-none border rounded w-full py-2 px-3 text-white leading-tight focus:outline-none focus:shadow-outline bg-gray-700 bg-opacity-50"
            id="email"
            type="text"
            placeholder="Email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />
        </div>
        <div className="mb-6">
          <label className="block text-gray-300 text-sm font-bold mb-2" htmlFor="password">
            Password
          </label>
          <input
            className="shadow appearance-none border rounded w-full py-2 px-3 text-white mb-3 leading-tight focus:outline-none focus:shadow-outline bg-gray-700 bg-opacity-50"
            id="password"
            type="password"
            placeholder="******************"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
        </div>
        <div className="flex items-center justify-between">
          <button
            className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full"
            type="submit"
          >
            Sign In
          </button>
        </div>
        <div className="text-center mt-4">
          <Link href="/signup" className="text-blue-400 hover:text-blue-600">
            Don't have an account? Sign up
          </Link>
        </div>
      </form>
    </div>
  )
}
```

### frontend/src/app/settings/page.tsx

```typescript
'use client'

import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import axios from 'axios'
import { FaQuestionCircle } from 'react-icons/fa'

export default function Settings() {
  const [isLoading, setIsLoading] = useState(true)
  const [githubUsername, setGithubUsername] = useState('')
  const [githubToken, setGithubToken] = useState('')
  const [linkedinEmail, setLinkedinEmail] = useState('')
  const [linkedinPassword, setLinkedinPassword] = useState('')
  const [linkedinUserId, setLinkedinUserId] = useState('')
  const [message, setMessage] = useState('')
  const router = useRouter()

  useEffect(() => {
    const token = localStorage.getItem('token')
    console.log(token)
    if (!token) {
      router.push('/login')
    } else {
      setIsLoading(false)
    }
  }, [router])

  const handleGithubSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    setMessage('')
    try {
      const response = await axios.post('http://localhost:8000/api/github/create', {
        username: githubUsername,
        token: githubToken
      }, {
        headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
      })
      setMessage('GitHub user created and scraping started')
    } catch (error) {
      setMessage('Error creating GitHub user')
    }
  }

  const handleLinkedinSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    setMessage('')
    try {
      const response = await axios.post('http://localhost:8000/api/linkedin/create', {
        username: linkedinUserId,
        email: linkedinEmail,
        password: linkedinPassword
      }, {
        headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
      })
      setMessage('LinkedIn user created and scraping started')
    } catch (error) {
      setMessage('Error creating LinkedIn user')
    }
  }

  if (isLoading) {
    return <div className="flex justify-center items-center h-screen">Loading...</div>
  }

  return (
    <div className="container mx-auto mt-8 p-4">
      <h1 className="text-3xl font-bold mb-8 text-center text-white">Connect Your Accounts</h1>
      
      <div className="mb-12 bg-gray-800 bg-opacity-50 p-6 rounded-lg shadow-lg">
      <h2 className="text-2xl font-semibold mb-4 text-white">github</h2>
        <form onSubmit={handleGithubSubmit} className="space-y-4">
          <div>
            <label htmlFor="githubUsername" className="block mb-1 text-white">github username</label>
            <input
              type="text"
              id="githubUsername"
              value={githubUsername}
              onChange={(e) => setGithubUsername(e.target.value)}
              className="w-full p-2 rounded bg-gray-700 bg-opacity-50 text-white"
              required
            />
          </div>
          <div className="relative">
            <label htmlFor="githubToken" className="block mb-1 text-white">
              github token
              <span className="ml-2 inline-block">
                <FaQuestionCircle className="text-gray-400 hover:text-white cursor-pointer" title="Optional, encrypted" />
              </span>
            </label>
            <input
              type="password"
              id="githubToken"
              value={githubToken}
              onChange={(e) => setGithubToken(e.target.value)}
              className="w-full p-2 rounded bg-gray-700 bg-opacity-50 text-white"
            />
          </div>
          <button type="submit" className="bg-gray-700 opacity-30 text-white px-4 py-2 rounded hover:bg-gray-900">
            set github user
          </button>
        </form>
      </div>

      <div className="mb-12 bg-gray-800 bg-opacity-50 p-6 rounded-lg shadow-lg">
        <h2 className="text-2xl font-semibold mb-4 text-white">linkedin</h2>
        <form onSubmit={handleLinkedinSubmit} className="space-y-4">
          <div>
            <label htmlFor="linkedinEmail" className="block mb-1 text-white">linkedin email</label>
            <input
              type="email"
              id="linkedinEmail"
              value={linkedinEmail}
              onChange={(e) => setLinkedinEmail(e.target.value)}
              className="w-full p-2 rounded bg-gray-700 bg-opacity-50 text-white"
              required
            />
          </div>
          <div>
            <label htmlFor="linkedinUserId" className="block mb-1 text-white">linkedin user id</label>
            <input
              type="text"
              id="linkedinUserId"
              value={linkedinUserId}
              onChange={(e) => setLinkedinUserId(e.target.value)}
              className="w-full p-2 rounded bg-gray-700 bg-opacity-50 text-white"
              required
            />
          </div>
          <div className="relative">
            <label htmlFor="linkedinPassword" className="block mb-1 text-white">
              linkedin Password
              <span className="ml-2 inline-block">
                <FaQuestionCircle className="text-gray-400 hover:text-white cursor-pointer" title="Encrypted" />
              </span>
            </label>
            <input
              type="password"
              id="linkedinPassword"
              value={linkedinPassword}
              onChange={(e) => setLinkedinPassword(e.target.value)}
              className="w-full p-2 rounded bg-gray-700 bg-opacity-50 text-white"
            />
          </div>
          <button type="submit" className="bg-gray-700 opacity-30 text-white px-4 py-2 rounded hover:bg-gray-900">
            set linkedin user
          </button>
        </form>
      </div>

    </div>
  )
}
```

### backend/lint.sh

```shell
#!/bin/bash
flake8 . --fix
ruff check . --fix
mypy .
```

[51 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]