network-ai 3.0.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.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/QUICKSTART.md +260 -0
  3. package/README.md +604 -0
  4. package/SKILL.md +568 -0
  5. package/dist/adapters/adapter-registry.d.ts +94 -0
  6. package/dist/adapters/adapter-registry.d.ts.map +1 -0
  7. package/dist/adapters/adapter-registry.js +355 -0
  8. package/dist/adapters/adapter-registry.js.map +1 -0
  9. package/dist/adapters/agno-adapter.d.ts +112 -0
  10. package/dist/adapters/agno-adapter.d.ts.map +1 -0
  11. package/dist/adapters/agno-adapter.js +140 -0
  12. package/dist/adapters/agno-adapter.js.map +1 -0
  13. package/dist/adapters/autogen-adapter.d.ts +67 -0
  14. package/dist/adapters/autogen-adapter.d.ts.map +1 -0
  15. package/dist/adapters/autogen-adapter.js +141 -0
  16. package/dist/adapters/autogen-adapter.js.map +1 -0
  17. package/dist/adapters/base-adapter.d.ts +51 -0
  18. package/dist/adapters/base-adapter.d.ts.map +1 -0
  19. package/dist/adapters/base-adapter.js +103 -0
  20. package/dist/adapters/base-adapter.js.map +1 -0
  21. package/dist/adapters/crewai-adapter.d.ts +72 -0
  22. package/dist/adapters/crewai-adapter.d.ts.map +1 -0
  23. package/dist/adapters/crewai-adapter.js +148 -0
  24. package/dist/adapters/crewai-adapter.js.map +1 -0
  25. package/dist/adapters/custom-adapter.d.ts +74 -0
  26. package/dist/adapters/custom-adapter.d.ts.map +1 -0
  27. package/dist/adapters/custom-adapter.js +142 -0
  28. package/dist/adapters/custom-adapter.js.map +1 -0
  29. package/dist/adapters/dspy-adapter.d.ts +70 -0
  30. package/dist/adapters/dspy-adapter.d.ts.map +1 -0
  31. package/dist/adapters/dspy-adapter.js +127 -0
  32. package/dist/adapters/dspy-adapter.js.map +1 -0
  33. package/dist/adapters/haystack-adapter.d.ts +83 -0
  34. package/dist/adapters/haystack-adapter.d.ts.map +1 -0
  35. package/dist/adapters/haystack-adapter.js +149 -0
  36. package/dist/adapters/haystack-adapter.js.map +1 -0
  37. package/dist/adapters/index.d.ts +47 -0
  38. package/dist/adapters/index.d.ts.map +1 -0
  39. package/dist/adapters/index.js +56 -0
  40. package/dist/adapters/index.js.map +1 -0
  41. package/dist/adapters/langchain-adapter.d.ts +51 -0
  42. package/dist/adapters/langchain-adapter.d.ts.map +1 -0
  43. package/dist/adapters/langchain-adapter.js +134 -0
  44. package/dist/adapters/langchain-adapter.js.map +1 -0
  45. package/dist/adapters/llamaindex-adapter.d.ts +89 -0
  46. package/dist/adapters/llamaindex-adapter.d.ts.map +1 -0
  47. package/dist/adapters/llamaindex-adapter.js +135 -0
  48. package/dist/adapters/llamaindex-adapter.js.map +1 -0
  49. package/dist/adapters/mcp-adapter.d.ts +90 -0
  50. package/dist/adapters/mcp-adapter.d.ts.map +1 -0
  51. package/dist/adapters/mcp-adapter.js +200 -0
  52. package/dist/adapters/mcp-adapter.js.map +1 -0
  53. package/dist/adapters/openai-assistants-adapter.d.ts +94 -0
  54. package/dist/adapters/openai-assistants-adapter.d.ts.map +1 -0
  55. package/dist/adapters/openai-assistants-adapter.js +130 -0
  56. package/dist/adapters/openai-assistants-adapter.js.map +1 -0
  57. package/dist/adapters/openclaw-adapter.d.ts +21 -0
  58. package/dist/adapters/openclaw-adapter.d.ts.map +1 -0
  59. package/dist/adapters/openclaw-adapter.js +140 -0
  60. package/dist/adapters/openclaw-adapter.js.map +1 -0
  61. package/dist/adapters/semantic-kernel-adapter.d.ts +73 -0
  62. package/dist/adapters/semantic-kernel-adapter.d.ts.map +1 -0
  63. package/dist/adapters/semantic-kernel-adapter.js +123 -0
  64. package/dist/adapters/semantic-kernel-adapter.js.map +1 -0
  65. package/dist/index.d.ts +379 -0
  66. package/dist/index.d.ts.map +1 -0
  67. package/dist/index.js +1428 -0
  68. package/dist/index.js.map +1 -0
  69. package/dist/lib/blackboard-validator.d.ts +205 -0
  70. package/dist/lib/blackboard-validator.d.ts.map +1 -0
  71. package/dist/lib/blackboard-validator.js +756 -0
  72. package/dist/lib/blackboard-validator.js.map +1 -0
  73. package/dist/lib/locked-blackboard.d.ts +174 -0
  74. package/dist/lib/locked-blackboard.d.ts.map +1 -0
  75. package/dist/lib/locked-blackboard.js +654 -0
  76. package/dist/lib/locked-blackboard.js.map +1 -0
  77. package/dist/lib/swarm-utils.d.ts +136 -0
  78. package/dist/lib/swarm-utils.d.ts.map +1 -0
  79. package/dist/lib/swarm-utils.js +510 -0
  80. package/dist/lib/swarm-utils.js.map +1 -0
  81. package/dist/security.d.ts +269 -0
  82. package/dist/security.d.ts.map +1 -0
  83. package/dist/security.js +713 -0
  84. package/dist/security.js.map +1 -0
  85. package/package.json +84 -0
  86. package/scripts/blackboard.py +819 -0
  87. package/scripts/check_permission.py +331 -0
  88. package/scripts/revoke_token.py +243 -0
  89. package/scripts/swarm_guard.py +1140 -0
  90. package/scripts/validate_token.py +97 -0
  91. package/types/agent-adapter.d.ts +244 -0
  92. package/types/openclaw-core.d.ts +52 -0
@@ -0,0 +1,331 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ AuthGuardian Permission Checker
4
+
5
+ Evaluates permission requests for accessing sensitive resources
6
+ (DATABASE, PAYMENTS, EMAIL, FILE_EXPORT).
7
+
8
+ Usage:
9
+ python check_permission.py --agent AGENT_ID --resource RESOURCE_TYPE \
10
+ --justification "REASON" [--scope SCOPE]
11
+
12
+ Example:
13
+ python check_permission.py --agent data_analyst --resource DATABASE \
14
+ --justification "Need customer order history for sales report" \
15
+ --scope "read:orders"
16
+ """
17
+
18
+ import argparse
19
+ import json
20
+ import re
21
+ import sys
22
+ import uuid
23
+ from datetime import datetime, timedelta, timezone
24
+ from pathlib import Path
25
+ from typing import Any, Optional
26
+
27
+ # Configuration
28
+ GRANT_TOKEN_TTL_MINUTES = 5
29
+ GRANTS_FILE = Path(__file__).parent.parent / "data" / "active_grants.json"
30
+ AUDIT_LOG = Path(__file__).parent.parent / "data" / "audit_log.jsonl"
31
+
32
+ # Default trust levels for known agents
33
+ DEFAULT_TRUST_LEVELS = {
34
+ "orchestrator": 0.9,
35
+ "data_analyst": 0.8,
36
+ "strategy_advisor": 0.7,
37
+ "risk_assessor": 0.85,
38
+ }
39
+
40
+ # Base risk scores for resource types
41
+ BASE_RISKS = {
42
+ "DATABASE": 0.5, # Internal database access
43
+ "PAYMENTS": 0.7, # Payment/financial systems
44
+ "EMAIL": 0.4, # Email sending capability
45
+ "FILE_EXPORT": 0.6, # Exporting data to files
46
+ }
47
+
48
+ # Default restrictions by resource type
49
+ RESTRICTIONS = {
50
+ "DATABASE": ["read_only", "max_records:100"],
51
+ "PAYMENTS": ["read_only", "no_pii_fields", "audit_required"],
52
+ "EMAIL": ["rate_limit:10_per_minute"],
53
+ "FILE_EXPORT": ["anonymize_pii", "local_only"],
54
+ }
55
+
56
+
57
+ def ensure_data_dir():
58
+ """Ensure data directory exists."""
59
+ data_dir = Path(__file__).parent.parent / "data"
60
+ data_dir.mkdir(exist_ok=True)
61
+ return data_dir
62
+
63
+
64
+ def score_justification(justification: str) -> float:
65
+ """
66
+ Score the quality of a justification.
67
+
68
+ Criteria:
69
+ - Length (more detail = better)
70
+ - Contains task-related keywords
71
+ - Contains specificity keywords
72
+ - Doesn't contain test/debug keywords
73
+ """
74
+ score = 0.0
75
+
76
+ if len(justification) > 20:
77
+ score += 0.2
78
+ if len(justification) > 50:
79
+ score += 0.2
80
+ if re.search(r'\b(task|purpose|need|require|generate|analyze|create|process)\b',
81
+ justification, re.IGNORECASE):
82
+ score += 0.2
83
+ if re.search(r'\b(specific|particular|exact|quarterly|annual|report|summary)\b',
84
+ justification, re.IGNORECASE):
85
+ score += 0.2
86
+ if not re.search(r'\b(test|debug|try|experiment)\b', justification, re.IGNORECASE):
87
+ score += 0.2
88
+
89
+ return min(score, 1.0)
90
+
91
+
92
+ def assess_risk(resource_type: str, scope: Optional[str] = None) -> float:
93
+ """
94
+ Assess the risk level of a permission request.
95
+
96
+ Factors:
97
+ - Base risk of resource type
98
+ - Scope breadth (broad scopes = higher risk)
99
+ - Write operations (higher risk)
100
+ """
101
+ risk = BASE_RISKS.get(resource_type, 0.5)
102
+
103
+ # Broad scopes increase risk
104
+ if not scope or scope in ("*", "all"):
105
+ risk += 0.2
106
+
107
+ # Write operations increase risk
108
+ if scope and re.search(r'\b(write|delete|update|modify|create)\b', scope, re.IGNORECASE):
109
+ risk += 0.2
110
+
111
+ return min(risk, 1.0)
112
+
113
+
114
+ def generate_grant_token() -> str:
115
+ """Generate a unique grant token."""
116
+ return f"grant_{uuid.uuid4().hex}"
117
+
118
+
119
+ def log_audit(action: str, details: dict[str, Any]) -> None:
120
+ """Append entry to audit log."""
121
+ ensure_data_dir()
122
+ entry: dict[str, Any] = {
123
+ "timestamp": datetime.now(timezone.utc).isoformat(),
124
+ "action": action,
125
+ "details": details
126
+ }
127
+ with open(AUDIT_LOG, "a") as f:
128
+ f.write(json.dumps(entry) + "\n")
129
+
130
+
131
+ def save_grant(grant: dict[str, Any]) -> None:
132
+ """Save grant to persistent storage."""
133
+ ensure_data_dir()
134
+ grants = {}
135
+ if GRANTS_FILE.exists():
136
+ try:
137
+ grants = json.loads(GRANTS_FILE.read_text())
138
+ except json.JSONDecodeError:
139
+ grants = {}
140
+
141
+ grants[grant["token"]] = grant
142
+ GRANTS_FILE.write_text(json.dumps(grants, indent=2))
143
+
144
+
145
+ def evaluate_permission(agent_id: str, resource_type: str,
146
+ justification: str, scope: Optional[str] = None) -> dict[str, Any]:
147
+ """
148
+ Evaluate a permission request using weighted scoring.
149
+
150
+ Weights:
151
+ - Justification Quality: 40%
152
+ - Agent Trust Level: 30%
153
+ - Risk Assessment: 30%
154
+ """
155
+ # Log the request
156
+ log_audit("permission_request", {
157
+ "agent_id": agent_id,
158
+ "resource_type": resource_type,
159
+ "justification": justification,
160
+ "scope": scope
161
+ })
162
+
163
+ # 1. Justification Quality (40% weight)
164
+ justification_score = score_justification(justification)
165
+ if justification_score < 0.3:
166
+ return {
167
+ "granted": False,
168
+ "reason": "Justification is insufficient. Please provide specific task context.",
169
+ "scores": {
170
+ "justification": justification_score,
171
+ "trust": None,
172
+ "risk": None
173
+ }
174
+ }
175
+
176
+ # 2. Agent Trust Level (30% weight)
177
+ trust_level = DEFAULT_TRUST_LEVELS.get(agent_id, 0.5)
178
+ if trust_level < 0.4:
179
+ return {
180
+ "granted": False,
181
+ "reason": "Agent trust level is below threshold. Escalate to human operator.",
182
+ "scores": {
183
+ "justification": justification_score,
184
+ "trust": trust_level,
185
+ "risk": None
186
+ }
187
+ }
188
+
189
+ # 3. Risk Assessment (30% weight)
190
+ risk_score = assess_risk(resource_type, scope)
191
+ if risk_score > 0.8:
192
+ return {
193
+ "granted": False,
194
+ "reason": "Risk assessment exceeds acceptable threshold. Narrow the requested scope.",
195
+ "scores": {
196
+ "justification": justification_score,
197
+ "trust": trust_level,
198
+ "risk": risk_score
199
+ }
200
+ }
201
+
202
+ # Calculate weighted approval score
203
+ weighted_score = (
204
+ justification_score * 0.4 +
205
+ trust_level * 0.3 +
206
+ (1 - risk_score) * 0.3
207
+ )
208
+
209
+ if weighted_score < 0.5:
210
+ return {
211
+ "granted": False,
212
+ "reason": f"Combined evaluation score ({weighted_score:.2f}) below threshold (0.5).",
213
+ "scores": {
214
+ "justification": justification_score,
215
+ "trust": trust_level,
216
+ "risk": risk_score,
217
+ "weighted": weighted_score
218
+ }
219
+ }
220
+
221
+ # Generate grant
222
+ token = generate_grant_token()
223
+ expires_at = (datetime.now(timezone.utc) + timedelta(minutes=GRANT_TOKEN_TTL_MINUTES)).isoformat()
224
+ restrictions = RESTRICTIONS.get(resource_type, [])
225
+
226
+ grant: dict[str, Any] = {
227
+ "token": token,
228
+ "agent_id": agent_id,
229
+ "resource_type": resource_type,
230
+ "scope": scope,
231
+ "expires_at": expires_at,
232
+ "restrictions": restrictions,
233
+ "granted_at": datetime.now(timezone.utc).isoformat()
234
+ }
235
+
236
+ # Save grant and log
237
+ save_grant(grant)
238
+ log_audit("permission_granted", grant)
239
+
240
+ return {
241
+ "granted": True,
242
+ "token": token,
243
+ "expires_at": expires_at,
244
+ "restrictions": restrictions,
245
+ "scores": {
246
+ "justification": justification_score,
247
+ "trust": trust_level,
248
+ "risk": risk_score,
249
+ "weighted": weighted_score
250
+ }
251
+ }
252
+
253
+
254
+ def main():
255
+ parser = argparse.ArgumentParser(
256
+ description="AuthGuardian Permission Checker",
257
+ formatter_class=argparse.RawDescriptionHelpFormatter,
258
+ epilog="""
259
+ Examples:
260
+ %(prog)s --agent data_analyst --resource SAP_API \\
261
+ --justification "Need Q4 invoice data for quarterly report"
262
+
263
+ %(prog)s --agent orchestrator --resource FINANCIAL_API \\
264
+ --justification "Generating board presentation financials" \\
265
+ --scope "read:revenue,read:expenses"
266
+ """
267
+ )
268
+
269
+ parser.add_argument(
270
+ "--agent", "-a",
271
+ required=True,
272
+ help="Agent ID requesting permission"
273
+ )
274
+ parser.add_argument(
275
+ "--resource", "-r",
276
+ required=True,
277
+ choices=["DATABASE", "PAYMENTS", "EMAIL", "FILE_EXPORT"],
278
+ help="Resource type to access"
279
+ )
280
+ parser.add_argument(
281
+ "--justification", "-j",
282
+ required=True,
283
+ help="Business justification for the request"
284
+ )
285
+ parser.add_argument(
286
+ "--scope", "-s",
287
+ help="Specific scope of access (e.g., 'read:invoices')"
288
+ )
289
+ parser.add_argument(
290
+ "--json",
291
+ action="store_true",
292
+ help="Output result as JSON"
293
+ )
294
+
295
+ args = parser.parse_args()
296
+
297
+ result = evaluate_permission(
298
+ agent_id=args.agent,
299
+ resource_type=args.resource,
300
+ justification=args.justification,
301
+ scope=args.scope
302
+ )
303
+
304
+ if args.json:
305
+ print(json.dumps(result, indent=2))
306
+ else:
307
+ if result["granted"]:
308
+ print("✅ GRANTED")
309
+ print(f"Token: {result['token']}")
310
+ print(f"Expires: {result['expires_at']}")
311
+ print(f"Restrictions: {', '.join(result['restrictions'])}")
312
+ else:
313
+ print("❌ DENIED")
314
+ print(f"Reason: {result['reason']}")
315
+
316
+ print("\n📊 Evaluation Scores:")
317
+ scores = result["scores"]
318
+ if scores.get("justification") is not None:
319
+ print(f" Justification: {scores['justification']:.2f}")
320
+ if scores.get("trust") is not None:
321
+ print(f" Trust Level: {scores['trust']:.2f}")
322
+ if scores.get("risk") is not None:
323
+ print(f" Risk Score: {scores['risk']:.2f}")
324
+ if scores.get("weighted") is not None:
325
+ print(f" Weighted: {scores['weighted']:.2f}")
326
+
327
+ sys.exit(0 if result["granted"] else 1)
328
+
329
+
330
+ if __name__ == "__main__":
331
+ main()
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Revoke Grant Token & TTL Enforcement
4
+
5
+ Revoke permission tokens and automatically cleanup expired grants.
6
+
7
+ Usage:
8
+ python revoke_token.py TOKEN # Revoke specific token
9
+ python revoke_token.py --cleanup # Remove all expired tokens
10
+ python revoke_token.py --list-expired # List expired tokens without removing
11
+
12
+ Example:
13
+ python revoke_token.py grant_a1b2c3d4e5f6
14
+ python revoke_token.py --cleanup --json
15
+ """
16
+
17
+ import argparse
18
+ import json
19
+ import sys
20
+ from datetime import datetime, timezone
21
+ from pathlib import Path
22
+ from typing import Any
23
+
24
+ GRANTS_FILE = Path(__file__).parent.parent / "data" / "active_grants.json"
25
+ AUDIT_LOG = Path(__file__).parent.parent / "data" / "audit_log.jsonl"
26
+
27
+
28
+ def log_audit(action: str, details: dict[str, Any]) -> None:
29
+ """Append entry to audit log."""
30
+ AUDIT_LOG.parent.mkdir(exist_ok=True)
31
+ entry: dict[str, Any] = {
32
+ "timestamp": datetime.now(timezone.utc).isoformat(),
33
+ "action": action,
34
+ "details": details
35
+ }
36
+ with open(AUDIT_LOG, "a") as f:
37
+ f.write(json.dumps(entry) + "\n")
38
+
39
+
40
+ def revoke_token(token: str) -> dict[str, Any]:
41
+ """Revoke a grant token."""
42
+ if not GRANTS_FILE.exists():
43
+ return {
44
+ "revoked": False,
45
+ "reason": "No grants file found"
46
+ }
47
+
48
+ try:
49
+ grants = json.loads(GRANTS_FILE.read_text())
50
+ except json.JSONDecodeError:
51
+ return {
52
+ "revoked": False,
53
+ "reason": "Invalid grants file"
54
+ }
55
+
56
+ if token not in grants:
57
+ return {
58
+ "revoked": False,
59
+ "reason": "Token not found"
60
+ }
61
+
62
+ grant = grants.pop(token)
63
+ GRANTS_FILE.write_text(json.dumps(grants, indent=2))
64
+
65
+ log_audit("permission_revoked", {
66
+ "token": token,
67
+ "original_grant": grant
68
+ })
69
+
70
+ return {
71
+ "revoked": True,
72
+ "grant": grant
73
+ }
74
+
75
+
76
+ def is_token_expired(grant: dict[str, Any]) -> bool:
77
+ """Check if a grant token has expired."""
78
+ expires_at = grant.get("expires_at")
79
+ if not expires_at:
80
+ return False
81
+ try:
82
+ expiry_time = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
83
+ return datetime.now(timezone.utc) > expiry_time
84
+ except (ValueError, AttributeError):
85
+ return False
86
+
87
+
88
+ def list_expired_tokens() -> dict[str, Any]:
89
+ """List all expired tokens without removing them."""
90
+ if not GRANTS_FILE.exists():
91
+ return {"expired_tokens": [], "total_grants": 0}
92
+
93
+ try:
94
+ grants = json.loads(GRANTS_FILE.read_text())
95
+ except json.JSONDecodeError:
96
+ return {"error": "Invalid grants file"}
97
+
98
+ expired: list[dict[str, Any]] = []
99
+ now = datetime.now(timezone.utc)
100
+
101
+ for token, grant in grants.items():
102
+ if is_token_expired(grant):
103
+ expired.append({
104
+ "token": token,
105
+ "agent": grant.get("agent_id"),
106
+ "resource": grant.get("resource_type"),
107
+ "expired_at": grant.get("expires_at")
108
+ })
109
+
110
+ return {
111
+ "expired_tokens": expired,
112
+ "expired_count": len(expired),
113
+ "total_grants": len(grants),
114
+ "checked_at": now.isoformat()
115
+ }
116
+
117
+
118
+ def cleanup_expired_tokens() -> dict[str, Any]:
119
+ """Remove all expired tokens from active grants (TTL enforcement)."""
120
+ if not GRANTS_FILE.exists():
121
+ return {
122
+ "cleaned": 0,
123
+ "remaining": 0,
124
+ "message": "No grants file found"
125
+ }
126
+
127
+ try:
128
+ grants = json.loads(GRANTS_FILE.read_text())
129
+ except json.JSONDecodeError:
130
+ return {
131
+ "cleaned": 0,
132
+ "error": "Invalid grants file"
133
+ }
134
+
135
+ original_count = len(grants)
136
+ expired_tokens: list[dict[str, Any]] = []
137
+
138
+ # Find and remove expired tokens
139
+ tokens_to_remove: list[str] = []
140
+ for token, grant in grants.items():
141
+ if is_token_expired(grant):
142
+ tokens_to_remove.append(token)
143
+ expired_tokens.append({
144
+ "token": token,
145
+ "agent": grant.get("agent_id"),
146
+ "resource": grant.get("resource_type"),
147
+ "expired_at": grant.get("expires_at")
148
+ })
149
+
150
+ for token in tokens_to_remove:
151
+ grants.pop(token)
152
+
153
+ # Write back cleaned grants
154
+ GRANTS_FILE.write_text(json.dumps(grants, indent=2))
155
+
156
+ # Log the cleanup
157
+ if expired_tokens:
158
+ log_audit("ttl_cleanup", {
159
+ "expired_count": len(expired_tokens),
160
+ "expired_tokens": expired_tokens,
161
+ "remaining_grants": len(grants)
162
+ })
163
+
164
+ return {
165
+ "cleaned": len(expired_tokens),
166
+ "expired_tokens": expired_tokens,
167
+ "remaining": len(grants),
168
+ "original_count": original_count,
169
+ "cleaned_at": datetime.now(timezone.utc).isoformat()
170
+ }
171
+
172
+
173
+ def main():
174
+ parser = argparse.ArgumentParser(
175
+ description="Revoke permission grant tokens and enforce TTL cleanup"
176
+ )
177
+ parser.add_argument("token", nargs="?", help="Grant token to revoke")
178
+ parser.add_argument("--cleanup", action="store_true",
179
+ help="Remove all expired tokens (TTL enforcement)")
180
+ parser.add_argument("--list-expired", action="store_true",
181
+ help="List expired tokens without removing")
182
+ parser.add_argument("--json", action="store_true", help="Output as JSON")
183
+
184
+ args = parser.parse_args()
185
+
186
+ # Handle --list-expired
187
+ if args.list_expired:
188
+ result = list_expired_tokens()
189
+ if args.json:
190
+ print(json.dumps(result, indent=2))
191
+ else:
192
+ expired = result.get("expired_tokens", [])
193
+ if expired:
194
+ print(f"⏰ Found {len(expired)} expired token(s):")
195
+ for t in expired:
196
+ print(f" • {t['token'][:20]}... ({t['agent']} → {t['resource']})")
197
+ print(f" Expired: {t['expired_at']}")
198
+ else:
199
+ print("✅ No expired tokens found")
200
+ print(f"\n Total grants: {result.get('total_grants', 0)}")
201
+ sys.exit(0)
202
+
203
+ # Handle --cleanup
204
+ if args.cleanup:
205
+ result = cleanup_expired_tokens()
206
+ if args.json:
207
+ print(json.dumps(result, indent=2))
208
+ else:
209
+ cleaned = result.get("cleaned", 0)
210
+ if cleaned > 0:
211
+ print(f"🧹 TTL Cleanup Complete")
212
+ print(f" Removed: {cleaned} expired token(s)")
213
+ for t in result.get("expired_tokens", []):
214
+ print(f" • {t['token'][:20]}... ({t['agent']})")
215
+ else:
216
+ print("✅ No expired tokens to clean")
217
+ print(f" Remaining active grants: {result.get('remaining', 0)}")
218
+ sys.exit(0)
219
+
220
+ # Handle single token revocation
221
+ if not args.token:
222
+ parser.print_help()
223
+ sys.exit(1)
224
+
225
+ result = revoke_token(args.token)
226
+
227
+ if args.json:
228
+ print(json.dumps(result, indent=2))
229
+ else:
230
+ if result["revoked"]:
231
+ grant = result["grant"]
232
+ print("✅ Token REVOKED")
233
+ print(f" Agent: {grant.get('agent_id')}")
234
+ print(f" Resource: {grant.get('resource_type')}")
235
+ else:
236
+ print("❌ Revocation FAILED")
237
+ print(f" Reason: {result.get('reason')}")
238
+
239
+ sys.exit(0 if result.get("revoked") or result.get("cleaned", 0) >= 0 else 1)
240
+
241
+
242
+ if __name__ == "__main__":
243
+ main()