acumatica-cli 0.3.0__tar.gz → 0.5.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 (34) hide show
  1. acumatica_cli-0.5.0/PKG-INFO +225 -0
  2. acumatica_cli-0.5.0/README.md +208 -0
  3. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/pyproject.toml +1 -1
  4. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/bootstrap_project.xml +329 -2
  5. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/cli.py +120 -5
  6. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/client.py +56 -9
  7. acumatica_cli-0.5.0/src/acumatica_cli/extract.py +503 -0
  8. acumatica_cli-0.5.0/src/acumatica_cli/extract_manifest.yaml +151 -0
  9. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/output.py +3 -1
  10. acumatica_cli-0.5.0/src/acumatica_cli/seed.py +449 -0
  11. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/templates/baseline/50-gl-preferences.yaml +2 -2
  12. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/templates/baseline/60-ledger-company.yaml +10 -8
  13. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/templates/bootstrap/company.yaml +2 -2
  14. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/templates/bootstrap/credit-terms.yaml +2 -2
  15. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/templates/setup/10-financial-year.yaml +1 -1
  16. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/templates/setup/20-master-calendar.yaml +1 -1
  17. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/templates/setup/30-open-periods.yaml +1 -1
  18. acumatica_cli-0.3.0/PKG-INFO +0 -239
  19. acumatica_cli-0.3.0/README.md +0 -222
  20. acumatica_cli-0.3.0/src/acumatica_cli/seed.py +0 -276
  21. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/__init__.py +0 -0
  22. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/bootstrap.py +0 -0
  23. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/bootstrap_plugin.cs +0 -0
  24. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/config.py +0 -0
  25. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/firstlogin.py +0 -0
  26. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/models.py +0 -0
  27. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/templates/baseline/10-subaccounts.yaml +0 -0
  28. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/templates/baseline/20-accounts.yaml +0 -0
  29. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/templates/baseline/40-ledger.yaml +0 -0
  30. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/templates/baseline/90-uoms.yaml +0 -0
  31. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/templates/bootstrap/features.yaml +0 -0
  32. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/templates/env +0 -0
  33. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/templates/gitignore +0 -0
  34. {acumatica_cli-0.3.0 → acumatica_cli-0.5.0}/src/acumatica_cli/tenant.py +0 -0
@@ -0,0 +1,225 @@
1
+ Metadata-Version: 2.3
2
+ Name: acumatica-cli
3
+ Version: 0.5.0
4
+ Summary: Acumatica ERP Config-as-Code: tenant provisioning, baseline config, and reference data
5
+ Author: Konstantin Borovik
6
+ Author-email: Konstantin Borovik <kb@lab5.ca>
7
+ Requires-Dist: click>=8.1
8
+ Requires-Dist: httpx>=0.27
9
+ Requires-Dist: pydantic>=2.7
10
+ Requires-Dist: pydantic-settings>=2.5
11
+ Requires-Dist: pyyaml>=6.0
12
+ Requires-Dist: rich>=13
13
+ Requires-Python: >=3.12
14
+ Project-URL: Homepage, https://lab5.ca
15
+ Project-URL: Repository, https://github.com/kborovik/acumatica-cli
16
+ Description-Content-Type: text/markdown
17
+
18
+ # Acumatica GitOps (Config-as-Code)
19
+
20
+ **`acu`** configures Acumatica ERP from YAML files in a git repo (GitOps).
21
+
22
+ **No UI clicks, no Configuration Wizard.**
23
+
24
+ ## How it works
25
+
26
+ 1. **Extract** the target Acumatica deployment with `acu extract` — its configuration becomes YAML, config as code.
27
+ 2. **Develop** changes in the YAML, in git: edit, review, version.
28
+ 3. **Re-deploy** with `acu apply`, then prove the live tenant matches the code with `acu diff`.
29
+
30
+ Four commands do the work, and every one is safe to re-run:
31
+
32
+ - **`acu tenant create`** — creates a tenant and bootstraps it in one step, ready for `apply`.
33
+ Re-running it against an existing tenant republishes the bootstrap package instead of failing.
34
+ - **`acu apply`** — pushes your YAML into the tenant as keyed upserts.
35
+ Running it twice changes nothing.
36
+ - **`acu diff`** — compares your YAML against the live tenant and exits with code 2 on drift.
37
+ - **`acu extract`** — reads a configured tenant and writes the YAML file set back out, the inverse of `apply`.
38
+ Existing files are skipped unless `--force`.
39
+
40
+ > **Tested against** Acumatica ERP **26.101.0225** on Windows Server 2025,
41
+ > contract REST endpoint **25.200.001**. Other versions will likely work,
42
+ > but only this combination is verified.
43
+
44
+ ## Why
45
+
46
+ Acumatica configuration normally lives in the web UI: wizards, screens, and manual data entry that nobody can review, version, or reproduce.
47
+
48
+ `acu` moves that configuration into YAML files in a git repo, so a tenant can be rebuilt from scratch, audited in a pull request, and checked for drift like any other infrastructure.
49
+
50
+ ## Quick start
51
+
52
+ ```sh
53
+ uv tool install acumatica-cli
54
+
55
+ acu config init --host erp.example.com my-erp
56
+ cd my-erp # edit .env: set ACU_PASSWORD
57
+
58
+ acu config check # read-only preflight
59
+ acu tenant create --id 3 --login DEV # create the tenant + bootstrap it
60
+ acu --tenant DEV apply # seed bootstrap/, baseline/, setup/
61
+ acu --tenant DEV diff # prove zero drift (exit 2 on drift)
62
+ ```
63
+
64
+ ## CLI map
65
+
66
+ ```text
67
+ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
68
+ [--username U] [--password P] [--version] [--completion [SHELL]]
69
+
70
+ ├── tenant tenant CRUD (ac.exe over SSH — control plane)
71
+ │ ├── list CompanyID, sign-in name, internal CD, type
72
+ │ ├── create --id N --login NAME create + bootstrap; re-run to republish
73
+ │ │ [--type SalesDemo|T100|U100] [--parent N] [--hidden] [--no-init]
74
+ │ └── delete --id N [--yes] delete the tenant and its data, recycle app pool
75
+
76
+ ├── apply [--dry-run] [FILES...] push YAML via REST (idempotent PUT upserts)
77
+ ├── diff [FILES...] drift check vs the live tenant (exit 2 on drift)
78
+ ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
79
+ │ dump live tenant state as seed YAML (inverse of apply)
80
+ ├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
81
+
82
+ └── config configuration ops
83
+ ├── init [--host HOST] [DIR] scaffold a data repo (.env plus example YAML)
84
+ ├── show print the resolved config as a complete .env
85
+ └── check read-only preflight: discovery, secrets, REST, SSH
86
+ ```
87
+
88
+ `apply` and `diff` called without FILES default to the scaffolded directories, in order: `bootstrap/`, then `baseline/`, then `setup/`.
89
+ `acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
90
+ Run `acu <command> --help` for details on any command.
91
+
92
+ ## The data repo
93
+
94
+ Your configuration lives in its own git repo, which `acu config init` scaffolds:
95
+
96
+ | Path | What it holds |
97
+ | ------------ | -------------------------------------------------------------------------- |
98
+ | `bootstrap/` | what makes a virgin tenant configurable: features, company, credit terms |
99
+ | `baseline/` | reference data: subaccounts, chart of accounts, ledger, units of measure |
100
+ | `setup/` | one-time actions: financial year, master calendar, open periods |
101
+ | `.env` | where to apply and who signs in, every key an `ACU_*` variable |
102
+
103
+ Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-`, and so on) encode dependency order. The scaffolded `.gitignore` keeps `.env` out of git — store it encrypted (for example as `.env.gpg`) and decrypt once per clone.
104
+
105
+ ## Installation
106
+
107
+ Requires Python 3.12 or newer.
108
+
109
+ ```sh
110
+ uv tool install acumatica-cli
111
+ ```
112
+
113
+ `pipx install acumatica-cli` and `pip install acumatica-cli` work too. For the latest development version straight from the main branch:
114
+
115
+ ```sh
116
+ uv tool install git+https://github.com/kborovik/acumatica-cli.git
117
+ ```
118
+
119
+ Verify with `acu --version`.
120
+
121
+ ## Configuration
122
+
123
+ Everything lives in one `.env` file: *where* to apply and *who* signs in. Three values are required; everything else has a code default matching a stock Acumatica install:
124
+
125
+ ```sh
126
+ ACU_BASE_URL=http://acu-dev1.vm.internal/AcumaticaERP # required: REST root
127
+ ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
128
+ ACU_SSH=Administrator@acu-dev1.vm.internal # required: control-plane user@host
129
+ ACU_USER=admin # optional, defaults to admin
130
+ ACU_PASSWORD=... # required
131
+ ```
132
+
133
+ Worth knowing:
134
+
135
+ - The file is found by walking up from the current directory, so any subdirectory of the data repo works. Without a `.env`, global flags plus the process environment supply the full configuration.
136
+ - Nothing is derived: split-horizon DNS, port forwards, and jump hosts are all handled by writing the address you actually want into the two address keys.
137
+ - `acu config show` prints the fully resolved configuration as a complete, valid `.env` — every knob visible, the password excluded. Redirect it to turn resolved state into a working config: `acu config show > .env`.
138
+
139
+ Verify before touching anything live:
140
+
141
+ ```sh
142
+ acu config check # read-only preflight: discovery, secrets, REST, SSH
143
+ acu apply --dry-run # show what would be written, write nothing
144
+ ```
145
+
146
+ ## Control and Data Planes
147
+
148
+ `acu` talks to an instance over two independent channels:
149
+
150
+ - **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).
151
+ - **Data plane (REST):** `acu apply`, `diff`, and `schema` use the contract-based API (`/entity/Default/25.200.001/`), where `PUT` is a keyed upsert — see [`docs/rest-api.md`](docs/rest-api.md).
152
+
153
+ If you only apply and diff YAML, you never need SSH. SSH setup is required only for `acu tenant`.
154
+
155
+ ## SSH setup (control plane)
156
+
157
+ `acu tenant` runs commands on the Windows guest through plain `ssh`. Two things about this setup are not obvious, and both are hard requirements.
158
+
159
+ **1. The default SSH shell on the Windows guest must be PowerShell.**
160
+ `acu` sends PowerShell syntax over the wire, and every one of those commands fails under `cmd.exe`, the Windows OpenSSH default. Switch it once, in an elevated PowerShell on the guest:
161
+
162
+ ```powershell
163
+ New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell `
164
+ -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" `
165
+ -PropertyType String -Force
166
+ ```
167
+
168
+ **2. Authentication must be key-based and non-interactive.**
169
+ `acu` connects with `BatchMode=yes`, so it will never answer a password prompt.
170
+ 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. On the guest:
171
+
172
+ Install + start the server (once):
173
+
174
+ ```powershell
175
+ Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
176
+ Set-Service sshd -StartupType Automatic
177
+ Start-Service sshd
178
+ ```
179
+
180
+ Authorize your public key for administrators:
181
+
182
+ ```powershell
183
+ Add-Content -Path C:\ProgramData\ssh\administrators_authorized_keys -Value "ssh-ed25519 AAAA... you@laptop"
184
+ ```
185
+
186
+ The file must be readable by SYSTEM/Administrators only, or sshd ignores it:
187
+
188
+ ```powershell
189
+ icacls C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"
190
+ ```
191
+
192
+ Then verify from your workstation — this one test proves both requirements at once (key auth works, and the shell is PowerShell):
193
+
194
+ ```sh
195
+ ssh -o BatchMode=yes Administrator@acu-dev1.vm.internal '$PSVersionTable.PSVersion'
196
+ ```
197
+
198
+ ## Development
199
+
200
+ ```sh
201
+ git clone https://github.com/kborovik/acumatica-cli.git
202
+ cd acumatica-cli
203
+ make install # editable install as a global uv tool
204
+ make check # offline gate: ruff, basedpyright strict, pytest
205
+ ```
206
+
207
+ The default test suite is fully offline.
208
+ REST is faked with `httpx.MockTransport`, SSH with a monkeypatched `subprocess.run` — no live instance is needed.
209
+ `make check` must pass before every commit.
210
+
211
+ ### Live end-to-end tier
212
+
213
+ `make e2e` runs the opt-in live tier against a real Acumatica instance (pytest marker `e2e`, deselected by the default suite).
214
+
215
+ Configuration is one file: a decrypted `.env` at the repo root names the instance — `ACU_BASE_URL`, `ACU_SSH`, `ACU_TENANT`, `ACU_PASSWORD`.
216
+ `make e2e` refuses to start without it.
217
+
218
+ The tier is self-contained.
219
+ Each run scaffolds a synthetic single-org company from the packaged `acu config init` templates into a temporary directory, copies the real `.env` into it, and runs the installed `acu` binary from there — no data repo, no pre-existing fixtures on the instance.
220
+ Scratch tenants (`E2E`, `E2EA`, `E2EB`) are created on the way in and always deleted on the way out, so nothing persists.
221
+
222
+ ```sh
223
+ make e2e # whole tier, about 20 minutes
224
+ make e2e FILE=test_provision_lifecycle # one file, by stem or path
225
+ ```
@@ -0,0 +1,208 @@
1
+ # Acumatica GitOps (Config-as-Code)
2
+
3
+ **`acu`** configures Acumatica ERP from YAML files in a git repo (GitOps).
4
+
5
+ **No UI clicks, no Configuration Wizard.**
6
+
7
+ ## How it works
8
+
9
+ 1. **Extract** the target Acumatica deployment with `acu extract` — its configuration becomes YAML, config as code.
10
+ 2. **Develop** changes in the YAML, in git: edit, review, version.
11
+ 3. **Re-deploy** with `acu apply`, then prove the live tenant matches the code with `acu diff`.
12
+
13
+ Four commands do the work, and every one is safe to re-run:
14
+
15
+ - **`acu tenant create`** — creates a tenant and bootstraps it in one step, ready for `apply`.
16
+ Re-running it against an existing tenant republishes the bootstrap package instead of failing.
17
+ - **`acu apply`** — pushes your YAML into the tenant as keyed upserts.
18
+ Running it twice changes nothing.
19
+ - **`acu diff`** — compares your YAML against the live tenant and exits with code 2 on drift.
20
+ - **`acu extract`** — reads a configured tenant and writes the YAML file set back out, the inverse of `apply`.
21
+ Existing files are skipped unless `--force`.
22
+
23
+ > **Tested against** Acumatica ERP **26.101.0225** on Windows Server 2025,
24
+ > contract REST endpoint **25.200.001**. Other versions will likely work,
25
+ > but only this combination is verified.
26
+
27
+ ## Why
28
+
29
+ Acumatica configuration normally lives in the web UI: wizards, screens, and manual data entry that nobody can review, version, or reproduce.
30
+
31
+ `acu` moves that configuration into YAML files in a git repo, so a tenant can be rebuilt from scratch, audited in a pull request, and checked for drift like any other infrastructure.
32
+
33
+ ## Quick start
34
+
35
+ ```sh
36
+ uv tool install acumatica-cli
37
+
38
+ acu config init --host erp.example.com my-erp
39
+ cd my-erp # edit .env: set ACU_PASSWORD
40
+
41
+ acu config check # read-only preflight
42
+ acu tenant create --id 3 --login DEV # create the tenant + bootstrap it
43
+ acu --tenant DEV apply # seed bootstrap/, baseline/, setup/
44
+ acu --tenant DEV diff # prove zero drift (exit 2 on drift)
45
+ ```
46
+
47
+ ## CLI map
48
+
49
+ ```text
50
+ acu [--tenant NAME] [--url URL] [--ssh USER@HOST] [--api-version V]
51
+ [--username U] [--password P] [--version] [--completion [SHELL]]
52
+
53
+ ├── tenant tenant CRUD (ac.exe over SSH — control plane)
54
+ │ ├── list CompanyID, sign-in name, internal CD, type
55
+ │ ├── create --id N --login NAME create + bootstrap; re-run to republish
56
+ │ │ [--type SalesDemo|T100|U100] [--parent N] [--hidden] [--no-init]
57
+ │ └── delete --id N [--yes] delete the tenant and its data, recycle app pool
58
+
59
+ ├── apply [--dry-run] [FILES...] push YAML via REST (idempotent PUT upserts)
60
+ ├── diff [FILES...] drift check vs the live tenant (exit 2 on drift)
61
+ ├── extract [--out DIR] [--only NAME]... [--force] [--dry-run]
62
+ │ dump live tenant state as seed YAML (inverse of apply)
63
+ ├── schema [--out DIR] dump the endpoint's OpenAPI schema (swagger.json)
64
+
65
+ └── config configuration ops
66
+ ├── init [--host HOST] [DIR] scaffold a data repo (.env plus example YAML)
67
+ ├── show print the resolved config as a complete .env
68
+ └── check read-only preflight: discovery, secrets, REST, SSH
69
+ ```
70
+
71
+ `apply` and `diff` called without FILES default to the scaffolded directories, in order: `bootstrap/`, then `baseline/`, then `setup/`.
72
+ `acu --completion` emits a completion script for bash, zsh, or fish — source it from your shell profile.
73
+ Run `acu <command> --help` for details on any command.
74
+
75
+ ## The data repo
76
+
77
+ Your configuration lives in its own git repo, which `acu config init` scaffolds:
78
+
79
+ | Path | What it holds |
80
+ | ------------ | -------------------------------------------------------------------------- |
81
+ | `bootstrap/` | what makes a virgin tenant configurable: features, company, credit terms |
82
+ | `baseline/` | reference data: subaccounts, chart of accounts, ledger, units of measure |
83
+ | `setup/` | one-time actions: financial year, master calendar, open periods |
84
+ | `.env` | where to apply and who signs in, every key an `ACU_*` variable |
85
+
86
+ Files in each directory apply alphabetically; the numbered prefixes (`10-`, `20-`, and so on) encode dependency order. The scaffolded `.gitignore` keeps `.env` out of git — store it encrypted (for example as `.env.gpg`) and decrypt once per clone.
87
+
88
+ ## Installation
89
+
90
+ Requires Python 3.12 or newer.
91
+
92
+ ```sh
93
+ uv tool install acumatica-cli
94
+ ```
95
+
96
+ `pipx install acumatica-cli` and `pip install acumatica-cli` work too. For the latest development version straight from the main branch:
97
+
98
+ ```sh
99
+ uv tool install git+https://github.com/kborovik/acumatica-cli.git
100
+ ```
101
+
102
+ Verify with `acu --version`.
103
+
104
+ ## Configuration
105
+
106
+ Everything lives in one `.env` file: *where* to apply and *who* signs in. Three values are required; everything else has a code default matching a stock Acumatica install:
107
+
108
+ ```sh
109
+ ACU_BASE_URL=http://acu-dev1.vm.internal/AcumaticaERP # required: REST root
110
+ ACU_TENANT=LAB5 # sign-in name of the tenant API sessions use
111
+ ACU_SSH=Administrator@acu-dev1.vm.internal # required: control-plane user@host
112
+ ACU_USER=admin # optional, defaults to admin
113
+ ACU_PASSWORD=... # required
114
+ ```
115
+
116
+ Worth knowing:
117
+
118
+ - The file is found by walking up from the current directory, so any subdirectory of the data repo works. Without a `.env`, global flags plus the process environment supply the full configuration.
119
+ - Nothing is derived: split-horizon DNS, port forwards, and jump hosts are all handled by writing the address you actually want into the two address keys.
120
+ - `acu config show` prints the fully resolved configuration as a complete, valid `.env` — every knob visible, the password excluded. Redirect it to turn resolved state into a working config: `acu config show > .env`.
121
+
122
+ Verify before touching anything live:
123
+
124
+ ```sh
125
+ acu config check # read-only preflight: discovery, secrets, REST, SSH
126
+ acu apply --dry-run # show what would be written, write nothing
127
+ ```
128
+
129
+ ## Control and Data Planes
130
+
131
+ `acu` talks to an instance over two independent channels:
132
+
133
+ - **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).
134
+ - **Data plane (REST):** `acu apply`, `diff`, and `schema` use the contract-based API (`/entity/Default/25.200.001/`), where `PUT` is a keyed upsert — see [`docs/rest-api.md`](docs/rest-api.md).
135
+
136
+ If you only apply and diff YAML, you never need SSH. SSH setup is required only for `acu tenant`.
137
+
138
+ ## SSH setup (control plane)
139
+
140
+ `acu tenant` runs commands on the Windows guest through plain `ssh`. Two things about this setup are not obvious, and both are hard requirements.
141
+
142
+ **1. The default SSH shell on the Windows guest must be PowerShell.**
143
+ `acu` sends PowerShell syntax over the wire, and every one of those commands fails under `cmd.exe`, the Windows OpenSSH default. Switch it once, in an elevated PowerShell on the guest:
144
+
145
+ ```powershell
146
+ New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell `
147
+ -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" `
148
+ -PropertyType String -Force
149
+ ```
150
+
151
+ **2. Authentication must be key-based and non-interactive.**
152
+ `acu` connects with `BatchMode=yes`, so it will never answer a password prompt.
153
+ 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. On the guest:
154
+
155
+ Install + start the server (once):
156
+
157
+ ```powershell
158
+ Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
159
+ Set-Service sshd -StartupType Automatic
160
+ Start-Service sshd
161
+ ```
162
+
163
+ Authorize your public key for administrators:
164
+
165
+ ```powershell
166
+ Add-Content -Path C:\ProgramData\ssh\administrators_authorized_keys -Value "ssh-ed25519 AAAA... you@laptop"
167
+ ```
168
+
169
+ The file must be readable by SYSTEM/Administrators only, or sshd ignores it:
170
+
171
+ ```powershell
172
+ icacls C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"
173
+ ```
174
+
175
+ Then verify from your workstation — this one test proves both requirements at once (key auth works, and the shell is PowerShell):
176
+
177
+ ```sh
178
+ ssh -o BatchMode=yes Administrator@acu-dev1.vm.internal '$PSVersionTable.PSVersion'
179
+ ```
180
+
181
+ ## Development
182
+
183
+ ```sh
184
+ git clone https://github.com/kborovik/acumatica-cli.git
185
+ cd acumatica-cli
186
+ make install # editable install as a global uv tool
187
+ make check # offline gate: ruff, basedpyright strict, pytest
188
+ ```
189
+
190
+ The default test suite is fully offline.
191
+ REST is faked with `httpx.MockTransport`, SSH with a monkeypatched `subprocess.run` — no live instance is needed.
192
+ `make check` must pass before every commit.
193
+
194
+ ### Live end-to-end tier
195
+
196
+ `make e2e` runs the opt-in live tier against a real Acumatica instance (pytest marker `e2e`, deselected by the default suite).
197
+
198
+ Configuration is one file: a decrypted `.env` at the repo root names the instance — `ACU_BASE_URL`, `ACU_SSH`, `ACU_TENANT`, `ACU_PASSWORD`.
199
+ `make e2e` refuses to start without it.
200
+
201
+ The tier is self-contained.
202
+ Each run scaffolds a synthetic single-org company from the packaged `acu config init` templates into a temporary directory, copies the real `.env` into it, and runs the installed `acu` binary from there — no data repo, no pre-existing fixtures on the instance.
203
+ Scratch tenants (`E2E`, `E2EA`, `E2EB`) are created on the way in and always deleted on the way out, so nothing persists.
204
+
205
+ ```sh
206
+ make e2e # whole tier, about 20 minutes
207
+ make e2e FILE=test_provision_lifecycle # one file, by stem or path
208
+ ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "acumatica-cli"
3
- version = "0.3.0"
3
+ version = "0.5.0"
4
4
  description = "Acumatica ERP Config-as-Code: tenant provisioning, baseline config, and reference data"
5
5
  authors = [{ name = "Konstantin Borovik", email = "kb@lab5.ca" }]
6
6
  readme = "README.md"