Repository navigation
Conversation
Add `dualentry classifications-lines list`, a read-only list of classification values across every classification. It filters by --classification, --id, --name, and --active/--inactive, and prints each line with its parent classification. Fix comma-separated ID filters, shared with statistical-journals: - Values were sent to the API as a nested list (`number=[1, 2]`), so multi-value filters never matched. They are now sent flat and the flag can be repeated (`--id 1,2 --id 3`). - An empty value (`--id ""`) is now a usage error instead of silently dropping the filter and listing every row. Fix the `list --all` resume hint for commands with their own filters: it now repeats every filter, spells boolean filters as on/off flags, and shell-quotes values containing spaces.
Add `dualentry webhooks` with topics, list, get, create, update, delete, and deliveries, covering every operation of the webhooks API. - create shows the signing secret in its own panel with a reminder that it is never shown again; JSON output keeps it for scripts. - create and update --clear-topics warn when a webhook has no topics, since it then receives no events. - update changes only what is passed; --topic replaces the subscription list, and --active also clears an auto-disable. - Webhook IDs are validated as UUIDs before any request. - list --all repeats --search and --active/--inactive in its resume hint, and deliveries --all prints a resume command for its own shape. - Delivery times are shown in the local timezone instead of as UTC with the offset cut off.
get/create/update printed raw UTC with microseconds while the delivery list showed local time. The API accepts any public http(s) URL, not only https.
andywoods@Andys-MacBook-Air dualentry-cli % uv run dualentry webhooks create \
> --url https://webhook.site/6f248f36-2b67-42b1-9888-151fd6449633
⚠ Update available: 0.1.18 → 0.1.20 ╭────────────────────────────────────────────────────────────────────╮ ⚠ Update available: 0.1.18 → 0.1.20 ╭────────────────────────────────────────────────────────────────────╮ ⚠ Update available: 0.1.18 → 0.1.20 ╭─────────────────────────────────────╮ ⚠ Update available: 0.1.18 → 0.1.20 ┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┳━━━━━━┳━━━━━━━┓ Showing 1 of 1 ⚠ Update available: 0.1.18 → 0.1.20 { |

Closes DEV-12112
Summary
The public API has a webhooks resource with eight operations: create, list, get, PUT, PATCH, delete, list topics, and list deliveries. The README links to the webhook documentation, but no CLI command could register a webhook, choose its topics, or check what was delivered. This PR adds
dualentry webhooks.Changes
topics,list,get,create,update,delete,deliveries.updateuses PATCH. The API's PUT accepts the same optional fields and runs the same update code, so a separate command would add nothing.-o jsonkeeps it for scripts.createandupdate --clear-topicsprint a warning; the API still allows it, so the CLI does too.updatechanges only what is passed.--topicreplaces the whole subscription list, and the help says so.--topicwith--clear-topics, or anupdatethat changes nothing, is a usage error.--activealso clears an auto-disable after repeated failures, and the help says that too.--allresume hints:list --allrepeats--search(shell-quoted) and--active/--inactive.deliveries --allprints a resume command for its own shape (webhooks deliveries <uuid> --all --offset N), through the shared truncation warning, which now accepts a ready-made command.getshows when and why a webhook was auto-disabled. The deliveries table shows times in the local timezone, not UTC with the offset cut off. All tables were checked at 80 columns. The formatters are typed withTypedDicts that match the API schemas.Test plan
uv run pytest): 344 passed on Python 3.11, 3.12 and 3.13 in CI-equivalent environments. The 137 skipped are integration tests that needX_API_KEY.uv run ruff check src/ tests/anduv run ruff format --check src/ tests/), withuv sync --lockedmainand for this commit alone against Add classifications-lines list command and fix CSV ID filters #39dualentry <command>against a live test organization; see Artifacts.tests/test_webhooks.py(33 tests) covers every command and everyupdateflag,is_active=falsebeing sent, the secret in both output formats, the no-topics warning, rejection before any request (malformed UUID, conflicting or missing options), both resume hints, and local-time rendering with a pinned timezone. Message assertions strip ANSI codes and box borders, so they also pass with CI's color output.Artifacts
Run against a test organization on production (
api.dualentry.com) with a real API key. Every webhook created here pointed atexample.comand was deleted afterwards; the org's webhook list is empty again. Signing secrets are redacted.Found by running it (fixed in 26aeaff)
--urlhelp said "HTTPS endpoint", but the API acceptedhttp://example.com/hook. The backend only rejects private and loopback hosts. The help now says "Public http(s) URL … use https in production".get/create/updateprinted raw UTC with microseconds (2026-10-30T21:22:19.393385+00:00), whiledeliveriesshowed local time. All of them now show local time.Lifecycle
A real delivery
I reactivated the webhook and created a classification to fire
core.classification/created.example.comdoesn't accept POSTs, so the delivery records a 405:Live integration suite
133 passed. The 4 failures also fail on
main(429 without backoff; 403 onvat-rates/product-tax-codesget), so nothing new.Merge note
This PR touches the same CHANGELOG lines and
output.pyarea as #40, so whichever merges second needs a small conflict resolved.