zjcode 0.0.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 (163) hide show
  1. deepagents_code/__init__.py +42 -0
  2. deepagents_code/__main__.py +6 -0
  3. deepagents_code/_ask_user_types.py +90 -0
  4. deepagents_code/_cli_context.py +96 -0
  5. deepagents_code/_constants.py +41 -0
  6. deepagents_code/_debug.py +148 -0
  7. deepagents_code/_debug_buffer.py +204 -0
  8. deepagents_code/_env_vars.py +411 -0
  9. deepagents_code/_fake_models.py +66 -0
  10. deepagents_code/_git.py +521 -0
  11. deepagents_code/_paths.py +69 -0
  12. deepagents_code/_server_config.py +576 -0
  13. deepagents_code/_session_stats.py +235 -0
  14. deepagents_code/_startup_error.py +45 -0
  15. deepagents_code/_testing_models.py +305 -0
  16. deepagents_code/_textual_patches.py +420 -0
  17. deepagents_code/_tool_stream.py +694 -0
  18. deepagents_code/_version.py +46 -0
  19. deepagents_code/agent.py +1976 -0
  20. deepagents_code/app.py +19239 -0
  21. deepagents_code/app.tcss +438 -0
  22. deepagents_code/approval_mode.py +131 -0
  23. deepagents_code/ask_user.py +306 -0
  24. deepagents_code/auth_display.py +185 -0
  25. deepagents_code/auth_store.py +545 -0
  26. deepagents_code/built_in_skills/__init__.py +5 -0
  27. deepagents_code/built_in_skills/remember/SKILL.md +118 -0
  28. deepagents_code/built_in_skills/skill-creator/SKILL.md +383 -0
  29. deepagents_code/built_in_skills/skill-creator/scripts/init_skill.py +366 -0
  30. deepagents_code/built_in_skills/skill-creator/scripts/quick_validate.py +158 -0
  31. deepagents_code/client/__init__.py +1 -0
  32. deepagents_code/client/commands/__init__.py +1 -0
  33. deepagents_code/client/commands/auth.py +541 -0
  34. deepagents_code/client/commands/config.py +714 -0
  35. deepagents_code/client/commands/mcp.py +250 -0
  36. deepagents_code/client/commands/tools.py +416 -0
  37. deepagents_code/client/launch/__init__.py +1 -0
  38. deepagents_code/client/launch/server.py +978 -0
  39. deepagents_code/client/launch/server_manager.py +540 -0
  40. deepagents_code/client/non_interactive.py +1758 -0
  41. deepagents_code/client/remote_client.py +794 -0
  42. deepagents_code/clipboard.py +217 -0
  43. deepagents_code/command_registry.py +450 -0
  44. deepagents_code/config.py +4829 -0
  45. deepagents_code/config_manifest.py +1451 -0
  46. deepagents_code/configurable_model.py +577 -0
  47. deepagents_code/default_agent_prompt.md +12 -0
  48. deepagents_code/doctor.py +559 -0
  49. deepagents_code/editor.py +142 -0
  50. deepagents_code/event_bus.py +411 -0
  51. deepagents_code/extras_info.py +661 -0
  52. deepagents_code/file_ops.py +576 -0
  53. deepagents_code/formatting.py +135 -0
  54. deepagents_code/goal_rubric.py +497 -0
  55. deepagents_code/goal_tools.py +459 -0
  56. deepagents_code/hooks.py +348 -0
  57. deepagents_code/input.py +1041 -0
  58. deepagents_code/integrations/__init__.py +1 -0
  59. deepagents_code/integrations/openai_codex.py +551 -0
  60. deepagents_code/integrations/sandbox_config.py +198 -0
  61. deepagents_code/integrations/sandbox_factory.py +1124 -0
  62. deepagents_code/integrations/sandbox_provider.py +137 -0
  63. deepagents_code/integrations/sandbox_registry.py +350 -0
  64. deepagents_code/iterm_cursor_guide.py +176 -0
  65. deepagents_code/local_context.py +926 -0
  66. deepagents_code/main.py +3985 -0
  67. deepagents_code/managed_tools.py +642 -0
  68. deepagents_code/mcp_auth.py +1950 -0
  69. deepagents_code/mcp_config.py +176 -0
  70. deepagents_code/mcp_disabled.py +212 -0
  71. deepagents_code/mcp_login_service.py +281 -0
  72. deepagents_code/mcp_oauth_ui.py +199 -0
  73. deepagents_code/mcp_providers/__init__.py +23 -0
  74. deepagents_code/mcp_providers/_registry.py +39 -0
  75. deepagents_code/mcp_providers/base.py +133 -0
  76. deepagents_code/mcp_providers/github.py +102 -0
  77. deepagents_code/mcp_providers/slack.py +175 -0
  78. deepagents_code/mcp_tools.py +2427 -0
  79. deepagents_code/mcp_trust.py +207 -0
  80. deepagents_code/media_utils.py +826 -0
  81. deepagents_code/memory_guard.py +474 -0
  82. deepagents_code/model_config.py +4156 -0
  83. deepagents_code/notifications.py +247 -0
  84. deepagents_code/offload.py +402 -0
  85. deepagents_code/onboarding.py +223 -0
  86. deepagents_code/output.py +69 -0
  87. deepagents_code/paste_collapse.py +103 -0
  88. deepagents_code/project_utils.py +231 -0
  89. deepagents_code/py.typed +0 -0
  90. deepagents_code/reasoning_effort.py +641 -0
  91. deepagents_code/reliable_rubric.py +97 -0
  92. deepagents_code/resume_state.py +237 -0
  93. deepagents_code/server_graph.py +310 -0
  94. deepagents_code/session_end_summary.py +329 -0
  95. deepagents_code/sessions.py +1716 -0
  96. deepagents_code/skills/__init__.py +18 -0
  97. deepagents_code/skills/commands.py +1252 -0
  98. deepagents_code/skills/invocation.py +112 -0
  99. deepagents_code/skills/load.py +196 -0
  100. deepagents_code/skills/trust.py +547 -0
  101. deepagents_code/state_migration.py +136 -0
  102. deepagents_code/subagents.py +278 -0
  103. deepagents_code/system_prompt.md +204 -0
  104. deepagents_code/terminal_capabilities.py +115 -0
  105. deepagents_code/terminal_escape.py +287 -0
  106. deepagents_code/theme.py +891 -0
  107. deepagents_code/todo_list_prompt.md +12 -0
  108. deepagents_code/tool_catalog.py +509 -0
  109. deepagents_code/tool_display.py +367 -0
  110. deepagents_code/tools.py +516 -0
  111. deepagents_code/tui/__init__.py +1 -0
  112. deepagents_code/tui/textual_adapter.py +2553 -0
  113. deepagents_code/tui/widgets/__init__.py +9 -0
  114. deepagents_code/tui/widgets/_js_eval_display.py +139 -0
  115. deepagents_code/tui/widgets/_links.py +261 -0
  116. deepagents_code/tui/widgets/agent_selector.py +420 -0
  117. deepagents_code/tui/widgets/approval.py +602 -0
  118. deepagents_code/tui/widgets/ask_user.py +515 -0
  119. deepagents_code/tui/widgets/auth.py +1997 -0
  120. deepagents_code/tui/widgets/autocomplete.py +890 -0
  121. deepagents_code/tui/widgets/chat_input.py +3181 -0
  122. deepagents_code/tui/widgets/codex_auth.py +452 -0
  123. deepagents_code/tui/widgets/cwd_switch.py +242 -0
  124. deepagents_code/tui/widgets/debug_console.py +868 -0
  125. deepagents_code/tui/widgets/diff.py +252 -0
  126. deepagents_code/tui/widgets/effort_selector.py +189 -0
  127. deepagents_code/tui/widgets/goal_review.py +390 -0
  128. deepagents_code/tui/widgets/goal_status.py +50 -0
  129. deepagents_code/tui/widgets/history.py +194 -0
  130. deepagents_code/tui/widgets/install_confirm.py +248 -0
  131. deepagents_code/tui/widgets/launch_init.py +482 -0
  132. deepagents_code/tui/widgets/loading.py +227 -0
  133. deepagents_code/tui/widgets/mcp_login.py +539 -0
  134. deepagents_code/tui/widgets/mcp_reconnect.py +212 -0
  135. deepagents_code/tui/widgets/mcp_viewer.py +1634 -0
  136. deepagents_code/tui/widgets/message_store.py +999 -0
  137. deepagents_code/tui/widgets/messages.py +3846 -0
  138. deepagents_code/tui/widgets/model_selector.py +2343 -0
  139. deepagents_code/tui/widgets/notification_center.py +456 -0
  140. deepagents_code/tui/widgets/notification_detail.py +257 -0
  141. deepagents_code/tui/widgets/notification_settings.py +170 -0
  142. deepagents_code/tui/widgets/restart_prompt.py +158 -0
  143. deepagents_code/tui/widgets/skill_trust.py +131 -0
  144. deepagents_code/tui/widgets/startup_tip.py +91 -0
  145. deepagents_code/tui/widgets/status.py +781 -0
  146. deepagents_code/tui/widgets/subagent_panel.py +969 -0
  147. deepagents_code/tui/widgets/theme_selector.py +401 -0
  148. deepagents_code/tui/widgets/thread_selector.py +2564 -0
  149. deepagents_code/tui/widgets/tool_renderers.py +190 -0
  150. deepagents_code/tui/widgets/tool_widgets.py +304 -0
  151. deepagents_code/tui/widgets/update_available.py +311 -0
  152. deepagents_code/tui/widgets/update_confirm.py +184 -0
  153. deepagents_code/tui/widgets/update_progress.py +327 -0
  154. deepagents_code/tui/widgets/welcome.py +508 -0
  155. deepagents_code/turn_end_summary.py +522 -0
  156. deepagents_code/ui.py +858 -0
  157. deepagents_code/unicode_security.py +563 -0
  158. deepagents_code/update_check.py +3124 -0
  159. zjcode-0.0.1.data/data/deepagents_code/default_agent_prompt.md +12 -0
  160. zjcode-0.0.1.dist-info/METADATA +220 -0
  161. zjcode-0.0.1.dist-info/RECORD +163 -0
  162. zjcode-0.0.1.dist-info/WHEEL +4 -0
  163. zjcode-0.0.1.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,508 @@
1
+ """Welcome banner widget."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING, Any, Final
9
+
10
+ from textual.color import Color as TColor
11
+ from textual.content import Content
12
+ from textual.style import Style as TStyle
13
+ from textual.widgets import Static
14
+
15
+ if TYPE_CHECKING:
16
+ from textual.events import Click, MouseMove
17
+
18
+ from deepagents_code import theme
19
+ from deepagents_code._env_vars import (
20
+ DEBUG,
21
+ HIDE_CWD,
22
+ HIDE_LANGSMITH_TRACING,
23
+ HIDE_SPLASH_VERSION,
24
+ SHOW_LANGSMITH_REPLICA_TRACING,
25
+ SPLASH_SHOW_CWD,
26
+ SPLASH_SHOW_MODEL,
27
+ is_env_truthy,
28
+ )
29
+ from deepagents_code._version import __version__
30
+ from deepagents_code.config import (
31
+ _get_editable_install_path,
32
+ _is_editable_install,
33
+ fetch_langsmith_project_url,
34
+ get_glyphs,
35
+ get_langsmith_project_name,
36
+ get_langsmith_replica_project,
37
+ )
38
+ from deepagents_code.tui.widgets._links import open_style_link
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+ _ANSI_THEMES: Final[frozenset[str]] = frozenset({"ansi-dark", "ansi-light"})
43
+ """Theme names whose color palette is determined by the terminal emulator
44
+ rather than by the app, so link styles use bold instead of a parsed color."""
45
+
46
+ _LANGSMITH_UTM_SOURCE: Final[str] = "deepagents-code"
47
+ """UTM source tag appended to LangSmith project URLs in the welcome banner."""
48
+
49
+
50
+ def _langsmith_project_link(project_url: str) -> str:
51
+ """Append the Deep Agents source tag to a LangSmith project URL.
52
+
53
+ Args:
54
+ project_url: LangSmith project URL.
55
+
56
+ Returns:
57
+ Project URL with the Deep Agents source tag.
58
+ """
59
+ return f"{project_url}?utm_source={_LANGSMITH_UTM_SOURCE}"
60
+
61
+
62
+ def _langsmith_project_link_style(
63
+ project_url: str,
64
+ *,
65
+ ansi: bool,
66
+ colors: theme.ThemeColors,
67
+ ) -> TStyle:
68
+ """Build the clickable style for a LangSmith project name.
69
+
70
+ Args:
71
+ project_url: LangSmith project URL.
72
+ ansi: Whether the active theme is an ANSI terminal theme.
73
+ colors: Active Deep Agents theme colors.
74
+
75
+ Returns:
76
+ Link style for a LangSmith project name.
77
+ """
78
+ link = _langsmith_project_link(project_url)
79
+ if ansi:
80
+ return TStyle(bold=True, link=link)
81
+ return TStyle(foreground=TColor.parse(colors.primary), link=link)
82
+
83
+
84
+ def _local_tag_style(*, ansi: bool, colors: theme.ThemeColors) -> str | TStyle:
85
+ """Build the style for the editable-install `(local)` tag.
86
+
87
+ Args:
88
+ ansi: Whether the active theme is an ANSI terminal theme.
89
+ colors: Active Deep Agents theme colors.
90
+
91
+ Returns:
92
+ A bold markup style under ANSI themes (whose palette the terminal owns,
93
+ so a parsed color could be invisible) or a bold themed
94
+ color otherwise.
95
+ """
96
+ if ansi:
97
+ return "bold"
98
+ return TStyle(foreground=TColor.parse(colors.tool), bold=True)
99
+
100
+
101
+ def _debug_tag_style(*, ansi: bool, colors: theme.ThemeColors) -> str | TStyle:
102
+ """Build the style for the `(debug enabled)` tag.
103
+
104
+ Args:
105
+ ansi: Whether the active theme is an ANSI terminal theme.
106
+ colors: Active Deep Agents theme colors.
107
+
108
+ Returns:
109
+ A bold yellow markup style under ANSI themes (whose palette the terminal
110
+ owns, so a parsed color could be invisible) or a bold themed warning
111
+ color otherwise.
112
+ """
113
+ if ansi:
114
+ return "bold yellow"
115
+ return TStyle(foreground=TColor.parse(colors.warning), bold=True)
116
+
117
+
118
+ def _home_prefixed(cwd: str) -> str:
119
+ """Format a directory path, using `~` for the home directory when possible.
120
+
121
+ Args:
122
+ cwd: Working directory path.
123
+
124
+ Returns:
125
+ The path with the home prefix collapsed to `~` when applicable.
126
+ """
127
+ path = Path(cwd)
128
+ try:
129
+ home = Path.home()
130
+ if path == home:
131
+ return "~"
132
+ if path.is_relative_to(home):
133
+ return "~/" + path.relative_to(home).as_posix()
134
+ # `Path.home()` raises `RuntimeError` when the home dir can't be resolved
135
+ # (no HOME/passwd entry); `ValueError` guards odd paths (e.g. embedded NUL).
136
+ # Either way, fall back to the accurate absolute path.
137
+ except (ValueError, RuntimeError):
138
+ pass
139
+ return str(path)
140
+
141
+
142
+ class WelcomeBanner(Static):
143
+ """Compact welcome banner shown at startup.
144
+
145
+ Renders a bordered box with the product title and optional version. A
146
+ `(debug enabled)` tag appears when `DEEPAGENTS_CODE_DEBUG` is enabled
147
+ (truthy), even when the version is hidden. A `(local)` tag appears for
148
+ editable installs only when the version is shown. Rows follow that appear
149
+ only when their data (and any env gate) is present. In render order: the
150
+ active model
151
+ (`SPLASH_SHOW_MODEL`, opt-in), working directory (`SPLASH_SHOW_CWD`,
152
+ opt-in), LangSmith tracing project and its replica (each clickable once its
153
+ URL resolves), thread ID (debug mode only), and the MCP tool count. MCP
154
+ server warnings and the editable-install path follow.
155
+ """
156
+
157
+ # Disable Textual's auto_links to prevent a flicker cycle: Style.__add__
158
+ # calls .copy() for linked styles, generating a fresh random _link_id on
159
+ # each render. This means highlight_link_id never stabilizes, causing an
160
+ # infinite hover-refresh loop.
161
+ auto_links = False
162
+
163
+ DEFAULT_CSS = """
164
+ WelcomeBanner {
165
+ height: auto;
166
+ border: round $primary;
167
+ padding: 0 2;
168
+ margin-bottom: 1;
169
+ }
170
+ """
171
+
172
+ def __init__(
173
+ self,
174
+ thread_id: str | None = None,
175
+ mcp_tool_count: int = 0,
176
+ *,
177
+ model_provider: str = "",
178
+ model_name: str = "",
179
+ cwd: str | None = None,
180
+ mcp_unauthenticated: int = 0,
181
+ mcp_errored: int = 0,
182
+ mcp_awaiting_reconnect: int = 0,
183
+ **kwargs: Any,
184
+ ) -> None:
185
+ """Initialize the welcome banner.
186
+
187
+ Args:
188
+ thread_id: Displayed only when debug mode is enabled.
189
+ mcp_tool_count: Number of MCP tools loaded at startup.
190
+ model_provider: Active model provider (e.g. `anthropic`). Displayed
191
+ only when `SPLASH_SHOW_MODEL` is enabled.
192
+ model_name: Active model name. Displayed only when `SPLASH_SHOW_MODEL` is
193
+ enabled.
194
+ cwd: Working directory. Defaults to the process cwd. Displayed only
195
+ when `SPLASH_SHOW_CWD` is enabled.
196
+ mcp_unauthenticated: Number of MCP servers awaiting login.
197
+ mcp_errored: Number of MCP servers that failed to load.
198
+ mcp_awaiting_reconnect: Number of MCP servers awaiting reconnect.
199
+ **kwargs: Additional arguments passed to parent.
200
+ """
201
+ self._model_provider = model_provider
202
+ self._model_name = model_name
203
+ self._cwd = cwd if cwd is not None else str(Path.cwd())
204
+ self._show_model = is_env_truthy(SPLASH_SHOW_MODEL)
205
+ # `_show_cwd` and `_hide_cwd` are deliberately orthogonal: `_show_cwd`
206
+ # gates the working-directory row (opt-in), while `_hide_cwd` gates the
207
+ # editable-install path below. They govern different surfaces, so both
208
+ # can be set without contradiction.
209
+ self._show_cwd = is_env_truthy(SPLASH_SHOW_CWD)
210
+ self._hide_cwd = is_env_truthy(HIDE_CWD)
211
+ self._hide_version = is_env_truthy(HIDE_SPLASH_VERSION)
212
+ # Avoid collision with Widget._thread_id (Textual internal int)
213
+ self._cli_thread_id = thread_id
214
+ self._mcp_tool_count = mcp_tool_count
215
+ self._mcp_unauthenticated = mcp_unauthenticated
216
+ self._mcp_errored = mcp_errored
217
+ self._mcp_awaiting_reconnect = mcp_awaiting_reconnect
218
+ self._hide_langsmith_tracing = is_env_truthy(HIDE_LANGSMITH_TRACING)
219
+ self._project_name: str | None = (
220
+ None if self._hide_langsmith_tracing else get_langsmith_project_name()
221
+ )
222
+ show_replica_tracing = is_env_truthy(
223
+ SHOW_LANGSMITH_REPLICA_TRACING,
224
+ default=True,
225
+ )
226
+ self._replica_project: str | None = (
227
+ get_langsmith_replica_project()
228
+ if self._project_name and show_replica_tracing
229
+ else None
230
+ )
231
+ self._project_urls: dict[str, str] = {}
232
+ self._debug_enabled = is_env_truthy(DEBUG)
233
+ self._show_thread_id = self._debug_enabled
234
+ super().__init__(self._build_banner(), **kwargs)
235
+
236
+ def on_mount(self) -> None:
237
+ """Watch for theme changes and start the LangSmith project-URL fetch."""
238
+ self.watch(self.app, "theme", self._on_theme_change, init=False)
239
+ if self._project_name:
240
+ self.run_worker(self._fetch_and_update, exclusive=True)
241
+
242
+ def _on_theme_change(self) -> None:
243
+ """Re-render the banner when the app theme changes."""
244
+ self.update(self._build_banner())
245
+
246
+ async def _fetch_and_update(self) -> None:
247
+ """Fetch the LangSmith URL in a thread and update the banner."""
248
+ if not self._project_name:
249
+ return
250
+ projects = dict.fromkeys(
251
+ project
252
+ for project in (self._project_name, self._replica_project)
253
+ if project
254
+ )
255
+ for project in projects:
256
+ try:
257
+ project_url = await asyncio.wait_for(
258
+ asyncio.to_thread(fetch_langsmith_project_url, project),
259
+ timeout=2.0,
260
+ )
261
+ except (TimeoutError, OSError):
262
+ logger.debug(
263
+ "LangSmith project URL fetch failed for %r", project, exc_info=True
264
+ )
265
+ project_url = None
266
+ if project_url:
267
+ self._project_urls[project] = project_url
268
+ self.update(self._build_banner())
269
+
270
+ def _project_url(self, project: str | None) -> str | None:
271
+ """Return the resolved LangSmith URL for a project.
272
+
273
+ Args:
274
+ project: Project name to look up.
275
+
276
+ Returns:
277
+ Resolved project URL, or `None` when missing or not yet fetched.
278
+ """
279
+ if project is None:
280
+ return None
281
+ return self._project_urls.get(project)
282
+
283
+ def update_model(self, *, provider: str, model: str) -> None:
284
+ """Track a new model and re-render when it is displayed.
285
+
286
+ Args:
287
+ provider: Active model provider.
288
+ model: Active model name.
289
+ """
290
+ self._model_provider = provider
291
+ self._model_name = model
292
+ if self._show_model:
293
+ self.update(self._build_banner())
294
+
295
+ def update_cwd(self, cwd: str) -> None:
296
+ """Track a new working directory and re-render when it is displayed.
297
+
298
+ Args:
299
+ cwd: New working directory path.
300
+ """
301
+ self._cwd = cwd
302
+ if self._show_cwd:
303
+ self.update(self._build_banner())
304
+
305
+ def update_thread_id(self, thread_id: str) -> None:
306
+ """Track a new thread ID and re-render when debug mode is active.
307
+
308
+ Args:
309
+ thread_id: The new thread ID.
310
+ """
311
+ self._cli_thread_id = thread_id
312
+ if self._show_thread_id:
313
+ self.update(self._build_banner())
314
+
315
+ def set_connected(
316
+ self,
317
+ mcp_tool_count: int = 0,
318
+ *,
319
+ mcp_unauthenticated: int = 0,
320
+ mcp_errored: int = 0,
321
+ mcp_awaiting_reconnect: int = 0,
322
+ ) -> None:
323
+ """Update MCP tool counts and re-render the banner.
324
+
325
+ Args:
326
+ mcp_tool_count: Number of MCP tools loaded during connection.
327
+ mcp_unauthenticated: Number of MCP servers awaiting login.
328
+ mcp_errored: Number of MCP servers that failed to load.
329
+ mcp_awaiting_reconnect: Number of MCP servers awaiting reconnect.
330
+ """
331
+ self._mcp_tool_count = mcp_tool_count
332
+ self._mcp_unauthenticated = mcp_unauthenticated
333
+ self._mcp_errored = mcp_errored
334
+ self._mcp_awaiting_reconnect = mcp_awaiting_reconnect
335
+ self.update(self._build_banner())
336
+
337
+ def on_click(self, event: Click) -> None: # noqa: PLR6301 # Textual event handler
338
+ """Open style-embedded hyperlinks on single click."""
339
+ open_style_link(event)
340
+
341
+ def on_mouse_move(self, event: MouseMove) -> None:
342
+ """Show a hand pointer over link spans and reset it elsewhere."""
343
+ self.styles.pointer = "pointer" if event.style.link else "default"
344
+
345
+ def on_leave(self) -> None:
346
+ """Reset the pointer shape when the mouse leaves the banner."""
347
+ self.styles.pointer = "default"
348
+
349
+ def _build_banner(self) -> Content:
350
+ """Build the banner content.
351
+
352
+ Returns:
353
+ Content with the title, optional version, and any applicable header
354
+ tags (`(debug enabled)` when debug is on; `(local)` for editable
355
+ installs when the version is shown), followed by any applicable rows
356
+ in order: model (when `SPLASH_SHOW_MODEL`), directory (when
357
+ `SPLASH_SHOW_CWD`), tracing and replica (each clickable once its URL
358
+ resolves), thread ID (debug only), MCP tool count, MCP server
359
+ warnings, and the editable-install path.
360
+ """
361
+ colors = theme.get_theme_colors(self)
362
+ ansi = self.app.theme in _ANSI_THEMES
363
+ accent: str | TStyle = "bold" if ansi else colors.primary
364
+ title_style: str | TStyle = (
365
+ "bold"
366
+ if ansi
367
+ else TStyle(foreground=TColor.parse(colors.primary), bold=True)
368
+ )
369
+ warn_color: str = "bold yellow" if ansi else colors.warning
370
+
371
+ parts: list[str | tuple[str, str | TStyle]] = [
372
+ (f"{get_glyphs().cursor} ", title_style),
373
+ ("dcode", "bold"),
374
+ ]
375
+ if not self._hide_version:
376
+ parts.append((f" v{__version__}", "dim"))
377
+ if self._debug_enabled:
378
+ parts.append(
379
+ (
380
+ " (debug enabled)",
381
+ _debug_tag_style(ansi=ansi, colors=colors),
382
+ )
383
+ )
384
+ if not self._hide_version and _is_editable_install():
385
+ parts.append((" (local)", _local_tag_style(ansi=ansi, colors=colors)))
386
+
387
+ # Row labels share a common column width so values stay aligned; the
388
+ # longest label ("directory:") needs 11 columns including its space.
389
+ rows: list[list[tuple[str, str | TStyle]]] = []
390
+ if self._show_model and self._model_name:
391
+ model_value = (
392
+ f"{self._model_provider}:{self._model_name}"
393
+ if self._model_provider
394
+ else self._model_name
395
+ )
396
+ rows.append([("model: ", "dim"), (model_value, accent)])
397
+ if self._show_cwd and self._cwd:
398
+ rows.append([("directory: ", "dim"), (_home_prefixed(self._cwd), accent)])
399
+ if self._project_name:
400
+ project_url = self._project_url(self._project_name)
401
+ if project_url:
402
+ rows.append(
403
+ [
404
+ ("tracing: ", "dim"),
405
+ (
406
+ f"'{self._project_name}'",
407
+ _langsmith_project_link_style(
408
+ project_url,
409
+ ansi=ansi,
410
+ colors=colors,
411
+ ),
412
+ ),
413
+ ]
414
+ )
415
+ else:
416
+ rows.append(
417
+ [("tracing: ", "dim"), (f"'{self._project_name}'", accent)]
418
+ )
419
+ if self._replica_project:
420
+ replica_url = self._project_url(self._replica_project)
421
+ if replica_url:
422
+ rows.append(
423
+ [
424
+ ("replica: ", "dim"),
425
+ (
426
+ f"'{self._replica_project}'",
427
+ _langsmith_project_link_style(
428
+ replica_url,
429
+ ansi=ansi,
430
+ colors=colors,
431
+ ),
432
+ ),
433
+ ]
434
+ )
435
+ else:
436
+ rows.append(
437
+ [("replica: ", "dim"), (f"'{self._replica_project}'", accent)]
438
+ )
439
+ if self._show_thread_id and self._cli_thread_id:
440
+ rows.append([("thread: ", "dim"), (self._cli_thread_id, "dim")])
441
+ if self._mcp_tool_count > 0:
442
+ label = "tool" if self._mcp_tool_count == 1 else "tools"
443
+ rows.append(
444
+ [("mcp: ", "dim"), (f"{self._mcp_tool_count} {label}", accent)]
445
+ )
446
+
447
+ for index, row in enumerate(rows):
448
+ parts.append("\n\n" if index == 0 else "\n")
449
+ parts.extend(row)
450
+
451
+ # MCP server warnings — actionable alerts not shown elsewhere in the banner.
452
+ warning_lines: list[list[tuple[str, str | TStyle]]] = []
453
+ if self._mcp_unauthenticated > 0:
454
+ server_label = "server" if self._mcp_unauthenticated == 1 else "servers"
455
+ verb = "needs" if self._mcp_unauthenticated == 1 else "need"
456
+ warning_lines.append(
457
+ [
458
+ (f"{get_glyphs().warning} ", warn_color),
459
+ (
460
+ (
461
+ f"{self._mcp_unauthenticated} MCP {server_label} {verb}"
462
+ " login — open /mcp"
463
+ ),
464
+ "dim",
465
+ ),
466
+ ]
467
+ )
468
+ if self._mcp_errored > 0:
469
+ server_label = "server" if self._mcp_errored == 1 else "servers"
470
+ warning_lines.append(
471
+ [
472
+ (f"{get_glyphs().warning} ", warn_color),
473
+ (
474
+ (
475
+ f"{self._mcp_errored} MCP {server_label} failed to load"
476
+ " — open /mcp for details"
477
+ ),
478
+ "dim",
479
+ ),
480
+ ]
481
+ )
482
+ if self._mcp_awaiting_reconnect > 0:
483
+ server_label = "server" if self._mcp_awaiting_reconnect == 1 else "servers"
484
+ warning_lines.append(
485
+ [
486
+ (f"{get_glyphs().warning} ", warn_color),
487
+ (
488
+ (
489
+ f"{self._mcp_awaiting_reconnect} MCP {server_label}"
490
+ " ready to load — run `/mcp reconnect`"
491
+ ),
492
+ "dim",
493
+ ),
494
+ ]
495
+ )
496
+
497
+ for line in warning_lines:
498
+ parts.append("\n")
499
+ parts.extend(line)
500
+
501
+ # Editable-install path for local development visibility.
502
+ if not self._hide_version and not self._hide_cwd:
503
+ editable_path = _get_editable_install_path()
504
+ if editable_path:
505
+ parts.append("\n")
506
+ parts.extend([("installed: ", "dim"), (editable_path, "dim")])
507
+
508
+ return Content.assemble(*parts)