snapdec 0.2.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.
- snapdec/_brand.py +8 -0
- snapdec/backends/__init__.py +5 -0
- snapdec/backends/base.py +31 -0
- snapdec/backends/keydetect.py +67 -0
- snapdec/backends/mock.py +72 -0
- snapdec/backends/remote_systemone.py +79 -0
- snapdec/cli/__init__.py +3 -0
- snapdec/cli/__main__.py +3 -0
- snapdec/cli/main.py +756 -0
- snapdec/config.py +119 -0
- snapdec/decisions/__init__.py +14 -0
- snapdec/decisions/envelope.py +98 -0
- snapdec/decisions/policy.py +51 -0
- snapdec/decisions/tier0/__init__.py +3 -0
- snapdec/decisions/tier0/project_facts.py +216 -0
- snapdec/hardware/__init__.py +4 -0
- snapdec/hardware/catalog.py +91 -0
- snapdec/hardware/detect.py +181 -0
- snapdec/hardware/profile.py +62 -0
- snapdec/integrations/__init__.py +4 -0
- snapdec/integrations/agents_json.py +162 -0
- snapdec/integrations/base.py +181 -0
- snapdec/integrations/claude_code.py +142 -0
- snapdec/integrations/codex.py +126 -0
- snapdec/integrations/json_family.py +126 -0
- snapdec/integrations/patchers.py +137 -0
- snapdec/integrations/registry.py +29 -0
- snapdec/mcp/__init__.py +3 -0
- snapdec/mcp/server.py +218 -0
- snapdec/runtime/__init__.py +8 -0
- snapdec/runtime/daemon.py +200 -0
- snapdec/runtime/ipc.py +148 -0
- snapdec/runtime/lifecycle.py +138 -0
- snapdec/runtime/probe.py +69 -0
- snapdec/runtime/provision.py +232 -0
- snapdec/skills/snapdec/SKILL.md +19 -0
- snapdec/skills/snapdec/references/recipes.md +21 -0
- snapdec/skills/snapdec/references/tools.md +26 -0
- snapdec-0.2.0.dist-info/METADATA +181 -0
- snapdec-0.2.0.dist-info/RECORD +43 -0
- snapdec-0.2.0.dist-info/WHEEL +4 -0
- snapdec-0.2.0.dist-info/entry_points.txt +2 -0
- snapdec-0.2.0.dist-info/licenses/LICENSE +202 -0
snapdec/_brand.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Single source of truth for the project name (rename gate §1.4).
|
|
2
|
+
|
|
3
|
+
A rename must be a single commit: this constant + pyproject.toml.
|
|
4
|
+
Do not hardcode the name anywhere else.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.2.0"
|
|
8
|
+
NAME = "snapdec" # final — rename gate cleared 2026-10-03, see docs/adr/0007
|
snapdec/backends/base.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Backend interface (§6).
|
|
2
|
+
|
|
3
|
+
Backends are interchangeable behind one interface; anything that already
|
|
4
|
+
speaks /v1/systemone (hosted Jev, kev.serve, laya-serve, OpenRouter) is
|
|
5
|
+
just a manifest/URL away — no adapter code needed (remote_systemone).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any, Protocol, runtime_checkable
|
|
12
|
+
|
|
13
|
+
from ..decisions.envelope import SystemOneRequest, SystemOneResponse
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Health:
|
|
18
|
+
status: str # ready | warming | degraded | failed
|
|
19
|
+
device: str = ""
|
|
20
|
+
detail: str = ""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@runtime_checkable
|
|
24
|
+
class Backend(Protocol):
|
|
25
|
+
id: str
|
|
26
|
+
|
|
27
|
+
def load(self) -> None: ...
|
|
28
|
+
def system_one(self, req: SystemOneRequest) -> SystemOneResponse: ...
|
|
29
|
+
def health(self) -> Health: ...
|
|
30
|
+
def close(self) -> None: ...
|
|
31
|
+
def describe(self) -> dict[str, Any]: ...
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Auto-detect the provider from a pasted API key (§9.1 'all auto').
|
|
2
|
+
|
|
3
|
+
Only System One-compatible endpoints are usable by the remote backend.
|
|
4
|
+
OpenRouter is the universal bridge (hosts Jev/Kev models); OpenAI/Groq/
|
|
5
|
+
Anthropic keys do NOT speak /v1/systemone and are reported as such with
|
|
6
|
+
a hint instead of being silently misrouted.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class ProviderGuess:
|
|
16
|
+
provider: str # openrouter | typesafe | unsupported:<id> | unknown
|
|
17
|
+
url: str = ""
|
|
18
|
+
default_models: tuple[str, ...] = ()
|
|
19
|
+
note: str = ""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_OPENROUTER = ProviderGuess(
|
|
23
|
+
"openrouter", "https://openrouter.ai/api/v1",
|
|
24
|
+
# free variants first — "paid or free judgement": try :free, fall back
|
|
25
|
+
("jaredpalmer/kev-4b:free", "jaredpalmer/kev-4b", "typesafe/jev-latest"),
|
|
26
|
+
"OpenRouter — universal bridge, hosts Jev/Kev",
|
|
27
|
+
)
|
|
28
|
+
_TYPESAFE = ProviderGuess(
|
|
29
|
+
"typesafe", "https://api.typesafe.ai",
|
|
30
|
+
("jev-latest",),
|
|
31
|
+
"TypeSafe Jev — native System One",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def detect_provider(key: str) -> ProviderGuess:
|
|
36
|
+
k = key.strip()
|
|
37
|
+
if k.startswith("sk-or-"):
|
|
38
|
+
return _OPENROUTER
|
|
39
|
+
if k.startswith("ts-") or k.startswith("tsk_"):
|
|
40
|
+
return _TYPESAFE
|
|
41
|
+
if k.startswith("sk-ant-"):
|
|
42
|
+
return ProviderGuess("unsupported:anthropic", note=(
|
|
43
|
+
"Anthropic key detected — Anthropic speaks /v1/messages, not "
|
|
44
|
+
"/v1/systemone. Create a free OpenRouter key (openrouter.ai/keys) "
|
|
45
|
+
"and paste that instead."))
|
|
46
|
+
if k.startswith("gsk_"):
|
|
47
|
+
return ProviderGuess("unsupported:groq", note=(
|
|
48
|
+
"Groq key detected — not System One compatible. "
|
|
49
|
+
"Use an OpenRouter key (openrouter.ai/keys)."))
|
|
50
|
+
if k.startswith("sk-proj-") or k.startswith("sk-"):
|
|
51
|
+
return ProviderGuess("unsupported:openai", note=(
|
|
52
|
+
"OpenAI key detected — not System One compatible. "
|
|
53
|
+
"Use an OpenRouter key (openrouter.ai/keys) or a TypeSafe key."))
|
|
54
|
+
return ProviderGuess("unknown")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def provider_by_id(pid: str) -> ProviderGuess:
|
|
58
|
+
return {"openrouter": _OPENROUTER, "typesafe": _TYPESAFE}.get(pid, ProviderGuess(pid))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def pick_model(guess: ProviderGuess, *, low_power: bool = False) -> str:
|
|
62
|
+
"""Device-aware default: hosted models are small enough that the wire
|
|
63
|
+
cost dominates; low-power devices still prefer the cheapest first."""
|
|
64
|
+
models = guess.default_models
|
|
65
|
+
if not models:
|
|
66
|
+
return ""
|
|
67
|
+
return models[0]
|
snapdec/backends/mock.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Deterministic mock backend — tests, demos, and `--backend mock`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import random
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from ..decisions.envelope import (
|
|
10
|
+
Answer,
|
|
11
|
+
SystemOneRequest,
|
|
12
|
+
SystemOneResponse,
|
|
13
|
+
)
|
|
14
|
+
from .base import Health
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MockBackend:
|
|
18
|
+
id = "mock"
|
|
19
|
+
|
|
20
|
+
def load(self) -> None:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def _dist(self, seed_text: str, options: list[str]) -> dict[str, float]:
|
|
24
|
+
"""Stable pseudo-distribution: same input → same numbers."""
|
|
25
|
+
rng = random.Random(int(hashlib.sha256(seed_text.encode("utf-8")).hexdigest()[:8], 16))
|
|
26
|
+
raw = [rng.random() + 0.35 for _ in options] or [1.0]
|
|
27
|
+
total = sum(raw)
|
|
28
|
+
probs = [r / total for r in raw]
|
|
29
|
+
# sharpen the top so `auto` is reachable with default thresholds
|
|
30
|
+
top = probs.index(max(probs))
|
|
31
|
+
probs[top] += 0.30
|
|
32
|
+
total = sum(probs)
|
|
33
|
+
return {o: p / total for o, p in zip(options, probs, strict=False)}
|
|
34
|
+
|
|
35
|
+
def system_one(self, req: SystemOneRequest) -> SystemOneResponse:
|
|
36
|
+
state = req.state if isinstance(req.state, str) else str(req.state)
|
|
37
|
+
answers: dict[str, Answer] = {}
|
|
38
|
+
for name, q in req.questions.items():
|
|
39
|
+
if q.type == "noul":
|
|
40
|
+
probs = self._dist(f"{state}|{name}", ["yes", "no"])
|
|
41
|
+
answers[name] = Answer(
|
|
42
|
+
type="noul", noul=probs["yes"] > probs["no"], probabilities=probs,
|
|
43
|
+
confidence=max(probs.values()),
|
|
44
|
+
)
|
|
45
|
+
elif q.type == "score":
|
|
46
|
+
levels = list(q.criteria) if isinstance(q.criteria, (list, dict)) else \
|
|
47
|
+
[f"L{i}" for i in range(1, 6)]
|
|
48
|
+
probs = self._dist(f"{state}|{name}", levels)
|
|
49
|
+
best = max(probs, key=probs.get)
|
|
50
|
+
idx = levels.index(best)
|
|
51
|
+
answers[name] = Answer(
|
|
52
|
+
type="score", score=idx + 1, nearest_level=best,
|
|
53
|
+
probabilities=probs, confidence=probs[best],
|
|
54
|
+
)
|
|
55
|
+
else: # choice
|
|
56
|
+
options = list(q.criteria) if q.criteria else ["a", "b"]
|
|
57
|
+
probs = self._dist(f"{state}|{name}", options)
|
|
58
|
+
best = max(probs, key=probs.get)
|
|
59
|
+
answers[name] = Answer(
|
|
60
|
+
type="choice", choice=best, probabilities=probs,
|
|
61
|
+
confidence=probs[best],
|
|
62
|
+
)
|
|
63
|
+
return SystemOneResponse(answers=answers, model="mock-1")
|
|
64
|
+
|
|
65
|
+
def health(self) -> Health:
|
|
66
|
+
return Health(status="ready", device="cpu", detail="deterministic mock")
|
|
67
|
+
|
|
68
|
+
def close(self) -> None:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
def describe(self) -> dict[str, Any]:
|
|
72
|
+
return {"name": self.id, "model": "mock-1", "remote": False, "ready": True}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Remote /v1/systemone backend (§6).
|
|
2
|
+
|
|
3
|
+
Works unchanged with: hosted TypeSafe Jev, OpenRouter-hosted models,
|
|
4
|
+
kev.serve, laya-serve, team servers, Telnyx/Opper/LLM Gateway.
|
|
5
|
+
Fail-closed: any transport error raises; the daemon converts it into a
|
|
6
|
+
`review` envelope — never into an exception toward the host agent.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
|
|
15
|
+
from ..decisions.envelope import (
|
|
16
|
+
Answer,
|
|
17
|
+
SystemOneRequest,
|
|
18
|
+
SystemOneResponse,
|
|
19
|
+
)
|
|
20
|
+
from .base import Health
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RemoteSystemOne:
|
|
24
|
+
id = "remote"
|
|
25
|
+
|
|
26
|
+
def __init__(self, base_url: str, model: str = "", api_key: str | None = None,
|
|
27
|
+
timeout: float = 5.0) -> None:
|
|
28
|
+
self.base_url = base_url.rstrip("/")
|
|
29
|
+
self.model = model
|
|
30
|
+
self.api_key = api_key
|
|
31
|
+
self.timeout = timeout
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------- http
|
|
34
|
+
def _headers(self) -> dict[str, str]:
|
|
35
|
+
h = {"content-type": "application/json"}
|
|
36
|
+
if self.api_key:
|
|
37
|
+
h["authorization"] = f"Bearer {self.api_key}"
|
|
38
|
+
h["x-api-key"] = self.api_key # kev.serve / laya-serve convention
|
|
39
|
+
return h
|
|
40
|
+
|
|
41
|
+
def load(self) -> None:
|
|
42
|
+
pass # no local state; readiness checked via health()
|
|
43
|
+
|
|
44
|
+
def system_one(self, req: SystemOneRequest) -> SystemOneResponse:
|
|
45
|
+
payload = req.model_dump(exclude_none=True)
|
|
46
|
+
if self.model:
|
|
47
|
+
payload["model"] = self.model
|
|
48
|
+
r = httpx.post(
|
|
49
|
+
f"{self.base_url}/v1/systemone",
|
|
50
|
+
json=payload, headers=self._headers(), timeout=self.timeout,
|
|
51
|
+
)
|
|
52
|
+
r.raise_for_status()
|
|
53
|
+
data = r.json()
|
|
54
|
+
answers = {k: Answer(**v) for k, v in (data.get("answers") or {}).items()}
|
|
55
|
+
return SystemOneResponse(answers=answers, usage=data.get("usage"),
|
|
56
|
+
model=data.get("model"))
|
|
57
|
+
|
|
58
|
+
def health(self) -> Health:
|
|
59
|
+
try:
|
|
60
|
+
with httpx.Client(timeout=self.timeout) as c:
|
|
61
|
+
r = c.get(f"{self.base_url}/v1/models", headers=self._headers())
|
|
62
|
+
if r.status_code == 404: # laya-serve exposes /models, not /v1/models
|
|
63
|
+
r = c.get(f"{self.base_url}/models", headers=self._headers())
|
|
64
|
+
if r.status_code in (200, 401, 403):
|
|
65
|
+
# 401/403 = server is up, key is wrong — still "reachable"
|
|
66
|
+
status = "ready" if r.status_code == 200 else "degraded"
|
|
67
|
+
return Health(status=status, device="remote",
|
|
68
|
+
detail=f"HTTP {r.status_code}")
|
|
69
|
+
return Health(status="degraded", device="remote",
|
|
70
|
+
detail=f"HTTP {r.status_code}")
|
|
71
|
+
except httpx.HTTPError as e:
|
|
72
|
+
return Health(status="failed", device="remote", detail=str(e))
|
|
73
|
+
|
|
74
|
+
def close(self) -> None:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
def describe(self) -> dict[str, Any]:
|
|
78
|
+
return {"name": self.id, "url": self.base_url, "model": self.model,
|
|
79
|
+
"remote": True}
|
snapdec/cli/__init__.py
ADDED
snapdec/cli/__main__.py
ADDED