contextual-engine 0.8.0__tar.gz → 0.8.5__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 (261) hide show
  1. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/PKG-INFO +1 -1
  2. contextual_engine-0.8.5/contextual/cli/commands/client.py +680 -0
  3. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/commands/initialisation.py +7 -21
  4. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/commands/mcp.py +20 -8
  5. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/core/output.py +30 -33
  6. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/config/global_/mcp_config.py +9 -6
  7. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/config/indexing.py +8 -3
  8. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/incremental/daemon_embed.py +15 -0
  9. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/indexing/pipeline.py +153 -44
  10. contextual_engine-0.8.5/contextual/integrations/__init__.py +1 -0
  11. contextual_engine-0.8.5/contextual/integrations/client_content.py +149 -0
  12. contextual_engine-0.8.5/contextual/integrations/client_writer.py +126 -0
  13. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/daemon.py +52 -12
  14. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/router.py +8 -20
  15. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/server.py +53 -10
  16. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/session_manager.py +28 -3
  17. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/shim.py +15 -2
  18. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/models/__init__.py +68 -34
  19. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/models/reranker_model.py +43 -1
  20. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/models/unified_embedder.py +52 -14
  21. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/runner.py +15 -2
  22. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/solo/m012_workspace_metadata.py +1 -1
  23. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/temporal/blame_pipeline.py +16 -18
  24. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/pyproject.toml +1 -1
  25. contextual_engine-0.8.0/contextual/cli/commands/client.py +0 -316
  26. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/.contextualignore +0 -0
  27. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/.github/workflows/ci.yml +0 -0
  28. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/.github/workflows/release.yml +0 -0
  29. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/.gitignore +0 -0
  30. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/.mcp.json +0 -0
  31. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/LICENSE +0 -0
  32. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/README.md +0 -0
  33. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/__init__.py +0 -0
  34. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/__main__.py +0 -0
  35. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/__init__.py +0 -0
  36. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/commands/__init__.py +0 -0
  37. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/commands/auth.py +0 -0
  38. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/commands/config.py +0 -0
  39. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/commands/doctor.py +0 -0
  40. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/commands/fetch.py +0 -0
  41. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/commands/index.py +0 -0
  42. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/commands/install.py +0 -0
  43. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/commands/stats.py +0 -0
  44. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/commands/workspace.py +0 -0
  45. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/core/__init__.py +0 -0
  46. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/core/context.py +0 -0
  47. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/core/decorators.py +0 -0
  48. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/cli/core/editor.py +0 -0
  49. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/config/__init__.py +0 -0
  50. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/config/base.py +0 -0
  51. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/config/global_/__init__.py +0 -0
  52. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/config/global_/security_config.py +0 -0
  53. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/config/global_/storage_config.py +0 -0
  54. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/config/paths.py +0 -0
  55. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/config/workspace/__init__.py +0 -0
  56. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/config/workspace/cache_config.py +0 -0
  57. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/config/workspace/indexing_config.py +0 -0
  58. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/config/workspace/observability_config.py +0 -0
  59. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/config/workspace/retrieval_config.py +0 -0
  60. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/config/workspace/workspace_config.py +0 -0
  61. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/core/__init__.py +0 -0
  62. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/core/errors.py +0 -0
  63. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/core/models.py +0 -0
  64. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/git/__init__.py +0 -0
  65. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/git/blame.py +0 -0
  66. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/git/hooks.py +0 -0
  67. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/git/incremental_git.py +0 -0
  68. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/graph/__init__.py +0 -0
  69. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/graph/dedup.py +0 -0
  70. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/graph/extractor.py +0 -0
  71. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/graph/integrator.py +0 -0
  72. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/graph/resolver.py +0 -0
  73. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/graph/staleness.py +0 -0
  74. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/graph/store.py +0 -0
  75. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/graph/traversal.py +0 -0
  76. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/incremental/__init__.py +0 -0
  77. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/incremental/file_watcher.py +0 -0
  78. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/incremental/incremental_indexer.py +0 -0
  79. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/indexing/__init__.py +0 -0
  80. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/indexing/_config.py +0 -0
  81. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/indexing/chunker.py +0 -0
  82. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/indexing/docs_pipeline.py +0 -0
  83. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/indexing/index_writer.py +0 -0
  84. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/indexing/merkle_tree.py +0 -0
  85. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/indexing/processor.py +0 -0
  86. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/indexing/symbol_extractor.py +0 -0
  87. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/__init__.py +0 -0
  88. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/__main__.py +0 -0
  89. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/_platform.py +0 -0
  90. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/access.py +0 -0
  91. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/config.py +0 -0
  92. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/middleware/__init__.py +0 -0
  93. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/middleware/audit.py +0 -0
  94. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/middleware/auth.py +0 -0
  95. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/middleware/ratelimit.py +0 -0
  96. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/middleware/warmup.py +0 -0
  97. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/middleware/workspace.py +0 -0
  98. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/tools/__init__.py +0 -0
  99. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/tools/graph.py +0 -0
  100. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/tools/nexus.py +0 -0
  101. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/tools/semantic.py +0 -0
  102. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/tools/system.py +0 -0
  103. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/mcp/tools/temporal.py +0 -0
  104. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/models/_inference.py +0 -0
  105. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/models/allowlist.py +0 -0
  106. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/models/base_embedder.py +0 -0
  107. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/observability/__init__.py +0 -0
  108. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/observability/config.py +0 -0
  109. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/observability/exporters.py +0 -0
  110. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/observability/logging.py +0 -0
  111. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/observability/retention.py +0 -0
  112. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/observability/tracer.py +0 -0
  113. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/retrieval/__init__.py +0 -0
  114. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/retrieval/context_assembler.py +0 -0
  115. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/retrieval/mmr.py +0 -0
  116. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/retrieval/pipeline.py +0 -0
  117. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/retrieval/ranker.py +0 -0
  118. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/retrieval/tokenizer.py +0 -0
  119. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/security/__init__.py +0 -0
  120. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/security/paths.py +0 -0
  121. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/security/sanitize.py +0 -0
  122. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/security/workspace.py +0 -0
  123. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/__init__.py +0 -0
  124. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/cache.py +0 -0
  125. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/connection.py +0 -0
  126. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/fts.py +0 -0
  127. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/lancedb_provider.py +0 -0
  128. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/__init__.py +0 -0
  129. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/enterprise/me001_orgs.py +0 -0
  130. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/enterprise/me002_org_members.py +0 -0
  131. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/enterprise/me003_teams.py +0 -0
  132. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/enterprise/me004_team_members.py +0 -0
  133. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/enterprise/me005_org_workspaces.py +0 -0
  134. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/enterprise/me006_workspace_permissions.py +0 -0
  135. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/enterprise/me007_idp_sync_log.py +0 -0
  136. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/enterprise/me008_org_policies.py +0 -0
  137. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/enterprise/me009_api_keys.py +0 -0
  138. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/enterprise/me010_org_audit_log.py +0 -0
  139. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/enterprise/me011_workspace_traceability.py +0 -0
  140. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/m001_global.py +0 -0
  141. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/m002_embeddings.py +0 -0
  142. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/m003_temporal.py +0 -0
  143. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/m004_graph.py +0 -0
  144. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/m005_cache.py +0 -0
  145. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/m006_logs.py +0 -0
  146. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/m007_terminal_sessions.py +0 -0
  147. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/m008_queue.py +0 -0
  148. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/m009_swarm.py +0 -0
  149. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/m010_task_force.py +0 -0
  150. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/m011_reminders.py +0 -0
  151. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/m013_temporal_l2.py +0 -0
  152. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/m014_nexus_foundation.py +0 -0
  153. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/solo/m001_global.py +0 -0
  154. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/solo/m002_embeddings.py +0 -0
  155. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/solo/m003_temporal.py +0 -0
  156. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/solo/m004_graph.py +0 -0
  157. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/solo/m005_cache.py +0 -0
  158. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/solo/m006_logs.py +0 -0
  159. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/solo/m007_terminal_sessions.py +0 -0
  160. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/solo/m008_queue.py +0 -0
  161. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/solo/m009_swarm.py +0 -0
  162. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/solo/m010_task_force.py +0 -0
  163. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/solo/m011_reminders.py +0 -0
  164. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/solo/m013_temporal_l2.py +0 -0
  165. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/solo/m014_nexus_foundation.py +0 -0
  166. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/teams/mt001_orgs.py +0 -0
  167. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/teams/mt002_org_members.py +0 -0
  168. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/teams/mt003_teams.py +0 -0
  169. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/teams/mt004_team_members.py +0 -0
  170. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/teams/mt005_team_workspaces.py +0 -0
  171. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/teams/mt006_workspace_permissions.py +0 -0
  172. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/teams/mt007_team_audit_log.py +0 -0
  173. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/migrations/teams/mt008_workspace_traceability.py +0 -0
  174. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/schema.py +0 -0
  175. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/schema_base.py +0 -0
  176. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/schema_ent.py +0 -0
  177. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/storage/schema_team.py +0 -0
  178. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/temporal/__init__.py +0 -0
  179. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/temporal/adr.py +0 -0
  180. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/temporal/context.py +0 -0
  181. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/temporal/query.py +0 -0
  182. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/contextual/temporal/velocity.py +0 -0
  183. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/ADRs/ADR-001-lancedb-over-vec0.md +0 -0
  184. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/ADRs/ADR-002-retry-exponential-backoff.md +0 -0
  185. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/ADRs/ADR-003-blake3-deduplication.md +0 -0
  186. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/ADRs/ADR-004-async-first-architecture.md +0 -0
  187. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/ADRs/ADR-005-model-lineup.md +0 -0
  188. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/ARCHITECTURE.md +0 -0
  189. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/CHANGELOG.md +0 -0
  190. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DATABASE.md +0 -0
  191. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DEPLOYMENT.md +0 -0
  192. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DISTRIBUTIONS.md +0 -0
  193. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/CHATGPT-DR-mcp-cli-security-testing-distribution.md +0 -0
  194. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/CHATGPT-DR-web-ui-experience.md +0 -0
  195. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/CLAUDE-DR-ai-agents-teams.md +0 -0
  196. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/CLAUDE-DR-competitive-landscape-market-analysis.md +0 -0
  197. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/CLAUDE-DR-core-architecture.md +0 -0
  198. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/CLAUDE-DR-mvp-phase0-technical-plan.md +0 -0
  199. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/CLAUDE-DR-strategic-briefing.md +0 -0
  200. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/GEMINI-DR-graph-implementation.md +0 -0
  201. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/GEMINI-DR-hybrid-retrieval-local-optimization.md +0 -0
  202. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/GEMINI-DR-infrastructure-security.md +0 -0
  203. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/GEMINI-DR-lance-specs.md +0 -0
  204. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/GEMINI-DR-nexus-graph-architecture.md +0 -0
  205. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/GEMINI-DR-nexus-graph-engineering.md +0 -0
  206. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/GEMINI-DR-vec0-vs-lancedb.md +0 -0
  207. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/GRAPH_LAYER_OPEN_QUESTIONS_RESOLVED.md +0 -0
  208. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/KIMI-DR-tech-stack-optimization.md +0 -0
  209. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/PERPLEXITY-DR-L2-temporal.md +0 -0
  210. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/PERPLEXITY-DR-client-integration.md +0 -0
  211. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/PERPLEXITY-DR-indexing-optimisation.md +0 -0
  212. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/PERPLEXITY-DR-lancedb-implementation.md +0 -0
  213. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/PERPLEXITY-DR-local-data-infrastructure.md +0 -0
  214. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/PERPLEXITY-DR-mcp-config.md +0 -0
  215. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/PERPLEXITY-DR-mcp-security-hardening.md +0 -0
  216. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/PERPLEXITY-DR-models-pipelines.md +0 -0
  217. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/PERPLEXITY-DR-python-async-ai-pipelines.md +0 -0
  218. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/DRs/PERPLEXITY-DR-semantic-indexing.md +0 -0
  219. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/FRONTEND.md +0 -0
  220. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/GITHUB.md +0 -0
  221. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/INTEGRATIONS.md +0 -0
  222. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/MCP_TOOLS.md +0 -0
  223. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/MEMORY.md +0 -0
  224. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/PROJECT_MANAGEMENT.md +0 -0
  225. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/PROJECT_PHASES.md +0 -0
  226. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/RESOURCES.md +0 -0
  227. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/ROADMAP.md +0 -0
  228. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/SECURITY.md +0 -0
  229. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/TEAMS.md +0 -0
  230. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/TECHNICAL_SPEC.md +0 -0
  231. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/TESTING.md +0 -0
  232. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/docs/VERSION.MD +0 -0
  233. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/pytest.ini +0 -0
  234. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/README.md +0 -0
  235. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/__init__.py +0 -0
  236. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/conftest.py +0 -0
  237. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/fixtures/__init__.py +0 -0
  238. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/fixtures/sample_code.py +0 -0
  239. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/fixtures/sample_docs.md +0 -0
  240. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_accuracy.py +0 -0
  241. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_architecture.py +0 -0
  242. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_async.py +0 -0
  243. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_behaviour.py +0 -0
  244. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_cache_stats.py +0 -0
  245. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_cli.py +0 -0
  246. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_flow.py +0 -0
  247. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_git.py +0 -0
  248. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_graph.py +0 -0
  249. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_graph_resolution_order.py +0 -0
  250. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_incremental.py +0 -0
  251. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_indexing.py +0 -0
  252. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_latency.py +0 -0
  253. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_mcp.py +0 -0
  254. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_mcp_hardening.py +0 -0
  255. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_observability.py +0 -0
  256. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_performance.py +0 -0
  257. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_query_cache_roundtrip.py +0 -0
  258. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_retrieval.py +0 -0
  259. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_retry.py +0 -0
  260. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_security.py +0 -0
  261. {contextual_engine-0.8.0 → contextual_engine-0.8.5}/tests/test_storage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: contextual-engine
3
- Version: 0.8.0
3
+ Version: 0.8.5
4
4
  Summary: Temporal-first local code memory for AI tools via MCP
5
5
  Project-URL: Homepage, https://contextuallabs.dev
6
6
  Project-URL: Documentation, https://contextuallabs.dev/docs
@@ -0,0 +1,680 @@
1
+ """Client configuration commands for MCP clients."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import shlex
8
+ import shutil
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Annotated
12
+
13
+ import typer
14
+
15
+ from contextual.cli.core import register_command, require_config, open_in_editor
16
+ from contextual.cli.core.output import (
17
+ console,
18
+ print_client_configured,
19
+ print_error,
20
+ print_info,
21
+ print_md_injected,
22
+ print_agents_md_hint,
23
+ print_client_list,
24
+ )
25
+ from contextual.integrations.client_content import (
26
+ cursor_file_content,
27
+ antigravity_file_content,
28
+ marker_block,
29
+ )
30
+ from contextual.integrations.client_writer import write_type_a, write_type_b
31
+
32
+
33
+ def _find_uvx() -> str | None:
34
+ """Locate a usable uvx executable."""
35
+ uvx_path = shutil.which("uvx")
36
+ if uvx_path:
37
+ return uvx_path
38
+
39
+ for candidate in [
40
+ Path.home() / ".local" / "bin" / "uvx",
41
+ Path("/usr/local/bin/uvx"),
42
+ Path("/opt/homebrew/bin/uvx"),
43
+ ]:
44
+ if candidate.exists():
45
+ return str(candidate)
46
+
47
+ return None
48
+
49
+
50
+ def _backup_config(config_path: Path) -> dict:
51
+ """Load an existing config, backing it up first when present."""
52
+ existing: dict = {}
53
+ if config_path.exists():
54
+ backup_path = config_path.with_suffix(".json.bak")
55
+ try:
56
+ shutil.copy2(config_path, backup_path)
57
+ except OSError:
58
+ pass
59
+
60
+ try:
61
+ existing = json.loads(config_path.read_text(encoding="utf-8"))
62
+ except (json.JSONDecodeError, OSError):
63
+ existing = {}
64
+
65
+ return existing
66
+
67
+
68
+ def _extract_workspace_from_entry(entry: dict) -> str | None:
69
+ """Return the resolved workspace path embedded in an MCP entry's args, or None."""
70
+ args = entry.get("args", [])
71
+ if not isinstance(args, list):
72
+ return None
73
+ # Standard entry: [..., "--workspace", "/path"]
74
+ try:
75
+ idx = args.index("--workspace")
76
+ if idx + 1 < len(args):
77
+ return str(Path(args[idx + 1]).resolve())
78
+ except ValueError:
79
+ pass
80
+ # Shell entry (Claude Desktop zsh -lc "... --workspace /path ..."):
81
+ # workspace path is the token immediately after --workspace in the shell string.
82
+ for arg in args:
83
+ if isinstance(arg, str) and "--workspace" in arg:
84
+ after = arg.split("--workspace", 1)[1].strip()
85
+ try:
86
+ tokens = shlex.split(after)
87
+ if tokens:
88
+ return str(Path(tokens[0]).resolve())
89
+ except ValueError:
90
+ pass
91
+ return None
92
+
93
+
94
+ def _persist_client_config(config_path: Path, document: dict, mcp_servers: dict) -> None:
95
+ """Atomically write the merged config back to disk."""
96
+ document["mcpServers"] = mcp_servers
97
+ try:
98
+ config_path.parent.mkdir(parents=True, exist_ok=True)
99
+ tmp = config_path.with_suffix(".json.tmp")
100
+ tmp.write_text(json.dumps(document, indent=2), encoding="utf-8")
101
+ os.chmod(tmp, 0o600)
102
+ os.replace(tmp, config_path)
103
+ except OSError as exc:
104
+ print_error(
105
+ f"Failed to write config: {exc}",
106
+ hint=f"Check permissions on {config_path.parent}",
107
+ )
108
+ raise typer.Exit(1) from exc
109
+
110
+
111
+ def _write_client_config(config_path: Path, mcp_entry: dict, workspace_root: Path) -> str:
112
+ """Merge the Contextual entry into a client config. Returns the key written.
113
+
114
+ Smart workspace detection:
115
+ - If an existing contextual entry already points at this workspace → update it in-place
116
+ (preserves the user's key, avoids accumulating duplicate entries).
117
+ - If no contextual entry exists → add under the bare ``contextual`` key.
118
+ - If ``contextual`` is taken by a different workspace → add as ``contextual-<dirname>``,
119
+ with a short path-hash suffix if that key is also taken.
120
+ """
121
+ import hashlib
122
+
123
+ existing = _backup_config(config_path)
124
+ mcp_servers = existing.get("mcpServers")
125
+ if not isinstance(mcp_servers, dict):
126
+ mcp_servers = {}
127
+
128
+ ws_str = str(workspace_root.resolve())
129
+
130
+ # 1. Look for an existing contextual entry that already owns this workspace.
131
+ for key, val in mcp_servers.items():
132
+ if isinstance(val, dict) and (key == "contextual" or key.startswith("contextual-")):
133
+ if _extract_workspace_from_entry(val) == ws_str:
134
+ mcp_servers[key] = mcp_entry
135
+ _persist_client_config(config_path, existing, mcp_servers)
136
+ return key
137
+
138
+ # 2. No match — pick the least-surprising new key.
139
+ if "contextual" not in mcp_servers:
140
+ key = "contextual"
141
+ else:
142
+ key = f"contextual-{workspace_root.name}"
143
+ if key in mcp_servers:
144
+ suffix = hashlib.sha256(ws_str.encode()).hexdigest()[:6]
145
+ key = f"contextual-{workspace_root.name}-{suffix}"
146
+
147
+ mcp_servers[key] = mcp_entry
148
+ _persist_client_config(config_path, existing, mcp_servers)
149
+ return key
150
+
151
+
152
+ def _build_mcp_entry(uvx_path: str, workspace_root: Path) -> dict:
153
+ """Build the default MCP launcher entry for Contextual."""
154
+ return {
155
+ "command": uvx_path,
156
+ "args": ["--from", "contextual-engine", "contextual-mcp", "--workspace", str(workspace_root)],
157
+ }
158
+
159
+
160
+ def _build_claude_mcp_entry(uvx_path: str, workspace_root: Path) -> dict:
161
+ """Build the Claude Desktop MCP entry with an explicit workspace cd."""
162
+ return {
163
+ "command": "/bin/zsh",
164
+ "args": [
165
+ "-lc",
166
+ (
167
+ f"cd {shlex.quote(str(workspace_root))} "
168
+ f"&& exec {shlex.quote(uvx_path)} --from contextual-engine contextual-mcp "
169
+ f"--workspace {shlex.quote(str(workspace_root))}"
170
+ ),
171
+ ],
172
+ }
173
+
174
+
175
+ def _build_vscode_mcp_entry(uvx_path: str, workspace_root: Path) -> dict:
176
+ """Build the VS Code / GitHub Copilot MCP entry.
177
+
178
+ VS Code requires an explicit ``"type": "stdio"`` field — other clients
179
+ accept entries without it.
180
+ """
181
+ return {
182
+ "type": "stdio",
183
+ "command": uvx_path,
184
+ "args": ["--from", "contextual-engine", "contextual-mcp", "--workspace", str(workspace_root)],
185
+ }
186
+
187
+
188
+ # ── VS Code / GitHub Copilot writer ──────────────────────────────────────────
189
+ # VS Code's mcp.json uses a "servers" root key (not "mcpServers").
190
+
191
+ def _persist_vscode_config(config_path: Path, document: dict, servers: dict) -> None:
192
+ """Atomically write VS Code mcp.json using the 'servers' root key."""
193
+ document["servers"] = servers
194
+ try:
195
+ config_path.parent.mkdir(parents=True, exist_ok=True)
196
+ tmp = config_path.with_suffix(".json.tmp")
197
+ tmp.write_text(json.dumps(document, indent=2), encoding="utf-8")
198
+ os.chmod(tmp, 0o600)
199
+ os.replace(tmp, config_path)
200
+ except OSError as exc:
201
+ print_error(
202
+ f"Failed to write config: {exc}",
203
+ hint=f"Check permissions on {config_path.parent}",
204
+ )
205
+ raise typer.Exit(1) from exc
206
+
207
+
208
+ def _write_vscode_config(config_path: Path, mcp_entry: dict, workspace_root: Path) -> str:
209
+ """Merge the Contextual entry into a VS Code mcp.json. Returns the key written."""
210
+ import hashlib
211
+
212
+ existing = _backup_config(config_path)
213
+ servers = existing.get("servers")
214
+ if not isinstance(servers, dict):
215
+ servers = {}
216
+
217
+ ws_str = str(workspace_root.resolve())
218
+
219
+ for key, val in servers.items():
220
+ if isinstance(val, dict) and (key == "contextual" or key.startswith("contextual-")):
221
+ if _extract_workspace_from_entry(val) == ws_str:
222
+ servers[key] = mcp_entry
223
+ _persist_vscode_config(config_path, existing, servers)
224
+ return key
225
+
226
+ if "contextual" not in servers:
227
+ key = "contextual"
228
+ else:
229
+ key = f"contextual-{workspace_root.name}"
230
+ if key in servers:
231
+ suffix = hashlib.sha256(ws_str.encode()).hexdigest()[:6]
232
+ key = f"contextual-{workspace_root.name}-{suffix}"
233
+
234
+ servers[key] = mcp_entry
235
+ _persist_vscode_config(config_path, existing, servers)
236
+ return key
237
+
238
+
239
+ def _configure_vscode_client(
240
+ config_path: Path,
241
+ mcp_entry: dict,
242
+ workspace_root: Path,
243
+ dry_run: bool = False,
244
+ ) -> None:
245
+ """Write Contextual MCP entry into a VS Code mcp.json."""
246
+ if dry_run:
247
+ from rich.syntax import Syntax
248
+ print_info(f"Dry run — would write to: {config_path}")
249
+ console.print()
250
+ console.print(
251
+ Syntax(
252
+ json.dumps({"servers": {"contextual": mcp_entry}}, indent=2),
253
+ "json",
254
+ theme="ansi_dark",
255
+ background_color="default",
256
+ )
257
+ )
258
+ console.print()
259
+ return
260
+
261
+ key = _write_vscode_config(config_path, mcp_entry, workspace_root)
262
+ print_client_configured("GitHub Copilot", key, config_path)
263
+
264
+
265
+ # ── Codex TOML writer ─────────────────────────────────────────────────────────
266
+ # Codex uses ~/.codex/config.toml with [mcp_servers.<name>] TOML tables.
267
+
268
+ def _persist_codex_toml(config_path: Path, document: dict) -> None:
269
+ """Atomically write Codex config.toml."""
270
+ import tomli_w
271
+ try:
272
+ config_path.parent.mkdir(parents=True, exist_ok=True)
273
+ tmp = config_path.with_suffix(".toml.tmp")
274
+ tmp.write_bytes(tomli_w.dumps(document).encode("utf-8"))
275
+ os.chmod(tmp, 0o600)
276
+ os.replace(tmp, config_path)
277
+ except OSError as exc:
278
+ print_error(
279
+ f"Failed to write config: {exc}",
280
+ hint=f"Check permissions on {config_path.parent}",
281
+ )
282
+ raise typer.Exit(1) from exc
283
+
284
+
285
+ def _write_codex_toml_config(config_path: Path, uvx_path: str, workspace_root: Path) -> str:
286
+ """Merge the Contextual entry into Codex config.toml. Returns the key written."""
287
+ import hashlib
288
+ import tomllib
289
+
290
+ existing: dict = {}
291
+ if config_path.exists():
292
+ backup_path = config_path.with_suffix(".toml.bak")
293
+ try:
294
+ shutil.copy2(config_path, backup_path)
295
+ except OSError:
296
+ pass
297
+ try:
298
+ with open(config_path, "rb") as fh:
299
+ existing = tomllib.load(fh)
300
+ except Exception:
301
+ existing = {}
302
+
303
+ mcp_servers = existing.setdefault("mcp_servers", {})
304
+ ws_str = str(workspace_root.resolve())
305
+ entry = {
306
+ "command": uvx_path,
307
+ "args": ["--from", "contextual-engine", "contextual-mcp", "--workspace", str(workspace_root)],
308
+ }
309
+
310
+ for key, val in mcp_servers.items():
311
+ if isinstance(val, dict) and (key == "contextual" or key.startswith("contextual-")):
312
+ args = val.get("args", [])
313
+ try:
314
+ idx = args.index("--workspace")
315
+ if idx + 1 < len(args) and str(Path(args[idx + 1]).resolve()) == ws_str:
316
+ mcp_servers[key] = entry
317
+ _persist_codex_toml(config_path, existing)
318
+ return key
319
+ except (ValueError, IndexError):
320
+ pass
321
+
322
+ if "contextual" not in mcp_servers:
323
+ key = "contextual"
324
+ else:
325
+ key = f"contextual-{workspace_root.name}"
326
+ if key in mcp_servers:
327
+ suffix = hashlib.sha256(ws_str.encode()).hexdigest()[:6]
328
+ key = f"contextual-{workspace_root.name}-{suffix}"
329
+
330
+ mcp_servers[key] = entry
331
+ _persist_codex_toml(config_path, existing)
332
+ return key
333
+
334
+
335
+ def _configure_codex_client(
336
+ config_path: Path,
337
+ uvx_path: str,
338
+ workspace_root: Path,
339
+ dry_run: bool = False,
340
+ ) -> None:
341
+ """Write Contextual MCP entry into Codex config.toml."""
342
+ if dry_run:
343
+ from rich.syntax import Syntax
344
+ print_info(f"Dry run — would write to: {config_path}")
345
+ console.print()
346
+ console.print(
347
+ Syntax(
348
+ f"[mcp_servers.contextual]\ncommand = {json.dumps(uvx_path)}\n"
349
+ f'args = ["--from", "contextual-engine", "contextual-mcp", '
350
+ f'"--workspace", {json.dumps(str(workspace_root))}]',
351
+ "toml",
352
+ theme="ansi_dark",
353
+ background_color="default",
354
+ )
355
+ )
356
+ console.print()
357
+ return
358
+
359
+ key = _write_codex_toml_config(config_path, uvx_path, workspace_root)
360
+ print_client_configured("Codex", key, config_path)
361
+
362
+
363
+ # ── Manual / UI-based clients ─────────────────────────────────────────────────
364
+ # ChatGPT and Perplexity have no writable local config file — MCP servers are
365
+ # added through their desktop UI. Display what the user needs to paste.
366
+
367
+ def _display_manual_config(
368
+ client_name: str,
369
+ uvx_path: str,
370
+ workspace_root: Path,
371
+ ui_steps: list[str],
372
+ ) -> None:
373
+ """Print manual setup instructions for UI-based MCP clients."""
374
+ from rich.syntax import Syntax
375
+ from rich.panel import Panel
376
+ from rich.text import Text
377
+
378
+ cmd = (
379
+ f"{uvx_path} --from contextual-engine contextual-mcp "
380
+ f"--workspace {shlex.quote(str(workspace_root))}"
381
+ )
382
+
383
+ console.print()
384
+ console.print(Panel(
385
+ Text(
386
+ f"{client_name} does not support a local config file — "
387
+ "MCP servers must be added through the app UI.",
388
+ style="white",
389
+ ),
390
+ title=f"[white bold]{client_name} — Manual Setup Required[/white bold]",
391
+ border_style="bright_black",
392
+ padding=(0, 1),
393
+ ))
394
+ console.print()
395
+
396
+ for i, step in enumerate(ui_steps, 1):
397
+ console.print(f" [white bold]{i}.[/white bold] [#6b6b6b]{step}[/#6b6b6b]")
398
+
399
+ console.print()
400
+ console.print(" [white bold]Server name:[/white bold] [white]contextual[/white]")
401
+ console.print(" [white bold]Command:[/white bold]")
402
+ console.print()
403
+ console.print(
404
+ Syntax(cmd, "bash", theme="ansi_dark", background_color="default")
405
+ )
406
+ console.print()
407
+
408
+
409
+ def _config_paths() -> dict[str, Path]:
410
+ """Return per-client config file paths, platform-aware.
411
+
412
+ Paths verified against docs/DRs/PERPLEXITY-DR-mcp-config.md (June 2026).
413
+ chatgpt / perplexity are intentionally absent — both are UI-only; no local
414
+ config file exists for them. They are handled by _display_manual_config().
415
+ """
416
+ home = Path.home()
417
+ is_mac = sys.platform == "darwin"
418
+ is_win = sys.platform == "win32"
419
+ app_support = (
420
+ home / "Library" / "Application Support"
421
+ if is_mac
422
+ else Path(os.environ.get("APPDATA", str(home)))
423
+ if is_win
424
+ else home / ".config"
425
+ )
426
+ return {
427
+ "claude": app_support / "Claude" / "claude_desktop_config.json",
428
+ "gemini-cli": home / ".gemini" / "settings.json",
429
+ "cursor": home / ".cursor" / "mcp.json",
430
+ "copilot": home / ".vscode" / "mcp.json",
431
+ "windsurf": home / ".codeium" / "windsurf" / "mcp_config.json",
432
+ "codex": home / ".codex" / "config.toml",
433
+ "antigravity": home / ".gemini" / "config" / "mcp_config.json",
434
+ }
435
+
436
+
437
+ def _configure_client(
438
+ client_name: str,
439
+ config_path: Path,
440
+ mcp_entry: dict,
441
+ workspace_root: Path,
442
+ dry_run: bool = False,
443
+ ) -> None:
444
+ """Write Contextual MCP entry into a client config file."""
445
+ if dry_run:
446
+ from rich.syntax import Syntax
447
+ print_info(f"Dry run — would write to: {config_path}")
448
+ console.print()
449
+ console.print(
450
+ Syntax(
451
+ json.dumps({"contextual": mcp_entry}, indent=2),
452
+ "json",
453
+ theme="ansi_dark",
454
+ background_color="default",
455
+ )
456
+ )
457
+ console.print()
458
+ return
459
+
460
+ key = _write_client_config(config_path, mcp_entry, workspace_root)
461
+ print_client_configured(client_name, key, config_path)
462
+
463
+
464
+ @register_command(group="client")
465
+ @require_config
466
+ def client(
467
+ typer_ctx: typer.Context,
468
+ claude: Annotated[
469
+ bool,
470
+ typer.Option("--claude", help="Configure Claude Desktop to use Contextual via MCP."),
471
+ ] = False,
472
+ claude_code: Annotated[
473
+ bool,
474
+ typer.Option("--claude-code", help="Configure Claude Code (CLI) to use Contextual via MCP."),
475
+ ] = False,
476
+ chatgpt: Annotated[
477
+ bool,
478
+ typer.Option("--chatgpt", help="Configure ChatGPT to use Contextual via MCP."),
479
+ ] = False,
480
+ perplexity: Annotated[
481
+ bool,
482
+ typer.Option("--perplexity", help="Configure Perplexity to use Contextual via MCP."),
483
+ ] = False,
484
+ gemini_cli: Annotated[
485
+ bool,
486
+ typer.Option("--gemini-cli", help="Configure Gemini CLI to use Contextual via MCP."),
487
+ ] = False,
488
+ cursor: Annotated[
489
+ bool,
490
+ typer.Option("--cursor", help="Configure Cursor IDE to use Contextual via MCP."),
491
+ ] = False,
492
+ copilot: Annotated[
493
+ bool,
494
+ typer.Option("--copilot", help="Configure GitHub Copilot (VS Code) to use Contextual via MCP."),
495
+ ] = False,
496
+ windsurf: Annotated[
497
+ bool,
498
+ typer.Option("--windsurf", help="Configure Windsurf to use Contextual via MCP."),
499
+ ] = False,
500
+ codex: Annotated[
501
+ bool,
502
+ typer.Option("--codex", help="Configure Codex CLI to use Contextual via MCP."),
503
+ ] = False,
504
+ antigravity: Annotated[
505
+ bool,
506
+ typer.Option("--antigravity", help="Configure Antigravity to use Contextual via MCP."),
507
+ ] = False,
508
+ cline: Annotated[
509
+ bool,
510
+ typer.Option("--cline", help="Inject Contextual tool instructions into AGENTS.md for Cline."),
511
+ ] = False,
512
+ dry_run: Annotated[
513
+ bool,
514
+ typer.Option("--dry-run", help="Show what would be written without writing."),
515
+ ] = False,
516
+ ) -> None:
517
+ """Configure an MCP client to use Contextual."""
518
+ if typer_ctx.invoked_subcommand is not None:
519
+ return
520
+
521
+ flags = [claude, claude_code, chatgpt, perplexity, gemini_cli, cursor, copilot, windsurf, codex, antigravity, cline]
522
+ selected = sum(flags)
523
+
524
+ if selected == 0:
525
+ print_error(
526
+ "Specify a client to configure.",
527
+ hint=(
528
+ "contextual client --claude\n"
529
+ " contextual client --claude-code\n"
530
+ " contextual client --cursor\n"
531
+ " contextual client --copilot\n"
532
+ " contextual client --windsurf\n"
533
+ " contextual client --antigravity\n"
534
+ " contextual client --cline\n"
535
+ " contextual client --gemini-cli\n"
536
+ " contextual client --codex\n"
537
+ " contextual client --chatgpt\n"
538
+ " contextual client --perplexity"
539
+ ),
540
+ )
541
+ raise typer.Exit(1)
542
+
543
+ if selected > 1:
544
+ print_error(
545
+ "Only one client can be configured at a time.",
546
+ hint="Run the command separately for each client.",
547
+ )
548
+ raise typer.Exit(1)
549
+
550
+ uvx_path = _find_uvx()
551
+ if not uvx_path:
552
+ print_error(
553
+ "uvx not found on PATH.",
554
+ hint="Install uv: curl -LsSf https://astral.sh/uv/install.sh | sh",
555
+ )
556
+ raise typer.Exit(1)
557
+
558
+ paths = _config_paths()
559
+ workspace_root = Path.cwd().resolve()
560
+
561
+ agents_md = workspace_root / ".contextual" / "AGENTS.md"
562
+
563
+ if claude:
564
+ entry = _build_claude_mcp_entry(uvx_path, workspace_root)
565
+ _configure_client("Claude Desktop", paths["claude"], entry, workspace_root, dry_run)
566
+ print_agents_md_hint(agents_md, "Claude → Project → Custom Instructions")
567
+
568
+ elif claude_code:
569
+ _configure_client("Claude Code", workspace_root / ".mcp.json", _build_mcp_entry(uvx_path, workspace_root), workspace_root, dry_run)
570
+ if not dry_run:
571
+ result = write_type_b(workspace_root / "CLAUDE.md", marker_block("claude-code"))
572
+ print_md_injected(workspace_root / "CLAUDE.md", result)
573
+
574
+ elif chatgpt:
575
+ _display_manual_config(
576
+ "ChatGPT",
577
+ uvx_path,
578
+ workspace_root,
579
+ ui_steps=[
580
+ "Open ChatGPT Desktop and go to Settings",
581
+ "Navigate to Connectors → Advanced → Developer mode",
582
+ "Click 'Add server' and enter the name and command below",
583
+ "Toggle the server on and restart ChatGPT",
584
+ ],
585
+ )
586
+ print_agents_md_hint(agents_md, "ChatGPT → Project → Custom Instructions")
587
+
588
+ elif perplexity:
589
+ _display_manual_config(
590
+ "Perplexity",
591
+ uvx_path,
592
+ workspace_root,
593
+ ui_steps=[
594
+ "Open Perplexity Desktop (macOS) and go to Settings",
595
+ "Navigate to Connectors → Add Connector",
596
+ "Enter the server name and command below",
597
+ "Toggle MCP on under Sources on the homepage",
598
+ ],
599
+ )
600
+ print_agents_md_hint(agents_md, "Perplexity → Space → System Prompt")
601
+
602
+ elif gemini_cli:
603
+ _configure_client("Gemini CLI", paths["gemini-cli"], _build_mcp_entry(uvx_path, workspace_root), workspace_root, dry_run)
604
+
605
+ elif cursor:
606
+ _configure_client("Cursor", paths["cursor"], _build_mcp_entry(uvx_path, workspace_root), workspace_root, dry_run)
607
+ if not dry_run:
608
+ result = write_type_a(workspace_root / ".cursor" / "rules" / "contextual.mdc", cursor_file_content())
609
+ print_md_injected(workspace_root / ".cursor" / "rules" / "contextual.mdc", result)
610
+
611
+ elif copilot:
612
+ _configure_vscode_client(paths["copilot"], _build_vscode_mcp_entry(uvx_path, workspace_root), workspace_root, dry_run)
613
+ if not dry_run:
614
+ result = write_type_b(workspace_root / ".github" / "copilot-instructions.md", marker_block("copilot"))
615
+ print_md_injected(workspace_root / ".github" / "copilot-instructions.md", result)
616
+
617
+ elif windsurf:
618
+ _configure_client("Windsurf", paths["windsurf"], _build_mcp_entry(uvx_path, workspace_root), workspace_root, dry_run)
619
+ if not dry_run:
620
+ result = write_type_b(workspace_root / ".windsurfrules", marker_block("windsurf"))
621
+ print_md_injected(workspace_root / ".windsurfrules", result)
622
+
623
+ elif codex:
624
+ _configure_codex_client(paths["codex"], uvx_path, workspace_root, dry_run)
625
+
626
+ elif antigravity:
627
+ _configure_client("Antigravity", paths["antigravity"], _build_mcp_entry(uvx_path, workspace_root), workspace_root, dry_run)
628
+ if not dry_run:
629
+ result = write_type_a(workspace_root / ".agent" / "rules" / "contextual.md", antigravity_file_content())
630
+ print_md_injected(workspace_root / ".agent" / "rules" / "contextual.md", result)
631
+
632
+ elif cline:
633
+ # Cline: markdown-only — no separate MCP config file.
634
+ if not dry_run:
635
+ result = write_type_b(workspace_root / "AGENTS.md", marker_block("cline"))
636
+ print_md_injected(workspace_root / "AGENTS.md", result)
637
+ else:
638
+ from rich.syntax import Syntax
639
+ print_info("Dry run — would inject into: AGENTS.md")
640
+ console.print()
641
+ console.print(Syntax(marker_block("cline")[:300] + "\n...", "markdown", theme="ansi_dark", background_color="default"))
642
+ console.print()
643
+
644
+
645
+ @register_command(group="client", name="list")
646
+ def client_list() -> None:
647
+ """List configured MCP clients."""
648
+ from contextual.mcp.access import registry
649
+
650
+ try:
651
+ registry.load_from_disk()
652
+ except Exception:
653
+ pass
654
+
655
+ clients_list = []
656
+ for name, info in registry._by_name.items():
657
+ clients_list.append({
658
+ "name": name,
659
+ "access": info.get("access", "none"),
660
+ "workspaces": info.get("workspaces", ["*"])
661
+ })
662
+ print_client_list(clients_list)
663
+
664
+
665
+ @register_command(group="client", name="config")
666
+ def client_config() -> None:
667
+ """Open global mcp.json client access policy configuration in editor."""
668
+ from contextual.config.paths import MCP_CONFIG_FILE as MCP_JSON_PATH
669
+ if not MCP_JSON_PATH.exists():
670
+ try:
671
+ MCP_JSON_PATH.parent.mkdir(parents=True, exist_ok=True)
672
+ MCP_JSON_PATH.write_text("{\n \"clients\": {}\n}\n")
673
+ except Exception as e:
674
+ print_error(f"Could not create mcp.json config: {e}")
675
+ raise typer.Exit(1)
676
+ try:
677
+ open_in_editor(MCP_JSON_PATH)
678
+ except Exception as e:
679
+ print_error(f"Failed to open config file: {e}")
680
+ raise typer.Exit(1)