datacharter 0.3.4__tar.gz → 0.4.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 (255) hide show
  1. {datacharter-0.3.4 → datacharter-0.4.0}/CHANGELOG.md +24 -0
  2. {datacharter-0.3.4 → datacharter-0.4.0}/PKG-INFO +1 -1
  3. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/__init__.py +1 -1
  4. datacharter-0.4.0/src/datacharter/agent/claude_code.py +201 -0
  5. datacharter-0.4.0/src/datacharter/agent/remote_tools.py +24 -0
  6. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/agent/tools.py +48 -8
  7. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/cli.py +19 -25
  8. datacharter-0.4.0/src/datacharter/contracts/access.py +31 -0
  9. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/contracts/loader.py +1 -0
  10. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/contracts/pii.py +26 -1
  11. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/contracts/writer.py +25 -0
  12. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/demo.py +3 -0
  13. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/engine/session.py +8 -0
  14. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/models.py +2 -0
  15. datacharter-0.4.0/src/datacharter/server/agent_backend.py +28 -0
  16. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/app.py +175 -15
  17. datacharter-0.3.4/src/datacharter/server/static/assets/index-B_FrSWR_.js → datacharter-0.4.0/src/datacharter/server/static/assets/index-D8N7a0N6.js +42 -42
  18. datacharter-0.3.4/src/datacharter/server/static/assets/index-DaS_YQZd.css → datacharter-0.4.0/src/datacharter/server/static/assets/index-FN6N1fIA.css +1 -1
  19. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/index.html +2 -2
  20. datacharter-0.4.0/tests/agent/test_claude_code.py +64 -0
  21. datacharter-0.4.0/tests/agent/test_remote_tools.py +28 -0
  22. datacharter-0.4.0/tests/agent/test_tools_masking.py +92 -0
  23. datacharter-0.4.0/tests/contracts/test_access.py +31 -0
  24. datacharter-0.4.0/tests/contracts/test_detect_pii.py +13 -0
  25. {datacharter-0.3.4 → datacharter-0.4.0}/tests/contracts/test_writer.py +31 -1
  26. datacharter-0.4.0/tests/server/test_agent_access_api.py +80 -0
  27. datacharter-0.4.0/tests/server/test_claude_agent_api.py +84 -0
  28. datacharter-0.4.0/tests/server/test_cold_start.py +155 -0
  29. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/App.tsx +40 -8
  30. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/api.ts +26 -0
  31. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/components/ChatPanel.tsx +18 -6
  32. datacharter-0.4.0/ui/src/components/ClaudeCodeConnect.test.tsx +50 -0
  33. datacharter-0.4.0/ui/src/components/ClaudeCodeConnect.tsx +52 -0
  34. datacharter-0.4.0/ui/src/components/SourceTree.test.tsx +57 -0
  35. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/components/SourceTree.tsx +82 -6
  36. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/onboarding.test.ts +9 -1
  37. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/onboarding.ts +7 -0
  38. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/styles.css +36 -0
  39. datacharter-0.3.4/tests/server/test_cold_start.py +0 -85
  40. {datacharter-0.3.4 → datacharter-0.4.0}/.github/workflows/banned-terms.yml +0 -0
  41. {datacharter-0.3.4 → datacharter-0.4.0}/.github/workflows/release.yml +0 -0
  42. {datacharter-0.3.4 → datacharter-0.4.0}/.github/workflows/test.yml +0 -0
  43. {datacharter-0.3.4 → datacharter-0.4.0}/.gitignore +0 -0
  44. {datacharter-0.3.4 → datacharter-0.4.0}/CODE_OF_CONDUCT.md +0 -0
  45. {datacharter-0.3.4 → datacharter-0.4.0}/CONTRIBUTING.md +0 -0
  46. {datacharter-0.3.4 → datacharter-0.4.0}/LICENSE +0 -0
  47. {datacharter-0.3.4 → datacharter-0.4.0}/README.md +0 -0
  48. {datacharter-0.3.4 → datacharter-0.4.0}/SECURITY.md +0 -0
  49. {datacharter-0.3.4 → datacharter-0.4.0}/THIRD-PARTY-NOTICES.md +0 -0
  50. {datacharter-0.3.4 → datacharter-0.4.0}/TRADEMARKS.md +0 -0
  51. {datacharter-0.3.4 → datacharter-0.4.0}/brand/README.md +0 -0
  52. {datacharter-0.3.4 → datacharter-0.4.0}/brand/demo.gif +0 -0
  53. {datacharter-0.3.4 → datacharter-0.4.0}/brand/hero.png +0 -0
  54. {datacharter-0.3.4 → datacharter-0.4.0}/brand/mark.svg +0 -0
  55. {datacharter-0.3.4 → datacharter-0.4.0}/brand/screenshot-m4-agent.png +0 -0
  56. {datacharter-0.3.4 → datacharter-0.4.0}/brand/screenshot-m5-complete.png +0 -0
  57. {datacharter-0.3.4 → datacharter-0.4.0}/brand/screenshot-m5-core.png +0 -0
  58. {datacharter-0.3.4 → datacharter-0.4.0}/brand/screenshot-m5-features.png +0 -0
  59. {datacharter-0.3.4 → datacharter-0.4.0}/compose.yaml +0 -0
  60. {datacharter-0.3.4 → datacharter-0.4.0}/docs/_config.yml +0 -0
  61. {datacharter-0.3.4 → datacharter-0.4.0}/docs/about.md +0 -0
  62. {datacharter-0.3.4 → datacharter-0.4.0}/docs/agent.md +0 -0
  63. {datacharter-0.3.4 → datacharter-0.4.0}/docs/assets/add-source.gif +0 -0
  64. {datacharter-0.3.4 → datacharter-0.4.0}/docs/assets/drag-drop.gif +0 -0
  65. {datacharter-0.3.4 → datacharter-0.4.0}/docs/assets/hero.png +0 -0
  66. {datacharter-0.3.4 → datacharter-0.4.0}/docs/assets/screenshot.png +0 -0
  67. {datacharter-0.3.4 → datacharter-0.4.0}/docs/assets/snapshot.gif +0 -0
  68. {datacharter-0.3.4 → datacharter-0.4.0}/docs/charter-yaml.md +0 -0
  69. {datacharter-0.3.4 → datacharter-0.4.0}/docs/cli.md +0 -0
  70. {datacharter-0.3.4 → datacharter-0.4.0}/docs/faq.md +0 -0
  71. {datacharter-0.3.4 → datacharter-0.4.0}/docs/index.html +0 -0
  72. {datacharter-0.3.4 → datacharter-0.4.0}/docs/mcp.md +0 -0
  73. {datacharter-0.3.4 → datacharter-0.4.0}/docs/quickstart.md +0 -0
  74. {datacharter-0.3.4 → datacharter-0.4.0}/docs/security.md +0 -0
  75. {datacharter-0.3.4 → datacharter-0.4.0}/docs/sources.md +0 -0
  76. {datacharter-0.3.4 → datacharter-0.4.0}/docs/workspace.md +0 -0
  77. {datacharter-0.3.4 → datacharter-0.4.0}/pyproject.toml +0 -0
  78. {datacharter-0.3.4 → datacharter-0.4.0}/scripts/build_ui.sh +0 -0
  79. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/agent/__init__.py +0 -0
  80. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/agent/cache.py +0 -0
  81. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/agent/llm.py +0 -0
  82. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/agent/loop.py +0 -0
  83. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/contracts/__init__.py +0 -0
  84. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/contracts/metrics.py +0 -0
  85. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/contracts/resolve.py +0 -0
  86. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/contracts/secrets.py +0 -0
  87. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/engine/__init__.py +0 -0
  88. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/engine/aggregate.py +0 -0
  89. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/engine/guard.py +0 -0
  90. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/engine/provenance.py +0 -0
  91. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/engine/pushdown.py +0 -0
  92. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/engine/scrub.py +0 -0
  93. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/engine/snowflake.py +0 -0
  94. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/engine/sources.py +0 -0
  95. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/engine/statekey.py +0 -0
  96. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/mcp/__init__.py +0 -0
  97. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/mcp/server.py +0 -0
  98. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/__init__.py +0 -0
  99. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/llm_admin.py +0 -0
  100. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/security.py +0 -0
  101. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/source_admin.py +0 -0
  102. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/abap-DLDM7-KI.js +0 -0
  103. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/apex-DNDY2TF8.js +0 -0
  104. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/azcli-Y6nb8tq_.js +0 -0
  105. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/bat-BwHxbl9M.js +0 -0
  106. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/bicep-CFznDFnq.js +0 -0
  107. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/cameligo-Bf6VGUru.js +0 -0
  108. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/clojure-Dnu-v4kV.js +0 -0
  109. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/codicon-Brq4_Ui5.ttf +0 -0
  110. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/coffee-Bd8akH9Z.js +0 -0
  111. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/cpp-BbWJElDN.js +0 -0
  112. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/csharp-Co3qMtFm.js +0 -0
  113. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/csp-D-4FJmMZ.js +0 -0
  114. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/css-DdJfP1eB.js +0 -0
  115. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/css.worker-CqwWTjeG.js +0 -0
  116. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/cssMode-BuAVOLr4.js +0 -0
  117. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/cypher-cTPe9QuQ.js +0 -0
  118. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/dart-BOtBlQCF.js +0 -0
  119. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/dockerfile-BG73LgW2.js +0 -0
  120. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/ecl-BEgZUVRK.js +0 -0
  121. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/editor.worker-DCKwvLbM.js +0 -0
  122. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/elixir-BkW5O-1t.js +0 -0
  123. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/flow9-BeJ5waoc.js +0 -0
  124. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/freemarker2-DM0thzwF.js +0 -0
  125. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/fsharp-PahG7c26.js +0 -0
  126. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/go-acbASCJo.js +0 -0
  127. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/graphql-BxJiqAUM.js +0 -0
  128. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/handlebars-BkMf3Qj4.js +0 -0
  129. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/hcl-DtV1sZF8.js +0 -0
  130. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/html-KeNy2AFd.js +0 -0
  131. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/html.worker-DhJ4VZMp.js +0 -0
  132. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/htmlMode-CN5rSB9p.js +0 -0
  133. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/ini-Kd9XrMLS.js +0 -0
  134. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/java-CXBNlu9o.js +0 -0
  135. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/javascript-CZu1iw4q.js +0 -0
  136. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/json.worker-BF9eV5mS.js +0 -0
  137. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/jsonMode-BtMXpE2Q.js +0 -0
  138. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/julia-cl7-CwDS.js +0 -0
  139. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/kotlin-s7OhZKlX.js +0 -0
  140. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/less-9HpZscsL.js +0 -0
  141. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/lexon-OrD6JF1K.js +0 -0
  142. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/liquid-mhqehfbE.js +0 -0
  143. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/lspLanguageFeatures-D1NXFkgg.js +0 -0
  144. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/lua-Cyyb5UIc.js +0 -0
  145. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/m3-B8OfTtLu.js +0 -0
  146. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/markdown-BFxVWTOG.js +0 -0
  147. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/mdx-69yOd9f1.js +0 -0
  148. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/mips-CiqrrVzr.js +0 -0
  149. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/monaco-CB2SckMT.css +0 -0
  150. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/monaco-DNze5gXB.js +0 -0
  151. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/msdax-DmeGPVcC.js +0 -0
  152. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/mysql-C_tMU-Nz.js +0 -0
  153. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/objective-c-BDtDVThU.js +0 -0
  154. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/pascal-vHIfCaH5.js +0 -0
  155. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/pascaligo-DtZ0uQbO.js +0 -0
  156. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/perl-Ub6l9XKa.js +0 -0
  157. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/pgsql-BlNEE0v7.js +0 -0
  158. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/php-BBUBE1dy.js +0 -0
  159. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/pla-DSh2-awV.js +0 -0
  160. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/postiats-CocnycG-.js +0 -0
  161. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/powerquery-tScXyioY.js +0 -0
  162. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/powershell-COWaemsV.js +0 -0
  163. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/protobuf-Brw8urJB.js +0 -0
  164. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/pug-8SOpv6rk.js +0 -0
  165. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/python-C5qpjWC4.js +0 -0
  166. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/qsharp-Bw9ernYp.js +0 -0
  167. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/r-j7ic8hl3.js +0 -0
  168. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/razor-fg0l3XC9.js +0 -0
  169. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/redis-Bu5POkcn.js +0 -0
  170. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/redshift-Bs9aos_-.js +0 -0
  171. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/restructuredtext-CqXO7rUv.js +0 -0
  172. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/ruby-zBfavPgS.js +0 -0
  173. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/rust-BzKRNQWT.js +0 -0
  174. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/sb-BBc9UKZt.js +0 -0
  175. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/scala-D9hQfWCl.js +0 -0
  176. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/scheme-BPhDTwHR.js +0 -0
  177. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/scss-CBJaRo0y.js +0 -0
  178. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/shell-DiJ1NA_G.js +0 -0
  179. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/solidity-Db0IVjzk.js +0 -0
  180. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/sophia-CnS9iZB_.js +0 -0
  181. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/sparql-CJmd_6j2.js +0 -0
  182. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/sql-ClhHkBeG.js +0 -0
  183. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/st-CHwy0fLd.js +0 -0
  184. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/swift-Bqt4WxQ4.js +0 -0
  185. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/systemverilog-Bs9z6M-B.js +0 -0
  186. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/tcl-Dm6ycUr_.js +0 -0
  187. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/ts.worker-DTZAwq0V.js +0 -0
  188. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/tsMode-CaXNdXrQ.js +0 -0
  189. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/twig-Csy3S7wG.js +0 -0
  190. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/typescript-CCbVPqil.js +0 -0
  191. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/typespec-Btyra-wh.js +0 -0
  192. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/vb-Db0cS2oM.js +0 -0
  193. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/wgsl-BTesnYfV.js +0 -0
  194. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/workers-CwEVRSMl.js +0 -0
  195. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/xml-CIpcyvha.js +0 -0
  196. {datacharter-0.3.4 → datacharter-0.4.0}/src/datacharter/server/static/assets/yaml-CiikAWeE.js +0 -0
  197. {datacharter-0.3.4 → datacharter-0.4.0}/tests/agent/test_agent.py +0 -0
  198. {datacharter-0.3.4 → datacharter-0.4.0}/tests/agent/test_cache.py +0 -0
  199. {datacharter-0.3.4 → datacharter-0.4.0}/tests/agent/test_llm_wire.py +0 -0
  200. {datacharter-0.3.4 → datacharter-0.4.0}/tests/contracts/test_loader.py +0 -0
  201. {datacharter-0.3.4 → datacharter-0.4.0}/tests/contracts/test_metrics.py +0 -0
  202. {datacharter-0.3.4 → datacharter-0.4.0}/tests/contracts/test_pii.py +0 -0
  203. {datacharter-0.3.4 → datacharter-0.4.0}/tests/contracts/test_secrets_store.py +0 -0
  204. {datacharter-0.3.4 → datacharter-0.4.0}/tests/engine/test_aggregate.py +0 -0
  205. {datacharter-0.3.4 → datacharter-0.4.0}/tests/engine/test_diff.py +0 -0
  206. {datacharter-0.3.4 → datacharter-0.4.0}/tests/engine/test_federation.py +0 -0
  207. {datacharter-0.3.4 → datacharter-0.4.0}/tests/engine/test_guard.py +0 -0
  208. {datacharter-0.3.4 → datacharter-0.4.0}/tests/engine/test_provenance.py +0 -0
  209. {datacharter-0.3.4 → datacharter-0.4.0}/tests/engine/test_pushdown.py +0 -0
  210. {datacharter-0.3.4 → datacharter-0.4.0}/tests/engine/test_scrub.py +0 -0
  211. {datacharter-0.3.4 → datacharter-0.4.0}/tests/engine/test_session.py +0 -0
  212. {datacharter-0.3.4 → datacharter-0.4.0}/tests/engine/test_source_lifecycle.py +0 -0
  213. {datacharter-0.3.4 → datacharter-0.4.0}/tests/engine/test_sources.py +0 -0
  214. {datacharter-0.3.4 → datacharter-0.4.0}/tests/integration/seed/mysql.sql +0 -0
  215. {datacharter-0.3.4 → datacharter-0.4.0}/tests/integration/seed/postgres.sql +0 -0
  216. {datacharter-0.3.4 → datacharter-0.4.0}/tests/integration/test_live_sources.py +0 -0
  217. {datacharter-0.3.4 → datacharter-0.4.0}/tests/mcp/test_mcp_server.py +0 -0
  218. {datacharter-0.3.4 → datacharter-0.4.0}/tests/server/test_api.py +0 -0
  219. {datacharter-0.3.4 → datacharter-0.4.0}/tests/server/test_llm_admin.py +0 -0
  220. {datacharter-0.3.4 → datacharter-0.4.0}/tests/server/test_offline.py +0 -0
  221. {datacharter-0.3.4 → datacharter-0.4.0}/tests/server/test_security.py +0 -0
  222. {datacharter-0.3.4 → datacharter-0.4.0}/tests/server/test_source_admin.py +0 -0
  223. {datacharter-0.3.4 → datacharter-0.4.0}/tests/server/test_sources_api.py +0 -0
  224. {datacharter-0.3.4 → datacharter-0.4.0}/tests/test_agent_vidaimock_e2e.py +0 -0
  225. {datacharter-0.3.4 → datacharter-0.4.0}/tests/test_cli_init.py +0 -0
  226. {datacharter-0.3.4 → datacharter-0.4.0}/tests/test_drift.py +0 -0
  227. {datacharter-0.3.4 → datacharter-0.4.0}/tests/test_explain.py +0 -0
  228. {datacharter-0.3.4 → datacharter-0.4.0}/tests/test_packaging_e2e.py +0 -0
  229. {datacharter-0.3.4 → datacharter-0.4.0}/tests/test_sample.py +0 -0
  230. {datacharter-0.3.4 → datacharter-0.4.0}/tests/test_scaffold.py +0 -0
  231. {datacharter-0.3.4 → datacharter-0.4.0}/tests/test_secrets_cli.py +0 -0
  232. {datacharter-0.3.4 → datacharter-0.4.0}/tests/test_snapshot_recheck.py +0 -0
  233. {datacharter-0.3.4 → datacharter-0.4.0}/ui/index.html +0 -0
  234. {datacharter-0.3.4 → datacharter-0.4.0}/ui/package-lock.json +0 -0
  235. {datacharter-0.3.4 → datacharter-0.4.0}/ui/package.json +0 -0
  236. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/components/ChartPanel.tsx +0 -0
  237. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/components/EmptyState.test.tsx +0 -0
  238. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/components/EmptyState.tsx +0 -0
  239. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/components/HelpModal.tsx +0 -0
  240. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/components/LLMConfig.tsx +0 -0
  241. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/components/QueryFiles.tsx +0 -0
  242. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/components/ResultsGrid.tsx +0 -0
  243. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/components/SourceForm.tsx +0 -0
  244. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/components/SourcesView.tsx +0 -0
  245. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/components/Tutorial.tsx +0 -0
  246. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/lib/caption.ts +0 -0
  247. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/lib/chartSpec.ts +0 -0
  248. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/lib/columns.ts +0 -0
  249. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/lib/useResize.ts +0 -0
  250. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/main.tsx +0 -0
  251. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/monaco.ts +0 -0
  252. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/test/setup.ts +0 -0
  253. {datacharter-0.3.4 → datacharter-0.4.0}/ui/src/vite-env.d.ts +0 -0
  254. {datacharter-0.3.4 → datacharter-0.4.0}/ui/tsconfig.json +0 -0
  255. {datacharter-0.3.4 → datacharter-0.4.0}/ui/vite.config.ts +0 -0
@@ -5,6 +5,30 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.4.0] - 2026-07-25
9
+
10
+ ### Added
11
+
12
+ - **Connect Claude Code:** run the chat agent on your local Claude Code subscription (no
13
+ API key) instead of an OpenAI-wire LLM — surfaced beside "Connect an LLM". Data stays
14
+ governed (Claude reaches it only through datacharter's read-only, PII-masked tools via a
15
+ loopback bridge), and the connection is refused, fail-closed, unless the tool sandbox
16
+ verifies (auto-denying any non-governed tools). Requires a Claude Pro/Max subscription +
17
+ Claude Code installed.
18
+ - **Per-field agent-access toggles (left panel):** control per field / table / source
19
+ whether the agent sees real values or masked (`•••`) ones, persisted to the contract
20
+ (`agent_access:`). PII (declared or auto-detected) defaults to masked; everything else to
21
+ real; an explicit toggle wins. Enforced on the agent surface only (agent, MCP, Claude
22
+ Code) — the human SQL editor is never masked. "Agent view" mirrors the toggles.
23
+ - Remove snapshots and uploaded tables directly from the sidebar (a ✕ on `local.*` and
24
+ uploaded rows), via `DELETE /api/snapshot/{name}` and `DELETE /api/uploads/{name}`.
25
+
26
+ ### Fixed
27
+
28
+ - The sidebar listed each attached-source table twice — once correctly and once as a fake
29
+ "upload" — because the engine's internal `<source>__<table>` compatibility views were
30
+ included in the catalog listing. They're now hidden.
31
+
8
32
  ## [0.3.4] - 2026-07-24
9
33
 
10
34
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datacharter
3
- Version: 0.3.4
3
+ Version: 0.4.0
4
4
  Summary: Charter your data — contract-governed local data exploration, powered by DuckDB
5
5
  Project-URL: Homepage, https://github.com/datacharter/datacharter
6
6
  Project-URL: Repository, https://github.com/datacharter/datacharter
@@ -1,3 +1,3 @@
1
1
  """DataCharter — contract-governed local data exploration."""
2
2
 
3
- __version__ = "0.3.4"
3
+ __version__ = "0.4.0"
@@ -0,0 +1,201 @@
1
+ """Drive the local Claude Code (headless, subscription) as datacharter's agent backend.
2
+
3
+ Governance: every invocation is locked down (strict MCP config + a deny-list settings file +
4
+ `--permission-mode dontAsk`) and gated by a fail-closed tool-surface assertion. Claude reaches
5
+ data only through the governed `datacharter mcp --serve-url` proxy. Never uses `--bare` — that
6
+ disables subscription auth. Design: docs/superpowers/specs/2026-07-24-claude-code-agent-backend-design.md
7
+ """ # noqa: E501
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import json
13
+ import os
14
+ import shutil
15
+ import sys
16
+ import tempfile
17
+ from collections.abc import AsyncIterator, Iterable, Iterator
18
+ from pathlib import Path
19
+
20
+ GOVERNED_TOOLS = [
21
+ "mcp__datacharter__query",
22
+ "mcp__datacharter__list_tables",
23
+ "mcp__datacharter__list_sources",
24
+ "mcp__datacharter__describe_table",
25
+ ]
26
+
27
+ # Best-effort removal of built-ins. The connect-time assertion is the real guarantee;
28
+ # extend this from a clean-env `init.tools` list if the assertion reports a stray built-in.
29
+ _DENY = [
30
+ "Bash", "Read", "Write", "Edit", "Glob", "Grep", "WebFetch", "WebSearch", "Agent",
31
+ "Workflow", "Skill", "ToolSearch", "TaskCreate", "TaskGet", "TaskList", "TaskUpdate",
32
+ "TaskStop", "TaskOutput", "ScheduleWakeup", "CronCreate", "CronDelete", "CronList",
33
+ "SendMessage", "NotebookEdit", "Monitor", "WaitForMcpServers", "Artifact",
34
+ "AskUserQuestion", "EnterPlanMode", "ExitPlanMode", "ReportFindings",
35
+ "DesignSync", "EnterWorktree", "ExitWorktree", "PushNotification", "RemoteTrigger",
36
+ ]
37
+
38
+
39
+ class ClaudeGovernanceError(RuntimeError):
40
+ """Raised when Claude Code exposes tools beyond the governed set — connection refused."""
41
+
42
+
43
+ def claude_available() -> bool:
44
+ return shutil.which("claude") is not None
45
+
46
+
47
+ def _dc_bin() -> str:
48
+ # Must be the SAME datacharter that's serving (it needs `mcp --serve-url`); prefer the
49
+ # console script next to the running interpreter over whatever is first on PATH.
50
+ sibling = Path(sys.executable).parent / "datacharter"
51
+ if sibling.exists():
52
+ return str(sibling)
53
+ return shutil.which("datacharter") or "datacharter"
54
+
55
+
56
+ def tool_surface_ok(tools: list[str]) -> bool:
57
+ return set(tools).issubset(set(GOVERNED_TOOLS))
58
+
59
+
60
+ def build_configs(
61
+ serve_url: str, dc_bin: str, tmpdir: Path, deny: list[str] | None = None
62
+ ) -> tuple[Path, Path]:
63
+ settings = tmpdir / "settings.json"
64
+ mcp = tmpdir / "mcp.json"
65
+ settings.write_text(
66
+ json.dumps(
67
+ {
68
+ "defaultMode": "dontAsk",
69
+ "permissions": {"allow": GOVERNED_TOOLS, "deny": deny or _DENY},
70
+ }
71
+ )
72
+ )
73
+ mcp.write_text(
74
+ json.dumps(
75
+ {
76
+ "mcpServers": {
77
+ "datacharter": {"command": dc_bin, "args": ["mcp", "--serve-url", serve_url]}
78
+ }
79
+ }
80
+ )
81
+ )
82
+ return settings, mcp
83
+
84
+
85
+ def _base_flags(settings: Path, mcp: Path) -> list[str]:
86
+ return [
87
+ "--strict-mcp-config", "--mcp-config", str(mcp),
88
+ "--settings", str(settings), "--permission-mode", "dontAsk",
89
+ ]
90
+
91
+
92
+ def _env() -> dict:
93
+ env = dict(os.environ)
94
+ env.pop("ANTHROPIC_API_KEY", None) # force subscription auth
95
+ return env
96
+
97
+
98
+ def parse_stream(lines: Iterable[str]) -> Iterator[dict]:
99
+ """Map claude `stream-json` NDJSON lines to `{kind, ...}` events.
100
+
101
+ kinds: `session` (init: session_id + tools), `tool_call` (governed tool + SQL),
102
+ `text` (assistant delta), `result` (final text + session_id + is_error)."""
103
+ for raw in lines:
104
+ raw = raw.strip()
105
+ if not raw:
106
+ continue
107
+ try:
108
+ m = json.loads(raw)
109
+ except json.JSONDecodeError:
110
+ continue
111
+ mtype = m.get("type")
112
+ if mtype == "system" and m.get("subtype") == "init":
113
+ yield {
114
+ "kind": "session",
115
+ "session_id": m.get("session_id"),
116
+ "tools": m.get("tools", []),
117
+ }
118
+ elif mtype == "stream_event":
119
+ ev = m.get("event", {}) or {}
120
+ name = str(ev.get("name", ""))
121
+ if ev.get("type") == "tool_use" and name.startswith("mcp__datacharter__"):
122
+ label = ev["name"].removeprefix("mcp__datacharter__")
123
+ sql = (ev.get("input") or {}).get("sql")
124
+ yield {"kind": "tool_call", "tool": f"{label}: {sql}" if sql else label}
125
+ delta = ev.get("delta") or {}
126
+ if delta.get("type") == "text_delta" and delta.get("text"):
127
+ yield {"kind": "text", "text": delta["text"]}
128
+ elif mtype == "result":
129
+ yield {
130
+ "kind": "result",
131
+ "session_id": m.get("session_id"),
132
+ "text": m.get("result", ""),
133
+ "is_error": m.get("is_error", False),
134
+ }
135
+
136
+
137
+ async def probe_tools(
138
+ serve_url: str, dc_bin: str | None = None, deny: list[str] | None = None
139
+ ) -> list[str]:
140
+ """Spawn a locked-down claude and return the tool names it actually exposes (init.tools)."""
141
+ dc_bin = dc_bin or _dc_bin()
142
+ with tempfile.TemporaryDirectory(prefix="dc-claude-") as td:
143
+ settings, mcp = build_configs(serve_url, dc_bin, Path(td), deny)
144
+ proc = await asyncio.create_subprocess_exec(
145
+ "claude", "-p", "ok", "--output-format", "stream-json", "--verbose",
146
+ *_base_flags(settings, mcp),
147
+ stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.DEVNULL, env=_env(),
148
+ )
149
+ out, _ = await proc.communicate()
150
+ for ev in parse_stream(out.decode().splitlines()):
151
+ if ev["kind"] == "session":
152
+ return ev.get("tools", [])
153
+ return []
154
+
155
+
156
+ async def assert_tool_surface(serve_url: str, dc_bin: str | None = None) -> list[str]:
157
+ """Fail-closed: probe Claude's tool surface, auto-deny any non-governed tools it finds,
158
+ and re-probe until only the governed tools remain — returning the effective deny-list.
159
+ Raises `ClaudeGovernanceError` if a non-governed tool cannot be disabled."""
160
+ dc_bin = dc_bin or _dc_bin()
161
+ deny = list(_DENY)
162
+ extras: list[str] = []
163
+ for _ in range(4):
164
+ tools = await probe_tools(serve_url, dc_bin, deny)
165
+ extras = sorted(set(tools) - set(GOVERNED_TOOLS))
166
+ if not extras:
167
+ return deny
168
+ deny += [t for t in extras if t not in deny] # deny only removes; loop converges
169
+ raise ClaudeGovernanceError(
170
+ "Refusing to connect: Claude Code exposes non-governed tools that could not be "
171
+ "disabled: " + ", ".join(extras)
172
+ )
173
+
174
+
175
+ async def run_turn(
176
+ question: str,
177
+ serve_url: str,
178
+ session_id: str | None = None,
179
+ dc_bin: str | None = None,
180
+ deny: list[str] | None = None,
181
+ ) -> AsyncIterator[dict]:
182
+ """Run one chat turn; yield parsed stream events. Resumes `session_id` for context.
183
+ `deny` is the effective deny-list from the connect-time assertion."""
184
+ dc_bin = dc_bin or _dc_bin()
185
+ with tempfile.TemporaryDirectory(prefix="dc-claude-") as td:
186
+ settings, mcp = build_configs(serve_url, dc_bin, Path(td), deny)
187
+ args = [
188
+ "claude", "-p", question, "--output-format", "stream-json",
189
+ "--verbose", "--include-partial-messages",
190
+ *_base_flags(settings, mcp),
191
+ ]
192
+ if session_id:
193
+ args += ["--resume", session_id]
194
+ proc = await asyncio.create_subprocess_exec(
195
+ *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.DEVNULL, env=_env()
196
+ )
197
+ assert proc.stdout is not None
198
+ async for line in proc.stdout:
199
+ for ev in parse_stream([line.decode()]):
200
+ yield ev
201
+ await proc.wait()
@@ -0,0 +1,24 @@
1
+ """A ToolBox-shaped facade that forwards tool calls to a running `datacharter serve`.
2
+
3
+ Lets the stdio MCP server (`datacharter mcp --serve-url`) expose the governed tools without
4
+ opening a second engine, which would deadlock on the workspace state-DB lock. Governance is
5
+ the serve process's — `/api/tool` runs the same guarded, PII-masking toolbox as `/api/query`.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import httpx
11
+
12
+
13
+ class RemoteToolBox:
14
+ def __init__(
15
+ self, serve_url: str, *, _transport: httpx.AsyncBaseTransport | None = None
16
+ ) -> None:
17
+ self._url = serve_url.rstrip("/") + "/api/tool"
18
+ self._transport = _transport
19
+
20
+ async def run(self, name: str, arguments: str) -> str:
21
+ async with httpx.AsyncClient(transport=self._transport, timeout=120) as client:
22
+ resp = await client.post(self._url, json={"name": name, "arguments": arguments or "{}"})
23
+ resp.raise_for_status()
24
+ return resp.json()["result"]
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import json
6
6
  from typing import Any
7
7
 
8
+ from datacharter.contracts.access import resolve_masked
8
9
  from datacharter.engine.session import Engine
9
10
  from datacharter.models import QueryResult, Source
10
11
 
@@ -63,13 +64,18 @@ _MAX_TOOL_ROWS = 50
63
64
  class ToolBox:
64
65
  """Executes tool calls against the engine; masks PII columns in returned data."""
65
66
 
66
- def __init__(self, engine: Engine, sources: list[Source]) -> None:
67
+ def __init__(
68
+ self, engine: Engine, sources: list[Source], *, auto_pii: set[str] | None = None
69
+ ) -> None:
67
70
  self._engine = engine
68
71
  # Column names flagged PII in any source's contract, matched case-insensitively.
69
72
  self._pii: set[str] = set()
70
73
  for src in sources:
71
74
  for cols in src.pii.values():
72
75
  self._pii.update(c.lower() for c in cols)
76
+ self._auto_pii = auto_pii or set()
77
+ # Per-source agent-access overrides (on=real, off=masked).
78
+ self._overrides = {s.name: s.agent_access for s in sources if s.agent_access}
73
79
 
74
80
  async def run(self, name: str, arguments: str) -> str:
75
81
  try:
@@ -111,17 +117,51 @@ class ToolBox:
111
117
  if not _is_safe_relation(relation):
112
118
  return "Error: invalid relation name."
113
119
  result = await self._engine.query(f"DESCRIBE {relation}", timeout_s=30)
114
- return self._render(result)
120
+ return self._render(result, set()) # schema is always visible; values masked only in query
115
121
 
116
122
  async def _query(self, args: dict) -> str:
117
123
  sql = str(args.get("sql", ""))
118
124
  result = await self._engine.query(sql, row_limit=_MAX_TOOL_ROWS)
119
- return self._render(result)
120
-
121
- def _render(self, result: QueryResult) -> str:
122
- mask_idx = {
123
- i for i, col in enumerate(result.columns) if col.lower() in self._pii
124
- }
125
+ return self._render(result, self._mask_indices(result))
126
+
127
+ def _masked(self, source: str, table: str, column: str) -> bool:
128
+ return resolve_masked(
129
+ source, table, column,
130
+ declared_pii=self._pii, auto_pii=self._auto_pii, overrides=self._overrides,
131
+ )
132
+
133
+ def _mask_indices(self, result: QueryResult) -> set[int]:
134
+ """Which output columns to mask. Prefer per-column lineage; when it's missing
135
+ (e.g. SELECT *), resolve each column against the query's touched relations so
136
+ agent-access overrides still apply; last resort is a name-based PII check."""
137
+ prov = result.provenance or {}
138
+ lineage = prov.get("lineage") or {}
139
+ rels = []
140
+ for r in prov.get("relations") or []:
141
+ parts = str(r).split(".")
142
+ if len(parts) >= 2:
143
+ rels.append((parts[-2], parts[-1])) # (source, table)
144
+ idx = set()
145
+ for i, outcol in enumerate(result.columns):
146
+ srcs = lineage.get(outcol)
147
+ if srcs:
148
+ if any(self._lineage_masked(s) for s in srcs):
149
+ idx.add(i)
150
+ elif rels:
151
+ if any(self._masked(s, t, outcol) for (s, t) in rels):
152
+ idx.add(i)
153
+ elif outcol.lower() in self._pii or outcol.lower() in self._auto_pii:
154
+ idx.add(i)
155
+ return idx
156
+
157
+ def _lineage_masked(self, qualified: str) -> bool:
158
+ parts = qualified.split(".")
159
+ if len(parts) >= 3:
160
+ return self._masked(parts[-3], parts[-2], parts[-1])
161
+ name = parts[-1].lower()
162
+ return name in self._pii or name in self._auto_pii
163
+
164
+ def _render(self, result: QueryResult, mask_idx: set[int]) -> str:
125
165
  rows = [
126
166
  [MASKED if i in mask_idx else v for i, v in enumerate(row)]
127
167
  for row in result.rows
@@ -160,7 +160,8 @@ def _cmd_serve(args: argparse.Namespace) -> int:
160
160
  ws = _resolve_serve_workspace(args.directory)
161
161
  llm = None if args.offline else (_local_llm(args.model) if args.local else None)
162
162
  app = create_app(
163
- ws, allow_spill=not args.no_spill, llm=llm, host=args.host, offline=args.offline
163
+ ws, allow_spill=not args.no_spill, llm=llm, host=args.host, port=args.port,
164
+ offline=args.offline,
164
165
  )
165
166
  if args.offline:
166
167
  _print_attestation(ws, args.host, args.port)
@@ -203,9 +204,17 @@ def _cmd_secrets(args: argparse.Namespace) -> int:
203
204
  def _cmd_mcp(args: argparse.Namespace) -> int:
204
205
  import asyncio
205
206
 
207
+ from datacharter.mcp.server import serve_stdio
208
+
209
+ if args.serve_url:
210
+ from datacharter.agent.remote_tools import RemoteToolBox
211
+
212
+ print(f"datacharter MCP proxy → {args.serve_url}", file=sys.stderr)
213
+ asyncio.run(serve_stdio(RemoteToolBox(args.serve_url)))
214
+ return 0
215
+
206
216
  from datacharter.agent.tools import ToolBox
207
217
  from datacharter.contracts import load_charter
208
- from datacharter.mcp.server import serve_stdio
209
218
 
210
219
  ws = Path(args.directory).resolve()
211
220
  if not (ws / "charter.yaml").exists():
@@ -254,29 +263,9 @@ def _cmd_diff(args: argparse.Namespace) -> int:
254
263
 
255
264
  async def _scan_pii(engine) -> dict[str, list[str]]:
256
265
  """Suggest PII columns per relation: by name, then by sampled values."""
257
- from datacharter.contracts.pii import classify_pii, detect_value_pii
258
-
259
- tables = await engine.query("SHOW ALL TABLES", timeout_s=30)
260
- idx = {c: i for i, c in enumerate(tables.columns)}
261
- suggestions: dict[str, list[str]] = {}
262
- for row in tables.rows:
263
- db = row[idx["database"]]
264
- if db in ("system", "temp"):
265
- continue
266
- table = row[idx["name"]]
267
- relation = table if db == "memory" else f"{db}.{table}"
268
- columns = list(row[idx["column_names"]])
269
- flagged = set(classify_pii(columns))
270
- remaining = [c for c in columns if c not in flagged]
271
- if remaining and all(ch.isalnum() or ch in "._" for ch in relation):
272
- sample = await engine.query(f"SELECT * FROM {relation} LIMIT 25", timeout_s=30)
273
- pos = {c: i for i, c in enumerate(sample.columns)}
274
- for col in remaining:
275
- if col in pos and detect_value_pii([r[pos[col]] for r in sample.rows]):
276
- flagged.add(col)
277
- if flagged:
278
- suggestions[relation] = [c for c in columns if c in flagged]
279
- return suggestions
266
+ from datacharter.contracts.pii import detect_pii
267
+
268
+ return await detect_pii(engine)
280
269
 
281
270
 
282
271
  def _cmd_scan(args: argparse.Namespace) -> int:
@@ -601,6 +590,11 @@ def main(argv: list[str] | None = None) -> int:
601
590
  "mcp", help="Run an MCP server over stdio exposing the governed query tools"
602
591
  )
603
592
  p_mcp.add_argument("directory", nargs="?", default=".")
593
+ p_mcp.add_argument(
594
+ "--serve-url",
595
+ default=None,
596
+ help="Proxy tools to a running `datacharter serve` instead of opening a local engine",
597
+ )
604
598
  p_mcp.set_defaults(func=_cmd_mcp)
605
599
 
606
600
  p_diff = sub.add_parser("diff", help="Diff two relations (rows only in each + common count)")
@@ -0,0 +1,31 @@
1
+ """Resolve whether an agent-facing column is masked (access OFF) or real (ON).
2
+
3
+ Precedence: field override -> table override -> source override -> default (masked iff the
4
+ column is PII: declared or auto-detected). In the contract, `on` = real, `off` = masked.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+
10
+ def resolve_masked(
11
+ source: str,
12
+ table: str,
13
+ column: str,
14
+ *,
15
+ declared_pii: set[str],
16
+ auto_pii: set[str],
17
+ overrides: dict,
18
+ ) -> bool:
19
+ """True if the agent should see this column masked (`•••`), False for real values."""
20
+ src_ov = overrides.get(source) or {}
21
+ columns = src_ov.get("columns") or {}
22
+ key = f"{table}.{column}"
23
+ if key in columns:
24
+ return not columns[key] # on -> not masked
25
+ tables = src_ov.get("tables") or {}
26
+ if table in tables:
27
+ return not tables[table]
28
+ if "source" in src_ov:
29
+ return not src_ov["source"]
30
+ col = column.lower()
31
+ return col in declared_pii or col in auto_pii # default: PII masked, else real
@@ -121,6 +121,7 @@ def _build_source(name: str, body: Any, resolver: SecretResolver, warnings: list
121
121
  path=path_value,
122
122
  tables=list(body.get("tables") or []),
123
123
  pii={k: list(v) for k, v in (body.get("pii") or {}).items()},
124
+ agent_access=dict(body.get("agent_access") or {}),
124
125
  max_rows=max_rows,
125
126
  )
126
127
  except ValidationError as exc:
@@ -11,7 +11,7 @@ from __future__ import annotations
11
11
 
12
12
  import re
13
13
 
14
- __all__ = ["classify_pii", "detect_value_pii", "PII_TOKENS"]
14
+ __all__ = ["classify_pii", "detect_value_pii", "detect_pii", "PII_TOKENS"]
15
15
 
16
16
  # High-precision value patterns only — distinctive enough that matching most of a
17
17
  # column's sampled values is strong evidence, without the false positives a loose
@@ -79,3 +79,28 @@ def detect_value_pii(values: list, *, threshold: float = 0.6) -> str | None:
79
79
  if sum(1 for s in samples if pattern.match(s)) / len(samples) >= threshold:
80
80
  return kind
81
81
  return None
82
+
83
+
84
+ async def detect_pii(engine) -> dict[str, list[str]]:
85
+ """Suggest PII columns per relation: by name, then by sampled values."""
86
+ tables = await engine.query("SHOW ALL TABLES", timeout_s=30)
87
+ idx = {c: i for i, c in enumerate(tables.columns)}
88
+ suggestions: dict[str, list[str]] = {}
89
+ for row in tables.rows:
90
+ db = row[idx["database"]]
91
+ if db in ("system", "temp"):
92
+ continue
93
+ table = row[idx["name"]]
94
+ relation = table if db == "memory" else f"{db}.{table}"
95
+ columns = list(row[idx["column_names"]])
96
+ flagged = set(classify_pii(columns))
97
+ remaining = [c for c in columns if c not in flagged]
98
+ if remaining and all(ch.isalnum() or ch in "._" for ch in relation):
99
+ sample = await engine.query(f"SELECT * FROM {relation} LIMIT 25", timeout_s=30)
100
+ pos = {c: i for i, c in enumerate(sample.columns)}
101
+ for col in remaining:
102
+ if col in pos and detect_value_pii([r[pos[col]] for r in sample.rows]):
103
+ flagged.add(col)
104
+ if flagged:
105
+ suggestions[relation] = [c for c in columns if c in flagged]
106
+ return suggestions
@@ -64,6 +64,31 @@ def remove_source(workspace: Path, name: str) -> None:
64
64
  _write(path, y, data)
65
65
 
66
66
 
67
+ def set_agent_access(
68
+ workspace: Path, source: str, table: str | None, column: str | None, value: bool
69
+ ) -> None:
70
+ """Persist one agent-access override (on=real, off=masked) at field/table/source level.
71
+
72
+ Touches only the source's `agent_access` block; everything else is preserved verbatim."""
73
+ path = workspace / CHARTER_FILE
74
+ y, data = _load(path)
75
+ sources = data.get("sources") or {}
76
+ if source not in sources:
77
+ raise ContractWriteError(f"Source '{source}' is not in the charter.")
78
+ entry = sources[source]
79
+ aa = entry.get("agent_access")
80
+ if aa is None:
81
+ aa = {}
82
+ entry["agent_access"] = aa
83
+ if column is not None and table is not None:
84
+ aa.setdefault("columns", {})[f"{table}.{column}"] = value
85
+ elif table is not None:
86
+ aa.setdefault("tables", {})[table] = value
87
+ else:
88
+ aa["source"] = value
89
+ _write(path, y, data)
90
+
91
+
67
92
  def set_pii(workspace: Path, source: str, table: str, columns: list[str]) -> None:
68
93
  """Merge PII column names into one source's pii map (round-trip).
69
94
 
@@ -21,6 +21,9 @@ def write_demo_data(workspace: Path) -> None:
21
21
  demo.mkdir(exist_ok=True)
22
22
  con = sqlite3.connect(str(demo / "store.db"))
23
23
  try:
24
+ # Idempotent: a prior demo may have been loaded then deleted, leaving store.db.
25
+ con.execute("DROP TABLE IF EXISTS customers")
26
+ con.execute("DROP TABLE IF EXISTS orders")
24
27
  con.execute("CREATE TABLE customers (id INTEGER, email TEXT, tier TEXT)")
25
28
  con.executemany(
26
29
  "INSERT INTO customers VALUES (?, ?, ?)",
@@ -467,6 +467,14 @@ class Engine:
467
467
  if staged is not None:
468
468
  conn.execute(f"DROP TABLE IF EXISTS {staged}")
469
469
 
470
+ def drop_local(self, name: str) -> None:
471
+ """Drop a persisted snapshot table `local.<name>`."""
472
+ if not re.fullmatch(r"[a-z][a-z0-9_]{0,62}", name):
473
+ raise EngineError("Snapshot name must be lowercase letters, digits, underscores.")
474
+ conn = self._require_conn()
475
+ with self._exec_lock:
476
+ conn.execute(f"DROP TABLE IF EXISTS local.{name}")
477
+
470
478
  # -- internals ----------------------------------------------------------
471
479
 
472
480
  def _attach_local(self, state: Path) -> None:
@@ -58,6 +58,8 @@ class Source(BaseModel):
58
58
  path: str | None = None
59
59
  tables: list[str] = Field(default_factory=list)
60
60
  pii: dict[str, list[str]] = Field(default_factory=dict)
61
+ #: Agent-access overrides (on=real, off=masked) at source/table/column level.
62
+ agent_access: dict = Field(default_factory=dict)
61
63
  #: Connector-extract cap (Snowflake). Overrides the engine default; ignored
62
64
  #: for ATTACH/file sources, which stream and are not row-capped.
63
65
  max_rows: int | None = Field(default=None, gt=0)
@@ -0,0 +1,28 @@
1
+ """Which backend powers the chat agent: the OpenAI-wire LLM, or local Claude Code."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ _CONFIG_FILE = ".datacharter/agent.json"
9
+ _VALID = ("llm", "claude-code")
10
+
11
+
12
+ def get_backend(workspace: Path) -> str:
13
+ path = workspace / _CONFIG_FILE
14
+ if not path.exists():
15
+ return "llm"
16
+ try:
17
+ backend = json.loads(path.read_text()).get("backend", "llm")
18
+ except Exception:
19
+ return "llm"
20
+ return backend if backend in _VALID else "llm"
21
+
22
+
23
+ def set_backend(workspace: Path, backend: str) -> None:
24
+ if backend not in _VALID:
25
+ raise ValueError(f"unknown backend: {backend!r}")
26
+ path = workspace / _CONFIG_FILE
27
+ path.parent.mkdir(parents=True, exist_ok=True)
28
+ path.write_text(json.dumps({"backend": backend}))