delimit-cli 3.10.0 → 3.10.2
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.
- package/gateway/ai/license.py +24 -2
- package/gateway/ai/license_core.py +7 -194
- package/gateway/ai/server.py +85 -52
- package/package.json +1 -1
package/gateway/ai/license.py
CHANGED
|
@@ -3,6 +3,11 @@ Delimit license — thin shim.
|
|
|
3
3
|
The enforcement logic is in license_core (shipped as compiled binary).
|
|
4
4
|
This shim handles imports and provides fallback error messages.
|
|
5
5
|
"""
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
# Always export LICENSE_FILE for tests and external access
|
|
9
|
+
LICENSE_FILE = Path.home() / ".delimit" / "license.json"
|
|
10
|
+
|
|
6
11
|
try:
|
|
7
12
|
from ai.license_core import (
|
|
8
13
|
load_license as get_license,
|
|
@@ -20,8 +25,21 @@ except ImportError:
|
|
|
20
25
|
|
|
21
26
|
LICENSE_FILE = Path.home() / ".delimit" / "license.json"
|
|
22
27
|
|
|
23
|
-
PRO_TOOLS =
|
|
24
|
-
|
|
28
|
+
PRO_TOOLS = frozenset({
|
|
29
|
+
"delimit_gov_evaluate", "delimit_gov_policy", "delimit_gov_run", "delimit_gov_verify",
|
|
30
|
+
"delimit_os_plan", "delimit_os_status", "delimit_os_gates",
|
|
31
|
+
"delimit_deploy_plan", "delimit_deploy_build", "delimit_deploy_publish",
|
|
32
|
+
"delimit_deploy_verify", "delimit_deploy_rollback", "delimit_deploy_status",
|
|
33
|
+
"delimit_deploy_site", "delimit_deploy_npm",
|
|
34
|
+
"delimit_memory_store", "delimit_memory_search", "delimit_memory_recent",
|
|
35
|
+
"delimit_vault_search", "delimit_vault_snapshot", "delimit_vault_health",
|
|
36
|
+
"delimit_evidence_collect", "delimit_evidence_verify",
|
|
37
|
+
"delimit_deliberate", "delimit_models",
|
|
38
|
+
"delimit_obs_metrics", "delimit_obs_logs", "delimit_obs_status",
|
|
39
|
+
"delimit_release_plan", "delimit_release_status", "delimit_release_sync",
|
|
40
|
+
"delimit_cost_analyze", "delimit_cost_optimize", "delimit_cost_alert",
|
|
41
|
+
})
|
|
42
|
+
FREE_TRIAL_LIMITS = {"delimit_deliberate": 3}
|
|
25
43
|
|
|
26
44
|
def get_license() -> dict:
|
|
27
45
|
if not LICENSE_FILE.exists():
|
|
@@ -36,12 +54,16 @@ except ImportError:
|
|
|
36
54
|
return lic.get("tier") in ("pro", "enterprise") and lic.get("valid", False)
|
|
37
55
|
|
|
38
56
|
def require_premium(tool_name: str) -> dict | None:
|
|
57
|
+
full_name = tool_name if tool_name.startswith("delimit_") else f"delimit_{tool_name}"
|
|
58
|
+
if full_name not in PRO_TOOLS:
|
|
59
|
+
return None
|
|
39
60
|
if is_premium():
|
|
40
61
|
return None
|
|
41
62
|
return {
|
|
42
63
|
"error": f"'{tool_name}' requires Delimit Pro. Upgrade at https://delimit.ai/pricing",
|
|
43
64
|
"status": "premium_required",
|
|
44
65
|
"tool": tool_name,
|
|
66
|
+
"current_tier": get_license().get("tier", "free"),
|
|
45
67
|
}
|
|
46
68
|
|
|
47
69
|
def activate_license(key: str) -> dict:
|
|
@@ -1,196 +1,9 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Delimit license enforcement core — compiled with Nuitka.
|
|
3
|
-
Contains: validation logic, re-validation, usage tracking, entitlement checks.
|
|
4
|
-
This module is distributed as a native binary (.so/.pyd), not readable Python.
|
|
5
|
-
"""
|
|
6
|
-
import hashlib
|
|
7
|
-
import json
|
|
8
|
-
import time
|
|
1
|
+
"""Delimit License Core — compiled binary required. Run: npx delimit-cli setup"""
|
|
9
2
|
from pathlib import Path
|
|
10
|
-
|
|
11
3
|
LICENSE_FILE = Path.home() / ".delimit" / "license.json"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# Pro tools that require a license
|
|
20
|
-
PRO_TOOLS = frozenset({
|
|
21
|
-
"delimit_gov_health", "delimit_gov_status", "delimit_gov_evaluate",
|
|
22
|
-
"delimit_gov_policy", "delimit_gov_run", "delimit_gov_verify",
|
|
23
|
-
"delimit_deploy_plan", "delimit_deploy_build", "delimit_deploy_publish",
|
|
24
|
-
"delimit_deploy_verify", "delimit_deploy_rollback", "delimit_deploy_site", "delimit_deploy_npm",
|
|
25
|
-
"delimit_memory_store", "delimit_memory_search", "delimit_memory_recent",
|
|
26
|
-
"delimit_vault_search", "delimit_vault_snapshot", "delimit_vault_health",
|
|
27
|
-
"delimit_evidence_collect", "delimit_evidence_verify",
|
|
28
|
-
"delimit_deliberate", "delimit_models",
|
|
29
|
-
"delimit_obs_metrics", "delimit_obs_logs", "delimit_obs_status",
|
|
30
|
-
"delimit_release_plan", "delimit_release_status", "delimit_release_sync",
|
|
31
|
-
"delimit_cost_analyze", "delimit_cost_optimize", "delimit_cost_alert",
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
# Free trial limits
|
|
35
|
-
FREE_TRIAL_LIMITS = {
|
|
36
|
-
"delimit_deliberate": 3,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def load_license() -> dict:
|
|
41
|
-
"""Load and validate license with re-validation."""
|
|
42
|
-
if not LICENSE_FILE.exists():
|
|
43
|
-
return {"tier": "free", "valid": True}
|
|
44
|
-
try:
|
|
45
|
-
data = json.loads(LICENSE_FILE.read_text())
|
|
46
|
-
if data.get("expires_at") and data["expires_at"] < time.time():
|
|
47
|
-
return {"tier": "free", "valid": True, "expired": True}
|
|
48
|
-
|
|
49
|
-
if data.get("tier") in ("pro", "enterprise") and data.get("valid"):
|
|
50
|
-
last_validated = data.get("last_validated_at", data.get("activated_at", 0))
|
|
51
|
-
elapsed = time.time() - last_validated
|
|
52
|
-
|
|
53
|
-
if elapsed > REVALIDATION_INTERVAL:
|
|
54
|
-
revalidated = _revalidate(data)
|
|
55
|
-
if revalidated.get("valid"):
|
|
56
|
-
data["last_validated_at"] = time.time()
|
|
57
|
-
data["validation_status"] = "current"
|
|
58
|
-
LICENSE_FILE.write_text(json.dumps(data, indent=2))
|
|
59
|
-
elif elapsed > REVALIDATION_INTERVAL + HARD_BLOCK:
|
|
60
|
-
return {"tier": "free", "valid": True, "revoked": True,
|
|
61
|
-
"reason": "License expired. Renew at https://delimit.ai/pricing"}
|
|
62
|
-
elif elapsed > REVALIDATION_INTERVAL + GRACE_PERIOD:
|
|
63
|
-
data["validation_status"] = "grace_period"
|
|
64
|
-
days_left = int((REVALIDATION_INTERVAL + HARD_BLOCK - elapsed) / 86400)
|
|
65
|
-
data["grace_days_remaining"] = days_left
|
|
66
|
-
else:
|
|
67
|
-
data["validation_status"] = "revalidation_pending"
|
|
68
|
-
return data
|
|
69
|
-
except Exception:
|
|
70
|
-
return {"tier": "free", "valid": True}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def check_premium() -> bool:
|
|
74
|
-
"""Check if user has a valid premium license."""
|
|
75
|
-
lic = load_license()
|
|
76
|
-
return lic.get("tier") in ("pro", "enterprise") and lic.get("valid", False)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def gate_tool(tool_name: str) -> dict | None:
|
|
80
|
-
"""Gate a Pro tool. Returns None if allowed, error dict if blocked."""
|
|
81
|
-
if tool_name not in PRO_TOOLS:
|
|
82
|
-
return None
|
|
83
|
-
if check_premium():
|
|
84
|
-
return None
|
|
85
|
-
|
|
86
|
-
# Check free trial
|
|
87
|
-
limit = FREE_TRIAL_LIMITS.get(tool_name)
|
|
88
|
-
if limit is not None:
|
|
89
|
-
used = _get_monthly_usage(tool_name)
|
|
90
|
-
if used < limit:
|
|
91
|
-
_increment_usage(tool_name)
|
|
92
|
-
return None
|
|
93
|
-
return {
|
|
94
|
-
"error": f"Free trial limit reached ({limit}/month). Upgrade to Pro for unlimited.",
|
|
95
|
-
"status": "trial_exhausted",
|
|
96
|
-
"tool": tool_name,
|
|
97
|
-
"used": used,
|
|
98
|
-
"limit": limit,
|
|
99
|
-
"upgrade_url": "https://delimit.ai/pricing",
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
"error": f"'{tool_name}' requires Delimit Pro ($10/mo). Upgrade at https://delimit.ai/pricing",
|
|
104
|
-
"status": "premium_required",
|
|
105
|
-
"tool": tool_name,
|
|
106
|
-
"current_tier": load_license().get("tier", "free"),
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def activate(key: str) -> dict:
|
|
111
|
-
"""Activate a license key."""
|
|
112
|
-
if not key or len(key) < 10:
|
|
113
|
-
return {"error": "Invalid license key format"}
|
|
114
|
-
|
|
115
|
-
machine_hash = hashlib.sha256(str(Path.home()).encode()).hexdigest()[:16]
|
|
116
|
-
|
|
117
|
-
try:
|
|
118
|
-
import urllib.request
|
|
119
|
-
data = json.dumps({"license_key": key, "instance_name": machine_hash}).encode()
|
|
120
|
-
req = urllib.request.Request(
|
|
121
|
-
LS_VALIDATE_URL, data=data,
|
|
122
|
-
headers={"Content-Type": "application/json", "Accept": "application/json"},
|
|
123
|
-
method="POST",
|
|
124
|
-
)
|
|
125
|
-
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
126
|
-
result = json.loads(resp.read())
|
|
127
|
-
|
|
128
|
-
if result.get("valid"):
|
|
129
|
-
license_data = {
|
|
130
|
-
"key": key, "tier": "pro", "valid": True,
|
|
131
|
-
"activated_at": time.time(), "last_validated_at": time.time(),
|
|
132
|
-
"machine_hash": machine_hash,
|
|
133
|
-
"instance_id": result.get("instance", {}).get("id"),
|
|
134
|
-
"validated_via": "lemon_squeezy",
|
|
135
|
-
}
|
|
136
|
-
LICENSE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
137
|
-
LICENSE_FILE.write_text(json.dumps(license_data, indent=2))
|
|
138
|
-
return {"status": "activated", "tier": "pro"}
|
|
139
|
-
return {"error": "Invalid license key.", "status": "invalid"}
|
|
140
|
-
|
|
141
|
-
except Exception:
|
|
142
|
-
license_data = {
|
|
143
|
-
"key": key, "tier": "pro", "valid": True,
|
|
144
|
-
"activated_at": time.time(), "last_validated_at": time.time(),
|
|
145
|
-
"machine_hash": machine_hash, "validated_via": "offline",
|
|
146
|
-
}
|
|
147
|
-
LICENSE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
148
|
-
LICENSE_FILE.write_text(json.dumps(license_data, indent=2))
|
|
149
|
-
return {"status": "activated", "tier": "pro", "message": "Activated offline."}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def _revalidate(data: dict) -> dict:
|
|
153
|
-
"""Re-validate against Lemon Squeezy."""
|
|
154
|
-
key = data.get("key", "")
|
|
155
|
-
if not key or key.startswith("JAMSONS"):
|
|
156
|
-
return {"valid": True}
|
|
157
|
-
try:
|
|
158
|
-
import urllib.request
|
|
159
|
-
req_data = json.dumps({"license_key": key}).encode()
|
|
160
|
-
req = urllib.request.Request(
|
|
161
|
-
LS_VALIDATE_URL, data=req_data,
|
|
162
|
-
headers={"Content-Type": "application/json", "Accept": "application/json"},
|
|
163
|
-
method="POST",
|
|
164
|
-
)
|
|
165
|
-
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
166
|
-
result = json.loads(resp.read())
|
|
167
|
-
return {"valid": result.get("valid", False)}
|
|
168
|
-
except Exception:
|
|
169
|
-
return {"valid": True, "offline": True}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def _get_monthly_usage(tool_name: str) -> int:
|
|
173
|
-
if not USAGE_FILE.exists():
|
|
174
|
-
return 0
|
|
175
|
-
try:
|
|
176
|
-
data = json.loads(USAGE_FILE.read_text())
|
|
177
|
-
return data.get(time.strftime("%Y-%m"), {}).get(tool_name, 0)
|
|
178
|
-
except Exception:
|
|
179
|
-
return 0
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def _increment_usage(tool_name: str) -> int:
|
|
183
|
-
month_key = time.strftime("%Y-%m")
|
|
184
|
-
data = {}
|
|
185
|
-
if USAGE_FILE.exists():
|
|
186
|
-
try:
|
|
187
|
-
data = json.loads(USAGE_FILE.read_text())
|
|
188
|
-
except Exception:
|
|
189
|
-
pass
|
|
190
|
-
if month_key not in data:
|
|
191
|
-
data[month_key] = {}
|
|
192
|
-
data[month_key][tool_name] = data[month_key].get(tool_name, 0) + 1
|
|
193
|
-
count = data[month_key][tool_name]
|
|
194
|
-
USAGE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
195
|
-
USAGE_FILE.write_text(json.dumps(data, indent=2))
|
|
196
|
-
return count
|
|
4
|
+
PRO_TOOLS = frozenset()
|
|
5
|
+
FREE_TRIAL_LIMITS = {}
|
|
6
|
+
def load_license(): return {"tier": "free", "valid": True}
|
|
7
|
+
def check_premium(): return False
|
|
8
|
+
def gate_tool(t): return None
|
|
9
|
+
def activate(k): return {"error": "License core not available. Run: npx delimit-cli setup"}
|
package/gateway/ai/server.py
CHANGED
|
@@ -57,25 +57,11 @@ def _experimental_tool():
|
|
|
57
57
|
return decorator
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
# Pro tools —
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"delimit_gov_policy", "delimit_gov_run", "delimit_gov_verify",
|
|
64
|
-
"delimit_deploy_plan", "delimit_deploy_build", "delimit_deploy_publish",
|
|
65
|
-
"delimit_deploy_verify", "delimit_deploy_rollback", "delimit_deploy_status",
|
|
66
|
-
"delimit_deploy_site", "delimit_deploy_npm",
|
|
67
|
-
"delimit_memory_store", "delimit_memory_search", "delimit_memory_recent",
|
|
68
|
-
"delimit_vault_search", "delimit_vault_snapshot", "delimit_vault_health",
|
|
69
|
-
"delimit_evidence_collect", "delimit_evidence_verify",
|
|
70
|
-
"delimit_deliberate", "delimit_models",
|
|
71
|
-
"delimit_obs_metrics", "delimit_obs_logs", "delimit_obs_status",
|
|
72
|
-
"delimit_release_plan", "delimit_release_status", "delimit_release_sync",
|
|
73
|
-
"delimit_cost_analyze", "delimit_cost_optimize", "delimit_cost_alert",
|
|
74
|
-
}
|
|
60
|
+
# Pro tools — single source of truth is license_core.py
|
|
61
|
+
# Import at module level; fallback to license.py shim if core unavailable
|
|
62
|
+
from ai.license import PRO_TOOLS
|
|
75
63
|
|
|
76
|
-
# Free tools —
|
|
77
|
-
# lint, diff, semver, explain, policy, init, diagnose, help, version,
|
|
78
|
-
# ledger_context, ledger_add, ledger_done, ledger_list, scan, zero_spec,
|
|
64
|
+
# Free tools — everything NOT in PRO_TOOLS
|
|
79
65
|
# security_audit, security_scan, test_generate, test_smoke, activate, license_status
|
|
80
66
|
|
|
81
67
|
|
|
@@ -567,30 +553,22 @@ def delimit_os_gates(plan_id: str) -> Dict[str, Any]:
|
|
|
567
553
|
|
|
568
554
|
@mcp.tool()
|
|
569
555
|
def delimit_gov_health(repo: str = ".") -> Dict[str, Any]:
|
|
570
|
-
"""Check governance system health
|
|
556
|
+
"""Check governance system health.
|
|
571
557
|
|
|
572
558
|
Args:
|
|
573
559
|
repo: Repository path to check.
|
|
574
560
|
"""
|
|
575
|
-
from ai.license import require_premium
|
|
576
|
-
gate = require_premium("gov_health")
|
|
577
|
-
if gate:
|
|
578
|
-
return gate
|
|
579
561
|
from backends.governance_bridge import health
|
|
580
562
|
return _with_next_steps("gov_health", _safe_call(health, repo=repo))
|
|
581
563
|
|
|
582
564
|
|
|
583
565
|
@mcp.tool()
|
|
584
566
|
def delimit_gov_status(repo: str = ".") -> Dict[str, Any]:
|
|
585
|
-
"""Get current governance status for a repository
|
|
567
|
+
"""Get current governance status for a repository.
|
|
586
568
|
|
|
587
569
|
Args:
|
|
588
570
|
repo: Repository path.
|
|
589
571
|
"""
|
|
590
|
-
from ai.license import require_premium
|
|
591
|
-
gate = require_premium("gov_status")
|
|
592
|
-
if gate:
|
|
593
|
-
return gate
|
|
594
572
|
from backends.governance_bridge import status
|
|
595
573
|
return _with_next_steps("gov_status", _safe_call(status, repo=repo))
|
|
596
574
|
|
|
@@ -1025,15 +1003,11 @@ def delimit_repo_config_audit(target: str = ".") -> Dict[str, Any]:
|
|
|
1025
1003
|
|
|
1026
1004
|
@mcp.tool()
|
|
1027
1005
|
def delimit_security_scan(target: str = ".") -> Dict[str, Any]:
|
|
1028
|
-
"""Scan for security vulnerabilities
|
|
1006
|
+
"""Scan for security vulnerabilities.
|
|
1029
1007
|
|
|
1030
1008
|
Args:
|
|
1031
1009
|
target: Repository or file path.
|
|
1032
1010
|
"""
|
|
1033
|
-
from ai.license import require_premium
|
|
1034
|
-
gate = require_premium("security_scan")
|
|
1035
|
-
if gate:
|
|
1036
|
-
return gate
|
|
1037
1011
|
from backends.repo_bridge import security_scan
|
|
1038
1012
|
return _with_next_steps("security_scan", _safe_call(security_scan, target=target))
|
|
1039
1013
|
|
|
@@ -1099,7 +1073,8 @@ def delimit_evidence_verify(bundle_id: Optional[str] = None, bundle_path: Option
|
|
|
1099
1073
|
|
|
1100
1074
|
@mcp.tool()
|
|
1101
1075
|
def delimit_release_plan(environment: str = "production", version: str = "", repository: str = ".", services: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
1102
|
-
"""
|
|
1076
|
+
""" (Pro).
|
|
1077
|
+
Generate a release plan from git history.
|
|
1103
1078
|
|
|
1104
1079
|
Reads git log since last tag, counts commits and changed files,
|
|
1105
1080
|
suggests a semver version, and generates a release checklist.
|
|
@@ -1111,6 +1086,10 @@ def delimit_release_plan(environment: str = "production", version: str = "", rep
|
|
|
1111
1086
|
repository: Repository path (default: current directory).
|
|
1112
1087
|
services: Optional service list.
|
|
1113
1088
|
"""
|
|
1089
|
+
from ai.license import require_premium
|
|
1090
|
+
gate = require_premium("release_plan")
|
|
1091
|
+
if gate:
|
|
1092
|
+
return gate
|
|
1114
1093
|
from backends.tools_infra import release_plan
|
|
1115
1094
|
return _with_next_steps("release_plan", _safe_call(release_plan, environment=environment, version=version, repository=repository, services=services))
|
|
1116
1095
|
|
|
@@ -1129,7 +1108,8 @@ def delimit_release_validate(environment: str, version: str) -> Dict[str, Any]:
|
|
|
1129
1108
|
|
|
1130
1109
|
@mcp.tool()
|
|
1131
1110
|
def delimit_release_status(environment: str = "production") -> Dict[str, Any]:
|
|
1132
|
-
"""
|
|
1111
|
+
""" (Pro).
|
|
1112
|
+
Check release/deploy status from file-based tracker and git state.
|
|
1133
1113
|
|
|
1134
1114
|
Shows latest deploy plan, current git tag, how many commits HEAD
|
|
1135
1115
|
is ahead of the tag, and recent deploy history.
|
|
@@ -1137,6 +1117,10 @@ def delimit_release_status(environment: str = "production") -> Dict[str, Any]:
|
|
|
1137
1117
|
Args:
|
|
1138
1118
|
environment: Target environment (staging/production).
|
|
1139
1119
|
"""
|
|
1120
|
+
from ai.license import require_premium
|
|
1121
|
+
gate = require_premium("release_status")
|
|
1122
|
+
if gate:
|
|
1123
|
+
return gate
|
|
1140
1124
|
from backends.tools_infra import release_status
|
|
1141
1125
|
return _with_next_steps("release_status", _safe_call(release_status, environment=environment))
|
|
1142
1126
|
|
|
@@ -1170,22 +1154,32 @@ def delimit_release_history(environment: str, limit: int = 10) -> Dict[str, Any]
|
|
|
1170
1154
|
|
|
1171
1155
|
@mcp.tool()
|
|
1172
1156
|
def delimit_cost_analyze(target: str = ".") -> Dict[str, Any]:
|
|
1173
|
-
"""
|
|
1157
|
+
""" (Pro).
|
|
1158
|
+
Analyze project costs by scanning Dockerfiles, dependencies, and cloud configs.
|
|
1174
1159
|
|
|
1175
1160
|
Args:
|
|
1176
1161
|
target: Project or infrastructure path to analyze.
|
|
1177
1162
|
"""
|
|
1163
|
+
from ai.license import require_premium
|
|
1164
|
+
gate = require_premium("cost_analyze")
|
|
1165
|
+
if gate:
|
|
1166
|
+
return gate
|
|
1178
1167
|
from backends.tools_data import cost_analyze
|
|
1179
1168
|
return _with_next_steps("cost_analyze", _safe_call(cost_analyze, target=target))
|
|
1180
1169
|
|
|
1181
1170
|
|
|
1182
1171
|
@mcp.tool()
|
|
1183
1172
|
def delimit_cost_optimize(target: str = ".") -> Dict[str, Any]:
|
|
1184
|
-
"""
|
|
1173
|
+
""" (Pro).
|
|
1174
|
+
Find cost optimization opportunities: unused deps, oversized images, uncompressed assets.
|
|
1185
1175
|
|
|
1186
1176
|
Args:
|
|
1187
1177
|
target: Project or infrastructure path to analyze.
|
|
1188
1178
|
"""
|
|
1179
|
+
from ai.license import require_premium
|
|
1180
|
+
gate = require_premium("cost_optimize")
|
|
1181
|
+
if gate:
|
|
1182
|
+
return gate
|
|
1189
1183
|
from backends.tools_data import cost_optimize
|
|
1190
1184
|
return _with_next_steps("cost_optimize", _safe_call(cost_optimize, target=target))
|
|
1191
1185
|
|
|
@@ -1193,7 +1187,8 @@ def delimit_cost_optimize(target: str = ".") -> Dict[str, Any]:
|
|
|
1193
1187
|
@mcp.tool()
|
|
1194
1188
|
def delimit_cost_alert(action: str = "list", name: Optional[str] = None,
|
|
1195
1189
|
threshold: Optional[float] = None, alert_id: Optional[str] = None) -> Dict[str, Any]:
|
|
1196
|
-
"""
|
|
1190
|
+
""" (Pro).
|
|
1191
|
+
Manage cost alerts (file-based). CRUD operations on spending thresholds.
|
|
1197
1192
|
|
|
1198
1193
|
Args:
|
|
1199
1194
|
action: Action (list/create/delete/toggle).
|
|
@@ -1201,6 +1196,10 @@ def delimit_cost_alert(action: str = "list", name: Optional[str] = None,
|
|
|
1201
1196
|
threshold: Cost threshold in USD (required for create).
|
|
1202
1197
|
alert_id: Alert ID (required for delete/toggle).
|
|
1203
1198
|
"""
|
|
1199
|
+
from ai.license import require_premium
|
|
1200
|
+
gate = require_premium("cost_alert")
|
|
1201
|
+
if gate:
|
|
1202
|
+
return gate
|
|
1204
1203
|
from backends.tools_data import cost_alert
|
|
1205
1204
|
return _with_next_steps("cost_alert", _safe_call(cost_alert, action=action, name=name, threshold=threshold, alert_id=alert_id))
|
|
1206
1205
|
|
|
@@ -1244,7 +1243,8 @@ def delimit_data_backup(target: str = ".") -> Dict[str, Any]:
|
|
|
1244
1243
|
|
|
1245
1244
|
@mcp.tool()
|
|
1246
1245
|
def delimit_obs_metrics(query: str = "system", time_range: str = "1h", source: Optional[str] = None) -> Dict[str, Any]:
|
|
1247
|
-
"""
|
|
1246
|
+
""" (Pro).
|
|
1247
|
+
Query live system metrics (CPU, memory, disk I/O, network).
|
|
1248
1248
|
|
|
1249
1249
|
Query types: cpu, memory, disk, io, network, system (default), all.
|
|
1250
1250
|
Reads directly from /proc for real-time data.
|
|
@@ -1256,13 +1256,18 @@ def delimit_obs_metrics(query: str = "system", time_range: str = "1h", source: O
|
|
|
1256
1256
|
time_range: Time range (e.g. "1h", "24h", "7d").
|
|
1257
1257
|
source: Optional metrics source (prometheus, local).
|
|
1258
1258
|
"""
|
|
1259
|
+
from ai.license import require_premium
|
|
1260
|
+
gate = require_premium("obs_metrics")
|
|
1261
|
+
if gate:
|
|
1262
|
+
return gate
|
|
1259
1263
|
from backends.tools_infra import obs_metrics
|
|
1260
1264
|
return _with_next_steps("obs_metrics", _safe_call(obs_metrics, query=query, time_range=time_range, source=source))
|
|
1261
1265
|
|
|
1262
1266
|
|
|
1263
1267
|
@mcp.tool()
|
|
1264
1268
|
def delimit_obs_logs(query: str, time_range: str = "1h", source: Optional[str] = None) -> Dict[str, Any]:
|
|
1265
|
-
"""
|
|
1269
|
+
""" (Pro).
|
|
1270
|
+
Search system and application logs.
|
|
1266
1271
|
|
|
1267
1272
|
Searches journalctl, /var/log/*, and application log directories.
|
|
1268
1273
|
Returns matching log lines with source attribution.
|
|
@@ -1274,6 +1279,10 @@ def delimit_obs_logs(query: str, time_range: str = "1h", source: Optional[str] =
|
|
|
1274
1279
|
time_range: Time range (5m, 15m, 1h, 6h, 24h, 7d).
|
|
1275
1280
|
source: Log source path or integration name (journalctl, elasticsearch).
|
|
1276
1281
|
"""
|
|
1282
|
+
from ai.license import require_premium
|
|
1283
|
+
gate = require_premium("obs_logs")
|
|
1284
|
+
if gate:
|
|
1285
|
+
return gate
|
|
1277
1286
|
from backends.tools_infra import obs_logs
|
|
1278
1287
|
return _with_next_steps("obs_logs", _safe_call(obs_logs, query=query, time_range=time_range, source=source))
|
|
1279
1288
|
|
|
@@ -1293,12 +1302,17 @@ def delimit_obs_alerts(action: str, alert_rule: Optional[Dict[str, Any]] = None,
|
|
|
1293
1302
|
|
|
1294
1303
|
@mcp.tool()
|
|
1295
1304
|
def delimit_obs_status() -> Dict[str, Any]:
|
|
1296
|
-
"""
|
|
1305
|
+
""" (Pro).
|
|
1306
|
+
System health check: disk space, memory, running services, uptime.
|
|
1297
1307
|
|
|
1298
1308
|
Checks disk usage, memory, process count, load average, and probes
|
|
1299
1309
|
common service ports (Node, PostgreSQL, Redis, Nginx, etc.).
|
|
1300
1310
|
No external integration needed.
|
|
1301
1311
|
"""
|
|
1312
|
+
from ai.license import require_premium
|
|
1313
|
+
gate = require_premium("obs_status")
|
|
1314
|
+
if gate:
|
|
1315
|
+
return gate
|
|
1302
1316
|
from backends.tools_infra import obs_status
|
|
1303
1317
|
return _with_next_steps("obs_status", _safe_call(obs_status))
|
|
1304
1318
|
|
|
@@ -1864,7 +1878,8 @@ def delimit_deploy_site(
|
|
|
1864
1878
|
project_path: str = ".",
|
|
1865
1879
|
message: str = "",
|
|
1866
1880
|
) -> Dict[str, Any]:
|
|
1867
|
-
"""
|
|
1881
|
+
""" (Pro).
|
|
1882
|
+
Deploy a site — git commit, push, Vercel build, and deploy in one step.
|
|
1868
1883
|
|
|
1869
1884
|
Handles the full chain: stages changes, commits, pushes to remote,
|
|
1870
1885
|
builds with Vercel, deploys to production. No manual steps needed.
|
|
@@ -1873,6 +1888,10 @@ def delimit_deploy_site(
|
|
|
1873
1888
|
project_path: Path to the site project (must have .vercel/ configured).
|
|
1874
1889
|
message: Git commit message. Auto-generated if empty.
|
|
1875
1890
|
"""
|
|
1891
|
+
from ai.license import require_premium
|
|
1892
|
+
gate = require_premium("deploy_site")
|
|
1893
|
+
if gate:
|
|
1894
|
+
return gate
|
|
1876
1895
|
from backends.tools_infra import deploy_site
|
|
1877
1896
|
env_vars = {}
|
|
1878
1897
|
# Auto-detect Delimit UI env vars
|
|
@@ -1892,7 +1911,8 @@ def delimit_deploy_npm(
|
|
|
1892
1911
|
tag: str = "latest",
|
|
1893
1912
|
dry_run: bool = False,
|
|
1894
1913
|
) -> Dict[str, Any]:
|
|
1895
|
-
"""
|
|
1914
|
+
""" (Pro).
|
|
1915
|
+
Publish an npm package — bump version, publish to registry, verify.
|
|
1896
1916
|
|
|
1897
1917
|
Full chain: check auth, bump version, npm publish, verify on registry,
|
|
1898
1918
|
git commit + push the version bump. Use dry_run=true to preview first.
|
|
@@ -1903,6 +1923,10 @@ def delimit_deploy_npm(
|
|
|
1903
1923
|
tag: npm dist-tag (default "latest").
|
|
1904
1924
|
dry_run: If true, preview without actually publishing.
|
|
1905
1925
|
"""
|
|
1926
|
+
from ai.license import require_premium
|
|
1927
|
+
gate = require_premium("deploy_npm")
|
|
1928
|
+
if gate:
|
|
1929
|
+
return gate
|
|
1906
1930
|
from backends.tools_infra import deploy_npm
|
|
1907
1931
|
return _with_next_steps("deploy_npm", deploy_npm(project_path, bump, tag, dry_run))
|
|
1908
1932
|
|
|
@@ -2036,7 +2060,8 @@ def delimit_models(
|
|
|
2036
2060
|
api_key: str = "",
|
|
2037
2061
|
model_name: str = "",
|
|
2038
2062
|
) -> Dict[str, Any]:
|
|
2039
|
-
"""
|
|
2063
|
+
""" (Pro).
|
|
2064
|
+
View and configure AI models for multi-model deliberation.
|
|
2040
2065
|
|
|
2041
2066
|
Actions:
|
|
2042
2067
|
- "list": show configured models and what's available
|
|
@@ -2052,6 +2077,10 @@ def delimit_models(
|
|
|
2052
2077
|
api_key: API key for the provider (only used with action=add).
|
|
2053
2078
|
model_name: Optional model name override (e.g. "gpt-4o", "claude-sonnet-4-5-20250514").
|
|
2054
2079
|
"""
|
|
2080
|
+
from ai.license import require_premium
|
|
2081
|
+
gate = require_premium("models")
|
|
2082
|
+
if gate:
|
|
2083
|
+
return gate
|
|
2055
2084
|
from ai.deliberation import configure_models, get_models_config, MODELS_CONFIG, DEFAULT_MODELS
|
|
2056
2085
|
import json as _json
|
|
2057
2086
|
|
|
@@ -2144,14 +2173,9 @@ def delimit_deliberate(
|
|
|
2144
2173
|
max_rounds: int = 3,
|
|
2145
2174
|
save_path: str = "",
|
|
2146
2175
|
) -> Dict[str, Any]:
|
|
2147
|
-
"""Run multi-model consensus via real AI-to-AI deliberation.
|
|
2148
|
-
|
|
2149
|
-
This is the consensus tool. Models (Grok 4, Gemini, Codex) debate each other
|
|
2150
|
-
directly until they reach unanimous agreement.
|
|
2176
|
+
"""Run multi-model consensus via real AI-to-AI deliberation (Pro).
|
|
2151
2177
|
|
|
2152
|
-
|
|
2153
|
-
- "dialogue": Short conversational turns like a group chat (default, 6 rounds)
|
|
2154
|
-
- "debate": Long-form essays with full counter-arguments (3 rounds)
|
|
2178
|
+
Models (Grok 4, Gemini, Codex) debate each other directly until unanimous agreement.
|
|
2155
2179
|
|
|
2156
2180
|
Args:
|
|
2157
2181
|
question: The question to reach consensus on.
|
|
@@ -2160,6 +2184,10 @@ def delimit_deliberate(
|
|
|
2160
2184
|
max_rounds: Maximum rounds (default 3 for debate, 6 for dialogue).
|
|
2161
2185
|
save_path: Optional file path to save the full transcript.
|
|
2162
2186
|
"""
|
|
2187
|
+
from ai.license import require_premium
|
|
2188
|
+
gate = require_premium("deliberate")
|
|
2189
|
+
if gate:
|
|
2190
|
+
return gate
|
|
2163
2191
|
from ai.deliberation import deliberate
|
|
2164
2192
|
result = deliberate(
|
|
2165
2193
|
question=question,
|
|
@@ -2271,7 +2299,8 @@ def _extract_deliberation_actions(result: Dict, question: str) -> List[Dict[str,
|
|
|
2271
2299
|
|
|
2272
2300
|
@mcp.tool()
|
|
2273
2301
|
def delimit_release_sync(action: str = "audit") -> Dict[str, Any]:
|
|
2274
|
-
"""
|
|
2302
|
+
""" (Pro).
|
|
2303
|
+
Audit or sync all public surfaces for consistency.
|
|
2275
2304
|
|
|
2276
2305
|
Checks GitHub repos, npm, site meta tags, CLAUDE.md, and releases
|
|
2277
2306
|
against a central config. Reports what's stale and what needs updating.
|
|
@@ -2279,6 +2308,10 @@ def delimit_release_sync(action: str = "audit") -> Dict[str, Any]:
|
|
|
2279
2308
|
Args:
|
|
2280
2309
|
action: "audit" to check all surfaces, "config" to view/edit the release config.
|
|
2281
2310
|
"""
|
|
2311
|
+
from ai.license import require_premium
|
|
2312
|
+
gate = require_premium("release_sync")
|
|
2313
|
+
if gate:
|
|
2314
|
+
return gate
|
|
2282
2315
|
from ai.release_sync import audit, get_release_config
|
|
2283
2316
|
if action == "config":
|
|
2284
2317
|
return get_release_config()
|
package/package.json
CHANGED