leancpms-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.
- cpms/__init__.py +0 -0
- cpms/cli/__init__.py +0 -0
- cpms/cli/client.py +126 -0
- cpms/cli/login.py +232 -0
- cpms/cli/main.py +982 -0
- cpms/manifest/__init__.py +0 -0
- cpms/manifest/documents.py +118 -0
- cpms/manifest/schema.py +374 -0
- leancpms_cli-0.1.0.dist-info/METADATA +211 -0
- leancpms_cli-0.1.0.dist-info/RECORD +13 -0
- leancpms_cli-0.1.0.dist-info/WHEEL +4 -0
- leancpms_cli-0.1.0.dist-info/entry_points.txt +2 -0
- leancpms_cli-0.1.0.dist-info/licenses/LICENSE +662 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: leancpms-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The cpms command alone: a client of a LeanCPMS instance's JSON API
|
|
5
|
+
License-Expression: AGPL-3.0-or-later
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.14
|
|
8
|
+
Requires-Dist: httpx>=0.28.1
|
|
9
|
+
Requires-Dist: pydantic>=2.14.1
|
|
10
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# `cpms`, the LeanCPMS command line
|
|
14
|
+
|
|
15
|
+
`cpms` manages an organization in a LeanCPMS instance from your own
|
|
16
|
+
machine: live state, sessions, the monthly statement, and the declared
|
|
17
|
+
state (organization, price list, sites) as YAML documents you can keep in
|
|
18
|
+
a git repository. It talks to the instance's JSON API over HTTPS with an
|
|
19
|
+
API key, and the same commands work for people and for AI agents.
|
|
20
|
+
|
|
21
|
+
This directory builds `leancpms-cli`, the command without the server. It
|
|
22
|
+
depends on httpx, pydantic and PyYAML only.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
With [uv](https://docs.astral.sh/uv/), from PyPI once it is published there:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv tool install leancpms-cli
|
|
30
|
+
cpms --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Until then, from a GitHub release (a tag `cli-v<version>`) or straight
|
|
34
|
+
from the repository:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
gh release download cli-v0.1.0 --repo forsslund/leancpms --pattern '*.whl'
|
|
38
|
+
uv tool install ./leancpms_cli-0.1.0-py3-none-any.whl
|
|
39
|
+
# or
|
|
40
|
+
uv tool install "git+https://github.com/forsslund/leancpms#subdirectory=cli"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- The repository is private for now, so both need read access to it on
|
|
44
|
+
GitHub; over SSH the URL is
|
|
45
|
+
`git+ssh://git@github.com/forsslund/leancpms#subdirectory=cli`.
|
|
46
|
+
- It needs Python 3.14. uv downloads it if your machine lacks it.
|
|
47
|
+
- To get a newer version, run `uv tool upgrade leancpms-cli`. To pin a
|
|
48
|
+
version, add `@<tag or commit>` after the repository URL.
|
|
49
|
+
- `pipx install "git+…#subdirectory=cli"` works too.
|
|
50
|
+
- Do not install it into an environment that already has the server
|
|
51
|
+
(`leancpms`). The server already includes `cpms`, and both packages
|
|
52
|
+
carry the same files, so uninstalling one breaks the other.
|
|
53
|
+
|
|
54
|
+
## Configure
|
|
55
|
+
|
|
56
|
+
Log in with the same e-mail address you use for the instance's portal:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
cpms login --url https://cpms.example.com
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
It prints a code and opens the portal's page for it in your browser (or
|
|
63
|
+
prints the address only: `--no-browser`, or no display). Log in there if
|
|
64
|
+
you are not, check that the page shows the same code, choose your
|
|
65
|
+
organization and whether the key may act (`write`) or only look (`read`),
|
|
66
|
+
and approve. Your role as a member decides which you may choose: members
|
|
67
|
+
are `read` unless the operator, or a colleague with a `write` key, made
|
|
68
|
+
them `write` (`cpms members set-role`). `cpms login` waits for that and then saves a
|
|
69
|
+
**personal key**, yours and that organization's, as the profile
|
|
70
|
+
`[default]` (`--profile NAME` for another) in `~/.config/cpms/config.toml`,
|
|
71
|
+
readable only by you. Other profiles in the file stay as they were.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
cpms whoami # which key is in use, whose, for which organization
|
|
75
|
+
cpms logout # revokes the key on the server and removes the profile
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The key stops working when you are removed as a member, and so does
|
|
79
|
+
every key you issued with it. Approve a code only if you just ran
|
|
80
|
+
`cpms login` yourself: whoever started it gets the key.
|
|
81
|
+
|
|
82
|
+
### Machine keys, for CI and agents
|
|
83
|
+
|
|
84
|
+
A pipeline or an agent of its own gets a machine key instead, which
|
|
85
|
+
records who issued it: `cpms keys issue --name ci --scope read` with your
|
|
86
|
+
personal `write` key. It is shown only once. An organization key sees only
|
|
87
|
+
its own organization. A key you issue lives only as long as your
|
|
88
|
+
membership: when you leave, it is revoked with your own. An integration
|
|
89
|
+
that must outlive you gets its key from the operator. Members you add do
|
|
90
|
+
stay when you leave; `cpms members list` shows who added whom.
|
|
91
|
+
|
|
92
|
+
Put the URL and the key in `~/.config/cpms/config.toml` (or
|
|
93
|
+
`$XDG_CONFIG_HOME/cpms/`, or the path in `CPMS_CONFIG`), readable only by
|
|
94
|
+
you (`chmod 600`):
|
|
95
|
+
|
|
96
|
+
```toml
|
|
97
|
+
[default]
|
|
98
|
+
url = "https://cpms.example.com"
|
|
99
|
+
api_key = "lcp_…"
|
|
100
|
+
|
|
101
|
+
[test]
|
|
102
|
+
url = "http://localhost:8000"
|
|
103
|
+
api_key = "lcp_…"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Alternatively, set `CPMS_URL` and `CPMS_API_KEY` in the environment, which
|
|
107
|
+
suits CI and agents. `--profile test` (or `CPMS_PROFILE=test`) picks a
|
|
108
|
+
profile. Without a profile, the environment wins, then `[default]`.
|
|
109
|
+
A networked command that has neither exits 2 and says what to set.
|
|
110
|
+
|
|
111
|
+
## Use
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
cpms # live state: sites, fuse groups, outlets, current per phase
|
|
115
|
+
cpms sessions --since 2026-10-01
|
|
116
|
+
cpms report statement --month 2026-10 # receipts incl./excl. VAT, sessions without one
|
|
117
|
+
cpms manifest export kvartersel > all.yaml
|
|
118
|
+
cpms manifest export kvartersel --site odalvagen > sites/odalvagen.yaml
|
|
119
|
+
cpms manifest validate sites/odalvagen.yaml # offline: the schema
|
|
120
|
+
cpms manifest plan sites/odalvagen.yaml # the server: the diff, references, live warnings
|
|
121
|
+
cpms manifest apply sites/odalvagen.yaml -m "Fused up to 50 A"
|
|
122
|
+
cpms manifest history kvartersel --site odalvagen
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`cpms --help` and `cpms manifest --help` list the rest.
|
|
126
|
+
|
|
127
|
+
## A repository of manifests
|
|
128
|
+
|
|
129
|
+
One file per document works well:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
organization.yaml
|
|
133
|
+
pricelist.yaml
|
|
134
|
+
sites/odalvagen.yaml
|
|
135
|
+
sites/brotorp.yaml
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Edit a file, run `validate` and `plan`, commit, then run `apply` with the
|
|
139
|
+
commit's reason as `-m`. The server holds the truth and the files are a
|
|
140
|
+
way to say things: if someone changed a price in the portal, `plan` shows
|
|
141
|
+
that applying your file would undo it, and `export` brings the file up to
|
|
142
|
+
date. `apply` refuses a
|
|
143
|
+
document that changed on the server since your plan; plan again. Removing
|
|
144
|
+
anything needs `--prune`. `validate` needs no key, so CI can run it on
|
|
145
|
+
every pull request; `plan` needs one.
|
|
146
|
+
|
|
147
|
+
## In your editor: the JSON Schema
|
|
148
|
+
|
|
149
|
+
The documents have a JSON Schema, so an editor can complete field names
|
|
150
|
+
and flag mistakes as you type. That works in editors that use
|
|
151
|
+
yaml-language-server: VS Code with Red Hat's YAML extension, Neovim,
|
|
152
|
+
Helix, Zed. Add this line at the top of each file:
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
# yaml-language-server: $schema=https://raw.githubusercontent.com/forsslund/leancpms/main/schema/manifest.cpms-v1.schema.json
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
That URL works once the repository is public; replace `main` with a tag
|
|
159
|
+
to pin a version. Until then, use a local copy, written by the command
|
|
160
|
+
itself and kept in your manifest repository:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
cpms manifest schema > cpms-v1.schema.json
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```yaml
|
|
167
|
+
# yaml-language-server: $schema=../cpms-v1.schema.json
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The path is relative to the YAML file. `export` does not write the
|
|
171
|
+
line, so add it again after an export.
|
|
172
|
+
|
|
173
|
+
The schema checks types, required and unknown fields, allowed values,
|
|
174
|
+
key spelling, and that amounts are quoted (`"4.70"`, not `4.70`). It does
|
|
175
|
+
not check anything that spans several fields: a budget above the fuse,
|
|
176
|
+
duplicate keys or ocpp ids, a plan naming a missing tariff, or an unknown
|
|
177
|
+
time zone. `validate` and `plan` check those.
|
|
178
|
+
|
|
179
|
+
## For agents
|
|
180
|
+
|
|
181
|
+
- `--json` works on every command, before or after the subcommand.
|
|
182
|
+
- Exit codes: 0 ok, 1 the action failed (the server said no, the server
|
|
183
|
+
could not be reached, a plan has errors), 2 usage or configuration.
|
|
184
|
+
- `cpms manifest apply` never prompts without a TTY. It prints the plan
|
|
185
|
+
and exits 2 unless you pass `--yes`. Show the plan to a person first,
|
|
186
|
+
then apply with `--yes -m "why"`.
|
|
187
|
+
- `session … stop|close` acts at once, with no confirmation step.
|
|
188
|
+
- Pass the key in the environment or the config file, never on the
|
|
189
|
+
command line or in a transcript.
|
|
190
|
+
|
|
191
|
+
## Releasing (maintainers)
|
|
192
|
+
|
|
193
|
+
Raise `version` in both `cli/pyproject.toml` and the root `pyproject.toml`
|
|
194
|
+
(a test keeps them equal), then tag and push:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
git tag cli-v0.1.1 && git push origin cli-v0.1.1
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`.github/workflows/release-cli.yml` builds the wheel and sdist, smoke-tests
|
|
201
|
+
the wheel, and attaches both to a GitHub release. It also publishes to PyPI
|
|
202
|
+
when the repository variable `PYPI_PUBLISH` is `true`. That needs, once:
|
|
203
|
+
a PyPI account with two-factor authentication; a *pending trusted
|
|
204
|
+
publisher* on PyPI for the project `leancpms-cli` (owner `forsslund`,
|
|
205
|
+
repository `leancpms`, workflow `release-cli.yml`, environment `pypi`);
|
|
206
|
+
and an environment `pypi` in the GitHub repository's settings. A pending
|
|
207
|
+
publisher does not reserve the name: if someone else registers
|
|
208
|
+
`leancpms-cli` first, it becomes invalid. The first release creates the
|
|
209
|
+
project and so reserves the name. Trusted publishing uses GitHub's OIDC
|
|
210
|
+
token, so no PyPI token is stored anywhere. A file published to PyPI cannot be replaced,
|
|
211
|
+
only yanked: a wrong release is fixed with a new version.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
cpms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
cpms/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
cpms/cli/client.py,sha256=vrdPc0qNjSqDbp-6qxskOSqGN3Zxdix52YqDeyE3c90,4679
|
|
4
|
+
cpms/cli/login.py,sha256=m_6ZUSXL37vZ6PRBWWx9asX-uzlfgyK1dzmGu3IaTys,8984
|
|
5
|
+
cpms/cli/main.py,sha256=9tmGHrKON6l4tHBEyQwF92Zsdg9k-cVu4EAlIOlUOg0,43586
|
|
6
|
+
cpms/manifest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
cpms/manifest/documents.py,sha256=BMkbDR4rgluGksw8_2DB55MX6iiYlbtygVdyulCBlnI,4091
|
|
8
|
+
cpms/manifest/schema.py,sha256=vCAsrhI89sERp8BlsZfgbwXTLPOqYmoQuCn0C25dKXs,12595
|
|
9
|
+
leancpms_cli-0.1.0.dist-info/METADATA,sha256=JoS80kl6v9hXCfiasxl5SHs_s0CkGl8RW75-MbCynKE,8425
|
|
10
|
+
leancpms_cli-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
11
|
+
leancpms_cli-0.1.0.dist-info/entry_points.txt,sha256=V0OzDYZuXsQA-wcXV1URBK1hx22QDviluGi3_9hsxTU,44
|
|
12
|
+
leancpms_cli-0.1.0.dist-info/licenses/LICENSE,sha256=jVa0BUaKrRH4erV2P5AeJ24I2WRv9chIGxditreJ6e0,34524
|
|
13
|
+
leancpms_cli-0.1.0.dist-info/RECORD,,
|