riciplay-cli 1.7.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.
- riciplay_cli/__init__.py +2 -0
- riciplay_cli/auto_updater.py +250 -0
- riciplay_cli/chat_engine.py +797 -0
- riciplay_cli/client.py +349 -0
- riciplay_cli/cmd_ai.py +412 -0
- riciplay_cli/cmd_auth.py +175 -0
- riciplay_cli/cmd_chat.py +542 -0
- riciplay_cli/cmd_gateway.py +376 -0
- riciplay_cli/cmd_init.py +216 -0
- riciplay_cli/cmd_monitor.py +82 -0
- riciplay_cli/cmd_recon.py +135 -0
- riciplay_cli/cmd_repair.py +116 -0
- riciplay_cli/cmd_report.py +300 -0
- riciplay_cli/cmd_review.py +696 -0
- riciplay_cli/cmd_scan.py +471 -0
- riciplay_cli/cmd_update.py +121 -0
- riciplay_cli/command_palette.py +275 -0
- riciplay_cli/compressor.py +276 -0
- riciplay_cli/config.py +211 -0
- riciplay_cli/credits.py +463 -0
- riciplay_cli/display.py +83 -0
- riciplay_cli/error_taxonomy.py +169 -0
- riciplay_cli/investigation/__init__.py +6 -0
- riciplay_cli/investigation/checkpoint_writer.py +305 -0
- riciplay_cli/investigation/context_assembler.py +322 -0
- riciplay_cli/investigation/leader.py +153 -0
- riciplay_cli/investigation/orchestrator.py +641 -0
- riciplay_cli/investigation/permission_gate.py +101 -0
- riciplay_cli/investigation/rag_promoter.py +216 -0
- riciplay_cli/investigation/session_manager.py +222 -0
- riciplay_cli/investigation/shared_memory.py +52 -0
- riciplay_cli/investigation/specialist.py +392 -0
- riciplay_cli/investigation/specialist_stats.py +232 -0
- riciplay_cli/investigation/specialists/__init__.py +21 -0
- riciplay_cli/investigation/specialists/api.py +23 -0
- riciplay_cli/investigation/specialists/auth.py +23 -0
- riciplay_cli/investigation/specialists/business_logic.py +23 -0
- riciplay_cli/investigation/specialists/cloud.py +23 -0
- riciplay_cli/investigation/specialists/recon.py +22 -0
- riciplay_cli/investigation/specialists/web.py +23 -0
- riciplay_cli/investigation/tool_viz.py +149 -0
- riciplay_cli/investigation/tools/__init__.py +8 -0
- riciplay_cli/investigation/tools/auth.py +88 -0
- riciplay_cli/investigation/tools/browser.py +81 -0
- riciplay_cli/investigation/tools/definitions.py +252 -0
- riciplay_cli/investigation/tools/executor.py +285 -0
- riciplay_cli/investigation/tools/external.py +104 -0
- riciplay_cli/investigation/tools/recon.py +129 -0
- riciplay_cli/investigation/tools/scanner.py +182 -0
- riciplay_cli/investigation/types.py +45 -0
- riciplay_cli/log_preprocessor.py +176 -0
- riciplay_cli/main.py +148 -0
- riciplay_cli/model_router.py +162 -0
- riciplay_cli/notebook.py +410 -0
- riciplay_cli/notebook_embeddings.py +288 -0
- riciplay_cli/pipeline/__init__.py +8 -0
- riciplay_cli/pipeline/attack_graph.py +297 -0
- riciplay_cli/pipeline/correlator.py +489 -0
- riciplay_cli/pipeline/discovery.py +279 -0
- riciplay_cli/pipeline/hypothesis.py +280 -0
- riciplay_cli/pipeline/report.py +316 -0
- riciplay_cli/pipeline/runner.py +799 -0
- riciplay_cli/pipeline/triager.py +333 -0
- riciplay_cli/planning_mode.py +345 -0
- riciplay_cli/pricing.py +240 -0
- riciplay_cli/proxy_manager.py +676 -0
- riciplay_cli/riciplay_md.py +244 -0
- riciplay_cli/rules_engine.py +563 -0
- riciplay_cli/sanitizer.py +207 -0
- riciplay_cli/semantic_cache.py +133 -0
- riciplay_cli/session_store.py +210 -0
- riciplay_cli/skill_generator.py +538 -0
- riciplay_cli/skill_loader.py +221 -0
- riciplay_cli/skills/__init__.py +0 -0
- riciplay_cli/skills/api-review.md +105 -0
- riciplay_cli/skills/auth-review.md +99 -0
- riciplay_cli/skills/deserialization.md +86 -0
- riciplay_cli/skills/sql-injection.md +70 -0
- riciplay_cli/skills/supply-chain.md +99 -0
- riciplay_cli/skills/xss-audit.md +67 -0
- riciplay_cli/slash_handler.py +1397 -0
- riciplay_cli/todo_manager.py +255 -0
- riciplay_cli/token_utils.py +86 -0
- riciplay_cli/tool_runners.py +838 -0
- riciplay_cli/tools/__init__.py +44 -0
- riciplay_cli/tools/registry.py +1157 -0
- riciplay_cli-1.7.0.dist-info/METADATA +12 -0
- riciplay_cli-1.7.0.dist-info/RECORD +91 -0
- riciplay_cli-1.7.0.dist-info/WHEEL +5 -0
- riciplay_cli-1.7.0.dist-info/entry_points.txt +2 -0
- riciplay_cli-1.7.0.dist-info/top_level.txt +1 -0
riciplay_cli/__init__.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auto-Updater (Phase F Part 3)
|
|
3
|
+
|
|
4
|
+
Checks for Riciplay CLI updates from GitHub releases.
|
|
5
|
+
Supports stable/beta/nightly channels. Checks on startup and on-demand.
|
|
6
|
+
Never auto-installs — always requires user confirmation.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
import time
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
import httpx
|
|
21
|
+
from packaging.version import Version, InvalidVersion
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
# Default configuration keys
|
|
26
|
+
DEFAULT_UPDATE_ENABLED = True
|
|
27
|
+
DEFAULT_UPDATE_CHANNEL = "stable"
|
|
28
|
+
DEFAULT_UPDATE_CHECK_INTERVAL = 86400 # 24 hours
|
|
29
|
+
|
|
30
|
+
# GitHub releases API
|
|
31
|
+
GITHUB_API_URL = "https://api.github.com/repos/Zaidux/Riciplay/releases"
|
|
32
|
+
UPDATE_CHECK_TIMEOUT = 10.0 # seconds
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _get_current_version() -> str:
|
|
36
|
+
"""Read current version from the riciplay package metadata."""
|
|
37
|
+
try:
|
|
38
|
+
from importlib.metadata import version
|
|
39
|
+
return version("riciplay-cli")
|
|
40
|
+
except Exception:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
# Fallback: try reading from nearby pyproject.toml
|
|
44
|
+
pyproject = Path(__file__).resolve().parent.parent / "pyproject.toml"
|
|
45
|
+
if pyproject.exists():
|
|
46
|
+
try:
|
|
47
|
+
content = pyproject.read_text(encoding="utf-8")
|
|
48
|
+
for line in content.split("\n"):
|
|
49
|
+
stripped = line.strip()
|
|
50
|
+
if stripped.startswith("version"):
|
|
51
|
+
parts = stripped.split("=", 1)
|
|
52
|
+
if len(parts) == 2:
|
|
53
|
+
return parts[1].strip().strip('"').strip("'")
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
return "0.0.0"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _parse_version(version_str: str) -> Version:
|
|
61
|
+
"""Parse a version string, stripping leading 'v' if present."""
|
|
62
|
+
clean = version_str.lstrip("v").strip()
|
|
63
|
+
return Version(clean)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class UpdateInfo:
|
|
67
|
+
"""Information about an available update."""
|
|
68
|
+
def __init__(self, version: str, channel: str, release_notes: str, download_url: str, published_at: str):
|
|
69
|
+
self.version = version
|
|
70
|
+
self.channel = channel
|
|
71
|
+
self.release_notes = release_notes
|
|
72
|
+
self.download_url = download_url
|
|
73
|
+
self.published_at = published_at
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def is_newer(self) -> bool:
|
|
77
|
+
try:
|
|
78
|
+
current = _parse_version(_get_current_version())
|
|
79
|
+
new = _parse_version(self.version)
|
|
80
|
+
return new > current
|
|
81
|
+
except (InvalidVersion, ValueError):
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class AutoUpdater:
|
|
86
|
+
"""Checks for and manages Riciplay CLI updates."""
|
|
87
|
+
|
|
88
|
+
def __init__(self, config_path: Optional[Path] = None):
|
|
89
|
+
self.config_path = config_path or (Path.home() / ".riciplay" / "update-config.json")
|
|
90
|
+
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
91
|
+
self._config = self._load_config()
|
|
92
|
+
|
|
93
|
+
def _load_config(self) -> dict:
|
|
94
|
+
if self.config_path.exists():
|
|
95
|
+
try:
|
|
96
|
+
return json.loads(self.config_path.read_text(encoding="utf-8"))
|
|
97
|
+
except (json.JSONDecodeError, KeyError):
|
|
98
|
+
pass
|
|
99
|
+
return {
|
|
100
|
+
"auto_update": DEFAULT_UPDATE_ENABLED,
|
|
101
|
+
"channel": DEFAULT_UPDATE_CHANNEL,
|
|
102
|
+
"check_interval": DEFAULT_UPDATE_CHECK_INTERVAL,
|
|
103
|
+
"last_check": None,
|
|
104
|
+
"update_history": [],
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
def _save_config(self) -> None:
|
|
108
|
+
self.config_path.write_text(json.dumps(self._config, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def is_enabled(self) -> bool:
|
|
112
|
+
return self._config.get("auto_update", DEFAULT_UPDATE_ENABLED)
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def channel(self) -> str:
|
|
116
|
+
return self._config.get("channel", DEFAULT_UPDATE_CHANNEL)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def should_check(self) -> bool:
|
|
120
|
+
if not self.is_enabled:
|
|
121
|
+
return False
|
|
122
|
+
interval = self._config.get("check_interval", DEFAULT_UPDATE_CHECK_INTERVAL)
|
|
123
|
+
last = self._config.get("last_check")
|
|
124
|
+
if not last:
|
|
125
|
+
return True
|
|
126
|
+
try:
|
|
127
|
+
last_time = datetime.fromisoformat(last).timestamp()
|
|
128
|
+
return (time.time() - last_time) > interval
|
|
129
|
+
except (ValueError, TypeError):
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
async def check_for_updates(self) -> Optional[UpdateInfo]:
|
|
133
|
+
"""Check GitHub releases for a newer version. Returns UpdateInfo if available."""
|
|
134
|
+
if not self.should_check:
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
async with httpx.AsyncClient(timeout=UPDATE_CHECK_TIMEOUT) as client:
|
|
139
|
+
resp = await client.get(
|
|
140
|
+
GITHUB_API_URL,
|
|
141
|
+
headers={"Accept": "application/vnd.github.v3+json"},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if resp.status_code != 200:
|
|
145
|
+
logger.debug("GitHub API returned %d — skipping update check", resp.status_code)
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
releases = resp.json()
|
|
149
|
+
if not releases:
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
current_version_str = _get_current_version()
|
|
153
|
+
current = _parse_version(current_version_str)
|
|
154
|
+
|
|
155
|
+
# Filter by channel
|
|
156
|
+
for release in releases:
|
|
157
|
+
tag = release.get("tag_name", "")
|
|
158
|
+
is_prerelease = release.get("prerelease", False)
|
|
159
|
+
|
|
160
|
+
# Channel filtering
|
|
161
|
+
if self.channel == "stable" and is_prerelease:
|
|
162
|
+
continue
|
|
163
|
+
elif self.channel == "beta":
|
|
164
|
+
if "beta" not in tag.lower() and not is_prerelease:
|
|
165
|
+
continue
|
|
166
|
+
elif self.channel == "nightly":
|
|
167
|
+
if "nightly" not in tag.lower() and "dev" not in tag.lower():
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
release_version = _parse_version(tag)
|
|
172
|
+
except (InvalidVersion, ValueError):
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
if release_version > current:
|
|
176
|
+
self._config["last_check"] = datetime.now(timezone.utc).isoformat()
|
|
177
|
+
self._save_config()
|
|
178
|
+
|
|
179
|
+
return UpdateInfo(
|
|
180
|
+
version=tag,
|
|
181
|
+
channel=self.channel,
|
|
182
|
+
release_notes=release.get("body", "")[:500],
|
|
183
|
+
download_url=release.get("html_url", ""),
|
|
184
|
+
published_at=release.get("published_at", ""),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
except Exception as exc:
|
|
188
|
+
logger.debug("Update check failed: %s", exc)
|
|
189
|
+
|
|
190
|
+
self._config["last_check"] = datetime.now(timezone.utc).isoformat()
|
|
191
|
+
self._save_config()
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
def install_update(self) -> tuple[bool, str]:
|
|
195
|
+
"""Install the latest version via pip. Returns (success, message)."""
|
|
196
|
+
try:
|
|
197
|
+
result = subprocess.run(
|
|
198
|
+
[sys.executable, "-m", "pip", "install", "--upgrade", "riciplay-cli"],
|
|
199
|
+
capture_output=True,
|
|
200
|
+
text=True,
|
|
201
|
+
timeout=60,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if result.returncode == 0:
|
|
205
|
+
new_version = _get_current_version()
|
|
206
|
+
self._config.setdefault("update_history", []).append({
|
|
207
|
+
"version": new_version,
|
|
208
|
+
"channel": self.channel,
|
|
209
|
+
"date": datetime.now(timezone.utc).isoformat(),
|
|
210
|
+
})
|
|
211
|
+
self._config["last_check"] = datetime.now(timezone.utc).isoformat()
|
|
212
|
+
self._save_config()
|
|
213
|
+
return True, f"Updated to riciplay-cli v{new_version}"
|
|
214
|
+
else:
|
|
215
|
+
error_msg = result.stderr.strip()[:200] or result.stdout.strip()[:200]
|
|
216
|
+
return False, f"pip install failed: {error_msg}"
|
|
217
|
+
|
|
218
|
+
except subprocess.TimeoutExpired:
|
|
219
|
+
return False, "pip install timed out after 60 seconds"
|
|
220
|
+
except Exception as exc:
|
|
221
|
+
return False, f"Install failed: {exc}"
|
|
222
|
+
|
|
223
|
+
def set_channel(self, channel: str) -> bool:
|
|
224
|
+
valid = {"stable", "beta", "nightly"}
|
|
225
|
+
if channel not in valid:
|
|
226
|
+
return False
|
|
227
|
+
self._config["channel"] = channel
|
|
228
|
+
self._save_config()
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
def set_auto_update(self, enabled: bool) -> None:
|
|
232
|
+
self._config["auto_update"] = enabled
|
|
233
|
+
self._save_config()
|
|
234
|
+
|
|
235
|
+
def get_status(self) -> dict:
|
|
236
|
+
return {
|
|
237
|
+
"current_version": _get_current_version(),
|
|
238
|
+
"channel": self.channel,
|
|
239
|
+
"auto_update": self.is_enabled,
|
|
240
|
+
"last_check": self._config.get("last_check"),
|
|
241
|
+
"update_history": self._config.get("update_history", []),
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
def manual_update_instructions(self) -> str:
|
|
245
|
+
return (
|
|
246
|
+
"To update manually:\n"
|
|
247
|
+
" pip install --upgrade riciplay-cli\n"
|
|
248
|
+
"Or from source:\n"
|
|
249
|
+
" cd /path/to/Riciplay/cli && pip install -e ."
|
|
250
|
+
)
|