akt-cli 0.1.0__py3-none-any.whl
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/__init__.py +6 -0
- akt/cli.py +211 -0
- akt/client.py +240 -0
- akt/commands.py +88 -0
- akt/config.py +121 -0
- akt/output.py +84 -0
- akt/registry.py +263 -0
- akt/resources.py +467 -0
- akt_cli-0.1.0.dist-info/METADATA +274 -0
- akt_cli-0.1.0.dist-info/RECORD +13 -0
- akt_cli-0.1.0.dist-info/WHEEL +4 -0
- akt_cli-0.1.0.dist-info/entry_points.txt +3 -0
- akt_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -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
|
+
[](https://pypi.org/project/akt-cli/)
|
|
22
|
+
[](https://github.com/AsyncAlchemist/akt-cli/actions/workflows/ci.yml)
|
|
23
|
+
[](https://codecov.io/gh/AsyncAlchemist/akt-cli)
|
|
24
|
+
[](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,13 @@
|
|
|
1
|
+
akt/__init__.py,sha256=7wtscfLuPhWHKiHAFVpOak5wZhpCOqN9XwLu1otdzj4,139
|
|
2
|
+
akt/cli.py,sha256=3wvk6nnxZ4MxRQD-9EPm4_5FIZ6s8KwAiu07lvSe2Ms,9405
|
|
3
|
+
akt/client.py,sha256=HX7WW7-gksX-UF6HyL0EYThzsFNkodTuFe-iD1UfEKY,8825
|
|
4
|
+
akt/commands.py,sha256=4JwJmrjJWprNYV3OvMnkYtaWQ778c6plU3tRJ2FSH2k,3316
|
|
5
|
+
akt/config.py,sha256=w_W9_3-wlmL7Opf5vBy4yJK9mFu_vWFqeTEk_lhR46o,3553
|
|
6
|
+
akt/output.py,sha256=6SIaS4lXL31haCnbCf9Y8piwBMKUVPyjpB3vZlG56EE,2561
|
|
7
|
+
akt/registry.py,sha256=73zx5zZbJcnCUcGgporemDDrUcncSD2qg3nWCbaORNs,8555
|
|
8
|
+
akt/resources.py,sha256=_j3e_IkPuNVRgzdpbTnFDSbOvvaRmeppsuDomWxeob0,18620
|
|
9
|
+
akt_cli-0.1.0.dist-info/licenses/LICENSE,sha256=R1kDA_V1t-bAnjasXbLIQDYSmtTXcCvXt0AjmeUEruo,1071
|
|
10
|
+
akt_cli-0.1.0.dist-info/WHEEL,sha256=uOqnPWqgFlbov4NeTCercq7cBQ2UN7xh5fiW55lOnAg,81
|
|
11
|
+
akt_cli-0.1.0.dist-info/entry_points.txt,sha256=EZWd1B5_FnT9N2HbD26co6LrDBHGIG0Ddh4y8e9Z_C8,38
|
|
12
|
+
akt_cli-0.1.0.dist-info/METADATA,sha256=jCSmbqSUwfqro7PLwZ1PpIFP9qiD76xVCcRkyIREy3k,11221
|
|
13
|
+
akt_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -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.
|