asp-cli 0.1.0__tar.gz → 0.5.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.
@@ -1,40 +1,40 @@
1
- # Python
2
- __pycache__/
3
- *.py[cod]
4
- *.egg-info/
5
- dist/
6
- build/
7
- *.egg
8
-
9
- # Virtual environment
10
- .venv/
11
-
12
- # Django
13
- *.sqlite3
14
- media/
15
- staticfiles/
16
-
17
- # Environment
18
- .env
19
- .env.local
20
-
21
- # IDE
22
- .idea/
23
- .vscode/
24
- *.swp
25
- *.swo
26
-
27
- # Node
28
- node_modules/
29
-
30
- # Local git worktrees
31
- .worktrees/
32
- /asf-doc/
33
- /asp-marketplace/
34
-
35
- # OS
36
- .DS_Store
37
- Thumbs.db
38
-
39
- .claude/*
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ *.egg
8
+
9
+ # Virtual environment
10
+ .venv/
11
+
12
+ # Django
13
+ *.sqlite3
14
+ media/
15
+ staticfiles/
16
+
17
+ # Environment
18
+ .env
19
+ .env.local
20
+
21
+ # IDE
22
+ .idea/
23
+ .vscode/
24
+ *.swp
25
+ *.swo
26
+
27
+ # Node
28
+ node_modules/
29
+
30
+ # Local git worktrees
31
+ .worktrees/
32
+ #/asf-doc/
33
+ #/asp-marketplace/
34
+
35
+ # OS
36
+ .DS_Store
37
+ Thumbs.db
38
+
39
+ .claude/*
40
40
  .asp/
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Agentic SOC Platform contributors
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.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Agentic SOC Platform contributors
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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asp-cli
3
- Version: 0.1.0
3
+ Version: 0.5.0
4
4
  Summary: Command line client for Agentic SOC Platform
5
5
  Project-URL: Homepage, https://github.com/FunnyWolf/agentic-soc-platform
6
6
  Project-URL: Repository, https://github.com/FunnyWolf/agentic-soc-platform
@@ -47,8 +47,55 @@ asp doctor
47
47
  asp case list
48
48
  ```
49
49
 
50
- For automation and agent skills, prefer stable JSON output:
50
+ `asp auth login` verifies the API URL and API key against the ASP server before writing local settings.
51
+
52
+ For automation and skills, prefer stable JSON output:
51
53
 
52
54
  ```powershell
53
55
  asp case list --output json
54
56
  ```
57
+
58
+ ## Publish to PyPI
59
+
60
+ The GitHub release workflow publishes `asp-cli` to PyPI automatically when a `v<version>` tag is pushed. The CLI package version does not include the leading `v` and must match the main release version, so update `version` in `pyproject.toml` before creating the tag.
61
+
62
+ For example, release tag `v0.1.0` publishes PyPI version `0.1.0`.
63
+
64
+ ### Automatic publishing
65
+
66
+ The workflow uses PyPI Trusted Publishing, so it does not need a PyPI API token in the repository or GitHub Secrets. Configure PyPI once with a trusted publisher:
67
+
68
+ - PyPI project: `asp-cli`
69
+ - Owner/repository: `FunnyWolf/agentic-soc-platform`
70
+ - Workflow: `release.yml`
71
+ - Environment: `pypi`
72
+
73
+ If the PyPI project does not exist yet, add the same entry under PyPI's pending publishers before the first release.
74
+
75
+ Release steps:
76
+
77
+ ```powershell
78
+ # Update cli\pyproject.toml first, for example: version = "0.1.0"
79
+ git tag v0.1.0
80
+ git push origin v0.1.0
81
+ ```
82
+
83
+ ### Manual fallback
84
+
85
+ Manual publishing requires a PyPI API token. Keep it local and do not commit it:
86
+
87
+ ```powershell
88
+ cd cli
89
+ Remove-Item -Recurse -Force dist -ErrorAction SilentlyContinue
90
+ uv build
91
+ uvx twine check dist\*
92
+ $env:UV_PUBLISH_TOKEN = "pypi-..."
93
+ uv publish --token $env:UV_PUBLISH_TOKEN
94
+ ```
95
+
96
+ After publishing, verify the package can be installed:
97
+
98
+ ```powershell
99
+ pipx install --force asp-cli
100
+ asp --version
101
+ ```
@@ -0,0 +1,72 @@
1
+ # ASP CLI
2
+
3
+ Command line client for Agentic SOC Platform.
4
+
5
+ `asp-cli` provides the `asp` command for SOC analysts and automation agents to authenticate with an ASP server, inspect cases and alerts, add comments, upload files, run playbooks, and query investigation integrations.
6
+
7
+ ## Install
8
+
9
+ ```powershell
10
+ pipx install asp-cli
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```powershell
16
+ asp auth login --api-url https://asp.example.com --api-key asp_xxx
17
+ asp doctor
18
+ asp case list
19
+ ```
20
+
21
+ `asp auth login` verifies the API URL and API key against the ASP server before writing local settings.
22
+
23
+ For automation and skills, prefer stable JSON output:
24
+
25
+ ```powershell
26
+ asp case list --output json
27
+ ```
28
+
29
+ ## Publish to PyPI
30
+
31
+ The GitHub release workflow publishes `asp-cli` to PyPI automatically when a `v<version>` tag is pushed. The CLI package version does not include the leading `v` and must match the main release version, so update `version` in `pyproject.toml` before creating the tag.
32
+
33
+ For example, release tag `v0.1.0` publishes PyPI version `0.1.0`.
34
+
35
+ ### Automatic publishing
36
+
37
+ The workflow uses PyPI Trusted Publishing, so it does not need a PyPI API token in the repository or GitHub Secrets. Configure PyPI once with a trusted publisher:
38
+
39
+ - PyPI project: `asp-cli`
40
+ - Owner/repository: `FunnyWolf/agentic-soc-platform`
41
+ - Workflow: `release.yml`
42
+ - Environment: `pypi`
43
+
44
+ If the PyPI project does not exist yet, add the same entry under PyPI's pending publishers before the first release.
45
+
46
+ Release steps:
47
+
48
+ ```powershell
49
+ # Update cli\pyproject.toml first, for example: version = "0.1.0"
50
+ git tag v0.1.0
51
+ git push origin v0.1.0
52
+ ```
53
+
54
+ ### Manual fallback
55
+
56
+ Manual publishing requires a PyPI API token. Keep it local and do not commit it:
57
+
58
+ ```powershell
59
+ cd cli
60
+ Remove-Item -Recurse -Force dist -ErrorAction SilentlyContinue
61
+ uv build
62
+ uvx twine check dist\*
63
+ $env:UV_PUBLISH_TOKEN = "pypi-..."
64
+ uv publish --token $env:UV_PUBLISH_TOKEN
65
+ ```
66
+
67
+ After publishing, verify the package can be installed:
68
+
69
+ ```powershell
70
+ pipx install --force asp-cli
71
+ asp --version
72
+ ```
@@ -1,48 +1,48 @@
1
- [project]
2
- name = "asp-cli"
3
- version = "0.1.0"
4
- description = "Command line client for Agentic SOC Platform"
5
- readme = "README.md"
6
- requires-python = ">=3.11"
7
- license = "MIT"
8
- authors = [
9
- { name = "Agentic SOC Platform contributors" },
10
- ]
11
- keywords = ["agentic-soc", "soc", "security", "cli"]
12
- classifiers = [
13
- "Development Status :: 3 - Alpha",
14
- "Environment :: Console",
15
- "Intended Audience :: Information Technology",
16
- "License :: OSI Approved :: MIT License",
17
- "Operating System :: OS Independent",
18
- "Programming Language :: Python :: 3",
19
- "Programming Language :: Python :: 3.11",
20
- "Programming Language :: Python :: 3.12",
21
- "Programming Language :: Python :: 3.13",
22
- "Topic :: Security",
23
- ]
24
- dependencies = [
25
- "httpx>=0.28.1",
26
- "jmespath>=1.0.1",
27
- "pydantic>=2.13.4",
28
- "rich>=14.2.0",
29
- "typer>=0.20.0",
30
- ]
31
-
32
- [project.urls]
33
- Homepage = "https://github.com/FunnyWolf/agentic-soc-platform"
34
- Repository = "https://github.com/FunnyWolf/agentic-soc-platform"
35
- Issues = "https://github.com/FunnyWolf/agentic-soc-platform/issues"
36
-
37
- [project.scripts]
38
- asp = "asp_cli.main:run"
39
-
40
- [build-system]
41
- requires = ["hatchling>=1.28"]
42
- build-backend = "hatchling.build"
43
-
44
- [tool.hatch.build.targets.wheel]
45
- packages = ["src/asp_cli"]
46
-
47
- [tool.hatch.build.targets.wheel.force-include]
48
- "src/asp_cli/spec/operations.json" = "asp_cli/spec/operations.json"
1
+ [project]
2
+ name = "asp-cli"
3
+ version = "0.5.0"
4
+ description = "Command line client for Agentic SOC Platform"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ authors = [
9
+ { name = "Agentic SOC Platform contributors" },
10
+ ]
11
+ keywords = ["agentic-soc", "soc", "security", "cli"]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Environment :: Console",
15
+ "Intended Audience :: Information Technology",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Topic :: Security",
23
+ ]
24
+ dependencies = [
25
+ "httpx>=0.28.1",
26
+ "jmespath>=1.0.1",
27
+ "pydantic>=2.13.4",
28
+ "rich>=14.2.0",
29
+ "typer>=0.20.0",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/FunnyWolf/agentic-soc-platform"
34
+ Repository = "https://github.com/FunnyWolf/agentic-soc-platform"
35
+ Issues = "https://github.com/FunnyWolf/agentic-soc-platform/issues"
36
+
37
+ [project.scripts]
38
+ asp = "asp_cli.main:run"
39
+
40
+ [build-system]
41
+ requires = ["hatchling>=1.28"]
42
+ build-backend = "hatchling.build"
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/asp_cli"]
46
+
47
+ [tool.hatch.build.targets.wheel.force-include]
48
+ "src/asp_cli/spec/operations.json" = "asp_cli/spec/operations.json"
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ import tomllib
4
+ from importlib.metadata import PackageNotFoundError, version
5
+ from pathlib import Path
6
+
7
+
8
+ def _source_version() -> str | None:
9
+ pyproject_path = Path(__file__).resolve().parents[2] / "pyproject.toml"
10
+ if not pyproject_path.exists():
11
+ return None
12
+
13
+ pyproject = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))
14
+ project_version = pyproject.get("project", {}).get("version")
15
+ if not isinstance(project_version, str):
16
+ raise RuntimeError("Missing project.version in cli pyproject.toml")
17
+ return project_version
18
+
19
+
20
+ try:
21
+ __version__ = _source_version() or version("asp-cli")
22
+ except PackageNotFoundError as exc:
23
+ raise RuntimeError("Cannot resolve asp-cli package version") from exc
@@ -1,118 +1,118 @@
1
- from __future__ import annotations
2
-
3
- import time
4
- from typing import Any
5
-
6
- import httpx
7
- from rich.console import Console
8
-
9
- from . import __version__
10
- from .config import redact_secret
11
- from .errors import (
12
- CliError,
13
- EXIT_AUTH,
14
- EXIT_NETWORK,
15
- EXIT_NOT_FOUND,
16
- EXIT_PERMISSION,
17
- EXIT_SERVER,
18
- EXIT_USAGE,
19
- )
20
-
21
-
22
- class AspClient:
23
- def __init__(
24
- self,
25
- *,
26
- api_url: str,
27
- api_key: str | None = None,
28
- verbose: bool = False,
29
- console: Console | None = None,
30
- timeout: float = 20.0,
31
- ) -> None:
32
- self.base_url = _normalize_base_url(api_url)
33
- self.api_key = api_key
34
- self.verbose = verbose
35
- self.console = console or Console(stderr=True)
36
- self.timeout = timeout
37
-
38
- def health(self) -> dict[str, Any]:
39
- return self.request("GET", "/api/health/", authenticated=False)
40
-
41
- def version(self) -> dict[str, Any]:
42
- return self.request("GET", "/api/agent/v1/version/")
43
-
44
- def request(self, method: str, path: str, *, authenticated: bool = True, json: Any = None, files: Any = None) -> dict[str, Any]:
45
- if authenticated and not self.api_key:
46
- raise CliError("missing_api_key", "API key is required", {}, EXIT_AUTH)
47
-
48
- headers = {
49
- "Accept": "application/json",
50
- "User-Agent": f"asp-cli/{__version__}",
51
- }
52
- if authenticated and self.api_key:
53
- headers["Authorization"] = f"Api-Key {self.api_key}"
54
-
55
- url = f"{self.base_url}{path}"
56
- started = time.perf_counter()
57
- try:
58
- response = httpx.request(method, url, headers=headers, json=json, files=files, timeout=self.timeout)
59
- except httpx.HTTPError as exc:
60
- raise CliError("network_error", f"Unable to reach ASP server: {exc}", {"url": _redact_url(url)}, EXIT_NETWORK) from exc
61
-
62
- elapsed_ms = int((time.perf_counter() - started) * 1000)
63
- if self.verbose:
64
- self.console.print(f"{method} {path} -> {response.status_code} ({elapsed_ms}ms)", style="dim")
65
-
66
- if response.status_code >= 400:
67
- self._raise_http_error(response, path)
68
-
69
- if not response.content:
70
- return {}
71
- try:
72
- payload = response.json()
73
- except ValueError as exc:
74
- raise CliError("invalid_response", "Server returned non-JSON response", {"status_code": response.status_code}, EXIT_SERVER) from exc
75
- if not isinstance(payload, dict):
76
- raise CliError("invalid_response", "Server response must be a JSON object", {"status_code": response.status_code}, EXIT_SERVER)
77
- return payload
78
-
79
- def _raise_http_error(self, response: httpx.Response, path: str) -> None:
80
- message = _response_message(response)
81
- details = {"status_code": response.status_code, "path": path}
82
- if response.status_code == 400:
83
- raise CliError("bad_request", message, details, EXIT_USAGE)
84
- if response.status_code == 401:
85
- raise CliError("authentication_failed", message, details, EXIT_AUTH)
86
- if response.status_code == 403:
87
- raise CliError("permission_denied", message, details, EXIT_PERMISSION)
88
- if response.status_code == 404:
89
- raise CliError("not_found", message, details, EXIT_NOT_FOUND)
90
- raise CliError("server_error", message, details, EXIT_SERVER)
91
-
92
-
93
- def _normalize_base_url(api_url: str) -> str:
94
- base = api_url.strip().rstrip("/")
95
- if base.endswith("/api"):
96
- base = base[:-4]
97
- if not base:
98
- raise CliError("missing_api_url", "ASP API URL is required", {}, EXIT_USAGE)
99
- return base
100
-
101
-
102
- def _response_message(response: httpx.Response) -> str:
103
- try:
104
- payload = response.json()
105
- except ValueError:
106
- return response.text.strip() or f"HTTP {response.status_code}"
107
- if isinstance(payload, dict):
108
- detail = payload.get("detail")
109
- if isinstance(detail, str):
110
- return detail
111
- error = payload.get("error")
112
- if isinstance(error, dict) and isinstance(error.get("message"), str):
113
- return error["message"]
114
- return f"HTTP {response.status_code}"
115
-
116
-
117
- def _redact_url(url: str) -> str:
118
- return url.replace(redact_secret(url), "****") if "asp_" in url else url
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from typing import Any
5
+
6
+ import httpx
7
+ from rich.console import Console
8
+
9
+ from . import __version__
10
+ from .config import redact_secret
11
+ from .errors import (
12
+ CliError,
13
+ EXIT_AUTH,
14
+ EXIT_NETWORK,
15
+ EXIT_NOT_FOUND,
16
+ EXIT_PERMISSION,
17
+ EXIT_SERVER,
18
+ EXIT_USAGE,
19
+ )
20
+
21
+
22
+ class AspClient:
23
+ def __init__(
24
+ self,
25
+ *,
26
+ api_url: str,
27
+ api_key: str | None = None,
28
+ verbose: bool = False,
29
+ console: Console | None = None,
30
+ timeout: float = 20.0,
31
+ ) -> None:
32
+ self.base_url = _normalize_base_url(api_url)
33
+ self.api_key = api_key
34
+ self.verbose = verbose
35
+ self.console = console or Console(stderr=True)
36
+ self.timeout = timeout
37
+
38
+ def health(self) -> dict[str, Any]:
39
+ return self.request("GET", "/api/health/", authenticated=False)
40
+
41
+ def version(self) -> dict[str, Any]:
42
+ return self.request("GET", "/api/agent/v1/version/")
43
+
44
+ def request(self, method: str, path: str, *, authenticated: bool = True, json: Any = None, files: Any = None) -> dict[str, Any]:
45
+ if authenticated and not self.api_key:
46
+ raise CliError("missing_api_key", "API key is required", {}, EXIT_AUTH)
47
+
48
+ headers = {
49
+ "Accept": "application/json",
50
+ "User-Agent": f"asp-cli/{__version__}",
51
+ }
52
+ if authenticated and self.api_key:
53
+ headers["Authorization"] = f"Api-Key {self.api_key}"
54
+
55
+ url = f"{self.base_url}{path}"
56
+ started = time.perf_counter()
57
+ try:
58
+ response = httpx.request(method, url, headers=headers, json=json, files=files, timeout=self.timeout)
59
+ except httpx.HTTPError as exc:
60
+ raise CliError("network_error", f"Unable to reach ASP server: {exc}", {"url": _redact_url(url)}, EXIT_NETWORK) from exc
61
+
62
+ elapsed_ms = int((time.perf_counter() - started) * 1000)
63
+ if self.verbose:
64
+ self.console.print(f"{method} {path} -> {response.status_code} ({elapsed_ms}ms)", style="dim")
65
+
66
+ if response.status_code >= 400:
67
+ self._raise_http_error(response, path)
68
+
69
+ if not response.content:
70
+ return {}
71
+ try:
72
+ payload = response.json()
73
+ except ValueError as exc:
74
+ raise CliError("invalid_response", "Server returned non-JSON response", {"status_code": response.status_code}, EXIT_SERVER) from exc
75
+ if not isinstance(payload, dict):
76
+ raise CliError("invalid_response", "Server response must be a JSON object", {"status_code": response.status_code}, EXIT_SERVER)
77
+ return payload
78
+
79
+ def _raise_http_error(self, response: httpx.Response, path: str) -> None:
80
+ message = _response_message(response)
81
+ details = {"status_code": response.status_code, "path": path}
82
+ if response.status_code == 400:
83
+ raise CliError("bad_request", message, details, EXIT_USAGE)
84
+ if response.status_code == 401:
85
+ raise CliError("authentication_failed", message, details, EXIT_AUTH)
86
+ if response.status_code == 403:
87
+ raise CliError("permission_denied", message, details, EXIT_PERMISSION)
88
+ if response.status_code == 404:
89
+ raise CliError("not_found", message, details, EXIT_NOT_FOUND)
90
+ raise CliError("server_error", message, details, EXIT_SERVER)
91
+
92
+
93
+ def _normalize_base_url(api_url: str) -> str:
94
+ base = api_url.strip().rstrip("/")
95
+ if base.endswith("/api"):
96
+ base = base[:-4]
97
+ if not base:
98
+ raise CliError("missing_api_url", "ASP API URL is required", {}, EXIT_USAGE)
99
+ return base
100
+
101
+
102
+ def _response_message(response: httpx.Response) -> str:
103
+ try:
104
+ payload = response.json()
105
+ except ValueError:
106
+ return response.text.strip() or f"HTTP {response.status_code}"
107
+ if isinstance(payload, dict):
108
+ detail = payload.get("detail")
109
+ if isinstance(detail, str):
110
+ return detail
111
+ error = payload.get("error")
112
+ if isinstance(error, dict) and isinstance(error.get("message"), str):
113
+ return error["message"]
114
+ return f"HTTP {response.status_code}"
115
+
116
+
117
+ def _redact_url(url: str) -> str:
118
+ return url.replace(redact_secret(url), "****") if "asp_" in url else url