context-forge-cli 0.4.7__tar.gz → 0.4.8__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.7 → context_forge_cli-0.4.8}/PKG-INFO +1 -1
  2. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/tui/app.py +13 -0
  3. context_forge_cli-0.4.8/contextforge/tui/clipboard.py +28 -0
  4. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/tui/widgets/tokens_panel.py +21 -1
  5. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/pyproject.toml +1 -1
  6. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/.contextforge.toml +0 -0
  7. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/.github/workflows/publish.yml +0 -0
  8. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/.gitignore +0 -0
  9. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/.python-version +0 -0
  10. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/AGENTS.md +0 -0
  11. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/PLAN.md +0 -0
  12. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/README.md +0 -0
  13. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/__init__.py +0 -0
  14. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/adapters/__init__.py +0 -0
  15. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/adapters/altimate_code.py +0 -0
  16. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/adapters/base.py +0 -0
  17. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/adapters/claude_code.py +0 -0
  18. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/adapters/claude_desktop.py +0 -0
  19. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/adapters/codex.py +0 -0
  20. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/adapters/gemini.py +0 -0
  21. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/adapters/registry.py +0 -0
  22. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/cli.py +0 -0
  23. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/core/__init__.py +0 -0
  24. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/core/analytics.py +0 -0
  25. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/core/compactor.py +0 -0
  26. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/core/db.py +0 -0
  27. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/core/injector.py +0 -0
  28. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/core/scanner.py +0 -0
  29. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/core/summarizer.py +0 -0
  30. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/core/token_analyzer.py +0 -0
  31. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/mcp_server.py +0 -0
  32. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/models/__init__.py +0 -0
  33. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/models/config.py +0 -0
  34. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/models/session.py +0 -0
  35. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/tui/__init__.py +0 -0
  36. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/tui/styles.tcss +0 -0
  37. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/tui/widgets/__init__.py +0 -0
  38. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/tui/widgets/session_detail.py +0 -0
  39. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/tui/widgets/session_table.py +0 -0
  40. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/tui/widgets/stats_panel.py +0 -0
  41. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/tui/widgets/status_bar.py +0 -0
  42. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/tui/widgets/transfer_panel.py +0 -0
  43. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/utils/__init__.py +0 -0
  44. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/utils/display.py +0 -0
  45. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/contextforge/utils/tokens.py +0 -0
  46. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/tests/__init__.py +0 -0
  47. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/tests/adapters/__init__.py +0 -0
  48. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/tests/adapters/test_claude_code.py +0 -0
  49. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/tests/conftest.py +0 -0
  50. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/tests/core/__init__.py +0 -0
  51. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/tests/core/test_compactor.py +0 -0
  52. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/tests/core/test_db.py +0 -0
  53. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/tests/core/test_injector.py +0 -0
  54. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/tests/core/test_summarizer.py +0 -0
  55. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/tests/core/test_token_analyzer.py +0 -0
  56. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/tests/fixtures/claude_session_sample.jsonl +0 -0
  57. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/tests/test_mcp_server.py +0 -0
  58. {context_forge_cli-0.4.7 → context_forge_cli-0.4.8}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: context-forge-cli
3
- Version: 0.4.7
3
+ Version: 0.4.8
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
@@ -8,6 +8,7 @@ from textual.binding import Binding
8
8
  from textual.containers import Horizontal, Vertical
9
9
  from textual.widgets import Footer, Header
10
10
 
11
+ from contextforge.tui.clipboard import copy as clipboard_copy
11
12
  from contextforge.tui.widgets.session_detail import SessionDetail
12
13
  from contextforge.tui.widgets.session_table import SessionTable
13
14
  from contextforge.tui.widgets.status_bar import StatusBar
@@ -31,6 +32,7 @@ class ContextForgeApp(App):
31
32
  Binding("x", "tokens", "# Tokens"),
32
33
  Binding("a", "analytics", "▤ Analytics"),
33
34
  Binding("/", "filter", "⌕ Filter"),
35
+ Binding("y", "yank_id", "📋 Copy ID"),
34
36
  ]
35
37
 
36
38
  def __init__(self, db_path: Path | None = None) -> None:
@@ -203,3 +205,14 @@ class ContextForgeApp(App):
203
205
  def action_filter(self) -> None:
204
206
  """Toggle the filter bar in the session table."""
205
207
  self.query_one(SessionTable).toggle_filter()
208
+
209
+ def action_yank_id(self) -> None:
210
+ """Copy the current session ID to the clipboard."""
211
+ sid = getattr(self, "_current_session_id", None)
212
+ if sid is None:
213
+ self.notify("Select a session first.", severity="warning")
214
+ return
215
+ if clipboard_copy(sid):
216
+ self.notify(f"Copied [cyan]{sid}[/cyan]", title="Yank")
217
+ else:
218
+ self.notify("Failed to copy to clipboard.", severity="error")
@@ -0,0 +1,28 @@
1
+ """Cross-platform clipboard utilities for the TUI."""
2
+
3
+ import platform
4
+ import subprocess
5
+
6
+
7
+ def copy(text: str) -> bool:
8
+ """Copy *text* to the system clipboard. Returns True on success."""
9
+ if not text:
10
+ return False
11
+ system = platform.system()
12
+ try:
13
+ if system == "Darwin":
14
+ subprocess.run(["pbcopy"], input=text, text=True, check=True)
15
+ elif system == "Linux":
16
+ # try Wayland first, then X11
17
+ try:
18
+ subprocess.run(["wl-copy"], input=text, text=True, check=True)
19
+ except FileNotFoundError:
20
+ subprocess.run(
21
+ ["xclip", "-selection", "clipboard"],
22
+ input=text, text=True, check=True,
23
+ )
24
+ else:
25
+ return False
26
+ return True
27
+ except Exception:
28
+ return False
@@ -12,6 +12,7 @@ from textual.screen import ModalScreen
12
12
  from textual.widgets import DataTable, Static
13
13
 
14
14
  from contextforge.models.session import Message
15
+ from contextforge.tui.clipboard import copy as clipboard_copy
15
16
 
16
17
  # ── Constants ─────────────────────────────────────────────────────────────────
17
18
 
@@ -55,6 +56,7 @@ class TurnDetailPanel(ModalScreen):
55
56
 
56
57
  BINDINGS = [
57
58
  Binding("escape,q", "dismiss(None)", "Close"),
59
+ Binding("y", "copy_content", "📋 Copy"),
58
60
  ]
59
61
 
60
62
  DEFAULT_CSS = """
@@ -89,13 +91,14 @@ class TurnDetailPanel(ModalScreen):
89
91
  super().__init__()
90
92
  self._turn_num = turn_num
91
93
  self._msg = msg
94
+ self._plain_text = ""
92
95
 
93
96
  def compose(self) -> ComposeResult:
94
97
  with Vertical():
95
98
  yield Static("", id="turn-header")
96
99
  with ScrollableContainer():
97
100
  yield Static("", id="turn-body")
98
- yield Static("[dim]ESC / q — close[/dim]", id="turn-footer")
101
+ yield Static("[dim]ESC / q — close Y — copy[/dim]", id="turn-footer")
99
102
 
100
103
  def on_mount(self) -> None:
101
104
  msg = self._msg
@@ -118,13 +121,17 @@ class TurnDetailPanel(ModalScreen):
118
121
  )
119
122
 
120
123
  body = Text()
124
+ plain_parts = []
121
125
 
122
126
  if msg.content:
123
127
  body.append("── Content ──\n", style="bold dim")
124
128
  body.append(msg.content + "\n")
129
+ plain_parts.append("── Content ──\n")
130
+ plain_parts.append(msg.content + "\n")
125
131
 
126
132
  if msg.tool_calls:
127
133
  body.append("\n── Tool Calls ──\n", style="bold dim")
134
+ plain_parts.append("\n── Tool Calls ──\n")
128
135
  for i, tc in enumerate(msg.tool_calls, 1):
129
136
  name = tc.get("name", "?")
130
137
  raw_input = tc.get("input", "")
@@ -134,18 +141,31 @@ class TurnDetailPanel(ModalScreen):
134
141
  formatted = raw_input if isinstance(raw_input, str) else str(raw_input)
135
142
  body.append(f"⚙ {i}. {name}\n", style="bold yellow")
136
143
  body.append(formatted + "\n", style="dim")
144
+ plain_parts.append(f"⚙ {i}. {name}\n")
145
+ plain_parts.append(formatted + "\n")
137
146
 
138
147
  if msg.tool_results:
139
148
  body.append("\n── Tool Results ──\n", style="bold dim")
149
+ plain_parts.append("\n── Tool Results ──\n")
140
150
  for i, tr in enumerate(msg.tool_results, 1):
141
151
  output = tr.get("output", "")
142
152
  if not isinstance(output, str):
143
153
  output = str(output)
144
154
  body.append(f"⇥ Result {i}\n", style="bold green")
145
155
  body.append(output + "\n", style="dim")
156
+ plain_parts.append(f"⇥ Result {i}\n")
157
+ plain_parts.append(output + "\n")
146
158
 
159
+ self._plain_text = "".join(plain_parts)
147
160
  self.query_one("#turn-body", Static).update(body)
148
161
 
162
+ def action_copy_content(self) -> None:
163
+ """Copy the turn content to the clipboard."""
164
+ if clipboard_copy(self._plain_text):
165
+ self.notify(f"Copied turn #{self._turn_num} content", title="Copy")
166
+ else:
167
+ self.notify("Failed to copy to clipboard.", severity="error")
168
+
149
169
 
150
170
  # ── TokensPanel ───────────────────────────────────────────────────────────────
151
171
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "context-forge-cli"
3
- version = "0.4.7"
3
+ version = "0.4.8"
4
4
  description = "Session manager and context bridge for agentic CLI tools"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }