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.
Files changed (45) hide show
  1. syntaxmatrix/__init__.py +6 -4
  2. syntaxmatrix/agentic/agents.py +195 -15
  3. syntaxmatrix/agentic/agents_orchestrer.py +16 -10
  4. syntaxmatrix/client_docs.py +237 -0
  5. syntaxmatrix/commentary.py +96 -25
  6. syntaxmatrix/core.py +156 -54
  7. syntaxmatrix/dataset_preprocessing.py +2 -2
  8. syntaxmatrix/db.py +60 -0
  9. syntaxmatrix/db_backends/__init__.py +1 -0
  10. syntaxmatrix/db_backends/postgres_backend.py +14 -0
  11. syntaxmatrix/db_backends/sqlite_backend.py +258 -0
  12. syntaxmatrix/db_contract.py +71 -0
  13. syntaxmatrix/kernel_manager.py +174 -150
  14. syntaxmatrix/page_builder_generation.py +654 -50
  15. syntaxmatrix/page_layout_contract.py +25 -3
  16. syntaxmatrix/page_patch_publish.py +368 -15
  17. syntaxmatrix/plugins/__init__.py +0 -0
  18. syntaxmatrix/plugins/plugin_manager.py +114 -0
  19. syntaxmatrix/premium/__init__.py +18 -0
  20. syntaxmatrix/premium/catalogue/__init__.py +121 -0
  21. syntaxmatrix/premium/gate.py +119 -0
  22. syntaxmatrix/premium/state.py +507 -0
  23. syntaxmatrix/premium/verify.py +222 -0
  24. syntaxmatrix/profiles.py +1 -1
  25. syntaxmatrix/routes.py +9782 -8004
  26. syntaxmatrix/settings/model_map.py +50 -65
  27. syntaxmatrix/settings/prompts.py +1435 -380
  28. syntaxmatrix/settings/string_navbar.py +4 -4
  29. syntaxmatrix/static/icons/bot_icon.png +0 -0
  30. syntaxmatrix/static/icons/bot_icon2.png +0 -0
  31. syntaxmatrix/templates/admin_billing.html +408 -0
  32. syntaxmatrix/templates/admin_branding.html +65 -2
  33. syntaxmatrix/templates/admin_features.html +54 -0
  34. syntaxmatrix/templates/dashboard.html +285 -8
  35. syntaxmatrix/templates/edit_page.html +199 -18
  36. syntaxmatrix/themes.py +17 -17
  37. syntaxmatrix/workspace_db.py +0 -23
  38. syntaxmatrix-3.0.0.dist-info/METADATA +219 -0
  39. {syntaxmatrix-2.6.4.3.dist-info → syntaxmatrix-3.0.0.dist-info}/RECORD +42 -30
  40. {syntaxmatrix-2.6.4.3.dist-info → syntaxmatrix-3.0.0.dist-info}/WHEEL +1 -1
  41. syntaxmatrix/settings/default.yaml +0 -13
  42. syntaxmatrix-2.6.4.3.dist-info/METADATA +0 -539
  43. syntaxmatrix-2.6.4.3.dist-info/licenses/LICENSE.txt +0 -21
  44. /syntaxmatrix/static/icons/{logo3.png → logo2.png} +0 -0
  45. {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)