caudate-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- api/__init__.py +5 -0
- api/anthropic_compat.py +1518 -0
- api/artifact_viewer.py +366 -0
- api/caudate_middleware.py +618 -0
- api/forge_bootstrapper_routes.py +377 -0
- api/forge_routes.py +630 -0
- api/forge_system_routes.py +294 -0
- api/openai_compat.py +1993 -0
- api/server.py +667 -0
- api/storyboard_page.py +677 -0
- caudate_cli-0.1.0.dist-info/METADATA +354 -0
- caudate_cli-0.1.0.dist-info/RECORD +153 -0
- caudate_cli-0.1.0.dist-info/WHEEL +5 -0
- caudate_cli-0.1.0.dist-info/entry_points.txt +2 -0
- caudate_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- caudate_cli-0.1.0.dist-info/top_level.txt +14 -0
- cognos_mcp/__init__.py +4 -0
- cognos_mcp/bridge.py +41 -0
- cognos_mcp/client.py +70 -0
- cognos_mcp/config.py +49 -0
- cognos_mcp/server.py +66 -0
- config.py +82 -0
- core/__init__.py +0 -0
- core/agent.py +468 -0
- core/agentic_loop.py +731 -0
- core/anthropic_auth.py +91 -0
- core/background.py +113 -0
- core/banner.py +134 -0
- core/bootstrap.py +292 -0
- core/citations.py +131 -0
- core/compaction.py +109 -0
- core/constitution.py +198 -0
- core/diff_viewer.py +87 -0
- core/export.py +85 -0
- core/file_refs.py +119 -0
- core/files.py +199 -0
- core/hooks.py +209 -0
- core/image.py +599 -0
- core/input.py +91 -0
- core/loop.py +238 -0
- core/memory_md.py +147 -0
- core/notifications.py +99 -0
- core/ownership.py +181 -0
- core/paste.py +81 -0
- core/permissions.py +210 -0
- core/plan_mode.py +215 -0
- core/sandbox_prompt.py +185 -0
- core/scheduler.py +195 -0
- core/schemas.py +202 -0
- core/session.py +90 -0
- core/settings.py +132 -0
- core/skills.py +398 -0
- core/slash_commands.py +977 -0
- core/statusline.py +61 -0
- core/subagent.py +300 -0
- core/thinking.py +50 -0
- core/updater.py +122 -0
- core/usage.py +109 -0
- core/worktree.py +93 -0
- execution/__init__.py +0 -0
- execution/executor.py +329 -0
- execution/plugins.py +108 -0
- execution/tools/__init__.py +0 -0
- execution/tools/agent_tool.py +107 -0
- execution/tools/agentic_tool.py +297 -0
- execution/tools/artifact_tool.py +191 -0
- execution/tools/ask_user_question_tool.py +137 -0
- execution/tools/base.py +81 -0
- execution/tools/calculator_tool.py +137 -0
- execution/tools/cognos_card_tool.py +124 -0
- execution/tools/cron_tool.py +215 -0
- execution/tools/datetime_tool.py +215 -0
- execution/tools/describe_image_tool.py +161 -0
- execution/tools/draw_tool.py +164 -0
- execution/tools/edit_image_tool.py +262 -0
- execution/tools/edit_tool.py +245 -0
- execution/tools/file_tool.py +90 -0
- execution/tools/find_anywhere_tool.py +255 -0
- execution/tools/forge_feature_tools.py +377 -0
- execution/tools/glob_tool.py +59 -0
- execution/tools/grep_tool.py +89 -0
- execution/tools/http_request_tool.py +224 -0
- execution/tools/load_skill_tool.py +104 -0
- execution/tools/longcat_avatar_tool.py +384 -0
- execution/tools/mcp_tool.py +100 -0
- execution/tools/notebook_tool.py +279 -0
- execution/tools/openapi_tool.py +440 -0
- execution/tools/plan_mode_tool.py +95 -0
- execution/tools/push_notification_tool.py +157 -0
- execution/tools/python_tool.py +61 -0
- execution/tools/respond_tool.py +40 -0
- execution/tools/sandbox_tool.py +378 -0
- execution/tools/search_tool.py +153 -0
- execution/tools/semantic_search_tool.py +106 -0
- execution/tools/shell_tool.py +283 -0
- execution/tools/speak_tool.py +134 -0
- execution/tools/storyboard_tool.py +727 -0
- execution/tools/system_info_tool.py +212 -0
- execution/tools/task_tool.py +323 -0
- execution/tools/think_tool.py +49 -0
- execution/tools/transcribe_audio_tool.py +86 -0
- execution/tools/update_memory_tool.py +92 -0
- execution/tools/web_fetch_tool.py +82 -0
- execution/tools/worktree_tool.py +174 -0
- llm/__init__.py +0 -0
- llm/fallback.py +116 -0
- llm/models.py +320 -0
- llm/provider.py +1356 -0
- llm/router.py +373 -0
- main.py +1889 -0
- memory/__init__.py +0 -0
- memory/episodic.py +99 -0
- memory/procedural.py +145 -0
- memory/semantic.py +71 -0
- memory/working.py +64 -0
- nn/__init__.py +43 -0
- nn/auto_evolve.py +245 -0
- nn/caudate.py +136 -0
- nn/config.py +141 -0
- nn/consolidator.py +81 -0
- nn/data.py +1635 -0
- nn/encoder.py +258 -0
- nn/forge_advisor.py +303 -0
- nn/format.py +235 -0
- nn/heads.py +432 -0
- nn/observer.py +994 -0
- nn/policy.py +214 -0
- nn/runtime.py +343 -0
- nn/scorer.py +175 -0
- nn/trainer.py +515 -0
- nn/vision.py +352 -0
- personality/__init__.py +23 -0
- personality/engine.py +129 -0
- personality/identity.py +144 -0
- personality/inner_voice.py +100 -0
- personality/mood.py +205 -0
- planning/__init__.py +0 -0
- planning/dev_server.py +221 -0
- planning/forge_models.py +718 -0
- planning/orchestrator.py +1363 -0
- planning/planner.py +451 -0
- planning/task_graph.py +61 -0
- reflection/__init__.py +0 -0
- reflection/meta_learner.py +156 -0
- reflection/reflector.py +127 -0
- ui/__init__.py +5 -0
- ui/display.py +88 -0
- voice/__init__.py +0 -0
- voice/conversation.py +125 -0
- voice/listener.py +111 -0
- voice/speaker.py +59 -0
- voice/stt.py +126 -0
- voice/tts.py +214 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""Forge system + provider routes — hardware detection, provider scan.
|
|
2
|
+
|
|
3
|
+
Mirrors LocalForge's /api/system/hardware and /api/providers/scan
|
|
4
|
+
under Cognos's /forge prefix. Lazy detection + on-disk cache so the
|
|
5
|
+
hot path is fast on every platform.
|
|
6
|
+
|
|
7
|
+
Endpoints:
|
|
8
|
+
GET /forge/system/hardware?refresh=1 CPU/RAM/GPU info
|
|
9
|
+
GET /forge/providers/scan probe Ollama + LM-Studio
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
import platform
|
|
19
|
+
import shutil
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
import time
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
import httpx
|
|
27
|
+
from fastapi import APIRouter
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
_HARDWARE_CACHE_PATH = Path(__file__).resolve().parents[1] / "data" / "hardware-cache.json"
|
|
32
|
+
_memory_cache: dict[str, Any] | None = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ─────────────────────────── Hardware detection ────────────────────────
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _read_disk_cache() -> dict[str, Any] | None:
|
|
39
|
+
try:
|
|
40
|
+
if not _HARDWARE_CACHE_PATH.exists():
|
|
41
|
+
return None
|
|
42
|
+
data = json.loads(_HARDWARE_CACHE_PATH.read_text())
|
|
43
|
+
if data.get("platform") != sys.platform:
|
|
44
|
+
return None
|
|
45
|
+
return data
|
|
46
|
+
except Exception:
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _write_disk_cache(value: dict[str, Any]) -> None:
|
|
51
|
+
try:
|
|
52
|
+
_HARDWARE_CACHE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
_HARDWARE_CACHE_PATH.write_text(json.dumps(value, indent=2))
|
|
54
|
+
except Exception as e:
|
|
55
|
+
logger.debug(f"hardware cache write failed: {e}")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _detect_apple_silicon() -> bool:
|
|
59
|
+
return sys.platform == "darwin" and platform.machine() in ("arm64", "aarch64")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _detect_total_ram_mb() -> int:
|
|
63
|
+
try:
|
|
64
|
+
import psutil
|
|
65
|
+
return round(psutil.virtual_memory().total / 1024 / 1024)
|
|
66
|
+
except Exception:
|
|
67
|
+
# Fallback for hosts without psutil
|
|
68
|
+
try:
|
|
69
|
+
with open("/proc/meminfo") as f:
|
|
70
|
+
for line in f:
|
|
71
|
+
if line.startswith("MemTotal:"):
|
|
72
|
+
kb = int(line.split()[1])
|
|
73
|
+
return round(kb / 1024)
|
|
74
|
+
except Exception:
|
|
75
|
+
pass
|
|
76
|
+
return 0
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _detect_nvidia_gpus() -> list[dict[str, Any]]:
|
|
80
|
+
"""Return one dict per NVIDIA GPU via nvidia-smi."""
|
|
81
|
+
if shutil.which("nvidia-smi") is None:
|
|
82
|
+
return []
|
|
83
|
+
try:
|
|
84
|
+
out = subprocess.check_output(
|
|
85
|
+
["nvidia-smi",
|
|
86
|
+
"--query-gpu=name,memory.total",
|
|
87
|
+
"--format=csv,noheader,nounits"],
|
|
88
|
+
stderr=subprocess.DEVNULL, timeout=3,
|
|
89
|
+
).decode().strip()
|
|
90
|
+
except Exception:
|
|
91
|
+
return []
|
|
92
|
+
gpus: list[dict[str, Any]] = []
|
|
93
|
+
for line in out.splitlines():
|
|
94
|
+
parts = [p.strip() for p in line.split(",")]
|
|
95
|
+
if len(parts) >= 2:
|
|
96
|
+
try:
|
|
97
|
+
vram = int(float(parts[1]))
|
|
98
|
+
except ValueError:
|
|
99
|
+
continue
|
|
100
|
+
gpus.append({
|
|
101
|
+
"vendor": "NVIDIA",
|
|
102
|
+
"model": parts[0],
|
|
103
|
+
"vram_mb": vram,
|
|
104
|
+
})
|
|
105
|
+
return gpus
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _detect_amd_gpus() -> list[dict[str, Any]]:
|
|
109
|
+
"""Best-effort AMD ROCm detection via rocm-smi."""
|
|
110
|
+
if shutil.which("rocm-smi") is None:
|
|
111
|
+
return []
|
|
112
|
+
try:
|
|
113
|
+
out = subprocess.check_output(
|
|
114
|
+
["rocm-smi", "--showmeminfo", "vram", "--csv"],
|
|
115
|
+
stderr=subprocess.DEVNULL, timeout=3,
|
|
116
|
+
).decode()
|
|
117
|
+
except Exception:
|
|
118
|
+
return []
|
|
119
|
+
gpus: list[dict[str, Any]] = []
|
|
120
|
+
# Parse CSV; columns vary by version. Look for vram total bytes.
|
|
121
|
+
for line in out.splitlines():
|
|
122
|
+
if "card" not in line.lower():
|
|
123
|
+
continue
|
|
124
|
+
cols = [c.strip() for c in line.split(",")]
|
|
125
|
+
if len(cols) >= 2:
|
|
126
|
+
try:
|
|
127
|
+
vram_bytes = int(cols[-1])
|
|
128
|
+
gpus.append({
|
|
129
|
+
"vendor": "AMD",
|
|
130
|
+
"model": cols[0],
|
|
131
|
+
"vram_mb": vram_bytes // (1024 * 1024),
|
|
132
|
+
})
|
|
133
|
+
except ValueError:
|
|
134
|
+
pass
|
|
135
|
+
return gpus
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _detect_apple_gpu_budget(total_ram_mb: int) -> dict[str, Any]:
|
|
139
|
+
"""Apple Silicon: GPU shares unified memory. Budget ~75% of RAM."""
|
|
140
|
+
return {
|
|
141
|
+
"vendor": "Apple",
|
|
142
|
+
"model": "Apple Silicon",
|
|
143
|
+
"vram_mb": round(total_ram_mb * 0.75),
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _detect_hardware() -> dict[str, Any]:
|
|
148
|
+
"""Snapshot the local machine's CPU/RAM/GPU. Conservative — failures
|
|
149
|
+
return ``{available: false, reason: ...}`` so the UI can render a
|
|
150
|
+
helpful message instead of a stack trace."""
|
|
151
|
+
plat = sys.platform
|
|
152
|
+
total_ram = _detect_total_ram_mb()
|
|
153
|
+
is_apple = _detect_apple_silicon()
|
|
154
|
+
cpu_count = os.cpu_count() or 0
|
|
155
|
+
|
|
156
|
+
if is_apple:
|
|
157
|
+
gpu = _detect_apple_gpu_budget(total_ram)
|
|
158
|
+
return {
|
|
159
|
+
"available": True,
|
|
160
|
+
"platform": plat,
|
|
161
|
+
"is_apple_silicon": True,
|
|
162
|
+
"cpu_count": cpu_count,
|
|
163
|
+
"gpus": [gpu],
|
|
164
|
+
"total_vram_mb": gpu["vram_mb"],
|
|
165
|
+
"total_system_ram_mb": total_ram,
|
|
166
|
+
"detected_at": time.time(),
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
gpus = _detect_nvidia_gpus() + _detect_amd_gpus()
|
|
170
|
+
if not gpus:
|
|
171
|
+
return {
|
|
172
|
+
"available": False,
|
|
173
|
+
"platform": plat,
|
|
174
|
+
"is_apple_silicon": False,
|
|
175
|
+
"cpu_count": cpu_count,
|
|
176
|
+
"total_system_ram_mb": total_ram,
|
|
177
|
+
"reason": "No discrete GPU with reportable VRAM detected "
|
|
178
|
+
"(checked nvidia-smi, rocm-smi).",
|
|
179
|
+
"detected_at": time.time(),
|
|
180
|
+
}
|
|
181
|
+
gpus.sort(key=lambda g: g["vram_mb"], reverse=True)
|
|
182
|
+
return {
|
|
183
|
+
"available": True,
|
|
184
|
+
"platform": plat,
|
|
185
|
+
"is_apple_silicon": False,
|
|
186
|
+
"cpu_count": cpu_count,
|
|
187
|
+
"gpus": gpus,
|
|
188
|
+
"total_vram_mb": max(g["vram_mb"] for g in gpus),
|
|
189
|
+
"total_system_ram_mb": total_ram,
|
|
190
|
+
"detected_at": time.time(),
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# ─────────────────────────── Provider scan ─────────────────────────────
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
_PROVIDERS = [
|
|
198
|
+
{
|
|
199
|
+
"id": "ollama",
|
|
200
|
+
"label": "Ollama",
|
|
201
|
+
"default_base_url": "http://127.0.0.1:11434",
|
|
202
|
+
"tags_path": "/api/tags",
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"id": "lm-studio",
|
|
206
|
+
"label": "LM Studio",
|
|
207
|
+
"default_base_url": "http://127.0.0.1:1234",
|
|
208
|
+
"tags_path": "/v1/models",
|
|
209
|
+
},
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
async def _probe_ollama(base_url: str, timeout: float = 1.5) -> int | None:
|
|
214
|
+
"""Return model count if Ollama responds with the expected shape."""
|
|
215
|
+
url = base_url.rstrip("/") + "/api/tags"
|
|
216
|
+
try:
|
|
217
|
+
async with httpx.AsyncClient(timeout=timeout) as cx:
|
|
218
|
+
r = await cx.get(url)
|
|
219
|
+
if r.status_code != 200:
|
|
220
|
+
return None
|
|
221
|
+
data = r.json()
|
|
222
|
+
if not isinstance(data, dict) or not isinstance(data.get("models"), list):
|
|
223
|
+
return None
|
|
224
|
+
return len(data["models"])
|
|
225
|
+
except Exception:
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
async def _probe_lm_studio(base_url: str, timeout: float = 1.5) -> int | None:
|
|
230
|
+
"""Return model count if LM-Studio's OpenAI-compat endpoint answers."""
|
|
231
|
+
url = base_url.rstrip("/") + "/v1/models"
|
|
232
|
+
try:
|
|
233
|
+
async with httpx.AsyncClient(timeout=timeout) as cx:
|
|
234
|
+
r = await cx.get(url)
|
|
235
|
+
if r.status_code != 200:
|
|
236
|
+
return None
|
|
237
|
+
data = r.json()
|
|
238
|
+
if not isinstance(data, dict) or not isinstance(data.get("data"), list):
|
|
239
|
+
return None
|
|
240
|
+
return len(data["data"])
|
|
241
|
+
except Exception:
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
async def _scan_providers() -> list[dict[str, Any]]:
|
|
246
|
+
hits: list[dict[str, Any]] = []
|
|
247
|
+
probes = [
|
|
248
|
+
(_PROVIDERS[0], _probe_ollama(_PROVIDERS[0]["default_base_url"])),
|
|
249
|
+
(_PROVIDERS[1], _probe_lm_studio(_PROVIDERS[1]["default_base_url"])),
|
|
250
|
+
]
|
|
251
|
+
for desc, coro in probes:
|
|
252
|
+
try:
|
|
253
|
+
count = await coro
|
|
254
|
+
except Exception:
|
|
255
|
+
count = None
|
|
256
|
+
if count is not None:
|
|
257
|
+
hits.append({
|
|
258
|
+
"provider_id": desc["id"],
|
|
259
|
+
"label": desc["label"],
|
|
260
|
+
"url": desc["default_base_url"],
|
|
261
|
+
"model_count": count,
|
|
262
|
+
})
|
|
263
|
+
return hits
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# ─────────────────────────────── Router ────────────────────────────────
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def build_router() -> APIRouter:
|
|
270
|
+
router = APIRouter(prefix="/forge", tags=["forge-system"])
|
|
271
|
+
|
|
272
|
+
@router.get("/system/hardware")
|
|
273
|
+
async def hardware(refresh: int = 0) -> dict[str, Any]:
|
|
274
|
+
global _memory_cache
|
|
275
|
+
if not refresh and _memory_cache is not None:
|
|
276
|
+
return _memory_cache
|
|
277
|
+
if not refresh:
|
|
278
|
+
disk = _read_disk_cache()
|
|
279
|
+
if disk is not None:
|
|
280
|
+
_memory_cache = disk
|
|
281
|
+
return disk
|
|
282
|
+
result = await asyncio.get_running_loop().run_in_executor(
|
|
283
|
+
None, _detect_hardware,
|
|
284
|
+
)
|
|
285
|
+
_memory_cache = result
|
|
286
|
+
if result.get("available"):
|
|
287
|
+
_write_disk_cache(result)
|
|
288
|
+
return result
|
|
289
|
+
|
|
290
|
+
@router.get("/providers/scan")
|
|
291
|
+
async def providers_scan() -> dict[str, Any]:
|
|
292
|
+
return {"hits": await _scan_providers()}
|
|
293
|
+
|
|
294
|
+
return router
|