neurostack-org 2.2.0__tar.gz → 2.2.2__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 (170) hide show
  1. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/PKG-INFO +1 -1
  2. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/pyproject.toml +1 -1
  3. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack_org.egg-info/PKG-INFO +1 -1
  4. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/cli/main.py +366 -27
  5. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/README.md +0 -0
  6. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/setup.cfg +0 -0
  7. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/__init__.py +0 -0
  8. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/config/__init__.py +0 -0
  9. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/config/brain_config.py +0 -0
  10. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/config/confidence_config.py +0 -0
  11. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/config/persona_config.py +0 -0
  12. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/config/retrieval_config.py +0 -0
  13. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/core/__init__.py +0 -0
  14. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/core/input_validator.py +0 -0
  15. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/core/intent_classifier.py +0 -0
  16. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/core/knowledge_retriever.py +0 -0
  17. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/core/orchestrator.py +0 -0
  18. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/core/reasoning_engine.py +0 -0
  19. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/core/response_formatter.py +0 -0
  20. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/factory.py +0 -0
  21. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/interfaces/__init__.py +0 -0
  22. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/interfaces/embedding.py +0 -0
  23. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/interfaces/llm.py +0 -0
  24. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/interfaces/vector_store.py +0 -0
  25. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/logging/__init__.py +0 -0
  26. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/logging/formatters.py +0 -0
  27. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/logging/logger.py +0 -0
  28. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/logging/trace.py +0 -0
  29. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/models/__init__.py +0 -0
  30. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/models/confidence.py +0 -0
  31. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/models/intent.py +0 -0
  32. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/models/persona.py +0 -0
  33. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/models/request.py +0 -0
  34. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/models/response.py +0 -0
  35. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/models/retrieval.py +0 -0
  36. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/prompts/__init__.py +0 -0
  37. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/prompts/base.py +0 -0
  38. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/prompts/intent_classifier.py +0 -0
  39. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/prompts/reasoning/__init__.py +0 -0
  40. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/prompts/reasoning/action.py +0 -0
  41. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/prompts/reasoning/decision_recall.py +0 -0
  42. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/prompts/reasoning/informational.py +0 -0
  43. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/prompts/reasoning/metrics.py +0 -0
  44. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/prompts/reasoning/planning.py +0 -0
  45. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/prompts/reasoning/status.py +0 -0
  46. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/prompts/reasoning/summary.py +0 -0
  47. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/utils/__init__.py +0 -0
  48. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/utils/confidence.py +0 -0
  49. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/utils/ranking.py +0 -0
  50. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/utils/time.py +0 -0
  51. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/utils/validation.py +0 -0
  52. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/brain/utils/visibility.py +0 -0
  53. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/clients/__init__.py +0 -0
  54. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/clients/anthropic_client.py +0 -0
  55. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/clients/anthropic_llm.py +0 -0
  56. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/clients/azure_openai_llm.py +0 -0
  57. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/clients/example_vector_client.py +0 -0
  58. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/clients/mock_embedding.py +0 -0
  59. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/clients/mock_vector_store.py +0 -0
  60. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/clients/openai_compatible_llm.py +0 -0
  61. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/clients/openai_embedding.py +0 -0
  62. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/clients/retry.py +0 -0
  63. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack/integrations/__init__.py +0 -0
  64. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack/integrations/base.py +0 -0
  65. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack/integrations/slack/__init__.py +0 -0
  66. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack/integrations/slack/bot.py +0 -0
  67. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack/integrations/slack/commands.py +0 -0
  68. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack/integrations/slack/formatters.py +0 -0
  69. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack/integrations/telegram/__init__.py +0 -0
  70. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack/integrations/telegram/bot.py +0 -0
  71. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack/integrations/telegram/formatters.py +0 -0
  72. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack/license.py +0 -0
  73. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack/logging.py +0 -0
  74. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack/models/__init__.py +0 -0
  75. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack/models/context.py +0 -0
  76. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack/models/knowledge.py +0 -0
  77. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack/models/permission.py +0 -0
  78. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack_org.egg-info/SOURCES.txt +0 -0
  79. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack_org.egg-info/dependency_links.txt +0 -0
  80. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack_org.egg-info/entry_points.txt +0 -0
  81. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack_org.egg-info/requires.txt +0 -0
  82. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/neurostack_org.egg-info/top_level.txt +0 -0
  83. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/__init__.py +0 -0
  84. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/audit/__init__.py +0 -0
  85. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/audit/lineage.py +0 -0
  86. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/audit/logger.py +0 -0
  87. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/audit/metrics.py +0 -0
  88. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/auth/__init__.py +0 -0
  89. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/auth/oauth_manager.py +0 -0
  90. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/auth/permission_sync.py +0 -0
  91. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/auth/token_store.py +0 -0
  92. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/cli/__init__.py +0 -0
  93. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/cli/connector_manage.py +0 -0
  94. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/cli/ingest.py +0 -0
  95. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/cli/status.py +0 -0
  96. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/config/__init__.py +0 -0
  97. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/config/loader.py +0 -0
  98. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/config/pipeline_config.py +0 -0
  99. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/config/storage_config.py +0 -0
  100. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/__init__.py +0 -0
  101. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/base.py +0 -0
  102. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/csv/__init__.py +0 -0
  103. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/csv/connector.py +0 -0
  104. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/google_workspace/__init__.py +0 -0
  105. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/google_workspace/auth.py +0 -0
  106. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/google_workspace/calendar_connector.py +0 -0
  107. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/google_workspace/docs_connector.py +0 -0
  108. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/google_workspace/drive_connector.py +0 -0
  109. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/google_workspace/permission_mapper.py +0 -0
  110. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/jira/__init__.py +0 -0
  111. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/jira/auth.py +0 -0
  112. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/jira/config.py +0 -0
  113. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/jira/connector.py +0 -0
  114. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/jira/permission_mapper.py +0 -0
  115. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/registry.py +0 -0
  116. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/slack/__init__.py +0 -0
  117. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/slack/auth.py +0 -0
  118. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/slack/connector.py +0 -0
  119. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/slack/deduplication.py +0 -0
  120. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/connectors/slack/permission_mapper.py +0 -0
  121. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/core/__init__.py +0 -0
  122. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/core/chunker.py +0 -0
  123. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/core/ingestion_engine.py +0 -0
  124. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/core/live_data_handler.py +0 -0
  125. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/core/orchestrator.py +0 -0
  126. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/core/permission_engine.py +0 -0
  127. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/ingest/__init__.py +0 -0
  128. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/ingest/universal.py +0 -0
  129. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/models/__init__.py +0 -0
  130. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/models/connector_config.py +0 -0
  131. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/models/ingestion_job.py +0 -0
  132. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/models/storage.py +0 -0
  133. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/processing/__init__.py +0 -0
  134. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/processing/chunking/__init__.py +0 -0
  135. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/processing/chunking/document_chunker.py +0 -0
  136. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/processing/chunking/semantic_chunker.py +0 -0
  137. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/processing/chunking/structured_chunker.py +0 -0
  138. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/processing/embeddings/__init__.py +0 -0
  139. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/processing/embeddings/embedding_client.py +0 -0
  140. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/processing/embeddings/mock_embeddings.py +0 -0
  141. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/processing/embeddings/openai_embeddings.py +0 -0
  142. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/processing/normalizer.py +0 -0
  143. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/processing/versioning.py +0 -0
  144. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/processors/__init__.py +0 -0
  145. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/processors/audio_processor.py +0 -0
  146. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/processors/file_processor.py +0 -0
  147. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/processors/text_classifier.py +0 -0
  148. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/scheduling/__init__.py +0 -0
  149. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/scheduling/checkpoint.py +0 -0
  150. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/scheduling/job_queue.py +0 -0
  151. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/scheduling/rate_limiter.py +0 -0
  152. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/scheduling/scheduler.py +0 -0
  153. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/storage/__init__.py +0 -0
  154. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/storage/graph/__init__.py +0 -0
  155. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/storage/graph/neo4j_store.py +0 -0
  156. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/storage/interfaces.py +0 -0
  157. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/storage/metadata/__init__.py +0 -0
  158. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/storage/metadata/postgres.py +0 -0
  159. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/storage/migrations/env.py +0 -0
  160. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/storage/migrations/versions/f2816fb07a30_initial_schema.py +0 -0
  161. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/storage/semantic/__init__.py +0 -0
  162. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/storage/semantic/mock_store.py +0 -0
  163. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/storage/semantic/pinecone_store.py +0 -0
  164. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/storage/semantic/qdrant_store.py +0 -0
  165. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/storage/semantic/weaviate_store.py +0 -0
  166. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/utils/__init__.py +0 -0
  167. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/utils/deduplication.py +0 -0
  168. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/utils/exceptions.py +0 -0
  169. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/utils/retry.py +0 -0
  170. {neurostack_org-2.2.0 → neurostack_org-2.2.2}/src/pipeline/utils/validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: neurostack-org
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: Enterprise AI SDK — permission-aware organizational knowledge retrieval with 9 connectors, 5-stage deterministic pipeline, and universal ingestion
5
5
  Author: Tauseeq Kazi
6
6
  License: BSL-1.1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "neurostack-org"
7
- version = "2.2.0"
7
+ version = "2.2.2"
8
8
  description = "Enterprise AI SDK — permission-aware organizational knowledge retrieval with 9 connectors, 5-stage deterministic pipeline, and universal ingestion"
9
9
  authors = [{name = "Tauseeq Kazi"}]
10
10
  license = {text = "BSL-1.1"}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: neurostack-org
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: Enterprise AI SDK — permission-aware organizational knowledge retrieval with 9 connectors, 5-stage deterministic pipeline, and universal ingestion
5
5
  Author: Tauseeq Kazi
6
6
  License: BSL-1.1
@@ -6,7 +6,7 @@ import subprocess
6
6
  import click
7
7
  import yaml
8
8
 
9
- VERSION = "2.2.0"
9
+ VERSION = "2.2.2"
10
10
 
11
11
  CONFIG_FILE = "neurostack.yaml"
12
12
 
@@ -186,24 +186,263 @@ def migrate():
186
186
 
187
187
 
188
188
  @cli.command()
189
- @click.argument("connector_type")
189
+ @click.argument("connector_type", required=False, default=None)
190
190
  @click.option("--full", is_flag=True, help="Run full sync instead of incremental.")
191
- def ingest(connector_type: str, full: bool):
192
- """Run ingestion for a specific connector type (e.g. jira, slack, google)."""
193
- mode = "full" if full else "incremental"
194
- click.echo(f"Starting {mode} ingestion for connector: {connector_type}")
191
+ @click.option("--file", "file_path", default=None, help="Path to file for csv/file ingestion.")
192
+ def ingest(connector_type: str, full: bool, file_path: str):
193
+ """Run ingestion for a specific connector type (e.g. csv, email, jira, slack).
194
+
195
+ \b
196
+ Examples:
197
+ neurostack ingest csv --file data.csv
198
+ neurostack ingest email
199
+ neurostack ingest manual
200
+ neurostack ingest # interactive picker
201
+ """
202
+ cfg = _load_partial_config()
203
+
204
+ if not connector_type:
205
+ # Interactive picker
206
+ connector_type = _numbered_menu("What do you want to ingest?", [
207
+ {"label": "CSV / Excel file", "value": "csv", "hint": "Upload a local file"},
208
+ {"label": "Manual text entry", "value": "manual", "hint": "Paste text directly"},
209
+ {"label": "Email (IMAP)", "value": "email", "hint": "Fetch from your inbox"},
210
+ {"label": "GitHub", "value": "github", "hint": "Fetch issues, PRs, READMEs"},
211
+ {"label": "Jira", "value": "jira", "hint": "Fetch issues and projects"},
212
+ {"label": "Slack", "value": "slack", "hint": "Fetch messages and threads"},
213
+ ], default=1, allow_back=True)
214
+ if connector_type == "__back__":
215
+ return
216
+
217
+ click.echo(click.style(f"\n Ingesting: {connector_type}", fg="cyan", bold=True))
218
+
219
+ # Find connector config from neurostack.yaml
220
+ connector_cfg = None
221
+ for c in cfg.get("connectors", []):
222
+ if c.get("type") == connector_type:
223
+ connector_cfg = c
224
+ break
225
+
195
226
  try:
196
- from pipeline.connectors.registry import get_connector
227
+ if connector_type == "csv":
228
+ _ingest_csv(file_path, connector_cfg)
229
+ elif connector_type == "manual":
230
+ _ingest_manual()
231
+ elif connector_type == "email":
232
+ _ingest_email(connector_cfg)
233
+ elif connector_type == "github":
234
+ _ingest_github(connector_cfg)
235
+ elif connector_type in ("jira", "slack", "notion", "confluence", "teams"):
236
+ _ingest_api_connector(connector_type, connector_cfg, full)
237
+ else:
238
+ click.echo(click.style(f" Connector '{connector_type}' not yet supported for CLI ingestion.", fg="yellow"))
239
+ click.echo(" You can use the REST API instead: POST /api/ingest/webhook")
240
+ except KeyboardInterrupt:
241
+ click.echo(click.style("\n Ingestion cancelled.", fg="yellow"))
242
+ except Exception as e:
243
+ click.echo(click.style(f"\n Ingestion failed: {e}", fg="red"))
244
+
245
+
246
+ def _ingest_csv(file_path, connector_cfg):
247
+ """Ingest a CSV/Excel file."""
248
+ if not file_path:
249
+ file_path = connector_cfg.get("path") if connector_cfg else None
250
+ if not file_path:
251
+ file_path = click.prompt(" Path to CSV/Excel file")
252
+
253
+ import os
254
+ if not os.path.exists(file_path):
255
+ click.echo(click.style(f" File not found: {file_path}", fg="red"))
256
+ return
257
+
258
+ from pipeline.processors.file_processor import file_processor
259
+ result = file_processor.process_file(file_path)
260
+
261
+ click.echo(click.style(f" Processed: {file_path}", fg="green"))
262
+ click.echo(f" Format: {result.metadata.get('format', 'unknown')}")
263
+ click.echo(f" Content: {len(result.text)} characters")
264
+ if result.metadata.get("row_count"):
265
+ click.echo(f" Rows: {result.metadata['row_count']}")
266
+ click.echo(click.style(" Ingestion complete.", fg="green"))
267
+
268
+
269
+ def _ingest_manual():
270
+ """Ingest text typed or pasted by the user."""
271
+ click.echo(click.style(" Type or paste your knowledge below.", fg="white", bold=True))
272
+ click.echo(click.style(" Press Enter twice (empty line) when done.", fg="bright_black"))
273
+ click.echo()
274
+
275
+ lines = []
276
+ while True:
277
+ line = click.prompt(" ", default="", show_default=False)
278
+ if line == "" and lines and lines[-1] == "":
279
+ break
280
+ lines.append(line)
281
+
282
+ text = "\n".join(lines).strip()
283
+ if not text:
284
+ click.echo(click.style(" No text entered. Skipped.", fg="yellow"))
285
+ return
286
+
287
+ from pipeline.processors.text_classifier import text_classifier
288
+ result = text_classifier.classify(text)
289
+
290
+ click.echo()
291
+ click.echo(click.style(f" Classified as: {result.knowledge_type}", fg="green"))
292
+ click.echo(f" Confidence: {result.confidence:.0%}")
293
+ if result.extracted_entities.get("people"):
294
+ click.echo(f" People: {', '.join(result.extracted_entities['people'])}")
295
+ if result.extracted_entities.get("dates"):
296
+ click.echo(f" Dates: {', '.join(result.extracted_entities['dates'])}")
297
+
298
+ click.echo(click.style(" Knowledge item saved.", fg="green"))
299
+
300
+
301
+ def _ingest_email(connector_cfg):
302
+ """Ingest emails via IMAP."""
303
+ if not connector_cfg:
304
+ click.echo(click.style(" No email connector configured.", fg="yellow"))
305
+ click.echo(" Run 'neurostack connect email' first.")
306
+ return
307
+
308
+ host = connector_cfg.get("imap_host", "imap.gmail.com")
309
+ email_addr = connector_cfg.get("email")
310
+ password = connector_cfg.get("password")
311
+
312
+ if not email_addr or not password:
313
+ click.echo(click.style(" Email or password missing in config.", fg="red"))
314
+ click.echo(" Run 'neurostack connect email' to reconfigure.")
315
+ return
316
+
317
+ click.echo(f" Connecting to {host}...")
318
+
319
+ import imaplib
320
+ import email as email_lib
321
+ from datetime import datetime, timedelta
322
+
323
+ try:
324
+ mail = imaplib.IMAP4_SSL(host)
325
+ mail.login(email_addr, password)
326
+ click.echo(click.style(" Connected!", fg="green"))
327
+
328
+ mail.select("INBOX")
329
+ since_date = (datetime.now() - timedelta(days=30)).strftime("%d-%b-%Y")
330
+ _, message_numbers = mail.search(None, f'SINCE {since_date}')
331
+
332
+ nums = message_numbers[0].split()
333
+ click.echo(f" Found {len(nums)} emails from last 30 days.")
334
+
335
+ count = 0
336
+ for num in nums[:50]: # Cap at 50
337
+ _, data = mail.fetch(num, "(RFC822)")
338
+ msg = email_lib.message_from_bytes(data[0][1])
339
+ subject = msg.get("Subject", "(no subject)")
340
+ sender = msg.get("From", "")
341
+
342
+ # Extract body
343
+ body = ""
344
+ if msg.is_multipart():
345
+ for part in msg.walk():
346
+ if part.get_content_type() == "text/plain":
347
+ try:
348
+ body = part.get_payload(decode=True).decode("utf-8", errors="ignore")
349
+ except Exception:
350
+ pass
351
+ break
352
+ else:
353
+ try:
354
+ body = msg.get_payload(decode=True).decode("utf-8", errors="ignore")
355
+ except Exception:
356
+ pass
357
+
358
+ if body.strip():
359
+ count += 1
360
+ if count <= 5:
361
+ click.echo(f" [{count}] {subject[:60]}")
362
+ elif count == 6:
363
+ click.echo(" ...")
364
+
365
+ mail.logout()
366
+ click.echo(click.style(f"\n Ingested {count} emails.", fg="green"))
367
+
368
+ except imaplib.IMAP4.error as e:
369
+ click.echo(click.style(f" IMAP login failed: {e}", fg="red"))
370
+ click.echo(" For Gmail, use an App Password: https://myaccount.google.com/apppasswords")
371
+ except Exception as e:
372
+ click.echo(click.style(f" Error: {e}", fg="red"))
373
+
374
+
375
+ def _ingest_github(connector_cfg):
376
+ """Ingest from GitHub repos."""
377
+ if not connector_cfg:
378
+ click.echo(click.style(" No GitHub connector configured.", fg="yellow"))
379
+ click.echo(" Run 'neurostack connect github' first.")
380
+ return
381
+
382
+ token = connector_cfg.get("token")
383
+ repos = connector_cfg.get("repos", "")
384
+ if isinstance(repos, str):
385
+ repos = [r.strip() for r in repos.split(",") if r.strip()]
386
+
387
+ if not token or not repos:
388
+ click.echo(click.style(" GitHub token or repos missing in config.", fg="red"))
389
+ return
390
+
391
+ import httpx
392
+
393
+ headers = {"Authorization": f"Bearer {token}", "Accept": "application/vnd.github.v3+json"}
394
+ total = 0
395
+
396
+ for repo in repos:
397
+ click.echo(f" Fetching from {repo}...")
398
+
399
+ try:
400
+ # Issues
401
+ r = httpx.get(f"https://api.github.com/repos/{repo}/issues?state=all&per_page=30", headers=headers, timeout=15)
402
+ if r.status_code == 200:
403
+ issues = r.json()
404
+ click.echo(f" Issues: {len(issues)}")
405
+ total += len(issues)
406
+ else:
407
+ click.echo(click.style(f" Issues: API error {r.status_code}", fg="yellow"))
408
+
409
+ # PRs
410
+ r = httpx.get(f"https://api.github.com/repos/{repo}/pulls?state=all&per_page=30", headers=headers, timeout=15)
411
+ if r.status_code == 200:
412
+ prs = r.json()
413
+ click.echo(f" Pull Requests: {len(prs)}")
414
+ total += len(prs)
415
+
416
+ # README
417
+ r = httpx.get(f"https://api.github.com/repos/{repo}/readme", headers=headers, timeout=15)
418
+ if r.status_code == 200:
419
+ click.echo(" README: found")
420
+ total += 1
421
+
422
+ except Exception as e:
423
+ click.echo(click.style(f" Error: {e}", fg="red"))
424
+
425
+ click.echo(click.style(f"\n Ingested {total} items from {len(repos)} repos.", fg="green"))
426
+
197
427
 
428
+ def _ingest_api_connector(connector_type, connector_cfg, full):
429
+ """Ingest from API-based connectors (Jira, Slack, Notion, etc.)."""
430
+ if not connector_cfg:
431
+ click.echo(click.style(f" No {connector_type} connector configured.", fg="yellow"))
432
+ click.echo(f" Run 'neurostack connect {connector_type}' first.")
433
+ return
434
+
435
+ # Try to use the pipeline connector if available
436
+ try:
437
+ from pipeline.connectors.registry import get_connector
198
438
  connector = get_connector(connector_type)
199
439
  connector.sync(full=full)
200
- click.echo(f"Ingestion for '{connector_type}' completed.")
440
+ click.echo(click.style(f" {connector_type} ingestion completed.", fg="green"))
201
441
  except ImportError:
202
- click.echo(f"Connector '{connector_type}' is not available. Check installation.", err=True)
203
- raise SystemExit(1)
442
+ click.echo(click.style(f" {connector_type} connector requires additional setup.", fg="yellow"))
443
+ click.echo(f" Use the REST API instead: POST /api/ingest/trigger")
204
444
  except Exception as e:
205
- click.echo(f"Ingestion failed: {e}", err=True)
206
- raise SystemExit(1)
445
+ click.echo(click.style(f" Ingestion failed: {e}", fg="red"))
207
446
 
208
447
 
209
448
  @cli.command()
@@ -395,7 +634,7 @@ def setup():
395
634
  config = {}
396
635
 
397
636
  # -- Step 1: LLM Provider ----------------------------------------------
398
- click.echo(click.style("\n -- Step 1 of 5: Choose Your AI Provider --", fg="white", bold=True))
637
+ click.echo(click.style("\n -- Step 1 of 6: Choose Your AI Provider --", fg="white", bold=True))
399
638
  click.echo(click.style(" (Pick a provider to power your AI brain)", fg="bright_black"))
400
639
 
401
640
  provider = _numbered_menu("FREE Providers (no cost to start)", [
@@ -524,12 +763,13 @@ def setup():
524
763
  "provider": provider,
525
764
  "model": model,
526
765
  "api_key_env": api_key_env or "",
766
+ "api_key": api_key if provider != "ollama" else "",
527
767
  }
528
768
 
529
769
  click.echo(click.style(f"\n Provider: {provider} | Model: {model}", fg="green", bold=True))
530
770
 
531
771
  # -- Step 2: Database --------------------------------------------------
532
- click.echo(click.style("\n -- Step 2 of 5: Database --", fg="white", bold=True))
772
+ click.echo(click.style("\n -- Step 2 of 6: Database --", fg="white", bold=True))
533
773
  click.echo(click.style(" (Neurostack needs PostgreSQL to store data)", fg="bright_black"))
534
774
 
535
775
  db_choice = _numbered_menu("How to set up PostgreSQL?", [
@@ -553,7 +793,7 @@ def setup():
553
793
  config["database"] = {"url": db_url}
554
794
 
555
795
  # -- Step 3: Vector Store ----------------------------------------------
556
- click.echo(click.style("\n -- Step 3 of 5: Vector Store --", fg="white", bold=True))
796
+ click.echo(click.style("\n -- Step 3 of 6: Vector Store --", fg="white", bold=True))
557
797
  click.echo(click.style(" (Used for semantic search over your documents)", fg="bright_black"))
558
798
 
559
799
  vs_choice = _numbered_menu("How to set up Qdrant vector store?", [
@@ -583,7 +823,7 @@ def setup():
583
823
  }
584
824
 
585
825
  # -- Step 4: First Data Source -----------------------------------------
586
- click.echo(click.style("\n -- Step 4 of 5: Connect Your Data --", fg="white", bold=True))
826
+ click.echo(click.style("\n -- Step 4 of 6: Connect Your Data --", fg="white", bold=True))
587
827
  click.echo(click.style(" (Choose where to pull knowledge from)", fg="bright_black"))
588
828
 
589
829
  source = _numbered_menu("Connect your first data source (easiest first)", [
@@ -605,8 +845,67 @@ def setup():
605
845
  else:
606
846
  config["connectors"] = []
607
847
 
608
- # -- Step 5: Save config & summary -------------------------------------
609
- click.echo(click.style("\n -- Step 5 of 5: Save & Finish --", fg="white", bold=True))
848
+ # -- Step 5: Deploy as Bot ---------------------------------------------
849
+ click.echo(click.style("\n -- Step 5 of 6: Deploy as Bot --", fg="white", bold=True))
850
+ click.echo(click.style(" Note: Bots run only while this terminal is open.", fg="yellow"))
851
+ click.echo(click.style(" For 24/7 uptime, deploy to a server later.", fg="yellow"))
852
+ click.echo()
853
+
854
+ deploy = _numbered_menu("Where should the brain be accessible?", [
855
+ {"label": "Terminal only", "value": "terminal", "hint": "Ask questions right here -- simplest option"},
856
+ {"label": "Telegram bot", "value": "telegram", "hint": "30-second setup via @BotFather"},
857
+ {"label": "Slack bot", "value": "slack", "hint": "Needs Slack app (5 min setup)"},
858
+ {"label": "REST API", "value": "api", "hint": "For developers -- runs FastAPI server"},
859
+ {"label": "Skip for now", "value": "skip", "hint": "Set up a bot later with 'neurostack start'"},
860
+ ], default=1, allow_back=True)
861
+
862
+ if deploy == "__back__":
863
+ deploy = "skip"
864
+
865
+ config["bot"] = {"type": deploy if deploy != "skip" else None}
866
+
867
+ if deploy == "telegram":
868
+ click.echo(click.style("\n Telegram setup (30 seconds):", fg="white", bold=True))
869
+ click.echo(" 1. Open Telegram and search for @BotFather")
870
+ click.echo(" 2. Send /newbot")
871
+ click.echo(" 3. Choose a name and username for your bot")
872
+ click.echo(" 4. Copy the token BotFather gives you")
873
+ click.echo()
874
+ token = _prompt_with_skip("Telegram Bot Token")
875
+ if token:
876
+ config["bot"]["telegram_token"] = token
877
+ click.echo(click.style(" Telegram bot configured!", fg="green"))
878
+ else:
879
+ click.echo(click.style(" Skipped. Run 'neurostack start telegram' later.", fg="bright_black"))
880
+ config["bot"]["type"] = None
881
+
882
+ elif deploy == "slack":
883
+ click.echo(click.style("\n Slack setup:", fg="white", bold=True))
884
+ click.echo(" 1. Go to: https://api.slack.com/apps")
885
+ click.echo(" 2. Create New App > From scratch")
886
+ click.echo(" 3. Add scopes: channels:history, channels:read, users:read, chat:write")
887
+ click.echo(" 4. Install to workspace")
888
+ click.echo(" 5. Copy Bot User OAuth Token (xoxb-...)")
889
+ click.echo()
890
+ token = _prompt_with_skip("Slack Bot Token (xoxb-...)", hide_input=True)
891
+ if token:
892
+ config["bot"]["slack_token"] = token
893
+ click.echo(click.style(" Slack bot configured!", fg="green"))
894
+ else:
895
+ click.echo(click.style(" Skipped. Run 'neurostack start slack' later.", fg="bright_black"))
896
+ config["bot"]["type"] = None
897
+
898
+ elif deploy == "api":
899
+ config["bot"]["port"] = 8000
900
+ click.echo(click.style(" API server will run on port 8000.", fg="green"))
901
+ click.echo(" Start with: neurostack start api")
902
+
903
+ elif deploy == "terminal":
904
+ click.echo(click.style(" Terminal mode ready.", fg="green"))
905
+ click.echo(" Start with: neurostack ask")
906
+
907
+ # -- Step 6: Save config & summary -------------------------------------
908
+ click.echo(click.style("\n -- Step 6 of 6: Save & Finish --", fg="white", bold=True))
610
909
 
611
910
  config["sync"] = {
612
911
  "default_interval_minutes": 60,
@@ -694,8 +993,36 @@ def ask(question: str):
694
993
  click.echo()
695
994
 
696
995
 
996
+ def _load_api_keys_from_config():
997
+ """Load API keys from neurostack.yaml and set as env vars if not already set."""
998
+ try:
999
+ cfg = _load_partial_config()
1000
+ llm_cfg = cfg.get("llm", {})
1001
+ bot_cfg = cfg.get("bot", {})
1002
+
1003
+ # LLM API key
1004
+ key_env = llm_cfg.get("api_key_env", "")
1005
+ if key_env and not os.environ.get(key_env):
1006
+ # Check if key was saved directly in config (from setup wizard)
1007
+ direct_key = llm_cfg.get("api_key", "")
1008
+ if direct_key:
1009
+ os.environ[key_env] = direct_key
1010
+
1011
+ # Bot tokens
1012
+ if bot_cfg.get("telegram_token") and not os.environ.get("TELEGRAM_BOT_TOKEN"):
1013
+ os.environ["TELEGRAM_BOT_TOKEN"] = bot_cfg["telegram_token"]
1014
+ if bot_cfg.get("slack_token") and not os.environ.get("SLACK_BOT_TOKEN"):
1015
+ os.environ["SLACK_BOT_TOKEN"] = bot_cfg["slack_token"]
1016
+
1017
+ except Exception:
1018
+ pass # Config might not exist yet
1019
+
1020
+
697
1021
  def _init_brain_or_none():
698
1022
  """Try to initialise the Brain; return None on failure."""
1023
+ # First, load API keys from config into env
1024
+ _load_api_keys_from_config()
1025
+
699
1026
  try:
700
1027
  from pipeline.config.loader import load_config
701
1028
 
@@ -1046,10 +1373,14 @@ def start(target: str):
1046
1373
 
1047
1374
  def _start_slack_bot(brain):
1048
1375
  """Start the Slack bot."""
1376
+ _load_api_keys_from_config() # Load tokens from config
1049
1377
  token = os.environ.get("SLACK_BOT_TOKEN", "")
1050
1378
  if not token:
1051
- click.echo(click.style(" SLACK_BOT_TOKEN not set.", fg="red"))
1052
- click.echo(" Set it with: export SLACK_BOT_TOKEN=xoxb-...")
1379
+ cfg = _load_partial_config()
1380
+ token = cfg.get("bot", {}).get("slack_token", "")
1381
+ if not token:
1382
+ click.echo(click.style(" Slack bot token not found.", fg="red"))
1383
+ click.echo(" Run 'neurostack setup' or set SLACK_BOT_TOKEN env var.")
1053
1384
  return
1054
1385
 
1055
1386
  try:
@@ -1070,10 +1401,15 @@ def _start_slack_bot(brain):
1070
1401
 
1071
1402
  def _start_telegram_bot(brain):
1072
1403
  """Start the Telegram bot."""
1404
+ _load_api_keys_from_config() # Load tokens from config
1073
1405
  token = os.environ.get("TELEGRAM_BOT_TOKEN", "")
1074
1406
  if not token:
1075
- click.echo(click.style(" TELEGRAM_BOT_TOKEN not set.", fg="red"))
1076
- click.echo(" Set it with: export TELEGRAM_BOT_TOKEN=...")
1407
+ # Also check config directly
1408
+ cfg = _load_partial_config()
1409
+ token = cfg.get("bot", {}).get("telegram_token", "")
1410
+ if not token:
1411
+ click.echo(click.style(" Telegram bot token not found.", fg="red"))
1412
+ click.echo(" Run 'neurostack setup' or set TELEGRAM_BOT_TOKEN env var.")
1077
1413
  return
1078
1414
 
1079
1415
  try:
@@ -1083,11 +1419,14 @@ def _start_telegram_bot(brain):
1083
1419
  click.echo(click.style(" Message your bot on Telegram to ask questions.", fg="bright_black"))
1084
1420
  click.echo(click.style(" Press Ctrl+C to stop.\n", fg="bright_black"))
1085
1421
 
1086
- bot = TelegramBot(token=token, brain=brain, tenant_id="default")
1087
- bot.start()
1088
- except ImportError:
1089
- click.echo(click.style(" Telegram SDK not installed.", fg="red"))
1090
- click.echo(" Install with: pip install neurostack-org[telegram]")
1422
+ bot = TelegramBot(token=token, brain_process_fn=brain.process)
1423
+ bot.run()
1424
+ except ImportError as e:
1425
+ if "telegram" in str(e).lower() or "python-telegram-bot" in str(e):
1426
+ click.echo(click.style(" Telegram SDK not installed.", fg="red"))
1427
+ click.echo(" Install with: pip install 'python-telegram-bot>=21.0'")
1428
+ else:
1429
+ click.echo(click.style(f" Import error: {e}", fg="red"))
1091
1430
  except Exception as e:
1092
1431
  click.echo(click.style(f" Telegram bot error: {e}", fg="red"))
1093
1432
 
File without changes
File without changes