apache-aiops 0.2.0__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.
- apache_aiops/__init__.py +8 -0
- apache_aiops/cli/__init__.py +9 -0
- apache_aiops/cli/_common.py +118 -0
- apache_aiops/cli/_root.py +206 -0
- apache_aiops/cli/undo.py +66 -0
- apache_aiops/config.py +74 -0
- apache_aiops/connection.py +222 -0
- apache_aiops/governance/__init__.py +50 -0
- apache_aiops/governance/audit.py +526 -0
- apache_aiops/governance/budget.py +222 -0
- apache_aiops/governance/decorators.py +599 -0
- apache_aiops/governance/outcome.py +102 -0
- apache_aiops/governance/paths.py +23 -0
- apache_aiops/governance/patterns.py +396 -0
- apache_aiops/governance/policy.py +67 -0
- apache_aiops/governance/sanitize.py +79 -0
- apache_aiops/governance/undo.py +259 -0
- apache_aiops/mcp_server/__init__.py +1 -0
- apache_aiops/mcp_server/_annotations.py +66 -0
- apache_aiops/mcp_server/_shared.py +67 -0
- apache_aiops/mcp_server/server.py +14 -0
- apache_aiops/mcp_server/tools/__init__.py +1 -0
- apache_aiops/mcp_server/tools/httpd.py +143 -0
- apache_aiops/mcp_server/tools/undo.py +204 -0
- apache_aiops/ops/__init__.py +1 -0
- apache_aiops/ops/httpd.py +81 -0
- apache_aiops-0.2.0.dist-info/METADATA +50 -0
- apache_aiops-0.2.0.dist-info/RECORD +31 -0
- apache_aiops-0.2.0.dist-info/WHEEL +4 -0
- apache_aiops-0.2.0.dist-info/entry_points.txt +3 -0
- apache_aiops-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Undo executor — list recorded inverse descriptors and APPLY them.
|
|
2
|
+
|
|
3
|
+
Every governed write records a replayable inverse descriptor (tool + params) to
|
|
4
|
+
``undo.db``. This module closes the loop: ``undo_apply`` looks up a recorded
|
|
5
|
+
descriptor and dispatches it to the named governed tool, so the inverse runs on
|
|
6
|
+
the SAME governance path as any other call (audited and budget-checked, and — if
|
|
7
|
+
the inverse itself is destructive — recorded under its own risk tier). Whether
|
|
8
|
+
the inverse *should* run is the caller's decision, exactly as for the original
|
|
9
|
+
write; ``undo_apply`` records it either way.
|
|
10
|
+
|
|
11
|
+
Note: recorded ``undo_params`` are the redacted safe-params captured at record
|
|
12
|
+
time, so an inverse that would need a secret value cannot be replayed here — by
|
|
13
|
+
design, inverses in this line key off ids/names, not credentials.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
from typing import Any, Optional
|
|
18
|
+
|
|
19
|
+
from apache_aiops.governance import governed_tool
|
|
20
|
+
from apache_aiops.governance.undo import get_undo_store
|
|
21
|
+
from apache_aiops.mcp_server._shared import mcp, tool_errors
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _resolve_tool(name: str) -> Any:
|
|
25
|
+
"""Return the governed callable registered under ``name`` (or None).
|
|
26
|
+
|
|
27
|
+
The CLI reaches this through ``undo apply``, whose process only imports
|
|
28
|
+
``apache_aiops.mcp_server.tools.undo`` — every other tool module is imported lazily inside
|
|
29
|
+
its own CLI command and so is absent here. Without forcing a full load,
|
|
30
|
+
``undo_apply`` would fail with "inverse tool not registered" for every write
|
|
31
|
+
tool when driven from the CLI (the MCP entry point loads all modules, so it
|
|
32
|
+
never hit this). If the first lookup misses, import ``apache_aiops.mcp_server.server`` —
|
|
33
|
+
which registers every ``@mcp.tool()`` as an import side effect — and retry.
|
|
34
|
+
Imported lazily to avoid the undo↔server cycle.
|
|
35
|
+
"""
|
|
36
|
+
tool = mcp._tool_manager._tools.get(name)
|
|
37
|
+
if tool is None:
|
|
38
|
+
import apache_aiops.mcp_server.server # noqa: F401 — registers all tools as a side effect
|
|
39
|
+
|
|
40
|
+
tool = mcp._tool_manager._tools.get(name)
|
|
41
|
+
return getattr(tool, "fn", None) if tool else None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@mcp.tool()
|
|
45
|
+
@governed_tool(risk_level="low")
|
|
46
|
+
@tool_errors("dict")
|
|
47
|
+
def undo_list(limit: int = 50, target: Optional[str] = None) -> dict:
|
|
48
|
+
"""[READ] List recorded, not-yet-applied undo tokens (most recent first).
|
|
49
|
+
|
|
50
|
+
Each entry names the original tool, the inverse tool that ``undo_apply``
|
|
51
|
+
would run, and a human note. Use the ``undoId`` with ``undo_apply``.
|
|
52
|
+
|
|
53
|
+
One extra row is read so ``truncated`` is measured rather than guessed from
|
|
54
|
+
the row count matching the limit; when it is true, older tokens exist.
|
|
55
|
+
|
|
56
|
+
Each entry carries ``effectVerified``. False means the original write
|
|
57
|
+
lost its response, so the change it reverses is PROBABLE, not confirmed —
|
|
58
|
+
check the live state before applying, and do not report the result as a
|
|
59
|
+
restore of a state that may never have been reached.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
limit: Max rows to return (default 50, capped at 500).
|
|
63
|
+
target: Unused (undo state is host-local); accepted for CLI uniformity.
|
|
64
|
+
"""
|
|
65
|
+
requested = max(1, min(int(limit), 500))
|
|
66
|
+
raw = get_undo_store().list(status="recorded", limit=requested + 1)
|
|
67
|
+
truncated = len(raw) > requested
|
|
68
|
+
rows = raw[:requested]
|
|
69
|
+
return {
|
|
70
|
+
"count": len(rows),
|
|
71
|
+
"returned": len(rows),
|
|
72
|
+
"limit": requested,
|
|
73
|
+
"truncated": truncated,
|
|
74
|
+
"undos": [
|
|
75
|
+
{
|
|
76
|
+
"undoId": r["undo_id"],
|
|
77
|
+
"ts": r["ts"],
|
|
78
|
+
"originalTool": r["tool"],
|
|
79
|
+
"inverseTool": r["undo_tool"],
|
|
80
|
+
"note": r.get("note", ""),
|
|
81
|
+
"effectVerified": bool(r.get("effect_verified", 1)),
|
|
82
|
+
}
|
|
83
|
+
for r in rows
|
|
84
|
+
],
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _origin_target(rec: dict) -> Optional[str]:
|
|
89
|
+
"""The target the ORIGINAL write ran against, read from its recorded params.
|
|
90
|
+
|
|
91
|
+
Without this the replay goes to whatever target the caller names — in
|
|
92
|
+
practice the config's first entry — so on a multi-target config the inverse
|
|
93
|
+
runs against the wrong host. It only *looks* harmless because the resource
|
|
94
|
+
usually does not exist there; when two hosts both hold a resource with the
|
|
95
|
+
same name the inverse succeeds on the wrong one, silently.
|
|
96
|
+
|
|
97
|
+
Caught live on 2026-08-03 in container-host-aiops: a ``stop_container``
|
|
98
|
+
recorded against a Podman target replayed against a Portainer target.
|
|
99
|
+
|
|
100
|
+
A ``target`` in ``orig_params`` also means the original call accepted one,
|
|
101
|
+
so the fallback cannot hand a keyword to a tool that has no such parameter.
|
|
102
|
+
"""
|
|
103
|
+
raw = rec.get("orig_params") or ""
|
|
104
|
+
try:
|
|
105
|
+
orig = json.loads(raw) if raw else {}
|
|
106
|
+
except (ValueError, TypeError):
|
|
107
|
+
return None # unreadable: fall back to the caller/default, as before
|
|
108
|
+
if not isinstance(orig, dict):
|
|
109
|
+
return None
|
|
110
|
+
value = orig.get("target")
|
|
111
|
+
return value if isinstance(value, str) and value else None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@mcp.tool()
|
|
115
|
+
@governed_tool(risk_level="medium")
|
|
116
|
+
@tool_errors("dict")
|
|
117
|
+
def undo_apply(undo_id: str, dry_run: bool = False, target: Optional[str] = None) -> dict:
|
|
118
|
+
"""[WRITE][risk=medium] Apply a recorded undo by dispatching its inverse tool.
|
|
119
|
+
|
|
120
|
+
The inverse runs through its own governed tool, so it is audited under its
|
|
121
|
+
own risk tier. Pass dry_run=True to preview the inverse call without
|
|
122
|
+
executing it. A token can only be applied once.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
undo_id: The undoId from undo_list (or an ``_undo_id`` in a write result).
|
|
126
|
+
dry_run: If True, preview the inverse tool + params without running it.
|
|
127
|
+
target: Passed through to the inverse tool when it accepts a target.
|
|
128
|
+
"""
|
|
129
|
+
store = get_undo_store()
|
|
130
|
+
rec = store.get(undo_id)
|
|
131
|
+
if not rec:
|
|
132
|
+
raise ValueError(f"Unknown undo id '{undo_id}'. Run undo_list to see available tokens.")
|
|
133
|
+
if rec["status"] != "recorded":
|
|
134
|
+
raise ValueError(
|
|
135
|
+
f"Undo '{undo_id}' is already '{rec['status']}' — a token can only be applied once."
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
inverse_tool = rec["undo_tool"]
|
|
139
|
+
# Unreadable parameters must stop the replay, not be replaced with {}.
|
|
140
|
+
# Defaulting dispatched the inverse tool with NO ARGUMENTS: for a tool whose
|
|
141
|
+
# parameters all have defaults that runs a real, unintended operation, and
|
|
142
|
+
# dry_run previewed it as a legitimate no-arg inverse. Refusing is the only
|
|
143
|
+
# honest answer — the recorded intent is gone, so there is nothing to apply.
|
|
144
|
+
raw = rec["undo_params"]
|
|
145
|
+
try:
|
|
146
|
+
params = json.loads(raw) if raw else {}
|
|
147
|
+
except (ValueError, TypeError) as exc:
|
|
148
|
+
raise ValueError(
|
|
149
|
+
f"Undo '{undo_id}' has unreadable recorded parameters, so its inverse "
|
|
150
|
+
f"cannot be replayed. Inspect the row in undo.db and re-run the "
|
|
151
|
+
f"inverse tool '{inverse_tool}' by hand if the change still needs "
|
|
152
|
+
f"reversing."
|
|
153
|
+
) from exc
|
|
154
|
+
if not isinstance(params, dict):
|
|
155
|
+
raise ValueError(
|
|
156
|
+
f"Undo '{undo_id}' recorded parameters of type "
|
|
157
|
+
f"'{type(params).__name__}' rather than an object, so its inverse "
|
|
158
|
+
f"cannot be replayed. Re-run the inverse tool '{inverse_tool}' by hand."
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
fn = _resolve_tool(inverse_tool)
|
|
162
|
+
if fn is None:
|
|
163
|
+
raise ValueError(
|
|
164
|
+
f"Inverse tool '{inverse_tool}' is not registered on this server; cannot apply."
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
effect_verified = bool(rec.get("effect_verified", 1))
|
|
168
|
+
if dry_run:
|
|
169
|
+
return {
|
|
170
|
+
"dryRun": True,
|
|
171
|
+
"undoId": undo_id,
|
|
172
|
+
"effectVerified": effect_verified,
|
|
173
|
+
"wouldApply": {"tool": inverse_tool, "params": params},
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
call_params = dict(params)
|
|
177
|
+
if "target" not in call_params:
|
|
178
|
+
chosen = target if target is not None else _origin_target(rec)
|
|
179
|
+
if chosen is not None:
|
|
180
|
+
call_params["target"] = chosen
|
|
181
|
+
result = fn(**call_params)
|
|
182
|
+
|
|
183
|
+
# Only mark applied when the inverse did not itself return an error dict.
|
|
184
|
+
if not (isinstance(result, dict) and result.get("error")):
|
|
185
|
+
store.mark(undo_id, "applied")
|
|
186
|
+
applied = True
|
|
187
|
+
else:
|
|
188
|
+
applied = False
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
"undoId": undo_id,
|
|
192
|
+
"applied": applied,
|
|
193
|
+
"effectVerified": effect_verified,
|
|
194
|
+
"inverseTool": inverse_tool,
|
|
195
|
+
"result": result,
|
|
196
|
+
"note": (
|
|
197
|
+
""
|
|
198
|
+
if effect_verified
|
|
199
|
+
else "The original write lost its response, so it was never confirmed "
|
|
200
|
+
"to have taken effect. This inverse ran regardless; report the "
|
|
201
|
+
"CURRENT server state, not a restore that may have had nothing "
|
|
202
|
+
"to restore."
|
|
203
|
+
),
|
|
204
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Apache HTTP Server operational logic."""
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Read-only Apache HTTP Server diagnosis."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from apache_aiops.connection import ApacheConnection
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def version(conn: ApacheConnection) -> dict:
|
|
9
|
+
return conn.command("-v")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build(conn: ApacheConnection) -> dict:
|
|
13
|
+
return conn.command("-V")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def runtime_config(conn: ApacheConnection) -> dict:
|
|
17
|
+
return conn.command("-t", "-D", "DUMP_RUN_CFG")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def config_includes(conn: ApacheConnection) -> dict:
|
|
21
|
+
return conn.command("-t", "-D", "DUMP_INCLUDES")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def compiled_modules(conn: ApacheConnection) -> dict:
|
|
25
|
+
return conn.command("-l")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def static_directives(conn: ApacheConnection) -> dict:
|
|
29
|
+
return conn.command("-L")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def server_status(conn: ApacheConnection) -> dict:
|
|
33
|
+
return conn.server_status()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def error_log(conn: ApacheConnection, limit: int = 50) -> dict:
|
|
37
|
+
return conn.error_log(limit)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def access_status(conn: ApacheConnection, limit: int = 200) -> dict:
|
|
41
|
+
return conn.access_status(limit)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def config_check(conn: ApacheConnection) -> dict:
|
|
45
|
+
return conn.command("configtest")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def vhosts(conn: ApacheConnection) -> dict:
|
|
49
|
+
return conn.command("-S")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def modules(conn: ApacheConnection) -> dict:
|
|
53
|
+
return conn.command("-M")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def probe(conn: ApacheConnection) -> dict:
|
|
57
|
+
return conn.probe()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def diagnose(conn: ApacheConnection) -> dict:
|
|
61
|
+
probes = {}
|
|
62
|
+
checks = [
|
|
63
|
+
("version", lambda: version(conn)),
|
|
64
|
+
("config", lambda: config_check(conn)),
|
|
65
|
+
("vhosts", lambda: vhosts(conn)),
|
|
66
|
+
("modules", lambda: modules(conn)),
|
|
67
|
+
]
|
|
68
|
+
if conn.target.probe_url:
|
|
69
|
+
checks.append(("http", lambda: probe(conn)))
|
|
70
|
+
for name, call in checks:
|
|
71
|
+
try:
|
|
72
|
+
probes[name] = {"data": call(), "error": None}
|
|
73
|
+
except Exception as exc:
|
|
74
|
+
probes[name] = {"data": None, "error": f"{type(exc).__name__}: {exc}"}
|
|
75
|
+
failed = [name for name, result in probes.items() if result["error"] is not None]
|
|
76
|
+
return {
|
|
77
|
+
"probes": probes,
|
|
78
|
+
"failedProbes": failed,
|
|
79
|
+
"complete": not failed,
|
|
80
|
+
"httpProbeConfigured": bool(conn.target.probe_url),
|
|
81
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: apache-aiops
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Governed Apache HTTP Server diagnosis for config, virtual hosts, modules and HTTP health.
|
|
5
|
+
Author-email: Wei Zhou <zhouwei008@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
10
|
+
Requires-Dist: mcp[cli]<3.0,>=2.0
|
|
11
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
12
|
+
Requires-Dist: rich<16.0,>=13.0
|
|
13
|
+
Requires-Dist: typer<1.0,>=0.12
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
<!-- mcp-name: io.github.AIops-tools/apache-aiops -->
|
|
17
|
+
# Apache AIops
|
|
18
|
+
|
|
19
|
+
A dedicated, governed MCP server, CLI and skill for Apache HTTP Server with 17 MCP tools. It uses Apache's own `apachectl` diagnostics, optional operator-configured HTTP health and `mod_status` URLs, and bounded reads of an operator-configured error log plus a privacy-preserving access-log status summary. Every call is audited and budgeted by the vendored governance harness.
|
|
20
|
+
|
|
21
|
+
## What it checks
|
|
22
|
+
|
|
23
|
+
- Apache version, config syntax, parsed virtual hosts and loaded modules.
|
|
24
|
+
- Build options and parsed runtime configuration.
|
|
25
|
+
- Parsed config include tree, compiled modules and directives from static modules.
|
|
26
|
+
- Optional machine-readable `mod_status` metrics, bounded error-log tail and privacy-preserving access-status summary.
|
|
27
|
+
- Optional HTTP health status without downloading the response body.
|
|
28
|
+
- `apache_diagnose` correlates the checks and preserves failures as errors with `data: null`.
|
|
29
|
+
|
|
30
|
+
## Setup from source
|
|
31
|
+
|
|
32
|
+
Requires Python 3.11+, `uv`, and `apachectl` on the operator host. Run `uv sync --dev`, `uv run apache-aiops init`, then `uv run apache-aiops doctor`. Manual `~/.apache-aiops/config.yaml`:
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
targets:
|
|
36
|
+
- name: local
|
|
37
|
+
apachectl_bin: apachectl
|
|
38
|
+
probe_url: http://127.0.0.1/health
|
|
39
|
+
status_url: http://127.0.0.1/server-status?auto
|
|
40
|
+
error_log: /var/log/apache2/error.log
|
|
41
|
+
access_log: /var/log/apache2/access.log
|
|
42
|
+
timeout: 10
|
|
43
|
+
verify_ssl: true
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Run `uv run apache-aiops diagnose`. The MCP entry point is `uv run apache-aiops-mcp`. The optional HTTP probe is configured by the operator, not chosen by a tool caller. TLS verification is enabled by default, and `ca_file` accepts a custom CA PEM.
|
|
47
|
+
|
|
48
|
+
## Scope and verification
|
|
49
|
+
|
|
50
|
+
`graceful-reload` first runs `configtest`, supports dry-run and two CLI confirmations, and reports an unknown final outcome even if the command returned: existing workers may still serve the old config. It has no safe inverse when the prior config file is not snapshotted. Vhost editing, TLS certificate management and module changes remain outside this release. See [verification](docs/VERIFICATION.md). Missing operations and live-environment feedback are welcome as issues or PRs.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
apache_aiops/__init__.py,sha256=z7L-cyDLucsZJXRxQ1HRKCriITkx0acvE4I8_QQI8Us,233
|
|
2
|
+
apache_aiops/config.py,sha256=3WeQnVGJUl1osfY8PQrk81aJKHDUsZzVdNmSNcyZPag,2840
|
|
3
|
+
apache_aiops/connection.py,sha256=m-lrIxtreB1cWXyVKfpOhQax5SgVbLsLdDLiOAOoovc,9384
|
|
4
|
+
apache_aiops/cli/__init__.py,sha256=WUzjLZLugDpUuLvX4zUYAh04f_qO-v3xF2TdMerFm0Y,204
|
|
5
|
+
apache_aiops/cli/_common.py,sha256=veEs4a8Vtf6X2TNh2eiYfvjSy15kufTle1mqVIyd_H4,5156
|
|
6
|
+
apache_aiops/cli/_root.py,sha256=VFAkpknlHj1DCUKntaaLeh9wZaVQKp5QZWbxw4F4hck,6438
|
|
7
|
+
apache_aiops/cli/undo.py,sha256=_0wrb1IGwzvnVqlPNaWDTQGGsep1rVTtW5_zQ1zuRiE,2052
|
|
8
|
+
apache_aiops/governance/__init__.py,sha256=AzqEiNuKb4yKkhZ-6W_AUbV7uhqEsOTMzc89qZOUwT4,1713
|
|
9
|
+
apache_aiops/governance/audit.py,sha256=TcpBwszPsToLQ8q_7As-yloXt-Ys677-_yp3BuCXsAY,19534
|
|
10
|
+
apache_aiops/governance/budget.py,sha256=1auVcTtTDDA4qh-NGQUIRwr5QzZSk1l_TWer4AxQbc8,8961
|
|
11
|
+
apache_aiops/governance/decorators.py,sha256=kpbaE7clZr59EAjM69hfi8STbRGOmIm7E6kLRsTU8_c,23302
|
|
12
|
+
apache_aiops/governance/outcome.py,sha256=Hc882_A7t68nePxsOtUwflazn9KztXmOOBNKXo6n3W0,4376
|
|
13
|
+
apache_aiops/governance/paths.py,sha256=jKInopiPU6hlZ9IjRfPo-zYryirKDBGpIdF7DS9bX0o,714
|
|
14
|
+
apache_aiops/governance/patterns.py,sha256=JrVtLlN_3lkZc5LrI4i2gyNlaITkwUgBcTgWRApN5j4,15747
|
|
15
|
+
apache_aiops/governance/policy.py,sha256=w4uKVpICVZxbXDpjiSH0N6iz8IBGCKPtFwX-ogCX9l8,2369
|
|
16
|
+
apache_aiops/governance/sanitize.py,sha256=EV4jv0DUM-dzwiurGiDVZdtv9I3DgzCy-iAXwih9pBE,3310
|
|
17
|
+
apache_aiops/governance/undo.py,sha256=aIYGsFKbpmHqseLx_fU5LM4F3C3AoEed9IEJ41hYUpU,9522
|
|
18
|
+
apache_aiops/mcp_server/__init__.py,sha256=GSkSfzs3HjgUTUTY4OyO7pisvGDHGTlgQ-QovC4jwdA,43
|
|
19
|
+
apache_aiops/mcp_server/_annotations.py,sha256=L8U5e4abQByPsT1FTV5Wgig146Vpdlx9s1eCtCXaYTI,2830
|
|
20
|
+
apache_aiops/mcp_server/_shared.py,sha256=EuKfq_pWCqiN5qKvs_zLwQF9oO4dj5OfihjR5JkeaN4,2065
|
|
21
|
+
apache_aiops/mcp_server/server.py,sha256=CCqvaXZlj6c8tq9OYb7AbhfN3rst-H776qjxikR6dZ0,392
|
|
22
|
+
apache_aiops/mcp_server/tools/__init__.py,sha256=AZOqmTsbMg1fh8IsC9fv0NSDdkNA_ohgiyueeO5kjAo,76
|
|
23
|
+
apache_aiops/mcp_server/tools/httpd.py,sha256=LMcFp-a_LOO8O9aCSEcxdUa65eVAgYsvjSsVFL57QxQ,4887
|
|
24
|
+
apache_aiops/mcp_server/tools/undo.py,sha256=smJqNDMfmPD27xS9roquP7xPZMlc-92Lren1as-sJzU,8606
|
|
25
|
+
apache_aiops/ops/__init__.py,sha256=rNjTzAXjJOzYJSALVVQrdX1pdM_0XlWCtIqIItLFpv4,44
|
|
26
|
+
apache_aiops/ops/httpd.py,sha256=-jIfOgb1eDoGUhvaHUwQWqlLDMNHcg8_WzD1iyLmoaU,2098
|
|
27
|
+
apache_aiops-0.2.0.dist-info/METADATA,sha256=f_iIgHfp-xbYErRJGoO9GtgKy-ZvGBAQ3uuEKBEDH5I,2686
|
|
28
|
+
apache_aiops-0.2.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
29
|
+
apache_aiops-0.2.0.dist-info/entry_points.txt,sha256=id-ymyzTFNGeKgCWtxZunb2zhaz0On7inowXgfcHTIs,109
|
|
30
|
+
apache_aiops-0.2.0.dist-info/licenses/LICENSE,sha256=-aN-YMNjW-2HFH71T7ohVfCJf9XeNX0MSVcPFpsgs_8,1083
|
|
31
|
+
apache_aiops-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wei <zhouwei008@gmail.com>
|
|
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.
|