code-context-control 2.58.0__tar.gz → 2.59.0__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 (354) hide show
  1. {code_context_control-2.58.0 → code_context_control-2.59.0}/PKG-INFO +9 -1
  2. {code_context_control-2.58.0 → code_context_control-2.59.0}/README.md +8 -0
  3. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/c3.py +1 -1
  4. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/guide/tools.html +41 -0
  5. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_server.py +258 -17
  6. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/app.js +4 -2
  7. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/drill_views.js +4 -48
  8. code_context_control-2.59.0/cli/hub_ui/components/hub_credentials.js +516 -0
  9. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/topbar.js +1 -1
  10. {code_context_control-2.58.0 → code_context_control-2.59.0}/code_context_control.egg-info/PKG-INFO +9 -1
  11. {code_context_control-2.58.0 → code_context_control-2.59.0}/code_context_control.egg-info/SOURCES.txt +2 -0
  12. {code_context_control-2.58.0 → code_context_control-2.59.0}/pyproject.toml +1 -1
  13. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/credential_store.py +5 -0
  14. code_context_control-2.59.0/tests/test_hub_credentials_routes_write.py +364 -0
  15. {code_context_control-2.58.0 → code_context_control-2.59.0}/LICENSE +0 -0
  16. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/__init__.py +0 -0
  17. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/_hook_utils.py +0 -0
  18. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/commands/__init__.py +0 -0
  19. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/commands/common.py +0 -0
  20. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/commands/parser.py +0 -0
  21. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/docs.html +0 -0
  22. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/edits.html +0 -0
  23. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/guide/bitbucket.html +0 -0
  24. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/guide/getting-started.html +0 -0
  25. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/guide/index.html +0 -0
  26. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/guide/oracle.html +0 -0
  27. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/guide/shared.css +0 -0
  28. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/guide/workflow.html +0 -0
  29. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hook_artifact.py +0 -0
  30. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hook_auto_snapshot.py +0 -0
  31. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hook_c3_signal.py +0 -0
  32. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hook_c3read.py +0 -0
  33. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hook_dispatch.py +0 -0
  34. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hook_edit_ledger.py +0 -0
  35. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hook_edit_unlock.py +0 -0
  36. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hook_filter.py +0 -0
  37. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hook_ghost_files.py +0 -0
  38. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hook_pretool_enforce.py +0 -0
  39. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hook_prompt_recall.py +0 -0
  40. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hook_read.py +0 -0
  41. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hook_session_stats.py +0 -0
  42. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hook_terse_advisor.py +0 -0
  43. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub.html +0 -0
  44. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/add_project.js +0 -0
  45. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/config_editor.js +0 -0
  46. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/drill_artifacts.js +0 -0
  47. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/drill_health.js +0 -0
  48. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/drill_panel.js +0 -0
  49. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/drill_subprojects.js +0 -0
  50. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/drill_tasks.js +0 -0
  51. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/global_search.js +0 -0
  52. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/mcp_manager.js +0 -0
  53. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/modals.js +0 -0
  54. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/project_card.js +0 -0
  55. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/project_tree.js +0 -0
  56. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/session_drawer.js +0 -0
  57. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/settings_modal.js +0 -0
  58. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/sidebar.js +0 -0
  59. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/summary_bar.js +0 -0
  60. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/task_board.js +0 -0
  61. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/components/toasts.js +0 -0
  62. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui/state.js +0 -0
  63. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/hub_ui.html +0 -0
  64. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/mcp_proxy.py +0 -0
  65. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/mcp_server.py +0 -0
  66. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/progress.py +0 -0
  67. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/server.py +0 -0
  68. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/__init__.py +0 -0
  69. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/_helpers.py +0 -0
  70. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/agent.py +0 -0
  71. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/artifacts.py +0 -0
  72. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/bitbucket.py +0 -0
  73. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/compress.py +0 -0
  74. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/credentials.py +0 -0
  75. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/delegate.py +0 -0
  76. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/edit.py +0 -0
  77. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/edits.py +0 -0
  78. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/federate.py +0 -0
  79. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/filter.py +0 -0
  80. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/impact.py +0 -0
  81. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/jira.py +0 -0
  82. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/memory.py +0 -0
  83. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/project.py +0 -0
  84. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/read.py +0 -0
  85. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/search.py +0 -0
  86. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/session.py +0 -0
  87. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/shell.py +0 -0
  88. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/status.py +0 -0
  89. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/tasks.py +0 -0
  90. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/tools/validate.py +0 -0
  91. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/api.js +0 -0
  92. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/app.js +0 -0
  93. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/components/bitbucket.js +0 -0
  94. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/components/chat.js +0 -0
  95. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/components/credentials.js +0 -0
  96. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/components/dashboard.js +0 -0
  97. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/components/edits.js +0 -0
  98. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/components/instructions.js +0 -0
  99. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/components/jira.js +0 -0
  100. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/components/memory.js +0 -0
  101. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/components/sessions.js +0 -0
  102. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/components/settings.js +0 -0
  103. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/components/sidebar.js +0 -0
  104. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/components/tasks.js +0 -0
  105. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/icons.js +0 -0
  106. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/pm_shared.js +0 -0
  107. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/shared.js +0 -0
  108. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui/theme.js +0 -0
  109. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui.html +0 -0
  110. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui_legacy.html +0 -0
  111. {code_context_control-2.58.0 → code_context_control-2.59.0}/cli/ui_nano.html +0 -0
  112. {code_context_control-2.58.0 → code_context_control-2.59.0}/code_context_control.egg-info/dependency_links.txt +0 -0
  113. {code_context_control-2.58.0 → code_context_control-2.59.0}/code_context_control.egg-info/entry_points.txt +0 -0
  114. {code_context_control-2.58.0 → code_context_control-2.59.0}/code_context_control.egg-info/requires.txt +0 -0
  115. {code_context_control-2.58.0 → code_context_control-2.59.0}/code_context_control.egg-info/top_level.txt +0 -0
  116. {code_context_control-2.58.0 → code_context_control-2.59.0}/core/__init__.py +0 -0
  117. {code_context_control-2.58.0 → code_context_control-2.59.0}/core/config.py +0 -0
  118. {code_context_control-2.58.0 → code_context_control-2.59.0}/core/ide.py +0 -0
  119. {code_context_control-2.58.0 → code_context_control-2.59.0}/core/mcp_toml.py +0 -0
  120. {code_context_control-2.58.0 → code_context_control-2.59.0}/core/web_security.py +0 -0
  121. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/__init__.py +0 -0
  122. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/config.py +0 -0
  123. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/mcp_oracle.py +0 -0
  124. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/oracle_server.py +0 -0
  125. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/oracle_ui.html +0 -0
  126. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/__init__.py +0 -0
  127. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/activity_reporter.py +0 -0
  128. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/api_auth.py +0 -0
  129. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/c3_bridge.py +0 -0
  130. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/chat_engine.py +0 -0
  131. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/chat_store.py +0 -0
  132. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/cross_memory.py +0 -0
  133. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/federated_graph.py +0 -0
  134. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/health_checker.py +0 -0
  135. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/insight_engine.py +0 -0
  136. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/local_session.py +0 -0
  137. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/memory_reader.py +0 -0
  138. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/memory_writer.py +0 -0
  139. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/project_scanner.py +0 -0
  140. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/review_agent.py +0 -0
  141. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/tool_executor.py +0 -0
  142. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/services/tool_registry.py +0 -0
  143. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/activity.js +0 -0
  144. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/agents.js +0 -0
  145. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/app.js +0 -0
  146. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/busy.js +0 -0
  147. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/chat/conversations.js +0 -0
  148. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/chat/input.js +0 -0
  149. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/chat/markdown.js +0 -0
  150. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/chat/send.js +0 -0
  151. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/chat/stream_renderer.js +0 -0
  152. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/chat/toolbar.js +0 -0
  153. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/core.js +0 -0
  154. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/crossgraph.js +0 -0
  155. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/header.js +0 -0
  156. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/insights.js +0 -0
  157. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/projects.js +0 -0
  158. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/settings.js +0 -0
  159. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/suggestions.js +0 -0
  160. {code_context_control-2.58.0 → code_context_control-2.59.0}/oracle/ui/theme_tabs.js +0 -0
  161. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/__init__.py +0 -0
  162. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/activity_log.py +0 -0
  163. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/agent_base.py +0 -0
  164. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/agents.py +0 -0
  165. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/artifact_defs.py +0 -0
  166. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/artifact_store.py +0 -0
  167. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/auto_memory.py +0 -0
  168. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/bench/__init__.py +0 -0
  169. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/bench/external/__init__.py +0 -0
  170. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/bench/external/aider_polyglot.py +0 -0
  171. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/bench/external/swe_bench.py +0 -0
  172. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/benchmark_dashboard.py +0 -0
  173. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/bitbucket_client.py +0 -0
  174. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/bitbucket_credentials.py +0 -0
  175. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/circuit_breaker.py +0 -0
  176. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/claude_md.py +0 -0
  177. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/compressor.py +0 -0
  178. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/context_snapshot.py +0 -0
  179. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/conversation_store.py +0 -0
  180. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/doc_index.py +0 -0
  181. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/e2e_benchmark.py +0 -0
  182. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/e2e_evaluator.py +0 -0
  183. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/e2e_tasks.py +0 -0
  184. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/edit_ledger.py +0 -0
  185. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/embedding_index.py +0 -0
  186. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/error_reporting.py +0 -0
  187. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/file_memory.py +0 -0
  188. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/git_context.py +0 -0
  189. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/hub_service.py +0 -0
  190. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/indexer.py +0 -0
  191. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/jira_client.py +0 -0
  192. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/jira_cloud.py +0 -0
  193. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/jira_credentials.py +0 -0
  194. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/jira_data_center.py +0 -0
  195. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/jira_links.py +0 -0
  196. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/memory.py +0 -0
  197. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/memory_consolidator.py +0 -0
  198. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/memory_distiller.py +0 -0
  199. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/memory_graph.py +0 -0
  200. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/memory_grounder.py +0 -0
  201. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/memory_queue.py +0 -0
  202. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/memory_scorer.py +0 -0
  203. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/metrics.py +0 -0
  204. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/notifications.py +0 -0
  205. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/ollama_bridge.py +0 -0
  206. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/ollama_client.py +0 -0
  207. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/ollama_credentials.py +0 -0
  208. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/output_filter.py +0 -0
  209. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/parser.py +0 -0
  210. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/project_manager.py +0 -0
  211. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/project_runtime.py +0 -0
  212. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/protocol.py +0 -0
  213. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/proxy_state.py +0 -0
  214. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/retention.py +0 -0
  215. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/retrieval_broker.py +0 -0
  216. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/router.py +0 -0
  217. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/runtime.py +0 -0
  218. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/scanner.py +0 -0
  219. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/session_benchmark.py +0 -0
  220. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/session_manager.py +0 -0
  221. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/session_preloader.py +0 -0
  222. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/subprojects.py +0 -0
  223. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/task_store.py +0 -0
  224. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/telemetry.py +0 -0
  225. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/text_index.py +0 -0
  226. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/time_tracker.py +0 -0
  227. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/tool_classifier.py +0 -0
  228. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/transcript_index.py +0 -0
  229. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/validation_cache.py +0 -0
  230. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/vector_store.py +0 -0
  231. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/watcher.py +0 -0
  232. {code_context_control-2.58.0 → code_context_control-2.59.0}/services/win_subprocess.py +0 -0
  233. {code_context_control-2.58.0 → code_context_control-2.59.0}/setup.cfg +0 -0
  234. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_activity_reporter.py +0 -0
  235. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_aider_polyglot.py +0 -0
  236. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_antigravity_transition.py +0 -0
  237. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_artifact_store.py +0 -0
  238. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_artifact_tool.py +0 -0
  239. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_bitbucket_cli_smoke.py +0 -0
  240. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_bitbucket_client.py +0 -0
  241. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_bitbucket_config_fallback.py +0 -0
  242. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_bitbucket_credentials.py +0 -0
  243. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_bitbucket_tool.py +0 -0
  244. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_c3_bridge_project_artifacts.py +0 -0
  245. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_c3_shell.py +0 -0
  246. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_circuit_breaker.py +0 -0
  247. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_claude_md_merge.py +0 -0
  248. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_cli_smoke.py +0 -0
  249. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_compressor_large_file.py +0 -0
  250. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_credential_store.py +0 -0
  251. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_credentials_routes.py +0 -0
  252. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_credentials_tool.py +0 -0
  253. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_creds_cli_smoke.py +0 -0
  254. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_delegate_cascade.py +0 -0
  255. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_e2e_benchmark.py +0 -0
  256. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_edit_ledger_hook.py +0 -0
  257. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_edit_normalization.py +0 -0
  258. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_enforcement_flip.py +0 -0
  259. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_federated_graph.py +0 -0
  260. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_filter_backoff.py +0 -0
  261. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_ghost_files.py +0 -0
  262. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_ghost_generation.py +0 -0
  263. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_git_branch_awareness.py +0 -0
  264. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_hook_dispatch.py +0 -0
  265. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_hook_pretool_enforce.py +0 -0
  266. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_hook_prompt_recall.py +0 -0
  267. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_hook_smoke.py +0 -0
  268. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_hook_state.py +0 -0
  269. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_hook_utf8_stdio.py +0 -0
  270. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_hub_credentials_route.py +0 -0
  271. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_hub_inspect_api.py +0 -0
  272. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_hub_server_smoke.py +0 -0
  273. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_hub_subproject_links.py +0 -0
  274. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_init_scan.py +0 -0
  275. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_install_mcp_entrypoint.py +0 -0
  276. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_jira_cli_smoke.py +0 -0
  277. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_jira_client.py +0 -0
  278. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_jira_config_fallback.py +0 -0
  279. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_jira_credentials.py +0 -0
  280. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_jira_links.py +0 -0
  281. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_jira_routes.py +0 -0
  282. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_jira_tool.py +0 -0
  283. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_lazy_store_init.py +0 -0
  284. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_mcp_host_guard.py +0 -0
  285. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_mcp_memory_timeout.py +0 -0
  286. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_mcp_server_smoke.py +0 -0
  287. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_mcp_toml.py +0 -0
  288. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_memory_distiller.py +0 -0
  289. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_memory_graph_api.py +0 -0
  290. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_memory_queue.py +0 -0
  291. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_memory_system.py +0 -0
  292. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_notification_dedup.py +0 -0
  293. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_notification_discipline.py +0 -0
  294. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_ollama_credentials.py +0 -0
  295. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_oracle_api_auth.py +0 -0
  296. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_oracle_apikey_api.py +0 -0
  297. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_oracle_c3_bridge.py +0 -0
  298. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_oracle_chat_engine.py +0 -0
  299. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_oracle_discovery_api.py +0 -0
  300. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_oracle_local_auth.py +0 -0
  301. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_oracle_ollama_bridge.py +0 -0
  302. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_oracle_scanner_cache.py +0 -0
  303. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_oracle_security_fixes.py +0 -0
  304. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_oracle_subproject_awareness.py +0 -0
  305. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_oracle_ui_bundle.py +0 -0
  306. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_output_filter.py +0 -0
  307. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_permissions.py +0 -0
  308. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_pm_api.py +0 -0
  309. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_project_manager.py +0 -0
  310. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_project_manager_merge.py +0 -0
  311. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_project_tool.py +0 -0
  312. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_read_coercion.py +0 -0
  313. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_read_edit_parity.py +0 -0
  314. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_related_facts.py +0 -0
  315. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_response_boilerplate.py +0 -0
  316. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_retention.py +0 -0
  317. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_review_digest.py +0 -0
  318. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_service_durability.py +0 -0
  319. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_session_benchmark.py +0 -0
  320. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_session_budget.py +0 -0
  321. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_shell_creds.py +0 -0
  322. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_shell_robustness.py +0 -0
  323. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_subproject_exclusion.py +0 -0
  324. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_subproject_federation.py +0 -0
  325. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_subprojects.py +0 -0
  326. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_swe_bench.py +0 -0
  327. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_task_store.py +0 -0
  328. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_task_tool.py +0 -0
  329. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_time_tracker.py +0 -0
  330. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_token_telemetry.py +0 -0
  331. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_tool_registry.py +0 -0
  332. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_upgrade_and_version.py +0 -0
  333. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_validate.py +0 -0
  334. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_version_sync.py +0 -0
  335. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_web_security.py +0 -0
  336. {code_context_control-2.58.0 → code_context_control-2.59.0}/tests/test_windows_reliability.py +0 -0
  337. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/__init__.py +0 -0
  338. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/backend.py +0 -0
  339. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/main.py +0 -0
  340. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/screens/__init__.py +0 -0
  341. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/screens/benchmark_view.py +0 -0
  342. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/screens/claudemd_view.py +0 -0
  343. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/screens/compress_view.py +0 -0
  344. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/screens/index_view.py +0 -0
  345. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/screens/init_view.py +0 -0
  346. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/screens/mcp_view.py +0 -0
  347. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/screens/optimize_view.py +0 -0
  348. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/screens/pipe_view.py +0 -0
  349. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/screens/projects_view.py +0 -0
  350. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/screens/search_view.py +0 -0
  351. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/screens/session_view.py +0 -0
  352. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/screens/stats.py +0 -0
  353. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/screens/ui_view.py +0 -0
  354. {code_context_control-2.58.0 → code_context_control-2.59.0}/tui/theme.tcss +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.58.0
3
+ Version: 2.59.0
4
4
  Summary: Local code-intelligence layer for AI coding tools (Claude Code, Codex, Copilot, Cursor, Antigravity). Retrieve less, read less, edit safer — version the configs that shape your agent (CLAUDE.md, skills, hooks, MCP), and manage multi-project + sub-project hierarchies from a local hub.
5
5
  Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -358,6 +358,14 @@ global secrets (realm-atomic resolution, tested), cross-project shells run
358
358
  with credentials disabled, and the vault is hard-excluded from the Oracle
359
359
  Discovery API.
360
360
 
361
+ Since **v2.59.0** the Hub has a top-level **Credentials** view: manage the
362
+ global vault (`~/.c3`) and every registered project's entries from one place —
363
+ create / edit / delete / `.env` import / flag toggles, with shadowing shown
364
+ both ways ("shadows global" on project entries, "shadowed in N projects" on
365
+ globals). The write-only wire contract extends to the hub: values are
366
+ submitted inbound-only and **no hub route ever returns a stored value** (there
367
+ is no `reveal` on the hub at all).
368
+
361
369
  ### Jira — Cloud + Data Center (v2.56.0)
362
370
 
363
371
  `c3_jira` connects to Jira Cloud (REST v3, email + API token) or self-hosted
@@ -295,6 +295,14 @@ global secrets (realm-atomic resolution, tested), cross-project shells run
295
295
  with credentials disabled, and the vault is hard-excluded from the Oracle
296
296
  Discovery API.
297
297
 
298
+ Since **v2.59.0** the Hub has a top-level **Credentials** view: manage the
299
+ global vault (`~/.c3`) and every registered project's entries from one place —
300
+ create / edit / delete / `.env` import / flag toggles, with shadowing shown
301
+ both ways ("shadows global" on project entries, "shadowed in N projects" on
302
+ globals). The write-only wire contract extends to the hub: values are
303
+ submitted inbound-only and **no hub route ever returns a stored value** (there
304
+ is no `reveal` on the hub at all).
305
+
298
306
  ### Jira — Cloud + Data Center (v2.56.0)
299
307
 
300
308
  `c3_jira` connects to Jira Cloud (REST v3, email + API token) or self-hosted
@@ -92,7 +92,7 @@ console = Console() if HAS_RICH else None
92
92
  # Config
93
93
  CONFIG_DIR = ".c3"
94
94
  CONFIG_FILE = ".c3/config.json"
95
- __version__ = "2.58.0"
95
+ __version__ = "2.59.0"
96
96
 
97
97
 
98
98
  def _command_deps() -> CommandDeps:
@@ -125,6 +125,7 @@
125
125
  <div class="sidebar-label">SCM</div>
126
126
  <a href="#c3_task" class="sidebar-link"><span class="icon">✅</span> c3_task</a>
127
127
  <a href="#c3_bitbucket" class="sidebar-link"><span class="icon">🪣</span> c3_bitbucket</a>
128
+ <a href="#c3_credentials" class="sidebar-link"><span class="icon">🔐</span> c3_credentials</a>
128
129
  </div>
129
130
  <div class="sidebar-section">
130
131
  <div class="sidebar-label">Cross-project</div>
@@ -161,6 +162,7 @@
161
162
  <a href="#c3_task" class="toc-item">c3_task <span class="cat">PM</span></a>
162
163
  <a href="#c3_artifacts" class="toc-item">c3_artifacts <span class="cat">Config</span></a>
163
164
  <a href="#c3_bitbucket" class="toc-item">c3_bitbucket <span class="cat">SCM</span></a>
165
+ <a href="#c3_credentials" class="toc-item">c3_credentials <span class="cat">Secrets</span></a>
164
166
  <a href="#c3_project" class="toc-item">c3_project <span class="cat">Multi-project</span></a>
165
167
  </div>
166
168
  </div>
@@ -1239,6 +1241,45 @@ c3_bitbucket(action=<span class="str">'merge_pr'</span>, pr_id=<span class="num"
1239
1241
  <p class="tool-desc"><strong>Audit trail:</strong> mutating actions (<code>merge_pr</code>, <code>create_branch</code>, <code>delete_branch</code>, webhook writes, etc.) are appended to the C3 edit ledger so the local audit log covers platform-side state changes too.</p>
1240
1242
  </div>
1241
1243
  </div>
1244
+
1245
+ <!-- c3_credentials -->
1246
+ <div class="tool-card" id="c3_credentials">
1247
+ <div class="tool-card-header">
1248
+ <span class="tool-name">c3_credentials</span>
1249
+ <div class="tag-row">
1250
+ <span class="badge badge-purple">secrets</span>
1251
+ <span class="badge">v2.58.0</span>
1252
+ </div>
1253
+ <span class="tool-tagline">Credential vault — the agent uses secrets by name, never by value</span>
1254
+ </div>
1255
+ <div class="tool-card-body">
1256
+ <p class="tool-desc">A user-managed secret store, <strong>global</strong> (<code>~/.c3</code>, visible in every project) or <strong>per-project</strong> (<code>.c3</code>, shadows the same-named global). Values live in the OS keyring (service <code>c3-creds</code>; values over ~1KB in a Fernet-encrypted <code>.c3/secrets.enc</code> whose random master key is itself a keyring entry) — never in config files, and <em>never in the model's context</em>. <strong>Injection-first:</strong> the agent addresses secrets by name and C3 decodes them only at the subprocess boundary — <code>c3_shell(env_creds='A,B')</code> injects env vars, <code>{{cred:NAME}}</code> inside <code>cmd</code> expands server-side, and <code>inject</code>-flagged entries auto-inject into every shell run; echoed values are redacted back to <code>[cred:NAME]</code>. Resolution is <strong>realm-atomic</strong>: a project-registered name resolves in the project realm or not at all, so a hostile repo's committed config can never siphon your global values (tested).</p>
1257
+
1258
+ <h4>Actions</h4>
1259
+ <table class="params-table">
1260
+ <thead><tr><th>Action</th><th>Group</th><th>Description</th></tr></thead>
1261
+ <tbody>
1262
+ <tr><td class="param-name">list</td><td>Read</td><td class="param-desc">Merged registry (project shadows global) — names, scope, type, flags, usage. Never values</td></tr>
1263
+ <tr><td class="param-name">describe</td><td>Read</td><td class="param-desc">Metadata + storage + live fingerprint (first 8 hex of SHA-256, computed on demand, never persisted)</td></tr>
1264
+ <tr><td class="param-name">check</td><td>Read</td><td class="param-desc">Resolvability probe — does the stored value decode?</td></tr>
1265
+ <tr><td class="param-name">set</td><td>Write</td><td class="param-desc">Create/replace an entry. <code>agent_readable</code> may be set at creation only — raising it on an existing entry is user-only</td></tr>
1266
+ <tr><td class="param-name">delete</td><td>Write</td><td class="param-desc">Remove value + registry entry (owning scope inferred when omitted)</td></tr>
1267
+ <tr><td class="param-name">reveal</td><td>Gated</td><td class="param-desc">The only value-returning action — refused unless the user enabled <code>agent_readable</code> on that entry</td></tr>
1268
+ </tbody>
1269
+ </table>
1270
+
1271
+ <h4>Examples</h4>
1272
+ <pre><code><span class="com"># What can I use? (names and metadata only)</span>
1273
+ c3_credentials(action=<span class="str">'list'</span>)
1274
+ c3_credentials(action=<span class="str">'check'</span>, name=<span class="str">'NPM_TOKEN'</span>)
1275
+
1276
+ <span class="com"># USE a secret without ever seeing it</span>
1277
+ c3_shell(cmd=<span class="str">'npm publish'</span>, env_creds=<span class="str">'NPM_TOKEN'</span>)
1278
+ c3_shell(cmd=<span class="str">'curl -H "Authorization: Bearer {{cred:API_KEY}}" https://api.example.com'</span>)</code></pre>
1279
+
1280
+ <p class="tool-desc"><strong>Surfaces:</strong> the <code>c3 creds</code> CLI (<code>set</code> / <code>get</code> / <code>list</code> / <code>rm</code> / <code>import .env</code>, <code>--global</code> for the shared scope), a per-project dashboard <strong>Credentials</strong> tab, and — since v2.59.0 — a top-level <strong>Credentials</strong> view in the Hub: a Global-vault manager plus a per-project browser with shadowing shown both ways ("shadows global" on project entries, "shadowed in N projects" on globals); the Hub's per-project drill panel manages too. No HTTP route ever returns a stored value (write-only wire contract, endpoint-sweep tested), every mutation is ledger-logged by name, the vault is hard-excluded from the Oracle Discovery API, and cross-project (<code>c3_project</code>) shells run with credentials disabled.</p>
1281
+ </div>
1282
+ </div>
1242
1283
  </div>
1243
1284
 
1244
1285
  <!-- ═══════════════════════════════════════ CROSS-PROJECT ═════ -->
@@ -333,6 +333,7 @@ _HUB_JS_FILES = [
333
333
  "hub_ui/components/session_drawer.js",
334
334
  "hub_ui/components/drill_panel.js",
335
335
  "hub_ui/components/drill_views.js",
336
+ "hub_ui/components/hub_credentials.js",
336
337
  "hub_ui/components/drill_subprojects.js",
337
338
  "hub_ui/components/drill_health.js",
338
339
  "hub_ui/components/drill_tasks.js",
@@ -437,8 +438,8 @@ def api_hub_config_set():
437
438
  cfg["projects_view"] = projects_view
438
439
  if "main_view" in data:
439
440
  main_view = str(data["main_view"]).strip().lower()
440
- if main_view not in {"projects", "board"}:
441
- return jsonify({"error": "main_view must be 'projects' or 'board'"}), 400
441
+ if main_view not in {"projects", "board", "creds"}:
442
+ return jsonify({"error": "main_view must be 'projects', 'board' or 'creds'"}), 400
442
443
  cfg["main_view"] = main_view
443
444
  if "oracle_url" in data:
444
445
  cfg["oracle_url"] = str(data["oracle_url"]).strip()
@@ -1773,12 +1774,100 @@ def api_projects_config_get():
1773
1774
  "defaults": {k: _config_defaults(k) for k in _CONFIG_READ_SECTIONS}})
1774
1775
 
1775
1776
 
1777
+ # ── Credentials (hub) ────────────────────────────────────────────────────────
1778
+ # Write-only wire contract, hub edition: values go IN over POST and are never
1779
+ # returned by any hub route (no reveal exists here). `credentials` stays out of
1780
+ # _CONFIG_WRITE_SECTIONS — these dedicated routes are the only hub write path.
1781
+
1782
+ _CRED_PUBLIC_FIELDS = ("scope", "type", "value_len", "env_var", "inject",
1783
+ "agent_readable", "description", "storage", "created",
1784
+ "updated")
1785
+
1786
+
1787
+ def _cred_entry_public(name, entry, *, usage=None, shadows_global=None):
1788
+ """Explicit allowlist serializer — structurally cannot emit a value."""
1789
+ rec = {"name": name}
1790
+ for key in _CRED_PUBLIC_FIELDS:
1791
+ rec[key] = entry.get(key, "")
1792
+ rec["value_len"] = entry.get("value_len", 0)
1793
+ rec["inject"] = bool(entry.get("inject"))
1794
+ rec["agent_readable"] = bool(entry.get("agent_readable"))
1795
+ if usage is not None:
1796
+ rec["last_used"] = (usage.get(name) or {}).get("last_used", "")
1797
+ rec["use_count"] = (usage.get(name) or {}).get("use_count", 0)
1798
+ if shadows_global is not None:
1799
+ rec["shadows_global"] = bool(shadows_global)
1800
+ return rec
1801
+
1802
+
1803
+ def _resolve_cred_target(path: str, scope: str, *, mutation: bool):
1804
+ """Resolve a credentials request target to (project, store_path, error).
1805
+
1806
+ scope='global' with no path targets the shared vault (~/.c3) directly;
1807
+ project scope requires a registered path. Mutations on a path without a
1808
+ .c3/ dir get 409 needs_init so the hub can't scatter .c3 dirs around."""
1809
+ from services import credential_store as cred_store
1810
+ if scope not in ("project", "global"):
1811
+ return None, "", (jsonify({"error": "scope must be 'project' or 'global'"}), 400)
1812
+ if not path:
1813
+ if scope == "project":
1814
+ return None, "", (jsonify({"error": "path is required for project scope"}), 400)
1815
+ home = cred_store.global_base()
1816
+ if home is None:
1817
+ return None, "", (jsonify({"error": "global scope unresolvable (no home dir)"}), 500)
1818
+ return None, str(home), None
1819
+ try:
1820
+ resolved = _resolve_project_path(path)
1821
+ except ValueError as e:
1822
+ return None, "", (jsonify({"error": str(e)}), 404)
1823
+ if mutation and not (resolved / ".c3").is_dir():
1824
+ return None, "", (jsonify({"error": "not initialized", "needs_init": True}), 409)
1825
+ return resolved, str(resolved), None
1826
+
1827
+
1828
+ def _hub_cred_audit(action: str, name: str, scope: str, project) -> None:
1829
+ """Names only — never values. Failure-safe. Project mutations audit to the
1830
+ target project's ActivityLog + EditLedger; global-scope mutations also land
1831
+ in ~/.c3/activity_log.jsonl so the shared vault keeps its own trail."""
1832
+ if project is not None:
1833
+ try:
1834
+ from services.activity_log import ActivityLog
1835
+ ActivityLog(str(project)).log("cred_action", {
1836
+ "kind": "creds", "action": action, "name": name,
1837
+ "scope": scope, "via": "hub",
1838
+ })
1839
+ except Exception:
1840
+ pass
1841
+ try:
1842
+ from services.edit_ledger import EditLedger
1843
+ EditLedger(str(project)).log_edit(
1844
+ file=f"cred://{name}", change_type=f"cred_{action}",
1845
+ summary=f"{action} {name} ({scope}) via Hub",
1846
+ tags=["creds", action],
1847
+ detail={"kind": "creds", "action": action, "name": name,
1848
+ "scope": scope},
1849
+ )
1850
+ except Exception:
1851
+ pass
1852
+ if scope == "global" or project is None:
1853
+ try:
1854
+ from services import credential_store as cred_store
1855
+ from services.activity_log import ActivityLog
1856
+ home = cred_store.global_base()
1857
+ if home is not None:
1858
+ ActivityLog(str(home)).log("cred_action", {
1859
+ "kind": "creds", "action": action, "name": name,
1860
+ "scope": scope, "via": "hub",
1861
+ })
1862
+ except Exception:
1863
+ pass
1864
+
1865
+
1776
1866
  @app.route("/api/projects/credentials", methods=["GET"])
1777
1867
  def api_projects_credentials():
1778
- """Read-only masked credential registry for a project (global entries +
1779
- project shadows). Values never transit the hub — the explicit field
1780
- allowlist below returns metadata only; management lives in the project UI
1781
- (`credentials` is deliberately absent from _CONFIG_WRITE_SECTIONS)."""
1868
+ """Masked credential registry for a project (global entries + project
1869
+ shadows). Values never transit the hub outbound — the allowlist serializer
1870
+ returns metadata, usage and shadow info only."""
1782
1871
  path = (request.args.get("path") or "").strip()
1783
1872
  if not path:
1784
1873
  return jsonify({"error": "path is required"}), 400
@@ -1787,22 +1876,174 @@ def api_projects_credentials():
1787
1876
  except ValueError as e:
1788
1877
  return jsonify({"error": str(e)}), 404
1789
1878
  from services import credential_store as cred_store
1879
+ usage = cred_store.read_usage_state(str(resolved))
1880
+ home = cred_store.global_base()
1881
+ global_names = set(cred_store.list_entries(str(home))) if home else set()
1790
1882
  entries = []
1791
1883
  for name, entry in cred_store.list_entries(str(resolved)).items():
1792
- entries.append({
1793
- "name": name,
1794
- "scope": entry.get("scope", ""),
1795
- "type": entry.get("type", "token"),
1796
- "value_len": entry.get("value_len", 0),
1797
- "env_var": entry.get("env_var", ""),
1798
- "inject": bool(entry.get("inject")),
1799
- "agent_readable": bool(entry.get("agent_readable")),
1800
- "description": entry.get("description", ""),
1801
- "updated": entry.get("updated", ""),
1802
- })
1884
+ entries.append(_cred_entry_public(
1885
+ name, entry, usage=usage,
1886
+ shadows_global=(entry.get("scope") == "project"
1887
+ and name in global_names)))
1803
1888
  return jsonify({"path": str(resolved), "entries": entries})
1804
1889
 
1805
1890
 
1891
+ @app.route("/api/projects/credentials", methods=["POST"])
1892
+ def api_projects_credentials_set():
1893
+ """Create/update an entry from the hub. `value` optional — metadata-only
1894
+ update when absent, touching ONLY the keys present in the payload; a
1895
+ submitted value is stored and never echoed back. scope='global' with no
1896
+ `path` targets the shared vault directly."""
1897
+ from services import credential_store as cred_store
1898
+ data = request.get_json(force=True) or {}
1899
+ name = str(data.get("name") or "").strip()
1900
+ scope = str(data.get("scope") or "project").strip().lower()
1901
+ project, store_path, err = _resolve_cred_target(
1902
+ str(data.get("path") or "").strip(), scope, mutation=True)
1903
+ if err:
1904
+ return err
1905
+ value = str(data.get("value") or "")
1906
+ ctype = str(data.get("type") or data.get("ctype") or "token")
1907
+ try:
1908
+ if value:
1909
+ entry = cred_store.set_credential(
1910
+ name, value, scope=scope, project_path=store_path, ctype=ctype,
1911
+ description=str(data.get("description") or ""),
1912
+ env_var=str(data.get("env_var") or ""),
1913
+ agent_readable=bool(data.get("agent_readable")),
1914
+ inject=bool(data.get("inject")))
1915
+ else:
1916
+ # Metadata-only update: touch ONLY the keys present in the payload
1917
+ # so a single-field toggle can't clobber the others.
1918
+ fields = {}
1919
+ for key in ("description", "env_var"):
1920
+ if key in data:
1921
+ fields[key] = str(data[key] or "")
1922
+ for key in ("agent_readable", "inject"):
1923
+ if key in data:
1924
+ fields[key] = bool(data[key])
1925
+ if "type" in data or "ctype" in data:
1926
+ fields["type"] = ctype
1927
+ entry = cred_store.update_metadata(
1928
+ name, scope=scope, project_path=store_path, **fields)
1929
+ except cred_store.CredentialError as exc:
1930
+ return jsonify({"error": str(exc)}), 400
1931
+ except RuntimeError as exc:
1932
+ return jsonify({"error": str(exc)}), 500
1933
+ _hub_cred_audit("set" if value else "update", name, scope, project)
1934
+ return jsonify({"entry": _cred_entry_public(name, {**entry, "scope": scope})})
1935
+
1936
+
1937
+ @app.route("/api/projects/credentials/import", methods=["POST"])
1938
+ def api_projects_credentials_import():
1939
+ """Import KEY=VALUE lines (.env paste). Values are stored, never echoed."""
1940
+ from services import credential_store as cred_store
1941
+ data = request.get_json(force=True) or {}
1942
+ scope = str(data.get("scope") or "project").strip().lower()
1943
+ project, store_path, err = _resolve_cred_target(
1944
+ str(data.get("path") or "").strip(), scope, mutation=True)
1945
+ if err:
1946
+ return err
1947
+ try:
1948
+ result = cred_store.import_env(
1949
+ str(data.get("text") or ""), scope=scope, project_path=store_path,
1950
+ overwrite=bool(data.get("overwrite")))
1951
+ except cred_store.CredentialError as exc:
1952
+ return jsonify({"error": str(exc)}), 400
1953
+ except RuntimeError as exc:
1954
+ return jsonify({"error": str(exc)}), 500
1955
+ for created in result["created"]:
1956
+ _hub_cred_audit("set", created, scope, project)
1957
+ return jsonify(result)
1958
+
1959
+
1960
+ @app.route("/api/projects/credentials/<name>", methods=["DELETE"])
1961
+ def api_projects_credentials_delete(name):
1962
+ """Delete an entry (value + registry). Scope inferred from the owning
1963
+ realm when omitted."""
1964
+ from services import credential_store as cred_store
1965
+ scope = str(request.args.get("scope") or "").strip().lower()
1966
+ path = str(request.args.get("path") or "").strip()
1967
+ project, store_path, err = _resolve_cred_target(
1968
+ path, scope or ("project" if path else "global"), mutation=True)
1969
+ if err:
1970
+ return err
1971
+ if not scope:
1972
+ entry = cred_store.get_entry(name, project_path=store_path)
1973
+ scope = (entry.get("scope") or "project") if entry else \
1974
+ ("project" if path else "global")
1975
+ try:
1976
+ removed = cred_store.delete_credential(
1977
+ name, scope=scope, project_path=store_path)
1978
+ except cred_store.CredentialError as exc:
1979
+ return jsonify({"error": str(exc)}), 400
1980
+ if removed:
1981
+ _hub_cred_audit("delete", name, scope, project)
1982
+ return jsonify({"removed": bool(removed), "scope": scope})
1983
+
1984
+
1985
+ @app.route("/api/projects/credentials/<name>/check", methods=["POST"])
1986
+ def api_projects_credentials_check(name):
1987
+ """Resolvability probe — returns a fingerprint, never the value."""
1988
+ from services import credential_store as cred_store
1989
+ data = request.get_json(silent=True) or {}
1990
+ path = str(data.get("path") or "").strip()
1991
+ project, store_path, err = _resolve_cred_target(
1992
+ path, "project" if path else "global", mutation=False)
1993
+ if err:
1994
+ return err
1995
+ entry = cred_store.get_entry(name, project_path=store_path)
1996
+ if not entry:
1997
+ return jsonify({"error": f"no credential named '{name}'"}), 404
1998
+ return jsonify({
1999
+ "name": name,
2000
+ "scope": entry["scope"],
2001
+ "storage": entry.get("storage", "keyring"),
2002
+ "resolvable": cred_store.get_value(name, project_path=store_path) is not None,
2003
+ "fingerprint": cred_store.fingerprint(name, project_path=store_path),
2004
+ })
2005
+
2006
+
2007
+ @app.route("/api/hub/credentials/overview", methods=["GET"])
2008
+ def api_hub_credentials_overview():
2009
+ """Cross-project credential inventory: the global vault plus each
2010
+ registered project's project-scoped entries, with shadow info both ways.
2011
+ Metadata only — the allowlist serializer structurally cannot emit a value."""
2012
+ from services import credential_store as cred_store
2013
+ home = cred_store.global_base()
2014
+ global_entries = cred_store.list_entries(str(home)) if home else {}
2015
+ shadowed_in = {name: [] for name in global_entries}
2016
+ projects_out = []
2017
+ for p in _pm().list_projects():
2018
+ ppath = str(p.get("path") or "")
2019
+ row = {"name": p.get("name") or "", "path": ppath,
2020
+ "initialized": True, "error": None, "entries": []}
2021
+ try:
2022
+ if not (Path(ppath) / ".c3").is_dir():
2023
+ row["initialized"] = False
2024
+ else:
2025
+ usage = cred_store.read_usage_state(ppath)
2026
+ for name, entry in cred_store.list_entries(ppath).items():
2027
+ if entry.get("scope") != "project":
2028
+ continue
2029
+ row["entries"].append(_cred_entry_public(
2030
+ name, entry, usage=usage,
2031
+ shadows_global=name in global_entries))
2032
+ if name in shadowed_in:
2033
+ shadowed_in[name].append(
2034
+ {"name": row["name"], "path": ppath})
2035
+ except Exception as e: # per-row isolation, like /api/search/global
2036
+ row["error"] = str(e)
2037
+ projects_out.append(row)
2038
+ global_usage = cred_store.read_usage_state(str(home)) if home else {}
2039
+ global_out = [
2040
+ {**_cred_entry_public(name, entry, usage=global_usage),
2041
+ "shadowed_in": shadowed_in.get(name, [])}
2042
+ for name, entry in global_entries.items()
2043
+ ]
2044
+ return jsonify({"global": {"entries": global_out}, "projects": projects_out})
2045
+
2046
+
1806
2047
  @app.route("/api/projects/config", methods=["PUT"])
1807
2048
  def api_projects_config_put():
1808
2049
  """Whitelisted section write: deep-merge, atomic replace, audited on the target."""
@@ -17,7 +17,7 @@ function App() {
17
17
  const [version, setVersion] = useState('');
18
18
  const [projects, setProjects] = useState([]);
19
19
  const [loaded, setLoaded] = useState(false);
20
- const [mainView, setMainView] = useState('projects'); // projects | board
20
+ const [mainView, setMainView] = useState('projects'); // projects | board | creds
21
21
  const [filter, setFilter] = useState('all'); // all | active | idle | tag:<x>
22
22
  const [search, setSearch] = useState('');
23
23
  const [view, setView] = useState('list'); // list | grid
@@ -44,7 +44,7 @@ function App() {
44
44
  if (cfg.projects_view === 'grid') setView('grid');
45
45
  if (cfg.sidebar_collapsed != null) setSidebarCollapsed(!!cfg.sidebar_collapsed);
46
46
  if (cfg.sidebar_group) setFilter(cfg.sidebar_group);
47
- if (cfg.main_view === 'board') setMainView('board');
47
+ if (cfg.main_view === 'board' || cfg.main_view === 'creds') setMainView(cfg.main_view);
48
48
  } catch { }
49
49
  try { const v = await api.get('/api/version'); setVersion(v.c3_version || ''); } catch { }
50
50
  }, []);
@@ -112,6 +112,8 @@ function App() {
112
112
  }}>
113
113
  {mainView === 'board' ? (
114
114
  <TaskBoard projects={projects} onOpenDrill={openDrill} />
115
+ ) : mainView === 'creds' ? (
116
+ <HubCredentials projects={projects} onOpenDrill={openDrill} />
115
117
  ) : (
116
118
  <React.Fragment>
117
119
  <SummaryBar projects={projects} search={search} setSearch={setSearch}
@@ -282,55 +282,11 @@ function DrillLedger({ project }) {
282
282
  );
283
283
  }
284
284
 
285
- // ── Sessions ───────────────────────────────────────────────────
286
- // Read-only: the hub never transits credential values — this lists metadata
287
- // from GET /api/projects/credentials; management lives in the project UI.
285
+ // ── Credentials ────────────────────────────────────────────────
286
+ // Full management via the shared CredsManager (hub_credentials.js).
287
+ // Write-only wire: values are submitted inbound-only and never returned.
288
288
  function DrillCredentials({ project }) {
289
- const [data, setData] = useState(null);
290
- const [err, setErr] = useState(null);
291
-
292
- const load = async () => {
293
- setErr(null);
294
- try {
295
- const d = await api.get('/api/projects/credentials?path=' + encodeURIComponent(project.path));
296
- setData(d);
297
- } catch (e) { setErr(e.message); }
298
- };
299
- useEffect(() => { setData(null); load(); }, [project.path]);
300
-
301
- if (err) return <DrillMsg text={'Failed to load credentials: ' + err} color={T.error} />;
302
- if (!data) return <DrillMsg text="Loading credentials…" />;
303
-
304
- const entries = data.entries || [];
305
- return (
306
- <div className="fade-up">
307
- <div style={{ fontSize: 11, color: T.textMuted, lineHeight: 1.5, marginBottom: 12 }}>
308
- Read-only view — values never transit the hub. Manage entries (and the
309
- agent_readable / inject flags) in this project's own UI Credentials tab
310
- or via <span className="mono">c3 creds</span>.
311
- </div>
312
- {entries.length === 0 ? (
313
- <DrillMsg text="No credentials registered for this project." />
314
- ) : entries.map((entry) => (
315
- <div key={entry.scope + '|' + entry.name} style={{
316
- display: 'flex', alignItems: 'center', gap: 8, padding: '8px 0',
317
- borderBottom: `1px solid ${T.border}`, fontSize: 12,
318
- }}>
319
- <I name="lock" size={12} color={T.textMuted} />
320
- <span className="mono" style={{ fontWeight: 600, color: T.text }}>{entry.name}</span>
321
- <Badge color={entry.scope === 'global' ? T.accent : T.ok}>{entry.scope}</Badge>
322
- <Badge color={T.textMuted}>{entry.type}</Badge>
323
- <span className="mono" style={{ color: T.textDim }}>len={entry.value_len}</span>
324
- {!!entry.inject && <Badge color={T.warn}>inject</Badge>}
325
- {!!entry.agent_readable && <Badge color={T.error}>agent_readable</Badge>}
326
- <span style={{
327
- flex: 1, color: T.textMuted, fontSize: 11, textAlign: 'right',
328
- whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
329
- }}>{entry.description}</span>
330
- </div>
331
- ))}
332
- </div>
333
- );
289
+ return <CredsManager path={project.path} projectName={project.name} />;
334
290
  }
335
291
 
336
292
  function DrillSessions({ project }) {