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,219 @@
1
+ import uuid
2
+ import logging
3
+ from typing import Any, List
4
+
5
+ from pydantic import BaseModel, Field
6
+ from starlette.requests import Request
7
+ from starlette.responses import JSONResponse
8
+ from starlette.routing import BaseRoute, Route
9
+
10
+ from minder.config import MinderConfig
11
+ from minder.observability.metrics import record_admin_operation
12
+ from minder.prompts.formatter import PromptDraft, polish_prompt_draft
13
+ from minder.prompts import PromptRegistry
14
+ from .context import AdminRouteContext
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class PromptCreateRequest(BaseModel):
20
+ name: str
21
+ title: str
22
+ description: str
23
+ content_template: str
24
+ arguments: List[str] = Field(default_factory=list)
25
+
26
+
27
+ class PromptUpdateRequest(BaseModel):
28
+ name: str | None = None
29
+ title: str | None = None
30
+ description: str | None = None
31
+ content_template: str | None = None
32
+ arguments: List[str] | None = None
33
+
34
+
35
+ class PromptPolishRequest(BaseModel):
36
+ name: str
37
+ title: str = ""
38
+ description: str = ""
39
+ content_template: str = ""
40
+ arguments: List[str] = Field(default_factory=list)
41
+
42
+
43
+ def _serialize_prompt(prompt: Any) -> dict[str, Any]:
44
+ return {
45
+ "id": str(prompt.id),
46
+ "name": prompt.name,
47
+ "title": prompt.title,
48
+ "description": prompt.description,
49
+ "content_template": prompt.content_template,
50
+ "arguments": list(getattr(prompt, "arguments", []) or []),
51
+ "created_at": prompt.created_at.isoformat() if prompt.created_at else None,
52
+ "updated_at": prompt.updated_at.isoformat() if prompt.updated_at else None,
53
+ "is_builtin": bool(getattr(prompt, "is_builtin", False)),
54
+ }
55
+
56
+
57
+ async def _sync_prompts(context: AdminRouteContext) -> None:
58
+ if context.prompt_sync_hook is None:
59
+ return
60
+ await context.prompt_sync_hook()
61
+
62
+
63
+ def _config_from_request(request: Request) -> MinderConfig:
64
+ config = getattr(request.app.state, "config", None)
65
+ if isinstance(config, MinderConfig):
66
+ return config
67
+ return MinderConfig()
68
+
69
+
70
+ def build_prompts_routes(context: AdminRouteContext) -> list[BaseRoute]:
71
+ async def list_prompts(request: Request) -> JSONResponse:
72
+ del request
73
+ await record_admin_operation(
74
+ operation="list_prompts",
75
+ outcome="success",
76
+ actor_id="system",
77
+ store=context.store,
78
+ )
79
+ try:
80
+ prompt_index = {
81
+ prompt.name: prompt for prompt in PromptRegistry.builtin_prompt_models()
82
+ }
83
+ for prompt in await context.store.list_prompts():
84
+ prompt_index[prompt.name] = prompt
85
+ ordered_prompts = sorted(
86
+ prompt_index.values(),
87
+ key=lambda prompt: (
88
+ not bool(getattr(prompt, "is_builtin", False)),
89
+ prompt.name,
90
+ ),
91
+ )
92
+ return JSONResponse(
93
+ [_serialize_prompt(prompt) for prompt in ordered_prompts]
94
+ )
95
+ except Exception as e:
96
+ logger.exception("Failed to list prompts", exc_info=e)
97
+ return JSONResponse({"error": str(e)}, status_code=500)
98
+
99
+ async def get_prompt(request: Request) -> JSONResponse:
100
+ prompt_id = request.path_params["prompt_id"]
101
+ await record_admin_operation(
102
+ operation="get_prompt",
103
+ outcome="success",
104
+ actor_id="system",
105
+ store=context.store,
106
+ )
107
+ try:
108
+ prompt = await context.store.get_prompt_by_id(uuid.UUID(prompt_id))
109
+ if not prompt:
110
+ return JSONResponse({"error": "Prompt not found"}, status_code=404)
111
+ return JSONResponse(_serialize_prompt(prompt))
112
+ except Exception as e:
113
+ logger.exception("Failed to get prompt", exc_info=e)
114
+ return JSONResponse({"error": str(e)}, status_code=500)
115
+
116
+ async def create_prompt(request: Request) -> JSONResponse:
117
+ await record_admin_operation(
118
+ operation="create_prompt",
119
+ outcome="success",
120
+ actor_id="system",
121
+ store=context.store,
122
+ )
123
+ try:
124
+ data = await request.json()
125
+ payload = PromptCreateRequest(**data)
126
+ prompt = await context.store.create_prompt(
127
+ name=payload.name,
128
+ title=payload.title,
129
+ description=payload.description,
130
+ content_template=payload.content_template,
131
+ arguments=payload.arguments,
132
+ )
133
+ await _sync_prompts(context)
134
+ return JSONResponse(_serialize_prompt(prompt), status_code=201)
135
+ except Exception as e:
136
+ logger.exception("Failed to create prompt", exc_info=e)
137
+ return JSONResponse({"error": str(e)}, status_code=400)
138
+
139
+ async def update_prompt(request: Request) -> JSONResponse:
140
+ prompt_id = request.path_params["prompt_id"]
141
+ await record_admin_operation(
142
+ operation="update_prompt",
143
+ outcome="success",
144
+ actor_id="system",
145
+ store=context.store,
146
+ )
147
+ try:
148
+ data = await request.json()
149
+ payload = PromptUpdateRequest(**data)
150
+ update_data = payload.model_dump(exclude_unset=True)
151
+ prompt = await context.store.update_prompt(
152
+ uuid.UUID(prompt_id), **update_data
153
+ )
154
+ if not prompt:
155
+ return JSONResponse({"error": "Prompt not found"}, status_code=404)
156
+ await _sync_prompts(context)
157
+ return JSONResponse(_serialize_prompt(prompt))
158
+ except Exception as e:
159
+ logger.exception("Failed to update prompt", exc_info=e)
160
+ return JSONResponse({"error": str(e)}, status_code=400)
161
+
162
+ async def delete_prompt(request: Request) -> JSONResponse:
163
+ prompt_id = request.path_params["prompt_id"]
164
+ await record_admin_operation(
165
+ operation="delete_prompt",
166
+ outcome="success",
167
+ actor_id="system",
168
+ store=context.store,
169
+ )
170
+ try:
171
+ await context.store.delete_prompt(uuid.UUID(prompt_id))
172
+ await _sync_prompts(context)
173
+ return JSONResponse({"status": "deleted"}, status_code=200)
174
+ except Exception as e:
175
+ logger.exception("Failed to delete prompt", exc_info=e)
176
+ return JSONResponse({"error": str(e)}, status_code=500)
177
+
178
+ async def polish_prompt(request: Request) -> JSONResponse:
179
+ await record_admin_operation(
180
+ operation="polish_prompt",
181
+ outcome="success",
182
+ actor_id="system",
183
+ store=context.store,
184
+ )
185
+ try:
186
+ data = await request.json()
187
+ payload = PromptPolishRequest(**data)
188
+ polished, metadata = polish_prompt_draft(
189
+ PromptDraft(
190
+ name=payload.name,
191
+ title=payload.title,
192
+ description=payload.description,
193
+ content_template=payload.content_template,
194
+ arguments=payload.arguments,
195
+ ),
196
+ _config_from_request(request),
197
+ )
198
+ return JSONResponse(
199
+ {
200
+ "name": polished.name,
201
+ "title": polished.title,
202
+ "description": polished.description,
203
+ "content_template": polished.content_template,
204
+ "arguments": polished.arguments,
205
+ "llm": metadata,
206
+ }
207
+ )
208
+ except Exception as e:
209
+ logger.exception("Failed to polish prompt", exc_info=e)
210
+ return JSONResponse({"error": str(e)}, status_code=400)
211
+
212
+ return [
213
+ Route("/api/v1/prompts", list_prompts, methods=["GET"]),
214
+ Route("/api/v1/prompts", create_prompt, methods=["POST"]),
215
+ Route("/api/v1/prompts/polish", polish_prompt, methods=["POST"]),
216
+ Route("/api/v1/prompts/{prompt_id}", get_prompt, methods=["GET"]),
217
+ Route("/api/v1/prompts/{prompt_id}", update_prompt, methods=["PATCH", "PUT"]),
218
+ Route("/api/v1/prompts/{prompt_id}", delete_prompt, methods=["DELETE"]),
219
+ ]
@@ -0,0 +1,127 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from urllib.parse import urlsplit
5
+
6
+ from fastapi import FastAPI
7
+ from starlette.middleware import Middleware
8
+ from starlette.middleware.cors import CORSMiddleware
9
+ from starlette.responses import FileResponse, PlainTextResponse, RedirectResponse
10
+ from starlette.routing import BaseRoute, Route
11
+
12
+ from minder.config import MinderConfig
13
+ from minder.observability.logging import AccessLogMiddleware, CorrelationIdMiddleware
14
+ from minder.observability.metrics import metrics_endpoint
15
+ from minder.store.interfaces import ICacheProvider, IGraphRepository, IOperationalStore
16
+
17
+ from .api import build_admin_api_routes
18
+ from .context import AdminRouteContext
19
+ from .dashboard import build_dashboard_routes
20
+ from .jobs import build_jobs_routes
21
+ from .memories import build_memories_routes
22
+ from .prompts import build_prompts_routes
23
+ from .runtime import build_runtime_routes
24
+ from .search import build_search_routes
25
+ from .skills import build_skills_routes
26
+
27
+
28
+ DEFAULT_DASHBOARD_DEV_ORIGIN = "http://localhost:8808"
29
+
30
+
31
+ def _favicon_path() -> Path:
32
+ return Path(__file__).resolve().parents[5] / "favicon.png"
33
+
34
+
35
+ def dashboard_dev_origin(config: MinderConfig) -> str | None:
36
+ dev_server_url = (config.dashboard.dev_server_url or "").strip()
37
+ if not dev_server_url:
38
+ return DEFAULT_DASHBOARD_DEV_ORIGIN
39
+ parts = urlsplit(dev_server_url)
40
+ if not parts.scheme or not parts.netloc:
41
+ return DEFAULT_DASHBOARD_DEV_ORIGIN
42
+ return f"{parts.scheme}://{parts.netloc}"
43
+
44
+
45
+ def build_http_routes(
46
+ *,
47
+ config: MinderConfig,
48
+ store: IOperationalStore,
49
+ graph_store: IGraphRepository | None = None,
50
+ cache: ICacheProvider | None = None,
51
+ prompt_sync_hook=None,
52
+ ) -> list[BaseRoute]:
53
+ context = AdminRouteContext.build(
54
+ config=config,
55
+ store=store,
56
+ graph_store=graph_store,
57
+ cache=cache,
58
+ prompt_sync_hook=prompt_sync_hook,
59
+ )
60
+
61
+ async def health(_request) -> PlainTextResponse:
62
+ return PlainTextResponse("ok", status_code=200)
63
+
64
+ async def favicon_png(_request) -> FileResponse | PlainTextResponse:
65
+ favicon = _favicon_path()
66
+ if favicon.is_file():
67
+ return FileResponse(favicon, media_type="image/png")
68
+ return PlainTextResponse("favicon not found", status_code=404)
69
+
70
+ async def favicon_ico(_request) -> RedirectResponse:
71
+ return RedirectResponse(url="/favicon.png", status_code=308)
72
+
73
+ return [
74
+ Route("/health", health, methods=["GET"]),
75
+ Route("/favicon.ico", favicon_ico, methods=["GET"]),
76
+ Route("/favicon.png", favicon_png, methods=["GET"]),
77
+ Route("/metrics", metrics_endpoint, methods=["GET"]),
78
+ *build_admin_api_routes(context),
79
+ *build_prompts_routes(context),
80
+ *build_jobs_routes(context),
81
+ *build_skills_routes(context),
82
+ *build_memories_routes(context),
83
+ *build_runtime_routes(context),
84
+ *build_search_routes(context),
85
+ *build_dashboard_routes(context),
86
+ ]
87
+
88
+
89
+ def build_http_app(
90
+ *,
91
+ config: MinderConfig,
92
+ store: IOperationalStore,
93
+ graph_store: IGraphRepository | None = None,
94
+ cache: ICacheProvider | None = None,
95
+ ) -> FastAPI:
96
+ from minder.application.admin.jobs import AdminJobService
97
+
98
+ middleware: list[Middleware] = []
99
+
100
+ # Observability middleware (innermost first — applied outermost-last)
101
+ middleware.append(Middleware(CorrelationIdMiddleware))
102
+ middleware.append(Middleware(AccessLogMiddleware))
103
+
104
+ dev_origin = dashboard_dev_origin(config)
105
+ if dev_origin:
106
+ middleware.append(
107
+ Middleware(
108
+ CORSMiddleware,
109
+ allow_origins=[dev_origin],
110
+ allow_credentials=True,
111
+ allow_methods=["*"],
112
+ allow_headers=["*"],
113
+ )
114
+ )
115
+ app = FastAPI(
116
+ routes=build_http_routes(
117
+ config=config,
118
+ store=store,
119
+ graph_store=graph_store,
120
+ cache=cache,
121
+ ),
122
+ middleware=middleware,
123
+ )
124
+ app.state.store = store
125
+ app.state.config = config
126
+ app.state.job_service = AdminJobService(store, config)
127
+ return app