batrachian-toad 0.5.22__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.
- batrachian_toad-0.5.22.dist-info/METADATA +197 -0
- batrachian_toad-0.5.22.dist-info/RECORD +120 -0
- batrachian_toad-0.5.22.dist-info/WHEEL +4 -0
- batrachian_toad-0.5.22.dist-info/entry_points.txt +2 -0
- batrachian_toad-0.5.22.dist-info/licenses/LICENSE +661 -0
- toad/__init__.py +46 -0
- toad/__main__.py +4 -0
- toad/_loop.py +86 -0
- toad/about.py +90 -0
- toad/acp/agent.py +671 -0
- toad/acp/api.py +47 -0
- toad/acp/encode_tool_call_id.py +12 -0
- toad/acp/messages.py +138 -0
- toad/acp/prompt.py +54 -0
- toad/acp/protocol.py +426 -0
- toad/agent.py +62 -0
- toad/agent_schema.py +70 -0
- toad/agents.py +45 -0
- toad/ansi/__init__.py +1 -0
- toad/ansi/_ansi.py +1612 -0
- toad/ansi/_ansi_colors.py +264 -0
- toad/ansi/_control_codes.py +37 -0
- toad/ansi/_keys.py +251 -0
- toad/ansi/_sgr_styles.py +64 -0
- toad/ansi/_stream_parser.py +418 -0
- toad/answer.py +22 -0
- toad/app.py +557 -0
- toad/atomic.py +37 -0
- toad/cli.py +257 -0
- toad/code_analyze.py +28 -0
- toad/complete.py +34 -0
- toad/constants.py +58 -0
- toad/conversation_markdown.py +19 -0
- toad/danger.py +371 -0
- toad/data/agents/ampcode.com.toml +51 -0
- toad/data/agents/augmentcode.com.toml +40 -0
- toad/data/agents/claude.com.toml +41 -0
- toad/data/agents/docker.com.toml +59 -0
- toad/data/agents/geminicli.com.toml +28 -0
- toad/data/agents/goose.ai.toml +51 -0
- toad/data/agents/inference.huggingface.co.toml +33 -0
- toad/data/agents/kimi.com.toml +35 -0
- toad/data/agents/openai.com.toml +53 -0
- toad/data/agents/opencode.ai.toml +61 -0
- toad/data/agents/openhands.dev.toml +44 -0
- toad/data/agents/stakpak.dev.toml +61 -0
- toad/data/agents/vibe.mistral.ai.toml +27 -0
- toad/data/agents/vtcode.dev.toml +62 -0
- toad/data/images/frog.png +0 -0
- toad/data/sounds/turn-over.wav +0 -0
- toad/db.py +5 -0
- toad/dec.py +332 -0
- toad/directory.py +234 -0
- toad/directory_watcher.py +96 -0
- toad/fuzzy.py +140 -0
- toad/gist.py +2 -0
- toad/history.py +138 -0
- toad/jsonrpc.py +576 -0
- toad/menus.py +14 -0
- toad/messages.py +74 -0
- toad/option_content.py +51 -0
- toad/os.py +0 -0
- toad/path_complete.py +145 -0
- toad/path_filter.py +124 -0
- toad/paths.py +71 -0
- toad/pill.py +23 -0
- toad/prompt/extract.py +19 -0
- toad/prompt/resource.py +68 -0
- toad/protocol.py +28 -0
- toad/screens/action_modal.py +94 -0
- toad/screens/agent_modal.py +172 -0
- toad/screens/command_edit_modal.py +58 -0
- toad/screens/main.py +192 -0
- toad/screens/permissions.py +390 -0
- toad/screens/permissions.tcss +72 -0
- toad/screens/settings.py +254 -0
- toad/screens/settings.tcss +101 -0
- toad/screens/store.py +476 -0
- toad/screens/store.tcss +261 -0
- toad/settings.py +354 -0
- toad/settings_schema.py +318 -0
- toad/shell.py +263 -0
- toad/shell_read.py +42 -0
- toad/slash_command.py +34 -0
- toad/toad.tcss +752 -0
- toad/version.py +80 -0
- toad/visuals/columns.py +273 -0
- toad/widgets/agent_response.py +79 -0
- toad/widgets/agent_thought.py +41 -0
- toad/widgets/command_pane.py +224 -0
- toad/widgets/condensed_path.py +93 -0
- toad/widgets/conversation.py +1626 -0
- toad/widgets/danger_warning.py +65 -0
- toad/widgets/diff_view.py +709 -0
- toad/widgets/flash.py +81 -0
- toad/widgets/future_text.py +126 -0
- toad/widgets/grid_select.py +223 -0
- toad/widgets/highlighted_textarea.py +180 -0
- toad/widgets/mandelbrot.py +294 -0
- toad/widgets/markdown_note.py +13 -0
- toad/widgets/menu.py +147 -0
- toad/widgets/non_selectable_label.py +5 -0
- toad/widgets/note.py +18 -0
- toad/widgets/path_search.py +381 -0
- toad/widgets/plan.py +180 -0
- toad/widgets/project_directory_tree.py +74 -0
- toad/widgets/prompt.py +741 -0
- toad/widgets/question.py +337 -0
- toad/widgets/shell_result.py +35 -0
- toad/widgets/shell_terminal.py +18 -0
- toad/widgets/side_bar.py +74 -0
- toad/widgets/slash_complete.py +211 -0
- toad/widgets/strike_text.py +66 -0
- toad/widgets/terminal.py +526 -0
- toad/widgets/terminal_tool.py +338 -0
- toad/widgets/throbber.py +90 -0
- toad/widgets/tool_call.py +303 -0
- toad/widgets/user_input.py +23 -0
- toad/widgets/version.py +5 -0
- toad/widgets/welcome.py +31 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from typing import cast
|
|
2
|
+
|
|
3
|
+
from textual import on
|
|
4
|
+
from textual import getters
|
|
5
|
+
from textual.app import ComposeResult
|
|
6
|
+
|
|
7
|
+
from textual import work
|
|
8
|
+
from textual.screen import ModalScreen
|
|
9
|
+
from textual import containers
|
|
10
|
+
from textual import widgets
|
|
11
|
+
from textual.reactive import var
|
|
12
|
+
|
|
13
|
+
import toad
|
|
14
|
+
from textual.binding import Binding
|
|
15
|
+
from toad.agent_schema import Action, Agent, OS, Command
|
|
16
|
+
from toad.app import ToadApp
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DescriptionContainer(containers.VerticalScroll):
|
|
20
|
+
def allow_focus(self) -> bool:
|
|
21
|
+
"""Focus only if it can be scrolled."""
|
|
22
|
+
return self.show_vertical_scrollbar
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AgentModal(ModalScreen):
|
|
26
|
+
AUTO_FOCUS = "Select#action-select"
|
|
27
|
+
|
|
28
|
+
BINDINGS = [
|
|
29
|
+
Binding("escape", "dismiss(None)", "Dismiss", show=False),
|
|
30
|
+
Binding("space", "dismiss('launch')", "Launch agent", priority=True),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
action = var("")
|
|
34
|
+
|
|
35
|
+
app = getters.app(ToadApp)
|
|
36
|
+
action_select = getters.query_one("#action-select", widgets.Select)
|
|
37
|
+
launcher_checkbox = getters.query_one("#launcher-checkbox", widgets.Checkbox)
|
|
38
|
+
|
|
39
|
+
def __init__(self, agent: Agent) -> None:
|
|
40
|
+
self._agent = agent
|
|
41
|
+
super().__init__()
|
|
42
|
+
|
|
43
|
+
def compose(self) -> ComposeResult:
|
|
44
|
+
launcher_set = frozenset(
|
|
45
|
+
self.app.settings.get("launcher.agents", str).splitlines()
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
agent = self._agent
|
|
49
|
+
|
|
50
|
+
app = self.app
|
|
51
|
+
launcher_set = frozenset(app.settings.get("launcher.agents", str).splitlines())
|
|
52
|
+
agent = self._agent
|
|
53
|
+
actions = agent["actions"]
|
|
54
|
+
|
|
55
|
+
script_os = cast(OS, toad.os)
|
|
56
|
+
if script_os not in actions:
|
|
57
|
+
script_os = "*"
|
|
58
|
+
|
|
59
|
+
commands: dict[Action, Command] = actions[cast(OS, script_os)]
|
|
60
|
+
script_choices = [
|
|
61
|
+
(action["description"], name) for name, action in commands.items()
|
|
62
|
+
]
|
|
63
|
+
script_choices.append((f"Launch {agent['name']}", "__launch__"))
|
|
64
|
+
|
|
65
|
+
with containers.Vertical(id="container"):
|
|
66
|
+
with DescriptionContainer(id="description-container"):
|
|
67
|
+
yield widgets.Markdown(agent["help"], id="description")
|
|
68
|
+
with containers.VerticalGroup():
|
|
69
|
+
if "install_acp" in commands:
|
|
70
|
+
yield widgets.Static(
|
|
71
|
+
f"{agent['name']} requires an ACP adapter to work with Toad. Install from the actions list.",
|
|
72
|
+
classes="acp-warning",
|
|
73
|
+
)
|
|
74
|
+
with containers.HorizontalGroup():
|
|
75
|
+
yield widgets.Checkbox(
|
|
76
|
+
"Show in launcher",
|
|
77
|
+
value=agent["identity"] in launcher_set,
|
|
78
|
+
id="launcher-checkbox",
|
|
79
|
+
)
|
|
80
|
+
yield widgets.Select(
|
|
81
|
+
script_choices,
|
|
82
|
+
prompt="Actions",
|
|
83
|
+
allow_blank=True,
|
|
84
|
+
id="action-select",
|
|
85
|
+
)
|
|
86
|
+
yield widgets.Button(
|
|
87
|
+
"Go", variant="primary", id="run-action", disabled=True
|
|
88
|
+
)
|
|
89
|
+
yield widgets.Footer()
|
|
90
|
+
|
|
91
|
+
def on_mount(self) -> None:
|
|
92
|
+
self.query_one("Footer").styles.animate("opacity", 1.0, duration=500 / 1000)
|
|
93
|
+
|
|
94
|
+
@on(widgets.Checkbox.Changed)
|
|
95
|
+
def on_checkbox_changed(self, event: widgets.Select.Changed) -> None:
|
|
96
|
+
launcher_agents = self.app.settings.get("launcher.agents", str).splitlines()
|
|
97
|
+
agent_identity = self._agent["identity"]
|
|
98
|
+
if agent_identity in launcher_agents:
|
|
99
|
+
launcher_agents.remove(agent_identity)
|
|
100
|
+
if event.value:
|
|
101
|
+
launcher_agents.insert(0, agent_identity)
|
|
102
|
+
self.app.settings.set("launcher.agents", "\n".join(launcher_agents))
|
|
103
|
+
|
|
104
|
+
@on(widgets.Select.Changed)
|
|
105
|
+
def on_select_changed(self, event: widgets.Select.Changed) -> None:
|
|
106
|
+
self.action = event.value if isinstance(event.value, str) else ""
|
|
107
|
+
|
|
108
|
+
@work
|
|
109
|
+
@on(widgets.Button.Pressed)
|
|
110
|
+
async def on_button_pressed(self) -> None:
|
|
111
|
+
agent = self._agent
|
|
112
|
+
action = self.action_select.value
|
|
113
|
+
|
|
114
|
+
assert isinstance(action, str)
|
|
115
|
+
if action == "__launch__":
|
|
116
|
+
self.dismiss("launch")
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
agent_actions = self._agent["actions"]
|
|
120
|
+
|
|
121
|
+
if (commands := agent_actions.get(toad.os, None)) is None:
|
|
122
|
+
commands = agent_actions.get("*", None)
|
|
123
|
+
if commands is None:
|
|
124
|
+
self.notify(
|
|
125
|
+
"Action is not available on this platform",
|
|
126
|
+
title="Agent action",
|
|
127
|
+
severity="error",
|
|
128
|
+
)
|
|
129
|
+
return
|
|
130
|
+
command = commands[action]
|
|
131
|
+
|
|
132
|
+
from toad.screens.action_modal import ActionModal
|
|
133
|
+
from toad.screens.command_edit_modal import CommandEditModal
|
|
134
|
+
|
|
135
|
+
title = command["description"]
|
|
136
|
+
agent_id = self._agent["identity"]
|
|
137
|
+
action_command = command["command"]
|
|
138
|
+
bootstrap_uv = command.get("bootstrap_uv", False)
|
|
139
|
+
|
|
140
|
+
agent = self._agent
|
|
141
|
+
# Focus the select
|
|
142
|
+
# It's unlikely the user wants to re-run the action
|
|
143
|
+
self.action_select.focus()
|
|
144
|
+
|
|
145
|
+
action_command = await self.app.push_screen_wait(
|
|
146
|
+
CommandEditModal(action_command)
|
|
147
|
+
)
|
|
148
|
+
if action_command is None:
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
return_code = await self.app.push_screen_wait(
|
|
152
|
+
ActionModal(
|
|
153
|
+
action,
|
|
154
|
+
agent_id,
|
|
155
|
+
title,
|
|
156
|
+
action_command,
|
|
157
|
+
bootstrap_uv=bootstrap_uv,
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
if return_code == 0 and action in {"install", "install-acp"}:
|
|
161
|
+
# Add to launcher if we installed something
|
|
162
|
+
if not self.launcher_checkbox.value:
|
|
163
|
+
self.notify(
|
|
164
|
+
f"{agent['name']} has been added to your launcher",
|
|
165
|
+
title="Add agent",
|
|
166
|
+
severity="information",
|
|
167
|
+
)
|
|
168
|
+
self.launcher_checkbox.value = True
|
|
169
|
+
|
|
170
|
+
def watch_action(self, action: str) -> None:
|
|
171
|
+
go_button = self.query_one("#run-action", widgets.Button)
|
|
172
|
+
go_button.disabled = not action
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from textual import on, work
|
|
2
|
+
from textual.app import ComposeResult
|
|
3
|
+
from textual.screen import ModalScreen
|
|
4
|
+
from textual import widgets
|
|
5
|
+
from textual import containers
|
|
6
|
+
from textual import getters
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CommandEditModal(ModalScreen[str | None]):
|
|
10
|
+
BINDINGS = [("escape", "dismiss", "Dismiss")]
|
|
11
|
+
|
|
12
|
+
text_area = getters.query_one(widgets.TextArea)
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
command: str,
|
|
17
|
+
name: str | None = None,
|
|
18
|
+
id: str | None = None,
|
|
19
|
+
classes: str | None = None,
|
|
20
|
+
):
|
|
21
|
+
self.command = command
|
|
22
|
+
super().__init__(name=name, id=id, classes=classes)
|
|
23
|
+
|
|
24
|
+
def compose(self) -> ComposeResult:
|
|
25
|
+
with containers.VerticalGroup(id="container"):
|
|
26
|
+
yield widgets.Static(
|
|
27
|
+
"Toad will run the following command(s).\n\nEdit if you want to.",
|
|
28
|
+
classes="instructions",
|
|
29
|
+
)
|
|
30
|
+
yield widgets.TextArea(
|
|
31
|
+
self.command, language="bash", highlight_cursor_line=False
|
|
32
|
+
)
|
|
33
|
+
with containers.HorizontalGroup(id="button-container"):
|
|
34
|
+
yield widgets.Button("OK", variant="primary", id="ok")
|
|
35
|
+
yield widgets.Button(
|
|
36
|
+
"Cancel", id="cancel", action="screen.dismiss(None)"
|
|
37
|
+
)
|
|
38
|
+
yield widgets.Footer()
|
|
39
|
+
|
|
40
|
+
@on(widgets.Button.Pressed, "#ok")
|
|
41
|
+
def on_ok_pressed(self, event: widgets.Button.Pressed) -> None:
|
|
42
|
+
self.dismiss(self.text_area.text)
|
|
43
|
+
|
|
44
|
+
@on(widgets.Button.Pressed, "#cancel")
|
|
45
|
+
def on_cancel_pressed(self, event: widgets.Button.Pressed) -> None:
|
|
46
|
+
self.dismiss(None)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
from textual.app import App
|
|
51
|
+
|
|
52
|
+
class ModalApp(App):
|
|
53
|
+
@work
|
|
54
|
+
async def on_mount(self) -> None:
|
|
55
|
+
result = await self.push_screen_wait(CommandEditModal("ls -al"))
|
|
56
|
+
self.notify(str(result))
|
|
57
|
+
|
|
58
|
+
ModalApp().run()
|
toad/screens/main.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
from functools import partial
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import random
|
|
4
|
+
|
|
5
|
+
from textual import on
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual import getters
|
|
8
|
+
from textual.binding import Binding
|
|
9
|
+
from textual.command import Hit, Hits, Provider, DiscoveryHit
|
|
10
|
+
from textual.content import Content
|
|
11
|
+
from textual.screen import Screen
|
|
12
|
+
from textual.reactive import var, reactive
|
|
13
|
+
from textual.widgets import Footer, OptionList, DirectoryTree, Tree
|
|
14
|
+
from textual import containers
|
|
15
|
+
from textual.widget import Widget
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from toad.app import ToadApp
|
|
19
|
+
from toad import messages
|
|
20
|
+
from toad.agent_schema import Agent
|
|
21
|
+
from toad.acp import messages as acp_messages
|
|
22
|
+
from toad.widgets.plan import Plan
|
|
23
|
+
from toad.widgets.throbber import Throbber
|
|
24
|
+
from toad.widgets.conversation import Conversation
|
|
25
|
+
from toad.widgets.project_directory_tree import ProjectDirectoryTree
|
|
26
|
+
from toad.widgets.side_bar import SideBar
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ModeProvider(Provider):
|
|
30
|
+
async def search(self, query: str) -> Hits:
|
|
31
|
+
"""Search for Python files."""
|
|
32
|
+
matcher = self.matcher(query)
|
|
33
|
+
|
|
34
|
+
screen = self.screen
|
|
35
|
+
assert isinstance(screen, MainScreen)
|
|
36
|
+
|
|
37
|
+
for mode in sorted(
|
|
38
|
+
screen.conversation.modes.values(), key=lambda mode: mode.name
|
|
39
|
+
):
|
|
40
|
+
command = mode.name
|
|
41
|
+
score = matcher.match(command)
|
|
42
|
+
if score > 0:
|
|
43
|
+
yield Hit(
|
|
44
|
+
score,
|
|
45
|
+
matcher.highlight(command),
|
|
46
|
+
partial(screen.conversation.set_mode, mode.id),
|
|
47
|
+
help=mode.description,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
async def discover(self) -> Hits:
|
|
51
|
+
screen = self.screen
|
|
52
|
+
assert isinstance(screen, MainScreen)
|
|
53
|
+
|
|
54
|
+
for mode in sorted(
|
|
55
|
+
screen.conversation.modes.values(), key=lambda mode: mode.name
|
|
56
|
+
):
|
|
57
|
+
yield DiscoveryHit(
|
|
58
|
+
mode.name,
|
|
59
|
+
partial(screen.conversation.set_mode, mode.id),
|
|
60
|
+
help=mode.description,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class MainScreen(Screen, can_focus=False):
|
|
65
|
+
AUTO_FOCUS = "Conversation Prompt TextArea"
|
|
66
|
+
|
|
67
|
+
COMMANDS = {ModeProvider}
|
|
68
|
+
BINDINGS = [
|
|
69
|
+
Binding("f3", "show_sidebar", "Sidebar"),
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
BINDING_GROUP_TITLE = "Screen"
|
|
73
|
+
busy_count = var(0)
|
|
74
|
+
throbber: getters.query_one[Throbber] = getters.query_one("#throbber")
|
|
75
|
+
conversation = getters.query_one(Conversation)
|
|
76
|
+
side_bar = getters.query_one(SideBar)
|
|
77
|
+
project_directory_tree = getters.query_one("#project_directory_tree")
|
|
78
|
+
|
|
79
|
+
column = reactive(False)
|
|
80
|
+
column_width = reactive(100)
|
|
81
|
+
scrollbar = reactive("")
|
|
82
|
+
project_path: var[Path] = var(Path("./").expanduser().absolute())
|
|
83
|
+
|
|
84
|
+
app = getters.app(ToadApp)
|
|
85
|
+
|
|
86
|
+
def __init__(self, project_path: Path, agent: Agent | None = None) -> None:
|
|
87
|
+
super().__init__()
|
|
88
|
+
self.set_reactive(MainScreen.project_path, project_path)
|
|
89
|
+
self._agent = agent
|
|
90
|
+
|
|
91
|
+
def get_loading_widget(self) -> Widget:
|
|
92
|
+
throbber = self.app.settings.get("ui.throbber", str)
|
|
93
|
+
if throbber == "quotes":
|
|
94
|
+
from toad.app import QUOTES
|
|
95
|
+
from toad.widgets.future_text import FutureText
|
|
96
|
+
|
|
97
|
+
quotes = QUOTES.copy()
|
|
98
|
+
random.shuffle(quotes)
|
|
99
|
+
return FutureText([Content(quote) for quote in quotes])
|
|
100
|
+
return super().get_loading_widget()
|
|
101
|
+
|
|
102
|
+
def compose(self) -> ComposeResult:
|
|
103
|
+
with containers.Center():
|
|
104
|
+
yield SideBar(
|
|
105
|
+
SideBar.Panel("Plan", Plan([])),
|
|
106
|
+
SideBar.Panel(
|
|
107
|
+
"Project",
|
|
108
|
+
ProjectDirectoryTree(
|
|
109
|
+
self.project_path,
|
|
110
|
+
id="project_directory_tree",
|
|
111
|
+
),
|
|
112
|
+
flex=True,
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
yield Conversation(self.project_path, self._agent).data_bind(
|
|
116
|
+
project_path=MainScreen.project_path,
|
|
117
|
+
column=MainScreen.column,
|
|
118
|
+
)
|
|
119
|
+
yield Footer()
|
|
120
|
+
|
|
121
|
+
def update_node_styles(self, animate: bool = True) -> None:
|
|
122
|
+
self.conversation.update_node_styles(animate=animate)
|
|
123
|
+
self.query_one(Footer).update_node_styles(animate=animate)
|
|
124
|
+
self.query_one(SideBar).update_node_styles(animate=animate)
|
|
125
|
+
|
|
126
|
+
@on(messages.ProjectDirectoryUpdated)
|
|
127
|
+
async def on_project_directory_update(self) -> None:
|
|
128
|
+
await self.query_one(ProjectDirectoryTree).reload()
|
|
129
|
+
|
|
130
|
+
@on(DirectoryTree.FileSelected, "ProjectDirectoryTree")
|
|
131
|
+
def on_project_directory_tree_selected(self, event: Tree.NodeSelected):
|
|
132
|
+
if (data := event.node.data) is not None:
|
|
133
|
+
self.conversation.insert_path_into_prompt(data.path)
|
|
134
|
+
|
|
135
|
+
@on(acp_messages.Plan)
|
|
136
|
+
async def on_acp_plan(self, message: acp_messages.Plan):
|
|
137
|
+
message.stop()
|
|
138
|
+
entries = [
|
|
139
|
+
Plan.Entry(
|
|
140
|
+
Content(entry["content"]),
|
|
141
|
+
entry.get("priority", "medium"),
|
|
142
|
+
entry.get("status", "pending"),
|
|
143
|
+
)
|
|
144
|
+
for entry in message.entries
|
|
145
|
+
]
|
|
146
|
+
self.query_one("SideBar Plan", Plan).entries = entries
|
|
147
|
+
|
|
148
|
+
def on_mount(self) -> None:
|
|
149
|
+
for tree in self.query("#project_directory_tree").results(DirectoryTree):
|
|
150
|
+
tree.data_bind(path=MainScreen.project_path)
|
|
151
|
+
for tree in self.query(DirectoryTree):
|
|
152
|
+
# tree.show_guides = False
|
|
153
|
+
tree.guide_depth = 3
|
|
154
|
+
|
|
155
|
+
@on(OptionList.OptionHighlighted)
|
|
156
|
+
def on_option_list_option_highlighted(
|
|
157
|
+
self, event: OptionList.OptionHighlighted
|
|
158
|
+
) -> None:
|
|
159
|
+
if event.option.id is not None:
|
|
160
|
+
self.conversation.prompt.suggest(event.option.id)
|
|
161
|
+
|
|
162
|
+
def check_action(self, action: str, parameters: tuple[object, ...]) -> bool | None:
|
|
163
|
+
if action == "show_sidebar" and self.side_bar.has_focus_within:
|
|
164
|
+
return False
|
|
165
|
+
return True
|
|
166
|
+
|
|
167
|
+
def action_show_sidebar(self) -> None:
|
|
168
|
+
self.side_bar.query_one("Collapsible CollapsibleTitle").focus()
|
|
169
|
+
|
|
170
|
+
def action_focus_prompt(self) -> None:
|
|
171
|
+
self.conversation.focus_prompt()
|
|
172
|
+
|
|
173
|
+
@on(SideBar.Dismiss)
|
|
174
|
+
def on_side_bar_dismiss(self, message: SideBar.Dismiss):
|
|
175
|
+
message.stop()
|
|
176
|
+
self.conversation.focus_prompt()
|
|
177
|
+
|
|
178
|
+
def watch_column(self, column: bool) -> None:
|
|
179
|
+
self.conversation.styles.max_width = (
|
|
180
|
+
max(10, self.column_width) if column else None
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def watch_column_width(self, column_width: int) -> None:
|
|
184
|
+
self.conversation.styles.max_width = (
|
|
185
|
+
max(10, column_width) if self.column else None
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def watch_scrollbar(self, old_scrollbar: str, scrollbar: str) -> None:
|
|
189
|
+
if old_scrollbar:
|
|
190
|
+
self.conversation.remove_class(f"-scrollbar-{old_scrollbar}")
|
|
191
|
+
if scrollbar:
|
|
192
|
+
self.conversation.add_class(f"-scrollbar-{scrollbar}")
|