patchi 0.7.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (496) hide show
  1. patchi/__init__.py +1 -0
  2. patchi/__main__.py +13 -0
  3. patchi/cli/__init__.py +0 -0
  4. patchi/cli/command_families.py +235 -0
  5. patchi/cli/commands/__init__.py +0 -0
  6. patchi/cli/commands/access_cmd.py +65 -0
  7. patchi/cli/commands/agent_stats_cmd.py +273 -0
  8. patchi/cli/commands/agents_cmd.py +187 -0
  9. patchi/cli/commands/ai_cmd.py +308 -0
  10. patchi/cli/commands/assure_cmd.py +538 -0
  11. patchi/cli/commands/audit_cmd.py +286 -0
  12. patchi/cli/commands/auto_cmd.py +110 -0
  13. patchi/cli/commands/blame_cmd.py +53 -0
  14. patchi/cli/commands/chains_cmd.py +327 -0
  15. patchi/cli/commands/charter_cmd.py +173 -0
  16. patchi/cli/commands/chat_cmd.py +597 -0
  17. patchi/cli/commands/check_cmd.py +232 -0
  18. patchi/cli/commands/cleanup_cmd.py +328 -0
  19. patchi/cli/commands/cockpit_cmd.py +208 -0
  20. patchi/cli/commands/commands_cmd.py +160 -0
  21. patchi/cli/commands/cross_repo_cmd.py +113 -0
  22. patchi/cli/commands/deps_cmd.py +110 -0
  23. patchi/cli/commands/dev_check_cmd.py +342 -0
  24. patchi/cli/commands/dev_cmd.py +445 -0
  25. patchi/cli/commands/doctor_cmd.py +457 -0
  26. patchi/cli/commands/findings_cmd.py +187 -0
  27. patchi/cli/commands/fix_cmd.py +648 -0
  28. patchi/cli/commands/fix_review_cmd.py +273 -0
  29. patchi/cli/commands/heatmap_cmd.py +62 -0
  30. patchi/cli/commands/help_cmd.py +159 -0
  31. patchi/cli/commands/hosted_cmd.py +980 -0
  32. patchi/cli/commands/init.py +456 -0
  33. patchi/cli/commands/key_cmd.py +571 -0
  34. patchi/cli/commands/learn_cmd.py +140 -0
  35. patchi/cli/commands/link_cmd.py +104 -0
  36. patchi/cli/commands/log_cmd.py +57 -0
  37. patchi/cli/commands/memory_cmd.py +215 -0
  38. patchi/cli/commands/model_cmd.py +207 -0
  39. patchi/cli/commands/notify_cmd.py +213 -0
  40. patchi/cli/commands/notify_prompts.py +29 -0
  41. patchi/cli/commands/patch_cmd.py +231 -0
  42. patchi/cli/commands/plan_cmd.py +128 -0
  43. patchi/cli/commands/plugins_cmd.py +307 -0
  44. patchi/cli/commands/queue_cmd.py +152 -0
  45. patchi/cli/commands/quick_cmd.py +159 -0
  46. patchi/cli/commands/ready_cmd.py +292 -0
  47. patchi/cli/commands/report_cmd.py +416 -0
  48. patchi/cli/commands/restrict_cmd.py +127 -0
  49. patchi/cli/commands/review_cmd.py +225 -0
  50. patchi/cli/commands/rules_cmd.py +233 -0
  51. patchi/cli/commands/scan_cmd.py +1908 -0
  52. patchi/cli/commands/settings_cmd.py +45 -0
  53. patchi/cli/commands/status_cmd.py +679 -0
  54. patchi/cli/commands/test_cmd.py +1052 -0
  55. patchi/cli/commands/trend_cmd.py +296 -0
  56. patchi/cli/commands/undo_cmd.py +235 -0
  57. patchi/cli/commands/update_cmd.py +345 -0
  58. patchi/cli/commands/verify_cmd.py +56 -0
  59. patchi/cli/commands/vr_cmd.py +440 -0
  60. patchi/cli/commands/watch_cmd.py +304 -0
  61. patchi/cli/commands/web_cmd.py +114 -0
  62. patchi/cli/console.py +101 -0
  63. patchi/cli/display/__init__.py +1 -0
  64. patchi/cli/display/live_audit.py +269 -0
  65. patchi/cli/display/live_progress.py +169 -0
  66. patchi/cli/framework.py +241 -0
  67. patchi/cli/logo.py +128 -0
  68. patchi/cli/main.py +222 -0
  69. patchi/cli/registry.py +1171 -0
  70. patchi/cli/style.py +68 -0
  71. patchi/cli/themes.py +187 -0
  72. patchi/cli/ux.py +444 -0
  73. patchi/core/__init__.py +0 -0
  74. patchi/core/agents/__init__.py +0 -0
  75. patchi/core/agents/api_fuzzer_agent.py +141 -0
  76. patchi/core/agents/attack_agent.py +242 -0
  77. patchi/core/agents/base.py +1012 -0
  78. patchi/core/agents/bug_predictor_agent.py +58 -0
  79. patchi/core/agents/build_tool_validator.py +150 -0
  80. patchi/core/agents/cache.py +192 -0
  81. patchi/core/agents/chaos_agent.py +133 -0
  82. patchi/core/agents/cicd_generator.py +240 -0
  83. patchi/core/agents/comment_scanner.py +280 -0
  84. patchi/core/agents/console_logging_agent.py +156 -0
  85. patchi/core/agents/coordinator.py +858 -0
  86. patchi/core/agents/core_scanner.py +269 -0
  87. patchi/core/agents/coverage_prioritizer.py +181 -0
  88. patchi/core/agents/dead_code_hygiene.py +296 -0
  89. patchi/core/agents/dead_code_scanner.py +692 -0
  90. patchi/core/agents/dependency_scanner.py +725 -0
  91. patchi/core/agents/doc_claim_agent.py +573 -0
  92. patchi/core/agents/duplicate_scanner.py +568 -0
  93. patchi/core/agents/env_scanner.py +410 -0
  94. patchi/core/agents/feature_flag_agent.py +89 -0
  95. patchi/core/agents/frontend_framework_agent.py +133 -0
  96. patchi/core/agents/goroutine_agent.py +82 -0
  97. patchi/core/agents/governor.py +1972 -0
  98. patchi/core/agents/i18n_agent.py +95 -0
  99. patchi/core/agents/license_compliance.py +247 -0
  100. patchi/core/agents/linking_agent.py +251 -0
  101. patchi/core/agents/memory_profiler_agent.py +102 -0
  102. patchi/core/agents/modernization_agent.py +84 -0
  103. patchi/core/agents/mutation_agent.py +174 -0
  104. patchi/core/agents/nl_query_agent.py +74 -0
  105. patchi/core/agents/promise_rejection_agent.py +100 -0
  106. patchi/core/agents/refactoring_agent.py +286 -0
  107. patchi/core/agents/resource_leak_agent.py +91 -0
  108. patchi/core/agents/route_graph_scanner.py +951 -0
  109. patchi/core/agents/run_agent.py +106 -0
  110. patchi/core/agents/rust_unwrap_agent.py +100 -0
  111. patchi/core/agents/sbom_generator.py +284 -0
  112. patchi/core/agents/scanners.py +67 -0
  113. patchi/core/agents/side/__init__.py +1 -0
  114. patchi/core/agents/side/build_agent.py +65 -0
  115. patchi/core/agents/side/format_agent.py +50 -0
  116. patchi/core/agents/side/install_agent.py +63 -0
  117. patchi/core/agents/side_file_scanner.py +549 -0
  118. patchi/core/agents/snapshot_drift_detector.py +146 -0
  119. patchi/core/agents/spa_route_inventory.py +203 -0
  120. patchi/core/agents/test_scanner.py +442 -0
  121. patchi/core/agents/tool_health.py +109 -0
  122. patchi/core/agents/tool_runner.py +162 -0
  123. patchi/core/agents/type_scanner.py +168 -0
  124. patchi/core/agents/ui_scanner.py +558 -0
  125. patchi/core/ai/__init__.py +20 -0
  126. patchi/core/ai/agent_audit.py +153 -0
  127. patchi/core/ai/agent_profiler.py +277 -0
  128. patchi/core/ai/client.py +499 -0
  129. patchi/core/ai/cost_tracker.py +107 -0
  130. patchi/core/ai/model_router.py +318 -0
  131. patchi/core/ai/orchestrator.py +625 -0
  132. patchi/core/ai/prompts.py +681 -0
  133. patchi/core/ai/smart.py +302 -0
  134. patchi/core/ai/tool_executor.py +24 -0
  135. patchi/core/ai/tools/__init__.py +10 -0
  136. patchi/core/ai/tools/executor.py +621 -0
  137. patchi/core/ai/tools/realize.py +1160 -0
  138. patchi/core/ai/tools/registry.py +1577 -0
  139. patchi/core/assurance/__init__.py +33 -0
  140. patchi/core/assurance/builder.py +211 -0
  141. patchi/core/assurance/graph.py +342 -0
  142. patchi/core/assurance/invariants.py +168 -0
  143. patchi/core/atomic.py +61 -0
  144. patchi/core/attackers/__init__.py +17 -0
  145. patchi/core/attackers/api_abuse.py +89 -0
  146. patchi/core/attackers/auth_bypass.py +73 -0
  147. patchi/core/attackers/base.py +91 -0
  148. patchi/core/attackers/business_logic.py +88 -0
  149. patchi/core/attackers/planner.py +108 -0
  150. patchi/core/attackers/priv_escalation.py +88 -0
  151. patchi/core/attackers/recon.py +87 -0
  152. patchi/core/brain/__init__.py +0 -0
  153. patchi/core/brain/ast_utils/__init__.py +81 -0
  154. patchi/core/brain/ast_utils/assignments.py +219 -0
  155. patchi/core/brain/ast_utils/calls.py +139 -0
  156. patchi/core/brain/ast_utils/config.py +132 -0
  157. patchi/core/brain/ast_utils/control_flow.py +74 -0
  158. patchi/core/brain/ast_utils/dead_symbols.py +205 -0
  159. patchi/core/brain/ast_utils/decorators.py +108 -0
  160. patchi/core/brain/ast_utils/helpers.py +144 -0
  161. patchi/core/brain/ast_utils/imports.py +169 -0
  162. patchi/core/brain/ast_utils/scan.py +143 -0
  163. patchi/core/brain/ast_utils/taint.py +310 -0
  164. patchi/core/brain/audit.py +379 -0
  165. patchi/core/brain/baseline.py +195 -0
  166. patchi/core/brain/blast_radius.py +312 -0
  167. patchi/core/brain/body_tags.py +144 -0
  168. patchi/core/brain/brain.py +1161 -0
  169. patchi/core/brain/brain_context.py +393 -0
  170. patchi/core/brain/brain_watcher.py +340 -0
  171. patchi/core/brain/charter.py +473 -0
  172. patchi/core/brain/classifier.py +214 -0
  173. patchi/core/brain/cockpit.py +350 -0
  174. patchi/core/brain/code_query.py +710 -0
  175. patchi/core/brain/contract.py +889 -0
  176. patchi/core/brain/contract_diff.py +155 -0
  177. patchi/core/brain/council.py +800 -0
  178. patchi/core/brain/cross_repo.py +104 -0
  179. patchi/core/brain/dead_code_tools.py +157 -0
  180. patchi/core/brain/doc_validator.py +197 -0
  181. patchi/core/brain/domain_activator.py +2183 -0
  182. patchi/core/brain/enriched_context.py +220 -0
  183. patchi/core/brain/file_corpus.py +220 -0
  184. patchi/core/brain/framework.py +953 -0
  185. patchi/core/brain/freshness.py +273 -0
  186. patchi/core/brain/git_aware.py +206 -0
  187. patchi/core/brain/heatmap.py +104 -0
  188. patchi/core/brain/ignore_parser.py +153 -0
  189. patchi/core/brain/import_graph.py +486 -0
  190. patchi/core/brain/languages.py +397 -0
  191. patchi/core/brain/layered_brain.py +419 -0
  192. patchi/core/brain/learning.py +229 -0
  193. patchi/core/brain/orphaned_endpoints.py +313 -0
  194. patchi/core/brain/personas/__init__.py +648 -0
  195. patchi/core/brain/personas/bad_user.py +165 -0
  196. patchi/core/brain/personas/base.py +349 -0
  197. patchi/core/brain/proactive.py +807 -0
  198. patchi/core/brain/project_context.py +121 -0
  199. patchi/core/brain/project_reader.py +521 -0
  200. patchi/core/brain/reasoning.py +269 -0
  201. patchi/core/brain/route_detector/__init__.py +15 -0
  202. patchi/core/brain/route_detector/base.py +31 -0
  203. patchi/core/brain/route_detector/csharp.py +209 -0
  204. patchi/core/brain/route_detector/file_based.py +81 -0
  205. patchi/core/brain/route_detector/go.py +152 -0
  206. patchi/core/brain/route_detector/java.py +195 -0
  207. patchi/core/brain/route_detector/javascript.py +197 -0
  208. patchi/core/brain/route_detector/php.py +158 -0
  209. patchi/core/brain/route_detector/python.py +193 -0
  210. patchi/core/brain/route_detector/registry.py +25 -0
  211. patchi/core/brain/route_detector/ruby.py +134 -0
  212. patchi/core/brain/route_detector/rust.py +216 -0
  213. patchi/core/brain/route_detector/swift.py +118 -0
  214. patchi/core/brain/route_mapper.py +265 -0
  215. patchi/core/brain/scanner.py +2136 -0
  216. patchi/core/brain/secrets.py +280 -0
  217. patchi/core/brain/symbol_graph.py +1955 -0
  218. patchi/core/brain/team.py +28 -0
  219. patchi/core/brain/trace_log.py +132 -0
  220. patchi/core/brain/type_checker/__init__.py +65 -0
  221. patchi/core/brain/type_checker/base.py +60 -0
  222. patchi/core/brain/type_checker/csharp.py +59 -0
  223. patchi/core/brain/type_checker/dart.py +59 -0
  224. patchi/core/brain/type_checker/go.py +81 -0
  225. patchi/core/brain/type_checker/java.py +75 -0
  226. patchi/core/brain/type_checker/kotlin.py +75 -0
  227. patchi/core/brain/type_checker/php.py +88 -0
  228. patchi/core/brain/type_checker/python.py +87 -0
  229. patchi/core/brain/type_checker/rust.py +81 -0
  230. patchi/core/brain/type_checker/swift.py +59 -0
  231. patchi/core/brain/type_checker/typescript.py +298 -0
  232. patchi/core/brain/understander.py +130 -0
  233. patchi/core/brain/verify.py +379 -0
  234. patchi/core/campaigns/__init__.py +14 -0
  235. patchi/core/campaigns/base.py +62 -0
  236. patchi/core/campaigns/data_flow_abuse.py +153 -0
  237. patchi/core/campaigns/orchestrator.py +140 -0
  238. patchi/core/campaigns/state_transitions.py +178 -0
  239. patchi/core/ci_bundle.py +104 -0
  240. patchi/core/config.py +311 -0
  241. patchi/core/constants.py +245 -0
  242. patchi/core/debug/__init__.py +25 -0
  243. patchi/core/debug/adapters/__init__.py +11 -0
  244. patchi/core/debug/adapters/codelldb.py +379 -0
  245. patchi/core/debug/adapters/node.py +222 -0
  246. patchi/core/debug/adapters/powershell.py +210 -0
  247. patchi/core/debug/adapters/python.py +244 -0
  248. patchi/core/debug/capture.py +210 -0
  249. patchi/core/debug/dap_client.py +268 -0
  250. patchi/core/detector/__init__.py +0 -0
  251. patchi/core/detector/audit.py +194 -0
  252. patchi/core/detector/bus.py +226 -0
  253. patchi/core/detector/dispatcher.py +275 -0
  254. patchi/core/detector/event.py +254 -0
  255. patchi/core/detector/sigma_engine.py +378 -0
  256. patchi/core/detector/triage.py +289 -0
  257. patchi/core/export/sarif.py +113 -0
  258. patchi/core/fix/__init__.py +17 -0
  259. patchi/core/fix/applier.py +574 -0
  260. patchi/core/fix/base.py +510 -0
  261. patchi/core/fix/code_fixer.py +169 -0
  262. patchi/core/fix/dead_code_remover.py +131 -0
  263. patchi/core/fix/fix_agents.py +775 -0
  264. patchi/core/fix/patch.py +444 -0
  265. patchi/core/fix/risk_gate.py +383 -0
  266. patchi/core/fix/security_fixer.py +93 -0
  267. patchi/core/fix/verify_loop.py +204 -0
  268. patchi/core/fuzz/__init__.py +8 -0
  269. patchi/core/fuzz/corpus.py +137 -0
  270. patchi/core/fuzz/input_fuzzer.py +256 -0
  271. patchi/core/fuzz/sequence_fuzzer.py +162 -0
  272. patchi/core/fuzz/state_fuzzer.py +236 -0
  273. patchi/core/health.py +407 -0
  274. patchi/core/hosted/__init__.py +1 -0
  275. patchi/core/hosted/anomaly.py +298 -0
  276. patchi/core/hosted/audit_log.py +102 -0
  277. patchi/core/hosted/compliance_report.py +148 -0
  278. patchi/core/hosted/ip_reputation.py +169 -0
  279. patchi/core/hosted/log_parsers.py +251 -0
  280. patchi/core/hosted/tokens.py +127 -0
  281. patchi/core/hosted/watchlist.py +164 -0
  282. patchi/core/hosted/webhooks.py +156 -0
  283. patchi/core/memory.py +437 -0
  284. patchi/core/notifications/__init__.py +18 -0
  285. patchi/core/notifications/channels.py +192 -0
  286. patchi/core/notifications/digest.py +141 -0
  287. patchi/core/notifications/escalation.py +158 -0
  288. patchi/core/notifications/notifier.py +182 -0
  289. patchi/core/notifications/quiet_hours.py +69 -0
  290. patchi/core/plugins/__init__.py +48 -0
  291. patchi/core/plugins/analyzer.py +326 -0
  292. patchi/core/plugins/analyzers/__init__.py +6 -0
  293. patchi/core/plugins/analyzers/dead_code_analyzer.py +120 -0
  294. patchi/core/plugins/analyzers/secret_scanner.py +158 -0
  295. patchi/core/plugins/analyzers/todo_scanner.py +115 -0
  296. patchi/core/plugins/registry.py +335 -0
  297. patchi/core/queue.py +375 -0
  298. patchi/core/reliability/__init__.py +13 -0
  299. patchi/core/reliability/chaos.py +163 -0
  300. patchi/core/reliability/fault_injector.py +172 -0
  301. patchi/core/reliability/idempotency.py +128 -0
  302. patchi/core/reliability/recovery.py +147 -0
  303. patchi/core/runtime/__init__.py +5 -0
  304. patchi/core/runtime/tracer.py +230 -0
  305. patchi/core/scan_bus.py +156 -0
  306. patchi/core/security/__init__.py +1 -0
  307. patchi/core/security/ai_validator.py +462 -0
  308. patchi/core/security/app_mapper.py +216 -0
  309. patchi/core/security/app_profile.py +464 -0
  310. patchi/core/security/attack_feedback.py +141 -0
  311. patchi/core/security/attack_scenarios/__init__.py +1 -0
  312. patchi/core/security/attack_tree.py +176 -0
  313. patchi/core/security/auth_audit_agent.py +344 -0
  314. patchi/core/security/authz_agent.py +602 -0
  315. patchi/core/security/auto_fix_proactive.py +620 -0
  316. patchi/core/security/auto_fixer.py +660 -0
  317. patchi/core/security/auto_ticket.py +53 -0
  318. patchi/core/security/bandit_agent.py +152 -0
  319. patchi/core/security/blast_radius.py +226 -0
  320. patchi/core/security/browser_tester.py +333 -0
  321. patchi/core/security/business_logic_agent.py +263 -0
  322. patchi/core/security/catch_block_auditor.py +395 -0
  323. patchi/core/security/cdn_cache_agent.py +357 -0
  324. patchi/core/security/chain_analyzer.py +321 -0
  325. patchi/core/security/chain_to_assurance.py +168 -0
  326. patchi/core/security/charter.py +510 -0
  327. patchi/core/security/cloud_waf_detector.py +295 -0
  328. patchi/core/security/codeql_agent.py +233 -0
  329. patchi/core/security/compliance_agent.py +348 -0
  330. patchi/core/security/confidence_gate.py +423 -0
  331. patchi/core/security/container_scanner.py +282 -0
  332. patchi/core/security/crypto_agent.py +378 -0
  333. patchi/core/security/cve_monitor.py +262 -0
  334. patchi/core/security/dast_agent.py +865 -0
  335. patchi/core/security/defectdojo.py +238 -0
  336. patchi/core/security/defenders/__init__.py +75 -0
  337. patchi/core/security/defenders/auth_middleware.py +115 -0
  338. patchi/core/security/defenders/base.py +97 -0
  339. patchi/core/security/defenders/block_ip.py +71 -0
  340. patchi/core/security/defenders/block_ws_origin.py +36 -0
  341. patchi/core/security/defenders/crypto_fix.py +118 -0
  342. patchi/core/security/defenders/enforce_rate_limit.py +108 -0
  343. patchi/core/security/defenders/fix_code.py +93 -0
  344. patchi/core/security/defenders/invalidate_session.py +40 -0
  345. patchi/core/security/defenders/patch_config.py +70 -0
  346. patchi/core/security/defenders/rotate_secret.py +54 -0
  347. patchi/core/security/defenders/suspend_account.py +41 -0
  348. patchi/core/security/defenders/update_dependency.py +160 -0
  349. patchi/core/security/defense_layer.py +217 -0
  350. patchi/core/security/dependency_vulnerability_agent.py +616 -0
  351. patchi/core/security/detection_pipeline.py +289 -0
  352. patchi/core/security/dns_security_agent.py +497 -0
  353. patchi/core/security/domain_activator_v2.py +913 -0
  354. patchi/core/security/domain_loader.py +1004 -0
  355. patchi/core/security/domains/__init__.py +0 -0
  356. patchi/core/security/email_authentication_agent.py +417 -0
  357. patchi/core/security/env_var_validator.py +304 -0
  358. patchi/core/security/evidence.py +100 -0
  359. patchi/core/security/falco_runtime_agent.py +387 -0
  360. patchi/core/security/fix-playbooks/__init__.py +0 -0
  361. patchi/core/security/gated_finding.py +85 -0
  362. patchi/core/security/git_diff_activator.py +367 -0
  363. patchi/core/security/governance.py +208 -0
  364. patchi/core/security/gradual_gate.py +34 -0
  365. patchi/core/security/history.py +315 -0
  366. patchi/core/security/iac_scanner.py +250 -0
  367. patchi/core/security/ignore_expiry.py +111 -0
  368. patchi/core/security/ignore_learner.py +416 -0
  369. patchi/core/security/injection_agent.py +319 -0
  370. patchi/core/security/insecure_randomness_agent.py +288 -0
  371. patchi/core/security/intent_analyzer.py +326 -0
  372. patchi/core/security/jwt_agent.py +453 -0
  373. patchi/core/security/kubernetes_agent.py +638 -0
  374. patchi/core/security/layer2_orchestrator.py +341 -0
  375. patchi/core/security/llm_security_agent.py +252 -0
  376. patchi/core/security/misconfig_agent.py +600 -0
  377. patchi/core/security/mobile_security_agent.py +474 -0
  378. patchi/core/security/network_agent.py +376 -0
  379. patchi/core/security/noise_filter.py +274 -0
  380. patchi/core/security/orchestrator.py +313 -0
  381. patchi/core/security/pattern_context.py +125 -0
  382. patchi/core/security/pattern_loader.py +101 -0
  383. patchi/core/security/pentest/__init__.py +4 -0
  384. patchi/core/security/pentest/base.py +58 -0
  385. patchi/core/security/pentest/dalfox_adapter.py +40 -0
  386. patchi/core/security/pentest/ffuf_adapter.py +48 -0
  387. patchi/core/security/pentest/nuclei_adapter.py +50 -0
  388. patchi/core/security/pentest/registry.py +80 -0
  389. patchi/core/security/pentest/shannon_adapter.py +107 -0
  390. patchi/core/security/pentest/sqlmap_adapter.py +56 -0
  391. patchi/core/security/pentest/zap_adapter.py +52 -0
  392. patchi/core/security/plan_auditor.py +276 -0
  393. patchi/core/security/policy_engine.py +272 -0
  394. patchi/core/security/prechecks.py +172 -0
  395. patchi/core/security/precommit_hook.py +247 -0
  396. patchi/core/security/privacy_agent.py +361 -0
  397. patchi/core/security/push_notification_agent.py +417 -0
  398. patchi/core/security/pysa_agent.py +143 -0
  399. patchi/core/security/reasoning.py +442 -0
  400. patchi/core/security/red_team_agent.py +184 -0
  401. patchi/core/security/red_team_engine.py +1142 -0
  402. patchi/core/security/remediation.py +360 -0
  403. patchi/core/security/request_interceptor.py +294 -0
  404. patchi/core/security/runtime_validator.py +245 -0
  405. patchi/core/security/saml_sso_agent.py +401 -0
  406. patchi/core/security/sast_agent.py +213 -0
  407. patchi/core/security/sast_gate.py +95 -0
  408. patchi/core/security/scheduler.py +287 -0
  409. patchi/core/security/secrets_runtime_agent.py +476 -0
  410. patchi/core/security/security_agents.py +175 -0
  411. patchi/core/security/security_config.py +551 -0
  412. patchi/core/security/security_probe.py +762 -0
  413. patchi/core/security/security_taint.py +1005 -0
  414. patchi/core/security/sensitive_data_agent.py +271 -0
  415. patchi/core/security/service_mesh_agent.py +412 -0
  416. patchi/core/security/session_management_agent.py +245 -0
  417. patchi/core/security/ssrf_agent.py +422 -0
  418. patchi/core/security/supply_chain.py +449 -0
  419. patchi/core/security/threat_model_generator.py +378 -0
  420. patchi/core/security/threat_model_updater.py +196 -0
  421. patchi/core/security/tool_adapters.py +135 -0
  422. patchi/core/security/tool_verify.py +173 -0
  423. patchi/core/security/websocket_security_agent.py +188 -0
  424. patchi/core/snapshot.py +242 -0
  425. patchi/core/tenant.py +386 -0
  426. patchi/core/testing/__init__.py +17 -0
  427. patchi/core/testing/_browser.py +147 -0
  428. patchi/core/testing/ads_agent.py +141 -0
  429. patchi/core/testing/api_contract_agent.py +450 -0
  430. patchi/core/testing/app_discovery_agent.py +641 -0
  431. patchi/core/testing/app_launcher.py +184 -0
  432. patchi/core/testing/browser_test_agent.py +516 -0
  433. patchi/core/testing/e2e_flow_agent.py +354 -0
  434. patchi/core/testing/flake_detector_agent.py +292 -0
  435. patchi/core/testing/gate.py +31 -0
  436. patchi/core/testing/live_test_runner.py +304 -0
  437. patchi/core/testing/live_v2/__init__.py +1 -0
  438. patchi/core/testing/live_v2/browser_pool.py +349 -0
  439. patchi/core/testing/live_v2/browser_test_runner.py +313 -0
  440. patchi/core/testing/live_v2/runner.py +626 -0
  441. patchi/core/testing/live_v2/screenshot_manager.py +474 -0
  442. patchi/core/testing/live_v2/stress_orchestrator.py +523 -0
  443. patchi/core/testing/live_v2/video_recorder.py +303 -0
  444. patchi/core/testing/regression_agent.py +302 -0
  445. patchi/core/testing/security_test_agent.py +253 -0
  446. patchi/core/testing/stress_test_agent.py +203 -0
  447. patchi/core/testing/test_agents.py +50 -0
  448. patchi/core/testing/test_config.py +194 -0
  449. patchi/core/testing/ui_accessibility_agent.py +324 -0
  450. patchi/core/testing/ui_button_agent.py +294 -0
  451. patchi/core/testing/ui_layout_agent.py +330 -0
  452. patchi/core/testing/unit_test_agent.py +1149 -0
  453. patchi/core/testing/visual_regression_agent.py +260 -0
  454. patchi/web/__init__.py +1 -0
  455. patchi/web/api/__init__.py +1 -0
  456. patchi/web/api/brain_map.py +114 -0
  457. patchi/web/api/charts.py +47 -0
  458. patchi/web/api/chat.py +224 -0
  459. patchi/web/api/cicd.py +576 -0
  460. patchi/web/api/dast_evidence.py +49 -0
  461. patchi/web/api/dev_check.py +320 -0
  462. patchi/web/api/fix.py +134 -0
  463. patchi/web/api/guard.py +65 -0
  464. patchi/web/api/hosted.py +128 -0
  465. patchi/web/api/hosted_v2.py +234 -0
  466. patchi/web/api/live_testing.py +699 -0
  467. patchi/web/api/scan.py +582 -0
  468. patchi/web/api/smart.py +111 -0
  469. patchi/web/api/tenant.py +117 -0
  470. patchi/web/api/tools.py +85 -0
  471. patchi/web/api_legacy.py +2401 -0
  472. patchi/web/app.py +310 -0
  473. patchi/web/events.py +483 -0
  474. patchi/web/routes/__init__.py +1 -0
  475. patchi/web/routes/assurance.py +541 -0
  476. patchi/web/routes/brain.py +36 -0
  477. patchi/web/routes/charter.py +227 -0
  478. patchi/web/routes/chat.py +23 -0
  479. patchi/web/routes/dashboard.py +558 -0
  480. patchi/web/routes/dashboard_v2.py +402 -0
  481. patchi/web/routes/findings.py +330 -0
  482. patchi/web/routes/guard.py +35 -0
  483. patchi/web/routes/history.py +126 -0
  484. patchi/web/routes/landing.py +52 -0
  485. patchi/web/routes/live_testing.py +24 -0
  486. patchi/web/routes/review.py +57 -0
  487. patchi/web/routes/self_improvement.py +110 -0
  488. patchi/web/routes/settings.py +49 -0
  489. patchi/web/routes/tokens.py +29 -0
  490. patchi/web/spawn.py +139 -0
  491. patchi/web/ws.py +141 -0
  492. patchi-0.7.5.dist-info/METADATA +315 -0
  493. patchi-0.7.5.dist-info/RECORD +496 -0
  494. patchi-0.7.5.dist-info/WHEEL +5 -0
  495. patchi-0.7.5.dist-info/licenses/LICENSE +60 -0
  496. patchi-0.7.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,187 @@
1
+ """
2
+ `p agents` — Inspect and monitor Patchi agents.
3
+
4
+ Subcommands:
5
+ p agents — list all agents grouped by type
6
+ p agents list — same as above
7
+ p agents list scanner — list only scanner agents
8
+ p agents list fix — list only fix agents p agents status — show which agents ran in the last scan and their results
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from pathlib import Path
14
+
15
+ from rich.panel import Panel
16
+ from rich.table import Table
17
+ from rich.text import Text
18
+
19
+ # Import scanners to trigger @register decorators
20
+ import patchi.core.agents.scanners # noqa: F401
21
+
22
+ # Security agents register lazily inside security_agents; import it so the
23
+ # whole SECURITY group appears in `p agents list` (previously showed 0).
24
+ import patchi.core.security.security_agents # noqa: F401
25
+ from patchi.cli.console import con
26
+ from patchi.core import memory as mem
27
+ from patchi.core.agents.base import AgentGroup, list_agents
28
+ from patchi.core.agents.coordinator import Coordinator
29
+ from patchi.core.config import require_project_root
30
+
31
+ _GROUP_ORDER = [
32
+ AgentGroup.SCANNER,
33
+ AgentGroup.FIX,
34
+ AgentGroup.TEST,
35
+ AgentGroup.SECURITY,
36
+ AgentGroup.GUARD,
37
+ ]
38
+
39
+
40
+ def run_list(group_name: str | None = None, root: Path | None = None) -> None:
41
+ """p agents [list] [group]"""
42
+ # Parse group filter
43
+ target_group: AgentGroup | None = None
44
+ if group_name:
45
+ try:
46
+ target_group = AgentGroup(group_name.lower())
47
+ except ValueError:
48
+ con.print(f"[red]Unknown group: {group_name!r}[/red]")
49
+ con.print(f"[dim]Valid groups: {', '.join(g.value for g in AgentGroup)}[/dim]")
50
+ return
51
+
52
+ # Load last scan results for status indicators
53
+ scan_results: dict = {}
54
+ try:
55
+ r = root or require_project_root()
56
+ scan_results = mem.get_scan_results(r)
57
+ except RuntimeError:
58
+ pass # no project — still show the agent list
59
+
60
+ con.print()
61
+
62
+ groups_to_show = [target_group] if target_group else _GROUP_ORDER
63
+ for group in groups_to_show:
64
+ agents = list_agents(group)
65
+ _render_group(group, agents, scan_results)
66
+
67
+ if not target_group:
68
+ con.print(
69
+ "[dim]Filter: [bold]p agents list scanner[/bold] · "
70
+ "[bold]p agents list security[/bold] …[/dim]"
71
+ )
72
+ con.print()
73
+
74
+
75
+ def _render_group(
76
+ group: AgentGroup,
77
+ agents: list,
78
+ scan_results: dict,
79
+ ) -> None:
80
+ ant_type = group.ant_type()
81
+ title = f"[bold #C8621A]{group.label()}[/bold #C8621A] [dim]({ant_type})[/dim]"
82
+
83
+ table = Table(show_header=True, header_style="dim", box=None, pad_edge=False, padding=(0, 2))
84
+ table.add_column("Agent", style="bold #F2EDD6", width=26)
85
+ table.add_column("Last Status", width=12)
86
+ table.add_column("Findings", width=10)
87
+ table.add_column("Duration", width=10)
88
+
89
+ for agent_cls in agents:
90
+ last = scan_results.get(agent_cls.name, {})
91
+ status_val = last.get("status", "idle")
92
+ duration_ms = last.get("duration_ms", 0)
93
+ findings = last.get("finding_count", 0)
94
+
95
+ status_text = _status_text(status_val)
96
+ dur_str = f"{duration_ms}ms" if duration_ms else "—"
97
+ find_str = str(findings) if findings else "—"
98
+
99
+ table.add_row(
100
+ agent_cls.name,
101
+ status_text,
102
+ find_str,
103
+ dur_str,
104
+ "",
105
+ )
106
+
107
+ con.print(Panel(table, title=title, border_style="#2A3D28", padding=(0, 1)))
108
+ con.print()
109
+
110
+
111
+ def run_status(root: Path | None = None) -> None:
112
+ """p agents status — detailed last-run status for all agents."""
113
+ try:
114
+ r = root or require_project_root()
115
+ scan_results = mem.get_scan_results(r)
116
+ except RuntimeError as e:
117
+ con.print(f"[red]{e}[/red]")
118
+ return
119
+
120
+ if not scan_results:
121
+ con.print("[dim]No agents have run yet. Run [bold]p scan[/bold] to start.[/dim]")
122
+ return
123
+
124
+ con.print()
125
+ con.print("[bold #F2EDD6]Last Agent Run Summary[/bold #F2EDD6]")
126
+ con.print()
127
+
128
+ table = Table(show_header=True, header_style="bold #C8621A", box=None, pad_edge=False)
129
+ table.add_column("Agent", style="bold #F2EDD6", width=26)
130
+ table.add_column("Status", width=10)
131
+ table.add_column("Findings", justify="right", width=10)
132
+ table.add_column("Files", justify="right", width=8)
133
+ table.add_column("Duration", justify="right", width=10)
134
+ table.add_column("Errors", style="dim", width=6)
135
+
136
+ for agent_name, data in sorted(scan_results.items()):
137
+ if agent_name == "Brain":
138
+ continue # Brain is not an agent
139
+ status_text = _status_text(data.get("status", "idle"))
140
+ dur = data.get("duration_ms", 0)
141
+ table.add_row(
142
+ agent_name,
143
+ status_text,
144
+ str(data.get("finding_count", 0)),
145
+ str(data.get("files_scanned", 0)),
146
+ f"{dur}ms",
147
+ str(len(data.get("errors", []))),
148
+ )
149
+
150
+ con.print(table)
151
+ con.print()
152
+
153
+
154
+ def run_reset(agent_name: str | None = None, root: Path | None = None) -> None:
155
+ """p agents reset [name] — reset circuit breaker for one or all agents."""
156
+ try:
157
+ r = root or require_project_root()
158
+ except RuntimeError as e:
159
+ con.print(f"[red]{e}[/red]")
160
+ return
161
+
162
+ coord = Coordinator(r)
163
+ reset = coord.reset_circuit_breaker(agent_name)
164
+ if reset:
165
+ con.print()
166
+ con.print(
167
+ f"[#4ADE80]✓[/#4ADE80] Circuit breaker reset for [bold]{len(reset)}[/bold] agent(s):"
168
+ )
169
+ for name in reset[:10]:
170
+ con.print(f" [dim] {name}[/dim]")
171
+ if len(reset) > 10:
172
+ con.print(f" [dim] ... and {len(reset) - 10} more[/dim]")
173
+ con.print()
174
+ else:
175
+ con.print("[dim]No circuit-broken agents to reset.[/dim]")
176
+
177
+
178
+ def _status_text(status: str) -> Text:
179
+ colors = {
180
+ "done": "#4ADE80",
181
+ "failed": "#FF4D6D",
182
+ "skipped": "dim",
183
+ "running": "#C8621A",
184
+ "idle": "dim",
185
+ }
186
+ label = str(status) if status is not None else "unknown"
187
+ return Text(label, style=colors.get(status, "white"))
@@ -0,0 +1,308 @@
1
+ """
2
+ `p ai` — AI configuration and status.
3
+
4
+ Subcommands:
5
+ p ai status — Show all configured AI keys and their status
6
+ p ai test — Send a test prompt to the active AI provider
7
+ p ai add — Add a new API key interactively
8
+ p ai remove <name> — Remove an API key by name
9
+ p ai profiles — List configured AI provider profiles
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ import time
16
+ from pathlib import Path
17
+
18
+ from rich.table import Table
19
+ from rich.text import Text
20
+
21
+ from patchi.cli.commands.key_cmd import PROVIDERS as KEY_PROVIDERS, _secure_key_input, _write_env_var, _ensure_gitignore
22
+ from patchi.cli.console import con
23
+ from patchi.core.config import require_project_root
24
+
25
+ _TEST_PROMPT = "Reply with exactly: PATCHI AI READY"
26
+
27
+
28
+ def _provider_by_name(name: str) -> dict | None:
29
+ for p in KEY_PROVIDERS:
30
+ if p["name"].lower() == name.lower():
31
+ return p
32
+ return None
33
+
34
+
35
+ def run(args) -> None:
36
+ ai_cmd = getattr(args, "ai_cmd", None) or "status"
37
+ try:
38
+ root = require_project_root()
39
+ except RuntimeError as e:
40
+ con.print(f"[red]{e}[/red]")
41
+ return
42
+
43
+ if ai_cmd == "status":
44
+ run_status(root)
45
+ elif ai_cmd == "test":
46
+ run_test(root)
47
+ elif ai_cmd == "add":
48
+ run_add(root)
49
+ elif ai_cmd == "remove":
50
+ run_remove(root, getattr(args, "name", ""))
51
+ else:
52
+ con.print(f"[red]Unknown ai subcommand: {ai_cmd!r}[/red]")
53
+
54
+
55
+ def run_profiles(root=None) -> None:
56
+ """List configured AI provider profiles (E-12 S-2 seed: p ai profiles)."""
57
+ from rich.table import Table
58
+
59
+ from patchi.cli.console import con
60
+
61
+ r = _resolve_root(root)
62
+ from patchi.core import config as cfg
63
+
64
+ keys = cfg.load(r).get("ai", {}).get("keys", [])
65
+ con.print()
66
+ if not keys:
67
+ con.print("[yellow]No AI provider profiles configured.[/yellow]")
68
+ con.print("[dim]Add one with `p key add` or edit .patchi/config.json.[/dim]")
69
+ con.print()
70
+ return
71
+
72
+ table = Table(show_header=True, header_style="bold #C8621A", box=None, pad_edge=False)
73
+ table.add_column("Nickname", style="bold #F2EDD6", width=14)
74
+ table.add_column("Provider", width=10)
75
+ table.add_column("Model", width=28)
76
+ table.add_column("Base URL", width=40)
77
+ table.add_column("Status", width=7)
78
+ table.add_column("Env Var", style="dim")
79
+
80
+ for k in keys:
81
+ status = k.get("status", "?")
82
+ color = {"ok": "#4ADE80", "error": "#FF4D6D"}.get(status, "#FACC15")
83
+ table.add_row(
84
+ k.get("nickname", "?"),
85
+ k.get("provider", "?"),
86
+ k.get("model", "?"),
87
+ k.get("base_url", "?"),
88
+ f"[{color}]{status}[/{color}]",
89
+ k.get("env_var", "?"),
90
+ )
91
+ con.print(table)
92
+ con.print()
93
+ con.print(
94
+ "[dim]Profile schema (E-12 S-2): nickname, provider, env_var, "
95
+ "base_url, model, format, status, extra_body[/dim]"
96
+ )
97
+ con.print()
98
+
99
+
100
+ def _resolve_root(root: Path | None) -> Path:
101
+ if root is not None:
102
+ return root
103
+ try:
104
+ return require_project_root()
105
+ except RuntimeError as e:
106
+ con.print(f"[red]{e}[/red]")
107
+ raise SystemExit(1) from e
108
+
109
+
110
+ def run_status(root: Path | None = None) -> None:
111
+ import patchi.core.config as cfg
112
+
113
+ config = cfg.load(_resolve_root(root))
114
+ ai_cfg = config.get("ai", {})
115
+ keys = ai_cfg.get("keys", [])
116
+ local = ai_cfg.get("local_model_name")
117
+
118
+ con.print()
119
+ con.print("[bold #C8621A]AI Configuration[/bold #C8621A]")
120
+ con.print()
121
+
122
+ # Local
123
+ if local:
124
+ con.print(f" [#4ADE80]●[/#4ADE80] Local model (Ollama): [bold]{local}[/bold]")
125
+ else:
126
+ con.print(" [dim]○ Local model: not configured[/dim]")
127
+
128
+ # API keys
129
+ if keys:
130
+ table = Table(show_header=True, header_style="bold #C8621A", box=None, pad_edge=False)
131
+ table.add_column("Name", style="bold", width=18)
132
+ table.add_column("Provider", width=16)
133
+ table.add_column("Model", width=24)
134
+ table.add_column("Status", width=12)
135
+
136
+ for k in keys:
137
+ env_var = k.get("env_var", "")
138
+ has_key = bool(os.environ.get(env_var))
139
+ status = (
140
+ Text("ready", style="#4ADE80") if has_key else Text("missing env", style="#FACC15")
141
+ )
142
+ table.add_row(
143
+ k.get("nickname", "?"),
144
+ k.get("base_url", "").replace("https://", "").split("/")[0][:16],
145
+ k.get("model", "?"),
146
+ status,
147
+ )
148
+ con.print(table)
149
+ else:
150
+ con.print(" [dim]No API keys configured. Run: p ai add[/dim]")
151
+
152
+ con.print()
153
+
154
+
155
+ def run_test(root: Path | None = None) -> None:
156
+ import patchi.core.config as cfg
157
+
158
+ config = cfg.load(_resolve_root(root))
159
+
160
+ con.print()
161
+ con.print("[bold #C8621A]Testing AI Connection[/bold #C8621A]")
162
+ con.print()
163
+
164
+ from patchi.core.fix.base import _call_ai
165
+
166
+ con.print(" Sending test prompt…", end=" ")
167
+ t0 = time.monotonic()
168
+ result = _call_ai(_TEST_PROMPT, config, max_tokens=20)
169
+ elapsed = (time.monotonic() - t0) * 1000
170
+
171
+ if result:
172
+ con.print(f"[#4ADE80]OK[/#4ADE80] ({elapsed:.0f}ms)")
173
+ con.print(f" Response: [dim]{result[:120]}[/dim]")
174
+ else:
175
+ con.print("[#FACC15]No response[/#FACC15]")
176
+ con.print(" [dim]No AI configured or all keys failed.[/dim]")
177
+ con.print()
178
+
179
+
180
+ def run_add(root: Path | None = None) -> None:
181
+ from rich.prompt import Prompt
182
+
183
+ import patchi.core.config as cfg
184
+
185
+ r = _resolve_root(root)
186
+
187
+ con.print()
188
+ con.print("[bold #C8621A]Add AI Key[/bold #C8621A]")
189
+ con.print()
190
+
191
+ # Show provider list (same as key add)
192
+ for i, p in enumerate(KEY_PROVIDERS, 1):
193
+ con.print(f" [bold]{i:>2}.[/bold] {p['name']:<14} [dim]{p['docs']}[/dim]")
194
+ con.print()
195
+
196
+ # Provider pick
197
+ raw = Prompt.ask("[#F2EDD6]Provider number or name[/#F2EDD6]")
198
+ provider_data = None
199
+ if raw.isdigit():
200
+ idx = int(raw) - 1
201
+ if 0 <= idx < len(KEY_PROVIDERS):
202
+ provider_data = KEY_PROVIDERS[idx]
203
+ else:
204
+ provider_data = _provider_by_name(raw)
205
+
206
+ if not provider_data:
207
+ con.print(f"[red]Unknown provider: {raw!r}[/red]")
208
+ return
209
+
210
+ provider_name = provider_data["name"]
211
+
212
+ # Handle Custom provider - prompt for details
213
+ if provider_name == "Custom":
214
+ provider_name = Prompt.ask("[#F2EDD6]Provider name[/#F2EDD6]")
215
+ if not provider_name.strip():
216
+ con.print("[red]Provider name is required.[/red]")
217
+ return
218
+ base_url = Prompt.ask("[#F2EDD6]API base URL[/#F2EDD6]", default="https://api.openai.com/v1")
219
+ if not base_url.strip():
220
+ con.print("[red]Base URL is required.[/red]")
221
+ return
222
+ model = Prompt.ask("[#F2EDD6]Model name[/#F2EDD6]")
223
+ if not model.strip():
224
+ con.print("[red]Model name is required.[/red]")
225
+ return
226
+ format_choices = ["openai", "anthropic", "google", "cohere"]
227
+ fmt = Prompt.ask("[#F2EDD6]API format[/#F2EDD6]", choices=format_choices, default="openai")
228
+ provider_data = {
229
+ "name": provider_name,
230
+ "base": base_url.strip(),
231
+ "model": model.strip(),
232
+ "format": fmt,
233
+ "docs": "custom",
234
+ }
235
+
236
+ # Nickname
237
+ nickname = Prompt.ask(
238
+ "[#F2EDD6]Nickname for this key[/#F2EDD6]",
239
+ default=provider_name,
240
+ )
241
+
242
+ # Key input - use secure input that works in PowerShell
243
+ key_value = _secure_key_input("[#F2EDD6]Paste your API key[/#F2EDD6]")
244
+ if not key_value.strip():
245
+ con.print("[red]No key entered.[/red]")
246
+ return
247
+
248
+ # Build env var name
249
+ env_var = f"PATCHI_KEY_{nickname.upper().replace(' ', '_').replace('-', '_')}"
250
+
251
+ # Write to .patchi/.env
252
+ env_file = r / ".patchi" / ".env"
253
+ _write_env_var(env_file, env_var, key_value.strip())
254
+
255
+ # Add to .gitignore
256
+ _ensure_gitignore(r)
257
+
258
+ # Save to config
259
+ config = cfg.load(r)
260
+ keys: list[dict] = config.get("ai", {}).get("keys", [])
261
+
262
+ # Replace if nickname already exists
263
+ keys = [k for k in keys if k.get("nickname") != nickname]
264
+ keys.append(
265
+ {
266
+ "provider": provider_name,
267
+ "nickname": nickname,
268
+ "env_var": env_var,
269
+ "format": provider_data["format"],
270
+ "base_url": provider_data["base"],
271
+ "model": provider_data["model"],
272
+ "status": "untested",
273
+ }
274
+ )
275
+ cfg.set_value("ai.keys", keys, r)
276
+
277
+ con.print()
278
+ con.print(
279
+ f"[#4ADE80]✓[/#4ADE80] Key [bold]{nickname}[/bold] saved.\n"
280
+ f"[dim]Env var: {env_var} · File: .patchi/.env[/dim]"
281
+ )
282
+ con.print()
283
+
284
+ # Offer to test immediately
285
+ from rich.prompt import Confirm
286
+ if Confirm.ask("Test this key now?", default=True):
287
+ run_test(nickname, root=r)
288
+
289
+
290
+ def run_remove(root: Path | None = None, name: str = "") -> None:
291
+ import patchi.core.config as cfg
292
+
293
+ if not name:
294
+ con.print("[red]Usage: p ai remove <nickname>[/red]")
295
+ return
296
+
297
+ r = _resolve_root(root)
298
+ config = cfg.load(r)
299
+ keys = config.get("ai", {}).get("keys", [])
300
+ before = len(keys)
301
+ config["ai"]["keys"] = [k for k in keys if k.get("nickname") != name]
302
+
303
+ if len(config["ai"]["keys"]) == before:
304
+ con.print(f"[yellow]No key with nickname {name!r} found.[/yellow]")
305
+ return
306
+
307
+ cfg.save(config, r)
308
+ con.print(f"[#4ADE80]✓[/#4ADE80] Key [bold]{name}[/bold] removed.")