syntaxmatrix 2.6.4.3__py3-none-any.whl → 3.0.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.
- syntaxmatrix/__init__.py +6 -4
- syntaxmatrix/agentic/agents.py +195 -15
- syntaxmatrix/agentic/agents_orchestrer.py +16 -10
- syntaxmatrix/client_docs.py +237 -0
- syntaxmatrix/commentary.py +96 -25
- syntaxmatrix/core.py +156 -54
- syntaxmatrix/dataset_preprocessing.py +2 -2
- syntaxmatrix/db.py +60 -0
- syntaxmatrix/db_backends/__init__.py +1 -0
- syntaxmatrix/db_backends/postgres_backend.py +14 -0
- syntaxmatrix/db_backends/sqlite_backend.py +258 -0
- syntaxmatrix/db_contract.py +71 -0
- syntaxmatrix/kernel_manager.py +174 -150
- syntaxmatrix/page_builder_generation.py +654 -50
- syntaxmatrix/page_layout_contract.py +25 -3
- syntaxmatrix/page_patch_publish.py +368 -15
- syntaxmatrix/plugins/__init__.py +0 -0
- syntaxmatrix/plugins/plugin_manager.py +114 -0
- syntaxmatrix/premium/__init__.py +18 -0
- syntaxmatrix/premium/catalogue/__init__.py +121 -0
- syntaxmatrix/premium/gate.py +119 -0
- syntaxmatrix/premium/state.py +507 -0
- syntaxmatrix/premium/verify.py +222 -0
- syntaxmatrix/profiles.py +1 -1
- syntaxmatrix/routes.py +9782 -8004
- syntaxmatrix/settings/model_map.py +50 -65
- syntaxmatrix/settings/prompts.py +1435 -380
- syntaxmatrix/settings/string_navbar.py +4 -4
- syntaxmatrix/static/icons/bot_icon.png +0 -0
- syntaxmatrix/static/icons/bot_icon2.png +0 -0
- syntaxmatrix/templates/admin_billing.html +408 -0
- syntaxmatrix/templates/admin_branding.html +65 -2
- syntaxmatrix/templates/admin_features.html +54 -0
- syntaxmatrix/templates/dashboard.html +285 -8
- syntaxmatrix/templates/edit_page.html +199 -18
- syntaxmatrix/themes.py +17 -17
- syntaxmatrix/workspace_db.py +0 -23
- syntaxmatrix-3.0.0.dist-info/METADATA +219 -0
- {syntaxmatrix-2.6.4.3.dist-info → syntaxmatrix-3.0.0.dist-info}/RECORD +42 -30
- {syntaxmatrix-2.6.4.3.dist-info → syntaxmatrix-3.0.0.dist-info}/WHEEL +1 -1
- syntaxmatrix/settings/default.yaml +0 -13
- syntaxmatrix-2.6.4.3.dist-info/METADATA +0 -539
- syntaxmatrix-2.6.4.3.dist-info/licenses/LICENSE.txt +0 -21
- /syntaxmatrix/static/icons/{logo3.png → logo2.png} +0 -0
- {syntaxmatrix-2.6.4.3.dist-info → syntaxmatrix-3.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any, Dict, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _safe_json_loads(raw: str, default: Any) -> Any:
|
|
9
|
+
try:
|
|
10
|
+
return json.loads(raw)
|
|
11
|
+
except Exception:
|
|
12
|
+
return default
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _read_json(path: str) -> Dict[str, Any]:
|
|
16
|
+
try:
|
|
17
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
18
|
+
data = json.load(f)
|
|
19
|
+
return data if isinstance(data, dict) else {}
|
|
20
|
+
except Exception:
|
|
21
|
+
return {}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _catalogue_dir() -> str:
|
|
25
|
+
return os.path.join(os.path.dirname(__file__))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def load_index() -> Dict[str, Any]:
|
|
29
|
+
"""Load catalogue index.json (safe, returns empty dict on failure)."""
|
|
30
|
+
return _read_json(os.path.join(_catalogue_dir(), "index.json"))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def load_catalogue(version: Optional[str] = None) -> Tuple[Dict[str, Any], str]:
|
|
34
|
+
"""Load a catalogue for the requested version.
|
|
35
|
+
|
|
36
|
+
Returns: (catalogue_dict, resolved_version)
|
|
37
|
+
"""
|
|
38
|
+
idx = load_index()
|
|
39
|
+
resolved = (version or "").strip() or str(idx.get("default") or "").strip()
|
|
40
|
+
versions = idx.get("versions") if isinstance(idx.get("versions"), dict) else {}
|
|
41
|
+
fname = versions.get(resolved) if isinstance(versions, dict) else None
|
|
42
|
+
|
|
43
|
+
if not fname:
|
|
44
|
+
# Fall back to default file name if index is missing or version unknown.
|
|
45
|
+
fname = "catalogue.v1.json"
|
|
46
|
+
|
|
47
|
+
cat = _read_json(os.path.join(_catalogue_dir(), str(fname)))
|
|
48
|
+
# If the loaded file advertises a version, prefer that.
|
|
49
|
+
advertised = str(cat.get("version") or "").strip()
|
|
50
|
+
if advertised:
|
|
51
|
+
resolved = advertised
|
|
52
|
+
return cat, resolved
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def ui_labels(version: Optional[str] = None) -> Dict[str, Any]:
|
|
56
|
+
cat, _ = load_catalogue(version)
|
|
57
|
+
ui = cat.get("ui") if isinstance(cat.get("ui"), dict) else {}
|
|
58
|
+
labels = ui.get("labels") if isinstance(ui.get("labels"), dict) else {}
|
|
59
|
+
return labels if isinstance(labels, dict) else {}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def resolve_entitlements(
|
|
63
|
+
*,
|
|
64
|
+
plan_id: str,
|
|
65
|
+
version: Optional[str] = None,
|
|
66
|
+
addons: Optional[list] = None,
|
|
67
|
+
custom_overrides: Optional[Dict[str, Any]] = None,
|
|
68
|
+
) -> Dict[str, Any]:
|
|
69
|
+
"""Resolve final entitlements from catalogue.
|
|
70
|
+
|
|
71
|
+
This keeps entitlements data-driven:
|
|
72
|
+
- base entitlements come from the plan in the catalogue
|
|
73
|
+
- optional add-ons apply deltas/overrides
|
|
74
|
+
- optional custom_overrides apply last
|
|
75
|
+
"""
|
|
76
|
+
pid = (plan_id or "free").strip().lower() or "free"
|
|
77
|
+
cat, resolved_version = load_catalogue(version)
|
|
78
|
+
|
|
79
|
+
plans = cat.get("plans") if isinstance(cat.get("plans"), dict) else {}
|
|
80
|
+
p = plans.get(pid) if isinstance(plans, dict) else None
|
|
81
|
+
base = {}
|
|
82
|
+
if isinstance(p, dict) and isinstance(p.get("entitlements"), dict):
|
|
83
|
+
base = dict(p.get("entitlements") or {})
|
|
84
|
+
|
|
85
|
+
# Always stamp plan + version for UI/debugging.
|
|
86
|
+
base["plan"] = pid
|
|
87
|
+
base["entitlement_version"] = resolved_version
|
|
88
|
+
|
|
89
|
+
# Apply add-ons.
|
|
90
|
+
addons_list = addons if isinstance(addons, list) else []
|
|
91
|
+
if addons_list:
|
|
92
|
+
addons_def = cat.get("addons") if isinstance(cat.get("addons"), dict) else {}
|
|
93
|
+
for addon_id in addons_list:
|
|
94
|
+
aid = str(addon_id or "").strip()
|
|
95
|
+
if not aid:
|
|
96
|
+
continue
|
|
97
|
+
a = addons_def.get(aid) if isinstance(addons_def, dict) else None
|
|
98
|
+
if not isinstance(a, dict):
|
|
99
|
+
continue
|
|
100
|
+
delta = a.get("entitlements_delta") if isinstance(a.get("entitlements_delta"), dict) else {}
|
|
101
|
+
for k, v in (delta or {}).items():
|
|
102
|
+
# Support "+N" increment for numeric limits.
|
|
103
|
+
if isinstance(v, str) and v.strip().startswith("+"):
|
|
104
|
+
try:
|
|
105
|
+
inc = int(v.strip()[1:])
|
|
106
|
+
cur = int(base.get(k) or 0)
|
|
107
|
+
base[k] = cur + inc
|
|
108
|
+
continue
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
base[k] = v
|
|
112
|
+
base["addons"] = addons_list
|
|
113
|
+
|
|
114
|
+
# Apply overrides last.
|
|
115
|
+
if isinstance(custom_overrides, dict) and custom_overrides:
|
|
116
|
+
for k, v in custom_overrides.items():
|
|
117
|
+
if k in ("sig",):
|
|
118
|
+
continue
|
|
119
|
+
base[k] = v
|
|
120
|
+
|
|
121
|
+
return base
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _safe_json_loads(raw: str, *, default: Any) -> Any:
|
|
10
|
+
try:
|
|
11
|
+
return json.loads(raw)
|
|
12
|
+
except Exception:
|
|
13
|
+
return default
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class GateSources:
|
|
18
|
+
"""Where the gate reads entitlements from.
|
|
19
|
+
|
|
20
|
+
Precedence (highest first):
|
|
21
|
+
1) env_json
|
|
22
|
+
2) db_setting_key
|
|
23
|
+
3) licence_file
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
env_json: str = "SMX_PREMIUM_ENTITLEMENTS"
|
|
27
|
+
db_setting_key: str = "premium.entitlements"
|
|
28
|
+
licence_file_relpath: str = os.path.join("premium", "licence.json")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class FeatureGate:
|
|
32
|
+
"""Runtime entitlement checks for premium features.
|
|
33
|
+
|
|
34
|
+
This is intentionally small, dependency-free, and safe:
|
|
35
|
+
- If anything fails, it returns 'not entitled' rather than crashing the app.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
*,
|
|
41
|
+
client_dir: str,
|
|
42
|
+
db: Optional[object] = None,
|
|
43
|
+
sources: Optional[GateSources] = None,
|
|
44
|
+
):
|
|
45
|
+
self._client_dir = client_dir
|
|
46
|
+
self._db = db
|
|
47
|
+
self._sources = sources or GateSources()
|
|
48
|
+
self._cache: Optional[Dict[str, Any]] = None
|
|
49
|
+
|
|
50
|
+
def _normalise(self, data: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
51
|
+
if not isinstance(data, dict):
|
|
52
|
+
return None
|
|
53
|
+
if isinstance(data.get("entitlements"), dict):
|
|
54
|
+
ent = dict(data.get("entitlements") or {})
|
|
55
|
+
if data.get("plan") and not ent.get("plan"):
|
|
56
|
+
ent["plan"] = data.get("plan")
|
|
57
|
+
return ent
|
|
58
|
+
return data
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _load_from_env(self) -> Optional[Dict[str, Any]]:
|
|
62
|
+
raw = os.environ.get(self._sources.env_json)
|
|
63
|
+
if not raw:
|
|
64
|
+
return None
|
|
65
|
+
data = _safe_json_loads(raw, default=None)
|
|
66
|
+
return self._normalise(data)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _load_from_db(self) -> Optional[Dict[str, Any]]:
|
|
70
|
+
if not self._db:
|
|
71
|
+
return None
|
|
72
|
+
get_setting = getattr(self._db, "get_setting", None)
|
|
73
|
+
if not callable(get_setting):
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
raw = get_setting(self._sources.db_setting_key, "{}")
|
|
77
|
+
data = _safe_json_loads(str(raw or "{}"), default={})
|
|
78
|
+
return self._normalise(data)
|
|
79
|
+
|
|
80
|
+
def _load_from_file(self) -> Optional[Dict[str, Any]]:
|
|
81
|
+
p = os.path.join(self._client_dir, self._sources.licence_file_relpath)
|
|
82
|
+
if not os.path.exists(p):
|
|
83
|
+
return None
|
|
84
|
+
try:
|
|
85
|
+
with open(p, "r", encoding="utf-8") as f:
|
|
86
|
+
data = json.load(f)
|
|
87
|
+
return self._normalise(data)
|
|
88
|
+
except Exception:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def entitlements(self, *, refresh: bool = False) -> Dict[str, Any]:
|
|
92
|
+
"""Returns entitlement dict (possibly empty)."""
|
|
93
|
+
if self._cache is not None and not refresh:
|
|
94
|
+
return self._cache
|
|
95
|
+
|
|
96
|
+
ent = self._load_from_env()
|
|
97
|
+
if ent is None:
|
|
98
|
+
ent = self._load_from_db()
|
|
99
|
+
if ent is None:
|
|
100
|
+
ent = self._load_from_file()
|
|
101
|
+
if ent is None:
|
|
102
|
+
ent = {}
|
|
103
|
+
|
|
104
|
+
self._cache = ent
|
|
105
|
+
return ent
|
|
106
|
+
|
|
107
|
+
def enabled(self, key: str) -> bool:
|
|
108
|
+
"""True if entitlement exists and is truthy."""
|
|
109
|
+
key = (key or "").strip()
|
|
110
|
+
if not key:
|
|
111
|
+
return False
|
|
112
|
+
ent = self.entitlements()
|
|
113
|
+
return bool(ent.get(key))
|
|
114
|
+
|
|
115
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
116
|
+
key = (key or "").strip()
|
|
117
|
+
if not key:
|
|
118
|
+
return default
|
|
119
|
+
return self.entitlements().get(key, default)
|