attune-ai 2.0.0__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 (457) hide show
  1. attune/__init__.py +358 -0
  2. attune/adaptive/__init__.py +13 -0
  3. attune/adaptive/task_complexity.py +127 -0
  4. attune/agent_monitoring.py +414 -0
  5. attune/cache/__init__.py +117 -0
  6. attune/cache/base.py +166 -0
  7. attune/cache/dependency_manager.py +256 -0
  8. attune/cache/hash_only.py +251 -0
  9. attune/cache/hybrid.py +457 -0
  10. attune/cache/storage.py +285 -0
  11. attune/cache_monitor.py +356 -0
  12. attune/cache_stats.py +298 -0
  13. attune/cli/__init__.py +152 -0
  14. attune/cli/__main__.py +12 -0
  15. attune/cli/commands/__init__.py +1 -0
  16. attune/cli/commands/batch.py +264 -0
  17. attune/cli/commands/cache.py +248 -0
  18. attune/cli/commands/help.py +331 -0
  19. attune/cli/commands/info.py +140 -0
  20. attune/cli/commands/inspect.py +436 -0
  21. attune/cli/commands/inspection.py +57 -0
  22. attune/cli/commands/memory.py +48 -0
  23. attune/cli/commands/metrics.py +92 -0
  24. attune/cli/commands/orchestrate.py +184 -0
  25. attune/cli/commands/patterns.py +207 -0
  26. attune/cli/commands/profiling.py +202 -0
  27. attune/cli/commands/provider.py +98 -0
  28. attune/cli/commands/routing.py +285 -0
  29. attune/cli/commands/setup.py +96 -0
  30. attune/cli/commands/status.py +235 -0
  31. attune/cli/commands/sync.py +166 -0
  32. attune/cli/commands/tier.py +121 -0
  33. attune/cli/commands/utilities.py +114 -0
  34. attune/cli/commands/workflow.py +579 -0
  35. attune/cli/core.py +32 -0
  36. attune/cli/parsers/__init__.py +68 -0
  37. attune/cli/parsers/batch.py +118 -0
  38. attune/cli/parsers/cache.py +65 -0
  39. attune/cli/parsers/help.py +41 -0
  40. attune/cli/parsers/info.py +26 -0
  41. attune/cli/parsers/inspect.py +66 -0
  42. attune/cli/parsers/metrics.py +42 -0
  43. attune/cli/parsers/orchestrate.py +61 -0
  44. attune/cli/parsers/patterns.py +54 -0
  45. attune/cli/parsers/provider.py +40 -0
  46. attune/cli/parsers/routing.py +110 -0
  47. attune/cli/parsers/setup.py +42 -0
  48. attune/cli/parsers/status.py +47 -0
  49. attune/cli/parsers/sync.py +31 -0
  50. attune/cli/parsers/tier.py +33 -0
  51. attune/cli/parsers/workflow.py +77 -0
  52. attune/cli/utils/__init__.py +1 -0
  53. attune/cli/utils/data.py +242 -0
  54. attune/cli/utils/helpers.py +68 -0
  55. attune/cli_legacy.py +3957 -0
  56. attune/cli_minimal.py +1159 -0
  57. attune/cli_router.py +437 -0
  58. attune/cli_unified.py +814 -0
  59. attune/config/__init__.py +66 -0
  60. attune/config/xml_config.py +286 -0
  61. attune/config.py +545 -0
  62. attune/coordination.py +870 -0
  63. attune/core.py +1511 -0
  64. attune/core_modules/__init__.py +15 -0
  65. attune/cost_tracker.py +626 -0
  66. attune/dashboard/__init__.py +41 -0
  67. attune/dashboard/app.py +512 -0
  68. attune/dashboard/simple_server.py +435 -0
  69. attune/dashboard/standalone_server.py +547 -0
  70. attune/discovery.py +306 -0
  71. attune/emergence.py +306 -0
  72. attune/exceptions.py +123 -0
  73. attune/feedback_loops.py +373 -0
  74. attune/hot_reload/README.md +473 -0
  75. attune/hot_reload/__init__.py +62 -0
  76. attune/hot_reload/config.py +83 -0
  77. attune/hot_reload/integration.py +229 -0
  78. attune/hot_reload/reloader.py +298 -0
  79. attune/hot_reload/watcher.py +183 -0
  80. attune/hot_reload/websocket.py +177 -0
  81. attune/levels.py +577 -0
  82. attune/leverage_points.py +441 -0
  83. attune/logging_config.py +261 -0
  84. attune/mcp/__init__.py +10 -0
  85. attune/mcp/server.py +506 -0
  86. attune/memory/__init__.py +237 -0
  87. attune/memory/claude_memory.py +469 -0
  88. attune/memory/config.py +224 -0
  89. attune/memory/control_panel.py +1290 -0
  90. attune/memory/control_panel_support.py +145 -0
  91. attune/memory/cross_session.py +845 -0
  92. attune/memory/edges.py +179 -0
  93. attune/memory/encryption.py +159 -0
  94. attune/memory/file_session.py +770 -0
  95. attune/memory/graph.py +570 -0
  96. attune/memory/long_term.py +913 -0
  97. attune/memory/long_term_types.py +99 -0
  98. attune/memory/mixins/__init__.py +25 -0
  99. attune/memory/mixins/backend_init_mixin.py +249 -0
  100. attune/memory/mixins/capabilities_mixin.py +208 -0
  101. attune/memory/mixins/handoff_mixin.py +208 -0
  102. attune/memory/mixins/lifecycle_mixin.py +49 -0
  103. attune/memory/mixins/long_term_mixin.py +352 -0
  104. attune/memory/mixins/promotion_mixin.py +109 -0
  105. attune/memory/mixins/short_term_mixin.py +182 -0
  106. attune/memory/nodes.py +179 -0
  107. attune/memory/redis_bootstrap.py +540 -0
  108. attune/memory/security/__init__.py +31 -0
  109. attune/memory/security/audit_logger.py +932 -0
  110. attune/memory/security/pii_scrubber.py +640 -0
  111. attune/memory/security/secrets_detector.py +678 -0
  112. attune/memory/short_term.py +2192 -0
  113. attune/memory/simple_storage.py +302 -0
  114. attune/memory/storage/__init__.py +15 -0
  115. attune/memory/storage_backend.py +167 -0
  116. attune/memory/summary_index.py +583 -0
  117. attune/memory/types.py +446 -0
  118. attune/memory/unified.py +182 -0
  119. attune/meta_workflows/__init__.py +74 -0
  120. attune/meta_workflows/agent_creator.py +248 -0
  121. attune/meta_workflows/builtin_templates.py +567 -0
  122. attune/meta_workflows/cli_commands/__init__.py +56 -0
  123. attune/meta_workflows/cli_commands/agent_commands.py +321 -0
  124. attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
  125. attune/meta_workflows/cli_commands/config_commands.py +232 -0
  126. attune/meta_workflows/cli_commands/memory_commands.py +182 -0
  127. attune/meta_workflows/cli_commands/template_commands.py +354 -0
  128. attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
  129. attune/meta_workflows/cli_meta_workflows.py +59 -0
  130. attune/meta_workflows/form_engine.py +292 -0
  131. attune/meta_workflows/intent_detector.py +409 -0
  132. attune/meta_workflows/models.py +569 -0
  133. attune/meta_workflows/pattern_learner.py +738 -0
  134. attune/meta_workflows/plan_generator.py +384 -0
  135. attune/meta_workflows/session_context.py +397 -0
  136. attune/meta_workflows/template_registry.py +229 -0
  137. attune/meta_workflows/workflow.py +984 -0
  138. attune/metrics/__init__.py +12 -0
  139. attune/metrics/collector.py +31 -0
  140. attune/metrics/prompt_metrics.py +194 -0
  141. attune/models/__init__.py +172 -0
  142. attune/models/__main__.py +13 -0
  143. attune/models/adaptive_routing.py +437 -0
  144. attune/models/auth_cli.py +444 -0
  145. attune/models/auth_strategy.py +450 -0
  146. attune/models/cli.py +655 -0
  147. attune/models/empathy_executor.py +354 -0
  148. attune/models/executor.py +257 -0
  149. attune/models/fallback.py +762 -0
  150. attune/models/provider_config.py +282 -0
  151. attune/models/registry.py +472 -0
  152. attune/models/tasks.py +359 -0
  153. attune/models/telemetry/__init__.py +71 -0
  154. attune/models/telemetry/analytics.py +594 -0
  155. attune/models/telemetry/backend.py +196 -0
  156. attune/models/telemetry/data_models.py +431 -0
  157. attune/models/telemetry/storage.py +489 -0
  158. attune/models/token_estimator.py +420 -0
  159. attune/models/validation.py +280 -0
  160. attune/monitoring/__init__.py +52 -0
  161. attune/monitoring/alerts.py +946 -0
  162. attune/monitoring/alerts_cli.py +448 -0
  163. attune/monitoring/multi_backend.py +271 -0
  164. attune/monitoring/otel_backend.py +362 -0
  165. attune/optimization/__init__.py +19 -0
  166. attune/optimization/context_optimizer.py +272 -0
  167. attune/orchestration/__init__.py +67 -0
  168. attune/orchestration/agent_templates.py +707 -0
  169. attune/orchestration/config_store.py +499 -0
  170. attune/orchestration/execution_strategies.py +2111 -0
  171. attune/orchestration/meta_orchestrator.py +1168 -0
  172. attune/orchestration/pattern_learner.py +696 -0
  173. attune/orchestration/real_tools.py +931 -0
  174. attune/pattern_cache.py +187 -0
  175. attune/pattern_library.py +542 -0
  176. attune/patterns/debugging/all_patterns.json +81 -0
  177. attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
  178. attune/patterns/refactoring_memory.json +89 -0
  179. attune/persistence.py +564 -0
  180. attune/platform_utils.py +265 -0
  181. attune/plugins/__init__.py +28 -0
  182. attune/plugins/base.py +361 -0
  183. attune/plugins/registry.py +268 -0
  184. attune/project_index/__init__.py +32 -0
  185. attune/project_index/cli.py +335 -0
  186. attune/project_index/index.py +667 -0
  187. attune/project_index/models.py +504 -0
  188. attune/project_index/reports.py +474 -0
  189. attune/project_index/scanner.py +777 -0
  190. attune/project_index/scanner_parallel.py +291 -0
  191. attune/prompts/__init__.py +61 -0
  192. attune/prompts/config.py +77 -0
  193. attune/prompts/context.py +177 -0
  194. attune/prompts/parser.py +285 -0
  195. attune/prompts/registry.py +313 -0
  196. attune/prompts/templates.py +208 -0
  197. attune/redis_config.py +302 -0
  198. attune/redis_memory.py +799 -0
  199. attune/resilience/__init__.py +56 -0
  200. attune/resilience/circuit_breaker.py +256 -0
  201. attune/resilience/fallback.py +179 -0
  202. attune/resilience/health.py +300 -0
  203. attune/resilience/retry.py +209 -0
  204. attune/resilience/timeout.py +135 -0
  205. attune/routing/__init__.py +43 -0
  206. attune/routing/chain_executor.py +433 -0
  207. attune/routing/classifier.py +217 -0
  208. attune/routing/smart_router.py +234 -0
  209. attune/routing/workflow_registry.py +343 -0
  210. attune/scaffolding/README.md +589 -0
  211. attune/scaffolding/__init__.py +35 -0
  212. attune/scaffolding/__main__.py +14 -0
  213. attune/scaffolding/cli.py +240 -0
  214. attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
  215. attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
  216. attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
  217. attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
  218. attune/socratic/__init__.py +256 -0
  219. attune/socratic/ab_testing.py +958 -0
  220. attune/socratic/blueprint.py +533 -0
  221. attune/socratic/cli.py +703 -0
  222. attune/socratic/collaboration.py +1114 -0
  223. attune/socratic/domain_templates.py +924 -0
  224. attune/socratic/embeddings.py +738 -0
  225. attune/socratic/engine.py +794 -0
  226. attune/socratic/explainer.py +682 -0
  227. attune/socratic/feedback.py +772 -0
  228. attune/socratic/forms.py +629 -0
  229. attune/socratic/generator.py +732 -0
  230. attune/socratic/llm_analyzer.py +637 -0
  231. attune/socratic/mcp_server.py +702 -0
  232. attune/socratic/session.py +312 -0
  233. attune/socratic/storage.py +667 -0
  234. attune/socratic/success.py +730 -0
  235. attune/socratic/visual_editor.py +860 -0
  236. attune/socratic/web_ui.py +958 -0
  237. attune/telemetry/__init__.py +39 -0
  238. attune/telemetry/agent_coordination.py +475 -0
  239. attune/telemetry/agent_tracking.py +367 -0
  240. attune/telemetry/approval_gates.py +545 -0
  241. attune/telemetry/cli.py +1231 -0
  242. attune/telemetry/commands/__init__.py +14 -0
  243. attune/telemetry/commands/dashboard_commands.py +696 -0
  244. attune/telemetry/event_streaming.py +409 -0
  245. attune/telemetry/feedback_loop.py +567 -0
  246. attune/telemetry/usage_tracker.py +591 -0
  247. attune/templates.py +754 -0
  248. attune/test_generator/__init__.py +38 -0
  249. attune/test_generator/__main__.py +14 -0
  250. attune/test_generator/cli.py +234 -0
  251. attune/test_generator/generator.py +355 -0
  252. attune/test_generator/risk_analyzer.py +216 -0
  253. attune/test_generator/templates/unit_test.py.jinja2 +272 -0
  254. attune/tier_recommender.py +384 -0
  255. attune/tools.py +183 -0
  256. attune/trust/__init__.py +28 -0
  257. attune/trust/circuit_breaker.py +579 -0
  258. attune/trust_building.py +527 -0
  259. attune/validation/__init__.py +19 -0
  260. attune/validation/xml_validator.py +281 -0
  261. attune/vscode_bridge.py +173 -0
  262. attune/workflow_commands.py +780 -0
  263. attune/workflow_patterns/__init__.py +33 -0
  264. attune/workflow_patterns/behavior.py +249 -0
  265. attune/workflow_patterns/core.py +76 -0
  266. attune/workflow_patterns/output.py +99 -0
  267. attune/workflow_patterns/registry.py +255 -0
  268. attune/workflow_patterns/structural.py +288 -0
  269. attune/workflows/__init__.py +539 -0
  270. attune/workflows/autonomous_test_gen.py +1268 -0
  271. attune/workflows/base.py +2667 -0
  272. attune/workflows/batch_processing.py +342 -0
  273. attune/workflows/bug_predict.py +1084 -0
  274. attune/workflows/builder.py +273 -0
  275. attune/workflows/caching.py +253 -0
  276. attune/workflows/code_review.py +1048 -0
  277. attune/workflows/code_review_adapters.py +312 -0
  278. attune/workflows/code_review_pipeline.py +722 -0
  279. attune/workflows/config.py +645 -0
  280. attune/workflows/dependency_check.py +644 -0
  281. attune/workflows/document_gen/__init__.py +25 -0
  282. attune/workflows/document_gen/config.py +30 -0
  283. attune/workflows/document_gen/report_formatter.py +162 -0
  284. attune/workflows/document_gen/workflow.py +1426 -0
  285. attune/workflows/document_manager.py +216 -0
  286. attune/workflows/document_manager_README.md +134 -0
  287. attune/workflows/documentation_orchestrator.py +1205 -0
  288. attune/workflows/history.py +510 -0
  289. attune/workflows/keyboard_shortcuts/__init__.py +39 -0
  290. attune/workflows/keyboard_shortcuts/generators.py +391 -0
  291. attune/workflows/keyboard_shortcuts/parsers.py +416 -0
  292. attune/workflows/keyboard_shortcuts/prompts.py +295 -0
  293. attune/workflows/keyboard_shortcuts/schema.py +193 -0
  294. attune/workflows/keyboard_shortcuts/workflow.py +509 -0
  295. attune/workflows/llm_base.py +363 -0
  296. attune/workflows/manage_docs.py +87 -0
  297. attune/workflows/manage_docs_README.md +134 -0
  298. attune/workflows/manage_documentation.py +821 -0
  299. attune/workflows/new_sample_workflow1.py +149 -0
  300. attune/workflows/new_sample_workflow1_README.md +150 -0
  301. attune/workflows/orchestrated_health_check.py +849 -0
  302. attune/workflows/orchestrated_release_prep.py +600 -0
  303. attune/workflows/output.py +413 -0
  304. attune/workflows/perf_audit.py +863 -0
  305. attune/workflows/pr_review.py +762 -0
  306. attune/workflows/progress.py +785 -0
  307. attune/workflows/progress_server.py +322 -0
  308. attune/workflows/progressive/README 2.md +454 -0
  309. attune/workflows/progressive/README.md +454 -0
  310. attune/workflows/progressive/__init__.py +82 -0
  311. attune/workflows/progressive/cli.py +219 -0
  312. attune/workflows/progressive/core.py +488 -0
  313. attune/workflows/progressive/orchestrator.py +723 -0
  314. attune/workflows/progressive/reports.py +520 -0
  315. attune/workflows/progressive/telemetry.py +274 -0
  316. attune/workflows/progressive/test_gen.py +495 -0
  317. attune/workflows/progressive/workflow.py +589 -0
  318. attune/workflows/refactor_plan.py +694 -0
  319. attune/workflows/release_prep.py +895 -0
  320. attune/workflows/release_prep_crew.py +969 -0
  321. attune/workflows/research_synthesis.py +404 -0
  322. attune/workflows/routing.py +168 -0
  323. attune/workflows/secure_release.py +593 -0
  324. attune/workflows/security_adapters.py +297 -0
  325. attune/workflows/security_audit.py +1329 -0
  326. attune/workflows/security_audit_phase3.py +355 -0
  327. attune/workflows/seo_optimization.py +633 -0
  328. attune/workflows/step_config.py +234 -0
  329. attune/workflows/telemetry_mixin.py +269 -0
  330. attune/workflows/test5.py +125 -0
  331. attune/workflows/test5_README.md +158 -0
  332. attune/workflows/test_coverage_boost_crew.py +849 -0
  333. attune/workflows/test_gen/__init__.py +52 -0
  334. attune/workflows/test_gen/ast_analyzer.py +249 -0
  335. attune/workflows/test_gen/config.py +88 -0
  336. attune/workflows/test_gen/data_models.py +38 -0
  337. attune/workflows/test_gen/report_formatter.py +289 -0
  338. attune/workflows/test_gen/test_templates.py +381 -0
  339. attune/workflows/test_gen/workflow.py +655 -0
  340. attune/workflows/test_gen.py +54 -0
  341. attune/workflows/test_gen_behavioral.py +477 -0
  342. attune/workflows/test_gen_parallel.py +341 -0
  343. attune/workflows/test_lifecycle.py +526 -0
  344. attune/workflows/test_maintenance.py +627 -0
  345. attune/workflows/test_maintenance_cli.py +590 -0
  346. attune/workflows/test_maintenance_crew.py +840 -0
  347. attune/workflows/test_runner.py +622 -0
  348. attune/workflows/tier_tracking.py +531 -0
  349. attune/workflows/xml_enhanced_crew.py +285 -0
  350. attune_ai-2.0.0.dist-info/METADATA +1026 -0
  351. attune_ai-2.0.0.dist-info/RECORD +457 -0
  352. attune_ai-2.0.0.dist-info/WHEEL +5 -0
  353. attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
  354. attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
  355. attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
  356. attune_ai-2.0.0.dist-info/top_level.txt +5 -0
  357. attune_healthcare/__init__.py +13 -0
  358. attune_healthcare/monitors/__init__.py +9 -0
  359. attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
  360. attune_healthcare/monitors/monitoring/__init__.py +44 -0
  361. attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
  362. attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
  363. attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
  364. attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
  365. attune_llm/README.md +553 -0
  366. attune_llm/__init__.py +28 -0
  367. attune_llm/agent_factory/__init__.py +53 -0
  368. attune_llm/agent_factory/adapters/__init__.py +85 -0
  369. attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
  370. attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
  371. attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
  372. attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
  373. attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
  374. attune_llm/agent_factory/adapters/native.py +228 -0
  375. attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
  376. attune_llm/agent_factory/base.py +305 -0
  377. attune_llm/agent_factory/crews/__init__.py +67 -0
  378. attune_llm/agent_factory/crews/code_review.py +1113 -0
  379. attune_llm/agent_factory/crews/health_check.py +1262 -0
  380. attune_llm/agent_factory/crews/refactoring.py +1128 -0
  381. attune_llm/agent_factory/crews/security_audit.py +1018 -0
  382. attune_llm/agent_factory/decorators.py +287 -0
  383. attune_llm/agent_factory/factory.py +558 -0
  384. attune_llm/agent_factory/framework.py +193 -0
  385. attune_llm/agent_factory/memory_integration.py +328 -0
  386. attune_llm/agent_factory/resilient.py +320 -0
  387. attune_llm/agents_md/__init__.py +22 -0
  388. attune_llm/agents_md/loader.py +218 -0
  389. attune_llm/agents_md/parser.py +271 -0
  390. attune_llm/agents_md/registry.py +307 -0
  391. attune_llm/claude_memory.py +466 -0
  392. attune_llm/cli/__init__.py +8 -0
  393. attune_llm/cli/sync_claude.py +487 -0
  394. attune_llm/code_health.py +1313 -0
  395. attune_llm/commands/__init__.py +51 -0
  396. attune_llm/commands/context.py +375 -0
  397. attune_llm/commands/loader.py +301 -0
  398. attune_llm/commands/models.py +231 -0
  399. attune_llm/commands/parser.py +371 -0
  400. attune_llm/commands/registry.py +429 -0
  401. attune_llm/config/__init__.py +29 -0
  402. attune_llm/config/unified.py +291 -0
  403. attune_llm/context/__init__.py +22 -0
  404. attune_llm/context/compaction.py +455 -0
  405. attune_llm/context/manager.py +434 -0
  406. attune_llm/contextual_patterns.py +361 -0
  407. attune_llm/core.py +907 -0
  408. attune_llm/git_pattern_extractor.py +435 -0
  409. attune_llm/hooks/__init__.py +24 -0
  410. attune_llm/hooks/config.py +306 -0
  411. attune_llm/hooks/executor.py +289 -0
  412. attune_llm/hooks/registry.py +302 -0
  413. attune_llm/hooks/scripts/__init__.py +39 -0
  414. attune_llm/hooks/scripts/evaluate_session.py +201 -0
  415. attune_llm/hooks/scripts/first_time_init.py +285 -0
  416. attune_llm/hooks/scripts/pre_compact.py +207 -0
  417. attune_llm/hooks/scripts/session_end.py +183 -0
  418. attune_llm/hooks/scripts/session_start.py +163 -0
  419. attune_llm/hooks/scripts/suggest_compact.py +225 -0
  420. attune_llm/learning/__init__.py +30 -0
  421. attune_llm/learning/evaluator.py +438 -0
  422. attune_llm/learning/extractor.py +514 -0
  423. attune_llm/learning/storage.py +560 -0
  424. attune_llm/levels.py +227 -0
  425. attune_llm/pattern_confidence.py +414 -0
  426. attune_llm/pattern_resolver.py +272 -0
  427. attune_llm/pattern_summary.py +350 -0
  428. attune_llm/providers.py +967 -0
  429. attune_llm/routing/__init__.py +32 -0
  430. attune_llm/routing/model_router.py +362 -0
  431. attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
  432. attune_llm/security/PHASE2_COMPLETE.md +384 -0
  433. attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
  434. attune_llm/security/QUICK_REFERENCE.md +316 -0
  435. attune_llm/security/README.md +262 -0
  436. attune_llm/security/__init__.py +62 -0
  437. attune_llm/security/audit_logger.py +929 -0
  438. attune_llm/security/audit_logger_example.py +152 -0
  439. attune_llm/security/pii_scrubber.py +640 -0
  440. attune_llm/security/secrets_detector.py +678 -0
  441. attune_llm/security/secrets_detector_example.py +304 -0
  442. attune_llm/security/secure_memdocs.py +1192 -0
  443. attune_llm/security/secure_memdocs_example.py +278 -0
  444. attune_llm/session_status.py +745 -0
  445. attune_llm/state.py +246 -0
  446. attune_llm/utils/__init__.py +5 -0
  447. attune_llm/utils/tokens.py +349 -0
  448. attune_software/SOFTWARE_PLUGIN_README.md +57 -0
  449. attune_software/__init__.py +13 -0
  450. attune_software/cli/__init__.py +120 -0
  451. attune_software/cli/inspect.py +362 -0
  452. attune_software/cli.py +574 -0
  453. attune_software/plugin.py +188 -0
  454. workflow_scaffolding/__init__.py +11 -0
  455. workflow_scaffolding/__main__.py +12 -0
  456. workflow_scaffolding/cli.py +206 -0
  457. workflow_scaffolding/generator.py +265 -0
attune/socratic/cli.py ADDED
@@ -0,0 +1,703 @@
1
+ """CLI for Socratic Workflow Builder
2
+
3
+ Provides command-line interface for:
4
+ - Starting interactive Socratic sessions
5
+ - Listing and resuming sessions
6
+ - Generating workflows from sessions
7
+ - Managing blueprints
8
+
9
+ Commands:
10
+ empathy socratic start [--goal "..."]
11
+ empathy socratic resume <session_id>
12
+ empathy socratic list [--state completed|in_progress]
13
+ empathy socratic generate <session_id>
14
+ empathy socratic blueprints [--domain security]
15
+ empathy socratic run <blueprint_id> [--input file.json]
16
+
17
+ Copyright 2026 Smart-AI-Memory
18
+ Licensed under Fair Source License 0.9
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import json
25
+ import sys
26
+ from typing import Any
27
+
28
+ from .engine import SocraticWorkflowBuilder
29
+ from .forms import FieldType, Form, FormField
30
+ from .session import SessionState
31
+ from .storage import get_default_storage
32
+
33
+ # =============================================================================
34
+ # CONSOLE FORMATTING
35
+ # =============================================================================
36
+
37
+
38
+ class Console:
39
+ """Simple console output formatting."""
40
+
41
+ COLORS = {
42
+ "reset": "\033[0m",
43
+ "bold": "\033[1m",
44
+ "dim": "\033[2m",
45
+ "red": "\033[91m",
46
+ "green": "\033[92m",
47
+ "yellow": "\033[93m",
48
+ "blue": "\033[94m",
49
+ "magenta": "\033[95m",
50
+ "cyan": "\033[96m",
51
+ }
52
+
53
+ def __init__(self, use_color: bool = True):
54
+ self.use_color = use_color and sys.stdout.isatty()
55
+
56
+ def _c(self, color: str, text: str) -> str:
57
+ """Apply color to text."""
58
+ if not self.use_color:
59
+ return text
60
+ return f"{self.COLORS.get(color, '')}{text}{self.COLORS['reset']}"
61
+
62
+ def header(self, text: str) -> None:
63
+ """Print a header."""
64
+ print()
65
+ print(self._c("bold", "=" * 60))
66
+ print(self._c("bold", f" {text}"))
67
+ print(self._c("bold", "=" * 60))
68
+ print()
69
+
70
+ def subheader(self, text: str) -> None:
71
+ """Print a subheader."""
72
+ print()
73
+ print(self._c("cyan", f"── {text} ──"))
74
+ print()
75
+
76
+ def success(self, text: str) -> None:
77
+ """Print success message."""
78
+ print(self._c("green", f"✓ {text}"))
79
+
80
+ def error(self, text: str) -> None:
81
+ """Print error message."""
82
+ print(self._c("red", f"✗ {text}"))
83
+
84
+ def warning(self, text: str) -> None:
85
+ """Print warning message."""
86
+ print(self._c("yellow", f"⚠ {text}"))
87
+
88
+ def info(self, text: str) -> None:
89
+ """Print info message."""
90
+ print(self._c("blue", f"ℹ {text}"))
91
+
92
+ def dim(self, text: str) -> None:
93
+ """Print dimmed text."""
94
+ print(self._c("dim", text))
95
+
96
+ def progress(self, value: float, width: int = 30) -> str:
97
+ """Generate progress bar."""
98
+ filled = int(value * width)
99
+ bar = "▓" * filled + "░" * (width - filled)
100
+ return f"[{bar}] {value:.0%}"
101
+
102
+ def table(self, headers: list[str], rows: list[list[str]]) -> None:
103
+ """Print a simple table."""
104
+ # Calculate column widths
105
+ widths = [len(h) for h in headers]
106
+ for row in rows:
107
+ for i, cell in enumerate(row):
108
+ if i < len(widths):
109
+ widths[i] = max(widths[i], len(str(cell)))
110
+
111
+ # Print header
112
+ header_line = " | ".join(self._c("bold", h.ljust(widths[i])) for i, h in enumerate(headers))
113
+ print(header_line)
114
+ print("-" * (sum(widths) + len(widths) * 3 - 1))
115
+
116
+ # Print rows
117
+ for row in rows:
118
+ row_line = " | ".join(str(cell).ljust(widths[i]) for i, cell in enumerate(row))
119
+ print(row_line)
120
+
121
+
122
+ console = Console()
123
+
124
+
125
+ # =============================================================================
126
+ # INTERACTIVE FORM RENDERER
127
+ # =============================================================================
128
+
129
+
130
+ def render_form_interactive(form: Form, console: Console) -> dict[str, Any]:
131
+ """Render a form and collect user input.
132
+
133
+ Args:
134
+ form: Form to render
135
+ console: Console for output
136
+
137
+ Returns:
138
+ Dictionary of answers
139
+ """
140
+ console.subheader(form.title)
141
+
142
+ if form.description:
143
+ print(form.description)
144
+ print()
145
+
146
+ print(console.progress(form.progress))
147
+ print()
148
+
149
+ answers: dict[str, Any] = {}
150
+
151
+ for field in form.fields:
152
+ # Check visibility
153
+ if not field.should_show(answers):
154
+ continue
155
+
156
+ # Render field
157
+ required = " *" if field.validation.required else ""
158
+ print(f"{console._c('bold', field.label)}{required}")
159
+
160
+ if field.help_text:
161
+ console.dim(f" {field.help_text}")
162
+
163
+ # Handle by field type
164
+ if field.field_type == FieldType.SINGLE_SELECT:
165
+ answers[field.id] = _input_single_select(field, console)
166
+
167
+ elif field.field_type == FieldType.MULTI_SELECT:
168
+ answers[field.id] = _input_multi_select(field, console)
169
+
170
+ elif field.field_type == FieldType.BOOLEAN:
171
+ answers[field.id] = _input_boolean(field, console)
172
+
173
+ elif field.field_type == FieldType.TEXT_AREA:
174
+ answers[field.id] = _input_text_area(field, console)
175
+
176
+ else: # TEXT, NUMBER, etc.
177
+ answers[field.id] = _input_text(field, console)
178
+
179
+ print()
180
+
181
+ return answers
182
+
183
+
184
+ def _input_single_select(field: FormField, console: Console) -> str | None:
185
+ """Input for single select field."""
186
+ for i, opt in enumerate(field.options, 1):
187
+ rec = console._c("green", " (Recommended)") if opt.recommended else ""
188
+ print(f" {i}. {opt.label}{rec}")
189
+ if opt.description:
190
+ console.dim(f" {opt.description}")
191
+
192
+ while True:
193
+ response = input("\n Enter number: ").strip()
194
+
195
+ if not response:
196
+ if not field.validation.required:
197
+ return None
198
+ console.error("This field is required")
199
+ continue
200
+
201
+ try:
202
+ idx = int(response) - 1
203
+ if 0 <= idx < len(field.options):
204
+ return field.options[idx].value
205
+ console.error(f"Enter a number between 1 and {len(field.options)}")
206
+ except ValueError:
207
+ console.error("Enter a valid number")
208
+
209
+
210
+ def _input_multi_select(field: FormField, console: Console) -> list[str]:
211
+ """Input for multi select field."""
212
+ for i, opt in enumerate(field.options, 1):
213
+ rec = console._c("green", " (Recommended)") if opt.recommended else ""
214
+ print(f" {i}. {opt.label}{rec}")
215
+ if opt.description:
216
+ console.dim(f" {opt.description}")
217
+
218
+ while True:
219
+ response = input("\n Enter numbers (comma-separated): ").strip()
220
+
221
+ if not response:
222
+ if not field.validation.required:
223
+ return []
224
+ console.error("Select at least one option")
225
+ continue
226
+
227
+ try:
228
+ indices = [int(x.strip()) - 1 for x in response.split(",")]
229
+ selected = []
230
+ for idx in indices:
231
+ if 0 <= idx < len(field.options):
232
+ selected.append(field.options[idx].value)
233
+
234
+ if selected:
235
+ return selected
236
+ console.error("No valid options selected")
237
+ except ValueError:
238
+ console.error("Enter valid numbers separated by commas")
239
+
240
+
241
+ def _input_boolean(field: FormField, console: Console) -> bool:
242
+ """Input for boolean field."""
243
+ while True:
244
+ response = input(" (y/n): ").strip().lower()
245
+
246
+ if response in ("y", "yes", "true", "1"):
247
+ return True
248
+ elif response in ("n", "no", "false", "0"):
249
+ return False
250
+ elif not response and not field.validation.required:
251
+ return False
252
+ else:
253
+ console.error("Enter 'y' or 'n'")
254
+
255
+
256
+ def _input_text(field: FormField, console: Console) -> str:
257
+ """Input for text field."""
258
+ prompt = f" {field.placeholder or 'Enter value'}: " if field.placeholder else " > "
259
+
260
+ while True:
261
+ response = input(prompt).strip()
262
+
263
+ if not response:
264
+ if not field.validation.required:
265
+ return ""
266
+ console.error("This field is required")
267
+ continue
268
+
269
+ # Validate
270
+ is_valid, error = field.validate(response)
271
+ if is_valid:
272
+ return response
273
+ console.error(error)
274
+
275
+
276
+ def _input_text_area(field: FormField, console: Console) -> str:
277
+ """Input for text area field."""
278
+ print(" (Enter text, then press Enter twice to finish)")
279
+
280
+ lines = []
281
+ empty_count = 0
282
+
283
+ while True:
284
+ line = input(" > " if not lines else " ")
285
+
286
+ if not line:
287
+ empty_count += 1
288
+ if empty_count >= 2:
289
+ break
290
+ lines.append("")
291
+ else:
292
+ empty_count = 0
293
+ lines.append(line)
294
+
295
+ response = "\n".join(lines).strip()
296
+
297
+ if not response and field.validation.required:
298
+ console.error("This field is required")
299
+ return _input_text_area(field, console)
300
+
301
+ return response
302
+
303
+
304
+ # =============================================================================
305
+ # CLI COMMANDS
306
+ # =============================================================================
307
+
308
+
309
+ def cmd_start(args: argparse.Namespace) -> int:
310
+ """Start a new Socratic session."""
311
+ storage = get_default_storage()
312
+ builder = SocraticWorkflowBuilder()
313
+
314
+ console.header("SOCRATIC WORKFLOW BUILDER")
315
+
316
+ # Start session
317
+ session = builder.start_session()
318
+ console.info(f"Session ID: {session.session_id[:8]}...")
319
+
320
+ # Get initial goal
321
+ if args.goal:
322
+ goal = args.goal
323
+ console.info(f"Goal: {goal}")
324
+ else:
325
+ initial_form = builder.get_initial_form()
326
+ answers = render_form_interactive(initial_form, console)
327
+ goal = answers.get("goal", "")
328
+
329
+ if not goal:
330
+ console.error("No goal provided")
331
+ return 1
332
+
333
+ # Set goal and analyze
334
+ session = builder.set_goal(session, goal)
335
+ storage.save_session(session)
336
+
337
+ # Show analysis
338
+ if session.goal_analysis:
339
+ console.subheader("Goal Analysis")
340
+ print(f" Domain: {session.goal_analysis.domain}")
341
+ print(f" Confidence: {session.goal_analysis.confidence:.0%}")
342
+
343
+ if session.goal_analysis.ambiguities:
344
+ print()
345
+ console.warning("Ambiguities detected:")
346
+ for amb in session.goal_analysis.ambiguities:
347
+ print(f" • {amb}")
348
+
349
+ # Interactive questioning loop
350
+ while not builder.is_ready_to_generate(session):
351
+ form = builder.get_next_questions(session)
352
+ if not form:
353
+ break
354
+
355
+ answers = render_form_interactive(form, console)
356
+ session = builder.submit_answers(session, answers)
357
+ storage.save_session(session)
358
+
359
+ # Show progress
360
+ summary = builder.get_session_summary(session)
361
+ console.info(f"Requirements completeness: {summary['requirements_completeness']:.0%}")
362
+
363
+ # Generate workflow
364
+ if builder.is_ready_to_generate(session):
365
+ console.subheader("Generating Workflow")
366
+
367
+ workflow = builder.generate_workflow(session)
368
+ storage.save_session(session)
369
+
370
+ if session.blueprint:
371
+ storage.save_blueprint(session.blueprint)
372
+
373
+ console.success("Workflow generated!")
374
+ print()
375
+ print(workflow.describe())
376
+
377
+ if session.blueprint:
378
+ console.info(f"Blueprint ID: {session.blueprint.id[:8]}...")
379
+
380
+ return 0
381
+
382
+
383
+ def cmd_resume(args: argparse.Namespace) -> int:
384
+ """Resume an existing session."""
385
+ storage = get_default_storage()
386
+ builder = SocraticWorkflowBuilder()
387
+
388
+ # Find session
389
+ session = storage.load_session(args.session_id)
390
+
391
+ # Try partial match
392
+ if not session:
393
+ sessions = storage.list_sessions()
394
+ matches = [s for s in sessions if s["session_id"].startswith(args.session_id)]
395
+ if len(matches) == 1:
396
+ session = storage.load_session(matches[0]["session_id"])
397
+ elif len(matches) > 1:
398
+ console.error(f"Multiple sessions match '{args.session_id}':")
399
+ for m in matches:
400
+ print(f" - {m['session_id'][:8]}... ({m['state']})")
401
+ return 1
402
+
403
+ if not session:
404
+ console.error(f"Session not found: {args.session_id}")
405
+ return 1
406
+
407
+ console.header(f"RESUMING SESSION {session.session_id[:8]}...")
408
+ console.info(f"Goal: {session.goal[:80]}...")
409
+ console.info(f"State: {session.state.value}")
410
+
411
+ # Rebuild builder state
412
+ if session.goal:
413
+ builder._sessions[session.session_id] = session
414
+
415
+ # Continue questioning or generate
416
+ if session.state == SessionState.COMPLETED:
417
+ console.success("Session already completed")
418
+ return 0
419
+
420
+ while not builder.is_ready_to_generate(session):
421
+ form = builder.get_next_questions(session)
422
+ if not form:
423
+ break
424
+
425
+ answers = render_form_interactive(form, console)
426
+ session = builder.submit_answers(session, answers)
427
+ storage.save_session(session)
428
+
429
+ if builder.is_ready_to_generate(session):
430
+ console.subheader("Generating Workflow")
431
+
432
+ workflow = builder.generate_workflow(session)
433
+ storage.save_session(session)
434
+
435
+ if session.blueprint:
436
+ storage.save_blueprint(session.blueprint)
437
+
438
+ console.success("Workflow generated!")
439
+ print()
440
+ print(workflow.describe())
441
+
442
+ return 0
443
+
444
+
445
+ def cmd_list(args: argparse.Namespace) -> int:
446
+ """List sessions."""
447
+ storage = get_default_storage()
448
+
449
+ state = None
450
+ if args.state:
451
+ try:
452
+ state = SessionState(args.state)
453
+ except ValueError:
454
+ console.error(f"Invalid state: {args.state}")
455
+ return 1
456
+
457
+ sessions = storage.list_sessions(state=state, limit=args.limit)
458
+
459
+ if not sessions:
460
+ console.info("No sessions found")
461
+ return 0
462
+
463
+ console.header("SOCRATIC SESSIONS")
464
+
465
+ headers = ["ID", "State", "Goal", "Updated"]
466
+ rows = []
467
+ for s in sessions:
468
+ rows.append(
469
+ [
470
+ s["session_id"][:8],
471
+ s["state"],
472
+ (
473
+ (s.get("goal") or "")[:40] + "..."
474
+ if len(s.get("goal") or "") > 40
475
+ else s.get("goal") or ""
476
+ ),
477
+ s.get("updated_at", "")[:16],
478
+ ]
479
+ )
480
+
481
+ console.table(headers, rows)
482
+ return 0
483
+
484
+
485
+ def cmd_blueprints(args: argparse.Namespace) -> int:
486
+ """List blueprints."""
487
+ storage = get_default_storage()
488
+
489
+ blueprints = storage.list_blueprints(domain=args.domain, limit=args.limit)
490
+
491
+ if not blueprints:
492
+ console.info("No blueprints found")
493
+ return 0
494
+
495
+ console.header("WORKFLOW BLUEPRINTS")
496
+
497
+ headers = ["ID", "Name", "Domain", "Agents", "Generated"]
498
+ rows = []
499
+ for b in blueprints:
500
+ rows.append(
501
+ [
502
+ b["id"][:8] if b.get("id") else "?",
503
+ b.get("name", "")[:30],
504
+ b.get("domain", ""),
505
+ str(b.get("agents_count", 0)),
506
+ (b.get("generated_at") or "")[:16],
507
+ ]
508
+ )
509
+
510
+ console.table(headers, rows)
511
+ return 0
512
+
513
+
514
+ def cmd_show(args: argparse.Namespace) -> int:
515
+ """Show details of a session or blueprint."""
516
+ storage = get_default_storage()
517
+
518
+ # Try as session first
519
+ session = storage.load_session(args.id)
520
+ if session:
521
+ console.header(f"SESSION: {session.session_id[:8]}...")
522
+
523
+ print(f"State: {session.state.value}")
524
+ print(f"Goal: {session.goal}")
525
+ print(f"Created: {session.created_at}")
526
+ print(f"Updated: {session.updated_at}")
527
+
528
+ if session.goal_analysis:
529
+ console.subheader("Analysis")
530
+ print(f"Domain: {session.goal_analysis.domain}")
531
+ print(f"Confidence: {session.goal_analysis.confidence:.0%}")
532
+ print(f"Intent: {session.goal_analysis.intent}")
533
+
534
+ if session.requirements.must_have:
535
+ console.subheader("Requirements")
536
+ for req in session.requirements.must_have:
537
+ print(f" • {req}")
538
+
539
+ return 0
540
+
541
+ # Try as blueprint
542
+ blueprint = storage.load_blueprint(args.id)
543
+ if blueprint:
544
+ console.header(f"BLUEPRINT: {blueprint.name}")
545
+
546
+ print(f"ID: {blueprint.id}")
547
+ print(f"Domain: {blueprint.domain}")
548
+ print(f"Languages: {', '.join(blueprint.supported_languages)}")
549
+ print(f"Quality Focus: {', '.join(blueprint.quality_focus)}")
550
+
551
+ console.subheader("Agents")
552
+ for agent in blueprint.agents:
553
+ print(f" • {agent.spec.name} ({agent.spec.role.value})")
554
+ print(f" Goal: {agent.spec.goal[:60]}...")
555
+
556
+ console.subheader("Stages")
557
+ for stage in blueprint.stages:
558
+ parallel = "(parallel)" if stage.parallel else "(sequential)"
559
+ print(f" • {stage.name} {parallel}")
560
+ print(f" Agents: {', '.join(stage.agent_ids)}")
561
+
562
+ return 0
563
+
564
+ console.error(f"Not found: {args.id}")
565
+ return 1
566
+
567
+
568
+ def cmd_delete(args: argparse.Namespace) -> int:
569
+ """Delete a session."""
570
+ storage = get_default_storage()
571
+
572
+ if not args.force:
573
+ response = input(f"Delete session {args.session_id}? (y/N): ").strip().lower()
574
+ if response != "y":
575
+ console.info("Cancelled")
576
+ return 0
577
+
578
+ if storage.delete_session(args.session_id):
579
+ console.success(f"Deleted session {args.session_id}")
580
+ return 0
581
+ else:
582
+ console.error(f"Session not found: {args.session_id}")
583
+ return 1
584
+
585
+
586
+ def cmd_export(args: argparse.Namespace) -> int:
587
+ """Export a blueprint to JSON."""
588
+ storage = get_default_storage()
589
+
590
+ blueprint = storage.load_blueprint(args.blueprint_id)
591
+ if not blueprint:
592
+ console.error(f"Blueprint not found: {args.blueprint_id}")
593
+ return 1
594
+
595
+ data = blueprint.to_dict()
596
+
597
+ if args.output:
598
+ with open(args.output, "w") as f:
599
+ json.dump(data, f, indent=2, default=str)
600
+ console.success(f"Exported to {args.output}")
601
+ else:
602
+ print(json.dumps(data, indent=2, default=str))
603
+
604
+ return 0
605
+
606
+
607
+ # =============================================================================
608
+ # MAIN ENTRY POINT
609
+ # =============================================================================
610
+
611
+
612
+ def create_parser() -> argparse.ArgumentParser:
613
+ """Create the argument parser."""
614
+ parser = argparse.ArgumentParser(
615
+ prog="empathy socratic",
616
+ description="Socratic Workflow Builder - Generate agent workflows through guided questioning",
617
+ )
618
+
619
+ subparsers = parser.add_subparsers(dest="command", help="Commands")
620
+
621
+ # start
622
+ start_parser = subparsers.add_parser("start", help="Start a new Socratic session")
623
+ start_parser.add_argument("--goal", "-g", help="Initial goal (skip first question)")
624
+ start_parser.add_argument("--non-interactive", action="store_true", help="Non-interactive mode")
625
+
626
+ # resume
627
+ resume_parser = subparsers.add_parser("resume", help="Resume an existing session")
628
+ resume_parser.add_argument("session_id", help="Session ID (can be partial)")
629
+
630
+ # list
631
+ list_parser = subparsers.add_parser("list", help="List sessions")
632
+ list_parser.add_argument(
633
+ "--state",
634
+ "-s",
635
+ choices=[
636
+ "awaiting_goal",
637
+ "awaiting_answers",
638
+ "ready_to_generate",
639
+ "completed",
640
+ "cancelled",
641
+ ],
642
+ )
643
+ list_parser.add_argument("--limit", "-n", type=int, default=20)
644
+
645
+ # blueprints
646
+ bp_parser = subparsers.add_parser("blueprints", help="List workflow blueprints")
647
+ bp_parser.add_argument("--domain", "-d", help="Filter by domain")
648
+ bp_parser.add_argument("--limit", "-n", type=int, default=20)
649
+
650
+ # show
651
+ show_parser = subparsers.add_parser("show", help="Show session or blueprint details")
652
+ show_parser.add_argument("id", help="Session or blueprint ID")
653
+
654
+ # delete
655
+ delete_parser = subparsers.add_parser("delete", help="Delete a session")
656
+ delete_parser.add_argument("session_id", help="Session ID")
657
+ delete_parser.add_argument("--force", "-f", action="store_true", help="Skip confirmation")
658
+
659
+ # export
660
+ export_parser = subparsers.add_parser("export", help="Export blueprint to JSON")
661
+ export_parser.add_argument("blueprint_id", help="Blueprint ID")
662
+ export_parser.add_argument("--output", "-o", help="Output file (default: stdout)")
663
+
664
+ return parser
665
+
666
+
667
+ def main(argv: list[str] | None = None) -> int:
668
+ """Main entry point."""
669
+ parser = create_parser()
670
+ args = parser.parse_args(argv)
671
+
672
+ if not args.command:
673
+ parser.print_help()
674
+ return 0
675
+
676
+ commands = {
677
+ "start": cmd_start,
678
+ "resume": cmd_resume,
679
+ "list": cmd_list,
680
+ "blueprints": cmd_blueprints,
681
+ "show": cmd_show,
682
+ "delete": cmd_delete,
683
+ "export": cmd_export,
684
+ }
685
+
686
+ cmd_func = commands.get(args.command)
687
+ if cmd_func:
688
+ try:
689
+ return cmd_func(args)
690
+ except KeyboardInterrupt:
691
+ print()
692
+ console.info("Interrupted")
693
+ return 130
694
+ except Exception as e:
695
+ console.error(f"Error: {e}")
696
+ return 1
697
+
698
+ parser.print_help()
699
+ return 0
700
+
701
+
702
+ if __name__ == "__main__":
703
+ sys.exit(main())