diff --git a/AGENTS.md b/AGENTS.md index 47126be..0fa41ec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,6 +29,47 @@ Use `-e` / `--env` only when the user explicitly asks you to hit a Same for `--company` / `-c`: only pass it when switching off the profile's default company. +The endpoint **registry** does the same job for the API route. When you +write `bcli get fixedAssets`, the registry already knows the +publisher / group / version for `fixedAssets` and builds the URL for +you. You do **not** need `--publisher … --group … --version …` — those +are escape hatches for the rare case where an admin hasn't imported the +endpoint yet, and they're hidden from `--help` for that reason. If +`bcli get ` errors with `RegistryError`, the fix is to import the +endpoint into the registry, not to pass override flags. + +--- + +## Don't write this — minimal command, please + +The single biggest tell that an agent is over-pattern-matching: +redundant flags that the profile + registry already supply. + +```bash +# ❌ Don't write this: +bcli -c LLC get fixedAssets --publisher beautech --group finance --version v1.5 --all -f json + +# ✅ Write this: +bcli -c LLC get fixedAssets +``` + +Why each flag was wrong: + +- `--publisher beautech --group finance --version v1.5` — the + registry resolves these automatically. Only pass them if the + endpoint isn't in the registry (and even then, prefer importing it). +- `--all` — pulls **every** page. Most asks need `--top 5` or no + pagination flag at all. Use `--all` only when the user explicitly + asks for a full export. +- `-f json` — bcli already auto-detects agents (`CLAUDECODE=1`, + `BCLI_AGENT=1`, or non-TTY stdout) and emits markdown. Pass `-f + json` only when you actually need to feed the result into `jq` or + another tool call. + +Rule of thumb: **start with the shortest command that names the +action** (`bcli get fixedAssets`), then add flags one at a time only +when the user's question demands them. + --- ## Endpoint discovery — don't guess names diff --git a/pyproject.toml b/pyproject.toml index ee4b03d..2e8fb26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ build-backend = "hatchling.build" # installed CLI binary (`bcli`) are unaffected — only `pip install` / # `uv tool install` use this name. name = "bc-cli" -version = "0.1.3" +version = "0.1.4" description = "Python SDK and CLI for Microsoft Dynamics 365 Business Central APIs" readme = "README.md" license = "Apache-2.0" diff --git a/src/bcli/_version.py b/src/bcli/_version.py index 3dc1f76..91f483f 100644 --- a/src/bcli/_version.py +++ b/src/bcli/_version.py @@ -1 +1,18 @@ -__version__ = "0.1.0" +"""Single source of truth: the installed package's metadata. + +Reading from importlib.metadata avoids the drift bug where pyproject.toml +got bumped but a hardcoded __version__ string in this module didn't — +the symptom was `bcli --version` reporting an older version than the +wheel actually shipped. + +Falls back to a placeholder for editable installs that haven't been +registered with metadata yet (rare, but cleaner than crashing). +""" +from __future__ import annotations + +from importlib.metadata import PackageNotFoundError, version + +try: + __version__ = version("bc-cli") +except PackageNotFoundError: # pragma: no cover — only triggers in dev sandboxes + __version__ = "0.0.0+unknown" diff --git a/src/bcli_cli/commands/attach_cmd.py b/src/bcli_cli/commands/attach_cmd.py index db742d0..0999a26 100644 --- a/src/bcli_cli/commands/attach_cmd.py +++ b/src/bcli_cli/commands/attach_cmd.py @@ -41,9 +41,9 @@ def upload_command( parent_type: str = typer.Option("Purchase Invoice", "--parent-type", help="BC parent entity type"), file_name: Optional[str] = typer.Option(None, "--file-name", help="Override the attachment filename (defaults to the source filename)"), content_type: Optional[str] = typer.Option(None, "--content-type", help="Override Content-Type for the binary PATCH (defaults to mime-guess or application/octet-stream)"), - publisher: Optional[str] = typer.Option(None, "--publisher", help="Custom API publisher (e.g. 'mycompany')"), - group: Optional[str] = typer.Option(None, "--group", help="Custom API group (e.g. 'finance')"), - version: Optional[str] = typer.Option(None, "--version", help="Custom API version (e.g. 'v1.5')"), + publisher: Optional[str] = typer.Option(None, "--publisher", hidden=True, help="Custom API publisher override (e.g. 'mycompany') — registry resolves this automatically"), + group: Optional[str] = typer.Option(None, "--group", hidden=True, help="Custom API group override (e.g. 'finance') — registry resolves this automatically"), + version: Optional[str] = typer.Option(None, "--version", hidden=True, help="Custom API version override (e.g. 'v1.5') — registry resolves this automatically"), standard: bool = typer.Option(False, "--standard", "--no-registry", help="Bypass the custom registry and force Microsoft's standard /api/v2.0/documentAttachments route. Use when a custom page isn't persisting (zero-GUID ids)."), format: Optional[str] = typer.Option(None, "--format", "-f", help="Output format: table, json, csv, ndjson, raw"), yes: bool = typer.Option(False, "--yes", "-y", help="Skip the read-only-profile warning prompt"), @@ -103,9 +103,9 @@ def test_command( invoice_date: Optional[str] = typer.Option(None, "--invoice-date", help="Invoice date (YYYY-MM-DD); defaults to today"), file_name: Optional[str] = typer.Option(None, "--file-name", help="Override the attachment filename"), content_type: Optional[str] = typer.Option(None, "--content-type", help="Override Content-Type for the binary PATCH"), - publisher: Optional[str] = typer.Option(None, "--publisher", help="Custom API publisher for the attach step"), - group: Optional[str] = typer.Option(None, "--group", help="Custom API group for the attach step"), - version: Optional[str] = typer.Option(None, "--version", help="Custom API version for the attach step"), + publisher: Optional[str] = typer.Option(None, "--publisher", hidden=True, help="Custom API publisher for the attach step — registry resolves this automatically"), + group: Optional[str] = typer.Option(None, "--group", hidden=True, help="Custom API group for the attach step — registry resolves this automatically"), + version: Optional[str] = typer.Option(None, "--version", hidden=True, help="Custom API version for the attach step — registry resolves this automatically"), standard: bool = typer.Option(False, "--standard", "--no-registry", help="Bypass the custom registry for the attach step (forces /api/v2.0/documentAttachments)"), format: Optional[str] = typer.Option(None, "--format", "-f", help="Output format: table, json, csv, ndjson, raw"), ) -> None: diff --git a/src/bcli_cli/commands/delete_cmd.py b/src/bcli_cli/commands/delete_cmd.py index 01ef196..efb1274 100644 --- a/src/bcli_cli/commands/delete_cmd.py +++ b/src/bcli_cli/commands/delete_cmd.py @@ -19,9 +19,9 @@ def delete_command( record_id: str = typer.Argument(help="Record ID to delete"), etag: str = typer.Option("*", "--etag", help="ETag for optimistic concurrency"), format: Optional[str] = typer.Option(None, "--format", "-f", help="Output format (unused, for flag consistency)"), - publisher: Optional[str] = typer.Option(None, "--publisher"), - group: Optional[str] = typer.Option(None, "--group"), - version: Optional[str] = typer.Option(None, "--version"), + publisher: Optional[str] = typer.Option(None, "--publisher", hidden=True), + group: Optional[str] = typer.Option(None, "--group", hidden=True), + version: Optional[str] = typer.Option(None, "--version", hidden=True), yes: bool = typer.Option(False, "--yes", "-y", help="Skip the read-only-profile warning prompt"), ) -> None: """DELETE a record.""" diff --git a/src/bcli_cli/commands/get_cmd.py b/src/bcli_cli/commands/get_cmd.py index cf3cc5a..5638b67 100644 --- a/src/bcli_cli/commands/get_cmd.py +++ b/src/bcli_cli/commands/get_cmd.py @@ -27,9 +27,9 @@ def get_command( count: bool = typer.Option(False, "--count", help="Include total record count"), all_pages: bool = typer.Option(False, "--all", help="Follow pagination to get all records"), format: Optional[str] = typer.Option(None, "--format", "-f", help="Output format: table, json, csv, ndjson, raw"), - publisher: Optional[str] = typer.Option(None, "--publisher", help="Custom API publisher override"), - group: Optional[str] = typer.Option(None, "--group", help="Custom API group override"), - version: Optional[str] = typer.Option(None, "--version", help="Custom API version override"), + publisher: Optional[str] = typer.Option(None, "--publisher", hidden=True, help="Custom API publisher override (escape hatch — registry resolves this automatically)"), + group: Optional[str] = typer.Option(None, "--group", hidden=True, help="Custom API group override (escape hatch — registry resolves this automatically)"), + version: Optional[str] = typer.Option(None, "--version", hidden=True, help="Custom API version override (escape hatch — registry resolves this automatically)"), ) -> None: """GET records from a Business Central entity. diff --git a/src/bcli_cli/commands/patch_cmd.py b/src/bcli_cli/commands/patch_cmd.py index 6296da3..9dcfa23 100644 --- a/src/bcli_cli/commands/patch_cmd.py +++ b/src/bcli_cli/commands/patch_cmd.py @@ -22,9 +22,9 @@ def patch_command( data: str = typer.Option(..., "--data", "-d", help="JSON data or @filename"), etag: str = typer.Option("*", "--etag", help="ETag for optimistic concurrency"), format: Optional[str] = typer.Option(None, "--format", "-f", help="Output format: table, json, csv, ndjson, raw"), - publisher: Optional[str] = typer.Option(None, "--publisher"), - group: Optional[str] = typer.Option(None, "--group"), - version: Optional[str] = typer.Option(None, "--version"), + publisher: Optional[str] = typer.Option(None, "--publisher", hidden=True), + group: Optional[str] = typer.Option(None, "--group", hidden=True), + version: Optional[str] = typer.Option(None, "--version", hidden=True), yes: bool = typer.Option(False, "--yes", "-y", help="Skip the read-only-profile warning prompt"), ) -> None: """PATCH (update) an existing record.""" diff --git a/src/bcli_cli/commands/post_cmd.py b/src/bcli_cli/commands/post_cmd.py index 6d669ec..a66805b 100644 --- a/src/bcli_cli/commands/post_cmd.py +++ b/src/bcli_cli/commands/post_cmd.py @@ -20,9 +20,9 @@ def post_command( endpoint: str = typer.Argument(help="Entity set name"), data: str = typer.Option(..., "--data", "-d", help="JSON data or @filename"), format: Optional[str] = typer.Option(None, "--format", "-f", help="Output format: table, json, csv, ndjson, raw"), - publisher: Optional[str] = typer.Option(None, "--publisher"), - group: Optional[str] = typer.Option(None, "--group"), - version: Optional[str] = typer.Option(None, "--version"), + publisher: Optional[str] = typer.Option(None, "--publisher", hidden=True), + group: Optional[str] = typer.Option(None, "--group", hidden=True), + version: Optional[str] = typer.Option(None, "--version", hidden=True), yes: bool = typer.Option(False, "--yes", "-y", help="Skip the read-only-profile warning prompt"), ) -> None: """POST (create) a new record.""" diff --git a/uv.lock b/uv.lock index 37620f9..4a940d7 100644 --- a/uv.lock +++ b/uv.lock @@ -302,7 +302,7 @@ wheels = [ [[package]] name = "bc-cli" -version = "0.1.2" +version = "0.1.4" source = { editable = "." } dependencies = [ { name = "httpx" },