acumatica-cli 0.15.3__tar.gz → 0.16.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.
Files changed (71) hide show
  1. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/PKG-INFO +57 -108
  2. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/README.md +56 -107
  3. acumatica_cli-0.16.0/pyproject.toml +109 -0
  4. acumatica_cli-0.15.3/pyproject.toml → acumatica_cli-0.16.0/pyproject.toml.orig +1 -1
  5. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/cli.py +161 -20
  6. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/config.py +66 -25
  7. acumatica_cli-0.16.0/src/acumatica_cli/inventory.py +548 -0
  8. acumatica_cli-0.16.0/src/acumatica_cli/reconcile.py +760 -0
  9. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/seed.py +3 -3
  10. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/target.py +12 -18
  11. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/README.md +2 -1
  12. acumatica_cli-0.16.0/src/acumatica_cli/templates/env +16 -0
  13. acumatica_cli-0.16.0/src/acumatica_cli/templates/target +7 -0
  14. acumatica_cli-0.15.3/src/acumatica_cli/templates/env +0 -17
  15. acumatica_cli-0.15.3/src/acumatica_cli/templates/target +0 -6
  16. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/__init__.py +0 -0
  17. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/bootstrap.py +0 -0
  18. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/bootstrap_plugin.cs +0 -0
  19. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/bootstrap_project.xml +0 -0
  20. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/client.py +0 -0
  21. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/extract.py +0 -0
  22. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/firstlogin.py +0 -0
  23. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/models.py +0 -0
  24. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/output.py +0 -0
  25. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/run.py +0 -0
  26. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/seed_catalog.yaml +0 -0
  27. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/state.py +0 -0
  28. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/baseline/10-subaccounts.yaml +0 -0
  29. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/baseline/20-accounts.yaml +0 -0
  30. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/baseline/40-ledger.yaml +0 -0
  31. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/baseline/50-gl-preferences.yaml +0 -0
  32. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/baseline/60-ledger-company.yaml +0 -0
  33. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/baseline/90-uoms.yaml +0 -0
  34. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/bootstrap/company.yaml +0 -0
  35. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/bootstrap/credit-terms.yaml +0 -0
  36. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/bootstrap/features.yaml +0 -0
  37. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/10-reason-codes.yaml +0 -0
  38. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/20-in-preferences.yaml +0 -0
  39. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/30-availability-rules.yaml +0 -0
  40. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/40-posting-classes.yaml +0 -0
  41. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/50-warehouse.yaml +0 -0
  42. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/51-warehouse-locations.yaml +0 -0
  43. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/52-warehouse-defaults.yaml +0 -0
  44. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/53-tax-categories.yaml +0 -0
  45. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/54-item-classes.yaml +0 -0
  46. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/56-so-preferences.yaml +0 -0
  47. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/57-po-preferences.yaml +0 -0
  48. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/58-order-types.yaml +0 -0
  49. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/60-ar-preferences.yaml +0 -0
  50. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/61-ap-preferences.yaml +0 -0
  51. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/62-ca-preferences.yaml +0 -0
  52. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/63-cash-account.yaml +0 -0
  53. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/64-payment-methods.yaml +0 -0
  54. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/65-statement-cycles.yaml +0 -0
  55. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/70-vendor-classes.yaml +0 -0
  56. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/71-customer-classes.yaml +0 -0
  57. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/75-vendors.yaml +0 -0
  58. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/76-customers.yaml +0 -0
  59. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/80-stock-items-parts.yaml +0 -0
  60. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/82-stock-items-kits.yaml +0 -0
  61. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/master/85-kit-specifications.yaml +0 -0
  62. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/setup/10-financial-year.yaml +0 -0
  63. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/setup/20-master-calendar.yaml +0 -0
  64. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/setup/30-open-periods.yaml +0 -0
  65. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/config/views/10-trial-balance.yaml +0 -0
  66. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/gitignore +0 -0
  67. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/scenario/10-seed-capital.yaml +0 -0
  68. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/scenario/20-buy.yaml +0 -0
  69. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/scenario/30-build.yaml +0 -0
  70. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/templates/scenario/40-sell.yaml +0 -0
  71. {acumatica_cli-0.15.3 → acumatica_cli-0.16.0}/src/acumatica_cli/tenant.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: acumatica-cli
3
- Version: 0.15.3
3
+ Version: 0.16.0
4
4
  Summary: Acumatica ERP Config-as-Code: tenant provisioning, baseline config, and reference data
5
5
  Author: Konstantin Borovik
6
6
  Author-email: Konstantin Borovik <kb@lab5.ca>
@@ -39,7 +39,7 @@ uv tool install acumatica-cli
39
39
 
40
40
  acu config init --host erp.example.com my-erp
41
41
  cd my-erp # edit .env: set ACU_PASSWORD, ACU_TENANT
42
- # keep ACU_API_VERSION in sync with target.yaml
42
+ # Default API pin = target.yaml default_api
43
43
  # start from a brand-new empty tenant
44
44
 
45
45
  acu config check # read-only preflight (incl. target.yaml)
@@ -49,18 +49,17 @@ acu --tenant DEV apply config/ # seed config/{bootstrap,baseline,setup
49
49
  acu --tenant DEV run scenario/ # once capital → buy → build → sell
50
50
  acu --tenant DEV diff config/ # prove zero drift (exit 2 on drift)
51
51
  acu --tenant DEV state # capture state/ trial-balance
52
- # warm: capital once-skips; Owner Capital stays 50000 (not 100000)
53
- acu --tenant DEV run scenario/
52
+ acu --tenant DEV run scenario/ # replay transaction scenarios
54
53
  ```
55
54
 
56
55
  Bare `apply` / `diff` (no path args) also prefer `config/` when those trees exist.
57
56
  See [docs/demo-seed.md](docs/demo-seed.md) for the entity map, once-guard, and apply-order notes.
58
57
 
59
- **Hosted Acumatica (no SSH):** the tenant already exists; leave `ACU_SSH` blank.
58
+ **Hosted Acumatica (no SSH):** the tenant already exists; set a blank `ACU_SSH=` in `.env` (scaffold omits the key — without it, acu defaults to `Administrator@<ACU_BASE_URL host>` for SSH boxes).
60
59
 
61
60
  ```sh
62
61
  acu config init --host customer.acumatica.com my-erp
63
- cd my-erp # edit .env: ACU_TENANT, ACU_PASSWORD; ACU_SSH=
62
+ cd my-erp # edit .env: ACU_TENANT, ACU_PASSWORD; add ACU_SSH=
64
63
  acu config check # REST preflight; ssh probe is skipped
65
64
  acu --tenant DEV bootstrap # publish AcuBootstrap via REST only
66
65
  acu --tenant DEV apply config/
@@ -89,6 +88,10 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
89
88
  │ capture derived state into state/ (not seed)
90
89
  ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
91
90
  │ inverse of apply into config/{bootstrap,baseline,setup,master}/
91
+ ├── inventory [--out DIR] [--force] [--dry-run] ARTIFACT
92
+ │ offline snapshot artifact → inventory/ (not seed)
93
+ ├── reconcile [--inventory DIR] [--config DIR] [--out DIR] [--force] [--dry-run]
94
+ │ inventory/ + optional config/ → findings/ only
92
95
  ├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
93
96
  │
94
97
  └── config configuration ops
@@ -102,9 +105,28 @@ A path like `config/` expands nested seed dirs in that fixed order.
102
105
  `run` without FILES defaults to `scenario/`.
103
106
  `state` without FILES defaults to `config/views/`; writes go to `state/` (`--out`).
104
107
  `extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
108
+ `inventory` is offline (no REST/SSH/password): SM203520 Settings XML ZIP or `ac.exe export xml` folder writes to `inventory/`.
109
+ `reconcile` is offline: compare `inventory/` to optional `config/` and write `findings/` only (never writes seed).
105
110
  `acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
106
111
  Run `acu <command> --help` for details on any command.
107
112
 
113
+ ### Dual readers, one writer
114
+
115
+ Two read paths, one mutator (V35).
116
+ Do not confuse them with each other or with `state`:
117
+
118
+ | Command | Plane | Input | Writes | Role |
119
+ | ------- | ----- | ----- | ------ | ---- |
120
+ | `extract` | REST (live) | tenant via contract API | `config/{bootstrap,baseline,setup,master}/` | Inverse of `apply` — seed YAML |
121
+ | `inventory` | Offline | SM203520 Settings XML ZIP or `ac.exe export xml` folder | `inventory/` (`summary.yaml` + `tables/`) | Full-table snapshot IR — not seed |
122
+ | `reconcile` | Offline | `inventory/` + optional `config/` | `findings/` only | Cross-check gaps/deltas — never mutates seed or tenant |
123
+ | `state` | REST (live) | `config/views/` | `state/` | Derived balances/totals — not seed, not inventory |
124
+ | `apply` | REST (live) | seed YAML under `config/` | tenant | **Sole** tenant writer (keyed PUT) |
125
+
126
+ `inventory/` and `findings/` are engagement outputs: not SEED_DIRS, never loaded by `apply`/`diff`, not scaffolded by `config init`.
127
+ Binary `.adb` snapshots are rejected (XML only).
128
+ See [docs/ac-exe.md](docs/ac-exe.md) for export / SM203520 notes and [docs/demo-seed.md](docs/demo-seed.md) for the extract/state/inventory map.
129
+
108
130
  ## The data repo
109
131
 
110
132
  Your configuration lives in its own git repo.
@@ -120,13 +142,14 @@ There is no `--flavor`.
120
142
  | `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
121
143
  | `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
122
144
  | `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
145
+ | `inventory/` | engagement: offline snapshot tables from `acu inventory` (not seed; not SEED_DIRS) |
146
+ | `findings/` | engagement: `acu reconcile` cross-check output (never apply path) |
123
147
  | `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
124
148
  | `.env` | where to apply and who signs in, every key an `ACU_*` variable |
125
149
 
126
150
  Legacy data repos may still keep root `bootstrap/`…`master/`; bare `apply`/`diff` prefer `config/` when present and never merge both trees.
127
151
 
128
152
  Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-`, and so on) encode dependency order.
129
- The scaffolded `.gitignore` keeps `.env` out of git — store it encrypted (for example as `.env.gpg`) and decrypt once per clone.
130
153
  Commit `target.yaml` with the seeds so every clone knows the verified ERP line and Default API generation.
131
154
 
132
155
  Seed YAML is state: `apply` upserts it, `diff` proves it.
@@ -141,38 +164,19 @@ git diff config/ # review extract delta before commit
141
164
  acu --tenant DEV apply config/ # replay extracted seed
142
165
  acu --tenant DEV diff config/ # expect exit 0
143
166
  ```
144
-
145
- **Migration (T115–T120 extract hard-cut):** extract no longer writes root `bootstrap/` / `baseline/` / `setup/` / `master/`.
146
- Paths are always `config/…`.
147
- There is no `--layout`.
148
- Move any root-layout extract output under `config/`, or re-extract into a modern data repo.
149
- Catalog rename: `extract_manifest.yaml` becomes `seed_catalog.yaml` (package data only; operators do not author it).
150
-
151
- Scenario YAML is different — it describes transactions that flow forward (not extractable seed).
152
- `acu run` executes each step in order (`put`, `action`, `wait`, `get`), captures server-assigned document numbers into `${var}` references for later steps, and checks `expect:` assertions as deltas against a pre-run snapshot, so additive scenarios re-run safely on a warm tenant.
153
- `once: true` scenarios declare a `present` inquire-absolute gate; when the probe already holds, the CLI prints `skip <path> (once: already present)` and runs neither steps nor expects (Owner Capital does not restack).
154
-
155
- `acu state` is the third observation path: it captures live derived state (balances) into `state/` for git review.
156
- It is not `extract` (config seed) and not `diff` (desired vs actual config).
157
- Packaged golden is trial-balance via contract `inquire:` (`EndingBalance` fixed-point); inventory-summary is not golden this pass. `gi:` stays optional when a GI is V12-verified and **Expose via OData** is on (`params` fail-closed vs `$metadata`).
158
- After a cold `acu run scenario/ && acu state`, warm `acu run scenario/10-seed-capital.yaml && acu state --assert-unchanged` is the once-class gate (full scenario re-run is additive and moves cash observations on the TB).
159
-
160
- **Migration (T112–T114 hard-cut):** CLI verb is `state` only (no `snapshot` alias).
161
- Bare defaults are `config/views/` (views) and `state/` (observations).
162
- Prior `config/snapshot/` and root `snapshot/` / `snapshots/` are not fallbacks — move views to `config/views/` or pass explicit path args.
163
-
164
167
  ### Seed `endpoint:` symbols
165
168
 
166
169
  Dual-served entities (on both Bootstrap and Default) need an explicit `endpoint:` line.
167
170
 
168
171
  | Value | Resolves to |
169
172
  | ----- | ----------- |
170
- | omitted | `Default/<ACU_API_VERSION>` for Default-only entities |
173
+ | omitted | `Default/<api_version>` for Default-only entities |
171
174
  | `bootstrap` | active `Bootstrap/<ver>` from `bootstrap/project.xml` or the packaged contract |
172
- | `default` | `Default/<ACU_API_VERSION>` — tracks the operator API version |
173
- | `Bootstrap/1.0.0` or `Default/25.200.001` | literal pin (ignores `ACU_API_VERSION` for Default) |
175
+ | `default` | `Default/<api_version>` — tracks the resolved API version |
176
+ | `Bootstrap/1.0.0` or `Default/25.200.001` | literal pin (ignores the resolved Default version) |
174
177
 
175
- Prefer symbolic `default` over a pinned `Default/25.200.001` so the seed tree travels with the configured API generation.
178
+ `api_version` resolves as `--api-version` flag, else `target.yaml` `default_api` when present, else code default `25.200.001` (never `ACU_API_VERSION` in `.env`).
179
+ Prefer symbolic `default` over a pinned `Default/25.200.001` so the seed tree travels with the dataset pin.
176
180
 
177
181
  ## Installation
178
182
 
@@ -193,41 +197,45 @@ Verify with `acu --version`.
193
197
 
194
198
  ## Configuration
195
199
 
196
- Everything lives in one `.env` file: *where* to apply and *who* signs in.
197
- Three values are required; everything else has a code default matching a stock Acumatica install:
200
+ Everything lives in one `.env` file: *where* to apply and *who* signs in
201
+ (`ACU_*` vars only).
202
+ The Default contract API pin is **not** in `.env` — it
203
+ lives in committed `target.yaml` (`default_api`).
198
204
 
199
205
  ```sh
200
206
  ACU_BASE_URL=http://acu-dev1.vm.internal/AcumaticaERP # required: REST root
201
207
  ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
202
- ACU_SSH=Administrator@acu-dev1.vm.internal # optional: control-plane user@host (tenant CRUD)
203
- ACU_API_VERSION=25.200.001 # Default contract version half only
208
+ # ACU_SSH omitted → defaults to Administrator@acu-dev1.vm.internal
209
+ # ACU_SSH= # hosted opt-out (blank key)
204
210
  ACU_USER=admin # optional, defaults to admin
205
211
  ACU_PASSWORD=... # required for live commands
206
212
  ```
207
213
 
208
- `ACU_API_VERSION` is the version half only (`25.200.001`), never `Default/25.200.001`.
209
- A full path would nest as `/entity/Default/Default/...`.
214
+ There is no `ACU_API_VERSION` env key (unknown `ACU_*` vars are ignored).
215
+ Ad-hoc override: `acu --api-version 24.200.001 …` (version half only, never
216
+ `Default/25.200.001` — a full path would nest as `/entity/Default/Default/...`).
210
217
 
211
- The committed `target.yaml` next to `.env` declares the verified matrix (what, not where):
218
+ The committed `target.yaml` next to `.env` declares the verified matrix (what, not where) and is the **sole data-repo Default pin**:
212
219
 
213
220
  ```yaml
214
- erp: "26.101.0225" # claimed product line/build (README-level detail)
215
- default_api: "25.200.001" # must match ACU_API_VERSION
221
+ erp: "26.101.0225" # claimed product line/build
222
+ default_api: "25.200.001" # sources Instance.api_version when --api-version absent
216
223
  ```
217
224
 
218
- When `target.yaml` is present, `apply` / `diff` / `run` / `extract` / `schema` / `bootstrap` hard-fail if `default_api` does not match the configured API version.
219
- `acu config check` reports the same match as a probe line.
220
- Missing `target.yaml` only warns on check unless you pass `--strict`.
225
+ When `target.yaml` is present, live commands resolve `api_version` from
226
+ `default_api` (source-merge). `acu config check` reports
227
+ `ok target (api_version from default_api=…; erp=… claimed)`.
228
+ Missing `target.yaml` only warns on check unless you pass `--strict` (then
229
+ the code default `25.200.001` is used).
221
230
 
222
231
  Worth knowing:
223
232
 
224
233
  - The file is found by walking up from the current directory, so any subdirectory of the data repo works.
225
234
  - Without a `.env`, global flags plus the process environment supply the full configuration.
226
- - `ACU_SSH` is optional.
227
- - Leave it blank on hosted instances; only `acu tenant` needs it.
228
- - Nothing is derived: split-horizon DNS, port forwards, and jump hosts are all handled by writing the address you actually want into the address keys.
229
- - `acu config show` prints the fully resolved configuration as a complete, valid `.env` — every knob visible, the password excluded.
230
- - When `target.yaml` is present, `config show` also comments `erp` / `default_api`.
235
+ - When `ACU_SSH` is **absent**, acu defaults to `Administrator@` + the `ACU_BASE_URL` hostname.
236
+ A **present blank** `ACU_SSH=` is the hosted opt-out.
237
+ Only `acu tenant` requires a non-empty value post-default.
238
+ - `acu config show` prints the resolved `.env` (password excluded; never `ACU_API_VERSION`) and comments `erp` / `default_api` plus the `api_version` source when `target.yaml` is present.
231
239
  - Redirect it to turn resolved state into a working config: `acu config show > .env`.
232
240
 
233
241
  Verify before touching anything live:
@@ -238,65 +246,6 @@ acu config check --strict # missing target.yaml becomes fail
238
246
  acu apply --dry-run # show what would be written, write nothing
239
247
  ```
240
248
 
241
- ## Control and Data Planes
242
-
243
- `acu` talks to an instance over two independent channels:
244
-
245
- - **Control plane (SSH):** `acu tenant` runs `ac.exe -cm:CompanyConfig` and `sqlcmd` on the Windows guest — see [`docs/ac-exe.md`](docs/ac-exe.md).
246
- - **Data plane (REST):** `acu bootstrap`, `apply`, `diff`, `run`, `extract`, and `schema` use the contract-based API and CustomizationApi — see [`docs/rest-api.md`](docs/rest-api.md).
247
-
248
- If you never touch `acu tenant`, you never need SSH.
249
- On a hosted instance, publish AcuBootstrap with `acu bootstrap`, then seed with `apply`.
250
- `acu bootstrap --export PATH` writes the package zip for manual import on the Customization Projects screen (SM204505) when you cannot call the API.
251
-
252
- ## SSH setup (control plane)
253
-
254
- `acu tenant` runs commands on the Windows guest through plain `ssh`.
255
- Two things about this setup are not obvious, and both are hard requirements.
256
-
257
- **1.
258
- The default SSH shell on the Windows guest must be PowerShell.**
259
- `acu` sends PowerShell syntax over the wire, and every one of those commands fails under `cmd.exe`, the Windows OpenSSH default.
260
- Switch it once, in an elevated PowerShell on the guest:
261
-
262
- ```powershell
263
- New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell `
264
- -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" `
265
- -PropertyType String -Force
266
- ```
267
-
268
- **2.
269
- Authentication must be key-based and non-interactive.**
270
- `acu` connects with `BatchMode=yes`, so it will never answer a password prompt.
271
- Because the default user is `Administrator` (an administrators-group member), Windows OpenSSH reads the key from the *machine-wide* file `C:\ProgramData\ssh\administrators_authorized_keys` — **not** from `~\.ssh\authorized_keys` like on Linux.
272
- On the guest:
273
-
274
- Install + start the server (once):
275
-
276
- ```powershell
277
- Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
278
- Set-Service sshd -StartupType Automatic
279
- Start-Service sshd
280
- ```
281
-
282
- Authorize your public key for administrators:
283
-
284
- ```powershell
285
- Add-Content -Path C:\ProgramData\ssh\administrators_authorized_keys -Value "ssh-ed25519 AAAA... you@laptop"
286
- ```
287
-
288
- The file must be readable by SYSTEM/Administrators only, or sshd ignores it:
289
-
290
- ```powershell
291
- icacls C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"
292
- ```
293
-
294
- Then verify from your workstation — this one test proves both requirements at once (key auth works, and the shell is PowerShell):
295
-
296
- ```sh
297
- ssh -o BatchMode=yes Administrator@acu-dev1.vm.internal '$PSVersionTable.PSVersion'
298
- ```
299
-
300
249
  ## Development
301
250
 
302
251
  Requires **GNU Make at least 3.82** — the Makefile uses `.ONESHELL`.
@@ -328,7 +277,7 @@ GitHub Actions re-runs the check on the tag, then publishes the GitHub release a
328
277
 
329
278
  `gmake e2e` runs the opt-in live tier against a real Acumatica instance (pytest marker `e2e`, deselected by the default suite).
330
279
 
331
- Configuration is one file: a decrypted `.env` at the repo root names the instance — `ACU_BASE_URL`, `ACU_SSH`, `ACU_TENANT`, `ACU_PASSWORD`.
280
+ Configuration is one file: a decrypted `.env` at the repo root names the instance — `ACU_BASE_URL`, `ACU_TENANT`, `ACU_PASSWORD` (and optional `ACU_SSH`; omitted defaults to `Administrator@` + base-url host).
332
281
  `gmake e2e` refuses to start without it.
333
282
 
334
283
  The tier is self-contained.
@@ -21,7 +21,7 @@ uv tool install acumatica-cli
21
21
 
22
22
  acu config init --host erp.example.com my-erp
23
23
  cd my-erp # edit .env: set ACU_PASSWORD, ACU_TENANT
24
- # keep ACU_API_VERSION in sync with target.yaml
24
+ # Default API pin = target.yaml default_api
25
25
  # start from a brand-new empty tenant
26
26
 
27
27
  acu config check # read-only preflight (incl. target.yaml)
@@ -31,18 +31,17 @@ acu --tenant DEV apply config/ # seed config/{bootstrap,baseline,setup
31
31
  acu --tenant DEV run scenario/ # once capital → buy → build → sell
32
32
  acu --tenant DEV diff config/ # prove zero drift (exit 2 on drift)
33
33
  acu --tenant DEV state # capture state/ trial-balance
34
- # warm: capital once-skips; Owner Capital stays 50000 (not 100000)
35
- acu --tenant DEV run scenario/
34
+ acu --tenant DEV run scenario/ # replay transaction scenarios
36
35
  ```
37
36
 
38
37
  Bare `apply` / `diff` (no path args) also prefer `config/` when those trees exist.
39
38
  See [docs/demo-seed.md](docs/demo-seed.md) for the entity map, once-guard, and apply-order notes.
40
39
 
41
- **Hosted Acumatica (no SSH):** the tenant already exists; leave `ACU_SSH` blank.
40
+ **Hosted Acumatica (no SSH):** the tenant already exists; set a blank `ACU_SSH=` in `.env` (scaffold omits the key — without it, acu defaults to `Administrator@<ACU_BASE_URL host>` for SSH boxes).
42
41
 
43
42
  ```sh
44
43
  acu config init --host customer.acumatica.com my-erp
45
- cd my-erp # edit .env: ACU_TENANT, ACU_PASSWORD; ACU_SSH=
44
+ cd my-erp # edit .env: ACU_TENANT, ACU_PASSWORD; add ACU_SSH=
46
45
  acu config check # REST preflight; ssh probe is skipped
47
46
  acu --tenant DEV bootstrap # publish AcuBootstrap via REST only
48
47
  acu --tenant DEV apply config/
@@ -71,6 +70,10 @@ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
71
70
  │ capture derived state into state/ (not seed)
72
71
  ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
73
72
  │ inverse of apply into config/{bootstrap,baseline,setup,master}/
73
+ ├── inventory [--out DIR] [--force] [--dry-run] ARTIFACT
74
+ │ offline snapshot artifact → inventory/ (not seed)
75
+ ├── reconcile [--inventory DIR] [--config DIR] [--out DIR] [--force] [--dry-run]
76
+ │ inventory/ + optional config/ → findings/ only
74
77
  ├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
75
78
  │
76
79
  └── config configuration ops
@@ -84,9 +87,28 @@ A path like `config/` expands nested seed dirs in that fixed order.
84
87
  `run` without FILES defaults to `scenario/`.
85
88
  `state` without FILES defaults to `config/views/`; writes go to `state/` (`--out`).
86
89
  `extract` always writes under `config/{bootstrap,baseline,setup,master}/` (catalog-driven; never root SEED_DIRS).
90
+ `inventory` is offline (no REST/SSH/password): SM203520 Settings XML ZIP or `ac.exe export xml` folder writes to `inventory/`.
91
+ `reconcile` is offline: compare `inventory/` to optional `config/` and write `findings/` only (never writes seed).
87
92
  `acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
88
93
  Run `acu <command> --help` for details on any command.
89
94
 
95
+ ### Dual readers, one writer
96
+
97
+ Two read paths, one mutator (V35).
98
+ Do not confuse them with each other or with `state`:
99
+
100
+ | Command | Plane | Input | Writes | Role |
101
+ | ------- | ----- | ----- | ------ | ---- |
102
+ | `extract` | REST (live) | tenant via contract API | `config/{bootstrap,baseline,setup,master}/` | Inverse of `apply` — seed YAML |
103
+ | `inventory` | Offline | SM203520 Settings XML ZIP or `ac.exe export xml` folder | `inventory/` (`summary.yaml` + `tables/`) | Full-table snapshot IR — not seed |
104
+ | `reconcile` | Offline | `inventory/` + optional `config/` | `findings/` only | Cross-check gaps/deltas — never mutates seed or tenant |
105
+ | `state` | REST (live) | `config/views/` | `state/` | Derived balances/totals — not seed, not inventory |
106
+ | `apply` | REST (live) | seed YAML under `config/` | tenant | **Sole** tenant writer (keyed PUT) |
107
+
108
+ `inventory/` and `findings/` are engagement outputs: not SEED_DIRS, never loaded by `apply`/`diff`, not scaffolded by `config init`.
109
+ Binary `.adb` snapshots are rejected (XML only).
110
+ See [docs/ac-exe.md](docs/ac-exe.md) for export / SM203520 notes and [docs/demo-seed.md](docs/demo-seed.md) for the extract/state/inventory map.
111
+
90
112
  ## The data repo
91
113
 
92
114
  Your configuration lives in its own git repo.
@@ -102,13 +124,14 @@ There is no `--flavor`.
102
124
  | `scenario/` | lifecycle txns for `acu run`: once capital, then buy, build, sell |
103
125
  | `config/views/` | observer views for `acu state` (`inquire:` / `entity:` / `gi:`; not SEED_DIRS) |
104
126
  | `state/` | committed derived-state observations (evidence, not seed; money/qty fixed-point) |
127
+ | `inventory/` | engagement: offline snapshot tables from `acu inventory` (not seed; not SEED_DIRS) |
128
+ | `findings/` | engagement: `acu reconcile` cross-check output (never apply path) |
105
129
  | `target.yaml` | committed verified matrix: `erp` + `default_api` (what, not where) |
106
130
  | `.env` | where to apply and who signs in, every key an `ACU_*` variable |
107
131
 
108
132
  Legacy data repos may still keep root `bootstrap/`…`master/`; bare `apply`/`diff` prefer `config/` when present and never merge both trees.
109
133
 
110
134
  Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-`, and so on) encode dependency order.
111
- The scaffolded `.gitignore` keeps `.env` out of git — store it encrypted (for example as `.env.gpg`) and decrypt once per clone.
112
135
  Commit `target.yaml` with the seeds so every clone knows the verified ERP line and Default API generation.
113
136
 
114
137
  Seed YAML is state: `apply` upserts it, `diff` proves it.
@@ -123,38 +146,19 @@ git diff config/ # review extract delta before commit
123
146
  acu --tenant DEV apply config/ # replay extracted seed
124
147
  acu --tenant DEV diff config/ # expect exit 0
125
148
  ```
126
-
127
- **Migration (T115–T120 extract hard-cut):** extract no longer writes root `bootstrap/` / `baseline/` / `setup/` / `master/`.
128
- Paths are always `config/…`.
129
- There is no `--layout`.
130
- Move any root-layout extract output under `config/`, or re-extract into a modern data repo.
131
- Catalog rename: `extract_manifest.yaml` becomes `seed_catalog.yaml` (package data only; operators do not author it).
132
-
133
- Scenario YAML is different — it describes transactions that flow forward (not extractable seed).
134
- `acu run` executes each step in order (`put`, `action`, `wait`, `get`), captures server-assigned document numbers into `${var}` references for later steps, and checks `expect:` assertions as deltas against a pre-run snapshot, so additive scenarios re-run safely on a warm tenant.
135
- `once: true` scenarios declare a `present` inquire-absolute gate; when the probe already holds, the CLI prints `skip <path> (once: already present)` and runs neither steps nor expects (Owner Capital does not restack).
136
-
137
- `acu state` is the third observation path: it captures live derived state (balances) into `state/` for git review.
138
- It is not `extract` (config seed) and not `diff` (desired vs actual config).
139
- Packaged golden is trial-balance via contract `inquire:` (`EndingBalance` fixed-point); inventory-summary is not golden this pass. `gi:` stays optional when a GI is V12-verified and **Expose via OData** is on (`params` fail-closed vs `$metadata`).
140
- After a cold `acu run scenario/ && acu state`, warm `acu run scenario/10-seed-capital.yaml && acu state --assert-unchanged` is the once-class gate (full scenario re-run is additive and moves cash observations on the TB).
141
-
142
- **Migration (T112–T114 hard-cut):** CLI verb is `state` only (no `snapshot` alias).
143
- Bare defaults are `config/views/` (views) and `state/` (observations).
144
- Prior `config/snapshot/` and root `snapshot/` / `snapshots/` are not fallbacks — move views to `config/views/` or pass explicit path args.
145
-
146
149
  ### Seed `endpoint:` symbols
147
150
 
148
151
  Dual-served entities (on both Bootstrap and Default) need an explicit `endpoint:` line.
149
152
 
150
153
  | Value | Resolves to |
151
154
  | ----- | ----------- |
152
- | omitted | `Default/<ACU_API_VERSION>` for Default-only entities |
155
+ | omitted | `Default/<api_version>` for Default-only entities |
153
156
  | `bootstrap` | active `Bootstrap/<ver>` from `bootstrap/project.xml` or the packaged contract |
154
- | `default` | `Default/<ACU_API_VERSION>` — tracks the operator API version |
155
- | `Bootstrap/1.0.0` or `Default/25.200.001` | literal pin (ignores `ACU_API_VERSION` for Default) |
157
+ | `default` | `Default/<api_version>` — tracks the resolved API version |
158
+ | `Bootstrap/1.0.0` or `Default/25.200.001` | literal pin (ignores the resolved Default version) |
156
159
 
157
- Prefer symbolic `default` over a pinned `Default/25.200.001` so the seed tree travels with the configured API generation.
160
+ `api_version` resolves as `--api-version` flag, else `target.yaml` `default_api` when present, else code default `25.200.001` (never `ACU_API_VERSION` in `.env`).
161
+ Prefer symbolic `default` over a pinned `Default/25.200.001` so the seed tree travels with the dataset pin.
158
162
 
159
163
  ## Installation
160
164
 
@@ -175,41 +179,45 @@ Verify with `acu --version`.
175
179
 
176
180
  ## Configuration
177
181
 
178
- Everything lives in one `.env` file: *where* to apply and *who* signs in.
179
- Three values are required; everything else has a code default matching a stock Acumatica install:
182
+ Everything lives in one `.env` file: *where* to apply and *who* signs in
183
+ (`ACU_*` vars only).
184
+ The Default contract API pin is **not** in `.env` — it
185
+ lives in committed `target.yaml` (`default_api`).
180
186
 
181
187
  ```sh
182
188
  ACU_BASE_URL=http://acu-dev1.vm.internal/AcumaticaERP # required: REST root
183
189
  ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
184
- ACU_SSH=Administrator@acu-dev1.vm.internal # optional: control-plane user@host (tenant CRUD)
185
- ACU_API_VERSION=25.200.001 # Default contract version half only
190
+ # ACU_SSH omitted → defaults to Administrator@acu-dev1.vm.internal
191
+ # ACU_SSH= # hosted opt-out (blank key)
186
192
  ACU_USER=admin # optional, defaults to admin
187
193
  ACU_PASSWORD=... # required for live commands
188
194
  ```
189
195
 
190
- `ACU_API_VERSION` is the version half only (`25.200.001`), never `Default/25.200.001`.
191
- A full path would nest as `/entity/Default/Default/...`.
196
+ There is no `ACU_API_VERSION` env key (unknown `ACU_*` vars are ignored).
197
+ Ad-hoc override: `acu --api-version 24.200.001 …` (version half only, never
198
+ `Default/25.200.001` — a full path would nest as `/entity/Default/Default/...`).
192
199
 
193
- The committed `target.yaml` next to `.env` declares the verified matrix (what, not where):
200
+ The committed `target.yaml` next to `.env` declares the verified matrix (what, not where) and is the **sole data-repo Default pin**:
194
201
 
195
202
  ```yaml
196
- erp: "26.101.0225" # claimed product line/build (README-level detail)
197
- default_api: "25.200.001" # must match ACU_API_VERSION
203
+ erp: "26.101.0225" # claimed product line/build
204
+ default_api: "25.200.001" # sources Instance.api_version when --api-version absent
198
205
  ```
199
206
 
200
- When `target.yaml` is present, `apply` / `diff` / `run` / `extract` / `schema` / `bootstrap` hard-fail if `default_api` does not match the configured API version.
201
- `acu config check` reports the same match as a probe line.
202
- Missing `target.yaml` only warns on check unless you pass `--strict`.
207
+ When `target.yaml` is present, live commands resolve `api_version` from
208
+ `default_api` (source-merge). `acu config check` reports
209
+ `ok target (api_version from default_api=…; erp=… claimed)`.
210
+ Missing `target.yaml` only warns on check unless you pass `--strict` (then
211
+ the code default `25.200.001` is used).
203
212
 
204
213
  Worth knowing:
205
214
 
206
215
  - The file is found by walking up from the current directory, so any subdirectory of the data repo works.
207
216
  - Without a `.env`, global flags plus the process environment supply the full configuration.
208
- - `ACU_SSH` is optional.
209
- - Leave it blank on hosted instances; only `acu tenant` needs it.
210
- - Nothing is derived: split-horizon DNS, port forwards, and jump hosts are all handled by writing the address you actually want into the address keys.
211
- - `acu config show` prints the fully resolved configuration as a complete, valid `.env` — every knob visible, the password excluded.
212
- - When `target.yaml` is present, `config show` also comments `erp` / `default_api`.
217
+ - When `ACU_SSH` is **absent**, acu defaults to `Administrator@` + the `ACU_BASE_URL` hostname.
218
+ A **present blank** `ACU_SSH=` is the hosted opt-out.
219
+ Only `acu tenant` requires a non-empty value post-default.
220
+ - `acu config show` prints the resolved `.env` (password excluded; never `ACU_API_VERSION`) and comments `erp` / `default_api` plus the `api_version` source when `target.yaml` is present.
213
221
  - Redirect it to turn resolved state into a working config: `acu config show > .env`.
214
222
 
215
223
  Verify before touching anything live:
@@ -220,65 +228,6 @@ acu config check --strict # missing target.yaml becomes fail
220
228
  acu apply --dry-run # show what would be written, write nothing
221
229
  ```
222
230
 
223
- ## Control and Data Planes
224
-
225
- `acu` talks to an instance over two independent channels:
226
-
227
- - **Control plane (SSH):** `acu tenant` runs `ac.exe -cm:CompanyConfig` and `sqlcmd` on the Windows guest — see [`docs/ac-exe.md`](docs/ac-exe.md).
228
- - **Data plane (REST):** `acu bootstrap`, `apply`, `diff`, `run`, `extract`, and `schema` use the contract-based API and CustomizationApi — see [`docs/rest-api.md`](docs/rest-api.md).
229
-
230
- If you never touch `acu tenant`, you never need SSH.
231
- On a hosted instance, publish AcuBootstrap with `acu bootstrap`, then seed with `apply`.
232
- `acu bootstrap --export PATH` writes the package zip for manual import on the Customization Projects screen (SM204505) when you cannot call the API.
233
-
234
- ## SSH setup (control plane)
235
-
236
- `acu tenant` runs commands on the Windows guest through plain `ssh`.
237
- Two things about this setup are not obvious, and both are hard requirements.
238
-
239
- **1.
240
- The default SSH shell on the Windows guest must be PowerShell.**
241
- `acu` sends PowerShell syntax over the wire, and every one of those commands fails under `cmd.exe`, the Windows OpenSSH default.
242
- Switch it once, in an elevated PowerShell on the guest:
243
-
244
- ```powershell
245
- New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell `
246
- -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" `
247
- -PropertyType String -Force
248
- ```
249
-
250
- **2.
251
- Authentication must be key-based and non-interactive.**
252
- `acu` connects with `BatchMode=yes`, so it will never answer a password prompt.
253
- Because the default user is `Administrator` (an administrators-group member), Windows OpenSSH reads the key from the *machine-wide* file `C:\ProgramData\ssh\administrators_authorized_keys` — **not** from `~\.ssh\authorized_keys` like on Linux.
254
- On the guest:
255
-
256
- Install + start the server (once):
257
-
258
- ```powershell
259
- Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
260
- Set-Service sshd -StartupType Automatic
261
- Start-Service sshd
262
- ```
263
-
264
- Authorize your public key for administrators:
265
-
266
- ```powershell
267
- Add-Content -Path C:\ProgramData\ssh\administrators_authorized_keys -Value "ssh-ed25519 AAAA... you@laptop"
268
- ```
269
-
270
- The file must be readable by SYSTEM/Administrators only, or sshd ignores it:
271
-
272
- ```powershell
273
- icacls C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"
274
- ```
275
-
276
- Then verify from your workstation — this one test proves both requirements at once (key auth works, and the shell is PowerShell):
277
-
278
- ```sh
279
- ssh -o BatchMode=yes Administrator@acu-dev1.vm.internal '$PSVersionTable.PSVersion'
280
- ```
281
-
282
231
  ## Development
283
232
 
284
233
  Requires **GNU Make at least 3.82** — the Makefile uses `.ONESHELL`.
@@ -310,7 +259,7 @@ GitHub Actions re-runs the check on the tag, then publishes the GitHub release a
310
259
 
311
260
  `gmake e2e` runs the opt-in live tier against a real Acumatica instance (pytest marker `e2e`, deselected by the default suite).
312
261
 
313
- Configuration is one file: a decrypted `.env` at the repo root names the instance — `ACU_BASE_URL`, `ACU_SSH`, `ACU_TENANT`, `ACU_PASSWORD`.
262
+ Configuration is one file: a decrypted `.env` at the repo root names the instance — `ACU_BASE_URL`, `ACU_TENANT`, `ACU_PASSWORD` (and optional `ACU_SSH`; omitted defaults to `Administrator@` + base-url host).
314
263
  `gmake e2e` refuses to start without it.
315
264
 
316
265
  The tier is self-contained.
@@ -0,0 +1,109 @@
1
+ [project]
2
+ name = "acumatica-cli"
3
+ version = "0.16.0"
4
+ description = "Acumatica ERP Config-as-Code: tenant provisioning, baseline config, and reference data"
5
+ license = "PolyForm-Noncommercial-1.0.0"
6
+ readme = "README.md"
7
+ requires-python = ">=3.14"
8
+ dependencies = [
9
+ "click>=8.1",
10
+ "httpx>=0.27",
11
+ "pydantic>=2.7",
12
+ "pydantic-settings>=2.5",
13
+ "pyyaml>=6.0",
14
+ "rich>=13",
15
+ ]
16
+
17
+ [[project.authors]]
18
+ name = "Konstantin Borovik"
19
+ email = "kb@lab5.ca"
20
+
21
+ [project.urls]
22
+ Homepage = "https://lab5.ca"
23
+ Repository = "https://github.com/kborovik/acumatica-cli"
24
+
25
+ [project.scripts]
26
+ acu = "acumatica_cli.cli:main"
27
+
28
+ [build-system]
29
+ requires = ["uv_build>=0.11,<0.12"]
30
+ build-backend = "uv_build"
31
+
32
+ [dependency-groups]
33
+ dev = [
34
+ "basedpyright>=1.37.0",
35
+ "pytest>=8",
36
+ "ruff>=0.13.0",
37
+ ]
38
+
39
+ [tool.pytest.ini_options]
40
+ testpaths = ["tests"]
41
+ addopts = '-q -m "not e2e"'
42
+ markers = ["e2e: live end-to-end tests against a real Acumatica instance (make e2e)"]
43
+
44
+ [tool.basedpyright]
45
+ typeCheckingMode = "strict"
46
+ pythonVersion = "3.14"
47
+ include = [
48
+ "src/acumatica_cli/",
49
+ "tests/",
50
+ ]
51
+ reportMissingTypeStubs = false
52
+ reportUnknownVariableType = false
53
+ reportUnknownMemberType = false
54
+ reportUnknownArgumentType = false
55
+ reportUnknownParameterType = false
56
+ reportUnknownLambdaType = false
57
+ reportUnusedFunction = "warning"
58
+
59
+ [tool.ruff]
60
+ target-version = "py314"
61
+ src = ["src"]
62
+
63
+ [tool.ruff.format]
64
+ docstring-code-format = true
65
+
66
+ [tool.ruff.lint]
67
+ fixable = ["ALL"]
68
+ select = [
69
+ "B",
70
+ "C4",
71
+ "C90",
72
+ "COM",
73
+ "D",
74
+ "E",
75
+ "F",
76
+ "I",
77
+ "N",
78
+ "PL",
79
+ "PT",
80
+ "RUF",
81
+ "SIM",
82
+ "T20",
83
+ "TID",
84
+ "UP",
85
+ "W",
86
+ ]
87
+ ignore = [
88
+ "PLR2004",
89
+ "PLC0415",
90
+ "D100",
91
+ "D104",
92
+ "D105",
93
+ "D107",
94
+ "COM812",
95
+ ]
96
+
97
+ [tool.ruff.lint.pydocstyle]
98
+ convention = "google"
99
+
100
+ [tool.ruff.lint.isort]
101
+ known-first-party = ["acumatica_cli"]
102
+
103
+ [tool.ruff.lint.per-file-ignores]
104
+ "src/acumatica_cli/cli.py" = ["PLR0913"]
105
+ "tests/*" = [
106
+ "D101",
107
+ "D102",
108
+ "D103",
109
+ ]