@agxnte/reidx 2.0.2

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 (135) hide show
  1. package/README.md +724 -0
  2. package/bin/reidx.js +33 -0
  3. package/package.json +40 -0
  4. package/pyproject.toml +45 -0
  5. package/scripts/find-python.js +24 -0
  6. package/scripts/postinstall.js +35 -0
  7. package/src/reidx/__init__.py +3 -0
  8. package/src/reidx/__main__.py +9 -0
  9. package/src/reidx/__pycache__/__init__.cpython-312.pyc +0 -0
  10. package/src/reidx/app/__init__.py +3 -0
  11. package/src/reidx/app/__pycache__/__init__.cpython-312.pyc +0 -0
  12. package/src/reidx/app/__pycache__/commands.cpython-312.pyc +0 -0
  13. package/src/reidx/app/commands.py +221 -0
  14. package/src/reidx/automation/__init__.py +3 -0
  15. package/src/reidx/automation/__pycache__/__init__.cpython-312.pyc +0 -0
  16. package/src/reidx/automation/__pycache__/exec.cpython-312.pyc +0 -0
  17. package/src/reidx/automation/exec.py +35 -0
  18. package/src/reidx/config/__init__.py +10 -0
  19. package/src/reidx/config/__pycache__/__init__.cpython-312.pyc +0 -0
  20. package/src/reidx/config/__pycache__/loader.cpython-312.pyc +0 -0
  21. package/src/reidx/config/__pycache__/models.cpython-312.pyc +0 -0
  22. package/src/reidx/config/__pycache__/settings.cpython-312.pyc +0 -0
  23. package/src/reidx/config/loader.py +104 -0
  24. package/src/reidx/config/models.py +49 -0
  25. package/src/reidx/config/settings.py +127 -0
  26. package/src/reidx/deepreid/__init__.py +8 -0
  27. package/src/reidx/deepreid/__pycache__/__init__.cpython-312.pyc +0 -0
  28. package/src/reidx/deepreid/__pycache__/pipeline.cpython-312.pyc +0 -0
  29. package/src/reidx/deepreid/pipeline.py +221 -0
  30. package/src/reidx/diagnostics/__init__.py +0 -0
  31. package/src/reidx/diagnostics/__pycache__/__init__.cpython-312.pyc +0 -0
  32. package/src/reidx/diagnostics/__pycache__/logger.cpython-312.pyc +0 -0
  33. package/src/reidx/diagnostics/logger.py +56 -0
  34. package/src/reidx/goals/__init__.py +21 -0
  35. package/src/reidx/goals/__pycache__/__init__.cpython-312.pyc +0 -0
  36. package/src/reidx/goals/__pycache__/models.cpython-312.pyc +0 -0
  37. package/src/reidx/goals/__pycache__/store.cpython-312.pyc +0 -0
  38. package/src/reidx/goals/models.py +101 -0
  39. package/src/reidx/goals/store.py +233 -0
  40. package/src/reidx/integrations/__init__.py +3 -0
  41. package/src/reidx/integrations/mcp.py +70 -0
  42. package/src/reidx/nyx/__init__.py +3 -0
  43. package/src/reidx/nyx/__pycache__/__init__.cpython-312.pyc +0 -0
  44. package/src/reidx/nyx/__pycache__/persona.cpython-312.pyc +0 -0
  45. package/src/reidx/nyx/persona.py +35 -0
  46. package/src/reidx/policy/__init__.py +15 -0
  47. package/src/reidx/policy/__pycache__/__init__.cpython-312.pyc +0 -0
  48. package/src/reidx/policy/__pycache__/engine.cpython-312.pyc +0 -0
  49. package/src/reidx/policy/__pycache__/models.cpython-312.pyc +0 -0
  50. package/src/reidx/policy/engine.py +101 -0
  51. package/src/reidx/policy/models.py +35 -0
  52. package/src/reidx/provider/__init__.py +16 -0
  53. package/src/reidx/provider/__pycache__/__init__.cpython-312.pyc +0 -0
  54. package/src/reidx/provider/__pycache__/_http.cpython-312.pyc +0 -0
  55. package/src/reidx/provider/__pycache__/anthropic.cpython-312.pyc +0 -0
  56. package/src/reidx/provider/__pycache__/base.cpython-312.pyc +0 -0
  57. package/src/reidx/provider/__pycache__/ollama.cpython-312.pyc +0 -0
  58. package/src/reidx/provider/__pycache__/openai.cpython-312.pyc +0 -0
  59. package/src/reidx/provider/__pycache__/registry.cpython-312.pyc +0 -0
  60. package/src/reidx/provider/__pycache__/store.cpython-312.pyc +0 -0
  61. package/src/reidx/provider/__pycache__/stub.cpython-312.pyc +0 -0
  62. package/src/reidx/provider/_http.py +29 -0
  63. package/src/reidx/provider/anthropic.py +176 -0
  64. package/src/reidx/provider/base.py +50 -0
  65. package/src/reidx/provider/ollama.py +109 -0
  66. package/src/reidx/provider/openai.py +144 -0
  67. package/src/reidx/provider/registry.py +88 -0
  68. package/src/reidx/provider/store.py +141 -0
  69. package/src/reidx/provider/stub.py +60 -0
  70. package/src/reidx/provider_manager/__init__.py +40 -0
  71. package/src/reidx/provider_manager/__pycache__/__init__.cpython-312.pyc +0 -0
  72. package/src/reidx/provider_manager/__pycache__/catalog.cpython-312.pyc +0 -0
  73. package/src/reidx/provider_manager/__pycache__/database.cpython-312.pyc +0 -0
  74. package/src/reidx/provider_manager/__pycache__/keychain.cpython-312.pyc +0 -0
  75. package/src/reidx/provider_manager/__pycache__/palette.cpython-312.pyc +0 -0
  76. package/src/reidx/provider_manager/catalog.py +330 -0
  77. package/src/reidx/provider_manager/database.py +234 -0
  78. package/src/reidx/provider_manager/keychain.py +102 -0
  79. package/src/reidx/provider_manager/palette.py +831 -0
  80. package/src/reidx/runtime/__init__.py +5 -0
  81. package/src/reidx/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  82. package/src/reidx/runtime/__pycache__/agent.cpython-312.pyc +0 -0
  83. package/src/reidx/runtime/__pycache__/orchestrator.cpython-312.pyc +0 -0
  84. package/src/reidx/runtime/__pycache__/reasoning.cpython-312.pyc +0 -0
  85. package/src/reidx/runtime/__pycache__/state.cpython-312.pyc +0 -0
  86. package/src/reidx/runtime/__pycache__/subagent.cpython-312.pyc +0 -0
  87. package/src/reidx/runtime/agent.py +198 -0
  88. package/src/reidx/runtime/orchestrator.py +244 -0
  89. package/src/reidx/runtime/reasoning.py +77 -0
  90. package/src/reidx/runtime/state.py +32 -0
  91. package/src/reidx/runtime/subagent.py +174 -0
  92. package/src/reidx/session/__init__.py +4 -0
  93. package/src/reidx/session/__pycache__/__init__.cpython-312.pyc +0 -0
  94. package/src/reidx/session/__pycache__/models.cpython-312.pyc +0 -0
  95. package/src/reidx/session/__pycache__/store.cpython-312.pyc +0 -0
  96. package/src/reidx/session/models.py +43 -0
  97. package/src/reidx/session/store.py +101 -0
  98. package/src/reidx/tasks/__init__.py +4 -0
  99. package/src/reidx/tasks/__pycache__/__init__.cpython-312.pyc +0 -0
  100. package/src/reidx/tasks/__pycache__/models.cpython-312.pyc +0 -0
  101. package/src/reidx/tasks/__pycache__/store.cpython-312.pyc +0 -0
  102. package/src/reidx/tasks/models.py +44 -0
  103. package/src/reidx/tasks/store.py +86 -0
  104. package/src/reidx/tools/__init__.py +44 -0
  105. package/src/reidx/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  106. package/src/reidx/tools/__pycache__/base.cpython-312.pyc +0 -0
  107. package/src/reidx/tools/__pycache__/file_tools.cpython-312.pyc +0 -0
  108. package/src/reidx/tools/__pycache__/registry.cpython-312.pyc +0 -0
  109. package/src/reidx/tools/__pycache__/shell_tool.cpython-312.pyc +0 -0
  110. package/src/reidx/tools/__pycache__/spawn_agent.cpython-312.pyc +0 -0
  111. package/src/reidx/tools/__pycache__/web_tools.cpython-312.pyc +0 -0
  112. package/src/reidx/tools/base.py +84 -0
  113. package/src/reidx/tools/file_tools.py +222 -0
  114. package/src/reidx/tools/registry.py +45 -0
  115. package/src/reidx/tools/shell_tool.py +75 -0
  116. package/src/reidx/tools/spawn_agent.py +185 -0
  117. package/src/reidx/tools/web_tools.py +216 -0
  118. package/src/reidx/ui/__init__.py +41 -0
  119. package/src/reidx/ui/__pycache__/__init__.cpython-312.pyc +0 -0
  120. package/src/reidx/ui/__pycache__/app.cpython-312.pyc +0 -0
  121. package/src/reidx/ui/__pycache__/commands.cpython-312.pyc +0 -0
  122. package/src/reidx/ui/__pycache__/render.cpython-312.pyc +0 -0
  123. package/src/reidx/ui/__pycache__/repl.cpython-312.pyc +0 -0
  124. package/src/reidx/ui/__pycache__/theme.cpython-312.pyc +0 -0
  125. package/src/reidx/ui/app.py +1343 -0
  126. package/src/reidx/ui/commands.py +663 -0
  127. package/src/reidx/ui/render.py +517 -0
  128. package/src/reidx/ui/repl.py +14 -0
  129. package/src/reidx/ui/theme.py +126 -0
  130. package/src/reidx/workflows/__init__.py +4 -0
  131. package/src/reidx/workflows/__pycache__/__init__.cpython-312.pyc +0 -0
  132. package/src/reidx/workflows/__pycache__/models.cpython-312.pyc +0 -0
  133. package/src/reidx/workflows/__pycache__/store.cpython-312.pyc +0 -0
  134. package/src/reidx/workflows/models.py +19 -0
  135. package/src/reidx/workflows/store.py +56 -0
@@ -0,0 +1,234 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import uuid
6
+ from dataclasses import asdict, dataclass, field
7
+ from pathlib import Path
8
+
9
+ from reidx.diagnostics.logger import get_logger
10
+ from reidx.provider_manager import keychain
11
+
12
+ log = get_logger("reidx.provider_manager.database")
13
+
14
+ SCHEMA_VERSION = 1
15
+ DB_FILENAME = "providers.db"
16
+ LEGACY_FILENAME = "providers.json"
17
+
18
+
19
+ @dataclass
20
+ class StoredKey:
21
+ id: str
22
+ label: str
23
+ encrypted_key: str
24
+
25
+ def decrypt(self) -> str:
26
+ return keychain.decrypt(self.encrypted_key)
27
+
28
+
29
+ @dataclass
30
+ class StoredProvider:
31
+ name: str
32
+ kind: str
33
+ base_url: str = ""
34
+ default_model: str = ""
35
+ auth_method: str = "bearer"
36
+ extra_headers: dict[str, str] = field(default_factory=dict)
37
+ catalog_id: str | None = None
38
+ keys: list[StoredKey] = field(default_factory=list)
39
+ active_key_id: str | None = None
40
+
41
+ def active_key(self) -> StoredKey | None:
42
+ if not self.active_key_id:
43
+ return self.keys[0] if self.keys else None
44
+ for k in self.keys:
45
+ if k.id == self.active_key_id:
46
+ return k
47
+ return self.keys[0] if self.keys else None
48
+
49
+ def decrypted_api_key(self) -> str:
50
+ k = self.active_key()
51
+ return k.decrypt() if k else ""
52
+
53
+
54
+ class ProviderDatabase:
55
+ def __init__(self, storage_root: Path) -> None:
56
+ self.path = Path(storage_root) / DB_FILENAME
57
+ self.legacy_path = Path(storage_root) / LEGACY_FILENAME
58
+ self._migrate_legacy_if_needed()
59
+
60
+ def _migrate_legacy_if_needed(self) -> None:
61
+ if self.path.exists() or not self.legacy_path.exists():
62
+ return
63
+ try:
64
+ data = json.loads(self.legacy_path.read_text(encoding="utf-8"))
65
+ except (OSError, ValueError):
66
+ log.warning("failed to read legacy providers.json; skipping migration")
67
+ return
68
+ records = data.get("providers", [])
69
+ if not records:
70
+ return
71
+ log.info("migrating %d provider(s) from legacy providers.json", len(records))
72
+ providers: list[dict] = []
73
+ for entry in records:
74
+ name = entry.get("name", "")
75
+ if not name:
76
+ continue
77
+ api_key = entry.get("api_key", "")
78
+ keys: list[dict] = []
79
+ active_key_id = None
80
+ if api_key:
81
+ kid = uuid.uuid4().hex[:12]
82
+ keys.append({
83
+ "id": kid,
84
+ "label": "Imported",
85
+ "encrypted_key": keychain.encrypt(api_key),
86
+ })
87
+ active_key_id = kid
88
+ providers.append({
89
+ "name": name,
90
+ "kind": entry.get("kind", "openai-compatible"),
91
+ "base_url": entry.get("base_url", ""),
92
+ "default_model": entry.get("default_model", ""),
93
+ "auth_method": "bearer",
94
+ "extra_headers": {},
95
+ "catalog_id": None,
96
+ "keys": keys,
97
+ "active_key_id": active_key_id,
98
+ })
99
+ self._write_raw({"version": SCHEMA_VERSION, "providers": providers})
100
+ try:
101
+ os.rename(self.legacy_path, self.legacy_path.with_suffix(".json.bak"))
102
+ except OSError:
103
+ pass
104
+ log.info("migration complete; legacy file backed up")
105
+
106
+ def _read_raw(self) -> dict:
107
+ if not self.path.exists():
108
+ return {"version": SCHEMA_VERSION, "providers": []}
109
+ try:
110
+ data = json.loads(self.path.read_text(encoding="utf-8"))
111
+ except (OSError, ValueError):
112
+ log.exception("failed to read providers.db; treating as empty")
113
+ return {"version": SCHEMA_VERSION, "providers": []}
114
+ if data.get("version", 0) > SCHEMA_VERSION:
115
+ log.warning("providers.db version %s > supported %s; some features may not work",
116
+ data.get("version"), SCHEMA_VERSION)
117
+ return data
118
+
119
+ def _write_raw(self, data: dict) -> None:
120
+ keychain.secure_write(self.path, json.dumps(data, indent=2))
121
+
122
+ def _to_stored(self, entry: dict) -> StoredProvider:
123
+ keys = [StoredKey(
124
+ id=k.get("id", ""),
125
+ label=k.get("label", ""),
126
+ encrypted_key=k.get("encrypted_key", ""),
127
+ ) for k in entry.get("keys", [])]
128
+ return StoredProvider(
129
+ name=entry.get("name", ""),
130
+ kind=entry.get("kind", "openai-compatible"),
131
+ base_url=entry.get("base_url", ""),
132
+ default_model=entry.get("default_model", ""),
133
+ auth_method=entry.get("auth_method", "bearer"),
134
+ extra_headers=entry.get("extra_headers", {}),
135
+ catalog_id=entry.get("catalog_id"),
136
+ keys=keys,
137
+ active_key_id=entry.get("active_key_id"),
138
+ )
139
+
140
+ def _to_dict(self, p: StoredProvider) -> dict:
141
+ return {
142
+ "name": p.name,
143
+ "kind": p.kind,
144
+ "base_url": p.base_url,
145
+ "default_model": p.default_model,
146
+ "auth_method": p.auth_method,
147
+ "extra_headers": p.extra_headers,
148
+ "catalog_id": p.catalog_id,
149
+ "keys": [asdict(k) for k in p.keys],
150
+ "active_key_id": p.active_key_id,
151
+ }
152
+
153
+ def list_providers(self) -> list[StoredProvider]:
154
+ data = self._read_raw()
155
+ return [self._to_stored(e) for e in data.get("providers", [])]
156
+
157
+ def get_provider(self, name: str) -> StoredProvider | None:
158
+ for p in self.list_providers():
159
+ if p.name == name:
160
+ return p
161
+ return None
162
+
163
+ def save_provider(self, provider: StoredProvider) -> None:
164
+ data = self._read_raw()
165
+ providers = data.get("providers", [])
166
+ providers = [e for e in providers if e.get("name") != provider.name]
167
+ providers.append(self._to_dict(provider))
168
+ data["providers"] = providers
169
+ self._write_raw(data)
170
+
171
+ def remove_provider(self, name: str) -> bool:
172
+ data = self._read_raw()
173
+ providers = data.get("providers", [])
174
+ remaining = [e for e in providers if e.get("name") != name]
175
+ if len(remaining) == len(providers):
176
+ return False
177
+ data["providers"] = remaining
178
+ self._write_raw(data)
179
+ return True
180
+
181
+ def add_key(self, provider_name: str, label: str, api_key: str) -> StoredKey | None:
182
+ p = self.get_provider(provider_name)
183
+ if p is None:
184
+ return None
185
+ key = StoredKey(
186
+ id=uuid.uuid4().hex[:12],
187
+ label=label,
188
+ encrypted_key=keychain.encrypt(api_key),
189
+ )
190
+ p.keys.append(key)
191
+ if p.active_key_id is None:
192
+ p.active_key_id = key.id
193
+ self.save_provider(p)
194
+ return key
195
+
196
+ def remove_key(self, provider_name: str, key_id: str) -> bool:
197
+ p = self.get_provider(provider_name)
198
+ if p is None:
199
+ return False
200
+ before = len(p.keys)
201
+ p.keys = [k for k in p.keys if k.id != key_id]
202
+ if len(p.keys) == before:
203
+ return False
204
+ if p.active_key_id == key_id:
205
+ p.active_key_id = p.keys[0].id if p.keys else None
206
+ self.save_provider(p)
207
+ return True
208
+
209
+ def rename_key(self, provider_name: str, key_id: str, new_label: str) -> bool:
210
+ p = self.get_provider(provider_name)
211
+ if p is None:
212
+ return False
213
+ for k in p.keys:
214
+ if k.id == key_id:
215
+ k.label = new_label
216
+ self.save_provider(p)
217
+ return True
218
+ return False
219
+
220
+ def set_active_key(self, provider_name: str, key_id: str) -> bool:
221
+ p = self.get_provider(provider_name)
222
+ if p is None:
223
+ return False
224
+ if not any(k.id == key_id for k in p.keys):
225
+ return False
226
+ p.active_key_id = key_id
227
+ self.save_provider(p)
228
+ return True
229
+
230
+ def get_decrypted_key(self, provider_name: str) -> str:
231
+ p = self.get_provider(provider_name)
232
+ if p is None:
233
+ return ""
234
+ return p.decrypted_api_key()
@@ -0,0 +1,102 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import os
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from reidx.diagnostics.logger import get_logger
9
+
10
+ log = get_logger("reidx.provider_manager.keychain")
11
+
12
+ _IS_WINDOWS = sys.platform == "win32"
13
+
14
+ if _IS_WINDOWS:
15
+ import ctypes
16
+ import ctypes.wintypes
17
+
18
+ class _DATA_BLOB(ctypes.Structure):
19
+ _fields_ = [
20
+ ("cbData", ctypes.wintypes.DWORD),
21
+ ("pbData", ctypes.POINTER(ctypes.c_char)),
22
+ ]
23
+
24
+ def _dpapi_encrypt(plaintext: bytes) -> bytes:
25
+ blob_in = _DATA_BLOB()
26
+ blob_in.pbData = ctypes.cast(
27
+ ctypes.create_string_buffer(plaintext, len(plaintext)),
28
+ ctypes.POINTER(ctypes.c_char),
29
+ )
30
+ blob_in.cbData = len(plaintext)
31
+ blob_out = _DATA_BLOB()
32
+ ok = ctypes.windll.crypt32.CryptProtectData(
33
+ ctypes.byref(blob_in),
34
+ None,
35
+ None,
36
+ None,
37
+ None,
38
+ 0,
39
+ ctypes.byref(blob_out),
40
+ )
41
+ if not ok:
42
+ raise OSError("DPAPI CryptProtectData failed")
43
+ try:
44
+ return ctypes.string_at(blob_out.pbData, blob_out.cbData)
45
+ finally:
46
+ ctypes.windll.kernel32.LocalFree(blob_out.pbData)
47
+
48
+ def _dpapi_decrypt(ciphertext: bytes) -> bytes:
49
+ blob_in = _DATA_BLOB()
50
+ blob_in.pbData = ctypes.cast(
51
+ ctypes.create_string_buffer(ciphertext, len(ciphertext)),
52
+ ctypes.POINTER(ctypes.c_char),
53
+ )
54
+ blob_in.cbData = len(ciphertext)
55
+ blob_out = _DATA_BLOB()
56
+ ok = ctypes.windll.crypt32.CryptUnprotectData(
57
+ ctypes.byref(blob_in),
58
+ None,
59
+ None,
60
+ None,
61
+ None,
62
+ 0,
63
+ ctypes.byref(blob_out),
64
+ )
65
+ if not ok:
66
+ raise OSError("DPAPI CryptUnprotectData failed")
67
+ try:
68
+ return ctypes.string_at(blob_out.pbData, blob_out.cbData)
69
+ finally:
70
+ ctypes.windll.kernel32.LocalFree(blob_out.pbData)
71
+
72
+
73
+ def encrypt(plaintext: str) -> str:
74
+ if not plaintext:
75
+ return ""
76
+ raw = plaintext.encode("utf-8")
77
+ if _IS_WINDOWS:
78
+ encrypted = _dpapi_encrypt(raw)
79
+ else:
80
+ log.warning("DPAPI not available on this platform; storing key without OS encryption")
81
+ encrypted = raw
82
+ return base64.b64encode(encrypted).decode("ascii")
83
+
84
+
85
+ def decrypt(ciphertext: str) -> str:
86
+ if not ciphertext:
87
+ return ""
88
+ raw = base64.b64decode(ciphertext)
89
+ if _IS_WINDOWS:
90
+ decrypted = _dpapi_decrypt(raw)
91
+ else:
92
+ decrypted = raw
93
+ return decrypted.decode("utf-8")
94
+
95
+
96
+ def secure_write(path: Path, data: str) -> None:
97
+ path.parent.mkdir(parents=True, exist_ok=True)
98
+ path.write_text(data, encoding="utf-8")
99
+ try:
100
+ os.chmod(path, 0o600)
101
+ except OSError:
102
+ pass