sanityops-cli 0.1.3__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.
- sanityops_cli/__init__.py +16 -0
- sanityops_cli/agents/__init__.py +14 -0
- sanityops_cli/agents/repair_agent/__init__.py +19 -0
- sanityops_cli/agents/repair_agent/agent.py +230 -0
- sanityops_cli/agents/repair_agent/prompts.py +100 -0
- sanityops_cli/agents/repair_agent/tools/__init__.py +19 -0
- sanityops_cli/agents/repair_agent/tools/store_repairs_tool.py +144 -0
- sanityops_cli/agents/scanner_agent/agent.py +332 -0
- sanityops_cli/agents/scanner_agent/hooks/progress_hook.py +152 -0
- sanityops_cli/agents/scanner_agent/models/finding.py +120 -0
- sanityops_cli/agents/scanner_agent/prompts.py +316 -0
- sanityops_cli/agents/scanner_agent/tools/grep_tool.py +667 -0
- sanityops_cli/agents/scanner_agent/tools/listfiles_tool.py +88 -0
- sanityops_cli/agents/scanner_agent/tools/readfile_tool.py +804 -0
- sanityops_cli/agents/scanner_agent/tools/storefindings_tool.py +178 -0
- sanityops_cli/api/__init__.py +16 -0
- sanityops_cli/api/client.py +403 -0
- sanityops_cli/commands/__init__.py +14 -0
- sanityops_cli/commands/config.py +312 -0
- sanityops_cli/commands/init.py +132 -0
- sanityops_cli/commands/inspect.py +646 -0
- sanityops_cli/constants/__init__.py +14 -0
- sanityops_cli/constants/config_defaults.py +22 -0
- sanityops_cli/constants/exit_codes.py +19 -0
- sanityops_cli/defect_checker/__init__.py +16 -0
- sanityops_cli/defect_checker/checker.py +100 -0
- sanityops_cli/defect_checker/llm_config.py +112 -0
- sanityops_cli/defect_checker/markdown_reporter.py +249 -0
- sanityops_cli/defect_checker/renderer.py +203 -0
- sanityops_cli/exceptions/__init__.py +14 -0
- sanityops_cli/exceptions/api_exceptions.py +60 -0
- sanityops_cli/exceptions/base_exceptions.py +25 -0
- sanityops_cli/help_panel.py +49 -0
- sanityops_cli/logging/__init__.py +18 -0
- sanityops_cli/logging/logger.py +108 -0
- sanityops_cli/main.py +123 -0
- sanityops_cli/progress/__init__.py +18 -0
- sanityops_cli/progress/tracker.py +159 -0
- sanityops_cli/renderers/__init__.py +14 -0
- sanityops_cli/renderers/command_renderer/inspect_command_renderer.py +87 -0
- sanityops_cli/templates/__init__.py +14 -0
- sanityops_cli/templates/inspect_config.yaml +55 -0
- sanityops_cli/utils/__init__.py +14 -0
- sanityops_cli/utils/artifact_packer.py +407 -0
- sanityops_cli/utils/config_loader.py +296 -0
- sanityops_cli/utils/config_resolver.py +358 -0
- sanityops_cli/utils/validators.py +117 -0
- sanityops_cli-0.1.3.dist-info/METADATA +213 -0
- sanityops_cli-0.1.3.dist-info/RECORD +53 -0
- sanityops_cli-0.1.3.dist-info/WHEEL +4 -0
- sanityops_cli-0.1.3.dist-info/entry_points.txt +2 -0
- sanityops_cli-0.1.3.dist-info/licenses/LICENSE +201 -0
- sanityops_cli-0.1.3.dist-info/licenses/NOTICE +5 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Copyright 2026 zipsonken
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
"""Step-based progress display with spinner, elapsed timing, and logging."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import time
|
|
21
|
+
from types import TracebackType
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from rich.console import Console, Group, RenderableType
|
|
25
|
+
from rich.live import Live
|
|
26
|
+
from rich.markup import escape
|
|
27
|
+
from rich.spinner import Spinner
|
|
28
|
+
from rich.status import Status
|
|
29
|
+
from rich.text import Text
|
|
30
|
+
|
|
31
|
+
from sanityops_cli.logging.logger import Logger
|
|
32
|
+
|
|
33
|
+
#: Refresh rate for spinner/live displays (Hz).
|
|
34
|
+
_REFRESH_PER_SECOND = 10
|
|
35
|
+
|
|
36
|
+
#: Width of the summary divider, in characters.
|
|
37
|
+
_DIVIDER_LENGTH = 30
|
|
38
|
+
|
|
39
|
+
__all__ = ["ProgressTracker", "StepContext"]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ProgressTracker:
|
|
43
|
+
"""Tracks sequential steps: spinner progress, per-step timing, logging."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, console: Console, logger: Logger, verbose: bool = False) -> None:
|
|
46
|
+
self.console = console
|
|
47
|
+
self.logger = logger
|
|
48
|
+
self.verbose = verbose
|
|
49
|
+
self.step_times: list[tuple[str, float]] = []
|
|
50
|
+
|
|
51
|
+
def step(self, name: str) -> StepContext:
|
|
52
|
+
"""Start a new tracked step, returning its context manager."""
|
|
53
|
+
return StepContext(self, name)
|
|
54
|
+
|
|
55
|
+
def summary(self) -> None:
|
|
56
|
+
"""Print a divider and the total elapsed time across all steps."""
|
|
57
|
+
total = sum(duration for _, duration in self.step_times)
|
|
58
|
+
self.console.print(f"[dim]{'─' * _DIVIDER_LENGTH}[/]")
|
|
59
|
+
self.console.print(f"[bold]Total time:[/] {total:.2f}s")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class _LiveConsole:
|
|
63
|
+
"""Console-like sink that appends printed lines into a step's Live display.
|
|
64
|
+
|
|
65
|
+
Duck-types ``Console.print`` so an agent's ProgressHook can stream detail
|
|
66
|
+
lines into the step's Live renderable (expand). The accumulated lines
|
|
67
|
+
collapse away when the Live stops, replaced by the step summary.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, live: Live, step_name: str) -> None:
|
|
71
|
+
self._live = live
|
|
72
|
+
self._step_name = step_name
|
|
73
|
+
self._lines: list[RenderableType] = []
|
|
74
|
+
|
|
75
|
+
def print(self, *objects: Any, **kwargs: Any) -> None:
|
|
76
|
+
"""Duck-type ``Console.print``: render objects into the Live display.
|
|
77
|
+
|
|
78
|
+
Multiple objects are joined with spaces (rich's default); a ``style``
|
|
79
|
+
kwarg is honored. Other rich keyword options are ignored — each print
|
|
80
|
+
is rendered as its own row in the Live display.
|
|
81
|
+
|
|
82
|
+
Callers must pass pre-escaped markup (see ``rich.markup.escape``),
|
|
83
|
+
mirroring ``Console.print`` semantics: the message is parsed as markup.
|
|
84
|
+
"""
|
|
85
|
+
message = " ".join(str(obj) for obj in objects) if objects else ""
|
|
86
|
+
line = Text.from_markup(message, style=kwargs.get("style"))
|
|
87
|
+
self._lines.append(line)
|
|
88
|
+
self._live.update(
|
|
89
|
+
Group(
|
|
90
|
+
Spinner("dots", text=f"[bold cyan]{escape(self._step_name)}[/]"),
|
|
91
|
+
*self._lines,
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class StepContext:
|
|
97
|
+
"""Context manager for a single tracked step."""
|
|
98
|
+
|
|
99
|
+
def __init__(self, tracker: ProgressTracker, name: str) -> None:
|
|
100
|
+
self.tracker = tracker
|
|
101
|
+
self.name = name
|
|
102
|
+
self._start: float = 0.0
|
|
103
|
+
self._live: Live | None = None
|
|
104
|
+
self._status: Status | None = None
|
|
105
|
+
self._live_console: _LiveConsole | None = None
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def console(self) -> Console | _LiveConsole:
|
|
109
|
+
"""Console to route agent progress output into (Live sink in verbose mode)."""
|
|
110
|
+
if self._live_console is not None:
|
|
111
|
+
return self._live_console
|
|
112
|
+
return self.tracker.console
|
|
113
|
+
|
|
114
|
+
def __enter__(self) -> StepContext:
|
|
115
|
+
self._start = time.perf_counter()
|
|
116
|
+
self.tracker.logger.step_started(self.name)
|
|
117
|
+
if self.tracker.verbose:
|
|
118
|
+
self._live = Live(
|
|
119
|
+
console=self.tracker.console, refresh_per_second=_REFRESH_PER_SECOND
|
|
120
|
+
)
|
|
121
|
+
self._live.start()
|
|
122
|
+
self._live.update(Spinner("dots", text=f"[bold cyan]{escape(self.name)}[/]"))
|
|
123
|
+
self._live_console = _LiveConsole(self._live, self.name)
|
|
124
|
+
else:
|
|
125
|
+
self._status = self.tracker.console.status(f"[bold cyan]{escape(self.name)}[/]")
|
|
126
|
+
self._status.start()
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
def __exit__(
|
|
130
|
+
self,
|
|
131
|
+
exc_type: type[BaseException] | None,
|
|
132
|
+
exc_val: BaseException | None,
|
|
133
|
+
exc_tb: TracebackType | None,
|
|
134
|
+
) -> bool:
|
|
135
|
+
duration = time.perf_counter() - self._start
|
|
136
|
+
if exc_type is None:
|
|
137
|
+
self.tracker.logger.step_completed(self.name, duration)
|
|
138
|
+
self.tracker.step_times.append((self.name, duration))
|
|
139
|
+
summary = Text.from_markup(f"[green]✓[/] {escape(self.name)} ({duration:.2f}s)")
|
|
140
|
+
if self._live is not None:
|
|
141
|
+
self._live.update(summary)
|
|
142
|
+
self._live.stop()
|
|
143
|
+
self._live = None
|
|
144
|
+
self._live_console = None
|
|
145
|
+
else:
|
|
146
|
+
self._status.stop()
|
|
147
|
+
self._status = None
|
|
148
|
+
self.tracker.console.print(summary)
|
|
149
|
+
else:
|
|
150
|
+
self.tracker.logger.step_failed(self.name, str(exc_val))
|
|
151
|
+
if self._live is not None:
|
|
152
|
+
self._live.update(Text.from_markup(f"[red]✗[/] {escape(self.name)} ({duration:.2f}s)"))
|
|
153
|
+
self._live.stop()
|
|
154
|
+
self._live = None
|
|
155
|
+
self._live_console = None
|
|
156
|
+
else:
|
|
157
|
+
self._status.stop()
|
|
158
|
+
self._status = None
|
|
159
|
+
return False
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright 2026 zipsonken
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
#
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Copyright 2026 zipsonken
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
from rich.panel import Panel
|
|
20
|
+
from rich.text import Text
|
|
21
|
+
|
|
22
|
+
console = Console()
|
|
23
|
+
|
|
24
|
+
# ============================================================================
|
|
25
|
+
# Inspect Command Renderer
|
|
26
|
+
# ============================================================================
|
|
27
|
+
|
|
28
|
+
def render_inspect_terminal(catalog: dict[str, Any]) -> None:
|
|
29
|
+
"""inspect resulte renderer for terminal"""
|
|
30
|
+
directory = catalog.get("directory", "-")
|
|
31
|
+
skills = catalog.get("skills", [])
|
|
32
|
+
tools = catalog.get("tools", [])
|
|
33
|
+
prompts = catalog.get("prompts", [])
|
|
34
|
+
meta = catalog.get("meta", {})
|
|
35
|
+
elapsed = meta.get("time_elapsed", 0)
|
|
36
|
+
loops = meta.get("loops_used", 0)
|
|
37
|
+
|
|
38
|
+
console.print()
|
|
39
|
+
header_text = Text()
|
|
40
|
+
header_text.append("📋 Inspect Report\n\n", style="bold")
|
|
41
|
+
header_text.append(f"Directory: {directory}", style="dim")
|
|
42
|
+
console.print(Panel(header_text, border_style="blue"))
|
|
43
|
+
console.print()
|
|
44
|
+
|
|
45
|
+
# Skills
|
|
46
|
+
console.print(f"[bold]🔧 Skills ({len(skills)})[/]")
|
|
47
|
+
if skills:
|
|
48
|
+
for s in skills:
|
|
49
|
+
name = s.get("name", "-")
|
|
50
|
+
path = s.get("path", "-")
|
|
51
|
+
desc = s.get("description", "")
|
|
52
|
+
console.print(f" ├─ {name:<20} {path:<30} {desc[:40]}")
|
|
53
|
+
else:
|
|
54
|
+
console.print(" [dim]None[/]")
|
|
55
|
+
console.print()
|
|
56
|
+
|
|
57
|
+
# Tools
|
|
58
|
+
console.print(f"[bold]⚙️ Tools ({len(tools)})[/]")
|
|
59
|
+
if tools:
|
|
60
|
+
for t in tools:
|
|
61
|
+
name = t.get("name", "-")
|
|
62
|
+
path = t.get("path", "-")
|
|
63
|
+
t_type = t.get("type", "unknown")
|
|
64
|
+
desc = t.get("description", "")
|
|
65
|
+
console.print(f" ├─ {name:<20} {path} ({t_type}) {desc[:30]}")
|
|
66
|
+
else:
|
|
67
|
+
console.print(" [dim]None[/]")
|
|
68
|
+
console.print()
|
|
69
|
+
|
|
70
|
+
# Prompts
|
|
71
|
+
console.print(f"[bold]💬 Prompts ({len(prompts)})[/]")
|
|
72
|
+
if prompts:
|
|
73
|
+
for p in prompts:
|
|
74
|
+
name = p.get("name", "-")
|
|
75
|
+
path = p.get("path", "-")
|
|
76
|
+
p_type = p.get("type", "file")
|
|
77
|
+
desc = p.get("description", "")
|
|
78
|
+
console.print(f" ├─ {name:<20} {path} ({p_type}) {desc[:30]}")
|
|
79
|
+
else:
|
|
80
|
+
console.print(" [dim]None[/]")
|
|
81
|
+
console.print()
|
|
82
|
+
|
|
83
|
+
# Statistics
|
|
84
|
+
console.print(
|
|
85
|
+
f"[dim]Statistics: {len(skills)} skills, {len(tools)} tools, {len(prompts)} prompts"
|
|
86
|
+
f" | Time elapsed: {elapsed:.1f}s | Loops: {loops}[/]"
|
|
87
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright 2026 zipsonken
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
#
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# Sanityops CLI - Inspect Configuration Template
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Usage:
|
|
5
|
+
# 1. Run: sanityops-cli init
|
|
6
|
+
# 2. Fill in model configuration below (required)
|
|
7
|
+
# 3. Connect to the Sanityops service:
|
|
8
|
+
# sanityops-cli config server.base_url "<your-server-url>"
|
|
9
|
+
# sanityops-cli config server.api_key
|
|
10
|
+
# 4. Configure artifact paths below
|
|
11
|
+
# 5. Run: sanityops-cli inspect
|
|
12
|
+
# (A project is created automatically on first upload and the
|
|
13
|
+
# project id/name are written back to this file.)
|
|
14
|
+
# ============================================================================
|
|
15
|
+
|
|
16
|
+
# Project metadata
|
|
17
|
+
# id: Left as a placeholder. A real project id is assigned automatically
|
|
18
|
+
# on first inspect upload (project auto-created), or set it manually.
|
|
19
|
+
# name: Project display name (optional, filled automatically on first upload)
|
|
20
|
+
project:
|
|
21
|
+
id: "00000000-0000-0000-0000-000000000000"
|
|
22
|
+
name: ""
|
|
23
|
+
|
|
24
|
+
# Model configuration
|
|
25
|
+
# Fill in the fields below, or remove this section entirely to configure
|
|
26
|
+
# via LLM_* environment variables instead:
|
|
27
|
+
# LLM_PROVIDER, LLM_API_KEY, LLM_MODEL_ID, LLM_BASE_URL
|
|
28
|
+
model:
|
|
29
|
+
provider: anthropic # Required: anthropic, openai, azure, etc.
|
|
30
|
+
api_key: "" # Required: your API key (keep this file out of git)
|
|
31
|
+
model_id: claude-sonnet-4-20250514 # Required: model identifier
|
|
32
|
+
base_url: "" # Optional: custom endpoint URL
|
|
33
|
+
|
|
34
|
+
# Agent configuration (optional)
|
|
35
|
+
# Override CLI defaults for timeout and token budget.
|
|
36
|
+
# agent:
|
|
37
|
+
# timeout: 1800 # seconds (default: 1800, 30min)
|
|
38
|
+
# token_budget: 200000
|
|
39
|
+
|
|
40
|
+
# System Prompt file list (supports any plaintext file format)
|
|
41
|
+
prompts:
|
|
42
|
+
- file: prompts/system_prompt.md
|
|
43
|
+
- file: prompts/fallback_prompt.py
|
|
44
|
+
- file: prompts/summary_prompt.ts
|
|
45
|
+
|
|
46
|
+
# Tools Schema file list (supports any plaintext file format)
|
|
47
|
+
tools:
|
|
48
|
+
- file: tools/search_tools.json
|
|
49
|
+
- file: tools/file_ops.py
|
|
50
|
+
- file: tools/code_tools.ts
|
|
51
|
+
|
|
52
|
+
# Skills file list (supports any plaintext file format)
|
|
53
|
+
skills:
|
|
54
|
+
- file: skills/code_review.md
|
|
55
|
+
- file: skills/code_fix/skill.md
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright 2026 zipsonken
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
#
|