@techwavedev/agi-agent-kit 1.1.7 → 1.2.1

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.

Potentially problematic release.


This version of @techwavedev/agi-agent-kit might be problematic. Click here for more details.

Files changed (111) hide show
  1. package/CHANGELOG.md +82 -1
  2. package/README.md +190 -12
  3. package/bin/init.js +30 -2
  4. package/package.json +6 -3
  5. package/templates/base/AGENTS.md +54 -23
  6. package/templates/base/README.md +325 -0
  7. package/templates/base/directives/memory_integration.md +95 -0
  8. package/templates/base/execution/memory_manager.py +309 -0
  9. package/templates/base/execution/session_boot.py +218 -0
  10. package/templates/base/execution/session_init.py +320 -0
  11. package/templates/base/skill-creator/SKILL_skillcreator.md +23 -36
  12. package/templates/base/skill-creator/scripts/init_skill.py +18 -135
  13. package/templates/skills/ec/README.md +31 -0
  14. package/templates/skills/ec/aws/SKILL.md +1020 -0
  15. package/templates/skills/ec/aws/defaults.yaml +13 -0
  16. package/templates/skills/ec/aws/references/common_patterns.md +80 -0
  17. package/templates/skills/ec/aws/references/mcp_servers.md +98 -0
  18. package/templates/skills/ec/aws-terraform/SKILL.md +349 -0
  19. package/templates/skills/ec/aws-terraform/references/best_practices.md +394 -0
  20. package/templates/skills/ec/aws-terraform/references/checkov_reference.md +337 -0
  21. package/templates/skills/ec/aws-terraform/scripts/configure_mcp.py +150 -0
  22. package/templates/skills/ec/confluent-kafka/SKILL.md +655 -0
  23. package/templates/skills/ec/confluent-kafka/references/ansible_playbooks.md +792 -0
  24. package/templates/skills/ec/confluent-kafka/references/ec_deployment.md +579 -0
  25. package/templates/skills/ec/confluent-kafka/references/kraft_migration.md +490 -0
  26. package/templates/skills/ec/confluent-kafka/references/troubleshooting.md +778 -0
  27. package/templates/skills/ec/confluent-kafka/references/upgrade_7x_to_8x.md +488 -0
  28. package/templates/skills/ec/confluent-kafka/scripts/kafka_health_check.py +435 -0
  29. package/templates/skills/ec/confluent-kafka/scripts/upgrade_preflight.py +568 -0
  30. package/templates/skills/ec/confluent-kafka/scripts/validate_config.py +455 -0
  31. package/templates/skills/ec/consul/SKILL.md +427 -0
  32. package/templates/skills/ec/consul/references/acl_setup.md +168 -0
  33. package/templates/skills/ec/consul/references/ha_config.md +196 -0
  34. package/templates/skills/ec/consul/references/troubleshooting.md +267 -0
  35. package/templates/skills/ec/consul/references/upgrades.md +213 -0
  36. package/templates/skills/ec/consul/scripts/consul_health_report.py +530 -0
  37. package/templates/skills/ec/consul/scripts/consul_status.py +264 -0
  38. package/templates/skills/ec/consul/scripts/generate_values.py +170 -0
  39. package/templates/skills/ec/documentation/SKILL.md +351 -0
  40. package/templates/skills/ec/documentation/references/best_practices.md +201 -0
  41. package/templates/skills/ec/documentation/scripts/analyze_code.py +307 -0
  42. package/templates/skills/ec/documentation/scripts/detect_changes.py +460 -0
  43. package/templates/skills/ec/documentation/scripts/generate_changelog.py +312 -0
  44. package/templates/skills/ec/documentation/scripts/sync_docs.py +272 -0
  45. package/templates/skills/ec/documentation/scripts/update_skill_docs.py +366 -0
  46. package/templates/skills/ec/gitlab/SKILL.md +529 -0
  47. package/templates/skills/ec/gitlab/references/agent_installation.md +416 -0
  48. package/templates/skills/ec/gitlab/references/api_reference.md +508 -0
  49. package/templates/skills/ec/gitlab/references/gitops_flux.md +465 -0
  50. package/templates/skills/ec/gitlab/references/troubleshooting.md +518 -0
  51. package/templates/skills/ec/gitlab/scripts/generate_agent_values.py +329 -0
  52. package/templates/skills/ec/gitlab/scripts/gitlab_agent_status.py +414 -0
  53. package/templates/skills/ec/jira/SKILL.md +484 -0
  54. package/templates/skills/ec/jira/references/jql_reference.md +148 -0
  55. package/templates/skills/ec/jira/scripts/add_comment.py +91 -0
  56. package/templates/skills/ec/jira/scripts/bulk_log_work.py +124 -0
  57. package/templates/skills/ec/jira/scripts/create_ticket.py +162 -0
  58. package/templates/skills/ec/jira/scripts/get_ticket.py +191 -0
  59. package/templates/skills/ec/jira/scripts/jira_client.py +383 -0
  60. package/templates/skills/ec/jira/scripts/log_work.py +154 -0
  61. package/templates/skills/ec/jira/scripts/search_tickets.py +104 -0
  62. package/templates/skills/ec/jira/scripts/update_comment.py +67 -0
  63. package/templates/skills/ec/jira/scripts/update_ticket.py +161 -0
  64. package/templates/skills/ec/karpenter/SKILL.md +301 -0
  65. package/templates/skills/ec/karpenter/references/ec2nodeclasses.md +421 -0
  66. package/templates/skills/ec/karpenter/references/migration.md +396 -0
  67. package/templates/skills/ec/karpenter/references/nodepools.md +400 -0
  68. package/templates/skills/ec/karpenter/references/troubleshooting.md +359 -0
  69. package/templates/skills/ec/karpenter/scripts/generate_ec2nodeclass.py +187 -0
  70. package/templates/skills/ec/karpenter/scripts/generate_nodepool.py +245 -0
  71. package/templates/skills/ec/karpenter/scripts/karpenter_status.py +359 -0
  72. package/templates/skills/ec/opensearch/SKILL.md +720 -0
  73. package/templates/skills/ec/opensearch/references/ml_neural_search.md +576 -0
  74. package/templates/skills/ec/opensearch/references/operator.md +532 -0
  75. package/templates/skills/ec/opensearch/references/query_dsl.md +532 -0
  76. package/templates/skills/ec/opensearch/scripts/configure_mcp.py +148 -0
  77. package/templates/skills/ec/victoriametrics/SKILL.md +598 -0
  78. package/templates/skills/ec/victoriametrics/references/kubernetes.md +531 -0
  79. package/templates/skills/ec/victoriametrics/references/prometheus_migration.md +333 -0
  80. package/templates/skills/ec/victoriametrics/references/troubleshooting.md +442 -0
  81. package/templates/skills/knowledge/SKILLS_CATALOG.md +274 -4
  82. package/templates/skills/knowledge/intelligent-routing/SKILL.md +237 -164
  83. package/templates/skills/knowledge/parallel-agents/SKILL.md +345 -73
  84. package/templates/skills/knowledge/plugin-discovery/SKILL.md +582 -0
  85. package/templates/skills/knowledge/plugin-discovery/scripts/platform_setup.py +1083 -0
  86. package/templates/skills/knowledge/design-md/README.md +0 -34
  87. package/templates/skills/knowledge/design-md/SKILL.md +0 -193
  88. package/templates/skills/knowledge/design-md/examples/DESIGN.md +0 -154
  89. package/templates/skills/knowledge/notebooklm-mcp/SKILL.md +0 -71
  90. package/templates/skills/knowledge/notebooklm-mcp/assets/example_asset.txt +0 -24
  91. package/templates/skills/knowledge/notebooklm-mcp/references/api_reference.md +0 -34
  92. package/templates/skills/knowledge/notebooklm-mcp/scripts/example.py +0 -19
  93. package/templates/skills/knowledge/react-components/README.md +0 -36
  94. package/templates/skills/knowledge/react-components/SKILL.md +0 -53
  95. package/templates/skills/knowledge/react-components/examples/gold-standard-card.tsx +0 -80
  96. package/templates/skills/knowledge/react-components/package-lock.json +0 -231
  97. package/templates/skills/knowledge/react-components/package.json +0 -16
  98. package/templates/skills/knowledge/react-components/resources/architecture-checklist.md +0 -15
  99. package/templates/skills/knowledge/react-components/resources/component-template.tsx +0 -37
  100. package/templates/skills/knowledge/react-components/resources/stitch-api-reference.md +0 -14
  101. package/templates/skills/knowledge/react-components/resources/style-guide.json +0 -27
  102. package/templates/skills/knowledge/react-components/scripts/fetch-stitch.sh +0 -30
  103. package/templates/skills/knowledge/react-components/scripts/validate.js +0 -68
  104. package/templates/skills/knowledge/self-update/SKILL.md +0 -60
  105. package/templates/skills/knowledge/self-update/scripts/update_kit.py +0 -103
  106. package/templates/skills/knowledge/stitch-loop/README.md +0 -54
  107. package/templates/skills/knowledge/stitch-loop/SKILL.md +0 -235
  108. package/templates/skills/knowledge/stitch-loop/examples/SITE.md +0 -73
  109. package/templates/skills/knowledge/stitch-loop/examples/next-prompt.md +0 -25
  110. package/templates/skills/knowledge/stitch-loop/resources/baton-schema.md +0 -61
  111. package/templates/skills/knowledge/stitch-loop/resources/site-template.md +0 -104
@@ -0,0 +1,455 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Confluent Kafka Configuration Validator
4
+
5
+ Validates Kafka broker server.properties for:
6
+ - Required settings presence
7
+ - Deprecated configuration detection
8
+ - Version-specific compatibility
9
+ - Security best practices
10
+ - Performance recommendations
11
+
12
+ Usage:
13
+ python validate_config.py --config /opt/confluent/etc/kafka/server.properties --version 8.0
14
+ python validate_config.py --compare broker-01:config1.properties broker-02:config2.properties
15
+
16
+ Arguments:
17
+ --config, -c Path to server.properties file to validate
18
+ --version, -v Target Confluent version (7.x, 8.x) for compatibility checks
19
+ --compare Compare configurations between brokers
20
+ --strict Enable strict mode (fail on warnings)
21
+ --json Output results as JSON
22
+
23
+ Exit Codes:
24
+ 0 - Valid configuration
25
+ 1 - Invalid arguments
26
+ 2 - Configuration file not found
27
+ 3 - Validation errors found
28
+ 4 - Critical errors (deprecated or incompatible settings)
29
+ """
30
+
31
+ import argparse
32
+ import json
33
+ import re
34
+ import sys
35
+ from pathlib import Path
36
+ from typing import Dict, List, Optional, Tuple
37
+
38
+
39
+ # Deprecated settings by version
40
+ DEPRECATED_SETTINGS = {
41
+ "8.0": [
42
+ "log.message.format.version",
43
+ "inter.broker.protocol.version",
44
+ "kafka.metrics.reporters", # Renamed
45
+ "broker.id.generation.enable", # KRaft handles this
46
+ ],
47
+ "7.6": [
48
+ "kafka.metrics.reporters",
49
+ ],
50
+ }
51
+
52
+ # Required settings for KRaft mode
53
+ KRAFT_REQUIRED = [
54
+ "process.roles",
55
+ "node.id",
56
+ "controller.quorum.voters",
57
+ "controller.listener.names",
58
+ ]
59
+
60
+ # Required settings for any production broker
61
+ PRODUCTION_REQUIRED = [
62
+ "log.dirs",
63
+ "listeners",
64
+ "advertised.listeners",
65
+ ]
66
+
67
+ # Security best practices
68
+ SECURITY_RECOMMENDED = {
69
+ "authorizer.class.name": "kafka.security.authorizer.AclAuthorizer",
70
+ "ssl.keystore.location": None, # Just check existence
71
+ "ssl.truststore.location": None,
72
+ "sasl.enabled.mechanisms": None,
73
+ }
74
+
75
+ # Performance recommendations
76
+ PERFORMANCE_DEFAULTS = {
77
+ "num.network.threads": ("8", "Should be at least 8 for production"),
78
+ "num.io.threads": ("16", "Should be 2x network threads or more"),
79
+ "socket.send.buffer.bytes": ("102400", "Increase for high throughput"),
80
+ "socket.receive.buffer.bytes": ("102400", "Increase for high throughput"),
81
+ "num.replica.fetchers": ("4", "Increase for faster replication"),
82
+ }
83
+
84
+
85
+ class ConfigValidator:
86
+ """Validates Kafka broker configuration."""
87
+
88
+ def __init__(self, config_path: str, version: str = "8.0"):
89
+ self.config_path = Path(config_path)
90
+ self.version = version
91
+ self.config: Dict[str, str] = {}
92
+ self.errors: List[str] = []
93
+ self.warnings: List[str] = []
94
+ self.info: List[str] = []
95
+
96
+ def load_config(self) -> bool:
97
+ """Load and parse the configuration file."""
98
+ if not self.config_path.exists():
99
+ self.errors.append(f"Configuration file not found: {self.config_path}")
100
+ return False
101
+
102
+ try:
103
+ with open(self.config_path, "r") as f:
104
+ for line_num, line in enumerate(f, 1):
105
+ line = line.strip()
106
+ # Skip comments and empty lines
107
+ if not line or line.startswith("#"):
108
+ continue
109
+ # Parse key=value
110
+ if "=" in line:
111
+ key, value = line.split("=", 1)
112
+ self.config[key.strip()] = value.strip()
113
+ return True
114
+ except Exception as e:
115
+ self.errors.append(f"Failed to parse config: {e}")
116
+ return False
117
+
118
+ def check_deprecated(self) -> None:
119
+ """Check for deprecated settings."""
120
+ deprecated = DEPRECATED_SETTINGS.get(self.version, [])
121
+ for setting in deprecated:
122
+ if setting in self.config:
123
+ self.errors.append(
124
+ f"DEPRECATED: '{setting}' is deprecated in version {self.version}. "
125
+ f"Current value: {self.config[setting]}"
126
+ )
127
+
128
+ def check_kraft_mode(self) -> None:
129
+ """Check KRaft mode configuration."""
130
+ # Check if using KRaft
131
+ if "process.roles" in self.config:
132
+ self.info.append("KRaft mode detected")
133
+
134
+ # Verify required KRaft settings
135
+ for setting in KRAFT_REQUIRED:
136
+ if setting not in self.config:
137
+ self.errors.append(f"MISSING: Required KRaft setting '{setting}'")
138
+
139
+ # Check for conflicting ZooKeeper settings
140
+ if "zookeeper.connect" in self.config and "zookeeper.metadata.migration.enable" not in self.config:
141
+ self.errors.append(
142
+ "CONFLICT: 'zookeeper.connect' present without 'zookeeper.metadata.migration.enable'. "
143
+ "Either remove ZK config or enable migration mode."
144
+ )
145
+
146
+ # Validate process.roles
147
+ roles = self.config.get("process.roles", "")
148
+ valid_roles = {"broker", "controller", "broker,controller", "controller,broker"}
149
+ if roles not in valid_roles:
150
+ self.errors.append(f"INVALID: 'process.roles' must be one of {valid_roles}")
151
+ else:
152
+ # ZooKeeper mode
153
+ if "zookeeper.connect" in self.config:
154
+ self.warnings.append(
155
+ "ZooKeeper mode detected. Consider migrating to KRaft for version 8.x+"
156
+ )
157
+ else:
158
+ self.errors.append(
159
+ "MISSING: Neither 'process.roles' (KRaft) nor 'zookeeper.connect' (ZK) found"
160
+ )
161
+
162
+ def check_required(self) -> None:
163
+ """Check for required production settings."""
164
+ for setting in PRODUCTION_REQUIRED:
165
+ if setting not in self.config:
166
+ self.errors.append(f"MISSING: Required setting '{setting}'")
167
+
168
+ def check_security(self) -> None:
169
+ """Check security best practices."""
170
+ # Check for authentication
171
+ if "sasl.enabled.mechanisms" not in self.config and "ssl.keystore.location" not in self.config:
172
+ self.warnings.append(
173
+ "SECURITY: No authentication configured (SASL or SSL client auth)"
174
+ )
175
+
176
+ # Check for authorization
177
+ if "authorizer.class.name" not in self.config:
178
+ self.warnings.append(
179
+ "SECURITY: No authorizer configured. Consider enabling ACLs."
180
+ )
181
+
182
+ # Check for TLS
183
+ if "ssl.keystore.location" not in self.config:
184
+ self.warnings.append(
185
+ "SECURITY: SSL/TLS not configured for broker"
186
+ )
187
+
188
+ # Check super.users
189
+ if "super.users" not in self.config and "authorizer.class.name" in self.config:
190
+ self.warnings.append(
191
+ "SECURITY: 'super.users' not set while ACLs enabled. "
192
+ "Ensure admin access is configured."
193
+ )
194
+
195
+ def check_performance(self) -> None:
196
+ """Check performance recommendations."""
197
+ for setting, (recommended, reason) in PERFORMANCE_DEFAULTS.items():
198
+ if setting in self.config:
199
+ try:
200
+ current = int(self.config[setting])
201
+ rec = int(recommended)
202
+ if current < rec:
203
+ self.info.append(
204
+ f"PERF: '{setting}' is {current}, recommended: {rec}. {reason}"
205
+ )
206
+ except ValueError:
207
+ pass
208
+ else:
209
+ self.info.append(f"PERF: '{setting}' not set. Default may be suboptimal.")
210
+
211
+ def check_listeners(self) -> None:
212
+ """Validate listener configuration."""
213
+ listeners = self.config.get("listeners", "")
214
+ advertised = self.config.get("advertised.listeners", "")
215
+ security_map = self.config.get("listener.security.protocol.map", "")
216
+
217
+ # Parse listeners
218
+ listener_names = set()
219
+ for listener in listeners.split(","):
220
+ if "://" in listener:
221
+ name = listener.split("://")[0]
222
+ listener_names.add(name)
223
+
224
+ # Check advertised.listeners matches
225
+ if advertised:
226
+ for listener in advertised.split(","):
227
+ if "://" in listener:
228
+ name = listener.split("://")[0]
229
+ if name not in listener_names:
230
+ self.errors.append(
231
+ f"MISMATCH: Advertised listener '{name}' not in listeners"
232
+ )
233
+
234
+ # Check security protocol map
235
+ if listener_names and security_map:
236
+ mapped_names = set()
237
+ for mapping in security_map.split(","):
238
+ if ":" in mapping:
239
+ name = mapping.split(":")[0]
240
+ mapped_names.add(name)
241
+
242
+ unmapped = listener_names - mapped_names
243
+ if unmapped:
244
+ self.errors.append(
245
+ f"MISMATCH: Listeners {unmapped} not in security protocol map"
246
+ )
247
+
248
+ def check_replication(self) -> None:
249
+ """Check replication settings."""
250
+ rf = self.config.get("default.replication.factor", "1")
251
+ min_isr = self.config.get("min.insync.replicas", "1")
252
+
253
+ try:
254
+ if int(rf) < 3:
255
+ self.warnings.append(
256
+ f"DURABILITY: 'default.replication.factor' is {rf}. "
257
+ f"Recommended: 3 for production."
258
+ )
259
+
260
+ if int(min_isr) < 2:
261
+ self.warnings.append(
262
+ f"DURABILITY: 'min.insync.replicas' is {min_isr}. "
263
+ f"Recommended: 2 for production with RF>=3."
264
+ )
265
+ except ValueError:
266
+ pass
267
+
268
+ def validate(self) -> Dict:
269
+ """Run all validation checks."""
270
+ if not self.load_config():
271
+ return self.get_results()
272
+
273
+ self.check_deprecated()
274
+ self.check_kraft_mode()
275
+ self.check_required()
276
+ self.check_listeners()
277
+ self.check_security()
278
+ self.check_replication()
279
+ self.check_performance()
280
+
281
+ return self.get_results()
282
+
283
+ def get_results(self) -> Dict:
284
+ """Get validation results."""
285
+ status = "valid"
286
+ if self.errors:
287
+ status = "invalid"
288
+ elif self.warnings:
289
+ status = "warnings"
290
+
291
+ return {
292
+ "config_path": str(self.config_path),
293
+ "version": self.version,
294
+ "status": status,
295
+ "errors": self.errors,
296
+ "warnings": self.warnings,
297
+ "info": self.info,
298
+ "settings_count": len(self.config),
299
+ }
300
+
301
+
302
+ def compare_configs(configs: List[str]) -> Dict:
303
+ """Compare configurations between multiple brokers."""
304
+ parsed = {}
305
+ differences = []
306
+
307
+ for config_spec in configs:
308
+ if ":" in config_spec:
309
+ name, path = config_spec.split(":", 1)
310
+ else:
311
+ name = Path(config_spec).stem
312
+ path = config_spec
313
+
314
+ validator = ConfigValidator(path)
315
+ if validator.load_config():
316
+ parsed[name] = validator.config
317
+
318
+ if len(parsed) < 2:
319
+ return {"error": "Need at least 2 valid configs to compare"}
320
+
321
+ # Find all unique keys
322
+ all_keys = set()
323
+ for config in parsed.values():
324
+ all_keys.update(config.keys())
325
+
326
+ # Compare each key
327
+ for key in sorted(all_keys):
328
+ values = {}
329
+ for name, config in parsed.items():
330
+ values[name] = config.get(key, "<not set>")
331
+
332
+ unique_values = set(values.values())
333
+ if len(unique_values) > 1:
334
+ differences.append({
335
+ "setting": key,
336
+ "values": values,
337
+ })
338
+
339
+ return {
340
+ "configs": list(parsed.keys()),
341
+ "differences": differences,
342
+ "identical_settings": len(all_keys) - len(differences),
343
+ }
344
+
345
+
346
+ def print_results(results: Dict, as_json: bool = False) -> None:
347
+ """Print validation results."""
348
+ if as_json:
349
+ print(json.dumps(results, indent=2))
350
+ return
351
+
352
+ print(f"\nšŸ“‹ Configuration Validation: {results['config_path']}")
353
+ print(f" Target Version: {results['version']}")
354
+ print(f" Settings Count: {results['settings_count']}")
355
+ print(f" Status: {'āœ… ' + results['status'].upper() if results['status'] == 'valid' else 'āš ļø ' + results['status'].upper() if results['status'] == 'warnings' else 'āŒ ' + results['status'].upper()}")
356
+ print()
357
+
358
+ if results["errors"]:
359
+ print("āŒ Errors:")
360
+ for error in results["errors"]:
361
+ print(f" • {error}")
362
+ print()
363
+
364
+ if results["warnings"]:
365
+ print("āš ļø Warnings:")
366
+ for warning in results["warnings"]:
367
+ print(f" • {warning}")
368
+ print()
369
+
370
+ if results["info"]:
371
+ print("ā„¹ļø Recommendations:")
372
+ for info in results["info"]:
373
+ print(f" • {info}")
374
+ print()
375
+
376
+
377
+ def print_comparison(results: Dict, as_json: bool = False) -> None:
378
+ """Print comparison results."""
379
+ if as_json:
380
+ print(json.dumps(results, indent=2))
381
+ return
382
+
383
+ if "error" in results:
384
+ print(f"āŒ {results['error']}")
385
+ return
386
+
387
+ print(f"\nšŸ“‹ Configuration Comparison")
388
+ print(f" Brokers: {', '.join(results['configs'])}")
389
+ print(f" Identical Settings: {results['identical_settings']}")
390
+ print(f" Differences: {len(results['differences'])}")
391
+ print()
392
+
393
+ if results["differences"]:
394
+ print("šŸ” Differences:")
395
+ for diff in results["differences"]:
396
+ print(f"\n {diff['setting']}:")
397
+ for broker, value in diff["values"].items():
398
+ print(f" {broker}: {value}")
399
+
400
+
401
+ def main():
402
+ parser = argparse.ArgumentParser(
403
+ description="Confluent Kafka Configuration Validator",
404
+ formatter_class=argparse.RawDescriptionHelpFormatter,
405
+ )
406
+ parser.add_argument(
407
+ "--config", "-c",
408
+ help="Path to server.properties file to validate",
409
+ )
410
+ parser.add_argument(
411
+ "--version", "-v",
412
+ default="8.0",
413
+ help="Target Confluent version (default: 8.0)",
414
+ )
415
+ parser.add_argument(
416
+ "--compare",
417
+ nargs="+",
418
+ help="Compare configurations (format: name:path or just path)",
419
+ )
420
+ parser.add_argument(
421
+ "--strict",
422
+ action="store_true",
423
+ help="Fail on warnings (exit code 3)",
424
+ )
425
+ parser.add_argument(
426
+ "--json",
427
+ action="store_true",
428
+ help="Output results as JSON",
429
+ )
430
+
431
+ args = parser.parse_args()
432
+
433
+ if args.compare:
434
+ results = compare_configs(args.compare)
435
+ print_comparison(results, args.json)
436
+ sys.exit(0 if not results.get("differences") else 3)
437
+
438
+ if not args.config:
439
+ parser.error("--config is required when not using --compare")
440
+
441
+ validator = ConfigValidator(args.config, args.version)
442
+ results = validator.validate()
443
+ print_results(results, args.json)
444
+
445
+ # Exit codes
446
+ if results["status"] == "invalid":
447
+ sys.exit(4 if any("DEPRECATED" in e for e in results["errors"]) else 3)
448
+ elif results["status"] == "warnings" and args.strict:
449
+ sys.exit(3)
450
+ else:
451
+ sys.exit(0)
452
+
453
+
454
+ if __name__ == "__main__":
455
+ main()