Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name>` 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
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
19 changes: 18 additions & 1 deletion src/bcli/_version.py
Original file line number Diff line number Diff line change
@@ -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"
12 changes: 6 additions & 6 deletions src/bcli_cli/commands/attach_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 3 additions & 3 deletions src/bcli_cli/commands/delete_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
6 changes: 3 additions & 3 deletions src/bcli_cli/commands/get_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
6 changes: 3 additions & 3 deletions src/bcli_cli/commands/patch_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
6 changes: 3 additions & 3 deletions src/bcli_cli/commands/post_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading