confpub-cli 0.2.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. confpub_cli-0.2.0/.github/workflows/publish.yml +49 -0
  2. confpub_cli-0.2.0/.gitignore +31 -0
  3. confpub_cli-0.2.0/LICENSE +21 -0
  4. confpub_cli-0.2.0/PKG-INFO +425 -0
  5. confpub_cli-0.2.0/PRD.md +817 -0
  6. confpub_cli-0.2.0/README.md +387 -0
  7. confpub_cli-0.2.0/confpub/__init__.py +3 -0
  8. confpub_cli-0.2.0/confpub/applier.py +188 -0
  9. confpub_cli-0.2.0/confpub/assets.py +153 -0
  10. confpub_cli-0.2.0/confpub/cli.py +388 -0
  11. confpub_cli-0.2.0/confpub/config.py +188 -0
  12. confpub_cli-0.2.0/confpub/confluence.py +239 -0
  13. confpub_cli-0.2.0/confpub/converter.py +371 -0
  14. confpub_cli-0.2.0/confpub/envelope.py +106 -0
  15. confpub_cli-0.2.0/confpub/errors.py +172 -0
  16. confpub_cli-0.2.0/confpub/guide.py +207 -0
  17. confpub_cli-0.2.0/confpub/lockfile.py +91 -0
  18. confpub_cli-0.2.0/confpub/manifest.py +180 -0
  19. confpub_cli-0.2.0/confpub/output.py +80 -0
  20. confpub_cli-0.2.0/confpub/planner.py +164 -0
  21. confpub_cli-0.2.0/confpub/publish.py +152 -0
  22. confpub_cli-0.2.0/confpub/py.typed +0 -0
  23. confpub_cli-0.2.0/confpub/validator.py +85 -0
  24. confpub_cli-0.2.0/confpub/verifier.py +122 -0
  25. confpub_cli-0.2.0/confpub.lock +10 -0
  26. confpub_cli-0.2.0/pyproject.toml +64 -0
  27. confpub_cli-0.2.0/tests/__init__.py +0 -0
  28. confpub_cli-0.2.0/tests/conftest.py +21 -0
  29. confpub_cli-0.2.0/tests/test_applier.py +155 -0
  30. confpub_cli-0.2.0/tests/test_assets.py +108 -0
  31. confpub_cli-0.2.0/tests/test_config.py +111 -0
  32. confpub_cli-0.2.0/tests/test_confluence.py +155 -0
  33. confpub_cli-0.2.0/tests/test_converter.py +222 -0
  34. confpub_cli-0.2.0/tests/test_envelope.py +117 -0
  35. confpub_cli-0.2.0/tests/test_errors.py +129 -0
  36. confpub_cli-0.2.0/tests/test_guide.py +101 -0
  37. confpub_cli-0.2.0/tests/test_integration.py +95 -0
  38. confpub_cli-0.2.0/tests/test_lockfile.py +112 -0
  39. confpub_cli-0.2.0/tests/test_manifest.py +189 -0
  40. confpub_cli-0.2.0/tests/test_output.py +90 -0
  41. confpub_cli-0.2.0/tests/test_planner.py +131 -0
  42. confpub_cli-0.2.0/tests/test_publish.py +134 -0
  43. confpub_cli-0.2.0/tests/test_validator.py +101 -0
  44. confpub_cli-0.2.0/tests/test_verifier.py +78 -0
  45. confpub_cli-0.2.0/uv.lock +1247 -0
@@ -0,0 +1,49 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ permissions:
9
+ id-token: write
10
+
11
+ jobs:
12
+ build:
13
+ name: Build distribution
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@v5
20
+
21
+ - name: Build package
22
+ run: uv build
23
+
24
+ - name: Upload distribution artifacts
25
+ uses: actions/upload-artifact@v4
26
+ with:
27
+ name: python-package-distributions
28
+ path: dist/
29
+
30
+ publish:
31
+ name: Publish to PyPI
32
+ needs: build
33
+ runs-on: ubuntu-latest
34
+ environment:
35
+ name: pypi
36
+ url: https://pypi.org/p/confpub-cli
37
+ permissions:
38
+ id-token: write
39
+ steps:
40
+ - name: Download distribution artifacts
41
+ uses: actions/download-artifact@v4
42
+ with:
43
+ name: python-package-distributions
44
+ path: dist/
45
+
46
+ - name: Publish to PyPI
47
+ uses: pypa/gh-action-pypi-publish@release/v1
48
+ with:
49
+ skip-existing: true
@@ -0,0 +1,31 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+
7
+ # Distribution / packaging
8
+ dist/
9
+ build/
10
+ *.egg-info/
11
+ *.egg
12
+
13
+ # Virtual environments
14
+ .venv/
15
+ venv/
16
+ ENV/
17
+
18
+ # Testing
19
+ .pytest_cache/
20
+ .coverage
21
+ htmlcov/
22
+
23
+ # IDE
24
+ .idea/
25
+ .vscode/
26
+ *.swp
27
+ *.swo
28
+
29
+ # OS
30
+ .DS_Store
31
+ Thumbs.db
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thomas Klok Rohde
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.
@@ -0,0 +1,425 @@
1
+ Metadata-Version: 2.4
2
+ Name: confpub-cli
3
+ Version: 0.2.0
4
+ Summary: Agent-first CLI to publish Markdown to Confluence
5
+ Project-URL: Homepage, https://github.com/ThomasRohde/confpub-cli
6
+ Project-URL: Repository, https://github.com/ThomasRohde/confpub-cli.git
7
+ Project-URL: Issues, https://github.com/ThomasRohde/confpub-cli/issues
8
+ Author-email: Thomas Klok Rohde <rohde.thomas@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: atlassian,cli,confluence,documentation,markdown,publishing
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Natural Language :: English
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Documentation
24
+ Classifier: Topic :: Office/Business
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.10
27
+ Requires-Dist: atlassian-python-api>=3.41
28
+ Requires-Dist: keyring>=24.0
29
+ Requires-Dist: markdown-it-py[linkify,plugins]>=3.0
30
+ Requires-Dist: orjson>=3.9
31
+ Requires-Dist: pydantic>=2.0
32
+ Requires-Dist: pyyaml>=6.0
33
+ Requires-Dist: typer[all]>=0.9
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
36
+ Requires-Dist: pytest>=7.0; extra == 'dev'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # confpub
40
+
41
+ **Agent-first CLI to publish Markdown to Confluence.**
42
+
43
+ Publish one file or an entire documentation tree — from the terminal, a CI pipeline, or an LLM agent. Every command returns structured JSON. Every error has a stable code. One call to `confpub guide` gives an agent everything it needs to drive the tool zero-shot.
44
+
45
+ ```
46
+ pip install confpub
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Quick Start
52
+
53
+ ### Publish a single file
54
+
55
+ ```bash
56
+ export CONFPUB_URL=https://yourorg.atlassian.net/wiki
57
+ export CONFPUB_TOKEN=your-api-token
58
+ export CONFPUB_USER=you@example.com
59
+
60
+ confpub page publish README.md --space DEV --parent "Engineering"
61
+ ```
62
+
63
+ ### Publish a documentation tree
64
+
65
+ Create a `confpub.yaml` manifest:
66
+
67
+ ```yaml
68
+ schema_version: "1.0"
69
+ space: DEV
70
+ parent: "Engineering"
71
+
72
+ pages:
73
+ - title: "Architecture Overview"
74
+ file: docs/architecture.md
75
+ assets:
76
+ - docs/diagrams/*.png
77
+ children:
78
+ - title: "API Reference"
79
+ file: docs/api.md
80
+ - title: "Deployment Guide"
81
+ file: docs/deploy.md
82
+ ```
83
+
84
+ Then run the transactional workflow:
85
+
86
+ ```bash
87
+ confpub plan create --manifest confpub.yaml # Plan (no writes)
88
+ confpub plan validate --plan confpub-plan.json # Check for drift
89
+ confpub plan apply --plan confpub-plan.json # Apply to Confluence
90
+ confpub plan verify --assertions verify.json # Assert post-conditions
91
+ ```
92
+
93
+ Or preview first with `--dry-run`:
94
+
95
+ ```bash
96
+ confpub plan apply --plan confpub-plan.json --dry-run
97
+ ```
98
+
99
+ ---
100
+
101
+ ## Features
102
+
103
+ - **Structured JSON output** — every command returns the same envelope shape on stdout
104
+ - **Transactional workflow** — plan → validate → apply → verify with fingerprint-based conflict detection
105
+ - **Markdown → Confluence** — code blocks become code macros, `> [!NOTE]` becomes Info panels, tables stay tables
106
+ - **Asset handling** — images are uploaded as attachments and URLs are rewritten automatically
107
+ - **Idempotent** — a lockfile tracks page IDs so re-publishing updates in place
108
+ - **Agent-ready** — `confpub guide` returns the full CLI schema; `LLM=true` suppresses interactive behavior
109
+ - **Cloud + Server** — works with Confluence Cloud (*.atlassian.net) and Server/Data Center
110
+
111
+ ---
112
+
113
+ ## Commands
114
+
115
+ All commands follow a `noun verb` pattern. Verbs telegraph mutation intent.
116
+
117
+ | Command | Mutates | Description |
118
+ |---------|---------|-------------|
119
+ | `confpub guide` | No | Machine-readable CLI schema |
120
+ | `confpub page list` | No | List pages in a space |
121
+ | `confpub page inspect` | No | Detailed view of one page |
122
+ | `confpub page publish` | **Yes** | Publish a single Markdown file |
123
+ | `confpub page delete` | **Yes** | Delete a page |
124
+ | `confpub space list` | No | List accessible spaces |
125
+ | `confpub attachment list` | No | List attachments on a page |
126
+ | `confpub attachment upload` | **Yes** | Upload a file as an attachment |
127
+ | `confpub plan create` | No | Generate a plan artifact from a manifest |
128
+ | `confpub plan validate` | No | Check a plan against current state |
129
+ | `confpub plan apply` | **Yes** | Execute a plan (supports `--dry-run`) |
130
+ | `confpub plan verify` | No | Assert post-conditions hold |
131
+ | `confpub auth inspect` | No | Show credential status |
132
+ | `confpub config set` | **Yes** | Write a config value |
133
+ | `confpub config inspect` | No | Show current config |
134
+
135
+ ---
136
+
137
+ ## Structured Envelope
138
+
139
+ Every command — success or failure — returns this exact shape on stdout:
140
+
141
+ ```json
142
+ {
143
+ "schema_version": "1.0",
144
+ "request_id": "req_20260228_143000_7f3a",
145
+ "ok": true,
146
+ "command": "page.publish",
147
+ "target": {
148
+ "space": "DEV",
149
+ "title": "Architecture Overview"
150
+ },
151
+ "result": { "..." : "..." },
152
+ "warnings": [],
153
+ "errors": [],
154
+ "metrics": {
155
+ "duration_ms": 842
156
+ }
157
+ }
158
+ ```
159
+
160
+ On failure, `ok` is `false`, `result` is `null`, and `errors` contains structured error objects:
161
+
162
+ ```json
163
+ {
164
+ "ok": false,
165
+ "errors": [
166
+ {
167
+ "code": "ERR_CONFLICT_FINGERPRINT",
168
+ "message": "Page was modified externally since plan was created",
169
+ "retryable": false,
170
+ "suggested_action": "fix_input",
171
+ "details": {
172
+ "page_id": "123456",
173
+ "plan_fingerprint": "sha256:abc123",
174
+ "current_fingerprint": "sha256:def456"
175
+ }
176
+ }
177
+ ]
178
+ }
179
+ ```
180
+
181
+ **Invariants:**
182
+ - `stdout` is exclusively JSON — one object, no preamble, no epilogue
183
+ - `errors` and `warnings` are always arrays (possibly empty)
184
+ - `result` is always present (`null` on failure)
185
+ - `stderr` gets progress events, diagnostics, and debug logs
186
+
187
+ ---
188
+
189
+ ## Exit Codes
190
+
191
+ | Code | Meaning | Action |
192
+ |------|---------|--------|
193
+ | `0` | Success | — |
194
+ | `10` | Validation error | Fix input, do not retry |
195
+ | `20` | Auth / permission | Re-authenticate or escalate |
196
+ | `40` | Conflict | Re-plan, do not blindly retry |
197
+ | `50` | I/O error | Retry with backoff |
198
+ | `90` | Internal error | File a bug |
199
+
200
+ ---
201
+
202
+ ## Error Codes
203
+
204
+ Stable across versions. An agent can branch on these without parsing messages.
205
+
206
+ ```
207
+ ERR_VALIDATION_REQUIRED Missing required argument
208
+ ERR_VALIDATION_MANIFEST Manifest fails schema validation
209
+ ERR_VALIDATION_MARKDOWN Unparseable Markdown
210
+ ERR_VALIDATION_ASSET_MISSING Referenced image not found on disk
211
+
212
+ ERR_AUTH_REQUIRED No credentials configured
213
+ ERR_AUTH_EXPIRED Token has expired
214
+ ERR_AUTH_FORBIDDEN Lacks permission to write
215
+
216
+ ERR_CONFLICT_FINGERPRINT Page changed since plan was created
217
+ ERR_CONFLICT_LOCK Another confpub process holds the lock
218
+ ERR_CONFLICT_PAGE_EXISTS Title exists with unexpected ID
219
+
220
+ ERR_IO_FILE_NOT_FOUND Source file missing
221
+ ERR_IO_CONNECTION Confluence unreachable
222
+ ERR_IO_TIMEOUT Request timed out
223
+
224
+ ERR_INTERNAL_CONVERTER Conversion crashed
225
+ ERR_INTERNAL_SDK Unexpected API response
226
+ ```
227
+
228
+ ---
229
+
230
+ ## Authentication
231
+
232
+ Credentials are resolved in this order (highest precedence first):
233
+
234
+ ```
235
+ CLI flags → --token / --user
236
+ Env vars → CONFPUB_TOKEN / CONFPUB_USER / CONFPUB_URL
237
+ Config file → ~/.config/confpub/config.json
238
+ OS keychain → via keyring
239
+ ```
240
+
241
+ Cloud vs Server is auto-detected from the URL: `*.atlassian.net` uses token + email auth; everything else uses PAT.
242
+
243
+ ```bash
244
+ # Check current auth status
245
+ confpub auth inspect
246
+
247
+ # Set config values
248
+ confpub config set base_url https://yourorg.atlassian.net/wiki
249
+ confpub config set user you@example.com
250
+ ```
251
+
252
+ When `LLM=true` or stdin is non-interactive, confpub never prompts — it returns a structured `ERR_AUTH_REQUIRED` error instead.
253
+
254
+ ---
255
+
256
+ ## Markdown Conversion
257
+
258
+ confpub converts Markdown to Confluence Storage Format:
259
+
260
+ | Markdown | Confluence Output |
261
+ |----------|-------------------|
262
+ | `# Heading` | `<h1>Heading</h1>` |
263
+ | `**bold**` | `<strong>bold</strong>` |
264
+ | `` `code` `` | `<code>code</code>` |
265
+ | Fenced code block | `<ac:structured-macro ac:name="code">` with language param |
266
+ | `> [!NOTE]` | Confluence Info macro |
267
+ | `> [!WARNING]` | Confluence Warning macro |
268
+ | `> [!TIP]` | Confluence Tip macro |
269
+ | `![img](photo.png)` | Upload attachment + `<ac:image>` reference |
270
+ | Tables | Standard XHTML `<table>` |
271
+ | `~~strikethrough~~` | `<del>strikethrough</del>` |
272
+
273
+ ---
274
+
275
+ ## Manifest Format
276
+
277
+ ```yaml
278
+ schema_version: "1.0"
279
+ space: DEV
280
+ parent: "Architecture Notes"
281
+
282
+ confluence:
283
+ base_url: https://yourorg.atlassian.net/wiki
284
+ auth:
285
+ type: token # Credentials via CONFPUB_TOKEN + CONFPUB_USER
286
+
287
+ conflict_strategy: fail # fail | overwrite | skip
288
+ on_removal: leave # leave | delete
289
+ version_comment: "Published by confpub @ {timestamp}"
290
+
291
+ labels:
292
+ - architecture
293
+ - auto-published
294
+
295
+ assertions:
296
+ - type: page.exists
297
+ title: "Overview"
298
+ - type: page.parent
299
+ title: "Components"
300
+ expected_parent: "Overview"
301
+
302
+ pages:
303
+ - title: "Overview"
304
+ file: overview.md
305
+
306
+ - title: "Component Design"
307
+ file: components/design.md
308
+ assets:
309
+ - components/diagrams/*.png
310
+ children:
311
+ - title: "API Reference"
312
+ file: components/api.md
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Lockfile
318
+
319
+ After the first successful apply, confpub writes `confpub.lock` alongside the manifest. Commit this to version control — it maps page titles to Confluence page IDs for idempotent re-publishing.
320
+
321
+ ```json
322
+ {
323
+ "schema_version": "1.0",
324
+ "last_updated": "2026-02-28T14:35:00Z",
325
+ "pages": {
326
+ "Overview": { "page_id": "123456", "version": 5 },
327
+ "Component Design": { "page_id": "123457", "version": 1 },
328
+ "API Reference": { "page_id": "123458", "version": 1 }
329
+ }
330
+ }
331
+ ```
332
+
333
+ ---
334
+
335
+ ## Agent Integration
336
+
337
+ An LLM agent can drive confpub entirely from one bootstrap call:
338
+
339
+ ```bash
340
+ # Step 1: Learn the CLI
341
+ confpub guide
342
+
343
+ # Step 2: Check credentials
344
+ confpub auth inspect
345
+
346
+ # Step 3: Explore
347
+ confpub space list
348
+ confpub page list --space DEV
349
+
350
+ # Step 4: Publish
351
+ confpub page publish doc.md --space DEV --parent "Docs" --dry-run
352
+ confpub page publish doc.md --space DEV --parent "Docs"
353
+ ```
354
+
355
+ The `guide` command returns the complete schema — all commands with flags, all error codes with exit codes and retry hints, auth precedence, and concurrency rules:
356
+
357
+ ```bash
358
+ confpub guide # Full schema
359
+ confpub guide --section auth # Just auth info
360
+ confpub guide --section error_codes # Just error codes
361
+ confpub guide --section commands # Just commands
362
+ ```
363
+
364
+ ### Environment variables for agents
365
+
366
+ | Variable | Effect |
367
+ |----------|--------|
368
+ | `LLM=true` | Suppress interactive prompts; return structured errors instead |
369
+ | `CONFPUB_TOKEN` | API token |
370
+ | `CONFPUB_USER` | Email / username |
371
+ | `CONFPUB_URL` | Confluence base URL |
372
+
373
+ ---
374
+
375
+ ## Development
376
+
377
+ ```bash
378
+ # Install in editable mode with dev dependencies
379
+ pip install -e ".[dev]"
380
+
381
+ # Run tests
382
+ pytest tests/ -v
383
+
384
+ # Run with coverage
385
+ pytest tests/ -v --cov=confpub
386
+ ```
387
+
388
+ ### Project Structure
389
+
390
+ ```
391
+ confpub/
392
+ ├── cli.py # Typer app, commands, envelope wrapping
393
+ ├── envelope.py # Pydantic envelope model
394
+ ├── errors.py # Error codes, exit codes, ConfpubError
395
+ ├── output.py # TOON / LLM=true / isatty logic
396
+ ├── config.py # Credential precedence
397
+ ├── confluence.py # atlassian-python-api wrapper
398
+ ├── converter.py # Markdown → Confluence Storage Format
399
+ ├── manifest.py # Manifest + plan artifact models
400
+ ├── lockfile.py # confpub.lock persistence
401
+ ├── assets.py # Asset discovery, upload, URL rewriting
402
+ ├── planner.py # plan.create
403
+ ├── validator.py # plan.validate
404
+ ├── applier.py # plan.apply
405
+ ├── verifier.py # plan.verify
406
+ ├── publish.py # page.publish shortcut
407
+ └── guide.py # Machine-readable CLI schema
408
+ ```
409
+
410
+ ### Technology Stack
411
+
412
+ | Concern | Choice |
413
+ |---------|--------|
414
+ | CLI framework | [Typer](https://typer.tiangolo.com) |
415
+ | Confluence API | [atlassian-python-api](https://github.com/atlassian-api/atlassian-python-api) |
416
+ | Markdown parsing | [markdown-it-py](https://github.com/executablebooks/markdown-it-py) |
417
+ | Validation | [Pydantic v2](https://docs.pydantic.dev) |
418
+ | JSON serialization | [orjson](https://github.com/ijl/orjson) |
419
+ | Credentials | [keyring](https://github.com/jaraco/keyring) + env vars |
420
+
421
+ ---
422
+
423
+ ## License
424
+
425
+ MIT