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.
- {bc_cli-0.3.0 → bc_cli-0.4.0}/AGENTS.md +118 -7
- {bc_cli-0.3.0 → bc_cli-0.4.0}/CHANGELOG.md +205 -1
- {bc_cli-0.3.0 → bc_cli-0.4.0}/PKG-INFO +1 -1
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/mcp-server.md +54 -31
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/saved-queries.md +74 -1
- {bc_cli-0.3.0 → bc_cli-0.4.0}/pyproject.toml +1 -1
- 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.3.0 → bc_cli-0.4.0}/src/bcli/client/_async.py +36 -4
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/client/_transport.py +40 -6
- bc_cli-0.4.0/src/bcli/exit_codes.py +69 -0
- bc_cli-0.4.0/src/bcli/result_envelope.py +140 -0
- 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.3.0 → bc_cli-0.4.0}/src/bcli_cli/_safety.py +6 -5
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/app.py +52 -4
- {bc_cli-0.3.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.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.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/extract_cmd.py +47 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/patch_cmd.py +61 -19
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/post_cmd.py +63 -16
- 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.3.0 → bc_cli-0.4.0}/src/bcli_cli/output/_formatters.py +19 -10
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_mcp/__main__.py +2 -2
- {bc_cli-0.3.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.3.0 → bc_cli-0.4.0}/tests/test_cli/test_batch_safety.py +13 -1
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_output_format.py +9 -2
- {bc_cli-0.3.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_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_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.3.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_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_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.3.0/src/bcli_cli/commands/batch_cmd.py +0 -416
- bc_cli-0.3.0/src/bcli_cli/commands/delete_cmd.py +0 -76
- bc_cli-0.3.0/src/bcli_mcp/_server.py +0 -162
- bc_cli-0.3.0/tests/test_mcp/test_server_tools.py +0 -215
- {bc_cli-0.3.0 → bc_cli-0.4.0}/.env.example +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/.github/workflows/publish.yml +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/.github/workflows/tests.yml +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/.gitignore +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/CODE_OF_CONDUCT.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/CONTRIBUTING.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/LICENSE +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/NOTICE +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/README.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/SECURITY.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/authentication.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/batch-operations.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/business-central-admin-setup.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/command-reference.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/configuration.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/contributing.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/custom-apis.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/demo-setup.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/extraction.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/getting-started.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/multi-company.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/plans/team-deployment.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/querying.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/sdk-usage.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/docs/write-operations.md +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/examples/ap-monthly-review.yaml +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/examples/attach-purchase-invoice-pdf.yaml +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/examples/create-purchase-invoice.yaml +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/examples/extract/purchase_invoice_lines.yaml +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/examples/month-end-cronus.yaml +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/examples/queries/sample.yaml +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/_url.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/_version.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/audit/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/audit/_factory.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/audit/_protocol.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/audit/_redact.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/auth/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/auth/_base.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/auth/_browser.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/auth/_credentials.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/auth/_device_code.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/auth/_secure_io.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/auth/_token_cache.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/bundle/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/bundle/_apply.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/bundle/_fetch.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/bundle/_manifest.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/bundle/_publish.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/bundle/_verify.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/client/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/client/_safety.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/client/_sync.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/config/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/config/_defaults.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/config/_loader.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/config/_model.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/diagnostics/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/diagnostics/_checks.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/errors.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/etl/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/etl/_auth.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/etl/_bridge.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/etl/_client.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/etl/_generic.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/etl/_polaris.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/etl/_stampers.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/_claude.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/_factory.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/_openai.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/_pdf.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/_protocol.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/_schema.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/extract/_yaml_writer.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/odata/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/odata/_escape.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/odata/_filter_fields.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/odata/_pagination.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/odata/_query.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/odata/_response.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/py.typed +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/registry/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/registry/_importers.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/registry/_registry.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/registry/_schema.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/registry/standard_v2.json +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/telemetry/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/telemetry/_azure_monitor.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/telemetry/_factory.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/telemetry/_protocol.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/telemetry/events.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/workflow/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/workflow/_loader.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/workflow/_models.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/workflow/_query_search.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli/workflow/_resolver.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/_audit_wrap.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/_dry_run.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/_state.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/_url_resolve.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/auth_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/company_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/config_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/context_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/doctor_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/endpoint_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/env_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/etl_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/get_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/query_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/refresh_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/registry_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/commands/test_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/output/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_cli/output/_display.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/src/bcli_mcp/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/conftest.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/fixtures/sample_postman_collection.json +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_audit/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_audit/test_factory.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_audit/test_redact.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_audit/test_sink.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_auth/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_auth/test_browser_auth.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_auth/test_secure_io.py +0 -0
- {bc_cli-0.3.0/tests/test_bundle → bc_cli-0.4.0/tests/test_batch_ledger}/__init__.py +0 -0
- {bc_cli-0.3.0/tests/test_cli → bc_cli-0.4.0/tests/test_bundle}/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_bundle/test_bundle_roundtrip.py +0 -0
- {bc_cli-0.3.0/tests/test_client → bc_cli-0.4.0/tests/test_cli}/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_audit_wrap.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_company_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_config_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_dry_run.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_pipe_handling.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_query_cmd.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_records_format.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_cli/test_state.py +0 -0
- {bc_cli-0.3.0/tests/test_config → bc_cli-0.4.0/tests/test_client}/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_client/test_resolve_url.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_client/test_safety.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_client/test_transport.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_client/test_upload_attachment.py +0 -0
- {bc_cli-0.3.0/tests/test_diagnostics → bc_cli-0.4.0/tests/test_config}/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_config/test_config.py +0 -0
- {bc_cli-0.3.0/tests/test_etl → bc_cli-0.4.0/tests/test_diagnostics}/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_diagnostics/test_checks.py +0 -0
- {bc_cli-0.3.0/tests/test_extract → bc_cli-0.4.0/tests/test_envelope}/__init__.py +0 -0
- {bc_cli-0.3.0/tests/test_mcp → bc_cli-0.4.0/tests/test_etl}/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_etl/test_bridge.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_etl/test_generic.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_etl/test_stampers.py +0 -0
- {bc_cli-0.3.0/tests/test_odata → bc_cli-0.4.0/tests/test_extract}/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_extract/test_claude.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_extract/test_factory.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_extract/test_openai.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_extract/test_pdf.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_extract/test_schema.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_extract/test_yaml_writer.py +0 -0
- {bc_cli-0.3.0/tests/test_registry → bc_cli-0.4.0/tests/test_mcp}/__init__.py +0 -0
- {bc_cli-0.3.0/tests/test_telemetry → bc_cli-0.4.0/tests/test_odata}/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_odata/test_escape.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_odata/test_filter_fields.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_odata/test_query.py +0 -0
- {bc_cli-0.3.0/tests/test_url → bc_cli-0.4.0/tests/test_registry}/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_registry/test_caution.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_registry/test_importers.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_registry/test_metadata_fields.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_registry/test_registry.py +0 -0
- {bc_cli-0.3.0/tests/test_workflow → bc_cli-0.4.0/tests/test_skill_init}/__init__.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_telemetry/test_events.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_telemetry/test_sink.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_url/test_origin_allowlist.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_url/test_url_builder.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_workflow/test_batch_integration.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_workflow/test_loader.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_workflow/test_models.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_workflow/test_query_search.py +0 -0
- {bc_cli-0.3.0 → bc_cli-0.4.0}/tests/test_workflow/test_resolver.py +0 -0
- {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
|
-
|
|
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
|
|
|
@@ -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.
|
|
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
|
+
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
|
|
11
|
-
|
|
12
|
-
|
|
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.
|
|
51
|
-
`
|
|
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
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
`
|
|
79
|
-
|
|
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 `
|
|
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 `
|
|
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. `
|
|
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 `
|
|
123
|
-
`
|
|
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
|
|
150
|
+
## Discovering field names
|
|
128
151
|
|
|
129
|
-
`
|
|
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 `
|
|
134
|
-
the returned record. No registry mutation, zero cache pollution.
|
|
135
|
-
2. One-time:
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
* `
|
|
152
|
-
|
|
153
|
-
|
|
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.
|
|
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
|