fast-resume 1.12.8__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.
- fast_resume/__init__.py +5 -0
- fast_resume/adapters/__init__.py +25 -0
- fast_resume/adapters/base.py +263 -0
- fast_resume/adapters/claude.py +209 -0
- fast_resume/adapters/codex.py +216 -0
- fast_resume/adapters/copilot.py +176 -0
- fast_resume/adapters/copilot_vscode.py +326 -0
- fast_resume/adapters/crush.py +341 -0
- fast_resume/adapters/opencode.py +333 -0
- fast_resume/adapters/vibe.py +188 -0
- fast_resume/assets/claude.png +0 -0
- fast_resume/assets/codex.png +0 -0
- fast_resume/assets/copilot-cli.png +0 -0
- fast_resume/assets/copilot-vscode.png +0 -0
- fast_resume/assets/crush.png +0 -0
- fast_resume/assets/opencode.png +0 -0
- fast_resume/assets/vibe.png +0 -0
- fast_resume/cli.py +327 -0
- fast_resume/config.py +30 -0
- fast_resume/index.py +758 -0
- fast_resume/logging_config.py +57 -0
- fast_resume/query.py +264 -0
- fast_resume/search.py +281 -0
- fast_resume/tui/__init__.py +58 -0
- fast_resume/tui/app.py +629 -0
- fast_resume/tui/filter_bar.py +128 -0
- fast_resume/tui/modal.py +73 -0
- fast_resume/tui/preview.py +396 -0
- fast_resume/tui/query.py +86 -0
- fast_resume/tui/results_table.py +178 -0
- fast_resume/tui/search_input.py +117 -0
- fast_resume/tui/styles.py +302 -0
- fast_resume/tui/utils.py +160 -0
- fast_resume-1.12.8.dist-info/METADATA +545 -0
- fast_resume-1.12.8.dist-info/RECORD +38 -0
- fast_resume-1.12.8.dist-info/WHEEL +4 -0
- fast_resume-1.12.8.dist-info/entry_points.txt +3 -0
- fast_resume-1.12.8.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Results table widget for displaying sessions."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from rich.text import Text
|
|
6
|
+
from textual.message import Message
|
|
7
|
+
from textual.widgets import DataTable
|
|
8
|
+
|
|
9
|
+
from ..adapters.base import Session
|
|
10
|
+
from .utils import (
|
|
11
|
+
format_directory,
|
|
12
|
+
format_time_ago,
|
|
13
|
+
get_age_color,
|
|
14
|
+
get_agent_icon,
|
|
15
|
+
highlight_matches,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Column width breakpoints: (min_width, agent, dir, msgs, date)
|
|
20
|
+
_COL_WIDTHS = [
|
|
21
|
+
(120, 12, 30, 6, 18), # Wide
|
|
22
|
+
(90, 12, 22, 5, 15), # Medium
|
|
23
|
+
(60, 12, 16, 5, 12), # Narrow
|
|
24
|
+
(0, 11, 0, 4, 10), # Very narrow (hide directory)
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ResultsTable(DataTable):
|
|
29
|
+
"""A data table for displaying session results.
|
|
30
|
+
|
|
31
|
+
Handles responsive column sizing and session rendering.
|
|
32
|
+
Emits ResultsTable.Selected when a row is highlighted.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
class Selected(Message):
|
|
36
|
+
"""Posted when a session is selected."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, session: Session | None) -> None:
|
|
39
|
+
self.session = session
|
|
40
|
+
super().__init__()
|
|
41
|
+
|
|
42
|
+
def __init__(self, id: str | None = None) -> None:
|
|
43
|
+
super().__init__(
|
|
44
|
+
id=id,
|
|
45
|
+
cursor_type="row",
|
|
46
|
+
cursor_background_priority="renderable",
|
|
47
|
+
cursor_foreground_priority="renderable",
|
|
48
|
+
)
|
|
49
|
+
self._displayed_sessions: list[Session] = []
|
|
50
|
+
self._title_width: int = 60
|
|
51
|
+
self._dir_width: int = 22
|
|
52
|
+
self._current_query: str = ""
|
|
53
|
+
|
|
54
|
+
def on_mount(self) -> None:
|
|
55
|
+
"""Set up table columns on mount."""
|
|
56
|
+
(
|
|
57
|
+
self._col_agent,
|
|
58
|
+
self._col_title,
|
|
59
|
+
self._col_dir,
|
|
60
|
+
self._col_msgs,
|
|
61
|
+
self._col_date,
|
|
62
|
+
) = self.add_columns("Agent", "Title", "Directory", "Turns", "Date")
|
|
63
|
+
self._update_responsive_widths()
|
|
64
|
+
|
|
65
|
+
def on_resize(self) -> None:
|
|
66
|
+
"""Handle resize events."""
|
|
67
|
+
if hasattr(self, "_col_agent"):
|
|
68
|
+
self._update_responsive_widths()
|
|
69
|
+
if self._displayed_sessions:
|
|
70
|
+
self._render_sessions()
|
|
71
|
+
|
|
72
|
+
def _update_responsive_widths(self) -> None:
|
|
73
|
+
"""Update column widths based on container size."""
|
|
74
|
+
width = self.size.width
|
|
75
|
+
if width == 0:
|
|
76
|
+
# Not yet laid out, use reasonable defaults
|
|
77
|
+
width = 120
|
|
78
|
+
|
|
79
|
+
# Find appropriate breakpoint
|
|
80
|
+
agent_w, dir_w, msgs_w, date_w = next(
|
|
81
|
+
(a, d, m, t) for min_w, a, d, m, t in _COL_WIDTHS if width >= min_w
|
|
82
|
+
)
|
|
83
|
+
title_w = max(15, width - agent_w - dir_w - msgs_w - date_w - 8)
|
|
84
|
+
|
|
85
|
+
for col in self.columns.values():
|
|
86
|
+
col.auto_width = False
|
|
87
|
+
self.columns[self._col_agent].width = agent_w
|
|
88
|
+
self.columns[self._col_title].width = title_w
|
|
89
|
+
self.columns[self._col_dir].width = dir_w
|
|
90
|
+
self.columns[self._col_msgs].width = msgs_w
|
|
91
|
+
self.columns[self._col_date].width = date_w
|
|
92
|
+
|
|
93
|
+
self._title_width, self._dir_width = title_w, dir_w
|
|
94
|
+
self.refresh()
|
|
95
|
+
|
|
96
|
+
def update_sessions(
|
|
97
|
+
self, sessions: list[Session], query: str = ""
|
|
98
|
+
) -> Session | None:
|
|
99
|
+
"""Update the table with new sessions.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
sessions: List of sessions to display.
|
|
103
|
+
query: Current search query for highlighting matches.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
The selected session (first one), or None if no sessions.
|
|
107
|
+
"""
|
|
108
|
+
self._displayed_sessions = sessions
|
|
109
|
+
self._current_query = query
|
|
110
|
+
self._render_sessions()
|
|
111
|
+
|
|
112
|
+
if sessions:
|
|
113
|
+
self.move_cursor(row=0)
|
|
114
|
+
return sessions[0]
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
def _render_sessions(self) -> None:
|
|
118
|
+
"""Render sessions to the table."""
|
|
119
|
+
self.clear()
|
|
120
|
+
|
|
121
|
+
if not self._displayed_sessions:
|
|
122
|
+
self.add_row(
|
|
123
|
+
"",
|
|
124
|
+
Text("No sessions found", style="dim italic"),
|
|
125
|
+
"",
|
|
126
|
+
"",
|
|
127
|
+
"",
|
|
128
|
+
)
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
for session in self._displayed_sessions:
|
|
132
|
+
# Get agent icon (image or text fallback)
|
|
133
|
+
icon = get_agent_icon(session.agent)
|
|
134
|
+
|
|
135
|
+
# Title - truncate and highlight matches
|
|
136
|
+
title = highlight_matches(
|
|
137
|
+
session.title, self._current_query, max_len=self._title_width
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Format directory - truncate based on column width
|
|
141
|
+
dir_w = self._dir_width
|
|
142
|
+
directory = format_directory(session.directory)
|
|
143
|
+
if dir_w > 0 and len(directory) > dir_w:
|
|
144
|
+
directory = "..." + directory[-(dir_w - 3) :]
|
|
145
|
+
dir_text = (
|
|
146
|
+
highlight_matches(directory, self._current_query)
|
|
147
|
+
if dir_w > 0
|
|
148
|
+
else Text("")
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Format message count
|
|
152
|
+
msgs_text = str(session.message_count) if session.message_count > 0 else "-"
|
|
153
|
+
|
|
154
|
+
# Format time with age-based gradient coloring
|
|
155
|
+
time_ago = format_time_ago(session.timestamp)
|
|
156
|
+
time_text = Text(time_ago.rjust(8))
|
|
157
|
+
age_hours = (datetime.now() - session.timestamp).total_seconds() / 3600
|
|
158
|
+
time_text.stylize(get_age_color(age_hours))
|
|
159
|
+
|
|
160
|
+
self.add_row(icon, title, dir_text, msgs_text, time_text)
|
|
161
|
+
|
|
162
|
+
def get_selected_session(self) -> Session | None:
|
|
163
|
+
"""Get the currently selected session."""
|
|
164
|
+
if self.cursor_row is not None and self.cursor_row < len(
|
|
165
|
+
self._displayed_sessions
|
|
166
|
+
):
|
|
167
|
+
return self._displayed_sessions[self.cursor_row]
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
def on_data_table_row_highlighted(self, event: DataTable.RowHighlighted) -> None:
|
|
171
|
+
"""Handle row highlight and emit Selected message."""
|
|
172
|
+
session = self.get_selected_session()
|
|
173
|
+
self.post_message(self.Selected(session))
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def displayed_sessions(self) -> list[Session]:
|
|
177
|
+
"""Get the list of displayed sessions."""
|
|
178
|
+
return self._displayed_sessions
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Search input components: highlighter and suggester for keyword syntax."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from rich.highlighter import Highlighter
|
|
6
|
+
from rich.text import Text
|
|
7
|
+
from textual.suggester import Suggester
|
|
8
|
+
|
|
9
|
+
from ..config import AGENTS
|
|
10
|
+
from .query import KEYWORD_PATTERN, is_valid_filter_value
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class KeywordHighlighter(Highlighter):
|
|
14
|
+
"""Highlighter for search keyword syntax (agent:, dir:, date:).
|
|
15
|
+
|
|
16
|
+
Applies Rich styles directly to keyword prefixes and their values.
|
|
17
|
+
Supports negation with - prefix or ! in value.
|
|
18
|
+
Invalid values are shown in red with strikethrough.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def highlight(self, text: Text) -> None:
|
|
22
|
+
"""Apply highlighting to keyword syntax in the text."""
|
|
23
|
+
plain = text.plain
|
|
24
|
+
for match in KEYWORD_PATTERN.finditer(plain):
|
|
25
|
+
neg_prefix = match.group(1)
|
|
26
|
+
keyword = match.group(2)
|
|
27
|
+
value = match.group(3)
|
|
28
|
+
|
|
29
|
+
is_valid = is_valid_filter_value(keyword, value)
|
|
30
|
+
|
|
31
|
+
# Style the negation prefix in red
|
|
32
|
+
if neg_prefix:
|
|
33
|
+
text.stylize("bold red", match.start(1), match.end(1))
|
|
34
|
+
|
|
35
|
+
# Style the keyword prefix
|
|
36
|
+
if is_valid:
|
|
37
|
+
text.stylize("bold cyan", match.start(2), match.end(2))
|
|
38
|
+
else:
|
|
39
|
+
text.stylize("bold red", match.start(2), match.end(2))
|
|
40
|
+
|
|
41
|
+
# Style the value
|
|
42
|
+
if not is_valid:
|
|
43
|
+
text.stylize("red strike", match.start(3), match.end(3))
|
|
44
|
+
elif value.startswith("!"):
|
|
45
|
+
text.stylize("bold red", match.start(3), match.start(3) + 1)
|
|
46
|
+
text.stylize("green", match.start(3) + 1, match.end(3))
|
|
47
|
+
else:
|
|
48
|
+
text.stylize("green", match.start(3), match.end(3))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Pattern to match partial keyword at end of input for autocomplete
|
|
52
|
+
_PARTIAL_KEYWORD_PATTERN = re.compile(
|
|
53
|
+
r"(-?)(agent:|dir:|date:)([^\s]*)$" # Keyword at end, possibly partial value
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Known values for each keyword type (agent values derived from AGENTS config)
|
|
57
|
+
_KEYWORD_VALUES = {
|
|
58
|
+
"agent:": list(AGENTS.keys()),
|
|
59
|
+
"date:": ["today", "yesterday", "week", "month"],
|
|
60
|
+
# dir: has no predefined values (user-specific paths)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class KeywordSuggester(Suggester):
|
|
65
|
+
"""Suggester for keyword value autocomplete.
|
|
66
|
+
|
|
67
|
+
Provides completions for:
|
|
68
|
+
- agent: values (claude, codex, etc.)
|
|
69
|
+
- date: values (today, yesterday, week, month)
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self) -> None:
|
|
73
|
+
super().__init__(use_cache=True, case_sensitive=False)
|
|
74
|
+
|
|
75
|
+
async def get_suggestion(self, value: str) -> str | None:
|
|
76
|
+
"""Get completion suggestion for the current input.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
value: Current input text (casefolded if case_sensitive=False)
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Complete input with suggested value, or None if no suggestion.
|
|
83
|
+
"""
|
|
84
|
+
# Find partial keyword at end of input
|
|
85
|
+
match = _PARTIAL_KEYWORD_PATTERN.search(value)
|
|
86
|
+
if not match:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
keyword = match.group(2) # agent:, dir:, date:
|
|
90
|
+
partial = match.group(3) # Partial value typed so far
|
|
91
|
+
|
|
92
|
+
# Get known values for this keyword
|
|
93
|
+
known_values = _KEYWORD_VALUES.get(keyword)
|
|
94
|
+
if not known_values:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
# Don't suggest if value is empty (user just typed "agent:")
|
|
98
|
+
if not partial:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
# Handle ! prefix on partial value
|
|
102
|
+
negated_value = partial.startswith("!")
|
|
103
|
+
search_partial = partial[1:] if negated_value else partial
|
|
104
|
+
|
|
105
|
+
# Find first matching value (but not exact match - already complete)
|
|
106
|
+
for candidate in known_values:
|
|
107
|
+
if candidate.lower().startswith(search_partial.lower()):
|
|
108
|
+
# Skip if already complete (no suggestion needed)
|
|
109
|
+
if candidate.lower() == search_partial.lower():
|
|
110
|
+
continue
|
|
111
|
+
# Build the suggestion
|
|
112
|
+
suggested_value = f"!{candidate}" if negated_value else candidate
|
|
113
|
+
# Replace partial with full value
|
|
114
|
+
suggestion = value[: match.start(3)] + suggested_value
|
|
115
|
+
return suggestion
|
|
116
|
+
|
|
117
|
+
return None
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""CSS styles for the TUI components."""
|
|
2
|
+
|
|
3
|
+
# CSS for the YoloModeModal
|
|
4
|
+
YOLO_MODAL_CSS = """
|
|
5
|
+
YoloModeModal {
|
|
6
|
+
align: center middle;
|
|
7
|
+
background: rgba(0, 0, 0, 0.6);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
YoloModeModal > Vertical {
|
|
11
|
+
width: 36;
|
|
12
|
+
height: auto;
|
|
13
|
+
background: $surface;
|
|
14
|
+
border: thick $primary 80%;
|
|
15
|
+
padding: 1 2;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
YoloModeModal #title {
|
|
19
|
+
text-align: center;
|
|
20
|
+
text-style: bold;
|
|
21
|
+
width: 100%;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
YoloModeModal #buttons {
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: auto;
|
|
27
|
+
align: center middle;
|
|
28
|
+
margin-top: 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
YoloModeModal Button {
|
|
32
|
+
margin: 0 1;
|
|
33
|
+
min-width: 10;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
YoloModeModal Button:focus {
|
|
37
|
+
background: $warning;
|
|
38
|
+
}
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
# CSS for the main FastResumeApp
|
|
42
|
+
APP_CSS = """
|
|
43
|
+
Screen {
|
|
44
|
+
layout: vertical;
|
|
45
|
+
width: 100%;
|
|
46
|
+
background: $surface;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Title bar - branding + session count */
|
|
50
|
+
#title-bar {
|
|
51
|
+
height: 1;
|
|
52
|
+
width: 100%;
|
|
53
|
+
padding: 0 1;
|
|
54
|
+
background: $surface;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#app-title {
|
|
58
|
+
width: 1fr;
|
|
59
|
+
color: $text;
|
|
60
|
+
text-style: bold;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#session-count {
|
|
64
|
+
dock: right;
|
|
65
|
+
color: $text-muted;
|
|
66
|
+
width: auto;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Search row */
|
|
70
|
+
#search-row {
|
|
71
|
+
height: 3;
|
|
72
|
+
width: 100%;
|
|
73
|
+
padding: 0 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#search-box {
|
|
77
|
+
width: 100%;
|
|
78
|
+
height: 3;
|
|
79
|
+
border: solid $primary-background-lighten-2;
|
|
80
|
+
background: $surface;
|
|
81
|
+
padding: 0 1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#search-box:focus-within {
|
|
85
|
+
border: solid $accent;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#search-icon {
|
|
89
|
+
width: 3;
|
|
90
|
+
color: $text-muted;
|
|
91
|
+
content-align: center middle;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#search-input {
|
|
95
|
+
width: 1fr;
|
|
96
|
+
border: none;
|
|
97
|
+
background: transparent;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#search-input:focus {
|
|
101
|
+
border: none;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Agent filter tabs - pill style */
|
|
105
|
+
#filter-container {
|
|
106
|
+
height: 1;
|
|
107
|
+
width: 100%;
|
|
108
|
+
padding: 0 1;
|
|
109
|
+
margin-bottom: 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.filter-btn {
|
|
113
|
+
width: auto;
|
|
114
|
+
height: 1;
|
|
115
|
+
margin: 0 1 0 0;
|
|
116
|
+
padding: 0 1;
|
|
117
|
+
border: none;
|
|
118
|
+
background: transparent;
|
|
119
|
+
color: $text-muted;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.filter-btn:hover {
|
|
123
|
+
color: $text;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.filter-btn:focus {
|
|
127
|
+
text-style: none;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.filter-btn.-active {
|
|
131
|
+
background: $accent 20%;
|
|
132
|
+
color: $accent;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.filter-icon {
|
|
136
|
+
width: 2;
|
|
137
|
+
height: 1;
|
|
138
|
+
margin-right: 1;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.filter-label {
|
|
142
|
+
height: 1;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.filter-btn.-active .filter-label {
|
|
146
|
+
text-style: bold;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Agent-specific filter colors */
|
|
150
|
+
#filter-claude {
|
|
151
|
+
color: #E87B35;
|
|
152
|
+
}
|
|
153
|
+
#filter-claude.-active {
|
|
154
|
+
background: #E87B35 20%;
|
|
155
|
+
color: #E87B35;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#filter-codex {
|
|
159
|
+
color: #00A67E;
|
|
160
|
+
}
|
|
161
|
+
#filter-codex.-active {
|
|
162
|
+
background: #00A67E 20%;
|
|
163
|
+
color: #00A67E;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#filter-copilot-cli {
|
|
167
|
+
color: #9CA3AF;
|
|
168
|
+
}
|
|
169
|
+
#filter-copilot-cli.-active {
|
|
170
|
+
background: #9CA3AF 20%;
|
|
171
|
+
color: #9CA3AF;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#filter-copilot-vscode {
|
|
175
|
+
color: #007ACC;
|
|
176
|
+
}
|
|
177
|
+
#filter-copilot-vscode.-active {
|
|
178
|
+
background: #007ACC 20%;
|
|
179
|
+
color: #007ACC;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#filter-crush {
|
|
183
|
+
color: #FF5F87;
|
|
184
|
+
}
|
|
185
|
+
#filter-crush.-active {
|
|
186
|
+
background: #FF5F87 20%;
|
|
187
|
+
color: #FF5F87;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#filter-opencode {
|
|
191
|
+
color: #6366F1;
|
|
192
|
+
}
|
|
193
|
+
#filter-opencode.-active {
|
|
194
|
+
background: #6366F1 20%;
|
|
195
|
+
color: #6366F1;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#filter-vibe {
|
|
199
|
+
color: #FF6B35;
|
|
200
|
+
}
|
|
201
|
+
#filter-vibe.-active {
|
|
202
|
+
background: #FF6B35 20%;
|
|
203
|
+
color: #FF6B35;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* Main content area */
|
|
207
|
+
#main-container {
|
|
208
|
+
height: 1fr;
|
|
209
|
+
width: 100%;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#results-container {
|
|
213
|
+
height: 1fr;
|
|
214
|
+
width: 100%;
|
|
215
|
+
overflow-x: hidden;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
#results-table {
|
|
219
|
+
height: 100%;
|
|
220
|
+
width: 100%;
|
|
221
|
+
overflow-x: hidden;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
DataTable {
|
|
225
|
+
background: transparent;
|
|
226
|
+
overflow-x: hidden;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
DataTable > .datatable--header {
|
|
230
|
+
text-style: bold;
|
|
231
|
+
color: $text;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
DataTable > .datatable--cursor {
|
|
235
|
+
background: $accent 30%;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
DataTable > .datatable--hover {
|
|
239
|
+
background: $surface-lighten-1;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* Preview pane - expanded */
|
|
243
|
+
#preview-container {
|
|
244
|
+
height: 12;
|
|
245
|
+
border-top: solid $accent 50%;
|
|
246
|
+
background: $surface;
|
|
247
|
+
padding: 0 1;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#preview-container.hidden {
|
|
251
|
+
display: none;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
#preview {
|
|
255
|
+
height: auto;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* Agent colors */
|
|
259
|
+
.agent-claude {
|
|
260
|
+
color: #E87B35;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.agent-codex {
|
|
264
|
+
color: #00A67E;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.agent-copilot {
|
|
268
|
+
color: #9CA3AF;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.agent-opencode {
|
|
272
|
+
color: #6366F1;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.agent-vibe {
|
|
276
|
+
color: #FF6B35;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.agent-crush {
|
|
280
|
+
color: #FF5F87;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* Footer styling */
|
|
284
|
+
Footer {
|
|
285
|
+
background: $primary-background;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
Footer > .footer--key {
|
|
289
|
+
background: $surface;
|
|
290
|
+
color: $text;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
Footer > .footer--description {
|
|
294
|
+
color: $text-muted;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
#query-time {
|
|
298
|
+
width: auto;
|
|
299
|
+
padding: 0 1;
|
|
300
|
+
color: $text-muted;
|
|
301
|
+
}
|
|
302
|
+
"""
|