bc-cli 0.3.0__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 (261) hide show
  1. {bc_cli-0.3.0 → bc_cli-0.4.0}/AGENTS.md +118 -7
  2. {bc_cli-0.3.0 → bc_cli-0.4.0}/CHANGELOG.md +205 -1
  3. {bc_cli-0.3.0 → bc_cli-0.4.0}/PKG-INFO +1 -1
  4. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/mcp-server.md +54 -31
  5. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/saved-queries.md +74 -1
  6. {bc_cli-0.3.0 → bc_cli-0.4.0}/pyproject.toml +1 -1
  7. bc_cli-0.4.0/src/bcli/batch/__init__.py +9 -0
  8. bc_cli-0.4.0/src/bcli/batch/ledger.py +563 -0
  9. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/client/_async.py +36 -4
  10. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/client/_transport.py +40 -6
  11. bc_cli-0.4.0/src/bcli/exit_codes.py +69 -0
  12. bc_cli-0.4.0/src/bcli/result_envelope.py +140 -0
  13. bc_cli-0.4.0/src/bcli_cli/_envelope_wrap.py +344 -0
  14. bc_cli-0.4.0/src/bcli_cli/_error_handler.py +140 -0
  15. bc_cli-0.4.0/src/bcli_cli/_progress.py +92 -0
  16. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/_safety.py +6 -5
  17. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/app.py +52 -4
  18. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/attach_cmd.py +81 -45
  19. bc_cli-0.4.0/src/bcli_cli/commands/batch_cmd.py +1089 -0
  20. bc_cli-0.4.0/src/bcli_cli/commands/delete_cmd.py +119 -0
  21. bc_cli-0.4.0/src/bcli_cli/commands/describe_cmd.py +562 -0
  22. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/extract_cmd.py +47 -0
  23. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/patch_cmd.py +61 -19
  24. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/post_cmd.py +63 -16
  25. bc_cli-0.4.0/src/bcli_cli/commands/skill_cmd.py +599 -0
  26. bc_cli-0.4.0/src/bcli_cli/commands/skill_init_cmd.py +755 -0
  27. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/output/_formatters.py +19 -10
  28. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_mcp/__main__.py +2 -2
  29. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_mcp/_runner.py +38 -32
  30. bc_cli-0.4.0/src/bcli_mcp/_server.py +243 -0
  31. bc_cli-0.4.0/src/bcli_mcp/_tool_generator.py +195 -0
  32. bc_cli-0.4.0/tests/test_batch_ledger/test_batch_cmd_ledger.py +265 -0
  33. bc_cli-0.4.0/tests/test_batch_ledger/test_idempotency_replay.py +225 -0
  34. bc_cli-0.4.0/tests/test_batch_ledger/test_ledger_idempotency.py +214 -0
  35. bc_cli-0.4.0/tests/test_batch_ledger/test_ledger_schema.py +340 -0
  36. bc_cli-0.4.0/tests/test_batch_ledger/test_rollback_cmd.py +299 -0
  37. bc_cli-0.4.0/tests/test_batch_ledger/test_state_list_cmds.py +124 -0
  38. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_batch_safety.py +13 -1
  39. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_output_format.py +9 -2
  40. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_safety.py +2 -2
  41. bc_cli-0.4.0/tests/test_describe/__init__.py +1 -0
  42. bc_cli-0.4.0/tests/test_describe/test_describe_cmd.py +493 -0
  43. bc_cli-0.4.0/tests/test_describe/test_describe_positionals_limits.py +119 -0
  44. bc_cli-0.4.0/tests/test_envelope/conftest.py +143 -0
  45. bc_cli-0.4.0/tests/test_envelope/test_batch_envelope_with_ledger.py +286 -0
  46. bc_cli-0.4.0/tests/test_envelope/test_envelope_other_verbs.py +245 -0
  47. bc_cli-0.4.0/tests/test_envelope/test_envelope_policy_violation.py +163 -0
  48. bc_cli-0.4.0/tests/test_envelope/test_envelope_post.py +289 -0
  49. bc_cli-0.4.0/tests/test_envelope/test_result_envelope.py +165 -0
  50. bc_cli-0.4.0/tests/test_errors/__init__.py +1 -0
  51. bc_cli-0.4.0/tests/test_errors/test_did_you_mean.py +116 -0
  52. bc_cli-0.4.0/tests/test_exit_codes/__init__.py +1 -0
  53. bc_cli-0.4.0/tests/test_exit_codes/test_taxonomy.py +57 -0
  54. bc_cli-0.4.0/tests/test_idempotency/__init__.py +1 -0
  55. bc_cli-0.4.0/tests/test_idempotency/conftest.py +64 -0
  56. bc_cli-0.4.0/tests/test_idempotency/test_cli_flags.py +135 -0
  57. bc_cli-0.4.0/tests/test_idempotency/test_idempotency_key.py +117 -0
  58. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_mcp/test_runner.py +66 -33
  59. bc_cli-0.4.0/tests/test_mcp/test_server_tools.py +328 -0
  60. bc_cli-0.4.0/tests/test_mcp/test_tool_generator.py +395 -0
  61. bc_cli-0.4.0/tests/test_output/__init__.py +1 -0
  62. bc_cli-0.4.0/tests/test_output/test_json_on_pipe.py +85 -0
  63. bc_cli-0.4.0/tests/test_progress/__init__.py +1 -0
  64. bc_cli-0.4.0/tests/test_progress/test_progress_events.py +157 -0
  65. bc_cli-0.4.0/tests/test_skill_init/conftest.py +147 -0
  66. bc_cli-0.4.0/tests/test_skill_init/test_skill_init_wizard.py +555 -0
  67. bc_cli-0.4.0/tests/test_skill_install/__init__.py +1 -0
  68. bc_cli-0.4.0/tests/test_skill_install/test_skill_install.py +484 -0
  69. bc_cli-0.4.0/tests/test_telemetry/__init__.py +0 -0
  70. bc_cli-0.4.0/tests/test_url/__init__.py +0 -0
  71. bc_cli-0.4.0/tests/test_workflow/__init__.py +0 -0
  72. bc_cli-0.3.0/src/bcli_cli/commands/batch_cmd.py +0 -416
  73. bc_cli-0.3.0/src/bcli_cli/commands/delete_cmd.py +0 -76
  74. bc_cli-0.3.0/src/bcli_mcp/_server.py +0 -162
  75. bc_cli-0.3.0/tests/test_mcp/test_server_tools.py +0 -215
  76. {bc_cli-0.3.0 → bc_cli-0.4.0}/.env.example +0 -0
  77. {bc_cli-0.3.0 → bc_cli-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  78. {bc_cli-0.3.0 → bc_cli-0.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  79. {bc_cli-0.3.0 → bc_cli-0.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  80. {bc_cli-0.3.0 → bc_cli-0.4.0}/.github/workflows/publish.yml +0 -0
  81. {bc_cli-0.3.0 → bc_cli-0.4.0}/.github/workflows/tests.yml +0 -0
  82. {bc_cli-0.3.0 → bc_cli-0.4.0}/.gitignore +0 -0
  83. {bc_cli-0.3.0 → bc_cli-0.4.0}/CODE_OF_CONDUCT.md +0 -0
  84. {bc_cli-0.3.0 → bc_cli-0.4.0}/CONTRIBUTING.md +0 -0
  85. {bc_cli-0.3.0 → bc_cli-0.4.0}/LICENSE +0 -0
  86. {bc_cli-0.3.0 → bc_cli-0.4.0}/NOTICE +0 -0
  87. {bc_cli-0.3.0 → bc_cli-0.4.0}/README.md +0 -0
  88. {bc_cli-0.3.0 → bc_cli-0.4.0}/SECURITY.md +0 -0
  89. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/authentication.md +0 -0
  90. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/batch-operations.md +0 -0
  91. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/business-central-admin-setup.md +0 -0
  92. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/command-reference.md +0 -0
  93. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/configuration.md +0 -0
  94. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/contributing.md +0 -0
  95. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/custom-apis.md +0 -0
  96. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/demo-setup.md +0 -0
  97. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/extraction.md +0 -0
  98. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/getting-started.md +0 -0
  99. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/multi-company.md +0 -0
  100. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/plans/team-deployment.md +0 -0
  101. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/querying.md +0 -0
  102. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/sdk-usage.md +0 -0
  103. {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/write-operations.md +0 -0
  104. {bc_cli-0.3.0 → bc_cli-0.4.0}/examples/ap-monthly-review.yaml +0 -0
  105. {bc_cli-0.3.0 → bc_cli-0.4.0}/examples/attach-purchase-invoice-pdf.yaml +0 -0
  106. {bc_cli-0.3.0 → bc_cli-0.4.0}/examples/create-purchase-invoice.yaml +0 -0
  107. {bc_cli-0.3.0 → bc_cli-0.4.0}/examples/extract/purchase_invoice_lines.yaml +0 -0
  108. {bc_cli-0.3.0 → bc_cli-0.4.0}/examples/month-end-cronus.yaml +0 -0
  109. {bc_cli-0.3.0 → bc_cli-0.4.0}/examples/queries/sample.yaml +0 -0
  110. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/__init__.py +0 -0
  111. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/_url.py +0 -0
  112. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/_version.py +0 -0
  113. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/audit/__init__.py +0 -0
  114. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/audit/_factory.py +0 -0
  115. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/audit/_protocol.py +0 -0
  116. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/audit/_redact.py +0 -0
  117. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/auth/__init__.py +0 -0
  118. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/auth/_base.py +0 -0
  119. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/auth/_browser.py +0 -0
  120. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/auth/_credentials.py +0 -0
  121. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/auth/_device_code.py +0 -0
  122. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/auth/_secure_io.py +0 -0
  123. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/auth/_token_cache.py +0 -0
  124. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/bundle/__init__.py +0 -0
  125. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/bundle/_apply.py +0 -0
  126. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/bundle/_fetch.py +0 -0
  127. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/bundle/_manifest.py +0 -0
  128. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/bundle/_publish.py +0 -0
  129. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/bundle/_verify.py +0 -0
  130. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/client/__init__.py +0 -0
  131. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/client/_safety.py +0 -0
  132. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/client/_sync.py +0 -0
  133. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/config/__init__.py +0 -0
  134. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/config/_defaults.py +0 -0
  135. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/config/_loader.py +0 -0
  136. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/config/_model.py +0 -0
  137. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/diagnostics/__init__.py +0 -0
  138. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/diagnostics/_checks.py +0 -0
  139. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/errors.py +0 -0
  140. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/etl/__init__.py +0 -0
  141. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/etl/_auth.py +0 -0
  142. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/etl/_bridge.py +0 -0
  143. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/etl/_client.py +0 -0
  144. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/etl/_generic.py +0 -0
  145. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/etl/_polaris.py +0 -0
  146. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/etl/_stampers.py +0 -0
  147. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/__init__.py +0 -0
  148. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/_claude.py +0 -0
  149. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/_factory.py +0 -0
  150. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/_openai.py +0 -0
  151. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/_pdf.py +0 -0
  152. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/_protocol.py +0 -0
  153. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/_schema.py +0 -0
  154. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/_yaml_writer.py +0 -0
  155. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/odata/__init__.py +0 -0
  156. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/odata/_escape.py +0 -0
  157. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/odata/_filter_fields.py +0 -0
  158. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/odata/_pagination.py +0 -0
  159. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/odata/_query.py +0 -0
  160. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/odata/_response.py +0 -0
  161. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/py.typed +0 -0
  162. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/registry/__init__.py +0 -0
  163. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/registry/_importers.py +0 -0
  164. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/registry/_registry.py +0 -0
  165. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/registry/_schema.py +0 -0
  166. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/registry/standard_v2.json +0 -0
  167. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/telemetry/__init__.py +0 -0
  168. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/telemetry/_azure_monitor.py +0 -0
  169. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/telemetry/_factory.py +0 -0
  170. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/telemetry/_protocol.py +0 -0
  171. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/telemetry/events.py +0 -0
  172. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/workflow/__init__.py +0 -0
  173. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/workflow/_loader.py +0 -0
  174. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/workflow/_models.py +0 -0
  175. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/workflow/_query_search.py +0 -0
  176. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/workflow/_resolver.py +0 -0
  177. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/__init__.py +0 -0
  178. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/_audit_wrap.py +0 -0
  179. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/_dry_run.py +0 -0
  180. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/_state.py +0 -0
  181. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/_url_resolve.py +0 -0
  182. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/__init__.py +0 -0
  183. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/auth_cmd.py +0 -0
  184. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/company_cmd.py +0 -0
  185. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/config_cmd.py +0 -0
  186. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/context_cmd.py +0 -0
  187. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/doctor_cmd.py +0 -0
  188. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/endpoint_cmd.py +0 -0
  189. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/env_cmd.py +0 -0
  190. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/etl_cmd.py +0 -0
  191. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/get_cmd.py +0 -0
  192. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/query_cmd.py +0 -0
  193. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/refresh_cmd.py +0 -0
  194. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/registry_cmd.py +0 -0
  195. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/test_cmd.py +0 -0
  196. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/output/__init__.py +0 -0
  197. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/output/_display.py +0 -0
  198. {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_mcp/__init__.py +0 -0
  199. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/conftest.py +0 -0
  200. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/fixtures/sample_postman_collection.json +0 -0
  201. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_audit/__init__.py +0 -0
  202. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_audit/test_factory.py +0 -0
  203. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_audit/test_redact.py +0 -0
  204. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_audit/test_sink.py +0 -0
  205. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_auth/__init__.py +0 -0
  206. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_auth/test_browser_auth.py +0 -0
  207. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_auth/test_secure_io.py +0 -0
  208. {bc_cli-0.3.0/tests/test_bundle → bc_cli-0.4.0/tests/test_batch_ledger}/__init__.py +0 -0
  209. {bc_cli-0.3.0/tests/test_cli → bc_cli-0.4.0/tests/test_bundle}/__init__.py +0 -0
  210. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_bundle/test_bundle_roundtrip.py +0 -0
  211. {bc_cli-0.3.0/tests/test_client → bc_cli-0.4.0/tests/test_cli}/__init__.py +0 -0
  212. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_audit_wrap.py +0 -0
  213. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_company_cmd.py +0 -0
  214. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_config_cmd.py +0 -0
  215. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_dry_run.py +0 -0
  216. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_pipe_handling.py +0 -0
  217. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_query_cmd.py +0 -0
  218. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_records_format.py +0 -0
  219. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_state.py +0 -0
  220. {bc_cli-0.3.0/tests/test_config → bc_cli-0.4.0/tests/test_client}/__init__.py +0 -0
  221. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_client/test_resolve_url.py +0 -0
  222. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_client/test_safety.py +0 -0
  223. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_client/test_transport.py +0 -0
  224. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_client/test_upload_attachment.py +0 -0
  225. {bc_cli-0.3.0/tests/test_diagnostics → bc_cli-0.4.0/tests/test_config}/__init__.py +0 -0
  226. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_config/test_config.py +0 -0
  227. {bc_cli-0.3.0/tests/test_etl → bc_cli-0.4.0/tests/test_diagnostics}/__init__.py +0 -0
  228. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_diagnostics/test_checks.py +0 -0
  229. {bc_cli-0.3.0/tests/test_extract → bc_cli-0.4.0/tests/test_envelope}/__init__.py +0 -0
  230. {bc_cli-0.3.0/tests/test_mcp → bc_cli-0.4.0/tests/test_etl}/__init__.py +0 -0
  231. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_etl/test_bridge.py +0 -0
  232. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_etl/test_generic.py +0 -0
  233. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_etl/test_stampers.py +0 -0
  234. {bc_cli-0.3.0/tests/test_odata → bc_cli-0.4.0/tests/test_extract}/__init__.py +0 -0
  235. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_extract/test_claude.py +0 -0
  236. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_extract/test_factory.py +0 -0
  237. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_extract/test_openai.py +0 -0
  238. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_extract/test_pdf.py +0 -0
  239. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_extract/test_schema.py +0 -0
  240. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_extract/test_yaml_writer.py +0 -0
  241. {bc_cli-0.3.0/tests/test_registry → bc_cli-0.4.0/tests/test_mcp}/__init__.py +0 -0
  242. {bc_cli-0.3.0/tests/test_telemetry → bc_cli-0.4.0/tests/test_odata}/__init__.py +0 -0
  243. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_odata/test_escape.py +0 -0
  244. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_odata/test_filter_fields.py +0 -0
  245. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_odata/test_query.py +0 -0
  246. {bc_cli-0.3.0/tests/test_url → bc_cli-0.4.0/tests/test_registry}/__init__.py +0 -0
  247. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_registry/test_caution.py +0 -0
  248. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_registry/test_importers.py +0 -0
  249. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_registry/test_metadata_fields.py +0 -0
  250. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_registry/test_registry.py +0 -0
  251. {bc_cli-0.3.0/tests/test_workflow → bc_cli-0.4.0/tests/test_skill_init}/__init__.py +0 -0
  252. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_telemetry/test_events.py +0 -0
  253. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_telemetry/test_sink.py +0 -0
  254. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_url/test_origin_allowlist.py +0 -0
  255. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_url/test_url_builder.py +0 -0
  256. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_workflow/test_batch_integration.py +0 -0
  257. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_workflow/test_loader.py +0 -0
  258. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_workflow/test_models.py +0 -0
  259. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_workflow/test_query_search.py +0 -0
  260. {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_workflow/test_resolver.py +0 -0
  261. {bc_cli-0.3.0 → bc_cli-0.4.0}/uv.lock +0 -0
@@ -161,6 +161,106 @@ user, don't loop.
161
161
 
162
162
  ---
163
163
 
164
+ ## Mutation result envelope — read this, not stdout
165
+
166
+ For real writes (not dry-run), pass `--result-out PATH` (or
167
+ `--result-fd N` on Unix harnesses) and parse the JSON envelope written
168
+ there. The envelope is the canonical record of what happened — stdout
169
+ is for human consumers; agents shouldn't scrape it.
170
+
171
+ ```bash
172
+ bcli --profile p post vendors --data '{"...": "..."}' --result-out /tmp/r.json
173
+ # then jq < /tmp/r.json
174
+ ```
175
+
176
+ The envelope is an 18-field JSON object: `invocation_id`,
177
+ `tool_version`, `profile`, `environment`, `company`, `method`,
178
+ `endpoint`, `resolved_url`, `record_id`, `dry_run`, `status`
179
+ (`succeeded` / `failed`), `exit_code`, `bc_correlation_id`,
180
+ `telemetry_event_id`, `audit_log_offset`, `started_at`,
181
+ `duration_ms`, plus `version` of the envelope schema. Atomic write
182
+ (tmp + `os.replace` + `fsync`) — the file appears whole or not at all.
183
+
184
+ **Failed envelopes** keep the same shape with `status="failed"`,
185
+ `exit_code` per the taxonomy below, and `bc_correlation_id` set when
186
+ BC returned one. Use it to diagnose without scraping a Python
187
+ traceback off stderr.
188
+
189
+ For `bcli batch run`, the envelope's `record_id` IS the ledger run id.
190
+ Pivot from there to `bcli batch state <run-id>` for per-step detail.
191
+
192
+ ## Batch operation state
193
+
194
+ `bcli batch run` writes a durable SQLite ledger that survives SIGKILL.
195
+ Three new commands let you inspect and undo runs:
196
+
197
+ ```bash
198
+ bcli batch list --format json # recent runs, newest first
199
+ bcli batch state <run-id> --format json # per-step detail for one run
200
+ bcli batch rollback <run-id> --dry-run # preview undo
201
+ bcli batch rollback <run-id> # apply undo (POST→DELETE only)
202
+ ```
203
+
204
+ `bcli batch list` filters with `--state STATE` (`completed`, `failed`,
205
+ `partially_committed`, `rolled_back`, `running`, `cancelled`). Use
206
+ `partially_committed` to find runs that died mid-way — those are where
207
+ ledger-based recovery is worth the cost.
208
+
209
+ Rollback issues `DELETE` for committed POSTs only. PATCH and DELETE
210
+ steps are marked `rollback_skipped` because there's no clean inverse
211
+ without a pre-image snapshot. `disable_writes` profiles refuse rollback
212
+ outright (no `--yes` bypass) — that's by design.
213
+
214
+ ## Exit code taxonomy
215
+
216
+ Don't treat all non-zero as "generic error." The taxonomy is
217
+ documented in `bcli describe`'s `exit_codes` field:
218
+
219
+ | Code | Meaning |
220
+ |---|---|
221
+ | 0 | success |
222
+ | 1 | generic crash / unhandled exception |
223
+ | 2 | usage error (bad flag, missing arg) |
224
+ | 3 | auth (token expired, login required) |
225
+ | 4 | not found (endpoint, record, profile) |
226
+ | 5 | validation (filter, param schema) |
227
+ | 6 | remote 4xx (BC rejected the request) |
228
+ | 7 | remote 5xx (BC server error) |
229
+ | 8 | policy refusal (`disable_writes` triggered without `--yes`) |
230
+
231
+ Key off the specific code. Exit `8` means "the profile is read-only";
232
+ prompting for `--yes` and retrying is the right move. Exit `3` means
233
+ "run `bcli auth login --profile X`." Exit `1` is the only one that
234
+ warrants "report to user and stop."
235
+
236
+ ## Idempotency keys for retries
237
+
238
+ For mutations you might retry, pass `--idempotency-key K`. The IETF
239
+ `Idempotency-Key` HTTP header goes out on the first call so any
240
+ gateway-level dedup applies; subsequent retries within the same `bcli
241
+ batch run` short-circuit through the ledger (no second HTTP, no
242
+ duplicate row).
243
+
244
+ ```bash
245
+ KEY=$(uuidgen)
246
+ bcli post vendors --data '{"...":"..."}' --idempotency-key "$KEY" --result-out r.json
247
+ # If you need to retry, reuse the same KEY — same-run replay is safe.
248
+ ```
249
+
250
+ Inside a batch YAML, declare per-step:
251
+
252
+ ```yaml
253
+ steps:
254
+ - name: create_vendor
255
+ action: post
256
+ endpoint: vendors
257
+ idempotency_key: "${{ params.vendor_no }}-2026Q2"
258
+ body: { ... }
259
+ ```
260
+
261
+ Cross-run replay is deferred (would mean scanning every ledger DB); the
262
+ header is sent on every retry so gateway-level dedup remains in play.
263
+
164
264
  ## Dry-run before writes
165
265
 
166
266
  Before any `post` / `patch` / `delete` / `attach upload`, run with `--dry-run`
@@ -226,15 +326,26 @@ on, and the CLI exit code is the answer when it's off.
226
326
 
227
327
  If the user has mounted `bcli-mcp` (see [`docs/mcp-server.md`](docs/mcp-server.md)),
228
328
  prefer those tools — they collapse discovery + query into single calls
229
- with structured results:
230
-
231
- - `list_endpoints()` full registry as JSON
232
- - `describe_endpoint(name, discover_fields=True)` metadata + field
233
- discovery in one call
234
- - `query(endpoint, filter, ...)` — the same as `bcli get`, but typed
329
+ with structured results. As of 0.4.0 the MCP server generates 23 tools
330
+ dynamically from `bcli describe`, including five new mutating verbs
331
+ (`bcli_post`, `bcli_patch`, `bcli_delete`, `bcli_attach_upload`,
332
+ `bcli_batch_run`) that internally pass `--result-out` and return the
333
+ envelope as the tool result.
334
+
335
+ **Tool names match the CLI command path** (`bcli_get`,
336
+ `bcli_endpoint_list`, `bcli_endpoint_info`, `bcli_endpoint_fields`,
337
+ `bcli_company_list`, …). The pre-0.4.0 names (`query`,
338
+ `list_endpoints`, `describe_endpoint`, `list_companies`) are gone — see
339
+ the migration table in [`docs/mcp-server.md`](docs/mcp-server.md) if
340
+ your client config references the old names.
341
+
342
+ For mutating tools, a `status="failed"` envelope surfaces as MCP
343
+ `ToolError` with the BC correlation id quoted in the error message —
344
+ you don't need to read the envelope separately for failures.
235
345
 
236
346
  The CLI recipes above still work fine if the MCP server isn't
237
- available; this is purely an "if you've got it, use it" optimization.
347
+ available; the MCP tools are an "if you've got them, use them"
348
+ optimization.
238
349
 
239
350
  ---
240
351
 
@@ -7,6 +7,159 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] — 2026-05-18 — Agent Interface Profile v0.1
11
+
12
+ The Agent Interface Profile (AIP) v0.1 lands: a small kernel of CLI
13
+ primitives that any agent runtime can drive deterministically, without
14
+ parallel schemas or hand-written MCP tools.
15
+
16
+ ### Added
17
+
18
+ - **`bcli describe --format json`** — canonical machine-readable
19
+ projection of the live Typer surface + endpoint registry + active
20
+ profile. One command MCP, completions, and docs all consume; new CLI
21
+ commands light up automatically. Cached at
22
+ `~/.config/bcli/describe/<profile>.<hash>.json` with mtime
23
+ invalidation. Subtree mode (`bcli describe get`, `bcli describe batch
24
+ run`) returns narrow output for token-constrained agents. Includes
25
+ forward-compat declarations: `emits_result_envelope`,
26
+ `emits_operation_state`, `requires_confirmation: "production"`, plus
27
+ the new `exit_codes` taxonomy and per-command `positionals` /
28
+ `required` / `limits` extensions.
29
+ - **Mutation result envelope (`--result-out PATH` / `--result-fd N`)**
30
+ on every mutating verb (`post`, `patch`, `delete`, `attach upload`,
31
+ `batch run`). Frozen 18-field JSON envelope written atomically
32
+ (`os.replace` + `fsync`); contains profile, environment, company,
33
+ method, endpoint, resolved URL, record id, status, exit code, BC
34
+ correlation id, started_at, duration_ms. Failed envelopes carry the
35
+ exit code (4 = not found, 6 = remote 4xx, 7 = remote 5xx, 8 = policy
36
+ refusal, etc.) so an agent can read it side-channel and act without
37
+ scraping stdout. For `batch run` the envelope's `record_id` is the
38
+ ledger run id — pivot directly to `bcli batch state <run-id>` for
39
+ per-step detail.
40
+ - **Batch operation ledger (SQLite)** — one
41
+ `~/.config/bcli/batch/<run-id>.db` per `bcli batch run` invocation.
42
+ WAL + `synchronous=NORMAL`, intent row written before each HTTP call
43
+ (survives SIGKILL); outcome row after. Derived run state
44
+ distinguishes `partially_committed` from a stale `running` stamp. New
45
+ commands: `bcli batch state <run-id>`, `bcli batch list [--state
46
+ STATE] [--limit N]`, `bcli batch rollback <run-id> [--dry-run]
47
+ [--yes]`. Rollback issues `DELETE` for committed POSTs only; PATCH /
48
+ DELETE marked `rollback_skipped` (no clean inverse without pre-image
49
+ snapshots). `disable_writes` is a hard refusal on rollback (no
50
+ `--yes` bypass).
51
+ - **Exit code taxonomy** — `bcli.exit_codes` defines 0/1/2/3/4/5/6/7/8
52
+ with short labels; `bcli describe` projects the map; centralized
53
+ error handler maps `BCLIError` subclasses (`AuthError → 3`,
54
+ `RegistryError → 4`, `ValidationError → 5`, `ConfigError → 2`,
55
+ `SafetyError → 8`).
56
+ - **"Did you mean" remediation hints** on `BCLIError` paths: auth →
57
+ `Run 'bcli auth login --profile X'`, config (no profiles) →
58
+ `Run 'bcli config init'`, config (unknown profile) → fuzzy match,
59
+ registry (no fuzzy) → `Run 'bcli registry import …'`.
60
+ - **JSON on pipe by default** — when stdout isn't a TTY and no
61
+ `--format` was passed, emit JSON. Pipelines, redirects, CI steps,
62
+ agent runtimes all get the canonical machine-readable shape with no
63
+ flag dance. The `CLAUDECODE` and `BCLI_AGENT` env hints keep their
64
+ markdown semantics (explicit user opt-in); legacy Windows console
65
+ host stays on markdown for the mojibake reason. `BCLI_FORMAT` and
66
+ explicit `--format` always win.
67
+ - **`--idempotency-key KEY`** on `post`, `patch`, `delete`, `attach
68
+ upload`. IETF `Idempotency-Key` HTTP header sent on the first call
69
+ (gateway-level dedup remains in play). Same-run replay protection in
70
+ `bcli batch run`: if two mutating steps share an `idempotency_key:`
71
+ in the YAML, the second is replayed (no second HTTP, no duplicate
72
+ ledger row), and the result entry carries `prior_seq`,
73
+ `prior_step_id`, `prior_bc_correlation_id`. Ledger schema migrates
74
+ v1 → v2 non-destructively via `ALTER TABLE step ADD COLUMN
75
+ idempotency_key`.
76
+ - **Progress events (`--progress-fd N`)** on `bcli batch run` and
77
+ `bcli extract run`. JSON-lines `step_started` / `step_completed`
78
+ written to a dedicated fd (separate from `--result-fd`). Stderr
79
+ stays human-readable; the fd channel is structured and stable for
80
+ agents to demux. Replayed steps emit a synthetic pair with
81
+ `status="replayed"` so the progress stream tells the truth.
82
+ - **23 dynamically-generated MCP tools** in `bcli_mcp` (was 4
83
+ hand-written). Server subprocesses `bcli describe` once on startup
84
+ and registers one tool per command; new CLI commands light up as
85
+ MCP tools automatically. Five new mutating tools (`bcli_post`,
86
+ `bcli_patch`, `bcli_delete`, `bcli_attach_upload`, `bcli_batch_run`)
87
+ pass `--result-out` and return the envelope as their tool result.
88
+ `status="failed"` envelopes surface as MCP `ToolError` with the BC
89
+ correlation id quoted.
90
+ - **`AsyncBCClient.delete_url(url, *, etag="*")`** — new SDK method
91
+ for absolute-URL deletes (used by the rollback path; avoids
92
+ re-resolving the registry at undo time).
93
+ - **`bcli skill install`** — generates `.claude/commands/bcli-<name>.md`
94
+ per saved query and per batch template (`~/.config/bcli/batches/
95
+ <profile>/*.yaml`). Generates a top-level
96
+ `.claude/skills/bcli/SKILL.md` index grouped by `categories:`.
97
+ SHA-256 content hash embedded in the provenance comment for
98
+ byte-stable idempotency — no `generated_at` timestamp, so re-runs on
99
+ unchanged sources are mtime-preserving no-ops. `manual: true` in a
100
+ file's YAML frontmatter protects it from regeneration. `--dry-run`
101
+ previews; `--target` resolves to explicit path > CWD with `.claude/`
102
+ > `$HOME`. Stdlib only — no jinja2; atomic writes via
103
+ `tempfile.mkstemp` + `os.replace`.
104
+ - **`bcli skill init`** — interactive wizard that reads `bcli describe
105
+ --format json` via subprocess, runs 4 Rich prompts (role / top-three
106
+ daily questions / slash-command style / generate-new-queries y/N),
107
+ fuzzy-matches existing saved queries against the top-three free
108
+ text (stdlib `difflib`), and proposes new role-tailored queries via
109
+ entry-point providers — each with a per-query `[y/N]` approval gate.
110
+ Generates `~/.claude/skills/bcli-<user>/SKILL.md` with YAML
111
+ provenance frontmatter. Atomic commit phase: snapshots existing
112
+ content, writes all targets, restores on first failure. Guardrails
113
+ via `_assert_writable` restrict writes to `~/.config/bcli/queries/`,
114
+ `~/.claude/skills/bcli-<user>/`, and `~/.config/bcli/skills/`;
115
+ symlink-safe via `Path.resolve(strict=False)` + `is_relative_to`.
116
+ - **`bcli skill update`** — idempotent re-run via state cache at
117
+ `~/.config/bcli/skills/.last-init.json`. The cache persists both
118
+ the interview answers AND the approved-query bodies so a later
119
+ `--non-interactive` replay re-writes the same queries verbatim
120
+ (without re-asking the operator). Describe-payload-hash mismatch
121
+ refuses silent replay and asks for a re-interview when the describe
122
+ surface changed under the user's feet.
123
+ - **Saved-query YAML schema extension** — three additive fields
124
+ (`description`, `categories`, `args`). Existing saved-query bundles
125
+ without these still work: when `args:` is omitted, `bcli skill
126
+ install` derives it from `params:` keys (required first, optional
127
+ second, both in YAML insertion order). Documented in
128
+ `docs/saved-queries.md`'s new "Slash-command projection" section.
129
+ - **Entry-point group `bcli.skill_init.role_templates`** — OSS bcli
130
+ ships with an opinion-free default proposer (returns `[]` for every
131
+ role). Downstream packages plug in role templates by registering
132
+ callables under this entry-point group via standard Python
133
+ packaging. Discovered at wizard time via
134
+ `importlib.metadata.entry_points`. The provider signature is
135
+ `(interview, payload) -> list[ProposedQuery]`; downstream
136
+ integrators publish their own integration documentation.
137
+
138
+ ### Changed
139
+
140
+ - **Policy refusal exit code 1 → 8.** Scripts that grep `if exit==1`
141
+ for "the read-only profile blocked me" need updating. Agents
142
+ consuming `bcli describe`'s new `exit_codes` field pick up the new
143
+ code automatically.
144
+ - **MCP tool renames** (breaking for existing MCP clients — Claude
145
+ Desktop, MCP Inspector configs referencing the old names need an
146
+ update):
147
+ - `query` → `bcli_get`
148
+ - `list_endpoints` → `bcli_endpoint_list`
149
+ - `describe_endpoint` → `bcli_endpoint_info` (with
150
+ `bcli_endpoint_fields` split out for field discovery)
151
+ - `list_companies` → `bcli_company_list`
152
+
153
+ Migration table in `docs/mcp-server.md`. Tool names now consistently
154
+ match the CLI command path.
155
+ - **MCP `bcli_get --top` cap remains 50 default / 1000 max** — parity
156
+ with the pre-rewrite hand-written `query` tool. CLI shell users can
157
+ still pass `--top 100000` directly; the cap is enforced only at the
158
+ MCP schema level (advisory for agent runtimes).
159
+ - **Stderr routing for `bcli batch run` metadata** — the ledger path
160
+ and run id print to stderr, not stdout. Stdout matches legacy batch
161
+ output byte-for-byte (required by the additive constraint).
162
+
10
163
  ### Fixed
11
164
 
12
165
  - **Clean SIGPIPE handling for piped output** — `bcli <cmd> | head`,
@@ -24,6 +177,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
24
177
  rows). Affects both `bcli q` saved queries and `bcli batch`
25
178
  workflows.
26
179
 
180
+ ### Breaking changes
181
+
182
+ The two items above (exit code 1→8 + MCP tool renames) are intentional
183
+ breaking changes called out in the AIP plan. Both have one-line
184
+ migration paths. Skill install / skill init / skill update are
185
+ additive — no breaking changes from the Skills layer.
186
+
187
+ ### Deferred to v0.5
188
+
189
+ - **Cross-run idempotency replay** — would require scanning every
190
+ `*.db` in `~/.config/bcli/batch/` on each mutating call. Same-run
191
+ protection covers the agent-retry case which is the common one;
192
+ gateway-level Idempotency-Key dedup covers the rest.
193
+ - **`batch run --idempotency-key`** as a run-level flag — collides
194
+ across multiple mutating steps. Per-step `idempotency_key:` in the
195
+ batch YAML is the correct surface.
196
+ - **`telemetry_event_id` / `audit_log_offset` on the envelope** —
197
+ currently always `null`. Wiring requires extending the
198
+ `TelemetrySink` and audit protocols to return the emitted event id /
199
+ log offset, touching every backend including the optional Azure
200
+ Monitor extra.
201
+ - **Plan-token binding for single mutations** — `batch run --plan-out`
202
+ works; `bcli post --plan-out` does not. Defer until requested.
203
+ - **Direct FastMCP schema-introspection test** — the `__signature__`
204
+ patch is indirectly covered via tool-list registration tests; a
205
+ future FastMCP upgrade could silently degrade tool input schemas
206
+ without breaking tests.
207
+ - **Etag capture in ledger** — rollback DELETEs use `etag="*"`. A
208
+ future concurrent edit between POST and rollback could clobber.
209
+ - **CWD-relative batch template discovery** for `bcli skill install` —
210
+ today only `~/.config/bcli/batches/<profile>/*.yaml` is scanned;
211
+ project-local batches in `./batches/` would also be useful for the
212
+ per-project `.claude/` workflow.
213
+ - **SKILL.md frontmatter `generated_at` churn cleanup** — the
214
+ timestamp ticks forward on every `bcli skill update
215
+ --non-interactive` replay, so the frontmatter changes even when the
216
+ body is byte-stable. Idempotency tests compare body-only; downstream
217
+ content-hash watchers would see noise. Cosmetic.
218
+ - **`bcli skill update` separated from `init`** — today
219
+ `update_command` delegates to `init_command(...)` verbatim.
220
+ Documented for future evolution.
221
+ - **Public `bcli.skill_init` namespace for the entry-point contract**
222
+ — downstream packages currently couple to
223
+ `bcli_cli.commands.skill_init_cmd` for `InterviewState` /
224
+ `ProposedQuery`. A future release could promote the protocol types
225
+ to a public `bcli.skill_init` namespace.
226
+
27
227
  ## [0.2.0] — 2026-05-06
28
228
 
29
229
  ### Added
@@ -249,6 +449,10 @@ moved to `bcli` (April 2026), then to the PyPI distribution name
249
449
  `bc-cli` (April 2026, after discovering the `bcli` PyPI name was
250
450
  squatted by an unrelated 2018 package).
251
451
 
252
- [Unreleased]: https://github.com/igor-ctrl/bcli/compare/v0.1.1...HEAD
452
+ [Unreleased]: https://github.com/igor-ctrl/bcli/compare/v0.4.0...HEAD
453
+ [0.4.0]: https://github.com/igor-ctrl/bcli/compare/v0.2.0...v0.4.0
454
+ [0.2.0]: https://github.com/igor-ctrl/bcli/compare/v0.1.5...v0.2.0
455
+ [0.1.5]: https://github.com/igor-ctrl/bcli/compare/v0.1.2...v0.1.5
456
+ [0.1.2]: https://github.com/igor-ctrl/bcli/compare/v0.1.1...v0.1.2
253
457
  [0.1.1]: https://github.com/igor-ctrl/bcli/compare/v0.1.0...v0.1.1
254
458
  [0.1.0]: https://github.com/igor-ctrl/bcli/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bc-cli
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Python SDK and CLI for Microsoft Dynamics 365 Business Central APIs
5
5
  Project-URL: Homepage, https://github.com/igor-ctrl/bcli
6
6
  Project-URL: Repository, https://github.com/igor-ctrl/bcli
@@ -7,9 +7,17 @@ server that lets MCP-aware clients drive bcli. The intended caller is Claude
7
7
  Desktop, but it also works with the official [MCP Inspector](https://github.com/modelcontextprotocol/inspector)
8
8
  and any other client that speaks the spec.
9
9
 
10
- The server is deliberately small (4 read-only tools) and delegates every call
11
- to the bcli CLI as a subprocess. Profile resolution, auth, retry, telemetry,
12
- and the read-only `disable_writes` gate are inherited from the CLI for free.
10
+ The server generates its tool list **dynamically** by subprocessing
11
+ `bcli describe --format json` once on startup, and delegates every call to the
12
+ bcli CLI as a subprocess. Profile resolution, auth, retry, telemetry, and the
13
+ read-only `disable_writes` gate are inherited from the CLI for free. New CLI
14
+ commands light up automatically as MCP tools; deprecated ones disappear.
15
+
16
+ Read commands return parsed stdout JSON. Mutating commands (`bcli_post`,
17
+ `bcli_patch`, `bcli_delete`, `bcli_attach_upload`, `bcli_batch_run`) pass
18
+ `--result-out <tmp>` and return the AIP §Phase 2 result envelope content as
19
+ the tool result. A `status="failed"` envelope surfaces as an MCP `ToolError`
20
+ with the BC correlation id quoted so the agent can cite it.
13
21
 
14
22
  ## Install
15
23
 
@@ -47,8 +55,15 @@ Add a `mcpServers` entry to `~/Library/Application Support/Claude/claude_desktop
47
55
  }
48
56
  ```
49
57
 
50
- Restart Claude Desktop. You should see four tools register: `query`,
51
- `list_endpoints`, `describe_endpoint`, `list_companies`.
58
+ Restart Claude Desktop. The server registers one tool per command in
59
+ `bcli describe`'s output — typically ~20 tools, one per read/mutating verb in
60
+ the CLI. Use the MCP Inspector to enumerate them, or run
61
+ `bcli describe --format json` directly to preview what'll appear.
62
+
63
+ Renames from the pre-Phase-5 surface: the old hand-written `query`,
64
+ `list_endpoints`, `describe_endpoint`, and `list_companies` are now
65
+ `bcli_get`, `bcli_endpoint_list`, `bcli_endpoint_info`, and `bcli_company_list`
66
+ respectively (matching the CLI subcommand paths).
52
67
 
53
68
  If `bcli-mcp` isn't on Claude Desktop's PATH (uv tool install paths can be
54
69
  tricky), use the full path:
@@ -66,17 +81,25 @@ tricky), use the full path:
66
81
 
67
82
  ## Tool surface
68
83
 
84
+ The tool list is generated from `bcli describe --format json` on startup, so
85
+ the canonical reference is the describe output for your install. The most
86
+ useful subset:
87
+
69
88
  | Tool | What it does | Notes |
70
89
  |------|--------------|-------|
71
- | `query` | Run an OData query against an entity. | `top` defaults to 50, capped at 1000. Use `select` to keep payloads small. |
72
- | `list_endpoints` | List entities the active profile can reach. | Honours `disable_standard_api`, `allowed_categories`, `allowed_endpoints`. |
73
- | `describe_endpoint` | Show fields, key, supported ops, and route for one entity. | `fields` is populated only after `bcli endpoint fields <name>` has been run. |
74
- | `list_companies` | Companies on the active environment. | Returns `[{id, name, alias, is_default}]`. |
75
-
76
- Mutating commands (`post` / `patch` / `delete`), file uploads, batch runs, and
77
- admin/setup flows are deliberately not exposed. Claude can always fall back to
78
- `Bash` + `bcli` directly when those are needed and that path trips the
79
- existing `disable_writes` confirmation prompt.
90
+ | `bcli_get` | Run an OData query against an entity. | `top` defaults to 50, capped at 1000 (carried from describe's `limits`). Use `select` to keep payloads small. |
91
+ | `bcli_endpoint_list` | List entities the active profile can reach. | Honours `disable_standard_api`, `allowed_categories`, `allowed_endpoints`. |
92
+ | `bcli_endpoint_info` | Show fields, key, supported ops, and route for one entity. | `fields` is populated only after `bcli endpoint fields <name>` has been run. |
93
+ | `bcli_endpoint_fields` | Discover the fields for one entity and persist them to the local registry. | One BC API call; populates the cache for every future call. |
94
+ | `bcli_company_list` | Companies on the active environment. | Returns `[{id, name, alias, is_default}]`. |
95
+ | `bcli_q` | Run a saved query by name with `${{ params.X }}` substitution. | The "daily questions" surface — hides OData syntax. |
96
+ | `bcli_post` / `bcli_patch` / `bcli_delete` | Mutating verbs. | Server passes `--result-out <tmp>` and returns the AIP §Phase 2 result envelope as the tool result. `status="failed"` raises `ToolError` with the BC correlation id. |
97
+ | `bcli_attach_upload` | Two-phase document attachment upload. | Same envelope contract as the other mutating verbs. |
98
+ | `bcli_batch_run` | Execute a YAML batch file. | Returns an envelope whose `record_id` is the batch ledger run id; pivot to `bcli_describe`'s batch state subcommand for per-step detail. |
99
+ | `bcli_describe` | Re-emit the full describe payload. | The MCP server itself uses this on startup; tools can call it to discover what else is exposed. |
100
+
101
+ `auth login`, `config init`, and other interactive commands (`effects:
102
+ ["other"]` in describe) are filtered out — those are command-line-only.
80
103
 
81
104
  ## Trust model — why the server resets cwd
82
105
 
@@ -95,20 +118,20 @@ sources are then exactly:
95
118
  Per-tool calls do not honour a per-request `cwd` argument. The server runs
96
119
  with a single fixed working directory for its lifetime.
97
120
 
98
- ## BC query objects vs entity pages — what to expect from `query()`
121
+ ## BC query objects vs entity pages — what to expect from `bcli_get`
99
122
 
100
123
  Not every endpoint in BC's OData surface is a fully-featured entity. Some
101
124
  are "query objects" — read-only summary pages exposed via OData (e.g.
102
125
  `customerSales`, `vendorPurchases`). They behave like entities for `GET`
103
126
  but Microsoft's runtime drops `$orderby` and `$filter` support on most of
104
- them. A `query()` call against one of these with `orderby=` or `filter=`
127
+ them. A `bcli_get` call against one of these with `orderby=` or `filter=`
105
128
  will 400 from BC.
106
129
 
107
130
  How to recognise one: there's no flag in the registry today (it'd require
108
131
  a hint per endpoint), so the practical signal is the 400 itself. The
109
132
  recovery pattern that works:
110
133
 
111
- 1. `query(entity="customerSales", top=1000)` — pull a bounded page.
134
+ 1. `bcli_get(endpoint="customerSales", top=1000)` — pull a bounded page.
112
135
  2. Sort the result client-side in your reasoning step.
113
136
  3. Take the top N.
114
137
 
@@ -119,24 +142,22 @@ you may miss the actual top customer. For BC tenants with very large
119
142
  summary pages, fall back to `bcli get …` via Bash with `--all` (which
120
143
  follows pagination).
121
144
 
122
- Entity pages (most of `bcli endpoint list`) support full OData. Use
123
- `describe_endpoint(name)` to see whether `fields_discovered` is `true`
145
+ Entity pages (most of `bcli_endpoint_list`) support full OData. Use
146
+ `bcli_endpoint_info(name)` to see whether `fields_discovered` is `true`
124
147
  and what `fields` look like; the registry doesn't currently track which
125
148
  endpoints are query objects vs entities, so you'll learn this empirically.
126
149
 
127
- ## Discovering field names — `discover_fields`
150
+ ## Discovering field names
128
151
 
129
- `describe_endpoint(name)` returns `fields: []` and `fields_discovered:
152
+ `bcli_endpoint_info(name)` returns `fields: []` and `fields_discovered:
130
153
  false` if the local registry hasn't probed BC for that entity yet. Two
131
154
  ways to recover, in order of cost:
132
155
 
133
- 1. Cheapest: call `query(entity=name, top=1)` once and read the keys off
134
- the returned record. No registry mutation, zero cache pollution.
135
- 2. One-time: pass `discover_fields=true` to `describe_endpoint`. The
136
- tool runs `bcli endpoint fields <name>` first, which fetches one
137
- record, persists the field names to the local registry, and then
138
- returns the populated metadata. Every subsequent call (any user, any
139
- session) gets `fields_discovered: true` for free.
156
+ 1. Cheapest: call `bcli_get(endpoint=name, top=1)` once and read the keys
157
+ off the returned record. No registry mutation, zero cache pollution.
158
+ 2. One-time: call `bcli_endpoint_fields(name)`. That fetches one record,
159
+ persists the field names to the local registry, and every subsequent
160
+ call (any user, any session) gets `fields_discovered: true` for free.
140
161
 
141
162
  Pick (1) for one-shot analysis. Pick (2) when the entity is one you'll
142
163
  revisit a lot — the registry-cached field list also feeds bcli's
@@ -148,9 +169,11 @@ Pairing an MCP server with a CLI tool is empirically token-favorable for
148
169
  **bounded, schema-stable** responses. The OSS server ships with two
149
170
  guard-rails baked in:
150
171
 
151
- * `query.top` defaults to 50 (max 1000) — an unbounded request can't pull a
152
- whole table into context.
153
- * Tool docstrings are short. The schema-payload Claude sees is small.
172
+ * `bcli_get.top` defaults to 50 (max 1000) — the safety bound is carried from
173
+ `bcli describe`'s `limits` field, so an unbounded request can't pull a whole
174
+ table into context.
175
+ * Tool descriptions come straight from the CLI's docstrings. They're short by
176
+ construction — the schema-payload Claude sees is small.
154
177
 
155
178
  It is **not** universally a token win. For browse-style "show me everything"
156
179
  workflows, falling back to `bcli get <entity> --format markdown` via Bash is
@@ -59,7 +59,9 @@ queries:
59
59
 
60
60
  | Field | Type | Notes |
61
61
  |----------------|----------|----------------------------------------------------------|
62
- | `description` | string | Shown in `bcli q` listing. |
62
+ | `description` | string | Shown in `bcli q` listing **and** the generated slash command's frontmatter. |
63
+ | `categories` | list[str] | Optional. Used by `bcli skill install` to group commands in the generated `SKILL.md` index. Falls back to `["unsorted"]`. |
64
+ | `args` | list[obj] | Optional. Explicit positional ordering for the generated slash command. If omitted, inferred from `params:` keys (required first, optional second). See *Slash-command projection* below. |
63
65
  | `endpoint` | string | Required. Entity-set name (resolved through the registry).|
64
66
  | `params` | mapping | Optional. Each key declares a parameter; see *Param declarations* below. |
65
67
  | `filter` | string | OData `$filter`. Supports `${{ params.X }}` substitution. |
@@ -162,3 +164,74 @@ bcli --profile ops q items-low-stock min=10
162
164
  * `--format` — override the active profile's output format (`json`,
163
165
  `markdown`, `csv`, `ndjson`, `table`).
164
166
  * `--dry-run` (global) — skips execution after resolving.
167
+
168
+ ## Slash-command projection (`bcli skill install`)
169
+
170
+ `bcli skill install` reads the saved queries for the active profile and
171
+ generates one Claude Code slash command per query at
172
+ `<target>/.claude/commands/bcli-<name>.md`, plus a top-level skill index
173
+ at `<target>/.claude/skills/bcli/SKILL.md` grouped by `categories:`.
174
+
175
+ Three optional fields on each query feed the generator:
176
+
177
+ ```yaml
178
+ queries:
179
+ utilization-by-esn:
180
+ description: Engine utilization (cycles, hours, FSN) for an ESN
181
+ categories: [aviation, daily-ops]
182
+ args:
183
+ - name: esn
184
+ type: string
185
+ example: "424322"
186
+ required: true
187
+ # existing fields below — params/filter/select/etc.
188
+ endpoint: util_history
189
+ params:
190
+ esn: {required: true}
191
+ filter: "engine_serial eq '${{ params.esn }}'"
192
+ ```
193
+
194
+ * `description` — used as the slash command's frontmatter `description:`
195
+ and listed under its category in `SKILL.md`.
196
+ * `categories` — list of strings. Each category becomes a section in
197
+ `SKILL.md`. Queries with no categories land under `unsorted`.
198
+ * `args` — explicit positional ordering for the generated slash command
199
+ body. Each entry: `{name, type, required, example}`. **If omitted, the
200
+ generator derives `args:` from `params:` keys** (required first,
201
+ optional with `default:` second, both in YAML insertion order). For
202
+ most queries you can leave `args:` out and the projected command will
203
+ still work; declare it explicitly when you want a different ordering
204
+ than the params dict gives you.
205
+
206
+ The generated command body invokes `bcli q <name> arg1=$1 arg2=$2 …
207
+ --format json`, so the positional → key mapping in the slash command
208
+ matches your `args:` order.
209
+
210
+ ### Idempotency and manual overrides
211
+
212
+ * Each generated file embeds a `content_hash: sha256:…` line in its
213
+ provenance comment. Re-running `bcli skill install` on unchanged
214
+ sources is a no-op (hash matches → file isn't rewritten).
215
+ * To protect a hand-edited slash command file from regeneration, add
216
+ `manual: true` to its YAML frontmatter:
217
+ ```markdown
218
+ ---
219
+ manual: true
220
+ description: My customised command
221
+ ---
222
+ ```
223
+ The installer skips any file whose frontmatter declares `manual: true`,
224
+ even if a saved query by the same name exists.
225
+ * Add `--dry-run` to preview without writing; add `--target PATH` to
226
+ point at a specific project root (defaults to CWD when it contains a
227
+ `.claude/` directory, else `$HOME`).
228
+
229
+ ### Batch templates
230
+
231
+ Batch workflow YAMLs under `~/.config/bcli/batches/<profile>/*.yaml` are
232
+ projected the same way as saved queries — one
233
+ `.claude/commands/bcli-batch-<name>.md` per file, body invoking
234
+ `bcli batch run <yaml> --set arg=$1 --format json --result-out …`.
235
+
236
+ CWD-relative batch discovery (a `batches/` directory checked into a
237
+ project repo) is a follow-up — open an issue if you need it.
@@ -9,7 +9,7 @@ build-backend = "hatchling.build"
9
9
  # installed CLI binary (`bcli`) are unaffected — only `pip install` /
10
10
  # `uv tool install` use this name.
11
11
  name = "bc-cli"
12
- version = "0.3.0"
12
+ version = "0.4.0"
13
13
  description = "Python SDK and CLI for Microsoft Dynamics 365 Business Central APIs"
14
14
  readme = "README.md"
15
15
  license = "Apache-2.0"
@@ -0,0 +1,9 @@
1
+ """Batch operation ledger (Phase 3 of AIP v0.1).
2
+
3
+ A durable SQLite ledger that records every batch run's intent + outcome
4
+ per step. Survives ``SIGKILL`` because the intent row is written
5
+ *before* the HTTP call, and ``PRAGMA synchronous=NORMAL`` keeps WAL
6
+ honest at commit time.
7
+ """
8
+
9
+ from bcli.batch.ledger import Ledger, RunLedgerExistsError # noqa: F401