ravmem-cli 0.1.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.
- ravmem/cli/__init__.py +0 -0
- ravmem/cli/analyze.py +484 -0
- ravmem/cli/auth.py +675 -0
- ravmem/cli/browse.py +144 -0
- ravmem/cli/config_cmd.py +69 -0
- ravmem/cli/diff.py +122 -0
- ravmem/cli/git_local.py +131 -0
- ravmem/cli/impact.py +106 -0
- ravmem/cli/info.py +82 -0
- ravmem/cli/kg.py +787 -0
- ravmem/cli/main.py +96 -0
- ravmem/cli/remote.py +549 -0
- ravmem/cli/search.py +91 -0
- ravmem/cli/serve.py +41 -0
- ravmem/client/__init__.py +3 -0
- ravmem/client/api.py +279 -0
- ravmem/client/config.py +52 -0
- ravmem/config.py +103 -0
- ravmem/config_loader.py +247 -0
- ravmem_cli-0.1.0.dist-info/METADATA +62 -0
- ravmem_cli-0.1.0.dist-info/RECORD +25 -0
- ravmem_cli-0.1.0.dist-info/WHEEL +5 -0
- ravmem_cli-0.1.0.dist-info/entry_points.txt +2 -0
- ravmem_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- ravmem_cli-0.1.0.dist-info/top_level.txt +1 -0
ravmem/config_loader.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""Structured YAML config loader with pydantic validation and env-var override.
|
|
2
|
+
|
|
3
|
+
Resolution order (first found wins):
|
|
4
|
+
1. --config CLI flag → passed as config_path argument
|
|
5
|
+
2. RAVMEM_CONFIG env var → path to a YAML file
|
|
6
|
+
3. $RAVMEM_DATA_DIR/ravmem.yaml
|
|
7
|
+
4. ~/.ravmem/ravmem.yaml
|
|
8
|
+
5. Built-in defaults (pydantic model defaults)
|
|
9
|
+
|
|
10
|
+
Env vars always win over file values — existing env var names continue to work
|
|
11
|
+
exactly as before; no breaking change for Docker Compose / systemd users.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
import yaml
|
|
22
|
+
from pydantic import BaseModel, field_validator, model_validator
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
_SENSITIVE_KEYS = {"admin_token", "password"}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# Pydantic config models
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
class TierLimits(BaseModel):
|
|
34
|
+
read: str = "1000/min"
|
|
35
|
+
mcp: str = "600/min"
|
|
36
|
+
write: str = "200/min"
|
|
37
|
+
index: str = "30/min"
|
|
38
|
+
auth: str = "60/min"
|
|
39
|
+
|
|
40
|
+
@field_validator("read", "mcp", "write", "index", "auth", mode="before")
|
|
41
|
+
@classmethod
|
|
42
|
+
def _validate_limit(cls, v: object) -> str:
|
|
43
|
+
if not isinstance(v, str):
|
|
44
|
+
v = str(v)
|
|
45
|
+
parts = v.split("/")
|
|
46
|
+
if len(parts) != 2 or not parts[0].strip().isdigit() or parts[1].strip() not in ("sec", "min", "hour"):
|
|
47
|
+
raise ValueError(f"Invalid rate limit format '{v}' — expected '<int>/sec|min|hour'")
|
|
48
|
+
return v
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class RateLimitTiers(BaseModel):
|
|
52
|
+
admin: TierLimits = TierLimits(
|
|
53
|
+
read="5000/min", mcp="3000/min", write="1000/min", index="300/min", auth="300/min"
|
|
54
|
+
)
|
|
55
|
+
high: TierLimits = TierLimits(
|
|
56
|
+
read="3000/min", mcp="1800/min", write="600/min", index="120/min", auth="180/min"
|
|
57
|
+
)
|
|
58
|
+
standard: TierLimits = TierLimits(
|
|
59
|
+
read="1000/min", mcp="600/min", write="200/min", index="30/min", auth="60/min"
|
|
60
|
+
)
|
|
61
|
+
restricted: TierLimits = TierLimits(
|
|
62
|
+
read="200/min", mcp="120/min", write="40/min", index="5/min", auth="20/min"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class RateLimitConfig(BaseModel):
|
|
67
|
+
enabled: bool = True
|
|
68
|
+
limits: TierLimits = TierLimits()
|
|
69
|
+
tiers: RateLimitTiers = RateLimitTiers()
|
|
70
|
+
# Brute-force lockout (Phase 2)
|
|
71
|
+
lockout_threshold: int = 5
|
|
72
|
+
lockout_duration: int = 600 # seconds
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ServerConfig(BaseModel):
|
|
76
|
+
host: str = "0.0.0.0"
|
|
77
|
+
port: int = 7432
|
|
78
|
+
max_concurrent_jobs: int = 2
|
|
79
|
+
log_level: str = "INFO"
|
|
80
|
+
data_dir: str = "" # resolved from env/default at load time
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ArcadeDBConfig(BaseModel):
|
|
84
|
+
host: str = "localhost"
|
|
85
|
+
port: int = 2480
|
|
86
|
+
password: str = "arcadedb" # override via ARCADEDB_PASSWORD — never write to YAML
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class AuthConfig(BaseModel):
|
|
90
|
+
system_db: str = "ravmem_system"
|
|
91
|
+
audit_log_retention_days: int = 90
|
|
92
|
+
admin_token: str = "" # env-only — warn if found in YAML
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ClusteringConfig(BaseModel):
|
|
96
|
+
seed: int = 42
|
|
97
|
+
resolution: float = 1.0
|
|
98
|
+
max_cluster_size: int = 500
|
|
99
|
+
subcluster_threshold: int = 200
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class RavMemConfig(BaseModel):
|
|
103
|
+
server: ServerConfig = ServerConfig()
|
|
104
|
+
arcadedb: ArcadeDBConfig = ArcadeDBConfig()
|
|
105
|
+
auth: AuthConfig = AuthConfig()
|
|
106
|
+
clustering: ClusteringConfig = ClusteringConfig()
|
|
107
|
+
rate_limit: RateLimitConfig = RateLimitConfig()
|
|
108
|
+
|
|
109
|
+
@model_validator(mode="after")
|
|
110
|
+
def _warn_sensitive_in_file(self) -> "RavMemConfig":
|
|
111
|
+
if self.auth.admin_token:
|
|
112
|
+
logger.warning(
|
|
113
|
+
"SECURITY WARNING: 'auth.admin_token' found in config file. "
|
|
114
|
+
"Use the RAVMEM_ADMIN_TOKEN env var instead — config files may be world-readable."
|
|
115
|
+
)
|
|
116
|
+
if self.arcadedb.password not in ("arcadedb", ""):
|
|
117
|
+
pass # password in file is acceptable for dev; no warning needed
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
# Loader
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
def _find_config_file(explicit_path: str | Path | None = None) -> Path | None:
|
|
126
|
+
"""Return the first config file that exists, following the resolution order."""
|
|
127
|
+
candidates: list[Path] = []
|
|
128
|
+
|
|
129
|
+
if explicit_path:
|
|
130
|
+
candidates.append(Path(explicit_path))
|
|
131
|
+
elif env_path := os.environ.get("RAVMEM_CONFIG"):
|
|
132
|
+
candidates.append(Path(env_path))
|
|
133
|
+
|
|
134
|
+
data_dir = os.environ.get("RAVMEM_DATA_DIR", str(Path.home() / ".ravmem" / "server"))
|
|
135
|
+
candidates.append(Path(data_dir) / "ravmem.yaml")
|
|
136
|
+
candidates.append(Path.home() / ".ravmem" / "ravmem.yaml")
|
|
137
|
+
|
|
138
|
+
for path in candidates:
|
|
139
|
+
if path.exists():
|
|
140
|
+
return path
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _deep_merge(base: dict, override: dict) -> dict:
|
|
145
|
+
"""Recursively merge override into base. Override values take precedence."""
|
|
146
|
+
result = dict(base)
|
|
147
|
+
for key, val in override.items():
|
|
148
|
+
if key in result and isinstance(result[key], dict) and isinstance(val, dict):
|
|
149
|
+
result[key] = _deep_merge(result[key], val)
|
|
150
|
+
else:
|
|
151
|
+
result[key] = val
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _apply_env_overrides(data: dict) -> dict:
|
|
156
|
+
"""Apply env vars on top of parsed YAML data. Env vars always win."""
|
|
157
|
+
env_map: dict[str, list[tuple]] = {
|
|
158
|
+
"RAVMEM_LOG_LEVEL": [("server", "log_level")],
|
|
159
|
+
"RAVMEM_MAX_CONCURRENT_JOBS": [("server", "max_concurrent_jobs")],
|
|
160
|
+
"RAVMEM_DATA_DIR": [("server", "data_dir")],
|
|
161
|
+
"ARCADEDB_HOST": [("arcadedb", "host")],
|
|
162
|
+
"ARCADEDB_PORT": [("arcadedb", "port")],
|
|
163
|
+
"ARCADEDB_PASSWORD": [("arcadedb", "password")],
|
|
164
|
+
"RAVMEM_SYSTEM_DB": [("auth", "system_db")],
|
|
165
|
+
"RAVMEM_AUDIT_LOG_RETENTION_DAYS": [("auth", "audit_log_retention_days")],
|
|
166
|
+
"RAVMEM_CLUSTER_SEED": [("clustering", "seed")],
|
|
167
|
+
"RAVMEM_CLUSTER_RESOLUTION": [("clustering", "resolution")],
|
|
168
|
+
"RAVMEM_CLUSTER_MAX_SIZE": [("clustering", "max_cluster_size")],
|
|
169
|
+
"RAVMEM_CLUSTER_SUBCLUSTER_THRESHOLD": [("clustering", "subcluster_threshold")],
|
|
170
|
+
"RAVMEM_RATELIMIT_ENABLED": [("rate_limit", "enabled")],
|
|
171
|
+
"RAVMEM_RATELIMIT_AUTH": [
|
|
172
|
+
("rate_limit", "limits", "auth"),
|
|
173
|
+
("rate_limit", "tiers", "standard", "auth"),
|
|
174
|
+
],
|
|
175
|
+
"RAVMEM_RATELIMIT_WRITE": [
|
|
176
|
+
("rate_limit", "limits", "write"),
|
|
177
|
+
("rate_limit", "tiers", "standard", "write"),
|
|
178
|
+
],
|
|
179
|
+
"RAVMEM_RATELIMIT_INDEX": [
|
|
180
|
+
("rate_limit", "limits", "index"),
|
|
181
|
+
("rate_limit", "tiers", "standard", "index"),
|
|
182
|
+
],
|
|
183
|
+
"RAVMEM_RATELIMIT_READ": [
|
|
184
|
+
("rate_limit", "limits", "read"),
|
|
185
|
+
("rate_limit", "tiers", "standard", "read"),
|
|
186
|
+
],
|
|
187
|
+
"RAVMEM_RATELIMIT_MCP": [
|
|
188
|
+
("rate_limit", "limits", "mcp"),
|
|
189
|
+
("rate_limit", "tiers", "standard", "mcp"),
|
|
190
|
+
],
|
|
191
|
+
"RAVMEM_RATELIMIT_LOCKOUT_THRESHOLD": [("rate_limit", "lockout_threshold")],
|
|
192
|
+
"RAVMEM_RATELIMIT_LOCKOUT_DURATION": [("rate_limit", "lockout_duration")],
|
|
193
|
+
}
|
|
194
|
+
for env_key, paths in env_map.items():
|
|
195
|
+
val = os.environ.get(env_key)
|
|
196
|
+
if val is None:
|
|
197
|
+
continue
|
|
198
|
+
# Convert boolean strings
|
|
199
|
+
if isinstance(val, str) and val.lower() in ("true", "1", "yes"):
|
|
200
|
+
val = True
|
|
201
|
+
elif isinstance(val, str) and val.lower() in ("false", "0", "no"):
|
|
202
|
+
val = False
|
|
203
|
+
# Apply to all target paths
|
|
204
|
+
for path in paths:
|
|
205
|
+
node = data
|
|
206
|
+
for part in path[:-1]:
|
|
207
|
+
node = node.setdefault(part, {})
|
|
208
|
+
node[path[-1]] = val
|
|
209
|
+
return data
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def load_config(config_path: str | Path | None = None) -> RavMemConfig:
|
|
213
|
+
"""Load, merge, validate and return the resolved RavMemConfig."""
|
|
214
|
+
raw: dict = {}
|
|
215
|
+
|
|
216
|
+
found = _find_config_file(config_path)
|
|
217
|
+
if found:
|
|
218
|
+
logger.debug("Loading config from %s", found)
|
|
219
|
+
with open(found) as fh:
|
|
220
|
+
file_data = yaml.safe_load(fh) or {}
|
|
221
|
+
raw = _deep_merge(raw, file_data)
|
|
222
|
+
|
|
223
|
+
raw = _apply_env_overrides(raw)
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
return RavMemConfig.model_validate(raw)
|
|
227
|
+
except Exception as exc:
|
|
228
|
+
raise ValueError(f"Invalid ravmem config: {exc}") from exc
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# Module-level singleton — loaded lazily on first access
|
|
232
|
+
_config: RavMemConfig | None = None
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def get_config(config_path: str | Path | None = None) -> RavMemConfig:
|
|
236
|
+
"""Return the module-level config singleton, loading it if necessary."""
|
|
237
|
+
global _config
|
|
238
|
+
if _config is None:
|
|
239
|
+
_config = load_config(config_path)
|
|
240
|
+
return _config
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def reload_config(config_path: str | Path | None = None) -> RavMemConfig:
|
|
244
|
+
"""Force-reload config (useful in tests or after serve --config flag)."""
|
|
245
|
+
global _config
|
|
246
|
+
_config = load_config(config_path)
|
|
247
|
+
return _config
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ravmem-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: RavMem CLI — lightweight client for the RavMem code knowledge graph server
|
|
5
|
+
Author-email: RavMem <harpreet.singh@ravmem.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://www.ravmem.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: typer[all]>=0.12.0
|
|
18
|
+
Requires-Dist: httpx>=0.27.0
|
|
19
|
+
Requires-Dist: pyyaml>=6.0
|
|
20
|
+
Requires-Dist: pydantic>=2.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# ravmem-cli
|
|
27
|
+
|
|
28
|
+
Lightweight CLI client for the [RavMem](https://github.com/ravmem/ravmem) code knowledge graph server.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install ravmem-cli
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Point the CLI at your RavMem server
|
|
40
|
+
ravmem remote config --url http://localhost:7432
|
|
41
|
+
|
|
42
|
+
# Register a repository
|
|
43
|
+
ravmem remote add /path/to/repo --name my-project --default-branch main
|
|
44
|
+
|
|
45
|
+
# Index a repository
|
|
46
|
+
ravmem analyze /path/to/repo --project <ID>
|
|
47
|
+
|
|
48
|
+
# Search symbols
|
|
49
|
+
ravmem search "authentication handler" --project <ID>
|
|
50
|
+
|
|
51
|
+
# Blast-radius analysis
|
|
52
|
+
ravmem impact MyClass.my_method --project <ID>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Requirements
|
|
56
|
+
|
|
57
|
+
- Python 3.10+
|
|
58
|
+
- A running [ravmem-server](https://pypi.org/project/ravmem-server/) instance
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
ravmem/config.py,sha256=SkoObiRD-kyHWkI94l3wJv1bURiHTQ1a6YSlrlBCvuk,3284
|
|
2
|
+
ravmem/config_loader.py,sha256=vvWJh3H0sUJgNMkaPnXLZNNPR_-xUozMdYurtpy6t2E,8757
|
|
3
|
+
ravmem/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
ravmem/cli/analyze.py,sha256=asBWizsgTLNEc3rV8zohSJn7Yh83vTX81qMF_MO_dQU,16322
|
|
5
|
+
ravmem/cli/auth.py,sha256=avYt_tgM6JtgAAl5LHTCbfFoa-8_4IYu6zAeAnyn5pw,21887
|
|
6
|
+
ravmem/cli/browse.py,sha256=9xYb0TBjUqmTMWKkd8a-MRCVi0TlgDak1Q2UrW2mlBA,4854
|
|
7
|
+
ravmem/cli/config_cmd.py,sha256=Cl-dm5KdfLOuGYL12j-SZGjGdDl0fdoPMVhvepzK4nI,2210
|
|
8
|
+
ravmem/cli/diff.py,sha256=m2Je-j9V1Yf4TSAvLUXBMB58Ff48IjD6Vm1qbI8NhDQ,3901
|
|
9
|
+
ravmem/cli/git_local.py,sha256=WodcZZeEgfnQX9w8rw70o6W56pkNJUHEgkrtjHvBBzM,4117
|
|
10
|
+
ravmem/cli/impact.py,sha256=JjTr_VQY9jGOFA1ZUZxXmn1mzv5ALmpNkuMhLw3VE4w,4021
|
|
11
|
+
ravmem/cli/info.py,sha256=PY1WCEJoKYa1UQ7u0ZdUdJb_KKmgI-st46D7M0Cwmuk,2492
|
|
12
|
+
ravmem/cli/kg.py,sha256=FnWEjCUq0Qw9TDRNk7zhvOrdl1nANNuh5LyUBoanwSU,30978
|
|
13
|
+
ravmem/cli/main.py,sha256=L5WFFptXvzssuc388Az_e8HGMGIeEWlrX6lSxH01_X0,2952
|
|
14
|
+
ravmem/cli/remote.py,sha256=l616Cs_txtGAP5udFR6JtZGo4tPpIf8ttNsrCh612ms,19622
|
|
15
|
+
ravmem/cli/search.py,sha256=ht56_YvblfXBibUYyRIJKynQnJQVJI28HEx6AdqbtbQ,3081
|
|
16
|
+
ravmem/cli/serve.py,sha256=HAsy3N4EVi7BPntg9220PowMLke65zgm7s31SjtT8bs,1503
|
|
17
|
+
ravmem/client/__init__.py,sha256=kqQNEjY_MMB4U7V7jeQVvwpyAH8My_indn47BERBgxk,71
|
|
18
|
+
ravmem/client/api.py,sha256=evBGJQOrmb6iBAHBrqYQVU1N637ltUPRj-aooXN-cAg,9417
|
|
19
|
+
ravmem/client/config.py,sha256=fvpB9CYxjsI-lM5gmuLtat73xqQFUEWzJsXRUt_fJ90,1405
|
|
20
|
+
ravmem_cli-0.1.0.dist-info/licenses/LICENSE,sha256=wI3Yj7pnbSHXvrmP3869k5Ft3ln5C83l-1w-RN0R14I,1063
|
|
21
|
+
ravmem_cli-0.1.0.dist-info/METADATA,sha256=5RUE_oivL9LW-P14cr4X82CjIhs9t0HSY0kFOfEmpWg,1696
|
|
22
|
+
ravmem_cli-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
23
|
+
ravmem_cli-0.1.0.dist-info/entry_points.txt,sha256=ldUYV83DRLplX6etlK-gvrs2MpBpWgk8KIGVMpNL354,47
|
|
24
|
+
ravmem_cli-0.1.0.dist-info/top_level.txt,sha256=nSWRo9Gm3vH4guHC29V3rbB9smoQm3zYxFhbrqLH8jo,7
|
|
25
|
+
ravmem_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 RavMem
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ravmem
|