minder-cli 0.2.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.
Files changed (132) hide show
  1. minder/__init__.py +12 -0
  2. minder/api/routers/prompts.py +177 -0
  3. minder/application/__init__.py +1 -0
  4. minder/application/admin/__init__.py +11 -0
  5. minder/application/admin/dto.py +453 -0
  6. minder/application/admin/jobs.py +327 -0
  7. minder/application/admin/use_cases.py +1895 -0
  8. minder/auth/__init__.py +12 -0
  9. minder/auth/context.py +26 -0
  10. minder/auth/middleware.py +70 -0
  11. minder/auth/principal.py +59 -0
  12. minder/auth/rate_limiter.py +89 -0
  13. minder/auth/rbac.py +60 -0
  14. minder/auth/service.py +541 -0
  15. minder/bootstrap/__init__.py +9 -0
  16. minder/bootstrap/providers.py +109 -0
  17. minder/bootstrap/transport.py +807 -0
  18. minder/cache/__init__.py +10 -0
  19. minder/cache/providers.py +140 -0
  20. minder/chunking/__init__.py +4 -0
  21. minder/chunking/code_splitter.py +184 -0
  22. minder/chunking/splitter.py +136 -0
  23. minder/cli.py +1542 -0
  24. minder/config.py +179 -0
  25. minder/continuity.py +363 -0
  26. minder/dev.py +160 -0
  27. minder/embedding/__init__.py +9 -0
  28. minder/embedding/base.py +7 -0
  29. minder/embedding/local.py +65 -0
  30. minder/embedding/openai.py +7 -0
  31. minder/graph/__init__.py +11 -0
  32. minder/graph/edges.py +13 -0
  33. minder/graph/executor.py +127 -0
  34. minder/graph/graph.py +263 -0
  35. minder/graph/nodes/__init__.py +27 -0
  36. minder/graph/nodes/evaluator.py +21 -0
  37. minder/graph/nodes/guard.py +64 -0
  38. minder/graph/nodes/llm.py +59 -0
  39. minder/graph/nodes/planning.py +30 -0
  40. minder/graph/nodes/reasoning.py +87 -0
  41. minder/graph/nodes/reranker.py +141 -0
  42. minder/graph/nodes/retriever.py +86 -0
  43. minder/graph/nodes/verification.py +230 -0
  44. minder/graph/nodes/workflow_planner.py +250 -0
  45. minder/graph/runtime.py +15 -0
  46. minder/graph/state.py +26 -0
  47. minder/llm/__init__.py +5 -0
  48. minder/llm/base.py +14 -0
  49. minder/llm/local.py +381 -0
  50. minder/llm/openai.py +89 -0
  51. minder/models/__init__.py +109 -0
  52. minder/models/base.py +10 -0
  53. minder/models/client.py +137 -0
  54. minder/models/document.py +34 -0
  55. minder/models/error.py +32 -0
  56. minder/models/graph.py +114 -0
  57. minder/models/history.py +32 -0
  58. minder/models/job.py +62 -0
  59. minder/models/prompt.py +41 -0
  60. minder/models/repository.py +62 -0
  61. minder/models/rule.py +68 -0
  62. minder/models/session.py +51 -0
  63. minder/models/skill.py +52 -0
  64. minder/models/user.py +41 -0
  65. minder/models/workflow.py +35 -0
  66. minder/observability/__init__.py +57 -0
  67. minder/observability/audit.py +243 -0
  68. minder/observability/logging.py +253 -0
  69. minder/observability/metrics.py +448 -0
  70. minder/observability/tracing.py +215 -0
  71. minder/presentation/__init__.py +1 -0
  72. minder/presentation/http/__init__.py +1 -0
  73. minder/presentation/http/admin/__init__.py +3 -0
  74. minder/presentation/http/admin/api.py +1309 -0
  75. minder/presentation/http/admin/context.py +94 -0
  76. minder/presentation/http/admin/dashboard.py +111 -0
  77. minder/presentation/http/admin/jobs.py +208 -0
  78. minder/presentation/http/admin/memories.py +185 -0
  79. minder/presentation/http/admin/prompts.py +219 -0
  80. minder/presentation/http/admin/routes.py +127 -0
  81. minder/presentation/http/admin/runtime.py +650 -0
  82. minder/presentation/http/admin/search.py +368 -0
  83. minder/presentation/http/admin/skills.py +230 -0
  84. minder/prompts/__init__.py +646 -0
  85. minder/prompts/formatter.py +142 -0
  86. minder/resources/__init__.py +318 -0
  87. minder/retrieval/__init__.py +5 -0
  88. minder/retrieval/hybrid.py +178 -0
  89. minder/retrieval/mmr.py +116 -0
  90. minder/retrieval/multi_hop.py +115 -0
  91. minder/runtime.py +15 -0
  92. minder/server.py +145 -0
  93. minder/store/__init__.py +64 -0
  94. minder/store/document.py +115 -0
  95. minder/store/error.py +82 -0
  96. minder/store/feedback.py +114 -0
  97. minder/store/graph.py +588 -0
  98. minder/store/history.py +57 -0
  99. minder/store/interfaces.py +512 -0
  100. minder/store/milvus/__init__.py +11 -0
  101. minder/store/milvus/client.py +26 -0
  102. minder/store/milvus/collections.py +15 -0
  103. minder/store/milvus/vector_store.py +232 -0
  104. minder/store/mongodb/__init__.py +11 -0
  105. minder/store/mongodb/client.py +49 -0
  106. minder/store/mongodb/indexes.py +90 -0
  107. minder/store/mongodb/operational_store.py +993 -0
  108. minder/store/relational.py +1087 -0
  109. minder/store/repo_state.py +58 -0
  110. minder/store/rule.py +93 -0
  111. minder/store/vector.py +79 -0
  112. minder/tools/__init__.py +47 -0
  113. minder/tools/auth.py +94 -0
  114. minder/tools/graph.py +839 -0
  115. minder/tools/ingest.py +353 -0
  116. minder/tools/memory.py +381 -0
  117. minder/tools/query.py +307 -0
  118. minder/tools/registry.py +269 -0
  119. minder/tools/repo_scanner.py +1266 -0
  120. minder/tools/search.py +15 -0
  121. minder/tools/session.py +316 -0
  122. minder/tools/skills.py +899 -0
  123. minder/tools/workflow.py +215 -0
  124. minder/transport/__init__.py +4 -0
  125. minder/transport/base.py +286 -0
  126. minder/transport/sse.py +252 -0
  127. minder/transport/stdio.py +29 -0
  128. minder_cli-0.2.0.dist-info/METADATA +318 -0
  129. minder_cli-0.2.0.dist-info/RECORD +132 -0
  130. minder_cli-0.2.0.dist-info/WHEEL +4 -0
  131. minder_cli-0.2.0.dist-info/entry_points.txt +2 -0
  132. minder_cli-0.2.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,12 @@
1
+ from .service import AuthService, AuthError, UserRole, ROLE_HIERARCHY
2
+ from .rbac import require_role
3
+ from .middleware import AuthMiddleware
4
+
5
+ __all__ = [
6
+ "AuthService",
7
+ "AuthError",
8
+ "UserRole",
9
+ "ROLE_HIERARCHY",
10
+ "require_role",
11
+ "AuthMiddleware",
12
+ ]
minder/auth/context.py ADDED
@@ -0,0 +1,26 @@
1
+ from contextvars import ContextVar
2
+ from typing import Optional
3
+
4
+ from minder.auth.principal import Principal
5
+ from minder.models.user import User
6
+
7
+ _current_user: ContextVar[Optional[User]] = ContextVar("current_user", default=None)
8
+ _current_principal: ContextVar[Optional[Principal]] = ContextVar("current_principal", default=None)
9
+
10
+ def set_current_user(user: Optional[User]) -> None:
11
+ _current_user.set(user)
12
+
13
+ def get_current_user() -> Optional[User]:
14
+ return _current_user.get()
15
+
16
+
17
+ def set_current_principal(principal: Optional[Principal]) -> None:
18
+ _current_principal.set(principal)
19
+ if principal is None or not hasattr(principal, "user"):
20
+ _current_user.set(None)
21
+ else:
22
+ _current_user.set(getattr(principal, "user"))
23
+
24
+
25
+ def get_current_principal() -> Optional[Principal]:
26
+ return _current_principal.get()
@@ -0,0 +1,70 @@
1
+ """
2
+ Auth Middleware — JWT extraction and validation for SSE / HTTP connections.
3
+
4
+ Usage:
5
+ middleware = AuthMiddleware(auth_service)
6
+ user = await middleware.authenticate(request.headers.get("Authorization"))
7
+ """
8
+
9
+ from typing import Optional
10
+
11
+ from minder.auth.principal import Principal
12
+ from minder.auth.service import AuthError, AuthService
13
+ from minder.models.user import User
14
+
15
+
16
+ class AuthMiddleware:
17
+ """
18
+ Extracts Bearer JWT from the Authorization header and returns the
19
+ authenticated User. Raises AuthError on any failure so callers can
20
+ convert it to the appropriate HTTP/transport error.
21
+ """
22
+
23
+ def __init__(self, auth_service: AuthService) -> None:
24
+ self._auth = auth_service
25
+
26
+ # ------------------------------------------------------------------
27
+ # Public API
28
+ # ------------------------------------------------------------------
29
+
30
+ def extract_bearer_token(self, authorization: Optional[str]) -> str:
31
+ """
32
+ Parse 'Bearer <token>' from the Authorization header value.
33
+
34
+ Raises:
35
+ AuthError(AUTH_MISSING_TOKEN) — header absent or empty.
36
+ AuthError(AUTH_INVALID_HEADER) — wrong scheme or malformed.
37
+ """
38
+ if not authorization or not authorization.strip():
39
+ raise AuthError("AUTH_MISSING_TOKEN", "Authorization header is required")
40
+
41
+ parts = authorization.strip().split(" ", 1)
42
+ if len(parts) != 2 or parts[0].lower() != "bearer":
43
+ raise AuthError(
44
+ "AUTH_INVALID_HEADER",
45
+ "Authorization header must use 'Bearer <token>' scheme",
46
+ )
47
+ token = parts[1].strip()
48
+ if not token:
49
+ raise AuthError("AUTH_MISSING_TOKEN", "Bearer token value is empty")
50
+ return token
51
+
52
+ async def authenticate(self, authorization: Optional[str]) -> User:
53
+ """
54
+ Full authentication flow: extract token → validate JWT → return user.
55
+
56
+ Raises AuthError on any failure.
57
+ """
58
+ token = self.extract_bearer_token(authorization)
59
+ return await self._auth.get_user_from_jwt(token)
60
+
61
+ async def authenticate_principal(
62
+ self,
63
+ authorization: Optional[str] = None,
64
+ *,
65
+ client_key: Optional[str] = None,
66
+ ) -> Principal:
67
+ if client_key and client_key.strip():
68
+ return await self._auth.get_principal_from_client_key(client_key.strip())
69
+ token = self.extract_bearer_token(authorization)
70
+ return await self._auth.get_principal_from_token(token)
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ import uuid
4
+ from dataclasses import dataclass, field
5
+ from typing import Any
6
+
7
+ from minder.models.user import User
8
+
9
+
10
+ @dataclass(slots=True)
11
+ class Principal:
12
+ principal_type: str
13
+ principal_id: uuid.UUID
14
+ role: str
15
+ scopes: list[str] = field(default_factory=list)
16
+ repo_scope: list[str] = field(default_factory=list)
17
+ metadata: dict[str, Any] = field(default_factory=dict)
18
+
19
+
20
+ @dataclass(slots=True)
21
+ class AdminUserPrincipal(Principal):
22
+ user: User | None = None
23
+
24
+ def __init__(self, user: User) -> None:
25
+ super().__init__(
26
+ principal_type="user",
27
+ principal_id=user.id,
28
+ role=user.role,
29
+ scopes=[],
30
+ repo_scope=[],
31
+ metadata={"email": user.email, "username": user.username},
32
+ )
33
+ self.user = user
34
+
35
+
36
+ @dataclass(slots=True)
37
+ class ClientPrincipal(Principal):
38
+ client_id: uuid.UUID = field(init=False)
39
+ client_slug: str = field(init=False)
40
+
41
+ def __init__(
42
+ self,
43
+ *,
44
+ client_id: uuid.UUID,
45
+ client_slug: str,
46
+ scopes: list[str],
47
+ repo_scope: list[str],
48
+ metadata: dict[str, Any] | None = None,
49
+ ) -> None:
50
+ super().__init__(
51
+ principal_type="client",
52
+ principal_id=client_id,
53
+ role="client",
54
+ scopes=scopes,
55
+ repo_scope=repo_scope,
56
+ metadata=metadata or {},
57
+ )
58
+ self.client_id = client_id
59
+ self.client_slug = client_slug
@@ -0,0 +1,89 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from dataclasses import dataclass
5
+
6
+ from minder.auth.principal import Principal
7
+ from minder.auth.service import AuthError
8
+ from minder.config import MinderConfig
9
+ from minder.store.interfaces import ICacheProvider
10
+
11
+
12
+ class RateLimitError(AuthError):
13
+ def __init__(self, message: str) -> None:
14
+ super().__init__("AUTH_RATE_LIMITED", message)
15
+
16
+
17
+ @dataclass(slots=True)
18
+ class RateLimitDecision:
19
+ limit: int
20
+ count: int
21
+ remaining: int
22
+ reset_at: int
23
+
24
+
25
+ class RateLimiter:
26
+ def __init__(
27
+ self,
28
+ *,
29
+ cache: ICacheProvider,
30
+ config: MinderConfig,
31
+ ) -> None:
32
+ self._cache = cache
33
+ self._config = config
34
+
35
+ def enabled(self) -> bool:
36
+ return self._config.rate_limit.enabled
37
+
38
+ def _limit_for_principal(self, principal: Principal) -> int:
39
+ if principal.principal_type == "client":
40
+ return self._config.rate_limit.client_limit
41
+ role = principal.role
42
+ if role == "admin":
43
+ return self._config.rate_limit.admin_limit
44
+ if role == "readonly":
45
+ return self._config.rate_limit.readonly_limit
46
+ return self._config.rate_limit.member_limit
47
+
48
+ def _key(self, principal: Principal, tool_name: str, window_start: int) -> str:
49
+ return f"rate_limit:{principal.principal_type}:{principal.principal_id}:{tool_name}:{window_start}"
50
+
51
+ async def enforce(self, *, principal: Principal, tool_name: str) -> RateLimitDecision:
52
+ limit = self._limit_for_principal(principal)
53
+ now = int(time.time())
54
+ window = self._config.rate_limit.window_seconds
55
+ window_start = now - (now % window)
56
+ reset_at = window_start + window
57
+ key = self._key(principal, tool_name, window_start)
58
+
59
+ count = await self._cache.incr(key)
60
+ if count == 1:
61
+ await self._cache.expire(key, window)
62
+
63
+ if count > limit:
64
+ raise RateLimitError(
65
+ f"Rate limit exceeded for {principal.principal_type} '{principal.principal_id}' on tool '{tool_name}'"
66
+ )
67
+
68
+ return RateLimitDecision(
69
+ limit=limit,
70
+ count=count,
71
+ remaining=max(limit - count, 0),
72
+ reset_at=reset_at,
73
+ )
74
+
75
+ async def get_usage(self, *, principal: Principal, tool_name: str) -> dict[str, int]:
76
+ limit = self._limit_for_principal(principal)
77
+ now = int(time.time())
78
+ window = self._config.rate_limit.window_seconds
79
+ window_start = now - (now % window)
80
+ reset_at = window_start + window
81
+ key = self._key(principal, tool_name, window_start)
82
+ raw = await self._cache.get(key)
83
+ count = int(raw or "0")
84
+ return {
85
+ "count": count,
86
+ "limit": limit,
87
+ "remaining": max(limit - count, 0),
88
+ "reset_at": reset_at,
89
+ }
minder/auth/rbac.py ADDED
@@ -0,0 +1,60 @@
1
+ """
2
+ RBAC — role-based access control decorator.
3
+
4
+ Usage:
5
+ @require_role(UserRole.ADMIN)
6
+ async def admin_only_handler(*args, user: User, **kwargs):
7
+ ...
8
+
9
+ The decorated function must receive the authenticated `user` as a keyword arg.
10
+ """
11
+
12
+ import functools
13
+ from typing import Any, Callable
14
+
15
+ from minder.auth.service import AuthError, ROLE_HIERARCHY, UserRole
16
+ from minder.models.user import User
17
+
18
+
19
+ def require_role(min_role: UserRole) -> Callable:
20
+ """
21
+ Decorator that enforces a minimum role on an async handler.
22
+
23
+ Raises:
24
+ AuthError(AUTH_NO_USER) — if `user` kwarg is absent.
25
+ AuthError(AUTH_INSUFFICIENT_ROLE) — if user's role is below min_role.
26
+ """
27
+
28
+ def decorator(func: Callable) -> Callable:
29
+ @functools.wraps(func)
30
+ async def wrapper(*args: Any, **kwargs: Any) -> Any:
31
+ user: User | None = kwargs.get("user")
32
+ if user is None:
33
+ raise AuthError(
34
+ "AUTH_NO_USER",
35
+ "No authenticated user provided to role-guarded handler",
36
+ )
37
+ try:
38
+ user_role = UserRole(user.role)
39
+ except ValueError:
40
+ raise AuthError(
41
+ "AUTH_UNKNOWN_ROLE",
42
+ f"Unrecognised role value: {user.role!r}",
43
+ )
44
+
45
+ user_level = ROLE_HIERARCHY.get(user_role, 0)
46
+ required_level = ROLE_HIERARCHY.get(min_role, 0)
47
+
48
+ if user_level < required_level:
49
+ raise AuthError(
50
+ "AUTH_INSUFFICIENT_ROLE",
51
+ (
52
+ f"Role '{user_role.value}' is insufficient. "
53
+ f"Required: '{min_role.value}' or higher."
54
+ ),
55
+ )
56
+ return await func(*args, **kwargs)
57
+
58
+ return wrapper
59
+
60
+ return decorator