raise-cli 2.2.1__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 (264) hide show
  1. raise_cli/__init__.py +38 -0
  2. raise_cli/__main__.py +30 -0
  3. raise_cli/adapters/__init__.py +91 -0
  4. raise_cli/adapters/declarative/__init__.py +26 -0
  5. raise_cli/adapters/declarative/adapter.py +267 -0
  6. raise_cli/adapters/declarative/discovery.py +94 -0
  7. raise_cli/adapters/declarative/expressions.py +150 -0
  8. raise_cli/adapters/declarative/reference/__init__.py +1 -0
  9. raise_cli/adapters/declarative/reference/github.yaml +143 -0
  10. raise_cli/adapters/declarative/schema.py +98 -0
  11. raise_cli/adapters/filesystem.py +299 -0
  12. raise_cli/adapters/mcp_bridge.py +10 -0
  13. raise_cli/adapters/mcp_confluence.py +246 -0
  14. raise_cli/adapters/mcp_jira.py +405 -0
  15. raise_cli/adapters/models.py +205 -0
  16. raise_cli/adapters/protocols.py +180 -0
  17. raise_cli/adapters/registry.py +90 -0
  18. raise_cli/adapters/sync.py +149 -0
  19. raise_cli/agents/__init__.py +14 -0
  20. raise_cli/agents/antigravity.yaml +8 -0
  21. raise_cli/agents/claude.yaml +8 -0
  22. raise_cli/agents/copilot.yaml +8 -0
  23. raise_cli/agents/copilot_plugin.py +124 -0
  24. raise_cli/agents/cursor.yaml +7 -0
  25. raise_cli/agents/roo.yaml +8 -0
  26. raise_cli/agents/windsurf.yaml +8 -0
  27. raise_cli/artifacts/__init__.py +30 -0
  28. raise_cli/artifacts/models.py +43 -0
  29. raise_cli/artifacts/reader.py +55 -0
  30. raise_cli/artifacts/renderer.py +104 -0
  31. raise_cli/artifacts/story_design.py +69 -0
  32. raise_cli/artifacts/writer.py +45 -0
  33. raise_cli/backlog/__init__.py +1 -0
  34. raise_cli/backlog/sync.py +115 -0
  35. raise_cli/cli/__init__.py +3 -0
  36. raise_cli/cli/commands/__init__.py +3 -0
  37. raise_cli/cli/commands/_resolve.py +153 -0
  38. raise_cli/cli/commands/adapters.py +362 -0
  39. raise_cli/cli/commands/artifact.py +137 -0
  40. raise_cli/cli/commands/backlog.py +333 -0
  41. raise_cli/cli/commands/base.py +31 -0
  42. raise_cli/cli/commands/discover.py +551 -0
  43. raise_cli/cli/commands/docs.py +130 -0
  44. raise_cli/cli/commands/doctor.py +177 -0
  45. raise_cli/cli/commands/gate.py +223 -0
  46. raise_cli/cli/commands/graph.py +1086 -0
  47. raise_cli/cli/commands/info.py +81 -0
  48. raise_cli/cli/commands/init.py +746 -0
  49. raise_cli/cli/commands/journal.py +167 -0
  50. raise_cli/cli/commands/mcp.py +524 -0
  51. raise_cli/cli/commands/memory.py +467 -0
  52. raise_cli/cli/commands/pattern.py +348 -0
  53. raise_cli/cli/commands/profile.py +59 -0
  54. raise_cli/cli/commands/publish.py +80 -0
  55. raise_cli/cli/commands/release.py +338 -0
  56. raise_cli/cli/commands/session.py +528 -0
  57. raise_cli/cli/commands/signal.py +410 -0
  58. raise_cli/cli/commands/skill.py +350 -0
  59. raise_cli/cli/commands/skill_set.py +145 -0
  60. raise_cli/cli/error_handler.py +158 -0
  61. raise_cli/cli/main.py +163 -0
  62. raise_cli/compat.py +66 -0
  63. raise_cli/config/__init__.py +41 -0
  64. raise_cli/config/agent_plugin.py +105 -0
  65. raise_cli/config/agent_registry.py +233 -0
  66. raise_cli/config/agents.py +120 -0
  67. raise_cli/config/ide.py +32 -0
  68. raise_cli/config/paths.py +379 -0
  69. raise_cli/config/settings.py +180 -0
  70. raise_cli/context/__init__.py +42 -0
  71. raise_cli/context/analyzers/__init__.py +16 -0
  72. raise_cli/context/analyzers/models.py +36 -0
  73. raise_cli/context/analyzers/protocol.py +43 -0
  74. raise_cli/context/analyzers/python.py +292 -0
  75. raise_cli/context/builder.py +1569 -0
  76. raise_cli/context/diff.py +213 -0
  77. raise_cli/context/extractors/__init__.py +13 -0
  78. raise_cli/context/extractors/skills.py +121 -0
  79. raise_cli/core/__init__.py +37 -0
  80. raise_cli/core/files.py +66 -0
  81. raise_cli/core/text.py +174 -0
  82. raise_cli/core/tools.py +441 -0
  83. raise_cli/discovery/__init__.py +50 -0
  84. raise_cli/discovery/analyzer.py +691 -0
  85. raise_cli/discovery/drift.py +355 -0
  86. raise_cli/discovery/scanner.py +1687 -0
  87. raise_cli/doctor/__init__.py +4 -0
  88. raise_cli/doctor/checks/__init__.py +1 -0
  89. raise_cli/doctor/checks/environment.py +110 -0
  90. raise_cli/doctor/checks/project.py +238 -0
  91. raise_cli/doctor/fix.py +80 -0
  92. raise_cli/doctor/models.py +56 -0
  93. raise_cli/doctor/protocol.py +43 -0
  94. raise_cli/doctor/registry.py +100 -0
  95. raise_cli/doctor/report.py +141 -0
  96. raise_cli/doctor/runner.py +95 -0
  97. raise_cli/engines/__init__.py +3 -0
  98. raise_cli/exceptions.py +215 -0
  99. raise_cli/gates/__init__.py +19 -0
  100. raise_cli/gates/builtin/__init__.py +1 -0
  101. raise_cli/gates/builtin/coverage.py +52 -0
  102. raise_cli/gates/builtin/lint.py +48 -0
  103. raise_cli/gates/builtin/tests.py +48 -0
  104. raise_cli/gates/builtin/types.py +48 -0
  105. raise_cli/gates/models.py +40 -0
  106. raise_cli/gates/protocol.py +41 -0
  107. raise_cli/gates/registry.py +141 -0
  108. raise_cli/governance/__init__.py +11 -0
  109. raise_cli/governance/extractor.py +412 -0
  110. raise_cli/governance/models.py +134 -0
  111. raise_cli/governance/parsers/__init__.py +35 -0
  112. raise_cli/governance/parsers/_convert.py +38 -0
  113. raise_cli/governance/parsers/adr.py +274 -0
  114. raise_cli/governance/parsers/backlog.py +356 -0
  115. raise_cli/governance/parsers/constitution.py +119 -0
  116. raise_cli/governance/parsers/epic.py +323 -0
  117. raise_cli/governance/parsers/glossary.py +316 -0
  118. raise_cli/governance/parsers/guardrails.py +345 -0
  119. raise_cli/governance/parsers/prd.py +112 -0
  120. raise_cli/governance/parsers/roadmap.py +118 -0
  121. raise_cli/governance/parsers/vision.py +116 -0
  122. raise_cli/graph/__init__.py +1 -0
  123. raise_cli/graph/backends/__init__.py +57 -0
  124. raise_cli/graph/backends/api.py +137 -0
  125. raise_cli/graph/backends/dual.py +139 -0
  126. raise_cli/graph/backends/pending.py +84 -0
  127. raise_cli/handlers/__init__.py +3 -0
  128. raise_cli/hooks/__init__.py +54 -0
  129. raise_cli/hooks/builtin/__init__.py +1 -0
  130. raise_cli/hooks/builtin/backlog.py +216 -0
  131. raise_cli/hooks/builtin/gate_bridge.py +83 -0
  132. raise_cli/hooks/builtin/jira_sync.py +127 -0
  133. raise_cli/hooks/builtin/memory.py +117 -0
  134. raise_cli/hooks/builtin/telemetry.py +72 -0
  135. raise_cli/hooks/emitter.py +184 -0
  136. raise_cli/hooks/events.py +262 -0
  137. raise_cli/hooks/protocol.py +38 -0
  138. raise_cli/hooks/registry.py +117 -0
  139. raise_cli/mcp/__init__.py +33 -0
  140. raise_cli/mcp/bridge.py +218 -0
  141. raise_cli/mcp/models.py +43 -0
  142. raise_cli/mcp/registry.py +77 -0
  143. raise_cli/mcp/schema.py +41 -0
  144. raise_cli/memory/__init__.py +58 -0
  145. raise_cli/memory/loader.py +247 -0
  146. raise_cli/memory/migration.py +241 -0
  147. raise_cli/memory/models.py +169 -0
  148. raise_cli/memory/writer.py +598 -0
  149. raise_cli/onboarding/__init__.py +103 -0
  150. raise_cli/onboarding/bootstrap.py +324 -0
  151. raise_cli/onboarding/claudemd.py +17 -0
  152. raise_cli/onboarding/conventions.py +742 -0
  153. raise_cli/onboarding/detection.py +374 -0
  154. raise_cli/onboarding/governance.py +443 -0
  155. raise_cli/onboarding/instructions.py +672 -0
  156. raise_cli/onboarding/manifest.py +201 -0
  157. raise_cli/onboarding/memory_md.py +399 -0
  158. raise_cli/onboarding/migration.py +207 -0
  159. raise_cli/onboarding/profile.py +624 -0
  160. raise_cli/onboarding/skill_conflict.py +100 -0
  161. raise_cli/onboarding/skill_manifest.py +176 -0
  162. raise_cli/onboarding/skills.py +437 -0
  163. raise_cli/onboarding/workflows.py +101 -0
  164. raise_cli/output/__init__.py +28 -0
  165. raise_cli/output/console.py +394 -0
  166. raise_cli/output/formatters/__init__.py +9 -0
  167. raise_cli/output/formatters/adapters.py +135 -0
  168. raise_cli/output/formatters/discover.py +439 -0
  169. raise_cli/output/formatters/skill.py +298 -0
  170. raise_cli/publish/__init__.py +3 -0
  171. raise_cli/publish/changelog.py +80 -0
  172. raise_cli/publish/check.py +179 -0
  173. raise_cli/publish/version.py +172 -0
  174. raise_cli/rai_base/__init__.py +22 -0
  175. raise_cli/rai_base/framework/__init__.py +7 -0
  176. raise_cli/rai_base/framework/methodology.yaml +233 -0
  177. raise_cli/rai_base/governance/__init__.py +1 -0
  178. raise_cli/rai_base/governance/architecture/__init__.py +1 -0
  179. raise_cli/rai_base/governance/architecture/domain-model.md +20 -0
  180. raise_cli/rai_base/governance/architecture/system-context.md +34 -0
  181. raise_cli/rai_base/governance/architecture/system-design.md +24 -0
  182. raise_cli/rai_base/governance/backlog.md +8 -0
  183. raise_cli/rai_base/governance/guardrails.md +17 -0
  184. raise_cli/rai_base/governance/prd.md +25 -0
  185. raise_cli/rai_base/governance/vision.md +16 -0
  186. raise_cli/rai_base/identity/__init__.py +8 -0
  187. raise_cli/rai_base/identity/core.md +119 -0
  188. raise_cli/rai_base/identity/perspective.md +119 -0
  189. raise_cli/rai_base/memory/__init__.py +7 -0
  190. raise_cli/rai_base/memory/patterns-base.jsonl +55 -0
  191. raise_cli/schemas/__init__.py +3 -0
  192. raise_cli/schemas/journal.py +49 -0
  193. raise_cli/schemas/session_state.py +117 -0
  194. raise_cli/session/__init__.py +5 -0
  195. raise_cli/session/bundle.py +820 -0
  196. raise_cli/session/close.py +268 -0
  197. raise_cli/session/journal.py +119 -0
  198. raise_cli/session/resolver.py +126 -0
  199. raise_cli/session/state.py +187 -0
  200. raise_cli/skills/__init__.py +44 -0
  201. raise_cli/skills/locator.py +141 -0
  202. raise_cli/skills/name_checker.py +199 -0
  203. raise_cli/skills/parser.py +145 -0
  204. raise_cli/skills/scaffold.py +212 -0
  205. raise_cli/skills/schema.py +132 -0
  206. raise_cli/skills/skillsets.py +195 -0
  207. raise_cli/skills/validator.py +197 -0
  208. raise_cli/skills_base/__init__.py +80 -0
  209. raise_cli/skills_base/contract-template.md +60 -0
  210. raise_cli/skills_base/preamble.md +37 -0
  211. raise_cli/skills_base/rai-architecture-review/SKILL.md +137 -0
  212. raise_cli/skills_base/rai-debug/SKILL.md +171 -0
  213. raise_cli/skills_base/rai-discover/SKILL.md +167 -0
  214. raise_cli/skills_base/rai-discover-document/SKILL.md +128 -0
  215. raise_cli/skills_base/rai-discover-scan/SKILL.md +147 -0
  216. raise_cli/skills_base/rai-discover-start/SKILL.md +145 -0
  217. raise_cli/skills_base/rai-discover-validate/SKILL.md +142 -0
  218. raise_cli/skills_base/rai-docs-update/SKILL.md +142 -0
  219. raise_cli/skills_base/rai-doctor/SKILL.md +120 -0
  220. raise_cli/skills_base/rai-epic-close/SKILL.md +165 -0
  221. raise_cli/skills_base/rai-epic-close/templates/retrospective.md +68 -0
  222. raise_cli/skills_base/rai-epic-design/SKILL.md +146 -0
  223. raise_cli/skills_base/rai-epic-design/templates/design.md +24 -0
  224. raise_cli/skills_base/rai-epic-design/templates/scope.md +76 -0
  225. raise_cli/skills_base/rai-epic-plan/SKILL.md +153 -0
  226. raise_cli/skills_base/rai-epic-plan/_references/sequencing-strategies.md +67 -0
  227. raise_cli/skills_base/rai-epic-plan/templates/plan-section.md +49 -0
  228. raise_cli/skills_base/rai-epic-run/SKILL.md +208 -0
  229. raise_cli/skills_base/rai-epic-start/SKILL.md +136 -0
  230. raise_cli/skills_base/rai-epic-start/templates/brief.md +34 -0
  231. raise_cli/skills_base/rai-mcp-add/SKILL.md +176 -0
  232. raise_cli/skills_base/rai-mcp-remove/SKILL.md +120 -0
  233. raise_cli/skills_base/rai-mcp-status/SKILL.md +147 -0
  234. raise_cli/skills_base/rai-problem-shape/SKILL.md +138 -0
  235. raise_cli/skills_base/rai-project-create/SKILL.md +144 -0
  236. raise_cli/skills_base/rai-project-onboard/SKILL.md +162 -0
  237. raise_cli/skills_base/rai-quality-review/SKILL.md +189 -0
  238. raise_cli/skills_base/rai-research/SKILL.md +143 -0
  239. raise_cli/skills_base/rai-research/references/research-prompt-template.md +317 -0
  240. raise_cli/skills_base/rai-session-close/SKILL.md +176 -0
  241. raise_cli/skills_base/rai-session-start/SKILL.md +110 -0
  242. raise_cli/skills_base/rai-story-close/SKILL.md +198 -0
  243. raise_cli/skills_base/rai-story-design/SKILL.md +203 -0
  244. raise_cli/skills_base/rai-story-design/references/tech-design-story-v2.md +293 -0
  245. raise_cli/skills_base/rai-story-implement/SKILL.md +115 -0
  246. raise_cli/skills_base/rai-story-plan/SKILL.md +135 -0
  247. raise_cli/skills_base/rai-story-review/SKILL.md +178 -0
  248. raise_cli/skills_base/rai-story-run/SKILL.md +282 -0
  249. raise_cli/skills_base/rai-story-start/SKILL.md +166 -0
  250. raise_cli/skills_base/rai-story-start/templates/story.md +38 -0
  251. raise_cli/skills_base/rai-welcome/SKILL.md +134 -0
  252. raise_cli/telemetry/__init__.py +42 -0
  253. raise_cli/telemetry/schemas.py +285 -0
  254. raise_cli/telemetry/writer.py +217 -0
  255. raise_cli/tier/__init__.py +0 -0
  256. raise_cli/tier/context.py +134 -0
  257. raise_cli/viz/__init__.py +7 -0
  258. raise_cli/viz/generator.py +406 -0
  259. raise_cli-2.2.1.dist-info/METADATA +433 -0
  260. raise_cli-2.2.1.dist-info/RECORD +264 -0
  261. raise_cli-2.2.1.dist-info/WHEEL +4 -0
  262. raise_cli-2.2.1.dist-info/entry_points.txt +40 -0
  263. raise_cli-2.2.1.dist-info/licenses/LICENSE +190 -0
  264. raise_cli-2.2.1.dist-info/licenses/NOTICE +4 -0
@@ -0,0 +1,394 @@
1
+ """Output console with format-aware printing.
2
+
3
+ This module provides a unified output interface that respects the --format flag,
4
+ supporting human-readable (Rich), JSON, and table formats.
5
+
6
+ Example:
7
+ >>> from raise_cli.output import get_console
8
+ >>>
9
+ >>> console = get_console()
10
+ >>> console.print_message("Processing...")
11
+ >>> console.print_success("Done!", details={"duration": "2.3s"})
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ from typing import TYPE_CHECKING, Any, Literal, cast
18
+
19
+ from rich.console import Console
20
+ from rich.table import Table
21
+ from rich.tree import Tree
22
+
23
+ if TYPE_CHECKING:
24
+ from collections.abc import Sequence
25
+
26
+ # Output format type
27
+ OutputFormat = Literal["human", "json", "table"]
28
+
29
+
30
+ class OutputConsole:
31
+ """Output abstraction that respects --format flag.
32
+
33
+ Provides consistent output formatting across human-readable, JSON,
34
+ and table formats for CLI commands.
35
+
36
+ Args:
37
+ format: Output format ("human", "json", or "table").
38
+ verbosity: Verbosity level (-1=quiet, 0=normal, 1-3=verbose).
39
+ color: Whether to use colors in human output.
40
+
41
+ Example:
42
+ >>> console = OutputConsole(format="human")
43
+ >>> console.print_message("Hello")
44
+ Hello
45
+ >>> console = OutputConsole(format="json")
46
+ >>> console.print_message("Hello")
47
+ {"message": "Hello"}
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ format: OutputFormat = "human",
53
+ verbosity: int = 0,
54
+ color: bool = True,
55
+ ) -> None:
56
+ """Initialize OutputConsole.
57
+
58
+ Args:
59
+ format: Output format ("human", "json", or "table").
60
+ verbosity: Verbosity level (-1=quiet, 0=normal, 1-3=verbose).
61
+ color: Whether to use colors in human output.
62
+ """
63
+ self.format = format
64
+ self.verbosity = verbosity
65
+ self.color = color
66
+ self._console = Console(force_terminal=color, no_color=not color)
67
+
68
+ def _is_quiet(self) -> bool:
69
+ """Check if output should be suppressed (quiet mode)."""
70
+ return self.verbosity < 0
71
+
72
+ def print_message(self, message: str, *, style: str | None = None) -> None:
73
+ """Print a simple message.
74
+
75
+ Args:
76
+ message: The message to print.
77
+ style: Optional Rich style for human format (e.g., "bold", "dim").
78
+
79
+ Example:
80
+ >>> console.print_message("Processing kata...")
81
+ Processing kata...
82
+ """
83
+ if self._is_quiet():
84
+ return
85
+
86
+ if self.format == "json":
87
+ self._print_json({"message": message})
88
+ else:
89
+ # Both human and table use plain text for messages
90
+ if style and self.color:
91
+ self._console.print(f"[{style}]{message}[/]")
92
+ else:
93
+ self._console.print(message)
94
+
95
+ def print_success(
96
+ self, message: str, *, details: dict[str, Any] | None = None
97
+ ) -> None:
98
+ """Print a success message with optional details.
99
+
100
+ Args:
101
+ message: The success message.
102
+ details: Optional key-value details to display.
103
+
104
+ Example:
105
+ >>> console.print_success("Kata completed", details={"duration": "2.3s"})
106
+ ✓ Kata completed (duration: 2.3s)
107
+ """
108
+ if self._is_quiet():
109
+ return
110
+
111
+ if self.format == "json":
112
+ output: dict[str, Any] = {"status": "success", "message": message}
113
+ if details:
114
+ output["details"] = details
115
+ self._print_json(output)
116
+ else:
117
+ # Human and table format
118
+ detail_str = ""
119
+ if details:
120
+ detail_str = (
121
+ " (" + ", ".join(f"{k}: {v}" for k, v in details.items()) + ")"
122
+ )
123
+ if self.color:
124
+ self._console.print(f"[green]✓[/] {message}{detail_str}")
125
+ else:
126
+ self._console.print(f"✓ {message}{detail_str}")
127
+
128
+ def print_warning(
129
+ self, message: str, *, details: dict[str, Any] | None = None
130
+ ) -> None:
131
+ """Print a warning message.
132
+
133
+ Args:
134
+ message: The warning message.
135
+ details: Optional key-value details to display.
136
+
137
+ Example:
138
+ >>> console.print_warning("Config not found, using defaults")
139
+ ⚠ Config not found, using defaults
140
+ """
141
+ if self._is_quiet():
142
+ return
143
+
144
+ if self.format == "json":
145
+ output: dict[str, Any] = {"status": "warning", "message": message}
146
+ if details:
147
+ output["details"] = details
148
+ self._print_json(output)
149
+ else:
150
+ # Human and table format
151
+ detail_str = ""
152
+ if details:
153
+ detail_str = (
154
+ " (" + ", ".join(f"{k}: {v}" for k, v in details.items()) + ")"
155
+ )
156
+ if self.color:
157
+ self._console.print(f"[yellow]⚠[/] {message}{detail_str}")
158
+ else:
159
+ self._console.print(f"⚠ {message}{detail_str}")
160
+
161
+ def print_data(self, data: dict[str, Any], *, title: str | None = None) -> None:
162
+ """Print a data structure.
163
+
164
+ Renders as:
165
+ - Human: Rich Tree for nested, key-value list for flat
166
+ - JSON: Raw dict
167
+ - Table: Key-value table
168
+
169
+ Args:
170
+ data: Dictionary to display.
171
+ title: Optional title for the output.
172
+
173
+ Example:
174
+ >>> console.print_data({"name": "discovery", "steps": 5})
175
+ """
176
+ if self._is_quiet():
177
+ return
178
+
179
+ if self.format == "json":
180
+ self._print_json(data)
181
+ elif self.format == "table":
182
+ self._print_data_as_table(data, title=title)
183
+ else:
184
+ # Human format - use tree for nested, simple for flat
185
+ if self._has_nested_values(data):
186
+ self._print_data_as_tree(data, title=title)
187
+ else:
188
+ self._print_data_as_kv(data, title=title)
189
+
190
+ def print_list(
191
+ self,
192
+ items: Sequence[dict[str, Any]],
193
+ *,
194
+ columns: list[str] | None = None,
195
+ title: str | None = None,
196
+ ) -> None:
197
+ """Print a list of items.
198
+
199
+ Renders as:
200
+ - Human: Bullet list
201
+ - JSON: Array
202
+ - Table: Rich Table with columns
203
+
204
+ Args:
205
+ items: List of dictionaries to display.
206
+ columns: Column names to display (default: all keys from first item).
207
+ title: Optional title for the output.
208
+
209
+ Example:
210
+ >>> console.print_list([
211
+ ... {"id": "kata/discovery", "name": "Discovery"},
212
+ ... {"id": "kata/design", "name": "Design"},
213
+ ... ])
214
+ """
215
+ if self._is_quiet():
216
+ return
217
+
218
+ if not items:
219
+ if self.format == "json":
220
+ self._print_json([])
221
+ else:
222
+ self._console.print("(no items)")
223
+ return
224
+
225
+ if self.format == "json":
226
+ self._print_json(list(items))
227
+ elif self.format == "table":
228
+ self._print_list_as_table(items, columns=columns, title=title)
229
+ else:
230
+ # Human format - bullet list
231
+ self._print_list_as_bullets(items, title=title)
232
+
233
+ # --- Private helpers ---
234
+
235
+ def _print_json(self, data: Any) -> None:
236
+ """Output data as JSON to stdout."""
237
+ print(json.dumps(data, indent=2, default=str))
238
+
239
+ def _has_nested_values(self, data: dict[str, Any]) -> bool:
240
+ """Check if dict has nested dict/list values."""
241
+ return any(isinstance(v, (dict, list)) for v in data.values())
242
+
243
+ def _print_data_as_tree(
244
+ self, data: dict[str, Any], *, title: str | None = None
245
+ ) -> None:
246
+ """Render nested dict as Rich Tree."""
247
+ tree = Tree(title or "Data")
248
+ self._add_dict_to_tree(tree, data)
249
+ self._console.print(tree)
250
+
251
+ def _add_dict_to_tree(self, tree: Tree, data: dict[str, Any]) -> None:
252
+ """Recursively add dict entries to tree."""
253
+ for key, value in data.items():
254
+ if isinstance(value, dict):
255
+ branch = tree.add(f"[bold]{key}[/]")
256
+ nested = cast(dict[str, Any], value)
257
+ self._add_dict_to_tree(branch, nested)
258
+ elif isinstance(value, list):
259
+ branch = tree.add(f"[bold]{key}[/]")
260
+ items = cast(list[Any], value)
261
+ for i, item in enumerate(items):
262
+ if isinstance(item, dict):
263
+ sub = branch.add(f"[dim][{i}][/]")
264
+ nested_item = cast(dict[str, Any], item)
265
+ self._add_dict_to_tree(sub, nested_item)
266
+ else:
267
+ branch.add(str(item))
268
+ else:
269
+ tree.add(f"[bold]{key}:[/] {value}")
270
+
271
+ def _print_data_as_kv(
272
+ self, data: dict[str, Any], *, title: str | None = None
273
+ ) -> None:
274
+ """Render flat dict as key-value pairs."""
275
+ if title:
276
+ self._console.print(f"[bold]{title}[/]")
277
+ for key, value in data.items():
278
+ self._console.print(f" [bold]{key}:[/] {value}")
279
+
280
+ def _print_data_as_table(
281
+ self, data: dict[str, Any], *, title: str | None = None
282
+ ) -> None:
283
+ """Render dict as two-column table."""
284
+ table = Table(title=title, show_header=True)
285
+ table.add_column("Key", style="bold")
286
+ table.add_column("Value")
287
+ for key, value in data.items():
288
+ table.add_row(key, str(value))
289
+ self._console.print(table)
290
+
291
+ def _print_list_as_bullets(
292
+ self, items: Sequence[dict[str, Any]], *, title: str | None = None
293
+ ) -> None:
294
+ """Render list as bullet points."""
295
+ if title:
296
+ self._console.print(f"{title}:")
297
+ for item in items:
298
+ # Create a summary from the first 2-3 values
299
+ values = list(item.values())[:3]
300
+ summary = " - ".join(str(v) for v in values)
301
+ self._console.print(f" • {summary}")
302
+
303
+ def _print_list_as_table(
304
+ self,
305
+ items: Sequence[dict[str, Any]],
306
+ *,
307
+ columns: list[str] | None = None,
308
+ title: str | None = None,
309
+ ) -> None:
310
+ """Render list as Rich Table."""
311
+ # Determine columns from first item if not specified
312
+ if columns is None:
313
+ columns = list(items[0].keys()) if items else []
314
+
315
+ table = Table(title=title, show_header=True)
316
+ for col in columns:
317
+ table.add_column(col.upper())
318
+
319
+ for item in items:
320
+ row = [str(item.get(col, "")) for col in columns]
321
+ table.add_row(*row)
322
+
323
+ self._console.print(table)
324
+
325
+
326
+ # --- Module-level singleton ---
327
+
328
+ _console: OutputConsole | None = None
329
+
330
+
331
+ def get_console() -> OutputConsole:
332
+ """Get or create the output console singleton.
333
+
334
+ Returns:
335
+ The global OutputConsole instance.
336
+
337
+ Example:
338
+ >>> console = get_console()
339
+ >>> console.print_message("Hello")
340
+ """
341
+ global _console # noqa: PLW0603
342
+ if _console is None:
343
+ _console = OutputConsole()
344
+ return _console
345
+
346
+
347
+ def set_console(console: OutputConsole | None) -> None:
348
+ """Set the output console (for testing or reconfiguration).
349
+
350
+ Args:
351
+ console: Console to use, or None to reset to default.
352
+
353
+ Example:
354
+ >>> set_console(OutputConsole(format="json"))
355
+ >>> # ... use console ...
356
+ >>> set_console(None) # Reset
357
+ """
358
+ global _console # noqa: PLW0603
359
+ _console = console
360
+
361
+
362
+ def configure_console(
363
+ format: OutputFormat = "human",
364
+ verbosity: int = 0,
365
+ color: bool = True,
366
+ ) -> OutputConsole:
367
+ """Configure and return the global console.
368
+
369
+ Creates a new OutputConsole with the given settings and sets it
370
+ as the global singleton.
371
+
372
+ Args:
373
+ format: Output format ("human", "json", or "table").
374
+ verbosity: Verbosity level (-1=quiet, 0=normal, 1-3=verbose).
375
+ color: Whether to use colors in human output.
376
+
377
+ Returns:
378
+ The newly configured OutputConsole.
379
+
380
+ Example:
381
+ >>> console = configure_console(format="json", verbosity=1)
382
+ """
383
+ global _console # noqa: PLW0603
384
+ _console = OutputConsole(format=format, verbosity=verbosity, color=color)
385
+ return _console
386
+
387
+
388
+ __all__ = [
389
+ "OutputConsole",
390
+ "OutputFormat",
391
+ "get_console",
392
+ "set_console",
393
+ "configure_console",
394
+ ]
@@ -0,0 +1,9 @@
1
+ """Output formatters for domain-specific result types.
2
+
3
+ Each formatter module provides functions that take a result object
4
+ and output format, returning formatted output for the console.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ __all__: list[str] = []
@@ -0,0 +1,135 @@
1
+ """Output formatters for adapter commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from rich.console import Console
9
+
10
+ if TYPE_CHECKING:
11
+ from raise_cli.adapters.declarative.schema import DeclarativeAdapterConfig
12
+
13
+
14
+ def format_list_human(
15
+ tier: str,
16
+ groups: list[dict[str, Any]],
17
+ console: Console,
18
+ ) -> None:
19
+ """Display adapter list grouped by entry point group.
20
+
21
+ Args:
22
+ tier: Current tier level.
23
+ groups: List of dicts with group, protocol_name, adapters keys.
24
+ console: Rich console for output.
25
+ """
26
+ console.print(f"Tier: {tier}\n")
27
+
28
+ for group_info in groups:
29
+ header = f"{group_info['group']} ({group_info['protocol_name']})"
30
+ console.print(f"[bold]{header}[/bold]")
31
+
32
+ adapters = group_info["adapters"]
33
+ if not adapters:
34
+ console.print(" [dim](none)[/dim]")
35
+ else:
36
+ for adapter in adapters:
37
+ console.print(f" {adapter['name']:<20s}{adapter['package']}")
38
+
39
+ console.print()
40
+
41
+
42
+ def format_list_json(tier: str, groups: list[dict[str, Any]]) -> str:
43
+ """Format adapter list as JSON.
44
+
45
+ Args:
46
+ tier: Current tier level.
47
+ groups: List of dicts with group, protocol_name, adapters keys.
48
+
49
+ Returns:
50
+ JSON string.
51
+ """
52
+ return json.dumps({"tier": tier, "groups": groups}, indent=2)
53
+
54
+
55
+ def format_check_human(
56
+ results: list[dict[str, Any]],
57
+ console: Console,
58
+ ) -> None:
59
+ """Display adapter check results with pass/fail indicators.
60
+
61
+ Args:
62
+ results: List of dicts with group, name, protocol_name, compliant, error keys.
63
+ console: Rich console for output.
64
+ """
65
+ if not results:
66
+ console.print("No adapters registered.")
67
+ return
68
+
69
+ console.print("Checking adapters...\n")
70
+
71
+ current_group = ""
72
+ for r in results:
73
+ if r["group"] != current_group:
74
+ current_group = r["group"]
75
+ console.print(f"[bold]{current_group}[/bold]")
76
+
77
+ if r["compliant"]:
78
+ console.print(
79
+ f" [green]\u2713[/green] {r['name']:<20s}{r['protocol_name']} compliant"
80
+ )
81
+ else:
82
+ console.print(f" [red]\u2717[/red] {r['name']:<20s}{r['error']}")
83
+
84
+ passed = sum(1 for r in results if r["compliant"])
85
+ total = len(results)
86
+ console.print()
87
+ if passed == total:
88
+ console.print(f"[green]All {total} adapters passed.[/green]")
89
+ else:
90
+ failed = total - passed
91
+ console.print(f"[red]{failed} of {total} adapters failed.[/red]")
92
+
93
+
94
+ def format_check_json(results: list[dict[str, Any]]) -> str:
95
+ """Format adapter check results as JSON.
96
+
97
+ Args:
98
+ results: List of dicts with group, name, protocol_name, compliant, error keys.
99
+
100
+ Returns:
101
+ JSON string.
102
+ """
103
+ passed = sum(1 for r in results if r["compliant"])
104
+ return json.dumps(
105
+ {
106
+ "results": results,
107
+ "total": len(results),
108
+ "passed": passed,
109
+ "all_passed": passed == len(results),
110
+ },
111
+ indent=2,
112
+ )
113
+
114
+
115
+ def format_validate_human(
116
+ config: DeclarativeAdapterConfig,
117
+ console: Console,
118
+ ) -> None:
119
+ """Display validation success for a declarative adapter config.
120
+
121
+ Args:
122
+ config: Validated adapter configuration.
123
+ console: Rich console for output.
124
+ """
125
+ mapped = sum(1 for v in config.methods.values() if v is not None)
126
+ unsupported = sum(1 for v in config.methods.values() if v is None)
127
+ server_cmd = f"{config.server.command} {' '.join(config.server.args)}".strip()
128
+
129
+ console.print("[green]✓ Valid adapter config[/green]")
130
+ console.print(f" Name: {config.adapter.name}")
131
+ console.print(f" Protocol: {config.adapter.protocol}")
132
+ if config.adapter.description:
133
+ console.print(f" Description: {config.adapter.description}")
134
+ console.print(f" Methods: {mapped} mapped, {unsupported} unsupported")
135
+ console.print(f" Server: {server_cmd}")