context-forge-cli 0.1.0__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.
- context_forge_cli-0.1.0.dist-info/METADATA +340 -0
- context_forge_cli-0.1.0.dist-info/RECORD +37 -0
- context_forge_cli-0.1.0.dist-info/WHEEL +4 -0
- context_forge_cli-0.1.0.dist-info/entry_points.txt +2 -0
- contextforge/__init__.py +3 -0
- contextforge/adapters/__init__.py +3 -0
- contextforge/adapters/altimate_code.py +179 -0
- contextforge/adapters/base.py +44 -0
- contextforge/adapters/claude_code.py +393 -0
- contextforge/adapters/claude_desktop.py +306 -0
- contextforge/adapters/codex.py +220 -0
- contextforge/adapters/registry.py +31 -0
- contextforge/cli.py +523 -0
- contextforge/core/__init__.py +0 -0
- contextforge/core/analytics.py +226 -0
- contextforge/core/compactor.py +182 -0
- contextforge/core/db.py +181 -0
- contextforge/core/injector.py +94 -0
- contextforge/core/scanner.py +72 -0
- contextforge/core/summarizer.py +155 -0
- contextforge/core/token_analyzer.py +91 -0
- contextforge/models/__init__.py +4 -0
- contextforge/models/config.py +34 -0
- contextforge/models/session.py +37 -0
- contextforge/tui/__init__.py +0 -0
- contextforge/tui/app.py +204 -0
- contextforge/tui/styles.tcss +50 -0
- contextforge/tui/widgets/__init__.py +0 -0
- contextforge/tui/widgets/session_detail.py +191 -0
- contextforge/tui/widgets/session_table.py +277 -0
- contextforge/tui/widgets/stats_panel.py +340 -0
- contextforge/tui/widgets/status_bar.py +102 -0
- contextforge/tui/widgets/tokens_panel.py +134 -0
- contextforge/tui/widgets/transfer_panel.py +119 -0
- contextforge/utils/__init__.py +0 -0
- contextforge/utils/display.py +105 -0
- contextforge/utils/tokens.py +17 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Rich console helpers."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
err_console = Console(stderr=True)
|
|
12
|
+
|
|
13
|
+
TOOL_COLORS = {
|
|
14
|
+
"claude_code": "cyan",
|
|
15
|
+
"codex": "green",
|
|
16
|
+
"altimate_code": "magenta",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
TOOL_SHORT = {
|
|
20
|
+
"claude_code": "claude",
|
|
21
|
+
"codex": "codex",
|
|
22
|
+
"altimate_code": "altimate",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def tool_color(tool: str) -> str:
|
|
27
|
+
return TOOL_COLORS.get(tool, "white")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _clean_title(raw: str, max_len: int = 42) -> str:
|
|
31
|
+
"""Return a single-line, truncated title from raw session display text."""
|
|
32
|
+
# Take only the first non-empty line
|
|
33
|
+
first_line = next((l.strip() for l in raw.splitlines() if l.strip()), raw.strip())
|
|
34
|
+
# Collapse internal whitespace
|
|
35
|
+
first_line = re.sub(r"\s+", " ", first_line)
|
|
36
|
+
if len(first_line) > max_len:
|
|
37
|
+
return first_line[: max_len - 1] + "…"
|
|
38
|
+
return first_line
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _project_name(cwd: str) -> str:
|
|
42
|
+
"""Return just the last two path components of a CWD."""
|
|
43
|
+
if not cwd:
|
|
44
|
+
return ""
|
|
45
|
+
parts = cwd.rstrip("/").split("/")
|
|
46
|
+
if len(parts) >= 2:
|
|
47
|
+
return "/".join(parts[-2:])
|
|
48
|
+
return parts[-1]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def sessions_table(rows: list[dict]) -> Table:
|
|
52
|
+
table = Table(
|
|
53
|
+
show_header=True,
|
|
54
|
+
header_style="bold",
|
|
55
|
+
show_lines=True, # horizontal rule between every row
|
|
56
|
+
expand=False,
|
|
57
|
+
)
|
|
58
|
+
table.add_column("ID", style="dim", no_wrap=True, width=13)
|
|
59
|
+
table.add_column("Tool", no_wrap=True, width=10)
|
|
60
|
+
table.add_column("Title", no_wrap=True, max_width=44)
|
|
61
|
+
table.add_column("Project", no_wrap=True, max_width=22, style="dim")
|
|
62
|
+
table.add_column("Tokens", justify="right", no_wrap=True, width=8)
|
|
63
|
+
table.add_column("Updated", no_wrap=True, width=16)
|
|
64
|
+
table.add_column("Summary", no_wrap=True, max_width=45, style="dim")
|
|
65
|
+
|
|
66
|
+
for row in rows:
|
|
67
|
+
tool = row.get("tool", "")
|
|
68
|
+
color = tool_color(tool)
|
|
69
|
+
tool_label = f"[{color}]{TOOL_SHORT.get(tool, tool)}[/{color}]"
|
|
70
|
+
|
|
71
|
+
session_id = str(row.get("id", ""))[:13]
|
|
72
|
+
title = _clean_title(row.get("title") or "")
|
|
73
|
+
project = _project_name(row.get("cwd") or "")
|
|
74
|
+
|
|
75
|
+
raw_tokens = row.get("token_count")
|
|
76
|
+
if raw_tokens and raw_tokens > 1_000_000:
|
|
77
|
+
tokens = f"{raw_tokens / 1_000_000:.1f}M"
|
|
78
|
+
elif raw_tokens and raw_tokens > 1_000:
|
|
79
|
+
tokens = f"{raw_tokens // 1000}k"
|
|
80
|
+
elif raw_tokens:
|
|
81
|
+
tokens = str(raw_tokens)
|
|
82
|
+
else:
|
|
83
|
+
tokens = ""
|
|
84
|
+
|
|
85
|
+
updated_ms = row.get("updated_at") or 0
|
|
86
|
+
try:
|
|
87
|
+
updated_dt = datetime.fromtimestamp(updated_ms / 1000, tz=timezone.utc)
|
|
88
|
+
updated = updated_dt.strftime("%m-%d %H:%M")
|
|
89
|
+
except Exception:
|
|
90
|
+
updated = "?"
|
|
91
|
+
|
|
92
|
+
summary_raw = row.get("summary") or ""
|
|
93
|
+
summary = _clean_title(summary_raw, max_len=45)
|
|
94
|
+
|
|
95
|
+
table.add_row(
|
|
96
|
+
session_id,
|
|
97
|
+
tool_label,
|
|
98
|
+
title,
|
|
99
|
+
project,
|
|
100
|
+
tokens,
|
|
101
|
+
updated,
|
|
102
|
+
summary,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return table
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Token counting utilities using tiktoken."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import tiktoken
|
|
5
|
+
|
|
6
|
+
_ENCODING = tiktoken.get_encoding("cl100k_base")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def count_tokens(text: str) -> int:
|
|
10
|
+
return len(_ENCODING.encode(text))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def truncate_to_budget(text: str, budget: int) -> str:
|
|
14
|
+
tokens = _ENCODING.encode(text)
|
|
15
|
+
if len(tokens) <= budget:
|
|
16
|
+
return text
|
|
17
|
+
return _ENCODING.decode(tokens[:budget])
|