memorius 0.4.2__tar.gz → 0.4.3__tar.gz

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 (71) hide show
  1. {memorius-0.4.2/memorius.egg-info → memorius-0.4.3}/PKG-INFO +1 -1
  2. {memorius-0.4.2 → memorius-0.4.3}/memorius/__init__.py +1 -1
  3. {memorius-0.4.2 → memorius-0.4.3}/memorius/config.py +4 -1
  4. {memorius-0.4.2 → memorius-0.4.3}/memorius/web_search.py +66 -0
  5. {memorius-0.4.2 → memorius-0.4.3/memorius.egg-info}/PKG-INFO +1 -1
  6. {memorius-0.4.2 → memorius-0.4.3}/pyproject.toml +1 -1
  7. {memorius-0.4.2 → memorius-0.4.3}/tests/test_web_search.py +41 -0
  8. {memorius-0.4.2 → memorius-0.4.3}/LICENSE +0 -0
  9. {memorius-0.4.2 → memorius-0.4.3}/MANIFEST.in +0 -0
  10. {memorius-0.4.2 → memorius-0.4.3}/README.md +0 -0
  11. {memorius-0.4.2 → memorius-0.4.3}/examples/quickstart.py +0 -0
  12. {memorius-0.4.2 → memorius-0.4.3}/hooks/memorius-hook.sh +0 -0
  13. {memorius-0.4.2 → memorius-0.4.3}/manifest.yaml +0 -0
  14. {memorius-0.4.2 → memorius-0.4.3}/memorius/cli/__init__.py +0 -0
  15. {memorius-0.4.2 → memorius-0.4.3}/memorius/cli/main.py +0 -0
  16. {memorius-0.4.2 → memorius-0.4.3}/memorius/cli/obsidian.py +0 -0
  17. {memorius-0.4.2 → memorius-0.4.3}/memorius/consolidation.py +0 -0
  18. {memorius-0.4.2 → memorius-0.4.3}/memorius/context_inject.py +0 -0
  19. {memorius-0.4.2 → memorius-0.4.3}/memorius/data/LICENSE +0 -0
  20. {memorius-0.4.2 → memorius-0.4.3}/memorius/data/__init__.py +0 -0
  21. {memorius-0.4.2 → memorius-0.4.3}/memorius/data/examples/__init__.py +0 -0
  22. {memorius-0.4.2 → memorius-0.4.3}/memorius/data/examples/quickstart.py +0 -0
  23. {memorius-0.4.2 → memorius-0.4.3}/memorius/data/manifest.yaml +0 -0
  24. {memorius-0.4.2 → memorius-0.4.3}/memorius/data/memorius-hook.sh +0 -0
  25. {memorius-0.4.2 → memorius-0.4.3}/memorius/embeddings.py +0 -0
  26. {memorius-0.4.2 → memorius-0.4.3}/memorius/factcheck.py +0 -0
  27. {memorius-0.4.2 → memorius-0.4.3}/memorius/graph.py +0 -0
  28. {memorius-0.4.2 → memorius-0.4.3}/memorius/hooks/__init__.py +0 -0
  29. {memorius-0.4.2 → memorius-0.4.3}/memorius/hooks/adapters/__init__.py +0 -0
  30. {memorius-0.4.2 → memorius-0.4.3}/memorius/hooks/adapters/base.py +0 -0
  31. {memorius-0.4.2 → memorius-0.4.3}/memorius/hooks/adapters/claude_code.py +0 -0
  32. {memorius-0.4.2 → memorius-0.4.3}/memorius/hooks/adapters/codex.py +0 -0
  33. {memorius-0.4.2 → memorius-0.4.3}/memorius/hooks/adapters/gemini.py +0 -0
  34. {memorius-0.4.2 → memorius-0.4.3}/memorius/hooks/adapters/generic.py +0 -0
  35. {memorius-0.4.2 → memorius-0.4.3}/memorius/hooks/adapters/openclaude.py +0 -0
  36. {memorius-0.4.2 → memorius-0.4.3}/memorius/hooks/adapters/openclaw.py +0 -0
  37. {memorius-0.4.2 → memorius-0.4.3}/memorius/hooks/adapters/opencode.py +0 -0
  38. {memorius-0.4.2 → memorius-0.4.3}/memorius/hooks/adapters/pi.py +0 -0
  39. {memorius-0.4.2 → memorius-0.4.3}/memorius/hooks/cli.py +0 -0
  40. {memorius-0.4.2 → memorius-0.4.3}/memorius/hooks/engine.py +0 -0
  41. {memorius-0.4.2 → memorius-0.4.3}/memorius/hooks/models.py +0 -0
  42. {memorius-0.4.2 → memorius-0.4.3}/memorius/llm_extract.py +0 -0
  43. {memorius-0.4.2 → memorius-0.4.3}/memorius/mcp_server.py +0 -0
  44. {memorius-0.4.2 → memorius-0.4.3}/memorius/meta_store.py +0 -0
  45. {memorius-0.4.2 → memorius-0.4.3}/memorius/model_download.py +0 -0
  46. {memorius-0.4.2 → memorius-0.4.3}/memorius/models.py +0 -0
  47. {memorius-0.4.2 → memorius-0.4.3}/memorius/normalizers/__init__.py +0 -0
  48. {memorius-0.4.2 → memorius-0.4.3}/memorius/normalizers/cli.py +0 -0
  49. {memorius-0.4.2 → memorius-0.4.3}/memorius/plugin_gen/__init__.py +0 -0
  50. {memorius-0.4.2 → memorius-0.4.3}/memorius/plugin_gen/cli.py +0 -0
  51. {memorius-0.4.2 → memorius-0.4.3}/memorius/rest_server.py +0 -0
  52. {memorius-0.4.2 → memorius-0.4.3}/memorius/session.py +0 -0
  53. {memorius-0.4.2 → memorius-0.4.3}/memorius/temporal.py +0 -0
  54. {memorius-0.4.2 → memorius-0.4.3}/memorius/utils.py +0 -0
  55. {memorius-0.4.2 → memorius-0.4.3}/memorius/validation.py +0 -0
  56. {memorius-0.4.2 → memorius-0.4.3}/memorius/vault.py +0 -0
  57. {memorius-0.4.2 → memorius-0.4.3}/memorius/vector_store.py +0 -0
  58. {memorius-0.4.2 → memorius-0.4.3}/memorius.egg-info/SOURCES.txt +0 -0
  59. {memorius-0.4.2 → memorius-0.4.3}/memorius.egg-info/dependency_links.txt +0 -0
  60. {memorius-0.4.2 → memorius-0.4.3}/memorius.egg-info/entry_points.txt +0 -0
  61. {memorius-0.4.2 → memorius-0.4.3}/memorius.egg-info/requires.txt +0 -0
  62. {memorius-0.4.2 → memorius-0.4.3}/memorius.egg-info/top_level.txt +0 -0
  63. {memorius-0.4.2 → memorius-0.4.3}/setup.cfg +0 -0
  64. {memorius-0.4.2 → memorius-0.4.3}/tests/test_core.py +0 -0
  65. {memorius-0.4.2 → memorius-0.4.3}/tests/test_encoding_regression.py +0 -0
  66. {memorius-0.4.2 → memorius-0.4.3}/tests/test_features.py +0 -0
  67. {memorius-0.4.2 → memorius-0.4.3}/tests/test_graph.py +0 -0
  68. {memorius-0.4.2 → memorius-0.4.3}/tests/test_integration.py +0 -0
  69. {memorius-0.4.2 → memorius-0.4.3}/tests/test_polish_regressions.py +0 -0
  70. {memorius-0.4.2 → memorius-0.4.3}/tests/test_temporal.py +0 -0
  71. {memorius-0.4.2 → memorius-0.4.3}/tests/test_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memorius
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: Self-contained memory vault for any AI agent — vector search, session diaries, and agent-agnostic hooks.
5
5
  Author-email: dimonapatrick243 <dimona.patrick@gmail.com>
6
6
  License-Expression: MIT
@@ -26,4 +26,4 @@ Architecture:
26
26
  memorius: Main CLI — init, mine, search, diary, status, serve, hook
27
27
  """
28
28
 
29
- __version__ = "0.4.2"
29
+ __version__ = "0.4.3"
@@ -40,7 +40,8 @@ hooks:
40
40
 
41
41
  retrieval:
42
42
  web_fallback: false # opt-in: augment thin local recall with web (local-first / privacy)
43
- web_provider: duckduckgo # duckduckgo (keyless) | mock (tests)
43
+ web_provider: duckduckgo # duckduckgo (keyless) | tavily (keyed) | mock (tests)
44
+ tavily_api_key: null # or set TAVILY_API_KEY env var
44
45
  web_min_results: 1 # if ZERO local hits, fall back to web
45
46
  web_max_results: 5 # max web results to return
46
47
  """
@@ -107,6 +108,7 @@ def _ensure_defaults(config: dict[str, Any]):
107
108
  retrieval = config.setdefault("retrieval", {})
108
109
  retrieval.setdefault("web_fallback", False)
109
110
  retrieval.setdefault("web_provider", "duckduckgo")
111
+ retrieval.setdefault("tavily_api_key", None)
110
112
  retrieval.setdefault("web_min_results", 1)
111
113
  retrieval.setdefault("web_max_results", 5)
112
114
 
@@ -126,6 +128,7 @@ def _apply_env_overrides(config: dict[str, Any]):
126
128
  "MEMORIUS_DEFAULT_VAULT": ("vault", "default"),
127
129
  "MEMORIUS_WEB_FALLBACK": ("retrieval", "web_fallback"),
128
130
  "MEMORIUS_WEB_PROVIDER": ("retrieval", "web_provider"),
131
+ "MEMORIUS_TAVILY_API_KEY": ("retrieval", "tavily_api_key"),
129
132
  }
130
133
  for env_key, (section, key) in overrides.items():
131
134
  val = os.environ.get(env_key)
@@ -16,6 +16,7 @@ from __future__ import annotations
16
16
 
17
17
  import html
18
18
  import logging
19
+ import os
19
20
  import urllib.parse
20
21
  import urllib.request
21
22
  from abc import ABC, abstractmethod
@@ -82,6 +83,68 @@ class DuckDuckGoProvider(WebSearchProvider):
82
83
  return _parse_ddg_lite(raw, max_results)
83
84
 
84
85
 
86
+ class TavilyProvider(WebSearchProvider):
87
+ """Tavily web search — API key required, best signal-to-noise for agents.
88
+
89
+ Key from ``api_key`` arg, config ``retrieval.tavily_api_key``, or
90
+ the ``TAVILY_API_KEY`` env var. Missing key -> warns + returns [].
91
+ """
92
+
93
+ name = "tavily"
94
+ _ENDPOINT = "https://api.tavily.com/search"
95
+
96
+ def __init__(self, api_key: str | None = None, timeout: float = 15.0):
97
+ self._api_key = api_key
98
+ self.timeout = timeout
99
+
100
+ def _resolve_key(self) -> str:
101
+ key = self._api_key or os.environ.get("TAVILY_API_KEY")
102
+ if not key:
103
+ raise RuntimeError(
104
+ "Tavily requires an API key: set TAVILY_API_KEY or "
105
+ "retrieval.tavily_api_key in config."
106
+ )
107
+ return key
108
+
109
+ def search(self, query: str, max_results: int = 5) -> list[WebResult]:
110
+ import json
111
+ try:
112
+ key = self._resolve_key()
113
+ except RuntimeError as e:
114
+ logger.warning(str(e))
115
+ return []
116
+ payload = {
117
+ "api_key": key,
118
+ "query": query,
119
+ "max_results": max_results,
120
+ "search_depth": "basic",
121
+ "include_answer": False,
122
+ "include_raw_content": False,
123
+ }
124
+ data = json.dumps(payload).encode()
125
+ req = urllib.request.Request(
126
+ self._ENDPOINT,
127
+ data=data,
128
+ headers={"Content-Type": "application/json"},
129
+ )
130
+ try:
131
+ with urllib.request.urlopen(req, timeout=self.timeout) as resp:
132
+ body = json.loads(resp.read().decode("utf-8", "replace"))
133
+ except Exception as e: # network/timeout/auth — never crash the CLI
134
+ logger.warning("Tavily search failed: %s", e)
135
+ return []
136
+ out: list[WebResult] = []
137
+ for r in body.get("results", [])[:max_results]:
138
+ out.append(
139
+ WebResult(
140
+ title=r.get("title", ""),
141
+ url=r.get("url", ""),
142
+ snippet=r.get("content", ""),
143
+ )
144
+ )
145
+ return out
146
+
147
+
85
148
  class MockProvider(WebSearchProvider):
86
149
  """Test double: returns canned results from a ``query -> [WebResult]`` map."""
87
150
 
@@ -164,6 +227,9 @@ def get_web_provider(
164
227
  """Build the configured web provider, or ``None`` if unavailable."""
165
228
  retrieval = config.get("retrieval", {}) if isinstance(config, dict) else {}
166
229
  name = (provider or retrieval.get("web_provider", "duckduckgo")).lower()
230
+ if name == "tavily":
231
+ retrieval = config.get("retrieval", {}) if isinstance(config, dict) else {}
232
+ return TavilyProvider(api_key=retrieval.get("tavily_api_key"))
167
233
  if name == "mock":
168
234
  return MockProvider()
169
235
  if name == "duckduckgo":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memorius
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: Self-contained memory vault for any AI agent — vector search, session diaries, and agent-agnostic hooks.
5
5
  Author-email: dimonapatrick243 <dimona.patrick@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "memorius"
3
- version = "0.4.2"
3
+ version = "0.4.3"
4
4
  description = "Self-contained memory vault for any AI agent — vector search, session diaries, and agent-agnostic hooks."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -9,6 +9,7 @@ from pathlib import Path
9
9
  from memorius.web_search import (
10
10
  DuckDuckGoProvider,
11
11
  MockProvider,
12
+ TavilyProvider,
12
13
  WebResult,
13
14
  _parse_ddg_lite,
14
15
  get_web_provider,
@@ -67,11 +68,51 @@ def test_get_web_provider_mock():
67
68
  assert isinstance(p, MockProvider)
68
69
 
69
70
 
71
+ def test_get_web_provider_tavily():
72
+ p = get_web_provider(_cfg(web_provider="tavily"))
73
+ assert isinstance(p, TavilyProvider)
74
+
75
+
70
76
  def test_get_web_provider_unknown_falls_back_to_duckduckgo(caplog):
71
77
  p = get_web_provider(_cfg(web_provider="bogus"))
72
78
  assert isinstance(p, DuckDuckGoProvider)
73
79
 
74
80
 
81
+ def test_tavily_provider_parses_response(monkeypatch):
82
+ import json
83
+ import urllib.request
84
+
85
+ fake = json.dumps({
86
+ "results": [
87
+ {"title": "Tavily A", "url": "https://a", "content": "snippet A"},
88
+ {"title": "Tavily B", "url": "https://b", "content": "snippet B"},
89
+ ]
90
+ }).encode()
91
+
92
+ class _Resp:
93
+ def read(self):
94
+ return fake
95
+ def __enter__(self):
96
+ return self
97
+ def __exit__(self, *a):
98
+ return False
99
+
100
+ monkeypatch.setattr(urllib.request, "urlopen", lambda *a, **k: _Resp())
101
+ p = TavilyProvider(api_key="tvly-x")
102
+ out = p.search("query", max_results=5)
103
+ assert len(out) == 2
104
+ assert out[0].title == "Tavily A"
105
+ assert out[0].url == "https://a"
106
+ assert out[0].snippet == "snippet A"
107
+
108
+
109
+ def test_tavily_provider_no_key_returns_empty(monkeypatch, capsys):
110
+ monkeypatch.delenv("TAVILY_API_KEY", raising=False)
111
+ p = TavilyProvider(api_key=None)
112
+ assert p.search("q") == [] # warns, never crashes
113
+
114
+
115
+
75
116
  # ── Fallback decision ─────────────────────────────────────────────────────
76
117
  def test_should_fallback_true_when_local_empty():
77
118
  assert should_fallback(0, _cfg()) is True
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes