soothe-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. soothe_cli/__init__.py +5 -0
  2. soothe_cli/cli/__init__.py +1 -0
  3. soothe_cli/cli/commands/__init__.py +1 -0
  4. soothe_cli/cli/commands/autopilot_cmd.py +410 -0
  5. soothe_cli/cli/commands/config_cmd.py +277 -0
  6. soothe_cli/cli/commands/run_cmd.py +87 -0
  7. soothe_cli/cli/commands/status_cmd.py +121 -0
  8. soothe_cli/cli/commands/subagent_names.py +17 -0
  9. soothe_cli/cli/commands/thread_cmd.py +657 -0
  10. soothe_cli/cli/execution/__init__.py +6 -0
  11. soothe_cli/cli/execution/daemon.py +194 -0
  12. soothe_cli/cli/execution/headless.py +99 -0
  13. soothe_cli/cli/execution/launcher.py +31 -0
  14. soothe_cli/cli/main.py +509 -0
  15. soothe_cli/cli/renderer.py +444 -0
  16. soothe_cli/cli/stream/__init__.py +17 -0
  17. soothe_cli/cli/stream/context.py +138 -0
  18. soothe_cli/cli/stream/display_line.py +83 -0
  19. soothe_cli/cli/stream/formatter.py +412 -0
  20. soothe_cli/cli/stream/pipeline.py +521 -0
  21. soothe_cli/cli/utils.py +46 -0
  22. soothe_cli/config/__init__.py +5 -0
  23. soothe_cli/config/cli_config.py +155 -0
  24. soothe_cli/plan/__init__.py +5 -0
  25. soothe_cli/plan/rich_tree.py +54 -0
  26. soothe_cli/shared/__init__.py +107 -0
  27. soothe_cli/shared/command_router.py +246 -0
  28. soothe_cli/shared/config_loader.py +68 -0
  29. soothe_cli/shared/display_policy.py +413 -0
  30. soothe_cli/shared/essential_events.py +68 -0
  31. soothe_cli/shared/event_processor.py +823 -0
  32. soothe_cli/shared/message_processing.py +393 -0
  33. soothe_cli/shared/presentation_engine.py +173 -0
  34. soothe_cli/shared/processor_state.py +80 -0
  35. soothe_cli/shared/renderer_protocol.py +158 -0
  36. soothe_cli/shared/rendering.py +43 -0
  37. soothe_cli/shared/slash_commands.py +354 -0
  38. soothe_cli/shared/subagent_routing.py +63 -0
  39. soothe_cli/shared/suppression_state.py +188 -0
  40. soothe_cli/shared/tool_formatters/__init__.py +27 -0
  41. soothe_cli/shared/tool_formatters/base.py +109 -0
  42. soothe_cli/shared/tool_formatters/execution.py +297 -0
  43. soothe_cli/shared/tool_formatters/fallback.py +128 -0
  44. soothe_cli/shared/tool_formatters/file_ops.py +299 -0
  45. soothe_cli/shared/tool_formatters/goal_formatter.py +331 -0
  46. soothe_cli/shared/tool_formatters/media.py +291 -0
  47. soothe_cli/shared/tool_formatters/structured.py +202 -0
  48. soothe_cli/shared/tool_formatters/web.py +143 -0
  49. soothe_cli/shared/tool_output_formatter.py +227 -0
  50. soothe_cli/shared/tui_trace_log.py +40 -0
  51. soothe_cli/tui/__init__.py +5 -0
  52. soothe_cli/tui/_ask_user_types.py +50 -0
  53. soothe_cli/tui/_cli_context.py +27 -0
  54. soothe_cli/tui/_env_vars.py +56 -0
  55. soothe_cli/tui/_session_stats.py +114 -0
  56. soothe_cli/tui/_version.py +21 -0
  57. soothe_cli/tui/app.py +4992 -0
  58. soothe_cli/tui/app.tcss +302 -0
  59. soothe_cli/tui/command_registry.py +310 -0
  60. soothe_cli/tui/config.py +2381 -0
  61. soothe_cli/tui/daemon_session.py +233 -0
  62. soothe_cli/tui/file_ops.py +409 -0
  63. soothe_cli/tui/formatting.py +28 -0
  64. soothe_cli/tui/hooks.py +23 -0
  65. soothe_cli/tui/input.py +782 -0
  66. soothe_cli/tui/media_utils.py +471 -0
  67. soothe_cli/tui/model_config.py +518 -0
  68. soothe_cli/tui/output.py +69 -0
  69. soothe_cli/tui/project_utils.py +188 -0
  70. soothe_cli/tui/sessions.py +1248 -0
  71. soothe_cli/tui/skills/__init__.py +5 -0
  72. soothe_cli/tui/skills/invocation.py +74 -0
  73. soothe_cli/tui/skills/load.py +93 -0
  74. soothe_cli/tui/textual_adapter.py +1430 -0
  75. soothe_cli/tui/theme.py +838 -0
  76. soothe_cli/tui/tool_display.py +297 -0
  77. soothe_cli/tui/unicode_security.py +502 -0
  78. soothe_cli/tui/update_check.py +447 -0
  79. soothe_cli/tui/widgets/__init__.py +9 -0
  80. soothe_cli/tui/widgets/_links.py +63 -0
  81. soothe_cli/tui/widgets/approval.py +430 -0
  82. soothe_cli/tui/widgets/ask_user.py +392 -0
  83. soothe_cli/tui/widgets/autocomplete.py +666 -0
  84. soothe_cli/tui/widgets/autopilot_dashboard.py +308 -0
  85. soothe_cli/tui/widgets/autopilot_screen.py +64 -0
  86. soothe_cli/tui/widgets/chat_input.py +1834 -0
  87. soothe_cli/tui/widgets/clipboard.py +128 -0
  88. soothe_cli/tui/widgets/diff.py +240 -0
  89. soothe_cli/tui/widgets/editor.py +140 -0
  90. soothe_cli/tui/widgets/history.py +221 -0
  91. soothe_cli/tui/widgets/loading.py +194 -0
  92. soothe_cli/tui/widgets/mcp_viewer.py +352 -0
  93. soothe_cli/tui/widgets/message_store.py +693 -0
  94. soothe_cli/tui/widgets/messages.py +1720 -0
  95. soothe_cli/tui/widgets/model_selector.py +988 -0
  96. soothe_cli/tui/widgets/notification_settings.py +155 -0
  97. soothe_cli/tui/widgets/status.py +403 -0
  98. soothe_cli/tui/widgets/theme_selector.py +158 -0
  99. soothe_cli/tui/widgets/thread_selector.py +1865 -0
  100. soothe_cli/tui/widgets/tool_renderers.py +148 -0
  101. soothe_cli/tui/widgets/tool_widgets.py +254 -0
  102. soothe_cli/tui/widgets/tools.py +165 -0
  103. soothe_cli/tui/widgets/welcome.py +330 -0
  104. soothe_cli-0.1.0.dist-info/METADATA +100 -0
  105. soothe_cli-0.1.0.dist-info/RECORD +107 -0
  106. soothe_cli-0.1.0.dist-info/WHEEL +4 -0
  107. soothe_cli-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,5 @@
1
+ """Skills discovery and invocation for TUI (stub from deepagents-cli migration).
2
+
3
+ This module provides skill discovery and invocation functionality for the TUI.
4
+ Full implementation should integrate with Soothe skills system.
5
+ """
@@ -0,0 +1,74 @@
1
+ """Skills discovery and invocation helpers for the Soothe Textual TUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ from collections import OrderedDict
8
+ from typing import TYPE_CHECKING
9
+
10
+ from soothe_sdk.client import fetch_skills_catalog, websocket_url_from_config
11
+
12
+ from soothe_cli.tui.skills.load import ExtendedSkillMetadata
13
+
14
+ if TYPE_CHECKING:
15
+ from soothe_cli.config.cli_config import CLIConfig
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ async def discover_skills_async(
21
+ daemon_config: CLIConfig | None = None,
22
+ ) -> list[ExtendedSkillMetadata]:
23
+ """Discover skills from daemon RPC (IG-174 Phase 2).
24
+
25
+ Fetches wire-safe skill metadata from daemon via WebSocket RPC.
26
+ Daemon handles all skill discovery (built-in, user, project, etc.)
27
+ and returns wire-safe metadata. No local filesystem access.
28
+
29
+ Args:
30
+ daemon_config: Daemon config for WebSocket URL construction.
31
+
32
+ Returns:
33
+ List of skill metadata dicts sorted by ascending precedence
34
+ (built-in first, winning entry last). Empty list if daemon
35
+ unavailable.
36
+ """
37
+ from soothe_sdk.client import WebSocketClient
38
+
39
+ if daemon_config is None:
40
+ logger.warning("No daemon_config provided for skills discovery; returning empty catalog")
41
+ return []
42
+
43
+ ws_url = websocket_url_from_config(daemon_config)
44
+ client = WebSocketClient(url=ws_url)
45
+
46
+ by_name: OrderedDict[str, ExtendedSkillMetadata] = OrderedDict()
47
+
48
+ try:
49
+ await client.connect()
50
+ skills_wire = await fetch_skills_catalog(client, timeout=15.0)
51
+ await client.close()
52
+
53
+ # Build by_name mapping from wire-safe metadata
54
+ for skill_meta in skills_wire:
55
+ name = skill_meta.get("name")
56
+ if name:
57
+ by_name[name] = skill_meta
58
+ except Exception as e:
59
+ logger.warning(f"Failed to fetch skills from daemon: {e}")
60
+ return []
61
+
62
+ return list(by_name.values())
63
+
64
+
65
+ def discover_skills(
66
+ daemon_config: CLIConfig | None = None,
67
+ ) -> list[ExtendedSkillMetadata]:
68
+ """Backward-compatible sync wrapper for async skills discovery."""
69
+ try:
70
+ asyncio.get_running_loop()
71
+ except RuntimeError:
72
+ return asyncio.run(discover_skills_async(daemon_config=daemon_config))
73
+ msg = "discover_skills() cannot be called from a running event loop; use discover_skills_async() instead."
74
+ raise RuntimeError(msg)
@@ -0,0 +1,93 @@
1
+ """Skills loading utilities for the Soothe Textual TUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from collections.abc import Sequence
7
+ from pathlib import Path
8
+ from typing import TypedDict
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class ExtendedSkillMetadata(TypedDict):
14
+ """Wire-safe skill metadata from daemon RPC (IG-174 Phase 2).
15
+
16
+ This TypedDict represents skill metadata fetched from daemon via WebSocket RPC.
17
+ All fields are wire-safe (no Path objects, use str representations).
18
+ """
19
+
20
+ name: str
21
+ description: str
22
+ source: str # "builtin", "user", "project", "agents", "claude"
23
+ path: str | None # Wire-safe path representation (not Path object)
24
+ tags: list[str]
25
+ tools: list[str] | None
26
+ default_model: str | None
27
+ requires: list[str] | None
28
+
29
+
30
+ def _is_under_allowed_roots(target: Path, roots: Sequence[Path]) -> bool:
31
+ """Return True if `target` is equal to or nested under one of `roots`."""
32
+ t = target.resolve()
33
+ for root in roots:
34
+ try:
35
+ t.relative_to(root.resolve())
36
+ except ValueError:
37
+ continue
38
+ else:
39
+ return True
40
+ return False
41
+
42
+
43
+ def load_skill_content(
44
+ skill_path: str | Path, *, allowed_roots: Sequence[Path] | None = None
45
+ ) -> str | None:
46
+ """Read `SKILL.md` for a skill directory with optional path containment checks.
47
+
48
+ Args:
49
+ skill_path: Path to the skill directory **or** to a `SKILL.md` file.
50
+ allowed_roots: Resolved directories that may contain the target file.
51
+ When empty or ``None``, any resolved path is accepted (tests only —
52
+ production callers should pass roots from daemon RPC).
53
+
54
+ Returns:
55
+ File contents as a string, or ``None`` only when the file is missing.
56
+
57
+ Raises:
58
+ PermissionError: When ``allowed_roots`` is non-empty and the resolved
59
+ `SKILL.md` path lies outside every allowed root.
60
+ OSError: Propagated from the filesystem when the file cannot be read.
61
+ """
62
+ raw = Path(skill_path)
63
+ skill_md = raw / "SKILL.md" if raw.is_dir() else raw
64
+ resolved_md = skill_md.resolve()
65
+
66
+ if allowed_roots:
67
+ roots = [Path(r).resolve() for r in allowed_roots]
68
+ if roots and not _is_under_allowed_roots(resolved_md, roots):
69
+ msg = f"Refusing to read skill file outside allowed directories: {resolved_md}"
70
+ raise PermissionError(msg)
71
+
72
+ if not resolved_md.is_file():
73
+ return None
74
+
75
+ return resolved_md.read_text(encoding="utf-8")
76
+
77
+
78
+ def strip_skill_frontmatter(content: str) -> str:
79
+ """Remove optional YAML frontmatter from SKILL markdown content."""
80
+ if not content.startswith("---\n"):
81
+ return content
82
+ end = content.find("\n---\n", 4)
83
+ if end == -1:
84
+ return content
85
+ return content[end + 5 :]
86
+
87
+
88
+ # Re-export for callers that import from this module
89
+ __all__ = [
90
+ "ExtendedSkillMetadata",
91
+ "load_skill_content",
92
+ "strip_skill_frontmatter",
93
+ ]