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.
- deepagents_code/__init__.py +42 -0
- deepagents_code/__main__.py +6 -0
- deepagents_code/_ask_user_types.py +90 -0
- deepagents_code/_cli_context.py +96 -0
- deepagents_code/_constants.py +41 -0
- deepagents_code/_debug.py +148 -0
- deepagents_code/_debug_buffer.py +204 -0
- deepagents_code/_env_vars.py +411 -0
- deepagents_code/_fake_models.py +66 -0
- deepagents_code/_git.py +521 -0
- deepagents_code/_paths.py +69 -0
- deepagents_code/_server_config.py +576 -0
- deepagents_code/_session_stats.py +235 -0
- deepagents_code/_startup_error.py +45 -0
- deepagents_code/_testing_models.py +305 -0
- deepagents_code/_textual_patches.py +420 -0
- deepagents_code/_tool_stream.py +694 -0
- deepagents_code/_version.py +46 -0
- deepagents_code/agent.py +1976 -0
- deepagents_code/app.py +19239 -0
- deepagents_code/app.tcss +438 -0
- deepagents_code/approval_mode.py +131 -0
- deepagents_code/ask_user.py +306 -0
- deepagents_code/auth_display.py +185 -0
- deepagents_code/auth_store.py +545 -0
- deepagents_code/built_in_skills/__init__.py +5 -0
- deepagents_code/built_in_skills/remember/SKILL.md +118 -0
- deepagents_code/built_in_skills/skill-creator/SKILL.md +383 -0
- deepagents_code/built_in_skills/skill-creator/scripts/init_skill.py +366 -0
- deepagents_code/built_in_skills/skill-creator/scripts/quick_validate.py +158 -0
- deepagents_code/client/__init__.py +1 -0
- deepagents_code/client/commands/__init__.py +1 -0
- deepagents_code/client/commands/auth.py +541 -0
- deepagents_code/client/commands/config.py +714 -0
- deepagents_code/client/commands/mcp.py +250 -0
- deepagents_code/client/commands/tools.py +416 -0
- deepagents_code/client/launch/__init__.py +1 -0
- deepagents_code/client/launch/server.py +978 -0
- deepagents_code/client/launch/server_manager.py +540 -0
- deepagents_code/client/non_interactive.py +1758 -0
- deepagents_code/client/remote_client.py +794 -0
- deepagents_code/clipboard.py +217 -0
- deepagents_code/command_registry.py +450 -0
- deepagents_code/config.py +4829 -0
- deepagents_code/config_manifest.py +1451 -0
- deepagents_code/configurable_model.py +577 -0
- deepagents_code/default_agent_prompt.md +12 -0
- deepagents_code/doctor.py +559 -0
- deepagents_code/editor.py +142 -0
- deepagents_code/event_bus.py +411 -0
- deepagents_code/extras_info.py +661 -0
- deepagents_code/file_ops.py +576 -0
- deepagents_code/formatting.py +135 -0
- deepagents_code/goal_rubric.py +497 -0
- deepagents_code/goal_tools.py +459 -0
- deepagents_code/hooks.py +348 -0
- deepagents_code/input.py +1041 -0
- deepagents_code/integrations/__init__.py +1 -0
- deepagents_code/integrations/openai_codex.py +551 -0
- deepagents_code/integrations/sandbox_config.py +198 -0
- deepagents_code/integrations/sandbox_factory.py +1124 -0
- deepagents_code/integrations/sandbox_provider.py +137 -0
- deepagents_code/integrations/sandbox_registry.py +350 -0
- deepagents_code/iterm_cursor_guide.py +176 -0
- deepagents_code/local_context.py +926 -0
- deepagents_code/main.py +3985 -0
- deepagents_code/managed_tools.py +642 -0
- deepagents_code/mcp_auth.py +1950 -0
- deepagents_code/mcp_config.py +176 -0
- deepagents_code/mcp_disabled.py +212 -0
- deepagents_code/mcp_login_service.py +281 -0
- deepagents_code/mcp_oauth_ui.py +199 -0
- deepagents_code/mcp_providers/__init__.py +23 -0
- deepagents_code/mcp_providers/_registry.py +39 -0
- deepagents_code/mcp_providers/base.py +133 -0
- deepagents_code/mcp_providers/github.py +102 -0
- deepagents_code/mcp_providers/slack.py +175 -0
- deepagents_code/mcp_tools.py +2427 -0
- deepagents_code/mcp_trust.py +207 -0
- deepagents_code/media_utils.py +826 -0
- deepagents_code/memory_guard.py +474 -0
- deepagents_code/model_config.py +4156 -0
- deepagents_code/notifications.py +247 -0
- deepagents_code/offload.py +402 -0
- deepagents_code/onboarding.py +223 -0
- deepagents_code/output.py +69 -0
- deepagents_code/paste_collapse.py +103 -0
- deepagents_code/project_utils.py +231 -0
- deepagents_code/py.typed +0 -0
- deepagents_code/reasoning_effort.py +641 -0
- deepagents_code/reliable_rubric.py +97 -0
- deepagents_code/resume_state.py +237 -0
- deepagents_code/server_graph.py +310 -0
- deepagents_code/session_end_summary.py +329 -0
- deepagents_code/sessions.py +1716 -0
- deepagents_code/skills/__init__.py +18 -0
- deepagents_code/skills/commands.py +1252 -0
- deepagents_code/skills/invocation.py +112 -0
- deepagents_code/skills/load.py +196 -0
- deepagents_code/skills/trust.py +547 -0
- deepagents_code/state_migration.py +136 -0
- deepagents_code/subagents.py +278 -0
- deepagents_code/system_prompt.md +204 -0
- deepagents_code/terminal_capabilities.py +115 -0
- deepagents_code/terminal_escape.py +287 -0
- deepagents_code/theme.py +891 -0
- deepagents_code/todo_list_prompt.md +12 -0
- deepagents_code/tool_catalog.py +509 -0
- deepagents_code/tool_display.py +367 -0
- deepagents_code/tools.py +516 -0
- deepagents_code/tui/__init__.py +1 -0
- deepagents_code/tui/textual_adapter.py +2553 -0
- deepagents_code/tui/widgets/__init__.py +9 -0
- deepagents_code/tui/widgets/_js_eval_display.py +139 -0
- deepagents_code/tui/widgets/_links.py +261 -0
- deepagents_code/tui/widgets/agent_selector.py +420 -0
- deepagents_code/tui/widgets/approval.py +602 -0
- deepagents_code/tui/widgets/ask_user.py +515 -0
- deepagents_code/tui/widgets/auth.py +1997 -0
- deepagents_code/tui/widgets/autocomplete.py +890 -0
- deepagents_code/tui/widgets/chat_input.py +3181 -0
- deepagents_code/tui/widgets/codex_auth.py +452 -0
- deepagents_code/tui/widgets/cwd_switch.py +242 -0
- deepagents_code/tui/widgets/debug_console.py +868 -0
- deepagents_code/tui/widgets/diff.py +252 -0
- deepagents_code/tui/widgets/effort_selector.py +189 -0
- deepagents_code/tui/widgets/goal_review.py +390 -0
- deepagents_code/tui/widgets/goal_status.py +50 -0
- deepagents_code/tui/widgets/history.py +194 -0
- deepagents_code/tui/widgets/install_confirm.py +248 -0
- deepagents_code/tui/widgets/launch_init.py +482 -0
- deepagents_code/tui/widgets/loading.py +227 -0
- deepagents_code/tui/widgets/mcp_login.py +539 -0
- deepagents_code/tui/widgets/mcp_reconnect.py +212 -0
- deepagents_code/tui/widgets/mcp_viewer.py +1634 -0
- deepagents_code/tui/widgets/message_store.py +999 -0
- deepagents_code/tui/widgets/messages.py +3846 -0
- deepagents_code/tui/widgets/model_selector.py +2343 -0
- deepagents_code/tui/widgets/notification_center.py +456 -0
- deepagents_code/tui/widgets/notification_detail.py +257 -0
- deepagents_code/tui/widgets/notification_settings.py +170 -0
- deepagents_code/tui/widgets/restart_prompt.py +158 -0
- deepagents_code/tui/widgets/skill_trust.py +131 -0
- deepagents_code/tui/widgets/startup_tip.py +91 -0
- deepagents_code/tui/widgets/status.py +781 -0
- deepagents_code/tui/widgets/subagent_panel.py +969 -0
- deepagents_code/tui/widgets/theme_selector.py +401 -0
- deepagents_code/tui/widgets/thread_selector.py +2564 -0
- deepagents_code/tui/widgets/tool_renderers.py +190 -0
- deepagents_code/tui/widgets/tool_widgets.py +304 -0
- deepagents_code/tui/widgets/update_available.py +311 -0
- deepagents_code/tui/widgets/update_confirm.py +184 -0
- deepagents_code/tui/widgets/update_progress.py +327 -0
- deepagents_code/tui/widgets/welcome.py +508 -0
- deepagents_code/turn_end_summary.py +522 -0
- deepagents_code/ui.py +858 -0
- deepagents_code/unicode_security.py +563 -0
- deepagents_code/update_check.py +3124 -0
- zjcode-0.0.1.data/data/deepagents_code/default_agent_prompt.md +12 -0
- zjcode-0.0.1.dist-info/METADATA +220 -0
- zjcode-0.0.1.dist-info/RECORD +163 -0
- zjcode-0.0.1.dist-info/WHEEL +4 -0
- 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)
|