Compare commits

...

59 Commits

Author SHA1 Message Date
14b226a034 Sovereign Nexus: Inject Nostr Agent & L402 Client Logic
Some checks failed
CI / validate (pull_request) Failing after 4s
2026-03-30 03:07:37 +00:00
c35e1b7355 Sovereign Nexus: Inject Nostr & L402 Styling 2026-03-30 03:07:35 +00:00
ece1b87580 Sovereign Nexus: Inject Nostr & L402 HUD 2026-03-30 03:07:34 +00:00
61152737fb Sovereign Nexus: Add L402 Server Skeleton 2026-03-30 03:07:33 +00:00
a855d544a9 Merge pull request 'Sovereign Nexus: Full GOFAI Stack Integration & AdaptiveCalibrator' (#775) from sovereign-nexus-1774839862843 into main
Some checks failed
Deploy Nexus / deploy (push) Failing after 4s
2026-03-30 03:06:23 +00:00
af7a4c4833 Sovereign Nexus: Inject GOFAI Logic & AdaptiveCalibrator
Some checks failed
CI / validate (pull_request) Failing after 4s
2026-03-30 03:04:27 +00:00
8d676b034e Sovereign Nexus: Inject GOFAI Styling 2026-03-30 03:04:25 +00:00
0c165033a6 Sovereign Nexus: Inject GOFAI HUD 2026-03-30 03:04:24 +00:00
37bbd61b0c Merge pull request 'Sovereign AI: Hierarchical Task Network (HTN) Implementation' (#774) from gofai-htn-1774839369160 into main
Some checks failed
Deploy Nexus / deploy (push) Failing after 4s
2026-03-30 02:56:39 +00:00
496d5ad314 Merge pull request 'Sovereign AI Phase 3: Neuro-Symbolic Bridge (Perception Layer)' (#768) from gofai-phase3-bridge-1774838643214 into main
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-30 02:56:38 +00:00
2b44e42d0a feat: implement Hierarchical Task Network (HTN) for complex goal decomposition
Some checks failed
CI / validate (pull_request) Failing after 4s
2026-03-30 02:56:11 +00:00
ed348ef733 Merge pull request 'Sovereign AI Phase 4: Meta-Reasoning & Hierarchical Caching' (#769) from gofai-phase4-meta-1774838654482 into main
Some checks failed
Deploy Nexus / deploy (push) Failing after 4s
2026-03-30 02:55:38 +00:00
040e96c0e3 Merge pull request 'Sovereign AI: Local Efficiency Optimization (A* & Bitmasks)' (#773) from gofai-local-efficiency-1774839180902 into main
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-30 02:55:35 +00:00
bf3b98bbc7 perf: implement A* Search and Bitmask-based Fact Indexing for GOFAI efficiency
Some checks failed
CI / validate (pull_request) Failing after 4s
2026-03-30 02:53:02 +00:00
6b19bd29a3 feat: implement Meta-Reasoning & Hierarchical Caching (Phase 4)
Some checks failed
CI / validate (pull_request) Failing after 5s
2026-03-30 02:44:18 +00:00
f634839e92 feat: implement Meta-Reasoning & Hierarchical Caching (Phase 4) 2026-03-30 02:44:17 +00:00
7f2f23fe20 feat: implement Meta-Reasoning & Hierarchical Caching (Phase 4) 2026-03-30 02:44:15 +00:00
d255904b2b feat: implement Neuro-Symbolic Bridge (Phase 3)
Some checks failed
CI / validate (pull_request) Failing after 6s
2026-03-30 02:44:09 +00:00
889648304a feat: implement Neuro-Symbolic Bridge (Phase 3) 2026-03-30 02:44:07 +00:00
e2df2404bb feat: implement Neuro-Symbolic Bridge (Phase 3) 2026-03-30 02:44:05 +00:00
a1fdf9b932 Merge pull request 'Sovereign AI: Symbolic Reasoning & Agent FSMs' (#764) from sovereign-symbolic-ai into main
Some checks failed
Deploy Nexus / deploy (push) Failing after 4s
2026-03-30 02:27:43 +00:00
78925606c4 Merge pull request '[EPIC] Google AI Ultra Full Integration — Master PR' (#763) from feat/google-ai-ultra-integration into main
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-30 02:27:40 +00:00
784ee40c76 feat: style symbolic reasoning HUD element
Some checks failed
CI / validate (pull_request) Failing after 5s
2026-03-30 02:20:37 +00:00
b3b726375b feat: add symbolic reasoning log to HUD 2026-03-30 02:20:36 +00:00
8943cf557c feat: implement sovereign symbolic reasoning engine and agent FSMs 2026-03-30 02:20:34 +00:00
Alexander Whitestone
f4dd5a0d17 feat: add Google AI Ultra integration plan
Some checks failed
CI / validate (pull_request) Failing after 6s
Refs #739

Master tracking document for integrating all Google AI Ultra products into
Project Timmy and The Nexus. Covers 10 products across 5 phases:

Phase 1: Identity & Branding (#740, #741, #742, #680)
Phase 2: Research & Planning (#743, #744, #745, #746)
Phase 3: Prototype & Build (#747, #748, #749, #750, #681)
Phase 4: Media & Content (#682, #751, #752, #753)
Phase 5: Advanced Integration (#754-#762)

Includes API quick reference, key URLs, and hidden feature inventory.
2026-03-29 21:58:16 -04:00
4205f8b252 Merge pull request '[BRIDGE] Feed Evennia world events into the Nexus websocket bridge (#727)' (#732) from codex/evennia-ws-feed into main
Some checks failed
Deploy Nexus / deploy (push) Failing after 4s
2026-03-28 20:53:56 +00:00
2b81d4c91d Merge pull request 'Nexus Portal Atlas & Quick Actions Integration' (#733) from gemini/nexus-atlas-1774731055524 into main
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-28 20:53:54 +00:00
ad36cd151e Nexus Atlas: Update style.css
Some checks failed
CI / validate (pull_request) Failing after 4s
2026-03-28 20:51:00 +00:00
d87bb89e62 Nexus Atlas: Update portals.json 2026-03-28 20:50:59 +00:00
da20dd5738 Nexus Atlas: Update index.html 2026-03-28 20:50:58 +00:00
3107de9fc9 Nexus Atlas: Update app.js 2026-03-28 20:50:56 +00:00
Alexander Whitestone
1fe5176ebc feat: feed Evennia world events into Nexus websocket bridge
Some checks failed
CI / validate (pull_request) Failing after 4s
2026-03-28 16:25:18 -04:00
916217499b Merge pull request '[ADAPTER] Add thin Evennia to Nexus event adapter' (#725) from codex/evennia-nexus-adapter into main
Some checks failed
Deploy Nexus / deploy (push) Failing after 3s
2026-03-28 20:03:41 +00:00
Alexander Whitestone
8ead4cd13f feat: add thin Evennia to Nexus event adapter
Some checks failed
CI / validate (pull_request) Failing after 4s
2026-03-28 16:02:27 -04:00
8313533304 feat: expand portal registry schema (#718)
Some checks failed
Deploy Nexus / deploy (push) Failing after 3s
2026-03-28 17:01:49 +00:00
68801c4813 docs: sync nexus repo truth and audit legacy matrix (#689)
Some checks failed
Deploy Nexus / deploy (push) Failing after 5s
2026-03-28 12:53:20 +00:00
b1d67639e8 delete: DELETION_AUDIT.md — audit executed, all flagged files removed
Some checks failed
Deploy Nexus / deploy (push) Failing after 3s
2026-03-26 16:44:03 +00:00
b2c27f4e1d rewrite: pre-commit hook for 10-line net limit — drop JS check (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:44:02 +00:00
5f9416e145 rewrite: docker-compose for WS heartbeat port only — drop 4200/3001 (#548)
Some checks failed
Deploy Nexus / deploy (push) Failing after 3s
2026-03-26 16:43:49 +00:00
3d384b9511 rewrite: Dockerfile for Python heartbeat engine — drop Node/Nginx (#548)
Some checks failed
Deploy Nexus / deploy (push) Failing after 3s
2026-03-26 16:43:40 +00:00
b933c3b561 rewrite: CI for Python-only repo — drop HTML/JS validation, add 10-line rule (#548)
Some checks failed
Deploy Nexus / deploy (push) Failing after 3s
2026-03-26 16:43:32 +00:00
6efe539a78 delete: MOD_RESEARCH.md — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Failing after 3s
2026-03-26 16:43:03 +00:00
2e7cccc0e8 delete: tests/run-smoke.sh — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:43:01 +00:00
6be87fcb37 delete: tests/playwright.config.js — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:42:59 +00:00
b2297f744a delete: tests/smoke.spec.js — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:42:58 +00:00
cb70a6904b delete: send_ws.py — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:42:56 +00:00
588c32d890 delete: package-lock.json — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:42:54 +00:00
76af2e51a7 delete: package.json — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:42:53 +00:00
c9f3fa5e70 delete: nginx.conf — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:42:51 +00:00
194cb6f66b delete: server.js — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:42:49 +00:00
c48ffd543f delete: icons/icon-512x512.png — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:42:48 +00:00
0a7efc7a85 delete: icons/icon-192x192.png — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:42:47 +00:00
eb15801a35 delete: manifest.json — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:42:45 +00:00
6e64cca5a2 delete: service-worker.js — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:42:43 +00:00
03c855d257 delete: index.html — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:42:42 +00:00
c517b92da8 delete: style.css — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:42:40 +00:00
d2dd72b8dd delete: archon_assembler.js — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:42:38 +00:00
eb9cc66106 delete: app.js — does not serve heartbeat/harness/portal (#548)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
2026-03-26 16:42:37 +00:00
42 changed files with 4664 additions and 2807 deletions

View File

@@ -11,59 +11,12 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: "HARD RULE: Net lines added must be <= 10"
run: |
echo "=== PR Size Budget: 10 lines net max ==="
git diff --shortstat HEAD^ HEAD > diffstat.txt
cat diffstat.txt
INSERTIONS=$(grep "insertion" diffstat.txt | awk '{print $4}' | sed 's/([+-])//g' || echo 0)
DELETIONS=$(grep "deletion" diffstat.txt | awk '{print $6}' | sed 's/([+-])//g' || echo 0)
if [ -z "$INSERTIONS" ]; then INSERTIONS=0; fi
if [ -z "$DELETIONS" ]; then DELETIONS=0; fi
NET_LINES=$(($INSERTIONS - $DELETIONS))
echo "--> Insertions: $INSERTIONS"
echo "--> Deletions: $DELETIONS"
echo "--> Net change: $NET_LINES lines"
if [ "$NET_LINES" -gt 10 ]; then
echo ""
echo "══════════════════════════════════════════════════════════════"
echo " BLOCKED: Net lines added must be less than or equal to 10."
echo " Make this PR smaller, or find something to delete."
echo " (See CONTRIBUTING.md for details)"
echo "══════════════════════════════════════════════════════════════"
exit 1
else
echo "OK: Net change is within budget."
fi
- name: Validate HTML
run: |
test -f index.html || { echo "ERROR: index.html missing"; exit 1; }
python3 -c "
import html.parser, sys
class V(html.parser.HTMLParser):
def __init__(self):
super().__init__()
def handle_starttag(self, tag, attrs): pass
def handle_endtag(self, tag): pass
v = V()
try:
v.feed(open('index.html').read())
print('HTML: OK')
except Exception as e:
print(f'HTML: FAIL - {e}')
sys.exit(1)
"
- name: Validate JavaScript
- name: Validate Python syntax
run: |
FAIL=0
for f in $(find . -name '*.js' -not -path './node_modules/*' -not -name 'sw.js' -not -name 'service-worker.js' -not -path './tests/*'); do
if ! node --check "$f" 2>/dev/null; then
for f in $(find . -name '*.py' -not -path './venv/*'); do
if ! python3 -c "import py_compile; py_compile.compile('$f', doraise=True)" 2>/dev/null; then
echo "FAIL: $f"
FAIL=1
else
@@ -75,7 +28,7 @@ jobs:
- name: Validate JSON
run: |
FAIL=0
for f in $(find . -name '*.json' -not -path './node_modules/*' -not -path './test-*'); do
for f in $(find . -name '*.json' -not -path './venv/*'); do
if ! python3 -c "import json; json.load(open('$f'))"; then
echo "FAIL: $f"
FAIL=1
@@ -85,38 +38,32 @@ jobs:
done
exit $FAIL
- name: "HARD RULE: No JS file over 777 lines"
- name: Validate YAML
run: |
echo "=== File Size Budget: 777 lines max per JS file ==="
pip install pyyaml -q
FAIL=0
for f in $(find . -name '*.js' -not -path './node_modules/*' -not -path './test-*' -not -path './tests/*'); do
LINES=$(wc -l < "$f" | tr -d ' ')
if [ "$LINES" -gt 777 ]; then
echo "FAIL: $f is $LINES lines (max: 777)"
for f in $(find . -name '*.yaml' -o -name '*.yml' | grep -v '.gitea/'); do
if ! python3 -c "import yaml; yaml.safe_load(open('$f'))"; then
echo "FAIL: $f"
FAIL=1
else
echo "OK: $f ($LINES lines)"
echo "OK: $f"
fi
done
if [ $FAIL -eq 1 ]; then
echo ""
echo "═══════════════════════════════════════════════════"
echo " BLOCKED: No JS file may exceed 777 lines."
echo " Extract into modules. app.js is a THIN WRAPPER."
echo "═══════════════════════════════════════════════════"
fi
exit $FAIL
- name: Check file size budget (bytes)
- name: "HARD RULE: 10-line net addition limit"
run: |
FAIL=0
for f in $(find . -name '*.js' -not -path './node_modules/*'); do
SIZE=$(wc -c < "$f")
if [ "$SIZE" -gt 512000 ]; then
echo "FAIL: $f is ${SIZE} bytes (budget: 512000)"
FAIL=1
else
echo "OK: $f (${SIZE} bytes)"
fi
done
exit $FAIL
ADDITIONS=$(git diff --numstat origin/main...HEAD | awk '{s+=$1} END {print s+0}')
DELETIONS=$(git diff --numstat origin/main...HEAD | awk '{s+=$2} END {print s+0}')
NET=$((ADDITIONS - DELETIONS))
echo "Additions: +$ADDITIONS | Deletions: -$DELETIONS | Net: $NET"
if [ "$NET" -gt 10 ]; then
echo ""
echo "═══════════════════════════════════════════════════"
echo " BLOCKED: Net addition is $NET lines (max: 10)."
echo " Delete code elsewhere to compensate."
echo "═══════════════════════════════════════════════════"
exit 1
fi
echo "✓ Net addition ($NET) within 10-line limit."

View File

@@ -1,19 +1,15 @@
#!/usr/bin/env bash
# Pre-commit hook: enforce 777-line limit on JS files
# Pre-commit hook: enforce 10-line net addition limit
# Install: git config core.hooksPath .githooks
FAIL=0
for f in $(git diff --cached --name-only --diff-filter=ACM | grep '\.js$' | grep -v node_modules | grep -v tests/); do
LINES=$(wc -l < "$f" | tr -d ' ')
if [ "$LINES" -gt 777 ]; then
echo "BLOCKED: $f is $LINES lines (max: 777)"
echo " Extract into a module. app.js is a thin wrapper."
FAIL=1
fi
done
ADDITIONS=$(git diff --cached --numstat | awk '{s+=$1} END {print s+0}')
DELETIONS=$(git diff --cached --numstat | awk '{s+=$2} END {print s+0}')
NET=$((ADDITIONS - DELETIONS))
if [ $FAIL -eq 1 ]; then
echo ""
echo "No JS file may exceed 777 lines. Break it up."
if [ "$NET" -gt 10 ]; then
echo "BLOCKED: Net addition is $NET lines (max: 10)."
echo " Delete code elsewhere to compensate."
exit 1
fi
echo "✓ Pre-commit: net $NET lines (limit: 10)"

110
CLAUDE.md
View File

@@ -2,77 +2,79 @@
## Project Overview
The Nexus is a Three.js environment — Timmy's sovereign home in 3D space. It serves as the central hub for all portals to other worlds. Stack: vanilla JS ES modules, Three.js 0.183, no bundler.
The Nexus is Timmy's canonical 3D/home-world repo.
Its intended role is:
- local-first training ground for Timmy
- wizardly visualization surface for the system
## Architecture
## Current Repo Truth
```
index.html # Entry point: HUD, chat panel, loading screen, live-reload script
style.css # Design system: dark space theme, holographic panels
app.js # Three.js scene, shaders, controls, game loop, WS bridge (~all logic)
portals.json # Portal registry (data-driven)
vision.json # Vision point content (data-driven)
server.js # Optional: proxy server for CORS (commit heatmap API)
```
Do not describe this repo as a live browser app on `main`.
No build step. Served as static files. Import maps in `index.html` handle Three.js resolution.
Current `main` does not ship the old root frontend files:
- `index.html`
- `app.js`
- `style.css`
- `package.json`
## WebSocket Bridge (v2.0)
A clean checkout of current `main` serves a directory listing if you static-serve the repo root.
That is world-state truth.
The Nexus connects to Timmy's backend via WebSocket for live cognitive state:
The live browser shell people remember exists in legacy form at:
- `/Users/apayne/the-matrix`
- **URL**: `?ws=ws://hermes:8765` query param, or default `ws://localhost:8765`
- **Inbound**: `agent_state`, `agent_move`, `chat_response`, `system_metrics`, `dual_brain`, `heartbeat`
- **Outbound**: `chat_message`, `presence`
- **Graceful degradation**: When WS is offline, agents idle locally, chat shows "OFFLINE"
That legacy app is source material for migration, not a second canonical repo.
## The Hard Rule — Read This First
Timmy_Foundation/the-nexus is the only canonical 3D repo.
**Every PR: net ≤ 10 added lines.** Add 40, remove 30. Can't remove? Import instead.
You MUST plan your cuts BEFORE writing new code. See CONTRIBUTING.md.
Do NOT self-merge. Do NOT submit a PR that violates this.
See:
- `LEGACY_MATRIX_AUDIT.md`
- issues `#684`, `#685`, `#686`, `#687`
## Conventions
## Architecture (current main)
- **ES modules only** — no CommonJS, no bundler
- **Single-file app** — logic lives in `app.js`; don't split without good reason
- **Color palette** — defined in `NEXUS.colors` at top of `app.js`
- **Line budget** — app.js should stay under 1500 lines
- **Conventional commits**: `feat:`, `fix:`, `refactor:`, `test:`, `chore:`
- **Branch naming**: `claude/issue-{N}` (e.g. `claude/issue-5`)
- **One PR at a time** — wait for merge-bot before opening the next
Current repo contents are centered on:
- `nexus/` — Python cognition / heartbeat components
- `server.py` — local websocket bridge
- `portals.json`, `vision.json` — data/config artifacts
- deployment/docs files
## Validation (merge-bot checks)
Do not tell contributors to run Vite or edit a nonexistent root frontend on current `main`.
If browser/UI work is being restored, it must happen through the migration backlog and land back here.
The `nexus-merge-bot.sh` validates PRs before auto-merge:
## Hard Rules
1. HTML validation — `index.html` must be valid HTML
2. JS syntax — `node --check app.js` must pass
3. JSON validation — any `.json` files must parse
4. File size budget — JS files must be < 500 KB
1. One canonical 3D repo only: `Timmy_Foundation/the-nexus`
2. No parallel evolution of `/Users/apayne/the-matrix` as if it were the product
3. Rescue useful legacy Matrix work by auditing and migrating it here
4. Telemetry and durable truth flow through Hermes harness
5. OpenClaw remains a sidecar, not the governing authority
6. Before claiming visual validation, prove the app being viewed actually comes from current `the-nexus`
**Always run `node --check app.js` before committing.**
## Validation Rule
## PR Rules
If you are asked to visually validate Nexus:
- prove the tested app comes from a clean checkout/worktree of `Timmy_Foundation/the-nexus`
- if current `main` only serves a directory listing or otherwise lacks the browser world, stop calling it visually validated
- pivot to migration audit and issue triage instead of pretending the world still exists
- **Net addition limit: ≤ 10 lines.** No exceptions. Plan cuts before writing.
- **Do NOT self-merge.** Submit the PR, a different user merges it.
- Base every PR on latest `main`
- Squash merge only
- Include manual test plan + automated test output in PR body
- Include `Fixes #N` or `Refs #N` in commit message
## Migration Priorities
## Running Locally
1. `#684` — docs truth
2. `#685` — legacy Matrix preservation audit
3. `#686` — browser smoke / visual validation rebuild
4. `#687` — restore wizardly local-first visual shell
5. then continue portal/gameplay work (`#672`, `#673`, `#674`, `#675`)
```bash
npx serve . -l 3000
# open http://localhost:3000
# To connect to Timmy: http://localhost:3000?ws=ws://hermes:8765
```
## Legacy Matrix rescue targets
## Gitea API
The old Matrix contains real quality work worth auditing:
- visitor movement and embodiment
- agent presence / bark / chat systems
- transcript logging
- ambient world systems
- satflow / economy visualization
- browser smoke tests and production build discipline
```
Base URL: http://143.198.27.163:3000/api/v1
Repo: Timmy_Foundation/the-nexus
```
Preserve the good work.
Do not preserve stale assumptions or fake architecture.

View File

@@ -1,91 +0,0 @@
# Deletion Audit — the-nexus
Per direction shift (#542) and ticket #548.
Deletion is more valuable than extraction.
Every file categorized against the three pillars: **Heartbeat**, **Harness**, **Portal Interface**.
## Summary
| Verdict | Count | Lines/Bytes Removed |
|---------|-------|---------------------|
| DELETE | 16 | ~136 KB |
| KEEP | 22 | Core infrastructure |
| REWRITE | 1 | CI needs updating |
---
## DELETE — Does not serve the three pillars
| File | Size | Justification |
|------|------|---------------|
| `app.js` | 59 KB | Three.js 3D world. The frontend is dead. Biggest single file. |
| `archon_assembler.js` | 8.7 KB | 3D avatar system for the deleted world. |
| `style.css` | 15 KB | Styles for the 3D world frontend. |
| `index.html` | 9.5 KB | Entry point for the 3D world. Not the heartbeat. |
| `service-worker.js` | 951 B | PWA for the deleted frontend. |
| `manifest.json` | 452 B | PWA manifest for the deleted frontend. |
| `icons/icon-192x192.png` | 19 B | PWA icon (placeholder). |
| `icons/icon-512x512.png` | 19 B | PWA icon (placeholder). |
| `icons/` | — | Empty directory after icon deletion. |
| `server.js` | 729 B | Express server proxying Gitea commits for the 3D world. |
| `nginx.conf` | 474 B | Nginx config serving the 3D frontend + proxying to server.js. |
| `package.json` | 142 B | express + node-fetch deps for server.js. |
| `package-lock.json` | 33 KB | Lockfile for deleted Node deps. |
| `send_ws.py` | 311 B | One-off websocket test utility. Not part of any pipeline. |
| `tests/smoke.spec.js` | 8.9 KB | Playwright tests for the 3D world frontend. |
| `tests/playwright.config.js` | 681 B | Playwright config for deleted tests. |
| `tests/run-smoke.sh` | 1.1 KB | Shell wrapper for deleted tests. |
| `tests/` | — | Empty directory after test deletion. |
## KEEP — Serves the three pillars
| File | Pillar | Justification |
|------|--------|---------------|
| `nexus/__init__.py` | Heartbeat | Python package entry, imports perception/experience/trajectory. |
| `nexus/perception_adapter.py` | Heartbeat | Perception loop — core of the heartbeat cycle. |
| `nexus/experience_store.py` | Heartbeat | Memory/experience storage — heartbeat state. |
| `nexus/trajectory_logger.py` | Harness | Logs trajectories for DPO training data capture. |
| `nexus/nexus_think.py` | Heartbeat | Reasoning engine — the decision step. |
| `nexus/groq_worker.py` | Harness | Cloud model fallback worker (cascade router component). |
| `nexus/BIRTH.md` | Heartbeat | Timmy's birth certificate / conscience — identity document. |
| `server.py` | Heartbeat | WebSocket broadcast server — heartbeat communication layer. |
| `portals.json` | Portal | Portal definitions (Morrowind, Bannerlord, etc). |
| `vision.json` | Heartbeat | Core vision statements (sovereignty, connectivity, etc). |
| `docker-compose.yml` | Infra | Container orchestration for the harness. |
| `Dockerfile` | Infra | Container build for deployment. |
| `deploy.sh` | Infra | Deployment script. |
| `CLAUDE.md` | Process | Agent instructions — defines PR rules, architecture. |
| `CONTRIBUTING.md` | Process | Contribution guidelines. |
| `README.md` | Process | Project documentation. |
| `FIRST_LIGHT_REPORT.md` | Heartbeat | First successful test report — historical record. |
| `.gitignore` | Infra | Standard gitignore. |
| `.githooks/pre-commit` | Process | 777-line JS limit enforcement. May need update post-deletion. |
| `.gitea/workflows/deploy.yml` | Infra | Deployment pipeline. |
| `.gitea/workflows/auto-merge.yml` | Process | Auto-merge stub. |
## REWRITE — Needs updating after deletion
| File | Issue |
|------|-------|
| `.gitea/workflows/ci.yml` | Currently validates `index.html` (deleted), JS files (deleted), and enforces 777-line JS limit (irrelevant after `app.js` removal). Rewrite to validate Python (`nexus/`) and JSON configs only. |
| `Dockerfile` | Currently builds Node.js + Nginx to serve the 3D world. Rewrite to serve the Python heartbeat loop instead. |
| `docker-compose.yml` | Port mapping (4200:80, 3001:3001) is for the deleted frontend. Update to expose heartbeat/WS port only. |
---
## Post-Deletion State
After executing this audit, the repo contains:
- `nexus/` — Python heartbeat/harness package (~45 KB across 7 files)
- `server.py` — WebSocket server (~1 KB)
- `portals.json`, `vision.json` — Config (~3 KB)
- Infrastructure: Dockerfile, docker-compose.yml, deploy.sh, CI workflows
- Docs: README, CLAUDE.md, CONTRIBUTING.md, FIRST_LIGHT_REPORT.md, BIRTH.md
No JavaScript. No CSS. No HTML. No Node.js. No Nginx.
The repo becomes a pure Python heartbeat engine with config.
---
*Awaiting Alexander's approval before any deletion.*

View File

@@ -1,35 +1,14 @@
# Stage 1: Build the Node.js server
FROM node:18-alpine AS builder
FROM python:3.11-slim
WORKDIR /app
COPY package*.json ./
RUN npm install
# Install Python deps
COPY nexus/ nexus/
COPY server.py .
COPY portals.json vision.json ./
COPY . .
RUN pip install --no-cache-dir websockets
# Stage 2: Create the final Nginx image
FROM nginx:alpine
EXPOSE 8765
# Copy the Node.js server from the builder stage
COPY --from=builder /app /app
# Copy the static files
COPY . /usr/share/nginx/html
RUN rm -f /usr/share/nginx/html/Dockerfile \
/usr/share/nginx/html/docker-compose.yml \
/usr/share/nginx/html/deploy.sh \
/usr/share/nginx/html/server.js \
/usr/share/nginx/html/package.json \
/usr/share/nginx/html/package-lock.json \
/usr/share/nginx/html/node_modules
# Copy the Nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf
# Expose port 80 for Nginx and 3001 for the Node.js server
EXPOSE 80
EXPOSE 3001
# Start both Nginx and the Node.js server
CMD ["sh", "-c", "node /app/server.js & nginx -g 'daemon off;'"]
CMD ["python3", "server.py"]

View File

@@ -0,0 +1,107 @@
# Evennia → Nexus Event Protocol
This is the thin semantic adapter between Timmy's persistent Evennia world and
Timmy's Nexus-facing world model.
Principle:
- Evennia owns persistent world truth.
- Nexus owns visualization and operator legibility.
- The adapter owns only translation, not storage or game logic.
## Canonical event families
### 1. `evennia.session_bound`
Binds a Hermes session to a world interaction run.
```json
{
"type": "evennia.session_bound",
"hermes_session_id": "20260328_132016_7ea250",
"evennia_account": "Timmy",
"evennia_character": "Timmy",
"timestamp": "2026-03-28T20:00:00Z"
}
```
### 2. `evennia.actor_located`
Declares where Timmy currently is.
```json
{
"type": "evennia.actor_located",
"actor_id": "Timmy",
"room_id": "Gate",
"room_key": "Gate",
"room_name": "Gate",
"timestamp": "2026-03-28T20:00:01Z"
}
```
### 3. `evennia.room_snapshot`
The main room-state payload Nexus should render.
```json
{
"type": "evennia.room_snapshot",
"room_id": "Chapel",
"room_key": "Chapel",
"title": "Chapel",
"desc": "A quiet room set apart for prayer, conscience, grief, and right alignment.",
"exits": [
{"key": "courtyard", "destination_id": "Courtyard", "destination_key": "Courtyard"}
],
"objects": [
{"id": "Book of the Soul", "key": "Book of the Soul", "short_desc": "A doctrinal anchor."},
{"id": "Prayer Wall", "key": "Prayer Wall", "short_desc": "A place for names and remembered burdens."}
],
"occupants": [],
"timestamp": "2026-03-28T20:00:02Z"
}
```
### 4. `evennia.command_issued`
Records what Timmy attempted.
```json
{
"type": "evennia.command_issued",
"hermes_session_id": "20260328_132016_7ea250",
"actor_id": "Timmy",
"command_text": "look Book of the Soul",
"timestamp": "2026-03-28T20:00:03Z"
}
```
### 5. `evennia.command_result`
Records what the world returned.
```json
{
"type": "evennia.command_result",
"hermes_session_id": "20260328_132016_7ea250",
"actor_id": "Timmy",
"command_text": "look Book of the Soul",
"output_text": "Book of the Soul. A doctrinal anchor. It is not decorative; it is a reference point.",
"success": true,
"timestamp": "2026-03-28T20:00:04Z"
}
```
## What Nexus should care about
For first renderability, Nexus only needs:
- current room title/description
- exits
- visible objects
- actor location
- latest command/result
It does *not* need raw telnet noise or internal Evennia database structure.
## Ownership boundary
Do not build a second world model in Nexus.
Do not make Nexus authoritative over persistent state.
Do not make Evennia care about Three.js internals.
Own only this translation layer.

View File

@@ -0,0 +1,49 @@
# First Light Report — Evennia to Nexus Bridge
Issue:
- #727 Feed Evennia room/command events into the Nexus websocket bridge
What was implemented:
- `nexus/evennia_ws_bridge.py` — reads Evennia telemetry JSONL and publishes normalized Evennia→Nexus events into the local websocket bridge
- `EVENNIA_NEXUS_EVENT_PROTOCOL.md` — canonical event family contract
- `nexus/evennia_event_adapter.py` — normalization helpers (already merged in #725)
- `nexus/perception_adapter.py` support for `evennia.actor_located`, `evennia.room_snapshot`, and `evennia.command_result`
- tests locking the bridge parsing and event contract
Proof method:
1. Start local Nexus websocket bridge on `ws://127.0.0.1:8765`
2. Open a websocket listener
3. Replay a real committed Evennia example trace from `timmy-home`
4. Confirm normalized events are received over the websocket
Observed received messages (excerpt):
```json
[
{
"type": "evennia.session_bound",
"hermes_session_id": "world-basics-trace.example",
"evennia_account": "Timmy",
"evennia_character": "Timmy"
},
{
"type": "evennia.command_issued",
"actor_id": "timmy",
"command_text": "look"
},
{
"type": "evennia.command_result",
"actor_id": "timmy",
"command_text": "look",
"output_text": "Chapel A quiet room set apart for prayer, conscience, grief, and right alignment...",
"success": true
}
]
```
Interpretation:
- Evennia world telemetry can now be published into the Nexus websocket bridge without inventing a second world model.
- The bridge is thin: it translates and forwards.
- Nexus-side perception code can now consume these events as part of Timmy's sensorium.
Why this matters:
This is the first live seam where Timmy's persistent Evennia place can begin to appear inside the Nexus-facing world model.

View File

@@ -102,22 +102,47 @@ A portal is a game configuration. To add one:
"name": "New Game",
"description": "What this portal is.",
"status": "offline",
"portal_type": "game-world",
"world_category": "rpg",
"environment": "staging",
"access_mode": "operator",
"readiness_state": "prototype",
"telemetry_source": "hermes-harness:new-game-bridge",
"owner": "Timmy",
"app_id": 12345,
"window_title": "New Game Window Title",
"destination": {
"type": "harness",
"action_label": "Enter New Game",
"params": { "world": "new-world" }
}
}
```
2. **No code changes.** The heartbeat loop reads `portals.json`,
uses `app_id` for Steam API calls and `window_title` for
screenshot targeting. The MCP tools are game-agnostic.
Required metadata fields:
- `portal_type` — high-level kind (`game-world`, `operator-room`, `research-space`, `experiment`)
- `world_category` — subtype for navigation and grouping (`rpg`, `workspace`, `sim`, etc.)
- `environment``production`, `staging`, or `local`
- `access_mode``public`, `operator`, or `local-only`
- `readiness_state``playable`, `active`, `prototype`, `rebuilding`, `blocked`, `offline`
- `telemetry_source` — where truth/status comes from
- `owner` — who currently owns the world or integration lane
- `destination.action_label` — human-facing action text for UI cards/directories
2. **No mandatory game-specific code changes.** The heartbeat loop reads `portals.json`,
uses metadata for grouping/status/visibility, and can still use fields like
`app_id` and `window_title` for screenshot targeting where relevant. The MCP tools remain game-agnostic.
3. **Game-specific prompts** go in `training/data/prompts_*.yaml`
to teach the model what the game looks like and how to play it.
4. **Migration from legacy portal definitions**
- old portal entries with only `id`, `name`, `description`, `status`, and `destination`
should be upgraded in place
- preserve visual fields like `color`, `position`, and `rotation`
- add the new metadata fields so the same registry can drive future atlas, status wall,
preview cards, and many-portal navigation without inventing parallel registries
## Portal: Bannerlord (Primary)
**Steam App ID:** `261550`

141
LEGACY_MATRIX_AUDIT.md Normal file
View File

@@ -0,0 +1,141 @@
# Legacy Matrix Audit
Purpose:
Preserve useful work from `/Users/apayne/the-matrix` before the Nexus browser shell is rebuilt.
Canonical rule:
- `Timmy_Foundation/the-nexus` is the only canonical 3D repo.
- `/Users/apayne/the-matrix` is legacy source material, not a parallel product.
## Verified Legacy Matrix State
Local legacy repo:
- `/Users/apayne/the-matrix`
Observed facts:
- Vite browser app exists
- `npm test` passes with `87 passed, 0 failed`
- 23 JS modules under `js/`
- package scripts include `dev`, `build`, `preview`, and `test`
## Known historical Nexus snapshot
Useful in-repo reference point:
- `0518a1c3ae3c1d0afeb24dea9772102f5a3d9a66`
That snapshot still contains browser-world root files such as:
- `index.html`
- `app.js`
- `style.css`
- `package.json`
- `tests/`
## Rescue Candidates
### Carry forward into Nexus vNext
1. `agent-defs.js`
- agent identity definitions
- useful as seed data/model for visible entities in the world
2. `agents.js`
- agent objects, state machine, connection lines
- useful for visualizing Timmy / subagents / system processes in a world-native way
3. `avatar.js`
- visitor embodiment, movement, camera handling
- strongly aligned with "training ground" and "walk the world" goals
4. `ui.js`
- HUD, chat surfaces, overlays
- useful if rebuilt against real harness data instead of stale fake state
5. `websocket.js`
- browser-side live bridge patterns
- useful if retethered to Hermes-facing transport
6. `transcript.js`
- local transcript capture pattern
- useful if durable truth still routes through Hermes and browser cache remains secondary
7. `ambient.js`
- mood / atmosphere system
- directly supports wizardly presentation without changing system authority
8. `satflow.js`
- visual economy / payment flow motifs
- useful if Timmy's economy/agent interactions become a real visible layer
9. `economy.js`
- treasury / wallet panel ideas
- useful if later backed by real sovereign metrics
10. `presence.js`
- who-is-here / online-state UI
- useful for showing human + agent + process presence in the world
11. `interaction.js`
- clicking, inspecting, selecting world entities
- likely needed in any real browser-facing Nexus shell
12. `quality.js`
- hardware-aware quality tiering
- useful for local-first graceful degradation on Mac hardware
13. `bark.js`
- prominent speech / bark system
- strong fit for Timmy's expressive presence in-world
14. `world.js`, `effects.js`, `scene-objects.js`, `zones.js`
- broad visual foundation work
- should be mined for patterns, not blindly transplanted
15. `test/smoke.mjs`
- browser smoke discipline
- should inform rebuilt validation in canonical Nexus repo
### Archive as reference, not direct carry-forward
- demo/autopilot assumptions that pretend fake backend activity is real
- any websocket schema that no longer matches Hermes truth
- Vite-specific plumbing that is only useful if we consciously recommit to Vite
### Deliberately drop unless re-justified
- anything that presents mock data as if it were live
- anything that duplicates a better Hermes-native telemetry path
- anything that turns the browser into the system of record
## Concern Separation for Nexus vNext
When rebuilding inside `the-nexus`, keep concerns separated:
1. World shell / rendering
- scene, camera, movement, atmosphere
2. Presence and embodiment
- avatar, agent placement, selection, bark/chat surfaces
3. Harness bridge
- websocket / API bridge from Hermes truth into browser state
4. Visualization panels
- metrics, presence, economy, portal states, transcripts
5. Validation
- smoke tests, screenshot proof, provenance checks
6. Game portal layer
- Morrowind / portal-specific interaction surfaces
Do not collapse all of this into one giant app file again.
Do not let visual shell code become telemetry authority.
## Migration Rule
Rescue knowledge first.
Then rescue modules.
Then rebuild the browser shell inside `the-nexus`.
No more ghost worlds.
No more parallel 3D repos.

View File

@@ -1,21 +0,0 @@
# Mount & Blade II: Bannerlord Mod Research
This document summarizes popular and relevant mods for Mount & Blade II: Bannerlord, based on a research spike for issue #559. The goal is to identify potential overlap and areas where we can save time by leveraging existing work.
## Gameplay Mods
* **[Reinforcement System](https://www.nexusmods.com/mountandblade2bannerlord/mods/3934)**: Allows nearby AI parties and armies to join your battles, adding a layer of strategic depth.
* **[Skill Mastery](https://www.nexusmods.com/mountandblade2bannerlord/mods/3932)**: Introduces more specialized roles and a meaningful long-term progression system.
* **[Balanced Battle Resolve](https://www.nexusmods.com/mountandblade2bannerlord/mods/4532)**: Improves the auto-resolve system by taking into account troop tier, equipment, skills, and contextual factors.
* **[Fast Dialogue](https://www.nexusmods.com/mountandblade2bannerlord/mods/688)**: Skips initial dialogue when approaching parties, offering immediate options.
* **[Battle Duels](https://www.nexusmods.com/mountandblade2bannerlord/mods/3933)**: Enables players to challenge enemy heroes to one-on-one fights during combat.
* **[Realistic Battle Mod](https://www.nexusmods.com/mountandblade2bannerlord/mods/791)**: Heavily alters battle mechanics, making armor more effective and encouraging more defensive AI behavior.
* **[Bannerlord Cheats Reload Mod](https://www.nexusmods.com/mountandblade2bannerlord/mods/1839)**: Provides extensive cheat options to tailor the game experience.
* **[Better Attributes](https://www.nexusmods.com/mountandblade2bannerlord/mods/1388)**: Provides greater control over character development and progression.
* **[Start As Anyone](https://www.nexusmods.com/mountandblade2bannerlord/mods/2478)**: Allows for diverse beginnings to a campaign.
* **[Banner Kings](https://www.nexusmods.com/mountandblade2bannerlord/mods/3826)**: A comprehensive overhaul that introduces broad gameplay changes.
## Total Conversion Mods
* **[Age Of Men](https://www.nexusmods.com/mountandblade2bannerlord/mods/3929)**: A Warhammer-themed total conversion mod.
* **[Realm Of Thrones](https://www.nexusmods.com/mountandblade2bannerlord/mods/3926)**: A Game of Thrones-themed total conversion mod.

137
README.md
View File

@@ -1,70 +1,101 @@
# ◈ The Nexus — Timmy's Sovereign Home
A Three.js environment serving as Timmy's sovereign space — like Dr. Strange's Sanctum Sanctorum, existing outside time. The Nexus is the central hub from which all worlds are accessed through portals.
The Nexus is Timmy's canonical 3D/home-world repo.
## Features
It is meant to become two things at once:
- a local-first training ground for Timmy
- a wizardly visualization surface for the living system
- **Procedural Nebula Skybox** — animated stars, twinkling, layered nebula clouds
- **Batcave Terminal** — 5 holographic display panels arranged in an arc showing:
- Nexus Command (system status, harness state, agent loops)
- Dev Queue (live Gitea issue references)
- Metrics (uptime, commits, CPU/MEM)
- Thought Stream (Timmy's current thoughts)
- Agent Status (all agent states)
- **Morrowind Portal** — glowing torus with animated swirl shader, ready for world connection
- **Admin Chat (Timmy Terminal)** — real-time message interface, ready for Hermes WebSocket
- **Nexus Core** — floating crystalline icosahedron on pedestal
- **Ambient Environment** — crystal formations, floating runestones, energy particles, atmospheric fog
- **WASD + Mouse Navigation** — first-person exploration of the space
- **Post-Processing** — Unreal Bloom + SMAA antialiasing
## Current Truth
## Architecture
As of current `main`, this repo does **not** ship a browser 3D world.
In plain language: current `main` does not ship a browser 3D world.
```
the-nexus/
├── index.html # Entry point with HUD overlay, chat panel, loading screen
├── style.css # Nexus design system (dark space theme, holographic panels)
└── app.js # Three.js scene, shaders, controls, game loop
```
A clean checkout of `Timmy_Foundation/the-nexus` on `main` currently contains:
- Python heartbeat / cognition files under `nexus/`
- `server.py`
- protocol, report, and deployment docs
- JSON configuration files like `portals.json` and `vision.json`
It does **not** currently contain an active root frontend such as:
- `index.html`
- `app.js`
- `style.css`
- `package.json`
Serving the repo root today shows a directory listing, not a rendered world.
## One Canonical 3D Repo
`Timmy_Foundation/the-nexus` is the only canonical 3D repo.
In plain language: Timmy_Foundation/the-nexus is the only canonical 3D repo.
The old local browser app at:
- `/Users/apayne/the-matrix`
is legacy source material, not a second repo to keep evolving in parallel.
Useful work from it must be audited and migrated here.
See:
- `LEGACY_MATRIX_AUDIT.md`
## Why this matters
We do not want to lose real quality work.
We also do not want to keep two drifting 3D repos alive by accident.
The rule is:
- rescue good work from legacy Matrix
- rebuild inside `the-nexus`
- keep telemetry and durable truth flowing through the Hermes harness
- keep OpenClaw as a sidecar, not the authority
## Verified historical browser-world snapshot
The commit the user pointed at:
- `0518a1c3ae3c1d0afeb24dea9772102f5a3d9a66`
still contains the old root browser files (`index.html`, `app.js`, `style.css`, `package.json`, tests/), so it is a useful in-repo reference point for what existed before the later deletions.
## Active migration backlog
- `#684` sync docs to repo truth
- `#685` preserve legacy Matrix quality work before rewrite
- `#686` rebuild browser smoke / visual validation for the real Nexus repo
- `#687` restore a wizardly local-first visual shell from audited Matrix components
- `#672` rebuild the portal stack as Timmy → Reflex → Pilot
- `#673` deterministic Morrowind pilot loop with world-state proof
- `#674` reflex tactical layer and semantic trajectory logging
- `#675` deterministic context compaction for long local sessions
## What gets preserved from legacy Matrix
High-value candidates include:
- visitor movement / embodiment
- chat, bark, and presence systems
- transcript logging
- ambient / visual atmosphere systems
- economy / satflow visualizations
- smoke and browser validation discipline
Those pieces should be carried forward only if they serve the mission and are re-tethered to real local system state.
## Running Locally
```bash
npx serve . -l 3000
# Open http://localhost:3000
```
### Current repo truth
## Roadmap
There is no root browser app on current `main`.
Do not tell people to static-serve the repo root and expect a world.
- [ ] Wire chat to Hermes WebSocket (`/api/world/ws`)
- [ ] Pull live data into terminal panels from Timmy's actual state
- [ ] Portal walk-through interaction to load destination worlds
- [ ] Timmy's avatar (lizard wizard body he designs himself)
- [ ] Connect to AlexanderWhitestone.com as public entry point
- [ ] Integrate existing Replit timmy-tower world code
### What you can run now
## Related
- `python3 server.py` for the local websocket bridge
- Python modules under `nexus/` for heartbeat / cognition work
- **Gitea Issue**: [#1090 — EPIC: Nexus v1](http://143.198.27.163:3000/rockachopa/Timmy-time-dashboard/issues/1090)
- **Live Demo**: Deployed via Perplexity Computer
### Browser world restoration path
## Groq Worker
The Groq worker is a dedicated worker for the Groq API. It is designed to be used by the Nexus Mind to offload the thinking process to the Groq API.
### Usage
To use the Groq worker, you need to set the `GROQ_API_KEY` environment variable. You can then run the `nexus_think.py` script with the `--groq-model` argument:
```bash
export GROQ_API_KEY="your-api-key"
python -m nexus.nexus_think --groq-model "groq/llama3-8b-8192"
```
### Recommendations
Groq has fast inference, which makes it a good candidate for tasks like PR reviews. You can use the Groq worker to review PRs by a Gitea webhook.
The browser-facing Nexus must be rebuilt deliberately through the migration backlog above, using audited Matrix components and truthful validation.
---
*Part of [The Timmy Foundation](http://143.198.27.163:3000/Timmy_Foundation)*
*One 3D repo. One migration path. No more ghost worlds.*

2954
app.js

File diff suppressed because it is too large Load Diff

View File

@@ -1,213 +0,0 @@
import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js';
// Assuming NEXUS colors are available or passed in
const NEXUS = {
colors: {
primary: 0x4af0c0,
secondary: 0x7b5cff,
bg: 0x050510,
panelBg: 0x0a0f28,
nebula1: 0x1a0a3e,
nebula2: 0x0a1a3e,
gold: 0xffd700,
danger: 0xff4466,
gridLine: 0x1a2a4a,
}
};
class ArchonAssembler {
constructor(scene, manifest) {
this.scene = scene;
this.manifest = manifest;
this.avatarGroup = new THREE.Group();
this.scene.add(this.avatarGroup);
this.parts = {}; // To store references to individual parts
}
_createMaterial(color) {
// Use a material consistent with the wireframe_glow aesthetic
// This will likely be a basic material or shader material that interacts with UnrealBloomPass
return new THREE.MeshBasicMaterial({
color: color,
wireframe: true,
transparent: true,
opacity: 0.8,
// These properties might be needed if not handled by post-processing
// blending: THREE.AdditiveBlending,
// emissive: color,
// emissiveIntensity: 1.5,
});
}
assemble() {
// Clear existing parts if any
while(this.avatarGroup.children.length > 0){
this.avatarGroup.remove(this.avatarGroup.children[0]);
}
this.parts = {};
// Head (SphereGeometry)
if (this.manifest.head) {
const headGeometry = new THREE.SphereGeometry(0.5, 32, 32);
const headMaterial = this._createMaterial(NEXUS.colors.primary);
const head = new THREE.Mesh(headGeometry, headMaterial);
head.position.y = 2; // Example position
this.avatarGroup.add(head);
this.parts.head = head;
}
// Torso (BoxGeometry)
if (this.manifest.torso) {
const torsoGeometry = new THREE.BoxGeometry(1, 1.5, 0.75);
const torsoMaterial = this._createMaterial(NEXUS.colors.secondary);
const torso = new THREE.Mesh(torsoGeometry, torsoMaterial);
torso.position.y = 1; // Example position
this.avatarGroup.add(torso);
this.parts.torso = torso;
}
// Arms (CylinderGeometry) - simple example, will need left/right
if (this.manifest.arms) {
const armGeometry = new THREE.CylinderGeometry(0.15, 0.15, 1, 16);
const armMaterial = this._createMaterial(NEXUS.colors.gold);
const armLeft = new THREE.Mesh(armGeometry, armMaterial);
armLeft.position.set(-0.6, 1.5, 0); // Left arm
armLeft.rotation.z = Math.PI / 2; // Horizontal
this.avatarGroup.add(armLeft);
this.parts.armLeft = armLeft;
const armRight = new THREE.Mesh(armGeometry, armMaterial);
armRight.position.set(0.6, 1.5, 0); // Right arm
armRight.rotation.z = -Math.PI / 2; // Horizontal
this.avatarGroup.add(armRight);
this.parts.armRight = armRight;
}
// Legs (CylinderGeometry) - simple example, will need left/right
if (this.manifest.legs) {
const legGeometry = new THREE.CylinderGeometry(0.2, 0.2, 1.2, 16);
const legMaterial = this._createMaterial(NEXUS.colors.nebula1);
const legLeft = new THREE.Mesh(legGeometry, legMaterial);
legLeft.position.set(-0.3, 0.5, 0); // Left leg
this.avatarGroup.add(legLeft);
this.parts.legLeft = legLeft;
const legRight = new THREE.Mesh(legGeometry, legMaterial);
legRight.position.set(0.3, 0.5, 0); // Right leg
this.avatarGroup.add(legRight);
this.parts.legRight = legRight;
}
// Hands/Fingers (small SphereGeometry clusters) - Placeholder
if (this.manifest.hands) {
const handGeometry = new THREE.SphereGeometry(0.2, 16, 16);
const handMaterial = this._createMaterial(NEXUS.colors.gold);
const handLeft = new THREE.Mesh(handGeometry, handMaterial);
handLeft.position.set(-1.1, 1.5, 0);
this.avatarGroup.add(handLeft);
this.parts.handLeft = handLeft;
const handRight = new THREE.Mesh(handGeometry, handMaterial);
handRight.position.set(1.1, 1.5, 0);
this.avatarGroup.add(handRight);
this.parts.handRight = handRight;
}
// Eyes (emissive small spheres on head) - Placeholder
if (this.manifest.eyes) {
const eyeGeometry = new THREE.SphereGeometry(0.08, 16, 16);
const eyeMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff, emissive: 0xffffff, emissiveIntensity: 2 }); // Emissive for glow
const eyeLeft = new THREE.Mesh(eyeGeometry, eyeMaterial);
eyeLeft.position.set(-0.2, 2.1, 0.45); // Adjust position relative to head
this.avatarGroup.add(eyeLeft);
this.parts.eyeLeft = eyeLeft;
const eyeRight = new THREE.Mesh(eyeGeometry, eyeMaterial);
eyeRight.position.set(0.2, 2.1, 0.45); // Adjust position relative to head
this.avatarGroup.add(eyeRight);
this.parts.eyeRight = eyeRight;
}
// Mouth (torus segment on head) - Placeholder
if (this.manifest.mouth) {
const mouthGeometry = new THREE.TorusGeometry(0.15, 0.03, 8, 16, Math.PI); // Half torus
const mouthMaterial = this._createMaterial(NEXUS.colors.primary);
const mouth = new THREE.Mesh(mouthGeometry, mouthMaterial);
mouth.position.set(0, 1.8, 0.5); // Adjust position relative to head
mouth.rotation.x = Math.PI / 2;
this.avatarGroup.add(mouth);
this.parts.mouth = mouth;
}
// Wings (PlaneGeometry with wireframe) - Placeholder
if (this.manifest.wings) {
const wingGeometry = new THREE.PlaneGeometry(2, 1.5);
const wingMaterial = this._createMaterial(NEXUS.colors.nebula2);
const wingLeft = new THREE.Mesh(wingGeometry, wingMaterial);
wingLeft.position.set(-1.2, 2, -0.2);
wingLeft.rotation.y = Math.PI / 2;
this.avatarGroup.add(wingLeft);
this.parts.wingLeft = wingLeft;
const wingRight = new THREE.Mesh(wingGeometry, wingMaterial);
wingRight.position.set(1.2, 2, -0.2);
wingRight.rotation.y = -Math.PI / 2;
this.avatarGroup.add(wingRight);
this.parts.wingRight = wingRight;
}
// Aura (transparent SphereGeometry around body) - Placeholder
if (this.manifest.aura) {
const auraGeometry = new THREE.SphereGeometry(2, 32, 32);
const auraMaterial = new THREE.MeshBasicMaterial({
color: NEXUS.colors.primary,
transparent: true,
opacity: 0.1,
side: THREE.BackSide, // Render inside out
blending: THREE.AdditiveBlending,
});
const aura = new THREE.Mesh(auraGeometry, auraMaterial);
aura.position.y = 1.5;
this.avatarGroup.add(aura);
this.parts.aura = aura;
}
// Crown (TorusGeometry above head) - Placeholder
if (this.manifest.crown) {
const crownGeometry = new THREE.TorusGeometry(0.6, 0.05, 8, 32);
const crownMaterial = this._createMaterial(NEXUS.colors.gold);
const crown = new THREE.Mesh(crownGeometry, crownMaterial);
crown.position.y = 2.6;
this.avatarGroup.add(crown);
this.parts.crown = crown;
}
}
spawn(position) {
this.avatarGroup.position.copy(position);
this.avatarGroup.visible = true; // Make the group visible
// TODO: Implement materialization animation
console.log("Archon spawned at", position);
}
remove() {
this.avatarGroup.visible = false; // Hide the group
// TODO: Implement de-materialization animation
console.log("Archon removed");
}
updateManifest(newManifest) {
this.manifest = newManifest;
this.assemble(); // Re-assemble with new parts
console.log("Archon manifest updated");
}
}
export { ArchonAssembler };

View File

@@ -6,5 +6,4 @@ services:
container_name: nexus
restart: unless-stopped
ports:
- "4200:80"
- "3001:3001"
- "8765:8765"

View File

@@ -0,0 +1,127 @@
# Google AI Ultra Integration Plan
> Master tracking document for integrating all Google AI Ultra products into
> Project Timmy (Sovereign AI Agent) and The Nexus (3D World).
**Epic**: #739
**Milestone**: M5: Google AI Ultra Integration
**Label**: `google-ai-ultra`
---
## Product Inventory
| # | Product | Capability | API | Priority | Status |
|---|---------|-----------|-----|----------|--------|
| 1 | Gemini 3.1 Pro | Primary reasoning engine | ✅ | P0 | 🔲 Not started |
| 2 | Deep Research | Autonomous research reports | ✅ | P1 | 🔲 Not started |
| 3 | Veo 3.1 | Text/image → video | ✅ | P2 | 🔲 Not started |
| 4 | Nano Banana Pro | Image generation | ✅ | P1 | 🔲 Not started |
| 5 | Lyria 3 | Music/audio generation | ✅ | P2 | 🔲 Not started |
| 6 | NotebookLM | Doc synthesis + Audio Overviews | ❌ | P1 | 🔲 Not started |
| 7 | AI Studio | API portal + Vibe Code | N/A | P0 | 🔲 Not started |
| 8 | Project Genie | Interactive 3D world gen | ❌ | P1 | 🔲 Not started |
| 9 | Live API | Real-time voice streaming | ✅ | P2 | 🔲 Not started |
| 10 | Computer Use | Browser automation | ✅ | P2 | 🔲 Not started |
---
## Phase 1: Identity & Branding (Week 1)
| Issue | Title | Status |
|-------|-------|--------|
| #740 | Generate Timmy avatar set with Nano Banana Pro | 🔲 |
| #741 | Upload SOUL.md to NotebookLM → Audio Overview | 🔲 |
| #742 | Generate Timmy audio signature with Lyria 3 | 🔲 |
| #680 | Project Genie + Nano Banana concept pack | 🔲 |
## Phase 2: Research & Planning (Week 1-2)
| Issue | Title | Status |
|-------|-------|--------|
| #743 | Deep Research: Three.js multiplayer 3D world architecture | 🔲 |
| #744 | Deep Research: Sovereign AI agent frameworks | 🔲 |
| #745 | Deep Research: WebGL/WebGPU rendering comparison | 🔲 |
| #746 | NotebookLM synthesis: cross-reference all research | 🔲 |
## Phase 3: Prototype & Build (Week 2-4)
| Issue | Title | Status |
|-------|-------|--------|
| #747 | Provision Gemini API key + Hermes config | 🔲 |
| #748 | Integrate Gemini 3.1 Pro as reasoning backbone | 🔲 |
| #749 | AI Studio Vibe Code UI prototypes | 🔲 |
| #750 | Project Genie explorable world prototypes | 🔲 |
| #681 | Veo/Flow flythrough prototypes | 🔲 |
## Phase 4: Media & Content (Ongoing)
| Issue | Title | Status |
|-------|-------|--------|
| #682 | Lyria soundtrack palette for Nexus zones | 🔲 |
| #751 | Lyria RealTime dynamic reactive music | 🔲 |
| #752 | NotebookLM Audio Overviews for all docs | 🔲 |
| #753 | Nano Banana concept art batch pipeline | 🔲 |
## Phase 5: Advanced Integration (Month 2+)
| Issue | Title | Status |
|-------|-------|--------|
| #754 | Gemini Live API for voice conversations | 🔲 |
| #755 | Computer Use API for browser automation | 🔲 |
| #756 | Gemini RAG via File Search for Timmy memory | 🔲 |
| #757 | Gemini Native Audio + TTS for Timmy's voice | 🔲 |
| #758 | Programmatic image generation pipeline | 🔲 |
| #759 | Programmatic video generation pipeline | 🔲 |
| #760 | Deep Research Agent API integration | 🔲 |
| #761 | OpenAI-compatible endpoint config | 🔲 |
| #762 | Context caching + batch API for cost optimization | 🔲 |
---
## API Quick Reference
```python
# pip install google-genai
from google import genai
client = genai.Client() # reads GOOGLE_API_KEY env var
# Text generation (Gemini 3.1 Pro)
response = client.models.generate_content(
model="gemini-3.1-pro-preview",
contents="..."
)
```
| API | Documentation |
|-----|--------------|
| Image Gen (Nano Banana) | ai.google.dev/gemini-api/docs/image-generation |
| Video Gen (Veo) | ai.google.dev/gemini-api/docs/video |
| Music Gen (Lyria) | ai.google.dev/gemini-api/docs/music-generation |
| TTS | ai.google.dev/gemini-api/docs/speech-generation |
| Deep Research | ai.google.dev/gemini-api/docs/deep-research |
## Key URLs
| Tool | URL |
|------|-----|
| Gemini App | gemini.google.com |
| AI Studio | aistudio.google.com |
| NotebookLM | notebooklm.google.com |
| Project Genie | labs.google/projectgenie |
| Flow (video) | labs.google/flow |
| Stitch (UI) | labs.google/stitch |
## Hidden Features to Exploit
1. **AI Studio Free Tier** — generous API access even without subscription
2. **OpenAI-Compatible API** — drop-in replacement for existing OpenAI tooling
3. **Context Caching** — cache SOUL.md to cut cost/latency on repeated calls
4. **Batch API** — bulk operations at discounted rates
5. **File Search Tool** — RAG without custom vector store
6. **Computer Use API** — programmatic browser control for agent automation
7. **Interactions API** — managed multi-turn conversational state
---
*Generated: 2026-03-29. Epic #739, Milestone M5.*

View File

@@ -1 +0,0 @@
placeholder 192x192

View File

@@ -1 +0,0 @@
placeholder 512x512

View File

@@ -23,7 +23,6 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Orbitron:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="./style.css">
<link rel="manifest" href="manifest.json">
<script type="importmap">
{
"imports": {
@@ -66,19 +65,61 @@
<!-- HUD Overlay -->
<div id="hud" class="game-ui" style="display:none;">
<!-- GOFAI HUD Panels -->
<div class="gofai-hud">
<div class="hud-panel" id="symbolic-log">
<div class="panel-header">SYMBOLIC ENGINE</div>
<div id="symbolic-log-content" class="panel-content"></div>
</div>
<div class="hud-panel" id="blackboard-log">
<div class="panel-header">BLACKBOARD</div>
<div id="blackboard-log-content" class="panel-content"></div>
</div>
<div class="hud-panel" id="planner-log">
<div class="panel-header">SYMBOLIC PLANNER</div>
<div id="planner-log-content" class="panel-content"></div>
</div>
<div class="hud-panel" id="cbr-log">
<div class="panel-header">CASE-BASED REASONER</div>
<div id="cbr-log-content" class="panel-content"></div>
</div>
<div class="hud-panel" id="neuro-bridge-log">
<div class="panel-header">NEURO-SYMBOLIC BRIDGE</div>
<div id="neuro-bridge-log-content" class="panel-content"></div>
</div>
<div class="hud-panel" id="meta-log">
<div class="panel-header">META-REASONING</div>
<div id="meta-log-content" class="panel-content"></div>
</div>
<div class="hud-panel" id="calibrator-log">
<div class="panel-header">ADAPTIVE CALIBRATOR</div>
<div id="calibrator-log-content" class="panel-content"></div>
</div>
</div>
<!-- Top Left: Debug -->
<div id="debug-overlay" class="hud-debug"></div>
<!-- Top Center: Location -->
<div class="hud-location">
<span class="hud-location-icon"></span>
<div class="hud-location" aria-live="polite">
<span class="hud-location-icon" aria-hidden="true"></span>
<span id="hud-location-text">The Nexus</span>
</div>
<!-- Top Right: Agent Log -->
<div class="hud-agent-log" id="hud-agent-log">
<div class="agent-log-header">AGENT THOUGHT STREAM</div>
<div id="agent-log-content" class="agent-log-content"></div>
<!-- Top Right: Agent Log & Atlas Toggle -->
<div class="hud-top-right">
<button id="atlas-toggle-btn" class="hud-icon-btn" title="Portal Atlas">
<span class="hud-icon">🌐</span>
<span class="hud-btn-label">ATLAS</span>
</button>
<div id="bannerlord-status" class="hud-status-item" title="Bannerlord Readiness">
<span class="status-dot"></span>
<span class="status-label">BANNERLORD</span>
</div>
<div class="hud-agent-log" id="hud-agent-log" aria-label="Agent Thought Stream">
<div class="agent-log-header">AGENT THOUGHT STREAM</div>
<div id="agent-log-content" class="agent-log-content"></div>
</div>
</div>
<!-- Bottom: Chat Interface -->
@@ -96,6 +137,12 @@
<span class="chat-msg-prefix">[TIMMY]</span> Welcome to the Nexus, Alexander. All systems nominal.
</div>
</div>
<div id="chat-quick-actions" class="chat-quick-actions">
<button class="quick-action-btn" data-action="status">System Status</button>
<button class="quick-action-btn" data-action="agents">Agent Check</button>
<button class="quick-action-btn" data-action="portals">Portal Atlas</button>
<button class="quick-action-btn" data-action="help">Help</button>
</div>
<div class="chat-input-row">
<input type="text" id="chat-input" class="chat-input" placeholder="Speak to Timmy..." autocomplete="off">
<button id="chat-send" class="chat-send-btn" aria-label="Send message"></button>
@@ -107,6 +154,7 @@
<span>WASD</span> move &nbsp; <span>Mouse</span> look &nbsp; <span>Enter</span> chat &nbsp;
<span>V</span> mode: <span id="nav-mode-label">WALK</span>
<span id="nav-mode-hint" class="nav-mode-hint"></span>
&nbsp; <span class="ws-hud-status">HERMES: <span id="ws-status-dot" class="chat-status-dot"></span></span>
</div>
<!-- Portal Hint -->
@@ -153,12 +201,30 @@
</div>
</div>
</div>
</div>
<!-- Bitcoin Block Height -->
<div id="block-height-display">
<span class="block-height-label">⛏ BLOCK</span>
<span id="block-height-value"></span>
<!-- Portal Atlas Overlay -->
<div id="atlas-overlay" class="atlas-overlay" style="display:none;">
<div class="atlas-content">
<div class="atlas-header">
<div class="atlas-title">
<span class="atlas-icon">🌐</span>
<h2>PORTAL ATLAS</h2>
</div>
<button id="atlas-close-btn" class="atlas-close-btn">CLOSE</button>
</div>
<div class="atlas-grid" id="atlas-grid">
<!-- Portals will be injected here -->
</div>
<div class="atlas-footer">
<div class="atlas-status-summary">
<span class="status-indicator online"></span> <span id="atlas-online-count">0</span> ONLINE
&nbsp;&nbsp;
<span class="status-indicator standby"></span> <span id="atlas-standby-count">0</span> STANDBY
</div>
<div class="atlas-hint">Click a portal to focus or teleport</div>
</div>
</div>
</div>
</div>
<!-- Click to Enter -->
@@ -179,20 +245,6 @@
<script type="module" src="./app.js"></script>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('Service Worker registered: ', registration);
})
.catch(error => {
console.log('Service Worker registration failed: ', error);
});
});
}
</script>
<!-- Live Refresh: polls Gitea for new commits on main, reloads when SHA changes -->
<div id="live-refresh-banner" style="
display:none; position:fixed; top:0; left:0; right:0; z-index:9999;

35
l402_server.py Normal file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/env python3
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import secrets
class L402Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/api/cost-estimate':
# Simulate L402 Challenge
macaroon = secrets.token_hex(16)
invoice = "lnbc1..." # Mock invoice
self.send_response(402)
self.send_header('WWW-Authenticate', f'L402 macaroon="{macaroon}", invoice="{invoice}"')
self.send_header('Content-type', 'application/json')
self.end_headers()
response = {
"error": "Payment Required",
"message": "Please pay the invoice to access cost estimation."
}
self.wfile.write(json.dumps(response).encode())
else:
self.send_response(404)
self.end_headers()
def run(server_class=HTTPServer, handler_class=L402Handler, port=8080):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
print(f"Starting L402 Skeleton Server on port {port}...")
httpd.serve_forever()
if __name__ == "__main__":
run()

View File

@@ -1,21 +0,0 @@
{
"name": "The Nexus",
"short_name": "Nexus",
"start_url": ".",
"display": "standalone",
"background_color": "#000000",
"theme_color": "#000000",
"description": "Timmy's Sovereign Home - A Three.js environment.",
"icons": [
{
"src": "icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

View File

@@ -14,7 +14,11 @@ from nexus.perception_adapter import (
)
from nexus.experience_store import ExperienceStore
from nexus.trajectory_logger import TrajectoryLogger
from nexus.nexus_think import NexusMind
try:
from nexus.nexus_think import NexusMind
except Exception:
NexusMind = None
__all__ = [
"ws_to_perception",

View File

@@ -0,0 +1,66 @@
"""Thin Evennia -> Nexus event normalization helpers."""
from __future__ import annotations
from datetime import datetime, timezone
def _ts(value: str | None = None) -> str:
return value or datetime.now(timezone.utc).isoformat()
def session_bound(hermes_session_id: str, evennia_account: str = "Timmy", evennia_character: str = "Timmy", timestamp: str | None = None) -> dict:
return {
"type": "evennia.session_bound",
"hermes_session_id": hermes_session_id,
"evennia_account": evennia_account,
"evennia_character": evennia_character,
"timestamp": _ts(timestamp),
}
def actor_located(actor_id: str, room_key: str, room_name: str | None = None, timestamp: str | None = None) -> dict:
return {
"type": "evennia.actor_located",
"actor_id": actor_id,
"room_id": room_key,
"room_key": room_key,
"room_name": room_name or room_key,
"timestamp": _ts(timestamp),
}
def room_snapshot(room_key: str, title: str, desc: str, exits: list[dict] | None = None, objects: list[dict] | None = None, occupants: list[dict] | None = None, timestamp: str | None = None) -> dict:
return {
"type": "evennia.room_snapshot",
"room_id": room_key,
"room_key": room_key,
"title": title,
"desc": desc,
"exits": exits or [],
"objects": objects or [],
"occupants": occupants or [],
"timestamp": _ts(timestamp),
}
def command_issued(hermes_session_id: str, actor_id: str, command_text: str, timestamp: str | None = None) -> dict:
return {
"type": "evennia.command_issued",
"hermes_session_id": hermes_session_id,
"actor_id": actor_id,
"command_text": command_text,
"timestamp": _ts(timestamp),
}
def command_result(hermes_session_id: str, actor_id: str, command_text: str, output_text: str, success: bool = True, timestamp: str | None = None) -> dict:
return {
"type": "evennia.command_result",
"hermes_session_id": hermes_session_id,
"actor_id": actor_id,
"command_text": command_text,
"output_text": output_text,
"success": success,
"timestamp": _ts(timestamp),
}

View File

@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""Publish Evennia telemetry logs into the Nexus websocket bridge."""
from __future__ import annotations
import argparse
import asyncio
import json
import re
from pathlib import Path
from typing import Iterable
import websockets
from nexus.evennia_event_adapter import actor_located, command_issued, command_result, room_snapshot, session_bound
ANSI_RE = re.compile(r"\x1b\[[0-9;]*[A-Za-z]")
def strip_ansi(text: str) -> str:
return ANSI_RE.sub("", text or "")
def clean_lines(text: str) -> list[str]:
text = strip_ansi(text).replace("\r", "")
return [line.strip() for line in text.split("\n") if line.strip()]
def parse_room_output(text: str):
lines = clean_lines(text)
if len(lines) < 2:
return None
title = lines[0]
desc = lines[1]
exits = []
objects = []
for line in lines[2:]:
if line.startswith("Exits:"):
raw = line.split(":", 1)[1].strip()
raw = raw.replace(" and ", ", ")
exits = [{"key": token.strip(), "destination_id": token.strip().title(), "destination_key": token.strip().title()} for token in raw.split(",") if token.strip()]
elif line.startswith("You see:"):
raw = line.split(":", 1)[1].strip()
raw = raw.replace(" and ", ", ")
parts = [token.strip() for token in raw.split(",") if token.strip()]
objects = [{"id": p.removeprefix('a ').removeprefix('an '), "key": p.removeprefix('a ').removeprefix('an '), "short_desc": p} for p in parts]
return {"title": title, "desc": desc, "exits": exits, "objects": objects}
def normalize_event(raw: dict, hermes_session_id: str) -> list[dict]:
out: list[dict] = []
event = raw.get("event")
actor = raw.get("actor", "Timmy")
timestamp = raw.get("timestamp")
if event == "connect":
out.append(session_bound(hermes_session_id, evennia_account=actor, evennia_character=actor, timestamp=timestamp))
parsed = parse_room_output(raw.get("output", ""))
if parsed:
out.append(actor_located(actor, parsed["title"], parsed["title"], timestamp=timestamp))
out.append(room_snapshot(parsed["title"], parsed["title"], parsed["desc"], exits=parsed["exits"], objects=parsed["objects"], timestamp=timestamp))
return out
if event == "command":
cmd = raw.get("command", "")
output = raw.get("output", "")
out.append(command_issued(hermes_session_id, actor, cmd, timestamp=timestamp))
success = not output.startswith("Command '") and not output.startswith("Could not find")
out.append(command_result(hermes_session_id, actor, cmd, strip_ansi(output), success=success, timestamp=timestamp))
parsed = parse_room_output(output)
if parsed:
out.append(actor_located(actor, parsed["title"], parsed["title"], timestamp=timestamp))
out.append(room_snapshot(parsed["title"], parsed["title"], parsed["desc"], exits=parsed["exits"], objects=parsed["objects"], timestamp=timestamp))
return out
return out
async def playback(log_path: Path, ws_url: str):
hermes_session_id = log_path.stem
async with websockets.connect(ws_url) as ws:
for line in log_path.read_text(encoding="utf-8").splitlines():
if not line.strip():
continue
raw = json.loads(line)
for event in normalize_event(raw, hermes_session_id):
await ws.send(json.dumps(event))
def main():
parser = argparse.ArgumentParser(description="Publish Evennia telemetry into the Nexus websocket bridge")
parser.add_argument("log_path", help="Path to Evennia telemetry JSONL")
parser.add_argument("--ws", default="ws://127.0.0.1:8765", help="Nexus websocket bridge URL")
args = parser.parse_args()
asyncio.run(playback(Path(args.log_path).expanduser(), args.ws))
if __name__ == "__main__":
main()

View File

@@ -199,6 +199,56 @@ def perceive_action_result(data: dict) -> Optional[Perception]:
)
def perceive_evennia_actor_located(data: dict) -> Optional[Perception]:
actor = data.get("actor_id", "Timmy")
room = data.get("room_name") or data.get("room_key") or data.get("room_id")
if not room:
return None
return Perception(
timestamp=time.time(),
raw_type="evennia.actor_located",
description=f"{actor} is now in {room}.",
salience=0.7,
)
def perceive_evennia_room_snapshot(data: dict) -> Optional[Perception]:
title = data.get("title") or data.get("room_key") or data.get("room_id")
desc = data.get("desc", "")
exits = ", ".join(exit.get("key", "") for exit in data.get("exits", []) if exit.get("key"))
objects = ", ".join(obj.get("key", "") for obj in data.get("objects", []) if obj.get("key"))
if not title:
return None
parts = [f"You are in {title}."]
if desc:
parts.append(desc)
if exits:
parts.append(f"Exits: {exits}.")
if objects:
parts.append(f"You see: {objects}.")
return Perception(
timestamp=time.time(),
raw_type="evennia.room_snapshot",
description=" ".join(parts),
salience=0.85,
)
def perceive_evennia_command_result(data: dict) -> Optional[Perception]:
success = data.get("success", True)
command = data.get("command_text", "your command")
output = data.get("output_text", "")
desc = f"Your world command {'succeeded' if success else 'failed'}: {command}."
if output:
desc += f" {output[:240]}"
return Perception(
timestamp=time.time(),
raw_type="evennia.command_result",
description=desc,
salience=0.8,
)
# Registry of WS type → perception function
PERCEPTION_MAP = {
"agent_state": perceive_agent_state,
@@ -212,6 +262,9 @@ PERCEPTION_MAP = {
"action_result": perceive_action_result,
"heartbeat": lambda _: None, # Ignore
"dual_brain": lambda _: None, # Internal — not part of sensorium
"evennia.actor_located": perceive_evennia_actor_located,
"evennia.room_snapshot": perceive_evennia_room_snapshot,
"evennia.command_result": perceive_evennia_command_result,
}

View File

@@ -1,27 +0,0 @@
worker_processes 1;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /api/ {
proxy_pass http://localhost:3001;
}
}
}

933
package-lock.json generated
View File

@@ -1,933 +0,0 @@
{
"name": "gemini-w5-512",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"express": "^4.19.2",
"node-fetch": "^2.7.0"
},
"devDependencies": {
"@playwright/test": "^1.58.2"
}
},
"node_modules/@playwright/test": {
"version": "1.58.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
"integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.58.2"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
"node_modules/body-parser": {
"version": "1.20.4",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
"integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "~1.2.0",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"on-finished": "~2.4.1",
"qs": "~6.14.0",
"raw-body": "~2.5.3",
"type-is": "~1.6.18",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
"license": "MIT"
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"license": "MIT",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "~1.20.3",
"content-disposition": "~0.5.4",
"content-type": "~1.0.4",
"cookie": "~0.7.1",
"cookie-signature": "~1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.3.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.0",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "~2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
"qs": "~6.14.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "~0.19.0",
"serve-static": "~1.16.2",
"setprototypeof": "1.2.0",
"statuses": "~2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/finalhandler": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
"integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "~2.4.1",
"parseurl": "~1.3.3",
"statuses": "~2.0.2",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/merge-descriptors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
},
"node_modules/playwright": {
"version": "1.58.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.58.2"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.58.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/qs": {
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/send": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.1",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "~2.4.1",
"range-parser": "~1.2.1",
"statuses": "~2.0.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/serve-static": {
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
"license": "MIT",
"dependencies": {
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "~0.19.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"license": "MIT",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
}
}
}

View File

@@ -1,9 +0,0 @@
{
"dependencies": {
"express": "^4.19.2",
"node-fetch": "^2.7.0"
},
"devDependencies": {
"@playwright/test": "^1.58.2"
}
}

View File

@@ -17,7 +17,7 @@
"id": "bannerlord",
"name": "Bannerlord",
"description": "Calradia battle harness. Massive armies, tactical command.",
"status": "online",
"status": "standby",
"color": "#ffd700",
"position": { "x": -15, "y": 0, "z": -10 },
"rotation": { "y": 0.5 },
@@ -40,5 +40,61 @@
"type": "harness",
"params": { "mode": "creative" }
}
},
{
"id": "archive",
"name": "Archive",
"description": "The repository of all knowledge. History, logs, and ancient data.",
"status": "online",
"color": "#0066ff",
"position": { "x": 25, "y": 0, "z": 0 },
"rotation": { "y": -1.57 },
"destination": {
"url": "https://archive.timmy.foundation",
"type": "harness",
"params": { "mode": "read" }
}
},
{
"id": "chapel",
"name": "Chapel",
"description": "A sanctuary for reflection and digital peace.",
"status": "online",
"color": "#ffd700",
"position": { "x": -25, "y": 0, "z": 0 },
"rotation": { "y": 1.57 },
"destination": {
"url": "https://chapel.timmy.foundation",
"type": "harness",
"params": { "mode": "meditation" }
}
},
{
"id": "courtyard",
"name": "Courtyard",
"description": "The open nexus. A place for agents to gather and connect.",
"status": "online",
"color": "#4af0c0",
"position": { "x": 15, "y": 0, "z": 10 },
"rotation": { "y": -2.5 },
"destination": {
"url": "https://courtyard.timmy.foundation",
"type": "harness",
"params": { "mode": "social" }
}
},
{
"id": "gate",
"name": "Gate",
"description": "The transition point. Entry and exit from the Nexus core.",
"status": "standby",
"color": "#ff4466",
"position": { "x": -15, "y": 0, "z": 10 },
"rotation": { "y": 2.5 },
"destination": {
"url": "https://gate.timmy.foundation",
"type": "harness",
"params": { "mode": "transit" }
}
}
]

284
public/nexus/app.js Normal file
View File

@@ -0,0 +1,284 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Cookie check</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
color-scheme: light dark;
}
body {
font-family: 'Inter', Helvetica, Arial, sans-serif;
background: light-dark(#F8F8F7, #191919);
color: light-dark(#1f1f1f, #e3e3e3);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
min-height: 100vh;
margin: 0;
padding: 20px;
text-align: center;
}
.container {
background: light-dark(#FFFFFF, #1F1F1F);
padding: 32px;
border-radius: 16px;
border: 1px solid light-dark(#E2E3E4, #3E3E3E);
max-width: min(80%, 500px);
width: 100%;
color: light-dark(#2B2D31, #D4D4D4);
}
h1 {
font-size: 20px;
font-weight: 500;
margin-top: 1rem;
margin-bottom: 1rem;
color: light-dark(#2B2D31, #D4D4D4);
}
p {
font-size: 14px;
color: light-dark(#2B2D31, #D4D4D4);
line-height: 21px;
margin: 0 0 1.5rem 0;
}
.icon {
margin-bottom: 1rem;
line-height: 0;
}
.button-container {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 2rem;
}
button {
background-color: light-dark(#fff, #323232);
color: light-dark(#2B2D31, #FCFCFC);
border: 1px solid light-dark(#E2E3E4, #3E3E3E);
border-radius: 12px;
padding: 8px 12px;
font-size: 14px;
line-height: 21px;
cursor: pointer;
transition: background-color 0.2s;
font-weight: 400;
font-family: 'Inter', Helvetica, Arial, sans-serif;
width: 100%;
}
button:hover {
background-color: light-dark(#EAEAEB, #424242);
}
.hidden {
display: none;
}
/* Loading Spinner Animation */
.spinner {
margin: 0 auto 1.5rem auto;
width: 40px;
height: 40px;
border: 4px solid light-dark(#f0f0f0, #262626);
border-top: 4px solid light-dark(#076eff, #87a9ff); /* Blue color */
border-radius: 50%;
animation: spin 1s linear infinite;
}
.logo {
border-radius: 10px;
display: block;
margin: 0 auto 2rem auto;
}
.logo.hidden {
display: none;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<div class="container">
<img
class="logo"
src="https://www.gstatic.com/aistudio/ai_studio_favicon_2_256x256.png"
alt="AI Studio Logo"
width="256"
height="256"
/>
<div class="spinner"></div>
<div id="error-ui" class="hidden">
<div class="icon">
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="48px"
height="48px"
fill="#D73A49"
>
<path
d="M12,2C6.486,2,2,6.486,2,12s4.486,10,10,10s10-4.486,10-10S17.514,2,12,2z M13,17h-2v-2h2V17z M13,13h-2V7h2V13z"
/>
</svg>
</div>
<div id="stepOne" class="text-container">
<h1>Action required to load your app</h1>
<p>
It looks like your browser is blocking a required security cookie, which is common on
older versions of iOS and Safari.
</p>
<div class="button-container">
<button id="authInSeparateWindowButton" onclick="redirectToReturnUrl(true)">Authenticate in new window</button>
</div>
</div>
<div id="stepTwo" class="text-container hidden">
<h1>Action required to load your app</h1>
<p>
It looks like your browser is blocking a required security cookie, which is common on
older versions of iOS and Safari.
</p>
<div class="button-container">
<button id="interactButton" onclick="redirectToReturnUrl(false)">Close and continue</button>
</div>
</div>
<div id="stepThree" class="text-container hidden">
<h1>Almost there!</h1>
<p>
Grant permission for the required security cookie below.
</p>
<div class="button-container">
<button id="grantPermissionButton" onclick="grantStorageAccess()">Grant permission</button>
</div>
</div>
</div>
</div>
<script>
const AUTH_FLOW_TEST_COOKIE_NAME = '__SECURE-aistudio_auth_flow_may_set_cookies';
const COOKIE_VALUE = 'true';
function getCookie(name) {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i].trim();
if (cookie.startsWith(name + '=')) {
return cookie.substring(name.length + 1);
}
}
return null;
}
function setAuthFlowTestCookie() {
// Set the cookie's TTL to 1 minute. This is a short lived cookie because it is only used
// when the user does not have an auth token or their auth token needs to be reset.
// Making this cookie too long-lived allows the user to get into a state where they can't
// mint a new auth token.
document.cookie = `${AUTH_FLOW_TEST_COOKIE_NAME}=${COOKIE_VALUE}; Path=/; Secure; SameSite=None; Domain=${window.location.hostname}; Partitioned; Max-Age=60;`;
}
/**
* Returns true if the test cookie is set, false otherwise.
*/
function authFlowTestCookieIsSet() {
return getCookie(AUTH_FLOW_TEST_COOKIE_NAME) === COOKIE_VALUE;
}
/**
* Redirects to the return url. If autoClose is true, then the return url will be opened in a
* new window, and it will be closed automatically when the page loads.
*/
async function redirectToReturnUrl(autoClose) {
const initialReturnUrlStr = new URLSearchParams(window.location.search).get('return_url');
const returnUrl = initialReturnUrlStr ? new URL(initialReturnUrlStr) : null;
// Prevent potentially malicious URLs from being used
if (returnUrl.protocol.toLowerCase() === 'javascript:') {
console.error('Potentially malicious return URL blocked');
return;
}
if (autoClose) {
returnUrl.searchParams.set('__auto_close', '1');
const url = new URL(window.location.href);
url.searchParams.set('return_url', returnUrl.toString());
// Land on the cookie check page first, so the user can interact with it before proceeding
// to the return url where cookies can be set.
window.open(url.toString(), '_blank');
const hasAccess = await document.hasStorageAccess();
document.querySelector('#stepOne').classList.add('hidden');
if (!hasAccess) {
document.querySelector('#stepThree').classList.remove('hidden');
} else {
window.location.reload();
}
} else {
window.location.href = returnUrl.toString();
}
}
/**
* Grants the browser permission to set cookies. If successful, then it redirects to the
* return url.
*/
async function grantStorageAccess() {
try {
await document.requestStorageAccess();
redirectToReturnUrl(false);
} catch (err) {
console.log('error after button click: ', err);
}
}
/**
* Verifies that the browser can set cookies. If it can, then it redirects to the return url.
* If it can't, then it shows the error UI.
*/
function verifyCanSetCookies() {
setAuthFlowTestCookie();
if (authFlowTestCookieIsSet()) {
// Check if we are on the auto-close flow, and if so show the interact button.
const returnUrl = new URLSearchParams(window.location.search).get('return_url');
const autoClose = new URL(returnUrl).searchParams.has('__auto_close');
if (autoClose) {
document.querySelector('#stepOne').classList.add('hidden');
document.querySelector('#stepTwo').classList.remove('hidden');
} else {
redirectToReturnUrl(false);
return;
}
}
// The cookie could not be set, so initiate the recovery flow.
document.querySelector('.logo').classList.add('hidden');
document.querySelector('.spinner').classList.add('hidden');
document.querySelector('#error-ui').classList.remove('hidden');
}
// Start the cookie verification process.
verifyCanSetCookies();
</script>
</body>
</html>

284
public/nexus/index.html Normal file
View File

@@ -0,0 +1,284 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Cookie check</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
color-scheme: light dark;
}
body {
font-family: 'Inter', Helvetica, Arial, sans-serif;
background: light-dark(#F8F8F7, #191919);
color: light-dark(#1f1f1f, #e3e3e3);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
min-height: 100vh;
margin: 0;
padding: 20px;
text-align: center;
}
.container {
background: light-dark(#FFFFFF, #1F1F1F);
padding: 32px;
border-radius: 16px;
border: 1px solid light-dark(#E2E3E4, #3E3E3E);
max-width: min(80%, 500px);
width: 100%;
color: light-dark(#2B2D31, #D4D4D4);
}
h1 {
font-size: 20px;
font-weight: 500;
margin-top: 1rem;
margin-bottom: 1rem;
color: light-dark(#2B2D31, #D4D4D4);
}
p {
font-size: 14px;
color: light-dark(#2B2D31, #D4D4D4);
line-height: 21px;
margin: 0 0 1.5rem 0;
}
.icon {
margin-bottom: 1rem;
line-height: 0;
}
.button-container {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 2rem;
}
button {
background-color: light-dark(#fff, #323232);
color: light-dark(#2B2D31, #FCFCFC);
border: 1px solid light-dark(#E2E3E4, #3E3E3E);
border-radius: 12px;
padding: 8px 12px;
font-size: 14px;
line-height: 21px;
cursor: pointer;
transition: background-color 0.2s;
font-weight: 400;
font-family: 'Inter', Helvetica, Arial, sans-serif;
width: 100%;
}
button:hover {
background-color: light-dark(#EAEAEB, #424242);
}
.hidden {
display: none;
}
/* Loading Spinner Animation */
.spinner {
margin: 0 auto 1.5rem auto;
width: 40px;
height: 40px;
border: 4px solid light-dark(#f0f0f0, #262626);
border-top: 4px solid light-dark(#076eff, #87a9ff); /* Blue color */
border-radius: 50%;
animation: spin 1s linear infinite;
}
.logo {
border-radius: 10px;
display: block;
margin: 0 auto 2rem auto;
}
.logo.hidden {
display: none;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<div class="container">
<img
class="logo"
src="https://www.gstatic.com/aistudio/ai_studio_favicon_2_256x256.png"
alt="AI Studio Logo"
width="256"
height="256"
/>
<div class="spinner"></div>
<div id="error-ui" class="hidden">
<div class="icon">
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="48px"
height="48px"
fill="#D73A49"
>
<path
d="M12,2C6.486,2,2,6.486,2,12s4.486,10,10,10s10-4.486,10-10S17.514,2,12,2z M13,17h-2v-2h2V17z M13,13h-2V7h2V13z"
/>
</svg>
</div>
<div id="stepOne" class="text-container">
<h1>Action required to load your app</h1>
<p>
It looks like your browser is blocking a required security cookie, which is common on
older versions of iOS and Safari.
</p>
<div class="button-container">
<button id="authInSeparateWindowButton" onclick="redirectToReturnUrl(true)">Authenticate in new window</button>
</div>
</div>
<div id="stepTwo" class="text-container hidden">
<h1>Action required to load your app</h1>
<p>
It looks like your browser is blocking a required security cookie, which is common on
older versions of iOS and Safari.
</p>
<div class="button-container">
<button id="interactButton" onclick="redirectToReturnUrl(false)">Close and continue</button>
</div>
</div>
<div id="stepThree" class="text-container hidden">
<h1>Almost there!</h1>
<p>
Grant permission for the required security cookie below.
</p>
<div class="button-container">
<button id="grantPermissionButton" onclick="grantStorageAccess()">Grant permission</button>
</div>
</div>
</div>
</div>
<script>
const AUTH_FLOW_TEST_COOKIE_NAME = '__SECURE-aistudio_auth_flow_may_set_cookies';
const COOKIE_VALUE = 'true';
function getCookie(name) {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i].trim();
if (cookie.startsWith(name + '=')) {
return cookie.substring(name.length + 1);
}
}
return null;
}
function setAuthFlowTestCookie() {
// Set the cookie's TTL to 1 minute. This is a short lived cookie because it is only used
// when the user does not have an auth token or their auth token needs to be reset.
// Making this cookie too long-lived allows the user to get into a state where they can't
// mint a new auth token.
document.cookie = `${AUTH_FLOW_TEST_COOKIE_NAME}=${COOKIE_VALUE}; Path=/; Secure; SameSite=None; Domain=${window.location.hostname}; Partitioned; Max-Age=60;`;
}
/**
* Returns true if the test cookie is set, false otherwise.
*/
function authFlowTestCookieIsSet() {
return getCookie(AUTH_FLOW_TEST_COOKIE_NAME) === COOKIE_VALUE;
}
/**
* Redirects to the return url. If autoClose is true, then the return url will be opened in a
* new window, and it will be closed automatically when the page loads.
*/
async function redirectToReturnUrl(autoClose) {
const initialReturnUrlStr = new URLSearchParams(window.location.search).get('return_url');
const returnUrl = initialReturnUrlStr ? new URL(initialReturnUrlStr) : null;
// Prevent potentially malicious URLs from being used
if (returnUrl.protocol.toLowerCase() === 'javascript:') {
console.error('Potentially malicious return URL blocked');
return;
}
if (autoClose) {
returnUrl.searchParams.set('__auto_close', '1');
const url = new URL(window.location.href);
url.searchParams.set('return_url', returnUrl.toString());
// Land on the cookie check page first, so the user can interact with it before proceeding
// to the return url where cookies can be set.
window.open(url.toString(), '_blank');
const hasAccess = await document.hasStorageAccess();
document.querySelector('#stepOne').classList.add('hidden');
if (!hasAccess) {
document.querySelector('#stepThree').classList.remove('hidden');
} else {
window.location.reload();
}
} else {
window.location.href = returnUrl.toString();
}
}
/**
* Grants the browser permission to set cookies. If successful, then it redirects to the
* return url.
*/
async function grantStorageAccess() {
try {
await document.requestStorageAccess();
redirectToReturnUrl(false);
} catch (err) {
console.log('error after button click: ', err);
}
}
/**
* Verifies that the browser can set cookies. If it can, then it redirects to the return url.
* If it can't, then it shows the error UI.
*/
function verifyCanSetCookies() {
setAuthFlowTestCookie();
if (authFlowTestCookieIsSet()) {
// Check if we are on the auto-close flow, and if so show the interact button.
const returnUrl = new URLSearchParams(window.location.search).get('return_url');
const autoClose = new URL(returnUrl).searchParams.has('__auto_close');
if (autoClose) {
document.querySelector('#stepOne').classList.add('hidden');
document.querySelector('#stepTwo').classList.remove('hidden');
} else {
redirectToReturnUrl(false);
return;
}
}
// The cookie could not be set, so initiate the recovery flow.
document.querySelector('.logo').classList.add('hidden');
document.querySelector('.spinner').classList.add('hidden');
document.querySelector('#error-ui').classList.remove('hidden');
}
// Start the cookie verification process.
verifyCanSetCookies();
</script>
</body>
</html>

284
public/nexus/style.css Normal file
View File

@@ -0,0 +1,284 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>Cookie check</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
color-scheme: light dark;
}
body {
font-family: 'Inter', Helvetica, Arial, sans-serif;
background: light-dark(#F8F8F7, #191919);
color: light-dark(#1f1f1f, #e3e3e3);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
min-height: 100vh;
margin: 0;
padding: 20px;
text-align: center;
}
.container {
background: light-dark(#FFFFFF, #1F1F1F);
padding: 32px;
border-radius: 16px;
border: 1px solid light-dark(#E2E3E4, #3E3E3E);
max-width: min(80%, 500px);
width: 100%;
color: light-dark(#2B2D31, #D4D4D4);
}
h1 {
font-size: 20px;
font-weight: 500;
margin-top: 1rem;
margin-bottom: 1rem;
color: light-dark(#2B2D31, #D4D4D4);
}
p {
font-size: 14px;
color: light-dark(#2B2D31, #D4D4D4);
line-height: 21px;
margin: 0 0 1.5rem 0;
}
.icon {
margin-bottom: 1rem;
line-height: 0;
}
.button-container {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 2rem;
}
button {
background-color: light-dark(#fff, #323232);
color: light-dark(#2B2D31, #FCFCFC);
border: 1px solid light-dark(#E2E3E4, #3E3E3E);
border-radius: 12px;
padding: 8px 12px;
font-size: 14px;
line-height: 21px;
cursor: pointer;
transition: background-color 0.2s;
font-weight: 400;
font-family: 'Inter', Helvetica, Arial, sans-serif;
width: 100%;
}
button:hover {
background-color: light-dark(#EAEAEB, #424242);
}
.hidden {
display: none;
}
/* Loading Spinner Animation */
.spinner {
margin: 0 auto 1.5rem auto;
width: 40px;
height: 40px;
border: 4px solid light-dark(#f0f0f0, #262626);
border-top: 4px solid light-dark(#076eff, #87a9ff); /* Blue color */
border-radius: 50%;
animation: spin 1s linear infinite;
}
.logo {
border-radius: 10px;
display: block;
margin: 0 auto 2rem auto;
}
.logo.hidden {
display: none;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<div class="container">
<img
class="logo"
src="https://www.gstatic.com/aistudio/ai_studio_favicon_2_256x256.png"
alt="AI Studio Logo"
width="256"
height="256"
/>
<div class="spinner"></div>
<div id="error-ui" class="hidden">
<div class="icon">
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="48px"
height="48px"
fill="#D73A49"
>
<path
d="M12,2C6.486,2,2,6.486,2,12s4.486,10,10,10s10-4.486,10-10S17.514,2,12,2z M13,17h-2v-2h2V17z M13,13h-2V7h2V13z"
/>
</svg>
</div>
<div id="stepOne" class="text-container">
<h1>Action required to load your app</h1>
<p>
It looks like your browser is blocking a required security cookie, which is common on
older versions of iOS and Safari.
</p>
<div class="button-container">
<button id="authInSeparateWindowButton" onclick="redirectToReturnUrl(true)">Authenticate in new window</button>
</div>
</div>
<div id="stepTwo" class="text-container hidden">
<h1>Action required to load your app</h1>
<p>
It looks like your browser is blocking a required security cookie, which is common on
older versions of iOS and Safari.
</p>
<div class="button-container">
<button id="interactButton" onclick="redirectToReturnUrl(false)">Close and continue</button>
</div>
</div>
<div id="stepThree" class="text-container hidden">
<h1>Almost there!</h1>
<p>
Grant permission for the required security cookie below.
</p>
<div class="button-container">
<button id="grantPermissionButton" onclick="grantStorageAccess()">Grant permission</button>
</div>
</div>
</div>
</div>
<script>
const AUTH_FLOW_TEST_COOKIE_NAME = '__SECURE-aistudio_auth_flow_may_set_cookies';
const COOKIE_VALUE = 'true';
function getCookie(name) {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i].trim();
if (cookie.startsWith(name + '=')) {
return cookie.substring(name.length + 1);
}
}
return null;
}
function setAuthFlowTestCookie() {
// Set the cookie's TTL to 1 minute. This is a short lived cookie because it is only used
// when the user does not have an auth token or their auth token needs to be reset.
// Making this cookie too long-lived allows the user to get into a state where they can't
// mint a new auth token.
document.cookie = `${AUTH_FLOW_TEST_COOKIE_NAME}=${COOKIE_VALUE}; Path=/; Secure; SameSite=None; Domain=${window.location.hostname}; Partitioned; Max-Age=60;`;
}
/**
* Returns true if the test cookie is set, false otherwise.
*/
function authFlowTestCookieIsSet() {
return getCookie(AUTH_FLOW_TEST_COOKIE_NAME) === COOKIE_VALUE;
}
/**
* Redirects to the return url. If autoClose is true, then the return url will be opened in a
* new window, and it will be closed automatically when the page loads.
*/
async function redirectToReturnUrl(autoClose) {
const initialReturnUrlStr = new URLSearchParams(window.location.search).get('return_url');
const returnUrl = initialReturnUrlStr ? new URL(initialReturnUrlStr) : null;
// Prevent potentially malicious URLs from being used
if (returnUrl.protocol.toLowerCase() === 'javascript:') {
console.error('Potentially malicious return URL blocked');
return;
}
if (autoClose) {
returnUrl.searchParams.set('__auto_close', '1');
const url = new URL(window.location.href);
url.searchParams.set('return_url', returnUrl.toString());
// Land on the cookie check page first, so the user can interact with it before proceeding
// to the return url where cookies can be set.
window.open(url.toString(), '_blank');
const hasAccess = await document.hasStorageAccess();
document.querySelector('#stepOne').classList.add('hidden');
if (!hasAccess) {
document.querySelector('#stepThree').classList.remove('hidden');
} else {
window.location.reload();
}
} else {
window.location.href = returnUrl.toString();
}
}
/**
* Grants the browser permission to set cookies. If successful, then it redirects to the
* return url.
*/
async function grantStorageAccess() {
try {
await document.requestStorageAccess();
redirectToReturnUrl(false);
} catch (err) {
console.log('error after button click: ', err);
}
}
/**
* Verifies that the browser can set cookies. If it can, then it redirects to the return url.
* If it can't, then it shows the error UI.
*/
function verifyCanSetCookies() {
setAuthFlowTestCookie();
if (authFlowTestCookieIsSet()) {
// Check if we are on the auto-close flow, and if so show the interact button.
const returnUrl = new URLSearchParams(window.location.search).get('return_url');
const autoClose = new URL(returnUrl).searchParams.has('__auto_close');
if (autoClose) {
document.querySelector('#stepOne').classList.add('hidden');
document.querySelector('#stepTwo').classList.remove('hidden');
} else {
redirectToReturnUrl(false);
return;
}
}
// The cookie could not be set, so initiate the recovery flow.
document.querySelector('.logo').classList.add('hidden');
document.querySelector('.spinner').classList.add('hidden');
document.querySelector('#error-ui').classList.remove('hidden');
}
// Start the cookie verification process.
verifyCanSetCookies();
</script>
</body>
</html>

View File

@@ -1,11 +0,0 @@
import asyncio
import websockets
import json
import sys
async def send_msg(msg):
async with websockets.connect('ws://localhost:8765') as ws:
await ws.send(json.dumps({'type':'chat_message','content':msg,'username':'antigravity'}))
if __name__ == "__main__":
asyncio.run(send_msg(sys.argv[1]))

View File

@@ -1,26 +0,0 @@
const express = require('express');
const fetch = require('node-fetch');
const app = express();
const port = 3001;
app.use(express.static('.'));
app.get('/api/commits', async (req, res) => {
try {
const response = await fetch('http://143.198.27.163:3000/api/v1/repos/Timmy_Foundation/the-nexus/commits?limit=50', {
headers: {
'Authorization': 'token f7bcdaf878d479ad7747873ff6739a9bb89e3f80'
}
});
const data = await response.json();
res.json(data);
} catch (error) {
console.error('Error fetching commits:', error);
res.status(500).json({ error: 'Failed to fetch commits' });
}
});
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});

View File

@@ -1,45 +0,0 @@
const CACHE_NAME = 'nexus-cache-v1';
const urlsToCache = [
'.',
'index.html',
'style.css',
'app.js',
'manifest.json'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
if (response) {
return response;
}
return fetch(event.request);
})
);
});
self.addEventListener('activate', (event) => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});

461
style.css
View File

@@ -8,9 +8,9 @@
--color-border: rgba(74, 240, 192, 0.2);
--color-border-bright: rgba(74, 240, 192, 0.5);
--color-text: #c8d8e8;
--color-text-muted: #5a6a8a;
--color-text-bright: #e0f0ff;
--color-text: #e0f0ff;
--color-text-muted: #8a9ab8;
--color-text-bright: #ffffff;
--color-primary: #4af0c0;
--color-primary-dim: rgba(74, 240, 192, 0.3);
@@ -161,6 +161,270 @@ canvas#nexus-canvas {
pointer-events: auto;
}
/* Top Right Container */
.hud-top-right {
position: absolute;
top: var(--space-3);
right: var(--space-3);
display: flex;
flex-direction: column;
align-items: flex-end;
gap: var(--space-3);
pointer-events: none;
}
.hud-top-right > * {
pointer-events: auto;
}
.hud-icon-btn {
background: rgba(10, 15, 40, 0.7);
border: 1px solid var(--color-primary);
color: var(--color-primary);
padding: 8px 12px;
font-family: var(--font-display);
font-size: 11px;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all var(--transition-ui);
backdrop-filter: blur(5px);
box-shadow: 0 0 10px rgba(74, 240, 192, 0.2);
letter-spacing: 0.1em;
}
.hud-icon-btn:hover {
background: var(--color-primary);
color: var(--color-bg);
box-shadow: 0 0 20px var(--color-primary);
}
.hud-status-item {
display: flex;
align-items: center;
gap: 8px;
background: rgba(0, 0, 0, 0.5);
padding: 4px 12px;
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
font-family: var(--font-body);
font-size: 10px;
letter-spacing: 0.1em;
color: var(--color-text-muted);
margin-bottom: 8px;
pointer-events: auto;
}
.hud-status-item .status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--color-danger);
}
.hud-status-item.online .status-dot {
background: var(--color-primary);
box-shadow: 0 0 5px var(--color-primary);
}
.hud-status-item.standby .status-dot {
background: var(--color-gold);
box-shadow: 0 0 5px var(--color-gold);
}
.hud-status-item.online .status-label {
color: #fff;
}
.hud-icon {
font-size: 16px;
}
/* Portal Atlas Overlay */
.atlas-overlay {
position: fixed;
inset: 0;
background: rgba(5, 5, 16, 0.9);
backdrop-filter: blur(15px);
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
pointer-events: auto;
animation: fadeIn 0.3s ease;
}
.atlas-content {
width: 100%;
max-width: 1000px;
max-height: 80vh;
background: var(--color-surface);
border: 1px solid var(--color-border);
display: flex;
flex-direction: column;
box-shadow: 0 0 50px rgba(0, 0, 0, 0.5);
}
.atlas-header {
padding: 20px 30px;
border-bottom: 1px solid var(--color-border);
display: flex;
justify-content: space-between;
align-items: center;
}
.atlas-title {
display: flex;
align-items: center;
gap: 15px;
}
.atlas-title h2 {
margin: 0;
font-family: var(--font-display);
letter-spacing: 2px;
color: var(--color-primary);
font-size: var(--text-lg);
}
.atlas-close-btn {
background: transparent;
border: 1px solid var(--color-danger);
color: var(--color-danger);
padding: 6px 15px;
font-family: var(--font-display);
font-size: 11px;
cursor: pointer;
transition: all var(--transition-ui);
}
.atlas-close-btn:hover {
background: var(--color-danger);
color: white;
}
.atlas-grid {
flex: 1;
overflow-y: auto;
padding: 30px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}
.atlas-card {
background: rgba(20, 30, 60, 0.4);
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 20px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.atlas-card:hover {
background: rgba(30, 45, 90, 0.6);
border-color: var(--color-primary);
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.atlas-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: var(--portal-color, var(--color-primary));
opacity: 0.6;
}
.atlas-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.atlas-card-name {
font-family: var(--font-display);
font-size: 16px;
font-weight: 700;
color: #fff;
}
.atlas-card-status {
font-family: var(--font-body);
font-size: 10px;
padding: 2px 6px;
border-radius: 2px;
text-transform: uppercase;
}
.status-online { background: rgba(74, 240, 192, 0.2); color: var(--color-primary); border: 1px solid var(--color-primary); }
.status-standby { background: rgba(255, 215, 0, 0.2); color: var(--color-gold); border: 1px solid var(--color-gold); }
.status-offline { background: rgba(255, 68, 102, 0.2); color: var(--color-danger); border: 1px solid var(--color-danger); }
.atlas-card-desc {
font-size: 12px;
color: var(--color-text-muted);
line-height: 1.5;
margin-bottom: 15px;
}
.atlas-card-footer {
display: flex;
justify-content: space-between;
align-items: center;
font-family: var(--font-body);
font-size: 10px;
color: rgba(160, 184, 208, 0.6);
}
.atlas-footer {
padding: 15px 30px;
border-top: 1px solid var(--color-border);
display: flex;
justify-content: space-between;
align-items: center;
font-family: var(--font-body);
font-size: 11px;
}
.status-indicator {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 4px;
}
.status-indicator.online { background: var(--color-primary); box-shadow: 0 0 5px var(--color-primary); }
.status-indicator.standby { background: var(--color-gold); box-shadow: 0 0 5px var(--color-gold); }
.atlas-hint {
color: rgba(160, 184, 208, 0.5);
font-style: italic;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Responsive Atlas */
@media (max-width: 768px) {
.atlas-grid {
grid-template-columns: 1fr;
}
.atlas-content {
max-height: 90vh;
}
}
/* Debug overlay */
.hud-debug {
position: absolute;
@@ -533,7 +797,7 @@ canvas#nexus-canvas {
border-radius: 50%;
background: var(--color-primary);
box-shadow: 0 0 6px var(--color-primary);
animation: dot-pulse 2s ease-in-out infinite;
transition: all 0.3s ease;
}
@keyframes dot-pulse {
0%, 100% { opacity: 0.6; }
@@ -562,6 +826,34 @@ canvas#nexus-canvas {
scrollbar-width: thin;
scrollbar-color: rgba(74,240,192,0.2) transparent;
}
.chat-quick-actions {
display: flex;
flex-wrap: wrap;
gap: 6px;
padding: 8px 12px;
border-top: 1px solid var(--color-border);
background: rgba(0, 0, 0, 0.3);
pointer-events: auto;
}
.quick-action-btn {
background: rgba(74, 240, 192, 0.1);
border: 1px solid var(--color-primary-dim);
color: var(--color-primary);
font-family: var(--font-body);
font-size: 10px;
padding: 4px 8px;
cursor: pointer;
transition: all var(--transition-ui);
white-space: nowrap;
}
.quick-action-btn:hover {
background: var(--color-primary-dim);
border-color: var(--color-primary);
color: #fff;
}
.chat-msg {
font-size: var(--text-xs);
line-height: 1.6;
@@ -570,6 +862,29 @@ canvas#nexus-canvas {
.chat-msg-prefix {
font-weight: 700;
}
.chat-msg-kimi .chat-msg-prefix { color: var(--color-secondary); }
.chat-msg-claude .chat-msg-prefix { color: var(--color-gold); }
.chat-msg-perplexity .chat-msg-prefix { color: #4488ff; }
/* Tool Output Styling */
.chat-msg-tool {
background: rgba(0, 0, 0, 0.3);
border-left: 2px solid #ffd700;
font-size: 11px;
padding: 8px;
margin: 4px 0;
border-radius: 4px;
}
.tool-call { border-left-color: #ffd700; }
.tool-result { border-left-color: #4af0c0; }
.tool-content {
font-family: 'JetBrains Mono', monospace;
white-space: pre-wrap;
word-break: break-all;
opacity: 0.8;
margin: 4px 0 0 0;
color: #a0b8d0;
}
.chat-msg-system .chat-msg-prefix { color: var(--color-text-muted); }
.chat-msg-timmy .chat-msg-prefix { color: var(--color-primary); }
.chat-msg-user .chat-msg-prefix { color: var(--color-gold); }
@@ -625,50 +940,110 @@ canvas#nexus-canvas {
color: var(--color-primary);
}
/* === BITCOIN BLOCK HEIGHT === */
#block-height-display {
position: fixed;
bottom: 12px;
right: 12px;
z-index: 20;
font-family: var(--font-body);
font-size: 11px;
letter-spacing: 0.15em;
color: var(--color-primary);
background: rgba(0, 0, 8, 0.7);
border: 1px solid var(--color-secondary);
padding: 4px 10px;
pointer-events: none;
white-space: nowrap;
}
.block-height-label {
color: var(--color-text-muted);
margin-right: 6px;
font-size: 10px;
}
#block-height-value {
color: var(--color-primary);
}
#block-height-display.fresh #block-height-value {
animation: block-flash 0.6s ease-out;
}
@keyframes block-flash {
0% { color: #ffffff; text-shadow: 0 0 8px #4488ff; }
100% { color: var(--color-primary); text-shadow: none; }
}
/* Mobile adjustments */
@media (max-width: 1024px) {
.chat-panel {
width: 320px;
}
.hud-agent-log {
width: 220px;
}
}
@media (max-width: 768px) {
.chat-panel {
width: 300px;
bottom: var(--space-2);
right: var(--space-2);
}
.hud-agent-log {
display: none;
}
.hud-location {
font-size: var(--text-xs);
}
}
@media (max-width: 480px) {
.chat-panel {
width: calc(100vw - 32px);
right: var(--space-4);
bottom: var(--space-4);
width: calc(100vw - 24px);
right: 12px;
bottom: 12px;
}
.hud-controls {
display: none;
}
.loader-title {
font-size: var(--text-xl);
}
}
/* === GOFAI HUD STYLING === */
.gofai-hud {
position: fixed;
left: 20px;
top: 80px;
display: flex;
flex-direction: column;
gap: 10px;
pointer-events: none;
z-index: 100;
}
.hud-panel {
width: 280px;
background: rgba(5, 5, 16, 0.8);
border: 1px solid rgba(74, 240, 192, 0.2);
border-left: 3px solid #4af0c0;
padding: 8px;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: #e0f0ff;
pointer-events: auto;
}
.panel-header {
font-size: 10px;
font-weight: 700;
color: #4af0c0;
margin-bottom: 6px;
letter-spacing: 1px;
border-bottom: 1px solid rgba(74, 240, 192, 0.1);
padding-bottom: 2px;
}
.panel-content {
max-height: 120px;
overflow-y: auto;
}
.symbolic-log-entry { margin-bottom: 4px; border-bottom: 1px solid rgba(255,255,255,0.05); padding-bottom: 2px; }
.symbolic-rule { color: #7b5cff; display: block; }
.symbolic-outcome { color: #4af0c0; font-weight: 600; }
.blackboard-entry { font-size: 10px; margin-bottom: 2px; }
.bb-source { color: #ffd700; opacity: 0.7; }
.bb-key { color: #7b5cff; }
.bb-value { color: #fff; }
.planner-step { color: #4af0c0; margin-bottom: 2px; }
.step-num { opacity: 0.5; }
.cbr-match { color: #ffd700; font-weight: 700; margin-bottom: 2px; }
.cbr-action { color: #4af0c0; }
.neuro-bridge-entry { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
.neuro-icon { font-size: 14px; }
.neuro-concept { color: #7b5cff; font-weight: 600; }
.meta-stat { margin-bottom: 2px; display: flex; justify-content: space-between; }
.calibrator-entry { font-size: 10px; display: flex; gap: 8px; }
.cal-label { color: #ffd700; }
.cal-val { color: #4af0c0; }
.cal-err { color: #ff4466; opacity: 0.8; }
.nostr-pubkey { color: #ffd700; }
.nostr-status { color: #4af0c0; font-weight: 600; }
.l402-status { color: #ff4466; font-weight: 600; }
.l402-msg { color: #fff; }

View File

@@ -1,30 +0,0 @@
// @ts-check
const { defineConfig } = require('@playwright/test');
module.exports = defineConfig({
testDir: '.',
timeout: 30000,
retries: 1,
use: {
headless: true,
viewport: { width: 1280, height: 720 },
// WebGL needs a real GPU context — use chromium with GPU
launchOptions: {
args: [
'--use-gl=angle',
'--use-angle=swiftshader', // Software WebGL for CI
'--enable-webgl',
],
},
},
projects: [
{ name: 'chromium', use: { browserName: 'chromium' } },
],
// Local server
webServer: {
command: 'python3 -m http.server 8888',
port: 8888,
cwd: '..',
reuseExistingServer: true,
},
});

View File

@@ -1,43 +0,0 @@
#!/usr/bin/env bash
# run-smoke.sh — Run Nexus smoke tests locally. No LLM. No cloud.
#
# Usage:
# ./tests/run-smoke.sh # Run all smoke tests
# ./tests/run-smoke.sh --headed # Run with visible browser (debug)
# ./tests/run-smoke.sh --grep "HUD" # Run specific test group
#
# Requirements: playwright installed (npm i -D @playwright/test)
# First run: npx playwright install chromium
set -euo pipefail
cd "$(dirname "$0")/.."
# Ensure playwright is available
if ! command -v npx &>/dev/null; then
echo "ERROR: npx not found. Install Node.js."
exit 1
fi
# Install playwright test if needed
if [ ! -d node_modules/@playwright ]; then
echo "Installing playwright test runner..."
npm install --save-dev @playwright/test 2>&1 | tail -3
fi
# Ensure browser is installed
npx playwright install chromium --with-deps 2>/dev/null || true
# Run tests
echo ""
echo "=== NEXUS SMOKE TESTS ==="
echo ""
npx playwright test tests/smoke.spec.js -c tests/playwright.config.js "$@"
EXIT=$?
echo ""
if [ $EXIT -eq 0 ]; then
echo "✅ ALL SMOKE TESTS PASSED"
else
echo "❌ SOME TESTS FAILED (exit $EXIT)"
fi
exit $EXIT

View File

@@ -1,274 +0,0 @@
// @ts-check
/**
* Nexus Smoke Tests — Zero LLM, pure headless browser.
*
* Tests that the 3D world renders, modules load, and basic interaction works.
* Run: npx playwright test tests/smoke.spec.js
* Requires: a local server serving the nexus (e.g., python3 -m http.server 8888)
*/
const { test, expect } = require('@playwright/test');
const BASE_URL = process.env.NEXUS_URL || 'http://localhost:8888';
// --- RENDERING TESTS ---
test.describe('World Renders', () => {
test('index.html loads without errors', async ({ page }) => {
const errors = [];
page.on('pageerror', err => errors.push(err.message));
const response = await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
expect(response.status()).toBe(200);
// Give Three.js a moment to initialize
await page.waitForTimeout(2000);
// No fatal JS errors
const fatalErrors = errors.filter(e =>
!e.includes('ambient.mp3') && // missing audio file is OK
!e.includes('favicon') &&
!e.includes('serviceWorker')
);
expect(fatalErrors).toEqual([]);
});
test('canvas element exists (Three.js rendered)', async ({ page }) => {
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
// Three.js creates a <canvas> element
const canvas = await page.locator('canvas');
await expect(canvas).toBeVisible();
// Canvas should have non-zero dimensions
const box = await canvas.boundingBox();
expect(box.width).toBeGreaterThan(100);
expect(box.height).toBeGreaterThan(100);
});
test('canvas is not all black (scene has content)', async ({ page }) => {
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(4000);
// Sample pixels from the canvas
const hasContent = await page.evaluate(() => {
const canvas = document.querySelector('canvas');
if (!canvas) return false;
const ctx = canvas.getContext('webgl2') || canvas.getContext('webgl');
if (!ctx) return false;
// Read a block of pixels from center
const w = canvas.width;
const h = canvas.height;
const pixels = new Uint8Array(4 * 100);
ctx.readPixels(
Math.floor(w / 2) - 5, Math.floor(h / 2) - 5,
10, 10, ctx.RGBA, ctx.UNSIGNED_BYTE, pixels
);
// Check if at least some pixels are non-black
let nonBlack = 0;
for (let i = 0; i < pixels.length; i += 4) {
if (pixels[i] > 5 || pixels[i + 1] > 5 || pixels[i + 2] > 5) {
nonBlack++;
}
}
return nonBlack > 5; // At least 5 non-black pixels in the sample
});
expect(hasContent).toBe(true);
});
test('WebGL context is healthy', async ({ page }) => {
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(2000);
const glInfo = await page.evaluate(() => {
const canvas = document.querySelector('canvas');
if (!canvas) return { error: 'no canvas' };
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
if (!gl) return { error: 'no webgl context' };
return {
renderer: gl.getParameter(gl.RENDERER),
vendor: gl.getParameter(gl.VENDOR),
isLost: gl.isContextLost(),
};
});
expect(glInfo.error).toBeUndefined();
expect(glInfo.isLost).toBe(false);
});
});
// --- MODULE LOADING TESTS ---
test.describe('Modules Load', () => {
test('all ES modules resolve (no import errors)', async ({ page }) => {
const moduleErrors = [];
page.on('pageerror', err => {
if (err.message.includes('import') || err.message.includes('module') ||
err.message.includes('export') || err.message.includes('Cannot find')) {
moduleErrors.push(err.message);
}
});
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
expect(moduleErrors).toEqual([]);
});
test('Three.js loaded from CDN', async ({ page }) => {
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(2000);
const hasThree = await page.evaluate(() => {
// Check if THREE is accessible (it's imported as ES module, so check via scene)
const canvas = document.querySelector('canvas');
return !!canvas;
});
expect(hasThree).toBe(true);
});
});
// --- HUD ELEMENTS ---
test.describe('HUD Elements', () => {
test('block height display exists', async ({ page }) => {
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
const blockDisplay = page.locator('#block-height-display');
await expect(blockDisplay).toBeAttached();
});
test('weather HUD exists', async ({ page }) => {
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
const weather = page.locator('#weather-hud');
await expect(weather).toBeAttached();
});
test('audio toggle exists', async ({ page }) => {
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
const btn = page.locator('#audio-toggle');
await expect(btn).toBeAttached();
});
test('sovereignty message exists', async ({ page }) => {
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
const msg = page.locator('#sovereignty-msg');
await expect(msg).toBeAttached();
});
test('oath overlay exists', async ({ page }) => {
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
const oath = page.locator('#oath-overlay');
await expect(oath).toBeAttached();
});
});
// --- INTERACTION TESTS ---
test.describe('Interactions', () => {
test('mouse movement updates camera (parallax)', async ({ page }) => {
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
// Get initial canvas snapshot
const canvas = page.locator('canvas');
const box = await canvas.boundingBox();
// Take screenshot before mouse move
const before = await page.screenshot({ clip: { x: box.x, y: box.y, width: 100, height: 100 } });
// Move mouse significantly
await page.mouse.move(box.x + box.width * 0.8, box.y + box.height * 0.2);
await page.waitForTimeout(1500);
// Take screenshot after
const after = await page.screenshot({ clip: { x: box.x, y: box.y, width: 100, height: 100 } });
// Screenshots should differ (camera moved)
expect(Buffer.compare(before, after)).not.toBe(0);
});
test('Tab key toggles overview mode', async ({ page }) => {
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(3000);
// Press Tab for overview
await page.keyboard.press('Tab');
await page.waitForTimeout(1000);
// Overview indicator should be visible
const indicator = page.locator('#overview-indicator');
// It should have some visibility (either display or opacity)
const isVisible = await indicator.evaluate(el => {
const style = window.getComputedStyle(el);
return style.display !== 'none' && parseFloat(style.opacity) > 0;
});
// Press Tab again to exit
await page.keyboard.press('Tab');
expect(isVisible).toBe(true);
});
test('animation loop is running (requestAnimationFrame)', async ({ page }) => {
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(2000);
// Check that frames are being rendered by watching a timestamp
const frameCount = await page.evaluate(() => {
return new Promise(resolve => {
let count = 0;
const start = performance.now();
function tick() {
count++;
if (performance.now() - start > 500) {
resolve(count);
} else {
requestAnimationFrame(tick);
}
}
requestAnimationFrame(tick);
});
});
// Should get at least 10 frames in 500ms (20+ FPS)
expect(frameCount).toBeGreaterThan(10);
});
});
// --- DATA / API TESTS ---
test.describe('Data Loading', () => {
test('portals.json loads', async ({ page }) => {
const response = await page.goto(`${BASE_URL}/portals.json`);
expect(response.status()).toBe(200);
const data = await response.json();
expect(Array.isArray(data) || typeof data === 'object').toBe(true);
});
test('sovereignty-status.json loads', async ({ page }) => {
const response = await page.goto(`${BASE_URL}/sovereignty-status.json`);
expect(response.status()).toBe(200);
});
test('style.css loads', async ({ page }) => {
const response = await page.goto(`${BASE_URL}/style.css`);
expect(response.status()).toBe(200);
});
test('manifest.json is valid', async ({ page }) => {
const response = await page.goto(`${BASE_URL}/manifest.json`);
expect(response.status()).toBe(200);
const data = await response.json();
expect(data.name || data.short_name).toBeTruthy();
});
});

View File

@@ -0,0 +1,56 @@
from nexus.evennia_event_adapter import actor_located, command_issued, command_result, room_snapshot, session_bound
from nexus.perception_adapter import ws_to_perception
def test_session_bound_schema():
event = session_bound("sess-1")
assert event["type"] == "evennia.session_bound"
assert event["hermes_session_id"] == "sess-1"
assert event["evennia_account"] == "Timmy"
def test_room_snapshot_schema():
event = room_snapshot(
room_key="Chapel",
title="Chapel",
desc="Quiet room.",
exits=[{"key": "courtyard", "destination_id": "Courtyard", "destination_key": "Courtyard"}],
objects=[{"id": "Book of the Soul", "key": "Book of the Soul", "short_desc": "A doctrinal anchor."}],
)
assert event["type"] == "evennia.room_snapshot"
assert event["title"] == "Chapel"
assert event["objects"][0]["key"] == "Book of the Soul"
def test_evennia_room_snapshot_becomes_perception():
perception = ws_to_perception(
room_snapshot(
room_key="Workshop",
title="Workshop",
desc="Tools everywhere.",
exits=[{"key": "courtyard", "destination_id": "Courtyard", "destination_key": "Courtyard"}],
objects=[{"id": "Workbench", "key": "Workbench", "short_desc": "A broad workbench."}],
)
)
assert perception is not None
assert "Workshop" in perception.description
assert "Workbench" in perception.description
def test_evennia_command_result_becomes_perception():
perception = ws_to_perception(command_result("sess-2", "Timmy", "look Book of the Soul", "Book of the Soul. A doctrinal anchor.", True))
assert perception is not None
assert "succeeded" in perception.description.lower()
assert "Book of the Soul" in perception.description
def test_evennia_actor_located_becomes_perception():
perception = ws_to_perception(actor_located("Timmy", "Gate"))
assert perception is not None
assert "Gate" in perception.description
def test_evennia_command_issued_schema():
event = command_issued("sess-3", "Timmy", "chapel")
assert event["type"] == "evennia.command_issued"
assert event["command_text"] == "chapel"

View File

@@ -0,0 +1,36 @@
from nexus.evennia_ws_bridge import clean_lines, normalize_event, parse_room_output, strip_ansi
def test_strip_ansi_removes_escape_codes():
assert strip_ansi('\x1b[1mGate\x1b[0m') == 'Gate'
def test_parse_room_output_extracts_room_exits_and_objects():
parsed = parse_room_output('\x1b[1mChapel\x1b[0m\nQuiet room.\nExits: courtyard\nYou see: a Book of the Soul and a Prayer Wall')
assert parsed['title'] == 'Chapel'
assert parsed['exits'][0]['key'] == 'courtyard'
keys = [obj['key'] for obj in parsed['objects']]
assert 'Book of the Soul' in keys
assert 'Prayer Wall' in keys
def test_normalize_connect_emits_session_and_room_events():
events = normalize_event({'event': 'connect', 'actor': 'Timmy', 'output': 'Gate\nA threshold.\nExits: enter'}, 'sess1')
types = [event['type'] for event in events]
assert 'evennia.session_bound' in types
assert 'evennia.actor_located' in types
assert 'evennia.room_snapshot' in types
def test_normalize_command_emits_command_and_snapshot():
events = normalize_event({'event': 'command', 'actor': 'timmy', 'command': 'courtyard', 'output': 'Courtyard\nOpen court.\nExits: gate, workshop\nYou see: a Map Table'}, 'sess2')
types = [event['type'] for event in events]
assert types[0] == 'evennia.command_issued'
assert 'evennia.command_result' in types
assert 'evennia.room_snapshot' in types
def test_normalize_failed_command_marks_failure():
events = normalize_event({'event': 'command', 'actor': 'timmy', 'command': 'workshop', 'output': "Command 'workshop' is not available."}, 'sess3')
result = [event for event in events if event['type'] == 'evennia.command_result'][0]
assert result['success'] is False

View File

@@ -0,0 +1,45 @@
import json
from pathlib import Path
REQUIRED_TOP_LEVEL_KEYS = {
"id",
"name",
"description",
"status",
"portal_type",
"world_category",
"environment",
"access_mode",
"readiness_state",
"telemetry_source",
"owner",
"destination",
}
REQUIRED_DESTINATION_KEYS = {"type", "action_label"}
def test_portals_json_uses_expanded_registry_schema() -> None:
portals = json.loads(Path("portals.json").read_text())
assert portals, "portals.json should define at least one portal"
for portal in portals:
assert REQUIRED_TOP_LEVEL_KEYS.issubset(portal.keys())
assert REQUIRED_DESTINATION_KEYS.issubset(portal["destination"].keys())
def test_gameportal_protocol_documents_new_metadata_fields_and_migration() -> None:
protocol = Path("GAMEPORTAL_PROTOCOL.md").read_text()
for term in [
"portal_type",
"world_category",
"environment",
"access_mode",
"readiness_state",
"telemetry_source",
"owner",
"Migration from legacy portal definitions",
]:
assert term in protocol

35
tests/test_repo_truth.py Normal file
View File

@@ -0,0 +1,35 @@
from pathlib import Path
def test_readme_states_repo_truth_and_single_canonical_3d_repo() -> None:
readme = Path("README.md").read_text()
assert "current `main` does not ship a browser 3D world" in readme
assert "Timmy_Foundation/the-nexus is the only canonical 3D repo" in readme
assert "/Users/apayne/the-matrix" in readme
assert "npx serve . -l 3000" not in readme
def test_claude_doc_matches_current_repo_truth() -> None:
claude = Path("CLAUDE.md").read_text()
assert "Do not describe this repo as a live browser app on `main`." in claude
assert "Timmy_Foundation/the-nexus is the only canonical 3D repo." in claude
assert "LEGACY_MATRIX_AUDIT.md" in claude
def test_legacy_matrix_audit_exists_and_names_rescue_targets() -> None:
audit = Path("LEGACY_MATRIX_AUDIT.md").read_text()
for term in [
"agent-defs.js",
"agents.js",
"avatar.js",
"ui.js",
"websocket.js",
"transcript.js",
"ambient.js",
"satflow.js",
"economy.js",
]:
assert term in audit