eledoctl 1.0.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 (33) hide show
  1. eledoctl-1.0.0/.gitignore +14 -0
  2. eledoctl-1.0.0/LICENSE +21 -0
  3. eledoctl-1.0.0/PKG-INFO +154 -0
  4. eledoctl-1.0.0/README.md +105 -0
  5. eledoctl-1.0.0/pyproject.toml +94 -0
  6. eledoctl-1.0.0/src/eledoctl/__init__.py +1 -0
  7. eledoctl-1.0.0/src/eledoctl/cli/__init__.py +1 -0
  8. eledoctl-1.0.0/src/eledoctl/cli/common.py +25 -0
  9. eledoctl-1.0.0/src/eledoctl/cli/documents.py +195 -0
  10. eledoctl-1.0.0/src/eledoctl/cli/internal/__init__.py +0 -0
  11. eledoctl-1.0.0/src/eledoctl/cli/internal/docs.py +37 -0
  12. eledoctl-1.0.0/src/eledoctl/cli/login.py +72 -0
  13. eledoctl-1.0.0/src/eledoctl/cli/main.py +30 -0
  14. eledoctl-1.0.0/src/eledoctl/cli/profile.py +26 -0
  15. eledoctl-1.0.0/src/eledoctl/cli/templates.py +48 -0
  16. eledoctl-1.0.0/src/eledoctl/config/__init__.py +1 -0
  17. eledoctl-1.0.0/src/eledoctl/config/settings.py +108 -0
  18. eledoctl-1.0.0/src/eledoctl/internal/__init__.py +0 -0
  19. eledoctl-1.0.0/src/eledoctl/internal/cms/__init__.py +0 -0
  20. eledoctl-1.0.0/src/eledoctl/internal/docsync/__init__.py +0 -0
  21. eledoctl-1.0.0/src/eledoctl/internal/docsync/slug.py +21 -0
  22. eledoctl-1.0.0/src/eledoctl/internal/transform/__init__.py +0 -0
  23. eledoctl-1.0.0/src/eledoctl/internal/transform/markdown.py +39 -0
  24. eledoctl-1.0.0/src/pyeledo/__init__.py +29 -0
  25. eledoctl-1.0.0/src/pyeledo/client.py +151 -0
  26. eledoctl-1.0.0/src/pyeledo/cms.py +1 -0
  27. eledoctl-1.0.0/src/pyeledo/exceptions.py +23 -0
  28. eledoctl-1.0.0/src/pyeledo/generate.py +81 -0
  29. eledoctl-1.0.0/src/pyeledo/profile.py +22 -0
  30. eledoctl-1.0.0/src/pyeledo/schema.py +91 -0
  31. eledoctl-1.0.0/src/pyeledo/templates.py +78 -0
  32. eledoctl-1.0.0/src/pyeledo/types.py +6 -0
  33. eledoctl-1.0.0/src/pyeledo/utils.py +95 -0
@@ -0,0 +1,14 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ .coverage
8
+ htmlcov/
9
+ dist/
10
+ build/
11
+ *.egg-info/
12
+ .env
13
+ .DS_Store
14
+ .idea
eledoctl-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eledo
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,154 @@
1
+ Metadata-Version: 2.4
2
+ Name: eledoctl
3
+ Version: 1.0.0
4
+ Summary: Command-line toolkit and async Python API client for Eledo.
5
+ Author: Eledo
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Eledo
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ License-File: LICENSE
28
+ Keywords: api,automation,cli,documents,eledo,pdf,sdk
29
+ Classifier: Development Status :: 5 - Production/Stable
30
+ Classifier: Environment :: Console
31
+ Classifier: Intended Audience :: Developers
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Programming Language :: Python :: 3
34
+ Classifier: Programming Language :: Python :: 3.12
35
+ Classifier: Programming Language :: Python :: 3.13
36
+ Classifier: Programming Language :: Python :: 3.14
37
+ Classifier: Topic :: Internet :: WWW/HTTP
38
+ Classifier: Topic :: Office/Business
39
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
40
+ Classifier: Topic :: Utilities
41
+ Classifier: Typing :: Typed
42
+ Requires-Python: >=3.12
43
+ Requires-Dist: click>=8.1
44
+ Requires-Dist: httpx>=0.27
45
+ Requires-Dist: platformdirs>=4.2
46
+ Provides-Extra: repl
47
+ Requires-Dist: prompt-toolkit>=3.0; extra == 'repl'
48
+ Description-Content-Type: text/markdown
49
+
50
+ # eledoctl
51
+ [![CI](https://github.com/eledo-online/eledoctl/actions/workflows/ci.yml/badge.svg)](https://github.com/eledo-online/eledoctl/actions/workflows/ci.yml)
52
+ [![codecov](https://codecov.io/gh/eledo-online/eledoctl/graph/badge.svg?token=IVVXIPSQHM)](https://codecov.io/gh/eledo-online/eledoctl)
53
+ [![Release](https://img.shields.io/github/v/release/eledo-online/eledoctl)](https://github.com/eledo-online/eledoctl/releases)
54
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
55
+ [![Eledo](https://img.shields.io/badge/Eledo-green.svg)](https://eledo.online/)
56
+ ![Eledo PDF automation overview](https://github.com/user-attachments/assets/31ee16e8-7a60-4989-8a73-cf2c3097cfa4)
57
+
58
+ `eledoctl` is an open-source command-line toolkit for Eledo.
59
+
60
+ It contains two Python modules:
61
+
62
+ * `pyeledo` — async-native Python API client for Eledo REST APIs
63
+ * `eledoctl` — CLI, REPL, and automation layer built on top of `pyeledo`
64
+
65
+ The project is MIT licensed. Public functionality is exposed through `pyeledo` and `eledoctl`, while internal Eledo tooling is implemented as optional extensions.
66
+
67
+ ## Architecture
68
+
69
+ ```text
70
+ repo/
71
+ ├── src/
72
+ │ ├── pyeledo/ # Async SDK / REST client
73
+ │ └── eledoctl/ # CLI / REPL / orchestration
74
+ └── tests/
75
+ ├── pyeledo/
76
+ └── eledoctl/
77
+ ```
78
+
79
+ `pyeledo` is async-first and async-only. It never stores credentials. Tokens are passed to the client by the caller.
80
+
81
+ ```python
82
+ from pyeledo import EledoClient, TemplateScope
83
+
84
+ async with EledoClient(token="...") as client:
85
+ profile = await client.get_profile()
86
+ templates = await client.get_templates(scope=TemplateScope.PRIVATE)
87
+ ```
88
+
89
+ ## Initial CLI Tree
90
+
91
+ ```bash
92
+ eledoctl profile
93
+ eledoctl templates list
94
+ eledoctl templates schema TEMPLATE_ID
95
+ eledoctl pdf generate TEMPLATE_ID --payload payload.json --output output.pdf
96
+ eledoctl internal docs sync docs
97
+ ```
98
+
99
+ For now the CLI passes an empty token unless `--token` is provided. Persistent token storage will be added later in `eledoctl`, not in `pyeledo`.
100
+
101
+ ## Installation
102
+
103
+ ```bash
104
+ pip install eledoctl
105
+ ```
106
+
107
+ ## Development
108
+
109
+ The project uses modern Python tooling based on `uv`.
110
+
111
+ ### Create or update the development environment
112
+
113
+ ```bash
114
+ uv sync --group dev
115
+ ```
116
+
117
+ ### Run the test suite
118
+
119
+ ```bash
120
+ uv run pytest
121
+ ```
122
+
123
+ ### Run the CLI
124
+
125
+ ```bash
126
+ uv run eledoctl --help
127
+ ```
128
+
129
+ ### Format the code
130
+
131
+ ```bash
132
+ uv run ruff format .
133
+ ```
134
+
135
+ ### Run linting
136
+
137
+ ```bash
138
+ uv run ruff check .
139
+ ```
140
+
141
+ ### Run type checking
142
+
143
+ ```bash
144
+ uv run mypy src
145
+ ```
146
+
147
+ ### Full validation
148
+
149
+ ```bash
150
+ uv run ruff format .
151
+ uv run ruff check .
152
+ uv run mypy src
153
+ uv run pytest
154
+ ```
@@ -0,0 +1,105 @@
1
+ # eledoctl
2
+ [![CI](https://github.com/eledo-online/eledoctl/actions/workflows/ci.yml/badge.svg)](https://github.com/eledo-online/eledoctl/actions/workflows/ci.yml)
3
+ [![codecov](https://codecov.io/gh/eledo-online/eledoctl/graph/badge.svg?token=IVVXIPSQHM)](https://codecov.io/gh/eledo-online/eledoctl)
4
+ [![Release](https://img.shields.io/github/v/release/eledo-online/eledoctl)](https://github.com/eledo-online/eledoctl/releases)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+ [![Eledo](https://img.shields.io/badge/Eledo-green.svg)](https://eledo.online/)
7
+ ![Eledo PDF automation overview](https://github.com/user-attachments/assets/31ee16e8-7a60-4989-8a73-cf2c3097cfa4)
8
+
9
+ `eledoctl` is an open-source command-line toolkit for Eledo.
10
+
11
+ It contains two Python modules:
12
+
13
+ * `pyeledo` — async-native Python API client for Eledo REST APIs
14
+ * `eledoctl` — CLI, REPL, and automation layer built on top of `pyeledo`
15
+
16
+ The project is MIT licensed. Public functionality is exposed through `pyeledo` and `eledoctl`, while internal Eledo tooling is implemented as optional extensions.
17
+
18
+ ## Architecture
19
+
20
+ ```text
21
+ repo/
22
+ ├── src/
23
+ │ ├── pyeledo/ # Async SDK / REST client
24
+ │ └── eledoctl/ # CLI / REPL / orchestration
25
+ └── tests/
26
+ ├── pyeledo/
27
+ └── eledoctl/
28
+ ```
29
+
30
+ `pyeledo` is async-first and async-only. It never stores credentials. Tokens are passed to the client by the caller.
31
+
32
+ ```python
33
+ from pyeledo import EledoClient, TemplateScope
34
+
35
+ async with EledoClient(token="...") as client:
36
+ profile = await client.get_profile()
37
+ templates = await client.get_templates(scope=TemplateScope.PRIVATE)
38
+ ```
39
+
40
+ ## Initial CLI Tree
41
+
42
+ ```bash
43
+ eledoctl profile
44
+ eledoctl templates list
45
+ eledoctl templates schema TEMPLATE_ID
46
+ eledoctl pdf generate TEMPLATE_ID --payload payload.json --output output.pdf
47
+ eledoctl internal docs sync docs
48
+ ```
49
+
50
+ For now the CLI passes an empty token unless `--token` is provided. Persistent token storage will be added later in `eledoctl`, not in `pyeledo`.
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ pip install eledoctl
56
+ ```
57
+
58
+ ## Development
59
+
60
+ The project uses modern Python tooling based on `uv`.
61
+
62
+ ### Create or update the development environment
63
+
64
+ ```bash
65
+ uv sync --group dev
66
+ ```
67
+
68
+ ### Run the test suite
69
+
70
+ ```bash
71
+ uv run pytest
72
+ ```
73
+
74
+ ### Run the CLI
75
+
76
+ ```bash
77
+ uv run eledoctl --help
78
+ ```
79
+
80
+ ### Format the code
81
+
82
+ ```bash
83
+ uv run ruff format .
84
+ ```
85
+
86
+ ### Run linting
87
+
88
+ ```bash
89
+ uv run ruff check .
90
+ ```
91
+
92
+ ### Run type checking
93
+
94
+ ```bash
95
+ uv run mypy src
96
+ ```
97
+
98
+ ### Full validation
99
+
100
+ ```bash
101
+ uv run ruff format .
102
+ uv run ruff check .
103
+ uv run mypy src
104
+ uv run pytest
105
+ ```
@@ -0,0 +1,94 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.24"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "eledoctl"
7
+ version = "1.0.0"
8
+ description = "Command-line toolkit and async Python API client for Eledo."
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ requires-python = ">=3.12"
12
+ authors = [
13
+ { name = "Eledo" }
14
+ ]
15
+ keywords = ["eledo", "cli", "sdk", "pdf", "automation", "api", "documents"]
16
+ classifiers = [
17
+ "Development Status :: 5 - Production/Stable",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Topic :: Office/Business",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ "Topic :: Utilities",
28
+ "Topic :: Internet :: WWW/HTTP",
29
+ "Typing :: Typed",
30
+ ]
31
+ dependencies = [
32
+ "click>=8.1",
33
+ "httpx>=0.27",
34
+ "platformdirs>=4.2",
35
+ ]
36
+
37
+ [project.optional-dependencies]
38
+ repl = [
39
+ "prompt-toolkit>=3.0",
40
+ ]
41
+
42
+ [dependency-groups]
43
+ dev = [
44
+ "pytest>=8.2",
45
+ "pytest-asyncio>=0.23",
46
+ "pytest-cov>=5.0",
47
+ "ruff>=0.5",
48
+ "mypy>=1.10",
49
+ ]
50
+
51
+ [project.scripts]
52
+ eledoctl = "eledoctl.cli.main:main"
53
+
54
+ [tool.hatch.build]
55
+ include = [
56
+ "src/pyeledo/**",
57
+ "src/eledoctl/**",
58
+ "README.md",
59
+ "LICENSE",
60
+ ]
61
+
62
+ exclude = [
63
+ ".github/**",
64
+ "tests/**",
65
+ "CONTRIBUTING.md",
66
+ "uv.lock",
67
+ ]
68
+
69
+ [tool.hatch.build.targets.wheel]
70
+ packages = ["src/pyeledo", "src/eledoctl"]
71
+
72
+ [tool.ruff]
73
+ line-length = 120
74
+ target-version = "py312"
75
+ src = ["src", "tests"]
76
+
77
+ [tool.ruff.lint]
78
+ select = ["E", "F", "I", "UP", "B", "SIM"]
79
+ ignore = []
80
+
81
+ [tool.ruff.format]
82
+ quote-style = "double"
83
+ indent-style = "space"
84
+
85
+ [tool.pytest.ini_options]
86
+ addopts = "--cov=pyeledo --cov=eledoctl --cov-report=term-missing"
87
+ asyncio_mode = "auto"
88
+ testpaths = ["tests"]
89
+
90
+ [tool.mypy]
91
+ python_version = "3.12"
92
+ strict = true
93
+ mypy_path = "src"
94
+ packages = ["pyeledo", "eledoctl"]
@@ -0,0 +1 @@
1
+ """Command-line toolkit for Eledo."""
@@ -0,0 +1 @@
1
+ """CLI command definitions."""
@@ -0,0 +1,25 @@
1
+ """Shared CLI utilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ from collections.abc import Coroutine
8
+ from typing import Any
9
+
10
+ import click
11
+
12
+ from eledoctl.config.settings import ConnectionSettings, load_connection_settings
13
+
14
+
15
+ def run[T](coro: Coroutine[Any, Any, T]) -> T:
16
+ """Run an asynchronous command implementation."""
17
+ return asyncio.run(coro)
18
+
19
+
20
+ def require_connection_settings() -> ConnectionSettings:
21
+ """Load persisted connection settings or raise a user-facing CLI error."""
22
+ try:
23
+ return load_connection_settings()
24
+ except (OSError, ValueError, json.JSONDecodeError) as exc:
25
+ raise click.ClickException(str(exc)) from exc
@@ -0,0 +1,195 @@
1
+ """PDF generation CLI commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ import click
9
+
10
+ from eledoctl.cli.common import require_connection_settings, run
11
+ from eledoctl.config.settings import ConnectionSettings
12
+ from pyeledo import EledoClient
13
+ from pyeledo.types import JsonObject
14
+ from pyeledo.utils import parse_json_object
15
+
16
+
17
+ @click.group("documents")
18
+ def documents_group() -> None:
19
+ """Document generation commands."""
20
+
21
+
22
+ @documents_group.command("generate")
23
+ @click.argument("template_id")
24
+ @click.option("--template-version", type=int, default=None, help="Optional template version.")
25
+ @click.option(
26
+ "--payload",
27
+ type=str,
28
+ default=None,
29
+ help='Inline JSON containing the Eledo "file" object.',
30
+ )
31
+ @click.option(
32
+ "--payload-file",
33
+ type=click.Path(path_type=Path, exists=True, dir_okay=False),
34
+ default=None,
35
+ help='Read the Eledo "file" object from a JSON file.',
36
+ )
37
+ @click.option(
38
+ "--payload-stdin",
39
+ is_flag=True,
40
+ help='Read the Eledo "file" object from standard input.',
41
+ )
42
+ @click.option(
43
+ "--add-field",
44
+ "fields",
45
+ multiple=True,
46
+ metavar="KEY=VALUE",
47
+ help=("Add a top-level primitive field. May be repeated. Ignored when a JSON payload source is provided."),
48
+ )
49
+ @click.option(
50
+ "--output",
51
+ "output_path",
52
+ type=click.Path(path_type=Path, dir_okay=False),
53
+ default=None,
54
+ help="Document output path. Defaults to filename returned by Eledo.",
55
+ )
56
+ @click.option(
57
+ "--output-dir",
58
+ type=click.Path(path_type=Path, file_okay=False),
59
+ default=None,
60
+ help="Directory for the generated document when --output is not provided.",
61
+ )
62
+ @click.option("--base64-json", is_flag=True, help="Print JSON metadata with base64 PDF content.")
63
+ def generate_pdf(
64
+ template_id: str,
65
+ template_version: int | None,
66
+ payload: str | None,
67
+ payload_file: Path | None,
68
+ payload_stdin: bool,
69
+ fields: tuple[str, ...],
70
+ output_path: Path | None,
71
+ output_dir: Path | None,
72
+ base64_json: bool,
73
+ ) -> None:
74
+ """Generate a PDF document from an Eledo template."""
75
+ settings = require_connection_settings()
76
+ run(
77
+ _generate_pdf(
78
+ template_id=template_id,
79
+ settings=settings,
80
+ template_version=template_version,
81
+ file_data=_resolve_payload(
82
+ payload=payload, payload_file=payload_file, payload_stdin=payload_stdin, fields=fields
83
+ ),
84
+ output_path=output_path,
85
+ output_dir=output_dir,
86
+ base64_json=base64_json,
87
+ )
88
+ )
89
+
90
+
91
+ async def _generate_pdf(
92
+ *,
93
+ template_id: str,
94
+ settings: ConnectionSettings,
95
+ template_version: int | None,
96
+ file_data: JsonObject | None,
97
+ output_path: Path | None,
98
+ output_dir: Path | None,
99
+ base64_json: bool,
100
+ ) -> None:
101
+ async with EledoClient(base_url=settings.base_url, token=settings.token) as client:
102
+ result = await client.generate_pdf(
103
+ template_id=template_id,
104
+ template_version=template_version,
105
+ file_data=file_data,
106
+ )
107
+
108
+ if base64_json:
109
+ click.echo(json.dumps(result.as_json(), indent=2))
110
+ return
111
+
112
+ destination = _resolve_output_path(
113
+ filename=result.filename,
114
+ output_path=output_path,
115
+ output_dir=output_dir,
116
+ )
117
+ destination.write_bytes(result.content)
118
+ click.echo(str(destination))
119
+
120
+
121
+ def _resolve_payload(
122
+ *,
123
+ payload: str | None,
124
+ payload_file: Path | None,
125
+ payload_stdin: bool,
126
+ fields: tuple[str, ...],
127
+ ) -> JsonObject | None:
128
+ """Read and parse document data from one configured input source."""
129
+ source_count = sum(
130
+ (
131
+ payload is not None,
132
+ payload_file is not None,
133
+ payload_stdin,
134
+ )
135
+ )
136
+
137
+ if source_count > 1:
138
+ raise click.ClickException("Use only one of --payload, --payload-file, or --payload-stdin.")
139
+
140
+ if source_count == 1 and fields:
141
+ click.echo(
142
+ "Warning: --add-field values are ignored because a JSON payload was provided.",
143
+ err=True,
144
+ )
145
+
146
+ if payload is not None:
147
+ return parse_json_object(payload) or None
148
+
149
+ if payload_file is not None:
150
+ return parse_json_object(payload_file.read_text(encoding="utf-8")) or None
151
+
152
+ if payload_stdin:
153
+ return parse_json_object(click.get_text_stream("stdin").read()) or None
154
+
155
+ return _build_field_payload(fields)
156
+
157
+
158
+ def _build_field_payload(fields: tuple[str, ...]) -> JsonObject | None:
159
+ """Build a JSON object from repeated KEY=VALUE field arguments."""
160
+ if not fields:
161
+ return None
162
+
163
+ payload: JsonObject = {}
164
+
165
+ for field in fields:
166
+ key, separator, value = field.partition("=")
167
+
168
+ if separator == "":
169
+ raise click.ClickException(f"Invalid field {field!r}. Expected KEY=VALUE.")
170
+
171
+ key = key.strip()
172
+
173
+ if not key:
174
+ raise click.ClickException(f"Invalid field {field!r}. Field name cannot be empty.")
175
+
176
+ payload[key] = value
177
+
178
+ return payload
179
+
180
+
181
+ def _resolve_output_path(
182
+ *,
183
+ filename: str,
184
+ output_path: Path | None,
185
+ output_dir: Path | None,
186
+ ) -> Path:
187
+ """Resolve the destination path for a generated document."""
188
+ if output_path is not None:
189
+ return output_path
190
+
191
+ if output_dir is not None:
192
+ output_dir.mkdir(parents=True, exist_ok=True)
193
+ return output_dir / filename
194
+
195
+ return Path(filename)
File without changes
@@ -0,0 +1,37 @@
1
+ """Internal documentation synchronization CLI commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import click
8
+
9
+
10
+ @click.group("internal")
11
+ def internal_group() -> None:
12
+ """Internal Eledo operational tooling."""
13
+
14
+
15
+ @internal_group.group("docs")
16
+ def internal_docs_group() -> None:
17
+ """Internal documentation synchronization tooling."""
18
+
19
+
20
+ @internal_docs_group.command("sync")
21
+ @click.argument("path", type=click.Path(path_type=Path))
22
+ @click.option(
23
+ "--review-report",
24
+ type=click.Path(path_type=Path, dir_okay=False),
25
+ default=None,
26
+ help="Optional path where manual review report will be written.",
27
+ )
28
+ @click.option("--dry-run", is_flag=True, help="Analyze without uploading changes.")
29
+ def sync_docs(path: Path, review_report: Path | None, dry_run: bool) -> None:
30
+ """Synchronize Git documentation into Eledo CMS.
31
+
32
+ Implementation will be added after the Eledo CMS CRUD API is available.
33
+ """
34
+ click.echo("Documentation sync scaffold is ready, but implementation is pending.")
35
+ click.echo(f"path={path}")
36
+ click.echo(f"review_report={review_report}")
37
+ click.echo(f"dry_run={dry_run}")