comate-cli 0.1.11__tar.gz → 0.2.1__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 (95) hide show
  1. {comate_cli-0.1.11 → comate_cli-0.2.1}/PKG-INFO +1 -1
  2. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/mcp_cli.py +30 -23
  3. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/animations.py +21 -20
  4. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/app.py +13 -8
  5. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/custom_slash_commands.py +13 -7
  6. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/event_renderer.py +149 -133
  7. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/logging_adapter.py +94 -15
  8. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/logo.py +7 -2
  9. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/question_view.py +22 -14
  10. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/rpc_protocol.py +12 -5
  11. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/rpc_stdio.py +105 -10
  12. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/status_bar.py +1 -2
  13. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/tool_view.py +36 -4
  14. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/tui.py +155 -105
  15. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/tui_parts/history_sync.py +2 -2
  16. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/tui_parts/key_bindings.py +51 -18
  17. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/tui_parts/render_panels.py +53 -3
  18. {comate_cli-0.1.11 → comate_cli-0.2.1}/pyproject.toml +1 -1
  19. comate_cli-0.2.1/tests/test_app_mcp_preload.py +48 -0
  20. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_completion_status_panel.py +88 -4
  21. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_custom_slash_commands.py +39 -0
  22. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_event_renderer.py +108 -52
  23. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_history_sync.py +5 -5
  24. comate_cli-0.2.1/tests/test_interrupt_exit_semantics.py +300 -0
  25. comate_cli-0.2.1/tests/test_logging_adapter.py +68 -0
  26. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_logo.py +14 -0
  27. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_mcp_cli.py +58 -0
  28. comate_cli-0.2.1/tests/test_question_key_bindings.py +181 -0
  29. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_question_view.py +117 -0
  30. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_rpc_protocol.py +7 -0
  31. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_rpc_stdio_bridge.py +83 -0
  32. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_status_bar.py +22 -0
  33. comate_cli-0.2.1/tests/test_task_panel_key_bindings.py +195 -0
  34. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_tool_view.py +1 -1
  35. comate_cli-0.2.1/uv.lock +2243 -0
  36. comate_cli-0.1.11/uv.lock +0 -2259
  37. {comate_cli-0.1.11 → comate_cli-0.2.1}/.gitignore +0 -0
  38. {comate_cli-0.1.11 → comate_cli-0.2.1}/README.md +0 -0
  39. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/__init__.py +0 -0
  40. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/__main__.py +0 -0
  41. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/main.py +0 -0
  42. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/__init__.py +0 -0
  43. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/assistant_render.py +0 -0
  44. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/env_utils.py +0 -0
  45. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/error_display.py +0 -0
  46. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/fragment_utils.py +0 -0
  47. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/history_printer.py +0 -0
  48. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/input_geometry.py +0 -0
  49. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/layout_coordinator.py +0 -0
  50. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/markdown_render.py +0 -0
  51. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/mention_completer.py +0 -0
  52. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/message_style.py +0 -0
  53. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/models.py +0 -0
  54. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/preflight.py +0 -0
  55. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/resume_selector.py +0 -0
  56. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/rewind_store.py +0 -0
  57. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/selection_menu.py +0 -0
  58. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/session_view.py +0 -0
  59. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/slash_commands.py +0 -0
  60. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/startup.py +0 -0
  61. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/text_effects.py +0 -0
  62. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/tips.py +0 -0
  63. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/tui_parts/__init__.py +0 -0
  64. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/tui_parts/commands.py +0 -0
  65. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/tui_parts/input_behavior.py +0 -0
  66. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/tui_parts/slash_command_registry.py +0 -0
  67. {comate_cli-0.1.11 → comate_cli-0.2.1}/comate_cli/terminal_agent/tui_parts/ui_mode.py +0 -0
  68. {comate_cli-0.1.11 → comate_cli-0.2.1}/test_memory.md +0 -0
  69. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/conftest.py +0 -0
  70. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_app_preflight_gate.py +0 -0
  71. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_app_shutdown.py +0 -0
  72. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_app_usage_line.py +0 -0
  73. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_cli_project_root.py +0 -0
  74. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_compact_command_semantics.py +0 -0
  75. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_completion_context_activation.py +0 -0
  76. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_context_command.py +0 -0
  77. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_input_behavior.py +0 -0
  78. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_layout_coordinator.py +0 -0
  79. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_main_args.py +0 -0
  80. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_mcp_slash_command.py +0 -0
  81. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_mention_completer.py +0 -0
  82. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_preflight.py +0 -0
  83. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_preflight_copilot.py +0 -0
  84. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_resume_selector.py +0 -0
  85. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_rewind_command_semantics.py +0 -0
  86. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_rewind_store.py +0 -0
  87. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_selection_menu.py +0 -0
  88. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_skills_slash_command.py +0 -0
  89. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_slash_argument_hint.py +0 -0
  90. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_slash_completer.py +0 -0
  91. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_slash_registry.py +0 -0
  92. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_tui_elapsed_status.py +0 -0
  93. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_tui_mcp_init_gate.py +0 -0
  94. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_tui_paste_placeholder.py +0 -0
  95. {comate_cli-0.1.11 → comate_cli-0.2.1}/tests/test_tui_split_invariance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comate-cli
3
- Version: 0.1.11
3
+ Version: 0.2.1
4
4
  Summary: Comate terminal CLI built on comate-agent-sdk
5
5
  Project-URL: Homepage, https://github.com/AndyLee1024/agent-sdk
6
6
  Project-URL: Repository, https://github.com/AndyLee1024/agent-sdk
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  import argparse
4
4
  import asyncio
5
5
  import sys
6
- from pathlib import Path
7
6
  from typing import Any
8
7
 
9
8
  from comate_agent_sdk.mcp import (
@@ -15,6 +14,7 @@ from comate_agent_sdk.mcp import (
15
14
  write_mcp_servers_to_path,
16
15
  )
17
16
  from comate_agent_sdk.mcp.types import McpServerConfig
17
+ from comate_agent_sdk.utils.paths import PathInput
18
18
 
19
19
 
20
20
  class McpCliError(ValueError):
@@ -23,8 +23,13 @@ class McpCliError(ValueError):
23
23
  self.exit_code = int(exit_code)
24
24
 
25
25
 
26
+ class _McpArgumentParser(argparse.ArgumentParser):
27
+ def error(self, message: str) -> None:
28
+ raise McpCliError(message)
29
+
30
+
26
31
  def _build_parser() -> argparse.ArgumentParser:
27
- parser = argparse.ArgumentParser(
32
+ parser = _McpArgumentParser(
28
33
  prog="comate mcp",
29
34
  description="Configure and manage MCP servers",
30
35
  formatter_class=argparse.RawDescriptionHelpFormatter,
@@ -143,11 +148,11 @@ def _parse_env_entries(entries: list[str]) -> dict[str, str]:
143
148
  return env
144
149
 
145
150
 
146
- def _clean_remainder_args(raw_args: list[str]) -> list[str]:
147
- args = list(raw_args)
148
- if args and args[0] == "--":
149
- return args[1:]
150
- return args
151
+ def _split_trailing_command_args(argv: list[str]) -> tuple[list[str], list[str]]:
152
+ if "--" not in argv:
153
+ return list(argv), []
154
+ separator_index = argv.index("--")
155
+ return list(argv[:separator_index]), list(argv[separator_index + 1 :])
151
156
 
152
157
 
153
158
  def _build_add_server_config(args: argparse.Namespace) -> McpServerConfig:
@@ -155,9 +160,7 @@ def _build_add_server_config(args: argparse.Namespace) -> McpServerConfig:
155
160
  header_entries = [str(item) for item in list(args.header or [])]
156
161
  env_entries = [str(item) for item in list(args.env or [])]
157
162
  command_or_url = str(args.command_or_url or "").strip()
158
- remainder = _clean_remainder_args(
159
- [str(item) for item in list(getattr(args, "extra_args", []) or [])]
160
- )
163
+ remainder = [str(item) for item in list(getattr(args, "extra_args", []) or [])]
161
164
 
162
165
  if transport == "http":
163
166
  if env_entries:
@@ -177,6 +180,9 @@ def _build_add_server_config(args: argparse.Namespace) -> McpServerConfig:
177
180
 
178
181
  if header_entries:
179
182
  raise McpCliError("--header is only supported for http transport")
183
+ stdio_args = list(remainder)
184
+ if not command_or_url and stdio_args:
185
+ command_or_url = stdio_args.pop(0)
180
186
  if not command_or_url:
181
187
  raise McpCliError("stdio transport requires command after <name>")
182
188
 
@@ -184,14 +190,14 @@ def _build_add_server_config(args: argparse.Namespace) -> McpServerConfig:
184
190
  cfg = {
185
191
  "command": command_or_url,
186
192
  }
187
- if remainder:
188
- cfg["args"] = remainder
193
+ if stdio_args:
194
+ cfg["args"] = stdio_args
189
195
  if env:
190
196
  cfg["env"] = env
191
197
  return cfg # type: ignore[return-value]
192
198
 
193
199
 
194
- def _load_servers_by_scope(*, scope: str, project_root: Path | None) -> dict[str, McpServerConfig]:
200
+ def _load_servers_by_scope(*, scope: str, project_root: PathInput | None) -> dict[str, McpServerConfig]:
195
201
  if scope == "effective":
196
202
  return load_effective_servers(project_root=project_root)
197
203
  return load_scope_servers(scope=scope, project_root=project_root) # type: ignore[arg-type]
@@ -200,7 +206,7 @@ def _load_servers_by_scope(*, scope: str, project_root: Path | None) -> dict[str
200
206
  def _read_effective_server_with_source(
201
207
  *,
202
208
  name: str,
203
- project_root: Path | None,
209
+ project_root: PathInput | None,
204
210
  ) -> tuple[McpServerConfig, str] | None:
205
211
  user_servers = load_scope_servers(scope="user", project_root=project_root)
206
212
  project_servers = load_scope_servers(scope="project", project_root=project_root)
@@ -230,7 +236,7 @@ def _format_server_endpoint(cfg: McpServerConfig) -> str:
230
236
  return " ".join([command, *str_args]).strip()
231
237
 
232
238
 
233
- def _cmd_add(args: argparse.Namespace, *, project_root: Path | None) -> None:
239
+ def _cmd_add(args: argparse.Namespace, *, project_root: PathInput | None) -> None:
234
240
  scope = str(args.scope)
235
241
  name = str(args.name).strip()
236
242
  if not name:
@@ -250,7 +256,7 @@ def _cmd_add(args: argparse.Namespace, *, project_root: Path | None) -> None:
250
256
  )
251
257
 
252
258
 
253
- def _cmd_remove(args: argparse.Namespace, *, project_root: Path | None) -> None:
259
+ def _cmd_remove(args: argparse.Namespace, *, project_root: PathInput | None) -> None:
254
260
  scope = str(args.scope)
255
261
  name = str(args.name).strip()
256
262
  if not name:
@@ -271,7 +277,7 @@ def _cmd_remove(args: argparse.Namespace, *, project_root: Path | None) -> None:
271
277
  )
272
278
 
273
279
 
274
- def _cmd_list(args: argparse.Namespace, *, project_root: Path | None) -> None:
280
+ def _cmd_list(args: argparse.Namespace, *, project_root: PathInput | None) -> None:
275
281
  scope = str(args.scope)
276
282
  servers = _load_servers_by_scope(scope=scope, project_root=project_root)
277
283
  if not servers:
@@ -295,7 +301,7 @@ def _cmd_list(args: argparse.Namespace, *, project_root: Path | None) -> None:
295
301
  sys.stdout.write(f"{alias}: {endpoint} ({server_type}) - {status}\n")
296
302
 
297
303
 
298
- def _cmd_get(args: argparse.Namespace, *, project_root: Path | None) -> None:
304
+ def _cmd_get(args: argparse.Namespace, *, project_root: PathInput | None) -> None:
299
305
  name = str(args.name).strip()
300
306
  scope = str(args.scope)
301
307
  if not name:
@@ -363,18 +369,19 @@ def _cmd_get(args: argparse.Namespace, *, project_root: Path | None) -> None:
363
369
  sys.stdout.write("\n".join(lines) + "\n")
364
370
 
365
371
 
366
- def run_mcp_command(argv: list[str], *, project_root: Path | None = None) -> None:
372
+ def run_mcp_command(argv: list[str], *, project_root: PathInput | None = None) -> None:
367
373
  parser = _build_parser()
368
- parsed, extra_args = parser.parse_known_args(argv)
374
+ parser_argv, trailing_args = _split_trailing_command_args(argv)
375
+ parsed = parser.parse_args(parser_argv)
369
376
  command = str(getattr(parsed, "command", "") or "").strip()
370
377
  if not command:
371
378
  parser.print_help(sys.stdout)
372
379
  return
373
- if command != "add" and extra_args:
374
- joined = " ".join(extra_args)
380
+ if command != "add" and trailing_args:
381
+ joined = " ".join(["--", *trailing_args])
375
382
  raise McpCliError(f"Unrecognized arguments: {joined}")
376
383
  if command == "add":
377
- setattr(parsed, "extra_args", list(extra_args))
384
+ setattr(parsed, "extra_args", list(trailing_args))
378
385
 
379
386
  handlers = {
380
387
  "add": _cmd_add,
@@ -11,26 +11,27 @@ from rich.text import Text
11
11
  from comate_agent_sdk.agent.events import StopEvent, TextEvent, ToolCallEvent, ToolResultEvent, UserQuestionEvent
12
12
 
13
13
  DEFAULT_STATUS_PHRASES: tuple[str, ...] = (
14
- "Vibing...",
15
- "Thinking...",
16
- "Reasoning...",
17
- "Planning next move...",
18
- "Reading context...",
19
- "Connecting dots...",
20
- "Synthesizing signal...",
21
- "Spotting edge cases...",
22
- "Checking assumptions...",
23
- "Tracing dependencies...",
24
- "Drafting response...",
25
- "Polishing details...",
26
- "Validating flow...",
27
- "Cross-checking facts...",
28
- "Refining intent...",
29
- "Mapping tools...",
30
- "Building confidence...",
31
- "Stitching answer...",
32
- "Finalizing output...",
33
- "Almost there...",
14
+ "Embellishing…",
15
+ "Vibing…",
16
+ "Thinking…",
17
+ "Reasoning…",
18
+ "Planning next move…",
19
+ "Reading context…",
20
+ "Connecting dots…",
21
+ "Synthesizing signal…",
22
+ "Spotting edge cases…",
23
+ "Checking assumptions…",
24
+ "Tracing dependencies…",
25
+ "Drafting response…",
26
+ "Polishing details…",
27
+ "Validating flow…",
28
+ "Cross-checking facts…",
29
+ "Refining intent…",
30
+ "Mapping tools…",
31
+ "Building confidence…",
32
+ "Stitching answer…",
33
+ "Finalizing output…",
34
+ "Almost there…",
34
35
  )
35
36
 
36
37
  BREATH_DOT_COLORS: tuple[str, ...] = (
@@ -69,8 +69,8 @@ async def _check_update() -> str | None:
69
69
  return None
70
70
 
71
71
  if _is_chinese_locale():
72
- url = f"https://mirrors.cloud.tencent.com/pypi/pypi/{package}/json"
73
- source_label = "tencent"
72
+ url = f"https://mirrors.tuna.tsinghua.edu.cn/pypi/{package}/json"
73
+ source_label = "tuna"
74
74
  else:
75
75
  url = f"https://pypi.org/pypi/{package}/json"
76
76
  source_label = "pypi"
@@ -90,9 +90,9 @@ async def _check_update() -> str | None:
90
90
  from packaging.version import Version
91
91
  if Version(latest) > Version(current):
92
92
  return (
93
- f"[dim]💡 New version available: [bold cyan]{latest}[/] "
93
+ f"[dim] New version available: [bold cyan]{latest}[/] "
94
94
  f"(current: {current}) "
95
- f"Run [bold]uv tool upgrade {package}[/] to upgrade.[/]"
95
+ f"Run [bold green]uv tool upgrade {package}[/] to upgrade.[/]"
96
96
  )
97
97
  except Exception:
98
98
  pass
@@ -230,16 +230,20 @@ def _format_resume_hint(session_id: str | None) -> str | None:
230
230
 
231
231
  async def _preload_mcp_in_tui(session: ChatSession) -> None:
232
232
  """在 TUI 内异步加载 MCP,初始化阶段不输出 scrollback 文案。"""
233
- if not bool(session._agent.options.mcp_enabled):
233
+ runtime = session.runtime
234
+ if not bool(runtime.options.mcp_enabled):
234
235
  return
235
236
 
236
237
  try:
237
- await session._agent.ensure_mcp_tools_loaded()
238
+ preload_task = runtime.start_mcp_preload()
239
+ if preload_task is None:
240
+ return
241
+ await preload_task
238
242
  except Exception as e:
239
243
  logger.warning(f"MCP init failed: {e}", exc_info=True)
240
244
  return
241
245
 
242
- mgr = session._agent._mcp_manager
246
+ mgr = runtime._mcp_manager
243
247
  if mgr is None:
244
248
  return
245
249
 
@@ -311,7 +315,7 @@ async def run(
311
315
 
312
316
  # 配置 TUI logging handler(将 SDK 日志输出到 TUI)
313
317
  from comate_cli.terminal_agent.logging_adapter import setup_tui_logging
314
- setup_tui_logging(renderer)
318
+ logging_session = setup_tui_logging(renderer)
315
319
 
316
320
  tui = TerminalAgentTUI(session, status_bar, renderer)
317
321
  tui.add_resume_history(mode)
@@ -333,6 +337,7 @@ async def run(
333
337
  exc_info=True,
334
338
  )
335
339
  finally:
340
+ logging_session.close()
336
341
  if active_session is session:
337
342
  await _graceful_shutdown(active_session)
338
343
  else:
@@ -10,6 +10,8 @@ from typing import Literal
10
10
 
11
11
  import yaml
12
12
 
13
+ from comate_agent_sdk.utils.paths import PathInput, normalize_path_input
14
+
13
15
  logger = logging.getLogger(__name__)
14
16
 
15
17
  DEFAULT_ITEM_MAX_BYTES = 32 * 1024
@@ -55,12 +57,15 @@ class CustomSlashLoadResult:
55
57
 
56
58
  def discover_custom_slash_commands(
57
59
  *,
58
- project_root: Path,
60
+ project_root: PathInput,
59
61
  builtin_names: set[str],
60
- user_root: Path | None = None,
62
+ user_root: PathInput | None = None,
61
63
  ) -> CustomSlashLoadResult:
62
- resolved_project_root = project_root.expanduser().resolve()
63
- resolved_user_root = (user_root or Path.home()).expanduser().resolve()
64
+ resolved_project_root = normalize_path_input(project_root, field_name="project_root")
65
+ resolved_user_root = normalize_path_input(
66
+ user_root if user_root is not None else Path.home(),
67
+ field_name="user_root",
68
+ )
64
69
 
65
70
  project_dir = resolved_project_root / ".agent" / "commands"
66
71
  user_dir = resolved_user_root / ".agent" / "commands"
@@ -188,11 +193,12 @@ async def render_custom_slash_prompt(
188
193
  *,
189
194
  command: CustomSlashCommand,
190
195
  raw_args: str,
191
- project_root: Path,
196
+ project_root: PathInput,
192
197
  item_max_bytes: int = DEFAULT_ITEM_MAX_BYTES,
193
198
  total_max_bytes: int = DEFAULT_TOTAL_MAX_BYTES,
194
199
  bash_timeout_seconds: float = DEFAULT_BASH_TIMEOUT_SECONDS,
195
200
  ) -> str:
201
+ resolved_project_root = normalize_path_input(project_root, field_name="project_root")
196
202
  normalized_args = raw_args.strip()
197
203
  try:
198
204
  arg_tokens = shlex.split(normalized_args) if normalized_args else []
@@ -213,7 +219,7 @@ async def render_custom_slash_prompt(
213
219
  after_bash = await _replace_bash_markers(
214
220
  text=replaced_text,
215
221
  marker_map=marker_map,
216
- project_root=project_root,
222
+ project_root=resolved_project_root,
217
223
  item_max_bytes=item_max_bytes,
218
224
  total_max_bytes=total_max_bytes,
219
225
  total_inserted_bytes=total_inserted_bytes,
@@ -224,7 +230,7 @@ async def render_custom_slash_prompt(
224
230
  after_file_ref = _replace_file_reference_markers(
225
231
  text=after_bash,
226
232
  marker_map=marker_map,
227
- project_root=project_root,
233
+ project_root=resolved_project_root,
228
234
  item_max_bytes=item_max_bytes,
229
235
  total_max_bytes=total_max_bytes,
230
236
  total_inserted_bytes=total_inserted_bytes,