# Project export: Sphere (Orbis)

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: OpenAI Build Week
- Tagline: AI can lead a beginner to a solution, but it can't make them understand. Sphere can. It shows you, visibly, where your machine is broken and how to fix it, so you learn instead of just copy-pasting.
- Devpost: https://devpost.com/software/sphere-orbis
- GitHub: https://github.com/RyanZWhalen/Sphere
- Video: https://www.youtube.com/embed/4gqaMNSIqLQ?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Ryan Whalen (13 commits), Claude Opus 4.8 (1 commits)

## Devpost submission (written by the team)

### Inspiration

As a beginner programmer, I was constantly confused and discouraged by ModuleNotFoundError. I couldn't even start programming. I was already drowning in errors that had nothing to do with the code I was trying to write. The terminal told me a package was missing, but never why: which of the several Pythons on my machine my project was actually using, or where things had gone wrong. For beginner programmers, my story isn't just an example; it's literally a canon event. Sphere is the tool I wish I'd had: one that makes the invisible state of your machine visible, so the wall that stops so many people before they begin becomes something you can actually see and understand.

### What it does

Sphere shows you the real state of your machine's Python setup as a live, interactive graph: every interpreter, every environment, and every installed package. It figures out which interpreter a given folder actually resolves to, then checks whether that interpreter can satisfy the project's requirements, showing each dependency as satisfied, version-mismatched, or missing. Crucially, it doesn't ask you to trust it: click any claim and Sphere shows the exact command it ran against that interpreter and what came back: a citation, not an opinion. And it doesn't just diagnose; it fixes. Sphere turns a broken environment into a working one, creating an isolated environment where one is needed rather than polluting your global Python, and re-scans to prove the fix worked instead of assuming it.

### How we built it

Sphere is a Python backend and a React front-end. The backend is a truth engine: it discovers interpreters across every install method (PATH, pyenv, uv, Homebrew, conda), and reads each one's packages by running that interpreter in an isolated subprocess, so the data is the interpreter's own ground truth, never a guess. It diffs a project's declared requirements against that reality using correct version-specifier and package-name logic. The front-end renders this as a spherical graph with a per-claim evidence layer and a preview-then-execute fix loop. I built the core, the introspection engine and the fix loop, with Codex, and used GPT-5.6 to power the plain-English diagnosis layer that explains problems and proposes fixes for beginners.

### Challenges we ran into

The hardest problems were about trust, because Sphere's whole value is being provably correct. Reading each environment's true package state meant running isolated subprocesses so an active shell couldn't contaminate the results, and we caught Sphere itself polluting the global Python with its own dependencies, the exact anti-pattern it warns against, which we fixed by making it install fully isolated. Building the verification layer meant capturing the actual command executed rather than reconstructing a plausible one: a fake receipt would have been worse than no receipt. And a subtle but critical bug: a failed fix once rendered as green "success," which would have broken the entire trust thesis. We caught it by deliberately testing the failure path, not just the happy path.

### Accomplishments we're proud of

This is a beginning to a tool that never lies about your machine, something I'm not only incredibly passionate about, but also proud of. Every red edge and version number is something Sphere read directly from an interpreter and can prove with the exact command it ran. We're proud that the deterministic core works with zero API keys and zero network: the AI is an enhancement, not a crutch, and that Sphere practices what it preaches by never polluting the environments it inspects. Most of all, we built the full arc: not just diagnose, but prove, fix, and verify; a broken project going green, live, because a fresh scan confirmed it.

### What we learned

That the hard part of a developer tool often isn't the feature; instead, it's being correct, and being able to prove it. We learned how deep the machinery under a simple ModuleNotFoundError really goes: interpreter resolution, PATH, environment isolation, the difference between a package being installed and being installed where your code will actually look for it. And we learned that the right division of labor with AI is deterministic code where correctness matters and language models where explanation and judgment matter: facts first, narration second.

### What's next

Sphere's model is ecosystem-agnostic: Python is fully implemented, and the same graph extends naturally to other worlds: JavaScript and node_modules, then the "why doesn't this run on my machine" problem beyond a single language: CUDA toolchains, Docker images, and eventually Kubernetes and external vendors. The long-term vision (Orbis) is a single visible, verifiable map of everything a project needs to run, whoever or whatever is doing the fixing, human or agent. As AI does more to our systems, the layer that lets us see and trust what it changed only becomes more essential. ChatGPT Feedback Session 019f7162-3fd6-7b72-9fa3-c3a2f2bc87d4

## README (from the GitHub repository)

# Sphere

Sphere makes a machine's Python state visible: every interpreter, environment, and
installed distribution is rendered as a live repository-to-runtime dependency graph.
It shows what the current folder would run, which requirements are missing or at the
wrong version, the evidence behind every verdict, and a guarded local repair plan.

Sphere is a localhost application. Its scanner and deterministic diagnosis run on the
machine; topology data is not sent to a hosted service.

## Judge quick start

### Prerequisites

- macOS or Linux (Windows is not currently supported)
- Python 3.11 or newer, available as `python3`
- Git, Make, and a POSIX shell
- Internet access during initial setup so `pip` can download dependencies and the
  small demo packages
- A browser

On Debian/Ubuntu, install the OS package that provides `venv` if `python3 -m venv`
is unavailable (commonly `python3-venv`). Confirm the selected Python first:

```bash
python3 --version
```

### Clone, prepare, and run

```bash
git clone https://github.com/RyanZWhalen/Sphere.git
cd Sphere
make setup
.sphere-venv/bin/sphere demo/sample-project --search-root demo
```

The two commands after `cd` are the complete demo path, in order. `make setup`:

1. Creates the dedicated `.sphere-venv` runtime.
2. Installs Sphere and its optional server dependencies only inside that runtime.
3. Runs `demo.sh` to create the reproducible three-state fixture.

Sphere then chooses a free `127.0.0.1` port and opens the graph. Nothing needs to be
built with Node: the production frontend is committed in `sphere/web/dist` and ships
inside the Python package.

If `python3` is not the desired Python, select an explicit 3.11+ executable:

```bash
make setup PYTHON=/absolute/path/to/python3.12
```

### Expected demo topology

The setup script creates, but does not commit, these local fixtures:

- `demo/sample-project`: no project environment; the folder resolves to a bare/shared
  interpreter that Sphere refuses to modify.
- `demo/.venv-broken`: `six==1.15.0` is installed, while the repository requires
  `six==1.16.0`; `idna` and `typing-extensions` are missing.
- `demo/.venv-good`: all three declared requirements are satisfied.

The first screen should therefore contain a red **Runs now** interpreter, a red
`.venv-broken` circle, and a green `.venv-good` circle marked **The fix**. Other bare
interpreters are collapsed into the expandable **Other interpreters on this machine**
group.

To reset only the sample data later:

```bash
make demo
```

## Demo walkthrough

1. Start on the red **Runs now** interpreter. The inspector shows that the folder's
   current Python is missing all three requirements, and Sphere recommends a local
   environment instead of polluting the shared interpreter.
2. Click `.venv-broken`. Its edge and node show one version mismatch and two missing
   packages.
3. Expand **Show evidence** under `six`. Sphere displays the exact interpreter path,
   rerunnable `python -I -c` metadata command, verbatim distribution list, the
   `reported 1.15.0 · requires ==1.16.0 · fails` proof, and the live scan timestamp.
4. Click **Preview fix**. The local repair agent shows the three exact `pip` commands
   before anything runs. Do not choose **Approve & run** during a read-only demo unless
   you intentionally want to modify `.venv-broken`.
5. Click `.venv-good` to show the fully satisfied alternative.

Interpreter and environment circles are draggable; the deterministic column/arc is
only their initial layout.

## Use Sphere on another repository

After `make setup`, point the isolated Sphere executable at any repository:

```bash
.sphere-venv/bin/sphere /absolute/path/to/repository \
  --search-root /absolute/path/to/search
```

The positional directory controls repository parsing and `python`/`python3` context
resolution. Each `--search-root` is recursively searched for `pyvenv.cfg`; the option
may be repeated:

```bash
.sphere-venv/bin/sphere ~/code/project \
  --search-root ~/code \
  --search-root ~/work
```

Useful server options:

```text
--port PORT       use a specific localhost port instead of a free one
--no-browser      start the server without opening a browser
```

For machine-readable output without the web server:

```bash
.sphere-venv/bin/python -m sphere.introspect \
  --indent 2 \
  --search-root /absolute/path/to/search \
  /absolute/path/to/repository
```

## What Sphere discovers

Interpreter sources include `PATH`, pyenv, conda, uv-managed Pythons, Homebrew, and
macOS framework/system locations. Environment discovery covers venv/virtualenv,
conda, uv project environments, common locations, and explicit search roots. Symlink
aliases are deduplicated through canonical real paths.

Repository declarations are read from:

- `requirements.txt`
- `[project].dependencies` in `pyproject.toml`
- `[tool.poetry.dependencies]` in `pyproject.toml`

Every target's installed packages are queried by running that target's own interpreter
with an isolated `importlib.metadata` subprocess. Sphere never imports discovered
packages into its own process.

## Evidence and repair safety

Every requirement row has a collapsed **Show evidence** receipt sourced from the same
authoritative repository-to-target edge as the visible verdict. Sphere never merges
package evidence from another interpreter or environment. If a query did not produce
enough evidence, the inspector says so rather than constructing a plausible result.

Scanning, topology, evidence, diagnosis, and fix preview are read-only. Writes require
an explicit **Approve & run** action. Sphere:

- refuses to modify its own `.sphere-venv`;
- refuses to install into shared system, framework, Homebrew, pyenv, or uv-managed
  interpreters;
- fingerprints previewed commands and rejects a stale plan;
- targets an environment through its exact interpreter path;
- allows removal only for a virtual environment directly inside the selected repository,
  after a separate preview and explicit approval;
- records command output and a per-step receipt; and
- re-scans afterward to verify the resulting graph verdict.

The local diagnosis and repair planning are deterministic. Sphere does not download a
language model, call GPT at runtime, or permit a model to generate arbitrary shell
commands.

## Test without rebuilding the frontend

The judge path itself is the fastest integration test:

```bash
make setup
.sphere-venv/bin/sphere demo/sample-project --search-root demo
```

Run the Python test suite with the isolated runtime:

```bash
.sphere-venv/bin/python -m unittest discover -s tests -v
```

Node is needed only when changing the React source. Frontend contributors can run:

```bash
npm --prefix frontend ci
npm --prefix frontend test
npm --prefix frontend run build
```

The final command refreshes the committed `sphere/web/dist` bundle.

## Troubleshooting

- **`python3 -m venv` fails:** install the platform's Python venv support or rerun
  setup with `PYTHON=/absolute/path/to/a/python3.11+`.
- **A demo environment is missing:** run `make demo`, then restart Sphere so it
  performs a fresh scan.
- **A project-local `.venv` should be removed:** select it in Sphere, choose
  **Preview environment removal**, then review and choose **Approve & remove**.
  Sphere will only offer this for a venv directly inside the selected repository.
- **The browser does not open:** add `--no-browser`, copy the printed localhost URL,
  and open it manually.
- **A port is occupied:** omit `--port` to let Sphere choose a free port.
- **A project environment is absent from the graph:** include its parent directory as
  a `--search-root`.
- **A discovery source is damaged:** check the topology's `warnings` array. Discovery
  sources fail independently so one unusual Python installation cannot abort a scan.

## Project structure

- `sphere/introspect.py`: stdlib-only machine and package topology scanner
- `sphere/requirements.py`: declaration parsing and package-version diffing
- `sph

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 25 recognized source files, 185 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- AI coding agent: Claude Code — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (32 of 32)

```
.DS_Store
.gitignore
demo.sh
demo/sample-project/app.py
demo/sample-project/requirements.txt
frontend/index.html
frontend/package.json
frontend/src/App.jsx
frontend/src/evidence.js
frontend/src/fixplan.js
frontend/src/main.jsx
frontend/src/styles.css
frontend/src/topology.js
frontend/test/evidence.test.js
frontend/test/fixplan.test.js
frontend/test/topology.test.js
frontend/vite.config.js
LICENSE
Makefile
pyproject.toml
README.md
sphere/__init__.py
sphere/apply.py
sphere/diagnose.py
sphere/fixplan.py
sphere/introspect.py
sphere/requirements.py
sphere/serve.py
tests/test_apply.py
tests/test_diagnose.py
tests/test_evidence.py
tests/test_fixplan.py
```

### Dependencies

- demo/sample-project/requirements.txt: idna@>=3.0, six@==1.16.0, typing-extensions@>=4.0
- frontend/package.json: @vitejs/plugin-react@^4.4.1, @xyflow/react@^12.10.1, react@^18.3.1, react-dom@^18.3.1, vite@^6.2.3
- pyproject.toml: fastapi@>=0.115, packaging@>=24, uvicorn[standard]@>=0.30

### Recent commits (newest first)

- feat(src): added delete environment feature
- docs: add judge setup and demo guide
- feat: ship reproducible demo evidence layer
- feat: wrapping up product
- Update Notes-1.tex
- style(frontend): spherical restyle — bezier edges, circular runtime tokens, arc
- test: added new bug fix in case of broken env during demo
- feat(src): modified sphere front-end and verified loop logic
- feat(frontend): built react + vite front-end mapping of dependencies
- feat(src): added new requirements.py file to parse requirements
- feat(demo): added demo script and environments for testing requirement parsing functionality
- feat(src): added introspect.py basic func. and init.py files
- Initial commit

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

### pyproject.toml

```
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[project]
name = "sphere"
version = "0.1.0"
description = "Machine-level Python topology introspection"
requires-python = ">=3.11"
dependencies = [
    "packaging>=24",
]

[project.optional-dependencies]
serve = [
    "fastapi>=0.115",
    "uvicorn[standard]>=0.30",
]

[project.scripts]
sphere = "sphere.serve:main"

[tool.setuptools]
packages = ["sphere"]

[tool.setuptools.package-data]
sphere = ["web/dist/**"]

```

### frontend/package.json

```
{
  "name": "sphere-frontend",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "test": "node --test"
  },
  "dependencies": {
    "@xyflow/react": "^12.10.1",
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^4.4.1",
    "vite": "^6.2.3"
  }
}

```

### demo/sample-project/requirements.txt

```
six==1.16.0
idna>=3.0
typing-extensions>=4.0

```

### demo/sample-project/app.py

```python
import six
import idna
print("sample project running on six", six.__version__)

```

### frontend/src/main.jsx

```javascript
import React from 'react';
import { createRoot } from 'react-dom/client';
import '@xyflow/react/dist/style.css';
import './styles.css';
import { App } from './App.jsx';

createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
);

```

### frontend/src/App.jsx

```javascript
import { useEffect, useMemo, useState } from 'react';
import {
  Background,
  Controls,
  Handle,
  Position,
  ReactFlow,
  ReactFlowProvider,
  useNodesState,
} from '@xyflow/react';
import { indexRepositoryRequiresEdges } from './topology.js';
import { commandLine, planNarrative, stepState } from './fixplan.js';
import { freshnessText, proofSentence, shellCommand } from './evidence.js';

const STATUS = {
  satisfied: { label: 'Satisfied', color: '#38c987' },
  'version-mismatch': { label: 'Version mismatch', color: '#f2b84b' },
  missing: { label: 'Missing', color: '#f06f70' },
  neutral: { label: 'Unknown', color: '#647185' },
};

const SEVERITY = { satisfied: 0, 'version-mismatch': 1, missing: 2, neutral: -1 };

// Layout constants for the candidate-runtime column and its outward arc.
const RUNTIME_COLUMN = 472;
const BASE_COLUMN = 796;
const RUNTIME_ROW = 190;
const RUNTIME_ARC = 84;

function basename(path = '') {
  return path.split('/').filter(Boolean).pop() || path;
}

function dirname(path = '') {
  const parts = path.split('/').filter(Boolean);
  parts.pop();
  return path.startsWith('/') ? `/${parts.join('/')}` : parts.join('/');
}

function shortPath(path = '') {
  const parts = path.split('/').filter(Boolean);
  return parts.length > 3 ? `…/${parts.slice(-3).join('/')}` : path;
}

function worstStatus(diff = []) {
  return diff.reduce(
    (worst, item) => (SEVERITY[item.status] > SEVERITY[worst] ? item.status : worst),
    'neutral',
  );
}

function displayInterpreter(node) {
  if (!node) return 'an unknown interpreter';
  return `${node.implementation || 'Python'} ${node.version || ''}`.trim();
}

function RuntimeNode({ data }) {
  const accent = data.status && data.status !== 'neutral' ? STATUS[data.status].color : null;
  const classes = ['runtime-card', `runtime-card--${data.kind}`, data.resolved ? 'is-resolved' : '', data.pending ? 'is-pending' : ''];
  return (
    <div className={classes.join(' ')} style={accent ? { '--accent': accent } : undefined}>
      <Handle type="target" position={Position.Left} className="flow-handle" />
      <div className="card-kicker">{data.kicker}</div>
      <div className="card-title">{data.title}</div>
      <div className="card-version">{data.version}</div>
      {data.detail && <div className="card-detail">{data.detail}</div>}
      <div className="card-tags">
        {data.resolved && <span className="tag tag--current">Runs now</span>}
        {data.fix && <span className="tag tag--fix">The fix</span>}
        {data.pending && <span className="tag tag--pending">Fix preview</span>}
        {data.status && data.status !== 'neutral' && <span className="tag" style={{ color: accent }}>{STATUS[data.status].label}</span>}
      </div>
      <Handle type="source" position={Position.Right} className="flow-handle" />
    </div>
  );
}

function OtherInterpretersNode({ data }) {
  return (
    <button className="other-card" type="button" onClick={(event) => { event.stopPropagation(); data.onToggle(); }}>
      <span>Other interpreters on this machine ({data.items.length})</span>
      <span className="other-card__chevron">{data.expanded ? '−' : '+'}</span>
      {data.expanded && (
        <span className="other-card__list">
          {data.items.map((item) => `${basename(item.path)} · ${item.version || 'unknown'}`).join('\n')}
        </span>
      )}
    </button>
  );
}

const NODE_TYPES = { runtime: RuntimeNode, other: OtherInterpretersNode };

function makeModel(topology, expanded, toggleOther, planTargetId) {
  const groups = topology.nodes || {};
  const interpreters = groups.interpreters || [];
  const environments = groups.environments || [];
  const repositories = groups.repositories || [];
  const contexts = groups.contexts || [];
  const rawEdges = topology.edges || [];
  const byId = new Map([...interpreters, ...environments, ...repositories, ...contexts].map((node) => [node.id, node]));
  const repository = repositories[0];
  const context = contexts[0];
  const resolution = rawEdges.find((edge) => edge.type === 'resolves-to' && edge.from === context?.id);
  const resolvedId = resolution?.to;
  const basedOnIds = new Set(rawEdges.filter((edge) => edge.type === 'based-on').map((edge) => edge.to));
  const foregroundInterpreterIds = new Set([resolvedId, ...basedOnIds].filter(Boolean));
  const foregroundInterpreters = interpreters.filter((node) => foregroundInterpreterIds.has(node.id));
  const otherInterpreters = interpreters.filter((node) => !foregroundInterpreterIds.has(node.id));
  // A runtime can also be the target of resolves-to and based-on edges. Only
  // the repository-origin requires edge for this exact node owns inspector data.
  const requiresByTarget = indexRepositoryRequiresEdges(rawEdges, repository?.id);
  const requiresEdgeForTarget = (targetId) => {
    const edge = requiresByTarget.get(targetId);
    return edge?.to === targetId && edge?.from === repository?.id ? edge : null;
  };
  const diffFor = (id) => requiresEdgeForTarget(id)?.diff || [];

  const nodes = [];
  if (context) {
    nodes.push({
      id: context.id,
      type: 'runtime',
      position: { x: 48, y: 58 },
      data: { kind: 'context', kicker: 'Folder', title: basename(context.path), version: 'Current context', detail: shortPath(context.path) },
    });
  }
  if (repository) {
    nodes.push({
      id: repository.id,
      type: 'runtime',
      position: { x: 48, y: 242 },
      data: {
        kind: 'repository',
        kicker: 'Repository',
        title: basename(repository.path),
        version: `${repository.requirements?.length || 0} declared requirement${repository.requirements?.length === 1 ? '' : 's'}`,
        detail: shortPath(repository.path),
        status: worstStatus(repository.requirements?.map(() => ({ status: 'neutral' }))),
      },
    });
  }

  // Candidate runtimes bow outward from the repository along a soft arc: the row
  // level with the repository sits furthest right, the outer rows curve back in,
  // 
[truncated — 22781 more characters]
```

### demo.sh

```shell
#!/usr/bin/env bash
# demo.sh — builds a self-contained Python topology for Sphere to visualize.
#
# Creates one sample project and two environments so the graph always has
# something interesting to show, on any machine:
#   demo/.venv-good    : satisfies the project's requirements   (all edges green)
#   demo/.venv-broken  : six at the wrong version, idna/typing-extensions missing
# The sample project itself has NO venv, so its folder resolves to the global
# interpreter — the "I just have a folder and ran python and it broke" case.
#
# Run:
#   ./demo.sh
#   .sphere-venv/bin/sphere demo/sample-project --search-root demo
#
# Override the base interpreter with:  PYTHON=/path/to/python3 ./demo.sh

set -euo pipefail

# --- locate a base interpreter ---------------------------------------------
PY="${PYTHON:-python3}"
if ! command -v "$PY" >/dev/null 2>&1; then
  echo "error: python3 not found on PATH (set PYTHON=/path/to/python3 to override)" >&2
  exit 1
fi
echo "Base interpreter: $("$PY" -c 'import sys; print(sys.executable, ".".join(map(str, sys.version_info[:3])))')"

# --- clean any previous run ------------------------------------------------
DEMO_DIR="demo"
PROJECT_DIR="$DEMO_DIR/sample-project"
rm -rf "$DEMO_DIR/.venv-good" "$DEMO_DIR/.venv-broken"
mkdir -p "$PROJECT_DIR"

# --- the sample project's declared requirements ----------------------------
# Tiny, pure-Python, universal wheels: fast to install, no build step, no GPU.
cat > "$PROJECT_DIR/requirements.txt" <<'EOF'
six==1.16.0
idna>=3.0
typing-extensions>=4.0
EOF

cat > "$PROJECT_DIR/app.py" <<'EOF'
import six
import idna
print("sample project running on six", six.__version__)
EOF

# helper: create a venv and install packages into it, quietly
make_venv () {
  local path="$1"; shift
  "$PY" -m venv "$path"
  "$path/bin/python" -m pip install --quiet --upgrade pip >/dev/null
  if [ "$#" -gt 0 ]; then
    "$path/bin/python" -m pip install --quiet "$@" >/dev/null
  fi
}

echo "Creating demo/.venv-good   (satisfies requirements)..."
make_venv "$DEMO_DIR/.venv-good" "six==1.16.0" "idna>=3.0" "typing-extensions>=4.0"

echo "Creating demo/.venv-broken (six wrong version, idna missing)..."
make_venv "$DEMO_DIR/.venv-broken" "six==1.15.0"

echo
echo "Done. Three-state topology is ready. Point Sphere at it:"
echo
echo "    .sphere-venv/bin/sphere $PROJECT_DIR --search-root $DEMO_DIR"
echo

```

### sphere/__init__.py

```python
"""Sphere's machine-introspection primitives."""


```

### frontend/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="theme-color" content="#0b1017" />
    <title>Sphere — Python topology</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

```

### frontend/vite.config.js

```javascript
import { resolve } from 'node:path';
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': 'http://127.0.0.1:8000',
    },
  },
  build: {
    outDir: resolve(import.meta.dirname, '../sphere/web/dist'),
    emptyOutDir: true,
    sourcemap: false,
  },
});

```

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