monolynx-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.
Files changed (45) hide show
  1. monolynx_cli-0.1.0/.gitignore +9 -0
  2. monolynx_cli-0.1.0/LICENSE +7 -0
  3. monolynx_cli-0.1.0/PKG-INFO +536 -0
  4. monolynx_cli-0.1.0/README.md +504 -0
  5. monolynx_cli-0.1.0/packaging/homebrew/README.md +156 -0
  6. monolynx_cli-0.1.0/packaging/homebrew/monolynx.rb +143 -0
  7. monolynx_cli-0.1.0/pyproject.toml +65 -0
  8. monolynx_cli-0.1.0/scripts/bump_formula.py +205 -0
  9. monolynx_cli-0.1.0/src/monolynx_cli/__init__.py +11 -0
  10. monolynx_cli-0.1.0/src/monolynx_cli/__main__.py +8 -0
  11. monolynx_cli-0.1.0/src/monolynx_cli/client.py +225 -0
  12. monolynx_cli-0.1.0/src/monolynx_cli/commands/__init__.py +1 -0
  13. monolynx_cli-0.1.0/src/monolynx_cli/commands/_common.py +157 -0
  14. monolynx_cli-0.1.0/src/monolynx_cli/commands/auth.py +169 -0
  15. monolynx_cli-0.1.0/src/monolynx_cli/commands/completion.py +44 -0
  16. monolynx_cli-0.1.0/src/monolynx_cli/commands/config.py +128 -0
  17. monolynx_cli-0.1.0/src/monolynx_cli/commands/member.py +76 -0
  18. monolynx_cli-0.1.0/src/monolynx_cli/commands/project.py +103 -0
  19. monolynx_cli-0.1.0/src/monolynx_cli/commands/sprint.py +143 -0
  20. monolynx_cli-0.1.0/src/monolynx_cli/commands/ticket.py +342 -0
  21. monolynx_cli-0.1.0/src/monolynx_cli/commands/wiki.py +275 -0
  22. monolynx_cli-0.1.0/src/monolynx_cli/config.py +157 -0
  23. monolynx_cli-0.1.0/src/monolynx_cli/main.py +149 -0
  24. monolynx_cli-0.1.0/src/monolynx_cli/oauth.py +411 -0
  25. monolynx_cli-0.1.0/src/monolynx_cli/options.py +44 -0
  26. monolynx_cli-0.1.0/src/monolynx_cli/output.py +186 -0
  27. monolynx_cli-0.1.0/src/monolynx_cli/version.py +9 -0
  28. monolynx_cli-0.1.0/tests/conftest.py +62 -0
  29. monolynx_cli-0.1.0/tests/test_auth_commands.py +1287 -0
  30. monolynx_cli-0.1.0/tests/test_client.py +995 -0
  31. monolynx_cli-0.1.0/tests/test_commands_common.py +365 -0
  32. monolynx_cli-0.1.0/tests/test_completion.py +105 -0
  33. monolynx_cli-0.1.0/tests/test_config.py +535 -0
  34. monolynx_cli-0.1.0/tests/test_config_commands.py +351 -0
  35. monolynx_cli-0.1.0/tests/test_error_handling.py +117 -0
  36. monolynx_cli-0.1.0/tests/test_main.py +961 -0
  37. monolynx_cli-0.1.0/tests/test_member_commands.py +468 -0
  38. monolynx_cli-0.1.0/tests/test_oauth.py +1270 -0
  39. monolynx_cli-0.1.0/tests/test_output.py +607 -0
  40. monolynx_cli-0.1.0/tests/test_project_commands.py +488 -0
  41. monolynx_cli-0.1.0/tests/test_smoke_e2e.py +54 -0
  42. monolynx_cli-0.1.0/tests/test_sprint_commands.py +444 -0
  43. monolynx_cli-0.1.0/tests/test_ticket_commands.py +855 -0
  44. monolynx_cli-0.1.0/tests/test_token_leak.py +123 -0
  45. monolynx_cli-0.1.0/tests/test_wiki_commands.py +1110 -0
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
7
+ .pytest_cache/
8
+ .coverage
9
+ htmlcov/
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2026 Piotr Krych
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,536 @@
1
+ Metadata-Version: 2.5
2
+ Name: monolynx-cli
3
+ Version: 0.1.0
4
+ Summary: Interfejs wiersza poleceń dla platformy Monolynx
5
+ Project-URL: Homepage, https://gitlab.com/piotrkrych/monolynx
6
+ Project-URL: Documentation, https://gitlab.com/piotrkrych/monolynx/-/blob/main/cli/README.md
7
+ Project-URL: Source, https://gitlab.com/piotrkrych/monolynx
8
+ Project-URL: Issues, https://gitlab.com/piotrkrych/monolynx/-/issues
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: cli,issue-tracking,monolynx,project-management,scrum
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: httpx>=0.27
21
+ Requires-Dist: platformdirs>=4.0
22
+ Requires-Dist: pydantic>=2.0
23
+ Requires-Dist: pyyaml>=6.0
24
+ Requires-Dist: rich>=13.0
25
+ Requires-Dist: tomli-w>=1.0
26
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
27
+ Requires-Dist: typer>=0.12
28
+ Provides-Extra: test
29
+ Requires-Dist: pytest-cov>=6.0; extra == 'test'
30
+ Requires-Dist: pytest>=8.0; extra == 'test'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # monolynx-cli
34
+
35
+ [![PyPI](https://img.shields.io/pypi/v/monolynx-cli)](https://pypi.org/project/monolynx-cli/)
36
+
37
+ Command-line client for the Monolynx platform: manage projects, tickets, sprints and wiki pages from your terminal.
38
+
39
+ ## Installation
40
+
41
+ Requirements: Python 3.10 or newer.
42
+
43
+ With [pipx](https://pipx.pypa.io/) (recommended, installs the CLI in its own isolated environment):
44
+
45
+ ```bash
46
+ pipx install monolynx-cli
47
+ ```
48
+
49
+ With [uv](https://docs.astral.sh/uv/):
50
+
51
+ ```bash
52
+ uv tool install monolynx-cli
53
+ ```
54
+
55
+ With pip:
56
+
57
+ ```bash
58
+ pip install monolynx-cli
59
+ ```
60
+
61
+ With Homebrew (macOS and Linux), from the `monolynx/tap` tap. Either install in one step, which adds the tap for you:
62
+
63
+ ```bash
64
+ brew install monolynx/tap/monolynx
65
+ ```
66
+
67
+ or add the tap first and then install by the short name:
68
+
69
+ ```bash
70
+ brew tap monolynx/tap
71
+ brew install monolynx
72
+ ```
73
+
74
+ The formula is not in homebrew-core, so the short `brew install monolynx` works only after `brew tap monolynx/tap`.
75
+
76
+ Check the installation:
77
+
78
+ ```bash
79
+ monolynx --version
80
+ ```
81
+
82
+ ## Quickstart
83
+
84
+ 1. Sign in. The default flow opens your browser and signs you in with OAuth:
85
+
86
+ ```bash
87
+ monolynx auth login
88
+ ```
89
+
90
+ On a CI runner or a headless machine, use an API token generated in the Monolynx dashboard (`/dashboard/profile/tokens`) instead:
91
+
92
+ ```bash
93
+ monolynx auth login --token osk_your_token
94
+ ```
95
+
96
+ 2. List the projects you have access to:
97
+
98
+ ```bash
99
+ monolynx project list
100
+ ```
101
+
102
+ ```text
103
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━┓
104
+ ┃ id ┃ name ┃ slug ┃ code ┃ role ┃
105
+ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━┩
106
+ │ 6f1c2a9e-3b4d-4c8e-9a1f-2d7b5e8c0a31 │ My Project │ my-project │ MP │ admin │
107
+ └──────────────────────────────────────┴────────────┴────────────┴──────┴────────┘
108
+ ```
109
+
110
+ Columns trimmed for brevity: the CLI prints every field the API returns, so the real table also has `description` and `created_at`.
111
+
112
+ 3. List the tickets of a project. Ticket commands need a project, passed as a global option before the command group:
113
+
114
+ ```bash
115
+ monolynx --project my-project ticket list
116
+ ```
117
+
118
+ ```text
119
+ ┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━┓
120
+ ┃ key ┃ title ┃ status ┃ priority ┃ story_points ┃
121
+ ┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━┩
122
+ │ MP-12 │ Export the monthly report │ in_progress │ high │ 3 │
123
+ │ MP-11 │ Fix login redirect │ todo │ medium │ 2 │
124
+ └────────┴──────────────────────────────┴─────────────┴──────────┴──────────────┘
125
+ Strona 1/1, razem: 2
126
+ ```
127
+
128
+ Columns trimmed for brevity: the real table has 14 columns (`id`, `key`, `title`, `description`, `status`, `priority`, `story_points`, `sprint_id`, `assignee_email`, `due_date`, `label_ids`, `created_via_ai`, `created_at`, `updated_at`). The last line is the page footer, printed on stderr.
129
+
130
+ To avoid typing `--project` every time, store the project in your profile (replace `default` with your profile name if you use another one):
131
+
132
+ ```bash
133
+ monolynx config set profile.default.project my-project
134
+ monolynx ticket list
135
+ ```
136
+
137
+ ### How signing in works
138
+
139
+ `monolynx auth login` without options uses OAuth 2.1 (Authorization Code with PKCE):
140
+
141
+ 1. The CLI reads the server metadata from `<endpoint>/.well-known/oauth-authorization-server`.
142
+ 2. It starts a short-lived local server on `127.0.0.1` (random port, path `/callback`) and registers itself with Dynamic Client Registration. Registration runs on every login, because the server matches the redirect address including the port.
143
+ 3. It opens your browser on the Monolynx consent page. You have 300 seconds to approve.
144
+ 4. It exchanges the code for tokens and checks them with `GET /api/v2/me`. Only then does it store them in the profile (`auth_type = "oauth"`, `access_token`, `refresh_token`, `expires_at`, `client_id`).
145
+
146
+ A timeout, a denied consent or a mismatched `state` parameter ends the login with exit code 3 and leaves the configuration unchanged.
147
+
148
+ Login options:
149
+
150
+ ```bash
151
+ monolynx auth login --no-browser
152
+ monolynx auth login --token osk_your_token
153
+ monolynx auth login --endpoint https://monolynx.example.com --profile work
154
+ ```
155
+
156
+ - `--no-browser` prints the authorization URL on stderr instead of opening a browser (the CLI does the same when opening the browser fails). The browser must still run on the same machine as the CLI, because the redirect goes to `127.0.0.1`. Over SSH without port forwarding, use `--token`.
157
+ - `--token` stores an API token without a browser (`auth_type = "token"`).
158
+ - `--endpoint` and `--profile` work with both flows.
159
+
160
+ The OAuth access token is valid for 30 days. Every command that calls the API keeps the session alive. `project`, `member`, `ticket`, `sprint` and `wiki` commands do it in two ways, `auth whoami` only reactively:
161
+
162
+ - Proactively (not in `auth whoami`): when the profile's `expires_at` is less than 24 hours away (or already past), the CLI refreshes the tokens once before the first request of the command and sends the request with the new access token. The server rotates the refresh token and both new tokens are saved in the profile, so regular use extends the session. If this refresh fails, the command goes on with the current token.
163
+ - Reactively: when the server answers 401, the CLI refreshes the tokens once and repeats the request. A failed refresh ends with a message asking you to run `monolynx auth login` and exit code 3.
164
+
165
+ Neither applies to `--token` profiles or to `MONOLYNX_TOKEN`.
166
+
167
+ `monolynx auth whoami` shows the signed-in user; for an OAuth profile it also shows `expires_at`. Tokens are always masked, never printed in full. `monolynx auth logout` removes the stored tokens from the profile.
168
+
169
+ ## Command reference
170
+
171
+ Help texts, prompts and messages of the CLI are in Polish. Run `monolynx --help`, `monolynx <group> --help` or `monolynx <group> <command> --help` for the built-in help.
172
+
173
+ Arguments in angle brackets are positional; `[...]` marks an optional one. Options are given after the command name, global options before the command group.
174
+
175
+ ### Global options
176
+
177
+ | Option | Default | Description |
178
+ |---|---|---|
179
+ | `-o`, `--output` | `table` in a terminal, `json` otherwise | Output format: `table`, `json`, `yaml` or `csv`. |
180
+ | `--no-color` | off | Disable table colors. The `NO_COLOR` environment variable (any non-empty value) does the same. |
181
+ | `-q`, `--quiet` | off | Silence progress messages on stderr. Data on stdout and error messages are unaffected. |
182
+ | `--profile` | active profile | Configuration profile to use. |
183
+ | `--project` | none | Project slug. |
184
+ | `--debug` | off | Log HTTP requests and responses on stderr (token always masked, body cut to 500 characters). |
185
+ | `--timeout` | `30.0` | HTTP request timeout in seconds, must be greater than zero. |
186
+ | `--version` | | Print the version and exit. |
187
+
188
+ ```bash
189
+ monolynx -o json auth whoami
190
+ monolynx --quiet -o csv config list
191
+ monolynx --profile work --project my-project ticket list
192
+ monolynx --timeout 10 --debug auth whoami
193
+ ```
194
+
195
+ ### `auth`
196
+
197
+ | Command | Arguments and options | Description |
198
+ |---|---|---|
199
+ | `auth login` | `--endpoint`, `--token`, `--profile`, `--no-browser` | Sign in with OAuth in the browser, or with an API token, and save the session in the profile. |
200
+ | `auth logout` | `--profile` | Remove the stored secrets (`token`, `access_token`, `refresh_token`, `expires_at`) of the profile. |
201
+ | `auth whoami` | `--profile` | Show the signed-in user. |
202
+
203
+ The local `--profile` of an `auth` command takes precedence over the global `--profile`.
204
+
205
+ ### `config`
206
+
207
+ | Command | Arguments and options | Description |
208
+ |---|---|---|
209
+ | `config get` | `<key>` | Print the value under a dotted key (secrets masked). |
210
+ | `config set` | `<key> <value>` | Set the value under a dotted key. |
211
+ | `config list` | | Print the whole configuration as `key`/`value` rows (secrets masked). |
212
+
213
+ Valid keys are `general.active_profile` and `profile.<name>.<field>`, see [Configuration and profiles](#configuration-and-profiles). These commands edit the file directly and ignore `--profile` and `--project`.
214
+
215
+ ### `project`
216
+
217
+ | Command | Arguments and options | Description |
218
+ |---|---|---|
219
+ | `project list` | | List the projects you have access to (all pages). |
220
+ | `project get` | `<slug>` | Show project details: your role, member and ticket counts, active sprint. |
221
+ | `project create` | `--name` (required), `--slug`, `--description`, `--code` | Create a project. Without `--slug` and `--code` the server derives them from the name. |
222
+ | `project update` | `<slug>`, `--name`, `--description`, `--new-slug` | Change a project; only the given options are sent. |
223
+ | `project delete` | `<slug>`, `-y`/`--yes` | Delete a project (asks for confirmation unless `--yes`). |
224
+ | `project summary` | `<slug>` | Show the project summary: unresolved errors, monitors, uptime, active sprint, backlog. |
225
+
226
+ ### `member`
227
+
228
+ | Command | Arguments and options | Description |
229
+ |---|---|---|
230
+ | `member list` | `--project` | List project members (all pages). |
231
+ | `member invite` | `<email>`, `--project`, `--role` (`member` or `admin`, default `member`) | Invite a person to the project. |
232
+ | `member remove` | `<email>`, `--project`, `-y`/`--yes` | Remove a member (asks for confirmation unless `--yes`). |
233
+
234
+ The local `--project` of a `member` command takes precedence over the global `--project`, `MONOLYNX_PROJECT` and the profile.
235
+
236
+ ### `ticket`
237
+
238
+ All `ticket` commands work on the project resolved from `--project`, `MONOLYNX_PROJECT` or the profile. `<ticket>` is a ticket UUID or key, for example `MON-42`.
239
+
240
+ | Command | Arguments and options | Description |
241
+ |---|---|---|
242
+ | `ticket list` | `--status`, `--priority`, `--search`, `--sprint`, `--due-before`, `--due-after`, `--overdue`, `--label`, `--page` | List project tickets with filters (one page). `--search` matches the title only; use `ticket search` to match the description too. |
243
+ | `ticket search` | `[query]`, `--status`, `--priority`, `--assignee`, `--sprint`, `--due-before`, `--due-after`, `--page` | Search tickets by phrase (title or description) and filters (one page). |
244
+ | `ticket get` | `<ticket>` | Show ticket details. |
245
+ | `ticket create` | `--title` (required), `--description`, `--description-file`, `--priority`, `--sp`/`--story-points`, `--sprint`, `--assignee`, `--due`, `--label`, `--ac`, `--spec-page`, `--blocked-by` | Create a ticket. `--label`, `--ac` and `--blocked-by` can be repeated; `--blocked-by` takes the UUID of the blocking ticket (not the key). |
246
+ | `ticket update` | `<ticket>`, `--title`, `--description`, `--description-file`, `--status`, `--priority`, `--sp`/`--story-points`, `--sprint`, `--assignee`, `--due`, `--label`, `--blocked-by`, `--no-blockers` | Update a ticket; only the given fields are sent. `--label` and `--blocked-by` replace the current values; `--blocked-by` takes the UUID of the blocking ticket (not the key). |
247
+ | `ticket delete` | `<ticket>`, `-y`/`--yes` | Delete a ticket (asks for confirmation unless `--yes`). |
248
+ | `ticket bulk-update` | `<tickets>...`, `--status`, `--priority`, `--assignee`, `--sprint`, `--due` | Update many tickets in one request. |
249
+ | `ticket comment list` | `<ticket>` | List ticket comments (all pages, flat list). |
250
+ | `ticket comment add` | `<ticket>`, `--body` (required) | Add a comment (markdown). |
251
+ | `ticket label list` | | List project labels (all pages, flat list). |
252
+ | `ticket label create` | `--name` (required), `--color` | Create a project label, for example `--color "#ff0000"`. |
253
+ | `ticket ac list` | `<ticket>` | List acceptance criteria (all pages, flat list). |
254
+ | `ticket ac add` | `<ticket> <description>` | Add an acceptance criterion. |
255
+ | `ticket ac update` | `<ticket> <criterion_id>`, `--description`, `--done`/`--not-done` | Change the description or mark a criterion as done or not done. |
256
+ | `ticket ac delete` | `<ticket> <criterion_id>` | Delete an acceptance criterion. |
257
+
258
+ Notes:
259
+
260
+ - Dates use the `YYYY-MM-DD` format. Ticket statuses are `backlog`, `todo`, `in_progress`, `in_review`, `done`. The server rejects an unknown status or priority in `ticket create`, `ticket update` and `ticket bulk-update` (exit code 1), but silently ignores it in the `--status` and `--priority` filters of `ticket list` and `ticket search`: the filter is not applied and the command succeeds, so check the spelling.
261
+ - Identifiers in the URL path (ticket, sprint, page, criterion, project slug) are URL-encoded. A bare `.` or `..` (or an empty value) is refused with exit code 2 before any request is sent.
262
+ - `--description -` and `--body -` read the text from stdin.
263
+ - In `ticket update` and `ticket bulk-update`, an empty string for `--sprint`, `--assignee` or `--due` clears the value.
264
+
265
+ ```bash
266
+ monolynx --project my-project ticket create --title "Fix login redirect" --priority high --sp 2 --ac "Redirect keeps the next parameter"
267
+ monolynx --project my-project ticket update MP-11 --status in_progress
268
+ git log -1 --format=%B | monolynx --project my-project ticket comment add MP-11 --body -
269
+ ```
270
+
271
+ ### `sprint`
272
+
273
+ | Command | Arguments and options | Description |
274
+ |---|---|---|
275
+ | `sprint list` | `--status` | List all project sprints (all pages, flat list), optionally filtered by status (for example `planning`, `active`, `completed`). |
276
+ | `sprint get` | `<sprint_id>` | Show sprint details. |
277
+ | `sprint create` | `--name` (required), `--start` (required), `--end`, `--goal` | Create a sprint. Dates use `YYYY-MM-DD`. |
278
+ | `sprint update` | `<sprint_id>`, `--name`, `--goal`, `--start`, `--end` | Change the name, goal or dates of a sprint. |
279
+ | `sprint start` | `<sprint_id>` | Start a sprint (only one sprint can be active at a time). |
280
+ | `sprint complete` | `<sprint_id>` | Complete a sprint; unfinished tickets go back to the backlog. |
281
+ | `sprint board` | | Show the Kanban board of the active sprint. |
282
+ | `sprint burndown` | `[sprint_id]` | Show the burndown of a sprint (the active one by default). |
283
+
284
+ ### `wiki`
285
+
286
+ | Command | Arguments and options | Description |
287
+ |---|---|---|
288
+ | `wiki list` | `--tree` | List all wiki pages of the project; `--tree` indents titles by depth. |
289
+ | `wiki get` | `<page_id>`, `--raw` | Show a page with its content; `--raw` prints only the markdown. |
290
+ | `wiki create` | `--title` (required), `--content`, `--file`, `--parent`, `--position`, `--public`/`--no-public` | Create a page. Content comes from `--content`, `--file` or stdin (`--content -`). |
291
+ | `wiki update` | `<page_id>`, `--title`, `--content`, `--file`, `--position`, `--public`/`--no-public` | Update a page; only the given fields are sent. |
292
+ | `wiki edit` | `<page_id>` | Edit the page content in `$EDITOR` (`vi` by default); saves only when the content changed. |
293
+ | `wiki delete` | `<page_id>`, `--yes` | Delete a page together with all its subpages. This cannot be undone. |
294
+ | `wiki search` | `<query>`, `--limit` (default `10`) | Semantic search over wiki pages. |
295
+ | `wiki config get` | | Show the LLM Wiki method configuration of the project. |
296
+ | `wiki config set` | `--enabled`/`--disabled` | Turn the LLM Wiki method on or off (requires the `settings:write` permission). |
297
+
298
+ `--public` publishes the page at `/blog/{slug}`, visible to anyone without signing in. `wiki delete` has no `-y` short form. `--content -` with an empty standard input, an unreadable or non-UTF-8 `--file`, and a missing project are usage errors (exit code 2, no request sent).
299
+
300
+ ### Reserved groups
301
+
302
+ `monitoring`, `issues`, `time`, `plan`, `rozliczenia`, `graph`, `pipeline`, `heartbeat` and `role` appear in `monolynx --help` but are reserved: they have no commands yet (not yet available).
303
+
304
+ ### Confirmations
305
+
306
+ `project delete`, `member remove`, `ticket delete` and `wiki delete` ask for confirmation in a terminal. Outside a terminal they refuse to run without `--yes` (exit code 2). Declining the prompt ends with exit code 1.
307
+
308
+ ## Configuration and profiles
309
+
310
+ The CLI keeps its settings in `config.toml` inside the per-user config directory reported by [platformdirs](https://pypi.org/project/platformdirs/) for the application name `monolynx`:
311
+
312
+ | System | Path |
313
+ |---|---|
314
+ | Linux | `~/.config/monolynx/config.toml` |
315
+ | macOS | `~/Library/Application Support/monolynx/config.toml` |
316
+ | Windows | the per-user config directory reported by platformdirs |
317
+
318
+ The file is written atomically with permissions `0600` (the directory gets `0700`; not applied on Windows). `auth login` creates it for you. Example with two profiles:
319
+
320
+ ```toml
321
+ [general]
322
+ active_profile = "prod"
323
+
324
+ [profile.prod]
325
+ endpoint = "https://monolynx.com"
326
+ project = "my-project"
327
+ auth_type = "oauth"
328
+ access_token = "..."
329
+ refresh_token = "..."
330
+ expires_at = "2026-10-29T12:00:00+00:00"
331
+ client_id = "..."
332
+
333
+ [profile.staging]
334
+ endpoint = "https://staging.monolynx.example.com"
335
+ project = "my-project"
336
+ auth_type = "token"
337
+ token = "osk_..."
338
+ ```
339
+
340
+ Profile fields: `endpoint`, `project`, `auth_type` (`token` or `oauth`), `token`, `access_token`, `refresh_token`, `expires_at`, `client_id`. `config get` and `config list` always mask `token`, `access_token` and `refresh_token`.
341
+
342
+ Switch profiles:
343
+
344
+ ```bash
345
+ monolynx --profile staging ticket list
346
+ MONOLYNX_PROFILE=staging monolynx ticket list
347
+ monolynx config set general.active_profile staging
348
+ ```
349
+
350
+ The profile is chosen in this order: `--profile`, then `MONOLYNX_PROFILE`, then `general.active_profile`, then `default`.
351
+
352
+ Only `auth login` has an `--endpoint` option. For other commands, set the endpoint in the profile or through `MONOLYNX_ENDPOINT`:
353
+
354
+ ```bash
355
+ monolynx config set profile.staging.endpoint https://staging.monolynx.example.com
356
+ ```
357
+
358
+ ## Environment variables
359
+
360
+ | Name | Meaning | Default |
361
+ |---|---|---|
362
+ | `MONOLYNX_ENDPOINT` | API address of the Monolynx server. | `https://monolynx.com` |
363
+ | `MONOLYNX_TOKEN` | API token used instead of the tokens stored in the profile. | none |
364
+ | `MONOLYNX_PROJECT` | Project slug. | none |
365
+ | `MONOLYNX_PROFILE` | Configuration profile. | `general.active_profile`, otherwise `default` |
366
+ | `NO_COLOR` | Any non-empty value disables table colors, like `--no-color`. | unset |
367
+
368
+ Resolution order for every setting: command-line option, then environment variable, then config file, then default. The options are `--profile` and `--project` (global), and `--endpoint` and `--token` (only in `auth login`).
369
+
370
+ `MONOLYNX_TOKEN` takes precedence over the tokens stored in the profile and is never refreshed. Use it in CI together with `MONOLYNX_ENDPOINT` and `MONOLYNX_PROJECT`, without a config file.
371
+
372
+ ## Exit codes
373
+
374
+ | Code | Meaning | Example causes |
375
+ |---|---|---|
376
+ | `0` | Success | |
377
+ | `1` | API or local error | 4xx other than 401/403 (for example 404 or 422), 429 or 5xx after all retries; corrupted config file; declined confirmation; `config get` for a key without a value |
378
+ | `2` | Usage error | Unknown option or bad argument; no project resolved; destructive command without `--yes` outside a terminal; `--timeout` not greater than zero; invalid `config` key; `update` without any field to change; empty stdin for `--content -`; a bare `.` or `..` as an identifier in a URL path |
379
+ | `3` | Authentication error | 401 or 403; failed token refresh; no token stored; OAuth login timeout, denied consent or mismatched `state` |
380
+ | `4` | Network error | Timeout, connection refused, DNS failure |
381
+
382
+ Errors are printed on stderr as `Błąd: <status> <title>: <detail>`. `--quiet` does not silence them.
383
+
384
+ Retries and timeouts:
385
+
386
+ - `GET` and `DELETE` are retried on 429 and 5xx; `POST` and `PATCH` only on 429, because retrying a 5xx could create a duplicate.
387
+ - At most 4 attempts, with backoff of 0.5 s, 1 s and 2 s. On 429 a `Retry-After` value in seconds takes precedence, capped at 60 s.
388
+ - Network errors are not retried.
389
+ - `--timeout` sets the timeout of a single request (30 s by default).
390
+ - `--debug` prints each request (method, URL, headers) and response (status, body cut to 500 characters) on stderr, with the token masked.
391
+
392
+ ## Piping and scripting
393
+
394
+ Without `-o`, the output format is `table` when stdout is a terminal and `json` otherwise, so piping into `jq` works without `-o json`. Global options such as `-o` go before the command group.
395
+
396
+ `ticket list` and `ticket search` return one page as a pagination envelope (use `--page` for the next pages):
397
+
398
+ ```json
399
+ {
400
+ "items": [{"key": "MP-12", "title": "Export the monthly report"}],
401
+ "page": 1,
402
+ "per_page": 20,
403
+ "total": 1,
404
+ "total_pages": 1
405
+ }
406
+ ```
407
+
408
+ Read the rows through `.items[]` (the example above is shortened; each item has all ticket fields):
409
+
410
+ ```bash
411
+ monolynx -o json --project my-project ticket list | jq -r '.items[] | .key'
412
+ monolynx -o json --project my-project ticket list --page 2 | jq '.total_pages'
413
+ ```
414
+
415
+ `project list`, `member list`, `wiki list`, `sprint list`, `ticket comment list`, `ticket label list` and `ticket ac list` fetch all pages themselves and return a flat list, so there is no `.items`:
416
+
417
+ ```bash
418
+ monolynx -o json project list | jq -r '.[].slug'
419
+ monolynx -o json --project my-project sprint list --status active | jq -r '.[].name'
420
+ monolynx -o json --project my-project ticket comment list MP-11 | jq -r '.[].content'
421
+ ```
422
+
423
+ Status messages such as `Zalogowano jako ...`, `Wylogowano z profilu ...` and `Ustawiono ...` (`auth login`, `auth logout`, `config set`) go to stderr and `--quiet` silences them, so stdout stays empty for these commands.
424
+
425
+ In `table` and `csv` formats the page footer (`Strona X/Y, razem: Z`) goes to stderr, so stdout holds data only.
426
+
427
+ A script that reacts to exit codes. It captures the CLI output first and pipes it to `jq` only afterwards, because `$?` after a pipeline holds the exit code of the last command (`jq`), not of `monolynx`:
428
+
429
+ ```bash
430
+ #!/usr/bin/env bash
431
+ set -u
432
+
433
+ json=$(monolynx -o json --project my-project ticket list --status todo)
434
+ status=$?
435
+ case $status in
436
+ 0) printf '%s\n' "$json" | jq -r '.items[] | .key' ;;
437
+ 3) echo "Not signed in or token expired: run 'monolynx auth login'." >&2; exit 3 ;;
438
+ 4) echo "Monolynx is unreachable, try again later." >&2; exit 4 ;;
439
+ *) echo "monolynx failed with exit code $status." >&2; exit "$status" ;;
440
+ esac
441
+ ```
442
+
443
+ ## The `mnx` alias
444
+
445
+ The package installs two equivalent commands, `monolynx` and `mnx`. Every command works with either:
446
+
447
+ ```bash
448
+ mnx ticket list
449
+ mnx -o json project list
450
+ ```
451
+
452
+ ### Shell completion
453
+
454
+ `monolynx completion <shell>` prints a completion script for `bash`, `zsh` or `fish` on stdout. It needs no sign-in and no configuration. An unknown shell ends with exit code 2.
455
+
456
+ The script is bound to the name the program was invoked as, so generate it separately for each command you want completed. `monolynx completion zsh` prints a script for `monolynx`, `mnx completion zsh` a script for `mnx`. Any other invocation name (for example `python -m monolynx_cli`) gets the script for `monolynx`.
457
+
458
+ Load the script into the current session (add the line to your shell startup file to make it permanent):
459
+
460
+ ```bash
461
+ source <(monolynx completion bash)
462
+ ```
463
+
464
+ ```zsh
465
+ autoload -Uz compinit && compinit
466
+ source <(monolynx completion zsh)
467
+ ```
468
+
469
+ ```fish
470
+ monolynx completion fish | source
471
+ ```
472
+
473
+ Or save it to the place your shell reads completions from. The zsh script starts with `#compdef`, so it works as a file in a directory on `$fpath` (the file must be named `_monolynx`):
474
+
475
+ ```bash
476
+ # bash
477
+ mkdir -p ~/.local/share/bash-completion/completions
478
+ monolynx completion bash > ~/.local/share/bash-completion/completions/monolynx
479
+ ```
480
+
481
+ ```zsh
482
+ # zsh: add "fpath+=~/.zfunc; autoload -Uz compinit; compinit" to ~/.zshrc, before compinit runs
483
+ mkdir -p ~/.zfunc
484
+ monolynx completion zsh > ~/.zfunc/_monolynx
485
+ ```
486
+
487
+ ```fish
488
+ # fish
489
+ mkdir -p ~/.config/fish/completions
490
+ monolynx completion fish > ~/.config/fish/completions/monolynx.fish
491
+ ```
492
+
493
+ For the `mnx` alias, do the same with `mnx completion <shell>` and save the result under the name `mnx` (`_mnx` for zsh, `mnx.fish` for fish):
494
+
495
+ ```bash
496
+ mnx completion fish > ~/.config/fish/completions/mnx.fish
497
+ ```
498
+
499
+ Alternatively, let the CLI install completion for your current shell with `monolynx --install-completion`, or print the script with `monolynx --show-completion` (to copy it or customize the installation). Both detect the shell automatically; `--install-completion` writes the script for `monolynx` only, so run `mnx --install-completion` for the alias. Open a new shell session afterwards.
500
+
501
+ ## Documentation and issues
502
+
503
+ - Documentation: <https://gitlab.com/piotrkrych/monolynx/-/blob/main/cli/README.md>
504
+ - Issues: <https://gitlab.com/piotrkrych/monolynx/-/issues>
505
+ - Source and homepage: <https://gitlab.com/piotrkrych/monolynx>
506
+
507
+ ## License
508
+
509
+ MIT. See the `LICENSE` file distributed with the package.
510
+
511
+ ## For command authors
512
+
513
+ Commands never print result data with `print()`, `typer.echo()` or `rich.print()`. A command builds a structure (`list[dict]` for records, `dict` for a single record or a pagination envelope with an `items` key) and hands it over as its last step:
514
+
515
+ ```python
516
+ from monolynx_cli.output import emit
517
+
518
+ emit(ctx, data)
519
+ ```
520
+
521
+ `emit(ctx, data)` reads the global options (`monolynx_cli.options.GlobalOptions`) and calls `render(data, fmt, *, no_color, quiet)`, the only function that writes result data to stdout, in `table`, `json`, `yaml` or `csv`. Progress messages (not data, not errors) go through `echo_stderr(msg, *, quiet=False, no_color=False)`, which respects `--quiet`. Error messages go to stderr with `typer.echo(..., err=True)` and are never silenced.
522
+
523
+ HTTP calls go through `monolynx_cli.client.MonolynxClient`, never through `httpx` directly. The client adds the `Authorization: Bearer`, `User-Agent: monolynx-cli/<version>` and `Accept: application/json` headers, applies the retry policy and returns parsed JSON (`None` for 204). Commands do not catch client exceptions: a `MonolynxError` goes up to the global handler in `monolynx_cli.main`, which prints it and exits with the code from [Exit codes](#exit-codes).
524
+
525
+ ```python
526
+ opts = get_options(ctx)
527
+ with MonolynxClient(settings.endpoint, settings.bearer, timeout=opts.timeout, debug=opts.debug) as client:
528
+ emit(ctx, client.get("/api/v2/projects"))
529
+ ```
530
+
531
+ Development install and tests, from the repository root:
532
+
533
+ ```bash
534
+ pip install -e "cli/[test]"
535
+ pytest cli/tests
536
+ ```