datacharter 0.3.3__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 (256) hide show
  1. {datacharter-0.3.3 → datacharter-0.4.0}/.github/workflows/test.yml +2 -0
  2. {datacharter-0.3.3 → datacharter-0.4.0}/CHANGELOG.md +33 -0
  3. {datacharter-0.3.3 → datacharter-0.4.0}/PKG-INFO +1 -1
  4. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/__init__.py +1 -1
  5. datacharter-0.4.0/src/datacharter/agent/claude_code.py +201 -0
  6. datacharter-0.4.0/src/datacharter/agent/remote_tools.py +24 -0
  7. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/agent/tools.py +48 -8
  8. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/cli.py +22 -56
  9. datacharter-0.4.0/src/datacharter/contracts/access.py +31 -0
  10. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/contracts/loader.py +1 -0
  11. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/contracts/pii.py +26 -1
  12. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/contracts/writer.py +25 -0
  13. datacharter-0.4.0/src/datacharter/demo.py +46 -0
  14. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/engine/session.py +8 -0
  15. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/models.py +2 -0
  16. datacharter-0.4.0/src/datacharter/server/agent_backend.py +28 -0
  17. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/app.py +198 -17
  18. datacharter-0.4.0/src/datacharter/server/static/assets/index-D8N7a0N6.js +298 -0
  19. datacharter-0.3.3/src/datacharter/server/static/assets/index-CX4OWOHb.css → datacharter-0.4.0/src/datacharter/server/static/assets/index-FN6N1fIA.css +1 -1
  20. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/index.html +2 -2
  21. datacharter-0.4.0/tests/agent/test_claude_code.py +64 -0
  22. datacharter-0.4.0/tests/agent/test_remote_tools.py +28 -0
  23. datacharter-0.4.0/tests/agent/test_tools_masking.py +92 -0
  24. datacharter-0.4.0/tests/contracts/test_access.py +31 -0
  25. datacharter-0.4.0/tests/contracts/test_detect_pii.py +13 -0
  26. {datacharter-0.3.3 → datacharter-0.4.0}/tests/contracts/test_writer.py +31 -1
  27. datacharter-0.4.0/tests/server/test_agent_access_api.py +80 -0
  28. datacharter-0.4.0/tests/server/test_claude_agent_api.py +84 -0
  29. datacharter-0.4.0/tests/server/test_cold_start.py +155 -0
  30. {datacharter-0.3.3 → datacharter-0.4.0}/ui/package-lock.json +1202 -37
  31. {datacharter-0.3.3 → datacharter-0.4.0}/ui/package.json +9 -2
  32. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/App.tsx +80 -37
  33. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/api.ts +28 -0
  34. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/components/ChatPanel.tsx +18 -6
  35. datacharter-0.4.0/ui/src/components/ClaudeCodeConnect.test.tsx +50 -0
  36. datacharter-0.4.0/ui/src/components/ClaudeCodeConnect.tsx +52 -0
  37. datacharter-0.4.0/ui/src/components/EmptyState.test.tsx +31 -0
  38. datacharter-0.4.0/ui/src/components/EmptyState.tsx +53 -0
  39. datacharter-0.4.0/ui/src/components/SourceTree.test.tsx +57 -0
  40. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/components/SourceTree.tsx +82 -6
  41. datacharter-0.4.0/ui/src/onboarding.test.ts +28 -0
  42. datacharter-0.4.0/ui/src/onboarding.ts +28 -0
  43. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/styles.css +50 -0
  44. datacharter-0.4.0/ui/src/test/setup.ts +1 -0
  45. {datacharter-0.3.3 → datacharter-0.4.0}/ui/vite.config.ts +6 -1
  46. datacharter-0.3.3/src/datacharter/server/static/assets/index-CHFhM_Tn.js +0 -298
  47. datacharter-0.3.3/tests/server/test_cold_start.py +0 -68
  48. {datacharter-0.3.3 → datacharter-0.4.0}/.github/workflows/banned-terms.yml +0 -0
  49. {datacharter-0.3.3 → datacharter-0.4.0}/.github/workflows/release.yml +0 -0
  50. {datacharter-0.3.3 → datacharter-0.4.0}/.gitignore +0 -0
  51. {datacharter-0.3.3 → datacharter-0.4.0}/CODE_OF_CONDUCT.md +0 -0
  52. {datacharter-0.3.3 → datacharter-0.4.0}/CONTRIBUTING.md +0 -0
  53. {datacharter-0.3.3 → datacharter-0.4.0}/LICENSE +0 -0
  54. {datacharter-0.3.3 → datacharter-0.4.0}/README.md +0 -0
  55. {datacharter-0.3.3 → datacharter-0.4.0}/SECURITY.md +0 -0
  56. {datacharter-0.3.3 → datacharter-0.4.0}/THIRD-PARTY-NOTICES.md +0 -0
  57. {datacharter-0.3.3 → datacharter-0.4.0}/TRADEMARKS.md +0 -0
  58. {datacharter-0.3.3 → datacharter-0.4.0}/brand/README.md +0 -0
  59. {datacharter-0.3.3 → datacharter-0.4.0}/brand/demo.gif +0 -0
  60. {datacharter-0.3.3 → datacharter-0.4.0}/brand/hero.png +0 -0
  61. {datacharter-0.3.3 → datacharter-0.4.0}/brand/mark.svg +0 -0
  62. {datacharter-0.3.3 → datacharter-0.4.0}/brand/screenshot-m4-agent.png +0 -0
  63. {datacharter-0.3.3 → datacharter-0.4.0}/brand/screenshot-m5-complete.png +0 -0
  64. {datacharter-0.3.3 → datacharter-0.4.0}/brand/screenshot-m5-core.png +0 -0
  65. {datacharter-0.3.3 → datacharter-0.4.0}/brand/screenshot-m5-features.png +0 -0
  66. {datacharter-0.3.3 → datacharter-0.4.0}/compose.yaml +0 -0
  67. {datacharter-0.3.3 → datacharter-0.4.0}/docs/_config.yml +0 -0
  68. {datacharter-0.3.3 → datacharter-0.4.0}/docs/about.md +0 -0
  69. {datacharter-0.3.3 → datacharter-0.4.0}/docs/agent.md +0 -0
  70. {datacharter-0.3.3 → datacharter-0.4.0}/docs/assets/add-source.gif +0 -0
  71. {datacharter-0.3.3 → datacharter-0.4.0}/docs/assets/drag-drop.gif +0 -0
  72. {datacharter-0.3.3 → datacharter-0.4.0}/docs/assets/hero.png +0 -0
  73. {datacharter-0.3.3 → datacharter-0.4.0}/docs/assets/screenshot.png +0 -0
  74. {datacharter-0.3.3 → datacharter-0.4.0}/docs/assets/snapshot.gif +0 -0
  75. {datacharter-0.3.3 → datacharter-0.4.0}/docs/charter-yaml.md +0 -0
  76. {datacharter-0.3.3 → datacharter-0.4.0}/docs/cli.md +0 -0
  77. {datacharter-0.3.3 → datacharter-0.4.0}/docs/faq.md +0 -0
  78. {datacharter-0.3.3 → datacharter-0.4.0}/docs/index.html +0 -0
  79. {datacharter-0.3.3 → datacharter-0.4.0}/docs/mcp.md +0 -0
  80. {datacharter-0.3.3 → datacharter-0.4.0}/docs/quickstart.md +0 -0
  81. {datacharter-0.3.3 → datacharter-0.4.0}/docs/security.md +0 -0
  82. {datacharter-0.3.3 → datacharter-0.4.0}/docs/sources.md +0 -0
  83. {datacharter-0.3.3 → datacharter-0.4.0}/docs/workspace.md +0 -0
  84. {datacharter-0.3.3 → datacharter-0.4.0}/pyproject.toml +0 -0
  85. {datacharter-0.3.3 → datacharter-0.4.0}/scripts/build_ui.sh +0 -0
  86. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/agent/__init__.py +0 -0
  87. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/agent/cache.py +0 -0
  88. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/agent/llm.py +0 -0
  89. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/agent/loop.py +0 -0
  90. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/contracts/__init__.py +0 -0
  91. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/contracts/metrics.py +0 -0
  92. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/contracts/resolve.py +0 -0
  93. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/contracts/secrets.py +0 -0
  94. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/engine/__init__.py +0 -0
  95. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/engine/aggregate.py +0 -0
  96. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/engine/guard.py +0 -0
  97. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/engine/provenance.py +0 -0
  98. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/engine/pushdown.py +0 -0
  99. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/engine/scrub.py +0 -0
  100. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/engine/snowflake.py +0 -0
  101. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/engine/sources.py +0 -0
  102. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/engine/statekey.py +0 -0
  103. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/mcp/__init__.py +0 -0
  104. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/mcp/server.py +0 -0
  105. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/__init__.py +0 -0
  106. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/llm_admin.py +0 -0
  107. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/security.py +0 -0
  108. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/source_admin.py +0 -0
  109. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/abap-DLDM7-KI.js +0 -0
  110. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/apex-DNDY2TF8.js +0 -0
  111. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/azcli-Y6nb8tq_.js +0 -0
  112. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/bat-BwHxbl9M.js +0 -0
  113. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/bicep-CFznDFnq.js +0 -0
  114. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/cameligo-Bf6VGUru.js +0 -0
  115. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/clojure-Dnu-v4kV.js +0 -0
  116. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/codicon-Brq4_Ui5.ttf +0 -0
  117. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/coffee-Bd8akH9Z.js +0 -0
  118. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/cpp-BbWJElDN.js +0 -0
  119. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/csharp-Co3qMtFm.js +0 -0
  120. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/csp-D-4FJmMZ.js +0 -0
  121. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/css-DdJfP1eB.js +0 -0
  122. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/css.worker-CqwWTjeG.js +0 -0
  123. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/cssMode-BuAVOLr4.js +0 -0
  124. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/cypher-cTPe9QuQ.js +0 -0
  125. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/dart-BOtBlQCF.js +0 -0
  126. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/dockerfile-BG73LgW2.js +0 -0
  127. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/ecl-BEgZUVRK.js +0 -0
  128. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/editor.worker-DCKwvLbM.js +0 -0
  129. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/elixir-BkW5O-1t.js +0 -0
  130. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/flow9-BeJ5waoc.js +0 -0
  131. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/freemarker2-DM0thzwF.js +0 -0
  132. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/fsharp-PahG7c26.js +0 -0
  133. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/go-acbASCJo.js +0 -0
  134. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/graphql-BxJiqAUM.js +0 -0
  135. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/handlebars-BkMf3Qj4.js +0 -0
  136. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/hcl-DtV1sZF8.js +0 -0
  137. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/html-KeNy2AFd.js +0 -0
  138. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/html.worker-DhJ4VZMp.js +0 -0
  139. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/htmlMode-CN5rSB9p.js +0 -0
  140. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/ini-Kd9XrMLS.js +0 -0
  141. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/java-CXBNlu9o.js +0 -0
  142. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/javascript-CZu1iw4q.js +0 -0
  143. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/json.worker-BF9eV5mS.js +0 -0
  144. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/jsonMode-BtMXpE2Q.js +0 -0
  145. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/julia-cl7-CwDS.js +0 -0
  146. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/kotlin-s7OhZKlX.js +0 -0
  147. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/less-9HpZscsL.js +0 -0
  148. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/lexon-OrD6JF1K.js +0 -0
  149. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/liquid-mhqehfbE.js +0 -0
  150. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/lspLanguageFeatures-D1NXFkgg.js +0 -0
  151. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/lua-Cyyb5UIc.js +0 -0
  152. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/m3-B8OfTtLu.js +0 -0
  153. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/markdown-BFxVWTOG.js +0 -0
  154. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/mdx-69yOd9f1.js +0 -0
  155. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/mips-CiqrrVzr.js +0 -0
  156. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/monaco-CB2SckMT.css +0 -0
  157. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/monaco-DNze5gXB.js +0 -0
  158. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/msdax-DmeGPVcC.js +0 -0
  159. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/mysql-C_tMU-Nz.js +0 -0
  160. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/objective-c-BDtDVThU.js +0 -0
  161. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/pascal-vHIfCaH5.js +0 -0
  162. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/pascaligo-DtZ0uQbO.js +0 -0
  163. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/perl-Ub6l9XKa.js +0 -0
  164. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/pgsql-BlNEE0v7.js +0 -0
  165. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/php-BBUBE1dy.js +0 -0
  166. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/pla-DSh2-awV.js +0 -0
  167. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/postiats-CocnycG-.js +0 -0
  168. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/powerquery-tScXyioY.js +0 -0
  169. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/powershell-COWaemsV.js +0 -0
  170. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/protobuf-Brw8urJB.js +0 -0
  171. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/pug-8SOpv6rk.js +0 -0
  172. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/python-C5qpjWC4.js +0 -0
  173. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/qsharp-Bw9ernYp.js +0 -0
  174. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/r-j7ic8hl3.js +0 -0
  175. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/razor-fg0l3XC9.js +0 -0
  176. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/redis-Bu5POkcn.js +0 -0
  177. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/redshift-Bs9aos_-.js +0 -0
  178. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/restructuredtext-CqXO7rUv.js +0 -0
  179. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/ruby-zBfavPgS.js +0 -0
  180. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/rust-BzKRNQWT.js +0 -0
  181. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/sb-BBc9UKZt.js +0 -0
  182. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/scala-D9hQfWCl.js +0 -0
  183. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/scheme-BPhDTwHR.js +0 -0
  184. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/scss-CBJaRo0y.js +0 -0
  185. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/shell-DiJ1NA_G.js +0 -0
  186. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/solidity-Db0IVjzk.js +0 -0
  187. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/sophia-CnS9iZB_.js +0 -0
  188. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/sparql-CJmd_6j2.js +0 -0
  189. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/sql-ClhHkBeG.js +0 -0
  190. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/st-CHwy0fLd.js +0 -0
  191. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/swift-Bqt4WxQ4.js +0 -0
  192. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/systemverilog-Bs9z6M-B.js +0 -0
  193. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/tcl-Dm6ycUr_.js +0 -0
  194. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/ts.worker-DTZAwq0V.js +0 -0
  195. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/tsMode-CaXNdXrQ.js +0 -0
  196. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/twig-Csy3S7wG.js +0 -0
  197. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/typescript-CCbVPqil.js +0 -0
  198. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/typespec-Btyra-wh.js +0 -0
  199. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/vb-Db0cS2oM.js +0 -0
  200. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/wgsl-BTesnYfV.js +0 -0
  201. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/workers-CwEVRSMl.js +0 -0
  202. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/xml-CIpcyvha.js +0 -0
  203. {datacharter-0.3.3 → datacharter-0.4.0}/src/datacharter/server/static/assets/yaml-CiikAWeE.js +0 -0
  204. {datacharter-0.3.3 → datacharter-0.4.0}/tests/agent/test_agent.py +0 -0
  205. {datacharter-0.3.3 → datacharter-0.4.0}/tests/agent/test_cache.py +0 -0
  206. {datacharter-0.3.3 → datacharter-0.4.0}/tests/agent/test_llm_wire.py +0 -0
  207. {datacharter-0.3.3 → datacharter-0.4.0}/tests/contracts/test_loader.py +0 -0
  208. {datacharter-0.3.3 → datacharter-0.4.0}/tests/contracts/test_metrics.py +0 -0
  209. {datacharter-0.3.3 → datacharter-0.4.0}/tests/contracts/test_pii.py +0 -0
  210. {datacharter-0.3.3 → datacharter-0.4.0}/tests/contracts/test_secrets_store.py +0 -0
  211. {datacharter-0.3.3 → datacharter-0.4.0}/tests/engine/test_aggregate.py +0 -0
  212. {datacharter-0.3.3 → datacharter-0.4.0}/tests/engine/test_diff.py +0 -0
  213. {datacharter-0.3.3 → datacharter-0.4.0}/tests/engine/test_federation.py +0 -0
  214. {datacharter-0.3.3 → datacharter-0.4.0}/tests/engine/test_guard.py +0 -0
  215. {datacharter-0.3.3 → datacharter-0.4.0}/tests/engine/test_provenance.py +0 -0
  216. {datacharter-0.3.3 → datacharter-0.4.0}/tests/engine/test_pushdown.py +0 -0
  217. {datacharter-0.3.3 → datacharter-0.4.0}/tests/engine/test_scrub.py +0 -0
  218. {datacharter-0.3.3 → datacharter-0.4.0}/tests/engine/test_session.py +0 -0
  219. {datacharter-0.3.3 → datacharter-0.4.0}/tests/engine/test_source_lifecycle.py +0 -0
  220. {datacharter-0.3.3 → datacharter-0.4.0}/tests/engine/test_sources.py +0 -0
  221. {datacharter-0.3.3 → datacharter-0.4.0}/tests/integration/seed/mysql.sql +0 -0
  222. {datacharter-0.3.3 → datacharter-0.4.0}/tests/integration/seed/postgres.sql +0 -0
  223. {datacharter-0.3.3 → datacharter-0.4.0}/tests/integration/test_live_sources.py +0 -0
  224. {datacharter-0.3.3 → datacharter-0.4.0}/tests/mcp/test_mcp_server.py +0 -0
  225. {datacharter-0.3.3 → datacharter-0.4.0}/tests/server/test_api.py +0 -0
  226. {datacharter-0.3.3 → datacharter-0.4.0}/tests/server/test_llm_admin.py +0 -0
  227. {datacharter-0.3.3 → datacharter-0.4.0}/tests/server/test_offline.py +0 -0
  228. {datacharter-0.3.3 → datacharter-0.4.0}/tests/server/test_security.py +0 -0
  229. {datacharter-0.3.3 → datacharter-0.4.0}/tests/server/test_source_admin.py +0 -0
  230. {datacharter-0.3.3 → datacharter-0.4.0}/tests/server/test_sources_api.py +0 -0
  231. {datacharter-0.3.3 → datacharter-0.4.0}/tests/test_agent_vidaimock_e2e.py +0 -0
  232. {datacharter-0.3.3 → datacharter-0.4.0}/tests/test_cli_init.py +0 -0
  233. {datacharter-0.3.3 → datacharter-0.4.0}/tests/test_drift.py +0 -0
  234. {datacharter-0.3.3 → datacharter-0.4.0}/tests/test_explain.py +0 -0
  235. {datacharter-0.3.3 → datacharter-0.4.0}/tests/test_packaging_e2e.py +0 -0
  236. {datacharter-0.3.3 → datacharter-0.4.0}/tests/test_sample.py +0 -0
  237. {datacharter-0.3.3 → datacharter-0.4.0}/tests/test_scaffold.py +0 -0
  238. {datacharter-0.3.3 → datacharter-0.4.0}/tests/test_secrets_cli.py +0 -0
  239. {datacharter-0.3.3 → datacharter-0.4.0}/tests/test_snapshot_recheck.py +0 -0
  240. {datacharter-0.3.3 → datacharter-0.4.0}/ui/index.html +0 -0
  241. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/components/ChartPanel.tsx +0 -0
  242. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/components/HelpModal.tsx +0 -0
  243. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/components/LLMConfig.tsx +0 -0
  244. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/components/QueryFiles.tsx +0 -0
  245. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/components/ResultsGrid.tsx +0 -0
  246. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/components/SourceForm.tsx +0 -0
  247. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/components/SourcesView.tsx +0 -0
  248. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/components/Tutorial.tsx +0 -0
  249. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/lib/caption.ts +0 -0
  250. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/lib/chartSpec.ts +0 -0
  251. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/lib/columns.ts +0 -0
  252. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/lib/useResize.ts +0 -0
  253. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/main.tsx +0 -0
  254. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/monaco.ts +0 -0
  255. {datacharter-0.3.3 → datacharter-0.4.0}/ui/src/vite-env.d.ts +0 -0
  256. {datacharter-0.3.3 → datacharter-0.4.0}/ui/tsconfig.json +0 -0
@@ -71,6 +71,8 @@ jobs:
71
71
  run: cd ui && npm ci
72
72
  - name: Typecheck + build
73
73
  run: cd ui && npm run build
74
+ - name: Unit test UI
75
+ run: cd ui && npm test
74
76
 
75
77
  package:
76
78
  runs-on: ubuntu-latest
@@ -5,6 +5,39 @@ 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
+
32
+ ## [0.3.4] - 2026-07-24
33
+
34
+ ### Added
35
+
36
+ - Empty workspaces get an "add your data" first-run launchpad (add a source / drop a
37
+ CSV / load the demo dataset) instead of the demo feature-tour, which now auto-shows
38
+ only when the workspace has sources. New `POST /api/demo` loads the demo `store` as a
39
+ normal, deletable source.
40
+
8
41
  ## [0.3.3] - 2026-07-24
9
42
 
10
43
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datacharter
3
- Version: 0.3.3
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.3"
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
@@ -7,6 +7,7 @@ import sys
7
7
  from pathlib import Path
8
8
 
9
9
  from datacharter import __version__
10
+ from datacharter.demo import write_demo_data
10
11
 
11
12
  CHARTER_TEMPLATE = """\
12
13
  # DataCharter workspace — https://github.com/datacharter/datacharter
@@ -72,7 +73,7 @@ def _cmd_init(args: argparse.Namespace) -> int:
72
73
  (ws / "queries").mkdir(exist_ok=True)
73
74
  _ensure_gitignore(ws)
74
75
  if args.demo:
75
- _write_demo_data(ws)
76
+ write_demo_data(ws)
76
77
  print(f"Workspace initialized in {ws}.")
77
78
  if args.demo:
78
79
  print("Demo data in demo/ — try: datacharter serve")
@@ -87,35 +88,6 @@ def _ensure_gitignore(ws: Path) -> None:
87
88
  gi.write_text(existing + joiner + GITIGNORE_BLOCK)
88
89
 
89
90
 
90
- def _write_demo_data(ws: Path) -> None:
91
- import sqlite3
92
-
93
- demo = ws / "demo"
94
- demo.mkdir(exist_ok=True)
95
- con = sqlite3.connect(str(demo / "store.db"))
96
- try:
97
- con.execute("CREATE TABLE customers (id INTEGER, email TEXT, tier TEXT)")
98
- con.executemany(
99
- "INSERT INTO customers VALUES (?, ?, ?)",
100
- [
101
- (1, "ada@example.com", "pro"),
102
- (2, "grace@example.com", "free"),
103
- (3, "edsger@example.com", "pro"),
104
- ],
105
- )
106
- con.execute("CREATE TABLE orders (customer_id INTEGER, total REAL, placed_on TEXT)")
107
- con.executemany(
108
- "INSERT INTO orders VALUES (?, ?, ?)",
109
- [
110
- ((i % 3) + 1, round((i * 37 % 200) + i * 0.5, 2), f"2026-01-{(i % 28) + 1:02d}")
111
- for i in range(90)
112
- ],
113
- )
114
- con.commit()
115
- finally:
116
- con.close()
117
-
118
-
119
91
  def _resolve_serve_workspace(directory: str) -> Path:
120
92
  """Workspace for serve; ephemeral demo when no charter.yaml (D3, no surprise writes)."""
121
93
  ws = Path(directory).resolve()
@@ -125,7 +97,7 @@ def _resolve_serve_workspace(directory: str) -> Path:
125
97
 
126
98
  demo_ws = Path(tempfile.mkdtemp(prefix="datacharter-demo-"))
127
99
  (demo_ws / "charter.yaml").write_text(DEMO_CHARTER)
128
- _write_demo_data(demo_ws)
100
+ write_demo_data(demo_ws)
129
101
  print(f"No charter.yaml in {ws} — serving an ephemeral demo workspace ({demo_ws}).")
130
102
  print("Run `datacharter init` here to start a real workspace.")
131
103
  return demo_ws
@@ -188,7 +160,8 @@ def _cmd_serve(args: argparse.Namespace) -> int:
188
160
  ws = _resolve_serve_workspace(args.directory)
189
161
  llm = None if args.offline else (_local_llm(args.model) if args.local else None)
190
162
  app = create_app(
191
- 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,
192
165
  )
193
166
  if args.offline:
194
167
  _print_attestation(ws, args.host, args.port)
@@ -231,9 +204,17 @@ def _cmd_secrets(args: argparse.Namespace) -> int:
231
204
  def _cmd_mcp(args: argparse.Namespace) -> int:
232
205
  import asyncio
233
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
+
234
216
  from datacharter.agent.tools import ToolBox
235
217
  from datacharter.contracts import load_charter
236
- from datacharter.mcp.server import serve_stdio
237
218
 
238
219
  ws = Path(args.directory).resolve()
239
220
  if not (ws / "charter.yaml").exists():
@@ -282,29 +263,9 @@ def _cmd_diff(args: argparse.Namespace) -> int:
282
263
 
283
264
  async def _scan_pii(engine) -> dict[str, list[str]]:
284
265
  """Suggest PII columns per relation: by name, then by sampled values."""
285
- from datacharter.contracts.pii import classify_pii, detect_value_pii
286
-
287
- tables = await engine.query("SHOW ALL TABLES", timeout_s=30)
288
- idx = {c: i for i, c in enumerate(tables.columns)}
289
- suggestions: dict[str, list[str]] = {}
290
- for row in tables.rows:
291
- db = row[idx["database"]]
292
- if db in ("system", "temp"):
293
- continue
294
- table = row[idx["name"]]
295
- relation = table if db == "memory" else f"{db}.{table}"
296
- columns = list(row[idx["column_names"]])
297
- flagged = set(classify_pii(columns))
298
- remaining = [c for c in columns if c not in flagged]
299
- if remaining and all(ch.isalnum() or ch in "._" for ch in relation):
300
- sample = await engine.query(f"SELECT * FROM {relation} LIMIT 25", timeout_s=30)
301
- pos = {c: i for i, c in enumerate(sample.columns)}
302
- for col in remaining:
303
- if col in pos and detect_value_pii([r[pos[col]] for r in sample.rows]):
304
- flagged.add(col)
305
- if flagged:
306
- suggestions[relation] = [c for c in columns if c in flagged]
307
- return suggestions
266
+ from datacharter.contracts.pii import detect_pii
267
+
268
+ return await detect_pii(engine)
308
269
 
309
270
 
310
271
  def _cmd_scan(args: argparse.Namespace) -> int:
@@ -629,6 +590,11 @@ def main(argv: list[str] | None = None) -> int:
629
590
  "mcp", help="Run an MCP server over stdio exposing the governed query tools"
630
591
  )
631
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
+ )
632
598
  p_mcp.set_defaults(func=_cmd_mcp)
633
599
 
634
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
 
@@ -0,0 +1,46 @@
1
+ """The demo dataset: a small sqlite `store` (customers + orders) for onboarding."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sqlite3
6
+ from pathlib import Path
7
+
8
+ # Descriptor for registering the demo as a real, deletable source (see POST /api/demo).
9
+ DEMO_SOURCE = {
10
+ "name": "store",
11
+ "type": "sqlite",
12
+ "path": "demo/store.db",
13
+ "tables": ["customers", "orders"],
14
+ "pii": {"customers": ["email"]},
15
+ }
16
+
17
+
18
+ def write_demo_data(workspace: Path) -> None:
19
+ """Write the demo `store.db` (customers + orders) under `<workspace>/demo/`."""
20
+ demo = workspace / "demo"
21
+ demo.mkdir(exist_ok=True)
22
+ con = sqlite3.connect(str(demo / "store.db"))
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")
27
+ con.execute("CREATE TABLE customers (id INTEGER, email TEXT, tier TEXT)")
28
+ con.executemany(
29
+ "INSERT INTO customers VALUES (?, ?, ?)",
30
+ [
31
+ (1, "ada@example.com", "pro"),
32
+ (2, "grace@example.com", "free"),
33
+ (3, "edsger@example.com", "pro"),
34
+ ],
35
+ )
36
+ con.execute("CREATE TABLE orders (customer_id INTEGER, total REAL, placed_on TEXT)")
37
+ con.executemany(
38
+ "INSERT INTO orders VALUES (?, ?, ?)",
39
+ [
40
+ ((i % 3) + 1, round((i * 37 % 200) + i * 0.5, 2), f"2026-01-{(i % 28) + 1:02d}")
41
+ for i in range(90)
42
+ ],
43
+ )
44
+ con.commit()
45
+ finally:
46
+ con.close()