minder-cli 0.3.2__tar.gz → 0.3.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.
- {minder_cli-0.3.2 → minder_cli-0.3.3}/PKG-INFO +1 -1
- {minder_cli-0.3.2 → minder_cli-0.3.3}/pyproject.toml +1 -1
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/cli.py +208 -74
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/config.py +2 -2
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/continuity.py +1 -1
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/embedding/local.py +2 -2
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/llm/local.py +1 -1
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/prompts/formatter.py +1 -1
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/tools/repo_scanner.py +29 -20
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/tools/skills.py +11 -6
- {minder_cli-0.3.2 → minder_cli-0.3.3}/.gitignore +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/LICENSE +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/README.md +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/api/routers/prompts.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/application/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/application/admin/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/application/admin/dto.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/application/admin/jobs.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/application/admin/use_cases.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/auth/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/auth/context.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/auth/middleware.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/auth/principal.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/auth/rate_limiter.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/auth/rbac.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/auth/service.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/bootstrap/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/bootstrap/providers.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/bootstrap/transport.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/cache/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/cache/providers.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/chunking/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/chunking/code_splitter.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/chunking/splitter.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/dev.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/embedding/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/embedding/base.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/embedding/openai.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/edges.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/executor.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/graph.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/nodes/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/nodes/evaluator.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/nodes/guard.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/nodes/llm.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/nodes/planning.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/nodes/reasoning.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/nodes/reranker.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/nodes/retriever.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/nodes/verification.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/nodes/workflow_planner.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/runtime.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/graph/state.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/llm/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/llm/base.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/llm/openai.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/models/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/models/base.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/models/client.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/models/document.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/models/error.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/models/graph.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/models/history.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/models/job.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/models/prompt.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/models/repository.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/models/rule.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/models/session.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/models/skill.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/models/user.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/models/workflow.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/observability/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/observability/audit.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/observability/logging.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/observability/metrics.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/observability/tracing.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/presentation/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/presentation/http/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/presentation/http/admin/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/presentation/http/admin/api.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/presentation/http/admin/context.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/presentation/http/admin/dashboard.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/presentation/http/admin/jobs.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/presentation/http/admin/memories.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/presentation/http/admin/prompts.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/presentation/http/admin/routes.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/presentation/http/admin/runtime.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/presentation/http/admin/search.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/presentation/http/admin/skills.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/prompts/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/resources/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/retrieval/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/retrieval/hybrid.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/retrieval/mmr.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/retrieval/multi_hop.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/runtime.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/server.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/document.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/error.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/feedback.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/graph.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/history.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/interfaces.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/milvus/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/milvus/client.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/milvus/collections.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/milvus/vector_store.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/mongodb/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/mongodb/client.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/mongodb/indexes.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/mongodb/operational_store.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/relational.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/repo_state.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/rule.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/store/vector.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/tools/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/tools/auth.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/tools/graph.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/tools/ingest.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/tools/memory.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/tools/query.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/tools/registry.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/tools/search.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/tools/session.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/tools/workflow.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/transport/__init__.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/transport/base.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/transport/sse.py +0 -0
- {minder_cli-0.3.2 → minder_cli-0.3.3}/src/minder/transport/stdio.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: minder-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Minder CLI is the command-line interface for the Minder self-hosted MCP platform.
|
|
5
5
|
Project-URL: Homepage, https://github.com/hiimtrung/minder
|
|
6
6
|
Project-URL: Repository, https://github.com/hiimtrung/minder
|
|
@@ -24,6 +24,7 @@ import httpx
|
|
|
24
24
|
from minder.tools.repo_scanner import RepoScanner
|
|
25
25
|
|
|
26
26
|
_DEFAULT_SERVER_URL = "http://localhost:8801/sse"
|
|
27
|
+
_DEFAULT_PROTOCOL = "sse"
|
|
27
28
|
_LOCAL_MCP_TARGETS = ("vscode", "cursor", "claude-code")
|
|
28
29
|
_PYPI_JSON_URL = "https://pypi.org/pypi/minder/json"
|
|
29
30
|
_IDE_GITIGNORE_KEY = "minder-ide-bootstrap"
|
|
@@ -120,14 +121,28 @@ def _prompt_client_key() -> str:
|
|
|
120
121
|
return client_key
|
|
121
122
|
|
|
122
123
|
|
|
124
|
+
def _prompt_protocol(default: str = _DEFAULT_PROTOCOL) -> str:
|
|
125
|
+
value = input(f"Minder protocol [sse/stdio] (default: {default}): ").strip().lower()
|
|
126
|
+
return value or default
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _normalize_protocol(raw_protocol: str | None) -> str:
|
|
130
|
+
protocol = (raw_protocol or "").strip().lower() or _DEFAULT_PROTOCOL
|
|
131
|
+
if protocol not in {"sse", "stdio"}:
|
|
132
|
+
raise ValueError("Protocol must be either 'sse' or 'stdio'")
|
|
133
|
+
return protocol
|
|
134
|
+
|
|
135
|
+
|
|
123
136
|
def _require_client_settings(config_path: Path) -> dict[str, Any]:
|
|
124
137
|
payload = _load_json(config_path)
|
|
138
|
+
protocol = _normalize_protocol(str(payload.get("protocol", _DEFAULT_PROTOCOL)))
|
|
125
139
|
client_key = str(payload.get("client_api_key", "")).strip()
|
|
126
140
|
server_url = str(payload.get("server_url", "")).strip()
|
|
127
141
|
if not client_key:
|
|
128
142
|
raise ValueError(f"No client_api_key found in {config_path}")
|
|
129
|
-
if not server_url:
|
|
143
|
+
if protocol == "sse" and not server_url:
|
|
130
144
|
raise ValueError(f"No server_url found in {config_path}")
|
|
145
|
+
payload["protocol"] = protocol
|
|
131
146
|
return payload
|
|
132
147
|
|
|
133
148
|
|
|
@@ -393,29 +408,31 @@ def _cli_update_commands(manager: str) -> list[list[str]]:
|
|
|
393
408
|
|
|
394
409
|
|
|
395
410
|
def _self_update_cli(manager: str) -> None:
|
|
411
|
+
before_version = _installed_package_version()
|
|
412
|
+
target_version = _latest_pypi_version()
|
|
396
413
|
failures: list[str] = []
|
|
397
414
|
for command in _cli_update_commands(manager):
|
|
398
415
|
executable = command[0]
|
|
399
416
|
if executable != sys.executable and shutil.which(executable) is None:
|
|
400
417
|
failures.append(f"{' '.join(command)} -> command not available")
|
|
401
418
|
continue
|
|
419
|
+
print(f"Executing CLI update: {' '.join(command)}")
|
|
402
420
|
result = subprocess.run(
|
|
403
421
|
command,
|
|
404
|
-
capture_output=
|
|
422
|
+
capture_output=False,
|
|
405
423
|
text=True,
|
|
406
424
|
check=False,
|
|
407
425
|
)
|
|
408
426
|
if result.returncode == 0:
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
427
|
+
after_version = _installed_package_version() or target_version
|
|
428
|
+
print(
|
|
429
|
+
"CLI update completed: "
|
|
430
|
+
f"{before_version or 'unknown'} -> {after_version or 'unknown'} "
|
|
431
|
+
f"via {' '.join(command)}"
|
|
432
|
+
)
|
|
413
433
|
return
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
)
|
|
417
|
-
failures.append(f"{' '.join(command)} -> {details}")
|
|
418
|
-
raise RuntimeError("CLI self-update failed: " + "; ".join(failures))
|
|
434
|
+
failures.append(f"{' '.join(command)} -> failed with exit code {result.returncode}")
|
|
435
|
+
raise RuntimeError("CLI update failed: " + "; ".join(failures))
|
|
419
436
|
|
|
420
437
|
|
|
421
438
|
def _installer_asset_filename(release_tag: str, *, installer: str) -> str:
|
|
@@ -448,7 +465,7 @@ def _run_bash_installer(script: str, env: dict[str, str]) -> subprocess.Complete
|
|
|
448
465
|
return subprocess.run(
|
|
449
466
|
["bash"],
|
|
450
467
|
input=script,
|
|
451
|
-
capture_output=
|
|
468
|
+
capture_output=False,
|
|
452
469
|
text=True,
|
|
453
470
|
env=env,
|
|
454
471
|
check=False,
|
|
@@ -462,7 +479,7 @@ def _run_powershell_installer(
|
|
|
462
479
|
return subprocess.run(
|
|
463
480
|
[executable, "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "-"],
|
|
464
481
|
input=script,
|
|
465
|
-
capture_output=
|
|
482
|
+
capture_output=False,
|
|
466
483
|
text=True,
|
|
467
484
|
env=env,
|
|
468
485
|
check=False,
|
|
@@ -491,20 +508,16 @@ def _self_update_server(install_dir: Path) -> None:
|
|
|
491
508
|
value = env_payload.get(key)
|
|
492
509
|
if value:
|
|
493
510
|
update_env[key] = value
|
|
511
|
+
print(f"Executing server update for {install_dir}...")
|
|
494
512
|
if installer_variant == "powershell":
|
|
495
513
|
result = _run_powershell_installer(installer_script, update_env)
|
|
496
514
|
else:
|
|
497
515
|
result = _run_bash_installer(installer_script, update_env)
|
|
498
516
|
if result.returncode != 0:
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
)
|
|
502
|
-
raise RuntimeError(f"Server self-update failed: {details}")
|
|
503
|
-
output = result.stdout.strip()
|
|
504
|
-
if output:
|
|
505
|
-
print(output)
|
|
517
|
+
raise RuntimeError(f"Server update failed with exit code {result.returncode}")
|
|
518
|
+
# Output is already streamed to stdout/stderr in installer helpers if not captured
|
|
506
519
|
print(
|
|
507
|
-
f"Server
|
|
520
|
+
f"Server update completed for {install_dir}: "
|
|
508
521
|
f"{current or 'unknown'} -> {latest}"
|
|
509
522
|
)
|
|
510
523
|
print(f"Release installer: {installer_url}")
|
|
@@ -529,7 +542,7 @@ def _check_update(args: argparse.Namespace) -> int:
|
|
|
529
542
|
return 0
|
|
530
543
|
|
|
531
544
|
|
|
532
|
-
def
|
|
545
|
+
def _update(args: argparse.Namespace) -> int:
|
|
533
546
|
components = ["cli", "server"] if args.component == "all" else [args.component]
|
|
534
547
|
if "cli" in components:
|
|
535
548
|
_self_update_cli(args.manager)
|
|
@@ -543,35 +556,42 @@ def _self_update(args: argparse.Namespace) -> int:
|
|
|
543
556
|
return 0
|
|
544
557
|
|
|
545
558
|
|
|
559
|
+
def _run_git(
|
|
560
|
+
args: list[str],
|
|
561
|
+
*,
|
|
562
|
+
cwd: Path | str | None = None,
|
|
563
|
+
capture_output: bool = True,
|
|
564
|
+
check: bool = True,
|
|
565
|
+
) -> subprocess.CompletedProcess[str]:
|
|
566
|
+
try:
|
|
567
|
+
return subprocess.run(
|
|
568
|
+
["git", *args],
|
|
569
|
+
cwd=cwd,
|
|
570
|
+
capture_output=capture_output,
|
|
571
|
+
text=True,
|
|
572
|
+
check=check,
|
|
573
|
+
)
|
|
574
|
+
except FileNotFoundError:
|
|
575
|
+
raise RuntimeError(
|
|
576
|
+
"Git executable not found. Please install git to support this command."
|
|
577
|
+
) from None
|
|
578
|
+
|
|
579
|
+
|
|
546
580
|
def _repo_root(path: str) -> Path:
|
|
547
|
-
result =
|
|
548
|
-
["git", "rev-parse", "--show-toplevel"],
|
|
549
|
-
cwd=path,
|
|
550
|
-
capture_output=True,
|
|
551
|
-
text=True,
|
|
552
|
-
check=True,
|
|
553
|
-
)
|
|
581
|
+
result = _run_git(["rev-parse", "--show-toplevel"], cwd=path)
|
|
554
582
|
return Path(result.stdout.strip()).resolve()
|
|
555
583
|
|
|
556
584
|
|
|
557
585
|
def _git_branch(repo_root: Path) -> str | None:
|
|
558
|
-
result =
|
|
559
|
-
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
560
|
-
cwd=repo_root,
|
|
561
|
-
capture_output=True,
|
|
562
|
-
text=True,
|
|
563
|
-
check=True,
|
|
564
|
-
)
|
|
586
|
+
result = _run_git(["rev-parse", "--abbrev-ref", "HEAD"], cwd=repo_root)
|
|
565
587
|
branch = result.stdout.strip()
|
|
566
588
|
return branch or None
|
|
567
589
|
|
|
568
590
|
|
|
569
591
|
def _git_remote_url(repo_root: Path) -> str | None:
|
|
570
|
-
result =
|
|
571
|
-
["
|
|
592
|
+
result = _run_git(
|
|
593
|
+
["config", "--get", "remote.origin.url"],
|
|
572
594
|
cwd=repo_root,
|
|
573
|
-
capture_output=True,
|
|
574
|
-
text=True,
|
|
575
595
|
check=False,
|
|
576
596
|
)
|
|
577
597
|
remote_url = result.stdout.strip()
|
|
@@ -623,39 +643,26 @@ def _git_file_delta(
|
|
|
623
643
|
diff_command.append(f"{diff_base}...HEAD")
|
|
624
644
|
else:
|
|
625
645
|
diff_command.append("HEAD")
|
|
626
|
-
diff_result =
|
|
627
|
-
diff_command,
|
|
628
|
-
cwd=repo_root,
|
|
629
|
-
capture_output=True,
|
|
630
|
-
text=True,
|
|
631
|
-
check=True,
|
|
632
|
-
)
|
|
646
|
+
diff_result = _run_git(diff_command, cwd=repo_root)
|
|
633
647
|
changed = {line.strip() for line in diff_result.stdout.splitlines() if line.strip()}
|
|
634
648
|
|
|
635
|
-
deleted_result =
|
|
649
|
+
deleted_result = _run_git(
|
|
636
650
|
[
|
|
637
|
-
"git",
|
|
638
651
|
"diff",
|
|
639
652
|
"--name-only",
|
|
640
653
|
"--diff-filter=D",
|
|
641
654
|
*([f"{diff_base}...HEAD"] if diff_base else ["HEAD"]),
|
|
642
655
|
],
|
|
643
656
|
cwd=repo_root,
|
|
644
|
-
capture_output=True,
|
|
645
|
-
text=True,
|
|
646
|
-
check=True,
|
|
647
657
|
)
|
|
648
658
|
deleted = {
|
|
649
659
|
line.strip() for line in deleted_result.stdout.splitlines() if line.strip()
|
|
650
660
|
}
|
|
651
661
|
changed.difference_update(deleted)
|
|
652
662
|
|
|
653
|
-
untracked_result =
|
|
654
|
-
["
|
|
663
|
+
untracked_result = _run_git(
|
|
664
|
+
["ls-files", "--others", "--exclude-standard"],
|
|
655
665
|
cwd=repo_root,
|
|
656
|
-
capture_output=True,
|
|
657
|
-
text=True,
|
|
658
|
-
check=True,
|
|
659
666
|
)
|
|
660
667
|
changed.update(
|
|
661
668
|
line.strip() for line in untracked_result.stdout.splitlines() if line.strip()
|
|
@@ -1078,7 +1085,29 @@ def _target_root_key(target: str) -> str:
|
|
|
1078
1085
|
return "mcpServers"
|
|
1079
1086
|
|
|
1080
1087
|
|
|
1081
|
-
def _target_entry(
|
|
1088
|
+
def _target_entry(
|
|
1089
|
+
target: str,
|
|
1090
|
+
*,
|
|
1091
|
+
protocol: str,
|
|
1092
|
+
server_url: str,
|
|
1093
|
+
client_key: str,
|
|
1094
|
+
cwd: Path,
|
|
1095
|
+
) -> dict[str, Any]:
|
|
1096
|
+
normalized_protocol = _normalize_protocol(protocol)
|
|
1097
|
+
if normalized_protocol == "stdio":
|
|
1098
|
+
entry: dict[str, Any] = {
|
|
1099
|
+
"command": "uv",
|
|
1100
|
+
"args": ["run", "python", "-m", "minder.server"],
|
|
1101
|
+
"cwd": str(cwd),
|
|
1102
|
+
"env": {
|
|
1103
|
+
"MINDER_SERVER__TRANSPORT": "stdio",
|
|
1104
|
+
"MINDER_CLIENT_API_KEY": client_key,
|
|
1105
|
+
},
|
|
1106
|
+
}
|
|
1107
|
+
if target in {"vscode", "cursor"}:
|
|
1108
|
+
entry["type"] = "stdio"
|
|
1109
|
+
return entry
|
|
1110
|
+
|
|
1082
1111
|
sse_url = _sse_url(server_url)
|
|
1083
1112
|
mcp_url = _mcp_url(server_url)
|
|
1084
1113
|
if target == "vscode":
|
|
@@ -1101,11 +1130,25 @@ def _target_entry(target: str, server_url: str, client_key: str) -> dict[str, An
|
|
|
1101
1130
|
raise ValueError(f"Unsupported MCP target: {target}")
|
|
1102
1131
|
|
|
1103
1132
|
|
|
1104
|
-
def _install_target(
|
|
1133
|
+
def _install_target(
|
|
1134
|
+
path: Path,
|
|
1135
|
+
target: str,
|
|
1136
|
+
*,
|
|
1137
|
+
protocol: str,
|
|
1138
|
+
server_url: str,
|
|
1139
|
+
client_key: str,
|
|
1140
|
+
cwd: Path,
|
|
1141
|
+
) -> None:
|
|
1105
1142
|
payload = _load_json(path)
|
|
1106
1143
|
root_key = _target_root_key(target)
|
|
1107
1144
|
payload.setdefault(root_key, {})
|
|
1108
|
-
payload[root_key]["minder"] = _target_entry(
|
|
1145
|
+
payload[root_key]["minder"] = _target_entry(
|
|
1146
|
+
target,
|
|
1147
|
+
protocol=protocol,
|
|
1148
|
+
server_url=server_url,
|
|
1149
|
+
client_key=client_key,
|
|
1150
|
+
cwd=cwd,
|
|
1151
|
+
)
|
|
1109
1152
|
if target == "vscode":
|
|
1110
1153
|
payload.setdefault("inputs", [])
|
|
1111
1154
|
_write_json(path, payload)
|
|
@@ -1159,9 +1202,19 @@ def _login(args: argparse.Namespace) -> int:
|
|
|
1159
1202
|
|
|
1160
1203
|
config_path = Path(args.config_path).expanduser()
|
|
1161
1204
|
existing = _load_json(config_path)
|
|
1205
|
+
default_protocol = _normalize_protocol(
|
|
1206
|
+
str(existing.get("protocol", _DEFAULT_PROTOCOL))
|
|
1207
|
+
)
|
|
1208
|
+
protocol = _normalize_protocol(args.protocol or _prompt_protocol(default_protocol))
|
|
1209
|
+
default_server_url = str(existing.get("server_url", _DEFAULT_SERVER_URL)).strip()
|
|
1210
|
+
if protocol == "sse":
|
|
1211
|
+
server_url = (args.server_url or "").strip() or default_server_url
|
|
1212
|
+
else:
|
|
1213
|
+
server_url = (args.server_url or "").strip() or default_server_url
|
|
1162
1214
|
payload = {
|
|
1163
1215
|
**existing,
|
|
1164
|
-
"
|
|
1216
|
+
"protocol": protocol,
|
|
1217
|
+
"server_url": server_url,
|
|
1165
1218
|
"client_api_key": client_key,
|
|
1166
1219
|
"default_headers": {
|
|
1167
1220
|
"X-Minder-Client-Key": client_key,
|
|
@@ -1169,6 +1222,8 @@ def _login(args: argparse.Namespace) -> int:
|
|
|
1169
1222
|
}
|
|
1170
1223
|
_write_json(config_path, payload)
|
|
1171
1224
|
print(f"Stored client credentials in {config_path}")
|
|
1225
|
+
print(f"Protocol: {protocol}")
|
|
1226
|
+
print(f"Server URL: {server_url}")
|
|
1172
1227
|
print(f"export MINDER_CLIENT_API_KEY={client_key}")
|
|
1173
1228
|
return 0
|
|
1174
1229
|
|
|
@@ -1176,6 +1231,7 @@ def _login(args: argparse.Namespace) -> int:
|
|
|
1176
1231
|
def _install_mcp(args: argparse.Namespace) -> int:
|
|
1177
1232
|
config_path = Path(args.config_path).expanduser()
|
|
1178
1233
|
settings = _require_client_settings(config_path)
|
|
1234
|
+
protocol = str(settings.get("protocol", _DEFAULT_PROTOCOL))
|
|
1179
1235
|
targets = _parse_targets(args.target)
|
|
1180
1236
|
install_root = Path(args.cwd).resolve()
|
|
1181
1237
|
installed_paths: list[Path] = []
|
|
@@ -1189,8 +1245,10 @@ def _install_mcp(args: argparse.Namespace) -> int:
|
|
|
1189
1245
|
_install_target(
|
|
1190
1246
|
path,
|
|
1191
1247
|
target,
|
|
1192
|
-
|
|
1193
|
-
str(settings
|
|
1248
|
+
protocol=protocol,
|
|
1249
|
+
server_url=str(settings.get("server_url", "")),
|
|
1250
|
+
client_key=str(settings["client_api_key"]),
|
|
1251
|
+
cwd=install_root,
|
|
1194
1252
|
)
|
|
1195
1253
|
installed_paths.append(path)
|
|
1196
1254
|
|
|
@@ -1222,6 +1280,7 @@ def _uninstall_mcp(args: argparse.Namespace) -> int:
|
|
|
1222
1280
|
def _install_ide(args: argparse.Namespace) -> int:
|
|
1223
1281
|
config_path = Path(args.config_path).expanduser()
|
|
1224
1282
|
settings = _require_client_settings(config_path)
|
|
1283
|
+
protocol = str(settings.get("protocol", _DEFAULT_PROTOCOL))
|
|
1225
1284
|
targets = _parse_targets(args.target)
|
|
1226
1285
|
install_root = Path(args.cwd).resolve()
|
|
1227
1286
|
installed_paths: list[Path] = []
|
|
@@ -1231,8 +1290,10 @@ def _install_ide(args: argparse.Namespace) -> int:
|
|
|
1231
1290
|
_install_target(
|
|
1232
1291
|
path,
|
|
1233
1292
|
target,
|
|
1234
|
-
|
|
1235
|
-
str(settings
|
|
1293
|
+
protocol=protocol,
|
|
1294
|
+
server_url=str(settings.get("server_url", "")),
|
|
1295
|
+
client_key=str(settings["client_api_key"]),
|
|
1296
|
+
cwd=install_root,
|
|
1236
1297
|
)
|
|
1237
1298
|
installed_paths.append(path)
|
|
1238
1299
|
|
|
@@ -1298,6 +1359,11 @@ def _resolve_repo_id(
|
|
|
1298
1359
|
def _sync(args: argparse.Namespace) -> int:
|
|
1299
1360
|
config_path = Path(args.config_path).expanduser()
|
|
1300
1361
|
settings = _require_client_settings(config_path)
|
|
1362
|
+
protocol = str(settings.get("protocol", _DEFAULT_PROTOCOL))
|
|
1363
|
+
if protocol != "sse":
|
|
1364
|
+
raise ValueError(
|
|
1365
|
+
"minder sync requires protocol 'sse' with a reachable server_url."
|
|
1366
|
+
)
|
|
1301
1367
|
if not args.skip_upgrade_check:
|
|
1302
1368
|
_maybe_print_upgrade_notice()
|
|
1303
1369
|
repo_root = _repo_root(args.repo_path)
|
|
@@ -1332,23 +1398,65 @@ def _sync(args: argparse.Namespace) -> int:
|
|
|
1332
1398
|
return 0
|
|
1333
1399
|
|
|
1334
1400
|
|
|
1401
|
+
def _version(args: argparse.Namespace) -> int:
|
|
1402
|
+
version = _installed_package_version()
|
|
1403
|
+
if version:
|
|
1404
|
+
print(f"minder {version}")
|
|
1405
|
+
else:
|
|
1406
|
+
print("minder version unknown (not installed as a package)")
|
|
1407
|
+
return 0
|
|
1408
|
+
|
|
1409
|
+
|
|
1410
|
+
def _check_version(args: argparse.Namespace) -> int: # noqa: ARG001
|
|
1411
|
+
installed, latest, has_update = _cli_update_available()
|
|
1412
|
+
print("CLI version:")
|
|
1413
|
+
print(f" installed: {installed or 'unknown'}")
|
|
1414
|
+
print(f" latest: {latest or 'unknown'}")
|
|
1415
|
+
if installed and latest:
|
|
1416
|
+
if has_update:
|
|
1417
|
+
print(f" status: update available ({installed} -> {latest})")
|
|
1418
|
+
else:
|
|
1419
|
+
print(" status: up to date")
|
|
1420
|
+
else:
|
|
1421
|
+
print(" status: unable to determine latest version")
|
|
1422
|
+
return 0
|
|
1423
|
+
|
|
1424
|
+
|
|
1335
1425
|
def build_parser() -> argparse.ArgumentParser:
|
|
1336
1426
|
parser = argparse.ArgumentParser(description="Minder CLI")
|
|
1427
|
+
parser.add_argument(
|
|
1428
|
+
"-v",
|
|
1429
|
+
"--version",
|
|
1430
|
+
action="version",
|
|
1431
|
+
version=f"minder {_installed_package_version() or 'unknown'}",
|
|
1432
|
+
)
|
|
1337
1433
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
1338
1434
|
|
|
1435
|
+
subparsers.add_parser("version", help="Show the Minder CLI version.")
|
|
1436
|
+
subparsers.add_parser(
|
|
1437
|
+
"check-version",
|
|
1438
|
+
help="Show installed CLI version and latest published version.",
|
|
1439
|
+
)
|
|
1440
|
+
|
|
1339
1441
|
login = subparsers.add_parser(
|
|
1340
1442
|
"login",
|
|
1341
|
-
help="Store
|
|
1443
|
+
help="Store Minder client auth + transport settings for CLI commands.",
|
|
1342
1444
|
)
|
|
1343
1445
|
login.add_argument(
|
|
1344
1446
|
"--client-key",
|
|
1345
1447
|
default=None,
|
|
1346
1448
|
help="Client API key in mkc_... format. If omitted, prompt securely.",
|
|
1347
1449
|
)
|
|
1450
|
+
login.add_argument(
|
|
1451
|
+
"--protocol",
|
|
1452
|
+
choices=("sse", "stdio"),
|
|
1453
|
+
default=None,
|
|
1454
|
+
help="Client protocol mode: sse (remote) or stdio (local process).",
|
|
1455
|
+
)
|
|
1348
1456
|
login.add_argument(
|
|
1349
1457
|
"--server-url",
|
|
1350
|
-
default=
|
|
1351
|
-
help="
|
|
1458
|
+
default=None,
|
|
1459
|
+
help="Minder server URL. Used for SSE mode and remote sync APIs.",
|
|
1352
1460
|
)
|
|
1353
1461
|
login.add_argument(
|
|
1354
1462
|
"--config-path",
|
|
@@ -1454,26 +1562,48 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1454
1562
|
help="Server install directory to inspect. Defaults to ~/.minder/current or the newest release directory.",
|
|
1455
1563
|
)
|
|
1456
1564
|
|
|
1565
|
+
update = subparsers.add_parser(
|
|
1566
|
+
"update",
|
|
1567
|
+
help="Apply an update for the Minder CLI, server deployment, or both.",
|
|
1568
|
+
)
|
|
1569
|
+
update.add_argument(
|
|
1570
|
+
"--component",
|
|
1571
|
+
choices=("cli", "server", "all"),
|
|
1572
|
+
default="cli",
|
|
1573
|
+
help="Which component to update.",
|
|
1574
|
+
)
|
|
1575
|
+
update.add_argument(
|
|
1576
|
+
"--manager",
|
|
1577
|
+
choices=("auto", "uv", "pipx", "pip"),
|
|
1578
|
+
default="auto",
|
|
1579
|
+
help="Preferred package manager for CLI update.",
|
|
1580
|
+
)
|
|
1581
|
+
update.add_argument(
|
|
1582
|
+
"--install-dir",
|
|
1583
|
+
default=None,
|
|
1584
|
+
help="Server install directory to update. Defaults to ~/.minder/current or the newest release directory.",
|
|
1585
|
+
)
|
|
1586
|
+
|
|
1457
1587
|
self_update = subparsers.add_parser(
|
|
1458
1588
|
"self-update",
|
|
1459
|
-
help=
|
|
1589
|
+
help=argparse.SUPPRESS,
|
|
1460
1590
|
)
|
|
1461
1591
|
self_update.add_argument(
|
|
1462
1592
|
"--component",
|
|
1463
1593
|
choices=("cli", "server", "all"),
|
|
1464
1594
|
default="cli",
|
|
1465
|
-
help=
|
|
1595
|
+
help=argparse.SUPPRESS,
|
|
1466
1596
|
)
|
|
1467
1597
|
self_update.add_argument(
|
|
1468
1598
|
"--manager",
|
|
1469
1599
|
choices=("auto", "uv", "pipx", "pip"),
|
|
1470
1600
|
default="auto",
|
|
1471
|
-
help=
|
|
1601
|
+
help=argparse.SUPPRESS,
|
|
1472
1602
|
)
|
|
1473
1603
|
self_update.add_argument(
|
|
1474
1604
|
"--install-dir",
|
|
1475
1605
|
default=None,
|
|
1476
|
-
help=
|
|
1606
|
+
help=argparse.SUPPRESS,
|
|
1477
1607
|
)
|
|
1478
1608
|
|
|
1479
1609
|
sync = subparsers.add_parser(
|
|
@@ -1529,10 +1659,14 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1529
1659
|
return _uninstall_ide(args)
|
|
1530
1660
|
if args.command == "check-update":
|
|
1531
1661
|
return _check_update(args)
|
|
1532
|
-
if args.command
|
|
1533
|
-
return
|
|
1662
|
+
if args.command in {"update", "self-update"}:
|
|
1663
|
+
return _update(args)
|
|
1534
1664
|
if args.command == "sync":
|
|
1535
1665
|
return _sync(args)
|
|
1666
|
+
if args.command == "version":
|
|
1667
|
+
return _version(args)
|
|
1668
|
+
if args.command == "check-version":
|
|
1669
|
+
return _check_version(args)
|
|
1536
1670
|
|
|
1537
1671
|
parser.error(f"Unknown command: {args.command}")
|
|
1538
1672
|
return 2
|
|
@@ -33,7 +33,7 @@ class AuthConfig(BaseModel):
|
|
|
33
33
|
class EmbeddingConfig(BaseModel):
|
|
34
34
|
provider: str = "ollama"
|
|
35
35
|
ollama_url: str = "http://localhost:11434"
|
|
36
|
-
ollama_model: str = "
|
|
36
|
+
ollama_model: str = "qwen3-embedding:0.6b"
|
|
37
37
|
dimensions: int = 768
|
|
38
38
|
openai_api_key: Optional[str] = None
|
|
39
39
|
openai_model: str = "text-embedding-3-small"
|
|
@@ -42,7 +42,7 @@ class EmbeddingConfig(BaseModel):
|
|
|
42
42
|
class LLMConfig(BaseModel):
|
|
43
43
|
provider: str = "ollama"
|
|
44
44
|
ollama_url: str = "http://localhost:11434"
|
|
45
|
-
ollama_model: str = "
|
|
45
|
+
ollama_model: str = "qwen3.5:4b"
|
|
46
46
|
context_length: int = 131072
|
|
47
47
|
temperature: float = 0.1
|
|
48
48
|
openai_api_key: Optional[str] = None
|
|
@@ -18,7 +18,7 @@ class LocalEmbeddingProvider:
|
|
|
18
18
|
def __init__(
|
|
19
19
|
self,
|
|
20
20
|
ollama_url: str = "http://localhost:11434",
|
|
21
|
-
ollama_model: str = "
|
|
21
|
+
ollama_model: str = "qwen3-embedding:0.6b",
|
|
22
22
|
dimensions: int = 768,
|
|
23
23
|
runtime: str = "auto",
|
|
24
24
|
) -> None:
|
|
@@ -73,4 +73,4 @@ class LocalEmbeddingProvider:
|
|
|
73
73
|
return None
|
|
74
74
|
except Exception:
|
|
75
75
|
logger.warning("Ollama embedding failed, using hash fallback")
|
|
76
|
-
return None
|
|
76
|
+
return None
|
|
@@ -132,6 +132,28 @@ class RepoScanner:
|
|
|
132
132
|
self._git_metadata_cache: dict[str, dict[str, Any]] = {}
|
|
133
133
|
self._git_line_commit_cache: dict[tuple[str, int], dict[str, str] | None] = {}
|
|
134
134
|
self._git_commit_detail_cache: dict[str, dict[str, str]] = {}
|
|
135
|
+
self._git_enabled = True
|
|
136
|
+
|
|
137
|
+
def _run_git(
|
|
138
|
+
self,
|
|
139
|
+
args: list[str],
|
|
140
|
+
*,
|
|
141
|
+
capture_output: bool = True,
|
|
142
|
+
check: bool = True,
|
|
143
|
+
) -> subprocess.CompletedProcess[str]:
|
|
144
|
+
if not self._git_enabled:
|
|
145
|
+
return subprocess.CompletedProcess(args, 1, stdout="", stderr="")
|
|
146
|
+
try:
|
|
147
|
+
return subprocess.run(
|
|
148
|
+
["git", *args],
|
|
149
|
+
cwd=self._root,
|
|
150
|
+
capture_output=capture_output,
|
|
151
|
+
text=True,
|
|
152
|
+
check=check,
|
|
153
|
+
)
|
|
154
|
+
except FileNotFoundError:
|
|
155
|
+
self._git_enabled = False
|
|
156
|
+
return subprocess.CompletedProcess(args, 1, stdout="", stderr="")
|
|
135
157
|
|
|
136
158
|
async def scan(self) -> dict[str, Any]:
|
|
137
159
|
service_dirs = self._discover_service_boundaries()
|
|
@@ -258,6 +280,7 @@ class RepoScanner:
|
|
|
258
280
|
builder._git_metadata_cache = {}
|
|
259
281
|
builder._git_line_commit_cache = {}
|
|
260
282
|
builder._git_commit_detail_cache = {}
|
|
283
|
+
builder._git_enabled = True
|
|
261
284
|
|
|
262
285
|
service_dirs = builder._discover_service_boundaries()
|
|
263
286
|
source_files = builder._resolve_source_files(changed_files)
|
|
@@ -526,9 +549,8 @@ class RepoScanner:
|
|
|
526
549
|
}
|
|
527
550
|
|
|
528
551
|
def _git_recent_commits(self, rel_path: str, limit: int = 5) -> list[dict[str, str]]:
|
|
529
|
-
result =
|
|
552
|
+
result = self._run_git(
|
|
530
553
|
[
|
|
531
|
-
"git",
|
|
532
554
|
"log",
|
|
533
555
|
"--follow",
|
|
534
556
|
"--format=%H%x1f%cI%x1f%s",
|
|
@@ -537,9 +559,6 @@ class RepoScanner:
|
|
|
537
559
|
"--",
|
|
538
560
|
rel_path,
|
|
539
561
|
],
|
|
540
|
-
cwd=self._root,
|
|
541
|
-
capture_output=True,
|
|
542
|
-
text=True,
|
|
543
562
|
check=False,
|
|
544
563
|
)
|
|
545
564
|
if result.returncode != 0:
|
|
@@ -564,9 +583,8 @@ class RepoScanner:
|
|
|
564
583
|
if cache_key in self._git_line_commit_cache:
|
|
565
584
|
return self._git_line_commit_cache[cache_key]
|
|
566
585
|
|
|
567
|
-
result =
|
|
586
|
+
result = self._run_git(
|
|
568
587
|
[
|
|
569
|
-
"git",
|
|
570
588
|
"blame",
|
|
571
589
|
"--line-porcelain",
|
|
572
590
|
"-L",
|
|
@@ -574,9 +592,6 @@ class RepoScanner:
|
|
|
574
592
|
"--",
|
|
575
593
|
rel_path,
|
|
576
594
|
],
|
|
577
|
-
cwd=self._root,
|
|
578
|
-
capture_output=True,
|
|
579
|
-
text=True,
|
|
580
595
|
check=False,
|
|
581
596
|
)
|
|
582
597
|
if result.returncode != 0:
|
|
@@ -598,11 +613,8 @@ class RepoScanner:
|
|
|
598
613
|
if cached is not None:
|
|
599
614
|
return cached
|
|
600
615
|
|
|
601
|
-
result =
|
|
602
|
-
["
|
|
603
|
-
cwd=self._root,
|
|
604
|
-
capture_output=True,
|
|
605
|
-
text=True,
|
|
616
|
+
result = self._run_git(
|
|
617
|
+
["show", "-s", "--format=%H%x1f%cI%x1f%s", sha],
|
|
606
618
|
check=False,
|
|
607
619
|
)
|
|
608
620
|
if result.returncode != 0:
|
|
@@ -627,11 +639,8 @@ class RepoScanner:
|
|
|
627
639
|
return details
|
|
628
640
|
|
|
629
641
|
def _git_status(self, rel_path: str, *, tracked: bool) -> str:
|
|
630
|
-
result =
|
|
631
|
-
["
|
|
632
|
-
cwd=self._root,
|
|
633
|
-
capture_output=True,
|
|
634
|
-
text=True,
|
|
642
|
+
result = self._run_git(
|
|
643
|
+
["status", "--short", "--", rel_path],
|
|
635
644
|
check=False,
|
|
636
645
|
)
|
|
637
646
|
if result.returncode != 0:
|
|
@@ -294,12 +294,17 @@ class SkillTools:
|
|
|
294
294
|
if ref:
|
|
295
295
|
command += ["--branch", ref]
|
|
296
296
|
command += [repo_url, tmp_dir]
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
297
|
+
try:
|
|
298
|
+
result = subprocess.run(
|
|
299
|
+
command,
|
|
300
|
+
capture_output=True,
|
|
301
|
+
text=True,
|
|
302
|
+
check=False,
|
|
303
|
+
)
|
|
304
|
+
except FileNotFoundError:
|
|
305
|
+
raise RuntimeError(
|
|
306
|
+
"Git executable not found. Please install git to support skill importation from remote repositories."
|
|
307
|
+
) from None
|
|
303
308
|
if result.returncode != 0:
|
|
304
309
|
message = (
|
|
305
310
|
result.stderr.strip() or result.stdout.strip() or "git clone failed"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|