cal-docs-client 1.0.0b1__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.
- cal_docs_client/__init__.py +20 -0
- cal_docs_client/__main__.py +21 -0
- cal_docs_client/_version.py +4 -0
- cal_docs_client/argbuilder.py +1424 -0
- cal_docs_client/cli.py +518 -0
- cal_docs_client/client.py +265 -0
- cal_docs_client/common/__init__.py +32 -0
- cal_docs_client/common/colour.py +107 -0
- cal_docs_client/version.py +32 -0
- cal_docs_client-1.0.0b1.dist-info/METADATA +120 -0
- cal_docs_client-1.0.0b1.dist-info/RECORD +14 -0
- cal_docs_client-1.0.0b1.dist-info/WHEEL +4 -0
- cal_docs_client-1.0.0b1.dist-info/entry_points.txt +2 -0
- cal_docs_client-1.0.0b1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
# client.py
|
|
3
|
+
# ─────────
|
|
4
|
+
#
|
|
5
|
+
# HTTP client for cal-docs-server API. Uses only Python stdlib (urllib.request).
|
|
6
|
+
#
|
|
7
|
+
# (c) 2026 Cyber Assessment Labs — MIT License; see LICENSE in the project root.
|
|
8
|
+
#
|
|
9
|
+
# Authors
|
|
10
|
+
# ───────
|
|
11
|
+
# bena (via Claude)
|
|
12
|
+
#
|
|
13
|
+
# Version History
|
|
14
|
+
# ───────────────
|
|
15
|
+
# Feb 2026 - Created
|
|
16
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
# Imports
|
|
20
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import urllib.error
|
|
24
|
+
import urllib.parse
|
|
25
|
+
import urllib.request
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
29
|
+
# Exceptions
|
|
30
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ClientError(Exception):
|
|
34
|
+
"""Error from the API client."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
38
|
+
# Client
|
|
39
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class DocsClient:
|
|
43
|
+
"""HTTP client for cal-docs-server API."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, server_url: str, token: str | None = None) -> None:
|
|
46
|
+
"""Initialize the client.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
server_url: Base URL of the cal-docs-server (e.g., "http://localhost:8080")
|
|
50
|
+
token: Optional API token for authenticated endpoints (upload)
|
|
51
|
+
"""
|
|
52
|
+
self.server_url = server_url.rstrip("/")
|
|
53
|
+
self.token = token
|
|
54
|
+
self._verified = False
|
|
55
|
+
self._server_version: str | None = None
|
|
56
|
+
self._api_version: str | None = None
|
|
57
|
+
|
|
58
|
+
# ────────────────────────────────────────────────────────────────────────────────────
|
|
59
|
+
# HTTP Methods
|
|
60
|
+
# ────────────────────────────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
def _request(
|
|
63
|
+
self,
|
|
64
|
+
method: str,
|
|
65
|
+
path: str,
|
|
66
|
+
data: bytes | None = None,
|
|
67
|
+
headers: dict[str, str] | None = None,
|
|
68
|
+
timeout: float = 30.0,
|
|
69
|
+
) -> bytes:
|
|
70
|
+
"""Make an HTTP request.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
method: HTTP method (GET, POST, etc.)
|
|
74
|
+
path: URL path (e.g., "/api/version")
|
|
75
|
+
data: Optional request body
|
|
76
|
+
headers: Optional headers
|
|
77
|
+
timeout: Request timeout in seconds
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Response body as bytes
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
ClientError: On HTTP or connection error
|
|
84
|
+
"""
|
|
85
|
+
url = f"{self.server_url}{path}"
|
|
86
|
+
req_headers = headers.copy() if headers else {}
|
|
87
|
+
|
|
88
|
+
if self.token:
|
|
89
|
+
req_headers["X-Token"] = self.token
|
|
90
|
+
|
|
91
|
+
request = urllib.request.Request(
|
|
92
|
+
url,
|
|
93
|
+
data=data,
|
|
94
|
+
headers=req_headers,
|
|
95
|
+
method=method,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
with urllib.request.urlopen(request, timeout=timeout) as response:
|
|
100
|
+
return response.read()
|
|
101
|
+
except urllib.error.HTTPError as e:
|
|
102
|
+
body = e.read().decode("utf-8", errors="replace")
|
|
103
|
+
try:
|
|
104
|
+
error_data = json.loads(body)
|
|
105
|
+
message = error_data.get("message", body)
|
|
106
|
+
except json.JSONDecodeError:
|
|
107
|
+
message = body
|
|
108
|
+
raise ClientError(f"HTTP {e.code}: {message}") from e
|
|
109
|
+
except urllib.error.URLError as e:
|
|
110
|
+
raise ClientError(f"Connection failed: {e.reason}") from e
|
|
111
|
+
except TimeoutError as e:
|
|
112
|
+
raise ClientError(f"Request timed out after {timeout}s") from e
|
|
113
|
+
|
|
114
|
+
def _get(self, path: str) -> bytes:
|
|
115
|
+
"""Make a GET request."""
|
|
116
|
+
return self._request("GET", path)
|
|
117
|
+
|
|
118
|
+
def _get_json(self, path: str) -> Any:
|
|
119
|
+
"""Make a GET request and parse JSON response."""
|
|
120
|
+
data = self._get(path)
|
|
121
|
+
try:
|
|
122
|
+
return json.loads(data)
|
|
123
|
+
except json.JSONDecodeError as e:
|
|
124
|
+
raise ClientError(f"Invalid JSON response: {e}") from e
|
|
125
|
+
|
|
126
|
+
def _post(
|
|
127
|
+
self,
|
|
128
|
+
path: str,
|
|
129
|
+
data: bytes,
|
|
130
|
+
content_type: str,
|
|
131
|
+
filename: str | None = None,
|
|
132
|
+
) -> bytes:
|
|
133
|
+
"""Make a POST request."""
|
|
134
|
+
headers = {"Content-Type": content_type}
|
|
135
|
+
if filename:
|
|
136
|
+
headers["X-Filename"] = filename
|
|
137
|
+
return self._request("POST", path, data, headers)
|
|
138
|
+
|
|
139
|
+
# ────────────────────────────────────────────────────────────────────────────────────
|
|
140
|
+
# Server Verification
|
|
141
|
+
# ────────────────────────────────────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
def verify_server(self) -> None:
|
|
144
|
+
"""Verify the server is cal-docs-server with a compatible API version.
|
|
145
|
+
|
|
146
|
+
Accepts API version 1.x, rejects 2.x and above.
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
ClientError: If server is not cal-docs-server or API version is incompatible
|
|
150
|
+
"""
|
|
151
|
+
if self._verified:
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
data = self._get_json("/api/version")
|
|
155
|
+
|
|
156
|
+
product = data.get("product", "")
|
|
157
|
+
if product != "cal-docs-server":
|
|
158
|
+
raise ClientError(
|
|
159
|
+
f"Server is not cal-docs-server (product: {product or 'unknown'})"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
api_version = data.get("apiVersion", "")
|
|
163
|
+
if not api_version:
|
|
164
|
+
raise ClientError("Server did not report an API version")
|
|
165
|
+
|
|
166
|
+
# Parse major version
|
|
167
|
+
try:
|
|
168
|
+
major = int(api_version.split(".")[0])
|
|
169
|
+
except ValueError, IndexError:
|
|
170
|
+
raise ClientError(f"Invalid API version format: {api_version}") from None
|
|
171
|
+
|
|
172
|
+
if major != 1:
|
|
173
|
+
raise ClientError(
|
|
174
|
+
f"Incompatible API version: {api_version} (this client requires 1.x)"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
self._verified = True
|
|
178
|
+
self._server_version = data.get("version", "unknown")
|
|
179
|
+
self._api_version = api_version
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def server_version(self) -> str:
|
|
183
|
+
"""Get the server version (requires verify_server to be called first)."""
|
|
184
|
+
return self._server_version or "unknown"
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def api_version(self) -> str:
|
|
188
|
+
"""Get the API version (requires verify_server to be called first)."""
|
|
189
|
+
return self._api_version or "unknown"
|
|
190
|
+
|
|
191
|
+
# ────────────────────────────────────────────────────────────────────────────────────
|
|
192
|
+
# API Methods
|
|
193
|
+
# ────────────────────────────────────────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
def get_version(self) -> dict[str, str]:
|
|
196
|
+
"""Get server version information.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Dict with 'product', 'version', and 'apiVersion' keys
|
|
200
|
+
"""
|
|
201
|
+
return self._get_json("/api/version")
|
|
202
|
+
|
|
203
|
+
def get_help(self) -> str:
|
|
204
|
+
"""Get the server's API help text.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Plain text API documentation
|
|
208
|
+
"""
|
|
209
|
+
return self._get("/api/help").decode("utf-8")
|
|
210
|
+
|
|
211
|
+
def get_spec(self) -> dict[str, Any]:
|
|
212
|
+
"""Get the OpenAPI specification.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
OpenAPI spec as a dict
|
|
216
|
+
"""
|
|
217
|
+
return self._get_json("/api/spec")
|
|
218
|
+
|
|
219
|
+
def get_projects(self, search: str | None = None) -> dict[str, Any]:
|
|
220
|
+
"""List documentation projects.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
search: Optional search term to filter projects
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Dict with 'projects' list and 'count' key
|
|
227
|
+
"""
|
|
228
|
+
path = "/api/projects"
|
|
229
|
+
if search:
|
|
230
|
+
path = f"{path}?search={urllib.parse.quote(search)}"
|
|
231
|
+
return self._get_json(path)
|
|
232
|
+
|
|
233
|
+
def download(self, project: str, version: str = "latest") -> bytes:
|
|
234
|
+
"""Download a documentation package.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
project: Project name
|
|
238
|
+
version: Version to download (default: "latest")
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Zip file contents as bytes
|
|
242
|
+
"""
|
|
243
|
+
path = (
|
|
244
|
+
f"/api/download/{urllib.parse.quote(project)}/{urllib.parse.quote(version)}"
|
|
245
|
+
)
|
|
246
|
+
return self._get(path)
|
|
247
|
+
|
|
248
|
+
def upload(self, filename: str, data: bytes) -> dict[str, Any]:
|
|
249
|
+
"""Upload a documentation package.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
filename: Name of the zip file (e.g., "myproject-1.0.0-docs.zip")
|
|
253
|
+
data: Zip file contents
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Server response with upload details
|
|
257
|
+
|
|
258
|
+
Raises:
|
|
259
|
+
ClientError: If upload fails (auth error, invalid file, etc.)
|
|
260
|
+
"""
|
|
261
|
+
response = self._post("/api/upload", data, "application/zip", filename)
|
|
262
|
+
try:
|
|
263
|
+
return json.loads(response)
|
|
264
|
+
except json.JSONDecodeError as e:
|
|
265
|
+
raise ClientError(f"Invalid JSON response: {e}") from e
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
# common/__init__.py
|
|
3
|
+
# ──────────────────
|
|
4
|
+
#
|
|
5
|
+
# Common utilities for cal-docs-client.
|
|
6
|
+
#
|
|
7
|
+
# (c) 2026 Cyber Assessment Labs — MIT License; see LICENSE in the project root.
|
|
8
|
+
#
|
|
9
|
+
# Authors
|
|
10
|
+
# ───────
|
|
11
|
+
# bena (via Claude)
|
|
12
|
+
#
|
|
13
|
+
# Version History
|
|
14
|
+
# ───────────────
|
|
15
|
+
# Feb 2026 - Created
|
|
16
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
from .colour import bold
|
|
19
|
+
from .colour import cyan
|
|
20
|
+
from .colour import green
|
|
21
|
+
from .colour import red
|
|
22
|
+
from .colour import set_colours_enabled
|
|
23
|
+
from .colour import yellow
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"bold",
|
|
27
|
+
"cyan",
|
|
28
|
+
"green",
|
|
29
|
+
"red",
|
|
30
|
+
"set_colours_enabled",
|
|
31
|
+
"yellow",
|
|
32
|
+
]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
# colour.py
|
|
3
|
+
# ─────────
|
|
4
|
+
#
|
|
5
|
+
# ANSI colour output for terminal. Only applies colours when stdout is a TTY
|
|
6
|
+
# and colours are not disabled.
|
|
7
|
+
#
|
|
8
|
+
# Uses colours that work well on both light and dark backgrounds.
|
|
9
|
+
#
|
|
10
|
+
# (c) 2026 Cyber Assessment Labs — MIT License; see LICENSE in the project root.
|
|
11
|
+
#
|
|
12
|
+
# Authors
|
|
13
|
+
# ───────
|
|
14
|
+
# bena (via Claude)
|
|
15
|
+
#
|
|
16
|
+
# Version History
|
|
17
|
+
# ───────────────
|
|
18
|
+
# Feb 2026 - Created
|
|
19
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
# Imports
|
|
23
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
import sys
|
|
26
|
+
|
|
27
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
# ANSI Codes
|
|
29
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
# These colours work well on both light and dark backgrounds
|
|
32
|
+
RESET = "\033[0m"
|
|
33
|
+
BOLD = "\033[1m"
|
|
34
|
+
GREEN = "\033[32m"
|
|
35
|
+
YELLOW = "\033[33m"
|
|
36
|
+
RED = "\033[31m"
|
|
37
|
+
CYAN = "\033[36m"
|
|
38
|
+
|
|
39
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
40
|
+
# State
|
|
41
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
_colours_enabled: bool | None = None
|
|
44
|
+
|
|
45
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
46
|
+
# Functions
|
|
47
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
51
|
+
def set_colours_enabled(enabled: bool) -> None:
|
|
52
|
+
"""Explicitly enable or disable colours."""
|
|
53
|
+
global _colours_enabled
|
|
54
|
+
_colours_enabled = enabled
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
58
|
+
def _should_use_colours() -> bool:
|
|
59
|
+
"""Determine if colours should be used."""
|
|
60
|
+
global _colours_enabled
|
|
61
|
+
|
|
62
|
+
# If explicitly set, use that
|
|
63
|
+
if _colours_enabled is not None:
|
|
64
|
+
return _colours_enabled
|
|
65
|
+
|
|
66
|
+
# Auto-detect: use colours if stdout is a TTY
|
|
67
|
+
return sys.stdout.isatty()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
71
|
+
def green(text: str) -> str:
|
|
72
|
+
"""Return text in green (for success)."""
|
|
73
|
+
if _should_use_colours():
|
|
74
|
+
return f"{GREEN}{text}{RESET}"
|
|
75
|
+
return text
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
79
|
+
def yellow(text: str) -> str:
|
|
80
|
+
"""Return text in yellow (for warning)."""
|
|
81
|
+
if _should_use_colours():
|
|
82
|
+
return f"{YELLOW}{text}{RESET}"
|
|
83
|
+
return text
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
87
|
+
def red(text: str) -> str:
|
|
88
|
+
"""Return text in red (for error)."""
|
|
89
|
+
if _should_use_colours():
|
|
90
|
+
return f"{RED}{text}{RESET}"
|
|
91
|
+
return text
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
95
|
+
def cyan(text: str) -> str:
|
|
96
|
+
"""Return text in cyan (for info)."""
|
|
97
|
+
if _should_use_colours():
|
|
98
|
+
return f"{CYAN}{text}{RESET}"
|
|
99
|
+
return text
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
103
|
+
def bold(text: str) -> str:
|
|
104
|
+
"""Return text in bold."""
|
|
105
|
+
if _should_use_colours():
|
|
106
|
+
return f"{BOLD}{text}{RESET}"
|
|
107
|
+
return text
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
# version.py
|
|
3
|
+
# ──────────
|
|
4
|
+
#
|
|
5
|
+
# Version handling for cal-docs-client.
|
|
6
|
+
#
|
|
7
|
+
# (c) 2026 Cyber Assessment Labs — MIT License; see LICENSE in the project root.
|
|
8
|
+
#
|
|
9
|
+
# Authors
|
|
10
|
+
# ───────
|
|
11
|
+
# bena (via Claude)
|
|
12
|
+
#
|
|
13
|
+
# Version History
|
|
14
|
+
# ───────────────
|
|
15
|
+
# Feb 2026 - Created
|
|
16
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ────────────────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
def _get_version() -> str:
|
|
21
|
+
"""Get the version string from the generated _version.py or return DEV."""
|
|
22
|
+
try:
|
|
23
|
+
# fmt: off
|
|
24
|
+
from ._version import __version__ as _v # pyright: ignore[reportMissingImports,reportUnknownVariableType] # noqa: I001
|
|
25
|
+
# fmt: on
|
|
26
|
+
|
|
27
|
+
return str(_v) # pyright: ignore[reportUnknownArgumentType]
|
|
28
|
+
except ImportError:
|
|
29
|
+
return "DEV"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
VERSION_STR: str = _get_version()
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cal-docs-client
|
|
3
|
+
Version: 1.0.0b1
|
|
4
|
+
Summary: CLI client for cal-docs-server documentation API
|
|
5
|
+
Project-URL: Repository, https://gitlab.com/cyberassessmentlabs/public/tools/cal-docs-client
|
|
6
|
+
Project-URL: Documentation, https://cyberassessmentlabs.gitlab.io/public/docs/cal-docs-client/latest
|
|
7
|
+
Author: Cyber Assessment Labs
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: api,cli,client,docs,documentation
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Requires-Python: >=3.14
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# cal-docs-client
|
|
22
|
+
|
|
23
|
+
CLI client for [cal-docs-server](https://gitlab.com/cyberassessmentlabs/public/tools/cal-docs-server) documentation API.
|
|
24
|
+
|
|
25
|
+
## Requirements
|
|
26
|
+
|
|
27
|
+
- Python 3.14+
|
|
28
|
+
- No external dependencies (stdlib only)
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install cal-docs-client
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or install from source:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git clone https://gitlab.com/cyberassessmentlabs/public/tools/cal-docs-client.git
|
|
40
|
+
cd cal-docs-client
|
|
41
|
+
pip install .
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Configuration
|
|
45
|
+
|
|
46
|
+
Configuration is optional. You can provide settings via command-line options, environment variables, or a config file.
|
|
47
|
+
|
|
48
|
+
### Config File (Optional)
|
|
49
|
+
|
|
50
|
+
For convenience, create a config file at `~/.config/cal-docs-client/config.json`:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"server": "https://docs.example.com",
|
|
55
|
+
"token": "your-api-token",
|
|
56
|
+
"no_colour": false
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
You can also specify a different config file with `-c`/`--config`.
|
|
61
|
+
|
|
62
|
+
### Configuration Priority
|
|
63
|
+
|
|
64
|
+
Settings are resolved in this order (first wins):
|
|
65
|
+
1. Command-line arguments (`-s`, `--no-colour`)
|
|
66
|
+
2. Environment variables (`CAL_DOCS_SERVER`, `CAL_DOCS_TOKEN`)
|
|
67
|
+
3. Config file
|
|
68
|
+
|
|
69
|
+
## Usage
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Show help
|
|
73
|
+
cal-docs-client --help
|
|
74
|
+
|
|
75
|
+
# Show server and client version
|
|
76
|
+
cal-docs-client version
|
|
77
|
+
|
|
78
|
+
# List all projects
|
|
79
|
+
cal-docs-client projects
|
|
80
|
+
|
|
81
|
+
# Search for projects
|
|
82
|
+
cal-docs-client projects --search myproject
|
|
83
|
+
|
|
84
|
+
# Download documentation
|
|
85
|
+
cal-docs-client download myproject latest
|
|
86
|
+
cal-docs-client download myproject 1.0.0 -o docs.zip
|
|
87
|
+
|
|
88
|
+
# Upload documentation (requires token in config or -t)
|
|
89
|
+
cal-docs-client upload myproject-1.0.0-docs.zip
|
|
90
|
+
|
|
91
|
+
# Show server API help
|
|
92
|
+
cal-docs-client help
|
|
93
|
+
|
|
94
|
+
# Get OpenAPI specification
|
|
95
|
+
cal-docs-client spec -o openapi.json
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Command-Line Options
|
|
99
|
+
|
|
100
|
+
You can pass all settings directly on the command line:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
cal-docs-client -s https://docs.example.com projects
|
|
104
|
+
cal-docs-client -s https://docs.example.com upload -t YOUR_TOKEN docs.zip
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Environment Variables
|
|
108
|
+
|
|
109
|
+
You can also use environment variables:
|
|
110
|
+
|
|
111
|
+
- `CAL_DOCS_SERVER` - Server URL
|
|
112
|
+
- `CAL_DOCS_TOKEN` - Authentication token
|
|
113
|
+
|
|
114
|
+
## API Version Compatibility
|
|
115
|
+
|
|
116
|
+
This client requires cal-docs-server API version 1.x. It will refuse to connect to servers with incompatible API versions.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT License - (c) 2026 Cyber Assessment Labs
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
cal_docs_client/__init__.py,sha256=-bKdz7ZdnfWxn04KMgNzpwUdxcKqRcR8DpcNM0NTBQA,929
|
|
2
|
+
cal_docs_client/__main__.py,sha256=fuTDiIx-veSOTEVhxxyKG3poYiQ2rk87Ko2Wwvpjj-4,965
|
|
3
|
+
cal_docs_client/_version.py,sha256=JQkLQAME1HDp3QVEh9RrTjuwWvqAl4Ya_t--FXT4jms,173
|
|
4
|
+
cal_docs_client/argbuilder.py,sha256=5cWtNrndIgp31hT_Za6pKQ60CBFtOdaLrPakjlDR9zA,64965
|
|
5
|
+
cal_docs_client/cli.py,sha256=ahGVrLz2LXYsPwKP-hA46hadoI77AOX1UV8G3WVNQ5Q,16895
|
|
6
|
+
cal_docs_client/client.py,sha256=kqSrGUF6RPUXQ_FX4pzf6pv4n3vICQZKjFpPOl4_DqU,11089
|
|
7
|
+
cal_docs_client/version.py,sha256=265arrq2YMLxGA3YJqtnoqiTSgY8LQBJJsu0XAmbHJ8,1573
|
|
8
|
+
cal_docs_client/common/__init__.py,sha256=NtGfBqAAZKrZ1Il4ceHIlOxIZtybV5ApBeZDBsw6D_Q,1176
|
|
9
|
+
cal_docs_client/common/colour.py,sha256=yIU4xb9HHXQSSMdZrJUyfTpij58CUHgO4YLsWVzVAn8,6527
|
|
10
|
+
cal_docs_client-1.0.0b1.dist-info/METADATA,sha256=R8lAKPMiVYpbSXvoV75dxeLz4TsyBpWEk6mIDs8zl8s,3022
|
|
11
|
+
cal_docs_client-1.0.0b1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
12
|
+
cal_docs_client-1.0.0b1.dist-info/entry_points.txt,sha256=IYL7hCO3QJ6Idq8_zAplfnRmoaMOaCNzg-3y4T_dQYs,61
|
|
13
|
+
cal_docs_client-1.0.0b1.dist-info/licenses/LICENSE,sha256=zIXdXMPhkY8xLlrhw7lOsWiFOecEYRukxgZIjMTKPuE,1078
|
|
14
|
+
cal_docs_client-1.0.0b1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cyber Assessment Labs
|
|
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.
|