context-forge-cli 0.4.6__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.
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/PKG-INFO +1 -1
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/tui/app.py +13 -0
- context_forge_cli-0.4.8/contextforge/tui/clipboard.py +28 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/tui/widgets/tokens_panel.py +21 -1
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/utils/tokens.py +2 -2
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/pyproject.toml +1 -1
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/.contextforge.toml +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/.github/workflows/publish.yml +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/.gitignore +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/.python-version +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/AGENTS.md +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/PLAN.md +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/README.md +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/__init__.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/adapters/__init__.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/adapters/altimate_code.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/adapters/base.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/adapters/claude_code.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/adapters/claude_desktop.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/adapters/codex.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/adapters/gemini.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/adapters/registry.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/cli.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/core/__init__.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/core/analytics.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/core/compactor.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/core/db.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/core/injector.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/core/scanner.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/core/summarizer.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/core/token_analyzer.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/mcp_server.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/models/__init__.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/models/config.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/models/session.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/tui/__init__.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/tui/styles.tcss +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/tui/widgets/__init__.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/tui/widgets/session_detail.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/tui/widgets/session_table.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/tui/widgets/stats_panel.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/tui/widgets/status_bar.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/tui/widgets/transfer_panel.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/utils/__init__.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/utils/display.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/tests/__init__.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/tests/adapters/__init__.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/tests/adapters/test_claude_code.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/tests/conftest.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/tests/core/__init__.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/tests/core/test_compactor.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/tests/core/test_db.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/tests/core/test_injector.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/tests/core/test_summarizer.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/tests/core/test_token_analyzer.py +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/tests/fixtures/claude_session_sample.jsonl +0 -0
- {context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/tests/test_mcp_server.py +0 -0
- {context_forge_cli-0.4.6 → 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.
|
|
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
|
{context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/tui/widgets/tokens_panel.py
RENAMED
|
@@ -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
|
|
|
@@ -23,12 +23,12 @@ def count_tokens(text) -> int:
|
|
|
23
23
|
)
|
|
24
24
|
else:
|
|
25
25
|
text = str(text) if text is not None else ""
|
|
26
|
-
return len(_get_encoding().encode(text))
|
|
26
|
+
return len(_get_encoding().encode(text, disallowed_special=()))
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def truncate_to_budget(text: str, budget: int) -> str:
|
|
30
30
|
enc = _get_encoding()
|
|
31
|
-
tokens = enc.encode(text)
|
|
31
|
+
tokens = enc.encode(text, disallowed_special=())
|
|
32
32
|
if len(tokens) <= budget:
|
|
33
33
|
return text
|
|
34
34
|
return enc.decode(tokens[:budget])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/tui/widgets/session_detail.py
RENAMED
|
File without changes
|
{context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/tui/widgets/session_table.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/contextforge/tui/widgets/transfer_panel.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{context_forge_cli-0.4.6 → context_forge_cli-0.4.8}/tests/fixtures/claude_session_sample.jsonl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|