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.
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ cpms = cpms.cli.main:main