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.
Files changed (53) hide show
  1. sanityops_cli/__init__.py +16 -0
  2. sanityops_cli/agents/__init__.py +14 -0
  3. sanityops_cli/agents/repair_agent/__init__.py +19 -0
  4. sanityops_cli/agents/repair_agent/agent.py +230 -0
  5. sanityops_cli/agents/repair_agent/prompts.py +100 -0
  6. sanityops_cli/agents/repair_agent/tools/__init__.py +19 -0
  7. sanityops_cli/agents/repair_agent/tools/store_repairs_tool.py +144 -0
  8. sanityops_cli/agents/scanner_agent/agent.py +332 -0
  9. sanityops_cli/agents/scanner_agent/hooks/progress_hook.py +152 -0
  10. sanityops_cli/agents/scanner_agent/models/finding.py +120 -0
  11. sanityops_cli/agents/scanner_agent/prompts.py +316 -0
  12. sanityops_cli/agents/scanner_agent/tools/grep_tool.py +667 -0
  13. sanityops_cli/agents/scanner_agent/tools/listfiles_tool.py +88 -0
  14. sanityops_cli/agents/scanner_agent/tools/readfile_tool.py +804 -0
  15. sanityops_cli/agents/scanner_agent/tools/storefindings_tool.py +178 -0
  16. sanityops_cli/api/__init__.py +16 -0
  17. sanityops_cli/api/client.py +403 -0
  18. sanityops_cli/commands/__init__.py +14 -0
  19. sanityops_cli/commands/config.py +312 -0
  20. sanityops_cli/commands/init.py +132 -0
  21. sanityops_cli/commands/inspect.py +646 -0
  22. sanityops_cli/constants/__init__.py +14 -0
  23. sanityops_cli/constants/config_defaults.py +22 -0
  24. sanityops_cli/constants/exit_codes.py +19 -0
  25. sanityops_cli/defect_checker/__init__.py +16 -0
  26. sanityops_cli/defect_checker/checker.py +100 -0
  27. sanityops_cli/defect_checker/llm_config.py +112 -0
  28. sanityops_cli/defect_checker/markdown_reporter.py +249 -0
  29. sanityops_cli/defect_checker/renderer.py +203 -0
  30. sanityops_cli/exceptions/__init__.py +14 -0
  31. sanityops_cli/exceptions/api_exceptions.py +60 -0
  32. sanityops_cli/exceptions/base_exceptions.py +25 -0
  33. sanityops_cli/help_panel.py +49 -0
  34. sanityops_cli/logging/__init__.py +18 -0
  35. sanityops_cli/logging/logger.py +108 -0
  36. sanityops_cli/main.py +123 -0
  37. sanityops_cli/progress/__init__.py +18 -0
  38. sanityops_cli/progress/tracker.py +159 -0
  39. sanityops_cli/renderers/__init__.py +14 -0
  40. sanityops_cli/renderers/command_renderer/inspect_command_renderer.py +87 -0
  41. sanityops_cli/templates/__init__.py +14 -0
  42. sanityops_cli/templates/inspect_config.yaml +55 -0
  43. sanityops_cli/utils/__init__.py +14 -0
  44. sanityops_cli/utils/artifact_packer.py +407 -0
  45. sanityops_cli/utils/config_loader.py +296 -0
  46. sanityops_cli/utils/config_resolver.py +358 -0
  47. sanityops_cli/utils/validators.py +117 -0
  48. sanityops_cli-0.1.3.dist-info/METADATA +213 -0
  49. sanityops_cli-0.1.3.dist-info/RECORD +53 -0
  50. sanityops_cli-0.1.3.dist-info/WHEEL +4 -0
  51. sanityops_cli-0.1.3.dist-info/entry_points.txt +2 -0
  52. sanityops_cli-0.1.3.dist-info/licenses/LICENSE +201 -0
  53. 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
+ #