sycommon-python-lib 0.2.1a2__tar.gz → 0.2.1a4__tar.gz

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 (165) hide show
  1. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/PKG-INFO +1 -1
  2. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/pyproject.toml +1 -1
  3. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/sandbox/file_ops.py +13 -0
  4. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/sandbox/http_sandbox_backend.py +192 -2
  5. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/middleware/sandbox.py +84 -32
  6. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon_python_lib.egg-info/PKG-INFO +1 -1
  7. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/README.md +0 -0
  8. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/setup.cfg +0 -0
  9. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/command/__init__.py +0 -0
  10. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/command/cli.py +0 -0
  11. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/command/core/__init__.py +0 -0
  12. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/command/core/console.py +0 -0
  13. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/command/core/models.py +0 -0
  14. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/command/core/project.py +0 -0
  15. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/command/core/utils.py +0 -0
  16. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/command/templates/__init__.py +0 -0
  17. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/command/templates/agent/__init__.py +0 -0
  18. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/command/templates/base/__init__.py +0 -0
  19. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/command/templates/web/__init__.py +0 -0
  20. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/__init__.py +0 -0
  21. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/__init__.py +0 -0
  22. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/01_basic_agent.py +0 -0
  23. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/02_tool_agent.py +0 -0
  24. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/03_structured_output.py +0 -0
  25. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/04_memory_agent.py +0 -0
  26. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/05_streaming.py +0 -0
  27. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/06_multi_agent.py +0 -0
  28. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/07_skills_agent.py +0 -0
  29. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/08_middleware.py +0 -0
  30. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/09_interrupt.py +0 -0
  31. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/10_custom_llm.py +0 -0
  32. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/11_complex_workflow.py +0 -0
  33. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/12_batch_processing.py +0 -0
  34. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/__init__.py +0 -0
  35. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/middleware/01_basic_monitoring.py +0 -0
  36. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/middleware/02_permission_control.py +0 -0
  37. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/middleware/03_tool_skill_filter.py +0 -0
  38. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/middleware/04_caching_retry.py +0 -0
  39. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/middleware/05_sanitization.py +0 -0
  40. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/middleware/06_tracking.py +0 -0
  41. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/middleware/07_advanced.py +0 -0
  42. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/middleware/08_progressive_skills.py +0 -0
  43. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/middleware/__init__.py +0 -0
  44. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/middleware/override_examples.py +0 -0
  45. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/examples/virtual_employee_demo.py +0 -0
  46. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/exports.py +0 -0
  47. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/get_agent.py +0 -0
  48. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/sandbox/__init__.py +0 -0
  49. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/sandbox/sandbox_pool.py +0 -0
  50. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/sandbox/session.py +0 -0
  51. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/skills/__init__.py +0 -0
  52. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/skills/examples/faq_handler/scripts/search.py +0 -0
  53. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/skills/exports.py +0 -0
  54. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/agent/virtual_employee.py +0 -0
  55. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/config/Config.py +0 -0
  56. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/config/DatabaseConfig.py +0 -0
  57. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/config/ElasticsearchConfig.py +0 -0
  58. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/config/EmbeddingConfig.py +0 -0
  59. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/config/LLMConfig.py +0 -0
  60. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/config/LangfuseConfig.py +0 -0
  61. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/config/MQConfig.py +0 -0
  62. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/config/RedisConfig.py +0 -0
  63. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/config/RerankerConfig.py +0 -0
  64. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/config/SentryConfig.py +0 -0
  65. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/config/__init__.py +0 -0
  66. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/database/async_base_db_service.py +0 -0
  67. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/database/async_database_service.py +0 -0
  68. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/database/base_db_service.py +0 -0
  69. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/database/database_service.py +0 -0
  70. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/database/elasticsearch_service.py +0 -0
  71. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/database/redis_service.py +0 -0
  72. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/database/token_usage_db_service.py +0 -0
  73. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/health/__init__.py +0 -0
  74. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/health/health_check.py +0 -0
  75. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/health/metrics.py +0 -0
  76. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/health/ping.py +0 -0
  77. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/heartbeat_process/__init__.py +0 -0
  78. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/heartbeat_process/heartbeat_config.py +0 -0
  79. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/heartbeat_process/heartbeat_process_manager.py +0 -0
  80. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/heartbeat_process/heartbeat_process_worker.py +0 -0
  81. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/llm/__init__.py +0 -0
  82. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/llm/embedding.py +0 -0
  83. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/llm/get_llm.py +0 -0
  84. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/llm/llm_logger.py +0 -0
  85. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/llm/llm_tokens.py +0 -0
  86. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/llm/llm_with_token_tracking.py +0 -0
  87. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/llm/native_with_fallback_runnable.py +0 -0
  88. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/llm/output_fixing_runnable.py +0 -0
  89. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/llm/struct_token.py +0 -0
  90. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/llm/sy_langfuse.py +0 -0
  91. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/llm/token_usage_es_service.py +0 -0
  92. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/llm/token_usage_mysql_service.py +0 -0
  93. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/llm/usage_token.py +0 -0
  94. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/logging/__init__.py +0 -0
  95. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/logging/async_sql_logger.py +0 -0
  96. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/logging/kafka_log.py +0 -0
  97. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/logging/logger_levels.py +0 -0
  98. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/logging/logger_wrapper.py +0 -0
  99. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/logging/process_logger.py +0 -0
  100. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/logging/sql_logger.py +0 -0
  101. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/middleware/__init__.py +0 -0
  102. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/middleware/context.py +0 -0
  103. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/middleware/cors.py +0 -0
  104. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/middleware/docs.py +0 -0
  105. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/middleware/exception.py +0 -0
  106. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/middleware/middleware.py +0 -0
  107. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/middleware/monitor_memory.py +0 -0
  108. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/middleware/mq.py +0 -0
  109. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/middleware/timeout.py +0 -0
  110. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/middleware/traceid.py +0 -0
  111. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/models/__init__.py +0 -0
  112. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/models/base_http.py +0 -0
  113. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/models/log.py +0 -0
  114. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/models/mqlistener_config.py +0 -0
  115. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/models/mqmsg_model.py +0 -0
  116. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/models/mqsend_config.py +0 -0
  117. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/models/sso_user.py +0 -0
  118. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/models/token_usage.py +0 -0
  119. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/models/token_usage_mysql.py +0 -0
  120. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/notice/__init__.py +0 -0
  121. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/notice/uvicorn_monitor.py +0 -0
  122. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/rabbitmq/process_pool_consumer.py +0 -0
  123. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/rabbitmq/rabbitmq_client.py +0 -0
  124. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/rabbitmq/rabbitmq_pool.py +0 -0
  125. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/rabbitmq/rabbitmq_service.py +0 -0
  126. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/rabbitmq/rabbitmq_service_client_manager.py +0 -0
  127. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/rabbitmq/rabbitmq_service_connection_monitor.py +0 -0
  128. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/rabbitmq/rabbitmq_service_consumer_manager.py +0 -0
  129. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/rabbitmq/rabbitmq_service_core.py +0 -0
  130. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/rabbitmq/rabbitmq_service_producer_manager.py +0 -0
  131. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/sentry/__init__.py +0 -0
  132. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/sentry/sy_sentry.py +0 -0
  133. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/services.py +0 -0
  134. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/sse/__init__.py +0 -0
  135. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/sse/event.py +0 -0
  136. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/sse/sse.py +0 -0
  137. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/synacos/__init__.py +0 -0
  138. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/synacos/example.py +0 -0
  139. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/synacos/example2.py +0 -0
  140. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/synacos/feign.py +0 -0
  141. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/synacos/feign_client.py +0 -0
  142. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/synacos/nacos_client_base.py +0 -0
  143. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/synacos/nacos_config_manager.py +0 -0
  144. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/synacos/nacos_heartbeat_manager.py +0 -0
  145. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/synacos/nacos_service.py +0 -0
  146. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/synacos/nacos_service_discovery.py +0 -0
  147. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/synacos/nacos_service_registration.py +0 -0
  148. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/synacos/param.py +0 -0
  149. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/tests/deep_agent_server.py +0 -0
  150. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/tests/test_deep_agent.py +0 -0
  151. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/tests/test_email.py +0 -0
  152. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/tests/test_mq.py +0 -0
  153. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/tools/__init__.py +0 -0
  154. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/tools/async_utils.py +0 -0
  155. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/tools/docs.py +0 -0
  156. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/tools/env.py +0 -0
  157. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/tools/merge_headers.py +0 -0
  158. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/tools/snowflake.py +0 -0
  159. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/tools/syemail.py +0 -0
  160. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon/tools/timing.py +0 -0
  161. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon_python_lib.egg-info/SOURCES.txt +0 -0
  162. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon_python_lib.egg-info/dependency_links.txt +0 -0
  163. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon_python_lib.egg-info/entry_points.txt +0 -0
  164. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon_python_lib.egg-info/requires.txt +0 -0
  165. {sycommon_python_lib-0.2.1a2 → sycommon_python_lib-0.2.1a4}/src/sycommon_python_lib.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.2.1a2
3
+ Version: 0.2.1a4
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sycommon-python-lib"
3
- version = "0.2.1a2"
3
+ version = "0.2.1a4"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -27,15 +27,23 @@ class FileOperationsMixin:
27
27
 
28
28
  _timeout: int # 类型提示,子类需要提供此属性
29
29
  user_id: str # 类型提示,子类需要提供此属性
30
+ _auto_sync: bool = False # 是否自动同步
31
+ _synced: bool = False # 是否已同步
30
32
 
31
33
  async def _post_async(self: "HTTPSandboxBackend", endpoint: str, data: dict, timeout: int = None) -> dict:
32
34
  """发送异步 POST 请求"""
33
35
  ...
34
36
 
37
+ async def _ensure_synced(self: "HTTPSandboxBackend"):
38
+ """确保目录已同步(内部方法)"""
39
+ if self._auto_sync and not self._synced:
40
+ await self.async_sync()
41
+
35
42
  # ============== 目录操作 ==============
36
43
 
37
44
  async def als_info(self: "HTTPSandboxBackend", path: str) -> List[FileInfo]:
38
45
  """异步列出目录内容"""
46
+ await self._ensure_synced()
39
47
  SYLogger.info(f"[Sandbox] 列出目录: {path}")
40
48
  result = await self._post_async(f"{SANDBOX_API_PREFIX}/ls", {
41
49
  "path": path,
@@ -49,6 +57,7 @@ class FileOperationsMixin:
49
57
 
50
58
  async def aread(self: "HTTPSandboxBackend", file_path: str, offset: int = 0, limit: int = 2000) -> str:
51
59
  """异步读取文件内容"""
60
+ await self._ensure_synced()
52
61
  SYLogger.info(f"[Sandbox] 读取文件: {file_path} (offset={offset}, limit={limit})")
53
62
  result = await self._post_async(f"{SANDBOX_API_PREFIX}/read", {
54
63
  "file_path": file_path,
@@ -65,6 +74,7 @@ class FileOperationsMixin:
65
74
 
66
75
  async def awrite(self: "HTTPSandboxBackend", file_path: str, content: str) -> WriteResult:
67
76
  """异步写入文件"""
77
+ await self._ensure_synced()
68
78
  SYLogger.info(f"[Sandbox] 写入文件: {file_path} ({len(content)} 字符)")
69
79
  result = await self._post_async(f"{SANDBOX_API_PREFIX}/write", {
70
80
  "file_path": file_path,
@@ -89,6 +99,7 @@ class FileOperationsMixin:
89
99
  replace_all: bool = False
90
100
  ) -> EditResult:
91
101
  """异步编辑文件"""
102
+ await self._ensure_synced()
92
103
  result = await self._post_async(f"{SANDBOX_API_PREFIX}/edit", {
93
104
  "file_path": file_path,
94
105
  "old_string": base64.b64encode(old_string.encode()).decode(),
@@ -106,6 +117,7 @@ class FileOperationsMixin:
106
117
 
107
118
  async def aglob_info(self: "HTTPSandboxBackend", pattern: str, path: str = "/") -> List[FileInfo]:
108
119
  """异步 glob 搜索文件"""
120
+ await self._ensure_synced()
109
121
  result = await self._post_async(f"{SANDBOX_API_PREFIX}/glob", {
110
122
  "pattern": pattern,
111
123
  "path": path,
@@ -120,6 +132,7 @@ class FileOperationsMixin:
120
132
  glob: str = None
121
133
  ) -> List[GrepMatch] | str:
122
134
  """异步搜索文件内容"""
135
+ await self._ensure_synced()
123
136
  try:
124
137
  result = await self._post_async(f"{SANDBOX_API_PREFIX}/grep", {
125
138
  "pattern": pattern,
@@ -6,6 +6,8 @@ HTTP 客户端沙箱后端
6
6
  """
7
7
 
8
8
  import base64
9
+ import os
10
+ from pathlib import Path
9
11
  from typing import Optional, List
10
12
 
11
13
  from sycommon.logging.kafka_log import SYLogger
@@ -41,11 +43,29 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
41
43
  user_id="user123"
42
44
  )
43
45
 
46
+ # 带自动同步目录
47
+ backend = HTTPSandboxBackend(
48
+ base_url="http://localhost:8080",
49
+ user_id="user123",
50
+ sync_dirs=[
51
+ ("/local/path/skills", "/skills"),
52
+ ("/local/path/memory", "/memory"),
53
+ ]
54
+ )
55
+ await backend.async_sync() # 手动触发同步
56
+
44
57
  # 异步调用
45
58
  result = await backend.aexecute("python --version")
46
59
  """
47
60
 
48
- def __init__(self, base_url: str, user_id: str, timeout: int = 120):
61
+ def __init__(
62
+ self,
63
+ base_url: str,
64
+ user_id: str,
65
+ timeout: int = 120,
66
+ sync_dirs: List[tuple[str, str]] = None,
67
+ auto_sync: bool = False
68
+ ):
49
69
  """
50
70
  初始化 HTTP 沙箱后端
51
71
 
@@ -53,10 +73,15 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
53
73
  base_url: 沙箱服务地址,如 http://192.168.1.100:8080
54
74
  user_id: 用户ID,用于隔离工作目录
55
75
  timeout: 默认超时时间(秒)
76
+ sync_dirs: 要同步的目录列表 [(本地路径, 沙箱路径), ...]
77
+ auto_sync: 是否在首次文件操作时自动同步(默认 False)
56
78
  """
57
79
  self._base_url = base_url.rstrip("/")
58
80
  self._user_id = user_id
59
81
  self._timeout = timeout
82
+ self._sync_dirs = sync_dirs or []
83
+ self._auto_sync = auto_sync
84
+ self._synced = False
60
85
 
61
86
  # 生成 ID
62
87
  self._id = self._base_url.replace(
@@ -77,6 +102,8 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
77
102
  group: str = "DEFAULT_GROUP",
78
103
  version: str = None,
79
104
  timeout: int = 120,
105
+ sync_dirs: List[tuple[str, str]] = None,
106
+ auto_sync: bool = False,
80
107
  ) -> "HTTPSandboxBackend":
81
108
  """
82
109
  从 Nacos 服务发现创建后端实例
@@ -87,6 +114,8 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
87
114
  group: 服务分组
88
115
  version: 服务版本
89
116
  timeout: 超时时间
117
+ sync_dirs: 要同步的目录列表 [(本地路径, 沙箱路径), ...]
118
+ auto_sync: 是否在首次文件操作时自动同步
90
119
 
91
120
  Returns:
92
121
  HTTPSandboxBackend 实例
@@ -117,7 +146,13 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
117
146
 
118
147
  SYLogger.info(f"Nacos 发现沙箱服务: {service_name} -> {base_url}")
119
148
 
120
- return cls(base_url=base_url, user_id=user_id, timeout=timeout)
149
+ return cls(
150
+ base_url=base_url,
151
+ user_id=user_id,
152
+ timeout=timeout,
153
+ sync_dirs=sync_dirs,
154
+ auto_sync=auto_sync
155
+ )
121
156
 
122
157
  # ============== 内部方法 ==============
123
158
 
@@ -154,6 +189,7 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
154
189
 
155
190
  async def aexecute(self, command: str, *, timeout: int = None) -> ExecuteResponse:
156
191
  """异步执行 shell 命令"""
192
+ await self._ensure_synced()
157
193
  SYLogger.info(f"[Sandbox] 执行命令: {command}")
158
194
  result = await self._post_async(f"{SANDBOX_API_PREFIX}/execute", {
159
195
  "command": command,
@@ -188,6 +224,160 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
188
224
  results.append(FileUploadResponse(path=path, error=str(e)))
189
225
  return results
190
226
 
227
+ async def aupload_directory(
228
+ self,
229
+ local_dir: str,
230
+ remote_path: str = "/",
231
+ exclude_patterns: List[str] = None
232
+ ) -> dict:
233
+ """
234
+ 异步上传整个目录到沙箱
235
+
236
+ Args:
237
+ local_dir: 本地目录路径
238
+ remote_path: 沙箱中的目标路径(默认为根目录)
239
+ exclude_patterns: 要排除的文件模式列表(如 [".git", "__pycache__", "*.pyc"])
240
+
241
+ Returns:
242
+ dict: {"success": int, "failed": int, "errors": list}
243
+
244
+ Example:
245
+ await backend.aupload_directory(
246
+ local_dir="/path/to/project/skills",
247
+ remote_path="/skills",
248
+ exclude_patterns=[".git", "__pycache__"]
249
+ )
250
+ """
251
+ local_path = Path(local_dir)
252
+ if not local_path.exists():
253
+ raise FileNotFoundError(f"本地目录不存在: {local_dir}")
254
+
255
+ if not local_path.is_dir():
256
+ raise NotADirectoryError(f"路径不是目录: {local_dir}")
257
+
258
+ exclude_patterns = exclude_patterns or [".git", "__pycache__", ".pyc", ".DS_Store", "node_modules"]
259
+
260
+ def should_exclude(name: str) -> bool:
261
+ for pattern in exclude_patterns:
262
+ if pattern.startswith("*"):
263
+ if name.endswith(pattern[1:]):
264
+ return True
265
+ elif name == pattern:
266
+ return True
267
+ return False
268
+
269
+ files_to_upload = []
270
+
271
+ # 收集所有文件
272
+ for root, dirs, files in os.walk(local_path):
273
+ # 过滤排除的目录
274
+ dirs[:] = [d for d in dirs if not should_exclude(d)]
275
+
276
+ for file in files:
277
+ if should_exclude(file):
278
+ continue
279
+
280
+ local_file = Path(root) / file
281
+ # 计算相对路径
282
+ relative_path = local_file.relative_to(local_path)
283
+ # 构建沙箱中的目标路径
284
+ sandbox_path = str(Path(remote_path) / relative_path)
285
+
286
+ files_to_upload.append((sandbox_path, local_file))
287
+
288
+ SYLogger.info(f"[Sandbox] 准备上传 {len(files_to_upload)} 个文件到沙箱")
289
+
290
+ # 批量上传
291
+ success_count = 0
292
+ failed_count = 0
293
+ errors = []
294
+
295
+ for sandbox_path, local_file in files_to_upload:
296
+ try:
297
+ with open(local_file, "rb") as f:
298
+ content = f.read()
299
+
300
+ result = await self._post_async(f"{SANDBOX_API_PREFIX}/upload", {
301
+ "path": sandbox_path,
302
+ "content": base64.b64encode(content).decode(),
303
+ "user_id": self._user_id
304
+ })
305
+
306
+ if result.get("error"):
307
+ failed_count += 1
308
+ errors.append({"path": sandbox_path, "error": result["error"]})
309
+ else:
310
+ success_count += 1
311
+
312
+ except Exception as e:
313
+ failed_count += 1
314
+ errors.append({"path": sandbox_path, "error": str(e)})
315
+
316
+ SYLogger.info(f"[Sandbox] 目录上传完成: 成功 {success_count}, 失败 {failed_count}")
317
+
318
+ return {
319
+ "success": success_count,
320
+ "failed": failed_count,
321
+ "errors": errors
322
+ }
323
+
324
+ async def async_sync(self) -> dict:
325
+ """
326
+ 手动同步所有配置的目录到沙箱
327
+
328
+ Returns:
329
+ dict: 每个目录的同步结果 {"path": result}
330
+
331
+ Example:
332
+ backend = HTTPSandboxBackend(
333
+ base_url="http://localhost:8080",
334
+ user_id="user123",
335
+ sync_dirs=[
336
+ ("/local/skills", "/skills"),
337
+ ("/local/memory", "/memory"),
338
+ ]
339
+ )
340
+ result = await backend.async_sync()
341
+ """
342
+ if not self._sync_dirs:
343
+ SYLogger.info("[Sandbox] 没有配置同步目录")
344
+ return {}
345
+
346
+ results = {}
347
+ for local_path, remote_path in self._sync_dirs:
348
+ SYLogger.info(f"[Sandbox] 同步目录: {local_path} -> {remote_path}")
349
+ local_dir = Path(local_path)
350
+
351
+ if not local_dir.exists():
352
+ SYLogger.error(f"[Sandbox] 本地目录不存在: {local_path}")
353
+ results[local_path] = {"error": "目录不存在"}
354
+ continue
355
+
356
+ if local_dir.is_dir():
357
+ result = await self.aupload_directory(
358
+ local_dir=str(local_dir),
359
+ remote_path=remote_path
360
+ )
361
+ else:
362
+ # 单文件
363
+ with open(local_dir, "rb") as f:
364
+ content = f.read()
365
+ upload_results = await self.aupload_files([(remote_path, content)])
366
+ result = {"success": 1 if not upload_results[0].error else 0,
367
+ "failed": 1 if upload_results[0].error else 0,
368
+ "errors": [{"path": remote_path, "error": upload_results[0].error}] if upload_results[0].error else []}
369
+
370
+ results[local_path] = result
371
+
372
+ self._synced = True
373
+ SYLogger.info("[Sandbox] 目录同步完成")
374
+ return results
375
+
376
+ async def _ensure_synced(self):
377
+ """确保目录已同步(内部方法,用于 auto_sync)"""
378
+ if self._auto_sync and not self._synced:
379
+ await self.async_sync()
380
+
191
381
  async def adownload_files(self, paths: List[str]) -> List[FileDownloadResponse]:
192
382
  """异步下载多个文件"""
193
383
  results = []
@@ -3,6 +3,25 @@
3
3
 
4
4
  提供沙箱容器的中间件和 API 路由
5
5
  支持 user_id 隔离,每个用户独立工作目录
6
+
7
+ 目录结构:
8
+ {WORKSPACE_BASE}/ # 默认: /tmp/sycommon_sandbox
9
+ ├── user_001/ # 用户 test_user_001 隔离目录
10
+ │ ├── skills/ # 同步的 /skills
11
+ │ │ └── multi-search-engine/
12
+ │ │ ├── config.json
13
+ │ │ └── ...
14
+ │ └── memory/ # 同步的 /memory
15
+ │ └── AGENTS.md
16
+ └── user_002/ # 其他用户完全隔离
17
+ ├── skills/...
18
+ └── memory/...
19
+
20
+ 路径隔离:
21
+ 所有 API 端点使用统一路径解析 (resolve_sandbox_path):
22
+ - /skills/foo.md -> {workspace}/skills/foo.md
23
+ - skills/foo.md -> {workspace}/skills/foo.md
24
+ - 防止路径遍历攻击 (../../../etc/passwd)
6
25
  """
7
26
 
8
27
  import os
@@ -35,6 +54,33 @@ def get_user_workspace(user_id: str) -> str:
35
54
  return workspace
36
55
 
37
56
 
57
+ def resolve_sandbox_path(path: str, workspace: str) -> str:
58
+ """
59
+ 将沙箱路径解析到用户工作目录,实现路径隔离
60
+
61
+ 所有路径(包括绝对路径)都会映射到用户工作目录下:
62
+ - /skills/foo.md -> {workspace}/skills/foo.md
63
+ - skills/foo.md -> {workspace}/skills/foo.md
64
+
65
+ 同时防止路径遍历攻击,确保解析后的路径仍在工作目录内
66
+ """
67
+ # 移除开头的 / 使其成为相对路径
68
+ if os.path.isabs(path):
69
+ path = path.lstrip('/')
70
+
71
+ # 规范化路径,防止 ../ 等路径遍历
72
+ path = os.path.normpath(path)
73
+
74
+ # 拼接到工作目录
75
+ resolved = os.path.normpath(os.path.join(workspace, path))
76
+
77
+ # 安全检查:确保最终路径在工作目录内
78
+ if not resolved.startswith(workspace):
79
+ raise ValueError(f"Path traversal detected: {path}")
80
+
81
+ return resolved
82
+
83
+
38
84
  # ============== 请求/响应模型 ==============
39
85
 
40
86
  class ExecuteRequest(BaseModel):
@@ -288,10 +334,11 @@ def setup_sandbox_handler(app: FastAPI, config: dict = None):
288
334
  async def ls_info(req: LsRequest):
289
335
  """列出目录内容"""
290
336
  workspace = get_user_workspace(req.user_id)
291
- target_path = req.path
292
-
293
- if not os.path.isabs(target_path):
294
- target_path = os.path.join(workspace, target_path)
337
+ try:
338
+ target_path = resolve_sandbox_path(req.path, workspace)
339
+ except ValueError as e:
340
+ SYLogger.error(f"[Sandbox Server] 列目录失败: {e}")
341
+ return []
295
342
 
296
343
  if not os.path.isdir(target_path):
297
344
  SYLogger.error(f"[Sandbox Server] 列目录失败: 目录不存在 {target_path}")
@@ -321,10 +368,11 @@ def setup_sandbox_handler(app: FastAPI, config: dict = None):
321
368
  async def read_file(req: ReadRequest):
322
369
  """读取文件内容"""
323
370
  workspace = get_user_workspace(req.user_id)
324
- file_path = req.file_path
325
-
326
- if not os.path.isabs(file_path):
327
- file_path = os.path.join(workspace, file_path)
371
+ try:
372
+ file_path = resolve_sandbox_path(req.file_path, workspace)
373
+ except ValueError as e:
374
+ SYLogger.error(f"[Sandbox Server] 读取失败: {e}")
375
+ return ReadResponse(error=str(e), content=None)
328
376
 
329
377
  if not os.path.isfile(file_path):
330
378
  SYLogger.error(f"[Sandbox Server] 读取失败: 文件不存在 {file_path}")
@@ -360,10 +408,11 @@ def setup_sandbox_handler(app: FastAPI, config: dict = None):
360
408
  async def write_file(req: WriteRequest):
361
409
  """写入新文件"""
362
410
  workspace = get_user_workspace(req.user_id)
363
- file_path = req.file_path
364
-
365
- if not os.path.isabs(file_path):
366
- file_path = os.path.join(workspace, file_path)
411
+ try:
412
+ file_path = resolve_sandbox_path(req.file_path, workspace)
413
+ except ValueError as e:
414
+ SYLogger.error(f"[Sandbox Server] 写入失败: {e}")
415
+ return WriteResponse(error=str(e), path=None)
367
416
 
368
417
  SYLogger.info(f"[Sandbox Server] 写入文件: {file_path}")
369
418
 
@@ -392,10 +441,11 @@ def setup_sandbox_handler(app: FastAPI, config: dict = None):
392
441
  async def edit_file(req: EditRequest):
393
442
  """编辑文件"""
394
443
  workspace = get_user_workspace(req.user_id)
395
- file_path = req.file_path
396
-
397
- if not os.path.isabs(file_path):
398
- file_path = os.path.join(workspace, file_path)
444
+ try:
445
+ file_path = resolve_sandbox_path(req.file_path, workspace)
446
+ except ValueError as e:
447
+ SYLogger.error(f"[Sandbox Server] 编辑失败: {e}")
448
+ return EditResponse(error=str(e))
399
449
 
400
450
  SYLogger.info(f"[Sandbox Server] 编辑文件: {file_path}")
401
451
 
@@ -440,10 +490,10 @@ def setup_sandbox_handler(app: FastAPI, config: dict = None):
440
490
  async def upload_file(req: UploadRequest):
441
491
  """上传文件"""
442
492
  workspace = get_user_workspace(req.user_id)
443
- file_path = req.path
444
-
445
- if not os.path.isabs(file_path):
446
- file_path = os.path.join(workspace, file_path)
493
+ try:
494
+ file_path = resolve_sandbox_path(req.path, workspace)
495
+ except ValueError as e:
496
+ return UploadResponse(path=req.path, error=str(e))
447
497
 
448
498
  try:
449
499
  content = base64.b64decode(req.content)
@@ -464,10 +514,10 @@ def setup_sandbox_handler(app: FastAPI, config: dict = None):
464
514
  async def download_file(req: DownloadRequest):
465
515
  """下载文件"""
466
516
  workspace = get_user_workspace(req.user_id)
467
- file_path = req.path
468
-
469
- if not os.path.isabs(file_path):
470
- file_path = os.path.join(workspace, file_path)
517
+ try:
518
+ file_path = resolve_sandbox_path(req.path, workspace)
519
+ except ValueError as e:
520
+ return DownloadResponse(path=req.path, error=str(e))
471
521
 
472
522
  if not os.path.isfile(file_path):
473
523
  return DownloadResponse(path=file_path, error="file_not_found")
@@ -490,10 +540,11 @@ def setup_sandbox_handler(app: FastAPI, config: dict = None):
490
540
  import glob as glob_module
491
541
 
492
542
  workspace = get_user_workspace(req.user_id)
493
- search_path = req.path
494
-
495
- if not os.path.isabs(search_path):
496
- search_path = os.path.join(workspace, search_path)
543
+ try:
544
+ search_path = resolve_sandbox_path(req.path, workspace)
545
+ except ValueError as e:
546
+ SYLogger.error(f"[Sandbox Server] glob 失败: {e}")
547
+ return []
497
548
 
498
549
  full_pattern = os.path.join(search_path, req.pattern)
499
550
  matches = sorted(glob_module.glob(full_pattern, recursive=True))
@@ -517,10 +568,11 @@ def setup_sandbox_handler(app: FastAPI, config: dict = None):
517
568
  import shlex
518
569
 
519
570
  workspace = get_user_workspace(req.user_id)
520
- search_path = req.path or workspace
521
-
522
- if not os.path.isabs(search_path):
523
- search_path = os.path.join(workspace, search_path)
571
+ try:
572
+ search_path = resolve_sandbox_path(req.path or "/", workspace)
573
+ except ValueError as e:
574
+ SYLogger.error(f"[Sandbox Server] grep 失败: {e}")
575
+ return []
524
576
 
525
577
  quoted_path = shlex.quote(search_path)
526
578
  grep_opts = "-rHnF"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.2.1a2
3
+ Version: 0.2.1a4
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown