context-forge-cli 0.4.3__tar.gz → 0.4.5__tar.gz

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 (58) hide show
  1. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/PKG-INFO +1 -1
  2. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/tui/app.py +8 -7
  3. context_forge_cli-0.4.5/contextforge/tui/styles.tcss +85 -0
  4. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/tui/widgets/session_detail.py +26 -20
  5. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/tui/widgets/session_table.py +31 -23
  6. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/tui/widgets/stats_panel.py +9 -7
  7. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/tui/widgets/status_bar.py +10 -9
  8. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/tui/widgets/tokens_panel.py +11 -11
  9. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/pyproject.toml +1 -1
  10. context_forge_cli-0.4.3/contextforge/tui/styles.tcss +0 -50
  11. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/.contextforge.toml +0 -0
  12. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/.github/workflows/publish.yml +0 -0
  13. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/.gitignore +0 -0
  14. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/.python-version +0 -0
  15. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/AGENTS.md +0 -0
  16. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/PLAN.md +0 -0
  17. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/README.md +0 -0
  18. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/__init__.py +0 -0
  19. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/adapters/__init__.py +0 -0
  20. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/adapters/altimate_code.py +0 -0
  21. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/adapters/base.py +0 -0
  22. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/adapters/claude_code.py +0 -0
  23. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/adapters/claude_desktop.py +0 -0
  24. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/adapters/codex.py +0 -0
  25. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/adapters/gemini.py +0 -0
  26. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/adapters/registry.py +0 -0
  27. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/cli.py +0 -0
  28. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/core/__init__.py +0 -0
  29. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/core/analytics.py +0 -0
  30. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/core/compactor.py +0 -0
  31. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/core/db.py +0 -0
  32. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/core/injector.py +0 -0
  33. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/core/scanner.py +0 -0
  34. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/core/summarizer.py +0 -0
  35. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/core/token_analyzer.py +0 -0
  36. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/mcp_server.py +0 -0
  37. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/models/__init__.py +0 -0
  38. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/models/config.py +0 -0
  39. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/models/session.py +0 -0
  40. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/tui/__init__.py +0 -0
  41. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/tui/widgets/__init__.py +0 -0
  42. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/tui/widgets/transfer_panel.py +0 -0
  43. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/utils/__init__.py +0 -0
  44. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/utils/display.py +0 -0
  45. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/contextforge/utils/tokens.py +0 -0
  46. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/tests/__init__.py +0 -0
  47. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/tests/adapters/__init__.py +0 -0
  48. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/tests/adapters/test_claude_code.py +0 -0
  49. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/tests/conftest.py +0 -0
  50. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/tests/core/__init__.py +0 -0
  51. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/tests/core/test_compactor.py +0 -0
  52. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/tests/core/test_db.py +0 -0
  53. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/tests/core/test_injector.py +0 -0
  54. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/tests/core/test_summarizer.py +0 -0
  55. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/tests/core/test_token_analyzer.py +0 -0
  56. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/tests/fixtures/claude_session_sample.jsonl +0 -0
  57. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/tests/test_mcp_server.py +0 -0
  58. {context_forge_cli-0.4.3 → context_forge_cli-0.4.5}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: context-forge-cli
3
- Version: 0.4.3
3
+ Version: 0.4.5
4
4
  Summary: Session manager and context bridge for agentic CLI tools
5
5
  Project-URL: Homepage, https://github.com/emmver/contextforge
6
6
  Project-URL: Repository, https://github.com/emmver/contextforge
@@ -19,17 +19,18 @@ class ContextForgeApp(App):
19
19
  """ContextForge dashboard — manage agentic CLI sessions."""
20
20
 
21
21
  TITLE = "ContextForge"
22
+ SUB_TITLE = "agentic session manager"
22
23
  CSS_PATH = "styles.tcss"
23
24
 
24
25
  BINDINGS = [
25
26
  Binding("q", "quit", "Quit"),
26
- Binding("r", "rescan", "Rescan"),
27
- Binding("s", "summarize", "Summarize"),
28
- Binding("t", "transfer", "Transfer"),
29
- Binding("c", "compact", "Compact"),
30
- Binding("x", "tokens", "Tokens"),
31
- Binding("a", "analytics", "Analytics"),
32
- Binding("/", "filter", "Filter"),
27
+ Binding("r", "rescan", "⟳ Rescan"),
28
+ Binding("s", "summarize", "✎ Summarize"),
29
+ Binding("t", "transfer", "⇄ Transfer"),
30
+ Binding("c", "compact", "⊛ Compact"),
31
+ Binding("x", "tokens", "# Tokens"),
32
+ Binding("a", "analytics", "▤ Analytics"),
33
+ Binding("/", "filter", "⌕ Filter"),
33
34
  ]
34
35
 
35
36
  def __init__(self, db_path: Path | None = None) -> None:
@@ -0,0 +1,85 @@
1
+ /* ContextForge TUI Styles */
2
+
3
+ /* ── Layout ─────────────────────────────────────────────── */
4
+
5
+ Screen {
6
+ layout: vertical;
7
+ }
8
+
9
+ #main-horizontal {
10
+ height: 1fr;
11
+ layout: horizontal;
12
+ }
13
+
14
+ /* ── Header ─────────────────────────────────────────────── */
15
+
16
+ Header {
17
+ background: $primary-background-darken-1;
18
+ color: $accent;
19
+ text-style: bold;
20
+ }
21
+
22
+ /* ── Footer ─────────────────────────────────────────────── */
23
+
24
+ Footer {
25
+ background: $primary-background-darken-2;
26
+ }
27
+
28
+ /* ── DataTable global tweaks ─────────────────────────────── */
29
+
30
+ DataTable > .datatable--header {
31
+ text-style: bold;
32
+ color: $accent;
33
+ background: $primary-background-darken-1;
34
+ }
35
+
36
+ DataTable > .datatable--cursor {
37
+ background: $primary;
38
+ color: $text;
39
+ }
40
+
41
+ /* ── Transfer modal ─────────────────────────────────────── */
42
+
43
+ TransferPanel {
44
+ align: center middle;
45
+ }
46
+
47
+ TransferPanel > Vertical {
48
+ width: 64;
49
+ height: auto;
50
+ max-height: 38;
51
+ border: thick $accent;
52
+ background: $surface;
53
+ padding: 1 2;
54
+ }
55
+
56
+ #panel-title {
57
+ text-style: bold;
58
+ color: $accent;
59
+ margin-bottom: 1;
60
+ padding-bottom: 1;
61
+ border-bottom: solid $primary-background-lighten-1;
62
+ }
63
+
64
+ .section-label {
65
+ text-style: bold;
66
+ margin-top: 1;
67
+ color: $text-muted;
68
+ }
69
+
70
+ #btn-row {
71
+ margin-top: 2;
72
+ height: auto;
73
+ layout: horizontal;
74
+ align: right middle;
75
+ }
76
+
77
+ #btn-row Button {
78
+ margin-left: 1;
79
+ }
80
+
81
+ /* ── Notifications ──────────────────────────────────────── */
82
+
83
+ Toast {
84
+ border: tall $accent;
85
+ }
@@ -11,21 +11,23 @@ from textual.widget import Widget
11
11
  from textual.widgets import Markdown, Static
12
12
 
13
13
  TOOL_COLORS = {
14
- "claude_code": "cyan",
15
- "codex": "green",
16
- "altimate_code": "magenta",
14
+ "claude_code": "cyan",
15
+ "codex": "green",
16
+ "altimate_code": "magenta",
17
17
  "claude_desktop": "yellow",
18
+ "gemini": "blue",
18
19
  }
19
20
  TOOL_LABELS = {
20
- "claude_code": "Claude Code",
21
- "codex": "Codex",
22
- "altimate_code": "altimate",
23
- "claude_desktop": "Desktop",
21
+ "claude_code": "◆ Claude Code",
22
+ "codex": "⬡ Codex",
23
+ "altimate_code": "⚡ Altimate",
24
+ "claude_desktop": "◇ Claude Desktop",
25
+ "gemini": "✦ Gemini",
24
26
  }
25
27
 
26
28
 
27
29
  def _meta_line(label: str, value: str, color: str = "cyan") -> str:
28
- return f"[bold {color}]{label:<10}[/bold {color}] {value}"
30
+ return f"[dim]{label:<10}[/dim] {value}"
29
31
 
30
32
 
31
33
  def _fmt_tokens(n: int | None) -> str:
@@ -75,11 +77,14 @@ class SessionDetail(Widget):
75
77
  color: $text-disabled;
76
78
  margin: 4 0;
77
79
  text-align: center;
80
+ text-style: italic;
78
81
  }
79
82
  SessionDetail #detail-title {
80
83
  text-style: bold;
81
84
  color: $accent;
82
85
  margin-bottom: 1;
86
+ padding-bottom: 1;
87
+ border-bottom: solid $primary-background-lighten-1;
83
88
  }
84
89
  SessionDetail #detail-meta-block {
85
90
  margin-bottom: 1;
@@ -88,8 +93,9 @@ class SessionDetail(Widget):
88
93
  }
89
94
  SessionDetail #detail-summary-label {
90
95
  text-style: bold;
91
- color: $text-muted;
96
+ color: $accent;
92
97
  margin-bottom: 1;
98
+ margin-top: 1;
93
99
  }
94
100
  SessionDetail Markdown {
95
101
  background: transparent;
@@ -111,11 +117,11 @@ class SessionDetail(Widget):
111
117
  yield Static("", id="detail-status")
112
118
  yield Static("", id="detail-tags")
113
119
  yield Static("", id="detail-id")
114
- yield Static("── Summary ──", id="detail-summary-label")
120
+ yield Static("✎ Summary", id="detail-summary-label")
115
121
  yield Markdown("", id="detail-summary-md")
116
122
 
117
123
  def on_mount(self) -> None:
118
- self.border_title = "Detail"
124
+ self.border_title = "◈ Detail"
119
125
  self.query_one("#detail-summary-label").display = False
120
126
  self.query_one("#detail-meta-block").display = False
121
127
  self.query_one("#detail-title").display = False
@@ -168,15 +174,15 @@ class SessionDetail(Widget):
168
174
  self.query_one("#detail-meta-block").display = True
169
175
  self.query_one("#detail-summary-label").display = True
170
176
 
171
- self.query_one("#detail-title", Static).update(f"[bold accent]{title}[/bold accent]")
172
- self.query_one("#detail-tool", Static).update(_meta_line("Tool", tool_markup, tool_color))
173
- self.query_one("#detail-cwd", Static).update(_meta_line("CWD", f"[dim]{cwd}[/dim]", "dim"))
174
- self.query_one("#detail-tokens", Static).update(_meta_line("Tokens", tok_markup, "dim"))
175
- self.query_one("#detail-created", Static).update(_meta_line("Created", f"[dim]{created_str}[/dim]", "dim"))
176
- self.query_one("#detail-updated", Static).update(_meta_line("Updated", f"[dim]{updated_str}[/dim]", "dim"))
177
- self.query_one("#detail-status", Static).update(_meta_line("Status", status, "dim"))
178
- self.query_one("#detail-tags", Static).update(_meta_line("Tags", tags, "dim"))
179
- self.query_one("#detail-id", Static).update(_meta_line("ID", f"[dim]{session_id}[/dim]", "dim"))
177
+ self.query_one("#detail-title", Static).update(f"[bold]{title}[/bold]")
178
+ self.query_one("#detail-tool", Static).update(_meta_line("Tool", tool_markup))
179
+ self.query_one("#detail-cwd", Static).update(_meta_line("Project", f"[dim]{cwd}[/dim]"))
180
+ self.query_one("#detail-tokens", Static).update(_meta_line("Tokens", tok_markup))
181
+ self.query_one("#detail-created", Static).update(_meta_line("Created", f"[dim]{created_str}[/dim]"))
182
+ self.query_one("#detail-updated", Static).update(_meta_line("Updated", f"[dim]{updated_str}[/dim]"))
183
+ self.query_one("#detail-status", Static).update(_meta_line("Status", f"[dim]{status}[/dim]"))
184
+ self.query_one("#detail-tags", Static).update(_meta_line("Tags", f"[dim]{tags}[/dim]"))
185
+ self.query_one("#detail-id", Static).update(_meta_line("ID", f"[dim]{session_id}[/dim]"))
180
186
 
181
187
  md = self.query_one("#detail-summary-md", Markdown)
182
188
  self.app.call_later(md.update, summary)
@@ -12,31 +12,36 @@ from textual.reactive import reactive
12
12
  from textual.widget import Widget
13
13
  from textual.widgets import Button, DataTable, Input, Label
14
14
 
15
- TOOL_EMOJI = {
16
- "claude_code": "🔵",
17
- "codex": "🟢",
18
- "altimate_code": "🟣",
19
- "claude_desktop": "🟡",
15
+ # Unicode symbols that evoke each tool brand (terminals can't render images)
16
+ TOOL_ICON = {
17
+ "claude_code": "◆", # Anthropic — solid diamond
18
+ "codex": "⬡", # OpenAI — hexagon
19
+ "altimate_code": "⚡", # Altimate — lightning
20
+ "claude_desktop": "◇", # Claude Desktop — open diamond
21
+ "gemini": "✦", # Google Gemini — four-pointed star
20
22
  }
21
23
 
22
24
  TOOL_MARKUP = {
23
- "claude_code": "[cyan]🔵 CC[/cyan]",
24
- "codex": "[green]🟢 Codex[/green]",
25
- "altimate_code": "[magenta]🟣 Alt[/magenta]",
26
- "claude_desktop": "[yellow]🟡 Desktop[/yellow]",
25
+ "claude_code": "[cyan]◆ Claude[/cyan]",
26
+ "codex": "[green]⬡ Codex[/green]",
27
+ "altimate_code": "[magenta]⚡ Alt[/magenta]",
28
+ "claude_desktop": "[yellow]◇ Desktop[/yellow]",
29
+ "gemini": "[blue]✦ Gemini[/blue]",
27
30
  }
28
31
 
29
32
  TOOL_DISPLAY = {
30
- "claude_code": "CC",
31
- "codex": "Codex",
32
- "altimate_code": "Alt",
33
- "claude_desktop": "Desktop",
33
+ "claude_code": "◆ Claude",
34
+ "codex": "⬡ Codex",
35
+ "altimate_code": "⚡ Alt",
36
+ "claude_desktop": "◇ Desktop",
37
+ "gemini": "✦ Gemini",
34
38
  }
35
39
 
36
40
  _FTOOL_MAP = {
37
41
  "ftool-all": None,
38
42
  "ftool-cc": "claude_code",
39
43
  "ftool-codex": "codex",
44
+ "ftool-gem": "gemini",
40
45
  "ftool-alt": "altimate_code",
41
46
  }
42
47
 
@@ -89,14 +94,16 @@ class SessionTable(Widget):
89
94
  SessionTable #filter-label {
90
95
  width: auto;
91
96
  margin-right: 1;
92
- color: $text-muted;
97
+ color: $accent;
98
+ text-style: bold;
93
99
  }
94
100
  SessionTable #filter-input {
95
- width: 28;
101
+ width: 26;
96
102
  margin-right: 1;
103
+ border: tall $primary;
97
104
  }
98
105
  SessionTable .filter-tool-btn {
99
- min-width: 7;
106
+ min-width: 9;
100
107
  height: 1;
101
108
  margin-right: 1;
102
109
  background: $surface;
@@ -147,14 +154,15 @@ class SessionTable(Widget):
147
154
  with Horizontal(id="filter-bar", classes="hidden"):
148
155
  yield Label("Filter:", id="filter-label")
149
156
  yield Input(placeholder="title or project…", id="filter-input")
150
- yield Button("All", id="ftool-all", classes="filter-tool-btn active")
151
- yield Button("CC", id="ftool-cc", classes="filter-tool-btn")
152
- yield Button("Codex", id="ftool-codex", classes="filter-tool-btn")
153
- yield Button("Alt", id="ftool-alt", classes="filter-tool-btn")
154
- yield DataTable(cursor_type="row", zebra_stripes=True)
157
+ yield Button("All", id="ftool-all", classes="filter-tool-btn active")
158
+ yield Button("◆ CC", id="ftool-cc", classes="filter-tool-btn")
159
+ yield Button("⬡ Codex", id="ftool-codex", classes="filter-tool-btn")
160
+ yield Button("✦ Gem", id="ftool-gem", classes="filter-tool-btn")
161
+ yield Button("⚡ Alt", id="ftool-alt", classes="filter-tool-btn")
162
+ yield DataTable(cursor_type="row", zebra_stripes=True, id="sessions-datatable")
155
163
 
156
164
  def on_mount(self) -> None:
157
- self.border_title = "Sessions"
165
+ self.border_title = "◈ Sessions"
158
166
  table = self.query_one(DataTable)
159
167
  table.add_columns("Tool", "Title", "Updated", "Tokens", "ID")
160
168
  self.reload()
@@ -210,7 +218,7 @@ class SessionTable(Widget):
210
218
  tool_label = TOOL_MARKUP.get(tool, f"⚪ {tool}")
211
219
 
212
220
  from contextforge.utils.display import _clean_title
213
- title = _clean_title(row.get("title") or "", max_len=38) or "(no title)"
221
+ title = _clean_title(row.get("title") or "", max_len=40) or "(no title)"
214
222
 
215
223
  updated_ms = row.get("updated_at") or 0
216
224
  try:
@@ -16,16 +16,18 @@ _BLOCKS = " ▏▎▍▌▋▊▉█" # 9 levels: 0/8 → 8/8 of a cell
16
16
  _SPARK = "▁▂▃▄▅▆▇█" # 8 vertical height levels
17
17
 
18
18
  TOOL_COLORS = {
19
- "claude_code": "cyan",
20
- "codex": "green",
21
- "altimate_code": "magenta",
19
+ "claude_code": "cyan",
20
+ "codex": "green",
21
+ "altimate_code": "magenta",
22
22
  "claude_desktop": "yellow",
23
+ "gemini": "blue",
23
24
  }
24
25
  TOOL_LABELS = {
25
- "claude_code": "Claude Code",
26
- "codex": "Codex",
27
- "altimate_code": "altimate",
28
- "claude_desktop": "Desktop",
26
+ "claude_code": "◆ Claude Code",
27
+ "codex": "⬡ Codex",
28
+ "altimate_code": "⚡ Altimate",
29
+ "claude_desktop": "◇ Desktop",
30
+ "gemini": "✦ Gemini",
29
31
  }
30
32
 
31
33
  WINDOW_LABELS = {
@@ -64,23 +64,24 @@ class StatusBar(Widget):
64
64
 
65
65
  tool_parts = []
66
66
  labels = {
67
- "claude_code": "CC",
68
- "codex": "Codex",
69
- "altimate_code": "Alt",
70
- "claude_desktop": "Desktop",
67
+ "claude_code": "◆ CC",
68
+ "codex": "⬡ Codex",
69
+ "altimate_code": "⚡ Alt",
70
+ "claude_desktop": "◇ Desktop",
71
+ "gemini": "✦ Gemini",
71
72
  }
72
73
  for tool, label in labels.items():
73
74
  n = counts.get(tool, 0)
74
75
  if n:
75
- tool_parts.append(f"{label}:{n}")
76
+ tool_parts.append(f"{label} {n}")
76
77
 
77
78
  tok_str = _fmt_tokens(total_tokens)
78
79
  self.query_one("#status-counts").update(
79
- f"Sessions — {' │ '.join(tool_parts)} │ Total:{len(rows)} │ {tok_str}"
80
+ f" {' [dim]│[/dim] '.join(tool_parts)} [dim]│[/dim] [bold]{len(rows)}[/bold] sessions [dim]│[/dim] {tok_str}"
80
81
  )
81
82
 
82
83
  now = datetime.now(timezone.utc).strftime("%H:%M:%S")
83
- self.query_one("#status-scan-time").update(f"Updated {now}")
84
+ self.query_one("#status-scan-time").update(f"[dim]⟳ {now}[/dim]")
84
85
 
85
86
  def set_filter_indicator(
86
87
  self, text: str, tool: str | None, match_count: int
@@ -94,9 +95,9 @@ class StatusBar(Widget):
94
95
  if text:
95
96
  parts.append(f'"{text}"')
96
97
  if tool:
97
- short = {"claude_code": "CC", "codex": "Codex", "altimate_code": "Alt", "claude_desktop": "Desktop"}.get(tool, tool)
98
+ short = {"claude_code": "◆ CC", "codex": "⬡ Codex", "altimate_code": "⚡ Alt", "claude_desktop": "◇ Desktop", "gemini": "✦ Gemini"}.get(tool, tool)
98
99
  parts.append(short)
99
100
 
100
101
  self.query_one("#status-filter", Static).update(
101
- f"[yellow]Filter: {' + '.join(parts)} ({match_count} matches)[/yellow]"
102
+ f"[yellow]⌕ {' + '.join(parts)} [dim]({match_count} matches)[/dim][/yellow]"
102
103
  )
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
  import json
5
5
  from pathlib import Path
6
6
 
7
- from rich.markup import escape
7
+ from rich.text import Text
8
8
  from textual.app import ComposeResult
9
9
  from textual.binding import Binding
10
10
  from textual.containers import ScrollableContainer, Vertical
@@ -94,16 +94,16 @@ class TurnDetailPanel(ModalScreen):
94
94
  + (f" [magenta]{len(msg.tool_results)} result(s)[/magenta]" if msg.tool_results else "")
95
95
  )
96
96
 
97
- parts: list[str] = []
97
+ body = Text()
98
98
 
99
99
  # ── Text content ────────────────────────────────────────────────────
100
100
  if msg.content:
101
- parts.append("[bold dim]── Content ──[/bold dim]")
102
- parts.append(escape(msg.content))
101
+ body.append("── Content ──\n", style="bold dim")
102
+ body.append(msg.content + "\n")
103
103
 
104
104
  # ── Tool calls ──────────────────────────────────────────────────────
105
105
  if msg.tool_calls:
106
- parts.append("\n[bold dim]── Tool Calls ──[/bold dim]")
106
+ body.append("\n── Tool Calls ──\n", style="bold dim")
107
107
  for i, tc in enumerate(msg.tool_calls, 1):
108
108
  name = tc.get("name", "?")
109
109
  raw_input = tc.get("input", "")
@@ -111,20 +111,20 @@ class TurnDetailPanel(ModalScreen):
111
111
  formatted = json.dumps(json.loads(raw_input), indent=2)
112
112
  except (json.JSONDecodeError, TypeError):
113
113
  formatted = raw_input if isinstance(raw_input, str) else str(raw_input)
114
- parts.append(f"[bold yellow]{i}. {escape(name)}[/bold yellow]")
115
- parts.append(f"[dim]{escape(formatted)}[/dim]")
114
+ body.append(f"{i}. {name}\n", style="bold yellow")
115
+ body.append(formatted + "\n", style="dim")
116
116
 
117
117
  # ── Tool results ────────────────────────────────────────────────────
118
118
  if msg.tool_results:
119
- parts.append("\n[bold dim]── Tool Results ──[/bold dim]")
119
+ body.append("\n── Tool Results ──\n", style="bold dim")
120
120
  for i, tr in enumerate(msg.tool_results, 1):
121
121
  output = tr.get("output", "")
122
122
  if not isinstance(output, str):
123
123
  output = str(output)
124
- parts.append(f"[bold green]Result {i}[/bold green]")
125
- parts.append(f"[dim]{escape(output)}[/dim]")
124
+ body.append(f"Result {i}\n", style="bold green")
125
+ body.append(output + "\n", style="dim")
126
126
 
127
- self.query_one("#turn-body", Static).update("\n".join(parts))
127
+ self.query_one("#turn-body", Static).update(body)
128
128
 
129
129
 
130
130
  class TokensPanel(ModalScreen):
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "context-forge-cli"
3
- version = "0.4.3"
3
+ version = "0.4.5"
4
4
  description = "Session manager and context bridge for agentic CLI tools"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -1,50 +0,0 @@
1
- /* ContextForge TUI Styles */
2
-
3
- /* ── Layout ─────────────────────────────────────────────── */
4
-
5
- Screen {
6
- layout: vertical;
7
- }
8
-
9
- #main-horizontal {
10
- height: 1fr;
11
- layout: horizontal;
12
- }
13
-
14
- /* ── Transfer modal ─────────────────────────────────────── */
15
-
16
- TransferPanel {
17
- align: center middle;
18
- }
19
-
20
- TransferPanel > Vertical {
21
- width: 64;
22
- height: auto;
23
- max-height: 38;
24
- border: thick $primary;
25
- background: $surface;
26
- padding: 1 2;
27
- }
28
-
29
- #panel-title {
30
- text-style: bold;
31
- color: $accent;
32
- margin-bottom: 1;
33
- }
34
-
35
- .section-label {
36
- text-style: bold;
37
- margin-top: 1;
38
- color: $text-muted;
39
- }
40
-
41
- #btn-row {
42
- margin-top: 2;
43
- height: auto;
44
- layout: horizontal;
45
- align: right middle;
46
- }
47
-
48
- #btn-row Button {
49
- margin-left: 1;
50
- }