delimit-cli 3.1.1 → 3.3.0
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/bin/delimit-cli.js +33 -0
- package/bin/delimit-setup.js +39 -2
- package/gateway/ai/license.py +133 -0
- package/gateway/ai/schemas/__init__.py +0 -0
- package/gateway/ai/server.py +355 -71
- package/package.json +1 -1
- package/server.json +22 -0
package/bin/delimit-cli.js
CHANGED
|
@@ -1194,6 +1194,39 @@ program
|
|
|
1194
1194
|
require('./delimit-setup.js');
|
|
1195
1195
|
});
|
|
1196
1196
|
|
|
1197
|
+
// Activate license key
|
|
1198
|
+
program
|
|
1199
|
+
.command('activate <key>')
|
|
1200
|
+
.description('Activate a Delimit Pro license key')
|
|
1201
|
+
.action(async (key) => {
|
|
1202
|
+
const os = require('os');
|
|
1203
|
+
const licenseDir = path.join(os.homedir(), '.delimit');
|
|
1204
|
+
const licensePath = path.join(licenseDir, 'license.json');
|
|
1205
|
+
|
|
1206
|
+
if (!key || key.length < 10) {
|
|
1207
|
+
console.error(chalk.red('Invalid license key format. Keys are at least 10 characters.'));
|
|
1208
|
+
process.exit(1);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// Write license file
|
|
1212
|
+
const crypto = require('crypto');
|
|
1213
|
+
const machineHash = crypto.createHash('sha256').update(os.homedir()).digest('hex').slice(0, 16);
|
|
1214
|
+
const licenseData = {
|
|
1215
|
+
key: key,
|
|
1216
|
+
tier: 'pro',
|
|
1217
|
+
valid: true,
|
|
1218
|
+
activated_at: Date.now() / 1000,
|
|
1219
|
+
machine_hash: machineHash,
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1222
|
+
if (!fs.existsSync(licenseDir)) {
|
|
1223
|
+
fs.mkdirSync(licenseDir, { recursive: true });
|
|
1224
|
+
}
|
|
1225
|
+
fs.writeFileSync(licensePath, JSON.stringify(licenseData, null, 2));
|
|
1226
|
+
console.log(chalk.green('License activated successfully.'));
|
|
1227
|
+
console.log(chalk.dim('Tier: pro'));
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1197
1230
|
// Hide legacy/internal commands from --help
|
|
1198
1231
|
['install', 'mode', 'status', 'policy', 'auth', 'audit',
|
|
1199
1232
|
'explain-decision', 'uninstall', 'proxy', 'hook'].forEach(name => {
|
package/bin/delimit-setup.js
CHANGED
|
@@ -262,8 +262,45 @@ Run full governance compliance checks. Verify security, policy compliance, evide
|
|
|
262
262
|
}
|
|
263
263
|
log(` ${green('✓')} ${installed} agents installed (${Object.keys(agents).length - installed} already existed)`);
|
|
264
264
|
|
|
265
|
-
// Step 5:
|
|
266
|
-
step(5, '
|
|
265
|
+
// Step 5: Create CLAUDE.md for first-run guidance
|
|
266
|
+
step(5, 'Setting up first-run guidance...');
|
|
267
|
+
|
|
268
|
+
const claudeMd = path.join(os.homedir(), 'CLAUDE.md');
|
|
269
|
+
if (!fs.existsSync(claudeMd)) {
|
|
270
|
+
fs.writeFileSync(claudeMd, `# Delimit AI Guardrails
|
|
271
|
+
|
|
272
|
+
Delimit governance tools are installed. On first use, try:
|
|
273
|
+
|
|
274
|
+
- "check governance health" — see the status of this project
|
|
275
|
+
- "initialize governance" — set up policies and ledger for this project
|
|
276
|
+
- "run test coverage" — measure test coverage
|
|
277
|
+
- "analyze this repo" — get a health report
|
|
278
|
+
|
|
279
|
+
## Quick Start
|
|
280
|
+
If this project hasn't been initialized for governance yet, say:
|
|
281
|
+
"initialize governance for this project"
|
|
282
|
+
|
|
283
|
+
This creates .delimit/policies.yml and a ledger directory.
|
|
284
|
+
|
|
285
|
+
## Available Agents
|
|
286
|
+
- /lint — check API specs for breaking changes
|
|
287
|
+
- /engineering — build, test, refactor with governance checks
|
|
288
|
+
- /governance — full compliance audit
|
|
289
|
+
|
|
290
|
+
## Key Tools
|
|
291
|
+
- delimit_init — bootstrap governance for a project
|
|
292
|
+
- delimit_lint — diff two OpenAPI specs
|
|
293
|
+
- delimit_test_coverage — measure test coverage
|
|
294
|
+
- delimit_gov_health — check governance status
|
|
295
|
+
- delimit_repo_analyze — full repo health report
|
|
296
|
+
`);
|
|
297
|
+
log(` ${green('✓')} Created ${claudeMd} with first-run guidance`);
|
|
298
|
+
} else {
|
|
299
|
+
log(` ${dim(' CLAUDE.md already exists — skipped')}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Step 6: Summary
|
|
303
|
+
step(6, 'Done!');
|
|
267
304
|
log('');
|
|
268
305
|
log(` ${green('Delimit is installed.')} Your AI agents are now monitored.`);
|
|
269
306
|
log('');
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Delimit license validation.
|
|
3
|
+
Free tools work without a key. Premium tools check for a valid key.
|
|
4
|
+
Validates against Lemon Squeezy API when online, falls back to local cache.
|
|
5
|
+
"""
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import time
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
LICENSE_FILE = Path.home() / ".delimit" / "license.json"
|
|
13
|
+
LS_VALIDATE_URL = "https://api.lemonsqueezy.com/v1/licenses/validate"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_license() -> dict:
|
|
17
|
+
"""Load license from ~/.delimit/license.json"""
|
|
18
|
+
if not LICENSE_FILE.exists():
|
|
19
|
+
return {"tier": "free", "valid": True}
|
|
20
|
+
try:
|
|
21
|
+
data = json.loads(LICENSE_FILE.read_text())
|
|
22
|
+
if data.get("expires_at") and data["expires_at"] < time.time():
|
|
23
|
+
return {"tier": "free", "valid": True, "expired": True}
|
|
24
|
+
return data
|
|
25
|
+
except Exception:
|
|
26
|
+
return {"tier": "free", "valid": True}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def is_premium() -> bool:
|
|
30
|
+
"""Check if user has a premium license."""
|
|
31
|
+
lic = get_license()
|
|
32
|
+
return lic.get("tier") in ("pro", "enterprise") and lic.get("valid", False)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def require_premium(tool_name: str) -> dict | None:
|
|
36
|
+
"""Check premium access. Returns None if allowed, error dict if not."""
|
|
37
|
+
if is_premium():
|
|
38
|
+
return None
|
|
39
|
+
return {
|
|
40
|
+
"error": f"'{tool_name}' requires Delimit Pro. Upgrade at https://delimit.ai/pricing",
|
|
41
|
+
"status": "premium_required",
|
|
42
|
+
"tool": tool_name,
|
|
43
|
+
"current_tier": get_license().get("tier", "free"),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def activate_license(key: str) -> dict:
|
|
48
|
+
"""Activate a license key via Lemon Squeezy API.
|
|
49
|
+
Falls back to local validation if API is unreachable."""
|
|
50
|
+
if not key or len(key) < 10:
|
|
51
|
+
return {"error": "Invalid license key format"}
|
|
52
|
+
|
|
53
|
+
machine_hash = hashlib.sha256(str(Path.home()).encode()).hexdigest()[:16]
|
|
54
|
+
|
|
55
|
+
# Try Lemon Squeezy remote validation
|
|
56
|
+
try:
|
|
57
|
+
import urllib.request
|
|
58
|
+
import urllib.error
|
|
59
|
+
|
|
60
|
+
data = json.dumps({
|
|
61
|
+
"license_key": key,
|
|
62
|
+
"instance_name": machine_hash,
|
|
63
|
+
}).encode()
|
|
64
|
+
|
|
65
|
+
req = urllib.request.Request(
|
|
66
|
+
LS_VALIDATE_URL,
|
|
67
|
+
data=data,
|
|
68
|
+
headers={
|
|
69
|
+
"Content-Type": "application/json",
|
|
70
|
+
"Accept": "application/json",
|
|
71
|
+
},
|
|
72
|
+
method="POST",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
76
|
+
result = json.loads(resp.read())
|
|
77
|
+
|
|
78
|
+
if result.get("valid"):
|
|
79
|
+
license_data = {
|
|
80
|
+
"key": key,
|
|
81
|
+
"tier": "pro",
|
|
82
|
+
"valid": True,
|
|
83
|
+
"activated_at": time.time(),
|
|
84
|
+
"machine_hash": machine_hash,
|
|
85
|
+
"instance_id": result.get("instance", {}).get("id"),
|
|
86
|
+
"license_id": result.get("license_key", {}).get("id"),
|
|
87
|
+
"customer_name": result.get("meta", {}).get("customer_name", ""),
|
|
88
|
+
"validated_via": "lemon_squeezy",
|
|
89
|
+
}
|
|
90
|
+
LICENSE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
91
|
+
LICENSE_FILE.write_text(json.dumps(license_data, indent=2))
|
|
92
|
+
return {"status": "activated", "tier": "pro", "message": "License activated successfully."}
|
|
93
|
+
else:
|
|
94
|
+
return {
|
|
95
|
+
"error": "Invalid license key. Check your key and try again.",
|
|
96
|
+
"status": "invalid",
|
|
97
|
+
"detail": result.get("error", ""),
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
except (urllib.error.URLError, OSError):
|
|
101
|
+
# API unreachable — accept key locally (offline activation)
|
|
102
|
+
license_data = {
|
|
103
|
+
"key": key,
|
|
104
|
+
"tier": "pro",
|
|
105
|
+
"valid": True,
|
|
106
|
+
"activated_at": time.time(),
|
|
107
|
+
"machine_hash": machine_hash,
|
|
108
|
+
"validated_via": "offline",
|
|
109
|
+
}
|
|
110
|
+
LICENSE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
111
|
+
LICENSE_FILE.write_text(json.dumps(license_data, indent=2))
|
|
112
|
+
return {
|
|
113
|
+
"status": "activated",
|
|
114
|
+
"tier": "pro",
|
|
115
|
+
"message": "License activated (offline). Will validate online next time.",
|
|
116
|
+
}
|
|
117
|
+
except Exception as e:
|
|
118
|
+
# Unexpected error — still activate locally
|
|
119
|
+
license_data = {
|
|
120
|
+
"key": key,
|
|
121
|
+
"tier": "pro",
|
|
122
|
+
"valid": True,
|
|
123
|
+
"activated_at": time.time(),
|
|
124
|
+
"machine_hash": machine_hash,
|
|
125
|
+
"validated_via": "fallback",
|
|
126
|
+
}
|
|
127
|
+
LICENSE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
LICENSE_FILE.write_text(json.dumps(license_data, indent=2))
|
|
129
|
+
return {
|
|
130
|
+
"status": "activated",
|
|
131
|
+
"tier": "pro",
|
|
132
|
+
"message": f"License activated (validation error: {e}). Will retry online later.",
|
|
133
|
+
}
|
|
File without changes
|
package/gateway/ai/server.py
CHANGED
|
@@ -36,7 +36,21 @@ logger = logging.getLogger("delimit.ai")
|
|
|
36
36
|
mcp = FastMCP("delimit")
|
|
37
37
|
mcp.description = "Delimit — The smart lint engine for OpenAPI. Unified agent surface."
|
|
38
38
|
|
|
39
|
-
VERSION = "2.0
|
|
39
|
+
VERSION = "3.2.0"
|
|
40
|
+
|
|
41
|
+
# LED-044: Hide STUB and PASS-THROUGH tools from MCP unless opted in.
|
|
42
|
+
# Set DELIMIT_SHOW_EXPERIMENTAL=1 to expose all tools (internal development).
|
|
43
|
+
SHOW_EXPERIMENTAL = os.environ.get("DELIMIT_SHOW_EXPERIMENTAL", "") == "1"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _experimental_tool():
|
|
47
|
+
"""Decorator that only registers the function as an MCP tool if SHOW_EXPERIMENTAL is set.
|
|
48
|
+
When disabled, the function still exists but is not exposed via MCP."""
|
|
49
|
+
def decorator(fn):
|
|
50
|
+
if SHOW_EXPERIMENTAL:
|
|
51
|
+
return mcp.tool()(fn)
|
|
52
|
+
return fn
|
|
53
|
+
return decorator
|
|
40
54
|
|
|
41
55
|
|
|
42
56
|
def _safe_call(fn, **kwargs) -> Dict[str, Any]:
|
|
@@ -266,7 +280,7 @@ def delimit_init(
|
|
|
266
280
|
|
|
267
281
|
@mcp.tool()
|
|
268
282
|
def delimit_os_plan(operation: str, target: str, parameters: Optional[Dict[str, Any]] = None, require_approval: bool = True) -> Dict[str, Any]:
|
|
269
|
-
"""Create a governed execution plan.
|
|
283
|
+
"""Create a governed execution plan (Pro).
|
|
270
284
|
|
|
271
285
|
Args:
|
|
272
286
|
operation: Operation to plan (e.g. "deploy", "migrate").
|
|
@@ -274,24 +288,36 @@ def delimit_os_plan(operation: str, target: str, parameters: Optional[Dict[str,
|
|
|
274
288
|
parameters: Operation parameters.
|
|
275
289
|
require_approval: Whether to require approval before execution.
|
|
276
290
|
"""
|
|
291
|
+
from ai.license import require_premium
|
|
292
|
+
gate = require_premium("os_plan")
|
|
293
|
+
if gate:
|
|
294
|
+
return gate
|
|
277
295
|
from backends.os_bridge import create_plan
|
|
278
296
|
return _safe_call(create_plan, operation=operation, target=target, parameters=parameters, require_approval=require_approval)
|
|
279
297
|
|
|
280
298
|
|
|
281
299
|
@mcp.tool()
|
|
282
300
|
def delimit_os_status() -> Dict[str, Any]:
|
|
283
|
-
"""Get current Delimit OS status with plan/task/token counts."""
|
|
301
|
+
"""Get current Delimit OS status with plan/task/token counts (Pro)."""
|
|
302
|
+
from ai.license import require_premium
|
|
303
|
+
gate = require_premium("os_status")
|
|
304
|
+
if gate:
|
|
305
|
+
return gate
|
|
284
306
|
from backends.os_bridge import get_status
|
|
285
307
|
return _safe_call(get_status)
|
|
286
308
|
|
|
287
309
|
|
|
288
310
|
@mcp.tool()
|
|
289
311
|
def delimit_os_gates(plan_id: str) -> Dict[str, Any]:
|
|
290
|
-
"""Check governance gates for a plan.
|
|
312
|
+
"""Check governance gates for a plan (Pro).
|
|
291
313
|
|
|
292
314
|
Args:
|
|
293
315
|
plan_id: The plan ID (e.g. "PLAN-A1B2C3D4").
|
|
294
316
|
"""
|
|
317
|
+
from ai.license import require_premium
|
|
318
|
+
gate = require_premium("os_gates")
|
|
319
|
+
if gate:
|
|
320
|
+
return gate
|
|
295
321
|
from backends.os_bridge import check_gates
|
|
296
322
|
return _safe_call(check_gates, plan_id=plan_id)
|
|
297
323
|
|
|
@@ -300,53 +326,69 @@ def delimit_os_gates(plan_id: str) -> Dict[str, Any]:
|
|
|
300
326
|
|
|
301
327
|
@mcp.tool()
|
|
302
328
|
def delimit_gov_health(repo: str = ".") -> Dict[str, Any]:
|
|
303
|
-
"""Check governance system health.
|
|
329
|
+
"""Check governance system health (Pro).
|
|
304
330
|
|
|
305
331
|
Args:
|
|
306
332
|
repo: Repository path to check.
|
|
307
333
|
"""
|
|
334
|
+
from ai.license import require_premium
|
|
335
|
+
gate = require_premium("gov_health")
|
|
336
|
+
if gate:
|
|
337
|
+
return gate
|
|
308
338
|
from backends.governance_bridge import health
|
|
309
339
|
return _safe_call(health, repo=repo)
|
|
310
340
|
|
|
311
341
|
|
|
312
342
|
@mcp.tool()
|
|
313
343
|
def delimit_gov_status(repo: str = ".") -> Dict[str, Any]:
|
|
314
|
-
"""Get current governance status for a repository.
|
|
344
|
+
"""Get current governance status for a repository (Pro).
|
|
315
345
|
|
|
316
346
|
Args:
|
|
317
347
|
repo: Repository path.
|
|
318
348
|
"""
|
|
349
|
+
from ai.license import require_premium
|
|
350
|
+
gate = require_premium("gov_status")
|
|
351
|
+
if gate:
|
|
352
|
+
return gate
|
|
319
353
|
from backends.governance_bridge import status
|
|
320
354
|
return _safe_call(status, repo=repo)
|
|
321
355
|
|
|
322
356
|
|
|
323
357
|
@mcp.tool()
|
|
324
358
|
def delimit_gov_policy(repo: str = ".") -> Dict[str, Any]:
|
|
325
|
-
"""Get governance policy for a repository.
|
|
359
|
+
"""Get governance policy for a repository (Pro).
|
|
326
360
|
|
|
327
361
|
Args:
|
|
328
362
|
repo: Repository path.
|
|
329
363
|
"""
|
|
364
|
+
from ai.license import require_premium
|
|
365
|
+
gate = require_premium("gov_policy")
|
|
366
|
+
if gate:
|
|
367
|
+
return gate
|
|
330
368
|
from backends.governance_bridge import policy
|
|
331
369
|
return _safe_call(policy, repo=repo)
|
|
332
370
|
|
|
333
371
|
|
|
334
372
|
@mcp.tool()
|
|
335
373
|
def delimit_gov_evaluate(action: str, context: Optional[Dict[str, Any]] = None, repo: str = ".") -> Dict[str, Any]:
|
|
336
|
-
"""Evaluate if governance is required for an action (requires governancegate).
|
|
374
|
+
"""Evaluate if governance is required for an action (requires governancegate) (Pro).
|
|
337
375
|
|
|
338
376
|
Args:
|
|
339
377
|
action: The action to evaluate.
|
|
340
378
|
context: Additional context.
|
|
341
379
|
repo: Repository path.
|
|
342
380
|
"""
|
|
381
|
+
from ai.license import require_premium
|
|
382
|
+
gate = require_premium("gov_evaluate")
|
|
383
|
+
if gate:
|
|
384
|
+
return gate
|
|
343
385
|
from backends.governance_bridge import evaluate_trigger
|
|
344
386
|
return _safe_call(evaluate_trigger, action=action, context=context, repo=repo)
|
|
345
387
|
|
|
346
388
|
|
|
347
389
|
@mcp.tool()
|
|
348
390
|
def delimit_gov_new_task(title: str, scope: str, risk_level: str = "medium", repo: str = ".") -> Dict[str, Any]:
|
|
349
|
-
"""Create a new governance task (requires governancegate).
|
|
391
|
+
"""Create a new governance task (requires governancegate) (Pro).
|
|
350
392
|
|
|
351
393
|
Args:
|
|
352
394
|
title: Task title.
|
|
@@ -354,30 +396,42 @@ def delimit_gov_new_task(title: str, scope: str, risk_level: str = "medium", rep
|
|
|
354
396
|
risk_level: Risk level (low/medium/high/critical).
|
|
355
397
|
repo: Repository path.
|
|
356
398
|
"""
|
|
399
|
+
from ai.license import require_premium
|
|
400
|
+
gate = require_premium("gov_new_task")
|
|
401
|
+
if gate:
|
|
402
|
+
return gate
|
|
357
403
|
from backends.governance_bridge import new_task
|
|
358
404
|
return _safe_call(new_task, title=title, scope=scope, risk_level=risk_level, repo=repo)
|
|
359
405
|
|
|
360
406
|
|
|
361
407
|
@mcp.tool()
|
|
362
408
|
def delimit_gov_run(task_id: str, repo: str = ".") -> Dict[str, Any]:
|
|
363
|
-
"""Run a governance task (requires governancegate).
|
|
409
|
+
"""Run a governance task (requires governancegate) (Pro).
|
|
364
410
|
|
|
365
411
|
Args:
|
|
366
412
|
task_id: Task ID to run.
|
|
367
413
|
repo: Repository path.
|
|
368
414
|
"""
|
|
415
|
+
from ai.license import require_premium
|
|
416
|
+
gate = require_premium("gov_run")
|
|
417
|
+
if gate:
|
|
418
|
+
return gate
|
|
369
419
|
from backends.governance_bridge import run_task
|
|
370
420
|
return _safe_call(run_task, task_id=task_id, repo=repo)
|
|
371
421
|
|
|
372
422
|
|
|
373
423
|
@mcp.tool()
|
|
374
424
|
def delimit_gov_verify(task_id: str, repo: str = ".") -> Dict[str, Any]:
|
|
375
|
-
"""Verify a governance task (requires governancegate).
|
|
425
|
+
"""Verify a governance task (requires governancegate) (Pro).
|
|
376
426
|
|
|
377
427
|
Args:
|
|
378
428
|
task_id: Task ID to verify.
|
|
379
429
|
repo: Repository path.
|
|
380
430
|
"""
|
|
431
|
+
from ai.license import require_premium
|
|
432
|
+
gate = require_premium("gov_verify")
|
|
433
|
+
if gate:
|
|
434
|
+
return gate
|
|
381
435
|
from backends.governance_bridge import verify
|
|
382
436
|
return _safe_call(verify, task_id=task_id, repo=repo)
|
|
383
437
|
|
|
@@ -386,36 +440,48 @@ def delimit_gov_verify(task_id: str, repo: str = ".") -> Dict[str, Any]:
|
|
|
386
440
|
|
|
387
441
|
@mcp.tool()
|
|
388
442
|
def delimit_memory_search(query: str, limit: int = 10) -> Dict[str, Any]:
|
|
389
|
-
"""Search conversation memory semantically.
|
|
443
|
+
"""Search conversation memory semantically (Pro).
|
|
390
444
|
|
|
391
445
|
Args:
|
|
392
446
|
query: Natural language search query.
|
|
393
447
|
limit: Maximum results to return.
|
|
394
448
|
"""
|
|
449
|
+
from ai.license import require_premium
|
|
450
|
+
gate = require_premium("memory_search")
|
|
451
|
+
if gate:
|
|
452
|
+
return gate
|
|
395
453
|
from backends.memory_bridge import search
|
|
396
454
|
return _safe_call(search, query=query, limit=limit)
|
|
397
455
|
|
|
398
456
|
|
|
399
457
|
@mcp.tool()
|
|
400
458
|
def delimit_memory_store(content: str, tags: Optional[List[str]] = None, context: Optional[str] = None) -> Dict[str, Any]:
|
|
401
|
-
"""Store a memory entry for future retrieval.
|
|
459
|
+
"""Store a memory entry for future retrieval (Pro).
|
|
402
460
|
|
|
403
461
|
Args:
|
|
404
462
|
content: The content to remember.
|
|
405
463
|
tags: Optional categorization tags.
|
|
406
464
|
context: Optional context about when/why this was stored.
|
|
407
465
|
"""
|
|
466
|
+
from ai.license import require_premium
|
|
467
|
+
gate = require_premium("memory_store")
|
|
468
|
+
if gate:
|
|
469
|
+
return gate
|
|
408
470
|
from backends.memory_bridge import store
|
|
409
471
|
return _safe_call(store, content=content, tags=tags, context=context)
|
|
410
472
|
|
|
411
473
|
|
|
412
474
|
@mcp.tool()
|
|
413
475
|
def delimit_memory_recent(limit: int = 5) -> Dict[str, Any]:
|
|
414
|
-
"""Get recent work summary from memory.
|
|
476
|
+
"""Get recent work summary from memory (Pro).
|
|
415
477
|
|
|
416
478
|
Args:
|
|
417
479
|
limit: Number of recent entries to return.
|
|
418
480
|
"""
|
|
481
|
+
from ai.license import require_premium
|
|
482
|
+
gate = require_premium("memory_recent")
|
|
483
|
+
if gate:
|
|
484
|
+
return gate
|
|
419
485
|
from backends.memory_bridge import get_recent
|
|
420
486
|
return _safe_call(get_recent, limit=limit)
|
|
421
487
|
|
|
@@ -424,25 +490,37 @@ def delimit_memory_recent(limit: int = 5) -> Dict[str, Any]:
|
|
|
424
490
|
|
|
425
491
|
@mcp.tool()
|
|
426
492
|
def delimit_vault_search(query: str) -> Dict[str, Any]:
|
|
427
|
-
"""Search vault entries.
|
|
493
|
+
"""Search vault entries (Pro).
|
|
428
494
|
|
|
429
495
|
Args:
|
|
430
496
|
query: Search query for vault entries.
|
|
431
497
|
"""
|
|
498
|
+
from ai.license import require_premium
|
|
499
|
+
gate = require_premium("vault_search")
|
|
500
|
+
if gate:
|
|
501
|
+
return gate
|
|
432
502
|
from backends.vault_bridge import search
|
|
433
503
|
return _safe_call(search, query=query)
|
|
434
504
|
|
|
435
505
|
|
|
436
506
|
@mcp.tool()
|
|
437
507
|
def delimit_vault_health() -> Dict[str, Any]:
|
|
438
|
-
"""Check vault health status."""
|
|
508
|
+
"""Check vault health status (Pro)."""
|
|
509
|
+
from ai.license import require_premium
|
|
510
|
+
gate = require_premium("vault_health")
|
|
511
|
+
if gate:
|
|
512
|
+
return gate
|
|
439
513
|
from backends.vault_bridge import health
|
|
440
514
|
return _safe_call(health)
|
|
441
515
|
|
|
442
516
|
|
|
443
517
|
@mcp.tool()
|
|
444
518
|
def delimit_vault_snapshot() -> Dict[str, Any]:
|
|
445
|
-
"""Get a vault state snapshot."""
|
|
519
|
+
"""Get a vault state snapshot (Pro)."""
|
|
520
|
+
from ai.license import require_premium
|
|
521
|
+
gate = require_premium("vault_snapshot")
|
|
522
|
+
if gate:
|
|
523
|
+
return gate
|
|
446
524
|
from backends.vault_bridge import snapshot
|
|
447
525
|
return _safe_call(snapshot)
|
|
448
526
|
|
|
@@ -456,82 +534,106 @@ def delimit_vault_snapshot() -> Dict[str, Any]:
|
|
|
456
534
|
|
|
457
535
|
@mcp.tool()
|
|
458
536
|
def delimit_deploy_plan(app: str, env: str, git_ref: Optional[str] = None) -> Dict[str, Any]:
|
|
459
|
-
"""Plan deployment with build steps.
|
|
537
|
+
"""Plan deployment with build steps (Pro).
|
|
460
538
|
|
|
461
539
|
Args:
|
|
462
540
|
app: Application name.
|
|
463
541
|
env: Target environment (staging/production).
|
|
464
542
|
git_ref: Git reference (branch, tag, or SHA).
|
|
465
543
|
"""
|
|
544
|
+
from ai.license import require_premium
|
|
545
|
+
gate = require_premium("deploy_plan")
|
|
546
|
+
if gate:
|
|
547
|
+
return gate
|
|
466
548
|
from backends.deploy_bridge import plan
|
|
467
549
|
return _safe_call(plan, app=app, env=env, git_ref=git_ref)
|
|
468
550
|
|
|
469
551
|
|
|
470
552
|
@mcp.tool()
|
|
471
553
|
def delimit_deploy_build(app: str, git_ref: Optional[str] = None) -> Dict[str, Any]:
|
|
472
|
-
"""Build Docker images with SHA tags.
|
|
554
|
+
"""Build Docker images with SHA tags (Pro).
|
|
473
555
|
|
|
474
556
|
Args:
|
|
475
557
|
app: Application name.
|
|
476
558
|
git_ref: Git reference.
|
|
477
559
|
"""
|
|
560
|
+
from ai.license import require_premium
|
|
561
|
+
gate = require_premium("deploy_build")
|
|
562
|
+
if gate:
|
|
563
|
+
return gate
|
|
478
564
|
from backends.deploy_bridge import build
|
|
479
565
|
return _safe_call(build, app=app, git_ref=git_ref)
|
|
480
566
|
|
|
481
567
|
|
|
482
568
|
@mcp.tool()
|
|
483
569
|
def delimit_deploy_publish(app: str, git_ref: Optional[str] = None) -> Dict[str, Any]:
|
|
484
|
-
"""Publish images to registry.
|
|
570
|
+
"""Publish images to registry (Pro).
|
|
485
571
|
|
|
486
572
|
Args:
|
|
487
573
|
app: Application name.
|
|
488
574
|
git_ref: Git reference.
|
|
489
575
|
"""
|
|
576
|
+
from ai.license import require_premium
|
|
577
|
+
gate = require_premium("deploy_publish")
|
|
578
|
+
if gate:
|
|
579
|
+
return gate
|
|
490
580
|
from backends.deploy_bridge import publish
|
|
491
581
|
return _safe_call(publish, app=app, git_ref=git_ref)
|
|
492
582
|
|
|
493
583
|
|
|
494
|
-
@
|
|
584
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
495
585
|
def delimit_deploy_verify(app: str, env: str, git_ref: Optional[str] = None) -> Dict[str, Any]:
|
|
496
|
-
"""Verify deployment health (experimental).
|
|
586
|
+
"""Verify deployment health (experimental) (Pro).
|
|
497
587
|
|
|
498
588
|
Args:
|
|
499
589
|
app: Application name.
|
|
500
590
|
env: Target environment.
|
|
501
591
|
git_ref: Git reference.
|
|
502
592
|
"""
|
|
593
|
+
from ai.license import require_premium
|
|
594
|
+
gate = require_premium("deploy_verify")
|
|
595
|
+
if gate:
|
|
596
|
+
return gate
|
|
503
597
|
from backends.deploy_bridge import verify
|
|
504
598
|
return _safe_call(verify, app=app, env=env, git_ref=git_ref)
|
|
505
599
|
|
|
506
600
|
|
|
507
601
|
@mcp.tool()
|
|
508
602
|
def delimit_deploy_rollback(app: str, env: str, to_sha: Optional[str] = None) -> Dict[str, Any]:
|
|
509
|
-
"""Rollback to previous SHA.
|
|
603
|
+
"""Rollback to previous SHA (Pro).
|
|
510
604
|
|
|
511
605
|
Args:
|
|
512
606
|
app: Application name.
|
|
513
607
|
env: Target environment.
|
|
514
608
|
to_sha: SHA to rollback to.
|
|
515
609
|
"""
|
|
610
|
+
from ai.license import require_premium
|
|
611
|
+
gate = require_premium("deploy_rollback")
|
|
612
|
+
if gate:
|
|
613
|
+
return gate
|
|
516
614
|
from backends.deploy_bridge import rollback
|
|
517
615
|
return _safe_call(rollback, app=app, env=env, to_sha=to_sha)
|
|
518
616
|
|
|
519
617
|
|
|
520
618
|
@mcp.tool()
|
|
521
619
|
def delimit_deploy_status(app: str, env: str) -> Dict[str, Any]:
|
|
522
|
-
"""Get deployment status.
|
|
620
|
+
"""Get deployment status (Pro).
|
|
523
621
|
|
|
524
622
|
Args:
|
|
525
623
|
app: Application name.
|
|
526
624
|
env: Target environment.
|
|
527
625
|
"""
|
|
626
|
+
from ai.license import require_premium
|
|
627
|
+
gate = require_premium("deploy_status")
|
|
628
|
+
if gate:
|
|
629
|
+
return gate
|
|
528
630
|
from backends.deploy_bridge import status
|
|
529
631
|
return _safe_call(status, app=app, env=env)
|
|
530
632
|
|
|
531
633
|
|
|
532
634
|
# ─── Intel ──────────────────────────────────────────────────────────────
|
|
533
635
|
|
|
534
|
-
@
|
|
636
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
535
637
|
def delimit_intel_dataset_register(name: str, schema: Dict[str, Any], description: Optional[str] = None) -> Dict[str, Any]:
|
|
536
638
|
"""Register a new dataset with schema (coming soon).
|
|
537
639
|
|
|
@@ -544,14 +646,14 @@ def delimit_intel_dataset_register(name: str, schema: Dict[str, Any], descriptio
|
|
|
544
646
|
return _safe_call(dataset_register, name=name, schema=schema, description=description)
|
|
545
647
|
|
|
546
648
|
|
|
547
|
-
@
|
|
649
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
548
650
|
def delimit_intel_dataset_list() -> Dict[str, Any]:
|
|
549
651
|
"""List registered datasets (coming soon)."""
|
|
550
652
|
from backends.intel_bridge import dataset_list
|
|
551
653
|
return _safe_call(dataset_list)
|
|
552
654
|
|
|
553
655
|
|
|
554
|
-
@
|
|
656
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
555
657
|
def delimit_intel_dataset_freeze(dataset_id: str) -> Dict[str, Any]:
|
|
556
658
|
"""Mark dataset as immutable (coming soon).
|
|
557
659
|
|
|
@@ -562,7 +664,7 @@ def delimit_intel_dataset_freeze(dataset_id: str) -> Dict[str, Any]:
|
|
|
562
664
|
return _safe_call(dataset_freeze, dataset_id=dataset_id)
|
|
563
665
|
|
|
564
666
|
|
|
565
|
-
@
|
|
667
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
566
668
|
def delimit_intel_snapshot_ingest(data: Dict[str, Any], provenance: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
567
669
|
"""Store research snapshot with provenance (coming soon).
|
|
568
670
|
|
|
@@ -574,7 +676,7 @@ def delimit_intel_snapshot_ingest(data: Dict[str, Any], provenance: Optional[Dic
|
|
|
574
676
|
return _safe_call(snapshot_ingest, data=data, provenance=provenance)
|
|
575
677
|
|
|
576
678
|
|
|
577
|
-
@
|
|
679
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
578
680
|
def delimit_intel_query(dataset_id: str, query: str, parameters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
579
681
|
"""Execute deterministic query on dataset (coming soon).
|
|
580
682
|
|
|
@@ -618,46 +720,62 @@ def delimit_generate_scaffold(project_type: str, name: str, packages: Optional[L
|
|
|
618
720
|
|
|
619
721
|
# ─── Repo (RepoDoctor + ConfigSentry) ──────────────────────────────────
|
|
620
722
|
|
|
621
|
-
@
|
|
723
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
622
724
|
def delimit_repo_diagnose(target: str = ".") -> Dict[str, Any]:
|
|
623
|
-
"""Diagnose repository health issues (experimental).
|
|
725
|
+
"""Diagnose repository health issues (experimental) (Pro).
|
|
624
726
|
|
|
625
727
|
Args:
|
|
626
728
|
target: Repository path.
|
|
627
729
|
"""
|
|
730
|
+
from ai.license import require_premium
|
|
731
|
+
gate = require_premium("repo_diagnose")
|
|
732
|
+
if gate:
|
|
733
|
+
return gate
|
|
628
734
|
from backends.repo_bridge import diagnose
|
|
629
735
|
return _safe_call(diagnose, target=target)
|
|
630
736
|
|
|
631
737
|
|
|
632
|
-
@
|
|
738
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
633
739
|
def delimit_repo_analyze(target: str = ".") -> Dict[str, Any]:
|
|
634
|
-
"""Analyze repository structure and quality (experimental).
|
|
740
|
+
"""Analyze repository structure and quality (experimental) (Pro).
|
|
635
741
|
|
|
636
742
|
Args:
|
|
637
743
|
target: Repository path.
|
|
638
744
|
"""
|
|
745
|
+
from ai.license import require_premium
|
|
746
|
+
gate = require_premium("repo_analyze")
|
|
747
|
+
if gate:
|
|
748
|
+
return gate
|
|
639
749
|
from backends.repo_bridge import analyze
|
|
640
750
|
return _safe_call(analyze, target=target)
|
|
641
751
|
|
|
642
752
|
|
|
643
|
-
@
|
|
753
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
644
754
|
def delimit_repo_config_validate(target: str = ".") -> Dict[str, Any]:
|
|
645
|
-
"""Validate configuration files (experimental).
|
|
755
|
+
"""Validate configuration files (experimental) (Pro).
|
|
646
756
|
|
|
647
757
|
Args:
|
|
648
758
|
target: Repository or config path.
|
|
649
759
|
"""
|
|
760
|
+
from ai.license import require_premium
|
|
761
|
+
gate = require_premium("repo_config_validate")
|
|
762
|
+
if gate:
|
|
763
|
+
return gate
|
|
650
764
|
from backends.repo_bridge import config_validate
|
|
651
765
|
return _safe_call(config_validate, target=target)
|
|
652
766
|
|
|
653
767
|
|
|
654
|
-
@
|
|
768
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
655
769
|
def delimit_repo_config_audit(target: str = ".") -> Dict[str, Any]:
|
|
656
|
-
"""Audit configuration compliance (experimental).
|
|
770
|
+
"""Audit configuration compliance (experimental) (Pro).
|
|
657
771
|
|
|
658
772
|
Args:
|
|
659
773
|
target: Repository or config path.
|
|
660
774
|
"""
|
|
775
|
+
from ai.license import require_premium
|
|
776
|
+
gate = require_premium("repo_config_audit")
|
|
777
|
+
if gate:
|
|
778
|
+
return gate
|
|
661
779
|
from backends.repo_bridge import config_audit
|
|
662
780
|
return _safe_call(config_audit, target=target)
|
|
663
781
|
|
|
@@ -666,22 +784,30 @@ def delimit_repo_config_audit(target: str = ".") -> Dict[str, Any]:
|
|
|
666
784
|
|
|
667
785
|
@mcp.tool()
|
|
668
786
|
def delimit_security_scan(target: str = ".") -> Dict[str, Any]:
|
|
669
|
-
"""Scan for security vulnerabilities.
|
|
787
|
+
"""Scan for security vulnerabilities (Pro).
|
|
670
788
|
|
|
671
789
|
Args:
|
|
672
790
|
target: Repository or file path.
|
|
673
791
|
"""
|
|
792
|
+
from ai.license import require_premium
|
|
793
|
+
gate = require_premium("security_scan")
|
|
794
|
+
if gate:
|
|
795
|
+
return gate
|
|
674
796
|
from backends.repo_bridge import security_scan
|
|
675
797
|
return _safe_call(security_scan, target=target)
|
|
676
798
|
|
|
677
799
|
|
|
678
|
-
@
|
|
800
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
679
801
|
def delimit_security_audit(target: str = ".") -> Dict[str, Any]:
|
|
680
|
-
"""Audit security compliance (experimental).
|
|
802
|
+
"""Audit security compliance (experimental) (Pro).
|
|
681
803
|
|
|
682
804
|
Args:
|
|
683
805
|
target: Repository or file path.
|
|
684
806
|
"""
|
|
807
|
+
from ai.license import require_premium
|
|
808
|
+
gate = require_premium("security_audit")
|
|
809
|
+
if gate:
|
|
810
|
+
return gate
|
|
685
811
|
from backends.repo_bridge import security_audit
|
|
686
812
|
return _safe_call(security_audit, target=target)
|
|
687
813
|
|
|
@@ -690,23 +816,31 @@ def delimit_security_audit(target: str = ".") -> Dict[str, Any]:
|
|
|
690
816
|
|
|
691
817
|
@mcp.tool()
|
|
692
818
|
def delimit_evidence_collect(target: str = ".") -> Dict[str, Any]:
|
|
693
|
-
"""Collect evidence artifacts for governance.
|
|
819
|
+
"""Collect evidence artifacts for governance (Pro).
|
|
694
820
|
|
|
695
821
|
Args:
|
|
696
822
|
target: Repository or task path.
|
|
697
823
|
"""
|
|
824
|
+
from ai.license import require_premium
|
|
825
|
+
gate = require_premium("evidence_collect")
|
|
826
|
+
if gate:
|
|
827
|
+
return gate
|
|
698
828
|
from backends.repo_bridge import evidence_collect
|
|
699
829
|
return _safe_call(evidence_collect, target=target)
|
|
700
830
|
|
|
701
831
|
|
|
702
832
|
@mcp.tool()
|
|
703
833
|
def delimit_evidence_verify(bundle_id: Optional[str] = None, bundle_path: Optional[str] = None) -> Dict[str, Any]:
|
|
704
|
-
"""Verify evidence bundle integrity.
|
|
834
|
+
"""Verify evidence bundle integrity (Pro).
|
|
705
835
|
|
|
706
836
|
Args:
|
|
707
837
|
bundle_id: Evidence bundle ID to verify.
|
|
708
838
|
bundle_path: Path to evidence bundle file.
|
|
709
839
|
"""
|
|
840
|
+
from ai.license import require_premium
|
|
841
|
+
gate = require_premium("evidence_verify")
|
|
842
|
+
if gate:
|
|
843
|
+
return gate
|
|
710
844
|
from backends.repo_bridge import evidence_verify
|
|
711
845
|
return _safe_call(evidence_verify, bundle_id=bundle_id, bundle_path=bundle_path)
|
|
712
846
|
|
|
@@ -718,7 +852,7 @@ def delimit_evidence_verify(bundle_id: Optional[str] = None, bundle_path: Option
|
|
|
718
852
|
|
|
719
853
|
# ─── ReleasePilot (Governance Primitive) ────────────────────────────────
|
|
720
854
|
|
|
721
|
-
@
|
|
855
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
722
856
|
def delimit_release_plan(environment: str, version: str, repository: str, services: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
723
857
|
"""Create deployment plan with approval gates (experimental).
|
|
724
858
|
|
|
@@ -732,7 +866,7 @@ def delimit_release_plan(environment: str, version: str, repository: str, servic
|
|
|
732
866
|
return _safe_call(release_plan, environment=environment, version=version, repository=repository, services=services)
|
|
733
867
|
|
|
734
868
|
|
|
735
|
-
@
|
|
869
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
736
870
|
def delimit_release_validate(environment: str, version: str) -> Dict[str, Any]:
|
|
737
871
|
"""Validate release readiness (experimental).
|
|
738
872
|
|
|
@@ -744,7 +878,7 @@ def delimit_release_validate(environment: str, version: str) -> Dict[str, Any]:
|
|
|
744
878
|
return _safe_call(release_validate, environment=environment, version=version)
|
|
745
879
|
|
|
746
880
|
|
|
747
|
-
@
|
|
881
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
748
882
|
def delimit_release_status(environment: str) -> Dict[str, Any]:
|
|
749
883
|
"""Check deployment status (experimental).
|
|
750
884
|
|
|
@@ -755,7 +889,7 @@ def delimit_release_status(environment: str) -> Dict[str, Any]:
|
|
|
755
889
|
return _safe_call(release_status, environment=environment)
|
|
756
890
|
|
|
757
891
|
|
|
758
|
-
@
|
|
892
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
759
893
|
def delimit_release_rollback(environment: str, version: str, to_version: str) -> Dict[str, Any]:
|
|
760
894
|
"""Rollback deployment to previous version (experimental).
|
|
761
895
|
|
|
@@ -768,7 +902,7 @@ def delimit_release_rollback(environment: str, version: str, to_version: str) ->
|
|
|
768
902
|
return _safe_call(release_rollback, environment=environment, version=version, to_version=to_version)
|
|
769
903
|
|
|
770
904
|
|
|
771
|
-
@
|
|
905
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
772
906
|
def delimit_release_history(environment: str, limit: int = 10) -> Dict[str, Any]:
|
|
773
907
|
"""Show release history (experimental).
|
|
774
908
|
|
|
@@ -793,7 +927,7 @@ def delimit_cost_analyze(target: str = ".") -> Dict[str, Any]:
|
|
|
793
927
|
return _safe_call(cost_analyze, target=target)
|
|
794
928
|
|
|
795
929
|
|
|
796
|
-
@
|
|
930
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
797
931
|
def delimit_cost_optimize(target: str = ".") -> Dict[str, Any]:
|
|
798
932
|
"""Generate cost optimization recommendations (experimental).
|
|
799
933
|
|
|
@@ -804,7 +938,7 @@ def delimit_cost_optimize(target: str = ".") -> Dict[str, Any]:
|
|
|
804
938
|
return _safe_call(cost_optimize, target=target)
|
|
805
939
|
|
|
806
940
|
|
|
807
|
-
@
|
|
941
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
808
942
|
def delimit_cost_alert(action: str = "list") -> Dict[str, Any]:
|
|
809
943
|
"""Manage cost alerts and notifications (experimental).
|
|
810
944
|
|
|
@@ -828,7 +962,7 @@ def delimit_data_validate(target: str = ".") -> Dict[str, Any]:
|
|
|
828
962
|
return _safe_call(data_validate, target=target)
|
|
829
963
|
|
|
830
964
|
|
|
831
|
-
@
|
|
965
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
832
966
|
def delimit_data_migrate(target: str = ".") -> Dict[str, Any]:
|
|
833
967
|
"""Execute data migration (experimental, plan-only by default).
|
|
834
968
|
|
|
@@ -839,7 +973,7 @@ def delimit_data_migrate(target: str = ".") -> Dict[str, Any]:
|
|
|
839
973
|
return _safe_call(data_migrate, target=target)
|
|
840
974
|
|
|
841
975
|
|
|
842
|
-
@
|
|
976
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
843
977
|
def delimit_data_backup(target: str = ".") -> Dict[str, Any]:
|
|
844
978
|
"""Create data backups (experimental).
|
|
845
979
|
|
|
@@ -852,7 +986,7 @@ def delimit_data_backup(target: str = ".") -> Dict[str, Any]:
|
|
|
852
986
|
|
|
853
987
|
# ─── ObservabilityOps (Internal OS) ────────────────────────────────────
|
|
854
988
|
|
|
855
|
-
@
|
|
989
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
856
990
|
def delimit_obs_metrics(query: str, time_range: str = "1h", source: Optional[str] = None) -> Dict[str, Any]:
|
|
857
991
|
"""Query and analyze metrics (experimental).
|
|
858
992
|
|
|
@@ -865,7 +999,7 @@ def delimit_obs_metrics(query: str, time_range: str = "1h", source: Optional[str
|
|
|
865
999
|
return _safe_call(obs_metrics, query=query, time_range=time_range, source=source)
|
|
866
1000
|
|
|
867
1001
|
|
|
868
|
-
@
|
|
1002
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
869
1003
|
def delimit_obs_logs(query: str, time_range: str = "1h", source: Optional[str] = None) -> Dict[str, Any]:
|
|
870
1004
|
"""Query and search logs (experimental).
|
|
871
1005
|
|
|
@@ -878,7 +1012,7 @@ def delimit_obs_logs(query: str, time_range: str = "1h", source: Optional[str] =
|
|
|
878
1012
|
return _safe_call(obs_logs, query=query, time_range=time_range, source=source)
|
|
879
1013
|
|
|
880
1014
|
|
|
881
|
-
@
|
|
1015
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
882
1016
|
def delimit_obs_alerts(action: str, alert_rule: Optional[Dict[str, Any]] = None, rule_id: Optional[str] = None) -> Dict[str, Any]:
|
|
883
1017
|
"""Manage alerting rules (experimental).
|
|
884
1018
|
|
|
@@ -891,7 +1025,7 @@ def delimit_obs_alerts(action: str, alert_rule: Optional[Dict[str, Any]] = None,
|
|
|
891
1025
|
return _safe_call(obs_alerts, action=action, alert_rule=alert_rule, rule_id=rule_id)
|
|
892
1026
|
|
|
893
1027
|
|
|
894
|
-
@
|
|
1028
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
895
1029
|
def delimit_obs_status() -> Dict[str, Any]:
|
|
896
1030
|
"""Get observability system status (experimental)."""
|
|
897
1031
|
from backends.ops_bridge import obs_status
|
|
@@ -900,7 +1034,7 @@ def delimit_obs_status() -> Dict[str, Any]:
|
|
|
900
1034
|
|
|
901
1035
|
# ─── DesignSystem (UI Tooling) ──────────────────────────────────────────
|
|
902
1036
|
|
|
903
|
-
@
|
|
1037
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
904
1038
|
def delimit_design_extract_tokens(figma_file_key: str, token_types: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
905
1039
|
"""Extract design tokens from Figma (coming soon).
|
|
906
1040
|
|
|
@@ -912,7 +1046,7 @@ def delimit_design_extract_tokens(figma_file_key: str, token_types: Optional[Lis
|
|
|
912
1046
|
return _safe_call(design_extract_tokens, figma_file_key=figma_file_key, token_types=token_types)
|
|
913
1047
|
|
|
914
1048
|
|
|
915
|
-
@
|
|
1049
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
916
1050
|
def delimit_design_generate_component(component_name: str, figma_node_id: Optional[str] = None, output_path: Optional[str] = None) -> Dict[str, Any]:
|
|
917
1051
|
"""Generate Next.js component from Figma design (coming soon).
|
|
918
1052
|
|
|
@@ -925,7 +1059,7 @@ def delimit_design_generate_component(component_name: str, figma_node_id: Option
|
|
|
925
1059
|
return _safe_call(design_generate_component, component_name=component_name, figma_node_id=figma_node_id, output_path=output_path)
|
|
926
1060
|
|
|
927
1061
|
|
|
928
|
-
@
|
|
1062
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
929
1063
|
def delimit_design_generate_tailwind(figma_file_key: str, output_path: Optional[str] = None) -> Dict[str, Any]:
|
|
930
1064
|
"""Generate Tailwind config from Figma design tokens (coming soon).
|
|
931
1065
|
|
|
@@ -937,7 +1071,7 @@ def delimit_design_generate_tailwind(figma_file_key: str, output_path: Optional[
|
|
|
937
1071
|
return _safe_call(design_generate_tailwind, figma_file_key=figma_file_key, output_path=output_path)
|
|
938
1072
|
|
|
939
1073
|
|
|
940
|
-
@
|
|
1074
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
941
1075
|
def delimit_design_validate_responsive(project_path: str, check_types: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
942
1076
|
"""Validate responsive design patterns (coming soon).
|
|
943
1077
|
|
|
@@ -949,7 +1083,7 @@ def delimit_design_validate_responsive(project_path: str, check_types: Optional[
|
|
|
949
1083
|
return _safe_call(design_validate_responsive, project_path=project_path, check_types=check_types)
|
|
950
1084
|
|
|
951
1085
|
|
|
952
|
-
@
|
|
1086
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
953
1087
|
def delimit_design_component_library(project_path: str, output_format: str = "json") -> Dict[str, Any]:
|
|
954
1088
|
"""Generate component library documentation (coming soon).
|
|
955
1089
|
|
|
@@ -963,7 +1097,7 @@ def delimit_design_component_library(project_path: str, output_format: str = "js
|
|
|
963
1097
|
|
|
964
1098
|
# ─── Storybook (UI Tooling + Visual Regression) ────────────────────────
|
|
965
1099
|
|
|
966
|
-
@
|
|
1100
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
967
1101
|
def delimit_story_generate(component_path: str, story_name: Optional[str] = None, variants: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
968
1102
|
"""Generate Storybook story for a component (coming soon).
|
|
969
1103
|
|
|
@@ -976,7 +1110,7 @@ def delimit_story_generate(component_path: str, story_name: Optional[str] = None
|
|
|
976
1110
|
return _safe_call(story_generate, component_path=component_path, story_name=story_name, variants=variants)
|
|
977
1111
|
|
|
978
1112
|
|
|
979
|
-
@
|
|
1113
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
980
1114
|
def delimit_story_visual_test(url: str, project_path: Optional[str] = None, threshold: float = 0.05) -> Dict[str, Any]:
|
|
981
1115
|
"""Run visual regression test with Playwright screenshots (coming soon).
|
|
982
1116
|
|
|
@@ -989,7 +1123,7 @@ def delimit_story_visual_test(url: str, project_path: Optional[str] = None, thre
|
|
|
989
1123
|
return _safe_call(story_visual_test, url=url, project_path=project_path, threshold=threshold)
|
|
990
1124
|
|
|
991
1125
|
|
|
992
|
-
@
|
|
1126
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
993
1127
|
def delimit_story_build(project_path: str, output_dir: Optional[str] = None) -> Dict[str, Any]:
|
|
994
1128
|
"""Build Storybook static site (coming soon).
|
|
995
1129
|
|
|
@@ -1001,7 +1135,7 @@ def delimit_story_build(project_path: str, output_dir: Optional[str] = None) ->
|
|
|
1001
1135
|
return _safe_call(story_build, project_path=project_path, output_dir=output_dir)
|
|
1002
1136
|
|
|
1003
1137
|
|
|
1004
|
-
@
|
|
1138
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
1005
1139
|
def delimit_story_accessibility(project_path: str, standards: str = "WCAG2AA") -> Dict[str, Any]:
|
|
1006
1140
|
"""Run WCAG accessibility tests on components (coming soon).
|
|
1007
1141
|
|
|
@@ -1015,46 +1149,58 @@ def delimit_story_accessibility(project_path: str, standards: str = "WCAG2AA") -
|
|
|
1015
1149
|
|
|
1016
1150
|
# ─── TestSmith (Testing) ───────────────────────────────────────────────
|
|
1017
1151
|
|
|
1018
|
-
@
|
|
1152
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
1019
1153
|
def delimit_test_generate(project_path: str, source_files: Optional[List[str]] = None, framework: str = "jest") -> Dict[str, Any]:
|
|
1020
|
-
"""Generate tests for source code (experimental).
|
|
1154
|
+
"""Generate tests for source code (experimental) (Pro).
|
|
1021
1155
|
|
|
1022
1156
|
Args:
|
|
1023
1157
|
project_path: Project path.
|
|
1024
1158
|
source_files: Specific files to generate tests for.
|
|
1025
1159
|
framework: Test framework (jest/pytest/vitest).
|
|
1026
1160
|
"""
|
|
1161
|
+
from ai.license import require_premium
|
|
1162
|
+
gate = require_premium("test_generate")
|
|
1163
|
+
if gate:
|
|
1164
|
+
return gate
|
|
1027
1165
|
from backends.ui_bridge import test_generate
|
|
1028
1166
|
return _safe_call(test_generate, project_path=project_path, source_files=source_files, framework=framework)
|
|
1029
1167
|
|
|
1030
1168
|
|
|
1031
|
-
@
|
|
1169
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
1032
1170
|
def delimit_test_coverage(project_path: str, threshold: int = 80) -> Dict[str, Any]:
|
|
1033
|
-
"""Analyze test coverage (experimental).
|
|
1171
|
+
"""Analyze test coverage (experimental) (Pro).
|
|
1034
1172
|
|
|
1035
1173
|
Args:
|
|
1036
1174
|
project_path: Project path.
|
|
1037
1175
|
threshold: Coverage threshold percentage.
|
|
1038
1176
|
"""
|
|
1177
|
+
from ai.license import require_premium
|
|
1178
|
+
gate = require_premium("test_coverage")
|
|
1179
|
+
if gate:
|
|
1180
|
+
return gate
|
|
1039
1181
|
from backends.ui_bridge import test_coverage
|
|
1040
1182
|
return _safe_call(test_coverage, project_path=project_path, threshold=threshold)
|
|
1041
1183
|
|
|
1042
1184
|
|
|
1043
1185
|
@mcp.tool()
|
|
1044
1186
|
def delimit_test_smoke(project_path: str, test_suite: Optional[str] = None) -> Dict[str, Any]:
|
|
1045
|
-
"""Run smoke tests (experimental).
|
|
1187
|
+
"""Run smoke tests (experimental) (Pro).
|
|
1046
1188
|
|
|
1047
1189
|
Args:
|
|
1048
1190
|
project_path: Project path.
|
|
1049
1191
|
test_suite: Specific test suite to run.
|
|
1050
1192
|
"""
|
|
1193
|
+
from ai.license import require_premium
|
|
1194
|
+
gate = require_premium("test_smoke")
|
|
1195
|
+
if gate:
|
|
1196
|
+
return gate
|
|
1051
1197
|
from backends.ui_bridge import test_smoke
|
|
1052
1198
|
return _safe_call(test_smoke, project_path=project_path, test_suite=test_suite)
|
|
1053
1199
|
|
|
1054
1200
|
|
|
1055
1201
|
# ─── Docs ───────────────────────────────────────────────────────────────
|
|
1056
1202
|
|
|
1057
|
-
@
|
|
1203
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
1058
1204
|
def delimit_docs_generate(target: str = ".") -> Dict[str, Any]:
|
|
1059
1205
|
"""Generate documentation for a project (experimental).
|
|
1060
1206
|
|
|
@@ -1065,7 +1211,7 @@ def delimit_docs_generate(target: str = ".") -> Dict[str, Any]:
|
|
|
1065
1211
|
return _safe_call(docs_generate, target=target)
|
|
1066
1212
|
|
|
1067
1213
|
|
|
1068
|
-
@
|
|
1214
|
+
@_experimental_tool() # HIDDEN: stub/pass-through (LED-044)
|
|
1069
1215
|
def delimit_docs_validate(target: str = ".") -> Dict[str, Any]:
|
|
1070
1216
|
"""Validate documentation quality and completeness (experimental).
|
|
1071
1217
|
|
|
@@ -1249,6 +1395,138 @@ def delimit_version() -> Dict[str, Any]:
|
|
|
1249
1395
|
}
|
|
1250
1396
|
|
|
1251
1397
|
|
|
1398
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
1399
|
+
# META TOOLS (help, diagnose)
|
|
1400
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
1401
|
+
|
|
1402
|
+
|
|
1403
|
+
TOOL_HELP = {
|
|
1404
|
+
"init": {"desc": "Initialize governance for a project", "example": "delimit_init(project_path='.', preset='default')", "params": "project_path (str), preset (strict|default|relaxed)"},
|
|
1405
|
+
"lint": {"desc": "Diff two OpenAPI specs and check policy violations", "example": "delimit_lint(old_spec='base.yaml', new_spec='new.yaml')", "params": "old_spec (path), new_spec (path), policy_file (optional path)"},
|
|
1406
|
+
"diff": {"desc": "Pure diff between two specs — no policy, just changes", "example": "delimit_diff(old_spec='base.yaml', new_spec='new.yaml')", "params": "old_spec (path), new_spec (path)"},
|
|
1407
|
+
"semver": {"desc": "Classify the semver bump for a spec change", "example": "delimit_semver(old_spec='base.yaml', new_spec='new.yaml', current_version='1.2.3')", "params": "old_spec, new_spec, current_version (optional)"},
|
|
1408
|
+
"explain": {"desc": "Human-readable explanation of API changes", "example": "delimit_explain(old_spec='base.yaml', new_spec='new.yaml', template='pr_comment')", "params": "old_spec, new_spec, template (developer|pr_comment|migration|changelog)"},
|
|
1409
|
+
"gov_health": {"desc": "Check governance status — is the project initialized?", "example": "delimit_gov_health(repo='.')", "params": "repo (path, default '.')"},
|
|
1410
|
+
"test_coverage": {"desc": "Measure test coverage for a project", "example": "delimit_test_coverage(project_path='.', threshold=80)", "params": "project_path, threshold (default 80)"},
|
|
1411
|
+
"repo_analyze": {"desc": "Full repo health report — code quality, security, dependencies", "example": "delimit_repo_analyze(target='.')", "params": "target (path)"},
|
|
1412
|
+
"zero_spec": {"desc": "Extract OpenAPI spec from source code (FastAPI, Express, NestJS)", "example": "delimit_zero_spec(project_dir='.')", "params": "project_dir (path)"},
|
|
1413
|
+
"sensor_github_issue": {"desc": "Monitor a GitHub issue for new comments", "example": "delimit_sensor_github_issue(repo='owner/repo', issue_number=123)", "params": "repo (owner/name), issue_number (int)"},
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
|
|
1417
|
+
@mcp.tool()
|
|
1418
|
+
def delimit_help(tool_name: str = "") -> Dict[str, Any]:
|
|
1419
|
+
"""Get help for a Delimit tool — what it does, parameters, and examples.
|
|
1420
|
+
|
|
1421
|
+
Args:
|
|
1422
|
+
tool_name: Tool name (e.g. 'lint', 'gov_health'). Leave empty for overview.
|
|
1423
|
+
"""
|
|
1424
|
+
if not tool_name:
|
|
1425
|
+
return {
|
|
1426
|
+
"message": "Delimit has 77 tools. Here are the most useful ones to start with:",
|
|
1427
|
+
"essential_tools": {k: v["desc"] for k, v in TOOL_HELP.items()},
|
|
1428
|
+
"tip": "Run delimit_help(tool_name='lint') for detailed help on a specific tool.",
|
|
1429
|
+
"all_tools": "Run delimit_version() for the complete list.",
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
# Normalize name
|
|
1433
|
+
clean = tool_name.replace("delimit_", "").replace("mcp__delimit__delimit_", "")
|
|
1434
|
+
info = TOOL_HELP.get(clean)
|
|
1435
|
+
if info:
|
|
1436
|
+
return {"tool": clean, **info}
|
|
1437
|
+
return {"error": f"No help for '{tool_name}'. Try: {', '.join(TOOL_HELP.keys())}"}
|
|
1438
|
+
|
|
1439
|
+
|
|
1440
|
+
@mcp.tool()
|
|
1441
|
+
def delimit_diagnose(project_path: str = ".") -> Dict[str, Any]:
|
|
1442
|
+
"""Diagnose your Delimit setup — check environment, config, and tool status.
|
|
1443
|
+
|
|
1444
|
+
Universal 'get me unstuck' command. Checks Python, MCP config, governance state,
|
|
1445
|
+
and reports any issues with suggested fixes.
|
|
1446
|
+
|
|
1447
|
+
Args:
|
|
1448
|
+
project_path: Project to diagnose.
|
|
1449
|
+
"""
|
|
1450
|
+
issues = []
|
|
1451
|
+
checks = {}
|
|
1452
|
+
|
|
1453
|
+
# Python version
|
|
1454
|
+
import sys
|
|
1455
|
+
checks["python"] = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
1456
|
+
|
|
1457
|
+
# Check .delimit/ dir
|
|
1458
|
+
p = Path(project_path).resolve()
|
|
1459
|
+
delimit_dir = p / ".delimit"
|
|
1460
|
+
policies = delimit_dir / "policies.yml"
|
|
1461
|
+
ledger = delimit_dir / "ledger" / "events.jsonl"
|
|
1462
|
+
|
|
1463
|
+
checks["project_path"] = str(p)
|
|
1464
|
+
checks["delimit_initialized"] = delimit_dir.is_dir()
|
|
1465
|
+
checks["policies_file"] = policies.is_file()
|
|
1466
|
+
checks["ledger_file"] = ledger.is_file()
|
|
1467
|
+
|
|
1468
|
+
if not delimit_dir.is_dir():
|
|
1469
|
+
issues.append({
|
|
1470
|
+
"issue": "Project not initialized",
|
|
1471
|
+
"fix": "Run delimit_init(project_path='.') or say 'initialize governance for this project'",
|
|
1472
|
+
})
|
|
1473
|
+
elif not policies.is_file():
|
|
1474
|
+
issues.append({
|
|
1475
|
+
"issue": "Missing policies.yml",
|
|
1476
|
+
"fix": "Run delimit_init(project_path='.', preset='default')",
|
|
1477
|
+
})
|
|
1478
|
+
|
|
1479
|
+
# Check key dependencies
|
|
1480
|
+
for pkg in ["yaml", "pydantic", "packaging"]:
|
|
1481
|
+
try:
|
|
1482
|
+
__import__(pkg)
|
|
1483
|
+
checks[f"dep_{pkg}"] = True
|
|
1484
|
+
except ImportError:
|
|
1485
|
+
checks[f"dep_{pkg}"] = False
|
|
1486
|
+
issues.append({"issue": f"Missing Python package: {pkg}", "fix": f"pip install {pkg}"})
|
|
1487
|
+
|
|
1488
|
+
# Check fastmcp
|
|
1489
|
+
try:
|
|
1490
|
+
import fastmcp
|
|
1491
|
+
checks["fastmcp"] = True
|
|
1492
|
+
except ImportError:
|
|
1493
|
+
checks["fastmcp"] = False
|
|
1494
|
+
issues.append({"issue": "FastMCP not installed", "fix": "pip install fastmcp"})
|
|
1495
|
+
|
|
1496
|
+
# Summary
|
|
1497
|
+
status = "healthy" if not issues else "issues_found"
|
|
1498
|
+
return {
|
|
1499
|
+
"status": status,
|
|
1500
|
+
"checks": checks,
|
|
1501
|
+
"issues": issues,
|
|
1502
|
+
"issue_count": len(issues),
|
|
1503
|
+
"tip": "If everything looks good but tools aren't working, try restarting Claude Code.",
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
|
|
1507
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
1508
|
+
# LICENSE
|
|
1509
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
1510
|
+
|
|
1511
|
+
|
|
1512
|
+
@mcp.tool()
|
|
1513
|
+
def delimit_activate(license_key: str) -> Dict[str, Any]:
|
|
1514
|
+
"""Activate a Delimit Pro license key.
|
|
1515
|
+
|
|
1516
|
+
Args:
|
|
1517
|
+
license_key: The license key to activate (e.g. DELIMIT-XXXX-XXXX-XXXX).
|
|
1518
|
+
"""
|
|
1519
|
+
from ai.license import activate_license
|
|
1520
|
+
return activate_license(license_key)
|
|
1521
|
+
|
|
1522
|
+
|
|
1523
|
+
@mcp.tool()
|
|
1524
|
+
def delimit_license_status() -> Dict[str, Any]:
|
|
1525
|
+
"""Check current Delimit license status -- tier, validity, and expiry."""
|
|
1526
|
+
from ai.license import get_license
|
|
1527
|
+
return get_license()
|
|
1528
|
+
|
|
1529
|
+
|
|
1252
1530
|
# ═══════════════════════════════════════════════════════════════════════
|
|
1253
1531
|
# ENTRY POINT
|
|
1254
1532
|
# ═══════════════════════════════════════════════════════════════════════
|
|
@@ -1261,3 +1539,9 @@ async def run_mcp_server(server, server_name="delimit"):
|
|
|
1261
1539
|
if __name__ == "__main__":
|
|
1262
1540
|
import asyncio
|
|
1263
1541
|
asyncio.run(run_mcp_server(mcp))
|
|
1542
|
+
|
|
1543
|
+
|
|
1544
|
+
def main():
|
|
1545
|
+
"""Entry point for `delimit-mcp` console script."""
|
|
1546
|
+
import asyncio
|
|
1547
|
+
asyncio.run(run_mcp_server(mcp))
|
package/package.json
CHANGED
package/server.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.delimit-ai/delimit",
|
|
4
|
+
"title": "Delimit",
|
|
5
|
+
"description": "API governance for AI agents. Detect breaking OpenAPI changes and enforce policies in CI.",
|
|
6
|
+
"repository": {
|
|
7
|
+
"url": "https://github.com/delimit-ai/delimit",
|
|
8
|
+
"source": "github"
|
|
9
|
+
},
|
|
10
|
+
"version": "3.2.0",
|
|
11
|
+
"websiteUrl": "https://delimit.ai",
|
|
12
|
+
"packages": [
|
|
13
|
+
{
|
|
14
|
+
"registryType": "npm",
|
|
15
|
+
"identifier": "delimit-cli",
|
|
16
|
+
"version": "3.2.0",
|
|
17
|
+
"transport": {
|
|
18
|
+
"type": "stdio"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|