akt-cli 0.1.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.
akt_cli-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AsyncAlchemist
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
akt_cli-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,274 @@
1
+ Metadata-Version: 2.4
2
+ Name: akt-cli
3
+ Version: 0.1.0
4
+ Summary: akt — a CLI toolbox to fully drive an Akaunting accounting instance
5
+ Keywords: akaunting,accounting,cli,invoices,bills,api
6
+ Author: AsyncAlchemist
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Environment :: Console
11
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
12
+ Requires-Dist: requests>=2.31
13
+ Requires-Python: >=3.12
14
+ Project-URL: Homepage, https://github.com/AsyncAlchemist/akt-cli
15
+ Project-URL: Repository, https://github.com/AsyncAlchemist/akt-cli
16
+ Project-URL: Issues, https://github.com/AsyncAlchemist/akt-cli/issues
17
+ Description-Content-Type: text/markdown
18
+
19
+ # akt — Akaunting CLI toolbox
20
+
21
+ [![PyPI](https://img.shields.io/pypi/v/akt-cli.svg)](https://pypi.org/project/akt-cli/)
22
+ [![CI](https://github.com/AsyncAlchemist/akt-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/AsyncAlchemist/akt-cli/actions/workflows/ci.yml)
23
+ [![codecov](https://codecov.io/gh/AsyncAlchemist/akt-cli/graph/badge.svg)](https://codecov.io/gh/AsyncAlchemist/akt-cli)
24
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
25
+
26
+ `akt` drives an [Akaunting](https://akaunting.com) instance entirely from the
27
+ command line: full create / read / update / delete for customers, vendors,
28
+ items, invoices, bills, payments, accounts, categories, taxes, currencies and
29
+ transfers — plus a `raw` escape hatch for any other API endpoint.
30
+
31
+ Built and tested against Akaunting **3.1.x**; it should work with any 3.x
32
+ deployment that exposes the REST API.
33
+
34
+ ## Install
35
+
36
+ From PyPI (the distribution is `akt-cli`; the command is `akt`):
37
+
38
+ ```bash
39
+ uv tool install akt-cli # installs the `akt` command globally
40
+ # or
41
+ pip install akt-cli
42
+ # or run without installing
43
+ uvx --from akt-cli akt --help
44
+ ```
45
+
46
+ From a checkout (the project is managed with [uv](https://docs.astral.sh/uv/)):
47
+
48
+ ```bash
49
+ uv sync # create .venv and install
50
+ uv run akt --help # run without activating
51
+ uv tool install . # install the `akt` command from source
52
+ ```
53
+
54
+ ## Configuration
55
+
56
+ `akt` needs a base URL, an admin email + password, and a company id. They are
57
+ resolved in this order (first wins):
58
+
59
+ 1. CLI flags: `--base-url`, `--email`, `--password`, `--company` (given **before**
60
+ the subcommand, e.g. `akt --company 2 customer list`).
61
+ 2. Environment: `AKT_BASE_URL`, `AKT_EMAIL`, `AKT_PASSWORD`, `AKT_COMPANY`,
62
+ `AKT_THROTTLE`.
63
+ 3. A dotenv file — `$AKT_ENV_FILE`, then `./.env`, then `~/.config/akt/akt.env`.
64
+ Akaunting's own install keys are recognised too: `APP_URL`,
65
+ `AKAUNTING_ADMIN_EMAIL`, `AKAUNTING_ADMIN_PASSWORD`.
66
+
67
+ Minimal `~/.config/akt/akt.env`:
68
+
69
+ ```ini
70
+ AKT_BASE_URL=https://accounting.example.com
71
+ AKT_EMAIL=admin@example.com
72
+ AKT_PASSWORD=your-password
73
+ AKT_COMPANY=1
74
+ ```
75
+
76
+ Then:
77
+
78
+ ```bash
79
+ uv run akt ping
80
+ uv run akt company
81
+ ```
82
+
83
+ Authentication is HTTP Basic against your Akaunting admin user.
84
+
85
+ ## Concepts mapped to Akaunting
86
+
87
+ Akaunting folds several nouns onto shared endpoints; `akt` hides that:
88
+
89
+ | akt noun | API endpoint | notes |
90
+ |------------|----------------|--------------------------------------------------|
91
+ | `customer` | `contacts` | contact of type `customer` |
92
+ | `vendor` | `contacts` | contact of type `vendor` (supplier) |
93
+ | `invoice` | `documents` | document of type `invoice` |
94
+ | `bill` | `documents` | document of type `bill` |
95
+ | `payment` | `transactions` | income (invoice) or expense (bill) transaction |
96
+ | `item`, `account`, `category`, `tax`, `currency`, `transfer` | as named | |
97
+
98
+ > The `contacts` and `documents` endpoints derive their permission from a
99
+ > `search=type:<x>` query param. `akt` injects this automatically — calling them
100
+ > raw without it returns `403 necessary access rights`.
101
+
102
+ ## Verbs
103
+
104
+ Every resource supports:
105
+
106
+ ```
107
+ akt <noun> list [--search 'field:value'] [--all] [--limit N] [--json]
108
+ akt <noun> get <id>
109
+ akt <noun> create --field value ...
110
+ akt <noun> update <id> --field value ...
111
+ akt <noun> delete <id>
112
+ akt <noun> enable <id> # where applicable
113
+ akt <noun> disable <id>
114
+ ```
115
+
116
+ Output is a table by default; add `--json` (works before or after the verb) for
117
+ raw JSON suitable for piping into `jq`.
118
+
119
+ Three ways to set body fields on create/update:
120
+
121
+ * typed flags shown by `akt <noun> create --help`
122
+ * `--set key=value` (repeatable; values are JSON-coerced, so `--set enabled=0`)
123
+ * `--data '<json>'` or `--data @file.json` (merged last, wins over everything)
124
+
125
+ ## Examples
126
+
127
+ ```bash
128
+ # Contacts
129
+ akt customer create --name "Northwind Traders" --email ar@northwind.com --currency-code USD
130
+ akt vendor create --name "Office Supply Co" --email billing@osc.com
131
+ akt customer list --search 'name:Northwind'
132
+ akt customer update 12 --phone "555-2000"
133
+ akt customer disable 12
134
+
135
+ # Items, categories, taxes
136
+ akt item create --name "Consulting Hour" --sale-price 150 --purchase-price 0
137
+ akt category create --name "Services" --type income
138
+ akt tax create --name "Sales Tax" --rate 8.25
139
+
140
+ # Invoice with line items (totals computed server-side; number auto-generated)
141
+ akt invoice create --contact 12 \
142
+ --item 'name=Consulting,price=150,quantity=10,item_id=2' \
143
+ --item 'name=Setup fee,price=500,quantity=1' \
144
+ --status sent
145
+
146
+ # Record a customer payment against that invoice (amount defaults to amount due)
147
+ akt payment create --invoice 34
148
+
149
+ # Partial payment of a specific amount via bank transfer
150
+ akt payment create --invoice 34 --amount 750 \
151
+ --payment-method offline-payments.bank_transfer.2
152
+
153
+ # Bills and vendor payments work the same way
154
+ akt bill create --contact 13 --item 'name=Paper,price=40,quantity=5'
155
+ akt payment create --bill 41
156
+
157
+ # Anything else: raw API access
158
+ akt raw GET reports
159
+ akt raw POST items --data '{"name":"Ad-hoc","type":"service","sale_price":99}'
160
+ akt company
161
+ akt settings --search 'key:default.account'
162
+ ```
163
+
164
+ ## Akaunting gotchas `akt` handles for you
165
+
166
+ Driving Akaunting's API directly has sharp edges; `akt` papers over these:
167
+
168
+ * **Type-scoped ACL** — `contacts` and `documents` need `search=type:<x>` on
169
+ *every* verb or the API returns `403 necessary access rights`.
170
+ * **Doubled totals** — Akaunting recomputes a document's total from its line
171
+ items and *adds* it to the `amount` you send. `akt` always sends `amount: 0`
172
+ so the server-computed total is authoritative.
173
+ * **Item `description`** — line items need a `description` key even when empty,
174
+ or creation 500s with `Undefined array key "description"`.
175
+ * **Updates wipe items** — a document update deletes and recreates all line
176
+ items from the request. `akt` resends the existing items on a partial update
177
+ so they aren't lost.
178
+ * **Nested payment route** — paying a document must POST to
179
+ `documents/{id}/transactions`; the flat `transactions` endpoint rejects it.
180
+ * **Full-replace updates** — Akaunting PUT re-validates required fields, so
181
+ `akt` merges your changes onto the current record.
182
+
183
+ ### Invoice creation may be gated by a plan check
184
+
185
+ In Akaunting 3.x, `CreateDocument::authorize()` gates **invoice** creation (only
186
+ `type == invoice`) behind a call to `api.akaunting.com/plans/limits` using the
187
+ `apps.api_key` setting. If that key is unset or the host can't reach
188
+ `api.akaunting.com`, invoice creation fails closed with
189
+ `500 Not able to create a new user` — in the **web UI too**, not just `akt`.
190
+ Bills, payments, contacts, items and transfers are unaffected. Fix it by setting
191
+ a valid `apps.api_key` (and allowing outbound HTTPS to `api.akaunting.com`).
192
+
193
+ ## Host bot-protection / throttling
194
+
195
+ Some hosts (e.g. cPanel with Imunify360) greylist an IP that issues a burst of
196
+ automated requests, returning an `Access denied by … bot-protection` page or
197
+ timing out. `akt` retries throttle/WAF responses with backoff, and
198
+ `--throttle SECONDS` (or `AKT_THROTTLE`) enforces a minimum gap between calls —
199
+ use `--throttle 1` for bulk work. A durable fix is to whitelist your IP in the
200
+ host firewall.
201
+
202
+ ## Testing
203
+
204
+ Tests are split in two:
205
+
206
+ * **`tests/unit/`** — offline tests for the body builders and arg parsing. No
207
+ network. This is what CI runs by default and what gates pull requests.
208
+ * **`tests/integration/`** — drive the real `akt` CLI against a live Akaunting
209
+ instance, exercising the full surface (contacts, items, bill → payment →
210
+ paid, transfers, …). Every record they create is deleted on teardown — even
211
+ on failure — so no invoices, bills or payments are left behind.
212
+
213
+ ```bash
214
+ uv run pytest tests/unit # fast, offline (default)
215
+ uv run pytest # integration tests auto-skip without creds
216
+
217
+ # Run integration tests against a deployment:
218
+ AKT_BASE_URL=https://accounting.example.com \
219
+ AKT_EMAIL=admin@example.com \
220
+ AKT_PASSWORD=… \
221
+ uv run pytest tests/integration -v
222
+ ```
223
+
224
+ > Invoice creation is `xfail`-ed when the host's plan-limit gate is active (see
225
+ > above); the rest of the suite must pass.
226
+
227
+ ### CI / CD
228
+
229
+ * **CI** (`.github/workflows/ci.yml`) runs on every push and PR: unit tests +
230
+ coverage, uploaded to [Codecov](https://codecov.io/gh/AsyncAlchemist/akt-cli).
231
+ * **Release** (`.github/workflows/release.yml`) runs the live integration suite
232
+ on published releases (and via *Run workflow*). Connection details come from
233
+ GitHub Actions **secrets** (`AKT_BASE_URL`, `AKT_EMAIL`, `AKT_PASSWORD`) — they
234
+ are never committed and are masked in logs.
235
+ * **Publish** (`.github/workflows/publish.yml`) builds the sdist + wheel and
236
+ uploads them via **Trusted Publishing (OIDC)** — no API token is stored.
237
+ *Run workflow* publishes to **TestPyPI**; a published release publishes to
238
+ **PyPI**.
239
+
240
+ ### Releasing to PyPI
241
+
242
+ One-time setup — add a *pending publisher* on each index
243
+ (Account → Publishing → *Add a pending publisher*) with:
244
+
245
+ | Field | TestPyPI | PyPI |
246
+ |-------|----------|------|
247
+ | Project Name | `akt-cli` | `akt-cli` |
248
+ | Owner | `AsyncAlchemist` | `AsyncAlchemist` |
249
+ | Repository name | `akt-cli` | `akt-cli` |
250
+ | Workflow name | `publish.yml` | `publish.yml` |
251
+ | Environment name | `testpypi` | `pypi` |
252
+
253
+ Then:
254
+
255
+ * **Verify** — *Actions → Publish (PyPI) → Run workflow* uploads the current
256
+ version to TestPyPI.
257
+ * **Release** — bump `version` in `pyproject.toml`, push, and publish a GitHub
258
+ Release. That runs the live integration suite and publishes to PyPI.
259
+
260
+ The code is small and declarative:
261
+
262
+ | file | purpose |
263
+ |-----------------|----------------------------------------------------------|
264
+ | `config.py` | credential resolution (flags / env / dotenv) |
265
+ | `client.py` | HTTP, auth, company scoping, pagination, retries |
266
+ | `resources.py` | field specs + body builders (documents, payments) |
267
+ | `registry.py` | the concrete list of resources and their columns |
268
+ | `commands.py` | generic list/get/create/update/delete/toggle handlers |
269
+ | `cli.py` | argparse wiring and entrypoint |
270
+ | `output.py` | JSON / table rendering |
271
+
272
+ ## License
273
+
274
+ [MIT](LICENSE) © AsyncAlchemist
@@ -0,0 +1,256 @@
1
+ # akt — Akaunting CLI toolbox
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/akt-cli.svg)](https://pypi.org/project/akt-cli/)
4
+ [![CI](https://github.com/AsyncAlchemist/akt-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/AsyncAlchemist/akt-cli/actions/workflows/ci.yml)
5
+ [![codecov](https://codecov.io/gh/AsyncAlchemist/akt-cli/graph/badge.svg)](https://codecov.io/gh/AsyncAlchemist/akt-cli)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+
8
+ `akt` drives an [Akaunting](https://akaunting.com) instance entirely from the
9
+ command line: full create / read / update / delete for customers, vendors,
10
+ items, invoices, bills, payments, accounts, categories, taxes, currencies and
11
+ transfers — plus a `raw` escape hatch for any other API endpoint.
12
+
13
+ Built and tested against Akaunting **3.1.x**; it should work with any 3.x
14
+ deployment that exposes the REST API.
15
+
16
+ ## Install
17
+
18
+ From PyPI (the distribution is `akt-cli`; the command is `akt`):
19
+
20
+ ```bash
21
+ uv tool install akt-cli # installs the `akt` command globally
22
+ # or
23
+ pip install akt-cli
24
+ # or run without installing
25
+ uvx --from akt-cli akt --help
26
+ ```
27
+
28
+ From a checkout (the project is managed with [uv](https://docs.astral.sh/uv/)):
29
+
30
+ ```bash
31
+ uv sync # create .venv and install
32
+ uv run akt --help # run without activating
33
+ uv tool install . # install the `akt` command from source
34
+ ```
35
+
36
+ ## Configuration
37
+
38
+ `akt` needs a base URL, an admin email + password, and a company id. They are
39
+ resolved in this order (first wins):
40
+
41
+ 1. CLI flags: `--base-url`, `--email`, `--password`, `--company` (given **before**
42
+ the subcommand, e.g. `akt --company 2 customer list`).
43
+ 2. Environment: `AKT_BASE_URL`, `AKT_EMAIL`, `AKT_PASSWORD`, `AKT_COMPANY`,
44
+ `AKT_THROTTLE`.
45
+ 3. A dotenv file — `$AKT_ENV_FILE`, then `./.env`, then `~/.config/akt/akt.env`.
46
+ Akaunting's own install keys are recognised too: `APP_URL`,
47
+ `AKAUNTING_ADMIN_EMAIL`, `AKAUNTING_ADMIN_PASSWORD`.
48
+
49
+ Minimal `~/.config/akt/akt.env`:
50
+
51
+ ```ini
52
+ AKT_BASE_URL=https://accounting.example.com
53
+ AKT_EMAIL=admin@example.com
54
+ AKT_PASSWORD=your-password
55
+ AKT_COMPANY=1
56
+ ```
57
+
58
+ Then:
59
+
60
+ ```bash
61
+ uv run akt ping
62
+ uv run akt company
63
+ ```
64
+
65
+ Authentication is HTTP Basic against your Akaunting admin user.
66
+
67
+ ## Concepts mapped to Akaunting
68
+
69
+ Akaunting folds several nouns onto shared endpoints; `akt` hides that:
70
+
71
+ | akt noun | API endpoint | notes |
72
+ |------------|----------------|--------------------------------------------------|
73
+ | `customer` | `contacts` | contact of type `customer` |
74
+ | `vendor` | `contacts` | contact of type `vendor` (supplier) |
75
+ | `invoice` | `documents` | document of type `invoice` |
76
+ | `bill` | `documents` | document of type `bill` |
77
+ | `payment` | `transactions` | income (invoice) or expense (bill) transaction |
78
+ | `item`, `account`, `category`, `tax`, `currency`, `transfer` | as named | |
79
+
80
+ > The `contacts` and `documents` endpoints derive their permission from a
81
+ > `search=type:<x>` query param. `akt` injects this automatically — calling them
82
+ > raw without it returns `403 necessary access rights`.
83
+
84
+ ## Verbs
85
+
86
+ Every resource supports:
87
+
88
+ ```
89
+ akt <noun> list [--search 'field:value'] [--all] [--limit N] [--json]
90
+ akt <noun> get <id>
91
+ akt <noun> create --field value ...
92
+ akt <noun> update <id> --field value ...
93
+ akt <noun> delete <id>
94
+ akt <noun> enable <id> # where applicable
95
+ akt <noun> disable <id>
96
+ ```
97
+
98
+ Output is a table by default; add `--json` (works before or after the verb) for
99
+ raw JSON suitable for piping into `jq`.
100
+
101
+ Three ways to set body fields on create/update:
102
+
103
+ * typed flags shown by `akt <noun> create --help`
104
+ * `--set key=value` (repeatable; values are JSON-coerced, so `--set enabled=0`)
105
+ * `--data '<json>'` or `--data @file.json` (merged last, wins over everything)
106
+
107
+ ## Examples
108
+
109
+ ```bash
110
+ # Contacts
111
+ akt customer create --name "Northwind Traders" --email ar@northwind.com --currency-code USD
112
+ akt vendor create --name "Office Supply Co" --email billing@osc.com
113
+ akt customer list --search 'name:Northwind'
114
+ akt customer update 12 --phone "555-2000"
115
+ akt customer disable 12
116
+
117
+ # Items, categories, taxes
118
+ akt item create --name "Consulting Hour" --sale-price 150 --purchase-price 0
119
+ akt category create --name "Services" --type income
120
+ akt tax create --name "Sales Tax" --rate 8.25
121
+
122
+ # Invoice with line items (totals computed server-side; number auto-generated)
123
+ akt invoice create --contact 12 \
124
+ --item 'name=Consulting,price=150,quantity=10,item_id=2' \
125
+ --item 'name=Setup fee,price=500,quantity=1' \
126
+ --status sent
127
+
128
+ # Record a customer payment against that invoice (amount defaults to amount due)
129
+ akt payment create --invoice 34
130
+
131
+ # Partial payment of a specific amount via bank transfer
132
+ akt payment create --invoice 34 --amount 750 \
133
+ --payment-method offline-payments.bank_transfer.2
134
+
135
+ # Bills and vendor payments work the same way
136
+ akt bill create --contact 13 --item 'name=Paper,price=40,quantity=5'
137
+ akt payment create --bill 41
138
+
139
+ # Anything else: raw API access
140
+ akt raw GET reports
141
+ akt raw POST items --data '{"name":"Ad-hoc","type":"service","sale_price":99}'
142
+ akt company
143
+ akt settings --search 'key:default.account'
144
+ ```
145
+
146
+ ## Akaunting gotchas `akt` handles for you
147
+
148
+ Driving Akaunting's API directly has sharp edges; `akt` papers over these:
149
+
150
+ * **Type-scoped ACL** — `contacts` and `documents` need `search=type:<x>` on
151
+ *every* verb or the API returns `403 necessary access rights`.
152
+ * **Doubled totals** — Akaunting recomputes a document's total from its line
153
+ items and *adds* it to the `amount` you send. `akt` always sends `amount: 0`
154
+ so the server-computed total is authoritative.
155
+ * **Item `description`** — line items need a `description` key even when empty,
156
+ or creation 500s with `Undefined array key "description"`.
157
+ * **Updates wipe items** — a document update deletes and recreates all line
158
+ items from the request. `akt` resends the existing items on a partial update
159
+ so they aren't lost.
160
+ * **Nested payment route** — paying a document must POST to
161
+ `documents/{id}/transactions`; the flat `transactions` endpoint rejects it.
162
+ * **Full-replace updates** — Akaunting PUT re-validates required fields, so
163
+ `akt` merges your changes onto the current record.
164
+
165
+ ### Invoice creation may be gated by a plan check
166
+
167
+ In Akaunting 3.x, `CreateDocument::authorize()` gates **invoice** creation (only
168
+ `type == invoice`) behind a call to `api.akaunting.com/plans/limits` using the
169
+ `apps.api_key` setting. If that key is unset or the host can't reach
170
+ `api.akaunting.com`, invoice creation fails closed with
171
+ `500 Not able to create a new user` — in the **web UI too**, not just `akt`.
172
+ Bills, payments, contacts, items and transfers are unaffected. Fix it by setting
173
+ a valid `apps.api_key` (and allowing outbound HTTPS to `api.akaunting.com`).
174
+
175
+ ## Host bot-protection / throttling
176
+
177
+ Some hosts (e.g. cPanel with Imunify360) greylist an IP that issues a burst of
178
+ automated requests, returning an `Access denied by … bot-protection` page or
179
+ timing out. `akt` retries throttle/WAF responses with backoff, and
180
+ `--throttle SECONDS` (or `AKT_THROTTLE`) enforces a minimum gap between calls —
181
+ use `--throttle 1` for bulk work. A durable fix is to whitelist your IP in the
182
+ host firewall.
183
+
184
+ ## Testing
185
+
186
+ Tests are split in two:
187
+
188
+ * **`tests/unit/`** — offline tests for the body builders and arg parsing. No
189
+ network. This is what CI runs by default and what gates pull requests.
190
+ * **`tests/integration/`** — drive the real `akt` CLI against a live Akaunting
191
+ instance, exercising the full surface (contacts, items, bill → payment →
192
+ paid, transfers, …). Every record they create is deleted on teardown — even
193
+ on failure — so no invoices, bills or payments are left behind.
194
+
195
+ ```bash
196
+ uv run pytest tests/unit # fast, offline (default)
197
+ uv run pytest # integration tests auto-skip without creds
198
+
199
+ # Run integration tests against a deployment:
200
+ AKT_BASE_URL=https://accounting.example.com \
201
+ AKT_EMAIL=admin@example.com \
202
+ AKT_PASSWORD=… \
203
+ uv run pytest tests/integration -v
204
+ ```
205
+
206
+ > Invoice creation is `xfail`-ed when the host's plan-limit gate is active (see
207
+ > above); the rest of the suite must pass.
208
+
209
+ ### CI / CD
210
+
211
+ * **CI** (`.github/workflows/ci.yml`) runs on every push and PR: unit tests +
212
+ coverage, uploaded to [Codecov](https://codecov.io/gh/AsyncAlchemist/akt-cli).
213
+ * **Release** (`.github/workflows/release.yml`) runs the live integration suite
214
+ on published releases (and via *Run workflow*). Connection details come from
215
+ GitHub Actions **secrets** (`AKT_BASE_URL`, `AKT_EMAIL`, `AKT_PASSWORD`) — they
216
+ are never committed and are masked in logs.
217
+ * **Publish** (`.github/workflows/publish.yml`) builds the sdist + wheel and
218
+ uploads them via **Trusted Publishing (OIDC)** — no API token is stored.
219
+ *Run workflow* publishes to **TestPyPI**; a published release publishes to
220
+ **PyPI**.
221
+
222
+ ### Releasing to PyPI
223
+
224
+ One-time setup — add a *pending publisher* on each index
225
+ (Account → Publishing → *Add a pending publisher*) with:
226
+
227
+ | Field | TestPyPI | PyPI |
228
+ |-------|----------|------|
229
+ | Project Name | `akt-cli` | `akt-cli` |
230
+ | Owner | `AsyncAlchemist` | `AsyncAlchemist` |
231
+ | Repository name | `akt-cli` | `akt-cli` |
232
+ | Workflow name | `publish.yml` | `publish.yml` |
233
+ | Environment name | `testpypi` | `pypi` |
234
+
235
+ Then:
236
+
237
+ * **Verify** — *Actions → Publish (PyPI) → Run workflow* uploads the current
238
+ version to TestPyPI.
239
+ * **Release** — bump `version` in `pyproject.toml`, push, and publish a GitHub
240
+ Release. That runs the live integration suite and publishes to PyPI.
241
+
242
+ The code is small and declarative:
243
+
244
+ | file | purpose |
245
+ |-----------------|----------------------------------------------------------|
246
+ | `config.py` | credential resolution (flags / env / dotenv) |
247
+ | `client.py` | HTTP, auth, company scoping, pagination, retries |
248
+ | `resources.py` | field specs + body builders (documents, payments) |
249
+ | `registry.py` | the concrete list of resources and their columns |
250
+ | `commands.py` | generic list/get/create/update/delete/toggle handlers |
251
+ | `cli.py` | argparse wiring and entrypoint |
252
+ | `output.py` | JSON / table rendering |
253
+
254
+ ## License
255
+
256
+ [MIT](LICENSE) © AsyncAlchemist
@@ -0,0 +1,54 @@
1
+ [project]
2
+ name = "akt-cli"
3
+ version = "0.1.0"
4
+ description = "akt — a CLI toolbox to fully drive an Akaunting accounting instance"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [
9
+ { name = "AsyncAlchemist" }
10
+ ]
11
+ requires-python = ">=3.12"
12
+ keywords = ["akaunting", "accounting", "cli", "invoices", "bills", "api"]
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Environment :: Console",
16
+ "Topic :: Office/Business :: Financial :: Accounting",
17
+ ]
18
+ dependencies = [
19
+ "requests>=2.31",
20
+ ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/AsyncAlchemist/akt-cli"
24
+ Repository = "https://github.com/AsyncAlchemist/akt-cli"
25
+ Issues = "https://github.com/AsyncAlchemist/akt-cli/issues"
26
+
27
+ [project.scripts]
28
+ akt = "akt.cli:main"
29
+
30
+ [build-system]
31
+ requires = ["uv_build>=0.11.14,<0.12.0"]
32
+ build-backend = "uv_build"
33
+
34
+ # Distribution is "akt-cli" but the import package + command stay "akt".
35
+ [tool.uv.build-backend]
36
+ module-name = "akt"
37
+ module-root = "src"
38
+
39
+ [tool.pyright]
40
+ extraPaths = ["src"]
41
+ reportMissingModuleSource = false
42
+
43
+ [tool.pytest.ini_options]
44
+ testpaths = ["tests"]
45
+ markers = [
46
+ "unit: offline tests with no network access (default CI)",
47
+ "integration: drive the CLI against a live Akaunting instance (needs AKT_* env)",
48
+ ]
49
+
50
+ [dependency-groups]
51
+ dev = [
52
+ "pytest>=9.1.1",
53
+ "pytest-cov>=5.0",
54
+ ]
@@ -0,0 +1,6 @@
1
+ """akt — a CLI toolbox for driving an Akaunting accounting instance."""
2
+
3
+ from .cli import main
4
+
5
+ __all__ = ["main"]
6
+ __version__ = "0.1.0"