minder-cli 0.3.1__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.
Files changed (132) hide show
  1. {minder_cli-0.3.1 → minder_cli-0.3.3}/PKG-INFO +1 -1
  2. {minder_cli-0.3.1 → minder_cli-0.3.3}/pyproject.toml +1 -1
  3. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/cli.py +208 -74
  4. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/config.py +2 -2
  5. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/continuity.py +1 -1
  6. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/embedding/local.py +2 -2
  7. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/nodes/reasoning.py +5 -2
  8. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/llm/local.py +13 -6
  9. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/prompts/formatter.py +1 -1
  10. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/tools/repo_scanner.py +29 -20
  11. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/tools/skills.py +11 -6
  12. {minder_cli-0.3.1 → minder_cli-0.3.3}/.gitignore +0 -0
  13. {minder_cli-0.3.1 → minder_cli-0.3.3}/LICENSE +0 -0
  14. {minder_cli-0.3.1 → minder_cli-0.3.3}/README.md +0 -0
  15. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/__init__.py +0 -0
  16. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/api/routers/prompts.py +0 -0
  17. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/application/__init__.py +0 -0
  18. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/application/admin/__init__.py +0 -0
  19. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/application/admin/dto.py +0 -0
  20. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/application/admin/jobs.py +0 -0
  21. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/application/admin/use_cases.py +0 -0
  22. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/auth/__init__.py +0 -0
  23. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/auth/context.py +0 -0
  24. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/auth/middleware.py +0 -0
  25. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/auth/principal.py +0 -0
  26. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/auth/rate_limiter.py +0 -0
  27. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/auth/rbac.py +0 -0
  28. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/auth/service.py +0 -0
  29. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/bootstrap/__init__.py +0 -0
  30. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/bootstrap/providers.py +0 -0
  31. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/bootstrap/transport.py +0 -0
  32. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/cache/__init__.py +0 -0
  33. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/cache/providers.py +0 -0
  34. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/chunking/__init__.py +0 -0
  35. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/chunking/code_splitter.py +0 -0
  36. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/chunking/splitter.py +0 -0
  37. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/dev.py +0 -0
  38. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/embedding/__init__.py +0 -0
  39. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/embedding/base.py +0 -0
  40. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/embedding/openai.py +0 -0
  41. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/__init__.py +0 -0
  42. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/edges.py +0 -0
  43. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/executor.py +0 -0
  44. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/graph.py +0 -0
  45. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/nodes/__init__.py +0 -0
  46. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/nodes/evaluator.py +0 -0
  47. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/nodes/guard.py +0 -0
  48. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/nodes/llm.py +0 -0
  49. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/nodes/planning.py +0 -0
  50. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/nodes/reranker.py +0 -0
  51. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/nodes/retriever.py +0 -0
  52. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/nodes/verification.py +0 -0
  53. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/nodes/workflow_planner.py +0 -0
  54. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/runtime.py +0 -0
  55. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/graph/state.py +0 -0
  56. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/llm/__init__.py +0 -0
  57. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/llm/base.py +0 -0
  58. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/llm/openai.py +0 -0
  59. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/models/__init__.py +0 -0
  60. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/models/base.py +0 -0
  61. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/models/client.py +0 -0
  62. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/models/document.py +0 -0
  63. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/models/error.py +0 -0
  64. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/models/graph.py +0 -0
  65. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/models/history.py +0 -0
  66. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/models/job.py +0 -0
  67. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/models/prompt.py +0 -0
  68. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/models/repository.py +0 -0
  69. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/models/rule.py +0 -0
  70. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/models/session.py +0 -0
  71. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/models/skill.py +0 -0
  72. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/models/user.py +0 -0
  73. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/models/workflow.py +0 -0
  74. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/observability/__init__.py +0 -0
  75. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/observability/audit.py +0 -0
  76. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/observability/logging.py +0 -0
  77. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/observability/metrics.py +0 -0
  78. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/observability/tracing.py +0 -0
  79. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/presentation/__init__.py +0 -0
  80. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/presentation/http/__init__.py +0 -0
  81. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/presentation/http/admin/__init__.py +0 -0
  82. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/presentation/http/admin/api.py +0 -0
  83. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/presentation/http/admin/context.py +0 -0
  84. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/presentation/http/admin/dashboard.py +0 -0
  85. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/presentation/http/admin/jobs.py +0 -0
  86. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/presentation/http/admin/memories.py +0 -0
  87. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/presentation/http/admin/prompts.py +0 -0
  88. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/presentation/http/admin/routes.py +0 -0
  89. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/presentation/http/admin/runtime.py +0 -0
  90. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/presentation/http/admin/search.py +0 -0
  91. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/presentation/http/admin/skills.py +0 -0
  92. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/prompts/__init__.py +0 -0
  93. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/resources/__init__.py +0 -0
  94. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/retrieval/__init__.py +0 -0
  95. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/retrieval/hybrid.py +0 -0
  96. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/retrieval/mmr.py +0 -0
  97. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/retrieval/multi_hop.py +0 -0
  98. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/runtime.py +0 -0
  99. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/server.py +0 -0
  100. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/__init__.py +0 -0
  101. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/document.py +0 -0
  102. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/error.py +0 -0
  103. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/feedback.py +0 -0
  104. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/graph.py +0 -0
  105. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/history.py +0 -0
  106. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/interfaces.py +0 -0
  107. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/milvus/__init__.py +0 -0
  108. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/milvus/client.py +0 -0
  109. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/milvus/collections.py +0 -0
  110. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/milvus/vector_store.py +0 -0
  111. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/mongodb/__init__.py +0 -0
  112. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/mongodb/client.py +0 -0
  113. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/mongodb/indexes.py +0 -0
  114. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/mongodb/operational_store.py +0 -0
  115. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/relational.py +0 -0
  116. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/repo_state.py +0 -0
  117. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/rule.py +0 -0
  118. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/store/vector.py +0 -0
  119. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/tools/__init__.py +0 -0
  120. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/tools/auth.py +0 -0
  121. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/tools/graph.py +0 -0
  122. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/tools/ingest.py +0 -0
  123. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/tools/memory.py +0 -0
  124. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/tools/query.py +0 -0
  125. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/tools/registry.py +0 -0
  126. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/tools/search.py +0 -0
  127. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/tools/session.py +0 -0
  128. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/tools/workflow.py +0 -0
  129. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/transport/__init__.py +0 -0
  130. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/transport/base.py +0 -0
  131. {minder_cli-0.3.1 → minder_cli-0.3.3}/src/minder/transport/sse.py +0 -0
  132. {minder_cli-0.3.1 → 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.1
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "minder-cli"
7
- version = "0.3.1"
7
+ version = "0.3.3"
8
8
  description = "Minder CLI is the command-line interface for the Minder self-hosted MCP platform."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.14"
@@ -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=True,
422
+ capture_output=False,
405
423
  text=True,
406
424
  check=False,
407
425
  )
408
426
  if result.returncode == 0:
409
- output = result.stdout.strip()
410
- if output:
411
- print(output)
412
- print(f"CLI self-update completed via: {' '.join(command)}")
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
- details = (
415
- result.stderr.strip() or result.stdout.strip() or str(result.returncode)
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=True,
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=True,
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
- details = (
500
- result.stderr.strip() or result.stdout.strip() or str(result.returncode)
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 self-update completed for {install_dir}: "
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 _self_update(args: argparse.Namespace) -> int:
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 = subprocess.run(
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 = subprocess.run(
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 = subprocess.run(
571
- ["git", "config", "--get", "remote.origin.url"],
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 = subprocess.run(
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 = subprocess.run(
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 = subprocess.run(
654
- ["git", "ls-files", "--others", "--exclude-standard"],
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(target: str, server_url: str, client_key: str) -> dict[str, Any]:
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(path: Path, target: str, server_url: str, client_key: str) -> None:
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(target, server_url, client_key)
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
- "server_url": args.server_url,
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
- str(settings["server_url"]),
1193
- str(settings["client_api_key"]),
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
- str(settings["server_url"]),
1235
- str(settings["client_api_key"]),
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 a Minder client API key for CLI-authenticated commands.",
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=_DEFAULT_SERVER_URL,
1351
- help="Default Minder server URL to pair with the stored client key.",
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="Apply a self-update for the Minder CLI, server deployment, or both.",
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="Which component to self-update.",
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="Preferred package manager for CLI self-update.",
1601
+ help=argparse.SUPPRESS,
1472
1602
  )
1473
1603
  self_update.add_argument(
1474
1604
  "--install-dir",
1475
1605
  default=None,
1476
- help="Server install directory to update. Defaults to ~/.minder/current or the newest release directory.",
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 == "self-update":
1533
- return _self_update(args)
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 = "embeddinggemma"
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 = "gemma4:e4b"
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
@@ -330,7 +330,7 @@ class ContinuitySynthesizer:
330
330
  },
331
331
  }, {
332
332
  "provider": "local_llm",
333
- "model": "gemma-4-e2b-it",
333
+ "model": self._config.llm.ollama_model,
334
334
  "runtime": self._llm.runtime,
335
335
  }
336
336
 
@@ -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 = "embeddinggemma",
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
@@ -9,12 +9,15 @@ from minder.tools.registry import tool_capability_manifest, tool_data_access_pol
9
9
 
10
10
  class ReasoningNode:
11
11
  def run(self, state: GraphState) -> GraphState:
12
+ reranked = getattr(state, "reranked_docs", []) or []
13
+ retrieved = getattr(state, "retrieved_docs", []) or []
14
+ docs = reranked or retrieved
12
15
  sources = [
13
16
  {"path": doc["path"], "title": doc["title"], "score": doc["score"]}
14
- for doc in state.reranked_docs
17
+ for doc in docs
15
18
  ]
16
19
  snippets = []
17
- for doc in state.reranked_docs[:3]:
20
+ for doc in docs[:3]:
18
21
  content = str(doc["content"]).strip()
19
22
  snippets.append(f"Source: {doc['path']}\n{content[:240]}")
20
23
 
@@ -24,7 +24,7 @@ class LocalModelLLM:
24
24
  def __init__(
25
25
  self,
26
26
  ollama_url: str = "http://localhost:11434",
27
- ollama_model: str = "gemma4:e4b",
27
+ ollama_model: str = "qwen3.5:4b",
28
28
  fail: bool = False,
29
29
  context_length: int = 131072,
30
30
  ) -> None:
@@ -53,8 +53,11 @@ class LocalModelLLM:
53
53
  raise RuntimeError("Local model unavailable")
54
54
 
55
55
  runtime = self.runtime
56
- source_paths = [doc["path"] for doc in state.reranked_docs[:3]]
57
- guidance = state.workflow_context.get("guidance", "")
56
+ reranked = getattr(state, "reranked_docs", []) or []
57
+ retrieved = getattr(state, "retrieved_docs", []) or []
58
+ docs = reranked or retrieved
59
+ source_paths = [doc["path"] for doc in docs[:3]]
60
+ guidance = getattr(state, "workflow_context", {}).get("guidance", "")
58
61
  fallback = (
59
62
  f"{guidance}\n"
60
63
  f"Plan intent: {state.plan.get('intent', 'unknown')}.\n"
@@ -82,8 +85,11 @@ class LocalModelLLM:
82
85
  raise RuntimeError("Local model unavailable")
83
86
 
84
87
  runtime = self.runtime
85
- source_paths = [doc["path"] for doc in state.reranked_docs[:3]]
86
- guidance = state.workflow_context.get("guidance", "")
88
+ reranked = getattr(state, "reranked_docs", []) or []
89
+ retrieved = getattr(state, "retrieved_docs", []) or []
90
+ docs = reranked or retrieved
91
+ source_paths = [doc["path"] for doc in docs[:3]]
92
+ guidance = getattr(state, "workflow_context", {}).get("guidance", "")
87
93
  fallback = (
88
94
  f"{guidance}\n"
89
95
  f"Plan intent: {state.plan.get('intent', 'unknown')}.\n"
@@ -132,7 +138,8 @@ class LocalModelLLM:
132
138
  return fallback
133
139
 
134
140
  try:
135
- return self._chat_ollama(prompt, max_tokens=max_tokens, temperature=temperature)
141
+ res = self._chat_ollama(prompt, max_tokens=max_tokens, temperature=temperature)
142
+ return res or fallback
136
143
  except Exception:
137
144
  logger.warning("Ollama completion failed, using fallback")
138
145
  return fallback
@@ -137,6 +137,6 @@ Make the prompt direct, structured, and useful for a coding workflow.
137
137
  )
138
138
  return merged, {
139
139
  "provider": "local_llm",
140
- "model": "gemma-4-e2b-it",
140
+ "model": config.llm.ollama_model,
141
141
  "runtime": runtime,
142
142
  }