bc-cli 0.2.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.
- {bc_cli-0.2.0 → bc_cli-0.4.0}/.gitignore +3 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/AGENTS.md +118 -7
- bc_cli-0.4.0/CHANGELOG.md +458 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/PKG-INFO +14 -1
- bc_cli-0.4.0/docs/extraction.md +236 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/docs/mcp-server.md +54 -31
- bc_cli-0.4.0/docs/plans/team-deployment.md +309 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/docs/saved-queries.md +74 -1
- bc_cli-0.4.0/examples/extract/purchase_invoice_lines.yaml +61 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/pyproject.toml +15 -2
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/__init__.py +2 -0
- bc_cli-0.4.0/src/bcli/batch/__init__.py +9 -0
- bc_cli-0.4.0/src/bcli/batch/ledger.py +563 -0
- bc_cli-0.4.0/src/bcli/bundle/__init__.py +47 -0
- bc_cli-0.4.0/src/bcli/bundle/_apply.py +323 -0
- bc_cli-0.4.0/src/bcli/bundle/_fetch.py +197 -0
- bc_cli-0.4.0/src/bcli/bundle/_manifest.py +127 -0
- bc_cli-0.4.0/src/bcli/bundle/_publish.py +110 -0
- bc_cli-0.4.0/src/bcli/bundle/_verify.py +166 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/client/_async.py +36 -4
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/client/_transport.py +40 -6
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/config/_model.py +48 -0
- bc_cli-0.4.0/src/bcli/diagnostics/__init__.py +26 -0
- bc_cli-0.4.0/src/bcli/diagnostics/_checks.py +510 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/errors.py +4 -0
- bc_cli-0.4.0/src/bcli/exit_codes.py +69 -0
- bc_cli-0.4.0/src/bcli/extract/__init__.py +43 -0
- bc_cli-0.4.0/src/bcli/extract/_claude.py +248 -0
- bc_cli-0.4.0/src/bcli/extract/_factory.py +106 -0
- bc_cli-0.4.0/src/bcli/extract/_openai.py +318 -0
- bc_cli-0.4.0/src/bcli/extract/_pdf.py +68 -0
- bc_cli-0.4.0/src/bcli/extract/_protocol.py +97 -0
- bc_cli-0.4.0/src/bcli/extract/_schema.py +208 -0
- bc_cli-0.4.0/src/bcli/extract/_yaml_writer.py +173 -0
- bc_cli-0.4.0/src/bcli/result_envelope.py +140 -0
- bc_cli-0.4.0/src/bcli/workflow/_query_search.py +154 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/workflow/_resolver.py +4 -2
- bc_cli-0.4.0/src/bcli_cli/_envelope_wrap.py +344 -0
- bc_cli-0.4.0/src/bcli_cli/_error_handler.py +140 -0
- bc_cli-0.4.0/src/bcli_cli/_progress.py +92 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/_safety.py +6 -5
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/_state.py +5 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/app.py +95 -2
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/commands/attach_cmd.py +81 -45
- bc_cli-0.4.0/src/bcli_cli/commands/batch_cmd.py +1089 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/commands/config_cmd.py +10 -0
- bc_cli-0.4.0/src/bcli_cli/commands/delete_cmd.py +119 -0
- bc_cli-0.4.0/src/bcli_cli/commands/describe_cmd.py +562 -0
- bc_cli-0.4.0/src/bcli_cli/commands/doctor_cmd.py +171 -0
- bc_cli-0.4.0/src/bcli_cli/commands/extract_cmd.py +240 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/commands/get_cmd.py +2 -1
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/commands/patch_cmd.py +61 -19
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/commands/post_cmd.py +63 -16
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/commands/query_cmd.py +260 -20
- bc_cli-0.4.0/src/bcli_cli/commands/refresh_cmd.py +298 -0
- bc_cli-0.4.0/src/bcli_cli/commands/skill_cmd.py +599 -0
- bc_cli-0.4.0/src/bcli_cli/commands/skill_init_cmd.py +755 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/output/_formatters.py +121 -12
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_mcp/__main__.py +2 -2
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_mcp/_runner.py +38 -32
- bc_cli-0.4.0/src/bcli_mcp/_server.py +243 -0
- bc_cli-0.4.0/src/bcli_mcp/_tool_generator.py +195 -0
- bc_cli-0.4.0/tests/test_batch_ledger/test_batch_cmd_ledger.py +265 -0
- bc_cli-0.4.0/tests/test_batch_ledger/test_idempotency_replay.py +225 -0
- bc_cli-0.4.0/tests/test_batch_ledger/test_ledger_idempotency.py +214 -0
- bc_cli-0.4.0/tests/test_batch_ledger/test_ledger_schema.py +340 -0
- bc_cli-0.4.0/tests/test_batch_ledger/test_rollback_cmd.py +299 -0
- bc_cli-0.4.0/tests/test_batch_ledger/test_state_list_cmds.py +124 -0
- bc_cli-0.4.0/tests/test_bundle/test_bundle_roundtrip.py +394 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_cli/test_batch_safety.py +13 -1
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_cli/test_output_format.py +9 -2
- bc_cli-0.4.0/tests/test_cli/test_pipe_handling.py +65 -0
- bc_cli-0.4.0/tests/test_cli/test_records_format.py +137 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_cli/test_safety.py +2 -2
- bc_cli-0.4.0/tests/test_describe/__init__.py +1 -0
- bc_cli-0.4.0/tests/test_describe/test_describe_cmd.py +493 -0
- bc_cli-0.4.0/tests/test_describe/test_describe_positionals_limits.py +119 -0
- bc_cli-0.4.0/tests/test_diagnostics/test_checks.py +306 -0
- bc_cli-0.4.0/tests/test_envelope/conftest.py +143 -0
- bc_cli-0.4.0/tests/test_envelope/test_batch_envelope_with_ledger.py +286 -0
- bc_cli-0.4.0/tests/test_envelope/test_envelope_other_verbs.py +245 -0
- bc_cli-0.4.0/tests/test_envelope/test_envelope_policy_violation.py +163 -0
- bc_cli-0.4.0/tests/test_envelope/test_envelope_post.py +289 -0
- bc_cli-0.4.0/tests/test_envelope/test_result_envelope.py +165 -0
- bc_cli-0.4.0/tests/test_errors/__init__.py +1 -0
- bc_cli-0.4.0/tests/test_errors/test_did_you_mean.py +116 -0
- bc_cli-0.4.0/tests/test_exit_codes/__init__.py +1 -0
- bc_cli-0.4.0/tests/test_exit_codes/test_taxonomy.py +57 -0
- bc_cli-0.4.0/tests/test_extract/test_claude.py +224 -0
- bc_cli-0.4.0/tests/test_extract/test_factory.py +131 -0
- bc_cli-0.4.0/tests/test_extract/test_openai.py +303 -0
- bc_cli-0.4.0/tests/test_extract/test_pdf.py +53 -0
- bc_cli-0.4.0/tests/test_extract/test_schema.py +192 -0
- bc_cli-0.4.0/tests/test_extract/test_yaml_writer.py +149 -0
- bc_cli-0.4.0/tests/test_idempotency/__init__.py +1 -0
- bc_cli-0.4.0/tests/test_idempotency/conftest.py +64 -0
- bc_cli-0.4.0/tests/test_idempotency/test_cli_flags.py +135 -0
- bc_cli-0.4.0/tests/test_idempotency/test_idempotency_key.py +117 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_mcp/test_runner.py +66 -33
- bc_cli-0.4.0/tests/test_mcp/test_server_tools.py +328 -0
- bc_cli-0.4.0/tests/test_mcp/test_tool_generator.py +395 -0
- bc_cli-0.4.0/tests/test_odata/__init__.py +0 -0
- bc_cli-0.4.0/tests/test_output/__init__.py +1 -0
- bc_cli-0.4.0/tests/test_output/test_json_on_pipe.py +85 -0
- bc_cli-0.4.0/tests/test_progress/__init__.py +1 -0
- bc_cli-0.4.0/tests/test_progress/test_progress_events.py +157 -0
- bc_cli-0.4.0/tests/test_registry/__init__.py +0 -0
- bc_cli-0.4.0/tests/test_skill_init/__init__.py +0 -0
- bc_cli-0.4.0/tests/test_skill_init/conftest.py +147 -0
- bc_cli-0.4.0/tests/test_skill_init/test_skill_init_wizard.py +555 -0
- bc_cli-0.4.0/tests/test_skill_install/__init__.py +1 -0
- bc_cli-0.4.0/tests/test_skill_install/test_skill_install.py +484 -0
- bc_cli-0.4.0/tests/test_telemetry/__init__.py +0 -0
- bc_cli-0.4.0/tests/test_url/__init__.py +0 -0
- bc_cli-0.4.0/tests/test_workflow/__init__.py +0 -0
- bc_cli-0.4.0/tests/test_workflow/test_query_search.py +139 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_workflow/test_resolver.py +10 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/uv.lock +201 -2
- bc_cli-0.2.0/CHANGELOG.md +0 -237
- bc_cli-0.2.0/src/bcli_cli/commands/batch_cmd.py +0 -416
- bc_cli-0.2.0/src/bcli_cli/commands/delete_cmd.py +0 -76
- bc_cli-0.2.0/src/bcli_mcp/_server.py +0 -162
- bc_cli-0.2.0/tests/test_mcp/test_server_tools.py +0 -215
- {bc_cli-0.2.0 → bc_cli-0.4.0}/.env.example +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/.github/workflows/publish.yml +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/.github/workflows/tests.yml +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/CODE_OF_CONDUCT.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/CONTRIBUTING.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/LICENSE +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/NOTICE +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/README.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/SECURITY.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/docs/authentication.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/docs/batch-operations.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/docs/business-central-admin-setup.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/docs/command-reference.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/docs/configuration.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/docs/contributing.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/docs/custom-apis.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/docs/demo-setup.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/docs/getting-started.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/docs/multi-company.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/docs/querying.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/docs/sdk-usage.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/docs/write-operations.md +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/examples/ap-monthly-review.yaml +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/examples/attach-purchase-invoice-pdf.yaml +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/examples/create-purchase-invoice.yaml +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/examples/month-end-cronus.yaml +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/examples/queries/sample.yaml +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/_url.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/_version.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/audit/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/audit/_factory.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/audit/_protocol.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/audit/_redact.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/auth/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/auth/_base.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/auth/_browser.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/auth/_credentials.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/auth/_device_code.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/auth/_secure_io.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/auth/_token_cache.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/client/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/client/_safety.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/client/_sync.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/config/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/config/_defaults.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/config/_loader.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/etl/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/etl/_auth.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/etl/_bridge.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/etl/_client.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/etl/_generic.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/etl/_polaris.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/etl/_stampers.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/odata/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/odata/_escape.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/odata/_filter_fields.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/odata/_pagination.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/odata/_query.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/odata/_response.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/py.typed +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/registry/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/registry/_importers.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/registry/_registry.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/registry/_schema.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/registry/standard_v2.json +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/telemetry/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/telemetry/_azure_monitor.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/telemetry/_factory.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/telemetry/_protocol.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/telemetry/events.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/workflow/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/workflow/_loader.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli/workflow/_models.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/_audit_wrap.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/_dry_run.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/_url_resolve.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/commands/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/commands/auth_cmd.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/commands/company_cmd.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/commands/context_cmd.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/commands/endpoint_cmd.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/commands/env_cmd.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/commands/etl_cmd.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/commands/registry_cmd.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/commands/test_cmd.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/output/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_cli/output/_display.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/src/bcli_mcp/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/conftest.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/fixtures/sample_postman_collection.json +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_audit/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_audit/test_factory.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_audit/test_redact.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_audit/test_sink.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_auth/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_auth/test_browser_auth.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_auth/test_secure_io.py +0 -0
- {bc_cli-0.2.0/tests/test_cli → bc_cli-0.4.0/tests/test_batch_ledger}/__init__.py +0 -0
- {bc_cli-0.2.0/tests/test_client → bc_cli-0.4.0/tests/test_bundle}/__init__.py +0 -0
- {bc_cli-0.2.0/tests/test_config → bc_cli-0.4.0/tests/test_cli}/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_cli/test_audit_wrap.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_cli/test_company_cmd.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_cli/test_config_cmd.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_cli/test_dry_run.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_cli/test_query_cmd.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_cli/test_state.py +0 -0
- {bc_cli-0.2.0/tests/test_etl → bc_cli-0.4.0/tests/test_client}/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_client/test_resolve_url.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_client/test_safety.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_client/test_transport.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_client/test_upload_attachment.py +0 -0
- {bc_cli-0.2.0/tests/test_mcp → bc_cli-0.4.0/tests/test_config}/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_config/test_config.py +0 -0
- {bc_cli-0.2.0/tests/test_odata → bc_cli-0.4.0/tests/test_diagnostics}/__init__.py +0 -0
- {bc_cli-0.2.0/tests/test_registry → bc_cli-0.4.0/tests/test_envelope}/__init__.py +0 -0
- {bc_cli-0.2.0/tests/test_telemetry → bc_cli-0.4.0/tests/test_etl}/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_etl/test_bridge.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_etl/test_generic.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_etl/test_stampers.py +0 -0
- {bc_cli-0.2.0/tests/test_url → bc_cli-0.4.0/tests/test_extract}/__init__.py +0 -0
- {bc_cli-0.2.0/tests/test_workflow → bc_cli-0.4.0/tests/test_mcp}/__init__.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_odata/test_escape.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_odata/test_filter_fields.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_odata/test_query.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_registry/test_caution.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_registry/test_importers.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_registry/test_metadata_fields.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_registry/test_registry.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_telemetry/test_events.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_telemetry/test_sink.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_url/test_origin_allowlist.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_url/test_url_builder.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_workflow/test_batch_integration.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_workflow/test_loader.py +0 -0
- {bc_cli-0.2.0 → bc_cli-0.4.0}/tests/test_workflow/test_models.py +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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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;
|
|
347
|
+
available; the MCP tools are an "if you've got them, use them"
|
|
348
|
+
optimization.
|
|
238
349
|
|
|
239
350
|
---
|
|
240
351
|
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
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
|
+
|
|
163
|
+
### Fixed
|
|
164
|
+
|
|
165
|
+
- **Clean SIGPIPE handling for piped output** — `bcli <cmd> | head`,
|
|
166
|
+
`| grep -m 1`, and similar pipe-truncating consumers now terminate
|
|
167
|
+
the CLI silently, matching `cat` and `grep` conventions, instead of
|
|
168
|
+
emitting a `BrokenPipeError: [Errno 32] Broken pipe` traceback at
|
|
169
|
+
interpreter shutdown. Implemented as a new `bcli_cli.app:main`
|
|
170
|
+
console-script entry point that installs `SIGPIPE -> SIG_DFL` on
|
|
171
|
+
POSIX with a `BrokenPipeError` safety net for Windows.
|
|
172
|
+
- **Hyphenated saved-query param names** — the workflow template
|
|
173
|
+
resolver now accepts hyphens in identifiers, so references like
|
|
174
|
+
`${{ params.vendor-no }}` substitute correctly. Previously the regex
|
|
175
|
+
matched only `[\w.]`, silently leaving the literal `${{ … }}` token
|
|
176
|
+
in the rendered filter (BC then 400'd or, worse, returned mismatched
|
|
177
|
+
rows). Affects both `bcli q` saved queries and `bcli batch`
|
|
178
|
+
workflows.
|
|
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
|
+
|
|
227
|
+
## [0.2.0] — 2026-05-06
|
|
228
|
+
|
|
229
|
+
### Added
|
|
230
|
+
|
|
231
|
+
- **Structured `--dry-run` output** — write commands (`post`, `patch`,
|
|
232
|
+
`delete`, `attach upload`) now emit a stable JSON envelope on stdout
|
|
233
|
+
when `--format json` / `ndjson` / `raw` is selected. Includes
|
|
234
|
+
`dry_run`, `method`, `endpoint`, `resolved_url`, `profile`,
|
|
235
|
+
`environment`, `company_id`, `body`, and `record_id` (when applicable).
|
|
236
|
+
Agents can parse the envelope before deciding whether to proceed. The
|
|
237
|
+
human format keeps the same yellow rich panel on stderr but is now
|
|
238
|
+
augmented with the resolved URL and profile context. See
|
|
239
|
+
`docs/write-operations.md`.
|
|
240
|
+
- **Opt-in audit log** — new `[audit]` config section persists every
|
|
241
|
+
write to a per-profile JSONL file. Each entry captures the resolved
|
|
242
|
+
URL, response status, BC `correlation_id`, latency, redacted request
|
|
243
|
+
body, and outcome (`completed` / `failed` / `dry_run`). Bounded disk
|
|
244
|
+
usage via single-backup rotation. SDK (`AsyncBCClient`) does NOT
|
|
245
|
+
auto-emit; this is a CLI-layer ergonomic on top of BC permission sets.
|
|
246
|
+
See `docs/configuration.md#audit-log`.
|
|
247
|
+
- **Endpoint `caution` flag** — `EndpointMetadata` now carries a
|
|
248
|
+
`caution: low | medium | high` level. Importers populate it
|
|
249
|
+
automatically from a verb-name heuristic (entities containing `post`,
|
|
250
|
+
`release`, `cancel`, `void`, `reverse`, `apply`, `unapply` are flagged
|
|
251
|
+
`high`). Surfaced in `bcli endpoint info` and the `list_endpoints` MCP
|
|
252
|
+
tool so agents can require explicit user confirmation before mutating
|
|
253
|
+
posted/closed records.
|
|
254
|
+
- New `AGENTS.md` recipes for dry-run-first writes, caution-level
|
|
255
|
+
interpretation, and audit-log location.
|
|
256
|
+
|
|
257
|
+
## [0.1.5] — 2026-05-05
|
|
258
|
+
|
|
259
|
+
### Added
|
|
260
|
+
|
|
261
|
+
- **Business Central admin setup guide** — new
|
|
262
|
+
`docs/business-central-admin-setup.md` walks a zero-knowledge user
|
|
263
|
+
through Entra app registration, localhost redirect setup, delegated BC
|
|
264
|
+
permissions, admin consent, BC user permission sets, first `bcli
|
|
265
|
+
config init`, and verification.
|
|
266
|
+
- **`bcli-mcp` preview server** — an MCP (Model Context Protocol) server
|
|
267
|
+
that lets Claude Desktop and other MCP clients drive bcli. Four
|
|
268
|
+
read-only tools: `query`, `list_endpoints`, `describe_endpoint`,
|
|
269
|
+
`list_companies`. Subprocess-only architecture inherits profile, auth,
|
|
270
|
+
retry, telemetry, and `disable_writes` from the CLI. Install with
|
|
271
|
+
`pip install "bc-cli[mcp]"`. See `docs/mcp-server.md`.
|
|
272
|
+
|
|
273
|
+
### Changed
|
|
274
|
+
|
|
275
|
+
- `bcli config init` now defaults to browser PKCE auth for local humans
|
|
276
|
+
and agents. New `--automation` and `--headless` shortcuts create
|
|
277
|
+
client-credentials and device-code profiles respectively.
|
|
278
|
+
- CLI runtime dependencies now ship with the base `bc-cli` install, so
|
|
279
|
+
`pip install bc-cli` and `uv tool install bc-cli` provide a working
|
|
280
|
+
`bcli` command without requiring an extra.
|
|
281
|
+
- `bcli company list` accepts `--format` (`json`, `markdown`, `csv`,
|
|
282
|
+
`ndjson`, `table`). Stable JSON shape:
|
|
283
|
+
`[{"id", "name", "alias", "is_default"}]`.
|
|
284
|
+
- `bcli endpoint list` and `bcli endpoint info` accept `--format json`.
|
|
285
|
+
Stable JSON shapes documented inline in each command's help text.
|
|
286
|
+
|
|
287
|
+
### Removed
|
|
288
|
+
|
|
289
|
+
- Removed WorkOS AuthKit support. Browser PKCE is now the delegated auth
|
|
290
|
+
path, Business Central remains the permission boundary, and
|
|
291
|
+
client-credentials profiles cover automation.
|
|
292
|
+
|
|
293
|
+
## [0.1.2] — 2026-04-29
|
|
294
|
+
|
|
295
|
+
Security release. Closes four findings from a strix.ai run against the
|
|
296
|
+
repo. No public SDK signature changes; the CLI gains one new flag
|
|
297
|
+
(`bcli batch run --yes`).
|
|
298
|
+
|
|
299
|
+
### Security
|
|
300
|
+
|
|
301
|
+
- **vuln-0001 (HIGH, CWE-352)** — WorkOS localhost callback now binds a
|
|
302
|
+
per-login high-entropy `state` token. Before this release, any local
|
|
303
|
+
request reaching `127.0.0.1:8401/callback?code=…` during the login
|
|
304
|
+
window would be exchanged for a role-bearing WorkOS identity and
|
|
305
|
+
cached on disk. The handler now rejects callbacks whose path is not
|
|
306
|
+
`/callback` (404) or whose `state` doesn't match the per-login token
|
|
307
|
+
(400), and surfaces invalid callbacks as auth failures rather than
|
|
308
|
+
masking them as timeouts.
|
|
309
|
+
- **vuln-0002 (MEDIUM, CWE-841)** — `bcli batch run` now enforces the
|
|
310
|
+
`disable_writes` profile gate that direct `post`/`patch`/`delete`
|
|
311
|
+
commands already honour. Mutating batch steps on a read-only profile
|
|
312
|
+
prompt for confirmation interactively or abort with exit 1 in
|
|
313
|
+
non-interactive sessions. New `--yes` / `-y` flag opts scripted use
|
|
314
|
+
past the prompt. Pure GET batches are unaffected, and `--dry-run`
|
|
315
|
+
still skips the gate so workflows can be previewed.
|
|
316
|
+
- **vuln-0003 (MEDIUM)** — Browser auth callback listener now binds an
|
|
317
|
+
ephemeral kernel-assigned port instead of a hard-coded 8400, and
|
|
318
|
+
serves continuously until a state-bound callback arrives or the
|
|
319
|
+
timeout expires. Stray requests (e.g. `/favicon.ico`) and
|
|
320
|
+
state-mismatched callbacks no longer consume the only callback slot.
|
|
321
|
+
Microsoft Entra accepts any port for `http://localhost` redirect URIs
|
|
322
|
+
on public clients per RFC 8252, so existing app registrations
|
|
323
|
+
continue to work without changes.
|
|
324
|
+
- **vuln-0004 (HIGH, CWE-841)** — `SafeContext` writes are now bound to
|
|
325
|
+
the explicit `environment` and `company_id` passed to
|
|
326
|
+
`client.safe_write(env, company)`, not the client's profile-bound
|
|
327
|
+
target. Previously the safety gate validated operator intent but the
|
|
328
|
+
underlying URL still resolved against the profile, so writes inside
|
|
329
|
+
`safe_write("Sandbox", "company-SANDBOX")` could still hit
|
|
330
|
+
`Production/company-PROD`. Closes the documentation-vs-behaviour
|
|
331
|
+
mismatch where the README/changelog claimed the gate prevented
|
|
332
|
+
wrong-environment writes.
|
|
333
|
+
|
|
334
|
+
### Changed
|
|
335
|
+
|
|
336
|
+
- `bcli batch run` accepts a new `--yes` / `-y` flag (see vuln-0002
|
|
337
|
+
above). Existing automation against writable profiles is unaffected;
|
|
338
|
+
CI scripts that run mutating batches against a `disable_writes`
|
|
339
|
+
profile must pass `--yes` or migrate to a writable profile.
|
|
340
|
+
|
|
341
|
+
## [0.1.1] — 2026-04-29
|
|
342
|
+
|
|
343
|
+
OSS-readiness polish. No SDK behaviour changes; one CLI rename and a
|
|
344
|
+
README/docstring cleanup pass.
|
|
345
|
+
|
|
346
|
+
### Changed
|
|
347
|
+
|
|
348
|
+
- **Renamed the document-attachment CLI command** to `bcli attach`,
|
|
349
|
+
with subcommands `bcli attach upload` and `bcli attach test`.
|
|
350
|
+
Functionality is unchanged. The previous name was a customer-name
|
|
351
|
+
leftover from internal development; the workflow is generic to any
|
|
352
|
+
Business Central tenant.
|
|
353
|
+
- Genericised customer-named example values in
|
|
354
|
+
`docs/authentication.md`, `examples/attach-purchase-invoice-pdf.yaml`,
|
|
355
|
+
and SDK docstrings.
|
|
356
|
+
|
|
357
|
+
### Fixed
|
|
358
|
+
|
|
359
|
+
- README license badge said Apache 2.0 but the License section said
|
|
360
|
+
MIT. Now consistent: Apache 2.0 throughout (matches the actual
|
|
361
|
+
`LICENSE` file and `pyproject.toml`).
|
|
362
|
+
|
|
363
|
+
## [0.1.0] — 2026-04-29
|
|
364
|
+
|
|
365
|
+
First public release on PyPI as **`bc-cli`**.
|
|
366
|
+
|
|
367
|
+
> **Note on the package name.** The PyPI distribution name is `bc-cli`,
|
|
368
|
+
> not `bcli` — the latter is squatted by an unrelated 2018-era
|
|
369
|
+
> "EC2 Cluster Creator" package. The Python import name (`import bcli`)
|
|
370
|
+
> and the CLI binary on PATH (`bcli`) are unaffected.
|
|
371
|
+
|
|
372
|
+
### Added
|
|
373
|
+
|
|
374
|
+
- **SDK** — `BCClient` (sync) and `AsyncBCClient` (async) for Microsoft
|
|
375
|
+
Dynamics 365 Business Central. Two construction modes: profile-based
|
|
376
|
+
(TOML config files) and programmatic (pass auth params directly).
|
|
377
|
+
- **CLI** — Typer-based `bcli` command with subcommands for query
|
|
378
|
+
(`get`), write (`post`/`patch`/`delete`/`attach`), config, auth,
|
|
379
|
+
registry import, batch operations, saved queries (`q`), and ETL
|
|
380
|
+
pipelines.
|
|
381
|
+
- **Three-tier endpoint resolution.** Custom registry → standard v2.0 →
|
|
382
|
+
fuzzy-match suggestion. Custom APIs imported from Postman v2.1
|
|
383
|
+
collections, raw JSON, or live `$metadata`.
|
|
384
|
+
- **Auth** — Client-credentials, device-code, browser (PKCE), and
|
|
385
|
+
WorkOS AuthKit (role → BC client_id mapping). Token cache with 5-min
|
|
386
|
+
expiry buffer; OS keychain integration via `keyring`.
|
|
387
|
+
- **Write safety** — `SafeContext` gate enforces explicit `environment`
|
|
388
|
+
+ `company_id` on writes; production writes require
|
|
389
|
+
`confirm_production=True`. CLI's `disable_writes` profile flag adds
|
|
390
|
+
an interactive confirmation prompt before any mutating call.
|
|
391
|
+
- **Saved queries** (`bcli q`) — Hide OData syntax behind named,
|
|
392
|
+
parameterised aliases stored per-profile. Per-param `type`,
|
|
393
|
+
`pattern`, `min`/`max`, and `enum` validation runs locally before any
|
|
394
|
+
HTTP call. String parameters interpolated into `filter:` are escaped
|
|
395
|
+
using OData v4 single-quote rules so an injection-shaped value cannot
|
|
396
|
+
break the literal.
|
|
397
|
+
- **ETL pipeline** — Built-in [dlt](https://dlthub.com) source for
|
|
398
|
+
incremental backup. Polaris REST catalog integration for Iceberg
|
|
399
|
+
snapshots. Generic + bcli-bridge layers.
|
|
400
|
+
- **Telemetry** — Pluggable backend with `null`, `console`,
|
|
401
|
+
`azure_monitor`, and arbitrary `module:Class` sinks. Privacy-first
|
|
402
|
+
defaults: token redaction in error messages, opt-in capture of filter
|
|
403
|
+
text and signed-in UPN. Every event carries `version`, `os`,
|
|
404
|
+
`os_release`, `arch`, `python_version`, and a stable per-laptop
|
|
405
|
+
`install_id` for downstream slicing.
|
|
406
|
+
- **Structured logging** — JSON request logs to the `bcli.http` logger
|
|
407
|
+
with method/url/status/retry-count/latency/correlation-id.
|
|
408
|
+
|
|
409
|
+
### Security
|
|
410
|
+
|
|
411
|
+
The 0.1.0 release went through two independent security review passes
|
|
412
|
+
before publish. Findings addressed:
|
|
413
|
+
|
|
414
|
+
- Pinned wheel-version installer scripts; scrubbed registry `supports`
|
|
415
|
+
arrays to `["GET"]` for read-only profiles; removed sensitive field
|
|
416
|
+
metadata from public artefacts shipped by the bootstrap installer.
|
|
417
|
+
- **Critical:** Project-level `.bcli.toml` files cannot override
|
|
418
|
+
`[telemetry] backend`. Closes an arbitrary-Python-import RCE on
|
|
419
|
+
`bcli` invocation in any directory containing a malicious
|
|
420
|
+
`.bcli.toml`.
|
|
421
|
+
- **High:** Token caches and identity caches written with `0o600`
|
|
422
|
+
perms via atomic write; parent dir tightened to `0o700`; one-shot
|
|
423
|
+
warning on insecure existing perms.
|
|
424
|
+
- **High:** WorkOS role cache now expires after 1 hour; revoked roles
|
|
425
|
+
no longer keep mapping to privileged BC apps indefinitely.
|
|
426
|
+
- **Medium:** Absolute-URL paths (`@odata.nextLink`) validated against
|
|
427
|
+
a `*.businesscentral.dynamics.com` / `*.bc.dynamics.com` host
|
|
428
|
+
allowlist before the bearer token is attached. Closes a
|
|
429
|
+
bearer-exfiltration vector via tampered BC responses.
|
|
430
|
+
- **Medium:** CI hardened — third-party actions pinned by full commit
|
|
431
|
+
SHA, default `permissions: contents: read`, `uv sync --locked` for
|
|
432
|
+
reproducible installs from a committed `uv.lock`.
|
|
433
|
+
- **Medium:** Saved-query OData injection prevention (filter-context
|
|
434
|
+
escape + per-parameter `type`/`pattern`/`min`/`max`/`enum`
|
|
435
|
+
validation).
|
|
436
|
+
|
|
437
|
+
### Known limitations
|
|
438
|
+
|
|
439
|
+
- Alpha software. The SDK and CLI surface may change in 0.x releases;
|
|
440
|
+
track this CHANGELOG for breaking changes.
|
|
441
|
+
- Some BC custom-API edge cases (zero-GUID ids on
|
|
442
|
+
`SourceTableTemporary` pages) require the `--standard` flag to
|
|
443
|
+
bypass the registry — see `bcli attach upload --help`.
|
|
444
|
+
|
|
445
|
+
## [Pre-0.1.0 history]
|
|
446
|
+
|
|
447
|
+
Earlier development happened under the working name `bcapi`, then
|
|
448
|
+
moved to `bcli` (April 2026), then to the PyPI distribution name
|
|
449
|
+
`bc-cli` (April 2026, after discovering the `bcli` PyPI name was
|
|
450
|
+
squatted by an unrelated 2018 package).
|
|
451
|
+
|
|
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
|
|
457
|
+
[0.1.1]: https://github.com/igor-ctrl/bcli/compare/v0.1.0...v0.1.1
|
|
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
|
+
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
|
|
@@ -32,14 +32,27 @@ Requires-Dist: tomlkit>=0.13
|
|
|
32
32
|
Requires-Dist: typer>=0.12
|
|
33
33
|
Provides-Extra: cli
|
|
34
34
|
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: anthropic>=0.40; extra == 'dev'
|
|
35
36
|
Requires-Dist: dlt[filesystem,parquet,s3]>=1.0; extra == 'dev'
|
|
36
37
|
Requires-Dist: mcp>=1.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: openai>=1.50; extra == 'dev'
|
|
39
|
+
Requires-Dist: pypdf>=4.0; extra == 'dev'
|
|
37
40
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
38
41
|
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
|
|
39
42
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
40
43
|
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
41
44
|
Provides-Extra: etl
|
|
42
45
|
Requires-Dist: dlt[filesystem,parquet,s3]>=1.0; extra == 'etl'
|
|
46
|
+
Provides-Extra: extract
|
|
47
|
+
Requires-Dist: anthropic>=0.40; extra == 'extract'
|
|
48
|
+
Requires-Dist: openai>=1.50; extra == 'extract'
|
|
49
|
+
Requires-Dist: pypdf>=4.0; extra == 'extract'
|
|
50
|
+
Provides-Extra: extract-claude
|
|
51
|
+
Requires-Dist: anthropic>=0.40; extra == 'extract-claude'
|
|
52
|
+
Requires-Dist: pypdf>=4.0; extra == 'extract-claude'
|
|
53
|
+
Provides-Extra: extract-openai
|
|
54
|
+
Requires-Dist: openai>=1.50; extra == 'extract-openai'
|
|
55
|
+
Requires-Dist: pypdf>=4.0; extra == 'extract-openai'
|
|
43
56
|
Provides-Extra: mcp
|
|
44
57
|
Requires-Dist: mcp>=1.0; extra == 'mcp'
|
|
45
58
|
Provides-Extra: polaris
|