CAPT Agent commited on
Commit
d2b9430
ยท
1 Parent(s): eb3bf93

๐Ÿ”ฅ Connect to real CAPT backend

Browse files
Files changed (4) hide show
  1. __pycache__/app.cpython-314.pyc +0 -0
  2. app.py +164 -24
  3. capt_backend.py +351 -0
  4. requirements.txt +1 -0
__pycache__/app.cpython-314.pyc ADDED
Binary file (24.2 kB). View file
 
app.py CHANGED
@@ -1,15 +1,56 @@
1
  #!/usr/bin/env python3
2
- """GIT HOLOGRAM โ€” Visualize any GitHub repo as causal threads."""
 
 
 
3
 
4
  import subprocess
5
  import tempfile
6
  import shutil
7
  import os
 
 
8
  from datetime import datetime
9
  from typing import List, Dict
10
 
11
  import gradio as gr
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  # โ”€โ”€ Git logic โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
14
 
15
  def get_git_commits(repo_path: str, max_commits: int = 50) -> List[Dict]:
@@ -156,24 +197,33 @@ def render_summary(commits: List[Dict]) -> str:
156
 
157
  # โ”€โ”€ Repo fetch โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
158
 
159
- def fetch_repo(repo_url: str, max_commits: int) -> str:
160
- """Clone shallowly, run hologram, clean up."""
161
  repo_url = repo_url.strip()
162
  if not repo_url:
163
- return "<p style='color:#f66'>Please enter a repo URL.</p>"
164
 
165
- # Normalize URL to git clone format
166
  if repo_url.startswith("http"):
167
  if not repo_url.endswith(".git"):
168
  repo_url += ".git"
169
  elif repo_url.startswith("git@"):
170
- pass # already SSH format
171
  else:
172
- # Assume GitHub username/repo shorthand
173
  if "/" not in repo_url:
174
  repo_url = f"knowurknot/{repo_url}"
175
  repo_url = f"https://github.com/{repo_url}.git"
176
 
 
 
 
 
 
 
 
 
 
 
177
  tmpdir = tempfile.mkdtemp(prefix="hologram_")
178
  try:
179
  depth = max(max_commits + 10, 50)
@@ -182,25 +232,61 @@ def fetch_repo(repo_url: str, max_commits: int) -> str:
182
  capture_output=True, text=True, timeout=60,
183
  )
184
  if result.returncode != 0:
185
- return f"<p style='color:#f66'>Clone failed: {result.stderr[:300]}</p>"
186
 
187
  commits = get_git_commits(tmpdir, max_commits)
188
  if not commits:
189
- return "<p style='color:#888'>No commits found in this repo.</p>"
190
 
191
  hologram_html = render_hologram(commits)
192
  summary_html = render_summary(commits)
 
193
 
194
- return f"""
195
- <div class="summary-card">{summary_html}</div>
196
- {hologram_html}
197
- """
198
  except subprocess.TimeoutExpired:
199
- return "<p style='color:#f66'>Clone timed out โ€” repo may be too large.</p>"
200
  except Exception as e:
201
- return f"<p style='color:#f66'>Error: {str(e)[:300]}</p>"
202
  finally:
203
- shutil.rmtree(tmpdir, ignore_errors=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
 
206
  # โ”€โ”€ Gradio UI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -220,17 +306,30 @@ CUSTOM_CSS = """
220
  border: 1px solid #2a2a40; color: #ccc; font-size: 14px; line-height: 1.8;
221
  }
222
  .thread { letter-spacing: 1px; }
 
 
 
 
 
 
223
  """
224
 
225
  DESCRIPTION = """
226
- # ๐ŸŒ€ GIT HOLOGRAM
227
- Visualize any Git repository as **causal threads of intention**. Each commit is a point in spacetime where thought crystallized into code.
228
-
229
- **Enter a GitHub repo URL** (or `owner/repo` shorthand) and watch the hologram form.
230
  """
231
 
232
  with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft()) as demo:
233
  gr.Markdown(DESCRIPTION)
 
 
 
 
 
 
 
234
  with gr.Row():
235
  repo_input = gr.Textbox(
236
  label="Repo URL or `owner/repo`",
@@ -242,14 +341,55 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft()) as demo:
242
  label="Max commits", scale=1,
243
  )
244
  generate_btn = gr.Button("๐Ÿ”ฎ Generate Hologram", variant="primary", scale=1)
245
- output = gr.HTML(label="Hologram")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
 
247
  generate_btn.click(
248
- fn=fetch_repo, inputs=[repo_input, max_commits_input], outputs=output,
 
 
249
  )
250
  repo_input.submit(
251
- fn=fetch_repo, inputs=[repo_input, max_commits_input], outputs=output,
 
 
252
  )
253
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  if __name__ == "__main__":
255
- demo.launch()
 
1
  #!/usr/bin/env python3
2
+ """
3
+ GIT HOLOGRAM โ€” Visualize any GitHub repo as causal threads.
4
+ Now with real bioCAPT integration: analysis, memory recall, and security audit.
5
+ """
6
 
7
  import subprocess
8
  import tempfile
9
  import shutil
10
  import os
11
+ import hashlib
12
+ import json
13
  from datetime import datetime
14
  from typing import List, Dict
15
 
16
  import gradio as gr
17
 
18
+ from capt_backend import CAPTBackend, health_banner_html, BRAINS
19
+
20
+ backend = CAPTBackend()
21
+
22
+ # โ”€โ”€ Persistent cache dir โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
23
+ CACHE_DIR = "/tmp/git-hologram-cache"
24
+ os.makedirs(CACHE_DIR, exist_ok=True)
25
+
26
+ def _repo_key(repo_url: str) -> str:
27
+ return hashlib.sha256(repo_url.encode()).hexdigest()[:16]
28
+
29
+ def _get_cached(repo_url: str):
30
+ key = _repo_key(repo_url)
31
+ meta_path = os.path.join(CACHE_DIR, f"{key}.json")
32
+ if os.path.exists(meta_path):
33
+ try:
34
+ with open(meta_path) as f:
35
+ meta = json.load(f)
36
+ repo_path = os.path.join(CACHE_DIR, key)
37
+ if os.path.isdir(repo_path):
38
+ return repo_path, meta
39
+ except Exception:
40
+ pass
41
+ return None, None
42
+
43
+ def _set_cached(repo_url: str, repo_path: str, meta: dict):
44
+ key = _repo_key(repo_url)
45
+ dest = os.path.join(CACHE_DIR, key)
46
+ if dest != repo_path and os.path.exists(repo_path):
47
+ if os.path.exists(dest):
48
+ shutil.rmtree(dest, ignore_errors=True)
49
+ shutil.move(repo_path, dest)
50
+ with open(os.path.join(CACHE_DIR, f"{key}.json"), "w") as f:
51
+ json.dump(meta, f)
52
+ return dest
53
+
54
  # โ”€โ”€ Git logic โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
55
 
56
  def get_git_commits(repo_path: str, max_commits: int = 50) -> List[Dict]:
 
197
 
198
  # โ”€โ”€ Repo fetch โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
199
 
200
+ def fetch_repo(repo_url: str, max_commits: int) -> tuple:
201
+ """Clone shallowly (with cache), run hologram, return (html, repo_url, status)."""
202
  repo_url = repo_url.strip()
203
  if not repo_url:
204
+ return "<p style='color:#f66'>Please enter a repo URL.</p>", "", "empty"
205
 
206
+ # Normalize URL
207
  if repo_url.startswith("http"):
208
  if not repo_url.endswith(".git"):
209
  repo_url += ".git"
210
  elif repo_url.startswith("git@"):
211
+ pass
212
  else:
 
213
  if "/" not in repo_url:
214
  repo_url = f"knowurknot/{repo_url}"
215
  repo_url = f"https://github.com/{repo_url}.git"
216
 
217
+ # Check cache
218
+ cached_path, meta = _get_cached(repo_url)
219
+ if cached_path:
220
+ commits = get_git_commits(cached_path, max_commits)
221
+ if commits:
222
+ hologram_html = render_hologram(commits)
223
+ summary_html = render_summary(commits)
224
+ full_html = f'<div class="summary-card">{summary_html}</div>{hologram_html}'
225
+ return full_html, repo_url, "cached"
226
+
227
  tmpdir = tempfile.mkdtemp(prefix="hologram_")
228
  try:
229
  depth = max(max_commits + 10, 50)
 
232
  capture_output=True, text=True, timeout=60,
233
  )
234
  if result.returncode != 0:
235
+ return f"<p style='color:#f66'>Clone failed: {result.stderr[:300]}</p>", repo_url, "error"
236
 
237
  commits = get_git_commits(tmpdir, max_commits)
238
  if not commits:
239
+ return "<p style='color:#888'>No commits found in this repo.</p>", repo_url, "empty"
240
 
241
  hologram_html = render_hologram(commits)
242
  summary_html = render_summary(commits)
243
+ full_html = f'<div class="summary-card">{summary_html}</div>{hologram_html}'
244
 
245
+ # Cache it
246
+ _set_cached(repo_url, tmpdir, {"url": repo_url, "commits": len(commits), "cached_at": datetime.now().isoformat()})
247
+ return full_html, repo_url, "ok"
 
248
  except subprocess.TimeoutExpired:
249
+ return "<p style='color:#f66'>Clone timed out โ€” repo may be too large.</p>", repo_url, "timeout"
250
  except Exception as e:
251
+ return f"<p style='color:#f66'>Error: {str(e)[:300]}</p>", repo_url, "error"
252
  finally:
253
+ if os.path.exists(tmpdir):
254
+ shutil.rmtree(tmpdir, ignore_errors=True)
255
+
256
+
257
+ # โ”€โ”€ CAPT Analysis โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
258
+
259
+ import asyncio
260
+
261
+ async def capt_analyze(repo_url: str, analysis_type: str) -> str:
262
+ if not repo_url.strip():
263
+ return "โš ๏ธ Generate a hologram first."
264
+ mid = "capt-core-01"
265
+ try:
266
+ loop = asyncio.get_event_loop()
267
+ if analysis_type == "codebase":
268
+ prompt = f"Analyze this codebase: {repo_url}\n\nProvide architectural insights, tech stack, complexity assessment, and improvement suggestions."
269
+ result = await loop.run_in_executor(None, lambda: backend.cogitate(prompt))
270
+ resp = result.get("response", result.get("content", "No response"))
271
+ fb = result.get("_fallback", False) or "error" in result
272
+ badge = "๐Ÿ”ด FALLBACK" if fb else "๐ŸŸข LIVE"
273
+ return f"**{badge}** ยท Codebase Analysis\n\n{resp}"
274
+ elif analysis_type == "commits":
275
+ result = await loop.run_in_executor(None, lambda: backend.echo_recall(f"Commit patterns for {repo_url}"))
276
+ resp = result.get("response", result.get("content", json.dumps(result, indent=2)))
277
+ fb = result.get("_fallback", False) or "error" in result
278
+ badge = "๐Ÿ”ด FALLBACK" if fb else "๐ŸŸข LIVE"
279
+ return f"**{badge}** ยท Commit Pattern Recall\n\n{resp}"
280
+ elif analysis_type == "security":
281
+ prompt = f"Security audit of repository {repo_url}. Check for secrets, vulnerabilities, dependency risks, and supply-chain exposure."
282
+ result = await loop.run_in_executor(None, lambda: backend.immu_scan(prompt))
283
+ resp = result.get("response", result.get("content", json.dumps(result, indent=2)))
284
+ fb = result.get("_fallback", False) or "error" in result
285
+ badge = "๐Ÿ”ด FALLBACK" if fb else "๐ŸŸข LIVE"
286
+ return f"**{badge}** ยท Security Audit (ImmuScan)\n\n{resp}"
287
+ return "Unknown analysis type."
288
+ except Exception as e:
289
+ return f"๐Ÿ”ด Error: {str(e)[:300]}"
290
 
291
 
292
  # โ”€โ”€ Gradio UI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
306
  border: 1px solid #2a2a40; color: #ccc; font-size: 14px; line-height: 1.8;
307
  }
308
  .thread { letter-spacing: 1px; }
309
+ .result-box {background:#0d0d14; border:1px solid #1a1a2e; border-radius:10px; padding:14px; min-height:120px}
310
+ #header {text-align: center; margin-bottom: 16px}
311
+ #header h1 {font-size: 2.2em; background: linear-gradient(135deg, #667eea, #764ba2); -webkit-background-clip: text; -webkit-text-fill-color: transparent}
312
+ @media (max-width: 768px) {
313
+ #header h1 {font-size: 1.6em !important}
314
+ }
315
  """
316
 
317
  DESCRIPTION = """
318
+ <div id="header">
319
+ <h1>๐ŸŒ€ GIT HOLOGRAM + CAPT</h1>
320
+ <p style="color:#888">Visualize any Git repository as <strong>causal threads of intention</strong>. Then analyze it with the bioCAPT cognitive backend.</p>
321
+ </div>
322
  """
323
 
324
  with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft()) as demo:
325
  gr.Markdown(DESCRIPTION)
326
+
327
+ # Health banner
328
+ health_html = gr.HTML(health_banner_html(backend))
329
+ refresh_health_btn = gr.Button("๐Ÿ”„ Refresh Health", size="sm")
330
+ refresh_health_btn.click(fn=lambda: health_banner_html(backend), outputs=health_html)
331
+
332
+ # Inputs
333
  with gr.Row():
334
  repo_input = gr.Textbox(
335
  label="Repo URL or `owner/repo`",
 
341
  label="Max commits", scale=1,
342
  )
343
  generate_btn = gr.Button("๐Ÿ”ฎ Generate Hologram", variant="primary", scale=1)
344
+
345
+ # Hologram output
346
+ hologram_output = gr.HTML(label="Hologram")
347
+
348
+ # Hidden state to track current repo URL
349
+ current_repo = gr.State("")
350
+
351
+ # CAPT Analysis section (appears after hologram)
352
+ with gr.Row():
353
+ analyze_select = gr.Dropdown(
354
+ ["codebase", "commits", "security"],
355
+ label="๐Ÿ“ก CAPT Analysis",
356
+ value="codebase",
357
+ scale=1,
358
+ )
359
+ analyze_btn = gr.Button("๐Ÿง  Analyze with CAPT", variant="primary", scale=0, min_width=180)
360
+
361
+ capt_output = gr.Markdown(label="CAPT Analysis Result", elem_classes=["result-box"])
362
+
363
+ def on_generate(repo_url, max_commits):
364
+ html, url, status = fetch_repo(repo_url, max_commits)
365
+ if status in ("cached", "ok"):
366
+ return html, url, f"โœ… Status: {status.upper()} ยท Repo cached"
367
+ return html, url, f"โš ๏ธ Status: {status.upper()}"
368
 
369
  generate_btn.click(
370
+ fn=on_generate,
371
+ inputs=[repo_input, max_commits_input],
372
+ outputs=[hologram_output, current_repo, capt_output],
373
  )
374
  repo_input.submit(
375
+ fn=on_generate,
376
+ inputs=[repo_input, max_commits_input],
377
+ outputs=[hologram_output, current_repo, capt_output],
378
  )
379
 
380
+ analyze_btn.click(
381
+ fn=capt_analyze,
382
+ inputs=[current_repo, analyze_select],
383
+ outputs=capt_output,
384
+ )
385
+
386
+ # Footer
387
+ gr.Markdown("""
388
+ ---
389
+ <div style="text-align:center;color:#6b6b7b;font-size:0.8rem">
390
+ Git Hologram ยท Powered by bioCAPT ยท Connected to real CAPT Cloudflare Workers
391
+ </div>
392
+ """)
393
+
394
  if __name__ == "__main__":
395
+ demo.launch()
capt_backend.py ADDED
@@ -0,0 +1,351 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ๐Ÿง  CAPT Backend Connector โ€” Universal module for all HF Spaces
3
+ Provides real CAPT/bioCAPT cognition via Cloudflare Workers with graceful fallbacks.
4
+
5
+ Usage:
6
+ from capt_backend import CAPTBackend, BrainRouter
7
+
8
+ backend = CAPTBackend()
9
+ result = backend.cogitate("What is consciousness?")
10
+ print(result["response"])
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import os
17
+ import time
18
+ from dataclasses import dataclass, field
19
+ from typing import Any, Optional
20
+ import requests
21
+
22
+ # โ”€โ”€โ”€ Configuration โ”€โ”€โ”€
23
+ _DEFAULT_WORKER = os.getenv("CAPT_WORKER_URL", "https://capt-brain-01.knowurknottty.workers.dev")
24
+ _OPENROUTER_KEY = os.getenv("OPENROUTER_API_KEY", "")
25
+
26
+ BRAINS = {
27
+ "capt-core-01": {
28
+ "name": "CAPT Core Alpha",
29
+ "icon": "๐Ÿง ",
30
+ "worker": "https://capt-brain-01.knowurknottty.workers.dev",
31
+ "specialties": ["general", "analysis", "reasoning", "constitution", "ethics"],
32
+ },
33
+ "biocapt-core-01": {
34
+ "name": "bioCAPT Genesis",
35
+ "icon": "๐Ÿงฌ",
36
+ "worker": "https://capt-brain-02-biocapt.knowurknottty.workers.dev",
37
+ "specialties": ["memory", "learning", "adapt", "bio", "organic"],
38
+ },
39
+ "frankencapt-core-01": {
40
+ "name": "FrankenCAPT Chimera",
41
+ "icon": "โš—๏ธ",
42
+ "worker": "https://capt-brain-03-frankencapt.knowurknottty.workers.dev",
43
+ "specialties": ["modular", "combine", "fuse", "skill", "build"],
44
+ },
45
+ "synthesis-core-01": {
46
+ "name": "Synthesis Nexus",
47
+ "icon": "๐Ÿ”ฎ",
48
+ "worker": "https://capt-brain-04-synthesis.knowurknottty.workers.dev",
49
+ "specialties": ["synthesize", "consensus", "meta", "orchestrate"],
50
+ },
51
+ "council-core-01": {
52
+ "name": "LLM Council",
53
+ "icon": "๐Ÿ›๏ธ",
54
+ "worker": "https://capt-brain-05-council.knowurknottty.workers.dev",
55
+ "specialties": ["council", "vote", "debate", "deliberate"],
56
+ },
57
+ }
58
+
59
+ SEVEN_LAWS = [
60
+ ("LAW_1_HUMAN_DIGNITY", "Respect human autonomy and dignity", "#00e676"),
61
+ ("LAW_2_TRUTHFULNESS", "Do not deceive or mislead", "#69f0ae"),
62
+ ("LAW_3_NON_MALEFICENCE", "Do no harm", "#ff5252"),
63
+ ("LAW_4_PRIVACY", "Protect personal data", "#448aff"),
64
+ ("LAW_5_TRANSPARENCY", "Explain reasoning when asked", "#ffd740"),
65
+ ("LAW_6_ACCOUNTABILITY", "Own mistakes, allow audit", "#ffab40"),
66
+ ("LAW_7_BENEFICENCE", "Act for human flourishing", "#e040fb"),
67
+ ]
68
+
69
+ @dataclass
70
+ class BrainHealth:
71
+ brain_id: str
72
+ name: str
73
+ icon: str
74
+ online: bool = False
75
+ status: str = "unknown"
76
+ latency_ms: int = 0
77
+ last_check: float = 0
78
+ error: str = ""
79
+
80
+
81
+ class CAPTBackend:
82
+ """Universal backend connector for CAPT cognitive architecture."""
83
+
84
+ def __init__(self, worker_url: str = _DEFAULT_WORKER, timeout: int = 60):
85
+ self.worker_url = worker_url.rstrip("/")
86
+ self.timeout = timeout
87
+ self._health_cache: dict[str, BrainHealth] = {}
88
+ self._cache_ttl = 30 # seconds
89
+
90
+ # โ”€โ”€โ”€ Health โ”€โ”€โ”€
91
+ def check_health(self, force: bool = False) -> dict[str, BrainHealth]:
92
+ now = time.time()
93
+ if not force and self._health_cache and all(
94
+ now - h.last_check < self._cache_ttl for h in self._health_cache.values()
95
+ ):
96
+ return self._health_cache
97
+
98
+ results = {}
99
+ for bid, binfo in BRAINS.items():
100
+ try:
101
+ r = requests.get(f"{binfo['worker']}/health", timeout=10)
102
+ data = r.json() if r.status_code == 200 else {}
103
+ results[bid] = BrainHealth(
104
+ brain_id=bid,
105
+ name=binfo["name"],
106
+ icon=binfo["icon"],
107
+ online=r.status_code == 200,
108
+ status=data.get("status", "unknown"),
109
+ latency_ms=int(r.elapsed.total_seconds() * 1000),
110
+ last_check=now,
111
+ )
112
+ except Exception as e:
113
+ results[bid] = BrainHealth(
114
+ brain_id=bid,
115
+ name=binfo["name"],
116
+ icon=binfo["icon"],
117
+ online=False,
118
+ status="offline",
119
+ last_check=now,
120
+ error=str(e),
121
+ )
122
+ self._health_cache = results
123
+ return results
124
+
125
+ @property
126
+ def is_online(self) -> bool:
127
+ health = self.check_health()
128
+ return any(h.online for h in health.values())
129
+
130
+ # โ”€โ”€โ”€ Core API โ”€โ”€โ”€
131
+ def cogitate(self, query: str, context: str = "", model: str = "", tags: list = None) -> dict:
132
+ """Run full 46-module CAPT pipeline."""
133
+ payload = {"query": query, "context": context, "modality": "text"}
134
+ if model:
135
+ payload["model"] = model
136
+ if tags:
137
+ payload["tags"] = tags
138
+ return self._post("/cogitate", payload)
139
+
140
+ def pulse(self, prompt: str, model: str = "", system: str = "", temperature: float = 0.7, max_tokens: int = 2048) -> dict:
141
+ """Direct LLM generation via PULSE."""
142
+ payload = {"prompt": prompt, "temperature": temperature, "maxTokens": max_tokens}
143
+ if model:
144
+ payload["model"] = model
145
+ if system:
146
+ payload["systemPrompt"] = system
147
+ return self._post("/pulse", payload)
148
+
149
+ def echo_store(self, content: str, salience: float = 0.5, tags: list = None) -> dict:
150
+ """Store an episodic memory trace."""
151
+ payload = {"content": content, "salience": salience}
152
+ if tags:
153
+ payload["tags"] = tags
154
+ return self._post("/echo/store", payload)
155
+
156
+ def echo_recall(self, query: str, top_k: int = 5) -> dict:
157
+ """Recall memory traces by similarity."""
158
+ return self._post("/echo/recall", {"query": query, "topK": top_k})
159
+
160
+ def immu_scan(self, text: str) -> dict:
161
+ """Constitutional scan for violations."""
162
+ return self._post("/immu/scan", {"text": text})
163
+
164
+ def constitution_enforce(self, text: str) -> dict:
165
+ """Enforce Seven Laws on content."""
166
+ return self._post("/constitution/enforce", {"text": text})
167
+
168
+ def get_status(self) -> dict:
169
+ """Get brain status and API endpoints."""
170
+ try:
171
+ r = requests.get(f"{self.worker_url}/status", timeout=10)
172
+ return r.json() if r.status_code == 200 else {"error": f"HTTP {r.status_code}"}
173
+ except Exception as e:
174
+ return {"error": str(e)}
175
+
176
+ # โ”€โ”€โ”€ Internal โ”€โ”€โ”€
177
+ def _post(self, endpoint: str, payload: dict) -> dict:
178
+ try:
179
+ r = requests.post(
180
+ f"{self.worker_url}{endpoint}",
181
+ json=payload,
182
+ timeout=self.timeout,
183
+ headers={"Content-Type": "application/json"},
184
+ )
185
+ if r.status_code == 200:
186
+ return r.json()
187
+ return {"error": f"HTTP {r.status_code}", "detail": r.text[:200]}
188
+ except Exception as e:
189
+ return {"error": str(e)}
190
+
191
+ # โ”€โ”€โ”€ Fallback Data (for when backend is down) โ”€โ”€โ”€
192
+ def get_fallback_cogitation(self, query: str) -> dict:
193
+ """Beautiful fallback when CAPT backend is unavailable."""
194
+ return {
195
+ "response": f"โšก CAPT Backend Status: Offline\n\nYour query: '{query[:60]}...'\n\nThe cognitive architecture is currently warming up. Try again in a moment, or check the Fleet Controller for brain health.",
196
+ "confidence": 0.0,
197
+ "pipelineTrace": {"stages": [], "totalLatencyMs": 0, "moduleCount": 0},
198
+ "echoTraces": [],
199
+ "consensus": {"confidence": 0, "dissenterCount": 0},
200
+ "threats": {"clean": True, "threats": []},
201
+ "metrics": {},
202
+ "_fallback": True,
203
+ }
204
+
205
+ def get_fallback_health_banner(self) -> str:
206
+ health = self.check_health()
207
+ online = sum(1 for h in health.values() if h.online)
208
+ total = len(health)
209
+ if online == total:
210
+ return f"๐ŸŸข All {total} brains online"
211
+ elif online > 0:
212
+ return f"๐ŸŸก {online}/{total} brains online"
213
+ return "๐Ÿ”ด All brains offline โ€” check Fleet Controller"
214
+
215
+
216
+ class BrainRouter:
217
+ """Routes queries to the best brain based on content analysis."""
218
+
219
+ KEYWORDS = {
220
+ "capt-core-01": ["general", "analyze", "explain", "what is", "how to", "why", "compare", "constitution", "ethics", "laws", "truth", "dignity"],
221
+ "biocapt-core-01": ["memory", "remember", "recall", "learn", "adapt", "evolve", "bio", "organic", "neural", "synapse", "cell", "dna"],
222
+ "frankencapt-core-01": ["modular", "combine", "fuse", "mix", "variant", "skill", "build", "assemble", "component", "plugin"],
223
+ "synthesis-core-01": ["synthesize", "consensus", "vote", "aggregate", "combine brains", "meta", "orchestrate", "coordinate", "unify"],
224
+ "council-core-01": ["council", "vote", "deliberate", "debate", "panel", "jury", "multi-model", "ensemble", "committee"],
225
+ }
226
+
227
+ @classmethod
228
+ def route(cls, query: str) -> str:
229
+ q = query.lower()
230
+ scores = {bid: 0 for bid in BRAINS}
231
+ for bid, keywords in cls.KEYWORDS.items():
232
+ for kw in keywords:
233
+ if kw in q:
234
+ scores[bid] += 1
235
+ best = max(scores, key=scores.get)
236
+ return best if scores[best] > 0 else "capt-core-01"
237
+
238
+ @classmethod
239
+ def get_brain_for_space(cls, space_name: str) -> str:
240
+ """Suggest the best brain for a given space type."""
241
+ space_lower = space_name.lower()
242
+ if "wiki" in space_lower:
243
+ return "capt-core-01"
244
+ elif "bio" in space_lower or "universal" in space_lower:
245
+ return "biocapt-core-01"
246
+ elif "franken" in space_lower or "forge" in space_lower:
247
+ return "frankencapt-core-01"
248
+ elif "council" in space_lower or "dashboard" in space_lower:
249
+ return "council-core-01"
250
+ elif "novel" in space_lower or "studio" in space_lower:
251
+ return "capt-core-01"
252
+ elif "cogitate" in space_lower:
253
+ return "capt-core-01"
254
+ return "capt-core-01"
255
+
256
+
257
+ # โ”€โ”€โ”€ OpenRouter Direct (when Worker is unavailable) โ”€โ”€โ”€
258
+ def direct_llm(prompt: str, model: str = "deepseek/deepseek-chat-v3-0324", system: str = "", temperature: float = 0.7) -> dict:
259
+ """Direct OpenRouter call โ€” bypasses CAPT Workers entirely."""
260
+ if not _OPENROUTER_KEY:
261
+ return {"error": "OPENROUTER_API_KEY not set", "content": "API key required for LLM generation."}
262
+ try:
263
+ r = requests.post(
264
+ "https://openrouter.ai/api/v1/chat/completions",
265
+ headers={
266
+ "Authorization": f"Bearer {_OPENROUTER_KEY}",
267
+ "Content-Type": "application/json",
268
+ "HTTP-Referer": "https://capt.dev",
269
+ "X-Title": "CAPT-HF-Spaces",
270
+ },
271
+ json={
272
+ "model": model,
273
+ "messages": [
274
+ {"role": "system", "content": system or "You are CAPT, a 46-module cognitive architecture."},
275
+ {"role": "user", "content": prompt},
276
+ ],
277
+ "temperature": temperature,
278
+ "max_tokens": 2048,
279
+ },
280
+ timeout=60,
281
+ )
282
+ data = r.json()
283
+ if r.status_code != 200:
284
+ return {"error": data.get("error", {}).get("message", "Unknown error"), "content": ""}
285
+ return {
286
+ "content": data["choices"][0]["message"]["content"],
287
+ "model": data.get("model", model),
288
+ }
289
+ except Exception as e:
290
+ return {"error": str(e), "content": ""}
291
+
292
+
293
+ # โ”€โ”€โ”€ Beautiful HTML Components โ”€โ”€โ”€
294
+ def health_banner_html(backend: CAPTBackend) -> str:
295
+ """Generate a beautiful health banner for Gradio/HTML embedding."""
296
+ health = backend.check_health()
297
+ online_count = sum(1 for h in health.values() if h.online)
298
+ total = len(health)
299
+
300
+ if online_count == total:
301
+ color = "#00e676"
302
+ emoji = "๐ŸŸข"
303
+ text = f"All {total} brains online"
304
+ elif online_count > 0:
305
+ color = "#ffd740"
306
+ emoji = "๐ŸŸก"
307
+ text = f"{online_count}/{total} brains online"
308
+ else:
309
+ color = "#ff5252"
310
+ emoji = "๐Ÿ”ด"
311
+ text = "All brains offline"
312
+
313
+ cards = ""
314
+ for bid, h in health.items():
315
+ dot = "๐ŸŸข" if h.online else "๐Ÿ”ด"
316
+ latency = f"{h.latency_ms}ms" if h.online else "offline"
317
+ cards += f'<div style="display:inline-block;padding:6px 10px;margin:3px;background:#1a1a2e;border-radius:8px;font-size:0.75rem"><span style="font-size:1rem">{h.icon}</span> {dot} {h.name}<br><span style="color:#8b8b9b">{latency}</span></div>'
318
+
319
+ return f"""
320
+ <div style="background:linear-gradient(135deg,#12121a,#0a0a0f);border:1px solid #1a1a2e;border-radius:12px;padding:14px;margin-bottom:16px">
321
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
322
+ <span style="font-size:1.2rem">{emoji}</span>
323
+ <span style="color:{color};font-weight:700;font-size:0.9rem">{text}</span>
324
+ <span style="color:#6b6b7b;font-size:0.75rem;margin-left:auto">CAPT Backend Connector v1.0</span>
325
+ </div>
326
+ <div style="display:flex;flex-wrap:wrap;gap:4px">{cards}</div>
327
+ </div>
328
+ """
329
+
330
+
331
+ def pipeline_trace_html(trace: dict) -> str:
332
+ """Render a beautiful pipeline trace."""
333
+ stages = trace.get("stages", [])
334
+ if not stages:
335
+ return "<div style='color:#6b6b7b;font-size:0.8rem'>No pipeline trace available</div>"
336
+
337
+ total = trace.get("totalLatencyMs", 0)
338
+ bars = ""
339
+ for s in stages:
340
+ color = "#00e676" if s.get("success") else "#ff5252"
341
+ name = s.get("module", "?")
342
+ latency = s.get("latencyMs", 0)
343
+ width = max(2, min(12, latency / 50))
344
+ bars += f'<div title="{name}: {latency}ms" style="display:inline-block;width:{width}px;height:20px;background:{color};margin-right:1px;border-radius:2px"></div>'
345
+
346
+ return f"""
347
+ <div style="background:#0d0d14;border-radius:8px;padding:10px">
348
+ <div style="color:#8b8b9b;font-size:0.7rem;margin-bottom:6px">โšก Pipeline: {len(stages)} modules ยท {total}ms</div>
349
+ <div style="display:flex;align-items:center;height:20px">{bars}</div>
350
+ </div>
351
+ """
requirements.txt CHANGED
@@ -1 +1,2 @@
1
  gradio>=5.0.0
 
 
1
  gradio>=5.0.0
2
+ requests>=2.28.0